GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_posix.cc
Date: 2026-10-11 02:40:02
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 757 int PosixCacheManager::AbortTxn(void *txn) {
109 757 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
110 757 LogCvmfs(kLogCache, kLogDebug, "abort %s", transaction->tmp_path.c_str());
111 757 close(transaction->fd);
112 757 const int result = unlink(transaction->tmp_path.c_str());
113 757 transaction->~Transaction();
114 757 no_inflight_txns_.fetch_sub(1);
115
2/2
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 662 times.
757 if (result == -1)
116 95 return -errno;
117 662 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 1461 bool PosixCacheManager::AcquireQuotaManager(QuotaManager *quota_mgr) {
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1461 times.
1461 if (quota_mgr == NULL)
128 ✗ return false;
129
1/2
✓ Branch 0 taken 1461 times.
✗ Branch 1 not taken.
1461 delete quota_mgr_;
130 1461 quota_mgr_ = quota_mgr;
131 1461 return true;
132 }
133
134
135 2196 int PosixCacheManager::Close(int fd) {
136
1/2
✓ Branch 0 taken 2196 times.
✗ Branch 1 not taken.
2196 const int retval = do_refcount_ ? fd_mgr_->Close(fd) : close(fd);
137
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 2150 times.
2196 if (retval != 0)
138 46 return -errno;
139 2150 return 0;
140 }
141
142
143 5452 int PosixCacheManager::CommitTxn(void *txn) {
144 5452 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
145 int result;
146 5452 LogCvmfs(kLogCache, kLogDebug, "commit %s %s",
147 transaction->final_path.c_str(), transaction->tmp_path.c_str());
148
149 5452 result = Flush(transaction);
150 5452 close(transaction->fd);
151
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 5406 times.
5452 if (result < 0) {
152 46 unlink(transaction->tmp_path.c_str());
153 46 transaction->~Transaction();
154 46 no_inflight_txns_.fetch_sub(1);
155 46 return result;
156 }
157
158 // To support debugging, move files into quarantine on file size mismatch
159
2/2
✓ Branch 0 taken 620 times.
✓ Branch 1 taken 4786 times.
5406 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 84 times.
✓ Branch 1 taken 536 times.
620 if ((transaction->expected_size != kSizeUnknown)
163
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
84 && (reports_correct_filesize_ || (transaction->size != 0))) {
164
1/3
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
84 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
165 "size check failure for %s, expected %lu, got %lu",
166 168 transaction->id.ToString().c_str(), transaction->expected_size,
167 transaction->size);
168
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 CopyPath2Path(transaction->tmp_path,
169
2/4
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 84 times.
✗ Branch 6 not taken.
168 cache_path_ + "/quarantaine/" + transaction->id.ToString());
170 84 unlink(transaction->tmp_path.c_str());
171 84 transaction->~Transaction();
172 84 no_inflight_txns_.fetch_sub(1);
173 84 return -EIO;
174 }
175 }
176
177
2/2
✓ Branch 0 taken 5273 times.
✓ Branch 1 taken 49 times.
5322 if ((transaction->label.flags & kLabelPinned)
178
2/2
✓ Branch 0 taken 586 times.
✓ Branch 1 taken 4687 times.
5273 || (transaction->label.flags & kLabelCatalog)) {
179 1905 const bool retval = quota_mgr_->Pin(
180
1/2
✓ Branch 1 taken 635 times.
✗ Branch 2 not taken.
635 transaction->id, transaction->size, transaction->label.GetDescription(),
181 635 (transaction->label.flags & kLabelCatalog));
182
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 586 times.
635 if (!retval) {
183
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 LogCvmfs(kLogCache, kLogDebug, "commit failed: cannot pin %s",
184 98 transaction->id.ToString().c_str());
185 49 unlink(transaction->tmp_path.c_str());
186 49 transaction->~Transaction();
187 49 no_inflight_txns_.fetch_sub(1);
188 49 return -ENOSPC;
189 }
190 }
191
192 // Move the temporary file into its final location
193
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 5181 times.
5273 if (alien_cache_) {
194 92 const int retval = chmod(transaction->tmp_path.c_str(), 0660);
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 assert(retval == 0);
196 }
197 5273 result = Rename(transaction->tmp_path.c_str(),
198 transaction->final_path.c_str());
199
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 5181 times.
5273 if (result < 0) {
200 92 LogCvmfs(kLogCache, kLogDebug, "commit failed: %s", strerror(errno));
201 92 unlink(transaction->tmp_path.c_str());
202
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if ((transaction->label.flags & kLabelPinned)
203
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 46 times.
92 || (transaction->label.flags & kLabelCatalog)) {
204 46 quota_mgr_->Remove(transaction->id);
205 }
206 } else {
207 // Success, inform quota manager
208
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 5132 times.
5181 if (transaction->label.flags & kLabelVolatile) {
209
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 quota_mgr_->InsertVolatile(transaction->id, transaction->size,
210 98 transaction->label.GetDescription());
211 5132 } else if (!transaction->label.IsCatalog()
212
6/6
✓ Branch 0 taken 4641 times.
✓ Branch 1 taken 491 times.
✓ Branch 3 taken 4592 times.
✓ Branch 4 taken 49 times.
✓ Branch 5 taken 4592 times.
✓ Branch 6 taken 540 times.
5132 && !transaction->label.IsPinned()) {
213
1/2
✓ Branch 1 taken 4592 times.
✗ Branch 2 not taken.
4592 quota_mgr_->Insert(transaction->id, transaction->size,
214 9184 transaction->label.GetDescription());
215 }
216 }
217 5273 transaction->~Transaction();
218 5273 no_inflight_txns_.fetch_sub(1);
219 5273 return result;
220 }
221
222 5679 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 4202 times.
✓ Branch 1 taken 1477 times.
5679 if (!alien_cache_) {
230
2/2
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 4156 times.
4202 if (!EnsureCacheDirectories())
231 46 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 5633 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 46 times.
✓ Branch 6 taken 5587 times.
5633 if (FileExists(cache_path + "/cvmfscatalog.cache")) {
236 46 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
237 "Not mounting on cvmfs 2.0.X cache");
238 46 return false;
239 }
240 5587 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 10574 bool PosixCacheManager::EnsureCacheDirectories() {
251
2/2
✓ Branch 1 taken 6280 times.
✓ Branch 2 taken 4294 times.
21148 if (cache_dirs_created_.load())
252 6280 return true;
253
254 4294 const MutexLockGuard guard(lock_cache_dirs_);
255
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4294 times.
8588 if (cache_dirs_created_.load())
256 ✗ return true;
257
258
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 4202 times.
4294 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 4294 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 4248 times.
4294 if (!MkdirDeep(cache_path_, mode, false)) {
263
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
264 "Failed to create cache directory %s", cache_path_.c_str());
265 46 return false;
266 }
267
268
1/2
✓ Branch 1 taken 4248 times.
✗ Branch 2 not taken.
4248 const FileSystemInfo fs_info = GetFileSystemInfo(cache_path_);
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4248 times.
4248 if (fs_info.type == kFsTypeTmpfs)
270 ✗ is_tmpfs_ = true;
271
272
2/4
✓ Branch 1 taken 4248 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4248 times.
4248 if (!MakeCacheDirectories(cache_path_, mode))
273 ✗ return false;
274
275
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 4156 times.
4248 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 92 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 92 times.
✗ Branch 5 not taken.
92 CreateFile(cache_path_ + "/.cvmfscache", 0600, true /* ignore_failure */);
280
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
281 "Cache directory structure created.");
282
1/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
92 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 92 default:
293 92 break;
294 }
295 }
296
297 4248 cache_dirs_created_.store(1);
298 4248 return true;
299 4294 }
300
301 5679 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 5679 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5679 times.
✗ Branch 5 not taken.
5679 cache_path, alien_cache, do_refcount, cleanup_unused_first));
307
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5679 times.
5679 assert(cache_manager.get() != nullptr);
308
309 5679 cache_manager->rename_workaround_ = rename_workaround;
310
311
1/2
✓ Branch 2 taken 5679 times.
✗ Branch 3 not taken.
5679 const bool result_ = cache_manager->InitCacheDirectory(cache_path);
312
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 5587 times.
5679 if (!result_) {
313 92 return NULL;
314 }
315
316 5587 return cache_manager.release();
317 5679 }
318
319
320 4946 void PosixCacheManager::CtrlTxn(const Label &label,
321 const int flags,
322 void *txn) {
323 4946 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
324 4946 transaction->label = label;
325 4946 }
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 46 void *PosixCacheManager::DoSaveState() {
346
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (do_refcount_) {
347 46 SavedState *state = new SavedState();
348 46 state->fd_mgr = std::unique_ptr<FdRefcountMgr>(fd_mgr_->Clone());
349 46 return state;
350 }
351 ✗ char *c = reinterpret_cast<char *>(smalloc(1));
352 ✗ *c = kMagicNoRefcount;
353 ✗ return c;
354 }
355
356
357 46 int PosixCacheManager::DoRestoreState(void *data) {
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(data);
359
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (do_refcount_) {
360 46 SavedState *state = reinterpret_cast<SavedState *>(data);
361
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (state->magic_number == kMagicRefcount) {
362 46 LogCvmfs(kLogCache, kLogDebug,
363 "Restoring refcount cache manager from "
364 "refcounted posix cache manager");
365
366 46 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 46 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 46 bool PosixCacheManager::DoFreeState(void *data) {
391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(data);
392 46 SavedState *state = reinterpret_cast<SavedState *>(data);
393
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (state->magic_number == kMagicRefcount) {
394
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 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 46 return true;
401 }
402
403
404 130 int PosixCacheManager::Dup(int fd) {
405
1/2
✓ Branch 0 taken 130 times.
✗ Branch 1 not taken.
130 const int new_fd = do_refcount_ ? fd_mgr_->Dup(fd) : dup(fd);
406
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 65 times.
130 if (new_fd < 0)
407 65 return -errno;
408 65 return new_fd;
409 }
410
411
412 8986 int PosixCacheManager::Flush(Transaction *transaction) {
413
2/2
✓ Branch 0 taken 2271 times.
✓ Branch 1 taken 6715 times.
8986 if (transaction->buf_pos == 0)
414 2271 return 0;
415 13430 const int written = write(transaction->fd, transaction->buffer,
416 6715 transaction->buf_pos);
417
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 6574 times.
6715 if (written < 0)
418 141 return -errno;
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6574 times.
6574 if (static_cast<unsigned>(written) != transaction->buf_pos) {
420 ✗ transaction->buf_pos -= written;
421 ✗ return -EIO;
422 }
423 6574 transaction->buf_pos = 0;
424 6574 return 0;
425 }
426
427
428 10097 inline string PosixCacheManager::GetPathInCache(const shash::Any &id) {
429
2/4
✓ Branch 2 taken 10097 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10097 times.
✗ Branch 6 not taken.
20194 return cache_path_ + "/" + id.MakePathWithoutSuffix();
430 }
431
432
433 1898 int64_t PosixCacheManager::GetSize(int fd) {
434 platform_stat64 info;
435 1898 const int retval = platform_fstat(fd, &info);
436
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 1852 times.
1898 if (retval != 0)
437 46 return -errno;
438 1852 return info.st_size;
439 }
440
441
442 3820 int PosixCacheManager::Open(const LabeledObject &object) {
443
1/2
✓ Branch 1 taken 3820 times.
✗ Branch 2 not taken.
3820 const string path = GetPathInCache(object.id);
444 int result;
445
1/2
✓ Branch 0 taken 3820 times.
✗ Branch 1 not taken.
3820 if (do_refcount_) {
446
1/2
✓ Branch 2 taken 3820 times.
✗ Branch 3 not taken.
3820 result = fd_mgr_->Open(object.id, path);
447 } else {
448 ✗ result = open(path.c_str(), O_RDONLY);
449 }
450
2/2
✓ Branch 0 taken 1508 times.
✓ Branch 1 taken 2312 times.
3820 if (result >= 0) {
451
1/2
✓ Branch 2 taken 1508 times.
✗ Branch 3 not taken.
1508 LogCvmfs(kLogCache, kLogDebug, "hit %s", path.c_str());
452 // platform_disable_kcache(result);
453
1/2
✓ Branch 1 taken 1508 times.
✗ Branch 2 not taken.
1508 quota_mgr_->Touch(object.id);
454 } else {
455 2312 result = -errno;
456
1/2
✓ Branch 2 taken 2312 times.
✗ Branch 3 not taken.
2312 LogCvmfs(kLogCache, kLogDebug, "miss %s (%d)", path.c_str(), result);
457 }
458 3820 return result;
459 3820 }
460
461
462 808 int PosixCacheManager::OpenFromTxn(void *txn) {
463 808 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
464 808 const int retval = Flush(transaction);
465
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 759 times.
808 if (retval < 0)
466 49 return retval;
467 int fd_rdonly;
468
469
1/2
✓ Branch 0 taken 759 times.
✗ Branch 1 not taken.
759 if (do_refcount_) {
470
2/4
✓ Branch 4 taken 759 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 759 times.
✗ Branch 8 not taken.
759 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 49 times.
✓ Branch 1 taken 710 times.
759 if (fd_rdonly == -1)
475 49 return -errno;
476 710 return fd_rdonly;
477 }
478
479
480 10960 int64_t PosixCacheManager::Pread(int fd,
481 void *buf,
482 uint64_t size,
483 uint64_t offset) {
484 int64_t result;
485 do {
486 10960 errno = 0;
487 10960 result = pread(fd, buf, size, offset);
488
3/4
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 10865 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 95 times.
10960 } while ((result == -1) && (errno == EINTR));
489
2/2
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 10865 times.
10960 if (result < 0)
490 95 return -errno;
491 10865 return result;
492 }
493
494
495 5567 int PosixCacheManager::Rename(const char *oldpath, const char *newpath) {
496 int result;
497
2/2
✓ Branch 0 taken 5420 times.
✓ Branch 1 taken 147 times.
5567 if (rename_workaround_ != kRenameLink) {
498 5420 result = rename(oldpath, newpath);
499
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 5279 times.
5420 if (result < 0)
500 141 return -errno;
501 5279 return 0;
502 }
503
504 147 result = link(oldpath, newpath);
505
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 49 times.
147 if (result < 0) {
506
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 49 times.
98 if (errno == EEXIST) {
507 49 LogCvmfs(kLogCache, kLogDebug, "%s already existed, ignoring", newpath);
508 } else {
509 49 return -errno;
510 }
511 }
512 98 result = unlink(oldpath);
513
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (result < 0)
514 ✗ return -errno;
515 98 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 756 int PosixCacheManager::Readahead(int fd) {
526 unsigned char *buf[4096];
527 int nbytes;
528 756 uint64_t pos = 0;
529
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 756 times.
756 if (is_tmpfs()) {
530 ✗ return 0;
531 }
532 do {
533
1/2
✓ Branch 1 taken 3484 times.
✗ Branch 2 not taken.
3484 nbytes = Pread(fd, buf, 4096, pos);
534 3484 pos += nbytes;
535
2/2
✓ Branch 0 taken 2728 times.
✓ Branch 1 taken 756 times.
3484 } while (nbytes == 4096);
536
1/2
✓ Branch 1 taken 756 times.
✗ Branch 2 not taken.
756 LogCvmfs(kLogCache, kLogDebug, "read-ahead %d, %" PRIu64, fd, pos);
537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 756 times.
756 if (nbytes < 0)
538 ✗ return nbytes;
539 756 return 0;
540 }
541
542
543 252 int PosixCacheManager::Reset(void *txn) {
544 252 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
545 252 transaction->buf_pos = 0;
546 252 transaction->size = 0;
547 252 int retval = lseek(transaction->fd, 0, SEEK_SET);
548
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 206 times.
252 if (retval < 0)
549 46 return -errno;
550 206 retval = ftruncate(transaction->fd, 0);
551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
206 if (retval < 0)
552 ✗ return -errno;
553 206 return 0;
554 }
555
556
557 6372 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 6372 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6372 times.
6372 if (!EnsureCacheDirectories())
564 ✗ return -EIO;
565
566 6372 no_inflight_txns_.fetch_add(1);
567
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 6326 times.
6372 if (cache_mode_ == kCacheReadOnly) {
568 46 no_inflight_txns_.fetch_sub(1);
569 46 return -EROFS;
570 }
571
572
2/2
✓ Branch 0 taken 5627 times.
✓ Branch 1 taken 699 times.
6326 if (size != kSizeUnknown) {
573
3/4
✓ Branch 1 taken 5627 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 5578 times.
5627 if (size > quota_mgr_->GetMaxFileSize()) {
574
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 LogCvmfs(kLogCache, kLogDebug,
575 "file too big for lru cache (%" PRIu64 " "
576 "requested but only %" PRIu64 " bytes free)",
577
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 size, quota_mgr_->GetMaxFileSize());
578 49 no_inflight_txns_.fetch_sub(1);
579 49 return -ENOSPC;
580 }
581
582 // For large files, ensure enough free cache space before writing the chunk
583
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 5529 times.
5578 if (size > kBigFile) {
584
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 const uint64_t cache_size = quota_mgr_->GetSize();
585
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 const uint64_t cache_capacity = quota_mgr_->GetCapacity();
586
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 assert(cache_capacity >= size);
587
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if ((cache_size + size) > cache_capacity) {
588 98 const uint64_t leave_size = std::min(cache_capacity / 2,
589 49 cache_capacity - size);
590
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 quota_mgr_->Cleanup(leave_size);
591 }
592 }
593 }
594
595
1/2
✓ Branch 1 taken 6277 times.
✗ Branch 2 not taken.
6277 string path_in_cache = GetPathInCache(id);
596
1/2
✓ Branch 2 taken 6277 times.
✗ Branch 3 not taken.
6277 Transaction *transaction = new (txn) Transaction(id, path_in_cache);
597
598 6277 char *template_path = NULL;
599 6277 unsigned temp_path_len = 0;
600
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 6179 times.
6277 if (rename_workaround_ == kRenameSamedir) {
601 98 temp_path_len = path_in_cache.length() + 6;
602 98 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
603 98 memcpy(template_path, path_in_cache.data(), path_in_cache.length());
604 98 memset(template_path + path_in_cache.length(), 'X', 6);
605 } else {
606 6179 temp_path_len = txn_template_path_.length();
607 6179 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
608
1/2
✓ Branch 1 taken 6179 times.
✗ Branch 2 not taken.
6179 memcpy(template_path, &txn_template_path_[0], temp_path_len);
609 }
610 6277 template_path[temp_path_len] = '\0';
611
612
1/2
✓ Branch 1 taken 6277 times.
✗ Branch 2 not taken.
6277 transaction->fd = mkstemp(template_path);
613
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 6209 times.
6277 if (transaction->fd == -1) {
614 68 transaction->~Transaction();
615 68 no_inflight_txns_.fetch_sub(1);
616 68 return -errno;
617 }
618
619
1/2
✓ Branch 1 taken 6209 times.
✗ Branch 2 not taken.
6209 LogCvmfs(kLogCache, kLogDebug, "start transaction on %s has result %d",
620 template_path, transaction->fd);
621
1/2
✓ Branch 1 taken 6209 times.
✗ Branch 2 not taken.
6209 transaction->tmp_path = template_path;
622 6209 transaction->expected_size = size;
623 6209 return transaction->fd;
624 6277 }
625
626
627 550 manifest::Breadcrumb PosixCacheManager::LoadBreadcrumb(
628 const std::string &fqrn) {
629 550 return manifest::Manifest::ReadBreadcrumb(fqrn, cache_path_);
630 }
631
632
633 245 bool PosixCacheManager::StoreBreadcrumb(const manifest::Manifest &manifest) {
634 245 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 184 void PosixCacheManager::TearDown2ReadOnly() {
645 184 cache_mode_ = kCacheReadOnly;
646
2/2
✓ Branch 1 taken 644 times.
✓ Branch 2 taken 138 times.
1564 while (no_inflight_txns_.load() != 0)
647 644 SafeSleepMs(50);
648
649 138 QuotaManager *old_manager = quota_mgr_;
650
1/2
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
138 quota_mgr_ = new NoopQuotaManager();
651
1/2
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
138 delete old_manager;
652 138 }
653
654
655 6011 int64_t PosixCacheManager::Write(const void *buf, uint64_t size, void *txn) {
656 6011 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
657
658
2/2
✓ Branch 0 taken 5014 times.
✓ Branch 1 taken 997 times.
6011 if (transaction->expected_size != kSizeUnknown) {
659
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 4968 times.
5014 if (transaction->size + size > transaction->expected_size) {
660 46 LogCvmfs(kLogCache, kLogDebug,
661 "Transaction size (%" PRIu64 ") > expected size (%" PRIu64 ")",
662 46 transaction->size + size, transaction->expected_size);
663 46 return -EFBIG;
664 }
665 }
666
667 5965 uint64_t written = 0;
668 5965 const unsigned char *read_pos = reinterpret_cast<const unsigned char *>(buf);
669
2/2
✓ Branch 0 taken 6891 times.
✓ Branch 1 taken 5919 times.
12810 while (written < size) {
670
2/2
✓ Branch 0 taken 2726 times.
✓ Branch 1 taken 4165 times.
6891 if (transaction->buf_pos == sizeof(transaction->buffer)) {
671
1/2
✓ Branch 1 taken 2726 times.
✗ Branch 2 not taken.
2726 const int retval = Flush(transaction);
672
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 2680 times.
2726 if (retval != 0) {
673 46 transaction->size += written;
674 46 return retval;
675 }
676 }
677 6845 const uint64_t remaining = size - written;
678 6845 const uint64_t space_in_buffer = sizeof(transaction->buffer)
679 6845 - transaction->buf_pos;
680 6845 const uint64_t batch_size = std::min(remaining, space_in_buffer);
681 6845 memcpy(transaction->buffer + transaction->buf_pos, read_pos, batch_size);
682 6845 transaction->buf_pos += batch_size;
683 6845 written += batch_size;
684 6845 read_pos += batch_size;
685 }
686 5919 transaction->size += written;
687 5919 return written;
688 }
689
690