Compare commits

..

19 commits
1.3 ... main

Author SHA1 Message Date
ce7400a298 Update Changelog.md
All checks were successful
Build for Releases / ensure-release-exists (push) Successful in 2s
Build for Releases / push-build-x86_64 (push) Successful in 6s
Run Unit Tests / build-and-run-unit-tests (push) Successful in 1m11s
Build for Releases / push-build-aarch64 (push) Successful in 59s
Build for Releases / push-build-x86_64_debian (push) Successful in 35s
Build for Releases / push-build-aarch64_debian (push) Successful in 5m13s
2024-11-15 17:39:10 +09:00
a58034aa0b Bump CMakeLists.txt cmake_minimum_required version 2024-11-15 17:38:38 +09:00
5484da169c Update CMakeLists.txt (version 1.7) 2024-11-15 17:37:23 +09:00
6f59393e0b Update Changelog.md, version 1.7 2024-11-15 17:37:06 +09:00
7bdeb049d4 Refactor hash-map
Tweaked the default hash function and hash-map-bucket-size.
2024-11-15 17:36:07 +09:00
f6f9803439 Update README.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 1m8s
2024-10-30 18:56:51 +09:00
b256350fbc Version 1.6
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
Build for Releases / ensure-release-exists (push) Successful in 1s
Build for Releases / push-build-x86_64 (push) Successful in 8s
Build for Releases / push-build-aarch64 (push) Successful in 15s
Build for Releases / push-build-x86_64_debian (push) Successful in 33s
Build for Releases / push-build-aarch64_debian (push) Successful in 5m0s
2024-10-28 13:45:21 +09:00
5d67e0dc50 Update Changelog.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
2024-10-24 16:41:46 +09:00
a415ab22ad Add option to preserve symlinks exactly
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 16s
The "--preserve-symlinks" option preserves the symlink target instead of
deriving absolute/relative-paths from it. If archived symlinks are
absolute-paths, then it is NOT recommended to use this option as the
symlinks can be clobbered on extraction (unless if "--no-safe-links" is
specified on extraction).
2024-10-24 16:37:55 +09:00
f81d007e7c Minor cleanup
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
2024-10-24 14:51:46 +09:00
14986f5c4a Update Changelog.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 5s
2024-10-24 14:00:06 +09:00
fb1c24ba2c Safe-links enforce on extract, fixes/refactorings
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
Resolves #20
2024-10-24 13:40:29 +09:00
9d31798da6 Version 1.5, update Changelog.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 14s
Build for Releases / ensure-release-exists (push) Successful in 0s
Build for Releases / push-build-x86_64 (push) Successful in 7s
Build for Releases / push-build-aarch64 (push) Successful in 57s
Build for Releases / push-build-x86_64_debian (push) Successful in 31s
Build for Releases / push-build-aarch64_debian (push) Successful in 2m2s
2024-10-21 17:38:53 +09:00
1c06462ca7 Fix where symlink marked invalid still created v1 2024-10-21 17:35:55 +09:00
50bc4a8a07 Version 1.4
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 15s
Build for Releases / ensure-release-exists (push) Successful in 0s
Build for Releases / push-build-x86_64 (push) Successful in 8s
Build for Releases / push-build-aarch64 (push) Successful in 58s
Build for Releases / push-build-x86_64_debian (push) Successful in 33s
Build for Releases / push-build-aarch64_debian (push) Successful in 5m48s
Resolves #19
2024-10-21 17:22:29 +09:00
df37f68bcf Update README.md 2024-10-21 17:22:08 +09:00
edcaee803c Update Changelog.md 2024-10-21 17:21:15 +09:00
50b1f4b274 Impl. safe-link checking for v1 file format
As mentioned in the previous commit, "safe links" is on by default,
meaning that any symlinks pointing to outside of archived files (or
invalid) will not be stored. To store such symlinks, "--no-safe-links"
must be specified.

This commit implements "safe links" for v1 of the file format.
2024-10-21 17:12:48 +09:00
cef3e4184a Impl. "safe links" by default for v0
By default, links that point to outside of archive (or possibly doesn't
point to anything) will be ignored. Use "--no-safe-links" to preserve
such symlinks.

Note this has only been implemented for v0 of the file format in this
commit, and not yet v1.
2024-10-21 17:12:28 +09:00
11 changed files with 590 additions and 111 deletions

