archive.c 11.8 KB
Newer Older
1 2 3
/* archive.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
4
 * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
5 6
 * 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
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/wait.h>
Natanael Copa's avatar
Natanael Copa committed
22
#include <sys/stat.h>
23
#include <limits.h>
Natanael Copa's avatar
Natanael Copa committed
24
#include <stdint.h>
Pierre Carrier's avatar
Pierre Carrier committed
25
#include <stdlib.h>
26 27

#include "apk_defines.h"
Natanael Copa's avatar
Natanael Copa committed
28
#include "apk_print.h"
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#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 */
48
	char padding[12];   /* 500-511 */
49 50
};

51 52 53 54
struct apk_tar_digest_info {
	char id[4];
	uint16_t nid;
	uint16_t size;
55
	unsigned char digest[];
56 57
};

Timo Teräs's avatar
Timo Teräs committed
58 59 60
#define GET_OCTAL(s)	get_octal(s, sizeof(s))
#define PUT_OCTAL(s,v)	put_octal(s, sizeof(s), v)

61 62 63
static int get_octal(char *s, size_t l)
{
	apk_blob_t b = APK_BLOB_PTR_LEN(s, l);
64
	return apk_blob_pull_uint(&b, 8);
65
}
66

Timo Teräs's avatar
Timo Teräs committed
67 68 69 70 71 72 73 74 75 76 77 78 79
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';
}

80 81 82 83
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
84 85
	EVP_MD_CTX mdctx;
	struct apk_checksum *csum;
86
};
87

88
static ssize_t tar_entry_read(void *stream, void *ptr, size_t size)
89
{
90 91
	struct apk_tar_entry_istream *teis =
		container_of(stream, struct apk_tar_entry_istream, is);
92
	ssize_t r;
93 94 95

	if (size > teis->bytes_left)
		size = teis->bytes_left;
96 97 98
        if (size == 0)
                return 0;

99 100 101
	r = teis->tar_is->read(teis->tar_is, ptr, size);
	if (r < 0)
		return r;
102

103
	teis->bytes_left -= r;
104
	if (teis->csum == NULL)
105
		return r;
106

107
	EVP_DigestUpdate(&teis->mdctx, ptr, r);
108 109 110
	if (teis->bytes_left == 0) {
		teis->csum->type = EVP_MD_CTX_size(&teis->mdctx);
		EVP_DigestFinal_ex(&teis->mdctx, teis->csum->data, NULL);
111
	}
112
	return r;
113 114
}

115 116 117 118
static void tar_entry_close(void *stream)
{
}

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
static int blob_realloc(apk_blob_t *b, int newsize)
{
	char *tmp;
	if (b->len >= newsize) return 0;
	tmp = realloc(b->ptr, newsize);
	if (!tmp) return -ENOMEM;
	b->ptr = tmp;
	b->len = newsize;
	return 0;
}

static void handle_extended_header(struct apk_file_info *fi, apk_blob_t hdr)
{
	apk_blob_t name, value;

	while (1) {
		char *start = hdr.ptr;
		unsigned int len = apk_blob_pull_uint(&hdr, 10);
		apk_blob_pull_char(&hdr, ' ');
		if (!apk_blob_split(hdr, APK_BLOB_STR("="), &name, &hdr)) break;
		len -= hdr.ptr - start + 1;
		if (len < 0 || hdr.len < len) break;
		value = APK_BLOB_PTR_LEN(hdr.ptr, len);
		hdr = APK_BLOB_PTR_LEN(hdr.ptr+len, hdr.len-len);
		apk_blob_pull_char(&hdr, '\n');
		if (APK_BLOB_IS_NULL(hdr)) break;
		value.ptr[value.len] = 0;

		if (apk_blob_compare(name, APK_BLOB_STR("path")) == 0) {
			fi->name = value.ptr;
		} else if (apk_blob_compare(name, APK_BLOB_STR("linkpath")) == 0) {
			fi->link_target = value.ptr;
		} else if (apk_blob_pull_blob_match(&name, APK_BLOB_STR("APK-TOOLS.checksum."))) {
			int type = APK_CHECKSUM_NONE;
			if (apk_blob_compare(name, APK_BLOB_STR("SHA1")) == 0)
				type = APK_CHECKSUM_SHA1;
			else if (apk_blob_compare(name, APK_BLOB_STR("MD5")) == 0)
				type = APK_CHECKSUM_MD5;
			if (type > fi->csum.type) {
				fi->csum.type = type;
				apk_blob_pull_hexdump(&value, APK_BLOB_CSUM(fi->csum));
				if (APK_BLOB_IS_NULL(value)) fi->csum.type = APK_CHECKSUM_NONE;
			}
		}
	}
}

166
int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
167
		  void *ctx, int soft_checksums, struct apk_id_cache *idc)
