GCC Code Coverage Report


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