view toys/pending/tar.c @ 1572:da1bf31ed322 draft

Tweak the "ignoring return value" fortify workaround for readlinkat. We zero the buffer and if the link read fails that's left alone, so it's ok for the symlink not to be there. Unfortunately, typecasting the return value to (void) doesn't shut up gcc, and having an if(); with the semicolon on the same line doesn't shut up llvm. (The semicolon on a new line would, but C does not have significant whitespace and I'm not going to humor llvm if it plans to start.) So far, empty curly brackets consistently get the warning to shut up.
author Rob Landley <rob@landley.net>
date Mon, 24 Nov 2014 17:23:23 -0600
parents 00c20f410c46
children 41efba077b75
line wrap: on
line source

/* tar.c - create/extract archives
 *
 * Copyright 2014 Ashwini Kumar <ak.ashwini81@gmail.com>
 *
 * USTAR interchange format is of interest in
 * See http://http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
 * For writing to external program
 * http://www.gnu.org/software/tar/manual/html_node/Writing-to-an-External-Program.html

USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc]", TOYFLAG_USR|TOYFLAG_BIN))

config TAR
  bool "tar"
  default n
  help
    usage: tar -[cxtzhmvO] [-X FILE] [-T FILE] [-f TARFILE] [-C DIR]

    Create, extract, or list files from a tar file

    Operation:
    c Create
    f Name of TARFILE ('-' for stdin/out)
    h Follow symlinks
    m Don't restore mtime
    t List
    v Verbose
    x Extract
    z (De)compress using gzip
    C Change to DIR before operation
    O Extract to stdout
    exclude=FILE File to exclude
    X File with names to exclude
    T File with names to include
*/
#define FOR_tar
#include "toys.h"

GLOBALS(
  char *fname;
  char *dir;
  struct arg_list *inc_file;
  struct arg_list *exc_file;
  char *tocmd;
  struct arg_list *exc;

  struct arg_list *inc, *pass;
  void *inodes, *handle;
)

struct tar_hdr {
  char name[100], mode[8], uid[8], gid[8],size[12], mtime[12], chksum[8],
       type, link[100], magic[8], uname[32], gname[32], major[8], minor[8],
       prefix[155], padd[12];
};

struct file_header {
  char *name, *link_target, *uname, *gname;
  off_t size;
  uid_t uid;
  gid_t gid;
  mode_t mode;
  time_t mtime;
  dev_t device;
};

struct archive_handler {
  int src_fd;
  struct file_header file_hdr;
  off_t offset;
  void (*extract_handler)(struct archive_handler*);
};

struct inode_list {
  struct inode_list *next;
  char *arg;
  ino_t ino;
  dev_t dev;
};

static void copy_in_out(int src, int dst, off_t size)
{
  int i, rd, rem = size%512, cnt;
  
  cnt = size/512 + (rem?1:0);

  for (i = 0; i < cnt; i++) {
    rd = (i == cnt-1 && rem) ? rem : 512;
    xreadall(src, toybuf, rd);
    writeall(dst, toybuf, rd);
  }
}

//convert to octal
static void itoo(char *str, int len, off_t val)
{
  char *t, tmp[sizeof(off_t)*3+1];
  int cnt  = sprintf(tmp, "%0*llo", len, val);

  t = tmp + cnt - len;
  if (*t == '0') t++;
  memcpy(str, t, len);
}

static struct inode_list *seen_inode(void **list, struct stat *st, char *name)
{
  if (!st) llist_traverse(*list, llist_free_arg);
  else if (!S_ISDIR(st->st_mode) && st->st_nlink > 1) {
    struct inode_list *new;

    for (new = *list; new; new = new->next)
      if(new->ino == st->st_ino && new->dev == st->st_dev)
        return new;

    new = xzalloc(sizeof(*new));
    new->ino = st->st_ino;
    new->dev = st->st_dev;
    new->arg = xstrdup(name);
    new->next = *list;
    *list = new;
  }
  return 0;
}

static void write_longname(struct archive_handler *tar, char *name, char type)
{
  struct tar_hdr tmp;
  unsigned int sum = 0;
  int i, sz = strlen(name) +1;
  char buf[512] = {0,};

  memset(&tmp, 0, sizeof(tmp));
  strcpy(tmp.name, "././@LongLink");
  sprintf(tmp.mode, "%0*d", sizeof(tmp.mode)-1, 0);
  sprintf(tmp.uid, "%0*d", sizeof(tmp.uid)-1, 0);
  sprintf(tmp.gid, "%0*d", sizeof(tmp.gid)-1, 0);
  sprintf(tmp.size, "%0*d", sizeof(tmp.size)-1, 0);
  sprintf(tmp.mtime, "%0*d", sizeof(tmp.mtime)-1, 0);
  itoo(tmp.size, sizeof(tmp.size), sz);
  tmp.type = type;
  memset(tmp.chksum, ' ', 8);
  strcpy(tmp.magic, "ustar  ");
  for (i= 0; i < 512; i++) sum += (unsigned int)((char*)&tmp)[i];
  itoo(tmp.chksum, sizeof(tmp.chksum)-1, sum);

  writeall(tar->src_fd, (void*) &tmp, sizeof(tmp));
  //write name to archive
  writeall(tar->src_fd, name, sz);
  if (sz%512) writeall(tar->src_fd, buf, (512-(sz%512)));
}

static int filter(struct arg_list *lst, char *name)
{
  struct arg_list *cur;

  for (cur = lst; cur; cur = cur->next)
    if (!fnmatch(cur->arg, name, 1<<3)) return 1;
  return 0;
}

static void add_file(struct archive_handler *tar, char **nam, struct stat *st)
{
  struct tar_hdr hdr;
  struct passwd *pw;
  struct group *gr;
  struct inode_list *node;
  int i, fd =-1;
  char *c, *p, *name = *nam, *lnk, *hname, buf[512] = {0,};
  unsigned int sum = 0;
  static int warn = 1;

  for (p = name; *p; p++)
    if ((p == name || p[-1] == '/') && *p != '/'
        && filter(TT.exc, p)) return;

  if (S_ISDIR(st->st_mode) && name[strlen(name)-1] != '/') {
    lnk = xmprintf("%s/",name);
    free(name);
    *nam = name = lnk;
  }
  hname = name;
  //remove leading '/' or relative path '../' component
  if (*hname == '/') hname++;
  if (!*hname) return;
  while ((c = strstr(hname, "../"))) hname = c + 3;
  if (warn && hname != name) {
    printf("removing leading '%.*s' "
        "from member names\n",hname-name, name);
    warn = 0;
  }

  memset(&hdr, 0, sizeof(hdr));
  strncpy(hdr.name, hname, sizeof(hdr.name));
  itoo(hdr.mode, sizeof(hdr.mode), st->st_mode &07777);
  itoo(hdr.uid, sizeof(hdr.uid), st->st_uid);
  itoo(hdr.gid, sizeof(hdr.gid), st->st_gid);
  itoo(hdr.size, sizeof(hdr.size), 0); //set size later
  itoo(hdr.mtime, sizeof(hdr.mtime), st->st_mtime);
  for (i=0; i<sizeof(hdr.chksum); i++) hdr.chksum[i] = ' ';

  if ((node = seen_inode(&TT.inodes, st, hname))) {
    //this is a hard link
    hdr.type = '1';
    if (strlen(node->arg) > sizeof(hdr.link))
      write_longname(tar, hname, 'K'); //write longname LINK
    strncpy(hdr.link, node->arg, sizeof(hdr.link));
  } else if (S_ISREG(st->st_mode)) {
    hdr.type = '0';
    if (st->st_size <= (off_t)0777777777777LL)
      itoo(hdr.size, sizeof(hdr.size), st->st_size);
    else {
      error_msg("can't store file '%s' of size '%d'\n", hname, st->st_size);
      return;
    }
  } else if (S_ISLNK(st->st_mode)) {
    hdr.type = '2'; //'K' long link
    if (!(lnk = xreadlink(name))) {
      perror_msg("readlink");
      return;
    }
    if (strlen(lnk) > sizeof(hdr.link))
      write_longname(tar, hname, 'K'); //write longname LINK
    strncpy(hdr.link, lnk, sizeof(hdr.link));
    free(lnk);
  }
  else if (S_ISDIR(st->st_mode)) hdr.type = '5';
  else if (S_ISFIFO(st->st_mode)) hdr.type = '6';
  else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
    hdr.type = (S_ISCHR(st->st_mode))?'3':'4';
    itoo(hdr.major, sizeof(hdr.major), major(st->st_rdev));
    itoo(hdr.minor, sizeof(hdr.minor), minor(st->st_rdev));
  } else {
    error_msg("unknown file type '%s'");
    return;
  }
  if (strlen(hname) > sizeof(hdr.name))
          write_longname(tar, hname, 'L'); //write longname NAME
  strcpy(hdr.magic, "ustar  ");
  if ((pw = getpwuid(st->st_uid)))
    snprintf(hdr.uname, sizeof(hdr.uname), "%s", pw->pw_name);
  else snprintf(hdr.uname, sizeof(hdr.uname), "%d", st->st_uid);

  if ((gr = getgrgid(st->st_gid)))
    snprintf(hdr.gname, sizeof(hdr.gname), "%s", gr->gr_name);
  else snprintf(hdr.gname, sizeof(hdr.gname), "%d", st->st_gid);

  //calculate chksum.
  for (i= 0; i < 512; i++) sum += (unsigned int)((char*)&hdr)[i];
  itoo(hdr.chksum, sizeof(hdr.chksum)-1, sum);
  if (toys.optflags & FLAG_v) printf("%s\n",hname);
  writeall(tar->src_fd, (void*)&hdr, 512);

  //write actual data to archive
  if (hdr.type != '0') return; //nothing to write
  if ((fd = open(name, O_RDONLY)) < 0) {
    perror_msg("can't open '%s'", name);
    return;
  }
  copy_in_out(fd, tar->src_fd, st->st_size);
  if (st->st_size%512) writeall(tar->src_fd, buf, (512-(st->st_size%512)));
  close(fd);
}

static int add_to_tar(struct dirtree *node)
{
  struct stat st;
  char *path;
  struct archive_handler *hdl = (struct archive_handler*)TT.handle;

  if (!fstat(hdl->src_fd, &st) && st.st_dev == node->st.st_dev
      && st.st_ino == node->st.st_ino) {
    error_msg("'%s' file is the archive; not dumped", TT.fname);
    return ((DIRTREE_RECURSE | ((toys.optflags & FLAG_h)?DIRTREE_SYMFOLLOW:0)));
  }

  if (node->parent && !dirtree_notdotdot(node)) return 0;
  path = dirtree_path(node, 0);
  add_file(hdl, &path, &(node->st)); //path may be modified
  free(path);
  if (toys.optflags & FLAG_no_recursion) return 0;
  return ((DIRTREE_RECURSE | ((toys.optflags & FLAG_h)?DIRTREE_SYMFOLLOW:0)));
}

static void compress_stream(struct archive_handler *tar_hdl)
{
  int pipefd[2];
  pid_t cpid;

  if (pipe(pipefd) == -1) error_exit("pipe");

  signal(SIGPIPE, SIG_IGN);
  cpid = fork();
  if (cpid == -1) perror_exit("fork");

  if (!cpid) {    /* Child reads from pipe */
    char *argv[] = {"gzip", "-f", NULL};
    xclose(pipefd[1]); /* Close unused write*/
    dup2(pipefd[0], 0);
    dup2(tar_hdl->src_fd, 1); //write to tar fd
    xexec(argv);
  } else {
    xclose(pipefd[0]);          /* Close unused read end */
    dup2(pipefd[1], tar_hdl->src_fd); //write to pipe
  }
}

