io_archive.c 11.5 KB
Newer Older
1
/* io_archive.c - Alpine Package Keeper (APK)
2 3
 *
 * 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
 * SPDX-License-Identifier: GPL-2.0-only
8 9 10 11 12 13 14
 */

#include <stdio.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
15
#include <utime.h>
16 17 18 19
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/wait.h>
Natanael Copa's avatar
Natanael Copa committed
20
#include <sys/stat.h>
Timo Teräs's avatar
Timo Teräs committed
21
#include <sys/xattr.h>
22
#include <sys/sysmacros.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
#include "apk_archive.h"
Timo Teräs's avatar
Timo Teräs committed
30
#include "apk_openssl.h"
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

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
};

Timo Teräs's avatar
Timo Teräs committed
52 53 54
#define GET_OCTAL(s)	get_octal(s, sizeof(s))
#define PUT_OCTAL(s,v)	put_octal(s, sizeof(s), v)

55
static unsigned int get_octal(char *s, size_t l)
56 57
{
	apk_blob_t b = APK_BLOB_PTR_LEN(s, l);
58
	return apk_blob_pull_uint(&b, 8);
59
}
60

Timo Teräs's avatar
Timo Teräs committed
61 62 63 64 65 66 67 68 69 70 71 72 73
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';
}

74
static int blob_realloc(apk_blob_t *b, size_t newsize)
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
{
	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;
94
		if (len < hdr.ptr - start + 1) break;
95
		len -= hdr.ptr - start + 1;
96
		if (hdr.len < len) break;
97 98 99 100 101 102 103 104 105 106
		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
107 108 109 110 111 112
		} 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,
			};
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
		} 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;
			}
		}
	}
}

128
int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
129
		  void *ctx, struct apk_id_cache *idc)
