archive.c 13.6 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>
Timo Teräs's avatar
Timo Teräs committed
23
#include <sys/xattr.h>
24
#include <limits.h>
Natanael Copa's avatar
Natanael Copa committed
25
#include <stdint.h>
Pierre Carrier's avatar
Pierre Carrier committed
26
#include <stdlib.h>
27 28

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

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

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

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

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

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

90 91 92 93 94 95 96 97 98 99
static void tar_entry_get_meta(void *stream, struct apk_file_meta *meta)
{
	struct apk_tar_entry_istream *teis =
		container_of(stream, struct apk_tar_entry_istream, is);
	*meta = (struct apk_file_meta) {
		.atime = teis->mtime,
		.mtime = teis->mtime,
	};
}

100
static ssize_t tar_entry_read(void *stream, void *ptr, size_t size)
101
{
102 103
	struct apk_tar_entry_istream *teis =
		container_of(stream, struct apk_tar_entry_istream, is);
104
	ssize_t r;
105 106 107

	if (size > teis->bytes_left)
		size = teis->bytes_left;
108 109
	if (size == 0)
		return 0;
110

111
	r = teis->tar_is->read(teis->tar_is, ptr, size);
112 113 114 115 116
	if (r <= 0) {
		/* If inner stream returned zero (end-of-stream), we
		 * are getting short read, because tar header indicated
		 * more was to be expected. */
		if (r == 0) return -ECONNABORTED;
117
		return r;
118
	}
119

120
	teis->bytes_left -= r;
121
	if (teis->csum == NULL)
122
		return r;
123

124
	EVP_DigestUpdate(&teis->mdctx, ptr, r);
125 126 127
	if (teis->bytes_left == 0) {
		teis->csum->type = EVP_MD_CTX_size(&teis->mdctx);
		EVP_DigestFinal_ex(&teis->mdctx, teis->csum->data, NULL);
128
	}
129
	return r;
130 131
}

132 133 134 135
static void tar_entry_close(void *stream)
{
}

136
static int blob_realloc(apk_blob_t *b, size_t newsize)
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 166 167
{
	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;
Timo Teräs's avatar
Timo Teräs committed
168 169 170 171 172 173
		} else if (apk_blob_pull_blob_match(&name, APK_BLOB_STR("SCHILY.xattr."))) {
			name.ptr[name.len] = 0;
			*apk_xattr_array_add(&fi->xattrs) = (struct apk_xattr) {
				.name = name.ptr,
				.value = value,
			};
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
		} 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;
			}
		}
	}
}

189
int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
190
		  void *ctx, int soft_checksums, struct apk_id_cache *idc)
