GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/cache_posix.cc Lines: 243 256 94.9 %
Date: 2019-02-03 02:48:13 Branches: 108 131 82.4 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * The cache module maintains the local file cache.  Files are
5
 * staged into the cache by Fetch().  The cache stores files with a name
6
 * according to their content hash.
7
 *
8
 * The procedure is
9
 *   -# Look in the catalog for content hash
10
 *   -# If it is in local cache: return file descriptor
11
 *   -# Otherwise download, store in cache and return fd
12
 *
13
 * Each running CVMFS instance has to have a separate cache directory.
14
 * The local cache directory (directories 00..ff) can be accessed
15
 * in parallel to a running CVMFS, i.e. files can be deleted for instance
16
 * anytime.  However, this will confuse the cache database managed by the lru
17
 * module.
18
 *
19
 * Files are created in txn directory first.  At the very latest
20
 * point they are renamed into their "real" content hash names atomically by
21
 * rename().  This concept is taken over from GROW-FS.
22
 *
23
 * Identical URLs won't be concurrently downloaded.  The first thread performs
24
 * the download and informs the other, waiting threads on pipes.
25
 */
26
27
#define __STDC_FORMAT_MACROS
28
29
#include "cvmfs_config.h"
30
#include "cache_posix.h"
31
32
#include <dirent.h>
33
#include <errno.h>
34
#include <fcntl.h>
35
#include <inttypes.h>
36
#include <pthread.h>
37
#include <sys/stat.h>
38
#include <sys/types.h>
39
#ifndef __APPLE__
40
#include <sys/statfs.h>
41
#endif
42
#include <unistd.h>
43
44
#include <algorithm>
45
#include <cassert>
46
#include <cstdio>
47
#include <cstdlib>
48
#include <cstring>
49
#include <map>
50
#include <vector>
51
52
#include "atomic.h"
53
#include "directory_entry.h"
54
#include "download.h"
55
#include "hash.h"
56
#include "logging.h"
57
#include "manifest.h"
58
#include "manifest_fetch.h"
59
#include "platform.h"
60
#include "quota.h"
61
#include "shortstring.h"
62
#include "signature.h"
63
#include "smalloc.h"
64
#include "statistics.h"
65
#include "util/posix.h"
66
67
#ifndef NFS_SUPER_MAGIC
68
#define NFS_SUPER_MAGIC 0x6969
69
#endif
70
#ifndef BEEGFS_SUPER_MAGIC
71
#define BEEGFS_SUPER_MAGIC 0x19830326
72
#endif
73
74
using namespace std;  // NOLINT
75
76
namespace {
77
78
/**
79
 * A CallGuard object can be placed at the beginning of a function.  It counts
80
 * the number of so-annotated functions that are in flight.  The Drainout() call
81
 * will wait until all functions that have been called so far are finished.
82
 *
83
 * The class is used in order to wait for remaining calls when switching into
84
 * the read-only cache mode.
85
 */
86
class CallGuard {
87
 public:
88
  CallGuard() {
89
    int32_t global_drainout = atomic_read32(&global_drainout_);
90
    drainout_ = (global_drainout != 0);
91
    if (!drainout_)
92
      atomic_inc32(&num_inflight_calls_);
93
  }
94
  ~CallGuard() {
95
    if (!drainout_)
96
      atomic_dec32(&num_inflight_calls_);
97
  }
98
  static void Drainout() {
99
    atomic_cas32(&global_drainout_, 0, 1);
100
    while (atomic_read32(&num_inflight_calls_) != 0)
101
      SafeSleepMs(50);
102
  }
103
 private:
104
  bool drainout_;
105
  static atomic_int32 global_drainout_;
106
  static atomic_int32 num_inflight_calls_;
107
};
108
atomic_int32 CallGuard::num_inflight_calls_ = 0;
109
atomic_int32 CallGuard::global_drainout_ = 0;
110
111
}  // anonymous namespace
112
113
114
//------------------------------------------------------------------------------
115
116
117
const uint64_t PosixCacheManager::kBigFile = 25 * 1024 * 1024;  // 25M
118
119
120
41
int PosixCacheManager::AbortTxn(void *txn) {
121
41
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
122
41
  LogCvmfs(kLogCache, kLogDebug, "abort %s", transaction->tmp_path.c_str());
123
41
  close(transaction->fd);
124
41
  int result = unlink(transaction->tmp_path.c_str());
125
41
  transaction->~Transaction();
126
41
  atomic_dec32(&no_inflight_txns_);
127
41
  if (result == -1)
128
2
    return -errno;
129
39
  return 0;
130
}
131
132
133
/**
134
 * This should only be used to replace the default NoopQuotaManager by a
135
 * PosixQuotaManager.  The cache manager takes the ownership of the passed
136
 * quota manager.
137
 */
