Path now goes to some trouble to prevent mixing / and \ slashes (causes trouble when hotloading, and some Windows APIs can't handle it, either)

WARNING: that means stuff like Path(nativeDataPath/"art/") is forbidden
and will raise errors on Windows when nativeDataPath contains \. always
use /"" to add a trailing slash. never embed "/" in OsPath component
strings (it's OK for VFS strings since they consistently use /).

wdir_watch, CmdLineArgs: avoid mixed separators
wutil: remove overzealous assertion (infinite recursion if an error
arose before we create a window)

refs #781

This was SVN commit r9424.
This commit is contained in:
janwas 2011-05-04 12:16:51 +00:00
parent 7523894760
commit a7152270f4
9 changed files with 113 additions and 64 deletions

View File

@ -33,6 +33,7 @@
STATUS_DEFINE(ERR, PATH_CHARACTER_ILLEGAL, L"illegal path character", -1);
STATUS_DEFINE(ERR, PATH_CHARACTER_UNSAFE, L"unsafe path character", -1);
STATUS_DEFINE(ERR, PATH_NOT_FOUND, L"path not found", -1);
STATUS_DEFINE(ERR, PATH_MIXED_SEPARATORS, L"path contains both slash and backslash separators", -1);
static bool path_is_dir_sep(wchar_t c)

View File

@ -48,6 +48,7 @@ namespace ERR
const Status PATH_CHARACTER_ILLEGAL = -100300;
const Status PATH_CHARACTER_UNSAFE = -100301;
const Status PATH_NOT_FOUND = -100302;
const Status PATH_MIXED_SEPARATORS = -100303;
}
/**
@ -69,18 +70,48 @@ LIB_API const wchar_t* path_name_only(const wchar_t* path);
// NB: there is a need for 'generic' paths (e.g. for Trace entry / archive pathnames).
// converting via c_str would be inefficient, and the Os/VfsPath typedefs are hopefully
// sufficient to avoid errors.
// converting between specialized variants via c_str would be inefficient, and the
// Os/VfsPath typedefs are hopefully sufficient to avoid errors.
class Path
{
public:
typedef std::wstring String;
Path() {}
Path(const char* p) : path(p, p+strlen(p)) {}
Path(const wchar_t* p) : path(p, p+wcslen(p)) {}
Path(const std::string& s) : path(s.begin(), s.end()) {}
Path(const std::wstring& s) : path(s) {}
Path()
{
DetectSeparator();
}
Path(const char* p)
: path(p, p+strlen(p))
{
DetectSeparator();
}
Path(const wchar_t* p)
: path(p, p+wcslen(p))
{
DetectSeparator();
}
Path(const std::string& s)
: path(s.begin(), s.end())
{
DetectSeparator();
}
Path(const std::wstring& s)
: path(s)
{
DetectSeparator();
}
Path& operator=(const Path& rhs)
{
path = rhs.path;
DetectSeparator(); // (warns if separators differ)
return *this;
}
bool empty() const
{
@ -111,38 +142,22 @@ public:
{
if(empty()) // (ensure length()-1 is safe)
return true; // (the VFS root directory is represented as an empty string)
// note: ideally, path strings would only contain '/' or even SYS_DIR_SEP.
// however, windows-specific code (e.g. the sound driver detection)
// uses these routines with '\\' strings. converting them all to
// '/' and then back before passing to OS functions would be annoying.
// also, the self-tests verify correct operation of such strings.
// it would be error-prone to only test the platform's separators.
// we therefore allow all separators here.
return path[path.length()-1] == '/' || path[path.length()-1] == '\\';
return path[path.length()-1] == separator;
}
Path Parent() const
{
size_t idxSlash = path.find_last_of('/');
const size_t idxSlash = path.find_last_of(separator);
if(idxSlash == String::npos)
{
idxSlash = path.find_last_of('\\');
if(idxSlash == String::npos)
return L"";
}
return L"";
return path.substr(0, idxSlash);
}
Path Filename() const
{
size_t idxSlash = path.find_last_of('/');
const size_t idxSlash = path.find_last_of(separator);
if(idxSlash == String::npos)
{
idxSlash = path.find_last_of('\\');
if(idxSlash == String::npos)
return path;
}
return path;
return path.substr(idxSlash+1);
}
@ -173,8 +188,13 @@ public:
Path operator/(Path rhs) const
{
Path ret = *this;
if(ret.path.empty()) // (empty paths assume '/')
ret.separator = rhs.separator;
if(!ret.IsDirectory())
ret.path += '/';
ret.path += ret.separator;
if(rhs.path.find((ret.separator == '/')? '\\' : '/') != String::npos)
DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS);
ret.path += rhs.path;
return ret;
}
@ -182,7 +202,27 @@ public:
static Status Validate(String::value_type c);
private:
void DetectSeparator()
{
const size_t idxBackslash = path.find('\\');
if(path.find('/') != String::npos && idxBackslash != String::npos)
DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS);
// (default to '/' for empty strings)
separator = (idxBackslash == String::npos)? '/' : '\\';
}
String path;
// note: ideally, path strings would only contain '/' or even SYS_DIR_SEP.
// however, Windows-specific code (e.g. the sound driver detection)
// uses these routines with '\\' strings. the boost::filesystem approach of
// converting them all to '/' and then back via external_file_string is
// annoying and inefficient. we allow either type of separators,
// appending whichever was first encountered. when modifying the path,
// we ensure the same separator is used.
wchar_t separator;
};
static inline std::wostream& operator<<(std::wostream& s, const Path& path)

View File

@ -111,6 +111,7 @@ public:
WARN_IF_FALSE(CloseHandle(m_ovl->hEvent));
if(ret == WAIT_OBJECT_0 || GetLastError() == ERROR_OPERATION_ABORTED)
{
SetLastError(0);
delete[] m_data;
free(m_ovl);
}
@ -130,14 +131,12 @@ public:
return m_path;
}
/**
* (this is the handle to be associated with the completion port)
**/
HANDLE GetDirHandle() const
void AttachTo(HANDLE hIOCP) const
{
return m_dirHandle;
AttachToCompletionPort(m_dirHandle, hIOCP, (uintptr_t)this);
}
// (called again after each notification, so it mustn't AttachToCompletionPort)
Status Issue()
{
if(m_dirHandle == INVALID_HANDLE_VALUE)
@ -150,8 +149,7 @@ public:
// not set: FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_LAST_ACCESS, FILE_NOTIFY_CHANGE_SECURITY
DWORD undefined = 0; // (non-NULL pointer avoids BoundsChecker warning)
m_ovl->Internal = 0;
const BOOL ok = ReadDirectoryChangesW(m_dirHandle, m_data, dataSize, watchSubtree, filter, &undefined, m_ovl, 0);
WARN_IF_FALSE(ok);
WARN_IF_FALSE(ReadDirectoryChangesW(m_dirHandle, m_data, dataSize, watchSubtree, filter, &undefined, m_ovl, 0));
return INFO::OK;
}
@ -163,10 +161,13 @@ public:
const FILE_NOTIFY_INFORMATION* fni = (const FILE_NOTIFY_INFORMATION*)m_data;
for(;;)
{
// convert name from BSTR (non-zero-terminated) to OsPath
// convert (non-zero-terminated) BSTR to Path::String
cassert(sizeof(wchar_t) == sizeof(WCHAR));
const size_t nameChars = fni->FileNameLength / sizeof(WCHAR);
const OsPath name(Path::String(fni->FileName, nameChars));
const size_t length = fni->FileNameLength / sizeof(WCHAR);
Path::String name(fni->FileName, length);
// since we watch subtrees, name may contain '\\'. OsPath forbids
// mixing directory separators, so convert them all to '/'.
std::replace(name.begin(), name.end(), '\\', '/');
const OsPath pathname = m_path / name;
const DirWatchNotification::EType type = TypeFromAction(fni->Action);
@ -195,7 +196,7 @@ private:
return DirWatchNotification::Changed;
default:
ENSURE(0);
DEBUG_WARN_ERR(ERR::LOGIC);
return DirWatchNotification::Changed;
}
}
@ -325,7 +326,7 @@ public:
}
PDirWatchRequest request(new DirWatchRequest(path));
AttachToCompletionPort(request->GetDirHandle(), hIOCP, (uintptr_t)request.get());
request->AttachTo(hIOCP);
RETURN_STATUS_IF_ERR(request->Issue());
dirWatch.reset(new DirWatch(&m_sentinel, request));
return INFO::OK;

