diff options
author | Rob Landley <rob@landley.net> | 2019-04-21 04:46:31 -0500 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2019-04-21 04:46:31 -0500 |
commit | c237aeab10add3d1a9da260c297a7f0587ded13e (patch) | |
tree | af89ec7d38256935fcd19487351374296c3bdc7f /toys/pending | |
parent | 0e289fc0516507811379be52c08664f915eeed2c (diff) | |
download | toybox-c237aeab10add3d1a9da260c297a7f0587ded13e.tar.gz |
Promote tar.
There's probably more to do, but it seems usable at this point.
Diffstat (limited to 'toys/pending')
-rw-r--r-- | toys/pending/tar.c | 814 |
1 files changed, 0 insertions, 814 deletions
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 <ak.ashwini81@gmail.com> - * - * 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; i<TT.hlc; i++) { - if (st->st_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; p += len) { - i = sscanf(p, "%u path=%n", &len, &n); - if (i<1 || len<4 || len>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; len<ARRAY_LEN(tbz); len++) - if (strend(TT.f, tbz[len])) toys.optflags |= FLAG_j; - } - - if (FLAG(j)||FLAG(z)||FLAG(J)) { - int pipefd[2] = {-1, TT.fd}; - - xpopen_both((char *[]){FLAG(z)?"gzip":FLAG(J)?"xz":"bzip2", "-f", NULL}, - pipefd); - close(TT.fd); - TT.fd = pipefd[0]; - } - do { - TT.warn = 1; - dirtree_flagread(dl->data, 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); - } -} |