From: Stephen Seo Date: Wed, 22 Jan 2025 08:53:35 +0000 (+0900) Subject: WIP --prefix: Parser and Helper code setup X-Git-Tag: 1.12^2~19 X-Git-Url: https://git.seodisparate.com/stephenseo/annotated.html?a=commitdiff_plain;h=a355441503cffbc1c6002ad747d44ba43a253ced;p=SimpleArchiver WIP --prefix: Parser and Helper code setup Added "--prefix " to parser to specify a prefix to prepend to files/dirs/symlinks when archiving or extracting. Added some helper functions to facilitate the creation of this feature and some unit tests to test them. --- diff --git a/src/helpers.c b/src/helpers.c index a1dcae7..ac465be 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -19,6 +19,7 @@ #include "helpers.h" #include +#include #include #include @@ -322,3 +323,219 @@ size_t simple_archiver_helper_num_digits(size_t value) { return digits; } + +const char * simple_archiver_helper_prefix_result_str( + SAHelperPrefixValResult result) { + switch (result) { + case SAHPrefixVal_OK: + return "OK"; + case SAHPrefixVal_NULL: + return "Prefix is NULL"; + case SAHPrefixVal_ZERO_LEN: + return "Prefix has zero length"; + case SAHPrefixVal_ROOT: + return "Prefix starts with slash (root)"; + case SAHPrefixVal_DOUBLE_SLASH: + return "Prefix has multiple consecutive slashes"; + default: + return "Unknown"; + } +} + +SAHelperPrefixValResult simple_archiver_helper_validate_prefix( + const char *prefix) { + if (!prefix) { + return SAHPrefixVal_NULL; + } + const unsigned long length = strlen(prefix); + if (length == 0) { + return SAHPrefixVal_ZERO_LEN; + } else if (prefix[0] == '/') { + return SAHPrefixVal_ROOT; + } + + uint_fast8_t was_slash = 0; + for (unsigned long idx = 0; idx < length; ++idx) { + if (prefix[idx] == '/') { + if (was_slash) { + return SAHPrefixVal_DOUBLE_SLASH; + } + was_slash = 1; + } else { + was_slash = 0; + } + } + + return SAHPrefixVal_OK; +} + +uint16_t simple_archiver_helper_str_slash_count(const char *str) { + uint16_t count = 0; + + const unsigned long length = strlen(str); + for (unsigned long idx = 0; idx < length; ++idx) { + if (str[idx] == '/') { + ++count; + } + } + + return count; +} + +char *simple_archiver_helper_insert_prefix_in_link_path(const char *prefix, + const char *link, + const char *path) { + uint16_t prefix_slash_count = simple_archiver_helper_str_slash_count(prefix); + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *cwd = getcwd(NULL, 0); + unsigned long cwd_length = strlen(cwd); + if (cwd[cwd_length - 1] != '/') { + // Ensure the cwd ends with a '/'. + char *new_cwd = malloc(cwd_length + 2); + memcpy(new_cwd, cwd, cwd_length); + new_cwd[cwd_length] = '/'; + new_cwd[cwd_length + 1] = 0; + free(cwd); + cwd = new_cwd; + ++cwd_length; + } + const unsigned long prefix_length = strlen(prefix); + const unsigned long path_length = strlen(path); + if (path[0] == '/') { + // Dealing with an absolute path. + + // First check if "path" is in archive. + size_t diff_idx = 0; + for (; cwd[diff_idx] == path[diff_idx] + && diff_idx < cwd_length + && diff_idx < path_length; + ++diff_idx); + + if (diff_idx == cwd_length) { + // "path" is in archive. + char *result_path = malloc(path_length + prefix_length + 1); + // Part of path matching cwd. + memcpy(result_path, path, cwd_length); + // Insert prefix. + memcpy(result_path + cwd_length, prefix, prefix_length); + // Rest of path. + memcpy(result_path + cwd_length + prefix_length, + path + cwd_length, + path_length - cwd_length); + result_path[path_length + prefix_length] = 0; + return result_path; + } else { + // "path" is not in archive, no need to insert prefix. + return strdup(path); + } + } else { + // Dealing with a relative path. + + // First check if "path" is in archive. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *filename_realpath = simple_archiver_helper_real_path_to_name(link); + if (!filename_realpath) { + return NULL; + } + const unsigned long filename_realpath_length = strlen(filename_realpath); + + size_t diff_idx = 0; + for (; filename_realpath[diff_idx] == cwd[diff_idx] + && diff_idx < filename_realpath_length + && diff_idx < cwd_length; + ++diff_idx); + int32_t level = simple_archiver_helper_str_slash_count( + filename_realpath + diff_idx); + const int32_t level_copy = level; + + size_t prev_start_idx = 0; + for (size_t path_idx = 0; path_idx < path_length; ++path_idx) { + if (path[path_idx] == '/') { + if (path_idx - prev_start_idx == 2 + && path[path_idx - 2] == '.' + && path[path_idx - 1] == '.') { + --level; + if (level < 0) { + break; + } + } else { + ++level; + } + prev_start_idx = path_idx + 1; + } + } + + if (level >= 0) { + // Relative path is in cwd, no need to insert prefix. + return strdup(path); + } else { + // Relative path refers to something outside of archive, "insert" prefix. + char *result = malloc(path_length + 1 + 3 * (size_t)prefix_slash_count); + memcpy(result, path, path_length); + level = level_copy; + size_t start_side_idx = 0; + for (size_t idx = 0; idx < path_length; ++idx) { + if (path[idx] == '/') { + if (idx - start_side_idx == 2 + && path[start_side_idx] == '.' + && path[start_side_idx + 1] == '.') { + --level; + if (level == -1) { + char *buf = malloc(path_length - idx - 1); + memcpy(buf, result + idx + 1, path_length - idx - 1); + for (size_t l_idx = 0; + l_idx < (size_t)prefix_slash_count; + ++l_idx) { + memcpy(result + idx + 1 + l_idx * 3, "../", 3); + } + memcpy(result + idx + 1 + (size_t)prefix_slash_count * 3, + buf, + path_length - idx - 1); + free(buf); + result[path_length + 3 * (size_t)prefix_slash_count] = 0; + return result; + } + } + start_side_idx = idx + 1; + } + } + free(result); + return NULL; + } + } +} + +char *simple_archiver_helper_real_path_to_name(const char *filename) { + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *filename_copy = strdup(filename); + char *filename_dir = dirname(filename_copy); + + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *filename_copy2 = strdup(filename); + char *filename_base = basename(filename_copy2); + const unsigned long basename_length = strlen(filename_base); + + // Get realpath to dirname. + __attribute__((cleanup(simple_archiver_helper_cleanup_c_string))) + char *dir_realpath = realpath(filename_dir, NULL); + if (!dir_realpath) { + return NULL; + } + const unsigned long dir_realpath_length = strlen(dir_realpath); + + // Concatenate dirname-realpath and basename. + if (dir_realpath[dir_realpath_length - 1] != '/') { + char *result = malloc(dir_realpath_length + basename_length + 2); + memcpy(result, dir_realpath, dir_realpath_length); + result[dir_realpath_length] = '/'; + memcpy(result + dir_realpath_length + 1, filename_base, basename_length); + result[dir_realpath_length + 1 + basename_length] = 0; + return result; + } else { + char *result = malloc(dir_realpath_length + basename_length + 1); + memcpy(result, dir_realpath, dir_realpath_length); + memcpy(result + dir_realpath_length, filename_base, basename_length); + result[dir_realpath_length + basename_length] = 0; + return result; + } +} diff --git a/src/helpers.h b/src/helpers.h index 05c47fc..7b0a4b2 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -62,6 +62,32 @@ char *simple_archiver_helper_cut_substr(const char *s, size_t start_idx, size_t simple_archiver_helper_num_digits(size_t value); +typedef enum SAHelperPrefixValResult { + SAHPrefixVal_OK = 0, + SAHPrefixVal_NULL, + SAHPrefixVal_ZERO_LEN, + SAHPrefixVal_ROOT, + SAHPrefixVal_DOUBLE_SLASH +} SAHelperPrefixValResult; + +// Returned c-string is a literal. +const char * simple_archiver_helper_prefix_result_str( + SAHelperPrefixValResult result); + +SAHelperPrefixValResult simple_archiver_helper_validate_prefix( + const char *prefix); + +uint16_t simple_archiver_helper_str_slash_count(const char *str); + +// Returned c-string must be free'd. +char *simple_archiver_helper_insert_prefix_in_link_path(const char *prefix, + const char *link, + const char *path); + +// Ensures the path to the filename is resolved, even if "filename" is a +// symbolic link. +char *simple_archiver_helper_real_path_to_name(const char *filename); + void simple_archiver_helper_cleanup_FILE(FILE **fd); void simple_archiver_helper_cleanup_malloced(void **data); void simple_archiver_helper_cleanup_c_string(char **str); diff --git a/src/parser.c b/src/parser.c index f3e3221..8897eef 100644 --- a/src/parser.c +++ b/src/parser.c @@ -158,6 +158,9 @@ void simple_archiver_print_usage(void) { fprintf(stderr, "-C : Change current working directory before " "archiving/extracting\n"); + fprintf(stderr, + "--prefix : set prefix for archived/extracted paths (do not" + "forget \"/\" if the prefix is a directory)\n"); fprintf(stderr, "--compressor : requires --decompressor and cmd " "must use stdin/stdout\n"); @@ -274,6 +277,7 @@ SDArchiverParsed simple_archiver_create_parsed(void) { parsed.mappings.GnameToGid = simple_archiver_hash_map_init(); parsed.mappings.GidToGid = simple_archiver_hash_map_init(); parsed.mappings.GnameToGname = simple_archiver_hash_map_init(); + parsed.prefix = NULL; return parsed; } @@ -347,6 +351,31 @@ int simple_archiver_parse_args(int argc, const char **argv, out->user_cwd = argv[1]; --argc; ++argv; + } else if (strcmp(argv[0], "--prefix") == 0) { + if (argc < 2) { + fprintf(stderr, "ERROR: --prefix specified but missing prefix!\n"); + simple_archiver_print_usage(); + return 1; + } + SAHelperPrefixValResult prefix_val_result = + simple_archiver_helper_validate_prefix(argv[1]); + if (prefix_val_result != SAHPrefixVal_OK) { + fprintf(stderr, + "ERROR: Invalid prefix: %s\n", + simple_archiver_helper_prefix_result_str(prefix_val_result)); + return 1; + } + const unsigned long prefix_length = strlen(argv[1]); + if (argv[1][prefix_length - 1] == '/') { + out->prefix = strdup(argv[1]); + } else { + out->prefix = malloc(prefix_length + 2); + memcpy(out->prefix, argv[1], prefix_length); + out->prefix[prefix_length] = '/'; + out->prefix[prefix_length + 1] = 0; + } + --argc; + ++argv; } else if (strcmp(argv[0], "--compressor") == 0) { if (argc < 2) { fprintf(stderr, "--compressor specfied but missing argument!\n"); @@ -723,6 +752,10 @@ void simple_archiver_free_parsed(SDArchiverParsed *parsed) { if (parsed->mappings.GnameToGname) { simple_archiver_hash_map_free(&parsed->mappings.GnameToGname); } + + if (parsed->prefix) { + free(parsed->prefix); + } } SDArchiverLinkedList *simple_archiver_parsed_to_filenames( diff --git a/src/parser.h b/src/parser.h index 0e0f7e5..241d9da 100644 --- a/src/parser.h +++ b/src/parser.h @@ -93,6 +93,8 @@ typedef struct SDArchiverParsed { uint_fast16_t dir_permissions; UsersInfos users_infos; SDA_UGMapping mappings; + /// Prefix for archived/extracted paths. + char *prefix; } SDArchiverParsed; typedef struct SDArchiverFileInfo { diff --git a/src/test.c b/src/test.c index 8df2a69..243bb6b 100644 --- a/src/test.c +++ b/src/test.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include // Local includes. #include "archiver.h" @@ -853,6 +856,58 @@ int main(void) { free(out); } + // Test insert prefix in link path. + { + char *cwd = getcwd(NULL, 0); + int ret = chdir("/tmp"); + CHECK_TRUE(ret == 0); + if (ret != 0) { + goto TEST_HELPERS_PREFIX_END; + } + ret = mkdir("/tmp/fifty", S_IRWXU); + CHECK_TRUE(ret == 0 || errno == EEXIST); + if (ret != 0 && errno != EEXIST) { + goto TEST_HELPERS_PREFIX_END; + } + + char *result = simple_archiver_helper_insert_prefix_in_link_path( + "one/two/three/", "fifty/link", "/tmp/fifty/link_target"); + CHECK_TRUE(result); + if (result) { + CHECK_STREQ(result, "/tmp/one/two/three/fifty/link_target"); + free(result); + } + + result = simple_archiver_helper_insert_prefix_in_link_path( + "one/two/three/", "fifty/link", "/other"); + CHECK_TRUE(result); + if (result) { + CHECK_STREQ(result, "/other"); + free(result); + } + + result = simple_archiver_helper_insert_prefix_in_link_path( + "one/two/three/", "fifty/link", "sixty/seventy/other"); + CHECK_TRUE(result); + if (result) { + CHECK_STREQ(result, "sixty/seventy/other"); + free(result); + } + + result = simple_archiver_helper_insert_prefix_in_link_path( + "one/two/three/", "fifty/link", "../../other"); + CHECK_TRUE(result); + if (result) { + CHECK_STREQ(result, "../../../../../other"); + free(result); + } + +TEST_HELPERS_PREFIX_END: + rmdir("/tmp/fifty"); + chdir(cwd); + free(cwd); + } + // Test archiver. { __attribute__((