168
{
169
	struct apk_file_info entry;
170 171
	struct apk_tar_entry_istream teis = {
		.is.read = tar_entry_read,
172
		.is.close = tar_entry_close,
173 174
		.tar_is = is,
	};
175
	struct tar_header buf;
176
	struct apk_tar_digest_info *odi;
177 178
	unsigned long offset = 0;
	int end = 0, r;
179 180
	size_t toskip, paxlen = 0;
	apk_blob_t pax = APK_BLOB_NULL, longname = APK_BLOB_NULL;
181

182
	odi = (struct apk_tar_digest_info *) &buf.linkname[3];
Timo Teräs's avatar
Timo Teräs committed
183
	EVP_MD_CTX_init(&teis.mdctx);
184
	memset(&entry, 0, sizeof(entry));
185
	while ((r = is->read(is, &buf, 512)) == 512) {
186 187
		offset += 512;
		if (buf.name[0] == '\0') {
188
			if (end) break;
189 190 191 192
			end++;
			continue;
		}

193
		entry = (struct apk_file_info){
194
			.size  = GET_OCTAL(buf.size),
195 196
			.uid   = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
			.gid   = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
197
			.mode  = GET_OCTAL(buf.mode) & 07777,
198
			.mtime = GET_OCTAL(buf.mtime),
199
			.name  = entry.name ?: buf.name,
200 201 202 203 204
			.uname = buf.uname,
			.gname = buf.gname,
			.device = makedev(GET_OCTAL(buf.devmajor),
					  GET_OCTAL(buf.devminor)),
		};
205 206
		buf.mode[0] = 0; /* to nul terminate 100-byte buf.name */
		buf.magic[0] = 0; /* to nul terminate 100-byte buf.linkname */
207
		teis.csum = NULL;
208

209
		if (paxlen) handle_extended_header(&entry, APK_BLOB_PTR_LEN(pax.ptr, paxlen));
210

211
		switch (buf.typeflag) {
212 213 214
		case 'L': /* GNU long name extension */
			if (blob_realloc(&longname, entry.size+1)) goto err_nomem;
			entry.name = longname.ptr;
215
			is->read(is, entry.name, entry.size);
Timo Teräs's avatar
Timo Teräs committed
216
			entry.name[entry.size] = 0;
217 218 219
			offset += entry.size;
			entry.size = 0;
			break;
220 221
		case 'K': /* GNU long link target extension - ignored */
			break;
222 223
		case '0':
		case '7': /* regular file */
224 225 226 227
			if (entry.csum.type == APK_CHECKSUM_NONE) {
				if (memcmp(odi->id, "APK2", 4) == 0 &&
				    odi->size <= sizeof(entry.csum.data)) {
					entry.csum.type = odi->size;
228
					memcpy(entry.csum.data, odi->digest, odi->size);
229 230
				} else if (soft_checksums)
					teis.csum = &entry.csum;
231
			}
232
			entry.mode |= S_IFREG;
233 234 235 236 237 238 239 240
			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;
241
			if (entry.csum.type == APK_CHECKSUM_NONE && soft_checksums) {
242 243 244 245 246
				EVP_Digest(buf.linkname, strlen(buf.linkname),
					   entry.csum.data, NULL,
					   apk_checksum_default(), NULL);
				entry.csum.type = APK_CHECKSUM_DEFAULT;
			}
247 248 249 250
			break;
		case '3': /* char device */
			entry.mode |= S_IFCHR;
			break;
Timo Teräs's avatar
Timo Teräs committed
251
		case '4': /* block device */
252 253 254 255 256
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
257 258 259 260 261 262 263 264 265
		case 'g': /* global pax header */
			break;
		case 'x': /* file specific pax header */
			paxlen = entry.size;
			entry.size = 0;
			if (blob_realloc(&pax, (paxlen + 511) & -512)) goto err_nomem;
			is->read(is, pax.ptr, paxlen);
			offset += paxlen;
			break;
266 267 268 269
		default:
			break;
		}

270
		teis.bytes_left = entry.size;
271 272
		if (entry.mode & S_IFMT) {
			/* callback parser function */
273 274
			if (teis.csum != NULL)
				EVP_DigestInit_ex(&teis.mdctx,
275
						  apk_checksum_default(), NULL);
276

277
			r = parser(ctx, &entry, &teis.is);
278
			if (r != 0) goto err;
279

280 281
			entry.name = buf.name;
			paxlen = 0;
282 283
		}

284 285 286 287 288 289 290 291
		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);
	}
292

293 294 295
	/* Read remaining end-of-archive records, to ensure we read all of
	 * the file. The underlying istream is likely doing checksumming. */
	if (r == 512) {
Timo Teräs's avatar
Timo Teräs committed
296
		while ((r = is->read(is, &buf, 512)) == 512) {
297
			if (buf.name[0] != 0) break;
Timo Teräs's avatar
Timo Teräs committed
298
		}
299 300
	}

301 302
	/* Check that there was no partial (or non-zero) record */
	if (r > 0) r = -EBADMSG;
Timo Teräs's avatar
Timo Teräs committed
303 304 305

