Browse Source
Merge pull request #2132 from wwylele/fix-fs-err
Merge pull request #2132 from wwylele/fix-fs-err
Correct FS error codes & add path boundary checksnce_cpp
committed by
GitHub
30 changed files with 1234 additions and 304 deletions
-
10src/core/CMakeLists.txt
-
28src/core/file_sys/archive_backend.h
-
115src/core/file_sys/archive_extsavedata.cpp
-
20src/core/file_sys/archive_ncch.cpp
-
8src/core/file_sys/archive_ncch.h
-
4src/core/file_sys/archive_savedata.cpp
-
279src/core/file_sys/archive_sdmc.cpp
-
26src/core/file_sys/archive_sdmc.h
-
70src/core/file_sys/archive_sdmcwriteonly.cpp
-
57src/core/file_sys/archive_sdmcwriteonly.h
-
4src/core/file_sys/archive_systemsavedata.cpp
-
6src/core/file_sys/directory_backend.h
-
150src/core/file_sys/disk_archive.cpp
-
43src/core/file_sys/disk_archive.h
-
40src/core/file_sys/errors.h
-
6src/core/file_sys/file_backend.h
-
31src/core/file_sys/ivfc_archive.cpp
-
20src/core/file_sys/ivfc_archive.h
-
98src/core/file_sys/path_parser.cpp
-
61src/core/file_sys/path_parser.h
-
283src/core/file_sys/savedata_archive.cpp
-
43src/core/file_sys/savedata_archive.h
-
9src/core/hle/result.h
-
9src/core/hle/service/cfg/cfg.cpp
-
58src/core/hle/service/fs/archive.cpp
-
2src/core/hle/service/fs/archive.h
-
2src/core/hle/service/ptm/ptm.cpp
-
2src/tests/CMakeLists.txt
-
38src/tests/core/file_sys/path_parser.cpp
-
14src/tests/glad.cpp
@ -0,0 +1,70 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <memory>
|
||||
|
#include "common/file_util.h"
|
||||
|
#include "core/file_sys/archive_sdmcwriteonly.h"
|
||||
|
#include "core/file_sys/directory_backend.h"
|
||||
|
#include "core/file_sys/errors.h"
|
||||
|
#include "core/file_sys/file_backend.h"
|
||||
|
#include "core/settings.h"
|
||||
|
|
||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
// FileSys namespace
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path, |
||||
|
const Mode& mode) const { |
||||
|
if (mode.read_flag) { |
||||
|
LOG_ERROR(Service_FS, "Read flag is not supported"); |
||||
|
return ERROR_INVALID_READ_FLAG; |
||||
|
} |
||||
|
return SDMCArchive::OpenFileBase(path, mode); |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory( |
||||
|
const Path& path) const { |
||||
|
LOG_ERROR(Service_FS, "Not supported"); |
||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
||||
|
} |
||||
|
|
||||
|
ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point) |
||||
|
: sdmc_directory(mount_point) { |
||||
|
LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); |
||||
|
} |
||||
|
|
||||
|
bool ArchiveFactory_SDMCWriteOnly::Initialize() { |
||||
|
if (!Settings::values.use_virtual_sd) { |
||||
|
LOG_WARNING(Service_FS, "SDMC disabled by config."); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!FileUtil::CreateFullPath(sdmc_directory)) { |
||||
|
LOG_ERROR(Service_FS, "Unable to create SDMC path."); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) { |
||||
|
auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory); |
||||
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
||||
|
} |
||||
|
|
||||
|
ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path, |
||||
|
const FileSys::ArchiveFormatInfo& format_info) { |
||||
|
// TODO(wwylele): hwtest this
|
||||
|
LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive."); |
||||
|
return ResultCode(-1); |
||||
|
} |
||||
|
|
||||
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const { |
||||
|
// TODO(Subv): Implement
|
||||
|
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
||||
|
return ResultCode(-1); |
||||
|
} |
||||
|
|
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,57 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "core/file_sys/archive_sdmc.h" |
||||
|
|
||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
|
// FileSys namespace |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
/** |
||||
|
* Archive backend for SDMC write-only archive. |
||||
|
* The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for |
||||
|
* - OpenDirectory is unsupported; |
||||
|
* - OpenFile with read flag is unsupported. |
||||
|
*/ |
||||
|
class SDMCWriteOnlyArchive : public SDMCArchive { |
||||
|
public: |
||||
|
SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {} |
||||
|
|
||||
|
std::string GetName() const override { |
||||
|
return "SDMCWriteOnlyArchive: " + mount_point; |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |
||||
|
const Mode& mode) const override; |
||||
|
|
||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; |
||||
|
}; |
||||
|
|
||||
|
/// File system interface to the SDMC write-only archive |
||||
|
class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory { |
||||
|
public: |
||||
|
ArchiveFactory_SDMCWriteOnly(const std::string& mount_point); |
||||
|
|
||||
|
/** |
||||
|
* Initialize the archive. |
||||
|
* @return true if it initialized successfully |
||||
|
*/ |
||||
|
bool Initialize(); |
||||
|
|
||||
|
std::string GetName() const override { |
||||
|
return "SDMCWriteOnly"; |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; |
||||
|
ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; |
||||
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; |
||||
|
|
||||
|
private: |
||||
|
std::string sdmc_directory; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#include "core/hle/result.h" |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS, |
||||
|
ErrorSummary::InvalidArgument, ErrorLevel::Usage); |
||||
|
const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags, |
||||
|
ErrorModule::FS, ErrorSummary::NotSupported, |
||||
|
ErrorLevel::Usage); |
||||
|
const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, |
||||
|
ErrorSummary::Canceled, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS, |
||||
|
ErrorSummary::InvalidArgument, ErrorLevel::Usage); |
||||
|
const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, |
||||
|
ErrorSummary::NotFound, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, |
||||
|
ErrorSummary::NotFound, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS, |
||||
|
ErrorSummary::NotFound, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory, |
||||
|
ErrorModule::FS, ErrorSummary::NotSupported, |
||||
|
ErrorLevel::Usage); |
||||
|
const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile, |
||||
|
ErrorModule::FS, ErrorSummary::Canceled, |
||||
|
ErrorLevel::Status); |
||||
|
const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists, |
||||
|
ErrorModule::FS, ErrorSummary::NothingHappened, |
||||
|
ErrorLevel::Status); |
||||
|
const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS, |
||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, |
||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status); |
||||
|
const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS, |
||||
|
ErrorSummary::Canceled, ErrorLevel::Status); |
||||
|
|
||||
|
} // namespace FileSys |
||||
@ -0,0 +1,98 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <set>
|
||||
|
#include "common/file_util.h"
|
||||
|
#include "common/string_util.h"
|
||||
|
#include "core/file_sys/path_parser.h"
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
PathParser::PathParser(const Path& path) { |
||||
|
if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) { |
||||
|
is_valid = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto path_string = path.AsString(); |
||||
|
if (path_string.size() == 0 || path_string[0] != '/') { |
||||
|
is_valid = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Filter out invalid characters for the host system.
|
||||
|
// Although some of these characters are valid on 3DS, they are unlikely to be used by games.
|
||||
|
if (std::find_if(path_string.begin(), path_string.end(), [](char c) { |
||||
|
static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'}; |
||||
|
return invalid_chars.find(c) != invalid_chars.end(); |
||||
|
}) != path_string.end()) { |
||||
|
is_valid = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Common::SplitString(path_string, '/', path_sequence); |
||||
|
|
||||
|
auto begin = path_sequence.begin(); |
||||
|
auto end = path_sequence.end(); |
||||
|
end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; }); |
||||
|
path_sequence = std::vector<std::string>(begin, end); |
||||
|
|
||||
|
// checks if the path is out of bounds.
|
||||
|
int level = 0; |
||||
|
for (auto& node : path_sequence) { |
||||
|
if (node == "..") { |
||||
|
--level; |
||||
|
if (level < 0) { |
||||
|
is_valid = false; |
||||
|
return; |
||||
|
} |
||||
|
} else { |
||||
|
++level; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
is_valid = true; |
||||
|
is_root = level == 0; |
||||
|
} |
||||
|
|
||||
|
PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const { |
||||
|
auto path = mount_point; |
||||
|
if (!FileUtil::IsDirectory(path)) |
||||
|
return InvalidMountPoint; |
||||
|
if (path_sequence.empty()) { |
||||
|
return DirectoryFound; |
||||
|
} |
||||
|
|
||||
|
for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) { |
||||
|
if (path.back() != '/') |
||||
|
path += '/'; |
||||
|
path += *iter; |
||||
|
|
||||
|
if (!FileUtil::Exists(path)) |
||||
|
return PathNotFound; |
||||
|
if (FileUtil::IsDirectory(path)) |
||||
|
continue; |
||||
|
return FileInPath; |
||||
|
} |
||||
|
|
||||
|
path += "/" + path_sequence.back(); |
||||
|
if (!FileUtil::Exists(path)) |
||||
|
return NotFound; |
||||
|
if (FileUtil::IsDirectory(path)) |
||||
|
return DirectoryFound; |
||||
|
return FileFound; |
||||
|
} |
||||
|
|
||||
|
std::string PathParser::BuildHostPath(const std::string& mount_point) const { |
||||
|
std::string path = mount_point; |
||||
|
for (auto& node : path_sequence) { |
||||
|
if (path.back() != '/') |
||||
|
path += '/'; |
||||
|
path += node; |
||||
|
} |
||||
|
return path; |
||||
|
} |
||||
|
|
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,61 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
#include "core/file_sys/archive_backend.h" |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
/** |
||||
|
* A helper class parsing and verifying a string-type Path. |
||||
|
* Every archives with a sub file system should use this class to parse the path argument and check |
||||
|
* the status of the file / directory in question on the host file system. |
||||
|
*/ |
||||
|
class PathParser { |
||||
|
public: |
||||
|
PathParser(const Path& path); |
||||
|
|
||||
|
/** |
||||
|
* Checks if the Path is valid. |
||||
|
* This function should be called once a PathParser is constructed. |
||||
|
* A Path is valid if: |
||||
|
* - it is a string path (with type LowPathType::Char or LowPathType::Wchar), |
||||
|
* - it starts with "/" (this seems a hard requirement in real 3DS), |
||||
|
* - it doesn't contain invalid characters, and |
||||
|
* - it doesn't go out of the root directory using "..". |
||||
|
*/ |
||||
|
bool IsValid() const { |
||||
|
return is_valid; |
||||
|
} |
||||
|
|
||||
|
/// Checks if the Path represents the root directory. |
||||
|
bool IsRootDirectory() const { |
||||
|
return is_root; |
||||
|
} |
||||
|
|
||||
|
enum HostStatus { |
||||
|
InvalidMountPoint, |
||||
|
PathNotFound, // "/a/b/c" when "a" doesn't exist |
||||
|
FileInPath, // "/a/b/c" when "a" is a file |
||||
|
FileFound, // "/a/b/c" when "c" is a file |
||||
|
DirectoryFound, // "/a/b/c" when "c" is a directory |
||||
|
NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist |
||||
|
}; |
||||
|
|
||||
|
/// Checks the status of the specified file / directory by the Path on the host file system. |
||||
|
HostStatus GetHostStatus(const std::string& mount_point) const; |
||||
|
|
||||
|
/// Builds a full path on the host file system. |
||||
|
std::string BuildHostPath(const std::string& mount_point) const; |
||||
|
|
||||
|
private: |
||||
|
std::vector<std::string> path_sequence; |
||||
|
bool is_valid{}; |
||||
|
bool is_root{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
@ -0,0 +1,283 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/file_util.h"
|
||||
|
#include "core/file_sys/disk_archive.h"
|
||||
|
#include "core/file_sys/errors.h"
|
||||
|
#include "core/file_sys/path_parser.h"
|
||||
|
#include "core/file_sys/savedata_archive.h"
|
||||
|
|
||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
// FileSys namespace
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path, |
||||
|
const Mode& mode) const { |
||||
|
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); |
||||
|
|
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
if (mode.hex == 0) { |
||||
|
LOG_ERROR(Service_FS, "Empty open mode"); |
||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
||||
|
} |
||||
|
|
||||
|
if (mode.create_flag && !mode.write_flag) { |
||||
|
LOG_ERROR(Service_FS, "Create flag set but write flag not set"); |
||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
||||
|
} |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
case PathParser::DirectoryFound: |
||||
|
LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); |
||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
||||
|
case PathParser::NotFound: |
||||
|
if (!mode.create_flag) { |
||||
|
LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", |
||||
|
full_path.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
} else { |
||||
|
// Create the file
|
||||
|
FileUtil::CreateEmptyFile(full_path); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); |
||||
|
if (!file.IsOpen()) { |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
} |
||||
|
|
||||
|
auto disk_file = std::make_unique<DiskFile>(std::move(file), mode); |
||||
|
return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::DeleteFile(const Path& path) const { |
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
case PathParser::DirectoryFound: |
||||
|
case PathParser::NotFound: |
||||
|
LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
} |
||||
|
|
||||
|
if (FileUtil::Delete(full_path)) { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { |
||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
|
// exist or similar. Verify.
|
||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status); |
||||
|
} |
||||
|
|
||||
|
template <typename T> |
||||
|
static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, |
||||
|
T deleter) { |
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
if (path_parser.IsRootDirectory()) |
||||
|
return ERROR_DIRECTORY_NOT_EMPTY; |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
case PathParser::NotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
case PathParser::FileFound: |
||||
|
LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); |
||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
||||
|
} |
||||
|
|
||||
|
if (deleter(full_path)) { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); |
||||
|
return ERROR_DIRECTORY_NOT_EMPTY; |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const { |
||||
|
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { |
||||
|
return DeleteDirectoryHelper( |
||||
|
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { |
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
||||
|
case PathParser::DirectoryFound: |
||||
|
case PathParser::FileFound: |
||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); |
||||
|
return ERROR_FILE_ALREADY_EXISTS; |
||||
|
} |
||||
|
|
||||
|
if (size == 0) { |
||||
|
FileUtil::CreateEmptyFile(full_path); |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
FileUtil::IOFile file(full_path, "wb"); |
||||
|
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||
|
// We do this by seeking to the right size, then writing a single null byte.
|
||||
|
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_FS, "Too large file"); |
||||
|
return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, |
||||
|
ErrorLevel::Info); |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { |
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
||||
|
case PathParser::DirectoryFound: |
||||
|
case PathParser::FileFound: |
||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); |
||||
|
return ERROR_DIRECTORY_ALREADY_EXISTS; |
||||
|
} |
||||
|
|
||||
|
if (FileUtil::CreateDir(mount_point + path.AsString())) { |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); |
||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled, |
||||
|
ErrorLevel::Status); |
||||
|
} |
||||
|
|
||||
|
ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { |
||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) |
||||
|
return RESULT_SUCCESS; |
||||
|
|
||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
|
// exist or similar. Verify.
|
||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status); |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( |
||||
|
const Path& path) const { |
||||
|
const PathParser path_parser(path); |
||||
|
|
||||
|
if (!path_parser.IsValid()) { |
||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
||||
|
return ERROR_INVALID_PATH; |
||||
|
} |
||||
|
|
||||
|
const auto full_path = path_parser.BuildHostPath(mount_point); |
||||
|
|
||||
|
switch (path_parser.GetHostStatus(mount_point)) { |
||||
|
case PathParser::InvalidMountPoint: |
||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
||||
|
return ERROR_FILE_NOT_FOUND; |
||||
|
case PathParser::PathNotFound: |
||||
|
case PathParser::NotFound: |
||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
||||
|
return ERROR_PATH_NOT_FOUND; |
||||
|
case PathParser::FileInPath: |
||||
|
case PathParser::FileFound: |
||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
||||
|
} |
||||
|
|
||||
|
auto directory = std::make_unique<DiskDirectory>(full_path); |
||||
|
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); |
||||
|
} |
||||
|
|
||||
|
u64 SaveDataArchive::GetFreeBytes() const { |
||||
|
// TODO: Stubbed to return 1GiB
|
||||
|
return 1024 * 1024 * 1024; |
||||
|
} |
||||
|
|
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,43 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
#include "core/file_sys/archive_backend.h" |
||||
|
#include "core/file_sys/directory_backend.h" |
||||
|
#include "core/file_sys/file_backend.h" |
||||
|
#include "core/hle/result.h" |
||||
|
|
||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
|
// FileSys namespace |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
/// Archive backend for general save data archive type (SaveData and SystemSaveData) |
||||
|
class SaveDataArchive : public ArchiveBackend { |
||||
|
public: |
||||
|
SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {} |
||||
|
|
||||
|
std::string GetName() const override { |
||||
|
return "SaveDataArchive: " + mount_point; |
||||
|
} |
||||
|
|
||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |
||||
|
const Mode& mode) const override; |
||||
|
ResultCode DeleteFile(const Path& path) const override; |
||||
|
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; |
||||
|
ResultCode DeleteDirectory(const Path& path) const override; |
||||
|
ResultCode DeleteDirectoryRecursively(const Path& path) const override; |
||||
|
ResultCode CreateFile(const Path& path, u64 size) const override; |
||||
|
ResultCode CreateDirectory(const Path& path) const override; |
||||
|
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; |
||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; |
||||
|
u64 GetFreeBytes() const override; |
||||
|
|
||||
|
protected: |
||||
|
std::string mount_point; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
@ -0,0 +1,38 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <catch.hpp>
|
||||
|
#include "common/file_util.h"
|
||||
|
#include "core/file_sys/path_parser.h"
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
TEST_CASE("PathParser", "[core][file_sys]") { |
||||
|
REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid()); |
||||
|
REQUIRE(!PathParser(Path("a")).IsValid()); |
||||
|
REQUIRE(!PathParser(Path("/|")).IsValid()); |
||||
|
REQUIRE(PathParser(Path("/a")).IsValid()); |
||||
|
REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid()); |
||||
|
REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid()); |
||||
|
REQUIRE(PathParser(Path("/")).IsRootDirectory()); |
||||
|
REQUIRE(!PathParser(Path("/a")).IsRootDirectory()); |
||||
|
REQUIRE(PathParser(Path("/a/..")).IsRootDirectory()); |
||||
|
} |
||||
|
|
||||
|
TEST_CASE("PathParser - Host file system", "[core][file_sys]") { |
||||
|
std::string test_dir = "./test"; |
||||
|
FileUtil::CreateDir(test_dir); |
||||
|
FileUtil::CreateDir(test_dir + "/z"); |
||||
|
FileUtil::CreateEmptyFile(test_dir + "/a"); |
||||
|
|
||||
|
REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound); |
||||
|
REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound); |
||||
|
REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound); |
||||
|
REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath); |
||||
|
REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound); |
||||
|
|
||||
|
FileUtil::DeleteDirRecursively(test_dir); |
||||
|
} |
||||
|
|
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright 2016 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <catch.hpp>
|
||||
|
#include <glad/glad.h>
|
||||
|
|
||||
|
// This is not an actual test, but a work-around for issue #2183.
|
||||
|
// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS
|
||||
|
// will error about undefined references from video_core to glad. So we explicitly use a glad
|
||||
|
// function here to shut up the linker.
|
||||
|
TEST_CASE("glad fake test", "[dummy]") { |
||||
|
REQUIRE(&gladLoadGL != nullptr); |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue