GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_remount.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 0 198 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 #include "cvmfs_config.h"
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 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 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 {
80 PANIC(kLogDebug | kLogSyslogErr,
81 "repository revision blacklisted, aborting");
82 }
83
84 LogCvmfs(kLogCvmfs, kLogDebug, "remounting root catalog");
85 const catalog::LoadReturn retval =
86 mountpoint_->catalog_mgr()->RemountDryrun();
87 switch (retval) {
88 case catalog::kLoadNew:
89 SetOfflineMode(false);
90 if (atomic_cas32(&drainout_mode_, 0, 1)) {
91 // As of this point, fuse callbacks return zero as cache timeout
92 LogCvmfs(kLogCvmfs, kLogDebug,
93 "new catalog revision available, "
94 "draining out meta-data caches");
95 invalidator_handle_.Reset();
96 invalidator_->InvalidateInodes(&invalidator_handle_);
97 atomic_inc32(&drainout_mode_);
98 // drainout_mode_ == 2, IsInDrainoutMode is now 'true'
99 } else {
100 LogCvmfs(kLogCvmfs, kLogDebug, "already in drainout mode, leaving");
101 }
102 return kStatusDraining;
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) ?
112 kStatusFailGeneral : 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 unsigned ttl = offline_mode_ ?
118 MountPoint::kShortTermTTL : 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 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 atomic_cas32(&maintenance_mode_, 0, 1);
152 fence_maintenance_.Open();
153
154 // All running Check() and TryFinish() methods returned. Both methods now
155 // return immediately as noops.
156
157 // Flush caches before reload of fuse module
158 invalidator_handle_.Reset();
159 invalidator_->InvalidateInodes(&invalidator_handle_);
160 invalidator_handle_.WaitFor();
161 }
162
163 FuseRemounter::FuseRemounter(MountPoint *mountpoint,
164 cvmfs::InodeGenerationInfo *inode_generation_info,
165 void **fuse_channel_or_session,
166 bool fuse_notify_invalidation)
167 : mountpoint_(mountpoint),
168 inode_generation_info_(inode_generation_info),
169 invalidator_(new FuseInvalidator(mountpoint,
170 fuse_channel_or_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 atomic_init32(&drainout_mode_);
179 atomic_init32(&maintenance_mode_);
180 atomic_init32(&critical_section_);
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 int retval = poll(&watch_ctrl, 1, timeout_ms);
211 if (retval < 0) {
212 if (errno == EINTR) {
213 if (timeout_ms >= 0) {
214 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 int retval = pthread_create(
278 &thread_remount_trigger_, NULL, MainRemountTrigger, this);
279 assert(retval == 0);
280
281 SetOfflineMode(mountpoint_->catalog_mgr()->offline_mode());
282 unsigned ttl = offline_mode_ ?
283 MountPoint::kShortTermTTL : 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 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 =
333 mountpoint_->inode_annotation()->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 atomic_xadd32(&drainout_mode_, -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