summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cc4group.c297
-rw-r--r--src/cc4group.h24
-rw-r--r--src/cppc4group.cpp15
-rw-r--r--src/cppc4group.hpp6
4 files changed, 284 insertions, 58 deletions
diff --git a/src/cc4group.c b/src/cc4group.c
index 484c9c5..82e8df5 100644
--- a/src/cc4group.c
+++ b/src/cc4group.c
@@ -103,6 +103,16 @@ struct CC4Group_t {
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)
{
if(management == Copy)
@@ -1546,7 +1556,103 @@ static void cc4group_calculateEntryCRC(CC4Group* const this, C4GroupEntryData* c
}
}
-static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData* const entryData, gzFile file)
+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_OK;
+
+ // 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);
+ }
+ else if(size == 0)
+ {
+ ret = Z_BUF_ERROR;
+ }
+
+ 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);
+ }
+ }
+
+ 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);
@@ -1560,11 +1666,9 @@ static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData
memScrambleHeader((uint8_t*)&header);
- if(gzwrite(file, &header, sizeof(header)) == 0)
+ if(!cc4group_deflateToCallback(callback, &header, sizeof(header), Z_NO_FLUSH, Z_OK))
{
- int code;
- gzerror(file, &code);
- SET_ZERROR_ERROR("gzwrite: writing a group header", code);
+ SET_MESSAGE_ERROR("Failed writing a group header");
return false;
}
@@ -1575,11 +1679,9 @@ static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData
entry->value.core.Offset = offset;
entry->value.core.Packed = 1;
- if(gzwrite(file, &entry->value.core, sizeof(entry->value.core)) == 0)
+ if(!cc4group_deflateToCallback(callback, &entry->value.core, sizeof(entry->value.core), Z_NO_FLUSH, Z_OK))
{
- int code;
- gzerror(file, &code);
- SET_ZERROR_ERROR("gzwrite: writing a group entry core", code);
+ SET_MESSAGE_ERROR("Failed writing a group entry core");
return false;
}
@@ -1590,15 +1692,17 @@ static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData
{
if(entry->value.core.Directory)
{
- cc4group_writeEntriesToGzFile(this, &entry->value, file);
+ if(!cc4group_writeEntries(this, &entry->value, callback))
+ {
+ // error is set in the recursive call
+ return false;
+ }
}
else if(entry->value.core.Size > 0)
{
- if(gzwrite(file, cc4group_getOnlyEntryData(this, &entry->value), entry->value.core.Size) == 0)
+ if(!cc4group_deflateToCallback(callback, cc4group_getOnlyEntryData(this, &entry->value), entry->value.core.Size, Z_NO_FLUSH, Z_OK))
{
- int code;
- gzerror(file, &code);
- SET_ZERROR_ERROR("gzwrite: writing entry data", code);
+ SET_MESSAGE_ERROR("Failed writing entry data");
return false;
}
}
@@ -1607,10 +1711,15 @@ static bool cc4group_writeEntriesToGzFile(CC4Group* const this, C4GroupEntryData
return true;
}
-static bool cc4group_saveIt(CC4Group* const this, const char* const path, bool const overwrite)
+static bool cc4group_saveWithWriteCallback(CC4Group* const this, CC4Group_WriteCallback const writeCallback, void* const arg, size_t maxBlockSize)
{
assert(this);
- assert(path);
+ assert(writeCallback);
+
+ if(maxBlockSize == 0)
+ {
+ maxBlockSize = 1024 * 1024;
+ }
if(this->parent != NULL)
{
@@ -1618,80 +1727,151 @@ static bool cc4group_saveIt(CC4Group* const this, const char* const path, bool c
return false;
}
- bool success = false;
- int file = open(path, O_WRONLY | O_BINARY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL), 0644);
+ 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(file == -1)
+ if(callback.buffer == NULL)
{
- SET_ERRNO_ERROR("open: creating the target group file");
+ SET_ERRNO_ERROR("malloc: allocating a buffer of maxBlockSize");
return false;
}
- int fdKeep = dup(file);
- if(fdKeep == -1)
+ bool deflateStarted = false;
+ int ret = deflateInit2(&callback.gzStream, 9, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
+ if(ret != Z_OK)
{
- SET_ERRNO_ERROR("dup: duplicating the opened group fd");
- return false;
+ SET_ZERROR_ERROR("deflateInit2", ret);
+ goto ret;
}
+ deflateStarted = true;
- gzFile gzfile = gzdopen(file, "wb");
- if(gzfile == NULL)
+ // write the real c4group magic instead
+ static const uint8_t magic[] = {C4GroupMagic1, C4GroupMagic2};
+ if(!cc4group_bufferedWriteToCallback(&callback, magic, 2))
{
- SET_ERRNO_ERROR("gzdopen: Opening the target group file");
+ SET_MESSAGE_ERROR("Failed writing the group magic");
goto ret;
}
- file = -1;
+
+ bool success = false;
cc4group_calculateEntrySizes(&this->root);
cc4group_calculateEntryCRC(this, &this->root);
- success = cc4group_writeEntriesToGzFile(this, &this->root, gzfile);
+ 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(file != -1)
+ if(deflateStarted)
{
- if(close(file) == -1)
- {
- fprintf(stderr, "WARNING: close: Closing the target group file failed: %s\n", strerror(errno));
- }
+ deflateEnd(&callback.gzStream);
}
- if(gzfile != NULL)
+ if(callback.buffer != NULL)
{
- int ret = gzclose(gzfile);
- if(ret != Z_OK)
- {
- fprintf(stderr, "WARNING: gzclose: Closing the gzFile failed: %s\n", zError(ret));
- }
+ free(callback.buffer);
}
- if(!success)
+ return success;
+}
+
+static bool cc4group_writeToFdCallback(const void* const data, size_t const size, void* arg)
+{
+ for(;;)
{
- if(unlink(path) == -1)
+ if(write(*((int*)arg), data, size) == -1)
{
- fprintf(stderr, "WARNING: unlink: Deleting the incomplete group file failed: %s\n", strerror(errno));
+ if(errno != EINTR)
+ {
+ return false;
+ }
+
+ continue;
}
+
+ return true;
}
- else
+}
+
+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)
{
- if(lseek(fdKeep, 0, SEEK_SET) != 0)
- {
- fprintf(stderr, "WARNING: lseek: Seeking to the group magic bytes failed: %s\n", strerror(errno));
- }
- else
+ 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)
{
- static uint8_t magic[2] = {C4GroupMagic1, C4GroupMagic2};
- if(write(fdKeep, magic, 2) != 2)
- {
- fprintf(stderr, "WARNING: write: Writing the correct group magic bytes failed: %s\n", strerror(errno));
- }
+ fprintf(stderr, "WARNING: close: Closing the target group file failed: %s\n", strerror(errno));
}
}
- if(fdKeep != -1)
+ if(!success)
{
- if(close(fdKeep) == -1)
+ if(unlink(path) == -1)
{
- fprintf(stderr, "WARNING: close: Closing the dup'ed target group file failed: %s\n", strerror(errno));
+ fprintf(stderr, "WARNING: unlink: Deleting the incomplete group file failed: %s\n", strerror(errno));
}
}
@@ -2150,6 +2330,9 @@ CC4Group_API cc4group = {
.save = cc4group_save,
.saveOverwrite = cc4group_saveOverwrite,
+ .saveToFd = cc4group_saveToFd,
+ .saveToFilePointer = cc4group_saveToFilePointer,
+ .saveWithWriteCallback = cc4group_saveWithWriteCallback,
.extractAll = cc4group_extractAll,
diff --git a/src/cc4group.h b/src/cc4group.h
index dd5664a..91344d5 100644
--- a/src/cc4group.h
+++ b/src/cc4group.h
@@ -150,6 +150,13 @@ typedef bool (*CC4Group_ReadCallback)(const void** const data, size_t* const siz
typedef bool (*CC4Group_ReadSetupCallback)(void* const arg);
+// callback type for saveWithWriteCallback
+// the callback needs to write the whole buffer (size bytes pointed to by data) to the desired place or copy it into it's own memory
+// after the callback returns, the buffer will be overwritten
+// the callback should return true on success and false on failure
+typedef bool (*CC4Group_WriteCallback)(const void* const data, size_t const size, void* const arg);
+
+
// this is the main API struct of cc4group
// it contains all available methods and constants
typedef struct {
@@ -230,6 +237,23 @@ typedef struct {
// be careful, any existing file will be overwritten in-place. if any error occurs after opening the target file (e.g. out of memory, a program or system crash), the previous contents will be lost
bool (*saveOverwrite)(CC4Group* const this, const char* const path);
+ // saves the compressed group file by writing it to the file descriptor
+ // the file descriptor must have been opened with write access and must be in blocking mode (should be default if O_NONBLOCK is not specified)
+ // also be aware that the file must be opened with binary mode on windows
+ bool (*saveToFd)(CC4Group* const this, int fd);
+
+ // saves the compressed group file by writing it to the FILE*
+ // the file must have been opened with read access; also be aware that the file must be opened with binary mode on windows
+ bool (*saveToFilePointer)(CC4Group* const this, FILE* file);
+
+ // saves the compressed group to a custom storage with the help of a writeCallback
+ // the callback will receive the custom arg argument as its arg argument at each call
+ // bufferSize denotes the maximum buffer size that the callback wants to receive
+ // be aware that cc4group will internally allocate a buffer of the given size
+ // bufferSize may be 0 to use the default of 1 MB
+ // writeCallback will be called whenever the internal buffer (of bufferSize) is full, and finally when the saving has finished with a smaller size to store the remaining data
+ bool (*saveWithWriteCallback)(CC4Group* const this, CC4Group_WriteCallback const writeCallback, void* const arg, size_t const bufferSize);
+
// extraction to disk
diff --git a/src/cppc4group.cpp b/src/cppc4group.cpp
index 409f2ef..5dc8799 100644
--- a/src/cppc4group.cpp
+++ b/src/cppc4group.cpp
@@ -125,6 +125,21 @@ bool CppC4Group::save(const std::string& path, const bool overwrite)
return (overwrite ? cc4group.saveOverwrite : cc4group.save)(p->g, path.c_str());
}
+bool CppC4Group::saveToFd(const int fd)
+{
+ return cc4group.saveToFd(p->g, fd);
+}
+
+bool CppC4Group::saveToFilePointer(FILE* file)
+{
+ return cc4group.saveToFilePointer(p->g, file);
+}
+
+bool CppC4Group::saveWithWriteCallback(const CppC4Group::WriteCallback callback, void *const arg, size_t bufferSize)
+{
+ return cc4group.saveWithWriteCallback(p->g, callback, arg, bufferSize);
+}
+
bool CppC4Group::extractAll(const std::string& path)
{
return cc4group.extractAll(p->g, path.c_str());
diff --git a/src/cppc4group.hpp b/src/cppc4group.hpp
index eb06585..03cb868 100644
--- a/src/cppc4group.hpp
+++ b/src/cppc4group.hpp
@@ -76,9 +76,10 @@ public:
// or an empty optional on failure
using TmpMemoryCallback = std::optional<TmpMemory>(*)(size_t size);
- // these two are equivalent to their C counterparts
+ // these are equivalent to their C counterparts
using ReadCallback = bool(*)(const void** const data, size_t* const size, void* const arg);
using SetupCallback = bool(*)(void* const arg);
+ using WriteCallback = bool(*)(const void* const data, size_t const size, void* const arg);
// these enums are just mapped to their C-counterparts internally
enum TmpMemoryStrategy {
@@ -119,6 +120,9 @@ public:
// save actually maps to both cc4group.save and cc4group.saveOverwrite, thanks to default arguments (yes, thats the reason why they are separate in the C-API)
bool save(const std::string& path, const bool overwrite = false);
+ bool saveToFd(const int fd);
+ bool saveToFilePointer(FILE* file);
+ bool saveWithWriteCallback(const WriteCallback callback, void* const arg = nullptr, size_t bufferSize = 0);
bool extractAll(const std::string& path);
bool extractSingle(const std::string& entryPath, const std::string& targetPath);