static void extract_to_stdout(struct archive_handler *tar)
{
  struct file_header *file_hdr = &tar->file_hdr;

  copy_in_out(tar->src_fd, 0, file_hdr->size);
  tar->offset += file_hdr->size;
}

static void extract_to_command(struct archive_handler *tar)
{
  int pipefd[2], status = 0;
  pid_t cpid;
  struct file_header *file_hdr = &tar->file_hdr;

  if (pipe(pipefd) == -1) error_exit("pipe");
  if (!S_ISREG(file_hdr->mode)) return; //only regular files are supported.

  cpid = fork();
  if (cpid == -1) perror_exit("fork");

  if (!cpid) {    // Child reads from pipe
    char buf[64], *argv[4] = {"sh", "-c", TT.tocmd, NULL};

    setenv("TAR_FILETYPE", "f", 1);
    sprintf(buf, "%0o", file_hdr->mode);
    setenv("TAR_MODE", buf, 1);
    sprintf(buf, "%ld", (long)file_hdr->size);
    setenv("TAR_SIZE", buf, 1);
    setenv("TAR_FILENAME", file_hdr->name, 1);
    setenv("TAR_UNAME", file_hdr->uname, 1);
    setenv("TAR_GNAME", file_hdr->gname, 1);
    sprintf(buf, "%0o", (int)file_hdr->mtime);
    setenv("TAR_MTIME", buf, 1);
    sprintf(buf, "%0o", file_hdr->uid);
    setenv("TAR_UID", buf, 1);
    sprintf(buf, "%0o", file_hdr->gid);
    setenv("TAR_GID", buf, 1);

    xclose(pipefd[1]); // Close unused write
    dup2(pipefd[0], 0);
    signal(SIGPIPE, SIG_DFL);
    xexec(argv);
  } else {
    xclose(pipefd[0]);  // Close unused read end
    copy_in_out(tar->src_fd, pipefd[1], file_hdr->size);
    tar->offset += file_hdr->size;
    xclose(pipefd[1]);
    waitpid(cpid, &status, 0);
    if (WIFSIGNALED(status))
      xprintf("tar : %d: child returned %d\n", cpid, WTERMSIG(status));
  }
}

static void extract_to_disk(struct archive_handler *tar)
{
  int flags, dst_fd = -1;
  char *s;
  struct stat ex;
  struct file_header *file_hdr = &tar->file_hdr;

  if (file_hdr->name[strlen(file_hdr->name)-1] == '/')
    file_hdr->name[strlen(file_hdr->name)-1] = 0;
  //Regular file with preceding path
  if ((s = strrchr(file_hdr->name, '/'))) {
    if (mkpathat(AT_FDCWD, file_hdr->name, 00, 2) && errno !=EEXIST) {
      error_msg(":%s: not created", file_hdr->name);
      return;
    }
  }

  //remove old file, if exists
  if (!(toys.optflags & FLAG_k) && !S_ISDIR(file_hdr->mode)
      && !lstat( file_hdr->name, &ex)) {
    if (unlink(file_hdr->name)) {
      perror_msg("can't remove: %s",file_hdr->name);
    }
  }

  //hard link
  if (S_ISREG(file_hdr->mode) && file_hdr->link_target) {
    if (link(file_hdr->link_target, file_hdr->name))
      perror_msg("can't link '%s' -> '%s'",file_hdr->name, file_hdr->link_target);
    goto COPY;
  }

  switch (file_hdr->mode & S_IFMT) {
    case S_IFREG:
      flags = O_WRONLY|O_CREAT|O_EXCL;
      if (toys.optflags & FLAG_overwrite) flags = O_WRONLY|O_CREAT|O_TRUNC;
      dst_fd = open(file_hdr->name, flags, file_hdr->mode & 07777);
      if (dst_fd == -1) perror_msg("%s: can't open", file_hdr->name);
      break;
    case S_IFDIR:
      if ((mkdir(file_hdr->name, file_hdr->mode) == -1) && errno != EEXIST)
        perror_msg("%s: can't create", file_hdr->name);
      break;
    case S_IFLNK:
      if (symlink(file_hdr->link_target, file_hdr->name))
        perror_msg("can't link '%s' -> '%s'",file_hdr->name, file_hdr->link_target);
      break;
    case S_IFBLK:
    case S_IFCHR:
    case S_IFIFO:
      if (mknod(file_hdr->name, file_hdr->mode, file_hdr->device))
        perror_msg("can't create '%s'", file_hdr->name);
      break;
    default:
      printf("type not yet supported\n");
      break;
  }

  //copy file....
COPY:
  copy_in_out(tar->src_fd, dst_fd, file_hdr->size);
  tar->offset += file_hdr->size;
  close(dst_fd);

  if (S_ISLNK(file_hdr->mode)) return;
  if (!(toys.optflags & FLAG_o)) {
    //set ownership..., --no-same-owner, --numeric-owner
    uid_t u = file_hdr->uid;
    gid_t g = file_hdr->gid;

    if (!(toys.optflags & FLAG_numeric_owner)) {
      struct group *gr = getgrnam(file_hdr->gname);
      struct passwd *pw = getpwnam(file_hdr->uname);
      if (pw) u = pw->pw_uid;
      if (gr) g = gr->gr_gid;
    }
    chown(file_hdr->name, u, g);
  }

  if (toys.optflags & FLAG_p) // || !(toys.optflags & FLAG_no_same_permissions))
    chmod(file_hdr->name, file_hdr->mode);

  //apply mtime
  if (!(toys.optflags & FLAG_m)) {
    struct timeval times[2] = {{file_hdr->mtime, 0},{file_hdr->mtime, 0}};
    utimes(file_hdr->name, times);
  }
}

