aboutsummaryrefslogtreecommitdiff
path: root/lib/install.c
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2020-03-18 22:25:27 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2020-03-18 22:25:27 -0400
commitccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8 (patch)
treeae167772a9a2343aa77bf8944b56abe853f6a2ec /lib/install.c
parent3731bb4679ee8716d4f579d5744c36a2d1b4a257 (diff)
downloadspmc-ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8.tar.gz
Refactor project: build/install libspm[_static.a].so to make unit testing possible
Diffstat (limited to 'lib/install.c')
-rw-r--r--lib/install.c328
1 files changed, 328 insertions, 0 deletions
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 <url.h>
+#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;
+}