summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarkus Mittendrein <git@maxmitti.tk>2019-04-21 20:09:56 +0200
committerMarkus Mittendrein <git@maxmitti.tk>2019-04-21 20:13:18 +0200
commit41eabf6ac74a7c0e81912742764ebea3d0e1fe07 (patch)
treed68a7cba942e3eeecde633f2d2b2cc31ffca661c
parent69c6e8b4d300f99a31dcb4491557ffa2452bc243 (diff)
downloadcc4group-41eabf6ac74a7c0e81912742764ebea3d0e1fe07.tar.gz
cc4group-41eabf6ac74a7c0e81912742764ebea3d0e1fe07.zip
He made cc4group lazy and he saw that it was good
-rw-r--r--examples/c4copy.c1
-rw-r--r--examples/c4info.c2
-rw-r--r--examples/c4ls.c3
-rw-r--r--examples/c4ls_asChild.c3
-rw-r--r--examples/c4ls_buffer.c3
-rw-r--r--examples/unc4group.c1
-rw-r--r--src/cc4group.c476
-rw-r--r--src/cc4group.h24
-rw-r--r--src/cppc4group.cpp18
-rw-r--r--src/cppc4group.hpp4
10 files changed, 406 insertions, 129 deletions
diff --git a/examples/c4copy.c b/examples/c4copy.c
index 35e201b..3c0d092 100644
--- a/examples/c4copy.c
+++ b/examples/c4copy.c
@@ -14,6 +14,7 @@ int main(int argc, char* argv[])
bool success = true;
CC4Group* group = cc4group.new();
+ cc4group.setLazy(group, false);
if(!cc4group.openExisting(group, argv[1]))
{
fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group));
diff --git a/examples/c4info.c b/examples/c4info.c
index ef4ace0..09d0257 100644
--- a/examples/c4info.c
+++ b/examples/c4info.c
@@ -31,7 +31,7 @@ int main(int argc, char* argv[])
CC4Group_EntryInfo info;
// yes, this should be error checked, but this is only an example. and actually, if the root entry info can not be retrieved, there must be some serious problem somewhere else
// anyway, the worst thing that can happen would be printing some random uninitialized data to stdout. what a nice surprise!
- cc4group.getEntryInfo(group, "", &info);
+ cc4group.getEntryInfo(group, "", &info, false);
puts(argv[1]);
printf("Created: %s\n", formatTime(info.modified));
diff --git a/examples/c4ls.c b/examples/c4ls.c
index 58cc29e..20b1aca 100644
--- a/examples/c4ls.c
+++ b/examples/c4ls.c
@@ -55,6 +55,7 @@ int main(int argc, char* argv[])
}
CC4Group* group = cc4group.new();
+ cc4group.setLazy(group, false);
bool success = cc4group.openExisting(group, argv[1]);
if(!success)
{
@@ -65,7 +66,7 @@ int main(int argc, char* argv[])
CC4Group_EntryInfo* infos;
size_t entries;
- success = cc4group.getEntryInfos(group, argc == 3 ? argv[2] : NULL, &infos, &entries);
+ success = cc4group.getEntryInfos(group, argc == 3 ? argv[2] : NULL, &infos, &entries, false);
if(!success)
{
diff --git a/examples/c4ls_asChild.c b/examples/c4ls_asChild.c
index 8a9d00b..4aef787 100644
--- a/examples/c4ls_asChild.c
+++ b/examples/c4ls_asChild.c
@@ -55,6 +55,7 @@ int main(int argc, char* argv[])
}
CC4Group* group = cc4group.new();
+ cc4group.setLazy(group, false);
bool success = cc4group.openExisting(group, argv[1]);
if(!success)
{
@@ -76,7 +77,7 @@ int main(int argc, char* argv[])
CC4Group_EntryInfo* infos;
size_t entries;
- success = cc4group.getEntryInfos(child, NULL, &infos, &entries);
+ success = cc4group.getEntryInfos(child, NULL, &infos, &entries, false);
if(!success)
{
diff --git a/examples/c4ls_buffer.c b/examples/c4ls_buffer.c
index 7280afd..df63cd6 100644
--- a/examples/c4ls_buffer.c
+++ b/examples/c4ls_buffer.c
@@ -100,6 +100,7 @@ int main(int argc, char* argv[])
}
CC4Group* group = cc4group.new();
+ cc4group.setLazy(group, false);
bool success = cc4group.openMemory(group, buffer, pos, cc4group.MemoryManagement.Take);
if(!success)
{
@@ -110,7 +111,7 @@ int main(int argc, char* argv[])
CC4Group_EntryInfo* infos;
size_t entries;
- success = cc4group.getEntryInfos(group, argc == 2 ? argv[1] : NULL, &infos, &entries);
+ success = cc4group.getEntryInfos(group, argc == 2 ? argv[1] : NULL, &infos, &entries, false);
if(!success)
{
diff --git a/examples/unc4group.c b/examples/unc4group.c
index 8d08eb3..ae6aaeb 100644
--- a/examples/unc4group.c
+++ b/examples/unc4group.c
@@ -12,6 +12,7 @@ int main(int argc, char* argv[])
}
CC4Group* group = cc4group.new();
+ cc4group.setLazy(group, false);
if(!cc4group.openExisting(group, argv[1]))
{
fprintf(stderr, "ERROR: Can not open group file \"%s\": %s\n", argv[1], cc4group.getErrorMessage(group));
diff --git a/src/cc4group.c b/src/cc4group.c
index 5b3e888..500d377 100644
--- a/src/cc4group.c
+++ b/src/cc4group.c
@@ -61,6 +61,8 @@ typedef struct C4GroupEntryData_t {
struct list_GroupEntryList* children;
struct C4GroupEntryData_t* parent;
+
+ size_t absolutePosition;
} C4GroupEntryData;
LIST_AUTO(C4GroupEntryData, GroupEntryList)
@@ -95,6 +97,23 @@ struct CC4Group_t {
size_t referenceCounter;
CC4Group* parent;
+
+ struct {
+ struct {
+ CC4Group_ReadCallback read;
+ CC4Group_ReadSetupCallback setup;
+ CC4Group_ReadSetupCallback deinit;
+ CC4Group_MemoryManagement memoryManagement;
+ void* arg;
+ } callback;
+ bool active;
+ size_t position;
+ z_stream gzStrm;
+ const uint8_t* lastData;
+ CleanUpJobList* cleanupJobs;
+ } readState;
+
+ bool lazy;
};
typedef struct {
@@ -109,7 +128,7 @@ typedef struct {
static bool cc4group_applyMemoryManagementStart(CC4Group_MemoryManagement const management, const uint8_t** data, size_t size)
{
- void* newData = management->start((void*)*data, size, management->arg);
+ void* newData = management->start(data == NULL ? NULL : (void*)*data, size, management->arg);
if(newData == NULL)
{
return false;
@@ -223,10 +242,48 @@ static void memScrambleHeader(uint8_t* const data)
}
}
+static bool cc4group_checkHeader(CC4Group* const this, C4GroupHeader* const header)
+{
+ if(header->Ver1 != 1 || header->Ver2 > 2)
+ {
+ SET_MESSAGE_ERROR("Unsupported group version. Only versions 1.0 up to 1.2 are supported.");
+ return false;
+ }
+
+ if(memcmp(C4GroupId, header->id, sizeof(header->id)) != 0)
+ {
+ SET_MALFORMED_MESSAGE_ERROR("The group id does not match.");
+ return false;
+ }
+
+ // make sure the maker is null terminated
+ header->Maker[C4GroupMaxMaker + 1] = '\0';
+
+ return true;
+}
+
static bool buildChildren(CC4Group* const this, C4GroupEntryData* const entry, size_t const dataSize)
{
+ // only if it's read already
+ if(this->readState.position < entry->absolutePosition + sizeof(C4GroupHeader))
+ {
+ entry->header = NULL;
+ return true;
+ }
+
+ if(!cc4group_checkHeader(this, entry->header))
+ {
+ return false;
+ }
C4GroupHeader* header = entry->header;
+ // only if it's read already
+ if(this->readState.position < entry->absolutePosition + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries)
+ {
+ entry->children = NULL;
+ return true;
+ }
+
entry->children = GroupEntryListNew();
uint8_t* data = entry->data + sizeof(C4GroupHeader);
@@ -243,9 +300,14 @@ static bool buildChildren(CC4Group* const this, C4GroupEntryData* const entry, s
return false;
}
- C4GroupEntryData* childEntry = &GroupEntryListAppend(entry->children, (C4GroupEntryData){.core = *core, .data = childData + core->Offset, .memoryManagement = cc4group.MemoryManagement.Reference, .children = NULL, .parent = entry})->value;
+ C4GroupEntryData* childEntry = &GroupEntryListAppend(entry->children, (C4GroupEntryData){.core = *core, .data = childData + core->Offset, .memoryManagement = cc4group.MemoryManagement.Reference, .children = NULL, .parent = entry, .absolutePosition = entry->absolutePosition + childDataOffset + core->Offset})->value;
+
+ if(this->readState.position < childEntry->absolutePosition + sizeof(C4GroupHeader))
+ {
+ childEntry->data = NULL;
- if(core->Directory)
+ }
+ else if(core->Directory)
{
memScrambleHeader(childEntry->data);
if(!buildChildren(this, childEntry, core->Size))
@@ -424,6 +486,7 @@ static CC4Group_TmpMemoryStrategy cc4group_tmpMemoryStrategy = cc4group_createTm
static bool cc4group_buildGroupHierarchy(CC4Group* const this)
{
this->root.data = this->uncompressedData;
+ this->root.absolutePosition = 0;
bool ret = buildChildren(this, &this->root, this->uncompressedSize);
this->root.core.Directory = 1;
this->root.core.FileName[0] = '\0';
@@ -503,6 +566,77 @@ static bool cc4group_inflateFillOutput(z_stream* const strm, CC4Group_ReadCallba
return false;
}
+static void cc4group_setLazy(CC4Group* const this, const bool lazy)
+{
+ assert(this->root.children == NULL);
+
+ this->lazy = lazy;
+}
+
+static void cc4group_cleanupReadState(CC4Group* const this)
+{
+ ForeachCleanupJob(this->readState.cleanupJobs)
+ {
+ job->value.func(job->value.data);
+ }
+ this->readState.cleanupJobs = NULL;
+
+ if(this->readState.lastData != NULL && this->lazy)
+ {
+ cc4group_applyMemoryManagementEnd(this->readState.callback.memoryManagement, this->readState.lastData);
+ }
+
+ // indicate end of prolonged use (for reference counting)
+ cc4group_applyMemoryManagementEnd(this->readState.callback.memoryManagement, NULL);
+
+ if(this->readState.callback.deinit != NULL)
+ {
+ if(!this->readState.callback.deinit(this->readState.callback.arg))
+ {
+ fprintf(stderr, "WARNING: cc4group_cleanupReadState: the deinitCallback failed\n");
+ }
+ }
+
+ inflateEnd(&this->readState.gzStrm);
+
+ this->readState.active = false;
+}
+
+static bool cc4group_uncompressUntilPosition(CC4Group* const this, size_t const position)
+{
+ CC4Group* rootGroup = this;
+ while(rootGroup->parent != NULL)
+ {
+ rootGroup = rootGroup->parent;
+ }
+
+ assert(rootGroup);
+ assert(rootGroup->readState.active);
+ assert(position <= rootGroup->uncompressedSize);
+
+ if(rootGroup->readState.position < position)
+ {
+ rootGroup->readState.gzStrm.avail_out = position - rootGroup->readState.position;
+
+ bool eof = false;
+ int inflateRet;
+ if(!cc4group_inflateFillOutput(&rootGroup->readState.gzStrm, rootGroup->readState.callback.read, rootGroup->readState.callback.arg, rootGroup->readState.callback.memoryManagement, &eof, &rootGroup->readState.lastData, &inflateRet))
+ {
+ SET_ZERROR_ERROR("inflate: inflating the group contents", inflateRet);
+ cc4group_cleanupReadState(rootGroup);
+ return false;
+ }
+
+ if(position == rootGroup->uncompressedSize)
+ {
+ cc4group_cleanupReadState(rootGroup);
+ }
+
+ rootGroup->readState.position = position;
+ }
+ return true;
+}
+
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);
@@ -531,7 +665,6 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
size_t totalReadSize = 0;
- const uint8_t* readData = NULL;
const uint8_t* readDataAfterMagic = NULL;
size_t readSize = 0;
bool eof = false;
@@ -540,21 +673,21 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
while(!eof && totalReadSize < 2)
{
- if(readData != NULL)
+ if(this->readState.lastData != NULL)
{
- cc4group_applyMemoryManagementEnd(memoryManagement, readData);
+ cc4group_applyMemoryManagementEnd(memoryManagement, this->readState.lastData);
}
- readData = NULL;
+ this->readState.lastData = NULL;
readSize = 0;
- eof = readCallback((const void**)&readData, &readSize, callbackArg);
+ eof = readCallback((const void**)&this->readState.lastData, &readSize, callbackArg);
if(readSize == 0)
{
continue;
}
- if(!cc4group_applyMemoryManagementStart(memoryManagement, &readData, readSize))
+ if(!cc4group_applyMemoryManagementStart(memoryManagement, &this->readState.lastData, readSize))
{
SET_ZERROR_ERROR("reading the group magic", CC4GROUP_MEM_ERROR);
goto ret;
@@ -563,13 +696,13 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
size_t newTotalReadSize = totalReadSize + readSize;
if(totalReadSize < 1 && newTotalReadSize > 0)
{
- magic1 = readData[0];
+ magic1 = this->readState.lastData[0];
}
if(totalReadSize < 2 && newTotalReadSize > 1)
{
- magic2 = readData[1 - totalReadSize];
- readDataAfterMagic = readData + (2 - totalReadSize);
+ magic2 = this->readState.lastData[1 - totalReadSize];
+ readDataAfterMagic = this->readState.lastData + (2 - totalReadSize);
}
totalReadSize = newTotalReadSize;
@@ -589,15 +722,13 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
goto ret;
}
- z_stream strm = {
- .zalloc = NULL,
- .zfree = NULL,
- .opaque = NULL,
- .next_in = magic,
- .avail_in = 2
- };
+ this->readState.gzStrm.zalloc = NULL;
+ this->readState.gzStrm.zfree = NULL;
+ this->readState.gzStrm.opaque = NULL;
+ this->readState.gzStrm.next_in = magic;
+ this->readState.gzStrm.avail_in = 2;
- int ret = inflateInit2(&strm, 15 + 16); // window size 15 + automatic gzip
+ int ret = inflateInit2(&this->readState.gzStrm, 15 + 16); // window size 15 + automatic gzip
inflateStarted = true;
if(ret != Z_OK)
@@ -615,11 +746,11 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
goto ret;
}
- strm.next_out = (Bytef*)header;
- strm.avail_out = currentSize;
+ this->readState.gzStrm.next_out = (Bytef*)header;
+ this->readState.gzStrm.avail_out = currentSize;
- ret = inflate(&strm, Z_SYNC_FLUSH);
- if(ret != Z_OK && (ret != Z_STREAM_END || strm.avail_in != 0))
+ ret = inflate(&this->readState.gzStrm, Z_SYNC_FLUSH);
+ if(ret != Z_OK && (ret != Z_STREAM_END || this->readState.gzStrm.avail_in != 0))
{
SET_ZERROR_ERROR("inflate: inflating the magic", ret);
goto ret;
@@ -627,11 +758,11 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
if(totalReadSize > 2)
{
- strm.next_in = (uint8_t*)readDataAfterMagic;
- strm.avail_in = totalReadSize - 2;
+ this->readState.gzStrm.next_in = (uint8_t*)readDataAfterMagic;
+ this->readState.gzStrm.avail_in = totalReadSize - 2;
}
- if(!cc4group_inflateFillOutput(&strm, readCallback, callbackArg, memoryManagement, &eof, &readData, &ret))
+ if(!cc4group_inflateFillOutput(&this->readState.gzStrm, readCallback, callbackArg, memoryManagement, &eof, &this->readState.lastData, &ret))
{
SET_ZERROR_ERROR("inflate: inflating the group header", ret);
goto ret;
@@ -643,7 +774,7 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
{
// the group is empty
// so the stream should have ended also
- if(ret != Z_STREAM_END || !eof || strm.avail_in > 0)
+ if(ret != Z_STREAM_END || !eof || this->readState.gzStrm.avail_in > 0)
{
SET_MALFORMED_MESSAGE_ERROR("The group is empty but the gzip stream has not ended yet.");
goto ret;
@@ -655,21 +786,11 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
goto ret;
}
- if(header->Ver1 != 1 || header->Ver2 > 2)
+ if(!cc4group_checkHeader(this, header))
{
- 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);
@@ -681,10 +802,10 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
C4GroupEntryCore* cores = (C4GroupEntryCore*)((uint8_t*)(header) + sizeof(C4GroupHeader));
- strm.next_out = (Bytef*)cores;
- strm.avail_out = sizeof(C4GroupEntryCore) * header->Entries;
+ this->readState.gzStrm.next_out = (Bytef*)cores;
+ this->readState.gzStrm.avail_out = sizeof(C4GroupEntryCore) * header->Entries;
- if(!cc4group_inflateFillOutput(&strm, readCallback, callbackArg, memoryManagement, &eof, &readData, &ret))
+ if(!cc4group_inflateFillOutput(&this->readState.gzStrm, readCallback, callbackArg, memoryManagement, &eof, &this->readState.lastData, &ret))
{
SET_ZERROR_ERROR("inflate: inflating the group entry dictionary", ret);
goto ret;
@@ -712,55 +833,82 @@ static bool cc4group_uncompressGroup(CC4Group* const this, CC4Group_ReadCallback
// 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;
- }
+ this->readState.gzStrm.next_out = data;
+ this->readState.gzStrm.avail_out = uncompressedSize;
- if(strm.avail_in > 0)
+ if(!this->lazy)
{
- // 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;
+ if(!cc4group_inflateFillOutput(&this->readState.gzStrm, readCallback, callbackArg, memoryManagement, &eof, &this->readState.lastData, &ret))
+ {
+ SET_ZERROR_ERROR("inflate: inflating the group contents", ret);
+ goto ret;
+ }
-ret:
- if(deinitCallback != NULL)
- {
- if(!deinitCallback(callbackArg))
+ if(this->readState.gzStrm.avail_in > 0)
{
- fprintf(stderr, "WARNING: cc4group_uncompressGroup: the deinitCallback failed\n");
+ // 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;
}
}
- if(readData != NULL)
- {
- cc4group_applyMemoryManagementEnd(memoryManagement, readData);
- }
+ retData = uncompressedData;
+ret:
if(header != NULL)
{
free(header);
}
- if(inflateStarted)
+ if(retData == NULL)
{
- inflateEnd(&strm);
- }
+ if(deinitCallback != NULL)
+ {
+ if(!deinitCallback(callbackArg))
+ {
+ fprintf(stderr, "WARNING: cc4group_uncompressGroup: the deinitCallback failed\n");
+ }
+ }
- if(retData == NULL && uncompressedData != NULL)
- {
- tmpCleanup.func(tmpCleanup.data);
+ if(this->readState.lastData != NULL)
+ {
+ cc4group_applyMemoryManagementEnd(memoryManagement, this->readState.lastData);
+ }
+
+ if(inflateStarted)
+ {
+ inflateEnd(&this->readState.gzStrm);
+ }
+
+ if(uncompressedData != NULL)
+ {
+ tmpCleanup.func(tmpCleanup.data);
+ }
}
- else if(uncompressedData != NULL)
+ else
{
- CleanUpJobListPrepend(this->cleanupJobs, tmpCleanup);
+ if(uncompressedData != NULL)
+ {
+ CleanUpJobListPrepend(this->cleanupJobs, tmpCleanup);
+ }
+
+ this->readState.position = this->readState.gzStrm.next_out - retData;
+ this->readState.callback.setup = initCallback;
+ this->readState.callback.read = readCallback;
+ this->readState.callback.deinit = deinitCallback;
+ this->readState.callback.arg = callbackArg;
+ this->readState.callback.memoryManagement = memoryManagement;
+ if(this->lazy)
+ {
+ this->readState.active = true;
+
+ // indicate prolonged use (for reference counting)
+ cc4group_applyMemoryManagementStart(memoryManagement, NULL, 0);
+ }
+ else
+ {
+ cc4group_cleanupReadState(this);
+ }
}
this->uncompressedData = retData;
@@ -808,14 +956,20 @@ static bool cc4group_initChunkBufferCallback(void* callbackArg)
static bool cc4group_deinitChunkBufferCallback(void* callbackArg)
{
free(((ChunkedReadData*)callbackArg)->buffer);
+ free(callbackArg);
return true;
}
+typedef struct {
+ ChunkedReadData chunkedData;
+ int fd;
+} FdReadData;
+
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);
+ FdReadData* arg = callbackArg;
+ void* buffer = arg->chunkedData.buffer;
+ ssize_t count = read(arg->fd, buffer, CHUNK_SIZE);
if(count > 0)
{
@@ -856,21 +1010,33 @@ 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)
+ if(subRoot == NULL || cc4group_getChildren(this, subRoot) == NULL)
{
return false;
}
+ this->realRoot = this->root;
this->root = *subRoot;
}
return true;
}
+static bool cc4group_cleanupMappedGroupFile(void* const arg)
+{
+ CompleteDataReadCallbackArg* data = arg;
+ if(cc4group_munmap((void*)data->data, data->size) == -1)
+ {
+ fprintf(stderr, "WARNING: munmap: Unmapping the group file failed: %s\n", strerror(errno));
+ }
+
+ free(data);
+
+ return true;
+}
+
static bool cc4group_uncompressGroupFromFile(CC4Group* const this, const char* const path)
{
uint8_t* mappedFile = MAP_FAILED;
@@ -946,8 +1112,17 @@ static bool cc4group_uncompressGroupFromFile(CC4Group* const this, const char* c
goto ret;
}
- CompleteDataReadCallbackArg data = {.data = mappedFile, .size = size};
- success = cc4group_uncompressGroup(this, cc4group_completeDataReadCallback, &data, cc4group.MemoryManagement.Reference, NULL, NULL);
+ CompleteDataReadCallbackArg* data = malloc(sizeof(CompleteDataReadCallbackArg));
+
+ if(data == NULL)
+ {
+ SET_ERRNO_ERROR("malloc: allocating memory for cleanup data failed");
+ goto ret;
+ }
+
+ *data = (CompleteDataReadCallbackArg){.data = mappedFile, .size = size};
+ success = cc4group_uncompressGroup(this, cc4group_completeDataReadCallback, data, cc4group.MemoryManagement.Reference, NULL, cc4group_cleanupMappedGroupFile);
+ mappedFile = MAP_FAILED;
ret:
if(mappedFile != MAP_FAILED)
@@ -974,6 +1149,7 @@ static void cc4group_init(CC4Group* const this)
this->referenceCounter = 1;
this->parent = NULL;
+ this->lazy = true;
this->uncompressedData = NULL;
this->uncompressedSize = 0;
@@ -991,6 +1167,11 @@ static void cc4group_init(CC4Group* const this)
this->error.formatter.data = NULL;
this->error.formatter.formatter = cc4group_noerrorFormatter;
this->error.lastFormattedMessage = NULL;
+
+ this->readState.active = false;
+ this->readState.lastData = NULL;
+ this->readState.cleanupJobs = CleanUpJobListNew();
+ AddCleanUpJob(CleanUpJobListDestroy, this->readState.cleanupJobs);
}
static bool cc4group_setMakerRecursively(CC4Group* const this, const C4GroupEntryData* groupEntry, const char* const maker)
@@ -1210,8 +1391,15 @@ 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);
+ FdReadData* arg = malloc(sizeof(FdReadData));
+ if(arg == NULL)
+ {
+ SET_ERRNO_ERROR("malloc: Allocating memory for temporary data");
+ return false;
+ }
+
+ arg->fd = fd;
+ return cc4group_uncompressGroup(this, cc4group_readFdReadCallback, arg, cc4group.MemoryManagement.Reference, cc4group_initChunkBufferCallback, cc4group_deinitChunkBufferCallback);
}
static bool cc4group_openFilePointer(CC4Group* const this, FILE* file)
@@ -1219,8 +1407,15 @@ 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);
+ ChunkedReadData* arg = malloc(sizeof(ChunkedReadData));
+ if(arg == NULL)
+ {
+ SET_ERRNO_ERROR("malloc: Allocating memory for temporary data");
+ return false;
+ }
+ 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)
@@ -1251,6 +1446,11 @@ static void cc4group_delete(CC4Group* const this)
{
assert(this);
+ if(this->readState.active)
+ {
+ cc4group_cleanupReadState(this);
+ }
+
ForeachCleanupJob(this->cleanupJobs)
{
job->value.func(job->value.data);
@@ -1331,7 +1531,6 @@ static bool cc4group_extractChildren(CC4Group* const this, const C4GroupEntryDat
if(root->core.Directory)
{
- assert(root->children);
if(cc4group_mkdir(tmpPath, 0755) == -1 /*&& errno != EEXIST*/)
{
SET_ERRNO_ERROR("mkdir: Creating target directory");
@@ -1547,28 +1746,63 @@ static bool cc4group_extractSingle(CC4Group* const this, const char* const entry
static const uint8_t* cc4group_getOnlyEntryData(CC4Group* const this, const C4GroupEntryData* entry)
{
- // TODO: lazy
- (void)this;
+ if(entry->data == NULL)
+ {
+ if(!cc4group_uncompressUntilPosition(this, entry->absolutePosition + entry->core.Size))
+ {
+ return NULL;
+ }
+
+ ((C4GroupEntryData*)entry)->data = this->uncompressedData + entry->absolutePosition;
+ }
return entry->data;
}
-static GroupEntryList* cc4group_getChildren(CC4Group* const this, const C4GroupEntryData* entry)
+static C4GroupHeader* cc4group_getHeader(CC4Group* const this, const C4GroupEntryData* entry)
{
- // TODO: lazy
- (void)this;
+ assert(entry->core.Directory);
- return entry->children;
+ if(entry->header == NULL)
+ {
+ if(!cc4group_uncompressUntilPosition(this, entry->absolutePosition + sizeof(C4GroupHeader)))
+ {
+ return NULL;
+ }
+
+ ((C4GroupEntryData*)entry)->data = this->uncompressedData + entry->absolutePosition;
+
+ memScrambleHeader(entry->data);
+ if(!cc4group_checkHeader(this, entry->header))
+ {
+ return NULL;
+ }
+ }
+
+ return entry->header;
}
-static C4GroupHeader* cc4group_getHeader(CC4Group* const this, const C4GroupEntryData* entry)
+static GroupEntryList* cc4group_getChildren(CC4Group* const this, const C4GroupEntryData* entry)
{
- // TODO: lazy
- (void)this;
+ if(entry->children == NULL)
+ {
+ if(cc4group_getHeader(this, entry) == NULL)
+ {
+ return NULL;
+ }
- assert(entry->core.Directory);
+ if(!cc4group_uncompressUntilPosition(this, entry->absolutePosition + sizeof(C4GroupHeader) + entry->header->Entries * sizeof(C4GroupEntryCore)))
+ {
+ return NULL;
+ }
- return entry->header;
+ if(!buildChildren(this, (C4GroupEntryData*)entry, entry->core.Size))
+ {
+ return NULL;
+ }
+ }
+
+ return entry->children;
}
static bool cc4group_getEntryData(CC4Group* const this, const char* const entryPath, const void** const data, size_t* size)
@@ -2037,10 +2271,10 @@ static bool cc4group_saveOverwrite(CC4Group* const this, const char* const path)
return cc4group_saveIt(this, path, true);
}
-static bool cc4group_getEntryInfoForEntry(CC4Group* const this, const C4GroupEntryData* const entry, CC4Group_EntryInfo* const info)
+static bool cc4group_getEntryInfoForEntry(CC4Group* const this, const C4GroupEntryData* const entry, CC4Group_EntryInfo* const info, bool const lazy)
{
- C4GroupHeader* header;
- if(entry->core.Directory)
+ C4GroupHeader* header = NULL;
+ if(entry->core.Directory && !(this->lazy && lazy))
{
header = cc4group_getHeader(this, entry);
if(header == NULL)
@@ -2051,19 +2285,19 @@ static bool cc4group_getEntryInfoForEntry(CC4Group* const this, const C4GroupEnt
*info = (CC4Group_EntryInfo){
.fileName = entry->core.FileName,
- .modified = entry->core.Directory ? header->Creation : entry->core.Modified,
- .author = entry->core.Directory ? header->Maker : entry->parent->header->Maker,
- .size = entry->core.Directory ? (sizeof(C4GroupHeader) + header->Entries * sizeof(C4GroupEntryCore)) : (size_t)entry->core.Size,
+ .modified = header != NULL ? header->Creation : entry->core.Modified,
+ .author = header != NULL ? header->Maker : entry->parent->header->Maker,
+ .size = header != NULL ? (sizeof(C4GroupHeader) + 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 ? header : entry->parent->header) // parents header is already loaded to access this child
+ .official = C4GroupHeader_isOfficial(header != NULL ? header : entry->parent->header) // parents header is already loaded to access this child
};
return true;
}
-static bool cc4group_getEntryInfo(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info)
+static bool cc4group_getEntryInfo(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info, bool const lazy)
{
assert(this);
assert(info);
@@ -2074,10 +2308,10 @@ static bool cc4group_getEntryInfo(CC4Group* const this, const char* const path,
return false;
}
- return cc4group_getEntryInfoForEntry(this, entry, info);
+ return cc4group_getEntryInfoForEntry(this, entry, info, lazy);
}
-static bool cc4group_getEntryInfos(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* size)
+static bool cc4group_getEntryInfos(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* size, bool const lazy)
{
assert(this);
assert(infos);
@@ -2114,17 +2348,18 @@ static bool cc4group_getEntryInfos(CC4Group* const this, const char* const path,
return false;
}
- *infos = myInfos;
+ CC4Group_EntryInfo* infosStart = myInfos;
ForeachGroupEntry(children)
{
- if(!cc4group_getEntryInfoForEntry(this, &entry->value, myInfos++))
+ if(!cc4group_getEntryInfoForEntry(this, &entry->value, myInfos++, lazy))
{
- free(myInfos);
+ free(infosStart);
return false;
}
}
+ *infos = infosStart;
return true;
}
@@ -2251,7 +2486,7 @@ static C4GroupEntryData* cc4group_addEntryToDirectory(CC4Group* const this, C4Gr
GroupEntryList* directoryChildren = cc4group_getChildren(this, directory);
if(directoryChildren == NULL)
{
- return NULL; // TODO: error checking in callees of this
+ return NULL;
}
C4GroupEntryData* newEntry = &GroupEntryListAppend(directoryChildren, *entry)->value;
@@ -2492,6 +2727,7 @@ static CC4Group* cc4group_openAsChild(CC4Group* const this, const char* const pa
child->root.core.Directory = 1;
child->root.core.FileName[0] = '\0';
child->root.children = cc4group_getChildren(this, entry);
+ child->uncompressedData = this->uncompressedData;
if(child->root.children == NULL)
{
@@ -2518,11 +2754,19 @@ static void* cc4group_memoryManagementTakeStart(void* const data, size_t const s
static void cc4group_memoryManagementTakeEnd(void* const data, void* const arg)
{
(void)arg;
- free(data);
+ if(data != NULL)
+ {
+ free(data);
+ }
}
static void* cc4group_memoryManagementCopyStart(void* const data, size_t const size, void* const arg)
{
+ if(data == NULL && size == 0)
+ {
+ return NULL;
+ }
+
(void)arg;
uint8_t* copy = malloc(size);
if(copy == NULL)
@@ -2536,7 +2780,10 @@ static void* cc4group_memoryManagementCopyStart(void* const data, size_t const s
static void cc4group_memoryManagementCopyEnd(void* const data, void* const arg)
{
(void)arg;
- free(data);
+ if(data != NULL)
+ {
+ free(data);
+ }
}
static void* cc4group_memoryManagementReferenceStart(void* const data, size_t const size, void* const arg)
@@ -2587,6 +2834,7 @@ CC4Group_API cc4group = {
.new = cc4group_new,
.delete = cc4group_unreference,
+ .setLazy = cc4group_setLazy,
.create = cc4group_create,
.openExisting = cc4group_openExisting,
diff --git a/src/cc4group.h b/src/cc4group.h
index 2309338..c8ff6f6 100644
--- a/src/cc4group.h
+++ b/src/cc4group.h
@@ -29,7 +29,7 @@
// the directory separator used for all entry paths in cc4group is "/" _regardless of the platform_. yes, you better watch out Windows users!
// in contrast to paths names are only the the name of the entry itself, not the whole path
// - to free all resources used by the group when it is not needed anymore, call cc4group.delete with the group pointer and discard it afterwards (i.e. don't use it anymore)
-// - all functions, except cc4group.new, cc4group.openAsChild, cc4group.delete and cc4group.setTmpMemoryStrategy (where the latter two can't fail) follow the same scheme
+// - all functions, except cc4group.new, cc4group.setLazy, cc4group.openAsChild, cc4group.delete and cc4group.setTmpMemoryStrategy (where the latter two can't fail) follow the same scheme
// all of them can fail, either caused by wrong arguments or by things outside of the applications control
// they all return _true_ if everything went well and _false_ if any error occured
// information about the error (incredibly useful for debugging or asking for help with problems) can be obtained by using one the cc4group.getError* functions
@@ -142,6 +142,8 @@ typedef void* (*CC4Group_TmpMemoryStrategy)(CC4Group* const this, const size_t s
// and of an end function, which does the actual cleanup
// and an optional custom arg that will be passed to both functions
// additionally to the predefined memory management strategies, custom strategies may be used
+// the start and end functions must have well-defined behavior when data = NULL and size = 0 (at the same time)
+// as this is used to signal the start and end of a prolonged use of the memory management strategy (needed for reference counting in cppc4group's custom memory management implementation)
typedef struct {
void* (*start)(void* const data, const size_t size, void* const arg);
void (*end)(void* const data, void* const arg);
@@ -213,6 +215,12 @@ typedef struct {
// child relations are tracked through reference counting, thus the group will be really destructed when all referencing child groups are gone
void (*delete)(CC4Group* const this);
+ // de-/activates the group's lazy mode
+ // in lazy mode, only as much data is read and uncompressed as needed by the latest call that needs some data
+ // by default the lazy mode is activated
+ // if you know that the whole group needs to be read anyway (e.g. for saving it later) it's a little bit faster in the end to disable the lazy mode
+ // if only a few data are needed from the group, the lazy mode may be significantly faster, depending on where the data is actually positioned in the group
+ void (*setLazy)(CC4Group* const this, const bool lazy);
// after creating a group with new, exactly one of create, openExisting, openMemory, openFd, openFilePointer or openWithReadCallback must be called before any other action on the group object may be executed (except delete)
// initializes the group to be a fresh, empty group
@@ -224,7 +232,7 @@ typedef struct {
// opens a group that is stored entirely in memory
// see the description of MemoryManagement to know if and when data has to be freed by the caller
// if the lazy mode is not used, the data can be freed immediately after this function returns
- // HINT: the lazy mode is actually not implemented yet, but at least I can't forget to mention it here anymore when it's actually done
+ // but if the lazy mode is used, the data can only be freed after the whole group has been read, or the cc4group object is deleted
bool (*openMemory)(CC4Group* const this, const void* const groupData, size_t const size, CC4Group_MemoryManagement const memoryManagement);
// opens a group through a file descriptor
@@ -240,6 +248,7 @@ typedef struct {
// initCallback is called before readCallback is called and deinitCallback is called after all read operations are done
// initCallback and deinitCallback may be NULL if they should not be used
// even when using the Reference memory management, the buffer returned by the readCallback may be reused across callback calls
+ // be careful, if the lazy mode is activated, all pointer arguments (most importantly callbackArg) passed in here needs to stay valid until the group object is deleted or the whole group has been read
bool (*openWithReadCallback)(CC4Group* const this, CC4Group_ReadCallback const readCallback, void* const callbackArg, CC4Group_MemoryManagement const memoryManagement, CC4Group_ReadSetupCallback const initCallback, CC4Group_ReadSetupCallback const deinitCallback);
@@ -283,14 +292,21 @@ typedef struct {
// retrieval of metadata about the stored files and directories
// stores all metadata known about the entry denoted by path into the CC4Group_EntryInfo struct pointed to by info, similar to stat
// an empty path "" or NULL will retrieve information about the root directory
- bool (*getEntryInfo)(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info);
+ // even if the group's lazy mode is activated, when getting the entry info of a directory, the directories group header must be loaded, which may be far inside the group...
+ // ..and requires to load everything up to that point, which may be quite slow
+ // if lazy is specified as true and the group's lazy mode is enabled, only information available from the parent group's header...
+ // ...and the entry's core will be used to fill out the entry info...
+ // ...this means that the following info may be inaccurate: modified, author, size, official
+ // if lazy is specified as false or the group has been loaded eagerly (i.e. not lazy), accurate information will be returned instead
+ bool (*getEntryInfo)(CC4Group* const this, const char* const path, CC4Group_EntryInfo* const info, bool const lazy);
// retrieves all metadata like getEntryInfo of all files and directories inside the directory denoted by path, similar to ls
// a pointer to a dynamically allocated array of all CC4Group_EntryInfo structs will be stored in infos
// the amount of infos (and thus the amount of files in the directory) is stored in size
// the caller must free the pointer stored in infos when it is not needed anymore
// an empty path "" or NULL will retrieve information about the root directory
- bool (*getEntryInfos)(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* const size);
+ // the lazy argument is the same as in getEntryInfo above
+ bool (*getEntryInfos)(CC4Group* const this, const char* const path, CC4Group_EntryInfo** const infos, size_t* const size, bool const lazy);
// data retrieval and manipulation
diff --git a/src/cppc4group.cpp b/src/cppc4group.cpp
index 04f7cef..71d9e0d 100644
--- a/src/cppc4group.cpp
+++ b/src/cppc4group.cpp
@@ -53,13 +53,21 @@ struct CppC4Group::MemoryManagement::Private {
{
auto customMemoryManagement = reinterpret_cast<CustomMemoryManagement*>(arg);
customMemoryManagement->referenceUsage();
+ if(data == nullptr && size == 0)
+ {
+ return nullptr;
+ }
+
return customMemoryManagement->start(data, size);
}
static void callEndAndCleanup(void* const data, void* const arg)
{
auto customMemoryManagement = reinterpret_cast<CustomMemoryManagement*>(arg);
- customMemoryManagement->end(data);
+ if(data != nullptr)
+ {
+ customMemoryManagement->end(data);
+ }
customMemoryManagement->dereferenceUsage();
}
@@ -346,10 +354,10 @@ namespace {
}
};
-std::optional<CppC4Group::EntryInfo> CppC4Group::getEntryInfo(const std::string& path)
+std::optional<CppC4Group::EntryInfo> CppC4Group::getEntryInfo(const std::string& path, bool lazy)
{
CC4Group_EntryInfo info;
- if(cc4group.getEntryInfo(p->g, path.c_str(), &info))
+ if(cc4group.getEntryInfo(p->g, path.c_str(), &info, lazy))
{
auto entryInfo = std::make_optional<CppC4Group::EntryInfo>();
fillEntryInfo(*entryInfo, info);
@@ -361,12 +369,12 @@ std::optional<CppC4Group::EntryInfo> CppC4Group::getEntryInfo(const std::string&
}
}
-std::optional<std::vector<CppC4Group::EntryInfo>> CppC4Group::getEntryInfos(const std::string& path)
+std::optional<std::vector<CppC4Group::EntryInfo>> CppC4Group::getEntryInfos(const std::string& path, bool lazy)
{
CC4Group_EntryInfo* infos;
size_t infoCount;
- if(cc4group.getEntryInfos(p->g, path.c_str(), &infos, &infoCount))
+ if(cc4group.getEntryInfos(p->g, path.c_str(), &infos, &infoCount, lazy))
{
auto entryInfos = std::make_optional<std::vector<CppC4Group::EntryInfo>>(infoCount);
diff --git a/src/cppc4group.hpp b/src/cppc4group.hpp
index d946cee..e69f9c2 100644
--- a/src/cppc4group.hpp
+++ b/src/cppc4group.hpp
@@ -161,8 +161,8 @@ public:
bool setOfficial(const bool official, const std::string& path = "", const bool recursive = false);
bool setExecutable(const bool executable, const std::string& path);
- std::optional<EntryInfo> getEntryInfo(const std::string& path = "");
- std::optional<std::vector<EntryInfo>> getEntryInfos(const std::string& path = "");
+ std::optional<EntryInfo> getEntryInfo(const std::string& path = "", bool lazy = false);
+ std::optional<std::vector<EntryInfo>> getEntryInfos(const std::string& path = "", bool lazy = false);
bool deleteEntry(const std::string& path, const bool recursive = false);
bool renameEntry(const std::string& oldPath, const std::string& newPath);