diff options
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | examples/c4ls_buffer.c | 130 | ||||
| -rw-r--r-- | src/cc4group.c | 280 | ||||
| -rw-r--r-- | src/cc4group.h | 5 |
4 files changed, 295 insertions, 121 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0adbb46..1762b16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ example(unc4group) example(c4copy) example(c4info) example(c4ls) +example(c4ls_buffer) example(c4touch) example(c4rm) example(c4mkdir) diff --git a/examples/c4ls_buffer.c b/examples/c4ls_buffer.c new file mode 100644 index 0000000..a021e61 --- /dev/null +++ b/examples/c4ls_buffer.c @@ -0,0 +1,130 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <locale.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "cc4group.h" + +const char* formatTime(int32_t time) +{ + time_t tTime = time; + static char ret[128]; + strftime(ret, sizeof(ret), "%c", localtime(&tTime)); + return ret; +} + +const char* formatSize(uint32_t size) +{ + static char ret[128]; + if(size < 1000) + { + snprintf(ret, 128, "%4ud", size); + return ret; + } + + static const char prefixes[] = " KMGTPE"; + + const char* prefix = prefixes; + uint32_t fract = 0; + + while(size >= 1000 && *(prefix + 1) != '\0') + { + ++prefix; + fract = size % 1000; + size /= 1000; + } + + if(size < 10) + { + snprintf(ret, 128, "%1.1f%c", (float)size + (float)fract / 1000, *prefix); + } + else + { + snprintf(ret, 128, "%3u%c", size, *prefix); + } + + return ret; +} + +int main(int argc, char* argv[]) +{ + if(argc > 2) + { + fprintf(stderr, "USAGE: %s [subgroup]\n", argv[0]); + fprintf(stderr, "Reads the group from stdin into memory and then opens it to list its contents (or the contents of a subgroup).\n"); + return EXIT_FAILURE; + } + + size_t size = 1024; + uint8_t* buffer = malloc(size); + if(buffer == NULL) + { + fprintf(stderr, "ERROR: Can not allocate memory for the buffer: \"%s\"\n", strerror(errno)); + return EXIT_FAILURE; + } + + size_t pos = 0; + for(;;) + { + ssize_t count = read(STDIN_FILENO, buffer + pos, size - pos); + if(count == 0) + { + break; + } + + if(count == -1) + { + fprintf(stderr, "ERROR: Reading stdin failed: \"%s\"\n", strerror(errno)); + free(buffer); + return EXIT_FAILURE; + } + + pos += count; + + if(pos >= size) + { + buffer = realloc(buffer, size *= 2); + + if(buffer == NULL) + { + fprintf(stderr, "ERROR: Can not allocate more memory for the buffer: \"%s\"\n", strerror(errno)); + return EXIT_FAILURE; + } + } + } + + CC4Group* group = cc4group.new(); + bool success = cc4group.openMemory(group, buffer, pos); + free(buffer); + if(!success) + { + fprintf(stderr, "ERROR: Can not open group file from stdin: %s\n", cc4group.getErrorMessage(group)); + } + else + { + CC4Group_EntryInfo* infos; + size_t entries; + + success = cc4group.getEntryInfos(group, argc == 2 ? argv[1] : NULL, &infos, &entries); + + if(!success) + { + fprintf(stderr, "ERROR: Can not list group entries: %s\n", cc4group.getErrorMessage(group)); + } + else + { + for(size_t i = 0; i < entries; ++i) + { + printf("%c %s %s %s\t%s\n", infos[i].directory ? 'd' : infos[i].executable ? 'x' : ' ', formatTime(infos[i].modified), formatSize(infos[i].totalSize), infos[i].fileName, infos[i].author); + } + + free(infos); + } + } + cc4group.delete(group); + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/cc4group.c b/src/cc4group.c index 29fe0d5..f3975ee 100644 --- a/src/cc4group.c +++ b/src/cc4group.c @@ -75,8 +75,6 @@ typedef struct { struct CC4Group_t { uint8_t* uncompressedData; size_t uncompressedSize; - const char* path; - const char* subPath; C4GroupEntryData root; C4GroupEntryData realRoot; @@ -329,98 +327,45 @@ static void* cc4group_createTmpMemoryAuto(CC4Group* const this, const size_t siz static CC4Group_TmpMemoryStrategy cc4group_tmpMemoryStrategy = cc4group_createTmpMemoryAuto; -static void cc4group_uncompressGroup(CC4Group* const this) +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_uncompressGroup(CC4Group* const this, const uint8_t* const compressedData, size_t const size) +{ + // only if the group is still empty + assert(this->root.children == NULL); + // declare variables here already for proper cleanup in error cases - uint8_t* retData = NULL; C4GroupHeader* header = NULL; - uint8_t* mappedFile = MAP_FAILED; - void* mapExtra; - uint8_t* mappedTmpFile = NULL; bool inflateStarted = false; + uint8_t* retData = NULL; size_t currentSize = 0; CC4Group_CleanupJob tmpCleanup; + uint8_t* mappedTmpFile = NULL; - char* path; - char* slash = NULL; - - this->path = path = strdup(this->path); - AddCleanupJob(free, (void*)this->path); - - int file = -1; - for(;;) - { - file = open(this->path, O_RDONLY | O_BINARY); - if(file != -1) - { - break; - } - - if(errno != ENOTDIR) - { - SET_ERRNO_ERROR("open: Opening group file"); - goto ret; - } - - char* oldSlash = slash; - - slash = strrchr(path, '/'); - - if(oldSlash != NULL) - { - *oldSlash = '/'; - } - - if(slash != NULL && slash != path) - { - *slash = '\0'; - this->subPath = slash + 1; - } - } - - struct stat st; - if(fstat(file, &st) == -1) - { - SET_ERRNO_ERROR("fstat: on the opened group file"); - goto ret; - } - - if(S_ISDIR(st.st_mode)) - { - SET_MESSAGE_ERROR("The specified group file is a directory"); - goto ret; - } - - off_t size = st.st_size; - mappedFile = cc4group_mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0, &mapExtra); - - 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; - } + static uint8_t magic[] = {0x1f, 0x8b}; - if((mappedFile[0] != C4GroupMagic1 && mappedFile[0] != 0x1f) || (mappedFile[1] != C4GroupMagic2 && mappedFile[1] != 0x8b)) + if((compressedData[0] != C4GroupMagic1 && compressedData[0] != magic[0]) || (compressedData[1] != C4GroupMagic2 && compressedData[1] != magic[1])) { SET_MESSAGE_ERROR("The file is not a valid group file. Magic bytes don't match."); goto ret; } - mappedFile[0] = 0x1f; - mappedFile[1] = 0x8b; - z_stream strm = { .zalloc = NULL, .zfree = NULL, .opaque = NULL, - .next_in = mappedFile, - .avail_in = size + .next_in = magic, + .avail_in = 2 }; int ret = inflateInit2(&strm, 15 + 16); // window size 15 + automatic gzip @@ -445,7 +390,16 @@ static void cc4group_uncompressGroup(CC4Group* const this) 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; + } + strm.next_in = (uint8_t*)compressedData + 2; // 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 + strm.avail_in = size - 2; + + ret = inflate(&strm, Z_SYNC_FLUSH); if(ret != Z_OK && (ret != Z_STREAM_END || strm.avail_in != 0)) { SET_ZERROR_ERROR("inflate: inflating the group header", ret); @@ -541,14 +495,6 @@ ret: inflateEnd(&strm); } - if(mappedFile != MAP_FAILED) - { - if(cc4group_munmap(mappedFile, size, mapExtra) == -1) - { - fprintf(stderr, "WARNING: munmap: Unmapping the group file failed: %s\n", strerror(errno)); - } - } - if(retData == NULL && mappedTmpFile != NULL) { tmpCleanup.func(tmpCleanup.data); @@ -560,18 +506,137 @@ ret: this->uncompressedData = retData; this->uncompressedSize = currentSize; + + if(retData == NULL) + { + return false; + } + return cc4group_buildGroupHierarchy(this); } -static bool cc4group_buildGroupHierarchy(CC4Group* const this) +static bool cc4group_openMemory(CC4Group* const this, const uint8_t* const compressedData, size_t const size) { - this->root.data = this->uncompressedData; - bool ret = buildChildren(this, &this->root, this->uncompressedSize); - this->root.core.Directory = 1; - this->root.core.FileName[0] = '\0'; + assert(this); + assert(compressedData); + assert(size); - AddCleanupJob(deleteChildren, this->root.children); + return cc4group_uncompressGroup(this, compressedData, size); +} - return ret; +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); + + 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; + void* mapExtra; + 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, &mapExtra); + + 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; + } + + success = cc4group_uncompressGroup(this, mappedFile, size); + +ret: + if(mappedFile != MAP_FAILED) + { + if(cc4group_munmap(mappedFile, size, mapExtra) == -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) @@ -580,8 +645,6 @@ static void cc4group_init(CC4Group* const this) this->uncompressedData = NULL; this->uncompressedSize = 0; - this->path = ""; - this->subPath = ""; this->root.data = NULL; this->root.freeData = false; @@ -767,33 +830,7 @@ static bool cc4group_openExisting(CC4Group* const this, const char* const path) { assert(this); - // only if the group is still empty - assert(this->root.children == NULL); - - this->path = path; - cc4group_uncompressGroup(this); - if(this->uncompressedData == NULL) - { - return false; - } - - cc4group_buildGroupHierarchy(this); - - if(*this->subPath != '\0') - { - this->realRoot = this->root; - - const C4GroupEntryData* subRoot = cc4group_getDirectoryByPath(this, this->subPath); - - if(subRoot == NULL) - { - return false; - } - - this->root = *subRoot; - } - - return true; + return cc4group_uncompressGroupFromFile(this, path); } static void cc4group_delete(CC4Group* const this) @@ -1650,6 +1687,7 @@ CC4Group_API cc4group = { .create = cc4group_create, .delete = cc4group_delete, .openExisting = cc4group_openExisting, + .openMemory = cc4group_openMemory, .save = cc4group_save, .saveOverwrite = cc4group_saveOverwrite, .extractAll = cc4group_extractAll, diff --git a/src/cc4group.h b/src/cc4group.h index 9c5625b..16c684a 100644 --- a/src/cc4group.h +++ b/src/cc4group.h @@ -36,9 +36,14 @@ typedef struct { bool (*create)(CC4Group* const this); void (*delete)(CC4Group* const this); + // opens a group on the filesystem; path may point to a directory inside a group // only open an existing group on a frehsly created group object bool (*openExisting)(CC4Group* const this, const char* const path); + // opens a group that is stored entirely in memory + // only open an in-memory group on a frehsly created group object + bool (*openMemory)(CC4Group* const this, const uint8_t* const groupData, size_t const size); + bool (*save)(CC4Group* const this, const char* const path); bool (*saveOverwrite)(CC4Group* const this, const char* const path); |
