Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure HDD1 is never damaged or corrupted due to unexpected RPCS3/game close #15036

Merged
merged 1 commit into from
Jan 11, 2024
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
Ensure HDD1 is never damaged or corrupted due to unexpected RPCS3/gam…
…e termination
  • Loading branch information
elad335 committed Jan 11, 2024
commit e3ebc5cd16e1616f45d6abc9814da6befe2ff51f
80 changes: 75 additions & 5 deletions rpcs3/Emu/Cell/Modules/cellSysCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ void fmt_class_string<CellSysCacheError>::format(std::string& out, u64 arg)

extern lv2_fs_mount_point g_mp_sys_dev_hdd1;

extern std::string get_syscache_state_corruption_indicator_file_path(std::string_view dir_path);

struct syscache_info
{
const std::string cache_root = rpcs3::utils::get_hdd1_dir() + "/caches/";
Expand All @@ -39,6 +41,8 @@ struct syscache_info

std::string cache_id;

bool retain_caches = false;

syscache_info() noexcept
{
// Check if dev_hdd1 is mounted by parent process
Expand All @@ -63,6 +67,12 @@ struct syscache_info
{
if (entry.is_directory && entry.name.starts_with(prefix))
{
if (fs::is_file(get_syscache_state_corruption_indicator_file_path(cache_root + '/' + entry.name)))
{
// State is not complete
break;
}

cache_id = std::move(entry.name);
break;
}
Expand All @@ -89,8 +99,50 @@ struct syscache_info
});
}
}

~syscache_info() noexcept
{
if (cache_id.empty())
{
return;
}

if (!retain_caches)
{
vfs::host::remove_all(cache_root + cache_id, cache_root, &g_mp_sys_dev_hdd1, true, false, true);
return;
}

idm::select<lv2_fs_object, lv2_file>([](u32 /*id*/, lv2_file& file)
{
if (file.file && file.mp->flags & lv2_mp_flag::cache && file.flags & CELL_FS_O_ACCMODE)
{
file.file.sync();
}
});

fs::remove_file(get_syscache_state_corruption_indicator_file_path(cache_root + cache_id));
}
};

extern std::string get_syscache_state_corruption_indicator_file_path(std::string_view dir_path)
{
constexpr std::u8string_view append_path = u8"/$hdd0_temp_state_indicator";
const std::string_view filename = reinterpret_cast<const char*>(append_path.data());

if (dir_path.empty())
{
return rpcs3::utils::get_hdd1_dir() + "/caches/" + ensure(g_fxo->try_get<syscache_info>())->cache_id + "/" + filename.data();
}

return std::string{dir_path} + filename.data();
}

extern void signal_system_cache_can_stay()
{
ensure(g_fxo->try_get<syscache_info>())->retain_caches = true;
}

error_code cellSysCacheClear()
{
cellSysutil.notice("cellSysCacheClear()");
Expand All @@ -116,7 +168,7 @@ error_code cellSysCacheClear()

error_code cellSysCacheMount(vm::ptr<CellSysCacheParam> param)
{
cellSysutil.notice("cellSysCacheMount(param=*0x%x)", param);
cellSysutil.notice("cellSysCacheMount(param=*0x%x ('%s'))", param, param.ptr(&CellSysCacheParam::cacheId));

auto& cache = g_fxo->get<syscache_info>();

Expand All @@ -141,8 +193,8 @@ error_code cellSysCacheMount(vm::ptr<CellSysCacheParam> param)

std::lock_guard lock0(g_mp_sys_dev_hdd1.mutex);

// Check if can reuse existing cache (won't if cache id is an empty string)
if (param->cacheId[0] && cache_id == cache.cache_id)
// Check if can reuse existing cache (won't if cache id is an empty string or cache is damaged/incomplete)
if (param->cacheId[0] && cache_id == cache.cache_id && !fs::is_file(get_syscache_state_corruption_indicator_file_path(cache.cache_root + cache_id)))
{
// Isn't mounted yet on first call to cellSysCacheMount
if (vfs::mount("/dev_hdd1", new_path))
Expand All @@ -152,15 +204,33 @@ error_code cellSysCacheMount(vm::ptr<CellSysCacheParam> param)
return not_an_error(CELL_SYSCACHE_RET_OK_RELAYED);
}

// Clear existing cache
const bool can_create = cache.cache_id != cache_id;

if (!cache.cache_id.empty())
{
// Clear previous cache
cache.clear(true);
}

// Set new cache id
cache.cache_id = std::move(cache_id);
fs::create_dir(new_path);

if (can_create)
{
const bool created = fs::create_dir(new_path);

if (!created)
{
if (fs::g_tls_error != fs::error::exist)
{
fmt::throw_exception("Failed to create HDD1 cache! (path='%s', reason='%s')", new_path, fs::g_tls_error);
}

// Clear new cache
cache.clear(false);
}
}

if (vfs::mount("/dev_hdd1", new_path))
g_fxo->get<lv2_fs_mount_info_map>().add("/dev_hdd1", &g_mp_sys_dev_hdd1);

Expand Down
4 changes: 4 additions & 0 deletions rpcs3/Emu/Cell/lv2/sys_process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ error_code sys_process_detach_child(u64 unk)
return CELL_OK;
}

extern void signal_system_cache_can_stay();

void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3)
{
ppu.state += cpu_flag::wait;
Expand All @@ -349,6 +351,7 @@ void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3)
Emu.CallFromMainThread([]()
{
sys_process.success("Process finished");
signal_system_cache_can_stay();
Emu.Kill();
});

