13#include <system_error>
24namespace morph::core {
40template <
typename Operation>
41int retryOnEintr(Operation operation) {
42 int result = operation();
43 while (result < 0 && errno == EINTR) {
77 std::function<std::size_t(
const void* buffer, std::size_t size, std::FILE* file)>
fwrite =
78 [](
const void* buffer, std::size_t size, std::FILE* file) {
return std::fwrite(buffer, 1, size, file); };
82 std::function<int(std::FILE* file)>
fflush = [](std::FILE* file) {
return std::fflush(file); };
87 std::function<int(std::FILE* file)>
fsync = [](std::FILE* file) {
89 return _commit(_fileno(file));
91 return ::fsync(fileno(file));
98 std::function<std::FILE*(
const std::string& path,
const char* mode)>
fopen = [](
const std::string& path,
101 return std::fopen(path.c_str(), mode);
112 std::function<bool(
const std::filesystem::path& path)>
canOpenForRead = [](
const std::filesystem::path& path) {
113 return static_cast<bool>(std::ifstream{path});
122 std::function<void(
const std::filesystem::path& path, std::uintmax_t newSize, std::error_code& errorCode)>
123 resizeFile = [](
const std::filesystem::path& path, std::uintmax_t newSize, std::error_code& errorCode) {
124 std::filesystem::resize_file(path, newSize, errorCode);
153 std::function<int(
const std::filesystem::path& dir)>
syncPath = [](
const std::filesystem::path& dir) {
158 std::filesystem::path
const resolved = dir.empty() ? std::filesystem::path{
"."} : dir;
165 int const dirFd = retryOnEintr([&resolved] {
167 return ::open(resolved.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
172 int const result = retryOnEintr([dirFd] { return ::fsync(dirFd); });
173 int const failure = result == 0 ? 0 : errno;
181enum class DirectorySync : std::uint8_t {
194enum class RollBack : std::uint8_t {
225[[nodiscard]]
inline DirectorySync classifyDirectorySync(
int syncPathResult)
noexcept {
226 if (syncPathResult == 0) {
227 return DirectorySync::durable;
236 switch (syncPathResult) {
245#if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || EOPNOTSUPP != ENOTSUP)
248 return DirectorySync::unsupported;
252 return DirectorySync::failed;
271inline long long wideFtell(std::FILE* file) {
273 return _ftelli64(file);
275 return ::ftello(file);
303inline void positionAtEnd(std::FILE* file)
noexcept {
304 if (file !=
nullptr) {
306 std::fseek(file, 0, SEEK_END);
372[[nodiscard]]
inline RollBack rollBackShortWrite(
FileIoOps& ioOps, std::FILE* file,
const std::filesystem::path& path,
373 long long offsetBeforeWrite) {
375 if (ioOps.fflush(file) != 0) {
379 return RollBack::torn;
381 if (offsetBeforeWrite < 0) {
382 return RollBack::torn;
384 std::error_code errorCode;
385 auto const onDisk = std::filesystem::file_size(path, errorCode);
386 auto target =
static_cast<std::uintmax_t
>(offsetBeforeWrite);
387 bool const sizeKnown = !errorCode;
389 target = std::min(target, onDisk);
392 ioOps.resizeFile(path, target, errorCode);
393 RollBack
const outcome = errorCode ? RollBack::torn : RollBack::clean;
401 std::fseek(file, 0, SEEK_END);
432inline void repairTornTail(
FileIoOps& ioOps,
const std::filesystem::path& path, std::string_view logComponent) {
433 std::error_code errorCode;
434 auto const size = std::filesystem::file_size(path, errorCode);
435 if (errorCode || size == 0) {
438 if (!ioOps.canOpenForRead(path)) {
441 std::ifstream input{path, std::ios::binary};
448 ::morph::log::logWarn(std::string{logComponent} +
": could not read " + path.string() +
449 " to check for a torn trailing record; leaving it untouched");
452 std::uintmax_t intactEnd = 0;
453 std::uintmax_t offset = 0;
455 while (std::getline(input, line)) {
456 offset += line.size();
467 ::morph::log::logWarn(std::string{logComponent} +
": read error while checking " + path.string() +
468 " for a torn trailing record; leaving it untouched");
471 if (intactEnd == size) {
474 ioOps.resizeFile(path, intactEnd, errorCode);
476 ::morph::log::logWarn(std::string{logComponent} +
": could not truncate torn trailing record in " +
477 path.string() +
": " + errorCode.message());
480 ::morph::log::logWarn(std::string{logComponent} +
": discarded " + std::to_string(size - intactEnd) +
481 " byte(s) of a torn trailing record in " + path.string());
The raw file-I/O primitives morph::journal::FileActionLog and morph::offline::FileOfflineQueue both c...
std::function< int(std::FILE *file)> fsync
Commits file's contents to durable storage. POSIX fsync / Windows _commit, resolved from file via fil...
std::function< int(std::FILE *file)> fflush
Flushes file's stdio buffer. Mirrors std::fflush.
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}...
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< 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(const std::filesystem::path &dir)> syncPath
Commits a directory's own metadata (new/renamed entries within it) to durable storage....
std::function< std::FILE *(const std::string &path, const char *mode)> fopen
Opens path in mode mode. Mirrors std::fopen.