tar.c - sbase - suckless unix tools
 (HTM) git clone git://git.suckless.org/sbase
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tar.c (14905B)
       ---
            1 /* See LICENSE file for copyright and license details. */
            2 #include <sys/stat.h>
            3 #include <sys/time.h>
            4 #include <sys/types.h>
            5 #ifndef major
            6 #include <sys/sysmacros.h>
            7 #endif
            8 
            9 #include <assert.h>
           10 #include <errno.h>
           11 #include <fcntl.h>
           12 #include <grp.h>
           13 #include <libgen.h>
           14 #include <pwd.h>
           15 #include <stdio.h>
           16 #include <stdlib.h>
           17 #include <string.h>
           18 #include <unistd.h>
           19 
           20 #include "fs.h"
           21 #include "util.h"
           22 
           23 #define BLKSIZ (sizeof (struct header)) /* must equal 512 bytes */
           24 
           25 enum Type {
           26         REG       = '0',
           27         AREG      = '\0',
           28         HARDLINK  = '1',
           29         SYMLINK   = '2',
           30         CHARDEV   = '3',
           31         BLOCKDEV  = '4',
           32         DIRECTORY = '5',
           33         FIFO      = '6',
           34         RESERVED  = '7'
           35 };
           36 
           37 struct header {
           38         char name[100];
           39         char mode[8];
           40         char uid[8];
           41         char gid[8];
           42         char size[12];
           43         char mtime[12];
           44         char chksum[8];
           45         char type;
           46         char linkname[100];
           47         char magic[6];
           48         char version[2];
           49         char uname[32];
           50         char gname[32];
           51         char major[8];
           52         char minor[8];
           53         char prefix[155];
           54         char padding[12];
           55 };
           56 
           57 static struct dirtime {
           58         char *name;
           59         time_t mtime;
           60 } *dirtimes;
           61 
           62 static size_t dirtimeslen;
           63 
           64 static int tarfd;
           65 static ino_t tarinode;
           66 static dev_t tardev;
           67 
           68 static int mflag, vflag;
           69 static int filtermode;
           70 static const char *filtertool;
           71 
           72 static const char *filtertools[] = {
           73         ['J'] = "xz",
           74         ['Z'] = "compress",
           75         ['a'] = "lzma",
           76         ['j'] = "bzip2",
           77         ['z'] = "gzip",
           78 };
           79 
           80 static void
           81 pushdirtime(char *name, time_t mtime)
           82 {
           83         dirtimes = ereallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes));
           84         dirtimes[dirtimeslen].name = estrdup(name);
           85         dirtimes[dirtimeslen].mtime = mtime;
           86         dirtimeslen++;
           87 }
           88 
           89 static struct dirtime *
           90 popdirtime(void)
           91 {
           92         if (dirtimeslen) {
           93                 dirtimeslen--;
           94                 return &dirtimes[dirtimeslen];
           95         }
           96         return NULL;
           97 }
           98 
           99 static int
          100 comp(int fd, const char *tool, const char *flags)
          101 {
          102         int fds[2];
          103 
          104         if (pipe(fds) < 0)
          105                 eprintf("pipe:");
          106 
          107         switch (fork()) {
          108         case -1:
          109                 eprintf("fork:");
          110         case 0:
          111                 dup2(fd, 1);
          112                 dup2(fds[0], 0);
          113                 close(fds[0]);
          114                 close(fds[1]);
          115 
          116                 execlp(tool, tool, flags, NULL);
          117                 weprintf("execlp %s:", tool);
          118                 _exit(1);
          119         }
          120         close(fds[0]);
          121         return fds[1];
          122 }
          123 
          124 static int
          125 decomp(int fd, const char *tool, const char *flags)
          126 {
          127         int fds[2];
          128 
          129         if (pipe(fds) < 0)
          130                 eprintf("pipe:");
          131 
          132         switch (fork()) {
          133         case -1:
          134                 eprintf("fork:");
          135         case 0:
          136                 dup2(fd, 0);
          137                 dup2(fds[1], 1);
          138                 close(fds[0]);
          139                 close(fds[1]);
          140 
          141                 execlp(tool, tool, flags, NULL);
          142                 weprintf("execlp %s:", tool);
          143                 _exit(1);
          144         }
          145         close(fds[1]);
          146         return fds[0];
          147 }
          148 
          149 static ssize_t
          150 eread(int fd, void *buf, size_t n)
          151 {
          152         ssize_t r;
          153 
          154 again:
          155         r = read(fd, buf, n);
          156         if (r < 0) {
          157                 if (errno == EINTR)
          158                         goto again;
          159                 eprintf("read:");
          160         }
          161         return r;
          162 }
          163 
          164 static ssize_t
          165 ewrite(int fd, const void *buf, size_t n)
          166 {
          167         ssize_t r;
          168 
          169         if ((r = write(fd, buf, n)) != n)
          170                 eprintf("write:");
          171         return r;
          172 }
          173 
          174 static unsigned
          175 chksum(struct header *h)
          176 {
          177         unsigned sum, i;
          178 
          179         memset(h->chksum, ' ', sizeof(h->chksum));
          180         for (i = 0, sum = 0, assert(BLKSIZ == 512); i < BLKSIZ; i++)
          181                 sum += *((unsigned char *)h + i);
          182         return sum;
          183 }
          184 
          185 static void
          186 putoctal(char *dst, unsigned num, int size)
          187 {
          188         if (snprintf(dst, size, "%.*o", size - 1, num) >= size)
          189                 eprintf("putoctal: input number '%o' too large\n", num);
          190 }
          191 
          192 static int
          193 archive(const char *path)
          194 {
          195         static const struct header blank = {
          196                 "././@LongLink", "0000600", "0000000", "0000000", "00000000000",
          197                 "00000000000"  , "       ",  AREG    , ""       , "ustar", "00",
          198         };
          199         char   b[BLKSIZ + BLKSIZ], *p;
          200         struct header *h = (struct header *)b;
          201         struct group  *gr;
          202         struct passwd *pw;
          203         struct stat st;
          204         ssize_t l, n, r;
          205         int fd = -1;
          206 
          207         if (lstat(path, &st) < 0) {
          208                 weprintf("lstat %s:", path);
          209                 return 0;
          210         } else if (st.st_ino == tarinode && st.st_dev == tardev) {
          211                 weprintf("ignoring %s\n", path);
          212                 return 0;
          213         }
          214         pw = getpwuid(st.st_uid);
          215         gr = getgrgid(st.st_gid);
          216 
          217         *h = blank;
          218         n  = strlcpy(h->name, path, sizeof(h->name));
          219         if (n >= sizeof(h->name)) {
          220                 *++h = blank;
          221                 h->type = 'L';
          222                 putoctal(h->size,   n,         sizeof(h->size));
          223                 putoctal(h->chksum, chksum(h), sizeof(h->chksum));
          224                 ewrite(tarfd, (char *)h, BLKSIZ);
          225 
          226                 for (p = (char *)path; n > 0; n -= BLKSIZ, p += BLKSIZ) {
          227                         if (n < BLKSIZ) {
          228                                 p = memcpy(h--, p, n);
          229                                 memset(p + n, 0, BLKSIZ - n);
          230                         }
          231                         ewrite(tarfd, p, BLKSIZ);
          232                 }
          233         }
          234 
          235         putoctal(h->mode,    (unsigned)st.st_mode & 0777, sizeof(h->mode));
          236         putoctal(h->uid,     (unsigned)st.st_uid,         sizeof(h->uid));
          237         putoctal(h->gid,     (unsigned)st.st_gid,         sizeof(h->gid));
          238         putoctal(h->mtime,   (unsigned)st.st_mtime,       sizeof(h->mtime));
          239         estrlcpy(h->uname,   pw ? pw->pw_name : "",       sizeof(h->uname));
          240         estrlcpy(h->gname,   gr ? gr->gr_name : "",       sizeof(h->gname));
          241 
          242         if (S_ISREG(st.st_mode)) {
          243                 h->type = REG;
          244                 putoctal(h->size, st.st_size,  sizeof(h->size));
          245                 fd = open(path, O_RDONLY);
          246                 if (fd < 0)
          247                         eprintf("open %s:", path);
          248         } else if (S_ISDIR(st.st_mode)) {
          249                 h->type = DIRECTORY;
          250         } else if (S_ISLNK(st.st_mode)) {
          251                 h->type = SYMLINK;
          252                 if ((r = readlink(path, h->linkname, sizeof(h->linkname) - 1)) < 0)
          253                         eprintf("readlink %s:", path);
          254                 h->linkname[r] = '\0';
          255         } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
          256                 h->type = S_ISCHR(st.st_mode) ? CHARDEV : BLOCKDEV;
          257                 putoctal(h->major, (unsigned)major(st.st_dev), sizeof(h->major));
          258                 putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof(h->minor));
          259         } else if (S_ISFIFO(st.st_mode)) {
          260                 h->type = FIFO;
          261         }
          262 
          263         putoctal(h->chksum, chksum(h), sizeof(h->chksum));
          264         ewrite(tarfd, b, BLKSIZ);
          265 
          266         if (fd != -1) {
          267                 while ((l = eread(fd, b, BLKSIZ)) > 0) {
          268                         if (l < BLKSIZ)
          269                                 memset(b + l, 0, BLKSIZ - l);
          270                         ewrite(tarfd, b, BLKSIZ);
          271                 }
          272                 close(fd);
          273         }
          274 
          275         return 0;
          276 }
          277 
          278 static int
          279 unarchive(char *fname, ssize_t l, char b[BLKSIZ])
          280 {
          281         struct header *h = (struct header *)b;
          282         struct timespec times[2];
          283         char lname[101], *tmp, *p;
          284         long mode, major, minor, type, mtime, uid, gid;
          285         int  fd = -1, lnk = h->type == SYMLINK;
          286 
          287         if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0'))
          288                 eprintf("strtol %s: invalid mtime\n", h->mtime);
          289         if (strcmp(fname, ".") && strcmp(fname, "./") && remove(fname) < 0)
          290                 if (errno != ENOENT) weprintf("remove %s:", fname);
          291 
          292         tmp = estrdup(fname);
          293         mkdirp(dirname(tmp), 0777, 0777);
          294         free(tmp);
          295 
          296         switch (h->type) {
          297         case REG:
          298         case AREG:
          299         case RESERVED:
          300                 if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
          301                         eprintf("strtol %s: invalid mode\n", h->mode);
          302                 fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
          303                 if (fd < 0)
          304                         eprintf("open %s:", fname);
          305                 break;
          306         case HARDLINK:
          307         case SYMLINK:
          308                 snprintf(lname, sizeof(lname), "%.*s", (int)sizeof(h->linkname),
          309                          h->linkname);
          310                 if ((lnk ? symlink:link)(lname, fname) < 0)
          311                         eprintf("%s %s -> %s:", lnk ? "symlink":"link", fname, lname);
          312                 lnk++;
          313                 break;
          314         case DIRECTORY:
          315                 if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
          316                         eprintf("strtol %s: invalid mode\n", h->mode);
          317                 if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST)
          318                         eprintf("mkdir %s:", fname);
          319                 pushdirtime(fname, mtime);
          320                 break;
          321         case CHARDEV:
          322         case BLOCKDEV:
          323                 if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
          324                         eprintf("strtol %s: invalid mode\n", h->mode);
          325                 if ((major = strtol(h->major, &p, 8)) < 0 || *p != '\0')
          326                         eprintf("strtol %s: invalid major device\n", h->major);
          327                 if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0')
          328                         eprintf("strtol %s: invalid minor device\n", h->minor);
          329                 type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
          330                 if (mknod(fname, type | mode, makedev(major, minor)) < 0)
          331                         eprintf("mknod %s:", fname);
          332                 break;
          333         case FIFO:
          334                 if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
          335                         eprintf("strtol %s: invalid mode\n", h->mode);
          336                 if (mknod(fname, S_IFIFO | mode, 0) < 0)
          337                         eprintf("mknod %s:", fname);
          338                 break;
          339         default:
          340                 eprintf("unsupported tar-filetype %c\n", h->type);
          341         }
          342 
          343         if ((uid = strtol(h->uid, &p, 8)) < 0 || *p != '\0')
          344                 eprintf("strtol %s: invalid uid\n", h->uid);
          345         if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0')
          346                 eprintf("strtol %s: invalid gid\n", h->gid);
          347 
          348         if (fd != -1) {
          349                 for (; l > 0; l -= BLKSIZ)
          350                         if (eread(tarfd, b, BLKSIZ) > 0)
          351                                 ewrite(fd, b, MIN(l, BLKSIZ));
          352                 close(fd);
          353         }
          354 
          355         if (lnk == 1)
          356                 return 0;
          357 
          358         times[0].tv_sec = times[1].tv_sec = mtime;
          359         times[0].tv_nsec = times[1].tv_nsec = 0;
          360         if (!mflag && utimensat(AT_FDCWD, fname, times, AT_SYMLINK_NOFOLLOW) < 0)
          361                 weprintf("utimensat %s:", fname);
          362         if (lnk) {
          363                 if (!getuid() && lchown(fname, uid, gid))
          364                         weprintf("lchown %s:", fname);
          365         } else {
          366                 if (!getuid() && chown(fname, uid, gid))
          367                         weprintf("chown %s:", fname);
          368                 if (chmod(fname, mode) < 0)
          369                         eprintf("fchmod %s:", fname);
          370         }
          371 
          372         return 0;
          373 }
          374 
          375 static void
          376 skipblk(ssize_t l)
          377 {
          378         char b[BLKSIZ];
          379 
          380         for (; l > 0; l -= BLKSIZ)
          381                 if (!eread(tarfd, b, BLKSIZ))
          382                         break;
          383 }
          384 
          385 static int
          386 print(char *fname, ssize_t l, char b[BLKSIZ])
          387 {
          388         puts(fname);
          389         skipblk(l);
          390         return 0;
          391 }
          392 
          393 static void
          394 c(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r)
          395 {
          396         archive(r->path);
          397         if (vflag)
          398                 puts(r->path);
          399 
          400         if (S_ISDIR(st->st_mode))
          401                 recurse(dirfd, name, NULL, r);
          402 }
          403 
          404 static void
          405 sanitize(struct header *h)
          406 {
          407         size_t i, j, l;
          408         struct {
          409                 char  *f;
          410                 size_t l;
          411         } fields[] = {
          412                 { h->mode,   sizeof(h->mode)   },
          413                 { h->uid,    sizeof(h->uid)    },
          414                 { h->gid,    sizeof(h->gid)    },
          415                 { h->size,   sizeof(h->size)   },
          416                 { h->mtime,  sizeof(h->mtime)  },
          417                 { h->chksum, sizeof(h->chksum) },
          418                 { h->major,  sizeof(h->major)  },
          419                 { h->minor,  sizeof(h->minor)  }
          420         };
          421 
          422         /* Numeric fields can be terminated with spaces instead of
          423          * NULs as per the ustar specification.  Patch all of them to
          424          * use NULs so we can perform string operations on them. */
          425         for (i = 0; i < LEN(fields); i++){
          426                 j = 0, l = fields[i].l - 1;
          427                 for (; j < l && fields[i].f[j] == ' '; j++);
          428                 for (; j <= l; j++)
          429                         if (fields[i].f[j] == ' ')
          430                                 fields[i].f[j] = '\0';
          431                 if (fields[i].f[l])
          432                         eprintf("numeric field #%d (%.*s) is not null or space terminated\n",
          433                                 i, l+1, fields[i].f);
          434         }
          435 }
          436 
          437 static void
          438 chktar(struct header *h)
          439 {
          440         const char *reason;
          441         char tmp[sizeof h->chksum], *err;
          442         long sum, i;
          443 
          444         if (h->prefix[0] == '\0' && h->name[0] == '\0') {
          445                 reason = "empty filename";
          446                 goto bad;
          447         }
          448         if (h->magic[0] && strncmp("ustar", h->magic, 5)) {
          449                 reason = "not ustar format";
          450                 goto bad;
          451         }
          452         memcpy(tmp, h->chksum, sizeof(tmp));
          453         for (i = sizeof(tmp)-1; i > 0 && tmp[i] == ' '; i--) {
          454                 tmp[i] = '\0';
          455         }
          456         sum = strtol(tmp, &err, 8);
          457         if (sum < 0 || sum >= BLKSIZ*256 || *err != '\0') {
          458                 reason = "invalid checksum";
          459                 goto bad;
          460         }
          461         if (sum != chksum(h)) {
          462                 reason = "incorrect checksum";
          463                 goto bad;
          464         }
          465         memcpy(h->chksum, tmp, sizeof(tmp));
          466         return;
          467 bad:
          468         eprintf("malformed tar archive: %s\n", reason);
          469 }
          470 
          471 static void
          472 xt(int argc, char *argv[], int mode)
          473 {
          474         long size, l;
          475         char b[BLKSIZ], fname[l = PATH_MAX + 1], *p, *q = NULL;
          476         int i, m, n;
          477         int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print;
          478         struct timespec times[2];
          479         struct header *h = (struct header *)b;
          480         struct dirtime *dirtime;
          481 
          482         while (eread(tarfd, b, BLKSIZ) > 0 && (h->name[0] || h->prefix[0])) {
          483                 chktar(h);
          484                 sanitize(h);
          485 
          486                 if ((size = strtol(h->size, &p, 8)) < 0 || *p != '\0')
          487                         eprintf("strtol %s: invalid size\n", h->size);
          488 
          489                 /* Long file path is read directly into fname*/
          490                 if (h->type == 'L' || h->type == 'x' || h->type == 'g') {
          491 
          492                         /* Read header only up to size of fname buffer */
          493                         for (q = fname; q < fname+size; q += BLKSIZ) {
          494                                 if (q + BLKSIZ >= fname + l)
          495                                         eprintf("name exceeds buffer: %.*s\n", q-fname, fname);
          496                                 eread(tarfd, q, BLKSIZ);
          497                         }
          498 
          499                         /* Convert pax x header with 'path=' field into L header */
          500                         if (h->type == 'x') for (q = fname; q < fname+size-16; q += n) {
          501                                 if ((n = strtol(q, &p, 10)) < 0 || *p != ' ')
          502                                         eprintf("strtol %.*s: invalid number\n", p+1-q, q);
          503                                 if (n && strncmp(p+1, "path=", 5) == 0) {
          504                                         memmove(fname, p+6, size = q+n - p-6 - 1);
          505                                         h->type = 'L';
          506                                         break;
          507                                 }
          508                         }
          509                         fname[size] = '\0';
          510 
          511                         /* Non L-like header (eg. pax 'g') is skipped by setting q=null */
          512                         if (h->type != 'L')
          513                                 q = NULL;
          514                         continue;
          515                 }
          516 
          517                 /* Ustar path is copied into fname if no L header (ie: q is NULL) */
          518                 if (!q) {
          519                         m = sizeof h->prefix, n = sizeof h->name;
          520                         p = "/" + !h->prefix[0];
          521                         snprintf(fname, l, "%.*s%s%.*s", m, h->prefix, p, n, h->name);
          522                 }
          523                 q = NULL;
          524 
          525                 /* If argc > 0 then only extract the given files/dirs */
          526                 if (argc) {
          527                         for (i = 0; i < argc; i++) {
          528                                 if (strncmp(argv[i], fname, n = strlen(argv[i])) == 0)
          529                                         if (strchr("/", fname[n]) || argv[i][n-1] == '/')
          530                                                 break;
          531                         }
          532                         if (i == argc) {
          533                                 skipblk(size);
          534                                 continue;
          535                         }
          536                 }
          537 
          538                 fn(fname, size, b);
          539                 if (vflag && mode != 't')
          540                         puts(fname);
          541         }
          542 
          543         if (mode == 'x' && !mflag) {
          544                 while ((dirtime = popdirtime())) {
          545                         times[0].tv_sec = times[1].tv_sec = dirtime->mtime;
          546                         times[0].tv_nsec = times[1].tv_nsec = 0;
          547                         if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0)
          548                                 eprintf("utimensat %s:", fname);
          549                         free(dirtime->name);
          550                 }
          551                 free(dirtimes);
          552                 dirtimes = NULL;
          553         }
          554 }
          555 
          556 char **args;
          557 int argn;
          558 
          559 static void
          560 usage(void)
          561 {
          562         eprintf("usage: %s [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p] "
          563                 "[-f file] [file ...]\n"
          564                 "       %s [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] path ... "
          565                 "[-f file]\n", argv0, argv0);
          566 }
          567 
          568 int
          569 main(int argc, char *argv[])
          570 {
          571         struct recursor r = { .fn = c, .follow = 'P', .flags = DIRFIRST };
          572         struct stat st;
          573         char *file = NULL, *dir = ".", mode = '\0';
          574         int fd;
          575 
          576         argv0 = argv[0];
          577         if (argc > 1 && strchr("cxt", mode = *argv[1]))
          578                 *(argv[1]+1) ? *argv[1] = '-' : (*++argv = argv0, --argc);
          579 
          580         ARGBEGIN {
          581         case 'x':
          582         case 'c':
          583         case 't':
          584                 mode = ARGC();
          585                 break;
          586         case 'C':
          587                 dir = EARGF(usage());
          588                 break;
          589         case 'f':
          590                 file = EARGF(usage());
          591                 break;
          592         case 'm':
          593                 mflag = 1;
          594                 break;
          595         case 'J':
          596         case 'Z':
          597         case 'a':
          598         case 'j':
          599         case 'z':
          600                 filtermode = ARGC();
          601                 filtertool = filtertools[filtermode];
          602                 break;
          603         case 'h':
          604                 r.follow = 'L';
          605                 break;
          606         case 'v':
          607                 vflag = 1;
          608                 break;
          609         case 'p':
          610                 break;  /* Do nothing as already default behaviour */
          611         default:
          612                 usage();
          613         } ARGEND
          614 
          615         switch (mode) {
          616         case 'c':
          617                 if (!argc)
          618                         usage();
          619                 tarfd = 1;
          620                 if (file && *file != '-') {
          621                         tarfd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
          622                         if (tarfd < 0)
          623                                 eprintf("open %s:", file);
          624                         if (lstat(file, &st) < 0)
          625                                 eprintf("lstat %s:", file);
          626                         tarinode = st.st_ino;
          627                         tardev = st.st_dev;
          628                 }
          629 
          630                 if (filtertool)
          631                         tarfd = comp(tarfd, filtertool, "-cf");
          632 
          633                 if (chdir(dir) < 0)
          634                         eprintf("chdir %s:", dir);
          635                 for (; *argv; argc--, argv++)
          636                         recurse(AT_FDCWD, *argv, NULL, &r);
          637                 break;
          638         case 't':
          639         case 'x':
          640                 tarfd = 0;
          641                 if (file && *file != '-') {
          642                         tarfd = open(file, O_RDONLY);
          643                         if (tarfd < 0)
          644                                 eprintf("open %s:", file);
          645                 }
          646 
          647                 if (filtertool) {
          648                         fd = tarfd;
          649                         tarfd = decomp(tarfd, filtertool, "-cdf");
          650                         close(fd);
          651                 }
          652 
          653                 if (chdir(dir) < 0)
          654                         eprintf("chdir %s:", dir);
          655                 xt(argc, argv, mode);
          656                 break;
          657         default:
          658                 usage();
          659         }
          660 
          661         return recurse_status;
          662 }