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

[WIP] LV2/FS: Implement timeless file times #14033

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
207 changes: 193 additions & 14 deletions rpcs3/Emu/Cell/lv2/sys_fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "Emu/IdManager.h"
#include "Emu/system_utils.hpp"
#include "Emu/Cell/lv2/sys_process.h"
#include "Emu/associative_map_file.h"

#include <span>

Expand Down Expand Up @@ -121,6 +122,120 @@ bool verify_mself(const fs::file& mself_file)
return true;
}

void init_fxo_vfs();

struct lv2_file_alternative_timestamps
{
std::map<std::string, associative_map_file, std::less<>> m_map;

shared_mutex m_mutex;

lv2_file_alternative_timestamps() noexcept
{
init_fxo_vfs();
g_fxo->init<lv2_fs_mount_info_map>();
}

struct file_times
{
s64 atime;
s64 mtime;
};

static constexpr std::string_view s_version = "1";

std::pair<bool, file_times> get_times(std::string_view filename)
{
if (filename.empty())
{
return {};
}

auto [mp_times, file_key] = get_keys(filename);

if (!mp_times)
{
return {};
}

file_times times{};

const std::string data = mp_times->get_entry(file_key);

usz a_index = data.find("atime: "sv);
usz m_index = data.find("mtime: "sv);

if (a_index != umax)
{
if (std::from_chars(data.data() + a_index + std::size("atime: "sv), data.data() + data.size(), times.atime).ec != std::errc())
{
return {};
}
}

if (m_index != umax)
{
if (std::from_chars(data.data() + m_index + std::size("mtime: "sv), data.data() + data.size(), times.mtime).ec != std::errc())
{
return {};
}
}

times.ctime = file_times.mtime;
return {true, times};
}

bool set_times(std::string_view filename, file_times times)
{
if (filename.empty())
{
return false;
}

const auto [mp_times, file_key] = get_keys(filename);

if (!mp_times)
{
return false;
}

mp_times->set_entry(file_key, fmt::format("version: %s, mtime: %d, atime: %d.", s_version, times.mtime, times.atime));
return true;
}

std::pair<associative_map_file*, std::string> get_keys(std::string view filename) const
{
std::string root = lv2_fs_object::get_device_root(filename);
std::string vfs_root = vfs::get(root);

if (!m_map.contains(vfs_root))
{
m_map.emplace(vfs_root, associative_map_file(vfs_root + u8"/$vfs-data"));
}

return {&m_map.at(vfs_root), "file-timestamp-" + vfs::get(filename).substr(vfs_root.size())};
}

bool move_times(std::string_view source, std::string_view dest)
{
const auto [mp_times_source, file_key_sorce] = get_keys(source);
const auto [mp_times_dest, file_key_dest] = get_keys(filename);

if (mp_times_dest != mp_times_source)
{
return false;
}

return mp_times_source->move_entries_with_prefix(source, dest);
}

bool remove_times(std::string_view filename)
{
const auto [mp_times, file_key] = get_keys(filename);
return mp_times->remove_entry(file_key);
}
};

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
Expand Down Expand Up @@ -838,6 +953,11 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3
return {CELL_EIO};
}

if (open_mode & fs::trunc)
{
g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vfs::retrieve(local_path));
}

if (flags & CELL_FS_O_MSELF && !verify_mself(file))
{
return {CELL_ENOTMSELF};
Expand Down Expand Up @@ -1059,6 +1179,13 @@ error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr<void> buf, u64 nbytes, v

const u64 read_bytes = file->op_read(buf, nbytes);
const bool failure = !read_bytes && file->file.pos() < file->file.size();

if (read_bytes)
{
// TODO: Remove only atime
g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vfs::retrieve(file->real_path));
}

lock.unlock();
ppu.check_state();

Expand Down Expand Up @@ -1149,6 +1276,12 @@ error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr<void> buf, u64 nbytes,
}

const u64 written = file->op_write(buf, nbytes);

if (written)
{
g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vfs::retrieve(file->real_path));
}

lock.unlock();
ppu.check_state();

Expand Down Expand Up @@ -1310,6 +1443,16 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
continue;
}

if (!mp.read_only)
{
if (auto [ok, times] = g_fxo->get<lv2_file_alternative_timestamps>().get_times(std::string(vpath) + "/" + data.back().name); ok)
{
data.back().atime = times.atime;
data.back().mtime = times.mtime;
data.back().ctime = times.mtime;
}
}

// Add additional entries for split file candidates (while ends with .66600)
while (data.back().name.ends_with(".66600"))
{
Expand Down Expand Up @@ -1504,6 +1647,16 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<CellFsStat>
}
}

if (!mp.read_only)
{
if (auto [ok, times] = g_fxo->get<lv2_file_alternative_timestamps>().get_times(vpath); ok)
{
info.atime = times.atime;
info.mtime = times.mtime;
info.ctime = times.mtime;
}
}

lock.unlock();
ppu.check_state();

Expand Down Expand Up @@ -1553,7 +1706,18 @@ error_code sys_fs_fstat(ppu_thread& ppu, u32 fd, vm::ptr<CellFsStat> sb)
return CELL_EIO;
}