static void add_to_list(struct arg_list **llist, char *name)
{
  struct arg_list **list = llist;

  while (*list) list=&((*list)->next);
  *list = xzalloc(sizeof(struct arg_list));
  (*list)->arg = name;
  if ((name[strlen(name)-1] == '/') && strlen(name) != 1)
    name[strlen(name)-1] = '\0';
}

static void add_from_file(struct arg_list **llist, struct arg_list *flist)
{
  char *line = NULL;

  while (flist) {
    int fd = 0;

    if (strcmp((char *)flist->arg, "-"))
      fd = xopen((char *)flist->arg, O_RDONLY);

    while ((line = get_line(fd))) {
      add_to_list(llist, line);
    }
    if (fd) close(fd);
    flist = flist->next;
  }
}

static struct archive_handler *init_handler()
{
  struct archive_handler *tar_hdl = xzalloc(sizeof(struct archive_handler));
  tar_hdl->extract_handler = extract_to_disk;
  return tar_hdl;
}

//convert octal to int
static int otoi(char *str, int len)
{
  long val;
  char *endp, inp[len+1]; //1 for NUL termination

  memcpy(inp, str, len);
  inp[len] = '\0'; //nul-termination made sure
  val = strtol(inp, &endp, 8);
  if (*endp && *endp != ' ') error_exit("invalid param");
  return (int)val;
}

static void extract_stream(struct archive_handler *tar_hdl)
{
  int pipefd[2];              
  pid_t cpid;                 

  if (pipe(pipefd) == -1) error_exit("pipe");

  cpid = fork();
  if (cpid == -1) perror_exit("fork");

  if (!cpid) {    /* Child reads from pipe */
    char *argv[] = {"gunzip", "-cf", "-", NULL};
    xclose(pipefd[0]); /* Close unused read*/
    dup2(tar_hdl->src_fd, 0);
    dup2(pipefd[1], 1); //write to pipe
    xexec(argv);
  } else {
    xclose(pipefd[1]);          /* Close unused read end */
    dup2(pipefd[0], tar_hdl->src_fd); //read from pipe
  }
}

static char *process_extended_hdr(struct archive_handler *tar, int size)
{
  char *value = NULL, *p, *buf = xzalloc(size+1);

  if (readall(tar->src_fd, buf, size) != size) error_exit("short read");
  buf[size] = 0;
  tar->offset += size;
  p = buf;

  while (size) {
    char *key;
    int len, n;

    // extended records are of the format: "LEN NAME=VALUE\n"
    sscanf(p, "%d %n", &len, &n);
    key = p + n;
    p += len;
    size -= len;
    p[-1] = 0;
    if (size < 0) {
      error_msg("corrupted extended header");
      break;
    }

    len = strlen("path=");
    if (!strncmp(key, "path=", len)) {
      value = key + strlen("path=");
      break;
    }
  }
  if (value) value = xstrdup(value);
  free(buf);
  return value;
}

static void tar_skip(struct archive_handler *tar, int sz)
{
  int x;

  while ((x = lskip(tar->src_fd, sz))) {
    tar->offset += sz - x;
    sz = x;
  }
  tar->offset += sz;
}

