GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_posix.cc
Date: 2026-10-25 02:40:14
Exec Total Coverage
Lines: 297 339 87.6%
Branches: 176 279 63.1%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * The cache module maintains the local file cache. Files are
5 * staged into the cache by Fetch(). The cache stores files with a name
6 * according to their content hash.
7 *
8 * The procedure is
9 * -# Look in the catalog for content hash
10 * -# If it is in local cache: return file descriptor
11 * -# Otherwise download, store in cache and return fd
12 *
13 * Each running CVMFS instance has to have a separate cache directory.
14 * The local cache directory (directories 00..ff) can be accessed
15 * in parallel to a running CVMFS, i.e. files can be deleted for instance
16 * anytime. However, this will confuse the cache database managed by the lru
17 * module.
18 *
19 * Files are created in txn directory first. At the very latest
20 * point they are renamed into their "real" content hash names atomically by
21 * rename(). This concept is taken over from GROW-FS.
22 *
23 * Identical URLs won't be concurrently downloaded. The first thread performs
24 * the download and informs the other, waiting threads on pipes.
25 */
26
27 #include "cache_posix.h"
28
29 #include <dirent.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <inttypes.h>
33 #include <pthread.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <memory>
38 #ifndef __APPLE__
39 #include <sys/statfs.h>
40 #endif
41 #include <unistd.h>
42
43 #include <algorithm>
44 #include <atomic>
45 #include <cassert>
46 #include <cstdio>
47 #include <cstdlib>
48 #include <cstring>
49
50 #include "crypto/hash.h"
51 #include "manifest.h"
52 #include "manifest_fetch.h"
53 #include "quota.h"
54 #include "util/logging.h"
55 #include "util/mutex.h"
56 #include "util/platform.h"
57 #include "util/posix.h"
58 #include "util/smalloc.h"
59
60 using namespace std; // NOLINT
61
62 namespace {
63
64 /**
65 * A CallGuard object can be placed at the beginning of a function. It counts
66 * the number of so-annotated functions that are in flight. The Drainout() call
67 * will wait until all functions that have been called so far are finished.
68 *
69 * The class is used in order to wait for remaining calls when switching into
70 * the read-only cache mode.
71 */
72 class CallGuard {
73 public:
74 CallGuard() {
75 const int32_t global_drainout = global_drainout_.load();
76 drainout_ = (global_drainout != 0);
77 if (!drainout_)
78 num_inflight_calls_.fetch_add(1);
79 }
80 ~CallGuard() {
81 if (!drainout_)
82 num_inflight_calls_.fetch_sub(1);
83 }
84 static void Drainout() {
85 int32_t expected_val = 0;
86 global_drainout_.compare_exchange_strong(expected_val, 1);
87 while (num_inflight_calls_.load() != 0)
88 SafeSleepMs(50);
89 }
90
91 private:
92 bool drainout_;
93 static std::atomic<int32_t> global_drainout_;
94 static std::atomic<int32_t> num_inflight_calls_;
95 };
96 std::atomic<int32_t> CallGuard::num_inflight_calls_(0);
97 std::atomic<int32_t> CallGuard::global_drainout_(0);
98
99 } // anonymous namespace
100
101
102 //------------------------------------------------------------------------------
103
104
105 const uint64_t PosixCacheManager::kBigFile = 25 * 1024 * 1024; // 25M
106
107
108 634 int PosixCacheManager::AbortTxn(void *txn) {
109 634 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
110 634 LogCvmfs(kLogCache, kLogDebug, "abort %s", transaction->tmp_path.c_str());
111 634 close(transaction->fd);
112 634 const int result = unlink(transaction->tmp_path.c_str());
113 634 transaction->~Transaction();
114 634 no_inflight_txns_.fetch_sub(1);
115
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 556 times.
634 if (result == -1)
116 78 return -errno;
117 556 return 0;
118 }
119
120
121 /**
122 * This should only be used to replace the default NoopQuotaManager by a
123 * PosixQuotaManager. The cache manager takes the ownership of the passed
124 * quota manager.
125 */
126 1615 bool PosixCacheManager::AcquireQuotaManager(QuotaManager *quota_mgr) {
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1615 times.
1615 if (quota_mgr == NULL)
128 ✗ return false;
129
1/2
✓ Branch 0 taken 1615 times.
✗ Branch 1 not taken.
1615 delete quota_mgr_;
130 1615 quota_mgr_ = quota_mgr;
131 1615 return true;
132 }
133
134
135 2221 int PosixCacheManager::Close(int fd) {
136
1/2
✓ Branch 0 taken 2221 times.
✗ Branch 1 not taken.
2221 const int retval = do_refcount_ ? fd_mgr_->Close(fd) : close(fd);
137
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 2182 times.
2221 if (retval != 0)
138 39 return -errno;
139 2182 return 0;
140 }
141
142
143 5015 int PosixCacheManager::CommitTxn(void *txn) {
144 5015 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
145 int result;
146 5015 LogCvmfs(kLogCache, kLogDebug, "commit %s %s",
147 transaction->final_path.c_str(), transaction->tmp_path.c_str());
148
149 5015 result = Flush(transaction);
150 5015 close(transaction->fd);
151
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 4976 times.
5015 if (result < 0) {
152 39 unlink(transaction->tmp_path.c_str());
153 39 transaction->~Transaction();
154 39 no_inflight_txns_.fetch_sub(1);
155 39 return result;
156 }
157
158 // To support debugging, move files into quarantine on file size mismatch
159
2/2
✓ Branch 0 taken 863 times.
✓ Branch 1 taken 4113 times.
4976 if (transaction->size != transaction->expected_size) {
160 // Allow size to be zero if alien cache, because hadoop-fuse-dfs returns
161 // size zero for a while
162
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 807 times.
863 if ((transaction->expected_size != kSizeUnknown)
163
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
56 && (reports_correct_filesize_ || (transaction->size != 0))) {
164
1/3
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
56 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
165 "size check failure for %s, expected %lu, got %lu",
166 112 transaction->id.ToString().c_str(), transaction->expected_size,
167 transaction->size);
168
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 CopyPath2Path(transaction->tmp_path,
169
2/4
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
112 cache_path_ + "/quarantaine/" + transaction->id.ToString());
170 56 unlink(transaction->tmp_path.c_str());
171 56 transaction->~Transaction();
172 56 no_inflight_txns_.fetch_sub(1);
173 56 return -EIO;
174 }
175 }
176
177
2/2
✓ Branch 0 taken 4881 times.
✓ Branch 1 taken 39 times.
4920 if ((transaction->label.flags & kLabelPinned)
178
2/2
✓ Branch 0 taken 846 times.
✓ Branch 1 taken 4035 times.
4881 || (transaction->label.flags & kLabelCatalog)) {
179 2655 const bool retval = quota_mgr_->Pin(
180
1/2
✓ Branch 1 taken 885 times.
✗ Branch 2 not taken.
885 transaction->id, transaction->size, transaction->label.GetDescription(),
181 885 (transaction->label.flags & kLabelCatalog));
182
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 846 times.
885 if (!retval) {
183
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 LogCvmfs(kLogCache, kLogDebug, "commit failed: cannot pin %s",
184 78 transaction->id.ToString().c_str());
185 39 unlink(transaction->tmp_path.c_str());
186 39 transaction->~Transaction();
187 39 no_inflight_txns_.fetch_sub(1);
188 39 return -ENOSPC;
189 }
190 }
191
192 // Move the temporary file into its final location
193
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 4803 times.
4881 if (alien_cache_) {
194 78 const int retval = chmod(transaction->tmp_path.c_str(), 0660);
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 assert(retval == 0);
196 }
197 4881 result = Rename(transaction->tmp_path.c_str(),
198 transaction->final_path.c_str());
199
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 4803 times.
4881 if (result < 0) {
200 78 LogCvmfs(kLogCache, kLogDebug, "commit failed: %s", strerror(errno));
201 78 unlink(transaction->tmp_path.c_str());
202
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 if ((transaction->label.flags & kLabelPinned)
203
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 39 times.
78 || (transaction->label.flags & kLabelCatalog)) {
204 39 quota_mgr_->Remove(transaction->id);
205 }
206 } else {
207 // Success, inform quota manager
208
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 4764 times.
4803 if (transaction->label.flags & kLabelVolatile) {
209
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 quota_mgr_->InsertVolatile(transaction->id, transaction->size,
210 78 transaction->label.GetDescription());
211 4764 } else if (!transaction->label.IsCatalog()
212
6/6
✓ Branch 0 taken 3996 times.
✓ Branch 1 taken 768 times.
✓ Branch 3 taken 3957 times.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 3957 times.
✓ Branch 6 taken 807 times.
4764 && !transaction->label.IsPinned()) {
213
1/2
✓ Branch 1 taken 3957 times.
✗ Branch 2 not taken.
3957 quota_mgr_->Insert(transaction->id, transaction->size,
214 7914 transaction->label.GetDescription());
215 }
216 }
217 4881 transaction->~Transaction();
218 4881 no_inflight_txns_.fetch_sub(1);
219 4881 return result;
220 }
221
222 4931 bool PosixCacheManager::InitCacheDirectory(const string &cache_path) {
223 // For an alien cache the directory skeleton is created lazily on the first
224 // write (see EnsureCacheDirectories), so that merely trying to mount a bogus
225 // fqrn -- e.g. a stray access to /cvmfs/<typo> -- does not leave empty
226 // directories behind in a per-fqrn alien cache (see #4217). A regular cache
227 // coincides with the workspace and backs the quota manager, which scans the
228 // 00..ff directories at start-up, so it is created eagerly.
229
2/2
✓ Branch 0 taken 3699 times.
✓ Branch 1 taken 1232 times.
4931 if (!alien_cache_) {
230
2/2
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 3660 times.
3699 if (!EnsureCacheDirectories())
231 39 return false;
232 }
233
234 // TODO(jblomer): we might not need to look anymore for cvmfs 2.0 relicts
235
3/5
✓ Branch 2 taken 4892 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39 times.
✓ Branch 6 taken 4853 times.
4892 if (FileExists(cache_path + "/cvmfscatalog.cache")) {
236 39 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
237 "Not mounting on cvmfs 2.0.X cache");
238 39 return false;
239 }
240 4853 return true;
241 }
242
243
244 /**
245 * Idempotently create the on-disk cache skeleton and detect the underlying
246 * file system. For alien caches this is deferred until the first cache write
247 * so that the skeleton is only materialized once the repository has actually
248 * been validated and content is about to be stored (see #4217).
249 */
250 9475 bool PosixCacheManager::EnsureCacheDirectories() {
251
2/2
✓ Branch 1 taken 5698 times.
✓ Branch 2 taken 3777 times.
18950 if (cache_dirs_created_.load())
252 5698 return true;
253
254 3777 const MutexLockGuard guard(lock_cache_dirs_);
255
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3777 times.
7554 if (cache_dirs_created_.load())
256 ✗ return true;
257
258
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 3699 times.
3777 const mode_t mode = alien_cache_ ? 0770 : 0700;
259
260 // Create the base directory first so that the file system type can be
261 // detected reliably: statfs() on a not-yet-existing path returns nothing.
262
3/4
✓ Branch 1 taken 3777 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 39 times.
✓ Branch 4 taken 3738 times.
3777 if (!MkdirDeep(cache_path_, mode, false)) {
263
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
264 "Failed to create cache directory %s", cache_path_.c_str());
265 39 return false;
266 }
267
268
1/2
✓ Branch 1 taken 3738 times.
✗ Branch 2 not taken.
3738 const FileSystemInfo fs_info = GetFileSystemInfo(cache_path_);
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3738 times.
3738 if (fs_info.type == kFsTypeTmpfs)
270 ✗ is_tmpfs_ = true;
271
272
2/4
✓ Branch 1 taken 3738 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3738 times.
3738 if (!MakeCacheDirectories(cache_path_, mode))
273 ✗ return false;
274
275
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 3660 times.
3738 if (alien_cache_) {
276 // Sentinel file for future use (see FileSystem::SetupPosixCacheMgr). For
277 // an alien cache the skeleton -- and hence this file -- is created here
278 // lazily rather than at mount time; a read-only alien cache may reject it.
279
2/4
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 78 times.
✗ Branch 5 not taken.
78 CreateFile(cache_path_ + "/.cvmfscache", 0600, true /* ignore_failure */);
280
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
281 "Cache directory structure created.");
282
1/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 78 times.
78 switch (fs_info.type) {
283 ✗ case kFsTypeNFS:
284 ✗ rename_workaround_ = kRenameLink;
285 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog, "Alien cache is on NFS.");
286 ✗ break;
287 ✗ case kFsTypeBeeGFS:
288 ✗ rename_workaround_ = kRenameSamedir;
289 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
290 "Alien cache is on BeeGFS.");
291 ✗ break;
292 78 default:
293 78 break;
294 }
295 }
296
297 3738 cache_dirs_created_.store(1);
298 3738 return true;
299 3777 }
300
301 4931 PosixCacheManager *PosixCacheManager::Create(
302 const string &cache_path, const bool alien_cache,
303 const RenameWorkarounds rename_workaround, const bool do_refcount,
304 const bool cleanup_unused_first) {
305 std::unique_ptr<PosixCacheManager> cache_manager(new PosixCacheManager(
306
2/4
✓ Branch 1 taken 4931 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4931 times.
✗ Branch 5 not taken.
4931 cache_path, alien_cache, do_refcount, cleanup_unused_first));
307
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4931 times.
4931 assert(cache_manager.get() != nullptr);
308
309 4931 cache_manager->rename_workaround_ = rename_workaround;
310
311
1/2
✓ Branch 2 taken 4931 times.
✗ Branch 3 not taken.
4931 const bool result_ = cache_manager->InitCacheDirectory(cache_path);
312
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 4853 times.
4931 if (!result_) {
313 78 return NULL;
314 }
315
316 4853 return cache_manager.release();
317 4931 }
318
319
320 4599 void PosixCacheManager::CtrlTxn(const Label &label,
321 const int flags,
322 void *txn) {
323 4599 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
324 4599 transaction->label = label;
325 4599 }
326
327
328 ✗ string PosixCacheManager::Describe() {
329 ✗ string msg;
330 ✗ if (do_refcount_) {
331 msg = "Refcounting Posix cache manager"
332 "(cache directory: "
333 ✗ + cache_path_ + ")\n";
334 } else {
335 ✗ msg = "Posix cache manager (cache directory: " + cache_path_ + ")\n";
336 }
337 ✗ return msg;
338 }
339
340
341 /**
342 * If not refcounting, nothing to do, the kernel keeps the state
343 * of open file descriptors. Return a dummy memory location.
344 */
345 36 void *PosixCacheManager::DoSaveState() {
346
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if (do_refcount_) {
347 36 SavedState *state = new SavedState();
348 36 state->fd_mgr = std::unique_ptr<FdRefcountMgr>(fd_mgr_->Clone());
349 36 return state;
350 }
351 ✗ char *c = reinterpret_cast<char *>(smalloc(1));
352 ✗ *c = kMagicNoRefcount;
353 ✗ return c;
354 }
355
356
357 36 int PosixCacheManager::DoRestoreState(void *data) {
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 assert(data);
359
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if (do_refcount_) {
360 36 SavedState *state = reinterpret_cast<SavedState *>(data);
361
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if (state->magic_number == kMagicRefcount) {
362 36 LogCvmfs(kLogCache, kLogDebug,
363 "Restoring refcount cache manager from "
364 "refcounted posix cache manager");
365
366 36 fd_mgr_->AssignFrom(state->fd_mgr.get());
367 } else {
368 ✗ LogCvmfs(kLogCache, kLogDebug,
369 "Restoring refcount cache manager from "
370 "non-refcounted posix cache manager");
371 }
372 36 return -1;
373 }
374
375 ✗ char *c = reinterpret_cast<char *>(data);
376 ✗ assert(*c == kMagicNoRefcount || *c == kMagicRefcount);
377 ✗ if (*c == kMagicRefcount) {
378 ✗ SavedState *state = reinterpret_cast<SavedState *>(data);
379 ✗ LogCvmfs(kLogCache, kLogDebug,
380 "Restoring non-refcount cache manager from "
381 "refcounted posix cache manager - this "
382 " is not possible, keep refcounting.");
383 ✗ fd_mgr_->AssignFrom(state->fd_mgr.get());
384 ✗ do_refcount_ = true;
385 }
386 ✗ return -1;
387 }
388
389
390 36 bool PosixCacheManager::DoFreeState(void *data) {
391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 assert(data);
392 36 SavedState *state = reinterpret_cast<SavedState *>(data);
393
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if (state->magic_number == kMagicRefcount) {
394
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 delete state;
395 } else {
396 // If not refcounted, the state is the dummy SavedState
397 // of the regular posix cache manager
398 ✗ free(data);
399 }
400 36 return true;
401 }
402
403
404 98 int PosixCacheManager::Dup(int fd) {
405
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 const int new_fd = do_refcount_ ? fd_mgr_->Dup(fd) : dup(fd);
406
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 49 times.
98 if (new_fd < 0)
407 49 return -errno;
408 49 return new_fd;
409 }
410
411
412 9450 int PosixCacheManager::Flush(Transaction *transaction) {
413
2/2
✓ Branch 0 taken 2219 times.
✓ Branch 1 taken 7231 times.
9450 if (transaction->buf_pos == 0)
414 2219 return 0;
415 14462 const int written = write(transaction->fd, transaction->buffer,
416 7231 transaction->buf_pos);
417
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 7114 times.
7231 if (written < 0)
418 117 return -errno;
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7114 times.
7114 if (static_cast<unsigned>(written) != transaction->buf_pos) {
420 ✗ transaction->buf_pos -= written;
421 ✗ return -EIO;
422 }
423 7114 transaction->buf_pos = 0;
424 7114 return 0;
425 }
426
427
428 9920 inline string PosixCacheManager::GetPathInCache(const shash::Any &id) {
429
2/4
✓ Branch 2 taken 9920 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9920 times.
✗ Branch 6 not taken.
19840 return cache_path_ + "/" + id.MakePathWithoutSuffix();
430 }
431
432
433 2327 int64_t PosixCacheManager::GetSize(int fd) {
434 platform_stat64 info;
435 2327 const int retval = platform_fstat(fd, &info);
436
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 2288 times.
2327 if (retval != 0)
437 39 return -errno;
438 2288 return info.st_size;
439 }
440
441
442 4222 int PosixCacheManager::Open(const LabeledObject &object) {
443
1/2
✓ Branch 1 taken 4222 times.
✗ Branch 2 not taken.
4222 const string path = GetPathInCache(object.id);
444 int result;
445
1/2
✓ Branch 0 taken 4222 times.
✗ Branch 1 not taken.
4222 if (do_refcount_) {
446
1/2
✓ Branch 2 taken 4222 times.
✗ Branch 3 not taken.
4222 result = fd_mgr_->Open(object.id, path);
447 } else {
448 ✗ result = open(path.c_str(), O_RDONLY);
449 }
450
2/2
✓ Branch 0 taken 1544 times.
✓ Branch 1 taken 2678 times.
4222 if (result >= 0) {
451
1/2
✓ Branch 2 taken 1544 times.
✗ Branch 3 not taken.
1544 LogCvmfs(kLogCache, kLogDebug, "hit %s", path.c_str());
452 // platform_disable_kcache(result);
453
1/2
✓ Branch 1 taken 1544 times.
✗ Branch 2 not taken.
1544 quota_mgr_->Touch(object.id);
454 } else {
455 2678 result = -errno;
456
1/2
✓ Branch 2 taken 2678 times.
✗ Branch 3 not taken.
2678 LogCvmfs(kLogCache, kLogDebug, "miss %s (%d)", path.c_str(), result);
457 }
458 4222 return result;
459 4222 }
460
461
462 1003 int PosixCacheManager::OpenFromTxn(void *txn) {
463 1003 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
464 1003 const int retval = Flush(transaction);
465
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 964 times.
1003 if (retval < 0)
466 39 return retval;
467 int fd_rdonly;
468
469
1/2
✓ Branch 0 taken 964 times.
✗ Branch 1 not taken.
964 if (do_refcount_) {
470
2/4
✓ Branch 4 taken 964 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 964 times.
✗ Branch 8 not taken.
964 fd_rdonly = fd_mgr_->Open(transaction->id, transaction->tmp_path.c_str());
471 } else {
472 ✗ fd_rdonly = open(transaction->tmp_path.c_str(), O_RDONLY);
473 }
474
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 925 times.
964 if (fd_rdonly == -1)
475 39 return -errno;
476 925 return fd_rdonly;
477 }
478
479
480 15852 int64_t PosixCacheManager::Pread(int fd,
481 void *buf,
482 uint64_t size,
483 uint64_t offset) {
484 int64_t result;
485 do {
486 15852 errno = 0;
487 15852 result = pread(fd, buf, size, offset);
488
3/4
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 15774 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78 times.
15852 } while ((result == -1) && (errno == EINTR));
489
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 15774 times.
15852 if (result < 0)
490 78 return -errno;
491 15774 return result;
492 }
493
494
495 5115 int PosixCacheManager::Rename(const char *oldpath, const char *newpath) {
496 int result;
497
2/2
✓ Branch 0 taken 4998 times.
✓ Branch 1 taken 117 times.
5115 if (rename_workaround_ != kRenameLink) {
498 4998 result = rename(oldpath, newpath);
499
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 4881 times.
4998 if (result < 0)
500 117 return -errno;
501 4881 return 0;
502 }
503
504 117 result = link(oldpath, newpath);
505
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 39 times.
117 if (result < 0) {
506
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 39 times.
78 if (errno == EEXIST) {
507 39 LogCvmfs(kLogCache, kLogDebug, "%s already existed, ignoring", newpath);
508 } else {
509 39 return -errno;
510 }
511 }
512 78 result = unlink(oldpath);
513
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 if (result < 0)
514 ✗ return -errno;
515 78 return 0;
516 }
517
518
519 /**
520 * Used by the sqlite vfs in order to preload file catalogs into the file system
521 * buffers.
522 *
523 * No-op if the fd is to a file that is on a tmpfs, and so already in page cache
524 */
525 1104 int PosixCacheManager::Readahead(int fd) {
526 unsigned char *buf[4096];
527 int nbytes;
528 1104 uint64_t pos = 0;
529
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1104 times.
1104 if (is_tmpfs()) {
530 ✗ return 0;
531 }
532 do {
533
1/2
✓ Branch 1 taken 5136 times.
✗ Branch 2 not taken.
5136 nbytes = Pread(fd, buf, 4096, pos);
534 5136 pos += nbytes;
535
2/2
✓ Branch 0 taken 4032 times.
✓ Branch 1 taken 1104 times.
5136 } while (nbytes == 4096);
536
1/2
✓ Branch 1 taken 1104 times.
✗ Branch 2 not taken.
1104 LogCvmfs(kLogCache, kLogDebug, "read-ahead %d, %" PRIu64, fd, pos);
537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1104 times.
1104 if (nbytes < 0)
538 ✗ return nbytes;
539 1104 return 0;
540 }
541
542
543 215 int PosixCacheManager::Reset(void *txn) {
544 215 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
545 215 transaction->buf_pos = 0;
546 215 transaction->size = 0;
547 215 int retval = lseek(transaction->fd, 0, SEEK_SET);
548
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 176 times.
215 if (retval < 0)
549 39 return -errno;
550 176 retval = ftruncate(transaction->fd, 0);
551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 176 times.
176 if (retval < 0)
552 ✗ return -errno;
553 176 return 0;
554 }
555
556
557 5776 int PosixCacheManager::StartTxn(const shash::Any &id,
558 uint64_t size,
559 void *txn) {
560 // Materialize the cache skeleton on first use. For alien caches this is
561 // deferred from mount time to here, so that a bogus fqrn never creates any
562 // directories (see #4217); for regular caches this is a lock-free no-op.
563
2/4
✓ Branch 1 taken 5776 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5776 times.
5776 if (!EnsureCacheDirectories())
564 ✗ return -EIO;
565
566 5776 no_inflight_txns_.fetch_add(1);
567
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 5737 times.
5776 if (cache_mode_ == kCacheReadOnly) {
568 39 no_inflight_txns_.fetch_sub(1);
569 39 return -EROFS;
570 }
571
572
2/2
✓ Branch 0 taken 4793 times.
✓ Branch 1 taken 944 times.
5737 if (size != kSizeUnknown) {
573
3/4
✓ Branch 1 taken 4793 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 39 times.
✓ Branch 4 taken 4754 times.
4793 if (size > quota_mgr_->GetMaxFileSize()) {
574
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 LogCvmfs(kLogCache, kLogDebug,
575 "file too big for lru cache (%" PRIu64 " "
576 "requested but only %" PRIu64 " bytes free)",
577
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 size, quota_mgr_->GetMaxFileSize());
578 39 no_inflight_txns_.fetch_sub(1);
579 39 return -ENOSPC;
580 }
581
582 // For large files, ensure enough free cache space before writing the chunk
583
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 4715 times.
4754 if (size > kBigFile) {
584
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 const uint64_t cache_size = quota_mgr_->GetSize();
585
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 const uint64_t cache_capacity = quota_mgr_->GetCapacity();
586
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 assert(cache_capacity >= size);
587
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 if ((cache_size + size) > cache_capacity) {
588 78 const uint64_t leave_size = std::min(cache_capacity / 2,
589 39 cache_capacity - size);
590
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 quota_mgr_->Cleanup(leave_size);
591 }
592 }
593 }
594
595
1/2
✓ Branch 1 taken 5698 times.
✗ Branch 2 not taken.
5698 string path_in_cache = GetPathInCache(id);
596
1/2
✓ Branch 2 taken 5698 times.
✗ Branch 3 not taken.
5698 Transaction *transaction = new (txn) Transaction(id, path_in_cache);
597
598 5698 char *template_path = NULL;
599 5698 unsigned temp_path_len = 0;
600
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 5620 times.
5698 if (rename_workaround_ == kRenameSamedir) {
601 78 temp_path_len = path_in_cache.length() + 6;
602 78 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
603 78 memcpy(template_path, path_in_cache.data(), path_in_cache.length());
604 78 memset(template_path + path_in_cache.length(), 'X', 6);
605 } else {
606 5620 temp_path_len = txn_template_path_.length();
607 5620 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
608
1/2
✓ Branch 1 taken 5620 times.
✗ Branch 2 not taken.
5620 memcpy(template_path, &txn_template_path_[0], temp_path_len);
609 }
610 5698 template_path[temp_path_len] = '\0';
611
612
1/2
✓ Branch 1 taken 5698 times.
✗ Branch 2 not taken.
5698 transaction->fd = mkstemp(template_path);
613
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 5649 times.
5698 if (transaction->fd == -1) {
614 49 transaction->~Transaction();
615 49 no_inflight_txns_.fetch_sub(1);
616 49 return -errno;
617 }
618
619
1/2
✓ Branch 1 taken 5649 times.
✗ Branch 2 not taken.
5649 LogCvmfs(kLogCache, kLogDebug, "start transaction on %s has result %d",
620 template_path, transaction->fd);
621
1/2
✓ Branch 1 taken 5649 times.
✗ Branch 2 not taken.
5649 transaction->tmp_path = template_path;
622 5649 transaction->expected_size = size;
623 5649 return transaction->fd;
624 5698 }
625
626
627 839 manifest::Breadcrumb PosixCacheManager::LoadBreadcrumb(
628 const std::string &fqrn) {
629 839 return manifest::Manifest::ReadBreadcrumb(fqrn, cache_path_);
630 }
631
632
633 387 bool PosixCacheManager::StoreBreadcrumb(const manifest::Manifest &manifest) {
634 387 return manifest.ExportBreadcrumb(cache_path_, 0600);
635 }
636
637
638 ✗ bool PosixCacheManager::StoreBreadcrumb(std::string fqrn,
639 manifest::Breadcrumb breadcrumb) {
640 ✗ return breadcrumb.Export(fqrn, cache_path_, 0600);
641 }
642
643
644 156 void PosixCacheManager::TearDown2ReadOnly() {
645 156 cache_mode_ = kCacheReadOnly;
646
2/2
✓ Branch 1 taken 585 times.
✓ Branch 2 taken 117 times.
1404 while (no_inflight_txns_.load() != 0)
647 585 SafeSleepMs(50);
648
649 117 QuotaManager *old_manager = quota_mgr_;
650
1/2
✓ Branch 2 taken 117 times.
✗ Branch 3 not taken.
117 quota_mgr_ = new NoopQuotaManager();
651
1/2
✓ Branch 0 taken 117 times.
✗ Branch 1 not taken.
117 delete old_manager;
652 117 }
653
654
655 5832 int64_t PosixCacheManager::Write(const void *buf, uint64_t size, void *txn) {
656 5832 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
657
658
2/2
✓ Branch 0 taken 4286 times.
✓ Branch 1 taken 1546 times.
5832 if (transaction->expected_size != kSizeUnknown) {
659
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 4247 times.
4286 if (transaction->size + size > transaction->expected_size) {
660 39 LogCvmfs(kLogCache, kLogDebug,
661 "Transaction size (%" PRIu64 ") > expected size (%" PRIu64 ")",
662 39 transaction->size + size, transaction->expected_size);
663 39 return -EFBIG;
664 }
665 }
666
667 5793 uint64_t written = 0;
668 5793 const unsigned char *read_pos = reinterpret_cast<const unsigned char *>(buf);
669
2/2
✓ Branch 0 taken 7368 times.
✓ Branch 1 taken 5754 times.
13122 while (written < size) {
670
2/2
✓ Branch 0 taken 3432 times.
✓ Branch 1 taken 3936 times.
7368 if (transaction->buf_pos == sizeof(transaction->buffer)) {
671
1/2
✓ Branch 1 taken 3432 times.
✗ Branch 2 not taken.
3432 const int retval = Flush(transaction);
672
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 3393 times.
3432 if (retval != 0) {
673 39 transaction->size += written;
674 39 return retval;
675 }
676 }
677 7329 const uint64_t remaining = size - written;
678 7329 const uint64_t space_in_buffer = sizeof(transaction->buffer)
679 7329 - transaction->buf_pos;
680 7329 const uint64_t batch_size = std::min(remaining, space_in_buffer);
681 7329 memcpy(transaction->buffer + transaction->buf_pos, read_pos, batch_size);
682 7329 transaction->buf_pos += batch_size;
683 7329 written += batch_size;
684 7329 read_pos += batch_size;
685 }
686 5754 transaction->size += written;
687 5754 return written;
688 }
689
690