mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 18:53:28 +01:00
Ensure HDD1 is never damaged or corrupted due to unexpected RPCS3/game termination
This commit is contained in:
parent
3513f6d83c
commit
825ae5b8aa
@ -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/";
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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()");
|
||||
@ -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>();
|
||||
|
||||
@ -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))
|
||||
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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*
|
||||
};
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
@ -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})
|
||||
@ -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))
|
||||
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user