Skip to content

Instantly share code, notes, and snippets.

@Amanieu
Created October 10, 2013 21:11
Show Gist options
  • Save Amanieu/6925729 to your computer and use it in GitHub Desktop.
Save Amanieu/6925729 to your computer and use it in GitHub Desktop.
/*
===========================================================================
Daemon GPL Source Code
Copyright (C) 2013 Unvanquished Developers
This file is part of the Daemon GPL Source Code (Daemon Source Code).
Daemon Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daemon Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daemon Source Code. If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "FileSystem.h"
#include "../../libs/minizip/unzip.h"
#include <vector>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#endif
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
namespace FS {
// Pak search paths
static std::vector<std::string> pakPaths;
// Library & executable path
static std::string libPath;
// Home path
static std::string homePath;
// Clean up platform compatibility issues
static FILE* my_fopen(Str::StringRef path, char modeChar)
{
#ifdef _WIN32
wchar_t mode[3] = L"xb";
mode[0] = modeChar;
return _wfopen(Str::UTF8To16(path).c_str(), mode);
#else
char mode[3] = "xb";
mode[0] = modeChar;
#if defined(__APPLE__)
FILE* fd = fopen(path.c_str(), mode);
#elif defined(__linux__)
FILE* fd = fopen64(path.c_str(), mode);
#endif
// Only allow opening regular files
if (fd) {
struct stat st;
fstat(fileno(fd), &st);
if (!S_ISREG(st.st_mode)) {
fclose(fd);
errno = ENOENT;
return NULL;
}
}
return fd;
#endif
}
static offset_t my_ftell(FILE* fd)
{
#ifdef _WIN32
return _ftelli64(fd);
#elif defined(__APPLE__)
return ftello(fd);
#elif defined(__linux__)
return ftello64(fd);
#endif
}
static int my_fseek(FILE* fd, offset_t off, int whence)
{
#ifdef _WIN32
return _fseeki64(fd, off, whence);
#elif defined(__APPLE__)
return fseeko(fd, off, whence);
#elif defined(__linux__)
return fseeko64(fd, off, whence);
#endif
}
#ifdef _WIN32
typedef struct _stat32i64 my_stat_t;
#else
typedef struct stat64 my_stat_t;
#endif
static int my_fstat(int fd, my_stat_t* st)
{
#ifdef _WIN32
return _fstat32i64(fd, st);
#else
return fstat64(fd, st);
#endif
}
static int my_stat(Str::StringRef path, my_stat_t* st)
{
#ifdef _WIN32
return _wstat32i64(Str::UTF8To16(path).c_str(), st);
#else
return stat64(path.c_str(), st);
#endif
}
// std::error_code support for minizip
class minizip_category_impl: public std::error_category
{
public:
virtual const char* name() const noexcept override final
{
return "unzip";
}
virtual std::string message(int ev) const override final
{
switch (ev) {
case UNZ_OK:
return "Success";
case UNZ_END_OF_LIST_OF_FILE:
return "End of list of file";
case UNZ_ERRNO:
return "I/O error";
case UNZ_PARAMERROR:
return "Invalid parameter";
case UNZ_BADZIPFILE:
return "Bad zip file";
case UNZ_INTERNALERROR:
return "Internal error";
case UNZ_CRCERROR:
return "CRC error";
default:
return "Unknown error";
}
}
};
static const minizip_category_impl& minizip_category()
{
static minizip_category_impl instance;
return instance;
}
// Support code for error handling
static void SetErrorCode(std::error_code& err, int ec, const std::error_category& ecat)
{
std::error_code ecode(ec, ecat);
if (&err == &throws())
throw std::system_error(ecode);
else
err = ecode;
}
static void ClearErrorCode(std::error_code& err)
{
if (&err != &throws())
err = std::error_code();
}
static bool HaveError(std::error_code& err)
{
return &err != &throws() && err;
}
static void SetErrorCodeSystem(std::error_code& err)
{
#ifdef _WIN32
SetErrorCode(err, _doserrno, std::system_category());
#else
SetErrorCode(err, errno, std::generic_category());
#endif
}
static void SetErrorCodeFileNotFound(std::error_code& err)
{
#ifdef _WIN32
SetErrorCode(err, ERROR_FILE_NOT_FOUND, std::system_category());
#else
SetErrorCode(err, ENOENT, std::generic_category());
#endif
}
static void SetErrorCodeZlib(std::error_code& err, int num)
{
if (num == UNZ_ERRNO)
SetErrorCodeSystem(err);
else
SetErrorCode(err, num, minizip_category());
}
// Common code for file copying
static void InternalCopyFile(File* in, File* out, std::error_code& err)
{
char buffer[65536];
while (true) {
size_t read = in->Read(buffer, sizeof(buffer), err);
if (HaveError(err) || read == 0)
return;
out->Write(buffer, read, err);
if (HaveError(err))
return;
}
}
// Determine path to the executable
static std::string DefaultBasePath()
{
#ifdef _WIN32
wchar_t buffer[MAX_PATH];
DWORD len = GetModuleFileNameW(NULL, buffer, MAX_PATH);
if (len == 0 || len >= MAX_PATH)
return "";
wchar_t* p = wcsrchr(buffer, L'\\');
if (!p)
return "";
*p = L'\0';
return Str::UTF16To8(buffer);
#elif defined(__linux__)
struct stat st;
if (lstat("/proc/self/exe", &st) == -1)
return "";
std::unique_ptr<char[]> out(new char[st.st_size + 1]);
int len = readlink("/proc/self/exe", out.get(), st.st_size + 1);
if (len != st.st_size)
return "";
out[st.st_size] = '\0';
char* p = strrchr(out.get(), '/');
if (!p)
return "";
*p = '\0';
return out.get();
#elif defined(__APPLE__)
uint32_t bufsize = 0;
_NSGetExecutablePath(NULL, &bufsize);
std::unique_ptr<char[]> out(new char[bufsize]);
_NSGetExecutablePath(out.get(), &bufsize);
char* p = strrchr(out.get(), '/');
if (!p)
return "";
*p = '\0';
return out.get();
#endif
}
// Determine path to user settings directory
static std::string DefaultHomePath()
{
#ifdef _WIN32
wchar_t buffer[MAX_PATH];
if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, buffer)))
return "";
return Str::UTF16To8(buffer) + "\\My Games\\Unvanquished";
#else
const char *home = getenv("HOME");
if (!home)
return "";
#ifdef __APPLE__
return std::string(home) + "/Library/Application Support/Unvanquished";
#else
return std::string(home) + "/.unvanquished";
#endif
#endif
}
// Test a directory for write permission
static bool TestWritePermission(Str::StringRef path)
{
// Create a temporary file in the path and then delete it
std::string fname = Path::Build(path, "/.test_write_permission");
FILE *file = my_fopen(fname, 'w');
if (!file)
return false;
fclose(file);
#ifdef _WIN32
DeleteFileW(Str::UTF8To16(fname).c_str());
#else
unlink(fname.c_str());
#endif
return true;
}
void Initialize()
{
Com_StartupVariable("fs_basepath");
Com_StartupVariable("fs_extrapath");
Com_StartupVariable("fs_homepath");
Com_StartupVariable("fs_libpath");
std::string defaultBasePath = DefaultBasePath();
std::string defaultHomePath = (!defaultBasePath.empty() && TestWritePermission(defaultBasePath)) ? defaultBasePath : DefaultHomePath();
libPath = Cvar_Get("fs_libpath", defaultBasePath.c_str(), CVAR_INIT)->string;
homePath = Cvar_Get("fs_homepath", defaultHomePath.c_str(), CVAR_INIT)->string;
const char* basePath = Cvar_Get("fs_basepath", defaultBasePath.c_str(), CVAR_INIT)->string;
const char* extraPath = Cvar_Get("fs_extrapath", "", CVAR_INIT)->string;
pakPaths.push_back(homePath);
if (basePath != homePath)
pakPaths.push_back(basePath);
if (extraPath[0] && extraPath != basePath && extraPath != homePath)
pakPaths.push_back(extraPath);
}
const std::string& GetHomePath()
{
return homePath;
}
const std::string& GetLibPath()
{
return libPath;
}
bool Path::IsValid(Str::StringRef path, bool allowDir)
{
bool nonAlphaNum = true;
for (char c: path) {
if (c >= 'a' && c <= 'z')
continue;
if (c >= 'A' && c <= 'Z')
continue;
if (c >= '0' && c <= '9')
continue;
if (nonAlphaNum)
return false;
if (c != '/' && c != '-' && c != '_' && c != '.')
return false;
nonAlphaNum = true;
}
// An empty path or a path ending with / is a directory
if (!allowDir && nonAlphaNum)
return path.empty() || path.back() == '/';
return true;
}
std::string Path::Build(Str::StringRef base, Str::StringRef path)
{
if (base.empty())
return path.str();
std::string out = base.str();
#ifdef _WIN32
if (out.back() != '/' && out.back() != '\\')
#else
if (out.back() != '/')
#endif
out.push_back('/');
out.append(path.data(), path.size());
return out;
}
std::pair<std::string, std::string> Path::Split(Str::StringRef path)
{
const char* p = std::find(path.begin(), path.end(), '/');
return {std::string(path.begin(), p), std::string(p, path.end())};
}
// Real file back by a stdio handle
class OSFile: public File {
public:
OSFile(std::string filename, FILE* fd)
: File(std::move(filename)), fd(fd) {}
~OSFile()
{
fclose(fd);
}
virtual offset_t Length(std::error_code& err) const override final
{
my_stat_t st;
if (my_fstat(fileno(fd), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_size;
}
}
virtual time_t Timestamp(std::error_code& err) const override final
{
my_stat_t st;
if (my_fstat(fileno(fd), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_mtime;
}
}
virtual void SeekCur(offset_t off, std::error_code& err) override final
{
if (my_fseek(fd, off, SEEK_CUR) != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
virtual void SeekSet(offset_t off, std::error_code& err) override final
{
if (my_fseek(fd, off, SEEK_SET) != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
virtual void SeekEnd(offset_t off, std::error_code& err) override final
{
if (my_fseek(fd, off, SEEK_END) != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
virtual offset_t Tell() const override final
{
return my_ftell(fd);
}
virtual size_t Read(void* buffer, size_t length, std::error_code& err) override final
{
size_t result = fread(buffer, 1, length, fd);
if (result != length && ferror(fd))
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
return result;
}
virtual void Write(const void* data, size_t length, std::error_code& err) override final
{
if (fwrite(data, 1, length, fd) != length)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
virtual void Flush(std::error_code& err) override final
{
if (fflush(fd) != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
private:
FILE* fd;
};
// File inside a zip archive, read-only
class ZipFile: public File {
public:
ZipFile(std::string filename, unzFile zipFile, FILE* rawZipFile)
: File(std::move(filename)), zipFile(zipFile), rawZipFile(rawZipFile) {}
~ZipFile()
{
unzClose(zipFile);
}
virtual offset_t Length(std::error_code& err) const override final
{
unz_file_info64 fileInfo;
int result = unzGetCurrentFileInfo64(zipFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if (result != UNZ_OK) {
SetErrorCodeZlib(err, result);
return 0;
} else {
ClearErrorCode(err);
return fileInfo.uncompressed_size;
}
}
virtual time_t Timestamp(std::error_code& err) const override final
{
my_stat_t st;
if (my_fstat(fileno(rawZipFile), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_mtime;
}
}
virtual void SeekCur(offset_t off, std::error_code& err) override final
{
SeekSet(Tell() + off, err);
}
virtual void SeekSet(offset_t off, std::error_code& err) override final
{
offset_t pos = unztell64(zipFile);
if (off < pos) {
pos = 0;
int result = unzOpenCurrentFile(zipFile);
if (result < 0) {
SetErrorCodeZlib(err, result);
return;
}
} else
off -= pos;
char seekBuffer[65536];
while (off != 0) {
size_t toRead = std::min<size_t>(sizeof(seekBuffer), off);
int result = unzReadCurrentFile(zipFile, seekBuffer, toRead);
if (result < 0) {
SetErrorCodeZlib(err, result);
return;
}
off -= toRead;
}
ClearErrorCode(err);
}
virtual void SeekEnd(offset_t off, std::error_code& err) override final
{
offset_t length = Length(err);
if (HaveError(err))
return;
SeekSet(length + off, err);
}
virtual offset_t Tell() const override final
{
return unztell64(zipFile);
}
virtual size_t Read(void* buffer, size_t length, std::error_code& err) override final
{
int result = unzReadCurrentFile(zipFile, buffer, length);
if (result < 0)
SetErrorCodeZlib(err, result);
else
ClearErrorCode(err);
return result;
}
virtual void Write(const void* data, size_t length, std::error_code& err) override final
{
Q_UNUSED(data);
Q_UNUSED(length);
SetErrorCode(err, EBADF, std::generic_category());
}
virtual void Flush(std::error_code& err) override final
{
Q_UNUSED(err);
}
private:
unzFile zipFile;
FILE* rawZipFile;
};
// Open a zip file
static std::pair<unzFile, FILE*> OpenZip(Str::StringRef path, std::error_code& err)
{
// Initialize the zlib I/O functions
zlib_filefunc64_def funcs;
funcs.zopen64_file = [](voidpf opaque, const void* filename, int mode) -> voidpf {
// Interpret the filename as a file handle
Q_UNUSED(opaque);
Q_UNUSED(mode);
return const_cast<void*>(filename);
};
funcs.zread_file = [](voidpf opaque, voidpf stream, void* buf, uLong size) -> uLong {
Q_UNUSED(opaque);
return fread(buf, 1, size, static_cast<FILE*>(stream));
};
funcs.zwrite_file = [](voidpf opaque, voidpf stream, const void* buf, uLong size) -> uLong {
Q_UNUSED(opaque);
return fwrite(buf, 1, size, static_cast<FILE*>(stream));
};
funcs.ztell64_file = [](voidpf opaque, voidpf stream) -> ZPOS64_T {
Q_UNUSED(opaque);
return my_ftell(static_cast<FILE*>(stream));
};
funcs.zseek64_file = [](voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) -> long {
Q_UNUSED(opaque);
switch (origin) {
case ZLIB_FILEFUNC_SEEK_CUR:
origin = SEEK_CUR;
break;
case ZLIB_FILEFUNC_SEEK_END:
origin = SEEK_END;
break;
case ZLIB_FILEFUNC_SEEK_SET:
origin = SEEK_SET;
break;
default:
return -1;
}
return my_fseek(static_cast<FILE*>(stream), offset, origin);
};
funcs.zclose_file = [](voidpf opaque, voidpf stream) -> int {
Q_UNUSED(opaque);
return fclose(static_cast<FILE*>(stream));
};
funcs.zerror_file = [](voidpf opaque, voidpf stream) -> int {
Q_UNUSED(opaque);
return ferror(static_cast<FILE*>(stream));
};
// Open the file
FILE* fd = my_fopen(path, 'r');
if (!fd) {
SetErrorCodeSystem(err);
return {nullptr, nullptr};
}
// Open the zip with zlib
unzFile zf = unzOpen2_64(fd, &funcs);
if (!zf) {
// Unfortunately unzOpen doesn't return an error code, so we assume UNZ_BADZIPFILE
// Note that unzOpen will close the file if an error occurs
SetErrorCodeZlib(err, UNZ_BADZIPFILE);
return {nullptr, nullptr};
}
ClearErrorCode(err);
return {zf, fd};
}
bool LoadPakInternal(const PakDesc& pak, const std::vector<PakDesc>* whitelist)
{
}
std::unique_ptr<File> PakNamespace::OpenRead(Str::StringRef path, std::error_code& err) const
{
auto it = fileMap.find(path);
if (it == fileMap.end()) {
SetErrorCodeFileNotFound(err);
return nullptr;
}
if (it->second.first->type == PAK_ZIP) {
unzFile zipFile;
FILE* rawZipFile;
std::tie(zipFile, rawZipFile) = OpenZip(it->second.first->path, err);
if (HaveError(err))
return nullptr;
int result = unzSetOffset64(zipFile, it->second.second);
if (result != UNZ_OK) {
unzClose(zipFile);
SetErrorCodeZlib(err, result);
return nullptr;
}
result = unzOpenCurrentFile(zipFile);
if (result != UNZ_OK) {
unzClose(zipFile);
SetErrorCodeZlib(err, result);
return nullptr;
}
return std::unique_ptr<File>(new ZipFile(path, zipFile, rawZipFile));
} else {
FILE* fd = my_fopen(Path::Build(it->second.first->path, path), 'r');
if (!fd) {
SetErrorCodeSystem(err);
return nullptr;
}
return std::unique_ptr<File>(new OSFile(path, fd));
}
}
bool PakNamespace::FileExists(Str::StringRef path) const
{
return fileMap.find(path) != fileMap.end();
}
const LoadedPak* PakNamespace::LocateFile(Str::StringRef path, std::error_code& err) const
{
auto it = fileMap.find(path);
if (it == fileMap.end()) {
SetErrorCodeFileNotFound(err);
return nullptr;
} else {
ClearErrorCode(err);
return it->second.first;
}
}
offset_t PakNamespace::FileLength(Str::StringRef path, std::error_code& err) const
{
auto it = fileMap.find(path);
if (it == fileMap.end()) {
SetErrorCodeFileNotFound(err);
return 0;
}
if (it->second.first->type == PAK_DIR) {
my_stat_t st;
if (my_stat(Path::Build(it->second.first->path, path), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_size;
}
} else {
unzFile zipFile = OpenZip(it->second.first->path, err).first;
if (HaveError(err))
return 0;
int result = unzSetOffset64(zipFile, it->second.second);
if (result != UNZ_OK) {
unzClose(zipFile);
SetErrorCodeZlib(err, result);
return 0;
}
unz_file_info64 fileInfo;
result = unzGetCurrentFileInfo64(zipFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
if (result != UNZ_OK) {
unzClose(zipFile);
SetErrorCodeZlib(err, result);
return 0;
}
ClearErrorCode(err);
return fileInfo.uncompressed_size;
}
}
time_t PakNamespace::FileTimestamp(Str::StringRef path, std::error_code& err) const
{
auto it = fileMap.find(path);
if (it == fileMap.end()) {
SetErrorCodeFileNotFound(err);
return 0;
}
my_stat_t st;
int result;
if (it->second.first->type == PAK_DIR)
result = my_stat(Path::Build(it->second.first->path, path), &st);
else
result = my_stat(it->second.first->path, &st);
if (result != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_mtime;
}
}
void PakNamespace::ExtractFile(Str::StringRef from, Str::StringRef to, std::error_code& err) const
{
std::unique_ptr<File> in = OpenRead(from, err);
if (HaveError(err))
return;
std::unique_ptr<File> out = HomePath::OpenWrite(to, err);
if (HaveError(err))
return;
InternalCopyFile(in.get(), out.get(), err);
}
std::string File::ReadAll(std::error_code& err)
{
offset_t length = Length(err);
if (HaveError(err))
return "";
std::string out;
out.resize(length);
Read(&out[0], length, err);
return out;
}
template<bool recursive> void PakNamespace::DirIter::InternalAdvance()
{
for (; iter != end; ++iter) {
// Filter out any paths not in the specified directory
if (!Str::IsPrefix(prefix, iter->first))
continue;
// Don't list directories if doing a recurive search
if (recursive && iter->first.back() == '/')
continue;
// List immediate subdirectories only if not doing a recursive search
if (!recursive) {
auto end = iter->first.back() == '/' ? iter->first.end() - 1 : iter->first.end();
auto p = std::find(iter->first.begin() + prefix.size(), end, '/');
if (p == end)
continue;
}
current = iter->first.substr(prefix.size());
return;
}
current.clear();
}
void PakNamespace::DirIter::Advance(std::error_code& err)
{
++iter;
InternalAdvance<false>();
ClearErrorCode(err);
}
void PakNamespace::DirIterTree::Advance(std::error_code& err)
{
++iter;
InternalAdvance<true>();
ClearErrorCode(err);
}
DirectoryIterator<PakNamespace::DirIter> PakNamespace::ListFiles(Str::StringRef path, std::error_code& err) const
{
DirIter state;
state.prefix = path;
if (!state.prefix.empty() && state.prefix.back() != '/')
state.prefix.push_back('/');
state.iter = fileMap.begin();
state.end = fileMap.end();
state.InternalAdvance<false>();
if (state.current.empty())
SetErrorCodeFileNotFound(err);
else
ClearErrorCode(err);
return DirectoryIterator<DirIter>(state);
}
DirectoryIterator<PakNamespace::DirIterTree> PakNamespace::ListFilesRecursive(Str::StringRef path, std::error_code& err) const
{
DirIterTree state;
state.prefix = path;
if (!state.prefix.empty() && state.prefix.back() != '/')
state.prefix.push_back('/');
state.iter = fileMap.begin();
state.end = fileMap.end();
state.InternalAdvance<false>();
if (state.current.empty())
SetErrorCodeFileNotFound(err);
else
ClearErrorCode(err);
return DirectoryIterator<DirIterTree>(state);
}
namespace HomePath {
static std::unique_ptr<File> OpenMode(Str::StringRef path, char mode, std::error_code& err)
{
if (!Path::IsValid(path, false)) {
SetErrorCodeFileNotFound(err);
return 0;
}
FILE* fd = my_fopen(Path::Build(homePath, path), mode);
if (!fd) {
SetErrorCodeSystem(err);
return nullptr;
} else {
ClearErrorCode(err);
return std::unique_ptr<File>(new OSFile(path, fd));
}
}
std::unique_ptr<File> OpenRead(Str::StringRef path, std::error_code& err)
{
return OpenMode(path, 'r', err);
}
std::unique_ptr<File> OpenWrite(Str::StringRef path, std::error_code& err)
{
return OpenMode(path, 'w', err);
}
std::unique_ptr<File> OpenAppend(Str::StringRef path, std::error_code& err)
{
return OpenMode(path, 'a', err);
}
bool FileExists(Str::StringRef path)
{
if (!Path::IsValid(path, false))
return false;
my_stat_t st;
return my_stat(Path::Build(homePath, path), &st) == 0;
}
offset_t FileLength(Str::StringRef path, std::error_code& err)
{
if (!Path::IsValid(path, false)) {
SetErrorCodeFileNotFound(err);
return 0;
}
my_stat_t st;
if (my_stat(Path::Build(homePath, path), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_size;
}
}
time_t FileTimestamp(Str::StringRef path, std::error_code& err)
{
if (!Path::IsValid(path, false)) {
SetErrorCodeFileNotFound(err);
return 0;
}
my_stat_t st;
if (my_stat(Path::Build(homePath, path), &st) != 0) {
SetErrorCodeSystem(err);
return 0;
} else {
ClearErrorCode(err);
return st.st_mtime;
}
}
void CopyFile(Str::StringRef dest, Str::StringRef src, std::error_code& err)
{
std::unique_ptr<File> in = OpenRead(src, err);
if (HaveError(err))
return;
std::unique_ptr<File> out = OpenWrite(dest, err);
if (HaveError(err))
return;
InternalCopyFile(in.get(), out.get(), err);
}
void MoveFile(Str::StringRef dest, Str::StringRef src, std::error_code& err)
{
if (!Path::IsValid(dest, false) || !Path::IsValid(src, false)) {
SetErrorCodeFileNotFound(err);
return;
}
#ifdef _WIN32
// _wrename doesn't follow the POSIX standard because it will fail if the target already exists
if (!MoveFileExW(Str::UTF8To16(Path::Build(homePath, src)).c_str(), Str::UTF8To16(Path::Build(homePath, dest)).c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
SetErrorCode(err, GetLastError(), std::system_category());
else
ClearErrorCode(err);
#else
if (rename(Path::Build(homePath, src).c_str(), Path::Build(homePath, dest).c_str()) != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
#endif
}
void DeleteFile(Str::StringRef path, std::error_code& err)
{
if (!Path::IsValid(path, false)) {
SetErrorCodeFileNotFound(err);
return;
}
#ifdef _WIN32
if (_wunlink(Str::UTF8To16(Path::Build(homePath, path)).c_str()) != 0)
#else
if (unlink(Path::Build(homePath, path).c_str()) != 0)
#endif
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
}
void DirIter::Advance(std::error_code& err)
{
#ifdef _WIN32
WIN32_FIND_DATAW findData;
do {
if (!FindNextFileW(handle.get(), &findData)) {
int ec = GetLastError();
if (ec == ERROR_NO_MORE_FILES)
ClearErrorCode(err);
else
SetErrorCode(err, ec, std::system_category());
current.clear();
return;
}
current = Str::UTF16To8(findData.cFileName);
} while (!Path::IsValid(current, false));
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
current.push_back('/');
ClearErrorCode(err);
#else
struct dirent* dirent;
my_stat_t st;
do {
errno = 0;
dirent = readdir(static_cast<DIR*>(handle.get()));
if (!dirent) {
if (errno != 0)
SetErrorCodeSystem(err);
else
ClearErrorCode(err);
current.clear();
return;
}
} while (!Path::IsValid(dirent->d_name, false) || my_stat(Path::Build(path, dirent->d_name), &st) != 0);
current = dirent->d_name;
if (S_ISDIR(st.st_mode))
current.push_back('/');
ClearErrorCode(err);
#endif
}
DirectoryIterator<DirIter> ListFiles(Str::StringRef path, std::error_code& err)
{
if (!Path::IsValid(path, true)) {
SetErrorCodeFileNotFound(err);
return {};
}
std::string dirPath = Path::Build(homePath, path);
if (dirPath.back() == '/')
dirPath.pop_back();
#ifdef _WIN32
WIN32_FIND_DATAW findData;
HANDLE handle = FindFirstFileW(Str::UTF8To16(dirPath + "/*").c_str(), &findData);
if (handle == INVALID_HANDLE_VALUE) {
int ec = GetLastError();
if (ec == ERROR_FILE_NOT_FOUND || ec == ERROR_NO_MORE_FILES)
ClearErrorCode(err);
else
SetErrorCode(err, GetLastError(), std::system_category());
return {};
}
DirIter state;
state.handle = std::shared_ptr<void>(handle, [](void* handle) {
FindClose(handle);
});
state.current = Str::UTF16To8(findData.cFileName);
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
state.current.push_back('/');
if (!Path::IsValid(state.current, true))
state.Advance(err);
else
ClearErrorCode(err);
return DirectoryIterator<DirIter>(std::move(state));
#else
DIR* handle = opendir(dirPath.c_str());
if (!handle) {
SetErrorCodeSystem(err);
return {};
}
DirIter state;
state.handle = std::shared_ptr<DIR>(handle, [](DIR* handle) {
closedir(handle);
});
state.path = std::move(dirPath);
state.Advance(err);
return DirectoryIterator<DirIter>(std::move(state));
#endif
}
void DirIterTree::InternalAdvance(std::error_code& err)
{
while (!dirs.empty()) {
if (!dirs.back()) {
dirs.pop_back();
if (!dirs.empty()) {
dirs.back().increment(err);
if (HaveError(err))
return;
}
} else if (dirs.back()->back() == '/') {
dirs.push_back(ListFiles(path, err));
if (HaveError(err))
return;
} else {
current.clear();
for (auto& x: dirs)
current.append(*x);
return;
}
}
current.clear();
}
void DirIterTree::Advance(std::error_code& err)
{
dirs.back().increment(err);
if (HaveError(err))
return;
InternalAdvance(err);
}
DirectoryIterator<DirIterTree> ListFilesRecursive(Str::StringRef path, std::error_code& err)
{
DirIterTree state;
state.path = path;
if (!state.path.empty() && state.path.back() != '/')
state.path.push_back('/');
state.dirs.push_back(ListFiles(state.path, err));
if (HaveError(err))
return {};
state.InternalAdvance(err);
return DirectoryIterator<DirIterTree>(std::move(state));
}
} // namespace HomePath
} // namespace FS
/*
===========================================================================
Daemon GPL Source Code
Copyright (C) 2013 Unvanquished Developers
This file is part of the Daemon GPL Source Code (Daemon Source Code).
Daemon Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daemon Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daemon Source Code. If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#ifndef FRAMEWORK_FILESYSTEM_H_
#define FRAMEWORK_FILESYSTEM_H_
#include <system_error>
#include <iterator>
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
#include "../../shared/String.h"
namespace FS {
// File offset type. Using 64bit to allow large files.
typedef int64_t offset_t;
// Special value to indicate the function should throw a system_error instead
// of returning an error code. This avoids the need to have 2 overloads for each
// function.
inline std::error_code& throws()
{
std::error_code* ptr = nullptr;
return *ptr;
}
// Generic file interface which can be a real file or a file in a pak
class File {
public:
// Files are noncopyable
File(const File&) = delete;
File& operator=(const File&) = delete;
File(File&&) = delete;
File& operator=(File&&) = delete;
// Close the file
virtual ~File() {}
// Get the name of the file
const std::string& FileName() const
{
return filename;
}
// Get the length of the file
virtual offset_t Length(std::error_code& err = throws()) const = 0;
// Get the timestamp (time of last modification) of the file
virtual time_t Timestamp(std::error_code& err = throws()) const = 0;
// File position manipulation
virtual void SeekCur(offset_t off, std::error_code& err = throws()) = 0;
virtual void SeekSet(offset_t off, std::error_code& err = throws()) = 0;
virtual void SeekEnd(offset_t off, std::error_code& err = throws()) = 0;
virtual offset_t Tell() const = 0;
// Read/Write from the file
virtual size_t Read(void* buffer, size_t length, std::error_code& err = throws()) = 0;
virtual void Write(const void* data, size_t length, std::error_code& err = throws()) = 0;
// Flush write buffers
virtual void Flush(std::error_code& err = throws()) = 0;
// Read the entire file into a string
std::string ReadAll(std::error_code& err = throws());
protected:
File(std::string filename)
: filename(std::move(filename)) {}
std::string filename;
};
// Path manipulation functions
namespace Path {
// Check whether a path is valid. Validation rules:
// - The path separator is the forward slash (/)
// - A path element must start and end with [0-9][A-Z][a-z]
// - A path element may contain [0-9][A-Z][a-z]-_.
// - A path element may not contain two successive -_. characters
// Note that paths are validated in all filesystem functions, so manual
// validation is usually not required.
bool IsValid(Str::StringRef path, bool allowDir);
// Build a path from components
std::string Build(Str::StringRef base, Str::StringRef path);
// Split a path into a directory path and a filename
std::pair<std::string, std::string> Split(Str::StringRef path);
} // namespace Path
// Iterator which iterates over all files in a directory
template<typename State> class DirectoryIterator: public std::iterator<std::input_iterator_tag, std::string> {
// State interface:
// void Advance(std::error_code& err); Advance to the next directory entry
// std::string current; Current entry, empty for end-of-directory
public:
DirectoryIterator() {}
explicit DirectoryIterator(State state)
: state(std::move(state)) {}
const std::string& operator*() const
{
return state.current;
}
const std::string* operator->() const
{
return &state.current;
}
DirectoryIterator& increment(std::error_code& err = throws())
{
state.Advance(err);
return *this;
}
DirectoryIterator& operator++()
{
return increment(throws());
}
DirectoryIterator operator++(int)
{
DirectoryIterator out = *this;
increment(throws());
return out;
}
explicit operator bool() const
{
return *this != DirectoryIterator();
}
friend bool operator==(const DirectoryIterator& a, const DirectoryIterator& b)
{
return a.state.current == b.state.current;
}
friend bool operator!=(const DirectoryIterator& a, const DirectoryIterator& b)
{
return a.state.current != b.state.current;
}
const DirectoryIterator& begin() const
{
return *this;
}
DirectoryIterator end() const
{
return DirectoryIterator();
}
private:
State state;
};
// Type of pak
enum pakType_t {
PAK_ZIP, // Zip archive
PAK_DIR // Directory
};
// Name, version and checksum used to describe a pak file
struct PakDesc {
// Base name of the pak, may include directories
std::string name;
// Version of the pak
std::string version;
// Checksum of the pak
std::string checksum;
};
// Information about a loaded pak
struct LoadedPak {
// Pak information
PakDesc pak;
// Type of pak
pakType_t type;
// Full path to the pak
std::string path;
};
// A namespace is a set of pak files which contain files
class PakNamespace {
typedef std::unordered_map<std::string, std::pair<LoadedPak*, offset_t>> fileMap_t;
struct DirIter {
template<bool recursive> void InternalAdvance();
void Advance(std::error_code& err);
std::string current;
std::string prefix;
fileMap_t::const_iterator iter, end;
};
struct DirIterTree: public DirIter {
void Advance(std::error_code& err);
};
std::vector<LoadedPak> pakList;
fileMap_t fileMap;
bool LoadPakInternal(const PakDesc& pak, const std::vector<PakDesc>* whitelist);
public:
// Load a pak into the namespace with all its dependencies
bool LoadPak(const PakDesc& pak)
{
return LoadPakInternal(pak, nullptr);
}
// Load a pak and its dependencies, but only allow paks from the whitelist
bool LoadPakRestrict(const PakDesc& pak, const std::vector<PakDesc>& whitelist)
{
return LoadPakInternal(pak, &whitelist);
}
// Get a list of all the loaded paks
const std::vector<LoadedPak>& GetLoadedPaks() const
{
return pakList;
}
// Open a file for reading
std::unique_ptr<File> OpenRead(Str::StringRef path, std::error_code& err = throws()) const;
// Check if a file exists
bool FileExists(Str::StringRef path) const;
// Get the pak a file is in
const LoadedPak* LocateFile(Str::StringRef path, std::error_code& err = throws()) const;
// Get the timestamp and size of a file
offset_t FileLength(Str::StringRef path, std::error_code& err = throws()) const;
time_t FileTimestamp(Str::StringRef path, std::error_code& err = throws()) const;
// Extract a file to the homepath
void ExtractFile(Str::StringRef from, Str::StringRef to, std::error_code& err = throws()) const;
// List all files in the given subdirectory, optionally recursing into subdirectories
// Directory names are returned with a trailing slash to differentiate them from files
DirectoryIterator<DirIter> ListFiles(Str::StringRef path, std::error_code& err = throws()) const;
DirectoryIterator<DirIterTree> ListFilesRecursive(Str::StringRef path, std::error_code& err = throws()) const;
};
// Operations which work on the homepath instead of paks
namespace HomePath {
// Open a file for reading/writing/appending/editing
std::unique_ptr<File> OpenRead(Str::StringRef path, std::error_code& err = throws());
std::unique_ptr<File> OpenWrite(Str::StringRef path, std::error_code& err = throws());
std::unique_ptr<File> OpenAppend(Str::StringRef path, std::error_code& err = throws());
// Check if a file exists
bool FileExists(Str::StringRef path);
// Get the timestamp and size of a file
offset_t FileLength(Str::StringRef path, std::error_code& err = throws());
time_t FileTimestamp(Str::StringRef path, std::error_code& err = throws());
// Copy/move/delete a file
void CopyFile(Str::StringRef dest, Str::StringRef src, std::error_code& err = throws());
void MoveFile(Str::StringRef dest, Str::StringRef src, std::error_code& err = throws());
void DeleteFile(Str::StringRef path, std::error_code& err = throws());
// List all files in the given subdirectory, optionally recursing into subdirectories
// Directory names are returned with a trailing slash to differentiate them from files
struct DirIter {
void Advance(std::error_code& err);
std::string current;
std::shared_ptr<void> handle;
#ifndef _WIN32
std::string path;
#endif
};
struct DirIterTree {
void Advance(std::error_code& err);
void InternalAdvance(std::error_code& err);
std::string current;
std::string path;
std::vector<DirectoryIterator<DirIter>> dirs;
};
DirectoryIterator<DirIter> ListFiles(Str::StringRef path, std::error_code& err = throws());
DirectoryIterator<DirIterTree> ListFilesRecursive(Str::StringRef path, std::error_code& err = throws());
} // namespace HomePath
// Initialize the filesystem and the main paths
void Initialize();
// Get the home path
const std::string& GetHomePath();
// Get the path containing program binaries
const std::string& GetLibPath();
} // namespace FS
#endif // FRAMEWORK_FILESYSTEM_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment