| 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 |