From ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Wed, 18 Mar 2020 22:25:27 -0400 Subject: Refactor project: build/install libspm[_static.a].so to make unit testing possible --- lib/install.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 lib/install.c (limited to 'lib/install.c') diff --git a/lib/install.c b/lib/install.c new file mode 100644 index 0000000..e0592db --- /dev/null +++ b/lib/install.c @@ -0,0 +1,328 @@ +/** + * @file install.c + */ +#include +#include "spm.h" + +void spm_install_show_package(ManifestPackage *package) { + if (package == NULL) { + fprintf(stderr, "ERROR: package was NULL\n"); + return; + } + printf(" -> %-20s %-10s (origin: %s)\n", package->name, package->version, package->origin); +} + +/** + * Install a package and its dependencies into a destination root. + * The destination is created if it does not exist. + * @param _destroot directory to install package + * @param _package name of archive to install (not a path) + * @return success=0, exists=1, error=-1 (general), -2 (unable to create `destroot`) + */ +int spm_install(SPM_Hierarchy *fs, const char *tmpdir, const char *_package) { + char *package = strdup(_package); + + if (!package) { + fprintf(SYSERROR); + return -1; + } + + if (exists(fs->rootdir) != 0) { + if (SPM_GLOBAL.verbose) { + printf("Creating destination root: %s\n", fs->rootdir); + } + if (mkdirs(fs->rootdir, 0755) != 0) { + fprintf(SYSERROR); + free(package); + return -2; + } + } + + if (SPM_GLOBAL.verbose) { + printf("Extracting archive: %s\n", package); + } + + if (tar_extract_archive(package, tmpdir) != 0) { + fprintf(stderr, "%s: %s\n", package, strerror(errno)); + free(package); + return -1; + } + + free(package); + return 0; +} + +int spm_install_package_record(SPM_Hierarchy *fs, char *tmpdir, char *package_name) { + RuntimeEnv *rt = runtime_copy(__environ); + char *records_topdir = strdup(fs->dbrecdir); + char *records_pkgdir = join((char *[]) {records_topdir, package_name, NULL}, DIRSEPS); + char *descriptor = join((char *[]) {tmpdir, SPM_META_DESCRIPTOR, NULL}, DIRSEPS); + char *filelist = join((char *[]) {tmpdir, SPM_META_FILELIST, NULL}, DIRSEPS); + + if (exists(records_pkgdir) != 0) { + if (mkdirs(records_pkgdir, 0755) != 0) { + return -1; + } + } + + if (exists(descriptor) != 0) { + fprintf(stderr, "Missing: %s\n", descriptor); + return 1; + } + + if (exists(filelist) != 0) { + fprintf(stderr, "Missing: %s\n", filelist); + return 2; + } + + if (rsync(NULL, descriptor, records_pkgdir) != 0) { + fprintf(stderr, "Failed to copy '%s' to '%s'\n", descriptor, records_pkgdir); + return 3; + } + + if (rsync(NULL, filelist, records_pkgdir) != 0) { + fprintf(stderr, "Failed to copy '%s' to '%s'\n", filelist, records_pkgdir); + return 4; + } + + free(records_topdir); + free(records_pkgdir); + free(descriptor); + free(filelist); + runtime_free(rt); + return 0; +} + +int spm_check_installed(SPM_Hierarchy *fs, char *package_name) { + char *records_topdir = join((char *[]) {fs->localstatedir, "db", "records", NULL}, DIRSEPS); + char *records_pkgdir = join((char *[]) {records_topdir, package_name, NULL}, DIRSEPS); + + char *descriptor = join((char *[]) {records_pkgdir, SPM_META_DESCRIPTOR, NULL}, DIRSEPS); + char *filelist = join((char *[]) {records_pkgdir, SPM_META_FILELIST, NULL}, DIRSEPS); + char **data = NULL; + + if ((exists(records_pkgdir) || exists(descriptor) || exists(descriptor)) != 0) { + free(records_topdir); + free(records_pkgdir); + free(descriptor); + free(filelist); + return 0; // does not exist + } + + data = spm_metadata_read(filelist, SPM_METADATA_VERIFY); + if (data == NULL) { + free(records_topdir); + free(records_pkgdir); + free(descriptor); + free(filelist); + return -1; + } + + for (size_t i = 0; data[i] != NULL; i++) { + free(data[i]); + } + free(data); + + free(records_topdir); + free(records_pkgdir); + free(descriptor); + free(filelist); + return 1; // exists +} + +/** + * + * @return + */ +char *spm_install_fetch(const char *pkgdir, const char *_package) { + char *package = strdup(_package); + if (package == NULL) { + perror("could not allocate memory for package name"); + fprintf(SYSERROR); + return NULL; + } + + long response = 0; + char *url = strdup(package); + char *payload = join_ex(DIRSEPS, pkgdir, basename(package), NULL); + size_t tmp_package_len = strlen(payload); + + if (tmp_package_len > strlen(package)) { + char *tmp = realloc(package, (tmp_package_len + 1) * sizeof(char)); + if (tmp == NULL) { + perror("cannot realloc package path"); + return NULL; + } + package = tmp; + } + strcpy(package, payload); + + if (exists(payload) != 0) { + if ((response = fetch(url, package)) >= 400) { + fprintf(stderr, "HTTP(%ld): %s\n", response, http_response_str(response)); + return NULL; + } + } + free(url); + free(payload); + + return package; +} + +/** + * Perform a full package installation + * @param mf + * @param rootdir + * @param packages + * @return 0=success, -1=failed to create storage, -2=denied by user + */ +int spm_do_install(SPM_Hierarchy *fs, ManifestList *mf, StrList *packages) { + size_t num_requirements = 0; + ManifestPackage **requirements = NULL; + char source[PATH_MAX]; + char *tmpdir = spm_mkdtemp("spm_destroot", NULL); + + if (tmpdir == NULL) { + perror("Could not create temporary destination root"); + fprintf(SYSERROR); + return -1; + } + + if (SPM_GLOBAL.verbose) { + printf("Installation root: %s\n", fs->rootdir); + } + + // Produce a dependency tree from requested package(s) + for (size_t i = 0; i < strlist_count(packages); i++) { + char *item = strlist_item(packages, i); + requirements = resolve_dependencies(mf, item); + if (requirements != NULL) { + for (size_t c = num_requirements; requirements[c] != NULL; c++) { + num_requirements++; + } + } + } + + // Install packages + printf("Requested package(s):\n"); + for (size_t i = 0; requirements !=NULL && requirements[i] != NULL; i++) { + spm_install_show_package(requirements[i]); + } + + if (SPM_GLOBAL.prompt_user) { + if (spm_prompt_user("Proceed with installation?", 1) == 0) { + exit(-2); + } + } + + int fetched = 0; + char *package_dir = strdup(SPM_GLOBAL.package_dir); + for (size_t i = 0; requirements != NULL && requirements[i] != NULL; i++) { + char *package_origin = calloc(PATH_MAX, sizeof(char)); + strncpy(package_origin, requirements[i]->origin, PATH_MAX); + + if (strstr(package_origin, SPM_GLOBAL.repo_target) == NULL) { + if (!endswith(package_origin, DIRSEPS)) { + strcat(package_origin, DIRSEPS); + } + strcat(package_origin, SPM_GLOBAL.repo_target); + } + + char *package_path = join((char *[]) {package_origin, requirements[i]->archive, NULL}, DIRSEPS); + char *package_localpath = join_ex(DIRSEPS, package_dir, requirements[i]->archive, NULL); + free(package_origin); + + // Download the archive if necessary + if (strstr(package_path, "://") != NULL && exists(package_localpath) != 0) { + printf("Fetching: %s\n", package_path); + package_path = spm_install_fetch(package_dir, package_path); + if (package_path == NULL) { + free(package_path); + free(package_localpath); + exit(1); + } + fetched = 1; + } + // Or copy the archive if necessary + else { + // TODO: Possibly an issue down the road, but not at the moment + // You have another local manifest in use. Copy any used packages from there into the local package directory. + if (exists(package_localpath) != 0 && strncmp(package_dir, package_path, strlen(package_dir)) != 0) { + printf("Copying: %s\n", package_path); + if (rsync(NULL, package_path, package_dir) != 0) { + fprintf(stderr, "Unable to copy: %s to %s\n", package_path, package_dir); + return -1; + } + fetched = 1; + } else if (exists(package_localpath) != 0) { + // All attempts to retrieve the requested package have failed. Die. + fprintf(stderr, "Package manifest in '%s' claims '%s' exists, however it does not.\n", requirements[i]->origin, package_path); + return -1; + } + } + free(package_path); + free(package_localpath); + } + + // Update the package manifest + if (fetched) { + printf("Updating package manifest...\n"); + Manifest *tmp_manifest = manifest_from(SPM_GLOBAL.package_dir); + manifest_write(tmp_manifest, package_dir); + manifest_free(tmp_manifest); + } + + printf("Installing package(s):\n"); + size_t num_installed = 0; + for (size_t i = 0; requirements != NULL && requirements[i] != NULL; i++) { + char *package_path = join((char *[]) {package_dir, requirements[i]->archive, NULL}, DIRSEPS); + + if (spm_check_installed(fs, requirements[i]->name)) { + printf(" -> %s is already installed\n", requirements[i]->name); + free(package_path); + continue; + } + + spm_install_show_package(requirements[i]); + spm_install(fs, tmpdir, package_path); + + // Relocate installation root + relocate_root(fs->rootdir, tmpdir); + + spm_install_package_record(fs, tmpdir, requirements[i]->name); + num_installed++; + free(package_path); + } + + // free requirements array + for (size_t i = 0; requirements != NULL && requirements[i] != NULL; i++) { + manifest_package_free(requirements[i]); + } + free(package_dir); + + if (num_installed != 0) { + // Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot + sprintf(source, "%s%c", tmpdir, DIRSEP); + + // Remove metadata files before copying + if (SPM_GLOBAL.verbose) { + printf("Removing metadata\n"); + } + spm_metadata_remove(source); + + // Copy temporary directory to destination + if (SPM_GLOBAL.verbose) { + printf("Installing tree: '%s' => '%s'\n", source, fs->rootdir); + } + + if (rsync(NULL, source, fs->rootdir) != 0) { + exit(1); + } + } + + if (SPM_GLOBAL.verbose) { + printf("Removing temporary storage: '%s'\n", tmpdir); + } + rmdirs(tmpdir); + return 0; +} -- cgit