2004-03-03 00:56:51 +01:00
|
|
|
// file layer on top of POSIX.
|
|
|
|
// provides streaming support and caching.
|
|
|
|
//
|
|
|
|
// Copyright (c) 2004 Jan Wassenberg
|
|
|
|
//
|
|
|
|
// This file 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 file 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-10-06 16:00:43 +02:00
|
|
|
#include "res.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
#include "file.h"
|
|
|
|
#include "detect.h"
|
|
|
|
#include "adts.h"
|
2004-08-05 21:20:45 +02:00
|
|
|
#include "sysdep/sysdep.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-04 14:41:53 +02:00
|
|
|
#include <vector>
|
|
|
|
#include <algorithm>
|
2004-06-11 00:24:03 +02:00
|
|
|
|
2004-06-09 15:49:32 +02:00
|
|
|
#include <string>
|
2004-06-08 17:16:50 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// block := power-of-two sized chunk of a file.
|
|
|
|
// all transfers are expanded to naturally aligned, whole blocks
|
|
|
|
// (this makes caching parts of files feasible; it is also much faster
|
|
|
|
// for some aio implementations, e.g. wposix).
|
2005-01-23 18:54:20 +01:00
|
|
|
const size_t BLOCK_SIZE_LOG2 = 16; // 2**16 = 64 KiB
|
2004-03-03 00:56:51 +01:00
|
|
|
const size_t BLOCK_SIZE = 1ul << BLOCK_SIZE_LOG2;
|
|
|
|
|
2004-08-05 16:01:49 +02:00
|
|
|
const size_t SECTOR_SIZE = 4096;
|
|
|
|
// reasonable guess. if too small, aio will do alignment.
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
// rationale for aio, instead of only using mmap:
|
2004-06-08 17:16:50 +02:00
|
|
|
// - parallelism: instead of just waiting for the transfer to complete,
|
2004-03-03 00:56:51 +01:00
|
|
|
// other work can be done in the meantime.
|
|
|
|
// example: decompressing from a Zip archive is practically free,
|
|
|
|
// because we inflate one block while reading the next.
|
|
|
|
// - throughput: with aio, the drive always has something to do, as opposed
|
|
|
|
// to read requests triggered by the OS for mapped files, which come
|
|
|
|
// in smaller chunks. this leads to much higher transfer rates.
|
|
|
|
// - memory: when used with VFS, aio makes better use of a file cache.
|
|
|
|
// data is generally compressed in an archive. a cache should store the
|
|
|
|
// decompressed and decoded (e.g. TGA color swapping) data; mmap would
|
|
|
|
// keep the original, compressed data in memory, which doesn't help.
|
|
|
|
// we bypass the OS file cache via aio, and store partial blocks here (*);
|
|
|
|
// higher level routines will cache the actual useful data.
|
|
|
|
// * requests for part of a block are usually followed by another.
|
|
|
|
|
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
|
2005-02-27 15:33:59 +01:00
|
|
|
// is s2 a subpath of s1, or vice versa? used by VFS and wdir_watch.
|
|
|
|
// works for portable and native paths.
|
|
|
|
bool file_is_subpath(const char* s1, const char* s2)
|
|
|
|
{
|
|
|
|
// make sure s1 is the shorter string
|
|
|
|
if(strlen(s1) > strlen(s2))
|
|
|
|
std::swap(s1, s2);
|
|
|
|
|
|
|
|
int c1 = 0, last_c1, c2;
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
last_c1 = c1;
|
|
|
|
c1 = *s1++, c2 = *s2++;
|
|
|
|
|
|
|
|
// end of s1 reached:
|
|
|
|
if(c1 == '\0')
|
|
|
|
{
|
|
|
|
// s1 matched s2 up until:
|
|
|
|
if((c2 == '\0') || // its end (i.e. they're equal length)
|
|
|
|
(c2 == '/' || c2 == DIR_SEP) || // start of next component
|
|
|
|
(last_c1 == '/' || last_c1 == DIR_SEP)) // ", but both have a trailing slash
|
|
|
|
// => is subpath
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mismatch => is not subpath
|
|
|
|
if(c1 != c2)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
|
2004-07-10 23:25:35 +02:00
|
|
|
enum Conversion
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-06-08 17:16:50 +02:00
|
|
|
TO_NATIVE,
|
|
|
|
TO_PORTABLE
|
|
|
|
};
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-07-09 21:57:59 +02:00
|
|
|
static int convert_path(char* dst, const char* src, Conversion conv = TO_NATIVE)
|
2004-06-08 17:16:50 +02:00
|
|
|
{
|
2004-12-01 19:44:38 +01:00
|
|
|
// DIR_SEP is assumed to be a single character!
|
2004-06-11 04:14:18 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
const char* s = src;
|
|
|
|
char* d = dst;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
char from = DIR_SEP, to = '/';
|
|
|
|
if(conv == TO_NATIVE)
|
|
|
|
from = '/', to = DIR_SEP;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
len++;
|
|
|
|
if(len >= PATH_MAX)
|
2004-12-01 19:44:38 +01:00
|
|
|
return ERR_PATH_LENGTH;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
char c = *s++;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
if(c == from)
|
|
|
|
c = to;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
*d++ = c;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
// end of string - done
|
2004-05-06 19:14:30 +02:00
|
|
|
if(c == '\0')
|
2004-06-08 17:16:50 +02:00
|
|
|
return 0;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-07-09 21:57:59 +02:00
|
|
|
// set by file_rel_chdir
|
2004-06-08 17:16:50 +02:00
|
|
|
static char n_root_dir[PATH_MAX];
|
|
|
|
static size_t n_root_dir_len;
|
|
|
|
|
2004-07-09 21:57:59 +02:00
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
// return the native equivalent of the given relative portable path
|
2004-07-09 21:57:59 +02:00
|
|
|
// (i.e. convert all '/' to the platform's directory separator)
|
|
|
|
// makes sure length < PATH_MAX.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_make_native_path(const char* path, char* n_path)
|
2004-07-09 21:57:59 +02:00
|
|
|
{
|
2004-12-01 19:44:38 +01:00
|
|
|
return convert_path(n_path, path, TO_NATIVE);
|
2004-07-09 21:57:59 +02:00
|
|
|
}
|
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
// return the portable equivalent of the given relative native path
|
|
|
|
// (i.e. convert the platform's directory separators to '/')
|
|
|
|
// makes sure length < PATH_MAX.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_make_portable_path(const char* n_path, char* path)
|
2004-07-09 21:57:59 +02:00
|
|
|
{
|
2004-12-01 19:44:38 +01:00
|
|
|
return convert_path(path, n_path, TO_PORTABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// return the native equivalent of the given portable path
|
|
|
|
// (i.e. convert all '/' to the platform's directory separator).
|
|
|
|
// also prepends current directory => n_full_path is absolute.
|
|
|
|
// makes sure length < PATH_MAX.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_make_full_native_path(const char* path, char* n_full_path)
|
2004-12-01 19:44:38 +01:00
|
|
|
{
|
|
|
|
strcpy(n_full_path, n_root_dir);
|
|
|
|
return convert_path(n_full_path+n_root_dir_len, path, TO_NATIVE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return the portable equivalent of the given relative native path
|
|
|
|
// (i.e. convert the platform's directory separators to '/')
|
|
|
|
// n_full_path is absolute; if it doesn't match the current dir, fail.
|
|
|
|
// (note: portable paths are always relative to file_rel_chdir root).
|
|
|
|
// makes sure length < PATH_MAX.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_make_full_portable_path(const char* n_full_path, char* path)
|
2004-12-01 19:44:38 +01:00
|
|
|
{
|
|
|
|
if(strncmp(n_full_path, n_root_dir, n_root_dir_len) != 0)
|
2004-07-09 21:57:59 +02:00
|
|
|
return -1;
|
2004-12-01 19:44:38 +01:00
|
|
|
return convert_path(path, n_full_path+n_root_dir_len, TO_PORTABLE);
|
2004-07-09 21:57:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-05-17 15:23:39 +02:00
|
|
|
// set current directory to rel_path, relative to the path to the executable,
|
|
|
|
// which is taken from argv0.
|
|
|
|
//
|
|
|
|
// example: executable in "$install_dir/system"; desired root dir is
|
|
|
|
// "$install_dir/data" => rel_path = "../data".
|
|
|
|
//
|
|
|
|
// this is necessary because the current directory is unknown at startup
|
|
|
|
// (e.g. it isn't set when invoked via batch file), and this is the
|
|
|
|
// easiest portable way to find our install directory.
|
|
|
|
//
|
|
|
|
// can only be called once, by design (see below). rel_path is trusted.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_rel_chdir(const char* argv0, const char* rel_path)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-17 15:23:39 +02:00
|
|
|
const char* msg = 0;
|
|
|
|
|
|
|
|
// security check: only allow attempting to chdir once, so that malicious
|
|
|
|
// code cannot circumvent the VFS checks that disallow access to anything
|
|
|
|
// above the current directory (set here).
|
|
|
|
// this routine is called early at startup, so any subsequent attempts
|
|
|
|
// are likely bogus.
|
2004-05-06 19:14:30 +02:00
|
|
|
static bool already_attempted;
|
|
|
|
if(already_attempted)
|
|
|
|
{
|
2004-05-17 15:23:39 +02:00
|
|
|
msg = "called more than once";
|
|
|
|
goto fail;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
already_attempted = true;
|
|
|
|
|
2004-05-18 02:38:39 +02:00
|
|
|
{
|
|
|
|
|
2004-08-05 21:20:45 +02:00
|
|
|
// get full path to executable
|
|
|
|
char n_path[PATH_MAX];
|
|
|
|
// .. first try safe, but system-dependent version
|
|
|
|
if(get_executable_name(n_path, PATH_MAX) < 0)
|
2004-05-27 03:11:21 +02:00
|
|
|
{
|
2004-08-05 21:20:45 +02:00
|
|
|
// .. failed; use argv[0]
|
|
|
|
if(!realpath(argv0, n_path))
|
|
|
|
goto fail;
|
2004-05-27 03:11:21 +02:00
|
|
|
}
|
|
|
|
|
2004-08-05 21:20:45 +02:00
|
|
|
// make sure it's valid
|
2005-02-02 04:27:40 +01:00
|
|
|
if(access(n_path, X_OK) < 0)
|
2004-05-17 15:23:39 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
// strip executable name and append rel_path
|
|
|
|
char* fn = strrchr(n_path, DIR_SEP);
|
2004-05-06 19:14:30 +02:00
|
|
|
if(!fn)
|
2004-05-17 15:23:39 +02:00
|
|
|
{
|
|
|
|
msg = "realpath returned an invalid path?";
|
|
|
|
goto fail;
|
|
|
|
}
|
2004-12-07 02:19:10 +01:00
|
|
|
CHECK_ERR(file_make_native_path(rel_path, fn+1));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-17 15:23:39 +02:00
|
|
|
if(chdir(n_path) < 0)
|
|
|
|
goto fail;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-08 17:16:50 +02:00
|
|
|
// get actual root dir - previous n_path may include ..
|
|
|
|
// (slight optimization, speeds up path lookup)
|
2005-01-30 17:13:15 +01:00
|
|
|
if(getcwd(n_root_dir, sizeof(n_root_dir)) == 0)
|
2004-06-08 17:16:50 +02:00
|
|
|
goto fail;
|
|
|
|
n_root_dir_len = strlen(n_root_dir)+1; // +1 for trailing DIR_SEP
|
|
|
|
n_root_dir[n_root_dir_len-1] = DIR_SEP;
|
|
|
|
// append to simplify code that uses n_root_dir
|
|
|
|
// already 0-terminated, since it's static
|
|
|
|
|
2004-05-17 15:23:39 +02:00
|
|
|
return 0;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-18 02:38:39 +02:00
|
|
|
}
|
|
|
|
|
2004-05-17 15:23:39 +02:00
|
|
|
fail:
|
|
|
|
debug_warn("file_rel_chdir failed");
|
|
|
|
if(msg)
|
|
|
|
{
|
|
|
|
debug_out("file_rel_chdir: %s\n", msg);
|
|
|
|
return -1;
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-17 15:23:39 +02:00
|
|
|
return -errno;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// need to store entries returned by readdir so they can be sorted.
|
|
|
|
struct DirEnt
|
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
const std::string name;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-12-07 02:19:10 +01:00
|
|
|
// store only required fields from struct stat to save space.
|
|
|
|
// in order of decl in VC2003 sys/stat.h.
|
|
|
|
mode_t st_mode;
|
|
|
|
off_t st_size;
|
2004-12-19 00:30:28 +01:00
|
|
|
// glibc compat hack - they have st_mtime #defined to st_mtim.tv_sec
|
|
|
|
#ifdef st_mtime
|
|
|
|
struct timespec st_mtim;
|
|
|
|
#else
|
2004-12-07 02:19:10 +01:00
|
|
|
time_t st_mtime;
|
2004-12-19 00:30:28 +01:00
|
|
|
#endif
|
2004-06-21 18:29:47 +02:00
|
|
|
|
2004-12-07 02:19:10 +01:00
|
|
|
DirEnt(const char* _name, mode_t _st_mode, off_t _st_size, time_t _st_mtime)
|
|
|
|
: name(_name)
|
|
|
|
{
|
|
|
|
st_mode = _st_mode;
|
|
|
|
st_size = _st_size;
|
|
|
|
st_mtime = _st_mtime;
|
|
|
|
}
|
|
|
|
|
|
|
|
// no copy ctor, since some members are const
|
2004-06-21 18:29:47 +02:00
|
|
|
private:
|
|
|
|
DirEnt& operator=(const DirEnt&);
|
2004-05-06 19:14:30 +02:00
|
|
|
};
|
|
|
|
|
2004-06-02 22:41:05 +02:00
|
|
|
// pointer to DirEnt: faster sorting, but more allocs.
|
2004-05-16 05:31:29 +02:00
|
|
|
typedef std::vector<const DirEnt*> DirEnts;
|
2004-12-07 02:19:10 +01:00
|
|
|
typedef DirEnts::const_iterator DirEntCIt;
|
2004-12-01 19:44:38 +01:00
|
|
|
typedef DirEnts::reverse_iterator DirEntRIt;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-12-10 00:12:02 +01:00
|
|
|
static bool dirent_less(const DirEnt* d1, const DirEnt* d2)
|
2004-05-16 05:31:29 +02:00
|
|
|
{ return d1->name.compare(d2->name) < 0; }
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-08-11 22:23:22 +02:00
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
// call <cb> for each file and subdirectory in <dir> (alphabetical order),
|
2004-12-07 02:19:10 +01:00
|
|
|
// passing the entry name (not full path!), stat info, and <user>.
|
2004-12-01 19:44:38 +01:00
|
|
|
//
|
|
|
|
// first builds a list of entries (sorted) and remembers if an error occurred.
|
|
|
|
// if <cb> returns non-zero, abort immediately and return that; otherwise,
|
|
|
|
// return first error encountered while listing files, or 0 on success.
|
2004-08-05 16:01:49 +02:00
|
|
|
//
|
2004-08-11 22:23:22 +02:00
|
|
|
// rationale:
|
|
|
|
// this makes file_enum and zip_enum slightly incompatible, since zip_enum
|
|
|
|
// returns the full path. that's necessary because VFS add_dirent_cb
|
|
|
|
// has no other way of determining what VFS dir a Zip file is in,
|
|
|
|
// since zip_enum enumerates all files in the archive (not only those
|
|
|
|
// in a given dir). no big deal though, since add_dirent_cb has to
|
|
|
|
// special-case Zip files anyway.
|
|
|
|
// the advantage here is simplicity, and sparing callbacks the trouble
|
|
|
|
// of converting from/to native path (we just give 'em the dirent name).
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_enum(const char* dir, const FileCB cb, const uintptr_t user)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-08-11 22:23:22 +02:00
|
|
|
// full path for stat
|
|
|
|
char n_path[PATH_MAX];
|
|
|
|
n_path[PATH_MAX-1] = '\0';
|
2004-05-06 19:14:30 +02:00
|
|
|
// will append filename to this, hence "path".
|
|
|
|
// 0-terminate simplifies filename strncpy below.
|
2004-12-01 19:44:38 +01:00
|
|
|
CHECK_ERR(file_make_native_path(dir, n_path));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-05-16 05:31:29 +02:00
|
|
|
// all entries are enumerated (adding to this container),
|
|
|
|
// std::sort-ed, then all passed to cb.
|
2004-05-06 19:14:30 +02:00
|
|
|
DirEnts dirents;
|
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
int stat_err = 0; // first error encountered by stat()
|
|
|
|
int cb_err = 0; // first error returned by cb
|
2004-05-06 19:14:30 +02:00
|
|
|
int ret;
|
|
|
|
|
2004-12-09 21:17:09 +01:00
|
|
|
// [16.6ms]
|
2004-12-10 00:12:02 +01:00
|
|
|
DIR* os_dir = opendir(n_path);
|
2004-05-06 19:14:30 +02:00
|
|
|
if(!os_dir)
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_PATH_NOT_FOUND;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
// will append file names here
|
|
|
|
const size_t n_path_len = strlen(n_path);
|
|
|
|
char* fn_start = n_path + n_path_len;
|
|
|
|
*fn_start++ = DIR_SEP;
|
|
|
|
|
2004-05-16 05:31:29 +02:00
|
|
|
struct dirent* os_ent;
|
|
|
|
while((os_ent = readdir(os_dir)))
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
const char* fn = os_ent->d_name;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-08-11 22:23:22 +02:00
|
|
|
strncpy(fn_start, fn, PATH_MAX-n_path_len-1);
|
2004-12-07 02:19:10 +01:00
|
|
|
// need path for stat (we only have filename ATM).
|
2004-12-09 21:17:09 +01:00
|
|
|
// this is actually minimally faster than changing directory!
|
2004-05-16 05:31:29 +02:00
|
|
|
// BTW, direct strcpy is faster than path_append -
|
|
|
|
// we save a strlen every iteration.
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2005-01-07 02:10:00 +01:00
|
|
|
struct stat s;
|
2004-12-09 21:17:09 +01:00
|
|
|
#ifdef _WIN32
|
2005-01-07 02:10:00 +01:00
|
|
|
// wposix readdir has enough information to return dirent
|
|
|
|
// status directly (much faster than calling stat).
|
|
|
|
ret = readdir_stat_np(os_dir, &s);
|
2004-12-09 21:17:09 +01:00
|
|
|
#else
|
2005-01-07 02:10:00 +01:00
|
|
|
// no need to go through file_stat - we already have the native path.
|
2004-12-09 21:17:09 +01:00
|
|
|
// [290ms]
|
2004-05-06 19:14:30 +02:00
|
|
|
ret = stat(n_path, &s);
|
2005-01-07 02:10:00 +01:00
|
|
|
#endif
|
2004-05-06 19:14:30 +02:00
|
|
|
if(ret < 0)
|
|
|
|
{
|
|
|
|
if(stat_err == 0)
|
2004-12-01 19:44:38 +01:00
|
|
|
stat_err = ret; // first error (see decl)
|
2004-05-06 19:14:30 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// dir
|
2005-01-07 02:10:00 +01:00
|
|
|
if(S_ISDIR(s.st_mode))
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
|
|
|
// skip . and ..
|
|
|
|
if(fn[0] == '.' && (fn[1] == '\0' || (fn[1] == '.' && fn[2] == '\0')))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// skip if neither dir nor file
|
2005-01-07 02:10:00 +01:00
|
|
|
else if(!S_ISREG(s.st_mode))
|
2004-05-06 19:14:30 +02:00
|
|
|
continue;
|
|
|
|
|
2004-12-09 21:17:09 +01:00
|
|
|
// [21ms]
|
2005-01-07 02:10:00 +01:00
|
|
|
dirents.push_back(new DirEnt(fn, s.st_mode, s.st_size, s.st_mtime));
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
2004-12-09 21:17:09 +01:00
|
|
|
|
|
|
|
// [2.5ms]
|
2004-05-06 19:14:30 +02:00
|
|
|
closedir(os_dir);
|
|
|
|
|
2004-12-09 21:17:09 +01:00
|
|
|
// [5.8ms]
|
2004-05-06 19:14:30 +02:00
|
|
|
std::sort(dirents.begin(), dirents.end(), dirent_less);
|
|
|
|
|
2004-12-07 02:19:10 +01:00
|
|
|
struct stat s;
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
for(DirEntCIt it = dirents.begin(); it != dirents.end(); ++it)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-12-10 00:12:02 +01:00
|
|
|
const DirEnt* ent = *it;
|
2004-05-16 05:31:29 +02:00
|
|
|
const char* name_c = ent->name.c_str();
|
2004-12-07 02:19:10 +01:00
|
|
|
s.st_mode = ent->st_mode;
|
|
|
|
s.st_size = ent->st_size;
|
|
|
|
s.st_mtime = ent->st_mtime;
|
|
|
|
ret = cb(name_c, &s, user);
|
2004-12-01 19:44:38 +01:00
|
|
|
if(ret != 0)
|
|
|
|
{
|
|
|
|
cb_err = ret; // first error (since we now abort)
|
|
|
|
break;
|
|
|
|
}
|
2004-07-28 19:53:45 +02:00
|
|
|
}
|
2004-05-16 05:31:29 +02:00
|
|
|
|
2004-12-09 21:17:09 +01:00
|
|
|
|
2004-12-01 19:44:38 +01:00
|
|
|
// free all memory (can't do in loop above because it can be aborted).
|
2004-12-09 21:17:09 +01:00
|
|
|
// [10.4ms]
|
2004-12-01 19:44:38 +01:00
|
|
|
for(DirEntRIt rit = dirents.rbegin(); rit != dirents.rend(); ++rit)
|
|
|
|
delete *rit;
|
|
|
|
|
|
|
|
if(cb_err != 0)
|
2004-05-06 19:14:30 +02:00
|
|
|
return cb_err;
|
|
|
|
return stat_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-27 02:33:20 +02:00
|
|
|
// get file status. output param is zeroed on error.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_stat(const char* path, struct stat* s)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-08-27 02:33:20 +02:00
|
|
|
memset(s, 0, sizeof(struct stat));
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
char n_path[PATH_MAX+1];
|
2004-12-07 02:19:10 +01:00
|
|
|
CHECK_ERR(file_make_native_path(path, n_path));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
|
|
|
return stat(n_path, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// file open/close
|
2004-05-06 19:14:30 +02:00
|
|
|
// stores information about file (e.g. size) in File struct
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
// interface rationale:
|
|
|
|
// - this module depends on the handle manager for IO management,
|
|
|
|
// but should be useable without the VFS (even if they are designed
|
|
|
|
// to work together).
|
|
|
|
// - allocating a Handle for the file info would solve several problems
|
|
|
|
// (see below), but we don't want to allocate 2..3 (VFS, file, Zip file)
|
|
|
|
// for every file opened - that'd add up quickly.
|
|
|
|
// the Files are always freed at exit though, since they're part of
|
|
|
|
// VFile handles in the VFS.
|
|
|
|
// - we want the VFS open logic to be triggered on file invalidate
|
|
|
|
// (if the dev. file is deleted, we should use what's in the archives).
|
2004-08-09 18:46:57 +02:00
|
|
|
// we don't want to make this module depend on VFS, so we don't
|
|
|
|
// have access to the file location DB; VFS needs to allocate the handle.
|
2004-03-03 00:56:51 +01:00
|
|
|
// - no problem exposing our internals via File struct -
|
|
|
|
// we're only used by the VFS and Zip modules. don't bother making
|
|
|
|
// an opaque struct - that'd have to be kept in sync with the real thing.
|
|
|
|
// - when Zip opens its archives via file_open, a handle isn't needed -
|
|
|
|
// the Zip module hides its File struct (required to close the file),
|
|
|
|
// and the Handle approach doesn't guard against some idiot calling
|
2004-08-09 18:46:57 +02:00
|
|
|
// close(our_fd_value) directly, either.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
// marker for File struct, to make sure it's valid
|
|
|
|
#ifdef PARANOIA
|
|
|
|
static const u32 FILE_MAGIC = FOURCC('F','I','L','E');
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2004-12-10 00:12:02 +01:00
|
|
|
static int file_validate(const uint line, File* f)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
|
|
|
const char* msg = "";
|
|
|
|
int err = -1;
|
|
|
|
|
|
|
|
if(!f)
|
|
|
|
{
|
|
|
|
msg = "File* parameter = 0";
|
|
|
|
err = ERR_INVALID_PARAM;
|
|
|
|
}
|
|
|
|
#ifdef PARANOIA
|
|
|
|
else if(f->magic != FILE_MAGIC)
|
|
|
|
msg = "File corrupted (magic field incorrect)";
|
|
|
|
#endif
|
|
|
|
else if(f->fd < 0)
|
|
|
|
msg = "File fd invalid (< 0)";
|
2004-06-03 02:17:24 +02:00
|
|
|
else if((f->mapping != 0) ^ (f->map_refs != 0))
|
|
|
|
msg = "File mapping without refs";
|
2004-03-03 00:56:51 +01:00
|
|
|
#ifndef NDEBUG
|
|
|
|
else if(!f->fn_hash)
|
|
|
|
msg = "File fn_hash not set";
|
|
|
|
#endif
|
|
|
|
// everything is OK
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// failed somewhere - err is the error code,
|
|
|
|
// or -1 if not set specifically above.
|
|
|
|
debug_out("file_validate at line %d failed: %s\n", line, msg);
|
2004-05-08 03:11:51 +02:00
|
|
|
debug_warn("file_validate failed");
|
2004-03-03 00:56:51 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define CHECK_FILE(f)\
|
|
|
|
do\
|
|
|
|
{\
|
|
|
|
int err = file_validate(__LINE__, f);\
|
|
|
|
if(err < 0)\
|
|
|
|
return err;\
|
|
|
|
}\
|
|
|
|
while(0);
|
|
|
|
|
|
|
|
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_open(const char* p_fn, const uint flags, File* f)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-09-19 17:51:12 +02:00
|
|
|
// zero output param in case we fail below.
|
2004-03-03 00:56:51 +01:00
|
|
|
memset(f, 0, sizeof(File));
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
char n_fn[PATH_MAX];
|
2004-12-07 02:19:10 +01:00
|
|
|
CHECK_ERR(file_make_native_path(p_fn, n_fn));
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
if(!f)
|
|
|
|
goto invalid_f;
|
|
|
|
// jump to CHECK_FILE post-check, which will handle this.
|
|
|
|
|
|
|
|
{
|
|
|
|
// don't stat if opening for writing - the file may not exist yet
|
2004-06-02 22:41:05 +02:00
|
|
|
off_t size = 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-07-05 04:30:53 +02:00
|
|
|
int oflag = O_RDONLY;
|
2004-03-03 00:56:51 +01:00
|
|
|
if(flags & FILE_WRITE)
|
2004-07-05 04:30:53 +02:00
|
|
|
oflag = O_WRONLY | O_CREAT;
|
2004-03-03 00:56:51 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
struct stat s;
|
2004-05-06 19:14:30 +02:00
|
|
|
if(stat(n_fn, &s) < 0)
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_FILE_NOT_FOUND;
|
2004-07-05 04:30:53 +02:00
|
|
|
if(!S_ISREG(s.st_mode))
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_NOT_FILE;
|
2004-03-03 00:56:51 +01:00
|
|
|
size = s.st_size;
|
|
|
|
}
|
|
|
|
|
2004-07-08 17:10:26 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
oflag |= O_BINARY;
|
|
|
|
#endif
|
|
|
|
|
2004-07-10 23:25:35 +02:00
|
|
|
int fd = open(n_fn, oflag, S_IRWXO|S_IRWXU|S_IRWXG);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(fd < 0)
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_FILE_ACCESS;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#ifdef PARANOIA
|
|
|
|
f->magic = FILE_MAGIC;
|
|
|
|
#endif
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
f->flags = flags;
|
|
|
|
f->size = size;
|
|
|
|
f->fn_hash = fnv_hash(n_fn); // copy filename instead?
|
|
|
|
f->mapping = 0;
|
|
|
|
f->map_refs = 0;
|
|
|
|
f->fd = fd;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
invalid_f:
|
2004-06-03 02:17:24 +02:00
|
|
|
CHECK_FILE(f);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_close(File* f)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
|
|
|
CHECK_FILE(f);
|
|
|
|
|
2004-06-03 02:27:50 +02:00
|
|
|
// make sure the mapping is actually freed,
|
|
|
|
// regardless of how many references remain.
|
|
|
|
if(f->map_refs > 1)
|
|
|
|
f->map_refs = 1;
|
2004-08-09 18:46:57 +02:00
|
|
|
if(f->mapping) // only free if necessary (unmap complains if not mapped)
|
|
|
|
file_unmap(f);
|
2004-06-03 02:27:50 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// (check fd to avoid BoundsChecker warning about invalid close() param)
|
|
|
|
if(f->fd != -1)
|
|
|
|
{
|
|
|
|
close(f->fd);
|
|
|
|
f->fd = -1;
|
|
|
|
}
|
|
|
|
f->size = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// async I/O
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2004-08-19 14:02:58 +02:00
|
|
|
// rationale:
|
|
|
|
// asynchronous IO routines don't cache; they're just a thin AIO wrapper.
|
|
|
|
// it's taken care of by file_io, which splits transfers into blocks
|
|
|
|
// and keeps temp buffers in memory (not user-allocated, because they
|
|
|
|
// might pull the rug out from under us at any time).
|
|
|
|
//
|
|
|
|
// doing so here would be more complicated: would have to handle "forwarding",
|
|
|
|
// i.e. recognizing that the desired block has been issued, but isn't yet
|
|
|
|
// complete. file_io also knows more about whether a block should be cached.
|
|
|
|
//
|
|
|
|
// disadvantages:
|
|
|
|
// - streamed data will always be read from disk. no problem, because
|
|
|
|
// such data (e.g. music, long speech) is unlikely to be used again soon.
|
|
|
|
// - prefetching (issuing the next few blocks from an archive during idle
|
|
|
|
// time, so that future out-of-order reads don't need to seek) isn't
|
|
|
|
// possible in the background (unless via thread, but that's discouraged).
|
|
|
|
// the utility is questionable, though: how to prefetch so as not to delay
|
|
|
|
// real IOs? can't determine "idle time" without completion notification,
|
|
|
|
// which is hard.
|
|
|
|
// we could get the same effect by bridging small gaps in file_io,
|
|
|
|
// and rearranging files in the archive in order of access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// starts transferring to/from the given buffer.
|
|
|
|
// no attempt is made at aligning or padding the transfer.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_start_io(File* f, off_t ofs, size_t size, void* p, FileIO* io)
|
2004-08-15 23:50:29 +02:00
|
|
|
{
|
2004-09-19 17:51:12 +02:00
|
|
|
// zero output param in case we fail below.
|
|
|
|
memset(io, 0, sizeof(FileIO));
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
#ifdef PARANOIA
|
2004-09-22 17:20:58 +02:00
|
|
|
debug_out("file_start_io: ofs=%d size=%d\n", ofs, size);
|
2004-08-18 04:12:52 +02:00
|
|
|
#endif
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-15 23:50:29 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// check params
|
|
|
|
//
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
CHECK_FILE(f);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
if(!size || !p || !io)
|
2004-03-03 00:56:51 +01:00
|
|
|
return ERR_INVALID_PARAM;
|
2004-08-18 04:12:52 +02:00
|
|
|
|
|
|
|
const bool is_write = (f->flags & FILE_WRITE) != 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
// cut off at EOF.
|
2004-07-08 16:30:35 +02:00
|
|
|
if(!is_write)
|
|
|
|
{
|
|
|
|
// avoid min() due to type conversion warnings.
|
2004-08-18 04:12:52 +02:00
|
|
|
const off_t bytes_left = f->size - ofs;
|
2004-07-08 16:30:35 +02:00
|
|
|
if(bytes_left < 0)
|
2004-08-15 23:50:29 +02:00
|
|
|
{
|
|
|
|
debug_warn("file_start_io: EOF");
|
2004-07-08 16:30:35 +02:00
|
|
|
return ERR_EOF;
|
2004-08-15 23:50:29 +02:00
|
|
|
}
|
2004-08-18 04:12:52 +02:00
|
|
|
if((off_t)size > bytes_left)
|
|
|
|
size = (size_t)bytes_left;
|
2004-07-08 16:30:35 +02:00
|
|
|
// guaranteed to fit, since size was > bytes_left
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// set the "I/O context", a pointer to the (newly allocated) aiocb.
|
|
|
|
// we can't store the whole aiocb in a struct - glibc's version is
|
|
|
|
// 144 bytes large. we don't currently need anything else (since this
|
|
|
|
// code is only a thin aio wrapper); if that changes, instead return a
|
|
|
|
// pointer to a struct containing the aiocb*.
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
aiocb* cb = (aiocb*)malloc(sizeof(aiocb));
|
2004-09-22 17:20:58 +02:00
|
|
|
memset(cb, 0, sizeof(aiocb));
|
2004-08-18 04:12:52 +02:00
|
|
|
if(!cb)
|
|
|
|
return ERR_NO_MEM;
|
2004-09-19 17:51:12 +02:00
|
|
|
io->cb = cb;
|
2004-07-08 16:30:35 +02:00
|
|
|
|
2004-07-31 04:02:44 +02:00
|
|
|
// send off async read/write request
|
2004-08-15 23:50:29 +02:00
|
|
|
cb->aio_lio_opcode = is_write? LIO_WRITE : LIO_READ;
|
2004-08-18 04:12:52 +02:00
|
|
|
cb->aio_buf = p;
|
2004-07-31 04:02:44 +02:00
|
|
|
cb->aio_fildes = f->fd;
|
|
|
|
cb->aio_offset = ofs;
|
|
|
|
cb->aio_nbytes = size;
|
2004-09-22 17:20:58 +02:00
|
|
|
#ifdef PARANOIA
|
|
|
|
debug_out("file_start_io: io=%p nbytes=%d\n", io, cb->aio_nbytes);
|
|
|
|
#endif
|
2004-08-18 04:12:52 +02:00
|
|
|
int err = lio_listio(LIO_NOWAIT, &cb, 1, (struct sigevent*)0);
|
2004-03-03 00:56:51 +01:00
|
|
|
if(err < 0)
|
|
|
|
{
|
2004-09-22 17:20:58 +02:00
|
|
|
#ifdef PARANOIA
|
|
|
|
debug_out("file_start_io: lio_listio: %d, %d[%s]\n", err, errno, strerror(errno));
|
|
|
|
#endif
|
2004-09-19 17:51:12 +02:00
|
|
|
file_discard_io(io);
|
2004-03-03 00:56:51 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2004-08-15 23:50:29 +02:00
|
|
|
return 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-15 23:50:29 +02:00
|
|
|
// indicates if the IO referenced by <io> has completed.
|
2004-08-10 17:57:35 +02:00
|
|
|
// return value: 0 if pending, 1 if complete, < 0 on error.
|
2004-09-19 17:51:12 +02:00
|
|
|
int file_io_complete(FileIO* io)
|
2004-08-10 17:57:35 +02:00
|
|
|
{
|
2004-09-19 17:51:12 +02:00
|
|
|
aiocb* cb = (aiocb*)io->cb;
|
2004-08-18 04:12:52 +02:00
|
|
|
int ret = aio_error(cb);
|
2004-08-10 17:57:35 +02:00
|
|
|
if(ret == EINPROGRESS)
|
|
|
|
return 0;
|
|
|
|
if(ret == 0)
|
|
|
|
return 1;
|
2004-10-05 15:07:25 +02:00
|
|
|
|
2004-08-10 17:57:35 +02:00
|
|
|
debug_warn("file_io_complete: unexpected aio_error return");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-09-19 17:51:12 +02:00
|
|
|
int file_wait_io(FileIO* io, void*& p, size_t& size)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-09-22 17:20:58 +02:00
|
|
|
debug_out("file_wait_io: hio=%p\n", io);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-07-31 04:02:44 +02:00
|
|
|
// zero output params in case something (e.g. H_DEREF) fails.
|
2004-03-03 00:56:51 +01:00
|
|
|
p = 0;
|
|
|
|
size = 0;
|
|
|
|
|
2004-09-19 17:51:12 +02:00
|
|
|
aiocb* cb = (aiocb*)io->cb;
|
2004-08-18 04:12:52 +02:00
|
|
|
|
|
|
|
// wait for transfer to complete.
|
|
|
|
const aiocb** cbs = (const aiocb**)&cb; // pass in an "array"
|
|
|
|
while(aio_error(cb) == EINPROGRESS)
|
|
|
|
aio_suspend(cbs, 1, (timespec*)0); // wait indefinitely
|
|
|
|
|
|
|
|
// query number of bytes transferred (-1 if the transfer failed)
|
|
|
|
const ssize_t bytes_transferred = aio_return(cb);
|
2004-09-22 17:20:58 +02:00
|
|
|
#ifdef PARANOIA
|
|
|
|
debug_out("file_wait_io: bytes_transferred=%d aio_nbytes=%d\n",
|
|
|
|
bytes_transferred, cb->aio_nbytes);
|
|
|
|
#endif
|
2004-12-01 22:37:01 +01:00
|
|
|
// (size was clipped to EOF in file_io => this is an actual IO error)
|
2004-08-18 04:12:52 +02:00
|
|
|
if(bytes_transferred < (ssize_t)cb->aio_nbytes)
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_IO;
|
2004-08-15 23:50:29 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
p = (void*)cb->aio_buf; // cast from volatile void*
|
|
|
|
size = bytes_transferred;
|
|
|
|
return 0;
|
|
|
|
}
|
2004-08-15 23:50:29 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-09-19 17:51:12 +02:00
|
|
|
int file_discard_io(FileIO* io)
|
2004-08-18 04:12:52 +02:00
|
|
|
{
|
2004-09-19 17:51:12 +02:00
|
|
|
memset(io->cb, 0, sizeof(aiocb));
|
2004-08-18 04:12:52 +02:00
|
|
|
// discourage further use.
|
2004-09-19 17:51:12 +02:00
|
|
|
free(io->cb);
|
|
|
|
io->cb = 0;
|
2004-08-18 04:12:52 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2004-08-15 23:50:29 +02:00
|
|
|
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2004-08-02 15:45:35 +02:00
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
|
2005-02-27 20:11:39 +01:00
|
|
|
ssize_t lowio(int fd, bool is_write, off_t ofs, size_t size, void* buf)
|
|
|
|
{
|
|
|
|
lseek(fd, ofs, SEEK_SET);
|
|
|
|
|
|
|
|
if(is_write)
|
|
|
|
return write(fd, buf, size);
|
|
|
|
else
|
|
|
|
return read (fd, buf, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
// L3 cache: intended to cache raw compressed data, since files aren't aligned
|
|
|
|
// in the archive; alignment code would force a read of the whole block,
|
|
|
|
// which would be a slowdown unless we keep them in memory.
|
|
|
|
//
|
|
|
|
// keep out of async code (although extra work for sync: must not issue/wait
|
|
|
|
// if was cached) to simplify things. disadvantage: problems if same block
|
|
|
|
// is issued twice, before the first call completes (via wait_io).
|
|
|
|
// that won't happen though unless we have threaded file_ios =>
|
|
|
|
// rare enough not to worry about performance.
|
|
|
|
//
|
|
|
|
// since sync code allocates the (temp) buffer, it's guaranteed
|
|
|
|
// to remain valid.
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// create an id for use with the Cache that uniquely identifies
|
|
|
|
// the block from the file <fn_hash> starting at <ofs> (aligned).
|
|
|
|
static u64 block_make_id(const u32 fn_hash, const off_t ofs)
|
|
|
|
{
|
|
|
|
// id format: filename hash | block number
|
|
|
|
// 63 32 31 0
|
|
|
|
//
|
|
|
|
// we assume the hash (currently: FNV) is unique for all filenames.
|
|
|
|
// chance of a collision is tiny, and a build tool will ensure
|
|
|
|
// filenames in the VFS archives are safe.
|
|
|
|
//
|
|
|
|
// block_num will always fit in 32 bits (assuming maximum file size
|
|
|
|
// = 2^32 * BLOCK_SIZE = 2^48 -- plenty); we check this, but don't
|
|
|
|
// include a workaround. we could return 0, and the caller would have
|
|
|
|
// to allocate their own buffer, but don't bother.
|
|
|
|
|
|
|
|
// make sure block_num fits in 32 bits
|
|
|
|
const size_t block_num = ofs / BLOCK_SIZE;
|
|
|
|
assert(block_num <= 0xffffffff);
|
|
|
|
|
|
|
|
u64 id = fn_hash; // careful, don't shift a u32 32 bits left
|
|
|
|
id <<= 32;
|
|
|
|
id |= block_num;
|
|
|
|
return id;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-02-27 20:11:39 +01:00
|
|
|
typedef std::pair<u64, void*> BlockCacheEntry;
|
2004-08-18 04:12:52 +02:00
|
|
|
typedef std::map<u64, void*> BlockCache;
|
|
|
|
typedef BlockCache::iterator BlockIt;
|
|
|
|
static BlockCache block_cache;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct IOSlot
|
|
|
|
{
|
|
|
|
FileIO io;
|
|
|
|
void* temp_buf;
|
|
|
|
|
|
|
|
u64 block_id;
|
|
|
|
// needed so that we can add the block to the cache when
|
|
|
|
// its IO is complete. if we add it when issuing, we'd no longer be
|
|
|
|
// thread-safe: someone else might find it in the cache before its
|
|
|
|
// transfer has completed. don't want to add an "is_complete" flag,
|
|
|
|
// because that'd be hard to update (on every wait_io).
|
|
|
|
|
|
|
|
void* cached_block;
|
|
|
|
// != 0 <==> data coming from cache and no IO issued.
|
|
|
|
|
|
|
|
|
|
|
|
// given buffer
|
|
|
|
// given buffer, will copy from cache
|
|
|
|
// temp buffer allocated here
|
|
|
|
// temp buffer taken from cache
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// don't just use operator[], so that block_cache isn't cluttered
|
|
|
|
// with IDs associated with 0 (blocks that wouldn't be cached anyway).
|
|
|
|
static void* block_find(u64 block_id)
|
|
|
|
{
|
|
|
|
BlockIt it = block_cache.find(block_id);
|
|
|
|
if(it == block_cache.end())
|
|
|
|
return 0;
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void block_add(u64 block_id, void* block)
|
|
|
|
{
|
|
|
|
if(block_find(block_id))
|
|
|
|
debug_warn("block_add: already in cache");
|
|
|
|
else
|
|
|
|
block_cache[block_id] = block;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
static ssize_t block_issue(File* f, IOSlot* slot, const off_t issue_ofs, void* buf)
|
|
|
|
{
|
|
|
|
memset(slot, 0, sizeof(IOSlot));
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
ssize_t issue_size = BLOCK_SIZE;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// check if in cache
|
|
|
|
slot->block_id = block_make_id(f->fn_hash, issue_ofs);
|
|
|
|
slot->cached_block = block_find(slot->block_id);
|
|
|
|
if(slot->cached_block)
|
|
|
|
goto skip_issue;
|
2004-07-08 16:30:35 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
//debug_out("%x miss\n", issue_ofs);
|
2004-07-08 16:30:35 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// allocate temp buffer
|
|
|
|
if(!buf)
|
|
|
|
buf = slot->temp_buf = mem_alloc(BLOCK_SIZE, BLOCK_SIZE);
|
2004-07-08 16:30:35 +02:00
|
|
|
|
|
|
|
|
2004-09-19 21:28:00 +02:00
|
|
|
// if using buffer, set position in it; otherwise, use temp buffer
|
|
|
|
CHECK_ERR(file_start_io(f, issue_ofs, BLOCK_SIZE, buf, &slot->io));
|
2004-08-15 23:50:29 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
skip_issue:
|
|
|
|
|
|
|
|
return issue_size;
|
|
|
|
}
|
2004-08-15 23:50:29 +02:00
|
|
|
|
2005-02-27 20:11:39 +01:00
|
|
|
|
|
|
|
// remove all blocks loaded from the file <fn>. used when reloading the file.
|
|
|
|
int file_invalidate_cache(const char* fn)
|
|
|
|
{
|
|
|
|
// convert to native path to match fn_hash set by file_open
|
|
|
|
char n_fn[PATH_MAX];
|
|
|
|
file_make_native_path(fn, n_fn);
|
|
|
|
|
|
|
|
const u32 fn_hash = fnv_hash(fn);
|
|
|
|
// notes:
|
|
|
|
// - don't use remove_if, because std::pair doesn't have operator=.
|
|
|
|
// - erasing elements during loop is ok because map iterators aren't
|
|
|
|
// invalidated.
|
|
|
|
for(BlockIt it = block_cache.begin(); it != block_cache.end(); ++it)
|
|
|
|
if((it->first >> 32) == fn_hash)
|
|
|
|
block_cache.erase(it);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
// the underlying aio implementation likes buffer and offset to be
|
|
|
|
// sector-aligned; if not, the transfer goes through an align buffer,
|
|
|
|
// and requires an extra memcpy.
|
|
|
|
//
|
|
|
|
// if the user specifies an unaligned buffer, there's not much we can
|
|
|
|
// do - we can't assume the buffer contains padding. therefore,
|
|
|
|
// callers should let us allocate the buffer if possible.
|
|
|
|
//
|
|
|
|
// if ofs misalign = buffer, only the first and last blocks will need
|
|
|
|
// to be copied by aio, since we read up to the next block boundary.
|
|
|
|
// otherwise, everything will have to be copied; at least we split
|
|
|
|
// the read into blocks, so aio's buffer won't have to cover the
|
|
|
|
// whole file.
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
|
|
|
|
|
2004-08-27 02:33:20 +02:00
|
|
|
// transfer <size> bytes, starting at <ofs>, to/from the given file.
|
|
|
|
// (read or write access was chosen at file-open time).
|
|
|
|
//
|
|
|
|
// if non-NULL, <cb> is called for each block transferred, passing <ctx>.
|
|
|
|
// it returns how much data was actually transferred, or a negative error
|
|
|
|
// code (in which case we abort the transfer and return that value).
|
|
|
|
// the callback mechanism is useful for user progress notification or
|
|
|
|
// processing data while waiting for the next I/O to complete
|
|
|
|
// (quasi-parallel, without the complexity of threads).
|
|
|
|
//
|
|
|
|
// return number of bytes transferred (see above), or a negative error code.
|
2004-12-10 00:12:02 +01:00
|
|
|
ssize_t file_io(File* f, off_t data_ofs, size_t data_size, void* data_buf,
|
|
|
|
FileIOCB cb, uintptr_t ctx) // optional
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-05-16 05:31:29 +02:00
|
|
|
#ifdef PARANOIA
|
2004-08-09 18:46:57 +02:00
|
|
|
debug_out("file_io fd=%d size=%d ofs=%d\n", f->fd, data_size, data_ofs);
|
2004-05-16 05:31:29 +02:00
|
|
|
#endif
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
CHECK_FILE(f);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
const bool is_write = !!(f->flags & FILE_WRITE);
|
|
|
|
const bool no_aio = !!(f->flags & FILE_NO_AIO);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
// when reading:
|
|
|
|
if(!is_write)
|
2004-07-31 04:02:44 +02:00
|
|
|
{
|
2004-08-09 18:46:57 +02:00
|
|
|
// cut data_size off at EOF
|
|
|
|
const ssize_t bytes_left = f->size - data_ofs;
|
2004-07-31 04:02:44 +02:00
|
|
|
if(bytes_left < 0)
|
2004-12-01 22:37:01 +01:00
|
|
|
return ERR_EOF;
|
2004-08-09 18:46:57 +02:00
|
|
|
data_size = MIN(data_size, (size_t)bytes_left);
|
2004-07-31 04:02:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-24 19:34:00 +02:00
|
|
|
bool temp = (data_buf == 0);
|
2004-07-31 04:02:44 +02:00
|
|
|
|
2004-08-15 23:50:29 +02:00
|
|
|
// sanity checks:
|
|
|
|
// .. temp blocks requested AND
|
|
|
|
// (not reading OR using lowio OR no callback)
|
2004-08-24 19:34:00 +02:00
|
|
|
if(temp && (is_write || no_aio || !cb))
|
2004-08-09 18:46:57 +02:00
|
|
|
{
|
2004-08-15 23:50:29 +02:00
|
|
|
debug_warn("file_io: invalid parameter");
|
2004-08-09 18:46:57 +02:00
|
|
|
return ERR_INVALID_PARAM;
|
2004-07-31 04:02:44 +02:00
|
|
|
}
|
2004-08-15 23:50:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
// only align if we allocate the buffer and in AIO mode
|
2004-08-24 19:34:00 +02:00
|
|
|
const bool do_align = temp;
|
|
|
|
const bool cache = temp;
|
2004-07-08 16:30:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
//
|
2004-08-09 18:46:57 +02:00
|
|
|
// calculate aligned transfer size (no change if !do_align)
|
2004-07-08 16:30:35 +02:00
|
|
|
//
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
off_t actual_ofs = data_ofs;
|
|
|
|
size_t actual_size = data_size;
|
|
|
|
void* actual_buf = data_buf;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
// note: we go to the trouble of aligning the first block (instead of
|
|
|
|
// just reading up to the next block and letting aio realign it),
|
|
|
|
// so that it can be taken from the cache.
|
|
|
|
// this is not possible if !do_align, since we have to allocate
|
|
|
|
// extra buffer space for the padding.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
const size_t ofs_misalign = data_ofs % BLOCK_SIZE;
|
|
|
|
const size_t lead_padding = do_align? ofs_misalign : 0;
|
|
|
|
// for convenience; used below.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
if(do_align)
|
|
|
|
{
|
|
|
|
actual_ofs -= (off_t)ofs_misalign;
|
|
|
|
actual_size = round_up(ofs_misalign + data_size, BLOCK_SIZE);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// skip aio code, use lowio
|
2004-08-09 18:46:57 +02:00
|
|
|
if(no_aio)
|
2004-08-18 04:12:52 +02:00
|
|
|
return lowio(f->fd, is_write, data_ofs, data_size, data_buf);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-09 18:46:57 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
2005-01-23 18:54:20 +01:00
|
|
|
// now we read the file in 64 KiB chunks, N-buffered.
|
2004-03-03 00:56:51 +01:00
|
|
|
// if reading from Zip, inflate while reading the next block.
|
|
|
|
//
|
|
|
|
|
2004-08-15 23:50:29 +02:00
|
|
|
const int MAX_IOS = 4;
|
2004-08-18 04:12:52 +02:00
|
|
|
IOSlot ios[MAX_IOS] = { {0} };
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
int head = 0;
|
|
|
|
int tail = 0;
|
|
|
|
int pending_ios = 0;
|
|
|
|
|
|
|
|
bool all_issued = false;
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
// (useful, raw data: possibly compressed, but doesn't count padding)
|
|
|
|
size_t raw_transferred_cnt = 0;
|
|
|
|
size_t issue_cnt = 0;
|
|
|
|
|
|
|
|
// if callback, what it reports; otherwise, = raw_transferred_cnt
|
|
|
|
// this is what we'll return
|
|
|
|
size_t actual_transferred_cnt = 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
ssize_t err = +1; // loop terminates if <= 0
|
|
|
|
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
// queue not full, data remaining to transfer, and no error:
|
|
|
|
// start transferring next block.
|
|
|
|
if(pending_ios < MAX_IOS && !all_issued && err > 0)
|
|
|
|
{
|
2004-08-15 23:50:29 +02:00
|
|
|
// get next free IO slot in ring buffer
|
2004-08-18 04:12:52 +02:00
|
|
|
IOSlot* slot = &ios[head];
|
2004-09-19 21:28:00 +02:00
|
|
|
memset(slot, 0, sizeof(IOSlot));
|
2004-08-15 23:50:29 +02:00
|
|
|
head = (head + 1) % MAX_IOS;
|
|
|
|
pending_ios++;
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
off_t issue_ofs = (off_t)(actual_ofs + issue_cnt);
|
|
|
|
|
2004-08-24 19:34:00 +02:00
|
|
|
void* buf = (temp)? 0 : (char*)actual_buf + issue_cnt;
|
2004-08-18 04:12:52 +02:00
|
|
|
ssize_t issued = block_issue(f, slot, issue_ofs, buf);
|
2004-09-22 17:20:58 +02:00
|
|
|
#ifdef PARANOIA
|
|
|
|
debug_out("file_io: block_issue: %d\n", issued);
|
|
|
|
#endif
|
2004-08-18 04:12:52 +02:00
|
|
|
if(issued < 0)
|
|
|
|
err = issued;
|
2004-03-03 00:56:51 +01:00
|
|
|
// transfer failed - loop will now terminate after
|
|
|
|
// waiting for all pending transfers to complete.
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
issue_cnt += issued;
|
2004-08-10 17:57:35 +02:00
|
|
|
if(issue_cnt >= actual_size)
|
2004-03-03 00:56:51 +01:00
|
|
|
all_issued = true;
|
2004-09-19 21:28:00 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
// IO pending: wait for it to complete, and process it.
|
|
|
|
else if(pending_ios)
|
|
|
|
{
|
2004-08-18 04:12:52 +02:00
|
|
|
IOSlot* slot = &ios[tail];
|
2004-03-03 00:56:51 +01:00
|
|
|
tail = (tail + 1) % MAX_IOS;
|
|
|
|
pending_ios--;
|
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
void* block = slot->cached_block;
|
|
|
|
size_t size = BLOCK_SIZE;
|
|
|
|
// wasn't in cache; it was issued, so wait for it
|
|
|
|
bool from_cache;
|
|
|
|
if(block)
|
|
|
|
from_cache = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
from_cache = false;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-09-19 17:51:12 +02:00
|
|
|
int ret = file_wait_io(&slot->io, block, size);
|
2004-08-18 04:12:52 +02:00
|
|
|
if(ret < 0)
|
|
|
|
err = (ssize_t)ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// first time; skip past padding
|
|
|
|
void* data = block;
|
|
|
|
if(raw_transferred_cnt == 0)
|
|
|
|
{
|
|
|
|
(char*&)data += lead_padding;
|
|
|
|
size -= lead_padding;
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't include trailing padding
|
|
|
|
if(raw_transferred_cnt + size > data_size)
|
|
|
|
size = data_size - raw_transferred_cnt;
|
|
|
|
|
|
|
|
|
2004-09-19 21:28:00 +02:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
// we have useable data from a previous temp buffer,
|
|
|
|
// but it needs to be copied into the user's buffer
|
2004-08-24 19:34:00 +02:00
|
|
|
if(from_cache && !temp)
|
2004-08-18 04:12:52 +02:00
|
|
|
memcpy((char*)data_buf+raw_transferred_cnt, data, size);
|
2004-08-11 22:23:22 +02:00
|
|
|
|
2004-08-10 17:57:35 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
//// if size comes out short, we must be at EOF
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
raw_transferred_cnt += size;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
if(cb && !(err <= 0))
|
|
|
|
{
|
2004-08-10 17:57:35 +02:00
|
|
|
ssize_t ret = cb(ctx, data, size);
|
|
|
|
// if negative: processing failed.
|
|
|
|
// loop will now terminate after waiting for all
|
2004-03-03 00:56:51 +01:00
|
|
|
// pending transfers to complete.
|
2004-08-10 17:57:35 +02:00
|
|
|
// note: don't abort if = 0: zip callback may not actually
|
|
|
|
// output anything if passed very little data.
|
|
|
|
if(ret < 0)
|
2004-03-03 00:56:51 +01:00
|
|
|
err = ret;
|
2004-05-06 19:14:30 +02:00
|
|
|
else
|
|
|
|
actual_transferred_cnt += ret;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
// no callback to process data: raw = actual
|
|
|
|
else
|
|
|
|
actual_transferred_cnt += size;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-08-18 04:12:52 +02:00
|
|
|
if(!from_cache)
|
2004-09-19 17:51:12 +02:00
|
|
|
file_discard_io(&slot->io);
|
2004-08-18 04:12:52 +02:00
|
|
|
|
2004-08-24 19:34:00 +02:00
|
|
|
if(temp)
|
2004-08-18 04:12:52 +02:00
|
|
|
{
|
|
|
|
// adding is allowed and we didn't take this from the cache already: add
|
|
|
|
if(!slot->cached_block)
|
|
|
|
block_add(slot->block_id, slot->temp_buf);
|
|
|
|
}
|
2004-09-19 21:28:00 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
// (all issued OR error) AND no pending transfers - done.
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2004-09-22 17:20:58 +02:00
|
|
|
#ifdef PARANOIA
|
|
|
|
debug_out("file_io: err=%d, actual_transferred_cnt=%d\n", err, actual_transferred_cnt);
|
|
|
|
#endif
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
// failed (0 means callback reports it's finished)
|
|
|
|
if(err < 0)
|
|
|
|
return err;
|
2004-08-09 18:46:57 +02:00
|
|
|
|
2004-08-10 17:57:35 +02:00
|
|
|
assert(issue_cnt >= raw_transferred_cnt && raw_transferred_cnt >= data_size);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
return (ssize_t)actual_transferred_cnt;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// memory mapping
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
// no significance aside from preventing uint overflow.
|
|
|
|
static const uint MAX_MAP_REFS = 255;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
// map the entire file <f> into memory. if already currently mapped,
|
|
|
|
// return the previous mapping (reference-counted).
|
|
|
|
// output parameters are zeroed on failure.
|
|
|
|
//
|
|
|
|
// the mapping will be removed (if still open) when its file is closed.
|
|
|
|
// however, map/unmap calls should still be paired so that the mapping
|
|
|
|
// may be removed when no longer needed.
|
|
|
|
//
|
|
|
|
// rationale: reference counting is required for zip_map: several
|
|
|
|
// Zip "mappings" each reference one ZArchive's actual file mapping.
|
|
|
|
// implement it here so that we also get refcounting for normal files.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_map(File* f, void*& p, size_t& size)
|
2004-06-03 02:17:24 +02:00
|
|
|
{
|
|
|
|
p = 0;
|
|
|
|
size = 0;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
CHECK_FILE(f);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-09 15:49:32 +02:00
|
|
|
const int prot = (f->flags & FILE_WRITE)? PROT_WRITE : PROT_READ;
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
// already mapped - increase refcount and return previous mapping.
|
|
|
|
if(f->mapping)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-03 02:17:24 +02:00
|
|
|
// prevent overflow; if we have this many refs, should find out why.
|
|
|
|
if(f->map_refs >= MAX_MAP_REFS)
|
|
|
|
{
|
|
|
|
debug_warn("file_map: too many references to mapping");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
f->map_refs++;
|
|
|
|
goto have_mapping;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2004-06-09 18:10:23 +02:00
|
|
|
// don't allow mapping zero-length files (doesn't make sense,
|
2004-08-09 18:46:57 +02:00
|
|
|
// and BoundsChecker warns about wposix mmap failing).
|
|
|
|
// then again, don't complain, because this might happen when mounting
|
|
|
|
// a dir containing empty files; each is opened as a Zip file.
|
2004-06-09 18:10:23 +02:00
|
|
|
if(f->size == 0)
|
|
|
|
return -1;
|
|
|
|
|
2004-06-03 16:20:09 +02:00
|
|
|
f->mapping = mmap((void*)0, f->size, prot, MAP_PRIVATE, f->fd, (off_t)0);
|
2004-06-03 02:17:24 +02:00
|
|
|
if(!f->mapping)
|
|
|
|
return ERR_NO_MEM;
|
|
|
|
|
|
|
|
f->map_refs = 1;
|
|
|
|
|
|
|
|
have_mapping:
|
|
|
|
p = f->mapping;
|
|
|
|
size = f->size;
|
2004-03-03 00:56:51 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-06-03 02:17:24 +02:00
|
|
|
// decrement the reference count for the mapping belonging to file <f>.
|
|
|
|
// fail if there are no references; remove the mapping if the count reaches 0.
|
|
|
|
//
|
|
|
|
// the mapping will be removed (if still open) when its file is closed.
|
|
|
|
// however, map/unmap calls should still be paired so that the mapping
|
|
|
|
// may be removed when no longer needed.
|
2004-12-10 00:12:02 +01:00
|
|
|
int file_unmap(File* f)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2004-06-03 02:17:24 +02:00
|
|
|
CHECK_FILE(f);
|
|
|
|
|
2004-06-03 02:27:50 +02:00
|
|
|
// file is not currently mapped
|
|
|
|
if(f->map_refs == 0)
|
2004-08-09 18:46:57 +02:00
|
|
|
{
|
|
|
|
debug_warn("file_unmap: not currently mapped");
|
2004-06-03 02:27:50 +02:00
|
|
|
return -1;
|
2004-08-09 18:46:57 +02:00
|
|
|
}
|
2004-06-03 02:27:50 +02:00
|
|
|
|
|
|
|
// still more than one reference remaining - done.
|
|
|
|
if(--f->map_refs > 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// no more references: remove the mapping
|
2004-12-10 00:12:02 +01:00
|
|
|
void* p = f->mapping;
|
2004-06-03 02:17:24 +02:00
|
|
|
f->mapping = 0;
|
2004-06-03 02:27:50 +02:00
|
|
|
// don't clear f->size - the file is still open.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-03 02:27:50 +02:00
|
|
|
return munmap(p, f->size);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|