| 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 | |
| 15 | using namespace llvm; |
| 16 | using namespace llvm::cas; |
| 17 | using namespace llvm::cas::ondisk; |
| 18 | |
| 19 | Error 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 | |
| 25 | Error 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 | |
| 35 | Expected<DatabaseFile> |
| 36 | DatabaseFile::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 | |
| 63 | Error 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 | |
| 91 | std::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 | |
| 103 | Error 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 | |