err:
	EVP_MD_CTX_cleanup(&teis.mdctx);
306 307
	free(pax.ptr);
	free(longname.ptr);
Timo Teräs's avatar
Timo Teräs committed
308
	return r;
309 310 311 312

err_nomem:
	r = -ENOMEM;
	goto err;
Timo Teräs's avatar
Timo Teräs committed
313 314
}

315 316
int apk_tar_write_entry(struct apk_ostream *os, const struct apk_file_info *ae,
			const char *data)
Timo Teräs's avatar
Timo Teräs committed
317 318 319 320 321 322 323 324 325 326 327 328 329
{
	struct tar_header buf;

	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;

330 331 332 333 334
		if (ae->name != NULL)
			strncpy(buf.name, ae->name, sizeof(buf.name));

		strncpy(buf.uname, ae->uname ?: "root", sizeof(buf.uname));
		strncpy(buf.gname, ae->gname ?: "root", sizeof(buf.gname));
Timo Teräs's avatar
Timo Teräs committed
335 336 337 338 339

		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);
340
		PUT_OCTAL(buf.mtime, ae->mtime ?: time(NULL));
Timo Teräs's avatar
Timo Teräs committed
341 342 343 344 345 346 347 348 349 350 351 352 353

		/* 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;

354 355 356 357 358
	if (ae == NULL) {
		/* End-of-archive is two empty headers */
		if (os->write(os, &buf, sizeof(buf)) != sizeof(buf))
			return -1;
	} else if (data != NULL) {
Timo Teräs's avatar
Timo Teräs committed
359 360
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
361
		if (apk_tar_write_padding(os, ae) != 0)
Timo Teräs's avatar
Timo Teräs committed
362 363 364
			return -1;
	}

365 366 367
	return 0;
}

368 369 370 371 372 373 374 375 376 377 378 379 380
int apk_tar_write_padding(struct apk_ostream *os, const struct apk_file_info *ae)
{
	static char padding[512];
	int pad;

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

	return 0;
}

381 382 383
int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
			      const char *suffix, struct apk_istream *is,
			      apk_progress_cb cb, void *cb_ctx)
384
{
385 386
	char *fn = ae->name;
	int fd, r = -1, atflags = 0;
387

388 389 390 391
	if (suffix != NULL) {
		fn = alloca(PATH_MAX);
		snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix);
	}
392 393 394 395

	if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) ||
	    (ae->link_target != NULL)) {
		/* non-standard entries need to be deleted first */
396
		unlinkat(atfd, fn, 0);
397
	}
398 399 400

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
401
		r = mkdirat(atfd, fn, ae->mode & 07777);
402 403 404 405 406
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
407
			int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
408 409

			fd = openat(atfd, fn, flags, ae->mode & 07777);
410 411
			if (fd < 0) {
				r = -1;
412
				break;
413
			}
414
			if (apk_istream_splice(is, fd, ae->size, cb, cb_ctx) == ae->size)
415 416
				r = 0;
			close(fd);
417
		} else {
418 419 420 421 422 423 424
			char *link_target = ae->link_target;
			if (suffix != NULL) {
				link_target = alloca(PATH_MAX);
				snprintf(link_target, PATH_MAX, "%s%s",
					 ae->link_target, suffix);
			}
			r = linkat(atfd, link_target, atfd, fn, 0);
425 426 427
		}
		break;
	case S_IFLNK:
428 429
		r = symlinkat(ae->link_target, atfd, fn);
		atflags |= AT_SYMLINK_NOFOLLOW;
430 431 432 433 434
		break;
	case S_IFSOCK:
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
435
		r = mknodat(atfd, fn, ae->mode & 07777, ae->device);
436 437
		break;
	}
438
	if (r == 0) {
439
		r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
440 441 442 443 444 445
		if (r < 0) {
			apk_error("Failed to set ownership on %s: %s",
				  fn, strerror(errno));
			return -errno;
		}

446
		/* chown resets suid bit so we need set it again */
447
		if (ae->mode & 07000) {
448
			r = fchmodat(atfd, fn, ae->mode & 07777, atflags);
449 450 451 452 453 454 455 456
			if (r < 0) {
				apk_error("Failed to set file permissions "
					  "on %s: %s",
					  fn, strerror(errno));
				return -errno;
			}
		}

457 458
		if (!S_ISLNK(ae->mode)) {
			/* preserve modification time */
459 460 461 462 463
			struct timespec times[2];

			times[0].tv_sec  = times[1].tv_sec  = ae->mtime;
			times[0].tv_nsec = times[1].tv_nsec = 0;
			r = utimensat(atfd, fn, times, atflags);
464 465 466 467 468
			if (r < 0) {
				apk_error("Failed to preserve modification time on %s: %s",
					fn, strerror(errno));
				return -errno;
			}
469
		}
470
	} else {
471 472
		apk_error("Failed to extract %s: %s", ae->name, strerror(errno));
		return -errno;
473
	}
474
	return 0;
475
}