diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cc4group.c | 297 | ||||
| -rw-r--r-- | src/cc4group.h | 24 | ||||
| -rw-r--r-- | src/cppc4group.cpp | 15 | ||||
| -rw-r--r-- | src/cppc4group.hpp | 6 |
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); |
