GCC Code Coverage Report


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