rpcs3/rpcs3/Emu/VFS.cpp
2019-08-22 02:13:39 +03:00

503 lines
8.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "stdafx.h"
#include "IdManager.h"
#include "System.h"
#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;
};
struct vfs_manager
{
shared_mutex mutex;
// VFS root
vfs_directory root;
};
bool vfs::mount(std::string_view vpath, std::string_view path)
{
const auto table = g_fxo->get<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();
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);
}
}
}
std::string vfs::get(std::string_view vpath, std::vector<std::string>* out_dir)
{
const auto table = g_fxo->get<vfs_manager>();
reader_lock lock(table->mutex);
// Resulting path fragments: decoded ones
std::vector<std::string_view> result;
result.reserve(vpath.size() / 2);
// Mounted path
std::string_view result_base;
if (vpath.empty())
{
// 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('/');
if (pos == 0)
{
// Relative path: point to non-existent location
return fs::get_config_dir() + "delete_this_dir.../delete_this...";
}
if (pos == -1)
{
// Absolute path: finalize
for (auto it = list.rbegin(), rend = list.rend(); it != rend; it++)
{
if (auto* dir = *it; dir && (!dir->path.empty() || list.size() == 1))
{
// Save latest valid mount path
result_base = dir->path;
// Erase unnecessary path fragments
result.erase(result.begin(), result.begin() + (std::distance(it, rend) - 1));
// 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;
}
// 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;
if (dir.second.path == "/"sv)
{
if (vpath.empty())
{
return {};
}
// Handle /host_root (not escaped, not processed)
return std::string{vpath.substr(1)};
}
break;
}
}
}
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;
}
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);
}