138
55
bool PosixCacheManager::AcquireQuotaManager(QuotaManager *quota_mgr) {
139
55
  if (quota_mgr == NULL)
140
    return false;
141
55
  delete quota_mgr_;
142
55
  quota_mgr_ = quota_mgr;
143
55
  return true;
144
}
145
146
147
140
int PosixCacheManager::Close(int fd) {
148
140
  int retval = close(fd);
149
140
  if (retval != 0)
150
1
    return -errno;
151
139
  return 0;
152
}
153
154
155
176
int PosixCacheManager::CommitTxn(void *txn) {
156
176
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
157
  int result;
158
  LogCvmfs(kLogCache, kLogDebug, "commit %s %s",
159
176
           transaction->final_path.c_str(), transaction->tmp_path.c_str());
160
161
176
  result = Flush(transaction);
162
176
  close(transaction->fd);
163
176
  if (result < 0) {
164
1
    unlink(transaction->tmp_path.c_str());
165
1
    transaction->~Transaction();
166
1
    atomic_dec32(&no_inflight_txns_);
167
1
    return result;
168
  }
169
170
  // To support debugging, move files into quarantine on file size mismatch
171
175
  if (transaction->size != transaction->expected_size) {
172
    // Allow size to be zero if alien cache, because hadoop-fuse-dfs returns
173
    // size zero for a while
174

56
    if ( (transaction->expected_size != kSizeUnknown) &&
175
         (reports_correct_filesize_ || (transaction->size != 0)) )
176
    {
177
      LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
178
               "size check failure for %s, expected %lu, got %lu",
179
               transaction->id.ToString().c_str(),
180
9
               transaction->expected_size, transaction->size);
181
      CopyPath2Path(transaction->tmp_path,
182
9
                    cache_path_ + "/quarantaine/" + transaction->id.ToString());
183
9
      unlink(transaction->tmp_path.c_str());
184
9
      transaction->~Transaction();
185
9
      atomic_dec32(&no_inflight_txns_);
186
9
      return -EIO;
187
    }
188
  }
189
190

166
  if ((transaction->object_info.type == kTypePinned) ||
191
      (transaction->object_info.type == kTypeCatalog))
192
  {
193
    bool retval = quota_mgr_->Pin(
194
      transaction->id, transaction->size, transaction->object_info.description,
195
37
      (transaction->object_info.type == kTypeCatalog));
196
37
    if (!retval) {
197
      LogCvmfs(kLogCache, kLogDebug, "commit failed: cannot pin %s",
198
1
               transaction->id.ToString().c_str());
199
1
      unlink(transaction->tmp_path.c_str());
200
1
      transaction->~Transaction();
201
1
      atomic_dec32(&no_inflight_txns_);
202
1
      return -ENOSPC;
203
    }
204
  }
205
206
  // Move the temporary file into its final location
207
165
  if (alien_cache_) {
208
1
    int retval = chmod(transaction->tmp_path.c_str(), 0660);
209
1
    assert(retval == 0);
210
  }
211
  result =
212
165
    Rename(transaction->tmp_path.c_str(), transaction->final_path.c_str());
213
165
  if (result < 0) {
214
2
    LogCvmfs(kLogCache, kLogDebug, "commit failed: %s", strerror(errno));
215
2
    unlink(transaction->tmp_path.c_str());
216

2
    if ((transaction->object_info.type == kTypePinned) ||
217
        (transaction->object_info.type == kTypeCatalog))
218
    {
219
1
      quota_mgr_->Remove(transaction->id);
220
    }
221
  } else {
222
    // Success, inform quota manager
223
163
    if (transaction->object_info.type == kTypeVolatile) {
224
      quota_mgr_->InsertVolatile(transaction->id, transaction->size,
225
1
                                 transaction->object_info.description);
226
162
    } else if (transaction->object_info.type == kTypeRegular) {
227
      quota_mgr_->Insert(transaction->id, transaction->size,
228
127
                         transaction->object_info.description);
229
    }
230
  }
