GCC Code Coverage Report


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