| /* tar.c - create/extract archives |
| * |
| * Copyright 2014 Ashwini Kumar <ak.ashwini81@gmail.com> |
| * |
| * USTAR interchange format is of interest in |
| * See http://http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html |
| * For writing to external program |
| * http://www.gnu.org/software/tar/manual/html_node/Writing-to-an-External-Program.html |
| |
| USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc]", TOYFLAG_USR|TOYFLAG_BIN)) |
| |
| config TAR |
| bool "tar" |
| default n |
| help |
| usage: tar -[cxtzhmvO] [-X FILE] [-T FILE] [-f TARFILE] [-C DIR] |
| |
| Create, extract, or list files from a tar file |
| |
| Operation: |
| c Create |
| f Name of TARFILE ('-' for stdin/out) |
| h Follow symlinks |
| m Don't restore mtime |
| t List |
| v Verbose |
| x Extract |
| z (De)compress using gzip |
| C Change to DIR before operation |
| O Extract to stdout |
| exclude=FILE File to exclude |
| X File with names to exclude |
| T File with names to include |
| */ |
| |
| #define FOR_tar |
| #include "toys.h" |
| |
| GLOBALS( |
| char *fname; |
| char *dir; |
| struct arg_list *inc_file; |
| struct arg_list *exc_file; |
| char *tocmd; |
| struct arg_list *exc; |
| |
| struct arg_list *inc, *pass; |
| void *inodes, *handle; |
| ) |
| |
| struct tar_hdr { |
| char name[100], mode[8], uid[8], gid[8],size[12], mtime[12], chksum[8], |
| type, link[100], magic[8], uname[32], gname[32], major[8], minor[8], |
| prefix[155], padd[12]; |
| }; |
| |
| struct file_header { |
| char *name, *link_target, *uname, *gname; |
| off_t size; |
| uid_t uid; |
| gid_t gid; |
| mode_t mode; |
| time_t mtime; |
| dev_t device; |
| }; |
| |
| struct archive_handler { |
| int src_fd; |
| struct file_header file_hdr; |
| off_t offset; |
| void (*extract_handler)(struct archive_handler*); |
| }; |
| |
| struct inode_list { |
| struct inode_list *next; |
| char *arg; |
| ino_t ino; |
| dev_t dev; |
| }; |
| |
| static void copy_in_out(int src, int dst, off_t size) |
| { |
| int i, rd, rem = size%512, cnt; |
| |
| cnt = size/512 + (rem?1:0); |
| |
| for (i = 0; i < cnt; i++) { |
| rd = (i == cnt-1 && rem) ? rem : 512; |
| xreadall(src, toybuf, rd); |
| writeall(dst, toybuf, rd); |
| } |
| } |
| |
| //convert to octal |
| static void itoo(char *str, int len, off_t val) |
| { |
| char *t, tmp[sizeof(off_t)*3+1]; |
| int cnt = sprintf(tmp, "%0*llo", len, (unsigned long long)val); |
| |
| t = tmp + cnt - len; |
| if (*t == '0') t++; |
| memcpy(str, t, len); |
| } |
| |
| static struct inode_list *seen_inode(void **list, struct stat *st, char *name) |
| { |
| if (!st) llist_traverse(*list, llist_free_arg); |
| else if (!S_ISDIR(st->st_mode) && st->st_nlink > 1) { |
| struct inode_list *new; |
| |
| for (new = *list; new; new = new->next) |
| if(new->ino == st->st_ino && new->dev == st->st_dev) |
| return new; |
| |
| new = xzalloc(sizeof(*new)); |
| new->ino = st->st_ino; |
| new->dev = st->st_dev; |
| new->arg = xstrdup(name); |
| new->next = *list; |
| *list = new; |
| } |
| return 0; |
| } |
| |
| static void write_longname(struct archive_handler *tar, char *name, char type) |
| { |
| struct tar_hdr tmp; |
| unsigned int sum = 0; |
| int i, sz = strlen(name) +1; |
| char buf[512] = {0,}; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| strcpy(tmp.name, "././@LongLink"); |
| sprintf(tmp.mode, "%0*d", (int)sizeof(tmp.mode)-1, 0); |
| sprintf(tmp.uid, "%0*d", (int)sizeof(tmp.uid)-1, 0); |
| sprintf(tmp.gid, "%0*d", (int)sizeof(tmp.gid)-1, 0); |
| sprintf(tmp.size, "%0*d", (int)sizeof(tmp.size)-1, 0); |
| sprintf(tmp.mtime, "%0*d", (int)sizeof(tmp.mtime)-1, 0); |
| itoo(tmp.size, sizeof(tmp.size), sz); |
| tmp.type = type; |
| memset(tmp.chksum, ' ', 8); |
| strcpy(tmp.magic, "ustar "); |
| for (i= 0; i < 512; i++) sum += (unsigned int)((char*)&tmp)[i]; |
| itoo(tmp.chksum, sizeof(tmp.chksum)-1, sum); |
| |
| writeall(tar->src_fd, (void*) &tmp, sizeof(tmp)); |
| //write name to archive |
| writeall(tar->src_fd, name, sz); |
| if (sz%512) writeall(tar->src_fd, buf, (512-(sz%512))); |
| } |
| |
| static int filter(struct arg_list *lst, char *name) |
| { |
| struct arg_list *cur; |
| |
| for (cur = lst; cur; cur = cur->next) |
| if (!fnmatch(cur->arg, name, 1<<3)) return 1; |
| return 0; |
| } |
| |
| static void add_file(struct archive_handler *tar, char **nam, struct stat *st) |
| { |
| struct tar_hdr hdr; |
| struct passwd *pw; |
| struct group *gr; |
| struct inode_list *node; |
| int i, fd =-1; |
| char *c, *p, *name = *nam, *lnk, *hname, buf[512] = {0,}; |
| unsigned int sum = 0; |
| static int warn = 1; |
| |
| for (p = name; *p; p++) |
| if ((p == name || p[-1] == '/') && *p != '/' |
| && filter(TT.exc, p)) return; |
| |
| if (S_ISDIR(st->st_mode) && name[strlen(name)-1] != '/') { |
| lnk = xmprintf("%s/",name); |
| free(name); |
| *nam = name = lnk; |
| } |
| hname = name; |
| //remove leading '/' or relative path '../' component |
| if (*hname == '/') hname++; |
| if (!*hname) return; |
| while ((c = strstr(hname, "../"))) hname = c + 3; |
| if (warn && hname != name) { |
| fprintf(stderr, "removing leading '%.*s' " |
| "from member names\n", (int)(hname-name), name); |
| warn = 0; |
| } |
| |
| memset(&hdr, 0, sizeof(hdr)); |
| strncpy(hdr.name, hname, sizeof(hdr.name)); |
| itoo(hdr.mode, sizeof(hdr.mode), st->st_mode &07777); |
| itoo(hdr.uid, sizeof(hdr.uid), st->st_uid); |
| itoo(hdr.gid, sizeof(hdr.gid), st->st_gid); |
| itoo(hdr.size, sizeof(hdr.size), 0); //set size later |
| itoo(hdr.mtime, sizeof(hdr.mtime), st->st_mtime); |
| for (i=0; i<sizeof(hdr.chksum); i++) hdr.chksum[i] = ' '; |
| |
| if ((node = seen_inode(&TT.inodes, st, hname))) { |
| //this is a hard link |
| hdr.type = '1'; |
| if (strlen(node->arg) > sizeof(hdr.link)) |
| write_longname(tar, hname, 'K'); //write longname LINK |
| xstrncpy(hdr.link, node->arg, sizeof(hdr.link)); |
| } else if (S_ISREG(st->st_mode)) { |
| hdr.type = '0'; |
| if (st->st_size <= (off_t)0777777777777LL) |
| itoo(hdr.size, sizeof(hdr.size), st->st_size); |
| else { |
| error_msg("can't store file '%s' of size '%lld'\n", |
| hname, (unsigned long long)st->st_size); |
| return; |
| } |
| } else if (S_ISLNK(st->st_mode)) { |
| hdr.type = '2'; //'K' long link |
| if (!(lnk = xreadlink(name))) { |
| perror_msg("readlink"); |
| return; |
| } |
| if (strlen(lnk) > sizeof(hdr.link)) |
| write_longname(tar, hname, 'K'); //write longname LINK |
| xstrncpy(hdr.link, lnk, sizeof(hdr.link)); |
| free(lnk); |
| } |
| else if (S_ISDIR(st->st_mode)) hdr.type = '5'; |
| else if (S_ISFIFO(st->st_mode)) hdr.type = '6'; |
| else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { |
| hdr.type = (S_ISCHR(st->st_mode))?'3':'4'; |
| itoo(hdr.major, sizeof(hdr.major), dev_major(st->st_rdev)); |
| itoo(hdr.minor, sizeof(hdr.minor), dev_minor(st->st_rdev)); |
| } else { |
| error_msg("unknown file type '%o'", st->st_mode & S_IFMT); |
| return; |
| } |
| if (strlen(hname) > sizeof(hdr.name)) |
| write_longname(tar, hname, 'L'); //write longname NAME |
| strcpy(hdr.magic, "ustar "); |
| if ((pw = getpwuid(st->st_uid))) |
| snprintf(hdr.uname, sizeof(hdr.uname), "%s", pw->pw_name); |
| else snprintf(hdr.uname, sizeof(hdr.uname), "%d", st->st_uid); |
| |
| if ((gr = getgrgid(st->st_gid))) |
| snprintf(hdr.gname, sizeof(hdr.gname), "%s", gr->gr_name); |
| else snprintf(hdr.gname, sizeof(hdr.gname), "%d", st->st_gid); |
| |
| //calculate chksum. |
| for (i= 0; i < 512; i++) sum += (unsigned int)((char*)&hdr)[i]; |
| itoo(hdr.chksum, sizeof(hdr.chksum)-1, sum); |
| if (toys.optflags & FLAG_v) printf("%s\n",hname); |
| writeall(tar->src_fd, (void*)&hdr, 512); |
| |
| //write actual data to archive |
| if (hdr.type != '0') return; //nothing to write |
| if ((fd = open(name, O_RDONLY)) < 0) { |
| perror_msg("can't open '%s'", name); |
| return; |
| } |
| copy_in_out(fd, tar->src_fd, st->st_size); |
| if (st->st_size%512) writeall(tar->src_fd, buf, (512-(st->st_size%512))); |
| close(fd); |
| } |
| |
| static int add_to_tar(struct dirtree *node) |
| { |
| struct stat st; |
| char *path; |
| struct archive_handler *hdl = (struct archive_handler*)TT.handle; |
| |
| if (!fstat(hdl->src_fd, &st) && st.st_dev == node->st.st_dev |
| && st.st_ino == node->st.st_ino) { |
| error_msg("'%s' file is the archive; not dumped", TT.fname); |
| return ((DIRTREE_RECURSE | ((toys.optflags & FLAG_h)?DIRTREE_SYMFOLLOW:0))); |
| } |
| |
| if (!dirtree_notdotdot(node)) return 0; |
| path = dirtree_path(node, 0); |
| add_file(hdl, &path, &(node->st)); //path may be modified |
| free(path); |
| if (toys.optflags & FLAG_no_recursion) return 0; |
| return ((DIRTREE_RECURSE | ((toys.optflags & FLAG_h)?DIRTREE_SYMFOLLOW:0))); |
| } |
| |
| static void compress_stream(struct archive_handler *tar_hdl) |
| { |
| int pipefd[2]; |
| pid_t cpid; |
| |
| xpipe(pipefd); |
| |
| signal(SIGPIPE, SIG_IGN); |
| cpid = fork(); |
| if (cpid == -1) perror_exit("fork"); |
| |
| if (!cpid) { /* Child reads from pipe */ |
| char *argv[] = {"gzip", "-f", NULL}; |
| xclose(pipefd[1]); /* Close unused write*/ |
| dup2(pipefd[0], 0); |
| dup2(tar_hdl->src_fd, 1); //write to tar fd |
| xexec(argv); |
| } else { |
| xclose(pipefd[0]); /* Close unused read end */ |
| dup2(pipefd[1], tar_hdl->src_fd); //write to pipe |
| } |
| } |
| |
| static void extract_to_stdout(struct archive_handler *tar) |
| { |
| struct file_header *file_hdr = &tar->file_hdr; |
| |
| copy_in_out(tar->src_fd, 0, file_hdr->size); |
| tar->offset += file_hdr->size; |
| } |
| |
| static void extract_to_command(struct archive_handler *tar) |
| { |
| int pipefd[2], status = 0; |
| pid_t cpid; |
| struct file_header *file_hdr = &tar->file_hdr; |
| |
| if (pipe(pipefd) == -1) error_exit("pipe"); |
| if (!S_ISREG(file_hdr->mode)) return; //only regular files are supported. |
| |
| cpid = fork(); |
| if (cpid == -1) perror_exit("fork"); |
| |
| if (!cpid) { // Child reads from pipe |
| char buf[64], *argv[4] = {"sh", "-c", TT.tocmd, NULL}; |
| |
| setenv("TAR_FILETYPE", "f", 1); |
| sprintf(buf, "%0o", file_hdr->mode); |
| setenv("TAR_MODE", buf, 1); |
| sprintf(buf, "%ld", (long)file_hdr->size); |
| setenv("TAR_SIZE", buf, 1); |
| setenv("TAR_FILENAME", file_hdr->name, 1); |
| setenv("TAR_UNAME", file_hdr->uname, 1); |
| setenv("TAR_GNAME", file_hdr->gname, 1); |
| sprintf(buf, "%0o", (int)file_hdr->mtime); |
| setenv("TAR_MTIME", buf, 1); |
| sprintf(buf, "%0o", file_hdr->uid); |
| setenv("TAR_UID", buf, 1); |
| sprintf(buf, "%0o", file_hdr->gid); |
| setenv("TAR_GID", buf, 1); |
| |
| xclose(pipefd[1]); // Close unused write |
| dup2(pipefd[0], 0); |
| signal(SIGPIPE, SIG_DFL); |
| xexec(argv); |
| } else { |
| xclose(pipefd[0]); // Close unused read end |
| copy_in_out(tar->src_fd, pipefd[1], file_hdr->size); |
| tar->offset += file_hdr->size; |
| xclose(pipefd[1]); |
| waitpid(cpid, &status, 0); |
| if (WIFSIGNALED(status)) |
| xprintf("tar : %d: child returned %d\n", cpid, WTERMSIG(status)); |
| } |
| } |
| |
| static void extract_to_disk(struct archive_handler *tar) |
| { |
| int flags, dst_fd = -1; |
| char *s; |
| struct stat ex; |
| struct file_header *file_hdr = &tar->file_hdr; |
| |
| flags = strlen(file_hdr->name); |
| if (flags>2) { |
| if (strstr(file_hdr->name, "/../") || !strcmp(file_hdr->name, "../") || |
| !strcmp(file_hdr->name+flags-3, "/..")) |
| { |
| error_msg("drop %s", file_hdr->name); |
| } |
| } |
| |
| if (file_hdr->name[flags-1] == '/') file_hdr->name[flags-1] = 0; |
| //Regular file with preceding path |
| if ((s = strrchr(file_hdr->name, '/'))) { |
| if (mkpathat(AT_FDCWD, file_hdr->name, 00, 2) && errno !=EEXIST) { |
| error_msg(":%s: not created", file_hdr->name); |
| return; |
| } |
| } |
| |
| //remove old file, if exists |
| if (!(toys.optflags & FLAG_k) && !S_ISDIR(file_hdr->mode) |
| && !lstat( file_hdr->name, &ex)) { |
| if (unlink(file_hdr->name)) { |
| perror_msg("can't remove: %s",file_hdr->name); |
| } |
| } |
| |
| //hard link |
| if (S_ISREG(file_hdr->mode) && file_hdr->link_target) { |
| if (link(file_hdr->link_target, file_hdr->name)) |
| perror_msg("can't link '%s' -> '%s'",file_hdr->name, file_hdr->link_target); |
| goto COPY; |
| } |
| |
| switch (file_hdr->mode & S_IFMT) { |
| case S_IFREG: |
| flags = O_WRONLY|O_CREAT|O_EXCL; |
| if (toys.optflags & FLAG_overwrite) flags = O_WRONLY|O_CREAT|O_TRUNC; |
| dst_fd = open(file_hdr->name, flags, file_hdr->mode & 07777); |
| if (dst_fd == -1) perror_msg("%s: can't open", file_hdr->name); |
| break; |
| case S_IFDIR: |
| if ((mkdir(file_hdr->name, file_hdr->mode) == -1) && errno != EEXIST) |
| perror_msg("%s: can't create", file_hdr->name); |
| break; |
| case S_IFLNK: |
| if (symlink(file_hdr->link_target, file_hdr->name)) |
| perror_msg("can't link '%s' -> '%s'",file_hdr->name, file_hdr->link_target); |
| break; |
| case S_IFBLK: |
| case S_IFCHR: |
| case S_IFIFO: |
| if (mknod(file_hdr->name, file_hdr->mode, file_hdr->device)) |
| perror_msg("can't create '%s'", file_hdr->name); |
| break; |
| default: |
| printf("type not yet supported\n"); |
| break; |
| } |
| |
| //copy file.... |
| COPY: |
| copy_in_out(tar->src_fd, dst_fd, file_hdr->size); |
| tar->offset += file_hdr->size; |
| close(dst_fd); |
| |
| if (S_ISLNK(file_hdr->mode)) return; |
| if (!(toys.optflags & FLAG_o)) { |
| //set ownership..., --no-same-owner, --numeric-owner |
| uid_t u = file_hdr->uid; |
| gid_t g = file_hdr->gid; |
| |
| if (!(toys.optflags & FLAG_numeric_owner)) { |
| struct group *gr = getgrnam(file_hdr->gname); |
| struct passwd *pw = getpwnam(file_hdr->uname); |
| if (pw) u = pw->pw_uid; |
| if (gr) g = gr->gr_gid; |
| } |
| if (chown(file_hdr->name, u, g)) |
| perror_msg("chown %d:%d '%s'", u, g, file_hdr->name);; |
| } |
| |
| if (toys.optflags & FLAG_p) // || !(toys.optflags & FLAG_no_same_permissions)) |
| chmod(file_hdr->name, file_hdr->mode); |
| |
| //apply mtime |
| if (!(toys.optflags & FLAG_m)) { |
| struct timeval times[2] = {{file_hdr->mtime, 0},{file_hdr->mtime, 0}}; |
| utimes(file_hdr->name, times); |
| } |
| } |
| |
| static void add_to_list(struct arg_list **llist, char *name) |
| { |
| struct arg_list **list = llist; |
| |
| while (*list) list=&((*list)->next); |
| *list = xzalloc(sizeof(struct arg_list)); |
| (*list)->arg = name; |
| if ((name[strlen(name)-1] == '/') && strlen(name) != 1) |
| name[strlen(name)-1] = '\0'; |
| } |
| |
| static void add_from_file(struct arg_list **llist, struct arg_list *flist) |
| { |
| char *line = NULL; |
| |
| while (flist) { |
| int fd = 0; |
| |
| if (strcmp((char *)flist->arg, "-")) |
| fd = xopen((char *)flist->arg, O_RDONLY); |
| |
| while ((line = get_line(fd))) { |
| add_to_list(llist, line); |
| } |
| if (fd) close(fd); |
| flist = flist->next; |
| } |
| } |
| |
| static struct archive_handler *init_handler() |
| { |
| struct archive_handler *tar_hdl = xzalloc(sizeof(struct archive_handler)); |
| tar_hdl->extract_handler = extract_to_disk; |
| return tar_hdl; |
| } |
| |
| //convert octal to int |
| static int otoi(char *str, int len) |
| { |
| long val; |
| char *endp, inp[len+1]; //1 for NUL termination |
| |
| memcpy(inp, str, len); |
| inp[len] = '\0'; //nul-termination made sure |
| val = strtol(inp, &endp, 8); |
| if (*endp && *endp != ' ') error_exit("invalid param"); |
| return (int)val; |
| } |
| |
| static void extract_stream(struct archive_handler *tar_hdl) |
| { |
| int pipefd[2]; |
| pid_t cpid; |
| |
| if (pipe(pipefd) == -1) error_exit("pipe"); |
| |
| cpid = fork(); |
| if (cpid == -1) perror_exit("fork"); |
| |
| if (!cpid) { /* Child reads from pipe */ |
| char *argv[] = {"gunzip", "-cf", "-", NULL}; |
| xclose(pipefd[0]); /* Close unused read*/ |
| dup2(tar_hdl->src_fd, 0); |
| dup2(pipefd[1], 1); //write to pipe |
| xexec(argv); |
| } else { |
| xclose(pipefd[1]); /* Close unused read end */ |
| dup2(pipefd[0], tar_hdl->src_fd); //read from pipe |
| } |
| } |
| |
| static char *process_extended_hdr(struct archive_handler *tar, int size) |
| { |
| char *value = NULL, *p, *buf = xzalloc(size+1); |
| |
| if (readall(tar->src_fd, buf, size) != size) error_exit("short read"); |
| buf[size] = 0; |
| tar->offset += size; |
| p = buf; |
| |
| while (size) { |
| char *key; |
| int len, n; |
| |
| // extended records are of the format: "LEN NAME=VALUE\n" |
| sscanf(p, "%d %n", &len, &n); |
| key = p + n; |
| p += len; |
| size -= len; |
| p[-1] = 0; |
| if (size < 0) { |
| error_msg("corrupted extended header"); |
| break; |
| } |
| |
| len = strlen("path="); |
| if (!strncmp(key, "path=", len)) { |
| value = key + strlen("path="); |
| break; |
| } |
| } |
| if (value) value = xstrdup(value); |
| free(buf); |
| return value; |
| } |
| |
| static void tar_skip(struct archive_handler *tar, int sz) |
| { |
| int x; |
| |
| while ((x = lskip(tar->src_fd, sz))) { |
| tar->offset += sz - x; |
| sz = x; |
| } |
| tar->offset += sz; |
| } |
| |
| static void unpack_tar(struct archive_handler *tar_hdl) |
| { |
| struct tar_hdr tar; |
| struct file_header *file_hdr; |
| int i, j, maj, min, sz, e = 0; |
| unsigned int cksum; |
| unsigned char *gzMagic; |
| char *longname = NULL, *longlink = NULL; |
| |
| while (1) { |
| cksum = 0; |
| if (tar_hdl->offset % 512) { |
| sz = 512 - tar_hdl->offset % 512; |
| tar_skip(tar_hdl, sz); |
| } |
| i = readall(tar_hdl->src_fd, &tar, 512); |
| tar_hdl->offset += i; |
| if (i != 512) { |
| if (i >= 2) goto CHECK_MAGIC; //may be a small (<512 byte)zipped file |
| error_exit("read error"); |
| } |
| |
| if (!tar.name[0]) { |
| if (e) return; //end of tar 2 empty blocks |
| e = 1;//empty jump to next block |
| continue; |
| } |
| if (strncmp(tar.magic, "ustar", 5)) { |
| //try detecting by reading magic |
| CHECK_MAGIC: |
| gzMagic = (unsigned char*)&tar; |
| if ((gzMagic[0] == 0x1f) && (gzMagic[1] == 0x8b) |
| && !lseek(tar_hdl->src_fd, -i, SEEK_CUR)) { |
| tar_hdl->offset -= i; |
| extract_stream(tar_hdl); |
| continue; |
| } |
| error_exit("invalid tar format"); |
| } |
| |
| for (j = 0; j<148; j++) cksum += (unsigned int)((char*)&tar)[j]; |
| for (j = 156; j<500; j++) cksum += (unsigned int)((char*)&tar)[j]; |
| //cksum field itself treated as ' ' |
| for ( j= 0; j<8; j++) cksum += (unsigned int)' '; |
| |
| if (cksum != otoi(tar.chksum, sizeof(tar.chksum))) error_exit("wrong cksum"); |
| |
| file_hdr = &tar_hdl->file_hdr; |
| memset(file_hdr, 0, sizeof(struct file_header)); |
| file_hdr->mode = otoi(tar.mode, sizeof(tar.mode)); |
| file_hdr->uid = otoi(tar.uid, sizeof(tar.uid)); |
| file_hdr->gid = otoi(tar.gid, sizeof(tar.gid)); |
| file_hdr->size = otoi(tar.size, sizeof(tar.size)); |
| file_hdr->mtime = otoi(tar.mtime, sizeof(tar.mtime)); |
| file_hdr->uname = xstrdup(tar.uname); |
| file_hdr->gname = xstrdup(tar.gname); |
| maj = otoi(tar.major, sizeof(tar.major)); |
| min = otoi(tar.minor, sizeof(tar.minor)); |
| file_hdr->device = dev_makedev(maj, min); |
| |
| if (tar.type <= '7') { |
| if (tar.link[0]) { |
| sz = sizeof(tar.link); |
| file_hdr->link_target = xmalloc(sz + 1); |
| memcpy(file_hdr->link_target, tar.link, sz); |
| file_hdr->link_target[sz] = '\0'; |
| } |
| |
| file_hdr->name = xzalloc(256);// pathname supported size |
| if (tar.prefix[0]) { |
| memcpy(file_hdr->name, tar.prefix, sizeof(tar.prefix)); |
| sz = strlen(file_hdr->name); |
| if (file_hdr->name[sz-1] != '/') file_hdr->name[sz] = '/'; |
| } |
| sz = strlen(file_hdr->name); |
| memcpy(file_hdr->name + sz, tar.name, sizeof(tar.name)); |
| if (file_hdr->name[255]) error_exit("filename too long"); |
| } |
| |
| switch (tar.type) { |
| // case '\0': |
| case '0': |
| case '7': |
| case '1': //Hard Link |
| file_hdr->mode |= S_IFREG; |
| break; |
| case '2': |
| file_hdr->mode |= S_IFLNK; |
| break; |
| case '3': |
| file_hdr->mode |= S_IFCHR; |
| break; |
| case '4': |
| file_hdr->mode |= S_IFBLK; |
| break; |
| case '5': |
| file_hdr->mode |= S_IFDIR; |
| break; |
| case '6': |
| file_hdr->mode |= S_IFIFO; |
| break; |
| case 'K': |
| longlink = xzalloc(file_hdr->size +1); |
| xread(tar_hdl->src_fd, longlink, file_hdr->size); |
| tar_hdl->offset += file_hdr->size; |
| continue; |
| case 'L': |
| free(longname); |
| longname = xzalloc(file_hdr->size +1); |
| xread(tar_hdl->src_fd, longname, file_hdr->size); |
| tar_hdl->offset += file_hdr->size; |
| continue; |
| case 'D': |
| case 'M': |
| case 'N': |
| case 'S': |
| case 'V': |
| case 'g': // pax global header |
| tar_skip(tar_hdl, file_hdr->size); |
| continue; |
| case 'x': // pax extended header |
| free(longname); |
| longname = process_extended_hdr(tar_hdl, file_hdr->size); |
| continue; |
| default: break; |
| } |
| |
| if (longname) { |
| free(file_hdr->name); |
| file_hdr->name = longname; |
| longname = NULL; |
| } |
| if (longlink) { |
| free(file_hdr->link_target); |
| file_hdr->link_target = longlink; |
| longlink = NULL; |
| } |
| |
| if ((file_hdr->mode & S_IFREG) && |
| file_hdr->name[strlen(file_hdr->name)-1] == '/') { |
| file_hdr->name[strlen(file_hdr->name)-1] = '\0'; |
| file_hdr->mode &= ~S_IFREG; |
| file_hdr->mode |= S_IFDIR; |
| } |
| |
| if ((file_hdr->link_target && *(file_hdr->link_target)) |
| || S_ISLNK(file_hdr->mode) || S_ISDIR(file_hdr->mode)) |
| file_hdr->size = 0; |
| |
| if (filter(TT.exc, file_hdr->name) || |
| (TT.inc && !filter(TT.inc, file_hdr->name))) goto SKIP; |
| add_to_list(&TT.pass, xstrdup(file_hdr->name)); |
| |
| if (toys.optflags & FLAG_t) { |
| if (toys.optflags & FLAG_v) { |
| char perm[11]; |
| struct tm *lc = localtime((const time_t*)&(file_hdr->mtime)); |
| |
| mode_to_string(file_hdr->mode, perm); |
| printf("%s %s/%s %9ld %d-%02d-%02d %02d:%02d:%02d ",perm,file_hdr->uname, |
| file_hdr->gname, (long)file_hdr->size, 1900+lc->tm_year, |
| 1+lc->tm_mon, lc->tm_mday, lc->tm_hour, lc->tm_min, lc->tm_sec); |
| } |
| printf("%s",file_hdr->name); |
| if (file_hdr->link_target) printf(" -> %s",file_hdr->link_target); |
| xputc('\n'); |
| SKIP: |
| tar_skip(tar_hdl, file_hdr->size); |
| } else { |
| if (toys.optflags & FLAG_v) printf("%s\n",file_hdr->name); |
| tar_hdl->extract_handler(tar_hdl); |
| } |
| free(file_hdr->name); |
| free(file_hdr->link_target); |
| free(file_hdr->uname); |
| free(file_hdr->gname); |
| } |
| } |
| |
| void tar_main(void) |
| { |
| struct archive_handler *tar_hdl; |
| int fd = 0; |
| struct arg_list *tmp; |
| char **args = toys.optargs; |
| |
| if (!geteuid()) toys.optflags |= FLAG_p; |
| |
| for (tmp = TT.exc; tmp; tmp = tmp->next) |
| tmp->arg = xstrdup(tmp->arg); //freeing at the end fails otherwise |
| |
| while(*args) add_to_list(&TT.inc, xstrdup(*args++)); |
| if (toys.optflags & FLAG_X) add_from_file(&TT.exc, TT.exc_file); |
| if (toys.optflags & FLAG_T) add_from_file(&TT.inc, TT.inc_file); |
| |
| if (toys.optflags & FLAG_c) { |
| if (!TT.inc) error_exit("empty archive"); |
| fd = 1; |
| } |
| if ((toys.optflags & FLAG_f) && strcmp(TT.fname, "-")) |
| fd = xcreate(TT.fname, fd*(O_WRONLY|O_CREAT|O_TRUNC), 0666); |
| if (toys.optflags & FLAG_C) xchdir(TT.dir); |
| |
| tar_hdl = init_handler(); |
| tar_hdl->src_fd = fd; |
| |
| if ((toys.optflags & FLAG_x) || (toys.optflags & FLAG_t)) { |
| if (toys.optflags & FLAG_O) tar_hdl->extract_handler = extract_to_stdout; |
| if (toys.optflags & FLAG_to_command) { |
| signal(SIGPIPE, SIG_IGN); //will be using pipe between child & parent |
| tar_hdl->extract_handler = extract_to_command; |
| } |
| if (toys.optflags & FLAG_z) extract_stream(tar_hdl); |
| unpack_tar(tar_hdl); |
| for (tmp = TT.inc; tmp; tmp = tmp->next) |
| if (!filter(TT.exc, tmp->arg) && !filter(TT.pass, tmp->arg)) |
| error_msg("'%s' not in archive", tmp->arg); |
| } else if (toys.optflags & FLAG_c) { |
| //create the tar here. |
| if (toys.optflags & FLAG_z) compress_stream(tar_hdl); |
| for (tmp = TT.inc; tmp; tmp = tmp->next) { |
| TT.handle = tar_hdl; |
| //recurse thru dir and add files to archive |
| dirtree_flagread(tmp->arg, DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_h), |
| add_to_tar); |
| } |
| memset(toybuf, 0, 1024); |
| writeall(tar_hdl->src_fd, toybuf, 1024); |
| seen_inode(&TT.inodes, 0, 0); |
| } |
| |
| if (CFG_TOYBOX_FREE) { |
| close(tar_hdl->src_fd); |
| free(tar_hdl); |
| llist_traverse(TT.exc, llist_free_arg); |
| llist_traverse(TT.inc, llist_free_arg); |
| llist_traverse(TT.pass, llist_free_arg); |
| } |
| } |