/*** Copyright (C) 2023 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuIOHandle.Unix.cpp Date: 2023-8-11 Author: Reece ***/ #include #include "AuIOHandle.hpp" #include "AuIOHandle.Unix.hpp" #include "FS/FS.hpp" #include "FS/FileAdvisory.Unix.hpp" #include "FS/FileStream.Unix.hpp" #include #include #include #include #if defined(AURORA_COMPILER_CLANG) // warning: enumeration values 'kEnumCount' and 'kEnumInvalid' not handled in switch [-Wswitch #pragma clang diagnostic ignored "-Wswitch" // Yea, I don't give a shit. #endif #if !defined(FD_CLOEXEC) #define FD_CLOEXEC 0 #endif namespace Aurora::IO { AuUInt64 DupHandle(AuUInt64 uOSHandle, bool bWriteAccess, bool bShareAccess) { int fd = ::dup(uOSHandle); if (fd < 0) { return 0; } if (!SetFDShareAccess(uOSHandle, bShareAccess)) { ::close(fd); return 0; } return AuUInt64(fd); } AuUInt64 AFileHandle::DupHandle(AuUInt64 uOSHandle, bool bWriteAccess) { return Aurora::IO::DupHandle(uOSHandle, bWriteAccess, false); } AuUInt64 AFileHandle::DupHandle(AuUInt64 uOSHandle, bool bWriteAccess, bool bShareAccess) { return Aurora::IO::DupHandle(uOSHandle, bWriteAccess, bShareAccess); } int SetFDShareAccess(AuUInt64 uOSHandle, bool bShareAccess) { int flags = ::fcntl(uOSHandle, F_GETFL, 0); if (flags == -1) { return 0; } if (bShareAccess) { flags &= ~FD_CLOEXEC; } else { flags |= FD_CLOEXEC; } flags = ::fcntl(uOSHandle, F_SETFL, flags); if (flags == -1) { return 0; } return 1; } void AFileHandle::CloseHandle(AuUInt64 uOSHandle, bool bFlushOnClose) { int fd = (int)uOSHandle; if (fd < 0) { return; } #if 0 if (bFlushOnClose) { ::fsync(uOSHandle); } ::close(fd); #else Grug::CloseHandle(uOSHandle, bFlushOnClose); #endif } void AFileHandle::InitStdIn(bool bSharing) { this->uOSReadHandle = DupHandle(STDIN_FILENO, false, bSharing); } void AFileHandle::InitStdOut(bool bError, bool bSharing) { this->uOSReadHandle = this->uOSWriteHandle = DupHandle(bError ? STDERR_FILENO : STDOUT_FILENO, true, bSharing); } bool AFileHandle::InitFromPath(HandleCreate create) { int iFileDescriptor { -1 }; if (create.path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } if (!FS::EFileOpenModeIsValid(create.eMode)) { SysPushErrorParam("Invalid open mode"); return false; } if (!FS::EFileAdvisoryLockLevelIsValid(create.eAdvisoryLevel)) { SysPushErrorParam("Invalid lock mode"); return false; } auto pathex = FS::NormalizePathRet(create.path); if (pathex.empty()) { SysPushErrorMemory(); return false; } if (create.bDirectIOMode) { this->bDirectIO = true; } int flags {}; switch (create.eMode) { case FS::EFileOpenMode::eRead: { break; } case FS::EFileOpenMode::eReadWrite: case FS::EFileOpenMode::eWrite: { if (create.bAlwaysCreateDirTree) { FS::CreateDirectories(pathex, true); } if (create.bFailIfNonEmptyFile) { #if defined(O_EXCL) flags = O_EXCL; #else if (AuFS::FileExists(pathex.c_str())) { SysPushErrorResourceExists("File {} already exists", create.path); return false; } #endif } break; } }; flags |= create.eMode == FS::EFileOpenMode::eRead ? O_RDONLY : (O_RDWR | O_CREAT); flags |= O_CLOEXEC; #if defined(O_DIRECT) flags |= this->bDirectIO ? O_DIRECT : 0; #else // TBD #endif iFileDescriptor = ::open(pathex.c_str(), flags, 0664); if (iFileDescriptor < 0) { if (create.bFailIfNonEmptyFile) { if (errno == EEXIST) { SysPushErrorResourceExists("File {} already exists", create.path); return false; } } if (errno != EISDIR && errno != ENOENT) { RuntimeWaitForSecondaryTick(); iFileDescriptor = ::open(pathex.c_str(), flags, 0664); } if (iFileDescriptor < 0) { if (errno == EEXIST) { SysPushErrorResourceExists("File {} already exists", create.path); return false; } if (errno == EISDIR) { flags &= ~(O_WRONLY | O_RDWR); iFileDescriptor = ::open(pathex.c_str(), flags, 0664); } if (iFileDescriptor < 0) { if (errno == ENFILE) { SysPushErrorIOResourceFailure("low resources"); return false; } SysPushErrorIO("Couldn't open file: {} ({}) {}", path, pathex, errno); return false; } } } if (!FS::ApplyDumbAdvisoryLock(iFileDescriptor, create.eAdvisoryLevel)) { SysPushErrorIO("Couldn't open file: {}. File node (not section) is locked.", path); ::close(iFileDescriptor); return false; } if (create.bFailIfNonEmptyFile) { if (PosixGetLength(iFileDescriptor)) { SysPushErrorResourceExists("File {} already exists", create.path); ::close(iFileDescriptor); return false; } } switch (create.eMode) { case FS::EFileOpenMode::eRead: { this->uOSReadHandle = AuUInt64(iFileDescriptor); break; } case FS::EFileOpenMode::eReadWrite: { this->uOSWriteHandle = AuUInt64(iFileDescriptor); this->uOSReadHandle = AuUInt64(iFileDescriptor); break; } case FS::EFileOpenMode::eWrite: { this->uOSWriteHandle = AuUInt64(iFileDescriptor); break; } }; this->bIsAsync = create.bAsyncHandle; this->path = create.path; return this->IsValid(); } AUKN_SYM bool IsHandleTTY(AuUInt uHandle) { if ((AuSInt)uHandle < 0) { SysPushErrorArg(); return false; } return ::isatty((int)uHandle); } AUKN_SYM bool IsHandlePipe(AuUInt uHandle) { struct stat st; if ((AuSInt)uHandle < 0) { SysPushErrorArg(); return false; } if (::fstat((int)uHandle, &st) != 0) { SysPushErrorIO("fstat failed"); return false; } return S_ISFIFO(st.st_mode); } AUKN_SYM bool IsHandleFile(AuUInt uHandle) { struct stat st; if ((AuSInt)uHandle < 0) { SysPushErrorArg(); return false; } if (::fstat((int)uHandle, &st) != 0) { SysPushErrorIO("fstat failed"); return false; } return S_ISREG(st.st_mode); } }