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