#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_STORAGE_SOURCE 1 #include "LinuxFileApi.h" #if defined(NUCLEX_STORAGE_LINUX) #include "../../Helpers/PosixApi.h" // Linux uses Posix error handling #include "Nuclex/Storage/Errors/BadPathError.h" #include // for PATH_MAX #include // ::open() and flags #include // ::read(), ::write(), ::rmdir(), etc. #include // To access ::errno directly #include // std::vector namespace Nuclex { namespace Storage { namespace FileSystem { namespace Linux { // ------------------------------------------------------------------------------------------- // void LinuxFileApi::MakeDirectory(const std::string &path) { int result = ::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if(result != 0) { int errorNumber = errno; std::string errorMessage(u8"Could not create directory '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::MakeDirectoryRecursively(const std::string &path) { // If the specified directory name doesn't exist, do our thing ::mode_t fileMode = StatMode(path); if(fileMode == 0) { // Recursively do it all again for the parent directory, if any std::size_t slashIndex = path.rfind('/'); if(slashIndex != std::string::npos) { MakeDirectoryRecursively(path.substr(0, slashIndex)); } // Create the last directory on the path (the recursive calls will have taken // care of the parent directories by now) MakeDirectory(path); } else { // Specified directory name already exists as a file or directory if(!S_ISDIR(fileMode)) { // && !S_ISLNK(fileMode)) { throw Nuclex::Storage::Errors::BadPathError( std::error_code(ENOTDIR, std::system_category()), u8"Could not create directory because a file with the same name exists" ); } } } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::RemoveDirectory(const std::string &path) { ::DIR *directoryHandle = OpenDirectory(path); DirectoryScope directoryScope(directoryHandle); struct ::dirent *directoryEntry; while((directoryEntry = ReadDirectory(directoryHandle)) != nullptr) { // Do not process the obligatory '.' and '..' directories if(directoryEntry->d_name[0] != '.') { std::string filePath = path + '/' + directoryEntry->d_name; ::mode_t entryMode = StatMode(filePath); bool isDirectory = S_ISDIR(entryMode); // || S_ISLNK(fileMode); // Subdirectories need to be handled by deleting their contents first if(isDirectory) { RemoveDirectory(filePath); } else { RemoveFile(filePath); } } } // The directory is empty, we can now safely remove it int result = ::rmdir(path.c_str()); if(unlikely(result != 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not remove directory '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::RemoveFile(const std::string &path) { int result = ::unlink(path.c_str()); if(unlikely(result != 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not delete file '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // std::string LinuxFileApi::GetCurrentWorkingDirectory() { char buffer[PATH_MAX]; char *result = ::getcwd(buffer, sizeof(buffer)); if(unlikely(result == nullptr)) { int errorNumber = errno; std::string errorMessage(u8"Could not retrieve current working directory"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return std::string(buffer); } // ------------------------------------------------------------------------------------------- // ::mode_t LinuxFileApi::StatMode(const std::string &path) { struct ::stat fileStatus; if(Stat(path, fileStatus)) { return fileStatus.st_mode; } else { return static_cast<::mode_t>(0); } } // ------------------------------------------------------------------------------------------- // bool LinuxFileApi::Stat(const std::string &path, struct ::stat &fileStatus) { int result = ::stat(path.c_str(), &fileStatus); if(result != 0) { int errorNumber = errno; // This is an okay outcome for us: the file or directory does not exist. if((errorNumber == ENOENT) || (errorNumber == ENOTDIR)) { return false; } std::string errorMessage(u8"Could not obtain file status for '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return true; } // ------------------------------------------------------------------------------------------- // bool LinuxFileApi::TryOpenFileForReading(const std::string &path, int &fileDescriptor) { fileDescriptor = ::open(path.c_str(), O_RDONLY | O_LARGEFILE); return (fileDescriptor >= 0); } // ------------------------------------------------------------------------------------------- // int LinuxFileApi::OpenFileForReading(const std::string &path) { int fileDescriptor = ::open(path.c_str(), O_RDONLY | O_LARGEFILE); if(unlikely(fileDescriptor < 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not open file '"); errorMessage.append(path); errorMessage.append(u8"' for reading"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return fileDescriptor; } // ------------------------------------------------------------------------------------------- // int LinuxFileApi::OpenFileForWriting(const std::string &path) { int fileDescriptor = ::open( path.c_str(), O_RDWR | O_CREAT | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); if(unlikely(fileDescriptor < 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not open file '"); errorMessage.append(path); errorMessage.append(u8"' for writing"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return fileDescriptor; } // ------------------------------------------------------------------------------------------- // std::uint64_t LinuxFileApi::Seek(int fileDescriptor, std::uint64_t location) { off_t result = ::lseek(fileDescriptor, location, SEEK_SET); if(unlikely(result == static_cast(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not reposition file cursor"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return static_cast(result); } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::Stat(int fileDescriptor, struct ::stat &fileStatus) { int result = ::fstat(fileDescriptor, &fileStatus); if(unlikely(result != 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not obtain file status"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // std::size_t LinuxFileApi::Read( int fileDescriptor, std::uint8_t *buffer, std::size_t count ) { ssize_t result = ::read(fileDescriptor, buffer, count); if(unlikely(result == static_cast(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not read data from file"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return static_cast(result); } // ------------------------------------------------------------------------------------------- // std::size_t LinuxFileApi::PositionalRead( int fileDescriptor, std::uint64_t offset, std::uint8_t *buffer, std::size_t count ) { ssize_t result = ::pread(fileDescriptor, buffer, count, static_cast(offset)); if(unlikely(result == static_cast(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not read data from file"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return static_cast(result); } // ------------------------------------------------------------------------------------------- // std::size_t LinuxFileApi::Write( int fileDescriptor, const std::uint8_t *buffer, std::size_t count ) { ssize_t result = ::write(fileDescriptor, buffer, count); if(unlikely(result == static_cast(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not write data to file"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return result; } // ------------------------------------------------------------------------------------------- // std::size_t LinuxFileApi::PositionalWrite( int fileDescriptor, std::uint64_t offset, const std::uint8_t *buffer, std::size_t count ) { ssize_t result = ::pwrite(fileDescriptor, buffer, count, static_cast(offset)); if(unlikely(result == static_cast(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not write data to file"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return result; } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::Close(int fileDescriptor, bool throwOnError /* = true */) { int result = ::close(fileDescriptor); if(throwOnError && (result == -1)) { int errorNumber = errno; std::string errorMessage(u8"Could not close file"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // ::DIR *LinuxFileApi::OpenDirectory(const std::string &path) { ::DIR *result = ::opendir(path.c_str()); if(unlikely(result == nullptr)) { int errorNumber = errno; std::string errorMessage(u8"Could not open directory '"); errorMessage.append(path); errorMessage.append(u8"' for enumeration"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return result; } // ------------------------------------------------------------------------------------------- // struct ::dirent *LinuxFileApi::ReadDirectory(::DIR *directory) { errno = 0; struct ::dirent *directoryEntry = ::readdir(directory); if(directoryEntry == nullptr) { int errorNumber = errno; // If readdir() returned zero because the last entry is reached, errno stays unchanged if(likely(errorNumber == 0)) { return nullptr; } else { std::string errorMessage(u8"Could not enumerate directory contents"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } return directoryEntry; } // ------------------------------------------------------------------------------------------- // void LinuxFileApi::CloseDirectory(::DIR *directory, bool throwOnError /* = true */) { int result = ::closedir(directory); if(throwOnError && (result != 0)) { int errorNumber = errno; std::string errorMessage(u8"Could not close directory"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // ------------------------------------------------------------------------------------------- // std::string LinuxFileApi::ReadLink(const std::string &path) { // Prepare a vector as a buffer to receive the link target path std::vector linkTargetPath; linkTargetPath.resize(256); // Now try to read the target of the symlink bool firstTry = true; for(;;) { ::ssize_t pathByteCount = ::readlink( path.c_str(), &linkTargetPath[0], linkTargetPath.size() ); // If another error occurred, there's nothing we can do to mend the situation if(unlikely(pathByteCount == ssize_t(-1))) { int errorNumber = errno; std::string errorMessage(u8"Could not read target of symlink '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } // If the path is too long for our buffer, enlarge the buffer and give it // another try. If we're already on the second try, give up. if(static_cast(pathByteCount) >= linkTargetPath.size()) { if(firstTry) { linkTargetPath.resize(4096); // Maximum length in ext4 firstTry = false; continue; // Do another try with the larger buffer size } else { int errorNumber = errno; std::string errorMessage(u8"Could not pinpoint length of symlink '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } } // Looks like the link target was read successfully, return the target path return std::string(&linkTargetPath[0], static_cast(pathByteCount)); } // for(;;) } // ------------------------------------------------------------------------------------------- // std::string LinuxFileApi::RealPath(const std::string &path) { char buffer[PATH_MAX]; char *result = ::realpath(path.c_str(), buffer); if(unlikely(result == nullptr)) { int errorNumber = errno; std::string errorMessage(u8"Could not resolve real path for '"); errorMessage.append(path); errorMessage.append(u8"'"); Helpers::PosixApi::ThrowExceptionForSystemError(errorMessage, errorNumber); } return std::string(buffer); } // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Storage::FileSystem::Linux #endif // defined(NUCLEX_STORAGE_LINUX)