View file

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.10)
project(SimpleArchiver C) project(SimpleArchiver C)
set(SimpleArchiver_VERSION 1.0) set(SimpleArchiver_VERSION 1.7)
set(SimpleArchiver_SOURCES set(SimpleArchiver_SOURCES
src/main.c src/main.c

View file

@ -2,6 +2,38 @@
## Upcoming Changes ## Upcoming Changes
## Version 1.7
Refactor the internal hash-map data structure.
Minor update to CMakeLists.txt.
## Version 1.6
Enforce "safe-links" on extraction by ensuring every extracted symlink actually
points to a file in the archive. Additionally any extracted symlinks that don't
point to a valid destination is removed. This "enforce safe-links on extract"
can be disabled with the "--no-safe-links" option.
Add "--preserve-symlinks" option that will verbatim store the symlinks' target.
Not recommended if symlinks are pointing to absolute paths, which will be
clobbered on extraction to a different directory unless if "--no-safe-links" is
specified on extraction.
## Version 1.5
Previous file-format-v1 implementation of "safe links" still created a symlink
if a relative or absolute link existed in the file. This version fixes this, and
prevents invalid symlinks from being created. (This check is only done if the
bit-flag is set in the file as mentioned in the file-format spec for v1 files.)
## Version 1.4
Do "safe links" behavior by default: symlinks pointing to outside of archived
files (or invalid symlinks) should not be included in the archive, unless if the
option "--no-safe-links" is specified. This is supported in both v0 and v1 file
formats.
## Version 1.3 ## Version 1.3
Prevent `simplearchiver` from busy-waiting during non-blocking IO by sleeping Prevent `simplearchiver` from busy-waiting during non-blocking IO by sleeping

View file

@ -27,6 +27,8 @@ API calls.
--overwrite-create : allows overwriting an archive file --overwrite-create : allows overwriting an archive file
--overwrite-extract : allows overwriting when extracting --overwrite-extract : allows overwriting when extracting
--no-abs-symlink : do not store absolute paths for symlinks --no-abs-symlink : do not store absolute paths for symlinks
--preserve-symlinks : preserve the symlink's path on archive creation instead of deriving abs/relative paths, ignores "--no-abs-symlink" (It is not recommended to use this option, as absolute-path-symlinks may be clobbered on extraction)
--no-safe-links : keep symlinks that link to outside archive contents
--temp-files-dir <dir> : where to store temporary files created when compressing (defaults to current working directory) --temp-files-dir <dir> : where to store temporary files created when compressing (defaults to current working directory)
--write-version <version> : Force write version file format (default 1) --write-version <version> : Force write version file format (default 1)
--chunk-min-size <bytes> : v1 file format minimum chunk size (default 4194304 or 4MiB) --chunk-min-size <bytes> : v1 file format minimum chunk size (default 4194304 or 4MiB)
@ -38,6 +40,11 @@ API calls.
Note that `--compressor` and `--decompressor` cmds must accept data from stdin Note that `--compressor` and `--decompressor` cmds must accept data from stdin
and return processed data to stdout. and return processed data to stdout.
## Using the Cosmopolitan-Compiled Version
Note that on Linux, the `actually_portable_simplearchiver` binaries may attempt
to open via Wine (if Wine is installed). [A workaround is mentioned here.](https://github.com/jart/cosmopolitan/blob/master/README.md#linux)
## Changes ## Changes
See the [Changelog](https://git.seodisparate.com/stephenseo/SimpleArchiver/src/branch/main/Changelog.md). See the [Changelog](https://git.seodisparate.com/stephenseo/SimpleArchiver/src/branch/main/Changelog.md).

View file

@ -57,6 +57,11 @@ Following the file-count bytes, the following bytes are added for each file:
2. The second bit is "other execute permission". 2. The second bit is "other execute permission".
3. The third bit is UNSET if relative links are preferred, and is SET 3. The third bit is UNSET if relative links are preferred, and is SET
if absolute links are preferred. if absolute links are preferred.
4. The fourth bit is set if this file/symlink-entry is invalid and must
be skipped. Ignore following bytes after these 4 bytes bit-flags in
this specification and skip to the next entry; if marked invalid,
the following specification bytes for this file/symlink entry must
not exist.
3. The third byte. 3. The third byte.
1. Currently unused. 1. Currently unused.
4. The fourth byte. 4. The fourth byte.
@ -128,6 +133,9 @@ Following the link-count bytes, the following bytes are added for each symlink:
2. The second byte. 2. The second byte.
1. The first bit is "other write permission". 1. The first bit is "other write permission".
2. The second bit is "other execute permission". 2. The second bit is "other execute permission".
3. If this bit is set, then this entry is marked invalid. The link name
will be preserved in this entry, but the following link target paths
will be set to zero-length and will not be stored.
2. 2 bytes 16-bit unsigned integer "link name" in big-endian. This does not 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. 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 3. X bytes of link-name (length defined by previous value). Is a NULL-terminated

View file

@ -114,6 +114,20 @@ void cleanup_temp_filename_delete(void ***ptrs_array) {
#endif #endif
} }
void cleanup_overwrite_filename_delete_simple(char **filename) {
if (filename && *filename) {
if ((*filename)[0] != 0) {
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
unlink(*filename);
#endif
}
free(*filename);
*filename = NULL;
}
}
int write_files_fn(void *data, void *ud) { int write_files_fn(void *data, void *ud) {
if (is_sig_int_occurred) { if (is_sig_int_occurred) {
return 1; return 1;
@ -745,13 +759,28 @@ int write_files_fn(void *data, void *ud) {
// Get absolute path. // Get absolute path.
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL; simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
abs_path = realpath(file_info->filename, NULL);
#endif
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL; simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
if ((state->parsed->flags & 0x100) != 0) {
// Preserve symlink target.
char *path_buf = malloc(1024);
ssize_t ret = readlink(file_info->filename, path_buf, 1023);
if (ret == -1) {
fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
free(path_buf);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} else {
path_buf[ret] = 0;
if (path_buf[0] == '/') {
abs_path = path_buf;
((uint8_t *)temp_to_write->buf)[1] |= 0x4;
} else {
rel_path = path_buf;
}
}
} else {
abs_path = realpath(file_info->filename, NULL);
if (abs_path) { if (abs_path) {
// Get relative path. // Get relative path.
// First get absolute path of link. // First get absolute path of link.
@ -764,29 +793,93 @@ int write_files_fn(void *data, void *ud) {
// fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n", // fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n",
// (char*)abs_path, (char*)link_abs_path); // (char*)abs_path, (char*)link_abs_path);
rel_path = rel_path = simple_archiver_filenames_to_relative_path(link_abs_path,
simple_archiver_filenames_to_relative_path(link_abs_path, abs_path); abs_path);
}
} }
} }
// Check if absolute path refers to one of the filenames. // Check if absolute path refers to one of the filenames.
if (abs_path && (state->parsed->flags & 0x20) == 0 && if (abs_path && (state->parsed->flags & 0x20) == 0 &&
(state->parsed->flags & 0x100) == 0 &&
!simple_archiver_hash_map_get(state->map, abs_path, !simple_archiver_hash_map_get(state->map, abs_path,
strlen(abs_path) + 1)) { strlen(abs_path) + 1)) {
// Is not a filename being archived, set preference to absolute path. // Is not a filename being archived.
fprintf(stderr, if ((state->parsed->flags & 0x80) != 0) {
// No safe links, set preference to absolute path.
fprintf(
stderr,
"NOTICE: abs_path exists, \"--no-abs-symlink\" not specified, " "NOTICE: abs_path exists, \"--no-abs-symlink\" not specified, "
"and link refers to file NOT in archive; preferring abs_path.\n"); "and link refers to file NOT in archive; preferring abs_path.\n");
((uint8_t *)temp_to_write->buf)[1] |= 0x4; ((uint8_t *)temp_to_write->buf)[1] |= 0x4;
} else {
// Safe links, do not store symlink!
fprintf(stderr,
"WARNING: Symlink \"%s\" points to outside archive contents, "
"will not be stored! (Use \"--no-safe-links\" to disable this "
"behavior)\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
}
} else if ((state->parsed->flags & 0x100) != 0 &&
(state->parsed->flags & 0x80) == 0 &&
(((uint8_t *)temp_to_write->buf)[1] & 0x8) == 0) {
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *resolved_path = NULL;
if (abs_path || rel_path) {
resolved_path = realpath(file_info->filename, NULL);
if (!resolved_path) {
fprintf(stderr,
"WARNING: Symlink \"%s\" is invalid, will not be stored! "
"(Use \"--no-safe-links\" to disable this behavior)\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} else if (!simple_archiver_hash_map_get(state->map, resolved_path,
strlen(resolved_path) + 1)) {
fprintf(stderr,
"WARNING: Symlink \"%s\" points to outside archive contents, "
"will not be stored! (Use \"--no-safe-links\" to disable "
"this behavior)\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
}
} else {
fprintf(stderr,
"WARNING: Unable to get target path from symlink \"%s\"!\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
}
}
if (!abs_path && !rel_path) {
// No valid paths, set as invalid.
fprintf(stderr,
"WARNING: Could not get valid abs/rel path for symlink \"%s\" "
"(invalid symlink)!\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} }
// Store the 4 byte bit-flags for file. // Store the 4 byte bit-flags for file.
simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write); simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write);
if ((((uint8_t *)temp_to_write->buf)[1] & 0x8) != 0) {
// Skipped symlink.
simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f);
simple_archiver_list_free(&to_write);
char format_str[64];
snprintf(format_str, 64, FILE_COUNTS_OUTPUT_FORMAT_STR_1, state->digits,
state->digits);
fprintf(stderr, format_str, ++(state->count), state->max);
return 0;
}
// Store the absolute and relative paths. // Store the absolute and relative paths.
if (!abs_path) { if (!abs_path) {
if ((state->parsed->flags & 0x100) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to get absolute path of link destination!\n"); "WARNING: Failed to get absolute path of link destination!\n");
}
temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); temp_to_write = malloc(sizeof(SDArchiverInternalToWrite));
temp_to_write->buf = malloc(2); temp_to_write->buf = malloc(2);
temp_to_write->size = 2; temp_to_write->size = 2;
@ -859,9 +952,17 @@ int write_files_fn(void *data, void *ud) {
// Write all previously set data. // Write all previously set data.
fprintf(stderr, "Writing symlink info: %s\n", file_info->filename); fprintf(stderr, "Writing symlink info: %s\n", file_info->filename);
if ((state->parsed->flags & 0x20) == 0) { if ((state->parsed->flags & 0x20) == 0) {
if (abs_path) {
fprintf(stderr, " abs path: %s\n", (char *)abs_path); fprintf(stderr, " abs path: %s\n", (char *)abs_path);
} else {
fprintf(stderr, " abs path is NOT set\n");
} }
}
if (rel_path) {
fprintf(stderr, " rel path: %s\n", (char *)rel_path); fprintf(stderr, " rel path: %s\n", (char *)rel_path);
} else {
fprintf(stderr, " rel path is NOT set\n");
}
simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f); simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f);
simple_archiver_list_free(&to_write); simple_archiver_list_free(&to_write);
} }
@ -937,10 +1038,15 @@ int filenames_to_abs_map_fn(void *data, void *ud) {
char *fullpath_dirname_copy = malloc(strlen(fullpath_dirname) + 1); char *fullpath_dirname_copy = malloc(strlen(fullpath_dirname) + 1);
strncpy(fullpath_dirname_copy, fullpath_dirname, strncpy(fullpath_dirname_copy, fullpath_dirname,
strlen(fullpath_dirname) + 1); strlen(fullpath_dirname) + 1);
if (!simple_archiver_hash_map_get(abs_filenames, fullpath_dirname_copy,
strlen(fullpath_dirname_copy) + 1)) {
simple_archiver_hash_map_insert( simple_archiver_hash_map_insert(
abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy, abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy,
strlen(fullpath_dirname_copy) + 1, strlen(fullpath_dirname_copy) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL); simple_archiver_helper_datastructure_cleanup_nop, NULL);
} else {
free(fullpath_dirname_copy);
}
} }
prev = fullpath_dirname; prev = fullpath_dirname;
} }
@ -1643,6 +1749,29 @@ int files_to_chunk_count(void *data, void *ud) {
int greater_fn(int64_t a, int64_t b) { return a > b; } int greater_fn(int64_t a, int64_t b) { return a > b; }
void simple_archiver_internal_paths_to_files_map(SDArchiverHashMap *files_map,
const char *filename) {
simple_archiver_hash_map_insert(
files_map, (void *)1, strdup((const char *)filename),
strlen((const char *)filename) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL);
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *filename_copy =
strdup(filename);
char *filename_dirname = dirname(filename_copy);
while (strcmp(filename_dirname, ".") != 0) {
if (!simple_archiver_hash_map_get(files_map, filename_dirname,
strlen(filename_dirname) + 1)) {
simple_archiver_hash_map_insert(
files_map, (void *)1, strdup(filename_dirname),
strlen(filename_dirname) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL);
}
filename_dirname = dirname(filename_dirname);
}
}
char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) { char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) {
switch (error) { switch (error) {
case SDAS_SUCCESS: case SDAS_SUCCESS:
@ -2022,15 +2151,36 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
node = node->next; node = node->next;
++u32; ++u32;
memset(buf, 0, 2); memset(buf, 0, 2);
uint_fast8_t is_invalid = 0;
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
// Check if symlink points to thing to be stored into archive. __attribute__((cleanup(
__attribute__(( simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
cleanup(simple_archiver_helper_cleanup_malloced))) void *abs_path =
realpath(node->data, NULL);
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL; simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
if ((state->parsed->flags & 0x100) != 0) {
// Preserve symlink target.
char *path_buf = malloc(1024);
ssize_t ret = readlink(node->data, path_buf, 1023);
if (ret == -1) {
fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
free(path_buf);
is_invalid = 1;
} else {
path_buf[ret] = 0;
if (path_buf[0] == '/') {
abs_path = path_buf;
buf[0] |= 1;
} else {
rel_path = path_buf;
}
}
} else {
abs_path = realpath(node->data, NULL);
// Check if symlink points to thing to be stored into archive.
if (abs_path) { if (abs_path) {
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *link_abs_path = simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
@ -2042,12 +2192,56 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
abs_path); abs_path);
} }
} }
}
if (abs_path && (state->parsed->flags & 0x20) == 0 && if (abs_path && (state->parsed->flags & 0x20) == 0 &&
(state->parsed->flags & 0x100) == 0 &&
!simple_archiver_hash_map_get(abs_filenames, abs_path, !simple_archiver_hash_map_get(abs_filenames, abs_path,
strlen(abs_path) + 1)) { strlen(abs_path) + 1)) {
// Is not a filename being archived, set preference to absolute path. // Is not a filename being archived.
if ((state->parsed->flags & 0x80) == 0) {
// Not a "safe link", mark invalid and continue.
is_invalid = 1;
fprintf(stderr,
"WARNING: \"%s\" points to outside of archived files (or is "
"invalid) and \"--no-safe-links\" not specified, will not "
"store abs/rel-links to this entry!\n",
(const char *)node->data);
} else {
// Safe links disabled, set preference to absolute path.
buf[0] |= 1; buf[0] |= 1;
} }
} else if ((state->parsed->flags & 0x100) != 0 &&
(state->parsed->flags & 0x80) == 0 && !is_invalid) {
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *target_realpath =
realpath(node->data, NULL);
if (!target_realpath) {
fprintf(
stderr,
"WARNING: \"%s\" is an invalid symlink and \"--no-safe-links\" "
"not specified, will skip this symlink!\n",
(const char *)node->data);
is_invalid = 1;
} else if (!simple_archiver_hash_map_get(abs_filenames, target_realpath,
strlen(target_realpath) + 1)) {
fprintf(
stderr,
"WARNING: \"%s\" points to outside of archived files and "
"\"--no-safe-links\" not specified, will skip this symlink!\n",
(const char *)node->data);
is_invalid = 1;
}
}
if (!abs_path && !rel_path) {
// No valid paths, mark as invalid.
fprintf(stderr,
"WARNING: \"%s\" is an invalid symlink, will not store rel/abs "
"link paths!\n",
(const char *)node->data);
is_invalid = 1;
}
// Get symlink stats for permissions. // Get symlink stats for permissions.
struct stat stat_buf; struct stat stat_buf;
@ -2089,6 +2283,11 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
buf[0] = 0xFE; buf[0] = 0xFE;
buf[1] = 3; buf[1] = 3;
#endif #endif
if (is_invalid) {
buf[1] |= 4;
}
if (fwrite(buf, 1, 2, out_f) != 2) { if (fwrite(buf, 1, 2, out_f) != 2) {
return SDAS_FAILED_TO_WRITE; return SDAS_FAILED_TO_WRITE;
} }
@ -2109,7 +2308,7 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
return SDAS_FAILED_TO_WRITE; return SDAS_FAILED_TO_WRITE;
} }
if (abs_path && (state->parsed->flags & 0x20) == 0) { if (abs_path && (state->parsed->flags & 0x20) == 0 && !is_invalid) {
len = strlen(abs_path); len = strlen(abs_path);
if (len >= 0xFFFF) { if (len >= 0xFFFF) {
fprintf(stderr, fprintf(stderr,
@ -2133,7 +2332,7 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
} }
} }
if (rel_path) { if (rel_path && !is_invalid) {
len = strlen(rel_path); len = strlen(rel_path);
if (len >= 0xFFFF) { if (len >= 0xFFFF) {
fprintf(stderr, fprintf(stderr,
@ -2786,6 +2985,18 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
// fprintf(stderr, "\"%s\" put in map\n", key); // fprintf(stderr, "\"%s\" put in map\n", key);
} }
} }
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *links_list =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_list_init();
__attribute__((cleanup(simple_archiver_hash_map_free)))
SDArchiverHashMap *files_map =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_hash_map_init();
for (uint32_t idx = 0; idx < size; ++idx) { for (uint32_t idx = 0; idx < size; ++idx) {
if (is_sig_int_occurred) { if (is_sig_int_occurred) {
return SDAS_SIGINT; return SDAS_SIGINT;
@ -2802,6 +3013,9 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
simple_archiver_helper_cleanup_malloced))) void *out_f_name = NULL; simple_archiver_helper_cleanup_malloced))) void *out_f_name = NULL;
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *out_f = __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) FILE *out_f =
NULL; NULL;
__attribute__((cleanup(
cleanup_overwrite_filename_delete_simple))) char *to_overwrite_dest =
NULL;
if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) { if (u16 < SIMPLE_ARCHIVER_BUFFER_SIZE) {
if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) { if (fread(buf, 1, u16 + 1, in_f) != (size_t)u16 + 1) {
return SDAS_INVALID_FILE; return SDAS_INVALID_FILE;
@ -2831,12 +3045,14 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (fd == -1) { if (fd == -1) {
if (errno == ELOOP) { if (errno == ELOOP) {
// Is an existing symbolic file. // Is an existing symbolic file.
unlink((const char *)buf); // Defer deletion to after "is invalid" check.
to_overwrite_dest = strdup((const char *)buf);
} }
} else { } else {
close(fd); close(fd);
// Is an existing file. // Is an existing file.
unlink((const char *)buf); // Defer deletion to after "is invalid" check.
to_overwrite_dest = strdup((const char *)buf);
} }
} }
if (!skip) { if (!skip) {
@ -2878,12 +3094,14 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (fd == -1) { if (fd == -1) {
if (errno == ELOOP) { if (errno == ELOOP) {
// Is an existing symbolic file. // Is an existing symbolic file.
unlink((const char *)uc_heap_buf); // Defer deletion to after "is invalid" check.
to_overwrite_dest = strdup((const char *)uc_heap_buf);
} }
} else { } else {
close(fd); close(fd);
// Is an existing file. // Is an existing file.
unlink((const char *)uc_heap_buf); // Defer deletion to after "is invalid" check.
to_overwrite_dest = strdup((const char *)uc_heap_buf);
} }
} }
if (!skip) { if (!skip) {
@ -2898,6 +3116,21 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
return SDAS_INVALID_FILE; return SDAS_INVALID_FILE;
} }
// Check for "invalid entry" flag.
if ((buf[1] & 0x8) != 0) {
free(to_overwrite_dest);
to_overwrite_dest = NULL;
fprintf(stderr, " This file entry was marked invalid, skipping...\n");
continue;
} else {
// Do deferred overwrite action: remove existing file/symlink.
cleanup_overwrite_filename_delete_simple(&to_overwrite_dest);
}
if (files_map && !skip && out_f_name) {
simple_archiver_internal_paths_to_files_map(files_map, out_f_name);
}
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
@ -3376,19 +3609,19 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (retry_symlink) { if (retry_symlink) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to create symlink after removing " " WARNING: Failed to create symlink after removing "
"existing symlink!\n"); "existing symlink!\n");
goto V0_SYMLINK_CREATE_AFTER_0; goto V0_SYMLINK_CREATE_AFTER_0;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V0_SYMLINK_CREATE_AFTER_0; goto V0_SYMLINK_CREATE_AFTER_0;
} else { } else {
fprintf(stderr, fprintf(stderr,
"NOTICE: Symlink already exists and " " NOTICE: Symlink already exists and "
"\"--overwrite-extract\" specified, attempting to " "\"--overwrite-extract\" specified, attempting to "
"overwrite...\n"); "overwrite...\n");
unlink(out_f_name); unlink(out_f_name);
@ -3399,16 +3632,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = fchmodat(AT_FDCWD, out_f_name, permissions, ret = fchmodat(AT_FDCWD, out_f_name, permissions,
AT_SYMLINK_NOFOLLOW); AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not " " NOTICE: Setting permissions of symlink is not "
"supported by FS/OS!\n"); "supported by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(
"WARNING: Failed to set permissions of symlink (%d)!\n", stderr,
" WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3426,19 +3663,19 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (retry_symlink) { if (retry_symlink) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to create symlink after removing " " WARNING: Failed to create symlink after removing "
"existing symlink!\n"); "existing symlink!\n");
goto V0_SYMLINK_CREATE_AFTER_1; goto V0_SYMLINK_CREATE_AFTER_1;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V0_SYMLINK_CREATE_AFTER_1; goto V0_SYMLINK_CREATE_AFTER_1;
} else { } else {
fprintf(stderr, fprintf(stderr,
"NOTICE: Symlink already exists and " " NOTICE: Symlink already exists and "
"\"--overwrite-extract\" specified, attempting to " "\"--overwrite-extract\" specified, attempting to "
"overwrite...\n"); "overwrite...\n");
unlink(out_f_name); unlink(out_f_name);
@ -3449,16 +3686,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = fchmodat(AT_FDCWD, out_f_name, permissions, ret = fchmodat(AT_FDCWD, out_f_name, permissions,
AT_SYMLINK_NOFOLLOW); AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not " " NOTICE: Setting permissions of symlink is not "
"supported by FS/OS!\n"); "supported by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(
"WARNING: Failed to set permissions of symlink (%d)!\n", stderr,
" WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3474,16 +3715,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = ret =
fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(
"NOTICE: Setting permissions of symlink is not supported " stderr,
" NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3496,16 +3741,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = ret =
fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(
"NOTICE: Setting permissions of symlink is not supported " stderr,
" NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3513,12 +3762,16 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
} else { } else {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink entry in archive has no paths to link to!\n"); " WARNING: Symlink entry in archive has no paths to link to!\n");
} }
} }
} }
} }
if (do_extract && links_list && files_map) {
simple_archiver_safe_links_enforce(links_list, files_map);
}
if (is_sig_int_occurred) { if (is_sig_int_occurred) {
return SDAS_SIGINT; return SDAS_SIGINT;
} }
@ -3563,6 +3816,21 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
#endif #endif
} }
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *links_list =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_list_init();
__attribute__((cleanup(simple_archiver_hash_map_free)))
SDArchiverHashMap *files_map =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_hash_map_init();
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *cwd_realpath =
realpath(".", NULL);
const int_fast8_t is_compressed = (buf[0] & 1) ? 1 : 0; const int_fast8_t is_compressed = (buf[0] & 1) ? 1 : 0;
__attribute__((cleanup( __attribute__((cleanup(
@ -3627,6 +3895,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
return SDAS_INVALID_FILE; return SDAS_INVALID_FILE;
} }
const uint_fast8_t absolute_preferred = (buf[0] & 1) ? 1 : 0; const uint_fast8_t absolute_preferred = (buf[0] & 1) ? 1 : 0;
const uint_fast8_t is_invalid = (buf[1] & 4) ? 1 : 0;
if (is_invalid) {
fprintf(stderr, " WARNING: This symlink entry was marked invalid!\n");
}
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
@ -3636,7 +3909,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
uint_fast8_t link_extracted = 0; uint_fast8_t link_extracted = 0;
uint_fast8_t skip_due_to_map = 0; uint_fast8_t skip_due_to_map = 0;
uint_fast8_t skip_due_to_invalid = 0; uint_fast8_t skip_due_to_invalid = is_invalid ? 1 : 0;
if (fread(buf, 1, 2, in_f) != 2) { if (fread(buf, 1, 2, in_f) != 2) {
return SDAS_INVALID_FILE; return SDAS_INVALID_FILE;
@ -3708,21 +3981,22 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
ret = symlink(path, link_name); ret = symlink(path, link_name);
if (ret == -1) { if (ret == -1) {
if (link_create_retry) { if (link_create_retry) {
fprintf(stderr, fprintf(
"WARNING: Failed to create symlink after removing existing " stderr,
" WARNING: Failed to create symlink after removing existing "
"symlink!\n"); "symlink!\n");
goto V1_SYMLINK_CREATE_AFTER_0; goto V1_SYMLINK_CREATE_AFTER_0;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V1_SYMLINK_CREATE_AFTER_0; goto V1_SYMLINK_CREATE_AFTER_0;
} else { } else {
fprintf( fprintf(stderr,
stderr, " NOTICE: Symlink already exists and "
"NOTICE: Symlink already exists and \"--overwrite-extract\" " "\"--overwrite-extract\" specified, attempting to "
"specified, attempting to overwrite...\n"); "overwrite...\n");
unlink(link_name); unlink(link_name);
link_create_retry = 1; link_create_retry = 1;
goto V1_SYMLINK_CREATE_RETRY_0; goto V1_SYMLINK_CREATE_RETRY_0;
@ -3734,11 +4008,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not supported " " NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3747,7 +4021,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
V1_SYMLINK_CREATE_AFTER_0: V1_SYMLINK_CREATE_AFTER_0:
link_create_retry = 1; link_create_retry = 1;
#endif #endif
} else { } else if (!do_extract) {
fprintf(stderr, " Abs path: %s\n", path); fprintf(stderr, " Abs path: %s\n", path);
} }
} else if (!do_extract) { } else if (!do_extract) {
@ -3780,21 +4054,22 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
ret = symlink(path, link_name); ret = symlink(path, link_name);
if (ret == -1) { if (ret == -1) {
if (link_create_retry) { if (link_create_retry) {
fprintf(stderr, fprintf(
"WARNING: Failed to create symlink after removing existing " stderr,
" WARNING: Failed to create symlink after removing existing "
"symlink!\n"); "symlink!\n");
goto V1_SYMLINK_CREATE_AFTER_1; goto V1_SYMLINK_CREATE_AFTER_1;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V1_SYMLINK_CREATE_AFTER_1; goto V1_SYMLINK_CREATE_AFTER_1;
} else { } else {
fprintf( fprintf(stderr,
stderr, " NOTICE: Symlink already exists and "
"NOTICE: Symlink already exists and \"--overwrite-extract\" " "\"--overwrite-extract\" specified, attempting to "
"specified, attempting to overwrite...\n"); "overwrite...\n");
unlink(link_name); unlink(link_name);
link_create_retry = 1; link_create_retry = 1;
goto V1_SYMLINK_CREATE_RETRY_1; goto V1_SYMLINK_CREATE_RETRY_1;
@ -3806,11 +4081,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not supported " " NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3819,7 +4094,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
V1_SYMLINK_CREATE_AFTER_1: V1_SYMLINK_CREATE_AFTER_1:
link_create_retry = 1; link_create_retry = 1;
#endif #endif
} else { } else if (!do_extract) {
fprintf(stderr, " Rel path: %s\n", path); fprintf(stderr, " Rel path: %s\n", path);
} }
} else if (!do_extract) { } else if (!do_extract) {
@ -3830,6 +4105,9 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
!skip_due_to_invalid) { !skip_due_to_invalid) {
fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n", fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n",
link_name); link_name);
} else if (do_extract && link_extracted && !skip_due_to_map &&
!skip_due_to_invalid && links_list) {
simple_archiver_list_add(links_list, strdup(link_name), NULL);
} }
} }
@ -3930,6 +4208,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
simple_archiver_helper_64_bit_be(&u64); simple_archiver_helper_64_bit_be(&u64);
file_info->file_size = u64; file_info->file_size = u64;
if (files_map) {
simple_archiver_internal_paths_to_files_map(files_map,
file_info->filename);
}
simple_archiver_list_add(file_info_list, file_info, simple_archiver_list_add(file_info_list, file_info,
free_internal_file_info); free_internal_file_info);
file_info = NULL; file_info = NULL;
@ -4259,6 +4542,10 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
} }
} }
if (do_extract && links_list && files_map) {
simple_archiver_safe_links_enforce(links_list, files_map);
}
return SDAS_SUCCESS; return SDAS_SUCCESS;
} }
@ -4442,3 +4729,69 @@ int simple_archiver_validate_file_path(const char *filepath) {
return 0; return 0;
} }
void simple_archiver_safe_links_enforce(SDArchiverLinkedList *links_list,
SDArchiverHashMap *files_map) {
uint_fast8_t need_to_print_note = 1;
// safe-links: Check that every link maps to a file in the files_map.
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *path_to_cwd =
realpath(".", NULL);
// Ensure path_to_cwd ends with '/'.
uint32_t idx = 0;
while (path_to_cwd[idx] != 0) {
++idx;
}
if (path_to_cwd[idx - 1] != '/') {
char *temp = malloc(idx + 2);
memcpy(temp, path_to_cwd, idx);
temp[idx] = '/';
temp[idx + 1] = 0;
free(path_to_cwd);
path_to_cwd = temp;
}
// Check every link to make sure it points to an existing file.
SDArchiverLLNode *links_node = links_list->head;
while (links_node->next != links_list->tail) {
links_node = links_node->next;
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *link_realpath =
realpath(links_node->data, NULL);
if (link_realpath) {
// Get local path.
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *link_localpath =
simple_archiver_filenames_to_relative_path(path_to_cwd,
link_realpath);
if (!simple_archiver_hash_map_get(files_map, link_localpath,
strlen(link_localpath) + 1)) {
// Invalid symlink.
fprintf(stderr,
"Symlink \"%s\" is invalid (not pointing to archived file), "
"removing...\n",
(const char *)links_node->data);
unlink(links_node->data);
if (need_to_print_note) {
fprintf(stderr,
"NOTE: Disable this behavior with \"--no-safe-links\" if "
"needed.\n");
need_to_print_note = 0;
}
}
} else {
// Invalid symlink.
fprintf(stderr,
"Symlink \"%s\" is invalid (failed to resolve), removing...\n",
(const char *)links_node->data);
unlink(links_node->data);
if (need_to_print_note) {
fprintf(stderr,
"NOTE: Disable this behavior with \"--no-safe-links\" if "
"needed.\n");
need_to_print_note = 0;
}
}
}
}