231
165
  transaction->~Transaction();
232
165
  atomic_dec32(&no_inflight_txns_);
233
165
  return result;
234
}
235
236
237
236
PosixCacheManager *PosixCacheManager::Create(
238
  const string &cache_path,
239
  const bool alien_cache,
240
  const RenameWorkarounds rename_workaround)
241
{
242
  UniquePtr<PosixCacheManager> cache_manager(
243
236
    new PosixCacheManager(cache_path, alien_cache));
244
236
  assert(cache_manager.IsValid());
245
246
236
  cache_manager->rename_workaround_ = rename_workaround;
247
236
  if (cache_manager->alien_cache_) {
248
40
    if (!MakeCacheDirectories(cache_path, 0770)) {
249
1
      return NULL;
250
    }
251
    LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
252
39
             "Cache directory structure created.");
253
    struct statfs cache_buf;
254
39
    int retval = statfs(cache_path.c_str(), &cache_buf);
255
39
    if (retval == 0) {
256
39
      switch (cache_buf.f_type) {
257
        case NFS_SUPER_MAGIC:
258
          cache_manager->rename_workaround_ = kRenameLink;
259
          LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
260
               "Alien cache is on NFS.");
261
          break;
262
        case BEEGFS_SUPER_MAGIC:
263
          cache_manager->rename_workaround_ = kRenameSamedir;
264
          LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
265
               "Alien cache is on BeeGFS.");
266
          break;
267
      }
268
    }
269
  } else {
270
196
    if (!MakeCacheDirectories(cache_path, 0700))
271
1
      return NULL;
272
  }
273
274
  // TODO(jblomer): we might not need to look anymore for cvmfs 2.0 relicts
275
234
  if (FileExists(cache_path + "/cvmfscatalog.cache")) {
276
    LogCvmfs(kLogCache, kLogDebug | kLogSyslogErr,
277
1
             "Not mounting on cvmfs 2.0.X cache");
278
1
    return NULL;
279
  }
280
281
233
  return cache_manager.Release();
282
}
283
284
285
191
void PosixCacheManager::CtrlTxn(
286
  const ObjectInfo &object_info,
287
  const int flags,
288
  void *txn)
289
{
290
191
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
291
191
  transaction->object_info = object_info;
292
191
}
293
294
295
string PosixCacheManager::Describe() {
296
  return "Posix cache manager (cache directory: " + cache_path_ + ")\n";
297
}
298
299
300
/**
301
 * Nothing to do, the kernel keeps the state of open file descriptors.  Return
302
 * a dummy memory location.
303
 */
304
1
void *PosixCacheManager::DoSaveState() {
305
1
  char *c = reinterpret_cast<char *>(smalloc(1));
306
1
  *c = '\0';
307
1
  return c;
308
}
309
310
311
1
bool PosixCacheManager::DoRestoreState(void *data) {
312
1
  assert(data);
313
1
  char *c = reinterpret_cast<char *>(data);
314
1
  assert(*c == '\0');
315
1
  return true;
316
}
317
318
319
1
bool PosixCacheManager::DoFreeState(void *data) {
320
1
  free(data);
321
1
  return true;
322
}
323
324
325
326
10
int PosixCacheManager::Dup(int fd) {
327
10
  int new_fd = dup(fd);
328
10
  if (new_fd < 0)
329
5
    return -errno;
330
5
  return new_fd;
331
}
332
333
334
364
int PosixCacheManager::Flush(Transaction *transaction) {
335
364
  if (transaction->buf_pos == 0)
336
100
    return 0;
337
  int written =
338
264
    write(transaction->fd, transaction->buffer, transaction->buf_pos);
339
264
  if (written < 0)
340
3
    return -errno;
341
261
  if (static_cast<unsigned>(written) != transaction->buf_pos) {
342
    transaction->buf_pos -= written;
343
    return -EIO;
344
  }
345
261
  transaction->buf_pos = 0;
346
261
  return 0;
347
}
348
349
350
508
inline string PosixCacheManager::GetPathInCache(const shash::Any &id) {
351
508
  return cache_path_ + "/" + id.MakePathWithoutSuffix();
352
}
353
354
355
88
int64_t PosixCacheManager::GetSize(int fd) {
356
  platform_stat64 info;
357
88
  int retval = platform_fstat(fd, &info);
358
88
  if (retval != 0)
359
1
    return -errno;
360
87
  return info.st_size;
361
}
362
363
364
286
int PosixCacheManager::Open(const BlessedObject &object) {
365
286
  const string path = GetPathInCache(object.id);
366
286
  int result = open(path.c_str(), O_RDONLY);
367
368
286
  if (result >= 0) {
369
69
    LogCvmfs(kLogCache, kLogDebug, "hit %s", path.c_str());
370
    // platform_disable_kcache(result);
371
69
    quota_mgr_->Touch(object.id);
372
  } else {
373
217
    result = -errno;
374
217
    LogCvmfs(kLogCache, kLogDebug, "miss %s (%d)", path.c_str(), result);
375
  }
376
286
  return result;
377
}
378
379
380
67
int PosixCacheManager::OpenFromTxn(void *txn) {
381
67
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
382
67
  int retval = Flush(transaction);
383
67
  if (retval < 0)
384
1
    return retval;
385
66
  int fd_rdonly = open(transaction->tmp_path.c_str(), O_RDONLY);
386
66
  if (fd_rdonly == -1)
387
1
    return -errno;
388
65
  return fd_rdonly;
389
}
390
391
392
743
int64_t PosixCacheManager::Pread(
393
  int fd,
394
  void *buf,
395
  uint64_t size,
396
  uint64_t offset)
