1
0
forked from 0ad/0ad

refactoring:

- simplify sys_get_executable_name interface - return OsPath directly,
rename to sys_ExecutablePathname
- add validation of path components in GetDirectoryEntries
- replace multiple calls to GetModuleFileNameW with
sys_ExecutablePathname
- lift nearly all MAX_PATH limitations (required at work)

This was SVN commit r9109.
This commit is contained in:
janwas 2011-03-23 16:14:47 +00:00
parent 64a02932e3
commit 0999ba0941
17 changed files with 121 additions and 146 deletions

View File

@ -44,11 +44,7 @@ static const OsPath& def_get_log_dir()
{
static OsPath logDir;
if(logDir.empty())
{
OsPath exePathname;
(void)sys_get_executable_name(exePathname);
logDir = exePathname.Parent();
}
logDir = sys_ExecutablePathname().Parent();
return logDir;
}

View File

@ -113,7 +113,7 @@ extern void ah_override_gl_upload_caps();
* return path to directory into which crash dumps should be written.
*
* must be callable at any time - in particular, before VFS init.
* paths are typically relative to sys_get_executable_name.
* paths are typically relative to sys_ExecutablePathname.
*
* @return path ending with directory separator (e.g. '/').
**/

View File

@ -24,6 +24,7 @@
#if OS_WIN
#include "lib/sysdep/sysdep.h"
#include "lib/sysdep/os/win/wutil.h"
#include "lib/external_libraries/dbghelp.h"
@ -42,7 +43,7 @@ void dbghelp_ImportFunctions()
// application loaded.") and then the system directory, whose
// dbghelp.dll is too old. we therefore specify the full path
// to our executable directory, which contains a newer dbghelp.dll.
const OsPath pathname = wutil_DetectExecutablePath()/"dbghelp.dll";
const OsPath pathname = sys_ExecutablePathname().Parent()/"dbghelp.dll";
HMODULE hDbghelp = LoadLibraryW(OsString(pathname).c_str());
debug_assert(hDbghelp);
#define FUNC(ret, name, params) p##name = (ret (__stdcall*) params)GetProcAddress(hDbghelp, #name);

View File

@ -59,8 +59,9 @@ LibError GetDirectoryEntries(const OsPath& path, FileInfos* files, DirectoryName
return LibError_from_errno();
}
for(size_t i = 0; osEnt->d_name[i] != '\0'; i++)
RETURN_ERR(Path::Validate(osEnt->d_name[i]));
const OsPath name(osEnt->d_name);
RETURN_ERR(name.Validate());
// get file information (mode, size, mtime)
struct stat s;

View File

@ -32,9 +32,8 @@
#include "lib/utf8.h"
ERROR_ASSOCIATE(ERR::PATH_EMPTY, L"path is an empty string", -1);
ERROR_ASSOCIATE(ERR::PATH_COMPONENT_SEPARATOR, L"path component contains dir separator", -1);
ERROR_ASSOCIATE(ERR::PATH_CHARACTER_ILLEGAL, L"illegal path character", -1);
ERROR_ASSOCIATE(ERR::PATH_CHARACTER_UNSAFE, L"unsafe path character", -1);
ERROR_ASSOCIATE(ERR::PATH_NOT_FOUND, L"path not found", -1);
@ -93,3 +92,33 @@ const wchar_t* path_name_only(const wchar_t* path)
const wchar_t* name = std::max(slash1, slash2)+1;
return name;
}
/*static*/ LibError Path::Validate(String::value_type c)
{
if(c < 32)
return ERR::PATH_CHARACTER_UNSAFE;
#if !OS_WIN
if(c >= UCHAR_MAX)
return ERR::PATH_CHARACTER_UNSAFE;
#endif
switch(c)
{
case '\\':
case '/':
case ':':
case '"':
case '?':
case '*':
case '<':
case '>':
case '|':
case '^':
return ERR::PATH_CHARACTER_ILLEGAL;
default:
return INFO::OK;
}
}

View File

