1
0
forked from 0ad/0ad
0ad/source/lib/res/file/vfs_mount.cpp
janwas 8435fb880c vfs_mount: bugfix: wasn't populating VFS from archive (cause of massive VFS slowdown recently). also fixed another overzealous validate check
ogl_tex: bind() implementation much simplified; also fix bug that set
TMU correctly when disabling texturing
wsdl: fix stdout.txt redirect; now works regardless of current
directory. also no longer disable file buffering except when in full
debug mode (because it's so slow). also add minimize button to caption
bar in windowed mode.

This was SVN commit r2919.
2005-10-12 23:48:44 +00:00

1134 lines
30 KiB
C++

#include "precompiled.h"
#include "../res.h"
#include "vfs_mount.h"
#include "vfs_path.h"
#include "vfs_tree.h"
#include "zip.h"
#include "sysdep/dir_watch.h"
#include <deque>
#include <list>
struct Stats
{
size_t mounted_dirs;
size_t mounted_archives;
size_t files_examined;
size_t files_updated;
void dump()
{
debug_printf("VFS stats\n");
debug_printf(" dirs=%u archives=%u\n", mounted_dirs, mounted_archives);
debug_printf(" files updated=%u files examined=%u\n", files_updated, files_examined);
}
};
static Stats stats;
void vfs_dump_stats()
{
stats.dump();
}
// location of a file: either archive or a real directory.
// not many instances => don't worry about efficiency.
struct Mount
{
// mounting into this VFS directory;
// must end in '/' (unless if root td, i.e. "")
std::string V_mount_point;
// real directory being mounted.
// if this Mount represents an archive, this is the real directory
// containing the Zip file (required so that this Mount is unmounted).
std::string P_name;
Handle archive;
uint pri;
// see enum VfsMountFlags
uint flags;
MountType type;
Mount(const char* V_mount_point_, const char* P_name_, Handle archive_, uint flags_, uint pri_)
: V_mount_point(V_mount_point_), P_name(P_name_)
{
archive = archive_;
flags = flags_;
pri = pri_;
if(archive > 0)
{
h_add_ref(archive);
type = MT_ARCHIVE;
}
else
type = MT_FILE;
}
~Mount()
{
if(archive > 0) // avoid h_mgr warning
zip_archive_close(archive);
}
Mount& operator=(const Mount& rhs)
{
V_mount_point = rhs.V_mount_point;
P_name = rhs.P_name;
archive = rhs.archive;
pri = rhs.pri;
flags = rhs.flags;
type = rhs.type;
if(archive > 0) // avoid h_mgr warning
h_add_ref(archive);
return *this;
}
struct equal_to : public std::binary_function<Mount, const char*, bool>
{
bool operator()(const Mount& m, const char* P_name) const
{
return (m.P_name == P_name);
}
};
private:
Mount();
};
char mount_get_type(const Mount* m)
{
switch(m->type)
{
case MT_ARCHIVE:
return 'A';
case MT_FILE:
return 'F';
default:
return '?';
}
}
bool mount_should_replace(const Mount* m_old, const Mount* m_new, bool files_are_identical)
{
// need to "replace" if not yet associated with a Mount
if(!m_old)
return true;
// keep old if new priority is lower.
if(m_new->pri < m_old->pri)
return false;
// since priority is not less, we really ought to always go with m_new.
// however, there is one special case we handle for performance reasons:
// if the file contents are the same, prefer the more efficient source.
// note that priority doesn't automatically take care of this,
// especially if set incorrectly.
if(files_are_identical && m_old->type > m_new->type)
return false;
return true;
}
///////////////////////////////////////////////////////////////////////////////
//
// populate the directory being mounted with files from real subdirectories
// and archives.
//
///////////////////////////////////////////////////////////////////////////////
static const Mount& add_mount(const char* V_mount_point, const char* P_real_path, Handle archive,
uint flags, uint pri);
// passed through dirent_cb's zip_enum to zip_cb
struct ZipCBParams
{
// tree directory into which we are adding the archive's files
TDir* const td;
// archive's location; assigned to all files added from here
const Mount* const m;
// storage for directory lookup optimization (see below).
// held across one zip_enum's zip_cb calls.
char last_path[VFS_MAX_PATH];
size_t last_path_len;
TDir* last_td;
ZipCBParams(TDir* dir_, const Mount* loc_)
: td(dir_), m(loc_)
{
last_path[0] = '\0';
last_path_len = 0;
last_td = 0;
}
// no copy ctor because some members are const
private:
ZipCBParams& operator=(const ZipCBParams&);
};
// called by add_ent's zip_enum for each file in the archive.
// we get the full path, since that's what is stored in Zip archives.
//
// [total time 21ms, with ~2000 file's (includes add_file cost)]
static int zip_cb(const char* path, const struct stat* s, uintptr_t user)
{
CHECK_PATH(path);
ZipCBParams* params = (ZipCBParams*)user;
TDir* td = params->td;
const Mount* m = params->m;
char* last_path = params->last_path;
size_t& last_path_len = params->last_path_len;
TDir*& last_td = params->last_td;
// extract file name (needed for add_file)
const char* fn = path;
const char* slash = strrchr(path, '/');
if(slash)
fn = slash+1;
// else: there is no path - it's in the archive's root td.
// into which directory should the file be inserted?
// naive approach: tree_lookup_dir the path (slow!)
// optimization: store the last file's path; if it's the same,
// use the directory we looked up last time (much faster!)
const size_t path_len = fn-path;
// .. same as last time
if(last_td && path_len == last_path_len &&
strnicmp(path, last_path, path_len) == 0)
td = last_td;
// .. last != current: need to do lookup
else
{
vfs_path_copy(last_path, path);
last_path_len = path_len;
last_path[last_path_len] = '\0';
// strip filename (tree_lookup_dir requirement)
CHECK_ERR(tree_lookup_dir(last_path, &td, LF_CREATE_MISSING|LF_START_DIR));
// we have to create them if missing, since we can't rely on the
// archiver placing directories before subdirs or files that
// reference them (WinZip doesn't always).
// we also need to start at the mount point (td).
last_td = td;
}
return tree_add_file(td, fn, m, s->st_size, s->st_mtime);
}
static bool archive_less(Handle hza1, Handle hza2)
{
const char* fn1 = h_filename(hza1);
const char* fn2 = h_filename(hza2);
return strcmp(fn1, fn2) < 0;
}
typedef std::vector<Handle> Archives;
typedef Archives::const_iterator ArchiveCIt;
static int enqueue_archive(const char* name, const char* P_archive_dir, Archives* archives)
{
// caller wants us to check if this is a Zip file and if so, append it to
// a list. this is only done in the mounted directory itself, not its
// subdirectories! see mount_dir_tree.
// the archives will be mounted after regular directory mounts are done.
if(archives)
{
// get complete path for zip_archive_open.
// this doesn't (need to) work for subdirectories of the mounted td!
// we can't use mount_get_path because we don't have the VFS path.
char P_path[PATH_MAX];
vfs_path_append(P_path, P_archive_dir, name);
// just open the Zip file and see if it's valid. we don't bother
// checking the extension because archives won't necessarily be
// called .zip (e.g. Quake III .pk3).
Handle archive = zip_archive_open(P_path);
if(archive > 0)
{
archives->push_back(archive);
// avoid also adding the Zip file itself to <td>.
return 0;
}
}
return 1;
}
static int mount_archive(TDir* td, const Mount& m)
{
stats.mounted_archives++;
ZipCBParams params(td, &m);
zip_enum(m.archive, zip_cb, (uintptr_t)&params);
return 0;
}
static int mount_archives(TDir* td, Archives* archives, const Mount* mount)
{
// VFS_MOUNT_ARCHIVES flag wasn't set, or no archives present
if(archives->empty())
return 0;
std::sort(archives->begin(), archives->end(), archive_less);
for(ArchiveCIt it = archives->begin(); it != archives->end(); ++it)
{
Handle hza = *it;
// add this archive to the mount list (address is guaranteed to
// remain valid).
const Mount& m = add_mount(mount->V_mount_point.c_str(), mount->P_name.c_str(), hza, 0, 0);
mount_archive(td, m);
}
return 0;
}
//-----------------------------------------------------------------------------
struct TDirAndPath
{
TDir* const td;
const std::string path;
TDirAndPath(TDir* d, const char* p)
: td(d), path(p)
{
}
// no copy ctor because some members are const
private:
TDirAndPath& operator=(const TDirAndPath&);
};
typedef std::deque<TDirAndPath> DirQueue;
static int enqueue_dir(TDir* parent_td, const char* name,
const char* P_parent_path, DirQueue* dir_queue)
{
// caller doesn't want us to enqueue subdirectories; bail.
if(!dir_queue)
return 0;
// skip versioning system directories - this avoids cluttering the
// VFS with hundreds of irrelevant files.
// we don't do this for Zip files because it's harder (we'd have to
// strstr the entire path) and it is assumed the Zip file builder
// will take care of it.
if(!strcmp(name, "CVS") || !strcmp(name, ".svn"))
return 0;
// prepend parent path to get complete pathname.
char P_path[PATH_MAX];
CHECK_ERR(vfs_path_append(P_path, P_parent_path, name));
// create subdirectory..
TDir* td;
CHECK_ERR(tree_add_dir(parent_td, name, &td));
// .. and add it to the list of directories to visit.
dir_queue->push_back(TDirAndPath(td, const_cast<const char*>(P_path)));
return 0;
}
// called by TDir::addR's file_enum for each entry in a real directory.
//
// if called for a real directory, it is added to VFS.
// else if called for a loose file that is a valid archive (*),
// it is mounted (all of its files are added)
// else the file is added to VFS.
//
// * we only perform this check in the directory being mounted,
// i.e. passed in by tree_add_dir. to determine if a file is an archive,
// we have to open it and read the header, which is slow.
// can't just check extension, because it might not be .zip (e.g. Quake3 .pk3).
//
// td - tree td into which the dirent is to be added
// m - real td's location; assigned to all files added from this mounting
// archives - if the dirent is an archive, its Mount is added here.
static int add_ent(TDir* td, DirEnt* ent, const char* P_parent_path, const Mount* m,
DirQueue* dir_queue, Archives* archives)
{
const char* name = ent->name;
// it's a directory entry.
if(DIRENT_IS_DIR(ent))
return enqueue_dir(td, name, P_parent_path, dir_queue);
// else: it's a file (dir_next_ent discards everything except for
// file and subdirectory entries).
if(enqueue_archive(name, m->P_name.c_str(), archives) == 0)
return 0;
// it's a regular data file; add it to the directory.
return tree_add_file(td, name, m, ent->size, ent->mtime);
}
// note: full path is needed for the dir watch.
static int populate_dir(TDir* td, const char* P_path, const Mount* m,
DirQueue* dir_queue, Archives* archives, int flags)
{
int err;
RealDir* rd = tree_get_real_dir(td);
WARN_ERR(mount_attach_real_dir(rd, P_path, m, flags));
DirIterator d;
RETURN_ERR(dir_open(P_path, &d));
DirEnt ent;
for(;;)
{
// don't RETURN_ERR since we need to close d.
err = dir_next_ent(&d, &ent);
if(err != 0)
break;
err = add_ent(td, &ent, P_path, m, dir_queue, archives);
WARN_ERR(err);
}
WARN_ERR(dir_close(&d));
return 0;
}
// actually mount the specified entry. split out of vfs_mount,
// because when invalidating (reloading) the VFS, we need to
// be able to mount without changing the mount list.
// add all loose files and subdirectories (recursive).
// also mounts all archives in P_real_path and adds to archives.
// add the contents of directory <p_path> to this TDir,
// marking the files' locations as <m>. flags: see VfsMountFlags.
//
// note: we are only able to add archives found in the root directory,
// due to dirent_cb implementation. that's ok - we don't want to check
// every single file to see if it's an archive (slow!).
static int mount_dir_tree(TDir* td, const Mount& m)
{
int err = 0;
// add_ent fills these queues with dirs/archives if the corresponding
// flags are set.
DirQueue dir_queue; // don't preallocate (not supported by TDirAndPath)
Archives archives;
archives.reserve(8); // preallocate for efficiency.
// instead of propagating flags down to add_dir, prevent recursing
// and adding archives by setting the destination pointers to 0 (easier).
DirQueue* const pdir_queue = (m.flags & VFS_MOUNT_RECURSIVE)? &dir_queue : 0;
Archives* parchives = (m.flags & VFS_MOUNT_ARCHIVES)? &archives : 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(TDirAndPath(td, m.P_name.c_str()));
do
{
TDir* const td = dir_queue.front().td;
const char* P_path = dir_queue.front().path.c_str();
int ret = populate_dir(td, P_path, &m, pdir_queue, parchives, m.flags);
if(!err)
err = ret;
// prevent searching for archives in subdirectories (slow!). this
// is currently required by the implementation anyway.
parchives = 0;
dir_queue.pop_front();
// pop at end of loop, because we hold a c_str() reference.
}
while(!dir_queue.empty());
// do not pass parchives because that has been set to 0!
mount_archives(td, &archives, &m);
return 0;
}
// the VFS stores the location (archive or directory) of each file;
// this allows multiple search paths without having to check each one
// when opening a file (slow).
//
// one Mount is allocated for each archive or directory mounted.
// therefore, files only /point/ to a (possibly shared) Mount.
// if a file's location changes (e.g. after mounting a higher-priority
// directory), the VFS entry will point to the new Mount; the priority
// of both locations is unchanged.
//
// allocate via mnt_create, passing the location. do not free!
// we keep track of all Locs allocated; they are freed at exit,
// and by mnt_free_all (useful when rebuilding the VFS).
// this is much easier and safer than walking the VFS tree and
// freeing every location we find.
///////////////////////////////////////////////////////////////////////////////
//
// mount list (allows multiple mountings, e.g. for mods)
//
///////////////////////////////////////////////////////////////////////////////
// every mounting results in at least one Mount (and possibly more, e.g.
// if the directory contains Zip archives, which each get a Mount).
//
// requirements for container:
// - must not invalidate iterators after insertion!
// (TFile holds a pointer to the Mount from which it was added)
// - must store items in order of insertion
// xxx
typedef std::list<Mount> Mounts;
typedef Mounts::iterator MountIt;
static Mounts mounts;
static const Mount& add_mount(const char* V_mount_point, const char* P_real_path, Handle hza,
uint flags, uint pri)
{
mounts.push_back(Mount(V_mount_point, P_real_path, hza, flags, pri));
return mounts.back();
}
// note: this is not a member function of Mount to avoid having to
// forward-declare mount_archive, mount_dir_tree.
static int remount(const Mount& m)
{
TDir* td;
CHECK_ERR(tree_lookup_dir(m.V_mount_point.c_str(), &td, LF_CREATE_MISSING));
switch(m.type)
{
case MT_ARCHIVE:
return mount_archive(td, m);
case MT_FILE:
return mount_dir_tree(td, m);
default:
debug_warn("remount: invalid type");
return ERR_CORRUPTED;
}
}
static inline void unmount_all(void)
{
mounts.clear();
}
static inline void remount_all()
{
std::for_each(mounts.begin(), mounts.end(), remount);
}
// mount <P_real_path> into the VFS at <V_mount_point>,
// which is created if it does not yet exist.
// files in that directory override the previous VFS contents if
// <pri>(ority) is not lower.
// all archives in <P_real_path> are also mounted, in alphabetical order.
//
// flags determines extra actions to perform; see VfsMountFlags.
//
// P_real_path = "." or "./" isn't allowed - see implementation for rationale.
int vfs_mount(const char* V_mount_point, const char* P_real_path, int flags, uint pri)
{
// callers have a tendency to forget required trailing '/';
// complain if it's not there, unless path = "" (root td).
#ifndef NDEBUG
const size_t len = strlen(V_mount_point);
if(len && V_mount_point[len-1] != '/')
debug_warn("vfs_mount: path doesn't end in '/'");
#endif
// make sure it's not already mounted, i.e. in mounts.
// also prevents mounting a parent directory of a previously mounted
// directory, or vice versa. example: mount $install/data and then
// $install/data/mods/official - mods/official would also be accessible
// from the first mount point - bad.
// no matter if it's an archive - still shouldn't be a "subpath".
for(MountIt it = mounts.begin(); it != mounts.end(); ++it)
{
if(file_is_subpath(P_real_path, it->P_name.c_str()))
{
debug_warn("vfs_mount: already mounted");
return -1;
}
}
// disallow "." because "./" isn't supported on Windows.
// it would also create a loophole for the parent td check above.
// "./" and "/." are caught by CHECK_PATH.
if(!strcmp(P_real_path, "."))
{
debug_warn("vfs_mount: mounting . not allowed");
return -1;
}
const Mount& m = add_mount(V_mount_point, P_real_path, 0, flags, pri);
return remount(m);
}
// rebuild the VFS, i.e. re-mount everything. open files are not affected.
// necessary after loose files or directories change, so that the VFS
// "notices" the changes and updates file locations. res calls this after
// dir_watch reports changes; can also be called from the console after a
// rebuild command. there is no provision for updating single VFS dirs -
// it's not worth the trouble.
static int rebuild()
{
tree_clear();
tree_init();
remount_all();
return 0;
}
// unmount a previously mounted item, and rebuild the VFS afterwards.
int vfs_unmount(const char* P_name)
{
// this removes all Mounts ensuing from the given mounting. their dtors
// free all resources and there's no need to remove the files from
// VFS (nor is this feasible), since it is completely rebuilt afterwards.
MountIt begin = mounts.begin(), end = mounts.end();
MountIt last = std::remove_if(begin, end,
std::bind2nd(Mount::equal_to(), P_name));
// none were removed - need to complain so that the caller notices.
if(last == end)
return ERR_PATH_NOT_FOUND;
// trim list and actually remove 'invalidated' entries.
mounts.erase(last, end);
return rebuild();
}
// if <path> or its ancestors are mounted,
// return a VFS path that accesses it.
// used when receiving paths from external code.
static int make_vfs_path(const char* P_path, char* V_path)
{
for(MountIt it = mounts.begin(); it != mounts.end(); ++it)
{
const Mount& m = *it;
if(m.type != MT_FILE)
continue;
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)
return 0;
}
return -1;
}
void mount_init()
{
tree_init();
}
void mount_shutdown()
{
tree_clear();
unmount_all();
}
int mount_attach_real_dir(RealDir* rd, const char* P_path, const Mount* m, int 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;
else
rd->m = m;
#ifndef NO_DIR_WATCH
if(flags & VFS_MOUNT_WATCH)
{
char N_path[PATH_MAX];
CHECK_ERR(file_make_full_native_path(P_path, N_path));
CHECK_ERR(dir_add_watch(N_path, &rd->watch));
}
#endif
return 0;
}
void mount_detach_real_dir(RealDir* rd)
{
rd->m = 0;
#ifndef NO_DIR_WATCH
if(rd->watch) // avoid dir_cancel_watch complaining
WARN_ERR(dir_cancel_watch(rd->watch));
rd->watch = 0;
#endif
}
int mount_populate(TDir* td, RealDir* rd)
{
UNUSED2(td);
UNUSED2(rd);
return 0;
}
//-----------------------------------------------------------------------------
// hotloading
//-----------------------------------------------------------------------------
// called by vfs_reload and vfs_reload_changed_files (which will already
// have rebuilt the VFS - doing so more than once a frame is unnecessary).
static int reload_without_rebuild(const char* fn)
{
// invalidate this file's cached blocks to make sure its contents are
// loaded anew.
CHECK_ERR(file_invalidate_cache(fn));
CHECK_ERR(h_reload(fn));
return 0;
}
// called via console command.
int vfs_reload(const char* fn)
{
// if <fn> currently maps to an archive, the VFS must switch
// over to using the loose file (that was presumably changed).
CHECK_ERR(rebuild());
return reload_without_rebuild(fn);
}
// get directory change notifications, and reload all affected files.
// must be called regularly (e.g. once a frame). this is much simpler
// than asynchronous notifications: everything would need to be thread-safe.
int vfs_reload_changed_files()
{
// array of reloads requested this frame (see 'do we really need to
// reload' below). go through gyrations to avoid heap allocs.
const size_t MAX_RELOADS_PER_FRAME = 12;
typedef char Path[VFS_MAX_PATH];
typedef Path PathList[MAX_RELOADS_PER_FRAME];
PathList pending_reloads;
uint num_pending = 0;
// process only as many notifications as we have room for; the others
// will be handled next frame. it's not imagineable that they'll pile up.
while(num_pending < MAX_RELOADS_PER_FRAME)
{
// get next notification
char N_path[PATH_MAX];
int ret = dir_get_changed_file(N_path);
CHECK_ERR(ret); // error? (doesn't cover 'none available')
if(ret != 0) // none available; done.
break;
// convert to VFS path
char P_path[PATH_MAX];
CHECK_ERR(file_make_full_portable_path(N_path, P_path));
char* V_path = pending_reloads[num_pending];
CHECK_ERR(make_vfs_path(P_path, V_path));
// do we really need to reload? try to avoid the considerable cost of
// rebuilding VFS and scanning all Handles.
//
// note: be careful to avoid 'race conditions' depending on the
// timeframe in which notifications reach us.
// example: editor deletes a.tga; we are notified; reload is
// triggered but fails since the file isn't found; further
// notifications (e.g. renamed a.tmp to a.tga) come within x [ms] and
// are ignored due to a time limit.
// therefore, we can only check for multiple reload requests a frame;
// to that purpose, an array is built and duplicates ignored.
const char* ext = strrchr(V_path, '.');
// .. directory change notification; ignore because we get
// per-file notifications anyway. (note: assume no extension =>
// it's a directory). this also protects the strcmp calls below.
if(!ext)
continue;
// .. compiled XML files the engine writes out by the hundreds;
// skipping them is a big performance gain.
if(!stricmp(ext, ".xmb"))
continue;
// .. temp files, usually created when an editor saves a file
// (delete, create temp, rename temp); no need to reload those.
if(!stricmp(ext, ".tmp"))
continue;
// .. more than one notification for a file; only reload once.
// note: this doesn't suffer from the 'reloaded too early'
// problem described above; if there's more than one
// request in the array, the file has since been written.
for(uint i = 0; i < num_pending; i++)
if(!strcmp(pending_reloads[i], V_path))
continue;
// path has already been written to pending_reloads,
// so just mark it valid.
num_pending++;
}
// rebuild VFS, in case a file that has been changed is currently
// mounted from an archive (reloading would just grab the unchanged
// version in the archive). the rebuild sees differing mtimes and
// always choses the loose file version. only do this once
// (instead of per reload request) because it's slow (> 1s)!
if(num_pending != 0)
CHECK_ERR(rebuild());
// now actually reload all files in the array we built
for(uint i = 0; i < num_pending; i++)
CHECK_ERR(reload_without_rebuild(pending_reloads[i]));
return 0;
}
//-----------------------------------------------------------------------------
// rationale for not using virtual functions for file_open vs zip_open:
// it would spread out the implementation of each function and makes
// keeping them in sync harder. we will very rarely add new sources and
// all these functions are in one spot anyway.
// given a Mount, return the actual location (portable path) of
// <V_path>. used by vfs_realpath and VFile_reopen.
int x_realpath(const Mount* m, const char* V_exact_path, char* P_real_path)
{
const char* P_parent_path = 0;
switch(m->type)
{
case MT_ARCHIVE:
P_parent_path = h_filename(m->archive);
break;
case MT_FILE:
P_parent_path = m->P_name.c_str();
break;
default:
debug_warn("x_realpath: invalid type");
return -1;
}
const char* remove = m->V_mount_point.c_str();
const char* replace = P_parent_path;
return path_replace(P_real_path, V_exact_path, remove, replace);
}
int x_open(const Mount* m, const char* V_exact_path, int flags, TFile* tf, XFile* xf)
{
// declare variables used in the switch below to avoid needing {}.
char P_path[PATH_MAX];
switch(m->type)
{
case MT_ARCHIVE:
if(flags & FILE_WRITE)
{
debug_warn("requesting write access to file in archive");
return -1;
}
RETURN_ERR(zip_open(m->archive, V_exact_path, flags, &xf->u.zf));
break;
case MT_FILE:
CHECK_ERR(x_realpath(m, V_exact_path, P_path));
RETURN_ERR(file_open(P_path, flags, &xf->u.f));
break;
default:
debug_warn("VFile_reload: invalid type");
return ERR_CORRUPTED;
}
// success
// note: don't assign these unless we succeed to avoid the
// false impression that all is well.
xf->type = m->type;
xf->tf = tf;
return 0;
}
int x_close(XFile* xf)
{
switch(xf->type)
{
// no file open (e.g. because x_open failed) -> nothing to do.
case MT_NONE:
return 0;
case MT_ARCHIVE:
(void)zip_close(&xf->u.zf);
break;
case MT_FILE:
(void)file_close(&xf->u.f);
break;
default:
debug_warn("x_close: invalid type");
break;
}
// update file state in VFS tree
// (must be done after close, since that calculates the size)
if(xf->u.f.flags & FILE_WRITE) // xxx what about other types?
tree_update_file(xf->tf, xf->u.f.size, time(0)); // can't fail
xf->type = MT_NONE;
return 0;
}
int x_validate(const XFile* xf)
{
switch(xf->type)
{
case MT_NONE:
if(xf->tf != 0)
return -100;
return 0; // ok, nothing else to check
case MT_FILE:
if(xf->tf == 0)
return -101;
return file_validate(&xf->u.f);
case MT_ARCHIVE:
// this could be set to 0 in x_open (since it's used to update the
// VFS after newly written files are closed, but archive files
// cannot be modified), but it's not ATM.
if(xf->tf == 0)
return -102;
return zip_validate(&xf->u.zf);
default:
return -103; // invalid type
}
UNREACHABLE;
}
bool x_is_open(const XFile* xf)
{
return (xf->type != MT_NONE);
}
cassert(offsetof(struct File, size ) == offsetof(struct ZFile, ucsize));
cassert(offsetof(struct File, flags) == offsetof(struct ZFile, flags));
// VFile was exceeding HDATA_USER_SIZE. flags and size (required
// in File as well as VFile) are now moved into the union.
// use the functions below to insulate against change a bit.
off_t x_size(const XFile* xf)
{
return xf->u.f.size;
}
void x_set_flags(XFile* xf, uint flags)
{
xf->u.f.flags = flags;
}
uint x_flags(const XFile* xf)
{
return xf->u.f.flags;
}
int x_io(XFile* xf, off_t ofs, size_t size, void* buf, FileIOCB cb, uintptr_t ctx)
{
switch(xf->type)
{
case MT_ARCHIVE:
// (vfs_open makes sure it's not opened for writing if zip)
return zip_read(&xf->u.zf, ofs, size, buf, cb, ctx);
case MT_FILE:
// normal file:
// let file_io alloc the buffer if the caller didn't (i.e. p = 0),
// because it knows about alignment / padding requirements
return file_io(&xf->u.f, ofs, size, buf, cb, ctx);
default:
debug_warn("vfs_io: invalid file type");
return ERR_CORRUPTED;
}
}
int x_map(XFile* xf, void*& p, size_t& size)
{
switch(xf->type)
{
case MT_ARCHIVE:
return zip_map(&xf->u.zf, p, size);
case MT_FILE:
return file_map(&xf->u.f, p, size);
default:
debug_warn("vfs_map: invalid type");
return ERR_CORRUPTED;
}
}
int x_unmap(XFile* xf)
{
switch(xf->type)
{
case MT_ARCHIVE:
return zip_unmap(&xf->u.zf);
case MT_FILE:
return file_unmap(&xf->u.f);
default:
debug_warn("vfs_unmap: invalid type");
return ERR_CORRUPTED;
}
}
int x_io_issue(XFile* xf, off_t ofs, size_t size, void* buf, XIo* xio)
{
xio->type = xf->type;
switch(xio->type)
{
case MT_ARCHIVE:
return zip_io_issue(&xf->u.zf, ofs, size, buf, &xio->u.zio);
case MT_FILE:
return file_io_issue(&xf->u.f, ofs, size, buf, &xio->u.fio);
default:
debug_warn("vfs_io_issue: invalid file type");
return ERR_CORRUPTED;
}
}
int x_io_has_completed(XIo* xio)
{
switch(xio->type)
{
case MT_ARCHIVE:
return zip_io_has_completed(&xio->u.zio);
case MT_FILE:
return file_io_has_completed(&xio->u.fio);
default:
debug_warn("vfs_io_has_completed: invalid type");
return ERR_CORRUPTED;
}
}
int x_io_wait(XIo* xio, void*& p, size_t& size)
{
switch(xio->type)
{
case MT_ARCHIVE:
return zip_io_wait(&xio->u.zio, p, size);
case MT_FILE:
return file_io_wait(&xio->u.fio, p, size);
default:
debug_warn("vfs_io_wait: invalid type");
return ERR_CORRUPTED;
}
}
int x_io_discard(XIo* xio)
{
switch(xio->type)
{
case MT_ARCHIVE:
return zip_io_discard(&xio->u.zio);
case MT_FILE:
return file_io_discard(&xio->u.fio);
default:
debug_warn("VIo_dtor: invalid type");
return ERR_CORRUPTED;
}
}
int x_io_validate(const XIo* xio)
{
switch(xio->type)
{
case MT_ARCHIVE:
return zip_io_validate(&xio->u.zio);
case MT_FILE:
return file_io_validate(&xio->u.fio);
default:
return -103; // invalid type
}
UNREACHABLE;
}