GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_posix.cc
Date: 2026-08-30 02:40:36
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 202 int PosixCacheManager::AbortTxn(void *txn) {
108 202 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
109 202 LogCvmfs(kLogCache, kLogDebug, "abort %s", transaction->tmp_path.c_str());
110 202 close(transaction->fd);
111 202 const int result = unlink(transaction->tmp_path.c_str());
112 202 transaction->~Transaction();
113 202 atomic_dec32(&no_inflight_txns_);
114
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 192 times.
202 if (result == -1)
115 10 return -errno;
116 192 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 1660 bool PosixCacheManager::AcquireQuotaManager(QuotaManager *quota_mgr) {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1660 times.
1660 if (quota_mgr == NULL)
127 return false;
128
1/2
✓ Branch 0 taken 1660 times.
✗ Branch 1 not taken.
1660 delete quota_mgr_;
129 1660 quota_mgr_ = quota_mgr;
130 1660 return true;
131 }
132
133
134 1814 int PosixCacheManager::Close(int fd) {
135
1/2
✓ Branch 0 taken 1814 times.
✗ Branch 1 not taken.
1814 const int retval = do_refcount_ ? fd_mgr_->Close(fd) : close(fd);
136
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1812 times.
1814 if (retval != 0)
137 2 return -errno;
138 1812 return 0;
139 }
140
141
142 1733 int PosixCacheManager::CommitTxn(void *txn) {
143 1733 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
144 int result;
145 1733 LogCvmfs(kLogCache, kLogDebug, "commit %s %s",
146 transaction->final_path.c_str(), transaction->tmp_path.c_str());
147
148 1733 result = Flush(transaction);
149 1733 close(transaction->fd);
150
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1731 times.
1733 if (result < 0) {
151 2 unlink(transaction->tmp_path.c_str());
152 2 transaction->~Transaction();
153 2 atomic_dec32(&no_inflight_txns_);
154 2 return result;
155 }
156
157 // To support debugging, move files into quarantine on file size mismatch
158
2/2
✓ Branch 0 taken 1039 times.
✓ Branch 1 taken 692 times.
1731 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 62 times.
✓ Branch 1 taken 977 times.
1039 if ((transaction->expected_size != kSizeUnknown)
162
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
62 && (reports_correct_filesize_ || (transaction->size != 0))) {
163
1/3
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
62 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
164 "size check failure for %s, expected %lu, got %lu",
165 124 transaction->id.ToString().c_str(), transaction->expected_size,
166 transaction->size);
167
1/2
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
62 CopyPath2Path(transaction->tmp_path,
168
2/4
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 62 times.
✗ Branch 6 not taken.
124 cache_path_ + "/quarantaine/" + transaction->id.ToString());
169 62 unlink(transaction->tmp_path.c_str());
170 62 transaction->~Transaction();
171 62 atomic_dec32(&no_inflight_txns_);
172 62 return -EIO;
173 }
174 }
175
176
2/2
✓ Branch 0 taken 1664 times.
✓ Branch 1 taken 5 times.
1669 if ((transaction->label.flags & kLabelPinned)
177
2/2
✓ Branch 0 taken 854 times.
✓ Branch 1 taken 810 times.
1664 || (transaction->label.flags & kLabelCatalog)) {
178 2577 const bool retval = quota_mgr_->Pin(
179
1/2
✓ Branch 1 taken 859 times.
✗ Branch 2 not taken.
859 transaction->id, transaction->size, transaction->label.GetDescription(),
180 859 (transaction->label.flags & kLabelCatalog));
181
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 854 times.
859 if (!retval) {
182
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 LogCvmfs(kLogCache, kLogDebug, "commit failed: cannot pin %s",
183 10 transaction->id.ToString().c_str());
184 5 unlink(transaction->tmp_path.c_str());
185 5 transaction->~Transaction();
186 5 atomic_dec32(&no_inflight_txns_);
187 5 return -ENOSPC;
188 }
189 }
190
191 // Move the temporary file into its final location
192
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1657 times.
1664 if (alien_cache_) {
193 7 const int retval = chmod(transaction->tmp_path.c_str(), 0660);
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 assert(retval == 0);
195 }
196 1664 result = Rename(transaction->tmp_path.c_str(),
197 transaction->final_path.c_str());
198
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1660 times.
1664 if (result < 0) {
199 4 LogCvmfs(kLogCache, kLogDebug, "commit failed: %s", strerror(errno));
200 4 unlink(transaction->tmp_path.c_str());
201
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if ((transaction->label.flags & kLabelPinned)
202
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 || (transaction->label.flags & kLabelCatalog)) {
203 2 quota_mgr_->Remove(transaction->id);
204 }
205 } else {
206 // Success, inform quota manager
207
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1655 times.
1660 if (transaction->label.flags & kLabelVolatile) {
208
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 quota_mgr_->InsertVolatile(transaction->id, transaction->size,
209 10 transaction->label.GetDescription());
210 1655 } else if (!transaction->label.IsCatalog()
211
6/6
✓ Branch 0 taken 808 times.
✓ Branch 1 taken 847 times.
✓ Branch 3 taken 803 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 803 times.
✓ Branch 6 taken 852 times.
1655 && !transaction->label.IsPinned()) {
212
1/2
✓ Branch 1 taken 803 times.
✗ Branch 2 not taken.
803 quota_mgr_->Insert(transaction->id, transaction->size,
213 1606 transaction->label.GetDescription());
214 }
215 }
216 1664 transaction->~Transaction();
217 1664 atomic_dec32(&no_inflight_txns_);
218 1664 return result;
219 }
220
221 4874 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 4594 times.
✓ Branch 1 taken 280 times.
4874 if (!alien_cache_) {
229
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4592 times.
4594 if (!EnsureCacheDirectories())
230 2 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 4872 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 4870 times.
4872 if (FileExists(cache_path + "/cvmfscatalog.cache")) {
235 2 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
236 "Not mounting on cvmfs 2.0.X cache");
237 2 return false;
238 }
239 4870 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 6571 bool PosixCacheManager::EnsureCacheDirectories() {
250
2/2
✓ Branch 1 taken 1970 times.
✓ Branch 2 taken 4601 times.
6571 if (atomic_read32(&cache_dirs_created_))
251 1970 return true;
252
253 4601 const MutexLockGuard guard(lock_cache_dirs_);
254
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4601 times.
4601 if (atomic_read32(&cache_dirs_created_))
255 return true;
256
257
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4594 times.
4601 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 4601 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4599 times.
4601 if (!MkdirDeep(cache_path_, mode, false)) {
262
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
263 "Failed to create cache directory %s", cache_path_.c_str());
264 2 return false;
265 }
266
267
1/2
✓ Branch 1 taken 4599 times.
✗ Branch 2 not taken.
4599 const FileSystemInfo fs_info = GetFileSystemInfo(cache_path_);
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4599 times.
4599 if (fs_info.type == kFsTypeTmpfs)
269 is_tmpfs_ = true;
270
271
2/4
✓ Branch 1 taken 4599 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4599 times.
4599 if (!MakeCacheDirectories(cache_path_, mode))
272 return false;
273
274
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4592 times.
4599 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 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 CreateFile(cache_path_ + "/.cvmfscache", 0600, true /* ignore_failure */);
279
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
280 "Cache directory structure created.");
281
1/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 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 7 default:
292 7 break;
293 }
294 }
295
296 4599 atomic_write32(&cache_dirs_created_, 1);
297 4599 return true;
298 4601 }
299
300 4874 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 4874 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4874 times.
✗ Branch 5 not taken.
4874 cache_path, alien_cache, do_refcount, cleanup_unused_first));
306
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4874 times.
4874 assert(cache_manager.get() != nullptr);
307
308 4874 cache_manager->rename_workaround_ = rename_workaround;
309
310
1/2
✓ Branch 2 taken 4874 times.
✗ Branch 3 not taken.
4874 const bool result_ = cache_manager->InitCacheDirectory(cache_path);
311
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4870 times.
4874 if (!result_) {
312 4 return NULL;
313 }
314
315 4870 return cache_manager.release();
316 4874 }
317
318
319 1842 void PosixCacheManager::CtrlTxn(const Label &label,
320 const int flags,
321 void *txn) {
322 1842 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
323 1842 transaction->label = label;
324 1842 }
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 2 void *PosixCacheManager::DoSaveState() {
345
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (do_refcount_) {
346 2 SavedState *state = new SavedState();
347 2 state->fd_mgr = std::unique_ptr<FdRefcountMgr>(fd_mgr_->Clone());
348 2 return state;
349 }
350 char *c = reinterpret_cast<char *>(smalloc(1));
351 *c = kMagicNoRefcount;
352 return c;
353 }
354
355
356 2 int PosixCacheManager::DoRestoreState(void *data) {
357
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 assert(data);
358
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (do_refcount_) {
359 2 SavedState *state = reinterpret_cast<SavedState *>(data);
360
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (state->magic_number == kMagicRefcount) {
361 2 LogCvmfs(kLogCache, kLogDebug,
362 "Restoring refcount cache manager from "
363 "refcounted posix cache manager");
364
365 2 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 2 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 2 bool PosixCacheManager::DoFreeState(void *data) {
390
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 assert(data);
391 2 SavedState *state = reinterpret_cast<SavedState *>(data);
392
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (state->magic_number == kMagicRefcount) {
393
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 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 2 return true;
400 }
401
402
403 64 int PosixCacheManager::Dup(int fd) {
404
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 const int new_fd = do_refcount_ ? fd_mgr_->Dup(fd) : dup(fd);
405
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 32 times.
64 if (new_fd < 0)
406 32 return -errno;
407 32 return new_fd;
408 }
409
410
411 5538 int PosixCacheManager::Flush(Transaction *transaction) {
412
2/2
✓ Branch 0 taken 1236 times.
✓ Branch 1 taken 4302 times.
5538 if (transaction->buf_pos == 0)
413 1236 return 0;
414 8604 const int written = write(transaction->fd, transaction->buffer,
415 4302 transaction->buf_pos);
416
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4293 times.
4302 if (written < 0)
417 9 return -errno;
418
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4293 times.
4293 if (static_cast<unsigned>(written) != transaction->buf_pos) {
419 transaction->buf_pos -= written;
420 return -EIO;
421 }
422 4293 transaction->buf_pos = 0;
423 4293 return 0;
424 }
425
426
427 5824 inline string PosixCacheManager::GetPathInCache(const shash::Any &id) {
428
2/4
✓ Branch 2 taken 5824 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5824 times.
✗ Branch 6 not taken.
11648 return cache_path_ + "/" + id.MakePathWithoutSuffix();
429 }
430
431
432 1777 int64_t PosixCacheManager::GetSize(int fd) {
433 platform_stat64 info;
434 1777 const int retval = platform_fstat(fd, &info);
435
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1772 times.
1777 if (retval != 0)
436 5 return -errno;
437 1772 return info.st_size;
438 }
439
440
441 3854 int PosixCacheManager::Open(const LabeledObject &object) {
442
1/2
✓ Branch 1 taken 3854 times.
✗ Branch 2 not taken.
3854 const string path = GetPathInCache(object.id);
443 int result;
444
1/2
✓ Branch 0 taken 3854 times.
✗ Branch 1 not taken.
3854 if (do_refcount_) {
445
1/2
✓ Branch 2 taken 3854 times.
✗ Branch 3 not taken.
3854 result = fd_mgr_->Open(object.id, path);
446 } else {
447 result = open(path.c_str(), O_RDONLY);
448 }
449
2/2
✓ Branch 0 taken 883 times.
✓ Branch 1 taken 2971 times.
3854 if (result >= 0) {
450
1/2
✓ Branch 2 taken 883 times.
✗ Branch 3 not taken.
883 LogCvmfs(kLogCache, kLogDebug, "hit %s", path.c_str());
451 // platform_disable_kcache(result);
452
1/2
✓ Branch 1 taken 883 times.
✗ Branch 2 not taken.
883 quota_mgr_->Touch(object.id);
453 } else {
454 2971 result = -errno;
455
1/2
✓ Branch 2 taken 2971 times.
✗ Branch 3 not taken.
2971 LogCvmfs(kLogCache, kLogDebug, "miss %s (%d)", path.c_str(), result);
456 }
457 3854 return result;
458 3854 }
459
460
461 1117 int PosixCacheManager::OpenFromTxn(void *txn) {
462 1117 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
463 1117 const int retval = Flush(transaction);
464
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1112 times.
1117 if (retval < 0)
465 5 return retval;
466 int fd_rdonly;
467
468
1/2
✓ Branch 0 taken 1112 times.
✗ Branch 1 not taken.
1112 if (do_refcount_) {
469
2/4
✓ Branch 4 taken 1112 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1112 times.
✗ Branch 8 not taken.
1112 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 5 times.
✓ Branch 1 taken 1107 times.
1112 if (fd_rdonly == -1)
474 5 return -errno;
475 1107 return fd_rdonly;
476 }
477
478
479 17290 int64_t PosixCacheManager::Pread(int fd,
480 void *buf,
481 uint64_t size,
482 uint64_t offset) {
483 int64_t result;
484 do {
485 17290 errno = 0;
486 17290 result = pread(fd, buf, size, offset);
487
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 17286 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
17290 } while ((result == -1) && (errno == EINTR));
488
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 17286 times.
17290 if (result < 0)
489 4 return -errno;
490 17286 return result;
491 }
492
493
494 1694 int PosixCacheManager::Rename(const char *oldpath, const char *newpath) {
495 int result;
496
2/2
✓ Branch 0 taken 1679 times.
✓ Branch 1 taken 15 times.
1694 if (rename_workaround_ != kRenameLink) {
497 1679 result = rename(oldpath, newpath);
498
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1670 times.
1679 if (result < 0)
499 9 return -errno;
500 1670 return 0;
501 }
502
503 15 result = link(oldpath, newpath);
504
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
15 if (result < 0) {
505
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (errno == EEXIST) {
506 5 LogCvmfs(kLogCache, kLogDebug, "%s already existed, ignoring", newpath);
507 } else {
508 5 return -errno;
509 }
510 }
511 10 result = unlink(oldpath);
512
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (result < 0)
513 return -errno;
514 10 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 1203 int PosixCacheManager::Readahead(int fd) {
525 unsigned char *buf[4096];
526 int nbytes;
527 1203 uint64_t pos = 0;
528
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1203 times.
1203 if (is_tmpfs()) {
529 return 0;
530 }
531 do {
532
1/2
✓ Branch 1 taken 5655 times.
✗ Branch 2 not taken.
5655 nbytes = Pread(fd, buf, 4096, pos);
533 5655 pos += nbytes;
534
2/2
✓ Branch 0 taken 4452 times.
✓ Branch 1 taken 1203 times.
5655 } while (nbytes == 4096);
535
1/2
✓ Branch 1 taken 1203 times.
✗ Branch 2 not taken.
1203 LogCvmfs(kLogCache, kLogDebug, "read-ahead %d, %" PRIu64, fd, pos);
536
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1203 times.
1203 if (nbytes < 0)
537 return nbytes;
538 1203 return 0;
539 }
540
541
542 186 int PosixCacheManager::Reset(void *txn) {
543 186 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
544 186 transaction->buf_pos = 0;
545 186 transaction->size = 0;
546 186 int retval = lseek(transaction->fd, 0, SEEK_SET);
547
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 184 times.
186 if (retval < 0)
548 2 return -errno;
549 184 retval = ftruncate(transaction->fd, 0);
550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 184 times.
184 if (retval < 0)
551 return -errno;
552 184 return 0;
553 }
554
555
556 1977 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 1977 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1977 times.
1977 if (!EnsureCacheDirectories())
563 return -EIO;
564
565 1977 atomic_inc32(&no_inflight_txns_);
566
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1975 times.
1977 if (cache_mode_ == kCacheReadOnly) {
567 2 atomic_dec32(&no_inflight_txns_);
568 2 return -EROFS;
569 }
570
571
2/2
✓ Branch 0 taken 813 times.
✓ Branch 1 taken 1162 times.
1975 if (size != kSizeUnknown) {
572
3/4
✓ Branch 1 taken 813 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 808 times.
813 if (size > quota_mgr_->GetMaxFileSize()) {
573
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 LogCvmfs(kLogCache, kLogDebug,
574 "file too big for lru cache (%" PRIu64 " "
575 "requested but only %" PRIu64 " bytes free)",
576
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 size, quota_mgr_->GetMaxFileSize());
577 5 atomic_dec32(&no_inflight_txns_);
578 5 return -ENOSPC;
579 }
580
581 // For large files, ensure enough free cache space before writing the chunk
582
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 803 times.
808 if (size > kBigFile) {
583
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 const uint64_t cache_size = quota_mgr_->GetSize();
584
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 const uint64_t cache_capacity = quota_mgr_->GetCapacity();
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(cache_capacity >= size);
586
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if ((cache_size + size) > cache_capacity) {
587 10 const uint64_t leave_size = std::min(cache_capacity / 2,
588 5 cache_capacity - size);
589
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 quota_mgr_->Cleanup(leave_size);
590 }
591 }
592 }
593
594
1/2
✓ Branch 1 taken 1970 times.
✗ Branch 2 not taken.
1970 string path_in_cache = GetPathInCache(id);
595
1/2
✓ Branch 2 taken 1970 times.
✗ Branch 3 not taken.
1970 Transaction *transaction = new (txn) Transaction(id, path_in_cache);
596
597 1970 char *template_path = NULL;
598 1970 unsigned temp_path_len = 0;
599
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1960 times.
1970 if (rename_workaround_ == kRenameSamedir) {
600 10 temp_path_len = path_in_cache.length() + 6;
601 10 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
602 10 memcpy(template_path, path_in_cache.data(), path_in_cache.length());
603 10 memset(template_path + path_in_cache.length(), 'X', 6);
604 } else {
605 1960 temp_path_len = txn_template_path_.length();
606 1960 template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
607
1/2
✓ Branch 1 taken 1960 times.
✗ Branch 2 not taken.
1960 memcpy(template_path, &txn_template_path_[0], temp_path_len);
608 }
609 1970 template_path[temp_path_len] = '\0';
610
611
1/2
✓ Branch 1 taken 1970 times.
✗ Branch 2 not taken.
1970 transaction->fd = mkstemp(template_path);
612
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 1935 times.
1970 if (transaction->fd == -1) {
613 35 transaction->~Transaction();
614 35 atomic_dec32(&no_inflight_txns_);
615 35 return -errno;
616 }
617
618
1/2
✓ Branch 1 taken 1935 times.
✗ Branch 2 not taken.
1935 LogCvmfs(kLogCache, kLogDebug, "start transaction on %s has result %d",
619 template_path, transaction->fd);
620
1/2
✓ Branch 1 taken 1935 times.
✗ Branch 2 not taken.
1935 transaction->tmp_path = template_path;
621 1935 transaction->expected_size = size;
622 1935 return transaction->fd;
623 1970 }
624
625
626 755 manifest::Breadcrumb PosixCacheManager::LoadBreadcrumb(
627 const std::string &fqrn) {
628 755 return manifest::Manifest::ReadBreadcrumb(fqrn, cache_path_);
629 }
630
631
632 318 bool PosixCacheManager::StoreBreadcrumb(const manifest::Manifest &manifest) {
633 318 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 8 void PosixCacheManager::TearDown2ReadOnly() {
644 8 cache_mode_ = kCacheReadOnly;
645
2/2
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 6 times.
34 while (atomic_read32(&no_inflight_txns_) != 0)
646 28 SafeSleepMs(50);
647
648 6 QuotaManager *old_manager = quota_mgr_;
649
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 quota_mgr_ = new NoopQuotaManager();
650
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 delete old_manager;
651 6 }
652
653
654 2609 int64_t PosixCacheManager::Write(const void *buf, uint64_t size, void *txn) {
655 2609 Transaction *transaction = reinterpret_cast<Transaction *>(txn);
656
657
2/2
✓ Branch 0 taken 760 times.
✓ Branch 1 taken 1849 times.
2609 if (transaction->expected_size != kSizeUnknown) {
658
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 758 times.
760 if (transaction->size + size > transaction->expected_size) {
659 2 LogCvmfs(kLogCache, kLogDebug,
660 "Transaction size (%" PRIu64 ") > expected size (%" PRIu64 ")",
661 2 transaction->size + size, transaction->expected_size);
662 2 return -EFBIG;
663 }
664 }
665
666 2607 uint64_t written = 0;
667 2607 const unsigned char *read_pos = reinterpret_cast<const unsigned char *>(buf);
668
2/2
✓ Branch 0 taken 4368 times.
✓ Branch 1 taken 2605 times.
6973 while (written < size) {
669
2/2
✓ Branch 0 taken 2688 times.
✓ Branch 1 taken 1680 times.
4368 if (transaction->buf_pos == sizeof(transaction->buffer)) {
670
1/2
✓ Branch 1 taken 2688 times.
✗ Branch 2 not taken.
2688 const int retval = Flush(transaction);
671
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2686 times.
2688 if (retval != 0) {
672 2 transaction->size += written;
673 2 return retval;
674 }
675 }
676 4366 const uint64_t remaining = size - written;
677 4366 const uint64_t space_in_buffer = sizeof(transaction->buffer)
678 4366 - transaction->buf_pos;
679 4366 const uint64_t batch_size = std::min(remaining, space_in_buffer);
680 4366 memcpy(transaction->buffer + transaction->buf_pos, read_pos, batch_size);
681 4366 transaction->buf_pos += batch_size;
682 4366 written += batch_size;
683 4366 read_pos += batch_size;
684 }
685 2605 transaction->size += written;
686 2605 return written;
687 }
688