diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index cfed3dc..64c5020 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(veloxcore STATIC src/net/probe.cpp src/io/sparse_file.cpp src/io/write_buffer.cpp + src/io/platform/linux/file_ops.cpp src/meta/veloxpart.cpp src/segment/segmenter.cpp src/segment/budget.cpp diff --git a/core/src/io/platform/file_ops.hpp b/core/src/io/platform/file_ops.hpp new file mode 100644 index 0000000..481ec52 --- /dev/null +++ b/core/src/io/platform/file_ops.hpp @@ -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//, 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 + +#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 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 flush_durable(int fd); + +} // namespace vdm::io::platform + +#endif // VDM_IO_PLATFORM_FILE_OPS_HPP diff --git a/core/src/io/platform/linux/file_ops.cpp b/core/src/io/platform/linux/file_ops.cpp new file mode 100644 index 0000000..28853bb --- /dev/null +++ b/core/src/io/platform/linux/file_ops.cpp @@ -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 +#include + +#include +#include +#include + +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 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(bytes)); + if (rc == 0) + return true; + if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) { + if (::ftruncate(fd, static_cast(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(offset), static_cast(len), POSIX_FADV_DONTNEED); +} + +Result flush_durable(int fd) { + while (::fdatasync(fd) != 0) { + if (errno == EINTR) + continue; + return sys_error("fdatasync", errno); + } + return ok(); +} + +} // namespace vdm::io::platform diff --git a/core/src/io/sparse_file.cpp b/core/src/io/sparse_file.cpp index fb98210..00113b7 100644 --- a/core/src/io/sparse_file.cpp +++ b/core/src/io/sparse_file.cpp @@ -9,6 +9,8 @@ #include #include +#include "io/platform/file_ops.hpp" + namespace vdm::io { namespace { @@ -90,20 +92,12 @@ Result SparseFile::open(std::string_view path, const OpenOptions &opts) { bool prealloc = false; if (opts.total_size > 0) { if (opts.preallocate) { - // posix_fallocate returns the error number directly and does not set errno. - int rc = ::posix_fallocate(fd, 0, static_cast(opts.total_size)); - if (rc == 0) { - prealloc = true; - } else if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) { - if (::ftruncate(fd, static_cast(opts.total_size)) != 0) { - int e = errno; - ::close(fd); - return sys_error("ftruncate " + p, e); - } - } else { + Result r = platform::preallocate(fd, opts.total_size); + if (!r.has_value()) { ::close(fd); - return sys_error("posix_fallocate " + p, rc); + return std::move(r).error(); } + prealloc = *r; } else if (!opts.truncate_existing) { // Resuming: make sure the file is at least total_size so pwrite offsets land. if (::ftruncate(fd, static_cast(opts.total_size)) != 0) { @@ -147,18 +141,13 @@ Result SparseFile::write_at(std::uint64_t offset, ConstByteSpan data) { Result SparseFile::sync() { if (fd_ < 0) return ErrorInfo(Error::internal, "sync on a closed SparseFile"); - while (::fdatasync(fd_) != 0) { - if (errno == EINTR) - continue; - return sys_error("fdatasync", errno); - } - return ok(); + return platform::flush_durable(fd_); } void SparseFile::advise_dontneed(std::uint64_t offset, std::uint64_t len) noexcept { if (fd_ < 0 || len == 0) return; - ::posix_fadvise(fd_, static_cast(offset), static_cast(len), POSIX_FADV_DONTNEED); + platform::advise_dontneed(fd_, offset, len); } Result SparseFile::resize(std::uint64_t size) { diff --git a/core/src/meta/veloxpart.cpp b/core/src/meta/veloxpart.cpp index 0446788..0cd00bd 100644 --- a/core/src/meta/veloxpart.cpp +++ b/core/src/meta/veloxpart.cpp @@ -13,6 +13,7 @@ #include #include +#include "io/platform/file_ops.hpp" #include "vdm/util/crc32.hpp" namespace vdm::meta { @@ -272,13 +273,11 @@ Result write_veloxpart_file(std::string_view path, const VeloxPart &vp, bo } if (fsync) { - while (::fdatasync(fd) != 0) { - if (errno == EINTR) - continue; - int e = errno; + Result r = vdm::io::platform::flush_durable(fd); + if (!r.has_value()) { ::close(fd); ::unlink(tmp.c_str()); - return sys_error("fdatasync " + tmp, e); + return ErrorInfo(r.error().code, "fdatasync " + tmp + ": " + r.error().context); } } if (::close(fd) != 0) {