diff --git a/src/io/physicalfilestream.cpp b/src/io/physicalfilestream.cpp index 778c6a3f..fb66daec 100644 --- a/src/io/physicalfilestream.cpp +++ b/src/io/physicalfilestream.cpp @@ -3,25 +3,84 @@ #include #include +#include namespace Impacto { namespace Io { +std::ios_base::openmode PhysicalFileStream::PrepareFileOpenMode( + CreateFlags flags, std::error_code& ec) { + std::ios_base::openmode mode = std::ios::binary; + bool fileExists = std::filesystem::exists(SourceFileName, ec); + if (ec) { + ImpLog(LL_Error, LC_IO, + "Failed to check whether file exists \"%s\", error: \"%s\"\n", + SourceFileName.generic_string().c_str(), ec.message().c_str()); + return {}; + } -IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, - bool exists) { - std::error_code ec; - if (exists && !std::filesystem::exists(fileName, ec)) { + bool writeNoExistNoCreate = + (flags & WRITE) && !fileExists && !(flags & CREATE_IF_NOT_EXISTS); + bool readNoExist = !(flags & WRITE) && (flags & READ) && !fileExists; + if (writeNoExistNoCreate || readNoExist) { + ec = std::make_error_code(std::errc::no_such_file_or_directory); + ImpLog(LL_Error, LC_IO, "Failed to open stream \"%s\", error: \"%s\"\n", + SourceFileName.generic_string().c_str(), ec.message().c_str()); + return {}; + } + + bool writeExistNoOverwrite = + (flags & WRITE) && fileExists && !(flags & TRUNCATE) && !(flags & APPEND); + if (writeExistNoOverwrite) { + // avoid truncating when in write only mode without truncate flag to + // preserve file size + // I think this is more intuitive than making it an error + // or letting the truncate happen + flags |= CreateFlagsMode::READ; + } + + // require write for create/overwrite flags + assert((flags & WRITE) || !(flags & CREATE_DIRS)); + assert((flags & WRITE) || !(flags & CREATE_IF_NOT_EXISTS)); + + // truncate is only needed when creating nonexistent if also reading + bool truncFlag = (flags & TRUNCATE) || + ((flags & READ) && !fileExists && + (flags & CREATE_IF_NOT_EXISTS) && !(flags & APPEND)); + + // trunc and append are mutually exclusive + assert(!truncFlag || !(flags & APPEND)); + + if (flags & READ) mode |= std::ios::in; + if (flags & WRITE) mode |= std::ios::out; + if (flags & APPEND) mode |= std::ios::app; + if (truncFlag) mode |= std::ios::trunc; + if (flags & CREATE_DIRS) { + std::filesystem::create_directories(SourceFileName.parent_path(), ec); if (ec) { - ImpLog(LL_Error, LC_IO, "Error checking file existence: %s\n", - ec.message().c_str()); - return IoError_Fail; + ImpLog(LL_Error, LC_IO, + "Failed to create directories for file \"%s\", error: \"%s\"\n", + SourceFileName.generic_string().c_str(), ec.message().c_str()); + return {}; } - return IoError_NotFound; } - PhysicalFileStream* result = new PhysicalFileStream(fileName, !exists); + return mode; +} + +IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, + CreateFlags flags) { + std::error_code ec; + PhysicalFileStream* result = new PhysicalFileStream(fileName, flags); + if (result->ErrorCode) { + ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", + fileName.c_str(), result->ErrorCode.message().c_str()); + delete result; + return result->ErrorCode == std::errc::no_such_file_or_directory + ? IoError_NotFound + : IoError_Fail; + } if (!result->FileStream) { ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", - fileName.c_str(), std::strerror(errno)); + fileName.c_str(), std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } @@ -37,21 +96,32 @@ IoError PhysicalFileStream::Create(std::string const& fileName, Stream** out, } int64_t PhysicalFileStream::Read(void* buffer, int64_t sz) { + if (!(Flags & READ)) { + return IoError_Fail; + } if (FileStream.eof()) { + FileStream.clear(); return IoError_Eof; } FileStream.read((char*)buffer, sz); if (!FileStream && !FileStream.eof()) { ImpLog(LL_Error, LC_IO, "Read failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.string().c_str(), + std::generic_category().message(errno).c_str()); + FileStream.clear(); return IoError_Fail; } int64_t read = FileStream.gcount(); + if (read == 0 && sz) return IoError_Eof; Position += read; return read; } int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { + if (!(Flags & READ) && (Flags & APPEND)) { + // seeking is useless in readless append mode + return IoError_Fail; + } int64_t absPos; switch (origin) { case RW_SEEK_SET: @@ -64,11 +134,16 @@ int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { absPos = Meta.Size + offset; break; } - if (absPos < 0 || absPos > Meta.Size) return IoError_Fail; + + // seeking past EOF is a legal operation, after write past EOF, the gap + // between prev file size and write pos is zero padded + if (absPos < 0) return IoError_Fail; FileStream.seekg(absPos, std::ios::beg); if (!FileStream && !FileStream.eof()) { ImpLog(LL_Error, LC_IO, "Seek failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.string().c_str(), + std::generic_category().message(errno).c_str()); + FileStream.clear(); return IoError_Fail; } Position = FileStream.tellg(); @@ -78,9 +153,19 @@ int64_t PhysicalFileStream::Seek(int64_t offset, int origin) { IoError PhysicalFileStream::Duplicate(Stream** outStream) { PhysicalFileStream* result = new PhysicalFileStream(*this); std::error_code ec; + if (result->ErrorCode) { + ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", + SourceFileName.string().c_str(), + result->ErrorCode.message().c_str()); + delete result; + return result->ErrorCode == std::errc::no_such_file_or_directory + ? IoError_NotFound + : IoError_Fail; + } if (!result->FileStream) { ImpLog(LL_Error, LC_IO, "Failed to open file \"%s\", error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.string().c_str(), + std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } @@ -93,7 +178,8 @@ IoError PhysicalFileStream::Duplicate(Stream** outStream) { } if (result->Seek(Position, RW_SEEK_SET) < 0) { ImpLog(LL_Error, LC_IO, "Seek failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.string().c_str(), + std::generic_category().message(errno).c_str()); delete result; return IoError_Fail; } @@ -102,10 +188,15 @@ IoError PhysicalFileStream::Duplicate(Stream** outStream) { } int64_t PhysicalFileStream::Write(void* buffer, int64_t sz, int cnt) { + if (!(Flags & WRITE)) { + return IoError_Fail; + } FileStream.write((char*)buffer, sz * cnt); if (!FileStream) { ImpLog(LL_Error, LC_IO, "Write failed for file \"%s\" with error: \"%s\"\n", - SourceFileName.string().c_str(), std::strerror(errno)); + SourceFileName.string().c_str(), + std::generic_category().message(errno).c_str()); + FileStream.clear(); return IoError_Fail; } int64_t written = sz * cnt; diff --git a/src/io/physicalfilestream.h b/src/io/physicalfilestream.h index e140d095..42bdddb7 100644 --- a/src/io/physicalfilestream.h +++ b/src/io/physicalfilestream.h @@ -3,6 +3,7 @@ #include "stream.h" #include #include +#include #include "buffering.h" namespace Impacto { @@ -10,33 +11,45 @@ namespace Io { class PhysicalFileStream : public Stream { public: + enum CreateFlagsMode { + READ = 1, + WRITE = READ << 1, + CREATE_IF_NOT_EXISTS = WRITE << 1, + TRUNCATE = CREATE_IF_NOT_EXISTS << 1, + APPEND = TRUNCATE << 1, + CREATE_DIRS = CREATE_IF_NOT_EXISTS << 1 + }; + using CreateFlags = int; static IoError Create(std::string const& fileName, Stream** out, - bool exists = true); + CreateFlags flags = CreateFlagsMode::READ); int64_t Read(void* buffer, int64_t sz) override; int64_t Seek(int64_t offset, int origin) override; IoError Duplicate(Stream** outStream) override; int64_t Write(void* buffer, int64_t sz, int cnt = 1) override; protected: - std::ios_base::openmode GetFileMode(bool truncate) { - // trunc will clear file if it exists, and allows creation of new file - return (truncate) ? std::ios::in | std::ios::out | std::ios::trunc | - std::ios::binary - : std::ios::in | std::ios::out | std::ios::binary; - } - PhysicalFileStream(std::filesystem::path filePath, bool truncate = false) - : Truncate(truncate), + std::ios_base::openmode PrepareFileOpenMode(CreateFlags flags, + std::error_code& ec); + + PhysicalFileStream(std::filesystem::path filePath, CreateFlags flags) + : Flags(flags), SourceFileName(std::move(filePath)), - FileStream(SourceFileName, GetFileMode(truncate)) { + FileStream(SourceFileName, PrepareFileOpenMode(flags, ErrorCode)) { Meta.FileName = SourceFileName.string(); } + PhysicalFileStream(std::filesystem::path filePath) + : PhysicalFileStream(std::move(filePath), CreateFlagsMode::READ) {} + PhysicalFileStream(PhysicalFileStream const& other) - : SourceFileName(other.SourceFileName), - FileStream(other.SourceFileName, GetFileMode(other.Truncate)) { + : Flags(other.Flags), + SourceFileName(other.SourceFileName), + FileStream(other.SourceFileName, + PrepareFileOpenMode(Flags, ErrorCode)) { Meta.FileName = SourceFileName.string(); } - bool Truncate; + std::error_code ErrorCode; + CreateFlags Flags; std::filesystem::path SourceFileName; std::fstream FileStream; };