archive.c 12.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 63 64
static int get_octal(char *s, size_t l)
{
	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
};
88

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

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

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

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

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

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

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
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;
Timo Teräs's avatar
Timo Teräs committed
152 153 154 155 156 157
		} 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,
			};
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
		} 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;
			}
		}
	}
}

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

189
	odi = (struct apk_tar_digest_info *) &buf.linkname[3];
Timo Teräs's avatar
Timo Teräs committed
190
	EVP_MD_CTX_init(&teis.mdctx);
191
	memset(&entry, 0, sizeof(entry));
192
	while ((r = is->read(is, &buf, 512)) == 512) {
193 194
		offset += 512;
		if (buf.name[0] == '\0') {
195
			if (end) break;
196 197 198 199
			end++;
			continue;
		}

200
		entry = (struct apk_file_info){
201
			.size  = GET_OCTAL(buf.size),
202 203
			.uid   = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
			.gid   = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
204
			.mode  = GET_OCTAL(buf.mode) & 07777,
205
			.mtime = GET_OCTAL(buf.mtime),
206
			.name  = entry.name ?: buf.name,
207 208 209 210
			.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
211
			.xattrs = entry.xattrs,
212
		};
213 214
		buf.mode[0] = 0; /* to nul terminate 100-byte buf.name */
		buf.magic[0] = 0; /* to nul terminate 100-byte buf.linkname */
215
		teis.csum = NULL;
Timo Teräs's avatar
Timo Teräs committed
216
		apk_xattr_array_resize(&entry.xattrs, 0);
217

218
		if (paxlen) handle_extended_header(&entry, APK_BLOB_PTR_LEN(pax.ptr, paxlen));
219

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

279
		teis.bytes_left = entry.size;
280 281
		if (entry.mode & S_IFMT) {
			/* callback parser function */
282 283
			if (teis.csum != NULL)
				EVP_DigestInit_ex(&teis.mdctx,
284
						  apk_checksum_default(), NULL);
285

286
			r = parser(ctx, &entry, &teis.is);
287
			if (r != 0) goto err;
288

289 290
			entry.name = buf.name;
			paxlen = 0;
291 292
		}

293 294 295 296 297 298 299 300
		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);
	}
301

302 303 304
	/* 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
305
		while ((r = is->read(is, &buf, 512)) == 512) {
306
			if (buf.name[0] != 0) break;
Timo Teräs's avatar
Timo Teräs committed
307
		}
308 309
	}

310 311
	/* Check that there was no partial (or non-zero) record */
	if (r > 0) r = -EBADMSG;
Timo Teräs's avatar
Timo Teräs committed
312 313 314

err:
	EVP_MD_CTX_cleanup(&teis.mdctx);
315 316
	free(pax.ptr);
	free(longname.ptr);
317
	apk_fileinfo_free(&entry);
Timo Teräs's avatar
Timo Teräs committed
318
	return r;
319 320 321 322

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

325 326
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
327 328 329 330 331 332 333 334 335 336 337 338 339
{
	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;

340 341 342 343 344
		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
345 346 347 348 349

		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);
350
		PUT_OCTAL(buf.mtime, ae->mtime ?: time(NULL));
Timo Teräs's avatar
Timo Teräs committed
351 352 353 354 355 356 357 358 359 360 361 362 363

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

364 365 366 367 368
	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
369 370
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
371
		if (apk_tar_write_padding(os, ae) != 0)
Timo Teräs's avatar
Timo Teräs committed
372 373 374
			return -1;
	}

375 376 377
	return 0;
}

378 379 380 381 382 383 384 385 386 387 388 389 390
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;
}

391 392 393
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)
394
{
Timo Teräs's avatar
Timo Teräs committed
395
	struct apk_xattr *xattr;
396 397
	char *fn = ae->name;
	int fd, r = -1, atflags = 0;
398

399 400 401 402
	if (suffix != NULL) {
		fn = alloca(PATH_MAX);
		snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix);
	}
403 404 405 406

	if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) ||
	    (ae->link_target != NULL)) {
		/* non-standard entries need to be deleted first */
407
		unlinkat(atfd, fn, 0);
408
	}
409 410 411

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
412
		r = mkdirat(atfd, fn, ae->mode & 07777);
413 414 415 416 417
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
418
			int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
419 420

			fd = openat(atfd, fn, flags, ae->mode & 07777);
421 422
			if (fd < 0) {
				r = -1;
423
				break;
424
			}
425
			if (apk_istream_splice(is, fd, ae->size, cb, cb_ctx) == ae->size)
426 427
				r = 0;
			close(fd);
428
		} else {
429 430 431 432 433 434 435
			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);
436 437 438
		}
		break;
	case S_IFLNK:
439 440
		r = symlinkat(ae->link_target, atfd, fn);
		atflags |= AT_SYMLINK_NOFOLLOW;
441 442 443 444 445
		break;
	case S_IFSOCK:
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
446
		r = mknodat(atfd, fn, ae->mode & 07777, ae->device);
447 448
		break;
	}
449
	if (r == 0) {
450
		r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
451 452 453 454 455 456
		if (r < 0) {
			apk_error("Failed to set ownership on %s: %s",
				  fn, strerror(errno));
			return -errno;
		}

457
		/* chown resets suid bit so we need set it again */
458
		if (ae->mode & 07000) {
459
			r = fchmodat(atfd, fn, ae->mode & 07777, atflags);
460 461 462 463 464 465 466 467
			if (r < 0) {
				apk_error("Failed to set file permissions "
					  "on %s: %s",
					  fn, strerror(errno));
				return -errno;
			}
		}

Timo Teräs's avatar
Timo Teräs committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
		/* extract xattrs */
		if (ae->xattrs && ae->xattrs->num) {
			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;
						break;
					}
				}
				close(fd);
			} else {
				r = errno;
			}
483
			if (r) {
Timo Teräs's avatar
Timo Teräs committed
484 485 486 487 488 489
				apk_error("Failed to set xattrs on %s: %s",
					  fn, strerror(r));
				return -r;
			}
		}

490 491
		if (!S_ISLNK(ae->mode)) {
			/* preserve modification time */
492 493 494 495 496
			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);
497 498 499 500 501
			if (r < 0) {
				apk_error("Failed to preserve modification time on %s: %s",
					fn, strerror(errno));
				return -errno;
			}
502
		}
503
	} else {
504 505
		apk_error("Failed to extract %s: %s", ae->name, strerror(errno));
		return -errno;
506
	}
507
	return 0;
508
}