aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2020-02-26 16:09:32 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2020-02-26 16:09:32 -0500
commitd3580f8c0b5c17150382c4b4456e0b1bfa81a50f (patch)
tree5d0fa6911974b2a0541738a7cffc408b09918b8b /src
parent639aa9dbf60050bf1bbaeea67df13729239c3a31 (diff)
downloadspmc-d3580f8c0b5c17150382c4b4456e0b1bfa81a50f.tar.gz
Multiple things:
* Add strdup_array() * Begin consolidating spm root information into SPM_Hierarchy * Begin consolidating metadata * Begin trimming repeated code (mostly file reading) * Store information about installed packages under [root]/var/db/records
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt29
-rw-r--r--src/config_global.c67
-rw-r--r--src/fs.c4
-rw-r--r--src/install.c68
-rw-r--r--src/manifest.c2
-rw-r--r--src/metadata.c128
-rw-r--r--src/mirrors.c41
-rw-r--r--src/spm.c31
-rw-r--r--src/str.c24
9 files changed, 325 insertions, 69 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b2a2c26..06065a8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,7 +3,34 @@ include_directories(
${CMAKE_BINARY_DIR}/include
)
-add_executable(spm spm.c config.c compat.c resolve.c fs.c rpath.c find.c shell.c archive.c str.c relocation.c install.c config_global.c manifest.c checksum.c extern/url.c version_spec.c spm_build.c mime.c internal_cmd.c environment.c mirrors.c strlist.c shlib.c user_input.c)
+add_executable(spm
+ spm.c
+ config.c
+ compat.c
+ resolve.c
+ fs.c
+ rpath.c
+ find.c
+ shell.c
+ archive.c
+ str.c
+ relocation.c
+ install.c
+ config_global.c
+ manifest.c
+ checksum.c
+ extern/url.c
+ version_spec.c
+ spm_build.c
+ mime.c
+ internal_cmd.c
+ environment.c
+ mirrors.c
+ strlist.c
+ shlib.c
+ user_input.c
+ metadata.c
+)
target_link_libraries(spm crypto ssl curl)
if (LINUX)
diff --git a/src/config_global.c b/src/config_global.c
index a43ae5b..fb97a61 100644
--- a/src/config_global.c
+++ b/src/config_global.c
@@ -155,6 +155,41 @@ void check_runtime_environment(void) {
}
/**
+ *
+ * @param basepath
+ * @return
+ */
+SPM_Hierarchy *spm_hierarchy_init(char *basepath) {
+ SPM_Hierarchy *fs = calloc(1, sizeof(SPM_Hierarchy));
+ fs->rootdir = strdup(basepath);
+ fs->bindir = join((char *[]) {fs->rootdir, "bin", NULL}, DIRSEPS);
+ fs->includedir = join((char *[]) {fs->rootdir, "include", NULL}, DIRSEPS);
+ fs->libdir = join((char *[]) {fs->rootdir, "lib", NULL}, DIRSEPS);
+ fs->datadir = join((char *[]) {fs->rootdir, "share", NULL}, DIRSEPS);
+ fs->localstatedir = join((char *[]) {fs->rootdir, "var", NULL}, DIRSEPS);
+ fs->sysconfdir = join((char *[]) {fs->rootdir, "etc", NULL}, DIRSEPS);
+ fs->mandir = join((char *[]) {fs->datadir, "man", NULL}, DIRSEPS);
+
+ return fs;
+}
+
+/**
+ *
+ * @param fs
+ */
+void spm_hierarchy_free(SPM_Hierarchy *fs) {
+ free(fs->rootdir);
+ free(fs->bindir);
+ free(fs->includedir);
+ free(fs->libdir);
+ free(fs->datadir);
+ free(fs->localstatedir);
+ free(fs->sysconfdir);
+ free(fs->mandir);
+ free(fs);
+}
+
+/**
* Populate global configuration structure
*/
void init_config_global(void) {
@@ -175,23 +210,23 @@ void init_config_global(void) {
}
// Initialize filesystem paths structure
- SPM_GLOBAL.fs.binpath = calloc(strlen(SPM_PROGRAM_BIN) + 1, sizeof(char));
- SPM_GLOBAL.fs.includepath = calloc(strlen(SPM_PROGRAM_INCLUDE) + 1, sizeof(char));
- SPM_GLOBAL.fs.libpath = calloc(strlen(SPM_PROGRAM_LIB) + 1, sizeof(char));
- SPM_GLOBAL.fs.datapath = calloc(strlen(SPM_PROGRAM_DATA) + 1, sizeof(char));
+ SPM_GLOBAL.fs.bindir = calloc(strlen(SPM_PROGRAM_BIN) + 1, sizeof(char));
+ SPM_GLOBAL.fs.includedir = calloc(strlen(SPM_PROGRAM_INCLUDE) + 1, sizeof(char));
+ SPM_GLOBAL.fs.libdir = calloc(strlen(SPM_PROGRAM_LIB) + 1, sizeof(char));
+ SPM_GLOBAL.fs.datadir = calloc(strlen(SPM_PROGRAM_DATA) + 1, sizeof(char));
- if (!SPM_GLOBAL.fs.binpath || !SPM_GLOBAL.fs.includepath
- || !SPM_GLOBAL.fs.libpath) {
+ if (!SPM_GLOBAL.fs.bindir || !SPM_GLOBAL.fs.includedir
+ || !SPM_GLOBAL.fs.libdir) {
perror("Unable to allocate memory for global filesystem paths");
fprintf(SYSERROR);
exit(errno);
}
- strcpy(SPM_GLOBAL.fs.binpath, SPM_PROGRAM_BIN);
- strcpy(SPM_GLOBAL.fs.includepath, SPM_PROGRAM_INCLUDE);
- strcpy(SPM_GLOBAL.fs.libpath, SPM_PROGRAM_LIB);
- strcpy(SPM_GLOBAL.fs.datapath, SPM_PROGRAM_DATA);
- SPM_GLOBAL.fs.manpath = join((char *[]) {SPM_PROGRAM_DATA, "man", NULL}, DIRSEPS);
+ strcpy(SPM_GLOBAL.fs.bindir, SPM_PROGRAM_BIN);
+ strcpy(SPM_GLOBAL.fs.includedir, SPM_PROGRAM_INCLUDE);
+ strcpy(SPM_GLOBAL.fs.libdir, SPM_PROGRAM_LIB);
+ strcpy(SPM_GLOBAL.fs.datadir, SPM_PROGRAM_DATA);
+ SPM_GLOBAL.fs.mandir = join((char *[]) {SPM_PROGRAM_DATA, "man", NULL}, DIRSEPS);
SPM_GLOBAL.user_config_basedir = get_user_conf_dir();
SPM_GLOBAL.user_config_file = get_user_config_file();
@@ -300,11 +335,11 @@ void free_global_config(void) {
mirror_list_free(SPM_GLOBAL.mirror_list);
}
- free(SPM_GLOBAL.fs.binpath);
- free(SPM_GLOBAL.fs.includepath);
- free(SPM_GLOBAL.fs.libpath);
- free(SPM_GLOBAL.fs.datapath);
- free(SPM_GLOBAL.fs.manpath);
+ free(SPM_GLOBAL.fs.bindir);
+ free(SPM_GLOBAL.fs.includedir);
+ free(SPM_GLOBAL.fs.libdir);
+ free(SPM_GLOBAL.fs.datadir);
+ free(SPM_GLOBAL.fs.mandir);
if (SPM_GLOBAL.config) {
config_free(SPM_GLOBAL.config);
}
diff --git a/src/fs.c b/src/fs.c
index ffb9135..6084c1e 100644
--- a/src/fs.c
+++ b/src/fs.c
@@ -244,10 +244,10 @@ char *expandpath(const char *_path) {
* - On UNIX, Win32 paths will be converted UNIX
* - On Win32, UNIX paths will be converted to Win32
*
- * This function is platform dependent.
+ * This function is platform dependent. The string is modified in-place.
*
* @param path a system path
- * @return string (caller is responsible for `free`ing memory)
+ * @return string
*/
char *normpath(const char *path) {
char *result = strdup(path);
diff --git a/src/install.c b/src/install.c
index 3603af5..2655626 100644
--- a/src/install.c
+++ b/src/install.c
@@ -48,7 +48,7 @@ void install_show_package(ManifestPackage *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 install(const char *destroot, const char *tmpdir, const char *_package) {
+int install(SPM_Hierarchy *fs, const char *tmpdir, const char *_package) {
char *package = strdup(_package);
if (!package) {
@@ -56,11 +56,11 @@ int install(const char *destroot, const char *tmpdir, const char *_package) {
return -1;
}
- if (exists(destroot) != 0) {
+ if (exists(fs->rootdir) != 0) {
if (SPM_GLOBAL.verbose) {
- printf("Creating destination root: %s\n", destroot);
+ printf("Creating destination root: %s\n", fs->rootdir);
}
- if (mkdirs(destroot, 0755) != 0) {
+ if (mkdirs(fs->rootdir, 0755) != 0) {
fprintf(SYSERROR);
free(package);
return -2;
@@ -81,12 +81,12 @@ int install(const char *destroot, const char *tmpdir, const char *_package) {
return 0;
}
-int install_package_record(char *from_root, char *package_name) {
+int install_package_record(SPM_Hierarchy *fs, char *tmpdir, char *package_name) {
RuntimeEnv *rt = runtime_copy(__environ);
- char *records_topdir = normpath(runtime_expand_var(rt, "$SPM_LOCALSTATE/db/records"));
+ 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 *[]) {from_root, SPM_META_DESCRIPTOR, NULL}, DIRSEPS);
- char *filelist = join((char *[]) {from_root, SPM_META_FILELIST, 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) {
@@ -122,21 +122,41 @@ int install_package_record(char *from_root, char *package_name) {
return 0;
}
-int is_installed(const char *rootdir, char *package_name) {
- RuntimeEnv *rt = runtime_copy(__environ);
- char *records_topdir = normpath(runtime_expand_var(rt, "$SPM_LOCALSTATE/db/records"));
+int is_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);
- int result = 1; // 1 == exists
+ 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) != 0) {
- // does not exist
- result = 0;
+ free(records_topdir);
+ free(records_pkgdir);
+ free(descriptor);
+ free(filelist);
+ return 0; // does not exist
+ }
+
+ data = 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++) {
+ printf("%zu: %s\n", i, data[i]);
+ free(data[i]);
+ }
+ free(data);
+
free(records_topdir);
free(records_pkgdir);
- // exists
- return result;
+ free(descriptor);
+ free(filelist);
+ return 1; // exists
}
/**
@@ -146,7 +166,7 @@ int is_installed(const char *rootdir, char *package_name) {
* @param packages
* @return 0=success, -1=failed to create storage, -2=denied by user
*/
-int do_install(ManifestList *mf, const char *rootdir, StrList *packages) {
+int do_install(SPM_Hierarchy *fs, ManifestList *mf, StrList *packages) {
size_t num_requirements = 0;
ManifestPackage **requirements = NULL;
char source[PATH_MAX];
@@ -159,7 +179,7 @@ int do_install(ManifestList *mf, const char *rootdir, StrList *packages) {
}
if (SPM_GLOBAL.verbose) {
- printf("Installation root: %s\n", rootdir);
+ printf("Installation root: %s\n", fs->rootdir);
}
// Produce a dependency tree from requested package(s)
@@ -207,15 +227,15 @@ int do_install(ManifestList *mf, const char *rootdir, StrList *packages) {
for (size_t i = 0; requirements != NULL && requirements[i] != NULL; i++) {
char *package_path = join((char *[]) {requirements[i]->origin, SPM_GLOBAL.repo_target, requirements[i]->archive, NULL}, DIRSEPS);
- if (is_installed(rootdir, requirements[i]->name)) {
+ if (is_installed(fs, requirements[i]->name)) {
printf(" -> %s is already installed\n", requirements[i]->name);
free(package_path);
continue;
}
install_show_package(requirements[i]);
- install(rootdir, tmpdir, package_path);
- install_package_record(tmpdir, requirements[i]->name);
+ install(fs, tmpdir, package_path);
+ install_package_record(fs, tmpdir, requirements[i]->name);
num_installed++;
free(package_path);
}
@@ -227,7 +247,7 @@ int do_install(ManifestList *mf, const char *rootdir, StrList *packages) {
if (num_installed != 0) {
// Relocate installation root
- relocate_root(rootdir, tmpdir);
+ relocate_root(fs->rootdir, tmpdir);
// Append a trailing slash to tmpdir to direct rsync to copy files, not the directory, into destroot
sprintf(source, "%s%c", tmpdir, DIRSEP);
@@ -240,10 +260,10 @@ int do_install(ManifestList *mf, const char *rootdir, StrList *packages) {
// Copy temporary directory to destination
if (SPM_GLOBAL.verbose) {
- printf("Installing tree: '%s' => '%s'\n", source, rootdir);
+ printf("Installing tree: '%s' => '%s'\n", source, fs->rootdir);
}
- if (rsync(NULL, source, rootdir) != 0) {
+ if (rsync(NULL, source, fs->rootdir) != 0) {
exit(1);
}
}
diff --git a/src/manifest.c b/src/manifest.c
index ba0fe6d..e971bb7 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -133,7 +133,7 @@ Manifest *manifest_from(const char *package_dir) {
exit(1);
}
char *depfile = join((char *[]) {tmpdir, SPM_META_DEPENDS, NULL}, DIRSEPS);
- info->packages[i]->requirements = file_readlines(depfile);
+ info->packages[i]->requirements = file_readlines(depfile, 0, 0, NULL);
// Record count of requirement specs
if (info->packages[i]->requirements != NULL) {
diff --git a/src/metadata.c b/src/metadata.c
new file mode 100644
index 0000000..d2bb078
--- /dev/null
+++ b/src/metadata.c
@@ -0,0 +1,128 @@
+#include "spm.h"
+
+static int verify_filelist(size_t lineno, char **a) {
+ if (lineno == 0) {
+ if (strncmp((*a), SPM_PACKAGE_HEADER_FILELIST, strlen(SPM_PACKAGE_HEADER_FILELIST)) != 0) {
+ fprintf(stderr, "invalid or missing header: line %zu: %s (expected: '%s')\n",
+ lineno, (*a), SPM_PACKAGE_HEADER_FILELIST);
+ return 1;
+ }
+ }
+ return -1;
+}
+
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+static int verify_depends(size_t lineno, char **a) {
+ return -1;
+}
+
+static int verify_descriptor(size_t lineno, char **a) {
+ if (lineno == 0) {
+ if (strncmp((*a), SPM_PACKAGE_HEADER_DESCRIPTOR, strlen(SPM_PACKAGE_HEADER_DESCRIPTOR)) != 0) {
+ fprintf(stderr, "invalid or missing header: line %zu: %s (expected: '%s')\n",
+ lineno, (*a), SPM_PACKAGE_HEADER_DESCRIPTOR);
+ return 1;
+ }
+ }
+ return -1;
+}
+
+static int verify_prefix(size_t lineno, char **a) {
+ size_t parity = lineno % 2;
+ if (parity == 0 && *(*a) == '#') {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
+
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+static int verify_no_op(size_t lineno, char **a) {
+ return -1;
+}
+
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+static int reader_metadata(size_t lineno, char **line) {
+ (*line) = strip((*line));
+ if (isempty((*line))) {
+ return 1; // indicate "continue"
+ }
+ return 0; // indicate "ok"
+}
+
+/**
+ * Detect the type of metadata based on file name and execute the appropriate function against each line
+ * in the file. Verification can be disabled by passing a non-zero value as the second argument.
+ * @param _filename
+ * @param no_verify SPM_METADATA_VERIFY or SPM_METADATA_NO_VERIFY
+ * @return array of strings (line endings removed)
+ */
+char **metadata_read(const char *_filename, int no_verify) {
+ char *filename = strdup(_filename);
+ char **data = NULL;
+ char **result = NULL;
+ size_t start = 0;
+ ReaderFn *func_verify;
+
+ // Guard
+ if (file_is_metadata(filename) == 0) {
+ free(filename);
+ return NULL;
+ }
+
+ // Setup function pointer and data starting offsets (if any)
+ if (strcmp(basename(filename), SPM_META_FILELIST) == 0) {
+ func_verify = verify_filelist;
+ start = 1;
+ } else if (strcmp(basename(filename), SPM_META_DESCRIPTOR) == 0) {
+ func_verify = verify_descriptor;
+ start = 1;
+ } else if (strcmp(basename(filename), SPM_META_DEPENDS) == 0) {
+ func_verify = verify_depends;
+ } else if (strcmp(basename(filename), SPM_META_PREFIX_BIN) == 0) {
+ func_verify = verify_prefix;
+ } else if (strcmp(basename(filename), SPM_META_PREFIX_TEXT) == 0) {
+ func_verify = verify_prefix;
+ } else {
+ func_verify = verify_no_op;
+ }
+
+ // Get file contents
+ data = file_readlines(filename, 0, 0, reader_metadata);
+
+ // Strip newlines and whitespace
+ for (size_t i = 0; data[i] != NULL; i++) {
+ data[i] = strip(data[i]);
+ }
+
+ // Perform verification
+ if (no_verify == 0) {
+ for (size_t i = 0; data[i] != NULL; i++) {
+ int status = func_verify(i, &data[i]);
+ if (status > 0) {
+ fprintf(stderr, "%s: file verification failed\n", filename);
+ return NULL;
+ } else if (status < 0) {
+ // NOT AN ERROR
+ // a negative value indicates the verification function has processed enough information
+ // so we can gracefully
+ break;
+ }
+ }
+ }
+
+ // If there was a header, duplicate the array from the start of the data
+ // Otherwise return the array
+ if (start > 0) {
+ result = strdup_array(&data[start]);
+ for (size_t i = 0; data[i] != NULL; i++) {
+ free(data[i]);
+ }
+ free(data);
+ } else {
+ result = data;
+ }
+
+ return result;
+}
diff --git a/src/mirrors.c b/src/mirrors.c
index 2275be4..6a67623 100644
--- a/src/mirrors.c
+++ b/src/mirrors.c
@@ -1,7 +1,7 @@
#include "spm.h"
#include "url.h"
-char **file_readlines(const char *filename) {
+char **file_readlines(const char *filename, size_t start, size_t limit, ReaderFn *readerFn) {
FILE *fp = NULL;
char **result = NULL;
char *buffer = NULL;
@@ -34,13 +34,46 @@ char **file_readlines(const char *filename) {
rewind(fp);
+ // Handle invalid start offset
+ if (start > lines) {
+ start = 0;
+ }
+
+ // Adjust line count when start offset is non-zero
+ if (start != 0 && start < lines) {
+ lines -= start;
+ }
+
+
+ // Handle minimum and maximum limits
+ if (limit == 0 || limit > lines) {
+ limit = lines;
+ }
+
// Populate results array
result = calloc(lines + 1, sizeof(char *));
- for (size_t i = 0; i < lines; i++) {
+ for (size_t i = start; i < limit; i++) {
+ if (i < start) {
+ continue;
+ }
+
if (fgets(buffer, BUFSIZ, fp) == NULL) {
break;
}
- result[i] = strdup(buffer);
+
+ if (readerFn != NULL) {
+ int status = readerFn(i - start, &buffer);
+ // A status greater than zero indicates we should ignore this line entirely and "continue"
+ // A status less than zero indicates we should "break"
+ // A zero status proceeds normally
+ if (status > 0) {
+ i--;
+ continue;
+ } else if (status < 0) {
+ break;
+ }
+ }
+ result[i - start] = strdup(buffer);
}
free(buffer);
@@ -49,7 +82,7 @@ char **file_readlines(const char *filename) {
}
char **mirror_list(const char *filename) {
- char **mirrors = file_readlines(filename);
+ char **mirrors = file_readlines(filename, 0, 0, NULL);
char **result = NULL;
size_t count;
for (count = 0; mirrors[count] != NULL; count++);
diff --git a/src/spm.c b/src/spm.c
index 7f58ad1..8444990 100644
--- a/src/spm.c
+++ b/src/spm.c
@@ -175,36 +175,24 @@ int main(int argc, char *argv[], char *arge[]) {
sprintf(rootdir, "%s%c%s", getenv("HOME"), DIRSEP, "spm_root");
}
+ SPM_Hierarchy *rootfs = spm_hierarchy_init(rootdir);
+
// Construct installation runtime environment
RuntimeEnv *rt = runtime_copy(arge);
// TODO: Move environment allocation out of (above) this loop if possible
// TODO: replace variables below with SPM_Hierarchy, and write some control functions
- char *spm_bindir = join((char *[]) {rootdir, "bin", NULL}, DIRSEPS);
- char *spm_includedir = join((char *[]) {rootdir, "include", NULL}, DIRSEPS);
- char *spm_libdir = join((char *[]) {rootdir, "lib", NULL}, DIRSEPS);
- char *spm_datadir = join((char *[]) {rootdir, "share", NULL}, DIRSEPS);
- char *spm_mandir = join((char *[]) {spm_datadir, "man", NULL}, DIRSEPS);
- char *spm_localstatedir = join((char *[]) {rootdir, "var", NULL}, DIRSEPS);
-
- runtime_set(rt, "SPM_BIN", spm_bindir);
- runtime_set(rt, "SPM_INCLUDE", spm_includedir);
- runtime_set(rt, "SPM_LIB", spm_libdir);
- runtime_set(rt, "SPM_DATA", spm_datadir);
- runtime_set(rt, "SPM_MAN", spm_mandir);
- runtime_set(rt, "SPM_LOCALSTATE", spm_localstatedir);
+ runtime_set(rt, "SPM_BIN", rootfs->bindir);
+ runtime_set(rt, "SPM_INCLUDE", rootfs->includedir);
+ runtime_set(rt, "SPM_LIB", rootfs->libdir);
+ runtime_set(rt, "SPM_DATA", rootfs->datadir);
+ runtime_set(rt, "SPM_MAN", rootfs->mandir);
+ runtime_set(rt, "SPM_LOCALSTATE", rootfs->localstatedir);
runtime_apply(rt);
- free(spm_bindir);
- free(spm_includedir);
- free(spm_libdir);
- free(spm_datadir);
- free(spm_mandir);
- free(spm_localstatedir);
-
if (RUNTIME_INSTALL) {
int status_install = 0;
- if ((status_install = do_install(mf, rootdir, packages)) == -1) {
+ if ((status_install = do_install(rootfs, mf, packages)) == -1) {
// failed to create temporary destination root
exit(1);
}
@@ -287,5 +275,6 @@ int main(int argc, char *argv[], char *arge[]) {
free_global_config();
strlist_free(packages);
manifestlist_free(mf);
+ spm_hierarchy_free(rootfs);
return 0;
}
diff --git a/src/str.c b/src/str.c
index 605bbee..79a7326 100644
--- a/src/str.c
+++ b/src/str.c
@@ -597,4 +597,28 @@ char *normalize_space(char *s) {
return result;
}
+/**
+ * Duplicate an array of strings
+ * @param array
+ * @return
+ */
+char **strdup_array(char **array) {
+ char **result = NULL;
+ size_t elems = 0;
+
+ // Guard
+ if (array == NULL) {
+ return NULL;
+ }
+ // Count elements in `array`
+ for (elems = 0; array[elems] != NULL; elems++);
+
+ // Create new array
+ result = calloc(elems + 1, sizeof(char *));
+ for (size_t i = 0; i < elems; i++) {
+ result[i] = strdup(array[i]);
+ }
+
+ return result;
+}