From 26a7d3f3e73acabd666cd2ea42922fa4eb2c0dad Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Sun, 3 Feb 2019 11:43:15 +0100 Subject: [PATCH] Add install command Signed-off-by: David Oberhollenzer --- Makefile.am | 5 ++ include/pkgreader.h | 2 + main/cmd/install/collect.c | 67 ++++++++++++++++++ main/cmd/install/install.c | 137 +++++++++++++++++++++++++++++++++++++ main/cmd/install/install.h | 44 ++++++++++++ main/cmd/install/pkglist.c | 51 ++++++++++++++ main/cmd/install/tsort.c | 69 +++++++++++++++++++ main/pkgreader.c | 30 +++++++- 8 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 main/cmd/install/collect.c create mode 100644 main/cmd/install/install.c create mode 100644 main/cmd/install/install.h create mode 100644 main/cmd/install/pkglist.c create mode 100644 main/cmd/install/tsort.c diff --git a/Makefile.am b/Makefile.am index 8c1ff8d..59ed72c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,11 @@ pkg_SOURCES += main/cmd/pack/desc.c main/cmd/pack/write_hdr.c pkg_SOURCES += main/cmd/dump/dump.c main/cmd/dump/dump.h main/cmd/dump/dump_toc.c pkg_SOURCES += main/cmd/dump/dump_header.c +# install command +pkg_SOURCES += main/cmd/install/collect.c main/cmd/install/install.c +pkg_SOURCES += main/cmd/install/install.h main/cmd/install/pkglist.c +pkg_SOURCES += main/cmd/install/tsort.c + # unpack command pkg_SOURCES += main/cmd/unpack.c diff --git a/include/pkgreader.h b/include/pkgreader.h index 8d6164a..1160fa3 100644 --- a/include/pkgreader.h +++ b/include/pkgreader.h @@ -7,6 +7,8 @@ typedef struct pkg_reader_t pkg_reader_t; pkg_reader_t *pkg_reader_open(const char *path); +pkg_reader_t *pkg_reader_open_repo(int dirfd, const char *name); + void pkg_reader_close(pkg_reader_t *reader); int pkg_reader_get_next_record(pkg_reader_t *reader); diff --git a/main/cmd/install/collect.c b/main/cmd/install/collect.c new file mode 100644 index 0000000..f792725 --- /dev/null +++ b/main/cmd/install/collect.c @@ -0,0 +1,67 @@ +#include "install.h" + +int collect_dependencies(int repofd, struct pkg_dep_list *list) +{ + struct pkg_dep_node *it; + pkg_dependency_t dep; + uint8_t buffer[257]; + pkg_reader_t *rd; + pkg_header_t hdr; + size_t i; + int ret; + + for (it = list->head; it != NULL; it = it->next) { + rd = pkg_reader_open_repo(repofd, it->name); + if (rd == NULL) + return -1; + + ret = pkg_reader_read_payload(rd, &hdr, sizeof(hdr)); + if (ret < 0) + goto fail; + if ((size_t)ret < sizeof(hdr)) + goto fail_trunc; + + it->num_deps = le16toh(hdr.num_depends); + it->deps = calloc(sizeof(it->deps[0]), it->num_deps); + if (it->deps == NULL) + goto fail_oom; + + for (i = 0; i < it->num_deps; ++i) { + ret = pkg_reader_read_payload(rd, &dep, sizeof(dep)); + if (ret < 0) + goto fail; + if ((size_t)ret < sizeof(hdr)) + goto fail_trunc; + + ret = pkg_reader_read_payload(rd, buffer, + dep.name_length); + if (ret < 0) + goto fail; + if ((size_t)ret < dep.name_length) + goto fail_trunc; + + buffer[dep.name_length] = '\0'; + + it->deps[i] = find_pkg(list, (char *)buffer); + if (it->deps[i] == NULL) { + it->deps[i] = append_pkg(list, (char *)buffer); + if (it->deps[i] == NULL) + goto fail; + } + } + + pkg_reader_close(rd); + } + + return 0; +fail_trunc: + fprintf(stderr, "%s: truncated header record\n", + pkg_reader_get_filename(rd)); + goto fail; +fail_oom: + fputs("out of memory\n", stderr); + goto fail; +fail: + pkg_reader_close(rd); + return -1; +} diff --git a/main/cmd/install/install.c b/main/cmd/install/install.c new file mode 100644 index 0000000..3ee0b12 --- /dev/null +++ b/main/cmd/install/install.c @@ -0,0 +1,137 @@ +#include "install.h" + +static const struct option long_opts[] = { + { "root", required_argument, NULL, 'r' }, + { "no-chown", required_argument, NULL, 'o' }, + { "no-chmod", required_argument, NULL, 'm' }, + { "repo-dir", required_argument, NULL, 'R' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "R:r:om"; + +static int unpack_packages(int repofd, int rootfd, int flags, + struct pkg_dep_list *list) +{ + struct pkg_dep_node *it; + pkg_reader_t *rd; + + for (it = list->head; it != NULL; it = it->next) { + rd = pkg_reader_open_repo(repofd, it->name); + if (rd == NULL) + return -1; + + if (pkg_unpack(rootfd, flags, rd)) { + pkg_reader_close(rd); + return -1; + } + + pkg_reader_close(rd); + } + + return 0; +} + +static int cmd_install(int argc, char **argv) +{ + int i, rootfd = AT_FDCWD, repofd = AT_FDCWD, flags = 0; + struct pkg_dep_list list; + int ret = EXIT_FAILURE; + + memset(&list, 0, sizeof(list)); + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'R': + if (repofd != AT_FDCWD) { + fputs("repo specified more than once\n", + stderr); + tell_read_help(argv[0]); + goto out; + } + repofd = open(optarg, O_RDONLY | O_DIRECTORY); + if (repofd < 0) { + perror(optarg); + goto out; + } + break; + case 'r': + if (rootfd != AT_FDCWD) { + fputs("root specified more than once\n", + stderr); + tell_read_help(argv[0]); + goto out; + } + + if (mkdir_p(optarg)) + goto out; + + rootfd = open(optarg, O_RDONLY | O_DIRECTORY); + if (rootfd < 0) { + perror(optarg); + goto out; + } + break; + case 'o': + flags |= UNPACK_NO_CHOWN; + break; + case 'm': + flags |= UNPACK_NO_CHMOD; + break; + default: + tell_read_help(argv[0]); + goto out; + } + } + + for (i = optind; i < argc; ++i) { + if (append_pkg(&list, argv[i]) == NULL) + goto out; + } + + if (collect_dependencies(repofd, &list)) + goto out; + + if (sort_by_dependencies(&list)) + goto out; + + if (unpack_packages(repofd, rootfd, flags, &list)) + goto out; + + ret = EXIT_SUCCESS; +out: + if (rootfd != AT_FDCWD) + close(rootfd); + if (repofd != AT_FDCWD) + close(repofd); + pkg_list_cleanup(&list); + return ret; +} + +static command_t install = { + .cmd = "install", + .usage = "[OPTIONS...] PACKAGES...", + .s_desc = "install packages and their dependencies", + .l_desc = +"The install command fetches a number of packages from a repository directory\n" +"by name and extracts them to a specified root directory. Dependencies of the\n" +"packages are evaluated and also installed.\n" +"\n" +"Possible options:\n" +" --repo-dir, -R Specify the input repository path to fetch the\n" +" packages from.\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_install, +}; + +REGISTER_COMMAND(install) diff --git a/main/cmd/install/install.h b/main/cmd/install/install.h new file mode 100644 index 0000000..506321c --- /dev/null +++ b/main/cmd/install/install.h @@ -0,0 +1,44 @@ +#ifndef INSTALL_H +#define INSTALL_H + +#include +#include +#include +#include +#include +#include + +#include "pkgreader.h" +#include "command.h" +#include "pkgio.h" +#include "util.h" + +struct pkg_dep_node { + char *name; + + /* num dependencies == number of outgoing edges */ + size_t num_deps; + + /* direct dependencies == array of outgoing edges */ + struct pkg_dep_node **deps; + + /* linked list pointer */ + struct pkg_dep_node *next; +}; + +struct pkg_dep_list { + struct pkg_dep_node *head; + struct pkg_dep_node *tail; +}; + +struct pkg_dep_node *append_pkg(struct pkg_dep_list *list, const char *name); + +struct pkg_dep_node *find_pkg(struct pkg_dep_list *list, const char *name); + +void pkg_list_cleanup(struct pkg_dep_list *list); + +int collect_dependencies(int repofd, struct pkg_dep_list *list); + +int sort_by_dependencies(struct pkg_dep_list *list); + +#endif /* INSTALL_H */ diff --git a/main/cmd/install/pkglist.c b/main/cmd/install/pkglist.c new file mode 100644 index 0000000..d55e896 --- /dev/null +++ b/main/cmd/install/pkglist.c @@ -0,0 +1,51 @@ +#include "install.h" + +struct pkg_dep_node *append_pkg(struct pkg_dep_list *list, const char *name) +{ + struct pkg_dep_node *new = calloc(1, sizeof(*new)); + + if (new == NULL) + goto fail_oom; + + new->name = strdup(name); + if (new->name == NULL) + goto fail_oom; + + if (list->tail == NULL) { + list->head = list->tail = new; + } else { + list->tail->next = new; + list->tail = new; + } + return new; +fail_oom: + fputs("out of memory\n", stderr); + free(new); + return NULL; +} + +struct pkg_dep_node *find_pkg(struct pkg_dep_list *list, const char *name) +{ + struct pkg_dep_node *it = list->head; + + while (it != NULL && strcmp(it->name, name) != 0) + it = it->next; + + return it; +} + +void pkg_list_cleanup(struct pkg_dep_list *list) +{ + struct pkg_dep_node *node; + + while (list->head != NULL) { + node = list->head; + list->head = node->next; + + free(node->deps); + free(node->name); + free(node); + } + + list->tail = NULL; +} diff --git a/main/cmd/install/tsort.c b/main/cmd/install/tsort.c new file mode 100644 index 0000000..809f58c --- /dev/null +++ b/main/cmd/install/tsort.c @@ -0,0 +1,69 @@ +#include "install.h" + +int sort_by_dependencies(struct pkg_dep_list *list) +{ + struct pkg_dep_node *it, *prev, *pkg; + struct pkg_dep_list result; + size_t i; + + result.head = NULL; + result.tail = NULL; + + while (list->head != NULL) { + /* find node with no outgoing edges */ + prev = NULL; + it = list->head; + + while (it != NULL && it->num_deps != 0) { + prev = it; + it = it->next; + } + + if (it == NULL) { + fputs("cycle detected in dependency graph\n", stderr); + pkg_list_cleanup(&result); + return -1; + } + + /* remove from graph */ + if (prev == NULL) { + list->head = it->next; + } else { + prev->next = it->next; + } + + if (it == list->tail) + list->tail = prev; + + /* remove edges pointing to the package */ + pkg = it; + + for (it = list->head; it != NULL; it = it->next) { + for (i = 0; i < it->num_deps; ++i) { + if (it->deps[i] == pkg) { + it->deps[i] = + it->deps[it->num_deps - 1]; + it->num_deps -= 1; + --i; + } + } + + if (it->num_deps == 0 && it->deps != NULL) { + free(it->deps); + it->deps = NULL; + } + } + + /* append to list */ + if (result.tail == NULL) { + result.head = result.tail = pkg; + } else { + result.tail->next = pkg; + result.tail = pkg; + } + } + + list->head = result.head; + list->tail = result.tail; + return 0; +} diff --git a/main/pkgreader.c b/main/pkgreader.c index 2894347..db50413 100644 --- a/main/pkgreader.c +++ b/main/pkgreader.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "util.h" #include "pkgreader.h" @@ -118,7 +119,7 @@ fail_comp: return -1; } -pkg_reader_t *pkg_reader_open(const char *path) +static pkg_reader_t *pkg_reader_openat(int dirfd, const char *path) { pkg_reader_t *rd = calloc(1, sizeof(*rd)); int ret; @@ -129,7 +130,7 @@ pkg_reader_t *pkg_reader_open(const char *path) } rd->path = path; - rd->fd = open(path, O_RDONLY); + rd->fd = openat(dirfd, path, O_RDONLY); if (rd->fd < 0) { perror(path); free(rd); @@ -150,6 +151,31 @@ fail: return NULL; } +pkg_reader_t *pkg_reader_open(const char *path) +{ + return pkg_reader_openat(AT_FDCWD, path); +} + +pkg_reader_t *pkg_reader_open_repo(int dirfd, const char *name) +{ + char *fname; + size_t i; + + for (i = 0; name[i] != '\0'; ++i) { + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') { + fprintf(stderr, + "illegal characters in package name '%s'\n", + name); + return NULL; + } + } + + fname = alloca(strlen(name) + 5); + sprintf(fname, "%s.pkg", name); + + return pkg_reader_openat(dirfd, fname); +} + void pkg_reader_close(pkg_reader_t *rd) { if (rd->stream != NULL)