Skip to content

Commit

Permalink
Physical file stream api with more specific flags, create parent dire…
Browse files Browse the repository at this point in the history
…ctories, better error printing
  • Loading branch information
Dextinfire committed Oct 5, 2024
1 parent cd1482e commit 6124773
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 29 deletions.
123 changes: 107 additions & 16 deletions src/io/physicalfilestream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,84 @@

#include <cstring>
#include <algorithm>
#include <system_error>

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;
}
Expand All @@ -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:
Expand All @@ -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();
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down
39 changes: 26 additions & 13 deletions src/io/physicalfilestream.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,53 @@
#include "stream.h"
#include <fstream>
#include <filesystem>
#include <system_error>
#include "buffering.h"

namespace Impacto {
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;
};
Expand Down

0 comments on commit 6124773

Please sign in to comment.