mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
fs: update + consolidate path normalization logic
This commit is contained in:
parent
5ef93778f6
commit
32803d9920
22 changed files with 1007 additions and 463 deletions
|
@ -40,7 +40,6 @@
|
|||
#include <stratosphere/fs/fs_speed_emulation.hpp>
|
||||
#include <stratosphere/fs/impl/fs_common_mount_name.hpp>
|
||||
#include <stratosphere/fs/fs_mount.hpp>
|
||||
#include <stratosphere/fs/fs_path_tool.hpp>
|
||||
#include <stratosphere/fs/fs_path_utils.hpp>
|
||||
#include <stratosphere/fs/fs_filesystem_utils.hpp>
|
||||
#include <stratosphere/fs/fs_romfs_filesystem.hpp>
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/fs/fs_common.hpp>
|
||||
#include <stratosphere/fssrv/sf/fssrv_sf_path.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace StringTraits {
|
||||
|
||||
constexpr inline char DirectorySeparator = '/';
|
||||
constexpr inline char DriveSeparator = ':';
|
||||
constexpr inline char Dot = '.';
|
||||
constexpr inline char NullTerminator = '\x00';
|
||||
|
||||
constexpr inline char AlternateDirectorySeparator = '\\';
|
||||
}
|
||||
|
||||
class PathTool {
|
||||
public:
|
||||
static constexpr const char RootPath[] = "/";
|
||||
public:
|
||||
static constexpr inline bool IsAlternateSeparator(char c) {
|
||||
return c == StringTraits::AlternateDirectorySeparator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsSeparator(char c) {
|
||||
return c == StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsAnySeparator(char c) {
|
||||
return IsSeparator(c) || IsAlternateSeparator(c);
|
||||
}
|
||||
|
||||
static constexpr inline bool IsNullTerminator(char c) {
|
||||
return c == StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsDot(char c) {
|
||||
return c == StringTraits::Dot;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsWindowsDriveCharacter(char c) {
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||
}
|
||||
|
||||
static constexpr inline bool IsDriveSeparator(char c) {
|
||||
return c == StringTraits::DriveSeparator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsWindowsAbsolutePath(const char *p) {
|
||||
return IsWindowsDriveCharacter(p[0]) && IsDriveSeparator(p[1]);
|
||||
}
|
||||
|
||||
static constexpr inline bool IsUnc(const char *p) {
|
||||
return (IsSeparator(p[0]) && IsSeparator(p[1])) || (IsAlternateSeparator(p[0]) && IsAlternateSeparator(p[1]));
|
||||
}
|
||||
|
||||
static constexpr inline bool IsCurrentDirectory(const char *p) {
|
||||
return IsDot(p[0]) && (IsSeparator(p[1]) || IsNullTerminator(p[1]));
|
||||
}
|
||||
|
||||
static constexpr inline bool IsParentDirectory(const char *p) {
|
||||
return IsDot(p[0]) && IsDot(p[1]) && (IsSeparator(p[2]) || IsNullTerminator(p[2]));
|
||||
}
|
||||
|
||||
static Result Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved = false);
|
||||
static Result IsNormalized(bool *out, const char *path);
|
||||
|
||||
static bool IsSubPath(const char *lhs, const char *rhs);
|
||||
};
|
||||
|
||||
}
|
|
@ -19,15 +19,59 @@
|
|||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace StringTraits {
|
||||
|
||||
constexpr inline char DirectorySeparator = '/';
|
||||
constexpr inline char DriveSeparator = ':';
|
||||
constexpr inline char Dot = '.';
|
||||
constexpr inline char NullTerminator = '\x00';
|
||||
|
||||
constexpr inline char AlternateDirectorySeparator = '\\';
|
||||
|
||||
}
|
||||
|
||||
/* Windows path utilities. */
|
||||
constexpr inline bool IsWindowsDrive(const char *path) {
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
const char c = path[0];
|
||||
return (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) && path[1] == StringTraits::DriveSeparator;
|
||||
}
|
||||
|
||||
constexpr inline bool IsUnc(const char *path) {
|
||||
return (path[0] == StringTraits::DirectorySeparator && path[1] == StringTraits::DirectorySeparator) ||
|
||||
(path[0] == StringTraits::AlternateDirectorySeparator && path[1] == StringTraits::AlternateDirectorySeparator);
|
||||
}
|
||||
|
||||
constexpr inline s64 GetWindowsPathSkipLength(const char *path) {
|
||||
if (IsWindowsDrive(path)) {
|
||||
return 2;
|
||||
}
|
||||
if (IsUnc(path)) {
|
||||
for (s64 i = 2; path[i] != StringTraits::NullTerminator; ++i) {
|
||||
if (path[i] == '$' || path[i] == ':') {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Path utilities. */
|
||||
inline void Replace(char *dst, size_t dst_size, char old_char, char new_char) {
|
||||
for (char *cur = dst; cur < dst + dst_size && *cur != '\x00'; cur++) {
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
for (char *cur = dst; cur < dst + dst_size && *cur != StringTraits::NullTerminator; ++cur) {
|
||||
if (*cur == old_char) {
|
||||
*cur = new_char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result FspPathPrintf(fssrv::sf::FspPath *dst, const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
inline Result FspPathPrintf(fssrv::sf::FspPath *dst, const char *format, ...) {
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
|
||||
/* Format the path. */
|
||||
std::va_list va_list;
|
||||
va_start(va_list, format);
|
||||
|
@ -43,6 +87,37 @@ namespace ams::fs {
|
|||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len);
|
||||
Result VerifyPath(const char *path, int max_path_len, int max_name_len);
|
||||
|
||||
bool IsSubPath(const char *lhs, const char *rhs);
|
||||
|
||||
/* Path normalization. */
|
||||
class PathNormalizer {
|
||||
public:
|
||||
static constexpr const char RootPath[] = "/";
|
||||
public:
|
||||
static constexpr inline bool IsSeparator(char c) {
|
||||
return c == StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsAnySeparator(char c) {
|
||||
return c == StringTraits::DirectorySeparator || c == StringTraits::AlternateDirectorySeparator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsNullTerminator(char c) {
|
||||
return c == StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
static constexpr inline bool IsCurrentDirectory(const char *p) {
|
||||
return p[0] == StringTraits::Dot && (IsSeparator(p[1]) || IsNullTerminator(p[1]));
|
||||
}
|
||||
|
||||
static constexpr inline bool IsParentDirectory(const char *p) {
|
||||
return p[0] == StringTraits::Dot && p[1] == StringTraits::Dot && (IsSeparator(p[2]) || IsNullTerminator(p[2]));
|
||||
}
|
||||
|
||||
static Result Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved = false, bool has_mount_name = false);
|
||||
static Result IsNormalized(bool *out, const char *path, bool unc_preserved = false, bool has_mount_name = false);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include <stratosphere/fs/fsa/fs_idirectory.hpp>
|
||||
#include <stratosphere/fs/fsa/fs_ifilesystem.hpp>
|
||||
#include <stratosphere/fs/fs_query_range.hpp>
|
||||
#include <stratosphere/fs/fs_path_tool.hpp>
|
||||
#include <stratosphere/fs/fs_path_utils.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
@ -100,12 +99,12 @@ namespace ams::fs {
|
|||
out_path->str[sizeof(out_path->str) - 1] = '\x00';
|
||||
|
||||
/* Replace directory separators. */
|
||||
Replace(out_path->str, sizeof(out_path->str) - 1, StringTraits::AlternateDirectorySeparator, StringTraits::DirectorySeparator);
|
||||
fs::Replace(out_path->str, sizeof(out_path->str) - 1, StringTraits::AlternateDirectorySeparator, StringTraits::DirectorySeparator);
|
||||
|
||||
/* Get lengths. */
|
||||
const auto mount_name_len = PathTool::IsWindowsAbsolutePath(path) ? 2 : 0;
|
||||
const auto rel_path = out_path->str + mount_name_len;
|
||||
const auto max_len = fs::EntryNameLengthMax - mount_name_len;
|
||||
const auto skip_len = fs::GetWindowsPathSkipLength(path);
|
||||
const auto rel_path = out_path->str + skip_len;
|
||||
const auto max_len = fs::EntryNameLengthMax - skip_len;
|
||||
return VerifyPath(rel_path, max_len, max_len);
|
||||
}
|
||||
public:
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace ams::fssrv {
|
|||
Option_AcceptEmpty = BIT(4),
|
||||
};
|
||||
private:
|
||||
using Buffer = std::unique_ptr<char[]>;
|
||||
using Buffer = std::unique_ptr<char[], fs::impl::Deleter>;
|
||||
private:
|
||||
Buffer buffer;
|
||||
const char *path;
|
||||
|
@ -39,8 +39,9 @@ namespace ams::fssrv {
|
|||
private:
|
||||
static Result Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name);
|
||||
public:
|
||||
/* TODO: Remove non-option constructor. */
|
||||
explicit PathNormalizer(const char *p) : buffer(), path(nullptr), result(ResultSuccess()) {
|
||||
this->result = Normalize(&this->path, &this->buffer, p, false, false, false);
|
||||
this->result = Normalize(std::addressof(this->path), std::addressof(this->buffer), p, false, false, false);
|
||||
}
|
||||
|
||||
PathNormalizer(const char *p, u32 option) : buffer(), path(nullptr), result(ResultSuccess()) {
|
||||
|
@ -50,15 +51,15 @@ namespace ams::fssrv {
|
|||
const bool preserve_unc = (option & Option_PreserveUnc);
|
||||
const bool preserve_tail_sep = (option & Option_PreserveTailSeparator);
|
||||
const bool has_mount_name = (option & Option_HasMountName);
|
||||
this->result = Normalize(&this->path, &this->buffer, p, preserve_unc, preserve_tail_sep, has_mount_name);
|
||||
this->result = Normalize(std::addressof(this->path), std::addressof(this->buffer), p, preserve_unc, preserve_tail_sep, has_mount_name);
|
||||
}
|
||||
}
|
||||
|
||||
inline Result GetResult() const {
|
||||
Result GetResult() const {
|
||||
return this->result;
|
||||
}
|
||||
|
||||
inline const char * GetPath() const {
|
||||
const char *GetPath() const {
|
||||
return this->path;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <stratosphere/fssystem/fssystem_external_code.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_partition_file_system.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_partition_file_system_meta.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_path_tool.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_thread_priority_changer.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_aes_ctr_storage.hpp>
|
||||
#include <stratosphere/fssystem/fssystem_aes_xts_storage.hpp>
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../fs/fs_common.hpp"
|
||||
#include "../fs/fs_file.hpp"
|
||||
#include "../fs/fs_directory.hpp"
|
||||
#include "../fs/fs_filesystem.hpp"
|
||||
#include "fssystem_path_tool.hpp"
|
||||
#include <stratosphere/fs/fs_common.hpp>
|
||||
#include <stratosphere/fs/fs_file.hpp>
|
||||
#include <stratosphere/fs/fs_directory.hpp>
|
||||
#include <stratosphere/fs/fs_filesystem.hpp>
|
||||
#include <stratosphere/fs/fs_path_utils.hpp>
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
|
@ -70,7 +70,7 @@ namespace ams::fssystem {
|
|||
}
|
||||
|
||||
/* Restore parent path. */
|
||||
work_path[parent_len] = StringTraits::NullTerminator;
|
||||
work_path[parent_len] = fs::StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
|
@ -91,13 +91,13 @@ namespace ams::fssystem {
|
|||
|
||||
/* Copy root path in, add a / if necessary. */
|
||||
std::memcpy(work_path, root_path, root_path_len);
|
||||
if (!PathTool::IsSeparator(work_path[root_path_len - 1])) {
|
||||
work_path[root_path_len++] = StringTraits::DirectorySeparator;
|
||||
if (!fs::PathNormalizer::IsSeparator(work_path[root_path_len - 1])) {
|
||||
work_path[root_path_len++] = fs::StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
/* Make sure the result path is still valid. */
|
||||
R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
work_path[root_path_len] = StringTraits::NullTerminator;
|
||||
work_path[root_path_len] = fs::StringTraits::NullTerminator;
|
||||
|
||||
return impl::IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent_buf, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ namespace ams::fssystem {
|
|||
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
return IterateDirectoryRecursively(fs, PathTool::RootPath, on_enter_dir, on_exit_dir, on_file);
|
||||
return IterateDirectoryRecursively(fs, fs::PathNormalizer::RootPath, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
/* TODO: Cleanup API */
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace ams::fs {
|
|||
|
||||
fssrv::sf::Path sf_path;
|
||||
if (len > 0) {
|
||||
const bool ending_sep = PathTool::IsSeparator(root_path[len - 1]);
|
||||
const bool ending_sep = PathNormalizer::IsSeparator(root_path[len - 1]);
|
||||
FspPathPrintf(std::addressof(sf_path), "%s%s", root_path, ending_sep ? "" : "/");
|
||||
} else {
|
||||
sf_path.str[0] = '\x00';
|
||||
|
|
569
libraries/libstratosphere/source/fs/fs_path_normalizer.cpp
Normal file
569
libraries/libstratosphere/source/fs/fs_path_normalizer.cpp
Normal file
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 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>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
Result CheckSharedName(const char *path, int len) {
|
||||
if (len == 1) {
|
||||
R_UNLESS(path[0] != StringTraits::Dot, fs::ResultInvalidPathFormat());
|
||||
} else if (len == 2) {
|
||||
R_UNLESS(path[0] != StringTraits::Dot || path[1] != StringTraits::Dot, fs::ResultInvalidPathFormat());
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ParseWindowsPath(const char **out_path, char *out, size_t *out_windows_path_len, bool *out_normalized, const char *path, size_t max_out_size, bool has_mount_name) {
|
||||
/* Prepare to parse. */
|
||||
const char * const path_start = path;
|
||||
if (out_normalized != nullptr) {
|
||||
*out_normalized = true;
|
||||
}
|
||||
|
||||
/* Handle start of path. */
|
||||
bool skipped_mount = false;
|
||||
auto prefix_len = 0;
|
||||
if (has_mount_name) {
|
||||
if (PathNormalizer::IsSeparator(path[0]) && path[1] == StringTraits::AlternateDirectorySeparator && path[2] == StringTraits::AlternateDirectorySeparator) {
|
||||
path += 1;
|
||||
prefix_len = 1;
|
||||
} else {
|
||||
/* Advance past separators. */
|
||||
while (PathNormalizer::IsSeparator(path[0])) {
|
||||
++path;
|
||||
}
|
||||
if (path != path_start) {
|
||||
if (path - path_start == 1 || IsWindowsDrive(path)) {
|
||||
prefix_len = 1;
|
||||
} else {
|
||||
if (path - path_start > 2 && out_normalized != nullptr) {
|
||||
*out_normalized = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
path -= 2;
|
||||
skipped_mount = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (PathNormalizer::IsSeparator(path[0]) && !IsUnc(path)) {
|
||||
path += 1;
|
||||
prefix_len = 1;
|
||||
}
|
||||
|
||||
|
||||
/* Parse the path. */
|
||||
const char *trimmed_path = path_start;
|
||||
if (IsWindowsDrive(path)) {
|
||||
/* Find the first separator. */
|
||||
int i;
|
||||
for (i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) {
|
||||
if (PathNormalizer::IsAnySeparator(path[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
trimmed_path = path + i;
|
||||
|
||||
const size_t win_path_len = trimmed_path - path_start;
|
||||
if (out != nullptr) {
|
||||
R_UNLESS(win_path_len <= max_out_size, fs::ResultTooLongPath());
|
||||
std::memcpy(out, path_start, win_path_len);
|
||||
}
|
||||
*out_path = trimmed_path;
|
||||
*out_windows_path_len = win_path_len;
|
||||
} else if (IsUnc(path)) {
|
||||
if (PathNormalizer::IsAnySeparator(path[2])) {
|
||||
AMS_ASSERT(!has_mount_name);
|
||||
return fs::ResultInvalidPathFormat();
|
||||
}
|
||||
|
||||
int cur_part_ofs = 0;
|
||||
bool needs_sep_fix = false;
|
||||
for (auto i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) {
|
||||
if (cur_part_ofs == 0 && path[i] == StringTraits::AlternateDirectorySeparator) {
|
||||
needs_sep_fix = true;
|
||||
if (out_normalized != nullptr) {
|
||||
*out_normalized = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
if (PathNormalizer::IsAnySeparator(path[i])) {
|
||||
if (path[i] == StringTraits::AlternateDirectorySeparator) {
|
||||
needs_sep_fix = true;
|
||||
}
|
||||
|
||||
if (cur_part_ofs != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_UNLESS(!PathNormalizer::IsSeparator(path[i + 1]), fs::ResultInvalidPathFormat());
|
||||
|
||||
R_TRY(CheckSharedName(path + 2, i - 2));
|
||||
|
||||
cur_part_ofs = i + 1;
|
||||
}
|
||||
if (path[i] == '$' || path[i] == StringTraits::DriveSeparator) {
|
||||
R_UNLESS(cur_part_ofs != 0, fs::ResultInvalidCharacter());
|
||||
R_UNLESS(PathNormalizer::IsAnySeparator(path[i + 1]) || PathNormalizer::IsNullTerminator(path[i + 1]), fs::ResultInvalidPathFormat());
|
||||
trimmed_path = path + i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed_path == path_start) {
|
||||
int tr_part_ofs = 0;
|
||||
int i;
|
||||
for (i = 2; !PathNormalizer::IsNullTerminator(path[i]); ++i) {
|
||||
if (PathNormalizer::IsAnySeparator(path[i])) {
|
||||
if (tr_part_ofs != 0) {
|
||||
R_TRY(CheckSharedName(path + tr_part_ofs, i - tr_part_ofs));
|
||||
trimmed_path = path + i;
|
||||
break;
|
||||
}
|
||||
|
||||
R_UNLESS(!PathNormalizer::IsSeparator(path[i + 1]), fs::ResultInvalidPathFormat());
|
||||
|
||||
R_TRY(CheckSharedName(path + 2, i - 2));
|
||||
|
||||
cur_part_ofs = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tr_part_ofs != 0 && trimmed_path == path_start) {
|
||||
R_TRY(CheckSharedName(path + tr_part_ofs, i - tr_part_ofs));
|
||||
trimmed_path = path + i;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t win_path_len = trimmed_path - path;
|
||||
const bool prepend_sep = prefix_len != 0 || skipped_mount;
|
||||
if (out != nullptr) {
|
||||
R_UNLESS(win_path_len <= max_out_size, fs::ResultTooLongPath());
|
||||
if (prepend_sep) {
|
||||
*(out++) = StringTraits::DirectorySeparator;
|
||||
}
|
||||
std::memcpy(out, path, win_path_len);
|
||||
out[0] = StringTraits::AlternateDirectorySeparator;
|
||||
out[1] = StringTraits::AlternateDirectorySeparator;
|
||||
if (needs_sep_fix) {
|
||||
for (size_t i = 2; i < win_path_len; ++i) {
|
||||
if (PathNormalizer::IsSeparator(out[i])) {
|
||||
out[i] = StringTraits::AlternateDirectorySeparator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*out_path = trimmed_path;
|
||||
*out_windows_path_len = win_path_len + (prepend_sep ? 1 : 0);
|
||||
} else {
|
||||
*out_path = trimmed_path;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SkipWindowsPath(const char **out_path, bool *out_normalized, const char *path, bool has_mount_name) {
|
||||
size_t windows_path_len;
|
||||
return ParseWindowsPath(out_path, nullptr, std::addressof(windows_path_len), out_normalized, path, 0, has_mount_name);
|
||||
}
|
||||
|
||||
Result ParseMountName(const char **out_path, char *out, size_t *out_mount_name_len, const char *path, size_t max_out_size) {
|
||||
/* Decide on a start. */
|
||||
const char *start = PathNormalizer::IsSeparator(path[0]) ? path + 1 : path;
|
||||
|
||||
/* Find the end of the mount name. */
|
||||
const char *cur;
|
||||
for (cur = start; cur < start + MountNameLengthMax + 1; ++cur) {
|
||||
if (*cur == StringTraits::DriveSeparator) {
|
||||
++cur;
|
||||
break;
|
||||
}
|
||||
if (PathNormalizer::IsSeparator(*cur)) {
|
||||
*out_path = path;
|
||||
*out_mount_name_len = 0;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
R_UNLESS(start < cur - 1, fs::ResultInvalidPathFormat());
|
||||
R_UNLESS(cur[-1] == StringTraits::DriveSeparator, fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Check the mount name doesn't contain a dot. */
|
||||
if (cur != start) {
|
||||
for (const char *p = start; p < cur; ++p) {
|
||||
R_UNLESS(*p != StringTraits::Dot, fs::ResultInvalidCharacter());
|
||||
}
|
||||
}
|
||||
|
||||
const size_t mount_name_len = cur - path;
|
||||
if (out != nullptr) {
|
||||
R_UNLESS(mount_name_len <= max_out_size, fs::ResultTooLongPath());
|
||||
std::memcpy(out, path, mount_name_len);
|
||||
}
|
||||
*out_path = cur;
|
||||
*out_mount_name_len = mount_name_len;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SkipMountName(const char **out_path, const char *path) {
|
||||
size_t mount_name_len;
|
||||
return ParseMountName(out_path, nullptr, std::addressof(mount_name_len), path, 0);
|
||||
}
|
||||
|
||||
bool IsParentDirectoryPathReplacementNeeded(const char *path) {
|
||||
if (!PathNormalizer::IsAnySeparator(path[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto i = 0; !PathNormalizer::IsNullTerminator(path[i]); ++i) {
|
||||
if (path[i + 0] == StringTraits::AlternateDirectorySeparator &&
|
||||
path[i + 1] == StringTraits::Dot &&
|
||||
path[i + 2] == StringTraits::Dot &&
|
||||
(PathNormalizer::IsAnySeparator(path[i + 3]) || PathNormalizer::IsNullTerminator(path[i + 3])))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PathNormalizer::IsAnySeparator(path[i + 0]) &&
|
||||
path[i + 1] == StringTraits::Dot &&
|
||||
path[i + 2] == StringTraits::Dot &&
|
||||
path[i + 3] == StringTraits::AlternateDirectorySeparator)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReplaceParentDirectoryPath(char *dst, const char *src) {
|
||||
dst[0] = StringTraits::DirectorySeparator;
|
||||
|
||||
int i = 1;
|
||||
while (!PathNormalizer::IsNullTerminator(src[i])) {
|
||||
if (PathNormalizer::IsAnySeparator(src[i - 1]) &&
|
||||
src[i + 0] == StringTraits::Dot &&
|
||||
src[i + 1] == StringTraits::Dot &&
|
||||
PathNormalizer::IsAnySeparator(src[i + 2]))
|
||||
{
|
||||
dst[i - 1] = StringTraits::DirectorySeparator;
|
||||
dst[i + 0] = StringTraits::Dot;
|
||||
dst[i + 1] = StringTraits::Dot;
|
||||
dst[i - 2] = StringTraits::DirectorySeparator;
|
||||
i += 3;
|
||||
} else {
|
||||
if (src[i - 1] == StringTraits::AlternateDirectorySeparator &&
|
||||
src[i + 0] == StringTraits::Dot &&
|
||||
src[i + 1] == StringTraits::Dot &&
|
||||
PathNormalizer::IsNullTerminator(src[i + 2]))
|
||||
{
|
||||
dst[i - 1] = StringTraits::DirectorySeparator;
|
||||
dst[i + 0] = StringTraits::Dot;
|
||||
dst[i + 1] = StringTraits::Dot;
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
dst[i] = src[i];
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
dst[i] = StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result PathNormalizer::Normalize(char *out, size_t *out_len, const char *path, size_t max_out_size, bool unc_preserved, bool has_mount_name) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(out_len != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
/* If we should, handle the mount name. */
|
||||
size_t prefix_len = 0;
|
||||
if (has_mount_name) {
|
||||
size_t mount_name_len = 0;
|
||||
R_TRY(ParseMountName(std::addressof(path), out, std::addressof(mount_name_len), path, max_out_size));
|
||||
prefix_len += mount_name_len;
|
||||
}
|
||||
|
||||
/* Deal with unc. */
|
||||
bool is_unc_path = false;
|
||||
if (unc_preserved) {
|
||||
const char * const path_start = path;
|
||||
size_t windows_path_len = 0;
|
||||
R_TRY(ParseWindowsPath(std::addressof(path), out + prefix_len, std::addressof(windows_path_len), nullptr, path, max_out_size, has_mount_name));
|
||||
prefix_len += windows_path_len;
|
||||
is_unc_path = path != path_start;
|
||||
}
|
||||
|
||||
/* Paths must start with / */
|
||||
R_UNLESS(prefix_len != 0 || IsSeparator(path[0]), fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Check if parent directory path replacement is needed. */
|
||||
std::unique_ptr<char[], fs::impl::Deleter> replacement_path;
|
||||
if (IsParentDirectoryPathReplacementNeeded(path)) {
|
||||
/* Allocate a buffer to hold the replacement path. */
|
||||
replacement_path = fs::impl::MakeUnique<char[]>(EntryNameLengthMax + 1);
|
||||
R_UNLESS(replacement_path != nullptr, fs::ResultAllocationFailureInNew());
|
||||
|
||||
/* Replace the path. */
|
||||
ReplaceParentDirectoryPath(replacement_path.get(), path);
|
||||
|
||||
/* Set path to be the replacement path. */
|
||||
path = replacement_path.get();
|
||||
}
|
||||
|
||||
bool skip_next_sep = false;
|
||||
size_t i = 0;
|
||||
size_t len = prefix_len;
|
||||
|
||||
while (!IsNullTerminator(path[i])) {
|
||||
if (IsSeparator(path[i])) {
|
||||
/* Swallow separators. */
|
||||
while (IsSeparator(path[++i])) { }
|
||||
if (IsNullTerminator(path[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle skip if needed */
|
||||
if (!skip_next_sep) {
|
||||
if (len + 1 == max_out_size) {
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
*out_len = len;
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
|
||||
out[len++] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
skip_next_sep = false;
|
||||
}
|
||||
|
||||
/* See length of current dir. */
|
||||
size_t dir_len = 0;
|
||||
while (!IsSeparator(path[i + dir_len]) && !IsNullTerminator(path[i + dir_len])) {
|
||||
++dir_len;
|
||||
}
|
||||
|
||||
if (IsCurrentDirectory(path + i)) {
|
||||
skip_next_sep = true;
|
||||
} else if (IsParentDirectory(path + i)) {
|
||||
AMS_ASSERT(IsSeparator(out[len - 1]));
|
||||
if (!is_unc_path) {
|
||||
AMS_ASSERT(IsSeparator(out[prefix_len]));
|
||||
}
|
||||
|
||||
/* Walk up a directory. */
|
||||
if (len == prefix_len + 1) {
|
||||
R_UNLESS(is_unc_path, fs::ResultDirectoryUnobtainable());
|
||||
--len;
|
||||
} else {
|
||||
len -= 2;
|
||||
do {
|
||||
if (IsSeparator(out[len])) {
|
||||
break;
|
||||
}
|
||||
--len;
|
||||
} while (len != prefix_len);
|
||||
}
|
||||
|
||||
if (!is_unc_path) {
|
||||
AMS_ASSERT(IsSeparator(out[prefix_len]));
|
||||
}
|
||||
|
||||
AMS_ASSERT(len < max_out_size);
|
||||
} else {
|
||||
/* Copy, possibly truncating. */
|
||||
if (len + dir_len + 1 <= max_out_size) {
|
||||
for (size_t j = 0; j < dir_len; ++j) {
|
||||
out[len++] = path[i+j];
|
||||
}
|
||||
} else {
|
||||
const size_t copy_len = max_out_size - 1 - len;
|
||||
for (size_t j = 0; j < copy_len; ++j) {
|
||||
out[len++] = path[i+j];
|
||||
}
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
*out_len = len;
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
}
|
||||
|
||||
i += dir_len;
|
||||
}
|
||||
|
||||
if (skip_next_sep) {
|
||||
--len;
|
||||
}
|
||||
|
||||
if (!is_unc_path && len == prefix_len && max_out_size > len) {
|
||||
out[len++] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath());
|
||||
|
||||
/* Null terminate. */
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
*out_len = len;
|
||||
|
||||
/* Assert normalized. */
|
||||
{
|
||||
bool normalized = false;
|
||||
const auto is_norm_result = IsNormalized(std::addressof(normalized), out, unc_preserved, has_mount_name);
|
||||
AMS_ASSERT(R_SUCCEEDED(is_norm_result));
|
||||
AMS_ASSERT(normalized);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PathNormalizer::IsNormalized(bool *out, const char *path, bool unc_preserved, bool has_mount_name) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
/* Save the start of the path. */
|
||||
const char *path_start = path;
|
||||
|
||||
/* If we should, skip the mount name. */
|
||||
if (has_mount_name) {
|
||||
R_TRY(SkipMountName(std::addressof(path), path));
|
||||
R_UNLESS(IsSeparator(*path), fs::ResultInvalidPathFormat());
|
||||
}
|
||||
|
||||
/* If we should, handle unc. */
|
||||
bool is_unc_path = false;
|
||||
if (unc_preserved) {
|
||||
path_start = path;
|
||||
|
||||
/* Skip the windows path. */
|
||||
bool normalized_windows = false;
|
||||
R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(normalized_windows), path, has_mount_name));
|
||||
|
||||
/* If we're not windows-normalized, we're not normalized. */
|
||||
if (!normalized_windows) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Handle the case where we're dealing with a unc path. */
|
||||
if (path != path_start) {
|
||||
is_unc_path = true;
|
||||
if (IsSeparator(path_start[0]) && IsSeparator(path_start[1])) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
if (IsNullTerminator(path[0])) {
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if parent directory path replacement is needed. */
|
||||
if (IsParentDirectoryPathReplacementNeeded(path)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Nintendo uses a state machine here. */
|
||||
enum class PathState {
|
||||
Start,
|
||||
Normal,
|
||||
FirstSeparator,
|
||||
Separator,
|
||||
CurrentDir,
|
||||
ParentDir,
|
||||
};
|
||||
|
||||
PathState state = PathState::Start;
|
||||
for (const char *cur = path; *cur != StringTraits::NullTerminator; ++cur) {
|
||||
const char c = *cur;
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
if (IsSeparator(c)) {
|
||||
state = PathState::FirstSeparator;
|
||||
} else {
|
||||
R_UNLESS(path != path_start, fs::ResultInvalidPathFormat());
|
||||
if (c == StringTraits::Dot) {
|
||||
state = PathState::CurrentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PathState::Normal:
|
||||
if (IsSeparator(c)) {
|
||||
state = PathState::Separator;
|
||||
}
|
||||
break;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Separator:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (c == StringTraits::Dot) {
|
||||
state = PathState::CurrentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (c == StringTraits::Dot) {
|
||||
state = PathState::ParentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::ParentDir:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
return fs::ResultInvalidPathFormat();
|
||||
case PathState::Normal:
|
||||
*out = true;
|
||||
break;
|
||||
case PathState::FirstSeparator:
|
||||
*out = !is_unc_path;
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
case PathState::ParentDir:
|
||||
case PathState::Separator:
|
||||
*out = false;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 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>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
|
||||
Result PathTool::Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved) {
|
||||
/* Paths must start with / */
|
||||
R_UNLESS(IsSeparator(src[0]), fs::ResultInvalidPathFormat());
|
||||
|
||||
bool skip_next_sep = false;
|
||||
size_t i = 0;
|
||||
size_t len = 0;
|
||||
|
||||
while (!IsNullTerminator(src[i])) {
|
||||
if (IsSeparator(src[i])) {
|
||||
/* Swallow separators. */
|
||||
while (IsSeparator(src[++i])) { }
|
||||
if (IsNullTerminator(src[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle skip if needed */
|
||||
if (!skip_next_sep) {
|
||||
if (len + 1 == max_out_size) {
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
|
||||
out[len++] = StringTraits::DirectorySeparator;
|
||||
|
||||
if (unc_preserved && len == 1) {
|
||||
while (len < i) {
|
||||
if (len + 1 == max_out_size) {
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
out[len++] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
}
|
||||
}
|
||||
skip_next_sep = false;
|
||||
}
|
||||
|
||||
/* See length of current dir. */
|
||||
size_t dir_len = 0;
|
||||
while (!IsSeparator(src[i+dir_len]) && !IsNullTerminator(src[i+dir_len])) {
|
||||
dir_len++;
|
||||
}
|
||||
|
||||
if (IsCurrentDirectory(&src[i])) {
|
||||
skip_next_sep = true;
|
||||
} else if (IsParentDirectory(&src[i])) {
|
||||
AMS_ABORT_UNLESS(IsSeparator(out[0]));
|
||||
AMS_ABORT_UNLESS(IsSeparator(out[len - 1]));
|
||||
R_UNLESS(len != 1, fs::ResultDirectoryUnobtainable());
|
||||
|
||||
/* Walk up a directory. */
|
||||
len -= 2;
|
||||
while (!IsSeparator(out[len])) {
|
||||
len--;
|
||||
}
|
||||
} else {
|
||||
/* Copy, possibly truncating. */
|
||||
if (len + dir_len + 1 <= max_out_size) {
|
||||
for (size_t j = 0; j < dir_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
} else {
|
||||
const size_t copy_len = max_out_size - 1 - len;
|
||||
for (size_t j = 0; j < copy_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
}
|
||||
|
||||
i += dir_len;
|
||||
}
|
||||
|
||||
if (skip_next_sep) {
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len == 0 && max_out_size) {
|
||||
out[len++] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath());
|
||||
|
||||
/* Null terminate. */
|
||||
out[len] = StringTraits::NullTerminator;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
|
||||
/* Assert normalized. */
|
||||
bool normalized = false;
|
||||
AMS_ABORT_UNLESS(unc_preserved || (R_SUCCEEDED(IsNormalized(&normalized, out)) && normalized));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PathTool::IsNormalized(bool *out, const char *path) {
|
||||
/* Nintendo uses a state machine here. */
|
||||
enum class PathState {
|
||||
Start,
|
||||
Normal,
|
||||
FirstSeparator,
|
||||
Separator,
|
||||
CurrentDir,
|
||||
ParentDir,
|
||||
WindowsDriveLetter,
|
||||
};
|
||||
|
||||
PathState state = PathState::Start;
|
||||
|
||||
for (const char *cur = path; *cur != StringTraits::NullTerminator; cur++) {
|
||||
const char c = *cur;
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
if (IsWindowsDriveCharacter(c)) {
|
||||
state = PathState::WindowsDriveLetter;
|
||||
} else if (IsSeparator(c)) {
|
||||
state = PathState::FirstSeparator;
|
||||
} else {
|
||||
return fs::ResultInvalidPathFormat();
|
||||
}
|
||||
break;
|
||||
case PathState::Normal:
|
||||
if (IsSeparator(c)) {
|
||||
state = PathState::Separator;
|
||||
}
|
||||
break;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Separator:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (IsDot(c)) {
|
||||
state = PathState::CurrentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (IsDot(c)) {
|
||||
state = PathState::ParentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::ParentDir:
|
||||
if (IsSeparator(c)) {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::WindowsDriveLetter:
|
||||
if (IsDriveSeparator(c)) {
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
return fs::ResultInvalidPathFormat();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
case PathState::WindowsDriveLetter:
|
||||
return fs::ResultInvalidPathFormat();
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Normal:
|
||||
*out = true;
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
case PathState::ParentDir:
|
||||
case PathState::Separator:
|
||||
*out = false;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
bool PathTool::IsSubPath(const char *lhs, const char *rhs) {
|
||||
AMS_ABORT_UNLESS(lhs != nullptr);
|
||||
AMS_ABORT_UNLESS(rhs != nullptr);
|
||||
|
||||
/* Special case certain paths. */
|
||||
if (IsSeparator(lhs[0]) && !IsSeparator(lhs[1]) && IsSeparator(rhs[0]) && IsSeparator(rhs[1])) {
|
||||
return false;
|
||||
}
|
||||
if (IsSeparator(rhs[0]) && !IsSeparator(rhs[1]) && IsSeparator(lhs[0]) && IsSeparator(lhs[1])) {
|
||||
return false;
|
||||
}
|
||||
if (IsSeparator(lhs[0]) && IsNullTerminator(lhs[1]) && IsSeparator(rhs[0]) && !IsNullTerminator(rhs[1])) {
|
||||
return true;
|
||||
}
|
||||
if (IsSeparator(rhs[0]) && IsNullTerminator(rhs[1]) && IsSeparator(lhs[0]) && !IsNullTerminator(lhs[1])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check subpath. */
|
||||
for (size_t i = 0; /* No terminating condition */; i++) {
|
||||
if (IsNullTerminator(lhs[i])) {
|
||||
return IsSeparator(rhs[i]);
|
||||
} else if (IsNullTerminator(rhs[i])) {
|
||||
return IsSeparator(lhs[i]);
|
||||
} else if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,31 +17,114 @@
|
|||
|
||||
namespace ams::fs {
|
||||
|
||||
Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
|
||||
const char *cur = path;
|
||||
size_t name_len = 0;
|
||||
namespace {
|
||||
|
||||
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
|
||||
const char c = *(cur++);
|
||||
class PathVerifier {
|
||||
private:
|
||||
u32 invalid_chars[6];
|
||||
u32 separators[2];
|
||||
public:
|
||||
PathVerifier() {
|
||||
/* Convert all invalid characters. */
|
||||
u32 *dst_invalid = this->invalid_chars;
|
||||
for (const char *cur = ":*?<>|"; *cur != '\x00'; ++cur) {
|
||||
AMS_ASSERT(dst_invalid < std::end(this->invalid_chars));
|
||||
const auto result = util::ConvertCharacterUtf8ToUtf32(dst_invalid, cur);
|
||||
AMS_ASSERT(result == util::CharacterEncodingResult_Success);
|
||||
++dst_invalid;
|
||||
}
|
||||
AMS_ASSERT(dst_invalid == std::end(this->invalid_chars));
|
||||
|
||||
/* If terminated, we're done. */
|
||||
R_SUCCEED_IF(PathTool::IsNullTerminator(c));
|
||||
/* Convert all separators. */
|
||||
u32 *dst_sep = this->separators;
|
||||
for (const char *cur = "/\\"; *cur != '\x00'; ++cur) {
|
||||
AMS_ASSERT(dst_sep < std::end(this->separators));
|
||||
const auto result = util::ConvertCharacterUtf8ToUtf32(dst_sep, cur);
|
||||
AMS_ASSERT(result == util::CharacterEncodingResult_Success);
|
||||
++dst_sep;
|
||||
}
|
||||
AMS_ASSERT(dst_sep == std::end(this->separators));
|
||||
}
|
||||
|
||||
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
||||
/* We should do this. */
|
||||
Result Verify(const char *path, int max_path_len, int max_name_len) const {
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
/* Banned characters: :*?<>| */
|
||||
R_UNLESS((c != ':' && c != '*' && c != '?' && c != '<' && c != '>' && c != '|'), fs::ResultInvalidCharacter());
|
||||
auto cur = path;
|
||||
auto name_len = 0;
|
||||
|
||||
name_len++;
|
||||
/* Check for separator. */
|
||||
if (c == '\\' || c == '/') {
|
||||
name_len = 0;
|
||||
}
|
||||
for (auto path_len = 0; path_len <= max_path_len && name_len <= max_name_len; ++path_len) {
|
||||
/* We're done, if the path is terminated. */
|
||||
R_SUCCEED_IF(*cur == '\x00');
|
||||
|
||||
/* Get the current utf-8 character. */
|
||||
util::CharacterEncodingResult result;
|
||||
char char_buf[4] = {};
|
||||
result = util::PickOutCharacterFromUtf8String(char_buf, std::addressof(cur));
|
||||
R_UNLESS(result == util::CharacterEncodingResult_Success, fs::ResultInvalidCharacter());
|
||||
|
||||
/* Convert the current utf-8 character to utf-32. */
|
||||
u32 path_char = 0;
|
||||
result = util::ConvertCharacterUtf8ToUtf32(std::addressof(path_char), char_buf);
|
||||
R_UNLESS(result == util::CharacterEncodingResult_Success, fs::ResultInvalidCharacter());
|
||||
|
||||
/* Check if the character is invalid. */
|
||||
for (const auto invalid : this->invalid_chars) {
|
||||
R_UNLESS(path_char != invalid, fs::ResultInvalidCharacter());
|
||||
}
|
||||
|
||||
/* Increment name length. */
|
||||
++name_len;
|
||||
|
||||
/* Check for separator. */
|
||||
for (const auto sep : this->separators) {
|
||||
if (path_char == sep) {
|
||||
name_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The path was too long. */
|
||||
return fs::ResultTooLongPath();
|
||||
}
|
||||
};
|
||||
|
||||
PathVerifier g_path_verifier;
|
||||
|
||||
}
|
||||
|
||||
Result VerifyPath(const char *path, int max_path_len, int max_name_len) {
|
||||
return g_path_verifier.Verify(path, max_path_len, max_name_len);
|
||||
}
|
||||
|
||||
bool IsSubPath(const char *lhs, const char *rhs) {
|
||||
AMS_ASSERT(lhs != nullptr);
|
||||
AMS_ASSERT(rhs != nullptr);
|
||||
|
||||
/* Special case certain paths. */
|
||||
if (IsUnc(lhs) && !IsUnc(rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (!IsUnc(lhs) && IsUnc(rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (PathNormalizer::IsSeparator(lhs[0]) && PathNormalizer::IsNullTerminator(lhs[1]) && PathNormalizer::IsSeparator(rhs[0]) && !PathNormalizer::IsNullTerminator(rhs[1])) {
|
||||
return true;
|
||||
}
|
||||
if (PathNormalizer::IsSeparator(rhs[0]) && PathNormalizer::IsNullTerminator(rhs[1]) && PathNormalizer::IsSeparator(lhs[0]) && !PathNormalizer::IsNullTerminator(lhs[1])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fs::ResultTooLongPath();
|
||||
/* Check subpath. */
|
||||
for (size_t i = 0; /* No terminating condition */; i++) {
|
||||
if (PathNormalizer::IsNullTerminator(lhs[i])) {
|
||||
return PathNormalizer::IsSeparator(rhs[i]);
|
||||
} else if (PathNormalizer::IsNullTerminator(rhs[i])) {
|
||||
return PathNormalizer::IsSeparator(lhs[i]);
|
||||
} else if (lhs[i] != rhs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ namespace ams::fs::impl {
|
|||
|
||||
const char *FindMountNameDriveSeparator(const char *path) {
|
||||
for (const char *cur = path; cur < path + MountNameLengthMax + 1; cur++) {
|
||||
if (PathTool::IsDriveSeparator(*cur)) {
|
||||
if (*cur == StringTraits::DriveSeparator) {
|
||||
return cur;
|
||||
} else if (PathTool::IsNullTerminator(*cur)) {
|
||||
} else if (PathNormalizer::IsNullTerminator(*cur)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace ams::fs::impl {
|
|||
|
||||
Result GetMountNameAndSubPath(MountName *out_mount_name, const char **out_sub_path, const char *path) {
|
||||
/* Handle the Host-path case. */
|
||||
if (PathTool::IsWindowsAbsolutePath(path) || PathTool::IsUnc(path)) {
|
||||
if (fs::IsWindowsDrive(path) || fs::IsUnc(path)) {
|
||||
std::strncpy(out_mount_name->str, HostRootFileSystemMountName, MountNameLengthMax);
|
||||
out_mount_name->str[MountNameLengthMax] = '\x00';
|
||||
return ResultSuccess();
|
||||
|
@ -51,8 +51,8 @@ namespace ams::fs::impl {
|
|||
|
||||
/* Ensure the result sub-path is valid. */
|
||||
const char *sub_path = drive_separator + 1;
|
||||
R_UNLESS(!PathTool::IsNullTerminator(sub_path[0]), fs::ResultInvalidMountName());
|
||||
R_UNLESS(PathTool::IsAnySeparator(sub_path[0]), fs::ResultInvalidPathFormat());
|
||||
R_UNLESS(!PathNormalizer::IsNullTerminator(sub_path[0]), fs::ResultInvalidMountName());
|
||||
R_UNLESS(PathNormalizer::IsAnySeparator(sub_path[0]), fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Set output. */
|
||||
std::memcpy(out_mount_name->str, path, len);
|
||||
|
@ -64,17 +64,17 @@ namespace ams::fs::impl {
|
|||
}
|
||||
|
||||
bool IsValidMountName(const char *name) {
|
||||
if (PathTool::IsNullTerminator(*name)) {
|
||||
if (PathNormalizer::IsNullTerminator(name[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PathTool::IsWindowsDriveCharacter(name[0]) && PathTool::IsNullTerminator(name[1])) {
|
||||
if ((('a' <= name[0] && name[0] <= 'z') || ('A' <= name[0] && name[0] <= 'Z')) && PathNormalizer::IsNullTerminator(name[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
for (const char *cur = name; !PathTool::IsNullTerminator(*cur); cur++) {
|
||||
if (PathTool::IsDriveSeparator(*cur) || PathTool::IsSeparator(*cur)) {
|
||||
for (const char *cur = name; !PathNormalizer::IsNullTerminator(*cur); cur++) {
|
||||
if (*cur == StringTraits::DriveSeparator || PathNormalizer::IsSeparator(*cur)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -87,10 +87,6 @@ namespace ams::fs::impl {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool IsWindowsDrive(const char *name) {
|
||||
return PathTool::IsWindowsAbsolutePath(name);
|
||||
}
|
||||
|
||||
bool IsReservedMountName(const char *name) {
|
||||
return name[0] == ReservedMountNamePrefixCharacter;
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ namespace ams::fssrv::impl {
|
|||
R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult());
|
||||
R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult());
|
||||
|
||||
const bool is_subpath = fssystem::PathTool::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||
const bool is_subpath = fs::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||
R_UNLESS(!is_subpath, fs::ResultDirectoryNotRenamable());
|
||||
|
||||
return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||
|
|
|
@ -18,51 +18,36 @@
|
|||
namespace ams::fssrv {
|
||||
|
||||
Result PathNormalizer::Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(out_path != nullptr);
|
||||
AMS_ASSERT(out_buf != nullptr);
|
||||
|
||||
/* Clear output. */
|
||||
*out_path = nullptr;
|
||||
*out_buf = Buffer();
|
||||
|
||||
/* Find start of path. */
|
||||
const char *path_start = path;
|
||||
if (has_mount_name) {
|
||||
while (path_start < path + fs::MountNameLengthMax + 1) {
|
||||
if (fssystem::PathTool::IsNullTerminator(*path_start)) {
|
||||
break;
|
||||
} else if (fssystem::PathTool::IsDriveSeparator(*(path_start++))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
R_UNLESS(path < path_start - 1, fs::ResultInvalidPath());
|
||||
R_UNLESS(fssystem::PathTool::IsDriveSeparator(*(path_start - 1)), fs::ResultInvalidPath());
|
||||
}
|
||||
|
||||
/* Check if we're normalized. */
|
||||
bool normalized = false;
|
||||
R_TRY(fssystem::PathTool::IsNormalized(&normalized, path_start));
|
||||
R_TRY(fs::PathNormalizer::IsNormalized(std::addressof(normalized), path, preserve_unc, has_mount_name));
|
||||
|
||||
if (normalized) {
|
||||
/* If we're already normalized, no allocation is needed. */
|
||||
*out_path = path;
|
||||
} else {
|
||||
/* Allocate a new buffer. */
|
||||
auto buffer = std::make_unique<char[]>(fs::EntryNameLengthMax + 1);
|
||||
auto buffer = fs::impl::MakeUnique<char[]>(fs::EntryNameLengthMax + 1);
|
||||
R_UNLESS(buffer != nullptr, fs::ResultAllocationFailureInPathNormalizer());
|
||||
|
||||
/* Copy in mount name. */
|
||||
const size_t mount_name_len = path_start - path;
|
||||
std::memcpy(buffer.get(), path, mount_name_len);
|
||||
|
||||
/* Generate normalized path. */
|
||||
size_t normalized_len = 0;
|
||||
R_TRY(fssystem::PathTool::Normalize(buffer.get() + mount_name_len, &normalized_len, path_start, fs::EntryNameLengthMax + 1 - mount_name_len, preserve_unc));
|
||||
R_TRY(fs::PathNormalizer::Normalize(buffer.get(), std::addressof(normalized_len), path, fs::EntryNameLengthMax + 1, preserve_unc, has_mount_name));
|
||||
|
||||
/* Preserve the tail separator, if we should. */
|
||||
if (preserve_tail_sep) {
|
||||
if (fssystem::PathTool::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1])) {
|
||||
/* Nintendo doesn't actually validate this. */
|
||||
R_UNLESS(mount_name_len + normalized_len < fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
buffer[mount_name_len + normalized_len] = fssystem::StringTraits::DirectorySeparator;
|
||||
buffer[mount_name_len + normalized_len + 1] = fssystem::StringTraits::NullTerminator;
|
||||
if (fs::PathNormalizer::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1]) && !fs::PathNormalizer::IsSeparator(buffer[normalized_len - 1])) {
|
||||
AMS_ASSERT(normalized_len < fs::EntryNameLengthMax);
|
||||
buffer[normalized_len] = fs::StringTraits::DirectorySeparator;
|
||||
buffer[normalized_len + 1] = fs::StringTraits::NullTerminator;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,10 @@ namespace ams::fssystem {
|
|||
|
||||
DirectoryRedirectionFileSystem::~DirectoryRedirectionFileSystem() {
|
||||
if (this->before_dir != nullptr) {
|
||||
std::free(this->before_dir);
|
||||
fs::impl::Deallocate(this->before_dir, this->before_dir_len);
|
||||
}
|
||||
if (this->after_dir != nullptr) {
|
||||
std::free(this->after_dir);
|
||||
fs::impl::Deallocate(this->after_dir, this->after_dir_len);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,26 +53,26 @@ namespace ams::fssystem {
|
|||
/* Normalize the path. */
|
||||
char normalized_path[fs::EntryNameLengthMax + 2];
|
||||
size_t normalized_path_len;
|
||||
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved()));
|
||||
R_TRY(fs::PathNormalizer::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved(), false));
|
||||
|
||||
/* Ensure terminating '/' */
|
||||
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||
AMS_ABORT_UNLESS(normalized_path_len + 2 <= sizeof(normalized_path));
|
||||
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
|
||||
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
|
||||
if (!fs::PathNormalizer::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||
AMS_ASSERT(normalized_path_len + 2 < sizeof(normalized_path));
|
||||
normalized_path[normalized_path_len] = fs::StringTraits::DirectorySeparator;
|
||||
normalized_path[normalized_path_len + 1] = fs::StringTraits::NullTerminator;
|
||||
|
||||
normalized_path_len++;
|
||||
++normalized_path_len;
|
||||
}
|
||||
|
||||
/* Allocate new path. */
|
||||
const size_t size = normalized_path_len + 1;
|
||||
char *new_dir = static_cast<char *>(std::malloc(size));
|
||||
char *new_dir = static_cast<char *>(fs::impl::Allocate(size));
|
||||
AMS_ABORT_UNLESS(new_dir != nullptr);
|
||||
/* TODO: custom ResultAllocationFailure? */
|
||||
|
||||
/* Copy path in. */
|
||||
std::memcpy(new_dir, normalized_path, normalized_path_len);
|
||||
new_dir[normalized_path_len] = StringTraits::NullTerminator;
|
||||
new_dir[normalized_path_len] = fs::StringTraits::NullTerminator;
|
||||
|
||||
/* Set output. */
|
||||
*out = new_dir;
|
||||
|
@ -82,20 +82,30 @@ namespace ams::fssystem {
|
|||
|
||||
Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) {
|
||||
/* Normalize both directories. */
|
||||
this->GetNormalizedDirectoryPath(&this->before_dir, &this->before_dir_len, before);
|
||||
this->GetNormalizedDirectoryPath(&this->after_dir, &this->after_dir_len, after);
|
||||
this->GetNormalizedDirectoryPath(std::addressof(this->before_dir), std::addressof(this->before_dir_len), before);
|
||||
this->GetNormalizedDirectoryPath(std::addressof(this->after_dir), std::addressof(this->after_dir_len), after);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(relative_path[0] == '/');
|
||||
AMS_ASSERT(this->before_dir_len >= 2);
|
||||
AMS_ASSERT(this->after_dir_len >= 2);
|
||||
AMS_ASSERT(this->after_dir_len <= out_size);
|
||||
AMS_ASSERT(fs::PathNormalizer::IsNullTerminator(this->before_dir[this->before_dir_len - 1]));
|
||||
AMS_ASSERT(fs::PathNormalizer::IsSeparator(this->before_dir[this->before_dir_len - 2]));
|
||||
AMS_ASSERT(fs::PathNormalizer::IsNullTerminator(this->after_dir[this->after_dir_len - 1]));
|
||||
AMS_ASSERT(fs::PathNormalizer::IsSeparator(this->after_dir[this->after_dir_len - 2]));
|
||||
|
||||
/* Normalize the relative path. */
|
||||
char normalized_rel_path[fs::EntryNameLengthMax + 1];
|
||||
size_t normalized_rel_path_len;
|
||||
R_TRY(PathTool::Normalize(normalized_rel_path, &normalized_rel_path_len, relative_path, sizeof(normalized_rel_path), this->IsUncPreserved()));
|
||||
R_TRY(fs::PathNormalizer::Normalize(normalized_rel_path, std::addressof(normalized_rel_path_len), relative_path, sizeof(normalized_rel_path), this->IsUncPreserved(), false));
|
||||
|
||||
const bool is_prefixed = std::memcmp(normalized_rel_path, this->before_dir, this->before_dir_len - 2) == 0 &&
|
||||
(PathTool::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || PathTool::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2]));
|
||||
(fs::PathNormalizer::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || fs::PathNormalizer::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2]));
|
||||
if (is_prefixed) {
|
||||
const size_t before_prefix_len = this->before_dir_len - 2;
|
||||
const size_t after_prefix_len = this->after_dir_len - 2;
|
||||
|
@ -105,12 +115,12 @@ namespace ams::fssystem {
|
|||
/* Copy normalized path. */
|
||||
std::memcpy(out, this->after_dir, after_prefix_len);
|
||||
std::memcpy(out + after_prefix_len, normalized_rel_path + before_prefix_len, normalized_rel_path_len - before_prefix_len);
|
||||
out[final_str_len] = StringTraits::NullTerminator;
|
||||
out[final_str_len] = fs::StringTraits::NullTerminator;
|
||||
} else {
|
||||
/* Path is not prefixed. */
|
||||
R_UNLESS(normalized_rel_path_len + 1 <= out_size, fs::ResultTooLongPath());
|
||||
std::memcpy(out, normalized_rel_path, normalized_rel_path_len);
|
||||
out[normalized_rel_path_len] = StringTraits::NullTerminator;
|
||||
out[normalized_rel_path_len] = fs::StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
|
|
|
@ -156,16 +156,16 @@ namespace ams::fssystem {
|
|||
|
||||
Result DirectorySaveDataFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
R_UNLESS(strnlen(relative_path, fs::EntryNameLengthMax + 1) < fs::EntryNameLengthMax + 1, fs::ResultTooLongPath());
|
||||
R_UNLESS(PathTool::IsSeparator(relative_path[0]), fs::ResultInvalidPath());
|
||||
R_UNLESS(fs::PathNormalizer::IsSeparator(relative_path[0]), fs::ResultInvalidPath());
|
||||
|
||||
/* Copy working directory path. */
|
||||
std::strncpy(out, WorkingDirectoryPath, out_size);
|
||||
out[out_size - 1] = StringTraits::NullTerminator;
|
||||
out[out_size - 1] = fs::StringTraits::NullTerminator;
|
||||
|
||||
/* Normalize it. */
|
||||
constexpr size_t WorkingDirectoryPathLength = sizeof(WorkingDirectoryPath) - 1;
|
||||
size_t normalized_length;
|
||||
return PathTool::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1));
|
||||
return fs::PathNormalizer::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1));
|
||||
}
|
||||
|
||||
void DirectorySaveDataFileSystem::OnWritableFileClose() {
|
||||
|
@ -186,7 +186,7 @@ namespace ams::fssystem {
|
|||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize));
|
||||
|
||||
/* Copy the directory recursively. */
|
||||
R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, PathTool::RootPath, PathTool::RootPath, work_buf.get(), work_buf_size));
|
||||
R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, fs::PathNormalizer::RootPath, fs::PathNormalizer::RootPath, work_buf.get(), work_buf_size));
|
||||
|
||||
return this->Commit();
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ namespace ams::fssystem {
|
|||
dir_entry.type = fs::DirectoryEntryType_File;
|
||||
dir_entry.file_size = this->parent->meta_data->GetEntry(this->cur_index)->size;
|
||||
std::strncpy(dir_entry.name, this->parent->meta_data->GetEntryName(this->cur_index), sizeof(dir_entry.name) - 1);
|
||||
dir_entry.name[sizeof(dir_entry.name) - 1] = StringTraits::NullTerminator;
|
||||
dir_entry.name[sizeof(dir_entry.name) - 1] = fs::StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
*out_count = entry_count;
|
||||
|
@ -349,11 +349,11 @@ namespace ams::fssystem {
|
|||
template <typename MetaType>
|
||||
Result PartitionFileSystemCore<MetaType>::DoGetEntryType(fs::DirectoryEntryType *out, const char *path) {
|
||||
/* Validate preconditions. */
|
||||
R_UNLESS(this->initialized, fs::ResultPreconditionViolation());
|
||||
R_UNLESS(PathTool::IsSeparator(path[0]), fs::ResultInvalidPathFormat());
|
||||
R_UNLESS(this->initialized, fs::ResultPreconditionViolation());
|
||||
R_UNLESS(fs::PathNormalizer::IsSeparator(path[0]), fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Check if the path is for a directory. */
|
||||
if (std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0) {
|
||||
if (std::strncmp(path, fs::PathNormalizer::RootPath, sizeof(fs::PathNormalizer::RootPath)) == 0) {
|
||||
*out = fs::DirectoryEntryType_Directory;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -384,8 +384,8 @@ namespace ams::fssystem {
|
|||
template <typename MetaType>
|
||||
Result PartitionFileSystemCore<MetaType>::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const char *path, fs::OpenDirectoryMode mode) {
|
||||
/* Validate preconditions. */
|
||||
R_UNLESS(this->initialized, fs::ResultPreconditionViolation());
|
||||
R_UNLESS(std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0, fs::ResultPathNotFound());
|
||||
R_UNLESS(this->initialized, fs::ResultPreconditionViolation());
|
||||
R_UNLESS(std::strncmp(path, fs::PathNormalizer::RootPath, sizeof(fs::PathNormalizer::RootPath)) == 0, fs::ResultPathNotFound());
|
||||
|
||||
/* Create and output the partition directory. */
|
||||
std::unique_ptr directory = std::make_unique<PartitionDirectory>(this, mode);
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace ams::fssystem {
|
|||
|
||||
SubDirectoryFileSystem::~SubDirectoryFileSystem() {
|
||||
if (this->base_path != nullptr) {
|
||||
std::free(this->base_path);
|
||||
fs::impl::Deallocate(this->base_path, this->base_path_len);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,25 +44,25 @@ namespace ams::fssystem {
|
|||
/* Normalize the path. */
|
||||
char normalized_path[fs::EntryNameLengthMax + 2];
|
||||
size_t normalized_path_len;
|
||||
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, bp, sizeof(normalized_path), this->IsUncPreserved()));
|
||||
R_TRY(fs::PathNormalizer::Normalize(normalized_path, std::addressof(normalized_path_len), bp, sizeof(normalized_path), this->IsUncPreserved()));
|
||||
|
||||
/* Ensure terminating '/' */
|
||||
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||
AMS_ABORT_UNLESS(normalized_path_len + 2 <= sizeof(normalized_path));
|
||||
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
|
||||
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
|
||||
if (!fs::PathNormalizer::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||
AMS_ASSERT(normalized_path_len + 2 < sizeof(normalized_path));
|
||||
normalized_path[normalized_path_len] = fs::StringTraits::DirectorySeparator;
|
||||
normalized_path[normalized_path_len + 1] = fs::StringTraits::NullTerminator;
|
||||
|
||||
normalized_path_len++;
|
||||
++normalized_path_len;
|
||||
}
|
||||
|
||||
/* Allocate new path. */
|
||||
this->base_path_len = normalized_path_len + 1;
|
||||
this->base_path = static_cast<char *>(std::malloc(this->base_path_len));
|
||||
this->base_path = static_cast<char *>(fs::impl::Allocate(this->base_path_len));
|
||||
R_UNLESS(this->base_path != nullptr, fs::ResultAllocationFailureInSubDirectoryFileSystem());
|
||||
|
||||
/* Copy path in. */
|
||||
std::memcpy(this->base_path, normalized_path, normalized_path_len);
|
||||
this->base_path[normalized_path_len] = StringTraits::NullTerminator;
|
||||
this->base_path[normalized_path_len] = fs::StringTraits::NullTerminator;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ namespace ams::fssystem {
|
|||
/* Normalize it. */
|
||||
const size_t prefix_size = this->base_path_len - 2;
|
||||
size_t normalized_len;
|
||||
return PathTool::Normalize(out + prefix_size, &normalized_len, relative_path, out_size - prefix_size, this->IsUncPreserved());
|
||||
return fs::PathNormalizer::Normalize(out + prefix_size, std::addressof(normalized_len), relative_path, out_size - prefix_size, this->IsUncPreserved(), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,15 +31,15 @@ namespace ams::fssystem {
|
|||
/* Normalize the path. */
|
||||
char normalized_path[fs::EntryNameLengthMax + 1];
|
||||
size_t normalized_path_len;
|
||||
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, path, sizeof(normalized_path)));
|
||||
R_TRY(fs::PathNormalizer::Normalize(normalized_path, std::addressof(normalized_path_len), path, sizeof(normalized_path)));
|
||||
|
||||
/* Repeatedly call CreateDirectory on each directory leading to the target. */
|
||||
for (size_t i = 1; i < normalized_path_len; i++) {
|
||||
/* If we detect a separator, create the directory. */
|
||||
if (PathTool::IsSeparator(normalized_path[i])) {
|
||||
normalized_path[i] = StringTraits::NullTerminator;
|
||||
if (fs::PathNormalizer::IsSeparator(normalized_path[i])) {
|
||||
normalized_path[i] = fs::StringTraits::NullTerminator;
|
||||
R_TRY(EnsureDirectory(fs, normalized_path));
|
||||
normalized_path[i] = StringTraits::DirectorySeparator;
|
||||
normalized_path[i] = fs::StringTraits::DirectorySeparator;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,10 +120,10 @@ namespace ams::fssystem {
|
|||
|
||||
/* Find previous separator, add null terminator */
|
||||
char *cur = &dst_path_buf[len - 2];
|
||||
while (!PathTool::IsSeparator(*cur) && cur > dst_path_buf) {
|
||||
while (!fs::PathNormalizer::IsSeparator(*cur) && cur > dst_path_buf) {
|
||||
cur--;
|
||||
}
|
||||
cur[1] = StringTraits::NullTerminator;
|
||||
cur[1] = fs::StringTraits::NullTerminator;
|
||||
|
||||
return ResultSuccess();
|
||||
},
|
||||
|
|
|
@ -40,5 +40,6 @@
|
|||
#include <vapours/util/util_overlap.hpp>
|
||||
#include <vapours/util/util_string_util.hpp>
|
||||
#include <vapours/util/util_variadic.hpp>
|
||||
#include <vapours/util/util_character_encoding.hpp>
|
||||
#include <vapours/util/util_format_string.hpp>
|
||||
#include <vapours/util/util_range.hpp>
|
||||
|
|
|
@ -13,14 +13,21 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../fs/fs_common.hpp"
|
||||
#include "../fs/fs_path_tool.hpp"
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
|
||||
namespace ams::fssystem {
|
||||
namespace ams::util {
|
||||
|
||||
namespace StringTraits = ::ams::fs::StringTraits;
|
||||
enum CharacterEncodingResult {
|
||||
CharacterEncodingResult_Success = 0,
|
||||
CharacterEncodingResult_InsufficientLength = 1,
|
||||
CharacterEncodingResult_InvalidFormat = 2,
|
||||
};
|
||||
|
||||
using PathTool = ::ams::fs::PathTool;
|
||||
CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32 *dst, const char *src);
|
||||
|
||||
CharacterEncodingResult PickOutCharacterFromUtf8String(char *dst, const char **str);
|
||||
|
||||
}
|
154
libraries/libvapours/source/util/util_character_encoding.cpp
Normal file
154
libraries/libvapours/source/util/util_character_encoding.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 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 <vapours.hpp>
|
||||
|
||||
namespace ams::util {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline int8_t Utf8NBytesInnerTable[0x100 + 1] = {
|
||||
-1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8,
|
||||
};
|
||||
|
||||
constexpr inline const char * const Utf8NBytesTable = static_cast<const char *>(static_cast<const void *>(Utf8NBytesInnerTable + 1));
|
||||
|
||||
}
|
||||
|
||||
CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32 *dst, const char *src) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
AMS_ASSERT(src != nullptr);
|
||||
|
||||
/* Perform the conversion. */
|
||||
const unsigned char *p = reinterpret_cast<const unsigned char *>(src);
|
||||
switch (Utf8NBytesTable[p[0]]) {
|
||||
case 1:
|
||||
*dst = static_cast<u32>(p[0]);
|
||||
return CharacterEncodingResult_Success;
|
||||
case 2:
|
||||
if ((p[0] & 0x1E) != 0) {
|
||||
if (Utf8NBytesTable[p[1]] == 0) {
|
||||
*dst = (static_cast<uint32_t>(p[0] & 0x1F) << 6) | (static_cast<uint32_t>(p[1] & 0x3F) << 0);
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0) {
|
||||
const u32 c = (static_cast<uint32_t>(p[0] & 0xF) << 12) | (static_cast<uint32_t>(p[1] & 0x3F) << 6) | (static_cast<uint32_t>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0 && Utf8NBytesTable[p[3]] == 0) {
|
||||
const u32 c = (static_cast<uint32_t>(p[0] & 0x7) << 18) | (static_cast<uint32_t>(p[1] & 0x3F) << 12) | (static_cast<uint32_t>(p[2] & 0x3F) << 6) | (static_cast<uint32_t>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We failed to convert. */
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
CharacterEncodingResult PickOutCharacterFromUtf8String(char *dst, const char **str) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
AMS_ASSERT(str != nullptr);
|
||||
AMS_ASSERT(*str != nullptr);
|
||||
|
||||
/* Clear the output. */
|
||||
dst[0] = 0;
|
||||
dst[1] = 0;
|
||||
dst[2] = 0;
|
||||
dst[3] = 0;
|
||||
|
||||
/* Perform the conversion. */
|
||||
const unsigned char *p = reinterpret_cast<const unsigned char *>(*str);
|
||||
u32 c = *p;
|
||||
switch (Utf8NBytesTable[c]) {
|
||||
case 1:
|
||||
dst[0] = (*str)[0];
|
||||
++(*str);
|
||||
break;
|
||||
case 2:
|
||||
if ((p[0] & 0x1E) != 0) {
|
||||
if (Utf8NBytesTable[p[1]] == 0) {
|
||||
c = (static_cast<uint32_t>(p[0] & 0x1F) << 6) | (static_cast<uint32_t>(p[1] & 0x3F) << 0);
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
(*str) += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 3:
|
||||
if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0) {
|
||||
c = (static_cast<uint32_t>(p[0] & 0xF) << 12) | (static_cast<uint32_t>(p[1] & 0x3F) << 6) | (static_cast<uint32_t>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
(*str) += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (Utf8NBytesTable[p[1]] == 0 && Utf8NBytesTable[p[2]] == 0 && Utf8NBytesTable[p[3]] == 0) {
|
||||
c = (static_cast<uint32_t>(p[0] & 0x7) << 18) | (static_cast<uint32_t>(p[1] & 0x3F) << 12) | (static_cast<uint32_t>(p[2] & 0x3F) << 6) | (static_cast<uint32_t>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
dst[3] = (*str)[3];
|
||||
(*str) += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue