archive.c 7.59 KB
Newer Older
1
2
3
4
5
6
/* archive.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
 * Copyright (C) 2008 Timo Teräs <timo.teras@iki.fi>
 * All rights reserved.
 *
7
 * This program is free software; you can redistribute it and/or modify it
8
9
10
11
12
13
14
15
16
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation. See http://www.gnu.org/ for details.
 */

#include <stdio.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
17
#include <utime.h>
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/wait.h>

#include "apk_defines.h"
#include "apk_archive.h"

struct tar_header {
	/* ustar header, Posix 1003.1 */
	char name[100];     /*   0-99 */
	char mode[8];       /* 100-107 */
	char uid[8];        /* 108-115 */
	char gid[8];        /* 116-123 */
	char size[12];      /* 124-135 */
	char mtime[12];     /* 136-147 */
	char chksum[8];     /* 148-155 */
	char typeflag;      /* 156-156 */
	char linkname[100]; /* 157-256 */
	char magic[8];      /* 257-264 */
	char uname[32];     /* 265-296 */
	char gname[32];     /* 297-328 */
	char devmajor[8];   /* 329-336 */
	char devminor[8];   /* 337-344 */
	char prefix[155];   /* 345-499 */
	char padding[12];   /* 500-512 */
};

Timo Teräs's avatar
Timo Teräs committed
47
48
49
#define GET_OCTAL(s)	get_octal(s, sizeof(s))
#define PUT_OCTAL(s,v)	put_octal(s, sizeof(s), v)

50
51
52
static int get_octal(char *s, size_t l)
{
	apk_blob_t b = APK_BLOB_PTR_LEN(s, l);
53
	return apk_blob_pull_uint(&b, 8);
54
}
55

Timo Teräs's avatar
Timo Teräs committed
56
57
58
59
60
61
62
63
64
65
66
67
68
static void put_octal(char *s, size_t l, size_t value)
{
	char *ptr = &s[l - 1];

	*(ptr--) = '\0';
	while (value != 0 && ptr >= s) {
		*(ptr--) = '0' + (value % 8);
		value /= 8;
	}
	while (ptr >= s)
		*(ptr--) = '0';
}

69
70
71
72
struct apk_tar_entry_istream {
	struct apk_istream is;
	struct apk_istream *tar_is;
	size_t bytes_left;
Timo Teräs's avatar
Timo Teräs committed
73
74
	EVP_MD_CTX mdctx;
	struct apk_checksum *csum;
75
};
76

77
static size_t tar_entry_read(void *stream, void *ptr, size_t size)
78
{
79
80
81
82
83
84
	struct apk_tar_entry_istream *teis =
		container_of(stream, struct apk_tar_entry_istream, is);

	if (size > teis->bytes_left)
		size = teis->bytes_left;
	size = teis->tar_is->read(teis->tar_is, ptr, size);
85
	if (size > 0) {
86
		teis->bytes_left -= size;
Timo Teräs's avatar
Timo Teräs committed
87
88
89
90
91
		EVP_DigestUpdate(&teis->mdctx, ptr, size);
		if (teis->bytes_left == 0) {
			teis->csum->type = EVP_MD_CTX_size(&teis->mdctx);
			EVP_DigestFinal_ex(&teis->mdctx, teis->csum->data, NULL);
		}
92
	}
93
	return size;
94
95
}

96
97
int apk_parse_tar(struct apk_istream *is, apk_archive_entry_parser parser,
		  void *ctx)
98
{
99
	struct apk_file_info entry;
100
101
102
	struct apk_tar_entry_istream teis = {
		.is.read = tar_entry_read,
		.tar_is = is,
Timo Teräs's avatar
Timo Teräs committed
103
		.csum = &entry.csum,
104
	};
105
106
107
	struct tar_header buf;
	unsigned long offset = 0;
	int end = 0, r;
108
	size_t toskip;
109

Timo Teräs's avatar
Timo Teräs committed
110
	EVP_MD_CTX_init(&teis.mdctx);
111
	memset(&entry, 0, sizeof(entry));
112
	while ((r = is->read(is, &buf, 512)) == 512) {
113
114
		offset += 512;
		if (buf.name[0] == '\0') {
115
116
			if (end) {
				r = 0;
117
				//break;
118
			}
119
120
121
122
			end++;
			continue;
		}

123
		entry = (struct apk_file_info){
124
125
126
			.size  = GET_OCTAL(buf.size),
			.uid   = GET_OCTAL(buf.uid),
			.gid   = GET_OCTAL(buf.gid),
127
			.mode  = GET_OCTAL(buf.mode) & 07777,
128
129
130
131
132
133
134
135
136
137
138
139
140
			.mtime = GET_OCTAL(buf.mtime),
			.name  = entry.name,
			.uname = buf.uname,
			.gname = buf.gname,
			.device = makedev(GET_OCTAL(buf.devmajor),
					  GET_OCTAL(buf.devminor)),
		};

		switch (buf.typeflag) {
		case 'L':
			if (entry.name != NULL)
				free(entry.name);
			entry.name = malloc(entry.size+1);
141
			is->read(is, entry.name, entry.size);
Timo Teräs's avatar
Timo Teräs committed
142
			entry.name[entry.size] = 0;
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
			offset += entry.size;
			entry.size = 0;
			break;
		case '0':
		case '7': /* regular file */
			entry.mode |= S_IFREG;
			break;
		case '1': /* hard link */
			entry.mode |= S_IFREG;
			entry.link_target = buf.linkname;
			break;
		case '2': /* symbolic link */
			entry.mode |= S_IFLNK;
			entry.link_target = buf.linkname;
			break;
		case '3': /* char device */
			entry.mode |= S_IFCHR;
			break;
Timo Teräs's avatar
Timo Teräs committed
161
		case '4': /* block device */
162
163
164
165
166
167
168
169
170
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
		default:
			break;
		}

171
		teis.bytes_left = entry.size;
172
173
174
175
176
		if (entry.mode & S_IFMT) {
			if (entry.name == NULL)
				entry.name = strdup(buf.name);

			/* callback parser function */
Timo Teräs's avatar
Timo Teräs committed
177
			EVP_DigestInit_ex(&teis.mdctx, apk_default_checksum(), NULL);
178
			r = parser(ctx, &entry, &teis.is);
Timo Teräs's avatar
Timo Teräs committed
179
			free(entry.name);
180
			if (r != 0)
Timo Teräs's avatar
Timo Teräs committed
181
				goto err;
182
183
184
185

			entry.name = NULL;
		}

186
187
188
189
190
191
192
193
		offset += entry.size - teis.bytes_left;
		toskip = teis.bytes_left;
		if ((offset + toskip) & 511)
			toskip += 512 - ((offset + toskip) & 511);
		offset += toskip;
		if (toskip != 0)
			is->read(is, NULL, toskip);
	}
Timo Teräs's avatar
Timo Teräs committed
194
	EVP_MD_CTX_cleanup(&teis.mdctx);
195

196
197
198
	if (r != 0) {
		apk_error("Bad TAR header (r=%d)", r);
		return -1;
199
200
	}

Timo Teräs's avatar
Timo Teräs committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
	return 0;

err:
	EVP_MD_CTX_cleanup(&teis.mdctx);
	return r;
}

