tar.c - sbase - suckless unix tools | |
git clone git://git.suckless.org/sbase | |
Log | |
Files | |
Refs | |
README | |
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(*dirt… | |
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", "00000… | |
197 "00000000000" , " ", AREG , "" , "ustar… | |
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->mtim… | |
239 estrlcpy(h->uname, pw ? pw->pw_name : "", sizeof(h->unam… | |
240 estrlcpy(h->gname, gr ? gr->gr_name : "", sizeof(h->gnam… | |
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)… | |
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-… | |
258 putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof(h-… | |
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 != '\… | |
288 eprintf("strtol %s: invalid mtime\n", h->mtime); | |
289 if (strcmp(fname, ".") && strcmp(fname, "./") && remove(fname) <… | |
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->li… | |
309 h->linkname); | |
310 if ((lnk ? symlink:link)(lname, fname) < 0) | |
311 eprintf("%s %s -> %s:", lnk ? "symlink":"link", … | |
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->… | |
327 if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0') | |
328 eprintf("strtol %s: invalid minor device\n", h->… | |
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_NOFOL… | |
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 recur… | |
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… | |
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') ? unarc… | |
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: %.… | |
496 eread(tarfd, q, BLKSIZ); | |
497 } | |
498 | |
499 /* Convert pax x header with 'path=' field into … | |
500 if (h->type == 'x') for (q = fname; q < fname+si… | |
501 if ((n = strtol(q, &p, 10)) < 0 || *p !=… | |
502 eprintf("strtol %.*s: invalid nu… | |
503 if (n && strncmp(p+1, "path=", 5) == 0) { | |
504 memmove(fname, p+6, size = q+n -… | |
505 h->type = 'L'; | |
506 break; | |
507 } | |
508 } | |
509 fname[size] = '\0'; | |
510 | |
511 /* Non L-like header (eg. pax 'g') is skipped by… | |
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… | |
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… | |
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(a… | |
529 if (strchr("/", fname[n]) || arg… | |
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->mti… | |
546 times[0].tv_nsec = times[1].tv_nsec = 0; | |
547 if (utimensat(AT_FDCWD, dirtime->name, times, 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… | |
563 "[-f file] [file ...]\n" | |
564 " %s [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-… | |
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,… | |
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 } |