GCC Code Coverage Report


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