View File

@ -508,7 +508,7 @@ HWND wutil_AppWindow()
if(!hAppWindow)
{
WARN_IF_FALSE(EnumWindows(FindAppWindowByPid, 0));
ENSURE(hAppWindow != 0);
// (hAppWindow may still be 0 if we haven't created a window yet)
}
return hAppWindow;

View File

@ -16,13 +16,21 @@
*/
#include "precompiled.h"
#include "CmdLineArgs.h"
#include "lib/sysdep/filesystem.h" // wrealpath
CmdLineArgs::CmdLineArgs(int argc, const char* argv[])
{
if (argc >= 1)
m_Arg0 = argv[0];
{
std::string arg0(argv[0]);
// avoid OsPath complaining about mixing both types of separators,
// which happens when running in the VC2010 debugger
std::replace(arg0.begin(), arg0.end(), '\\', '/');
m_Arg0 = wrealpath(arg0);
}
for (int i = 1; i < argc; ++i)
{
@ -85,7 +93,7 @@ std::vector<CStr> CmdLineArgs::GetMultiple(const char* name) const
return values;
}
CStr CmdLineArgs::GetArg0() const
OsPath CmdLineArgs::GetArg0() const
{
return m_Arg0;
}

View File

@ -19,6 +19,7 @@
#define INCLUDED_CMDLINEARGS
#include "ps/CStr.h"
#include "lib/os_path.h"
class CmdLineArgs
{
@ -58,12 +59,12 @@ public:
* Get the value of argv[0], which is typically meant to be the name/path of
* the program (but the actual value is up to whoever executed the program).
*/
CStr GetArg0() const;
OsPath GetArg0() const;
private:
typedef std::vector<std::pair<CStr, CStr> > ArgsT;
ArgsT m_Args;
CStr m_Arg0;
OsPath m_Arg0;
};
#endif // INCLUDED_CMDLINEARGS

View File

@ -448,8 +448,8 @@ static void InitVfs(const CmdLineArgs& args)
const size_t cacheSize = ChooseCacheSize();
g_VFS = CreateVfs(cacheSize);
g_VFS->Mount(L"screenshots/", paths.Data()/"screenshots/");
const OsPath readonlyConfig = paths.RData()/"config/";
g_VFS->Mount(L"screenshots/", paths.Data()/"screenshots"/"");
const OsPath readonlyConfig = paths.RData()/"config"/"";
g_VFS->Mount(L"config/", readonlyConfig);
if(readonlyConfig != paths.Config())
g_VFS->Mount(L"config/", paths.Config());

View File

@ -31,9 +31,9 @@ Paths::Paths(const CmdLineArgs& args)
m_root = Root(args.GetArg0());
#ifdef INSTALLED_DATADIR
m_rdata = WIDEN(STRINGIZE(INSTALLED_DATADIR)) L"/";
m_rdata = WIDEN(STRINGIZE(INSTALLED_DATADIR))/L"";
#else
m_rdata = m_root/"data/";
m_rdata = m_root/"data"/"";
#endif
const char* subdirectoryName = args.Has("writableRoot")? 0 : "0ad";
@ -42,18 +42,18 @@ Paths::Paths(const CmdLineArgs& args)
if(!subdirectoryName)
{
m_data = m_rdata;
m_config = m_data/"config/";
m_cache = m_data/"cache/";
m_logs = m_root/"logs/";
m_config = m_data/"config"/"";
m_cache = m_data/"cache"/"";
m_logs = m_root/"logs"/"";
}
else
{
#if OS_WIN
const OsPath appdata = wutil_AppdataPath() / subdirectoryName/"";
m_data = appdata/"data/";
m_config = appdata/"config/";
m_cache = appdata/"cache/";
m_logs = appdata/"logs/";
m_data = appdata/"data"/"";
m_config = appdata/"config"/"";
m_cache = appdata/"cache"/"";
m_logs = appdata/"logs"/"";
#else
const char* envHome = getenv("HOME");
ENSURE(envHome);
@ -63,8 +63,8 @@ Paths::Paths(const CmdLineArgs& args)
const OsPath xdgCache = XDG_Path("XDG_CACHE_HOME", home, home/".cache/" ) / subdirectoryName;
m_data = xdgData/"";
m_cache = xdgCache/"";
m_config = xdgConfig/"config/";
m_logs = xdgConfig/"logs/";
m_config = xdgConfig/"config"/"";
m_logs = xdgConfig/"logs"/"";
#endif
}
}

View File

@ -58,15 +58,13 @@ CReplayLogger::CReplayLogger(ScriptInterface& scriptInterface) :
// to avoid accidentally overwriting earlier logs.
std::wstringstream name;
name << L"sim_log/" << getpid();
name << getpid();
static int run = -1;
if (++run)
name << "-" << run;
name << L"/commands.txt";
OsPath path = psLogDir() / name.str();
OsPath path = psLogDir() / L"sim_log" / name.str() / L"commands.txt";
CreateDirectories(path.Parent(), 0700);
m_Stream = new std::ofstream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
}