From c237aeab10add3d1a9da260c297a7f0587ded13e Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Sun, 21 Apr 2019 04:46:31 -0500 Subject: Promote tar. There's probably more to do, but it seems usable at this point. --- toys/pending/tar.c | 814 ----------------------------------------------------- toys/posix/tar.c | 814 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 814 insertions(+), 814 deletions(-) delete mode 100644 toys/pending/tar.c create mode 100644 toys/posix/tar.c diff --git a/toys/pending/tar.c b/toys/pending/tar.c deleted file mode 100644 index f31b5f2b..00000000 --- a/toys/pending/tar.c +++ /dev/null @@ -1,814 +0,0 @@ -/* tar.c - create/extract archives - * - * Copyright 2014 Ashwini Kumar - * - * For the command, see - * http://pubs.opengroup.org/onlinepubs/007908799/xcu/tar.html - * For the modern file format, see - * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 - * https://en.wikipedia.org/wiki/Tar_(computing)#File_format - * https://www.gnu.org/software/tar/manual/html_node/Tar-Internals.html - * - * For writing to external program - * http://www.gnu.org/software/tar/manual/html_node/Writing-to-an-External-Program.html - * - * Toybox will never implement the "pax" command as a matter of policy. - * - * Why --exclude pattern but no --include? tar cvzf a.tgz dir --include '*.txt' - * Extract into dir same as filename, --restrict? "Tarball is splodey" - * - -USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN)) - -config TAR - bool "tar" - default n - help - usage: tar [-cxtfvohmjkO] [-XT FILE] [-f TARFILE] [-C DIR] - - Create, extract, or list files in a .tar (or compressed t?z) file. - - Options: - c Create x Extract t Test - f Name of TARFILE C Change to DIR first v Verbose: show filenames - o Ignore owner h Follow symlinks m Ignore mtime - j bzip2 compression z gzip compression - O Extract to stdout X exclude names in FILE T include names in FILE - --exclude=FILE File pattern(s) to exclude - --restrict All archive contents must extract under a single subdirctory. -*/ - -#define FOR_tar -#include "toys.h" - -GLOBALS( - char *f, *C; - struct arg_list *T, *X; - char *to_command, *owner, *group, *mtime; - struct arg_list *exclude; - - struct double_list *incl, *excl, *seen; - struct string_list *dirs; - char *cwd; - int fd, ouid, ggid, hlc, warn, adev, aino; - time_t mtt; - - // hardlinks seen so far (hlc many) - struct { - char *arg; - ino_t ino; - dev_t dev; - } *hlx; - - // Parsed information about a tar header. - struct tar_header { - char *name, *link_target, *uname, *gname; - long long size; - uid_t uid; - gid_t gid; - mode_t mode; - time_t mtime; - dev_t device; - } hdr; -) - -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]; -}; - -// convert from int to octal (or base-256) -static void itoo(char *str, int len, unsigned long long val) -{ - // Do we need binary encoding? - if (!(val>>(3*(len-1)))) sprintf(str, "%0*llo", len-1, val); - else { - *str = 128; - while (--len) *++str = val>>(3*len); - } -} -#define ITOO(x, y) itoo(x, sizeof(x), y) - -// convert octal (or base-256) to int -static unsigned long long otoi(char *str, unsigned len) -{ - unsigned long long val = 0; - - // When tar value too big or octal, use binary encoding with high bit set - if (128&*str) while (--len) val = (val<<8)+*++str; - else { - while (len && *str>='0' && *str<='7') val = val*8+*str++-'0', len--; - if (len && *str && *str != ' ') error_exit("bad header"); - } - - return val; -} -#define OTOI(x) otoi(x, sizeof(x)) - -// Calculate packet checksum, with cksum field treated as 8 spaces -static unsigned cksum(void *data) -{ - unsigned i, cksum = 8*' '; - - for (i = 0; i<500; i += (i==147) ? 9 : 1) cksum += ((char *)data)[i]; - - return cksum; -} - -static void write_longname(char *name, char type) -{ - struct tar_hdr tmp; - int sz = strlen(name) +1; - - memset(&tmp, 0, sizeof(tmp)); - strcpy(tmp.name, "././@LongLink"); - ITOO(tmp.uid, 0); - ITOO(tmp.gid, 0); - ITOO(tmp.size, sz); - ITOO(tmp.mtime, 0); - tmp.type = type; - strcpy(tmp.magic, "ustar "); - - // Historical nonsense to match other implementations. Never used. - ITOO(tmp.mode, 0644); - strcpy(tmp.uname, "root"); - strcpy(tmp.gname, "root"); - - // Calculate checksum. Since 512*255 = 0377000 in octal, this can never - // use more than 6 digits. The last byte is ' ' or historical reasons. - itoo(tmp.chksum, sizeof(tmp.chksum)-1, cksum(&tmp)); - tmp.chksum[7] = ' '; - - // write header and name, padded with NUL to block size - xwrite(TT.fd, &tmp, 512); - xwrite(TT.fd, name, sz); - if (sz%512) xwrite(TT.fd, toybuf, 512-(sz%512)); -} - -static struct double_list *filter(struct double_list *lst, char *name) -{ - struct double_list *end = lst; - - if (lst) - // constant is FNM_LEADING_DIR - do if (!fnmatch(lst->data, name, 1<<3)) return lst; - while (end != (lst = lst->next)); - - return 0; -} - -static void skippy(long long len) -{ - if (lskip(TT.fd, len)) perror_exit("EOF"); -} - -// allocate and read data from TT.fd -static void alloread(void *buf, int len) -{ - // actually void **, but automatic typecasting doesn't work with void ** :( - void **b = buf; - - free(*b); - *b = xmalloc(len+1); - xreadall(TT.fd, *b, len); - b[len] = 0; -} - -// callback from dirtree to create archive -static int add_to_tar(struct dirtree *node) -{ - struct stat *st = &(node->st); - struct tar_hdr hdr; - struct passwd *pw = pw; - struct group *gr = gr; - int i, fd =-1; - char *c, *p, *name, *lnk = lnk, *hname; - - if (!dirtree_notdotdot(node)) return 0; - if (TT.adev == st->st_dev && TT.aino == st->st_ino) { - error_msg("'%s' file is the archive; not dumped", node->name); - return 0; - } - - i = 1; - name = dirtree_path(node, &i); - - // exclusion defaults to --no-anchored and --wildcards-match-slash - for (p = name; *p;) { - if (filter(TT.excl, p)) goto done; - while (*p && *p!='/') p++; - while (*p=='/') p++; - } - - // Consume the 1 extra byte alocated in dirtree_path() - if (S_ISDIR(st->st_mode) && name[i-1] != '/') strcat(name, "/"); - - // remove leading / and any .. entries from saved name - for (hname = name; *hname == '/'; hname++); - for (c = hname;;) { - if (!(c = strstr(c, ".."))) break; - if (c == hname || c[-1] == '/') { - if (!c[2]) goto done; - if (c[2]=='/') c = hname = c+3; - } else c+= 2; - } - if (!*hname) goto done; - - if (TT.warn && hname != name) { - fprintf(stderr, "removing leading '%.*s' from member names\n", - (int)(hname-name), name); - TT.warn = 0; - } - - if (TT.owner) st->st_uid = TT.ouid; - if (TT.group) st->st_gid = TT.ggid; - if (TT.mtime) st->st_mtime = TT.mtt; - - memset(&hdr, 0, sizeof(hdr)); - strncpy(hdr.name, hname, sizeof(hdr.name)); - ITOO(hdr.mode, st->st_mode &07777); - ITOO(hdr.uid, st->st_uid); - ITOO(hdr.gid, st->st_gid); - ITOO(hdr.size, 0); //set size later - ITOO(hdr.mtime, st->st_mtime); - strcpy(hdr.magic, "ustar "); - - // Hard link or symlink? i=0 neither, i=1 hardlink, i=2 symlink - - // Are there hardlinks to a non-directory entry? - if (st->st_nlink>1 && !S_ISDIR(st->st_mode)) { - // Have we seen this dev&ino before? - for (i = 0; ist_ino == TT.hlx[i].ino && st->st_dev == TT.hlx[i].dev) - break; - } - if (i != TT.hlc) { - lnk = TT.hlx[i].arg; - i = 1; - } else { - // first time we've seen it. Store as normal file, but remember it. - if (!(TT.hlc&255)) TT.hlx = xrealloc(TT.hlx, TT.hlc+256); - TT.hlx[TT.hlc].arg = xstrdup(hname); - TT.hlx[TT.hlc].ino = st->st_ino; - TT.hlx[TT.hlc].dev = st->st_dev; - TT.hlc++; - i = 0; - } - } else i = 0; - - // !i because hardlink to a symlink is a thing. - if (!i && S_ISLNK(st->st_mode)) { - i = 2; - lnk = xreadlink(name); - } - - // Handle file types - if (i) { - hdr.type = '0'+i; - if (i==2 && !(lnk = xreadlink(name))) { - perror_msg("readlink"); - goto done; - } - if (strlen(lnk) > sizeof(hdr.link)) write_longname(lnk, 'K'); - strncpy(hdr.link, lnk, sizeof(hdr.link)); - if (i) free(lnk); - } else if (S_ISREG(st->st_mode)) { - hdr.type = '0'; - ITOO(hdr.size, st->st_size); - } 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, dev_major(st->st_rdev)); - ITOO(hdr.minor, dev_minor(st->st_rdev)); - } else { - error_msg("unknown file type '%o'", st->st_mode & S_IFMT); - goto done; - } - - if (strlen(hname) > sizeof(hdr.name)) write_longname(hname, 'L'); - - if (!FLAG(numeric_owner)) { - if (TT.owner || (pw = bufgetpwuid(st->st_uid))) - strncpy(hdr.uname, TT.owner ? TT.owner : pw->pw_name, sizeof(hdr.uname)); - if (TT.group || (gr = bufgetgrgid(st->st_gid))) - strncpy(hdr.gname, TT.group ? TT.group : gr->gr_name, sizeof(hdr.gname)); - } - - itoo(hdr.chksum, sizeof(hdr.chksum)-1, cksum(&hdr)); - hdr.chksum[7] = ' '; - - if (FLAG(v)) printf("%s\n", hname); - - // Write header and data to archive - xwrite(TT.fd, &hdr, 512); - if (hdr.type == '0') { - if ((fd = open(name, O_RDONLY)) < 0) perror_msg("can't open '%s'", name); - else { - xsendfile_pad(fd, TT.fd, st->st_size); - if (st->st_size%512) writeall(TT.fd, toybuf, (512-(st->st_size%512))); - close(fd); - } - } -done: - free(name); - - return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!FLAG(no_recursion); -} - -static void wsettime(char *s, long long sec) -{ - struct timespec times[2] = {{sec, 0},{sec, 0}}; - - if (utimensat(AT_FDCWD, s, times, AT_SYMLINK_NOFOLLOW)) - perror_msg("settime %lld %s", sec, s); -} - -// Do pending directory utimes(), NULL to flush all. -static int dirflush(char *name) -{ - char *s = 0, *ss; - - // Barf if name not in TT.cwd - if (name) { - ss = s = xabspath(name, -1); - if (TT.cwd[1] && (!strstart(&ss, TT.cwd) || *ss!='/')) { - error_msg("'%s' not under '%s'", name, TT.cwd); - free(s); - - return 1; - } - - if (FLAG(restrict)) { - free(TT.cwd); - TT.cwd = strdup(s); - toys.optflags ^= FLAG_restrict; - } - } - - // Set deferred utimes() for directories this file isn't under. - // (Files must be depth-first ordered in tarball for this to matter.) - while (TT.dirs) { - - // If next file is under (or equal to) this dir, keep waiting - if (name && strstart(&ss, ss = s) && (!*ss || *ss=='/')) break; - - wsettime(TT.dirs->str+sizeof(long long), *(long long *)TT.dirs->str); - free(llist_pop(&TT.dirs)); - } - free(s); - - // name was under TT.cwd - return 0; -} - -static void extract_to_disk(void) -{ - char *name = TT.hdr.name; - int ala = TT.hdr.mode; - - if (dirflush(name)) { - if (S_ISREG(ala) && !TT.hdr.link_target) skippy(TT.hdr.size); - - return; - } - - // create path before file if necessary - if (strrchr(name, '/') && mkpath(name) && errno !=EEXIST) - return perror_msg(":%s: can't mkdir", name); - - // remove old file, if exists - if (!FLAG(k) && !S_ISDIR(ala) && unlink(name) && errno!=ENOENT) - return perror_msg("can't remove: %s", name); - - if (S_ISREG(ala)) { - // hardlink? - if (TT.hdr.link_target) { - if (link(TT.hdr.link_target, name)) - return perror_msg("can't link '%s' -> '%s'", name, TT.hdr.link_target); - // write contents - } else { - int fd = xcreate(name, O_WRONLY|O_CREAT|(FLAG(overwrite)?O_TRUNC:O_EXCL), - WARN_ONLY|(ala & 07777)); - if (fd != -1) { - xsendfile_len(TT.fd, fd, TT.hdr.size); - close(fd); - } - } - } else if (S_ISDIR(ala)) { - if ((mkdir(name, 0700) == -1) && errno != EEXIST) - return perror_msg("%s: can't create", TT.hdr.name); - } else if (S_ISLNK(ala)) { - if (symlink(TT.hdr.link_target, TT.hdr.name)) - return perror_msg("can't link '%s' -> '%s'", name, TT.hdr.link_target); - } else if (mknod(name, ala, TT.hdr.device)) - return perror_msg("can't create '%s'", name); - - // Set ownership - if (!FLAG(o) && !geteuid()) { - int u = TT.hdr.uid, g = TT.hdr.gid; - - if (TT.owner) TT.hdr.uid = TT.ouid; - else if (!FLAG(numeric_owner) && *TT.hdr.uname) { - struct passwd *pw = getpwnam(TT.hdr.uname); - if (pw && (TT.owner || !FLAG(numeric_owner))) TT.hdr.uid = pw->pw_uid; - } - - if (TT.group) TT.hdr.gid = TT.ggid; - else if (!FLAG(numeric_owner) && *TT.hdr.uname) { - struct group *gr = getgrnam(TT.hdr.gname); - if (gr) TT.hdr.gid = gr->gr_gid; - } - - if (lchown(name, u, g)) perror_msg("chown %d:%d '%s'", u, g, name);; - } - - if (!S_ISLNK(ala)) chmod(TT.hdr.name, FLAG(p) ? ala : ala&0777); - - // Apply mtime. - if (!FLAG(m)) { - if (S_ISDIR(ala)) { - struct string_list *sl; - - // Writing files into a directory changes directory timestamps, so - // defer mtime updates until contents written. - - sl = xmalloc(sizeof(struct string_list)+sizeof(long long)+strlen(name)+1); - *(long long *)sl->str = TT.hdr.mtime; - strcpy(sl->str+sizeof(long long), name); - sl->next = TT.dirs; - TT.dirs = sl; - } else wsettime(TT.hdr.name, TT.hdr.mtime); - } -} - -static void unpack_tar(struct tar_hdr *first) -{ - struct double_list *walk, *delete; - struct tar_hdr tar; - int i, and = 0; - unsigned maj, min; - char *s; - - for (;;) { - if (first) { - memcpy(&tar, first, i = 512); - first = 0; - } else { - // align to next block and read it - if (TT.hdr.size%512) skippy(512-TT.hdr.size%512); - i = readall(TT.fd, &tar, 512); - } - - if (i && i!=512) error_exit("short header"); - - // Two consecutive empty headers ends tar even if there's more data - if (!i || !*tar.name) { - if (!i || and++) return; - TT.hdr.size = 0; - continue; - } - // ensure null temination even of pathological packets - tar.padd[0] = and = 0; - - // Is this a valid Unix Standard TAR header? - if (memcmp(tar.magic, "ustar", 5)) error_exit("bad header"); - if (cksum(&tar) != OTOI(tar.chksum)) error_exit("bad cksum"); - TT.hdr.size = OTOI(tar.size); - - // If this header isn't writing something to the filesystem - if (tar.type<'0' || tar.type>'7') { - - // Long name extension header? - if (tar.type == 'K') alloread(&TT.hdr.link_target, TT.hdr.size); - else if (tar.type == 'L') alloread(&TT.hdr.name, TT.hdr.size); - else if (tar.type == 'x') { - char *p, *buf = 0; - int i, len, n; - - // Posix extended record "LEN NAME=VALUE\n" format - alloread(&buf, TT.hdr.size); - for (p = buf; (p-buf)TT.hdr.size) { - error_msg("bad header"); - break; - } - p[len-1] = 0; - if (i == 2) { - TT.hdr.name = xstrdup(p+n); - break; - } - } - free(buf); - - // Ignore everything else. - } else skippy(TT.hdr.size); - - continue; - } - - // At this point, we have something to output. Convert metadata. - TT.hdr.mode = OTOI(tar.mode); - TT.hdr.mode |= (char []){8,8,10,2,6,4,1,8}[tar.type-'0']<<12; - TT.hdr.uid = OTOI(tar.uid); - TT.hdr.gid = OTOI(tar.gid); - TT.hdr.mtime = OTOI(tar.mtime); - maj = OTOI(tar.major); - min = OTOI(tar.minor); - TT.hdr.device = dev_makedev(maj, min); - - TT.hdr.uname = xstrndup(TT.owner ? TT.owner : tar.uname, sizeof(tar.uname)); - TT.hdr.gname = xstrndup(TT.group ? TT.group : tar.gname, sizeof(tar.gname)); - - if (TT.owner) TT.hdr.uid = TT.ouid; - else if (!FLAG(numeric_owner)) { - struct passwd *pw = getpwnam(TT.hdr.uname); - if (pw && (TT.owner || !FLAG(numeric_owner))) TT.hdr.uid = pw->pw_uid; - } - - if (TT.group) TT.hdr.gid = TT.ggid; - else if (!FLAG(numeric_owner)) { - struct group *gr = getgrnam(TT.hdr.gname); - if (gr) TT.hdr.gid = gr->gr_gid; - } - - if (!TT.hdr.link_target && *tar.link) - TT.hdr.link_target = xstrndup(tar.link, sizeof(tar.link)); - if (!TT.hdr.name) { - // Glue prefix and name fields together with / if necessary - i = strnlen(tar.prefix, sizeof(tar.prefix)); - TT.hdr.name = xmprintf("%.*s%s%.*s", i, tar.prefix, - (i && tar.prefix[i-1] != '/') ? "/" : "", - (int)sizeof(tar.name), tar.name); - } - - // Old broken tar recorded dir as "file with trailing slash" - if (S_ISREG(TT.hdr.mode) && (s = strend(TT.hdr.name, "/"))) { - *s = 0; - TT.hdr.mode = (TT.hdr.mode & ~S_IFMT) | S_IFDIR; - } - - // Non-regular files don't have contents stored in archive. - if ((TT.hdr.link_target && *TT.hdr.link_target) || !S_ISREG(TT.hdr.mode)) - TT.hdr.size = 0; - - // Files are seen even if excluded, so check them here. - // TT.seen points to first seen entry in TT.incl, or NULL if none yet. - - if ((delete = filter(TT.incl, TT.hdr.name)) && TT.incl != TT.seen) { - if (!TT.seen) TT.seen = delete; - - // Move seen entry to end of list. - if (TT.incl == delete) TT.incl = TT.incl->next; - else for (walk = TT.incl; walk != TT.seen; walk = walk->next) { - if (walk == delete) { - dlist_pop(&walk); - dlist_add_nomalloc(&TT.incl, delete); - } - } - } - - // Skip excluded files - if (filter(TT.excl, TT.hdr.name) || (TT.incl && !delete)) - skippy(TT.hdr.size); - else if (FLAG(t)) { - if (FLAG(v)) { - struct tm *lc = localtime(TT.mtime ? &TT.mtt : &TT.hdr.mtime); - char perm[12], gname[12]; - - mode_to_string(TT.hdr.mode, perm); - printf("%s", perm); - sprintf(perm, "%u", TT.hdr.uid); - sprintf(gname, "%u", TT.hdr.gid); - printf(" %s/%s ", *TT.hdr.uname ? TT.hdr.uname : perm, - *TT.hdr.gname ? TT.hdr.gname : gname); - if (tar.type=='3' || tar.type=='4') printf("%u,%u", maj, min); - else printf("%9lld", (long long)TT.hdr.size); - sprintf(perm, ":%02d", lc->tm_sec); - printf(" %d-%02d-%02d %02d:%02d%s ", 1900+lc->tm_year, 1+lc->tm_mon, - lc->tm_mday, lc->tm_hour, lc->tm_min, FLAG(full_time) ? perm : ""); - } - printf("%s", TT.hdr.name); - if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target); - xputc('\n'); - skippy(TT.hdr.size); - } else { - if (FLAG(v)) printf("%s\n", TT.hdr.name); - if (FLAG(O)) xsendfile_len(TT.fd, 1, TT.hdr.size); - else if (FLAG(to_command)) { - if (S_ISREG(TT.hdr.mode)) { - int fd, pid; - - xsetenv("TAR_FILETYPE", "f"); - xsetenv(xmprintf("TAR_MODE=%o", TT.hdr.mode), 0); - xsetenv(xmprintf("TAR_SIZE=%lld", TT.hdr.size), 0); - xsetenv("TAR_FILENAME", TT.hdr.name); - xsetenv("TAR_UNAME", TT.hdr.uname); - xsetenv("TAR_GNAME", TT.hdr.gname); - xsetenv(xmprintf("TAR_MTIME=%llo", (long long)TT.hdr.mtime), 0); - xsetenv(xmprintf("TAR_UID=%o", TT.hdr.uid), 0); - xsetenv(xmprintf("TAR_GID=%o", TT.hdr.gid), 0); - - pid = xpopen((char *[]){"sh", "-c", TT.to_command, NULL}, &fd, 0); - // todo: short write exits tar here, other skips data. - xsendfile_len(TT.fd, fd, TT.hdr.size); - fd = xpclose(pid, fd); - if (fd) error_msg("%d: Child returned %d", pid, fd); - } - } else extract_to_disk(); - } - - free(TT.hdr.name); - free(TT.hdr.link_target); - free(TT.hdr.uname); - free(TT.hdr.gname); - TT.hdr.name = TT.hdr.link_target = 0; - } -} - -// Add copy of filename (minus trailing \n and /) to dlist ** -static void trim2list(void *list, char *pline) -{ - char *n = xstrdup(pline); - int i = strlen(n); - - dlist_add(list, n); - if (i && n[i-1]=='\n') i--; - while (i && n[i-1] == '/') i--; - n[i] = 0; -} - -// do_lines callback, selects TT.incl or TT.excl based on call order -static void do_XT(char **pline, long len) -{ - if (pline) trim2list(TT.X ? &TT.excl : &TT.incl, *pline); -} - -void tar_main(void) -{ - char *s, **args = toys.optargs; - int len = 0; - - // Needed when extracting to command - signal(SIGPIPE, SIG_IGN); - - // Get possible early errors out of the way - if (!geteuid()) toys.optflags |= FLAG_p; - if (TT.owner) TT.ouid = xgetuid(TT.owner); - if (TT.group) TT.ggid = xgetgid(TT.group); - if (TT.mtime) xparsedate(TT.mtime, &TT.mtt, (void *)&s, 1); - - // Collect file list. - for (; TT.exclude; TT.exclude = TT.exclude->next) - trim2list(&TT.excl, TT.exclude->arg); - for (;TT.X; TT.X = TT.X->next) do_lines(xopenro(TT.X->arg), '\n', do_XT); - for (args = toys.optargs; *args; args++) trim2list(&TT.incl, *args); - for (;TT.T; TT.T = TT.T->next) do_lines(xopenro(TT.T->arg), '\n', do_XT); - - // If include file list empty, don't create empty archive - if (FLAG(c)) { - if (!TT.incl) error_exit("empty archive"); - TT.fd = 1; - } - - // nommu reentry for nonseekable input skips this, parent did it for us - if (toys.stacktop) { - if (TT.f && strcmp(TT.f, "-")) - TT.fd = xcreate(TT.f, TT.fd*(O_WRONLY|O_CREAT|O_TRUNC), 0666); - // Get destination directory - if (TT.C) xchdir(TT.C); - } - - // Get destination directory - TT.cwd = xabspath(s = xgetcwd(), 1); - free(s); - - // Remember archive inode - { - struct stat st; - - if (!fstat(TT.fd, &st)) { - TT.aino = st.st_ino; - TT.adev = st.st_dev; - } - } - - // Are we reading? - if (FLAG(x)||FLAG(t)) { - struct tar_hdr *hdr = 0; - - // autodetect compression type when not specified - if (!(FLAG(j)||FLAG(z)||FLAG(J))) { - len = xread(TT.fd, hdr = (void *)(toybuf+sizeof(toybuf)-512), 512); - if (len!=512 || strncmp("ustar", hdr->magic, 5)) { - // detect gzip and bzip signatures - if (SWAP_BE16(*(short *)hdr)==0x1f8b) toys.optflags |= FLAG_z; - else if (!memcmp(hdr->name, "BZh", 3)) toys.optflags |= FLAG_j; - else if (peek_be(hdr->name, 7) == 0xfd377a585a0000) - toys.optflags |= FLAG_J; - else error_exit("Not tar"); - - // if we can seek back we don't need to loop and copy data - if (!lseek(TT.fd, -len, SEEK_CUR)) hdr = 0; - } - } - - if (FLAG(j)||FLAG(z)||FLAG(J)) { - int pipefd[2] = {hdr ? -1 : TT.fd, -1}, i, pid; - char *cmd[] = {"bzcat", 0}; - - if (FLAG(J)) cmd[0] = "xzcat"; - else if FLAG(z) cmd[0]++; - xpopen_both(cmd, pipefd); - - if (!hdr) { - // If we could seek, child gzip inherited fd and we read its output - close(TT.fd); - TT.fd = pipefd[1]; - - } else { - - // If we autodetected type but then couldn't lseek to put the data back - // we have to loop reading data from TT.fd and pass it to gzip ourselves - // (starting with the block of data we read to autodetect). - - // dirty trick: move gzip input pipe to stdin so child closes spare copy - dup2(pipefd[0], 0); - if (pipefd[0]) close(pipefd[0]); - - // Fork a copy of ourselves to handle extraction (reads from zip output - // pipe, writes to stdout). - pipefd[0] = pipefd[1]; - pipefd[1] = 1; - pid = xpopen_both(0, pipefd); - close(pipefd[1]); - - // loop writing collated data to zip proc - xwrite(0, hdr, len); - for (;;) { - if ((i = read(TT.fd, toybuf, sizeof(toybuf)))<1) { - close(0); - xwaitpid(pid); - return; - } - xwrite(0, toybuf, i); - } - } - } - - unpack_tar(hdr); - dirflush(0); - - // Each time a TT.incl entry is seen it's moved to the end of the list, - // with TT.seen pointing to first seen list entry. Anything between - // TT.incl and TT.seen wasn't encountered in archive.. - if (TT.seen != TT.incl) { - if (!TT.seen) TT.seen = TT.incl; - while (TT.incl != TT.seen) { - error_msg("'%s' not in archive", TT.incl->data); - TT.incl = TT.incl->next; - } - } - - // are we writing? (Don't have to test flag here, one of 3 must be set) - } else { - struct double_list *dl = TT.incl; - - // autodetect compression type based on -f name. (Use > to avoid.) - if (TT.f && !FLAG(j) && !FLAG(z)) { - char *tbz[] = {".tbz", ".tbz2", ".tar.bz", ".tar.bz2"}; - if (strend(TT.f, ".tgz") || strend(TT.f, ".tar.gz")) - toys.optflags |= FLAG_z; - if (strend(TT.f, ".txz") || strend(TT.f, ".tar.xz")) - toys.optflags |= FLAG_J; - else for (len = 0; lendata, FLAG(h)?DIRTREE_SYMFOLLOW:0, add_to_tar); - } while (TT.incl != (dl = dl->next)); - - writeall(TT.fd, toybuf, 1024); - } - - if (CFG_TOYBOX_FREE) { - llist_traverse(TT.excl, llist_free_double); - llist_traverse(TT.incl, llist_free_double); - while(TT.hlc) free(TT.hlx[--TT.hlc].arg); - free(TT.hlx); - free(TT.cwd); - close(TT.fd); - } -} diff --git a/toys/posix/tar.c b/toys/posix/tar.c new file mode 100644 index 00000000..cfeedcba --- /dev/null +++ b/toys/posix/tar.c @@ -0,0 +1,814 @@ +/* tar.c - create/extract archives + * + * Copyright 2014 Ashwini Kumar + * + * For the command, see + * http://pubs.opengroup.org/onlinepubs/007908799/xcu/tar.html + * For the modern file format, see + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 + * https://en.wikipedia.org/wiki/Tar_(computing)#File_format + * https://www.gnu.org/software/tar/manual/html_node/Tar-Internals.html + * + * For writing to external program + * http://www.gnu.org/software/tar/manual/html_node/Writing-to-an-External-Program.html + * + * Toybox will never implement the "pax" command as a matter of policy. + * + * Why --exclude pattern but no --include? tar cvzf a.tgz dir --include '*.txt' + * Extract into dir same as filename, --restrict? "Tarball is splodey" + * + +USE_TAR(NEWTOY(tar, "&(restrict)(full-time)(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(mtime):(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)J(xz)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):a[!txc][!jzJa]", TOYFLAG_USR|TOYFLAG_BIN)) + +config TAR + bool "tar" + default y + help + usage: tar [-cxtfvohmjkO] [-XT FILE] [-f TARFILE] [-C DIR] + + Create, extract, or list files in a .tar (or compressed t?z) file. + + Options: + c Create x Extract t Test + f Name of TARFILE C Change to DIR first v Verbose: show filenames + o Ignore owner h Follow symlinks m Ignore mtime + j bzip2 compression z gzip compression + O Extract to stdout X exclude names in FILE T include names in FILE + --exclude=FILE File pattern(s) to exclude + --restrict All archive contents must extract under a single subdirctory. +*/ + +#define FOR_tar +#include "toys.h" + +GLOBALS( + char *f, *C; + struct arg_list *T, *X; + char *to_command, *owner, *group, *mtime; + struct arg_list *exclude; + + struct double_list *incl, *excl, *seen; + struct string_list *dirs; + char *cwd; + int fd, ouid, ggid, hlc, warn, adev, aino; + time_t mtt; + + // hardlinks seen so far (hlc many) + struct { + char *arg; + ino_t ino; + dev_t dev; + } *hlx; + + // Parsed information about a tar header. + struct tar_header { + char *name, *link_target, *uname, *gname; + long long size; + uid_t uid; + gid_t gid; + mode_t mode; + time_t mtime; + dev_t device; + } hdr; +) + +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]; +}; + +// convert from int to octal (or base-256) +static void itoo(char *str, int len, unsigned long long val) +{ + // Do we need binary encoding? + if (!(val>>(3*(len-1)))) sprintf(str, "%0*llo", len-1, val); + else { + *str = 128; + while (--len) *++str = val>>(3*len); + } +} +#define ITOO(x, y) itoo(x, sizeof(x), y) + +// convert octal (or base-256) to int +static unsigned long long otoi(char *str, unsigned len) +{ + unsigned long long val = 0; + + // When tar value too big or octal, use binary encoding with high bit set + if (128&*str) while (--len) val = (val<<8)+*++str; + else { + while (len && *str>='0' && *str<='7') val = val*8+*str++-'0', len--; + if (len && *str && *str != ' ') error_exit("bad header"); + } + + return val; +} +#define OTOI(x) otoi(x, sizeof(x)) + +// Calculate packet checksum, with cksum field treated as 8 spaces +static unsigned cksum(void *data) +{ + unsigned i, cksum = 8*' '; + + for (i = 0; i<500; i += (i==147) ? 9 : 1) cksum += ((char *)data)[i]; + + return cksum; +} + +static void write_longname(char *name, char type) +{ + struct tar_hdr tmp; + int sz = strlen(name) +1; + + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.name, "././@LongLink"); + ITOO(tmp.uid, 0); + ITOO(tmp.gid, 0); + ITOO(tmp.size, sz); + ITOO(tmp.mtime, 0); + tmp.type = type; + strcpy(tmp.magic, "ustar "); + + // Historical nonsense to match other implementations. Never used. + ITOO(tmp.mode, 0644); + strcpy(tmp.uname, "root"); + strcpy(tmp.gname, "root"); + + // Calculate checksum. Since 512*255 = 0377000 in octal, this can never + // use more than 6 digits. The last byte is ' ' or historical reasons. + itoo(tmp.chksum, sizeof(tmp.chksum)-1, cksum(&tmp)); + tmp.chksum[7] = ' '; + + // write header and name, padded with NUL to block size + xwrite(TT.fd, &tmp, 512); + xwrite(TT.fd, name, sz); + if (sz%512) xwrite(TT.fd, toybuf, 512-(sz%512)); +} + +static struct double_list *filter(struct double_list *lst, char *name) +{ + struct double_list *end = lst; + + if (lst) + // constant is FNM_LEADING_DIR + do if (!fnmatch(lst->data, name, 1<<3)) return lst; + while (end != (lst = lst->next)); + + return 0; +} + +static void skippy(long long len) +{ + if (lskip(TT.fd, len)) perror_exit("EOF"); +} + +// allocate and read data from TT.fd +static void alloread(void *buf, int len) +{ + // actually void **, but automatic typecasting doesn't work with void ** :( + void **b = buf; + + free(*b); + *b = xmalloc(len+1); + xreadall(TT.fd, *b, len); + b[len] = 0; +} + +// callback from dirtree to create archive +static int add_to_tar(struct dirtree *node) +{ + struct stat *st = &(node->st); + struct tar_hdr hdr; + struct passwd *pw = pw; + struct group *gr = gr; + int i, fd =-1; + char *c, *p, *name, *lnk = lnk, *hname; + + if (!dirtree_notdotdot(node)) return 0; + if (TT.adev == st->st_dev && TT.aino == st->st_ino) { + error_msg("'%s' file is the archive; not dumped", node->name); + return 0; + } + + i = 1; + name = dirtree_path(node, &i); + + // exclusion defaults to --no-anchored and --wildcards-match-slash + for (p = name; *p;) { + if (filter(TT.excl, p)) goto done; + while (*p && *p!='/') p++; + while (*p=='/') p++; + } + + // Consume the 1 extra byte alocated in dirtree_path() + if (S_ISDIR(st->st_mode) && name[i-1] != '/') strcat(name, "/"); + + // remove leading / and any .. entries from saved name + for (hname = name; *hname == '/'; hname++); + for (c = hname;;) { + if (!(c = strstr(c, ".."))) break; + if (c == hname || c[-1] == '/') { + if (!c[2]) goto done; + if (c[2]=='/') c = hname = c+3; + } else c+= 2; + } + if (!*hname) goto done; + + if (TT.warn && hname != name) { + fprintf(stderr, "removing leading '%.*s' from member names\n", + (int)(hname-name), name); + TT.warn = 0; + } + + if (TT.owner) st->st_uid = TT.ouid; + if (TT.group) st->st_gid = TT.ggid; + if (TT.mtime) st->st_mtime = TT.mtt; + + memset(&hdr, 0, sizeof(hdr)); + strncpy(hdr.name, hname, sizeof(hdr.name)); + ITOO(hdr.mode, st->st_mode &07777); + ITOO(hdr.uid, st->st_uid); + ITOO(hdr.gid, st->st_gid); + ITOO(hdr.size, 0); //set size later + ITOO(hdr.mtime, st->st_mtime); + strcpy(hdr.magic, "ustar "); + + // Hard link or symlink? i=0 neither, i=1 hardlink, i=2 symlink + + // Are there hardlinks to a non-directory entry? + if (st->st_nlink>1 && !S_ISDIR(st->st_mode)) { + // Have we seen this dev&ino before? + for (i = 0; ist_ino == TT.hlx[i].ino && st->st_dev == TT.hlx[i].dev) + break; + } + if (i != TT.hlc) { + lnk = TT.hlx[i].arg; + i = 1; + } else { + // first time we've seen it. Store as normal file, but remember it. + if (!(TT.hlc&255)) TT.hlx = xrealloc(TT.hlx, TT.hlc+256); + TT.hlx[TT.hlc].arg = xstrdup(hname); + TT.hlx[TT.hlc].ino = st->st_ino; + TT.hlx[TT.hlc].dev = st->st_dev; + TT.hlc++; + i = 0; + } + } else i = 0; + + // !i because hardlink to a symlink is a thing. + if (!i && S_ISLNK(st->st_mode)) { + i = 2; + lnk = xreadlink(name); + } + + // Handle file types + if (i) { + hdr.type = '0'+i; + if (i==2 && !(lnk = xreadlink(name))) { + perror_msg("readlink"); + goto done; + } + if (strlen(lnk) > sizeof(hdr.link)) write_longname(lnk, 'K'); + strncpy(hdr.link, lnk, sizeof(hdr.link)); + if (i) free(lnk); + } else if (S_ISREG(st->st_mode)) { + hdr.type = '0'; + ITOO(hdr.size, st->st_size); + } 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, dev_major(st->st_rdev)); + ITOO(hdr.minor, dev_minor(st->st_rdev)); + } else { + error_msg("unknown file type '%o'", st->st_mode & S_IFMT); + goto done; + } + + if (strlen(hname) > sizeof(hdr.name)) write_longname(hname, 'L'); + + if (!FLAG(numeric_owner)) { + if (TT.owner || (pw = bufgetpwuid(st->st_uid))) + strncpy(hdr.uname, TT.owner ? TT.owner : pw->pw_name, sizeof(hdr.uname)); + if (TT.group || (gr = bufgetgrgid(st->st_gid))) + strncpy(hdr.gname, TT.group ? TT.group : gr->gr_name, sizeof(hdr.gname)); + } + + itoo(hdr.chksum, sizeof(hdr.chksum)-1, cksum(&hdr)); + hdr.chksum[7] = ' '; + + if (FLAG(v)) printf("%s\n", hname); + + // Write header and data to archive + xwrite(TT.fd, &hdr, 512); + if (hdr.type == '0') { + if ((fd = open(name, O_RDONLY)) < 0) perror_msg("can't open '%s'", name); + else { + xsendfile_pad(fd, TT.fd, st->st_size); + if (st->st_size%512) writeall(TT.fd, toybuf, (512-(st->st_size%512))); + close(fd); + } + } +done: + free(name); + + return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!FLAG(no_recursion); +} + +static void wsettime(char *s, long long sec) +{ + struct timespec times[2] = {{sec, 0},{sec, 0}}; + + if (utimensat(AT_FDCWD, s, times, AT_SYMLINK_NOFOLLOW)) + perror_msg("settime %lld %s", sec, s); +} + +// Do pending directory utimes(), NULL to flush all. +static int dirflush(char *name) +{ + char *s = 0, *ss; + + // Barf if name not in TT.cwd + if (name) { + ss = s = xabspath(name, -1); + if (TT.cwd[1] && (!strstart(&ss, TT.cwd) || *ss!='/')) { + error_msg("'%s' not under '%s'", name, TT.cwd); + free(s); + + return 1; + } + + if (FLAG(restrict)) { + free(TT.cwd); + TT.cwd = strdup(s); + toys.optflags ^= FLAG_restrict; + } + } + + // Set deferred utimes() for directories this file isn't under. + // (Files must be depth-first ordered in tarball for this to matter.) + while (TT.dirs) { + + // If next file is under (or equal to) this dir, keep waiting + if (name && strstart(&ss, ss = s) && (!*ss || *ss=='/')) break; + + wsettime(TT.dirs->str+sizeof(long long), *(long long *)TT.dirs->str); + free(llist_pop(&TT.dirs)); + } + free(s); + + // name was under TT.cwd + return 0; +} + +static void extract_to_disk(void) +{ + char *name = TT.hdr.name; + int ala = TT.hdr.mode; + + if (dirflush(name)) { + if (S_ISREG(ala) && !TT.hdr.link_target) skippy(TT.hdr.size); + + return; + } + + // create path before file if necessary + if (strrchr(name, '/') && mkpath(name) && errno !=EEXIST) + return perror_msg(":%s: can't mkdir", name); + + // remove old file, if exists + if (!FLAG(k) && !S_ISDIR(ala) && unlink(name) && errno!=ENOENT) + return perror_msg("can't remove: %s", name); + + if (S_ISREG(ala)) { + // hardlink? + if (TT.hdr.link_target) { + if (link(TT.hdr.link_target, name)) + return perror_msg("can't link '%s' -> '%s'", name, TT.hdr.link_target); + // write contents + } else { + int fd = xcreate(name, O_WRONLY|O_CREAT|(FLAG(overwrite)?O_TRUNC:O_EXCL), + WARN_ONLY|(ala & 07777)); + if (fd != -1) { + xsendfile_len(TT.fd, fd, TT.hdr.size); + close(fd); + } + } + } else if (S_ISDIR(ala)) { + if ((mkdir(name, 0700) == -1) && errno != EEXIST) + return perror_msg("%s: can't create", TT.hdr.name); + } else if (S_ISLNK(ala)) { + if (symlink(TT.hdr.link_target, TT.hdr.name)) + return perror_msg("can't link '%s' -> '%s'", name, TT.hdr.link_target); + } else if (mknod(name, ala, TT.hdr.device)) + return perror_msg("can't create '%s'", name); + + // Set ownership + if (!FLAG(o) && !geteuid()) { + int u = TT.hdr.uid, g = TT.hdr.gid; + + if (TT.owner) TT.hdr.uid = TT.ouid; + else if (!FLAG(numeric_owner) && *TT.hdr.uname) { + struct passwd *pw = getpwnam(TT.hdr.uname); + if (pw && (TT.owner || !FLAG(numeric_owner))) TT.hdr.uid = pw->pw_uid; + } + + if (TT.group) TT.hdr.gid = TT.ggid; + else if (!FLAG(numeric_owner) && *TT.hdr.uname) { + struct group *gr = getgrnam(TT.hdr.gname); + if (gr) TT.hdr.gid = gr->gr_gid; + } + + if (lchown(name, u, g)) perror_msg("chown %d:%d '%s'", u, g, name);; + } + + if (!S_ISLNK(ala)) chmod(TT.hdr.name, FLAG(p) ? ala : ala&0777); + + // Apply mtime. + if (!FLAG(m)) { + if (S_ISDIR(ala)) { + struct string_list *sl; + + // Writing files into a directory changes directory timestamps, so + // defer mtime updates until contents written. + + sl = xmalloc(sizeof(struct string_list)+sizeof(long long)+strlen(name)+1); + *(long long *)sl->str = TT.hdr.mtime; + strcpy(sl->str+sizeof(long long), name); + sl->next = TT.dirs; + TT.dirs = sl; + } else wsettime(TT.hdr.name, TT.hdr.mtime); + } +} + +static void unpack_tar(struct tar_hdr *first) +{ + struct double_list *walk, *delete; + struct tar_hdr tar; + int i, and = 0; + unsigned maj, min; + char *s; + + for (;;) { + if (first) { + memcpy(&tar, first, i = 512); + first = 0; + } else { + // align to next block and read it + if (TT.hdr.size%512) skippy(512-TT.hdr.size%512); + i = readall(TT.fd, &tar, 512); + } + + if (i && i!=512) error_exit("short header"); + + // Two consecutive empty headers ends tar even if there's more data + if (!i || !*tar.name) { + if (!i || and++) return; + TT.hdr.size = 0; + continue; + } + // ensure null temination even of pathological packets + tar.padd[0] = and = 0; + + // Is this a valid Unix Standard TAR header? + if (memcmp(tar.magic, "ustar", 5)) error_exit("bad header"); + if (cksum(&tar) != OTOI(tar.chksum)) error_exit("bad cksum"); + TT.hdr.size = OTOI(tar.size); + + // If this header isn't writing something to the filesystem + if (tar.type<'0' || tar.type>'7') { + + // Long name extension header? + if (tar.type == 'K') alloread(&TT.hdr.link_target, TT.hdr.size); + else if (tar.type == 'L') alloread(&TT.hdr.name, TT.hdr.size); + else if (tar.type == 'x') { + char *p, *buf = 0; + int i, len, n; + + // Posix extended record "LEN NAME=VALUE\n" format + alloread(&buf, TT.hdr.size); + for (p = buf; (p-buf)TT.hdr.size) { + error_msg("bad header"); + break; + } + p[len-1] = 0; + if (i == 2) { + TT.hdr.name = xstrdup(p+n); + break; + } + } + free(buf); + + // Ignore everything else. + } else skippy(TT.hdr.size); + + continue; + } + + // At this point, we have something to output. Convert metadata. + TT.hdr.mode = OTOI(tar.mode); + TT.hdr.mode |= (char []){8,8,10,2,6,4,1,8}[tar.type-'0']<<12; + TT.hdr.uid = OTOI(tar.uid); + TT.hdr.gid = OTOI(tar.gid); + TT.hdr.mtime = OTOI(tar.mtime); + maj = OTOI(tar.major); + min = OTOI(tar.minor); + TT.hdr.device = dev_makedev(maj, min); + + TT.hdr.uname = xstrndup(TT.owner ? TT.owner : tar.uname, sizeof(tar.uname)); + TT.hdr.gname = xstrndup(TT.group ? TT.group : tar.gname, sizeof(tar.gname)); + + if (TT.owner) TT.hdr.uid = TT.ouid; + else if (!FLAG(numeric_owner)) { + struct passwd *pw = getpwnam(TT.hdr.uname); + if (pw && (TT.owner || !FLAG(numeric_owner))) TT.hdr.uid = pw->pw_uid; + } + + if (TT.group) TT.hdr.gid = TT.ggid; + else if (!FLAG(numeric_owner)) { + struct group *gr = getgrnam(TT.hdr.gname); + if (gr) TT.hdr.gid = gr->gr_gid; + } + + if (!TT.hdr.link_target && *tar.link) + TT.hdr.link_target = xstrndup(tar.link, sizeof(tar.link)); + if (!TT.hdr.name) { + // Glue prefix and name fields together with / if necessary + i = strnlen(tar.prefix, sizeof(tar.prefix)); + TT.hdr.name = xmprintf("%.*s%s%.*s", i, tar.prefix, + (i && tar.prefix[i-1] != '/') ? "/" : "", + (int)sizeof(tar.name), tar.name); + } + + // Old broken tar recorded dir as "file with trailing slash" + if (S_ISREG(TT.hdr.mode) && (s = strend(TT.hdr.name, "/"))) { + *s = 0; + TT.hdr.mode = (TT.hdr.mode & ~S_IFMT) | S_IFDIR; + } + + // Non-regular files don't have contents stored in archive. + if ((TT.hdr.link_target && *TT.hdr.link_target) || !S_ISREG(TT.hdr.mode)) + TT.hdr.size = 0; + + // Files are seen even if excluded, so check them here. + // TT.seen points to first seen entry in TT.incl, or NULL if none yet. + + if ((delete = filter(TT.incl, TT.hdr.name)) && TT.incl != TT.seen) { + if (!TT.seen) TT.seen = delete; + + // Move seen entry to end of list. + if (TT.incl == delete) TT.incl = TT.incl->next; + else for (walk = TT.incl; walk != TT.seen; walk = walk->next) { + if (walk == delete) { + dlist_pop(&walk); + dlist_add_nomalloc(&TT.incl, delete); + } + } + } + + // Skip excluded files + if (filter(TT.excl, TT.hdr.name) || (TT.incl && !delete)) + skippy(TT.hdr.size); + else if (FLAG(t)) { + if (FLAG(v)) { + struct tm *lc = localtime(TT.mtime ? &TT.mtt : &TT.hdr.mtime); + char perm[12], gname[12]; + + mode_to_string(TT.hdr.mode, perm); + printf("%s", perm); + sprintf(perm, "%u", TT.hdr.uid); + sprintf(gname, "%u", TT.hdr.gid); + printf(" %s/%s ", *TT.hdr.uname ? TT.hdr.uname : perm, + *TT.hdr.gname ? TT.hdr.gname : gname); + if (tar.type=='3' || tar.type=='4') printf("%u,%u", maj, min); + else printf("%9lld", (long long)TT.hdr.size); + sprintf(perm, ":%02d", lc->tm_sec); + printf(" %d-%02d-%02d %02d:%02d%s ", 1900+lc->tm_year, 1+lc->tm_mon, + lc->tm_mday, lc->tm_hour, lc->tm_min, FLAG(full_time) ? perm : ""); + } + printf("%s", TT.hdr.name); + if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target); + xputc('\n'); + skippy(TT.hdr.size); + } else { + if (FLAG(v)) printf("%s\n", TT.hdr.name); + if (FLAG(O)) xsendfile_len(TT.fd, 1, TT.hdr.size); + else if (FLAG(to_command)) { + if (S_ISREG(TT.hdr.mode)) { + int fd, pid; + + xsetenv("TAR_FILETYPE", "f"); + xsetenv(xmprintf("TAR_MODE=%o", TT.hdr.mode), 0); + xsetenv(xmprintf("TAR_SIZE=%lld", TT.hdr.size), 0); + xsetenv("TAR_FILENAME", TT.hdr.name); + xsetenv("TAR_UNAME", TT.hdr.uname); + xsetenv("TAR_GNAME", TT.hdr.gname); + xsetenv(xmprintf("TAR_MTIME=%llo", (long long)TT.hdr.mtime), 0); + xsetenv(xmprintf("TAR_UID=%o", TT.hdr.uid), 0); + xsetenv(xmprintf("TAR_GID=%o", TT.hdr.gid), 0); + + pid = xpopen((char *[]){"sh", "-c", TT.to_command, NULL}, &fd, 0); + // todo: short write exits tar here, other skips data. + xsendfile_len(TT.fd, fd, TT.hdr.size); + fd = xpclose(pid, fd); + if (fd) error_msg("%d: Child returned %d", pid, fd); + } + } else extract_to_disk(); + } + + free(TT.hdr.name); + free(TT.hdr.link_target); + free(TT.hdr.uname); + free(TT.hdr.gname); + TT.hdr.name = TT.hdr.link_target = 0; + } +} + +// Add copy of filename (minus trailing \n and /) to dlist ** +static void trim2list(void *list, char *pline) +{ + char *n = xstrdup(pline); + int i = strlen(n); + + dlist_add(list, n); + if (i && n[i-1]=='\n') i--; + while (i && n[i-1] == '/') i--; + n[i] = 0; +} + +// do_lines callback, selects TT.incl or TT.excl based on call order +static void do_XT(char **pline, long len) +{ + if (pline) trim2list(TT.X ? &TT.excl : &TT.incl, *pline); +} + +void tar_main(void) +{ + char *s, **args = toys.optargs; + int len = 0; + + // Needed when extracting to command + signal(SIGPIPE, SIG_IGN); + + // Get possible early errors out of the way + if (!geteuid()) toys.optflags |= FLAG_p; + if (TT.owner) TT.ouid = xgetuid(TT.owner); + if (TT.group) TT.ggid = xgetgid(TT.group); + if (TT.mtime) xparsedate(TT.mtime, &TT.mtt, (void *)&s, 1); + + // Collect file list. + for (; TT.exclude; TT.exclude = TT.exclude->next) + trim2list(&TT.excl, TT.exclude->arg); + for (;TT.X; TT.X = TT.X->next) do_lines(xopenro(TT.X->arg), '\n', do_XT); + for (args = toys.optargs; *args; args++) trim2list(&TT.incl, *args); + for (;TT.T; TT.T = TT.T->next) do_lines(xopenro(TT.T->arg), '\n', do_XT); + + // If include file list empty, don't create empty archive + if (FLAG(c)) { + if (!TT.incl) error_exit("empty archive"); + TT.fd = 1; + } + + // nommu reentry for nonseekable input skips this, parent did it for us + if (toys.stacktop) { + if (TT.f && strcmp(TT.f, "-")) + TT.fd = xcreate(TT.f, TT.fd*(O_WRONLY|O_CREAT|O_TRUNC), 0666); + // Get destination directory + if (TT.C) xchdir(TT.C); + } + + // Get destination directory + TT.cwd = xabspath(s = xgetcwd(), 1); + free(s); + + // Remember archive inode + { + struct stat st; + + if (!fstat(TT.fd, &st)) { + TT.aino = st.st_ino; + TT.adev = st.st_dev; + } + } + + // Are we reading? + if (FLAG(x)||FLAG(t)) { + struct tar_hdr *hdr = 0; + + // autodetect compression type when not specified + if (!(FLAG(j)||FLAG(z)||FLAG(J))) { + len = xread(TT.fd, hdr = (void *)(toybuf+sizeof(toybuf)-512), 512); + if (len!=512 || strncmp("ustar", hdr->magic, 5)) { + // detect gzip and bzip signatures + if (SWAP_BE16(*(short *)hdr)==0x1f8b) toys.optflags |= FLAG_z; + else if (!memcmp(hdr->name, "BZh", 3)) toys.optflags |= FLAG_j; + else if (peek_be(hdr->name, 7) == 0xfd377a585a0000) + toys.optflags |= FLAG_J; + else error_exit("Not tar"); + + // if we can seek back we don't need to loop and copy data + if (!lseek(TT.fd, -len, SEEK_CUR)) hdr = 0; + } + } + + if (FLAG(j)||FLAG(z)||FLAG(J)) { + int pipefd[2] = {hdr ? -1 : TT.fd, -1}, i, pid; + char *cmd[] = {"bzcat", 0}; + + if (FLAG(J)) cmd[0] = "xzcat"; + else if FLAG(z) cmd[0]++; + xpopen_both(cmd, pipefd); + + if (!hdr) { + // If we could seek, child gzip inherited fd and we read its output + close(TT.fd); + TT.fd = pipefd[1]; + + } else { + + // If we autodetected type but then couldn't lseek to put the data back + // we have to loop reading data from TT.fd and pass it to gzip ourselves + // (starting with the block of data we read to autodetect). + + // dirty trick: move gzip input pipe to stdin so child closes spare copy + dup2(pipefd[0], 0); + if (pipefd[0]) close(pipefd[0]); + + // Fork a copy of ourselves to handle extraction (reads from zip output + // pipe, writes to stdout). + pipefd[0] = pipefd[1]; + pipefd[1] = 1; + pid = xpopen_both(0, pipefd); + close(pipefd[1]); + + // loop writing collated data to zip proc + xwrite(0, hdr, len); + for (;;) { + if ((i = read(TT.fd, toybuf, sizeof(toybuf)))<1) { + close(0); + xwaitpid(pid); + return; + } + xwrite(0, toybuf, i); + } + } + } + + unpack_tar(hdr); + dirflush(0); + + // Each time a TT.incl entry is seen it's moved to the end of the list, + // with TT.seen pointing to first seen list entry. Anything between + // TT.incl and TT.seen wasn't encountered in archive.. + if (TT.seen != TT.incl) { + if (!TT.seen) TT.seen = TT.incl; + while (TT.incl != TT.seen) { + error_msg("'%s' not in archive", TT.incl->data); + TT.incl = TT.incl->next; + } + } + + // are we writing? (Don't have to test flag here, one of 3 must be set) + } else { + struct double_list *dl = TT.incl; + + // autodetect compression type based on -f name. (Use > to avoid.) + if (TT.f && !FLAG(j) && !FLAG(z)) { + char *tbz[] = {".tbz", ".tbz2", ".tar.bz", ".tar.bz2"}; + if (strend(TT.f, ".tgz") || strend(TT.f, ".tar.gz")) + toys.optflags |= FLAG_z; + if (strend(TT.f, ".txz") || strend(TT.f, ".tar.xz")) + toys.optflags |= FLAG_J; + else for (len = 0; lendata, FLAG(h)?DIRTREE_SYMFOLLOW:0, add_to_tar); + } while (TT.incl != (dl = dl->next)); + + writeall(TT.fd, toybuf, 1024); + } + + if (CFG_TOYBOX_FREE) { + llist_traverse(TT.excl, llist_free_double); + llist_traverse(TT.incl, llist_free_double); + while(TT.hlc) free(TT.hlx[--TT.hlc].arg); + free(TT.hlx); + free(TT.cwd); + close(TT.fd); + } +} -- cgit v1.2.3