// vdm/io/sparse_file.cpp #include "vdm/io/sparse_file.hpp" #include #include #include #include #include namespace vdm::io { 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 SparseFile::~SparseFile() { if (fd_ >= 0) ::close(fd_); } SparseFile::SparseFile(SparseFile &&o) noexcept : fd_(std::exchange(o.fd_, -1)), preallocated_(std::exchange(o.preallocated_, false)), path_(std::move(o.path_)) {} SparseFile &SparseFile::operator=(SparseFile &&o) noexcept { if (this != &o) { if (fd_ >= 0) ::close(fd_); fd_ = std::exchange(o.fd_, -1); preallocated_ = std::exchange(o.preallocated_, false); path_ = std::move(o.path_); } return *this; } void SparseFile::reset() noexcept { fd_ = -1; preallocated_ = false; path_.clear(); } Result SparseFile::open(std::string_view path) { return open(path, OpenOptions{}); } Result SparseFile::open(std::string_view path, const OpenOptions &opts) { if (fd_ >= 0) return ErrorInfo(Error::internal, "SparseFile already open"); std::string p(path); // O_NOFOLLOW: the final component of a download target must never be a symlink, on // create or on resume. DAEMON canonicalises the path and checks it against the allowed // roots before start(), but a symlink swapped in afterwards would redirect our writes // outside those roots (daemon/docs/safepath-adversarial.md leans on this open closing // that TOCTOU window). A symlinked leaf fails here with ELOOP -> Error::path_rejected. int flags = O_WRONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW; if (opts.truncate_existing) flags |= O_TRUNC; int fd = ::open(p.c_str(), flags, 0644); if (fd < 0) return sys_error("open " + p, errno); 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 { ::close(fd); return sys_error("posix_fallocate " + p, rc); } } 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) { int e = errno; ::close(fd); return sys_error("ftruncate " + p, e); } } } fd_ = fd; preallocated_ = prealloc; path_ = std::move(p); return ok(); } Result SparseFile::write_at(std::uint64_t offset, ConstByteSpan data) { if (fd_ < 0) return ErrorInfo(Error::internal, "write_at on a closed SparseFile"); const std::byte *p = data.data(); std::size_t remaining = data.size(); off_t pos = static_cast(offset); while (remaining > 0) { ssize_t n = ::pwrite(fd_, p, remaining, pos); if (n < 0) { if (errno == EINTR) continue; return sys_error("pwrite", errno); } if (n == 0) return ErrorInfo(Error::io_error, "pwrite returned 0"); p += n; pos += n; remaining -= static_cast(n); } return ok(); } 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(); } 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); } Result SparseFile::resize(std::uint64_t size) { if (fd_ < 0) return ErrorInfo(Error::internal, "resize on a closed SparseFile"); while (::ftruncate(fd_, static_cast(size)) != 0) { if (errno == EINTR) continue; return sys_error("ftruncate", errno); } return ok(); } Result SparseFile::close() { if (fd_ < 0) return ok(); int fd = std::exchange(fd_, -1); int rc = ::close(fd); reset(); if (rc != 0) return sys_error("close", errno); return ok(); } } // namespace vdm::io