@ -43,8 +43,8 @@
namespace ERR
{
const LibError PATH_EMPTY = -100300;
const LibError PATH_COMPONENT_SEPARATOR = -100301;
const LibError PATH_CHARACTER_ILLEGAL = -100300;
const LibError PATH_CHARACTER_UNSAFE = -100301;
const LibError PATH_NOT_FOUND = -100302;
}
@ -177,14 +177,7 @@ public:
return ret;
}
LibError Validate() const
{
for(size_t i = 0; i < path.length(); i++)
{
}
return INFO::OK;
}
static LibError Validate(String::value_type c);
private:
String path;

View File

@ -30,30 +30,23 @@
#include <cstdio>
LibError sys_get_executable_name(OsPath& pathname)
OsPath sys_ExecutablePathname()
{
const char* path;
Dl_info dl_info;
// Find the executable's filename
Dl_info dl_info;
memset(&dl_info, 0, sizeof(dl_info));
if (!T::dladdr((void *)sys_get_executable_name, &dl_info) ||
!dl_info.dli_fname )
{
return ERR::NO_SYS;
}
path = dl_info.dli_fname;
if (!T::dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname)
return OsPath();
const char* path = dl_info.dli_fname;
// If this looks like an absolute path, use realpath to get the normalized
// path (with no '.' or '..')
if (path[0] == '/')
{
char resolvedBuf[PATH_MAX];
char* resolved = realpath(path, resolvedBuf);
if (!resolved)
return ERR::FAIL;
pathname = resolved;
return INFO::OK;
char resolved[PATH_MAX];
if (!realpath(path, resolved))
return OsPath();
return resolved;
}
// If this looks like a relative path, resolve against cwd and use realpath
@ -61,22 +54,20 @@ LibError sys_get_executable_name(OsPath& pathname)
{
char cwd[PATH_MAX];
if (!T::getcwd(cwd, PATH_MAX))
return ERR::NO_SYS;
return OsPath();
char absolute[PATH_MAX];
int ret = snprintf(absolute, PATH_MAX, "%s/%s", cwd, path);
if (ret < 0 || ret >= PATH_MAX)
return ERR::NO_SYS; // path too long, or other error
char resolvedBuf[PATH_MAX];
char* resolved = realpath(absolute, resolvedBuf);
if (!resolved)
return ERR::NO_SYS;
pathname = resolved;
return INFO::OK;
return OsPath(); // path too long, or other error
char resolved[PATH_MAX];
if (!realpath(absolute, resolved))
return OsPath();
return resolved;
}
// If it's not a path at all, i.e. it's just a filename, we'd
// probably have to search through PATH to find it.
// That's complex and should be uncommon, so don't bother.
return ERR::NO_SYS;
return OsPath();
}

View File

@ -78,7 +78,7 @@ LibError gfx_get_video_mode(int* xres, int* yres, int* bpp, int* freq)
}
LibError sys_get_executable_name(OsPath& pathname)
OsPath sys_ExecutablePathname()
{
static char name[PATH_MAX];
static bool init = false;
@ -88,9 +88,7 @@ LibError sys_get_executable_name(OsPath& pathname)
char temp[PATH_MAX];
u32 size = PATH_MAX;
if (_NSGetExecutablePath( temp, &size ))
{
return ERR::NO_SYS;
}
return OsPath();
realpath(temp, name);
}
@ -102,7 +100,5 @@ LibError sys_get_executable_name(OsPath& pathname)
debug_printf(L"app bundle name: %hs\n", name);
}
pathname = name;
return INFO::OK;
return name;
}

View File

