1
0
forked from 0ad/0ad

# refactor VFS path-related functions; split into separate files

- replace all hardcoded strrchr functions (used to get extension / file
name only) with path helper functions (found a few bugs in the process)
- split VFS-independent path helper functions into lib/path_util (allows
including by other files without pulling in entire VFS)
- renamed pp_* functions path_package_*
- split remaining path helper functions into lib/res/file/path
- vfs: split should-reload logic out of vfs_reload_changed_files

- lib: add comments to rand/xrand

This was SVN commit r3796.
This commit is contained in:
janwas 2006-04-22 16:26:16 +00:00
parent b5d9da29c1
commit 01700f0e9f
37 changed files with 872 additions and 871 deletions

View File

@ -116,22 +116,14 @@ void CTextureManager::LoadTextures(CTerrainPropertiesPtr props, const char* dir)
// we can't use FindFile's filter param because new texture formats // we can't use FindFile's filter param because new texture formats
// may later be added and that interface doesn't support specifying // may later be added and that interface doesn't support specifying
// multiple extensions. // multiple extensions.
const char* ext = strrchr(texture_name, '.'); if(!tex_is_known_extension(texture_name))
if(!ext
|| !stricmp(ext, ".xml")
|| !stricmp(ext, ".xmb")
|| !stricmp(ext, ".dtd")
|| !stricmp(ext, ".jbf") // PSP browser files. (This list is getting quite long and ugly...)
)
continue;
// Also allow storage of other temporary files in the texture directories
if(ext[strlen(ext)-1] == '~')
continue; continue;
// build name of associated xml file (i.e. replace extension) // build name of associated xml file (i.e. replace extension)
char xml_name[PATH_MAX+5]; // add room for .XML char xml_name[PATH_MAX+5]; // add room for .XML
strcpy_s(xml_name, PATH_MAX, texture_name); strcpy_s(xml_name, PATH_MAX, texture_name);
strcpy(xml_name + (ext-texture_name), ".xml"); // safe const char* ext = path_extension(texture_name);
SAFE_STRCPY(xml_name + (ext-texture_name), "xml");
CTerrainPropertiesPtr myprops; CTerrainPropertiesPtr myprops;
// Has XML file -> attempt to load properties // Has XML file -> attempt to load properties

View File

@ -26,9 +26,11 @@
#include <stdio.h> #include <stdio.h>
#include "sysdep/gfx.h" #include "sysdep/gfx.h"
#include "res/graphics/ogl_tex.h" #include "lib/res/graphics/ogl_tex.h"
#include "res/file/file.h" #include "lib/res/file/file.h"
#include "res/file/vfs.h" #include "lib/res/file/vfs.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/path_util.h"
#include "app_hooks.h" #include "app_hooks.h"
@ -56,10 +58,9 @@ static const char* get_log_dir()
ONCE(\ ONCE(\
char N_exe_name[PATH_MAX];\ char N_exe_name[PATH_MAX];\
(void)sys_get_executable_name(N_exe_name, ARRAY_SIZE(N_exe_name));\ (void)sys_get_executable_name(N_exe_name, ARRAY_SIZE(N_exe_name));\
/* strip app name (we only need path) */\ /* strip app name (we only want its path) */\
char* slash = strrchr(N_exe_name, DIR_SEP);\ path_strip_fn(N_exe_name);\
if(slash) *slash = '\0';\ (void)path_append(N_log_dir, N_exe_name, "../logs/");
(void)vfs_path_append(N_log_dir, N_exe_name, "../logs/");
); );
return N_log_dir; return N_log_dir;
} }

View File

@ -26,14 +26,15 @@
#include <string.h> #include <string.h>
#include "lib.h" #include "lib.h"
#include "debug.h"
#include "debug_stl.h"
#include "posix.h" #include "posix.h"
// some functions here are called from within mmgr; disable its hooks // some functions here are called from within mmgr; disable its hooks
// so that our allocations don't cause infinite recursion. // so that our allocations don't cause infinite recursion.
#include "nommgr.h" #include "nommgr.h"
#include "self_test.h" #include "self_test.h"
#include "app_hooks.h" #include "app_hooks.h"
#include "lib/path_util.h"
#include "debug_stl.h"
#include "debug.h"
// needed when writing crashlog // needed when writing crashlog
@ -216,7 +217,7 @@ void debug_wprintf(const wchar_t* fmt, ...)
LibError debug_write_crashlog(const wchar_t* text) LibError debug_write_crashlog(const wchar_t* text)
{ {
// note: we go through some gyrations here (strcpy+strcat) to avoid // note: we go through some gyrations here (strcpy+strcat) to avoid
// dependency on file code (vfs_path_append). // dependency on file code (path_append).
char N_path[PATH_MAX]; char N_path[PATH_MAX];
strcpy_s(N_path, ARRAY_SIZE(N_path), ah_get_log_dir()); strcpy_s(N_path, ARRAY_SIZE(N_path), ah_get_log_dir());
strcat_s(N_path, ARRAY_SIZE(N_path), "crashlog.txt"); strcat_s(N_path, ARRAY_SIZE(N_path), "crashlog.txt");
@ -300,12 +301,9 @@ static const char* symbol_string_build(void* symbol, const char* name, const cha
if(file && line) if(file && line)
{ {
// strip path from filename (long and irrelevant) // strip path from filename (long and irrelevant)
const char* filename_only = file; const char* fn_only = path_name_only(file);
const char* slash = strrchr(file, DIR_SEP);
if(slash)
filename_only = slash+1;
len = snprintf(string, STRING_MAX-1, "%s:%05d ", filename_only, line); len = snprintf(string, STRING_MAX-1, "%s:%05d ", fn_only, line);
} }
// only address is known // only address is known
else else
@ -471,9 +469,8 @@ ErrorReaction debug_display_error(const wchar_t* description,
description = ah_translate(description); description = ah_translate(description);
// display in output window; double-click will navigate to error location. // display in output window; double-click will navigate to error location.
const char* slash = strrchr(file, DIR_SEP); const char* fn_only = path_name_only(file);
const char* filename = slash? slash+1 : file; debug_wprintf(L"%hs(%d): %ls\n", fn_only, line, description);
debug_wprintf(L"%hs(%d): %ls\n", filename, line, description);
// allocate memory for the stack trace. this needs to be quite large, // allocate memory for the stack trace. this needs to be quite large,
// so preallocating is undesirable. it must work even if the heap is // so preallocating is undesirable. it must work even if the heap is
@ -559,13 +556,12 @@ ErrorReaction debug_assert_failed(const char* expr,
// __FILE__ evaluates to the full path (albeit without drive letter) // __FILE__ evaluates to the full path (albeit without drive letter)
// which is rather long. we only display the base name for clarity. // which is rather long. we only display the base name for clarity.
const char* slash = strrchr(file, DIR_SEP); const char* fn_only = path_name_only(file);
const char* base_name = slash? slash+1 : file;
uint skip = 1; void* context = 0; uint skip = 1; void* context = 0;
wchar_t buf[400]; wchar_t buf[400];
swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed at %hs:%d (%hs): \"%hs\"", base_name, line, func, expr); swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed at %hs:%d (%hs): \"%hs\"", fn_only, line, func, expr);
return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, base_name,line); return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, fn_only,line);
} }
@ -583,14 +579,13 @@ ErrorReaction debug_warn_err(LibError err,
// __FILE__ evaluates to the full path (albeit without drive letter) // __FILE__ evaluates to the full path (albeit without drive letter)
// which is rather long. we only display the base name for clarity. // which is rather long. we only display the base name for clarity.
const char* slash = strrchr(file, DIR_SEP); const char* fn_only = path_name_only(file);
const char* base_name = slash? slash+1 : file;
uint skip = 1; void* context = 0; uint skip = 1; void* context = 0;
wchar_t buf[400]; wchar_t buf[400];
char err_buf[200]; error_description_r(err, err_buf, ARRAY_SIZE(err_buf)); char err_buf[200]; error_description_r(err, err_buf, ARRAY_SIZE(err_buf));
swprintf(buf, ARRAY_SIZE(buf), L"Function call failed at %hs:%d (%hs): return value was %d (%hs)", base_name, line, func, err, err_buf); swprintf(buf, ARRAY_SIZE(buf), L"Function call failed at %hs:%d (%hs): return value was %d (%hs)", fn_only, line, func, err, err_buf);
return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, base_name,line); return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, fn_only,line);
} }

View File

@ -526,18 +526,21 @@ int match_wildcardw(const wchar_t* s, const wchar_t* w)
// avoids several common pitfalls; see discussion at // avoids several common pitfalls; see discussion at
// http://www.azillionmonkeys.com/qed/random.html // http://www.azillionmonkeys.com/qed/random.html
// Silly heuristic: glibc has RAND_MAX of 2^31 - 1 while MS CRT has 2^15-1 or something... // rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits;
// double that amount by concatenating 2 random numbers.
// this is not to fix poor rand() randomness - the number returned will be
// folded down to a much smaller interval anyway. instead, a larger XRAND_MAX
// decreases the probability of having to repeat the loop.
#if RAND_MAX < 65536 #if RAND_MAX < 65536
static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1; static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1;
static uint xrand()
uint fullrand()
{ {
return rand()*(RAND_MAX+1) + rand(); return rand()*(RAND_MAX+1) + rand();
} }
// rand() is already ok; no need to do anything.
#else #else
static const uint XRAND_MAX = RAND_MAX; static const uint XRAND_MAX = RAND_MAX;
static uint xrand()
uint fullrand()
{ {
return rand(); return rand();
} }
@ -556,9 +559,12 @@ uint rand(uint min_inclusive, uint max_exclusive)
const uint inv_range = XRAND_MAX / range; const uint inv_range = XRAND_MAX / range;
// generate random number in [0, range) // generate random number in [0, range)
// idea: avoid skewed distributions when <range> doesn't evenly divide
// XRAND_MAX by simply discarding values in the "remainder".
// not expected to run often since XRAND_MAX is large.
uint x; uint x;
do do
x = fullrand(); x = xrand();
while(x >= range * inv_range); while(x >= range * inv_range);
x /= inv_range; x /= inv_range;

View File

@ -413,7 +413,7 @@ ERR(-100262, ERR_MEM_OVERWRITTEN, "Wrote to memory outside valid allocation")
// file + vfs // file + vfs
// .. path // .. path
ERR(-100300, ERR_PATH_LENGTH, "Path exceeds VFS_MAX_PATH characters") ERR(-100300, ERR_PATH_LENGTH, "Path exceeds PATH_MAX characters")
ERR(-100301, ERR_PATH_EMPTY, "Path is an empty string") ERR(-100301, ERR_PATH_EMPTY, "Path is an empty string")
ERR(-100302, ERR_PATH_NOT_RELATIVE, "Path is not relative") ERR(-100302, ERR_PATH_NOT_RELATIVE, "Path is not relative")
ERR(-100303, ERR_PATH_NON_PORTABLE, "Path contains OS-specific dir separator") ERR(-100303, ERR_PATH_NON_PORTABLE, "Path contains OS-specific dir separator")

299
source/lib/path_util.cpp Normal file
View File

