mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Strip the legacy Android, React Native, Java, Gradle, and res/ directories from platforms/ios that were inherited from the original port. Configure the CMake build for the iOS target with local module discovery, PCAP and CURL guards, rapidyaml source path fixes, Vulkan disabled, lz4 build flags scoped, libjpeg-turbo skipped on iOS, and the Qt UI and test runners turned off. Set CMAKE_SYSTEM_PROCESSOR to arm64 for cross-compilation. Add a self-contained GitHub Actions workflow that builds an unsigned IPA for real devices using the iphoneos SDK, named with the commit SHA. Merge upstream master to stay current.
2728 lines
65 KiB
C++
2728 lines
65 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "FileSystem.h"
|
|
#include "Error.h"
|
|
#include "Path.h"
|
|
#include "Assertions.h"
|
|
#include "Console.h"
|
|
#include "StringUtil.h"
|
|
#include "Path.h"
|
|
#include "ProgressCallback.h"
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <numeric>
|
|
|
|
#ifdef __APPLE__
|
|
#include <mach-o/dyld.h>
|
|
#include <stdlib.h>
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <sys/sysctl.h>
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#include "common/RedtapeWindows.h"
|
|
#include <io.h>
|
|
#include <malloc.h>
|
|
#include <pathcch.h>
|
|
#include <winioctl.h>
|
|
#include <share.h>
|
|
#include <shlobj.h>
|
|
#else
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
|
|
static std::time_t ConvertFileTimeToUnixTime(const FILETIME& ft)
|
|
{
|
|
// based off https://stackoverflow.com/a/6161842
|
|
static constexpr s64 WINDOWS_TICK = 10000000;
|
|
static constexpr s64 SEC_TO_UNIX_EPOCH = 11644473600LL;
|
|
|
|
const s64 full = static_cast<s64>((static_cast<u64>(ft.dwHighDateTime) << 32) | static_cast<u64>(ft.dwLowDateTime));
|
|
return static_cast<std::time_t>(full / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
|
|
}
|
|
|
|
template <class T>
|
|
static bool IsUNCPath(const T& path)
|
|
{
|
|
return (path.length() >= 3 && path[0] == '\\' && path[1] == '\\');
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline bool FileSystemCharacterIsSane(char32_t c, bool strip_slashes)
|
|
{
|
|
#ifdef _WIN32
|
|
// https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#naming-conventions
|
|
if ((c == U'/' || c == U'\\') && strip_slashes)
|
|
return false;
|
|
|
|
if (c == U'<' || c == U'>' || c == U':' || c == U'"' || c == U'|' || c == U'?' || c == U'*' || c == 0 ||
|
|
c <= static_cast<char32_t>(31))
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
if (c == '/' && strip_slashes)
|
|
return false;
|
|
|
|
// drop asterisks too, they make globbing annoying
|
|
if (c == '*')
|
|
return false;
|
|
|
|
// macos doesn't allow colons, apparently
|
|
#ifdef __APPLE__
|
|
if (c == U':')
|
|
return false;
|
|
#endif
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
static inline void PathAppendString(std::string& dst, const T& src)
|
|
{
|
|
if (dst.capacity() < (dst.length() + src.length()))
|
|
dst.reserve(dst.length() + src.length());
|
|
|
|
bool last_separator = (!dst.empty() && dst.back() == FS_OSPATH_SEPARATOR_CHARACTER);
|
|
|
|
size_t index = 0;
|
|
|
|
#ifdef _WIN32
|
|
// special case for UNC paths here
|
|
if (dst.empty() && src.length() >= 3 && src[0] == '\\' && src[1] == '\\' && src[2] != '\\')
|
|
{
|
|
dst.append("\\\\");
|
|
index = 2;
|
|
}
|
|
#endif
|
|
|
|
for (; index < src.length(); index++)
|
|
{
|
|
const char ch = src[index];
|
|
|
|
#ifdef _WIN32
|
|
// convert forward slashes to backslashes
|
|
if (ch == '\\' || ch == '/')
|
|
#else
|
|
if (ch == '/')
|
|
#endif
|
|
{
|
|
if (last_separator)
|
|
continue;
|
|
last_separator = true;
|
|
dst.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
|
}
|
|
else
|
|
{
|
|
last_separator = false;
|
|
dst.push_back(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string Path::SanitizeFileName(const std::string_view str, bool strip_slashes /* = true */)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(str.length());
|
|
|
|
size_t pos = 0;
|
|
while (pos < str.length())
|
|
{
|
|
char32_t ch;
|
|
pos += StringUtil::DecodeUTF8(str, pos, &ch);
|
|
ch = FileSystemCharacterIsSane(ch, strip_slashes) ? ch : U'_';
|
|
StringUtil::EncodeAndAppendUTF8(ret, ch);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Windows: Can't end filename with a period.
|
|
if (ret.length() > 0 && ret.back() == '.')
|
|
ret.back() = '_';
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Path::SanitizeFileName(std::string* str, bool strip_slashes /* = true */)
|
|
{
|
|
const size_t len = str->length();
|
|
|
|
char small_buf[128];
|
|
std::unique_ptr<char[]> large_buf;
|
|
char* str_copy = small_buf;
|
|
if (len >= std::size(small_buf))
|
|
{
|
|
large_buf = std::make_unique<char[]>(len + 1);
|
|
str_copy = large_buf.get();
|
|
}
|
|
std::memcpy(str_copy, str->c_str(), sizeof(char) * (len + 1));
|
|
str->clear();
|
|
|
|
size_t pos = 0;
|
|
while (pos < len)
|
|
{
|
|
char32_t ch;
|
|
pos += StringUtil::DecodeUTF8(str_copy + pos, pos - len, &ch);
|
|
ch = FileSystemCharacterIsSane(ch, strip_slashes) ? ch : U'_';
|
|
StringUtil::EncodeAndAppendUTF8(*str, ch);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Windows: Can't end filename with a period.
|
|
if (str->length() > 0 && str->back() == '.')
|
|
str->back() = '_';
|
|
#endif
|
|
}
|
|
|
|
bool Path::IsValidFileName(const std::string_view str, bool allow_slashes)
|
|
{
|
|
const size_t len = str.length();
|
|
size_t pos = 0;
|
|
while (pos < len)
|
|
{
|
|
char32_t ch;
|
|
pos += StringUtil::DecodeUTF8(str.data() + pos, pos - len, &ch);
|
|
if (!FileSystemCharacterIsSane(ch, !allow_slashes))
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Windows: Can't end filename with a period.
|
|
if (len > 0 && str.back() == '.')
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
bool FileSystem::GetWin32Path(std::wstring* dest, std::string_view str)
|
|
{
|
|
// Just convert to wide if it's a relative path, MAX_PATH still applies.
|
|
if (!Path::IsAbsolute(str))
|
|
return StringUtil::UTF8StringToWideString(*dest, str);
|
|
|
|
// PathCchCanonicalizeEx() thankfully takes care of everything.
|
|
// But need to widen the string first, avoid the stack allocation.
|
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), nullptr, 0);
|
|
if (wlen <= 0) [[unlikely]]
|
|
return false;
|
|
|
|
// So copy it to a temp wide buffer first.
|
|
wchar_t* wstr_buf = static_cast<wchar_t*>(_malloca(sizeof(wchar_t) * (static_cast<size_t>(wlen) + 1)));
|
|
wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), wstr_buf, wlen);
|
|
if (wlen <= 0) [[unlikely]]
|
|
{
|
|
_freea(wstr_buf);
|
|
return false;
|
|
}
|
|
|
|
// And use PathCchCanonicalizeEx() to fix up any non-direct elements.
|
|
wstr_buf[wlen] = '\0';
|
|
dest->resize(std::max<size_t>(static_cast<size_t>(wlen) + (IsUNCPath(str) ? 9 : 5), 16));
|
|
for (;;)
|
|
{
|
|
const HRESULT hr =
|
|
PathCchCanonicalizeEx(dest->data(), dest->size(), wstr_buf, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
dest->resize(std::wcslen(dest->data()));
|
|
_freea(wstr_buf);
|
|
return true;
|
|
}
|
|
else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
|
|
{
|
|
dest->resize(dest->size() * 2);
|
|
continue;
|
|
}
|
|
else [[unlikely]]
|
|
{
|
|
Console.ErrorFmt("PathCchCanonicalizeEx() returned {:08X}", static_cast<unsigned>(hr));
|
|
_freea(wstr_buf);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::wstring FileSystem::GetWin32Path(std::string_view str)
|
|
{
|
|
std::wstring ret;
|
|
if (!GetWin32Path(&ret, str))
|
|
ret.clear();
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Path::IsAbsolute(const std::string_view path)
|
|
{
|
|
#ifdef _WIN32
|
|
return (path.length() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) &&
|
|
path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
|
|
(path.length() >= 3 && path[0] == '\\' && path[1] == '\\');
|
|
#else
|
|
return (path.length() >= 1 && path[0] == '/');
|
|
#endif
|
|
}
|
|
|
|
std::string Path::RealPath(const std::string_view path)
|
|
{
|
|
// Resolve non-absolute paths first.
|
|
std::vector<std::string_view> components;
|
|
// We need to keep the full combined path in scope
|
|
// as SplitNativePath() returns string_views to it.
|
|
std::string buf;
|
|
if (!IsAbsolute(path))
|
|
{
|
|
buf = Path::Combine(FileSystem::GetWorkingDirectory(), path);
|
|
components = Path::SplitNativePath(buf);
|
|
}
|
|
else
|
|
components = Path::SplitNativePath(path);
|
|
|
|
std::string realpath;
|
|
if (components.empty())
|
|
return realpath;
|
|
|
|
// Different to path because relative.
|
|
realpath.reserve(std::accumulate(components.begin(), components.end(), static_cast<size_t>(0),
|
|
[](size_t l, const std::string_view& s) { return l + s.length(); }) +
|
|
components.size() + 1);
|
|
|
|
#ifdef _WIN32
|
|
std::wstring wrealpath;
|
|
std::vector<WCHAR> symlink_buf;
|
|
wrealpath.reserve(realpath.size());
|
|
symlink_buf.resize(path.size() + 1);
|
|
|
|
// Check for any symbolic links throughout the path while adding components.
|
|
const bool skip_first = IsUNCPath(path);
|
|
bool test_symlink = true;
|
|
for (const std::string_view& comp : components)
|
|
{
|
|
if (!realpath.empty())
|
|
{
|
|
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
|
realpath.append(comp);
|
|
}
|
|
else if (skip_first)
|
|
{
|
|
realpath.append(comp);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
realpath.append(comp);
|
|
}
|
|
if (test_symlink)
|
|
{
|
|
DWORD attribs;
|
|
if (FileSystem::GetWin32Path(&wrealpath, realpath) &&
|
|
(attribs = GetFileAttributesW(wrealpath.c_str())) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
// if not a link, go to the next component
|
|
if (attribs & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
{
|
|
const HANDLE hFile =
|
|
CreateFileW(wrealpath.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
// is a link! resolve it.
|
|
DWORD ret = GetFinalPathNameByHandleW(hFile, symlink_buf.data(), static_cast<DWORD>(symlink_buf.size()),
|
|
FILE_NAME_NORMALIZED);
|
|
if (ret > symlink_buf.size())
|
|
{
|
|
symlink_buf.resize(ret);
|
|
ret = GetFinalPathNameByHandleW(hFile, symlink_buf.data(), static_cast<DWORD>(symlink_buf.size()),
|
|
FILE_NAME_NORMALIZED);
|
|
}
|
|
if (ret != 0)
|
|
StringUtil::WideStringToUTF8String(realpath, std::wstring_view(symlink_buf.data(), ret));
|
|
else
|
|
test_symlink = false;
|
|
|
|
CloseHandle(hFile);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a file or link
|
|
test_symlink = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetFinalPathNameByHandleW() adds a \\?\ prefix, so remove it.
|
|
if (realpath.starts_with("\\\\?\\") && IsAbsolute(std::string_view(realpath.data() + 4, realpath.size() - 4)))
|
|
{
|
|
realpath.erase(0, 4);
|
|
}
|
|
else if (realpath.starts_with("\\\\?\\UNC\\"))
|
|
{
|
|
realpath.erase(0, 7);
|
|
realpath.insert(realpath.begin(), '\\');
|
|
}
|
|
|
|
#else
|
|
// Why this monstrosity instead of calling realpath()? realpath() only works on files that exist.
|
|
std::string basepath;
|
|
std::string symlink;
|
|
|
|
basepath.reserve(realpath.capacity());
|
|
symlink.resize(realpath.capacity());
|
|
|
|
// Check for any symbolic links throughout the path while adding components.
|
|
bool test_symlink = true;
|
|
for (const std::string_view& comp : components)
|
|
{
|
|
if (!test_symlink)
|
|
{
|
|
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
|
realpath.append(comp);
|
|
continue;
|
|
}
|
|
|
|
basepath = realpath;
|
|
if (realpath.empty() || realpath.back() != FS_OSPATH_SEPARATOR_CHARACTER)
|
|
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
|
realpath.append(comp);
|
|
|
|
// Check if the last component added is a symlink
|
|
struct stat sb;
|
|
if (lstat(realpath.c_str(), &sb) != 0)
|
|
{
|
|
// Don't bother checking any further components once we error out.
|
|
test_symlink = false;
|
|
continue;
|
|
}
|
|
else if (!S_ISLNK(sb.st_mode))
|
|
{
|
|
// Nope, keep going.
|
|
continue;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
ssize_t sz = readlink(realpath.c_str(), symlink.data(), symlink.size());
|
|
if (sz < 0)
|
|
{
|
|
// shouldn't happen, due to the S_ISLNK check above.
|
|
test_symlink = false;
|
|
break;
|
|
}
|
|
else if (static_cast<size_t>(sz) == symlink.size())
|
|
{
|
|
// need a larger buffer
|
|
symlink.resize(symlink.size() * 2);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// is a link, and we resolved it. gotta check if the symlink itself is relative :(
|
|
symlink.resize(static_cast<size_t>(sz));
|
|
if (!Path::IsAbsolute(symlink))
|
|
{
|
|
// symlink is relative to the directory of the symlink
|
|
realpath = basepath;
|
|
if (realpath.empty() || realpath.back() != FS_OSPATH_SEPARATOR_CHARACTER)
|
|
realpath.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
|
realpath.append(symlink);
|
|
}
|
|
else
|
|
{
|
|
// Use the new, symlinked path.
|
|
realpath = symlink;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any relative symlinks were resolved, there may be '.' and '..'
|
|
// components in the resultant path, which must be removed.
|
|
realpath = Path::Canonicalize(realpath);
|
|
|
|
#endif
|
|
|
|
return realpath;
|
|
}
|
|
|
|
std::string Path::ToNativePath(const std::string_view path)
|
|
{
|
|
std::string ret;
|
|
PathAppendString(ret, path);
|
|
|
|
// remove trailing slashes
|
|
if (ret.length() > 1)
|
|
{
|
|
while (ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
|
|
ret.pop_back();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Path::ToNativePath(std::string* path)
|
|
{
|
|
*path = Path::ToNativePath(*path);
|
|
}
|
|
|
|
std::string Path::Canonicalize(const std::string_view path)
|
|
{
|
|
std::vector<std::string_view> components = Path::SplitNativePath(path);
|
|
std::vector<std::string_view> new_components;
|
|
new_components.reserve(components.size());
|
|
for (const std::string_view& component : components)
|
|
{
|
|
if (component == ".")
|
|
{
|
|
// current directory, so it can be skipped, unless it's the only component
|
|
if (components.size() == 1)
|
|
new_components.push_back(std::move(component));
|
|
}
|
|
else if (component == "..")
|
|
{
|
|
// parent directory, pop one off if we're not at the beginning, otherwise preserve.
|
|
if (!new_components.empty())
|
|
new_components.pop_back();
|
|
else
|
|
new_components.push_back(std::move(component));
|
|
}
|
|
else
|
|
{
|
|
// anything else, preserve
|
|
new_components.push_back(std::move(component));
|
|
}
|
|
}
|
|
|
|
return Path::JoinNativePath(new_components);
|
|
}
|
|
|
|
void Path::Canonicalize(std::string* path)
|
|
{
|
|
*path = Canonicalize(*path);
|
|
}
|
|
|
|
std::string Path::MakeRelative(const std::string_view path, const std::string_view relative_to)
|
|
{
|
|
// simple algorithm, we just work on the components. could probably be better, but it'll do for now.
|
|
std::vector<std::string_view> path_components(SplitNativePath(path));
|
|
std::vector<std::string_view> relative_components(SplitNativePath(relative_to));
|
|
std::vector<std::string_view> new_components;
|
|
|
|
// both must be absolute paths
|
|
if (Path::IsAbsolute(path) && Path::IsAbsolute(relative_to))
|
|
{
|
|
// find the number of same components
|
|
size_t num_same = 0;
|
|
for (size_t i = 0; i < path_components.size() && i < relative_components.size(); i++)
|
|
{
|
|
if (path_components[i] == relative_components[i])
|
|
num_same++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
// we need at least one same component
|
|
if (num_same > 0)
|
|
{
|
|
// from the relative_to directory, back up to the start of the common components
|
|
const size_t num_ups = relative_components.size() - num_same;
|
|
for (size_t i = 0; i < num_ups; i++)
|
|
new_components.emplace_back("..");
|
|
|
|
// and add the remainder of the path components
|
|
for (size_t i = num_same; i < path_components.size(); i++)
|
|
new_components.push_back(std::move(path_components[i]));
|
|
}
|
|
else
|
|
{
|
|
// no similarity
|
|
new_components = std::move(path_components);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not absolute
|
|
new_components = std::move(path_components);
|
|
}
|
|
|
|
return JoinNativePath(new_components);
|
|
}
|
|
|
|
std::string_view Path::GetExtension(const std::string_view path)
|
|
{
|
|
const std::string_view::size_type pos = path.rfind('.');
|
|
if (pos == std::string_view::npos)
|
|
return std::string_view();
|
|
else
|
|
return path.substr(pos + 1);
|
|
}
|
|
|
|
std::string_view Path::StripExtension(const std::string_view path)
|
|
{
|
|
const std::string_view::size_type pos = path.rfind('.');
|
|
if (pos == std::string_view::npos)
|
|
return path;
|
|
|
|
return path.substr(0, pos);
|
|
}
|
|
|
|
std::string Path::ReplaceExtension(const std::string_view path, const std::string_view new_extension)
|
|
{
|
|
const std::string_view::size_type pos = path.rfind('.');
|
|
if (pos == std::string_view::npos)
|
|
return std::string(path);
|
|
|
|
std::string ret(path, 0, pos + 1);
|
|
ret.append(new_extension);
|
|
return ret;
|
|
}
|
|
|
|
static std::string_view::size_type GetLastSeperatorPosition(const std::string_view filename, bool include_separator)
|
|
{
|
|
std::string_view::size_type last_separator = filename.rfind('/');
|
|
if (include_separator && last_separator != std::string_view::npos)
|
|
last_separator++;
|
|
|
|
#if defined(_WIN32)
|
|
std::string_view::size_type other_last_separator = filename.rfind('\\');
|
|
if (other_last_separator != std::string_view::npos)
|
|
{
|
|
if (include_separator)
|
|
other_last_separator++;
|
|
if (last_separator == std::string_view::npos || other_last_separator > last_separator)
|
|
last_separator = other_last_separator;
|
|
}
|
|
#endif
|
|
|
|
return last_separator;
|
|
}
|
|
|
|
std::string_view Path::GetDirectory(const std::string_view path)
|
|
{
|
|
const std::string::size_type pos = GetLastSeperatorPosition(path, false);
|
|
if (pos == std::string_view::npos)
|
|
return {};
|
|
|
|
return path.substr(0, pos);
|
|
}
|
|
|
|
std::string_view Path::GetFileName(const std::string_view path)
|
|
{
|
|
const std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
|
|
if (pos == std::string_view::npos)
|
|
return path;
|
|
|
|
return path.substr(pos);
|
|
}
|
|
|
|
std::string_view Path::GetFileTitle(const std::string_view path)
|
|
{
|
|
const std::string_view filename(GetFileName(path));
|
|
const std::string::size_type pos = filename.rfind('.');
|
|
if (pos == std::string_view::npos)
|
|
return filename;
|
|
|
|
return filename.substr(0, pos);
|
|
}
|
|
|
|
std::string Path::ChangeFileName(const std::string_view path, const std::string_view new_filename)
|
|
{
|
|
std::string ret;
|
|
PathAppendString(ret, path);
|
|
|
|
const std::string_view::size_type pos = GetLastSeperatorPosition(ret, true);
|
|
if (pos == std::string_view::npos)
|
|
{
|
|
ret.clear();
|
|
PathAppendString(ret, new_filename);
|
|
}
|
|
else
|
|
{
|
|
if (!new_filename.empty())
|
|
{
|
|
ret.erase(pos);
|
|
PathAppendString(ret, new_filename);
|
|
}
|
|
else
|
|
{
|
|
ret.erase(pos - 1);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Path::ChangeFileName(std::string* path, const std::string_view new_filename)
|
|
{
|
|
*path = ChangeFileName(*path, new_filename);
|
|
}
|
|
|
|
std::string Path::AppendDirectory(const std::string_view path, const std::string_view new_dir)
|
|
{
|
|
std::string ret;
|
|
if (!new_dir.empty())
|
|
{
|
|
const std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
|
|
|
|
ret.reserve(path.length() + new_dir.length() + 1);
|
|
if (pos != std::string_view::npos)
|
|
PathAppendString(ret, path.substr(0, pos));
|
|
|
|
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
|
|
ret.pop_back();
|
|
|
|
if (!ret.empty())
|
|
ret += FS_OSPATH_SEPARATOR_CHARACTER;
|
|
|
|
PathAppendString(ret, new_dir);
|
|
|
|
if (pos != std::string_view::npos)
|
|
{
|
|
const std::string_view filepart(path.substr(pos));
|
|
if (!filepart.empty())
|
|
{
|
|
ret += FS_OSPATH_SEPARATOR_CHARACTER;
|
|
PathAppendString(ret, filepart);
|
|
}
|
|
}
|
|
else if (!path.empty())
|
|
{
|
|
ret += FS_OSPATH_SEPARATOR_CHARACTER;
|
|
PathAppendString(ret, path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PathAppendString(ret, path);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Path::AppendDirectory(std::string* path, const std::string_view new_dir)
|
|
{
|
|
*path = AppendDirectory(*path, new_dir);
|
|
}
|
|
|
|
std::vector<std::string_view> Path::SplitWindowsPath(const std::string_view path)
|
|
{
|
|
std::vector<std::string_view> parts;
|
|
|
|
std::string::size_type start = 0;
|
|
std::string::size_type pos = 0;
|
|
|
|
// preserve unc paths
|
|
if (path.size() > 2 && path[0] == '\\' && path[1] == '\\')
|
|
pos = 2;
|
|
|
|
while (pos < path.size())
|
|
{
|
|
if (path[pos] != '/' && path[pos] != '\\')
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
// skip consecutive separators
|
|
if (pos != start)
|
|
parts.push_back(path.substr(start, pos - start));
|
|
|
|
pos++;
|
|
start = pos;
|
|
}
|
|
|
|
if (start != pos)
|
|
parts.push_back(path.substr(start));
|
|
|
|
return parts;
|
|
}
|
|
|
|
std::string Path::JoinWindowsPath(const std::vector<std::string_view>& components)
|
|
{
|
|
return StringUtil::JoinString(components.begin(), components.end(), '\\');
|
|
}
|
|
|
|
std::vector<std::string_view> Path::SplitNativePath(const std::string_view path)
|
|
{
|
|
#ifdef _WIN32
|
|
return SplitWindowsPath(path);
|
|
#else
|
|
std::vector<std::string_view> parts;
|
|
|
|
std::string::size_type start = 0;
|
|
std::string::size_type pos = 0;
|
|
while (pos < path.size())
|
|
{
|
|
if (path[pos] != '/')
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
// skip consecutive separators
|
|
// for unix, we create an empty element at the beginning when it's an absolute path
|
|
// that way, when it's re-joined later, we preserve the starting slash.
|
|
if (pos != start || pos == 0)
|
|
parts.push_back(path.substr(start, pos - start));
|
|
|
|
pos++;
|
|
start = pos;
|
|
}
|
|
|
|
if (start != pos)
|
|
parts.push_back(path.substr(start));
|
|
|
|
return parts;
|
|
#endif
|
|
}
|
|
|
|
std::string Path::JoinNativePath(const std::vector<std::string_view>& components)
|
|
{
|
|
return StringUtil::JoinString(components.begin(), components.end(), FS_OSPATH_SEPARATOR_CHARACTER);
|
|
}
|
|
|
|
std::vector<std::string> FileSystem::GetRootDirectoryList()
|
|
{
|
|
std::vector<std::string> results;
|
|
|
|
#if defined(_WIN32)
|
|
char buf[256];
|
|
const DWORD size = GetLogicalDriveStringsA(sizeof(buf), buf);
|
|
if (size != 0 && size < (sizeof(buf) - 1))
|
|
{
|
|
const char* ptr = buf;
|
|
while (*ptr != '\0')
|
|
{
|
|
const std::size_t len = std::strlen(ptr);
|
|
results.emplace_back(ptr, len);
|
|
ptr += len + 1u;
|
|
}
|
|
}
|
|
#else
|
|
const char* home_path = std::getenv("HOME");
|
|
if (home_path)
|
|
results.push_back(home_path);
|
|
|
|
results.push_back("/");
|
|
#endif
|
|
|
|
return results;
|
|
}
|
|
|
|
std::string Path::BuildRelativePath(const std::string_view filename, const std::string_view new_filename)
|
|
{
|
|
std::string new_string;
|
|
|
|
std::string_view::size_type pos = GetLastSeperatorPosition(filename, true);
|
|
if (pos != std::string_view::npos)
|
|
new_string.assign(filename, 0, pos);
|
|
new_string.append(new_filename);
|
|
return new_string;
|
|
}
|
|
|
|
std::string Path::Combine(const std::string_view base, const std::string_view next)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(base.length() + next.length() + 1);
|
|
|
|
PathAppendString(ret, base);
|
|
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
|
|
ret.pop_back();
|
|
|
|
ret += FS_OSPATH_SEPARATOR_CHARACTER;
|
|
PathAppendString(ret, next);
|
|
while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER)
|
|
ret.pop_back();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
std::string Path::URLEncode(std::string_view str)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(str.length() + ((str.length() + 3) / 4) * 3);
|
|
|
|
for (size_t i = 0, l = str.size(); i < l; i++)
|
|
{
|
|
const char c = str[i];
|
|
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' ||
|
|
c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')')
|
|
{
|
|
ret.push_back(c);
|
|
}
|
|
else
|
|
{
|
|
ret.push_back('%');
|
|
|
|
const unsigned char n1 = static_cast<unsigned char>(c) >> 4;
|
|
const unsigned char n2 = static_cast<unsigned char>(c) & 0x0F;
|
|
ret.push_back((n1 >= 10) ? ('a' + (n1 - 10)) : ('0' + n1));
|
|
ret.push_back((n2 >= 10) ? ('a' + (n2 - 10)) : ('0' + n2));
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::string Path::URLDecode(std::string_view str)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(str.length());
|
|
|
|
for (size_t i = 0, l = str.size(); i < l; i++)
|
|
{
|
|
const char c = str[i];
|
|
if (c == '+')
|
|
{
|
|
ret.push_back(c);
|
|
}
|
|
else if (c == '%')
|
|
{
|
|
if ((i + 2) >= str.length())
|
|
break;
|
|
|
|
const char clower = str[i + 1];
|
|
const char cupper = str[i + 2];
|
|
const unsigned char lower =
|
|
(clower >= '0' && clower <= '9') ?
|
|
static_cast<unsigned char>(clower - '0') :
|
|
((clower >= 'a' && clower <= 'f') ?
|
|
static_cast<unsigned char>(clower - 'a') :
|
|
((clower >= 'A' && clower <= 'F') ? static_cast<unsigned char>(clower - 'A') : 0));
|
|
const unsigned char upper =
|
|
(cupper >= '0' && cupper <= '9') ?
|
|
static_cast<unsigned char>(cupper - '0') :
|
|
((cupper >= 'a' && cupper <= 'f') ?
|
|
static_cast<unsigned char>(cupper - 'a') :
|
|
((cupper >= 'A' && cupper <= 'F') ? static_cast<unsigned char>(cupper - 'A') : 0));
|
|
const char dch = static_cast<char>(lower | (upper << 4));
|
|
ret.push_back(dch);
|
|
}
|
|
else
|
|
{
|
|
ret.push_back(c);
|
|
}
|
|
}
|
|
|
|
return std::string(str);
|
|
}
|
|
|
|
std::string Path::CreateFileURL(std::string_view path)
|
|
{
|
|
pxAssert(IsAbsolute(path));
|
|
|
|
std::string ret;
|
|
ret.reserve(path.length() + 10);
|
|
ret.append("file://");
|
|
|
|
const std::vector<std::string_view> components = SplitNativePath(path);
|
|
pxAssertRel(!components.empty(), "Trying to create a URL from an empty path.");
|
|
|
|
const std::string_view& first = components.front();
|
|
#ifdef _WIN32
|
|
// Windows doesn't urlencode the drive letter.
|
|
// UNC paths should be omit the leading slash.
|
|
if (first.starts_with("\\\\"))
|
|
{
|
|
// file://hostname/...
|
|
ret.append(first.substr(2));
|
|
}
|
|
else
|
|
{
|
|
// file:///c:/...
|
|
fmt::format_to(std::back_inserter(ret), "/{}", first);
|
|
}
|
|
#else
|
|
// Don't append a leading slash for the first component.
|
|
ret.append(first);
|
|
#endif
|
|
|
|
for (size_t comp = 1; comp < components.size(); comp++)
|
|
{
|
|
fmt::format_to(std::back_inserter(ret), "/{}", URLEncode(components[comp]));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error* error)
|
|
{
|
|
#ifdef _WIN32
|
|
const std::wstring wfilename = GetWin32Path(filename);
|
|
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
|
|
if (!wfilename.empty() && !wmode.empty())
|
|
{
|
|
std::FILE* fp;
|
|
const errno_t err = _wfopen_s(&fp, wfilename.c_str(), wmode.c_str());
|
|
if (err != 0)
|
|
{
|
|
Error::SetErrno(error, err);
|
|
return nullptr;
|
|
}
|
|
|
|
return fp;
|
|
}
|
|
|
|
std::FILE* fp;
|
|
const errno_t err = fopen_s(&fp, filename, mode);
|
|
if (err != 0)
|
|
{
|
|
Error::SetErrno(error, err);
|
|
return nullptr;
|
|
}
|
|
|
|
return fp;
|
|
#else
|
|
std::FILE* fp = std::fopen(filename, mode);
|
|
if (!fp)
|
|
Error::SetErrno(error, errno);
|
|
return fp;
|
|
#endif
|
|
}
|
|
|
|
std::FILE* FileSystem::OpenCFileTryIgnoreCase(const char* filename, const char* mode, Error* error)
|
|
{
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
return OpenCFile(filename, mode, error);
|
|
#else
|
|
std::FILE* fp = std::fopen(filename, mode);
|
|
const auto cur_errno = errno;
|
|
|
|
if (!fp)
|
|
{
|
|
const auto dir = std::string(Path::GetDirectory(filename));
|
|
FindResultsArray files;
|
|
if (FindFiles(dir.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &files))
|
|
{
|
|
for (auto& file : files)
|
|
{
|
|
if (StringUtil::compareNoCase(file.FileName, filename))
|
|
{
|
|
fp = std::fopen(file.FileName.c_str(), mode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!fp)
|
|
Error::SetErrno(error, cur_errno);
|
|
return fp;
|
|
#endif
|
|
}
|
|
|
|
|
|
int FileSystem::OpenFDFile(const char* filename, int flags, int mode, Error* error)
|
|
{
|
|
#ifdef _WIN32
|
|
const std::wstring wfilename = GetWin32Path(filename);
|
|
if (!wfilename.empty())
|
|
return _wopen(wfilename.c_str(), flags, mode);
|
|
|
|
return -1;
|
|
#else
|
|
const int fd = open(filename, flags, mode);
|
|
if (fd < 0)
|
|
Error::SetErrno(error, errno);
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFile(const char* filename, const char* mode, Error* error)
|
|
{
|
|
return ManagedCFilePtr(OpenCFile(filename, mode, error));
|
|
}
|
|
|
|
FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFileTryIgnoreCase(const char* filename, const char* mode, Error* error)
|
|
{
|
|
return ManagedCFilePtr(OpenCFileTryIgnoreCase(filename, mode, error));
|
|
}
|
|
|
|
std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error)
|
|
{
|
|
#ifdef _WIN32
|
|
const std::wstring wfilename = GetWin32Path(filename);
|
|
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
|
|
if (wfilename.empty() || wmode.empty())
|
|
return nullptr;
|
|
|
|
int share_flags = 0;
|
|
switch (share_mode)
|
|
{
|
|
case FileShareMode::DenyNone:
|
|
share_flags = _SH_DENYNO;
|
|
break;
|
|
case FileShareMode::DenyRead:
|
|
share_flags = _SH_DENYRD;
|
|
break;
|
|
case FileShareMode::DenyWrite:
|
|
share_flags = _SH_DENYWR;
|
|
break;
|
|
case FileShareMode::DenyReadWrite:
|
|
default:
|
|
share_flags = _SH_DENYRW;
|
|
break;
|
|
}
|
|
|
|
std::FILE* fp = _wfsopen(wfilename.c_str(), wmode.c_str(), share_flags);
|
|
if (fp)
|
|
return fp;
|
|
|
|
Error::SetErrno(error, errno);
|
|
return nullptr;
|
|
#else
|
|
std::FILE* fp = std::fopen(filename, mode);
|
|
if (!fp)
|
|
Error::SetErrno(error, errno);
|
|
return fp;
|
|
#endif
|
|
}
|
|
|
|
FileSystem::ManagedCFilePtr FileSystem::OpenManagedSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error)
|
|
{
|
|
return ManagedCFilePtr(OpenSharedCFile(filename, mode, share_mode, error));
|
|
}
|
|
|
|
int FileSystem::FSeek64(std::FILE* fp, s64 offset, int whence)
|
|
{
|
|
#ifdef _WIN32
|
|
return _fseeki64(fp, offset, whence);
|
|
#else
|
|
return fseeko(fp, static_cast<off_t>(offset), whence);
|
|
#endif
|
|
}
|
|
|
|
s64 FileSystem::FTell64(std::FILE* fp)
|
|
{
|
|
#ifdef _WIN32
|
|
return static_cast<s64>(_ftelli64(fp));
|
|
#else
|
|
return static_cast<s64>(ftello(fp));
|
|
#endif
|
|
}
|
|
|
|
s64 FileSystem::FSize64(std::FILE* fp)
|
|
{
|
|
const s64 pos = FTell64(fp);
|
|
if (pos >= 0)
|
|
{
|
|
if (FSeek64(fp, 0, SEEK_END) == 0)
|
|
{
|
|
const s64 size = FTell64(fp);
|
|
if (FSeek64(fp, pos, SEEK_SET) == 0)
|
|
return size;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
s64 FileSystem::GetPathFileSize(const char* Path)
|
|
{
|
|
FILESYSTEM_STAT_DATA sd;
|
|
if (!StatFile(Path, &sd))
|
|
return -1;
|
|
|
|
return sd.Size;
|
|
}
|
|
|
|
std::optional<std::time_t> FileSystem::GetFileTimestamp(const char* path)
|
|
{
|
|
FILESYSTEM_STAT_DATA sd;
|
|
if (!StatFile(path, &sd))
|
|
return std::nullopt;
|
|
|
|
return sd.ModificationTime;
|
|
}
|
|
|
|
std::optional<std::vector<u8>> FileSystem::ReadBinaryFile(const char* filename)
|
|
{
|
|
ManagedCFilePtr fp = OpenManagedCFile(filename, "rb");
|
|
if (!fp)
|
|
return std::nullopt;
|
|
|
|
return ReadBinaryFile(fp.get());
|
|
}
|
|
|
|
std::optional<std::vector<u8>> FileSystem::ReadBinaryFile(std::FILE* fp)
|
|
{
|
|
const s64 size = FSize64(fp);
|
|
if (size < 0)
|
|
return std::nullopt;
|
|
|
|
std::fseek(fp, 0, SEEK_SET);
|
|
std::vector<u8> res(static_cast<size_t>(size));
|
|
if (size > 0 && std::fread(res.data(), 1u, static_cast<size_t>(size), fp) != static_cast<size_t>(size))
|
|
return std::nullopt;
|
|
|
|
return res;
|
|
}
|
|
|
|
std::optional<std::string> FileSystem::ReadFileToString(const char* filename)
|
|
{
|
|
ManagedCFilePtr fp = OpenManagedCFile(filename, "rb");
|
|
if (!fp)
|
|
return std::nullopt;
|
|
|
|
return ReadFileToString(fp.get());
|
|
}
|
|
|
|
std::optional<std::string> FileSystem::ReadFileToString(std::FILE* fp)
|
|
{
|
|
const s64 size = FSize64(fp);
|
|
if (size < 0)
|
|
return std::nullopt;
|
|
|
|
std::fseek(fp, 0, SEEK_SET);
|
|
std::string res;
|
|
res.resize(static_cast<size_t>(size));
|
|
// NOTE - assumes mode 'rb', for example, this will fail over missing Windows carriage return bytes
|
|
if (size > 0 && std::fread(res.data(), 1u, static_cast<size_t>(size), fp) != static_cast<size_t>(size))
|
|
return std::nullopt;
|
|
|
|
return res;
|
|
}
|
|
|
|
bool FileSystem::WriteBinaryFile(const char* filename, const void* data, size_t data_length)
|
|
{
|
|
ManagedCFilePtr fp = OpenManagedCFile(filename, "wb");
|
|
if (!fp)
|
|
return false;
|
|
|
|
if (data_length > 0 && std::fwrite(data, 1u, data_length, fp.get()) != data_length)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::WriteStringToFile(const char* filename, const std::string_view sv)
|
|
{
|
|
ManagedCFilePtr fp = OpenManagedCFile(filename, "wb");
|
|
if (!fp)
|
|
return false;
|
|
|
|
if (sv.length() > 0 && std::fwrite(sv.data(), 1u, sv.length(), fp.get()) != sv.length())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t FileSystem::ReadFileWithProgress(std::FILE* fp, void* dst, size_t length,
|
|
ProgressCallback* progress, Error* error, size_t chunk_size)
|
|
{
|
|
progress->SetProgressRange(100);
|
|
|
|
return FileSystem::ReadFileWithPartialProgress(fp, dst, length, progress, 0, 100, error, chunk_size);
|
|
}
|
|
|
|
size_t FileSystem::ReadFileWithPartialProgress(std::FILE* fp, void* dst, size_t length,
|
|
ProgressCallback* progress, int startPercent, int endPercent, Error* error, size_t chunk_size)
|
|
{
|
|
const int deltaPercent = endPercent - startPercent;
|
|
|
|
size_t done = 0;
|
|
while (done < length)
|
|
{
|
|
if (progress->IsCancelled())
|
|
break;
|
|
|
|
const size_t read_size = std::min(length - done, chunk_size);
|
|
if (std::fread(static_cast<u8*>(dst) + done, read_size, 1, fp) != 1)
|
|
{
|
|
Error::SetErrno(error, "fread() failed: ", errno);
|
|
break;
|
|
}
|
|
|
|
progress->SetProgressValue(startPercent + (done * deltaPercent) / length);
|
|
done += read_size;
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
static std::span<const u8> MapBinaryFileForRead(HANDLE handle)
|
|
{
|
|
LARGE_INTEGER size;
|
|
if (!GetFileSizeEx(handle, &size) || size.QuadPart == 0)
|
|
return {};
|
|
HANDLE mapping = CreateFileMappingW(handle, nullptr, PAGE_READONLY, size.HighPart, size.LowPart, nullptr);
|
|
if (!mapping)
|
|
return {};
|
|
void* ptr = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
|
|
CloseHandle(mapping);
|
|
return {static_cast<const u8*>(ptr), static_cast<size_t>(size.QuadPart)};
|
|
}
|
|
#else
|
|
static std::span<const u8> MapBinaryFileForRead(int fd)
|
|
{
|
|
struct stat s;
|
|
if (0 != fstat(fd, &s) || s.st_size == 0)
|
|
return {};
|
|
size_t size = static_cast<size_t>(s.st_size);
|
|
void* ptr = mmap(nullptr, size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
|
|
if (ptr == MAP_FAILED)
|
|
return {};
|
|
return {static_cast<const u8*>(ptr), size};
|
|
}
|
|
#endif
|
|
|
|
std::span<const u8> FileSystem::MapBinaryFileForRead(const char* path)
|
|
{
|
|
#ifdef _WIN32
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
HANDLE handle = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return {};
|
|
std::span<const u8> result = ::MapBinaryFileForRead(handle);
|
|
CloseHandle(handle);
|
|
return result;
|
|
#else
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd <= 0)
|
|
return {};
|
|
std::span<const u8> result = ::MapBinaryFileForRead(fd);
|
|
close(fd);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
std::span<const u8> FileSystem::MapBinaryFileForRead(std::FILE* fp)
|
|
{
|
|
#ifdef _WIN32
|
|
return ::MapBinaryFileForRead(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp))));
|
|
#else
|
|
return ::MapBinaryFileForRead(fileno(fp));
|
|
#endif
|
|
}
|
|
|
|
void FileSystem::UnmapFile(std::span<const u8> file)
|
|
{
|
|
#ifdef _WIN32
|
|
UnmapViewOfFile(const_cast<u8*>(file.data()));
|
|
#else
|
|
munmap(const_cast<u8*>(file.data()), file.size());
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::EnsureDirectoryExists(const char* path, bool recursive, Error* error)
|
|
{
|
|
if (FileSystem::DirectoryExists(path))
|
|
return true;
|
|
|
|
// if it fails to create, we're not going to be able to use it anyway
|
|
return FileSystem::CreateDirectoryPath(path, recursive, error);
|
|
}
|
|
|
|
bool FileSystem::RecursiveDeleteDirectory(const char* path)
|
|
{
|
|
FindResultsArray results;
|
|
if (FindFiles(path, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results))
|
|
{
|
|
for (const FILESYSTEM_FIND_DATA& fd : results)
|
|
{
|
|
if (IsSymbolicLink(fd.FileName.c_str()))
|
|
{
|
|
if (!DeleteSymbolicLink(fd.FileName.c_str()))
|
|
return false;
|
|
}
|
|
else if ((fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
if (!RecursiveDeleteDirectory(fd.FileName.c_str()))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!DeleteFilePath(fd.FileName.c_str()))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return DeleteDirectory(path);
|
|
}
|
|
|
|
bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace)
|
|
{
|
|
#ifndef _WIN32
|
|
// TODO: There's technically a race here between checking and opening the file..
|
|
// But fopen doesn't specify any way to say "don't create if it exists"...
|
|
if (!replace && FileExists(destination))
|
|
return false;
|
|
|
|
auto in_fp = OpenManagedCFile(source, "rb");
|
|
if (!in_fp)
|
|
return false;
|
|
|
|
auto out_fp = OpenManagedCFile(destination, "wb");
|
|
if (!out_fp)
|
|
return false;
|
|
|
|
u8 buf[4096];
|
|
while (!std::feof(in_fp.get()))
|
|
{
|
|
size_t bytes_in = std::fread(buf, 1, sizeof(buf), in_fp.get());
|
|
if ((bytes_in == 0 && !std::feof(in_fp.get())) ||
|
|
(bytes_in > 0 && std::fwrite(buf, 1, bytes_in, out_fp.get()) != bytes_in))
|
|
{
|
|
out_fp.reset();
|
|
DeleteFilePath(destination);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (std::fflush(out_fp.get()) != 0)
|
|
{
|
|
out_fp.reset();
|
|
DeleteFilePath(destination);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
return CopyFileW(GetWin32Path(source).c_str(), GetWin32Path(destination).c_str(), !replace);
|
|
#endif
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
static u32 TranslateWin32Attributes(u32 Win32Attributes)
|
|
{
|
|
u32 r = 0;
|
|
|
|
if (Win32Attributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
r |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
if (Win32Attributes & FILE_ATTRIBUTE_READONLY)
|
|
r |= FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY;
|
|
if (Win32Attributes & FILE_ATTRIBUTE_COMPRESSED)
|
|
r |= FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED;
|
|
|
|
return r;
|
|
}
|
|
|
|
static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path, const char* path, const char* pattern,
|
|
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited, ProgressCallback* cancel)
|
|
{
|
|
if (cancel && cancel->IsCancelled())
|
|
return 0;
|
|
|
|
std::string search_dir;
|
|
if (path)
|
|
{
|
|
if (parent_path)
|
|
search_dir = fmt::format("{}\\{}\\{}\\*", origin_path, parent_path, path);
|
|
else
|
|
search_dir = fmt::format("{}\\{}\\*", origin_path, path);
|
|
}
|
|
else
|
|
{
|
|
search_dir = fmt::format("{}\\*", origin_path);
|
|
}
|
|
|
|
// holder for utf-8 conversion
|
|
WIN32_FIND_DATAW wfd;
|
|
std::string utf8_filename;
|
|
utf8_filename.reserve((sizeof(wfd.cFileName) / sizeof(wfd.cFileName[0])) * 2);
|
|
|
|
const HANDLE hFind = FindFirstFileW(FileSystem::GetWin32Path(search_dir).c_str(), &wfd);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
|
|
// small speed optimization for '*' case
|
|
bool hasWildCards = false;
|
|
bool wildCardMatchAll = false;
|
|
u32 nFiles = 0;
|
|
if (std::strpbrk(pattern, "*?"))
|
|
{
|
|
hasWildCards = true;
|
|
wildCardMatchAll = !(std::strcmp(pattern, "*"));
|
|
}
|
|
|
|
// iterate results
|
|
do
|
|
{
|
|
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & FILESYSTEM_FIND_HIDDEN_FILES))
|
|
continue;
|
|
|
|
if (wfd.cFileName[0] == L'.')
|
|
{
|
|
if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0'))
|
|
continue;
|
|
}
|
|
|
|
if (!StringUtil::WideStringToUTF8String(utf8_filename, wfd.cFileName))
|
|
continue;
|
|
|
|
FILESYSTEM_FIND_DATA outData;
|
|
outData.Attributes = 0;
|
|
|
|
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
if (flags & FILESYSTEM_FIND_RECURSIVE)
|
|
{
|
|
// check that we're not following an infinite symbolic link loop
|
|
std::string real_recurse_dir;
|
|
if (parent_path)
|
|
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename));
|
|
else if (path)
|
|
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename));
|
|
else
|
|
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}", origin_path, utf8_filename));
|
|
if (real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
|
|
{
|
|
if (!real_recurse_dir.empty())
|
|
visited.push_back(std::move(real_recurse_dir));
|
|
|
|
// recurse into this directory
|
|
if (parent_path)
|
|
{
|
|
const std::string recurse_dir = fmt::format("{}\\{}", parent_path, path);
|
|
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags, results, visited, cancel);
|
|
}
|
|
else
|
|
{
|
|
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited, cancel);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(flags & FILESYSTEM_FIND_FOLDERS))
|
|
continue;
|
|
|
|
outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
if (!(flags & FILESYSTEM_FIND_FILES))
|
|
continue;
|
|
}
|
|
|
|
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
|
|
outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY;
|
|
|
|
// match the filename
|
|
if (hasWildCards)
|
|
{
|
|
if (!wildCardMatchAll && !StringUtil::WildcardMatch(utf8_filename.c_str(), pattern))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (std::strcmp(utf8_filename.c_str(), pattern) != 0)
|
|
continue;
|
|
}
|
|
|
|
// add file to list
|
|
if (!(flags & FILESYSTEM_FIND_RELATIVE_PATHS))
|
|
{
|
|
if (parent_path)
|
|
outData.FileName = fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename);
|
|
else if (path)
|
|
outData.FileName = fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename);
|
|
else
|
|
outData.FileName = fmt::format("{}\\{}", origin_path, utf8_filename);
|
|
}
|
|
else
|
|
{
|
|
if (parent_path)
|
|
outData.FileName = fmt::format("{}\\{}\\{}", parent_path, path, utf8_filename);
|
|
else if (path)
|
|
outData.FileName = fmt::format("{}\\{}", path, utf8_filename);
|
|
else
|
|
outData.FileName = utf8_filename;
|
|
}
|
|
|
|
outData.CreationTime = ConvertFileTimeToUnixTime(wfd.ftCreationTime);
|
|
outData.ModificationTime = ConvertFileTimeToUnixTime(wfd.ftLastWriteTime);
|
|
outData.Size = (static_cast<u64>(wfd.nFileSizeHigh) << 32) | static_cast<u64>(wfd.nFileSizeLow);
|
|
|
|
nFiles++;
|
|
results->push_back(std::move(outData));
|
|
} while (FindNextFileW(hFind, &wfd) == TRUE);
|
|
FindClose(hFind);
|
|
|
|
return nFiles;
|
|
}
|
|
|
|
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// clear result array
|
|
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
|
|
results->clear();
|
|
|
|
// add self if recursive, we don't want to visit it twice
|
|
std::vector<std::string> visited;
|
|
if (flags & FILESYSTEM_FIND_RECURSIVE)
|
|
{
|
|
std::string real_path = Path::RealPath(path);
|
|
if (!real_path.empty())
|
|
visited.push_back(std::move(real_path));
|
|
}
|
|
|
|
// enter the recursive function
|
|
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
|
|
return false;
|
|
|
|
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
|
|
{
|
|
std::sort(results->begin(), results->end(), [](const FILESYSTEM_FIND_DATA& lhs, const FILESYSTEM_FIND_DATA& rhs) {
|
|
// directories first
|
|
if ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) !=
|
|
(rhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
return ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0);
|
|
}
|
|
|
|
return (StringUtil::Strcasecmp(lhs.FileName.c_str(), rhs.FileName.c_str()) < 0);
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void TranslateStat64(struct stat* st, const struct _stat64& st64)
|
|
{
|
|
static constexpr __int64 MAX_SIZE = static_cast<__int64>(std::numeric_limits<decltype(st->st_size)>::max());
|
|
st->st_dev = st64.st_dev;
|
|
st->st_ino = st64.st_ino;
|
|
st->st_mode = st64.st_mode;
|
|
st->st_nlink = st64.st_nlink;
|
|
st->st_uid = st64.st_uid;
|
|
st->st_rdev = st64.st_rdev;
|
|
st->st_size = static_cast<decltype(st->st_size)>((st64.st_size > MAX_SIZE) ? MAX_SIZE : st64.st_size);
|
|
st->st_atime = static_cast<time_t>(st64.st_atime);
|
|
st->st_mtime = static_cast<time_t>(st64.st_mtime);
|
|
st->st_ctime = static_cast<time_t>(st64.st_ctime);
|
|
}
|
|
|
|
bool FileSystem::StatFile(const char* path, struct stat* st)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
return false;
|
|
|
|
struct _stat64 st64;
|
|
if (_wstat64(wpath.c_str(), &st64) != 0)
|
|
return false;
|
|
|
|
TranslateStat64(st, st64);
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
|
|
{
|
|
const int fd = _fileno(fp);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
struct _stat64 st64;
|
|
if (_fstat64(fd, &st64) != 0)
|
|
return false;
|
|
|
|
TranslateStat64(st, st64);
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
return false;
|
|
|
|
// determine attributes for the path. if it's a directory, things have to be handled differently..
|
|
DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
|
|
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
|
|
return false;
|
|
|
|
// test if it is a directory
|
|
HANDLE hFile;
|
|
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
}
|
|
else
|
|
{
|
|
hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
|
OPEN_EXISTING, 0, nullptr);
|
|
}
|
|
|
|
// createfile succeded?
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
// use GetFileInformationByHandle
|
|
BY_HANDLE_FILE_INFORMATION bhfi;
|
|
if (GetFileInformationByHandle(hFile, &bhfi) == FALSE)
|
|
{
|
|
CloseHandle(hFile);
|
|
return false;
|
|
}
|
|
|
|
// close handle
|
|
CloseHandle(hFile);
|
|
|
|
// fill in the stat data
|
|
sd->Attributes = TranslateWin32Attributes(bhfi.dwFileAttributes);
|
|
sd->CreationTime = ConvertFileTimeToUnixTime(bhfi.ftCreationTime);
|
|
sd->ModificationTime = ConvertFileTimeToUnixTime(bhfi.ftLastWriteTime);
|
|
sd->Size = static_cast<s64>(((u64)bhfi.nFileSizeHigh) << 32 | (u64)bhfi.nFileSizeLow);
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd)
|
|
{
|
|
const int fd = _fileno(fp);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
struct _stat64 st;
|
|
if (_fstat64(fd, &st) != 0)
|
|
return false;
|
|
|
|
// parse attributes
|
|
sd->CreationTime = st.st_ctime;
|
|
sd->ModificationTime = st.st_mtime;
|
|
sd->Attributes = 0;
|
|
if ((st.st_mode & _S_IFMT) == _S_IFDIR)
|
|
sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
// parse size
|
|
if ((st.st_mode & _S_IFMT) == _S_IFREG)
|
|
sd->Size = st.st_size;
|
|
else
|
|
sd->Size = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::FileExists(const char* path)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
return false;
|
|
|
|
// determine attributes for the path. if it's a directory, things have to be handled differently..
|
|
DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
|
|
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
|
|
return false;
|
|
|
|
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DirectoryExists(const char* path)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
return false;
|
|
|
|
// determine attributes for the path. if it's a directory, things have to be handled differently..
|
|
DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
|
|
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
|
|
return false;
|
|
|
|
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool FileSystem::DirectoryIsEmpty(const char* path)
|
|
{
|
|
std::wstring wpath = GetWin32Path(path);
|
|
wpath += L"\\*";
|
|
|
|
WIN32_FIND_DATAW wfd;
|
|
HANDLE hFind = FindFirstFileW(wpath.c_str(), &wfd);
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
return true;
|
|
|
|
do
|
|
{
|
|
if (wfd.cFileName[0] == L'.')
|
|
{
|
|
if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0'))
|
|
continue;
|
|
}
|
|
|
|
FindClose(hFind);
|
|
return false;
|
|
} while (FindNextFileW(hFind, &wfd));
|
|
|
|
FindClose(hFind);
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::CreateDirectoryPath(const char* Path, bool Recursive, Error* error)
|
|
{
|
|
const std::wstring wpath = GetWin32Path(Path);
|
|
|
|
// has a path
|
|
if (wpath.empty()) [[unlikely]]
|
|
{
|
|
Error::SetStringView(error, "Path is empty.");
|
|
return false;
|
|
}
|
|
|
|
// try just flat-out, might work if there's no other segments that have to be made
|
|
if (CreateDirectoryW(wpath.c_str(), nullptr))
|
|
return true;
|
|
|
|
// check error
|
|
DWORD lastError = GetLastError();
|
|
if (lastError == ERROR_ALREADY_EXISTS)
|
|
{
|
|
// check the attributes
|
|
const u32 Attributes = GetFileAttributesW(wpath.c_str());
|
|
if (Attributes != INVALID_FILE_ATTRIBUTES && Attributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
return true;
|
|
}
|
|
|
|
if (!Recursive)
|
|
{
|
|
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
|
|
return false;
|
|
}
|
|
|
|
// check error
|
|
if (lastError == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
// part of the path does not exist, so we'll create the parent folders, then
|
|
// the full path again.
|
|
const size_t pathLength = wpath.size();
|
|
std::wstring tempPath;
|
|
tempPath.reserve(pathLength);
|
|
|
|
// for absolute paths, we need to skip over the path root
|
|
size_t rootLength = 0;
|
|
if (Path::IsAbsolute(Path))
|
|
{
|
|
const wchar_t* root_start = wpath.c_str();
|
|
wchar_t* root_end;
|
|
const HRESULT hr = PathCchSkipRoot(const_cast<wchar_t*>(root_start), &root_end);
|
|
if (FAILED(hr))
|
|
{
|
|
Error::SetHResult(error, "PathCchSkipRoot() failed: ", hr);
|
|
return false;
|
|
}
|
|
rootLength = static_cast<size_t>(root_end - root_start);
|
|
|
|
// copy path root
|
|
tempPath.append(wpath, 0, rootLength);
|
|
}
|
|
|
|
// create directories along the path
|
|
for (size_t i = rootLength; i < pathLength; i++)
|
|
{
|
|
if (wpath[i] == L'\\' || wpath[i] == L'/')
|
|
{
|
|
const BOOL result = CreateDirectoryW(tempPath.c_str(), nullptr);
|
|
if (!result)
|
|
{
|
|
lastError = GetLastError();
|
|
if (lastError != ERROR_ALREADY_EXISTS) // fine, continue to next path segment
|
|
{
|
|
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// replace / with \.
|
|
tempPath.push_back('\\');
|
|
}
|
|
else
|
|
{
|
|
tempPath.push_back(wpath[i]);
|
|
}
|
|
}
|
|
|
|
// re-create the end if it's not a separator, check / as well because windows can interpret them
|
|
if (wpath[pathLength - 1] != L'\\' && wpath[pathLength - 1] != L'/')
|
|
{
|
|
const BOOL result = CreateDirectoryW(wpath.c_str(), nullptr);
|
|
if (!result)
|
|
{
|
|
lastError = GetLastError();
|
|
if (lastError != ERROR_ALREADY_EXISTS)
|
|
{
|
|
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ok
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// unhandled error
|
|
Error::SetWin32(error, "CreateDirectoryW() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FileSystem::DeleteFilePath(const char* path, Error* error)
|
|
{
|
|
if (path[0] == '\0')
|
|
{
|
|
Error::SetStringView(error, "Path is empty.");
|
|
return false;
|
|
}
|
|
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
|
|
if (fileAttributes == INVALID_FILE_ATTRIBUTES || fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
Error::SetStringView(error, "File does not exist.");
|
|
return false;
|
|
}
|
|
|
|
if (!DeleteFileW(wpath.c_str()))
|
|
{
|
|
Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error)
|
|
{
|
|
const std::wstring old_wpath = GetWin32Path(old_path);
|
|
const std::wstring new_wpath = GetWin32Path(new_path);
|
|
|
|
if (!MoveFileExW(old_wpath.c_str(), new_wpath.c_str(), MOVEFILE_REPLACE_EXISTING))
|
|
{
|
|
const DWORD err = GetLastError();
|
|
Error::SetWin32(error, "MoveFileExW() failed: ", err);
|
|
Console.Error("MoveFileEx('%s', '%s') failed: %08X", old_path, new_path, err);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DeleteDirectory(const char* path)
|
|
{
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
return RemoveDirectoryW(wpath.c_str());
|
|
}
|
|
|
|
std::string FileSystem::GetProgramPath()
|
|
{
|
|
std::wstring buffer;
|
|
buffer.resize(MAX_PATH);
|
|
|
|
// Fall back to the main module if this fails.
|
|
HMODULE module = nullptr;
|
|
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
reinterpret_cast<LPCWSTR>(&GetProgramPath), &module);
|
|
|
|
for (;;)
|
|
{
|
|
DWORD nChars = GetModuleFileNameW(module, buffer.data(), static_cast<DWORD>(buffer.size()));
|
|
if (nChars == static_cast<DWORD>(buffer.size()) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
buffer.resize(buffer.size() * 2);
|
|
continue;
|
|
}
|
|
|
|
buffer.resize(nChars);
|
|
break;
|
|
}
|
|
|
|
// Windows symlinks don't behave silly like Linux, so no need to RealPath() it.
|
|
return StringUtil::WideStringToUTF8String(buffer);
|
|
}
|
|
|
|
std::string FileSystem::GetWorkingDirectory()
|
|
{
|
|
DWORD required_size = GetCurrentDirectoryW(0, nullptr);
|
|
if (!required_size)
|
|
return {};
|
|
|
|
std::wstring buffer;
|
|
buffer.resize(required_size - 1);
|
|
|
|
if (!GetCurrentDirectoryW(static_cast<DWORD>(buffer.size() + 1), buffer.data()))
|
|
return {};
|
|
|
|
return StringUtil::WideStringToUTF8String(buffer);
|
|
}
|
|
|
|
bool FileSystem::SetWorkingDirectory(const char* path)
|
|
{
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
return (SetCurrentDirectoryW(wpath.c_str()) == TRUE);
|
|
}
|
|
|
|
bool FileSystem::SetPathCompression(const char* path, bool enable)
|
|
{
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
const DWORD attrs = GetFileAttributesW(wpath.c_str());
|
|
if (attrs == INVALID_FILE_ATTRIBUTES)
|
|
return false;
|
|
|
|
const bool isCompressed = (attrs & FILE_ATTRIBUTE_COMPRESSED) != 0;
|
|
if (enable == isCompressed)
|
|
{
|
|
// already compressed/not compressed
|
|
return true;
|
|
}
|
|
|
|
const bool isFile = !(attrs & FILE_ATTRIBUTE_DIRECTORY);
|
|
const DWORD flags = isFile ? FILE_ATTRIBUTE_NORMAL : (FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_DIRECTORY);
|
|
|
|
const HANDLE handle = CreateFileW(wpath.c_str(),
|
|
FILE_GENERIC_WRITE | FILE_GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_DELETE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
flags,
|
|
nullptr);
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
DWORD bytesReturned = 0;
|
|
DWORD compressMode = enable ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE;
|
|
|
|
bool result = DeviceIoControl(
|
|
handle, FSCTL_SET_COMPRESSION,
|
|
&compressMode, 2, nullptr, 0,
|
|
&bytesReturned, nullptr);
|
|
|
|
CloseHandle(handle);
|
|
return result;
|
|
}
|
|
|
|
bool FileSystem::CreateSymLink(const char* link, const char* target)
|
|
{
|
|
// convert to wide string
|
|
const std::wstring wlink = GetWin32Path(link);
|
|
if (wlink.empty())
|
|
return false;
|
|
|
|
const std::wstring wtarget = GetWin32Path(target);
|
|
if (wtarget.empty())
|
|
return false;
|
|
|
|
// check if it's a directory
|
|
DWORD flags = 0;
|
|
if (DirectoryExists(target))
|
|
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
|
|
|
|
// create the symbolic link
|
|
return CreateSymbolicLinkW(wlink.c_str(), wtarget.c_str(), flags) != 0;
|
|
}
|
|
|
|
bool FileSystem::IsSymbolicLink(const char* path)
|
|
{
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
return false;
|
|
|
|
// determine attributes for the path
|
|
const DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
|
|
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
|
|
return false;
|
|
|
|
return fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT;
|
|
}
|
|
|
|
bool FileSystem::DeleteSymbolicLink(const char* path, Error* error)
|
|
{
|
|
// convert to wide string
|
|
const std::wstring wpath = GetWin32Path(path);
|
|
if (wpath.empty())
|
|
{
|
|
Error::SetStringView(error, "Invalid path.");
|
|
return false;
|
|
}
|
|
|
|
// delete the symbolic link
|
|
if (DirectoryExists(path))
|
|
{
|
|
if (!RemoveDirectoryW(wpath.c_str()))
|
|
{
|
|
Error::SetWin32(error, "RemoveDirectoryW() failed: ", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!DeleteFileW(wpath.c_str()))
|
|
{
|
|
Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
|
|
// No 32-bit file offsets breaking stuff please.
|
|
static_assert(sizeof(off_t) == sizeof(s64));
|
|
|
|
static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern,
|
|
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited, ProgressCallback* cancel)
|
|
{
|
|
if (cancel && cancel->IsCancelled())
|
|
return 0;
|
|
|
|
std::string tempStr;
|
|
if (Path)
|
|
{
|
|
if (ParentPath)
|
|
tempStr = fmt::format("{}/{}/{}", OriginPath, ParentPath, Path);
|
|
else
|
|
tempStr = fmt::format("{}/{}", OriginPath, Path);
|
|
}
|
|
else
|
|
{
|
|
tempStr = fmt::format("{}", OriginPath);
|
|
}
|
|
|
|
DIR* pDir = opendir(tempStr.c_str());
|
|
if (!pDir)
|
|
return 0;
|
|
|
|
// small speed optimization for '*' case
|
|
bool hasWildCards = false;
|
|
bool wildCardMatchAll = false;
|
|
u32 nFiles = 0;
|
|
if (std::strpbrk(Pattern, "*?"))
|
|
{
|
|
hasWildCards = true;
|
|
wildCardMatchAll = (std::strcmp(Pattern, "*") == 0);
|
|
}
|
|
|
|
// iterate results
|
|
struct dirent* pDirEnt;
|
|
while ((pDirEnt = readdir(pDir)) != nullptr)
|
|
{
|
|
if (pDirEnt->d_name[0] == '.')
|
|
{
|
|
if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0'))
|
|
continue;
|
|
|
|
if (!(Flags & FILESYSTEM_FIND_HIDDEN_FILES))
|
|
continue;
|
|
}
|
|
|
|
std::string full_path;
|
|
if (ParentPath)
|
|
full_path = fmt::format("{}/{}/{}/{}", OriginPath, ParentPath, Path, pDirEnt->d_name);
|
|
else if (Path)
|
|
full_path = fmt::format("{}/{}/{}", OriginPath, Path, pDirEnt->d_name);
|
|
else
|
|
full_path = fmt::format("{}/{}", OriginPath, pDirEnt->d_name);
|
|
|
|
FILESYSTEM_FIND_DATA outData;
|
|
outData.Attributes = 0;
|
|
|
|
struct stat sDir;
|
|
if (stat(full_path.c_str(), &sDir) < 0)
|
|
continue;
|
|
|
|
if (S_ISDIR(sDir.st_mode))
|
|
{
|
|
if (Flags & FILESYSTEM_FIND_RECURSIVE)
|
|
{
|
|
// check that we're not following an infinite symbolic link loop
|
|
if (std::string real_recurse_dir = Path::RealPath(full_path);
|
|
real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
|
|
{
|
|
if (!real_recurse_dir.empty())
|
|
visited.push_back(std::move(real_recurse_dir));
|
|
|
|
// recurse into this directory
|
|
if (ParentPath)
|
|
{
|
|
const std::string recursive_dir = fmt::format("{}/{}", ParentPath, Path);
|
|
nFiles += RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
|
|
}
|
|
else
|
|
{
|
|
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(Flags & FILESYSTEM_FIND_FOLDERS))
|
|
continue;
|
|
|
|
outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
if (!(Flags & FILESYSTEM_FIND_FILES))
|
|
continue;
|
|
}
|
|
|
|
outData.Size = static_cast<u64>(sDir.st_size);
|
|
outData.CreationTime = sDir.st_ctime;
|
|
outData.ModificationTime = sDir.st_mtime;
|
|
|
|
// match the filename
|
|
if (hasWildCards)
|
|
{
|
|
if (!wildCardMatchAll && !StringUtil::WildcardMatch(pDirEnt->d_name, Pattern))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (std::strcmp(pDirEnt->d_name, Pattern) != 0)
|
|
continue;
|
|
}
|
|
|
|
// add file to list
|
|
if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS))
|
|
{
|
|
outData.FileName = std::move(full_path);
|
|
}
|
|
else
|
|
{
|
|
if (ParentPath)
|
|
outData.FileName = fmt::format("{}/{}/{}", ParentPath, Path, pDirEnt->d_name);
|
|
else if (Path)
|
|
outData.FileName = fmt::format("{}/{}", Path, pDirEnt->d_name);
|
|
else
|
|
outData.FileName = pDirEnt->d_name;
|
|
}
|
|
|
|
nFiles++;
|
|
pResults->push_back(std::move(outData));
|
|
}
|
|
|
|
closedir(pDir);
|
|
return nFiles;
|
|
}
|
|
|
|
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// clear result array
|
|
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
|
|
results->clear();
|
|
|
|
// add self if recursive, we don't want to visit it twice
|
|
std::vector<std::string> visited;
|
|
if (flags & FILESYSTEM_FIND_RECURSIVE)
|
|
{
|
|
std::string real_path = Path::RealPath(path);
|
|
if (!real_path.empty())
|
|
visited.push_back(std::move(real_path));
|
|
}
|
|
|
|
// enter the recursive function
|
|
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
|
|
return false;
|
|
|
|
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
|
|
{
|
|
std::sort(results->begin(), results->end(), [](const FILESYSTEM_FIND_DATA& lhs, const FILESYSTEM_FIND_DATA& rhs) {
|
|
// directories first
|
|
if ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) !=
|
|
(rhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
return ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0);
|
|
}
|
|
|
|
return (StringUtil::Strcasecmp(lhs.FileName.c_str(), rhs.FileName.c_str()) < 0);
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::StatFile(const char* path, struct stat* st)
|
|
{
|
|
return stat(path, st) == 0;
|
|
}
|
|
|
|
bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
|
|
{
|
|
const int fd = fileno(fp);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
return fstat(fd, st) == 0;
|
|
}
|
|
|
|
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// stat file
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) < 0)
|
|
return false;
|
|
|
|
// parse attributes
|
|
sd->CreationTime = sysStatData.st_ctime;
|
|
sd->ModificationTime = sysStatData.st_mtime;
|
|
sd->Attributes = 0;
|
|
if (S_ISDIR(sysStatData.st_mode))
|
|
sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
// parse size
|
|
if (S_ISREG(sysStatData.st_mode))
|
|
sd->Size = sysStatData.st_size;
|
|
else
|
|
sd->Size = 0;
|
|
|
|
// ok
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd)
|
|
{
|
|
const int fd = fileno(fp);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
// stat file
|
|
struct stat sysStatData;
|
|
if (fstat(fd, &sysStatData) < 0)
|
|
return false;
|
|
|
|
// parse attributes
|
|
sd->CreationTime = sysStatData.st_ctime;
|
|
sd->ModificationTime = sysStatData.st_mtime;
|
|
sd->Attributes = 0;
|
|
if (S_ISDIR(sysStatData.st_mode))
|
|
sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
// parse size
|
|
if (S_ISREG(sysStatData.st_mode))
|
|
sd->Size = sysStatData.st_size;
|
|
else
|
|
sd->Size = 0;
|
|
|
|
// ok
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::FileExists(const char* path)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// stat file
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) < 0)
|
|
return false;
|
|
|
|
if (S_ISDIR(sysStatData.st_mode))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DirectoryExists(const char* path)
|
|
{
|
|
// has a path
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
// stat file
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) < 0)
|
|
return false;
|
|
|
|
if (S_ISDIR(sysStatData.st_mode))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool FileSystem::DirectoryIsEmpty(const char* path)
|
|
{
|
|
DIR* pDir = opendir(path);
|
|
if (pDir == nullptr)
|
|
return true;
|
|
|
|
// iterate results
|
|
struct dirent* pDirEnt;
|
|
while ((pDirEnt = readdir(pDir)) != nullptr)
|
|
{
|
|
if (pDirEnt->d_name[0] == '.')
|
|
{
|
|
if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0'))
|
|
continue;
|
|
}
|
|
|
|
closedir(pDir);
|
|
return false;
|
|
}
|
|
|
|
closedir(pDir);
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::CreateDirectoryPath(const char* path, bool recursive, Error* error)
|
|
{
|
|
// has a path
|
|
const size_t pathLength = std::strlen(path);
|
|
if (pathLength == 0)
|
|
return false;
|
|
|
|
// try just flat-out, might work if there's no other segments that have to be made
|
|
if (mkdir(path, 0777) == 0)
|
|
return true;
|
|
|
|
// check error
|
|
int lastError = errno;
|
|
if (lastError == EEXIST)
|
|
{
|
|
// check the attributes
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) == 0 && S_ISDIR(sysStatData.st_mode))
|
|
return true;
|
|
}
|
|
|
|
if (!recursive)
|
|
{
|
|
Error::SetErrno(error, "mkdir() failed: ", lastError);
|
|
return false;
|
|
}
|
|
|
|
if (lastError == ENOENT)
|
|
{
|
|
// part of the path does not exist, so we'll create the parent folders, then
|
|
// the full path again.
|
|
std::string tempPath;
|
|
tempPath.reserve(pathLength);
|
|
|
|
// create directories along the path
|
|
for (size_t i = 0; i < pathLength; i++)
|
|
{
|
|
if (i > 0 && path[i] == '/')
|
|
{
|
|
if (mkdir(tempPath.c_str(), 0777) < 0)
|
|
{
|
|
lastError = errno;
|
|
if (lastError != EEXIST) // fine, continue to next path segment
|
|
{
|
|
Error::SetErrno(error, "mkdir() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
tempPath.push_back(path[i]);
|
|
}
|
|
|
|
// re-create the end if it's not a separator, check / as well because windows can interpret them
|
|
if (path[pathLength - 1] != '/')
|
|
{
|
|
if (mkdir(path, 0777) < 0)
|
|
{
|
|
lastError = errno;
|
|
if (lastError != EEXIST)
|
|
{
|
|
Error::SetErrno(error, "mkdir() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ok
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// unhandled error
|
|
Error::SetErrno(error, "mkdir() failed: ", lastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FileSystem::DeleteFilePath(const char* path, Error* error)
|
|
{
|
|
if (path[0] == '\0')
|
|
{
|
|
Error::SetStringView(error, "Path is empty.");
|
|
return false;
|
|
}
|
|
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode))
|
|
{
|
|
Error::SetStringView(error, "File does not exist.");
|
|
return false;
|
|
}
|
|
|
|
if (unlink(path) != 0)
|
|
{
|
|
Error::SetErrno(error, "unlink() failed: ", errno);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error)
|
|
{
|
|
if (old_path[0] == '\0' || new_path[0] == '\0')
|
|
{
|
|
Error::SetStringView(error, "Path is empty.");
|
|
return false;
|
|
}
|
|
|
|
if (rename(old_path, new_path) != 0)
|
|
{
|
|
const int err = errno;
|
|
Error::SetErrno(error, "rename() failed: ", err);
|
|
Console.Error("rename('%s', '%s') failed: %d", old_path, new_path, err);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DeleteDirectory(const char* path)
|
|
{
|
|
if (path[0] == '\0')
|
|
return false;
|
|
|
|
struct stat sysStatData;
|
|
if (stat(path, &sysStatData) != 0 || !S_ISDIR(sysStatData.st_mode))
|
|
return false;
|
|
|
|
return (rmdir(path) == 0);
|
|
}
|
|
|
|
std::string FileSystem::GetPackagePath()
|
|
{
|
|
// NOTE: The reason this function is separated from FileSystem::GetProgramPath() is because
|
|
// This path check breaks other usages of FileSystem::GetProgramPath for the AppImage.
|
|
// Notably the CI-generated AppImage fails to start because PCSX2 can't find its resources
|
|
// since it tries to look for them relative to the .AppImage file instead of relative to the actual executable.
|
|
|
|
// Check if we are running inside appimage. If so, return the path to the appimage instead.
|
|
if (const char* appimage_path = getenv("APPIMAGE"))
|
|
return std::string(appimage_path);
|
|
|
|
// Otherwise, find the executable file directly
|
|
return GetProgramPath();
|
|
}
|
|
|
|
std::string FileSystem::GetProgramPath()
|
|
{
|
|
#if defined(__linux__)
|
|
static const char* exeFileName = "/proc/self/exe";
|
|
|
|
int curSize = PATH_MAX;
|
|
char* buffer = static_cast<char*>(std::realloc(nullptr, curSize));
|
|
for (;;)
|
|
{
|
|
int len = readlink(exeFileName, buffer, curSize);
|
|
if (len < 0)
|
|
{
|
|
std::free(buffer);
|
|
return {};
|
|
}
|
|
else if (len < curSize)
|
|
{
|
|
buffer[len] = '\0';
|
|
std::string ret(buffer, len);
|
|
std::free(buffer);
|
|
return ret;
|
|
}
|
|
|
|
curSize *= 2;
|
|
buffer = static_cast<char*>(std::realloc(buffer, curSize));
|
|
}
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
int curSize = PATH_MAX;
|
|
char* buffer = static_cast<char*>(std::realloc(nullptr, curSize));
|
|
for (;;)
|
|
{
|
|
u32 nChars = curSize - 1;
|
|
int res = _NSGetExecutablePath(buffer, &nChars);
|
|
if (res == 0)
|
|
{
|
|
buffer[nChars] = 0;
|
|
|
|
char* resolvedBuffer = realpath(buffer, nullptr);
|
|
if (resolvedBuffer == nullptr)
|
|
{
|
|
std::free(buffer);
|
|
return {};
|
|
}
|
|
|
|
std::string ret(buffer);
|
|
std::free(buffer);
|
|
return ret;
|
|
}
|
|
|
|
curSize *= 2;
|
|
buffer = static_cast<char*>(std::realloc(buffer, curSize + 1));
|
|
}
|
|
|
|
#elif defined(__FreeBSD__)
|
|
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
|
char buffer[PATH_MAX];
|
|
size_t cb = sizeof(buffer) - 1;
|
|
int res = sysctl(mib, std::size(mib), buffer, &cb, nullptr, 0);
|
|
if (res != 0)
|
|
return {};
|
|
|
|
buffer[cb] = '\0';
|
|
return buffer;
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
std::string FileSystem::GetWorkingDirectory()
|
|
{
|
|
std::string buffer;
|
|
buffer.resize(PATH_MAX);
|
|
while (!getcwd(buffer.data(), buffer.size()))
|
|
{
|
|
if (errno != ERANGE)
|
|
return {};
|
|
|
|
buffer.resize(buffer.size() * 2);
|
|
}
|
|
|
|
buffer.resize(std::strlen(buffer.c_str())); // Remove excess nulls
|
|
return buffer;
|
|
}
|
|
|
|
bool FileSystem::SetWorkingDirectory(const char* path)
|
|
{
|
|
return (chdir(path) == 0);
|
|
}
|
|
|
|
bool FileSystem::SetPathCompression(const char* path, bool enable)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FileSystem::CreateSymLink(const char* link, const char* target)
|
|
{
|
|
return symlink(target, link) == 0;
|
|
}
|
|
|
|
bool FileSystem::IsSymbolicLink(const char* path)
|
|
{
|
|
struct stat sysStatData;
|
|
if (lstat(path, &sysStatData) < 0)
|
|
return false;
|
|
|
|
return S_ISLNK(sysStatData.st_mode);
|
|
}
|
|
|
|
bool FileSystem::DeleteSymbolicLink(const char* path, Error* error)
|
|
{
|
|
if (unlink(path) != 0)
|
|
{
|
|
Error::SetErrno(error, "unlink() failed: ", errno);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FileSystem::POSIXLock::POSIXLock(int fd)
|
|
{
|
|
if (lockf(fd, F_LOCK, 0) == 0)
|
|
{
|
|
m_fd = fd;
|
|
}
|
|
else
|
|
{
|
|
Console.Error("lockf() failed: %d", errno);
|
|
m_fd = -1;
|
|
}
|
|
}
|
|
|
|
FileSystem::POSIXLock::POSIXLock(std::FILE* fp)
|
|
{
|
|
m_fd = fileno(fp);
|
|
if (m_fd >= 0)
|
|
{
|
|
if (lockf(m_fd, F_LOCK, 0) != 0)
|
|
{
|
|
Console.Error("lockf() failed: %d", errno);
|
|
m_fd = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
FileSystem::POSIXLock::~POSIXLock()
|
|
{
|
|
if (m_fd >= 0)
|
|
lockf(m_fd, F_ULOCK, m_fd);
|
|
}
|
|
|
|
#endif
|