diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 2534f6a8c1..cfbab23419 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -14,7 +14,6 @@ #include "Emu/system_utils.hpp" #include "Emu/Cell/lv2/sys_process.h" -#include #include #include @@ -93,15 +92,22 @@ void fmt_class_string::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 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>(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 path, vm::ptr 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 dev_name, vm::cptr 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 dev_name, vm::cptr 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 dev_name, vm::cptr } } - 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 dev_name, vm::cptr return CELL_EIO; } - g_fxo->get().add(vpath, mp, device_name, filesystem, prot); + g_fxo->get().add("/" + std::string{root_name}, mp, device_name, filesystem, prot); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index e64a2b4edb..68ecf1e287 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -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 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