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