447 lines
12 KiB
C++
447 lines
12 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: FS.cpp
|
|
Date: 2021-6-16
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "FS.hpp"
|
|
#include "Source/Locale/Locale.hpp"
|
|
|
|
namespace Aurora::IO::FS
|
|
{
|
|
static bool IsMagicCharacter(char c)
|
|
{
|
|
return (c == '.') ||
|
|
(c == '!') ||
|
|
(c == '~') ||
|
|
(c == '?') ||
|
|
(c == '^');
|
|
}
|
|
|
|
static void ResolveAbsolutePath(bool requireMountExpand, bool requireSanitization, const AuString &path, AuString &result)
|
|
{
|
|
AuString buffer;
|
|
bool isResource {};
|
|
buffer.reserve(4096);
|
|
result.reserve(4096);
|
|
|
|
/**
|
|
Resolve special character prefixes
|
|
*/
|
|
if ((path.size() >= 2) && (requireMountExpand))
|
|
{
|
|
if (path[1] == kPathSplitter)
|
|
{
|
|
// Working directory
|
|
if (path[0] == '.')
|
|
{
|
|
SysAssert(Process::GetWorkingDirectory(buffer));
|
|
buffer += kPathSplitter;
|
|
}
|
|
// Binary directory
|
|
else if (path[0] == '^')
|
|
{
|
|
SysAssert(Process::GetProcDirectory(buffer));
|
|
buffer += kPathSplitter;
|
|
}
|
|
// Local Aurora profile
|
|
else if (path[0] == '~')
|
|
{
|
|
SysAssert(FS::GetProfileDomain(buffer));
|
|
}
|
|
// Global Aurora profile
|
|
else if (path[0] == '!')
|
|
{
|
|
SysAssert(FS::GetSystemDomain(buffer));
|
|
}
|
|
else if (path[0] == '?')
|
|
{
|
|
// ...except for this one
|
|
isResource = true;
|
|
}
|
|
else
|
|
{
|
|
buffer.insert(buffer.begin(), path.begin(), path.begin() + 2); // ???
|
|
}
|
|
|
|
buffer.insert(buffer.end(), path.begin() + 2, path.end());
|
|
}
|
|
else
|
|
{
|
|
buffer += path;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer += path;
|
|
}
|
|
|
|
if (requireSanitization)
|
|
{
|
|
/**
|
|
Quickly handle the edge case in some modern UNIX derived systems, and in
|
|
Windows UNC paths, where paths may start with '//' or '\\' respectively
|
|
*/
|
|
if (AuStartsWith(buffer, kDoublePathSplitter))
|
|
{
|
|
result += kDoublePathSplitter;
|
|
}
|
|
else if (buffer.size() && buffer[0] == kPathSplitter)
|
|
{
|
|
result += kPathSplitter;
|
|
}
|
|
|
|
/**
|
|
Technically, UTF-8 strings may contain the "NUL" byte.
|
|
|
|
o Character numbers from U+0000 to U+007F (US-ASCII repertoire)
|
|
correspond to octets 00 to 7F (7 bit US-ASCII paths). A direct
|
|
consequence is that a plain ASCII string is also a valid UTF-8
|
|
string.
|
|
|
|
You know what would suck? If we were targeting an esoteric platform,
|
|
say a kernel, and we we're trying to avoid string exploits by potential
|
|
attackers. I'm sure that would never happen to poor anticheat devs.
|
|
*/
|
|
const auto parts = AuSplitString(buffer, AuString(1, kPathSplitter), true /*ignore empty tokens*/); /// zzzz im going to SLEEP
|
|
for (const auto &ch : parts) // can you tell why FIO shouldn't be in hot paths yet?
|
|
{
|
|
if (ch == "..")
|
|
{
|
|
if (!result.size()) continue;
|
|
|
|
auto i = result.size() - 1;
|
|
|
|
if (i != 0)
|
|
{
|
|
i -= (result[result.size() - 1] == kPathSplitter);
|
|
}
|
|
|
|
while (i > 0 && result[i] != kPathSplitter)
|
|
{
|
|
--i;
|
|
}
|
|
|
|
if (i >= 0)
|
|
{
|
|
result.resize(i);
|
|
result += kPathSplitter;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ((ch.size() == 1) && (IsMagicCharacter(ch[0])))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result += ch;
|
|
result += kPathSplitter;
|
|
}
|
|
|
|
auto i = result.size() - 1;
|
|
if (result[i] == kPathSplitter)
|
|
{
|
|
result.resize(i);
|
|
}
|
|
}
|
|
else // !requireSanitization
|
|
{
|
|
result = buffer;
|
|
}
|
|
|
|
if (isResource)
|
|
{
|
|
AuString path;
|
|
if (!FS::GetSystemResourcePath(result, path))
|
|
{
|
|
result.clear();
|
|
}
|
|
else
|
|
{
|
|
result = path;
|
|
}
|
|
}
|
|
}
|
|
|
|
void /* internal, local export */ _NormalizePath(AuString &str)
|
|
{
|
|
bool requiresExpanding = false;
|
|
bool requiresMountUpdate = false;
|
|
|
|
requiresExpanding = str.size() && IsMagicCharacter(str[0]);
|
|
|
|
if (str.size() == 1)
|
|
{
|
|
if (str[0] == '.'
|
|
// Win32 has no concept of a rootfs
|
|
// However, all users expect paths in userspace programs to assume CWD
|
|
// Since we are not on a NIX operating system, we can assume these mean CWD
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
||
|
|
str[0] == '/' ||
|
|
str[1] == '\\'
|
|
#endif
|
|
)
|
|
{
|
|
AuProcess::GetWorkingDirectory(str);
|
|
return;
|
|
}
|
|
|
|
if (str[0] == '^')
|
|
{
|
|
AuProcess::GetProcDirectory(str);
|
|
return;
|
|
}
|
|
|
|
if (str[0] == '~')
|
|
{
|
|
AuFS::GetProfileDomain(str);
|
|
return;
|
|
}
|
|
|
|
if (str[0] == '!')
|
|
{
|
|
AuFS::GetSystemDomain(str);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// best case -> O(n) wherein we merely check each BYTE with a handful of branch conditions
|
|
int doubleDots = 0;
|
|
int doubleSlash = 0;
|
|
for (auto &character : str)
|
|
{
|
|
if ((character == '\\') || (character == '/'))
|
|
{
|
|
character = kPathSplitter;
|
|
doubleSlash++;
|
|
}
|
|
else
|
|
{
|
|
doubleSlash = 0;
|
|
}
|
|
|
|
if (character == '.')
|
|
{
|
|
doubleDots++;
|
|
}
|
|
else
|
|
{
|
|
doubleDots = 0;
|
|
}
|
|
|
|
requiresExpanding |= doubleDots >= 2;
|
|
requiresExpanding |= doubleSlash >= 2;
|
|
}
|
|
|
|
// plus this, i guess
|
|
if (str.size() >= 2)
|
|
{
|
|
if ((str[1] == '\\') || (str[1] == '/'))
|
|
{
|
|
auto c = str[0];
|
|
if ((c == '.') || (c == '~') || (c == '!') || (c == '?' || (c == '^')))
|
|
{
|
|
requiresMountUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// worst case -> yea have fun
|
|
if (requiresExpanding || requiresMountUpdate)
|
|
{
|
|
AuString temp;
|
|
ResolveAbsolutePath(requiresMountUpdate, requiresExpanding, str, temp);
|
|
str = temp;
|
|
}
|
|
}
|
|
|
|
AUKN_SYM bool ReadString(const AuString &path, AuString &buffer)
|
|
{
|
|
AuByteBuffer fileBuffer;
|
|
|
|
if (!ReadFile(path, fileBuffer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Locale::Encoding::DecodeUTF8(fileBuffer.data(), fileBuffer.size(), buffer, Locale::ECodePage::eUTF8).first != 0;
|
|
}
|
|
|
|
AUKN_SYM bool WriteString(const AuString &path, const AuString &str)
|
|
{
|
|
char bom[3]
|
|
{
|
|
'\xEF', '\xBB', '\xBF'
|
|
};
|
|
|
|
auto pStream = FS::OpenWriteUnique(path);
|
|
if (!pStream)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok {};
|
|
ok = pStream->Write(AuMemoryViewStreamRead { bom });
|
|
ok &= pStream->Write(AuMemoryViewStreamRead { str });
|
|
pStream->Flush();
|
|
pStream->WriteEoS();
|
|
|
|
return ok;
|
|
}
|
|
|
|
AUKN_SYM bool WriteNewString(const AuString &path, const AuString &str)
|
|
{
|
|
char bom[3]
|
|
{
|
|
'\xEF', '\xBB', '\xBF'
|
|
};
|
|
|
|
auto pStream = FS::OpenWriteUnique(path);
|
|
if (!pStream)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pStream->GetLength())
|
|
{
|
|
SysPushErrorIO("File exists: {}", path);
|
|
return {};
|
|
}
|
|
|
|
bool ok {};
|
|
ok = pStream->Write(AuMemoryViewStreamRead { bom });
|
|
ok &= pStream->Write(AuMemoryViewStreamRead { str });
|
|
pStream->Flush();
|
|
pStream->WriteEoS();
|
|
|
|
return ok;
|
|
}
|
|
|
|
AUKN_SYM bool NormalizePath(AuString &out, const AuString &in)
|
|
{
|
|
try
|
|
{
|
|
out = NormalizePathRet(in);
|
|
return out.size();
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static AuUInt GetLastSplitterIndex(const AuString &path)
|
|
{
|
|
AuUInt indexA {}, indexB {};
|
|
|
|
auto a = path.find_last_of('\\');
|
|
if (a != AuString::npos) indexA = a;
|
|
|
|
auto b = path.find_last_of('/');
|
|
if (b != AuString::npos) indexB = b;
|
|
|
|
return AuMax(indexA, indexB);
|
|
}
|
|
|
|
AUKN_SYM bool GetFileFromPath(AuString &out, const AuString &path)
|
|
{
|
|
try
|
|
{
|
|
if (path.empty()) return false;
|
|
if (path[path.size() - 1] == '.') return false;
|
|
|
|
AuUInt max = GetLastSplitterIndex(path);
|
|
if (max == path.size()) return false;
|
|
|
|
out = path.substr(max + 1);
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AUKN_SYM bool GetDirectoryFromPath(AuString &out, const AuString &path)
|
|
{
|
|
try
|
|
{
|
|
if (path.empty()) return false;
|
|
if (path[path.size() - 1] == '.')
|
|
{
|
|
if (path.size() > 2 && (path[path.size() - 2] == '\\' || path[path.size() - 2] == '/'))
|
|
{
|
|
out = path.substr(0, path.size() - 1);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AuUInt max = GetLastSplitterIndex(path);
|
|
if (!max)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (max == path.size())
|
|
{
|
|
out = path;
|
|
return true;
|
|
}
|
|
|
|
out = path.substr(0, max + 1);
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
out.clear();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AUKN_SYM bool GoUpToSeparator(AuString &out, const AuString &path)
|
|
{
|
|
try
|
|
{
|
|
if (path.empty()) return false;
|
|
if (path[path.size() - 1] == '.')
|
|
{
|
|
if (path.size() > 2 && (path[path.size() - 2] == '\\' || path[path.size() - 2] == '/'))
|
|
{
|
|
out = path.substr(0, path.size() - 2);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AuUInt max = GetLastSplitterIndex(path);
|
|
if (!max)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (max == path.size())
|
|
{
|
|
out = path.substr(0, max - 1);
|
|
return true;
|
|
}
|
|
|
|
out = path.substr(0, max);
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
out.clear();
|
|
return false;
|
|
}
|
|
}
|
|
} |