const fs::stat_t info = file->file.stat();
fs::stat_t info = file->file.stat();

if (!mp.read_only)
{
if (auto [ok, times] = g_fxo->get<lv2_file_alternative_timestamps>().get_times(vfs::retrieve(vpath)); ok)
{
info.atime = times.atime;
info.mtime = times.mtime;
info.ctime = times.mtime;
}
}

lock.unlock();
ppu.check_state();

Expand Down Expand Up @@ -1687,10 +1851,9 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to
return CELL_EROFS;
}

// Done in vfs::host::rename
//std::lock_guard lock(mp->mutex);
std::lock_guard lock(mp->mutex);

if (!vfs::host::rename(local_from, local_to, mp.mp, false))
if (!vfs::host::rename(local_from, local_to, mp.mp, false, false))
{
switch (auto error = fs::g_tls_error)
{
Expand All @@ -1702,6 +1865,8 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to
return {CELL_EIO, from}; // ???
}

g_fxo->get<lv2_file_alternative_timestamps>().move_times(from, to);

sys_fs.notice("sys_fs_rename(): %s renamed to %s", from, to);
return CELL_OK;
}
Expand Down Expand Up @@ -1753,6 +1918,8 @@ error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr<char> path)
return {CELL_EIO, path}; // ???
}

g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vpath);

sys_fs.notice("sys_fs_rmdir(): directory %s removed", path);
return CELL_OK;
}
Expand Down Expand Up @@ -1812,6 +1979,8 @@ error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr<char> path)
return {CELL_EIO, path}; // ???
}

ensure(g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vpath));

sys_fs.notice("sys_fs_unlink(): file %s deleted", path);
return CELL_OK;
}
Expand Down Expand Up @@ -1926,6 +2095,11 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
? file->op_read(arg->buf, arg->size)
: file->op_write(arg->buf, arg->size);

if (!file->mp.read_only && arg->out_size)
{
g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vfs::retrieve(file->real_path));
}

ensure(old_pos == file->file.seek(old_pos));

// TODO: EDATA corruption detection
Expand Down Expand Up @@ -2747,6 +2921,8 @@ error_code sys_fs_ftruncate(ppu_thread& ppu, u32 fd, u64 size)
return CELL_EIO; // ???
}

g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vfs::retrieve(file->real_path));

return CELL_OK;
}

Expand Down Expand Up @@ -2884,22 +3060,25 @@ error_code sys_fs_utime(ppu_thread& ppu, vm::cptr<char> path, vm::cptr<CellFsUti
return {CELL_EROFS, path};
}

const s64 actime = timep->actime;
const s64 modtime = timep->modtime;

std::lock_guard lock(mp->mutex);

if (!fs::utime(local_path, timep->actime, timep->modtime))
// Not actually required to succeed
if (fs::utime(local_path, actime, modtime))
{
switch (auto error = fs::g_tls_error)
{
case fs::error::noent:
{
return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path};
}
default: sys_fs.error("sys_fs_utime(): unknown error %s", error);
}
g_fxo->get<lv2_file_alternative_timestamps>().remove_times(vpath);
return CELL_OK;
}

return {CELL_EIO, path}; // ???
if (!fs::exists(local_path))
{
return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.erro, CELL_ENOENT, path};
}

ensure(g_fxo->get<lv2_file_alternative_timestamps>().set_times(vpath, {.atime = actime, .mtime = modtime}));

return CELL_OK;
}

Expand Down
17 changes: 15 additions & 2 deletions rpcs3/Emu/VFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ struct vfs_manager
SAVESTATE_INIT_POS(48);
};

extern void init_fxo_vfs()
{
g_fxo->need<vfs_manager>();
}

bool vfs::mount(std::string_view vpath, std::string_view path, bool is_dir)
{
if (vpath.empty())
Expand Down Expand Up @@ -927,13 +932,16 @@ std::string vfs::host::hash_path(const std::string& path, const std::string& dev
return fmt::format(u8"%s/$%s%s", dev_root, fmt::base57(std::hash<std::string>()(path)), 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 vfs::host::rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool unlocked) noexcept
{
// Lock mount point, close file descriptors, retry
const auto from0 = std::string_view(from).substr(0, from.find_last_not_of(fs::delim) + 1);
const auto escaped_from = Emu.GetCallbacks().resolve_path(from);

std::lock_guard lock(mp->mutex);
if (!unlocked)
{
mp->mutex.lock();
}

auto check_path = [&](std::string_view path)
{
Expand Down Expand Up @@ -1007,6 +1015,11 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2
}
});

if (!unlocked)
{
mp->mutex.unlock();
}

fs::g_tls_error = fs_error;
return res;
}
Expand Down
2 changes: 1 addition & 1 deletion rpcs3/Emu/VFS.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace vfs
std::string hash_path(const std::string& path, const std::string& dev_root);

// 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 rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite, bool unlocked = false);

// Delete file without deleting its contents, emulated with MoveFileEx on Windows
bool unlink(const std::string& path, const std::string& dev_root);
Expand Down
Loading