GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_posix.cc
Date: 2026-10-04 02:40:33
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 <cassert>
45 #include <cstdio>
46 #include <cstdlib>
47 #include <cstring>
48
49 #include "crypto/hash.h"
50 #include "manifest.h"
51 #include "manifest_fetch.h"
52 #include "quota.h"
53 #include "util/atomic.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 = atomic_read32(&global_drainout_);
76 drainout_ = (global_drainout != 0);
77 if (!drainout_)
78 atomic_inc32(&num_inflight_calls_);
79 }
80 ~CallGuard() {
81 if (!drainout_)
82 atomic_dec32(&num_inflight_calls_);
83 }
84 static void Drainout() {
85 atomic_cas32(&global_drainout_, 0, 1);
86 while (atomic_read32(&num_inflight_calls_) != 0)
87 SafeSleepMs(50);
88 }
89
90 private:
91 bool drainout_;
92 static atomic_int32 global_drainout_;
93 static atomic_int32 num_inflight_calls_;
94 };
95 atomic_int32 CallGuard::num_inflight_calls_ = 0;
96 atomic_int32 CallGuard::global_drainout_ = 0;
97
98 } // anonymous namespace
99
100
101 //------------------------------------------------------------------------------
102
103
104 const uint64_t PosixCacheManager::kBigFile = 25 * 1024 * 1024; // 25M
105
106
107 795 int PosixCacheManager::AbortTxn(void *txn) {
108 795 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
109 795 LogCvmfs(kLogCache, kLogDebug, "abort %s", transaction->tmp_path.c_str());
110 795 close(transaction->fd);
111 795 const int result = unlink(transaction->tmp_path.c_str());
112 795 transaction->~Transaction();
113 795 atomic_dec32(&no_inflight_txns_);
114
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 703 times.
795 if (result == -1)
115 92 return -errno;
116 703 return 0;
117 }
118
119
120 /**
121 * This should only be used to replace the default NoopQuotaManager by a
122 * PosixQuotaManager. The cache manager takes the ownership of the passed
123 * quota manager.
124 */
125 276 bool PosixCacheManager::AcquireQuotaManager(QuotaManager *quota_mgr) {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 276 times.
276 if (quota_mgr == NULL)
127 ✗ return false;
128
1/2
✓ Branch 0 taken 276 times.
✗ Branch 1 not taken.
276 delete quota_mgr_;
129 276 quota_mgr_ = quota_mgr;
130 276 return true;
131 }
132
133
134 2366 int PosixCacheManager::Close(int fd) {
135
1/2
✓ Branch 0 taken 2366 times.
✗ Branch 1 not taken.
2366 const int retval = do_refcount_ ? fd_mgr_->Close(fd) : close(fd);
136
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 2320 times.
2366 if (retval != 0)
137 46 return -errno;
138 2320 return 0;
139 }
140
141
142 5442 int PosixCacheManager::CommitTxn(void *txn) {
143 5442 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
144 int result;
145 5442 LogCvmfs(kLogCache, kLogDebug, "commit %s %s",
146 transaction->final_path.c_str(), transaction->tmp_path.c_str());
147
148 5442 result = Flush(transaction);
149 5442 close(transaction->fd);
150
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 5396 times.
5442 if (result < 0) {
151 46 unlink(transaction->tmp_path.c_str());
152 46 transaction->~Transaction();
153 46 atomic_dec32(&no_inflight_txns_);
154 46 return result;
155 }
156
157 // To support debugging, move files into quarantine on file size mismatch
158
2/2
✓ Branch 0 taken 848 times.
✓ Branch 1 taken 4548 times.
5396 if (transaction->size != transaction->expected_size) {
159 // Allow size to be zero if alien cache, because hadoop-fuse-dfs returns
160 // size zero for a while
161
2/2
✓ Branch 0 taken 118 times.
✓ Branch 1 taken 730 times.
848 if ((transaction->expected_size != kSizeUnknown)
162
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 118 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
118 && (reports_correct_filesize_ || (transaction->size != 0))) {
163
1/3
✓ Branch 2 taken 118 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
118 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
164 "size check failure for %s, expected %lu, got %lu",
165 236 transaction->id.ToString().c_str(), transaction->expected_size,
166 transaction->size);
167
1/2
✓ Branch 1 taken 118 times.
✗ Branch 2 not taken.
118 CopyPath2Path(transaction->tmp_path,
168
2/4
✓ Branch 2 taken 118 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 118 times.
✗ Branch 6 not taken.
236 cache_path_ + "/quarantaine/" + transaction->id.ToString());
169 118 unlink(transaction->tmp_path.c_str());
170 118 transaction->~Transaction();
171 118 atomic_dec32(&no_inflight_txns_);
172 118 return -EIO;
173 }
174 }
175
176
2/2
✓ Branch 0 taken 5235 times.
✓ Branch 1 taken 43 times.
5278 if ((transaction->label.flags & kLabelPinned)
177
2/2
✓ Branch 0 taken 750 times.
✓ Branch 1 taken 4485 times.
5235 || (transaction->label.flags & kLabelCatalog)) {
178 2379 const bool retval = quota_mgr_->Pin(
179
1/2
✓ Branch 1 taken 793 times.
✗ Branch 2 not taken.
793 transaction->id, transaction->size, transaction->label.GetDescription(),
180 793 (transaction->label.flags & kLabelCatalog));
181
2/2
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 750 times.
793 if (!retval) {
182
1/2
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
43 LogCvmfs(kLogCache, kLogDebug, "commit failed: cannot pin %s",
183 86 transaction->id.ToString().c_str());
184 43 unlink(transaction->tmp_path.c_str());
185 43 transaction->~Transaction();
186 43 atomic_dec32(&no_inflight_txns_);
187 43 return -ENOSPC;
188 }
189 }
190
191 // Move the temporary file into its final location
192
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 5146 times.
5235 if (alien_cache_) {
193 89 const int retval = chmod(transaction->tmp_path.c_str(), 0660);
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 assert(retval == 0);
195 }
196 5235 result = Rename(transaction->tmp_path.c_str(),
197 transaction->final_path.c_str());
198
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 5143 times.
5235 if (result < 0) {
199 92 LogCvmfs(kLogCache, kLogDebug, "commit failed: %s", strerror(errno));
200 92 unlink(transaction->tmp_path.c_str());
201
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if ((transaction->label.flags & kLabelPinned)
202
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 46 times.
92 || (transaction->label.flags & kLabelCatalog)) {
203 46 quota_mgr_->Remove(transaction->id);
204 }
205 } else {
206 // Success, inform quota manager
207
2/2
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 5100 times.
5143 if (transaction->label.flags & kLabelVolatile) {
208
1/2
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
43 quota_mgr_->InsertVolatile(transaction->id, transaction->size,
209 86 transaction->label.GetDescription());
210 5100 } else if (!transaction->label.IsCatalog()
211
6/6
✓ Branch 0 taken 4439 times.
✓ Branch 1 taken 661 times.
✓ Branch 3 taken 4396 times.
✓ Branch 4 taken 43 times.
✓ Branch 5 taken 4396 times.
✓ Branch 6 taken 704 times.
5100 && !transaction->label.IsPinned()) {
212
1/2
✓ Branch 1 taken 4396 times.
✗ Branch 2 not taken.
4396 quota_mgr_->Insert(transaction->id, transaction->size,
213 8792 transaction->label.GetDescription());
214 }
215 }
216 5235 transaction->~Transaction();
217 5235 atomic_dec32(&no_inflight_txns_);
218 5235 return result;
219 }
220
221 6531 bool PosixCacheManager::InitCacheDirectory(const string &cache_path) {
222 // For an alien cache the directory skeleton is created lazily on the first
223 // write (see EnsureCacheDirectories), so that merely trying to mount a bogus
224 // fqrn -- e.g. a stray access to /cvmfs/<typo> -- does not leave empty
225 // directories behind in a per-fqrn alien cache (see #4217). A regular cache
226 // coincides with the workspace and backs the quota manager, which scans the
227 // 00..ff directories at start-up, so it is created eagerly.
228
2/2
✓ Branch 0 taken 5177 times.
✓ Branch 1 taken 1354 times.
6531 if (!alien_cache_) {
229
2/2
✓ Branch 1 taken 46 times.
✓ Branch 2 taken 5131 times.
5177 if (!EnsureCacheDirectories())
230 46 return false;
231 }
232
233 // TODO(jblomer): we might not need to look anymore for cvmfs 2.0 relicts
234
3/5
✓ Branch 2 taken 6485 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 46 times.
✓ Branch 6 taken 6439 times.
6485 if (FileExists(cache_path + "/cvmfscatalog.cache")) {
235 46 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
236 "Not mounting on cvmfs 2.0.X cache");
237 46 return false;
238 }
239 6439 return true;
240 }
241
242
243 /**
244 * Idempotently create the on-disk cache skeleton and detect the underlying
245 * file system. For alien caches this is deferred until the first cache write
246 * so that the skeleton is only materialized once the repository has actually
247 * been validated and content is about to be stored (see #4217).
248 */
249 11585 bool PosixCacheManager::EnsureCacheDirectories() {
250
2/2
✓ Branch 1 taken 6319 times.
✓ Branch 2 taken 5266 times.
11585 if (atomic_read32(&cache_dirs_created_))
251 6319 return true;
252
253 5266 const MutexLockGuard guard(lock_cache_dirs_);
254
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5266 times.
5266 if (atomic_read32(&cache_dirs_created_))
255 ✗ return true;
256
257
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 5177 times.
5266 const mode_t mode = alien_cache_ ? 0770 : 0700;
258
259 // Create the base directory first so that the file system type can be
260 // detected reliably: statfs() on a not-yet-existing path returns nothing.
261
3/4
✓ Branch 1 taken 5266 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 5220 times.
5266 if (!MkdirDeep(cache_path_, mode, false)) {
262
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
263 "Failed to create cache directory %s", cache_path_.c_str());
264 46 return false;
265 }
266
267
1/2
✓ Branch 1 taken 5220 times.
✗ Branch 2 not taken.
5220 const FileSystemInfo fs_info = GetFileSystemInfo(cache_path_);
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5220 times.
5220 if (fs_info.type == kFsTypeTmpfs)
269 ✗ is_tmpfs_ = true;
270
271
2/4
✓ Branch 1 taken 5220 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5220 times.
5220 if (!MakeCacheDirectories(cache_path_, mode))
272 ✗ return false;
273
274
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 5131 times.
5220 if (alien_cache_) {
275 // Sentinel file for future use (see FileSystem::SetupPosixCacheMgr). For
276 // an alien cache the skeleton -- and hence this file -- is created here
277 // lazily rather than at mount time; a read-only alien cache may reject it.
278
2/4
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 89 times.
✗ Branch 5 not taken.
89 CreateFile(cache_path_ + "/.cvmfscache", 0600, true /* ignore_failure */);
279
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
280 "Cache directory structure created.");
281
1/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
89 switch (fs_info.type) {
282 ✗ case kFsTypeNFS:
283 ✗ rename_workaround_ = kRenameLink;
284 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog, "Alien cache is on NFS.");
285 ✗ break;
286 ✗ case kFsTypeBeeGFS:
287 ✗ rename_workaround_ = kRenameSamedir;
288 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
289 "Alien cache is on BeeGFS.");
290 ✗ break;
291 89 default:
292 89 break;
293 }
294 }
295
296 5220 atomic_write32(&cache_dirs_created_, 1);
297 5220 return true;
298 5266 }
299
300 6531 PosixCacheManager *PosixCacheManager::Create(
301 const string &cache_path, const bool alien_cache,
302 const RenameWorkarounds rename_workaround, const bool do_refcount,
303 const bool cleanup_unused_first) {
304 std::unique_ptr<PosixCacheManager> cache_manager(new PosixCacheManager(
305
2/4
✓ Branch 1 taken 6531 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6531 times.
✗ Branch 5 not taken.
6531 cache_path, alien_cache, do_refcount, cleanup_unused_first));
306
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6531 times.
6531 assert(cache_manager.get() != nullptr);
307
308 6531 cache_manager->rename_workaround_ = rename_workaround;
309
310
1/2
✓ Branch 2 taken 6531 times.
✗ Branch 3 not taken.
6531 const bool result_ = cache_manager->InitCacheDirectory(cache_path);
311
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 6439 times.
6531 if (!result_) {
312 92 return NULL;
313 }
314
315 6439 return cache_manager.release();
316 6531 }
317
318
319 5022 void PosixCacheManager::CtrlTxn(const Label &label,
320 const int flags,
321 void *txn) {
322 5022 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
323 5022 transaction->label = label;
324 5022 }
325
326
327 ✗ string PosixCacheManager::Describe() {
328 ✗ string msg;
329 ✗ if (do_refcount_) {
330 msg = "Refcounting Posix cache manager"
331 "(cache directory: "
332 ✗ + cache_path_ + ")\n";
333 } else {
334 ✗ msg = "Posix cache manager (cache directory: " + cache_path_ + ")\n";
335 }
336 ✗ return msg;
337 }
338
339
340 /**
341 * If not refcounting, nothing to do, the kernel keeps the state
342 * of open file descriptors. Return a dummy memory location.
343 */
344 43 void *PosixCacheManager::DoSaveState() {
345
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 if (do_refcount_) {
346 43 SavedState *state = new SavedState();
347 43 state->fd_mgr = std::unique_ptr<FdRefcountMgr>(fd_mgr_->Clone());
348 43 return state;
349 }
350 ✗ char *c = reinterpret_cast<char *>(smalloc(1));
351 ✗ *c = kMagicNoRefcount;
352 ✗ return c;
353 }
354
355
356 43 int PosixCacheManager::DoRestoreState(void *data) {
357
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 43 times.
43 assert(data);
358
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 if (do_refcount_) {
359 43 SavedState *state = reinterpret_cast<SavedState *>(data);
360
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 if (state->magic_number == kMagicRefcount) {
361 43 LogCvmfs(kLogCache, kLogDebug,
362 "Restoring refcount cache manager from "
363 "refcounted posix cache manager");
364
365 43 fd_mgr_->AssignFrom(state->fd_mgr.get());
366 } else {
367 ✗ LogCvmfs(kLogCache, kLogDebug,
368 "Restoring refcount cache manager from "
369 "non-refcounted posix cache manager");
370 }
371 43 return -1;
372 }
373
374 ✗ char *c = reinterpret_cast<char *>(data);
375 ✗ assert(*c == kMagicNoRefcount || *c == kMagicRefcount);
376 ✗ if (*c == kMagicRefcount) {
377 ✗ SavedState *state = reinterpret_cast<SavedState *>(data);
378 ✗ LogCvmfs(kLogCache, kLogDebug,
379 "Restoring non-refcount cache manager from "
380 "refcounted posix cache manager - this "
381 " is not possible, keep refcounting.");
382 ✗ fd_mgr_->AssignFrom(state->fd_mgr.get());
383 ✗ do_refcount_ = true;
384 }
385 ✗ return -1;
386 }
387
388
389 43 bool PosixCacheManager::DoFreeState(void *data) {
390
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 43 times.
43 assert(data);
391 43 SavedState *state = reinterpret_cast<SavedState *>(data);
392
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 if (state->magic_number == kMagicRefcount) {
393
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 delete state;
394 } else {
395 // If not refcounted, the state is the dummy SavedState
396 // of the regular posix cache manager
397 ✗ free(data);
398 }
399 43 return true;
400 }
401
402
403 164 int PosixCacheManager::Dup(int fd) {
404
1/2
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
164 const int new_fd = do_refcount_ ? fd_mgr_->Dup(fd) : dup(fd);
405
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 82 times.
164 if (new_fd < 0)
406 82 return -errno;
407 82 return new_fd;
408 }
409
410
411 9610 int PosixCacheManager::Flush(Transaction *transaction) {
412
2/2
✓ Branch 0 taken 2493 times.
✓ Branch 1 taken 7117 times.
9610 if (transaction->buf_pos == 0)
413 2493 return 0;
414 14234 const int written = write(transaction->fd, transaction->buffer,
415 7117 transaction->buf_pos);
416
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 6979 times.
7117 if (written < 0)
417 138 return -errno;
418
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6979 times.
6979 if (static_cast<unsigned>(written) != transaction->buf_pos) {
419 ✗ transaction->buf_pos -= written;
420 ✗ return -EIO;
421 }
422 6979 transaction->buf_pos = 0;
423 6979 return 0;
424 }
425
426
427 10305 inline string PosixCacheManager::GetPathInCache(const shash::Any &id) {
428
2/4
✓ Branch 2 taken 10305 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10305 times.
✗ Branch 6 not taken.
20610 return cache_path_ + "/" + id.MakePathWithoutSuffix();
429 }
430
431
432 1594 int64_t PosixCacheManager::GetSize(int fd) {
433 platform_stat64 info;
434 1594 const int retval = platform_fstat(fd, &info);
435
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 1548 times.
1594 if (retval != 0)
436 46 return -errno;
437 1548 return info.st_size;
438 }
439
440
441 3986 int PosixCacheManager::Open(const LabeledObject &object) {
442
1/2
✓ Branch 1 taken 3986 times.
✗ Branch 2 not taken.
3986 const string path = GetPathInCache(object.id);
443 int result;
444
1/2
✓ Branch 0 taken 3986 times.
✗ Branch 1 not taken.
3986 if (do_refcount_) {
445
1/2
✓ Branch 2 taken 3986 times.
✗ Branch 3 not taken.
3986 result = fd_mgr_->Open(object.id, path);
446 } else {
447 ✗ result = open(path.c_str(), O_RDONLY);
448 }
449
2/2
✓ Branch 0 taken 1363 times.
✓ Branch 1 taken 2623 times.
3986 if (result >= 0) {
450
1/2
✓ Branch 2 taken 1363 times.
✗ Branch 3 not taken.
1363 LogCvmfs(kLogCache, kLogDebug, "hit %s", path.c_str());
451 // platform_disable_kcache(result);
452
1/2
✓ Branch 1 taken 1363 times.
✗ Branch 2 not taken.
1363 quota_mgr_->Touch(object.id);
453 } else {
454 2623 result = -errno;
455
1/2
✓ Branch 2 taken 2623 times.
✗ Branch 3 not taken.
2623 LogCvmfs(kLogCache, kLogDebug, "miss %s (%d)", path.c_str(), result);
456 }
457 3986 return result;
458 3986 }
459
460
461 1058 int PosixCacheManager::OpenFromTxn(void *txn) {
462 1058 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
463 1058 const int retval = Flush(transaction);
464
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 1012 times.
1058 if (retval < 0)
465 46 return retval;
466 int fd_rdonly;
467
468
1/2
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
1012 if (do_refcount_) {
469
2/4
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1012 times.
✗ Branch 8 not taken.
1012 fd_rdonly = fd_mgr_->Open(transaction->id, transaction->tmp_path.c_str());
470 } else {
471 ✗ fd_rdonly = open(transaction->tmp_path.c_str(), O_RDONLY);
472 }
473
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 966 times.
1012 if (fd_rdonly == -1)
474 46 return -errno;
475 966 return fd_rdonly;
476 }
477
478
479 10629 int64_t PosixCacheManager::Pread(int fd,
480 void *buf,
481 uint64_t size,
482 uint64_t offset) {
483 int64_t result;
484 do {
485 10629 errno = 0;
486 10629 result = pread(fd, buf, size, offset);
487
3/4
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 10537 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 92 times.
10629 } while ((result == -1) && (errno == EINTR));
488
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 10537 times.
10629 if (result < 0)
489 92 return -errno;
490 10537 return result;
491 }
492
493
494 5511 int PosixCacheManager::Rename(const char *oldpath, const char *newpath) {
495 int result;
496
2/2
✓ Branch 0 taken 5373 times.
✓ Branch 1 taken 138 times.
5511 if (rename_workaround_ != kRenameLink) {
497 5373 result = rename(oldpath, newpath);
498
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 5235 times.
5373 if (result < 0)
499 138 return -errno;
500 5235 return 0;
501 }
502
503 138 result = link(oldpath, newpath);
504
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 46 times.
138 if (result < 0) {
505
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 46 times.
92 if (errno == EEXIST) {
506 46 LogCvmfs(kLogCache, kLogDebug, "%s already existed, ignoring", newpath);
507 } else {
508 46 return -errno;
509 }
510 }
511 92 result = unlink(oldpath);
512
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (result < 0)
513 ✗ return -errno;
514 92 return 0;
515 }
516
517
518 /**
519 * Used by the sqlite vfs in order to preload file catalogs into the file system
520 * buffers.
521 *
522 * No-op if the fd is to a file that is on a tmpfs, and so already in page cache
523 */
524 597 int PosixCacheManager::Readahead(int fd) {
525 unsigned char *buf[4096];
526 int nbytes;
527 597 uint64_t pos = 0;
528
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 597 times.
597 if (is_tmpfs()) {
529 ✗ return 0;
530 }
531 do {
532
1/2
✓ Branch 1 taken 2953 times.
✗ Branch 2 not taken.
2953 nbytes = Pread(fd, buf, 4096, pos);
533 2953 pos += nbytes;
534
2/2
✓ Branch 0 taken 2356 times.
✓ Branch 1 taken 597 times.
2953 } while (nbytes == 4096);
535
1/2
✓ Branch 1 taken 597 times.
✗ Branch 2 not taken.
597 LogCvmfs(kLogCache, kLogDebug, "read-ahead %d, %" PRIu64, fd, pos);
536
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 597 times.
597 if (nbytes < 0)
537 ✗ return nbytes;
538 597 return 0;
539 }
540
541
542 331 int PosixCacheManager::Reset(void *txn) {
543 331 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
544 331 transaction->buf_pos = 0;
545 331 transaction->size = 0;
546 331 int retval = lseek(transaction->fd, 0, SEEK_SET);
547
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 285 times.
331 if (retval < 0)
548 46 return -errno;
549 285 retval = ftruncate(transaction->fd, 0);
550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 285 times.
285 if (retval < 0)
551 ✗ return -errno;
552 285 return 0;
553 }
554
555
556 6408 int PosixCacheManager::StartTxn(const shash::Any &id,
557 uint64_t size,
558 void *txn) {
559 // Materialize the cache skeleton on first use. For alien caches this is
560 // deferred from mount time to here, so that a bogus fqrn never creates any
561 // directories (see #4217); for regular caches this is a lock-free no-op.
562
2/4
✓ Branch 1 taken 6408 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6408 times.
6408 if (!EnsureCacheDirectories())
563 ✗ return -EIO;
564
565 6408 atomic_inc32(&no_inflight_txns_);
566
2/2
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 6365 times.
6408 if (cache_mode_ == kCacheReadOnly) {
567 43 atomic_dec32(&no_inflight_txns_);
568 43 return -EROFS;
569 }
570
571
2/2
✓ Branch 0 taken 5396 times.
✓ Branch 1 taken 969 times.
6365 if (size != kSizeUnknown) {
572
3/4
✓ Branch 1 taken 5396 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 5350 times.
5396 if (size > quota_mgr_->GetMaxFileSize()) {
573
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 LogCvmfs(kLogCache, kLogDebug,
574 "file too big for lru cache (%" PRIu64 " "
575 "requested but only %" PRIu64 " bytes free)",
576
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 size, quota_mgr_->GetMaxFileSize());
577 46 atomic_dec32(&no_inflight_txns_);
578 46 return -ENOSPC;
579 }
580
581 // For large files, ensure enough free cache space before writing the chunk
582
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 5304 times.
5350 if (size > kBigFile) {
583
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 const uint64_t cache_size = quota_mgr_->GetSize();
584
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 const uint64_t cache_capacity = quota_mgr_->GetCapacity();
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 assert(cache_capacity >= size);
586
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if ((cache_size + size) > cache_capacity) {
587 92 const uint64_t leave_size = std::min(cache_capacity / 2,
588 46 cache_capacity - size);
589
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 quota_mgr_->Cleanup(leave_size);
590 }
591 }
592 }
593
594
1/2
✓ Branch 1 taken 6319 times.
✗ Branch 2 not taken.
6319 string path_in_cache = GetPathInCache(id);
595
1/2
✓ Branch 2 taken 6319 times.
✗ Branch 3 not taken.
6319 Transaction *transaction = new (txn) Transaction(id, path_in_cache);
596
597 6319 char *template_path = NULL;
598 6319 unsigned temp_path_len = 0;
599
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 6227 times.
6319 if (rename_workaround_ == kRenameSamedir) {
600 92 temp_path_len = path_in_cache.length() + 6;
601 92 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
602 92 memcpy(template_path, path_in_cache.data(), path_in_cache.length());
603 92 memset(template_path + path_in_cache.length(), 'X', 6);
604 } else {
605 6227 temp_path_len = txn_template_path_.length();
606 6227 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
607
1/2
✓ Branch 1 taken 6227 times.
✗ Branch 2 not taken.
6227 memcpy(template_path, &txn_template_path_[0], temp_path_len);
608 }
609 6319 template_path[temp_path_len] = '\0';
610
611
1/2
✓ Branch 1 taken 6319 times.
✗ Branch 2 not taken.
6319 transaction->fd = mkstemp(template_path);
612
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 6237 times.
6319 if (transaction->fd == -1) {
613 82 transaction->~Transaction();
614 82 atomic_dec32(&no_inflight_txns_);
615 82 return -errno;
616 }
617
618
1/2
✓ Branch 1 taken 6237 times.
✗ Branch 2 not taken.
6237 LogCvmfs(kLogCache, kLogDebug, "start transaction on %s has result %d",
619 template_path, transaction->fd);
620
1/2
✓ Branch 1 taken 6237 times.
✗ Branch 2 not taken.
6237 transaction->tmp_path = template_path;
621 6237 transaction->expected_size = size;
622 6237 return transaction->fd;
623 6319 }
624
625
626 216 manifest::Breadcrumb PosixCacheManager::LoadBreadcrumb(
627 const std::string &fqrn) {
628 216 return manifest::Manifest::ReadBreadcrumb(fqrn, cache_path_);
629 }
630
631
632 62 bool PosixCacheManager::StoreBreadcrumb(const manifest::Manifest &manifest) {
633 62 return manifest.ExportBreadcrumb(cache_path_, 0600);
634 }
635
636
637 ✗ bool PosixCacheManager::StoreBreadcrumb(std::string fqrn,
638 manifest::Breadcrumb breadcrumb) {
639 ✗ return breadcrumb.Export(fqrn, cache_path_, 0600);
640 }
641
642
643 175 void PosixCacheManager::TearDown2ReadOnly() {
644 175 cache_mode_ = kCacheReadOnly;
645
2/2
✓ Branch 1 taken 635 times.
✓ Branch 2 taken 129 times.
764 while (atomic_read32(&no_inflight_txns_) != 0)
646 635 SafeSleepMs(50);
647
648 129 QuotaManager *old_manager = quota_mgr_;
649
1/2
✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
129 quota_mgr_ = new NoopQuotaManager();
650
1/2
✓ Branch 0 taken 129 times.
✗ Branch 1 not taken.
129 delete old_manager;
651 129 }
652
653
654 6203 int64_t PosixCacheManager::Write(const void *buf, uint64_t size, void *txn) {
655 6203 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
656
657
2/2
✓ Branch 0 taken 4819 times.
✓ Branch 1 taken 1384 times.
6203 if (transaction->expected_size != kSizeUnknown) {
658
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 4773 times.
4819 if (transaction->size + size > transaction->expected_size) {
659 46 LogCvmfs(kLogCache, kLogDebug,
660 "Transaction size (%" PRIu64 ") > expected size (%" PRIu64 ")",
661 46 transaction->size + size, transaction->expected_size);
662 46 return -EFBIG;
663 }
664 }
665
666 6157 uint64_t written = 0;
667 6157 const unsigned char *read_pos = reinterpret_cast<const unsigned char *>(buf);
668
2/2
✓ Branch 0 taken 7327 times.
✓ Branch 1 taken 6111 times.
13438 while (written < size) {
669
2/2
✓ Branch 0 taken 3110 times.
✓ Branch 1 taken 4217 times.
7327 if (transaction->buf_pos == sizeof(transaction->buffer)) {
670
1/2
✓ Branch 1 taken 3110 times.
✗ Branch 2 not taken.
3110 const int retval = Flush(transaction);
671
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 3064 times.
3110 if (retval != 0) {
672 46 transaction->size += written;
673 46 return retval;
674 }
675 }
676 7281 const uint64_t remaining = size - written;
677 7281 const uint64_t space_in_buffer = sizeof(transaction->buffer)
678 7281 - transaction->buf_pos;
679 7281 const uint64_t batch_size = std::min(remaining, space_in_buffer);
680 7281 memcpy(transaction->buffer + transaction->buf_pos, read_pos, batch_size);
681 7281 transaction->buf_pos += batch_size;
682 7281 written += batch_size;
683 7281 read_pos += batch_size;
684 }
685 6111 transaction->size += written;
686 6111 return written;
687 }
688