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