191
{
192
	struct apk_file_info entry;
193
	struct apk_tar_entry_istream teis = {
194
		.is.get_meta = tar_entry_get_meta,
195
		.is.read = tar_entry_read,
196
		.is.close = tar_entry_close,
197 198
		.tar_is = is,
	};
199
	struct tar_header buf;
200
	struct apk_tar_digest_info *odi;
201 202
	unsigned long offset = 0;
	int end = 0, r;
203 204
	size_t toskip, paxlen = 0;
	apk_blob_t pax = APK_BLOB_NULL, longname = APK_BLOB_NULL;
205

206
	odi = (struct apk_tar_digest_info *) &buf.linkname[3];
Timo Teräs's avatar
Timo Teräs committed
207
	EVP_MD_CTX_init(&teis.mdctx);
208
	memset(&entry, 0, sizeof(entry));
209
	while ((r = is->read(is, &buf, 512)) == 512) {
210 211
		offset += 512;
		if (buf.name[0] == '\0') {
212
			if (end) break;
213 214 215 216
			end++;
			continue;
		}

217
		entry = (struct apk_file_info){
218
			.size  = GET_OCTAL(buf.size),
219 220
			.uid   = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
			.gid   = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
221
			.mode  = GET_OCTAL(buf.mode) & 07777,
222
			.mtime = GET_OCTAL(buf.mtime),
223
			.name  = entry.name ?: buf.name,
224 225 226 227
			.uname = buf.uname,
			.gname = buf.gname,
			.device = makedev(GET_OCTAL(buf.devmajor),
					  GET_OCTAL(buf.devminor)),
Timo Teräs's avatar
Timo Teräs committed
228
			.xattrs = entry.xattrs,
229
		};
230 231
		buf.mode[0] = 0; /* to nul terminate 100-byte buf.name */
		buf.magic[0] = 0; /* to nul terminate 100-byte buf.linkname */
232
		teis.csum = NULL;
233
		teis.mtime = entry.mtime;
Timo Teräs's avatar
Timo Teräs committed
234
		apk_xattr_array_resize(&entry.xattrs, 0);
235

236 237
		if (entry.size >= SSIZE_MAX-512) goto err;

238 239 240 241
		if (paxlen) {
			handle_extended_header(&entry, APK_BLOB_PTR_LEN(pax.ptr, paxlen));
			apk_fileinfo_hash_xattr(&entry);
		}
242

243
		switch (buf.typeflag) {
244
		case 'L': /* GNU long name extension */
245 246 247
			if ((r = blob_realloc(&longname, entry.size+1)) != 0 ||
			    (r = is->read(is, longname.ptr, entry.size)) != entry.size)
				goto err;
248
			entry.name = longname.ptr;
Timo Teräs's avatar
Timo Teräs committed
249
			entry.name[entry.size] = 0;
250 251 252
			offset += entry.size;
			entry.size = 0;
			break;
253 254
		case 'K': /* GNU long link target extension - ignored */
			break;
255 256
		case '0':
		case '7': /* regular file */
257 258 259 260
			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;
261
					memcpy(entry.csum.data, odi->digest, odi->size);
262 263
				} else if (soft_checksums)
					teis.csum = &entry.csum;
264
			}
265
			entry.mode |= S_IFREG;
266 267 268
			break;
		case '1': /* hard link */
			entry.mode |= S_IFREG;
269
			if (!entry.link_target) entry.link_target = buf.linkname;
270 271 272
			break;
		case '2': /* symbolic link */
			entry.mode |= S_IFLNK;
273
			if (!entry.link_target) entry.link_target = buf.linkname;
274
			if (entry.csum.type == APK_CHECKSUM_NONE && soft_checksums) {
275 276 277 278 279
				EVP_Digest(buf.linkname, strlen(buf.linkname),
					   entry.csum.data, NULL,
					   apk_checksum_default(), NULL);
				entry.csum.type = APK_CHECKSUM_DEFAULT;
			}
280 281 282 283
			break;
		case '3': /* char device */
			entry.mode |= S_IFCHR;
			break;
Timo Teräs's avatar
Timo Teräs committed
284
		case '4': /* block device */
285 286 287 288 289
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
290 291 292 293 294
		case 'g': /* global pax header */
			break;
		case 'x': /* file specific pax header */
			paxlen = entry.size;
			entry.size = 0;
295 296 297
			if ((r = blob_realloc(&pax, (paxlen + 511) & -512)) != 0 ||
			    (r = is->read(is, pax.ptr, paxlen)) != paxlen)
				goto err;
298 299
			offset += paxlen;
			break;
300 301 302 303
		default:
			break;
		}

304
		teis.bytes_left = entry.size;
305 306
		if (entry.mode & S_IFMT) {
			/* callback parser function */
307 308
			if (teis.csum != NULL)
				EVP_DigestInit_ex(&teis.mdctx,
309
						  apk_checksum_default(), NULL);
310

311
			r = parser(ctx, &entry, &teis.is);
312
			if (r != 0) goto err;
313

314 315
			entry.name = buf.name;
			paxlen = 0;
316 317
		}

318 319 320 321 322
		offset += entry.size - teis.bytes_left;
		toskip = teis.bytes_left;
		if ((offset + toskip) & 511)
			toskip += 512 - ((offset + toskip) & 511);
		offset += toskip;
323 324 325 326
		if (toskip != 0) {
			if ((r = is->read(is, NULL, toskip)) != toskip)
				goto err;
		}
327
	}
328

329 330 331
	/* 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
332
		while ((r = is->read(is, &buf, 512)) == 512) {
333
			if (buf.name[0] != 0) break;
Timo Teräs's avatar
Timo Teräs committed
334
		}
335
	}
336
	if (r == 0) goto ok;
Timo Teräs's avatar
Timo Teräs committed
337
err:
338 339 340
	/* Check that there was no partial (or non-zero) record */
	if (r >= 0) r = -EBADMSG;
ok:
Timo Teräs's avatar
Timo Teräs committed
341
	EVP_MD_CTX_cleanup(&teis.mdctx);
342 343
	free(pax.ptr);
	free(longname.ptr);
344
	apk_fileinfo_free(&entry);
Timo Teräs's avatar
Timo Teräs committed
345 346 347
	return r;
}