@ -0,0 +1,299 @@
/**
* =========================================================================
* File : path_util.cpp
* Project : 0 A.D.
* Description : helper functions for path strings.
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2004-2006 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* 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.
*/
#include "precompiled.h"
#include <string.h>
#include "lib.h"
#include "posix.h"
#include "path_util.h"
static inline bool is_dir_sep(char c)
{
if(c == '/' || c == DIR_SEP)
return true;
return false;
}
// is s2 a subpath of s1, or vice versa?
bool path_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) OR
is_dir_sep(c2) || // start of next component OR
is_dir_sep(last_c1)) // ", but both have a trailing slash
// => is subpath
return true;
}
// mismatch => is not subpath
if(c1 != c2)
return false;
}
}
// if path is invalid, return a descriptive error code, otherwise ERR_OK.
LibError path_validate(const char* path)
{
// disallow "/", because it would create a second 'root' (with name = "").
// root dir is "".
if(path[0] == '/')
return ERR_PATH_NOT_RELATIVE;
// scan each char in path string; count length.
int c = 0; // current char; used for .. detection
size_t path_len = 0;
for(;;)
{
const int last_c = c;
c = path[path_len++];
// whole path is too long
if(path_len >= PATH_MAX)
return ERR_PATH_LENGTH;
// disallow:
// - ".." (prevent going above the VFS root dir)
// - "./" (security hole when mounting and not supported on Windows).
// allow "/.", because CVS backup files include it.
if(last_c == '.' && (c == '.' || c == '/'))
return ERR_PATH_NON_CANONICAL;
// disallow OS-specific dir separators
if(c == '\\' || c == ':')
return ERR_PATH_NON_PORTABLE;
// end of string, no errors encountered
if(c == '\0')
break;
}
return ERR_OK;
}
// if name is invalid, return a descriptive error code, otherwise ERR_OK.
// (name is a path component, i.e. that between directory separators)
LibError path_component_validate(const char* name)
{
// disallow empty strings
if(*name == '\0')
return ERR_PATH_EMPTY;
for(;;)
{
const int c = *name++;
// disallow *any* dir separators (regardless of which
// platform we're on).
if(c == '\\' || c == ':' || c == '/')
return ERR_PATH_COMPONENT_SEPARATOR;
// end of string, no errors encountered
if(c == '\0')
break;
}
return ERR_OK;
}
// copy path strings (provided for convenience).
void path_copy(char* dst, const char* src)
{
strcpy_s(dst, PATH_MAX, src);
}
// combine <path1> and <path2> into one path, and write to <dst>.
// if necessary, a directory separator is added between the paths.
// each may be empty, filenames, or full paths.
// total path length (including '\0') must not exceed PATH_MAX.
LibError path_append(char* dst, const char* path1, const char* path2)
{
const size_t len1 = strlen(path1);
const size_t len2 = strlen(path2);
size_t total_len = len1 + len2 + 1; // includes '\0'
// check if we need to add '/' between path1 and path2
// note: the second can't start with '/' (not allowed by path_validate)
bool need_separator = false;
if(len1 != 0 && !is_dir_sep(path1[len1-1]))
{
total_len++; // for '/'
need_separator = true;
}
if(total_len+1 > PATH_MAX)
WARN_RETURN(ERR_PATH_LENGTH);
strcpy(dst, path1); // safe
dst += len1;
if(need_separator)
*dst++ = '/';
strcpy(dst, path2); // safe
return ERR_OK;
}
// strip <remove> from the start of <src>, prepend <replace>,
// and write to <dst>.
// returns ERR_FAIL if the beginning of <src> doesn't match <remove>.
LibError path_replace(char* dst, const char* src, const char* remove, const char* replace)
{
// remove doesn't match start of <src>
const size_t remove_len = strlen(remove);
if(strncmp(src, remove, remove_len) != 0)
WARN_RETURN(ERR_FAIL);
// get rid of trailing / in src (must not be included in remove)
const char* start = src+remove_len;
if(is_dir_sep(*start))
start++;
// prepend replace.
RETURN_ERR(path_append(dst, replace, start));
return ERR_OK;
}
//-----------------------------------------------------------------------------
// split paths into specific parts
// return pointer to the name component within path (i.e. skips over all
// characters up to the last dir separator, if any).
const char* path_name_only(const char* path)
{
// first try: look for portable '/'
const char* slash = strrchr(path, '/');
// not present
if(!slash)
{
// now look for platform-specific DIR_SEP
slash = strrchr(path, DIR_SEP);
// neither present, it's a filename only
if(!slash)
return path;
}
const char* name = slash+1;
return name;
}
// if <path> contains a name component, it is stripped away.
void path_strip_fn(char* path)
{
char* name = (char*)path_name_only(path);
*name = '\0'; // cut off string here
}
// fill <dir> with the directory path portion of <path>
// ("" if root dir, otherwise ending with '/').
// note: copies to <dir> and proceeds to path_strip_fn it.
void path_dir_only(const char* path, char* dir)
{
path_copy(dir, path);
path_strip_fn(dir);
}
// return extension of <fn>, or "" if there is none.
// NOTE: does not include the period; e.g. "a.bmp" yields "bmp".
const char* path_extension(const char* fn)
{
const char* dot = strrchr(fn, '.');
if(!dot)
return "";
const char* ext = dot+1;
return ext;
}
//-----------------------------------------------------------------------------
// convenience "class" that simplifies successively appending a filename to
// its parent directory. this avoids needing to allocate memory and calling
// strlen/strcat. used by wdll_ver and dir_next_ent.
// we want to maintain C compatibility, so this isn't a C++ class.
// write the given directory path into our buffer and set end/chars_left
// accordingly. <dir> need not but can end with a directory separator.
//
// note: <dir> and the filename set via path_package_append_file are separated by
// '/'. this is to allow use on portable paths; the function otherwise
// does not care if paths are relative/portable/absolute.
LibError path_package_set_dir(PathPackage* pp, const char* dir)
{
// -1 allows for trailing DIR_SEP that will be added if not
// already present.
if(strcpy_s(pp->path, ARRAY_SIZE(pp->path)-1, dir) != 0)
WARN_RETURN(ERR_PATH_LENGTH);
size_t len = strlen(pp->path);
// add directory separator if not already present
// .. but only check this if dir != "" (=> len-1 is safe)
if(len != 0)
{
char* last_char = pp->path+len-1;
if(!is_dir_sep(*last_char))
{
*(last_char+1) = '/';
// note: need to 0-terminate because pp.path is uninitialized
// and we overwrite strcpy_s's terminator above.
*(last_char+2) = '\0';
// only bump by 1 - filename must overwrite '\0'.
len++;
}
}
pp->end = pp->path+len;
pp->chars_left = ARRAY_SIZE(pp->path)-len;
return ERR_OK;
}
// append the given filename to the directory established by the last
// path_package_set_dir on this package. the whole path is accessible at pp->path.
LibError path_package_append_file(PathPackage* pp, const char* path)
{
CHECK_ERR(strcpy_s(pp->end, pp->chars_left, path));
return ERR_OK;
}

105
source/lib/path_util.h Normal file
View File

@ -0,0 +1,105 @@
/**
* =========================================================================
* File : path_util.h
* Project : 0 A.D.
* Description : helper functions for path strings.
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2004-2006 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* 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.
*/
// notes:
// - this module is split out of lib/res/file so that it can be used from
// other code without pulling in the entire file manager.
// - there is no restriction on buffer lengths except the underlying OS.
// input buffers must not exceed PATH_MAX chars, while outputs
// must hold at least that much.
// - unless otherwise mentioned, all functions are intended to work with
// native and portable and VFS paths.
// when reading, both '/' and DIR_SEP are accepted; '/' is written.
#ifndef PATH_UTIL_H__
#define PATH_UTIL_H__
#include "posix.h" // PATH_MAX
// is s2 a subpath of s1, or vice versa?
extern bool path_is_subpath(const char* s1, const char* s2);
// if path is invalid, return a descriptive error code, otherwise ERR_OK.
extern LibError path_validate(const char* path);
// if name is invalid, return a descriptive error code, otherwise ERR_OK.
// (name is a path component, i.e. that between directory separators)
extern LibError path_component_validate(const char* name);
// copy path strings (provided for convenience).
extern void path_copy(char* dst, const char* src);
// combine <path1> and <path2> into one path, and write to <dst>.
// if necessary, a directory separator is added between the paths.
// each may be empty, filenames, or full paths.
// total path length (including '\0') must not exceed PATH_MAX.
extern LibError path_append(char* dst, const char* path1, const char* path2);
// strip <remove> from the start of <src>, prepend <replace>,
// and write to <dst>.
// returns ERR_FAIL if the beginning of <src> doesn't match <remove>.
extern LibError path_replace(char* dst, const char* src, const char* remove, const char* replace);
// return pointer to the name component within path (i.e. skips over all
// characters up to the last dir separator, if any).
extern const char* path_name_only(const char* path);
// if <path> contains a name component, it is stripped away.
extern void path_strip_fn(char* path);
// fill <dir> with the directory path portion of <path>
// ("" if root dir, otherwise ending with '/').
// note: copies to <dir> and proceeds to path_strip_fn it.
extern void path_dir_only(const char* path, char* dir);
// return extension of <fn>, or "" if there is none.
// NOTE: does not include the period; e.g. "a.bmp" yields "bmp".
extern const char* path_extension(const char* fn);
//-----------------------------------------------------------------------------
// convenience "class" that simplifies successively appending a filename to
// its parent directory. this avoids needing to allocate memory and calling
// strlen/strcat. used by wdll_ver and dir_next_ent.
// we want to maintain C compatibility, so this isn't a C++ class.
struct PathPackage
{
char* end;
size_t chars_left;
char path[PATH_MAX];
};
// write the given directory path into our buffer and set end/chars_left
// accordingly. <dir> need not but can end with a directory separator.
//
// note: <dir> and the filename set via path_package_append_file are separated by
// '/'. this is to allow use on portable paths; the function otherwise
// does not care if paths are relative/portable/absolute.
extern LibError path_package_set_dir(PathPackage* pp, const char* dir);
// append the given filename to the directory established by the last
// path_package_set_dir on this package. the whole path is accessible at pp->path.
extern LibError path_package_append_file(PathPackage* pp, const char* path);
#endif // #ifndef PATH_UTIL_H__

View File