static void unpack_tar(struct archive_handler *tar_hdl)
{
  struct tar_hdr tar;
  struct file_header *file_hdr;
  int i, j, maj, min, sz, e = 0;
  unsigned int cksum;
  unsigned char *gzMagic;
  char *longname = NULL, *longlink = NULL;

  while (1) {
    cksum = 0;
    if (tar_hdl->offset % 512) {
      sz = 512 - tar_hdl->offset % 512;
      tar_skip(tar_hdl, sz);
    }
    i = readall(tar_hdl->src_fd, &tar, 512);
    tar_hdl->offset += i;
    if (i != 512) {
      if (i >= 2) goto CHECK_MAGIC; //may be a small (<512 byte)zipped file
      error_exit("read error");
    }

    if (!tar.name[0]) {
      if (e) return; //end of tar 2 empty blocks
      e = 1;//empty jump to next block
      continue;
    }
    if (strncmp(tar.magic, "ustar", 5)) {
      //try detecting by reading magic
CHECK_MAGIC:
      gzMagic = (unsigned char*)&tar;
      if ((gzMagic[0] == 0x1f) && (gzMagic[1] == 0x8b) 
          && !lseek(tar_hdl->src_fd, -i, SEEK_CUR)) {
        tar_hdl->offset -= i;
        extract_stream(tar_hdl);
        continue;
      }
      error_exit("invalid tar format");
    }

    for (j = 0; j<148; j++) cksum += (unsigned int)((char*)&tar)[j];
    for (j = 156; j<500; j++) cksum += (unsigned int)((char*)&tar)[j];
    //cksum field itself treated as ' '
    for ( j= 0; j<8; j++) cksum += (unsigned int)' ';

    if (cksum != otoi(tar.chksum, sizeof(tar.chksum))) error_exit("wrong cksum");

    file_hdr = &tar_hdl->file_hdr;
    memset(file_hdr, 0, sizeof(struct file_header));
    file_hdr->mode = otoi(tar.mode, sizeof(tar.mode));
    file_hdr->uid = otoi(tar.uid, sizeof(tar.uid));
    file_hdr->gid = otoi(tar.gid, sizeof(tar.gid));
    file_hdr->size = otoi(tar.size, sizeof(tar.size));
    file_hdr->mtime = otoi(tar.mtime, sizeof(tar.mtime));
    file_hdr->uname = xstrdup(tar.uname);
    file_hdr->gname = xstrdup(tar.gname);
    maj = otoi(tar.major, sizeof(tar.major));
    min = otoi(tar.minor, sizeof(tar.minor));
    file_hdr->device = makedev(maj, min);

    if (tar.type <= '7') {
      if (tar.link[0]) {
        sz = sizeof(tar.link);
        file_hdr->link_target = xmalloc(sz + 1);
        memcpy(file_hdr->link_target, tar.link, sz);
        file_hdr->link_target[sz] = '\0';
      }

      file_hdr->name = xzalloc(256);// pathname supported size
      if (tar.prefix[0]) {
        memcpy(file_hdr->name, tar.prefix, sizeof(tar.prefix));
        sz = strlen(file_hdr->name);
        if (file_hdr->name[sz-1] != '/') file_hdr->name[sz] = '/';
      }
      sz = strlen(file_hdr->name);
      memcpy(file_hdr->name + sz, tar.name, sizeof(tar.name));
      if (file_hdr->name[255]) error_exit("filename too long");
    }

    switch (tar.type) {
      //    case '\0':
      case '0':
      case '7':
      case '1': //Hard Link
        file_hdr->mode |= S_IFREG;
        break;
      case '2':
        file_hdr->mode |= S_IFLNK;
        break;
      case '3':
        file_hdr->mode |= S_IFCHR;
        break;
      case '4':
        file_hdr->mode |= S_IFBLK;
        break;
      case '5':
        file_hdr->mode |= S_IFDIR;
        break;
      case '6':
        file_hdr->mode |= S_IFIFO;
        break;
      case 'K':
        longlink = xzalloc(file_hdr->size +1);
        xread(tar_hdl->src_fd, longlink, file_hdr->size);
        tar_hdl->offset += file_hdr->size;
        continue;
      case 'L':
        free(longname);
        longname = xzalloc(file_hdr->size +1);           
        xread(tar_hdl->src_fd, longname, file_hdr->size);
        tar_hdl->offset += file_hdr->size;
        continue;
      case 'D':
      case 'M':
      case 'N':
      case 'S':
      case 'V':
      case 'g':  // pax global header
        tar_skip(tar_hdl, file_hdr->size);
        continue;
      case 'x':  // pax extended header
        free(longname);
        longname = process_extended_hdr(tar_hdl, file_hdr->size);
        continue;
      default: break;
    }

    if (longname) {
      free(file_hdr->name);
      file_hdr->name = longname;
      longname = NULL;
    }
    if (longlink) {
      free(file_hdr->link_target);
      file_hdr->link_target = longlink;
      longlink = NULL;
    }

    if ((file_hdr->mode & S_IFREG) && 
        file_hdr->name[strlen(file_hdr->name)-1] == '/') {
      file_hdr->name[strlen(file_hdr->name)-1] = '\0';
      file_hdr->mode &= ~S_IFREG;
      file_hdr->mode |= S_IFDIR;
    }

    if ((file_hdr->link_target && *(file_hdr->link_target)) 
        || S_ISLNK(file_hdr->mode) || S_ISDIR(file_hdr->mode))
      file_hdr->size = 0;

    if (filter(TT.exc, file_hdr->name) ||
        (TT.inc && !filter(TT.inc, file_hdr->name))) goto SKIP;
    add_to_list(&TT.pass, xstrdup(file_hdr->name));

    if (toys.optflags & FLAG_t) {
      if (toys.optflags & FLAG_v) {
        char perm[11];
        struct tm *lc = localtime((const time_t*)&(file_hdr->mtime));

        mode_to_string(file_hdr->mode, perm);
        printf("%s %s/%s %9ld %d-%02d-%02d %02d:%02d:%02d ",perm,file_hdr->uname,
            file_hdr->gname, (long)file_hdr->size, 1900+lc->tm_year,
            1+lc->tm_mon, lc->tm_mday, lc->tm_hour, lc->tm_min, lc->tm_sec);
      }
      printf("%s",file_hdr->name);
      if (file_hdr->link_target) printf(" -> %s",file_hdr->link_target);
      xputc('\n');
SKIP:
      tar_skip(tar_hdl, file_hdr->size);
    } else {
      if (toys.optflags & FLAG_v) printf("%s\n",file_hdr->name);
      tar_hdl->extract_handler(tar_hdl);
    }
    free(file_hdr->name);
    free(file_hdr->link_target);
    free(file_hdr->uname);
    free(file_hdr->gname);
  }
}

