diff --git a/CMakeLists.txt b/CMakeLists.txt index 9203554..1ecbcee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(SimpleArchiver_SOURCES src/main.c src/parser.c src/helpers.c + src/archiver.c src/data_structures/linked_list.c src/data_structures/hash_map.c src/data_structures/priority_heap.c diff --git a/cosmopolitan/Makefile b/cosmopolitan/Makefile index 7ac2466..bf753de 100644 --- a/cosmopolitan/Makefile +++ b/cosmopolitan/Makefile @@ -7,6 +7,7 @@ SOURCES = \ ../src/main.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 \ @@ -16,6 +17,7 @@ HEADERS = \ ../src/parser.h \ ../src/parser_internal.h \ ../src/helpers.h \ + ../src/archiver.h \ ../src/algorithms/linear_congruential_gen.h \ ../src/data_structures/linked_list.h \ ../src/data_structures/hash_map.h \ diff --git a/src/archiver.c b/src/archiver.c new file mode 100644 index 0000000..c1853cb --- /dev/null +++ b/src/archiver.c @@ -0,0 +1,271 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `archiver.c` is the source for an interface to creating an archive file. + */ + +#include "archiver.h" + +#include +#include +#include + +#include "helpers.h" + +typedef struct SDArchiverInternalToWrite { + void *buf; + uint64_t size; +} SDArchiverInternalToWrite; + +void free_FILE_helper(FILE **fd) { + if (fd && *fd) { + fclose(*fd); + *fd = NULL; + } +} + +void free_internal_to_write(void *data) { + SDArchiverInternalToWrite *to_write = data; + free(to_write->buf); + free(data); +} + +int write_list_datas_fn(void *data, void *ud) { + SDArchiverInternalToWrite *to_write = data; + FILE *out_f = ud; + + fwrite(to_write->buf, 1, to_write->size, out_f); + return 0; +} + +int write_files_fn(void *data, void *ud) { + const SDArchiverFileInfo *file_info = data; + const SDArchiverState *state = ud; + + __attribute__((cleanup(simple_archiver_list_free))) + SDArchiverLinkedList *to_write = simple_archiver_list_init(); + SDArchiverInternalToWrite *temp_to_write; + + if (!file_info->filename) { + // Invalid entry, no filename. + return 1; + } else if (!state->out_f) { + // Invalid out-ptr. + return 1; + } else if (!file_info->link_dest) { + // Regular file, not a symbolic link. + if (state->parsed->compressor && state->parsed->decompressor) { + // De/compressor specified. + // TODO + } else { + uint16_t u16; + uint64_t u64; + + u16 = strlen(file_info->filename); + + // Write filename length. + simple_archiver_helper_16_bit_be(&u16); + temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); + temp_to_write->buf = malloc(2); + temp_to_write->size = 2; + memcpy(temp_to_write->buf, &u16, 2); + simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write); + + // Write filename. + simple_archiver_helper_16_bit_be(&u16); + temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); + temp_to_write->buf = malloc(u16 + 1); + temp_to_write->size = u16 + 1; + memcpy(temp_to_write->buf, file_info->filename, u16 + 1); + simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write); + + // Write flags. + temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); + temp_to_write->buf = malloc(4); + temp_to_write->size = 4; + for (unsigned int idx = 0; idx < temp_to_write->size; ++idx) { + ((unsigned char *)temp_to_write->buf)[idx] = 0; + } + simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write); + + // Write file length. + __attribute__((cleanup(free_FILE_helper))) FILE *fd = + fopen(file_info->filename, "rb"); + if (!fd) { + // Error. + return 1; + } else if (fseek(fd, 0, SEEK_END) != 0) { + // Error. + return 1; + } + long end = ftell(fd); + if (end == -1L) { + // Error. + return 1; + } + u64 = end; + simple_archiver_helper_64_bit_be(&u64); + temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); + temp_to_write->buf = malloc(8); + temp_to_write->size = 8; + memcpy(temp_to_write->buf, &u64, 8); + simple_archiver_list_add(to_write, temp_to_write, free_internal_to_write); + + if (fseek(fd, 0, SEEK_SET) != 0) { + // Error. + return 1; + } + + // Write all previuosly set data before writing file. + simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f); + + simple_archiver_list_free(&to_write); + + // Write file. + char buf[1024]; + size_t ret; + do { + ret = fread(buf, 1, 1024, fd); + if (ret == 1024) { + fwrite(buf, 1, 1024, state->out_f); + } else if (ret > 0) { + fwrite(buf, 1, ret, state->out_f); + } + if (feof(fd)) { + break; + } else if (ferror(fd)) { + // Error. + break; + } + } while (1); + } + } else { + // A symblic link. + // TODO + } + + return 0; +} + +SDArchiverState *simple_archiver_init_state(const SDArchiverParsed *parsed) { + if (!parsed) { + return NULL; + } + + SDArchiverState *state = malloc(sizeof(SDArchiverState)); + state->flags = 0; + state->parsed = parsed; + state->out_f = NULL; + + return state; +} + +void simple_archiver_free_state(SDArchiverState **state) { + if (state && *state) { + free(*state); + *state = NULL; + } +} + +int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, + const SDArchiverLinkedList *filenames) { + if (fwrite("SIMPLE_ARCHIVE_VER", 1, 18, out_f) != 18) { + return SDAS_FAILED_TO_WRITE; + } + + uint16_t u16 = 0; + + // No need to convert to big-endian for version 0. + // 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) { + // Write the four flag bytes with first bit set. + unsigned char c = 1; + if (fwrite(&c, 1, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + c = 0; + for (unsigned int i = 0; i < 3; ++i) { + if (fwrite(&c, 1, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + } + + // De/compressor bytes. + u16 = strlen(state->parsed->compressor); + // To big-endian. + simple_archiver_helper_16_bit_be(&u16); + // Write the size in big-endian. + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + // From big-endian. + simple_archiver_helper_16_bit_be(&u16); + // Write the compressor cmd including the NULL at the end of the string. + if (fwrite(state->parsed->compressor, 1, u16 + 1, out_f) != + (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + + u16 = strlen(state->parsed->decompressor); + // To big-endian. + simple_archiver_helper_16_bit_be(&u16); + // Write the size in big-endian. + if (fwrite(&u16, 2, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + // From big-endian. + simple_archiver_helper_16_bit_be(&u16); + // Write the decompressor cmd including the NULL at the end of the string. + if (fwrite(state->parsed->decompressor, 1, u16 + 1, out_f) != + (size_t)u16 + 1) { + return SDAS_FAILED_TO_WRITE; + } + } else { + // Write the four flag bytes with first bit NOT set. + unsigned char c = 0; + for (unsigned int i = 0; i < 4; ++i) { + if (fwrite(&c, 1, 1, out_f) != 1) { + return SDAS_FAILED_TO_WRITE; + } + } + } + + // Write file count. + { + uint32_t u32 = filenames->count; + simple_archiver_helper_32_bit_be(&u32); + if (fwrite(&u32, 1, 4, out_f) != 4) { + return SDAS_FAILED_TO_WRITE; + } + } + + // Iterate over files in list to write. + state->out_f = out_f; + if (simple_archiver_list_get(filenames, write_files_fn, state)) { + // Error occurred. + } + state->out_f = NULL; + + return SDAS_SUCCESS; +} diff --git a/src/archiver.h b/src/archiver.h new file mode 100644 index 0000000..4cff97d --- /dev/null +++ b/src/archiver.h @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `archiver.h` is the header for an interface to creating an archive file. + */ + +#ifndef SEODISPARATE_COM_SIMPLE_ARCHIVER_ARCHIVER_H_ +#define SEODISPARATE_COM_SIMPLE_ARCHIVER_ARCHIVER_H_ + +#include + +#include "data_structures/linked_list.h" +#include "parser.h" + +typedef struct SDArchiverState { + /* + */ + unsigned int flags; + const SDArchiverParsed *parsed; + FILE *out_f; +} SDArchiverState; + +enum SDArchiverStateReturns { + SDAS_SUCCESS = 0, + SDAS_HEADER_ALREADY_WRITTEN = 1, + SDAS_FAILED_TO_WRITE, + SDAS_NO_COMPRESSOR, + SDAS_NO_DECOMPRESSOR, + SDAS_INVALID_PARSED_STATE +}; + +SDArchiverState *simple_archiver_init_state(const SDArchiverParsed *parsed); +void simple_archiver_free_state(SDArchiverState **state); + +/// Returns zero on success. Otherwise one value from SDArchiverStateReturns +/// enum. +int simple_archiver_write_all(FILE *out_f, SDArchiverState *state, + const SDArchiverLinkedList *filenames); + +#endif diff --git a/src/data_structures/linked_list.c b/src/data_structures/linked_list.c index c4ad0dd..c114be7 100644 --- a/src/data_structures/linked_list.c +++ b/src/data_structures/linked_list.c @@ -178,7 +178,7 @@ int simple_archiver_list_remove_once(SDArchiverLinkedList *list, return 0; } -void *simple_archiver_list_get(SDArchiverLinkedList *list, +void *simple_archiver_list_get(const SDArchiverLinkedList *list, int (*data_check_fn)(void *, void *), void *user_data) { if (!list) { diff --git a/src/data_structures/linked_list.h b/src/data_structures/linked_list.h index fbebf8e..5b093c2 100644 --- a/src/data_structures/linked_list.h +++ b/src/data_structures/linked_list.h @@ -62,7 +62,7 @@ int simple_archiver_list_remove_once(SDArchiverLinkedList *list, /// Returns non-null on success. /// data_check_fn must return non-zero if the data passed to it is to be /// returned. -void *simple_archiver_list_get(SDArchiverLinkedList *list, +void *simple_archiver_list_get(const SDArchiverLinkedList *list, int (*data_check_fn)(void *, void *), void *user_data); diff --git a/src/main.c b/src/main.c index da67c9d..1492fa3 100644 --- a/src/main.c +++ b/src/main.c @@ -18,15 +18,16 @@ #include +#include "archiver.h" #include "parser.h" int print_list_fn(void *data, __attribute__((unused)) void *ud) { const SDArchiverFileInfo *file_info = data; if (file_info->link_dest == NULL) { - printf(" REGULAR FILE: %s\n", file_info->filename); + fprintf(stderr, " REGULAR FILE: %s\n", file_info->filename); } else { - printf(" SYMBOLIC LINK: %s -> %s\n", file_info->filename, - file_info->link_dest); + fprintf(stderr, " SYMBOLIC LINK: %s -> %s\n", file_info->filename, + file_info->link_dest); } return 0; } @@ -54,8 +55,25 @@ int main(int argc, const char **argv) { SDArchiverLinkedList *filenames = simple_archiver_parsed_to_filenames(&parsed); - puts("Filenames:"); + fprintf(stderr, "Filenames:\n"); simple_archiver_list_get(filenames, print_list_fn, NULL); + if ((parsed.flags & 1) == 0) { + FILE *file = fopen(parsed.filename, "wb"); + if (!file) { + fprintf(stderr, "ERROR: Failed to open \"%s\" for writing!\n", + parsed.filename); + return 2; + } + + __attribute__((cleanup(simple_archiver_free_state))) + SDArchiverState *state = simple_archiver_init_state(&parsed); + + if (simple_archiver_write_all(file, state, filenames) != SDAS_SUCCESS) { + fprintf(stderr, "Error during writing."); + } + fclose(file); + } + return 0; } diff --git a/src/parser.c b/src/parser.c index 37e174e..c59131e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -134,16 +134,23 @@ int list_remove_same_str_fn(void *data, void *ud) { } void simple_archiver_print_usage(void) { - puts("Usage flags:"); - puts("-c : create archive file"); - puts("-x : extract archive file"); - puts("-f : filename to work on"); - puts("--compressor : requires --decompressor"); - puts("--decompressor : requires --compressor"); - puts("--overwrite-create : allows overwriting an archive file"); - puts("-- : specifies remaining arguments are files to archive/extract"); - puts("If creating archive file, remaining args specify files to archive."); - puts("If extracting archive file, remaining args specify files to extract."); + fprintf(stderr, "Usage flags:\n"); + fprintf(stderr, "-c : create archive file\n"); + fprintf(stderr, "-x : extract archive file\n"); + fprintf(stderr, "-f : filename to work on\n"); + fprintf(stderr, + "--compressor : requires --decompressor\n"); + fprintf(stderr, + "--decompressor : requires --compressor\n"); + fprintf(stderr, "--overwrite-create : allows overwriting an archive file\n"); + fprintf(stderr, + "-- : specifies remaining arguments are files to archive/extract\n"); + fprintf( + stderr, + "If creating archive file, remaining args specify files to archive.\n"); + fprintf( + stderr, + "If extracting archive file, remaining args specify files to extract.\n"); } SDArchiverParsed simple_archiver_create_parsed(void) { @@ -181,7 +188,10 @@ int simple_archiver_parse_args(int argc, const char **argv, while (argc > 0) { if (!is_remaining_args) { - if (strcmp(argv[0], "-c") == 0) { + if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) { + simple_archiver_print_usage(); + exit(0); + } else if (strcmp(argv[0], "-c") == 0) { // unset first bit. out->flags &= 0xFFFFFFFE; } else if (strcmp(argv[0], "-x") == 0) { @@ -348,7 +358,7 @@ SDArchiverLinkedList *simple_archiver_parsed_to_filenames( strcmp(dir_entry->d_name, "..") == 0) { continue; } - printf("dir entry in %s is %s\n", next, dir_entry->d_name); + fprintf(stderr, "dir entry in %s is %s\n", next, dir_entry->d_name); int combined_size = strlen(next) + strlen(dir_entry->d_name) + 2; char *combined_path = malloc(combined_size); snprintf(combined_path, combined_size, "%s/%s", next,