/* archive.c - Alpine Package Keeper (APK) * * Copyright (C) 2005-2008 Natanael Copa * Copyright (C) 2008 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. See http://www.gnu.org/ for details. */ #include #include #include #include #include #include #include #include #include #include "apk_defines.h" #include "apk_archive.h" #ifndef GUNZIP_BINARY #define GUNZIP_BINARY "/bin/gunzip" #endif struct tar_header { /* ustar header, Posix 1003.1 */ char name[100]; /* 0-99 */ char mode[8]; /* 100-107 */ char uid[8]; /* 108-115 */ char gid[8]; /* 116-123 */ char size[12]; /* 124-135 */ char mtime[12]; /* 136-147 */ char chksum[8]; /* 148-155 */ char typeflag; /* 156-156 */ char linkname[100]; /* 157-256 */ char magic[8]; /* 257-264 */ char uname[32]; /* 265-296 */ char gname[32]; /* 297-328 */ char devmajor[8]; /* 329-336 */ char devminor[8]; /* 337-344 */ char prefix[155]; /* 345-499 */ char padding[12]; /* 500-512 */ }; static int get_dev_null(void) { static int fd_null = 0; if (fd_null == 0) { fd_null = open("/dev/null", O_WRONLY); if (fd_null < 0) err(EX_OSFILE, "/dev/null"); } return fd_null; } pid_t apk_open_gz(int *fd) { int pipe_fd[2]; pid_t child_pid; if (pipe(pipe_fd) < 0) err(EX_OSERR, "pipe"); child_pid = fork(); if (child_pid < 0) err(EX_OSERR, "fork"); if (child_pid == 0) { close(pipe_fd[0]); dup2(pipe_fd[1], STDOUT_FILENO); dup2(*fd, STDIN_FILENO); dup2(get_dev_null(), STDERR_FILENO); close(pipe_fd[1]); execl(GUNZIP_BINARY, "gunzip", "-c", NULL); err(EX_UNAVAILABLE, GUNZIP_BINARY); } close(pipe_fd[1]); *fd = pipe_fd[0]; return child_pid; } #define GET_OCTAL(s) apk_blob_uint(APK_BLOB_PTR_LEN(s, sizeof(s)), 8) static int do_splice(int from_fd, int to_fd, int len) { int i = 0, r; while (i != len) { r = splice(from_fd, NULL, to_fd, NULL, len - i, SPLICE_F_MOVE); if (r == -1) return i; i += r; } return i; } int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx) { struct apk_archive_entry entry; struct tar_header buf; unsigned long offset = 0; int end = 0, r; memset(&entry, 0, sizeof(entry)); while (read(fd, &buf, 512) == 512) { offset += 512; if (buf.name[0] == '\0') { if (end) break; end++; continue; } entry = (struct apk_archive_entry){ .size = GET_OCTAL(buf.size), .uid = GET_OCTAL(buf.uid), .gid = GET_OCTAL(buf.gid), .mode = GET_OCTAL(buf.mode) & 07777, .mtime = GET_OCTAL(buf.mtime), .name = entry.name, .uname = buf.uname, .gname = buf.gname, .device = makedev(GET_OCTAL(buf.devmajor), GET_OCTAL(buf.devminor)), .read_fd = fd, }; switch (buf.typeflag) { case 'L': if (entry.name != NULL) free(entry.name); entry.name = malloc(entry.size+1); read(fd, entry.name, entry.size); offset += entry.size; entry.size = 0; break; case '0': case '7': /* regular file */ entry.mode |= S_IFREG; break; case '1': /* hard link */ entry.mode |= S_IFREG; entry.link_target = buf.linkname; break; case '2': /* symbolic link */ entry.mode |= S_IFLNK; entry.link_target = buf.linkname; break; case '3': /* char device */ entry.mode |= S_IFCHR; break; case '4': /* block devicek */ entry.mode |= S_IFBLK; break; case '5': /* directory */ entry.mode |= S_IFDIR; break; default: break; } if (entry.mode & S_IFMT) { if (entry.name == NULL) entry.name = strdup(buf.name); /* callback parser function */ offset += entry.size; r = parser(&entry, ctx); if (r != 0) return r; offset -= entry.size; free(entry.name); entry.name = NULL; } if (entry.size) offset += do_splice(fd, get_dev_null(), entry.size); /* align to next 512 block */ if (offset & 511) offset += do_splice(fd, get_dev_null(), 512 - (offset & 511)); } return 0; } int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx) { pid_t pid; int r, status; pid = apk_open_gz(&fd); if (pid < 0) return pid; r = apk_parse_tar(fd, parser, ctx); close(fd); waitpid(pid, &status, 0); return r; } apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae) { char *str; int pos = 0; ssize_t r; str = malloc(ae->size + 1); pos = 0; while (ae->size) { r = read(ae->read_fd, &str[pos], ae->size); if (r < 0) { free(str); return APK_BLOB_NULL; } pos += r; ae->size -= r; } str[pos] = 0; return APK_BLOB_PTR_LEN(str, pos+1); } int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *fn) { int r = -1; if (fn == NULL) fn = ae->name; /* BIG HONKING FIXME */ unlink(fn); switch (ae->mode & S_IFMT) { case S_IFDIR: r = mkdir(fn, ae->mode & 07777); if (r < 0 && errno == EEXIST) r = 0; break; case S_IFREG: if (ae->link_target == NULL) { r = open(fn, O_WRONLY | O_CREAT, ae->mode & 07777); if (r < 0) break; ae->size -= do_splice(ae->read_fd, r, ae->size); close(r); r = ae->size ? -1 : 0; } else { r = link(ae->link_target, fn); } break; case S_IFLNK: r = symlink(ae->link_target, fn); break; case S_IFSOCK: case S_IFBLK: case S_IFCHR: case S_IFIFO: r = mknod(fn, ae->mode & 07777, ae->device); break; } if (r == 0) { if (!S_ISLNK(ae->mode)) r = chown(fn, ae->uid, ae->gid); else r = lchown(fn, ae->uid, ae->gid); if (r < 0) apk_error("Failed to set ownership on %s: %s", fn, strerror(errno)); } else { apk_error("Failed to extract %s\n", ae->name); } return r; } struct checksum_and_tee { int in_fd, tee_fd; void *ptr; }; static void *__apk_checksum_and_tee(void *arg) { struct checksum_and_tee *args = (struct checksum_and_tee *) arg; char buf[2*1024]; int r, w, wt; __off64_t offset; csum_ctx_t ctx; int dosplice = 1; offset = lseek(args->in_fd, 0, SEEK_CUR); csum_init(&ctx); do { r = read(args->in_fd, buf, sizeof(buf)); if (r <= 0) break; wt = 0; do { if (dosplice) { w = splice(args->in_fd, &offset, args->tee_fd, NULL, r - wt, SPLICE_F_MOVE); if (w < 0) { dosplice = 0; continue; } } else { w = write(args->tee_fd, &buf[wt], r - wt); if (w < 0) break; } wt += w; } while (wt != r); csum_process(&ctx, buf, r); } while (r == sizeof(buf)); csum_finish(&ctx, args->ptr); close(args->tee_fd); close(args->in_fd); free(args); return NULL; } pthread_t apk_checksum_and_tee(int *fd, void *ptr) { struct checksum_and_tee *args; int fds[2]; pthread_t tid; if (pipe(fds) < 0) return -1; fcntl(fds[0], F_SETFD, FD_CLOEXEC); fcntl(fds[1], F_SETFD, FD_CLOEXEC); args = malloc(sizeof(*args)); *args = (struct checksum_and_tee){ *fd, fds[1], ptr }; if (pthread_create(&tid, NULL, __apk_checksum_and_tee, args) < 0) return -1; *fd = fds[0]; return tid; }