130
{
131
	struct apk_file_info entry;
132
	struct apk_segment_istream segment;
133 134
	struct tar_header buf;
	int end = 0, r;
135 136
	size_t toskip, paxlen = 0;
	apk_blob_t pax = APK_BLOB_NULL, longname = APK_BLOB_NULL;
Timo Teräs's avatar
Timo Teräs committed
137
	char filename[sizeof buf.name + sizeof buf.prefix + 2];
138

139 140
	if (IS_ERR_OR_NULL(is)) return PTR_ERR(is) ?: -EINVAL;

141
	memset(&entry, 0, sizeof(entry));
Timo Teräs's avatar
Timo Teräs committed
142
	entry.name = buf.name;
143
	while ((r = apk_istream_read(is, &buf, 512)) == 512) {
144
		if (buf.name[0] == '\0') {
145
			if (end) break;
146 147 148 149
			end++;
			continue;
		}

150
		entry = (struct apk_file_info){
151
			.size  = GET_OCTAL(buf.size),
152 153
			.uid   = apk_resolve_uid(idc, buf.uname, GET_OCTAL(buf.uid)),
			.gid   = apk_resolve_gid(idc, buf.gname, GET_OCTAL(buf.gid)),
154
			.mode  = GET_OCTAL(buf.mode) & 07777,
155
			.mtime = GET_OCTAL(buf.mtime),
Timo Teräs's avatar
Timo Teräs committed
156
			.name  = entry.name,
157 158 159 160
			.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
161
			.xattrs = entry.xattrs,
162
		};
Timo Teräs's avatar
Timo Teräs committed
163 164 165 166 167 168
		if (buf.prefix[0] && buf.typeflag != 'x' && buf.typeflag != 'g') {
			snprintf(filename, sizeof filename, "%.*s/%.*s",
				 (int) sizeof buf.prefix, buf.prefix,
				 (int) sizeof buf.name, buf.name);
			entry.name = filename;
		}
169 170
		buf.mode[0] = 0; /* to nul terminate 100-byte buf.name */
		buf.magic[0] = 0; /* to nul terminate 100-byte buf.linkname */
Timo Teräs's avatar
Timo Teräs committed
171
		apk_xattr_array_resize(&entry.xattrs, 0);
172

173 174
		if (entry.size >= SSIZE_MAX-512) goto err;

175 176 177 178
		if (paxlen) {
			handle_extended_header(&entry, APK_BLOB_PTR_LEN(pax.ptr, paxlen));
			apk_fileinfo_hash_xattr(&entry);
		}
179

180
		toskip = (entry.size + 511) & -512;
181
		switch (buf.typeflag) {
182
		case 'L': /* GNU long name extension */
183
			if ((r = blob_realloc(&longname, entry.size+1)) != 0 ||
184
			    (r = apk_istream_read(is, longname.ptr, entry.size)) != entry.size)
185
				goto err;
186
			entry.name = longname.ptr;
Timo Teräs's avatar
Timo Teräs committed
187
			entry.name[entry.size] = 0;
188
			toskip -= entry.size;
189
			break;
190 191
		case 'K': /* GNU long link target extension - ignored */
			break;
192 193
		case '0':
		case '7': /* regular file */
194
			entry.mode |= S_IFREG;
195 196 197
			break;
		case '1': /* hard link */
			entry.mode |= S_IFREG;
198
			if (!entry.link_target) entry.link_target = buf.linkname;
199 200 201
			break;
		case '2': /* symbolic link */
			entry.mode |= S_IFLNK;
202
			if (!entry.link_target) entry.link_target = buf.linkname;
203 204 205 206
			break;
		case '3': /* char device */
			entry.mode |= S_IFCHR;
			break;
Timo Teräs's avatar
Timo Teräs committed
207
		case '4': /* block device */
208 209 210 211 212
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
Jesse Young's avatar
Jesse Young committed
213 214 215
		case '6': /* fifo */
			entry.mode |= S_IFIFO;
			break;
216 217 218 219
		case 'g': /* global pax header */
			break;
		case 'x': /* file specific pax header */
			paxlen = entry.size;
220
			if ((r = blob_realloc(&pax, (paxlen + 511) & -512)) != 0 ||
221
			    (r = apk_istream_read(is, pax.ptr, paxlen)) != paxlen)
222
				goto err;
223
			toskip -= entry.size;
224
			break;
225 226 227 228
		default:
			break;
		}

229 230 231 232 233 234
		if (strnlen(entry.name, PATH_MAX) >= PATH_MAX-10 ||
		    (entry.link_target && strnlen(entry.link_target, PATH_MAX) >= PATH_MAX-10)) {
			r = -ENAMETOOLONG;
			goto err;
		}

235
		if (entry.mode & S_IFMT) {
236 237
			apk_istream_segment(&segment, is, entry.size, entry.mtime);
			r = parser(ctx, &entry, &segment.is);
238
			if (r != 0) goto err;
239
			apk_istream_close(&segment.is);
240

241
			entry.name = buf.name;
242
			toskip -= entry.size;
243
			paxlen = 0;
244 245
		}

246 247
		if (toskip && (r = apk_istream_read(is, NULL, toskip)) != toskip)
			goto err;
248
	}
249

250 251 252
	/* Read remaining end-of-archive records, to ensure we read all of
	 * the file. The underlying istream is likely doing checksumming. */
	if (r == 512) {
253
		while ((r = apk_istream_read(is, &buf, 512)) == 512) {
254
			if (buf.name[0] != 0) break;
Timo Teräs's avatar
Timo Teräs committed
255
		}
256
	}
257
	if (r == 0) goto ok;
Timo Teräs's avatar
Timo Teräs committed
258
err:
259 260 261
	/* Check that there was no partial (or non-zero) record */
	if (r >= 0) r = -EBADMSG;
ok:
262 263
	free(pax.ptr);
	free(longname.ptr);
264
	apk_fileinfo_free(&entry);
265
	apk_istream_close(is);
Timo Teräs's avatar
Timo Teräs committed
266 267 268
	return r;
}

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

284
		if (ae->name != NULL)
Timo Teräs's avatar
Timo Teräs committed
285
			strlcpy(buf.name, ae->name, sizeof buf.name);
286

Timo Teräs's avatar
Timo Teräs committed
287 288
		strlcpy(buf.uname, ae->uname ?: "root", sizeof buf.uname);
		strlcpy(buf.gname, ae->gname ?: "root", sizeof buf.gname);
