]> git.seodisparate.com - SimpleArchiver/commitdiff
WIP --prefix: Parser and Helper code setup
authorStephen Seo <seo.disparate@gmail.com>
Wed, 22 Jan 2025 08:53:35 +0000 (17:53 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Wed, 22 Jan 2025 08:53:35 +0000 (17:53 +0900)
Added "--prefix <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.

src/helpers.c
src/helpers.h
src/parser.c
src/parser.h
src/test.c

index a1dcae745bf7e285b22c9f9f084191c9417f68fb..ac465beebb8c077a1cd8963f1a9cc5fede3d5761 100644 (file)
@@ -19,6 +19,7 @@
 #include "helpers.h"
 
 #include <ctype.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -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;
+  }
+}
index 05c47fc76e1d54f6251bee7601e6a282cbdac9ac..7b0a4b29cc5802dc9e95f09aaf458e340d3bfd77 100644 (file)
@@ -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);
index f3e32211ca68107607cbe47c305592634ebe1840..8897eefcf053cbe0c4a52bfc6661f0f9f70ad20e 100644 (file)
@@ -158,6 +158,9 @@ void simple_archiver_print_usage(void) {
   fprintf(stderr,
           "-C <dir> : Change current working directory before "
           "archiving/extracting\n");
+  fprintf(stderr,
+          "--prefix <prefix> : set prefix for archived/extracted paths (do not"
+          "forget \"/\" if the prefix is a directory)\n");
   fprintf(stderr,
           "--compressor <full_compress_cmd> : 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(
index 0e0f7e581a05b1f434f950425b597930e1bdfb1a..241d9da90c491890278966066eb2511a3cc78dd8 100644 (file)
@@ -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 {
index 8df2a697e72bb82a4511e881e78233c7671da058..243bb6bd60683add037f6b336142fcd2e7eca6b1 100644 (file)
@@ -22,6 +22,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <inttypes.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
 
 // 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__((