// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Everything that talks to the operating system: file descriptors, advisory // locks, preallocation, the reserved virtual ranges that keep row pointers // stable while a table grows, durability calls and the atomic create/replace // dance. Nothing here knows what a table is; the Storage partition does. export module Crafter.DB:File; import std; import :Error; export namespace Crafter::DB { enum class OpenMode : std::uint8_t { OpenOrCreate, // open the table, create it when missing (default) OpenExisting, // IOError when missing CreateNew, // IOError when present }; enum class Durability : std::uint8_t { OnFlush, // durable after Flush() and on destruction (default) EveryAppend, // Flush() after every Append/Emplace/Update/Set/Erase }; struct OpenOptions { OpenMode mode = OpenMode::OpenOrCreate; bool readOnly = false; // PROT_READ mapping, shared lock, no migration, no writes bool createDirectories = true; // create missing parent directories with directoryPermissions std::filesystem::perms filePermissions = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write; // 0600 std::filesystem::perms directoryPermissions = std::filesystem::perms::owner_all; // 0700 Durability durability = Durability::OnFlush; bool migrate = true; // run the Previous chain on an older file; false throws MigrationError instead bool backupBeforeMigrate = true; // keep the pre-migration file as .v.bak std::uint64_t reserveBytes = std::uint64_t(32) << 30; // virtual address space reserved per region bool excludeFromCoreDumps = true; // MADV_DONTDUMP on the mappings bool prefetch = false; // MADV_WILLNEED over the committed data at open (cold-cache batch jobs) }; // An open file descriptor. Every operation throws IOError on failure. class File { public: File() = default; File(const File&) = delete; File& operator=(const File&) = delete; File(File&& other) noexcept; File& operator=(File&& other) noexcept; ~File(); static File Open(const std::filesystem::path& path, bool writable); static File CreateExclusive(const std::filesystem::path& path, std::filesystem::perms permissions); bool IsOpen() const noexcept { return descriptor != -1; } std::int32_t Descriptor() const noexcept { return descriptor; } const std::filesystem::path& Path() const noexcept { return path; } void SetPath(std::filesystem::path newPath) { path = std::move(newPath); } // flock(LOCK_NB). Advisory, per open file description, released on close. void LockExclusive(); void LockShared(); std::uint64_t Size() const; void Truncate(std::uint64_t length); // fallocate: real blocks, so a full disk is an error here and never a // SIGBUS on first touch of a mapped page. void Allocate(std::uint64_t offset, std::uint64_t length); void ReadAt(std::uint64_t offset, std::span out) const; void WriteAt(std::uint64_t offset, std::span in); void SyncData(); void Sync(); void Close() noexcept; private: std::int32_t descriptor = -1; std::filesystem::path path; }; // A reserved virtual address range into which file extents are mapped // back to back, so the region reads as one contiguous array that grows // without ever moving. class MappedRegion { public: MappedRegion() = default; MappedRegion(const MappedRegion&) = delete; MappedRegion& operator=(const MappedRegion&) = delete; MappedRegion(MappedRegion&& other) noexcept; MappedRegion& operator=(MappedRegion&& other) noexcept; ~MappedRegion(); void Reserve(std::uint64_t bytes); // Maps [fileOffset, fileOffset + length) at Base() + Mapped(). void Map(const File& file, std::uint64_t fileOffset, std::uint64_t length, bool writable); void ExcludeFromCoreDumps(); void WillNeed(std::uint64_t length); void Release() noexcept; std::byte* Base() const noexcept { return base; } std::uint64_t Mapped() const noexcept { return mapped; } std::uint64_t Reserved() const noexcept { return reserved; } private: std::byte* base = nullptr; std::uint64_t reserved = 0; std::uint64_t mapped = 0; bool dontDump = false; }; // A plain shared mapping of a few pages (the header). class MappedPages { public: MappedPages() = default; MappedPages(const MappedPages&) = delete; MappedPages& operator=(const MappedPages&) = delete; MappedPages(MappedPages&& other) noexcept; MappedPages& operator=(MappedPages&& other) noexcept; ~MappedPages(); void Map(const File& file, std::uint64_t fileOffset, std::uint64_t length, bool writable); void Sync(); void Release() noexcept; std::byte* Data() const noexcept { return data; } std::uint64_t Length() const noexcept { return length; } private: std::byte* data = nullptr; std::uint64_t length = 0; }; namespace Os { // mkdir -p with an explicit mode for every created component, so a // private cache directory is never world-readable, not even briefly. void CreateDirectories(const std::filesystem::path& directory, std::filesystem::perms permissions); void Rename(const std::filesystem::path& from, const std::filesystem::path& to); // Atomic rename that fails instead of replacing; false when `to` exists. bool RenameNoReplace(const std::filesystem::path& from, const std::filesystem::path& to); void HardLink(const std::filesystem::path& from, const std::filesystem::path& to); bool Remove(const std::filesystem::path& path) noexcept; void SyncDirectory(const std::filesystem::path& directory); void RandomBytes(std::span out); std::uint64_t UnixNanos() noexcept; std::uint64_t PageSize() noexcept; std::int64_t ProcessId() noexcept; } }