master xplshn/aruu / cmd / pseudo / tar.c
   1
   2
   3#include "config.h"
   4#include "fs.h"
   5#include "utf.h"
   6#include "util.h"
   7
   8#include <assert.h>
   9#include <errno.h>
  10#include <fcntl.h>
  11#include <fnmatch.h>
  12#include <grp.h>
  13#include <libgen.h>
  14#include <pwd.h>
  15#include <stdio.h>
  16#include <stdlib.h>
  17#include <string.h>
  18#include <sys/stat.h>
  19#include <sys/sysmacros.h>
  20#include <sys/time.h>
  21#include <sys/types.h>
  22#include <unistd.h>
  23
  24#if FEATURE_TAR_TTY_SAFE
  25static void
  26safe_puts(const char *s)
  27{
  28	Rune r;
  29	int n;
  30
  31	if (isatty(1)) {
  32		while (*s) {
  33			n = chartorune(&r, s);
  34			if (r == Runeerror) {
  35				putchar('?');
  36				s++;
  37			} else if (isprintrune(r)) {
  38				fputrune(&r, stdout);
  39				s += n;
  40			} else {
  41				putchar('?');
  42				s += n;
  43			}
  44		}
  45		putchar('\n');
  46	} else {
  47		puts(s);
  48	}
  49}
  50#else
  51#define safe_puts(s) puts(s)
  52#endif
  53
  54#define BLKSIZ (sizeof (struct header)) /* must equal 512 bytes */
  55
  56enum Type {
  57	REG       = '0',
  58	AREG      = '\0',
  59	HARDLINK  = '1',
  60	SYMLINK   = '2',
  61	CHARDEV   = '3',
  62	BLOCKDEV  = '4',
  63	DIRECTORY = '5',
  64	FIFO      = '6',
  65	RESERVED  = '7'
  66};
  67
  68struct header {
  69	char name[100];
  70	char mode[8];
  71	char uid[8];
  72	char gid[8];
  73	char size[12];
  74	char mtime[12];
  75	char chksum[8];
  76	char type;
  77	char linkname[100];
  78	char magic[6];
  79	char version[2];
  80	char uname[32];
  81	char gname[32];
  82	char major[8];
  83	char minor[8];
  84	char prefix[155];
  85	char padding[12];
  86};
  87
  88static struct dirtime {
  89	char *name;
  90	time_t mtime;
  91} *dirtimes;
  92
  93static size_t dirtimeslen;
  94
  95static int tarfd;
  96static ino_t tarinode;
  97static dev_t tardev;
  98
  99static int mflag, vflag;
 100static int filtermode;
 101
 102#if FEATURE_TAR_EXCLUDE
 103static char **excludes = NULL;
 104static size_t excludes_cnt = 0;
 105
 106static void
 107add_exclude(const char *pattern)
 108{
 109	excludes = ereallocarray(excludes, excludes_cnt + 1, sizeof(*excludes));
 110	excludes[excludes_cnt++] = estrdup(pattern);
 111}
 112
 113static int
 114is_excluded(const char *path)
 115{
 116	size_t i;
 117	const char *base = strrchr(path, '/');
 118	base = base ? base + 1 : path;
 119
 120	for (i = 0; i < excludes_cnt; i++) {
 121		if (fnmatch(excludes[i], path, 0) == 0 ||
 122		    fnmatch(excludes[i], base, 0) == 0)
 123			return 1;
 124	}
 125	return 0;
 126}
 127#endif
 128static const char *filtertool;
 129
 130static const char *filtertools[] = {
 131	['J'] = "xz",
 132	['Z'] = "compress",
 133	['a'] = "lzma",
 134	['j'] = "bzip2",
 135	['z'] = "gzip",
 136};
 137
 138#if FEATURE_TAR_TO_STDOUT
 139static int Oflag_stdout = 0;
 140#else
 141#define Oflag_stdout 0
 142#endif
 143
 144#if FEATURE_TAR_KEEP_OLD
 145static int kflag_keep = 0;
 146#else
 147#define kflag_keep 0
 148#endif
 149
 150#if FEATURE_TAR_STRIP_COMPONENTS
 151static int strip_components_count = 0;
 152
 153static char *
 154strip_components(char *path, int count)
 155{
 156	char *p = path;
 157	int i;
 158
 159	for (i = 0; i < count; i++) {
 160		p = strchr(p, '/');
 161		if (!p)
 162			return NULL;
 163		while (*p == '/')
 164			p++;
 165	}
 166	return p;
 167}
 168#else
 169#define strip_components_count 0
 170#endif
 171
 172#if FEATURE_TAR_FILES_FROM
 173static char **files_from = NULL;
 174static size_t files_from_cnt = 0;
 175
 176static void
 177add_files_from(const char *path)
 178{
 179	files_from = ereallocarray(files_from, files_from_cnt + 1, sizeof(*files_from));
 180	files_from[files_from_cnt++] = estrdup(path);
 181}
 182
 183static void
 184load_files_from_file(const char *path)
 185{
 186	FILE *fp = fopen(path, "r");
 187	char line[PATH_MAX];
 188
 189	if (!fp)
 190		eprintf("open %s:", path);
 191	while (fgets(line, sizeof(line), fp)) {
 192		size_t len = strlen(line);
 193		while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
 194			len--;
 195		line[len] = '\0';
 196		if (len > 0)
 197			add_files_from(line);
 198	}
 199	fclose(fp);
 200}
 201#else
 202#define files_from_cnt 0
 203#endif
 204
 205#if FEATURE_TAR_EXCLUDE_FROM
 206static void
 207load_excludes_from_file(const char *path)
 208{
 209	FILE *fp = fopen(path, "r");
 210	char line[PATH_MAX];
 211
 212	if (!fp)
 213		eprintf("open %s:", path);
 214	while (fgets(line, sizeof(line), fp)) {
 215		size_t len = strlen(line);
 216		while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
 217			len--;
 218		line[len] = '\0';
 219		if (len > 0) {
 220#if FEATURE_TAR_EXCLUDE
 221			add_exclude(line);
 222#else
 223			(void)line;
 224#endif
 225		}
 226	}
 227	fclose(fp);
 228}
 229#endif
 230
 231static void
 232pushdirtime(char *name, time_t mtime)
 233{
 234	dirtimes = ereallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes));
 235	dirtimes[dirtimeslen].name = estrdup(name);
 236	dirtimes[dirtimeslen].mtime = mtime;
 237	dirtimeslen++;
 238}
 239
 240static struct dirtime *
 241popdirtime(void)
 242{
 243	if (dirtimeslen) {
 244		dirtimeslen--;
 245		return &dirtimes[dirtimeslen];
 246	}
 247	return NULL;
 248}
 249
 250static int
 251comp(int fd, const char *tool, const char *flags)
 252{
 253	int fds[2];
 254
 255	if (pipe(fds) < 0)
 256		eprintf("pipe:");
 257
 258	switch (fork()) {
 259	case -1:
 260		eprintf("fork:");
 261		/* fallthrough */
 262	case 0:
 263		dup2(fd, 1);
 264		dup2(fds[0], 0);
 265		close(fds[0]);
 266		close(fds[1]);
 267
 268		execlp(tool, tool, flags, NULL);
 269		weprintf("execlp %s:", tool);
 270		_exit(1);
 271	}
 272	close(fds[0]);
 273	return fds[1];
 274}
 275
 276static int
 277decomp(int fd, const char *tool, const char *flags)
 278{
 279	int fds[2];
 280
 281	if (pipe(fds) < 0)
 282		eprintf("pipe:");
 283
 284	switch (fork()) {
 285	case -1:
 286		eprintf("fork:");
 287		/* fallthrough */
 288	case 0:
 289		dup2(fd, 0);
 290		dup2(fds[1], 1);
 291		close(fds[0]);
 292		close(fds[1]);
 293
 294		execlp(tool, tool, flags, NULL);
 295		weprintf("execlp %s:", tool);
 296		_exit(1);
 297	}
 298	close(fds[1]);
 299	return fds[0];
 300}
 301
 302static ssize_t
 303eread(int fd, void *buf, size_t n)
 304{
 305	ssize_t r;
 306
 307again:
 308	r = read(fd, buf, n);
 309	if (r < 0) {
 310		if (errno == EINTR)
 311			goto again;
 312		eprintf("read:");
 313	}
 314	return r;
 315}
 316
 317static ssize_t
 318ewrite(int fd, const void *buf, size_t n)
 319{
 320	ssize_t r;
 321
 322	if ((r = write(fd, buf, n)) < 0 || (size_t)r != n)
 323		eprintf("write:");
 324	return r;
 325}
 326
 327static unsigned
 328chksum(struct header *h)
 329{
 330	unsigned sum, i;
 331
 332	memset(h->chksum, ' ', sizeof(h->chksum));
 333	for (i = 0, sum = 0, assert(BLKSIZ == 512); i < BLKSIZ; i++)
 334		sum += *((unsigned char *)h + i);
 335	return sum;
 336}
 337
 338#if FEATURE_TAR_CREATE
 339static void
 340putoctal(char *dst, unsigned num, int size)
 341{
 342	if (snprintf(dst, size, "%.*o", size - 1, num) >= size)
 343		eprintf("putoctal: input number '%o' too large\n", num);
 344}
 345
 346static int
 347archive(const char *path)
 348{
 349	static const struct header blank = {
 350		.name = "././@LongLink",
 351		.mode = "0000600",
 352		.uid = "0000000",
 353		.gid = "0000000",
 354		.size = "00000000000",
 355		.mtime = "00000000000",
 356		.chksum = "       ",
 357		.type = AREG,
 358		.linkname = "",
 359		.magic = "ustar",
 360		.version = { '0', '0' }
 361	};
 362	char   b[BLKSIZ + BLKSIZ], *p;
 363	struct header *h = (struct header *)b;
 364	struct group  *gr;
 365	struct passwd *pw;
 366	struct stat st;
 367	ssize_t l, n, r;
 368	int fd = -1;
 369
 370	if (lstat(path, &st) < 0) {
 371		weprintf("lstat %s:", path);
 372		return 0;
 373	} else if (st.st_ino == tarinode && st.st_dev == tardev) {
 374		weprintf("ignoring %s\n", path);
 375		return 0;
 376	}
 377	pw = getpwuid(st.st_uid);
 378	gr = getgrgid(st.st_gid);
 379
 380	*h = blank;
 381	n  = strlcpy(h->name, path, sizeof(h->name));
 382	if ((size_t)n >= sizeof(h->name)) {
 383		*++h = blank;
 384		h->type = 'L';
 385		putoctal(h->size,   n,         sizeof(h->size));
 386		putoctal(h->chksum, chksum(h), sizeof(h->chksum));
 387		ewrite(tarfd, (char *)h, BLKSIZ);
 388
 389		for (p = (char *)path; n > 0; n -= BLKSIZ, p += BLKSIZ) {
 390			if ((size_t)n < BLKSIZ) {
 391				p = memcpy(h--, p, n);
 392				memset(p + n, 0, BLKSIZ - (size_t)n);
 393			}
 394			ewrite(tarfd, p, BLKSIZ);
 395		}
 396	}
 397
 398	putoctal(h->mode,    (unsigned)st.st_mode & 0777, sizeof(h->mode));
 399	putoctal(h->uid,     (unsigned)st.st_uid,         sizeof(h->uid));
 400	putoctal(h->gid,     (unsigned)st.st_gid,         sizeof(h->gid));
 401	putoctal(h->mtime,   (unsigned)st.st_mtime,       sizeof(h->mtime));
 402	estrlcpy(h->uname,   pw ? pw->pw_name : "",       sizeof(h->uname));
 403	estrlcpy(h->gname,   gr ? gr->gr_name : "",       sizeof(h->gname));
 404
 405	if (S_ISREG(st.st_mode)) {
 406		h->type = REG;
 407		putoctal(h->size, st.st_size,  sizeof(h->size));
 408		fd = open(path, O_RDONLY);
 409		if (fd < 0)
 410			eprintf("open %s:", path);
 411	} else if (S_ISDIR(st.st_mode)) {
 412		h->type = DIRECTORY;
 413	} else if (S_ISLNK(st.st_mode)) {
 414		h->type = SYMLINK;
 415		if ((r = readlink(path, h->linkname, sizeof(h->linkname) - 1)) < 0)
 416			eprintf("readlink %s:", path);
 417		h->linkname[r] = '\0';
 418	} else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
 419		h->type = S_ISCHR(st.st_mode) ? CHARDEV : BLOCKDEV;
 420		putoctal(h->major, (unsigned)major(st.st_dev), sizeof(h->major));
 421		putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof(h->minor));
 422	} else if (S_ISFIFO(st.st_mode)) {
 423		h->type = FIFO;
 424	}
 425
 426	putoctal(h->chksum, chksum(h), sizeof(h->chksum));
 427	ewrite(tarfd, b, BLKSIZ);
 428
 429	if (fd != -1) {
 430		while ((l = eread(fd, b, BLKSIZ)) > 0) {
 431			if ((size_t)l < BLKSIZ)
 432				memset(b + l, 0, BLKSIZ - (size_t)l);
 433			ewrite(tarfd, b, BLKSIZ);
 434		}
 435		close(fd);
 436	}
 437
 438	return 0;
 439}
 440#endif
 441
 442static void
 443skipblk(ssize_t l)
 444{
 445	char b[BLKSIZ];
 446
 447	for (; l > 0; l -= BLKSIZ)
 448		if (!eread(tarfd, b, BLKSIZ))
 449			break;
 450}
 451
 452static int
 453unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 454{
 455	struct header *h = (struct header *)b;
 456	struct timespec times[2];
 457	struct stat st;
 458	char lname[101], *tmp, *p;
 459	long mode, major, minor, type, mtime, uid, gid;
 460	int  fd = -1, lnk = h->type == SYMLINK;
 461
 462	if (kflag_keep && !Oflag_stdout) {
 463		if (lstat(fname, &st) == 0) {
 464			skipblk(l);
 465			return 0;
 466		}
 467	}
 468
 469	if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0'))
 470		eprintf("strtol %s: invalid mtime\n", h->mtime);
 471
 472	if (Oflag_stdout) {
 473		if (h->type == REG || h->type == AREG || h->type == RESERVED) {
 474			fd = 1;
 475		} else {
 476			return 0;
 477		}
 478	} else {
 479		if (strcmp(fname, ".") && strcmp(fname, "./") && remove(fname) < 0)
 480			if (errno != ENOENT) weprintf("remove %s:", fname);
 481
 482		tmp = estrdup(fname);
 483		mkdirp(dirname(tmp), 0777, 0777);
 484		free(tmp);
 485
 486		switch (h->type) {
 487		case REG:
 488		case AREG:
 489		case RESERVED:
 490			if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
 491				eprintf("strtol %s: invalid mode\n", h->mode);
 492#if FEATURE_TAR_NOFOLLOW
 493			fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT | O_NOFOLLOW, 0600);
 494#else
 495			fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
 496#endif
 497			if (fd < 0)
 498				eprintf("open %s:", fname);
 499			break;
 500		case HARDLINK:
 501		case SYMLINK:
 502			snprintf(lname, sizeof(lname), "%.*s", (int)sizeof(h->linkname),
 503			         h->linkname);
 504			if ((lnk ? symlink:link)(lname, fname) < 0)
 505				eprintf("%s %s -> %s:", lnk ? "symlink":"link", fname, lname);
 506			lnk++;
 507			break;
 508		case DIRECTORY:
 509			if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
 510				eprintf("strtol %s: invalid mode\n", h->mode);
 511			if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST)
 512				eprintf("mkdir %s:", fname);
 513			pushdirtime(fname, mtime);
 514			break;
 515		case CHARDEV:
 516		case BLOCKDEV:
 517			if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
 518				eprintf("strtol %s: invalid mode\n", h->mode);
 519			if ((major = strtol(h->major, &p, 8)) < 0 || *p != '\0')
 520				eprintf("strtol %s: invalid major device\n", h->major);
 521			if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0')
 522				eprintf("strtol %s: invalid minor device\n", h->minor);
 523			type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
 524			if (mknod(fname, type | mode, makedev(major, minor)) < 0)
 525				eprintf("mknod %s:", fname);
 526			break;
 527		case FIFO:
 528			if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
 529				eprintf("strtol %s: invalid mode\n", h->mode);
 530			if (mknod(fname, S_IFIFO | mode, 0) < 0)
 531				eprintf("mknod %s:", fname);
 532			break;
 533		default:
 534			eprintf("unsupported tar-filetype %c\n", h->type);
 535		}
 536	}
 537
 538	if (!Oflag_stdout) {
 539		if ((uid = strtol(h->uid, &p, 8)) < 0 || *p != '\0')
 540			eprintf("strtol %s: invalid uid\n", h->uid);
 541		if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0')
 542			eprintf("strtol %s: invalid gid\n", h->gid);
 543	}
 544
 545	if (fd != -1) {
 546		for (; l > 0; l -= BLKSIZ)
 547			if (eread(tarfd, b, BLKSIZ) > 0)
 548				ewrite(fd, b, MIN(l, (ssize_t)BLKSIZ));
 549		if (fd != 1)
 550			close(fd);
 551	}
 552
 553	if (Oflag_stdout)
 554		return 0;
 555
 556	if (lnk == 1)
 557		return 0;
 558
 559	times[0].tv_sec = times[1].tv_sec = mtime;
 560	times[0].tv_nsec = times[1].tv_nsec = 0;
 561	if (!mflag && utimensat(AT_FDCWD, fname, times, AT_SYMLINK_NOFOLLOW) < 0)
 562		weprintf("utimensat %s:", fname);
 563	if (lnk) {
 564		if (!getuid() && lchown(fname, uid, gid))
 565			weprintf("lchown %s:", fname);
 566	} else {
 567		if (!getuid() && chown(fname, uid, gid))
 568			weprintf("chown %s:", fname);
 569		if (chmod(fname, mode) < 0)
 570			eprintf("fchmod %s:", fname);
 571	}
 572
 573	return 0;
 574}
 575
 576
 577static int
 578print(char *fname, ssize_t l, char b[BLKSIZ])
 579{
 580	(void)b;
 581	safe_puts(fname);
 582	skipblk(l);
 583	return 0;
 584}
 585
 586#if FEATURE_TAR_CREATE
 587static void
 588c(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r)
 589{
 590	(void)data;
 591#if FEATURE_TAR_EXCLUDE
 592	if (is_excluded(r->path))
 593		return;
 594#endif
 595	archive(r->path);
 596	if (vflag)
 597		safe_puts(r->path);
 598
 599	if (S_ISDIR(st->st_mode))
 600		recurse(dirfd, name, NULL, r);
 601}
 602#endif
 603
 604static void
 605sanitize(struct header *h)
 606{
 607	size_t i, j, l;
 608	struct {
 609		char  *f;
 610		size_t l;
 611	} fields[] = {
 612		{ h->mode,   sizeof(h->mode)   },
 613		{ h->uid,    sizeof(h->uid)    },
 614		{ h->gid,    sizeof(h->gid)    },
 615		{ h->size,   sizeof(h->size)   },
 616		{ h->mtime,  sizeof(h->mtime)  },
 617		{ h->chksum, sizeof(h->chksum) },
 618		{ h->major,  sizeof(h->major)  },
 619		{ h->minor,  sizeof(h->minor)  }
 620	};
 621
 622	/* Numeric fields can be terminated with spaces instead of
 623	 * NULs as per the ustar specification.  Patch all of them to
 624	 * use NULs so we can perform string operations on them. */
 625	for (i = 0; i < LEN(fields); i++){
 626		j = 0, l = fields[i].l - 1;
 627		for (; j < l && fields[i].f[j] == ' '; j++);
 628		for (; j <= l; j++)
 629			if (fields[i].f[j] == ' ')
 630				fields[i].f[j] = '\0';
 631		if (fields[i].f[l])
 632			eprintf("numeric field #%d (%.*s) is not null or space terminated\n",
 633			        i, l+1, fields[i].f);
 634	}
 635}
 636
 637static void
 638chktar(struct header *h)
 639{
 640	const char *reason;
 641	char tmp[sizeof h->chksum], *err;
 642	long sum, i;
 643
 644	if (h->prefix[0] == '\0' && h->name[0] == '\0') {
 645		reason = "empty filename";
 646		goto bad;
 647	}
 648	if (h->magic[0] && strncmp("ustar", h->magic, 5)) {
 649		reason = "not ustar format";
 650		goto bad;
 651	}
 652	memcpy(tmp, h->chksum, sizeof(tmp));
 653	for (i = sizeof(tmp)-1; i > 0 && tmp[i] == ' '; i--) {
 654		tmp[i] = '\0';
 655	}
 656	sum = strtol(tmp, &err, 8);
 657	if (sum < 0 || sum >= (long)(BLKSIZ*256) || *err != '\0') {
 658		reason = "invalid checksum";
 659		goto bad;
 660	}
 661	if (sum != chksum(h)) {
 662		reason = "incorrect checksum";
 663		goto bad;
 664	}
 665	memcpy(h->chksum, tmp, sizeof(tmp));
 666	return;
 667bad:
 668	eprintf("malformed tar archive: %s\n", reason);
 669}
 670
 671static void
 672xt(int argc, char *argv[], int mode)
 673{
 674	long size;
 675	char b[BLKSIZ], fname[PATH_MAX + 1], *p, *q = NULL, *stripped;
 676	int i, m, n, match;
 677	int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print;
 678	struct timespec times[2];
 679	struct header *h = (struct header *)b;
 680	struct dirtime *dirtime;
 681#if FEATURE_TAR_FILES_FROM
 682	size_t idx;
 683#endif
 684
 685	while (eread(tarfd, b, BLKSIZ) > 0 && (h->name[0] || h->prefix[0])) {
 686		chktar(h);
 687		sanitize(h);
 688
 689		if ((size = strtol(h->size, &p, 8)) < 0 || *p != '\0')
 690			eprintf("strtol %s: invalid size\n", h->size);
 691
 692		/* Long file path is read directly into fname*/
 693		if (h->type == 'L' || h->type == 'x' || h->type == 'g') {
 694
 695			/* Read header only up to size of fname buffer */
 696			for (q = fname; q < fname+size; q += BLKSIZ) {
 697				if (q + BLKSIZ >= fname + sizeof fname)
 698					eprintf("name exceeds buffer: %.*s\n", q-fname, fname);
 699				eread(tarfd, q, BLKSIZ);
 700			}
 701
 702			/* Convert pax x header with 'path=' field into L header */
 703			if (h->type == 'x') for (q = fname; q < fname+size-16; q += n) {
 704				if ((n = strtol(q, &p, 10)) < 0 || *p != ' ')
 705					eprintf("strtol %.*s: invalid number\n", p+1-q, q);
 706				if (n && strncmp(p+1, "path=", 5) == 0) {
 707					memmove(fname, p+6, size = q+n - p-6 - 1);
 708					h->type = 'L';
 709					break;
 710				}
 711			}
 712			fname[size] = '\0';
 713
 714			/* Non L-like header (eg. pax 'g') is skipped by setting q=null */
 715			if (h->type != 'L')
 716				q = NULL;
 717			continue;
 718		}
 719
 720		/* Ustar path is copied into fname if no L header (ie: q is NULL) */
 721		if (!q) {
 722			m = sizeof h->prefix, n = sizeof h->name;
 723			p = "/" + !h->prefix[0];
 724			snprintf(fname, sizeof fname, "%.*s%s%.*s", m, h->prefix, p, n, h->name);
 725		}
 726		q = NULL;
 727
 728		/* If argc > 0 or files_from_cnt > 0 then only extract the matching files/dirs */
 729		if (argc || files_from_cnt) {
 730			match = 0;
 731			for (i = 0; i < argc; i++) {
 732				if (strncmp(argv[i], fname, n = strlen(argv[i])) == 0) {
 733					if (strchr("/", fname[n]) || argv[i][n-1] == '/') {
 734						match = 1;
 735						break;
 736					}
 737				}
 738			}
 739#if FEATURE_TAR_FILES_FROM
 740			if (!match) {
 741				for (idx = 0; idx < files_from_cnt; idx++) {
 742					if (strncmp(files_from[idx], fname, n = strlen(files_from[idx])) == 0) {
 743						if (strchr("/", fname[n]) || files_from[idx][n-1] == '/') {
 744							match = 1;
 745							break;
 746						}
 747					}
 748				}
 749			}
 750#endif
 751			if (!match) {
 752				skipblk(size);
 753				continue;
 754			}
 755		}
 756
 757		stripped = fname;
 758#if FEATURE_TAR_STRIP_COMPONENTS
 759		if (mode == 'x' && strip_components_count > 0) {
 760			stripped = strip_components(fname, strip_components_count);
 761			if (!stripped || *stripped == '\0') {
 762				skipblk(size);
 763				continue;
 764			}
 765		}
 766#endif
 767
 768		fn(stripped, size, b);
 769		if (vflag && mode != 't')
 770			safe_puts(fname);
 771	}
 772
 773	if (mode == 'x' && !mflag) {
 774		while ((dirtime = popdirtime())) {
 775			times[0].tv_sec = times[1].tv_sec = dirtime->mtime;
 776			times[0].tv_nsec = times[1].tv_nsec = 0;
 777			if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0)
 778				eprintf("utimensat %s:", fname);
 779			free(dirtime->name);
 780		}
 781		free(dirtimes);
 782		dirtimes = NULL;
 783	}
 784}
 785
 786char **args;
 787int argn;
 788
 789static void
 790usage(void)
 791{
 792#if FEATURE_TAR_CREATE
 793	eprintf("usage: %s [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p] "
 794	        "[-f file] [file ...]\n"
 795	        "       %s [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] path ... "
 796	        "[-f file]\n", argv0, argv0);
 797#else
 798	eprintf("usage: %s [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p] "
 799	        "[-f file] [file ...]\n", argv0);
 800#endif
 801}
 802
 803// ?man tar: tape archiver
 804// ?man arguments: x | t | -x | -t] [file ...
 805// ?man tar [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] [-T file] [-X file] path ... [-f file]
 806// ?man manipulate tape archive files
 807int
 808main(int argc, char *argv[])
 809{
 810#if FEATURE_TAR_CREATE
 811	struct recursor r = { .fn = c, .follow = 'P', .flags = DIRFIRST };
 812#endif
 813	struct stat st;
 814	char *file = NULL, *dir = ".", mode = '\0';
 815	int fd;
 816	size_t i;
 817
 818	argv0 = argv[0];
 819#if FEATURE_TAR_CREATE
 820	if (argc > 1 && strchr("cxt", mode = *argv[1]))
 821#else
 822	if (argc > 1 && strchr("xt", mode = *argv[1]))
 823#endif
 824		*(argv[1]+1) ? *argv[1] = '-' : (*++argv = argv0, --argc);
 825
 826	ARGBEGIN {
 827	// ?man -x: extract files from an archive
 828	case 'x':
 829#if FEATURE_TAR_CREATE
 830	// ?man -c: create a new archive
 831	case 'c':
 832#endif
 833	// ?man -t: list the contents of an archive
 834	case 't':
 835		mode = ARGC();
 836		break;
 837	// ?man -C:dir: specify option flag
 838	case 'C':
 839		dir = EARGF(usage());
 840		break;
 841	// ?man -f:file: specify archive file
 842	case 'f':
 843		file = EARGF(usage());
 844		break;
 845	// ?man -m: specify mode or limit
 846	case 'm':
 847		mflag = 1;
 848		break;
 849	// ?man -J: specify option flag
 850	case 'J':
 851	// ?man -Z: specify option flag
 852	case 'Z':
 853	// ?man -a: print or show all entries
 854	case 'a':
 855	// ?man -j: specify option flag
 856	case 'j':
 857	// ?man -z: specify option flag
 858	case 'z':
 859		filtermode = ARGC();
 860		filtertool = filtertools[filtermode];
 861		break;
 862	// ?man -h: suppress headers or print help
 863	case 'h':
 864#if FEATURE_TAR_CREATE
 865		r.follow = 'L';
 866#endif
 867		break;
 868	// ?man -v: verbosely list files processed
 869	case 'v':
 870		vflag = 1;
 871		break;
 872	// ?man -p: preserve file attributes
 873	case 'p':
 874		break;  /* do nothing as already default behaviour */
 875#if FEATURE_TAR_TO_STDOUT
 876	// ?man -O: extract files to stdout
 877	case 'O':
 878		Oflag_stdout = 1;
 879		break;
 880#endif
 881#if FEATURE_TAR_KEEP_OLD
 882	// ?man -k: keep existing files, do not overwrite
 883	case 'k':
 884		kflag_keep = 1;
 885		break;
 886#endif
 887#if FEATURE_TAR_FILES_FROM
 888	// ?man -T:file: -T file: read filenames from file
 889	case 'T':
 890		load_files_from_file(EARGF(usage()));
 891		break;
 892#endif
 893#if FEATURE_TAR_EXCLUDE_FROM
 894	// ?man -X:file: -X file: exclude patterns in file
 895	case 'X':
 896		load_excludes_from_file(EARGF(usage()));
 897		break;
 898#endif
 899#if FEATURE_TAR_STRIP_COMPONENTS
 900	// ?man -s:num: -strip-components num: strip num components
 901	case 's':
 902		if (strcmp(argv[0], "strip-components") == 0) {
 903			argv[0] = "s";
 904			strip_components_count = estrtonum(EARGF(usage()), 0, INT_MAX);
 905			brk_ = 1;
 906		} else if (strncmp(argv[0], "strip-components=", 17) == 0) {
 907			strip_components_count = estrtonum(argv[0] + 17, 0, INT_MAX);
 908			brk_ = 1;
 909		} else {
 910			usage();
 911		}
 912		break;
 913#endif
 914	// ?man --:num: specify - option
 915	case '-':
 916#if FEATURE_TAR_EXCLUDE
 917		if (strncmp(argv[0], "-exclude=", 9) == 0) {
 918			add_exclude(argv[0] + 9);
 919			brk_ = 1;
 920			break;
 921		} else if (strcmp(argv[0], "-exclude") == 0) {
 922			argv[0] = "-";
 923			add_exclude(EARGF(usage()));
 924			brk_ = 1;
 925			break;
 926		}
 927#endif
 928#if FEATURE_TAR_EXCLUDE_FROM
 929		if (strncmp(argv[0], "-exclude-from=", 14) == 0) {
 930			load_excludes_from_file(argv[0] + 14);
 931			brk_ = 1;
 932			break;
 933		} else if (strcmp(argv[0], "-exclude-from") == 0) {
 934			argv[0] = "-";
 935			load_excludes_from_file(EARGF(usage()));
 936			brk_ = 1;
 937			break;
 938		}
 939#endif
 940#if FEATURE_TAR_TO_STDOUT
 941		if (strcmp(argv[0], "-to-stdout") == 0) {
 942			Oflag_stdout = 1;
 943			brk_ = 1;
 944			break;
 945		}
 946#endif
 947#if FEATURE_TAR_KEEP_OLD
 948		if (strcmp(argv[0], "-keep-old-files") == 0) {
 949			kflag_keep = 1;
 950			brk_ = 1;
 951			break;
 952		}
 953#endif
 954#if FEATURE_TAR_STRIP_COMPONENTS
 955		if (strncmp(argv[0], "-strip-components=", 18) == 0) {
 956			strip_components_count = estrtonum(argv[0] + 18, 0, INT_MAX);
 957			brk_ = 1;
 958			break;
 959		} else if (strcmp(argv[0], "-strip-components") == 0) {
 960			argv[0] = "-";
 961			strip_components_count = estrtonum(EARGF(usage()), 0, INT_MAX);
 962			brk_ = 1;
 963			break;
 964		}
 965#endif
 966#if FEATURE_TAR_FILES_FROM
 967		if (strncmp(argv[0], "-files-from=", 12) == 0) {
 968			load_files_from_file(argv[0] + 12);
 969			brk_ = 1;
 970			break;
 971		} else if (strcmp(argv[0], "-files-from") == 0) {
 972			argv[0] = "-";
 973			load_files_from_file(EARGF(usage()));
 974			brk_ = 1;
 975			break;
 976		}
 977#endif
 978		usage();
 979		break;
 980	default:
 981		usage();
 982	} ARGEND
 983
 984	switch (mode) {
 985#if FEATURE_TAR_CREATE
 986	// ?man -c: create a new archive
 987	case 'c':
 988		if (!argc && !files_from_cnt)
 989			usage();
 990		tarfd = 1;
 991		if (file && *file != '-') {
 992			tarfd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
 993			if (tarfd < 0)
 994				eprintf("open %s:", file);
 995			if (lstat(file, &st) < 0)
 996				eprintf("lstat %s:", file);
 997			tarinode = st.st_ino;
 998			tardev = st.st_dev;
 999		}
1000
1001		if (filtertool)
1002			tarfd = comp(tarfd, filtertool, "-cf");
1003
1004		if (chdir(dir) < 0)
1005			eprintf("chdir %s:", dir);
1006		for (; *argv; argc--, argv++)
1007			recurse(AT_FDCWD, *argv, NULL, &r);
1008#if FEATURE_TAR_FILES_FROM
1009		for (i = 0; i < files_from_cnt; i++)
1010			recurse(AT_FDCWD, files_from[i], NULL, &r);
1011#endif
1012		break;
1013#endif
1014	// ?man -t: list the contents of an archive
1015	case 't':
1016	// ?man -x: extract files from an archive
1017	case 'x':
1018		tarfd = 0;
1019		if (file && *file != '-') {
1020			tarfd = open(file, O_RDONLY);
1021			if (tarfd < 0)
1022				eprintf("open %s:", file);
1023		}
1024
1025		if (filtertool) {
1026			fd = tarfd;
1027			tarfd = decomp(tarfd, filtertool, "-cdf");
1028			close(fd);
1029		}
1030
1031		if (chdir(dir) < 0)
1032			eprintf("chdir %s:", dir);
1033		xt(argc, argv, mode);
1034		break;
1035	default:
1036		usage();
1037	}
1038
1039#if FEATURE_TAR_EXCLUDE
1040	if (excludes) {
1041		for (i = 0; i < excludes_cnt; i++)
1042			free(excludes[i]);
1043		free(excludes);
1044	}
1045#endif
1046#if FEATURE_TAR_FILES_FROM
1047	if (files_from) {
1048		for (i = 0; i < files_from_cnt; i++)
1049			free(files_from[i]);
1050		free(files_from);
1051	}
1052#endif
1053	return recurse_status;
1054}