1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file This file implements the common abstractions for CAS database file.
10///
11//===----------------------------------------------------------------------===//
12
13#include "DatabaseFile.h"
14
15using namespace llvm;
16using namespace llvm::cas;
17using namespace llvm::cas::ondisk;
18
19Error ondisk::createTableConfigError(std::errc ErrC, StringRef Path,
20 StringRef TableName, const Twine &Msg) {
21 return createStringError(EC: make_error_code(e: ErrC),
22 S: Path + "[" + TableName + "]: " + Msg);
23}
24
25Error ondisk::checkTable(StringRef Label, size_t Expected, size_t Observed,
26 StringRef Path, StringRef TrieName) {
27 if (Expected == Observed)
28 return Error::success();
29 return createTableConfigError(ErrC: std::errc::invalid_argument, Path, TableName: TrieName,
30 Msg: "mismatched " + Label +
31 " (expected: " + Twine(Expected) +
32 ", observed: " + Twine(Observed) + ")");
33}
34
35Expected<DatabaseFile>
36DatabaseFile::create(const Twine &Path, uint64_t Capacity,
37 std::shared_ptr<OnDiskCASLogger> Logger,
38 function_ref<Error(DatabaseFile &)> NewDBConstructor) {
39 // Constructor for if the file doesn't exist.
40 auto NewFileConstructor = [&](MappedFileRegionArena &Alloc) -> Error {
41 if (Alloc.capacity() <
42 sizeof(Header) + sizeof(MappedFileRegionArena::Header))
43 return createTableConfigError(ErrC: std::errc::argument_out_of_domain,
44 Path: Path.str(), TableName: "datafile",
45 Msg: "Allocator too small for header");
46 (void)new (Alloc.data()) Header{.Magic: getMagic(), .Version: getVersion(), .RootTableOffset: {0}};
47 DatabaseFile DB(Alloc);
48 return NewDBConstructor(DB);
49 };
50
51 // Get or create the file.
52 MappedFileRegionArena Alloc;
53 if (Error E =
54 MappedFileRegionArena::create(Path, Capacity, HeaderOffset: sizeof(Header),
55 Logger: std::move(Logger), NewFileConstructor)
56 .moveInto(Value&: Alloc))
57 return std::move(E);
58
59 return DatabaseFile::get(
60 Alloc: std::make_unique<MappedFileRegionArena>(args: std::move(Alloc)));
61}
62
63Error DatabaseFile::addTable(TableHandle Table) {
64 assert(Table);
65 assert(&Table.getRegion() == &getRegion());
66 int64_t ExistingRootOffset = 0;
67 const int64_t NewOffset =
68 reinterpret_cast<const char *>(&Table.getHeader()) - getRegion().data();
69 if (H->RootTableOffset.compare_exchange_strong(i1&: ExistingRootOffset, i2: NewOffset))
70 return Error::success();
71
72 // Silently ignore attempts to set the root to itself.
73 if (ExistingRootOffset == NewOffset)
74 return Error::success();
75
76 // Return an proper error message.
77 TableHandle Root(getRegion(), ExistingRootOffset);
78 if (Root.getName() == Table.getName())
79 return createStringError(
80 EC: make_error_code(e: std::errc::not_supported),
81 S: "collision with existing table of the same name '" + Table.getName() +
82 "'");
83
84 return createStringError(EC: make_error_code(e: std::errc::not_supported),
85 S: "cannot add new table '" + Table.getName() +
86 "'"
87 " to existing root '" +
88 Root.getName() + "'");
89}
90
91std::optional<TableHandle> DatabaseFile::findTable(StringRef Name) {
92 int64_t RootTableOffset = H->RootTableOffset.load();
93 if (!RootTableOffset)
94 return std::nullopt;
95
96 TableHandle Root(getRegion(), RootTableOffset);
97 if (Root.getName() == Name)
98 return Root;
99
100 return std::nullopt;
101}
102
103Error DatabaseFile::validate(MappedFileRegion &Region) {
104 if (Region.size() < sizeof(Header))
105 return createStringError(EC: std::errc::invalid_argument,
106 Fmt: "database: missing header");
107
108 // Check the magic and version.
109 auto *H = reinterpret_cast<Header *>(Region.data());
110 if (H->Magic != getMagic())
111 return createStringError(EC: std::errc::invalid_argument,
112 Fmt: "database: bad magic");
113 if (H->Version != getVersion())
114 return createStringError(EC: std::errc::invalid_argument,
115 Fmt: "database: wrong version");
116
117 auto *MFH = reinterpret_cast<MappedFileRegionArena::Header *>(Region.data() +
118 sizeof(Header));
119 // Check the bump-ptr, which should point past the header.
120 if (MFH->BumpPtr.load() < (int64_t)sizeof(Header))
121 return createStringError(EC: std::errc::invalid_argument,
122 Fmt: "database: corrupt bump-ptr");
123
124 return Error::success();
125}
126