GCC Code Coverage Report


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