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