GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_plugin/cvmfs_cache_posix.cc
Date: 2026-04-19 02:41:37
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