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