cellSaveData: add auto maintenance routine in Emu.Init()

This routine:
1) Removes junk backup directories
2) Fixes interrupted save data process in edge case
This case can happen if emu terminates between two atomic renames.

Also use directory renaming technique for delete op.
Also rewrite recreate operation to be part of atomic process.
This commit is contained in:
Nekotekina 2019-09-25 01:49:13 +03:00
parent 297016aba3
commit f841b47b6b
2 changed files with 75 additions and 25 deletions

View File

@ -630,7 +630,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
}
}
auto delete_save = [&](const std::string& del_path)
auto delete_save = [&]()
{
strcpy_trunc(doneGet->dirName, save_entries[selected].dirName);
doneGet->hddFreeSizeKB = 40 * 1024 * 1024 - 1; // Read explanation in cellHddGameCheck
@ -638,31 +638,38 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
doneGet->excResult = CELL_OK;
std::memset(doneGet->reserved, 0, sizeof(doneGet->reserved));
const fs::dir _dir{del_path};
const std::string old_path = base_dir + ".backup_" + save_entries[selected].dirName + "/";
const std::string del_path = base_dir + save_entries[selected].dirName + "/";
const fs::dir _dir(del_path);
for (auto&& file : _dir)
{
if (!file.is_directory)
{
doneGet->sizeKB += static_cast<s32>(::align(file.size, 4096));
if (!fs::remove_file(del_path + file.name))
{
doneGet->excResult = CELL_SAVEDATA_ERROR_FAILURE;
}
}
}
if (!_dir)
if (_dir)
{
// Remove old backup
fs::remove_all(old_path);
// Remove savedata by renaming
if (!vfs::host::rename(del_path, old_path, false))
{
fmt::throw_exception("Failed to move directory %s (%s)", del_path, fs::g_tls_error);
}
// Cleanup
fs::remove_all(old_path);
}
else
{
doneGet->excResult = CELL_SAVEDATA_ERROR_NODATA;
}
if (!doneGet->excResult && !fs::remove_dir(del_path))
{
doneGet->excResult = CELL_SAVEDATA_ERROR_FAILURE;
}
funcDone(ppu, result, doneGet);
};
@ -701,7 +708,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
if (operation == SAVEDATA_OP_LIST_DELETE)
{
delete_save(base_dir + save_entries[selected].dirName + '/');
delete_save();
if (result->result < 0)
{
@ -777,7 +784,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
if (operation == SAVEDATA_OP_FIXED_DELETE)
{
delete_save(base_dir + save_entries[selected].dirName + '/');
delete_save();
if (result->result < 0)
{
@ -813,6 +820,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
psf::registry psf = psf::load_object(fs::file(dir_path + "PARAM.SFO"));
bool has_modified = false;
bool recreated = false;
lv2_sleep(ppu, 250);
@ -1044,15 +1052,21 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
return {CELL_SAVEDATA_ERROR_PARAM, "50"};
}
// TODO: Only delete data, not owner info
for (const auto& entry : fs::dir(dir_path))
// Clear secure file info
for (auto it = psf.cbegin(), end = psf.cend(); it != end;)
{
if (!entry.is_directory)
{
fs::remove_file(dir_path + entry.name);
}
if (it->first[0] == '*')
it = psf.erase(it);
else
it++;
}
// Clear order info
blist.clear();
// Set to not load files
has_modified = true;
recreated = true;
break;
}
@ -1081,7 +1095,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
// First, preload all files (TODO: beware of possible lag, although it should be insignificant)
for (auto&& entry : fs::dir(dir_path))
{
if (!entry.is_directory)
if (!recreated && !entry.is_directory)
{
// Read file into a vector and make a memory file
all_times.emplace(entry.name, std::make_pair(entry.atime, entry.mtime));
@ -1319,10 +1333,10 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
}
// Remove old backup
fs::remove_all(old_path, false);
fs::remove_all(old_path);
// Backup old savedata
if (!vfs::host::rename(dir_path, old_path, true))
if (!vfs::host::rename(dir_path, old_path, false))
{
fmt::throw_exception("Failed to move directory %s (%s)", dir_path, fs::g_tls_error);
}

View File

@ -355,6 +355,8 @@ void Emulator::Init()
}
};
const std::string save_path = dev_hdd0 + "home/" + m_usr + "/savedata/";
if (g_cfg.vfs.init_dirs)
{
make_path_verbose(dev_hdd0);
@ -367,7 +369,7 @@ void Emulator::Init()
make_path_verbose(dev_hdd0 + "home/");
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/");
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/exdata/");
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/savedata/");
make_path_verbose(save_path);
make_path_verbose(dev_hdd0 + "home/" + m_usr + "/trophy/");
if (!fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s))
@ -385,6 +387,40 @@ void Emulator::Init()
make_path_verbose(dev_hdd1 + "game/");
}
// Fixup savedata
for (const auto& entry : fs::dir(save_path))
{
if (entry.is_directory && entry.name.compare(0, 8, ".backup_", 8) == 0)
{
const std::string desired = entry.name.substr(8);
const std::string pending = save_path + ".working_" + desired;
if (fs::is_dir(pending))
{
// Finalize interrupted saving
if (!fs::rename(pending, save_path + desired, false))
{
LOG_FATAL(GENERAL, "Failed to fix save data: %s (%s)", pending, fs::g_tls_error);
continue;
}
else
{
LOG_SUCCESS(GENERAL, "Fixed save data: %s", desired);
}
}
// Remove pending backup data
if (!fs::remove_all(save_path + entry.name))
{
LOG_FATAL(GENERAL, "Failed to remove save data backup: %s%s (%s)", save_path, entry.name, fs::g_tls_error);
}
else
{
LOG_SUCCESS(GENERAL, "Removed save data backup: %s%s", save_path, entry.name);
}
}
}
make_path_verbose(fs::get_cache_dir() + "shaderlog/");
make_path_verbose(fs::get_config_dir() + "captures/");