summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--examples/c4ls_buffer.c130
-rw-r--r--src/cc4group.c280
-rw-r--r--src/cc4group.h5
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);