@ -712,10 +712,7 @@ LibError afile_unmap(File* f)
static inline bool file_type_is_uncompressible(const char* fn) static inline bool file_type_is_uncompressible(const char* fn)
{ {
const char* ext = strrchr(fn, '.'); const char* ext = path_extension(fn);
// no extension? bail; assume compressible
if(!ext)
return true;
// this is a selection of file types that are certainly not // this is a selection of file types that are certainly not
// further compressible. we need not include every type under the sun - // further compressible. we need not include every type under the sun -

View File

@ -54,239 +54,6 @@
// convenience "class" that simplifies successively appending a filename to
// its parent directory. this avoids needing to allocate memory and calling
// strlen/strcat. used by wdll_ver and dir_next_ent.
// we want to maintain C compatibility, so this isn't a C++ class.
// write the given directory path into our buffer and set end/chars_left
// accordingly. <dir> need not but can end with a directory separator.
//
// note: <dir> and the filename set via pp_append_file are separated by
// '/'. this is to allow use on portable paths; the function otherwise
// does not care if paths are relative/portable/absolute.
LibError pp_set_dir(PathPackage* pp, const char* dir)
{
// -1 allows for trailing DIR_SEP that will be added if not
// already present.
if(strcpy_s(pp->path, ARRAY_SIZE(pp->path)-1, dir) != 0)
WARN_RETURN(ERR_PATH_LENGTH);
size_t len = strlen(pp->path);
// add directory separator if not already present
// .. but only check this if dir != "" (=> len-1 is safe)
if(len != 0)
{
char* last_char = pp->path+len-1;
// note: must handle both portable and native separators -
// <dir> may be either.
if(*last_char != '/' && *last_char != DIR_SEP)
{
*(last_char+1) = '/';
// note: need to 0-terminate because pp.path is uninitialized
// and we overwrite strcpy_s's terminator above.
*(last_char+2) = '\0';
// only bump by 1 - filename must overwrite '\0'.
len++;
}
}
pp->end = pp->path+len;
pp->chars_left = ARRAY_SIZE(pp->path)-len;
return ERR_OK;
}
// append the given filename to the directory established by the last
// pp_set_dir on this package. the whole path is accessible at pp->path.
LibError pp_append_file(PathPackage* pp, const char* fn)
{
CHECK_ERR(strcpy_s(pp->end, pp->chars_left, fn));
return ERR_OK;
}
//-----------------------------------------------------------------------------
// 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;
}
}
enum Conversion
{
TO_NATIVE,
TO_PORTABLE
};
static LibError convert_path(char* dst, const char* src, Conversion conv = TO_NATIVE)
{
// DIR_SEP is assumed to be a single character!
const char* s = src;
char* d = dst;
char from = DIR_SEP, to = '/';
if(conv == TO_NATIVE)
from = '/', to = DIR_SEP;
size_t len = 0;
for(;;)
{
len++;
if(len >= PATH_MAX)
WARN_RETURN(ERR_PATH_LENGTH);
char c = *s++;
if(c == from)
c = to;
*d++ = c;
// end of string - done
if(c == '\0')
return ERR_OK;
}
}
// set by file_set_root_dir
static char n_root_dir[PATH_MAX];
static size_t n_root_dir_len;
// return the native equivalent of the given relative portable path
// (i.e. convert all '/' to the platform's directory separator)
// makes sure length < PATH_MAX.
LibError file_make_native_path(const char* path, char* n_path)
{
return convert_path(n_path, path, TO_NATIVE);
}
// return the portable equivalent of the given relative native path
// (i.e. convert the platform's directory separators to '/')
// makes sure length < PATH_MAX.
LibError file_make_portable_path(const char* n_path, char* path)
{
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.
LibError file_make_full_native_path(const char* path, char* n_full_path)
{
debug_assert(path != n_full_path); // doesn't work in-place
strcpy_s(n_full_path, PATH_MAX, 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 the file root dir).
// makes sure length < PATH_MAX.
LibError file_make_full_portable_path(const char* n_full_path, char* path)
{
debug_assert(path != n_full_path); // doesn't work in-place
if(strncmp(n_full_path, n_root_dir, n_root_dir_len) != 0)
WARN_RETURN(ERR_PATH_NOT_FOUND);
return convert_path(path, n_full_path+n_root_dir_len, TO_PORTABLE);
}
// establish the root directory from <rel_path>, which is treated as
// relative to the executable's directory (determined via argv[0]).
// all relative file paths passed to this module will be based from
// this root dir.
//
// example: executable in "$install_dir/system"; desired root dir is
// "$install_dir/data" => rel_path = "../data".
//
// argv[0] 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.
LibError file_set_root_dir(const char* argv0, const char* rel_path)
{
// 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.
static bool already_attempted;
if(already_attempted)
WARN_RETURN(ERR_ROOT_DIR_ALREADY_SET);
already_attempted = true;
// get full path to executable
char n_path[PATH_MAX];
// .. first try safe, but system-dependent version
if(sys_get_executable_name(n_path, PATH_MAX) < 0)
{
// .. failed; use argv[0]
if(!realpath(argv0, n_path))
return LibError_from_errno();
}
// make sure it's valid
if(access(n_path, X_OK) < 0)
return LibError_from_errno();
// strip executable name, append rel_path, convert to native
char* slash = strrchr(n_path, DIR_SEP);
// .. safely handle n_path not containing DIR_SEP (not expected)
if(!slash) slash = n_path-1;
RETURN_ERR(file_make_native_path(rel_path, slash+1));
// get actual root dir - previous n_path may include ".."
// (slight optimization, speeds up path lookup)
if(!realpath(n_path, n_root_dir))
return LibError_from_errno();
// .. append DIR_SEP to simplify code that uses n_root_dir
// (note: already 0-terminated, since it's static)
n_root_dir_len = strlen(n_root_dir)+1; // +1 for trailing DIR_SEP
n_root_dir[n_root_dir_len-1] = DIR_SEP;
return ERR_OK;
}
//-----------------------------------------------------------------------------
// layer on top of POSIX opendir/readdir/closedir that handles // layer on top of POSIX opendir/readdir/closedir that handles
// portable -> native path conversion, ignores non-file/directory entries, // portable -> native path conversion, ignores non-file/directory entries,
// and additionally returns the file status (size and mtime). // and additionally returns the file status (size and mtime).
@ -332,7 +99,7 @@ LibError dir_open(const char* P_path, DirIterator* d_)
if(!d->os_dir) if(!d->os_dir)
return LibError_from_errno(); return LibError_from_errno();
RETURN_ERR(pp_set_dir(&d->pp, n_path)); RETURN_ERR(path_package_set_dir(&d->pp, n_path));
return ERR_OK; return ERR_OK;
} }
@ -357,7 +124,7 @@ get_another_entry:
// copy os_ent.name[]; we need it for stat() #if !OS_WIN and // copy os_ent.name[]; we need it for stat() #if !OS_WIN and
// return it as ent.name (since os_ent.name[] is volatile). // return it as ent.name (since os_ent.name[] is volatile).
pp_append_file(&d->pp, os_ent->d_name); path_package_append_file(&d->pp, os_ent->d_name);
const char* name = d->pp.end; const char* name = d->pp.end;
// get file information (mode, size, mtime) // get file information (mode, size, mtime)
@ -368,7 +135,7 @@ get_another_entry:
CHECK_ERR(readdir_stat_np(d->os_dir, &s)); CHECK_ERR(readdir_stat_np(d->os_dir, &s));
#else #else
// .. call regular stat(). // .. call regular stat().
// we need the full pathname for this. don't use vfs_path_append because // we need the full pathname for this. don't use path_append because
// it would unnecessarily call strlen. // it would unnecessarily call strlen.
CHECK_ERR(stat(d->pp.path, &s)); CHECK_ERR(stat(d->pp.path, &s));
@ -404,90 +171,8 @@ LibError dir_close(DirIterator* d_)
} }
static bool dirent_less(const DirEnt& d1, const DirEnt& d2)
{
return strcmp(d1.name, d2.name) < 0;
}
// enumerate all directory entries in <P_path>; add to container and
// then sort it by filename.
LibError file_get_sorted_dirents(const char* P_path, DirEnts& dirents)
{
DirIterator d;
RETURN_ERR(dir_open(P_path, &d));
dirents.reserve(50); // preallocate for efficiency
DirEnt ent;
for(;;)
{
LibError ret = dir_next_ent(&d, &ent);
if(ret == ERR_DIR_END)
break;
RETURN_ERR(ret);
ent.name = file_make_unique_fn_copy(ent.name);
dirents.push_back(ent);
}
std::sort(dirents.begin(), dirents.end(), dirent_less);
(void)dir_close(&d);
return ERR_OK;
}
// call <cb> for each file and subdirectory in <dir> (alphabetical order),
// passing the entry name (not full path!), stat info, and <user>.
//
// 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.
//
// rationale:
// this makes file_enum and zip_enum slightly incompatible, since zip_enum
// returns the full path. that's necessary because VFS zip_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_ent 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).
LibError file_enum(const char* P_path, const FileCB cb, const uintptr_t user)
{
LibError stat_err = ERR_OK; // first error encountered by stat()
LibError cb_err = ERR_OK; // first error returned by cb
DirEnts dirents;
RETURN_ERR(file_get_sorted_dirents(P_path, dirents));
// call back for each entry (now sorted);
// first, expand each DirEnt to full struct stat (we store as such to
// reduce memory use and therefore speed up sorting)
struct stat s;
memset(&s, 0, sizeof(s));
// .. not needed for plain files (OS opens them; memento doesn't help)
const uintptr_t memento = 0;
for(DirEntCIt it = dirents.begin(); it != dirents.end(); ++it)
{
const DirEnt& dirent = *it;
s.st_mode = (dirent.size == -1)? S_IFDIR : S_IFREG;
s.st_size = dirent.size;
s.st_mtime = dirent.mtime;
LibError ret = cb(dirent.name, &s, memento, user);
if(ret != INFO_CB_CONTINUE)
{
cb_err = ret; // first error (since we now abort)
break;
}
}
if(cb_err != ERR_OK)
return cb_err;
return stat_err;
}
// get file information. output param is zeroed on error. // get file information. output param is zeroed on error.
static LibError file_stat_impl(const char* fn, struct stat* s, bool warn_if_failed = true) static LibError file_stat_impl(const char* fn, struct stat* s, bool warn_if_failed = true)
@ -556,6 +241,8 @@ LibError file_delete(const char* fn)
// and the Handle approach doesn't guard against some idiot calling // and the Handle approach doesn't guard against some idiot calling
// close(our_fd_value) directly, either. // close(our_fd_value) directly, either.
cassert(sizeof(PosixFile) < FILE_OPAQUE_SIZE);
LibError file_validate(const File* f) LibError file_validate(const File* f)
{ {
@ -573,87 +260,6 @@ LibError file_validate(const File* f)
return ERR_OK; return ERR_OK;
} }
// rationale: we want a constant-time IsAtomFn(string pointer) lookup:
// this avoids any overhead of calling file_make_unique_fn_copy on
// already-atomized strings. that requires allocating from one contiguous
// arena, which is also more memory-efficient than the heap (no headers).
static Pool atom_pool;
// allocate a copy of P_fn in our string pool. strings are equal iff
// their addresses are equal, thus allowing fast comparison.
//
// if the (generous) filename storage is full, 0 is returned.
// this is not ever expected to happen; callers need not check the
// return value because a warning is raised anyway.
const char* file_make_unique_fn_copy(const char* P_fn)
{
// early out: if already an atom, return immediately.
if(pool_contains(&atom_pool, (void*)P_fn))
return P_fn;
const size_t fn_len = strlen(P_fn);
const char* unique_fn;
// check if already allocated; return existing copy if so.
//
// rationale: the entire storage could be done via container,
// rather than simply using it as a lookup mapping.
// however, DynHashTbl together with Pool (see above) is more efficient.
typedef DynHashTbl<const char*, const char*> AtomMap;
static AtomMap atom_map;
unique_fn = atom_map.find(P_fn);
if(unique_fn)
return unique_fn;
unique_fn = (const char*)pool_alloc(&atom_pool, fn_len+1);
if(!unique_fn)
{
DEBUG_WARN_ERR(ERR_NO_MEM);
return 0;
}
memcpy2((void*)unique_fn, P_fn, fn_len);
((char*)unique_fn)[fn_len] = '\0';
atom_map.insert(unique_fn, unique_fn);
stats_unique_name(fn_len);
return unique_fn;
}
const char* file_get_random_name()
{
// there had better be names in atom_pool, else this will fail.
debug_assert(atom_pool.da.pos != 0);
again:
const size_t start_ofs = (size_t)rand(0, (uint)atom_pool.da.pos-1);
// scan ahead to next string boundary
const char* start = (const char*)atom_pool.da.base+start_ofs;
const char* next_0 = strchr(start, '\0')+1;
// .. at end of storage: restart
if((u8*)next_0 >= atom_pool.da.base+atom_pool.da.pos)
goto again;
// .. skip all '\0' (may be several due to pool alignment)
const char* next_name = next_0;
while(*next_name == '\0') next_name++;
return next_name;
}
static inline void atom_init()
{
pool_create(&atom_pool, 8*MiB, POOL_VARIABLE_ALLOCS);
}
static inline void atom_shutdown()
{
(void)pool_destroy(&atom_pool);
}
LibError file_open(const char* P_fn, uint flags, File* f) LibError file_open(const char* P_fn, uint flags, File* f)
{ {
@ -853,7 +459,7 @@ LibError file_unmap(File* f)
LibError file_init() LibError file_init()
{ {
atom_init(); path_init();
file_cache_init(); file_cache_init();
file_io_init(); file_io_init();
@ -866,7 +472,7 @@ LibError file_init()
LibError file_shutdown() LibError file_shutdown()
{ {
stats_dump(); stats_dump();
atom_shutdown(); path_shutdown();
file_io_shutdown(); file_io_shutdown();
return ERR_OK; return ERR_OK;
} }

View File

@ -28,35 +28,6 @@
extern LibError file_init(); extern LibError file_init();
// convenience "class" that simplifies successively appending a filename to
// its parent directory. this avoids needing to allocate memory and calling
// strlen/strcat. used by wdll_ver and dir_next_ent.
// we want to maintain C compatibility, so this isn't a C++ class.
struct PathPackage
{
char* end;
size_t chars_left;
char path[PATH_MAX];
};
// write the given directory path into our buffer and set end/chars_left
// accordingly. <dir> need not but can end with a directory separator.
//
// note: <dir> and the filename set via pp_append_file are separated by
// '/'. this is to allow use on portable paths; the function otherwise
// does not care if paths are relative/portable/absolute.
extern LibError pp_set_dir(PathPackage* pp, const char* dir);
// append the given filename to the directory established by the last
// pp_set_dir on this package. the whole path is accessible at pp->path.
extern LibError pp_append_file(PathPackage* pp, const char* file);
// is s2 a subpath of s1, or vice versa? used by VFS and wdir_watch.
// works for portable and native paths.
extern bool file_is_subpath(const char* s1, const char* s2);
// //
// path conversion functions (native <--> portable), // path conversion functions (native <--> portable),

View File

@ -20,6 +20,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/ */
#include "lib/path_util.h"
#include "path.h"
#include "file.h" #include "file.h"
#include "file_cache.h" #include "file_cache.h"
#include "file_io.h" #include "file_io.h"
@ -31,7 +34,6 @@
#include "archive.h" #include "archive.h"
#include "vfs.h" #include "vfs.h"
#include "vfs_path.h"
#include "vfs_mount.h" #include "vfs_mount.h"
#include "vfs_tree.h" #include "vfs_tree.h"
#include "vfs_redirector.h" #include "vfs_redirector.h"
@ -69,4 +71,3 @@ struct PosixFile
void* mapping; void* mapping;
uint map_refs; uint map_refs;
}; };
cassert(sizeof(PosixFile) < FILE_OPAQUE_SIZE);

View File

@ -0,0 +1,274 @@
/**
* =========================================================================
* File : path.cpp
* Project : 0 A.D.
* Description : helper functions for VFS paths.
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2004-2006 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* 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.
*/
#include "precompiled.h"
#include <string.h>
#include "lib.h"
#include "adts.h"
#include "file_internal.h"
#include "allocators.h"
// path types:
// p_*: posix (e.g. mount object name or for open())
// v_*: vfs (e.g. mount point)
// fn : filename only (e.g. from readdir)
// dir_name: directory only, no path (e.g. subdir name)
//
// all paths must be relative (no leading '/'); components are separated
// by '/'; no ':', '\\', "." or ".." allowed; root dir is "".
//
// grammar:
// path ::= dir*file?
// dir ::= name/
// file ::= name
// name ::= [^/]
enum Conversion
{
TO_NATIVE,
TO_PORTABLE
};
static LibError convert_path(char* dst, const char* src, Conversion conv = TO_NATIVE)
{
// DIR_SEP is assumed to be a single character!
const char* s = src;
char* d = dst;
char from = DIR_SEP, to = '/';
if(conv == TO_NATIVE)
from = '/', to = DIR_SEP;
size_t len = 0;
for(;;)
{
len++;
if(len >= PATH_MAX)
WARN_RETURN(ERR_PATH_LENGTH);
char c = *s++;
if(c == from)
c = to;
*d++ = c;
// end of string - done
if(c == '\0')
return ERR_OK;
}
}
// set by file_set_root_dir
static char n_root_dir[PATH_MAX];
static size_t n_root_dir_len;
// return the native equivalent of the given relative portable path
// (i.e. convert all '/' to the platform's directory separator)
// makes sure length < PATH_MAX.
LibError file_make_native_path(const char* path, char* n_path)
{
return convert_path(n_path, path, TO_NATIVE);
}
// return the portable equivalent of the given relative native path
// (i.e. convert the platform's directory separators to '/')
// makes sure length < PATH_MAX.
LibError file_make_portable_path(const char* n_path, char* path)
{
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.
LibError file_make_full_native_path(const char* path, char* n_full_path)
{
debug_assert(path != n_full_path); // doesn't work in-place
strcpy_s(n_full_path, PATH_MAX, 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 the file root dir).
// makes sure length < PATH_MAX.
LibError file_make_full_portable_path(const char* n_full_path, char* path)
{
debug_assert(path != n_full_path); // doesn't work in-place
if(strncmp(n_full_path, n_root_dir, n_root_dir_len) != 0)
WARN_RETURN(ERR_PATH_NOT_FOUND);
return convert_path(path, n_full_path+n_root_dir_len, TO_PORTABLE);
}
// establish the root directory from <rel_path>, which is treated as
// relative to the executable's directory (determined via argv[0]).
// all relative file paths passed to this module will be based from
// this root dir.
//
// example: executable in "$install_dir/system"; desired root dir is
// "$install_dir/data" => rel_path = "../data".
//
// argv[0] 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.
LibError file_set_root_dir(const char* argv0, const char* rel_path)
{
// 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.
static bool already_attempted;
if(already_attempted)
WARN_RETURN(ERR_ROOT_DIR_ALREADY_SET);
already_attempted = true;
// get full path to executable
char n_path[PATH_MAX];
// .. first try safe, but system-dependent version
if(sys_get_executable_name(n_path, PATH_MAX) < 0)
{
// .. failed; use argv[0]
if(!realpath(argv0, n_path))
return LibError_from_errno();
}
// make sure it's valid
if(access(n_path, X_OK) < 0)
return LibError_from_errno();
// strip executable name, append rel_path, convert to native
char* start_of_fn = (char*)path_name_only(n_path);
RETURN_ERR(file_make_native_path(rel_path, start_of_fn));
// get actual root dir - previous n_path may include ".."
// (slight optimization, speeds up path lookup)
if(!realpath(n_path, n_root_dir))
return LibError_from_errno();
// .. append DIR_SEP to simplify code that uses n_root_dir
// (note: already 0-terminated, since it's static)
n_root_dir_len = strlen(n_root_dir)+1; // +1 for trailing DIR_SEP
n_root_dir[n_root_dir_len-1] = DIR_SEP;
return ERR_OK;
}
//-----------------------------------------------------------------------------
// storage for path strings
//-----------------------------------------------------------------------------
// rationale: we want a constant-time IsAtomFn(string pointer) lookup:
// this avoids any overhead of calling file_make_unique_fn_copy on
// already-atomized strings. that requires allocating from one contiguous
// arena, which is also more memory-efficient than the heap (no headers).
static Pool atom_pool;
// allocate a copy of P_fn in our string pool. strings are equal iff
// their addresses are equal, thus allowing fast comparison.
//
// if the (generous) filename storage is full, 0 is returned.
// this is not ever expected to happen; callers need not check the
// return value because a warning is raised anyway.
const char* file_make_unique_fn_copy(const char* P_fn)
{
// early out: if already an atom, return immediately.
if(pool_contains(&atom_pool, (void*)P_fn))
return P_fn;
const size_t fn_len = strlen(P_fn);
const char* unique_fn;
// check if already allocated; return existing copy if so.
//
// rationale: the entire storage could be done via container,
// rather than simply using it as a lookup mapping.
// however, DynHashTbl together with Pool (see above) is more efficient.
typedef DynHashTbl<const char*, const char*> AtomMap;
static AtomMap atom_map;
unique_fn = atom_map.find(P_fn);
if(unique_fn)
return unique_fn;
unique_fn = (const char*)pool_alloc(&atom_pool, fn_len+1);
if(!unique_fn)
{
DEBUG_WARN_ERR(ERR_NO_MEM);
return 0;
}
memcpy2((void*)unique_fn, P_fn, fn_len);
((char*)unique_fn)[fn_len] = '\0';
atom_map.insert(unique_fn, unique_fn);
stats_unique_name(fn_len);
return unique_fn;
}
void path_init()
{
pool_create(&atom_pool, 8*MiB, POOL_VARIABLE_ALLOCS);
}
void path_shutdown()
{
(void)pool_destroy(&atom_pool);
}
const char* file_get_random_name()
{
// there had better be names in atom_pool, else this will fail.
debug_assert(atom_pool.da.pos != 0);
again:
const size_t start_ofs = (size_t)rand(0, (uint)atom_pool.da.pos-1);
// scan ahead to next string boundary
const char* start = (const char*)atom_pool.da.base+start_ofs;
const char* next_0 = strchr(start, '\0')+1;
// .. at end of storage: restart
if((u8*)next_0 >= atom_pool.da.base+atom_pool.da.pos)
goto again;
// .. skip all '\0' (may be several due to pool alignment)
const char* next_name = next_0;
while(*next_name == '\0') next_name++;
return next_name;
}

View File

@ -1,6 +1,6 @@
/** /**
* ========================================================================= * =========================================================================
* File : vfs_path.h * File : path.h
* Project : 0 A.D. * Project : 0 A.D.
* Description : helper functions for VFS paths. * Description : helper functions for VFS paths.
* *
@ -9,7 +9,7 @@
*/ */
/* /*
* Copyright (c) 2004-2005 Jan Wassenberg * Copyright (c) 2004-2006 Jan Wassenberg
* *
* Redistribution and/or modification are also permitted under the * Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the * terms of the GNU General Public License as published by the
@ -50,13 +50,16 @@ extern void path_dir_only(const char* V_src_fn, char* V_dir_only);
// return pointer to the name component within V_src_fn // return pointer to the name component within V_src_fn
extern const char* path_name_only(const char* V_src_fn); extern const char* path_name_only(const char* V_src_fn);
extern const char* path_extension(const char* fn);
extern void path_strip_fn(char* fn);
struct NextNumberedFilenameInfo struct NextNumberedFilenameInfo
{ {
int next_num; int next_num;
}; };
// fill V_next_fn (which must be big enough for VFS_MAX_PATH chars) with // fill V_next_fn (which must be big enough for PATH_MAX chars) with
// the next numbered filename according to the pattern defined by V_fn_fmt. // the next numbered filename according to the pattern defined by V_fn_fmt.
// <nfi> must be initially zeroed (e.g. by defining as static) and passed // <nfi> must be initially zeroed (e.g. by defining as static) and passed
// each time. // each time.
@ -70,5 +73,7 @@ struct NextNumberedFilenameInfo
extern void next_numbered_filename(const char* V_fn_fmt, extern void next_numbered_filename(const char* V_fn_fmt,
NextNumberedFilenameInfo* nfi, char* V_next_fn, bool use_vfs = true); NextNumberedFilenameInfo* nfi, char* V_next_fn, bool use_vfs = true);
extern void path_init();
extern void path_shutdown();
#endif // #ifndef VFS_PATH_H__ #endif // #ifndef VFS_PATH_H__

View File

@ -123,8 +123,8 @@ static LibError VDir_reload(VDir* vd, const char* path, Handle UNUSED(hvd))
{ {
// add required trailing slash if not already present to make // add required trailing slash if not already present to make
// caller's life easier. // caller's life easier.
char V_path_slash[VFS_MAX_PATH]; char V_path_slash[PATH_MAX];
RETURN_ERR(vfs_path_append(V_path_slash, path, "")); RETURN_ERR(path_append(V_path_slash, path, ""));
RETURN_ERR(tree_dir_open(V_path_slash, &vd->it)); RETURN_ERR(tree_dir_open(V_path_slash, &vd->it));
vd->it_valid = 1; vd->it_valid = 1;
@ -250,7 +250,7 @@ LibError vfs_dir_enum(const char* start_path, uint flags, const char* user_filte
debug_assert((flags & ~(VFS_DIR_RECURSIVE)) == 0); debug_assert((flags & ~(VFS_DIR_RECURSIVE)) == 0);
const bool recursive = (flags & VFS_DIR_RECURSIVE) != 0; const bool recursive = (flags & VFS_DIR_RECURSIVE) != 0;
char filter_buf[VFS_MAX_PATH]; char filter_buf[PATH_MAX];
const char* filter = user_filter; const char* filter = user_filter;
bool user_filter_wants_dirs = true; bool user_filter_wants_dirs = true;
if(user_filter) if(user_filter)
@ -281,7 +281,7 @@ LibError vfs_dir_enum(const char* start_path, uint flags, const char* user_filte
// note: can't refer to the queue contents - those are invalidated // note: can't refer to the queue contents - those are invalidated
// as soon as a directory is pushed onto it. // as soon as a directory is pushed onto it.
PathPackage pp; PathPackage pp;
(void)pp_set_dir(&pp, dir_queue.front()); (void)path_package_set_dir(&pp, dir_queue.front());
dir_queue.pop(); dir_queue.pop();
Handle hdir = vfs_dir_open(pp.path); Handle hdir = vfs_dir_open(pp.path);
@ -296,7 +296,7 @@ LibError vfs_dir_enum(const char* start_path, uint flags, const char* user_filte
while(vfs_dir_next_ent(hdir, &ent, filter) == 0) while(vfs_dir_next_ent(hdir, &ent, filter) == 0)
{ {
// build complete path (DirEnt only stores entry name) // build complete path (DirEnt only stores entry name)
(void)pp_append_file(&pp, ent.name); (void)path_package_append_file(&pp, ent.name);
const char* atom_path = file_make_unique_fn_copy(pp.path); const char* atom_path = file_make_unique_fn_copy(pp.path);
if(DIRENT_IS_DIR(&ent)) if(DIRENT_IS_DIR(&ent))
@ -772,17 +772,57 @@ LibError vfs_reload(const char* fn)
return reload_without_rebuild(fn); return reload_without_rebuild(fn);
} }
// array of reloads requested this frame (see 'do we really need to
// reload' below). go through gyrations to avoid heap allocs.
const size_t MAX_RELOADS_PER_FRAME = 12;
typedef char Path[PATH_MAX];
typedef Path PathList[MAX_RELOADS_PER_FRAME];
// do we really need to reload? try to avoid the considerable cost of
// rebuilding VFS and scanning all Handles.
static bool can_ignore_reload(const char* V_path, PathList pending_reloads, uint num_pending)
{
// note: be careful to avoid 'race conditions' depending on the
// timeframe in which notifications reach us.
// example: editor deletes a.tga; we are notified; reload is
// triggered but fails since the file isn't found; further
// notifications (e.g. renamed a.tmp to a.tga) come within x [ms] and
// are ignored due to a time limit.
// therefore, we can only check for multiple reload requests a frame;
// to that purpose, an array is built and duplicates ignored.
const char* ext = path_extension(V_path);
// .. directory change notification; ignore because we get
// per-file notifications anyway. (note: assume no extension =>
// it's a directory).
if(ext[0] == '\0')
return true;
// .. compiled XML files the engine writes out by the hundreds;
// skipping them is a big performance gain.
if(!stricmp(ext, "xmb"))
return true;
// .. temp files, usually created when an editor saves a file
// (delete, create temp, rename temp); no need to reload those.
if(!stricmp(ext, "tmp"))
return true;
// .. more than one notification for a file; only reload once.
// note: this doesn't suffer from the 'reloaded too early'
// problem described above; if there's more than one
// request in the array, the file has since been written.
for(uint i = 0; i < num_pending; i++)
{
if(!strcmp(pending_reloads[i], V_path))
return true;
}
return false;
}
// get directory change notifications, and reload all affected files. // get directory change notifications, and reload all affected files.
// must be called regularly (e.g. once a frame). this is much simpler // must be called regularly (e.g. once a frame). this is much simpler
// than asynchronous notifications: everything would need to be thread-safe. // than asynchronous notifications: everything would need to be thread-safe.
LibError vfs_reload_changed_files() LibError vfs_reload_changed_files()
{ {
// array of reloads requested this frame (see 'do we really need to
// reload' below). go through gyrations to avoid heap allocs.
const size_t MAX_RELOADS_PER_FRAME = 12;
typedef char Path[VFS_MAX_PATH];
typedef Path PathList[MAX_RELOADS_PER_FRAME];
PathList pending_reloads; PathList pending_reloads;
uint num_pending = 0; uint num_pending = 0;
@ -803,38 +843,8 @@ LibError vfs_reload_changed_files()
char* V_path = pending_reloads[num_pending]; char* V_path = pending_reloads[num_pending];
CHECK_ERR(mount_make_vfs_path(P_path, V_path)); CHECK_ERR(mount_make_vfs_path(P_path, V_path));
// do we really need to reload? try to avoid the considerable cost of if(can_ignore_reload(V_path, pending_reloads, num_pending))
// rebuilding VFS and scanning all Handles.
//
// note: be careful to avoid 'race conditions' depending on the
// timeframe in which notifications reach us.
// example: editor deletes a.tga; we are notified; reload is
// triggered but fails since the file isn't found; further
// notifications (e.g. renamed a.tmp to a.tga) come within x [ms] and
// are ignored due to a time limit.
// therefore, we can only check for multiple reload requests a frame;
// to that purpose, an array is built and duplicates ignored.
const char* ext = strrchr(V_path, '.');
// .. directory change notification; ignore because we get
// per-file notifications anyway. (note: assume no extension =>
// it's a directory). this also protects the strcmp calls below.
if(!ext)
continue; continue;
// .. compiled XML files the engine writes out by the hundreds;
// skipping them is a big performance gain.
if(!stricmp(ext, ".xmb"))
continue;
// .. temp files, usually created when an editor saves a file
// (delete, create temp, rename temp); no need to reload those.
if(!stricmp(ext, ".tmp"))
continue;
// .. more than one notification for a file; only reload once.
// note: this doesn't suffer from the 'reloaded too early'
// problem described above; if there's more than one
// request in the array, the file has since been written.
for(uint i = 0; i < num_pending; i++)
if(!strcmp(pending_reloads[i], V_path))
continue;
// path has already been written to pending_reloads, // path has already been written to pending_reloads,
// so just mark it valid. // so just mark it valid.

View File

@ -227,19 +227,21 @@ extern void vfs_display(void);
// paths // paths
// //
// the VFS doesn't require this length restriction - VFS internal storage // note: the VFS doesn't specify any path length restriction -
// is not fixed-length. the purpose here is to give an indication of how // internal filename storage is not fixed-length.
// large fixed-size user buffers should be. length includes trailing '\0'. // for an an indication of how large fixed-size user buffers should be,
#define VFS_MAX_PATH 256 // use PATH_MAX.
// convenience function // convenience function
extern void vfs_path_copy(char* dst, const char* src); extern void path_copy(char* dst, const char* src);
// combine <path1> and <path2> into one path, and write to <dst>. // combine <path1> and <path2> into one path, and write to <dst>.
// if necessary, a directory separator is added between the paths. // if necessary, a directory separator is added between the paths.
// each may be empty, filenames, or full paths. // each may be empty, filenames, or full paths.
// total path length (including '\0') must not exceed VFS_MAX_PATH. // total path length (including '\0') must not exceed PATH_MAX.
extern LibError vfs_path_append(char* dst, const char* path1, const char* path2); extern LibError path_append(char* dst, const char* path1, const char* path2);
extern const char* path_extension(const char* fn);
// VFS paths are of the form: "(dir/)*file?" // VFS paths are of the form: "(dir/)*file?"
// in English: '/' as path separator; trailing '/' required for dir names; // in English: '/' as path separator; trailing '/' required for dir names;

View File

@ -227,7 +227,7 @@ static LibError afile_cb(const char* atom_fn, const struct stat* s, uintptr_t me
CHECK_PATH(atom_fn); CHECK_PATH(atom_fn);
const char* name = path_name_only(atom_fn); const char* name = path_name_only(atom_fn);
char path[VFS_MAX_PATH]; char path[PATH_MAX];
path_dir_only(atom_fn, path); path_dir_only(atom_fn, path);
const char* atom_path = file_make_unique_fn_copy(path); const char* atom_path = file_make_unique_fn_copy(path);
@ -288,7 +288,7 @@ static LibError enqueue_archive(const char* name, const char* P_archive_dir, Arc
// this doesn't (need to) work for subdirectories of the mounted td! // this doesn't (need to) work for subdirectories of the mounted td!
// we can't use mount_get_path because we don't have the VFS path. // we can't use mount_get_path because we don't have the VFS path.
char P_path[PATH_MAX]; char P_path[PATH_MAX];
RETURN_ERR(vfs_path_append(P_path, P_archive_dir, name)); RETURN_ERR(path_append(P_path, P_archive_dir, name));
// just open the Zip file and see if it's valid. we don't bother // just open the Zip file and see if it's valid. we don't bother
// checking the extension because archives won't necessarily be // checking the extension because archives won't necessarily be
@ -374,7 +374,7 @@ static LibError enqueue_dir(TDir* parent_td, const char* name,
// prepend parent path to get complete pathname. // prepend parent path to get complete pathname.
char P_path[PATH_MAX]; char P_path[PATH_MAX];
CHECK_ERR(vfs_path_append(P_path, P_parent_path, name)); CHECK_ERR(path_append(P_path, P_parent_path, name));
// create subdirectory.. // create subdirectory..
TDir* td; TDir* td;
@ -429,7 +429,7 @@ static LibError add_ent(TDir* td, DirEnt* ent, const char* P_parent_path, const
{ {
// prepend parent path to get complete pathname. // prepend parent path to get complete pathname.
char V_path[PATH_MAX]; char V_path[PATH_MAX];
CHECK_ERR(vfs_path_append(V_path, tfile_get_atom_fn((TFile*)td), name)); CHECK_ERR(path_append(V_path, tfile_get_atom_fn((TFile*)td), name));
const char* atom_fn = file_make_unique_fn_copy(V_path); const char* atom_fn = file_make_unique_fn_copy(V_path);
vfs_opt_notify_loose_file(atom_fn); vfs_opt_notify_loose_file(atom_fn);
@ -639,7 +639,7 @@ LibError vfs_mount(const char* V_mount_point, const char* P_real_path, uint flag
// no matter if it's an archive - still shouldn't be a "subpath". // no matter if it's an archive - still shouldn't be a "subpath".
for(MountIt it = mounts.begin(); it != mounts.end(); ++it) for(MountIt it = mounts.begin(); it != mounts.end(); ++it)
{ {
if(file_is_subpath(P_real_path, it->P_name.c_str())) if(path_is_subpath(P_real_path, it->P_name.c_str()))
WARN_RETURN(ERR_ALREADY_MOUNTED); WARN_RETURN(ERR_ALREADY_MOUNTED);
} }

View File

@ -601,8 +601,7 @@ class IsArchive
public: public:
IsArchive(const char* archive_fn) IsArchive(const char* archive_fn)
{ {
archive_ext = strrchr(archive_fn, '.'); archive_ext = path_extension(archive_fn);
if(!archive_ext) archive_ext = ""; // for safe comparison
} }
bool operator()(DirEnt& ent) const bool operator()(DirEnt& ent) const
@ -612,8 +611,7 @@ public:
return true; return true;
// remove if not same extension // remove if not same extension
const char* ext = strrchr(ent.name, '.'); const char* ext = path_extension(ent.name);
if(!ext) ext = ""; // for safe comparison
if(stricmp(archive_ext, ext) != 0) if(stricmp(archive_ext, ext) != 0)
return true; return true;
@ -633,7 +631,7 @@ static LibError vfs_opt_init(const char* trace_filename, const char* archive_fn_
// note: this is needed by should_rebuild_main_archive and later in // note: this is needed by should_rebuild_main_archive and later in
// vfs_opt_continue; must be done here instead of inside the former // vfs_opt_continue; must be done here instead of inside the former
// because that is not called when force_build == true. // because that is not called when force_build == true.
char dir[VFS_MAX_PATH]; char dir[PATH_MAX];
path_dir_only(archive_fn_fmt, dir); path_dir_only(archive_fn_fmt, dir);
RETURN_ERR(file_get_sorted_dirents(dir, existing_archives)); RETURN_ERR(file_get_sorted_dirents(dir, existing_archives));
DirEntIt new_end = std::remove_if(existing_archives.begin(), existing_archives.end(), IsArchive(archive_fn)); DirEntIt new_end = std::remove_if(existing_archives.begin(), existing_archives.end(), IsArchive(archive_fn));
@ -682,13 +680,13 @@ static int vfs_opt_continue()
// delete old archives // delete old archives
PathPackage pp; // need path to each existing_archive, not only name PathPackage pp; // need path to each existing_archive, not only name
{ {
char archive_dir[VFS_MAX_PATH]; char archive_dir[PATH_MAX];
path_dir_only(archive_fn, archive_dir); path_dir_only(archive_fn, archive_dir);
(void)pp_set_dir(&pp, archive_dir); (void)path_package_set_dir(&pp, archive_dir);
} }
for(DirEntCIt it = existing_archives.begin(); it != existing_archives.end(); ++it) for(DirEntCIt it = existing_archives.begin(); it != existing_archives.end(); ++it)
{ {
(void)pp_append_file(&pp, it->name); (void)path_package_append_file(&pp, it->name);
(void)file_delete(pp.path); (void)file_delete(pp.path);
} }
@ -731,7 +729,7 @@ static LibError build_mini_archive(const char* mini_archive_fn_fmt)
V_fns[loose_files.size()] = 0; // terminator V_fns[loose_files.size()] = 0; // terminator
// get new unused mini archive name at P_dst_path // get new unused mini archive name at P_dst_path
char mini_archive_fn[VFS_MAX_PATH]; char mini_archive_fn[PATH_MAX];
static NextNumberedFilenameInfo nfi; static NextNumberedFilenameInfo nfi;
bool use_vfs = false; // can't use VFS for archive files bool use_vfs = false; // can't use VFS for archive files
next_numbered_filename(mini_archive_fn_fmt, &nfi, mini_archive_fn, use_vfs); next_numbered_filename(mini_archive_fn_fmt, &nfi, mini_archive_fn, use_vfs);

View File

@ -1,258 +0,0 @@
/**
* =========================================================================
* File : vfs_path.cpp
* Project : 0 A.D.
* Description : helper functions for VFS paths.
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2004-2005 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* 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.
*/
#include "precompiled.h"
#include <string.h>
#include "lib.h"
#include "file_internal.h"
// path types:
// p_*: posix (e.g. mount object name or for open())
// v_*: vfs (e.g. mount point)
// fn : filename only (e.g. from readdir)
// dir_name: directory only, no path (e.g. subdir name)
//
// all paths must be relative (no leading '/'); components are separated
// by '/'; no ':', '\\', "." or ".." allowed; root dir is "".
//
// grammar:
// path ::= dir*file?
// dir ::= name/
// file ::= name
// name ::= [^/]
// if path is invalid, return a descriptive error code, otherwise ERR_OK.
LibError path_validate(const char* path)
{
// disallow "/", because it would create a second 'root' (with name = "").
// root dir is "".
if(path[0] == '/')
return ERR_PATH_NOT_RELATIVE;
// scan each char in path string; count length.
int c = 0; // current char; used for .. detection
size_t path_len = 0;
for(;;)
{
const int last_c = c;
c = path[path_len++];
// whole path is too long
if(path_len >= VFS_MAX_PATH)
return ERR_PATH_LENGTH;
// disallow:
// - ".." (prevent going above the VFS root dir)
// - "./" (security hole when mounting and not supported on Windows).
// allow "/.", because CVS backup files include it.
if(last_c == '.' && (c == '.' || c == '/'))
return ERR_PATH_NON_CANONICAL;
// disallow OS-specific dir separators
if(c == '\\' || c == ':')
return ERR_PATH_NON_PORTABLE;
// end of string, no errors encountered
if(c == '\0')
break;
}
return ERR_OK;
}
// if name is invalid, return a descriptive error code, otherwise ERR_OK.
LibError path_component_validate(const char* name)
{
// disallow empty strings
if(*name == '\0')
return ERR_PATH_EMPTY;
for(;;)
{
const int c = *name++;
// disallow dir separators
if(c == '\\' || c == ':' || c == '/')
return ERR_PATH_COMPONENT_SEPARATOR;
// end of string, no errors encountered
if(c == '\0')
break;
}
return ERR_OK;
}
// convenience function
void vfs_path_copy(char* dst, const char* src)
{
strcpy_s(dst, VFS_MAX_PATH, src);
}
// combine <path1> and <path2> into one path, and write to <dst>.
// if necessary, a directory separator is added between the paths.
// each may be empty, filenames, or full paths.
// total path length (including '\0') must not exceed VFS_MAX_PATH.
LibError vfs_path_append(char* dst, const char* path1, const char* path2)
{
const size_t len1 = strlen(path1);
const size_t len2 = strlen(path2);
size_t total_len = len1 + len2 + 1; // includes '\0'
// check if we need to add '/' between path1 and path2
// note: the second can't start with '/' (not allowed by path_validate)
bool need_separator = false;
if(len1 != 0 && path1[len1-1] != '/')
{
total_len++; // for '/'
need_separator = true;
}
if(total_len+1 > VFS_MAX_PATH)
WARN_RETURN(ERR_PATH_LENGTH);
strcpy(dst, path1); // safe
dst += len1;
if(need_separator)
*dst++ = '/';
strcpy(dst, path2); // safe
return ERR_OK;
}
// strip <remove> from the start of <src>, prepend <replace>,
// and write to <dst>.
// used when converting VFS <--> real paths.
LibError path_replace(char* dst, const char* src, const char* remove, const char* replace)
{
// remove doesn't match start of <src>
const size_t remove_len = strlen(remove);
if(strncmp(src, remove, remove_len) != 0)
WARN_RETURN(ERR_FAIL);
// get rid of trailing / in src (must not be included in remove)
const char* start = src+remove_len;
if(*start == '/' || *start == DIR_SEP)
start++;
// prepend replace.
CHECK_ERR(vfs_path_append(dst, replace, start));
return ERR_OK;
}
// fill V_dir_only with the path portion of V_src_fn
// ("" if root dir, otherwise ending with /)
void path_dir_only(const char* V_src_fn, char* V_dir_only)
{
vfs_path_copy(V_dir_only, V_src_fn);
char* slash = strrchr(V_dir_only, '/');
// was filename only; directory = "" (empty string)
if(!slash)
V_dir_only[0] = '\0';
// normal directory+filename: cut off after last slash
else
*(slash+1) = '\0';
}
// return pointer to the name component within V_src_fn
const char* path_name_only(const char* V_src_fn)
{
const char* slash = strrchr(V_src_fn, '/');
return slash? slash+1 : V_src_fn;
}
// fill V_next_fn (which must be big enough for VFS_MAX_PATH chars) with
// the next numbered filename according to the pattern defined by V_fn_fmt.
// <nfi> must be initially zeroed (e.g. by defining as static) and passed
// each time.
// if <use_vfs> (default), the paths are treated as VFS paths; otherwise,
// file.cpp's functions are used. this is necessary because one of
// our callers needs a filename for VFS archive files.
//
// this function is useful when creating new files which are not to
// overwrite the previous ones, e.g. screenshots.
// example for V_fn_fmt: "screenshots/screenshot%04d.png".
void next_numbered_filename(const char* fn_fmt,
NextNumberedFilenameInfo* nfi, char* next_fn, bool use_vfs)
{
// (first call only:) scan directory and set next_num according to
// highest matching filename found. this avoids filling "holes" in
// the number series due to deleted files, which could be confusing.
// example: add 1st and 2nd; [exit] delete 1st; [restart]
// add 3rd -> without this measure it would get number 1, not 3.
if(nfi->next_num == 0)
{
char dir[VFS_MAX_PATH];
path_dir_only(fn_fmt, dir);
const char* name_fmt = path_name_only(fn_fmt);
int max_num = -1; int num;
DirEnt ent;
if(use_vfs)
{
Handle hd = vfs_dir_open(dir);
if(hd > 0)
{
while(vfs_dir_next_ent(hd, &ent, 0) == ERR_OK)
if(!DIRENT_IS_DIR(&ent) && sscanf(ent.name, name_fmt, &num) == 1)
max_num = MAX(num, max_num);
(void)vfs_dir_close(hd);
}
}
else
{
DirIterator it;
if(dir_open(dir, &it) == ERR_OK)
{
while(dir_next_ent(&it, &ent) == ERR_OK)
if(!DIRENT_IS_DIR(&ent) && sscanf(ent.name, name_fmt, &num) == 1)
max_num = MAX(num, max_num);
(void)dir_close(&it);
}
}
nfi->next_num = max_num+1;
}
bool (*exists)(const char* fn) = use_vfs? vfs_exists : file_exists;
// now increment number until that file doesn't yet exist.
// this is fairly slow, but typically only happens once due
// to scan loop above. (we still need to provide for looping since
// someone may have added files in the meantime)
// binary search isn't expected to improve things.
do
snprintf(next_fn, VFS_MAX_PATH, fn_fmt, nfi->next_num++);
while(exists(next_fn));
}

View File

@ -213,8 +213,8 @@ public:
LibError add(const char* name_tmp, TNodeType type, TNode** pnode) LibError add(const char* name_tmp, TNodeType type, TNode** pnode)
{ {
char V_new_path_tmp[VFS_MAX_PATH]; char V_new_path_tmp[PATH_MAX];
vfs_path_append(V_new_path_tmp, V_path, name_tmp); path_append(V_new_path_tmp, V_path, name_tmp);
const char* V_new_path = file_make_unique_fn_copy(V_new_path_tmp); const char* V_new_path = file_make_unique_fn_copy(V_new_path_tmp);
const char* name = path_name_only(V_new_path); const char* name = path_name_only(V_new_path);
@ -377,7 +377,7 @@ static LibError lookup(TDir* td, const char* path, uint flags, TNode** pnode)
// copy into (writeable) buffer so we can 'tokenize' path components // copy into (writeable) buffer so we can 'tokenize' path components
// by replacing '/' with '\0'. // by replacing '/' with '\0'.
char V_path[VFS_MAX_PATH]; char V_path[PATH_MAX];
strcpy_s(V_path, sizeof(V_path), path); strcpy_s(V_path, sizeof(V_path), path);
char* cur_component = V_path; char* cur_component = V_path;

View File

@ -191,7 +191,7 @@ static void Cursor_dtor(Cursor* c)
static LibError Cursor_reload(Cursor* c, const char* name, Handle) static LibError Cursor_reload(Cursor* c, const char* name, Handle)
{ {
char filename[VFS_MAX_PATH]; char filename[PATH_MAX];
// read pixel offset of the cursor's hotspot [the bit of it that's // read pixel offset of the cursor's hotspot [the bit of it that's
// drawn at (g_mouse_x,g_mouse_y)] from file. // drawn at (g_mouse_x,g_mouse_y)] from file.

View File

@ -27,6 +27,7 @@
#include "tex_codec.h" #include "tex_codec.h"
#include "tex.h" #include "tex.h"
#include "lib/path_util.h"
static const TexCodecVTbl* codecs; static const TexCodecVTbl* codecs;
@ -54,11 +55,7 @@ int tex_codec_register(TexCodecVTbl* c)
// tex_is_known_extension. // tex_is_known_extension.
LibError tex_codec_for_filename(const char* fn, const TexCodecVTbl** c) LibError tex_codec_for_filename(const char* fn, const TexCodecVTbl** c)
{ {
const char* ext = strrchr(fn, '.'); const char* ext = path_extension(fn);
if(!ext)
return ERR_UNKNOWN_FORMAT;
ext++; // skip '.'
for(*c = codecs; *c; *c = (*c)->next) for(*c = codecs; *c; *c = (*c)->next)
{ {
// we found it // we found it

View File

@ -50,7 +50,7 @@
# include "sysdep/win/win_internal.h" # include "sysdep/win/win_internal.h"
#endif #endif
#include "../res.h" #include "lib/res/res.h"
#include "snd_mgr.h" #include "snd_mgr.h"
#include "lib/timer.h" #include "lib/timer.h"
#include "app_hooks.h" #include "app_hooks.h"
@ -929,9 +929,9 @@ static LibError SndData_reload(SndData* sd, const char* fn, Handle hsd)
} }
file_type; file_type;
const char* ext = strrchr(fn, '.'); const char* ext = path_extension(fn);
// .. OGG (data will be passed directly to OpenAL) // .. OGG (data will be passed directly to OpenAL)
if(ext && !stricmp(ext, ".ogg")) if(!stricmp(ext, "ogg"))
{ {
#ifdef OGG_HACK #ifdef OGG_HACK
#else #else
@ -950,7 +950,7 @@ static LibError SndData_reload(SndData* sd, const char* fn, Handle hsd)
file_type = FT_OGG; file_type = FT_OGG;
} }
// .. WAV // .. WAV
else if(ext && !stricmp(ext, ".wav")) else if(!stricmp(ext, "wav"))
file_type = FT_WAV; file_type = FT_WAV;
// .. unknown extension // .. unknown extension
else else
@ -1375,7 +1375,7 @@ static LibError VSrc_reload(VSrc* vs, const char* fn, Handle hvs)
CHECK_ERR(err); CHECK_ERR(err);
// //
// if extension is .txt, fn is a definition file containing the // if extension is "txt", fn is a definition file containing the
// sound file name and its gain; otherwise, read directly from fn // sound file name and its gain; otherwise, read directly from fn
// and assume default gain (1.0). // and assume default gain (1.0).
// //
@ -1385,8 +1385,8 @@ static LibError VSrc_reload(VSrc* vs, const char* fn, Handle hvs)
// extracted from stringstream; // extracted from stringstream;
// declare here so that it doesn't go out of scope below. // declare here so that it doesn't go out of scope below.
const char* ext = strchr(fn, '.'); const char* ext = path_extension(fn);
if(ext && !stricmp(ext, ".txt")) if(!stricmp(ext, "txt"))
{ {
FileIOBuf buf; size_t size; FileIOBuf buf; size_t size;
RETURN_ERR(vfs_load(fn, buf, size)); RETURN_ERR(vfs_load(fn, buf, size));

View File

@ -144,7 +144,7 @@ extern LibError snd_set_master_gain(float gain);
// open and return a handle to a sound instance. // open and return a handle to a sound instance.
// //
// if <snd_fn> is a text file (extension ".txt"), it is assumed // if <snd_fn> is a text file (extension "txt"), it is assumed
// to be a definition file containing the sound file name and // to be a definition file containing the sound file name and
// its gain (0.0 .. 1.0). // its gain (0.0 .. 1.0).
// otherwise, <snd_fn> is taken to be the sound file name and // otherwise, <snd_fn> is taken to be the sound file name and

View File

@ -26,6 +26,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "win_internal.h" #include "win_internal.h"
#include "lib/path_util.h"
#include "dll_ver.h" #include "dll_ver.h"
#if MSC_VERSION #if MSC_VERSION
@ -108,8 +109,8 @@ LibError dll_list_add(const char* name)
// ".sys" extension, so always appending ".dll" is incorrect. // ".sys" extension, so always appending ".dll" is incorrect.
char buf[MAX_PATH]; char buf[MAX_PATH];
const char* dll_name = name; const char* dll_name = name;
const char* ext = strrchr(name, '.'); const char* ext = path_extension(name);
if(!ext) if(ext[0] == '\0') // no extension
{ {
snprintf(buf, ARRAY_SIZE(buf), "%s.dll", name); snprintf(buf, ARRAY_SIZE(buf), "%s.dll", name);
dll_name = buf; dll_name = buf;
@ -128,8 +129,7 @@ LibError dll_list_add(const char* name)
dll_list_pos += sprintf(dll_list_pos, ", "); dll_list_pos += sprintf(dll_list_pos, ", ");
// extract filename. // extract filename.
const char* slash = strrchr(dll_name, '\\'); const char* dll_fn = path_name_only(dll_name);
const char* dll_fn = slash? slash+1 : dll_name;
int len = snprintf(dll_list_pos, max_chars_to_write, "%s (%s)", dll_fn, dll_ver); int len = snprintf(dll_list_pos, max_chars_to_write, "%s (%s)", dll_fn, dll_ver);
// success // success

View File

@ -36,6 +36,7 @@
#include "wdbg.h" #include "wdbg.h"
#include "debug_stl.h" #include "debug_stl.h"
#include "app_hooks.h" #include "app_hooks.h"
#include "lib/path_util.h"
#if CPU_IA32 #if CPU_IA32
# include "lib/sysdep/ia32.h" # include "lib/sysdep/ia32.h"
#endif #endif
@ -207,11 +208,7 @@ LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file,
// this loses information, but that isn't expected to be a // this loses information, but that isn't expected to be a
// problem and is balanced by not having to do this from every // problem and is balanced by not having to do this from every
// call site (full path is too long to display nicely). // call site (full path is too long to display nicely).
const char* base_name = line_info.FileName; const char* base_name = path_name_only(line_info.FileName);
const char* slash = strrchr(base_name, DIR_SEP);
if(slash)
base_name = slash+1;
snprintf(file, DBG_FILE_LEN, "%s", base_name); snprintf(file, DBG_FILE_LEN, "%s", base_name);
successes++; successes++;
} }
@ -1918,7 +1915,7 @@ void wdbg_write_minidump(EXCEPTION_POINTERS* exception_pointers)
lock(); lock();
// note: we go through some gyrations here (strcpy+strcat) to avoid // note: we go through some gyrations here (strcpy+strcat) to avoid
// dependency on file code (vfs_path_append). // dependency on file code (path_append).
char N_path[PATH_MAX]; char N_path[PATH_MAX];
strcpy_s(N_path, ARRAY_SIZE(N_path), ah_get_log_dir()); strcpy_s(N_path, ARRAY_SIZE(N_path), ah_get_log_dir());
strcat_s(N_path, ARRAY_SIZE(N_path), "crashlog.dmp"); strcat_s(N_path, ARRAY_SIZE(N_path), "crashlog.dmp");

View File

@ -22,16 +22,15 @@
#include "precompiled.h" #include "precompiled.h"
#include "lib.h"
#include "win_internal.h"
#include "lib/res/file/file.h" // file_is_subpath
#include <string> #include <string>
#include <map> #include <map>
#include <list> #include <list>
#include "lib.h"
#include "lib/path_util.h"
#include "win_internal.h"
#include "lib/res/file/file.h" // path_is_subpath
#pragma data_seg(WIN_CALLBACK_POST_ATEXIT(x)) #pragma data_seg(WIN_CALLBACK_POST_ATEXIT(x))
WIN_REGISTER_FUNC(wdir_watch_shutdown); WIN_REGISTER_FUNC(wdir_watch_shutdown);
@ -204,7 +203,7 @@ LibError dir_add_watch(const char* dir, intptr_t* _reqnum)
if(!w) if(!w)
continue; continue;
const char* old_dir = w->dir_name.c_str(); const char* old_dir = w->dir_name.c_str();
if(file_is_subpath(dir, old_dir)) if(path_is_subpath(dir, old_dir))
{ {
reqnum = w->reqnum; reqnum = w->reqnum;
w->refs++; w->refs++;

View File

@ -26,6 +26,7 @@
#include <stdlib.h> // __argc #include <stdlib.h> // __argc
#include "win_internal.h" #include "win_internal.h"
#include "lib/path_util.h"
#if MSC_VERSION >= 1400 #if MSC_VERSION >= 1400
#include <process.h> // __security_init_cookie #include <process.h> // __security_init_cookie
@ -318,11 +319,7 @@ static inline void pre_libc_init()
GetSystemDirectory(win_sys_dir, sizeof(win_sys_dir)); GetSystemDirectory(win_sys_dir, sizeof(win_sys_dir));
if(GetModuleFileName(GetModuleHandle(0), win_exe_dir, MAX_PATH) != 0) if(GetModuleFileName(GetModuleHandle(0), win_exe_dir, MAX_PATH) != 0)
{ path_strip_fn(win_exe_dir);
char* slash = strrchr(win_exe_dir, '\\');
if(slash)
*slash = '\0';
}
// HACK: make sure a reference to user32 is held, even if someone // HACK: make sure a reference to user32 is held, even if someone
// decides to delay-load it. this fixes bug #66, which was the // decides to delay-load it. this fixes bug #66, which was the

View File

@ -26,9 +26,10 @@
#include <stdlib.h> #include <stdlib.h>
#include "lib.h" #include "lib.h"
#include "posix.h"
#include "win_internal.h" #include "win_internal.h"
#include "allocators.h" #include "allocators.h"
#include "lib/path_util.h"
#include "posix.h"
// cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding // cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding
@ -793,11 +794,12 @@ void* dlopen(const char* so_name, int flags)
// if present, strip .so extension; add .dll extension // if present, strip .so extension; add .dll extension
char dll_name[MAX_PATH]; char dll_name[MAX_PATH];
strcpy_s(dll_name, ARRAY_SIZE(dll_name)-4, so_name); strcpy_s(dll_name, ARRAY_SIZE(dll_name)-5, so_name);
char* ext = strrchr(dll_name, '.'); char* ext = (char*)path_extension(dll_name);
if(!ext) if(ext[0] == '\0') // no extension
ext = dll_name + strlen(dll_name); strcat(dll_name, ".dll"); // safe
strcpy(ext, ".dll"); // safe else // need to replace extension
SAFE_STRCPY(ext, "dll");
HMODULE hModule = LoadLibrary(dll_name); HMODULE hModule = LoadLibrary(dll_name);
if(!hModule) if(!hModule)

View File

@ -28,6 +28,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "lib/path_util.h"
#include "lib/sysdep/snd.h" #include "lib/sysdep/snd.h"
#include "dll_ver.h" // dll_list_* #include "dll_ver.h" // dll_list_*
#include "win_internal.h" #include "win_internal.h"
@ -99,7 +100,7 @@ static LibError add_if_oal_dll(const DirEnt* ent, PathPackage* pp, StringSet* dl
if(!ret.second) // insert failed - element already there if(!ret.second) // insert failed - element already there
return ERR_OK; return ERR_OK;
RETURN_ERR(pp_append_file(pp, fn)); RETURN_ERR(path_package_append_file(pp, fn));
return dll_list_add(pp->path); return dll_list_add(pp->path);
} }
@ -113,7 +114,7 @@ static LibError add_if_oal_dll(const DirEnt* ent, PathPackage* pp, StringSet* dl
static LibError add_oal_dlls_in_dir(const char* dir, StringSet* dlls) static LibError add_oal_dlls_in_dir(const char* dir, StringSet* dlls)
{ {
PathPackage pp; PathPackage pp;
RETURN_ERR(pp_set_dir(&pp, dir)); RETURN_ERR(path_package_set_dir(&pp, dir));
DirIterator d; DirIterator d;
RETURN_ERR(dir_open(dir, &d)); RETURN_ERR(dir_open(dir, &d));

View File

@ -3,6 +3,7 @@
#include "CLogger.h" #include "CLogger.h"
#include "ConfigDB.h" #include "ConfigDB.h"
#include "lib.h" #include "lib.h"
#include "lib/path_util.h"
#include "lib/res/file/file.h" #include "lib/res/file/file.h"
#include <time.h> #include <time.h>
@ -37,12 +38,12 @@ CLogger::CLogger()
char N_path[PATH_MAX]; char N_path[PATH_MAX];
(void)file_make_full_native_path("../logs", N_path); (void)file_make_full_native_path("../logs", N_path);
PathPackage pp; PathPackage pp;
(void)pp_set_dir(&pp, N_path); (void)path_package_set_dir(&pp, N_path);
(void)pp_append_file(&pp, "mainlog.html"); (void)path_package_append_file(&pp, "mainlog.html");
m_MainLog.open (pp.path, ofstream::out | ofstream::trunc); m_MainLog.open (pp.path, ofstream::out | ofstream::trunc);
(void)pp_append_file(&pp, "interestinglog.html"); (void)path_package_append_file(&pp, "interestinglog.html");
m_InterestingLog.open(pp.path, ofstream::out | ofstream::trunc); m_InterestingLog.open(pp.path, ofstream::out | ofstream::trunc);
(void)pp_append_file(&pp, "memorylog.html"); (void)path_package_append_file(&pp, "memorylog.html");
m_MemoryLog.open (pp.path, ofstream::out | ofstream::trunc); m_MemoryLog.open (pp.path, ofstream::out | ofstream::trunc);
//Write Headers for the HTML documents //Write Headers for the HTML documents

View File

@ -330,8 +330,8 @@ bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, CStr path)
{ {
debug_assert(ns >= 0 && ns < CFG_LAST); debug_assert(ns >= 0 && ns < CFG_LAST);
char realpath[VFS_MAX_PATH]; char realpath[PATH_MAX];
char nativepath[VFS_MAX_PATH]; char nativepath[PATH_MAX];
const char *filepath=path.c_str(); const char *filepath=path.c_str();
int err; int err;
FILE *fp; FILE *fp;

View File

@ -9,6 +9,7 @@
#include "precompiled.h" #include "precompiled.h"
#include "FileUnpacker.h" #include "FileUnpacker.h"
#include "lib/path_util.h"
#include "lib/res/res.h" #include "lib/res/res.h"
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
@ -44,8 +45,8 @@ void CFileUnpacker::Read(const char* filename,const char magicstr[4])
// somewhat of a hack: if loading a map (.PMP), tell the file manager // somewhat of a hack: if loading a map (.PMP), tell the file manager
// that the buffer will be kept in memory longer (avoids warning). // that the buffer will be kept in memory longer (avoids warning).
uint flags = 0; uint flags = 0;
const char* ext = strrchr(filename, '.'); const char* ext = path_extension(filename);
if(ext && !stricmp(ext, ".pmp")) if(!stricmp(ext, "pmp"))
flags |= FILE_LONG_LIVED; flags |= FILE_LONG_LIVED;
// load the whole thing into memory // load the whole thing into memory

View File

@ -18,6 +18,7 @@
#include "Profile.h" #include "Profile.h"
#include "Renderer.h" #include "Renderer.h"
#include "lib/res/graphics/unifont.h" #include "lib/res/graphics/unifont.h"
#include "lib/path_util.h"
#include "lib/res/file/file.h" #include "lib/res/file/file.h"
#include "Hotkey.h" #include "Hotkey.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
@ -418,8 +419,8 @@ void CProfileViewer::SaveToFile()
char N_path[PATH_MAX]; char N_path[PATH_MAX];
(void)file_make_full_native_path("../logs", N_path); (void)file_make_full_native_path("../logs", N_path);
PathPackage pp; PathPackage pp;
(void)pp_set_dir(&pp, N_path); (void)path_package_set_dir(&pp, N_path);
(void)pp_append_file(&pp, "profile.txt"); (void)path_package_append_file(&pp, "profile.txt");
// Open the file. (It will be closed when the CProfileViewer // Open the file. (It will be closed when the CProfileViewer
// destructor is called.) // destructor is called.)

View File

@ -1,7 +1,7 @@
#include "precompiled.h" #include "precompiled.h"
#include "lib/res/file/path.h"
#include "lib/res/file/vfs.h" #include "lib/res/file/vfs.h"
#include "lib/res/file/vfs_path.h"
#include "lib/ogl.h" #include "lib/ogl.h"
#include "lib/timer.h" #include "lib/timer.h"
#include "lib/sysdep/gfx.h" #include "lib/sysdep/gfx.h"
@ -170,9 +170,9 @@ void WriteScreenshot(const char* extension)
// get next available numbered filename // get next available numbered filename
// .. bake extension into format string. // .. bake extension into format string.
// note: %04d -> always 4 digits, so sorting by filename works correctly. // note: %04d -> always 4 digits, so sorting by filename works correctly.
char file_format_string[VFS_MAX_PATH]; char file_format_string[PATH_MAX];
snprintf(file_format_string, PATH_MAX, "screenshots/screenshot%%04d.%s", extension); snprintf(file_format_string, PATH_MAX, "screenshots/screenshot%%04d.%s", extension);
char fn[VFS_MAX_PATH]; char fn[PATH_MAX];
next_numbered_filename(file_format_string, &screenshot_nfi, fn); next_numbered_filename(file_format_string, &screenshot_nfi, fn);
const char* atom_fn = file_make_unique_fn_copy(fn); const char* atom_fn = file_make_unique_fn_copy(fn);
@ -209,9 +209,9 @@ void WriteBigScreenshot(const char* extension, int tiles)
// get next available numbered filename // get next available numbered filename
// .. bake extension into format string. // .. bake extension into format string.
// note: %04d -> always 4 digits, so sorting by filename works correctly. // note: %04d -> always 4 digits, so sorting by filename works correctly.
char file_format_string[VFS_MAX_PATH]; char file_format_string[PATH_MAX];
snprintf(file_format_string, PATH_MAX, "screenshots/screenshot%%04d.%s", extension); snprintf(file_format_string, PATH_MAX, "screenshots/screenshot%%04d.%s", extension);
char fn[VFS_MAX_PATH]; char fn[PATH_MAX];
next_numbered_filename(file_format_string, &screenshot_nfi, fn); next_numbered_filename(file_format_string, &screenshot_nfi, fn);
const char* atom_fn = file_make_unique_fn_copy(fn); const char* atom_fn = file_make_unique_fn_copy(fn);

View File

@ -106,7 +106,7 @@ InputSource *CVFSEntityResolver::resolveEntity(const XMLCh *const UNUSED(publicI
char *path=XMLString::transcode(systemId); char *path=XMLString::transcode(systemId);
char *orgpath=path; char *orgpath=path;
char abspath[VFS_MAX_PATH]; char abspath[PATH_MAX];
const char *end=strchr(m_DocName, '\0'); const char *end=strchr(m_DocName, '\0');
if (IS_PATH_SEP(*path)) if (IS_PATH_SEP(*path))
@ -140,9 +140,9 @@ InputSource *CVFSEntityResolver::resolveEntity(const XMLCh *const UNUSED(publicI
const ptrdiff_t prefixlen=end-m_DocName; const ptrdiff_t prefixlen=end-m_DocName;
memcpy2(abspath, m_DocName, prefixlen); memcpy2(abspath, m_DocName, prefixlen);
strncpy(abspath+prefixlen, path, VFS_MAX_PATH-prefixlen); strncpy(abspath+prefixlen, path, PATH_MAX-prefixlen);
// strncpy might not have terminated, if path was too long // strncpy might not have terminated, if path was too long
abspath[VFS_MAX_PATH-1]=0; abspath[PATH_MAX-1]=0;
path=abspath; path=abspath;
} }

View File

@ -37,6 +37,7 @@
#include "ModelDef.h" #include "ModelDef.h"
#include "ogl.h" #include "ogl.h"
#include "lib/path_util.h"
#include "lib/res/res.h" #include "lib/res/res.h"
#include "lib/res/file/file.h" #include "lib/res/file/file.h"
#include "lib/res/graphics/tex.h" #include "lib/res/graphics/tex.h"
@ -1245,7 +1246,7 @@ int CRenderer::LoadAlphaMaps()
// //
Handle textures[NumAlphaMaps] = {0}; Handle textures[NumAlphaMaps] = {0};
PathPackage pp; PathPackage pp;
(void)pp_set_dir(&pp, "art/textures/terrain/alphamaps/special"); (void)path_package_set_dir(&pp, "art/textures/terrain/alphamaps/special");
const char* fnames[NumAlphaMaps] = { const char* fnames[NumAlphaMaps] = {
"blendcircle.dds", "blendcircle.dds",
"blendlshape.dds", "blendlshape.dds",
@ -1268,7 +1269,7 @@ int CRenderer::LoadAlphaMaps()
uint bpp = 0; uint bpp = 0;
for(uint i=0;i<NumAlphaMaps;i++) for(uint i=0;i<NumAlphaMaps;i++)
{ {
(void)pp_append_file(&pp, fnames[i]); (void)path_package_append_file(&pp, fnames[i]);
// note: these individual textures can be discarded afterwards; // note: these individual textures can be discarded afterwards;
// we cache the composite. // we cache the composite.
textures[i] = ogl_tex_load(pp.path, RES_NO_CACHE); textures[i] = ogl_tex_load(pp.path, RES_NO_CACHE);

View File

@ -72,7 +72,7 @@ int WaterManager::LoadWaterTextures()
while (cur_loading_water_tex < num_textures) while (cur_loading_water_tex < num_textures)
{ {
char waterName[VFS_MAX_PATH]; char waterName[PATH_MAX];
// TODO: add a member variable and setter for this. (can't make this // TODO: add a member variable and setter for this. (can't make this
// a parameter because this function is called via delay-load code) // a parameter because this function is called via delay-load code)
static const char* const water_type = "animation2"; static const char* const water_type = "animation2";