348 349
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
350 351 352 353 354 355 356 357 358 359 360 361 362
{
	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;

363 364 365 366 367
		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
368 369 370 371 372

		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);
373
		PUT_OCTAL(buf.mtime, ae->mtime ?: time(NULL));
Timo Teräs's avatar
Timo Teräs committed
374 375 376 377 378 379 380 381 382 383 384 385 386

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

387 388 389 390 391
	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
392 393
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
394
		if (apk_tar_write_padding(os, ae) != 0)
Timo Teräs's avatar
Timo Teräs committed
395 396 397
			return -1;
	}

398 399 400
	return 0;
}

401 402 403 404 405 406 407 408 409 410 411 412 413
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;
}

414 415 416
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)
417
{
Timo Teräs's avatar
Timo Teräs committed
418
	struct apk_xattr *xattr;
419
	char *fn = ae->name;
420
	int fd, r = -1, atflags = 0, ret = 0;
421

422 423 424 425
	if (suffix != NULL) {
		fn = alloca(PATH_MAX);
		snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix);
	}
426 427 428 429

	if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) ||
	    (ae->link_target != NULL)) {
		/* non-standard entries need to be deleted first */
430
		unlinkat(atfd, fn, 0);
431
	}
432 433 434

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
435
		r = mkdirat(atfd, fn, ae->mode & 07777);
436 437
		if (r < 0 && errno != EEXIST)
			ret = -errno;
438 439 440
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
441
			int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
442 443

			fd = openat(atfd, fn, flags, ae->mode & 07777);
444
			if (fd < 0) {
445
				ret = -errno;
446
				break;
447
			}
448 449
			r = apk_istream_splice(is, fd, ae->size, cb, cb_ctx);
			if (r != ae->size) ret = r < 0 ? r : -ENOSPC;
450
			close(fd);
451
		} else {
452 453 454 455 456 457 458
			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);
459
			if (r < 0) ret = -errno;
460 461 462
		}
		break;
	case S_IFLNK:
463
		r = symlinkat(ae->link_target, atfd, fn);
464
		if (r < 0) ret = -errno;
465
		atflags |= AT_SYMLINK_NOFOLLOW;
466 467 468 469 470
		break;
	case S_IFSOCK:
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
471
		r = mknodat(atfd, fn, ae->mode & 07777, ae->device);
472
		if (r < 0) ret = -errno;
473 474
		break;
	}
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
	if (ret) {
		apk_error("Failed to create %s: %s", ae->name, strerror(-ret));
		return ret;
	}

	r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
	if (r < 0) {
		apk_error("Failed to set ownership on %s: %s",
			  fn, strerror(errno));
		if (!ret) ret = -errno;
	}

	/* chown resets suid bit so we need set it again */
	if (ae->mode & 07000) {
		r = fchmodat(atfd, fn, ae->mode & 07777, atflags);
490
		if (r < 0) {
491 492
			apk_error("Failed to set file permissions "
				  "on %s: %s",
493
				  fn, strerror(errno));
494
			if (!ret) ret = -errno;
495
		}
496
	}
497

498
	/* extract xattrs */
499
	if (!S_ISLNK(ae->mode) && ae->xattrs && ae->xattrs->num) {
500 501 502 503 504 505 506
		r = 0;
		fd = openat(atfd, fn, O_RDWR);
		if (fd >= 0) {
			foreach_array_item(xattr, ae->xattrs) {
				if (fsetxattr(fd, xattr->name, xattr->value.ptr, xattr->value.len, 0) < 0) {
					r = -errno;
					if (r != -ENOTSUP) break;
Timo Teräs's avatar
Timo Teräs committed
507 508
				}
			}
509 510 511 512 513 514
			close(fd);
		} else {
			r = -errno;
		}
		if (r) {
			if (r != -ENOTSUP)
Timo Teräs's avatar
Timo Teräs committed
515
				apk_error("Failed to set xattrs on %s: %s",
516
					  fn, strerror(-r));
517
			if (!ret) ret = r;
Timo Teräs's avatar
Timo Teräs committed
518
		}
519
	}
Timo Teräs's avatar
Timo Teräs committed
520

521 522 523 524 525 526 527 528 529 530 531
	if (!S_ISLNK(ae->mode)) {
		/* preserve modification time */
		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);
		if (r < 0) {
			apk_error("Failed to preserve modification time on %s: %s",
				fn, strerror(errno));
			if (!ret || ret == -ENOTSUP) ret = -errno;
532
		}
533
	}
534

535
	return ret;
536
}