142 lines
6.4 KiB
Text
142 lines
6.4 KiB
Text
|
|
// 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 <file>.v<old>.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<std::byte> out) const;
|
||
|
|
void WriteAt(std::uint64_t offset, std::span<const std::byte> 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<std::byte> out);
|
||
|
|
std::uint64_t UnixNanos() noexcept;
|
||
|
|
std::uint64_t PageSize() noexcept;
|
||
|
|
std::int64_t ProcessId() noexcept;
|
||
|
|
}
|
||
|
|
}
|