]> git.ipfire.org Git - thirdparty/ccache.git/blame - src/InodeCache.cpp
C++-ify is_precompiled_header and move to Util
[thirdparty/ccache.git] / src / InodeCache.cpp
CommitLineData
213d9883
OL
1// Copyright (C) 2020 Joel Rosdahl and other contributors
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
JR
21#include "Config.hpp"
22#include "Fd.hpp"
23#include "Finalizer.hpp"
a5021452 24#include "Hash.hpp"
2354adc2 25#include "Stat.hpp"
53b35a42 26#include "TemporaryFile.hpp"
2354adc2
JR
27#include "Util.hpp"
28#include "ccache.hpp"
2354adc2
JR
29#include "logging.hpp"
30
31#include <atomic>
32#include <libgen.h>
33#include <sys/mman.h>
34#include <type_traits>
213d9883 35
213d9883 36// The inode cache resides on a file that is mapped into shared memory by
88699012
JR
37// running processes. It is implemented as a two level structure, where the top
38// level is a hash table consisting of buckets. Each bucket contains entries
213d9883
OL
39// that are sorted in LRU order. Entries map from keys representing files to
40// cached hash results.
41//
42// Concurrent access is guarded by a mutex in each bucket.
43//
44// Current cache size is fixed and the given constants are considered large
45// enough for most projects. The size could be made configurable if there is a
46// demand for it.
213d9883 47
88699012
JR
48namespace {
49
50// The version number corresponds to the format of the cache entries and to
51// semantics of the key fields.
52//
53// Note: The key is hashed using the main hash algorithm, so the version number
54// does not need to be incremented if said algorithm is changed (except if the
55// digest size changes since that affects the entry format).
56const uint32_t k_version = 1;
57
58// Note: Increment the version number if constants affecting storage size are
59// changed.
213d9883
OL
60const uint32_t k_num_buckets = 32 * 1024;
61const uint32_t k_num_entries = 4;
62
0a189c2c 63static_assert(Digest::size() == 20,
213d9883 64 "Increment version number if size of digest is changed.");
0a189c2c
JR
65static_assert(std::is_trivially_copyable<Digest>::value,
66 "Digest is expected to be trivially copyable.");
213d9883
OL
67
68static_assert(
69 static_cast<int>(InodeCache::ContentType::binary) == 0,
70 "Numeric value is part of key, increment version number if changed.");
71static_assert(
72 static_cast<int>(InodeCache::ContentType::code) == 1,
73 "Numeric value is part of key, increment version number if changed.");
74static_assert(
75 static_cast<int>(InodeCache::ContentType::code_with_sloppy_time_macros) == 2,
76 "Numeric value is part of key, increment version number if changed.");
77static_assert(
78 static_cast<int>(InodeCache::ContentType::precompiled_header) == 3,
79 "Numeric value is part of key, increment version number if changed.");
80
81} // namespace
82
83struct InodeCache::Key
84{
85 ContentType type;
86 dev_t st_dev;
87 ino_t st_ino;
88 mode_t st_mode;
2354adc2 89#ifdef HAVE_STRUCT_STAT_ST_MTIM
213d9883 90 timespec st_mtim;
2354adc2 91#else
213d9883 92 time_t st_mtim;
2354adc2
JR
93#endif
94#ifdef HAVE_STRUCT_STAT_ST_CTIM
213d9883 95 timespec st_ctim; // Included for sanity checking.
2354adc2 96#else
213d9883 97 time_t st_ctim; // Included for sanity checking.
2354adc2 98#endif
213d9883
OL
99 off_t st_size; // Included for sanity checking.
100 bool sloppy_time_macros;
101};
102
103struct InodeCache::Entry
104{
0a189c2c
JR
105 Digest key_digest; // Hashed key
106 Digest file_digest; // Cached file hash
213d9883
OL
107 int return_value; // Cached return value
108};
109
110struct InodeCache::Bucket
111{
112 pthread_mutex_t mt;
113 Entry entries[k_num_entries];
114};
115
116struct InodeCache::SharedRegion
117{
118 uint32_t version;
119 std::atomic<int64_t> hits;
120 std::atomic<int64_t> misses;
121 std::atomic<int64_t> errors;
122 Bucket buckets[k_num_buckets];
123};
124
125bool
126InodeCache::mmap_file(const std::string& inode_cache_file)
127{
128 if (m_sr) {
129 munmap(m_sr, sizeof(SharedRegion));
130 m_sr = nullptr;
131 }
e071bcfd
JR
132 Fd fd(open(inode_cache_file.c_str(), O_RDWR));
133 if (!fd) {
213d9883
OL
134 cc_log("Failed to open inode cache %s: %s",
135 inode_cache_file.c_str(),
136 strerror(errno));
137 return false;
138 }
139 bool is_nfs;
e071bcfd 140 if (Util::is_nfs_fd(*fd, &is_nfs) == 0 && is_nfs) {
213d9883
OL
141 cc_log(
142 "Inode cache not supported because the cache file is located on nfs: %s",
143 inode_cache_file.c_str());
213d9883
OL
144 return false;
145 }
146 SharedRegion* sr = reinterpret_cast<SharedRegion*>(mmap(
e071bcfd
JR
147 nullptr, sizeof(SharedRegion), PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0));
148 fd.close();
213d9883
OL
149 if (sr == reinterpret_cast<void*>(-1)) {
150 cc_log("Failed to mmap %s: %s", inode_cache_file.c_str(), strerror(errno));
151 return false;
152 }
153 // Drop the file from disk if the found version is not matching. This will
154 // allow a new file to be generated.
155 if (sr->version != k_version) {
156 cc_log(
77bf7bc3
JR
157 "Dropping inode cache because found version %u does not match expected"
158 " version %u",
213d9883
OL
159 sr->version,
160 k_version);
161 munmap(sr, sizeof(SharedRegion));
162 unlink(inode_cache_file.c_str());
163 return false;
164 }
165 m_sr = sr;
166 if (m_config.debug()) {
167 cc_log("inode cache file loaded: %s", inode_cache_file.c_str());
168 }
169 return true;
170}
171
172bool
0a189c2c 173InodeCache::hash_inode(const char* path, ContentType type, Digest& digest)
213d9883
OL
174{
175 Stat stat = Stat::stat(path);
176 if (!stat) {
177 cc_log("Could not stat %s: %s", path, strerror(stat.error_number()));
178 return false;
179 }
180
181 Key key;
182 memset(&key, 0, sizeof(Key));
183 key.type = type;
184 key.st_dev = stat.device();
185 key.st_ino = stat.inode();
186 key.st_mode = stat.mode();
2354adc2 187#ifdef HAVE_STRUCT_STAT_ST_MTIM
213d9883 188 key.st_mtim = stat.mtim();
2354adc2 189#else
213d9883 190 key.st_mtim = stat.mtime();
2354adc2
JR
191#endif
192#ifdef HAVE_STRUCT_STAT_ST_CTIM
213d9883 193 key.st_ctim = stat.ctim();
2354adc2 194#else
213d9883 195 key.st_ctim = stat.ctime();
2354adc2 196#endif
213d9883
OL
197 key.st_size = stat.size();
198
a5021452
JR
199 Hash hash;
200 hash.hash(&key, sizeof(Key));
201 digest = hash.digest();
213d9883
OL
202 return true;
203}
204
205InodeCache::Bucket*
206InodeCache::acquire_bucket(uint32_t index)
207{
208 Bucket* bucket = &m_sr->buckets[index];
209 int err = pthread_mutex_lock(&bucket->mt);
2354adc2 210#ifdef PTHREAD_MUTEX_ROBUST
213d9883 211 if (err == EOWNERDEAD) {
77bf7bc3 212 if (m_config.debug()) {
213d9883 213 ++m_sr->errors;
77bf7bc3 214 }
213d9883
OL
215 err = pthread_mutex_consistent(&bucket->mt);
216 if (err) {
217 cc_log(
218 "Can't consolidate stale mutex at index %u: %s", index, strerror(err));
219 cc_log("Consider removing the inode cache file if the problem persists");
220 return nullptr;
221 }
222 cc_log("Wiping bucket at index %u because of stale mutex", index);
223 memset(bucket->entries, 0, sizeof(Bucket::entries));
224 } else {
2354adc2 225#endif
213d9883
OL
226 if (err) {
227 cc_log("Failed to lock mutex at index %u: %s", index, strerror(err));
228 cc_log("Consider removing the inode cache file if problem persists");
229 ++m_sr->errors;
230 return nullptr;
231 }
2354adc2 232#ifdef PTHREAD_MUTEX_ROBUST
213d9883 233 }
2354adc2 234#endif
213d9883
OL
235 return bucket;
236}
237
238InodeCache::Bucket*
0a189c2c 239InodeCache::acquire_bucket(const Digest& key_digest)
213d9883
OL
240{
241 uint32_t hash;
0a189c2c 242 Util::big_endian_to_int(key_digest.bytes(), hash);
213d9883
OL
243 return acquire_bucket(hash % k_num_buckets);
244}
245
246void
247InodeCache::release_bucket(Bucket* bucket)
248{
249 pthread_mutex_unlock(&bucket->mt);
250}
251
252bool
253InodeCache::create_new_file(const std::string& filename)
254{
255 cc_log("Creating a new inode cache");
256
257 // Create the new file to a temporary name to prevent other processes from
258 // mapping it before it is fully initialized.
53b35a42 259 TemporaryFile tmp_file(filename);
e071bcfd 260
53b35a42 261 Finalizer temp_file_remover([&] { unlink(tmp_file.path.c_str()); });
e071bcfd 262
213d9883 263 bool is_nfs;
53b35a42 264 if (Util::is_nfs_fd(*tmp_file.fd, &is_nfs) == 0 && is_nfs) {
213d9883 265 cc_log(
77bf7bc3
JR
266 "Inode cache not supported because the cache file would be located on"
267 " nfs: %s",
213d9883 268 filename.c_str());
213d9883
OL
269 return false;
270 }
53b35a42 271 int err = Util::fallocate(*tmp_file.fd, sizeof(SharedRegion));
213d9883
OL
272 if (err) {
273 cc_log("Failed to allocate file space for inode cache: %s", strerror(err));
213d9883
OL
274 return false;
275 }
276 SharedRegion* sr =
277 reinterpret_cast<SharedRegion*>(mmap(nullptr,
278 sizeof(SharedRegion),
279 PROT_READ | PROT_WRITE,
280 MAP_SHARED,
53b35a42 281 *tmp_file.fd,
213d9883
OL
282 0));
283 if (sr == reinterpret_cast<void*>(-1)) {
284 cc_log("Failed to mmap new inode cache: %s", strerror(errno));
213d9883
OL
285 return false;
286 }
287
288 // Initialize new shared region.
289 sr->version = k_version;
290 pthread_mutexattr_t mattr;
291 pthread_mutexattr_init(&mattr);
292 pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
2354adc2 293#ifdef PTHREAD_MUTEX_ROBUST
213d9883 294 pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
2354adc2 295#endif
62a4755c
JR
296 for (auto& bucket : sr->buckets) {
297 pthread_mutex_init(&bucket.mt, &mattr);
213d9883
OL
298 }
299
300 munmap(sr, sizeof(SharedRegion));
53b35a42 301 tmp_file.fd.close();
213d9883
OL
302
303 // link() will fail silently if a file with the same name already exists.
304 // This will be the case if two processes try to create a new file
305 // simultaneously. Thus close the current file handle and reopen a new one,
306 // which will make us use the first created file even if we didn't win the
307 // race.
53b35a42 308 if (link(tmp_file.path.c_str(), filename.c_str()) != 0) {
213d9883 309 cc_log("Failed to link new inode cache: %s", strerror(errno));
213d9883
OL
310 return false;
311 }
312
213d9883
OL
313 return true;
314}
315
316bool
317InodeCache::initialize()
318{
319 if (m_failed || !m_config.inode_cache()) {
320 return false;
321 }
322
323 if (m_sr) {
324 return true;
325 }
326
327 std::string filename = get_file();
328 if (m_sr || mmap_file(filename)) {
329 return true;
330 }
331
332 // Try to create a new cache if we failed to map an existing file.
333 create_new_file(filename);
334
335 // Concurrent processes could try to create new files simultaneously and the
336 // file that actually landed on disk will be from the process that won the
337 // race. Thus we try to open the file from disk instead of reusing the file
338 // handle to the file we just created.
339 if (mmap_file(filename)) {
340 return true;
341 }
342
343 m_failed = true;
344 return false;
345}
346
77bf7bc3 347InodeCache::InodeCache(const Config& config) : m_config(config)
213d9883
OL
348{
349}
350
351InodeCache::~InodeCache()
352{
353 if (m_sr) {
354 munmap(m_sr, sizeof(SharedRegion));
355 }
356}
357
358bool
359InodeCache::get(const char* path,
360 ContentType type,
0a189c2c 361 Digest& file_digest,
213d9883
OL
362 int* return_value)
363{
364 if (!initialize()) {
365 return false;
366 }
367
0a189c2c
JR
368 Digest key_digest;
369 if (!hash_inode(path, type, key_digest)) {
213d9883
OL
370 return false;
371 }
372
373 Bucket* bucket = acquire_bucket(key_digest);
374
375 if (!bucket) {
376 return false;
377 }
378
379 bool found = false;
380
381 for (uint32_t i = 0; i < k_num_entries; ++i) {
0a189c2c 382 if (bucket->entries[i].key_digest == key_digest) {
213d9883
OL
383 if (i > 0) {
384 Entry tmp = bucket->entries[i];
385 memmove(&bucket->entries[1], &bucket->entries[0], sizeof(Entry) * i);
386 bucket->entries[0] = tmp;
387 }
388
0a189c2c 389 file_digest = bucket->entries[0].file_digest;
213d9883
OL
390 if (return_value) {
391 *return_value = bucket->entries[0].return_value;
392 }
393 found = true;
394 break;
395 }
396 }
397 release_bucket(bucket);
398
399 cc_log("inode cache %s: %s", found ? "hit" : "miss", path);
400
401 if (m_config.debug()) {
402 if (found) {
403 ++m_sr->hits;
404 } else {
405 ++m_sr->misses;
406 }
407 cc_log(
408 "accumulated stats for inode cache: hits=%ld, misses=%ld, errors=%ld",
409 static_cast<long>(m_sr->hits.load()),
410 static_cast<long>(m_sr->misses.load()),
411 static_cast<long>(m_sr->errors.load()));
412 }
413 return found;
414}
415
416bool
417InodeCache::put(const char* path,
418 ContentType type,
0a189c2c 419 const Digest& file_digest,
213d9883
OL
420 int return_value)
421{
422 if (!initialize()) {
423 return false;
424 }
425
0a189c2c
JR
426 Digest key_digest;
427 if (!hash_inode(path, type, key_digest)) {
213d9883
OL
428 return false;
429 }
430
431 Bucket* bucket = acquire_bucket(key_digest);
432
433 if (!bucket) {
434 return false;
435 }
436
437 memmove(&bucket->entries[1],
438 &bucket->entries[0],
439 sizeof(Entry) * (k_num_entries - 1));
440
441 bucket->entries[0].key_digest = key_digest;
442 bucket->entries[0].file_digest = file_digest;
443 bucket->entries[0].return_value = return_value;
444
445 release_bucket(bucket);
446
447 cc_log("inode cache insert: %s", path);
448
449 return true;
450}
451
452bool
453InodeCache::drop()
454{
455 std::string file = get_file();
456 if (unlink(file.c_str()) != 0) {
457 return false;
458 }
459 if (m_sr) {
460 munmap(m_sr, sizeof(SharedRegion));
461 m_sr = nullptr;
462 }
463 return true;
464}
465
466std::string
467InodeCache::get_file()
468{
469 return fmt::format("{}/inode-cache.v{}", m_config.temporary_dir(), k_version);
470}
471
472int64_t
473InodeCache::get_hits()
474{
475 return initialize() ? m_sr->hits.load() : -1;
476}
477
478int64_t
479InodeCache::get_misses()
480{
481 return initialize() ? m_sr->misses.load() : -1;
482}
483
484int64_t
485InodeCache::get_errors()
486{
487 return initialize() ? m_sr->errors.load() : -1;
488}