diff options
| -rw-r--r-- | CC4Group.kdev4 | 3 | ||||
| -rw-r--r-- | CMakeLists.txt | 32 | ||||
| -rw-r--r-- | examples/c4cat.c | 128 | ||||
| -rw-r--r-- | examples/c4cat_dyn.c | 145 | ||||
| -rw-r--r-- | examples/c4copy.c | 36 | ||||
| -rw-r--r-- | examples/c4info.c | 45 | ||||
| -rw-r--r-- | examples/c4ls.c | 87 | ||||
| -rw-r--r-- | examples/c4mkdir.c | 49 | ||||
| -rw-r--r-- | examples/c4rm.c | 49 | ||||
| -rw-r--r-- | examples/c4touch.c | 61 | ||||
| -rw-r--r-- | examples/unc4group.c | 46 | ||||
| -rw-r--r-- | main.c | 376 | ||||
| -rw-r--r-- | src/GenericList.h (renamed from GenericList.h) | 0 | ||||
| -rw-r--r-- | src/c4groupentrycore.c | 24 | ||||
| -rw-r--r-- | src/c4groupentrycore.h | 26 | ||||
| -rw-r--r-- | src/c4groupheader.c | 40 | ||||
| -rw-r--r-- | src/c4groupheader.h | 27 | ||||
| -rw-r--r-- | src/cc4group.c | 1620 | ||||
| -rw-r--r-- | src/cc4group.h | 97 |
19 files changed, 2511 insertions, 380 deletions
diff --git a/CC4Group.kdev4 b/CC4Group.kdev4 deleted file mode 100644 index 063ba2a..0000000 --- a/CC4Group.kdev4 +++ /dev/null @@ -1,3 +0,0 @@ -[Project] -Name=CC4Group -Manager=KDevCMakeManager diff --git a/CMakeLists.txt b/CMakeLists.txt index b7bb5cc..47da632 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,36 @@ endif() find_package(ZLIB REQUIRED) -add_executable(cc4group main.c) +set(cc4group_SRC + src/cc4group.c + src/c4groupheader.c + src/c4groupentrycore.c +) + +add_library(cc4group STATIC ${cc4group_SRC}) target_link_libraries(cc4group PRIVATE ZLIB::ZLIB) target_include_directories(cc4group PRIVATE ZLIB::ZLIB) +set_property(TARGET cc4group PROPERTY POSITION_INDEPENDENT_CODE ON) + +add_library(cc4group_dyn SHARED ${cc4group_SRC}) +target_link_libraries(cc4group_dyn PRIVATE ZLIB::ZLIB) +target_include_directories(cc4group_dyn PRIVATE ZLIB::ZLIB) + +include_directories(src) + +function(example name) + add_executable(${name} examples/${name}.c) + target_link_libraries(${name} PRIVATE cc4group) +endfunction() + +example(unc4group) +example(c4cat) +example(c4copy) +example(c4info) +example(c4ls) +example(c4touch) +example(c4rm) +example(c4mkdir) + +add_executable(c4cat_dyn examples/c4cat_dyn.c) +target_link_libraries(c4cat_dyn PRIVATE -ldl) diff --git a/examples/c4cat.c b/examples/c4cat.c new file mode 100644 index 0000000..04f51b6 --- /dev/null +++ b/examples/c4cat.c @@ -0,0 +1,128 @@ +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> + +#include "cc4group.h" + +bool catNormalFile(const char* const path, const off_t size) +{ + int file = open(path, O_RDONLY); + if(file == -1) + { + fprintf(stderr, "ERROR: Reading \"%s\": %s\n", path, strerror(errno)); + return false; + } + + void* mappedFile = mmap(NULL, size, PROT_READ, MAP_PRIVATE, file, 0); + + if(close(file) == -1) + { + fprintf(stderr, "ERROR: Closing \"%s\": %s\n", path, strerror(errno)); + } + + if(mappedFile == MAP_FAILED) + { + fprintf(stderr, "ERROR: Mapping \"%s\" for reading: %s\n", path, strerror(errno)); + return false; + } + + if(write(STDOUT_FILENO, mappedFile, size) != size) + { + fprintf(stderr, "ERROR: Writing file contents of \"%s\" to stdout: %s\n", path, strerror(errno)); + } + + if(munmap(mappedFile, size) == -1) + { + fprintf(stderr, "ERROR: Unmapping \"%s\": %s\n", path, strerror(errno)); + } + + return true; +} + +bool catFileWithPathsAndSize(const char* const completePath, const char* const groupPath, off_t fileSize) +{ + if(strcmp(groupPath, completePath) == 0) + { + return catNormalFile(completePath, fileSize); + } + + const char* pathInGroup = completePath + strlen(groupPath) + 1; + CC4Group* group = cc4group.new(); + if(!cc4group.openExisting(group, groupPath)) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", groupPath, cc4group.getErrorMessage(group)); + return false; + } + + const void* data; + size_t size; + bool success = cc4group.getEntryData(group, pathInGroup, &data, &size); + + if(success) + { + if(write(STDOUT_FILENO, data, size) != (ssize_t)size) + { + fprintf(stderr, "ERROR: Writing file contents of \"%s\" to stdout: %s\n", completePath, strerror(errno)); + } + } + else + { + fprintf(stderr, "ERROR: Reading \"%s\" from \"%s\": %s\n", pathInGroup, groupPath, strerror(ENOENT)); + } + + cc4group.delete(group); + + return success; +} + +bool catFile(const char* const completePath) +{ + struct stat st; + + char* groupPath = strdup(completePath); + char* slash; + for(slash = groupPath + strlen(groupPath); slash != NULL; slash = strrchr(groupPath, '/')) + { + *slash = '\0'; + if(stat(groupPath, &st) == 0 && !S_ISDIR(st.st_mode)) + { + break; + } + } + + bool success; + if(slash == NULL) + { + fprintf(stderr, "ERROR: Reading \"%s\": %s\n", completePath, strerror(ENOENT)); + success = false; + } + else + { + success = catFileWithPathsAndSize(completePath, groupPath, st.st_size); + } + + free(groupPath); + return success; +} + +int main(int argc, char* argv[]) +{ + if(argc < 2) + { + fprintf(stderr, "USAGE: %s <entry path>...\n", argv[0]); + return EXIT_FAILURE; + } + + bool success = true; + for(int i = 1; i < argc; ++i) + { + success &= catFile(argv[i]); + } + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4cat_dyn.c b/examples/c4cat_dyn.c new file mode 100644 index 0000000..92da979 --- /dev/null +++ b/examples/c4cat_dyn.c @@ -0,0 +1,145 @@ +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> + +#include <dlfcn.h> +#include <assert.h> + +#define CC4GROUP_DYNAMIC_LOAD +#include "cc4group.h" +#undef CC4GROUP_DYNAMIC_LOAD + +CC4Group_API* cc4group_dyn; +#define cc4group (*cc4group_dyn) + +bool catNormalFile(const char* const path, const off_t size) +{ + int file = open(path, O_RDONLY); + if(file == -1) + { + fprintf(stderr, "ERROR: Reading \"%s\": %s\n", path, strerror(errno)); + return false; + } + + void* mappedFile = mmap(NULL, size, PROT_READ, MAP_PRIVATE, file, 0); + + if(close(file) == -1) + { + fprintf(stderr, "ERROR: Closing \"%s\": %s\n", path, strerror(errno)); + } + + if(mappedFile == MAP_FAILED) + { + fprintf(stderr, "ERROR: Mapping \"%s\" for reading: %s\n", path, strerror(errno)); + return false; + } + + if(write(STDOUT_FILENO, mappedFile, size) != size) + { + fprintf(stderr, "ERROR: Writing file contents of \"%s\" to stdout: %s\n", path, strerror(errno)); + } + + if(munmap(mappedFile, size) == -1) + { + fprintf(stderr, "ERROR: Unmapping \"%s\": %s\n", path, strerror(errno)); + } + + return true; +} + +bool catFileWithPathsAndSize(const char* const completePath, const char* const groupPath, off_t fileSize) +{ + if(strcmp(groupPath, completePath) == 0) + { + return catNormalFile(completePath, fileSize); + } + + const char* pathInGroup = completePath + strlen(groupPath) + 1; + CC4Group* group = cc4group.new(); + if(!cc4group.openExisting(group, groupPath)) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", groupPath, cc4group.getErrorMessage(group)); + return false; + } + + const void* data; + size_t size; + bool success = cc4group.getEntryData(group, pathInGroup, &data, &size); + + if(success) + { + if(write(STDOUT_FILENO, data, size) != (ssize_t)size) + { + fprintf(stderr, "ERROR: Writing file contents of \"%s\" to stdout: %s\n", completePath, strerror(errno)); + } + } + else + { + fprintf(stderr, "ERROR: Reading \"%s\" from \"%s\": %s\n", pathInGroup, groupPath, strerror(ENOENT)); + } + + cc4group.delete(group); + + return success; +} + +bool catFile(const char* const completePath) +{ + struct stat st; + + char* groupPath = strdup(completePath); + char* slash; + for(slash = groupPath + strlen(groupPath); slash != NULL; slash = strrchr(groupPath, '/')) + { + *slash = '\0'; + if(stat(groupPath, &st) == 0 && !S_ISDIR(st.st_mode)) + { + break; + } + } + + bool success; + if(slash == NULL) + { + fprintf(stderr, "ERROR: Reading \"%s\": %s\n", completePath, strerror(ENOENT)); + success = false; + } + else + { + success = catFileWithPathsAndSize(completePath, groupPath, st.st_size); + } + + free(groupPath); + return success; +} + +int main(int argc, char* argv[]) +{ + void* dl = dlopen("./libcc4group_dyn.so", RTLD_LAZY); + assert(dl); + + dlerror(); + cc4group_dyn = dlsym(dl, "cc4group"); + assert(!dlerror()); + + if(argc < 2) + { + fprintf(stderr, "USAGE: %s <entry path>...\n", argv[0]); + return EXIT_FAILURE; + } + + bool success = true; + for(int i = 1; i < argc; ++i) + { + success &= catFile(argv[i]); + } + + dlclose(dl); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4copy.c b/examples/c4copy.c new file mode 100644 index 0000000..35e201b --- /dev/null +++ b/examples/c4copy.c @@ -0,0 +1,36 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "cc4group.h" + +int main(int argc, char* argv[]) +{ + if(argc != 3) + { + fprintf(stderr, "USAGE: %s <group> <target>\n", argv[0]); + return EXIT_FAILURE; + } + + bool success = true; + + CC4Group* group = cc4group.new(); + if(!cc4group.openExisting(group, argv[1])) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + + success = false; + } + else + { + success = cc4group.save(group, argv[2]); + + if(!success) + { + fprintf(stderr, "ERROR: Can not save group file to \"%s\": %s\n", argv[2], cc4group.getErrorMessage(group)); + } + } + + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4info.c b/examples/c4info.c new file mode 100644 index 0000000..6fa63fe --- /dev/null +++ b/examples/c4info.c @@ -0,0 +1,45 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#include "cc4group.h" + +const char* formatTime(int32_t time) +{ + time_t tTime = time; + static char ret[128]; + strftime(ret, sizeof(ret), "%c", localtime(&tTime)); + return ret; +} + +int main(int argc, char* argv[]) +{ + if(argc != 2) + { + fprintf(stderr, "USAGE: %s <group>\n", argv[0]); + return EXIT_FAILURE; + } + + CC4Group* group = cc4group.new(); + bool success = cc4group.openExisting(group, argv[1]); + if(!success) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + } + else + { + CC4Group_EntryInfo info; + cc4group.getEntryInfo(group, "", &info); + + puts(argv[1]); + printf("Created: %s\n", formatTime(info.modified)); + printf("Author: %s\n", info.author); + if(info.official) + { + puts("Official"); + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4ls.c b/examples/c4ls.c new file mode 100644 index 0000000..4324d9b --- /dev/null +++ b/examples/c4ls.c @@ -0,0 +1,87 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <locale.h> + +#include "cc4group.h" + +const char* formatTime(int32_t time) +{ + time_t tTime = time; + static char ret[128]; + strftime(ret, sizeof(ret), "%c", localtime(&tTime)); + return ret; +} + +const char* formatSize(size_t size) +{ + static char ret[128]; + if(size < 1000) + { + snprintf(ret, 128, "%4zd", size); + return ret; + } + + static const char prefixes[] = " KMGTPE"; + + const char* prefix = prefixes; + size_t fract = 0; + + while(size >= 1000 && *(prefix + 1) != '\0') + { + ++prefix; + fract = size % 1000; + size /= 1000; + } + + if(size < 10) + { + snprintf(ret, 128, "%1.1f%c", (float)size + (float)fract / 1000, *prefix); + } + else + { + snprintf(ret, 128, "%3zd%c", size, *prefix); + } + + return ret; +} + +int main(int argc, char* argv[]) +{ + if(argc != 2 && argc != 3) + { + fprintf(stderr, "USAGE: %s <group> [subgroup]\n", argv[0]); + return EXIT_FAILURE; + } + + CC4Group* group = cc4group.new(); + bool success = cc4group.openExisting(group, argv[1]); + if(!success) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + } + else + { + CC4Group_EntryInfo* infos; + size_t entries; + + success = cc4group.getEntryInfos(group, argc == 3 ? argv[2] : NULL, &infos, &entries); + + if(!success) + { + fprintf(stderr, "ERROR: Can not list group entries: %s\n", cc4group.getErrorMessage(group)); + } + else + { + for(size_t i = 0; i < entries; ++i) + { + printf("%c %s %s %s\t%s\n", infos[i].directory ? 'd' : infos[i].executable ? 'x' : ' ', formatTime(infos[i].modified), formatSize(infos[i].totalSize), infos[i].fileName, infos[i].author); + } + + free(infos); + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4mkdir.c b/examples/c4mkdir.c new file mode 100644 index 0000000..5677125 --- /dev/null +++ b/examples/c4mkdir.c @@ -0,0 +1,49 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "cc4group.h" + +int main(int argc, char* argv[]) +{ + bool recursive = argc == 4 && strcmp(argv[1], "-r") == 0; + + if(argc != 3 && !recursive) + { + fprintf(stderr, "USAGE: %s [-r] <group> <entry>\n", argv[0]); + return EXIT_FAILURE; + } + + if(recursive) + { + ++argv; + } + + CC4Group* group = cc4group.new(); + bool success = cc4group.openExisting(group, argv[1]); + if(!success) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + } + else + { + success = cc4group.deleteEntry(group, argv[2], recursive); + + if(!success) + { + fprintf(stderr, "ERROR: Can not delete group entry \"%s\": %s\n", argv[2], cc4group.getErrorMessage(group)); + } + else + { + success = cc4group.saveOverwrite(group, argv[1]); + + if(!success) + { + fprintf(stderr, "ERROR: Can not save group file \"%s\": %s\n", argv[2], cc4group.getErrorMessage(group)); + } + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4rm.c b/examples/c4rm.c new file mode 100644 index 0000000..5677125 --- /dev/null +++ b/examples/c4rm.c @@ -0,0 +1,49 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "cc4group.h" + +int main(int argc, char* argv[]) +{ + bool recursive = argc == 4 && strcmp(argv[1], "-r") == 0; + + if(argc != 3 && !recursive) + { + fprintf(stderr, "USAGE: %s [-r] <group> <entry>\n", argv[0]); + return EXIT_FAILURE; + } + + if(recursive) + { + ++argv; + } + + CC4Group* group = cc4group.new(); + bool success = cc4group.openExisting(group, argv[1]); + if(!success) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + } + else + { + success = cc4group.deleteEntry(group, argv[2], recursive); + + if(!success) + { + fprintf(stderr, "ERROR: Can not delete group entry \"%s\": %s\n", argv[2], cc4group.getErrorMessage(group)); + } + else + { + success = cc4group.saveOverwrite(group, argv[1]); + + if(!success) + { + fprintf(stderr, "ERROR: Can not save group file \"%s\": %s\n", argv[2], cc4group.getErrorMessage(group)); + } + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/c4touch.c b/examples/c4touch.c new file mode 100644 index 0000000..5d0000d --- /dev/null +++ b/examples/c4touch.c @@ -0,0 +1,61 @@ +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <time.h> + +#include "cc4group.h" + +int main(int argc, char* argv[]) +{ + if(argc != 2 && argc != 3) + { + fprintf(stderr, "USAGE: %s <group> [author]\n", argv[0]); + return EXIT_FAILURE; + } + + CC4Group* group = cc4group.new(); + bool success, exists; + struct stat st; + if(stat(argv[1], &st) == 0) + { + success = cc4group.openExisting(group, argv[1]); + exists = true; + } + else + { + success = cc4group.create(group); + exists = false; + } + if(!success) + { + const char* error = cc4group.getErrorMessage(group); + if(exists) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], error); + } + else + { + fprintf(stderr, "ERROR: Can not initialize empty group file: %s\n", error); + } + } + + if(success) + { + cc4group.setCreation(group, time(NULL), NULL, false); + + if(argc == 3) + { + cc4group.setMaker(group, argv[2], NULL, true); + } + + success = (exists ? cc4group.saveOverwrite : cc4group.save)(group, argv[1]); + + if(!success) + { + fprintf(stderr, "ERROR: Can not save group file to \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/unc4group.c b/examples/unc4group.c new file mode 100644 index 0000000..8d08eb3 --- /dev/null +++ b/examples/unc4group.c @@ -0,0 +1,46 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "cc4group.h" + +int main(int argc, char* argv[]) +{ + if(argc != 3 && argc != 4) + { + fprintf(stderr, "USAGE: %s <group> [group entry] <target>\n", argv[0]); + return EXIT_FAILURE; + } + + CC4Group* group = cc4group.new(); + if(!cc4group.openExisting(group, argv[1])) + { + fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group)); + + cc4group.delete(group); + return EXIT_FAILURE; + } + + bool success; + if(argc == 3) + { + success = cc4group.extractAll(group, argv[2]); + } + else if(argc == 4) + { + success = cc4group.extractSingle(group, argv[2], argv[3]); + } + else + { + fprintf(stderr, "ERROR: Can't get here\n"); + success = false; + } + + if(!success) + { + fprintf(stderr, "ERROR: Extracting the desired file/group: %s\n", cc4group.getErrorMessage(group)); + } + + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} @@ -1,376 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <string.h> -#include <errno.h> -#include <sys/mman.h> -#include <sys/time.h> -#include <time.h> - -#include <zlib.h> - -#include <assert.h> - -#include "GenericList.h" - -#define C4GroupMaxMaker 30 -#define C4GroupMaxPassword 30 - -typedef struct { - char id[25]; - uint8_t Reserved1[3]; - int32_t Ver1, Ver2; - int32_t Entries; - char Maker[C4GroupMaxMaker + 2]; - char Password[C4GroupMaxPassword + 2]; - int32_t Creation; - int32_t Original; - uint8_t Reserved2[92]; -} __attribute__((__packed__)) C4GroupHeader; - -typedef struct { - char FileName[257]; - uint8_t Reserved1[3]; - int32_t Packed; - int32_t Directory; - int32_t Size; - int32_t Reserved2; - int32_t Offset; - int32_t Modified; - uint8_t HasCRC; - uint32_t CRC; - uint8_t Executable; - uint8_t Reserved3[26]; -} __attribute__((__packed__)) C4GroupEntryCore; - -struct list_GroupEntryList; -typedef struct { - C4GroupEntryCore core; - uint8_t* data; - struct list_GroupEntryList* children; -} C4GroupEntryData; - -LIST_AUTO(C4GroupEntryData, GroupEntryList) -#define ForeachGroupEntry(list) LIST_FOREACH(GroupEntryList, list, entry) - -off_t fileSizeFd(int fd) -{ - struct stat st; - - if(fstat(fd, &st) == -1) - { - return -1; - } - - return st.st_size; -} - -void memScrambleHeader(uint8_t* data) -{ - // XOR deface - for(size_t i = 0; i < sizeof(C4GroupHeader); i++) - data[i] ^= 237; - // byte swap - for(size_t i = 0; i + 2 < sizeof(C4GroupHeader); i += 3) - { - uint8_t temp = data[i]; - data[i] = data[i + 2]; - data[i + 2] = temp; - } -} - -void buildChildren(C4GroupEntryData* entry) -{ - C4GroupHeader* header = (C4GroupHeader*)entry->data; - - entry->children = GroupEntryListNew(); - - uint8_t* data = entry->data + sizeof(C4GroupHeader); - uint8_t* childData = entry->data + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; - - for(size_t i = 0; i < (size_t)header->Entries; ++i) - { - C4GroupEntryCore* core = (C4GroupEntryCore*)(data + sizeof(C4GroupEntryCore) * i); - C4GroupEntryData* childEntry = &GroupEntryListAppend(entry->children, (C4GroupEntryData){.core = *core, .data = childData + core->Offset, .children = NULL})->value; - - if(core->Directory) - { - memScrambleHeader(childEntry->data); - buildChildren(childEntry); - } - } -} - -const char* formatTime(int32_t time) -{ - time_t tTime = time; - static char ret[128]; - strftime(ret, sizeof(ret), "%c", localtime(&tTime)); - return ret; -} - -void handleChildren(GroupEntryList* entries, size_t indent, const char* path) -{ - size_t pathLen = strlen(path); - char* targetPath = malloc(pathLen + 260 + 1); - - strcpy(targetPath, path); - strcpy(targetPath + pathLen++, "/"); - - if(mkdir(path, 0755) == -1) - { - fprintf(stderr, "ERROR: Creating target directory \"%s\": %s\n", path, strerror(errno)); - goto ret; - } - - ForeachGroupEntry(entries) - { - for(size_t i = 0; i < indent; ++i) - { - putchar('\t'); - } - - strcpy(targetPath + pathLen, entry->value.core.FileName); - -// printf("%s\t%s\t%d B\n", targetPath, formatTime(entry->value.core.Modified), entry->value.core.Size); - - if(!entry->value.core.Directory) - { - int file = open(targetPath, O_WRONLY | O_CREAT, entry->value.core.Executable ? 0755 : 0644); - if(file == -1) - { - fprintf(stderr, "ERROR: Creating target file \"%s\": %s\n", targetPath, strerror(errno)); - goto ret; - } - write(file, entry->value.data, entry->value.core.Size); - - if(close(file) == -1) - { - fprintf(stderr, "ERROR: Closing file \"%s\": %s\n", targetPath, strerror(errno)); - } - - struct timeval tv[2] = {{.tv_usec = 0, .tv_sec = entry->value.core.Modified}, {.tv_usec = 0, .tv_sec = entry->value.core.Modified}}; - if(utimes(targetPath, tv) == -1) - { - fprintf(stderr, "ERROR: Setting modification time for \"%s\": %s\n", targetPath, strerror(errno)); - } - } - - if(entry->value.core.Directory) - { - handleChildren(entry->value.children, indent + 1, targetPath); - } - } - -ret: - free(targetPath); -} - -void deleteChildren(GroupEntryList* entries) -{ - ForeachGroupEntry(entries) - { - if(entry->value.core.Directory) - { - deleteChildren(entry->value.children); - } - } - - GroupEntryListDestroy(entries); -} - -int main(int argc, char* argv[]) -{ - if(argc != 3) - { - fprintf(stderr, "USAGE: %s <group> <target>\n", argv[0]); - return EXIT_FAILURE; - } - - int file = open(argv[1], O_RDONLY); - if(file == -1) - { - fprintf(stderr, "ERROR: Opening file \"%s\": %s\n", argv[1], strerror(errno)); - return EXIT_FAILURE; - } - - __off_t size = fileSizeFd(file); - if(size == -1) - { - fprintf(stderr, "ERROR: Getting file size: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - else - { - printf("Size: %ld\n", size); - } - - uint8_t* mappedFile = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0); - - if(mappedFile == MAP_FAILED) - { - fprintf(stderr, "ERROR: Mapping file: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - if(close(file) == -1) - { - fprintf(stderr, "ERROR: Closing file: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - if((mappedFile[0] != 0x1e && mappedFile[0] != 0x1f) || (mappedFile[1] != 0x8c && mappedFile[1] != 0x8b)) - { - fprintf(stderr, "ERROR: The file is not a valid group file. Magic bytes don't match.\n"); - return EXIT_FAILURE; - } - - mappedFile[0] = 0x1f; - mappedFile[1] = 0x8b; - - z_stream strm = { - .zalloc = NULL, - .zfree = NULL, - .opaque = NULL, - .next_in = mappedFile, - .avail_in = size - }; - - int ret = inflateInit2(&strm, 15 + 16); // window size 15 + automatic gzip - - if(ret != Z_OK) - { - fprintf(stderr, "ERROR: inflateInit2: %s\n", zError(ret)); - return EXIT_FAILURE; - } - - size_t currentSize = sizeof(C4GroupHeader); - C4GroupHeader* header = malloc(currentSize); - - strm.next_out = (Bytef*)header; - strm.avail_out = currentSize; - - ret = inflate(&strm, Z_SYNC_FLUSH); - if(ret != Z_OK) - { - fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret)); - return EXIT_FAILURE; - } - - memScrambleHeader((uint8_t*)header); - - printf("Version %d.%d\n", header->Ver1, header->Ver2); - printf("%d Entries\n", header->Entries); - printf("%sOriginal\n", header->Original == 1234567 ? "" : "Not "); - printf("Created %s\n", formatTime(header->Creation)); - puts(header->id); - puts(header->Maker); - - assert(header->Ver1 == 1); - assert(header->Ver2 == 2); - - currentSize += sizeof(C4GroupEntryCore) * header->Entries; - header = realloc(header, currentSize); - C4GroupEntryCore* cores = (C4GroupEntryCore*)((void*)(header) + sizeof(C4GroupHeader)); - - GroupEntryList* entries = GroupEntryListNew(); - - strm.next_out = (Bytef*)cores; - strm.avail_out = sizeof(C4GroupEntryCore) * header->Entries; - - ret = inflate(&strm, Z_SYNC_FLUSH); - if(ret != Z_OK) - { - fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret)); - return EXIT_FAILURE; - } - - C4GroupEntryCore* last = cores + header->Entries - 1; - size_t uncompressedSize = last->Offset + last->Size; - - currentSize += uncompressedSize; - -#define TMP_FILE "cc4group.tmp" - int tmpFile = open(TMP_FILE, O_CREAT | O_RDWR | O_TRUNC | O_EXCL, 0600); - if(tmpFile == -1) - { - fprintf(stderr, "ERROR: Opening tmp file \"%s\": %s\n", "cc4group.tmp", strerror(errno)); - return EXIT_FAILURE; - } - - if(unlink(TMP_FILE) == -1) - { - fprintf(stderr, "ERROR: Deleting tmp file \"%s\". Manual deletion is required: %s\n", TMP_FILE, strerror(errno)); - } -#undef TMP_FILE - - // allocate file size - // https://gist.github.com/marcetcheverry/991042 - if(lseek(tmpFile, currentSize - 1, SEEK_SET) == -1) - { - fprintf(stderr, "ERROR: Seeking the tmp file to the end: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - if(write(tmpFile, "", 1) == -1) - { - fprintf(stderr, "ERROR: Writing to the tmp file's end: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - uint8_t* mmappedHeader = mmap(NULL, currentSize, PROT_READ | PROT_WRITE, MAP_SHARED, tmpFile, 0); - if(mmappedHeader == MAP_FAILED) - { - fprintf(stderr, "ERROR: Mapping tmp file: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - if(close(tmpFile) == -1) - { - fprintf(stderr, "ERROR: Closing tmp file \"cc4group.tmp\": %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - uint8_t* data = (void*)(mmappedHeader) + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; - - // write alredy decompressed header and cores into the file - memcpy(mmappedHeader, header, data - mmappedHeader); - - strm.next_out = data; - strm.avail_out = uncompressedSize; - - ret = inflate(&strm, Z_SYNC_FLUSH); - if(ret != Z_STREAM_END) - { - fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret)); - return EXIT_FAILURE; - } - - inflateEnd(&strm); - - if(munmap(mappedFile, size) == -1) - { - fprintf(stderr, "ERROR: Unmapping file: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - C4GroupEntryData root = {.data = (uint8_t*)mmappedHeader, .children = NULL}; - buildChildren(&root); - - handleChildren(root.children, 0, argv[2]); - - deleteChildren(entries); - - free(header); - - if(munmap(mmappedHeader, size) == -1) - { - fprintf(stderr, "ERROR: Unmapping tmp file: %s\n", strerror(errno)); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/GenericList.h b/src/GenericList.h index 0679bb7..0679bb7 100644 --- a/GenericList.h +++ b/src/GenericList.h diff --git a/src/c4groupentrycore.c b/src/c4groupentrycore.c new file mode 100644 index 0000000..8b483cc --- /dev/null +++ b/src/c4groupentrycore.c @@ -0,0 +1,24 @@ +#include "c4groupentrycore.h" +#include <string.h> +#include <time.h> + +void C4GroupEntryCore_init(C4GroupEntryCore* const this) +{ + memset(this->FileName, 0, sizeof(this->FileName)); + memset(this->Reserved1, 0, sizeof(this->Reserved1)); + this->Packed = 0; + this->Directory = 0; + this->Size = 0; + this->Reserved2 = 0; + this->Offset = 0; + this->Modified = time(NULL); + this->HasCRC = C4GroupEntryCore_NoCRC; + this->CRC = 0; + this->Executable = 0; + memset(this->Reserved3, 0, sizeof(this->Reserved3)); +} + +void C4GroupEntryCore_setFileName(C4GroupEntryCore* const this, const char* const fileName) +{ + strncpy(this->FileName, fileName, sizeof(this->FileName) - 1); +} diff --git a/src/c4groupentrycore.h b/src/c4groupentrycore.h new file mode 100644 index 0000000..c77a5d1 --- /dev/null +++ b/src/c4groupentrycore.h @@ -0,0 +1,26 @@ +#pragma once +#include <stdint.h> + +typedef enum { + C4GroupEntryCore_NoCRC = 0, + C4GroupEntryCore_ContentsCRC = 1, + C4GroupEntryCore_ContentsFileNameCRC = 2 +} C4GroupEntryCore_HasCRC; + +typedef struct { + char FileName[257]; + uint8_t Reserved1[3]; + int32_t Packed; + int32_t Directory; + int32_t Size; + int32_t Reserved2; + int32_t Offset; + int32_t Modified; + uint8_t HasCRC; + uint32_t CRC; + uint8_t Executable; + uint8_t Reserved3[26]; +} __attribute__((__packed__)) C4GroupEntryCore; + +void C4GroupEntryCore_init(C4GroupEntryCore* const this); +void C4GroupEntryCore_setFileName(C4GroupEntryCore* const this, const char* const fileName); diff --git a/src/c4groupheader.c b/src/c4groupheader.c new file mode 100644 index 0000000..e06155c --- /dev/null +++ b/src/c4groupheader.c @@ -0,0 +1,40 @@ +#include "c4groupheader.h" +#include <string.h> +#include <time.h> + +void C4GroupHeader_init(C4GroupHeader* const this) +{ + // id is not cleared because the id string exactly fits the size + strcpy(this->id, C4GroupId); + memset(this->Reserved1, 0, sizeof(this->Reserved1)); + memset(this->Reserved2, 0, sizeof(this->Reserved2)); + memset(this->Password, 0, sizeof(this->Password)); + + this->Ver1 = 1; + this->Ver2 = 2; + this->Entries = 0; + C4GroupHeader_setMaker(this, "New C4Group"); + this->Maker[C4GroupMaxMaker + 1] = '\0'; + C4GroupHeader_setCreation(this, time(NULL)); + C4GroupHeader_setOfficial(this, false); +} + +void C4GroupHeader_setMaker(C4GroupHeader* const this, const char* const maker) +{ + strncpy(this->Maker, maker, C4GroupMaxMaker + 1); +} + +void C4GroupHeader_setCreation(C4GroupHeader* const this, int32_t const creation) +{ + this->Creation = creation; +} + +void C4GroupHeader_setOfficial(C4GroupHeader* const this, const bool official) +{ + this->Official = official ? C4GroupOfficial : 0; +} + +bool C4GroupHeader_isOfficial(const C4GroupHeader* const this) +{ + return this->Official == C4GroupOfficial; +} diff --git a/src/c4groupheader.h b/src/c4groupheader.h new file mode 100644 index 0000000..1faa912 --- /dev/null +++ b/src/c4groupheader.h @@ -0,0 +1,27 @@ +#pragma once +#include <stdint.h> +#include <stdbool.h> + +#define C4GroupMaxMaker 30 +#define C4GroupMaxPassword 30 + +#define C4GroupId "RedWolf Design GrpFolder" +#define C4GroupOfficial 1234567 + +typedef struct { + char id[25]; + uint8_t Reserved1[3]; + int32_t Ver1, Ver2; + int32_t Entries; + char Maker[C4GroupMaxMaker + 2]; + char Password[C4GroupMaxPassword + 2]; // also reserved + int32_t Creation; + int32_t Official; + uint8_t Reserved2[92]; +} __attribute__((__packed__)) C4GroupHeader; + +void C4GroupHeader_init(C4GroupHeader* const this); +void C4GroupHeader_setMaker(C4GroupHeader* const this, const char* const maker); +bool C4GroupHeader_isOfficial(const C4GroupHeader* const this); +void C4GroupHeader_setOfficial(C4GroupHeader* const this, bool const official); +void C4GroupHeader_setCreation(C4GroupHeader* const this, int32_t const creation); diff --git a/src/cc4group.c b/src/cc4group.c new file mode 100644 index 0000000..043efda --- /dev/null +++ b/src/cc4group.c @@ -0,0 +1,1620 @@ +// TODO? Error handling of lists? + +#define _POSIX_C_SOURCE 200809L +#define _GNU_SOURCE + +#include "cc4group.h" +#include "c4groupheader.h" +#include "c4groupentrycore.h" +#include "GenericList.h" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <time.h> +#include <assert.h> + +#include <stdio.h> + +#include "zlib.h" + +#define SET_ERROR(errorCauser, errorCode, errorFormatter, data) do { this->error.code = errorCode; this->error.formatter.formatter = errorFormatter; this->error.causer = errorCauser; this->error.method = __func__; } while(0) +#define SET_MESSAGE_ERROR(message) SET_ERROR(message, 1337, cc4group_messageFormatter, NULL) +#define SET_MALFORMED_MESSAGE_ERROR(message) SET_MESSAGE_ERROR("The group file is malformed: " message) +#define SET_ERRNO_ERROR(causer) SET_ERROR(causer, errno, cc4group_strerrorFormatter, NULL) +#define SET_ZERROR_ERROR(causer, error) SET_ERROR(causer, error, cc4group_zerrorFormatter, (void*)errno) + +static bool cc4group_openExisting(CC4Group* const this, const char* const path); +static void cc4group_delete(CC4Group* const this); + +// extraction to disk +static bool cc4group_extractAll(CC4Group* const this, const char* const targetPath); +static bool cc4group_extractSingle(CC4Group* const this, const char* const entryPath, const char* const targetPath); + +// the group owns the data pointed to. the pointer is valid until the group destructor is called +static bool cc4group_getEntryData(const CC4Group* const this, const char* const entryPath, const void** const data, size_t* size); + +#define C4GroupMagic1 0x1e +#define C4GroupMagic2 0x8c + +struct list_GroupEntryList; +typedef struct C4GroupEntryData_t { + C4GroupEntryCore core; + union { + uint8_t* data; + // if the entry is a directory, the beginning of the data contains the header + C4GroupHeader* header; + }; + struct list_GroupEntryList* children; + + struct C4GroupEntryData_t* parent; +} C4GroupEntryData; + +LIST_AUTO(C4GroupEntryData, GroupEntryList) +#define ForeachGroupEntry(list) LIST_FOREACH(GroupEntryList, list, entry) + +LIST_AUTO(CC4Group_CleanupJob, CleanUpJobList) +#define ForeachCleanupJob(list) LIST_FOREACH(CleanUpJobList, list, job) + +typedef char* (*ErrorFormatter)(int32_t const code, const char* const method, const char* const causer, void* const data); + +typedef struct { + void* data; + ErrorFormatter formatter; +} ErrorFormatterData; + +struct CC4Group_t { + uint8_t* uncompressedData; + size_t uncompressedSize; + const char* path; + const char* subPath; + C4GroupEntryData root; + C4GroupEntryData realRoot; + + CleanUpJobList* cleanupJobs; + + struct { + int32_t code; + const char* method; + const char* causer; + ErrorFormatterData formatter; + + char* lastFormattedMessage; + } error; +}; + +static const C4GroupEntryData* cc4group_getEntryByPath(const CC4Group* const this, const char* const entryPath); + +static char* cc4group_strerrorFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) +{ + (void)data; + char* message; + asprintf(&message, "%s: %s: %s (%d)", method, causer, strerror(code), code); + return message; +} + +static char* cc4group_zerrorFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) +{ + // restore errno for the case that code is Z_ERRNO + errno = (size_t)data; + char* message; + asprintf(&message, "%s: %s: %s (%d)", method, causer, zError(code), code); + return message; +} + +static char* cc4group_messageFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) +{ + (void)code; + (void)data; + + char* message; + asprintf(&message, "%s: %s", method, causer); + return message; +} + +static char* cc4group_noerrorFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) +{ + (void)code; + (void)method; + (void)causer; + (void)data; + + return strdup("No Error"); +} + +#define AddCleanupJob(func, data) CleanUpJobListPrepend(this->cleanupJobs, (CC4Group_CleanupJob){(CC4Group_CleanupFunc)func, data}); + +static void memScrambleHeader(uint8_t* const data) +{ + // XOR deface + for(size_t i = 0; i < sizeof(C4GroupHeader); i++) + data[i] ^= 237; + // byte swap + for(size_t i = 0; i + 2 < sizeof(C4GroupHeader); i += 3) + { + uint8_t temp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = temp; + } +} + +static bool buildChildren(CC4Group* const this, C4GroupEntryData* const entry, size_t const dataSize) +{ + C4GroupHeader* header = entry->header; + + entry->children = GroupEntryListNew(); + + uint8_t* data = entry->data + sizeof(C4GroupHeader); + size_t childDataOffset = sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; + uint8_t* childData = entry->data + childDataOffset; + + for(size_t i = 0; i < (size_t)header->Entries; ++i) + { + C4GroupEntryCore* core = (C4GroupEntryCore*)(data + sizeof(C4GroupEntryCore) * i); + + if(core->Offset + core->Size + childDataOffset > dataSize) + { + SET_MALFORMED_MESSAGE_ERROR("The group entry dictionary contains invalid entries."); + return false; + } + + C4GroupEntryData* childEntry = &GroupEntryListAppend(entry->children, (C4GroupEntryData){.core = *core, .data = childData + core->Offset, .children = NULL, .parent = entry})->value; + + if(core->Directory) + { + memScrambleHeader(childEntry->data); + if(!buildChildren(this, childEntry, core->Size)) + { + return false; + } + } + } + + return true; +} + +static void deleteChildren(GroupEntryList* const entries) +{ + ForeachGroupEntry(entries) + { + if(entry->value.core.Directory) + { + deleteChildren(entry->value.children); + } + } + + GroupEntryListDestroy(entries); +} + +static void* cc4group_mapSizedWriteFd(CC4Group* const this, int fd, size_t size) +{ + // allocate file size + // https://gist.github.com/marcetcheverry/991042 + if(lseek(fd, size - 1, SEEK_SET) == -1) + { + SET_ERRNO_ERROR("lseek: seeking to the desired file size"); + return MAP_FAILED; + } + + if(write(fd, "", 1) == -1) + { + SET_ERRNO_ERROR("write: writing to the file's end"); + return MAP_FAILED; + } + + return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); +} + +typedef struct { + void* addr; + size_t size; +} MunmapData; + +static void cc4group_unmapTmpMemoryFile(MunmapData* data) +{ + if(munmap(data->addr, data->size) == -1) + { + fprintf(stderr, "WARNING: munmap: Unmapping tempory file failed: %s\n", strerror(errno)); + } + + free(data); +} + +static void* cc4group_createTmpMemoryFile(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob) +{ + void* ret; +#define TMP_FILE "cc4group.tmp" + int tmpFile = open(TMP_FILE, O_CREAT | O_RDWR | O_TRUNC | O_EXCL, 0600); + if(tmpFile == -1) + { + SET_ERRNO_ERROR("open: Opening tmp file \"" TMP_FILE "\""); + return NULL; + } + + if(unlink(TMP_FILE) == -1) + { + fprintf(stderr, "WARNING: unlink: Failed to delete tmp file \"" TMP_FILE "\". Manual deletion is required: %s\n", strerror(errno)); + } +#undef TMP_FILE + + ret = cc4group_mapSizedWriteFd(this, tmpFile, size); + if(ret == MAP_FAILED) + { + // error message is set in the method + ret = NULL; + } + else + { + MunmapData* unmapData = malloc(sizeof(MunmapData)); + + if(unmapData == NULL) + { + fprintf(stderr, "ERROR: allocating memory for cleanup data: %s\n", strerror(errno)); + } + + *unmapData = (MunmapData){ret, size}; + *cleanupJob = (CC4Group_CleanupJob){(CC4Group_CleanupFunc)cc4group_unmapTmpMemoryFile, unmapData}; + } + + if(close(tmpFile) == -1) + { + fprintf(stderr, "WARNING: close: Closing tmp file failed: %s\n", strerror(errno)); + + if(ret != NULL) + { + cleanupJob->func(cleanupJob->data); + } + return NULL; + } + + return ret; +} + +static void* cc4group_createTmpMemoryMalloc(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob) +{ + (void)this; + // error checking is left out intentionally. it is the job of the calling function + void* ret = malloc(size); + + *cleanupJob = (CC4Group_CleanupJob){free, ret}; + + return ret; +} + +// uses in memory for up to 500MB, although it falls back to tmp file if in memory fails +static void* cc4group_createTmpMemoryAuto(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob) +{ + void* ret = NULL; + + if(size < 500 * 1024 * 1024) + { + ret = cc4group_createTmpMemoryMalloc(this, size, cleanupJob); + } + + if(ret == NULL) + { + // error checking is left out intentionally. it is the job of the calling function + ret = cc4group_createTmpMemoryFile(this, size, cleanupJob); + } + + return ret; +} + +static CC4Group_TmpMemoryStrategy cc4group_tmpMemoryStrategy = cc4group_createTmpMemoryAuto; + +static void cc4group_uncompressGroup(CC4Group* const this) +{ + // declare variables here already for proper cleanup in error cases + uint8_t* retData = NULL; + C4GroupHeader* header = NULL; + uint8_t* mappedFile = MAP_FAILED; + uint8_t* mappedTmpFile = NULL; + bool inflateStarted = false; + size_t currentSize = 0; + CC4Group_CleanupJob tmpCleanup; + + char* path; + char* slash = NULL; + + this->path = path = strdup(this->path); + AddCleanupJob(free, (void*)this->path); + + int file = -1; + for(;;) + { + file = open(this->path, O_RDONLY); + if(file != -1) + { + break; + } + + if(errno != ENOTDIR) + { + SET_ERRNO_ERROR("open: Opening group file"); + goto ret; + } + + char* oldSlash = slash; + + slash = strrchr(path, '/'); + + if(oldSlash != NULL) + { + *oldSlash = '/'; + } + + if(slash != NULL && slash != path) + { + *slash = '\0'; + this->subPath = slash + 1; + } + } + + struct stat st; + if(fstat(file, &st) == -1) + { + SET_ERRNO_ERROR("fstat: on the opened group file"); + goto ret; + } + + if(S_ISDIR(st.st_mode)) + { + SET_MESSAGE_ERROR("The specified group file is a directory"); + goto ret; + } + + off_t size = st.st_size; + mappedFile = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0); + + if(close(file) == -1) + { + SET_ERRNO_ERROR("close: Closing the group file"); + goto ret; + } + + if(mappedFile == MAP_FAILED) + { + SET_ERRNO_ERROR("mmap: Mapping the group file"); + goto ret; + } + + if((mappedFile[0] != C4GroupMagic1 && mappedFile[0] != 0x1f) || (mappedFile[1] != C4GroupMagic2 && mappedFile[1] != 0x8b)) + { + SET_MESSAGE_ERROR("The file is not a valid group file. Magic bytes don't match."); + goto ret; + } + + mappedFile[0] = 0x1f; + mappedFile[1] = 0x8b; + + z_stream strm = { + .zalloc = NULL, + .zfree = NULL, + .opaque = NULL, + .next_in = mappedFile, + .avail_in = size + }; + + int ret = inflateInit2(&strm, 15 + 16); // window size 15 + automatic gzip + inflateStarted = true; + + if(ret != Z_OK) + { + SET_ZERROR_ERROR("inflateInit2", ret); + goto ret; + } + + currentSize = sizeof(C4GroupHeader); + header = malloc(currentSize); + + if(header == NULL) + { + SET_ERRNO_ERROR("malloc: allocating memory for group header"); + goto ret; + } + + strm.next_out = (Bytef*)header; + strm.avail_out = currentSize; + + ret = inflate(&strm, Z_SYNC_FLUSH); + + if(ret != Z_OK && (ret != Z_STREAM_END || strm.avail_in != 0)) + { + SET_ZERROR_ERROR("inflate: inflating the group header", ret); + goto ret; + } + + memScrambleHeader((uint8_t*)header); + + if(header->Entries == 0) + { + // the group is empty + // so the stream should have ended also + if(ret != Z_STREAM_END) + { + SET_MALFORMED_MESSAGE_ERROR("The group is empty but the gzip stream has not ended yet."); + goto ret; + } + + retData = (void*)header; + header = NULL; + AddCleanupJob(free, header); + goto ret; + } + + if(header->Ver1 != 1 || header->Ver2 > 2) + { + SET_MESSAGE_ERROR("Unsupported group version. Only versions 1.0 up to 1.2 are supported."); + goto ret; + } + + if(memcmp(C4GroupId, header->id, sizeof(header->id)) != 0) + { + SET_MALFORMED_MESSAGE_ERROR("The group id does not match."); + goto ret; + } + + // make sure the maker is null terminated + header->Maker[C4GroupMaxMaker + 1] = '\0'; + + currentSize += sizeof(C4GroupEntryCore) * header->Entries; + header = realloc(header, currentSize); + + if(header == NULL) + { + SET_ERRNO_ERROR("realloc: allocating memory for group entry directory"); + goto ret; + } + + C4GroupEntryCore* cores = (C4GroupEntryCore*)((void*)(header) + sizeof(C4GroupHeader)); + + strm.next_out = (Bytef*)cores; + strm.avail_out = sizeof(C4GroupEntryCore) * header->Entries; + + ret = inflate(&strm, Z_SYNC_FLUSH); + if(ret != Z_OK) + { + SET_ZERROR_ERROR("inflate: inflating the group entry dictionary", ret); + goto ret; + } + + C4GroupEntryCore* last = cores + header->Entries - 1; + size_t uncompressedSize = last->Offset + last->Size; + + currentSize += uncompressedSize; + + mappedTmpFile = cc4group_tmpMemoryStrategy(this, currentSize, &tmpCleanup); + + uint8_t* data = (void*)(mappedTmpFile) + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; + + // write alredy decompressed header and cores into the file + memcpy(mappedTmpFile, header, data - mappedTmpFile); + + strm.next_out = data; + strm.avail_out = uncompressedSize; + + ret = inflate(&strm, Z_SYNC_FLUSH); + if(ret != Z_STREAM_END || strm.avail_in != 0 || strm.avail_out != 0) + { + SET_ZERROR_ERROR("inflate: inflating group contents", ret); + goto ret; + } + + retData = mappedTmpFile; + +ret: + if(header != NULL) + { + free(header); + } + + if(inflateStarted) + { + inflateEnd(&strm); + } + + if(mappedFile != MAP_FAILED) + { + if(munmap(mappedFile, size) == -1) + { + fprintf(stderr, "WARNING: munmap: Unmapping the group file failed: %s\n", strerror(errno)); + } + } + + if(retData == NULL && mappedTmpFile != NULL) + { + tmpCleanup.func(tmpCleanup.data); + } + else if(mappedTmpFile != NULL) + { + CleanUpJobListPrepend(this->cleanupJobs, tmpCleanup); + } + + this->uncompressedData = retData; + this->uncompressedSize = currentSize; +} + +static bool cc4group_buildGroupHierarchy(CC4Group* const this) +{ + this->root.data = this->uncompressedData; + bool ret = buildChildren(this, &this->root, this->uncompressedSize); + this->root.core.Directory = 1; + this->root.core.FileName[0] = '\0'; + + AddCleanupJob(deleteChildren, this->root.children); + + return ret; +} + +static void cc4group_init(CC4Group* const this) +{ + assert(this); + + this->uncompressedData = NULL; + this->uncompressedSize = 0; + this->path = ""; + this->subPath = ""; + + this->root.parent = NULL; + this->root.children = NULL; + this->root.core.Directory = true; + + this->cleanupJobs = CleanUpJobListNew(); + + this->error.code = 0; + this->error.method = NULL; + this->error.formatter.data = NULL; + this->error.formatter.formatter = cc4group_noerrorFormatter; + this->error.lastFormattedMessage = NULL; +} + +static void cc4group_setRecursiveMaker(const C4GroupEntryData* groupEntry, const char* const maker) +{ + ForeachGroupEntry(groupEntry->children) + { + if(entry->value.core.Directory) + { + C4GroupHeader_setMaker(entry->value.header, maker); + cc4group_setRecursiveMaker(&entry->value, maker); + } + } +} + +static bool cc4group_setMaker(CC4Group* const this, const char* const maker, const char* const path, bool const recursive) +{ + assert(this); + assert(maker); + + const C4GroupEntryData* entry; + if(path == NULL || *path == '\0') + { + entry = &this->root; + } + else + { + entry = cc4group_getEntryByPath(this, path); + + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired target directory does not exist"); + return false; + } + else if(!entry->core.Directory) + { + SET_MESSAGE_ERROR("The desired target is not a directory"); + return false; + } + } + + C4GroupHeader_setMaker(entry->header, maker); + if(recursive) + { + cc4group_setRecursiveMaker(entry, maker); + } + return true; +} + +static void cc4group_setRecursiveOfficial(const C4GroupEntryData* groupEntry, bool const official) +{ + ForeachGroupEntry(groupEntry->children) + { + if(entry->value.core.Directory) + { + C4GroupHeader_setOfficial(entry->value.header, official); + cc4group_setRecursiveOfficial(&entry->value, official); + } + } +} + +static bool cc4group_setOfficial(CC4Group* const this, bool const official, const char* const path, bool const recursive) +{ + assert(this); + + const C4GroupEntryData* entry; + if(path == NULL || *path == '\0') + { + entry = &this->root; + } + else + { + entry = cc4group_getEntryByPath(this, path); + + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired target directory does not exist"); + return false; + } + else if(!entry->core.Directory) + { + SET_MESSAGE_ERROR("The desired target is not a directory"); + return false; + } + } + + C4GroupHeader_setOfficial(entry->header, official); + if(recursive) + { + cc4group_setRecursiveOfficial(entry, official); + } + return true; +} + +static void cc4group_setRecursiveCreation(const C4GroupEntryData* groupEntry, int32_t const creation) +{ + ForeachGroupEntry(groupEntry->children) + { + entry->value.core.Modified = creation; + if(entry->value.core.Directory) + { + C4GroupHeader_setCreation(entry->value.header, creation); + cc4group_setRecursiveCreation(&entry->value, creation); + } + } +} + +static bool cc4group_setCreation(CC4Group* const this, int32_t const creation, const char* const path, bool const recursive) +{ + assert(this); + + C4GroupEntryData* entry; + if(path == NULL || *path == '\0') + { + entry = &this->root; + } + else + { + entry = (C4GroupEntryData*)cc4group_getEntryByPath(this, path); + + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired target file or directory does not exist"); + return false; + } + } + + entry->core.Modified = creation; + + if(entry->core.Directory) + { + C4GroupHeader_setCreation(entry->header, creation); + if(recursive) + { + cc4group_setRecursiveCreation(entry, creation); + } + } + return true; +} + +static bool cc4group_initNewHeader(CC4Group* const this) +{ + assert(this); + + this->root.header = malloc(sizeof(C4GroupHeader)); + + if(this->root.header == NULL) + { + SET_ERRNO_ERROR("malloc: allocating memory for a new group header"); + return false; + } + + AddCleanupJob(free, this->root.header); + + C4GroupHeader_init(this->root.header); + + return true; +} + +static CC4Group* cc4group_new(void) +{ + CC4Group* this = malloc(sizeof(CC4Group)); + cc4group_init(this); + return this; +} + +static bool cc4group_create(CC4Group* const this) +{ + assert(this); + + if(!cc4group_initNewHeader(this)) + { + return false; + } + + this->root.children = GroupEntryListNew(); + AddCleanupJob(deleteChildren, this->root.children); + + return true; +} + +static bool cc4group_openExisting(CC4Group* const this, const char* const path) +{ + assert(this); + + // only if the group is still empty + assert(this->root.children == NULL); + + this->path = path; + cc4group_uncompressGroup(this); + if(this->uncompressedData == NULL) + { + return false; + } + + cc4group_buildGroupHierarchy(this); + + if(*this->subPath != '\0') + { + this->realRoot = this->root; + + const C4GroupEntryData* subRoot = cc4group_getEntryByPath(this, this->subPath); + + if(subRoot == NULL || !subRoot->core.Directory) + { + SET_MESSAGE_ERROR("The desired subgroup does not exist as a child in the mother group"); + return false; + } + + this->root = *subRoot; + } + + return true; +} + +static void cc4group_delete(CC4Group* const this) +{ + assert(this); + + ForeachCleanupJob(this->cleanupJobs) + { + job->value.func(job->value.data); + } + + CleanUpJobListDestroy(this->cleanupJobs); + + if(this->error.lastFormattedMessage != NULL) + { + free(this->error.lastFormattedMessage); + } + + free(this); +} + +static bool cc4group_extractEntry(CC4Group* const this, const C4GroupEntryData* const root, const char* const targetPath) +{ + assert(root); + assert(!root->core.Directory); + + int file = open(targetPath, O_WRONLY | O_CREAT | O_EXCL, root->core.Executable ? 0755 : 0644); + if(file == -1) + { + SET_ERRNO_ERROR("open: Creating target file"); + return false; + } + write(file, root->data, root->core.Size); + + if(close(file) == -1) + { + fprintf(stderr, "WARNING: close: Closing the extracted file \"%s\" failed: %s\n", targetPath, strerror(errno)); + } + + struct timeval tv[2] = {{.tv_usec = 0, .tv_sec = root->core.Modified}, {.tv_usec = 0, .tv_sec = root->core.Modified}}; + if(utimes(targetPath, tv) == -1) + { + fprintf(stderr, "WARNING: utimes: Setting modification time for \"%s\" failed: %s\n", targetPath, strerror(errno)); + } + + return true; +} + +static bool cc4group_extractChildren(CC4Group* const this, const C4GroupEntryData* const root, const char* const targetPath) +{ + assert(root); + + char* tmpPath = malloc((strlen(targetPath) + 1 + strlen(root->core.FileName) + 1) * sizeof(*tmpPath)); + // + 1 for '/' + 1 for '\0' + + if(tmpPath == NULL) + { + SET_ERRNO_ERROR("malloc: Allocating memory for target path"); + return false; + } + + strcpy(tmpPath, targetPath); + if(*targetPath != '\0') + { + strcat(tmpPath, "/"); + } + strcat(tmpPath, root->core.FileName); + + bool success = true; + + if(root->core.Directory) + { + assert(root->children); + if(mkdir(tmpPath, 0755) == -1 /*&& errno != EEXIST*/) + { + SET_ERRNO_ERROR("mkdir: Creating target directory"); + success = false; + goto ret; + } + + ForeachGroupEntry(root->children) + { + if(!cc4group_extractChildren(this, &entry->value, tmpPath)) + { + success = false; + goto ret; + } + } + + success = true; + goto ret; + } + else + { + success = cc4group_extractEntry(this ,root, tmpPath); + goto ret; + } + +ret: + free(tmpPath); + return success; +} + +static bool cc4group_extractAll(CC4Group* const this, const char* const targetPath) +{ + assert(this); + + return cc4group_extractChildren(this, &this->root, targetPath); +} + +static const C4GroupEntryData* cc4group_getEntryByPath(const CC4Group* const this, const char* const entryPath) +{ + assert(this); + assert(this->root.children); + assert(entryPath); + + if(*entryPath == '\0') + { + return &this->root; + } + + const C4GroupEntryData* currentParent = &this->root; + char* path = strdup(entryPath); + + for(char* tokenIn = path, *savePtr; ; tokenIn = NULL) + { + const char* part = strtok_r(tokenIn, "/", &savePtr); + + if(part == NULL || *part == '\0') + { + break; + } + else if(!currentParent->core.Directory) + { + currentParent = NULL; + break; + } + + bool found = false; + ForeachGroupEntry(currentParent->children) + { + if(strcmp(entry->value.core.FileName, part) == 0) + { + currentParent = &entry->value; + found = true; + break; + } + } + + if(!found) + { + currentParent = NULL; + break; + } + } + + free(path); + + return currentParent; +} + +static bool cc4group_extractSingle(CC4Group* const this, const char* const entryPath, const char* const targetPath) +{ + assert(this); + assert(entryPath); + assert(targetPath); + + const C4GroupEntryData* entry = cc4group_getEntryByPath(this, entryPath); + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired file was not found in the group file"); + return false; + } + + return cc4group_extractChildren(this, entry, targetPath); +} + +static bool cc4group_getEntryData(const CC4Group* const this, const char* const entryPath, const void** const data, size_t* size) +{ + assert(this); + assert(entryPath); + assert(data); + assert(size); + + const C4GroupEntryData* entry = cc4group_getEntryByPath(this, entryPath); + if(entry == NULL || entry->core.Directory) + { + return false; + } + + *data = entry->data; + *size = entry->core.Size; + return true; +} + +static void cc4group_setTmpMemoryStrategy(const CC4Group_TmpMemoryStrategy strategy) +{ + cc4group_tmpMemoryStrategy = strategy; +} + +static const char* cc4group_getErrorMessage(CC4Group* const this) +{ + assert(this); + + if(this->error.lastFormattedMessage != NULL) + { + free(this->error.lastFormattedMessage); + } + + this->error.lastFormattedMessage = this->error.formatter.formatter(this->error.code, this->error.method, this->error.causer, this->error.formatter.data); + return this->error.lastFormattedMessage; +} + +static int32_t cc4group_getErrorCode(const CC4Group* const this) +{ + assert(this); + + return this->error.code; +} + +static const char* cc4group_getErrorMethod(const CC4Group* const this) +{ + assert(this); + + return this->error.method; +} + +static const char* cc4group_getErrorCauser(const CC4Group* const this) +{ + assert(this); + + return this->error.causer; +} + +static size_t cc4group_calculateEntrySizes(C4GroupEntryData* const entryData) +{ + if(entryData->core.Directory) + { + size_t sum = sizeof(C4GroupHeader) + GroupEntryListSize(entryData->children) * sizeof(C4GroupEntryCore); + ForeachGroupEntry(entryData->children) + { + sum += cc4group_calculateEntrySizes(&entry->value); + } + + return entryData->core.Size = sum; + } + else + { + return entryData->core.Size; + } +} + +static void cc4group_calculateEntryCRC(C4GroupEntryData* const groupEntry) +{ + if(groupEntry->core.Directory) + { + uint32_t crc = 0; + ForeachGroupEntry(groupEntry->children) + { + cc4group_calculateEntryCRC(&entry->value); + crc ^= entry->value.core.CRC; + } + groupEntry->core.HasCRC = C4GroupEntryCore_ContentsFileNameCRC; + groupEntry->core.CRC = crc; + } + else if(groupEntry->core.HasCRC != C4GroupEntryCore_ContentsFileNameCRC) + { + uint32_t crc = crc32(0, groupEntry->data, groupEntry->core.Size); + crc = crc32(crc, (uint8_t*)groupEntry->core.FileName, strlen(groupEntry->core.FileName)); + + groupEntry->core.HasCRC = C4GroupEntryCore_ContentsFileNameCRC; + groupEntry->core.CRC = crc; + } +} + +static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData* const entryData, gzFile file) +{ + C4GroupHeader header = *(C4GroupHeader*)entryData->data; + header.Entries = GroupEntryListSize(entryData->children); + header.Ver1 = 1; + header.Ver2 = 2; + + if(header.Creation == 0) + { + header.Creation = time(NULL); + } + + memScrambleHeader((uint8_t*)&header); + + if(gzwrite(file, &header, sizeof(header)) == 0) + { + int code; + gzerror(file, &code); + SET_ZERROR_ERROR("gzwrite: writing a group header", code); + return false; + } + + size_t offset = 0; + + ForeachGroupEntry(entryData->children) + { + entry->value.core.Offset = offset; + entry->value.core.Packed = 1; + + if(gzwrite(file, &entry->value.core, sizeof(entry->value.core)) == 0) + { + int code; + gzerror(file, &code); + SET_ZERROR_ERROR("gzwrite: writing a group entry core", code); + return false; + } + + offset += entry->value.core.Size; + } + + ForeachGroupEntry(entryData->children) + { + if(entry->value.core.Directory) + { + cc4group_writeEntriesToGzFile(this, &entry->value, file); + } + else if(entry->value.core.Size > 0) + { + if(gzwrite(file, entry->value.data, entry->value.core.Size) == 0) + { + int code; + gzerror(file, &code); + SET_ZERROR_ERROR("gzwrite: writing entry data", code); + return false; + } + } + } + + return true; +} + +static bool cc4group_saveIt(CC4Group* const this, const char* const path, bool const overwrite) +{ + assert(this); + + bool success = false; + int file = open(path, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL), 0644); + + if(file == -1) + { + SET_ERRNO_ERROR("open: creating the target group file"); + return false; + } + + int fdKeep = dup(file); + if(fdKeep == -1) + { + SET_ERRNO_ERROR("dup: duplicating the opened group fd"); + return false; + } + + gzFile gzfile = gzdopen(file, "wb"); + if(gzfile == NULL) + { + SET_ERRNO_ERROR("gzdopen: Opening the target group file"); + goto ret; + } + file = -1; + + cc4group_calculateEntrySizes(&this->root); + cc4group_calculateEntryCRC(&this->root); + success = cc4group_writeEntriesToGzFile(this, &this->root, gzfile); + +ret: + if(file != -1) + { + if(close(file) == -1) + { + fprintf(stderr, "WARNING: close: Closing the target group file failed: %s\n", strerror(errno)); + } + } + + if(gzfile != NULL) + { + int ret = gzclose(gzfile); + if(ret != Z_OK) + { + fprintf(stderr, "WARNING: gzclose: Closing the gzFile failed: %s\n", zError(ret)); + } + } + + if(!success) + { + if(unlink(path) == -1) + { + fprintf(stderr, "WARNING: unlink: Deleting the incomplete group file failed: %s\n", strerror(errno)); + } + } + else + { + if(lseek(fdKeep, 0, SEEK_SET) != 0) + { + fprintf(stderr, "WARNING: lseek: Seeking to the group magic bytes failed: %s\n", strerror(errno)); + } + else + { + static uint8_t magic[2] = {C4GroupMagic1, C4GroupMagic2}; + if(write(fdKeep, magic, 2) != 2) + { + fprintf(stderr, "WARNING: write: Writing the correct group magic bytes failed: %s\n", strerror(errno)); + } + } + } + + if(fdKeep != -1) + { + if(close(fdKeep) == -1) + { + fprintf(stderr, "WARNING: close: Closing the dup'ed target group file failed: %s\n", strerror(errno)); + } + } + + return success; +} + +static bool cc4group_save(CC4Group* const this, const char* const path) +{ + return cc4group_saveIt(this, path, false); +} + +static bool cc4group_saveOverwrite(CC4Group* const this, const char* const path) +{ + return cc4group_saveIt(this, path, true); +} + +static void cc4group_getEntryInfoForEntry(const C4GroupEntryData* const entry, CC4Group_EntryInfo* const info) +{ + *info = (CC4Group_EntryInfo){ + .fileName = entry->core.FileName, + .modified = entry->core.Directory ? entry->header->Creation : entry->core.Modified, + .author = entry->core.Directory ? entry->header->Maker : entry->parent->header->Maker, + .size = entry->core.Directory ? (sizeof(C4GroupHeader) + entry->header->Entries * sizeof(C4GroupEntryCore)) : (size_t)entry->core.Size, + .totalSize = entry->core.Size, + .executable = entry->core.Executable ? true : false, + .directory = entry->core.Directory ? true : false, + .official = C4GroupHeader_isOfficial(entry->core.Directory ? entry->header: entry->parent->header) + }; +} + +static bool cc4group_getEntryInfo(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info) +{ + assert(this); + assert(path); + assert(info); + + const C4GroupEntryData* entry = cc4group_getEntryByPath(this, path); + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired file was not found in the group file"); + return false; + } + + cc4group_getEntryInfoForEntry(entry, info); + return true; +} + +static bool cc4group_getEntryInfos(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* size) +{ + assert(this); + assert(infos); + assert(size); + + const C4GroupEntryData* node; + + if(path != NULL && *path != '\0') + { + const C4GroupEntryData* entry = cc4group_getEntryByPath(this, path); + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired subgroup was not found in the group file"); + return false; + } + + if(!entry->core.Directory) + { + SET_MESSAGE_ERROR("The desired subgroup is a file instead of a group"); + return false; + } + + node = entry; + } + else + { + node = &this->root; + } + + + *size = GroupEntryListSize(node->children); + CC4Group_EntryInfo* myInfos = malloc(*size * sizeof(CC4Group_EntryInfo)); + if(myInfos == NULL) + { + SET_ERRNO_ERROR("malloc: allocating memory for group entry infos"); + return false; + } + *infos = myInfos; + + ForeachGroupEntry(node->children) + { + cc4group_getEntryInfoForEntry(&entry->value, myInfos++); + } + + return true; +} + +// returns the child path and stores the parentPath in parentPath; modifies combinedPath +static char* cc4group_splitParentAndChildPaths(char* const combinedPath, char** const parentPath) +{ + char* slash = strrchr(combinedPath, '/'); + + if(slash == NULL) + { + *parentPath = ""; + return combinedPath; + } + else + { + *slash = '\0'; + *parentPath = combinedPath; + return slash + 1; + } +} + +static bool cc4group_getParentAndChildEntries(CC4Group* const this, const char* const path, const C4GroupEntryData** parentEntry, GroupEntryListEntry** childEntry) +{ + const C4GroupEntryData* parent; + + char* myPath = strdup(path); + char* parentPath; + char* entryName = cc4group_splitParentAndChildPaths(myPath, &parentPath); + + parent = cc4group_getEntryByPath(this, parentPath); + + if(parent == NULL) + { + SET_MESSAGE_ERROR("The containing directory of the desired file was not found in the group file"); + free(myPath); + return false; + } + *parentEntry = parent; + + GroupEntryListEntry* child = NULL; + ForeachGroupEntry(parent->children) + { + if(strcmp(entry->value.core.FileName, entryName) == 0) + { + child = entry; + break; + } + } + free(myPath); + + if(child == NULL) + { + SET_MESSAGE_ERROR("The desired file was not found in the group file"); + return false; + } + + *childEntry = child; + + return true; +} + +static bool cc4group_deleteEntry(CC4Group* const this, const char* const path, bool const recursive) +{ + assert(this); + assert(path); + + const C4GroupEntryData* parent; + GroupEntryListEntry* deleteEntry = NULL; + + if(!cc4group_getParentAndChildEntries(this, path, &parent, &deleteEntry)) + { + return false; + } + + if(deleteEntry->value.core.Directory) + { + if(!recursive) + { + SET_MESSAGE_ERROR("The desired entry is a subgroup but a non-recursive deletion was requested"); + return false; + } + + deleteChildren(deleteEntry->value.children); + } + + GroupEntryListRemove(parent->children, deleteEntry); + parent->header->Entries = GroupEntryListSize(parent->children); + + return true; +} + +static C4GroupEntryData* cc4group_addEntryToDirectory(CC4Group* const this, C4GroupEntryData* const directory, const C4GroupEntryData* const entry) +{ + assert(this); + assert(directory->core.Directory); + + // TODO? Implement engine-optimized order sorting here? + C4GroupEntryData* newEntry = &GroupEntryListAppend(directory->children, *entry)->value; + newEntry->parent = directory; + directory->header->Entries = GroupEntryListSize(directory->children); + + return newEntry; +} + +static bool cc4group_renameEntry(CC4Group* const this, const char* const oldPath, const char* const newPath) +{ + assert(this); + assert(oldPath); + assert(newPath); + + if(cc4group_getEntryByPath(this, newPath) != NULL) + { + SET_MESSAGE_ERROR("The desired target path already exists"); + return false; + } + + const C4GroupEntryData* oldParent; + GroupEntryListEntry* entry = NULL; + + if(!cc4group_getParentAndChildEntries(this, oldPath, &oldParent, &entry)) + { + return false; + } + + char* targetPath = strdup(newPath); + char* targetDirectory; + char* targetName = cc4group_splitParentAndChildPaths(targetPath, &targetDirectory); + + C4GroupEntryData* newParent = (C4GroupEntryData*)cc4group_getEntryByPath(this, targetDirectory); + if(newParent == NULL) + { + SET_MESSAGE_ERROR("The desired target directory does not exist"); + free(targetPath); + return false; + } + + C4GroupEntryCore_setFileName(&entry->value.core, targetName); + cc4group_addEntryToDirectory(this, newParent, &entry->value); + GroupEntryListRemove(oldParent->children, entry); + + free(targetPath); + return true; +} + +static C4GroupEntryData* cc4group_createEntry(CC4Group* const this, const char* const path) +{ + if(cc4group_getEntryByPath(this, path) != NULL) + { + SET_MESSAGE_ERROR("The desired target path already exists"); + return NULL; + } + + char* targetPath = strdup(path); + char* targetDirectory; + char* targetName = cc4group_splitParentAndChildPaths(targetPath, &targetDirectory); + + C4GroupEntryData* parent = (C4GroupEntryData*)cc4group_getEntryByPath(this, targetDirectory); + if(parent == NULL) + { + SET_MESSAGE_ERROR("The desired containing target directory does not exist"); + free(targetPath); + return NULL; + } + + if(!parent->core.Directory) + { + SET_MESSAGE_ERROR("The desired containing target directory is a file"); + free(targetPath); + return NULL; + } + + C4GroupEntryData entry; + + entry.data = NULL; + entry.children = GroupEntryListNew(); + + C4GroupEntryCore_init(&entry.core); + C4GroupEntryCore_setFileName(&entry.core, targetName); + + free(targetPath); + + return cc4group_addEntryToDirectory(this, parent, &entry); +} + +static bool cc4group_createDirectory(CC4Group* const this, const char* const path) +{ + assert(this); + assert(path); + + C4GroupEntryData* entry = cc4group_createEntry(this, path); + + if(entry == NULL) + { + return false; + } + + C4GroupHeader* header = malloc(sizeof(C4GroupHeader)); + + if(header == NULL) + { + SET_ERRNO_ERROR("malloc: allocating the group header for the new directory"); + return NULL; + } + + C4GroupHeader_init(header); + C4GroupHeader_setOfficial(header, C4GroupHeader_isOfficial(entry->parent->header)); + C4GroupHeader_setMaker(header, entry->parent->header->Maker); + + entry->header = header; + entry->core.Directory = 1; + + return true; +} + +static bool cc4group_createFile(CC4Group* const this, const char* const path, void* const data, size_t const size) +{ + assert(this); + assert(path); + + C4GroupEntryData* entry = cc4group_createEntry(this, path); + + if(entry == NULL) + { + return false; + } + + if(size != 0 && data != NULL) + { + entry->core.Size = size; + entry->data = data; + + AddCleanupJob(free, data); + } + + return true; +} + +static bool cc4group_setEntryData(const CC4Group* const this, const char* const entryPath, void* const data, size_t const size) +{ + assert(this); + assert(entryPath); + + C4GroupEntryData* entry = (C4GroupEntryData*)cc4group_getEntryByPath(this, entryPath); + if(entry == NULL || entry->core.Directory) + { + return false; + } + + if(data != NULL && size != 0) + { + entry->data = data; + entry->core.Size = size; + + AddCleanupJob(free, data); + } + else + { + entry->data = NULL; + entry->core.Size = 0; + } + + return true; +} + +static bool cc4group_setExecutable(CC4Group* const this, bool const executable, const char* const path) +{ + assert(this); + + C4GroupEntryData* entry; + if(path == NULL || *path == '\0') + { + entry = &this->root; + } + else + { + entry = (C4GroupEntryData*)cc4group_getEntryByPath(this, path); + + if(entry == NULL) + { + SET_MESSAGE_ERROR("The desired target file does not exist"); + return false; + } + else if(entry->core.Directory) + { + SET_MESSAGE_ERROR("The desired target is not a file"); + return false; + } + } + + entry->core.Executable = executable ? 1 : 0; + return true; +} + +CC4Group_API cc4group = { + .new = cc4group_new, + .create = cc4group_create, + .delete = cc4group_delete, + .openExisting = cc4group_openExisting, + .save = cc4group_save, + .saveOverwrite = cc4group_saveOverwrite, + .extractAll = cc4group_extractAll, + .extractSingle = cc4group_extractSingle, + .getEntryData = cc4group_getEntryData, + .setTmpMemoryStrategy = cc4group_setTmpMemoryStrategy, + + .TmpMemoryStrategies = { + .Memory = cc4group_createTmpMemoryMalloc, + .File = cc4group_createTmpMemoryFile, + .Auto = cc4group_createTmpMemoryAuto + }, + + .getErrorMessage = cc4group_getErrorMessage, + .getErrorCode = cc4group_getErrorCode, + .getErrorMethod = cc4group_getErrorMethod, + .getErrorCauser = cc4group_getErrorCauser, + + .setMaker = cc4group_setMaker, + .setCreation = cc4group_setCreation, + .setOfficial = cc4group_setOfficial, + .setExecutable = cc4group_setExecutable, + + .getEntryInfo = cc4group_getEntryInfo, + .getEntryInfos = cc4group_getEntryInfos, + + .deleteEntry = cc4group_deleteEntry, + .renameEntry = cc4group_renameEntry, + .createDirectory = cc4group_createDirectory, + .createFile = cc4group_createFile, + .setEntryData = cc4group_setEntryData +}; diff --git a/src/cc4group.h b/src/cc4group.h new file mode 100644 index 0000000..ef3a55d --- /dev/null +++ b/src/cc4group.h @@ -0,0 +1,97 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#define this this_ +#define new new_ +#define delete delete_ +#endif + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +typedef struct { + const char* fileName; + int32_t modified; + const char* author; + size_t size; + size_t totalSize; + bool executable; + bool directory; + bool official; +} CC4Group_EntryInfo; + +typedef struct CC4Group_t CC4Group; + +typedef void(*CC4Group_CleanupFunc)(void* data); +typedef struct { + CC4Group_CleanupFunc func; + void* data; +} CC4Group_CleanupJob; +typedef void* (*CC4Group_TmpMemoryStrategy)(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob); + +typedef struct { + CC4Group* (*new)(void); + bool (*create)(CC4Group* const this); + void (*delete)(CC4Group* const this); + + // only open an existing group on a frehsly created group object + bool (*openExisting)(CC4Group* const this, const char* const path); + + bool (*save)(CC4Group* const this, const char* const path); + bool (*saveOverwrite)(CC4Group* const this, const char* const path); + + // extraction to disk + bool (*extractAll)(CC4Group* const this, const char* const targetPath); + bool (*extractSingle)(CC4Group* const this, const char* const entryPath, const char* const targetPath); + + // the group owns the data pointed to. the pointer is valid until the group destructor is called + bool (*getEntryData)(const CC4Group* const this, const char* const entryPath, const void** const data, size_t* const size); + + // the returned error message pointer is valid until the next call to getErrorMessage is issued or the group is destructed + const char* (*getErrorMessage)(CC4Group* const this); + + int32_t (*getErrorCode)(const CC4Group* const this); + + const char* (*getErrorMethod)(const CC4Group* const this); + const char* (*getErrorCauser)(const CC4Group* const this); + + struct { + CC4Group_TmpMemoryStrategy Memory; + CC4Group_TmpMemoryStrategy File; + CC4Group_TmpMemoryStrategy Auto; + } const TmpMemoryStrategies; + + void (*setTmpMemoryStrategy)(const CC4Group_TmpMemoryStrategy strategy); + + // group metadata handling + bool (*setMaker)(CC4Group* const this, const char* const maker, const char* const path, bool const recursive); + bool (*setCreation)(CC4Group* const this, int32_t const creation, const char* const path, bool const recursive); + bool (*setOfficial)(CC4Group* const this, bool const official, const char* const path, bool const recursive); + bool (*setExecutable)(CC4Group* const this, bool const executable, const char* const path); + + bool (*getEntryInfo)(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info); + bool (*getEntryInfos)(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* const size); + + // modifying the group + bool (*deleteEntry)(CC4Group* const this, const char* const path, bool const recursive); + bool (*renameEntry)(CC4Group* const this, const char* const oldPath, const char* const newPath); + bool (*createDirectory)(CC4Group* const this, const char* const path); + + // ownership of the data is taken by the group + bool (*createFile)(CC4Group* const this, const char* const path, void* const data, size_t const size); + // ownership of the data is taken by the group + bool (*setEntryData)(const CC4Group* const this, const char* const entryPath, void* const data, size_t const size); +} const CC4Group_API; + +#ifndef CC4GROUP_DYNAMIC_LOAD +extern CC4Group_API cc4group; +#endif + +#ifdef __cplusplus +} +#undef this +#undef new +#undef delete +#endif |
