Introduction
Introduction Statistics Contact Development Disclaimer Help
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 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.