397
{
398
  int64_t result;
399

743
  do {
400
743
    errno = 0;
401
743
    result = pread(fd, buf, size, offset);
402
  } while ((result == -1) && (errno == EINTR));
403
743
  if (result < 0)
404
2
    return -errno;
405
741
  return result;
406
}
407
408
409
171
int PosixCacheManager::Rename(const char *oldpath, const char *newpath) {
410
  int result;
411
171
  if (rename_workaround_ != kRenameLink) {
412
168
    result = rename(oldpath, newpath);
413
168
    if (result < 0)
414
3
      return -errno;
415
165
    return 0;
416
  }
417
418
3
  result = link(oldpath, newpath);
419
3
  if (result < 0) {
420
2
    if (errno == EEXIST)
421
1
      LogCvmfs(kLogCache, kLogDebug, "%s already existed, ignoring", newpath);
422
    else
423
1
      return -errno;
424
  }
425
2
  result = unlink(oldpath);
426
2
  if (result < 0)
427
    return -errno;
428
2
  return 0;
429
}
430
431
432
/**
433
 * Used by the sqlite vfs in order to preload file catalogs into the file system
434
 * buffers.
435
 */
436
52
int PosixCacheManager::Readahead(int fd) {
437
  unsigned char *buf[4096];
438
  int nbytes;
439
52
  uint64_t pos = 0;
440
244
  do {
441
244
    nbytes = Pread(fd, buf, 4096, pos);
442
244
    pos += nbytes;
443
  } while (nbytes == 4096);
444
52
  LogCvmfs(kLogCache, kLogDebug, "read-ahead %d, %" PRIu64, fd, pos);
445
52
  if (nbytes < 0)
446
    return nbytes;
447
52
  return 0;
448
}
449
450
451
5
int PosixCacheManager::Reset(void *txn) {
452
5
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
453
5
  transaction->buf_pos = 0;
454
5
  transaction->size = 0;
455
5
  int retval = lseek(transaction->fd, 0, SEEK_SET);
456
5
  if (retval < 0)
457
1
    return -errno;
458
4
  retval = ftruncate(transaction->fd, 0);
459
4
  if (retval < 0)
460
    return -errno;
461
4
  return 0;
462
}
463
464
465
224
int PosixCacheManager::StartTxn(
466
  const shash::Any &id,
467
  uint64_t size,
468
  void *txn)
