2004-03-03 00:56:51 +01:00
|
|
|
// virtual file system - transparent access to files in archives;
|
2004-05-13 15:52:48 +02:00
|
|
|
// allows multiple mount points
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
2004-05-13 15:52:48 +02:00
|
|
|
// Copyright (c) 2004 Jan Wassenberg
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
// 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/
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
#include "precompiled.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#include "lib.h"
|
2004-06-01 19:34:12 +02:00
|
|
|
#include "res.h"
|
2004-05-06 19:14:30 +02:00
|
|
|
#include "adts.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// currently not thread safe. will have to change that if
|
|
|
|
// a prefetch thread is to be used.
|
2004-03-03 00:56:51 +01:00
|
|
|
// not safe to call before main!
|
|
|
|
|
|
|
|
|
|
|
|
// rationale for no forcibly-close support:
|
|
|
|
// issue:
|
|
|
|
// we might want to edit files while the game has them open.
|
|
|
|
// usual case: edit file, notify engine that it should be reloaded.
|
|
|
|
// here: need to tell the engine to stop what it's doing and close the file;
|
|
|
|
// only then can the artist write to the file, and trigger a reload.
|
|
|
|
//
|
|
|
|
// work involved:
|
|
|
|
// since closing a file with pending aios results in undefined
|
|
|
|
// behavior on Win32, we would have to keep track of all aios from each file,
|
|
|
|
// and cancel them. we'd also need to notify the higher level resource user
|
|
|
|
// that its read was cancelled, as opposed to failing due to read errors
|
|
|
|
// (which might cause the game to terminate).
|
|
|
|
//
|
|
|
|
// this is just more work than benefit. cases where the game holds on to files
|
|
|
|
// are rare:
|
|
|
|
// - streaming music (artist can use regular commands to stop the current
|
|
|
|
// track, or all music)
|
|
|
|
// - if the engine happens to be reading that file at the moment (expected
|
|
|
|
// to happen only during loading, and these are usually one-shot anway,
|
|
|
|
// i.e. it'll be done soon)
|
|
|
|
// - bug (someone didn't close a file - tough luck, and should be fixed
|
|
|
|
// instead of hacking around it).
|
|
|
|
// - archives (these remain open. allowing reload would mean we'd have to keep
|
|
|
|
// track of all files from an archive, and reload them all. another hassle.
|
|
|
|
// anyway, if files are to be changed in-game, then change the plain-file
|
2004-05-06 19:14:30 +02:00
|
|
|
// version - that's what they're for).
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// path
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// path types:
|
2004-05-13 15:52:48 +02:00
|
|
|
// fn : filename only, no path at all.
|
|
|
|
// f_* : path intended directly for underlying file layer.
|
2004-05-13 19:23:07 +02:00
|
|
|
// component separator is '/'; no absolute paths, or ':', '\\' allowed.
|
2004-05-13 15:52:48 +02:00
|
|
|
// * : as above, but path within the VFS.
|
2004-05-13 19:23:07 +02:00
|
|
|
// "" is root dir; no absolute path allowed.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// path1 and path2 may be empty, filenames, or full paths.
|
|
|
|
static int path_append(char* dst, const char* path1, const char* path2)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
const size_t path1_len = strlen(path1);
|
2004-05-06 19:14:30 +02:00
|
|
|
const size_t path2_len = strlen(path2);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
bool need_separator = false;
|
|
|
|
|
|
|
|
size_t total_len = path1_len + path2_len + 1; // includes '\0'
|
|
|
|
if(path1_len > 0 && path1[path1_len-1] != '/')
|
|
|
|
{
|
|
|
|
total_len++; // for '/'
|
|
|
|
need_separator = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(total_len+1 > VFS_MAX_PATH)
|
|
|
|
return ERR_VFS_PATH_LENGTH;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
char* p = dst;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
strcpy(p, path1);
|
|
|
|
p += path1_len;
|
|
|
|
if(need_separator)
|
2004-05-06 19:14:30 +02:00
|
|
|
*p++ = '/';
|
|
|
|
strcpy(p, path2);
|
|
|
|
return 0;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
static int path_validate(const uint line, const char* const path)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-06 19:14:30 +02:00
|
|
|
size_t path_len = 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
const char* msg = 0; // error occurred <==> != 0
|
|
|
|
int err = -1; // pass error code to caller
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// disallow absolute path for safety, in case of *nix systems.
|
2004-05-06 19:14:30 +02:00
|
|
|
if(path[0] == '/')
|
|
|
|
{
|
|
|
|
msg = "absolute path";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
// scan each char in path string; count length.
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
const int c = path[path_len++];
|
|
|
|
|
|
|
|
// whole path is too long
|
|
|
|
if(path_len >= VFS_MAX_PATH)
|
|
|
|
{
|
|
|
|
msg = "path too long";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
// disallow ".." to prevent going above the VFS root dir
|
|
|
|
static bool last_was_dot;
|
|
|
|
if(c == '.')
|
|
|
|
{
|
|
|
|
if(last_was_dot)
|
|
|
|
{
|
|
|
|
msg = "contains \"..\"";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
last_was_dot = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
last_was_dot = false;
|
|
|
|
|
|
|
|
// disallow OS-specific dir separators
|
|
|
|
if(c == '\\' || c == ':')
|
|
|
|
{
|
|
|
|
msg = "contains OS-specific dir separator (e.g. '\\', ':')";
|
|
|
|
goto fail;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// end of string, all is well.
|
|
|
|
if(c == '\0')
|
|
|
|
goto ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
// failed somewhere - err is the error code,
|
|
|
|
// or -1 if not set specifically above.
|
|
|
|
fail:
|
|
|
|
debug_out("path_validate at line %d failed: %s", err);
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("path_validate failed");
|
2004-05-06 19:14:30 +02:00
|
|
|
return err;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
ok:
|
|
|
|
return 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-07 03:27:05 +02:00
|
|
|
#define CHECK_PATH(path) CHECK_ERR(path_validate(__LINE__, path))
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2004-05-08 03:11:51 +02:00
|
|
|
// "file system" (tree structure; stores location of each file)
|
2004-05-06 19:14:30 +02:00
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
// 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).
|
|
|
|
//
|
2004-06-01 19:34:12 +02:00
|
|
|
// one FileLoc is allocated for each archive or directory mounted.
|
|
|
|
// therefore, files only /point/ to a (possibly shared) FileLoc.
|
2004-05-06 19:14:30 +02:00
|
|
|
// if a file's location changes (e.g. after mounting a higher-priority
|
2004-06-01 19:34:12 +02:00
|
|
|
// directory), the VFS entry will point to the new FileLoc; the priority
|
2004-05-06 19:14:30 +02:00
|
|
|
// of both locations is unchanged.
|
|
|
|
//
|
2004-05-08 03:11:51 +02:00
|
|
|
// allocate via mnt_create, passing the location. do not free!
|
2004-05-06 19:14:30 +02:00
|
|
|
// we keep track of all Locs allocated; they are freed at exit,
|
2004-05-08 03:11:51 +02:00
|
|
|
// and by mnt_free_all (useful when rebuilding the VFS).
|
2004-05-06 19:14:30 +02:00
|
|
|
// this is much easier and safer than walking the VFS tree and
|
|
|
|
// freeing every location we find.
|
|
|
|
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
// not many instances => don't worry about struct size / alignment.
|
2004-06-01 19:34:12 +02:00
|
|
|
struct FileLoc
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
Handle archive;
|
|
|
|
std::string dir;
|
2004-05-06 19:14:30 +02:00
|
|
|
uint pri;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
FileLoc() {}
|
|
|
|
FileLoc(Handle _archive, const char* _dir, uint _pri)
|
2004-05-08 03:11:51 +02:00
|
|
|
: archive(_archive), dir(_dir), pri(_pri) {}
|
2004-05-06 19:14:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
// rationale for separate file / subdir containers:
|
|
|
|
// problems:
|
|
|
|
// - more code for insertion (oh well);
|
|
|
|
// - makes ordered output of all dirents difficult
|
|
|
|
// (but dirs and files are usually displayed separately)
|
|
|
|
// advantages:
|
|
|
|
// - simplifies lookup code: it can just check if a path is there,
|
|
|
|
// no need to check if the entry is actually a directory
|
|
|
|
// - storing Dir objects directly in the map means less
|
|
|
|
// memory allocations / no need to free them.
|
|
|
|
//
|
2004-06-02 17:32:42 +02:00
|
|
|
// add_* aborts if a subdir or file of the same name already exists.
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
typedef std::map<const std::string, const FileLoc*> Files;
|
2004-05-06 19:14:30 +02:00
|
|
|
typedef Files::iterator FileIt;
|
2004-06-01 19:34:12 +02:00
|
|
|
// notes:
|
|
|
|
// - FileLoc is allocated and owned by caller (the mount code)
|
|
|
|
// - priority is accessed by following the FileLoc pointer.
|
|
|
|
// keeping a copy in the map would lead to better cache coherency,
|
|
|
|
// but it's a bit more clumsy (map filename to struct {pri, FileLoc*}).
|
|
|
|
// revisit if file lookup open is too slow (unlikely).
|
|
|
|
|
|
|
|
struct Dir;
|
|
|
|
typedef std::pair<const std::string, Dir> SubDir;
|
|
|
|
typedef std::map<const std::string, Dir> SubDirs;
|
|
|
|
typedef SubDirs::iterator SubDirIt;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
struct Dir
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
int add_file(const char* name, const FileLoc* loc);
|
|
|
|
const FileLoc* find_file(const char* name);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
int add_subdir(const char* name);
|
|
|
|
Dir* find_subdir(const char* name);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
void clearR();
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
SubDirs subdirs;
|
|
|
|
Files files;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
int Dir::add_subdir(const char* const fn)
|
2004-06-01 19:34:12 +02:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
if(find_file(fn) || find_subdir(fn))
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
debug_warn("dir_add: file or subdirectory of same name already exists");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
subdirs[fn];
|
|
|
|
// side effect: maps <fn> to a newly constructed Dir()
|
|
|
|
// non-const => cannot be optimized away.
|
|
|
|
return 0;
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
Dir* Dir::find_subdir(const char* const fn)
|
2004-06-01 19:34:12 +02:00
|
|
|
{
|
|
|
|
SubDirIt it = subdirs.find(fn);
|
|
|
|
if(it == subdirs.end())
|
2004-05-06 19:14:30 +02:00
|
|
|
return 0;
|
2004-06-01 19:34:12 +02:00
|
|
|
return &it->second;
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
int Dir::add_file(const char* const fn, const FileLoc* const loc)
|
2004-06-01 19:34:12 +02:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
if(find_subdir(fn))
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
debug_warn("dir_add: file of same name already exists");
|
|
|
|
return -1;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
// default pointer ctor sets it to 0 =>
|
|
|
|
// if fn wasn't already in the container, old_loc is 0.
|
2004-06-02 17:32:42 +02:00
|
|
|
typedef const FileLoc* Data;
|
|
|
|
// for absolute clarity; the container holds const FileLoc* objects.
|
|
|
|
// operator[] returns a reference to that.
|
|
|
|
// need this typedef to work around a GCC bug?
|
|
|
|
Data& old_loc = files[fn];
|
2004-06-01 19:34:12 +02:00
|
|
|
// old loc exists and is higher priority - keep it.
|
|
|
|
if(old_loc && old_loc->pri > loc->pri)
|
|
|
|
return 1;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
old_loc = loc;
|
|
|
|
return 0;
|
|
|
|
}
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
const FileLoc* Dir::find_file(const char* const fn)
|
2004-06-01 19:34:12 +02:00
|
|
|
{
|
|
|
|
FileIt it = files.find(fn);
|
|
|
|
if(it == files.end())
|
|
|
|
return 0;
|
|
|
|
return it->second;
|
|
|
|
}
|
2004-05-08 03:11:51 +02:00
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
void Dir::clearR()
|
|
|
|
{
|
|
|
|
SubDirIt it;
|
|
|
|
for(it = subdirs.begin(); it != subdirs.end(); ++it)
|
|
|
|
{
|
|
|
|
Dir& subdir = it->second;
|
|
|
|
subdir.clearR();
|
|
|
|
}
|
|
|
|
|
|
|
|
subdirs.clear();
|
|
|
|
files.clear();
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
static Dir vfs_root;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
enum LookupFlags
|
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
LF_DEFAULT = 0,
|
2004-06-02 17:32:42 +02:00
|
|
|
|
|
|
|
LF_CREATE_MISSING_COMPONENTS = 1,
|
|
|
|
|
|
|
|
LF_LAST = 2
|
2004-05-06 19:14:30 +02:00
|
|
|
};
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2004-05-13 19:23:07 +02:00
|
|
|
// starts in VFS root directory (path = "").
|
2004-06-02 17:32:42 +02:00
|
|
|
// path may specify a file or directory; it need not end in '/'.
|
2004-06-01 19:34:12 +02:00
|
|
|
static int tree_lookup(const char* path, const FileLoc** const loc = 0, Dir** const dir = 0, LookupFlags flags = LF_DEFAULT)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-13 19:23:07 +02:00
|
|
|
CHECK_PATH(path);
|
2004-06-02 17:32:42 +02:00
|
|
|
assert(loc != 0 || dir != 0);
|
|
|
|
assert(flags < LF_LAST);
|
|
|
|
|
|
|
|
const bool create_missing_components = flags & LF_CREATE_MISSING_COMPONENTS;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
// copy into (writeable) buffer so we can 'tokenize' path components
|
2004-06-02 17:32:42 +02:00
|
|
|
// by replacing '/' with '\0'. length check done by CHECK_PATH.
|
2004-05-06 19:14:30 +02:00
|
|
|
char buf[VFS_MAX_PATH];
|
2004-05-13 19:23:07 +02:00
|
|
|
strcpy(buf, path);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
Dir* cur_dir = &vfs_root;
|
2004-06-02 17:32:42 +02:00
|
|
|
const char* cur_component = buf;
|
|
|
|
bool is_last_component = false;
|
|
|
|
|
|
|
|
// subdirectory traverse logic
|
|
|
|
// valid:
|
|
|
|
// "" (root dir),
|
|
|
|
// "*file",
|
|
|
|
// "*dir[/]"
|
|
|
|
// invalid:
|
|
|
|
// "/*" (caught by CHECK_PATH),
|
|
|
|
// "*file/*" (subdir switch will fail)
|
|
|
|
//
|
|
|
|
// a bit tricky: make sure we go through the directory checks for the
|
|
|
|
// last component - it may either be a file or directory name.
|
|
|
|
// we don't require a trailing '/' for dir names because appending it
|
|
|
|
// to a given dir name would be more (unnecessary) work for the caller.
|
|
|
|
|
|
|
|
// successively navigate to the next subdirectory in <path>.
|
2004-05-06 19:14:30 +02:00
|
|
|
for(;;)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
// "extract" cur_component string (0-terminate by replacing '/')
|
2004-05-06 19:14:30 +02:00
|
|
|
char* slash = strchr(cur_component, '/');
|
2004-06-02 17:32:42 +02:00
|
|
|
if(slash)
|
|
|
|
*slash = 0;
|
|
|
|
// allow root dir ("") and trailing '/' if looking up a dir
|
|
|
|
if(*cur_component == '\0' && loc == 0)
|
2004-06-01 19:34:12 +02:00
|
|
|
break;
|
2004-06-02 17:32:42 +02:00
|
|
|
is_last_component = (slash == 0);
|
2004-06-01 19:34:12 +02:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
// create <cur_component> subdir (no-op if it already exists)
|
2004-06-01 19:34:12 +02:00
|
|
|
if(create_missing_components)
|
2004-06-02 17:32:42 +02:00
|
|
|
cur_dir->add_subdir(cur_component);
|
2004-06-01 19:34:12 +02:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
// switch to <cur_component>
|
|
|
|
Dir* subdir = cur_dir->find_subdir(cur_component);
|
2004-06-01 19:34:12 +02:00
|
|
|
if(!subdir)
|
2004-06-02 17:32:42 +02:00
|
|
|
{
|
|
|
|
// this last component is a filename.
|
|
|
|
if(is_last_component && loc != 0)
|
|
|
|
break;
|
|
|
|
// otherwise, we had an (invalid) subdir name - fail.
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
cur_dir = subdir; // don't assign if invalid - need cur_dir below.
|
2004-06-01 19:34:12 +02:00
|
|
|
|
|
|
|
// next component
|
2004-06-02 17:32:42 +02:00
|
|
|
if(is_last_component)
|
|
|
|
break;
|
2004-06-01 19:34:12 +02:00
|
|
|
cur_component = slash+1;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-02 17:32:42 +02:00
|
|
|
// caller wants a file (possibly in addition to its dir)
|
2004-06-01 19:34:12 +02:00
|
|
|
if(loc)
|
2004-05-08 03:11:51 +02:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
*loc = cur_dir->find_file(cur_component);
|
2004-06-01 19:34:12 +02:00
|
|
|
// .. but the file doesn't exist
|
|
|
|
if(!*loc)
|
2004-06-02 17:32:42 +02:00
|
|
|
return -ENOENT;
|
2004-05-08 03:11:51 +02:00
|
|
|
}
|
2004-06-02 17:32:42 +02:00
|
|
|
|
|
|
|
// if loc != 0, we know cur_dir is correct, because it contains
|
|
|
|
// the desired file (otherwise, the find_file above will have failed).
|
|
|
|
// if loc == 0, path is a dir name, and we have successfully traversed
|
|
|
|
// all path components (subdirectories) in the loop.
|
2004-06-01 19:34:12 +02:00
|
|
|
if(dir)
|
|
|
|
*dir = cur_dir;
|
2004-06-02 17:32:42 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
return 0;
|
2004-05-08 03:11:51 +02:00
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
static inline void tree_clear()
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
vfs_root.clearR();
|
2004-05-08 03:11:51 +02:00
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
struct FileCBParams
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
Dir* const dir;
|
2004-06-02 17:32:42 +02:00
|
|
|
const FileLoc* const loc;
|
|
|
|
FileCBParams(Dir* _dir, const FileLoc* _loc)
|
|
|
|
: dir(_dir), loc(_loc) {}
|
2004-05-08 03:11:51 +02:00
|
|
|
};
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
// called for each OS dir ent.
|
|
|
|
// add each file and directory to the VFS dir.
|
|
|
|
//
|
|
|
|
// note:
|
|
|
|
// we don't mount archives here for performance reasons.
|
|
|
|
// that means archives in subdirectories of mount points aren't added!
|
|
|
|
// rationale: can't determine if file is an archive via extension -
|
|
|
|
// they might be called .pk3 or whatnot. for every file in the tree, we'd have
|
|
|
|
// to try to open it as an archive - not good.
|
|
|
|
// this restriction also simplifies the code a bit, but if it's a problem,
|
|
|
|
// just generate a list of archives here and mount them from the caller.
|
2004-05-13 15:52:48 +02:00
|
|
|
static int add_dirent_cb(const char* const fn, const uint flags, const ssize_t size, const uintptr_t user)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
const FileCBParams* const params = (FileCBParams*)user;
|
2004-06-02 17:32:42 +02:00
|
|
|
Dir* const cur_dir = params->dir;
|
2004-06-01 19:34:12 +02:00
|
|
|
const FileLoc* const cur_loc = params->loc;
|
|
|
|
|
|
|
|
int err;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
// directory
|
|
|
|
if(flags & LOC_DIR)
|
2004-06-02 17:32:42 +02:00
|
|
|
err = cur_dir->add_subdir(fn);
|
2004-05-06 19:14:30 +02:00
|
|
|
// file
|
|
|
|
else
|
2004-06-02 17:32:42 +02:00
|
|
|
err = cur_dir->add_file(fn, cur_loc);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
if(err < 0)
|
|
|
|
return -EEXIST;
|
2004-05-06 19:14:30 +02:00
|
|
|
return 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
static int tree_add_dirR(Dir* const dir, const char* const f_path, const FileLoc* const loc)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
CHECK_PATH(f_path);
|
|
|
|
|
|
|
|
// add files and subdirs to vdir
|
2004-06-02 17:32:42 +02:00
|
|
|
const FileCBParams params(dir, loc);
|
2004-05-13 15:52:48 +02:00
|
|
|
file_enum(f_path, add_dirent_cb, (uintptr_t)¶ms);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
for(SubDirIt it = dir->subdirs.begin(); it != dir->subdirs.end(); ++it)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
Dir* const subdir = &it->second;
|
|
|
|
const char* const subdir_name_c = (it->first).c_str();
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
char f_subdir_path[VFS_MAX_PATH];
|
2004-06-01 19:34:12 +02:00
|
|
|
CHECK_ERR(path_append(f_subdir_path, f_path, subdir_name_c));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
tree_add_dirR(subdir, f_subdir_path, loc);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
static int tree_add_loc(Dir* const dir, const FileLoc* const loc)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
if(loc->archive > 0)
|
2004-05-13 15:52:48 +02:00
|
|
|
{
|
2004-06-02 17:32:42 +02:00
|
|
|
const FileCBParams params(dir, loc);
|
2004-05-08 03:11:51 +02:00
|
|
|
return zip_enum(loc->archive, add_dirent_cb, (uintptr_t)¶ms);
|
2004-05-13 15:52:48 +02:00
|
|
|
}
|
2004-05-08 03:11:51 +02:00
|
|
|
else
|
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
const char* f_path_c = loc->dir.c_str();
|
2004-06-01 19:34:12 +02:00
|
|
|
return tree_add_dirR(dir, f_path_c, loc);
|
2004-05-08 03:11:51 +02:00
|
|
|
}
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// mount archives and directories into the VFS
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// container must not invalidate iterators after insertion!
|
|
|
|
// (we keep and pass around pointers to Mount.archive_locs elements)
|
|
|
|
// see below.
|
2004-06-02 17:32:42 +02:00
|
|
|
//
|
|
|
|
// not const, because we h_free a handle in FileLoc
|
|
|
|
// (it resets the handle to 0)
|
2004-06-01 19:34:12 +02:00
|
|
|
typedef std::list<FileLoc> Locs;
|
2004-05-08 03:11:51 +02:00
|
|
|
typedef Locs::iterator LocIt;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
struct Mount
|
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
// mounting into this VFS directory ("" for root)
|
|
|
|
std::string v_path;
|
|
|
|
|
|
|
|
// what is being mounted; either directory,
|
|
|
|
// or archive filename (=> is_single_archive = true)
|
|
|
|
std::string f_name;
|
2004-05-08 03:11:51 +02:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
uint pri;
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// storage for all Locs ensuing from this mounting.
|
2004-06-01 19:34:12 +02:00
|
|
|
// the VFS tree only holds pointers to FileLoc, which is why the
|
2004-05-13 15:52:48 +02:00
|
|
|
// Locs container must not invalidate its contents after adding,
|
|
|
|
// and also why the VFS tree must be rebuilt after unmounting something.
|
2004-06-01 19:34:12 +02:00
|
|
|
FileLoc dir_loc;
|
2004-05-08 03:11:51 +02:00
|
|
|
Locs archive_locs;
|
2004-06-01 19:34:12 +02:00
|
|
|
// if not is_single_archive, contains one FileLoc for every archive
|
2004-05-13 15:52:48 +02:00
|
|
|
// in the directory (but not its children - see remount()).
|
2004-06-01 19:34:12 +02:00
|
|
|
// otherwise, contains exactly one FileLoc for the single archive.
|
2004-05-13 15:52:48 +02:00
|
|
|
|
|
|
|
// is f_name an archive filename? if not, it's a directory.
|
|
|
|
bool is_single_archive;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
Mount() {}
|
2004-05-13 15:52:48 +02:00
|
|
|
Mount(const char* _v_path, const char* _f_name, uint _pri)
|
|
|
|
: v_path(_v_path), f_name(_f_name), pri(_pri),
|
2004-06-02 17:32:42 +02:00
|
|
|
dir_loc(0, _f_name, 0), archive_locs(), is_single_archive(false)
|
2004-05-13 15:52:48 +02:00
|
|
|
{
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
};
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
typedef std::vector<Mount> Mounts;
|
2004-05-06 19:14:30 +02:00
|
|
|
typedef Mounts::iterator MountIt;
|
|
|
|
static Mounts mounts;
|
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// support for mounting multiple archives in a directory
|
|
|
|
// (useful for mix-in mods and patches).
|
|
|
|
// all archives are enumerated, added to a Locs list,
|
|
|
|
// and mounted (in alphabetical order!)
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
struct ArchiveCBParams
|
|
|
|
{
|
|
|
|
// we need a full path to open the archive, and only receive
|
|
|
|
// the filename, so prepend this (the directory being searched).
|
|
|
|
const char* f_dir;
|
|
|
|
|
|
|
|
// priority at which the archive is to be mounted.
|
|
|
|
// specify here, instead of when actually adding the archive,
|
|
|
|
// because Locs are created const.
|
|
|
|
uint pri;
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
// will add one FileLoc to this container for
|
2004-05-13 15:52:48 +02:00
|
|
|
// every archive successfully opened.
|
|
|
|
Locs* archive_locs;
|
|
|
|
};
|
|
|
|
|
|
|
|
// called for each directory entry.
|
|
|
|
// add each successfully opened archive to list.
|
|
|
|
static int archive_cb(const char* const fn, const uint flags, const ssize_t size, const uintptr_t user)
|
|
|
|
{
|
|
|
|
// not interested in subdirectories
|
|
|
|
if(flags & LOC_DIR)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
const ArchiveCBParams* const params = (ArchiveCBParams*)user;
|
|
|
|
const char* const f_dir = params->f_dir;
|
|
|
|
const uint pri = params->pri;
|
|
|
|
Locs* const archive_locs = params->archive_locs;
|
|
|
|
|
|
|
|
// get full path (fn is filename only)
|
|
|
|
char f_path[VFS_MAX_PATH];
|
|
|
|
CHECK_ERR(path_append(f_path, f_dir, fn));
|
2004-05-08 03:11:51 +02:00
|
|
|
|
|
|
|
// don't check filename extension - archives won't necessarily
|
|
|
|
// be called .zip (example: Quake III .pk3).
|
|
|
|
// just try to open the file.
|
2004-05-13 15:52:48 +02:00
|
|
|
const Handle archive = zip_archive_open(f_path);
|
2004-05-08 03:11:51 +02:00
|
|
|
if(archive > 0)
|
2004-06-01 19:34:12 +02:00
|
|
|
archive_locs->push_back(FileLoc(archive, "", pri));
|
2004-05-08 03:11:51 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// only add archive to list; don't add its files into the VFS yet,
|
|
|
|
// to simplify debugging (we see which files are in which archive)
|
2004-05-08 03:11:51 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// actually mount the specified entry (either Zip archive or dir).
|
|
|
|
// split out of vfs_mount because we need to mount without changing the
|
|
|
|
// mount list, when invalidating (reloading) the VFS.
|
|
|
|
static int remount(Mount& m)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
int err;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
const char* const v_path = m.v_path.c_str();
|
|
|
|
const char* const f_name = m.f_name.c_str();
|
|
|
|
const uint pri = m.pri;
|
2004-06-02 17:32:42 +02:00
|
|
|
const FileLoc& dir_loc = m.dir_loc;
|
2004-05-13 15:52:48 +02:00
|
|
|
Locs& archive_locs = m.archive_locs;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
Dir* dir;
|
|
|
|
CHECK_ERR(tree_lookup(v_path, 0, &dir, LF_CREATE_MISSING_COMPONENTS));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
// check if target is a single Zip archive
|
|
|
|
// order doesn't matter; can't have both an archive and dir
|
2004-05-13 15:52:48 +02:00
|
|
|
const Handle archive = zip_archive_open(f_name);
|
2004-05-08 03:11:51 +02:00
|
|
|
if(archive > 0)
|
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
m.is_single_archive = true;
|
2004-06-01 19:34:12 +02:00
|
|
|
archive_locs.push_back(FileLoc(archive, "", pri));
|
|
|
|
const FileLoc* loc = &archive_locs.front();
|
|
|
|
return tree_add_loc(dir, loc);
|
2004-05-08 03:11:51 +02:00
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
// enumerate all archives
|
|
|
|
ArchiveCBParams params = { f_name, pri, &archive_locs };
|
|
|
|
file_enum(f_name, archive_cb, (uintptr_t)¶ms);
|
|
|
|
|
|
|
|
for(LocIt it = archive_locs.begin(); it != archive_locs.end(); ++it)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
const FileLoc* const loc = &*it;
|
|
|
|
tree_add_loc(dir, loc);
|
2004-05-13 15:52:48 +02:00
|
|
|
}
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
err = tree_add_loc(dir, &dir_loc);
|
2004-05-08 03:11:51 +02:00
|
|
|
if(err < 0)
|
|
|
|
err = err;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
return 0;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int unmount(Mount& m)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
for(LocIt it = m.archive_locs.begin(); it != m.archive_locs.end(); ++it)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
FileLoc& loc = *it;
|
2004-05-08 03:11:51 +02:00
|
|
|
zip_archive_close(loc.archive);
|
|
|
|
}
|
|
|
|
|
|
|
|
m.archive_locs.clear();
|
2004-05-06 19:14:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
static inline void unmount_all(void)
|
2004-05-08 03:11:51 +02:00
|
|
|
{ std::for_each(mounts.begin(), mounts.end(), unmount); }
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
static inline void remount_all()
|
2004-05-08 03:11:51 +02:00
|
|
|
{ std::for_each(mounts.begin(), mounts.end(), remount); }
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
void vfs_shutdown(void)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
tree_clear();
|
|
|
|
unmount_all();
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
|
|
|
|
int vfs_mount(const char* const vfs_mount_point, const char* const name, const uint pri)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
ONCE(atexit2(vfs_shutdown));
|
2004-05-08 03:11:51 +02:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// make sure it's not already mounted, i.e. in mounts
|
2004-05-13 15:52:48 +02:00
|
|
|
for(MountIt it = mounts.begin(); it != mounts.end(); ++it)
|
|
|
|
if(it->f_name == name)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("vfs_mount: already mounted");
|
2004-05-06 19:14:30 +02:00
|
|
|
return -1;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-05-29 14:00:53 +02:00
|
|
|
// disallow . because "./" isn't supported on Windows.
|
|
|
|
// the more important reason is that mount points must not overlap
|
|
|
|
// (i.e. mount $install/data and then $install/data/mods/official -
|
|
|
|
// mods/official would also be accessible from the first mount point).
|
|
|
|
if(!strcmp(name, ".") || !strcmp(name, "./"))
|
|
|
|
{
|
|
|
|
debug_warn("vfs_mount: mounting . not allowed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
mounts.push_back(Mount(vfs_mount_point, name, pri));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
// actually mount the entry
|
2004-05-13 15:52:48 +02:00
|
|
|
Mount& m = mounts.back();
|
2004-05-06 19:14:30 +02:00
|
|
|
return remount(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int vfs_rebuild()
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
tree_clear();
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
unmount_all();
|
|
|
|
remount_all();
|
|
|
|
return 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
int vfs_unmount(const char* name)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-06 19:14:30 +02:00
|
|
|
for(MountIt it = mounts.begin(); it != mounts.end(); ++it)
|
|
|
|
// found the corresponding entry
|
2004-05-13 15:52:48 +02:00
|
|
|
if(it->f_name == name)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
Mount& m = *it;
|
|
|
|
unmount(m);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
mounts.erase(it);
|
2004-05-08 03:11:51 +02:00
|
|
|
return vfs_rebuild();
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ERR_PATH_NOT_FOUND;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2004-06-01 19:34:12 +02:00
|
|
|
// directory
|
2004-05-06 19:14:30 +02:00
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
int vfs_realpath(const char* fn, char* full_path)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
const FileLoc* loc;
|
2004-05-06 19:14:30 +02:00
|
|
|
CHECK_ERR(tree_lookup(fn, &loc));
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
if(loc->archive > 0)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
const char* archive_fn = h_filename(loc->archive);
|
2004-05-06 19:14:30 +02:00
|
|
|
if(!archive_fn)
|
|
|
|
return -1;
|
|
|
|
strncpy(full_path, archive_fn, PATH_MAX);
|
|
|
|
}
|
2004-05-08 03:11:51 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
strncpy(full_path, loc->dir.c_str(), PATH_MAX);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
return 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
int vfs_stat(const char* fn, struct stat* s)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
const FileLoc* loc;
|
2004-05-06 19:14:30 +02:00
|
|
|
CHECK_ERR(tree_lookup(fn, &loc));
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
if(loc->archive > 0)
|
|
|
|
return zip_stat(loc->archive, fn, s);
|
2004-05-06 19:14:30 +02:00
|
|
|
else
|
2004-05-08 03:11:51 +02:00
|
|
|
{
|
|
|
|
const char* dir = loc->dir.c_str();
|
|
|
|
return file_stat(dir, s);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-29 14:00:53 +02:00
|
|
|
struct VDir
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
// we need to cache the complete contents of the directory:
|
|
|
|
//
|
|
|
|
SubDirs* subdirs;
|
|
|
|
SubDirIt subdir_it;
|
|
|
|
Files* files;
|
|
|
|
FileIt file_it;
|
2004-05-29 14:00:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
H_TYPE_DEFINE(VDir);
|
|
|
|
|
|
|
|
static void VDir_init(VDir* vd, va_list args)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void VDir_dtor(VDir* vd)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
delete vd->subdirs;
|
|
|
|
delete vd->files;
|
2004-05-29 14:00:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int VDir_reload(VDir* vd, const char* path)
|
|
|
|
{
|
|
|
|
// check if actually reloaded, and why it happened?
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
Dir* dir;
|
|
|
|
CHECK_ERR(tree_lookup(path, 0, &dir));
|
2004-05-29 14:00:53 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
vd->subdirs = new SubDirs(dir->subdirs);
|
|
|
|
vd->subdir_it = vd->subdirs->begin();
|
|
|
|
vd->files = new Files(dir->files);
|
|
|
|
vd->file_it = vd->files->begin();
|
2004-05-29 14:00:53 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Handle vfs_open_dir(const char* const path)
|
|
|
|
{
|
|
|
|
return h_alloc(H_VDir, path, 0);
|
|
|
|
}
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
|
2004-05-29 14:00:53 +02:00
|
|
|
int vfs_close_dir(Handle& hd)
|
|
|
|
{
|
|
|
|
return h_free(hd, H_VDir);
|
|
|
|
}
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
|
|
|
|
// filter:
|
|
|
|
// 0: any file
|
|
|
|
// ".": file without extension (filename doesn't contain '.')
|
|
|
|
// ".ext": file with extension <ext> (which must not contain '.')
|
|
|
|
// "/": subdirectory
|
2004-05-29 14:00:53 +02:00
|
|
|
int vfs_next_dirent(const Handle hd, vfsDirEnt* ent, const char* const filter)
|
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
H_DEREF(hd, VDir, vd);
|
2004-05-29 14:00:53 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
// interpret filter
|
|
|
|
bool filter_dir = false;
|
|
|
|
bool filter_no_ext = false;
|
|
|
|
if(filter)
|
2004-05-29 14:00:53 +02:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
if(filter[0] == '/')
|
|
|
|
{
|
|
|
|
if(filter[1] != '\0')
|
|
|
|
goto invalid_filter;
|
|
|
|
filter_dir = true;
|
|
|
|
}
|
|
|
|
else if(filter[0] == '.')
|
2004-05-29 14:00:53 +02:00
|
|
|
{
|
2004-06-01 19:34:12 +02:00
|
|
|
if(strchr(filter+1, '.'))
|
|
|
|
goto invalid_filter;
|
|
|
|
filter_no_ext = filter[1] == '\0';
|
2004-05-29 14:00:53 +02:00
|
|
|
}
|
2004-06-01 19:34:12 +02:00
|
|
|
else
|
|
|
|
goto invalid_filter;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* fn;
|
2004-05-29 14:00:53 +02:00
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
// caller wants a subdirectory; return the next one.
|
|
|
|
if(filter_dir)
|
|
|
|
{
|
|
|
|
if(vd->subdir_it == vd->subdirs->end())
|
|
|
|
return -1;
|
|
|
|
fn = vd->subdir_it->first.c_str();
|
|
|
|
++vd->subdir_it;
|
|
|
|
goto have_match;
|
2004-05-29 14:00:53 +02:00
|
|
|
}
|
2004-06-01 19:34:12 +02:00
|
|
|
|
|
|
|
// caller wants a file; loop until one matches or end of list.
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
if(vd->file_it == vd->files->end())
|
|
|
|
return -1;
|
|
|
|
fn = vd->file_it->first.c_str();
|
|
|
|
++vd->file_it;
|
|
|
|
|
|
|
|
char* const ext = strrchr(fn, '.');
|
|
|
|
if(!filter || (filter_no_ext && !ext) || strcmp(ext, filter) == 0)
|
|
|
|
goto have_match;
|
|
|
|
}
|
|
|
|
|
|
|
|
have_match:
|
|
|
|
ent->name = fn;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
invalid_filter:
|
|
|
|
debug_warn("vfs_next_dirent: invalid filter");
|
|
|
|
return -1;
|
2004-05-29 14:00:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// file
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
// internal file state flags
|
|
|
|
// make sure these don't conflict with vfs.h flags
|
|
|
|
VF_OPEN = 0x100,
|
|
|
|
VF_ZIP = 0x200,
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct VFile
|
|
|
|
{
|
|
|
|
// cached contents of file from vfs_load
|
|
|
|
// (can't just use pointer - may be freed behind our back)
|
|
|
|
Handle hm;
|
|
|
|
|
|
|
|
union
|
|
|
|
{
|
|
|
|
File f;
|
|
|
|
ZFile zf;
|
|
|
|
};
|
2004-03-05 17:23:31 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// be aware when adding fields that we're already pushing the size limit
|
|
|
|
// (especially in PARANOIA builds, which add a member!)
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
H_TYPE_DEFINE(VFile)
|
|
|
|
|
|
|
|
|
2004-03-05 17:23:31 +01:00
|
|
|
// with #define PARANOIA, File and ZFile get an additional member,
|
|
|
|
// and 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.
|
|
|
|
|
|
|
|
|
|
|
|
static size_t& vf_size(VFile* vf)
|
|
|
|
{
|
|
|
|
assert(offsetof(struct File, size) == offsetof(struct ZFile, ucsize));
|
|
|
|
return vf->f.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int& vf_flags(VFile* vf)
|
|
|
|
{
|
|
|
|
assert(offsetof(struct File, flags) == offsetof(struct ZFile, flags));
|
|
|
|
return vf->f.flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
static void VFile_init(VFile* vf, va_list args)
|
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
int flags = va_arg(args, int);
|
|
|
|
vf_flags(vf) = flags;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void VFile_dtor(VFile* vf)
|
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
int& flags = vf_flags(vf);
|
|
|
|
|
|
|
|
if(flags & VF_OPEN)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
if(flags & VF_ZIP)
|
2004-03-03 00:56:51 +01:00
|
|
|
zip_close(&vf->zf);
|
|
|
|
else
|
|
|
|
file_close(&vf->f);
|
|
|
|
|
2004-03-05 17:23:31 +01:00
|
|
|
flags &= ~(VF_OPEN);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
mem_free_h(vf->hm);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
static int VFile_reload(VFile* vf, const char* path)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
int& flags = vf_flags(vf);
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// we're done if file is already open. need to check this because reload order
|
|
|
|
// (e.g. if resource opens a file) is unspecified.
|
2004-03-05 17:23:31 +01:00
|
|
|
if(flags & VF_OPEN)
|
2004-03-03 00:56:51 +01:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
int err = -1;
|
|
|
|
|
|
|
|
|
2004-06-01 19:34:12 +02:00
|
|
|
const FileLoc* loc;
|
2004-05-13 15:52:48 +02:00
|
|
|
CHECK_ERR(tree_lookup(path, &loc));
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
if(loc->archive <= 0)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-13 15:52:48 +02:00
|
|
|
char f_path[PATH_MAX];
|
2004-05-08 03:11:51 +02:00
|
|
|
const char* dir = loc->dir.c_str();
|
2004-05-13 15:52:48 +02:00
|
|
|
CHECK_ERR(path_append(f_path, dir, path));
|
|
|
|
CHECK_ERR(file_open(f_path, vf_flags(vf), &vf->f));
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(flags & VFS_WRITE)
|
|
|
|
{
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("requesting write access to file in archive");
|
2004-05-06 19:14:30 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-13 15:52:48 +02:00
|
|
|
CHECK_ERR(zip_open(loc->archive, path, &vf->zf));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
flags |= VF_ZIP;
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
// success
|
2004-03-05 17:23:31 +01:00
|
|
|
flags |= VF_OPEN;
|
2004-03-03 00:56:51 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
Handle vfs_open(const char* fn, uint flags /* = 0 */)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-06 19:14:30 +02:00
|
|
|
Handle h = h_alloc(H_VFile, fn, 0, flags);
|
2004-03-03 00:56:51 +01:00
|
|
|
// pass file flags to init
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-05-06 19:14:30 +02:00
|
|
|
debug_out("vfs_open fn=%s %I64x\n", fn, h);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return h;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline int vfs_close(Handle& h)
|
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-05-06 19:14:30 +02:00
|
|
|
debug_out("vfs_close %I64x\n", h);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
return h_free(h, H_VFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p)
|
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-05-06 19:14:30 +02:00
|
|
|
debug_out("vfs_io ofs=%d size=%d\n", ofs, size);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
H_DEREF(hf, VFile, vf);
|
|
|
|
|
|
|
|
// (vfs_open makes sure it's not opened for writing if zip)
|
2004-03-05 17:23:31 +01:00
|
|
|
if(vf_flags(vf) & VF_ZIP)
|
2004-03-03 00:56:51 +01:00
|
|
|
return zip_read(&vf->zf, ofs, size, p);
|
|
|
|
|
|
|
|
// normal file:
|
2004-05-06 19:14:30 +02:00
|
|
|
// let file_io alloc the buffer if the caller didn't (i.e. p = 0),
|
2004-03-03 00:56:51 +01:00
|
|
|
// because it knows about alignment / padding requirements
|
|
|
|
return file_io(&vf->f, ofs, size, &p);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Handle vfs_load(const char* fn, void*& p, size_t& size)
|
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-05-06 19:14:30 +02:00
|
|
|
debug_out("vfs_load fn=%s\n", fn);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
p = 0; // vfs_io needs initial 0 value
|
2004-03-05 17:23:31 +01:00
|
|
|
size = 0; // in case open or deref fails
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
Handle hf = vfs_open(fn);
|
|
|
|
if(hf <= 0)
|
|
|
|
return hf; // error code
|
|
|
|
H_DEREF(hf, VFile, vf);
|
|
|
|
|
|
|
|
Handle hm = 0;
|
2004-03-05 17:23:31 +01:00
|
|
|
size = vf_size(vf);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// already read into mem - return existing mem handle
|
|
|
|
// TODO: what if mapped?
|
|
|
|
if(vf->hm > 0)
|
|
|
|
{
|
|
|
|
p = mem_get_ptr(vf->hm, &size);
|
|
|
|
if(p)
|
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
assert(vf_size(vf) == size && "vfs_load: mismatch between File and Mem size");
|
2004-03-03 00:56:51 +01:00
|
|
|
hm = vf->hm;
|
|
|
|
goto skip_read;
|
|
|
|
}
|
|
|
|
else
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("vfs_load: invalid MEM attached to vfile (0 pointer)");
|
2004-03-03 00:56:51 +01:00
|
|
|
// happens if someone frees the pointer. not an error!
|
|
|
|
}
|
|
|
|
|
|
|
|
{ // VC6 goto fix
|
2004-03-03 16:16:20 +01:00
|
|
|
ssize_t nread = vfs_io(hf, 0, size, p);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(nread > 0)
|
|
|
|
hm = mem_assign(p, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
skip_read:
|
|
|
|
|
|
|
|
vfs_close(hf);
|
|
|
|
|
|
|
|
// if we fail, make sure these are set to 0
|
|
|
|
// (they may have been assigned values above)
|
|
|
|
if(hm <= 0)
|
|
|
|
p = 0, size = 0;
|
|
|
|
|
|
|
|
return hm;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int vfs_store(const char* fn, void* p, size_t size)
|
|
|
|
{
|
|
|
|
Handle hf = vfs_open(fn, VFS_WRITE);
|
|
|
|
if(hf <= 0)
|
|
|
|
return (int)hf; // error code
|
|
|
|
H_DEREF(hf, VFile, vf);
|
|
|
|
int ret = vfs_io(hf, 0, size, p);
|
|
|
|
vfs_close(hf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
Handle vfs_map(const char* fn, uint flags, void*& p, size_t& size)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
|
|
|
Handle hf = vfs_open(fn, flags);
|
|
|
|
H_DEREF(hf, VFile, vf);
|
2004-05-06 19:14:30 +02:00
|
|
|
CHECK_ERR(file_map(&vf->f, p, size));
|
2004-03-03 00:56:51 +01:00
|
|
|
MEM_DTOR dtor = 0;
|
|
|
|
uintptr_t ctx = 0;
|
|
|
|
return mem_assign(p, size, 0, dtor, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int vfs_unmap(Handle& hm)
|
|
|
|
{
|
2004-03-05 17:23:31 +01:00
|
|
|
return -1;
|
|
|
|
// return h_free(hm, H_MMap);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|