]> git.seodisparate.com - SimpleArchiver/commitdiff
Fixes for other file formats, v4 file prep.
authorStephen Seo <seo.disparate@gmail.com>
Tue, 25 Feb 2025 02:50:12 +0000 (11:50 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Tue, 25 Feb 2025 08:41:19 +0000 (17:41 +0900)
Linked-List count is now uint64_t instead of size_t.

file_format.md
src/archiver.c
src/archiver.h
src/data_structures/hash_map.c
src/data_structures/linked_list.c
src/data_structures/linked_list.h
src/parser.c

index 7ed5eff5fdcabb148d5eb74b56b0e027b453904b..1a2275183f22d7c94675cdf83347918b4514b66b 100644 (file)
@@ -440,3 +440,191 @@ directory and not specify "intermediate" directories. For example, if there is a
 directory structure such as "/a/b/c/", then it is possible for there to be only
 an entry "/a/b/c/" and the directories "/a/b/" and "/a/" may need to be created
 even though they are not specified.
+
+## Format Version 4
+
+This file format is nearly identical to file format version 3, but with larger
+unsigned integer sizes for symlink count, chunk count, file count, and directory
+count.
+
+File extension is "*.simplearchive" but this isn't really checked.
+
+First 18 bytes of file will be (in ascii):
+
+    SIMPLE_ARCHIVE_VER
+
+Next 2 bytes is a 16-bit unsigned integer "version" in big-endian. It will be:
+
+    0x00 0x04
+
+Next 4 bytes are bit-flags.
+
+1. The first byte
+    1. The first bit is set if de/compressor is set for this archive.
+
+The remaining unused flags in the previous bit-flags bytes are reserved for
+future revisions and are currently ignored.
+
+If the previous "de/compressor is set" flag is enabled, then the next section is
+added:
+
+1. 2 bytes is 16-bit unsigned integer "compressor cmd+args" in big-endian. This
+   does not include the NULL at the end of the string.
+2. X bytes of "compressor cmd+args" (length defined by previous value). Is a
+   NULL-terminated string.
+3. 2 bytes is 16-bit unsigned integer "decompressor cmd+args" in big-endian.
+   This does not include the NULL at the end of the string.
+4. X bytes of "decompressor cmd+args" (length defined by previous value). Is a
+   NULL-terminated string.
+
+The next 8 bytes is a 64-bit unsigned integer "link count" in big-endian which
+will indicate the number of symbolic links in this archive.
+
+Following the link-count bytes, the following bytes are added for each symlink:
+
+1. 2 bytes bit-flags:
+    1. The first byte.
+        1. The first bit is UNSET if relative links are preferred, and is SET if
+           absolute links are preferred.
+        2. The second bit is "user read permission".
+        3. The third bit is "user write permission".
+        4. The fourth bit is "user execute permission".
+        5. The fifth bit is "group read permission".
+        6. The sixth bit is "group write permission".
+        7. The seventh bit is "group execute permission".
+        8. The eighth bit is "other read permission".
+    2. The second byte.
+        1. The first bit is "other write permission".
+        2. The second bit is "other execute permission".
+        3. If this bit is set, then this entry is marked invalid. The link name
+           will be preserved in this entry, but the following link target paths
+           will be set to zero-length and will not be stored.
+        4. If this bit is set, then this symlink points to something outside of
+           this archive.
+2. 2 bytes 16-bit unsigned integer "link name" in big-endian. This does not
+   include the NULL at the end of the string. Must not be zero.
+3. X bytes of link-name (length defined by previous value). Is a NULL-terminated
+   string.
+4. 2 bytes is 16-bit unsigned integer "link target absolute path" in
+   big-endian. This does not include the NULL at the end of the string.
+5. X bytes of link-target-absolute-path (length defined by previous value).
+   Is a NULL-terminated string. If the previous "size" value is 0, then
+   this entry does not exist and should be skipped.
+6. 2 bytes is 16-bit unsigned integer "link target relative path" in
+   big-endian. This does not include the NULL at the end of the string.
+7. X bytes of link-target-relative-path (length defined by previous value).
+   Is a NULL-terminated string. If the previous "size" value is 0, then
+   this entry does not exist and should be skipped.
+8. 4 bytes is a 32-bit unsigned integer in big-endian storing UID for this
+   symlink.
+9. 4 bytes is a 32-bit unsigned integer in big-endian storing GID for this
+   symlink.
+10. 2 bytes 16-bit unsigned integer "user name" length in big-endian. This does
+    not include the NULL at the end of the string.
+11. X bytes of user-name (length defined by previous value). Is a
+    NULL-terminated string. If the previous "size" value is 0, then this entry
+    does not exist and should be skipped.
+10. 2 bytes 16-bit unsigned integer "group name" length in big-endian. This does
+    not include the NULL at the end of the string.
+11. X bytes of group-name (length defined by previous value). Is a
+    NULL-terminated string. If the previous "size" value is 0, then this entry
+    does not exist and should be skipped.
+
+After the symlink related data, the next 8 bytes is a 64-bit unsigned integer
+"chunk count" in big-endian which will indicate the number of chunks in this
+archive.
+
+Following the chunk-count bytes, the following bytes are added for each chunk:
+
+1. 8 bytes that are a 64-bit unsigned integer "file count" in big-endian.
+
+The following bytes are added for each file within the current chunk:
+
+1. 2 bytes that are a 16-bit unsigned integer "filename length" in big-endian.
+   This does not include the NULL at the end of the string.
+2. X bytes of filename (length defined by previous value). Is a NULL-terminated
+   string.
+3. 4 bytes bit-flags.
+    1. The first byte.
+        1. The first bit is "user read permission".
+        2. The second bit is "user write permission".
+        3. The third bit is "user execute permission".
+        4. The fourth bit is "group read permission".
+        5. The fifth bit is "group write permission".
+        6. The sixth bit is "group execute permission".
+        7. The seventh bit is "other read permission".
+        8. The eighth bit is "other write permission".
+    2. The second byte.
+        1. The first bit is "other execute permission".
+    3. The third byte.
+        1. Currently unused.
+    4. The fourth byte.
+        1. Currently unused.
+4. Two 4-byte unsigned integers in big-endian for UID and GID.
+    1. A 32-bit unsigned integer in big endian that specifies the UID of the
+       file. Note that during extraction, if the user is not root, then this
+       value will be ignored.
+    2. A 32-bit unsigned integer in big endian that specifies the GID of the
+       file. Note that during extraction, if the user is not root, then this
+       value will be ignored.
+5. 2 bytes 16-bit unsigned integer "user name" length in big-endian. This does
+   not include the NULL at the end of the string.
+6. X bytes of user-name (length defined by previous value). Is a
+   NULL-terminated string. If the previous "size" value is 0, then this entry
+   does not exist and should be skipped.
+7. 2 bytes 16-bit unsigned integer "group name" length in big-endian. This does
+   not include the NULL at the end of the string.
+8. X bytes of group-name (length defined by previous value). Is a
+   NULL-terminated string. If the previous "size" value is 0, then this entry
+   does not exist and should be skipped.
+9. A 64-bit unsigned integer in big endian for the "size of file".
+
+After the files' metadata are the current chunk's data:
+
+1. A 64-bit unsigned integer in big endian for the "size of chunk".
+2. X bytes of data for the current chunk of the previously specified size. If
+   not using de/compressor, this section is the previously mentioned files
+   concatenated with each other. If using de/compressor, this section is the
+   previously mentioned files concatenated and compressed into a single blob of
+   data.
+
+After all the chunks:
+8 bytes (a 64-bit unsigned integer) of "directory count" in big-endian.
+
+After this, for each directory of count "directory count":
+
+1. 2 bytes (16-bit unsigned integer) "directory name length" in big-endian. This
+   does not include the NULL at the end of the string.
+2. X bytes of "directory name" with length of "directory name length". Note that
+   this string is stored as a NULL-terminated string.
+3. 2 bytes permissions flags:
+    1. The first byte's bits:
+        1. User read permission
+        2. User write permission
+        3. User execute permission
+        4. Group read permission
+        5. Group write permission
+        6. Group execute permission
+        7. Other read permission
+        8. Other write permission
+    2. The second byte's bits (unused bits are assumed to be set to 0):
+        1. Other execute permission
+4. Two 4-byte unsigned integers in big-endian for UID and GID.
+    1. A 32-bit unsigned integer in big-endian that is the UID of the directory.
+    2. A 32-bit unsigned integer in big-endian that is the GID of the directory.
+5. 2 bytes 16-bit unsigned integer "user name" length in big-endian. This does
+   not include the NULL at the end of the string.
+6. X bytes of user-name (length defined by previous value). Is a
+   NULL-terminated string. If the previous "size" value is 0, then this entry
+   does not exist and should be skipped.
+7. 2 bytes 16-bit unsigned integer "group name" length in big-endian. This does
+   not include the NULL at the end of the string.
+8. X bytes of group-name (length defined by previous value). Is a
+   NULL-terminated string. If the previous "size" value is 0, then this entry
+   does not exist and should be skipped.
+
+Note that it is possible for directory entries to consist of only the "tail end"
+directory and not specify "intermediate" directories. For example, if there is a
+directory structure such as "/a/b/c/", then it is possible for there to be only
+an entry "/a/b/c/" and the directories "/a/b/" and "/a/" may need to be created
+even though they are not specified.
index 73fc38816783e4cd22891849024dabe291fdf88a..19be2a889d4568b5a64d514e25d96a86a59137f1 100644 (file)
@@ -1858,7 +1858,7 @@ void simple_archiver_internal_paths_to_files_map(SDArchiverHashMap *files_map,
   }
 }
 
-int internal_write_dir_entries_v2_v3(void *data, void *ud) {
+int internal_write_dir_entries_v2_v3_v4(void *data, void *ud) {
   const char *dir = data;
   void **ptrs = ud;
   FILE *out_f = ptrs[0];
@@ -2020,7 +2020,7 @@ int internal_write_dir_entries_v2_v3(void *data, void *ud) {
     return 1;
   }
 
-  if (state->parsed->write_version == 3) {
+  if (state->parsed->write_version == 3 || state->parsed->write_version == 4) {
     u32 = stat_buf.st_uid;
     if (state->parsed->flags & 0x400) {
       u32 = state->parsed->uid;
@@ -4401,7 +4401,7 @@ int simple_archiver_write_v2(FILE *out_f, SDArchiverState *state,
   void_ptrs[1] = state;
 
   if (simple_archiver_list_get(dirs_list,
-                               internal_write_dir_entries_v2_v3,
+                               internal_write_dir_entries_v2_v3_v4,
                                void_ptrs)) {
     free(void_ptrs);
     return SDAS_INTERNAL_ERROR;
@@ -5643,7 +5643,7 @@ int simple_archiver_write_v3(FILE *out_f, SDArchiverState *state,
   void_ptrs[1] = state;
 
   if (simple_archiver_list_get(dirs_list,
-                               internal_write_dir_entries_v2_v3,
+                               internal_write_dir_entries_v2_v3_v4,
                                void_ptrs)) {
     free(void_ptrs);
     return SDAS_INTERNAL_ERROR;
@@ -7520,7 +7520,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
     uint64_t chunk_idx = 0;
 
     SDArchiverLLNode *node = file_info_list->head;
-    uint16_t file_idx = 0;
+    uint32_t file_idx = 0;
 
 #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
     SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC ||          \
@@ -7630,7 +7630,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
         const SDArchiverInternalFileInfo *file_info = node->data;
         if (file_info->other_flags & 2) {
           fprintf(stderr,
-                  "  FILE %3" PRIu16 " of %3" PRIu32 ": %s\n",
+                  "  FILE %3" PRIu32 " of %3" PRIu32 ": %s\n",
                   ++file_idx,
                   file_count,
                   file_info->filename);
@@ -7803,7 +7803,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
         const SDArchiverInternalFileInfo *file_info = node->data;
         if (file_info->other_flags & 2) {
           fprintf(stderr,
-                  "  FILE %3" PRIu16 " of %3" PRIu32 ": %s\n",
+                  "  FILE %3" PRIu32 " of %3" PRIu32 ": %s\n",
                   ++file_idx,
                   file_count,
                   file_info->filename);
@@ -9217,7 +9217,7 @@ int simple_archiver_parse_archive_version_3(FILE *in_f,
     uint64_t chunk_idx = 0;
 
     SDArchiverLLNode *node = file_info_list->head;
-    uint16_t file_idx = 0;
+    uint32_t file_idx = 0;
 
 #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
     SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC ||          \
@@ -9325,14 +9325,14 @@ int simple_archiver_parse_archive_version_3(FILE *in_f,
         }
         node = node->next;
         const SDArchiverInternalFileInfo *file_info = node->data;
+        ++file_idx;
         if (file_info->other_flags & 2) {
           fprintf(stderr,
-                  "  FILE %3" PRIu16 " of %3" PRIu32 ": %s\n",
+                  "  FILE %3" PRIu32 " of %3" PRIu32 ": %s\n",
                   file_idx,
                   file_count,
                   file_info->filename);
         }
-        ++file_idx;
 
         const size_t filename_length = strlen(file_info->filename);
 
@@ -9508,14 +9508,14 @@ int simple_archiver_parse_archive_version_3(FILE *in_f,
         }
         node = node->next;
         const SDArchiverInternalFileInfo *file_info = node->data;
+        ++file_idx;
         if (file_info->other_flags & 2) {
           fprintf(stderr,
-                  "  FILE %3" PRIu16 " of %3" PRIu32 ": %s\n",
+                  "  FILE %3" PRIu32 " of %3" PRIu32 ": %s\n",
                   file_idx,
                   file_count,
                   file_info->filename);
         }
-        ++file_idx;
         chunk_idx += file_info->file_size;
         if (chunk_idx > chunk_size) {
           fprintf(stderr, "ERROR Files in chunk is larger than chunk!\n");
index c53e23b1878c08dfdf003813b20685dc7d11bb00..b2283c38c490ee78beda778f3c573fcbc4fd9864 100644 (file)
@@ -80,6 +80,9 @@ int simple_archiver_write_v2(FILE *out_f, SDArchiverState *state,
 int simple_archiver_write_v3(FILE *out_f, SDArchiverState *state,
                              const SDArchiverLinkedList *filenames);
 
+int simple_archiver_write_v4(FILE *out_f, SDArchiverState *state,
+                             const SDArchiverLinkedList *filenames);
+
 /// Returns zero on success.
 int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract,
                                        const SDArchiverState *state);
@@ -100,6 +103,10 @@ int simple_archiver_parse_archive_version_2(FILE *in_f, int_fast8_t do_extract,
 int simple_archiver_parse_archive_version_3(FILE *in_f, int_fast8_t do_extract,
                                             const SDArchiverState *state);
 
+/// Returns zero on success.
+int simple_archiver_parse_archive_version_4(FILE *in_f, int_fast8_t do_extract,
+                                            const SDArchiverState *state);
+
 /// Returns zero on success.
 int simple_archiver_de_compress(int pipe_fd_in[2], int pipe_fd_out[2],
                                 const char *cmd, void *pid_out);
index 46534578903908b1cca0447a668537bb027e2007..e782d9c2bebeb278d6169161171d16eefa642884 100644 (file)
@@ -253,7 +253,7 @@ int simple_archiver_hash_map_remove(SDArchiverHashMap *hash_map, void *key,
   key_data.key = key;
   key_data.key_size = key_size;
 
-  int result = simple_archiver_list_remove(
+  uint64_t result = simple_archiver_list_remove(
       hash_map->buckets[hash], simple_archiver_hash_map_internal_pick_in_list,
       &key_data);
   if (result == 1) {
index 08142a123943160df98b3c394948351c08e15245..2e9a39d86c44f0e0009bc2b8760b5852c7b090d8 100644 (file)
@@ -113,17 +113,17 @@ int simple_archiver_list_add_front(SDArchiverLinkedList *list, void *data,
   return 0;
 }
 
-int simple_archiver_list_remove(SDArchiverLinkedList *list,
-                                int (*data_check_fn)(void *, void *),
-                                void *user_data) {
+uint64_t simple_archiver_list_remove(SDArchiverLinkedList *list,
+                                     int (*data_check_fn)(void *, void *),
+                                     void *user_data) {
   if (!list) {
     return 0;
   }
 
-  int32_t removed_count = 0;
+  uint64_t removed_count = 0;
 
   SDArchiverLLNode *node = list->head;
-  int32_t iter_removed = 0;
+  uint64_t iter_removed = 0;
   while (node) {
     if (iter_removed == 0) {
       node = node->next;
index f9cf80651818a5bf08728b41fd3874ec1580d059..05d3b4e6cfe67aebfa7655bf8a1aa857a03a5ac3 100644 (file)
@@ -20,6 +20,7 @@
 #define SEODISPARATE_COM_SIMPLE_ARCHIVER_DATA_STRUCTURE_LINKED_LIST_H_
 
 #include <stddef.h>
+#include <stdint.h>
 
 typedef struct SDArchiverLLNode {
   struct SDArchiverLLNode *next;
@@ -31,7 +32,7 @@ typedef struct SDArchiverLLNode {
 typedef struct SDArchiverLinkedList {
   SDArchiverLLNode *head;
   SDArchiverLLNode *tail;
-  size_t count;
+  uint64_t count;
 } SDArchiverLinkedList;
 
 SDArchiverLinkedList *simple_archiver_list_init(void);
@@ -55,9 +56,9 @@ int simple_archiver_list_add_front(SDArchiverLinkedList *list, void *data,
 /// Returns number of removed items.
 /// data_check_fn must return non-zero if the data passed to it is to be
 /// removed.
-int simple_archiver_list_remove(SDArchiverLinkedList *list,
-                                int (*data_check_fn)(void *, void *),
-                                void *user_data);
+uint64_t simple_archiver_list_remove(SDArchiverLinkedList *list,
+                                     int (*data_check_fn)(void *, void *),
+                                     void *user_data);
 
 /// Returns 1 on removed, 0 if not removed.
 /// data_check_fn must return non-zero if the data passed to it is to be
index efecdce1f39e622889302bb0f33c7dcc386928a8..f4fcae437505850bbc4dc8c3c0798c10db853861 100644 (file)
@@ -189,7 +189,7 @@ void simple_archiver_print_usage(void) {
           "when compressing (defaults to same directory as output file)\n");
   fprintf(stderr,
           "--write-version <version> : Force write version file format "
-          "(default 3)\n");
+          "(default 4)\n");
   fprintf(stderr,
           "--chunk-min-size <bytes> : minimum chunk size (default 4194304 or "
           "4MiB) when using chunks (file formats v. 1 and up)\n");
@@ -304,7 +304,7 @@ SDArchiverParsed simple_archiver_create_parsed(void) {
   parsed.working_files = NULL;
   parsed.temp_dir = NULL;
   parsed.user_cwd = NULL;
-  parsed.write_version = 3;
+  parsed.write_version = 4;
   parsed.minimum_chunk_size = 4194304;
   parsed.uid = 0;
   parsed.gid = 0;
@@ -491,8 +491,8 @@ int simple_archiver_parse_args(int argc, const char **argv,
           fprintf(stderr, "ERROR: --write-version cannot be negative!\n");
           simple_archiver_print_usage();
           return 1;
-        } else if (version > 3) {
-          fprintf(stderr, "ERROR: --write-version must be 0, 1, 2, or 3!\n");
+        } else if (version > 4) {
+          fprintf(stderr, "ERROR: --write-version must be 0, 1, 2, 3, or 4!\n");
           simple_archiver_print_usage();
           return 1;
         }