View file

@ -109,4 +109,9 @@ char *simple_archiver_file_abs_path(const char *filename);
/// Returns 5 if "filepath" is NULL. /// Returns 5 if "filepath" is NULL.
int simple_archiver_validate_file_path(const char *filepath); int simple_archiver_validate_file_path(const char *filepath);
/// Removes links from "links_list" in cwd if it is not valid or does not point
/// to a file in "files_map".
void simple_archiver_safe_links_enforce(SDArchiverLinkedList *links_list,
SDArchiverHashMap *files_map);
#endif #endif

View file

@ -76,19 +76,17 @@ int simple_archiver_hash_map_internal_pick_in_list(void *data, void *ud) {
uint64_t simple_archiver_hash_default_fn(const void *key, size_t key_size) { uint64_t simple_archiver_hash_default_fn(const void *key, size_t key_size) {
uint64_t seed = 0; uint64_t seed = 0;
uint64_t temp = 0; uint64_t temp;
size_t count = 0;
for (size_t idx = 0; idx < key_size; ++idx) { for (size_t idx = 0; idx < key_size; ++idx) {
temp |= ((uint64_t) * ((uint8_t *)key + idx)) << (8 * count); temp = (uint64_t)(((uint8_t*)key)[idx]) + seed;
++count; if (idx % 3 == 0) {
if (count >= 8) { temp ^= 0xA5A538A5A9B5A5A5;
count = 0; } else if (idx % 3 == 1) {
seed += temp; temp ^= 0xD7A58BD7A58BD7AA;
temp = 0; } else {
temp ^= 0x8B7A8B8B87CB8B84;
} }
} seed += simple_archiver_algo_lcg_defaults(temp);
if (temp != 0) {
seed += temp;
} }
return simple_archiver_algo_lcg_defaults(seed); return simple_archiver_algo_lcg_defaults(seed);
@ -106,7 +104,7 @@ int simple_archiver_hash_map_internal_rehash(SDArchiverHashMap *hash_map) {
} }
SDArchiverHashMap new_hash_map; SDArchiverHashMap new_hash_map;
new_hash_map.hash_fn = hash_map->hash_fn; new_hash_map.hash_fn = hash_map->hash_fn;
new_hash_map.buckets_size = hash_map->buckets_size * 2; new_hash_map.buckets_size = (hash_map->buckets_size - 1) * 2 + 1;
// Pointers have the same size (at least on the same machine), so // Pointers have the same size (at least on the same machine), so
// sizeof(void*) should be ok. // sizeof(void*) should be ok.
new_hash_map.buckets = malloc(sizeof(void *) * new_hash_map.buckets_size); new_hash_map.buckets = malloc(sizeof(void *) * new_hash_map.buckets_size);
@ -154,7 +152,7 @@ SDArchiverHashMap *simple_archiver_hash_map_init_custom_hasher(
uint64_t (*hash_fn)(const void *, size_t)) { uint64_t (*hash_fn)(const void *, size_t)) {
SDArchiverHashMap *hash_map = malloc(sizeof(SDArchiverHashMap)); SDArchiverHashMap *hash_map = malloc(sizeof(SDArchiverHashMap));
hash_map->hash_fn = hash_fn; hash_map->hash_fn = hash_fn;
hash_map->buckets_size = SC_SA_DS_HASH_MAP_START_BUCKET_SIZE; hash_map->buckets_size = SC_SA_DS_HASH_MAP_START_BUCKET_SIZE + 1;
// Pointers have the same size (at least on the same machine), so // Pointers have the same size (at least on the same machine), so
// sizeof(void*) should be ok. // sizeof(void*) should be ok.
hash_map->buckets = malloc(sizeof(void *) * hash_map->buckets_size); hash_map->buckets = malloc(sizeof(void *) * hash_map->buckets_size);

View file

@ -219,6 +219,59 @@ int main(void) {
simple_archiver_hash_map_free(&hash_map); simple_archiver_hash_map_free(&hash_map);
} }
// Test hashing.
//{
// printf("Distribution of 13 over 33...\n");
// unsigned int counts[33];
// memset(counts, 0, sizeof(unsigned int) * 33);
// uint64_t hash;
// hash = simple_archiver_hash_default_fn("/", 2);
// printf("%s in bucket %lu (%lu)\n", "/", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/faq", 5);
// printf("%s in bucket %lu (%lu)\n", "/faq", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/FAQ", 5);
// printf("%s in bucket %lu (%lu)\n", "/FAQ", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/url", 5);
// printf("%s in bucket %lu (%lu)\n", "/url", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/home", 6);
// printf("%s in bucket %lu (%lu)\n", "/home", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/blog", 6);
// printf("%s in bucket %lu (%lu)\n", "/blog", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/test", 6);
// printf("%s in bucket %lu (%lu)\n", "/test", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/menu", 6);
// printf("%s in bucket %lu (%lu)\n", "/menu", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/posts", 7);
// printf("%s in bucket %lu (%lu)\n", "/posts", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/about", 7);
// printf("%s in bucket %lu (%lu)\n", "/about", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/media", 7);
// printf("%s in bucket %lu (%lu)\n", "/media", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/social", 8);
// printf("%s in bucket %lu (%lu)\n", "/social", hash % 33, hash);
// counts[hash % 33] += 1;
// hash = simple_archiver_hash_default_fn("/projects", 10);
// printf("%s in bucket %lu (%lu)\n", "/projects", hash % 33, hash);
// counts[hash % 33] += 1;
// for (unsigned int idx = 0; idx < 33; ++idx) {
// printf("Bucket %u: %u\n", idx, counts[idx]);
// }
//}
// Test PriorityHeap. // Test PriorityHeap.
{ {
SDArchiverPHeap *priority_heap = simple_archiver_priority_heap_init(); SDArchiverPHeap *priority_heap = simple_archiver_priority_heap_init();

View file

@ -168,6 +168,15 @@ void simple_archiver_print_usage(void) {
fprintf(stderr, "--overwrite-extract : allows overwriting when extracting\n"); fprintf(stderr, "--overwrite-extract : allows overwriting when extracting\n");
fprintf(stderr, fprintf(stderr,
"--no-abs-symlink : do not store absolute paths for symlinks\n"); "--no-abs-symlink : do not store absolute paths for symlinks\n");
fprintf(
stderr,
"--preserve-symlinks : preserve the symlink's path on archive creation "
"instead of deriving abs/relative paths, ignores \"--no-abs-symlink\" "
"(It is not recommended to use this option, as absolute-path-symlinks "
"may be clobbered on extraction)\n");
fprintf(stderr,
"--no-safe-links : keep symlinks that link to outside archive "
"contents\n");
fprintf(stderr, fprintf(stderr,
"--temp-files-dir <dir> : where to store temporary files created " "--temp-files-dir <dir> : where to store temporary files created "
"when compressing (defaults to current working directory)\n"); "when compressing (defaults to current working directory)\n");
@ -303,6 +312,13 @@ int simple_archiver_parse_args(int argc, const char **argv,
out->flags |= 0x8; out->flags |= 0x8;
} else if (strcmp(argv[0], "--no-abs-symlink") == 0) { } else if (strcmp(argv[0], "--no-abs-symlink") == 0) {
out->flags |= 0x20; out->flags |= 0x20;
} else if (strcmp(argv[0], "--preserve-symlinks") == 0) {
out->flags |= 0x100;
} else if (strcmp(argv[0], "--no-safe-links") == 0) {
out->flags |= 0x80;
fprintf(stderr,
"NOTICE: Disabling safe-links, symlinks that point to outside "
"archived files will be preserved!\n");
} else if (strcmp(argv[0], "--temp-files-dir") == 0) { } else if (strcmp(argv[0], "--temp-files-dir") == 0) {
if (argc < 2) { if (argc < 2) {
fprintf(stderr, "ERROR: --temp-files-dir is missing an argument!\n"); fprintf(stderr, "ERROR: --temp-files-dir is missing an argument!\n");

View file

@ -36,6 +36,8 @@ typedef struct SDArchiverParsed {
/// 0b xxx1 xxxx - Create archive to stdout or read archive from stdin. /// 0b xxx1 xxxx - Create archive to stdout or read archive from stdin.
/// 0b xx1x xxxx - Do not save absolute paths for symlinks. /// 0b xx1x xxxx - Do not save absolute paths for symlinks.
/// 0b x1xx xxxx - Sort files by size before archiving. /// 0b x1xx xxxx - Sort files by size before archiving.
/// 0b 1xxx xxxx - No safe links.
/// 0b xxxx xxx1 xxxx xxxx - Preserve symlink target.
uint32_t flags; uint32_t flags;
/// Null-terminated string. /// Null-terminated string.
char *filename; char *filename;

View file

@ -256,6 +256,11 @@ int main(void) {
CHECK_STREQ(rel_path, "../other/dir/"); CHECK_STREQ(rel_path, "../other/dir/");
simple_archiver_helper_cleanup_c_string(&rel_path); simple_archiver_helper_cleanup_c_string(&rel_path);
rel_path = simple_archiver_filenames_to_relative_path(
"/one/two/three/", "/one/two/three/four");
CHECK_STREQ(rel_path, "four");
simple_archiver_helper_cleanup_c_string(&rel_path);
CHECK_FALSE(simple_archiver_validate_file_path("Local/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("/Abs/Path"));
CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really")); CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really"));