]> git.seodisparate.com - SimpleArchiver/commitdiff
Add option to preserve symlinks exactly
authorStephen Seo <seo.disparate@gmail.com>
Thu, 24 Oct 2024 07:37:55 +0000 (16:37 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Thu, 24 Oct 2024 07:37:55 +0000 (16:37 +0900)
The "--preserve-symlinks" option preserves the symlink target instead of
deriving absolute/relative-paths from it. If archived symlinks are
absolute-paths, then it is NOT recommended to use this option as the
symlinks can be clobbered on extraction (unless if "--no-safe-links" is
specified on extraction).

README.md
src/archiver.c
src/parser.c
src/parser.h

index c5c7b36000398fcd35b601252953291366c317a4..7336efd15b2a0a5f1083c68186deb31738ee8ad1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ API calls.
     --overwrite-create : allows overwriting an archive file
     --overwrite-extract : allows overwriting when extracting
     --no-abs-symlink : do not store absolute paths for symlinks
+    --preserve-symlinks : preserve the symlink's path on archive creation instead of deriving abs/relative paths, ignores "--no-abs-symlink" (It is not recommended to use this option, as absolute-path-symlinks may be clobbered on extraction)
     --no-safe-links : keep symlinks that link to outside archive contents
     --temp-files-dir <dir> : where to store temporary files created when compressing (defaults to current working directory)
     --write-version <version> : Force write version file format (default 1)
index 6ffbaac6c8580960d4a509ad9bef57bbf682660b..c085182c5c2bec0af57a049d688a39fc77801ea2 100644 (file)
@@ -759,32 +759,49 @@ int write_files_fn(void *data, void *ud) {
     // Get absolute path.
     __attribute__((cleanup(
         simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
-#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
-    SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC ||          \
-    SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
-    abs_path = realpath(file_info->filename, NULL);
-#endif
     __attribute__((cleanup(
         simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
-    if (abs_path) {
-      // Get relative path.
-      // First get absolute path of link.
-      __attribute__((cleanup(
-          simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
-          simple_archiver_file_abs_path(file_info->filename);
-      if (!link_abs_path) {
-        fprintf(stderr, "WARNING: Failed to get absolute path of link!\n");
+
+    if ((state->parsed->flags & 0x100) != 0) {
+      // Preserve symlink target.
+      char *path_buf = malloc(1024);
+      ssize_t ret = readlink(file_info->filename, path_buf, 1023);
+      if (ret == -1) {
+        fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
+        free(path_buf);
+        ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
       } else {
-        // fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n",
-        //                 (char*)abs_path, (char*)link_abs_path);
+        path_buf[ret] = 0;
+        if (path_buf[0] == '/') {
+          abs_path = path_buf;
+          ((uint8_t *)temp_to_write->buf)[1] |= 0x4;
+        } else {
+          rel_path = path_buf;
+        }
+      }
+    } else {
+      abs_path = realpath(file_info->filename, NULL);
+      if (abs_path) {
+        // Get relative path.
+        // First get absolute path of link.
+        __attribute__((cleanup(
+            simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
+            simple_archiver_file_abs_path(file_info->filename);
+        if (!link_abs_path) {
+          fprintf(stderr, "WARNING: Failed to get absolute path of link!\n");
+        } else {
+          // fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n",
+          //                 (char*)abs_path, (char*)link_abs_path);
 
-        rel_path =
-            simple_archiver_filenames_to_relative_path(link_abs_path, abs_path);
+          rel_path = simple_archiver_filenames_to_relative_path(link_abs_path,
+                                                                abs_path);
+        }
       }
     }
 
     // Check if absolute path refers to one of the filenames.
     if (abs_path && (state->parsed->flags & 0x20) == 0 &&
+        (state->parsed->flags & 0x100) == 0 &&
         !simple_archiver_hash_map_get(state->map, abs_path,
                                       strlen(abs_path) + 1)) {
       // Is not a filename being archived.
@@ -804,6 +821,34 @@ int write_files_fn(void *data, void *ud) {
                 file_info->filename);
         ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
       }
+    } else if ((state->parsed->flags & 0x100) != 0 &&
+               (state->parsed->flags & 0x80) == 0 &&
+               (((uint8_t *)temp_to_write->buf)[1] & 0x8) == 0) {
+      __attribute__((cleanup(
+          simple_archiver_helper_cleanup_c_string))) char *resolved_path = NULL;
+      if (abs_path || rel_path) {
+        resolved_path = realpath(file_info->filename, NULL);
+        if (!resolved_path) {
+          fprintf(stderr,
+                  "WARNING: Symlink \"%s\" is invalid, will not be stored!  "
+                  "(Use \"--no-safe-links\" to disable this behavior)\n",
+                  file_info->filename);
+          ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
+        } else if (!simple_archiver_hash_map_get(state->map, resolved_path,
+                                                 strlen(resolved_path) + 1)) {
+          fprintf(stderr,
+                  "WARNING: Symlink \"%s\" points to outside archive contents, "
+                  "will not be stored! (Use \"--no-safe-links\" to disable "
+                  "this behavior)\n",
+                  file_info->filename);
+          ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
+        }
+      } else {
+        fprintf(stderr,
+                "WARNING: Unable to get target path from symlink \"%s\"!\n",
+                file_info->filename);
+        ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
+      }
     }
 
     if (!abs_path && !rel_path) {
@@ -831,8 +876,10 @@ int write_files_fn(void *data, void *ud) {
 
     // Store the absolute and relative paths.
     if (!abs_path) {
-      fprintf(stderr,
-              "WARNING: Failed to get absolute path of link destination!\n");
+      if ((state->parsed->flags & 0x100) == 0) {
+        fprintf(stderr,
+                "WARNING: Failed to get absolute path of link destination!\n");
+      }
       temp_to_write = malloc(sizeof(SDArchiverInternalToWrite));
       temp_to_write->buf = malloc(2);
       temp_to_write->size = 2;
@@ -905,9 +952,17 @@ int write_files_fn(void *data, void *ud) {
     // Write all previously set data.
     fprintf(stderr, "Writing symlink info: %s\n", file_info->filename);
     if ((state->parsed->flags & 0x20) == 0) {
-      fprintf(stderr, "  abs path: %s\n", (char *)abs_path);
+      if (abs_path) {
+        fprintf(stderr, "  abs path: %s\n", (char *)abs_path);
+      } else {
+        fprintf(stderr, "  abs path is NOT set\n");
+      }
+    }
+    if (rel_path) {
+      fprintf(stderr, "  rel path: %s\n", (char *)rel_path);
+    } else {
+      fprintf(stderr, "  rel path is NOT set\n");
     }
-    fprintf(stderr, "  rel path: %s\n", (char *)rel_path);
     simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f);
     simple_archiver_list_free(&to_write);
   }
@@ -983,10 +1038,15 @@ int filenames_to_abs_map_fn(void *data, void *ud) {
       char *fullpath_dirname_copy = malloc(strlen(fullpath_dirname) + 1);
       strncpy(fullpath_dirname_copy, fullpath_dirname,
               strlen(fullpath_dirname) + 1);
-      simple_archiver_hash_map_insert(
-          abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy,
-          strlen(fullpath_dirname_copy) + 1,
-          simple_archiver_helper_datastructure_cleanup_nop, NULL);
+      if (!simple_archiver_hash_map_get(abs_filenames, fullpath_dirname_copy,
+                                        strlen(fullpath_dirname_copy) + 1)) {
+        simple_archiver_hash_map_insert(
+            abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy,
+            strlen(fullpath_dirname_copy) + 1,
+            simple_archiver_helper_datastructure_cleanup_nop, NULL);
+      } else {
+        free(fullpath_dirname_copy);
+      }
     }
     prev = fullpath_dirname;
   }
@@ -2091,30 +2151,51 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
       node = node->next;
       ++u32;
       memset(buf, 0, 2);
+
+      uint_fast8_t is_invalid = 0;
+
 #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
     SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC ||          \
     SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
-      // Check if symlink points to thing to be stored into archive.
-      __attribute__((
-          cleanup(simple_archiver_helper_cleanup_malloced))) void *abs_path =
-          realpath(node->data, NULL);
+      __attribute__((cleanup(
+          simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
       __attribute__((cleanup(
           simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
-      if (abs_path) {
-        __attribute__((cleanup(
-            simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
-            simple_archiver_file_abs_path(node->data);
-        if (!link_abs_path) {
-          fprintf(stderr, "WARNING: Failed to get absolute path to link!\n");
+      if ((state->parsed->flags & 0x100) != 0) {
+        // Preserve symlink target.
+        char *path_buf = malloc(1024);
+        ssize_t ret = readlink(node->data, path_buf, 1023);
+        if (ret == -1) {
+          fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
+          free(path_buf);
+          is_invalid = 1;
         } else {
-          rel_path = simple_archiver_filenames_to_relative_path(link_abs_path,
-                                                                abs_path);
+          path_buf[ret] = 0;
+          if (path_buf[0] == '/') {
+            abs_path = path_buf;
+            buf[0] |= 1;
+          } else {
+            rel_path = path_buf;
+          }
+        }
+      } else {
+        abs_path = realpath(node->data, NULL);
+        // Check if symlink points to thing to be stored into archive.
+        if (abs_path) {
+          __attribute__((cleanup(
+              simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
+              simple_archiver_file_abs_path(node->data);
+          if (!link_abs_path) {
+            fprintf(stderr, "WARNING: Failed to get absolute path to link!\n");
+          } else {
+            rel_path = simple_archiver_filenames_to_relative_path(link_abs_path,
+                                                                  abs_path);
+          }
         }
       }
 
-      uint_fast8_t is_invalid = 0;
-
       if (abs_path && (state->parsed->flags & 0x20) == 0 &&
+          (state->parsed->flags & 0x100) == 0 &&
           !simple_archiver_hash_map_get(abs_filenames, abs_path,
                                         strlen(abs_path) + 1)) {
         // Is not a filename being archived.
@@ -2130,6 +2211,27 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
           // Safe links disabled, set preference to absolute path.
           buf[0] |= 1;
         }
+      } else if ((state->parsed->flags & 0x100) != 0 &&
+                 (state->parsed->flags & 0x80) == 0 && !is_invalid) {
+        __attribute__((cleanup(
+            simple_archiver_helper_cleanup_c_string))) char *target_realpath =
+            realpath(node->data, NULL);
+        if (!target_realpath) {
+          fprintf(
+              stderr,
+              "WARNING: \"%s\" is an invalid symlink and \"--no-safe-links\" "
+              "not specified, will skip this symlink!\n",
+              (const char *)node->data);
+          is_invalid = 1;
+        } else if (!simple_archiver_hash_map_get(abs_filenames, target_realpath,
+                                                 strlen(target_realpath) + 1)) {
+          fprintf(
+              stderr,
+              "WARNING: \"%s\" points to outside of archived files and "
+              "\"--no-safe-links\" not specified, will skip this symlink!\n",
+              (const char *)node->data);
+          is_invalid = 1;
+        }
       }
 
       if (!abs_path && !rel_path) {
index 8a66a5830f3d1a509476b117860711ad52b6552f..2f37e466a8e30a2b1ed48e9517b246b07a8321ea 100644 (file)
@@ -168,6 +168,12 @@ void simple_archiver_print_usage(void) {
   fprintf(stderr, "--overwrite-extract : allows overwriting when extracting\n");
   fprintf(stderr,
           "--no-abs-symlink : do not store absolute paths for symlinks\n");
+  fprintf(
+      stderr,
+      "--preserve-symlinks : preserve the symlink's path on archive creation "
+      "instead of deriving abs/relative paths, ignores \"--no-abs-symlink\" "
+      "(It is not recommended to use this option, as absolute-path-symlinks "
+      "may be clobbered on extraction)\n");
   fprintf(stderr,
           "--no-safe-links : keep symlinks that link to outside archive "
           "contents\n");
@@ -306,6 +312,8 @@ int simple_archiver_parse_args(int argc, const char **argv,
         out->flags |= 0x8;
       } else if (strcmp(argv[0], "--no-abs-symlink") == 0) {
         out->flags |= 0x20;
+      } else if (strcmp(argv[0], "--preserve-symlinks") == 0) {
+        out->flags |= 0x100;
       } else if (strcmp(argv[0], "--no-safe-links") == 0) {
         out->flags |= 0x80;
         fprintf(stderr,
index 71462b2c5e9ef0a8d5f7f0e8872567c9d7a2ac4c..9c8cfd8f6694ef47f94fb6ff42f9c39791f6d78e 100644 (file)
@@ -37,6 +37,7 @@ typedef struct SDArchiverParsed {
   /// 0b xx1x xxxx - Do not save absolute paths for symlinks.
   /// 0b x1xx xxxx - Sort files by size before archiving.
   /// 0b 1xxx xxxx - No safe links.
+  /// 0b xxxx xxx1 xxxx xxxx - Preserve symlink target.
   uint32_t flags;
   /// Null-terminated string.
   char *filename;