Win32 FS: Rewrite (fix) vfs::host::rename

This commit is contained in:
Eladash 2020-09-11 14:06:46 +03:00 committed by Ivan
parent 22269ca0d7
commit b8fa6fb4c4
16 changed files with 329 additions and 108 deletions

View File

@ -1616,6 +1616,116 @@ bool fs::remove_all(const std::string& path, bool remove_root)
return true; return true;
} }
std::string fs::escape_path(std::string_view path)
{
std::string real; real.resize(path.size());
#ifdef _WIN32
constexpr auto& delim = "/\\";
#else
constexpr auto& delim = "/";
#endif
auto get_char = [&](std::size_t& from, std::size_t& to, std::size_t count)
{
std::memcpy(&real[to], &path[from], count);
from += count, to += count;
};
std::size_t i = 0, j = -1, pos_nondelim = 0, after_delim = 0;
if (i < path.size())
{
j = 0;
}
for (; i < path.size();)
{
real[j] = path[i];
#ifdef _Win32
if (real[j] == '\\')
{
real[j] = '/';
}
#endif
// If the current character was preceeded by a delimiter special treatment is required:
// If another deleimiter is encountered, remove it (do not write it to output string)
// Otherwise test if it is a "." or ".." sequence.
if (std::exchange(after_delim, path[i] == delim[0] || path[i] == delim[1]))
{
if (!after_delim)
{
if (real[j] == '.')
{
if (i + 1 == path.size())
{
break;
}
get_char(i, j, 1);
switch (real[j])
{
case '.':
{
bool remove_element = true;
std::size_t k = 1;
for (; k + i != path.size(); k++)
{
switch (path[i + k])
{
case '.': continue;
case delim[0]: case delim[1]: break;
default: remove_element = false; break;
}
}
if (remove_element)
{
if (i == 1u)
{
j = pos_nondelim;
real[j] = '\0';// Ensure termination at this posistion
after_delim = true;
i += k;
continue;
}
}
get_char(i, j, k);
continue;
}
case '/':
{
i++;
after_delim = true;
continue;
}
default: get_char(i, j, 1); continue;
}
}
pos_nondelim = j;
get_char(i, j, 1);
}
else
{
i++;
}
}
else
{
get_char(i, j, 1);
}
}
if (j != umax && (real[j] == delim[0] || real[j] == delim[1])) j--; // Do not include a delmiter at the end
real.resize(j + 1);
return real;
}
u64 fs::get_dir_size(const std::string& path, u64 rounding_alignment) u64 fs::get_dir_size(const std::string& path, u64 rounding_alignment)
{ {
u64 result = 0; u64 result = 0;

View File

@ -498,6 +498,9 @@ namespace fs
// Get common cache directory // Get common cache directory
const std::string& get_cache_dir(); const std::string& get_cache_dir();
// Get real path for comparisons (TODO: investigate std::filesystem::path::compare implementation)
std::string escape_path(std::string_view path);
// Delete directory and all its contents recursively // Delete directory and all its contents recursively
bool remove_all(const std::string& path, bool remove_root = true); bool remove_all(const std::string& path, bool remove_root = true);

View File

@ -4,6 +4,7 @@
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "Emu/IdManager.h" #include "Emu/IdManager.h"
#include "Emu/Cell/PPUModule.h" #include "Emu/Cell/PPUModule.h"
#include "Emu/Cell/lv2/sys_fs.h"
#include "cellSysutil.h" #include "cellSysutil.h"
#include "cellMsgDialog.h" #include "cellMsgDialog.h"
@ -555,7 +556,7 @@ error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPa
psf::save_object(fs::file(perm->temp + "/PARAM.SFO", fs::rewrite), perm->sfo); psf::save_object(fs::file(perm->temp + "/PARAM.SFO", fs::rewrite), perm->sfo);
// Make temporary directory persistent (atomically) // Make temporary directory persistent (atomically)
if (vfs::host::rename(perm->temp, vfs::get(dir), false)) if (vfs::host::rename(perm->temp, vfs::get(dir), &g_mp_sys_dev_hdd0, false))
{ {
cellGame.success("cellGameContentPermit(): directory '%s' has been created", dir); cellGame.success("cellGameContentPermit(): directory '%s' has been created", dir);

View File

@ -78,11 +78,12 @@ error_code cellGifDecOpen(PMainHandle mainHandle, PPSubHandle subHandle, PSrc sr
case CELL_GIFDEC_FILE: case CELL_GIFDEC_FILE:
{ {
// Get file descriptor and size // Get file descriptor and size
fs::file file_s(vfs::get(src->fileName.get_ptr())); const auto real_path = vfs::get(src->fileName.get_ptr());
fs::file file_s(real_path);
if (!file_s) return CELL_GIFDEC_ERROR_OPEN_FILE; if (!file_s) return CELL_GIFDEC_ERROR_OPEN_FILE;
current_subHandle.fileSize = file_s.size(); current_subHandle.fileSize = file_s.size();
current_subHandle.fd = idm::make<lv2_fs_object, lv2_file>(src->fileName.get_ptr(), std::move(file_s), 0, 0); current_subHandle.fd = idm::make<lv2_fs_object, lv2_file>(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path);
break; break;
} }
} }

View File

@ -69,11 +69,12 @@ error_code cellJpgDecOpen(u32 mainHandle, vm::ptr<u32> subHandle, vm::ptr<CellJp
case CELL_JPGDEC_FILE: case CELL_JPGDEC_FILE:
{ {
// Get file descriptor and size // Get file descriptor and size
fs::file file_s(vfs::get(src->fileName.get_ptr())); const auto real_path = vfs::get(src->fileName.get_ptr());
fs::file file_s(real_path);
if (!file_s) return CELL_JPGDEC_ERROR_OPEN_FILE; if (!file_s) return CELL_JPGDEC_ERROR_OPEN_FILE;
current_subHandle.fileSize = file_s.size(); current_subHandle.fileSize = file_s.size();
current_subHandle.fd = idm::make<lv2_fs_object, lv2_file>(src->fileName.get_ptr(), std::move(file_s), 0, 0); current_subHandle.fd = idm::make<lv2_fs_object, lv2_file>(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path);
break; break;
} }
} }

View File

@ -432,8 +432,10 @@ error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc
// Depending on the source type, get the first 8 bytes // Depending on the source type, get the first 8 bytes
if (source->srcSelect == CELL_PNGDEC_FILE) if (source->srcSelect == CELL_PNGDEC_FILE)
{ {
const auto real_path = vfs::get(stream->source.fileName.get_ptr());
// Open a file stream // Open a file stream
fs::file file_stream(vfs::get(stream->source.fileName.get_ptr())); fs::file file_stream(real_path);
// Check if opening of the PNG file failed // Check if opening of the PNG file failed
if (!file_stream) if (!file_stream)
@ -450,7 +452,7 @@ error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc
} }
// Get the file descriptor // Get the file descriptor
buffer->fd = idm::make<lv2_fs_object, lv2_file>(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0); buffer->fd = idm::make<lv2_fs_object, lv2_file>(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0, real_path);
// Indicate that we need to read from a file stream // Indicate that we need to read from a file stream
buffer->file = true; buffer->file = true;

View File

@ -964,7 +964,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
fs::remove_all(old_path); fs::remove_all(old_path);
// Remove savedata by renaming // Remove savedata by renaming
if (!vfs::host::rename(del_path, old_path, false)) if (!vfs::host::rename(del_path, old_path, &g_mp_sys_dev_hdd0, false))
{ {
fmt::throw_exception("Failed to move directory %s (%s)", del_path, fs::g_tls_error); fmt::throw_exception("Failed to move directory %s (%s)", del_path, fs::g_tls_error);
} }
@ -1917,13 +1917,13 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
fs::remove_all(old_path); fs::remove_all(old_path);
// Backup old savedata // Backup old savedata
if (!vfs::host::rename(dir_path, old_path, false)) if (!vfs::host::rename(dir_path, old_path, &g_mp_sys_dev_hdd0, false))
{ {
fmt::throw_exception("Failed to move directory %s (%s)", dir_path, fs::g_tls_error); fmt::throw_exception("Failed to move directory %s (%s)", dir_path, fs::g_tls_error);
} }
// Commit new savedata // Commit new savedata
if (!vfs::host::rename(new_path, dir_path, false)) if (!vfs::host::rename(new_path, dir_path, &g_mp_sys_dev_hdd0, false))
{ {
// TODO: handle the case when only commit failed at the next save load // TODO: handle the case when only commit failed at the next save load
fmt::throw_exception("Failed to move directory %s (%s)", new_path, fs::g_tls_error); fmt::throw_exception("Failed to move directory %s (%s)", new_path, fs::g_tls_error);

View File

@ -71,7 +71,7 @@ struct syscache_info
void clear(bool remove_root) noexcept void clear(bool remove_root) noexcept
{ {
// Clear cache // Clear cache
if (!vfs::host::remove_all(cache_root + cache_id, cache_root, remove_root)) if (!vfs::host::remove_all(cache_root + cache_id, cache_root, &g_mp_sys_dev_hdd1, remove_root))
{ {
cellSysutil.fatal("cellSysCache: failed to clear cache directory '%s%s' (%s)", cache_root, cache_id, fs::g_tls_error); cellSysutil.fatal("cellSysCache: failed to clear cache directory '%s%s' (%s)", cache_root, cache_id, fs::g_tls_error);
} }

View File

@ -230,33 +230,13 @@ error_code sys_fs_test(ppu_thread& ppu, u32 arg1, u32 arg2, vm::ptr<u32> arg3, u
return CELL_OK; return CELL_OK;
} }
lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s32 flags, s32 mode, lv2_file_type type, const lv2_fs_mount_point* mp)
{ {
if (vpath.empty())
{
return {CELL_ENOENT};
}
std::string path;
const std::string local_path = vfs::get(vpath, nullptr, &path);
const auto mp = lv2_fs_object::get_mp(path);
if (vpath.find_first_not_of('/') == umax)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
// TODO: other checks for path // TODO: other checks for path
if (fs::is_dir(local_path)) if (fs::is_dir(local_path))
{ {
return {CELL_EISDIR, path}; return {CELL_EISDIR};
} }
bs_t<fs::open_mode> open_mode{}; bs_t<fs::open_mode> open_mode{};
@ -272,7 +252,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
{ {
if (flags & CELL_FS_O_ACCMODE || flags & (CELL_FS_O_CREAT | CELL_FS_O_TRUNC)) if (flags & CELL_FS_O_ACCMODE || flags & (CELL_FS_O_CREAT | CELL_FS_O_TRUNC))
{ {
return {CELL_EPERM, path}; return {CELL_EPERM};
} }
} }
@ -326,14 +306,12 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
if (!open_mode) if (!open_mode)
{ {
fmt::throw_exception("lv2_file::open(%s): Invalid or unimplemented flags: %#o" HERE, path, flags); fmt::throw_exception("lv2_file::open_raw(): Invalid or unimplemented flags: %#o" HERE, flags);
} }
fs::file file; std::lock_guard lock(mp->mutex);
{
std::lock_guard lock(mp->mutex); fs::file file(local_path, open_mode);
file.open(local_path, open_mode);
}
if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent)
{ {
@ -365,7 +343,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
// Failed to create file on read-only FS (file doesn't exist) // Failed to create file on read-only FS (file doesn't exist)
if (flags & CELL_FS_O_CREAT) if (flags & CELL_FS_O_CREAT)
{ {
return {CELL_EROFS, path}; return {CELL_EROFS};
} }
} }
@ -376,11 +354,11 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
switch (auto error = fs::g_tls_error) switch (auto error = fs::g_tls_error)
{ {
case fs::error::noent: return {CELL_ENOENT, path}; case fs::error::noent: return {CELL_ENOENT};
default: sys_fs.error("lv2_file::open(): unknown error %s", error); default: sys_fs.error("lv2_file::open(): unknown error %s", error);
} }
return {CELL_EIO, path}; return {CELL_EIO};
} }
if (mp->flags & lv2_mp_flag::read_only) if (mp->flags & lv2_mp_flag::read_only)
@ -388,27 +366,27 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
// Failed to create file on read-only FS (file exists) // Failed to create file on read-only FS (file exists)
if (flags & CELL_FS_O_CREAT && flags & CELL_FS_O_EXCL) if (flags & CELL_FS_O_CREAT && flags & CELL_FS_O_EXCL)
{ {
return {CELL_EEXIST, path}; return {CELL_EEXIST};
} }
// Failed to truncate file on read-only FS // Failed to truncate file on read-only FS
if (flags & CELL_FS_O_TRUNC) if (flags & CELL_FS_O_TRUNC)
{ {
return {CELL_EROFS, path}; return {CELL_EROFS};
} }
} }
if (flags & CELL_FS_O_MSELF && !verify_mself(file)) if (flags & CELL_FS_O_MSELF && !verify_mself(file))
{ {
return {CELL_ENOTMSELF, path}; return {CELL_ENOTMSELF};
} }
if (size == 8) if (type >= lv2_file_type::sdata)
{ {
// check for sdata // check for sdata
switch (*static_cast<const be_t<u64>*>(arg)) switch (type)
{ {
case 0x18000000010: case lv2_file_type::sdata:
{ {
// check if the file has the NPD header, or else assume its not encrypted // check if the file has the NPD header, or else assume its not encrypted
u32 magic; u32 magic;
@ -419,7 +397,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
auto sdata_file = std::make_unique<EDATADecrypter>(std::move(file)); auto sdata_file = std::make_unique<EDATADecrypter>(std::move(file));
if (!sdata_file->ReadHeader()) if (!sdata_file->ReadHeader())
{ {
return {CELL_EFSSPECIFIC, path}; return {CELL_EFSSPECIFIC};
} }
file.reset(std::move(sdata_file)); file.reset(std::move(sdata_file));
@ -428,7 +406,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
break; break;
} }
// edata // edata
case 0x2: case lv2_file_type::edata:
{ {
// check if the file has the NPD header, or else assume its not encrypted // check if the file has the NPD header, or else assume its not encrypted
u32 magic; u32 magic;
@ -440,7 +418,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
auto sdata_file = std::make_unique<EDATADecrypter>(std::move(file), edatkeys->devKlic.load(), edatkeys->rifKey.load()); auto sdata_file = std::make_unique<EDATADecrypter>(std::move(file), edatkeys->devKlic.load(), edatkeys->rifKey.load());
if (!sdata_file->ReadHeader()) if (!sdata_file->ReadHeader())
{ {
return {CELL_EFSSPECIFIC, path}; return {CELL_EFSSPECIFIC};
} }
file.reset(std::move(sdata_file)); file.reset(std::move(sdata_file));
@ -452,7 +430,49 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo
} }
} }
return {.error = {}, .ppath = path, .file = std::move(file)}; return {.error = {}, .file = std::move(file)};
}
lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size)
{
if (vpath.empty())
{
return {CELL_ENOENT};
}
std::string path;
const std::string local_path = vfs::get(vpath, nullptr, &path);
const auto mp = lv2_fs_object::get_mp(path);
if (vpath.find_first_not_of('/') == umax)
{
return {CELL_EISDIR, path};
}
if (local_path.empty())
{
return {CELL_ENOTMOUNTED, path};
}
lv2_file_type type = lv2_file_type::regular;
if (size == 8)
{
// see lv2_file::open_raw
switch (*static_cast<const be_t<u64>*>(arg))
{
case 0x18000000010: type = lv2_file_type::sdata; break;
case 0x2: type = lv2_file_type::edata; break;
default:
break;
}
}
auto [error, file] = open_raw(local_path, flags, mode, type, mp);
return {.error = error, .ppath = std::move(path), .real_path = std::move(local_path), .file = std::move(file), .type = type};
} }
error_code sys_fs_open(ppu_thread& ppu, vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode, vm::cptr<void> arg, u64 size) error_code sys_fs_open(ppu_thread& ppu, vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode, vm::cptr<void> arg, u64 size)
@ -464,7 +484,7 @@ error_code sys_fs_open(ppu_thread& ppu, vm::cptr<char> path, s32 flags, vm::ptr<
if (!path) if (!path)
return CELL_EFAULT; return CELL_EFAULT;
auto [error, ppath, file] = lv2_file::open(path.get_ptr(), flags, mode, arg.get_ptr(), size); auto [error, ppath, real, file, type] = lv2_file::open(path.get_ptr(), flags, mode, arg.get_ptr(), size);
if (error) if (error)
{ {
@ -476,42 +496,25 @@ error_code sys_fs_open(ppu_thread& ppu, vm::cptr<char> path, s32 flags, vm::ptr<
return {error, path}; return {error, path};
} }
lv2_file_type type = lv2_file_type::regular; if (type >= lv2_file_type::sdata)
if (size == 8)
{ {
// see lv2_file::open sys_fs.warning("sys_fs_open(): NPDRM detected");
switch (vm::read64(arg.addr()))
{
case 0x18000000010:
case 0x2:
{
type = lv2_file_type::npdrm;
sys_fs.warning("sys_fs_open(): NPDRM detected");
break;
}
default:
break;
}
}
if (type == lv2_file_type::npdrm) if (const u32 id = idm::import<lv2_fs_object, lv2_file>([&ppath = ppath, &file = file, mode, flags, &real = real, &type = type]() -> std::shared_ptr<lv2_file>
{
if (const u32 id = idm::import<lv2_fs_object, lv2_file>([&ppath = ppath, &file = file, mode, flags]() -> std::shared_ptr<lv2_file>
{ {
if (!g_fxo->get<loaded_npdrm_keys>()->npdrm_fds.try_inc(16)) if (!g_fxo->get<loaded_npdrm_keys>()->npdrm_fds.try_inc(16))
{ {
return nullptr; return nullptr;
} }
return std::make_shared<lv2_file>(ppath, std::move(file), mode, flags, lv2_file_type::npdrm); return std::make_shared<lv2_file>(ppath, std::move(file), mode, flags, real, type);
})) }))
{ {
*fd = id; *fd = id;
return CELL_OK; return CELL_OK;
} }
} }
else if (const u32 id = idm::make<lv2_fs_object, lv2_file>(ppath, std::move(file), mode, flags)) else if (const u32 id = idm::make<lv2_fs_object, lv2_file>(ppath, std::move(file), mode, flags, real))
{ {
*fd = id; *fd = id;
return CELL_OK; return CELL_OK;
@ -622,7 +625,7 @@ error_code sys_fs_close(ppu_thread& ppu, u32 fd)
const auto file = idm::withdraw<lv2_fs_object, lv2_file>(fd, [](lv2_file& file) const auto file = idm::withdraw<lv2_fs_object, lv2_file>(fd, [](lv2_file& file)
{ {
if (file.type == lv2_file_type::npdrm) if (file.type >= lv2_file_type::sdata)
{ {
g_fxo->get<loaded_npdrm_keys>()->npdrm_fds--; g_fxo->get<loaded_npdrm_keys>()->npdrm_fds--;
} }
@ -674,11 +677,9 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
return {CELL_ENOTDIR, path}; return {CELL_ENOTDIR, path};
} }
fs::dir dir; std::lock_guard lock(mp->mutex);
{
std::lock_guard lock(mp->mutex); fs::dir dir(local_path);
dir.open(local_path);
}
if (!dir) if (!dir)
{ {
@ -1029,9 +1030,10 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to
return CELL_EROFS; return CELL_EROFS;
} }
std::lock_guard lock(mp->mutex); // Done in vfs::host::rename
//std::lock_guard lock(mp->mutex);
if (!vfs::host::rename(local_from, local_to, false)) if (!vfs::host::rename(local_from, local_to, mp, false))
{ {
switch (auto error = fs::g_tls_error) switch (auto error = fs::g_tls_error)
{ {
@ -1266,6 +1268,8 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
return CELL_EBADF; return CELL_EBADF;
} }
std::lock_guard lock(file->mp->mutex);
auto sdata_file = std::make_unique<EDATADecrypter>(lv2_file::make_view(file, arg->offset)); auto sdata_file = std::make_unique<EDATADecrypter>(lv2_file::make_view(file, arg->offset));
if (!sdata_file->ReadHeader()) if (!sdata_file->ReadHeader())
@ -1282,7 +1286,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
return nullptr; return nullptr;
} }
return std::make_shared<lv2_file>(file, std::move(stream), file.mode, file.flags, lv2_file_type::npdrm); return std::make_shared<lv2_file>(file, std::move(stream), file.mode, file.flags, file.real_path, lv2_file_type::sdata);
})) }))
{ {
arg->out_code = CELL_OK; arg->out_code = CELL_OK;

View File

@ -3,9 +3,9 @@
#include "Emu/Memory/vm_ptr.h" #include "Emu/Memory/vm_ptr.h"
#include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/ErrorCodes.h"
#include "Utilities/File.h" #include "Utilities/File.h"
#include "Utilities/mutex.h"
#include <string> #include <string>
#include <mutex>
// Open Flags // Open Flags
enum : s32 enum : s32
@ -134,7 +134,8 @@ enum class lv2_mp_flag
enum class lv2_file_type enum class lv2_file_type
{ {
regular = 0, regular = 0,
npdrm, sdata,
edata,
}; };
struct lv2_fs_mount_point struct lv2_fs_mount_point
@ -143,9 +144,17 @@ struct lv2_fs_mount_point
const u32 block_size = 4096; const u32 block_size = 4096;
const bs_t<lv2_mp_flag> flags{}; const bs_t<lv2_mp_flag> flags{};
shared_mutex mutex; mutable std::recursive_mutex mutex;
}; };
extern lv2_fs_mount_point g_mp_sys_dev_hdd0;
extern lv2_fs_mount_point g_mp_sys_dev_hdd1;
extern lv2_fs_mount_point g_mp_sys_dev_usb;
extern lv2_fs_mount_point g_mp_sys_dev_bdvd;
extern lv2_fs_mount_point g_mp_sys_app_home;
extern lv2_fs_mount_point g_mp_sys_host_root;
extern lv2_fs_mount_point g_mp_sys_dev_flash;
struct lv2_fs_object struct lv2_fs_object
{ {
using id_type = lv2_fs_object; using id_type = lv2_fs_object;
@ -185,41 +194,61 @@ struct lv2_fs_object
struct lv2_file final : lv2_fs_object struct lv2_file final : lv2_fs_object
{ {
const fs::file file; fs::file file;
const s32 mode; const s32 mode;
const s32 flags; const s32 flags;
std::string real_path;
const lv2_file_type type; const lv2_file_type type;
// Stream lock // Stream lock
atomic_t<u32> lock{0}; atomic_t<u32> lock{0};
lv2_file(std::string_view filename, fs::file&& file, s32 mode, s32 flags, lv2_file_type type = {}) // Some variables for convinience of data restoration
struct save_restore_t
{
u64 seek_pos;
u64 atime;
u64 mtime;
} restore_data{};
lv2_file(std::string_view filename, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {})
: lv2_fs_object(lv2_fs_object::get_mp(filename), filename) : lv2_fs_object(lv2_fs_object::get_mp(filename), filename)
, file(std::move(file)) , file(std::move(file))
, mode(mode) , mode(mode)
, flags(flags) , flags(flags)
, real_path(real_path)
, type(type) , type(type)
{ {
} }
lv2_file(const lv2_file& host, fs::file&& file, s32 mode, s32 flags, lv2_file_type type = {}) lv2_file(const lv2_file& host, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {})
: lv2_fs_object(host.mp, host.name.data()) : lv2_fs_object(host.mp, host.name.data())
, file(std::move(file)) , file(std::move(file))
, mode(mode) , mode(mode)
, flags(flags) , flags(flags)
, real_path(real_path)
, type(type) , type(type)
{ {
} }
struct open_raw_result_t
{
CellError error;
fs::file file;
};
struct open_result_t struct open_result_t
{ {
CellError error; CellError error;
std::string ppath; std::string ppath;
std::string real_path;
fs::file file; fs::file file;
lv2_file_type type;
}; };
// Open a file with wrapped logic of sys_fs_open // Open a file with wrapped logic of sys_fs_open
static open_result_t open(std::string_view path, s32 flags, s32 mode, const void* arg = {}, u64 size = 0); static open_raw_result_t open_raw(const std::string& path, s32 flags, s32 mode, lv2_file_type type = lv2_file_type::regular, const lv2_fs_mount_point* mp = nullptr);
static open_result_t open(std::string_view vpath, s32 flags, s32 mode, const void* arg = {}, u64 size = 0);
// File reading with intermediate buffer // File reading with intermediate buffer
static u64 op_read(const fs::file& file, vm::ptr<void> buf, u64 size); static u64 op_read(const fs::file& file, vm::ptr<void> buf, u64 size);

View File

@ -21,7 +21,7 @@ static error_code overlay_load_module(vm::ptr<u32> ovlmid, const std::string& vp
{ {
if (!src) if (!src)
{ {
auto [fs_error, ppath, lv2_file] = lv2_file::open(vpath, 0, 0); auto [fs_error, ppath, path, lv2_file, type] = lv2_file::open(vpath, 0, 0);
if (fs_error) if (fs_error)
{ {

View File

@ -156,7 +156,7 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr<s
if (!src) if (!src)
{ {
auto [fs_error, ppath, lv2_file] = lv2_file::open(vpath, 0, 0); auto [fs_error, ppath, path0, lv2_file, type] = lv2_file::open(vpath, 0, 0);
if (fs_error) if (fs_error)
{ {

View File

@ -242,7 +242,7 @@ error_code sys_spu_image_open(ppu_thread& ppu, vm::ptr<sys_spu_image> img, vm::c
sys_spu.warning("sys_spu_image_open(img=*0x%x, path=%s)", img, path); sys_spu.warning("sys_spu_image_open(img=*0x%x, path=%s)", img, path);
auto [fs_error, ppath, file] = lv2_file::open(path.get_ptr(), 0, 0); auto [fs_error, ppath, path0, file, type] = lv2_file::open(path.get_ptr(), 0, 0);
if (fs_error) if (fs_error)
{ {

View File

@ -3,6 +3,8 @@
#include "System.h" #include "System.h"
#include "VFS.h" #include "VFS.h"
#include "Cell/lv2/sys_fs.h"
#include "Utilities/mutex.h" #include "Utilities/mutex.h"
#include "Utilities/StrUtil.h" #include "Utilities/StrUtil.h"
@ -10,6 +12,8 @@
#include <Windows.h> #include <Windows.h>
#endif #endif
#include <thread>
struct vfs_directory struct vfs_directory
{ {
// Real path (empty if root or not exists) // Real path (empty if root or not exists)
@ -706,18 +710,77 @@ 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(__rdtsc())); return fmt::format(u8"%s/%s%s", dev_root, fmt::base57(std::hash<std::string>()(path)), fmt::base57(__rdtsc()));
} }
bool vfs::host::rename(const std::string& from, const std::string& to, bool overwrite) bool vfs::host::rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite)
{ {
while (!fs::rename(from, to, overwrite)) #ifdef _WIN32
constexpr auto& delim = "/\\";
#else
constexpr auto& delim = "/";
#endif
// Lock mount point, close file descriptors, retry
const auto from0 = std::string_view(from).substr(0, from.find_last_not_of(delim) + 1);
const auto escaped_from = fs::escape_path(from);
// Lock app_home as well because it could be in the same drive as current mount point (TODO)
std::scoped_lock lock(mp->mutex, g_mp_sys_app_home.mutex);
auto check_path = [&](std::string_view path)
{ {
// Try to ignore access error in order to prevent spurious failure return path.starts_with(from) && (path.size() == from.size() || path[from.size()] == delim[0] || path[from.size()] == delim[1]);
};
idm::select<lv2_fs_object, lv2_file>([&](u32 id, lv2_file& file)
{
if (check_path(fs::escape_path(file.real_path)))
{
verify(HERE), file.mp == mp || file.mp == &g_mp_sys_app_home;
file.restore_data.seek_pos = file.file.pos();
file.file.close(); // Actually close it!
}
});
bool res = false;
for (;; std::this_thread::yield())
{
if (fs::rename(from, to, overwrite))
{
res = true;
break;
}
if (Emu.IsStopped() || fs::g_tls_error != fs::error::acces) if (Emu.IsStopped() || fs::g_tls_error != fs::error::acces)
{ {
return false; res = false;
break;
} }
} }
return true; const auto fs_error = fs::g_tls_error;
idm::select<lv2_fs_object, lv2_file>([&](u32 id, lv2_file& file)
{
const auto escaped_real = fs::escape_path(file.real_path);
if (check_path(escaped_real))
{
// Update internal path
if (res)
{
file.real_path = to + (escaped_real != escaped_from ? '/' + file.real_path.substr(from0.size()) : ""s);
}
// Reopen with ignored TRUNC, APPEND, CREATE and EXCL flags
auto res0 = lv2_file::open_raw(file.real_path, file.flags & CELL_FS_O_ACCMODE, file.mode, file.type, file.mp);
file.file = std::move(res0.file);
verify(HERE), file.file.operator bool();
file.file.seek(file.restore_data.seek_pos);
}
});
fs::g_tls_error = fs_error;
return res;
} }
bool vfs::host::unlink(const std::string& path, const std::string& dev_root) bool vfs::host::unlink(const std::string& path, const std::string& dev_root)
@ -754,7 +817,7 @@ bool vfs::host::unlink(const std::string& path, const std::string& dev_root)
#endif #endif
} }
bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, bool remove_root) bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root)
{ {
#ifdef _WIN32 #ifdef _WIN32
if (remove_root) if (remove_root)
@ -762,12 +825,12 @@ bool vfs::host::remove_all(const std::string& path, const std::string& dev_root,
// Rename to special dummy folder which will be ignored by VFS (but opened file handles can still read or write it) // 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); const std::string dummy = hash_path(path, dev_root);
if (!vfs::host::rename(path, dummy, false)) if (!vfs::host::rename(path, dummy, mp, false))
{ {
return false; return false;
} }
if (!vfs::host::remove_all(dummy, dev_root, false)) if (!vfs::host::remove_all(dummy, dev_root, mp, false))
{ {
return false; return false;
} }
@ -802,7 +865,7 @@ bool vfs::host::remove_all(const std::string& path, const std::string& dev_root,
} }
else else
{ {
if (!vfs::host::remove_all(path + '/' + entry.name, dev_root)) if (!vfs::host::remove_all(path + '/' + entry.name, dev_root, mp))
{ {
return false; return false;
} }

View File

@ -4,6 +4,8 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
struct lv2_fs_mount_point;
namespace vfs namespace vfs
{ {
// Mount VFS device // Mount VFS device
@ -24,12 +26,12 @@ namespace vfs
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);
// Call fs::rename with retry on access error // Call fs::rename with retry on access error
bool rename(const std::string& from, const std::string& to, bool overwrite); bool rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite);
// Delete file without deleting its contents, emulated with MoveFileEx on Windows // Delete file without deleting its contents, emulated with MoveFileEx on Windows
bool unlink(const std::string& path, const std::string& dev_root); bool unlink(const std::string& path, const std::string& dev_root);
// Delete folder contents using rename, done atomically if remove_root is true // Delete folder contents using rename, done atomically if remove_root is true
bool remove_all(const std::string& path, const std::string& dev_root, bool remove_root = true); bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true);
} }
} }

View File

@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/Cell/lv2/sys_fs.h"
#include "TRP.h" #include "TRP.h"
#include "Crypto/sha1.h" #include "Crypto/sha1.h"
#include "Utilities/StrUtil.h" #include "Utilities/StrUtil.h"
@ -41,7 +42,11 @@ bool TRPLoader::Install(const std::string& dest, bool show)
{ {
trp_f.seek(entry.offset); trp_f.seek(entry.offset);
buffer.resize(entry.size); buffer.resize(entry.size);
if (!trp_f.read(buffer)) continue; // ??? if (!trp_f.read(buffer))
{
trp_log.error("Failed to read TRPEntry at: offset=0x%x, size=0x%x", entry.offset, entry.size);
continue; // ???
}
// Create the file in the temporary directory // Create the file in the temporary directory
success = fs::write_file(temp + vfs::escape(entry.name), fs::create + fs::excl, buffer); success = fs::write_file(temp + vfs::escape(entry.name), fs::create + fs::excl, buffer);
@ -53,7 +58,7 @@ bool TRPLoader::Install(const std::string& dest, bool show)
if (success) if (success)
{ {
success = vfs::host::remove_all(local_path, Emu.GetHddDir(), true) || !fs::is_dir(local_path); success = vfs::host::remove_all(local_path, Emu.GetHddDir(), &g_mp_sys_dev_hdd0, true) || !fs::is_dir(local_path);
if (success) if (success)
{ {