469
{
470
224
  atomic_inc32(&no_inflight_txns_);
471
224
  if (cache_mode_ == kCacheReadOnly) {
472
1
    atomic_dec32(&no_inflight_txns_);
473
1
    return -EROFS;
474
  }
475
476
223
  if (size != kSizeUnknown) {
477
144
    if (size > quota_mgr_->GetMaxFileSize()) {
478
      LogCvmfs(kLogCache, kLogDebug, "file too big for lru cache (%" PRIu64 " "
479
               "requested but only %" PRIu64 " bytes free)",
480
1
               size, quota_mgr_->GetMaxFileSize());
481
1
      atomic_dec32(&no_inflight_txns_);
482
1
      return -ENOSPC;
483
    }
484
485
    // For large files, ensure enough free cache space before writing the chunk
486
143
    if (size > kBigFile) {
487
1
      uint64_t cache_size = quota_mgr_->GetSize();
488
1
      uint64_t cache_capacity = quota_mgr_->GetCapacity();
489
1
      assert(cache_capacity >= size);
490
1
      if ((cache_size + size) > cache_capacity) {
491
        uint64_t leave_size =
492
1
          std::min(cache_capacity / 2, cache_capacity - size);
493
1
        quota_mgr_->Cleanup(leave_size);
494
      }
495
    }
496
  }
497
498
222
  string path_in_cache = GetPathInCache(id);
499
222
  Transaction *transaction = new (txn) Transaction(id, path_in_cache);
500
501
222
  char *template_path = NULL;
502
222
  unsigned temp_path_len = 0;
503
222
  if (rename_workaround_ == kRenameSamedir) {
504
2
    temp_path_len = path_in_cache.length() + 6;
505
2
    template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
506
2
    memcpy(template_path, path_in_cache.data(), path_in_cache.length());
507
2
    memset(template_path + path_in_cache.length(), 'X', 6);
508
  } else {
509
220
    temp_path_len = txn_template_path_.length();
510
220
    template_path = reinterpret_cast<char *>(alloca(temp_path_len + 1));
511
220
    memcpy(template_path, &txn_template_path_[0], temp_path_len);
512
  }
513
222
  template_path[temp_path_len] = '\0';
514
515
222
  transaction->fd = mkstemp(template_path);
516
222
  if (transaction->fd == -1) {
517
5
    transaction->~Transaction();
518
5
    atomic_dec32(&no_inflight_txns_);
519
5
    return -errno;
520
  }
521
522
  LogCvmfs(kLogCache, kLogDebug, "start transaction on %s has result %d",
523
217
           template_path, transaction->fd);
524
217
  transaction->tmp_path = template_path;
525
217
  transaction->expected_size = size;
526
217
  return transaction->fd;
527
}
528
529
530
4
void PosixCacheManager::TearDown2ReadOnly() {
531
4
  cache_mode_ = kCacheReadOnly;
532
22
  while (atomic_read32(&no_inflight_txns_) != 0)
533
15
    SafeSleepMs(50);
534
535
3
  QuotaManager *old_manager = quota_mgr_;
536
3
  quota_mgr_ = new NoopQuotaManager();
537
3
  delete old_manager;
538
3
}
539
540
541
208
int64_t PosixCacheManager::Write(const void *buf, uint64_t size, void *txn) {
542
208
  Transaction *transaction = reinterpret_cast<Transaction *>(txn);
543
544
208
  if (transaction->expected_size != kSizeUnknown) {
545
132
    if (transaction->size + size > transaction->expected_size) {
546
      LogCvmfs(kLogCache, kLogDebug,
547
               "Transaction size (%" PRIu64 ") > expected size (%" PRIu64 ")",
548
1
               transaction->size + size, transaction->expected_size);
549
1
      return -EFBIG;
550
    }
551
  }
552
553
207
  uint64_t written = 0;
554
207
  const unsigned char *read_pos = reinterpret_cast<const unsigned char *>(buf);
555
680
  while (written < size) {
556
267
    if (transaction->buf_pos == sizeof(transaction->buffer)) {
557
121
      int retval = Flush(transaction);
558
121
      if (retval != 0) {
559
1
        transaction->size += written;
560
1
        return retval;
561
      }
562
    }
563
266
    uint64_t remaining = size - written;
564
    uint64_t space_in_buffer =
565
266
      sizeof(transaction->buffer) - transaction->buf_pos;
566
266
    uint64_t batch_size = std::min(remaining, space_in_buffer);
567
266
    memcpy(transaction->buffer + transaction->buf_pos, read_pos, batch_size);
568
266
    transaction->buf_pos += batch_size;
569
266
    written += batch_size;
570
266
    read_pos += batch_size;
571
  }
572
206
  transaction->size += written;
573
206
  return written;
574
}