rpcsx/rpcs3/Emu/VFS.cpp

504 lines
8.3 KiB
C++
Raw Normal View History

#include "stdafx.h"
2016-06-02 17:16:01 +02:00
#include "IdManager.h"
#include "System.h"
2016-04-14 00:59:00 +02:00
#include "VFS.h"
#include "Utilities/mutex.h"
#include "Utilities/StrUtil.h"
struct vfs_directory
{
// Real path (empty if root or not exists)
std::string path;
// Virtual subdirectories (vector because only vector allows incomplete types)
std::vector<std::pair<std::string, vfs_directory>> dirs;
};
2016-06-02 17:16:01 +02:00
struct vfs_manager
{
shared_mutex mutex;
// VFS root
vfs_directory root;
2016-06-02 17:16:01 +02:00
};
2016-04-14 00:59:00 +02:00
bool vfs::mount(std::string_view vpath, std::string_view path)
2016-04-14 00:59:00 +02:00
{
2016-06-02 17:16:01 +02:00
const auto table = fxm::get_always<vfs_manager>();
std::lock_guard lock(table->mutex);
if (vpath.empty())
{
// Empty relative path, should set relative path base; unsupported
return false;
}
for (std::vector<vfs_directory*> list{&table->root};;)
{
// Skip one or more '/'
const auto pos = vpath.find_first_not_of('/');
if (pos == 0)
{
// Mounting relative path is not supported
return false;
}
if (pos == -1)
{
// Mounting completed
list.back()->path = path;
return true;
}
// Get fragment name
const auto name = vpath.substr(pos, vpath.find_first_of('/', pos) - pos);
vpath.remove_prefix(name.size() + pos);
if (name == ".")
{
// Keep current
continue;
}
if (name == "..")
{
// Root parent is root
if (list.size() == 1)
{
continue;
}
// Go back one level
list.resize(list.size() - 1);
continue;
}
// Find or add
const auto last = list.back();
2016-06-02 17:16:01 +02:00
for (auto& dir : last->dirs)
{
if (dir.first == name)
{
list.push_back(&dir.second);
break;
}
}
if (last == list.back())
{
// Add new entry
list.push_back(&last->dirs.emplace_back(name, vfs_directory{}).second);
}
}
2016-04-14 00:59:00 +02:00
}
std::string vfs::get(std::string_view vpath, std::vector<std::string>* out_dir)
2016-04-14 00:59:00 +02:00
{
2017-09-18 23:03:26 +02:00
const auto table = fxm::get_always<vfs_manager>();
reader_lock lock(table->mutex);
// Resulting path fragments: decoded ones
std::vector<std::string_view> result;
result.reserve(vpath.size() / 2);
2017-09-18 23:03:26 +02:00
// Mounted path
std::string_view result_base;
2016-04-14 00:59:00 +02:00
if (vpath.empty())
2016-04-14 00:59:00 +02:00
{
// Empty relative path (reuse further return)
vpath = ".";
}
for (std::vector<const vfs_directory*> list{&table->root};;)
{
// Skip one or more '/'
const auto pos = vpath.find_first_not_of('/');
2016-04-14 00:59:00 +02:00
if (pos == 0)
2017-09-18 23:03:26 +02:00
{
// Relative path: point to non-existent location
return fs::get_config_dir() + "delete_this_dir.../delete_this...";
2017-09-18 23:03:26 +02:00
}
2016-04-14 00:59:00 +02:00
if (pos == -1)
{
// Absolute path: finalize
for (auto it = list.rbegin(), rend = list.rend(); it != rend; it++)
{
2019-03-05 19:47:28 +01:00
if (auto* dir = *it; dir && (!dir->path.empty() || list.size() == 1))
{
// Save latest valid mount path
result_base = dir->path;
2017-09-18 23:03:26 +02:00
// Erase unnecessary path fragments
result.erase(result.begin(), result.begin() + (std::distance(it, rend) - 1));
2016-04-14 00:59:00 +02:00
// Extract mounted subdirectories (TODO)
if (out_dir)
{
for (auto& pair : dir->dirs)
{
if (!pair.second.path.empty())
{
out_dir->emplace_back(pair.first);
}
}
}
break;
}
}
if (!vpath.empty())
{
// Finalize path with '/'
result.emplace_back("");
}
break;
}
2016-04-14 00:59:00 +02:00
// Get fragment name
const auto name = vpath.substr(pos, vpath.find_first_of('/', pos) - pos);
vpath.remove_prefix(name.size() + pos);
// Process special directories
if (name == ".")
{
// Keep current
continue;
}
if (name == "..")
{
// Root parent is root
if (list.size() == 1)
{
continue;
}
// Go back one level
list.resize(list.size() - 1);
result.resize(result.size() - 1);
continue;
}
const auto last = list.back();
list.push_back(nullptr);
result.push_back(name);
if (!last)
{
continue;
}
for (auto& dir : last->dirs)
{
if (dir.first == name)
{
list.back() = &dir.second;
2018-09-19 13:15:38 +02:00
if (dir.second.path == "/"sv)
{
if (vpath.empty())
{
return {};
}
// Handle /host_root (not escaped, not processed)
return std::string{vpath.substr(1)};
}
2018-09-19 13:15:38 +02:00
break;
}
}
2016-04-14 00:59:00 +02:00
}
if (result_base.empty())
{
// Not mounted
return {};
}
// Escape and merge path fragments
return std::string{result_base} + vfs::escape(fmt::merge(result, "/"));
}
std::string vfs::escape(std::string_view path)
{
std::string result;
result.reserve(path.size());
for (std::size_t i = 0, s = path.size(); i < s; i++)
{
switch (char c = path[i])
{
case '<':
{
result += u8"";
break;
}
case '>':
{
result += u8"";
break;
}
case ':':
{
result += u8"";
break;
}
case '"':
{
result += u8"";
break;
}
case '\\':
{
result += u8"";
break;
}
case '|':
{
result += u8"";
break;
}
case '?':
{
result += u8"";
break;
}
case '*':
{
result += u8"";
break;
}
case char{u8""[0]}:
{
// Escape full-width characters 0xFF01..0xFF5e with (0xFF01)
switch (path[i + 1])
{
case char{u8""[1]}:
{
const uchar c3 = reinterpret_cast<const uchar&>(path[i + 2]);
if (c3 >= 0x81 && c3 <= 0xbf)
{
result += u8"";
}
break;
}
case char{u8""[1]}:
{
const uchar c3 = reinterpret_cast<const uchar&>(path[i + 2]);
if (c3 >= 0x80 && c3 <= 0x9e)
{
result += u8"";
}
break;
}
}
result += c;
break;
}
default:
{
result += c;
break;
}
}
}
return result;
}
std::string vfs::unescape(std::string_view path)
{
std::string result;
result.reserve(path.size());
for (std::size_t i = 0, s = path.size(); i < s; i++)
{
switch (char c = path[i])
{
case char{u8""[0]}:
{
switch (path[i + 1])
{
case char{u8""[1]}:
{
const uchar c3 = reinterpret_cast<const uchar&>(path[i + 2]);
if (c3 >= 0x81 && c3 <= 0xbf)
{
switch (path[i + 2])
{
case char{u8""[2]}:
{
i += 3;
result += c;
continue;
}
case char{u8""[2]}:
{
result += '<';
break;
}
case char{u8""[2]}:
{
result += '>';
break;
}
case char{u8""[2]}:
{
result += ':';
break;
}
case char{u8""[2]}:
{
result += '"';
break;
}
case char{u8""[2]}:
{
result += '\\';
break;
}
case char{u8""[2]}:
{
result += '?';
break;
}
case char{u8""[2]}:
{
result += '*';
break;
}
case char{u8""[2]}:
{
if (i == 0)
{
// Special case: filename starts with full-width $ likely created by vfs::host::unlink
result.resize(1, '.');
return result;
}
[[fallthrough]];
}
default:
{
// Unrecognized character (ignored)
break;
}
}
i += 2;
}
else
{
result += c;
}
break;
}
case char{u8""[1]}:
{
const uchar c3 = reinterpret_cast<const uchar&>(path[i + 2]);
if (c3 >= 0x80 && c3 <= 0x9e)
{
switch (path[i + 2])
{
case char{u8""[2]}:
{
result += '|';
break;
}
default:
{
// Unrecognized character (ignored)
break;
}
}
i += 2;
}
else
{
result += c;
}
break;
}
default:
{
result += c;
break;
}
}
break;
}
default:
{
result += c;
break;
}
}
}
return result;
2016-04-14 00:59:00 +02:00
}
bool vfs::host::rename(const std::string& from, const std::string& to, bool overwrite)
{
while (!fs::rename(from, to, overwrite))
{
// Try to ignore access error in order to prevent spurious failure
if (Emu.IsStopped() || fs::g_tls_error != fs::error::acces)
{
return false;
}
}
return true;
}
bool vfs::host::unlink(const std::string& path)
{
#ifdef _WIN32
if (path.size() < 2 || reinterpret_cast<const u16&>(path.front()) != "//"_u16)
{
// 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 = fmt::format(u8"%s/%s%s", fs::get_parent_dir(path), fmt::base57(std::hash<std::string>()(path)), fmt::base57(__rdtsc()));
if (!fs::rename(path, dummy, true))
{
return false;
}
if (fs::file f{dummy, fs::read + fs::write})
{
// Set to delete on close on last handle
f.set_delete();
return true;
}
// TODO: what could cause this and how to handle it
return true;
}
#endif
return fs::remove_file(path);
}