core: introduce the file_ops platform seam (ADR 0020 Phase 0)

Pure refactor, zero behaviour change. posix_fallocate/ftruncate,
posix_fadvise(DONTNEED), and fdatasync move out of sparse_file.cpp and
meta/veloxpart.cpp into core/src/io/platform/linux/file_ops.cpp behind
a new seam (core/src/io/platform/file_ops.hpp). Neither caller names a
Linux-only syscall anymore, and no #ifdef exists outside platform/linux/.
Clears the way for the PORT lane's macOS backend.

Full suite: 44/44 green (build/dev).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-15 17:56:02 +04:00
co-authored by Claude Sonnet 5
parent facd851824
commit 936136db06
5 changed files with 124 additions and 24 deletions
+1
View File
@@ -20,6 +20,7 @@ add_library(veloxcore STATIC
src/net/probe.cpp src/net/probe.cpp
src/io/sparse_file.cpp src/io/sparse_file.cpp
src/io/write_buffer.cpp src/io/write_buffer.cpp
src/io/platform/linux/file_ops.cpp
src/meta/veloxpart.cpp src/meta/veloxpart.cpp
src/segment/segmenter.cpp src/segment/segmenter.cpp
src/segment/budget.cpp src/segment/budget.cpp
+38
View File
@@ -0,0 +1,38 @@
// vdm/io/platform/file_ops.hpp — platform seam for the three Linux-only file-I/O
// primitives sparse_file.cpp and meta/veloxpart.cpp use (ADR 0020, docs/08-porting.md).
//
// One implementation file per OS under platform/<os>/, selected at build time. No #ifdef
// here, no OS-specific types in the signatures — a reader of the callers must not need to
// know which OS they're on. Linux (platform/linux/file_ops.cpp) is the reference
// implementation and is the only one that exists until the PORT lane adds macOS.
//
// This header compiles standalone.
#ifndef VDM_IO_PLATFORM_FILE_OPS_HPP
#define VDM_IO_PLATFORM_FILE_OPS_HPP
#include <cstdint>
#include "vdm/util/result.hpp"
namespace vdm::io::platform {
// Reserve `bytes` for `fd` so a large transfer doesn't fragment or hit ENOSPC mid-write.
// Linux: posix_fallocate, falling back to ftruncate on EOPNOTSUPP/ENOSYS/EINVAL exactly as
// SparseFile did before this seam existed. Returns whether a true extent-reserving
// preallocation happened (false when the ftruncate fallback was used) so callers can keep
// reporting SparseFile::preallocated() unchanged.
[[nodiscard]] Result<bool> preallocate(int fd, std::uint64_t bytes);
// Drop cached pages for [offset, offset+len) from the OS page cache, best-effort. A
// platform with no equivalent (macOS) is a documented no-op, not a failure — never let
// this block or fail the transfer path.
void advise_dontneed(int fd, std::uint64_t offset, std::uint64_t len) noexcept;
// Flush `fd`'s data to durable storage without waiting on metadata that doesn't affect
// data readback. Linux: fdatasync.
[[nodiscard]] Result<void> flush_durable(int fd);
} // namespace vdm::io::platform
#endif // VDM_IO_PLATFORM_FILE_OPS_HPP
+73
View File
@@ -0,0 +1,73 @@
// vdm/io/platform/linux/file_ops.cpp — Linux backend for the file_ops seam.
//
// Reference implementation (ADR 0020): moved unchanged from sparse_file.cpp and
// meta/veloxpart.cpp. Linux behaviour must never change as a result of a port; if a port
// needs a semantic change here, that's an ADR, not a seam edit.
#include "io/platform/file_ops.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <string>
namespace vdm::io::platform {
namespace {
Error errno_to_error(int e) noexcept {
switch (e) {
case ENOSPC:
case EDQUOT:
return Error::disk_full;
case EACCES:
case EPERM:
case EROFS:
return Error::permission_denied;
case ENOENT:
case ENOTDIR:
case EISDIR:
case ENAMETOOLONG:
case ELOOP:
return Error::path_rejected;
default:
return Error::io_error;
}
}
ErrorInfo sys_error(std::string_view what, int e) {
return ErrorInfo(errno_to_error(e), std::string(what) + ": " + std::strerror(e));
}
} // namespace
Result<bool> preallocate(int fd, std::uint64_t bytes) {
// posix_fallocate returns the error number directly and does not set errno.
int rc = ::posix_fallocate(fd, 0, static_cast<off_t>(bytes));
if (rc == 0)
return true;
if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) {
if (::ftruncate(fd, static_cast<off_t>(bytes)) != 0)
return sys_error("ftruncate", errno);
return false;
}
return sys_error("posix_fallocate", rc);
}
void advise_dontneed(int fd, std::uint64_t offset, std::uint64_t len) noexcept {
if (len == 0)
return;
::posix_fadvise(fd, static_cast<off_t>(offset), static_cast<off_t>(len), POSIX_FADV_DONTNEED);
}
Result<void> flush_durable(int fd) {
while (::fdatasync(fd) != 0) {
if (errno == EINTR)
continue;
return sys_error("fdatasync", errno);
}
return ok();
}
} // namespace vdm::io::platform
+8 -19
View File
@@ -9,6 +9,8 @@
#include <cstring> #include <cstring>
#include <utility> #include <utility>
#include "io/platform/file_ops.hpp"
namespace vdm::io { namespace vdm::io {
namespace { namespace {
@@ -90,20 +92,12 @@ Result<void> SparseFile::open(std::string_view path, const OpenOptions &opts) {
bool prealloc = false; bool prealloc = false;
if (opts.total_size > 0) { if (opts.total_size > 0) {
if (opts.preallocate) { if (opts.preallocate) {
// posix_fallocate returns the error number directly and does not set errno. Result<bool> r = platform::preallocate(fd, opts.total_size);
int rc = ::posix_fallocate(fd, 0, static_cast<off_t>(opts.total_size)); if (!r.has_value()) {
if (rc == 0) {
prealloc = true;
} else if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) {
if (::ftruncate(fd, static_cast<off_t>(opts.total_size)) != 0) {
int e = errno;
::close(fd);
return sys_error("ftruncate " + p, e);
}
} else {
::close(fd); ::close(fd);
return sys_error("posix_fallocate " + p, rc); return std::move(r).error();
} }
prealloc = *r;
} else if (!opts.truncate_existing) { } else if (!opts.truncate_existing) {
// Resuming: make sure the file is at least total_size so pwrite offsets land. // Resuming: make sure the file is at least total_size so pwrite offsets land.
if (::ftruncate(fd, static_cast<off_t>(opts.total_size)) != 0) { if (::ftruncate(fd, static_cast<off_t>(opts.total_size)) != 0) {
@@ -147,18 +141,13 @@ Result<void> SparseFile::write_at(std::uint64_t offset, ConstByteSpan data) {
Result<void> SparseFile::sync() { Result<void> SparseFile::sync() {
if (fd_ < 0) if (fd_ < 0)
return ErrorInfo(Error::internal, "sync on a closed SparseFile"); return ErrorInfo(Error::internal, "sync on a closed SparseFile");
while (::fdatasync(fd_) != 0) { return platform::flush_durable(fd_);
if (errno == EINTR)
continue;
return sys_error("fdatasync", errno);
}
return ok();
} }
void SparseFile::advise_dontneed(std::uint64_t offset, std::uint64_t len) noexcept { void SparseFile::advise_dontneed(std::uint64_t offset, std::uint64_t len) noexcept {
if (fd_ < 0 || len == 0) if (fd_ < 0 || len == 0)
return; return;
::posix_fadvise(fd_, static_cast<off_t>(offset), static_cast<off_t>(len), POSIX_FADV_DONTNEED); platform::advise_dontneed(fd_, offset, len);
} }
Result<void> SparseFile::resize(std::uint64_t size) { Result<void> SparseFile::resize(std::uint64_t size) {
+4 -5
View File
@@ -13,6 +13,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "io/platform/file_ops.hpp"
#include "vdm/util/crc32.hpp" #include "vdm/util/crc32.hpp"
namespace vdm::meta { namespace vdm::meta {
@@ -272,13 +273,11 @@ Result<void> write_veloxpart_file(std::string_view path, const VeloxPart &vp, bo
} }
if (fsync) { if (fsync) {
while (::fdatasync(fd) != 0) { Result<void> r = vdm::io::platform::flush_durable(fd);
if (errno == EINTR) if (!r.has_value()) {
continue;
int e = errno;
::close(fd); ::close(fd);
::unlink(tmp.c_str()); ::unlink(tmp.c_str());
return sys_error("fdatasync " + tmp, e); return ErrorInfo(r.error().code, "fdatasync " + tmp + ": " + r.error().context);
} }
} }
if (::close(fd) != 0) { if (::close(fd) != 0) {