/*** 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) { 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; } static bool WriteFileEx(const AuString &path, const Memory::MemoryViewRead &blob, bool bCheck) { auto pFile = OpenWriteUnique(path); SysCheckReturn(pFile, false); if (bCheck) { if (pFile->GetLength()) { SysPushErrorIO("File exists: {}", path); return {}; } } AuUInt written = blob.length; if (!pFile->Write(Memory::MemoryViewStreamRead(blob, written))) { SysPushErrorMem(); return false; } pFile->WriteEoS(); if (written != blob.length) { SysPushErrorIO(); return false; } return true; } AUKN_SYM bool WriteFile(const AuString &path, const Memory::MemoryViewRead &blob) { return WriteFileEx(path, blob, false); } AUKN_SYM bool WriteNewFile(const AuString &path, const Memory::MemoryViewRead &blob) { return WriteFileEx(path, blob, true); } AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer) { AuMemoryViewWrite writeView; 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) { return UnixExists(NormalizePathRet(path), false); } AUKN_SYM bool DirExists(const AuString &path) { return UnixExists(NormalizePathRet(path), true); } AUKN_SYM bool DirMk(const AuString &path) { return CreateDirectories(NormalizePathRet(path), false); } AUKN_SYM bool Remove(const AuString &path) { return remove(NormalizePathRet(path).c_str()) != -1; } AUKN_SYM bool Relink(const AuString &src, const AuString &dest) { auto normalizedDestPath = NormalizePathRet(dest); CreateDirectories(destPathNormalized, true); return ::rename(NormalizePathRet(src).c_str(), destPathNormalized.c_str()) != -1; } #if defined(AURORA_IS_LINUX_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { auto normalizedSrcPath = NormalizePathRet(src); auto normalizedDestPath = NormalizePathRet(dest); int input, output; if ((input = ::open(normalizedSrcPath.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } struct stat fileinfo = { 0 }; if (::fstat(input, &fileinfo) != 0) { ::close(input); return false; } if ((output = ::creat(normalizedDestPath.c_str(), fileinfo.st_mode)) == -1) { ::close(input); return false; } off_t bytesCopied = 0; auto result = ::sendfile(output, input, &bytesCopied, fileinfo.st_size) != -1; ::close(input); ::close(output); return result; } #elif defined(AURORA_IS_BSD_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { auto normalizedSrcPath = NormalizePathRet(src); auto normalizedDestPath = NormalizePathRet(dest); CreateDirectories(normalizedDestPath, true); int input, output; if ((input = ::open(normalizedSrcPath.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } struct stat fileinfo = { 0 }; if (::fstat(input, &fileinfo) != 0) { close(input) return false; } if ((output = ::creat(normalizedDestPath.c_str(), fileinfo.st_mode)) == -1) { close(input); 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 = {}; auto path = NormalizePathRet(pathRel); 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 = AuTime::CTimeNSNormalize(s.st_ctime_nsec); stat.modifiedNs = AuTime::CTimeNSNormalize(s.st_mtime_nsec); stat.accessedNs = AuTime::CTimeNSNormalize(s.st_atime_nsec); #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