archive.c 10.2 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
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/wait.h>
Natanael Copa's avatar
Natanael Copa committed
23
#include <sys/stat.h>
24
#include <limits.h>
Natanael Copa's avatar
Natanael Copa committed
25
#include <stdint.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 48 49 50
#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 */
};

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
int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
120
		  void *ctx, int soft_checksums, struct apk_id_cache *idc)
121
{
122
	struct apk_file_info entry;
123 124
	struct apk_tar_entry_istream teis = {
		.is.read = tar_entry_read,
125
		.is.close = tar_entry_close,
126 127
		.tar_is = is,
	};
128
	struct tar_header buf;
129
	struct apk_tar_digest_info *odi, *di;
130 131
	unsigned long offset = 0;
	int end = 0, r;
132
	size_t toskip;
133

134 135
	odi = (struct apk_tar_digest_info *) &buf.linkname[3];
	di  = (struct apk_tar_digest_info *) &buf.devmajor[0];
Timo Teräs's avatar
Timo Teräs committed
136
	EVP_MD_CTX_init(&teis.mdctx);
137
	memset(&entry, 0, sizeof(entry));
138
	while ((r = is->read(is, &buf, 512)) == 512) {
139 140
		offset += 512;
		if (buf.name[0] == '\0') {
141 142
			if (end)
				break;
143 144 145 146
			end++;
			continue;
		}

147
		entry = (struct apk_file_info){
148
			.size  = GET_OCTAL(buf.size),
149 150
			.uid   = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
			.gid   = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
151
			.mode  = GET_OCTAL(buf.mode) & 07777,
152 153 154 155 156 157 158
			.mtime = GET_OCTAL(buf.mtime),
			.name  = entry.name,
			.uname = buf.uname,
			.gname = buf.gname,
			.device = makedev(GET_OCTAL(buf.devmajor),
					  GET_OCTAL(buf.devminor)),
		};
159
		teis.csum = NULL;
160

161 162 163 164 165 166 167
		if (memcmp(di->id, "APK2", 4) == 0 &&
		    di->size <= sizeof(entry.csum.data)) {
			entry.csum.type = di->size;
			memcpy(&entry.csum.data[0], buf.devminor, sizeof(buf.devminor));
			memcpy(&entry.csum.data[sizeof(buf.devminor)], buf.padding, sizeof(buf.padding));
		}

168 169 170 171 172
		switch (buf.typeflag) {
		case 'L':
			if (entry.name != NULL)
				free(entry.name);
			entry.name = malloc(entry.size+1);
173
			is->read(is, entry.name, entry.size);
Timo Teräs's avatar
Timo Teräs committed
174
			entry.name[entry.size] = 0;
175 176 177 178 179 180
			offset += entry.size;
			entry.size = 0;
			break;
		case '0':
		case '7': /* regular file */
			entry.mode |= S_IFREG;
181 182 183 184 185 186 187 188
			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;
					memcpy(entry.csum.data, odi->digest,
					       odi->size);
				} else if (soft_checksums)
					teis.csum = &entry.csum;
189
			}
190 191 192 193 194 195 196 197
			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;
198 199 200 201 202 203 204
			if (entry.csum.type == APK_CHECKSUM_NONE &&
			    soft_checksums) {
				EVP_Digest(buf.linkname, strlen(buf.linkname),
					   entry.csum.data, NULL,
					   apk_checksum_default(), NULL);
				entry.csum.type = APK_CHECKSUM_DEFAULT;
			}
205 206 207 208
			break;
		case '3': /* char device */
			entry.mode |= S_IFCHR;
			break;
Timo Teräs's avatar
Timo Teräs committed
209
		case '4': /* block device */
210 211 212 213 214 215 216 217 218
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
		default:
			break;
		}

219
		teis.bytes_left = entry.size;
