GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.h
Date: 2025-12-28 02:35:52
Exec Total Coverage
Lines: 302 427 70.7%
Branches: 117 304 38.5%

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