mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-12-01 02:37:27 +01:00
1774 lines
80 KiB
C++
1774 lines
80 KiB
C++
/*
|
|
* Copyright (c) Atmosphère-NX
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <stratosphere.hpp>
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
#include <stratosphere/windows.hpp>
|
|
#include <winerror.h>
|
|
#include <winioctl.h>
|
|
#elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS)
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
#if defined(ATMOSPHERE_OS_LINUX)
|
|
#include <sys/syscall.h>
|
|
#elif defined(ATMOSPHERE_OS_MACOS)
|
|
extern "C" ssize_t __getdirentries64(int fd, char *buffer, size_t buffer_size, uintptr_t *basep);
|
|
#endif
|
|
|
|
#if !defined(ATMOSPHERE_OS_HORIZON)
|
|
namespace ams::fssystem {
|
|
|
|
namespace {
|
|
|
|
constexpr s64 NanoSecondsPerWindowsTick = 100;
|
|
constexpr s64 WindowsTicksPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds() / TimeSpan::FromNanoSeconds(NanoSecondsPerWindowsTick).GetNanoSeconds();
|
|
constexpr s64 OffsetToConvertToPosixTime = 11644473600;
|
|
|
|
[[maybe_unused]] constexpr ALWAYS_INLINE s64 ConvertWindowsTimeToPosixTime(s64 windows_ticks) {
|
|
return (windows_ticks / WindowsTicksPerSecond) - OffsetToConvertToPosixTime;
|
|
}
|
|
|
|
[[maybe_unused]] constexpr ALWAYS_INLINE s64 ConvertPosixTimeToWindowsTime(s64 posix_sec, s64 posix_ns = 0) {
|
|
return ((posix_sec + OffsetToConvertToPosixTime) * WindowsTicksPerSecond) + util::DivideUp<s64>(posix_ns, NanoSecondsPerWindowsTick);
|
|
}
|
|
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
constexpr int MaxFilePathLength = MAX_PATH - 1;
|
|
constexpr int MaxDirectoryPathLength = MaxFilePathLength - (8 + 1 + 3);
|
|
static_assert(MaxFilePathLength == 259);
|
|
static_assert(MaxDirectoryPathLength == 247);
|
|
|
|
bool AreLongPathsEnabledImpl() {
|
|
/* Get handle to ntdll. */
|
|
const HMODULE module = ::GetModuleHandleW(L"ntdll");
|
|
if (module == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
/* Get function pointer to long paths enabled. */
|
|
const auto enabled_funcptr = ::GetProcAddress(module, "RtlAreLongPathsEnabled");
|
|
if (enabled_funcptr == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
/* Get whether long paths are enabled. */
|
|
using FunctionType = BOOLEAN (NTAPI *)();
|
|
return reinterpret_cast<FunctionType>(reinterpret_cast<uintptr_t>(enabled_funcptr))();
|
|
}
|
|
|
|
Result ConvertLastErrorToResult() {
|
|
switch (::GetLastError()) {
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_NO_MORE_FILES:
|
|
case ERROR_BAD_NETPATH:
|
|
case ERROR_BAD_NET_NAME:
|
|
case ERROR_DIRECTORY:
|
|
case ERROR_BAD_DEVICE:
|
|
case ERROR_CONNECTION_UNAVAIL:
|
|
case ERROR_NO_NET_OR_BAD_PATH:
|
|
case ERROR_NOT_CONNECTED:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
case ERROR_ACCESS_DENIED:
|
|
case ERROR_SHARING_VIOLATION:
|
|
R_THROW(fs::ResultTargetLocked());
|
|
case ERROR_HANDLE_EOF:
|
|
R_THROW(fs::ResultOutOfRange());
|
|
case ERROR_FILE_EXISTS:
|
|
case ERROR_ALREADY_EXISTS:
|
|
R_THROW(fs::ResultPathAlreadyExists());
|
|
case ERROR_DISK_FULL:
|
|
case ERROR_SPACES_NOT_ENOUGH_DRIVES:
|
|
R_THROW(fs::ResultNotEnoughFreeSpace());
|
|
case ERROR_DIR_NOT_EMPTY:
|
|
R_THROW(fs::ResultDirectoryNotEmpty());
|
|
case ERROR_BAD_PATHNAME:
|
|
R_THROW(fs::ResultInvalidPathFormat());
|
|
case ERROR_FILENAME_EXCED_RANGE:
|
|
R_THROW(fs::ResultTooLongPath());
|
|
default:
|
|
//printf("Returning ConvertLastErrorToResult() -> ResultUnexpectedInLocalFileSystemE, last_error=0x%08x\n", static_cast<u32>(::GetLastError()));
|
|
R_THROW(fs::ResultUnexpectedInLocalFileSystemE());
|
|
}
|
|
}
|
|
|
|
Result WaitDeletionCompletion(const wchar_t *native_path) {
|
|
/* Wait for the path to be deleted. */
|
|
constexpr int MaxTryCount = 25;
|
|
for (int i = 0; i < MaxTryCount; ++i) {
|
|
/* Get the file attributes. */
|
|
const auto attr = ::GetFileAttributesW(native_path);
|
|
|
|
/* If they're not invalid, we're done. */
|
|
R_SUCCEED_IF(attr != INVALID_FILE_ATTRIBUTES);
|
|
|
|
/* Get last error. */
|
|
const auto err = ::GetLastError();
|
|
|
|
/* If error was file not found, the delete is complete. */
|
|
R_SUCCEED_IF(err == ERROR_FILE_NOT_FOUND);
|
|
|
|
/* If the error was access denied, we want to try again. */
|
|
R_UNLESS(err == ERROR_ACCESS_DENIED, ConvertLastErrorToResult());
|
|
|
|
/* Sleep before checking again. */
|
|
::Sleep(2);
|
|
}
|
|
|
|
/* We received access denied 25 times in a row. */
|
|
R_THROW(fs::ResultTargetLocked());
|
|
}
|
|
|
|
Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const wchar_t *native_path) {
|
|
const auto res = ::GetFileAttributesW(native_path);
|
|
if (res == INVALID_FILE_ATTRIBUTES) {
|
|
switch (::GetLastError()) {
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_ACCESS_DENIED:
|
|
case ERROR_BAD_NETPATH:
|
|
case ERROR_BAD_NET_NAME:
|
|
case ERROR_BAD_DEVICE:
|
|
case ERROR_CONNECTION_UNAVAIL:
|
|
case ERROR_NO_NET_OR_BAD_PATH:
|
|
case ERROR_NOT_CONNECTED:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
default:
|
|
//printf("Returning GetEntryTypeImpl() -> ResultUnexpectedInLocalFileSystemF, last_error=0x%08x\n", static_cast<u32>(::GetLastError()));
|
|
R_THROW(fs::ResultUnexpectedInLocalFileSystemF());
|
|
}
|
|
}
|
|
|
|
*out = (res & FILE_ATTRIBUTE_DIRECTORY) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result SetFileSizeImpl(HANDLE handle, s64 size) {
|
|
/* Seek to the desired size. */
|
|
LARGE_INTEGER seek;
|
|
seek.QuadPart = size;
|
|
R_UNLESS(::SetFilePointerEx(handle, seek, nullptr, FILE_BEGIN) != 0, ConvertLastErrorToResult());
|
|
|
|
/* Try to set the file size. */
|
|
if (::SetEndOfFile(handle) == 0) {
|
|
/* Check if the error resulted from too large size. */
|
|
R_UNLESS(::GetLastError() == ERROR_INVALID_PARAMETER, ConvertLastErrorToResult());
|
|
R_UNLESS(size <= INT64_C(0x00000FFFFFFF0000), ConvertLastErrorToResult());
|
|
|
|
/* The file size is too large. */
|
|
R_THROW(fs::ResultTooLargeSize());
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
class LocalFile : public ::ams::fs::fsa::IFile, public ::ams::fs::impl::Newable {
|
|
private:
|
|
const HANDLE m_handle;
|
|
const fs::OpenMode m_open_mode;
|
|
public:
|
|
LocalFile(HANDLE h, fs::OpenMode m) : m_handle(h), m_open_mode(m) { /* ... */ }
|
|
|
|
virtual ~LocalFile() {
|
|
::CloseHandle(m_handle);
|
|
}
|
|
public:
|
|
virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
|
|
/* Check that read is possible. */
|
|
size_t dry_read_size;
|
|
R_TRY(this->DryRead(std::addressof(dry_read_size), offset, size, option, m_open_mode));
|
|
|
|
/* If we have nothing to read, we don't need to do anything. */
|
|
if (dry_read_size == 0) {
|
|
*out = 0;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* Prepare to do asynchronous IO. */
|
|
OVERLAPPED overlapped = {};
|
|
overlapped.Offset = static_cast<DWORD>(offset);
|
|
overlapped.OffsetHigh = static_cast<DWORD>(offset >> BITSIZEOF(DWORD));
|
|
overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr);
|
|
R_UNLESS(overlapped.hEvent != nullptr, fs::ResultUnexpectedInLocalFileSystemA());
|
|
ON_SCOPE_EXIT { ::CloseHandle(overlapped.hEvent); };
|
|
|
|
/* Read from the file. */
|
|
DWORD size_read;
|
|
if (!::ReadFile(m_handle, buffer, static_cast<DWORD>(size), std::addressof(size_read), std::addressof(overlapped))) {
|
|
/* If we fail for reason other than io pending, return the error result. */
|
|
const auto err = ::GetLastError();
|
|
R_UNLESS(err == ERROR_IO_PENDING, ConvertLastErrorToResult());
|
|
|
|
/* Get the wait result. */
|
|
if (!::GetOverlappedResult(m_handle, std::addressof(overlapped), std::addressof(size_read), true)) {
|
|
/* We failed...check if it's because we're at the end of the file. */
|
|
R_UNLESS(::GetLastError() == ERROR_HANDLE_EOF, ConvertLastErrorToResult());
|
|
|
|
/* Get the file size. */
|
|
LARGE_INTEGER file_size;
|
|
R_UNLESS(::GetFileSizeEx(m_handle, std::addressof(file_size)), ConvertLastErrorToResult());
|
|
|
|
/* Check the filesize matches offset. */
|
|
R_UNLESS(file_size.QuadPart == offset, ConvertLastErrorToResult());
|
|
}
|
|
}
|
|
|
|
/* Set the output read size. */
|
|
*out = size_read;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoGetSize(s64 *out) override {
|
|
/* Get the file size. */
|
|
LARGE_INTEGER size;
|
|
R_UNLESS(::GetFileSizeEx(m_handle, std::addressof(size)), fs::ResultUnexpectedInLocalFileSystemD());
|
|
|
|
/* Set the output. */
|
|
*out = size.QuadPart;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoFlush() override {
|
|
/* If we're not writable, we have nothing to flush. */
|
|
R_SUCCEED_IF((m_open_mode & fs::OpenMode_Write) == 0);
|
|
|
|
/* Flush our buffer. */
|
|
R_UNLESS(::FlushFileBuffers(m_handle), fs::ResultUnexpectedInLocalFileSystemC());
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
|
|
/* Verify that we can write. */
|
|
bool needs_append;
|
|
R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_open_mode));
|
|
|
|
/* If we need to, perform the write. */
|
|
if (size != 0) {
|
|
/* Prepare to do asynchronous IO. */
|
|
OVERLAPPED overlapped = {};
|
|
overlapped.Offset = static_cast<DWORD>(offset);
|
|
overlapped.OffsetHigh = static_cast<DWORD>(offset >> BITSIZEOF(DWORD));
|
|
overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr);
|
|
R_UNLESS(overlapped.hEvent != nullptr, fs::ResultUnexpectedInLocalFileSystemA());
|
|
ON_SCOPE_EXIT { ::CloseHandle(overlapped.hEvent); };
|
|
|
|
/* Write to the file. */
|
|
DWORD size_written;
|
|
if (!::WriteFile(m_handle, buffer, static_cast<DWORD>(size), std::addressof(size_written), std::addressof(overlapped))) {
|
|
/* If we fail for reason other than io pending, return the error result. */
|
|
const auto err = ::GetLastError();
|
|
R_UNLESS(err == ERROR_IO_PENDING, ConvertLastErrorToResult());
|
|
|
|
/* Get the wait result. */
|
|
R_UNLESS(::GetOverlappedResult(m_handle, std::addressof(overlapped), std::addressof(size_written), true), ConvertLastErrorToResult());
|
|
}
|
|
|
|
/* Check that a correct amount of data was written. */
|
|
R_UNLESS(size_written >= size, fs::ResultNotEnoughFreeSpace());
|
|
|
|
/* Sanity check that we wrote the right amount. */
|
|
AMS_ASSERT(size_written == size);
|
|
}
|
|
|
|
/* If we need to, flush. */
|
|
if (option.HasFlushFlag()) {
|
|
R_TRY(this->Flush());
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoSetSize(s64 size) override {
|
|
/* Verify we can set the size. */
|
|
R_TRY(this->DrySetSize(size, m_open_mode));
|
|
|
|
/* Try to set the file size. */
|
|
R_RETURN(SetFileSizeImpl(m_handle, size));
|
|
}
|
|
|
|
virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
|
|
AMS_UNUSED(offset, size, src, src_size);
|
|
switch (op_id) {
|
|
case fs::OperationId::Invalidate:
|
|
R_SUCCEED();
|
|
case fs::OperationId::QueryRange:
|
|
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
|
|
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
|
|
static_cast<fs::QueryRangeInfo *>(dst)->Clear();
|
|
R_SUCCEED();
|
|
default:
|
|
R_THROW(fs::ResultUnsupportedOperateRangeForTmFileSystemFile());
|
|
}
|
|
}
|
|
public:
|
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
|
AMS_ABORT("GetDomainObjectId() should never be called on a LocalFile");
|
|
}
|
|
};
|
|
|
|
bool IsDirectory(const WIN32_FIND_DATAW &fd) {
|
|
return fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
|
|
class LocalDirectory : public ::ams::fs::fsa::IDirectory, public ::ams::fs::impl::Newable {
|
|
private:
|
|
std::unique_ptr<wchar_t[], ::ams::fs::impl::Deleter> m_path;
|
|
HANDLE m_dir_handle;
|
|
HANDLE m_search_handle;
|
|
fs::OpenDirectoryMode m_open_mode;
|
|
public:
|
|
LocalDirectory(HANDLE d, fs::OpenDirectoryMode m, std::unique_ptr<wchar_t[], ::ams::fs::impl::Deleter> &&p) : m_path(std::move(p)), m_dir_handle(d), m_search_handle(INVALID_HANDLE_VALUE) {
|
|
m_open_mode = static_cast<fs::OpenDirectoryMode>(util::ToUnderlying(m) & ~util::ToUnderlying(fs::OpenDirectoryMode_NotRequireFileSize));
|
|
}
|
|
|
|
virtual ~LocalDirectory() {
|
|
if (m_search_handle != INVALID_HANDLE_VALUE) {
|
|
::FindClose(m_search_handle);
|
|
}
|
|
::CloseHandle(m_dir_handle);
|
|
}
|
|
public:
|
|
virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override {
|
|
auto read_count = 0;
|
|
while (read_count < max_entries) {
|
|
/* Read the next file. */
|
|
WIN32_FIND_DATAW fd;
|
|
std::memset(fd.cFileName, 0, sizeof(fd.cFileName));
|
|
if (m_search_handle == INVALID_HANDLE_VALUE) {
|
|
/* Create our search handle. */
|
|
if (m_search_handle = ::FindFirstFileW(m_path.get(), std::addressof(fd)); m_search_handle == INVALID_HANDLE_VALUE) {
|
|
/* Check that we failed because there are no files. */
|
|
R_UNLESS(::GetLastError() == ERROR_FILE_NOT_FOUND, ConvertLastErrorToResult());
|
|
break;
|
|
}
|
|
} else if (!::FindNextFileW(m_search_handle, std::addressof(fd))) {
|
|
/* Check that we failed because we ran out of files. */
|
|
R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult());
|
|
break;
|
|
}
|
|
|
|
/* If we shouldn't create an entry, continue. */
|
|
if (!this->IsReadTarget(fd)) {
|
|
continue;
|
|
}
|
|
|
|
/* Create the entry. */
|
|
auto &entry = out_entries[read_count++];
|
|
|
|
std::memset(entry.name, 0, sizeof(entry.name));
|
|
const auto wide_res = ::WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, entry.name, sizeof(entry.name), nullptr, nullptr);
|
|
R_UNLESS(wide_res != 0, fs::ResultInvalidPath());
|
|
|
|
entry.type = IsDirectory(fd) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File;
|
|
entry.file_size = static_cast<s64>(fd.nFileSizeLow) | static_cast<s64>(static_cast<u64>(fd.nFileSizeHigh) << BITSIZEOF(fd.nFileSizeLow));
|
|
}
|
|
|
|
/* Set the output read count. */
|
|
*out_count = read_count;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoGetEntryCount(s64 *out) override {
|
|
/* Open a new search handle. */
|
|
WIN32_FIND_DATAW fd;
|
|
auto handle = ::FindFirstFileW(m_path.get(), std::addressof(fd));
|
|
R_UNLESS(handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult());
|
|
ON_SCOPE_EXIT { ::FindClose(handle); };
|
|
|
|
/* Iterate to get the total entry count. */
|
|
auto entry_count = 0;
|
|
while (::FindNextFileW(handle, std::addressof(fd))) {
|
|
if (this->IsReadTarget(fd)) {
|
|
++entry_count;
|
|
}
|
|
}
|
|
|
|
/* Check that we stopped iterating because we ran out of files. */
|
|
R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult());
|
|
|
|
/* Set the output. */
|
|
*out = entry_count;
|
|
R_SUCCEED();
|
|
}
|
|
private:
|
|
bool IsReadTarget(const WIN32_FIND_DATAW &fd) const {
|
|
/* If the file is "..", don't return it. */
|
|
if (::wcsncmp(fd.cFileName, L"..", 3) == 0 || ::wcsncmp(fd.cFileName, L".", 2) == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Return whether our open mode supports the target. */
|
|
if (IsDirectory(fd)) {
|
|
return m_open_mode != fs::OpenDirectoryMode_File;
|
|
} else {
|
|
return m_open_mode != fs::OpenDirectoryMode_Directory;
|
|
}
|
|
}
|
|
public:
|
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
|
AMS_ABORT("GetDomainObjectId() should never be called on a LocalDirectory");
|
|
}
|
|
};
|
|
|
|
#else
|
|
constexpr int MaxFilePathLength = PATH_MAX - 1;
|
|
constexpr int MaxDirectoryPathLength = PATH_MAX - 1;
|
|
|
|
#if defined (ATMOSPHERE_OS_LINUX)
|
|
struct linux_dirent64 {
|
|
ino64_t d_ino;
|
|
off64_t d_off;
|
|
unsigned short d_reclen;
|
|
unsigned char d_type;
|
|
char d_name[];
|
|
};
|
|
|
|
using NativeDirectoryEntryType = struct linux_dirent64;
|
|
#else
|
|
using NativeDirectoryEntryType = struct dirent;
|
|
#endif
|
|
|
|
bool AreLongPathsEnabledImpl() {
|
|
/* TODO: How does this work on linux/macos? */
|
|
return true;
|
|
}
|
|
|
|
enum ErrnoSource {
|
|
ErrnoSource_OpenFile, // 0
|
|
ErrnoSource_CreateFile, // 1
|
|
ErrnoSource_Unlink, // 2
|
|
ErrnoSource_Pread, // 3
|
|
ErrnoSource_Pwrite, // 4
|
|
ErrnoSource_Ftruncate, // 5
|
|
//
|
|
ErrnoSource_OpenDirectory, // 6
|
|
ErrnoSource_Mkdir, // 7
|
|
ErrnoSource_Rmdir, // 8
|
|
ErrnoSource_GetDents, // 9
|
|
//
|
|
ErrnoSource_RenameDirectory, // 10
|
|
ErrnoSource_RenameFile, // 11
|
|
//
|
|
ErrnoSource_Stat, // 12
|
|
ErrnoSource_Statvfs, // 13
|
|
};
|
|
|
|
Result ConvertErrnoToResult(ErrnoSource source) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
case EEXIST:
|
|
switch (source) {
|
|
case ErrnoSource_Rmdir:
|
|
R_THROW(fs::ResultDirectoryNotEmpty());
|
|
default:
|
|
R_THROW(fs::ResultPathAlreadyExists());
|
|
}
|
|
case ENOTDIR:
|
|
switch (source) {
|
|
case ErrnoSource_Rmdir:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
default:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
}
|
|
case EISDIR:
|
|
switch (source) {
|
|
case ErrnoSource_CreateFile:
|
|
R_THROW(fs::ResultPathAlreadyExists());
|
|
case ErrnoSource_OpenFile:
|
|
case ErrnoSource_Unlink:
|
|
R_THROW(fs::ResultPathNotFound());
|
|
default:
|
|
R_THROW(fs::ResultUnexpectedInLocalFileSystemE());
|
|
}
|
|
case ENOTEMPTY:
|
|
R_THROW(fs::ResultDirectoryNotEmpty());
|
|
case EACCES:
|
|
case EINTR:
|
|
R_THROW(fs::ResultTargetLocked());
|
|
default:
|
|
//printf("Returning default errno -> result, errno=%d, source=%d\n", errno, static_cast<int>(source));
|
|
R_THROW(fs::ResultUnexpectedInLocalFileSystemE());
|
|
}
|
|
}
|
|
|
|
Result WaitDeletionCompletion(const char *native_path) {
|
|
/* TODO: Does linux need to wait for delete to complete? */
|
|
AMS_UNUSED(native_path);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
|
|
Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *native_path) {
|
|
struct stat st;
|
|
R_UNLESS(::stat(native_path, std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat));
|
|
|
|
*out = (S_ISDIR(st.st_mode)) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
auto RetryForEIntr(auto f) {
|
|
decltype(f()) res;
|
|
do {
|
|
res = f();
|
|
} while (res < 0 && errno == EINTR);
|
|
return res;
|
|
};
|
|
|
|
void CloseFileDescriptor(int handle) {
|
|
const int res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA {
|
|
return ::close(handle);
|
|
});
|
|
AMS_ASSERT(res == 0);
|
|
AMS_UNUSED(res);
|
|
}
|
|
|
|
Result SetFileSizeImpl(int handle, s64 size) {
|
|
const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::ftruncate(handle, size); });
|
|
R_UNLESS(res == 0, ConvertErrnoToResult(ErrnoSource_Ftruncate));
|
|
R_SUCCEED();
|
|
}
|
|
|
|
class LocalFile : public ::ams::fs::fsa::IFile, public ::ams::fs::impl::Newable {
|
|
private:
|
|
const int m_handle;
|
|
const fs::OpenMode m_open_mode;
|
|
public:
|
|
LocalFile(int h, fs::OpenMode m) : m_handle(h), m_open_mode(m) { /* ... */ }
|
|
|
|
virtual ~LocalFile() {
|
|
CloseFileDescriptor(m_handle);
|
|
}
|
|
public:
|
|
virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
|
|
/* Check that read is possible. */
|
|
size_t dry_read_size;
|
|
R_TRY(this->DryRead(std::addressof(dry_read_size), offset, size, option, m_open_mode));
|
|
|
|
/* If we have nothing to read, we don't need to do anything. */
|
|
if (dry_read_size == 0) {
|
|
*out = 0;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* Read. */
|
|
const auto read_size = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> ssize_t { return ::pread(m_handle, buffer, size, offset); });
|
|
R_UNLESS(read_size >= 0, ConvertErrnoToResult(ErrnoSource_Pread));
|
|
|
|
/* Set output. */
|
|
*out = static_cast<size_t>(read_size);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoGetSize(s64 *out) override {
|
|
/* Get the file size. */
|
|
const auto size = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> s64 { return ::lseek(m_handle, 0, SEEK_END); });
|
|
R_UNLESS(size >= 0, fs::ResultUnexpectedInLocalFileSystemD());
|
|
|
|
/* Set the output. */
|
|
*out = size;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoFlush() override {
|
|
/* If we're not writable, we have nothing to flush. */
|
|
R_SUCCEED_IF((m_open_mode & fs::OpenMode_Write) == 0);
|
|
|
|
/* Flush our buffer. */
|
|
const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::fsync(m_handle); });
|
|
R_UNLESS(res == 0, fs::ResultUnexpectedInLocalFileSystemC());
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
|
|
/* Verify that we can write. */
|
|
bool needs_append;
|
|
R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_open_mode));
|
|
|
|
/* If we need to, perform the write. */
|
|
if (size != 0) {
|
|
/* Read. */
|
|
const auto size_written = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> ssize_t { return ::pwrite(m_handle, buffer, size, offset); });
|
|
R_UNLESS(size_written >= 0, ConvertErrnoToResult(ErrnoSource_Pwrite));
|
|
|
|
/* Check that a correct amount of data was written. */
|
|
R_UNLESS(static_cast<size_t>(size_written) >= size, fs::ResultNotEnoughFreeSpace());
|
|
|
|
/* Sanity check that we wrote the right amount. */
|
|
AMS_ASSERT(static_cast<size_t>(size_written) == size);
|
|
}
|
|
|
|
/* If we need to, flush. */
|
|
if (option.HasFlushFlag()) {
|
|
R_TRY(this->Flush());
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoSetSize(s64 size) override {
|
|
/* Verify we can set the size. */
|
|
R_TRY(this->DrySetSize(size, m_open_mode));
|
|
|
|
/* Try to set the file size. */
|
|
R_RETURN(SetFileSizeImpl(m_handle, size));
|
|
}
|
|
|
|
virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
|
|
AMS_UNUSED(offset, size, src, src_size);
|
|
switch (op_id) {
|
|
case fs::OperationId::Invalidate:
|
|
R_SUCCEED();
|
|
case fs::OperationId::QueryRange:
|
|
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
|
|
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
|
|
static_cast<fs::QueryRangeInfo *>(dst)->Clear();
|
|
R_SUCCEED();
|
|
default:
|
|
R_THROW(fs::ResultUnsupportedOperateRangeForTmFileSystemFile());
|
|
}
|
|
}
|
|
public:
|
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
|
AMS_ABORT("GetDomainObjectId() should never be called on a LocalFile");
|
|
}
|
|
};
|
|
|
|
class LocalDirectory : public ::ams::fs::fsa::IDirectory, public ::ams::fs::impl::Newable {
|
|
private:
|
|
std::unique_ptr<char[], ::ams::fs::impl::Deleter> m_path;
|
|
int m_dir_handle;
|
|
fs::OpenDirectoryMode m_open_mode;
|
|
bool m_not_require_file_size;
|
|
|
|
std::unique_ptr<fs::DirectoryEntry[], ::ams::fs::impl::Deleter> m_temp_entries;
|
|
int m_temp_entries_count;
|
|
int m_temp_entries_ofs;
|
|
|
|
#if defined(ATMOSPHERE_OS_MACOS)
|
|
uintptr_t m_basep = 0;
|
|
#endif
|
|
public:
|
|
LocalDirectory(int d, fs::OpenDirectoryMode m, std::unique_ptr<char[], ::ams::fs::impl::Deleter> &&p) : m_path(std::move(p)), m_dir_handle(d), m_temp_entries(nullptr), m_temp_entries_count(0), m_temp_entries_ofs(0) {
|
|
m_open_mode = static_cast<fs::OpenDirectoryMode>(util::ToUnderlying(m) & ~util::ToUnderlying(fs::OpenDirectoryMode_NotRequireFileSize));
|
|
m_not_require_file_size = m & fs::OpenDirectoryMode_NotRequireFileSize;
|
|
}
|
|
|
|
virtual ~LocalDirectory() {
|
|
CloseFileDescriptor(m_dir_handle);
|
|
}
|
|
public:
|
|
virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override {
|
|
auto read_count = 0;
|
|
|
|
/* Copy out any pending entries from a previous call. */
|
|
while (m_temp_entries_ofs < m_temp_entries_count && read_count < max_entries) {
|
|
out_entries[read_count++] = m_temp_entries[m_temp_entries_ofs++];
|
|
}
|
|
|
|
/* If we're done with our temporary entries, release them. */
|
|
if (m_temp_entries_ofs == m_temp_entries_count) {
|
|
m_temp_entries.reset();
|
|
m_temp_entries_ofs = 0;
|
|
m_temp_entries_count = 0;
|
|
}
|
|
|
|
if (read_count < max_entries) {
|
|
/* Declare buffer to hold temporary path. */
|
|
char path_buf[PATH_MAX];
|
|
auto base_path_len = std::strlen(m_path.get());
|
|
std::memcpy(path_buf, m_path.get(), base_path_len);
|
|
if (path_buf[base_path_len - 1] != '/') {
|
|
path_buf[base_path_len++] = '/';
|
|
}
|
|
|
|
#if defined(ATMOSPHERE_OS_LINUX)
|
|
char buf[1_KB];
|
|
#else
|
|
char buf[2_KB];
|
|
#endif
|
|
NativeDirectoryEntryType *ent = nullptr;
|
|
while (read_count < max_entries) {
|
|
/* Read next entries. */
|
|
#if defined (ATMOSPHERE_OS_LINUX)
|
|
const auto nread = ::syscall(SYS_getdents64, m_dir_handle, buf, sizeof(buf));
|
|
#elif defined(ATMOSPHERE_OS_MACOS)
|
|
const auto nread = ::__getdirentries64(m_dir_handle, buf, sizeof(buf), std::addressof(m_basep));
|
|
#else
|
|
#error "Unknown OS to read from directory FD"
|
|
#endif
|
|
R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents));
|
|
|
|
/* If we read nothing, we've hit the end of the directory. */
|
|
if (nread == 0) {
|
|
break;
|
|
}
|
|
|
|
/* Determine the number of entries we read. */
|
|
auto cur_read_entries = 0;
|
|
for (auto pos = 0; pos < nread; pos += ent->d_reclen) {
|
|
/* Get the native entry. */
|
|
ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos);
|
|
|
|
/* If the entry isn't a read target, ignore it. */
|
|
if (IsReadTarget(ent)) {
|
|
++cur_read_entries;
|
|
}
|
|
}
|
|
|
|
/* If we'll end up reading more than we can fit, allocate a temporary buffer. */
|
|
if (read_count + cur_read_entries > max_entries) {
|
|
/* Allocate temporary entries. */
|
|
m_temp_entries_count = (read_count + cur_read_entries) - max_entries;
|
|
m_temp_entries_ofs = 0;
|
|
|
|
/* TODO: Non-fatal? */
|
|
m_temp_entries = fs::impl::MakeUnique<fs::DirectoryEntry[]>(m_temp_entries_count);
|
|
AMS_ABORT_UNLESS(m_temp_entries != nullptr);
|
|
}
|
|
|
|
/* Iterate received entries. */
|
|
for (auto pos = 0; pos < nread; pos += ent->d_reclen) {
|
|
/* Get the native entry. */
|
|
ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos);
|
|
|
|
/* If the entry isn't a read target, ignore it. */
|
|
if (!IsReadTarget(ent)) {
|
|
continue;
|
|
}
|
|
|
|
/* Decide on the output entry. */
|
|
fs::DirectoryEntry *out_entry;
|
|
if (read_count < max_entries) {
|
|
out_entry = std::addressof(out_entries[read_count++]);
|
|
} else {
|
|
out_entry = std::addressof(m_temp_entries[m_temp_entries_ofs++]);
|
|
}
|
|
|
|
/* Setup the output entry. */
|
|
{
|
|
std::memset(out_entry->name, 0, sizeof(out_entry->name));
|
|
|
|
const auto name_len = std::strlen(ent->d_name);
|
|
AMS_ABORT_UNLESS(name_len <= fs::EntryNameLengthMax);
|
|
|
|
std::memcpy(out_entry->name, ent->d_name, name_len + 1);
|
|
|
|
out_entry->type = (ent->d_type == DT_DIR) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File;
|
|
|
|
/* If we have to, get the filesize. This is (unfortunately) expensive on linux. */
|
|
if (out_entry->type == fs::DirectoryEntryType_File && !m_not_require_file_size) {
|
|
/* Set up the temporary file path. */
|
|
AMS_ABORT_UNLESS(base_path_len + name_len + 1 <= PATH_MAX);
|
|
std::memcpy(path_buf + base_path_len, ent->d_name, name_len + 1);
|
|
|
|
/* Get the file stats. */
|
|
struct stat st;
|
|
R_UNLESS(::stat(path_buf, std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat));
|
|
|
|
out_entry->file_size = static_cast<s64>(st.st_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ensure our temporary entries are correct. */
|
|
if (m_temp_entries != nullptr) {
|
|
AMS_ASSERT(read_count == max_entries);
|
|
AMS_ASSERT(m_temp_entries_ofs == m_temp_entries_count);
|
|
m_temp_entries_ofs = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the output read count. */
|
|
*out_count = read_count;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
virtual Result DoGetEntryCount(s64 *out) override {
|
|
/* Open the directory anew. */
|
|
const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(m_path.get(), O_RDONLY | O_DIRECTORY); });
|
|
R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory));
|
|
ON_SCOPE_EXIT { CloseFileDescriptor(handle); };
|
|
|
|
/* Iterate to get the total entry count. */
|
|
auto entry_count = 0;
|
|
{
|
|
#if defined(ATMOSPHERE_OS_LINUX)
|
|
char buf[1_KB];
|
|
#else
|
|
char buf[2_KB];
|
|
uintptr_t basep = 0;
|
|
#endif
|
|
|
|
NativeDirectoryEntryType *ent = nullptr;
|
|
while (true) {
|
|
/* Read next entries. */
|
|
#if defined (ATMOSPHERE_OS_LINUX)
|
|
const auto nread = ::syscall(SYS_getdents64, handle, buf, sizeof(buf));
|
|
#elif defined(ATMOSPHERE_OS_MACOS)
|
|
const auto nread = ::__getdirentries64(handle, buf, sizeof(buf), std::addressof(basep));
|
|
#else
|
|
#error "Unknown OS to read from directory FD"
|
|
#endif
|
|
|
|
R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents));
|
|
|
|
/* If we read nothing, we've hit the end of the directory. */
|
|
if (nread == 0) {
|
|
break;
|
|
}
|
|
|
|
/* Iterate received entries. */
|
|
for (auto pos = 0; pos < nread; pos += ent->d_reclen) {
|
|
/* Get the entry. */
|
|
ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos);
|
|
|
|
/* If the entry is a read target, increment our count. */
|
|
if (IsReadTarget(ent)) {
|
|
++entry_count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*out = entry_count;
|
|
R_SUCCEED();
|
|
}
|
|
private:
|
|
bool IsReadTarget(const NativeDirectoryEntryType *ent) const {
|
|
/* If the file is "..", don't return it. */
|
|
if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Return whether our open mode supports the target. */
|
|
if (ent->d_type == DT_DIR) {
|
|
return m_open_mode != fs::OpenDirectoryMode_File;
|
|
} else {
|
|
return m_open_mode != fs::OpenDirectoryMode_Directory;
|
|
}
|
|
}
|
|
public:
|
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
|
AMS_ABORT("GetDomainObjectId() should never be called on a LocalDirectory");
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
bool AreLongPathsEnabled() {
|
|
AMS_FUNCTION_LOCAL_STATIC(bool, s_enabled, AreLongPathsEnabledImpl());
|
|
return s_enabled;
|
|
}
|
|
|
|
}
|
|
|
|
Result LocalFileSystem::Initialize(const fs::Path &root_path, fssystem::PathCaseSensitiveMode case_sensitive_mode) {
|
|
/* Initialize our root path. */
|
|
R_TRY(m_root_path.Initialize(root_path));
|
|
|
|
/* If we're not empty, we'll need to convert to a native path. */
|
|
if (m_root_path.IsEmpty()) {
|
|
/* Reset our native path, since we're acting without a root. */
|
|
m_native_path_buffer.reset(nullptr);
|
|
m_native_path_length = 0;
|
|
} else {
|
|
/* Convert to native path. */
|
|
NativePathBuffer native_path = nullptr;
|
|
int native_len = 0;
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Get path length. */
|
|
native_len = ::MultiByteToWideChar(CP_UTF8, 0, m_root_path.GetString(), -1, nullptr, 0);
|
|
|
|
/* Allocate our native path buffer. */
|
|
native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + 1);
|
|
R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Convert path. */
|
|
const auto res = ::MultiByteToWideChar(CP_UTF8, 0, m_root_path.GetString(), -1, native_path.get(), native_len);
|
|
R_UNLESS(res != 0, fs::ResultTooLongPath());
|
|
R_UNLESS(res <= static_cast<int>(fs::EntryNameLengthMax + 1), fs::ResultTooLongPath());
|
|
|
|
/* Fix up directory separators. */
|
|
for (NativeCharacterType *p = native_path.get(); *p != 0; ++p) {
|
|
if (*p == '/') {
|
|
*p = '\\';
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
/* Get path size. */
|
|
native_len = std::strlen(m_root_path.GetString());
|
|
|
|
/* Tentatively assume other operating systems do the sane thing and use utf-8 strings. */
|
|
native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + 1);
|
|
R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Copy in path. */
|
|
std::memcpy(native_path.get(), m_root_path.GetString(), native_len + 1);
|
|
}
|
|
#endif
|
|
|
|
/* Temporarily set case sensitive mode to insensitive, and verify we can get the root directory. */
|
|
m_case_sensitive_mode = fssystem::PathCaseSensitiveMode_CaseInsensitive;
|
|
{
|
|
constexpr fs::Path RequiredRootPath = fs::MakeConstantPath("/");
|
|
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(this->GetEntryType(std::addressof(type), RequiredRootPath));
|
|
|
|
R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound());
|
|
}
|
|
|
|
/* Set our native path members. */
|
|
m_native_path_buffer = std::move(native_path);
|
|
m_native_path_length = native_len;
|
|
}
|
|
|
|
/* Set our case sensitive mode. */
|
|
m_case_sensitive_mode = case_sensitive_mode;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::GetCaseSensitivePath(int *out_size, char *dst, size_t dst_size, const char *path, const char *work_path) {
|
|
AMS_UNUSED(out_size, dst, dst_size, path, work_path);
|
|
AMS_ABORT("TODO");
|
|
}
|
|
|
|
Result LocalFileSystem::CheckPathCaseSensitively(const NativeCharacterType *path, const NativeCharacterType *root_path, NativeCharacterType *cs_buf, size_t cs_size, bool check_case_sensitivity) {
|
|
AMS_UNUSED(path, root_path, cs_buf, cs_size, check_case_sensitivity);
|
|
AMS_ABORT("TODO");
|
|
}
|
|
|
|
Result LocalFileSystem::ResolveFullPath(NativePathBuffer *out, const fs::Path &path, int max_len, int min_len, bool check_case_sensitivity) {
|
|
/* Create the full path. */
|
|
fs::Path full_path;
|
|
R_TRY(full_path.Combine(m_root_path, path));
|
|
|
|
/* Check that the path is valid. */
|
|
fs::PathFlags flags;
|
|
flags.AllowWindowsPath();
|
|
flags.AllowRelativePath();
|
|
flags.AllowEmptyPath();
|
|
R_TRY(fs::PathFormatter::CheckPathFormat(full_path.GetString(), flags));
|
|
|
|
/* Check the path's character count. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
AreLongPathsEnabled();
|
|
// TODO: R_TRY(fs::CheckCharacterCountForWindows(full_path.GetString(), MaxBasePathLength, AreLongPathsEnabled() ? 0 : max_len));
|
|
AMS_UNUSED(max_len);
|
|
#else
|
|
AreLongPathsEnabled();
|
|
/* TODO: Check character count for linux/macos? */
|
|
AMS_UNUSED(max_len);
|
|
#endif
|
|
|
|
|
|
/* Convert to native path. */
|
|
NativePathBuffer native_path = nullptr;
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Get path length. */
|
|
const int native_len = ::MultiByteToWideChar(CP_UTF8, 0, full_path.GetString(), -1, nullptr, 0);
|
|
|
|
/* Allocate our native path buffer. */
|
|
native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + min_len + 1);
|
|
R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Convert path. */
|
|
const auto res = ::MultiByteToWideChar(CP_UTF8, 0, full_path.GetString(), -1, native_path.get(), native_len);
|
|
R_UNLESS(res != 0, fs::ResultTooLongPath());
|
|
R_UNLESS(res <= native_len, fs::ResultTooLongPath());
|
|
|
|
/* Fix up directory separators. */
|
|
s32 len = 0;
|
|
for (NativeCharacterType *p = native_path.get(); *p != 0; ++p) {
|
|
if (*p == '/') {
|
|
*p = '\\';
|
|
}
|
|
++len;
|
|
}
|
|
|
|
/* Fix up trailing : */
|
|
if (native_path[len - 1] == ':') {
|
|
native_path[len] = '\\';
|
|
native_path[len + 1] = 0;
|
|
}
|
|
|
|
/* If case sensitivity is required, allocate case sensitive buffer. */
|
|
if (m_case_sensitive_mode == PathCaseSensitiveMode_CaseSensitive && native_path[0] != 0) {
|
|
/* Allocate case sensitive buffer. */
|
|
auto case_sensitive_buffer_size = sizeof(NativeCharacterType) * (m_native_path_length + native_len + 1 + fs::EntryNameLengthMax);
|
|
NativePathBuffer case_sensitive_path_buffer = fs::impl::MakeUnique<NativeCharacterType[]>(case_sensitive_buffer_size / sizeof(NativeCharacterType));
|
|
R_UNLESS(case_sensitive_path_buffer != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Get root path. */
|
|
const NativeCharacterType *root_path = m_native_path_buffer.get() != nullptr ? m_native_path_buffer.get() : L"";
|
|
|
|
/* Perform case sensitive path checking. */
|
|
R_TRY(this->CheckPathCaseSensitively(native_path.get(), root_path, case_sensitive_path_buffer.get(), case_sensitive_buffer_size, check_case_sensitivity));
|
|
}
|
|
|
|
/* Set default path, if empty. */
|
|
if (native_path[0] == 0) {
|
|
native_path[0] = '.';
|
|
native_path[1] = '\\';
|
|
native_path[2] = 0;
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
/* Get path size. */
|
|
const int native_len = std::strlen(full_path.GetString());
|
|
|
|
/* Tentatively assume other operating systems do the sane thing and use utf-8 strings. */
|
|
native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + min_len + 1);
|
|
R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Copy in path. */
|
|
std::memcpy(native_path.get(), full_path.GetString(), native_len + 1);
|
|
|
|
/* TODO: Is case sensitivity adjustment needed here? */
|
|
AMS_UNUSED(check_case_sensitivity);
|
|
}
|
|
#endif
|
|
|
|
/* Set the output path. */
|
|
*out = std::move(native_path);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoGetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const fs::Path &path) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, false));
|
|
|
|
/* Get the disk free space. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
ULARGE_INTEGER free, total, total_free;
|
|
R_UNLESS(::GetDiskFreeSpaceExW(native_path.get(), std::addressof(free), std::addressof(total), std::addressof(total_free)), ConvertLastErrorToResult());
|
|
|
|
*out_free = static_cast<s64>(free.QuadPart);
|
|
*out_total = static_cast<s64>(total.QuadPart);
|
|
*out_total_free = static_cast<s64>(total_free.QuadPart);
|
|
}
|
|
#else
|
|
{
|
|
struct statvfs st;
|
|
const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::statvfs(native_path.get(), std::addressof(st)); });
|
|
R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Statvfs));
|
|
|
|
*out_free = static_cast<s64>(st.f_bavail) * static_cast<s64>(st.f_frsize);
|
|
*out_total = static_cast<s64>(st.f_blocks) * static_cast<s64>(st.f_frsize);
|
|
*out_total_free = static_cast<s64>(st.f_bfree) * static_cast<s64>(st.f_frsize);
|
|
}
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DeleteDirectoryRecursivelyInternal(const NativeCharacterType *path, bool delete_top) {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Get the path length. */
|
|
const auto path_len = ::wcslen(path);
|
|
|
|
/* Allocate a new path buffer. */
|
|
NativePathBuffer cur_path_buf = fs::impl::MakeUnique<NativeCharacterType[]>(path_len + MAX_PATH);
|
|
R_UNLESS(cur_path_buf.get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Copy the path into the temporary buffer. */
|
|
::wcscpy(cur_path_buf.get(), path);
|
|
::wcscat(cur_path_buf.get(), L"\\*");
|
|
|
|
/* Iterate the directory, deleting all contents. */
|
|
{
|
|
/* Begin finding. */
|
|
WIN32_FIND_DATAW fd;
|
|
const auto handle = ::FindFirstFileW(cur_path_buf.get(), std::addressof(fd));
|
|
R_UNLESS(handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult());
|
|
ON_SCOPE_EXIT { ::FindClose(handle); };
|
|
|
|
/* Clear the path from <path>\\* to path\\ */
|
|
wchar_t * const dst = cur_path_buf.get() + path_len + 1;
|
|
*dst = 0;
|
|
|
|
/* Loop files. */
|
|
while (::FindNextFileW(handle, std::addressof(fd))) {
|
|
/* Skip . and .. */
|
|
if (::wcsncmp(fd.cFileName, L"..", 3) == 0 || ::wcsncmp(fd.cFileName, L".", 2) == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Copy the current filename to our working path. */
|
|
::wcscpy(dst, fd.cFileName);
|
|
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
/* If a directory, delete it recursively. */
|
|
R_TRY(this->DeleteDirectoryRecursivelyInternal(cur_path_buf.get(), true));
|
|
} else {
|
|
/* If a file, just delete it. */
|
|
auto delete_file = [&]() -> Result {
|
|
R_UNLESS(::DeleteFileW(cur_path_buf.get()), ConvertLastErrorToResult());
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_file));
|
|
R_TRY(WaitDeletionCompletion(cur_path_buf.get()));
|
|
}
|
|
}
|
|
|
|
/* Check that we stopped iterating because we ran out of files. */
|
|
R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult());
|
|
}
|
|
|
|
/* If we should, delete the top level directory. */
|
|
if (delete_top) {
|
|
auto delete_impl = [&] () -> Result {
|
|
R_UNLESS(::RemoveDirectoryW(path), ConvertLastErrorToResult());
|
|
R_SUCCEED();
|
|
};
|
|
|
|
/* Perform the delete. */
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl));
|
|
|
|
/* Wait for the deletion to complete. */
|
|
R_TRY(WaitDeletionCompletion(path));
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
/* Get the path length. */
|
|
const auto path_len = std::strlen(path);
|
|
|
|
/* Allocate a temporary buffer. */
|
|
NativePathBuffer cur_path_buf = fs::impl::MakeUnique<NativeCharacterType[]>(path_len + PATH_MAX);
|
|
R_UNLESS(cur_path_buf.get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique());
|
|
|
|
/* Copy the path into the temporary buffer. */
|
|
std::memcpy(cur_path_buf.get(), path, path_len);
|
|
auto ofs = path_len;
|
|
if (cur_path_buf.get()[ofs - 1] != '/') {
|
|
cur_path_buf.get()[ofs++] = '/';
|
|
}
|
|
|
|
/* Iterate the directory, deleting all contents. */
|
|
{
|
|
/* Open the directory. */
|
|
const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(path, O_RDONLY | O_DIRECTORY); });
|
|
R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory));
|
|
ON_SCOPE_EXIT { CloseFileDescriptor(handle); };
|
|
|
|
#if defined(ATMOSPHERE_OS_LINUX)
|
|
char buf[1_KB];
|
|
#else
|
|
char buf[2_KB];
|
|
uintptr_t basep = 0;
|
|
#endif
|
|
|
|
NativeDirectoryEntryType *ent = nullptr;
|
|
static_assert(sizeof(*ent) <= sizeof(buf));
|
|
while (true) {
|
|
/* Read next entries. */
|
|
#if defined (ATMOSPHERE_OS_LINUX)
|
|
const auto nread = ::syscall(SYS_getdents64, handle, buf, sizeof(buf));
|
|
#elif defined(ATMOSPHERE_OS_MACOS)
|
|
const auto nread = ::__getdirentries64(handle, buf, sizeof(buf), std::addressof(basep));
|
|
#else
|
|
#error "Unknown OS to read from directory FD"
|
|
#endif
|
|
R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents));
|
|
|
|
/* If we read nothing, we've hit the end of the directory. */
|
|
if (nread == 0) {
|
|
break;
|
|
}
|
|
|
|
/* Iterate received entries. */
|
|
for (auto pos = 0; pos < nread; pos += ent->d_reclen) {
|
|
/* Get the entry. */
|
|
ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos);
|
|
|
|
/* Skip . and .. */
|
|
if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Get the entry name length. */
|
|
const int e_len = std::strlen(ent->d_name);
|
|
std::memcpy(cur_path_buf.get() + ofs, ent->d_name, e_len + 1);
|
|
|
|
/* Get the dir type. */
|
|
const auto d_type = ent->d_type;
|
|
|
|
if (d_type == DT_DIR) {
|
|
/* If a directory, recursively delete it. */
|
|
R_TRY(this->DeleteDirectoryRecursivelyInternal(cur_path_buf.get(), true));
|
|
} else {
|
|
/* If a file, just delete it. */
|
|
auto delete_file = [&]() -> Result {
|
|
const auto res = ::unlink(cur_path_buf.get());
|
|
R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Unlink));
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_file));
|
|
R_TRY(WaitDeletionCompletion(cur_path_buf.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we should, delete the top level directory. */
|
|
if (delete_top) {
|
|
auto delete_impl = [&] () -> Result {
|
|
R_UNLESS(::rmdir(path) == 0, ConvertErrnoToResult(ErrnoSource_Rmdir));
|
|
R_SUCCEED();
|
|
};
|
|
|
|
/* Perform the delete. */
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl));
|
|
|
|
/* Wait for the deletion to complete. */
|
|
R_TRY(WaitDeletionCompletion(path));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoCreateFile(const fs::Path &path, s64 size, int flags) {
|
|
AMS_UNUSED(flags);
|
|
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, false));
|
|
|
|
/* Create the file. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Get handle to created file. */
|
|
const auto handle = ::CreateFileW(native_path.get(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
/* If we failed because of target locked, it may be the case that the path already exists as a directory. */
|
|
R_TRY_CATCH(ConvertLastErrorToResult()) {
|
|
R_CATCH(fs::ResultTargetLocked) {
|
|
/* Get the file attributes. */
|
|
const auto attr = ::GetFileAttributesW(native_path.get());
|
|
|
|
/* Check they're valid. */
|
|
R_UNLESS(attr != INVALID_FILE_ATTRIBUTES, R_CURRENT_RESULT);
|
|
|
|
/* Check that they specify a directory. */
|
|
R_UNLESS((attr & FILE_ATTRIBUTE_DIRECTORY) != 0, R_CURRENT_RESULT);
|
|
|
|
/* The path is an existing directory. */
|
|
R_THROW(fs::ResultPathAlreadyExists());
|
|
}
|
|
} R_END_TRY_CATCH;
|
|
}
|
|
ON_RESULT_FAILURE { ::DeleteFileW(native_path.get()); };
|
|
ON_SCOPE_EXIT { ::CloseHandle(handle); };
|
|
|
|
/* Set the file as sparse. */
|
|
{
|
|
DWORD dummy;
|
|
::DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, std::addressof(dummy), nullptr);
|
|
}
|
|
|
|
/* Set the file size. */
|
|
if (size > 0) {
|
|
R_TRY(SetFileSizeImpl(handle, size));
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
/* Create the file. */
|
|
const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> int {
|
|
return ::open(native_path.get(), O_WRONLY | O_CREAT | O_EXCL, 0666);
|
|
});
|
|
R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_CreateFile));
|
|
ON_RESULT_FAILURE { ::unlink(native_path.get()); };
|
|
ON_SCOPE_EXIT { CloseFileDescriptor(handle); };
|
|
|
|
/* Set the file as sparse. */
|
|
/* TODO: How do you do this on macos/linux? */
|
|
|
|
/* Set the file size. */
|
|
if (size > 0) {
|
|
R_TRY(SetFileSizeImpl(handle, size));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoDeleteFile(const fs::Path &path) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Delete the file, retrying on target locked. */
|
|
auto delete_impl = [&] () -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Try to delete the file directly. */
|
|
R_SUCCEED_IF(::DeleteFileW(native_path.get()));
|
|
|
|
/* Convert the last error to a result. */
|
|
const auto last_error_result = ConvertLastErrorToResult();
|
|
|
|
/* Check if access denied; it may indicate we tried to open a directory. */
|
|
R_UNLESS(::GetLastError() == ERROR_ACCESS_DENIED, last_error_result);
|
|
|
|
/* Check if we tried to open a directory. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get()));
|
|
|
|
/* If the type is anything other than directory, perform generic result conversion. */
|
|
R_UNLESS(type == fs::DirectoryEntryType_Directory, last_error_result);
|
|
|
|
/* Return path not found, for trying to open a file as a directory. */
|
|
R_THROW(fs::ResultPathNotFound());
|
|
}
|
|
#else
|
|
{
|
|
/* If on macOS, we need to check if the path is a directory before trying to unlink it. */
|
|
/* This is because unlink succeeds on directories when executing as superuser. */
|
|
#if defined(ATMOSPHERE_OS_MACOS)
|
|
{
|
|
/* Check if we tried to open a directory. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get()));
|
|
R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound());
|
|
}
|
|
#endif
|
|
|
|
/* Delete the file. */
|
|
const auto res = ::unlink(native_path.get());
|
|
R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Unlink));
|
|
}
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
};
|
|
|
|
/* Perform the delete. */
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl));
|
|
|
|
/* Wait for the deletion to complete. */
|
|
R_RETURN(WaitDeletionCompletion(native_path.get()));
|
|
}
|
|
|
|
Result LocalFileSystem::DoCreateDirectory(const fs::Path &path) {
|
|
/* Check for path validity. */
|
|
R_UNLESS(path != "/", fs::ResultPathNotFound());
|
|
R_UNLESS(path != ".", fs::ResultPathNotFound());
|
|
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxDirectoryPathLength, 0, false));
|
|
|
|
/* Create the directory. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
R_UNLESS(::CreateDirectoryW(native_path.get(), nullptr), ConvertLastErrorToResult());
|
|
#else
|
|
R_UNLESS(::mkdir(native_path.get(), 0777) == 0, ConvertErrnoToResult(ErrnoSource_Mkdir));
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoDeleteDirectory(const fs::Path &path) {
|
|
/* Guard against deletion of raw drive. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
R_UNLESS(!fs::IsWindowsDriveRootPath(path), fs::ResultDirectoryNotDeletable());
|
|
#else
|
|
/* TODO: Linux/macOS? */
|
|
#endif
|
|
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Delete the directory, retrying on target locked. */
|
|
auto delete_impl = [&] () -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
R_UNLESS(::RemoveDirectoryW(native_path.get()), ConvertLastErrorToResult());
|
|
#else
|
|
R_UNLESS(::rmdir(native_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_Rmdir));
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
};
|
|
|
|
/* Perform the delete. */
|
|
R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl));
|
|
|
|
/* Wait for the deletion to complete. */
|
|
R_RETURN(WaitDeletionCompletion(native_path.get()));
|
|
}
|
|
|
|
Result LocalFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) {
|
|
/* Guard against deletion of raw drive. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
R_UNLESS(!fs::IsWindowsDriveRootPath(path), fs::ResultDirectoryNotDeletable());
|
|
#else
|
|
/* TODO: Linux/macOS? */
|
|
#endif
|
|
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Delete the directory. */
|
|
R_RETURN(this->DeleteDirectoryRecursivelyInternal(native_path.get(), true));
|
|
}
|
|
|
|
Result LocalFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) {
|
|
/* Resolve the old path. */
|
|
NativePathBuffer native_old_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_old_path), old_path, MaxFilePathLength, 0, true));
|
|
|
|
/* Resolve the new path. */
|
|
NativePathBuffer native_new_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_new_path), new_path, MaxFilePathLength, 0, false));
|
|
|
|
/* Check that the old path is a file. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_old_path.get()));
|
|
R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound());
|
|
|
|
/* Perform the rename. */
|
|
auto rename_impl = [&]() -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
if (!::MoveFileW(native_old_path.get(), native_new_path.get())) {
|
|
R_TRY_CATCH(ConvertLastErrorToResult()) {
|
|
R_CATCH(fs::ResultTargetLocked) {
|
|
/* If we're performing a self rename, succeed. */
|
|
R_SUCCEED_IF(::wcscmp(native_old_path.get(), native_new_path.get()) == 0);
|
|
|
|
/* Otherwise, check if the new path already exists. */
|
|
const auto attr = ::GetFileAttributesW(native_new_path.get());
|
|
R_UNLESS(attr == INVALID_FILE_ATTRIBUTES, fs::ResultPathAlreadyExists());
|
|
|
|
/* Return the original result. */
|
|
R_THROW(R_CURRENT_RESULT);
|
|
}
|
|
} R_END_TRY_CATCH;
|
|
}
|
|
#else
|
|
{
|
|
/* ::rename() will destroy an existing file at new path...so check for that case ahead of time. */
|
|
{
|
|
struct stat st;
|
|
R_UNLESS(::stat(native_new_path.get(), std::addressof(st)) < 0, fs::ResultPathAlreadyExists());
|
|
}
|
|
|
|
/* Rename the file. */
|
|
R_UNLESS(::rename(native_old_path.get(), native_new_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_RenameFile));
|
|
}
|
|
#endif
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_RETURN(fssystem::RetryToAvoidTargetLocked(rename_impl));
|
|
}
|
|
|
|
Result LocalFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) {
|
|
/* Resolve the old path. */
|
|
NativePathBuffer native_old_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_old_path), old_path, MaxDirectoryPathLength, 0, true));
|
|
|
|
/* Resolve the new path. */
|
|
NativePathBuffer native_new_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_new_path), new_path, MaxDirectoryPathLength, 0, false));
|
|
|
|
/* Check that the old path is a file. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_old_path.get()));
|
|
R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound());
|
|
|
|
/* Perform the rename. */
|
|
auto rename_impl = [&]() -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
if (!::MoveFileW(native_old_path.get(), native_new_path.get())) {
|
|
R_TRY_CATCH(ConvertLastErrorToResult()) {
|
|
R_CATCH(fs::ResultTargetLocked) {
|
|
/* If we're performing a self rename, succeed. */
|
|
R_SUCCEED_IF(::wcscmp(native_old_path.get(), native_new_path.get()) == 0);
|
|
|
|
/* Otherwise, check if the new path already exists. */
|
|
const auto attr = ::GetFileAttributesW(native_new_path.get());
|
|
R_UNLESS(attr == INVALID_FILE_ATTRIBUTES, fs::ResultPathAlreadyExists());
|
|
|
|
/* Return the original result. */
|
|
R_THROW(R_CURRENT_RESULT);
|
|
}
|
|
} R_END_TRY_CATCH;
|
|
}
|
|
#else
|
|
{
|
|
/* ::rename() will overwrite an existing empty directory at the target, so check for that ahead of time. */
|
|
{
|
|
struct stat st;
|
|
R_UNLESS(::stat(native_new_path.get(), std::addressof(st)) < 0, fs::ResultPathAlreadyExists());
|
|
}
|
|
|
|
/* Rename the directory. */
|
|
R_UNLESS(::rename(native_old_path.get(), native_new_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_RenameDirectory));
|
|
}
|
|
#endif
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_RETURN(fssystem::RetryToAvoidTargetLocked(rename_impl));
|
|
}
|
|
|
|
Result LocalFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Get the entry type. */
|
|
R_RETURN(GetEntryTypeImpl(out, native_path.get()));
|
|
}
|
|
|
|
Result LocalFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Open the file, retrying on target locked. */
|
|
auto open_impl = [&] () -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
/* Open a windows file handle. */
|
|
const DWORD desired_access = ((mode & fs::OpenMode_Read) ? GENERIC_READ : 0) | ((mode & fs::OpenMode_Write) ? GENERIC_WRITE : 0);
|
|
const auto file_handle = ::CreateFileW(native_path.get(), desired_access, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
|
|
if (file_handle == INVALID_HANDLE_VALUE) {
|
|
/* Convert the last error to a result. */
|
|
const auto last_error_result = ConvertLastErrorToResult();
|
|
|
|
/* Check if access denied; it may indicate we tried to open a directory. */
|
|
R_UNLESS(::GetLastError() == ERROR_ACCESS_DENIED, last_error_result);
|
|
|
|
/* Check if we tried to open a directory. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get()));
|
|
|
|
/* If the type isn't file, return path not found. */
|
|
R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound());
|
|
|
|
/* Return the error we encountered earlier. */
|
|
R_THROW(last_error_result);
|
|
}
|
|
ON_RESULT_FAILURE { ::CloseHandle(file_handle); };
|
|
#else
|
|
const bool is_read = (mode & fs::OpenMode_Read);
|
|
const bool is_write = (mode & fs::OpenMode_Write);
|
|
int file_handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA {
|
|
return ::open(native_path.get(), (is_read && is_write) ? (O_RDWR) : (is_write ? (O_WRONLY) : (is_read ? (O_RDONLY) : (0))));
|
|
});
|
|
R_UNLESS(file_handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenFile));
|
|
ON_RESULT_FAILURE { CloseFileDescriptor(file_handle); };
|
|
#endif
|
|
|
|
/* Create a new local file. */
|
|
auto file = std::make_unique<LocalFile>(file_handle, mode);
|
|
R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInLocalFileSystemA());
|
|
|
|
/* Set the output file. */
|
|
*out_file = std::move(file);
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_RETURN(fssystem::RetryToAvoidTargetLocked(open_impl));
|
|
}
|
|
|
|
Result LocalFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 3, true));
|
|
|
|
/* Open the directory, retrying on target locked. */
|
|
auto open_impl = [&] () -> Result {
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
/* Open a handle file handle. */
|
|
const auto dir_handle = ::CreateFileW(native_path.get(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
R_UNLESS(dir_handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult());
|
|
ON_RESULT_FAILURE { ::CloseHandle(dir_handle); };
|
|
|
|
/* Check that we tried to open a directory. */
|
|
fs::DirectoryEntryType type;
|
|
R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get()));
|
|
|
|
/* If the type isn't directory, return path not found. */
|
|
R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound());
|
|
|
|
/* Fix up the path for us to perform a windows search. */
|
|
const auto native_len = ::wcslen(native_path.get());
|
|
const bool has_sep = native_len > 0 && native_path[native_len - 1] == '\\';
|
|
if (has_sep) {
|
|
native_path[native_len + 0] = '*';
|
|
native_path[native_len + 1] = 0;
|
|
} else {
|
|
native_path[native_len + 0] = '\\';
|
|
native_path[native_len + 1] = '*';
|
|
native_path[native_len + 2] = 0;
|
|
}
|
|
#else
|
|
/* Open the directory. */
|
|
const auto dir_handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(native_path.get(), O_RDONLY | O_DIRECTORY); });
|
|
R_UNLESS(dir_handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory));
|
|
ON_RESULT_FAILURE { CloseFileDescriptor(dir_handle); };
|
|
#endif
|
|
|
|
/* Create a new local directory. */
|
|
auto dir = std::make_unique<LocalDirectory>(dir_handle, mode, std::move(native_path));
|
|
R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedInLocalFileSystemB());
|
|
|
|
/* Set the output directory. */
|
|
*out_dir = std::move(dir);
|
|
R_SUCCEED();
|
|
};
|
|
|
|
R_RETURN(fssystem::RetryToAvoidTargetLocked(open_impl));
|
|
}
|
|
|
|
Result LocalFileSystem::DoCommit() {
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) {
|
|
s64 dummy;
|
|
R_RETURN(this->DoGetDiskFreeSpace(out, std::addressof(dummy), std::addressof(dummy), path));
|
|
}
|
|
|
|
Result LocalFileSystem::DoGetTotalSpaceSize(s64 *out, const fs::Path &path) {
|
|
s64 dummy;
|
|
R_RETURN(this->DoGetDiskFreeSpace(std::addressof(dummy), out, std::addressof(dummy), path));
|
|
}
|
|
|
|
Result LocalFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Delete the directory. */
|
|
R_RETURN(this->DeleteDirectoryRecursivelyInternal(native_path.get(), false));
|
|
}
|
|
|
|
Result LocalFileSystem::DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) {
|
|
/* Resolve the path. */
|
|
NativePathBuffer native_path;
|
|
R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true));
|
|
|
|
/* Get the file timestamp. */
|
|
#if defined(ATMOSPHERE_OS_WINDOWS)
|
|
{
|
|
/* Get the file attributes. */
|
|
WIN32_FILE_ATTRIBUTE_DATA attr;
|
|
R_UNLESS(::GetFileAttributesExW(native_path.get(), GetFileExInfoStandard, std::addressof(attr)), ConvertLastErrorToResult());
|
|
|
|
/* Check that the file isn't a directory. */
|
|
R_UNLESS((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0, fs::ResultPathNotFound());
|
|
|
|
/* Decode the FILETIME values. */
|
|
const s64 create = static_cast<s64>(static_cast<u64>(attr.ftCreationTime.dwLowDateTime ) | (static_cast<u64>(attr.ftCreationTime.dwHighDateTime ) << BITSIZEOF(attr.ftCreationTime.dwLowDateTime )));
|
|
const s64 access = static_cast<s64>(static_cast<u64>(attr.ftLastAccessTime.dwLowDateTime) | (static_cast<u64>(attr.ftLastAccessTime.dwHighDateTime) << BITSIZEOF(attr.ftLastAccessTime.dwLowDateTime)));
|
|
const s64 modify = static_cast<s64>(static_cast<u64>(attr.ftLastWriteTime.dwLowDateTime ) | (static_cast<u64>(attr.ftLastWriteTime.dwHighDateTime ) << BITSIZEOF(attr.ftLastWriteTime.dwLowDateTime )));
|
|
|
|
/* Set the output timestamps. */
|
|
if (m_use_posix_time) {
|
|
out->create = ConvertWindowsTimeToPosixTime(create);
|
|
out->access = ConvertWindowsTimeToPosixTime(access);
|
|
out->modify = ConvertWindowsTimeToPosixTime(modify);
|
|
} else {
|
|
out->create = create;
|
|
out->access = access;
|
|
out->modify = modify;
|
|
}
|
|
|
|
/* We're not using local timestamps. */
|
|
out->is_local_time = false;
|
|
}
|
|
#else
|
|
{
|
|
/* Get the file stats. */
|
|
struct stat st;
|
|
R_UNLESS(::stat(native_path.get(), std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat));
|
|
|
|
/* Check that the path isn't a directory. */
|
|
R_UNLESS(!(S_ISDIR(st.st_mode)), fs::ResultPathNotFound());
|
|
|
|
/* Set the output timestamps. */
|
|
#if defined(ATMOSPHERE_OS_LINUX)
|
|
if (m_use_posix_time) {
|
|
out->create = st.st_ctim.tv_sec;
|
|
out->access = st.st_atim.tv_sec;
|
|
out->modify = st.st_mtim.tv_sec;
|
|
} else {
|
|
out->create = ConvertPosixTimeToWindowsTime(st.st_ctim.tv_sec, st.st_ctim.tv_nsec);
|
|
out->access = ConvertPosixTimeToWindowsTime(st.st_atim.tv_sec, st.st_atim.tv_nsec);
|
|
out->modify = ConvertPosixTimeToWindowsTime(st.st_mtim.tv_sec, st.st_mtim.tv_nsec);
|
|
}
|
|
#else
|
|
if (m_use_posix_time) {
|
|
out->create = st.st_ctimespec.tv_sec;
|
|
out->access = st.st_atimespec.tv_sec;
|
|
out->modify = st.st_mtimespec.tv_sec;
|
|
} else {
|
|
out->create = ConvertPosixTimeToWindowsTime(st.st_ctimespec.tv_sec, st.st_ctimespec.tv_nsec);
|
|
out->access = ConvertPosixTimeToWindowsTime(st.st_atimespec.tv_sec, st.st_atimespec.tv_nsec);
|
|
out->modify = ConvertPosixTimeToWindowsTime(st.st_mtimespec.tv_sec, st.st_mtimespec.tv_nsec);
|
|
}
|
|
#endif
|
|
|
|
/* We're not using local timestamps. */
|
|
out->is_local_time = false;
|
|
}
|
|
#endif
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) {
|
|
AMS_UNUSED(dst, dst_size, src, src_size, query, path);
|
|
R_THROW(fs::ResultUnsupportedOperation());
|
|
}
|
|
|
|
Result LocalFileSystem::DoCommitProvisionally(s64 counter) {
|
|
AMS_UNUSED(counter);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result LocalFileSystem::DoRollback() {
|
|
R_SUCCEED();
|
|
}
|
|
|
|
}
|
|
#endif |