GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 201 273 73.6%
Branches: 75 180 41.7%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #define __STDC_FORMAT_MACROS
6
7
8 #include "glue_buffer.h"
9
10 #include <errno.h>
11 #include <poll.h>
12 #include <unistd.h>
13
14 #include <cassert>
15 #include <cstdlib>
16 #include <cstring>
17 #include <string>
18
19 #include "util/exception.h"
20 #include "util/logging.h"
21 #include "util/mutex.h"
22 #include "util/posix.h"
23 #include "util/smalloc.h"
24
25 using namespace std; // NOLINT
26
27 namespace glue {
28
29 PathStore &PathStore::operator=(const PathStore &other) {
30 if (&other == this)
31 return *this;
32
33 delete string_heap_;
34 CopyFrom(other);
35 return *this;
36 }
37
38
39 PathStore::PathStore(const PathStore &other) { CopyFrom(other); }
40
41
42 void PathStore::CopyFrom(const PathStore &other) {
43 map_ = other.map_;
44
45 string_heap_ = new StringHeap(other.string_heap_->used());
46 const shash::Md5 empty_path = map_.empty_key();
47 for (unsigned i = 0; i < map_.capacity(); ++i) {
48 if (map_.keys()[i] != empty_path) {
49 (map_.values() + i)->name = string_heap_->AddString(
50 map_.values()[i].name.length(), map_.values()[i].name.data());
51 }
52 }
53 }
54
55
56 //------------------------------------------------------------------------------
57
58
59 1031 void InodeTracker::InitLock() {
60 1031 lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
61 1031 const int retval = pthread_mutex_init(lock_, NULL);
62
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1031 times.
1031 assert(retval == 0);
63 1031 }
64
65
66 void InodeTracker::CopyFrom(const InodeTracker &other) {
67 assert(other.version_ == kVersion);
68 version_ = kVersion;
69 path_map_ = other.path_map_;
70 inode_ex_map_ = other.inode_ex_map_;
71 inode_references_ = other.inode_references_;
72 statistics_ = other.statistics_;
73 }
74
75
76
2/4
✓ Branch 2 taken 1031 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1031 times.
✗ Branch 6 not taken.
1031 InodeTracker::InodeTracker() {
77 1031 version_ = kVersion;
78 1031 InitLock();
79 1031 }
80
81
82 InodeTracker::InodeTracker(const InodeTracker &other) {
83 CopyFrom(other);
84 InitLock();
85 }
86
87
88 InodeTracker &InodeTracker::operator=(const InodeTracker &other) {
89 if (&other == this)
90 return *this;
91
92 CopyFrom(other);
93 return *this;
94 }
95
96
97 1029 InodeTracker::~InodeTracker() {
98 1029 pthread_mutex_destroy(lock_);
99 1029 free(lock_);
100 1029 }
101
102
103 //------------------------------------------------------------------------------
104
105 685 DentryTracker::DentryTracker() : version_(kVersion), is_active_(true) {
106 685 pipe_terminate_[0] = pipe_terminate_[1] = -1;
107 685 cleaning_interval_ms_ = -1;
108 685 InitLock();
109 685 }
110
111
112 787 DentryTracker::~DentryTracker() {
113
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 743 times.
787 if (pipe_terminate_[1] >= 0) {
114 44 char t = 'T';
115 44 WritePipe(pipe_terminate_[1], &t, 1);
116 44 pthread_join(thread_cleaner_, NULL);
117 44 ClosePipe(pipe_terminate_);
118 }
119 787 pthread_mutex_destroy(lock_);
120 787 free(lock_);
121 787 }
122
123
124 144 DentryTracker::DentryTracker(const DentryTracker &other) {
125
1/2
✓ Branch 1 taken 144 times.
✗ Branch 2 not taken.
144 CopyFrom(other);
126 144 pipe_terminate_[0] = pipe_terminate_[1] = -1;
127 144 cleaning_interval_ms_ = -1;
128 144 InitLock();
129 144 }
130
131
132 DentryTracker &DentryTracker::operator=(const DentryTracker &other) {
133 if (&other == this)
134 return *this;
135
136 Lock();
137 CopyFrom(other);
138 Unlock();
139 return *this;
140 }
141
142
143 144 void DentryTracker::CopyFrom(const DentryTracker &other) {
144
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 144 times.
144 assert(other.version_ == kVersion);
145
146 144 version_ = kVersion;
147 144 statistics_ = other.statistics_;
148 144 is_active_ = other.is_active_;
149 144 entries_ = other.entries_;
150 144 }
151
152
153 144 DentryTracker *DentryTracker::Move() {
154 144 Lock();
155
1/2
✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
144 DentryTracker *new_tracker = new DentryTracker(*this);
156 144 statistics_.num_remove += entries_.size();
157 144 entries_.Clear();
158 144 Unlock();
159 144 return new_tracker;
160 }
161
162
163 44 void DentryTracker::SpawnCleaner(unsigned interval_s) {
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(pipe_terminate_[0] == -1);
165 44 cleaning_interval_ms_ = interval_s * 1000;
166
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if (cleaning_interval_ms_ == 0)
167 cleaning_interval_ms_ = -1;
168 44 MakePipe(pipe_terminate_);
169 44 const int retval = pthread_create(&thread_cleaner_, NULL, MainCleaner, this);
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(retval == 0);
171 44 }
172
173
174 44 void *DentryTracker::MainCleaner(void *data) {
175 44 DentryTracker *tracker = reinterpret_cast<DentryTracker *>(data);
176
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug, "starting negative entry cache cleaner");
177
178 struct pollfd watch_term;
179 44 watch_term.fd = tracker->pipe_terminate_[0];
180 44 watch_term.events = POLLIN | POLLPRI;
181 44 int timeout_ms = tracker->cleaning_interval_ms_;
182 ;
183 44 uint64_t deadline = platform_monotonic_time() + timeout_ms / 1000;
184 while (true) {
185 176 watch_term.revents = 0;
186
1/2
✓ Branch 1 taken 176 times.
✗ Branch 2 not taken.
176 const int retval = poll(&watch_term, 1, timeout_ms);
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 176 times.
176 if (retval < 0) {
188 if (errno == EINTR) {
189 if (timeout_ms >= 0) {
190 const uint64_t now = platform_monotonic_time();
191 timeout_ms = (now > deadline) ? 0 : (deadline - now) * 1000;
192 }
193 132 continue;
194 }
195 abort();
196 }
197 176 timeout_ms = tracker->cleaning_interval_ms_;
198 176 deadline = platform_monotonic_time() + timeout_ms / 1000;
199
200
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
176 if (retval == 0) {
201
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 LogCvmfs(kLogCvmfs, kLogDebug, "negative entry cleaner: pruning");
202
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 tracker->Prune();
203 132 continue;
204 }
205
206
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(watch_term.revents != 0);
207
208 44 char c = 0;
209
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 ReadPipe(tracker->pipe_terminate_[0], &c, 1);
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(c == 'T');
211 44 break;
212 132 }
213
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug, "stopping negative entry cache cleaner");
214 44 return NULL;
215 }
216
217
218 829 void DentryTracker::InitLock() {
219 829 lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
220 829 const int retval = pthread_mutex_init(lock_, NULL);
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 829 times.
829 assert(retval == 0);
222 829 }
223
224
225 278 void DentryTracker::Prune() {
226 278 Lock();
227 278 DoPrune(platform_monotonic_time());
228 278 Unlock();
229 278 }
230
231
232 406 DentryTracker::Cursor DentryTracker::BeginEnumerate() {
233 406 Entry *head = NULL;
234 406 Lock();
235
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 entries_.Peek(&head);
236 406 return Cursor(head);
237 }
238
239
240 44022 bool DentryTracker::NextEntry(Cursor *cursor, uint64_t *inode_parent,
241 NameString *name) {
242
2/2
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 43770 times.
44022 if (cursor->head == NULL)
243 252 return false;
244
2/2
✓ Branch 1 taken 120 times.
✓ Branch 2 taken 43650 times.
43770 if (cursor->pos >= entries_.size())
245 120 return false;
246 43650 Entry *e = cursor->head + cursor->pos;
247 43650 *inode_parent = e->inode_parent;
248 43650 *name = e->name;
249 43650 cursor->pos++;
250 43650 return true;
251 }
252
253
254 406 void DentryTracker::EndEnumerate(Cursor * /* cursor */) { Unlock(); }
255
256
257 //------------------------------------------------------------------------------
258
259
260
1/2
✓ Branch 3 taken 583 times.
✗ Branch 4 not taken.
583 PageCacheTracker::PageCacheTracker() : version_(kVersion), is_active_(true) {
261
1/2
✓ Branch 1 taken 583 times.
✗ Branch 2 not taken.
583 map_.Init(16, 0, hasher_inode);
262 583 InitLock();
263 583 }
264
265
266 581 PageCacheTracker::~PageCacheTracker() {
267 581 pthread_mutex_destroy(lock_);
268 581 free(lock_);
269 581 }
270
271
272 PageCacheTracker::PageCacheTracker(const PageCacheTracker &other) {
273 CopyFrom(other);
274 InitLock();
275 }
276
277
278 PageCacheTracker &PageCacheTracker::operator=(const PageCacheTracker &other) {
279 if (&other == this)
280 return *this;
281
282 const MutexLockGuard guard(lock_);
283 CopyFrom(other);
284 return *this;
285 }
286
287
288 void PageCacheTracker::CopyFrom(const PageCacheTracker &other) {
289 assert(other.version_ == kVersion);
290
291 version_ = kVersion;
292 is_active_ = other.is_active_;
293 statistics_ = other.statistics_;
294
295 map_.Init(16, 0, hasher_inode);
296 map_ = other.map_;
297 stat_store_ = other.stat_store_;
298 }
299
300
301 583 void PageCacheTracker::InitLock() {
302 583 lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
303 583 const int retval = pthread_mutex_init(lock_, NULL);
304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 583 times.
583 assert(retval == 0);
305 583 }
306
307 428 PageCacheTracker::OpenDirectives PageCacheTracker::Open(
308 uint64_t inode, const shash::Any &hash, const struct stat &info)
309 {
310 428 OpenDirectives open_directives;
311 // Old behavior: always flush page cache on open
312
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 384 times.
428 if (!is_active_)
313 44 return open_directives;
314
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 384 times.
384 if (inode != info.st_ino) {
316 PANIC(kLogStderr | kLogDebug,
317 "invalid entry on open: %" PRIu64 " with st_ino=%" PRIu64,
318 " hash=%s size=%" PRIu64, inode, info.st_ino, hash.ToString().c_str(),
319 info.st_size);
320 }
321
322 384 const MutexLockGuard guard(lock_);
323
324
1/2
✓ Branch 1 taken 384 times.
✗ Branch 2 not taken.
384 Entry entry;
325
1/2
✓ Branch 1 taken 384 times.
✗ Branch 2 not taken.
384 const bool retval = map_.Lookup(inode, &entry);
326
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 256 times.
384 if (!retval) {
327 128 open_directives.keep_cache = true;
328 128 open_directives.direct_io = false;
329 128 statistics_.n_insert++;
330 128 statistics_.n_open_cached++;
331
332 128 entry.nopen = 1;
333
1/2
✓ Branch 1 taken 128 times.
✗ Branch 2 not taken.
128 entry.idx_stat = stat_store_.Add(info);
334 128 entry.hash = hash;
335
1/2
✓ Branch 1 taken 128 times.
✗ Branch 2 not taken.
128 map_.Insert(inode, entry);
336 128 return open_directives;
337 }
338
339
2/2
✓ Branch 1 taken 170 times.
✓ Branch 2 taken 86 times.
256 if (entry.hash == hash) {
340 170 open_directives.direct_io = false;
341
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 128 times.
170 if (entry.nopen < 0) {
342 // The page cache is still in the transition phase and may contain old
343 // content. So trigger a flush of the cache in any case.
344 42 open_directives.keep_cache = false;
345 42 statistics_.n_open_flush++;
346 42 entry.nopen--;
347
1/2
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
42 map_.Insert(inode, entry);
348 42 return open_directives;
349 } else {
350 128 open_directives.keep_cache = true;
351 128 statistics_.n_open_cached++;
352
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 if (entry.nopen++ == 0)
353 entry.idx_stat = stat_store_.Add(info);
354
1/2
✓ Branch 1 taken 128 times.
✗ Branch 2 not taken.
128 map_.Insert(inode, entry);
355 128 return open_directives;
356 }
357 }
358
359 // Page cache mismatch and old data has still open file attached to it,
360 // circumvent the page cache entirely and use direct I/O. In this case,
361 // cvmfs_close() will _not_ call Close().
362
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 42 times.
86 if (entry.nopen != 0) {
363 44 open_directives.keep_cache = true;
364 44 open_directives.direct_io = true;
365 44 statistics_.n_open_direct++;
366 44 return open_directives;
367 }
368
369 // Stale data in the page cache, start the transition phase in which newly
370 // opened files flush the page cache and re-populate it with the new hash.
371 // The first file to reach Close() will finish the transition phase and
372 // mark the new hash as committed.
373 42 open_directives.direct_io = false;
374 42 open_directives.keep_cache = false;
375 42 statistics_.n_open_flush++;
376 42 entry.hash = hash;
377
1/2
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
42 entry.idx_stat = stat_store_.Add(info);
378 42 entry.nopen = -1;
379
1/2
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
42 map_.Insert(inode, entry);
380 42 return open_directives;
381 384 }
382
383 PageCacheTracker::OpenDirectives PageCacheTracker::OpenDirect() {
384 OpenDirectives open_directives(true, true);
385 // Old behavior: always flush page cache on open
386 if (!is_active_)
387 return open_directives;
388
389 const MutexLockGuard guard(lock_);
390 statistics_.n_open_direct++;
391 return open_directives;
392 }
393
394 340 void PageCacheTracker::Close(uint64_t inode) {
395
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 296 times.
340 if (!is_active_)
396 44 return;
397
398 296 const MutexLockGuard guard(lock_);
399
1/2
✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
296 Entry entry;
400
1/2
✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
296 bool retval = map_.Lookup(inode, &entry);
401
402 296 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
403 "PageCacheTracker::Close Race condition? "
404 "Did not find inode %lu",
405 inode)
406
3/6
✓ Branch 0 taken 296 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 296 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 296 times.
296 || !AssertOrLog(entry.nopen != 0, kLogCvmfs, kLogSyslogWarn | kLogDebug,
407 "PageCacheTracker::Close Race condition? "
408 "Inode %lu has no open entries",
409 inode)) {
410 return;
411 }
412
413 296 const int32_t old_open = entry.nopen;
414
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 254 times.
296 if (entry.nopen < 0) {
415 // At this point we know that any stale data has been flushed from the
416 // cache and only data related to the currently booked content hash
417 // can be present. So clear the transition bit (sign bit).
418 42 entry.nopen = -entry.nopen;
419 }
420 296 entry.nopen--;
421
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 169 times.
296 if (entry.nopen == 0) {
422 // File closed, remove struct stat information
423
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 127 times.
127 if (entry.idx_stat < 0) {
424 PANIC(kLogSyslogErr | kLogDebug,
425 "page cache tracker: missing stat entry! Entry info: inode %" PRIu64
426 " - open counter %d - hash %s",
427 inode, old_open, entry.hash.ToString().c_str());
428 }
429
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 const uint64_t inode_update = stat_store_.Erase(entry.idx_stat);
430
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 Entry entry_update;
431
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 retval = map_.Lookup(inode_update, &entry_update);
432
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 127 times.
127 if (!retval) {
433 PANIC(kLogSyslogErr | kLogDebug,
434 "invalid inode in page cache tracker: inode %" PRIu64
435 ", replacing %" PRIu64,
436 inode_update, inode);
437 }
438 127 entry_update.idx_stat = entry.idx_stat;
439
1/2
✓ Branch 1 taken 127 times.
✗ Branch 2 not taken.
127 map_.Insert(inode_update, entry_update);
440 127 entry.idx_stat = -1;
441 }
442
1/2
✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
296 map_.Insert(inode, entry);
443
1/2
✓ Branch 1 taken 296 times.
✗ Branch 2 not taken.
296 }
444
445 86 PageCacheTracker::EvictRaii::EvictRaii(PageCacheTracker *t) : tracker_(t) {
446 86 const int retval = pthread_mutex_lock(tracker_->lock_);
447
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 assert(retval == 0);
448 86 }
449
450 86 PageCacheTracker::EvictRaii::~EvictRaii() {
451 86 const int retval = pthread_mutex_unlock(tracker_->lock_);
452
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 assert(retval == 0);
453 86 }
454
455 86 void PageCacheTracker::EvictRaii::Evict(uint64_t inode) {
456
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 42 times.
86 if (!tracker_->is_active_)
457 44 return;
458
459 42 const bool contained_inode = tracker_->map_.Erase(inode);
460
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (contained_inode)
461 42 tracker_->statistics_.n_remove++;
462 }
463
464 } // namespace glue
465