| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "DFPlatform.h" |
| #include <DocFormats/DFStorage.h> |
| #include "DFCommon.h" |
| #include "DFString.h" |
| #include "DFFilesystem.h" |
| #include "DFBuffer.h" |
| #include "DFZipFile.h" |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| typedef struct DFStorageOps DFStorageOps; |
| |
| struct DFStorageOps { |
| int (*save)(DFStorage *storage, DFError **error); |
| int (*read)(DFStorage *storage, const char *path, void **buf, size_t *nbytes, DFError **error); |
| int (*write)(DFStorage *storage, const char *path, void *buf, size_t nbytes, DFError **error); |
| int (*exists)(DFStorage *storage, const char *path); |
| int (*delete)(DFStorage *storage, const char *path, DFError **error); |
| const char **(*list)(DFStorage *storage, DFError **error); |
| }; |
| |
| struct DFStorage { |
| size_t retainCount; |
| DFFileFormat format; |
| char *rootPath; |
| char *zipFilename; |
| DFHashTable *files; |
| const DFStorageOps *ops; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DFStorage (Filesystem) // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| static int fsSave(DFStorage *storage, DFError **error) |
| { |
| // Nothing to do here; we've already written everything to the filesystem |
| // at the time the calls were made. |
| return 1; |
| } |
| |
| static int fsRead(DFStorage *storage, const char *path, void **buf, size_t *nbytes, DFError **error) |
| { |
| char *fullPath = DFAppendPathComponent(storage->rootPath,path); |
| int ok = 0; |
| |
| FILE *file = fopen(fullPath,"rb"); |
| if (file == NULL) { |
| DFErrorSetPosix(error,errno); |
| goto end; |
| } |
| |
| size_t balloc = 4096; |
| size_t blen = 0; |
| char *mem = (char *)xmalloc(balloc); |
| |
| size_t r; |
| while (0 < (r = fread(&mem[blen],1,4096,file))) { |
| balloc += r; |
| blen += r; |
| mem = (char *)xrealloc(mem,balloc); |
| } |
| ok = 1; |
| |
| *buf = mem; |
| *nbytes = blen; |
| |
| end: |
| if (file != NULL) |
| fclose(file); |
| free(fullPath); |
| return ok; |
| } |
| |
| static int fsWrite(DFStorage *storage, const char *path, void *buf, size_t nbytes, DFError **error) |
| { |
| char *fullPath = DFAppendPathComponent(storage->rootPath,path); |
| char *parentPath = DFPathDirName(fullPath); |
| int r = 0; |
| FILE *file = NULL; |
| |
| if (!DFFileExists(parentPath) && !DFCreateDirectory(parentPath,1,error)) |
| goto end; |
| |
| file = fopen(fullPath,"wb"); |
| if (file == NULL) { |
| DFErrorSetPosix(error,errno); |
| goto end; |
| } |
| size_t w = fwrite(buf,1,nbytes,file); |
| if (w != nbytes) { |
| DFErrorFormat(error,"Incomplete write"); |
| goto end; |
| } |
| r = 1; |
| |
| end: |
| if (file != NULL) |
| fclose(file); |
| free(parentPath); |
| free(fullPath); |
| return r; |
| } |
| |
| static int fsExists(DFStorage *storage, const char *path) |
| { |
| char *fullPath = DFAppendPathComponent(storage->rootPath,path); |
| int r = DFFileExists(fullPath); |
| free(fullPath); |
| return r; |
| } |
| |
| static int fsDelete(DFStorage *storage, const char *path, DFError **error) |
| { |
| char *fullPath = DFAppendPathComponent(storage->rootPath,path); |
| int r = DFDeleteFile(fullPath,error); |
| free(fullPath); |
| return r; |
| } |
| |
| const char **fsList(DFStorage *storage, DFError **error) |
| { |
| const char **allPaths = DFContentsOfDirectory(storage->rootPath,1,error); |
| if (allPaths == NULL) |
| return NULL;; |
| |
| DFArray *filesOnly = DFArrayNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| for (int i = 0; allPaths[i]; i++) { |
| const char *relPath = allPaths[i]; |
| char *absPath = DFAppendPathComponent(storage->rootPath,relPath); |
| if (!DFIsDirectory(absPath)) |
| DFArrayAppend(filesOnly,(void *)relPath); |
| free(absPath); |
| } |
| |
| const char **result = DFStringArrayFlatten(filesOnly); |
| DFArrayRelease(filesOnly); |
| free(allPaths); |
| return result; |
| } |
| |
| static DFStorageOps fsOps = { |
| .save = fsSave, |
| .read = fsRead, |
| .write = fsWrite, |
| .exists = fsExists, |
| .delete = fsDelete, |
| .list = fsList, |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DFStorage (Memory) // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| static int memSave(DFStorage *storage, DFError **error) |
| { |
| // Nothing to do here; memory-backed storage objects are intended to be temporary, and are never |
| // saved to disk |
| return 1; |
| } |
| |
| static int memRead(DFStorage *storage, const char *path, void **buf, size_t *nbytes, DFError **error) |
| { |
| DFBuffer *buffer = DFHashTableLookup(storage->files,path); |
| if (buffer == NULL) { |
| DFErrorSetPosix(error,ENOENT); |
| return 0; |
| } |
| |
| *buf = xmalloc(buffer->len); |
| memcpy(*buf,buffer->data,buffer->len); |
| *nbytes = buffer->len; |
| |
| return 1; |
| } |
| |
| static int memWrite(DFStorage *storage, const char *path, void *buf, size_t nbytes, DFError **error) |
| { |
| DFBuffer *buffer = DFBufferNew(); |
| DFBufferAppendData(buffer,buf,nbytes); |
| DFHashTableAdd(storage->files,path,buffer); |
| DFBufferRelease(buffer); |
| return 1; |
| } |
| |
| static int memExists(DFStorage *storage, const char *path) |
| { |
| return (DFHashTableLookup(storage->files,path) != NULL); |
| } |
| |
| static int memDelete(DFStorage *storage, const char *path, DFError **error) |
| { |
| DFHashTableRemove(storage->files,path); |
| return 1; |
| } |
| |
| static const char **memList(DFStorage *storage, DFError **error) |
| { |
| return DFHashTableCopyKeys(storage->files); |
| } |
| |
| static DFStorageOps memOps = { |
| .save = memSave, |
| .read = memRead, |
| .write = memWrite, |
| .exists = memExists, |
| .delete = memDelete, |
| .list = memList, |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DFStorage (Zip) // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| // Currently, zip storage objects operate just like memory storage objects, in that they store their |
| // contents in a hash table. The only difference is that they extract all the entries from a zip |
| // file on creation, and overwrite the zip file on save. |
| // |
| // Eventually, we should make it so the entries are read on-demand, and also that a new, temporary |
| // zip file is written if the storage is modified, and that new version replaces the existing one on |
| // save. |
| |
| static int zipSave(DFStorage *storage, DFError **error) |
| { |
| return DFZip(storage->zipFilename,storage,error); |
| } |
| |
| static int zipRead(DFStorage *storage, const char *path, void **buf, size_t *nbytes, DFError **error) |
| { |
| DFBuffer *buffer = DFHashTableLookup(storage->files,path); |
| if (buffer == NULL) { |
| DFErrorSetPosix(error,ENOENT); |
| return 0; |
| } |
| |
| *buf = xmalloc(buffer->len); |
| memcpy(*buf,buffer->data,buffer->len); |
| *nbytes = buffer->len; |
| |
| return 1; |
| } |
| |
| static int zipWrite(DFStorage *storage, const char *path, void *buf, size_t nbytes, DFError **error) |
| { |
| DFBuffer *buffer = DFBufferNew(); |
| DFBufferAppendData(buffer,buf,nbytes); |
| DFHashTableAdd(storage->files,path,buffer); |
| DFBufferRelease(buffer); |
| return 1; |
| } |
| |
| static int zipExists(DFStorage *storage, const char *path) |
| { |
| return (DFHashTableLookup(storage->files,path) != NULL); |
| } |
| |
| static int zipDelete(DFStorage *storage, const char *path, DFError **error) |
| { |
| DFHashTableRemove(storage->files,path); |
| return 1; |
| } |
| |
| static const char **zipList(DFStorage *storage, DFError **error) |
| { |
| return DFHashTableCopyKeys(storage->files); |
| } |
| |
| static DFStorageOps zipOps = { |
| .save = zipSave, |
| .read = zipRead, |
| .write = zipWrite, |
| .exists = zipExists, |
| .delete = zipDelete, |
| .list = zipList, |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DFStorage // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| // Normalize the path to remove multiple consecutive / characters, and to remove any trailing / |
| // character. This ensures that for implementations which rely on an exact match of the path |
| // (specifically, the memory storage object), that any non-essential differences in the supplied |
| // path will not matter. |
| |
| static char *fixPath(const char *input) |
| { |
| char *normalized = DFPathNormalize(input); |
| char *result; |
| if (normalized[0] == '/') |
| result = xstrdup(&normalized[1]); |
| else |
| result = xstrdup(normalized); |
| free(normalized); |
| return result; |
| } |
| |
| static DFStorage *DFStorageNew(DFFileFormat format, const DFStorageOps *ops) |
| { |
| DFStorage *storage = (DFStorage *)xcalloc(1,sizeof(DFStorage)); |
| storage->retainCount = 1; |
| storage->format = format; |
| storage->ops = ops; |
| return storage; |
| } |
| |
| DFStorage *DFStorageNewFilesystem(const char *rootPath, DFFileFormat format) |
| { |
| if ((rootPath == NULL) || (strlen(rootPath) == 0)) |
| rootPath = ".";; |
| DFStorage *storage = DFStorageNew(format,&fsOps); |
| storage->rootPath = xstrdup(rootPath); |
| return storage; |
| } |
| |
| DFStorage *DFStorageNewMemory(DFFileFormat format) |
| { |
| DFStorage *storage = DFStorageNew(format,&memOps); |
| storage->files = DFHashTableNew((DFCopyFunction)DFBufferRetain,(DFFreeFunction)DFBufferRelease); |
| return storage; |
| } |
| |
| DFStorage *DFStorageCreateZip(const char *filename, DFError **error) |
| { |
| // Note that with the current implementation, the file doesn't actually get saved until we do a DFStorageSave. |
| if (DFFileExists(filename)) { |
| DFErrorFormat(error,"File already exists"); |
| return NULL; |
| } |
| |
| DFStorage *storage = DFStorageNew(DFFileFormatFromFilename(filename),&zipOps); |
| storage->files = DFHashTableNew((DFCopyFunction)DFBufferRetain,(DFFreeFunction)DFBufferRelease); |
| storage->zipFilename = xstrdup(filename); |
| return storage; |
| } |
| |
| DFStorage *DFStorageOpenZip(const char *filename, DFError **error) |
| { |
| if (!DFFileExists(filename)) { |
| DFErrorFormat(error,"File does not exist"); |
| return NULL; |
| } |
| |
| DFStorage *storage = DFStorageNew(DFFileFormatFromFilename(filename),&zipOps); |
| storage->files = DFHashTableNew((DFCopyFunction)DFBufferRetain,(DFFreeFunction)DFBufferRelease); |
| storage->zipFilename = xstrdup(filename); |
| |
| if (!DFUnzip(filename,storage,error)) { |
| DFStorageRelease(storage); |
| return NULL; |
| } |
| return storage; |
| } |
| |
| DFStorage *DFStorageRetain(DFStorage *storage) |
| { |
| if (storage != NULL) |
| storage->retainCount++; |
| return storage; |
| } |
| |
| void DFStorageRelease(DFStorage *storage) |
| { |
| if ((storage == NULL) || (--storage->retainCount > 0)) |
| return; |
| |
| DFHashTableRelease(storage->files); |
| free(storage->rootPath); |
| free(storage->zipFilename); |
| free(storage); |
| } |
| |
| DFFileFormat DFStorageFormat(DFStorage *storage) |
| { |
| return storage->format; |
| } |
| |
| int DFStorageSave(DFStorage *storage, DFError **error) |
| { |
| return storage->ops->save(storage,error); |
| } |
| |
| int DFStorageRead(DFStorage *storage, const char *path, void **buf, size_t *nbytes, DFError **error) |
| { |
| char *fixed = fixPath(path); |
| int r = storage->ops->read(storage,fixed,buf,nbytes,error); |
| free(fixed); |
| return r; |
| } |
| |
| int DFStorageWrite(DFStorage *storage, const char *path, void *buf, size_t nbytes, DFError **error) |
| { |
| char *fixed = fixPath(path); |
| int r = storage->ops->write(storage,fixed,buf,nbytes,error); |
| free(fixed); |
| return r; |
| } |
| |
| int DFStorageExists(DFStorage *storage, const char *path) |
| { |
| char *fixed = fixPath(path); |
| int r = storage->ops->exists(storage,fixed); |
| free(fixed); |
| return r; |
| } |
| |
| int DFStorageDelete(DFStorage *storage, const char *path, DFError **error) |
| { |
| char *fixed = fixPath(path); |
| int r = storage->ops->delete(storage,fixed,error); |
| free(fixed); |
| return r; |
| } |
| |
| const char **DFStorageList(DFStorage *storage, DFError **error) |
| { |
| return storage->ops->list(storage,error); |
| } |