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 |