Atomic PARAM.SFO writes

This commit is contained in:
Eladash 2021-02-21 21:55:07 +02:00 committed by Ivan
parent 0878db4e17
commit 932f31e37b
9 changed files with 108 additions and 11 deletions

View File

@ -18,6 +18,11 @@ using namespace std::literals::string_literals;
#include <cwchar>
#include <Windows.h>
namespace utils
{
u64 get_unique_tsc();
}
static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
{
// String size + null terminator
@ -1915,6 +1920,55 @@ fs::file fs::make_gather(std::vector<fs::file> files)
return result;
}
fs::pending_file::pending_file(const std::string& path)
{
do
{
m_path = fmt::format(u8"%s/%s.%s.tmp", get_parent_dir(path), std::string_view(path).substr(path.find_last_of(fs::delim) + 1), fmt::base57(utils::get_unique_tsc()));
if (file.open(m_path, fs::create + fs::write + fs::read + fs::excl))
{
m_dest = path;
break;
}
m_path.clear();
}
while (fs::g_tls_error == fs::error::exist); // Only retry if failed due to existing file
}
fs::pending_file::~pending_file()
{
file.close();
if (!m_path.empty())
{
fs::remove_file(m_path);
}
}
bool fs::pending_file::commit(bool overwrite)
{
if (!file || m_path.empty())
{
fs::g_tls_error = fs::error::noent;
return false;
}
// The temporary file's contents must be on disk before rename
file.sync();
file.close();
if (fs::rename(m_path, m_dest, overwrite))
{
// Disable the destructor
m_path.clear();
return true;
}
return false;
}
template<>
void fmt_class_string<fs::seek_mode>::format(std::string& out, u64 arg)
{

View File

@ -602,6 +602,22 @@ namespace fs
// Get common cache directory
const std::string& get_cache_dir();
// Unique pending file creation destined to be renamed to the destination file
struct pending_file
{
fs::file file;
// This is meant to modify files atomically, overwriting is likely
bool commit(bool overwrite = true);
pending_file(const std::string& path);
~pending_file();
private:
std::string m_path; // Pending file path
std::string m_dest; // Destination file path
};
// Get real path for comparisons (TODO: investigate std::filesystem::path::compare implementation)
std::string escape_path(std::string_view path);

View File

@ -666,7 +666,9 @@ error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPa
if (!perm->temp.empty())
{
// Create PARAM.SFO
psf::save_object(fs::file(perm->temp + "/PARAM.SFO", fs::rewrite), perm->sfo);
fs::pending_file temp(perm->temp + "/PARAM.SFO");
temp.file.write(psf::save_object(perm->sfo));
ensure(temp.commit());
// Make temporary directory persistent (atomically)
if (vfs::host::rename(perm->temp, vfs::get(dir), &g_mp_sys_dev_hdd0, false))
@ -684,7 +686,9 @@ error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPa
else if (perm->can_create)
{
// Update PARAM.SFO
psf::save_object(fs::file(vfs::get(dir + "/PARAM.SFO"), fs::rewrite), perm->sfo);
fs::pending_file temp(vfs::get(dir + "/PARAM.SFO"));
temp.file.write(psf::save_object(perm->sfo));
ensure(temp.commit());
}
// Cleanup
@ -806,7 +810,9 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
psf::assign(sfo, fmt::format("TITLE_%02d", i), psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->titleLang[i]));
}
psf::save_object(fs::file(vfs::get(dir + "/PARAM.SFO"), fs::rewrite), sfo);
fs::pending_file temp(vfs::get(dir + "/PARAM.SFO"));
temp.file.write(psf::save_object(sfo));
ensure(temp.commit());
}
return CELL_OK;

View File

@ -1306,7 +1306,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
if (!entry.is_directory)
{
if (entry.name == "PARAM.SFO" || entry.name == "PARAM.PFD")
if (entry.name == "."sv)
{
continue;
}
if (entry.name == "PARAM.SFO"sv || entry.name == "PARAM.PFD"sv)
{
continue; // system files are not included in the file list
}
@ -1899,7 +1904,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
// Write all files in temporary directory
auto& fsfo = all_files["PARAM.SFO"];
fsfo = fs::make_stream<std::vector<uchar>>();
psf::save_object(fsfo, psf);
fsfo.write(psf::save_object(psf));
for (auto&& pair : all_files)
{

View File

@ -768,6 +768,13 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
// Preprocess entries
data.back().name = vfs::unescape(data.back().name);
if (!data.back().is_directory && data.back().name == "."sv)
{
// Files hidden from emulation
data.resize(data.size() - 1);
continue;
}
// Add additional entries for split file candidates (while ends with .66600)
while (data.back().name.ends_with(".66600"))
{

View File

@ -1299,8 +1299,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
games[m_title_id] = bdvd_dir;
YAML::Emitter out;
out << games;
fs::file(fs::get_config_dir() + "/games.yml.tmp", fs::rewrite).write(out.c_str(), out.size());
fs::rename(fs::get_config_dir() + "/games.yml.tmp", fs::get_config_dir() + "/games.yml", true);
fs::pending_file temp(fs::get_config_dir() + "/games.yml");
temp.file.write(out.c_str(), out.size());
temp.commit();
}
else if (m_cat == "1P" && from_hdd0_game)
{

View File

@ -113,6 +113,8 @@ namespace psf
return result;
}
stream.seek(0);
// Get header
header_t header;
ensure(stream.read(header));
@ -186,8 +188,10 @@ namespace psf
return result;
}
void save_object(const fs::file& stream, const psf::registry& psf)
std::vector<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
{
fs::file stream = fs::make_stream<std::vector<u8>>(std::move(init));
std::vector<def_table_t> indices; indices.reserve(psf.size());
// Generate indices and calculate key table length
@ -264,6 +268,8 @@ namespace psf
fmt::throw_exception("Invalid entry format (key='%s', fmt=0x%x)", entry.first, fmt);
}
}
return std::move(static_cast<fs::container_stream<std::vector<u8>>*>(stream.release().get())->obj);
}
std::string_view get_string(const registry& psf, const std::string& key, std::string_view def)

View File

@ -53,7 +53,7 @@ namespace psf
registry load_object(const fs::file&);
// Convert PSF registry to SFO binary format
void save_object(const fs::file&, const registry&);
std::vector<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
// Get string value or default value
std::string_view get_string(const registry& psf, const std::string& key, std::string_view def = ""sv);

View File

@ -190,8 +190,9 @@ void emu_settings::SaveSettings()
}
// Save config atomically
fs::file(config_name + ".tmp", fs::rewrite).write(out.c_str(), out.size());
fs::rename(config_name + ".tmp", config_name, true);
fs::pending_file temp(config_name);
temp.file.write(out.c_str(), out.size());
temp.commit();
// Check if the running config/title is the same as the edited config/title.
if (config_name == g_cfg.name || m_title_id == Emu.GetTitleID())