diff --git a/src/archiver.c b/src/archiver.c index 07d38f4..6a3a1c7 100644 --- a/src/archiver.c +++ b/src/archiver.c @@ -875,7 +875,9 @@ void cleanup_free_fn(void *data) { free(data); } int filenames_to_abs_map_fn(void *data, void *ud) { SDArchiverFileInfo *file_info = data; - SDArchiverHashMap **abs_filenames = ud; + void **ptr_array = ud; + SDArchiverHashMap **abs_filenames = ptr_array[0]; + const char *user_cwd = ptr_array[1]; // Get combined full path to file. char *fullpath = filename_to_absolute_path(file_info->filename); @@ -892,11 +894,16 @@ int filenames_to_abs_map_fn(void *data, void *ud) { #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX - cwd_dirname = realpath(".", NULL); + if (user_cwd) { + cwd_dirname = realpath(user_cwd, NULL); + } else { + cwd_dirname = realpath(".", NULL); + } #endif if (!cwd_dirname) { return 1; } + // fprintf(stderr, "cwd_dirname: %s\n", (char*)cwd_dirname); // Use copy of fullpath to avoid clobbering it. __attribute__((cleanup(free_malloced_memory))) void *fullpath_copy = @@ -947,6 +954,8 @@ char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) { return "Failed to create set of filenames (internal error)"; case SDAS_FAILED_TO_EXTRACT_SYMLINK: return "Failed to extract symlink (internal error)"; + case SDAS_FAILED_TO_CHANGE_CWD: + return "Failed to change current working directory"; default: return "Unknown error"; } @@ -978,10 +987,14 @@ int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, // 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(); - if (simple_archiver_list_get(filenames, filenames_to_abs_map_fn, - &abs_filenames)) { + 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); if (fwrite("SIMPLE_ARCHIVE_VER", 1, 18, out_f) != 18) { return SDAS_FAILED_TO_WRITE; @@ -1102,6 +1115,16 @@ int simple_archiver_parse_archive_info(FILE *in_f, int do_extract, 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 + } + __attribute__((cleanup(free_malloced_memory))) void *decompressor_cmd = NULL; if ((buf[0] & 1) != 0) { diff --git a/src/archiver.h b/src/archiver.h index dfa1289..8d2de1a 100644 --- a/src/archiver.h +++ b/src/archiver.h @@ -46,7 +46,8 @@ enum SDArchiverStateReturns { SDAS_INVALID_FILE, SDAS_INTERNAL_ERROR, SDAS_FAILED_TO_CREATE_MAP, - SDAS_FAILED_TO_EXTRACT_SYMLINK + SDAS_FAILED_TO_EXTRACT_SYMLINK, + SDAS_FAILED_TO_CHANGE_CWD }; /// Returned pointer must not be freed. diff --git a/src/helpers.c b/src/helpers.c index 385d511..3e2df43 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -191,3 +191,28 @@ int simple_archiver_helper_make_dirs(const char *file_path) { return 1; #endif } + +char *simple_archiver_helper_cut_substr(const char *s, unsigned int start_idx, + unsigned int end_idx) { + unsigned int s_len = strlen(s); + if (start_idx > end_idx || start_idx >= s_len || end_idx > s_len) { + return NULL; + } else if (end_idx == s_len) { + if (start_idx == 0) { + return NULL; + } + char *new_s = malloc(start_idx + 1); + strncpy(new_s, s, start_idx + 1); + new_s[start_idx] = 0; + return new_s; + } else if (start_idx == 0) { + char *new_s = malloc(s_len - end_idx + 1); + strncpy(new_s, s + end_idx, s_len - end_idx + 1); + return new_s; + } else { + char *new_s = malloc(start_idx + s_len - end_idx + 1); + strncpy(new_s, s, start_idx); + strncpy(new_s + start_idx, s + end_idx, s_len - end_idx + 1); + return new_s; + } +} diff --git a/src/helpers.h b/src/helpers.h index 809076c..643d54f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -46,4 +46,10 @@ void simple_archiver_helper_cmd_string_argv_free_ptr(char ***argv_strs); /// Returns zero on success. int simple_archiver_helper_make_dirs(const char *file_path); +/// Returns non-NULL on success. +/// Must be free'd with "free()" if non-NULL. +/// start_idx is inclusive and end_idx is exclusive. +char *simple_archiver_helper_cut_substr(const char *s, unsigned int start_idx, + unsigned int end_idx); + #endif diff --git a/src/parser.c b/src/parser.c index b18b5e7..be1a1e3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -39,7 +39,7 @@ #include "parser_internal.h" /// Gets the first non "./"-like character in the filename. -unsigned int simple_archiver_parser_internal_filename_idx( +unsigned int simple_archiver_parser_internal_get_first_non_current_idx( const char *filename) { unsigned int idx = 0; unsigned int known_good_idx = 0; @@ -142,6 +142,10 @@ void simple_archiver_print_usage(void) { fprintf(stderr, " Use \"-f -\" to work on stdout when creating archive or stdin " "when reading archive\n"); + fprintf(stderr, " NOTICE: \"-f\" is not affected by \"-C\"!\n"); + fprintf(stderr, + "-C : Change current working directory before " + "archiving/extracting\n"); fprintf(stderr, "--compressor : requires --decompressor\n"); fprintf(stderr, @@ -175,6 +179,7 @@ SDArchiverParsed simple_archiver_create_parsed(void) { parsed.decompressor = NULL; parsed.working_files = NULL; parsed.temp_dir = NULL; + parsed.user_cwd = NULL; return parsed; } @@ -239,6 +244,15 @@ int simple_archiver_parse_args(int argc, const char **argv, } --argc; ++argv; + } else if (strcmp(argv[0], "-C") == 0) { + if (argc < 2) { + fprintf(stderr, "ERROR: -C specified but missing argument!\n"); + simple_archiver_print_usage(); + return 1; + } + out->user_cwd = argv[1]; + --argc; + ++argv; } else if (strcmp(argv[0], "--compressor") == 0) { if (argc < 2) { fprintf(stderr, "--compressor specfied but missing argument!\n"); @@ -286,11 +300,31 @@ int simple_archiver_parse_args(int argc, const char **argv, if (out->working_files == NULL) { out->working_files = malloc(sizeof(char *) * 2); unsigned int arg_idx = - simple_archiver_parser_internal_filename_idx(argv[0]); - int arg_length = strlen(argv[0] + arg_idx) + 1; + simple_archiver_parser_internal_get_first_non_current_idx(argv[0]); + unsigned int arg_length = strlen(argv[0] + arg_idx) + 1; out->working_files[0] = malloc(arg_length); strncpy(out->working_files[0], argv[0] + arg_idx, arg_length); simple_archiver_parser_internal_remove_end_slash(out->working_files[0]); + if (out->user_cwd) { + if (out->user_cwd[strlen(out->user_cwd) - 1] != '/') { + char *temp = malloc(strlen(out->user_cwd) + 1 + + strlen(out->working_files[0]) + 1); + strncpy(temp, out->user_cwd, strlen(out->user_cwd) + 1); + temp[strlen(out->user_cwd)] = '/'; + strncpy(temp + strlen(out->user_cwd) + 1, out->working_files[0], + strlen(out->working_files[0]) + 1); + free(out->working_files[0]); + out->working_files[0] = temp; + } else { + char *temp = malloc(strlen(out->user_cwd) + + strlen(out->working_files[0]) + 1); + strncpy(temp, out->user_cwd, strlen(out->user_cwd) + 1); + strncpy(temp + strlen(out->user_cwd), out->working_files[0], + strlen(out->working_files[0]) + 1); + free(out->working_files[0]); + out->working_files[0] = temp; + } + } out->working_files[1] = NULL; } else { int working_size = 1; @@ -307,13 +341,37 @@ int simple_archiver_parse_args(int argc, const char **argv, // Set new actual last element to NULL. out->working_files[working_size] = NULL; unsigned int arg_idx = - simple_archiver_parser_internal_filename_idx(argv[0]); + simple_archiver_parser_internal_get_first_non_current_idx(argv[0]); int size = strlen(argv[0] + arg_idx) + 1; // Set last element to the arg. out->working_files[working_size - 1] = malloc(size); strncpy(out->working_files[working_size - 1], argv[0] + arg_idx, size); simple_archiver_parser_internal_remove_end_slash( out->working_files[working_size - 1]); + if (out->user_cwd) { + if (out->user_cwd[strlen(out->user_cwd) - 1] != '/') { + char *temp = + malloc(strlen(out->user_cwd) + 1 + + strlen(out->working_files[working_size - 1]) + 1); + strncpy(temp, out->user_cwd, strlen(out->user_cwd) + 1); + temp[strlen(out->user_cwd)] = '/'; + strncpy(temp + strlen(out->user_cwd) + 1, + out->working_files[working_size - 1], + strlen(out->working_files[working_size - 1]) + 1); + free(out->working_files[working_size - 1]); + out->working_files[working_size - 1] = temp; + } else { + char *temp = + malloc(strlen(out->user_cwd) + + strlen(out->working_files[working_size - 1]) + 1); + strncpy(temp, out->user_cwd, strlen(out->user_cwd) + 1); + strncpy(temp + strlen(out->user_cwd), + out->working_files[working_size - 1], + strlen(out->working_files[working_size - 1]) + 1); + free(out->working_files[working_size - 1]); + out->working_files[working_size - 1] = temp; + } + } } } @@ -366,12 +424,13 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( for (char **iter = parsed->working_files; iter && *iter; ++iter) { struct stat st; memset(&st, 0, sizeof(struct stat)); - fstatat(AT_FDCWD, *iter, &st, AT_SYMLINK_NOFOLLOW); + char *file_path = *iter; + fstatat(AT_FDCWD, file_path, &st, AT_SYMLINK_NOFOLLOW); if ((st.st_mode & S_IFMT) == S_IFREG || (st.st_mode & S_IFMT) == S_IFLNK) { // Is a regular file or a symbolic link. - int len = strlen(*iter) + 1; + int len = strlen(file_path) + 1; char *filename = malloc(len); - strncpy(filename, *iter, len); + strncpy(filename, file_path, len); if (simple_archiver_hash_map_get(hash_map, filename, len - 1) == NULL) { SDArchiverFileInfo *file_info = malloc(sizeof(SDArchiverFileInfo)); file_info->filename = filename; @@ -405,7 +464,7 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( // Is a directory. __attribute__((cleanup(simple_archiver_list_free))) SDArchiverLinkedList *dir_list = simple_archiver_list_init(); - simple_archiver_list_add(dir_list, *iter, container_no_free_fn); + simple_archiver_list_add(dir_list, file_path, container_no_free_fn); char *next; while (dir_list->count != 0) { simple_archiver_list_get(dir_list, list_get_last_fn, &next); @@ -428,7 +487,8 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( snprintf(combined_path, combined_size, "%s/%s", next, dir_entry->d_name); unsigned int valid_idx = - simple_archiver_parser_internal_filename_idx(combined_path); + simple_archiver_parser_internal_get_first_non_current_idx( + combined_path); if (valid_idx > 0) { char *new_path = malloc(combined_size - valid_idx); strncpy(new_path, combined_path + valid_idx, @@ -497,12 +557,14 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( } #endif - // Remove leading "./" entries from files_list. for (SDArchiverLLNode *iter = files_list->head->next; iter != files_list->tail; iter = iter->next) { SDArchiverFileInfo *file_info = iter->data; + + // Remove leading "./" entries from files_list. unsigned int idx = - simple_archiver_parser_internal_filename_idx(file_info->filename); + simple_archiver_parser_internal_get_first_non_current_idx( + file_info->filename); if (idx > 0) { int len = strlen(file_info->filename) + 1 - idx; char *substr = malloc(len); @@ -510,6 +572,28 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( free(file_info->filename); file_info->filename = substr; } + + // Remove "./" entries inside the file path. + int slash_found = 0; + int dot_found = 0; + for (idx = strlen(file_info->filename); idx-- > 0;) { + if (file_info->filename[idx] == '/') { + if (dot_found) { + char *temp = simple_archiver_helper_cut_substr(file_info->filename, + idx + 1, idx + 3); + free(file_info->filename); + file_info->filename = temp; + } else { + slash_found = 1; + continue; + } + } else if (file_info->filename[idx] == '.' && slash_found) { + dot_found = 1; + continue; + } + slash_found = 0; + dot_found = 0; + } } return files_list; diff --git a/src/parser.h b/src/parser.h index 9ba42a7..465c363 100644 --- a/src/parser.h +++ b/src/parser.h @@ -45,6 +45,8 @@ typedef struct SDArchiverParsed { /// Determines where to place temporary files. If NULL, temporary files are /// created in the current working directory. const char *temp_dir; + /// Dir specified by "-C". + const char *user_cwd; } SDArchiverParsed; typedef struct SDArchiverFileInfo { diff --git a/src/parser_internal.h b/src/parser_internal.h index b2ea7ba..3b4f588 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -21,6 +21,7 @@ #include "parser.h" -unsigned int simple_archiver_parser_internal_filename_idx(const char *filename); +unsigned int simple_archiver_parser_internal_get_first_non_current_idx( + const char *filename); #endif diff --git a/src/test.c b/src/test.c index 09a1f9a..25c5890 100644 --- a/src/test.c +++ b/src/test.c @@ -57,34 +57,42 @@ static int checks_passed = 0; int main(void) { // Test parser. { - unsigned int idx = simple_archiver_parser_internal_filename_idx("test"); + unsigned int idx = + simple_archiver_parser_internal_get_first_non_current_idx("test"); CHECK_TRUE(idx == 0); - idx = simple_archiver_parser_internal_filename_idx("./test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx("./test"); CHECK_TRUE(idx == 2); - idx = simple_archiver_parser_internal_filename_idx("././test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx("././test"); CHECK_TRUE(idx == 4); - idx = simple_archiver_parser_internal_filename_idx("././//././//./test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "././//././//./test"); CHECK_TRUE(idx == 14); - idx = simple_archiver_parser_internal_filename_idx("/././//././//./test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "/././//././//./test"); CHECK_TRUE(idx == 0); - idx = simple_archiver_parser_internal_filename_idx(".derp/.//././//./test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + ".derp/.//././//./test"); CHECK_TRUE(idx == 0); - idx = simple_archiver_parser_internal_filename_idx("././/.derp/.///./test"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "././/.derp/.///./test"); CHECK_TRUE(idx == 5); - idx = simple_archiver_parser_internal_filename_idx("././/.//.//./"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "././/.//.//./"); CHECK_TRUE(idx == 11); - idx = simple_archiver_parser_internal_filename_idx("././/.//.//."); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "././/.//.//."); CHECK_TRUE(idx == 11); - idx = simple_archiver_parser_internal_filename_idx("././/.//.//"); + idx = simple_archiver_parser_internal_get_first_non_current_idx( + "././/.//.//"); CHECK_TRUE(idx == 8); SDArchiverParsed parsed = simple_archiver_create_parsed(); @@ -197,6 +205,39 @@ int main(void) { simple_archiver_helper_cmd_string_argv_free(result_argv); } while (0); + // Test helpers cut substr. + { + const char *s = "one two three."; + unsigned int s_len = strlen(s); + // Invalid range. + char *out = simple_archiver_helper_cut_substr(s, 1, 0); + CHECK_FALSE(out); + // First idx out of range. + out = simple_archiver_helper_cut_substr(s, s_len, s_len + 1); + CHECK_FALSE(out); + // Second idx out of range. + out = simple_archiver_helper_cut_substr(s, 1, s_len + 1); + CHECK_FALSE(out); + // Invalid cut of full string. + out = simple_archiver_helper_cut_substr(s, 0, s_len); + CHECK_FALSE(out); + // Cut end of string. + out = simple_archiver_helper_cut_substr(s, 2, s_len); + CHECK_TRUE(out); + CHECK_STREQ(out, "on"); + free(out); + // Cut start of string. + out = simple_archiver_helper_cut_substr(s, 0, s_len - 3); + CHECK_TRUE(out); + CHECK_STREQ(out, "ee."); + free(out); + // Cut inside string. + out = simple_archiver_helper_cut_substr(s, 4, 8); + CHECK_TRUE(out); + CHECK_STREQ(out, "one three."); + free(out); + } + printf("Checks checked: %u\n", checks_checked); printf("Checks passed: %u\n", checks_passed); return checks_passed == checks_checked ? 0 : 1;