forked from 0ad/0ad
568 lines
10 KiB
C++
Executable File
568 lines
10 KiB
C++
Executable File
// virtual file system - transparent access to files in archives;
|
|
// allows multiple search paths
|
|
//
|
|
// Copyright (c) 2003 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/
|
|
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#include "posix.h"
|
|
#include "zip.h"
|
|
#include "misc.h"
|
|
#include "vfs.h"
|
|
#include "mem.h"
|
|
|
|
|
|
// H_VFILE handle
|
|
struct VFILE
|
|
{
|
|
int fd;
|
|
|
|
size_t size; // compressed size, if a Zip file
|
|
|
|
// Zip only:
|
|
size_t ucsize;
|
|
size_t ofs;
|
|
|
|
Handle hm; // memory handle to the file or archive, if a Zip file
|
|
};
|
|
|
|
|
|
// rationale for n-archives per PATH entry:
|
|
// We need to be able to unmount specific paths (e.g. when switching mods).
|
|
// Don't want to remount everything (slow), or specify a mod tag when mounting
|
|
// (not this module's job). Instead, we include all archives in one path entry;
|
|
// the game keeps track of what path(s) it mounted for a mod,
|
|
// and unmounts those when needed.
|
|
|
|
struct PATH
|
|
{
|
|
struct PATH* next; // linked list
|
|
|
|
char* dir; // relative to root dir;
|
|
// points to space at end of this struct
|
|
|
|
size_t num_archives;
|
|
Handle archives[1];
|
|
|
|
// space allocated here for archive Handles + dir string
|
|
};
|
|
static PATH* path_list;
|
|
|
|
|
|
static void vfile_dtor(void* p)
|
|
{
|
|
VFILE* vf = (VFILE*)p;
|
|
|
|
if(vf->fd > 0)
|
|
{
|
|
close(vf->fd);
|
|
vf->fd = -1;
|
|
}
|
|
|
|
mem_free(vf->hm);
|
|
}
|
|
|
|
|
|
int vfs_set_root(const char* argv0, const char* root)
|
|
{
|
|
if(access(argv0, X_OK) != 0)
|
|
return -1;
|
|
|
|
char path[PATH_MAX+1];
|
|
path[PATH_MAX] = 0;
|
|
if(!realpath(argv0, path))
|
|
return -1;
|
|
|
|
// remove executable name
|
|
char* fn = strrchr(path, DIR_SEP);
|
|
if(!fn)
|
|
return -1;
|
|
*fn = 0;
|
|
|
|
chdir(path);
|
|
chdir(root);
|
|
|
|
return vfs_mount(".");
|
|
}
|
|
|
|
|
|
int vfs_mount(const char* path)
|
|
{
|
|
const size_t path_len = strlen(path);
|
|
if(path_len > VFS_MAX_PATH)
|
|
{
|
|
assert(!"vfs_mount_dir: path name is longer than VFS_MAX_PATH");
|
|
return -1;
|
|
}
|
|
|
|
// enumerate all archives in <path>
|
|
std::vector<std::string> archives;
|
|
DIR* dir = opendir(path);
|
|
struct dirent* ent;
|
|
while((ent = readdir(dir)))
|
|
{
|
|
struct stat s;
|
|
if(stat(ent->d_name, &s) < 0)
|
|
continue;
|
|
if(s.st_mode == S_IFREG) // regular file
|
|
archives.push_back(ent->d_name);
|
|
}
|
|
closedir(dir);
|
|
const size_t num_archives = archives.size();
|
|
|
|
// alloc search path entry (add to front)
|
|
const size_t archives_size = num_archives*sizeof(Handle);
|
|
const size_t entry_size = round_up((long)(sizeof(PATH)+archives_size+path_len+1), 32);
|
|
PATH* entry = (PATH*)mem_alloc(entry_size, 32, MEM_HEAP);
|
|
if(!entry)
|
|
return -1;
|
|
entry->next = path_list;
|
|
path_list = entry;
|
|
|
|
entry->dir = (char*)&entry->archives[0] + archives_size;
|
|
strcpy(entry->dir, path);
|
|
|
|
// add archives in alphabetical order
|
|
std::sort(archives.begin(), archives.end());
|
|
entry->num_archives = num_archives;
|
|
for(size_t i = 0; i < num_archives; i++)
|
|
entry->archives[i] = zip_open(archives[i].c_str());
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int vfs_umount(const char* path)
|
|
{
|
|
PATH** prev = &path_list;
|
|
PATH* entry = path_list;
|
|
while(entry)
|
|
{
|
|
// found
|
|
if(!strcmp(entry->dir, path))
|
|
{
|
|
// close all archives
|
|
for(size_t i = 0; i < entry->num_archives; i++)
|
|
h_free(entry->archives[i], H_ZARCHIVE);
|
|
|
|
// remove from list
|
|
*prev = entry->next;
|
|
mem_free(entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
prev = &entry->next;
|
|
entry = entry->next;
|
|
}
|
|
|
|
// not found
|
|
return -1;
|
|
}
|
|
|
|
|
|
// call func, passing the data argument, for each mounted path
|
|
// fail if its return value is < 0, stop if it returns 0
|
|
static int vfs_foreach_path(int (*func)(const char* path, Handle ha, void* data), const char* fn, void* data)
|
|
{
|
|
char buf[PATH_MAX+1]; buf[PATH_MAX] = 0;
|
|
|
|
for(PATH* entry = path_list; entry; entry = entry->next)
|
|
{
|
|
// dir
|
|
const char* path = fn;
|
|
if(entry->dir[0] != '.' || entry->dir[1] != '\0')
|
|
{
|
|
// only prepend dir if not "." (root) - "./" isn't portable
|
|
snprintf(buf, PATH_MAX, "%s/%s", entry->dir, fn);
|
|
path = buf;
|
|
}
|
|
|
|
int err = func(path, 0, data);
|
|
if(err <= 0)
|
|
return err;
|
|
|
|
// archive
|
|
for(size_t i = 0; i < entry->num_archives; i++)
|
|
{
|
|
err = func(path, entry->archives[i], data);
|
|
if(err <= 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return -1; // func never returned 0
|
|
}
|
|
|
|
|
|
|
|
static int realpath_cb(const char* path, Handle ha, void* data)
|
|
{
|
|
char* full_path = (char*)data;
|
|
struct stat s;
|
|
|
|
if(!path && !ha)
|
|
{
|
|
assert(0 && "realpath_cb: called with invalid path and archive handle");
|
|
return 1;
|
|
}
|
|
|
|
if(path)
|
|
{
|
|
if(!stat(path, &s))
|
|
{
|
|
strncpy(full_path, path, PATH_MAX);
|
|
return 0;
|
|
}
|
|
}
|
|
else if(ha)
|
|
{
|
|
if(!zip_stat(ha, path, &s))
|
|
{
|
|
zip_archive_info(ha, full_path, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int vfs_realpath(const char* fn, char* full_path)
|
|
{
|
|
return vfs_foreach_path(realpath_cb, fn, full_path);
|
|
}
|
|
|
|
|
|
static int stat_cb(const char* path, Handle ha, void* data)
|
|
{
|
|
struct stat* s = (struct stat*)data;
|
|
|
|
if(path)
|
|
return stat(path, s)? 1 : 0;
|
|
else if(ha)
|
|
return zip_stat(ha, path, s)? 1 : 0;
|
|
|
|
assert(0 && "stat_cb: called with invalid path and archive handle");
|
|
return 1;
|
|
}
|
|
|
|
int vfs_stat(const char* fn, struct stat* s)
|
|
{
|
|
return vfs_foreach_path(stat_cb, fn, s);
|
|
}
|
|
|
|
|
|
|
|
static int open_cb(const char* path, Handle ha, void* data)
|
|
{
|
|
struct stat s;
|
|
VFILE* vf = (VFILE*)data;
|
|
|
|
// normal file
|
|
if(path)
|
|
{
|
|
if(stat(path, &s) < 0)
|
|
return 1;
|
|
|
|
int fd = open(path, O_RDONLY);
|
|
if(fd < 0)
|
|
return 1;
|
|
|
|
vf->fd = fd;
|
|
vf->size = s.st_size;
|
|
}
|
|
// from archive
|
|
else if(ha)
|
|
{
|
|
ZFILE* zf = zip_lookup(ha, path);
|
|
if(!zf)
|
|
return 1;
|
|
|
|
Handle hm;
|
|
if(zip_archive_info(ha, 0, &hm) < 0)
|
|
return 1;
|
|
|
|
vf->ofs = zf->ofs;
|
|
vf->size = zf->csize;
|
|
vf->ucsize = zf->ucsize;
|
|
vf->fd = -1;
|
|
vf->hm = hm;
|
|
}
|
|
else
|
|
{
|
|
assert(0 && "open_cb: called with invalid path and archive handle");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Handle vfs_open(const char* fn)
|
|
{
|
|
u32 fn_hash = fnv_hash(fn, strlen(fn));
|
|
|
|
VFILE* vf;
|
|
Handle hv = h_alloc(fn_hash, H_VFILE, vfile_dtor, (void**)&vf);
|
|
if(!hv)
|
|
return 0;
|
|
|
|
// already open
|
|
if(vf->size)
|
|
return hv;
|
|
|
|
if(vfs_foreach_path(open_cb, fn, vf) < 0)
|
|
{
|
|
h_free(hv, H_VFILE);
|
|
return 0;
|
|
}
|
|
|
|
return hv;
|
|
}
|
|
|
|
|
|
|
|
const uint IDX_BITS = 4;
|
|
const uint NUM_SLOTS = 1ul << IDX_BITS;
|
|
const uint TAG_BITS = 32 - IDX_BITS;
|
|
static struct Slot
|
|
{
|
|
u32 tag; // = 0 <==> slot available
|
|
struct aiocb cb;
|
|
}
|
|
slots[NUM_SLOTS];
|
|
|
|
u32 vfs_start_read(const Handle hf, size_t& ofs, void** buf)
|
|
{
|
|
VFILE* vf = (VFILE*)h_user_data(hf, H_VFILE);
|
|
if(!vf)
|
|
return 0;
|
|
|
|
if(ofs >= vf->size)
|
|
return 0;
|
|
size_t bytes_left = vf->size - ofs;
|
|
|
|
// TODO: thread safety
|
|
|
|
// find a free slot
|
|
int i = 0;
|
|
Slot* s = slots;
|
|
for(; i < NUM_SLOTS; i++, s++)
|
|
if(!s->tag)
|
|
break;
|
|
if(i == NUM_SLOTS)
|
|
{
|
|
assert(!"vfs_start_read: too many active reads; increase NUM_SLOTS");
|
|
return 0;
|
|
}
|
|
|
|
// mark it in use
|
|
static u32 tag;
|
|
if(++tag == 1ul << TAG_BITS)
|
|
{
|
|
assert(!"vfs_start_read: tag overflow!");
|
|
tag = 1;
|
|
}
|
|
s->tag = tag;
|
|
|
|
struct aiocb* cb = &s->cb;
|
|
|
|
// use the buffer given (e.g. read directly into output buffer)
|
|
if(buf)
|
|
cb->aio_buf = *buf;
|
|
// allocate our own (reused for subsequent requests)
|
|
else
|
|
if(!cb->aio_buf)
|
|
{
|
|
cb->aio_buf = mem_alloc(64*KB, 64*KB, MEM_HEAP);
|
|
if(!cb->aio_buf)
|
|
return 0;
|
|
}
|
|
|
|
// align to 64 KB for speed
|
|
size_t rsize = 64*KB - (ofs & 0xffff); // min(~, bytes_left) - avoid warning
|
|
if(rsize > bytes_left)
|
|
rsize = bytes_left;
|
|
|
|
cb->aio_offset = (off_t)ofs;
|
|
cb->aio_nbytes = rsize;
|
|
aio_read(cb);
|
|
|
|
ofs += rsize;
|
|
if(buf)
|
|
(size_t&)*buf += rsize;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int vfs_finish_read(const u32 slot, void*& p, size_t& size)
|
|
{
|
|
p = 0;
|
|
size = 0;
|
|
|
|
const uint idx = slot & (NUM_SLOTS-1);
|
|
const u32 tag = slot >> IDX_BITS;
|
|
Slot* const s = &slots[idx];
|
|
if(s->tag != tag)
|
|
{
|
|
assert(!"vfs_finish_read: invalid slot");
|
|
return -1;
|
|
}
|
|
|
|
struct aiocb* cb = &s->cb;
|
|
|
|
// wait for read to complete
|
|
while(aio_error(cb) == -EINPROGRESS)
|
|
aio_suspend(&cb, 1, 0);
|
|
|
|
ssize_t bytes_read = aio_return(cb);
|
|
|
|
s->tag = 0; // free this slot
|
|
|
|
p = (void *)cb->aio_buf;
|
|
size = bytes_read;
|
|
|
|
return (bytes_read > 0)? 0 : -1;
|
|
}
|
|
|
|
|
|
|
|
Handle vfs_load(const char* fn, void*& _p, size_t& _size, bool dont_map)
|
|
{
|
|
_p = 0;
|
|
_size = 0;
|
|
|
|
Handle hf = vfs_open(fn);
|
|
if(!hf)
|
|
return 0;
|
|
|
|
VFILE* vf = (VFILE*)h_user_data(hf, H_VFILE);
|
|
|
|
const bool deflated = vf->fd == -1 && vf->size != vf->ucsize;
|
|
const size_t in_size = vf->size;
|
|
const size_t out_size = deflated? vf->ucsize : vf->size;
|
|
|
|
// already mapped or read
|
|
if(vf->hm)
|
|
{
|
|
MEM* m = (MEM*)h_user_data(vf->hm, H_MEM);
|
|
if(m)
|
|
{
|
|
assert(out_size == m->size && "vfs_load: mismatch between VFILE and MEM size");
|
|
|
|
_p = m->p;
|
|
_size = m->size;
|
|
return vf->hm;
|
|
}
|
|
else
|
|
assert(0 && "vfs_load: invalid MEM attached to VFILE");
|
|
}
|
|
|
|
// decide whether to map the file, or read it
|
|
MemType mt = MEM_MAPPED;
|
|
if(deflated || dont_map)
|
|
mt = MEM_POOL;
|
|
|
|
// allocate memory / map the file
|
|
Handle hm;
|
|
void* out = mem_alloc(out_size, 64*KB, mt, vf->fd, &hm);
|
|
if(!out)
|
|
{
|
|
vfs_close(hf);
|
|
return 0;
|
|
}
|
|
|
|
if(mt == MEM_MAPPED)
|
|
{
|
|
_p = out;
|
|
_size = out_size;
|
|
return vf->hm = hm;
|
|
}
|
|
|
|
// now we read the file in 64 KB chunks (double buffered);
|
|
// if in an archive, we inflate while waiting for the next chunk to finish
|
|
u32 slots[2];
|
|
int active_read = 0;
|
|
|
|
void* pos = out; // if not inflating, read directly into output buffer
|
|
size_t ofs = vf->ofs;
|
|
|
|
void* ctx;
|
|
if(deflated)
|
|
{
|
|
pos = 0; // read into separate buffer
|
|
ctx = zip_inflate_start(out, out_size);
|
|
}
|
|
|
|
bool first = true;
|
|
bool done = false;
|
|
|
|
for(;;)
|
|
{
|
|
// start reading next block
|
|
if(!done)
|
|
slots[active_read] = vfs_start_read(hf, ofs, &pos);
|
|
|
|
active_read ^= 1;
|
|
|
|
// process block read in previous iteration
|
|
if(!first)
|
|
{
|
|
void* p;
|
|
size_t bytes_read;
|
|
vfs_finish_read(slots[active_read], p, bytes_read);
|
|
|
|
// inflate what we read
|
|
if(deflated)
|
|
zip_inflate_process(ctx, p, bytes_read);
|
|
}
|
|
|
|
first = false;
|
|
if(done)
|
|
break;
|
|
// one more iteration to process the last pending block
|
|
if(ofs >= in_size)
|
|
done = true;
|
|
}
|
|
|
|
if(deflated)
|
|
{
|
|
if(zip_inflate_end(ctx) < 0)
|
|
{
|
|
mem_free(out);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
_p = out;
|
|
_size = out_size;
|
|
return vf->hm = hm;
|
|
}
|
|
|
|
|
|
int vfs_close(Handle h)
|
|
{
|
|
return h_free(h, H_VFILE);
|
|
}
|