From 0d1ae015fef8e15442dafa61b9c8d929ce467969 Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Wed, 15 Aug 2018 22:46:40 +0200 Subject: Refactor code into a library and implement basic file management methods --- src/GenericList.h | 142 +++++ src/c4groupentrycore.c | 24 + src/c4groupentrycore.h | 26 + src/c4groupheader.c | 40 ++ src/c4groupheader.h | 27 + src/cc4group.c | 1620 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cc4group.h | 97 +++ 7 files changed, 1976 insertions(+) create mode 100644 src/GenericList.h create mode 100644 src/c4groupentrycore.c create mode 100644 src/c4groupentrycore.h create mode 100644 src/c4groupheader.c create mode 100644 src/c4groupheader.h create mode 100644 src/cc4group.c create mode 100644 src/cc4group.h (limited to 'src') diff --git a/src/GenericList.h b/src/GenericList.h new file mode 100644 index 0000000..0679bb7 --- /dev/null +++ b/src/GenericList.h @@ -0,0 +1,142 @@ +#pragma once + +/** + * + * A collection of macros to create a doubly linked list of an arbitrary type. + * + * For normal usage just use LIST_AUTO(type, listTypeName) where type is the type to be stored and listTypeName is the desired typeName of the list container. + * List nodes get the type listTypeName##Entry. + * + * LIST_AUTO generates a list container struct of the name listTypeName, + * a list entry struct of the name listTypeName##Entry and + * functions of the name listTypeName##function for each function of + * New, Destroy, Empty (checks if the list is empty), Size, First, Last, Insert and Remove. + * + * For a simple foreach loop over a list use LIST_FOREACH(listTypeName, list, loopVariable), + * where listTypeName is the same as for LIST_AUTO, list is the list to be looped over and loopVariable is the name of the currently iterated list entry. + * + * (c) Markus Mittendrein - 2017-2018 + * + **/ + +#include +#include + +#define LIST_ENTRY_STRUCT(typeName, type) struct list_entry_##typeName {\ + type value;\ + struct list_entry_##typeName* next;\ + struct list_entry_##typeName* prev;\ +} + +#define LIST_STRUCT(type) struct list_##type\ +{\ + struct list_entry_##type* head;\ + struct list_entry_##type* tail;\ + size_t size;\ +} + +#define LIST_NEW(type, name) struct list_##type* name(void)\ +{\ + struct list_##type* ret = malloc(sizeof(*ret));\ + if(ret != NULL)\ + {\ + ret->head = NULL;\ + ret->tail = NULL;\ + ret->size = 0;\ + }\ + return ret;\ +} + +#define LIST_DESTROY(type, name) void name(struct list_##type* list)\ +{\ + for(struct list_entry_##type* entry = list->head; entry != NULL; )\ + {\ + struct list_entry_##type* tmp = entry;\ + entry = entry->next; free(tmp);\ + }\ + free(list);\ +} + +#define LIST_EMPTY(type, name) bool name(struct list_##type* list) { return list->size == 0; } + +#define LIST_SIZE(type, name) size_t name(struct list_##type* list) { return list->size; } + +#define LIST_FIRST(type, name) struct list_entry_##type* name(struct list_##type* list) { return list->head; } + +#define LIST_LAST(type, name) struct list_entry_##type* name(struct list_##type* list) { return list->tail; } + +#define LIST_INSERT(typeName, type, name) struct list_entry_##typeName* name(struct list_##typeName* list, struct list_entry_##typeName* after, type value)\ +{\ + struct list_entry_##typeName* newEntry = malloc(sizeof(*newEntry));\ + if(newEntry == NULL)\ + {\ + return NULL;\ + }\ +\ +newEntry->value = value;\ + newEntry->prev = after;\ +\ + if(after != NULL)\ + {\ + newEntry->next = after->next;\ + after->next = newEntry;\ + }\ + else\ + {\ + newEntry->next = list->head;\ + list->head = newEntry;\ + }\ +\ + if(newEntry->next != NULL)\ + {\ + newEntry->next->prev = newEntry;\ + }\ +\ + if(after == list->tail)\ + {\ + list->tail = newEntry;\ + }\ +\ + ++list->size;\ + return newEntry;\ +} + +#define LIST_REMOVE(type, name) void name(struct list_##type* list, struct list_entry_##type* entry)\ +{\ + if(entry == list->head)\ + {\ + list->head = entry->next;\ + }\ + if(entry == list->tail)\ + {\ + list->tail = entry->prev;\ + }\ +\ + if(entry->next != NULL)\ + {\ + entry->next->prev = entry->prev;\ + }\ +\ + if(entry->prev != NULL)\ + {\ + entry->prev->next = entry->next;\ + }\ +\ + free(entry);\ + --list->size;\ +} + +#define LIST_AUTO(type, listTypeName) typedef LIST_ENTRY_STRUCT(listTypeName, type) listTypeName##Entry;\ +typedef LIST_STRUCT(listTypeName) listTypeName;\ +LIST_NEW(listTypeName, listTypeName##New)\ +LIST_DESTROY(listTypeName, listTypeName##Destroy)\ +LIST_EMPTY(listTypeName, listTypeName##Empty)\ +LIST_SIZE(listTypeName, listTypeName##Size)\ +LIST_FIRST(listTypeName, listTypeName##First)\ +LIST_LAST(listTypeName, listTypeName##Last)\ +LIST_INSERT(listTypeName, type, listTypeName##Insert)\ +LIST_REMOVE(listTypeName, listTypeName##Remove)\ +struct list_entry_##listTypeName* listTypeName##Append(struct list_##listTypeName* list, type value) { return listTypeName##Insert(list, list->tail, value); }\ +struct list_entry_##listTypeName* listTypeName##Prepend(struct list_##listTypeName* list, type value) { return listTypeName##Insert(list, NULL, value); } + +#define LIST_FOREACH(listTypeName, list, var) for(listTypeName##Entry* var = listTypeName##First(list); var != NULL; var = var->next) 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 +#include + +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 + +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 +#include + +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 +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 +#include +#include + +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 -- cgit v1.2.3-54-g00ecf