Update fs.

This commit is contained in:
David Reid
2026-01-07 18:10:39 +10:00
parent 5f3de510b2
commit 44b847fbf8
2 changed files with 934 additions and 172 deletions
+669 -136
View File
@@ -3,14 +3,6 @@
#include "fs.h" #include "fs.h"
/* BEG fs_platform_detection.c */
#if defined(_WIN32)
#define FS_WIN32
#else
#define FS_POSIX
#endif
/* END fs_platform_detection.c */
#include <errno.h> #include <errno.h>
/* BEG fs_common_macros.c */ /* BEG fs_common_macros.c */
@@ -19,6 +11,16 @@
#include <string.h> #include <string.h>
#include <stdarg.h> #include <stdarg.h>
#if defined(__clang__) && defined(__has_attribute)
#if __has_attribute(suppress)
#define FS_SUPPRESS_CLANG_ANALYZER __attribute__((suppress))
#else
#define FS_SUPPRESS_CLANG_ANALYZER
#endif
#else
#define FS_SUPPRESS_CLANG_ANALYZER
#endif
/* BEG fs_va_copy.c */ /* BEG fs_va_copy.c */
#ifndef fs_va_copy #ifndef fs_va_copy
#if !defined(_MSC_VER) || _MSC_VER >= 1800 #if !defined(_MSC_VER) || _MSC_VER >= 1800
@@ -370,7 +372,7 @@ FS_API int fs_strnicmp(const char* str1, const char* str2, size_t count)
/* BEG fs_result.c */ /* BEG fs_result.c */
FS_API const char* fs_result_to_string(fs_result result) FS_API const char* fs_result_description(fs_result result)
{ {
switch (result) switch (result)
{ {
@@ -831,72 +833,9 @@ code. These are the differences:
Parameter ordering is the same as c89thread to make amalgamation easier. Parameter ordering is the same as c89thread to make amalgamation easier.
*/ */
/* BEG fs_thread_basic_types.c */
#if defined(FS_POSIX)
#ifndef FS_USE_PTHREAD
#define FS_USE_PTHREAD
#endif
#ifndef FS_NO_PTHREAD_IN_HEADER
#include <pthread.h>
typedef pthread_t fs_pthread_t;
typedef pthread_mutex_t fs_pthread_mutex_t;
#else
typedef fs_uintptr fs_pthread_t;
typedef union fs_pthread_mutex_t { char __data[40]; fs_uint64 __alignment; } fs_pthread_mutex_t;
#endif
#endif
/* END fs_thread_basic_types.c */
/* BEG fs_thread_mtx.h */
#if defined(FS_WIN32)
typedef struct
{
void* handle; /* HANDLE, CreateMutex(), CreateEvent() */
int type;
} fs_mtx;
#else
/*
We may need to force the use of a manual recursive mutex which will happen when compiling
on very old compilers, or with `-std=c89`.
*/
/* If __STDC_VERSION__ is not defined it means we're compiling in C89 mode. */
#if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && !defined(__STDC_VERSION__)
#define FS_USE_MANUAL_RECURSIVE_MUTEX
#endif
/* This is for checking if PTHREAD_MUTEX_RECURSIVE is available. */
#if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && (!defined(__USE_UNIX98) && !defined(__USE_XOPEN2K8))
#define FS_USE_MANUAL_RECURSIVE_MUTEX
#endif
#ifdef FS_USE_MANUAL_RECURSIVE_MUTEX
typedef struct
{
fs_pthread_mutex_t mutex; /* The underlying pthread mutex. */
fs_pthread_mutex_t guard; /* Guard for metadata (owner and recursionCount). */
fs_pthread_t owner;
int recursionCount;
int type;
} fs_mtx;
#else
typedef fs_pthread_mutex_t fs_mtx;
#endif
#endif
enum
{
fs_mtx_plain = 0x00000000,
fs_mtx_timed = 0x00000001,
fs_mtx_recursive = 0x00000002
};
/* END fs_thread_mtx.h */
/* BEG fs_thread_mtx.c */ /* BEG fs_thread_mtx.c */
#if defined(FS_WIN32) && !defined(FS_USE_PTHREAD) #if defined(FS_WIN32) && !defined(FS_USE_PTHREAD)
static int fs_mtx_init(fs_mtx* mutex, int type) FS_API int fs_mtx_init(fs_mtx* mutex, int type)
{ {
HANDLE hMutex; HANDLE hMutex;
@@ -929,7 +868,7 @@ static int fs_mtx_init(fs_mtx* mutex, int type)
return FS_SUCCESS; return FS_SUCCESS;
} }
static void fs_mtx_destroy(fs_mtx* mutex) FS_API void fs_mtx_destroy(fs_mtx* mutex)
{ {
if (mutex == NULL) { if (mutex == NULL) {
return; return;
@@ -938,7 +877,7 @@ static void fs_mtx_destroy(fs_mtx* mutex)
CloseHandle((HANDLE)mutex->handle); CloseHandle((HANDLE)mutex->handle);
} }
static int fs_mtx_lock(fs_mtx* mutex) FS_API int fs_mtx_lock(fs_mtx* mutex)
{ {
DWORD result; DWORD result;
@@ -954,7 +893,7 @@ static int fs_mtx_lock(fs_mtx* mutex)
return FS_SUCCESS; return FS_SUCCESS;
} }
static int fs_mtx_unlock(fs_mtx* mutex) FS_API int fs_mtx_unlock(fs_mtx* mutex)
{ {
BOOL result; BOOL result;
@@ -975,7 +914,7 @@ static int fs_mtx_unlock(fs_mtx* mutex)
return FS_SUCCESS; return FS_SUCCESS;
} }
#else #else
static int fs_mtx_init(fs_mtx* mutex, int type) FS_API int fs_mtx_init(fs_mtx* mutex, int type)
{ {
int result; int result;
@@ -1029,7 +968,7 @@ static int fs_mtx_init(fs_mtx* mutex, int type)
#endif #endif
} }
static void fs_mtx_destroy(fs_mtx* mutex) FS_API void fs_mtx_destroy(fs_mtx* mutex)
{ {
if (mutex == NULL) { if (mutex == NULL) {
return; return;
@@ -1051,7 +990,7 @@ static void fs_mtx_destroy(fs_mtx* mutex)
#endif #endif
} }
static int fs_mtx_lock(fs_mtx* mutex) FS_API int fs_mtx_lock(fs_mtx* mutex)
{ {
int result; int result;
@@ -1123,7 +1062,7 @@ static int fs_mtx_lock(fs_mtx* mutex)
#endif #endif
} }
static int fs_mtx_unlock(fs_mtx* mutex) FS_API int fs_mtx_unlock(fs_mtx* mutex)
{ {
int result; int result;
@@ -1551,17 +1490,6 @@ static void fs_backend_uninit(const fs_backend* pBackend, fs* pFS)
} }
} }
static fs_result fs_backend_ioctl(const fs_backend* pBackend, fs* pFS, int command, void* pArgs)
{
FS_ASSERT(pBackend != NULL);
if (pBackend->ioctl == NULL) {
return FS_NOT_IMPLEMENTED;
} else {
return pBackend->ioctl(pFS, command, pArgs);
}
}
static fs_result fs_backend_remove(const fs_backend* pBackend, fs* pFS, const char* pFilePath) static fs_result fs_backend_remove(const fs_backend* pBackend, fs* pFS, const char* pFilePath)
{ {
FS_ASSERT(pBackend != NULL); FS_ASSERT(pBackend != NULL);
@@ -2366,6 +2294,22 @@ static const char* fs_string_cstr(const fs_string* pString)
return pString->stack; return pString->stack;
} }
static char* fs_string_cstr_mutable(fs_string* pString)
{
FS_ASSERT(pString != NULL);
FS_ASSERT(pString->ref == NULL); /* Can't get a mutable string from a reference string. */
if (pString->ref != NULL) {
return NULL;
}
if (pString->heap != NULL) {
return pString->heap;
}
return pString->stack;
}
static size_t fs_string_len(const fs_string* pString) static size_t fs_string_len(const fs_string* pString)
{ {
return pString->len; return pString->len;
@@ -2670,6 +2614,10 @@ FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS)
result = FS_SUCCESS; result = FS_SUCCESS;
} }
if (result != FS_SUCCESS) {
return result;
}
*ppFS = pFS; *ppFS = pFS;
return FS_SUCCESS; return FS_SUCCESS;
} }
@@ -2719,15 +2667,6 @@ FS_API void fs_uninit(fs* pFS)
fs_free(pFS, &pFS->allocationCallbacks); fs_free(pFS, &pFS->allocationCallbacks);
} }
FS_API fs_result fs_ioctl(fs* pFS, int request, void* pArg)
{
if (pFS == NULL) {
return FS_INVALID_ARGS;
}
return fs_backend_ioctl(pFS->pBackend, pFS, request, pArg);
}
FS_API fs_result fs_remove(fs* pFS, const char* pFilePath, int options) FS_API fs_result fs_remove(fs* pFS, const char* pFilePath, int options)
{ {
fs_result result; fs_result result;
@@ -2800,7 +2739,7 @@ FS_API fs_result fs_rename(fs* pFS, const char* pOldPath, const char* pNewPath,
pMountPointNew = fs_find_best_write_mount_point(pFS, pNewPath, options, &realNewPath); pMountPointNew = fs_find_best_write_mount_point(pFS, pNewPath, options, &realNewPath);
if (pMountPointNew == NULL) { if (pMountPointNew == NULL) {
fs_string_free(&realNewPath, fs_get_allocation_callbacks(pFS)); fs_string_free(&realOldPath, fs_get_allocation_callbacks(pFS));
return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */ return FS_DOES_NOT_EXIST; /* Couldn't find a mount point. */
} }
@@ -2897,7 +2836,6 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options)
} }
FS_COPY_MEMORY(pRunningPathHeap, pRunningPathStack, runningPathLen); FS_COPY_MEMORY(pRunningPathHeap, pRunningPathStack, runningPathLen);
pRunningPath = pRunningPathHeap;
} else { } else {
char* pNewRunningPathHeap; char* pNewRunningPathHeap;
@@ -2908,8 +2846,10 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options)
return FS_OUT_OF_MEMORY; return FS_OUT_OF_MEMORY;
} }
pRunningPath = pNewRunningPathHeap; pRunningPathHeap = pNewRunningPathHeap;
} }
pRunningPath = pRunningPathHeap;
} }
FS_COPY_MEMORY(pRunningPath + runningPathLen, iSegment.pFullPath + iSegment.segmentOffset, iSegment.segmentLength); FS_COPY_MEMORY(pRunningPath + runningPathLen, iSegment.pFullPath + iSegment.segmentOffset, iSegment.segmentLength);
@@ -3973,7 +3913,13 @@ FS_API fs_result fs_file_duplicate(fs_file* pFile, fs_file** ppDuplicate)
return result; return result;
} }
return fs_backend_file_duplicate(fs_get_backend_or_default(fs_file_get_fs(pFile)), pFile, *ppDuplicate); result = fs_backend_file_duplicate(fs_get_backend_or_default(fs_file_get_fs(pFile)), pFile, *ppDuplicate);
if (result != FS_SUCCESS) {
fs_file_free(ppDuplicate);
return result;
}
return result;
} }
FS_API void* fs_file_get_backend_data(fs_file* pFile) FS_API void* fs_file_get_backend_data(fs_file* pFile)
@@ -5324,6 +5270,609 @@ FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, const vo
} }
/* "FSSRLZ1\0" */
#define FS_SERIALIZED_SIG_0 0x52535346
#define FS_SERIALIZED_SIG_1 0x00315A4C
static fs_result fs_serialize_directory(fs* pFS, const char* pDirectoryPath, const char* pBasePath, int options, fs_stream* pOutputStream, fs_stream* pTOCStream, fs_uint32* pTOCEntryCount, fs_uint64* pRunningFileOffset)
{
fs_result result;
fs_iterator* pIterator;
char padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
FS_ASSERT(pTOCEntryCount != NULL);
for (pIterator = fs_first(pFS, pDirectoryPath, options); pIterator != NULL; pIterator = fs_next(pIterator)) {
fs_string path = fs_string_new();
int pathLen;
const char* pTrimmedPath;
size_t trimmedPathLen;
fs_uint32 fileFlags;
fs_uint64 fileSize;
fs_uint64 fileOffset;
*pTOCEntryCount += 1;
/* Flags. */
fileFlags = 0;
if (pIterator->info.directory) {
fileFlags |= 0x1; /* Directory. */
}
result = fs_stream_write(pTOCStream, &fileFlags, 4, NULL);
if (result != FS_SUCCESS) {
return result;
}
/* Construct the full path. It's the full path, trimmed with the base path, that we store in the TOC. */
pathLen = fs_path_append(path.stack, sizeof(path.stack), pDirectoryPath, FS_NULL_TERMINATED, pIterator->pName, pIterator->nameLen);
if (pathLen < 0) {
return FS_ERROR;
}
path.len = (size_t)pathLen;
if (path.len > sizeof(path.stack)) {
result = fs_string_alloc(pathLen, fs_get_allocation_callbacks(pFS), &path);
if (result != FS_SUCCESS) {
return result;
}
fs_path_append(path.heap, path.len + 1, pDirectoryPath, FS_NULL_TERMINATED, pIterator->pName, pIterator->nameLen);
}
pTrimmedPath = fs_path_trim_base(fs_string_cstr(&path), fs_string_len(&path), pBasePath, FS_NULL_TERMINATED);
if (pTrimmedPath == NULL) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return FS_ERROR;
}
trimmedPathLen = strlen(pTrimmedPath);
/* Path Length. */
result = fs_stream_write(pTOCStream, &trimmedPathLen, 4, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
/* Path. */
result = fs_stream_write(pTOCStream, pTrimmedPath, trimmedPathLen, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
/* Null Terminator. */
result = fs_stream_write(pTOCStream, "\0", 1, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
/* Padding. */
result = fs_stream_write(pTOCStream, padding, FS_ALIGN(trimmedPathLen + 1, 8) - (trimmedPathLen + 1), NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
if (pIterator->info.directory) {
fileOffset = 0;
fileSize = 0;
} else {
fileOffset = *pRunningFileOffset;
fileSize = 0;
/* Open the file and transfer the data to the stream. */
{
fs_file* pFile;
char buffer[4096];
size_t bytesRead;
result = fs_file_open(pFS, fs_string_cstr(&path), FS_READ | options, &pFile);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
for (;;) {
result = fs_file_read(pFile, buffer, sizeof(buffer), &bytesRead);
if (result != FS_SUCCESS && result != FS_AT_END) {
fs_file_close(pFile);
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
if (bytesRead == 0) {
break;
}
result = fs_stream_write(pOutputStream, buffer, bytesRead, NULL);
if (result != FS_SUCCESS) {
fs_file_close(pFile);
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
fileSize += bytesRead;
if (result == FS_AT_END) {
break;
}
}
fs_file_close(pFile);
pFile = NULL;
}
*pRunningFileOffset += fileSize;
/* Add padding zeros to align the data to 8 bytes. */
result = fs_stream_write(pOutputStream, padding, FS_ALIGN(*pRunningFileOffset, 8) - *pRunningFileOffset, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
*pRunningFileOffset = FS_ALIGN(*pRunningFileOffset, 8);
}
/* File Size. */
result = fs_stream_write(pTOCStream, &fileSize, 8, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
/* File Offset. */
result = fs_stream_write(pTOCStream, &fileOffset, 8, NULL);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
/* If it was a directory we need to call this function recursively. */
if (pIterator->info.directory) {
result = fs_serialize_directory(pFS, fs_string_cstr(&path), pBasePath, options, pOutputStream, pTOCStream, pTOCEntryCount, pRunningFileOffset);
if (result != FS_SUCCESS) {
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
return result;
}
}
/* The full path is no longer needed. */
fs_string_free(&path, fs_get_allocation_callbacks(pFS));
}
return FS_SUCCESS;
}
FS_API fs_result fs_serialize(fs* pFS, const char* pDirectoryPath, int options, fs_stream* pOutputStream)
{
fs_result result;
fs_uint32 sig[2] = { FS_SERIALIZED_SIG_0, FS_SERIALIZED_SIG_1 };
fs_memory_stream toc;
fs_uint32 tocEntryCount = 0; /* <-- Must be initialized to zero. */
fs_uint64 runningOffset = 0; /* <-- Must be initialized to zero. */
fs_uint64 tocOffset;
fs_int64 initialPos;
if (pOutputStream == NULL) {
return FS_INVALID_ARGS;
}
if (pDirectoryPath == NULL) {
pDirectoryPath = "";
}
/* Initialize the TOC stream. We fill out the TOC stream separately, and then at the end we append it to the end of the main stream. */
result = fs_memory_stream_init_write(fs_get_allocation_callbacks(pFS), &toc);
if (result != FS_SUCCESS) {
return result;
}
/*
We want our data to be aligned to 8 bytes, so we'll grab the initial position of the stream and write some padding
to get our initial aligment set up.
*/
result = fs_stream_tell(pOutputStream, &initialPos);
if (result != FS_SUCCESS) {
fs_memory_stream_uninit(&toc);
return result;
}
result = fs_stream_write(pOutputStream, "\0\0\0\0\0\0\0\0", FS_ALIGN(initialPos, 8) - initialPos, NULL);
if (result != FS_SUCCESS) {
fs_memory_stream_uninit(&toc);
return result;
}
/* File Data. */
result = fs_serialize_directory(pFS, pDirectoryPath, pDirectoryPath, options, pOutputStream, (fs_stream*)&toc, &tocEntryCount, &runningOffset);
if (result != FS_SUCCESS) {
fs_memory_stream_uninit(&toc);
return result;
}
/* TOC. */
{
void* pTOCData;
size_t tocDataSize;
/* Grab the TOC offset for output later. */
tocOffset = runningOffset;
pTOCData = fs_memory_stream_take_ownership(&toc, &tocDataSize); /* <-- Should never fail. */
FS_ASSERT(pTOCData != NULL || tocDataSize == 0);
fs_memory_stream_uninit(&toc);
result = fs_stream_write(pOutputStream, pTOCData, tocDataSize, NULL);
fs_free(pTOCData, fs_get_allocation_callbacks(pFS));
if (result != FS_SUCCESS) {
fs_memory_stream_uninit(&toc);
return result;
}
runningOffset += tocDataSize;
}
/* Tail. */
{
char padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
fs_int64 baseOffset;
fs_uint32 reserved;
/* Padding to align to 8 bytes. */
result = fs_stream_write(pOutputStream, padding, FS_ALIGN(runningOffset, 8) - runningOffset, NULL);
if (result != FS_SUCCESS) {
return result;
}
runningOffset = FS_ALIGN(runningOffset, 8);
/*
We'll only be outputting 32 more bytes at this point:
Signature (8 bytes)
Base Offset (8 bytes)
TOC Offset (8 bytes)
TOC Entry Count (4 bytes)
Reserved (4 bytes)
*/
baseOffset = -(fs_int64)(runningOffset + 32);
/* Signature. */
result = fs_stream_write(pOutputStream, sig, 8, NULL);
if (result != FS_SUCCESS) {
return result;
}
/* Base Offset. */
result = fs_stream_write(pOutputStream, &baseOffset, 8, NULL);
if (result != FS_SUCCESS) {
return result;
}
/* TOC Offset. */
result = fs_stream_write(pOutputStream, &tocOffset, 8, NULL);
if (result != FS_SUCCESS) {
return result;
}
/* TOC Entry Count. */
result = fs_stream_write(pOutputStream, &tocEntryCount, 4, NULL);
if (result != FS_SUCCESS) {
return result;
}
/* Reserved. */
reserved = 0;
result = fs_stream_write(pOutputStream, &reserved, 4, NULL);
if (result != FS_SUCCESS) {
return result;
}
}
return FS_SUCCESS;
}
static fs_result fs_stream_read_u32_le(fs_stream* pStream, fs_uint32* pResult)
{
fs_result result;
fs_uint8 bytes[4];
result = fs_stream_read(pStream, bytes, 4, NULL);
if (result != FS_SUCCESS) {
return result;
}
*pResult = ((fs_uint32)bytes[0] << 0) | ((fs_uint32)bytes[1] << 8) | ((fs_uint32)bytes[2] << 16) | ((fs_uint32)bytes[3] << 24);
return FS_SUCCESS;
}
static fs_result fs_stream_read_u64_le(fs_stream* pStream, fs_uint64* pResult)
{
fs_result result;
fs_uint8 bytes[8];
result = fs_stream_read(pStream, bytes, 8, NULL);
if (result != FS_SUCCESS) {
return result;
}
*pResult =
((fs_uint64)bytes[0] << 0) | ((fs_uint64)bytes[1] << 8) | ((fs_uint64)bytes[2] << 16) | ((fs_uint64)bytes[3] << 24) |
((fs_uint64)bytes[4] << 32) | ((fs_uint64)bytes[5] << 40) | ((fs_uint64)bytes[6] << 48) | ((fs_uint64)bytes[7] << 56);
return FS_SUCCESS;
}
static fs_result fs_stream_read_s64_le(fs_stream* pStream, fs_int64* pResult)
{
fs_result result;
fs_uint64 temp;
result = fs_stream_read_u64_le(pStream, &temp);
if (result != FS_SUCCESS) {
return result;
}
*pResult = (fs_int64)temp;
return FS_SUCCESS;
}
FS_API fs_result fs_deserialize(fs* pFS, const char* pDirectoryPath, int options, fs_stream* pInputStream)
{
fs_result result;
fs_uint32 sig[2];
fs_int64 baseOffset;
fs_uint64 tocOffset;
fs_uint32 tocEntryCount;
fs_uint32 reserved;
fs_uint32 iEntry;
if (pInputStream == NULL) {
return FS_INVALID_ARGS;
}
if (pDirectoryPath == NULL) {
pDirectoryPath = "";
}
/* First thing we need to do is read the tail. */
result = fs_stream_seek(pInputStream, -32, FS_SEEK_END);
if (result != FS_SUCCESS) {
return result; /* Failed to seek to the tail. Either too small, or the stream does not support seeking from the end. */
}
/* Signature. */
result = fs_stream_read_u32_le(pInputStream, &sig[0]);
if (result != FS_SUCCESS) {
return result;
}
result = fs_stream_read_u32_le(pInputStream, &sig[1]);
if (result != FS_SUCCESS) {
return result;
}
if (sig[0] != FS_SERIALIZED_SIG_0 || sig[1] != FS_SERIALIZED_SIG_1) {
return FS_INVALID_DATA; /* Not a serialized stream. */
}
/* Base Offset (relative to end). */
result = fs_stream_read_s64_le(pInputStream, &baseOffset);
if (result != FS_SUCCESS) {
return result;
}
/* TOC Offset (local, relative to base). */
result = fs_stream_read_u64_le(pInputStream, &tocOffset);
if (result != FS_SUCCESS) {
return result;
}
/* TOC Entry Count. */
result = fs_stream_read_u32_le(pInputStream, &tocEntryCount);
if (result != FS_SUCCESS) {
return result;
}
/* Reserved (set to zero). */
result = fs_stream_read_u32_le(pInputStream, &reserved);
if (result != FS_SUCCESS) {
return result;
}
/* Now we can seek to the TOC. */
result = fs_stream_seek(pInputStream, baseOffset + (fs_int64)tocOffset, FS_SEEK_END);
if (result != FS_SUCCESS) {
return result;
}
/* To restore the files we just iterate over the TOC and read. */
for (iEntry = 0; iEntry < tocEntryCount; iEntry += 1) {
fs_uint32 flags;
fs_uint32 localPathLen;
fs_string localPath;
fs_string fullPath;
int fullPathLen;
fs_uint64 fileSize;
fs_uint64 fileOffset;
fs_int64 currentOffset;
fs_file* pFile = NULL;
/* Flags. */
result = fs_stream_read_u32_le(pInputStream, &flags);
if (result != FS_SUCCESS) {
return result;
}
/* Path Length. */
result = fs_stream_read_u32_le(pInputStream, &localPathLen);
if (result != FS_SUCCESS) {
return result;
}
/* Path. */
result = fs_string_alloc(localPathLen + 1, fs_get_allocation_callbacks(pFS), &localPath);
if (result != FS_SUCCESS) {
return result;
}
result = fs_stream_read(pInputStream, fs_string_cstr_mutable(&localPath), localPathLen + 1, NULL); /* +1 to read the null terminator in the same call. */
if (result != FS_SUCCESS) {
fs_string_free(&localPath, fs_get_allocation_callbacks(pFS));
return result;
}
if (fs_string_cstr(&localPath)[localPathLen] != '\0') {
fs_string_free(&localPath, fs_get_allocation_callbacks(pFS));
return FS_INVALID_DATA; /* Path is not null terminated. */
}
localPath.len = (size_t)localPathLen;
fullPath = fs_string_new();
fullPathLen = fs_path_append(fullPath.stack, sizeof(fullPath.stack), pDirectoryPath, FS_NULL_TERMINATED, fs_string_cstr(&localPath), fs_string_len(&localPath));
if (fullPathLen < 0) {
fs_string_free(&localPath, fs_get_allocation_callbacks(pFS));
return FS_ERROR;
}
fullPath.len = (size_t)fullPathLen;
if (fullPath.len > sizeof(fullPath.stack)) {
result = fs_string_alloc(fullPathLen, fs_get_allocation_callbacks(pFS), &fullPath);
if (result != FS_SUCCESS) {
fs_string_free(&localPath, fs_get_allocation_callbacks(pFS));
return result;
}
fs_path_append(fullPath.heap, fullPath.len + 1, pDirectoryPath, FS_NULL_TERMINATED, fs_string_cstr(&localPath), fs_string_len(&localPath));
}
fs_string_free(&localPath, fs_get_allocation_callbacks(pFS));
/* Padding. */
result = fs_stream_seek(pInputStream, FS_ALIGN(localPathLen + 1, 8) - (localPathLen + 1), FS_SEEK_CUR);
if (result != FS_SUCCESS) {
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
return result;
}
/* File Size. */
result = fs_stream_read_u64_le(pInputStream, &fileSize);
if (result != FS_SUCCESS) {
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
return result;
}
/* File Offset. */
result = fs_stream_read_u64_le(pInputStream, &fileOffset);
if (result != FS_SUCCESS) {
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
return result;
}
/* We've now read the TOC entry. We now have enough information to extract the data. */
result = fs_stream_tell(pInputStream, &currentOffset);
if (result != FS_SUCCESS) {
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
return result;
}
if ((flags & 0x1) != 0) {
/* Directory. */
result = fs_mkdir(pFS, fs_string_cstr(&fullPath), options);
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
if (result != FS_SUCCESS && result != FS_ALREADY_EXISTS) {
return result;
}
} else {
/* File. */
fs_uint64 bytesRemaining;
result = fs_file_open(pFS, fs_string_cstr(&fullPath), FS_WRITE | FS_TRUNCATE | options, &pFile);
fs_string_free(&fullPath, fs_get_allocation_callbacks(pFS));
if (result != FS_SUCCESS) {
return result;
}
/* Seek to the file data using base offset + local offset. */
result = fs_stream_seek(pInputStream, baseOffset + (fs_int64)fileOffset, FS_SEEK_END);
if (result != FS_SUCCESS) {
fs_file_close(pFile);
return result;
}
/* Copy the data across. */
bytesRemaining = fileSize;
while (bytesRemaining > 0) {
char buffer[4096];
size_t bytesRead;
fs_uint64 bytesToRead;
bytesToRead = bytesRemaining;
if (bytesToRead > sizeof(buffer)) {
bytesToRead = sizeof(buffer);
}
result = fs_stream_read(pInputStream, buffer, (size_t)bytesToRead, &bytesRead); /* Safe cast to size_t because it's clamped to the capacity of `buffer`. */
if (result != FS_SUCCESS && result != FS_AT_END) {
fs_file_close(pFile);
return result;
}
if (bytesRead == 0) {
break;
}
result = fs_file_write(pFile, buffer, bytesRead, NULL);
if (result != FS_SUCCESS) {
fs_file_close(pFile);
return result;
}
bytesRemaining -= bytesRead;
if (result == FS_AT_END) {
break;
}
}
fs_file_close(pFile);
pFile = NULL;
}
/* We need to seek back to where we were in the TOC so we can continue processing. */
result = fs_stream_seek(pInputStream, currentOffset, FS_SEEK_SET);
if (result != FS_SUCCESS) {
return result;
}
}
return FS_SUCCESS;
}
/* BEG fs_backend_posix.c */ /* BEG fs_backend_posix.c */
#if !defined(_WIN32) /* <-- Add any platforms that lack POSIX support here. */ #if !defined(_WIN32) /* <-- Add any platforms that lack POSIX support here. */
#define FS_SUPPORTS_POSIX #define FS_SUPPORTS_POSIX
@@ -5369,15 +5918,6 @@ static void fs_uninit_posix(fs* pFS)
(void)pFS; (void)pFS;
} }
static fs_result fs_ioctl_posix(fs* pFS, int op, void* pArg)
{
(void)pFS;
(void)op;
(void)pArg;
return FS_NOT_IMPLEMENTED;
}
static fs_result fs_remove_posix(fs* pFS, const char* pFilePath) static fs_result fs_remove_posix(fs* pFS, const char* pFilePath)
{ {
int result = remove(pFilePath); int result = remove(pFilePath);
@@ -5933,7 +6473,6 @@ static fs_backend fs_posix_backend =
fs_alloc_size_posix, fs_alloc_size_posix,
fs_init_posix, fs_init_posix,
fs_uninit_posix, fs_uninit_posix,
fs_ioctl_posix,
fs_remove_posix, fs_remove_posix,
fs_rename_posix, fs_rename_posix,
fs_mkdir_posix, fs_mkdir_posix,
@@ -6190,15 +6729,6 @@ static void fs_uninit_win32(fs* pFS)
(void)pFS; (void)pFS;
} }
static fs_result fs_ioctl_win32(fs* pFS, int op, void* pArg)
{
(void)pFS;
(void)op;
(void)pArg;
return FS_NOT_IMPLEMENTED;
}
static fs_result fs_remove_win32(fs* pFS, const char* pFilePath) static fs_result fs_remove_win32(fs* pFS, const char* pFilePath)
{ {
BOOL resultWin32; BOOL resultWin32;
@@ -6841,7 +7371,6 @@ static fs_backend fs_win32_backend =
fs_alloc_size_win32, fs_alloc_size_win32,
fs_init_win32, fs_init_win32,
fs_uninit_win32, fs_uninit_win32,
fs_ioctl_win32,
fs_remove_win32, fs_remove_win32,
fs_rename_win32, fs_rename_win32,
fs_mkdir_win32, fs_mkdir_win32,
@@ -8402,7 +8931,8 @@ FS_API fs_result fs_memory_stream_read(fs_memory_stream* pStream, void* pDst, si
FS_API fs_result fs_memory_stream_write(fs_memory_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten) FS_API fs_result fs_memory_stream_write(fs_memory_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten)
{ {
size_t newSize; size_t writeEndPosition;
size_t newDataSize;
if (pBytesWritten != NULL) { if (pBytesWritten != NULL) {
*pBytesWritten = 0; *pBytesWritten = 0;
@@ -8417,13 +8947,13 @@ FS_API fs_result fs_memory_stream_write(fs_memory_stream* pStream, const void* p
return FS_INVALID_OPERATION; return FS_INVALID_OPERATION;
} }
newSize = *pStream->pDataSize + bytesToWrite; /* Calculate where the write will end and resize if necessary. */
if (newSize > pStream->write.dataCap) { writeEndPosition = pStream->cursor + bytesToWrite;
/* Need to resize. */ if (writeEndPosition > pStream->write.dataCap) {
void* pNewBuffer; void* pNewBuffer;
size_t newCap; size_t newCap;
newCap = FS_MAX(newSize, pStream->write.dataCap * 2); newCap = FS_MAX(writeEndPosition, pStream->write.dataCap * 2);
pNewBuffer = fs_realloc(*pStream->ppData, newCap, &pStream->allocationCallbacks); pNewBuffer = fs_realloc(*pStream->ppData, newCap, &pStream->allocationCallbacks);
if (pNewBuffer == NULL) { if (pNewBuffer == NULL) {
return FS_OUT_OF_MEMORY; return FS_OUT_OF_MEMORY;
@@ -8433,10 +8963,15 @@ FS_API fs_result fs_memory_stream_write(fs_memory_stream* pStream, const void* p
pStream->write.dataCap = newCap; pStream->write.dataCap = newCap;
} }
FS_ASSERT(newSize <= pStream->write.dataCap); FS_ASSERT(writeEndPosition <= pStream->write.dataCap);
FS_COPY_MEMORY(FS_OFFSET_PTR(*pStream->ppData, *pStream->pDataSize), pSrc, bytesToWrite); /* Write out the data, starting from our cursor. */
*pStream->pDataSize = newSize; FS_COPY_MEMORY(FS_OFFSET_PTR(*pStream->ppData, pStream->cursor), pSrc, bytesToWrite);
pStream->cursor = writeEndPosition;
/* Update the data size if we wrote beyond the current end. */
newDataSize = FS_MAX(*pStream->pDataSize, writeEndPosition);
*pStream->pDataSize = newDataSize;
if (pBytesWritten != NULL) { if (pBytesWritten != NULL) {
*pBytesWritten = bytesToWrite; /* We always write all or nothing here. */ *pBytesWritten = bytesToWrite; /* We always write all or nothing here. */
@@ -8457,8 +8992,6 @@ FS_API fs_result fs_memory_stream_seek(fs_memory_stream* pStream, fs_int64 offse
return FS_INVALID_ARGS; /* Trying to seek too far. This will never happen on 64-bit builds. */ return FS_INVALID_ARGS; /* Trying to seek too far. This will never happen on 64-bit builds. */
} }
newCursor = pStream->cursor;
if (origin == FS_SEEK_SET) { if (origin == FS_SEEK_SET) {
newCursor = 0; newCursor = 0;
} else if (origin == FS_SEEK_CUR) { } else if (origin == FS_SEEK_CUR) {
@@ -8830,7 +9363,7 @@ static FS_ASAN fs_uint32 fs_strlen_limited(char const* s, fs_uint32 limit)
return (fs_uint32)(sn - s); return (fs_uint32)(sn - s);
} }
FS_API_SPRINTF_DEF int fs_vsprintfcb(fs_sprintf_callback* callback, void* user, char* buf, char const* fmt, va_list va) FS_SUPPRESS_CLANG_ANALYZER FS_API_SPRINTF_DEF int fs_vsprintfcb(fs_sprintf_callback* callback, void* user, char* buf, char const* fmt, va_list va)
{ {
static char hex[] = "0123456789abcdefxp"; static char hex[] = "0123456789abcdefxp";
static char hexu[] = "0123456789ABCDEFXP"; static char hexu[] = "0123456789ABCDEFXP";
@@ -9883,7 +10416,7 @@ static char* fs_clamp_callback(const char* buf, void* user, size_t len)
len = c->count; len = c->count;
if (len) { if (len) {
if (buf != c->buf) { if (buf != c->buf && buf != NULL) {
const char* s, *se; const char* s, *se;
char* d; char* d;
d = c->buf; d = c->buf;
+265 -36
View File
@@ -740,12 +740,6 @@ init
uninit uninit
This is where you should do any cleanup. Do not close the stream here. This is where you should do any cleanup. Do not close the stream here.
ioctl
This function is optional. You can use this to implement custom IO control commands. Return
`FS_INVALID_OPERATION` if the command is not recognized. The format of the `pArg` parameter is
command specific. If the backend does not need to implement this function, it can be left as
`NULL` or return `FS_NOT_IMPLEMENTED`.
remove remove
This function is used to delete a file or directory. This is not recursive. If the path is This function is used to delete a file or directory. This is not recursive. If the path is
a directory, the backend should return an error if it is not empty. Backends do not need to a directory, the backend should return an error if it is not empty. Backends do not need to
@@ -939,6 +933,14 @@ see some random tags and stuff in this file. These are just used for doing a dum
extern "C" { extern "C" {
#endif #endif
/* BEG fs_platform_detection.c */
#if defined(_WIN32)
#define FS_WIN32
#else
#define FS_POSIX
#endif
/* END fs_platform_detection.c */
/* BEG fs_compiler_compat.h */ /* BEG fs_compiler_compat.h */
#include <stddef.h> /* For size_t. */ #include <stddef.h> /* For size_t. */
#include <stdarg.h> /* For va_list. */ #include <stdarg.h> /* For va_list. */
@@ -1060,6 +1062,75 @@ typedef unsigned int fs_bool32;
/* END fs_compiler_compat.h */ /* END fs_compiler_compat.h */
/* BEG fs_thread_basic_types.h */
#if defined(FS_POSIX)
#ifndef FS_USE_PTHREAD
#define FS_USE_PTHREAD
#endif
#ifndef FS_NO_PTHREAD_IN_HEADER
#include <pthread.h>
typedef pthread_t fs_pthread_t;
typedef pthread_mutex_t fs_pthread_mutex_t;
#else
typedef fs_uintptr fs_pthread_t;
typedef union fs_pthread_mutex_t { char __data[40]; fs_uint64 __alignment; } fs_pthread_mutex_t;
#endif
#endif
/* END fs_thread_basic_types.h */
/* BEG fs_thread_mtx.h */
#if defined(FS_WIN32)
typedef struct
{
void* handle; /* HANDLE, CreateMutex(), CreateEvent() */
int type;
} fs_mtx;
#else
/*
We may need to force the use of a manual recursive mutex which will happen when compiling
on very old compilers, or with `-std=c89`.
*/
/* If __STDC_VERSION__ is not defined it means we're compiling in C89 mode. */
#if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && !defined(__STDC_VERSION__)
#define FS_USE_MANUAL_RECURSIVE_MUTEX
#endif
/* This is for checking if PTHREAD_MUTEX_RECURSIVE is available. */
#if !defined(FS_USE_MANUAL_RECURSIVE_MUTEX) && (!defined(__USE_UNIX98) && !defined(__USE_XOPEN2K8))
#define FS_USE_MANUAL_RECURSIVE_MUTEX
#endif
#ifdef FS_USE_MANUAL_RECURSIVE_MUTEX
typedef struct
{
fs_pthread_mutex_t mutex; /* The underlying pthread mutex. */
fs_pthread_mutex_t guard; /* Guard for metadata (owner and recursionCount). */
fs_pthread_t owner;
int recursionCount;
int type;
} fs_mtx;
#else
typedef fs_pthread_mutex_t fs_mtx;
#endif
#endif
enum
{
fs_mtx_plain = 0x00000000,
fs_mtx_timed = 0x00000001,
fs_mtx_recursive = 0x00000002
};
FS_API int fs_mtx_init(fs_mtx* mutex, int type);
FS_API void fs_mtx_destroy(fs_mtx* mutex);
FS_API int fs_mtx_lock(fs_mtx* mutex);
FS_API int fs_mtx_unlock(fs_mtx* mutex);
/* END fs_thread_mtx.h */
/* BEG fs_result.h */ /* BEG fs_result.h */
typedef enum typedef enum
{ {
@@ -1125,7 +1196,7 @@ typedef enum
FS_HAS_MORE_OUTPUT = 102 /* Some stream has more output data to be read, but there's not enough room in the output buffer. */ FS_HAS_MORE_OUTPUT = 102 /* Some stream has more output data to be read, but there's not enough room in the output buffer. */
} fs_result; } fs_result;
FS_API const char* fs_result_to_string(fs_result result); FS_API const char* fs_result_description(fs_result result);
/* END fs_result.h */ /* END fs_result.h */
@@ -1160,6 +1231,7 @@ The stream vtable can support both reading and writing, but it doesn't need to s
the same time. If one is not supported, simply leave the relevant `read` or `write` callback as the same time. If one is not supported, simply leave the relevant `read` or `write` callback as
`NULL`, or have them return FS_NOT_IMPLEMENTED. `NULL`, or have them return FS_NOT_IMPLEMENTED.
*/ */
typedef enum fs_seek_origin typedef enum fs_seek_origin
{ {
FS_SEEK_SET = 0, FS_SEEK_SET = 0,
@@ -1229,6 +1301,7 @@ appended to the end of the data.
For flexiblity in case the backend does not support cursor retrieval or positioning, the data will be read For flexiblity in case the backend does not support cursor retrieval or positioning, the data will be read
in fixed sized chunks. in fixed sized chunks.
*/ */
typedef enum fs_format typedef enum fs_format
{ {
FS_FORMAT_TEXT, FS_FORMAT_TEXT,
@@ -1443,6 +1516,42 @@ struct fs_iterator
fs_file_info info; fs_file_info info;
}; };
/*
Configuration structure for fs objects.
Members
-------
pBackend
The backend to use. If NULL, the standard file system backend will be used.
pBackendConfig
A pointer to a backend-specific configuration structure. This is passed directly to the
backend's init function. The documentation for your backend will tell you how to use this. Most
backends will allow you to set this to NULL.
pStream
A stream to use for archive backends. This is required for any file-backed backend, such as ZIP
archives. If the backend does not need a stream, this can be NULL.
pArchiveTypes
An array of archive types to register. This can be NULL if no archive types are to be
registered. Archive types are mapped to file extensions. See `fs_archive_type` for more
information.
archiveTypeCount
The number of archive types in the `pArchiveTypes` array. Set this to 0 if `pArchiveTypes` is
NULL.
onRefCountChanged
A callback that is fired when the reference count of a fs object changes.
pRefCountChangedUserData
User data that is passed to the `onRefCountChanged` callback.
pAllocationCallbacks
Custom allocation callbacks. If NULL, the standard malloc/realloc/free functions will be used.
*/
struct fs_config struct fs_config
{ {
const fs_backend* pBackend; const fs_backend* pBackend;
@@ -1464,7 +1573,6 @@ struct fs_backend
size_t (* alloc_size )(const void* pBackendConfig); size_t (* alloc_size )(const void* pBackendConfig);
fs_result (* init )(fs* pFS, const void* pBackendConfig, fs_stream* pStream); /* Return 0 on success or an errno result code on error. pBackendConfig is a pointer to a backend-specific struct. The documentation for your backend will tell you how to use this. You can usually pass in NULL for this. */ fs_result (* init )(fs* pFS, const void* pBackendConfig, fs_stream* pStream); /* Return 0 on success or an errno result code on error. pBackendConfig is a pointer to a backend-specific struct. The documentation for your backend will tell you how to use this. You can usually pass in NULL for this. */
void (* uninit )(fs* pFS); void (* uninit )(fs* pFS);
fs_result (* ioctl )(fs* pFS, int op, void* pArg); /* Optional. */
fs_result (* remove )(fs* pFS, const char* pFilePath); fs_result (* remove )(fs* pFS, const char* pFilePath);
fs_result (* rename )(fs* pFS, const char* pOldPath, const char* pNewPath); /* Return FS_DIFFERENT_DEVICE if the old and new paths are on different devices and would require a copy. */ fs_result (* rename )(fs* pFS, const char* pOldPath, const char* pNewPath); /* Return FS_DIFFERENT_DEVICE if the old and new paths are on different devices and would require a copy. */
fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_ALREADY_EXISTS if directory already exists. Return FS_DOES_NOT_EXIST if a parent directory does not exist. */ fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_ALREADY_EXISTS if directory already exists. Return FS_DOES_NOT_EXIST if a parent directory does not exist. */
@@ -1661,34 +1769,6 @@ fs_init()
FS_API void fs_uninit(fs* pFS); FS_API void fs_uninit(fs* pFS);
/*
Performs a control operation on the file system.
This is backend-specific. Check the documentation for the backend you are using to see what
operations are supported.
Parameters
----------
pFS : (in)
A pointer to the file system object. Must not be NULL.
op : (in)
The operation to perform. This is backend-specific.
pArg : (in, optional)
An optional pointer to an argument struct. This is backend-specific. Can be NULL if the
operation does not require any arguments.
Return Value
------------
Returns FS_SUCCESS on success; any other result code otherwise. May return FS_NOT_IMPLEMENTED if
the operation is not supported by the backend.
*/
FS_API fs_result fs_ioctl(fs* pFS, int op, void* pArg);
/* /*
Removes a file or empty directory. Removes a file or empty directory.
@@ -3291,6 +3371,154 @@ FS_API fs_result fs_file_open_and_read(fs* pFS, const char* pFilePath, fs_format
FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, const void* pData, size_t dataSize); FS_API fs_result fs_file_open_and_write(fs* pFS, const char* pFilePath, const void* pData, size_t dataSize);
/*
Serializes a file system subdirectory to a stream.
This function recursively serializes all files and directories within the specified directory
to a binary stream. The serialized data can later be restored using `fs_deserialize()`.
The directory parameter specifies which directory to serialize. This function is built on top of
standard file iteration functions, i.e. `fs_first()`, `fs_next()`. The directory and options all
work the same way as they do for `fs_first()`.
It is possible for serialization to fail partway through, in which case the stream will contain
partial data. It is the caller's responsibility to handle this case appropriately.
When calculating offsets, it will use `fs_stream_tell()` to get the current position in the stream.
Keep this in mind if you are appending this to the end of an existing stream. This may or may not
be useful to you depending on your use case.
The format is designed to be appendable to existing payloads using tools like `cat`. Offsets are
stored relative to the end of the archive to support this use case. The Base Offset field in the
tail specifies where file data begins relative to the end of the stream, and will always be a
negative number. Each file's offset is then relative to this base offset. To seek to a file's data,
you would add the file's offset to the base offset and then seek by that amount relative to the end
of the stream.
Below is the format:
|: MAIN STRUCTURE :|
|------------------------------------------------|
| n | File Data |
|------|-----------------------------------------|
| n | TOC Entries |
|------|-----------------------------------------|
| 8 | 'FSSRLZ1\0' |
| 8 | Base Offset (negative, relative to end) |
| 8 | TOC Offset (relative to Base Offset) |
| 4 | TOC Entry Count |
| 4 | Reserved (set to zero) |
|: TOC ENTRY :|
|------------------------------------------------|
| 4 | File Flags |
| 4 | File Path Length |
| n | File Path |
| 1 | '\0' (Null Terminator) |
| n | Padding to 8-byte alignment |
| 8 | File Size |
| 8 | File Offset (local, relative) |
|: FILE FLAGS :|
|------------------------------------------------|
| 0x1 | Directory |
Notes:
- The signature is located at the end of the stream. To parse, seek to the last 32 bytes, read
the signature, base offset, TOC offset and entry count, then calculate the absolute TOC offset
as Base Offset + TOC Offset and seek to that position, relative to the end, to read the TOC.
The base offset is always negative.
- Directories should be explicitly listed in the TOC, and also have the "Directory" flag set.
- Directories must be listed before any files that are contained within them.
- File Offset and TOC Offset are both local offsets relative to the Base Offset position. Sum
the offsets with the base offset, and then seek relative to the end.
- The Base Offset is relative to the end of the archive (negative value).
- File data is aligned to 8-byte boundaries.
- All multi-byte values are little-endian.
- File paths are stored using UTF-8 encoding.
- To calculate the padding between the null terminator and the next 8-byte aligned offset, round
the length of the file path plus one (for the null terminator) up to the next multiple of 8,
then subtract the length of the file path plus one.
Parameters
----------
pFS : (in)
A pointer to the file system object. Must not be NULL.
pDirectoryPath : (in, optional)
The path to the directory to serialize.
options : (in)
Options for the serialization operation. These are passed in directly to `fs_first()` and
therefore have the same meaning. See `fs_first()` for details. These will also be passed into
`fs_file_open()` when opening files, combined with `FS_READ`.
pOutputStream : (in)
A pointer to the output stream where the serialized data will be written. Must not be NULL.
Return Value
------------
Returns FS_SUCCESS on success; any other result code otherwise.
See Also
--------
fs_deserialize()
fs_first()
*/
FS_API fs_result fs_serialize(fs* pFS, const char* pDirectoryPath, int options, fs_stream* pOutputStream);
/*
Deserializes file system data from a stream.
This function reads serialized file system data from a stream and recreates the files and
directories in the specified subdirectory. The format of the data must match that produced by
`fs_serialize()`.
The subdirectory parameter specifies where to restore the serialized data. If NULL or empty, the
data is restored to the file system root. The path is relative to the file system root.
Existing files with the same name will be overwritten during deserialization. If the output
directory already exists, it will not emptied before restoring the data.
This function will not attempt to clean up any partially restored data if an error occurs during
the process.
Parameters
----------
pFS : (in)
A pointer to the file system object. Must not be NULL.
pSubdirectoryPath : (in, optional)
The path to the subdirectory where the data should be restored. Can be NULL or empty to
restore to the filesystem root. The path should use forward slashes ('/') as separators.
options : (in)
Options for the deserialization operation. These will be passed into `fs_file_open()` when
creating files. See `fs_file_open()` for details.
pInputStream : (in)
A pointer to the input stream containing the serialized data. Must not be NULL.
Return Value
------------
Returns FS_SUCCESS on success; any other result code otherwise.
See Also
--------
fs_serialize()
*/
FS_API fs_result fs_deserialize(fs* pFS, const char* pDirectoryPath, int options, fs_stream* pInputStream);
/* BEG fs_backend_posix.h */ /* BEG fs_backend_posix.h */
extern const fs_backend* FS_BACKEND_POSIX; extern const fs_backend* FS_BACKEND_POSIX;
/* END fs_backend_posix.h */ /* END fs_backend_posix.h */
@@ -3417,6 +3645,7 @@ read and write from different locations from the same fs_memory_stream object, y
seek before doing your read or write. You cannot read and write at the same time across seek before doing your read or write. You cannot read and write at the same time across
multiple threads for the same fs_memory_stream object. multiple threads for the same fs_memory_stream object.
*/ */
typedef struct fs_memory_stream fs_memory_stream; typedef struct fs_memory_stream fs_memory_stream;
struct fs_memory_stream struct fs_memory_stream