morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
morph::core::FileIoOps Struct Reference

The raw file-I/O primitives morph::journal::FileActionLog and morph::offline::FileOfflineQueue both call, as an injectable strategy. Every member defaults to the real syscall/stdlib call it stands in for, so a default-constructed FileIoOps is byte-for-byte what both classes called directly before this seam existed — no behavior change for a normal caller. More...

#include <file_io_ops.hpp>

Public Attributes

std::function< std::size_t(const void *buffer, std::size_t size, std::FILE *file)> fwrite
 Writes size bytes from buffer to file. Mirrors std::fwrite.
std::function< int(std::FILE *file)> fflush = [](std::FILE* file) { return std::fflush(file); }
 Flushes file's stdio buffer. Mirrors std::fflush.
std::function< int(std::FILE *file)> fsync
 Commits file's contents to durable storage. POSIX fsync / Windows _commit, resolved from file via fileno/_fileno.
std::function< std::FILE *(const std::string &path, const char *mode)> fopen
 Opens path in mode mode. Mirrors std::fopen.
std::function< bool(const std::filesystem::path &path)> canOpenForRead
 Reports whether path can be opened for reading right now. Stands in for std::ifstream{path}'s own open-succeeded check (repairTornTail()'s if (!input)) — a fault-injection test has no way to make a real std::ifstream construction fail without actually breaking the filesystem, so this predicate is consulted first; the real default performs the real open std::ifstream itself would.
std::function< void(const std::filesystem::path &path, std::uintmax_t newSize, std::error_code &errorCode)> resizeFile
 Truncates/extends path to newSize bytes. Mirrors std::filesystem::resize_file.
std::function< int(const std::filesystem::path &dir)> syncPath
 Commits a directory's own metadata (new/renamed entries within it) to durable storage. fsync on a file makes only that file's data durable – not the directory entry that names it, so a fresh file's creation or a rename can vanish on power loss even after the file's own contents were fsynced. POSIX: open(dir, O_RDONLY|O_DIRECTORY) + fsync + close. A no-op on Windows, documented as such rather than faked – FlushFileBuffers's semantics for a directory handle differ enough from POSIX fsync that pretending otherwise would be misleading, and this seam's Windows story is already file-fsync-only.

Detailed Description

The raw file-I/O primitives morph::journal::FileActionLog and morph::offline::FileOfflineQueue both call, as an injectable strategy. Every member defaults to the real syscall/stdlib call it stands in for, so a default-constructed FileIoOps is byte-for-byte what both classes called directly before this seam existed — no behavior change for a normal caller.

Why this exists
Both classes have several branch arms that only run when a real OS-level file-I/O call fails partway through an otherwise-successful operation (disk full, fd closed underneath, a permission change racing an exact window). None of those are reachable from a portable unit test without this seam, and FileActionLog and FileOfflineQueue have the identical gap. A test constructs a FileIoOps whose relevant member fails on demand (or on the Nth call, or forever) and passes it to the class under test; every other member stays at its real default, so the rest of the class's I/O behaves normally around the one injected failure.
Thread safety
FileIoOps itself is a plain value type with no shared state — copying or moving one has ordinary value semantics. Whether the callbacks themselves are safe to call from multiple threads concurrently is up to whatever a test installs; the real default callbacks are exactly the real syscalls, which already have their own well-defined thread-safety.

Definition at line 74 of file file_io_ops.hpp.

Member Data Documentation

◆ fwrite

std::function<std::size_t(const void* buffer, std::size_t size, std::FILE* file)> morph::core::FileIoOps::fwrite
Initial value:
=
[](const void* buffer, std::size_t size, std::FILE* file) { return std::fwrite(buffer, 1, size, file); }

Writes size bytes from buffer to file. Mirrors std::fwrite.

Returns
The number of bytes actually written; short of size on failure.

Definition at line 77 of file file_io_ops.hpp.

◆ fflush

std::function<int(std::FILE* file)> morph::core::FileIoOps::fflush = [](std::FILE* file) { return std::fflush(file); }

Flushes file's stdio buffer. Mirrors std::fflush.

Returns
0 on success, nonzero on failure.

Definition at line 82 of file file_io_ops.hpp.

◆ fsync

std::function<int(std::FILE* file)> morph::core::FileIoOps::fsync
Initial value:
= [](std::FILE* file) {
return ::fsync(fileno(file));
}

Commits file's contents to durable storage. POSIX fsync / Windows _commit, resolved from file via fileno/_fileno.

Returns
0 on success, nonzero on failure.

Definition at line 87 of file file_io_ops.hpp.

◆ fopen

std::function<std::FILE*(const std::string& path, const char* mode)> morph::core::FileIoOps::fopen
Initial value:
= [](const std::string& path,
const char* mode) {
return std::fopen(path.c_str(), mode);
}

Opens path in mode mode. Mirrors std::fopen.

Returns
The open file, or nullptr on failure.

Definition at line 98 of file file_io_ops.hpp.

◆ canOpenForRead

std::function<bool(const std::filesystem::path& path)> morph::core::FileIoOps::canOpenForRead
Initial value:
= [](const std::filesystem::path& path) {
return static_cast<bool>(std::ifstream{path});
}

Reports whether path can be opened for reading right now. Stands in for std::ifstream{path}'s own open-succeeded check (repairTornTail()'s if (!input)) — a fault-injection test has no way to make a real std::ifstream construction fail without actually breaking the filesystem, so this predicate is consulted first; the real default performs the real open std::ifstream itself would.

Returns
true if path is currently readable.

Definition at line 112 of file file_io_ops.hpp.

◆ resizeFile

std::function<void(const std::filesystem::path& path, std::uintmax_t newSize, std::error_code& errorCode)> morph::core::FileIoOps::resizeFile
Initial value:
= [](const std::filesystem::path& path, std::uintmax_t newSize, std::error_code& errorCode) {
std::filesystem::resize_file(path, newSize, errorCode);
}

Truncates/extends path to newSize bytes. Mirrors std::filesystem::resize_file.

Parameters
pathPath to resize.
newSizeTarget size, in bytes.
errorCodeSet on failure, cleared on success — same contract as std::filesystem::resize_file's own error_code overload.

Definition at line 123 of file file_io_ops.hpp.

◆ syncPath

std::function<int(const std::filesystem::path& dir)> morph::core::FileIoOps::syncPath
Initial value:
= [](const std::filesystem::path& dir) {
std::filesystem::path const resolved = dir.empty() ? std::filesystem::path{"."} : dir;
int const dirFd = retryOnEintr([&resolved] {
return ::open(resolved.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
});
if (dirFd < 0) {
return errno;
}
int const result = retryOnEintr([dirFd] { return ::fsync(dirFd); });
int const failure = result == 0 ? 0 : errno;
::close(dirFd);
return failure;
}

Commits a directory's own metadata (new/renamed entries within it) to durable storage. fsync on a file makes only that file's data durable – not the directory entry that names it, so a fresh file's creation or a rename can vanish on power loss even after the file's own contents were fsynced. POSIX: open(dir, O_RDONLY|O_DIRECTORY) + fsync + close. A no-op on Windows, documented as such rather than faked – FlushFileBuffers's semantics for a directory handle differ enough from POSIX fsync that pretending otherwise would be misleading, and this seam's Windows story is already file-fsync-only.

Parameters
dirDirectory whose entries were just mutated. A path::parent_path() of a bare relative filename (e.g. "queue.ndjson", with no directory component) is the empty path, not "." – treated here as the current directory, the same resolution the shell and every POSIX call already give an empty path's implicit caller, so a relative, directory-less path still gets its containing directory synced instead of failing open() with ENOENT on every call site that derives dir from parent_path().
Returns
0 on success, otherwise the errno that caused the failure. Returning the code rather than a bare -1 is what lets classifyDirectorySync() tell "this platform cannot do it" from "this platform tried and failed", which the call sites treat very differently. A custom (test) sink is free to return any nonzero value; anything it does not recognise counts as a real failure.

Definition at line 153 of file file_io_ops.hpp.


The documentation for this struct was generated from the following file: