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