GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/bundle_mgr.cc
Date: 2026-10-18 02:40:11
Exec Total Coverage
Lines: 202 276 73.2%
Branches: 147 364 40.4%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "bundle_mgr.h"
6
7 #include <fcntl.h>
8 #include <pthread.h>
9 #include <unistd.h>
10
11 #include <cassert>
12 #include <cerrno>
13 #include <cstdlib>
14 #include <cstring>
15 #include <memory>
16 #include <string>
17 #include <vector>
18
19 #include "cache.h"
20 #include "catalog_mgr_client.h"
21 #include "fetch.h"
22 #include "file_chunk.h"
23 #include "json_document.h"
24 #include "mountpoint.h"
25 #include "options.h"
26 #include "shortstring.h"
27 #include "util/posix.h"
28
29 namespace {
30 constexpr size_t kDefaultBundlePoolSize = 8;
31
32 // Read the .cvmfsbundle-<basename> file via the cvmfs cache
33 92 BundleFileMgr *LoadBundleFromCvmfs(MountPoint *mp,
34 const PathString &bundle_file_path) {
35
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 catalog::DirectoryEntry dirent;
36
2/4
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 92 times.
92 if (!mp->catalog_mgr()->LookupPath(bundle_file_path, catalog::kLookupDefault,
37 &dirent)) {
38 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-LOAD: LookupPath failed for %s",
39 ✗ bundle_file_path.ToString().c_str());
40 ✗ return nullptr;
41 }
42 92 cvmfs::Fetcher *fetcher = mp->fetcher();
43
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (fetcher == nullptr) {
44 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-LOAD: fetcher is null");
45 ✗ return nullptr;
46 }
47
48 92 CacheManager::Label label;
49
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 label.path = bundle_file_path.ToString();
50
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 label.size = dirent.size();
51 92 label.zip_algorithm = dirent.compression_algorithm();
52
2/4
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 92 times.
✗ Branch 6 not taken.
276 const int fd = fetcher->Fetch(
53
1/2
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
184 CacheManager::LabeledObject(dirent.checksum(), label));
54
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if (fd < 0) {
55
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-LOAD: Fetch returned fd=%d", fd);
56 92 return nullptr;
57 }
58
59 ✗ CacheManager *cache_mgr = mp->file_system()->cache_mgr();
60 ✗ std::string content;
61 ✗ content.resize(static_cast<size_t>(dirent.size()));
62 ✗ const int64_t n = cache_mgr->Pread(fd, &content[0], content.size(), 0);
63 ✗ cache_mgr->Close(fd);
64 ✗ if (n < 0 || static_cast<size_t>(n) != content.size()) {
65 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-LOAD: Pread returned %ld want %zu",
66 static_cast<long>(n), content.size());
67 ✗ return nullptr;
68 }
69
70 // The bundle file may start with a "#%CVMFS_BUNDLE version=..." header
71 // line (per file_bundle.h); strip any leading lines beginning with '#'
72 // before handing off to the strict JSON parser.
73 ✗ size_t json_start = 0;
74 ✗ while (json_start < content.size() && content[json_start] == '#') {
75 ✗ const size_t nl = content.find('\n', json_start);
76 ✗ if (nl == std::string::npos) {
77 ✗ json_start = content.size();
78 ✗ break;
79 }
80 ✗ json_start = nl + 1;
81 }
82 const std::string json_text = (json_start == 0) ? content
83 ✗ : content.substr(json_start);
84
85 ✗ JsonDocument *doc = JsonDocument::Create(json_text);
86 ✗ if (doc == nullptr) {
87 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
88 "BUNDLE-LOAD: JsonDocument::Create failed (size=%zu)",
89 json_text.size());
90 ✗ return nullptr;
91 }
92 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-LOAD: loaded bundle %s (%zu bytes)",
93 ✗ bundle_file_path.ToString().c_str(), content.size());
94 ✗ return new BundleFileMgr(doc);
95 92 }
96 } // namespace
97
98 414 BundleMgr::BundleMgr(MountPoint *mp)
99 414 : mount_point_(mp), fetcher_threads_(), pool_size_(kDefaultBundlePoolSize) {
100 414 terminating_.store(0);
101 414 pthread_mutex_init(&worker_read_mutex_, nullptr);
102
103 // Pool size override via CVMFS_BUNDLE_POOL_SIZE
104
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 if (mount_point_ != nullptr && mount_point_->file_system() != nullptr
105
3/6
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 414 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 414 times.
✗ Branch 7 not taken.
828 && mount_point_->file_system()->options_mgr() != nullptr) {
106 414 std::string opt;
107
3/6
✓ Branch 4 taken 414 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 414 times.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 414 times.
414 if (mount_point_->file_system()->options_mgr()->GetValue(
108 "CVMFS_BUNDLE_POOL_SIZE", &opt)) {
109 ✗ char *end = nullptr;
110 ✗ const unsigned long n = std::strtoul(opt.c_str(), &end, 10);
111 ✗ if (end != opt.c_str() && n >= 1) {
112 ✗ pool_size_ = static_cast<size_t>(n);
113 }
114 }
115 414 }
116
117 // The queues are created here so that ScheduleTrigger() can already
118 // enqueue before Spawn(); pipe file descriptors, unlike threads, survive
119 // the fuse client's daemonization fork.
120
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 MakePipe(pipe_bm_);
121
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 MakePipe(pipe_triggers_);
122
123 // Non-blocking writes so TrySendPath/ScheduleTrigger can drop when a
124 // queue is full. Per pipe(7), writes <= PIPE_BUF are atomic on
125 // non-blocking pipes: they either fully succeed or fail with EAGAIN.
126
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 int flags = fcntl(pipe_bm_[1], F_GETFL);
127
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 fcntl(pipe_bm_[1], F_SETFL, flags | O_NONBLOCK);
128
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 flags = fcntl(pipe_triggers_[1], F_GETFL);
129
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 fcntl(pipe_triggers_[1], F_SETFL, flags | O_NONBLOCK);
130 414 }
131
132 414 void BundleMgr::Spawn() {
133 414 SpawnFetcherPool();
134
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 if (is_valid_)
135 414 SpawnDispatcher();
136 414 }
137
138 92 bool BundleMgr::ScheduleTrigger(const PathString &path) {
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (not is_valid_) {
140 ✗ LogCvmfs(kLogBundleMgr,
141 kLogDebug,
142 "BundleMgr is not in a valid state. Can't schedule trigger!");
143 ✗ return false;
144 }
145 // A single non-blocking attempt: prefetching is best-effort, so if the
146 // trigger queue is full the request is dropped instead of stalling the
147 // caller (an open() holding the remount fence).
148
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
92 if (not TrySendPath(pipe_triggers_[1], path)) {
149 ✗ LogCvmfs(kLogBundleMgr, kLogDebug, "trigger queue full, dropping %s",
150 ✗ path.ToString().c_str());
151 ✗ return false;
152 }
153 92 return true;
154 }
155
156 /**
157 * Dependency paths in a bundle are absolute from the repository root.
158 * Entries without a leading slash (optionally prefixed with "./") are
159 * resolved relative to the directory holding the bundle file.
160 */
161 276 PathString BundleMgr::NormalizeDependencyPath(const PathString &path,
162 const PathString &parent_path) {
163
2/4
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 276 times.
276 if (path.StartsWith(PathString("/", 1)))
164 ✗ return path;
165
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 const PathString relative = path.StartsWith(PathString("./", 2))
166 ? path.Suffix(2)
167
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 276 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 6 taken 276 times.
✗ Branch 7 not taken.
276 : path;
168
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 PathString normalized(parent_path);
169
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 normalized.Append("/", 1);
170
1/2
✓ Branch 3 taken 276 times.
✗ Branch 4 not taken.
276 normalized.Append(relative.GetChars(), relative.GetLength());
171
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 return normalized;
172 276 }
173
174 598 void BundleMgr::JoinFetcherPool() {
175
2/2
✓ Branch 0 taken 184 times.
✓ Branch 1 taken 414 times.
598 if (pipe_bm_[1] < 0)
176 184 return;
177 // Send one kTerminate per worker. Workers drain all queued kFetch
178 // messages before reaching their kTerminate (FIFO pipe), so we can't
179 // just close the pipe — that would EOF some workers mid-drain.
180
2/2
✓ Branch 1 taken 3312 times.
✓ Branch 2 taken 414 times.
3726 for (size_t i = 0; i < fetcher_threads_.size(); ++i) {
181 3312 Command cmd = Command::kTerminate;
182 while (true) {
183
1/2
✓ Branch 1 taken 3312 times.
✗ Branch 2 not taken.
3312 const ssize_t n = ::write(pipe_bm_[1], &cmd, sizeof(Command));
184
1/2
✓ Branch 0 taken 3312 times.
✗ Branch 1 not taken.
3312 if (n == sizeof(Command))
185 3312 break;
186 ✗ if (errno != EAGAIN && errno != EWOULDBLOCK)
187 ✗ break;
188 }
189 }
190 // Wait for every worker to drain its share of the queue and exit.
191
2/2
✓ Branch 5 taken 3312 times.
✓ Branch 6 taken 414 times.
3726 for (auto &t : fetcher_threads_) {
192
1/2
✓ Branch 2 taken 3312 times.
✗ Branch 3 not taken.
3312 pthread_join(*t, nullptr);
193 }
194 414 fetcher_threads_.clear();
195 414 ClosePipe(pipe_bm_);
196 // Mark the pool as gone so that a subsequent call (e.g. the destructor
197 // running after an explicit JoinFetcherPool()) is a no-op instead of a
198 // double close/join.
199 414 pipe_bm_[0] = pipe_bm_[1] = -1;
200 }
201
202 414 void BundleMgr::SpawnFetcherPool() {
203
2/2
✓ Branch 0 taken 3312 times.
✓ Branch 1 taken 414 times.
3726 for (size_t i = 0; i < pool_size_; ++i) {
204
1/2
✓ Branch 1 taken 3312 times.
✗ Branch 2 not taken.
3312 std::unique_ptr<pthread_t> thread(new pthread_t());
205 3312 const int res = pthread_create(thread.get(), nullptr, MainBundleMgrFetcher,
206 this);
207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3312 times.
3312 if (res != 0) {
208 ✗ LogCvmfs(kLogBundleMgr, kLogDebug,
209 "Thread creation failed! pool_size_=%zu spawned=%zu", pool_size_,
210 i);
211 ✗ is_valid_ = false;
212 ✗ return;
213 }
214
1/2
✓ Branch 2 taken 3312 times.
✗ Branch 3 not taken.
3312 fetcher_threads_.emplace_back(std::move(thread));
215
1/2
✓ Branch 1 taken 3312 times.
✗ Branch 2 not taken.
3312 }
216 }
217
218 414 void BundleMgr::SpawnDispatcher() {
219 414 dispatcher_thread_.reset(new pthread_t());
220 414 const int res = pthread_create(dispatcher_thread_.get(), nullptr,
221 MainBundleMgrDispatcher, this);
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 414 times.
414 if (res != 0) {
223 ✗ LogCvmfs(kLogBundleMgr, kLogDebug, "Dispatcher thread creation failed!");
224 ✗ dispatcher_thread_.reset();
225 ✗ is_valid_ = false;
226 }
227 414 }
228
229 506 void BundleMgr::JoinDispatcher() {
230
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 414 times.
506 if (pipe_triggers_[1] < 0)
231 92 return;
232 // The dispatcher only exists once Spawn() has run; without it there is
233 // just the pipe to close.
234
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 if (dispatcher_thread_) {
235 414 Command cmd = Command::kTerminate;
236 while (true) {
237
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 const ssize_t n = ::write(pipe_triggers_[1], &cmd, sizeof(Command));
238
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 if (n == sizeof(Command))
239 414 break;
240 ✗ if (errno != EAGAIN && errno != EWOULDBLOCK)
241 ✗ break;
242 }
243
1/2
✓ Branch 2 taken 414 times.
✗ Branch 3 not taken.
414 pthread_join(*dispatcher_thread_, nullptr);
244 414 dispatcher_thread_.reset();
245 }
246 414 ClosePipe(pipe_triggers_);
247 414 pipe_triggers_[0] = pipe_triggers_[1] = -1;
248 }
249
250 /**
251 * Loads the bundle spec that belongs to the given trigger file and enqueues
252 * its dependencies for the fetcher pool. Runs on the dispatcher thread.
253 */
254 92 void BundleMgr::ProcessTrigger(const PathString &trigger_path) {
255
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 const NameString fname = GetFileName(trigger_path);
256
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 const PathString parent_path = GetParentPath(trigger_path);
257 // There is a naming convention regarding the name of the file with the
258 // contents of the bundle
259
2/4
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 92 times.
✗ Branch 5 not taken.
184 const PathString bundle_file_path(parent_path.ToString() + "/.cvmfsbundle-"
260
3/6
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 92 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 92 times.
✗ Branch 8 not taken.
276 + fname.ToString());
261
262 const std::unique_ptr<BundleFileMgr> bfm(
263
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 LoadBundleFromCvmfs(mount_point_, bundle_file_path));
264
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 if (bfm == nullptr) {
265
1/2
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
92 LogCvmfs(kLogCvmfs, kLogDebug, "Couldn't fetch bundle associated to %s",
266
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
184 trigger_path.ToString().c_str());
267 92 return;
268 }
269 ✗ EnqueueDependencies(bfm.get(), parent_path);
270
4/8
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 92 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 92 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 92 times.
368 }
271
272 368 void BundleMgr::EnqueueDependencies(BundleFileMgr *bfm,
273 const PathString &parent_path) {
274
4/6
✓ Branch 1 taken 368 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 368 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 92 times.
✓ Branch 7 taken 276 times.
368 while (auto file = bfm->GetNext()) {
275
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 const PathString path = NormalizeDependencyPath(file, parent_path);
276 // A single non-blocking attempt: if the dependency queue is full the
277 // entry is dropped (prefetching is best-effort) instead of spinning.
278
2/4
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 276 times.
276 if (not TrySendPath(pipe_bm_[1], path)) {
279 ✗ LogCvmfs(kLogBundleMgr, kLogDebug, "dependency queue full, dropping %s",
280 ✗ path.ToString().c_str());
281 }
282
2/2
✓ Branch 2 taken 276 times.
✓ Branch 3 taken 92 times.
644 }
283 92 }
284
285 276 void BundleMgr::FetchPath(const PathString &path) {
286
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 catalog::DirectoryEntry dirent;
287
1/2
✓ Branch 2 taken 276 times.
✗ Branch 3 not taken.
276 const bool found = mount_point_->catalog_mgr()->LookupPath(
288 path, catalog::kLookupDefault, &dirent);
289 276 cvmfs::Fetcher *this_fetcher = dirent.IsExternalFile()
290
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 276 times.
276 ? mount_point_->external_fetcher()
291 276 : mount_point_->fetcher();
292
2/4
✓ Branch 0 taken 276 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 276 times.
276 if (not(found and this_fetcher)) {
293 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
294 "BUNDLE-FETCH: lookup failed for %s (found=%d)",
295 ✗ path.ToString().c_str(), int(found));
296 ✗ return;
297 }
298
1/2
✓ Branch 2 taken 276 times.
✗ Branch 3 not taken.
276 LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-FETCH: prefetching %s",
299
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
552 path.ToString().c_str());
300
301
2/2
✓ Branch 1 taken 138 times.
✓ Branch 2 taken 138 times.
276 if (dirent.IsChunkedFile()) {
302 // Files above the chunking threshold are stored as per-chunk objects;
303 // their bulk object only exists if the repository sets
304 // CVMFS_GENERATE_LEGACY_BULK_CHUNKS. Fetch the chunks, exactly like the
305 // read path does.
306
1/2
✓ Branch 1 taken 138 times.
✗ Branch 2 not taken.
138 FileChunkList chunks;
307
1/2
✓ Branch 3 taken 138 times.
✗ Branch 4 not taken.
138 if (!mount_point_->catalog_mgr()->ListFileChunks(
308 path, dirent.hash_algorithm(), &chunks)
309
3/6
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 138 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 138 times.
138 || chunks.IsEmpty()) {
310 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "BUNDLE-FETCH: no chunks found for %s",
311 ✗ path.ToString().c_str());
312 ✗ return;
313 }
314
2/2
✓ Branch 1 taken 414 times.
✓ Branch 2 taken 138 times.
552 for (unsigned i = 0; i < chunks.size(); ++i) {
315 414 CacheManager::Label label;
316
1/2
✓ Branch 1 taken 414 times.
✗ Branch 2 not taken.
414 label.path = path.ToString();
317 414 label.size = chunks.AtPtr(i)->size();
318 414 label.zip_algorithm = dirent.compression_algorithm();
319 414 label.flags |= CacheManager::kLabelChunked;
320
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 414 times.
414 if (mount_point_->catalog_mgr()->volatile_flag())
321 ✗ label.flags |= CacheManager::kLabelVolatile;
322
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 414 times.
414 if (dirent.IsExternalFile()) {
323 ✗ label.flags |= CacheManager::kLabelExternal;
324 ✗ label.range_offset = chunks.AtPtr(i)->offset();
325 }
326
2/4
✓ Branch 2 taken 414 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 414 times.
✗ Branch 6 not taken.
1242 const int fd = this_fetcher->Fetch(
327
1/2
✓ Branch 3 taken 414 times.
✗ Branch 4 not taken.
828 CacheManager::LabeledObject(chunks.AtPtr(i)->content_hash(), label));
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 414 times.
414 if (fd >= 0)
329 ✗ mount_point_->file_system()->cache_mgr()->Close(fd);
330 414 }
331 138 return;
332 138 }
333
334 138 CacheManager::Label label;
335
1/2
✓ Branch 1 taken 138 times.
✗ Branch 2 not taken.
138 label.path = path.ToString();
336
1/2
✓ Branch 1 taken 138 times.
✗ Branch 2 not taken.
138 label.size = dirent.size();
337 138 label.zip_algorithm = dirent.compression_algorithm();
338
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 138 times.
138 if (mount_point_->catalog_mgr()->volatile_flag())
339 ✗ label.flags |= CacheManager::kLabelVolatile;
340
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
138 if (dirent.IsExternalFile())
341 ✗ label.flags |= CacheManager::kLabelExternal;
342
2/4
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 138 times.
✗ Branch 6 not taken.
414 const int fd = this_fetcher->Fetch(
343
1/2
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
276 CacheManager::LabeledObject(dirent.checksum(), label));
344
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 138 times.
138 if (fd >= 0)
345 ✗ mount_point_->file_system()->cache_mgr()->Close(fd);
346
2/2
✓ Branch 2 taken 138 times.
✓ Branch 3 taken 138 times.
276 }
347
348 414 void *BundleMgr::MainBundleMgrDispatcher(void *data) {
349 #ifndef __APPLE__
350 414 pthread_setname_np(pthread_self(), "bm_dispatch");
351 #endif
352 414 BundleMgr *mgr = static_cast<BundleMgr *>(data);
353 414 const int rfd = mgr->pipe_triggers_[0];
354
355 // Single reader on this pipe, so no receive mutex is needed here
356 while (true) {
357 506 Command cmd = Command::kTerminate;
358
1/2
✓ Branch 1 taken 506 times.
✗ Branch 2 not taken.
506 const ssize_t n = read(rfd, &cmd, sizeof(Command));
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 506 times.
506 if (n != static_cast<ssize_t>(sizeof(Command)))
360 ✗ break;
361
2/2
✓ Branch 0 taken 414 times.
✓ Branch 1 taken 92 times.
506 if (cmd != Command::kFetch)
362 414 break;
363
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 const PathString path = mgr->ReceivePath(rfd);
364 // While terminating, drain the queue without processing so that
365 // unmounting does not wait for spec downloads
366
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
184 if (mgr->terminating_.load() == 0)
367
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
92 mgr->ProcessTrigger(path);
368 92 }
369
370 414 pthread_exit(nullptr);
371 }
372
373 3312 void *BundleMgr::MainBundleMgrFetcher(void *data) {
374 #ifndef __APPLE__
375 3312 pthread_setname_np(pthread_self(), "bm_fetcher");
376 #endif
377 3312 BundleMgr *mgr = static_cast<BundleMgr *>(data);
378 3312 const int rfd = mgr->pipe_bm_[0];
379
380 while (true) {
381 3588 Command cmd = Command::kTerminate;
382 3588 PathString path;
383 3588 bool got_path = false;
384 3588 bool eof = false;
385
386 // Atomically receive cmd + (optional) path payload. The whole receipt
387 // is under worker_read_mutex_ so messages aren't interleaved between
388 // workers reading from the shared pipe.
389 3588 pthread_mutex_lock(&mgr->worker_read_mutex_);
390
1/2
✓ Branch 1 taken 3588 times.
✗ Branch 2 not taken.
3588 const ssize_t n = read(rfd, &cmd, sizeof(Command));
391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3588 times.
3588 if (n != static_cast<ssize_t>(sizeof(Command))) {
392 ✗ eof = true;
393
2/2
✓ Branch 0 taken 276 times.
✓ Branch 1 taken 3312 times.
3588 } else if (cmd == Command::kFetch) {
394
2/4
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 276 times.
✗ Branch 5 not taken.
276 path = mgr->ReceivePath(rfd);
395 276 got_path = true;
396 }
397 3588 pthread_mutex_unlock(&mgr->worker_read_mutex_);
398
399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3588 times.
3588 if (eof)
400 ✗ break;
401
402 3588 bool terminate = false;
403
2/2
✓ Branch 0 taken 276 times.
✓ Branch 1 taken 3312 times.
3588 switch (cmd) {
404 276 case Command::kFetch: {
405
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 276 times.
276 if (!got_path) {
406 ✗ terminate = true;
407 ✗ break;
408 }
409 // While terminating, drain the queue without fetching so that
410 // unmounting does not wait for pending downloads
411
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
552 if (mgr->terminating_.load() == 0)
412
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 mgr->FetchPath(path);
413 276 } break;
414 3312 case Command::kTerminate:
415 default:
416 3312 terminate = true;
417 3312 break;
418 }
419
2/2
✓ Branch 0 taken 3312 times.
✓ Branch 1 taken 276 times.
3588 if (terminate) {
420 3312 break;
421 }
422
2/2
✓ Branch 1 taken 276 times.
✓ Branch 2 taken 3312 times.
3864 }
423
424 3312 pthread_exit(nullptr);
425 }
426
427 460 PathString BundleMgr::ReceivePath(int fd) const {
428
1/2
✓ Branch 1 taken 460 times.
✗ Branch 2 not taken.
460 const std::string buffer = BlockingReceive(fd);
429
1/2
✓ Branch 1 taken 460 times.
✗ Branch 2 not taken.
460 assert(buffer.size() > 0 && "A path can't be empty");
430
1/2
✓ Branch 1 taken 460 times.
✗ Branch 2 not taken.
920 return PathString(buffer);
431 460 }
432
433 77694 bool BundleMgr::TrySendPath(int fd, const PathString &path) const {
434 // The whole message (command + length + payload) is sent as a single
435 // write: per pipe(7), writes <= PIPE_BUF to a non-blocking pipe are
436 // atomic, they either fully succeed or fail with EAGAIN. Sending the
437 // parts separately could hit a full queue in the middle of a message
438 // and corrupt the stream for all readers.
439 77694 const Command cmd = Command::kFetch;
440 77694 const size_t length = path.GetLength();
441 77694 const size_t msg_size = sizeof(cmd) + sizeof(length) + length;
442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 77694 times.
77694 if (msg_size > PIPE_BUF) {
443 ✗ LogCvmfs(kLogBundleMgr, kLogDebug,
444 "path too long for the work queue, dropping %s",
445 ✗ path.ToString().c_str());
446 ✗ return false;
447 }
448 char msg[PIPE_BUF];
449 77694 memcpy(msg, &cmd, sizeof(cmd));
450 77694 memcpy(msg + sizeof(cmd), &length, sizeof(length));
451 77694 memcpy(msg + sizeof(cmd) + sizeof(length), path.GetChars(), length);
452
453
1/2
✓ Branch 1 taken 77694 times.
✗ Branch 2 not taken.
77694 const ssize_t n = write(fd, msg, msg_size);
454
2/2
✓ Branch 0 taken 77648 times.
✓ Branch 1 taken 46 times.
77694 if (n == static_cast<ssize_t>(msg_size))
455 77648 return true;
456
2/6
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
46 if ((n < 0) && not(errno == EAGAIN || errno == EWOULDBLOCK)) {
457 ✗ LogCvmfs(kLogBundleMgr, kLogDebug,
458 ✗ "write() on the work queue failed unexpectedly (errno=%d)", errno);
459 }
460 46 return false;
461 }
462