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
10/// Implements \c ObjectStore and \c ActionCache on top of a dynamically loaded
11/// plugin that provides the C API in \c "llvm-c/CAS/PluginAPI_functions.h".
12///
13/// The asynchronous entry points of the plugin API are not called yet; they
14/// will be wired up once \c ObjectStore and \c ActionCache grow asynchronous
15/// interfaces.
16///
17//===----------------------------------------------------------------------===//
18
19#include "PluginAPI.h"
20#include "llvm/ADT/STLFunctionalExtras.h"
21#include "llvm/ADT/ScopeExit.h"
22#include "llvm/CAS/ActionCache.h"
23#include "llvm/CAS/ObjectStore.h"
24#include "llvm/Support/DynamicLibrary.h"
25#include "llvm/Support/Error.h"
26#include "llvm/Support/MemoryBuffer.h"
27
28using namespace llvm;
29using namespace llvm::cas;
30
31namespace {
32
33class PluginCASContext : public CASContext {
34public:
35 void printIDImpl(raw_ostream &OS, const CASID &ID) const final;
36
37 StringRef getHashSchemaIdentifier() const final { return SchemaName; }
38
39 static Expected<std::shared_ptr<PluginCASContext>>
40 create(StringRef PluginPath, StringRef OnDiskPath,
41 ArrayRef<std::pair<std::string, std::string>> PluginArgs);
42
43 ~PluginCASContext() { Functions.cas_dispose(c_cas); }
44
45 llcas_functions_t Functions{};
46 llcas_cas_t c_cas = nullptr;
47 std::string SchemaName;
48
49 static Error errorAndDispose(char *c_err, const llcas_functions_t &Funcs) {
50 Error E = createStringError(EC: inconvertibleErrorCode(), S: c_err);
51 Funcs.string_dispose(c_err);
52 return E;
53 }
54
55 Error errorAndDispose(char *c_err) const {
56 return errorAndDispose(c_err, Funcs: Functions);
57 }
58};
59
60} // anonymous namespace
61
62void PluginCASContext::printIDImpl(raw_ostream &OS, const CASID &ID) const {
63 ArrayRef<uint8_t> Hash = ID.getHash();
64 char *c_printed_id = nullptr;
65 char *c_err = nullptr;
66 if (Functions.digest_print(c_cas, llcas_digest_t{.data: Hash.data(), .size: Hash.size()},
67 &c_printed_id, &c_err))
68 report_fatal_error(Err: errorAndDispose(c_err));
69 OS << c_printed_id;
70 Functions.string_dispose(c_printed_id);
71}
72
73/// Loads the plugin library at \p PluginPath and looks up its functions.
74static Expected<llcas_functions_t> loadPluginFunctions(StringRef PluginPath) {
75 auto reportError = [PluginPath](const Twine &Description) -> Error {
76 std::error_code EC = inconvertibleErrorCode();
77 return createStringError(EC, S: "error loading '" + PluginPath +
78 "': " + Description);
79 };
80
81 SmallString<256> PathBuf = PluginPath;
82 std::string ErrMsg;
83 sys::DynamicLibrary Lib =
84 sys::DynamicLibrary::getPermanentLibrary(filename: PathBuf.c_str(), errMsg: &ErrMsg);
85 if (!Lib.isValid())
86 return reportError(ErrMsg);
87
88 llcas_functions_t Functions{};
89
90#define CASPLUGINAPI_FUNCTION(name, required) \
91 if (!(Functions.name = (decltype(llcas_functions_t::name)) \
92 Lib.getAddressOfSymbol("llcas_" #name))) { \
93 if (required) \
94 return reportError("failed symbol 'llcas_" #name "' lookup"); \
95 }
96#include "PluginAPI_functions.def"
97#undef CASPLUGINAPI_FUNCTION
98
99 return Functions;
100}
101
102/// Creates a \c llcas_cas_options_t for \p OnDiskPath and \p PluginArgs. On
103/// success the caller is responsible for disposing it.
104static Expected<llcas_cas_options_t>
105createPluginOptions(const llcas_functions_t &Functions, StringRef OnDiskPath,
106 ArrayRef<std::pair<std::string, std::string>> PluginArgs) {
107 llcas_cas_options_t c_opts = Functions.cas_options_create();
108 Functions.cas_options_set_client_version(c_opts, LLCAS_VERSION_MAJOR,
109 LLCAS_VERSION_MINOR);
110 SmallString<256> OnDiskPathBuf = OnDiskPath;
111 Functions.cas_options_set_ondisk_path(c_opts, OnDiskPathBuf.c_str());
112 for (const auto &Pair : PluginArgs) {
113 char *c_err = nullptr;
114 if (Functions.cas_options_set_option(c_opts, Pair.first.c_str(),
115 Pair.second.c_str(), &c_err)) {
116 Functions.cas_options_dispose(c_opts);
117 return PluginCASContext::errorAndDispose(c_err, Funcs: Functions);
118 }
119 }
120 return c_opts;
121}
122
123Expected<std::shared_ptr<PluginCASContext>> PluginCASContext::create(
124 StringRef PluginPath, StringRef OnDiskPath,
125 ArrayRef<std::pair<std::string, std::string>> PluginArgs) {
126 llcas_functions_t Functions{};
127 if (Error E = loadPluginFunctions(PluginPath).moveInto(Value&: Functions))
128 return std::move(E);
129
130 llcas_cas_options_t c_opts = nullptr;
131 if (Error E = createPluginOptions(Functions, OnDiskPath, PluginArgs)
132 .moveInto(Value&: c_opts))
133 return std::move(E);
134 scope_exit DisposeOptions([&]() { Functions.cas_options_dispose(c_opts); });
135
136 char *c_err = nullptr;
137 llcas_cas_t c_cas = Functions.cas_create(c_opts, &c_err);
138 if (!c_cas)
139 return errorAndDispose(c_err, Funcs: Functions);
140
141 char *c_schema = Functions.cas_get_hash_schema_name(c_cas);
142 std::string SchemaName = c_schema;
143 Functions.string_dispose(c_schema);
144
145 auto Ctx = std::make_shared<PluginCASContext>();
146 Ctx->Functions = Functions;
147 Ctx->c_cas = c_cas;
148 Ctx->SchemaName = std::move(SchemaName);
149 return Ctx;
150}
151
152//===----------------------------------------------------------------------===//
153// ObjectStore API
154//===----------------------------------------------------------------------===//
155
156namespace {
157
158class PluginObjectStore : public ObjectStore {
159public:
160 Expected<CASID> parseID(StringRef ID) final;
161 Expected<ObjectRef> store(ArrayRef<ObjectRef> Refs,
162 ArrayRef<char> Data) final;
163 Expected<ObjectRef> storeFromFile(StringRef Path) final;
164 Error exportDataToFile(ObjectHandle Node, StringRef Path) const final;
165 CASID getID(ObjectRef Ref) const final;
166 std::optional<ObjectRef> getReference(const CASID &ID) const final;
167 Expected<bool> isMaterialized(ObjectRef Ref) const final;
168 Expected<std::optional<ObjectHandle>> loadIfExists(ObjectRef Ref) final;
169 uint64_t getDataSize(ObjectHandle Node) const final;
170 Error forEachRef(ObjectHandle Node,
171 function_ref<Error(ObjectRef)> Callback) const final;
172 ObjectRef readRef(ObjectHandle Node, size_t I) const final;
173 size_t getNumRefs(ObjectHandle Node) const final;
174 ArrayRef<char> getData(ObjectHandle Node,
175 bool RequiresNullTerminator = false) const final;
176 std::unique_ptr<MemoryBuffer>
177 getStandaloneMemoryBufferImpl(ObjectHandle Node, StringRef Name,
178 bool RequiresNullTerminator) final;
179 Error validateObject(const CASID &ID) final {
180 // Not supported yet. Always return success.
181 return Error::success();
182 }
183
184 Error validate(bool CheckHash) const final;
185
186 Error setSizeLimit(std::optional<uint64_t> SizeLimit) final;
187 Expected<std::optional<uint64_t>> getStorageSize() const final;
188 Error pruneStorageData() final;
189
190 PluginObjectStore(std::shared_ptr<PluginCASContext>);
191
192 /// Exposes \c makeObjectRef to the file-local helpers below.
193 ObjectRef makeRef(uint64_t InternalRef) const {
194 return makeObjectRef(InternalRef);
195 }
196
197 std::shared_ptr<PluginCASContext> Ctx;
198};
199
200} // anonymous namespace
201
202Expected<CASID> PluginObjectStore::parseID(StringRef ID) {
203 // Use big enough stack so that we don't have to allocate in the heap.
204 SmallString<148> IDBuf(ID);
205 SmallVector<uint8_t, 68> BytesBuf(68);
206
207 auto parseDigest = [&]() -> Expected<unsigned> {
208 char *c_err = nullptr;
209 unsigned NumBytes = Ctx->Functions.digest_parse(
210 Ctx->c_cas, IDBuf.c_str(), BytesBuf.data(), BytesBuf.size(), &c_err);
211 if (NumBytes == 0)
212 return Ctx->errorAndDispose(c_err);
213 return NumBytes;
214 };
215
216 Expected<unsigned> NumBytes = parseDigest();
217 if (!NumBytes)
218 return NumBytes.takeError();
219
220 if (*NumBytes > BytesBuf.size()) {
221 BytesBuf.resize(N: *NumBytes);
222 NumBytes = parseDigest();
223 if (!NumBytes)
224 return NumBytes.takeError();
225 assert(*NumBytes == BytesBuf.size());
226 } else {
227 BytesBuf.truncate(N: *NumBytes);
228 }
229
230 return CASID::create(Context: Ctx.get(), Hash: toStringRef(Input: BytesBuf));
231}
232
233Expected<ObjectRef> PluginObjectStore::store(ArrayRef<ObjectRef> Refs,
234 ArrayRef<char> Data) {
235 SmallVector<llcas_objectid_t, 64> c_ids;
236 c_ids.reserve(N: Refs.size());
237 for (ObjectRef Ref : Refs) {
238 c_ids.push_back(Elt: llcas_objectid_t{.opaque: Ref.getInternalRef(ExpectedCAS: *this)});
239 }
240
241 llcas_objectid_t c_stored_id;
242 char *c_err = nullptr;
243 if (Ctx->Functions.cas_store_object(
244 Ctx->c_cas, llcas_data_t{.data: Data.data(), .size: Data.size()}, c_ids.data(),
245 c_ids.size(), &c_stored_id, &c_err))
246 return Ctx->errorAndDispose(c_err);
247
248 return makeObjectRef(InternalRef: c_stored_id.opaque);
249}
250
251Expected<ObjectRef> PluginObjectStore::storeFromFile(StringRef Path) {
252 if (!Ctx->Functions.cas_store_from_filepath)
253 return ObjectStore::storeFromFile(Path);
254
255 llcas_objectid_t c_stored_id;
256 char *c_err = nullptr;
257 std::string PathStr = Path.str();
258 if (Ctx->Functions.cas_store_from_filepath(Ctx->c_cas, PathStr.c_str(),
259 &c_stored_id, &c_err))
260 return Ctx->errorAndDispose(c_err);
261
262 return makeObjectRef(InternalRef: c_stored_id.opaque);
263}
264
265Error PluginObjectStore::exportDataToFile(ObjectHandle Node,
266 StringRef Path) const {
267 if (!Ctx->Functions.loaded_object_export_data_to_filepath)
268 return ObjectStore::exportDataToFile(Node, Path);
269
270 char *c_err = nullptr;
271 std::string PathStr = Path.str();
272 if (Ctx->Functions.loaded_object_export_data_to_filepath(
273 Ctx->c_cas, llcas_loaded_object_t{.opaque: Node.getInternalRef(ExpectedCAS: *this)},
274 PathStr.c_str(), &c_err))
275 return Ctx->errorAndDispose(c_err);
276
277 return Error::success();
278}
279
280static StringRef toStringRef(llcas_digest_t c_digest) {
281 return StringRef((const char *)c_digest.data, c_digest.size);
282}
283
284CASID PluginObjectStore::getID(ObjectRef Ref) const {
285 llcas_objectid_t c_id{.opaque: Ref.getInternalRef(ExpectedCAS: *this)};
286 llcas_digest_t c_digest =
287 Ctx->Functions.objectid_get_digest(Ctx->c_cas, c_id);
288 return CASID::create(Context: Ctx.get(), Hash: toStringRef(c_digest));
289}
290
291std::optional<ObjectRef>
292PluginObjectStore::getReference(const CASID &ID) const {
293 ArrayRef<uint8_t> Hash = ID.getHash();
294 llcas_objectid_t c_id;
295 char *c_err = nullptr;
296 if (Ctx->Functions.cas_get_objectid(
297 Ctx->c_cas, llcas_digest_t{.data: Hash.data(), .size: Hash.size()}, &c_id, &c_err))
298 report_fatal_error(Err: Ctx->errorAndDispose(c_err));
299
300 return makeObjectRef(InternalRef: c_id.opaque);
301}
302
303Expected<bool> PluginObjectStore::isMaterialized(ObjectRef Ref) const {
304 llcas_objectid_t c_id{.opaque: Ref.getInternalRef(ExpectedCAS: *this)};
305 char *c_err = nullptr;
306 llcas_lookup_result_t c_result = Ctx->Functions.cas_contains_object(
307 Ctx->c_cas, c_id, /*globally=*/false, &c_err);
308 switch (c_result) {
309 case LLCAS_LOOKUP_RESULT_SUCCESS:
310 return true;
311 case LLCAS_LOOKUP_RESULT_NOTFOUND:
312 return false;
313 case LLCAS_LOOKUP_RESULT_ERROR:
314 return Ctx->errorAndDispose(c_err);
315 }
316 llvm_unreachable("unknown llcas_lookup_result_t value");
317}
318
319Expected<std::optional<ObjectHandle>>
320PluginObjectStore::loadIfExists(ObjectRef Ref) {
321 llcas_objectid_t c_id{.opaque: Ref.getInternalRef(ExpectedCAS: *this)};
322 llcas_loaded_object_t c_obj;
323 char *c_err = nullptr;
324 llcas_lookup_result_t c_result =
325 Ctx->Functions.cas_load_object(Ctx->c_cas, c_id, &c_obj, &c_err);
326 switch (c_result) {
327 case LLCAS_LOOKUP_RESULT_SUCCESS:
328 return makeObjectHandle(InternalRef: c_obj.opaque);
329 case LLCAS_LOOKUP_RESULT_NOTFOUND:
330 return std::nullopt;
331 case LLCAS_LOOKUP_RESULT_ERROR:
332 return Ctx->errorAndDispose(c_err);
333 }
334 llvm_unreachable("unknown llcas_lookup_result_t value");
335}
336
337namespace {
338
339class ObjectRefsWrapper {
340public:
341 ObjectRefsWrapper(const ObjectHandle &Node, const PluginObjectStore &Store)
342 : Store(Store), Ctx(*Store.Ctx) {
343 llcas_loaded_object_t c_obj{.opaque: Node.getInternalRef(ExpectedCAS: Store)};
344 this->c_refs = Ctx.Functions.loaded_object_get_refs(Ctx.c_cas, c_obj);
345 }
346
347 size_t size() const {
348 return Ctx.Functions.object_refs_get_count(Ctx.c_cas, c_refs);
349 }
350
351 ObjectRef operator[](size_t I) const {
352 llcas_objectid_t c_id =
353 Ctx.Functions.object_refs_get_id(Ctx.c_cas, c_refs, I);
354 return Store.makeRef(InternalRef: c_id.opaque);
355 }
356
357private:
358 const PluginObjectStore &Store;
359 PluginCASContext &Ctx;
360 llcas_object_refs_t c_refs;
361};
362
363} // namespace
364
365// FIXME: Replace forEachRef/readRef/getNumRefs APIs with an iterator interface.
366Error PluginObjectStore::forEachRef(
367 ObjectHandle Node, function_ref<Error(ObjectRef)> Callback) const {
368 ObjectRefsWrapper Refs(Node, *this);
369 for (unsigned I = 0, E = Refs.size(); I != E; ++I) {
370 if (Error E = Callback(Refs[I]))
371 return E;
372 }
373 return Error::success();
374}
375
376ObjectRef PluginObjectStore::readRef(ObjectHandle Node, size_t I) const {
377 ObjectRefsWrapper Refs(Node, *this);
378 return Refs[I];
379}
380
381size_t PluginObjectStore::getNumRefs(ObjectHandle Node) const {
382 ObjectRefsWrapper Refs(Node, *this);
383 return Refs.size();
384}
385
386// FIXME: Remove getDataSize(ObjectHandle) from API requirement,
387// \c getData(ObjectHandle) should be enough.
388uint64_t PluginObjectStore::getDataSize(ObjectHandle Node) const {
389 ArrayRef<char> Data = getData(Node);
390 return Data.size();
391}
392
393ArrayRef<char> PluginObjectStore::getData(ObjectHandle Node,
394 bool RequiresNullTerminator) const {
395 // FIXME: Remove RequiresNullTerminator from ObjectStore API requirement?
396 // It is a requirement for the plugin API.
397 llcas_data_t c_data = Ctx->Functions.loaded_object_get_data(
398 Ctx->c_cas, llcas_loaded_object_t{.opaque: Node.getInternalRef(ExpectedCAS: *this)});
399 return ArrayRef((const char *)c_data.data, c_data.size);
400}
401
402namespace {
403/// A MemoryBuffer over a plugin's standalone buffer, which it releases when
404/// destroyed. It holds the dispose function directly rather than the
405/// \c llcas_cas_t, since the point of the buffer is to outlive that.
406class PluginStandaloneMemoryBuffer final : public MemoryBuffer {
407public:
408 using DisposeFn = void (*)(llcas_data_t);
409
410 PluginStandaloneMemoryBuffer(llcas_data_t Data, StringRef Name,
411 DisposeFn Dispose)
412 : Data(Data), Name(Name.str()), Dispose(Dispose) {
413 const char *Start = static_cast<const char *>(Data.data);
414 init(BufStart: Start, BufEnd: Start + Data.size, /*RequiresNullTerminator=*/true);
415 }
416
417 ~PluginStandaloneMemoryBuffer() override { Dispose(Data); }
418
419 StringRef getBufferIdentifier() const final { return Name; }
420
421 BufferKind getBufferKind() const final { return MemoryBuffer_Malloc; }
422
423private:
424 llcas_data_t Data;
425 std::string Name;
426 DisposeFn Dispose;
427};
428} // namespace
429
430std::unique_ptr<MemoryBuffer> PluginObjectStore::getStandaloneMemoryBufferImpl(
431 ObjectHandle Node, StringRef Name, bool RequiresNullTerminator) {
432 // Both halves are needed: without the disposer there is no way to release
433 // what the getter hands out.
434 if (!Ctx->Functions.loaded_object_get_standalone_data ||
435 !Ctx->Functions.standalone_data_dispose)
436 return ObjectStore::getStandaloneMemoryBufferImpl(Node, Name,
437 RequiresNullTerminator);
438
439 llcas_data_t c_data = Ctx->Functions.loaded_object_get_standalone_data(
440 Ctx->c_cas, llcas_loaded_object_t{.opaque: Node.getInternalRef(ExpectedCAS: *this)});
441 if (!c_data.data)
442 return ObjectStore::getStandaloneMemoryBufferImpl(Node, Name,
443 RequiresNullTerminator);
444
445 return std::make_unique<PluginStandaloneMemoryBuffer>(
446 args&: c_data, args&: Name, args&: Ctx->Functions.standalone_data_dispose);
447}
448
449Error PluginObjectStore::setSizeLimit(std::optional<uint64_t> SizeLimit) {
450 if (Ctx->Functions.cas_set_ondisk_size_limit) {
451 char *c_err = nullptr;
452 if (Ctx->Functions.cas_set_ondisk_size_limit(Ctx->c_cas,
453 SizeLimit.value_or(u: 0), &c_err))
454 return Ctx->errorAndDispose(c_err);
455 }
456 return Error::success();
457}
458
459Expected<std::optional<uint64_t>> PluginObjectStore::getStorageSize() const {
460 if (!Ctx->Functions.cas_get_ondisk_size)
461 return std::nullopt;
462 char *c_err = nullptr;
463 int64_t ret = Ctx->Functions.cas_get_ondisk_size(Ctx->c_cas, &c_err);
464 switch (ret) {
465 case -1:
466 return std::nullopt;
467 case -2:
468 return Ctx->errorAndDispose(c_err);
469 default:
470 return ret;
471 }
472}
473
474Error PluginObjectStore::pruneStorageData() {
475 if (Ctx->Functions.cas_prune_ondisk_data) {
476 char *c_err = nullptr;
477 if (Ctx->Functions.cas_prune_ondisk_data(Ctx->c_cas, &c_err))
478 return Ctx->errorAndDispose(c_err);
479 }
480 return Error::success();
481}
482
483Error PluginObjectStore::validate(bool CheckHash) const {
484 if (Ctx->Functions.cas_validate) {
485 char *c_err = nullptr;
486 if (Ctx->Functions.cas_validate(Ctx->c_cas, CheckHash, &c_err))
487 return Ctx->errorAndDispose(c_err);
488 return Error::success();
489 }
490 return createStringError(Fmt: "plugin cas doesn't support validation");
491}
492
493PluginObjectStore::PluginObjectStore(std::shared_ptr<PluginCASContext> CASCtx)
494 : ObjectStore(*CASCtx), Ctx(std::move(CASCtx)) {}
495
496//===----------------------------------------------------------------------===//
497// ActionCache API
498//===----------------------------------------------------------------------===//
499
500namespace {
501
502class PluginActionCache : public ActionCache {
503public:
504 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ResolvedKey,
505 bool CanBeDistributed) const final;
506
507 Error putImpl(ArrayRef<uint8_t> ResolvedKey, const CASID &Result,
508 bool CanBeDistributed) final;
509
510 PluginActionCache(std::shared_ptr<PluginCASContext>);
511
512 Error validate() const final;
513
514private:
515 std::shared_ptr<PluginCASContext> Ctx;
516};
517
518} // anonymous namespace
519
520Expected<std::optional<CASID>>
521PluginActionCache::getImpl(ArrayRef<uint8_t> ResolvedKey,
522 bool CanBeDistributed) const {
523 llcas_objectid_t c_value;
524 char *c_err = nullptr;
525 llcas_lookup_result_t c_result = Ctx->Functions.actioncache_get_for_digest(
526 Ctx->c_cas, llcas_digest_t{.data: ResolvedKey.data(), .size: ResolvedKey.size()},
527 &c_value, CanBeDistributed, &c_err);
528 switch (c_result) {
529 case LLCAS_LOOKUP_RESULT_SUCCESS: {
530 llcas_digest_t c_digest =
531 Ctx->Functions.objectid_get_digest(Ctx->c_cas, c_value);
532 return CASID::create(Context: Ctx.get(), Hash: toStringRef(c_digest));
533 }
534 case LLCAS_LOOKUP_RESULT_NOTFOUND:
535 return std::nullopt;
536 case LLCAS_LOOKUP_RESULT_ERROR:
537 return Ctx->errorAndDispose(c_err);
538 }
539 llvm_unreachable("unknown llcas_lookup_result_t value");
540}
541
542Error PluginActionCache::putImpl(ArrayRef<uint8_t> ResolvedKey,
543 const CASID &Result, bool CanBeDistributed) {
544 ArrayRef<uint8_t> Hash = Result.getHash();
545 llcas_objectid_t c_value;
546 char *c_err = nullptr;
547 if (Ctx->Functions.cas_get_objectid(Ctx->c_cas,
548 llcas_digest_t{.data: Hash.data(), .size: Hash.size()},
549 &c_value, &c_err))
550 return Ctx->errorAndDispose(c_err);
551
552 if (Ctx->Functions.actioncache_put_for_digest(
553 Ctx->c_cas, llcas_digest_t{.data: ResolvedKey.data(), .size: ResolvedKey.size()},
554 c_value, CanBeDistributed, &c_err))
555 return Ctx->errorAndDispose(c_err);
556
557 return Error::success();
558}
559
560PluginActionCache::PluginActionCache(std::shared_ptr<PluginCASContext> CASCtx)
561 : ActionCache(*CASCtx), Ctx(std::move(CASCtx)) {}
562
563Error PluginActionCache::validate() const {
564 if (Ctx->Functions.actioncache_validate) {
565 char *c_err = nullptr;
566 if (Ctx->Functions.actioncache_validate(Ctx->c_cas, &c_err))
567 return Ctx->errorAndDispose(c_err);
568 return Error::success();
569 }
570 return createStringError(Fmt: "plugin action cache doesn't support validation");
571}
572
573//===----------------------------------------------------------------------===//
574// createPluginCASDatabases API
575//===----------------------------------------------------------------------===//
576
577Expected<std::pair<std::shared_ptr<ObjectStore>, std::shared_ptr<ActionCache>>>
578cas::createPluginCASDatabases(
579 StringRef PluginPath, StringRef OnDiskPath,
580 ArrayRef<std::pair<std::string, std::string>> PluginArgs) {
581 std::shared_ptr<PluginCASContext> Ctx;
582 if (Error E = PluginCASContext::create(PluginPath, OnDiskPath, PluginArgs)
583 .moveInto(Value&: Ctx))
584 return std::move(E);
585 auto CAS = std::make_shared<PluginObjectStore>(args&: Ctx);
586 auto AC = std::make_shared<PluginActionCache>(args: std::move(Ctx));
587 return std::make_pair(x: std::move(CAS), y: std::move(AC));
588}
589
590/// Loads the plugin and calls \p Fn with a \c llcas_cas_options_t for
591/// \p OnDiskPath and \p PluginArgs, converting the returned
592/// \c llcas_validation_result_t.
593static Expected<ValidationResult> callPluginValidationFunction(
594 StringRef PluginPath, StringRef OnDiskPath,
595 ArrayRef<std::pair<std::string, std::string>> PluginArgs,
596 function_ref<Expected<llcas_validation_result_t>(
597 const llcas_functions_t &, llcas_cas_options_t, char **)>
598 Fn) {
599 llcas_functions_t Functions{};
600 if (Error E = loadPluginFunctions(PluginPath).moveInto(Value&: Functions))
601 return std::move(E);
602
603 llcas_cas_options_t c_opts = nullptr;
604 if (Error E = createPluginOptions(Functions, OnDiskPath, PluginArgs)
605 .moveInto(Value&: c_opts))
606 return std::move(E);
607 scope_exit DisposeOptions([&]() { Functions.cas_options_dispose(c_opts); });
608
609 char *c_err = nullptr;
610 llcas_validation_result_t Result;
611 if (Error E = Fn(Functions, c_opts, &c_err).moveInto(Value&: Result))
612 return std::move(E);
613 switch (Result) {
614 case LLCAS_VALIDATION_RESULT_VALID:
615 return ValidationResult::Valid;
616 case LLCAS_VALIDATION_RESULT_RECOVERED:
617 return ValidationResult::Recovered;
618 case LLCAS_VALIDATION_RESULT_SKIPPED:
619 return ValidationResult::Skipped;
620 case LLCAS_VALIDATION_RESULT_ERROR:
621 return PluginCASContext::errorAndDispose(c_err, Funcs: Functions);
622 }
623 // The plugin is outside of our control, so an unknown result is an error
624 // rather than unreachable. Include the plugin's error message, if any.
625 std::string Msg;
626 raw_string_ostream OS(Msg);
627 OS << "unknown validation result " << static_cast<int>(Result) << " from '"
628 << PluginPath << "'";
629 if (c_err) {
630 OS << ": " << c_err;
631 Functions.string_dispose(c_err);
632 }
633 return createStringError(S: Msg);
634}
635
636Expected<ValidationResult> cas::validatePluginCASDatabasesIfNeeded(
637 StringRef PluginPath, StringRef OnDiskPath,
638 ArrayRef<std::pair<std::string, std::string>> PluginArgs, bool CheckHash,
639 bool ForceValidation) {
640 return callPluginValidationFunction(
641 PluginPath, OnDiskPath, PluginArgs,
642 Fn: [&](const llcas_functions_t &Functions, llcas_cas_options_t c_opts,
643 char **c_err) -> Expected<llcas_validation_result_t> {
644 if (!Functions.cas_validate_if_needed)
645 return createStringError(
646 Fmt: "plugin cas doesn't support validate-if-needed");
647 return Functions.cas_validate_if_needed(c_opts, CheckHash,
648 ForceValidation, c_err);
649 });
650}
651
652Expected<ValidationResult> cas::recoverPluginCASDatabases(
653 StringRef PluginPath, StringRef OnDiskPath,
654 ArrayRef<std::pair<std::string, std::string>> PluginArgs) {
655 return callPluginValidationFunction(
656 PluginPath, OnDiskPath, PluginArgs,
657 Fn: [&](const llcas_functions_t &Functions, llcas_cas_options_t c_opts,
658 char **c_err) -> Expected<llcas_validation_result_t> {
659 if (!Functions.cas_recover_ondisk_data)
660 return createStringError(Fmt: "plugin cas doesn't support recovery");
661 return Functions.cas_recover_ondisk_data(c_opts, c_err);
662 });
663}
664