#include "unpack.h" static const struct option long_opts[] = { { "root", required_argument, NULL, 'r' }, { "no-chown", required_argument, NULL, 'o' }, { "no-chmod", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0 }, }; static const char *short_opts = "r:om"; static image_entry_t *get_file_entry(image_entry_t *list, uint32_t id) { while (list != NULL) { if (S_ISREG(list->mode) && list->data.file.id == id) return list; list = list->next; } return NULL; } static int unpack_files(int dirfd, image_entry_t *list, pkg_reader_t *rd) { uint8_t buffer[2048]; image_entry_t *meta; file_data_t frec; ssize_t ret; size_t diff; uint64_t i; int fd; for (;;) { ret = pkg_reader_read_payload(rd, &frec, sizeof(frec)); if (ret == 0) break; if (ret < 0) return -1; if ((size_t)ret < sizeof(frec)) goto fail_trunc; frec.id = le32toh(frec.id); meta = get_file_entry(list, frec.id); if (meta == NULL) { fprintf(stderr, "%s: missing meta information for " "file %u\n", pkg_reader_get_filename(rd), (unsigned int)frec.id); return -1; } fd = openat(dirfd, meta->name, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { perror(meta->name); return -1; } for (i = 0; i < meta->data.file.size; i += ret) { if ((meta->data.file.size - i) < (uint64_t)sizeof(buffer)) { diff = meta->data.file.size - i; } else { diff = sizeof(buffer); } ret = pkg_reader_read_payload(rd, buffer, diff); if (ret < 0) goto fail_fd; if ((size_t)ret < diff) goto fail_trunc; ret = write_retry(fd, buffer, diff); if (ret < 0) { perror(meta->name); goto fail_fd; } if ((size_t)ret < diff) { fprintf(stderr, "%s: truncated write\n", pkg_reader_get_filename(rd)); goto fail_fd; } } close(fd); } return 0; fail_fd: close(fd); return -1; fail_trunc: fprintf(stderr, "%s: truncated file data record\n", pkg_reader_get_filename(rd)); return -1; } static int change_permissions(int dirfd, image_entry_t *list, int flags) { while (list != NULL) { if (S_ISLNK(list->mode)) { list = list->next; continue; } if (!(flags & FLAG_NO_CHMOD) && !S_ISLNK(list->mode)) { if (fchmodat(dirfd, list->name, list->mode & 07777, 0)) { fprintf(stderr, "%s: chmod: %s\n", list->name, strerror(errno)); return -1; } } if (!(flags & FLAG_NO_CHOWN)) { if (fchownat(dirfd, list->name, list->uid, list->gid, AT_SYMLINK_NOFOLLOW)) { fprintf(stderr, "%s: chown: %s\n", list->name, strerror(errno)); return -1; } } list = list->next; } return 0; } static int cmd_unpack(int argc, char **argv) { const char *root = NULL, *filename; int i, rootfd, ret, flags = 0; image_entry_t *list = NULL; pkg_reader_t *rd; record_t *hdr; for (;;) { i = getopt_long(argc, argv, short_opts, long_opts, NULL); if (i == -1) break; switch (i) { case 'r': root = optarg; break; case 'o': flags |= FLAG_NO_CHOWN; break; case 'm': flags |= FLAG_NO_CHMOD; break; default: tell_read_help(argv[0]); return EXIT_FAILURE; } } if (optind >= argc) { fputs("missing argument: package file\n", stderr); return EXIT_FAILURE; } filename = argv[optind++]; if (optind < argc) fputs("warning: ignoring extra arguments\n", stderr); if (root == NULL) { rootfd = AT_FDCWD; } else { if (mkdir_p(root)) return EXIT_FAILURE; rootfd = open(root, O_RDONLY | O_DIRECTORY); if (rootfd < 0) { perror(root); return EXIT_FAILURE; } } rd = pkg_reader_open(filename); if (rd == NULL) goto fail_rootfd; list = image_entry_list_from_package(rd); if (list == NULL) { pkg_reader_close(rd); goto fail_rootfd; } list = image_entry_sort(list); if (pkg_reader_rewind(rd)) goto fail; if (create_hierarchy(rootfd, list)) goto fail; for (;;) { ret = pkg_reader_get_next_record(rd); if (ret == 0) break; if (ret < 0) goto fail; hdr = pkg_reader_current_record_header(rd); if (hdr->magic == PKG_MAGIC_DATA) { if (unpack_files(rootfd, list, rd)) goto fail; } } if (change_permissions(rootfd, list, flags)) goto fail; image_entry_free_list(list); pkg_reader_close(rd); if (rootfd != AT_FDCWD) close(rootfd); return EXIT_SUCCESS; fail: image_entry_free_list(list); pkg_reader_close(rd); fail_rootfd: if (rootfd != AT_FDCWD) close(rootfd); return EXIT_FAILURE; } static command_t unpack = { .cmd = "unpack", .usage = "[OPTIONS...] ", .s_desc = "unpack the contents of a package file", .l_desc = "The unpack command extracts the file hierarchy stored in a package into\n" "a destination directory (default: current working directory).\n" "\n" "Possible options:\n" " --root, -r A root directory to unpack the package. Defaults\n" " to the current working directory if not set.\n" " --no-chown, -o Do not change ownership of the extracted data.\n" " Keep the uid/gid of the user who runs the program.\n" " --no-chmod, -m Do not change permission flags of the extarcted\n" " data. Use 0644 for all files and 0755 for all\n" " directories.\n", .run_cmd = cmd_unpack, }; REGISTER_COMMAND(unpack)