void tar_main(void)
{
  struct archive_handler *tar_hdl;
  int fd = 0, flags = O_RDONLY;
  struct arg_list *tmp;
  char **args = toys.optargs;

  if (!toys.argv[1]) {
    toys.exithelp++;
    error_exit(NULL);
  }

  if (!geteuid()) toys.optflags |= FLAG_p;

  for (tmp = TT.exc; tmp; tmp = tmp->next)
    tmp->arg = xstrdup(tmp->arg); //freeing at the end fails otherwise

  while(*args) add_to_list(&TT.inc, xstrdup(*args++));
  if (toys.optflags & FLAG_X) add_from_file(&TT.exc, TT.exc_file);
  if (toys.optflags & FLAG_T) add_from_file(&TT.inc, TT.inc_file);

  if (toys.optflags & FLAG_c) {
    if (!TT.inc) error_exit("empty archive");
    fd = 1, flags = O_WRONLY|O_CREAT|O_TRUNC;
  }
  if ((toys.optflags & FLAG_f) && strcmp(TT.fname, "-")) 
    fd = xcreate(TT.fname, flags, 0666);
  if (toys.optflags & FLAG_C) xchdir(TT.dir);

  tar_hdl = init_handler();
  tar_hdl->src_fd = fd;

  if (toys.optflags & FLAG_x || toys.optflags & FLAG_t) {
    if (toys.optflags & FLAG_O) tar_hdl->extract_handler = extract_to_stdout;
    if (toys.optflags & FLAG_to_command) {
      signal(SIGPIPE, SIG_IGN); //will be using pipe between child & parent
      tar_hdl->extract_handler = extract_to_command;
    }
    if (toys.optflags & FLAG_z) extract_stream(tar_hdl);
    unpack_tar(tar_hdl);
    for (tmp = TT.inc; tmp; tmp = tmp->next)
      if (!filter(TT.exc, tmp->arg) && !filter(TT.pass, tmp->arg))
        error_msg("'%s' not in archive", tmp->arg);
  } else if (toys.optflags & FLAG_c) {
    //create the tar here.
    if (toys.optflags & FLAG_z) compress_stream(tar_hdl);
    for (tmp = TT.inc; tmp; tmp = tmp->next) {
      TT.handle = tar_hdl;
      //recurse thru dir and add files to archive
      struct dirtree *root = dirtree_add_node(0,tmp->arg,toys.optflags & FLAG_h);

      if (root) dirtree_handle_callback(root, add_to_tar);
    }
    memset(toybuf, 0, 1024);
    writeall(tar_hdl->src_fd, toybuf, 1024);
    seen_inode(&TT.inodes, 0, 0);
  }

  if (CFG_TOYBOX_FREE) {
    close(tar_hdl->src_fd);
    free(tar_hdl);
    llist_traverse(TT.exc, llist_free_arg);
    llist_traverse(TT.inc, llist_free_arg);
    llist_traverse(TT.pass, llist_free_arg);
  }
}