celestia/src/celcompat/fs.cpp

663 lines
15 KiB
C++

#include "fs.h"
#include <vector>
#include <memory>
#ifdef _WIN32
#include <celutil/winutil.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
#if defined(_MSC_VER) && !defined(__clang__)
// M$VC++ build without C++ exceptions are not supported yet
#ifndef __cpp_exceptions
#define __cpp_exceptions 1
#endif
#endif
#if ! __cpp_exceptions
#include <cstdlib>
#endif
#ifdef _WIN32
#define W(s) L##s
#else
#define W(s) s
#endif
namespace celestia
{
namespace filesystem
{
// we should skip "." and ".."
template<typename CharT> static bool is_special_dir(const CharT s[])
{
return s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'));
}
path operator/(const path& lhs, const path& rhs)
{
return path(lhs) /= rhs;
}
std::ostream& operator<<(std::ostream& os, const path& p)
{
os << '"' << p.string() << '"';
return os;
}
path u8path(const std::string& source)
{
#ifdef _WIN32
return UTF8ToWide(source);
#else
return source; // FIXME
#endif
}
#ifdef _WIN32
void path::fixup_separators()
{
if (m_fmt == native_format)
return;
string_type::size_type pos = 0;
for(;;)
{
pos = m_path.find(L'/', pos);
if (pos == path::string_type::npos)
break;
m_path[pos] = preferred_separator;
pos++;
}
}
#endif
path path::filename() const
{
auto pos = m_path.rfind(preferred_separator);
if (pos == string_type::npos)
pos = 0;
else
pos++;
auto fn = m_path.substr(pos);
if (is_special_dir(fn.c_str()))
return path();
path p(fn);
return p;
}
path path::stem() const
{
auto fn = filename().native();
auto pos = fn.rfind(W('.'));
if (pos == 0 || pos == string_type::npos)
return fn;
return fn.substr(0, pos);
}
path path::extension() const
{
auto fn = filename().native();
auto pos = fn.rfind(W('.'));
if (pos == 0 || pos == string_type::npos)
return path();
return fn.substr(pos);
}
path path::parent_path() const
{
auto pos = m_path.rfind(preferred_separator);
if (pos == 0)
return path(m_path.substr(0, 1));
if (pos == string_type::npos)
return path();
return path(m_path.substr(0, pos));
}
path& path::replace_extension(const path& replacement)
{
auto pos = m_path.rfind(W('.'));
if (pos != (m_path.length() - 1))
{
if (pos != 0 && pos != string_type::npos)
m_path.resize(pos);
}
else
{
// special case for "/dir/" or "/dir/."
path basename = filename();
if (!(basename.empty() || basename == W(".")))
m_path.resize(pos);
}
if (replacement.empty())
return *this;
if (replacement.m_path[0] != W('.'))
m_path += W('.');
m_path += replacement.m_path;
return *this;
}
bool path::is_absolute() const
{
#ifdef _WIN32
return m_path[0] == preferred_separator || m_path[1] == L':';
#else
return m_path[0] == preferred_separator;
#endif
}
struct directory_iterator::SearchImpl
{
path m_path {};
directory_options m_options { directory_options::none };
#ifdef _WIN32
HANDLE m_handle { INVALID_HANDLE_VALUE };
#else
DIR *m_dir { nullptr };
#endif
explicit SearchImpl(const path& p, directory_options options) :
m_options(options),
m_path(p)
{
};
~SearchImpl();
bool advance(directory_entry&, std::error_code*);
};
directory_iterator::SearchImpl::~SearchImpl()
{
#ifdef _WIN32
if (m_handle != INVALID_HANDLE_VALUE)
{
FindClose(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
#else
if (m_dir != nullptr)
{
closedir(m_dir);
m_dir = nullptr;
}
#endif
}
bool directory_iterator::SearchImpl::advance(directory_entry& entry, std::error_code* ec)
{
if (ec != nullptr)
ec->clear();
#ifdef _WIN32
WIN32_FIND_DATAW findData;
if (m_handle == INVALID_HANDLE_VALUE)
{
m_handle = FindFirstFileW((m_path / L"*").c_str(), &findData);
if (m_handle == INVALID_HANDLE_VALUE)
{
std::error_code m_ec = std::error_code(GetLastError(), std::system_category());
m_ec = std::error_code(GetLastError(), std::system_category());
if (m_ec.value() == ERROR_ACCESS_DENIED && bool(m_options & directory_options::skip_permission_denied))
m_ec.clear();
if (m_ec)
{
if (ec != nullptr)
*ec = m_ec;
else
#if __cpp_exceptions
throw filesystem_error(m_ec, "celfs::directory_iterator::SearchImpl::advance error");
#else
std::abort();
#endif
}
return false;
}
}
else
{
if (!FindNextFileW(m_handle, &findData))
return false;
}
while (is_special_dir(findData.cFileName))
{
if (!FindNextFileW(m_handle, &findData))
return false;
}
entry = directory_entry(std::move(m_path / findData.cFileName));
return true;
#else
if (m_dir == nullptr)
{
m_dir = opendir(m_path.c_str());
if (m_dir == nullptr)
{
std::error_code m_ec = std::error_code(errno, std::system_category());
if (m_ec.value() == EACCES && bool(m_options & directory_options::skip_permission_denied))
m_ec.clear();
if (m_ec)
{
if (ec != nullptr)
*ec = m_ec;
else
#if __cpp_exceptions
throw filesystem_error(m_ec, "celfs::directory_iterator::SearchImpl::advance error");
#else
std::abort();
#endif
}
return false;
}
}
struct dirent* ent = nullptr;
do
{
ent = readdir(m_dir);
if (ent == nullptr)
return false;
}
while (is_special_dir(ent->d_name));
entry = directory_entry(std::move(m_path / ent->d_name));
return true;
#endif
}
directory_iterator::directory_iterator(const path& p) :
directory_iterator::directory_iterator(p, directory_options::none, nullptr)
{
}
directory_iterator::directory_iterator(const path& p, directory_options options) :
directory_iterator::directory_iterator(p, options, nullptr)
{
}
directory_iterator::directory_iterator(const path& p, std::error_code& ec) :
directory_iterator::directory_iterator(p, directory_options::none, &ec)
{
}
directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) :
directory_iterator::directory_iterator(p, directory_options::none, &ec)
{
}
directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code* ec, bool advance) :
m_path(p),
m_search(new SearchImpl(p, options))
{
if (advance)
__increment(ec);
else
m_entry = directory_entry(p);
}
void directory_iterator::reset()
{
*this = directory_iterator();
}
bool directory_iterator::operator==(const directory_iterator& other) const noexcept
{
return m_search == other.m_search && m_entry == other.m_entry;
}
directory_iterator& directory_iterator::operator++()
{
return __increment();
}
directory_iterator& directory_iterator::increment(std::error_code &ec)
{
return __increment(&ec);
}
directory_iterator& directory_iterator::__increment(std::error_code *ec)
{
if (m_search != nullptr && !m_search->advance(m_entry, ec))
reset();
return *this;
}
struct recursive_directory_iterator::DirStack
{
std::vector<directory_iterator> m_dirIters;
};
recursive_directory_iterator::recursive_directory_iterator(const path& p) : recursive_directory_iterator(p, directory_options::none, nullptr)
{
}
recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) : recursive_directory_iterator(p, options, nullptr)
{
}
recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) : recursive_directory_iterator(p, directory_options::none, &ec)
{
}
recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) : recursive_directory_iterator(p, options, &ec)
{
}
recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code* ec)
{
m_options = options;
if (m_dirs == nullptr)
m_dirs = std::shared_ptr<DirStack>(new DirStack);
m_iter = directory_iterator(p, options, ec, false);
__increment(ec); // advance to the first element
if (m_iter == end(m_iter))
reset();
}
bool recursive_directory_iterator::operator==(const recursive_directory_iterator& other) const noexcept
{
return m_depth == other.m_depth &&
m_pending == other.m_pending &&
m_dirs == other.m_dirs &&
m_iter == other.m_iter;
}
recursive_directory_iterator& recursive_directory_iterator::operator++()
{
return __increment();
}
recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code &ec)
{
return __increment(&ec);
}
recursive_directory_iterator& recursive_directory_iterator::__increment(std::error_code *ec)
{
if (m_dirs == nullptr)
return *this;
m_iter.__increment(ec);
while (m_iter == end(m_iter))
{
if (m_dirs->m_dirIters.empty())
{
reset();
return *this;
}
m_iter = std::move(m_dirs->m_dirIters.back());
m_iter.__increment(ec);
m_dirs->m_dirIters.pop_back();
}
auto path = m_iter->path();
if (m_pending && (path.empty() || is_directory(path)))
{
m_dirs->m_dirIters.emplace_back(m_iter);
m_iter = directory_iterator(path, m_options, ec, false);
}
return *this;
}
void recursive_directory_iterator::pop()
{
std::error_code ec;
pop(ec);
}
void recursive_directory_iterator::pop(std::error_code& ec)
{
if (m_dirs->m_dirIters.empty())
ec = std::error_code(errno, std::system_category()); // filesystem_error
else
m_dirs->m_dirIters.pop_back();
}
void recursive_directory_iterator::reset()
{
*this = recursive_directory_iterator();
}
uintmax_t file_size(const path& p, std::error_code& ec) noexcept
{
#ifdef _WIN32
WIN32_FILE_ATTRIBUTE_DATA attr;
LARGE_INTEGER fileSize = { 0 };
if (GetFileAttributesExW(p.c_str(), GetFileExInfoStandard, &attr))
{
fileSize.LowPart = attr.nFileSizeLow;
fileSize.HighPart = attr.nFileSizeHigh;
return static_cast<uintmax_t>(fileSize.QuadPart);
}
else
{
ec = std::error_code(errno, std::system_category());
return static_cast<uintmax_t>(-1);
}
#else
struct stat stat_buf;
int rc = stat(p.c_str(), &stat_buf);
if (rc == -1)
{
ec = std::error_code(errno, std::system_category());
return static_cast<uintmax_t>(-1);
}
return static_cast<uintmax_t>(stat_buf.st_size);
#endif
}
uintmax_t file_size(const path& p)
{
std::error_code ec;
uintmax_t s = file_size(p, ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::file_size error");
#else
std::abort();
#endif
return s;
}
bool exists(const path& p, std::error_code& ec) noexcept
{
ec.clear();
#ifdef _WIN32
DWORD attr = GetFileAttributesW(&p.native()[0]);
if (attr != INVALID_FILE_ATTRIBUTES)
return true;
switch (GetLastError())
{
// behave like boost::filesystem
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_NAME:
case ERROR_INVALID_DRIVE:
case ERROR_NOT_READY:
case ERROR_INVALID_PARAMETER:
case ERROR_BAD_PATHNAME:
case ERROR_BAD_NETPATH:
return false;
default:
break;
}
#else
struct stat buf;
if (stat(p.c_str(), &buf) == 0)
return true;
if (errno == ENOENT)
return false;
#endif
ec = std::error_code(errno, std::system_category());
return false;
}
bool exists(const path& p)
{
std::error_code ec;
bool r = exists(p, ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::exists error");
#else
std::abort();
#endif
return r;
}
bool is_directory(const path& p, std::error_code& ec) noexcept
{
ec.clear();
#ifdef _WIN32
DWORD attr = GetFileAttributesW(&p.native()[0]);
if (attr == INVALID_FILE_ATTRIBUTES)
{
ec = std::error_code(errno, std::system_category());
return false;
}
return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
struct stat buf;
if (stat(p.c_str(), &buf) != 0)
{
ec = std::error_code(errno, std::system_category());
return false;
}
return S_ISDIR(buf.st_mode);
#endif
}
bool is_directory(const path& p)
{
std::error_code ec;
bool r = is_directory(p, ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::is_directory error");
#else
std::abort();
#endif
return r;
}
bool create_directory(const path& p, std::error_code& ec) noexcept
{
bool r;
#ifdef _WIN32
r = _wmkdir(p.c_str()) == 0;
#else
r = mkdir(p.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
#endif
if (!r)
ec = std::error_code(errno, std::system_category());
return r;
}
bool create_directory(const path& p)
{
std::error_code ec;
bool r = create_directory(p, ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::create_directory error");
#else
std::abort();
#endif
return r;
}
path current_path()
{
std::error_code ec;
path p = current_path(ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::current_path error");
#else
std::abort();
#endif
return p;
}
path current_path(std::error_code& ec)
{
#ifdef _WIN32
std::wstring buffer(MAX_PATH + 1, 0);
DWORD r = GetModuleFileNameW(nullptr, &buffer[0], MAX_PATH);
if (r == 0)
{
ec = std::error_code(errno, std::system_category());
return path();
}
auto pos = buffer.find_last_of(L"\\/");
return buffer.substr(0, pos);
#else
std::string buffer(256, 0);
char *r = getcwd(&buffer[0], buffer.size());
if (r == nullptr)
{
ec = std::error_code(errno, std::system_category());
return path();
}
return buffer;
#endif
}
void current_path(const path& p)
{
std::error_code ec;
current_path(p, ec);
if (ec)
#if __cpp_exceptions
throw filesystem_error(ec, "celfs::current_path error");
#else
std::abort();
#endif
}
void current_path(const path& p, std::error_code& ec) noexcept
{
#ifdef _WIN32
BOOL ret = SetCurrentDirectoryW(p.c_str());
if (!ret)
ec = std::error_code(errno, std::system_category());
#else
int ret = chdir(p.c_str());
if (ret != 0)
ec = std::error_code(errno, std::system_category());
#endif
}
}
}