Rewrite vfs::get and vfs::mount

Preprocess . and .. correctly
Don't use recursive locking
Also use std::string_view
Fix format system for std::string and std::string_view
Fix fmt::merge for std::string_view
This commit is contained in:
Nekotekina 2018-09-11 19:02:19 +03:00
parent 16dcbe8c74
commit e8b5555630
9 changed files with 346 additions and 151 deletions

View File

@ -513,6 +513,13 @@ bool fs::create_path(const std::string& path)
bool fs::remove_dir(const std::string& path)
{
if (path.empty())
{
// Don't allow removing empty path (TODO)
g_tls_error = fs::error::noent;
return false;
}
if (auto device = get_virtual_device(path))
{
return device->remove_dir(path);
@ -539,6 +546,13 @@ bool fs::remove_dir(const std::string& path)
bool fs::rename(const std::string& from, const std::string& to, bool overwrite)
{
if (from.empty() || to.empty())
{
// Don't allow opening empty path (TODO)
g_tls_error = fs::error::noent;
return false;
}
const auto device = get_virtual_device(from);
if (device != get_virtual_device(to))
@ -786,6 +800,13 @@ void fs::file::xfail() const
fs::file::file(const std::string& path, bs_t<open_mode> mode)
{
if (path.empty())
{
// Don't allow opening empty path (TODO)
g_tls_error = fs::error::noent;
return;
}
if (auto device = get_virtual_device(path))
{
if (auto&& _file = device->open(path, mode))
@ -1176,6 +1197,13 @@ void fs::dir::xnull() const
bool fs::dir::open(const std::string& path)
{
if (path.empty())
{
// Don't allow opening empty path (TODO)
g_tls_error = fs::error::noent;
return false;
}
if (auto device = get_virtual_device(path))
{
if (auto&& _dir = device->open_dir(path))

View File

@ -4,6 +4,7 @@
#include "cfmt.h"
#include <algorithm>
#include <string_view>
#ifdef _WIN32
#include <Windows.h>
@ -73,32 +74,31 @@ void fmt_class_string<fmt::base57>::format(std::string& out, u64 arg)
void fmt_class_string<const void*>::format(std::string& out, u64 arg)
{
if (arg)
{
fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg)));
}
else
{
out += "(NULL)";
}
fmt::append(out, "%p", arg);
}
void fmt_class_string<const char*>::format(std::string& out, u64 arg)
{
if (arg)
{
out += reinterpret_cast<const char*>(static_cast<std::uintptr_t>(arg));
out += reinterpret_cast<const char*>(arg);
}
else
{
out += "(NULL)";
out += "(NULLSTR)";
}
}
template <>
void fmt_class_string<std::string>::format(std::string& out, u64 arg)
{
out += get_object(arg).c_str(); // TODO?
out += get_object(arg);
}
template <>
void fmt_class_string<std::string_view>::format(std::string& out, u64 arg)
{
out += get_object(arg);
}
template <>

View File

@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include <functional>
#include <string_view>
// Copy null-terminated string from std::string to char array with truncation
template <std::size_t N>
@ -100,10 +101,10 @@ namespace fmt
auto end = source.end();
for (--end; it != end; ++it)
{
result += *it + separator;
result += std::string{*it} + separator;
}
return result + source.back();
return result + std::string{source.back()};
}
template <typename T>

View File

@ -198,7 +198,13 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
if (!path[0])
return CELL_ENOENT;
const std::string& local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
@ -207,7 +213,7 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
// TODO: other checks for path
if (local_path == "/" || fs::is_dir(local_path))
if (fs::is_dir(local_path))
{
return {CELL_EISDIR, path};
}
@ -462,7 +468,14 @@ error_code sys_fs_opendir(vm::cptr<char> path, vm::ptr<u32> fd)
if (!path[0])
return CELL_ENOENT;
const std::string& local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
// TODO: open root
return {CELL_EPERM, path};
}
if (local_path.empty())
{
@ -471,11 +484,6 @@ error_code sys_fs_opendir(vm::cptr<char> path, vm::ptr<u32> fd)
// TODO: other checks for path
if (local_path == "/")
{
return {CELL_EPERM, path};
}
if (fs::is_file(local_path))
{
return {CELL_ENOTDIR, path};
@ -559,7 +567,14 @@ error_code sys_fs_stat(vm::cptr<char> path, vm::ptr<CellFsStat> sb)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
*sb = {CELL_FS_S_IFDIR | 0444};
return CELL_OK;
}
if (local_path.empty())
{
@ -568,12 +583,6 @@ error_code sys_fs_stat(vm::cptr<char> path, vm::ptr<CellFsStat> sb)
fs::stat_t info{};
if (local_path == "/")
{
sb->mode = CELL_FS_S_IFDIR | 0444;
return CELL_OK;
}
if (!fs::stat(local_path, info))
{
switch (auto error = fs::g_tls_error)
@ -671,18 +680,19 @@ error_code sys_fs_mkdir(vm::cptr<char> path, s32 mode)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EEXIST, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EEXIST, path};
}
if (!fs::create_path(local_path))
{
switch (auto error = fs::g_tls_error)
@ -703,19 +713,22 @@ error_code sys_fs_rename(vm::cptr<char> from, vm::cptr<char> to)
{
sys_fs.warning("sys_fs_rename(from=%s, to=%s)", from, to);
const std::string local_from = vfs::get(from.get_ptr());
const std::string local_to = vfs::get(to.get_ptr());
const std::string_view vfrom = from.get_ptr();
const std::string local_from = vfs::get(vfrom);
const std::string_view vto = to.get_ptr();
const std::string local_to = vfs::get(vto);
if (vfrom.find_first_not_of('/') == -1 || vto.find_first_not_of('/') == -1)
{
return CELL_EPERM;
}
if (local_from.empty() || local_to.empty())
{
return CELL_ENOTMOUNTED;
}
if (local_to == "/" || local_from == "/")
{
return CELL_EPERM;
}
if (!fs::rename(local_from, local_to, false))
{
switch (auto error = fs::g_tls_error)
@ -742,18 +755,19 @@ error_code sys_fs_rmdir(vm::cptr<char> path)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EPERM, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EPERM, path};
}
if (!fs::remove_dir(local_path))
{
switch (auto error = fs::g_tls_error)
@ -780,18 +794,19 @@ error_code sys_fs_unlink(vm::cptr<char> path)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EISDIR, path};
}
if (!fs::remove_file(local_path))
{
switch (auto error = fs::g_tls_error)
@ -922,16 +937,17 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr<void> _arg, u32 _size)
{
const auto arg = vm::static_ptr_cast<lv2_file_c0000002>(_arg);
const std::string local_path = vfs::get(arg->path.get_ptr());
const std::string_view vpath = arg->path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EPERM, vpath};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, arg->path};
}
if (local_path == "/")
{
return {CELL_EPERM, arg->path};
return {CELL_ENOTMOUNTED, vpath};
}
fs::device_stat info;
@ -939,7 +955,7 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr<void> _arg, u32 _size)
{
switch (auto error = fs::g_tls_error)
{
case fs::error::noent: return {CELL_ENOENT, arg->path};
case fs::error::noent: return {CELL_ENOENT, vpath};
default: sys_fs.error("sys_fs_fcntl(0xc0000002): unknown error %s", error);
}
@ -1300,18 +1316,19 @@ error_code sys_fs_truncate(vm::cptr<char> path, u64 size)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EISDIR, path};
}
if (!fs::truncate_file(local_path, size))
{
switch (auto error = fs::g_tls_error)
@ -1398,18 +1415,19 @@ error_code sys_fs_disk_free(vm::cptr<char> path, vm::ptr<u64> total_free, vm::pt
if (!path[0])
return CELL_EINVAL;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EPERM, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EPERM, path};
}
fs::device_stat info;
if (!fs::statfs(local_path, info))
{
@ -1439,18 +1457,19 @@ error_code sys_fs_utime(vm::cptr<char> path, vm::cptr<CellFsUtimbuf> timep)
if (!path[0])
return CELL_ENOENT;
const std::string local_path = vfs::get(path.get_ptr());
const std::string_view vpath = path.get_ptr();
const std::string local_path = vfs::get(vpath);
if (vpath.find_first_not_of('/') == -1)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
if (local_path == "/")
{
return {CELL_EISDIR, path};
}
if (!fs::utime(local_path, timep->actime, timep->modtime))
{
switch (auto error = fs::g_tls_error)

View File

@ -708,13 +708,12 @@ void Emulator::Load(bool add_only)
std::string bdvd_dir = g_cfg.vfs.dev_bdvd;
// Mount default relative path to non-existent directory
vfs::mount("", fs::get_config_dir() + "delete_this_dir/");
vfs::mount("dev_hdd0", fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", emu_dir));
vfs::mount("dev_hdd1", fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", emu_dir));
vfs::mount("dev_flash", g_cfg.vfs.get_dev_flash());
vfs::mount("dev_usb", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("dev_usb000", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("app_home", home_dir.empty() ? elf_dir + '/' : fmt::replace_all(home_dir, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_hdd0", fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_hdd1", fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_flash", g_cfg.vfs.get_dev_flash());
vfs::mount("/dev_usb", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("/dev_usb000", fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir));
vfs::mount("/app_home", home_dir.empty() ? elf_dir + '/' : fmt::replace_all(home_dir, "$(EmulatorDir)", emu_dir));
// Special boot mode (directory scan)
if (fs::is_dir(m_path))
@ -903,7 +902,7 @@ void Emulator::Load(bool add_only)
{
fs::file sfb_file;
vfs::mount("dev_bdvd", bdvd_dir);
vfs::mount("/dev_bdvd", bdvd_dir);
LOG_NOTICE(LOADER, "Disc: %s", vfs::get("/dev_bdvd"));
if (!sfb_file.open(vfs::get("/dev_bdvd/PS3_DISC.SFB")) || sfb_file.size() < 4 || sfb_file.read<u32>() != ".SFB"_u32)
@ -964,7 +963,7 @@ void Emulator::Load(bool add_only)
}
else if (m_cat == "DG" && from_hdd0_game)
{
vfs::mount("dev_bdvd/PS3_GAME", hdd0_game + m_path.substr(hdd0_game.size(), 10));
vfs::mount("/dev_bdvd/PS3_GAME", hdd0_game + m_path.substr(hdd0_game.size(), 10));
LOG_NOTICE(LOADER, "Game: %s", vfs::get("/dev_bdvd/PS3_GAME"));
}
else if (disc.empty())
@ -975,7 +974,7 @@ void Emulator::Load(bool add_only)
else
{
bdvd_dir = disc;
vfs::mount("dev_bdvd", bdvd_dir);
vfs::mount("/dev_bdvd", bdvd_dir);
LOG_NOTICE(LOADER, "Disk: %s", vfs::get("/dev_bdvd"));
}
@ -1062,10 +1061,10 @@ void Emulator::Load(bool add_only)
return m_path = hdd0_boot, Load();
}
// Mount /host_root/ if necessary
// Mount /host_root/ if necessary (special value)
if (g_cfg.vfs.host_root)
{
vfs::mount("host_root", {});
vfs::mount("/host_root", "/");
}
// Open SELF or ELF

View File

@ -2,88 +2,234 @@
#include "IdManager.h"
#include "VFS.h"
#include <regex>
#include "Utilities/mutex.h"
#include "Utilities/StrUtil.h"
struct vfs_directory
{
// Real path (empty if root or not exists)
std::string path;
// Virtual subdirectories (vector because only vector allows incomplete types)
std::vector<std::pair<std::string, vfs_directory>> dirs;
};
struct vfs_manager
{
shared_mutex mutex;
// Device name -> Real path
std::unordered_map<std::string, std::string> mounted;
// VFS root
vfs_directory root;
};
const std::regex s_regex_ps3("^/+(.*?)(?:$|/)(.*)", std::regex::optimize);
bool vfs::mount(const std::string& dev_name, const std::string& path)
bool vfs::mount(std::string_view vpath, std::string_view path)
{
const auto table = fxm::get_always<vfs_manager>();
safe_writer_lock lock(table->mutex);
std::lock_guard lock(table->mutex);
return table->mounted.emplace(dev_name, path).second;
if (vpath.empty())
{
// Empty relative path, should set relative path base; unsupported
return false;
}
for (std::vector<vfs_directory*> list{&table->root};;)
{
// Skip one or more '/'
const auto pos = vpath.find_first_not_of('/');
if (pos == 0)
{
// Mounting relative path is not supported
return false;
}
if (pos == -1)
{
// Mounting completed
list.back()->path = path;
return true;
}
// Get fragment name
const auto name = vpath.substr(pos, vpath.find_first_of('/', pos) - pos);
vpath.remove_prefix(name.size() + pos);
if (name == ".")
{
// Keep current
continue;
}
if (name == "..")
{
// Root parent is root
if (list.size() == 1)
{
continue;
}
// Go back one level
list.resize(list.size() - 1);
continue;
}
// Find or add
const auto last = list.back();
for (auto& dir : last->dirs)
{
if (dir.first == name)
{
list.push_back(&dir.second);
break;
}
}
if (last == list.back())
{
// Add new entry
list.push_back(&last->dirs.emplace_back(name, vfs_directory{}).second);
}
}
}
std::string vfs::get(const std::string& vpath, const std::string* prev, std::size_t pos)
std::string vfs::get(std::string_view vpath, std::vector<std::string>* out_dir)
{
const auto table = fxm::get_always<vfs_manager>();
safe_reader_lock lock(table->mutex);
reader_lock lock(table->mutex);
std::smatch match;
// Resulting path fragments: decoded ones
std::vector<std::string_view> result;
result.reserve(vpath.size() / 2);
if (!std::regex_match(vpath.begin() + pos, vpath.end(), match, s_regex_ps3))
// Mounted path
std::string_view result_base;
if (vpath.empty())
{
const auto found = table->mounted.find("");
// Empty relative path (reuse further return)
vpath = ".";
}
if (found == table->mounted.end())
for (std::vector<const vfs_directory*> list{&table->root};;)
{
// Skip one or more '/'
const auto pos = vpath.find_first_not_of('/');
if (pos == 0)
{
LOG_WARNING(GENERAL, "vfs::get(): no default directory: %s", vpath);
return {};
// Relative path: point to non-existent location
return fs::get_config_dir() + "delete_this_dir.../delete_this...";
}
return found->second + vfs::escape(vpath);
}
if (match.length(1) + pos == 0)
{
return "/";
}
std::string dev;
if (prev)
{
dev += *prev;
dev += '/';
}
dev += match.str(1);
const auto found = table->mounted.find(dev);
if (found == table->mounted.end())
{
if (match.length(2))
if (pos == -1)
{
return vfs::get(vpath, &dev, pos + match.position(1) + match.length(1));
// Absolute path: finalize
for (auto it = list.rbegin(), rend = list.rend(); it != rend; it++)
{
if (auto* dir = *it; dir && !dir->path.empty())
{
// Save latest valid mount path
result_base = dir->path;
// Erase unnecessary path fragments
result.erase(result.begin(), result.begin() + (std::distance(it, rend) - 1));
// Extract mounted subdirectories (TODO)
if (out_dir)
{
for (auto& pair : dir->dirs)
{
if (!pair.second.path.empty())
{
out_dir->emplace_back(pair.first);
}
}
}
break;
}
}
if (!vpath.empty())
{
// Finalize path with '/'
result.emplace_back("");
}
break;
}
LOG_WARNING(GENERAL, "vfs::get(): device not found: %s", vpath);
// Get fragment name
const auto name = vpath.substr(pos, vpath.find_first_of('/', pos) - pos);
vpath.remove_prefix(name.size() + pos);
// Process special directories
if (name == ".")
{
// Keep current
continue;
}
if (name == "..")
{
// Root parent is root
if (list.size() == 1)
{
continue;
}
// Go back one level
list.resize(list.size() - 1);
result.resize(result.size() - 1);
continue;
}
const auto last = list.back();
list.push_back(nullptr);
result.push_back(name);
if (!last)
{
continue;
}
for (auto& dir : last->dirs)
{
if (dir.first == name)
{
list.back() = &dir.second;
break;
}
}
if (last->path == "/")
{
if (vpath.empty())
{
return {};
}
// Handle /host_root (not escaped, not processed)
return std::string{vpath.substr(1)};
}
}
if (result_base.empty())
{
// Not mounted
return {};
}
if (found->second.empty())
{
// Don't escape /host_root (TODO)
return match.str(2);
}
// Escape and concatenate
return found->second + vfs::escape(match.str(2));
// Escape and merge path fragments
return std::string{result_base} + vfs::escape(fmt::merge(result, "/"));
}
std::string vfs::escape(const std::string& path)
std::string vfs::escape(std::string_view path)
{
std::string result;
result.reserve(path.size());
@ -175,7 +321,7 @@ std::string vfs::escape(const std::string& path)
return result;
}
std::string vfs::unescape(const std::string& path)
std::string vfs::unescape(std::string_view path)
{
std::string result;
result.reserve(path.size());

View File

@ -1,18 +1,20 @@
#pragma once
#include <vector>
#include <string>
#include <string_view>
namespace vfs
{
// Mount VFS device
bool mount(const std::string& dev_name, const std::string& path);
bool mount(std::string_view vpath, std::string_view path);
// Convert VFS path to fs path
std::string get(const std::string& vpath, const std::string* = nullptr, std::size_t = 0);
// Convert VFS path to fs path, optionally listing directories mounted in it
std::string get(std::string_view vpath, std::vector<std::string>* out_dir = nullptr);
// Escape VFS path by replacing non-portable characters with surrogates
std::string escape(const std::string& path);
std::string escape(std::string_view path);
// Invert escape operation
std::string unescape(const std::string& path);
std::string unescape(std::string_view path);
}

View File

@ -55,8 +55,7 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr<gui_settings> gui_s
m_show_platinum_trophies = m_gui_settings->GetValue(gui::tr_show_platinum).toBool();
// HACK: dev_hdd0 must be mounted for vfs to work for loading trophies.
vfs::mount("dev_hdd0", Emu.GetHddDir());
vfs::mount("/dev_hdd0", Emu.GetHddDir());
// Get the currently selected user's trophy path.
m_trophy_dir = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/";

View File

@ -43,5 +43,6 @@ namespace std { inline namespace literals { inline namespace chrono_literals {}}
#include <functional>
#include <unordered_map>
#include <algorithm>
#include <string_view>
using namespace std::literals;