GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_plugin/cvmfs_cache_posix.cc
Date: 2026-09-27 02:40:09
Exec Total Coverage
Lines: 0 279 0.0%
Branches: 0 130 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 **/
5
6 #include <fcntl.h>
7 #include <signal.h>
8 #include <sys/types.h>
9 #include <sys/wait.h>
10
11 #include <cstring>
12 #include <string>
13
14 #include "cache_plugin/libcvmfs_cache.h"
15 #include "cache_posix.h"
16 #include "smallhash.h"
17 #include "util/atomic.h"
18 #include "util/logging.h"
19 #include "util/posix.h"
20 #include "util/string.h"
21
22 ✗ bool operator==(const cvmcache_hash &a, const cvmcache_hash &b) {
23 ✗ return memcmp(a.digest, b.digest, 20) == 0;
24 }
25
26 ✗ bool operator!=(const cvmcache_hash &a, const cvmcache_hash &b) {
27 ✗ return memcmp(a.digest, b.digest, 20) != 0;
28 }
29
30 namespace {
31
32 struct CacheObject {
33 uint32_t refcnt;
34 int fd;
35 uint64_t size;
36 };
37
38 struct Txn {
39 struct cvmcache_hash hash;
40 uint64_t size;
41 cvmcache_object_type type;
42 void *txn;
43 int fd;
44 };
45
46 struct Listing {
47 cvmcache_object_type type;
48 std::vector<std::string> list;
49 std::vector<std::string>::iterator it;
50 };
51
52 struct Settings {
53 ✗ Settings()
54 ✗ : is_alien(false)
55 ✗ , cache_base_defined(false)
56 ✗ , cache_dir_defined(false)
57 ✗ , quota_limit(0) { }
58
59 ✗ bool IsValid() {
60 ✗ if (is_alien && quota_limit > 0) {
61 ✗ error_reason = "Alien cache cannot be managed (no quota manager allowed)";
62 ✗ return false;
63 }
64 ✗ if (is_alien && workspace == "") {
65 ✗ error_reason = "Workspace option needs to be set for alien cache";
66 ✗ return false;
67 }
68 ✗ if ((is_alien ? 1 : 0) + (cache_base_defined ? 1 : 0)
69 ✗ + (cache_dir_defined ? 1 : 0)
70 != 1) {
71 error_reason = "CVMFS_CACHE_DIR, CVMFS_CACHE_BASE and CVMFS_CACHE_ALIEN "
72 ✗ "are mutually exclusive. Exactly one needs to be defined.";
73 ✗ return false;
74 }
75 ✗ return true;
76 }
77
78 bool is_alien;
79 bool cache_base_defined;
80 bool cache_dir_defined;
81 /**
82 * Soft limit in bytes for the cache. The quota manager removes half the
83 * cache when the limit is exceeded.
84 */
85 int64_t quota_limit;
86 std::string cache_path;
87 /**
88 * Different from cache_path only if CVMFS_WORKSPACE or
89 * CVMFS_CACHE_WORKSPACE is set.
90 */
91 std::string workspace;
92
93 std::string error_reason;
94 };
95
96
97 ✗ Settings GetSettings(cvmcache_option_map *options) {
98 ✗ Settings settings;
99 ✗ char *optarg = NULL;
100
101 ✗ if ((optarg = cvmcache_options_get(options, "CVMFS_CACHE_QUOTA_LIMIT"))) {
102 ✗ settings.quota_limit = String2Int64(optarg) * 1024 * 1024;
103 ✗ cvmcache_options_free(optarg);
104 }
105
106 ✗ if ((optarg = cvmcache_options_get(options, "CVMFS_CACHE_BASE"))) {
107 ✗ settings.cache_base_defined = true;
108 ✗ settings.cache_path = MakeCanonicalPath(optarg);
109 ✗ settings.cache_path += "/shared"; // this cache is always shared
110 ✗ settings.workspace = settings.cache_path; // default value for workspace
111 ✗ cvmcache_options_free(optarg);
112 }
113
114 ✗ if ((optarg = cvmcache_options_get(options, "CVMFS_CACHE_DIR"))) {
115 ✗ settings.cache_dir_defined = true;
116 ✗ settings.cache_path = optarg;
117 ✗ settings.workspace = settings.cache_path; // default value for workspace
118 ✗ cvmcache_options_free(optarg);
119 }
120 ✗ if ((optarg = cvmcache_options_get(options, "CVMFS_CACHE_ALIEN"))) {
121 ✗ settings.is_alien = true;
122 ✗ settings.cache_path = optarg;
123 ✗ cvmcache_options_free(optarg);
124 }
125
126 ✗ if ((optarg = cvmcache_options_get(options, "CVMFS_CACHE_WORKSPACE"))) {
127 // Used for the shared quota manager
128 ✗ settings.workspace = optarg;
129 ✗ cvmcache_options_free(optarg);
130 }
131 ✗ return settings;
132 }
133
134 ✗ uint32_t cvmcache_hash_hasher(const struct cvmcache_hash &key) {
135 return static_cast<uint32_t>(
136 ✗ *(reinterpret_cast<const uint32_t *>(key.digest) + 1));
137 }
138
139 ✗ uint32_t uint64_hasher(const uint64_t &key) { return (uint32_t)key; }
140
141 ✗ shash::Any Chash2Cpphash(const struct cvmcache_hash *h) {
142 ✗ shash::Any hash;
143 ✗ memcpy(hash.digest, h->digest, sizeof(h->digest));
144 ✗ hash.algorithm = static_cast<shash::Algorithms>(h->algorithm);
145 ✗ return hash;
146 }
147
148 ✗ struct cvmcache_hash Cpphash2Chash(const shash::Any &hash) {
149 struct cvmcache_hash h;
150 ✗ memset(h.digest, 0, 20); // ensure deterministic digest
151 ✗ memcpy(h.digest, hash.digest, sizeof(h.digest));
152 ✗ h.algorithm = hash.algorithm;
153 ✗ return h;
154 }
155
156 SmallHashDynamic<struct cvmcache_hash, CacheObject> *g_opened_objects;
157 SmallHashDynamic<uint64_t, Txn> *g_transactions;
158 SmallHashDynamic<uint64_t, Listing> *g_listings;
159 PosixCacheManager *g_cache_mgr;
160 cvmcache_context *g_ctx;
161 atomic_int32 g_terminated;
162 uint64_t g_pinned_size;
163 uint64_t g_used_size;
164 uint64_t g_capacity;
165
166 ✗ int posix_chrefcnt(struct cvmcache_hash *id, int32_t change_by) {
167 CacheObject object;
168 ✗ if (!g_opened_objects->Lookup(*id, &object)) {
169 ✗ if (change_by < 0) {
170 ✗ return CVMCACHE_STATUS_BADCOUNT;
171 }
172 ✗ const CacheManager::LabeledObject labeled_object(Chash2Cpphash(id));
173 ✗ const int fd = g_cache_mgr->Open(labeled_object);
174 ✗ if (fd < 0) {
175 ✗ return CVMCACHE_STATUS_NOENTRY;
176 }
177 ✗ object.fd = fd;
178 ✗ object.size = g_cache_mgr->GetSize(fd);
179 ✗ object.refcnt = 0;
180 ✗ g_pinned_size += object.size;
181 ✗ } else if (static_cast<int32_t>(object.refcnt) + change_by < 0) {
182 ✗ return CVMCACHE_STATUS_BADCOUNT;
183 }
184
185 ✗ object.refcnt += change_by;
186 ✗ if (object.refcnt == 0) {
187 ✗ if (g_cache_mgr->Close(object.fd) != 0) {
188 ✗ return CVMCACHE_STATUS_IOERR;
189 }
190 ✗ g_pinned_size -= object.size;
191 ✗ g_opened_objects->Erase(*id);
192 } else {
193 ✗ g_opened_objects->Insert(*id, object);
194 }
195 ✗ return CVMCACHE_STATUS_OK;
196 }
197
198 // Only gives info for opened objects.
199 // Should be fine, since cvmfs only requests info for opened objects.
200 ✗ int posix_obj_info(struct cvmcache_hash *id,
201 struct cvmcache_object_info *info) {
202 CacheObject object;
203 ✗ if (!g_opened_objects->Lookup(*id, &object)) {
204 ✗ return CVMCACHE_STATUS_NOENTRY;
205 }
206 ✗ info->id = *id;
207 ✗ info->size = object.size;
208 ✗ return CVMCACHE_STATUS_OK;
209 }
210
211 ✗ int posix_pread(struct cvmcache_hash *id, uint64_t offset, uint32_t *size,
212 unsigned char *buffer) {
213 CacheObject object;
214 ✗ if (!g_opened_objects->Lookup(*id, &object)) {
215 ✗ return CVMCACHE_STATUS_NOENTRY;
216 }
217 ✗ if (offset > object.size) {
218 ✗ return CVMCACHE_STATUS_OUTOFBOUNDS;
219 }
220 ✗ const int64_t bytes_read = g_cache_mgr->Pread(object.fd, buffer, *size,
221 offset);
222 ✗ if (bytes_read < 0) {
223 ✗ return CVMCACHE_STATUS_IOERR;
224 }
225 ✗ *size = static_cast<uint32_t>(bytes_read);
226 ✗ return CVMCACHE_STATUS_OK;
227 }
228
229 ✗ int posix_start_txn(struct cvmcache_hash *id,
230 uint64_t txn_id,
231 struct cvmcache_object_info *info) {
232 // cachemgr deletes txn in commit_txn
233 ✗ void *txn = malloc(g_cache_mgr->SizeOfTxn());
234 ✗ const int fd = g_cache_mgr->StartTxn(Chash2Cpphash(id), info->size, txn);
235 ✗ if (fd < 0) {
236 ✗ return CVMCACHE_STATUS_IOERR;
237 }
238 Txn transaction;
239 ✗ transaction.fd = fd;
240 ✗ transaction.hash = *id;
241 ✗ transaction.txn = txn;
242 ✗ transaction.size = info->size;
243 ✗ transaction.type = info->type;
244 ✗ g_transactions->Insert(txn_id, transaction);
245
246 ✗ CacheManager::Label label;
247 ✗ if (info->type == CVMCACHE_OBJECT_CATALOG) {
248 ✗ label.flags |= CacheManager::kLabelCatalog;
249 ✗ } else if (info->type == CVMCACHE_OBJECT_VOLATILE) {
250 ✗ label.flags = CacheManager::kLabelVolatile;
251 }
252 ✗ if (info->description) {
253 ✗ label.path = info->description;
254 }
255 ✗ g_cache_mgr->CtrlTxn(label, 0, txn);
256 ✗ return CVMCACHE_STATUS_OK;
257 }
258
259 ✗ int posix_write_txn(uint64_t txn_id, unsigned char *buffer, uint32_t size) {
260 Txn transaction;
261 ✗ if (!g_transactions->Lookup(txn_id, &transaction)) {
262 ✗ return CVMCACHE_STATUS_NOENTRY;
263 }
264 ✗ const int64_t bytes_written = g_cache_mgr->Write(buffer, size,
265 transaction.txn);
266 ✗ if ((bytes_written >= 0) && (static_cast<uint32_t>(bytes_written) == size)) {
267 ✗ return CVMCACHE_STATUS_OK;
268 } else {
269 ✗ return CVMCACHE_STATUS_IOERR;
270 }
271 }
272
273 ✗ int posix_commit_txn(uint64_t txn_id) {
274 Txn transaction;
275 ✗ if (!g_transactions->Lookup(txn_id, &transaction)) {
276 ✗ return CVMCACHE_STATUS_NOENTRY;
277 }
278 CacheObject object;
279 ✗ if (!g_opened_objects->Lookup(transaction.hash, &object)) {
280 ✗ object.fd = g_cache_mgr->OpenFromTxn(transaction.txn);
281 ✗ if (object.fd < 0) {
282 ✗ return CVMCACHE_STATUS_IOERR;
283 }
284 ✗ const int result = g_cache_mgr->CommitTxn(transaction.txn);
285 ✗ if (result) {
286 ✗ return CVMCACHE_STATUS_IOERR;
287 }
288 ✗ object.refcnt = 0;
289 ✗ object.size = g_cache_mgr->GetSize(object.fd);
290 ✗ g_pinned_size += object.size;
291 ✗ g_used_size += object.size;
292 } else {
293 ✗ if (g_cache_mgr->AbortTxn(transaction.txn) != 0) {
294 ✗ return CVMCACHE_STATUS_IOERR;
295 }
296 }
297 ✗ object.refcnt += 1;
298
299 ✗ g_opened_objects->Insert(transaction.hash, object);
300 ✗ g_transactions->Erase(txn_id);
301 ✗ return CVMCACHE_STATUS_OK;
302 }
303
304 ✗ int posix_abort_txn(uint64_t txn_id) {
305 Txn transaction;
306 ✗ if (!g_transactions->Lookup(txn_id, &transaction)) {
307 ✗ return CVMCACHE_STATUS_NOENTRY;
308 }
309 ✗ if (g_cache_mgr->AbortTxn(transaction.txn)) {
310 ✗ return CVMCACHE_STATUS_IOERR;
311 }
312 ✗ g_transactions->Erase(txn_id);
313 ✗ return CVMCACHE_STATUS_OK;
314 }
315
316 ✗ int posix_info(struct cvmcache_info *info) {
317 ✗ info->no_shrink = -1;
318 ✗ info->size_bytes = g_capacity;
319 ✗ info->used_bytes = g_used_size;
320 ✗ info->pinned_bytes = g_pinned_size;
321 ✗ return CVMCACHE_STATUS_OK;
322 }
323
324 ✗ int posix_breadcrumb_store(const char *fqrn,
325 const cvmcache_breadcrumb *breadcrumb) {
326 ✗ const manifest::Breadcrumb bc(Chash2Cpphash(&breadcrumb->catalog_hash),
327 ✗ breadcrumb->timestamp, breadcrumb->revision);
328 ✗ if (!g_cache_mgr->StoreBreadcrumb(fqrn, bc)) {
329 ✗ return CVMCACHE_STATUS_IOERR;
330 }
331 ✗ return CVMCACHE_STATUS_OK;
332 }
333
334 ✗ int posix_breadcrumb_load(const char *fqrn, cvmcache_breadcrumb *breadcrumb) {
335 ✗ const manifest::Breadcrumb bc = g_cache_mgr->LoadBreadcrumb(fqrn);
336 ✗ if (!bc.IsValid()) {
337 ✗ return CVMCACHE_STATUS_NOENTRY;
338 }
339 ✗ breadcrumb->catalog_hash = Cpphash2Chash(bc.catalog_hash);
340 ✗ breadcrumb->timestamp = bc.timestamp;
341 ✗ breadcrumb->revision = bc.revision;
342 ✗ return CVMCACHE_STATUS_OK;
343 }
344
345 ✗ void handle_sigint(int sig) {
346 ✗ cvmcache_terminate(g_ctx);
347 ✗ atomic_inc32(&g_terminated);
348 }
349
350 } // namespace
351
352 ✗ int main(int argc, char **argv) {
353 ✗ if (argc < 2) {
354 ✗ fprintf(stderr, "Missing argument: path to config file\n");
355 ✗ return 1;
356 }
357
358 ✗ cvmcache_init_global();
359
360 ✗ cvmcache_option_map *options = cvmcache_options_init();
361 ✗ if (cvmcache_options_parse(options, argv[1]) != 0) {
362 ✗ LogCvmfs(kLogCache, kLogStderr | kLogSyslogErr,
363 ✗ "cannot parse options file %s", argv[1]);
364 ✗ return 1;
365 }
366 ✗ char *debug_log = cvmcache_options_get(options,
367 "CVMFS_CACHE_PLUGIN_DEBUGLOG");
368 ✗ if (debug_log != NULL) {
369 ✗ SetLogDebugFile(debug_log);
370 ✗ cvmcache_options_free(debug_log);
371 } else {
372 ✗ SetLogDebugFile("/dev/null");
373 }
374 ✗ char *locator = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_LOCATOR");
375 ✗ if (locator == NULL) {
376 ✗ LogCvmfs(kLogCache, kLogStderr | kLogSyslogErr,
377 "CVMFS_CACHE_PLUGIN_LOCATOR missing");
378 ✗ cvmcache_options_fini(options);
379 ✗ return 1;
380 }
381 ✗ char *test_mode = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_TEST");
382 ✗ if (!test_mode) {
383 ✗ char *watchdog_crash_dump_path = cvmcache_options_get(
384 options, "CVMFS_CACHE_PLUGIN_CRASH_DUMP");
385 ✗ cvmcache_spawn_watchdog(watchdog_crash_dump_path);
386 ✗ if (watchdog_crash_dump_path)
387 ✗ cvmcache_options_free(watchdog_crash_dump_path);
388 }
389
390 ✗ Settings settings = GetSettings(options);
391 ✗ if (!settings.IsValid()) {
392 ✗ LogCvmfs(kLogCache, kLogStderr | kLogSyslogErr,
393 ✗ "Invalid config in file %s: %s", argv[1],
394 settings.error_reason.c_str());
395 ✗ return 1;
396 }
397
398 ✗ g_cache_mgr = PosixCacheManager::Create(settings.cache_path,
399 ✗ settings.is_alien);
400
401 cvmcache_hash empty_hash;
402 ✗ empty_hash.algorithm = 0;
403 ✗ memset(empty_hash.digest, 0, 20);
404 ✗ g_opened_objects = new SmallHashDynamic<cvmcache_hash, CacheObject>;
405 ✗ g_transactions = new SmallHashDynamic<uint64_t, Txn>;
406 ✗ g_listings = new SmallHashDynamic<uint64_t, Listing>;
407 ✗ g_opened_objects->Init(32, empty_hash, cvmcache_hash_hasher);
408 ✗ g_transactions->Init(32, (uint64_t(-1)), uint64_hasher);
409 ✗ g_listings->Init(32, (uint64_t(-1)), uint64_hasher);
410 ✗ g_pinned_size = 0;
411 ✗ g_used_size = 0;
412 ✗ g_capacity = CVMCACHE_SIZE_UNKNOWN;
413
414 struct cvmcache_callbacks callbacks;
415 ✗ memset(&callbacks, 0, sizeof(callbacks));
416 ✗ callbacks.cvmcache_chrefcnt = posix_chrefcnt;
417 ✗ callbacks.cvmcache_obj_info = posix_obj_info;
418 ✗ callbacks.cvmcache_pread = posix_pread;
419 ✗ callbacks.cvmcache_start_txn = posix_start_txn;
420 ✗ callbacks.cvmcache_write_txn = posix_write_txn;
421 ✗ callbacks.cvmcache_commit_txn = posix_commit_txn;
422 ✗ callbacks.cvmcache_abort_txn = posix_abort_txn;
423 ✗ callbacks.cvmcache_info = posix_info;
424 ✗ callbacks.cvmcache_breadcrumb_store = posix_breadcrumb_store;
425 ✗ callbacks.cvmcache_breadcrumb_load = posix_breadcrumb_load;
426 ✗ callbacks.capabilities = CVMCACHE_CAP_WRITE + CVMCACHE_CAP_REFCOUNT
427 + CVMCACHE_CAP_INFO + CVMCACHE_CAP_BREADCRUMB;
428
429 ✗ g_ctx = cvmcache_init(&callbacks);
430 ✗ const int retval = cvmcache_listen(g_ctx, locator);
431 ✗ if (!retval) {
432 ✗ LogCvmfs(kLogCache, kLogStderr | kLogSyslogErr, "failed to listen on %s",
433 locator);
434 ✗ return 1;
435 }
436
437 ✗ if (test_mode) {
438 // Daemonize, print out PID
439 pid_t pid;
440 int statloc;
441 ✗ if ((pid = fork()) == 0) {
442 ✗ if ((pid = fork()) == 0) {
443 ✗ const int null_read = open("/dev/null", O_RDONLY);
444 ✗ const int null_write = open("/dev/null", O_WRONLY);
445 ✗ assert((null_read >= 0) && (null_write >= 0));
446 ✗ int retval = dup2(null_read, 0);
447 ✗ assert(retval == 0);
448 ✗ retval = dup2(null_write, 1);
449 ✗ assert(retval == 1);
450 ✗ retval = dup2(null_write, 2);
451 ✗ assert(retval == 2);
452 ✗ close(null_read);
453 ✗ close(null_write);
454 } else {
455 ✗ assert(pid > 0);
456 ✗ printf("%d\n", pid);
457 ✗ fflush(stdout);
458 ✗ fsync(1);
459 ✗ _exit(0);
460 }
461 } else {
462 ✗ assert(pid > 0);
463 ✗ waitpid(pid, &statloc, 0);
464 ✗ _exit(0);
465 }
466 }
467
468 ✗ LogCvmfs(kLogCache, kLogStdout, "Listening for cvmfs clients on %s", locator);
469
470 ✗ cvmcache_process_requests(g_ctx, 0);
471
472 ✗ if (!cvmcache_is_supervised()) {
473 ✗ LogCvmfs(kLogCache, kLogStdout,
474 "Running unsupervised. Quit by SIGINT (CTRL+C)");
475 ✗ atomic_init32(&g_terminated);
476 ✗ signal(SIGINT, handle_sigint);
477 ✗ while (atomic_read32(&g_terminated) == 0)
478 ✗ sleep(1);
479 }
480
481 ✗ cvmcache_wait_for(g_ctx);
482
483 ✗ delete g_opened_objects;
484 ✗ g_opened_objects = NULL;
485 ✗ delete g_transactions;
486 ✗ g_transactions = NULL;
487 ✗ delete g_listings;
488 ✗ g_listings = NULL;
489 ✗ delete g_cache_mgr;
490 ✗ g_cache_mgr = NULL;
491
492 ✗ cvmcache_options_free(locator);
493 ✗ cvmcache_options_fini(options);
494
495 ✗ cvmcache_terminate_watchdog();
496 ✗ cvmcache_cleanup_global();
497 ✗ return 0;
498 }
499