@ -59,27 +59,26 @@ filesystem;
// called from the first filetime_to_time_t() call, not win.cpp init;
// this means we can rely on the current directory having been set to
// the app's directory (and therefore its appendant volume - see above).
// the app's directory (and the corresponding volume - see above).
static void detect_filesystem()
{
wchar_t root_path[MAX_PATH] = L"c:\\"; // default in case GCD fails
DWORD gcd_ret = GetCurrentDirectoryW(ARRAY_SIZE(root_path), root_path);
debug_assert(gcd_ret != 0);
// if this fails, no problem - we have the default from above.
root_path[3] = '\0'; // cut off after "c:\"
const DWORD length = GetCurrentDirectoryW(0, 0);
debug_assert(length != 0);
std::wstring rootPath(length, '\0');
const DWORD charsWritten = GetCurrentDirectoryW(length, &rootPath[0]);
debug_assert(charsWritten == length-1);
wchar_t fs_name[32] = {0};
BOOL ret = GetVolumeInformationW(root_path, 0,0,0,0,0, fs_name, sizeof(fs_name));
fs_name[ARRAY_SIZE(fs_name)-1] = '\0';
wchar_t drive[_MAX_DRIVE];
debug_assert(_wsplitpath_s(&rootPath[0], drive, ARRAY_SIZE(drive), 0,0, 0,0, 0,0) == 0);
wchar_t filesystemName[MAX_PATH+1] = {0}; // mandated by GetVolumeInformationW
BOOL ret = GetVolumeInformationW(OsString(OsPath(drive)/"").c_str(), 0,0,0,0,0, filesystemName, ARRAY_SIZE(filesystemName));
debug_assert(ret != 0);
// if this fails, no problem - we really only care if fs is FAT,
// and will assume that's not the case (since fs_name != "FAT").
filesystem = FS_UNKNOWN;
if(!wcsncmp(fs_name, L"FAT", 3)) // e.g. FAT32
if(!wcsncmp(filesystemName, L"FAT", 3)) // e.g. FAT32
filesystem = FS_FAT;
else if(!wcscmp(fs_name, L"NTFS"))
else if(!wcscmp(filesystemName, L"NTFS"))
filesystem = FS_NTFS;
}

View File

@ -40,6 +40,7 @@
#include "lib/module_init.h"
#include "lib/posix/posix_pthread.h"
#include "lib/sysdep/sysdep.h"
#include "lib/sysdep/os/win/wutil.h"
#include "lib/sysdep/os/win/winit.h"
#include "lib/sysdep/os/win/wmi.h" // for SDL_GetVideoInfo
@ -1477,21 +1478,6 @@ void SDL_Quit()
}
static OsPath GetStdoutPathname()
{
// the current directory is unreliable, so use the full path to
// the current executable.
wchar_t pathnameEXE[MAX_PATH];
const DWORD charsWritten = GetModuleFileNameW(0, pathnameEXE, ARRAY_SIZE(pathnameEXE));
debug_assert(charsWritten);
// add the EXE name to the filename to allow multiple executables
// with their own redirections. (we can't use wutil_ExecutablePath
// because it doesn't return the basename)
OsPath pathname = OsPath(pathnameEXE).ChangeExtension(L"_stdout.txt");
return pathname;
}
static void RedirectStdout()
{
// this process is apparently attached to a console, and users might be
@ -1499,7 +1485,10 @@ static void RedirectStdout()
if(wutil_IsValidHandle(GetStdHandle(STD_OUTPUT_HANDLE)))
return;
const OsPath pathname = GetStdoutPathname();
// this code may be included in multiple executables sharing the same
// directory, so include the executable's name in the filename. use its
// full path since the current directory is unreliable.
const OsPath pathname = sys_ExecutablePathname().ChangeExtension(L"_stdout.txt");
// ignore BoundsChecker warnings here. subsystem is set to "Windows"
// to prevent the OS from opening a console on startup (ugly).

View File

@ -363,6 +363,19 @@ LibError sys_error_description_r(int user_err, wchar_t* buf, size_t max_chars)
}
static LibError GetModulePathname(HMODULE hModule, OsPath& pathname)
{
wchar_t pathnameBuf[32768]; // NTFS limit
const DWORD length = (DWORD)ARRAY_SIZE(pathnameBuf);
const DWORD charsWritten = GetModuleFileNameW(hModule, pathnameBuf, length);
if(charsWritten == 0) // failed
return LibError_from_GLE();
debug_assert(charsWritten < length); // why would the above buffer ever be exceeded?
pathname = pathnameBuf;
return INFO::OK;
}
LibError sys_get_module_filename(void* addr, OsPath& pathname)
{
MEMORY_BASIC_INFORMATION mbi;
@ -370,27 +383,15 @@ LibError sys_get_module_filename(void* addr, OsPath& pathname)
if(!bytesWritten)
return LibError_from_GLE();
debug_assert(bytesWritten >= sizeof(mbi));
const HMODULE hModule = (HMODULE)mbi.AllocationBase;
wchar_t pathnameBuf[MAX_PATH+1];
const DWORD charsWritten = GetModuleFileNameW(hModule, pathnameBuf, (DWORD)ARRAY_SIZE(pathnameBuf));
if(charsWritten == 0)
return LibError_from_GLE();
debug_assert(charsWritten < ARRAY_SIZE(pathnameBuf));
pathname = pathnameBuf;
return INFO::OK;
return GetModulePathname((HMODULE)mbi.AllocationBase, pathname);
}
LibError sys_get_executable_name(OsPath& pathname)
OsPath sys_ExecutablePathname()
{
wchar_t pathnameBuf[MAX_PATH+1];
const DWORD charsWritten = GetModuleFileNameW(0, pathnameBuf, (DWORD)ARRAY_SIZE(pathnameBuf));
if(charsWritten == 0)
return LibError_from_GLE();
debug_assert(charsWritten < ARRAY_SIZE(pathnameBuf));
pathname = pathnameBuf;
return INFO::OK;
OsPath pathname;
debug_assert(GetModulePathname(0, pathname) == INFO::OK);
return pathname;
}
@ -437,7 +438,7 @@ LibError sys_pick_directory(OsPath& path)
return INFO::SKIPPED;
// translate ITEMIDLIST to string
wchar_t pathBuf[MAX_PATH];
wchar_t pathBuf[MAX_PATH]; // mandated by SHGetPathFromIDListW
const BOOL ok = SHGetPathFromIDListW(pidl, pathBuf);
// free the ITEMIDLIST

View File

@ -33,6 +33,7 @@
#include "lib/file/file.h"
#include "lib/file/vfs/vfs.h"
#include "lib/posix/posix.h"
#include "lib/sysdep/sysdep.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/os/win/wdbg.h" // wdbg_assert
#include "lib/sysdep/os/win/winit.h"
@ -266,14 +267,6 @@ bool wutil_HasCommandLineArgument(const wchar_t* arg)
//-----------------------------------------------------------------------------
// directories
OsPath wutil_DetectExecutablePath()
{
wchar_t modulePathname[MAX_PATH+1] = {0};
const DWORD len = GetModuleFileNameW(GetModuleHandle(0), modulePathname, MAX_PATH);
debug_assert(len != 0);
return OsPath(modulePathname).Parent();
}
// (NB: wutil_Init is called before static ctors => use placement new)
static OsPath* systemPath;
static OsPath* executablePath;
@ -298,22 +291,25 @@ const OsPath& wutil_AppdataPath()
static void GetDirectories()
{
WinScopedPreserveLastError s;
wchar_t path[MAX_PATH+1] = {0};
// system directory
{
const UINT charsWritten = GetSystemDirectoryW(path, MAX_PATH);
debug_assert(charsWritten != 0);
const UINT length = GetSystemDirectoryW(0, 0);
debug_assert(length != 0);
std::wstring path(length, '\0');
const UINT charsWritten = GetSystemDirectoryW(&path[0], length);
debug_assert(charsWritten == length-1);
systemPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path);
}
// executable's directory
executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(wutil_DetectExecutablePath());
executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(sys_ExecutablePathname().Parent());
// application data
{
HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder
HANDLE token = 0;
wchar_t path[MAX_PATH]; // mandated by SHGetFolderPathW
const HRESULT ret = SHGetFolderPathW(hwnd, CSIDL_APPDATA, token, 0, path);
debug_assert(SUCCEEDED(ret));
appdataPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path);

View File

@ -168,10 +168,6 @@ extern bool wutil_HasCommandLineArgument(const wchar_t* arg);
//-----------------------------------------------------------------------------
// directories
// used by wutil_ExecutablePath, but provided in case other code
// needs to know this before our wutil_Init runs.
extern OsPath wutil_DetectExecutablePath();
extern const OsPath& wutil_SystemPath();
extern const OsPath& wutil_ExecutablePath();
extern const OsPath& wutil_AppdataPath();

View File

