Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 98 additions & 21 deletions rpcs3/Emu/Cell/lv2/sys_fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,31 +147,102 @@ bool verify_mself(const fs::file& mself_file)
}

// TODO: May not be thread-safe (or even, process-safe)
bool has_non_directory_components(std::string_view path)
bool has_non_directory_components(std::string_view path, bool ends_with_delim_dot_or_dotdot)
{
std::string path0{path};
std::string sub_path{path};

fs::stat_t stat{};

if (ends_with_delim_dot_or_dotdot)
{
// Special case: /dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN/./ is not allowed
// Because lookup inside a file is not allowed, even if /./ points to itself
// This check is relevant only for when it is at the end of path
// Because if it is other path componenets the functions handle it fine
// We cannot know this from `path` argument because it is resolved by VFS
std::string_view edited_path{path};

edited_path = edited_path.substr(0, edited_path.find_last_not_of(fs::delim) + 1);

const auto elem = edited_path.substr(edited_path.find_last_of(fs::delim) + 1);

if (elem == "." || elem == "..")
{
edited_path.remove_suffix(elem.size());
edited_path = edited_path.substr(0, edited_path.find_last_not_of(fs::delim) + 1);
}

if (fs::get_stat(std::string{edited_path}, stat))
{
if (!stat.is_directory)
{
sys_fs.warning("has_non_directory_components() returned an affirmative (edited_path=%s)", edited_path);
}

return !stat.is_directory;
}

if (fs::g_tls_error == fs::error::notdir)
{
return true;
}

if (fs::g_tls_error != fs::error::inval && fs::g_tls_error != fs::error::noent)
{
fmt::throw_exception("sys_fs: fs::get_stat() failed with error '%s' (edited_path=%s)", fs::g_tls_error, edited_path);
}
}

while (true)
{
const std::string sub_path = fs::get_parent_dir(path0);
std::string_view path_sv = fs::get_parent_dir_view(sub_path);
const usz sv_size = path_sv.size();

if (sub_path.size() >= path0.size())
if (sv_size >= sub_path.size())
{
sys_fs.warning("has_non_directory_components() did not find a directory! (path=%s, sub_path=%s)", path, sub_path);
break;
}

fs::stat_t stat{};
// Trim child path tail
path_sv = {};
sub_path.resize(sv_size);

if (fs::get_stat(sub_path, stat))
{
if (!stat.is_directory)
{
sys_fs.warning("has_non_directory_components() returned an affirmative (sub_path=%s)", sub_path);
}

return !stat.is_directory;
}

path0 = std::move(sub_path);
if (fs::g_tls_error == fs::error::notdir)
{
return true;
}

if (fs::g_tls_error != fs::error::inval && fs::g_tls_error != fs::error::noent)
{
fmt::throw_exception("sys_fs: fs::get_stat() failed with error '%s' (sub_path=%s)", fs::g_tls_error, sub_path);
}
}

return false;
}

// Takes virtual path only
bool ends_with_delim_dot_or_dotdot(std::string_view vpath)
{
if (vpath.find_last_not_of('/') != vpath.size() - 1)

@Megamouse Megamouse Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as vpath.endsWith(/)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird spaghetti code man

{
return true;
}

return vpath.ends_with("/.") || vpath.ends_with("/..");
}

lv2_fs_mount_info_map::lv2_fs_mount_info_map()
{
for (auto mp = &g_mp_sys_dev_root; mp; mp = mp->next) // Scan and keep track of pre-mounted devices
Expand Down Expand Up @@ -864,7 +935,7 @@ error_code sys_fs_test(ppu_thread&, u32 arg1, u32 arg2, vm::ptr<u32> arg3, u32 a
return CELL_OK;
}

lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s32 flags, bool has_write_access, lv2_file_type type, const lv2_fs_mount_info& mp)
lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s32 flags, bool has_write_access, lv2_file_type type, const lv2_fs_mount_info& mp, bool ends_with_dot)
{
// TODO: other checks for path

Expand Down Expand Up @@ -995,12 +1066,12 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3
case fs::error::isdir: return {CELL_EISDIR};
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_dot))
{
return {CELL_ENOTDIR};
}

fmt::throw_exception("unknown error %s", error);
fmt::throw_exception("unknown error %s (local=%s)", error, local_path);
}
}
}
Expand Down Expand Up @@ -1137,7 +1208,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 /*
}
}

auto [error, file] = open_raw(local_path, flags, has_fs_write_rights(vpath), type, mp);
auto [error, file] = open_raw(local_path, flags, has_fs_write_rights(vpath), type, mp, ends_with_delim_dot_or_dotdot(vpath));

return {.error = error, .ppath = std::move(path), .real_path = std::move(local_path), .file = std::move(file), .type = type};
}
Expand Down Expand Up @@ -1477,7 +1548,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -1708,16 +1779,22 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<CellFsStat>
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}

fmt::throw_exception("unknown error %s", error);
fmt::throw_exception("unknown error %s (local=%s)", error, local_path);
}
}
}

if (ends_with_delim_dot_or_dotdot(vpath) && !info.is_directory)
{
// If ending with "/.", it must be a directory
return { CELL_ENOTDIR, path };
}

lock.unlock();
ppu.check_state();

Expand Down Expand Up @@ -1853,7 +1930,7 @@ error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr<char> path, s32 mode)
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -1925,7 +2002,7 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to
case fs::error::exist: return {CELL_EEXIST, to};
default:
{
if (has_non_directory_components(local_from))
if (has_non_directory_components(local_from, ends_with_delim_dot_or_dotdot(vfrom)))
{
return {CELL_ENOTDIR, from};
}
Expand Down Expand Up @@ -1987,7 +2064,7 @@ error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr<char> path)
case fs::error::notempty: return {CELL_ENOTEMPTY, path};
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, vpath };
}
Expand Down Expand Up @@ -2055,7 +2132,7 @@ error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr<char> path)
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -2900,7 +2977,7 @@ error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u
case fs::error::noent: return {CELL_ENOENT, path};
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -2968,7 +3045,7 @@ error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr<char> path, u64 size)
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -3088,7 +3165,7 @@ error_code sys_fs_chmod(ppu_thread&, vm::cptr<char> path, s32 mode)
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down Expand Up @@ -3235,7 +3312,7 @@ error_code sys_fs_utime(ppu_thread& ppu, vm::cptr<char> path, vm::cptr<CellFsUti
}
default:
{
if (has_non_directory_components(local_path))
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
{
return { CELL_ENOTDIR, path };
}
Expand Down
2 changes: 1 addition & 1 deletion rpcs3/Emu/Cell/lv2/sys_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ struct lv2_file final : lv2_fs_object
};

// Open a file with wrapped logic of sys_fs_open
static open_raw_result_t open_raw(const std::string& path, s32 flags, bool has_write_access, lv2_file_type type = lv2_file_type::regular, const lv2_fs_mount_info& mp = g_mi_sys_not_found);
static open_raw_result_t open_raw(const std::string& path, s32 flags, bool has_write_access, lv2_file_type type = lv2_file_type::regular, const lv2_fs_mount_info& mp = g_mi_sys_not_found, bool ends_with_dot = false);
static open_result_t open(std::string_view vpath, s32 flags, s32 mode, const void* arg = {}, u64 size = 0);

// File reading with intermediate buffer
Expand Down
Loading