Expand Down Expand Up @@ -481,6 +484,7 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
}
};

signal_system_cache_can_stay();
Emu.Kill(false);
});

Expand Down
12 changes: 12 additions & 0 deletions rpcs3/Emu/Memory/vm_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,15 @@ struct fmt_class_string<vm::_ptr_base<char, u32>, void> : fmt_class_string<vm::_
{
// Classify char* as const char*
};

template <usz Size>
struct fmt_class_string<vm::_ptr_base<const char[Size], u32>, void> : fmt_class_string<vm::_ptr_base<const char, u32>>
{
// Classify const char[] as const char*
};

template <usz Size>
struct fmt_class_string<vm::_ptr_base<char[Size], u32>, void> : fmt_class_string<vm::_ptr_base<const char, u32>>
{
// Classify char[] as const char*
};
4 changes: 4 additions & 0 deletions rpcs3/Emu/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ extern bool ppu_load_rel_exec(const ppu_rel_object&);

extern void send_close_home_menu_cmds();

extern void signal_system_cache_can_stay();

fs::file make_file_view(const fs::file& file, u64 offset, u64 size);

fs::file g_tty;
Expand Down Expand Up @@ -3033,6 +3035,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s

to_ar = std::make_unique<utils::serial>();
to_ar->m_file_handler = make_compressed_serialization_file_handler(file.file);

signal_system_cache_can_stay();
break;
}

Expand Down
49 changes: 36 additions & 13 deletions rpcs3/Emu/VFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,9 +922,9 @@ std::string vfs::unescape(std::string_view name)
return result;
}

std::string vfs::host::hash_path(const std::string& path, const std::string& dev_root)
std::string vfs::host::hash_path(const std::string& path, const std::string& dev_root, std::string_view prefix)
{
return fmt::format(u8"%s/$%s%s", dev_root, fmt::base57(std::hash<std::string>()(path)), fmt::base57(utils::get_unique_tsc()));
return fmt::format(u8"%s/$%s%s%s", dev_root, fmt::base57(std::hash<std::string>()(path)), prefix, fmt::base57(utils::get_unique_tsc()));
}

bool vfs::host::rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool lock)
Expand Down Expand Up @@ -1032,11 +1032,21 @@ bool vfs::host::unlink(const std::string& path, [[maybe_unused]] const std::stri
else
{
// Rename to special dummy name which will be ignored by VFS (but opened file handles can still read or write it)
const std::string dummy = hash_path(path, dev_root);
std::string dummy = hash_path(path, dev_root, "file");

if (!fs::rename(path, dummy, true))
while (true)
{
return false;
if (fs::rename(path, dummy, false))
{
break;
}

if (fs::g_tls_error != fs::error::exist)
{
return false;
}

dummy = hash_path(path, dev_root, "file");
}

if (fs::file f{dummy, fs::read + fs::write})
Expand All @@ -1056,17 +1066,33 @@ bool vfs::host::unlink(const std::string& path, [[maybe_unused]] const std::stri
#endif
}

bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std::string& dev_root, [[maybe_unused]] const lv2_fs_mount_point* mp, bool remove_root, [[maybe_unused]] bool lock)
bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std::string& dev_root, [[maybe_unused]] const lv2_fs_mount_point* mp, [[maybe_unused]] bool remove_root, [[maybe_unused]] bool lock, [[maybe_unused]] bool force_atomic)
{
#ifdef _WIN32
#ifndef _WIN32
if (!force_atomic)
{
return fs::remove_all(path, remove_root);
}
#endif

if (remove_root)
{
// Rename to special dummy folder which will be ignored by VFS (but opened file handles can still read or write it)
const std::string dummy = hash_path(path, dev_root);
std::string dummy = hash_path(path, dev_root, "dir");

if (!vfs::host::rename(path, dummy, mp, false, lock))
while (true)
{
return false;
if (vfs::host::rename(path, dummy, mp, false, lock))
{
break;
}

if (fs::g_tls_error != fs::error::exist)
{
return false;
}

dummy = hash_path(path, dev_root, "dir");
}

if (!vfs::host::remove_all(dummy, dev_root, mp, false, lock))
Expand Down Expand Up @@ -1113,7 +1139,4 @@ bool vfs::host::remove_all(const std::string& path, [[maybe_unused]] const std::
}

return true;
#else
return fs::remove_all(path, remove_root);
#endif
}
4 changes: 2 additions & 2 deletions rpcs3/Emu/VFS.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace vfs
namespace host
{
// For internal use (don't use)
std::string hash_path(const std::string& path, const std::string& dev_root);
std::string hash_path(const std::string& path, const std::string& dev_root, std::string_view prefix = {});

// Call fs::rename with retry on access error
bool rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool lock = true);
Expand All @@ -40,6 +40,6 @@ namespace vfs
bool unlink(const std::string& path, const std::string& dev_root);

// Delete folder contents using rename, done atomically if remove_root is true
bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true, bool lock = true);
bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true, bool lock = true, bool force_atomic = false);
}
}