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 }