diff --git a/source/graphics/MapWriter.cpp b/source/graphics/MapWriter.cpp index e1e244fea9..dd0e7322bf 100644 --- a/source/graphics/MapWriter.cpp +++ b/source/graphics/MapWriter.cpp @@ -156,7 +156,7 @@ void CMapWriter::PackTerrain(CFilePacker& packer, CTerrain *pTerrain) void CMapWriter::WriteXML(const char* filename, CUnitManager* pUnitMan, CLightEnv *pLightEnv, CCamera *pCamera) { - Handle h = vfs_open(filename, FILE_WRITE_TO_MOD|FILE_NO_AIO); + Handle h = vfs_open(filename, FILE_WRITE_TO_TARGET|FILE_NO_AIO); if (h <= 0) { debug_warn("Failed to open map XML file"); diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index d3ed8cab46..4dcbeb9082 100644 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -19,9 +19,6 @@ using namespace std; -// filter for vfs_next_dirent -static const char* SupportedTextureFormats[] = { "*.png", "*.dds", "*.tga", "*.bmp" }; - CTextureManager::CTextureManager(): m_LastGroupIndex(0) {} @@ -176,7 +173,7 @@ void CTextureManager::RecurseDirectory(CTerrainPropertiesPtr parentProps, const int CTextureManager::LoadTerrainTextures() { - RecurseDirectory(CTerrainPropertiesPtr(), "art/textures/terrain/types"); + RecurseDirectory(CTerrainPropertiesPtr(), "art/textures/terrain/types/"); return 0; } diff --git a/source/lib/input.cpp b/source/lib/input.cpp index 9fb0369ed0..8ac69f4c55 100644 --- a/source/lib/input.cpp +++ b/source/lib/input.cpp @@ -124,7 +124,7 @@ LibError in_playback(const char* fn) f = fopen(fn, "rb"); if(!f) - WARN_RETURN(ERR_FILE_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); u32 rec_start_time; fread(&rec_start_time, sizeof(u32), 1, f); diff --git a/source/lib/lib_errors.cpp b/source/lib/lib_errors.cpp index fcbcd85551..e292e48795 100644 --- a/source/lib/lib_errors.cpp +++ b/source/lib/lib_errors.cpp @@ -85,7 +85,7 @@ LibError LibError_from_errno(bool warn_if_failed) case EINVAL: err = ERR_INVALID_PARAM; break; case ENOSYS: err = ERR_NOT_IMPLEMENTED; break; - case ENOENT: err = ERR_PATH_NOT_FOUND; break; + case ENOENT: err = ERR_TNODE_NOT_FOUND; break; case EACCES: err = ERR_FILE_ACCESS; break; case EIO: err = ERR_IO; break; case ENAMETOOLONG: err = ERR_PATH_LENGTH; break; @@ -122,7 +122,7 @@ static int return_errno_from_LibError(LibError err) case ERR_INVALID_PARAM: return EINVAL; case ERR_NOT_IMPLEMENTED: return ENOSYS; - case ERR_PATH_NOT_FOUND: return ENOENT; + case ERR_TNODE_NOT_FOUND: return ENOENT; case ERR_FILE_ACCESS: return EACCES; case ERR_IO: return EIO; case ERR_PATH_LENGTH: return ENAMETOOLONG; diff --git a/source/lib/lib_errors.h b/source/lib/lib_errors.h index dea8aa70b1..a75c54070a 100644 --- a/source/lib/lib_errors.h +++ b/source/lib/lib_errors.h @@ -425,28 +425,27 @@ ERR(-100302, ERR_PATH_NOT_RELATIVE, "Path is not relative") ERR(-100303, ERR_PATH_NON_PORTABLE, "Path contains OS-specific dir separator") ERR(-100304, ERR_PATH_NON_CANONICAL, "Path contains unsupported .. or ./") ERR(-100305, ERR_PATH_COMPONENT_SEPARATOR, "Path component contains dir separator") +// .. tree node +ERR(-100310, ERR_TNODE_NOT_FOUND, "VFile not found") +ERR(-100311, ERR_TNODE_WRONG_TYPE, "Not a directory") // .. open -ERR(-100310, ERR_FILE_NOT_FOUND, "VFile not found") -ERR(-100311, ERR_NOT_FILE, "Not a file") -ERR(-100312, ERR_FILE_ACCESS, "Insufficient access rights to open file") +ERR(-100320, ERR_FILE_ACCESS, "Insufficient access rights to open file") // .. enum -ERR(-100320, ERR_PATH_NOT_FOUND, "VDir not found") -ERR(-100321, ERR_NOT_DIR, "Not a directory") -ERR(-100322, ERR_DIR_END, "End of directory reached (no more files)") +ERR(-100330, ERR_DIR_END, "End of directory reached (no more files)") // .. IO -ERR(-100330, ERR_IO, "Error during IO") -ERR(-100331, ERR_EOF, "Reading beyond end of file") +ERR(-100340, ERR_IO, "Error during IO") +ERR(-100341, ERR_EOF, "Reading beyond end of file") // .. mount -ERR(-100340, ERR_ALREADY_MOUNTED, "Directory (tree) already mounted") -ERR(-100341, ERR_NOT_MOUNTED, "Specified directory is not mounted") -ERR(-100342, ERR_INVALID_MOUNT_TYPE, "Invalid mount type (memory corruption?)") -ERR(-100343, ERR_ROOT_DIR_ALREADY_SET, "Attempting to set FS root dir more than once") +ERR(-100350, ERR_ALREADY_MOUNTED, "Directory (tree) already mounted") +ERR(-100351, ERR_NOT_MOUNTED, "Specified directory is not mounted") +ERR(-100352, ERR_INVALID_MOUNT_TYPE, "Invalid mount type (memory corruption?)") +ERR(-100353, ERR_ROOT_DIR_ALREADY_SET, "Attempting to set FS root dir more than once") // .. misc -ERR(-100350, ERR_UNKNOWN_CMETHOD, "Unknown/unsupported compression method") -ERR(-100351, ERR_IS_COMPRESSED, "Invalid operation for a compressed file") -ERR(-100352, ERR_NOT_MAPPED, "File was not mapped") -ERR(-100353, ERR_NOT_IN_CACHE, "[Internal] Entry not found in cache") -ERR(-100354, ERR_TRACE_EMPTY, "No valid entries in trace") +ERR(-100360, ERR_UNKNOWN_CMETHOD, "Unknown/unsupported compression method") +ERR(-100361, ERR_IS_COMPRESSED, "Invalid operation for a compressed file") +ERR(-100362, ERR_NOT_MAPPED, "File was not mapped") +ERR(-100363, ERR_NOT_IN_CACHE, "[Internal] Entry not found in cache") +ERR(-100364, ERR_TRACE_EMPTY, "No valid entries in trace") // file format ERR(-100400, ERR_UNKNOWN_FORMAT, "Unknown file format") diff --git a/source/lib/path_util.cpp b/source/lib/path_util.cpp index 1a5c2f24b9..3b80a79dc9 100644 --- a/source/lib/path_util.cpp +++ b/source/lib/path_util.cpp @@ -145,22 +145,35 @@ void path_copy(char* dst, const char* src) // if necessary, a directory separator is added between the paths. // each may be empty, filenames, or full paths. // total path length (including '\0') must not exceed PATH_MAX. -LibError path_append(char* dst, const char* path1, const char* path2) +LibError path_append(char* dst, const char* path1, const char* path2, uint flags) { const size_t len1 = strlen(path1); const size_t len2 = strlen(path2); size_t total_len = len1 + len2 + 1; // includes '\0' + const bool no_end_slash1 = (len1 == 0 || !is_dir_sep(path1[len1-1])); + const bool no_end_slash2 = (len2 == 0 || !is_dir_sep(path2[len2-1])); // check if we need to add '/' between path1 and path2 - // note: the second can't start with '/' (not allowed by path_validate) + // notes: + // - the second can't start with '/' (not allowed by path_validate) + // - must check len2 as well - if it's empty, we'd end up + // inadvertently terminating the string with '/'. bool need_separator = false; - if(len1 != 0 && !is_dir_sep(path1[len1-1])) + if(len2 != 0 && len1 != 0 && no_end_slash1) { total_len++; // for '/' need_separator = true; } - if(total_len+1 > PATH_MAX) + // check if trailing slash requested and not already present + bool need_terminator = false; + if(flags & PATH_APPEND_SLASH && no_end_slash2) + { + total_len++; // for '/' + need_terminator = true; + } + + if(total_len > PATH_MAX) WARN_RETURN(ERR_PATH_LENGTH); strcpy(dst, path1); // safe @@ -168,21 +181,26 @@ LibError path_append(char* dst, const char* path1, const char* path2) if(need_separator) *dst++ = '/'; strcpy(dst, path2); // safe + if(need_terminator) + strcpy(dst+len2, "/"); // safe + return ERR_OK; } // strip from the start of , prepend , // and write to . -// returns ERR_FAIL if the beginning of doesn't match . +// returns ERR_FAIL (without warning!) if the beginning of doesn't +// match . LibError path_replace(char* dst, const char* src, const char* remove, const char* replace) { // remove doesn't match start of const size_t remove_len = strlen(remove); if(strncmp(src, remove, remove_len) != 0) - WARN_RETURN(ERR_FAIL); + return ERR_FAIL; // NOWARN - // get rid of trailing / in src (must not be included in remove) + // if removing will leave a separator at beginning of src, remove it + // (example: "a/b"; removing "a" would yield "/b") const char* start = src+remove_len; if(is_dir_sep(*start)) start++; @@ -193,6 +211,8 @@ LibError path_replace(char* dst, const char* src, const char* remove, const char } + + //----------------------------------------------------------------------------- // split paths into specific parts @@ -218,6 +238,30 @@ const char* path_name_only(const char* path) } +// return last component within path. this is similar to path_name_only, +// but correctly handles VFS paths, which must end with '/'. +// (path_name_only would return "") +const char* path_last_component(const char* path) +{ + // ('\0' is end of set string) + static const char separators[3] = { DIR_SEP, '/', '\0' }; + + const char* pos = path; + const char* last_component = path; + + for(;;) + { + if(*pos == '\0') + break; + last_component = pos; + const size_t component_len = strcspn(pos, separators); + pos += component_len+1; // +1 for separator + } + + return last_component; +} + + // if contains a name component, it is stripped away. void path_strip_fn(char* path) { @@ -248,6 +292,62 @@ const char* path_extension(const char* fn) } +// call with for each component in . +LibError path_foreach_component(const char* path_org, PathComponentCb cb, void* ctx) +{ + CHECK_PATH(path_org); + + // copy into (writeable) buffer so we can 'tokenize' path components by + // replacing '/' with '\0'. + char path[PATH_MAX]; + strcpy_s(path, ARRAY_SIZE(path), path_org); + char* cur_component = path; + + bool is_dir = true; // until we find a component without slash + + // successively navigate to the next component in . + for(;;) + { + // at end of string - done. + // (this happens if is empty or ends with slash) + if(*cur_component == '\0') + break; + + // find end of cur_component + char* slash = (char*)strchr(cur_component, '/'); + // .. try platform-specific separator + if(!slash) + slash = (char*)strchr(cur_component, DIR_SEP); + + // decide its type and 0-terminate + // .. filename (by definition) + if(!slash) + is_dir = false; + // .. directory + else + *slash = '\0'; // 0-terminate cur_component + + LibError ret = cb(cur_component, is_dir, ctx); + // callback wants to abort - return its value. + if(ret != INFO_CB_CONTINUE) + return ret; + + // filename is by definition the last component. abort now + // in case the callback didn't. + if(!is_dir) + break; + + // advance to next component + // .. undo having replaced '/' with '\0' - this means will + // store the complete path up to and including cur_component. + *slash = '/'; + cur_component = slash+1; + } + + return ERR_OK; +} + + //----------------------------------------------------------------------------- // convenience "class" that simplifies successively appending a filename to diff --git a/source/lib/path_util.h b/source/lib/path_util.h index b01a92acae..f26eef7251 100644 --- a/source/lib/path_util.h +++ b/source/lib/path_util.h @@ -35,6 +35,16 @@ #include "posix.h" // PATH_MAX +// if path is invalid (see source for criteria), return a +// descriptive error code, otherwise ERR_OK. +extern LibError path_validate(const char* path); +#define CHECK_PATH(path) RETURN_ERR(path_validate(path)) + +// if name is invalid, (see source for criteria), return a +// descriptive error code, otherwise ERR_OK. +extern LibError path_component_validate(const char* name); + + // is s2 a subpath of s1, or vice versa? extern bool path_is_subpath(const char* s1, const char* s2); @@ -48,21 +58,36 @@ extern LibError path_component_validate(const char* name); // copy path strings (provided for convenience). extern void path_copy(char* dst, const char* src); +enum PathAppendFlags +{ + // make sure ends up with a trailing slash. this is useful for + // VFS directory paths, which have that requirement. + PATH_APPEND_SLASH = 1 +}; + // combine and into one path, and write to . // if necessary, a directory separator is added between the paths. // each may be empty, filenames, or full paths. // total path length (including '\0') must not exceed PATH_MAX. -extern LibError path_append(char* dst, const char* path1, const char* path2); +extern LibError path_append(char* dst, const char* path1, const char* path2, + uint flags = 0); // strip from the start of , prepend , // and write to . -// returns ERR_FAIL if the beginning of doesn't match . +// returns ERR_FAIL (without warning!) if the beginning of doesn't +// match . extern LibError path_replace(char* dst, const char* src, const char* remove, const char* replace); + // return pointer to the name component within path (i.e. skips over all // characters up to the last dir separator, if any). extern const char* path_name_only(const char* path); +// return last component within path. this is similar to path_name_only, +// but correctly handles VFS paths, which must end with '/'. +// (path_name_only would return "") +extern const char* path_last_component(const char* path); + // if contains a name component, it is stripped away. extern void path_strip_fn(char* path); @@ -76,6 +101,29 @@ extern void path_dir_only(const char* path, char* dir); extern const char* path_extension(const char* fn); +// called for each component in a path string, indicating if it's +// a directory (i.e. is followed by a slash in the original +// path). +// if path is empty (i.e. ""), this is not called. +// +// component: 0-terminated name of the component (does not include any +// trailing slash!) +// ctx: context parameter that was passed to path_foreach_component. +// return: INFO_CB_CONTINUE to continue operation normally; anything else +// will cause path_foreach_component to abort immediately and return that. +// no need to 'abort' (e.g. return ERR_OK) after a filename is encountered - +// that's taken care of automatically. +// +// rationale: +// - we indicate if it's a directory via bool. this isn't as nice as a +// flag or enum, but vfs_tree already has TNodeType and we don't want +// to expose that or create a new one. +typedef LibError (*PathComponentCb)(const char* component, bool is_dir, void* ctx); + +// call with for each component in . +extern LibError path_foreach_component(const char* path, PathComponentCb cb, void* ctx); + + //----------------------------------------------------------------------------- // convenience "class" that simplifies successively appending a filename to diff --git a/source/lib/res/file/archive.cpp b/source/lib/res/file/archive.cpp index b52cbdc41e..052024bb7c 100644 --- a/source/lib/res/file/archive.cpp +++ b/source/lib/res/file/archive.cpp @@ -182,7 +182,7 @@ static LibError archive_get_file_info(Archive* a, const char* fn, uintptr_t meme } } - WARN_RETURN(ERR_FILE_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); } diff --git a/source/lib/res/file/file.cpp b/source/lib/res/file/file.cpp index 2055857607..6d8d5a53f0 100644 --- a/source/lib/res/file/file.cpp +++ b/source/lib/res/file/file.cpp @@ -185,6 +185,20 @@ LibError dir_close(DirIterator* di) } +LibError dir_create(const char* P_path, mode_t mode) +{ + char N_path[PATH_MAX]; + RETURN_ERR(file_make_full_native_path(P_path, N_path)); + + struct stat s; + int ret = stat(N_path, &s); + if(ret == 0) + return INFO_ALREADY_PRESENT; + + errno = 0; + ret = mkdir(N_path, mode); + return LibError_from_posix(ret); +} @@ -255,8 +269,23 @@ LibError file_delete(const char* fn) // and the Handle approach doesn't guard against some idiot calling // close(our_fd_value) directly, either. + +struct PosixFile +{ + int fd; + + // for reference counted memory-mapping + void* mapping; + uint map_refs; +}; cassert(sizeof(PosixFile) < FILE_OPAQUE_SIZE); +int file_fd_from_PosixFile(File* f) +{ + const PosixFile* pf = (const PosixFile*)f->opaque; + return pf->fd; +} + LibError file_validate(const File* f) { @@ -298,7 +327,7 @@ LibError file_open(const char* P_fn, uint flags, File* f) // get file size struct stat s; if(stat(N_fn, &s) < 0) - WARN_RETURN(ERR_FILE_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); size = s.st_size; // note: despite increased overhead, the AIO read method is still @@ -312,7 +341,7 @@ LibError file_open(const char* P_fn, uint flags, File* f) // make sure is a regular file if(!S_ISREG(s.st_mode)) - WARN_RETURN(ERR_NOT_FILE); + WARN_RETURN(ERR_TNODE_WRONG_TYPE); } #if OS_WIN diff --git a/source/lib/res/file/file.h b/source/lib/res/file/file.h index 1255b4e53d..11abb7cdc6 100644 --- a/source/lib/res/file/file.h +++ b/source/lib/res/file/file.h @@ -78,7 +78,7 @@ extern const char* file_get_random_name(); // -// dir_next_ent +// directory // const size_t DIR_ITERATOR_OPAQUE_SIZE = 40; @@ -151,6 +151,9 @@ extern LibError dir_next_ent(DirIterator* d, DirEnt* ent); extern LibError dir_close(DirIterator* d); +extern LibError dir_create(const char* P_path, mode_t mode); + + #ifdef __cplusplus typedef std::vector DirEnts; @@ -282,8 +285,8 @@ enum FileFlags // (only relevant for VFS) file will be written into the // appropriate subdirectory of the mount point established by - // vfs_mod_set_write_target. see documentation there. - FILE_WRITE_TO_MOD = FILE_WRITE|0x100, + // 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 diff --git a/source/lib/res/file/file_internal.h b/source/lib/res/file/file_internal.h index 6e68546d44..57c12cb2f0 100644 --- a/source/lib/res/file/file_internal.h +++ b/source/lib/res/file/file_internal.h @@ -83,13 +83,6 @@ extern LibError file_io_call_back(const void* block, size_t size, // higher-level code such as VfsUtil. extern LibError dir_filtered_next_ent(DirIterator* di, DirEnt* ent, const char* filter); - -// used by file.cpp and file_io.cpp -struct PosixFile -{ - int fd; - - // for reference counted memory-mapping - void* mapping; - uint map_refs; -}; +// returns file descriptor (int) given File (assumed to represent PosixFile). +// this avoids the need for declaring PosixFile here for file_io's use. +extern int file_fd_from_PosixFile(File* f); diff --git a/source/lib/res/file/file_io.cpp b/source/lib/res/file/file_io.cpp index a700f02ba4..16d7d1ba2e 100644 --- a/source/lib/res/file/file_io.cpp +++ b/source/lib/res/file/file_io.cpp @@ -109,7 +109,6 @@ LibError file_io_issue(File* f, off_t ofs, size_t size, void* p, FileIo* io) WARN_RETURN(ERR_INVALID_PARAM); const bool is_write = (f->flags & FILE_WRITE) != 0; - PosixFile* pf = (PosixFile*)f->opaque; PosixFileIo* pio = (PosixFileIo*)io; // note: cutting off at EOF is necessary to avoid transfer errors, @@ -137,7 +136,7 @@ LibError file_io_issue(File* f, off_t ofs, size_t size, void* p, FileIo* io) // send off async read/write request cb->aio_lio_opcode = is_write? LIO_WRITE : LIO_READ; cb->aio_buf = (volatile void*)p; - cb->aio_fildes = pf->fd; + cb->aio_fildes = file_fd_from_PosixFile(f); cb->aio_offset = ofs; cb->aio_nbytes = size; int err = lio_listio(LIO_NOWAIT, &cb, 1, (struct sigevent*)0); @@ -380,9 +379,7 @@ class IOManager ssize_t lowio() { - const PosixFile* pf = (const PosixFile*)f->opaque; - const int fd = pf->fd; - + const int fd = file_fd_from_PosixFile(f); lseek(fd, start_ofs, SEEK_SET); // emulate temp buffers - we take care of allocating and freeing. diff --git a/source/lib/res/file/path.cpp b/source/lib/res/file/path.cpp index 62550b54be..b0cb4a86e1 100644 --- a/source/lib/res/file/path.cpp +++ b/source/lib/res/file/path.cpp @@ -129,7 +129,7 @@ LibError file_make_full_portable_path(const char* n_full_path, char* path) debug_assert(path != n_full_path); // doesn't work in-place if(strncmp(n_full_path, n_root_dir, n_root_dir_len) != 0) - WARN_RETURN(ERR_PATH_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); return convert_path(path, n_full_path+n_root_dir_len, TO_PORTABLE); } diff --git a/source/lib/res/file/path.h b/source/lib/res/file/path.h index 1ed41738df..d6350c5e8d 100644 --- a/source/lib/res/file/path.h +++ b/source/lib/res/file/path.h @@ -25,35 +25,6 @@ #include "lib.h" -// internal use only: - -// if path is invalid (see source for criteria), return a -// descriptive error code, otherwise ERR_OK. -extern LibError path_validate(const char* path); -#define CHECK_PATH(path) RETURN_ERR(path_validate(path)) - -// if name is invalid, (see source for criteria), return a -// descriptive error code, otherwise ERR_OK. -extern LibError path_component_validate(const char* name); - - -// strip from the start of , prepend , -// and write to . -// used when converting VFS <--> real paths. -extern LibError path_replace(char* dst, const char* src, const char* remove, const char* replace); - - -// fill V_dir_only with the path portion of V_src_fn -// ("" if root dir, otherwise ending with /) -extern void path_dir_only(const char* V_src_fn, char* V_dir_only); - -// return pointer to the name component within V_src_fn -extern const char* path_name_only(const char* V_src_fn); - -extern const char* path_extension(const char* fn); - -extern void path_strip_fn(char* fn); - struct NextNumberedFilenameInfo { int next_num; @@ -76,6 +47,11 @@ extern void next_numbered_filename(const char* V_fn_fmt, extern bool path_is_atom_fn(const char* fn); +extern const char* file_get_random_name(); + +// note: other functions are declared directly in the public file.h header. + + extern void path_init(); extern void path_shutdown(); diff --git a/source/lib/res/file/trace.cpp b/source/lib/res/file/trace.cpp index 6aa274cfa1..c9192e77c0 100644 --- a/source/lib/res/file/trace.cpp +++ b/source/lib/res/file/trace.cpp @@ -264,7 +264,7 @@ LibError trace_read_from_file(const char* trace_filename, Trace* t) RETURN_ERR(file_make_full_native_path(trace_filename, N_fn)); FILE* f = fopen(N_fn, "rt"); if(!f) - WARN_RETURN(ERR_FILE_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); // we use trace_add, which is the same mechanism called by trace_notify*; // therefore, tracing needs to be enabled. diff --git a/source/lib/res/file/vfs.cpp b/source/lib/res/file/vfs.cpp index e2fa5096f3..b58581e271 100644 --- a/source/lib/res/file/vfs.cpp +++ b/source/lib/res/file/vfs.cpp @@ -113,14 +113,17 @@ static void VDir_dtor(VDir* vd) } } -static LibError VDir_reload(VDir* vd, const char* path, Handle UNUSED(hvd)) +static LibError VDir_reload(VDir* vd, const char* V_path, Handle UNUSED(hvd)) { + debug_assert(*V_path == '\0' || V_path[strlen(V_path)-1] == '/'); +/*/*SLASH // add required trailing slash if not already present to make // caller's life easier. char V_path_slash[PATH_MAX]; RETURN_ERR(path_append(V_path_slash, path, "")); +*/ - RETURN_ERR(xdir_open(V_path_slash, &vd->di)); + RETURN_ERR(xdir_open(V_path, &vd->di)); vd->di_valid = 1; return ERR_OK; } @@ -202,7 +205,8 @@ LibError vfs_realpath(const char* V_path, char* realpath) CHECK_ERR(tree_lookup(V_path, &tf)); const char* atom_fn = tfile_get_atom_fn(tf); - return mount_realpath(atom_fn, tf, realpath); + const Mount* m = tfile_get_mount(tf); + return mount_realpath(atom_fn, m, realpath); } @@ -289,12 +293,12 @@ static LibError VFile_reload(VFile* vf, const char* V_path, Handle) return err; } - // careful! FILE_WRITE_TO_MOD consists of 2 bits; they must both be + // careful! FILE_WRITE_TO_TARGET consists of 2 bits; they must both be // set (one of them is FILE_WRITE, which can be set independently). // this is a bit ugly but better than requiring users to write - // FILE_WRITE|FILE_WRITE_TO_MOD. - if((flags & FILE_WRITE_TO_MOD) == FILE_WRITE_TO_MOD) - RETURN_ERR(set_mount_to_mod_target(tf)); + // FILE_WRITE|FILE_WRITE_TO_TARGET. + if((flags & FILE_WRITE_TO_TARGET) == FILE_WRITE_TO_TARGET) + RETURN_ERR(set_mount_to_write_target(tf)); RETURN_ERR(xfile_open(V_path, flags, tf, &vf->f)); diff --git a/source/lib/res/file/vfs.h b/source/lib/res/file/vfs.h index 7b3cc0ec51..280e72a2ee 100644 --- a/source/lib/res/file/vfs.h +++ b/source/lib/res/file/vfs.h @@ -232,17 +232,6 @@ extern void vfs_display(void); // for an an indication of how large fixed-size user buffers should be, // use PATH_MAX. -// convenience function -extern void path_copy(char* dst, const char* src); - -// combine and into one path, and write to . -// if necessary, a directory separator is added between the paths. -// each may be empty, filenames, or full paths. -// total path length (including '\0') must not exceed PATH_MAX. -extern LibError path_append(char* dst, const char* path1, const char* path2); - -extern const char* path_extension(const char* fn); - // VFS paths are of the form: "(dir/)*file?" // in English: '/' as path separator; trailing '/' required for dir names; // no leading '/', since "" is the root dir. @@ -290,12 +279,12 @@ extern LibError vfs_unmount(const char* name); // set current "mod write directory" to P_target_dir, which must // already have been mounted into the VFS. -// all files opened for writing with the FILE_WRITE_TO_MOD flag set will +// all files opened for writing with the FILE_WRITE_TO_TARGET flag set will // be written into the appropriate subdirectory of this mount point. // // this allows e.g. the editor to write files that are already // stored in archives, which are read-only. -extern LibError vfs_mod_set_write_target(const char* P_target_dir); +extern LibError vfs_set_write_target(const char* P_target_dir); // diff --git a/source/lib/res/file/vfs_mount.cpp b/source/lib/res/file/vfs_mount.cpp index 532a9fec3f..7143661234 100644 --- a/source/lib/res/file/vfs_mount.cpp +++ b/source/lib/res/file/vfs_mount.cpp @@ -170,15 +170,20 @@ bool mount_should_replace(const Mount* m_old, const Mount* m_new, } -// given a file's TFile and V_path, return its actual location (portable path). -LibError mount_realpath(const char* V_path, const TFile* tf, char* P_real_path) +// given Mount and V_path, return its actual location (portable path). +LibError mount_realpath(const char* V_path, const Mount* m, char* P_real_path) { - const Mount* m = tfile_get_mount(tf); - const char* P_parent_path = m->P_name.c_str(); - const char* remove = m->V_mount_point.c_str(); - const char* replace = P_parent_path; - return path_replace(P_real_path, V_path, remove, replace); + const char* replace = m->P_name.c_str(); // P_parent_path + CHECK_ERR(path_replace(P_real_path, V_path, remove, replace)); + + // if P_real_path ends with '/' (a remnant from V_path), strip + // it because that's not acceptable for portable paths. + const size_t P_len = strlen(P_real_path); + if(P_len != 0 && P_real_path[P_len-1] == '/') + P_real_path[P_len-1] = '\0'; + + return ERR_OK; } @@ -500,8 +505,10 @@ static LibError mount_dir_tree(TDir* td, const Mount& m) // pop to come at end of loop => this is easiest) dir_queue.push_back(TDirAndPath(td, m.P_name.c_str())); +static int seq2; do { +seq2++; TDir* const td = dir_queue.front().td; const char* P_path = dir_queue.front().path.c_str(); @@ -585,7 +592,7 @@ static const Mount& add_mount(const char* V_mount_point, const char* P_real_path static LibError remount(const Mount& m) { TDir* td; - CHECK_ERR(tree_lookup_dir(m.V_mount_point.c_str(), &td, LF_CREATE_MISSING)); + CHECK_ERR(tree_add_path(m.V_mount_point.c_str(), &m, &td)); switch(m.type) { @@ -706,7 +713,7 @@ LibError vfs_unmount(const char* P_name) std::bind2nd(Mount::equal_to(), P_name)); // none were removed - need to complain so that the caller notices. if(last == end) - WARN_RETURN(ERR_PATH_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); // trim list and actually remove 'invalidated' entries. mounts.erase(last, end); @@ -732,26 +739,55 @@ LibError mount_make_vfs_path(const char* P_path, char* V_path) const char* remove = m.P_name.c_str(); const char* replace = m.V_mount_point.c_str(); - if(path_replace(V_path, P_path, remove, replace) == 0) + if(path_replace(V_path, P_path, remove, replace) == ERR_OK) return ERR_OK; } - WARN_RETURN(ERR_PATH_NOT_FOUND); + WARN_RETURN(ERR_TNODE_NOT_FOUND); } -static const Mount* mod_target; +static const Mount* write_target; + +// 2006-05-09 JW note: we are wanting to move XMB files into a separate +// folder tree (no longer interspersed with XML), so that deleting them is +// easier and dirs are less cluttered. +// +// if several mods are active, VFS would have several RealDirs mounted +// and could no longer automatically determine the write target. +// +// one solution would be to use this set_write_target support to choose the +// correct dir; however, XMB files may be generated whilst editing +// (which also requires a write_target to write files that are actually +// currently in archives), so we'd need to save/restore write_target. +// this would't be thread-safe => disaster. +// +// a vfs_store_to(filename, flags, N_actual_path) API would work, but it'd +// impose significant burden on users (finding the actual native dir), +// and be prone to abuse. additionally, it would be difficult to +// propagate N_actual_path to VFile_reload where it is needed; +// this would end up messy. +// +// instead, we'll write XMB files into VFS path "mods/$MODNAME/..", +// into which the realdir of the same name (located in some writable folder) +// is mounted; VFS therefore can write without problems. +// +// however, other code (e.g. archive builder) doesn't know about this +// trick - it only sees the flat VFS namespace, which doesn't +// include mods/$MODNAME (that is hidden). to solve this, we also mount +// any active mod's XMB dir into VFS root for read access. + // set current "mod write directory" to P_target_dir, which must // already have been mounted into the VFS. -// all files opened for writing with the FILE_WRITE_TO_MOD flag set will +// all files opened for writing with the FILE_WRITE_TO_TARGET flag set will // be written into the appropriate subdirectory of this mount point. // // this allows e.g. the editor to write files that are already // stored in archives, which are read-only. -LibError vfs_mod_set_write_target(const char* P_target_dir) +LibError vfs_set_write_target(const char* P_target_dir) { for(MountIt it = mounts.begin(); it != mounts.end(); ++it) { @@ -763,7 +799,7 @@ LibError vfs_mod_set_write_target(const char* P_target_dir) // found it in list of mounted dirs if(!strcmp(m.P_name.c_str(), P_target_dir)) { - mod_target = &m; + write_target = &m; return ERR_OK; } } @@ -772,17 +808,17 @@ LibError vfs_mod_set_write_target(const char* P_target_dir) } -// 'relocate' tf to the mounting established by vfs_mod_set_write_target. -// call if is being opened with FILE_WRITE_TO_MOD flag set. -LibError set_mount_to_mod_target(TFile* tf) +// 'relocate' tf to the mounting established by vfs_set_write_target. +// call if is being opened with FILE_WRITE_TO_TARGET flag set. +LibError set_mount_to_write_target(TFile* tf) { - if(!mod_target) + if(!write_target) WARN_RETURN(ERR_NOT_MOUNTED); - tfile_set_mount(tf, mod_target); + tfile_set_mount(tf, write_target); // invalidate the previous values. we don't need to be clever and - // set size to that of the file in the new mod_target mount point. + // set size to that of the file in the new write_target mount point. // this is because we're only called for files that are being // opened for writing, which will change these values anyway. tree_update_file(tf, 0, 0); @@ -807,16 +843,20 @@ void mount_shutdown() } +static const Mount* MULTIPLE_MOUNTINGS = (const Mount*)-1; - - - +// RDTODO: when should this be called? TDir ctor can already set this. LibError mount_attach_real_dir(RealDir* rd, const char* P_path, const Mount* m, uint flags) { // more than one real dir mounted into VFS dir // (=> can't create files for writing here) if(rd->m) - rd->m = (const Mount*)-1; + { + // HACK: until RealDir reorg is done, we're going to have to deal with + // "attaching" to real dirs twice. don't mess up rd->m if m is the same. + if(rd->m != m) + rd->m = MULTIPLE_MOUNTINGS; + } else rd->m = m; @@ -848,6 +888,18 @@ void mount_detach_real_dir(RealDir* rd) } +LibError mount_create_real_dir(const char* V_path, const Mount* m) +{ + if(!m || m == MULTIPLE_MOUNTINGS || m->type != MT_FILE) + return ERR_OK; + + char P_path[PATH_MAX]; + RETURN_ERR(mount_realpath(V_path, m, P_path)); + + return dir_create(P_path, S_IRWXU|S_IRWXG|S_IRWXO); +} + + LibError mount_populate(TDir* td, RealDir* rd) { UNUSED2(td); diff --git a/source/lib/res/file/vfs_mount.h b/source/lib/res/file/vfs_mount.h index bac16da9f0..75e0be42f6 100644 --- a/source/lib/res/file/vfs_mount.h +++ b/source/lib/res/file/vfs_mount.h @@ -61,9 +61,8 @@ extern char mount_get_type(const Mount* m); extern Handle mount_get_archive(const Mount* m); -// given a file's TFile and V_path, return its actual location (portable path). -extern LibError mount_realpath(const char* V_path, const TFile* tf, char* P_real_path); - +// given Mount and V_path, return its actual location (portable path). +extern LibError mount_realpath(const char* V_path, const Mount* m, char* P_real_path); @@ -86,6 +85,8 @@ struct RealDir extern LibError mount_attach_real_dir(RealDir* rd, const char* P_path, const Mount* m, uint flags); extern void mount_detach_real_dir(RealDir* rd); +extern LibError mount_create_real_dir(const char* V_path, const Mount* m); + extern LibError mount_populate(TDir* td, RealDir* rd); @@ -95,9 +96,9 @@ extern LibError mount_populate(TDir* td, RealDir* rd); // because this call leaves the VFS in limbo!! extern void mount_release_all_archives(); -// 'relocate' tf to the mounting established by vfs_mod_set_write_target. -// call if is being opened with FILE_WRITE_TO_MOD flag set. -extern LibError set_mount_to_mod_target(TFile* tf); +// 'relocate' tf to the mounting established by vfs_set_write_target. +// call if is being opened with FILE_WRITE_TO_TARGET flag set. +extern LibError set_mount_to_write_target(TFile* tf); // rebuild the VFS, i.e. re-mount everything. open files are not affected. diff --git a/source/lib/res/file/vfs_redirector.cpp b/source/lib/res/file/vfs_redirector.cpp index fe31162721..ed32d57a8a 100644 --- a/source/lib/res/file/vfs_redirector.cpp +++ b/source/lib/res/file/vfs_redirector.cpp @@ -46,7 +46,8 @@ LibError file_open_vfs(const char* V_path, uint flags, TFile* tf, File* f) // out { char N_path[PATH_MAX]; - RETURN_ERR(mount_realpath(V_path, tf, N_path)); + const Mount* m = tfile_get_mount(tf); + RETURN_ERR(mount_realpath(V_path, m, N_path)); RETURN_ERR(file_open(N_path, flags|FILE_DONT_SET_FN, f)); // file_open didn't set fc.atom_fn due to FILE_DONT_SET_FN. f->atom_fn = file_make_unique_fn_copy(V_path); @@ -148,6 +149,8 @@ LibError xfile_open(const char* V_path, uint flags, TFile* tf, File* f) { // find out who is providing this file const Mount* m = tfile_get_mount(tf); + debug_assert(m != 0); + // HACK: see decl of vtbls. ideally vtbl would already be stored in // Mount, but that's not implemented yet. char c = mount_get_type(m); diff --git a/source/lib/res/file/vfs_tree.cpp b/source/lib/res/file/vfs_tree.cpp index 07d38971a5..01eb71b7c9 100644 --- a/source/lib/res/file/vfs_tree.cpp +++ b/source/lib/res/file/vfs_tree.cpp @@ -96,6 +96,8 @@ class TNode { public: TNodeType type; + // allocated and owned by vfs_mount + const Mount* m; // rationale: we store both entire path and name component. // this increases size of VFS (2 pointers needed here) and @@ -103,10 +105,14 @@ public: // iterate over all dir name components. // we could retrieve name via strrchr(path, '/'), but that is slow. const char* V_path; + // ends with slash if this is a directory! + // this is compared as a normal string (not pointer comparison), but + // the pointer passed must obviously remain valid, so it is + // usually an atom_fn. const char* name; - TNode(TNodeType type_, const char* V_path_, const char* name_) - : type(type_), V_path(V_path_), name(name_) + TNode(TNodeType type_, const char* V_path_, const char* name_, const Mount* m_) + : type(type_), V_path(V_path_), name(name_), m(m_) { } }; @@ -115,19 +121,14 @@ public: class TFile : public TNode { public: - // required: - const Mount* m; - // allocated and owned by caller (mount code) - off_t size; time_t mtime; uintptr_t memento; - TFile(const char* V_path, const char* name, const Mount* m_) - : TNode(NT_FILE, V_path, name) + TFile(const char* V_path, const char* name, const Mount* m) + : TNode(NT_FILE, V_path, name, m) { - m = m_; size = 0; mtime = 0; memento = 0; @@ -187,20 +188,21 @@ class TDir : public TNode { uint flags; // enum TDirFlags - RealDir rd; - TChildren children; public: - TDir(const char* V_path, const char* name) - : TNode(NT_DIR, V_path, name), children() +RealDir rd; // HACK; removeme + + TDir(const char* V_path, const char* name, const Mount* m_) + : TNode(NT_DIR, V_path, name, 0), children() { flags = 0; - rd.m = 0; + + rd.m = m_; rd.watch = 0; + mount_create_real_dir(V_path, rd.m); } - TNode* find(const char* name) const { return children.find(name); } TChildrenIt begin() const { return children.begin(); } TChildrenIt end() const { return children.end(); } @@ -218,36 +220,42 @@ public: } } - - LibError add(const char* name_tmp, TNodeType type, TNode** pnode) + TNode* find(const char* name) const { + return children.find(name); + } + + // must not be called if already exists! use find() first or + // find_and_add instead. + LibError add(const char* name_tmp, TNodeType type, TNode** pnode, const Mount* m_override = 0) + { + // note: must be done before path_append for security + // (otherwise, '/' in wouldn't be caught) + RETURN_ERR(path_component_validate(name_tmp)); + char V_new_path_tmp[PATH_MAX]; - path_append(V_new_path_tmp, V_path, name_tmp); + const uint flags = (type == NT_DIR)? PATH_APPEND_SLASH : 0; + RETURN_ERR(path_append(V_new_path_tmp, V_path, name_tmp, flags)); const char* V_new_path = file_make_unique_fn_copy(V_new_path_tmp); const char* name = path_name_only(V_new_path); + if(type == NT_DIR) + name = file_make_unique_fn_copy(name_tmp); - RETURN_ERR(path_component_validate(name)); - - TNode* node = children.find(name); - if(node) - { - if(node->type != type) - return (type == NT_FILE)? ERR_NOT_FILE : ERR_NOT_DIR; - - *pnode = node; - return INFO_ALREADY_PRESENT; - } + const Mount* m = rd.m; + if(m_override) + m = m_override; // note: if anything below fails, this mem remains allocated in the // pool, but that "can't happen" and is OK because pool is big enough. void* mem = node_alloc(); if(!mem) WARN_RETURN(ERR_NO_MEM); + TNode* node; #include "nommgr.h" if(type == NT_FILE) - node = new(mem) TFile(V_new_path, name, rd.m); + node = new(mem) TFile(V_new_path, name, m); else - node = new(mem) TDir(V_new_path, name); + node = new(mem) TDir (V_new_path, name, m); #include "mmgr.h" children.insert(name, node); @@ -256,6 +264,23 @@ public: return ERR_OK; } + LibError find_and_add(const char* name, TNodeType type, TNode** pnode, const Mount* m = 0) + { + TNode* node = children.find(name); + if(node) + { + // wrong type (dir vs. file) + if(node->type != type) + WARN_RETURN(ERR_TNODE_WRONG_TYPE); + + *pnode = node; + return INFO_ALREADY_PRESENT; + } + + return add(name, type, pnode, m); + } + + // empty this directory and all subdirectories; used when rebuilding VFS. void clearR() { @@ -368,77 +393,56 @@ static void displayR(TDir* td, int indent_level) } +struct LookupCbParams +{ + const bool create_missing; + TDir* td; // current dir; assigned from node + TNode* node; // latest node returned (dir or file) + LookupCbParams(uint flags, TDir* td_) + : create_missing((flags & LF_CREATE_MISSING) != 0), td(td_) + { + // init in case lookup's is "". + // this works because TDir is derived from TNode. + node = (TNode*)td; + } +}; + +static LibError lookup_cb(const char* component, bool is_dir, void* ctx) +{ + LookupCbParams* p = (LookupCbParams*)ctx; + const TNodeType type = is_dir? NT_DIR : NT_FILE; + + p->td->populate(); + + p->node = p->td->find(component); + if(!p->node) + { + if(p->create_missing) + RETURN_ERR(p->td->add(component, type, &p->node)); + else + // complaining is left to callers; vfs_exists must be + // able to fail quietly. + return ERR_TNODE_NOT_FOUND; // NOWARN + } + if(p->node->type != type) + WARN_RETURN(ERR_TNODE_WRONG_TYPE); + + if(is_dir) + p->td = (TDir*)p->node; + + return INFO_CB_CONTINUE; +} + static LibError lookup(TDir* td, const char* path, uint flags, TNode** pnode) { - // early out: "" => return this directory (usually VFS root) - if(path[0] == '\0') - { - *pnode = (TNode*)td; // HACK: TDir is at start of TNode - return ERR_OK; - } - - CHECK_PATH(path); - debug_assert( (flags & ~(LF_CREATE_MISSING|LF_START_DIR)) == 0 ); // no undefined bits set + debug_assert( (flags & ~(LF_CREATE_MISSING|LF_START_DIR)) == 0 ); - const bool create_missing = !!(flags & LF_CREATE_MISSING); - - // copy into (writeable) buffer so we can 'tokenize' path components - // by replacing '/' with '\0'. - char V_path[PATH_MAX]; - strcpy_s(V_path, sizeof(V_path), path); - char* cur_component = V_path; - - TNodeType type = NT_DIR; - - // successively navigate to the next component in . - TNode* node = 0; - for(;;) - { - // "extract" cur_component string (0-terminate by replacing '/') - char* slash = (char*)strchr(cur_component, '/'); - if(!slash) - { - // string ended in slash => return the current dir node. - if(*cur_component == '\0') - break; - - // it's a filename - type = NT_FILE; - } - // normal operation (cur_component is a directory) - else - { - td->populate(); - - *slash = '\0'; - } - - // create (no-op if it already exists) - if(create_missing) - RETURN_ERR(td->add(V_path, type, &node)); - else - { - node = td->find(cur_component); - if(!node) - return slash? ERR_PATH_NOT_FOUND : ERR_FILE_NOT_FOUND; - if(node->type != type) - return slash? ERR_NOT_DIR : ERR_NOT_FILE; - } - - // cur_component was a filename => we're done - if(!slash) - break; - // else: it was a directory; advance - // .. undo having replaced '/' with '\0' - this means V_path will - // store the complete path up to and including cur_component. - *slash = '/'; - cur_component = slash+1; - td = (TDir*)node; - } + LookupCbParams p(flags, td); + RETURN_ERR(path_foreach_component(path, lookup_cb, &p)); // success. - *pnode = node; + *pnode = p.node; return ERR_OK; } @@ -468,7 +472,7 @@ static void tree_root_init() #include "nommgr.h" // placement new void* mem = node_alloc(); if(mem) - tree_root = new(mem) TDir("", ""); + tree_root = new(mem) TDir("", "", 0); #include "mmgr.h" } @@ -540,7 +544,7 @@ LibError tree_add_file(TDir* td, const char* name, const Mount* m, off_t size, time_t mtime, uintptr_t memento) { TNode* node; - LibError ret = td->add(name, NT_FILE, &node); + LibError ret = td->find_and_add(name, NT_FILE, &node); RETURN_ERR(ret); if(ret == INFO_ALREADY_PRESENT) { @@ -566,7 +570,7 @@ LibError tree_add_file(TDir* td, const char* name, LibError tree_add_dir(TDir* td, const char* name, TDir** ptd) { TNode* node; - RETURN_ERR(td->add(name, NT_DIR, &node)); + RETURN_ERR(td->find_and_add(name, NT_DIR, &node)); *ptd = (TDir*)node; return ERR_OK; } @@ -577,7 +581,7 @@ LibError tree_lookup_dir(const char* path, TDir** ptd, uint flags) { // path is not a directory; TDir::lookup might return a file node if(path[0] != '\0' && path[strlen(path)-1] != '/') - WARN_RETURN(ERR_NOT_DIR); + WARN_RETURN(ERR_TNODE_WRONG_TYPE); TDir* td = (flags & LF_START_DIR)? *ptd : tree_root; TNode* node; @@ -592,7 +596,7 @@ LibError tree_lookup(const char* path, TFile** pfile, uint flags) { // path is not a file; TDir::lookup might return a directory node if(path[0] == '\0' || path[strlen(path)-1] == '/') - WARN_RETURN(ERR_NOT_FILE); + WARN_RETURN(ERR_TNODE_WRONG_TYPE); TNode* node; LibError ret = lookup(tree_root, path, flags, &node); @@ -602,11 +606,46 @@ LibError tree_lookup(const char* path, TFile** pfile, uint flags) } +struct AddPathCbParams +{ + const Mount* const m; + TDir* td; + AddPathCbParams(const Mount* m_) + : m(m_), td(tree_root) {} +}; + +static LibError add_path_cb(const char* component, bool is_dir, void* ctx) +{ + AddPathCbParams* p = (AddPathCbParams*)ctx; + + // should only be called for directory paths, so complain if not dir. + if(!is_dir) + WARN_RETURN(ERR_TNODE_WRONG_TYPE); + + TNode* node; + RETURN_ERR(p->td->find_and_add(component, NT_DIR, &node, p->m)); + + p->td = (TDir*)node; + return INFO_CB_CONTINUE; +} + +// iterate over all components in V_dir_path (must reference a directory, +// i.e. end in slash). for any that are missing, add them with the +// specified mount point. this is useful for mounting directories. +// +// passes back the last directory encountered. +LibError tree_add_path(const char* V_dir_path, const Mount* m, TDir** ptd) +{ + AddPathCbParams p(m); + RETURN_ERR(path_foreach_component(V_dir_path, add_path_cb, &p)); + *ptd = p.td; + return ERR_OK; +} + + ////////////////////////////////////////////////////////////////////////////// - - // rationale: see DirIterator definition in file.h. struct TreeDirIterator { diff --git a/source/lib/res/file/vfs_tree.h b/source/lib/res/file/vfs_tree.h index 7129507e7a..c460b2141f 100644 --- a/source/lib/res/file/vfs_tree.h +++ b/source/lib/res/file/vfs_tree.h @@ -91,6 +91,14 @@ extern LibError tree_lookup(const char* path, TFile** ptf, uint flags = 0); extern LibError tree_lookup_dir(const char* path, TDir** ptd, uint flags = 0); +// iterate over all components in V_dir_path (must reference a directory, +// i.e. end in slash). for any that are missing, add them with the +// specified mount point. this is useful for mounting directories. +// +// passes back the last directory encountered. +extern LibError tree_add_path(const char* V_dir_path, const Mount* m, TDir** ptd); + + extern LibError tree_dir_open(const char* path_slash, DirIterator* di); extern LibError tree_dir_next_ent(DirIterator* di, DirEnt* ent); extern LibError tree_dir_close(DirIterator* di); diff --git a/source/lib/res/res.h b/source/lib/res/res.h index f0c9556d37..da8eb0ee40 100644 --- a/source/lib/res/res.h +++ b/source/lib/res/res.h @@ -2,4 +2,5 @@ #include "h_mgr.h" #include "file/vfs.h" +#include "lib/path_util.h" #include "mem.h" diff --git a/source/lib/sysdep/win/waio.cpp b/source/lib/sysdep/win/waio.cpp index c422553c28..df1232d559 100644 --- a/source/lib/sysdep/win/waio.cpp +++ b/source/lib/sysdep/win/waio.cpp @@ -250,7 +250,7 @@ static LibError aio_h_set(const int fd, const HANDLE h) // setting invalid handle if(!is_valid_file_handle(h)) { - err = ERR_NOT_FILE; + err = ERR_TNODE_WRONG_TYPE; goto fail; } } diff --git a/source/lib/sysdep/win/wdbg_sym.cpp b/source/lib/sysdep/win/wdbg_sym.cpp index 348825f31d..a8b7409a19 100644 --- a/source/lib/sysdep/win/wdbg_sym.cpp +++ b/source/lib/sysdep/win/wdbg_sym.cpp @@ -290,6 +290,8 @@ static LibError ia32_walk_stack(STACKFRAME64* sf) // read stack frame void* fp = ((void**)prev_fp)[0]; void* ret_addr = ((void**)prev_fp)[1]; + if(!fp) + return INFO_ALL_COMPLETE; if(!debug_is_stack_ptr(fp)) WARN_RETURN(ERR_14); if(!debug_is_code_ptr(ret_addr)) diff --git a/source/lib/sysdep/win/win.cpp b/source/lib/sysdep/win/win.cpp index 0e9af0b147..8e522699dc 100644 --- a/source/lib/sysdep/win/win.cpp +++ b/source/lib/sysdep/win/win.cpp @@ -55,9 +55,8 @@ static LibError LibError_from_GLE(bool warn_if_failed = true) case ERROR_ACCESS_DENIED: err = ERR_FILE_ACCESS; break; case ERROR_FILE_NOT_FOUND: - err = ERR_FILE_NOT_FOUND; break; case ERROR_PATH_NOT_FOUND: - err = ERR_PATH_NOT_FOUND; break; + err = ERR_TNODE_NOT_FOUND; break; default: err = ERR_FAIL; break; diff --git a/source/lib/sysdep/win/wposix.h b/source/lib/sysdep/win/wposix.h index 40d029f08a..2ed4314ad9 100644 --- a/source/lib/sysdep/win/wposix.h +++ b/source/lib/sysdep/win/wposix.h @@ -59,7 +59,8 @@ extern "C" { // // -#define PATH_MAX 260 // matches Win32 MAX_PATH +// Win32 MAX_PATH is 260; our number may be a bit more efficient. +#define PATH_MAX 256 #if OS_WIN # ifndef SIZE_MAX // VC2005 already defines this in limits.h diff --git a/source/ps/FilePacker.cpp b/source/ps/FilePacker.cpp index 162629f700..25f46fd0a3 100644 --- a/source/ps/FilePacker.cpp +++ b/source/ps/FilePacker.cpp @@ -34,7 +34,7 @@ CFilePacker::CFilePacker(u32 version, const char magicstr[4]) void CFilePacker::Write(const char* filename) { // write out all data (including header) - if(vfs_store(filename, &m_Data[0], m_Data.size(), FILE_NO_AIO|FILE_WRITE_TO_MOD) < 0) + if(vfs_store(filename, &m_Data[0], m_Data.size(), FILE_NO_AIO|FILE_WRITE_TO_TARGET) < 0) throw PSERROR_File_WriteFailed(); } diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index f6155600ec..a0325d7a06 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -571,12 +571,23 @@ static void InitVfs(const char* argv0) (void)file_set_root_dir(argv0, "../data"); vfs_init(); - vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE); vfs_mount("screenshots/", "screenshots"); vfs_mount("profiles/", "profiles", VFS_MOUNT_RECURSIVE); + // rationale: + // - this is in a separate real directory so that it can later be moved + // to $APPDATA to allow running without Admin access. + // - we mount as archivable so that all files will be added to archive. + // even though we write out XMBs here, they will eventually be read, + // so putting them in an archive boosts performance. + vfs_mount("cache/", "cache", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_ARCHIVABLE); + + // --- this belongs in a LoadMod function + vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE); + // --- + // TODO: once people can load multiple mods, set the top one to be the write target - vfs_mod_set_write_target("mods/official"); + vfs_set_write_target("mods/official"); // don't try vfs_display yet: SDL_Init hasn't yet redirected stdout } diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index 11cdb1b82a..25a64d924d 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -132,25 +132,17 @@ no_ip: } - +// not thread-safe! static const wchar_t* HardcodedErrorString(int err) { -#define E(sym) case sym: return L ## #sym; - - switch(err) - { - E(ERR_NO_MEM) - E(ERR_FILE_NOT_FOUND) - E(ERR_INVALID_HANDLE) - E(ERR_INVALID_PARAM) - E(ERR_EOF) - E(ERR_PATH_NOT_FOUND) - E(ERR_PATH_LENGTH) - default: - return 0; - } + char description[200]; + error_description_r((LibError)err, description, ARRAY_SIZE(description)); + static wchar_t output_buf[200]; + mbstowcs(output_buf, description, ARRAY_SIZE(output_buf)); + return output_buf; } +// not thread-safe! const wchar_t* ErrorString(int err) { // language file not available (yet) diff --git a/source/ps/VFSUtil.cpp b/source/ps/VFSUtil.cpp index 2d6c728177..7a2302cca0 100644 --- a/source/ps/VFSUtil.cpp +++ b/source/ps/VFSUtil.cpp @@ -26,7 +26,10 @@ bool VFSUtil::FindFiles (const CStr& dirname, const char* filter, FileList& file DirEnt entry; while ((err = vfs_dir_next_ent(dir, &entry, filter)) == 0) { - files.push_back(dirname+"/"+entry.name); + CStr path = dirname+entry.name; + if(DIRENT_IS_DIR(&entry)) + path += '/'; + files.push_back(path); } if (err != ERR_DIR_END) diff --git a/source/ps/VFSUtil.h b/source/ps/VFSUtil.h index aea9fc7d30..5324c32837 100644 --- a/source/ps/VFSUtil.h +++ b/source/ps/VFSUtil.h @@ -1,4 +1,5 @@ #include "ps/CStr.h" +#include "lib/path_util.h" // for convenience #include "lib/res/file/vfs.h" namespace VFSUtil diff --git a/source/ps/XML/Xeromyces.cpp b/source/ps/XML/Xeromyces.cpp index a8e187f4c7..0efa58065d 100644 --- a/source/ps/XML/Xeromyces.cpp +++ b/source/ps/XML/Xeromyces.cpp @@ -170,6 +170,37 @@ void CXeromyces::Terminate() } } + +// Find out write location of the XMB file corresponding to xmlFilename +void CXeromyces::getXMBPath(const char* xmlFilename, const char* xmbFilename, + char* xmbPath) +{ + // rationale: + // - it is necessary to write out XMB files into a subdirectory + // corresponding to the mod from which the XML file is taken. + // this avoids confusion when multiple mods are active - + // their XMB files' VFS filename would otherwise be indistinguishable. + // - we group files in the cache/ mount point first by mod, and only + // then XMB. this is so that all output files for a given mod can + // easily be deleted. the operation of deleting all old/unused + // XMB files requires a program anyway (to find out which are no + // longer needed), so it's not a problem that XMB files reside in + // a subdirectory (which would make manually deleting all harder). + + // get real path of XML file (e.g. mods/official/entities/...) + char P_XMBRealPath[PATH_MAX]; + vfs_realpath(xmlFilename, P_XMBRealPath); + + // extract mod name from that + char modName[PATH_MAX]; + // .. NOTE: can't use %s, of course (keeps going beyond '/') + int matches = sscanf(P_XMBRealPath, "mods/%[^/]", modName); + debug_assert(matches == 1); + + // build full name: cache, then mod name, XMB subdir, original XMB path + snprintf(xmbPath, PATH_MAX, "cache/mods/%s/xmb/%s", modName, xmbFilename); +} + PSRETURN CXeromyces::Load(const char* filename) { // Make sure the .xml actually exists @@ -224,11 +255,14 @@ PSRETURN CXeromyces::Load(const char* filename) } xmbFilename += buf; + char xmbPath[PATH_MAX]; + getXMBPath(filename, xmbFilename, xmbPath); + // If the file exists, use it - if (vfs_exists(xmbFilename)) + if (vfs_exists(xmbPath)) { - if (ReadXMBFile(xmbFilename)) + if (ReadXMBFile(xmbPath)) return PSRETURN_OK; else return PSRETURN_Xeromyces_XMLOpenFailed; @@ -292,7 +326,7 @@ PSRETURN CXeromyces::Load(const char* filename) handler.CreateXMB(); // Save the file to disk, so it can be loaded quickly next time - vfs_store(xmbFilename, handler.buffer.buffer, handler.buffer.length, FILE_NO_AIO); + vfs_store(xmbPath, handler.buffer.buffer, handler.buffer.length, FILE_NO_AIO); // Store the buffer so it can be freed later XMBBuffer = handler.buffer.steal_buffer(); diff --git a/source/ps/XML/Xeromyces.h b/source/ps/XML/Xeromyces.h index acf5108eaf..05c5444246 100644 --- a/source/ps/XML/Xeromyces.h +++ b/source/ps/XML/Xeromyces.h @@ -30,6 +30,11 @@ public: static void Terminate(); private: + + // Find out write location of the XMB file corresponding to xmlFilename + void getXMBPath(const char* xmlFilename, const char* xmbFilename, + char* xmbPath); + bool ReadXMBFile(const char* filename); XMBFile* XMB; diff --git a/source/ps/i18n.cpp b/source/ps/i18n.cpp index 53d9b55ea0..58cf1e3f12 100644 --- a/source/ps/i18n.cpp +++ b/source/ps/i18n.cpp @@ -42,7 +42,7 @@ bool I18n::LoadLanguage(const char* name) // Automatically delete the pointer when returning early std::auto_ptr locale (locale_ptr); - CStr dirname = CStr("language/")+name; + CStr dirname = CStr("language/")+name+"/"; VFSUtil::FileList files; VFSUtil::FileList::iterator filename; diff --git a/source/ps/scripting/JSInterface_VFS.cpp b/source/ps/scripting/JSInterface_VFS.cpp index 12e0cb8663..99734fb974 100644 --- a/source/ps/scripting/JSInterface_VFS.cpp +++ b/source/ps/scripting/JSInterface_VFS.cpp @@ -13,7 +13,7 @@ // shared error handling code #define JS_CHECK_FILE_ERR(err)\ /* this is liable to happen often, so don't complain */\ - if(err == ERR_FILE_NOT_FOUND)\ + if(err == ERR_TNODE_NOT_FOUND)\ {\ *rval = JSVAL_NULL;\ return( JS_TRUE );\ diff --git a/source/scripting/DOMEvent.cpp b/source/scripting/DOMEvent.cpp index 331d36e365..485ed4520d 100644 --- a/source/scripting/DOMEvent.cpp +++ b/source/scripting/DOMEvent.cpp @@ -11,8 +11,13 @@ IEventTarget::~IEventTarget() delete( it->second ); } +TIMER_ADD_CLIENT(tc_dispatch_total); +TIMER_ADD_CLIENT(tc_dispatch_js_total); + bool IEventTarget::_DispatchEvent( CScriptEvent* evt, IEventTarget* target ) { +TIMER_ACCRUE(tc_dispatch_total); + PROFILE_START( "_DispatchEvent" ); // TODO: Deal correctly with multiple handlers @@ -29,10 +34,12 @@ bool IEventTarget::_DispatchEvent( CScriptEvent* evt, IEventTarget* target ) for( it = handlers.begin(); it != handlers.end(); it++ ) { DOMEventHandler id = *it; +{TIMER_ACCRUE(tc_dispatch_js_total); if( id && id->DispatchEvent( GetScriptExecContext( target ), evt ) ) { return( true ); } +} } HandlerRange range = m_Handlers_name.equal_range( evt->m_Type ); @@ -40,10 +47,12 @@ bool IEventTarget::_DispatchEvent( CScriptEvent* evt, IEventTarget* target ) for( itm = range.first; itm != range.second; itm++ ) { DOMEventHandler id = itm->second; +{TIMER_ACCRUE(tc_dispatch_js_total); if( id && id->DispatchEvent( GetScriptExecContext( target ), evt ) ) { return( true ); } +} } if( after && after->_DispatchEvent( evt, target ) )