GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.h
Date: 2025-06-29 02:35:41
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 <gtest/gtest_prod.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 184 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 383546 InodeEx() : inode_ex_(0) { }
68 87630 InodeEx(uint64_t inode, EFileType type)
69 87630 : inode_ex_(inode | (static_cast<uint64_t>(type) << 60)) { }
70 46 InodeEx(uint64_t inode, unsigned mode)
71 46 : inode_ex_(inode | (ShiftMode(mode) << 60)) { }
72
73 1995022 inline uint64_t GetInode() const { return inode_ex_ & ~(uint64_t(15) << 60); }
74 540 inline EFileType GetFileType() const {
75 540 return static_cast<EFileType>(inode_ex_ >> 60);
76 }
77
78 680936 inline bool operator==(const InodeEx &other) const {
79 680936 return GetInode() == other.GetInode();
80 }
81 180894 inline bool operator!=(const InodeEx &other) const {
82 180894 return GetInode() != other.GetInode();
83 }
84
85 138 inline bool IsCompatibleFileType(unsigned mode) const {
86 138 return (static_cast<uint64_t>(GetFileType()) == ShiftMode(mode))
87
4/4
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 46 times.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 46 times.
138 || (GetFileType() == kUnknownType);
88 }
89
90 private:
91 uint64_t inode_ex_;
92 };
93
94 468360 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 468360 *(reinterpret_cast<const uint32_t *>(key.digest) + 1));
98 }
99
100 412516 static inline uint32_t hasher_inode(const uint64_t &inode) {
101 412516 return MurmurHash2(&inode, sizeof(inode), 0x07387a4f);
102 }
103
104 223772 static inline uint32_t hasher_inode_ex(const InodeEx &inode_ex) {
105 223772 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 453776 StringRef() { length_ = NULL; }
118
119 264 uint16_t length() const { return *length_; }
120 uint16_t size() const { return sizeof(uint16_t) + *length_; }
121 47280 static uint16_t size(const uint16_t length) {
122 47280 return sizeof(uint16_t) + length;
123 }
124 264 char *data() const { return reinterpret_cast<char *>(length_ + 1); }
125 47280 static StringRef Place(const uint16_t length, const char *str, void *addr) {
126 47280 StringRef result;
127 47280 result.length_ = reinterpret_cast<uint16_t *>(addr);
128 47280 *result.length_ = length;
129
2/2
✓ Branch 0 taken 47190 times.
✓ Branch 1 taken 90 times.
47280 if (length > 0)
130 47190 memcpy(result.length_ + 1, str, length);
131 47280 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 946 StringHeap() {
150
1/2
✓ Branch 1 taken 946 times.
✗ Branch 2 not taken.
946 Init(128 * 1024); // 128kB (should be >= 64kB+2B which is largest string)
151 946 }
152
153
1/2
✓ Branch 3 taken 133 times.
✗ Branch 4 not taken.
133 explicit StringHeap(const uint64_t minimum_size) { Init(minimum_size); }
154
155 1079 void Init(const uint64_t minimum_size) {
156 1079 size_ = 0;
157 1079 used_ = 0;
158
159 // Initial bin: 128kB or smallest power of 2 >= minimum size
160 1079 uint64_t pow2_size = 128 * 1024;
161
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 1079 times.
1138 while (pow2_size < minimum_size)
162 59 pow2_size *= 2;
163 1079 AddBin(pow2_size);
164 1079 }
165
166 1076 ~StringHeap() {
167
2/2
✓ Branch 1 taken 1076 times.
✓ Branch 2 taken 1076 times.
2152 for (unsigned i = 0; i < bins_.size(); ++i) {
168 1076 smunmap(bins_.At(i));
169 }
170 1076 }
171
172 47280 StringRef AddString(const uint16_t length, const char *str) {
173 47280 const uint16_t str_size = StringRef::size(length);
174 47280 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 47280 times.
47280 if (remaining_bin_size < str_size) {
177 size_ += remaining_bin_size;
178 AddBin(2 * bin_size_);
179 }
180 47280 StringRef result = StringRef::Place(
181 length, str,
182 47280 static_cast<char *>(bins_.At(bins_.size() - 1)) + bin_used_);
183 47280 size_ += str_size;
184 47280 used_ += str_size;
185 47280 bin_used_ += str_size;
186 47280 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 177 uint64_t GetSizeAlloc() const {
201 177 uint64_t s = bin_size_;
202 177 uint64_t result = 0;
203
2/2
✓ Branch 1 taken 177 times.
✓ Branch 2 taken 177 times.
354 for (unsigned i = 0; i < bins_.size(); ++i) {
204 177 result += s;
205 177 s /= 2;
206 }
207 177 return result;
208 }
209
210 private:
211 1079 void AddBin(const uint64_t size) {
212 1079 void *bin = smmap(size);
213
1/2
✓ Branch 1 taken 1079 times.
✗ Branch 2 not taken.
1079 bins_.PushBack(bin);
214 1079 bin_size_ = size;
215 1079 bin_used_ = 0;
216 1079 }
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 226 Cursor() : idx(0) { }
236 uint32_t idx;
237 };
238
239
240 902 PathStore() {
241
3/6
✓ Branch 2 taken 902 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 902 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 902 times.
✗ Branch 10 not taken.
902 map_.Init(16, shash::Md5(shash::AsciiPtr("!")), hasher_md5);
242
2/4
✓ Branch 1 taken 902 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 902 times.
✗ Branch 5 not taken.
902 string_heap_ = new StringHeap();
243 902 }
244
245
1/2
✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
900 ~PathStore() { delete string_heap_; }
246
247 explicit PathStore(const PathStore &other);
248 PathStore &operator=(const PathStore &other);
249
250 94470 void Insert(const shash::Md5 &md5path, const PathString &path) {
251
1/2
✓ Branch 1 taken 94470 times.
✗ Branch 2 not taken.
94470 PathInfo info;
252
1/2
✓ Branch 1 taken 94470 times.
✗ Branch 2 not taken.
94470 const bool found = map_.Lookup(md5path, &info);
253
2/2
✓ Branch 0 taken 47190 times.
✓ Branch 1 taken 47280 times.
94470 if (found) {
254 47190 info.refcnt++;
255
1/2
✓ Branch 1 taken 47190 times.
✗ Branch 2 not taken.
47190 map_.Insert(md5path, info);
256 47280 return;
257 }
258
259
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 PathInfo new_entry;
260
3/4
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 90 times.
✓ Branch 4 taken 47190 times.
47280 if (path.IsEmpty()) {
261
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 new_entry.name = string_heap_->AddString(0, "");
262
1/2
✓ Branch 1 taken 90 times.
✗ Branch 2 not taken.
90 map_.Insert(md5path, new_entry);
263 90 return;
264 }
265
266
1/2
✓ Branch 1 taken 47190 times.
✗ Branch 2 not taken.
47190 const PathString parent_path = GetParentPath(path);
267
1/2
✓ Branch 3 taken 47190 times.
✗ Branch 4 not taken.
47190 new_entry.parent = shash::Md5(parent_path.GetChars(),
268 parent_path.GetLength());
269
1/2
✓ Branch 1 taken 47190 times.
✗ Branch 2 not taken.
47190 Insert(new_entry.parent, parent_path);
270
271 47190 const uint16_t name_length = path.GetLength() - parent_path.GetLength() - 1;
272 47190 const char *name_str = path.GetChars() + parent_path.GetLength() + 1;
273
1/2
✓ Branch 1 taken 47190 times.
✗ Branch 2 not taken.
47190 new_entry.name = string_heap_->AddString(name_length, name_str);
274
1/2
✓ Branch 1 taken 47190 times.
✗ Branch 2 not taken.
47190 map_.Insert(md5path, new_entry);
275 47190 }
276
277 132 bool Lookup(const shash::Md5 &md5path, PathString *path) {
278
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 PathInfo info;
279
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 bool retval = map_.Lookup(md5path, &info);
280
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 132 times.
132 if (!retval)
281 return false;
282
283
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 88 times.
132 if (info.parent.IsNull())
284 44 return true;
285
286
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 retval = Lookup(info.parent, path);
287
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 assert(retval);
288
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 path->Append("/", 1);
289
1/2
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
88 path->Append(info.name.data(), info.name.length());
290 88 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 226 Cursor BeginEnumerate() { return Cursor(); }
328
329 308 bool Next(Cursor *cursor, shash::Md5 *parent, StringRef *name) {
330 308 const shash::Md5 empty_key = map_.empty_key();
331
2/2
✓ Branch 1 taken 1848 times.
✓ Branch 2 taken 132 times.
1980 while (cursor->idx < map_.capacity()) {
332
2/2
✓ Branch 2 taken 1672 times.
✓ Branch 3 taken 176 times.
1848 if (map_.keys()[cursor->idx] == empty_key) {
333 1672 cursor->idx++;
334 1672 continue;
335 }
336 176 *parent = map_.values()[cursor->idx].parent;
337 176 *name = map_.values()[cursor->idx].name;
338 176 cursor->idx++;
339 176 return true;
340 }
341 132 return false;
342 }
343
344 private:
345 struct PathInfo {
346 406188 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 44222 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 44222 times.
44222 assert(store_.size() < (1LU << 31));
374 44222 const int32_t index = static_cast<int>(store_.size());
375 44222 store_.PushBack(info);
376 44222 return index;
377 }
378
379 // Note that that if the last element is removed, no swap has taken place
380 44177 uint64_t Erase(int32_t index) {
381 44177 struct stat const info_back = store_.At(store_.size() - 1);
382 44177 store_.Replace(index, info_back);
383 44177 store_.SetSize(store_.size() - 1);
384
1/2
✓ Branch 1 taken 44177 times.
✗ Branch 2 not taken.
44177 store_.ShrinkIfOversized();
385 44177 return info_back.st_ino;
386 }
387
388 87780 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 902 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 902 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 902 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 902 times.
✗ Branch 14 not taken.
902 PathMap() { map_.Init(16, shash::Md5(shash::AsciiPtr("!")), hasher_md5); }
401
402 44 bool LookupPath(const shash::Md5 &md5path, PathString *path) {
403 44 const bool found = path_store_.Lookup(md5path, path);
404 44 return found;
405 }
406
407 44 uint64_t LookupInodeByPath(const PathString &path) {
408 uint64_t inode;
409
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 const bool found = map_.Lookup(
410
1/2
✓ Branch 3 taken 44 times.
✗ Branch 4 not taken.
44 shash::Md5(path.GetChars(), path.GetLength()), &inode);
411
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 if (found)
412 44 return inode;
413 return 0;
414 }
415
416 132 uint64_t LookupInodeByMd5Path(const shash::Md5 &md5path) {
417 uint64_t inode;
418
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 const bool found = map_.Lookup(md5path, &inode);
419
1/2
✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
132 if (found)
420 132 return inode;
421 return 0;
422 }
423
424 47280 shash::Md5 Insert(const PathString &path, const uint64_t inode) {
425 47280 shash::Md5 md5path(path.GetChars(), path.GetLength());
426
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 if (!map_.Contains(md5path)) {
427 47280 path_store_.Insert(md5path, path);
428 47280 map_.Insert(md5path, inode);
429 }
430 47280 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 534 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 902 times.
✗ Branch 4 not taken.
902 InodeExMap() { map_.Init(16, InodeEx(), hasher_inode_ex); }
469
470 88 bool LookupMd5Path(InodeEx *inode_ex, shash::Md5 *md5path) {
471 88 const bool found = map_.LookupEx(inode_ex, md5path);
472 88 return found;
473 }
474
475 47280 void Insert(const InodeEx inode_ex, const shash::Md5 &md5path) {
476 47280 map_.Insert(inode_ex, md5path);
477 47280 }
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 226 Cursor() : idx(0) { }
500 uint32_t idx;
501 };
502
503
1/2
✓ Branch 2 taken 902 times.
✗ Branch 3 not taken.
902 InodeReferences() { map_.Init(16, 0, hasher_inode); }
504
505 47280 bool Get(const uint64_t inode, const uint32_t by) {
506 47280 uint32_t refcounter = 0;
507
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 const bool found = map_.Lookup(inode, &refcounter);
508 47280 const bool new_inode = !found;
509 47280 refcounter += by; // This is 0 if the inode is not found
510
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 map_.Insert(inode, refcounter);
511 47280 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 226 Cursor BeginEnumerate() { return Cursor(); }
547
548 141714 bool Next(Cursor *cursor, uint64_t *inode) {
549 141714 const uint64_t empty_key = map_.empty_key();
550
2/2
✓ Branch 1 taken 372792 times.
✓ Branch 2 taken 226 times.
373018 while (cursor->idx < map_.capacity()) {
551
2/2
✓ Branch 1 taken 231304 times.
✓ Branch 2 taken 141488 times.
372792 if (map_.keys()[cursor->idx] == empty_key) {
552 231304 cursor->idx++;
553 231304 continue;
554 }
555 141488 *inode = map_.keys()[cursor->idx];
556 141488 cursor->idx++;
557 141488 return true;
558 }
559 226 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 226 explicit Cursor(const PathStore::Cursor &p,
580 const InodeReferences::Cursor &i)
581 226 : 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 902 Statistics() {
626 902 atomic_init64(&num_inserts);
627 902 atomic_init64(&num_removes);
628 902 atomic_init64(&num_references);
629 902 atomic_init64(&num_hits_inode);
630 902 atomic_init64(&num_hits_path);
631 902 atomic_init64(&num_misses_path);
632 902 }
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 47280 void VfsGetBy(const InodeEx inode_ex, const uint32_t by,
657 const PathString &path) {
658 47280 const uint64_t inode = inode_ex.GetInode();
659 47280 Lock();
660
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 const bool is_new_inode = inode_references_.Get(inode, by);
661
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 const shash::Md5 md5path = path_map_.Insert(path, inode);
662
1/2
✓ Branch 1 taken 47280 times.
✗ Branch 2 not taken.
47280 inode_ex_map_.Insert(inode_ex, md5path);
663 47280 Unlock();
664
665 47280 atomic_xadd64(&statistics_.num_references, by);
666
1/2
✓ Branch 0 taken 47280 times.
✗ Branch 1 not taken.
47280 if (is_new_inode)
667 47280 atomic_inc64(&statistics_.num_inserts);
668 47280 }
669
670 47280 void VfsGet(const InodeEx inode_ex, const PathString &path) {
671 47280 VfsGetBy(inode_ex, 1, path);
672 47280 }
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 88 bool FindDentry(uint64_t ino, uint64_t *parent_ino, NameString *name) {
703 88 PathString path;
704 88 InodeEx inodex(ino, InodeEx::kUnknownType);
705
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 shash::Md5 md5path;
706
707 88 Lock();
708
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 bool found = inode_ex_map_.LookupMd5Path(&inodex, &md5path);
709
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 44 times.
88 if (found) {
710
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 found = path_map_.LookupPath(md5path, &path);
711
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(found);
712
2/4
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
44 *name = GetFileName(path);
713
2/4
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
44 path = GetParentPath(path);
714
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 *parent_ino = path_map_.LookupInodeByPath(path);
715 }
716 88 Unlock();
717 88 return found;
718 88 }
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 226 Cursor BeginEnumerate() {
740 226 Lock();
741 226 return Cursor(path_map_.path_store()->BeginEnumerate(),
742 452 inode_references_.BeginEnumerate());
743 }
744
745 308 bool NextEntry(Cursor *cursor, uint64_t *inode_parent, NameString *name) {
746
1/2
✓ Branch 1 taken 308 times.
✗ Branch 2 not taken.
308 shash::Md5 parent_md5;
747 308 StringRef name_ref;
748
1/2
✓ Branch 2 taken 308 times.
✗ Branch 3 not taken.
308 const bool result = path_map_.path_store()->Next(&(cursor->csr_paths),
749 &parent_md5, &name_ref);
750
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 176 times.
308 if (!result)
751 132 return false;
752
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 132 times.
176 if (parent_md5.IsNull())
753 44 *inode_parent = 0;
754 else
755
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 *inode_parent = path_map_.LookupInodeByMd5Path(parent_md5);
756
1/2
✓ Branch 3 taken 176 times.
✗ Branch 4 not taken.
176 name->Assign(name_ref.data(), name_ref.length());
757 176 return true;
758 }
759
760 141714 bool NextInode(Cursor *cursor, uint64_t *inode) {
761 141714 return inode_references_.Next(&(cursor->csr_inos), inode);
762 }
763
764 226 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 47594 inline void Lock() const {
772 47594 const int retval = pthread_mutex_lock(lock_);
773
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47594 times.
47594 assert(retval == 0);
774 47594 }
775 47594 inline void Unlock() const {
776 47594 const int retval = pthread_mutex_unlock(lock_);
777
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47594 times.
47594 assert(retval == 0);
778 47594 }
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 94384 Entry(uint64_t e, uint64_t p, const char *n)
800 94384 : 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 446 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 728 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 264 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 94472 void Add(const uint64_t inode_parent, const char *name, uint64_t timeout_s) {
836
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 94428 times.
94472 if (!is_active_)
837 44 return;
838
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 94384 times.
94428 if (timeout_s == 0)
839 44 return;
840
841 94384 const uint64_t now = platform_monotonic_time();
842 94384 Lock();
843
1/2
✓ Branch 2 taken 94384 times.
✗ Branch 3 not taken.
94384 entries_.PushBack(Entry(now + timeout_s, inode_parent, name));
844 94384 statistics_.num_insert++;
845 94384 DoPrune(now);
846 94384 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 44 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 95326 inline void Lock() const {
870 95326 const int retval = pthread_mutex_lock(lock_);
871
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 95326 times.
95326 assert(retval == 0);
872 95326 }
873 95326 inline void Unlock() const {
874 95326 const int retval = pthread_mutex_unlock(lock_);
875
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 95326 times.
95326 assert(retval == 0);
876 95326 }
877
878 94742 void DoPrune(uint64_t now) {
879 Entry *entry;
880
3/4
✓ Branch 1 taken 94874 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 94652 times.
✓ Branch 4 taken 222 times.
94874 while (entries_.Peek(&entry)) {
881
2/2
✓ Branch 0 taken 94520 times.
✓ Branch 1 taken 132 times.
94652 if (entry->expiry >= now)
882 94520 break;
883
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 entries_.PopFront();
884 132 statistics_.num_remove++;
885 }
886 94742 statistics_.num_prune++;
887 94742 }
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 9543 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 536 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 410 Statistics()
980 410 : n_insert(0)
981 410 , n_remove(0)
982 410 , n_open_direct(0)
983 410 , n_open_flush(0)
984 410 , 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 88 bool GetInfoIfOpen(uint64_t inode, shash::Any *hash, struct stat *info) {
1008 88 const MutexLockGuard guard(lock_);
1009
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 Entry entry;
1010
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 const bool retval = map_.Lookup(inode, &entry);
1011
3/4
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 44 times.
88 if (retval && (entry.nopen != 0)) {
1012
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 assert(entry.idx_stat >= 0);
1013 44 *hash = entry.hash;
1014
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 if (info != NULL)
1015
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 *info = stat_store_.Get(entry.idx_stat);
1016 44 return true;
1017 }
1018 44 return false;
1019 88 }
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 88 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 44 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