sys_fs: Reimplement path analysis

This commit is contained in:
Elad 2026-04-15 13:34:14 +03:00
parent b729772041
commit 48acbbe4f5
2 changed files with 129 additions and 60 deletions

View File

@ -14,7 +14,6 @@
#include "Emu/system_utils.hpp"
#include "Emu/Cell/lv2/sys_process.h"
#include <filesystem>
#include <span>
#include <shared_mutex>
@ -93,15 +92,22 @@ void fmt_class_string<lv2_dir>::format(std::string& out, u64 arg)
bool has_fs_write_rights(std::string_view vpath)
{
// VSH has access to everything
if (g_ps3_process_info.has_root_perm())
return true;
const bool has_root_perm = g_ps3_process_info.has_root_perm();
const auto norm_vpath = lv2_fs_object::get_normalized_path(vpath);
const auto parent_dir = fs::get_parent_dir_view(norm_vpath);
const auto parent_dir = fs::get_parent_dir_view(vpath);
const auto [dev_root, trail] = lv2_fs_object::get_path_root_and_trail(parent_dir);
// This is not exhaustive, PS3 has a unix filesystem with rights for each directory and files
// This is mostly meant to protect against games doing insane things(ie NPUB30003 => NPUB30008)
if (parent_dir == "/dev_hdd0" || parent_dir == "/dev_hdd0/game")
// This is mostly meant to protect against games doing insane things (ie NPUB30003 => NPUB30008)
if (dev_root == "dev_hdd0"sv && (trail.empty() || trail == "game"sv))
return has_root_perm;
// This is read-only for games
if (dev_root.starts_with("dev_flash"sv))
return has_root_perm;
// Technically should not reach here, but handle it anyways
if (dev_root == "dev_bdvd"sv || dev_root == "dev_ps2disc"sv || dev_root.empty())
return false;
return true;
@ -205,27 +211,29 @@ bool lv2_fs_mount_info_map::remove(std::string_view path)
const lv2_fs_mount_info& lv2_fs_mount_info_map::lookup(std::string_view path, bool no_cell_fs_path, std::string* mount_path) const
{
if (path.starts_with("/"sv))
const auto [dev_root, trail] = lv2_fs_object::get_path_root_and_trail(path);
if (dev_root.empty())
{
if (trail.empty())
{
return map.find("/")->second;
}
return g_mi_sys_not_found;
}
if (const auto iterator = map.find("/" + std::string{dev_root}); iterator != map.end())
{
constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv;
const std::string normalized_path = lv2_fs_object::get_normalized_path(path);
std::string_view parent_dir;
u32 parent_level = 0;
do
{
parent_dir = fs::get_parent_dir_view(normalized_path, parent_level++);
if (const auto iterator = map.find(parent_dir); iterator != map.end())
{
if (iterator->second == &g_mp_sys_dev_root && parent_level > 1)
break;
if (no_cell_fs_path && iterator->second.device.starts_with(cell_fs_path))
return lookup(iterator->second.device.substr(cell_fs_path.size()), no_cell_fs_path, mount_path); // Recursively look up the parent mount info
if (mount_path)
*mount_path = iterator->first;
return iterator->second;
}
} while (parent_dir.length() > 1); // Exit the loop when parent_dir == "/" or empty
if (no_cell_fs_path && iterator->second.device.starts_with(cell_fs_path))
return lookup(iterator->second.device.substr(cell_fs_path.size()), no_cell_fs_path, mount_path); // Recursively look up the parent mount info
if (mount_path)
*mount_path = iterator->first;
return iterator->second;
}
return g_mi_sys_not_found;
@ -287,36 +295,89 @@ bool lv2_fs_mount_info_map::vfs_unmount(std::string_view vpath, bool remove_from
return result;
}
std::string lv2_fs_object::get_normalized_path(std::string_view path)
std::pair<std::string_view, std::string> lv2_fs_object::get_path_root_and_trail(std::string_view filename)
{
std::string normalized_path = std::filesystem::path(path).lexically_normal().string();
#ifdef _WIN32
std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/');
#endif
if (normalized_path.ends_with('/'))
normalized_path.pop_back();
return normalized_path.empty() ? "/" : normalized_path;
}
std::string lv2_fs_object::get_device_root(std::string_view filename)
{
std::string path = get_normalized_path(filename); // Prevent getting fooled by ".." trick such as "/dev_usb000/../dev_flash"
if (const auto first = path.find_first_not_of("/"sv); first != umax)
if (filename.empty())
{
if (const auto pos = path.substr(first).find_first_of("/"sv); pos != umax)
path = path.substr(0, first + pos);
path = path.substr(std::max<std::make_signed_t<usz>>(0, first - 1)); // Remove duplicate leading '/' while keeping only one
}
else
{
path = path.substr(0, 1);
// Should CELL_ENOENT later - root cannot have a trail
return {""sv, "ENOENT"};
}
return path;
std::string_view root;
std::string trail;
usz level = 0;
usz pos = 0;
while (pos != umax)
{
const usz ndl_pos = filename.find_first_not_of("/", pos);
if (ndl_pos == pos)
{
// Should CELL_ENOENT later - root cannot have a trail
return {""sv, "ENOENT"};
}
if (ndl_pos == umax)
{
break;
}
const usz dl_pos = ndl_pos == umax ? usz{umax} : filename.find_first_of("/", ndl_pos);
std::string_view component = filename.substr(ndl_pos, dl_pos - ndl_pos);
if (component == "."sv)
{
// No change
// level += 0;
pos = dl_pos;
continue;
}
if (component == ".."sv)
{
if (level > 1)
{
ensure(!trail.empty());
trail.resize(trail.find_last_of("/") + 1);
trail.resize(trail.find_last_not_of("/") + 1);
}
else if (level == 1)
{
// Reset root
root = {};
}
else//if (level == 0)
{
// Should CELL_ENOENT later - root cannot have a trail
return {""sv, "ENOENT"};
}
ensure(level)--;
pos = dl_pos;
continue;
}
if (level == 0)
{
root = component;
}
else if (trail.empty())
{
trail = std::string{component};
}
else
{
trail += "/";
trail.append(component);
}
level++;
pos = dl_pos;
}
return { root, std::move(trail) };
}
lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string* vfs_path)
@ -328,7 +389,7 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string
filename.remove_prefix(cell_fs_path.size());
const bool is_path = filename.starts_with("/"sv);
std::string mp_name = is_path ? get_device_root(filename) : std::string(filename);
std::string mp_name = is_path ? std::string{get_path_root_and_trail(filename).first} : std::string(filename);
const auto check_mp = [&]()
{
@ -1405,6 +1466,10 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
break;
}
case fs::error::notdir:
{
return { CELL_ENOTDIR, path };
}
default:
{
if (has_non_directory_components(local_path))
@ -3398,7 +3463,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
return {path_error, path_sv};
}
const std::string vpath = lv2_fs_object::get_normalized_path(path_sv);
const auto [root_name, trail] = lv2_fs_object::get_path_root_and_trail(path_sv);
std::string vfs_path;
const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path);
@ -3416,8 +3481,8 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
if (vfs_path.empty())
return {CELL_ENOTSUP, device_name};
if (vpath.find_first_not_of('/') == umax || !vfs::get(vpath).empty())
return {CELL_EEXIST, vpath};
if (root_name.empty() || !vfs::get(path_sv).empty())
return {CELL_EEXIST, path_sv};
if (mp == &g_mp_sys_dev_hdd1)
{
@ -3452,7 +3517,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
}
}
if (!vfs::mount(vpath, vfs_path, !is_simplefs))
if (!vfs::mount("/" + std::string{root_name}, vfs_path, !is_simplefs))
{
if (is_simplefs)
{
@ -3469,7 +3534,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
return CELL_EIO;
}
g_fxo->get<lv2_fs_mount_info_map>().add(vpath, mp, device_name, filesystem, prot);
g_fxo->get<lv2_fs_mount_info_map>().add("/" + std::string{root_name}, mp, device_name, filesystem, prot);
return CELL_OK;
}

View File

@ -245,11 +245,15 @@ public:
lv2_fs_object& operator=(const lv2_fs_object&) = delete;
// Normalize a virtual path
static std::string get_normalized_path(std::string_view path);
// Get the device's root path (e.g. "/dev_hdd0") from a given path
// Cut the trail and return it in seccond argument
static std::pair<std::string_view, std::string> get_path_root_and_trail(std::string_view path);
// Get the device's root path (e.g. "/dev_hdd0") from a given path
static std::string get_device_root(std::string_view filename);
static std::string get_device_root(std::string_view filename)
{
return std::string{get_path_root_and_trail(filename).first};
}
// Filename can be either a path starting with '/' or a CELL_FS device name
// This should be used only when handling devices that are not mounted