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