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