| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/cache.cc |
| Date: | 2026-04-26 02:35:59 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 95 | 115 | 82.6% |
| Branches: | 60 | 124 | 48.4% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * This file is part of the CernVM File System. | ||
| 3 | */ | ||
| 4 | |||
| 5 | #include "cache.h" | ||
| 6 | |||
| 7 | #include <alloca.h> | ||
| 8 | #include <errno.h> | ||
| 9 | |||
| 10 | #include <cassert> | ||
| 11 | #include <cstdlib> | ||
| 12 | #include <string> | ||
| 13 | |||
| 14 | #include "compression/compression.h" | ||
| 15 | #include "crypto/hash.h" | ||
| 16 | #include "quota.h" | ||
| 17 | #include "util/posix.h" | ||
| 18 | #include "util/smalloc.h" | ||
| 19 | |||
| 20 | using namespace std; // NOLINT | ||
| 21 | |||
| 22 | const uint64_t CacheManager::kSizeUnknown = uint64_t(-1); | ||
| 23 | |||
| 24 | |||
| 25 |
1/2✓ Branch 3 taken 8604 times.
✗ Branch 4 not taken.
|
8604 | CacheManager::CacheManager() : quota_mgr_(new NoopQuotaManager()) { } |
| 26 | |||
| 27 | |||
| 28 |
2/2✓ Branch 0 taken 8227 times.
✓ Branch 1 taken 363 times.
|
17180 | CacheManager::~CacheManager() { delete quota_mgr_; } |
| 29 | |||
| 30 | |||
| 31 | /** | ||
| 32 | * Compresses and checksums the file pointed to by fd. The hash algorithm needs | ||
| 33 | * to be set in id. | ||
| 34 | */ | ||
| 35 | 168 | int CacheManager::ChecksumFd(int fd, shash::Any *id) { | |
| 36 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
168 | shash::ContextPtr hash_context(id->algorithm); |
| 37 | 168 | hash_context.buffer = alloca(hash_context.size); | |
| 38 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
168 | shash::Init(hash_context); |
| 39 | |||
| 40 | z_stream strm; | ||
| 41 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
168 | zlib::CompressInit(&strm); |
| 42 | zlib::StreamStates retval; | ||
| 43 | |||
| 44 | unsigned char buf[4096]; | ||
| 45 | 168 | uint64_t pos = 0; | |
| 46 | bool eof; | ||
| 47 | |||
| 48 | do { | ||
| 49 |
1/2✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
|
210 | const int64_t nbytes = Pread(fd, buf, 4096, pos); |
| 50 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 168 times.
|
210 | if (nbytes < 0) { |
| 51 |
1/2✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
|
42 | zlib::CompressFini(&strm); |
| 52 | 42 | return nbytes; | |
| 53 | } | ||
| 54 | 168 | pos += nbytes; | |
| 55 | 168 | eof = nbytes < 4096; | |
| 56 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
168 | retval = zlib::CompressZStream2Null(buf, nbytes, eof, &strm, &hash_context); |
| 57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 168 times.
|
168 | if (retval == zlib::kStreamDataError) { |
| 58 | ✗ | zlib::CompressFini(&strm); | |
| 59 | ✗ | return -EINVAL; | |
| 60 | } | ||
| 61 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 126 times.
|
168 | } while (!eof); |
| 62 | |||
| 63 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | zlib::CompressFini(&strm); |
| 64 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
|
126 | if (retval != zlib::kStreamEnd) |
| 65 | ✗ | return -EINVAL; | |
| 66 |
1/2✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
|
126 | shash::Final(hash_context, id); |
| 67 | 126 | return 0; | |
| 68 | } | ||
| 69 | |||
| 70 | |||
| 71 | /** | ||
| 72 | * Commits the memory blob buffer to the given chunk id. No checking! | ||
| 73 | * The hash and the memory blob need to match. | ||
| 74 | */ | ||
| 75 | 5964 | bool CacheManager::CommitFromMem(const LabeledObject &object, | |
| 76 | const unsigned char *buffer, | ||
| 77 | const uint64_t size) { | ||
| 78 | 5964 | void *txn = alloca(this->SizeOfTxn()); | |
| 79 | 5964 | const int fd = this->StartTxn(object.id, size, txn); | |
| 80 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 5925 times.
|
5964 | if (fd < 0) |
| 81 | 39 | return false; | |
| 82 | 5925 | this->CtrlTxn(object.label, 0, txn); | |
| 83 | 5925 | int64_t retval = this->Write(buffer, size, txn); | |
| 84 |
3/4✓ Branch 0 taken 5883 times.
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5883 times.
|
5925 | if ((retval < 0) || (static_cast<uint64_t>(retval) != size)) { |
| 85 | 42 | this->AbortTxn(txn); | |
| 86 | 42 | return false; | |
| 87 | } | ||
| 88 | 5883 | retval = this->CommitTxn(txn); | |
| 89 | 5883 | return retval == 0; | |
| 90 | } | ||
| 91 | |||
| 92 | |||
| 93 | 156 | void CacheManager::FreeState(const int fd_progress, void *data) { | |
| 94 | 156 | State *state = reinterpret_cast<State *>(data); | |
| 95 |
2/2✓ Branch 0 taken 78 times.
✓ Branch 1 taken 78 times.
|
156 | if (fd_progress >= 0) |
| 96 |
2/4✓ Branch 2 taken 78 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 78 times.
✗ Branch 6 not taken.
|
78 | SendMsg2Socket(fd_progress, "Releasing saved open files table\n"); |
| 97 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 156 times.
|
156 | assert(state->version == kStateVersion); |
| 98 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 156 times.
|
156 | assert(state->manager_type == id()); |
| 99 | 156 | const bool result = DoFreeState(state->concrete_state); | |
| 100 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 156 times.
|
156 | if (!result) { |
| 101 | ✗ | if (fd_progress >= 0) { | |
| 102 | ✗ | SendMsg2Socket(fd_progress, | |
| 103 | " *** Releasing open files table failed!\n"); | ||
| 104 | } | ||
| 105 | ✗ | abort(); | |
| 106 | } | ||
| 107 |
1/2✓ Branch 0 taken 156 times.
✗ Branch 1 not taken.
|
156 | delete state; |
| 108 | 156 | } | |
| 109 | |||
| 110 | |||
| 111 | /** | ||
| 112 | * Tries to open a file and copies its contents into a newly malloc'd | ||
| 113 | * memory area. User of the function has to free buffer (if successful). | ||
| 114 | * | ||
| 115 | * @param[in] id content hash of the catalog entry. | ||
| 116 | * @param[out] buffer Contents of the file | ||
| 117 | * @param[out] size Size of the file | ||
| 118 | * \return True if successful, false otherwise. | ||
| 119 | */ | ||
| 120 | 1045 | bool CacheManager::Open2Mem(const LabeledObject &object, | |
| 121 | unsigned char **buffer, | ||
| 122 | uint64_t *size) { | ||
| 123 | 1045 | *size = 0; | |
| 124 | 1045 | *buffer = NULL; | |
| 125 | |||
| 126 | 1045 | const int fd = this->Open(object); | |
| 127 |
2/2✓ Branch 0 taken 307 times.
✓ Branch 1 taken 738 times.
|
1045 | if (fd < 0) |
| 128 | 307 | return false; | |
| 129 | |||
| 130 | 738 | const int64_t s = this->GetSize(fd); | |
| 131 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 738 times.
|
738 | assert(s >= 0); |
| 132 | 738 | *size = static_cast<uint64_t>(s); | |
| 133 | |||
| 134 | 738 | int64_t retval = 0; | |
| 135 |
2/2✓ Branch 0 taken 655 times.
✓ Branch 1 taken 83 times.
|
738 | if (*size > 0) { |
| 136 | 655 | *buffer = static_cast<unsigned char *>(smalloc(*size)); | |
| 137 | 655 | retval = this->Pread(fd, *buffer, *size, 0); | |
| 138 | } else { | ||
| 139 | 83 | *buffer = NULL; | |
| 140 | } | ||
| 141 | |||
| 142 | 738 | this->Close(fd); | |
| 143 |
3/4✓ Branch 0 taken 696 times.
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 696 times.
|
738 | if ((retval < 0) || (static_cast<uint64_t>(retval) != *size)) { |
| 144 | 42 | free(*buffer); | |
| 145 | 42 | *buffer = NULL; | |
| 146 | 42 | *size = 0; | |
| 147 | 42 | return false; | |
| 148 | } | ||
| 149 | 696 | return true; | |
| 150 | } | ||
| 151 | |||
| 152 | |||
| 153 | /** | ||
| 154 | * Uses the regular open and, if the file exists in the cache, pins it. There | ||
| 155 | * is a race condition: the file can be removed between the open and the Pin. | ||
| 156 | * This is fixed by the quota manager's unpin method that removes files which | ||
| 157 | * do not exist anymore in the cache. (The quota manager also translates double | ||
| 158 | * pins into a no-op, so that the accounting does not get out of sync.) | ||
| 159 | */ | ||
| 160 | 2513 | int CacheManager::OpenPinned(const LabeledObject &object) { | |
| 161 | 2513 | const int fd = this->Open(object); | |
| 162 |
2/2✓ Branch 0 taken 589 times.
✓ Branch 1 taken 1924 times.
|
2513 | if (fd >= 0) { |
| 163 | 589 | const int64_t size = this->GetSize(fd); | |
| 164 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 589 times.
|
589 | if (size < 0) { |
| 165 | ✗ | this->Close(fd); | |
| 166 | ✗ | return size; | |
| 167 | } | ||
| 168 |
1/2✓ Branch 1 taken 589 times.
✗ Branch 2 not taken.
|
589 | const bool retval = quota_mgr_->Pin(object.id, static_cast<uint64_t>(size), |
| 169 | 1178 | object.label.GetDescription(), | |
| 170 | 589 | object.label.IsCatalog()); | |
| 171 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 547 times.
|
589 | if (!retval) { |
| 172 | 42 | this->Close(fd); | |
| 173 | 42 | return -ENOSPC; | |
| 174 | } | ||
| 175 | } | ||
| 176 | 2471 | return fd; | |
| 177 | } | ||
| 178 | |||
| 179 | |||
| 180 | 158 | int CacheManager::RestoreState(const int fd_progress, void *data) { | |
| 181 | 158 | State *state = reinterpret_cast<State *>(data); | |
| 182 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 78 times.
|
158 | if (fd_progress >= 0) |
| 183 |
2/4✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80 times.
✗ Branch 6 not taken.
|
80 | SendMsg2Socket(fd_progress, "Restoring open files table... "); |
| 184 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
|
158 | if (state->version != kStateVersion) { |
| 185 | ✗ | if (fd_progress >= 0) | |
| 186 | ✗ | SendMsg2Socket(fd_progress, "unsupported state version!\n"); | |
| 187 | ✗ | abort(); | |
| 188 | } | ||
| 189 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
|
158 | if (state->manager_type != id()) { |
| 190 | ✗ | if (fd_progress >= 0) | |
| 191 | ✗ | SendMsg2Socket(fd_progress, "switching cache manager unsupported!\n"); | |
| 192 | ✗ | abort(); | |
| 193 | } | ||
| 194 | 158 | const int new_root_fd = DoRestoreState(state->concrete_state); | |
| 195 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
|
158 | if (new_root_fd < -1) { |
| 196 | ✗ | if (fd_progress >= 0) | |
| 197 | ✗ | SendMsg2Socket(fd_progress, "FAILED!\n"); | |
| 198 | ✗ | abort(); | |
| 199 | } | ||
| 200 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 78 times.
|
158 | if (fd_progress >= 0) |
| 201 |
2/4✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80 times.
✗ Branch 6 not taken.
|
80 | SendMsg2Socket(fd_progress, "done\n"); |
| 202 | 158 | return new_root_fd; | |
| 203 | } | ||
| 204 | |||
| 205 | |||
| 206 | /** | ||
| 207 | * The actual work is done in the concrete cache managers. | ||
| 208 | */ | ||
| 209 | 158 | void *CacheManager::SaveState(const int fd_progress) { | |
| 210 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 78 times.
|
158 | if (fd_progress >= 0) |
| 211 |
2/4✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80 times.
✗ Branch 6 not taken.
|
80 | SendMsg2Socket(fd_progress, "Saving open files table\n"); |
| 212 | 158 | State *state = new State(); | |
| 213 | 158 | state->manager_type = id(); | |
| 214 | 158 | state->concrete_state = DoSaveState(); | |
| 215 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
|
158 | if (state->concrete_state == NULL) { |
| 216 | ✗ | if (fd_progress >= 0) { | |
| 217 | ✗ | SendMsg2Socket( | |
| 218 | fd_progress, | ||
| 219 | " *** This cache manager does not support saving state!\n"); | ||
| 220 | } | ||
| 221 | ✗ | abort(); | |
| 222 | } | ||
| 223 | 158 | return state; | |
| 224 | } | ||
| 225 |