@ -114,14 +114,11 @@ extern LibError sys_error_description_r(int err, wchar_t* buf, size_t max_chars)
LibError sys_get_module_filename(void* addr, OsPath& pathname);
/**
* Get path to the current executable.
*
* @param pathname Full path to executable (unchanged unless INFO::OK is returned).
* @return LibError
* @return full pathname of the current executable.
*
* this is useful for determining installation directory, e.g. for VFS.
**/
LIB_API LibError sys_get_executable_name(OsPath& pathname);
LIB_API OsPath sys_ExecutablePathname();
/**
* Get the current user's login name.

View File

@ -47,13 +47,12 @@ public:
TS_ASSERT_DIFFERS(a, b);
}
void test_sys_get_executable_name()
void test_sys_ExecutablePathname()
{
OsPath path;
OsPath path = sys_ExecutablePathname();
// Try it first with the real executable (i.e. the
// one that's running this test code)
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
// Check it's absolute
TSM_ASSERT(L"Path: "+path.string(), path_is_absolute(path.string().c_str()));
// Check the file exists
@ -61,7 +60,7 @@ public:
TSM_ASSERT_EQUALS(L"Path: "+path.string(), wstat(path, &s), 0);
// Do some platform-specific tests, based on the
// implementations of sys_get_executable_name:
// implementations of sys_ExecutablePathname:
#if OS_LINUX
// Since the implementation uses realpath, the tested files need to
@ -110,39 +109,34 @@ public:
// Try with absolute paths
{
Mock_dladdr d(rootstr+"/example/executable");
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
TS_ASSERT_PATH_EQUALS(path, rootstrw/L"example/executable");
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), rootstrw/L"example/executable");
}
{
Mock_dladdr d(rootstr+"/example/./a/b/../e/../../executable");
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
TS_ASSERT_PATH_EQUALS(path, rootstrw/L"example/executable");
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), rootstrw/L"example/executable");
}
// Try with relative paths
{
Mock_dladdr d("./executable");
Mock_getcwd m(rootstr+"/example");
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
TS_ASSERT_PATH_EQUALS(path, rootstrw/L"example/executable");
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), rootstrw/L"example/executable");
}
{
Mock_dladdr d("./executable");
Mock_getcwd m(rootstr+"/example/");
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
TS_ASSERT_PATH_EQUALS(path, rootstrw/L"example/executable");
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), rootstrw/L"example/executable");
}
{
Mock_dladdr d("../d/../../f/executable");
Mock_getcwd m(rootstr+"/example/a/b/c");
TS_ASSERT_EQUALS(sys_get_executable_name(path), INFO::OK);
TS_ASSERT_PATH_EQUALS(path, rootstrw/L"example/a/f/executable");
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), rootstrw/L"example/a/f/executable");
}
// Try with pathless names
{
Mock_dladdr d("executable");
TS_ASSERT_EQUALS(sys_get_executable_name(path), ERR::NO_SYS);
TS_ASSERT_PATH_EQUALS(sys_ExecutablePathname(), OsPath());
}
// Clean up the temporary files
@ -161,7 +155,7 @@ public:
#endif // OS_LINUX
}
// Mock classes for test_sys_get_executable_name
// Mock classes for test_sys_ExecutablePathname
#if OS_LINUX
class Mock_dladdr : public T::Base_dladdr
{

View File

@ -73,11 +73,9 @@ Paths::Paths(const CmdLineArgs& args)
/*static*/ OsPath Paths::Root(const OsPath& argv0)
{
// get full path to executable
OsPath pathname;
// .. first try safe, but system-dependent version
if(sys_get_executable_name(pathname) != INFO::OK)
OsPath pathname = sys_ExecutablePathname(); // safe, but requires OS-specific implementation
if(pathname.empty()) // failed, use argv[0] instead
{
// .. failed; use argv[0]
errno = 0;
pathname = wrealpath(argv0);
if(pathname.empty())

View File

@ -90,12 +90,10 @@ bool ts_str_contains(const std::wstring& str1, const std::wstring& str2)
// we need the (version-controlled) binaries/data directory because it
// contains input files (it is assumed that developer's machines have
// write access to those directories). note that argv0 isn't
// available, so we use sys_get_executable_name.
// available, so we use sys_ExecutablePathname.
OsPath DataDir()
{
OsPath path;
TS_ASSERT_OK(sys_get_executable_name(path));
return path.Parent()/"../data";
return sys_ExecutablePathname().Parent()/"../data";
}
// Script-based testing setup: