GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.cc
Date: 2025-07-06 02:35:01
Exec Total Coverage
Lines: 203 275 73.8%
Branches: 77 184 41.8%

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