Directory: | cvmfs/ |
---|---|
File: | cvmfs/cache.cc |
Date: | 2025-07-13 02:35:07 |
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 4726 times.
✗ Branch 4 not taken.
|
4726 | CacheManager::CacheManager() : quota_mgr_(new NoopQuotaManager()) { } |
28 | |||
29 | |||
30 |
2/2✓ Branch 0 taken 4556 times.
✓ Branch 1 taken 156 times.
|
9424 | 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 | 184 | int CacheManager::ChecksumFd(int fd, shash::Any *id) { | |
38 |
1/2✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
|
184 | shash::ContextPtr hash_context(id->algorithm); |
39 | 184 | hash_context.buffer = alloca(hash_context.size); | |
40 |
1/2✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
|
184 | shash::Init(hash_context); |
41 | |||
42 | z_stream strm; | ||
43 |
1/2✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
|
184 | zlib::CompressInit(&strm); |
44 | zlib::StreamStates retval; | ||
45 | |||
46 | unsigned char buf[4096]; | ||
47 | 184 | uint64_t pos = 0; | |
48 | bool eof; | ||
49 | |||
50 | do { | ||
51 |
1/2✓ Branch 1 taken 230 times.
✗ Branch 2 not taken.
|
230 | const int64_t nbytes = Pread(fd, buf, 4096, pos); |
52 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 184 times.
|
230 | if (nbytes < 0) { |
53 |
1/2✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
|
46 | zlib::CompressFini(&strm); |
54 | 46 | return nbytes; | |
55 | } | ||
56 | 184 | pos += nbytes; | |
57 | 184 | eof = nbytes < 4096; | |
58 |
1/2✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
|
184 | retval = zlib::CompressZStream2Null(buf, nbytes, eof, &strm, &hash_context); |
59 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 184 times.
|
184 | if (retval == zlib::kStreamDataError) { |
60 | ✗ | zlib::CompressFini(&strm); | |
61 | ✗ | return -EINVAL; | |
62 | } | ||
63 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 138 times.
|
184 | } while (!eof); |
64 | |||
65 |
1/2✓ Branch 1 taken 138 times.
✗ Branch 2 not taken.
|
138 | zlib::CompressFini(&strm); |
66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 138 times.
|
138 | if (retval != zlib::kStreamEnd) |
67 | ✗ | return -EINVAL; | |
68 |
1/2✓ Branch 1 taken 138 times.
✗ Branch 2 not taken.
|
138 | shash::Final(hash_context, id); |
69 | 138 | 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 | 5670 | bool CacheManager::CommitFromMem(const LabeledObject &object, | |
78 | const unsigned char *buffer, | ||
79 | const uint64_t size) { | ||
80 | 5670 | void *txn = alloca(this->SizeOfTxn()); | |
81 | 5670 | const int fd = this->StartTxn(object.id, size, txn); | |
82 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5663 times.
|
5670 | if (fd < 0) |
83 | 7 | return false; | |
84 | 5663 | this->CtrlTxn(object.label, 0, txn); | |
85 | 5663 | int64_t retval = this->Write(buffer, size, txn); | |
86 |
3/4✓ Branch 0 taken 5620 times.
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5620 times.
|
5663 | if ((retval < 0) || (static_cast<uint64_t>(retval) != size)) { |
87 | 43 | this->AbortTxn(txn); | |
88 | 43 | return false; | |
89 | } | ||
90 | 5620 | retval = this->CommitTxn(txn); | |
91 | 5620 | return retval == 0; | |
92 | } | ||
93 | |||
94 | |||
95 | 100 | void CacheManager::FreeState(const int fd_progress, void *data) { | |
96 | 100 | State *state = reinterpret_cast<State *>(data); | |
97 |
2/2✓ Branch 0 taken 86 times.
✓ Branch 1 taken 14 times.
|
100 | if (fd_progress >= 0) |
98 |
2/4✓ Branch 2 taken 86 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 86 times.
✗ Branch 6 not taken.
|
86 | SendMsg2Socket(fd_progress, "Releasing saved open files table\n"); |
99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
|
100 | assert(state->version == kStateVersion); |
100 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
|
100 | assert(state->manager_type == id()); |
101 | 100 | const bool result = DoFreeState(state->concrete_state); | |
102 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
|
100 | 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 100 times.
✗ Branch 1 not taken.
|
100 | delete state; |
110 | 100 | } | |
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 | 389 | bool CacheManager::Open2Mem(const LabeledObject &object, | |
123 | unsigned char **buffer, | ||
124 | uint64_t *size) { | ||
125 | 389 | *size = 0; | |
126 | 389 | *buffer = NULL; | |
127 | |||
128 | 389 | const int fd = this->Open(object); | |
129 |
2/2✓ Branch 0 taken 86 times.
✓ Branch 1 taken 296 times.
|
382 | if (fd < 0) |
130 | 86 | return false; | |
131 | |||
132 | 296 | const int64_t s = this->GetSize(fd); | |
133 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 303 times.
|
303 | assert(s >= 0); |
134 | 303 | *size = static_cast<uint64_t>(s); | |
135 | |||
136 | 303 | int64_t retval = 0; | |
137 |
2/2✓ Branch 0 taken 248 times.
✓ Branch 1 taken 55 times.
|
303 | if (*size > 0) { |
138 | 248 | *buffer = static_cast<unsigned char *>(smalloc(*size)); | |
139 | 248 | retval = this->Pread(fd, *buffer, *size, 0); | |
140 | } else { | ||
141 | 55 | *buffer = NULL; | |
142 | } | ||
143 | |||
144 | 303 | this->Close(fd); | |
145 |
3/4✓ Branch 0 taken 257 times.
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 257 times.
|
303 | if ((retval < 0) || (static_cast<uint64_t>(retval) != *size)) { |
146 | 46 | free(*buffer); | |
147 | 46 | *buffer = NULL; | |
148 | 46 | *size = 0; | |
149 | 46 | return false; | |
150 | } | ||
151 | 257 | 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 | 1268 | int CacheManager::OpenPinned(const LabeledObject &object) { | |
163 | 1268 | const int fd = this->Open(object); | |
164 |
2/2✓ Branch 0 taken 243 times.
✓ Branch 1 taken 1025 times.
|
1268 | if (fd >= 0) { |
165 | 243 | const int64_t size = this->GetSize(fd); | |
166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 243 times.
|
243 | if (size < 0) { |
167 | ✗ | this->Close(fd); | |
168 | ✗ | return size; | |
169 | } | ||
170 |
1/2✓ Branch 1 taken 243 times.
✗ Branch 2 not taken.
|
243 | const bool retval = quota_mgr_->Pin(object.id, static_cast<uint64_t>(size), |
171 | 486 | object.label.GetDescription(), | |
172 | 243 | object.label.IsCatalog()); | |
173 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 200 times.
|
243 | if (!retval) { |
174 | 43 | this->Close(fd); | |
175 | 43 | return -ENOSPC; | |
176 | } | ||
177 | } | ||
178 | 1225 | return fd; | |
179 | } | ||
180 | |||
181 | |||
182 | 102 | int CacheManager::RestoreState(const int fd_progress, void *data) { | |
183 | 102 | State *state = reinterpret_cast<State *>(data); | |
184 |
2/2✓ Branch 0 taken 88 times.
✓ Branch 1 taken 14 times.
|
102 | if (fd_progress >= 0) |
185 |
2/4✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 88 times.
✗ Branch 6 not taken.
|
88 | SendMsg2Socket(fd_progress, "Restoring open files table... "); |
186 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
|
102 | 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 102 times.
|
102 | if (state->manager_type != id()) { |
192 | ✗ | if (fd_progress >= 0) | |
193 | ✗ | SendMsg2Socket(fd_progress, "switching cache manager unsupported!\n"); | |
194 | ✗ | abort(); | |
195 | } | ||
196 | 102 | const int new_root_fd = DoRestoreState(state->concrete_state); | |
197 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
|
102 | 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 88 times.
✓ Branch 1 taken 14 times.
|
102 | if (fd_progress >= 0) |
203 |
2/4✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 88 times.
✗ Branch 6 not taken.
|
88 | SendMsg2Socket(fd_progress, "done\n"); |
204 | 102 | return new_root_fd; | |
205 | } | ||
206 | |||
207 | |||
208 | /** | ||
209 | * The actual work is done in the concrete cache managers. | ||
210 | */ | ||
211 | 102 | void *CacheManager::SaveState(const int fd_progress) { | |
212 |
2/2✓ Branch 0 taken 88 times.
✓ Branch 1 taken 14 times.
|
102 | if (fd_progress >= 0) |
213 |
2/4✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 88 times.
✗ Branch 6 not taken.
|
88 | SendMsg2Socket(fd_progress, "Saving open files table\n"); |
214 | 102 | State *state = new State(); | |
215 | 102 | state->manager_type = id(); | |
216 | 102 | state->concrete_state = DoSaveState(); | |
217 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 102 times.
|
102 | 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 | 102 | return state; | |
226 | } | ||
227 |