int apk_write_tar_entry(struct apk_ostream *os, const struct apk_file_info *ae, char *data)
{
	static char padding[512];
	struct tar_header buf;
	int pad;

	memset(&buf, 0, sizeof(buf));
	if (ae != NULL) {
		const unsigned char *src;
	        int chksum, i;

		if (S_ISREG(ae->mode))
			buf.typeflag = '0';
		else
			return -1;

		strncpy(buf.name, ae->name, sizeof(buf.name));
		strncpy(buf.uname, ae->uname, sizeof(buf.uname));
		strncpy(buf.gname, ae->gname, sizeof(buf.gname));

		PUT_OCTAL(buf.size, ae->size);
		PUT_OCTAL(buf.uid, ae->uid);
		PUT_OCTAL(buf.gid, ae->gid);
		PUT_OCTAL(buf.mode, ae->mode & 07777);
		PUT_OCTAL(buf.mtime, ae->mtime);

		/* Checksum */
		strcpy(buf.magic, "ustar  ");
		memset(buf.chksum, ' ', sizeof(buf.chksum));
		src = (const unsigned char *) &buf;
		for (i = chksum = 0; i < sizeof(buf); i++)
			chksum += src[i];
	        put_octal(buf.chksum, sizeof(buf.chksum)-1, chksum);
	}

	if (os->write(os, &buf, sizeof(buf)) != sizeof(buf))
		return -1;

	if (data != NULL) {
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
		pad = 512 - (ae->size & 511);
		if (pad != 512 &&
		    os->write(os, padding, pad) != pad)
			return -1;
	}

255
256
257
	return 0;
}

258
int apk_archive_entry_extract(const struct apk_file_info *ae,
259
			      struct apk_istream *is,
260
261
			      const char *fn, apk_progress_cb cb,
			      void *cb_ctx)
262
{
263
	struct utimbuf utb;
264
	int r = -1, fd;
265
266
267
268
269
270
271
272
273

	if (fn == NULL)
		fn = ae->name;

	/* BIG HONKING FIXME */
	unlink(fn);

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
274
		r = mkdir(fn, ae->mode & 07777);
275
276
277
278
279
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
280
281
282
			fd = open(fn, O_WRONLY | O_CREAT, ae->mode & 07777);
			if (fd < 0) {
				r = -1;
283
				break;
284
			}
285
286
			if (apk_istream_splice(is, fd, ae->size, cb, cb_ctx)
			    == ae->size)
287
288
				r = 0;
			close(fd);
289
290
291
292
293
294
295
296
297
298
299
		} else {
			r = link(ae->link_target, fn);
		}
		break;
	case S_IFLNK:
		r = symlink(ae->link_target, fn);
		break;
	case S_IFSOCK:
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
300
		r = mknod(fn, ae->mode & 07777, ae->device);
301
302
		break;
	}
303
304
305
306
307
	if (r == 0) {
		if (!S_ISLNK(ae->mode))
			r = chown(fn, ae->uid, ae->gid);
		else
			r = lchown(fn, ae->uid, ae->gid);
308
309
310
311
312
313
		if (r < 0) {
			apk_error("Failed to set ownership on %s: %s",
				  fn, strerror(errno));
			return -errno;
		}

314
		/* chown resets suid bit so we need set it again */
315
		if (ae->mode & 07000) {
316
			r = chmod(fn, ae->mode & 07777);
317
318
319
320
321
322
323
324
			if (r < 0) {
				apk_error("Failed to set file permissions "
					  "on %s: %s",
					  fn, strerror(errno));
				return -errno;
			}
		}

325
326
327
328
329
330
331
332
333
		if (!S_ISLNK(ae->mode)) {
			/* preserve modification time */
			utb.actime = utb.modtime = ae->mtime;
			r = utime(fn, &utb);
			if (r < 0) {
				apk_error("Failed to preserve modification time on %s: %s",
					fn, strerror(errno));
				return -errno;
			}
334
		}
335
	} else {
336
337
		apk_error("Failed to extract %s: %s", ae->name, strerror(errno));
		return -errno;
338
	}
339
	return 0;
340
}