GCC Code Coverage Report


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