/*** 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) { return mkdir(path.c_str(), 0775) == 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.exists = 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->existsFile) { 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->existsDirectory) { continue; } if (!AuTryInsert(dirs, stat->fileName)) { return false; } } return true; } AUKN_SYM bool WriteFile(const AuString &path, const Memory::MemoryViewRead &blob) { auto file = OpenWriteUnique(path); SysCheckReturn(file, false); AuUInt written = blob.length; if (!file->Write(Memory::MemoryViewStreamRead(blob, written))) { SysPushErrorMem(); return false; } if (written != blob.length) { SysPushErrorIO(); return false; } return true; } AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer) { AuUInt read; auto file = OpenReadUnique(path, EFileAdvisoryLockLevel::eNoSafety); SysCheckReturn(file, false); auto len = file->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 (len == 0) { if (AuStartsWith(path, "/proc/") || AuStartsWith(path, "/sys/") || AuStartsWith(path, "/dev/")) { len = 4096 * 10; } else { buffer.clear(); return true; } } if (!AuTryResize(buffer, len)) { SysPushErrorMem(); return false; } if (!file->Read(Memory::MemoryViewStreamWrite {buffer.begin(), buffer.end(), read})) { SysPushErrorIO(); return false; } // NOTE: File devices love to lie // Do not entertain an arbitrarily large page length provided by non-regular fds return AuTryResize(buffer, read); } 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) { return rename(NormalizePathRet(src).c_str(), NormalizePathRet(dest).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)) == -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); int input, output; if ((input = open(normalizedSrcPath.c_str(), O_RDONLY)) == -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) { AuLogWarn("Critical IO error while stating file (errno: {}, path: {})", errno, path); } return false; } stat.existsFile = S_ISREG(s.st_mode); stat.existsDirectory = S_ISDIR(s.st_mode); stat.existsSystemResource = S_ISSOCK(s.st_mode); stat.exists = stat.existsFile || stat.existsDirectory || stat.existsSystemResource; if (!stat.exists) { AuLogWarn("Missing attribute type in stat mode {} (of path {})", s.st_mode, path); return false; } stat.size = s.st_size; stat.created = Time::CTimeToMS(s.st_ctime); stat.modified = Time::CTimeToMS(s.st_mtime); stat.accessed = Time::CTimeToMS(s.st_atime); err = lstat(path.c_str(), &s); if (err != -1) { stat.symLink = S_ISLNK(s.st_mode); } return true; } } #endif