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