diff --git a/source/lib/res/vfs.cpp b/source/lib/res/vfs.cpp index a1a9cbf4f3..47cf109441 100755 --- a/source/lib/res/vfs.cpp +++ b/source/lib/res/vfs.cpp @@ -355,7 +355,7 @@ static int dirent_cb(const char* name, const struct stat* s, uintptr_t user) char new_path[VFS_MAX_PATH]; CHECK_ERR(path_append(new_path, p_path, name)); - TDir* new_dir = tree_add_dir(dir, new_path, mount_point); + TDir* new_dir = tree_add_dir(dir, name); dir_queue->push_back(DirAndPath(new_dir, new_path)); return 0; } @@ -396,13 +396,18 @@ static int dirent_cb(const char* name, const struct stat* s, uintptr_t user) static int populate_dir(TDir* dir_, const char* p_path_, const TMountPoint* mount_point, bool recursive, TMountPoints* archives) { DirQueue dir_queue; - dir_queue.push_back(DirAndPath(dir_, p_path_)); DirQueue* const pdir_queue = recursive? &dir_queue : 0; + // kickoff (less efficient than goto, but c_str reference requires + // pop to come at end of loop => this is easiest) + dir_queue.push_back(DirAndPath(dir_, p_path_)); + do { - TDir* dir = dir_queue.front().dir; + TDir* const dir = dir_queue.front().dir; const char* p_path = dir_queue.front().path.c_str(); + + tree_mount(dir, p_path, mount_point); // add files and subdirs to this dir; // also adds the contents of archives if archives != 0. @@ -493,8 +498,8 @@ static int remount(Mount& m) const char* p_real_path = m.p_real_path.c_str(); const int flags = m.flags; const uint pri = m.pri; - TMountPoint* mount_point = &m.mount_point; - TMountPoints& archives = m.archives; + TMountPoint* mount_point = &m.mount_point; + TMountPoints& archives = m.archives; // callers have a tendency to forget required trailing '/'; // complain if it's not there, unless path = "" (root dir). @@ -528,10 +533,14 @@ static int unmount(Mount& m) // trivial, but used by vfs_shutdown and vfs_rebuild static inline void unmount_all(void) - { std::for_each(mounts.begin(), mounts.end(), unmount); } +{ + std::for_each(mounts.begin(), mounts.end(), unmount); +} static inline void remount_all() - { std::for_each(mounts.begin(), mounts.end(), remount); } +{ + std::for_each(mounts.begin(), mounts.end(), remount); +} @@ -568,14 +577,9 @@ int vfs_mount(const char* v_mount_point, const char* p_real_path, int flags, uin return -1; } - CHECK_PATH(v_mount_point); - - const Mount& new_mount = Mount(v_mount_point, p_real_path, flags, pri); - mounts.push_back(new_mount); - // actually mount the entry - Mount& m = mounts.back(); - return remount(m); + mounts.push_back(Mount(v_mount_point, p_real_path, flags, pri)); + return remount(mounts.back()); } @@ -653,12 +657,18 @@ static int make_file_path(char* path, const char* vfs_path, const TMountPoint* m struct VDir { - // we need to cache the complete contents of the directory: + // xxx we need to cache the complete contents of the directory: // if we reference the real directory and it changes, // the c_str pointers may become invalid, and some files // may be returned out of order / not at all. // we copy the directory's subdirectory and file containers. void* latch; + + // safety check +#ifndef NDEBUG + const char* filter; + bool filter_latched; +#endif }; H_TYPE_DEFINE(VDir); @@ -710,31 +720,44 @@ int vfs_close_dir(Handle& hd) return h_free(hd, H_VDir); } -// make sure we can assign directly from TDirent to vfsDirEnt -// (more efficient) -cassert(offsetof(vfsDirEnt, name) == offsetof(TDirent, name)); -cassert(offsetof(vfsDirEnt, size) == offsetof(TDirent, size)); -cassert(offsetof(vfsDirEnt, mtime) == offsetof(TDirent, mtime)); // retrieve the next dir entry (in alphabetical order) matching . // return 0 on success, ERR_DIR_END if no matching entry was found, // or a negative error code on failure. // filter values: -// - 0: any file; -// - "/": any subdirectory -// - anything else: pattern for name (may include '?' and '*' wildcards) +// - 0: anything; +// - "/": any subdirectory; +// - "/|": any subdirectory, or as below with ; +// - : any file whose name matches; ? and * wildcards are allowed. // -// xxx rationale: the filename is currently stored internally as -// std::string (=> less manual memory allocation). we don't want to -// return a reference, because that would break C compatibility. -// we're trying to avoid fixed-size buffers, so that is out as well. -// finally, allocating a copy is not so good because it has to be -// freed by the user (won't happen). returning a volatile pointer -// to the string itself via c_str is the only remaining option. +// note that the directory entries are only scanned once; after the +// end is reached (-> ERR_DIR_END returned), no further entries can +// be retrieved, even if filter changes (which shouldn't happen - see impl). +// +// rationale for returning a pointer to the name string: we're trying to +// avoid arbitrary name length limits, so fixed-size buffers are out. +// allocating a copy isn't good because it has to be freed by the user +// (won't happen). that leaves a (const!) pointer to the internal storage. int vfs_next_dirent(const Handle hd, vfsDirEnt* ent, const char* filter) { H_DEREF(hd, VDir, vd); - return tree_next_dirent(vd->latch, filter, (TDirent*)ent); + + // warn if scanning the directory twice with different filters + // (this used to work with dir/file because they were stored separately). + // it is imaginable that someone will want to change it, but until + // there's a good reason, leave this check in. note: only comparing + // pointers isn't 100% certain, but it's safe enough and easy. +#ifndef NDEBUG + if(!vd->filter_latched) + { + vd->filter = filter; + vd->filter_latched = true; + } + if(vd->filter != filter) + debug_warn("vfs_next_dirent: filter has changed for this directory. are you scanning it twice?"); +#endif + + return tree_next_dirent(vd->latch, filter, ent); } diff --git a/source/lib/res/vfs.h b/source/lib/res/vfs.h index a832ca9b7c..e2cb3ddc81 100755 --- a/source/lib/res/vfs.h +++ b/source/lib/res/vfs.h @@ -118,8 +118,18 @@ extern int vfs_close_dir(Handle& hd); // or a negative error code on failure. // filter values: // - 0: anything; -// - "/": any subdirectory -// - anything else: pattern for name (may include '?' and '*' wildcards) +// - "/": any subdirectory; +// - "/|": any subdirectory, or as below with ; +// - : any file whose name matches; ? and * wildcards are allowed. +// +// note that the directory entries are only scanned once; after the +// end is reached (-> ERR_DIR_END returned), no further entries can +// be retrieved, even if filter changes (which shouldn't happen - see impl). +// +// rationale for returning a pointer to the name string: we're trying to +// avoid arbitrary name length limits, so fixed-size buffers are out. +// allocating a copy isn't good because it has to be freed by the user +// (won't happen). that leaves a (const!) pointer to the internal storage. extern int vfs_next_dirent(Handle hd, vfsDirEnt* ent, const char* filter = 0); diff --git a/source/lib/res/vfs_path.cpp b/source/lib/res/vfs_path.cpp index cbe0cf12d8..1fd928d150 100644 --- a/source/lib/res/vfs_path.cpp +++ b/source/lib/res/vfs_path.cpp @@ -92,6 +92,32 @@ ok: #define CHECK_PATH(path) CHECK_ERR(path_validate(__LINE__, path)) +bool path_component_valid(const char* name) +{ + // disallow empty strings + if(*name == '\0') + return false; + + int c = 0; + for(;;) + { + int last_c = c; + c = *name++; + + // disallow '..' (would allow escaping the VFS root dir sandbox) + if(c == '.' && last_c == '.') + return false; + + // disallow dir separators + if(c == '\\' || c == ':' || c == '/') + return false; + + if(c == '\0') + return true; + } +} + + // convenience function inline void path_copy(char* dst, const char* src) { @@ -129,6 +155,7 @@ int path_append(char* dst, const char* path1, const char* path2) return 0; } + // strip from the start of , prepend , // and write to . // used when converting VFS <--> real paths. diff --git a/source/lib/res/vfs_path.h b/source/lib/res/vfs_path.h index b574a58d2a..d66f3e5de1 100644 --- a/source/lib/res/vfs_path.h +++ b/source/lib/res/vfs_path.h @@ -10,6 +10,8 @@ extern int path_validate(const uint line, const char* path); #define CHECK_PATH(path) CHECK_ERR(path_validate(__LINE__, path)) +extern bool path_component_valid(const char* name); + // convenience function extern void path_copy(char* dst, const char* src); diff --git a/source/lib/res/vfs_tree.cpp b/source/lib/res/vfs_tree.cpp index e4bc23b1a9..7c6272adc1 100644 --- a/source/lib/res/vfs_tree.cpp +++ b/source/lib/res/vfs_tree.cpp @@ -54,7 +54,10 @@ public: TNode* add(const char* fn); TNode* find(const char* fn); - size_t size() { return num_entries; } + size_t size() + { + return num_entries; + } class iterator; iterator begin() const; @@ -88,7 +91,7 @@ public: void init(); TNode* find(const char* fn, TNodeType desired_type); TNode* add(const char* fn, TNodeType new_type); - TDir* add_dir(const char* path, const TMountPoint* mount_point_); + int mount(const char* path, const TMountPoint*, bool watch); int lookup(const char* path, uint flags, TNode** pnode, char* exact_path); void clearR(); void displayR(int indent_level); @@ -187,42 +190,70 @@ void node_free_all() class TChildren::iterator { public: - typedef std::random_access_iterator_tag iterator_category; + typedef std::forward_iterator_tag iterator_category; typedef TNode* T; typedef T value_type; typedef ptrdiff_t difference_type; - typedef const TNode** pointer; - typedef const TNode*& reference; + typedef const T* pointer; + typedef const T& reference; iterator() - {} - iterator(T* pos_) : pos(pos_) - {} - T& operator[](int idx) const - { return pos[idx]; } + { + } + iterator(T* pos_, T* end_) : pos(pos_), end(end_) + { + } T& operator*() const - { return *pos; } - const T* operator->() const - { return &**this; } + { + return *pos; + } iterator& operator++() // pre - { ++pos; return (*this); } - iterator operator++(int) // post - { iterator tmp = *this; ++*this; return tmp; } + { + do + pos++; + while(pos != end && *pos == 0); + return (*this); + } bool operator==(const iterator& rhs) const - { return pos == rhs.pos; } - bool operator!=(const iterator& rhs) const - { return !(*this == rhs); } + { + return pos == rhs.pos; + } bool operator<(const iterator& rhs) const - { return (pos < rhs.pos); } + { + return (pos < rhs.pos); + } + + // derived + const T* operator->() const + { + return &**this; + } + bool operator!=(const iterator& rhs) const + { + return !(*this == rhs); + } + iterator operator++(int) // post + { + iterator tmp = *this; ++*this; return tmp; + } protected: T* pos; + T* end; + // only used when incrementing (avoid going beyond end of table) }; TChildren::iterator TChildren::begin() const -{ return iterator(tbl); } +{ + TNode** pos = tbl; + while(pos != tbl+max_entries && *pos == 0) + pos++; + return iterator(pos, tbl+max_entries); +} TChildren::iterator TChildren::end() const -{ return iterator(tbl+max_entries); } +{ + return iterator(tbl+max_entries, 0); +} void TChildren::init() @@ -236,7 +267,6 @@ void TChildren::init() void TChildren::clear() { -memset(tbl, 0, max_entries*sizeof(TNode*)); free(tbl); tbl = 0; num_entries = max_entries = 0; @@ -353,9 +383,12 @@ TNode* TDir::find(const char* fn, TNodeType desired_type) } -TNode* TDir::add(const char* fn, TNodeType new_type) +TNode* TDir::add(const char* name, TNodeType new_type) { - TNode* node = children.add(fn); + if(!path_component_valid(name)) + return 0; + + TNode* node = children.add(name); if(!node) return 0; // already initialized @@ -376,30 +409,21 @@ TNode* TDir::add(const char* fn, TNodeType new_type) // note: full VFS path is needed for the dir watch. -TDir* TDir::add_dir(const char* path, const TMountPoint* mp) +int TDir::mount(const char* path, const TMountPoint* mp, bool watch) { - const char* subdir_name = strrchr(path, '/'); - if(!subdir_name++) - return 0; - - TNode* node = add(subdir_name, N_DIR); - if(!node) - return 0; - TDir* subdir = &node->u.dir; - // more than one real dir mounted into VFS dir // (=> can't create files for writing here) - if(subdir->mount_point) - subdir->mount_point = (TMountPoint*)-1; + if(mount_point) + mount_point = (TMountPoint*)-1; else - subdir->mount_point = mp; + mount_point = mp; #ifndef NO_DIR_WATCH - int ret = res_watch_dir(path, &subdir->watch); - assert(ret == 0); + if(watch) + CHECK_ERR(res_watch_dir(path, &this->watch)); #endif - return subdir; + return 0; } @@ -500,7 +524,7 @@ void TDir::clearR() for(TChildIt it = children.begin(); it != children.end(); ++it) { TNode* node = *it; - if(node && node->type == N_DIR) + if(node->type == N_DIR) node->u.dir.clearR(); } @@ -602,9 +626,17 @@ TFile* tree_add_file(TDir* dir, const char* name) } -TDir* tree_add_dir(TDir* dir, const char* path, const TMountPoint* mount_point) +TDir* tree_add_dir(TDir* dir, const char* name) { - return dir->add_dir(path, mount_point); + TNode* node = dir->add(name, N_DIR); + return node? &node->u.dir: 0; +} + + + +int tree_mount(TDir* dir, const char* path, const TMountPoint* mount_point, bool watch) +{ + return dir->mount(path, mount_point, watch); } @@ -648,7 +680,7 @@ struct NodeLatch static bool ci_less(const TNode* n1, const TNode* n2) { - return stricmp(n1->exact_name, n2->exact_name) <= 0; + return stricmp(n1->exact_name, n2->exact_name) < 0; } NodeLatch(TChildren& c) @@ -656,17 +688,14 @@ struct NodeLatch i = 0; v.reserve(c.size()); - // don't std::copy because c contains a lot of NULL entries - // (which we must not return); this way is easier than having the - // iterator strip them. - for(TChildIt it = c.begin(); it != c.end(); ++it) - if(*it) - v.push_back(*it); - + std::copy(c.begin(), c.end(), std::back_inserter(v)); std::sort(v.begin(), v.end(), ci_less); } - bool empty() const { return i == v.size(); } + bool empty() const + { + return i == v.size(); + } const TNode* get_next() { @@ -686,7 +715,7 @@ int tree_open_dir(const char* path_slash, void** latch) } -int tree_next_dirent(void* latch_, const char* filter, TDirent* dirent) +int tree_next_dirent(void* latch_, const char* filter, vfsDirEnt* dirent) { bool want_dir = true; if(filter) diff --git a/source/lib/res/vfs_tree.h b/source/lib/res/vfs_tree.h index 1958091b89..4ff1222736 100644 --- a/source/lib/res/vfs_tree.h +++ b/source/lib/res/vfs_tree.h @@ -1,3 +1,26 @@ +// virtual file system: tree of files and directories +// +// Copyright (c) 2005 Jan Wassenberg +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// Contact info: +// Jan.Wassenberg@stud.uni-karlsruhe.de +// http://www.stud.uni-karlsruhe.de/~urkt/ + +#ifndef VFS_TREE_H__ +#define VFS_TREE_H__ + +#include "vfs.h" // vfsDirEnt + struct TMountPoint; class TDir; @@ -26,13 +49,6 @@ struct TFile } }; -// keep in sync with vfs.h vfsDirEnt! -struct TDirent -{ - const char* name; - off_t size; - time_t mtime; -}; enum TreeLookupFlags { @@ -47,7 +63,9 @@ extern void tree_clear(); extern void tree_display(); extern TFile* tree_add_file(TDir* dir, const char* name); -extern TDir* tree_add_dir(TDir* parent, const char* path, const TMountPoint*); +extern TDir * tree_add_dir (TDir* dir, const char* name); + +extern int tree_mount(TDir* dir, const char* path, const TMountPoint*, bool watch = true); // starting at VFS root, traverse and pass back information @@ -83,5 +101,7 @@ extern int tree_lookup_dir(const char* path, TDir** pdir, uint flags = 0, char* extern int tree_lookup(const char* path, TFile** pfile, uint flags = 0, char* exact_path = 0); extern int tree_open_dir(const char* path_slash, void** latch); -extern int tree_next_dirent(void* latch_, const char* filter, TDirent* dirent); +extern int tree_next_dirent(void* latch_, const char* filter, vfsDirEnt* dirent); extern int tree_close_dir(void* latch_); + +#endif // #ifndef VFS_TREE_H__ diff --git a/source/ps/VFSUtil.cpp b/source/ps/VFSUtil.cpp index 424a5a0c34..1229f37ae0 100755 --- a/source/ps/VFSUtil.cpp +++ b/source/ps/VFSUtil.cpp @@ -41,15 +41,16 @@ bool VFSUtil::FindFiles (CStr& dirname, const char* filter, FileList& files) } -// call for each file in the directory; -// if , files in subdirectories are also returned. +// call for each entry matching (see vfs_next_dirent) in the +// directory; if , entries in subdirectories are +// also returned. // -// note: EnumFileCB path and ent are only valid during the callback. +// note: EnumDirEntsCB path and ent are only valid during the callback. int VFSUtil::EnumDirEnts(const CStr start_path, const char* user_filter, bool recursive, EnumDirEntsCB cb, void* context) { // note: currently no need to return subdirectories, - // but enabling it isn't hard. + // but enabling it isn't hard (we have to check for / anyway). char filter_buf[VFS_MAX_PATH]; const char* filter = user_filter;