GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_remount.cc
Date: 2026-10-11 02:40:02
Exec Total Coverage
Lines: 0 202 0.0%
Branches: 0 218 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include "fuse_remount.h"
7
8 #include <errno.h>
9 #include <poll.h>
10 #include <unistd.h>
11
12 #include <cassert>
13 #include <cstdlib>
14 #include <cstring>
15
16 #include "backoff.h"
17 #include "catalog_mgr_client.h"
18 #include "fuse_inode_gen.h"
19 #include "lru_md.h"
20 #include "mountpoint.h"
21 #include "util/exception.h"
22 #include "util/logging.h"
23 #include "util/posix.h"
24
25 using namespace std; // NOLINT
26
27
28 ✗ FuseRemounter::Status FuseRemounter::ChangeRoot(const shash::Any &root_hash) {
29 ✗ if (mountpoint_->catalog_mgr()->GetRootHash() == root_hash)
30 ✗ return kStatusUp2Date;
31
32 ✗ const FenceGuard fence_guard(&fence_maintenance_);
33 ✗ if (IsInMaintenanceMode())
34 ✗ return kStatusMaintenance;
35
36 ✗ int32_t expected_val = 0;
37 ✗ if (drainout_mode_.compare_exchange_strong(expected_val, 1)) {
38 // As of this point, fuse callbacks return zero as cache timeout
39 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "chroot, draining out meta-data caches");
40 ✗ invalidator_handle_.Reset();
41 ✗ invalidator_->InvalidateInodes(&invalidator_handle_);
42 ✗ drainout_mode_.fetch_add(1);
43 // drainout_mode_ == 2, IsInDrainoutMode is now 'true'
44 } else {
45 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "already in drainout mode, leaving");
46 ✗ return kStatusDraining;
47 }
48
49 ✗ int32_t drainout_code = 0;
50 ✗ BackoffThrottle throttle;
51 do {
52 ✗ TryFinish(root_hash);
53 ✗ drainout_code = drainout_mode_.load();
54 ✗ if (drainout_code == 0)
55 ✗ break;
56 ✗ throttle.Throttle();
57 } while (true);
58
59 ✗ if (mountpoint_->catalog_mgr()->GetRootHash() == root_hash)
60 ✗ return kStatusUp2Date;
61 ✗ return kStatusFailGeneral;
62 }
63
64
65 /**
66 * Executed by the trigger thread, or triggered from cvmfs_talk. Moves into
67 * drainout mode if a new catalog is available online.
68 */
69 ✗ FuseRemounter::Status FuseRemounter::Check() {
70 ✗ const FenceGuard fence_guard(&fence_maintenance_);
71 ✗ if (IsInMaintenanceMode())
72 ✗ return kStatusMaintenance;
73
74 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
75 "catalog TTL expired, checking revision against blacklists");
76 ✗ if (mountpoint_->ReloadBlacklists()
77 ✗ && mountpoint_->catalog_mgr()->IsRevisionBlacklisted()) {
78 ✗ PANIC(kLogDebug | kLogSyslogErr,
79 "repository revision blacklisted, aborting");
80 }
81
82 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "remounting root catalog");
83 ✗ const catalog::LoadReturn retval = mountpoint_->catalog_mgr()
84 ✗ ->RemountDryrun();
85 ✗ switch (retval) {
86 ✗ case catalog::kLoadNew: {
87 ✗ SetOfflineMode(false);
88 ✗ int32_t expected_val = 0;
89 ✗ if (drainout_mode_.compare_exchange_strong(expected_val, 1)) {
90 // As of this point, fuse callbacks return zero as cache timeout
91 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
92 "new catalog revision available, "
93 "draining out meta-data caches");
94 ✗ invalidator_handle_.Reset();
95 ✗ invalidator_->InvalidateInodes(&invalidator_handle_);
96 ✗ drainout_mode_.fetch_add(1);
97 // drainout_mode_ == 2, IsInDrainoutMode is now 'true'
98 } else {
99 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "already in drainout mode, leaving");
100 }
101 ✗ return kStatusDraining;
102 }
103 ✗ case catalog::kLoadFail:
104 case catalog::kLoadNoSpace:
105 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
106 "reload failed (%s), applying short term TTL",
107 catalog::Code2Ascii(retval));
108 ✗ SetOfflineMode(true);
109 ✗ catalogs_valid_until_ = time(NULL) + MountPoint::kShortTermTTL;
110 ✗ SetAlarm(MountPoint::kShortTermTTL);
111 ✗ return (retval == catalog::kLoadFail) ? kStatusFailGeneral
112 ✗ : kStatusFailNoSpace;
113 ✗ case catalog::kLoadUp2Date: {
114 ✗ LogCvmfs(kLogCvmfs, kLogDebug,
115 "catalog up to date (could be offline mode)");
116 ✗ SetOfflineMode(mountpoint_->catalog_mgr()->offline_mode());
117 ✗ const unsigned ttl = offline_mode_ ? MountPoint::kShortTermTTL
118 ✗ : mountpoint_->GetEffectiveTtlSec();
119 ✗ catalogs_valid_until_ = time(NULL) + ttl;
120 ✗ SetAlarm(ttl);
121 ✗ return kStatusUp2Date;
122 }
123 ✗ default:
124 ✗ PANIC(NULL);
125 }
126 }
127
128
129 /**
130 * Used from the TalkManager. Continuously calls 'check' until it returns with
131 * "up to date" or a failure.
132 */
133 ✗ FuseRemounter::Status FuseRemounter::CheckSynchronously() {
134 ✗ BackoffThrottle throttle;
135 while (true) {
136 ✗ const Status status = Check();
137 ✗ switch (status) {
138 ✗ case kStatusDraining:
139 ✗ TryFinish();
140 ✗ break;
141 ✗ default:
142 ✗ return status;
143 }
144 ✗ throttle.Throttle();
145 }
146 }
147
148
149 ✗ void FuseRemounter::EnterMaintenanceMode() {
150 ✗ fence_maintenance_.Drain();
151 ✗ int32_t expected_val = 0;
152 ✗ maintenance_mode_.compare_exchange_strong(expected_val, 1);
153 ✗ fence_maintenance_.Open();
154
155 // All running Check() and TryFinish() methods returned. Both methods now
156 // return immediately as noops.
157
158 // Flush caches before reload of fuse module
159 ✗ invalidator_handle_.Reset();
160 ✗ invalidator_->InvalidateInodes(&invalidator_handle_);
161 ✗ invalidator_handle_.WaitFor();
162 }
163
164 ✗ FuseRemounter::FuseRemounter(MountPoint *mountpoint,
165 cvmfs::InodeGenerationInfo *inode_generation_info,
166 struct fuse_session **fuse_session,
167 ✗ bool fuse_notify_invalidation)
168 ✗ : mountpoint_(mountpoint)
169 ✗ , inode_generation_info_(inode_generation_info)
170 ✗ , invalidator_(new FuseInvalidator(mountpoint, fuse_session,
171 ✗ fuse_notify_invalidation))
172 ✗ , invalidator_handle_(static_cast<int>(mountpoint->kcache_timeout_sec()))
173 ✗ , fence_(new Fence())
174 ✗ , offline_mode_(false)
175 ✗ , catalogs_valid_until_(MountPoint::kIndefiniteDeadline) {
176 ✗ memset(&thread_remount_trigger_, 0, sizeof(thread_remount_trigger_));
177 ✗ pipe_remount_trigger_[0] = pipe_remount_trigger_[1] = -1;
178 ✗ drainout_mode_.store(0);
179 ✗ maintenance_mode_.store(0);
180 ✗ critical_section_.store(0);
181 }
182
183 ✗ FuseRemounter::~FuseRemounter() {
184 ✗ if (HasRemountTrigger()) {
185 ✗ char quit = 'Q';
186 ✗ WritePipe(pipe_remount_trigger_[1], &quit, 1);
187 ✗ pthread_join(thread_remount_trigger_, NULL);
188 ✗ ClosePipe(pipe_remount_trigger_);
189 }
190 ✗ delete invalidator_;
191 ✗ delete fence_;
192 }
193
194
195 /**
196 * Triggers the Check() method when the catalog TTL expires. Works essentially
197 * as an alarm() timer.
198 */
199 ✗ void *FuseRemounter::MainRemountTrigger(void *data) {
200 ✗ FuseRemounter *remounter = reinterpret_cast<FuseRemounter *>(data);
201 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "starting remount trigger");
202 char c;
203 ✗ int timeout_ms = -1;
204 ✗ uint64_t deadline = 0;
205 struct pollfd watch_ctrl;
206 ✗ watch_ctrl.fd = remounter->pipe_remount_trigger_[0];
207 ✗ watch_ctrl.events = POLLIN | POLLPRI;
208 while (true) {
209 ✗ watch_ctrl.revents = 0;
210 ✗ const int retval = poll(&watch_ctrl, 1, timeout_ms);
211 ✗ if (retval < 0) {
212 ✗ if (errno == EINTR) {
213 ✗ if (timeout_ms >= 0) {
214 ✗ const uint64_t now = platform_monotonic_time();
215 ✗ timeout_ms = (now > deadline) ? 0 : (deadline - now) * 1000;
216 }
217 ✗ continue;
218 }
219 ✗ PANIC(kLogSyslogErr | kLogDebug,
220 "remount trigger connection failure (%d)", errno);
221 }
222
223 ✗ if (retval == 0) {
224 ✗ remounter->Check();
225 ✗ timeout_ms = -1;
226 ✗ continue;
227 }
228
229 ✗ assert(watch_ctrl.revents != 0);
230
231 ✗ ReadPipe(remounter->pipe_remount_trigger_[0], &c, 1);
232 ✗ if (c == 'Q')
233 ✗ break;
234 ✗ assert(c == 'T');
235 ✗ ReadPipe(remounter->pipe_remount_trigger_[0], &timeout_ms, sizeof(int));
236 ✗ deadline = platform_monotonic_time() + timeout_ms / 1000;
237 }
238 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "stopping remount trigger");
239 ✗ return NULL;
240 }
241
242
243 ✗ void FuseRemounter::SetAlarm(int timeout) {
244 // Remounting could be called for a non auto-update repository
245 ✗ if (!HasRemountTrigger())
246 ✗ return;
247
248 ✗ timeout *= 1000; // timeout given in ms
249 ✗ const unsigned buf_size = 1 + sizeof(int);
250 char buf[buf_size];
251 ✗ buf[0] = 'T';
252 ✗ memcpy(&buf[1], &timeout, sizeof(timeout));
253 ✗ WritePipe(pipe_remount_trigger_[1], buf, buf_size);
254 }
255
256
257 ✗ void FuseRemounter::SetOfflineMode(bool value) {
258 ✗ if (value == offline_mode_)
259 ✗ return;
260 ✗ offline_mode_ = value;
261
262 ✗ if (offline_mode_) {
263 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
264 "warning, could not apply updated catalog revision, "
265 "entering offline mode");
266 ✗ mountpoint_->file_system()->io_error_info()->AddIoError();
267 } else {
268 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslog, "recovered from offline mode");
269 }
270 }
271
272
273 ✗ void FuseRemounter::Spawn() {
274 ✗ invalidator_->Spawn();
275 ✗ if (!mountpoint_->fixed_catalog()) {
276 ✗ MakePipe(pipe_remount_trigger_);
277 ✗ const int retval = pthread_create(&thread_remount_trigger_, NULL,
278 MainRemountTrigger, this);
279 ✗ assert(retval == 0);
280
281 ✗ SetOfflineMode(mountpoint_->catalog_mgr()->offline_mode());
282 ✗ const unsigned ttl = offline_mode_ ? MountPoint::kShortTermTTL
283 ✗ : mountpoint_->GetEffectiveTtlSec();
284 ✗ catalogs_valid_until_ = time(NULL) + ttl;
285 ✗ SetAlarm(ttl);
286 }
287 }
288
289
290 /**
291 * Applies a previously started remount operation. This is called from the
292 * fuse callbacks or from CheckSynchronously(). Usually, the method quits
293 * immediately except when a new catalog is available and the kernel caches are
294 * flushed.
295 */
296 ✗ void FuseRemounter::TryFinish(const shash::Any &root_hash) {
297 ✗ const FenceGuard fence_guard(&fence_maintenance_);
298 ✗ if (IsInMaintenanceMode())
299 ✗ return;
300 ✗ if (!EnterCriticalSection())
301 ✗ return;
302 ✗ if (!IsInDrainoutMode()) {
303 ✗ LeaveCriticalSection();
304 ✗ return;
305 }
306
307 // No one else is in this code path and we have a valid FuseInvalidator handle
308
309 ✗ if (!invalidator_handle_.IsDone()) {
310 ✗ LeaveCriticalSection();
311 ✗ return;
312 }
313 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "caches drained out, applying new catalog");
314
315 // No new inserts into caches
316 ✗ mountpoint_->inode_cache()->Pause();
317 ✗ mountpoint_->path_cache()->Pause();
318 ✗ mountpoint_->md5path_cache()->Pause();
319 ✗ mountpoint_->inode_cache()->Drop();
320 ✗ mountpoint_->path_cache()->Drop();
321 ✗ mountpoint_->md5path_cache()->Drop();
322
323 // Ensure that all Fuse callbacks left the catalog query code
324 ✗ fence_->Drain();
325 catalog::LoadReturn retval;
326 ✗ if (root_hash.IsNull()) {
327 ✗ retval = mountpoint_->catalog_mgr()->Remount();
328 } else {
329 ✗ retval = mountpoint_->catalog_mgr()->ChangeRoot(root_hash);
330 }
331 ✗ if (mountpoint_->inode_annotation()) {
332 ✗ inode_generation_info_->inode_generation = mountpoint_->inode_annotation()
333 ✗ ->GetGeneration();
334 }
335 ✗ mountpoint_->ReEvaluateAuthz();
336 ✗ fence_->Open();
337
338 ✗ mountpoint_->inode_cache()->Resume();
339 ✗ mountpoint_->path_cache()->Resume();
340 ✗ mountpoint_->md5path_cache()->Resume();
341
342 ✗ drainout_mode_.fetch_add(-2); // 2 --> 0, end of drainout mode
343
344 ✗ if ((retval == catalog::kLoadFail) || (retval == catalog::kLoadNoSpace)) {
345 // Can temporarily "escape" offline mode if update came from updated
346 // alien cache
347 ✗ SetOfflineMode(true);
348 ✗ catalogs_valid_until_ = time(NULL) + MountPoint::kShortTermTTL;
349 ✗ SetAlarm(MountPoint::kShortTermTTL);
350 } else {
351 ✗ SetOfflineMode(false);
352 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "switched to catalog revision %" PRIu64,
353 ✗ mountpoint_->catalog_mgr()->GetRevision());
354 ✗ catalogs_valid_until_ = time(NULL) + mountpoint_->GetEffectiveTtlSec();
355 ✗ SetAlarm(mountpoint_->GetEffectiveTtlSec());
356 }
357
358 ✗ LeaveCriticalSection();
359 }
360