mirror of
				https://github.com/Atmosphere-NX/Atmosphere-libs.git
				synced 2025-10-31 03:25:47 +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 |