]> git.ipfire.org Git - thirdparty/ccache.git/blame - src/InodeCache.cpp
feat: Port the inode cache to Windows (#1388)
[thirdparty/ccache.git] / src / InodeCache.cpp
CommitLineData
162f778a 1// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
213d9883
OL
2//
3// See doc/AUTHORS.adoc for a complete list of contributors.
4//
5// This program is free software; you can redistribute it and/or modify it
6// under the terms of the GNU General Public License as published by the Free
7// Software Foundation; either version 3 of the License, or (at your option)
8// any later version.
9//
10// This program is distributed in the hope that it will be useful, but WITHOUT
11// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13// more details.
14//
15// You should have received a copy of the GNU General Public License along with
16// this program; if not, write to the Free Software Foundation, Inc., 51
17// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19#include "InodeCache.hpp"
20
2354adc2 21#include "Config.hpp"
a5021452 22#include "Hash.hpp"
2354adc2 23#include "Util.hpp"
2354adc2 24
c86a4628 25#include <util/DirEntry.hpp>
54cbb06a 26#include <util/Fd.hpp>
f88e6084 27#include <util/Finalizer.hpp>
55ee4572 28#include <util/PathString.hpp>
1da50f6f 29#include <util/TemporaryFile.hpp>
119e8fca 30#include <util/conversion.hpp>
b194ea3a 31#include <util/file.hpp>
ef96a84a 32#include <util/fmtmacros.hpp>
13cb56cd 33#include <util/logging.hpp>
119e8fca 34
ca9ec9cf 35#include <fcntl.h>
55ee4572
TF
36
37#ifndef _WIN32
38# include <libgen.h>
39# include <sched.h>
40# include <unistd.h>
41#endif
65501f48 42
5edcc6c3
JR
43#ifdef HAVE_LINUX_FS_H
44# include <linux/magic.h>
45# include <sys/statfs.h>
46#elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
47# include <sys/mount.h>
48# include <sys/param.h>
49#endif
50
65501f48 51#include <atomic>
2354adc2 52#include <type_traits>
0de51002 53#include <vector>
213d9883 54
213d9883 55// The inode cache resides on a file that is mapped into shared memory by
88699012
JR
56// running processes. It is implemented as a two level structure, where the top
57// level is a hash table consisting of buckets. Each bucket contains entries
213d9883
OL
58// that are sorted in LRU order. Entries map from keys representing files to
59// cached hash results.
60//
61// Concurrent access is guarded by a mutex in each bucket.
62//
63// Current cache size is fixed and the given constants are considered large
64// enough for most projects. The size could be made configurable if there is a
65// demand for it.
213d9883 66
88699012
JR
67namespace {
68
69// The version number corresponds to the format of the cache entries and to
70// semantics of the key fields.
71//
72// Note: The key is hashed using the main hash algorithm, so the version number
73// does not need to be incremented if said algorithm is changed (except if the
74// digest size changes since that affects the entry format).
95e63758 75const uint32_t k_version = 2;
88699012
JR
76
77// Note: Increment the version number if constants affecting storage size are
78// changed.
213d9883
OL
79const uint32_t k_num_buckets = 32 * 1024;
80const uint32_t k_num_entries = 4;
81
6534bb3e
JR
82// Maximum time the spin lock loop will try before giving up.
83const auto k_max_lock_duration = util::Duration(5);
84
de637952
JR
85// The memory-mapped file may reside on a filesystem with compression. Memory
86// accesses to the file risk crashing if such a filesystem gets full, so stop
87// using the inode cache well before this happens.
88const uint64_t k_min_fs_mib_left = 100; // 100 MiB
89
90// How long a filesystem space check is valid before we make a new one.
91const util::Duration k_fs_space_check_valid_duration(1);
92
0e4e4b63 93static_assert(std::tuple_size<Hash::Digest>() == 20,
213d9883 94 "Increment version number if size of digest is changed.");
0e4e4b63 95static_assert(std::is_trivially_copyable<Hash::Digest>::value,
0a189c2c 96 "Digest is expected to be trivially copyable.");
213d9883
OL
97
98static_assert(
561be208 99 static_cast<int>(InodeCache::ContentType::raw) == 0,
213d9883
OL
100 "Numeric value is part of key, increment version number if changed.");
101static_assert(
561be208 102 static_cast<int>(InodeCache::ContentType::checked_for_temporal_macros) == 1,
213d9883
OL
103 "Numeric value is part of key, increment version number if changed.");
104
5edcc6c3
JR
105bool
106fd_is_on_known_to_work_file_system(int fd)
107{
55ee4572 108#ifndef _WIN32
5edcc6c3
JR
109 bool known_to_work = false;
110 struct statfs buf;
111 if (fstatfs(fd, &buf) != 0) {
112 LOG("fstatfs failed: {}", strerror(errno));
113 } else {
55ee4572 114# ifdef HAVE_LINUX_FS_H
bed8a2c1
JR
115 // statfs's f_type field is a signed 32-bit integer on some platforms. Large
116 // values therefore cause narrowing warnings, so cast the value to a large
117 // unsigned type.
118 const auto f_type = static_cast<uintmax_t>(buf.f_type);
119 switch (f_type) {
5edcc6c3
JR
120 // Is a filesystem you know works with the inode cache missing in this
121 // list? Please submit an issue or pull request to the ccache project.
122 case 0x9123683e: // BTRFS_SUPER_MAGIC
123 case 0xef53: // EXT2_SUPER_MAGIC
124 case 0x01021994: // TMPFS_MAGIC
125 case 0x58465342: // XFS_SUPER_MAGIC
126 known_to_work = true;
127 break;
128 default:
129 LOG("Filesystem type 0x{:x} not known to work for the inode cache",
bed8a2c1 130 f_type);
5edcc6c3 131 }
55ee4572 132# elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) // macOS X and some BSDs
5edcc6c3
JR
133 static const std::vector<std::string> known_to_work_filesystems = {
134 // Is a filesystem you know works with the inode cache missing in this
135 // list? Please submit an issue or pull request to the ccache project.
136 "apfs",
95e63758
OS
137 "tmpfs",
138 "ufs",
5edcc6c3 139 "xfs",
95e63758 140 "zfs",
5edcc6c3
JR
141 };
142 if (std::find(known_to_work_filesystems.begin(),
143 known_to_work_filesystems.end(),
144 buf.f_fstypename)
145 != known_to_work_filesystems.end()) {
146 known_to_work = true;
147 } else {
148 LOG("Filesystem type {} not known to work for the inode cache",
149 buf.f_fstypename);
150 }
55ee4572
TF
151# else
152# error Inconsistency: INODE_CACHE_SUPPORTED is set but we should not get here
153# endif
154 }
155 return known_to_work;
5edcc6c3 156#else
55ee4572
TF
157 HANDLE file = (HANDLE)_get_osfhandle(fd);
158 if (file == INVALID_HANDLE_VALUE) {
159 return false;
160 }
161
162 // Try to get information about remote protocol for this file.
163 // If the call succeeds, this is a remote file.
164 // If the call fails with invalid parameter error, consider that it is a local
165 // file
166 FILE_REMOTE_PROTOCOL_INFO infos;
167 if (GetFileInformationByHandleEx(
168 file, FileRemoteProtocolInfo, &infos, sizeof(infos))) {
169 return false;
5edcc6c3 170 }
55ee4572
TF
171
172 if (GetLastError() == ERROR_INVALID_PARAMETER) {
173 return true;
5edcc6c3 174 }
55ee4572
TF
175
176 return false;
177#endif
5edcc6c3
JR
178}
179
95e63758
OS
180bool
181spin_lock(std::atomic<pid_t>& owner_pid, const pid_t self_pid)
182{
183 pid_t prev_pid = 0;
184 pid_t lock_pid = 0;
185 bool reset_timer = false;
186 util::TimePoint lock_time;
162f778a 187 while (true) {
95e63758
OS
188 for (int i = 0; i < 10000; ++i) {
189 lock_pid = owner_pid.load(std::memory_order_relaxed);
190 if (lock_pid == 0
191 && owner_pid.compare_exchange_weak(
192 lock_pid, self_pid, std::memory_order_acquire)) {
162f778a 193 return true;
95e63758
OS
194 }
195
196 if (prev_pid != lock_pid) {
6534bb3e
JR
197 // Check for changing PID here so ABA locking is detected with better
198 // probability.
95e63758
OS
199 prev_pid = lock_pid;
200 reset_timer = true;
201 }
55ee4572 202 std::this_thread::yield();
95e63758 203 }
6534bb3e 204 // If everything is OK, we should never hit this.
95e63758
OS
205 if (reset_timer) {
206 lock_time = util::TimePoint::now();
207 reset_timer = false;
6534bb3e 208 } else if (util::TimePoint::now() - lock_time > k_max_lock_duration) {
162f778a 209 return false;
95e63758
OS
210 }
211 }
212}
213
214void
215spin_unlock(std::atomic<pid_t>& owner_pid)
216{
217 owner_pid.store(0, std::memory_order_release);
218}
219
213d9883
OL
220} // namespace
221
222struct InodeCache::Key
223{
224 ContentType type;
225 dev_t st_dev;
226 ino_t st_ino;
227 mode_t st_mode;
213d9883 228 timespec st_mtim;
213d9883 229 timespec st_ctim; // Included for sanity checking.
bcdaeb5c 230 off_t st_size; // Included for sanity checking.
213d9883
OL
231};
232
233struct InodeCache::Entry
234{
0e4e4b63
JR
235 Hash::Digest key_digest; // Hashed key
236 Hash::Digest file_digest; // Cached file hash
237 int return_value; // Cached return value
213d9883
OL
238};
239
240struct InodeCache::Bucket
241{
95e63758 242 std::atomic<pid_t> owner_pid;
213d9883
OL
243 Entry entries[k_num_entries];
244};
245
246struct InodeCache::SharedRegion
247{
248 uint32_t version;
249 std::atomic<int64_t> hits;
250 std::atomic<int64_t> misses;
251 std::atomic<int64_t> errors;
252 Bucket buckets[k_num_buckets];
253};
254
255bool
256InodeCache::mmap_file(const std::string& inode_cache_file)
257{
55ee4572
TF
258 m_sr = nullptr;
259 m_map.unmap();
54cbb06a 260 m_fd = util::Fd(open(inode_cache_file.c_str(), O_RDWR));
de637952 261 if (!m_fd) {
60904e7b 262 LOG("Failed to open inode cache {}: {}", inode_cache_file, strerror(errno));
213d9883
OL
263 return false;
264 }
de637952 265 if (!fd_is_on_known_to_work_file_system(*m_fd)) {
213d9883
OL
266 return false;
267 }
55ee4572
TF
268
269 auto map = util::MemoryMap::map(*m_fd, sizeof(SharedRegion));
270 if (!map) {
271 LOG("Failed to map inode cache file {}: {}", inode_cache_file, map.error());
213d9883
OL
272 return false;
273 }
55ee4572
TF
274
275 SharedRegion* sr = reinterpret_cast<SharedRegion*>(map->ptr());
276
213d9883
OL
277 // Drop the file from disk if the found version is not matching. This will
278 // allow a new file to be generated.
279 if (sr->version != k_version) {
60904e7b 280 LOG(
b8a69ab8
JR
281 "Dropping inode cache because found version {} does not match expected"
282 " version {}",
213d9883
OL
283 sr->version,
284 k_version);
55ee4572
TF
285 map->unmap();
286 m_fd.close();
213d9883
OL
287 unlink(inode_cache_file.c_str());
288 return false;
289 }
55ee4572 290 m_map = std::move(*map);
213d9883
OL
291 m_sr = sr;
292 if (m_config.debug()) {
908ea60a 293 LOG("Inode cache file loaded: {}", inode_cache_file);
213d9883
OL
294 }
295 return true;
296}
297
298bool
2292fef1
JR
299InodeCache::hash_inode(const std::string& path,
300 ContentType type,
0e4e4b63 301 Hash::Digest& digest)
213d9883 302{
c86a4628
JR
303 util::DirEntry de(path);
304 if (!de.exists()) {
305 LOG("Could not stat {}: {}", path, strerror(de.error_number()));
213d9883
OL
306 return false;
307 }
308
3b6c2a5a
JR
309 // See comment for InodeCache::InodeCache why this check is done.
310 auto now = util::TimePoint::now();
c86a4628 311 if (now - de.ctime() < m_min_age || now - de.mtime() < m_min_age) {
3b6c2a5a
JR
312 LOG("Too new ctime or mtime of {}, not considering for inode cache", path);
313 return false;
314 }
315
213d9883
OL
316 Key key;
317 memset(&key, 0, sizeof(Key));
318 key.type = type;
c86a4628
JR
319 key.st_dev = de.device();
320 key.st_ino = de.inode();
321 key.st_mode = de.mode();
55ee4572
TF
322 // Note: Manually copying sec and nsec of mtime and ctime to prevent copying
323 // the padding bytes
324 auto mtime = de.mtime().to_timespec();
325 key.st_mtim.tv_sec = mtime.tv_sec;
326 key.st_mtim.tv_nsec = mtime.tv_nsec;
327 auto ctime = de.ctime().to_timespec();
328 key.st_ctim.tv_sec = ctime.tv_sec;
329 key.st_ctim.tv_nsec = ctime.tv_nsec;
c86a4628 330 key.st_size = de.size();
213d9883 331
a5021452 332 Hash hash;
0de51002
JR
333 hash.hash(nonstd::span<const uint8_t>(reinterpret_cast<const uint8_t*>(&key),
334 sizeof(key)));
a5021452 335 digest = hash.digest();
213d9883
OL
336 return true;
337}
338
55043100 339bool
0e4e4b63 340InodeCache::with_bucket(const Hash::Digest& key_digest,
55043100 341 const BucketHandler& bucket_handler)
213d9883 342{
55043100 343 uint32_t hash;
119e8fca 344 util::big_endian_to_int(key_digest.data(), hash);
55043100 345 const uint32_t index = hash % k_num_buckets;
213d9883 346 Bucket* bucket = &m_sr->buckets[index];
162f778a
JR
347 bool acquired_lock = spin_lock(bucket->owner_pid, m_self_pid);
348 while (!acquired_lock) {
349 LOG("Dropping inode cache file because of stale mutex at index {}", index);
95e63758 350 if (!drop() || !initialize()) {
55043100 351 return false;
213d9883 352 }
95e63758 353 if (m_config.debug()) {
213d9883 354 ++m_sr->errors;
213d9883 355 }
95e63758 356 bucket = &m_sr->buckets[index];
162f778a 357 acquired_lock = spin_lock(bucket->owner_pid, m_self_pid);
213d9883 358 }
55043100
JR
359 try {
360 bucket_handler(bucket);
361 } catch (...) {
95e63758 362 spin_unlock(bucket->owner_pid);
55043100
JR
363 throw;
364 }
95e63758 365 spin_unlock(bucket->owner_pid);
55043100 366 return true;
213d9883
OL
367}
368
369bool
370InodeCache::create_new_file(const std::string& filename)
371{
213d9883
OL
372 // Create the new file to a temporary name to prevent other processes from
373 // mapping it before it is fully initialized.
1da50f6f
JR
374 auto tmp_file = util::TemporaryFile::create(filename);
375 if (!tmp_file) {
376 LOG("Failed to created inode cache file: {}", tmp_file.error());
377 return false;
378 }
e071bcfd 379
55ee4572
TF
380 util::Finalizer temp_file_remover(
381 [&] { unlink(util::PathString(tmp_file->path).c_str()); });
e071bcfd 382
1da50f6f 383 if (!fd_is_on_known_to_work_file_system(*tmp_file->fd)) {
213d9883
OL
384 return false;
385 }
b194ea3a 386
1da50f6f 387 if (auto result = util::fallocate(*tmp_file->fd, sizeof(SharedRegion));
b194ea3a
JR
388 !result) {
389 LOG("Failed to allocate file space for inode cache: {}", result.error());
213d9883
OL
390 return false;
391 }
55ee4572
TF
392
393 auto map = util::MemoryMap::map(*tmp_file->fd, sizeof(SharedRegion));
394 if (!map) {
395 LOG("Failed to mmap new inode cache: {}", map.error());
213d9883
OL
396 return false;
397 }
398
55ee4572
TF
399 SharedRegion* sr = reinterpret_cast<SharedRegion*>(map->ptr());
400
213d9883
OL
401 // Initialize new shared region.
402 sr->version = k_version;
62a4755c 403 for (auto& bucket : sr->buckets) {
95e63758
OS
404 bucket.owner_pid = 0;
405 memset(bucket.entries, 0, sizeof(Bucket::entries));
213d9883
OL
406 }
407
55ee4572
TF
408 sr = nullptr;
409 map->unmap();
1da50f6f 410 tmp_file->fd.close();
213d9883 411
55ee4572 412#ifndef _WIN32
213d9883
OL
413 // link() will fail silently if a file with the same name already exists.
414 // This will be the case if two processes try to create a new file
415 // simultaneously. Thus close the current file handle and reopen a new one,
416 // which will make us use the first created file even if we didn't win the
417 // race.
1da50f6f 418 if (link(tmp_file->path.c_str(), filename.c_str()) != 0) {
60904e7b 419 LOG("Failed to link new inode cache: {}", strerror(errno));
213d9883
OL
420 return false;
421 }
55ee4572
TF
422#else
423 if (MoveFileA(util::PathString(tmp_file->path).c_str(), filename.c_str())
424 == 0) {
425 unsigned error = GetLastError();
426 if (error == ERROR_FILE_EXISTS) {
427 // Not an error, another process won the race. Remove the file we just
428 // created
429 DeleteFileA(util::PathString(tmp_file->path).c_str());
430 LOG("Another process created inode cache {}", filename);
431 return true;
432 } else {
433 LOG("Failed to move new inode cache: {}", error);
434 return false;
435 }
436 }
437#endif
213d9883 438
292b39dd 439 LOG("Created a new inode cache {}", filename);
213d9883
OL
440 return true;
441}
442
443bool
444InodeCache::initialize()
445{
446 if (m_failed || !m_config.inode_cache()) {
447 return false;
448 }
449
de637952
JR
450 if (m_fd) {
451 auto now = util::TimePoint::now();
452 if (now > m_last_fs_space_check + k_fs_space_check_valid_duration) {
453 m_last_fs_space_check = now;
454
55ee4572
TF
455 uint64_t free_space = 0;
456#ifndef _WIN32
de637952
JR
457 struct statfs buf;
458 if (fstatfs(*m_fd, &buf) != 0) {
459 LOG("fstatfs failed: {}", strerror(errno));
460 return false;
461 }
55ee4572
TF
462 free_space = static_cast<uint64_t>(buf.f_bavail) * 512;
463#else
464 ULARGE_INTEGER free_space_for_user{};
465
466 if (GetDiskFreeSpaceExA(m_config.temporary_dir().c_str(),
467 &free_space_for_user,
468 nullptr,
469 nullptr)
470 == 0) {
471 LOG("GetDiskFreeSpaceExA failed: {}", GetLastError());
472 return false;
473 }
474 free_space = free_space_for_user.QuadPart;
475#endif
476 if (free_space < k_min_fs_mib_left * 1024 * 1024) {
de637952
JR
477 LOG("Filesystem has less than {} MiB free space, not using inode cache",
478 k_min_fs_mib_left);
479 return false;
480 }
481 }
482 }
483
213d9883
OL
484 if (m_sr) {
485 return true;
486 }
487
488 std::string filename = get_file();
489 if (m_sr || mmap_file(filename)) {
490 return true;
491 }
492
493 // Try to create a new cache if we failed to map an existing file.
494 create_new_file(filename);
495
496 // Concurrent processes could try to create new files simultaneously and the
497 // file that actually landed on disk will be from the process that won the
498 // race. Thus we try to open the file from disk instead of reusing the file
499 // handle to the file we just created.
500 if (mmap_file(filename)) {
501 return true;
502 }
503
504 m_failed = true;
505 return false;
506}
507
3b6c2a5a
JR
508InodeCache::InodeCache(const Config& config, util::Duration min_age)
509 : m_config(config),
510 // CCACHE_DISABLE_INODE_CACHE_MIN_AGE is only for testing purposes; see
511 // test/suites/inode_cache.bash.
512 m_min_age(getenv("CCACHE_DISABLE_INODE_CACHE_MIN_AGE") ? util::Duration(0)
95e63758
OS
513 : min_age),
514 m_self_pid(getpid())
213d9883
OL
515{
516}
517
518InodeCache::~InodeCache()
519{
520 if (m_sr) {
bf2c2cc6
JR
521 LOG("Accumulated stats for inode cache: hits={}, misses={}, errors={}",
522 m_sr->hits.load(),
523 m_sr->misses.load(),
524 m_sr->errors.load());
213d9883
OL
525 }
526}
527
5edcc6c3
JR
528bool
529InodeCache::available(int fd)
530{
531 return fd_is_on_known_to_work_file_system(fd);
532}
533
cd4e26df
JR
534std::optional<std::pair<HashSourceCodeResult, Hash::Digest>>
535InodeCache::get(const std::string& path, ContentType type)
213d9883
OL
536{
537 if (!initialize()) {
a179db20 538 return std::nullopt;
213d9883
OL
539 }
540
0e4e4b63 541 Hash::Digest key_digest;
0a189c2c 542 if (!hash_inode(path, type, key_digest)) {
a179db20 543 return std::nullopt;
213d9883
OL
544 }
545
a179db20 546 std::optional<HashSourceCodeResult> result;
cd4e26df 547 Hash::Digest file_digest;
547cef6c 548 const bool success = with_bucket(key_digest, [&](const auto bucket) {
55043100
JR
549 for (uint32_t i = 0; i < k_num_entries; ++i) {
550 if (bucket->entries[i].key_digest == key_digest) {
551 if (i > 0) {
552 Entry tmp = bucket->entries[i];
553 memmove(&bucket->entries[1], &bucket->entries[0], sizeof(Entry) * i);
554 bucket->entries[0] = tmp;
555 }
556
557 file_digest = bucket->entries[0].file_digest;
a179db20
JR
558 result =
559 HashSourceCodeResult::from_bitmask(bucket->entries[0].return_value);
55043100 560 break;
213d9883 561 }
213d9883 562 }
55043100
JR
563 });
564 if (!success) {
a179db20 565 return std::nullopt;
213d9883 566 }
213d9883 567
213d9883 568 if (m_config.debug()) {
a179db20
JR
569 LOG("Inode cache {}: {}", result ? "hit" : "miss", path);
570 if (result) {
213d9883
OL
571 ++m_sr->hits;
572 } else {
573 ++m_sr->misses;
574 }
213d9883 575 }
cd4e26df
JR
576 if (result) {
577 return std::make_pair(*result, file_digest);
578 } else {
579 return std::nullopt;
580 }
213d9883
OL
581}
582
583bool
2292fef1 584InodeCache::put(const std::string& path,
213d9883 585 ContentType type,
0e4e4b63 586 const Hash::Digest& file_digest,
38ab9d38 587 HashSourceCodeResult return_value)
213d9883
OL
588{
589 if (!initialize()) {
590 return false;
591 }
592
0e4e4b63 593 Hash::Digest key_digest;
0a189c2c 594 if (!hash_inode(path, type, key_digest)) {
213d9883
OL
595 return false;
596 }
597
547cef6c 598 const bool success = with_bucket(key_digest, [&](const auto bucket) {
55043100
JR
599 memmove(&bucket->entries[1],
600 &bucket->entries[0],
601 sizeof(Entry) * (k_num_entries - 1));
602
603 bucket->entries[0].key_digest = key_digest;
604 bucket->entries[0].file_digest = file_digest;
38ab9d38 605 bucket->entries[0].return_value = return_value.to_bitmask();
55043100 606 });
213d9883 607
55043100 608 if (!success) {
213d9883
OL
609 return false;
610 }
611
22ff4a0e
JR
612 if (m_config.debug()) {
613 LOG("Inode cache insert: {}", path);
614 }
213d9883
OL
615 return true;
616}
617
618bool
619InodeCache::drop()
620{
55ee4572
TF
621 m_sr = nullptr;
622 m_map.unmap();
623 m_fd.close();
213d9883 624 std::string file = get_file();
95e63758 625 if (unlink(file.c_str()) != 0 && errno != ENOENT) {
213d9883
OL
626 return false;
627 }
292b39dd 628 LOG("Dropped inode cache {}", file);
213d9883
OL
629 return true;
630}
631
632std::string
633InodeCache::get_file()
634{
14ef9790
JR
635 const uint8_t arch_bits = 8 * sizeof(void*);
636 return FMT(
637 "{}/inode-cache-{}.v{}", m_config.temporary_dir(), arch_bits, k_version);
213d9883
OL
638}
639
640int64_t
641InodeCache::get_hits()
642{
643 return initialize() ? m_sr->hits.load() : -1;
644}
645
646int64_t
647InodeCache::get_misses()
648{
649 return initialize() ? m_sr->misses.load() : -1;
650}
651
652int64_t
653InodeCache::get_errors()
654{
655 return initialize() ? m_sr->errors.load() : -1;
656}