GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.h
Date: 2026-10-11 02:40:02
Exec Total Coverage
Lines: 302 443 68.2%
Branches: 117 306 38.2%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This module provides the inode tracker in order to remember inodes
5 * and their parents that are in use by the kernel.
6 *
7 * These objects have to survive reloading of the library, so no virtual
8 * functions.
9 */
10
11 #include <pthread.h>
12 #include <sched.h>
13 #include <stdint.h>
14
15 #include <atomic>
16 #include <cassert>
17 #include <cstring>
18 #include <string>
19
20 #include "bigqueue.h"
21 #include "bigvector.h"
22 #include "crypto/hash.h"
23 #include "directory_entry.h"
24 #include "duplex_testing.h"
25 #include "shortstring.h"
26 #include "smallhash.h"
27 #include "util/exception.h"
28 #include "util/mutex.h"
29 #include "util/posix.h" // IWYU pragma: keep
30 #include "util/smalloc.h"
31 #include "util/string.h"
32
33 #ifndef CVMFS_GLUE_BUFFER_H_
34 #define CVMFS_GLUE_BUFFER_H_
35
36 namespace glue {
37
38 /**
39 * Inode + file type. Stores the file type in the 4 most significant bits
40 * of the 64 bit unsigned integer representing the inode. That makes the class
41 * compatible with a pure 64bit inode used in previous cvmfs versions in the
42 * inode tracker. The file type is stored using the POSIX representation in
43 * the inode's mode field.
44 * Note that InodeEx, used as a hash table key, hashes only over the inode part.
45 */
46 class InodeEx {
47 private:
48 // Extracts the file type bits from the POSIX mode field and shifts them to
49 // the right so that they align with EFileType constants.
50 24 static inline uint64_t ShiftMode(unsigned mode) { return (mode >> 12) & 017; }
51
52 public:
53 enum EFileType {
54 kUnknownType = 0,
55 kRegular = 010,
56 kSymlink = 012,
57 kDirectory = 004,
58 kFifo = 001,
59 kSocket = 014,
60 kCharDev = 002,
61 kBulkDev = 006,
62 };
63
64 29741 InodeEx() : inode_ex_(0) { }
65 4710 InodeEx(uint64_t inode, EFileType type)
66 4710 : inode_ex_(inode | (static_cast<uint64_t>(type) << 60)) { }
67 6 InodeEx(uint64_t inode, unsigned mode)
68 6 : inode_ex_(inode | (ShiftMode(mode) << 60)) { }
69
70 93562 inline uint64_t GetInode() const { return inode_ex_ & ~(uint64_t(15) << 60); }
71 60 inline EFileType GetFileType() const {
72 60 return static_cast<EFileType>(inode_ex_ >> 60);
73 }
74
75 32936 inline bool operator==(const InodeEx &other) const {
76 32936 return GetInode() == other.GetInode();
77 }
78 7959 inline bool operator!=(const InodeEx &other) const {
79 7959 return GetInode() != other.GetInode();
80 }
81
82 18 inline bool IsCompatibleFileType(unsigned mode) const {
83 18 return (static_cast<uint64_t>(GetFileType()) == ShiftMode(mode))
84
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 6 times.
18 || (GetFileType() == kUnknownType);
85 }
86
87 private:
88 uint64_t inode_ex_;
89 };
90
91 10270 static inline uint32_t hasher_md5(const shash::Md5 &key) {
92 // Don't start with the first bytes, because == is using them as well
93 return static_cast<uint32_t>(
94 10270 *(reinterpret_cast<const uint32_t *>(key.digest) + 1));
95 }
96
97 14976 static inline uint32_t hasher_inode(const uint64_t &inode) {
98 14976 return MurmurHash2(&inode, sizeof(inode), 0x07387a4f);
99 }
100
101 10702 static inline uint32_t hasher_inode_ex(const InodeEx &inode_ex) {
102 10702 return hasher_inode(inode_ex.GetInode());
103 }
104
105
106 //------------------------------------------------------------------------------
107
108
109 /**
110 * Pointer to a 2 byte length information followed by the characters
111 */
112 class StringRef {
113 public:
114 22066 StringRef() { length_ = NULL; }
115
116 24 uint16_t length() const { return *length_; }
117 ✗ uint16_t size() const { return sizeof(uint16_t) + *length_; }
118 1040 static uint16_t size(const uint16_t length) {
119 1040 return sizeof(uint16_t) + length;
120 }
121 24 char *data() const { return reinterpret_cast<char *>(length_ + 1); }
122 1040 static StringRef Place(const uint16_t length, const char *str, void *addr) {
123 1040 StringRef result;
124 1040 result.length_ = reinterpret_cast<uint16_t *>(addr);
125 1040 *result.length_ = length;
126
2/2
✓ Branch 0 taken 1035 times.
✓ Branch 1 taken 5 times.
1040 if (length > 0)
127 1035 memcpy(result.length_ + 1, str, length);
128 1040 return result;
129 }
130
131 private:
132 uint16_t *length_;
133 };
134
135
136 //------------------------------------------------------------------------------
137
138
139 /**
140 * Manages memory bins with immutable strings (deleting is a no-op).
141 * When the fraction of garbage is too large, the user of the StringHeap
142 * can copy the entire contents to a new heap.
143 */
144 class StringHeap : public SingleCopy {
145 public:
146 601 StringHeap() {
147
1/2
✓ Branch 1 taken 601 times.
✗ Branch 2 not taken.
601 Init(128 * 1024); // 128kB (should be >= 64kB+2B which is largest string)
148 601 }
149
150
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 explicit StringHeap(const uint64_t minimum_size) { Init(minimum_size); }
151
152 614 void Init(const uint64_t minimum_size) {
153 614 size_ = 0;
154 614 used_ = 0;
155
156 // Initial bin: 128kB or smallest power of 2 >= minimum size
157 614 uint64_t pow2_size = 128 * 1024;
158
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 614 times.
633 while (pow2_size < minimum_size)
159 19 pow2_size *= 2;
160 614 AddBin(pow2_size);
161 614 }
162
163 611 ~StringHeap() {
164
2/2
✓ Branch 1 taken 611 times.
✓ Branch 2 taken 611 times.
1222 for (unsigned i = 0; i < bins_.size(); ++i) {
165 611 smunmap(bins_.At(i));
166 }
167 611 }
168
169 1040 StringRef AddString(const uint16_t length, const char *str) {
170 1040 const uint16_t str_size = StringRef::size(length);
171 1040 const uint64_t remaining_bin_size = bin_size_ - bin_used_;
172 // May require opening of new bin
173
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1040 times.
1040 if (remaining_bin_size < str_size) {
174 ✗ size_ += remaining_bin_size;
175 ✗ AddBin(2 * bin_size_);
176 }
177 1040 StringRef result = StringRef::Place(
178 length, str,
179 1040 static_cast<char *>(bins_.At(bins_.size() - 1)) + bin_used_);
180 1040 size_ += str_size;
181 1040 used_ += str_size;
182 1040 bin_used_ += str_size;
183 1040 return result;
184 }
185
186 ✗ void RemoveString(const StringRef str_ref) { used_ -= str_ref.size(); }
187
188 ✗ double GetUsage() const {
189 ✗ if (size_ == 0)
190 ✗ return 1.0;
191 ✗ return static_cast<double>(used_) / static_cast<double>(size_);
192 }
193
194 ✗ uint64_t used() const { return used_; }
195
196 // mmap'd bytes, used for testing
197 17 uint64_t GetSizeAlloc() const {
198 17 uint64_t s = bin_size_;
199 17 uint64_t result = 0;
200
2/2
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 17 times.
34 for (unsigned i = 0; i < bins_.size(); ++i) {
201 17 result += s;
202 17 s /= 2;
203 }
204 17 return result;
205 }
206
207 private:
208 614 void AddBin(const uint64_t size) {
209 614 void *bin = smmap(size);
210
1/2
✓ Branch 1 taken 614 times.
✗ Branch 2 not taken.
614 bins_.PushBack(bin);
211 614 bin_size_ = size;
212 614 bin_used_ = 0;
213 614 }
214
215 uint64_t size_;
216 uint64_t used_;
217 uint64_t bin_size_;
218 uint64_t bin_used_;
219 BigVector<void *> bins_;
220 };
221
222
223 //------------------------------------------------------------------------------
224
225
226 class PathStore {
227 public:
228 /**
229 * Used to enumerate all paths
230 */
231 struct Cursor {
232 11 Cursor() : idx(0) { }
233 uint32_t idx;
234 };
235
236
237 597 PathStore() {
238
3/6
✓ Branch 2 taken 597 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 597 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 597 times.
✗ Branch 10 not taken.
597 map_.Init(16, shash::Md5(shash::AsciiPtr("!")), hasher_md5);
239
2/4
✓ Branch 1 taken 597 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 597 times.
✗ Branch 5 not taken.
597 string_heap_ = new StringHeap();
240 597 }
241
242
1/2
✓ Branch 0 taken 595 times.
✗ Branch 1 not taken.
595 ~PathStore() { delete string_heap_; }
243
244 explicit PathStore(const PathStore &other);
245 PathStore &operator=(const PathStore &other);
246
247 2075 void Insert(const shash::Md5 &md5path, const PathString &path) {
248
1/2
✓ Branch 1 taken 2075 times.
✗ Branch 2 not taken.
2075 PathInfo info;
249
1/2
✓ Branch 1 taken 2075 times.
✗ Branch 2 not taken.
2075 const bool found = map_.Lookup(md5path, &info);
250
2/2
✓ Branch 0 taken 1035 times.
✓ Branch 1 taken 1040 times.
2075 if (found) {
251 1035 info.refcnt++;
252
1/2
✓ Branch 1 taken 1035 times.
✗ Branch 2 not taken.
1035 map_.Insert(md5path, info);
253 1040 return;
254 }
255
256
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 PathInfo new_entry;
257
3/4
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1035 times.
1040 if (path.IsEmpty()) {
258
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 new_entry.name = string_heap_->AddString(0, "");
259
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 map_.Insert(md5path, new_entry);
260 5 return;
261 }
262
263
1/2
✓ Branch 1 taken 1035 times.
✗ Branch 2 not taken.
1035 const PathString parent_path = GetParentPath(path);
264
1/2
✓ Branch 3 taken 1035 times.
✗ Branch 4 not taken.
1035 new_entry.parent = shash::Md5(parent_path.GetChars(),
265 parent_path.GetLength());
266
1/2
✓ Branch 1 taken 1035 times.
✗ Branch 2 not taken.
1035 Insert(new_entry.parent, parent_path);
267
268 1035 const uint16_t name_length = path.GetLength() - parent_path.GetLength() - 1;
269 1035 const char *name_str = path.GetChars() + parent_path.GetLength() + 1;
270
1/2
✓ Branch 1 taken 1035 times.
✗ Branch 2 not taken.
1035 new_entry.name = string_heap_->AddString(name_length, name_str);
271
1/2
✓ Branch 1 taken 1035 times.
✗ Branch 2 not taken.
1035 map_.Insert(md5path, new_entry);
272 1035 }
273
274 12 bool Lookup(const shash::Md5 &md5path, PathString *path) {
275
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 PathInfo info;
276
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 bool retval = map_.Lookup(md5path, &info);
277
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!retval)
278 ✗ return false;
279
280
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 8 times.
12 if (info.parent.IsNull())
281 4 return true;
282
283
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 retval = Lookup(info.parent, path);
284
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 assert(retval);
285
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 path->Append("/", 1);
286
1/2
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
8 path->Append(info.name.data(), info.name.length());
287 8 return true;
288 }
289
290 ✗ void Erase(const shash::Md5 &md5path) {
291 ✗ PathInfo info;
292 ✗ const bool found = map_.Lookup(md5path, &info);
293 ✗ if (!found)
294 ✗ return;
295
296 ✗ info.refcnt--;
297 ✗ if (info.refcnt == 0) {
298 ✗ map_.Erase(md5path);
299 ✗ string_heap_->RemoveString(info.name);
300 ✗ if (string_heap_->GetUsage() < 0.75) {
301 ✗ StringHeap *new_string_heap = new StringHeap(string_heap_->used());
302 ✗ const shash::Md5 empty_path = map_.empty_key();
303 ✗ for (unsigned i = 0; i < map_.capacity(); ++i) {
304 ✗ if (map_.keys()[i] != empty_path) {
305 ✗ (map_.values() + i)->name = new_string_heap->AddString(
306 ✗ map_.values()[i].name.length(), map_.values()[i].name.data());
307 }
308 }
309 ✗ delete string_heap_;
310 ✗ string_heap_ = new_string_heap;
311 }
312 ✗ Erase(info.parent);
313 } else {
314 ✗ map_.Insert(md5path, info);
315 }
316 }
317
318 void Clear() {
319 map_.Clear();
320 delete string_heap_;
321 string_heap_ = new StringHeap();
322 }
323
324 11 Cursor BeginEnumerate() { return Cursor(); }
325
326 28 bool Next(Cursor *cursor, shash::Md5 *parent, StringRef *name) {
327 28 const shash::Md5 empty_key = map_.empty_key();
328
2/2
✓ Branch 1 taken 168 times.
✓ Branch 2 taken 12 times.
180 while (cursor->idx < map_.capacity()) {
329
2/2
✓ Branch 2 taken 152 times.
✓ Branch 3 taken 16 times.
168 if (map_.keys()[cursor->idx] == empty_key) {
330 152 cursor->idx++;
331 152 continue;
332 }
333 16 *parent = map_.values()[cursor->idx].parent;
334 16 *name = map_.values()[cursor->idx].name;
335 16 cursor->idx++;
336 16 return true;
337 }
338 12 return false;
339 }
340
341 private:
342 struct PathInfo {
343 20998 PathInfo() { refcnt = 1; }
344 shash::Md5 parent;
345 uint32_t refcnt;
346 StringRef name;
347 };
348
349 void CopyFrom(const PathStore &other);
350
351 SmallHashDynamic<shash::Md5, PathInfo> map_;
352 StringHeap *string_heap_;
353 };
354
355
356 //------------------------------------------------------------------------------
357
358
359 /**
360 * A vector of stat structs. When removing items, the empty slot is swapped
361 * with the last element so that there are no gaps in the vector. The memory
362 * allocation of the vector grows and shrinks with the size.
363 * Removal of items returns the inode of the element swapped with the gap so
364 * that the page entry tracker can update its index.
365 */
366 class StatStore {
367 public:
368 6024 int32_t Add(const struct stat &info) {
369 // We don't support more that 2B open files
370
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6024 times.
6024 assert(store_.size() < (1LU << 31));
371 6024 const int32_t index = static_cast<int>(store_.size());
372 6024 store_.PushBack(info);
373 6024 return index;
374 }
375
376 // Note that that if the last element is removed, no swap has taken place
377 6019 uint64_t Erase(int32_t index) {
378 6019 struct stat const info_back = store_.At(store_.size() - 1);
379 6019 store_.Replace(index, info_back);
380 6019 store_.SetSize(store_.size() - 1);
381
1/2
✓ Branch 1 taken 6019 times.
✗ Branch 2 not taken.
6019 store_.ShrinkIfOversized();
382 6019 return info_back.st_ino;
383 }
384
385 11956 struct stat Get(int32_t index) const { return store_.At(index); }
386
387 private:
388 BigVector<struct stat> store_;
389 };
390
391
392 //------------------------------------------------------------------------------
393
394
395 class PathMap {
396 public:
397
4/8
✓ Branch 2 taken 597 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 597 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 597 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 597 times.
✗ Branch 14 not taken.
597 PathMap() { map_.Init(16, shash::Md5(shash::AsciiPtr("!")), hasher_md5); }
398
399 4 bool LookupPath(const shash::Md5 &md5path, PathString *path) {
400 4 const bool found = path_store_.Lookup(md5path, path);
401 4 return found;
402 }
403
404 4 uint64_t LookupInodeByPath(const PathString &path) {
405 uint64_t inode;
406
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 const bool found = map_.Lookup(
407
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 shash::Md5(path.GetChars(), path.GetLength()), &inode);
408
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (found)
409 4 return inode;
410 ✗ return 0;
411 }
412
413 12 uint64_t LookupInodeByMd5Path(const shash::Md5 &md5path) {
414 uint64_t inode;
415
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 const bool found = map_.Lookup(md5path, &inode);
416
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (found)
417 12 return inode;
418 ✗ return 0;
419 }
420
421 1040 shash::Md5 Insert(const PathString &path, const uint64_t inode) {
422 1040 shash::Md5 md5path(path.GetChars(), path.GetLength());
423
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 if (!map_.Contains(md5path)) {
424 1040 path_store_.Insert(md5path, path);
425 1040 map_.Insert(md5path, inode);
426 }
427 1040 return md5path;
428 }
429
430 ✗ void Erase(const shash::Md5 &md5path) {
431 ✗ const bool found = map_.Contains(md5path);
432 ✗ if (found) {
433 ✗ path_store_.Erase(md5path);
434 ✗ map_.Erase(md5path);
435 }
436 }
437
438 ✗ void Replace(const shash::Md5 &md5path, uint64_t new_inode) {
439 ✗ map_.Insert(md5path, new_inode);
440 }
441
442 void Clear() {
443 map_.Clear();
444 path_store_.Clear();
445 }
446
447 // For enumerating
448 39 PathStore *path_store() { return &path_store_; }
449
450 private:
451 SmallHashDynamic<shash::Md5, uint64_t> map_;
452 PathStore path_store_;
453 };
454
455
456 //------------------------------------------------------------------------------
457
458
459 /**
460 * This class has the same memory layout than the previous "InodeMap" class,
461 * therefore there is no data structure migration during reload required.
462 */
463 class InodeExMap {
464 public:
465
1/2
✓ Branch 3 taken 597 times.
✗ Branch 4 not taken.
597 InodeExMap() { map_.Init(16, InodeEx(), hasher_inode_ex); }
466
467 8 bool LookupMd5Path(InodeEx *inode_ex, shash::Md5 *md5path) {
468 8 const bool found = map_.LookupEx(inode_ex, md5path);
469 8 return found;
470 }
471
472 1040 void Insert(const InodeEx inode_ex, const shash::Md5 &md5path) {
473 1040 map_.Insert(inode_ex, md5path);
474 1040 }
475
476 ✗ void Erase(const uint64_t inode) {
477 ✗ map_.Erase(InodeEx(inode, InodeEx::kUnknownType));
478 }
479
480 void Clear() { map_.Clear(); }
481
482 private:
483 SmallHashDynamic<InodeEx, shash::Md5> map_;
484 };
485
486
487 //------------------------------------------------------------------------------
488
489
490 class InodeReferences {
491 public:
492 /**
493 * Used to enumerate all inodes
494 */
495 struct Cursor {
496 11 Cursor() : idx(0) { }
497 uint32_t idx;
498 };
499
500
1/2
✓ Branch 2 taken 597 times.
✗ Branch 3 not taken.
597 InodeReferences() { map_.Init(16, 0, hasher_inode); }
501
502 1040 bool Get(const uint64_t inode, const uint32_t by) {
503 1040 uint32_t refcounter = 0;
504
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 const bool found = map_.Lookup(inode, &refcounter);
505 1040 const bool new_inode = !found;
506 1040 refcounter += by; // This is 0 if the inode is not found
507
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 map_.Insert(inode, refcounter);
508 1040 return new_inode;
509 }
510
511 ✗ bool Put(const uint64_t inode, const uint32_t by) {
512 uint32_t refcounter;
513 ✗ const bool found = map_.Lookup(inode, &refcounter);
514 ✗ if (!found) {
515 // May happen if a retired inode is cleared, i.e. if a file with
516 // outdated content is closed
517 ✗ return false;
518 }
519
520 ✗ if (refcounter < by) {
521 ✗ PANIC(kLogSyslogErr | kLogDebug,
522 "inode tracker refcount mismatch, inode % " PRIu64
523 ", refcounts %u / %u",
524 inode, refcounter, by);
525 }
526
527 ✗ if (refcounter == by) {
528 ✗ map_.Erase(inode);
529 ✗ return true;
530 }
531 ✗ refcounter -= by;
532 ✗ map_.Insert(inode, refcounter);
533 ✗ return false;
534 }
535
536 ✗ void Replace(const uint64_t old_inode, const uint64_t new_inode) {
537 ✗ map_.Erase(old_inode);
538 ✗ map_.Insert(new_inode, 0);
539 }
540
541 void Clear() { map_.Clear(); }
542
543 11 Cursor BeginEnumerate() { return Cursor(); }
544
545 3099 bool Next(Cursor *cursor, uint64_t *inode) {
546 3099 const uint64_t empty_key = map_.empty_key();
547
2/2
✓ Branch 1 taken 8232 times.
✓ Branch 2 taken 11 times.
8243 while (cursor->idx < map_.capacity()) {
548
2/2
✓ Branch 1 taken 5144 times.
✓ Branch 2 taken 3088 times.
8232 if (map_.keys()[cursor->idx] == empty_key) {
549 5144 cursor->idx++;
550 5144 continue;
551 }
552 3088 *inode = map_.keys()[cursor->idx];
553 3088 cursor->idx++;
554 3088 return true;
555 }
556 11 return false;
557 }
558
559 private:
560 SmallHashDynamic<uint64_t, uint32_t> map_;
561 };
562
563
564 //------------------------------------------------------------------------------
565
566
567 /**
568 * Tracks inode reference counters as given by Fuse.
569 */
570 class InodeTracker {
571 public:
572 /**
573 * Used to actively evict all known paths from kernel caches
574 */
575 struct Cursor {
576 11 explicit Cursor(const PathStore::Cursor &p,
577 const InodeReferences::Cursor &i)
578 11 : csr_paths(p), csr_inos(i) { }
579 PathStore::Cursor csr_paths;
580 InodeReferences::Cursor csr_inos;
581 };
582
583 /**
584 * To avoid taking the InodeTracker mutex multiple times, the fuse
585 * forget_multi callback releases inodes references through this RAII object.
586 * Copy and assign operator should be deleted but that would require
587 * all compilers to use RVO. TODO(jblomer): fix with C++11
588 */
589 class VfsPutRaii {
590 public:
591 ✗ explicit VfsPutRaii(InodeTracker *t) : tracker_(t) { tracker_->Lock(); }
592 ✗ ~VfsPutRaii() { tracker_->Unlock(); }
593
594 ✗ bool VfsPut(const uint64_t inode, const uint32_t by) {
595 ✗ const bool removed = tracker_->inode_references_.Put(inode, by);
596 ✗ if (removed) {
597 // TODO(jblomer): pop operation (Lookup+Erase)
598 ✗ shash::Md5 md5path;
599 ✗ InodeEx inode_ex(inode, InodeEx::kUnknownType);
600 ✗ const bool found = tracker_->inode_ex_map_.LookupMd5Path(&inode_ex,
601 &md5path);
602 ✗ if (!found) {
603 ✗ PANIC(kLogSyslogErr | kLogDebug,
604 "inode tracker ref map and path map out of sync: %" PRIu64,
605 inode);
606 }
607 ✗ tracker_->inode_ex_map_.Erase(inode);
608 ✗ tracker_->path_map_.Erase(md5path);
609 ✗ tracker_->statistics_.num_removes.fetch_add(1);
610 }
611 ✗ tracker_->statistics_.num_references.fetch_add(-int32_t(by));
612 ✗ return removed;
613 }
614
615 private:
616 InodeTracker *tracker_;
617 };
618
619 // Cannot be moved to the statistics manager because it has to survive
620 // reloads. Added manually in the fuse module initialization and in talk.cc.
621 struct Statistics {
622 597 Statistics() {
623 597 num_inserts.store(0);
624 597 num_removes.store(0);
625 597 num_references.store(0);
626 597 num_hits_inode.store(0);
627 597 num_hits_path.store(0);
628 597 num_misses_path.store(0);
629 597 }
630 ✗ Statistics(const Statistics &reference)
631 ✗ : num_inserts(reference.num_inserts.load())
632 ✗ , num_removes(reference.num_removes.load())
633 ✗ , num_references(reference.num_references.load())
634 ✗ , num_hits_inode(reference.num_hits_inode.load())
635 ✗ , num_hits_path(reference.num_hits_path.load())
636 ✗ , num_misses_path(reference.num_misses_path.load()) { }
637
638 ✗ Statistics &operator=(const Statistics &reference) {
639 ✗ if (this != &reference) {
640 ✗ num_inserts.store(reference.num_inserts.load());
641 ✗ num_removes.store(reference.num_removes.load());
642 ✗ num_references.store(reference.num_references.load());
643 ✗ num_hits_inode.store(reference.num_hits_inode.load());
644 ✗ num_hits_path.store(reference.num_hits_path.load());
645 ✗ num_misses_path.store(reference.num_misses_path.load());
646 }
647 ✗ return *(this);
648 }
649
650 std::string Print() {
651 return "inserts: " + StringifyInt(num_inserts.load())
652 + " removes: " + StringifyInt(num_removes.load())
653 + " references: " + StringifyInt(num_references.load())
654 + " hits(inode): " + StringifyInt(num_hits_inode.load())
655 + " hits(path): " + StringifyInt(num_hits_path.load())
656 + " misses(path): " + StringifyInt(num_misses_path.load());
657 }
658 std::atomic<int64_t> num_inserts;
659 std::atomic<int64_t> num_removes;
660 std::atomic<int64_t> num_references;
661 std::atomic<int64_t> num_hits_inode;
662 std::atomic<int64_t> num_hits_path;
663 std::atomic<int64_t> num_misses_path;
664 };
665 ✗ Statistics GetStatistics() { return statistics_; }
666
667 InodeTracker();
668 explicit InodeTracker(const InodeTracker &other);
669 InodeTracker &operator=(const InodeTracker &other);
670 ~InodeTracker();
671
672 1040 void VfsGetBy(const InodeEx inode_ex, const uint32_t by,
673 const PathString &path) {
674 1040 const uint64_t inode = inode_ex.GetInode();
675 1040 Lock();
676
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 const bool is_new_inode = inode_references_.Get(inode, by);
677
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 const shash::Md5 md5path = path_map_.Insert(path, inode);
678
1/2
✓ Branch 1 taken 1040 times.
✗ Branch 2 not taken.
1040 inode_ex_map_.Insert(inode_ex, md5path);
679 1040 Unlock();
680
681 1040 statistics_.num_references.fetch_add(by);
682
1/2
✓ Branch 0 taken 1040 times.
✗ Branch 1 not taken.
1040 if (is_new_inode)
683 1040 statistics_.num_inserts.fetch_add(1);
684 1040 }
685
686 1040 void VfsGet(const InodeEx inode_ex, const PathString &path) {
687 1040 VfsGetBy(inode_ex, 1, path);
688 1040 }
689
690 ✗ VfsPutRaii GetVfsPutRaii() { return VfsPutRaii(this); }
691
692 ✗ bool FindPath(InodeEx *inode_ex, PathString *path) {
693 ✗ Lock();
694 ✗ shash::Md5 md5path;
695 ✗ bool found = inode_ex_map_.LookupMd5Path(inode_ex, &md5path);
696 ✗ if (found) {
697 ✗ found = path_map_.LookupPath(md5path, path);
698 ✗ assert(found);
699 }
700 ✗ Unlock();
701
702 ✗ if (found) {
703 ✗ statistics_.num_hits_path.fetch_add(1);
704 } else {
705 ✗ statistics_.num_misses_path.fetch_add(1);
706 }
707 ✗ return found;
708 }
709
710 ✗ uint64_t FindInode(const PathString &path) {
711 ✗ Lock();
712 ✗ const uint64_t inode = path_map_.LookupInodeByPath(path);
713 ✗ Unlock();
714 ✗ statistics_.num_hits_inode.fetch_add(1);
715 ✗ return inode;
716 }
717
718 8 bool FindDentry(uint64_t ino, uint64_t *parent_ino, NameString *name) {
719 8 PathString path;
720 8 InodeEx inodex(ino, InodeEx::kUnknownType);
721
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 shash::Md5 md5path;
722
723 8 Lock();
724
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 bool found = inode_ex_map_.LookupMd5Path(&inodex, &md5path);
725
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (found) {
726
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 found = path_map_.LookupPath(md5path, &path);
727
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(found);
728
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 *name = GetFileName(path);
729
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 path = GetParentPath(path);
730
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 *parent_ino = path_map_.LookupInodeByPath(path);
731 }
732 8 Unlock();
733 8 return found;
734 8 }
735
736 /**
737 * The new inode has reference counter 0. Returns true if the inode was
738 * found and replaced
739 */
740 ✗ bool ReplaceInode(uint64_t old_inode, const InodeEx &new_inode) {
741 ✗ shash::Md5 md5path;
742 ✗ InodeEx old_inode_ex(old_inode, InodeEx::kUnknownType);
743 ✗ Lock();
744 ✗ const bool found = inode_ex_map_.LookupMd5Path(&old_inode_ex, &md5path);
745 ✗ if (found) {
746 ✗ inode_references_.Replace(old_inode, new_inode.GetInode());
747 ✗ path_map_.Replace(md5path, new_inode.GetInode());
748 ✗ inode_ex_map_.Erase(old_inode);
749 ✗ inode_ex_map_.Insert(new_inode, md5path);
750 }
751 ✗ Unlock();
752 ✗ return found;
753 }
754
755 11 Cursor BeginEnumerate() {
756 11 Lock();
757 11 return Cursor(path_map_.path_store()->BeginEnumerate(),
758 22 inode_references_.BeginEnumerate());
759 }
760
761 28 bool NextEntry(Cursor *cursor, uint64_t *inode_parent, NameString *name) {
762
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 shash::Md5 parent_md5;
763 28 StringRef name_ref;
764
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 const bool result = path_map_.path_store()->Next(&(cursor->csr_paths),
765 &parent_md5, &name_ref);
766
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 16 times.
28 if (!result)
767 12 return false;
768
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 12 times.
16 if (parent_md5.IsNull())
769 4 *inode_parent = 0;
770 else
771
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 *inode_parent = path_map_.LookupInodeByMd5Path(parent_md5);
772
1/2
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
16 name->Assign(name_ref.data(), name_ref.length());
773 16 return true;
774 }
775
776 3099 bool NextInode(Cursor *cursor, uint64_t *inode) {
777 3099 return inode_references_.Next(&(cursor->csr_inos), inode);
778 }
779
780 11 void EndEnumerate(Cursor *cursor) { Unlock(); }
781
782 private:
783 static const unsigned kVersion = 4;
784
785 void InitLock();
786 void CopyFrom(const InodeTracker &other);
787 1059 inline void Lock() const {
788 1059 const int retval = pthread_mutex_lock(lock_);
789
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1059 times.
1059 assert(retval == 0);
790 1059 }
791 1059 inline void Unlock() const {
792 1059 const int retval = pthread_mutex_unlock(lock_);
793
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1059 times.
1059 assert(retval == 0);
794 1059 }
795
796 unsigned version_;
797 pthread_mutex_t *lock_;
798 PathMap path_map_;
799 InodeExMap inode_ex_map_;
800 InodeReferences inode_references_;
801 Statistics statistics_;
802 }; // class InodeTracker
803
804
805 /**
806 * Tracks fuse name lookup replies for active cache eviction.
807 * Class renamed from previous name NentryTracker
808 */
809 class DentryTracker {
810 FRIEND_TEST(T_GlueBuffer, DentryTracker);
811
812 private:
813 struct Entry {
814 Entry() : expiry(0), inode_parent(0) { }
815 2068 Entry(uint64_t e, uint64_t p, const char *n)
816 2068 : expiry(e), inode_parent(p), name(n, strlen(n)) { }
817 uint64_t expiry;
818 uint64_t inode_parent;
819 NameString name;
820 };
821
822 public:
823 struct Cursor {
824 37 explicit Cursor(Entry *h) : head(h), pos(0) { }
825 Entry *head;
826 size_t pos;
827 };
828
829 // Cannot be moved to the statistics manager because it has to survive
830 // reloads. Added manually in the fuse module initialization and in talk.cc.
831 struct Statistics {
832 566 Statistics() : num_insert(0), num_remove(0), num_prune(0) { }
833 int64_t num_insert;
834 int64_t num_remove;
835 int64_t num_prune;
836 };
837 30 Statistics GetStatistics() { return statistics_; }
838
839 static void *MainCleaner(void *data);
840
841 DentryTracker();
842 DentryTracker(const DentryTracker &other);
843 DentryTracker &operator=(const DentryTracker &other);
844 ~DentryTracker();
845
846 /**
847 * Lock object during copy
848 */
849 DentryTracker *Move();
850
851 2076 void Add(const uint64_t inode_parent, const char *name, uint64_t timeout_s) {
852
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2072 times.
2076 if (!is_active_)
853 4 return;
854
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2068 times.
2072 if (timeout_s == 0)
855 4 return;
856
857 2068 const uint64_t now = platform_monotonic_time();
858 2068 Lock();
859
1/2
✓ Branch 2 taken 2068 times.
✗ Branch 3 not taken.
2068 entries_.PushBack(Entry(now + timeout_s, inode_parent, name));
860 2068 statistics_.num_insert++;
861 2068 DoPrune(now);
862 2068 Unlock();
863 }
864
865 void Prune();
866 /**
867 * The nentry tracker is only needed for active cache eviction and can
868 * otherwise ignore new entries.
869 */
870 4 void Disable() { is_active_ = false; }
871 ✗ bool is_active() const { return is_active_; }
872
873 void SpawnCleaner(unsigned interval_s);
874
875 Cursor BeginEnumerate();
876 bool NextEntry(Cursor *cursor, uint64_t *inode_parent, NameString *name);
877 void EndEnumerate(Cursor *cursor);
878
879 private:
880 static const unsigned kVersion = 0;
881
882 void CopyFrom(const DentryTracker &other);
883
884 void InitLock();
885 2141 inline void Lock() const {
886 2141 const int retval = pthread_mutex_lock(lock_);
887
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141 times.
2141 assert(retval == 0);
888 2141 }
889 2141 inline void Unlock() const {
890 2141 const int retval = pthread_mutex_unlock(lock_);
891
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141 times.
2141 assert(retval == 0);
892 2141 }
893
894 2099 void DoPrune(uint64_t now) {
895 Entry *entry;
896
3/4
✓ Branch 1 taken 2113 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2090 times.
✓ Branch 4 taken 23 times.
2113 while (entries_.Peek(&entry)) {
897
2/2
✓ Branch 0 taken 2076 times.
✓ Branch 1 taken 14 times.
2090 if (entry->expiry >= now)
898 2076 break;
899
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 entries_.PopFront();
900 14 statistics_.num_remove++;
901 }
902 2099 statistics_.num_prune++;
903 2099 }
904
905 pthread_mutex_t *lock_;
906 unsigned version_;
907 Statistics statistics_;
908 bool is_active_;
909 BigQueue<Entry> entries_;
910
911 int pipe_terminate_[2];
912 int cleaning_interval_ms_;
913 pthread_t thread_cleaner_;
914 }; // class DentryTracker
915
916 /**
917 * Tracks the content hash associated to inodes of regular files whose content
918 * may be in the page cache. It is used in cvmfs_open() and cvmfs_close().
919 */
920 class PageCacheTracker {
921 private:
922 struct Entry {
923 11727 Entry() : nopen(0), idx_stat(-1) { }
924 Entry(int32_t n, int32_t i, const shash::Any &h)
925 : nopen(n), idx_stat(i), hash(h) { }
926 /**
927 * Reference counter for currently open files with a given inode. If the
928 * sign bit is set, the entry is in the transition phase from one hash to
929 * another. The sign will be cleared on Close() in this case.
930 */
931 int32_t nopen;
932 /**
933 * Points into the list of stat structs; >= 0 only for open files.
934 */
935 int32_t idx_stat;
936 /**
937 * The content hash of the data stored in the page cache. For chunked files,
938 * hash contains an artificial hash over all the chunk hash values.
939 */
940 shash::Any hash;
941 };
942
943 public:
944 /**
945 * In the fuse file handle, use bit 62 to indicate that the file was opened
946 * with a direct I/O setting and cvmfs_release() should not call Close().
947 * Note that the sign bit (bit 63) indicates chunked files.
948 */
949 static const unsigned int kBitDirectIo = 62;
950
951 /**
952 * Instruct cvmfs_open() on how to handle the page cache.
953 */
954 struct OpenDirectives {
955 /**
956 * Flush the page cache; logically, the flush takes place some time between
957 * cvmfs_open() and cvmfs_close(). That's important in case we have two
958 * open() calls on stale page cache data.
959 */
960 bool keep_cache;
961 /**
962 * Don't use the page cache at all (neither write nor read). If this is set
963 * on cvmfs_open(), don't call Close() on cvmfs_close().
964 * Direct I/O prevents shared mmap on the file. Private mmap, however,
965 * which includes loading binaries, still works.
966 */
967 bool direct_io;
968
969 // Defaults to the old (pre v2.10) behavior: always flush the cache, never
970 // use direct I/O.
971 60 OpenDirectives() : keep_cache(false), direct_io(false) { }
972
973 ✗ OpenDirectives(bool k, bool d) : keep_cache(k), direct_io(d) { }
974 };
975
976 /**
977 * To avoid taking the PageCacheTracker mutex multiple times, the
978 * fuse forget_multi callback evicts inodes through this RAII object.
979 * Copy and assign operator should be deleted but that would require
980 * all compilers to use RVO. TODO(jblomer): fix with C++11
981 */
982 class EvictRaii {
983 public:
984 explicit EvictRaii(PageCacheTracker *t);
985 ~EvictRaii();
986 void Evict(uint64_t inode);
987
988 private:
989 PageCacheTracker *tracker_;
990 };
991
992 // Cannot be moved to the statistics manager because it has to survive
993 // reloads. Added manually in the fuse module initialization and in talk.cc.
994 struct Statistics {
995 554 Statistics()
996 554 : n_insert(0)
997 554 , n_remove(0)
998 554 , n_open_direct(0)
999 554 , n_open_flush(0)
1000 554 , n_open_cached(0) { }
1001 uint64_t n_insert;
1002 uint64_t n_remove;
1003 uint64_t n_open_direct;
1004 uint64_t n_open_flush;
1005 uint64_t n_open_cached;
1006 };
1007 ✗ Statistics GetStatistics() { return statistics_; }
1008
1009 PageCacheTracker();
1010 explicit PageCacheTracker(const PageCacheTracker &other);
1011 PageCacheTracker &operator=(const PageCacheTracker &other);
1012 ~PageCacheTracker();
1013
1014 OpenDirectives Open(uint64_t inode, const shash::Any &hash,
1015 const struct stat &info);
1016 /**
1017 * Forced direct I/O open. Used when the corresponding flag is set in the
1018 * file catalogs. In this case, we don't need to track the inode.
1019 */
1020 OpenDirectives OpenDirect();
1021 void Close(uint64_t inode);
1022
1023 8 bool GetInfoIfOpen(uint64_t inode, shash::Any *hash, struct stat *info) {
1024 8 const MutexLockGuard guard(lock_);
1025
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 Entry entry;
1026
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 const bool retval = map_.Lookup(inode, &entry);
1027
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
8 if (retval && (entry.nopen != 0)) {
1028
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(entry.idx_stat >= 0);
1029 4 *hash = entry.hash;
1030
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (info != NULL)
1031
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 *info = stat_store_.Get(entry.idx_stat);
1032 4 return true;
1033 }
1034 4 return false;
1035 8 }
1036
1037 /**
1038 * Checks if the dirent's inode is registered in the page cache tracker and
1039 * if
1040 * - it is currently open and has a different content than dirent
1041 * - it has been previously found stale (no matter if now open or not)
1042 */
1043 ✗ bool IsStale(const catalog::DirectoryEntry &dirent) {
1044 ✗ Entry entry;
1045 ✗ const MutexLockGuard guard(lock_);
1046
1047 ✗ const bool retval = map_.Lookup(dirent.inode(), &entry);
1048 ✗ if (!retval)
1049 ✗ return false;
1050 ✗ if (entry.hash.IsNull()) {
1051 // A previous call to IsStale() returned true (see below)
1052 ✗ return true;
1053 }
1054 ✗ if (entry.nopen == 0)
1055 ✗ return false;
1056 ✗ if (entry.hash == dirent.checksum())
1057 ✗ return false;
1058
1059 ✗ bool is_stale = true;
1060 ✗ if (dirent.IsChunkedFile()) {
1061 // Shortcut for chunked files: go by last modified timestamp
1062 ✗ is_stale = stat_store_.Get(entry.idx_stat).st_mtime != dirent.mtime();
1063 }
1064 ✗ if (is_stale) {
1065 // We mark that inode as "stale" by setting its hash to NULL.
1066 // When we check next time IsStale(), it is returned stale even
1067 // if it is not open.
1068 // The call to GetInfoIfOpen() will from now on return the null hash.
1069 // That works, the caller will still assume that the version in the
1070 // page cache tracker is different from any inode in the catalogs.
1071 ✗ entry.hash = shash::Any();
1072 ✗ map_.Insert(dirent.inode(), entry);
1073 }
1074 ✗ return is_stale;
1075 }
1076
1077 10 EvictRaii GetEvictRaii() { return EvictRaii(this); }
1078
1079 // Used in RestoreState to prevent using the page cache tracker from a
1080 // previous version after hotpatch
1081 6 void Disable() { is_active_ = false; }
1082
1083 private:
1084 static const unsigned kVersion = 0;
1085
1086 void InitLock();
1087 void CopyFrom(const PageCacheTracker &other);
1088
1089 pthread_mutex_t *lock_;
1090 unsigned version_;
1091 /**
1092 * The page cache tracker only works correctly if it is used from the start
1093 * of the mount. If the instance is hot-patched from a previous version, the
1094 * page cache tracker remains turned off.
1095 */
1096 bool is_active_;
1097 Statistics statistics_;
1098 SmallHashDynamic<uint64_t, Entry> map_;
1099 StatStore stat_store_;
1100 };
1101
1102
1103 } // namespace glue
1104
1105 #endif // CVMFS_GLUE_BUFFER_H_
1106