GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_plugin/cvmfs_cache_ram.cc
Date: 2026-05-19 11:45:12
Exec Total Coverage
Lines: 0 401 0.0%
Branches: 0 174 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * A cache plugin that stores all data in a fixed-size memory chunk.
5 */
6
7 #include <alloca.h>
8 #include <fcntl.h>
9 #include <inttypes.h>
10 #include <signal.h>
11 #include <stdint.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15
16 #include <algorithm>
17 #include <cassert>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <string>
22 #include <vector>
23
24 #include "cache_plugin/libcvmfs_cache.h"
25 #include "lru.h"
26 #include "malloc_heap.h"
27 #include "smallhash.h"
28 #include "util/concurrency.h"
29 #include "util/logging.h"
30 #include "util/murmur.hxx"
31 #include "util/platform.h"
32 #include "util/smalloc.h"
33 #include "util/string.h"
34
35 using namespace std; // NOLINT
36
37 /**
38 * Header of the data pieces in the cache. After the object header, the
39 * zero-terminated description and the object data follows.
40 */
41 struct ObjectHeader {
42 ObjectHeader() {
43 txn_id = uint64_t(-1);
44 size_data = 0;
45 size_desc = 0;
46 refcnt = 0;
47 type = CVMCACHE_OBJECT_REGULAR;
48 memset(&id, 0, sizeof(id));
49 }
50
51 char *GetDescription() {
52 if (size_desc == 0)
53 return NULL;
54 return reinterpret_cast<char *>(this) + sizeof(ObjectHeader);
55 }
56
57 void SetDescription(char *description) {
58 if (description == NULL)
59 return;
60 memcpy(reinterpret_cast<char *>(this) + sizeof(ObjectHeader), description,
61 strlen(description) + 1);
62 }
63
64 unsigned char *GetData() {
65 return reinterpret_cast<unsigned char *>(this) + sizeof(ObjectHeader)
66 + size_desc;
67 }
68
69 /**
70 * Set during a running transaction so that we know where to look for pointers
71 * when the memory block gets compacted. Once committed, this is
72 * uint64_t(-1).
73 */
74 uint64_t txn_id;
75 /**
76 * Can be zero.
77 */
78 uint32_t size_data;
79 /**
80 * String length + 1 (null terminated) or null if the description is NULL.
81 */
82 uint32_t size_desc;
83 /**
84 * During a transaction, neg_nbytes_written is used to track the number of
85 * already written bytes. On commit, refcnt is set to 1.
86 */
87 union {
88 int32_t refcnt;
89 int32_t neg_nbytes_written;
90 };
91 cvmcache_object_type type;
92 struct cvmcache_hash id;
93 };
94
95
96 /**
97 * Listings are generated and cached during the entire life time of a listing
98 * id. Not very memory efficient but we don't optimize for listings.
99 */
100 struct Listing {
101 Listing() : pos(0) { }
102 uint64_t pos;
103 vector<struct cvmcache_object_info> elems;
104 };
105
106
107 /**
108 * Allows us to use a cvmcache_hash in (hash) maps.
109 */
110 struct ComparableHash {
111 ComparableHash() { memset(&hash, 0, sizeof(hash)); }
112 explicit ComparableHash(const struct cvmcache_hash &h) : hash(h) { }
113 bool operator==(const ComparableHash &other) const {
114 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
115 const_cast<cvmcache_hash *>(&(other.hash)))
116 == 0;
117 }
118 bool operator!=(const ComparableHash &other) const {
119 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
120 const_cast<cvmcache_hash *>(&(other.hash)))
121 != 0;
122 }
123 bool operator<(const ComparableHash &other) const {
124 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
125 const_cast<cvmcache_hash *>(&(other.hash)))
126 < 0;
127 }
128 bool operator>(const ComparableHash &other) const {
129 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
130 const_cast<cvmcache_hash *>(&(other.hash)))
131 > 0;
132 }
133
134 struct cvmcache_hash hash;
135 };
136
137
138 namespace {
139
140 static inline uint32_t hasher_uint64(const uint64_t &key) {
141 return MurmurHash2(&key, sizeof(key), 0x07387a4f);
142 }
143
144 static inline uint32_t hasher_any(const ComparableHash &key) {
145 return static_cast<uint32_t>(
146 *(reinterpret_cast<const uint32_t *>(&key.hash)));
147 }
148
149 } // anonymous namespace
150
151
152 /**
153 * Used in the PluginRamCache when detaching nested catalogs.
154 */
155 struct cvmcache_context *ctx;
156
157
158 /**
159 * Implements all the cache plugin callbacks. Singleton.
160 */
161 class PluginRamCache : public Callbackable<MallocHeap::BlockPtr> {
162 public:
163 static PluginRamCache *Create(const string &mem_size_str) {
164 assert(instance_ == NULL);
165
166 uint64_t mem_size_bytes;
167 if (HasSuffix(mem_size_str, "%", false)) {
168 mem_size_bytes = platform_memsize() * String2Uint64(mem_size_str) / 100;
169 } else {
170 mem_size_bytes = String2Uint64(mem_size_str) * 1024 * 1024;
171 }
172 instance_ = new PluginRamCache(mem_size_bytes);
173 return instance_;
174 }
175
176 static PluginRamCache *GetInstance() {
177 assert(instance_ != NULL);
178 return instance_;
179 }
180
181 ~PluginRamCache() {
182 delete storage_;
183 delete objects_all_;
184 delete objects_volatile_;
185 instance_ = NULL;
186 }
187
188 void DropBreadcrumbs() { breadcrumbs_.clear(); }
189
190 static int ram_chrefcnt(struct cvmcache_hash *id, int32_t change_by) {
191 const ComparableHash h(*id);
192 ObjectHeader *object;
193 if (!Me()->objects_all_->Lookup(h, &object))
194 return CVMCACHE_STATUS_NOENTRY;
195
196 if (object->type == CVMCACHE_OBJECT_VOLATILE)
197 Me()->objects_volatile_->Update(h);
198
199 if (change_by == 0)
200 return CVMCACHE_STATUS_OK;
201 if ((object->refcnt + change_by) < 0)
202 return CVMCACHE_STATUS_BADCOUNT;
203
204 if (object->refcnt == 0) {
205 Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(object);
206 Me()->CheckHighPinWatermark();
207 }
208 object->refcnt += change_by;
209 if (object->refcnt == 0) {
210 Me()->cache_info_.pinned_bytes -= Me()->storage_->GetSize(object);
211 Me()->in_danger_zone_ = Me()->IsInDangerZone();
212 }
213 return CVMCACHE_STATUS_OK;
214 }
215
216
217 static int ram_obj_info(struct cvmcache_hash *id,
218 struct cvmcache_object_info *info) {
219 const ComparableHash h(*id);
220 ObjectHeader *object;
221 if (!Me()->objects_all_->Lookup(h, &object, false))
222 return CVMCACHE_STATUS_NOENTRY;
223
224 info->size = object->size_data;
225 info->type = object->type;
226 info->pinned = object->refcnt > 0;
227 info->description = (object->GetDescription() == NULL)
228 ? NULL
229 : strdup(object->GetDescription());
230 return CVMCACHE_STATUS_OK;
231 }
232
233
234 static int ram_pread(struct cvmcache_hash *id,
235 uint64_t offset,
236 uint32_t *size,
237 unsigned char *buffer) {
238 const ComparableHash h(*id);
239 ObjectHeader *object;
240 const bool retval = Me()->objects_all_->Lookup(h, &object, false);
241 assert(retval);
242 if (offset > object->size_data)
243 return CVMCACHE_STATUS_OUTOFBOUNDS;
244 const unsigned nbytes = std::min(
245 *size, static_cast<uint32_t>(object->size_data - offset));
246 memcpy(buffer, object->GetData() + offset, nbytes);
247 *size = nbytes;
248 return CVMCACHE_STATUS_OK;
249 }
250
251
252 static int ram_start_txn(struct cvmcache_hash *id,
253 uint64_t txn_id,
254 struct cvmcache_object_info *info) {
255 ObjectHeader object_header;
256 object_header.txn_id = txn_id;
257 if (info->size != CVMCACHE_SIZE_UNKNOWN)
258 object_header.size_data = info->size;
259 else
260 object_header.size_data = 4096;
261 if (info->description != NULL)
262 object_header.size_desc = strlen(info->description) + 1;
263 object_header.refcnt = 1;
264 object_header.type = info->type;
265 object_header.id = *id;
266
267 const uint32_t total_size = sizeof(object_header) + object_header.size_desc
268 + object_header.size_data;
269 Me()->TryFreeSpace(total_size);
270 ObjectHeader *allocd_object = reinterpret_cast<ObjectHeader *>(
271 Me()->storage_->Allocate(total_size, &object_header,
272 sizeof(object_header)));
273 if (allocd_object == NULL)
274 return CVMCACHE_STATUS_NOSPACE;
275
276 allocd_object->SetDescription(info->description);
277 Me()->transactions_.Insert(txn_id, allocd_object);
278 return CVMCACHE_STATUS_OK;
279 }
280
281
282 static int ram_write_txn(uint64_t txn_id,
283 unsigned char *buffer,
284 uint32_t size) {
285 ObjectHeader *txn_object;
286 int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
287 assert(retval);
288 assert(size > 0);
289
290 if (txn_object->neg_nbytes_written > 0)
291 txn_object->neg_nbytes_written = 0;
292 if ((size - txn_object->neg_nbytes_written) > txn_object->size_data) {
293 const uint32_t current_size = Me()->storage_->GetSize(txn_object);
294 const uint32_t header_size = current_size - txn_object->size_data;
295 const uint32_t new_size = std::max(
296 header_size + size - txn_object->neg_nbytes_written,
297 uint32_t(current_size * kObjectExpandFactor));
298 const bool did_compact = Me()->TryFreeSpace(new_size);
299 if (did_compact) {
300 retval = Me()->transactions_.Lookup(txn_id, &txn_object);
301 assert(retval);
302 }
303 txn_object = reinterpret_cast<ObjectHeader *>(
304 Me()->storage_->Expand(txn_object, new_size));
305 if (txn_object == NULL)
306 return CVMCACHE_STATUS_NOSPACE;
307 txn_object->size_data = new_size - header_size;
308 Me()->transactions_.Insert(txn_id, txn_object);
309 }
310
311 memcpy(txn_object->GetData() - txn_object->neg_nbytes_written, buffer,
312 size);
313 txn_object->neg_nbytes_written -= size;
314 return CVMCACHE_STATUS_OK;
315 }
316
317
318 static int ram_commit_txn(uint64_t txn_id) {
319 Me()->TryFreeSpace(0);
320 if (Me()->objects_all_->IsFull())
321 return CVMCACHE_STATUS_NOSPACE;
322
323 ObjectHeader *txn_object;
324 const int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
325 assert(retval);
326
327 Me()->transactions_.Erase(txn_id);
328 const ComparableHash h(txn_object->id);
329 ObjectHeader *existing_object;
330 if (Me()->objects_all_->Lookup(h, &existing_object)) {
331 // Concurrent addition of same objects, drop the one at hand and
332 // increase ref count of existing copy
333 Me()->storage_->MarkFree(txn_object);
334 if (existing_object->refcnt == 0)
335 Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(
336 existing_object);
337 existing_object->refcnt++;
338 } else {
339 txn_object->txn_id = uint64_t(-1);
340 if (txn_object->neg_nbytes_written > 0)
341 txn_object->neg_nbytes_written = 0;
342 txn_object->size_data = -(txn_object->neg_nbytes_written);
343 txn_object->refcnt = 1;
344 Me()->cache_info_.used_bytes += Me()->storage_->GetSize(txn_object);
345 Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(txn_object);
346 Me()->objects_all_->Insert(h, txn_object);
347 if (txn_object->type == CVMCACHE_OBJECT_VOLATILE) {
348 assert(!Me()->objects_volatile_->IsFull());
349 Me()->objects_volatile_->Insert(h, txn_object);
350 }
351 }
352 Me()->CheckHighPinWatermark();
353 return CVMCACHE_STATUS_OK;
354 }
355
356
357 static int ram_abort_txn(uint64_t txn_id) {
358 ObjectHeader *txn_object = NULL;
359 const int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
360 assert(retval);
361 Me()->transactions_.Erase(txn_id);
362 Me()->storage_->MarkFree(txn_object);
363 return CVMCACHE_STATUS_OK;
364 }
365
366
367 static int ram_info(struct cvmcache_info *info) {
368 *info = Me()->cache_info_;
369 return CVMCACHE_STATUS_OK;
370 }
371
372
373 static int ram_shrink(uint64_t shrink_to, uint64_t *used) {
374 *used = Me()->cache_info_.used_bytes;
375 if (*used <= shrink_to)
376 return CVMCACHE_STATUS_OK;
377
378 Me()->DoShrink(shrink_to);
379 *used = Me()->cache_info_.used_bytes;
380 return (*used <= shrink_to) ? CVMCACHE_STATUS_OK : CVMCACHE_STATUS_PARTIAL;
381 }
382
383
384 static int ram_listing_begin(uint64_t lst_id,
385 enum cvmcache_object_type type) {
386 Listing *lst = new Listing();
387 Me()->objects_all_->FilterBegin();
388 while (Me()->objects_all_->FilterNext()) {
389 ComparableHash h;
390 ObjectHeader *object;
391 Me()->objects_all_->FilterGet(&h, &object);
392 if (object->type != type)
393 continue;
394
395 struct cvmcache_object_info item;
396 item.id = object->id;
397 item.size = object->size_data;
398 item.type = type;
399 item.pinned = object->refcnt != 0;
400 item.description = (object->size_desc > 0)
401 ? strdup(object->GetDescription())
402 : NULL;
403 lst->elems.push_back(item);
404 }
405 Me()->objects_all_->FilterEnd();
406
407 Me()->listings_.Insert(lst_id, lst);
408 return CVMCACHE_STATUS_OK;
409 }
410
411
412 static int ram_listing_next(int64_t listing_id,
413 struct cvmcache_object_info *item) {
414 Listing *lst;
415 const bool retval = Me()->listings_.Lookup(listing_id, &lst);
416 assert(retval);
417 if (lst->pos >= lst->elems.size())
418 return CVMCACHE_STATUS_OUTOFBOUNDS;
419 *item = lst->elems[lst->pos];
420 lst->pos++;
421 return CVMCACHE_STATUS_OK;
422 }
423
424
425 static int ram_listing_end(int64_t listing_id) {
426 Listing *lst;
427 const bool retval = Me()->listings_.Lookup(listing_id, &lst);
428 assert(retval);
429
430 // Don't free description strings, done by the library
431 delete lst;
432 Me()->listings_.Erase(listing_id);
433 return CVMCACHE_STATUS_OK;
434 }
435
436
437 static int ram_breadcrumb_store(const char *fqrn,
438 const cvmcache_breadcrumb *breadcrumb) {
439 Me()->breadcrumbs_[fqrn] = *breadcrumb;
440 return CVMCACHE_STATUS_OK;
441 }
442
443
444 static int ram_breadcrumb_load(const char *fqrn,
445 cvmcache_breadcrumb *breadcrumb) {
446 const map<std::string, cvmcache_breadcrumb>::const_iterator
447 itr = Me()->breadcrumbs_.find(fqrn);
448 if (itr == Me()->breadcrumbs_.end())
449 return CVMCACHE_STATUS_NOENTRY;
450 *breadcrumb = itr->second;
451 return CVMCACHE_STATUS_OK;
452 }
453
454 private:
455 static const uint64_t kMinSize; // 100 * 1024 * 1024;
456 static const double kShrinkFactor; // = 0.75;
457 static const double kObjectExpandFactor; // = 1.5;
458 static const double kSlotFraction; // = 0.04;
459 static const double kDangerZoneThreshold; // = 0.7
460
461 static PluginRamCache *instance_;
462 static PluginRamCache *Me() { return instance_; }
463 explicit PluginRamCache(uint64_t mem_size) {
464 in_danger_zone_ = false;
465
466 const uint64_t heap_size = RoundUp8(
467 std::max(kMinSize, uint64_t(mem_size * (1.0 - kSlotFraction))));
468 memset(&cache_info_, 0, sizeof(cache_info_));
469 cache_info_.size_bytes = heap_size;
470 storage_ = new MallocHeap(
471 heap_size, this->MakeCallback(&PluginRamCache::OnBlockMove, this));
472
473 struct cvmcache_hash hash_empty;
474 memset(&hash_empty, 0, sizeof(hash_empty));
475
476 transactions_.Init(64, uint64_t(-1), hasher_uint64);
477 listings_.Init(8, uint64_t(-1), hasher_uint64);
478
479 const double slot_size = lru::LruCache<ComparableHash,
480 ObjectHeader *>::GetEntrySize();
481 const uint64_t num_slots = uint64_t((heap_size * kSlotFraction)
482 / (2.0 * slot_size));
483 const unsigned mask_64 = ~((1 << 6) - 1);
484
485 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
486 "Allocating %" PRIu64 "MB of memory for up to %" PRIu64 " objects",
487 heap_size / (1024 * 1024), num_slots & mask_64);
488
489 // Number of cache entries must be a multiple of 64
490 objects_all_ = new lru::LruCache<ComparableHash, ObjectHeader *>(
491 num_slots & mask_64,
492 ComparableHash(hash_empty),
493 hasher_any,
494 perf::StatisticsTemplate("objects_all", &statistics_));
495 objects_volatile_ = new lru::LruCache<ComparableHash, ObjectHeader *>(
496 num_slots & mask_64,
497 ComparableHash(hash_empty),
498 hasher_any,
499 perf::StatisticsTemplate("objects_volatile", &statistics_));
500 }
501
502 /**
503 * Returns true if memory compaction took place and pointers might have been
504 * invalidated.
505 */
506 bool TryFreeSpace(uint64_t bytes_required) {
507 if (!objects_all_->IsFull() && storage_->HasSpaceFor(bytes_required))
508 return false;
509
510 // Free space occupied due to piecewise catalog storage
511 if (!objects_all_->IsFull()) {
512 LogCvmfs(kLogCache, kLogDebug, "compacting ram cache");
513 storage_->Compact();
514 if (storage_->HasSpaceFor(bytes_required))
515 return true;
516 }
517
518 const uint64_t shrink_to = std::min(
519 storage_->capacity() - (bytes_required + 8),
520 uint64_t(storage_->capacity() * kShrinkFactor));
521 DoShrink(shrink_to);
522 return true;
523 }
524
525 void OnBlockMove(const MallocHeap::BlockPtr &ptr) {
526 assert(ptr.pointer);
527 ObjectHeader *object = reinterpret_cast<ObjectHeader *>(ptr.pointer);
528 const ComparableHash h(object->id);
529 if (object->txn_id == uint64_t(-1)) {
530 bool retval = objects_all_->UpdateValue(h, object);
531 assert(retval);
532 if (object->type == CVMCACHE_OBJECT_VOLATILE) {
533 retval = objects_volatile_->UpdateValue(h, object);
534 assert(retval);
535 }
536 } else {
537 const uint64_t old_size = transactions_.size();
538 transactions_.Insert(object->txn_id, object);
539 assert(old_size == transactions_.size());
540 }
541 }
542
543
544 void DoShrink(uint64_t shrink_to) {
545 ComparableHash h;
546 ObjectHeader *object;
547
548 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
549 "clean up cache until at most %lu KB is used", shrink_to / 1024);
550
551 objects_volatile_->FilterBegin();
552 while (objects_volatile_->FilterNext()) {
553 objects_volatile_->FilterGet(&h, &object);
554 if (object->refcnt != 0)
555 continue;
556 cache_info_.used_bytes -= storage_->GetSize(object);
557 storage_->MarkFree(object);
558 objects_volatile_->FilterDelete();
559 objects_all_->Forget(h);
560 if (storage_->compacted_bytes() <= shrink_to)
561 break;
562 }
563 objects_volatile_->FilterEnd();
564
565 objects_all_->FilterBegin();
566 while ((storage_->compacted_bytes() > shrink_to)
567 && objects_all_->FilterNext()) {
568 objects_all_->FilterGet(&h, &object);
569 if (object->refcnt != 0)
570 continue;
571 assert(object->type != CVMCACHE_OBJECT_VOLATILE);
572 cache_info_.used_bytes -= storage_->GetSize(object);
573 storage_->MarkFree(object);
574 objects_all_->FilterDelete();
575 }
576 objects_all_->FilterEnd();
577
578 storage_->Compact();
579 cache_info_.no_shrink++;
580 }
581
582 void CheckHighPinWatermark() {
583 if (!Me()->in_danger_zone_ && Me()->IsInDangerZone()) {
584 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslog,
585 "high watermark of pinned files");
586 Me()->in_danger_zone_ = true;
587 cvmcache_ask_detach(ctx);
588 }
589 }
590
591 bool IsInDangerZone() {
592 return (static_cast<double>(cache_info_.pinned_bytes)
593 / static_cast<double>(cache_info_.size_bytes))
594 > kDangerZoneThreshold;
595 }
596
597
598 struct cvmcache_info cache_info_;
599 perf::Statistics statistics_;
600 SmallHashDynamic<uint64_t, ObjectHeader *> transactions_;
601 SmallHashDynamic<uint64_t, Listing *> listings_;
602 lru::LruCache<ComparableHash, ObjectHeader *> *objects_all_;
603 lru::LruCache<ComparableHash, ObjectHeader *> *objects_volatile_;
604 map<std::string, cvmcache_breadcrumb> breadcrumbs_;
605 MallocHeap *storage_;
606 bool in_danger_zone_;
607 }; // class PluginRamCache
608
609 PluginRamCache *PluginRamCache::instance_ = NULL;
610 const uint64_t PluginRamCache::kMinSize = 100 * 1024 * 1024;
611 const double PluginRamCache::kShrinkFactor = 0.75;
612 const double PluginRamCache::kObjectExpandFactor = 1.5;
613 const double PluginRamCache::kSlotFraction = 0.04;
614 const double PluginRamCache::kDangerZoneThreshold = 0.7;
615
616
617 static void Usage(const char *progname) {
618 LogCvmfs(kLogCache, kLogStdout, "%s <config file>", progname);
619 }
620
621
622 /**
623 * For testing and debugging purposes, the cache manager drops its
624 * breadcrumb cache upon SIGUSR2 retrieval
625 */
626 void DropBreadcrumbs(int sig, siginfo_t *siginfo, void *context) {
627 LogCvmfs(kLogCache, kLogSyslog | kLogDebug, "dropping breadcrumbs");
628 PluginRamCache::GetInstance()->DropBreadcrumbs();
629 }
630
631
632 int main(int argc, char **argv) {
633 if (argc < 2) {
634 Usage(argv[0]);
635 return 1;
636 }
637
638 SetLogDebugFile("/dev/null");
639
640 cvmcache_init_global();
641
642 cvmcache_option_map *options = cvmcache_options_init();
643 if (cvmcache_options_parse(options, argv[1]) != 0) {
644 LogCvmfs(kLogCache, kLogStderr, "cannot parse options file %s", argv[1]);
645 return 1;
646 }
647 char *debug_log = cvmcache_options_get(options,
648 "CVMFS_CACHE_PLUGIN_DEBUGLOG");
649 if (debug_log != NULL) {
650 SetLogDebugFile(debug_log);
651 cvmcache_options_free(debug_log);
652 }
653 char *locator = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_LOCATOR");
654 if (locator == NULL) {
655 LogCvmfs(kLogCache, kLogStderr, "CVMFS_CACHE_PLUGIN_LOCATOR missing");
656 cvmcache_options_fini(options);
657 return 1;
658 }
659 char *mem_size = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_SIZE");
660 if (mem_size == NULL) {
661 LogCvmfs(kLogCache, kLogStderr, "CVMFS_CACHE_PLUGIN_SIZE missing");
662 cvmcache_options_fini(options);
663 return 1;
664 }
665 char *test_mode = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_TEST");
666
667 if (!test_mode)
668 cvmcache_spawn_watchdog(NULL);
669
670 PluginRamCache *plugin = PluginRamCache::Create(mem_size);
671 struct sigaction sa;
672 memset(&sa, 0, sizeof(sa));
673 sa.sa_sigaction = DropBreadcrumbs;
674 sa.sa_flags = SA_SIGINFO;
675 sigfillset(&sa.sa_mask);
676 int retval = sigaction(SIGUSR2, &sa, NULL);
677 assert(retval == 0);
678
679 struct cvmcache_callbacks callbacks;
680 memset(&callbacks, 0, sizeof(callbacks));
681 callbacks.cvmcache_chrefcnt = plugin->ram_chrefcnt;
682 callbacks.cvmcache_obj_info = plugin->ram_obj_info;
683 callbacks.cvmcache_pread = plugin->ram_pread;
684 callbacks.cvmcache_start_txn = plugin->ram_start_txn;
685 callbacks.cvmcache_write_txn = plugin->ram_write_txn;
686 callbacks.cvmcache_commit_txn = plugin->ram_commit_txn;
687 callbacks.cvmcache_abort_txn = plugin->ram_abort_txn;
688 callbacks.cvmcache_info = plugin->ram_info;
689 callbacks.cvmcache_shrink = plugin->ram_shrink;
690 callbacks.cvmcache_listing_begin = plugin->ram_listing_begin;
691 callbacks.cvmcache_listing_next = plugin->ram_listing_next;
692 callbacks.cvmcache_listing_end = plugin->ram_listing_end;
693 callbacks.cvmcache_breadcrumb_store = plugin->ram_breadcrumb_store;
694 callbacks.cvmcache_breadcrumb_load = plugin->ram_breadcrumb_load;
695 callbacks.capabilities = CVMCACHE_CAP_ALL_V2;
696
697 ctx = cvmcache_init(&callbacks);
698 retval = cvmcache_listen(ctx, locator);
699 if (!retval) {
700 LogCvmfs(kLogCache, kLogStderr, "failed to listen on %s", locator);
701 return 1;
702 }
703
704 if (test_mode) {
705 // Daemonize, print out PID
706 pid_t pid;
707 int statloc;
708 if ((pid = fork()) == 0) {
709 if ((pid = fork()) == 0) {
710 const int null_read = open("/dev/null", O_RDONLY);
711 const int null_write = open("/dev/null", O_WRONLY);
712 assert((null_read >= 0) && (null_write >= 0));
713 int retval = dup2(null_read, 0);
714 assert(retval == 0);
715 retval = dup2(null_write, 1);
716 assert(retval == 1);
717 retval = dup2(null_write, 2);
718 assert(retval == 2);
719 close(null_read);
720 close(null_write);
721 } else {
722 assert(pid > 0);
723 printf("%d\n", pid);
724 fflush(stdout);
725 fsync(1);
726 _exit(0);
727 }
728 } else {
729 assert(pid > 0);
730 waitpid(pid, &statloc, 0);
731 _exit(0);
732 }
733 }
734
735 LogCvmfs(kLogCache, kLogStdout,
736 "Listening for cvmfs clients on %s\n"
737 "NOTE: this process needs to run as user cvmfs\n",
738 locator);
739
740 cvmcache_process_requests(ctx, 0);
741 if (test_mode)
742 while (true)
743 sleep(1);
744 if (!cvmcache_is_supervised()) {
745 LogCvmfs(kLogCache, kLogStdout, "Press <Ctrl+D> to quit");
746 LogCvmfs(kLogCache, kLogStdout,
747 "Press <R Enter> to ask clients to release nested catalogs");
748 while (true) {
749 char buf;
750 retval = read(fileno(stdin), &buf, 1);
751 if (retval != 1)
752 break;
753 if (buf == 'R') {
754 LogCvmfs(kLogCache, kLogStdout,
755 " ... asking clients to release nested catalogs");
756 cvmcache_ask_detach(ctx);
757 }
758 }
759 cvmcache_terminate(ctx);
760 } else {
761 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
762 "CernVM-FS RAM cache plugin started in supervised mode");
763 }
764
765 cvmcache_wait_for(ctx);
766 LogCvmfs(kLogCache, kLogDebug | kLogStdout, " ... good bye");
767 cvmcache_options_free(mem_size);
768 cvmcache_options_free(locator);
769 cvmcache_options_fini(options);
770 cvmcache_terminate_watchdog();
771 cvmcache_cleanup_global();
772 return 0;
773 }
774