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/fs.c | 504 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 lib/fs.c (limited to 'lib/fs.c') diff --git a/lib/fs.c b/lib/fs.c new file mode 100644 index 0000000..d920248 --- /dev/null +++ b/lib/fs.c @@ -0,0 +1,504 @@ +/** + * @file fs.c + */ +#include "spm.h" + +/** + * + * @param _path + * @return + */ +FSTree *fstree(const char *_path, char **filter_by, unsigned int filter_mode) { + FTS *parent = NULL; + FTSENT *node = NULL; + FSTree *fsdata = NULL; + int no_filter = 0; + char *path = NULL; + char *abspath = realpath(_path, NULL); + + if (filter_mode & SPM_FSTREE_FLT_RELATIVE) { + path = strdup(_path); + } else { + path = abspath; + } + + if (path == NULL) { + perror(_path); + fprintf(SYSERROR); + return NULL; + } + char *root[2] = { path, NULL }; + + if (filter_by == NULL) { + // Create an array with an empty string. This signifies we want don't want to filter any paths. + no_filter = 1; + filter_by = calloc(2, sizeof(char *)); + filter_by[0] = calloc(2, sizeof(char)); + strcpy(filter_by[0], ""); + } + + size_t dirs_size = 2; + size_t dirs_records = 0; + size_t files_size = 2; + size_t files_records = 0; + + fsdata = (FSTree *)calloc(1, sizeof(FSTree)); + fsdata->root = (char *)calloc(PATH_MAX, sizeof(char)); + fsdata->dirs = (char **)calloc(dirs_size, sizeof(char *)); + fsdata->files = (char **)calloc(files_size, sizeof(char *)); + + if (filter_mode & SPM_FSTREE_FLT_RELATIVE) { + // Return an absolute path regardless + strncpy(fsdata->root, abspath, PATH_MAX - 1); + } else { + strncpy(fsdata->root, path, PATH_MAX - 1); + } + + parent = fts_open(root, FTS_PHYSICAL | FTS_NOCHDIR, &_fstree_compare); + + if (parent != NULL) { + while ((node = fts_read(parent)) != NULL) { + for (size_t i = 0; filter_by[i] != NULL; i++) { + // Drop paths containing filter string(s) according to the requested mode + if (filter_mode & SPM_FSTREE_FLT_CONTAINS && strstr(node->fts_path, filter_by[i]) == NULL) { + continue; + } + else if (filter_mode & SPM_FSTREE_FLT_ENDSWITH && !endswith(node->fts_path, filter_by[i])) { + continue; + } + else if (filter_mode & SPM_FSTREE_FLT_STARTSWITH && !startswith(node->fts_path, filter_by[i])) { + continue; + } + switch (node->fts_info) { + case FTS_D: + if (strcmp(node->fts_path, "..") == 0 || strcmp(node->fts_path, ".") == 0) { + continue; + } + fsdata->dirs = (char **) realloc(fsdata->dirs, sizeof(char *) * dirs_size); + fsdata->dirs[dirs_size - 1] = NULL; + fsdata->dirs[dirs_records] = (char *) calloc(strlen(node->fts_path) + 1, sizeof(char)); + strncpy(fsdata->dirs[dirs_records], node->fts_path, strlen(node->fts_path)); + dirs_size++; + dirs_records++; + break; + case FTS_F: + case FTS_SL: + fsdata->files = (char **) realloc(fsdata->files, sizeof(char *) * files_size); + fsdata->files[files_size - 1] = NULL; + fsdata->files[files_records] = (char *) calloc(strlen(node->fts_path) + 1, sizeof(char)); + strncpy(fsdata->files[files_records], node->fts_path, strlen(node->fts_path)); + files_size++; + files_records++; + break; + default: + break; + } + } + } + fts_close(parent); + } + fsdata->dirs_length = dirs_records; + fsdata->files_length = files_records; + free(path); + if (no_filter) { + free(filter_by[0]); + free(filter_by); + } + return fsdata; +} + +/** + * + * @param one + * @param two + * @return + */ +int _fstree_compare(const FTSENT **one, const FTSENT **two) { + return (strcmp((*one)->fts_name, (*two)->fts_name)); +} + +/** + * + * @param _path + * @return + */ +int rmdirs(const char *_path) { + if (access(_path, F_OK) != 0) { + return -1; + } + + FSTree *data = fstree(_path, NULL, SPM_FSTREE_FLT_NONE); + if (data->files) { + for (size_t i = 0; data->files[i] != NULL; i++) { + remove(data->files[i]); + } + } + if (data->dirs) { + for (size_t i = data->dirs_length - 1; i != 0; i--) { + remove(data->dirs[i]); + } + } + remove(data->root); + + fstree_free(data); + return 0; +} + +/** + * Free a `FSTree` structure + * @param fsdata + */ +void fstree_free(FSTree *fsdata) { + if (fsdata != NULL) { + if (fsdata->root != NULL) { + free(fsdata->root); + } + if (fsdata->files != NULL) { + for (int i = 0; fsdata->files[i] != NULL; i++) { + free(fsdata->files[i]); + } + free(fsdata->files); + } + if (fsdata->dirs != NULL) { + for (int i = 0; fsdata->dirs[i] != NULL; i++) { + free(fsdata->dirs[i]); + } + free(fsdata->dirs); + } + free(fsdata); + } +} + +/** + * Expand "~" to the user's home directory + * + * Example: + * ~~~{.c} + * char *home = expandpath("~"); // == /home/username + * char *config = expandpath("~/.config"); // == /home/username/.config + * char *nope = expandpath("/tmp/test"); // == /tmp/test + * char *nada = expandpath("/~/broken"); // == /~/broken + * + * free(home); + * free(config); + * free(nope); + * free(nada); + * ~~~ + * + * @param _path (Must start with a `~`) + * @return success=expanded path or original path, failure=NULL + */ +char *expandpath(const char *_path) { + if (_path == NULL) { + return NULL; + } + const char *homes[] = { + "HOME", + "USERPROFILE", + }; + char home[PATH_MAX]; + char tmp[PATH_MAX]; + char *ptmp = tmp; + char result[PATH_MAX]; + char *sep = NULL; + + memset(home, '\0', sizeof(home)); + memset(ptmp, '\0', sizeof(tmp)); + memset(result, '\0', sizeof(result)); + + strncpy(ptmp, _path, PATH_MAX - 1); + + // Check whether there's a reason to continue processing the string + if (*ptmp != '~') { + return strdup(ptmp); + } + + // Remove tilde from the string and shift its contents to the left + strchrdel(ptmp, "~"); + + // Figure out where the user's home directory resides + for (size_t i = 0; i < sizeof(homes); i++) { + char *tmphome; + if ((tmphome = getenv(homes[i])) != NULL) { + strncpy(home, tmphome, PATH_MAX - 1); + break; + } + } + + // A broken runtime environment means we can't do anything else here + if (isempty(home)) { + return NULL; + } + + // Scan the path for a directory separator + if ((sep = strpbrk(ptmp, "/\\")) != NULL) { + // Jump past it + ptmp = sep + 1; + } + + // Construct the new path + strncat(result, home, PATH_MAX - 1); + if (sep) { + strncat(result, DIRSEPS, PATH_MAX - 1); + strncat(result, ptmp, PATH_MAX - 1); + } + + return strdup(result); +} + +/** + * Converts Win32 path to Unix path, and vice versa + * - On UNIX, Win32 paths will be converted UNIX + * - On Win32, UNIX paths will be converted to Win32 + * + * This function is platform dependent. The string is modified in-place. + * + * @param path a system path + * @return string + */ +char *normpath(const char *path) { + char *result = strdup(path); + char *tmp = result; + + while (*tmp) { + if (*tmp == NOT_DIRSEP) { + *tmp = DIRSEP; + tmp++; + continue; + } + tmp++; + } + return result; +} + + +/** + * Strip file name from directory + * Note: Caller is responsible for freeing memory + * + * @param _path + * @return success=path to directory, failure=NULL + */ +char *dirname(const char *_path) { + if (_path == NULL) { + return NULL; + } + char *path = strdup(_path); + char *last = strrchr(path, DIRSEP); + if (!last) { + return NULL; + } + // Step backward, stopping on the first non-separator + // This ensures strings like "/usr//////" are converted to "/usr", but... + // it will do nothing to fix up a path like "/usr//////bin/bash + char *lookback = last; + while (*(lookback - 1) == DIRSEP) { + lookback--; + } + + *lookback = '\0'; + return path; +} + +/** + * Strip directory from file name + * Note: Caller is responsible for freeing memory + * + * @param _path + * @return success=file name, failure=NULL + */ +char *basename(char *path) { + char *result = NULL; + char *last = NULL; + + if ((last = strrchr(path, DIRSEP)) == NULL) { + return result; + } + // Perform a lookahead ensuring the string is valid beyond the last separator + if (last++ != NULL) { + result = last; + } + + return result; +} + +/** + * Basic rsync wrapper for copying files + * @param _args arguments to pass to rsync (set to `NULL` for default options) + * @param _source source file or directory + * @param _destination destination file or directory + * @return success=0, failure=-1 + */ +int rsync(const char *_args, const char *_source, const char *_destination) { + int returncode; + Process *proc = NULL; + char *args = NULL; + if (_args) { + args = strdup(_args); + } + char *source = strdup(_source); + char *destination = strdup(_destination); + char cmd[PATH_MAX]; + char *args_combined = (char *)calloc(PATH_MAX, sizeof(char)); + + memset(cmd, '\0', sizeof(cmd)); + strcpy(args_combined, "--archive --hard-links "); + if (args) { + strcat(args_combined, _args); + } + + strchrdel(args_combined, SHELL_INVALID); + strchrdel(source, SHELL_INVALID); + strchrdel(destination, SHELL_INVALID); + + snprintf(cmd, PATH_MAX, "rsync %s \"%s\" \"%s\" 2>&1", args_combined, source, destination); + shell(&proc, SHELL_OUTPUT, cmd); + if (!proc) { + if (args) { + free(args); + } + free(source); + free(destination); + return -1; + } + + returncode = proc->returncode; + if (returncode != 0 && proc->output) { + fprintf(stderr, "%s\n", proc->output); + } + shell_free(proc); + + if (args) { + free(args); + } + free(args_combined); + free(source); + free(destination); + return returncode; +} + +/** + * Return the size of a file + * @param filename + * @return + */ +long int get_file_size(const char *filename) { + long int result = 0; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return -1; + } + fseek(fp, 0, SEEK_END); + result = ftell(fp); + fclose(fp); + return result; +} + +/** + * Attempt to create a directory (or directories) + * @param _path A path to create + * @param mode UNIX permissions (octal) + * @return success=0, failure=-1 (+ errno will be set) + */ +int mkdirs(const char *_path, mode_t mode) { + int result = 0; + char *path = normpath(_path); + char tmp[PATH_MAX]; + tmp[0] = '\0'; + + char sep[2]; + sprintf(sep, "%c", DIRSEP); + char **parts = split(path, sep); + for (int i = 0; parts[i] != NULL; i++) { + strcat(tmp, parts[i]); + strcat(tmp, sep); + if (access(tmp, F_OK) != 0) { + result = mkdir(tmp, mode); + } + } + split_free(parts); + return result; +} + +/** + * Short wrapper for `access`. Check if file exists. + * + * Example: + * ~~~{.c} + * if (exists("example.txt") != 0) { + * // handle error + * } + * ~~~ + * @param filename + * @return + */ +int exists(const char *filename) { + return access(filename, F_OK); +} + +/** + * Convert size in bytes to the closest human-readable unit + * + * NOTE: Caller is responsible for freeing memory + * + * Example: + * ~~~{.c} + * char *output; + * output = human_readable_size(1); // "1B" + * free(output); + * output = human_readable_size(1024) // "1.0K" + * free(output); + * output = human_readable_size(1024000) // "1.0M" + * free(output); + * // and so on + * ~~~ + * + * @param n size to convert + * @return string + */ +char *human_readable_size(uint64_t n) { + size_t i; + double result = (double)n; + char *unit[] = {"B", "K", "M", "G", "T", "P", "E"}; + char r[255]; + memset(r, '\0', sizeof(r)); + + for (i = 0; i < sizeof(unit); i++) { + if (fabs(result) < 1024) { + break; + } + result /= 1024.0; + } + + if (unit[i][0] == 'B') { + sprintf(r, "%0.0lf%s", result, unit[i]); + } + else { + sprintf(r, "%0.2lf%s", result, unit[i]); + } + + return strdup(r); +} + +/** + * Create a named temporary directory + * @param name + * @return success=path, failure=NULL + */ +char *spm_mkdtemp(const char *name, const char *extended_path) { + const char *template_unique = "XXXXXX"; + char *tmpdir = NULL; + char *template = calloc(PATH_MAX, sizeof(char)); + + sprintf(template, "%s%s%s_%s", TMP_DIR, DIRSEPS, name, template_unique); + tmpdir = mkdtemp(template); + if (extended_path != NULL) { + char extended[PATH_MAX] = {0,}; + strncpy(extended, tmpdir, PATH_MAX - 1); + strcat(extended, DIRSEPS); + strcat(extended, extended_path); + mkdirs(extended, 0755); + } + return tmpdir; +} + -- cgit