sys_fs: Fix ENOTDIR check for paths ending with /dot or /dotdot

They must be directories, even file-path/dot points to itself.
This commit is contained in:
Elad
2026-07-14 23:22:41 +03:00
parent 9d9f210ddb
commit 217769d801
2 changed files with 74 additions and 18 deletions
+73 -17
View File
@@ -147,12 +147,52 @@ 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 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)
{
std::string_view path_sv = fs::get_parent_dir_view(sub_path);
@@ -160,7 +200,7 @@ bool has_non_directory_components(std::string_view path)
if (sv_size >= sub_path.size())
{
sys_fs.error("has_non_directory_components() did not find a directory! (path=%s, sub_path=%s)", path, sub_path);
sys_fs.warning("has_non_directory_components() did not find a directory! (path=%s, sub_path=%s)", path, sub_path);
break;
}
@@ -172,13 +212,18 @@ bool has_non_directory_components(std::string_view path)
{
if (!stat.is_directory)
{
sys_fs.error("has_non_directory_components() returned an affirmative (sub_path=%s)", sub_path);
sys_fs.warning("has_non_directory_components() returned an affirmative (sub_path=%s)", sub_path);
}
return !stat.is_directory;
}
if (fs::g_tls_error != fs::error::noent)
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);
}
@@ -187,6 +232,17 @@ bool has_non_directory_components(std::string_view 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)
{
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
@@ -879,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
@@ -1010,7 +1066,7 @@ 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};
}
@@ -1152,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};
}
@@ -1492,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 };
}
@@ -1723,7 +1779,7 @@ 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 };
}
@@ -1868,7 +1924,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 };
}
@@ -1940,7 +1996,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};
}
@@ -2002,7 +2058,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 };
}
@@ -2070,7 +2126,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 };
}
@@ -2915,7 +2971,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 };
}
@@ -2983,7 +3039,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 };
}
@@ -3103,7 +3159,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 };
}
@@ -3250,7 +3306,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 };
}
+1 -1
View File
@@ -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