Timo Teräs's avatar
Timo Teräs committed
289 290 291 292 293

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

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

305
	if (apk_ostream_write(os, &buf, sizeof(buf)) != sizeof(buf))
Timo Teräs's avatar
Timo Teräs committed
306 307
		return -1;

308 309
	if (ae == NULL) {
		/* End-of-archive is two empty headers */
310
		if (apk_ostream_write(os, &buf, sizeof(buf)) != sizeof(buf))
311 312
			return -1;
	} else if (data != NULL) {
313
		if (apk_ostream_write(os, data, ae->size) != ae->size)
Timo Teräs's avatar
Timo Teräs committed
314
			return -1;
315
		if (apk_tar_write_padding(os, ae) != 0)
Timo Teräs's avatar
Timo Teräs committed
316 317 318
			return -1;
	}

319 320 321
	return 0;
}

322 323 324 325 326 327 328
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 &&
329
	    apk_ostream_write(os, padding, pad) != pad)
330 331 332 333 334
		return -1;

	return 0;
}

335
int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
336 337
			      const char *extract_name, const char *link_target,
			      struct apk_istream *is,
338 339
			      apk_progress_cb cb, void *cb_ctx,
			      unsigned int apk_extract_flags)
340
{
Timo Teräs's avatar
Timo Teräs committed
341
	struct apk_xattr *xattr;
342
	const char *fn = extract_name ?: ae->name;
343
	int fd, r = -1, atflags = 0, ret = 0;
344

345
	if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno;
346 347 348

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
349
		r = mkdirat(atfd, fn, ae->mode & 07777);
350 351
		if (r < 0 && errno != EEXIST)
			ret = -errno;
352 353 354
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
355
			int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL;
356 357

			fd = openat(atfd, fn, flags, ae->mode & 07777);
358
			if (fd < 0) {
359
				ret = -errno;
360
				break;
361
			}
362 363
			r = apk_istream_splice(is, fd, ae->size, cb, cb_ctx);
			if (r != ae->size) ret = r < 0 ? r : -ENOSPC;
364
			close(fd);
365
		} else {
366
			r = linkat(atfd, link_target ?: ae->link_target, atfd, fn, 0);
367
			if (r < 0) ret = -errno;
368 369 370
		}
		break;
	case S_IFLNK:
371
		r = symlinkat(link_target ?: ae->link_target, atfd, fn);
372
		if (r < 0) ret = -errno;
373
		atflags |= AT_SYMLINK_NOFOLLOW;
374 375 376 377
		break;
	case S_IFBLK:
	case S_IFCHR:
	case S_IFIFO:
Jesse Young's avatar
Jesse Young committed
378
		r = mknodat(atfd, fn, ae->mode, ae->device);
379
		if (r < 0) ret = -errno;
380 381
		break;
	}
382 383 384 385 386
	if (ret) {
		apk_error("Failed to create %s: %s", ae->name, strerror(-ret));
		return ret;
	}

387 388
	if (!(apk_extract_flags & APK_EXTRACTF_NO_CHOWN)) {
		r = fchownat(atfd, fn, ae->uid, ae->gid, atflags);
389
		if (r < 0) {
390
			apk_error("Failed to set ownership on %s: %s",
391
				  fn, strerror(errno));
392
			if (!ret) ret = -errno;
393
		}
394 395 396 397 398 399 400 401 402 403 404

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

407
	/* extract xattrs */
408
	if (!S_ISLNK(ae->mode) && ae->xattrs && ae->xattrs->num) {
409 410 411 412 413 414 415
		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
416 417
				}
			}
418 419 420 421 422 423
			close(fd);
		} else {
			r = -errno;
		}
		if (r) {
			if (r != -ENOTSUP)
Timo Teräs's avatar
Timo Teräs committed
424
				apk_error("Failed to set xattrs on %s: %s",
425
					  fn, strerror(-r));
426
			if (!ret) ret = r;
Timo Teräs's avatar
Timo Teräs committed
427
		}
428
	}
Timo Teräs's avatar
Timo Teräs committed
429

430 431 432 433 434 435 436 437 438 439 440
	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;
441
		}
442
	}
443

444
	return ret;
445
}