// TODO? Error handling of lists? #define _POSIX_C_SOURCE 200809L #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "cc4group.h" #include "c4groupheader.h" #include "c4groupentrycore.h" #include "GenericList.h" #include "platform/platform.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_EOD_ERROR(message) SET_MESSAGE_ERROR("Unexpected end of group data while " message) #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(CC4Group* const this, const char* const entryPath, const void** const data, size_t* size); #define C4GroupMagic1 0x1e #define C4GroupMagic2 0x8c #define CC4GROUP_UNEXPECTED_EOD -100 #define CC4GROUP_MEM_ERROR -101 struct list_GroupEntryList; typedef struct C4GroupEntryData_t { C4GroupEntryCore core; CC4Group_MemoryManagement memoryManagement; 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; C4GroupEntryData root; C4GroupEntryData realRoot; CleanUpJobList* cleanupJobs; struct { int32_t code; const char* method; const char* causer; ErrorFormatterData formatter; char* lastFormattedMessage; } error; size_t referenceCounter; CC4Group* parent; }; typedef struct { CC4Group_WriteCallback callback; void* arg; size_t bufferSize; size_t position; void* buffer; bool magicDone; z_stream gzStream; } WriteCallback; static bool cc4group_applyMemoryManagementStart(CC4Group_MemoryManagement const management, const uint8_t** data, size_t size) { void* newData = management->start((void*)*data, size, management->arg); if(newData == NULL) { return false; } *data = newData; return true; } static void cc4group_applyMemoryManagementEnd(CC4Group_MemoryManagement const management, const uint8_t* data) { management->end((void*)data, management->arg); } static const uint8_t* cc4group_getOnlyEntryData(CC4Group* const this, const C4GroupEntryData* entry); static const C4GroupEntryData* cc4group_getEntryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot, bool* error); static const C4GroupEntryData* cc4group_getDirectoryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot); static const C4GroupEntryData* cc4group_getFileByPath(CC4Group* const this, const char* const entryPath); static const C4GroupEntryData* cc4group_getFileOrDirectoryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot); static char* cc4group_strerrorFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) { (void)data; char* message; if(asprintf(&message, "%s: %s: %s (%d)", method, causer, strerror(code), code) == -1) { return NULL; } return message; } static char* cc4group_zerrorFormatter(int32_t const code, const char* const method, const char* const causer, void* const data) { int storedErrno = (size_t)data; // restore errno for the case that code is Z_ERRNO if(code == Z_ERRNO) { errno = storedErrno; } char* message; const char* errorString; if(code == CC4GROUP_MEM_ERROR) { errorString = ""; if(asprintf(&message, "%s: %s: Can't allocate memory for copying the input data: %s (%d)", method, causer, strerror(storedErrno), storedErrno) == -1) { return NULL; } return message; } if(code == CC4GROUP_UNEXPECTED_EOD) { errorString = "Unexpected end of group data"; } else if(code == Z_OK) { errorString = "Z_OK"; } else { errorString = zError(code); } if(asprintf(&message, "%s: %s: %s (%d)", method, causer, errorString, code) == -1) { return NULL; } 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; if(asprintf(&message, "%s: %s", method, causer) == -1) { return NULL; } 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, .memoryManagement = cc4group.MemoryManagement.Reference, .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.data != NULL) { cc4group_applyMemoryManagementEnd(entry->value.memoryManagement, entry->value.data); } 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 cc4group_mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); } static const char tmpFileTemplate[] = "cc4grouptmpXXXXXX"; typedef struct { void* addr; size_t size; char fileName[sizeof(tmpFileTemplate) / sizeof(tmpFileTemplate[0])]; bool unlinkLater; } MunmapData; static void cc4group_tryWarnUnlink(const char* const fileName) { if(unlink(fileName) == -1) { fprintf(stderr, "WARNING: Removing file \"%s\" failed. Manual deletion is required", fileName); } } static void cc4group_unmapTmpMemoryFile(MunmapData* data) { if(cc4group_munmap(data->addr, data->size) == -1) { fprintf(stderr, "WARNING: munmap: Unmapping tempory file failed: %s\n", strerror(errno)); } if(data->unlinkLater) { cc4group_tryWarnUnlink(data->fileName); } free(data); } static void* cc4group_createTmpMemoryFile(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob) { MunmapData* unmapData = malloc(sizeof(MunmapData)); if(unmapData == NULL) { SET_ERRNO_ERROR("malloc: allocating memory for cleanup data"); return NULL; } strncpy(unmapData->fileName, tmpFileTemplate, sizeof(unmapData->fileName) / sizeof(unmapData->fileName[0])); void* ret = NULL; int tmpFile = mkstemp(unmapData->fileName); if(tmpFile == -1) { SET_ERRNO_ERROR("mkstemp: Creating and opening the tmp file"); unmapData->fileName[0] = '\0'; goto ret; } unmapData->unlinkLater = unlink(unmapData->fileName) == -1; // in case it can't be deleted now (Windows -.-), delete it when cleaning up ret = cc4group_mapSizedWriteFd(this, tmpFile, size); if(ret == MAP_FAILED) { // error message is set in the method ret = NULL; } else { unmapData->addr = ret; unmapData->size = size; *cleanupJob = (CC4Group_CleanupJob){.func = (CC4Group_CleanupFunc)cc4group_unmapTmpMemoryFile, .data = unmapData}; } ret: if(tmpFile != -1 && close(tmpFile) == -1) { fprintf(stderr, "WARNING: close: Closing tmp file failed: %s\n", strerror(errno)); } if(ret == NULL) { if(unmapData->fileName[0] != '\0' && unmapData->unlinkLater) { cc4group_tryWarnUnlink(unmapData->fileName); } free(unmapData); } return ret; } static void* cc4group_createTmpMemoryMalloc(CC4Group* const this, const size_t size, CC4Group_CleanupJob* cleanupJob) { (void)this; void* ret = malloc(size); if(ret == NULL) { SET_ERRNO_ERROR("malloc: allocating tmp memory"); } else { *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 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 bool cc4group_inflateFillOutput(z_stream* const strm, CC4Group_ReadCallback const callback, void* const callbackArg, CC4Group_MemoryManagement const memoryManagement, bool* eof, const uint8_t** const lastData, int* inflateRet) { int ret = Z_OK; while(strm->avail_out > 0) { if(strm->avail_in == 0) { if(*eof) { *inflateRet = CC4GROUP_UNEXPECTED_EOD; return false; } if(*lastData != NULL) { cc4group_applyMemoryManagementEnd(memoryManagement, *lastData); *lastData = NULL; } size_t readSize = 0; *eof = callback((const void**)lastData, &readSize, callbackArg); if(readSize == 0) { continue; } if(!cc4group_applyMemoryManagementStart(memoryManagement, lastData, readSize)) { *inflateRet = CC4GROUP_MEM_ERROR; return false; } strm->avail_in = readSize; strm->next_in = (uint8_t*)*lastData; // I don't know why this must be non-const; anyway a read-only mapped input doesn't segfault. so it seems to be used read-only } ret = inflate(strm, *eof ? Z_FINISH : Z_NO_FLUSH); if(ret != Z_OK && (ret != Z_STREAM_END || strm->avail_in != 0) && !(ret == Z_BUF_ERROR && strm->avail_out == 0)) { break; } } if(ret == Z_BUF_ERROR && strm->avail_out == 0) { return true; } *inflateRet = ret; if(ret == Z_OK) { return true; } if(ret == Z_STREAM_END && strm->avail_in == 0) { // the input should have really ended now // but some callbacks may need to be called again to return EOF if(!*eof) { size_t readSize = 0; *eof = callback((const void**)lastData, &readSize, callbackArg); } return *eof; } return false; } static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback const readCallback, void* const callbackArg, CC4Group_MemoryManagement const memoryManagement, CC4Group_ReadSetupCallback const initCallback, CC4Group_ReadSetupCallback const deinitCallback) { assert(this); assert(readCallback); assert(callbackArg); // only if the group is still empty assert(this->root.children == NULL); // declare variables here already for proper cleanup in error cases C4GroupHeader* header = NULL; bool inflateStarted = false; uint8_t* retData = NULL; size_t currentSize = 0; CC4Group_CleanupJob tmpCleanup; uint8_t* uncompressedData = NULL; if(initCallback != NULL) { if(!initCallback(callbackArg)) { SET_MESSAGE_ERROR("The initCallback failed"); return false; } } size_t totalReadSize = 0; const uint8_t* readData = NULL; const uint8_t* readDataAfterMagic = NULL; size_t readSize = 0; bool eof = false; uint8_t magic1 = 0, magic2 = 0; while(!eof && totalReadSize < 2) { if(readData != NULL) { cc4group_applyMemoryManagementEnd(memoryManagement, readData); } readData = NULL; readSize = 0; eof = readCallback((const void**)&readData, &readSize, callbackArg); if(readSize == 0) { continue; } if(!cc4group_applyMemoryManagementStart(memoryManagement, &readData, readSize)) { SET_ZERROR_ERROR("reading the group magic", CC4GROUP_MEM_ERROR); goto ret; } size_t newTotalReadSize = totalReadSize + readSize; if(totalReadSize < 1 && newTotalReadSize > 0) { magic1 = readData[0]; } if(totalReadSize < 2 && newTotalReadSize > 1) { magic2 = readData[1 - totalReadSize]; readDataAfterMagic = readData + (2 - totalReadSize); } totalReadSize = newTotalReadSize; } if(eof && totalReadSize < 2) { SET_EOD_ERROR("reading the group magic"); goto ret; } static uint8_t magic[] = {0x1f, 0x8b}; if((magic1 != C4GroupMagic1 && magic1 != magic[0]) || (magic2 != C4GroupMagic2 && magic2 != magic[1])) { SET_MESSAGE_ERROR("The file is not a valid group file. Magic bytes don't match."); goto ret; } z_stream strm = { .zalloc = NULL, .zfree = NULL, .opaque = NULL, .next_in = magic, .avail_in = 2 }; 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 magic", ret); goto ret; } if(totalReadSize > 2) { strm.next_in = (uint8_t*)readDataAfterMagic; strm.avail_in = totalReadSize - 2; } if(!cc4group_inflateFillOutput(&strm, readCallback, callbackArg, memoryManagement, &eof, &readData, &ret)) { 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 || !eof || strm.avail_in > 0) { 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*)((uint8_t*)(header) + sizeof(C4GroupHeader)); strm.next_out = (Bytef*)cores; strm.avail_out = sizeof(C4GroupEntryCore) * header->Entries; if(!cc4group_inflateFillOutput(&strm, readCallback, callbackArg, memoryManagement, &eof, &readData, &ret)) { 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; if(currentSize == 0) { SET_MESSAGE_ERROR("Bug: the computed size of the whole uncompressed group somehow turned out to be 0"); goto ret; } uncompressedData = cc4group_tmpMemoryStrategy(this, currentSize, &tmpCleanup); if(uncompressedData == NULL) { goto ret; } uint8_t* data = (uint8_t*)(uncompressedData) + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; // write alredy decompressed header and cores into the new tmp memory memcpy(uncompressedData, header, data - uncompressedData); strm.next_out = data; strm.avail_out = uncompressedSize; if(!cc4group_inflateFillOutput(&strm, readCallback, callbackArg, memoryManagement, &eof, &readData, &ret)) { SET_ZERROR_ERROR("inflate: inflating the group contents", ret); goto ret; } if(strm.avail_in > 0) { // NOTE: This may miss additional garbage data at the end if the read callback happens to read exactly the right amount of data SET_MALFORMED_MESSAGE_ERROR("The group contents are read completely but more data is left to read"); goto ret; } retData = uncompressedData; ret: if(deinitCallback != NULL) { if(!deinitCallback(callbackArg)) { fprintf(stderr, "WARNING: cc4group_uncompressGroup: the deinitCallback failed\n"); } } if(readData != NULL) { cc4group_applyMemoryManagementEnd(memoryManagement, readData); } if(header != NULL) { free(header); } if(inflateStarted) { inflateEnd(&strm); } if(retData == NULL && uncompressedData != NULL) { tmpCleanup.func(tmpCleanup.data); } else if(uncompressedData != NULL) { CleanUpJobListPrepend(this->cleanupJobs, tmpCleanup); } this->uncompressedData = retData; this->uncompressedSize = currentSize; if(retData == NULL) { return false; } return cc4group_buildGroupHierarchy(this); } typedef struct { const void* data; size_t size; } CompleteDataReadCallbackArg; static bool cc4group_completeDataReadCallback(const void** const data, size_t* const size, void* callbackArg) { CompleteDataReadCallbackArg* argData = callbackArg; *data = argData->data; *size = argData->size; return true; } typedef struct { void* arg; void* buffer; } ChunkedReadData; static bool cc4group_initChunkBufferCallback(void* callbackArg) { #define CHUNK_SIZE 1024 * 1024 void* buffer = malloc(CHUNK_SIZE); if(buffer == NULL) { return false; } ((ChunkedReadData*)callbackArg)->buffer = buffer; return true; } static bool cc4group_deinitChunkBufferCallback(void* callbackArg) { free(((ChunkedReadData*)callbackArg)->buffer); return true; } static bool cc4group_readFdReadCallback(const void** const data, size_t* const size, void* callbackArg) { ChunkedReadData* arg = callbackArg; void* buffer = arg->buffer; ssize_t count = read(*(int*)arg->arg, buffer, CHUNK_SIZE); if(count > 0) { *data = buffer; *size = count; return false; } return true; } static bool cc4group_readFilePointerReadCallback(const void** const data, size_t* const size, void* callbackArg) { ChunkedReadData* arg = callbackArg; void* buffer = arg->buffer; size_t count = fread(buffer, 1, CHUNK_SIZE, arg->arg); if(count > 0) { *data = buffer; *size = count; return false; } return true; #undef CHUNK_SIZE } static bool cc4group_openMemory(CC4Group* const this, const void* const compressedData, size_t const size, CC4Group_MemoryManagement const memoryManagement) { assert(this); assert(compressedData); assert(size); CompleteDataReadCallbackArg data = {.data = compressedData, .size = size}; return cc4group_uncompressGroup(this, cc4group_completeDataReadCallback, &data, memoryManagement, NULL, NULL); } static bool cc4group_setSubRoot(CC4Group* const this, const char* const subPath) { if(subPath != NULL && *subPath != '\0') { this->realRoot = this->root; const C4GroupEntryData* subRoot = cc4group_getDirectoryByPath(this, subPath, false); if(subRoot == NULL) { return false; } this->root = *subRoot; } return true; } static bool cc4group_uncompressGroupFromFile(CC4Group* const this, const char* const path) { uint8_t* mappedFile = MAP_FAILED; bool success = false; char* slash = NULL; char* tmpPath = strdup(path); if(tmpPath == NULL) { SET_ERRNO_ERROR("strdup: duplicating the path"); return false; } char* subPath = NULL; int file = -1; for(;;) { file = open(tmpPath, O_RDONLY | O_BINARY); if(file != -1) { break; } if(errno != ENOTDIR) { SET_ERRNO_ERROR("open: Opening group file"); goto ret; } char* oldSlash = slash; slash = strrchr(tmpPath, '/'); if(oldSlash != NULL) { *oldSlash = '/'; } if(slash != NULL && slash != tmpPath) { *slash = '\0'; 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 = cc4group_mmap(NULL, size, PROT_READ, 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; } CompleteDataReadCallbackArg data = {.data = mappedFile, .size = size}; success = cc4group_uncompressGroup(this, cc4group_completeDataReadCallback, &data, cc4group.MemoryManagement.Reference, NULL, NULL); ret: if(mappedFile != MAP_FAILED) { if(cc4group_munmap(mappedFile, size) == -1) { fprintf(stderr, "WARNING: munmap: Unmapping the group file failed: %s\n", strerror(errno)); } } if(success) { success = cc4group_setSubRoot(this, subPath); } free(tmpPath); return success; } static void cc4group_init(CC4Group* const this) { assert(this); this->referenceCounter = 1; this->parent = NULL; this->uncompressedData = NULL; this->uncompressedSize = 0; this->root.data = NULL; this->root.memoryManagement = cc4group.MemoryManagement.Reference; 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_setMakerRecursively(const C4GroupEntryData* groupEntry, const char* const maker) { ForeachGroupEntry(groupEntry->children) { if(entry->value.core.Directory) { C4GroupHeader_setMaker(entry->value.header, maker); cc4group_setMakerRecursively(&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 = cc4group_getDirectoryByPath(this, path, true); if(entry == NULL) { return false; } C4GroupHeader_setMaker(entry->header, maker); if(recursive) { cc4group_setMakerRecursively(entry, maker); } return true; } static void cc4group_setOfficialRecursively(const C4GroupEntryData* groupEntry, bool const official) { ForeachGroupEntry(groupEntry->children) { if(entry->value.core.Directory) { C4GroupHeader_setOfficial(entry->value.header, official); cc4group_setOfficialRecursively(&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 = cc4group_getDirectoryByPath(this, path, true); if(entry == NULL) { return false; } C4GroupHeader_setOfficial(entry->header, official); if(recursive) { cc4group_setOfficialRecursively(entry, official); } return true; } static void cc4group_setCreationRecursively(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_setCreationRecursively(&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 = (C4GroupEntryData*)cc4group_getFileOrDirectoryByPath(this, path, true); if(entry == NULL) { return false; } entry->core.Modified = creation; if(entry->core.Directory) { C4GroupHeader_setCreation(entry->header, creation); if(recursive) { cc4group_setCreationRecursively(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)); if(this != NULL) { 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_openFd(CC4Group* const this, int fd) { // assert is in cc4group_uncompressGroup ChunkedReadData arg = {.arg = &fd}; return cc4group_uncompressGroup(this, cc4group_readFdReadCallback, &arg, cc4group.MemoryManagement.Reference, cc4group_initChunkBufferCallback, cc4group_deinitChunkBufferCallback); } static bool cc4group_openFilePointer(CC4Group* const this, FILE* file) { // assert(this) is in cc4group_uncompressGroup assert(file); ChunkedReadData arg = {.arg = file}; return cc4group_uncompressGroup(this, cc4group_readFilePointerReadCallback, &arg, cc4group.MemoryManagement.Reference, cc4group_initChunkBufferCallback, cc4group_deinitChunkBufferCallback); } static bool cc4group_openExisting(CC4Group* const this, const char* const path) { assert(this); assert(path); if(strcmp(path, "-") == 0) { SET_BINARY(STDIN_FILENO); return cc4group_openFd(this, STDIN_FILENO); } return cc4group_uncompressGroupFromFile(this, path); } static void cc4group_unreference(CC4Group* const this) { assert(this); if(--this->referenceCounter == 0) { cc4group_delete(this); } } 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_BINARY | O_CREAT | O_EXCL, root->core.Executable ? 0755 : 0644); if(file == -1) { SET_ERRNO_ERROR("open: Creating target file"); return false; } write(file, cc4group_getOnlyEntryData(this, root), root->core.Size); if(close(file) == -1) { fprintf(stderr, "WARNING: close: Closing the extracted file \"%s\" failed: %s\n", targetPath, strerror(errno)); } struct utimbuf times = {.actime = root->core.Modified, .modtime = root->core.Modified}; if(utime(targetPath, ×) == -1) { fprintf(stderr, "WARNING: utime: 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(cc4group_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); } // sets *error to true if any error is set static const C4GroupEntryData* cc4group_getEntryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot, bool* error) { assert(this); assert(this->root.children); if(entryPath == NULL || *entryPath == '\0') { if(allowRoot) { return &this->root; } else { SET_MESSAGE_ERROR("The root directory is not allowed for this"); *error = true; return NULL; } } const C4GroupEntryData* currentParent = &this->root; char* path = strdup(entryPath); if(path == NULL) { *error = true; SET_ERRNO_ERROR("strdup: duplicating the path"); return NULL; } 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; } // TODO: lazy 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; } // sets error static const C4GroupEntryData* cc4group_getDirectoryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot) { // asserts are in cc4group_getEntryByPath bool error = false; const C4GroupEntryData* entry = cc4group_getEntryByPath(this, entryPath, allowRoot, &error); if(error) { return NULL; } if(entry == NULL) { SET_MESSAGE_ERROR("The dirctory was not found in the group file"); return NULL; } if(!entry->core.Directory) { SET_MESSAGE_ERROR("The specified path is a file, not a directory"); return NULL; } return entry; } // sets error static const C4GroupEntryData* cc4group_getFileByPath(CC4Group* const this, const char* const entryPath) { // asserts are in cc4group_getEntryByPath bool error = false; const C4GroupEntryData* entry = cc4group_getEntryByPath(this, entryPath, true, &error); if(error) { return NULL; } if(entry == NULL) { SET_MESSAGE_ERROR("The file was not found in the group file"); return NULL; } if(entry->core.Directory) { SET_MESSAGE_ERROR("The specified path is a directory, not a file"); return NULL; } return entry; } // sets error static const C4GroupEntryData* cc4group_getFileOrDirectoryByPath(CC4Group* const this, const char* const entryPath, bool allowRoot) { // asserts are in cc4group_getEntryByPath bool error = false; const C4GroupEntryData* entry = cc4group_getEntryByPath(this, entryPath, allowRoot, &error); if(error) { return false; } if(entry == NULL) { SET_MESSAGE_ERROR("The file or directory was not found in the group file"); return NULL; } return entry; } static bool cc4group_extractSingle(CC4Group* const this, const char* const entryPath, const char* const targetPath) { assert(this); assert(targetPath); const C4GroupEntryData* entry = cc4group_getFileOrDirectoryByPath(this, entryPath, true); if(entry == NULL) { return false; } return cc4group_extractChildren(this, entry, targetPath); } static const uint8_t* cc4group_getOnlyEntryData(CC4Group* const this, const C4GroupEntryData* entry) { // TODO: lazy (void)this; return entry->data; } static bool cc4group_getEntryData(CC4Group* const this, const char* const entryPath, const void** const data, size_t* size) { assert(this); assert(data); assert(size); const C4GroupEntryData* entry = cc4group_getFileByPath(this, entryPath); if(entry == NULL) { return false; } *data = cc4group_getOnlyEntryData(this, entry); *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(CC4Group* const this, C4GroupEntryData* const groupEntry) { if(groupEntry->core.Directory) { uint32_t crc = 0; ForeachGroupEntry(groupEntry->children) { cc4group_calculateEntryCRC(this, &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, cc4group_getOnlyEntryData(this, groupEntry), 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_flushBufferedWrite(WriteCallback* const callback) { bool success = true; if(callback->position > 0) { success = callback->callback(callback->buffer, callback->position, callback->arg); callback->position = 0; } return success; } static bool cc4group_bufferedWriteToCallback(WriteCallback* const callback, const void* const data, size_t size) { assert(callback); assert(data || size == 0); size_t dataPosition = 0; while(size > 0) { size_t amount = size - dataPosition; size_t bufferLeft = callback->bufferSize - callback->position; if(amount > bufferLeft) { amount = bufferLeft; } if(amount > 0) { memcpy(callback->buffer + callback->position, data + dataPosition, amount); callback->position += amount; dataPosition += amount; size -= amount; } assert(callback->position <= callback->bufferSize); if(callback->position == callback->bufferSize) { if(!cc4group_flushBufferedWrite(callback)) { return false; } } } return true; } static bool cc4group_deflateToCallback(WriteCallback* const callback, const void* const data, size_t size, int flushMode, int expectedRet) { assert(callback); assert(data || size == 0); callback->gzStream.next_in = (void*)data; callback->gzStream.avail_in = size; int ret = Z_BUF_ERROR; // skip the gzip magic if not done yet if(!callback->magicDone) { static uint8_t magicDummy[2]; callback->gzStream.next_out = magicDummy; callback->gzStream.avail_out = 2; callback->magicDone = true; ret = deflate(&callback->gzStream, Z_NO_FLUSH); } while(ret == Z_BUF_ERROR || callback->gzStream.avail_in > 0) { callback->gzStream.next_out = callback->buffer + callback->position; callback->gzStream.avail_out = callback->bufferSize - callback->position; ret = deflate(&callback->gzStream, flushMode); callback->position = callback->bufferSize - callback->gzStream.avail_out; if(callback->position == callback->bufferSize) { cc4group_flushBufferedWrite(callback); } if(ret == Z_STREAM_ERROR) { return false; } } assert(callback->gzStream.avail_in == 0); return ret == expectedRet; } static bool cc4group_writeEntries(CC4Group* const this, C4GroupEntryData* const entryData, WriteCallback* const callback) { C4GroupHeader header = *(C4GroupHeader*)cc4group_getOnlyEntryData(this, entryData); header.Entries = GroupEntryListSize(entryData->children); header.Ver1 = 1; header.Ver2 = 2; if(header.Creation == 0) { header.Creation = time(NULL); } memScrambleHeader((uint8_t*)&header); if(!cc4group_deflateToCallback(callback, &header, sizeof(header), Z_NO_FLUSH, Z_OK)) { SET_MESSAGE_ERROR("Failed writing a group header"); return false; } size_t offset = 0; ForeachGroupEntry(entryData->children) { entry->value.core.Offset = offset; entry->value.core.Packed = 1; if(!cc4group_deflateToCallback(callback, &entry->value.core, sizeof(entry->value.core), Z_NO_FLUSH, Z_OK)) { SET_MESSAGE_ERROR("Failed writing a group entry core"); return false; } offset += entry->value.core.Size; } ForeachGroupEntry(entryData->children) { if(entry->value.core.Directory) { if(!cc4group_writeEntries(this, &entry->value, callback)) { // error is set in the recursive call return false; } } else if(entry->value.core.Size > 0) { if(!cc4group_deflateToCallback(callback, cc4group_getOnlyEntryData(this, &entry->value), entry->value.core.Size, Z_NO_FLUSH, Z_OK)) { SET_MESSAGE_ERROR("Failed writing entry data"); return false; } } } return true; } static bool cc4group_saveWithWriteCallback(CC4Group* const this, CC4Group_WriteCallback const writeCallback, void* const arg, size_t maxBlockSize) { assert(this); assert(writeCallback); if(maxBlockSize == 0) { maxBlockSize = 1024 * 1024; } if(this->parent != NULL) { SET_MESSAGE_ERROR("Saving is currently not implemented for groups opened with openAsChild"); return false; } WriteCallback callback = { .callback = writeCallback, .arg = arg, .bufferSize = maxBlockSize, .position = 0, .buffer = malloc(maxBlockSize), .magicDone = false, .gzStream = { .zalloc = NULL, .zfree = NULL, .opaque = NULL, .next_in = NULL, .avail_in = 0, } }; if(callback.buffer == NULL) { SET_ERRNO_ERROR("malloc: allocating a buffer of maxBlockSize"); return false; } bool deflateStarted = false; int ret = deflateInit2(&callback.gzStream, 9, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY); if(ret != Z_OK) { SET_ZERROR_ERROR("deflateInit2", ret); goto ret; } deflateStarted = true; // write the real c4group magic instead static const uint8_t magic[] = {C4GroupMagic1, C4GroupMagic2}; if(!cc4group_bufferedWriteToCallback(&callback, magic, 2)) { SET_MESSAGE_ERROR("Failed writing the group magic"); goto ret; } bool success = false; cc4group_calculateEntrySizes(&this->root); cc4group_calculateEntryCRC(this, &this->root); if(!cc4group_writeEntries(this, &this->root, &callback)) { // error is set in cc4group_writeEntries goto ret; } if(!cc4group_deflateToCallback(&callback, NULL, 0, Z_FINISH, Z_STREAM_END)) { SET_MESSAGE_ERROR("Failed finishing the deflate stream"); goto ret; } if(!cc4group_flushBufferedWrite(&callback)) { SET_MESSAGE_ERROR("Failed writing the remaining buffered data at the end of the group"); goto ret; } success = true; ret: if(deflateStarted) { deflateEnd(&callback.gzStream); } if(callback.buffer != NULL) { free(callback.buffer); } return success; } static bool cc4group_writeToFdCallback(const void* const data, size_t const size, void* arg) { for(;;) { if(write(*((int*)arg), data, size) == -1) { if(errno != EINTR) { return false; } continue; } return true; } } static bool cc4group_writeToFilePointerCallback(const void* const data, size_t const size, void* arg) { return fwrite(data, 1, size, arg) == size; } static bool cc4group_saveToFilePointer(CC4Group* const this, FILE* file) { return cc4group_saveWithWriteCallback(this, cc4group_writeToFilePointerCallback, file, 0); } static bool cc4group_saveToFd(CC4Group* const this, int fd) { return cc4group_saveWithWriteCallback(this, cc4group_writeToFdCallback, &fd, 0); } static bool cc4group_saveIt(CC4Group* const this, const char* const path, bool const overwrite) { assert(this); assert(path); if(strcmp(path, "-") == 0) { SET_BINARY(STDOUT_FILENO); return cc4group_saveToFd(this, STDOUT_FILENO); } bool success = false; int file = open(path, O_WRONLY | O_BINARY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL), 0644); if(file == -1) { SET_ERRNO_ERROR("open: creating the target group file"); return false; } success = cc4group_saveWithWriteCallback(this, cc4group_writeToFdCallback, &file, 0); if(file != -1) { if(close(file) == -1) { fprintf(stderr, "WARNING: close: Closing the target group file failed: %s\n", strerror(errno)); } } if(!success) { if(unlink(path) == -1) { fprintf(stderr, "WARNING: unlink: Deleting the incomplete 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(info); const C4GroupEntryData* entry = cc4group_getFileOrDirectoryByPath(this, path, true); if(entry == NULL) { 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_getDirectoryByPath(this, path, true); if(entry == NULL) { 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, const 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; if(path == NULL || *path == '\0') { SET_MESSAGE_ERROR("The path must contain at least one parent folder separated with /"); return false; } char* myPath = strdup(path); if(myPath == NULL) { SET_ERRNO_ERROR("strdup: duplicating the path"); return false; } const char* parentPath; char* entryName = cc4group_splitParentAndChildPaths(myPath, &parentPath); parent = cc4group_getDirectoryByPath(this, parentPath, true); if(parent == NULL) { 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); 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(C4GroupEntryData* const directory, const C4GroupEntryData* const entry) { 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(newPath); bool error = false; if(cc4group_getEntryByPath(this, newPath, false, &error) != NULL) { SET_MESSAGE_ERROR("The desired target path already exists"); return false; } if(error) { return false; } const C4GroupEntryData* oldParent; GroupEntryListEntry* entry = NULL; if(!cc4group_getParentAndChildEntries(this, oldPath, &oldParent, &entry)) { return false; } char* targetPath = strdup(newPath); if(targetPath == NULL) { SET_ERRNO_ERROR("strdup: duplicating the new path"); return false; } const char* targetDirectory; char* targetName = cc4group_splitParentAndChildPaths(targetPath, &targetDirectory); C4GroupEntryData* newParent = (C4GroupEntryData*)cc4group_getDirectoryByPath(this, targetDirectory, true); if(newParent == NULL) { free(targetPath); return false; } C4GroupEntryCore_setFileName(&entry->value.core, targetName); cc4group_addEntryToDirectory(newParent, &entry->value); GroupEntryListRemove(oldParent->children, entry); free(targetPath); return true; } static C4GroupEntryData* cc4group_createEntry(CC4Group* const this, const char* const path) { bool error = false; if(cc4group_getEntryByPath(this, path, false, &error) != NULL) { SET_MESSAGE_ERROR("The desired target path already exists"); return NULL; } if(error) { return false; } char* targetPath = strdup(path); if(targetPath == NULL) { SET_ERRNO_ERROR("strdup: duplicating the path"); return NULL; } const char* targetDirectory; char* targetName = cc4group_splitParentAndChildPaths(targetPath, &targetDirectory); C4GroupEntryData* parent = (C4GroupEntryData*)cc4group_getDirectoryByPath(this, targetDirectory, true); if(parent == NULL) { free(targetPath); return NULL; } C4GroupEntryData entry; entry.data = NULL; entry.memoryManagement = cc4group.MemoryManagement.Reference; entry.children = GroupEntryListNew(); C4GroupEntryCore_init(&entry.core); C4GroupEntryCore_setFileName(&entry.core, targetName); free(targetPath); return cc4group_addEntryToDirectory(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; AddCleanUpJob(free, header); return true; } static bool cc4group_createFile(CC4Group* const this, const char* const path) { assert(this); assert(path); C4GroupEntryData* entry = cc4group_createEntry(this, path); if(entry == NULL) { return false; } return true; } static bool cc4group_setEntryData(CC4Group* const this, const char* const entryPath, const void* const data, size_t const size, CC4Group_MemoryManagement const memoryManagement) { assert(this); assert(entryPath); C4GroupEntryData* entry = (C4GroupEntryData*)cc4group_getFileByPath(this, entryPath); if(entry == NULL) { return false; } if(entry->data != NULL) { cc4group_applyMemoryManagementEnd(entry->memoryManagement, entry->data); entry->data = NULL; entry->core.Size = 0; entry->memoryManagement = cc4group.MemoryManagement.Reference; } if(data != NULL && size != 0) { const uint8_t* ownData = data; if(!cc4group_applyMemoryManagementStart(memoryManagement, &ownData, size)) { SET_ERRNO_ERROR("memoryManagement: allocating memory for copying the file data"); return false; } else { entry->data = (uint8_t*)ownData; } entry->core.Size = size; entry->memoryManagement = memoryManagement; } entry->core.HasCRC = C4GroupEntryCore_NoCRC; return true; } static bool cc4group_setExecutable(CC4Group* const this, bool const executable, const char* const path) { assert(this); C4GroupEntryData* entry = (C4GroupEntryData*)cc4group_getFileByPath(this, path); if(entry == NULL) { return false; } entry->core.Executable = executable ? 1 : 0; return true; } static CC4Group* cc4group_openAsChild(CC4Group* const this, const char* const path) { assert(this); const C4GroupEntryData* entry = cc4group_getDirectoryByPath(this, path, false); if(entry == NULL) { return NULL; } CC4Group* child = cc4group_new(); if(child == NULL) { SET_ERRNO_ERROR("malloc: allocating the child group object"); return NULL; } memcpy(&child->root.core, &entry->core, sizeof(child->root.core)); child->root.data = entry->data; child->root.core.Directory = 1; child->root.core.FileName[0] = '\0'; child->root.children = entry->children; child->parent = this; ++this->referenceCounter; CleanUpJobListAppend(child->cleanupJobs, (CC4Group_CleanupJob){.func = (CC4Group_CleanupFunc)cc4group_unreference, .data = this}); return child; } static void* cc4group_memoryManagementTakeStart(void* const data, size_t const size, void* const arg) { (void)size; (void)arg; return data; } static void cc4group_memoryManagementTakeEnd(void* const data, void* const arg) { (void)arg; free(data); } static void* cc4group_memoryManagementCopyStart(void* const data, size_t const size, void* const arg) { (void)arg; uint8_t* copy = malloc(size); if(copy == NULL) { return NULL; } memcpy(copy, data, size); return copy; } static void cc4group_memoryManagementCopyEnd(void* const data, void* const arg) { (void)arg; free(data); } static void* cc4group_memoryManagementReferenceStart(void* const data, size_t const size, void* const arg) { (void)size; (void)arg; return data; } static void cc4group_memoryManagementReferenceEnd(void* const data, void* const arg) { (void)data; (void)arg; } static CC4Group_MemoryManagement_t takeMemoryManagement = { .start = cc4group_memoryManagementTakeStart, .end = cc4group_memoryManagementTakeEnd, }; static CC4Group_MemoryManagement_t copyMemoryManagement = { .start = cc4group_memoryManagementCopyStart, .end = cc4group_memoryManagementCopyEnd, }; static CC4Group_MemoryManagement_t referenceMemoryManagement = { .start = cc4group_memoryManagementReferenceStart, .end = cc4group_memoryManagementReferenceEnd, }; CC4Group_API cc4group = { .MemoryManagement = { .Take = &takeMemoryManagement, .Copy = ©MemoryManagement, .Reference = &referenceMemoryManagement }, .TmpMemoryStrategies = { .Memory = cc4group_createTmpMemoryMalloc, .File = cc4group_createTmpMemoryFile, .Auto = cc4group_createTmpMemoryAuto }, .setTmpMemoryStrategy = cc4group_setTmpMemoryStrategy, .new = cc4group_new, .delete = cc4group_unreference, .create = cc4group_create, .openExisting = cc4group_openExisting, .openMemory = cc4group_openMemory, .openFd = cc4group_openFd, .openFilePointer = cc4group_openFilePointer, .openWithReadCallback = cc4group_uncompressGroup, .save = cc4group_save, .saveOverwrite = cc4group_saveOverwrite, .saveToFd = cc4group_saveToFd, .saveToFilePointer = cc4group_saveToFilePointer, .saveWithWriteCallback = cc4group_saveWithWriteCallback, .extractAll = cc4group_extractAll, .extractSingle = cc4group_extractSingle, .getEntryInfo = cc4group_getEntryInfo, .getEntryInfos = cc4group_getEntryInfos, .getEntryData = cc4group_getEntryData, .setEntryData = cc4group_setEntryData, .setMaker = cc4group_setMaker, .setCreation = cc4group_setCreation, .setOfficial = cc4group_setOfficial, .setExecutable = cc4group_setExecutable, .createDirectory = cc4group_createDirectory, .createFile = cc4group_createFile, .renameEntry = cc4group_renameEntry, .deleteEntry = cc4group_deleteEntry, .getErrorMessage = cc4group_getErrorMessage, .getErrorCode = cc4group_getErrorCode, .getErrorMethod = cc4group_getErrorMethod, .getErrorCauser = cc4group_getErrorCauser, .openAsChild = cc4group_openAsChild };