/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: FS.Unix.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "FS.hpp" #include "FS.Generic.hpp" #include #if !defined(_AURUNTIME_GENERICFS) #include #include #include #include #include #if defined(AURORA_IS_LINUX_DERIVED) #include #endif namespace Aurora::IO::FS { bool _MkDir(const AuString &path) { AuString subdir; if ((path.size() > 1) && ((path[path.size() - 1] == '/') || (path[path.size() - 1] == '\\'))) { subdir = path.substr(0, path.size() - 1); } else { subdir = path; } GoUpToSeparator(subdir, subdir); mode_t mode { 0775 }; struct stat s; if (::stat(subdir.c_str(), &s) != -1) { mode = s.st_mode; } return ::mkdir(path.c_str(), mode) == 0; } struct ReadDirStructure : IReadDir { DIR *pDIR {}; struct dirent *pDE {}; bool bFirstTick { true }; bool bDead { false }; StatEx stat; AuString sPath; bool bFast {}; ~ReadDirStructure() { if (this->pDIR) { ::closedir(this->pDIR); } } virtual StatEx *Next() override { bool bTryAgain {}; if (this->bDead) { return {}; } do { if (!(this->pDE = ::readdir(this->pDIR))) { this->bDead = true; return {}; } if (this->pDE->d_name == AuString(".") || this->pDE->d_name == AuString("..")) { bTryAgain = true; continue; } stat.bExists = true; stat.fileName = this->pDE->d_name; if (stat.fileName.empty()) { bTryAgain = true; continue; } stat.path = NormalizePathRet(this->sPath + stat.fileName); if (this->bFast) { // nvm wont work. still need the is type dir/file flags // return &stat; } if (!StatFile(stat.path.c_str(), stat)) { bTryAgain = true; } } while (AuExchange(bTryAgain, false)); return &stat; } }; static AuSPtr ReadDirEx(const AuString &string, bool bFast) { if (string.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return {}; } auto pObj = AuMakeShared(); if (!pObj) { SysPushErrorMem(); return {}; } pObj->bFast = bFast; if (!AuIOFS::NormalizePath(pObj->sPath, string)) { SysPushErrorMem(); return {}; } if (!AuTryInsert(pObj->sPath, '/')) { SysPushErrorMem(); return {}; } pObj->pDIR = ::opendir(pObj->sPath.c_str()); if (!pObj->pDIR) { SysPushErrorIO(); return {}; } return pObj; } AUKN_SYM AuSPtr ReadDir(const AuString &string) { return ReadDirEx(string, false); } AUKN_SYM bool FilesInDirectory(const AuString &string, AuList &files) { auto itr = ReadDirEx(string, true); if (!itr) { return false; } // SECURITY(): if next fails, its indistinguishable from end of file list, and will return true. it kinda sucks while (auto stat = itr->Next()) { if (!stat->bExistsFile) { continue; } if (!AuTryInsert(files, stat->fileName)) { return false; } } return true; } AUKN_SYM bool DirsInDirectory(const AuString &string, AuList &dirs) { auto itr = ReadDirEx(string, true); if (!itr) { return false; } // SECURITY(): if next fails, its indistinguishable from end of file list, and will return true. it kinda sucks while (auto stat = itr->Next()) { if (!stat->bExistsDirectory) { continue; } if (!AuTryInsert(dirs, stat->fileName)) { return false; } } return true; } AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer) { AuMemoryViewWrite writeView; if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } bool bIsStupidFD = AuStartsWith(path, "/proc/") || AuStartsWith(path, "/sys/") || AuStartsWith(path, "/dev/"); auto pFile = OpenReadUnique(path, bIsStupidFD ? EFileAdvisoryLockLevel::eNoSafety : EFileAdvisoryLockLevel::eBlockWrite); SysCheckReturn(pFile, false); bool bIsZero = buffer.readPtr == buffer.base; auto qwLength = pFile->GetLength(); // NOTE: Linux filesystems are such a cluster fuck of unimplemented interfaces and half-assed drivers // It's not unusual for these "files" to not support the required seek operations across NIX-like oses. if (qwLength == 0) { if (bIsStupidFD) { qwLength = 4096 * 10; } else { return true; } } writeView = buffer.GetOrAllocateLinearWriteable(qwLength); if (!writeView) { return {}; } AuUInt uLength { qwLength }; if (!pFile->Read(Memory::MemoryViewStreamWrite { writeView, uLength })) { SysPushErrorIO(); return false; } // NOTE: File devices love to lie // Do not entertain an arbitrarily large page length provided by non-regular fds buffer.writePtr += uLength; if (bIsZero) { AuTryDownsize(buffer, uLength); } return true; } static bool UnixExists(const AuString &path, bool dir) { struct stat s; int err = ::stat(path.c_str(), &s); if (-1 == err) { SysAssert(ENOENT == errno, "General File IO Error, path {}", path); return false; } return dir ? S_ISDIR(s.st_mode) : S_ISREG(s.st_mode); } AUKN_SYM bool FileExists(const AuString &path) { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathExpanded = NormalizePathRet(path); if (pathExpanded.empty()) { SysPushErrorMemory(); return false; } return UnixExists(pathExpanded, false); } AUKN_SYM bool DirExists(const AuString &path) { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathExpanded = NormalizePathRet(path); if (pathExpanded.empty()) { SysPushErrorMemory(); return false; } return UnixExists(pathExpanded, true); } AUKN_SYM bool DirMk(const AuString &path) { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathExpanded = NormalizePathRet(path); if (pathExpanded.empty()) { SysPushErrorMemory(); return false; } return CreateDirectories(pathExpanded, false); } AUKN_SYM bool Remove(const AuString &path) { struct stat s; if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathExpanded = NormalizePathRet(path); if (pathExpanded.empty()) { SysPushErrorMemory(); return false; } if (::stat(pathExpanded.c_str(), &s) == -1) { if (errno == ENOENT) { return true; } } else { if (S_ISDIR(s.st_mode)) { if (::rmdir(pathExpanded.c_str()) == 0) { return true; } } if (S_ISREG(s.st_mode)) { if (::unlink(pathExpanded.c_str()) == 0) { return true; } } } if (::remove(pathExpanded.c_str()) == 0) { return true; } return false; } AUKN_SYM bool Relink(const AuString &src, const AuString &dest) { if (src.empty() || dest.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathSrcExpanded = NormalizePathRet(src); auto pathExpanded = NormalizePathRet(dest); if (pathSrcExpanded.empty() || pathExpanded.empty()) { SysPushErrorMemory(); return false; } return ::rename(pathSrcExpanded.c_str(), pathExpanded.c_str()) != -1; } #if defined(AURORA_IS_LINUX_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { if (src.empty() || dest.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathSrcExpanded = NormalizePathRet(src); auto pathExpanded = NormalizePathRet(dest); if (pathSrcExpanded.empty() || pathExpanded.empty()) { SysPushErrorMemory(); return false; } CreateDirectories(pathExpanded, true); int input, output; if ((input = ::open(pathSrcExpanded.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } struct stat fileinfo = { 0 }; if (::fstat(input, &fileinfo) != 0) { ::close(input); SysPushErrorIO("fstat failed"); return false; } if ((output = ::creat(pathExpanded.c_str(), fileinfo.st_mode)) == -1) { ::close(input); SysPushErrorIO("creat failed"); return false; } off_t bytesCopied = 0; auto result = ::sendfile(output, input, &bytesCopied, fileinfo.st_size) != -1; ::close(input); ::close(output); return bytesCopied == fileinfo.st_size; } #elif defined(AURORA_IS_BSD_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { if (src.empty() || dest.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathSrcExpanded = NormalizePathRet(src); auto pathExpanded = NormalizePathRet(dest); if (pathSrcExpanded.empty() || pathExpanded.empty()) { SysPushErrorMemory(); return false; } CreateDirectories(pathExpanded, true); int input, output; if ((input = ::open(pathSrcExpanded.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } struct stat fileinfo = { 0 }; if (::fstat(input, &fileinfo) != 0) { close(input) SysPushErrorIO("fstat failed"); return false; } if ((output = ::creat(pathExpanded.c_str(), fileinfo.st_mode)) == -1) { close(input); SysPushErrorIO("creat failed"); return false; } auto result = ::fcopyfile(input, output, 0, COPYFILE_ALL) == 0; ::close(input); ::close(output); return result; } #else AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { // TODO: not that i care return false; } #endif AUKN_SYM bool StatFile(const AuString &pathRel, Stat &stat) { stat = {}; if (pathRel.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto path = NormalizePathRet(pathRel); if (path.empty()) { SysPushErrorMemory(); return false; } struct stat s; auto err = ::stat(path.c_str(), &s); if (err == -1) { if (ENOENT != errno) { SysPushErrorIO("Critical IO error while stating file (errno: {}, path: {})", errno, path); } return false; } stat.bExistsFile = S_ISREG(s.st_mode); stat.bExistsDirectory = S_ISDIR(s.st_mode); stat.bExistsSystemResource = S_ISSOCK(s.st_mode); stat.bExists = stat.bExistsFile || stat.bExistsDirectory || stat.bExistsSystemResource; if (!stat.bExists) { SysPushErrorIO("Missing attribute type in stat mode {} (of path {})", s.st_mode, path); return false; } stat.uSize = s.st_size; #if defined(AURORA_IS_LINUX_DERIVED) stat.createdNs = AuUInt64(s.st_ctim.tv_nsec) + AuMSToNS(Time::CTimeToMS(s.st_ctim.tv_sec)); stat.modifiedNs = AuUInt64(s.st_mtim.tv_nsec) + AuMSToNS(Time::CTimeToMS(s.st_mtim.tv_sec)); stat.accessedNs = AuUInt64(s.st_atim.tv_nsec) + AuMSToNS(Time::CTimeToMS(s.st_atim.tv_sec)); #else stat.createdNs = AuMSToNS(Time::CTimeToMS(s.st_ctime)); stat.modifiedNs = AuMSToNS(Time::CTimeToMS(s.st_mtime)); stat.accessedNs = AuMSToNS(Time::CTimeToMS(s.st_atime)); #endif err = lstat(path.c_str(), &s); if (err != -1) { stat.bSymLink = S_ISLNK(s.st_mode); } return true; } } #endif