- fix bug (wasn't setting mount_point of dir being mounted => crash while writing screenshot)

- add check for changing filters while enumerating dirents
- add path_component_valid
- change iterator to only return valid entries (removing that burden
from user code)

This was SVN commit r2067.
This commit is contained in:
janwas 2005-03-27 17:27:49 +00:00
parent f777979cc8
commit 2fa430a4e6
7 changed files with 212 additions and 100 deletions

View File

@ -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 <filter>.
// 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;
// - "/|<pattern>": any subdirectory, or as below with <pattern>;
// - <pattern>: 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);
}

View File

@ -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;
// - "/|<pattern>": any subdirectory, or as below with <pattern>;
// - <pattern>: 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);

View File

@ -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 <remove> from the start of <src>, prepend <replace>,
// and write to <dst>.
// used when converting VFS <--> real paths.

View File

@ -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);

View File

@ -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)

View File

@ -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 <path> 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__

View File

@ -41,15 +41,16 @@ bool VFSUtil::FindFiles (CStr& dirname, const char* filter, FileList& files)
}
// call <cb> for each file in the <start_path> directory;
// if <recursive>, files in subdirectories are also returned.
// call <cb> for each entry matching <filter> (see vfs_next_dirent) in the
// <start_path> directory; if <recursive>, 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;