#include "helpers.h"
#include <ctype.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
return digits;
}
+
+const char * simple_archiver_helper_prefix_result_str(
+ SAHelperPrefixValResult result) {
+ switch (result) {
+ case SAHPrefixVal_OK:
+ return "OK";
+ case SAHPrefixVal_NULL:
+ return "Prefix is NULL";
+ case SAHPrefixVal_ZERO_LEN:
+ return "Prefix has zero length";
+ case SAHPrefixVal_ROOT:
+ return "Prefix starts with slash (root)";
+ case SAHPrefixVal_DOUBLE_SLASH:
+ return "Prefix has multiple consecutive slashes";
+ default:
+ return "Unknown";
+ }
+}
+
+SAHelperPrefixValResult simple_archiver_helper_validate_prefix(
+ const char *prefix) {
+ if (!prefix) {
+ return SAHPrefixVal_NULL;
+ }
+ const unsigned long length = strlen(prefix);
+ if (length == 0) {
+ return SAHPrefixVal_ZERO_LEN;
+ } else if (prefix[0] == '/') {
+ return SAHPrefixVal_ROOT;
+ }
+
+ uint_fast8_t was_slash = 0;
+ for (unsigned long idx = 0; idx < length; ++idx) {
+ if (prefix[idx] == '/') {
+ if (was_slash) {
+ return SAHPrefixVal_DOUBLE_SLASH;
+ }
+ was_slash = 1;
+ } else {
+ was_slash = 0;
+ }
+ }
+
+ return SAHPrefixVal_OK;
+}
+
+uint16_t simple_archiver_helper_str_slash_count(const char *str) {
+ uint16_t count = 0;
+
+ const unsigned long length = strlen(str);
+ for (unsigned long idx = 0; idx < length; ++idx) {
+ if (str[idx] == '/') {
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+char *simple_archiver_helper_insert_prefix_in_link_path(const char *prefix,
+ const char *link,
+ const char *path) {
+ uint16_t prefix_slash_count = simple_archiver_helper_str_slash_count(prefix);
+ __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+ char *cwd = getcwd(NULL, 0);
+ unsigned long cwd_length = strlen(cwd);
+ if (cwd[cwd_length - 1] != '/') {
+ // Ensure the cwd ends with a '/'.
+ char *new_cwd = malloc(cwd_length + 2);
+ memcpy(new_cwd, cwd, cwd_length);
+ new_cwd[cwd_length] = '/';
+ new_cwd[cwd_length + 1] = 0;
+ free(cwd);
+ cwd = new_cwd;
+ ++cwd_length;
+ }
+ const unsigned long prefix_length = strlen(prefix);
+ const unsigned long path_length = strlen(path);
+ if (path[0] == '/') {
+ // Dealing with an absolute path.
+
+ // First check if "path" is in archive.
+ size_t diff_idx = 0;
+ for (; cwd[diff_idx] == path[diff_idx]
+ && diff_idx < cwd_length
+ && diff_idx < path_length;
+ ++diff_idx);
+
+ if (diff_idx == cwd_length) {
+ // "path" is in archive.
+ char *result_path = malloc(path_length + prefix_length + 1);
+ // Part of path matching cwd.
+ memcpy(result_path, path, cwd_length);
+ // Insert prefix.
+ memcpy(result_path + cwd_length, prefix, prefix_length);
+ // Rest of path.
+ memcpy(result_path + cwd_length + prefix_length,
+ path + cwd_length,
+ path_length - cwd_length);
+ result_path[path_length + prefix_length] = 0;
+ return result_path;
+ } else {
+ // "path" is not in archive, no need to insert prefix.
+ return strdup(path);
+ }
+ } else {
+ // Dealing with a relative path.
+
+ // First check if "path" is in archive.
+ __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+ char *filename_realpath = simple_archiver_helper_real_path_to_name(link);
+ if (!filename_realpath) {
+ return NULL;
+ }
+ const unsigned long filename_realpath_length = strlen(filename_realpath);
+
+ size_t diff_idx = 0;
+ for (; filename_realpath[diff_idx] == cwd[diff_idx]
+ && diff_idx < filename_realpath_length
+ && diff_idx < cwd_length;
+ ++diff_idx);
+ int32_t level = simple_archiver_helper_str_slash_count(
+ filename_realpath + diff_idx);
+ const int32_t level_copy = level;
+
+ size_t prev_start_idx = 0;
+ for (size_t path_idx = 0; path_idx < path_length; ++path_idx) {
+ if (path[path_idx] == '/') {
+ if (path_idx - prev_start_idx == 2
+ && path[path_idx - 2] == '.'
+ && path[path_idx - 1] == '.') {
+ --level;
+ if (level < 0) {
+ break;
+ }
+ } else {
+ ++level;
+ }
+ prev_start_idx = path_idx + 1;
+ }
+ }
+
+ if (level >= 0) {
+ // Relative path is in cwd, no need to insert prefix.
+ return strdup(path);
+ } else {
+ // Relative path refers to something outside of archive, "insert" prefix.
+ char *result = malloc(path_length + 1 + 3 * (size_t)prefix_slash_count);
+ memcpy(result, path, path_length);
+ level = level_copy;
+ size_t start_side_idx = 0;
+ for (size_t idx = 0; idx < path_length; ++idx) {
+ if (path[idx] == '/') {
+ if (idx - start_side_idx == 2
+ && path[start_side_idx] == '.'
+ && path[start_side_idx + 1] == '.') {
+ --level;
+ if (level == -1) {
+ char *buf = malloc(path_length - idx - 1);
+ memcpy(buf, result + idx + 1, path_length - idx - 1);
+ for (size_t l_idx = 0;
+ l_idx < (size_t)prefix_slash_count;
+ ++l_idx) {
+ memcpy(result + idx + 1 + l_idx * 3, "../", 3);
+ }
+ memcpy(result + idx + 1 + (size_t)prefix_slash_count * 3,
+ buf,
+ path_length - idx - 1);
+ free(buf);
+ result[path_length + 3 * (size_t)prefix_slash_count] = 0;
+ return result;
+ }
+ }
+ start_side_idx = idx + 1;
+ }
+ }
+ free(result);
+ return NULL;
+ }
+ }
+}
+
+char *simple_archiver_helper_real_path_to_name(const char *filename) {
+ __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+ char *filename_copy = strdup(filename);
+ char *filename_dir = dirname(filename_copy);
+
+ __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+ char *filename_copy2 = strdup(filename);
+ char *filename_base = basename(filename_copy2);
+ const unsigned long basename_length = strlen(filename_base);
+
+ // Get realpath to dirname.
+ __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+ char *dir_realpath = realpath(filename_dir, NULL);
+ if (!dir_realpath) {
+ return NULL;
+ }
+ const unsigned long dir_realpath_length = strlen(dir_realpath);
+
+ // Concatenate dirname-realpath and basename.
+ if (dir_realpath[dir_realpath_length - 1] != '/') {
+ char *result = malloc(dir_realpath_length + basename_length + 2);
+ memcpy(result, dir_realpath, dir_realpath_length);
+ result[dir_realpath_length] = '/';
+ memcpy(result + dir_realpath_length + 1, filename_base, basename_length);
+ result[dir_realpath_length + 1 + basename_length] = 0;
+ return result;
+ } else {
+ char *result = malloc(dir_realpath_length + basename_length + 1);
+ memcpy(result, dir_realpath, dir_realpath_length);
+ memcpy(result + dir_realpath_length, filename_base, basename_length);
+ result[dir_realpath_length + basename_length] = 0;
+ return result;
+ }
+}
fprintf(stderr,
"-C <dir> : Change current working directory before "
"archiving/extracting\n");
+ fprintf(stderr,
+ "--prefix <prefix> : set prefix for archived/extracted paths (do not"
+ "forget \"/\" if the prefix is a directory)\n");
fprintf(stderr,
"--compressor <full_compress_cmd> : requires --decompressor and cmd "
"must use stdin/stdout\n");
parsed.mappings.GnameToGid = simple_archiver_hash_map_init();
parsed.mappings.GidToGid = simple_archiver_hash_map_init();
parsed.mappings.GnameToGname = simple_archiver_hash_map_init();
+ parsed.prefix = NULL;
return parsed;
}
out->user_cwd = argv[1];
--argc;
++argv;
+ } else if (strcmp(argv[0], "--prefix") == 0) {
+ if (argc < 2) {
+ fprintf(stderr, "ERROR: --prefix specified but missing prefix!\n");
+ simple_archiver_print_usage();
+ return 1;
+ }
+ SAHelperPrefixValResult prefix_val_result =
+ simple_archiver_helper_validate_prefix(argv[1]);
+ if (prefix_val_result != SAHPrefixVal_OK) {
+ fprintf(stderr,
+ "ERROR: Invalid prefix: %s\n",
+ simple_archiver_helper_prefix_result_str(prefix_val_result));
+ return 1;
+ }
+ const unsigned long prefix_length = strlen(argv[1]);
+ if (argv[1][prefix_length - 1] == '/') {
+ out->prefix = strdup(argv[1]);
+ } else {
+ out->prefix = malloc(prefix_length + 2);
+ memcpy(out->prefix, argv[1], prefix_length);
+ out->prefix[prefix_length] = '/';
+ out->prefix[prefix_length + 1] = 0;
+ }
+ --argc;
+ ++argv;
} else if (strcmp(argv[0], "--compressor") == 0) {
if (argc < 2) {
fprintf(stderr, "--compressor specfied but missing argument!\n");
if (parsed->mappings.GnameToGname) {
simple_archiver_hash_map_free(&parsed->mappings.GnameToGname);
}
+
+ if (parsed->prefix) {
+ free(parsed->prefix);
+ }
}
SDArchiverLinkedList *simple_archiver_parsed_to_filenames(
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
// Local includes.
#include "archiver.h"
free(out);
}
+ // Test insert prefix in link path.
+ {
+ char *cwd = getcwd(NULL, 0);
+ int ret = chdir("/tmp");
+ CHECK_TRUE(ret == 0);
+ if (ret != 0) {
+ goto TEST_HELPERS_PREFIX_END;
+ }
+ ret = mkdir("/tmp/fifty", S_IRWXU);
+ CHECK_TRUE(ret == 0 || errno == EEXIST);
+ if (ret != 0 && errno != EEXIST) {
+ goto TEST_HELPERS_PREFIX_END;
+ }
+
+ char *result = simple_archiver_helper_insert_prefix_in_link_path(
+ "one/two/three/", "fifty/link", "/tmp/fifty/link_target");
+ CHECK_TRUE(result);
+ if (result) {
+ CHECK_STREQ(result, "/tmp/one/two/three/fifty/link_target");
+ free(result);
+ }
+
+ result = simple_archiver_helper_insert_prefix_in_link_path(
+ "one/two/three/", "fifty/link", "/other");
+ CHECK_TRUE(result);
+ if (result) {
+ CHECK_STREQ(result, "/other");
+ free(result);
+ }
+
+ result = simple_archiver_helper_insert_prefix_in_link_path(
+ "one/two/three/", "fifty/link", "sixty/seventy/other");
+ CHECK_TRUE(result);
+ if (result) {
+ CHECK_STREQ(result, "sixty/seventy/other");
+ free(result);
+ }
+
+ result = simple_archiver_helper_insert_prefix_in_link_path(
+ "one/two/three/", "fifty/link", "../../other");
+ CHECK_TRUE(result);
+ if (result) {
+ CHECK_STREQ(result, "../../../../../other");
+ free(result);
+ }
+
+TEST_HELPERS_PREFIX_END:
+ rmdir("/tmp/fifty");
+ chdir(cwd);
+ free(cwd);
+ }
+
// Test archiver.
{
__attribute__((