/** * ========================================================================= * File : file.h * Project : 0 A.D. * Description : file layer on top of POSIX. avoids the need for * : absolute paths and provides fast I/O. * ========================================================================= */ // license: GPL; see lib/license.txt #ifndef INCLUDED_FILE #define INCLUDED_FILE #include "lib/posix/posix_filesystem.h" // struct stat namespace ERR { const LibError FILE_ACCESS = -110000; const LibError FILE_NOT_MAPPED = -110001; const LibError DIR_END = -110002; } extern LibError file_init(); // used by vfs_redirector to call various file objects' methods. struct FileProvider_VTbl; // // path conversion functions (native <--> portable), // for external libraries that require the real filename. // // replaces '/' with platform's directory separator and vice versa. // verifies path length < PATH_MAX (otherwise return ERR::PATH_LENGTH). // // relative paths (relative to root dir) extern LibError file_make_native_path(const char* path, char* n_path); extern LibError file_make_portable_path(const char* n_path, char* path); // as above, but with full native paths (portable paths are always relative). // prepends current directory, resp. makes sure it matches the given path. extern LibError file_make_full_native_path(const char* path, char* n_full_path); extern LibError file_make_full_portable_path(const char* n_full_path, char* path); // establish the root directory from , which is treated as // relative to the executable's directory (determined via argv[0]). // all relative file paths passed to this module will be based from // this root dir. // // example: executable in "$install_dir/system"; desired root dir is // "$install_dir/data" => rel_path = "../data". // // argv[0] is necessary because the current directory is unknown at startup // (e.g. it isn't set when invoked via batch file), and this is the // easiest portable way to find our install directory. // // can only be called once, by design (see below). rel_path is trusted. extern LibError file_set_root_dir(const char* argv0, const char* rel_path); // allocate a copy of P_fn in our string pool. strings are equal iff // their addresses are equal, thus allowing fast comparison. // // if the (generous) filename storage is full, 0 is returned. // this is not ever expected to happen; callers need not check the // return value because a warning is raised anyway. extern const char* file_make_unique_fn_copy(const char* P_fn); extern const char* file_get_random_name(); // // directory // const size_t DIR_ITERATOR_OPAQUE_SIZE = 40; // layer on top of POSIX opendir/readdir/closedir that handles // portable -> native path conversion, ignores non-file/directory entries, // and additionally returns the file status (size and mtime). // directory state initialized by dir_open. // rationale: some private storage apart from opendir's DIR* is required // to support stat(). we prefer having the caller reserve room (on the stack) // rather than allocating dynamically (less efficient or more complicated). // // this is an opaque struct to avoid exposing our internals and insulate // user code against changes; we verify at compile-time that the // public/private definitions match. // note: cannot just typedef to DirIterator_ because other modules // instantiate this. struct DirIterator { // safety check - used to verify correct calling of dir_filtered_next_ent const char* filter; // .. has filter been assigned? this flag is necessary because // there are no "invalid" filter values we can use. uint filter_latched : 1; const FileProvider_VTbl* type; char opaque[DIR_ITERATOR_OPAQUE_SIZE]; }; class TFile; // information about a directory entry filled by dir_next_ent. struct DirEnt { // we want to keep this as small as possible because // file_enum allocates one copy for each file in the directory. // store only required stat fields (in VC's order of decl) off_t size; time_t mtime; // name (not including path!) of this entry. // valid until a subsequent dir_next_ent or dir_close call for the // current dir state. // rationale: we don't want to return a pointer to a copy because // users would have to free it (won't happen). const char* name; const TFile* tf; }; // return [bool] indicating whether the given DirEnt* (filled by // dir_next_ent) represents a directory. #define DIRENT_IS_DIR(p_ent) ((p_ent)->size == -1) // prepare to iterate (once) over entries in the given directory. // if INFO::OK is returned, is ready for subsequent dir_next_ent calls and // must be freed via dir_close. extern LibError dir_open(const char* P_path, DirIterator* d); // return ERR::DIR_END if all entries have already been returned once, // another negative error code, or INFO::OK on success, in which case // describes the next (order is unspecified) directory entry. extern LibError dir_next_ent(DirIterator* d, DirEnt* ent); // indicate the directory iterator is no longer needed; all resources it // held are freed. extern LibError dir_close(DirIterator* d); extern bool dir_exists(const char* P_path); extern LibError dir_create(const char* P_path); extern LibError dir_delete(const char* P_path); #ifdef __cplusplus typedef std::vector DirEnts; typedef DirEnts::iterator DirEntIt; typedef DirEnts::const_iterator DirEntCIt; // enumerate all directory entries in ; add to container and // then sort it by filename. extern LibError file_get_sorted_dirents(const char* P_path, DirEnts& dirents); #endif // #ifdef __cplusplus // called by file_enum for each entry in the directory. // name doesn't include path! // return INFO::CB_CONTINUE to continue calling; anything else will cause // file_enum to abort and immediately return that value. typedef LibError (*FileCB)(const char* name, const struct stat* s, uintptr_t memento, const uintptr_t user); // call for each file and subdirectory in (alphabetical order), // passing the entry name (not full path!), stat info, and . // // first builds a list of entries (sorted) and remembers if an error occurred. // if returns non-zero, abort immediately and return that; otherwise, // return first error encountered while listing files, or 0 on success. extern LibError file_enum(const char* dir, FileCB cb, uintptr_t user); // chosen for semi-nice 48 byte total struct File size. // each implementation checks if this is enough. const size_t FILE_OPAQUE_SIZE = 52; // represents an open file of any type (OS, archive, VFS). // contains common fields and opaque storage for type-specific fields. // // this cannot merely be added in a separate VFS layer: it would want to // share some common fields, which either requires this approach // (one publically visible struct with space for private storage), or // a common struct layout / embedding a FileCommon struct at // the beginning. the latter is a bit messy since fields must be accessed // as e.g. af->fc.flags. one shared struct also makes for a common // interface. struct File { uint flags; off_t size; // copy of the filename that is uniquely identified by its address. // used as key for file cache. // NOTE: not set by file_open! (because the path passed there is // a native path; it has no use within VFS and would only // unnecessarily clutter the filename storage) const char* atom_fn; // can be 0 if not currently in use; otherwise, points to // the file provider's vtbl. const FileProvider_VTbl* type; // storage for the provider-specific fields. // the implementations cast this to their e.g. PosixFile struct. // // note: when doing so, there's no need to verify type - if // vfs_io dispatches to afile_read, then the File.type must obviously // have been "archive". // if users call the e.g. archive.h methods directly, we assume they // know what they're doing and don't check that. u8 opaque[FILE_OPAQUE_SIZE]; }; // note: these are all set during file_open and cannot be changed thereafter. enum FileFlags { // IO: // ------------------------------------------------------------------------ // write-only access; otherwise, read only. // // unless FILE_NO_AIO is set, data that is to be written must be // aligned and padded to a multiple of file_sector_size bytes; // this requirement avoids the need for align buffers. // // note: only allowing either reads or writes simplifies file cache // coherency (need only invalidate when closing a FILE_WRITE file). FILE_WRITE = 0x01, // translate newlines: convert from/to native representation when // reading/writing. this is useful if files we create need to be // edited externally - e.g. Notepad requires \r\n. // caveats: // - FILE_NO_AIO must be set; translation is done by OS read()/write(). // - not supported by POSIX, so this currently only has meaning on Win32. FILE_TEXT = 0x02, // skip the aio path and use the OS-provided synchronous blocking // read()/write() calls. this avoids the need for buffer alignment // set out below, so it's useful for writing small text files. FILE_NO_AIO = 0x04, // caching: // ------------------------------------------------------------------------ // do not add the (entire) contents of this file to the cache. // this flag should be specified when the data is cached at a higher // level (e.g. OpenGL textures) to avoid wasting previous cache space. FILE_CACHED_AT_HIGHER_LEVEL = 0x10, // enable caching individual blocks read from a file. the block cache // is small, organized as LRU and incurs some copying overhead, so it // should only be enabled when needed. this is the case for archives, // where the cache absorbs overhead of block-aligning all IOs. FILE_CACHE_BLOCK = 0x20, // notify us that the file buffer returned by file_io will not be // freed immediately (i.e. before the next allocation). // allocation policy may differ and a warning is suppressed. FILE_LONG_LIVED = 0x40, // misc: // ------------------------------------------------------------------------ // instruct file_open not to set FileCommon.atom_fn. // this is a slight optimization used by VFS code: file_open // would store the portable name, which is only used when calling // the OS's open(); this would unnecessarily waste atom_fn memory. // // note: other file.cpp functions require atom_fn to be set, // so this behavior is only triggered via flag (caller is // promising they will set atom_fn). FILE_DONT_SET_FN = 0x80, // (only relevant for VFS) file will be written into the // appropriate subdirectory of the mount point established by // vfs_set_write_target. see documentation there. FILE_WRITE_TO_TARGET = FILE_WRITE|0x100, // sum of all flags above. used when validating flag parameters. FILE_FLAG_ALL = 0x1FF }; // get file information. output param is zeroed on error. extern LibError file_stat(const char* path, struct stat*); // does the given file exist? (implemented via file_stat) extern bool file_exists(const char* fn); // permanently delete the file. be very careful with this! extern LibError file_delete(const char* fn); // is ignored here. // rationale: all file providers' open() routines should ideally take the // same parameters. since afile_open requires archive Handle and // memento, we need some way of passing them; TFile is sufficient // (via vfs_tree accessor methods). extern LibError file_open(const char* fn, uint flags, File* f); // note: final file size is calculated and returned in f->size. // see implementation for rationale. extern LibError file_close(File* f); extern LibError file_validate(const File* f); #define CHECK_FILE(f) RETURN_ERR(file_validate(f)) // remove all blocks loaded from the file . used when reloading the file. extern LibError file_cache_invalidate(const char* fn); #include "file_io.h" // // memory mapping // // useful for files that are too large to be loaded into memory, // or if only (non-sequential) portions of a file are needed at a time. // map the entire file into memory. if already currently mapped, // return the previous mapping (reference-counted). // output parameters are zeroed on failure. // // the mapping will be removed (if still open) when its file is closed. // however, map/unmap calls should still be paired so that the mapping // may be removed when no longer needed. // // rationale: reference counting is required for zip_map: several // Zip "mappings" each reference one ZArchive's actual file mapping. // implement it here so that we also get refcounting for normal files. extern LibError file_map(File* f, void*& p, size_t& size); // decrement the reference count for the mapping belonging to file . // fail if there are no references; remove the mapping if the count reaches 0. // // the mapping will be removed (if still open) when its file is closed. // however, map/unmap calls should still be paired so that the mapping // may be removed when no longer needed. extern LibError file_unmap(File* f); extern LibError file_shutdown(); #endif // #ifndef INCLUDED_FILE