220 221 222 223 224
		if (entry.mode & S_IFMT) {
			if (entry.name == NULL)
				entry.name = strdup(buf.name);

			/* callback parser function */
225 226
			if (teis.csum != NULL)
				EVP_DigestInit_ex(&teis.mdctx,
227
						  apk_checksum_default(), NULL);
228

229
			r = parser(ctx, &entry, &teis.is);
Timo Teräs's avatar
Timo Teräs committed
230
			free(entry.name);
231
			if (r != 0)
Timo Teräs's avatar
Timo Teräs committed
232
				goto err;
233 234 235 236

			entry.name = NULL;
		}

237 238 239 240 241 242 243 244
		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
245
	EVP_MD_CTX_cleanup(&teis.mdctx);
246

247 248 249
	/* 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
250
		while ((r = is->read(is, &buf, 512)) == 512) {
251
			if (buf.name[0] != 0)
252
				return -EBADMSG;
Timo Teräs's avatar
Timo Teräs committed
253
		}
254 255
	}

256
	/* Check that there was no partial record */
257
	if (r > 0)
258
		r = -EBADMSG;
259

260
	return r;
Timo Teräs's avatar
Timo Teräs committed
261 262 263 264 265 266

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

267 268
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
269 270 271 272 273 274 275 276 277 278 279 280 281
{
	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;

282 283 284 285 286
		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
287 288 289 290 291

		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);
292
		PUT_OCTAL(buf.mtime, ae->mtime ?: time(NULL));
Timo Teräs's avatar
Timo Teräs committed
293 294 295 296 297 298 299 300 301 302 303 304 305

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

306 307 308 309 310
	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
311 312
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
313
		if (apk_tar_write_padding(os, ae) != 0)
Timo Teräs's avatar
Timo Teräs committed
314 315 316
			return -1;
	}

317 318 319
	return 0;
}

320 321 322 323 324 325 326 327 328 329 330 331 332
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;
}

333 334 335
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)
336
{
337 338
	char *fn = ae->name;
	int fd, r = -1, atflags = 0;
339

340 341 342 343
	if (suffix != NULL) {
		fn = alloca(PATH_MAX);
		snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix);
	}
344 345 346 347

	if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) ||
	    (ae->link_target != NULL)) {
		/* non-standard entries need to be deleted first */
348
		unlinkat(atfd, fn, 0);
349
	}
350 351 352

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
353
		r = mkdirat(atfd, fn, ae->mode & 07777);
354 355 356 357 358
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
359
			int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
360 361

			fd = openat(atfd, fn, flags, ae->mode & 07777);
362 363
			if (fd < 0) {
				r = -1;
364
				break;
365
			}
366 367
			if (apk_istream_splice(is, fd, ae->size, cb, cb_ctx)
			    == ae->size)
368 369
				r = 0;
			close(fd);
370
		} else {
371 372 373 374 375 376 377
			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);
378 379 380
		}
		break;
	case S_IFLNK:
381 382
		r = symlinkat(ae->link_target, atfd, fn);
		atflags |= AT_SYMLINK_NOFOLLOW;
383 384 385 386 387
		break;
	case S_IFSOCK:
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
388
		r = mknodat(atfd, fn, ae->mode & 07777, ae->device);
389 390
		break;
	}
391
	if (r == 0) {
392
		r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
393 394 395 396 397 398
		if (r < 0) {
			apk_error("Failed to set ownership on %s: %s",
				  fn, strerror(errno));
			return -errno;
		}

399
		/* chown resets suid bit so we need set it again */
400
		if (ae->mode & 07000) {
401
			r = fchmodat(atfd, fn, ae->mode & 07777, atflags);
402 403 404 405 406 407 408 409
			if (r < 0) {
				apk_error("Failed to set file permissions "
					  "on %s: %s",
					  fn, strerror(errno));
				return -errno;
			}
		}

410 411
		if (!S_ISLNK(ae->mode)) {
			/* preserve modification time */
412 413 414 415 416
			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);
417 418 419 420 421
			if (r < 0) {
				apk_error("Failed to preserve modification time on %s: %s",
					fn, strerror(errno));
				return -errno;
			}
422
		}
423
	} else {
424 425
		apk_error("Failed to extract %s: %s", ae->name, strerror(errno));
		return -errno;
426
	}
427
	return 0;
428
}