Atmosphere/libraries/libstratosphere/source/fssystem/fssystem_local_file_system.cpp

1754 lines
79 KiB
C++
Raw Normal View History

/*
* 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 <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());
}
/* 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. */
LONG high = size >> BITSIZEOF(LONG);
::SetFilePointer(handle, static_cast<LONG>(size), std::addressof(high), FILE_BEGIN);
/* Set the file size. */
R_UNLESS(::SetEndOfFile(handle), ConvertLastErrorToResult());
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");
}
};
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, HANDLE s, 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(s) {
m_open_mode = static_cast<fs::OpenDirectoryMode>(util::ToUnderlying(m) & ~util::ToUnderlying(fs::OpenDirectoryMode_NotRequireFileSize));
}
virtual ~LocalDirectory() {
::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 (!::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 = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 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 (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
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, int s, 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;
AMS_ASSERT(s < 0);
AMS_UNUSED(s);
}
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_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_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) {
/* 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) {
/* 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); };
/* 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;
}
/* Open windows search handle. */
WIN32_FIND_DATAW fd;
const auto search_handle = ::FindFirstFileW(native_path.get(), std::addressof(fd));
if (search_handle == INVALID_HANDLE_VALUE) {
/* Convert the last error to a result. */
const auto last_error_result = ConvertLastErrorToResult();
/* Fix the path, so that we can check if we tried to open a file. */
native_path[native_len] = 0;
/* 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 directory, return path not found. */
R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound());
/* Return the error we encountered earlier. */
R_THROW(last_error_result);
}
ON_RESULT_FAILURE_2 { ::CloseHandle(search_handle); };
#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); };
/* Non-windows doesn't have separate search handle. */
const auto search_handle = -1;
#endif
/* Create a new local directory. */
auto dir = std::make_unique<LocalDirectory>(dir_handle, search_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