diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ecbcee..4b53330 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,49 @@ endif() add_executable(simplearchiver ${SimpleArchiver_SOURCES}) +target_compile_options(simplearchiver PUBLIC + -Wall -Wformat -Wformat=2 -Wconversion -Wimplicit-fallthrough + -Werror=format-security + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 + -D_GLIBCXX_ASSERTIONS + -fstrict-flex-arrays=3 + -fstack-clash-protection -fstack-protector-strong + -Wl,-z,nodlopen -Wl,-z,noexecstack + -Wl,-z,relro -Wl,-z,now + -Wl,--as-needed -Wl,--no-copy-dt-needed-entries + -fPIE -pie +) + +target_link_options(simplearchiver PUBLIC + -Wall -Wformat -Wformat=2 -Wconversion -Wimplicit-fallthrough + -Werror=format-security + -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 + -D_GLIBCXX_ASSERTIONS + -fstrict-flex-arrays=3 + -fstack-clash-protection -fstack-protector-strong + -Wl,-z,nodlopen -Wl,-z,noexecstack + -Wl,-z,relro -Wl,-z,now + -Wl,--as-needed -Wl,--no-copy-dt-needed-entries + -fPIE -pie +) + +# Inhibit format-string-related warning in src/archiver.c . +set_source_files_properties(src/archiver.c + PROPERTIES + COMPILE_FLAGS -Wno-format-nonliteral +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_options(simplearchiver PUBLIC + -fno-delete-null-pointer-checks -fno-strict-overflow + -fno-strict-aliasing -ftrivial-auto-var-init=zero + ) + target_link_options(simplearchiver PUBLIC + -fno-delete-null-pointer-checks -fno-strict-overflow + -fno-strict-aliasing -ftrivial-auto-var-init=zero + ) +endif() + add_executable(test_datastructures src/data_structures/test.c src/data_structures/linked_list.c @@ -40,6 +83,7 @@ add_executable(test_simplearchiver src/test.c src/parser.c src/helpers.c + src/archiver.c src/algorithms/linear_congruential_gen.c src/data_structures/linked_list.c src/data_structures/hash_map.c diff --git a/README.md b/README.md index d1aaaba..ec456c3 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,15 @@ API calls. Use "-f -" to work on stdout when creating archive or stdin when reading archive NOTICE: "-f" is not affected by "-C"! -C : Change current working directory before archiving/extracting - --compressor : requires --decompressor - --decompressor : requires --compressor + --compressor : requires --decompressor and cmd must use stdin/stdout + --decompressor : requires --compressor and cmd must use stdin/stdout Specifying "--decompressor" when extracting overrides archive file's stored decompressor cmd --overwrite-create : allows overwriting an archive file --overwrite-extract : allows overwriting when extracting --no-abs-symlink : do not store absolute paths for symlinks --temp-files-dir : where to store temporary files created when compressing (defaults to current working directory) + --write-version : Force write version file format (default 1) + --chunk-min-size : v1 file format minimum chunk size (default 4194304 or 4MiB) -- : specifies remaining arguments are files to archive/extract If creating archive file, remaining args specify files to archive. If extracting archive file, remaining args specify files to extract. diff --git a/file_format.md b/file_format.md index fb9c210..4e39544 100644 --- a/file_format.md +++ b/file_format.md @@ -76,3 +76,117 @@ Following the file-count bytes, the following bytes are added for each file: 1. 8 bytes 64-bit unsigned integer "size of filename in this archive file" in big-endian. 2. X bytes file data (length defined by previous value). + +## Format Version 1 + +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 0x01 + +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 4 bytes is a 32-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". +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. + +After the symlink related data, the next 4 bytes is a 32-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. 4 bytes that are a 32-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. 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. diff --git a/file_format_1_example_0 b/file_format_1_example_0 new file mode 100644 index 0000000..a39ecf6 Binary files /dev/null and b/file_format_1_example_0 differ diff --git a/file_format_1_example_1 b/file_format_1_example_1 new file mode 100644 index 0000000..696a49f Binary files /dev/null and b/file_format_1_example_1 differ diff --git a/invalid_file_format_0_example_0 b/invalid_file_format_0_example_0 new file mode 100644 index 0000000..9755999 Binary files /dev/null and b/invalid_file_format_0_example_0 differ diff --git a/invalid_file_format_0_example_1 b/invalid_file_format_0_example_1 new file mode 100644 index 0000000..77f4424 Binary files /dev/null and b/invalid_file_format_0_example_1 differ diff --git a/invalid_file_format_1_example_0 b/invalid_file_format_1_example_0 new file mode 100644 index 0000000..29d9159 Binary files /dev/null and b/invalid_file_format_1_example_0 differ diff --git a/invalid_file_format_1_example_1 b/invalid_file_format_1_example_1 new file mode 100644 index 0000000..87a8c65 Binary files /dev/null and b/invalid_file_format_1_example_1 differ diff --git a/invalid_file_format_1_example_2 b/invalid_file_format_1_example_2 new file mode 100644 index 0000000..9b003b0 Binary files /dev/null and b/invalid_file_format_1_example_2 differ diff --git a/src/archiver.c b/src/archiver.c index a993b18..e1ca153 100644 --- a/src/archiver.c +++ b/src/archiver.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #endif @@ -43,10 +44,12 @@ #define FILE_COUNTS_OUTPUT_FORMAT_STR_0 "\nFile %%%lulu of %%%lulu.\n" #define FILE_COUNTS_OUTPUT_FORMAT_STR_1 "[%%%lulu/%%%lulu]\n" +#define SIMPLE_ARCHIVER_BUFFER_SIZE (1024 * 32) + #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX -int is_sig_pipe_occurred = 0; +volatile int is_sig_pipe_occurred = 0; void handle_sig_pipe(int sig) { if (sig == SIGPIPE) { @@ -60,6 +63,16 @@ typedef struct SDArchiverInternalToWrite { uint64_t size; } SDArchiverInternalToWrite; +typedef struct SDArchiverInternalFileInfo { + char *filename; + uint8_t bit_flags[4]; + uint32_t uid; + uint32_t gid; + uint64_t file_size; + /// xxxx xxx1 - is invalid. + int_fast8_t other_flags; +} SDArchiverInternalFileInfo; + void free_internal_to_write(void *data) { SDArchiverInternalToWrite *to_write = data; free(to_write->buf); @@ -91,49 +104,6 @@ void cleanup_temp_filename_delete(void ***ptrs_array) { #endif } -char *filename_to_absolute_path(const char *filename) { -#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ - SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ - SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX - __attribute__((cleanup(simple_archiver_helper_cleanup_malloced))) void *path = - malloc(strlen(filename) + 1); - strncpy(path, filename, strlen(filename) + 1); - - char *path_dir = dirname(path); - if (!path_dir) { - return NULL; - } - - __attribute__(( - cleanup(simple_archiver_helper_cleanup_malloced))) void *dir_realpath = - realpath(path_dir, NULL); - if (!dir_realpath) { - return NULL; - } - - // Recreate "path" since it may have been modified by dirname(). - simple_archiver_helper_cleanup_malloced(&path); - path = malloc(strlen(filename) + 1); - strncpy(path, filename, strlen(filename) + 1); - - char *filename_basename = basename(path); - if (!filename_basename) { - return NULL; - } - - // Get combined full path to file. - char *fullpath = - malloc(strlen(dir_realpath) + 1 + strlen(filename_basename) + 1); - strncpy(fullpath, dir_realpath, strlen(dir_realpath) + 1); - fullpath[strlen(dir_realpath)] = '/'; - strncpy(fullpath + strlen(dir_realpath) + 1, filename_basename, - strlen(filename_basename) + 1); - - return fullpath; -#endif - return NULL; -} - int write_files_fn(void *data, void *ud) { const SDArchiverFileInfo *file_info = data; SDArchiverState *state = ud; @@ -278,8 +248,8 @@ int write_files_fn(void *data, void *ud) { } // Write file to pipe, and read from other pipe. - char write_buf[1024]; - char read_buf[1024]; + char write_buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + char read_buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; int_fast8_t write_again = 0; int_fast8_t write_done = 0; int_fast8_t read_done = 0; @@ -297,7 +267,8 @@ int write_files_fn(void *data, void *ud) { // Read from file. if (!write_done) { if (!write_again) { - write_count = fread(write_buf, 1, 1024, file_fd); + write_count = + fread(write_buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, file_fd); } if (write_count > 0) { ret = write(pipe_into_cmd[1], write_buf, write_count); @@ -339,7 +310,7 @@ int write_files_fn(void *data, void *ud) { // Read from compressor. if (!read_done) { - ret = read(pipe_outof_cmd[0], read_buf, 1024); + ret = read(pipe_outof_cmd[0], read_buf, SIMPLE_ARCHIVER_BUFFER_SIZE); if (ret > 0) { read_count = fwrite(read_buf, 1, (size_t)ret, tmp_fd); if (read_count != (size_t)ret) { @@ -495,9 +466,9 @@ int write_files_fn(void *data, void *ud) { // Write file. fprintf(stderr, "Writing compressed file: %s\n", file_info->filename); do { - write_count = fread(write_buf, 1, 1024, tmp_fd); - if (write_count == 1024) { - fwrite(write_buf, 1, 1024, state->out_f); + write_count = fread(write_buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, tmp_fd); + if (write_count == SIMPLE_ARCHIVER_BUFFER_SIZE) { + fwrite(write_buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, state->out_f); } else if (write_count > 0) { fwrite(write_buf, 1, write_count, state->out_f); } @@ -636,12 +607,12 @@ int write_files_fn(void *data, void *ud) { // Write file. fprintf(stderr, "Writing file: %s\n", file_info->filename); - char buf[1024]; + char buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; size_t ret; do { - ret = fread(buf, 1, 1024, fd); - if (ret == 1024) { - fwrite(buf, 1, 1024, state->out_f); + ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, fd); + if (ret == SIMPLE_ARCHIVER_BUFFER_SIZE) { + fwrite(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, state->out_f); } else if (ret > 0) { fwrite(buf, 1, ret, state->out_f); } @@ -752,53 +723,15 @@ int write_files_fn(void *data, void *ud) { // First get absolute path of link. __attribute__((cleanup( simple_archiver_helper_cleanup_malloced))) void *link_abs_path = - filename_to_absolute_path(file_info->filename); + 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); - // Compare paths to get relative path. - // Get first non-common char. - size_t idx; - size_t last_slash; - for (idx = 0, last_slash = 0; - idx < strlen(abs_path) && idx < strlen(link_abs_path); ++idx) { - if (((const char *)abs_path)[idx] != - ((const char *)link_abs_path)[idx]) { - break; - } else if (((const char *)abs_path)[idx] == '/') { - last_slash = idx + 1; - } - } - // Get substrings of both paths. - char *link_substr = (char *)link_abs_path + last_slash; - char *dest_substr = (char *)abs_path + last_slash; - rel_path = malloc(strlen(dest_substr) + 1); - strncpy(rel_path, dest_substr, strlen(dest_substr) + 1); - // fprintf(stderr, "DEBUG: link_substr: %s\nDEBUG: dest_substr: %s\n", - // link_substr, dest_substr); - - // Generate the relative path. - int_fast8_t has_slash = 0; - idx = 0; - do { - for (; link_substr[idx] != '/' && link_substr[idx] != 0; ++idx); - if (link_substr[idx] == 0) { - has_slash = 0; - } else { - has_slash = 1; - char *new_rel_path = malloc(strlen(rel_path) + 1 + 3); - new_rel_path[0] = '.'; - new_rel_path[1] = '.'; - new_rel_path[2] = '/'; - strncpy(new_rel_path + 3, rel_path, strlen(rel_path) + 1); - free(rel_path); - rel_path = new_rel_path; - ++idx; - } - } while (has_slash); + rel_path = + simple_archiver_filenames_to_relative_path(link_abs_path, abs_path); } } @@ -925,7 +858,7 @@ int filenames_to_abs_map_fn(void *data, void *ud) { } // Get combined full path to file. - char *fullpath = filename_to_absolute_path(file_info->filename); + char *fullpath = simple_archiver_file_abs_path(file_info->filename); if (!fullpath) { return 1; } @@ -977,6 +910,684 @@ int filenames_to_abs_map_fn(void *data, void *ud) { return 0; } +int read_buf_full_from_fd(FILE *fd, char *read_buf, const size_t read_buf_size, + const size_t amount_total, char *dst_buf) { + size_t amount = amount_total; + while (amount != 0) { + if (amount >= read_buf_size) { + if (fread(read_buf, 1, read_buf_size, fd) != read_buf_size) { + return SDAS_INVALID_FILE; + } + if (dst_buf) { + memcpy(dst_buf + (amount_total - amount), read_buf, read_buf_size); + } + amount -= read_buf_size; + } else { + if (fread(read_buf, 1, amount, fd) != amount) { + return SDAS_INVALID_FILE; + } + if (dst_buf) { + memcpy(dst_buf + (amount_total - amount), read_buf, amount); + } + amount = 0; + } + } + + return SDAS_SUCCESS; +} + +int read_fd_to_out_fd(FILE *in_fd, FILE *out_fd, char *read_buf, + const size_t read_buf_size, const size_t amount_total) { + size_t amount = amount_total; + while (amount != 0) { + if (amount >= read_buf_size) { + if (fread(read_buf, 1, read_buf_size, in_fd) != read_buf_size) { + return SDAS_INVALID_FILE; + } else if (fwrite(read_buf, 1, read_buf_size, out_fd) != read_buf_size) { + return SDAS_FAILED_TO_WRITE; + } + amount -= read_buf_size; + } else { + if (fread(read_buf, 1, amount, in_fd) != amount) { + return SDAS_INVALID_FILE; + } else if (fwrite(read_buf, 1, amount, out_fd) != amount) { + return SDAS_FAILED_TO_WRITE; + } + amount = 0; + } + } + return SDAS_SUCCESS; +} + +int try_write_to_decomp(int *to_dec_pipe, uint64_t *chunk_remaining, FILE *in_f, + char *buf, const size_t buf_size, char *hold_buf, + ssize_t *has_hold) { + if (*to_dec_pipe >= 0) { + if (*chunk_remaining > 0) { + if (*chunk_remaining > buf_size) { + if (*has_hold < 0) { + size_t fread_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, in_f); + if (fread_ret == 0) { + goto TRY_WRITE_TO_DECOMP_END; + } else { + ssize_t write_ret = write(*to_dec_pipe, buf, fread_ret); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + *has_hold = (ssize_t)fread_ret; + memcpy(hold_buf, buf, fread_ret); + return SDAS_SUCCESS; + } else { + return SDAS_INTERNAL_ERROR; + } + } else if (write_ret == 0) { + return SDAS_INTERNAL_ERROR; + } else if ((size_t)write_ret < fread_ret) { + *chunk_remaining -= (size_t)write_ret; + *has_hold = (ssize_t)fread_ret - write_ret; + memcpy(hold_buf, buf + write_ret, (size_t)*has_hold); + return SDAS_SUCCESS; + } else { + *chunk_remaining -= (size_t)write_ret; + } + } + } else { + ssize_t write_ret = write(*to_dec_pipe, hold_buf, (size_t)*has_hold); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return SDAS_SUCCESS; + } else { + return SDAS_INTERNAL_ERROR; + } + } else if (write_ret == 0) { + return SDAS_INTERNAL_ERROR; + } else if (write_ret < *has_hold) { + *chunk_remaining -= (size_t)write_ret; + memcpy(buf, hold_buf + write_ret, (size_t)(*has_hold - write_ret)); + memcpy(hold_buf, buf, (size_t)(*has_hold - write_ret)); + *has_hold = *has_hold - write_ret; + return SDAS_SUCCESS; + } else { + *chunk_remaining -= (size_t)*has_hold; + *has_hold = -1; + } + } + } else { + if (*has_hold < 0) { + size_t fread_ret = fread(buf, 1, *chunk_remaining, in_f); + if (fread_ret == 0) { + goto TRY_WRITE_TO_DECOMP_END; + } else { + ssize_t write_ret = write(*to_dec_pipe, buf, fread_ret); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + *has_hold = (int)fread_ret; + memcpy(hold_buf, buf, fread_ret); + return SDAS_SUCCESS; + } else { + return SDAS_INTERNAL_ERROR; + } + } else if (write_ret == 0) { + return SDAS_INTERNAL_ERROR; + } else if ((size_t)write_ret < fread_ret) { + *chunk_remaining -= (size_t)write_ret; + *has_hold = (ssize_t)fread_ret - write_ret; + memcpy(hold_buf, buf + write_ret, (size_t)*has_hold); + return SDAS_SUCCESS; + } else if ((size_t)write_ret <= *chunk_remaining) { + *chunk_remaining -= (size_t)write_ret; + } else { + return SDAS_INTERNAL_ERROR; + } + } + } else { + ssize_t write_ret = write(*to_dec_pipe, hold_buf, (size_t)*has_hold); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return SDAS_SUCCESS; + } else { + return SDAS_INTERNAL_ERROR; + } + } else if (write_ret == 0) { + return SDAS_INTERNAL_ERROR; + } else if (write_ret < *has_hold) { + *chunk_remaining -= (size_t)write_ret; + memcpy(buf, hold_buf + write_ret, (size_t)(*has_hold - write_ret)); + memcpy(hold_buf, buf, (size_t)(*has_hold - write_ret)); + *has_hold = *has_hold - write_ret; + return SDAS_SUCCESS; + } else { + *chunk_remaining -= (size_t)*has_hold; + *has_hold = -1; + } + } + } + } + } + +TRY_WRITE_TO_DECOMP_END: + if (*to_dec_pipe >= 0 && *chunk_remaining == 0 && *has_hold < 0) { + close(*to_dec_pipe); + *to_dec_pipe = -1; + } + + return SDAS_SUCCESS; +} + +/// Returns SDAS_SUCCESS on success. +int read_decomp_to_out_file(const char *out_filename, int in_pipe, + char *read_buf, const size_t read_buf_size, + const uint64_t file_size, int *to_dec_pipe, + uint64_t *chunk_remaining, FILE *in_f, + char *hold_buf, ssize_t *has_hold) { + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *out_fd = + NULL; + if (out_filename) { + out_fd = fopen(out_filename, "wb"); + if (!out_fd) { + fprintf(stderr, "ERROR Failed to open \"%s\" for writing!\n", + out_filename); + return SDAS_INTERNAL_ERROR; + } + } + + uint64_t written_amt = 0; + ssize_t read_ret; + size_t fwrite_ret; + while (written_amt < file_size) { + int ret = try_write_to_decomp(to_dec_pipe, chunk_remaining, in_f, read_buf, + read_buf_size, hold_buf, has_hold); + if (ret != SDAS_SUCCESS) { + return ret; + } + if (file_size - written_amt >= read_buf_size) { + read_ret = read(in_pipe, read_buf, read_buf_size); + if (read_ret > 0) { + if (out_fd) { + fwrite_ret = fwrite(read_buf, 1, (size_t)read_ret, out_fd); + if (fwrite_ret == (size_t)read_ret) { + written_amt += fwrite_ret; + } else if (ferror(out_fd)) { + fprintf(stderr, "ERROR Failed to write decompressed data!\n"); + return SDAS_INTERNAL_ERROR; + } else { + fprintf( + stderr, + "ERROR Failed to write decompressed data (invalid state)!\n"); + return SDAS_INTERNAL_ERROR; + } + } else { + written_amt += (size_t)read_ret; + } + } else if (read_ret == 0) { + // EOF. + if (written_amt < file_size) { + fprintf(stderr, + "ERROR Decompressed EOF while file needs more bytes!\n"); + return SDAS_INTERNAL_ERROR; + } else { + break; + } + } else { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking read from pipe. + continue; + } else { + // Error. + fprintf(stderr, "ERROR Failed to read from decompressor! (%lu)\n", + read_ret); + return SDAS_INTERNAL_ERROR; + } + } + } else { + read_ret = read(in_pipe, read_buf, file_size - written_amt); + if (read_ret > 0) { + if (out_fd) { + fwrite_ret = fwrite(read_buf, 1, (size_t)read_ret, out_fd); + if (fwrite_ret == (size_t)read_ret) { + written_amt += fwrite_ret; + } else if (ferror(out_fd)) { + fprintf(stderr, "ERROR Failed to write decompressed data!\n"); + return SDAS_INTERNAL_ERROR; + } else { + fprintf( + stderr, + "ERROR Failed to write decompressed data (invalid state)!\n"); + return SDAS_INTERNAL_ERROR; + } + } else { + written_amt += (size_t)read_ret; + } + } else if (read_ret == 0) { + // EOF. + if (written_amt < file_size) { + fprintf(stderr, + "ERROR Decompressed EOF while file needs more bytes!\n"); + return SDAS_INTERNAL_ERROR; + } else { + break; + } + } else { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking read from pipe. + continue; + } else { + // Error. + fprintf(stderr, "ERROR Failed to read from decompressor! (%d)\n", + errno); + return SDAS_INTERNAL_ERROR; + } + } + } + } + + return written_amt == file_size ? SDAS_SUCCESS : SDAS_INTERNAL_ERROR; +} + +void free_internal_file_info(void *data) { + SDArchiverInternalFileInfo *file_info = data; + if (file_info) { + if (file_info->filename) { + free(file_info->filename); + } + free(file_info); + } +} + +void cleanup_internal_file_info(SDArchiverInternalFileInfo **file_info) { + if (file_info && *file_info) { + if ((*file_info)->filename) { + free((*file_info)->filename); + } + free(*file_info); + *file_info = NULL; + } +} + +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX +mode_t permissions_from_bits_v1_symlink(const uint8_t flags[2], + uint_fast8_t print) { + mode_t permissions = 0; + + if ((flags[0] & 2) != 0) { + permissions |= S_IRUSR; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 4) != 0) { + permissions |= S_IWUSR; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 8) != 0) { + permissions |= S_IXUSR; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x10) != 0) { + permissions |= S_IRGRP; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x20) != 0) { + permissions |= S_IWGRP; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x40) != 0) { + permissions |= S_IXGRP; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x80) != 0) { + permissions |= S_IROTH; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[1] & 1) != 0) { + permissions |= S_IWOTH; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[1] & 2) != 0) { + permissions |= S_IXOTH; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + + return permissions; +} + +mode_t permissions_from_bits_version_1(const uint8_t flags[4], + uint_fast8_t print) { + mode_t permissions = 0; + + if ((flags[0] & 1) != 0) { + permissions |= S_IRUSR; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 2) != 0) { + permissions |= S_IWUSR; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 4) != 0) { + permissions |= S_IXUSR; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 8) != 0) { + permissions |= S_IRGRP; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x10) != 0) { + permissions |= S_IWGRP; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x20) != 0) { + permissions |= S_IXGRP; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x40) != 0) { + permissions |= S_IROTH; + if (print) { + fprintf(stderr, "r"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[0] & 0x80) != 0) { + permissions |= S_IWOTH; + if (print) { + fprintf(stderr, "w"); + } + } else if (print) { + fprintf(stderr, "-"); + } + if ((flags[1] & 1) != 0) { + permissions |= S_IXOTH; + if (print) { + fprintf(stderr, "x"); + } + } else if (print) { + fprintf(stderr, "-"); + } + + return permissions; +} + +void print_permissions(mode_t permissions) { + if ((permissions & S_IRUSR)) { + fprintf(stderr, "r"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IWUSR)) { + fprintf(stderr, "w"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IXUSR)) { + fprintf(stderr, "x"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IRGRP)) { + fprintf(stderr, "r"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IWGRP)) { + fprintf(stderr, "w"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IXGRP)) { + fprintf(stderr, "x"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IROTH)) { + fprintf(stderr, "r"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IWOTH)) { + fprintf(stderr, "w"); + } else { + fprintf(stderr, "-"); + } + if ((permissions & S_IXOTH)) { + fprintf(stderr, "x"); + } else { + fprintf(stderr, "-"); + } +} + +#endif + +void simple_archiver_internal_cleanup_int_fd(int *fd) { + if (fd && *fd >= 0) { + close(*fd); + *fd = -1; + } +} + +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX +void simple_archiver_internal_cleanup_decomp_pid(pid_t *decomp_pid) { + if (decomp_pid && *decomp_pid >= 0) { + int decompressor_status; + int decompressor_return_val; + int retries = 0; + int decompressor_ret; + CHECK_DECOMPRESSER: + decompressor_ret = waitpid(*decomp_pid, &decompressor_status, 0); + if (decompressor_ret == *decomp_pid) { + // Status is available. + decompressor_return_val = WIFEXITED(decompressor_status); + if (decompressor_return_val && WEXITSTATUS(decompressor_status)) { + fprintf(stderr, + "WARNING: Exec failed (exec exit code %d)! Invalid " + "decompressor cmd?\n", + decompressor_return_val); + } + } else if (decompressor_ret == 0) { + // Probably still running. + ++retries; + if (retries > 5) { + fprintf(stderr, "WARNING Decompressor process not stopped!\n"); + return; + } + sleep(5); + goto CHECK_DECOMPRESSER; + } else { + // Error. + fprintf(stderr, + "WARNING: Exec failed (exec exit code unknown)! Invalid " + "decompressor cmd?\n"); + } + *decomp_pid = -1; + } +} +#endif + +int symlinks_and_files_from_files(void *data, void *ud) { + SDArchiverFileInfo *file_info = data; + void **ptr_array = ud; + SDArchiverLinkedList *symlinks_list = ptr_array[0]; + SDArchiverLinkedList *files_list = ptr_array[1]; + const char *user_cwd = ptr_array[2]; + + if (file_info->filename) { + if (file_info->link_dest) { + simple_archiver_list_add( + symlinks_list, file_info->filename, + simple_archiver_helper_datastructure_cleanup_nop); + } else { + SDArchiverInternalFileInfo *file_info_struct = + malloc(sizeof(SDArchiverInternalFileInfo)); + file_info_struct->filename = strdup(file_info->filename); + file_info_struct->bit_flags[0] = 0xFF; + file_info_struct->bit_flags[1] = 1; + file_info_struct->bit_flags[2] = 0; + file_info_struct->bit_flags[3] = 0; + file_info_struct->uid = 0; + file_info_struct->gid = 0; + file_info_struct->file_size = 0; +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + __attribute__((cleanup( + simple_archiver_helper_cleanup_chdir_back))) char *original_cwd = + NULL; + if (user_cwd) { + original_cwd = realpath(".", NULL); + if (chdir(user_cwd)) { + free(file_info_struct); + return 1; + } + } + struct stat stat_buf; + memset(&stat_buf, 0, sizeof(struct stat)); + int stat_status = fstatat(AT_FDCWD, file_info_struct->filename, &stat_buf, + AT_SYMLINK_NOFOLLOW); + if (stat_status != 0) { + free(file_info_struct); + return 1; + } + file_info_struct->bit_flags[0] = 0; + file_info_struct->bit_flags[1] &= 0xFE; + if ((stat_buf.st_mode & S_IRUSR) != 0) { + file_info_struct->bit_flags[0] |= 1; + } + if ((stat_buf.st_mode & S_IWUSR) != 0) { + file_info_struct->bit_flags[0] |= 2; + } + if ((stat_buf.st_mode & S_IXUSR) != 0) { + file_info_struct->bit_flags[0] |= 4; + } + if ((stat_buf.st_mode & S_IRGRP) != 0) { + file_info_struct->bit_flags[0] |= 8; + } + if ((stat_buf.st_mode & S_IWGRP) != 0) { + file_info_struct->bit_flags[0] |= 0x10; + } + if ((stat_buf.st_mode & S_IXGRP) != 0) { + file_info_struct->bit_flags[0] |= 0x20; + } + if ((stat_buf.st_mode & S_IROTH) != 0) { + file_info_struct->bit_flags[0] |= 0x40; + } + if ((stat_buf.st_mode & S_IWOTH) != 0) { + file_info_struct->bit_flags[0] |= 0x80; + } + if ((stat_buf.st_mode & S_IXOTH) != 0) { + file_info_struct->bit_flags[1] |= 1; + } + file_info_struct->uid = stat_buf.st_uid; + file_info_struct->gid = stat_buf.st_gid; +#endif + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *fd = + fopen(file_info_struct->filename, "rb"); + if (!fd) { + free(file_info_struct); + return 1; + } + if (fseek(fd, 0, SEEK_END) < 0) { + free(file_info_struct); + return 1; + } + long ftell_ret = ftell(fd); + if (ftell_ret < 0) { + free(file_info_struct); + return 1; + } + file_info_struct->file_size = (uint64_t)ftell_ret; + simple_archiver_list_add(files_list, file_info_struct, + free_internal_file_info); + } + } + + return 0; +} + +int files_to_chunk_count(void *data, void *ud) { + SDArchiverInternalFileInfo *file_info_struct = data; + void **ptrs = ud; + const uint64_t *chunk_size = ptrs[0]; + uint64_t *current_size = ptrs[1]; + uint64_t *current_count = ptrs[2]; + SDArchiverLinkedList *chunk_counts = ptrs[3]; + + ++(*current_count); + + // Get file size. + *current_size += file_info_struct->file_size; + + // Check size. + if (*current_size >= *chunk_size) { + uint64_t *count = malloc(sizeof(uint64_t)); + *count = *current_count; + simple_archiver_list_add(chunk_counts, count, NULL); + *current_count = 0; + *current_size = 0; + } + + return 0; +} + char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) { switch (error) { case SDAS_SUCCESS: @@ -1001,6 +1612,8 @@ char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) { return "Failed to extract symlink (internal error)"; case SDAS_FAILED_TO_CHANGE_CWD: return "Failed to change current working directory"; + case SDAS_INVALID_WRITE_VERSION: + return "Unsupported write version file format"; default: return "Unknown error"; } @@ -1032,6 +1645,20 @@ void simple_archiver_free_state(SDArchiverState **state) { int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, const SDArchiverLinkedList *filenames) { + switch (state->parsed->write_version) { + case 0: + return simple_archiver_write_v0(out_f, state, filenames); + case 1: + return simple_archiver_write_v1(out_f, state, filenames); + default: + fprintf(stderr, "ERROR: Unsupported write version %u!\n", + state->parsed->write_version); + return SDAS_INVALID_WRITE_VERSION; + } +} + +int simple_archiver_write_v0(FILE *out_f, SDArchiverState *state, + const SDArchiverLinkedList *filenames) { // First create a "set" of absolute paths to given filenames. __attribute__((cleanup(simple_archiver_hash_map_free))) SDArchiverHashMap *abs_filenames = simple_archiver_hash_map_init(); @@ -1171,14 +1798,732 @@ int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, return SDAS_SUCCESS; } +int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state, + const SDArchiverLinkedList *filenames) { + // First create a "set" of absolute paths to given filenames. + __attribute__((cleanup(simple_archiver_hash_map_free))) + SDArchiverHashMap *abs_filenames = simple_archiver_hash_map_init(); + void **ptr_array = malloc(sizeof(void *) * 2); + ptr_array[0] = abs_filenames; + ptr_array[1] = (void *)state->parsed->user_cwd; + if (simple_archiver_list_get(filenames, filenames_to_abs_map_fn, ptr_array)) { + free(ptr_array); + return SDAS_FAILED_TO_CREATE_MAP; + } + free(ptr_array); + + // Get a list of symlinks and a list of files. + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *symlinks_list = simple_archiver_list_init(); + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *files_list = simple_archiver_list_init(); + + ptr_array = malloc(sizeof(void *) * 3); + ptr_array[0] = symlinks_list; + ptr_array[1] = files_list; + ptr_array[2] = (void *)state->parsed->user_cwd; + + if (simple_archiver_list_get(filenames, symlinks_and_files_from_files, + ptr_array)) { + free(ptr_array); + return SDAS_INTERNAL_ERROR; + } + free(ptr_array); + + if (fwrite("SIMPLE_ARCHIVE_VER", 1, 18, out_f) != 18) { + return SDAS_FAILED_TO_WRITE; + } + + char buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + uint16_t u16 = 1; + + simple_archiver_helper_16_bit_be(&u16); + + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + + if (state->parsed->compressor && !state->parsed->decompressor) { + return SDAS_NO_DECOMPRESSOR; + } else if (!state->parsed->compressor && state->parsed->decompressor) { + return SDAS_NO_COMPRESSOR; + } else if (state->parsed->compressor && state->parsed->decompressor) { + // 4 bytes flags, using de/compressor. + memset(buf, 0, 4); + buf[0] |= 1; + if (fwrite(buf, 1, 4, out_f) != 4) { + return SDAS_FAILED_TO_WRITE; + } + + size_t len = strlen(state->parsed->compressor); + if (len >= 0xFFFF) { + fprintf(stderr, "ERROR: Compressor cmd is too long!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 1, 2, out_f) != 2) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + + if (fwrite(state->parsed->compressor, 1, u16 + 1, out_f) != + (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + + len = strlen(state->parsed->decompressor); + if (len >= 0xFFFF) { + fprintf(stderr, "ERROR: Decompressor cmd is too long!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 1, 2, out_f) != 2) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + + if (fwrite(state->parsed->decompressor, 1, u16 + 1, out_f) != + (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + } else { + // 4 bytes flags, not using de/compressor. + memset(buf, 0, 4); + if (fwrite(buf, 1, 4, out_f) != 4) { + return SDAS_FAILED_TO_WRITE; + } + } + + if (symlinks_list->count > 0xFFFFFFFF) { + fprintf(stderr, "ERROR: Too many symlinks!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + uint32_t u32 = (uint32_t)symlinks_list->count; + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 4, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_32_bit_be(&u32); + + // Change cwd if user specified. + __attribute__((cleanup( + simple_archiver_helper_cleanup_chdir_back))) char *original_cwd = NULL; + if (state->parsed->user_cwd) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + original_cwd = realpath(".", NULL); + if (chdir(state->parsed->user_cwd)) { + return SDAS_INTERNAL_ERROR; + } +#endif + } + + { + const SDArchiverLLNode *node = symlinks_list->head; + for (u32 = 0; + u32 < (uint32_t)symlinks_list->count && node != symlinks_list->tail;) { + node = node->next; + ++u32; + memset(buf, 0, 2); +#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 *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"); + } else { + rel_path = simple_archiver_filenames_to_relative_path(link_abs_path, + abs_path); + } + } + if (abs_path && (state->parsed->flags & 0x20) == 0 && + !simple_archiver_hash_map_get(abs_filenames, abs_path, + strlen(abs_path) + 1)) { + // Is not a filename being archived, set preference to absolute path. + buf[0] |= 1; + } + + // Get symlink stats for permissions. + struct stat stat_buf; + memset(&stat_buf, 0, sizeof(struct stat)); + int stat_status = + fstatat(AT_FDCWD, node->data, &stat_buf, AT_SYMLINK_NOFOLLOW); + if (stat_status != 0) { + return SDAS_INTERNAL_ERROR; + } + + if ((stat_buf.st_mode & S_IRUSR) != 0) { + buf[0] |= 2; + } + if ((stat_buf.st_mode & S_IWUSR) != 0) { + buf[0] |= 4; + } + if ((stat_buf.st_mode & S_IXUSR) != 0) { + buf[0] |= 8; + } + if ((stat_buf.st_mode & S_IRGRP) != 0) { + buf[0] |= 0x10; + } + if ((stat_buf.st_mode & S_IWGRP) != 0) { + buf[0] |= 0x20; + } + if ((stat_buf.st_mode & S_IXGRP) != 0) { + buf[0] |= 0x40; + } + if ((stat_buf.st_mode & S_IROTH) != 0) { + buf[0] |= (char)0x80; + } + if ((stat_buf.st_mode & S_IWOTH) != 0) { + buf[1] |= 1; + } + if ((stat_buf.st_mode & S_IXOTH) != 0) { + buf[1] |= 2; + } +#else + buf[0] = 0xFE; + buf[1] = 3; +#endif + if (fwrite(buf, 1, 2, out_f) != 2) { + return SDAS_FAILED_TO_WRITE; + } + + size_t len = strlen(node->data); + if (len >= 0xFFFF) { + fprintf(stderr, "ERROR: Link name is too long!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(node->data, 1, u16 + 1, out_f) != (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + + if (abs_path && (state->parsed->flags & 0x20) == 0) { + len = strlen(abs_path); + if (len >= 0xFFFF) { + fprintf(stderr, + "ERROR: Symlink destination absolute path is too long!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(abs_path, 1, u16 + 1, out_f) != (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + } else { + u16 = 0; + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + } + + if (rel_path) { + len = strlen(rel_path); + if (len >= 0xFFFF) { + fprintf(stderr, + "ERROR: Symlink destination relative path is too long!\n"); + return SDAS_INVALID_PARSED_STATE; + } + + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(rel_path, 1, u16 + 1, out_f) != (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + } else { + u16 = 0; + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + } + } + if (u32 != (uint32_t)symlinks_list->count) { + fprintf(stderr, "ERROR: Iterated through %u symlinks out of %u total!\n", + u32, (uint32_t)symlinks_list->count); + return SDAS_INTERNAL_ERROR; + } + } + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *chunk_counts = simple_archiver_list_init(); + + { + uint64_t current_size = 0; + uint64_t current_count = 0; + void **ptrs = malloc(sizeof(void *) * 4); + ptrs[0] = (void *)&state->parsed->minimum_chunk_size; + ptrs[1] = ¤t_size; + ptrs[2] = ¤t_count; + ptrs[3] = chunk_counts; + if (simple_archiver_list_get(files_list, files_to_chunk_count, ptrs)) { + free(ptrs); + fprintf(stderr, "ERROR: Internal error calculating chunk counts!\n"); + return SDAS_INTERNAL_ERROR; + } + free(ptrs); + if (current_size > 0 && current_count > 0) { + uint64_t *count = malloc(sizeof(uint64_t)); + *count = current_count; + simple_archiver_list_add(chunk_counts, count, NULL); + } + } + + // Verify chunk counts. + { + uint64_t count = 0; + for (SDArchiverLLNode *node = chunk_counts->head->next; + node != chunk_counts->tail; node = node->next) { + if (*((uint64_t *)node->data) > 0xFFFFFFFF) { + fprintf(stderr, "ERROR: file count in chunk is too large!\n"); + return SDAS_INTERNAL_ERROR; + } + count += *((uint64_t *)node->data); + // fprintf(stderr, "DEBUG: chunk count %4llu\n", + // *((uint64_t*)node->data)); + } + if (count != files_list->count) { + fprintf(stderr, + "ERROR: Internal error calculating chunk counts (invalid number " + "of files)!\n"); + return SDAS_INTERNAL_ERROR; + } + } + + // Write number of chunks. + if (chunk_counts->count > 0xFFFFFFFF) { + fprintf(stderr, "ERROR: Too many chunks!\n"); + return SDAS_INTERNAL_ERROR; + } + u32 = (uint32_t)chunk_counts->count; + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 4, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + + __attribute__((cleanup(simple_archiver_helper_cleanup_malloced))) void + *non_compressing_chunk_size = NULL; + if (!state->parsed->compressor || !state->parsed->decompressor) { + non_compressing_chunk_size = malloc(sizeof(uint64_t)); + } + uint64_t *non_c_chunk_size = non_compressing_chunk_size; + + SDArchiverLLNode *file_node = files_list->head; + uint64_t chunk_count = 0; + for (SDArchiverLLNode *chunk_c_node = chunk_counts->head->next; + chunk_c_node != chunk_counts->tail; chunk_c_node = chunk_c_node->next) { + fprintf(stderr, "CHUNK %3lu of %3lu\n", ++chunk_count, chunk_counts->count); + // Write file count before iterating through files. + if (non_c_chunk_size) { + *non_c_chunk_size = 0; + } + + u32 = (uint32_t)(*((uint64_t *)chunk_c_node->data)); + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 4, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + SDArchiverLLNode *saved_node = file_node; + for (uint64_t file_idx = 0; file_idx < *((uint64_t *)chunk_c_node->data); + ++file_idx) { + file_node = file_node->next; + if (file_node == files_list->tail) { + return SDAS_INTERNAL_ERROR; + } + const SDArchiverInternalFileInfo *file_info_struct = file_node->data; + if (non_c_chunk_size) { + *non_c_chunk_size += file_info_struct->file_size; + } + size_t len = strlen(file_info_struct->filename); + if (len >= 0xFFFF) { + fprintf(stderr, "ERROR: Filename is too large!\n"); + return SDAS_INVALID_FILE; + } + u16 = (uint16_t)len; + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + simple_archiver_helper_16_bit_be(&u16); + if (fwrite(file_info_struct->filename, 1, u16 + 1, out_f) != + (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } else if (fwrite(file_info_struct->bit_flags, 1, 4, out_f) != 4) { + return SDAS_FAILED_TO_WRITE; + } + // UID and GID. + u32 = file_info_struct->uid; + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 4, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + u32 = file_info_struct->gid; + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 4, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + + uint64_t u64 = file_info_struct->file_size; + simple_archiver_helper_64_bit_be(&u64); + if (fwrite(&u64, 8, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + } + + file_node = saved_node; + + if (state->parsed->compressor && state->parsed->decompressor) { + // Is compressing. + + size_t temp_filename_size = strlen(state->parsed->temp_dir) + 1 + 64; + __attribute__((cleanup( + simple_archiver_helper_cleanup_c_string))) char *temp_filename = + malloc(temp_filename_size); + + __attribute__((cleanup(cleanup_temp_filename_delete))) void **ptrs_array = + malloc(sizeof(void *) * 2); + ptrs_array[0] = NULL; + ptrs_array[1] = NULL; + + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *temp_fd = NULL; + + if (state->parsed->temp_dir) { + size_t idx = 0; + size_t temp_dir_len = strlen(state->parsed->temp_dir); + snprintf(temp_filename, temp_filename_size, TEMP_FILENAME_CMP, + state->parsed->temp_dir, + state->parsed->temp_dir[temp_dir_len - 1] == '/' ? "" : "/", + idx); + do { + FILE *test_fd = fopen(temp_filename, "rb"); + if (test_fd) { + // File exists. + fclose(test_fd); + snprintf( + temp_filename, temp_filename_size, TEMP_FILENAME_CMP, + state->parsed->temp_dir, + state->parsed->temp_dir[temp_dir_len - 1] == '/' ? "" : "/", + ++idx); + } else if (idx > 0xFFFF) { + return SDAS_INTERNAL_ERROR; + } else { + break; + } + } while (1); + temp_fd = fopen(temp_filename, "w+b"); + ptrs_array[0] = temp_filename; + } else { + temp_fd = tmpfile(); + } + + if (!temp_fd) { + return SDAS_INTERNAL_ERROR; + } + + // Handle SIGPIPE. + is_sig_pipe_occurred = 0; + signal(SIGPIPE, handle_sig_pipe); + + int pipe_into_cmd[2]; + int pipe_outof_cmd[2]; + __attribute__((cleanup( + simple_archiver_internal_cleanup_decomp_pid))) pid_t compressor_pid = + -1; + + if (pipe(pipe_into_cmd) != 0) { + // Unable to create pipes. + return SDAS_INTERNAL_ERROR; + } else if (pipe(pipe_outof_cmd) != 0) { + // Unable to create second set of pipes. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + return SDAS_INTERNAL_ERROR; + } else if (fcntl(pipe_into_cmd[1], F_SETFL, O_NONBLOCK) != 0) { + // Unable to set non-blocking on into-write-pipe. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + close(pipe_outof_cmd[1]); + return SDAS_INTERNAL_ERROR; + } else if (fcntl(pipe_outof_cmd[0], F_SETFL, O_NONBLOCK) != 0) { + // Unable to set non-blocking on outof-read-pipe. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + close(pipe_outof_cmd[1]); + return SDAS_INTERNAL_ERROR; + } else if (simple_archiver_de_compress(pipe_into_cmd, pipe_outof_cmd, + state->parsed->compressor, + &compressor_pid) != 0) { + // Failed to spawn compressor. + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + fprintf(stderr, + "WARNING: Failed to start compressor cmd! Invalid cmd?\n"); + return SDAS_INTERNAL_ERROR; + } + + // Close unnecessary pipe fds on this end of the transfer. + close(pipe_into_cmd[0]); + close(pipe_outof_cmd[1]); + + // Set up cleanup so that remaining open pipes in this side is cleaned up. + __attribute__((cleanup( + simple_archiver_internal_cleanup_int_fd))) int pipe_outof_read = + pipe_outof_cmd[0]; + __attribute__((cleanup( + simple_archiver_internal_cleanup_int_fd))) int pipe_into_write = + pipe_into_cmd[1]; + + int_fast8_t to_temp_finished = 0; + for (uint64_t file_idx = 0; file_idx < *((uint64_t *)chunk_c_node->data); + ++file_idx) { + file_node = file_node->next; + if (file_node == files_list->tail) { + return SDAS_INTERNAL_ERROR; + } + const SDArchiverInternalFileInfo *file_info_struct = file_node->data; + fprintf(stderr, " FILE %3lu of %3lu: %s\n", file_idx + 1, + *(uint64_t *)chunk_c_node->data, file_info_struct->filename); + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *fd = + fopen(file_info_struct->filename, "rb"); + + int_fast8_t to_comp_finished = 0; + char hold_buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + ssize_t has_hold = -1; + while (!to_comp_finished) { + if (!to_comp_finished) { + // Write to compressor. + if (ferror(fd)) { + fprintf(stderr, "ERROR: Writing to chunk, file read error!\n"); + return SDAS_INTERNAL_ERROR; + } + if (has_hold < 0) { + size_t fread_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, fd); + if (fread_ret > 0) { + ssize_t write_ret = write(pipe_into_write, buf, fread_ret); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking write. + has_hold = (int)fread_ret; + memcpy(hold_buf, buf, fread_ret); + } else { + fprintf( + stderr, + "ERROR: Writing to compressor, pipe write error!\n"); + return SDAS_FAILED_TO_WRITE; + } + } else if (write_ret == 0) { + fprintf( + stderr, + "ERROR: Writing to compressor, unable to write bytes!\n"); + return SDAS_FAILED_TO_WRITE; + } else if ((size_t)write_ret < fread_ret) { + has_hold = (ssize_t)fread_ret - write_ret; + memcpy(hold_buf, buf + write_ret, (size_t)has_hold); + } + } + + if (feof(fd) && has_hold < 0) { + to_comp_finished = 1; + } + } else { + ssize_t write_ret = + write(pipe_into_write, hold_buf, (size_t)has_hold); + if (write_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking write. + } else { + return SDAS_INTERNAL_ERROR; + } + } else if (write_ret < has_hold) { + memcpy(buf, hold_buf + write_ret, + (size_t)(has_hold - write_ret)); + memcpy(hold_buf, buf, (size_t)(has_hold - write_ret)); + has_hold = has_hold - write_ret; + } else if (write_ret != has_hold) { + return SDAS_INTERNAL_ERROR; + } else { + has_hold = -1; + } + } + } + + // Write compressed data to temp file. + ssize_t read_ret = + read(pipe_outof_read, buf, SIMPLE_ARCHIVER_BUFFER_SIZE); + if (read_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking read. + } else { + fprintf(stderr, + "ERROR: Reading from compressor, pipe read error!\n"); + return SDAS_INTERNAL_ERROR; + } + } else if (read_ret == 0) { + // EOF. + to_temp_finished = 1; + } else { + size_t fwrite_ret = fwrite(buf, 1, (size_t)read_ret, temp_fd); + if (fwrite_ret != (size_t)read_ret) { + fprintf(stderr, + "ERROR: Reading from compressor, failed to write to " + "temporary file!\n"); + return SDAS_INTERNAL_ERROR; + } + } + } + } + + simple_archiver_internal_cleanup_int_fd(&pipe_into_write); + + // Finish writing. + if (!to_temp_finished) { + while (1) { + ssize_t read_ret = + read(pipe_outof_read, buf, SIMPLE_ARCHIVER_BUFFER_SIZE); + if (read_ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking read. + } else { + fprintf(stderr, + "ERROR: Reading from compressor, pipe read error!\n"); + return SDAS_INTERNAL_ERROR; + } + } else if (read_ret == 0) { + // EOF. + break; + } else { + size_t fwrite_ret = fwrite(buf, 1, (size_t)read_ret, temp_fd); + if (fwrite_ret != (size_t)read_ret) { + fprintf(stderr, + "ERROR: Reading from compressor, failed to write to " + "temporary file!\n"); + return SDAS_INTERNAL_ERROR; + } + } + } + } + + long comp_chunk_size = ftell(temp_fd); + if (comp_chunk_size < 0) { + fprintf(stderr, + "ERROR: Temp file reported negative size after compression!\n"); + return SDAS_INTERNAL_ERROR; + } + + // Write compressed chunk size. + uint64_t u64 = (uint64_t)comp_chunk_size; + simple_archiver_helper_64_bit_be(&u64); + if (fwrite(&u64, 8, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + + if (fseek(temp_fd, 0, SEEK_SET) != 0) { + return SDAS_INTERNAL_ERROR; + } + + size_t written_size = 0; + + // Write compressed chunk. + while (!feof(temp_fd)) { + if (ferror(temp_fd)) { + return SDAS_INTERNAL_ERROR; + } + size_t fread_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, temp_fd); + if (fread_ret > 0) { + size_t fwrite_ret = fwrite(buf, 1, fread_ret, out_f); + written_size += fread_ret; + if (fwrite_ret != fread_ret) { + fprintf(stderr, + "ERROR: Partial write of read bytes from temp file to " + "output file!\n"); + return SDAS_FAILED_TO_WRITE; + } + } + } + + if (written_size != (size_t)comp_chunk_size) { + fprintf(stderr, + "ERROR: Written chunk size is not actual chunk size!\n"); + return SDAS_FAILED_TO_WRITE; + } + + // Cleanup and remove temp_fd. + simple_archiver_helper_cleanup_FILE(&temp_fd); + } else { + // Is NOT compressing. + if (!non_c_chunk_size) { + return SDAS_INTERNAL_ERROR; + } + simple_archiver_helper_64_bit_be(non_c_chunk_size); + fwrite(non_c_chunk_size, 8, 1, out_f); + for (uint64_t file_idx = 0; file_idx < *((uint64_t *)chunk_c_node->data); + ++file_idx) { + file_node = file_node->next; + if (file_node == files_list->tail) { + return SDAS_INTERNAL_ERROR; + } + const SDArchiverInternalFileInfo *file_info_struct = file_node->data; + fprintf(stderr, " FILE %3lu of %3lu: %s\n", file_idx + 1, + *(uint64_t *)chunk_c_node->data, file_info_struct->filename); + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *fd = + fopen(file_info_struct->filename, "rb"); + while (!feof(fd)) { + if (ferror(fd)) { + fprintf(stderr, "ERROR: Writing to chunk, file read error!\n"); + return SDAS_INTERNAL_ERROR; + } + size_t fread_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, fd); + if (fread_ret > 0) { + size_t fwrite_ret = fwrite(buf, 1, fread_ret, out_f); + if (fwrite_ret != fread_ret) { + fprintf(stderr, "ERROR: Writing to chunk, file write error!\n"); + return SDAS_FAILED_TO_WRITE; + } + } + } + } + } + } + + return SDAS_SUCCESS; +} + int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, const SDArchiverState *state) { - uint8_t buf[1024]; - memset(buf, 0, 1024); + uint8_t buf[32]; + memset(buf, 0, 32); uint16_t u16; - uint32_t u32; - uint64_t u64; - int_fast8_t is_compressed = 0; if (fread(buf, 1, 18, in_f) != 18) { return SDAS_INVALID_FILE; @@ -1186,10 +2531,30 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, return SDAS_INVALID_FILE; } else if (fread(buf, 1, 2, in_f) != 2) { return SDAS_INVALID_FILE; - } else if (buf[0] != 0 || buf[1] != 0) { - // Version is not zero. + } + + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + + if (u16 == 0) { + return simple_archiver_parse_archive_version_0(in_f, do_extract, state); + } else if (u16 == 1) { + return simple_archiver_parse_archive_version_1(in_f, do_extract, state); + } else { + fprintf(stderr, "ERROR Unsupported archive version %u!\n", u16); return SDAS_INVALID_FILE; - } else if (fread(buf, 1, 4, in_f) != 4) { + } +} + +int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract, + const SDArchiverState *state) { + uint8_t buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int_fast8_t is_compressed = 0; + + if (fread(buf, 1, 4, in_f) != 4) { return SDAS_INVALID_FILE; } @@ -1216,11 +2581,11 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } simple_archiver_helper_16_bit_be(&u16); fprintf(stderr, "Compressor size is %u\n", u16); - if (u16 < 1024) { + if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - buf[1023] = 0; + buf[SIMPLE_ARCHIVER_BUFFER_SIZE - 1] = 0; fprintf(stderr, "Compressor cmd: %s\n", buf); } else { __attribute__(( @@ -1240,11 +2605,11 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } simple_archiver_helper_16_bit_be(&u16); fprintf(stderr, "Decompressor size is %u\n", u16); - if (u16 < 1024) { + if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - buf[1023] = 0; + buf[SIMPLE_ARCHIVER_BUFFER_SIZE - 1] = 0; fprintf(stderr, "Decompressor cmd: %s\n", buf); decompressor_cmd = malloc(u16 + 1); memcpy((char *)decompressor_cmd, buf, u16 + 1); @@ -1259,6 +2624,8 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } uc_heap_buf[u16 - 1] = 0; fprintf(stderr, "Decompressor cmd: %s\n", uc_heap_buf); + decompressor_cmd = heap_buf; + heap_buf = NULL; } } else { fprintf(stderr, "De/compressor flag is NOT set.\n"); @@ -1274,7 +2641,7 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, const size_t digits = simple_archiver_helper_num_digits(size); char format_str[128]; snprintf(format_str, 128, FILE_COUNTS_OUTPUT_FORMAT_STR_0, digits, digits); - int_fast8_t skip = 0; + int_fast8_t skip; __attribute__((cleanup(simple_archiver_hash_map_free))) SDArchiverHashMap *hash_map = NULL; if (state && state->parsed->working_files && @@ -1292,6 +2659,7 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } } for (uint32_t idx = 0; idx < size; ++idx) { + skip = 0; fprintf(stderr, format_str, idx + 1, size); if (feof(in_f) || ferror(in_f)) { return SDAS_INVALID_FILE; @@ -1303,13 +2671,18 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, simple_archiver_helper_cleanup_malloced))) void *out_f_name = NULL; __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *out_f = NULL; - if (u16 < 1024) { + if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - buf[1023] = 0; + buf[SIMPLE_ARCHIVER_BUFFER_SIZE - 1] = 0; fprintf(stderr, " Filename: %s\n", buf); - if (do_extract) { + if (simple_archiver_validate_file_path((char *)buf)) { + fprintf(stderr, " ERROR: Invalid filename!\n"); + skip = 1; + } + + if (do_extract && !skip) { if ((state->parsed->flags & 0x8) == 0) { __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *test_fd = fopen((const char *)buf, "rb"); @@ -1323,6 +2696,17 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } } else { skip = 0; + int fd = open((const char *)buf, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno == ELOOP) { + // Is an existing symbolic file. + unlink((const char *)buf); + } + } else { + close(fd); + // Is an existing file. + unlink((const char *)buf); + } } if (!skip) { out_f_name = malloc(strlen((const char *)buf) + 1); @@ -1332,17 +2716,23 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } else { __attribute__(( cleanup(simple_archiver_helper_cleanup_malloced))) void *heap_buf = - malloc(u16 + 1); + malloc((uint32_t)u16 + 1); uint8_t *uc_heap_buf = heap_buf; - if (fread(uc_heap_buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { + if (fread(uc_heap_buf, 1, (uint32_t)u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - uc_heap_buf[u16 - 1] = 0; + uc_heap_buf[u16] = 0; fprintf(stderr, " Filename: %s\n", uc_heap_buf); - if (do_extract) { + + if (simple_archiver_validate_file_path((char *)uc_heap_buf)) { + fprintf(stderr, " ERROR: Invalid filename!\n"); + skip = 1; + } + + if (do_extract && !skip) { if ((state->parsed->flags & 0x8) == 0) { __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) - FILE *test_fd = fopen((const char *)buf, "rb"); + FILE *test_fd = fopen((const char *)uc_heap_buf, "rb"); if (test_fd) { skip = 1; fprintf(stderr, @@ -1353,10 +2743,22 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } } else { skip = 0; + int fd = open((const char *)uc_heap_buf, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno == ELOOP) { + // Is an existing symbolic file. + unlink((const char *)uc_heap_buf); + } + } else { + close(fd); + // Is an existing file. + unlink((const char *)uc_heap_buf); + } } if (!skip) { - out_f_name = malloc(strlen((const char *)buf) + 1); - memcpy(out_f_name, buf, strlen((const char *)buf) + 1); + out_f_name = malloc(strlen((const char *)uc_heap_buf) + 1); + memcpy(out_f_name, uc_heap_buf, + strlen((const char *)uc_heap_buf) + 1); } } } @@ -1365,10 +2767,10 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, return SDAS_INVALID_FILE; } - mode_t permissions = 0; #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + mode_t permissions = 0; if (do_extract == 0) { fprintf(stderr, " Permissions: "); @@ -1479,6 +2881,12 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, simple_archiver_helper_make_dirs((const char *)out_f_name); out_f = fopen(out_f_name, "wb"); + if (!out_f) { + fprintf(stderr, + "WARNING: Failed to open \"%s\" for writing! (No write " + "permissions?)\n", + (char *)out_f_name); + } __attribute__(( cleanup(cleanup_temp_filename_delete))) void **ptrs_array = malloc(sizeof(void *) * 2); @@ -1487,7 +2895,7 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX - if (is_compressed) { + if (is_compressed && out_f) { // Handle SIGPIPE. signal(SIGPIPE, handle_sig_pipe); @@ -1576,8 +2984,8 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, int_fast8_t write_again = 0; int_fast8_t write_pipe_done = 0; int_fast8_t read_pipe_done = 0; - size_t fread_ret; - char recv_buf[1024]; + size_t fread_ret = 0; + char recv_buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; size_t amount_to_read; while (!write_pipe_done || !read_pipe_done) { if (is_sig_pipe_occurred) { @@ -1590,8 +2998,8 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, // Read from file. if (!write_pipe_done) { if (!write_again && compressed_file_size != 0) { - if (compressed_file_size > 1024) { - amount_to_read = 1024; + if (compressed_file_size > SIMPLE_ARCHIVER_BUFFER_SIZE) { + amount_to_read = SIMPLE_ARCHIVER_BUFFER_SIZE; } else { amount_to_read = compressed_file_size; } @@ -1633,7 +3041,8 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, // Read output from decompressor and write to file. if (!read_pipe_done) { - ssize_t read_ret = read(pipe_outof_cmd[0], recv_buf, 1024); + ssize_t read_ret = read(pipe_outof_cmd[0], recv_buf, + SIMPLE_ARCHIVER_BUFFER_SIZE); if (read_ret > 0) { size_t fwrite_ret = fwrite(recv_buf, 1, (size_t)read_ret, out_f); @@ -1689,16 +3098,18 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, uint64_t compressed_file_size = u64; size_t fread_ret; while (compressed_file_size != 0) { - if (compressed_file_size > 1024) { - fread_ret = fread(buf, 1, 1024, in_f); + if (compressed_file_size > SIMPLE_ARCHIVER_BUFFER_SIZE) { + fread_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, in_f); if (ferror(in_f)) { // Error. return SDAS_INTERNAL_ERROR; } - fwrite(buf, 1, fread_ret, out_f); - if (ferror(out_f)) { - // Error. - return SDAS_INTERNAL_ERROR; + if (out_f) { + fwrite(buf, 1, fread_ret, out_f); + if (ferror(out_f)) { + // Error. + return SDAS_INTERNAL_ERROR; + } } compressed_file_size -= fread_ret; } else { @@ -1707,10 +3118,12 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, // Error. return SDAS_INTERNAL_ERROR; } - fwrite(buf, 1, fread_ret, out_f); - if (ferror(out_f)) { - // Error. - return SDAS_INTERNAL_ERROR; + if (out_f) { + fwrite(buf, 1, fread_ret, out_f); + if (ferror(out_f)) { + // Error. + return SDAS_INTERNAL_ERROR; + } } compressed_file_size -= fread_ret; } @@ -1723,12 +3136,14 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, } ptrs_array[0] = NULL; - fprintf(stderr, " Extracted.\n"); + if (out_f) { + fprintf(stderr, " Extracted.\n"); + } #endif } else { while (u64 != 0) { - if (u64 > 1024) { - size_t read_ret = fread(buf, 1, 1024, in_f); + if (u64 > SIMPLE_ARCHIVER_BUFFER_SIZE) { + size_t read_ret = fread(buf, 1, SIMPLE_ARCHIVER_BUFFER_SIZE, in_f); if (read_ret > 0) { u64 -= read_ret; } else if (ferror(in_f)) { @@ -1762,11 +3177,11 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, simple_archiver_helper_16_bit_be(&u16); if (u16 == 0) { fprintf(stderr, " Link does not have absolute path.\n"); - } else if (u16 < 1024) { + } else if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - buf[1023] = 0; + buf[SIMPLE_ARCHIVER_BUFFER_SIZE - 1] = 0; fprintf(stderr, " Link absolute path: %s\n", buf); abs_path = malloc((size_t)u16 + 1); strncpy(abs_path, (char *)buf, (size_t)u16 + 1); @@ -1785,11 +3200,11 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, simple_archiver_helper_16_bit_be(&u16); if (u16 == 0) { fprintf(stderr, " Link does not have relative path.\n"); - } else if (u16 < 1024) { + } else if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { return SDAS_INVALID_FILE; } - buf[1023] = 0; + buf[SIMPLE_ARCHIVER_BUFFER_SIZE - 1] = 0; fprintf(stderr, " Link relative path: %s\n", buf); rel_path = malloc((size_t)u16 + 1); strncpy(rel_path, (char *)buf, (size_t)u16 + 1); @@ -1802,26 +3217,108 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, fprintf(stderr, " Link relative path: %s\n", (char *)rel_path); } - if (do_extract) { + if (do_extract && !skip) { simple_archiver_helper_make_dirs((const char *)out_f_name); if (abs_path && rel_path) { if (abs_preferred) { #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX - int ret = symlink(abs_path, out_f_name); + int_fast8_t retry_symlink = 0; + int ret; + V0_SYMLINK_CREATE_RETRY_0: + ret = symlink(abs_path, out_f_name); if (ret == -1) { - return SDAS_FAILED_TO_EXTRACT_SYMLINK; + if (retry_symlink) { + fprintf(stderr, + "WARNING: Failed to create symlink after removing " + "existing symlink!\n"); + goto V0_SYMLINK_CREATE_AFTER_0; + } else if (errno == EEXIST) { + if ((state->parsed->flags & 8) == 0) { + fprintf( + stderr, + "WARNING: Symlink already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + goto V0_SYMLINK_CREATE_AFTER_0; + } else { + fprintf(stderr, + "NOTICE: Symlink already exists and " + "\"--overwrite-extract\" specified, attempting to " + "overwrite...\n"); + unlink(out_f_name); + retry_symlink = 1; + goto V0_SYMLINK_CREATE_RETRY_0; + } + } else { + return SDAS_FAILED_TO_EXTRACT_SYMLINK; + } } + ret = fchmodat(AT_FDCWD, out_f_name, permissions, + AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not " + "supported by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } + V0_SYMLINK_CREATE_AFTER_0: + retry_symlink = 1; #endif } else { #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX - int ret = symlink(rel_path, out_f_name); + int_fast8_t retry_symlink = 0; + int ret; + V0_SYMLINK_CREATE_RETRY_1: + ret = symlink(rel_path, out_f_name); if (ret == -1) { - return SDAS_FAILED_TO_EXTRACT_SYMLINK; + if (retry_symlink) { + fprintf(stderr, + "WARNING: Failed to create symlink after removing " + "existing symlink!\n"); + goto V0_SYMLINK_CREATE_AFTER_1; + } else if (errno == EEXIST) { + if ((state->parsed->flags & 8) == 0) { + fprintf( + stderr, + "WARNING: Symlink already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + goto V0_SYMLINK_CREATE_AFTER_1; + } else { + fprintf(stderr, + "NOTICE: Symlink already exists and " + "\"--overwrite-extract\" specified, attempting to " + "overwrite...\n"); + unlink(out_f_name); + retry_symlink = 1; + goto V0_SYMLINK_CREATE_RETRY_1; + } + } else { + return SDAS_FAILED_TO_EXTRACT_SYMLINK; + } } + ret = fchmodat(AT_FDCWD, out_f_name, permissions, + AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not " + "supported by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } + V0_SYMLINK_CREATE_AFTER_1: + retry_symlink = 1; #endif } } else if (abs_path) { @@ -1832,6 +3329,19 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, if (ret == -1) { return SDAS_FAILED_TO_EXTRACT_SYMLINK; } + ret = + fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not supported " + "by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } #endif } else if (rel_path) { #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ @@ -1841,6 +3351,19 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, if (ret == -1) { return SDAS_FAILED_TO_EXTRACT_SYMLINK; } + ret = + fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not supported " + "by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } #endif } else { fprintf( @@ -1854,6 +3377,723 @@ int simple_archiver_parse_archive_info(FILE *in_f, int_fast8_t do_extract, return SDAS_SUCCESS; } +int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract, + const SDArchiverState *state) { + uint8_t buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + uint16_t u16; + uint32_t u32; + uint64_t u64; + + __attribute__((cleanup(simple_archiver_hash_map_free))) + SDArchiverHashMap *working_files_map = NULL; + if (state && state->parsed->working_files && + state->parsed->working_files[0] != NULL) { + working_files_map = simple_archiver_hash_map_init(); + for (char **iter = state->parsed->working_files; *iter != NULL; ++iter) { + size_t len = strlen(*iter) + 1; + char *key = malloc(len); + memcpy(key, *iter, len); + key[len - 1] = 0; + simple_archiver_hash_map_insert( + working_files_map, key, key, len, + simple_archiver_helper_datastructure_cleanup_nop, NULL); + } + } + + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + + if (do_extract && state->parsed->user_cwd) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + if (chdir(state->parsed->user_cwd)) { + return SDAS_FAILED_TO_CHANGE_CWD; + } +#endif + } + + const int_fast8_t is_compressed = (buf[0] & 1) ? 1 : 0; + + __attribute__((cleanup( + simple_archiver_helper_cleanup_c_string))) char *compressor_cmd = NULL; + __attribute__((cleanup( + simple_archiver_helper_cleanup_c_string))) char *decompressor_cmd = NULL; + + if (is_compressed) { + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + compressor_cmd = malloc(u16 + 1); + int ret = + read_buf_full_from_fd(in_f, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, + u16 + 1, compressor_cmd); + if (ret != SDAS_SUCCESS) { + return ret; + } + compressor_cmd[u16] = 0; + + fprintf(stderr, "Compressor command: %s\n", compressor_cmd); + + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + decompressor_cmd = malloc(u16 + 1); + ret = read_buf_full_from_fd(in_f, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, + u16 + 1, decompressor_cmd); + if (ret != SDAS_SUCCESS) { + return ret; + } + decompressor_cmd[u16] = 0; + + fprintf(stderr, "Decompressor command: %s\n", decompressor_cmd); + if (state && state->parsed && state->parsed->decompressor) { + fprintf(stderr, "Overriding decompressor with: %s\n", + state->parsed->decompressor); + } + } + + // Link count. + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + memcpy(&u32, buf, 4); + simple_archiver_helper_32_bit_be(&u32); + + for (uint32_t idx = 0; idx < u32; ++idx) { + fprintf(stderr, "SYMLINK %3u of %3u\n", idx + 1, u32); + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + const uint_fast8_t absolute_preferred = (buf[0] & 1) ? 1 : 0; + +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + mode_t permissions = permissions_from_bits_v1_symlink(buf, 0); +#endif + + uint_fast8_t link_extracted = 0; + uint_fast8_t skip_due_to_map = 0; + uint_fast8_t skip_due_to_invalid = 0; + + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + + __attribute__(( + cleanup(simple_archiver_helper_cleanup_c_string))) char *link_name = + malloc(u16 + 1); + + int ret = read_buf_full_from_fd( + in_f, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, u16 + 1, link_name); + if (ret != SDAS_SUCCESS) { + return ret; + } + + if (!do_extract) { + fprintf(stderr, " Link name: %s\n", link_name); + if (absolute_preferred) { + fprintf(stderr, " Absolute path preferred.\n"); + } else { + fprintf(stderr, " Relative path preferred.\n"); + } +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + fprintf(stderr, " Link Permissions: "); + print_permissions(permissions); + fprintf(stderr, "\n"); +#endif + } + + if (simple_archiver_validate_file_path(link_name)) { + fprintf(stderr, " WARNING: Invalid link name \"%s\"!\n", link_name); + skip_due_to_invalid = 1; + } + + if (working_files_map && + simple_archiver_hash_map_get(working_files_map, link_name, u16 + 1) == + NULL) { + skip_due_to_map = 1; + fprintf(stderr, " Skipping not specified in args...\n"); + } + + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + if (u16 != 0) { + __attribute__(( + cleanup(simple_archiver_helper_cleanup_c_string))) char *path = + malloc(u16 + 1); + ret = read_buf_full_from_fd(in_f, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, u16 + 1, path); + if (ret != SDAS_SUCCESS) { + return ret; + } + path[u16] = 0; + if (do_extract && !skip_due_to_map && !skip_due_to_invalid && + absolute_preferred) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + simple_archiver_helper_make_dirs(link_name); + int_fast8_t link_create_retry = 0; + V1_SYMLINK_CREATE_RETRY_0: + ret = symlink(path, link_name); + if (ret == -1) { + if (link_create_retry) { + fprintf(stderr, + "WARNING: Failed to create symlink after removing existing " + "symlink!\n"); + goto V1_SYMLINK_CREATE_AFTER_0; + } else if (errno == EEXIST) { + if ((state->parsed->flags & 8) == 0) { + fprintf(stderr, + "WARNING: Symlink already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + goto V1_SYMLINK_CREATE_AFTER_0; + } else { + fprintf( + stderr, + "NOTICE: Symlink already exists and \"--overwrite-extract\" " + "specified, attempting to overwrite...\n"); + unlink(link_name); + link_create_retry = 1; + goto V1_SYMLINK_CREATE_RETRY_0; + } + } + return SDAS_FAILED_TO_EXTRACT_SYMLINK; + } + ret = fchmodat(AT_FDCWD, link_name, permissions, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not supported " + "by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } + link_extracted = 1; + fprintf(stderr, " %s -> %s\n", link_name, path); + V1_SYMLINK_CREATE_AFTER_0: + link_create_retry = 1; +#endif + } else { + fprintf(stderr, " Abs path: %s\n", path); + } + } else if (!do_extract) { + fprintf(stderr, " No Absolute path.\n"); + } + + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + if (u16 != 0) { + __attribute__(( + cleanup(simple_archiver_helper_cleanup_c_string))) char *path = + malloc(u16 + 1); + ret = read_buf_full_from_fd(in_f, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, u16 + 1, path); + if (ret != SDAS_SUCCESS) { + return ret; + } + path[u16] = 0; + if (do_extract && !skip_due_to_map && !skip_due_to_invalid && + !absolute_preferred) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + simple_archiver_helper_make_dirs(link_name); + int_fast8_t link_create_retry = 0; + V1_SYMLINK_CREATE_RETRY_1: + ret = symlink(path, link_name); + if (ret == -1) { + if (link_create_retry) { + fprintf(stderr, + "WARNING: Failed to create symlink after removing existing " + "symlink!\n"); + goto V1_SYMLINK_CREATE_AFTER_1; + } else if (errno == EEXIST) { + if ((state->parsed->flags & 8) == 0) { + fprintf(stderr, + "WARNING: Symlink already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + goto V1_SYMLINK_CREATE_AFTER_1; + } else { + fprintf( + stderr, + "NOTICE: Symlink already exists and \"--overwrite-extract\" " + "specified, attempting to overwrite...\n"); + unlink(link_name); + link_create_retry = 1; + goto V1_SYMLINK_CREATE_RETRY_1; + } + } + return SDAS_FAILED_TO_EXTRACT_SYMLINK; + } + ret = fchmodat(AT_FDCWD, link_name, permissions, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + if (errno == EOPNOTSUPP) { + fprintf(stderr, + "NOTICE: Setting permissions of symlink is not supported " + "by FS/OS!\n"); + } else { + fprintf(stderr, + "WARNING: Failed to set permissions of symlink (%d)!\n", + errno); + } + } + link_extracted = 1; + fprintf(stderr, " %s -> %s\n", link_name, path); + V1_SYMLINK_CREATE_AFTER_1: + link_create_retry = 1; +#endif + } else { + fprintf(stderr, " Rel path: %s\n", path); + } + } else if (!do_extract) { + fprintf(stderr, " No Relative path.\n"); + } + + if (do_extract && !link_extracted && !skip_due_to_map && + !skip_due_to_invalid) { + fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n", + link_name); + } + } + + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + memcpy(&u32, buf, 4); + simple_archiver_helper_32_bit_be(&u32); + + const uint32_t chunk_count = u32; + for (uint32_t chunk_idx = 0; chunk_idx < chunk_count; ++chunk_idx) { + fprintf(stderr, "CHUNK %3u of %3u\n", chunk_idx + 1, chunk_count); + + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + memcpy(&u32, buf, 4); + simple_archiver_helper_32_bit_be(&u32); + + const uint32_t file_count = u32; + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *file_info_list = simple_archiver_list_init(); + + __attribute__((cleanup(cleanup_internal_file_info))) + SDArchiverInternalFileInfo *file_info = NULL; + + for (uint32_t file_idx = 0; file_idx < file_count; ++file_idx) { + file_info = malloc(sizeof(SDArchiverInternalFileInfo)); + memset(file_info, 0, sizeof(SDArchiverInternalFileInfo)); + + if (fread(buf, 1, 2, in_f) != 2) { + return SDAS_INVALID_FILE; + } + memcpy(&u16, buf, 2); + simple_archiver_helper_16_bit_be(&u16); + + file_info->filename = malloc(u16 + 1); + int ret = + read_buf_full_from_fd(in_f, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, + u16 + 1, file_info->filename); + if (ret != SDAS_SUCCESS) { + return ret; + } + file_info->filename[u16] = 0; + + if (simple_archiver_validate_file_path(file_info->filename)) { + fprintf(stderr, "ERROR: File idx %u: Invalid filename!\n", file_idx); + file_info->other_flags |= 1; + } + + if (state && state->parsed && (state->parsed->flags & 8) != 0) { + int fd = open((const char *)buf, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno == ELOOP) { + // Exists as a symlink. + fprintf(stderr, + "WARNING: Filename \"%s\" already exists as symlink, " + "removing...\n", + (const char *)buf); + unlink((const char *)buf); + } else { + // File doesn't exist, do nothing. + } + } else { + close(fd); + fprintf(stderr, "WARNING: File \"%s\" already exists, removing...\n", + (const char *)buf); + unlink((const char *)buf); + } + } + + if (fread(file_info->bit_flags, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + memcpy(&u32, buf, 4); + simple_archiver_helper_32_bit_be(&u32); + file_info->uid = u32; + + if (fread(buf, 1, 4, in_f) != 4) { + return SDAS_INVALID_FILE; + } + memcpy(&u32, buf, 4); + simple_archiver_helper_32_bit_be(&u32); + file_info->gid = u32; + + if (fread(buf, 1, 8, in_f) != 8) { + return SDAS_INVALID_FILE; + } + memcpy(&u64, buf, 8); + simple_archiver_helper_64_bit_be(&u64); + file_info->file_size = u64; + + simple_archiver_list_add(file_info_list, file_info, + free_internal_file_info); + file_info = NULL; + } + + if (fread(buf, 1, 8, in_f) != 8) { + return SDAS_INVALID_FILE; + } + memcpy(&u64, buf, 8); + simple_archiver_helper_64_bit_be(&u64); + + const uint64_t chunk_size = u64; + uint64_t chunk_remaining = chunk_size; + uint64_t chunk_idx = 0; + + SDArchiverLLNode *node = file_info_list->head; + uint16_t file_idx = 0; + +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + if (is_compressed) { + // Start the decompressing process and read into files. + + // Handle SIGPIPE. + is_sig_pipe_occurred = 0; + signal(SIGPIPE, handle_sig_pipe); + + int pipe_into_cmd[2]; + int pipe_outof_cmd[2]; + __attribute__((cleanup( + simple_archiver_internal_cleanup_decomp_pid))) pid_t decompressor_pid; + if (pipe(pipe_into_cmd) != 0) { + // Unable to create pipes. + break; + } else if (pipe(pipe_outof_cmd) != 0) { + // Unable to create second set of pipes. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + return SDAS_INTERNAL_ERROR; + } else if (fcntl(pipe_into_cmd[1], F_SETFL, O_NONBLOCK) != 0) { + // Unable to set non-blocking on into-write-pipe. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + close(pipe_outof_cmd[1]); + return SDAS_INTERNAL_ERROR; + } else if (fcntl(pipe_outof_cmd[0], F_SETFL, O_NONBLOCK) != 0) { + // Unable to set non-blocking on outof-read-pipe. + close(pipe_into_cmd[0]); + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + close(pipe_outof_cmd[1]); + return SDAS_INTERNAL_ERROR; + } + + if (state && state->parsed && state->parsed->decompressor) { + if (simple_archiver_de_compress(pipe_into_cmd, pipe_outof_cmd, + state->parsed->decompressor, + &decompressor_pid) != 0) { + // Failed to spawn compressor. + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + fprintf(stderr, + "WARNING: Failed to start decompressor cmd! Invalid cmd?\n"); + return SDAS_INTERNAL_ERROR; + } + } else { + if (simple_archiver_de_compress(pipe_into_cmd, pipe_outof_cmd, + decompressor_cmd, + &decompressor_pid) != 0) { + // Failed to spawn compressor. + close(pipe_into_cmd[1]); + close(pipe_outof_cmd[0]); + fprintf(stderr, + "WARNING: Failed to start decompressor cmd! Invalid cmd?\n"); + return SDAS_INTERNAL_ERROR; + } + } + + // Close unnecessary pipe fds on this end of the transfer. + close(pipe_into_cmd[0]); + close(pipe_outof_cmd[1]); + + __attribute__((cleanup( + simple_archiver_internal_cleanup_int_fd))) int pipe_outof_read = + pipe_outof_cmd[0]; + __attribute__((cleanup( + simple_archiver_internal_cleanup_int_fd))) int pipe_into_write = + pipe_into_cmd[1]; + + int decompressor_status; + int decompressor_return_val; + int decompressor_ret = + waitpid(decompressor_pid, &decompressor_status, WNOHANG); + if (decompressor_ret == decompressor_pid) { + // Status is available. + if (WIFEXITED(decompressor_status)) { + decompressor_return_val = WEXITSTATUS(decompressor_status); + fprintf(stderr, + "WARNING: Exec failed (exec exit code %d)! Invalid " + "decompressor cmd?\n", + decompressor_return_val); + return SDAS_INTERNAL_ERROR; + } + } else if (decompressor_ret == 0) { + // Probably still running, continue on. + } else { + // Error. + fprintf(stderr, + "WARNING: Exec failed (exec exit code unknown)! Invalid " + "decompressor cmd?\n"); + return SDAS_INTERNAL_ERROR; + } + + char hold_buf[SIMPLE_ARCHIVER_BUFFER_SIZE]; + ssize_t has_hold = -1; + + while (node->next != file_info_list->tail) { + node = node->next; + const SDArchiverInternalFileInfo *file_info = node->data; + fprintf(stderr, " FILE %3u of %3u: %s\n", ++file_idx, file_count, + file_info->filename); + + uint_fast8_t skip_due_to_map = 0; + if (working_files_map && simple_archiver_hash_map_get( + working_files_map, file_info->filename, + strlen(file_info->filename) + 1) == NULL) { + skip_due_to_map = 1; + fprintf(stderr, " Skipping not specified in args...\n"); + } else if ((file_info->other_flags & 1) != 0) { + fprintf(stderr, " Skipping invalid filename...\n"); + } + + if (do_extract && !skip_due_to_map && + (file_info->other_flags & 1) == 0) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + mode_t permissions = + permissions_from_bits_version_1(file_info->bit_flags, 0); +#endif + if ((state->parsed->flags & 8) == 0) { + // Check if file already exists. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *temp_fd = fopen(file_info->filename, "r"); + if (temp_fd) { + fprintf(stderr, + " WARNING: File already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + read_decomp_to_out_file(NULL, pipe_outof_read, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, &pipe_into_write, + &chunk_remaining, in_f, hold_buf, + &has_hold); + continue; + } + } + + simple_archiver_helper_make_dirs(file_info->filename); + int ret = read_decomp_to_out_file( + file_info->filename, pipe_outof_read, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, file_info->file_size, + &pipe_into_write, &chunk_remaining, in_f, hold_buf, &has_hold); + if (ret != SDAS_SUCCESS) { + return ret; + } +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + if (chmod(file_info->filename, permissions) == -1) { + return SDAS_INTERNAL_ERROR; + } else if (geteuid() == 0 && + chown(file_info->filename, file_info->uid, + file_info->gid) != 0) { + fprintf(stderr, + "ERROR Failed to set UID/GID as EUID 0 of file \"%s\"!\n", + file_info->filename); + return SDAS_INTERNAL_ERROR; + } +#endif + } else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) { + fprintf(stderr, " Permissions: "); + permissions_from_bits_version_1(file_info->bit_flags, 1); + fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid, + file_info->gid); + if (is_compressed) { + fprintf(stderr, " File size (uncompressed): %lu\n", + file_info->file_size); + } else { + fprintf(stderr, " File size: %lu\n", file_info->file_size); + } + int ret = read_decomp_to_out_file( + NULL, pipe_outof_read, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, &pipe_into_write, &chunk_remaining, in_f, + hold_buf, &has_hold); + if (ret != SDAS_SUCCESS) { + return ret; + } + } else { + int ret = read_decomp_to_out_file( + NULL, pipe_outof_read, (char *)buf, SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, &pipe_into_write, &chunk_remaining, in_f, + hold_buf, &has_hold); + if (ret != SDAS_SUCCESS) { + return ret; + } + } + } + + // Ensure EOF is left from pipe. + ssize_t read_ret = + read(pipe_outof_read, buf, SIMPLE_ARCHIVER_BUFFER_SIZE); + if (read_ret > 0) { + fprintf(stderr, "WARNING decompressor didn't reach EOF!\n"); + } + } else { +#else + // } (This comment exists so that vim can correctly match curly-braces. + if (!is_compressed) { +#endif + while (node->next != file_info_list->tail) { + node = node->next; + const SDArchiverInternalFileInfo *file_info = node->data; + fprintf(stderr, " FILE %3u of %3u: %s\n", ++file_idx, file_count, + file_info->filename); + chunk_idx += file_info->file_size; + if (chunk_idx > chunk_size) { + fprintf(stderr, "ERROR Files in chunk is larger than chunk!\n"); + return SDAS_INTERNAL_ERROR; + } + + uint_fast8_t skip_due_to_map = 0; + if (working_files_map && simple_archiver_hash_map_get( + working_files_map, file_info->filename, + strlen(file_info->filename) + 1) == NULL) { + skip_due_to_map = 1; + fprintf(stderr, " Skipping not specified in args...\n"); + } else if (file_info->other_flags & 1) { + fprintf(stderr, " Skipping invalid filename...\n"); + } + + if (do_extract && !skip_due_to_map && + (file_info->other_flags & 1) == 0) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + mode_t permissions = + permissions_from_bits_version_1(file_info->bit_flags, 0); +#endif + if ((state->parsed->flags & 8) == 0) { + // Check if file already exists. + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *temp_fd = fopen(file_info->filename, "r"); + if (temp_fd) { + fprintf(stderr, + " WARNING: File already exists and " + "\"--overwrite-extract\" is not specified, skipping!\n"); + int ret = read_buf_full_from_fd(in_f, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, NULL); + if (ret != SDAS_SUCCESS) { + return ret; + } + continue; + } + } + simple_archiver_helper_make_dirs(file_info->filename); + __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) + FILE *out_fd = fopen(file_info->filename, "wb"); + int ret = read_fd_to_out_fd(in_f, out_fd, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size); + if (ret != SDAS_SUCCESS) { + return ret; + } + simple_archiver_helper_cleanup_FILE(&out_fd); +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + if (chmod(file_info->filename, permissions) == -1) { + return SDAS_INTERNAL_ERROR; + } else if (geteuid() == 0 && + chown(file_info->filename, file_info->uid, + file_info->gid) != 0) { + fprintf(stderr, + "ERROR Failed to set UID/GID as EUID 0 of file \"%s\"!\n", + file_info->filename); + return SDAS_INTERNAL_ERROR; + } +#endif + } else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) { + fprintf(stderr, " Permissions: "); + permissions_from_bits_version_1(file_info->bit_flags, 1); + fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid, + file_info->gid); + if (is_compressed) { + fprintf(stderr, " File size (compressed): %lu\n", + file_info->file_size); + } else { + fprintf(stderr, " File size: %lu\n", file_info->file_size); + } + int ret = read_buf_full_from_fd(in_f, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, NULL); + if (ret != SDAS_SUCCESS) { + return ret; + } + } else { + int ret = read_buf_full_from_fd(in_f, (char *)buf, + SIMPLE_ARCHIVER_BUFFER_SIZE, + file_info->file_size, NULL); + if (ret != SDAS_SUCCESS) { + return ret; + } + } + } + } + } + + return SDAS_SUCCESS; +} + int simple_archiver_de_compress(int pipe_fd_in[2], int pipe_fd_out[2], const char *cmd, void *pid_out) { #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ @@ -1911,3 +4151,126 @@ int simple_archiver_de_compress(int pipe_fd_in[2], int pipe_fd_out[2], return 1; #endif } + +char *simple_archiver_filenames_to_relative_path(const char *from_abs, + const char *to_abs) { + if (!from_abs || !to_abs) { + return NULL; + } + + // Get first non-common char and last slash before it. + uint_fast32_t idx; + uint_fast32_t last_slash; + for (idx = 0, last_slash = 0; idx < strlen(from_abs) && idx < strlen(to_abs); + ++idx) { + if (((const char *)to_abs)[idx] != ((const char *)from_abs)[idx]) { + break; + } else if (((const char *)to_abs)[idx] == '/') { + last_slash = idx + 1; + } + } + + // Get substrings of both paths. + char *link_substr = (char *)from_abs + last_slash; + char *dest_substr = (char *)to_abs + last_slash; + char *rel_path = malloc(strlen(dest_substr) + 1); + strncpy(rel_path, dest_substr, strlen(dest_substr) + 1); + + // fprintf(stderr, "DEBUG: link_substr \"%s\", dest_substr \"%s\"\n", + // link_substr, dest_substr); + + // Get the relative path finally. + int_fast8_t has_slash = 0; + idx = 0; + do { + for (; link_substr[idx] != '/' && link_substr[idx] != 0; ++idx); + if (link_substr[idx] == 0) { + has_slash = 0; + } else { + has_slash = 1; + size_t new_rel_path_size = strlen(rel_path) + 1 + 3; + char *new_rel_path = malloc(new_rel_path_size); + new_rel_path[0] = '.'; + new_rel_path[1] = '.'; + new_rel_path[2] = '/'; + strncpy(new_rel_path + 3, rel_path, new_rel_path_size - 3); + free(rel_path); + rel_path = new_rel_path; + ++idx; + } + } while (has_slash); + + return rel_path; +} + +char *simple_archiver_file_abs_path(const char *filename) { +#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ + SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX + __attribute__((cleanup(simple_archiver_helper_cleanup_malloced))) void *path = + malloc(strlen(filename) + 1); + strncpy(path, filename, strlen(filename) + 1); + + char *path_dir = dirname(path); + if (!path_dir) { + return NULL; + } + + __attribute__(( + cleanup(simple_archiver_helper_cleanup_malloced))) void *dir_realpath = + realpath(path_dir, NULL); + if (!dir_realpath) { + return NULL; + } + + // Recreate "path" since it may have been modified by dirname(). + simple_archiver_helper_cleanup_malloced(&path); + path = malloc(strlen(filename) + 1); + strncpy(path, filename, strlen(filename) + 1); + + char *filename_basename = basename(path); + if (!filename_basename) { + return NULL; + } + + // Get combined full path to file. + const size_t realpath_size = strlen(dir_realpath) + 1; + const size_t basename_size = strlen(filename_basename) + 1; + const size_t fullpath_size = realpath_size + basename_size; + char *fullpath = malloc(fullpath_size); + strncpy(fullpath, dir_realpath, realpath_size); + fullpath[realpath_size - 1] = '/'; + strcpy(fullpath + realpath_size, filename_basename); + + return fullpath; +#endif + return NULL; +} + +int simple_archiver_validate_file_path(const char *filepath) { + if (!filepath) { + return 5; + } + + const size_t len = strlen(filepath); + + if (len >= 1 && filepath[0] == '/') { + return 1; + } else if (len >= 3 && filepath[0] == '.' && filepath[1] == '.' && + filepath[2] == '/') { + return 2; + } else if (len >= 3 && filepath[len - 1] == '.' && filepath[len - 2] == '.' && + filepath[len - 3] == '/') { + return 4; + } + + for (size_t idx = 0; idx < len; ++idx) { + if (len - idx < 4) { + break; + } else if (strncmp(filepath + idx, "/../", 4) == 0) { + return 3; + } + } + + return 0; +} diff --git a/src/archiver.h b/src/archiver.h index e651b87..012fd39 100644 --- a/src/archiver.h +++ b/src/archiver.h @@ -40,7 +40,7 @@ typedef struct SDArchiverState { size_t digits; } SDArchiverState; -enum SDArchiverStateReturns { +typedef enum SDArchiverStateReturns { SDAS_SUCCESS = 0, SDAS_HEADER_ALREADY_WRITTEN = 1, SDAS_FAILED_TO_WRITE, @@ -51,8 +51,9 @@ enum SDArchiverStateReturns { SDAS_INTERNAL_ERROR, SDAS_FAILED_TO_CREATE_MAP, SDAS_FAILED_TO_EXTRACT_SYMLINK, - SDAS_FAILED_TO_CHANGE_CWD -}; + SDAS_FAILED_TO_CHANGE_CWD, + SDAS_INVALID_WRITE_VERSION +} SDArchiverStateReturns; /// Returned pointer must not be freed. char *simple_archiver_error_to_string(enum SDArchiverStateReturns error); @@ -65,12 +66,46 @@ void simple_archiver_free_state(SDArchiverState **state); int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, const SDArchiverLinkedList *filenames); +int simple_archiver_write_v0(FILE *out_f, SDArchiverState *state, + const SDArchiverLinkedList *filenames); + +int simple_archiver_write_v1(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); +/// Returns zero on success. +int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract, + const SDArchiverState *state); + +/// Returns zero on success. +int simple_archiver_parse_archive_version_1(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); +/// If returns non-NULL, must be free'd. +char *simple_archiver_filenames_to_relative_path(const char *from_abs, + const char *to_abs); + +/// Gets the absolute path to a file given a path to a file. +/// Should also work on symlinks such that the returned string is the path to +/// the link itself, not what it points to. +/// Non-NULL on success, and must be free'd if non-NULL. +char *simple_archiver_file_abs_path(const char *filename); + +/// Used to validate a file in a ".simplearchive" file to avoid writing outside +/// of current working directory. +/// Returns zero if file is OK. +/// Returns 1 if file starts with '/'. +/// Returns 2 if file contains '../' at the start. +/// Returns 3 if file contains '/../' in the middle. +/// Returns 4 if file contains '/..' at the end. +/// Returns 5 if "filepath" is NULL. +int simple_archiver_validate_file_path(const char *filepath); + #endif diff --git a/src/main.c b/src/main.c index fef432f..937b5c2 100644 --- a/src/main.c +++ b/src/main.c @@ -67,12 +67,13 @@ int main(int argc, const char **argv) { } } + SDArchiverParsedStatus parsed_status; __attribute__((cleanup(simple_archiver_list_free))) SDArchiverLinkedList *filenames = - simple_archiver_parsed_to_filenames(&parsed); - if (!filenames) { - fprintf(stderr, - "ERROR: Failed to resolve filenames from positional arguments!\n"); + simple_archiver_parsed_to_filenames(&parsed, &parsed_status); + if (!filenames || parsed_status != SDAPS_SUCCESS) { + fprintf(stderr, "ERROR: %s!\n", + simple_archiver_parsed_status_to_str(parsed_status)); return 8; } @@ -96,7 +97,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_write_all(file, state, filenames); if (ret != SDAS_SUCCESS) { fprintf(stderr, "Error during writing.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } fclose(file); @@ -112,7 +114,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_write_all(stdout, state, filenames); if (ret != SDAS_SUCCESS) { fprintf(stderr, "Error during writing.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } } @@ -129,7 +132,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_parse_archive_info(file, 0, NULL); if (ret != 0) { fprintf(stderr, "Error during archive checking/examining.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } fclose(file); @@ -137,7 +141,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_parse_archive_info(stdin, 0, NULL); if (ret != 0) { fprintf(stderr, "Error during archive checking/examining.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } } @@ -156,7 +161,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_parse_archive_info(file, 1, state); if (ret != SDAS_SUCCESS) { fprintf(stderr, "Error during archive extracting.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } fclose(file); @@ -164,7 +170,8 @@ int main(int argc, const char **argv) { int ret = simple_archiver_parse_archive_info(stdin, 1, state); if (ret != SDAS_SUCCESS) { fprintf(stderr, "Error during archive extracting.\n"); - char *error_str = simple_archiver_error_to_string(ret); + char *error_str = + simple_archiver_error_to_string((SDArchiverStateReturns)ret); fprintf(stderr, " %s\n", error_str); } } diff --git a/src/parser.c b/src/parser.c index a69dfbf..31a9ea8 100644 --- a/src/parser.c +++ b/src/parser.c @@ -131,6 +131,17 @@ int list_remove_same_str_fn(void *data, void *ud) { return 0; } +char *simple_archiver_parsed_status_to_str(SDArchiverParsedStatus status) { + switch (status) { + case SDAPS_SUCCESS: + return "Success"; + case SDAPS_NO_USER_CWD: + return "No user current working directory (-C )"; + default: + return "Unknown error"; + } +} + void simple_archiver_print_usage(void) { fprintf(stderr, "Usage flags:\n"); fprintf(stderr, "-c : create archive file\n"); @@ -145,9 +156,11 @@ void simple_archiver_print_usage(void) { "-C : Change current working directory before " "archiving/extracting\n"); fprintf(stderr, - "--compressor : requires --decompressor\n"); + "--compressor : requires --decompressor and cmd " + "must use stdin/stdout\n"); fprintf(stderr, - "--decompressor : requires --compressor\n"); + "--decompressor : requires --compressor and " + "cmd must use stdin/stdout\n"); fprintf(stderr, " Specifying \"--decompressor\" when extracting overrides archive " "file's stored decompressor cmd\n"); @@ -158,6 +171,12 @@ void simple_archiver_print_usage(void) { fprintf(stderr, "--temp-files-dir : where to store temporary files created " "when compressing (defaults to current working directory)\n"); + fprintf(stderr, + "--write-version : Force write version file format " + "(default 1)\n"); + fprintf(stderr, + "--chunk-min-size : v1 file format minimum chunk size " + "(default 4194304 or 4MiB)\n"); fprintf(stderr, "-- : specifies remaining arguments are files to archive/extract\n"); fprintf( @@ -178,6 +197,8 @@ SDArchiverParsed simple_archiver_create_parsed(void) { parsed.working_files = NULL; parsed.temp_dir = NULL; parsed.user_cwd = NULL; + parsed.write_version = 1; + parsed.minimum_chunk_size = 4194304; return parsed; } @@ -288,11 +309,50 @@ int simple_archiver_parse_args(int argc, const char **argv, out->temp_dir = argv[1]; --argc; ++argv; + } else if (strcmp(argv[0], "--write-version") == 0) { + if (argc < 2) { + fprintf(stderr, + "ERROR: --write-version expects an integer argument!\n"); + simple_archiver_print_usage(); + return 1; + } + int version = atoi(argv[1]); + if (version < 0) { + fprintf(stderr, "ERROR: --write-version cannot be negative!\n"); + simple_archiver_print_usage(); + return 1; + } else if (version > 1) { + fprintf(stderr, "ERROR: --write-version must be 0 or 1!\n"); + simple_archiver_print_usage(); + return 1; + } + out->write_version = (uint32_t)version; + --argc; + ++argv; + } else if (strcmp(argv[0], "--chunk-min-size") == 0) { + if (argc < 2) { + fprintf(stderr, + "ERROR: --chunk-min-size expects an integer argument!\n"); + simple_archiver_print_usage(); + return 1; + } + out->minimum_chunk_size = strtoull(argv[1], NULL, 10); + if (out->minimum_chunk_size == 0) { + fprintf(stderr, "ERROR: --chunk-min-size cannot be zero!\n"); + simple_archiver_print_usage(); + return 1; + } + --argc; + ++argv; } else if (argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == 0) { is_remaining_args = 1; } else if (argv[0][0] != '-') { is_remaining_args = 1; continue; + } else { + fprintf(stderr, "ERROR: Got invalid arg \"%s\"!\n", argv[0]); + simple_archiver_print_usage(); + return 1; } } else { if (out->working_files == NULL) { @@ -367,7 +427,7 @@ void simple_archiver_free_parsed(SDArchiverParsed *parsed) { } SDArchiverLinkedList *simple_archiver_parsed_to_filenames( - const SDArchiverParsed *parsed) { + const SDArchiverParsed *parsed, SDArchiverParsedStatus *status_out) { SDArchiverLinkedList *files_list = simple_archiver_list_init(); __attribute__((cleanup(simple_archiver_hash_map_free))) SDArchiverHashMap *hash_map = simple_archiver_hash_map_init(); @@ -381,6 +441,9 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( original_cwd = realpath(".", NULL); if (chdir(parsed->user_cwd)) { simple_archiver_list_free(&files_list); + if (status_out) { + *status_out = SDAPS_NO_USER_CWD; + } return NULL; } } @@ -607,5 +670,8 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( } } + if (status_out) { + *status_out = SDAPS_SUCCESS; + } return files_list; } diff --git a/src/parser.h b/src/parser.h index 008e800..c3734d0 100644 --- a/src/parser.h +++ b/src/parser.h @@ -51,6 +51,10 @@ typedef struct SDArchiverParsed { const char *temp_dir; /// Dir specified by "-C". const char *user_cwd; + /// Currently only 0 and 1 is supported. + uint32_t write_version; + /// The minimum size of a chunk in bytes (the last chunk may be less). + uint64_t minimum_chunk_size; } SDArchiverParsed; typedef struct SDArchiverFileInfo { @@ -59,6 +63,14 @@ typedef struct SDArchiverFileInfo { char *link_dest; } SDArchiverFileInfo; +typedef enum SDArchiverParsedStatus { + SDAPS_SUCCESS, + SDAPS_NO_USER_CWD, +} SDArchiverParsedStatus; + +/// Returned c-string does not need to be free'd. +char *simple_archiver_parsed_status_to_str(SDArchiverParsedStatus status); + void simple_archiver_print_usage(void); SDArchiverParsed simple_archiver_create_parsed(void); @@ -74,6 +86,6 @@ void simple_archiver_free_parsed(SDArchiverParsed *parsed); /// Each entry in the linked list is an SDArchiverFileInfo object. SDArchiverLinkedList *simple_archiver_parsed_to_filenames( - const SDArchiverParsed *parsed); + const SDArchiverParsed *parsed, SDArchiverParsedStatus *status_out); #endif diff --git a/src/test.c b/src/test.c index 869816f..420fc30 100644 --- a/src/test.c +++ b/src/test.c @@ -23,6 +23,7 @@ #include // Local includes. +#include "archiver.h" #include "helpers.h" #include "parser_internal.h" @@ -241,6 +242,27 @@ int main(void) { free(out); } + // Test archiver. + { + __attribute__(( + cleanup(simple_archiver_helper_cleanup_c_string))) char *rel_path = + simple_archiver_filenames_to_relative_path( + "/one/two/three/four/five", "/one/two/branch/other/path"); + CHECK_STREQ(rel_path, "../../branch/other/path"); + simple_archiver_helper_cleanup_c_string(&rel_path); + + rel_path = simple_archiver_filenames_to_relative_path( + "/one/two/three/four/five", "/one/two/three/other/dir/"); + CHECK_STREQ(rel_path, "../other/dir/"); + simple_archiver_helper_cleanup_c_string(&rel_path); + + CHECK_FALSE(simple_archiver_validate_file_path("Local/Path")); + CHECK_TRUE(simple_archiver_validate_file_path("/Abs/Path")); + CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really")); + CHECK_TRUE(simple_archiver_validate_file_path("./../almost")); + CHECK_TRUE(simple_archiver_validate_file_path("strange/..")); + } + printf("Checks checked: %u\n", checks_checked); printf("Checks passed: %u\n", checks_passed); return checks_passed == checks_checked ? 0 : 1;