GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/cvmfs.cc Lines: 0 1154 0.0 %
Date: 2019-02-03 02:48:13 Branches: 0 629 0.0 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * CernVM-FS is a FUSE module which implements an HTTP read-only filesystem.
5
 * The original idea is based on GROW-FS.
6
 *
7
 * CernVM-FS shows a remote HTTP directory as local file system.  The client
8
 * sees all available files.  On first access, a file is downloaded and
9
 * cached locally.  All downloaded pieces are verified by a cryptographic
10
 * content hash.
11
 *
12
 * To do so, a directory hive has to be transformed into a CVMFS2
13
 * "repository".  This can be done by the CernVM-FS server tools.
14
 *
15
 * This preparation of directories is transparent to web servers and
16
 * web proxies.  They just serve static content, i.e. arbitrary files.
17
 * Any HTTP server should do the job.  We use Apache + Squid.  Serving
18
 * files from the memory of a web proxy brings a significant performance
19
 * improvement.
20
 */
21
22
// TODO(jblomer): the file system root should probably always return 1 for an
23
// inode.  See also integration test #23.
24
25
#define ENOATTR ENODATA  /**< instead of including attr/xattr.h */
26
#define FUSE_USE_VERSION 26
27
28
#ifndef __STDC_FORMAT_MACROS
29
#define __STDC_FORMAT_MACROS
30
#endif
31
32
// sys/xattr.h conflicts with linux/xattr.h and needs to be loaded very early
33
#include <sys/xattr.h>  // NOLINT
34
35
#include "cvmfs_config.h"
36
#include "cvmfs.h"
37
38
#include <dirent.h>
39
#include <errno.h>
40
#include <fcntl.h>
41
#include <fuse/fuse_lowlevel.h>
42
#include <fuse/fuse_opt.h>
43
#include <google/dense_hash_map>
44
#include <inttypes.h>
45
#include <openssl/crypto.h>
46
#include <pthread.h>
47
#include <stddef.h>
48
#include <stdint.h>
49
#include <sys/errno.h>
50
#include <sys/file.h>
51
#include <sys/mount.h>
52
#include <sys/resource.h>
53
#include <sys/stat.h>
54
#include <sys/time.h>
55
#include <sys/types.h>
56
#include <sys/wait.h>
57
#include <unistd.h>
58
59
#include <algorithm>
60
#include <cassert>
61
#include <cstdio>
62
#include <cstdlib>
63
#include <cstring>
64
#include <ctime>
65
#include <functional>
66
#include <map>
67
#include <new>
68
#include <string>
69
#include <vector>
70
71
#include "atomic.h"
72
#include "authz/authz_session.h"
73
#include "auto_umount.h"
74
#include "backoff.h"
75
#include "cache.h"
76
#include "catalog_mgr_client.h"
77
#include "clientctx.h"
78
#include "compat.h"
79
#include "compression.h"
80
#include "directory_entry.h"
81
#include "download.h"
82
#include "fence.h"
83
#include "fetch.h"
84
#include "file_chunk.h"
85
#include "fuse_inode_gen.h"
86
#include "fuse_remount.h"
87
#include "globals.h"
88
#include "glue_buffer.h"
89
#include "hash.h"
90
#include "history_sqlite.h"
91
#include "loader.h"
92
#include "logging.h"
93
#include "lru_md.h"
94
#include "manifest_fetch.h"
95
#include "monitor.h"
96
#include "mountpoint.h"
97
#include "nfs_maps.h"
98
#include "options.h"
99
#include "platform.h"
100
#include "quota_listener.h"
101
#include "quota_posix.h"
102
#include "shortstring.h"
103
#include "signature.h"
104
#include "smalloc.h"
105
#include "sqlitemem.h"
106
#include "sqlitevfs.h"
107
#include "statistics.h"
108
#include "talk.h"
109
#include "tracer.h"
110
#include "util_concurrency.h"
111
#include "uuid.h"
112
#include "wpad.h"
113
#include "xattr.h"
114
115
using namespace std;  // NOLINT
116
117
namespace cvmfs {
118
119
FileSystem *file_system_ = NULL;
120
MountPoint *mount_point_ = NULL;
121
TalkManager *talk_mgr_ = NULL;
122
Watchdog *watchdog_ = NULL;
123
FuseRemounter *fuse_remounter_ = NULL;
124
InodeGenerationInfo inode_generation_info_;
125
126
127
/**
128
 * For cvmfs_opendir / cvmfs_readdir
129
 * TODO: use mmap for very large listings
130
 */
131
struct DirectoryListing {
132
  char *buffer;  /**< Filled by fuse_add_direntry */
133
134
  // Not really used anymore.  But directory listing needs to be migrated during
135
  // hotpatch. If buffer is allocated by smmap, capacity is zero.
136
  size_t size;
137
  size_t capacity;
138
139
  DirectoryListing() : buffer(NULL), size(0), capacity(0) { }
140
};
141
142
const loader::LoaderExports *loader_exports_ = NULL;
143
OptionsManager *options_mgr_ = NULL;
144
pid_t pid_ = 0;  /**< will be set after deamon() */
145
quota::ListenerHandle *watchdog_listener_ = NULL;
146
quota::ListenerHandle *unpin_listener_ = NULL;
147
148
149
typedef google::dense_hash_map<uint64_t, DirectoryListing,
150
                               hash_murmur<uint64_t> >
151
        DirectoryHandles;
152
DirectoryHandles *directory_handles_ = NULL;
153
pthread_mutex_t lock_directory_handles_ = PTHREAD_MUTEX_INITIALIZER;
154
uint64_t next_directory_handle_ = 0;
155
156
unsigned max_open_files_; /**< maximum allowed number of open files */
157
/**
158
 * Number of reserved file descriptors for internal use
159
 */
160
const int kNumReservedFd = 512;
161
162
163
static inline double GetKcacheTimeout() {
164
  if (!fuse_remounter_->IsCaching())
165
    return 0.0;
166
  return mount_point_->kcache_timeout_sec();
167
}
168
169
170
void GetReloadStatus(bool *drainout_mode, bool *maintenance_mode) {
171
  *drainout_mode = fuse_remounter_->IsInDrainoutMode();
172
  *maintenance_mode = fuse_remounter_->IsInMaintenanceMode();
173
}
174
175
176
static bool UseWatchdog() {
177
  if (loader_exports_ == NULL || loader_exports_->version < 2) {
178
    return true;  // spawn watchdog by default
179
                  // Note: with library versions before 2.1.8 it might not
180
                  //       create stack traces properly in all cases
181
  }
182
183
  return !loader_exports_->disable_watchdog;
184
}
185
186
std::string PrintInodeGeneration() {
187
  return "init-catalog-revision: " +
188
    StringifyInt(inode_generation_info_.initial_revision) + "  " +
189
    "current-catalog-revision: " +
190
    StringifyInt(mount_point_->catalog_mgr()->GetRevision()) + "  " +
191
    "incarnation: " + StringifyInt(inode_generation_info_.incarnation) + "  " +
192
    "inode generation: " + StringifyInt(inode_generation_info_.inode_generation)
193
    + "\n";
194
}
195
196
197
static bool CheckVoms(const fuse_ctx &fctx) {
198
  if (!mount_point_->has_membership_req())
199
    return true;
200
  string mreq = mount_point_->membership_req();
201
  LogCvmfs(kLogCvmfs, kLogDebug, "Got VOMS authz %s from filesystem "
202
           "properties", mreq.c_str());
203
204
  if (fctx.uid == 0)
205
    return true;
206
207
  return mount_point_->authz_session_mgr()->IsMemberOf(fctx.pid, mreq);
208
}
209
210
211
static bool GetDirentForInode(const fuse_ino_t ino,
212
                              catalog::DirectoryEntry *dirent)
213
{
214
  // Lookup inode in cache
215
  if (mount_point_->inode_cache()->Lookup(ino, dirent))
216
    return true;
217
218
  // Look in the catalogs in 2 steps: lookup inode->path, lookup path
219
  catalog::DirectoryEntry dirent_negative =
220
    catalog::DirectoryEntry(catalog::kDirentNegative);
221
  // Reset directory entry.  If the function returns false and dirent is no
222
  // the kDirentNegative, it was an I/O error
223
  *dirent = catalog::DirectoryEntry();
224
225
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
226
227
  if (file_system_->IsNfsSource()) {
228
    // NFS mode
229
    PathString path;
230
    bool retval = file_system_->nfs_maps()->GetPath(ino, &path);
231
    if (!retval) {
232
      *dirent = dirent_negative;
233
      return false;
234
    }
235
    if (catalog_mgr->LookupPath(path, catalog::kLookupSole, dirent)) {
236
      // Fix inodes
237
      dirent->set_inode(ino);
238
      mount_point_->inode_cache()->Insert(ino, *dirent);
239
      return true;
240
    }
241
    return false;  // Not found in catalog or catalog load error
242
  }
243
244
  // Non-NFS mode
245
  PathString path;
246
  if (ino == catalog_mgr->GetRootInode()) {
247
    bool retval =
248
      catalog_mgr->LookupPath(PathString(), catalog::kLookupSole, dirent);
249
    assert(retval);
250
    dirent->set_inode(ino);
251
    mount_point_->inode_cache()->Insert(ino, *dirent);
252
    return true;
253
  }
254
255
  bool retval = mount_point_->inode_tracker()->FindPath(ino, &path);
256
  if (!retval) {
257
    // Can this ever happen?
258
    LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
259
             "GetDirentForInode inode lookup failure %" PRId64, ino);
260
    *dirent = dirent_negative;
261
    return false;
262
  }
263
  if (catalog_mgr->LookupPath(path, catalog::kLookupSole, dirent)) {
264
    // Fix inodes
265
    dirent->set_inode(ino);
266
    mount_point_->inode_cache()->Insert(ino, *dirent);
267
    return true;
268
  }
269
270
  // Can happen after reload of catalogs or on catalog load failure
271
  LogCvmfs(kLogCvmfs, kLogDebug, "GetDirentForInode path lookup failure");
272
  return false;
273
}
274
275
276
static bool GetDirentForPath(const PathString &path,
277
                             catalog::DirectoryEntry *dirent)
278
{
279
  uint64_t live_inode = 0;
280
  if (!file_system_->IsNfsSource())
281
    live_inode = mount_point_->inode_tracker()->FindInode(path);
282
283
  shash::Md5 md5path(path.GetChars(), path.GetLength());
284
  if (mount_point_->md5path_cache()->Lookup(md5path, dirent)) {
285
    if (dirent->GetSpecial() == catalog::kDirentNegative)
286
      return false;
287
    if (!file_system_->IsNfsSource() && (live_inode != 0))
288
      dirent->set_inode(live_inode);
289
    return true;
290
  }
291
292
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
293
294
  // Lookup inode in catalog TODO: not twice md5 calculation
295
  bool retval;
296
  retval = catalog_mgr->LookupPath(path, catalog::kLookupSole, dirent);
297
  if (retval) {
298
    if (file_system_->IsNfsSource()) {
299
      // Fix inode
300
      dirent->set_inode(file_system_->nfs_maps()->GetInode(path));
301
    } else {
302
      if (live_inode != 0)
303
        dirent->set_inode(live_inode);
304
    }
305
    mount_point_->md5path_cache()->Insert(md5path, *dirent);
306
    return true;
307
  }
308
309
  LogCvmfs(kLogCvmfs, kLogDebug, "GetDirentForPath, no entry");
310
  // Only insert ENOENT results into negative cache.  Otherwise it was an
311
  // error loading nested catalogs
312
  if (dirent->GetSpecial() == catalog::kDirentNegative)
313
    mount_point_->md5path_cache()->InsertNegative(md5path);
314
  return false;
315
}
316
317
318
static bool GetPathForInode(const fuse_ino_t ino, PathString *path) {
319
  // Check the path cache first
320
  if (mount_point_->path_cache()->Lookup(ino, path))
321
    return true;
322
323
  if (file_system_->IsNfsSource()) {
324
    // NFS mode, just a lookup
325
    LogCvmfs(kLogCvmfs, kLogDebug, "MISS %d - lookup in NFS maps", ino);
326
    if (file_system_->nfs_maps()->GetPath(ino, path)) {
327
      mount_point_->path_cache()->Insert(ino, *path);
328
      return true;
329
    }
330
    return false;
331
  }
332
333
  if (ino == mount_point_->catalog_mgr()->GetRootInode())
334
    return true;
335
336
  LogCvmfs(kLogCvmfs, kLogDebug, "MISS %d - looking in inode tracker", ino);
337
  bool retval = mount_point_->inode_tracker()->FindPath(ino, path);
338
  assert(retval);
339
  mount_point_->path_cache()->Insert(ino, *path);
340
  return true;
341
}
342
343
static void DoTraceInode(const int event,
344
                          fuse_ino_t ino,
345
                          const std::string &msg)
346
{
347
  PathString path;
348
  bool found = GetPathForInode(ino, &path);
349
  if (!found) {
350
    LogCvmfs(kLogCvmfs, kLogDebug,
351
      "Tracing: Could not find path for inode %" PRIu64, uint64_t(ino));
352
    mount_point_->tracer()->Trace(event, PathString("@UNKNOWN"), msg);
353
  } else {
354
    mount_point_->tracer()->Trace(event, path, msg);
355
  }
356
}
357
358
static void inline TraceInode(const int event,
359
                                fuse_ino_t ino,
360
                                const std::string &msg)
361
{
362
  if (mount_point_->tracer()->IsActive()) DoTraceInode(event, ino, msg);
363
}
364
365
/**
366
 * Find the inode number of a file name in a directory given by inode.
367
 * This or getattr is called as kind of prerequisit to every operation.
368
 * We do check catalog TTL here (and reload, if necessary).
369
 */
370
static void cvmfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
371
  perf::Inc(file_system_->n_fs_lookup());
372
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
373
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
374
  fuse_remounter_->TryFinish();
375
376
  fuse_remounter_->fence()->Enter();
377
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
378
379
  parent = catalog_mgr->MangleInode(parent);
380
  LogCvmfs(kLogCvmfs, kLogDebug,
381
           "cvmfs_lookup in parent inode: %" PRIu64 " for name: %s",
382
           uint64_t(parent), name);
383
384
  PathString path;
385
  PathString parent_path;
386
  catalog::DirectoryEntry dirent;
387
  struct fuse_entry_param result;
388
389
  memset(&result, 0, sizeof(result));
390
  double timeout = GetKcacheTimeout();
391
  result.attr_timeout = timeout;
392
  result.entry_timeout = timeout;
393
394
  // Special NFS lookups: . and ..
395
  if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) {
396
    if (GetDirentForInode(parent, &dirent)) {
397
      if (strcmp(name, ".") == 0) {
398
        goto lookup_reply_positive;
399
      } else {
400
        // Lookup for ".."
401
        if (dirent.inode() == catalog_mgr->GetRootInode()) {
402
          dirent.set_inode(1);
403
          goto lookup_reply_positive;
404
        }
405
        if (!GetPathForInode(parent, &parent_path))
406
          goto lookup_reply_negative;
407
        if (GetDirentForPath(GetParentPath(parent_path), &dirent))
408
          goto lookup_reply_positive;
409
      }
410
    }
411
    // No entry for "." or no entry for ".."
412
    if (dirent.GetSpecial() == catalog::kDirentNegative)
413
      goto lookup_reply_negative;
414
    else
415
      goto lookup_reply_error;
416
    assert(false);
417
  }
418
419
  if (!GetPathForInode(parent, &parent_path)) {
420
    LogCvmfs(kLogCvmfs, kLogDebug, "no path for parent inode found");
421
    goto lookup_reply_negative;
422
  }
423
424
  path.Assign(parent_path);
425
  path.Append("/", 1);
426
  path.Append(name, strlen(name));
427
  mount_point_->tracer()->Trace(Tracer::kEventLookup, path, "lookup()");
428
  if (!GetDirentForPath(path, &dirent)) {
429
    if (dirent.GetSpecial() == catalog::kDirentNegative)
430
      goto lookup_reply_negative;
431
    else
432
      goto lookup_reply_error;
433
  }
434
435
 lookup_reply_positive:
436
  if (!file_system_->IsNfsSource())
437
    mount_point_->inode_tracker()->VfsGet(dirent.inode(), path);
438
  fuse_remounter_->fence()->Leave();
439
  result.ino = dirent.inode();
440
  result.attr = dirent.GetStatStructure();
441
  fuse_reply_entry(req, &result);
442
  return;
443
444
 lookup_reply_negative:
445
  fuse_remounter_->fence()->Leave();
446
  perf::Inc(file_system_->n_fs_lookup_negative());
447
  result.ino = 0;
448
  fuse_reply_entry(req, &result);
449
  return;
450
451
 lookup_reply_error:
452
  fuse_remounter_->fence()->Leave();
453
  fuse_reply_err(req, EIO);
454
}
455
456
457
/**
458
 *
459
 */
460
static void cvmfs_forget(
461
  fuse_req_t req,
462
  fuse_ino_t ino,
463
  unsigned long nlookup  // NOLINT
464
) {
465
  perf::Inc(file_system_->n_fs_forget());
466
467
  // The libfuse high-level library does the same
468
  if (ino == FUSE_ROOT_ID) {
469
    fuse_reply_none(req);
470
    return;
471
  }
472
473
  fuse_remounter_->fence()->Enter();
474
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
475
  // This has been seen to deadlock on the debug log mutex on SL5.  Problem of
476
  // old kernel/fuse?
477
  LogCvmfs(kLogCvmfs, kLogDebug, "forget on inode %" PRIu64 " by %u",
478
           uint64_t(ino), nlookup);
479
  if (!file_system_->IsNfsSource())
480
    mount_point_->inode_tracker()->VfsPut(ino, nlookup);
481
  fuse_remounter_->fence()->Leave();
482
  fuse_reply_none(req);
483
}
484
485
486
/**
487
 * Looks into dirent to decide if this is an EIO negative reply or an
488
 * ENOENT negative reply
489
 */
490
static void ReplyNegative(const catalog::DirectoryEntry &dirent,
491
                          fuse_req_t req)
492
{
493
  if (dirent.GetSpecial() == catalog::kDirentNegative)
494
    fuse_reply_err(req, ENOENT);
495
  else
496
    fuse_reply_err(req, EIO);
497
}
498
499
500
/**
501
 * Transform a cvmfs dirent into a struct stat.
502
 */
503
static void cvmfs_getattr(fuse_req_t req, fuse_ino_t ino,
504
                          struct fuse_file_info *fi)
505
{
506
  perf::Inc(file_system_->n_fs_stat());
507
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
508
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
509
  fuse_remounter_->TryFinish();
510
511
  fuse_remounter_->fence()->Enter();
512
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
513
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_getattr (stat) for inode: %" PRIu64,
514
           uint64_t(ino));
515
516
  if (!CheckVoms(*fuse_ctx)) {
517
    fuse_remounter_->fence()->Leave();
518
    fuse_reply_err(req, EACCES);
519
    return;
520
  }
521
  catalog::DirectoryEntry dirent;
522
  const bool found = GetDirentForInode(ino, &dirent);
523
  TraceInode(Tracer::kEventGetAttr, ino, "getattr()");
524
  fuse_remounter_->fence()->Leave();
525
526
  if (!found) {
527
    ReplyNegative(dirent, req);
528
    return;
529
  }
530
531
  struct stat info = dirent.GetStatStructure();
532
533
  fuse_reply_attr(req, &info, GetKcacheTimeout());
534
}
535
536
537
/**
538
 * Reads a symlink from the catalog.  Environment variables are expanded.
539
 */
540
static void cvmfs_readlink(fuse_req_t req, fuse_ino_t ino) {
541
  perf::Inc(file_system_->n_fs_readlink());
542
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
543
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
544
545
  fuse_remounter_->fence()->Enter();
546
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
547
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_readlink on inode: %" PRIu64,
548
           uint64_t(ino));
549
550
  catalog::DirectoryEntry dirent;
551
  const bool found = GetDirentForInode(ino, &dirent);
552
  TraceInode(Tracer::kEventReadlink, ino, "readlink()");
553
  fuse_remounter_->fence()->Leave();
554
555
  if (!found) {
556
    ReplyNegative(dirent, req);
557
    return;
558
  }
559
560
  if (!dirent.IsLink()) {
561
    fuse_reply_err(req, EINVAL);
562
    return;
563
  }
564
565
  fuse_reply_readlink(req, dirent.symlink().c_str());
566
}
567
568
569
static void AddToDirListing(const fuse_req_t req,
570
                            const char *name, const struct stat *stat_info,
571
                            BigVector<char> *listing)
572
{
573
  LogCvmfs(kLogCvmfs, kLogDebug, "Add to listing: %s, inode %" PRIu64,
574
           name, uint64_t(stat_info->st_ino));
575
  size_t remaining_size = listing->capacity() - listing->size();
576
  const size_t entry_size = fuse_add_direntry(req, NULL, 0, name, stat_info, 0);
577
578
  while (entry_size > remaining_size) {
579
    listing->DoubleCapacity();
580
    remaining_size = listing->capacity() - listing->size();
581
  }
582
583
  char *buffer;
584
  bool large_alloc;
585
  listing->ShareBuffer(&buffer, &large_alloc);
586
  fuse_add_direntry(req, buffer + listing->size(),
587
                    remaining_size, name, stat_info,
588
                    listing->size() + entry_size);
589
  listing->SetSize(listing->size() + entry_size);
590
}
591
592
593
/**
594
 * Open a directory for listing.
595
 */
596
static void cvmfs_opendir(fuse_req_t req, fuse_ino_t ino,
597
                          struct fuse_file_info *fi)
598
{
599
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
600
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
601
  fuse_remounter_->TryFinish();
602
603
  fuse_remounter_->fence()->Enter();
604
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
605
  ino = catalog_mgr->MangleInode(ino);
606
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64,
607
           uint64_t(ino));
608
  if (!CheckVoms(*fuse_ctx)) {
609
    fuse_remounter_->fence()->Leave();
610
    fuse_reply_err(req, EACCES);
611
    return;
612
  }
613
614
  TraceInode(Tracer::kEventOpenDir, ino, "opendir()");
615
  PathString path;
616
  catalog::DirectoryEntry d;
617
  bool found = GetPathForInode(ino, &path);
618
  if (!found) {
619
    fuse_remounter_->fence()->Leave();
620
    fuse_reply_err(req, ENOENT);
621
    return;
622
  }
623
  found = GetDirentForInode(ino, &d);
624
625
  if (!found) {
626
    fuse_remounter_->fence()->Leave();
627
    ReplyNegative(d, req);
628
    return;
629
  }
630
  if (!d.IsDirectory()) {
631
    fuse_remounter_->fence()->Leave();
632
    fuse_reply_err(req, ENOTDIR);
633
    return;
634
  }
635
636
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64 ", path %s",
637
           uint64_t(ino), path.c_str());
638
639
  // Build listing
640
  BigVector<char> fuse_listing(512);
641
642
  // Add current directory link
643
  struct stat info;
644
  info = d.GetStatStructure();
645
  AddToDirListing(req, ".", &info, &fuse_listing);
646
647
  // Add parent directory link
648
  catalog::DirectoryEntry p;
649
  if (d.inode() != catalog_mgr->GetRootInode() &&
650
      GetDirentForPath(GetParentPath(path), &p))
651
  {
652
    info = p.GetStatStructure();
653
    AddToDirListing(req, "..", &info, &fuse_listing);
654
  }
655
656
  // Add all names
657
  catalog::StatEntryList listing_from_catalog;
658
  bool retval = catalog_mgr->ListingStat(path, &listing_from_catalog);
659
660
  if (!retval) {
661
    fuse_remounter_->fence()->Leave();
662
    fuse_listing.Clear();  // Buffer is shared, empty manually
663
    fuse_reply_err(req, EIO);
664
    return;
665
  }
666
  for (unsigned i = 0; i < listing_from_catalog.size(); ++i) {
667
    // Fix inodes
668
    PathString entry_path;
669
    entry_path.Assign(path);
670
    entry_path.Append("/", 1);
671
    entry_path.Append(listing_from_catalog.AtPtr(i)->name.GetChars(),
672
                      listing_from_catalog.AtPtr(i)->name.GetLength());
673
674
    catalog::DirectoryEntry entry_dirent;
675
    if (!GetDirentForPath(entry_path, &entry_dirent)) {
676
      LogCvmfs(kLogCvmfs, kLogDebug, "listing entry %s vanished, skipping",
677
               entry_path.c_str());
678
      continue;
679
    }
680
681
    struct stat fixed_info = listing_from_catalog.AtPtr(i)->info;
682
    fixed_info.st_ino = entry_dirent.inode();
683
    AddToDirListing(req, listing_from_catalog.AtPtr(i)->name.c_str(),
684
                    &fixed_info, &fuse_listing);
685
  }
686
  fuse_remounter_->fence()->Leave();
687
688
  DirectoryListing stream_listing;
689
  stream_listing.size = fuse_listing.size();
690
  stream_listing.capacity = fuse_listing.capacity();
691
  bool large_alloc;
692
  fuse_listing.ShareBuffer(&stream_listing.buffer, &large_alloc);
693
  if (large_alloc)
694
    stream_listing.capacity = 0;
695
696
  // Save the directory listing and return a handle to the listing
697
  pthread_mutex_lock(&lock_directory_handles_);
698
  LogCvmfs(kLogCvmfs, kLogDebug,
699
           "linking directory handle %d to dir inode: %" PRIu64,
700
           next_directory_handle_, uint64_t(ino));
701
  (*directory_handles_)[next_directory_handle_] = stream_listing;
702
  fi->fh = next_directory_handle_;
703
  ++next_directory_handle_;
704
  pthread_mutex_unlock(&lock_directory_handles_);
705
  perf::Inc(file_system_->n_fs_dir_open());
706
  perf::Inc(file_system_->no_open_dirs());
707
708
  fuse_reply_open(req, fi);
709
}
710
711
712
/**
713
 * Release a directory.
714
 */
715
static void cvmfs_releasedir(fuse_req_t req, fuse_ino_t ino,
716
                             struct fuse_file_info *fi)
717
{
718
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
719
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_releasedir on inode %" PRIu64
720
           ", handle %d", uint64_t(ino), fi->fh);
721
722
  int reply = 0;
723
724
  pthread_mutex_lock(&lock_directory_handles_);
725
  DirectoryHandles::iterator iter_handle =
726
    directory_handles_->find(fi->fh);
727
  if (iter_handle != directory_handles_->end()) {
728
    if (iter_handle->second.capacity == 0)
729
      smunmap(iter_handle->second.buffer);
730
    else
731
      free(iter_handle->second.buffer);
732
    directory_handles_->erase(iter_handle);
733
    pthread_mutex_unlock(&lock_directory_handles_);
734
    perf::Dec(file_system_->no_open_dirs());
735
  } else {
736
    pthread_mutex_unlock(&lock_directory_handles_);
737
    reply = EINVAL;
738
  }
739
740
  fuse_reply_err(req, reply);
741
}
742
743
744
/**
745
 * Very large directory listings have to be sent in slices.
746
 */
747
static void ReplyBufferSlice(const fuse_req_t req, const char *buffer,
748
                             const size_t buffer_size, const off_t offset,
749
                             const size_t max_size)
750
{
751
  if (offset < static_cast<int>(buffer_size)) {
752
    fuse_reply_buf(req, buffer + offset,
753
      std::min(static_cast<size_t>(buffer_size - offset), max_size));
754
  } else {
755
    fuse_reply_buf(req, NULL, 0);
756
  }
757
}
758
759
760
/**
761
 * Read the directory listing.
762
 */
763
static void cvmfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
764
                          off_t off, struct fuse_file_info *fi)
765
{
766
  LogCvmfs(kLogCvmfs, kLogDebug,
767
           "cvmfs_readdir on inode %" PRIu64 " reading %d bytes from offset %d",
768
           uint64_t(mount_point_->catalog_mgr()->MangleInode(ino)), size, off);
769
770
  DirectoryListing listing;
771
772
  pthread_mutex_lock(&lock_directory_handles_);
773
  DirectoryHandles::const_iterator iter_handle =
774
    directory_handles_->find(fi->fh);
775
  if (iter_handle != directory_handles_->end()) {
776
    listing = iter_handle->second;
777
    pthread_mutex_unlock(&lock_directory_handles_);
778
779
    ReplyBufferSlice(req, listing.buffer, listing.size, off, size);
780
    return;
781
  }
782
783
  pthread_mutex_unlock(&lock_directory_handles_);
784
  fuse_reply_err(req, EINVAL);
785
}
786
787
788
/**
789
 * Open a file from cache.  If necessary, file is downloaded first.
790
 *
791
 * \return Read-only file descriptor in fi->fh or kChunkedFileHandle for
792
 * chunked files
793
 */
794
static void cvmfs_open(fuse_req_t req, fuse_ino_t ino,
795
                       struct fuse_file_info *fi)
796
{
797
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
798
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
799
  fuse_remounter_->fence()->Enter();
800
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
801
  ino = catalog_mgr->MangleInode(ino);
802
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_open on inode: %" PRIu64,
803
           uint64_t(ino));
804
805
  int fd = -1;
806
  catalog::DirectoryEntry dirent;
807
  PathString path;
808
809
  bool found = GetPathForInode(ino, &path);
810
  if (!found) {
811
    fuse_remounter_->fence()->Leave();
812
    fuse_reply_err(req, ENOENT);
813
    return;
814
  }
815
  found = GetDirentForInode(ino, &dirent);
816
  if (!found) {
817
    fuse_remounter_->fence()->Leave();
818
    ReplyNegative(dirent, req);
819
    return;
820
  }
821
822
  if (!CheckVoms(*fuse_ctx)) {
823
    fuse_remounter_->fence()->Leave();
824
    fuse_reply_err(req, EACCES);
825
    return;
826
  }
827
828
  mount_point_->tracer()->Trace(Tracer::kEventOpen, path, "open()");
829
  // Don't check.  Either done by the OS or one wants to purposefully work
830
  // around wrong open flags
831
  // if ((fi->flags & 3) != O_RDONLY) {
832
  //   fuse_reply_err(req, EROFS);
833
  //   return;
834
  // }
835
#ifdef __APPLE__
836
  if ((fi->flags & O_SHLOCK) || (fi->flags & O_EXLOCK)) {
837
    fuse_remounter_->fence()->Leave();
838
    fuse_reply_err(req, EOPNOTSUPP);
839
    return;
840
  }
841
#endif
842
  if (fi->flags & O_EXCL) {
843
    fuse_remounter_->fence()->Leave();
844
    fuse_reply_err(req, EEXIST);
845
    return;
846
  }
847
848
  perf::Inc(file_system_->n_fs_open());  // Count actual open / fetch operations
849
850
  if (!dirent.IsChunkedFile()) {
851
    fuse_remounter_->fence()->Leave();
852
  } else {
853
    LogCvmfs(kLogCvmfs, kLogDebug,
854
             "chunked file %s opened (download delayed to read() call)",
855
             path.c_str());
856
857
    if (perf::Xadd(file_system_->no_open_files(), 1) >=
858
        (static_cast<int>(max_open_files_))-kNumReservedFd)
859
    {
860
      perf::Dec(file_system_->no_open_files());
861
      fuse_remounter_->fence()->Leave();
862
      LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
863
      fuse_reply_err(req, EMFILE);
864
      return;
865
    }
866
867
    // Figure out unique inode from annotated catalog
868
    catalog::DirectoryEntry dirent_origin;
869
    if (!catalog_mgr->LookupPath(path, catalog::kLookupSole, &dirent_origin)) {
870
      fuse_remounter_->fence()->Leave();
871
      LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
872
               "chunked file %s vanished unexpectedly", path.c_str());
873
      fuse_reply_err(req, ENOENT);
874
      return;
875
    }
876
    const uint64_t unique_inode = dirent_origin.inode();
877
878
    ChunkTables *chunk_tables = mount_point_->chunk_tables();
879
    chunk_tables->Lock();
880
    if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
881
      chunk_tables->Unlock();
882
883
      // Retrieve File chunks from the catalog
884
      UniquePtr<FileChunkList> chunks(new FileChunkList());
885
      if (!catalog_mgr->ListFileChunks(path, dirent.hash_algorithm(),
886
                                       chunks.weak_ref()) ||
887
          chunks->IsEmpty())
888
      {
889
        fuse_remounter_->fence()->Leave();
890
        LogCvmfs(kLogCvmfs, kLogDebug| kLogSyslogErr, "file %s is marked as "
891
                 "'chunked', but no chunks found.", path.c_str());
892
        fuse_reply_err(req, EIO);
893
        return;
894
      }
895
      fuse_remounter_->fence()->Leave();
896
897
      chunk_tables->Lock();
898
      // Check again to avoid race
899
      if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
900
        chunk_tables->inode2chunks.Insert(
901
          unique_inode, FileChunkReflist(chunks.Release(), path,
902
                                         dirent.compression_algorithm(),
903
                                         dirent.IsExternalFile()));
904
        chunk_tables->inode2references.Insert(unique_inode, 1);
905
      } else {
906
        uint32_t refctr;
907
        bool retval =
908
          chunk_tables->inode2references.Lookup(unique_inode, &refctr);
909
        assert(retval);
910
        chunk_tables->inode2references.Insert(unique_inode, refctr+1);
911
      }
912
    } else {
913
      fuse_remounter_->fence()->Leave();
914
      uint32_t refctr;
915
      bool retval =
916
        chunk_tables->inode2references.Lookup(unique_inode, &refctr);
917
      assert(retval);
918
      chunk_tables->inode2references.Insert(unique_inode, refctr+1);
919
    }
920
921
    // Update the chunk handle list
922
    LogCvmfs(kLogCvmfs, kLogDebug,
923
             "linking chunk handle %d to unique inode: %" PRIu64,
924
             chunk_tables->next_handle, uint64_t(unique_inode));
925
    chunk_tables->handle2fd.Insert(chunk_tables->next_handle, ChunkFd());
926
    chunk_tables->handle2uniqino.Insert(chunk_tables->next_handle,
927
                                        unique_inode);
928
    // The same inode can refer to different revisions of a path.  Don't cache.
929
    fi->keep_cache = 0;
930
    fi->fh = static_cast<uint64_t>(-chunk_tables->next_handle);
931
    ++chunk_tables->next_handle;
932
    chunk_tables->Unlock();
933
934
    fuse_reply_open(req, fi);
935
    return;
936
  }
937
938
  Fetcher *this_fetcher = dirent.IsExternalFile()
939
    ? mount_point_->external_fetcher()
940
    : mount_point_->fetcher();
941
  fd = this_fetcher->Fetch(
942
    dirent.checksum(),
943
    dirent.size(),
944
    string(path.GetChars(), path.GetLength()),
945
    dirent.compression_algorithm(),
946
    mount_point_->catalog_mgr()->volatile_flag()
947
      ? CacheManager::kTypeVolatile
948
      : CacheManager::kTypeRegular);
949
950
  if (fd >= 0) {
951
    if (perf::Xadd(file_system_->no_open_files(), 1) <
952
        (static_cast<int>(max_open_files_))-kNumReservedFd) {
953
      LogCvmfs(kLogCvmfs, kLogDebug, "file %s opened (fd %d)",
954
               path.c_str(), fd);
955
      // The same inode can refer to different revisions of a path. Don't cache.
956
      fi->keep_cache = 0;
957
      fi->fh = fd;
958
      fuse_reply_open(req, fi);
959
      return;
960
    } else {
961
      if (file_system_->cache_mgr()->Close(fd) == 0)
962
        perf::Dec(file_system_->no_open_files());
963
      LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
964
      fuse_reply_err(req, EMFILE);
965
      return;
966
    }
967
    assert(false);
968
  }
969
970
  // fd < 0
971
  LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
972
           "failed to open inode: %" PRIu64 ", CAS key %s, error code %d",
973
           uint64_t(ino), dirent.checksum().ToString().c_str(), errno);
974
  if (errno == EMFILE) {
975
    fuse_reply_err(req, EMFILE);
976
    return;
977
  }
978
979
  mount_point_->backoff_throttle()->Throttle();
980
981
  perf::Inc(file_system_->n_io_error());
982
  fuse_reply_err(req, -fd);
983
}
984
985
986
/**
987
 * Redirected to pread into cache.
988
 */
989
static void cvmfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
990
                       struct fuse_file_info *fi)
991
{
992
  LogCvmfs(kLogCvmfs, kLogDebug,
993
           "cvmfs_read inode: %" PRIu64 " reading %d bytes from offset %d "
994
           "fd %d", uint64_t(mount_point_->catalog_mgr()->MangleInode(ino)),
995
           size, off, fi->fh);
996
  perf::Inc(file_system_->n_fs_read());
997
998
  // Get data chunk (<=128k guaranteed by Fuse)
999
  char *data = static_cast<char *>(alloca(size));
1000
  unsigned int overall_bytes_fetched = 0;
1001
1002
  // Do we have a a chunked file?
1003
  if (static_cast<int64_t>(fi->fh) < 0) {
1004
    const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1005
    ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
1006
1007
    const uint64_t chunk_handle =
1008
      static_cast<uint64_t>(-static_cast<int64_t>(fi->fh));
1009
    uint64_t unique_inode;
1010
    ChunkFd chunk_fd;
1011
    FileChunkReflist chunks;
1012
    bool retval;
1013
1014
    // Fetch unique inode, chunk list and file descriptor
1015
    ChunkTables *chunk_tables = mount_point_->chunk_tables();
1016
    chunk_tables->Lock();
1017
    retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1018
    if (!retval) {
1019
      LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1020
      unique_inode = ino;
1021
    }
1022
    retval = chunk_tables->inode2chunks.Lookup(unique_inode, &chunks);
1023
    assert(retval);
1024
    chunk_tables->Unlock();
1025
1026
    unsigned chunk_idx = chunks.FindChunkIdx(off);
1027
1028
    // Lock chunk handle
1029
    pthread_mutex_t *handle_lock = chunk_tables->Handle2Lock(chunk_handle);
1030
    LockMutex(handle_lock);
1031
    chunk_tables->Lock();
1032
    retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1033
    assert(retval);
1034
    chunk_tables->Unlock();
1035
1036
    // Fetch all needed chunks and read the requested data
1037
    off_t offset_in_chunk = off - chunks.list->AtPtr(chunk_idx)->offset();
1038
    do {
1039
      // Open file descriptor to chunk
1040
      if ((chunk_fd.fd == -1) || (chunk_fd.chunk_idx != chunk_idx)) {
1041
        if (chunk_fd.fd != -1) file_system_->cache_mgr()->Close(chunk_fd.fd);
1042
        string verbose_path = "Part of " + chunks.path.ToString();
1043
        if (chunks.external_data) {
1044
          chunk_fd.fd = mount_point_->external_fetcher()->Fetch(
1045
            chunks.list->AtPtr(chunk_idx)->content_hash(),
1046
            chunks.list->AtPtr(chunk_idx)->size(),
1047
            verbose_path,
1048
            chunks.compression_alg,
1049
            mount_point_->catalog_mgr()->volatile_flag()
1050
              ? CacheManager::kTypeVolatile
1051
              : CacheManager::kTypeRegular,
1052
            chunks.path.ToString(),
1053
            chunks.list->AtPtr(chunk_idx)->offset());
1054
        } else {
1055
          chunk_fd.fd = mount_point_->fetcher()->Fetch(
1056
            chunks.list->AtPtr(chunk_idx)->content_hash(),
1057
            chunks.list->AtPtr(chunk_idx)->size(),
1058
            verbose_path,
1059
            chunks.compression_alg,
1060
            mount_point_->catalog_mgr()->volatile_flag()
1061
              ? CacheManager::kTypeVolatile
1062
              : CacheManager::kTypeRegular);
1063
        }
1064
        if (chunk_fd.fd < 0) {
1065
          chunk_fd.fd = -1;
1066
          chunk_tables->Lock();
1067
          chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1068
          chunk_tables->Unlock();
1069
          UnlockMutex(handle_lock);
1070
          fuse_reply_err(req, EIO);
1071
          return;
1072
        }
1073
        chunk_fd.chunk_idx = chunk_idx;
1074
      }
1075
1076
      LogCvmfs(kLogCvmfs, kLogDebug, "reading from chunk fd %d",
1077
               chunk_fd.fd);
1078
      // Read data from chunk
1079
      const size_t bytes_to_read = size - overall_bytes_fetched;
1080
      const size_t remaining_bytes_in_chunk =
1081
        chunks.list->AtPtr(chunk_idx)->size() - offset_in_chunk;
1082
      size_t bytes_to_read_in_chunk =
1083
        std::min(bytes_to_read, remaining_bytes_in_chunk);
1084
      const int64_t bytes_fetched = file_system_->cache_mgr()->Pread(
1085
        chunk_fd.fd,
1086
        data + overall_bytes_fetched,
1087
        bytes_to_read_in_chunk,
1088
        offset_in_chunk);
1089
1090
      if (bytes_fetched < 0) {
1091
        LogCvmfs(kLogCvmfs, kLogSyslogErr, "read err no %" PRId64 " (%s)",
1092
                 bytes_fetched, chunks.path.ToString().c_str());
1093
        chunk_tables->Lock();
1094
        chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1095
        chunk_tables->Unlock();
1096
        UnlockMutex(handle_lock);
1097
        fuse_reply_err(req, -bytes_fetched);
1098
        return;
1099
      }
1100
      overall_bytes_fetched += bytes_fetched;
1101
1102
      // Proceed to the next chunk to keep on reading data
1103
      ++chunk_idx;
1104
      offset_in_chunk = 0;
1105
    } while ((overall_bytes_fetched < size) &&
1106
             (chunk_idx < chunks.list->size()));
1107
1108
    // Update chunk file descriptor
1109
    chunk_tables->Lock();
1110
    chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1111
    chunk_tables->Unlock();
1112
    UnlockMutex(handle_lock);
1113
    LogCvmfs(kLogCvmfs, kLogDebug, "released chunk file descriptor %d",
1114
             chunk_fd.fd);
1115
  } else {
1116
    const int64_t fd = fi->fh;
1117
    int64_t nbytes = file_system_->cache_mgr()->Pread(fd, data, size, off);
1118
    if (nbytes < 0) {
1119
      fuse_reply_err(req, -nbytes);
1120
      return;
1121
    }
1122
    overall_bytes_fetched = nbytes;
1123
  }
1124
1125
  // Push it to user
1126
  fuse_reply_buf(req, data, overall_bytes_fetched);
1127
  LogCvmfs(kLogCvmfs, kLogDebug, "pushed %d bytes to user",
1128
           overall_bytes_fetched);
1129
}
1130
1131
1132
/**
1133
 * File close operation, redirected into cache.
1134
 */
1135
static void cvmfs_release(fuse_req_t req, fuse_ino_t ino,
1136
                          struct fuse_file_info *fi)
1137
{
1138
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
1139
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_release on inode: %" PRIu64,
1140
           uint64_t(ino));
1141
  const int64_t fd = fi->fh;
1142
1143
  // do we have a chunked file?
1144
  if (static_cast<int64_t>(fi->fh) < 0) {
1145
    const uint64_t chunk_handle =
1146
      static_cast<uint64_t>(-static_cast<int64_t>(fi->fh));
1147
    LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk handle %" PRIu64,
1148
             chunk_handle);
1149
    uint64_t unique_inode;
1150
    ChunkFd chunk_fd;
1151
    FileChunkReflist chunks;
1152
    uint32_t refctr;
1153
    bool retval;
1154
1155
    ChunkTables *chunk_tables = mount_point_->chunk_tables();
1156
    chunk_tables->Lock();
1157
    retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1158
    if (!retval) {
1159
      LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1160
      unique_inode = ino;
1161
    } else {
1162
      chunk_tables->handle2uniqino.Erase(chunk_handle);
1163
    }
1164
    retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1165
    assert(retval);
1166
    chunk_tables->handle2fd.Erase(chunk_handle);
1167
1168
    retval = chunk_tables->inode2references.Lookup(unique_inode, &refctr);
1169
    assert(retval);
1170
    refctr--;
1171
    if (refctr == 0) {
1172
      LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk list for inode %" PRIu64,
1173
               uint64_t(unique_inode));
1174
      FileChunkReflist to_delete;
1175
      retval = chunk_tables->inode2chunks.Lookup(unique_inode, &to_delete);
1176
      assert(retval);
1177
      chunk_tables->inode2references.Erase(unique_inode);
1178
      chunk_tables->inode2chunks.Erase(unique_inode);
1179
      delete to_delete.list;
1180
    } else {
1181
      chunk_tables->inode2references.Insert(unique_inode, refctr);
1182
    }
1183
    chunk_tables->Unlock();
1184
1185
    if (chunk_fd.fd != -1)
1186
      file_system_->cache_mgr()->Close(chunk_fd.fd);
1187
    perf::Dec(file_system_->no_open_files());
1188
  } else {
1189
    if (file_system_->cache_mgr()->Close(fd) == 0) {
1190
      perf::Dec(file_system_->no_open_files());
1191
    }
1192
  }
1193
  fuse_reply_err(req, 0);
1194
}
1195
1196
1197
static void cvmfs_statfs(fuse_req_t req, fuse_ino_t ino) {
1198
  ino = mount_point_->catalog_mgr()->MangleInode(ino);
1199
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_statfs on inode: %" PRIu64,
1200
           uint64_t(ino));
1201
1202
  // If we return 0 it will cause the fs to be ignored in "df"
1203
  struct statvfs info;
1204
  memset(&info, 0, sizeof(info));
1205
1206
  TraceInode(Tracer::kEventStatFs, ino, "statfs()");
1207
1208
  // Unmanaged cache
1209
  if (!file_system_->cache_mgr()->quota_mgr()->HasCapability(
1210
       QuotaManager::kCapIntrospectSize))
1211
  {
1212
    fuse_reply_statfs(req, &info);
1213
    return;
1214
  }
1215
1216
  uint64_t available = 0;
1217
  uint64_t size = file_system_->cache_mgr()->quota_mgr()->GetSize();
1218
  uint64_t capacity = file_system_->cache_mgr()->quota_mgr()->GetCapacity();
1219
  // Fuse/OS X doesn't like values < 512
1220
  info.f_bsize = info.f_frsize = 512;
1221
1222
  if (capacity == (uint64_t)(-1)) {
1223
    // Unknown capacity, set capacity = size
1224
    info.f_blocks = size / info.f_bsize;
1225
  } else {
1226
    // Take values from LRU module
1227
    info.f_blocks = capacity / info.f_bsize;
1228
    available = capacity - size;
1229
  }
1230
1231
  info.f_bfree = info.f_bavail = available / info.f_bsize;
1232
1233
  // Inodes / entries
1234
  fuse_remounter_->fence()->Enter();
1235
  uint64_t all_inodes = mount_point_->catalog_mgr()->all_inodes();
1236
  uint64_t loaded_inode = mount_point_->catalog_mgr()->loaded_inodes();
1237
  info.f_files = all_inodes;
1238
  info.f_ffree = info.f_favail = all_inodes - loaded_inode;
1239
  fuse_remounter_->fence()->Leave();
1240
1241
  fuse_reply_statfs(req, &info);
1242
}
1243
1244
1245
#ifdef __APPLE__
1246
static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1247
                           size_t size, uint32_t position)
1248
#else
1249
static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1250
                           size_t size)
1251
#endif
1252
{
1253
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1254
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
1255
1256
  fuse_remounter_->fence()->Enter();
1257
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1258
  ino = catalog_mgr->MangleInode(ino);
1259
  LogCvmfs(kLogCvmfs, kLogDebug,
1260
           "cvmfs_getxattr on inode: %" PRIu64 " for xattr: %s",
1261
           uint64_t(ino), name);
1262
  if (!CheckVoms(*fuse_ctx)) {
1263
    fuse_remounter_->fence()->Leave();
1264
    fuse_reply_err(req, EACCES);
1265
    return;
1266
  }
1267
  TraceInode(Tracer::kEventGetXAttr, ino, "getxattr()");
1268
1269
  const string attr = name;
1270
  catalog::DirectoryEntry d;
1271
  const bool found = GetDirentForInode(ino, &d);
1272
  bool retval;
1273
  XattrList xattrs;
1274
1275
  PathString path;
1276
  retval = GetPathForInode(ino, &path);
1277
  assert(retval);
1278
  if (d.IsLink()) {
1279
    catalog::LookupOptions lookup_options = static_cast<catalog::LookupOptions>(
1280
      catalog::kLookupSole | catalog::kLookupRawSymlink);
1281
    catalog::DirectoryEntry raw_symlink;
1282
    retval = catalog_mgr->LookupPath(path, lookup_options, &raw_symlink);
1283
    assert(retval);
1284
    d.set_symlink(raw_symlink.symlink());
1285
  }
1286
  if (d.HasXattrs()) {
1287
    retval = catalog_mgr->LookupXattrs(path, &xattrs);
1288
    assert(retval);
1289
  }
1290
  fuse_remounter_->fence()->Leave();
1291
1292
  if (!found) {
1293
    ReplyNegative(d, req);
1294
    return;
1295
  }
1296
1297
  string attribute_value;
1298
1299
  if (attr == "user.pid") {
1300
    attribute_value = StringifyInt(pid_);
1301
  } else if (attr == "user.version") {
1302
    attribute_value = string(VERSION) + "." + string(CVMFS_PATCH_LEVEL);
1303
  } else if (attr == "user.pubkeys") {
1304
    attribute_value = mount_point_->signature_mgr()->GetActivePubkeys();
1305
  } else if (attr == "user.hash") {
1306
    if (!d.checksum().IsNull()) {
1307
      attribute_value = d.checksum().ToString();
1308
    } else {
1309
      fuse_reply_err(req, ENOATTR);
1310
      return;
1311
    }
1312
  } else if (attr == "user.lhash") {
1313
    if (!d.checksum().IsNull()) {
1314
      string result;
1315
      CacheManager::ObjectInfo object_info;
1316
      object_info.description = path.ToString();
1317
      if (mount_point_->catalog_mgr()->volatile_flag())
1318
        object_info.type = CacheManager::kTypeVolatile;
1319
      int fd = file_system_->cache_mgr()->Open(
1320
        CacheManager::Bless(d.checksum(), object_info));
1321
      if (fd < 0) {
1322
        attribute_value = "Not in cache";
1323
      } else {
1324
        shash::Any hash(d.checksum().algorithm);
1325
        int retval_i = file_system_->cache_mgr()->ChecksumFd(fd, &hash);
1326
        if (retval_i != 0)
1327
          attribute_value = "I/O error (" + StringifyInt(retval_i) + ")";
1328
        else
1329
          attribute_value = hash.ToString();
1330
        file_system_->cache_mgr()->Close(fd);
1331
      }
1332
    } else {
1333
      fuse_reply_err(req, ENOATTR);
1334
      return;
1335
    }
1336
  } else if ((attr == "xfsroot.rawlink") || (attr == "user.rawlink")) {
1337
    if (d.IsLink()) {
1338
      attribute_value = d.symlink().ToString();
1339
    } else {
1340
      fuse_reply_err(req, ENOATTR);
1341
      return;
1342
    }
1343
  } else if (attr == "user.revision") {
1344
    const uint64_t revision = catalog_mgr->GetRevision();
1345
    attribute_value = StringifyInt(revision);
1346
  } else if (attr == "user.root_hash") {
1347
    attribute_value = catalog_mgr->GetRootHash().ToString();
1348
  } else if (attr == "user.tag") {
1349
    attribute_value = mount_point_->repository_tag();
1350
  } else if (attr == "user.expires") {
1351
    if (fuse_remounter_->catalogs_valid_until() ==
1352
        MountPoint::kIndefiniteDeadline)
1353
    {
1354
      attribute_value = "never (fixed root catalog)";
1355
    } else {
1356
      time_t now = time(NULL);
1357
      attribute_value = StringifyInt(
1358
        (fuse_remounter_->catalogs_valid_until() - now) / 60);
1359
    }
1360
  } else if (attr == "user.maxfd") {
1361
    attribute_value = StringifyInt(max_open_files_ - kNumReservedFd);
1362
  } else if (attr == "user.usedfd") {
1363
    attribute_value = file_system_->no_open_files()->ToString();
1364
  } else if (attr == "user.useddirp") {
1365
    attribute_value = file_system_->no_open_dirs()->ToString();
1366
  } else if (attr == "user.nioerr") {
1367
    attribute_value = file_system_->n_io_error()->ToString();
1368
  } else if (attr == "user.proxy") {
1369
    vector< vector<download::DownloadManager::ProxyInfo> > proxy_chain;
1370
    unsigned current_group;
1371
    mount_point_->download_mgr()->GetProxyInfo(
1372
      &proxy_chain, &current_group, NULL);
1373
    if (proxy_chain.size()) {
1374
      attribute_value = proxy_chain[current_group][0].url;
1375
    } else {
1376
      attribute_value = "DIRECT";
1377
    }
1378
  } else if (attr == "user.authz") {
1379
    if (!mount_point_->has_membership_req()) {
1380
      fuse_reply_err(req, ENOATTR);
1381
      return;
1382
    }
1383
    attribute_value = mount_point_->membership_req();
1384
  } else if (attr == "user.chunks") {
1385
    if (d.IsRegular()) {
1386
      if (d.IsChunkedFile()) {
1387
        FileChunkList chunks;
1388
        if (!catalog_mgr->ListFileChunks(path, d.hash_algorithm(), &chunks) ||
1389
            chunks.IsEmpty())
1390
        {
1391
          LogCvmfs(kLogCvmfs, kLogDebug| kLogSyslogErr, "file %s is marked as "
1392
                   "'chunked', but no chunks found.", path.c_str());
1393
          fuse_reply_err(req, EIO);
1394
          return;
1395
        } else {
1396
          attribute_value = StringifyInt(chunks.size());
1397
        }
1398
      } else {
1399
        attribute_value = "1";
1400
      }
1401
    } else {
1402
      fuse_reply_err(req, ENOATTR);
1403
      return;
1404
    }
1405
  } else if (attr == "user.external_file") {
1406
    if (d.IsRegular()) {
1407
      attribute_value = d.IsExternalFile() ? "1" : "0";
1408
    } else {
1409
      fuse_reply_err(req, ENOATTR);
1410
      return;
1411
    }
1412
  } else if (attr == "user.external_host") {
1413
    vector<string> host_chain;
1414
    vector<int> rtt;
1415
    unsigned current_host;
1416
    mount_point_->external_download_mgr()->GetHostInfo(
1417
      &host_chain, &rtt, &current_host);
1418
    if (host_chain.size()) {
1419
      attribute_value = string(host_chain[current_host]);
1420
    } else {
1421
      attribute_value = "internal error: no hosts defined";
1422
    }
1423
  } else if (attr == "user.compression") {
1424
    if (d.IsRegular()) {
1425
      attribute_value = zlib::AlgorithmName(d.compression_algorithm());
1426
    } else {
1427
      fuse_reply_err(req, ENOATTR);
1428
      return;
1429
    }
1430
  } else if (attr == "user.host") {
1431
    vector<string> host_chain;
1432
    vector<int> rtt;
1433
    unsigned current_host;
1434
    mount_point_->download_mgr()->GetHostInfo(&host_chain, &rtt, &current_host);
1435
    if (host_chain.size()) {
1436
      attribute_value = string(host_chain[current_host]);
1437
    } else {
1438
      attribute_value = "internal error: no hosts defined";
1439
    }
1440
  } else if (attr == "user.host_list") {
1441
    vector<string> host_chain;
1442
    vector<int> rtt;
1443
    unsigned current_host;
1444
    mount_point_->download_mgr()->GetHostInfo(&host_chain, &rtt, &current_host);
1445
    if (host_chain.size()) {
1446
      attribute_value = host_chain[current_host];
1447
      for (unsigned i = 1; i < host_chain.size(); ++i) {
1448
        attribute_value +=
1449
          ";" + host_chain[(i+current_host) % host_chain.size()];
1450
      }
1451
    } else {
1452
      attribute_value = "internal error: no hosts defined";
1453
    }
1454
  } else if (attr == "user.uptime") {
1455
    time_t now = time(NULL);
1456
    uint64_t uptime = now - loader_exports_->boot_time;
1457
    attribute_value = StringifyInt(uptime / 60);
1458
  } else if (attr == "user.nclg") {
1459
    const int num_catalogs = catalog_mgr->GetNumCatalogs();
1460
    attribute_value = StringifyInt(num_catalogs);
1461
  } else if (attr == "user.nopen") {
1462
    attribute_value = file_system_->n_fs_open()->ToString();
1463
  } else if (attr == "user.ndiropen") {
1464
    attribute_value = file_system_->n_fs_dir_open()->ToString();
1465
  } else if (attr == "user.ndownload") {
1466
    attribute_value =
1467
      mount_point_->statistics()->Lookup("fetch.n_downloads")->Print();
1468
  } else if (attr == "user.timeout") {
1469
    unsigned seconds, seconds_direct;
1470
    mount_point_->download_mgr()->GetTimeout(&seconds, &seconds_direct);
1471
    attribute_value = StringifyInt(seconds);
1472
  } else if (attr == "user.timeout_direct") {
1473
    unsigned seconds, seconds_direct;
1474
    mount_point_->download_mgr()->GetTimeout(&seconds, &seconds_direct);
1475
    attribute_value = StringifyInt(seconds_direct);
1476
  } else if (attr == "user.external_timeout") {
1477
    unsigned seconds, seconds_direct;
1478
    mount_point_->download_mgr()->GetTimeout(&seconds, &seconds_direct);
1479
    attribute_value = StringifyInt(seconds_direct);
1480
  } else if (attr == "user.rx") {
1481
    perf::Statistics *statistics = mount_point_->statistics();
1482
    int64_t rx = statistics->Lookup("download.sz_transferred_bytes")->Get();
1483
    attribute_value = StringifyInt(rx/1024);
1484
  } else if (attr == "user.speed") {
1485
    perf::Statistics *statistics = mount_point_->statistics();
1486
    int64_t rx = statistics->Lookup("download.sz_transferred_bytes")->Get();
1487
    int64_t time = statistics->Lookup("download.sz_transfer_time")->Get();
1488
    if (time == 0)
1489
      attribute_value = "n/a";
1490
    else
1491
      attribute_value = StringifyInt((1000 * (rx/1024))/time);
1492
  } else if (attr == "user.fqrn") {
1493
    attribute_value = loader_exports_->repository_name;
1494
  } else if (attr == "user.inode_max") {
1495
    attribute_value = StringifyInt(
1496
      inode_generation_info_.inode_generation +
1497
      catalog_mgr->inode_gauge());
1498
  } else if (attr == "user.ncleanup24") {
1499
    QuotaManager *quota_mgr = file_system_->cache_mgr()->quota_mgr();
1500
    if (!quota_mgr->HasCapability(QuotaManager::kCapIntrospectCleanupRate)) {
1501
      attribute_value = StringifyInt(-1);
1502
    } else {
1503
      const uint64_t period_s = 24 * 60 * 60;
1504
      const uint64_t rate = quota_mgr->GetCleanupRate(period_s);
1505
      attribute_value = StringifyInt(rate);
1506
    }
1507
  } else {
1508
    if (!xattrs.Get(attr, &attribute_value)) {
1509
      fuse_reply_err(req, ENOATTR);
1510
      return;
1511
    }
1512
  }
1513
1514
  if (size == 0) {
1515
    fuse_reply_xattr(req, attribute_value.length());
1516
  } else if (size >= attribute_value.length()) {
1517
    fuse_reply_buf(req, &attribute_value[0], attribute_value.length());
1518
  } else {
1519
    fuse_reply_err(req, ERANGE);
1520
  }
1521
}
1522
1523
1524
static void cvmfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
1525
  const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1526
  ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid);
1527
1528
  fuse_remounter_->fence()->Enter();
1529
  catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1530
  ino = catalog_mgr->MangleInode(ino);
1531
  TraceInode(Tracer::kEventListAttr, ino, "listxattr()");
1532
  LogCvmfs(kLogCvmfs, kLogDebug,
1533
           "cvmfs_listxattr on inode: %" PRIu64 ", size %u [hide xattrs %d]",
1534
           uint64_t(ino), size, mount_point_->hide_magic_xattrs());
1535
1536
  catalog::DirectoryEntry d;
1537
  const bool found = GetDirentForInode(ino, &d);
1538
  XattrList xattrs;
1539
  if (d.HasXattrs()) {
1540
    PathString path;
1541
    bool retval = GetPathForInode(ino, &path);
1542
    assert(retval);
1543
    retval = catalog_mgr->LookupXattrs(path, &xattrs);
1544
    assert(retval);
1545
  }
1546
  fuse_remounter_->fence()->Leave();
1547
1548
  if (!found) {
1549
    ReplyNegative(d, req);
1550
    return;
1551
  }
1552
1553
  const char base_list[] = "user.pid\0user.version\0user.revision\0"
1554
    "user.root_hash\0user.expires\0user.maxfd\0user.usedfd\0user.nioerr\0"
1555
    "user.host\0user.proxy\0user.uptime\0user.nclg\0user.nopen\0"
1556
    "user.ndownload\0user.timeout\0user.timeout_direct\0user.rx\0user.speed\0"
1557
    "user.fqrn\0user.ndiropen\0user.inode_max\0user.tag\0user.host_list\0"
1558
    "user.external_host\0user.external_timeout\0user.pubkeys\0"
1559
    "user.ncleanup24\0";
1560
  string attribute_list;
1561
  if (mount_point_->hide_magic_xattrs()) {
1562
    LogCvmfs(kLogCvmfs, kLogDebug, "Hiding extended attributes");
1563
    attribute_list = xattrs.ListKeysPosix("");
1564
  } else {
1565
    attribute_list = string(base_list, sizeof(base_list)-1);
1566
    if (!d.checksum().IsNull()) {
1567
      const char regular_file_list[] = "user.hash\0user.lhash\0";
1568
      attribute_list += string(regular_file_list, sizeof(regular_file_list)-1);
1569
    }
1570
1571
    if (d.IsLink()) {
1572
      const char symlink_list[] = "xfsroot.rawlink\0user.rawlink\0";
1573
      attribute_list += string(symlink_list, sizeof(symlink_list)-1);
1574
    } else if (d.IsRegular()) {
1575
      const char regular_file_list[] = "user.external_file\0user.compression\0"
1576
                                       "user.chunks\0";
1577
      attribute_list += string(regular_file_list, sizeof(regular_file_list)-1);
1578
    }
1579
1580
    if (mount_point_->has_membership_req()) {
1581
      attribute_list += "user.authz\0";
1582
    }
1583
    attribute_list = xattrs.ListKeysPosix(attribute_list);
1584
  }
1585
1586
  if (size == 0) {
1587
    fuse_reply_xattr(req, attribute_list.length());
1588
  } else if (size >= attribute_list.length()) {
1589
    if (attribute_list.empty())
1590
      fuse_reply_buf(req, NULL, 0);
1591
    else
1592
      fuse_reply_buf(req, &attribute_list[0], attribute_list.length());
1593
  } else {
1594
    fuse_reply_err(req, ERANGE);
1595
  }
1596
}
1597
1598
1599
bool Evict(const string &path) {
1600
  catalog::DirectoryEntry dirent;
1601
  fuse_remounter_->fence()->Enter();
1602
  const bool found = GetDirentForPath(PathString(path), &dirent);
1603
  fuse_remounter_->fence()->Leave();
1604
1605
  if (!found || !dirent.IsRegular())
1606
    return false;
1607
  file_system_->cache_mgr()->quota_mgr()->Remove(dirent.checksum());
1608
  return true;
1609
}
1610
1611
1612
bool Pin(const string &path) {
1613
  catalog::DirectoryEntry dirent;
1614
  fuse_remounter_->fence()->Enter();
1615
  const bool found = GetDirentForPath(PathString(path), &dirent);
1616
  if (!found || !dirent.IsRegular()) {
1617
    fuse_remounter_->fence()->Leave();
1618
    return false;
1619
  }
1620
1621
  if (!dirent.IsChunkedFile()) {
1622
    fuse_remounter_->fence()->Leave();
1623
  } else {
1624
    FileChunkList chunks;
1625
    mount_point_->catalog_mgr()->ListFileChunks(
1626
      PathString(path), dirent.hash_algorithm(), &chunks);
1627
    fuse_remounter_->fence()->Leave();
1628
    for (unsigned i = 0; i < chunks.size(); ++i) {
1629
      bool retval =
1630
        file_system_->cache_mgr()->quota_mgr()->Pin(
1631
          chunks.AtPtr(i)->content_hash(),
1632
          chunks.AtPtr(i)->size(),
1633
          "Part of " + path,
1634
          false);
1635
      if (!retval)
1636
        return false;
1637
      int fd = -1;
1638
      if (dirent.IsExternalFile()) {
1639
        fd = mount_point_->external_fetcher()->Fetch(
1640
          chunks.AtPtr(i)->content_hash(),
1641
          chunks.AtPtr(i)->size(),
1642
          "Part of " + path,
1643
          dirent.compression_algorithm(),
1644
          CacheManager::kTypePinned,
1645
          path,
1646
          chunks.AtPtr(i)->offset());
1647
      } else {
1648
        fd = mount_point_->fetcher()->Fetch(
1649
          chunks.AtPtr(i)->content_hash(),
1650
          chunks.AtPtr(i)->size(),
1651
          "Part of " + path,
1652
          dirent.compression_algorithm(),
1653
          CacheManager::kTypePinned);
1654
      }
1655
      if (fd < 0) {
1656
        return false;
1657
      }
1658
      file_system_->cache_mgr()->Close(fd);
1659
    }
1660
    return true;
1661
  }
1662
1663
  bool retval = file_system_->cache_mgr()->quota_mgr()->Pin(
1664
    dirent.checksum(), dirent.size(), path, false);
1665
  if (!retval)
1666
    return false;
1667
  Fetcher *this_fetcher = dirent.IsExternalFile()
1668
    ? mount_point_->external_fetcher()
1669
    : mount_point_->fetcher();
1670
  int fd = this_fetcher->Fetch(
1671
    dirent.checksum(), dirent.size(), path, dirent.compression_algorithm(),
1672
    CacheManager::kTypePinned);
1673
  if (fd < 0) {
1674
    return false;
1675
  }
1676
  file_system_->cache_mgr()->Close(fd);
1677
  return true;
1678
}
1679
1680
1681
/**
1682
 * Do after-daemon() initialization
1683
 */
1684
static void cvmfs_init(void *userdata, struct fuse_conn_info *conn) {
1685
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_init");
1686
1687
  // NFS support
1688
#ifdef CVMFS_NFS_SUPPORT
1689
  conn->want |= FUSE_CAP_EXPORT_SUPPORT;
1690
#endif
1691
}
1692
1693
static void cvmfs_destroy(void *unused __attribute__((unused))) {
1694
  // The debug log is already closed at this point
1695
  LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_destroy");
1696
}
1697
1698
/**
1699
 * Puts the callback functions in one single structure
1700
 */
1701
static void SetCvmfsOperations(struct fuse_lowlevel_ops *cvmfs_operations) {
1702
  memset(cvmfs_operations, 0, sizeof(*cvmfs_operations));
1703
1704
  // Init/Fini
1705
  cvmfs_operations->init     = cvmfs_init;
1706
  cvmfs_operations->destroy  = cvmfs_destroy;
1707
1708
  cvmfs_operations->lookup      = cvmfs_lookup;
1709
  cvmfs_operations->getattr     = cvmfs_getattr;
1710
  cvmfs_operations->readlink    = cvmfs_readlink;
1711
  cvmfs_operations->open        = cvmfs_open;
1712
  cvmfs_operations->read        = cvmfs_read;
1713
  cvmfs_operations->release     = cvmfs_release;
1714
  cvmfs_operations->opendir     = cvmfs_opendir;
1715
  cvmfs_operations->readdir     = cvmfs_readdir;
1716
  cvmfs_operations->releasedir  = cvmfs_releasedir;
1717
  cvmfs_operations->statfs      = cvmfs_statfs;
1718
  cvmfs_operations->getxattr    = cvmfs_getxattr;
1719
  cvmfs_operations->listxattr   = cvmfs_listxattr;
1720
  cvmfs_operations->forget      = cvmfs_forget;
1721
}
1722
1723
// Called by cvmfs_talk when switching into read-only cache mode
1724
void UnregisterQuotaListener() {
1725
  if (cvmfs::unpin_listener_) {
1726
    quota::UnregisterListener(cvmfs::unpin_listener_);
1727
    cvmfs::unpin_listener_ = NULL;
1728
  }
1729
  if (cvmfs::watchdog_listener_) {
1730
    quota::UnregisterListener(cvmfs::watchdog_listener_);
1731
    cvmfs::watchdog_listener_ = NULL;
1732
  }
1733
}
1734
1735
}  // namespace cvmfs
1736
1737
1738
string *g_boot_error = NULL;
1739
1740
__attribute__((visibility("default")))
1741
loader::CvmfsExports *g_cvmfs_exports = NULL;
1742
1743
1744
/**
1745
 * Construct a file system but prevent hanging when already mounted.  That
1746
 * means: at most one "system" mount of any given repository name.
1747
 */
1748
static FileSystem *InitSystemFs(
1749
  const string &mount_path,
1750
  const string &fqrn,
1751
  FileSystem::FileSystemInfo fs_info)
1752
{
1753
  fs_info.wait_workspace = false;
1754
  FileSystem *file_system = FileSystem::Create(fs_info);
1755
1756
  if (file_system->boot_status() == loader::kFailLockWorkspace) {
1757
    string fqrn_from_xattr;
1758
    int retval = platform_getxattr(mount_path, "user.fqrn", &fqrn_from_xattr);
1759
    if (!retval) {
1760
      // Cvmfs not mounted anymore, but another cvmfs process is still in
1761
      // shutdown procedure.  Try again and wait for lock
1762
      delete file_system;
1763
      fs_info.wait_workspace = true;
1764
      file_system = FileSystem::Create(fs_info);
1765
    } else {
1766
      if (fqrn_from_xattr == fqrn) {
1767
        LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
1768
                 "repository already mounted on %s", mount_path.c_str());
1769
        file_system->set_boot_status(loader::kFailDoubleMount);
1770
      } else {
1771
        LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1772
                 "CernVM-FS repository %s already mounted on %s",
1773
                 fqrn.c_str(), mount_path.c_str());
1774
        file_system->set_boot_status(loader::kFailOtherMount);
1775
      }
1776
    }
1777
  }
1778
1779
  return file_system;
1780
}
1781
1782
1783
static void InitOptionsMgr(const loader::LoaderExports *loader_exports) {
1784
  if (loader_exports->version >= 3 && loader_exports->simple_options_parsing) {
1785
    cvmfs::options_mgr_ = new SimpleOptionsParser(
1786
      new DefaultOptionsTemplateManager(loader_exports->repository_name));
1787
  } else {
1788
    cvmfs::options_mgr_ = new BashOptionsManager(
1789
      new DefaultOptionsTemplateManager(loader_exports->repository_name));
1790
  }
1791
1792
  if (loader_exports->config_files != "") {
1793
    vector<string> tokens = SplitString(loader_exports->config_files, ':');
1794
    for (unsigned i = 0, s = tokens.size(); i < s; ++i) {
1795
      cvmfs::options_mgr_->ParsePath(tokens[i], false);
1796
    }
1797
  } else {
1798
    cvmfs::options_mgr_->ParseDefault(loader_exports->repository_name);
1799
  }
1800
}
1801
1802
1803
static int Init(const loader::LoaderExports *loader_exports) {
1804
  g_boot_error = new string("unknown error");
1805
  cvmfs::loader_exports_ = loader_exports;
1806
  InitOptionsMgr(loader_exports);
1807
1808
  FileSystem::FileSystemInfo fs_info;
1809
  fs_info.type = FileSystem::kFsFuse;
1810
  fs_info.name = loader_exports->repository_name;
1811
  fs_info.exe_path = loader_exports->program_name;
1812
  fs_info.options_mgr = cvmfs::options_mgr_;
1813
  fs_info.foreground = loader_exports->foreground;
1814
  cvmfs::file_system_ = InitSystemFs(
1815
    loader_exports->mount_point,
1816
    loader_exports->repository_name,
1817
    fs_info);
1818
  if (!cvmfs::file_system_->IsValid()) {
1819
    *g_boot_error = cvmfs::file_system_->boot_error();
1820
    return cvmfs::file_system_->boot_status();
1821
  }
1822
1823
  cvmfs::mount_point_ = MountPoint::Create(loader_exports->repository_name,
1824
                                           cvmfs::file_system_);
1825
  if (!cvmfs::mount_point_->IsValid()) {
1826
    *g_boot_error = cvmfs::mount_point_->boot_error();
1827
    return cvmfs::mount_point_->boot_status();
1828
  }
1829
1830
  cvmfs::directory_handles_ = new cvmfs::DirectoryHandles();
1831
  cvmfs::directory_handles_->set_empty_key((uint64_t)(-1));
1832
  cvmfs::directory_handles_->set_deleted_key((uint64_t)(-2));
1833
1834
  LogCvmfs(kLogCvmfs, kLogDebug, "fuse inode size is %d bits",
1835
           sizeof(fuse_ino_t) * 8);
1836
1837
  cvmfs::inode_generation_info_.initial_revision =
1838
    cvmfs::mount_point_->catalog_mgr()->GetRevision();
1839
  cvmfs::inode_generation_info_.inode_generation =
1840
    cvmfs::mount_point_->inode_annotation()->GetGeneration();
1841
  LogCvmfs(kLogCvmfs, kLogDebug, "root inode is %" PRIu64,
1842
           uint64_t(cvmfs::mount_point_->catalog_mgr()->GetRootInode()));
1843
1844
  struct fuse_chan **channel = NULL;
1845
  if (loader_exports->version >= 4)
1846
    channel = loader_exports->fuse_channel;
1847
1848
  bool fuse_notify_invalidation = true;
1849
  std::string buf;
1850
  if (cvmfs::options_mgr_->GetValue("CVMFS_FUSE_NOTIFY_INVALIDATION",
1851
                                    &buf)) {
1852
    if (!cvmfs::options_mgr_->IsOn(buf)) {
1853
      fuse_notify_invalidation = false;
1854
    }
1855
  }
1856
  cvmfs::fuse_remounter_ =
1857
      new FuseRemounter(cvmfs::mount_point_, &cvmfs::inode_generation_info_,
1858
                        channel, fuse_notify_invalidation);
1859
1860
  // Monitor, check for maximum number of open files
1861
  if (cvmfs::UseWatchdog()) {
1862
    cvmfs::watchdog_ = Watchdog::Create("./stacktrace." +
1863
                                        loader_exports->repository_name);
1864
    if (cvmfs::watchdog_ == NULL) {
1865
      *g_boot_error = "failed to initialize watchdog.";
1866
      return loader::kFailMonitor;
1867
    }
1868
  }
1869
  cvmfs::max_open_files_ = monitor::GetMaxOpenFiles();
1870
1871
  // Control & command interface
1872
  cvmfs::talk_mgr_ = TalkManager::Create(
1873
    "./cvmfs_io." + cvmfs::mount_point_->fqrn(),
1874
    cvmfs::mount_point_,
1875
    cvmfs::fuse_remounter_);
1876
  if (cvmfs::talk_mgr_ == NULL) {
1877
    *g_boot_error = "failed to initialize talk socket (" +
1878
                    StringifyInt(errno) + ")";
1879
    return loader::kFailTalk;
1880
  }
1881
1882
  auto_umount::SetMountpoint(loader_exports->mount_point);
1883
1884
  return loader::kFailOk;
1885
}
1886
1887
1888
/**
1889
 * Things that have to be executed after fork() / daemon()
1890
 */
1891
static void Spawn() {
1892
  // First thing: fork off the watchdog while we still have a single-threaded
1893
  // well-defined state
1894
  cvmfs::pid_ = getpid();
1895
  if (cvmfs::watchdog_) {
1896
    cvmfs::watchdog_->RegisterOnCrash(auto_umount::UmountOnCrash);
1897
    cvmfs::watchdog_->Spawn();
1898
  }
1899
1900
  cvmfs::fuse_remounter_->Spawn();
1901
1902
  cvmfs::mount_point_->download_mgr()->Spawn();
1903
  cvmfs::mount_point_->external_download_mgr()->Spawn();
1904
  if (cvmfs::mount_point_->resolv_conf_watcher() != NULL)
1905
    cvmfs::mount_point_->resolv_conf_watcher()->Spawn();
1906
  QuotaManager *quota_mgr = cvmfs::file_system_->cache_mgr()->quota_mgr();
1907
  quota_mgr->Spawn();
1908
  if (quota_mgr->HasCapability(QuotaManager::kCapListeners)) {
1909
    cvmfs::watchdog_listener_ = quota::RegisterWatchdogListener(
1910
      quota_mgr,
1911
      cvmfs::mount_point_->uuid()->uuid() + "-watchdog");
1912
    cvmfs::unpin_listener_ = quota::RegisterUnpinListener(
1913
      quota_mgr,
1914
      cvmfs::mount_point_->catalog_mgr(),
1915
      cvmfs::mount_point_->uuid()->uuid() + "-unpin");
1916
  }
1917
  cvmfs::mount_point_->tracer()->Spawn();
1918
  cvmfs::talk_mgr_->Spawn();
1919
  if (cvmfs::file_system_->nfs_maps() != NULL)
1920
    cvmfs::file_system_->nfs_maps()->Spawn();
1921
1922
  cvmfs::file_system_->cache_mgr()->Spawn();
1923
}
1924
1925
1926
static string GetErrorMsg() {
1927
  if (g_boot_error)
1928
    return *g_boot_error;
1929
  return "";
1930
}
1931
1932
1933
static void Fini() {
1934
  delete cvmfs::talk_mgr_;
1935
  cvmfs::talk_mgr_ = NULL;
1936
1937
  // The remonter has a reference to the mount point and the inode generation
1938
  delete cvmfs::fuse_remounter_;
1939
  cvmfs::fuse_remounter_ = NULL;
1940
1941
  // The unpin listener requires the catalog, so this must be unregistered
1942
  // before the catalog manager is removed
1943
  if (cvmfs::unpin_listener_ != NULL) {
1944
    quota::UnregisterListener(cvmfs::unpin_listener_);
1945
    cvmfs::unpin_listener_ = NULL;
1946
  }
1947
  if (cvmfs::watchdog_listener_ != NULL) {
1948
    quota::UnregisterListener(cvmfs::watchdog_listener_);
1949
    cvmfs::watchdog_listener_ = NULL;
1950
  }
1951
1952
  delete cvmfs::directory_handles_;
1953
  delete cvmfs::mount_point_;
1954
  delete cvmfs::file_system_;
1955
  delete cvmfs::options_mgr_;
1956
  cvmfs::directory_handles_ = NULL;
1957
  cvmfs::mount_point_ = NULL;
1958
  cvmfs::file_system_ = NULL;
1959
  cvmfs::options_mgr_ = NULL;
1960
1961
  delete cvmfs::watchdog_;
1962
  cvmfs::watchdog_ = NULL;
1963
1964
  delete g_boot_error;
1965
  g_boot_error = NULL;
1966
  auto_umount::SetMountpoint("");
1967
}
1968
1969
1970
static int AltProcessFlavor(int argc, char **argv) {
1971
  if (strcmp(argv[1], "__cachemgr__") == 0) {
1972
    return PosixQuotaManager::MainCacheManager(argc, argv);
1973
  }
1974
  if (strcmp(argv[1], "__wpad__") == 0) {
1975
    return download::MainResolveProxyDescription(argc, argv);
1976
  }
1977
  return 1;
1978
}
1979
1980
1981
static bool MaintenanceMode(const int fd_progress) {
1982
  SendMsg2Socket(fd_progress, "Entering maintenance mode\n");
1983
  string msg_progress = "Draining out kernel caches (";
1984
  if (FuseInvalidator::HasFuseNotifyInval())
1985
    msg_progress += "up to ";
1986
  msg_progress += StringifyInt(static_cast<int>(
1987
                               cvmfs::mount_point_->kcache_timeout_sec())) +
1988
                  "s)\n";
1989
  SendMsg2Socket(fd_progress, msg_progress);
1990
  cvmfs::fuse_remounter_->EnterMaintenanceMode();
1991
  return true;
1992
}
1993
1994
1995
static bool SaveState(const int fd_progress, loader::StateList *saved_states) {
1996
  string msg_progress;
1997
1998
  unsigned num_open_dirs = cvmfs::directory_handles_->size();
1999
  if (num_open_dirs != 0) {
2000
#ifdef DEBUGMSG
2001
    for (cvmfs::DirectoryHandles::iterator i =
2002
         cvmfs::directory_handles_->begin(),
2003
         iEnd = cvmfs::directory_handles_->end(); i != iEnd; ++i)
2004
    {
2005
      LogCvmfs(kLogCvmfs, kLogDebug, "saving dirhandle %d", i->first);
2006
    }
2007
#endif
2008
2009
    msg_progress = "Saving open directory handles (" +
2010
      StringifyInt(num_open_dirs) + " handles)\n";
2011
    SendMsg2Socket(fd_progress, msg_progress);
2012
2013
    // TODO(jblomer): should rather be saved just in a malloc'd memory block
2014
    cvmfs::DirectoryHandles *saved_handles =
2015
      new cvmfs::DirectoryHandles(*cvmfs::directory_handles_);
2016
    loader::SavedState *save_open_dirs = new loader::SavedState();
2017
    save_open_dirs->state_id = loader::kStateOpenDirs;
2018
    save_open_dirs->state = saved_handles;
2019
    saved_states->push_back(save_open_dirs);
2020
  }
2021
2022
  if (!cvmfs::file_system_->IsNfsSource()) {
2023
    msg_progress = "Saving inode tracker\n";
2024
    SendMsg2Socket(fd_progress, msg_progress);
2025
    glue::InodeTracker *saved_inode_tracker =
2026
      new glue::InodeTracker(*cvmfs::mount_point_->inode_tracker());
2027
    loader::SavedState *state_glue_buffer = new loader::SavedState();
2028
    state_glue_buffer->state_id = loader::kStateGlueBufferV4;
2029
    state_glue_buffer->state = saved_inode_tracker;
2030
    saved_states->push_back(state_glue_buffer);
2031
  }
2032
2033
  msg_progress = "Saving chunk tables\n";
2034
  SendMsg2Socket(fd_progress, msg_progress);
2035
  ChunkTables *saved_chunk_tables = new ChunkTables(
2036
    *cvmfs::mount_point_->chunk_tables());
2037
  loader::SavedState *state_chunk_tables = new loader::SavedState();
2038
  state_chunk_tables->state_id = loader::kStateOpenChunksV4;
2039
  state_chunk_tables->state = saved_chunk_tables;
2040
  saved_states->push_back(state_chunk_tables);
2041
2042
  msg_progress = "Saving inode generation\n";
2043
  SendMsg2Socket(fd_progress, msg_progress);
2044
  cvmfs::inode_generation_info_.inode_generation +=
2045
    cvmfs::mount_point_->catalog_mgr()->inode_gauge();
2046
  cvmfs::InodeGenerationInfo *saved_inode_generation =
2047
    new cvmfs::InodeGenerationInfo(cvmfs::inode_generation_info_);
2048
  loader::SavedState *state_inode_generation = new loader::SavedState();
2049
  state_inode_generation->state_id = loader::kStateInodeGeneration;
2050
  state_inode_generation->state = saved_inode_generation;
2051
  saved_states->push_back(state_inode_generation);
2052
2053
  SendMsg2Socket(fd_progress, msg_progress);
2054
  loader::SavedState *state_cache_mgr = new loader::SavedState();
2055
  state_cache_mgr->state_id = loader::kStateOpenFiles;
2056
  state_cache_mgr->state =
2057
    cvmfs::file_system_->cache_mgr()->SaveState(fd_progress);
2058
  saved_states->push_back(state_cache_mgr);
2059
2060
  msg_progress = "Saving open files counter\n";
2061
  SendMsg2Socket(fd_progress, msg_progress);
2062
  uint32_t *saved_num_fd =
2063
    new uint32_t(cvmfs::file_system_->no_open_files()->Get());
2064
  loader::SavedState *state_num_fd = new loader::SavedState();
2065
  state_num_fd->state_id = loader::kStateOpenFilesCounter;
2066
  state_num_fd->state = saved_num_fd;
2067
  saved_states->push_back(state_num_fd);
2068
2069
  return true;
2070
}
2071
2072
2073
static bool RestoreState(const int fd_progress,
2074
                         const loader::StateList &saved_states)
2075
{
2076
  for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
2077
    if (saved_states[i]->state_id == loader::kStateOpenDirs) {
2078
      SendMsg2Socket(fd_progress, "Restoring open directory handles... ");
2079
      delete cvmfs::directory_handles_;
2080
      cvmfs::DirectoryHandles *saved_handles =
2081
        (cvmfs::DirectoryHandles *)saved_states[i]->state;
2082
      cvmfs::directory_handles_ = new cvmfs::DirectoryHandles(*saved_handles);
2083
      cvmfs::file_system_->no_open_dirs()->Set(
2084
        cvmfs::directory_handles_->size());
2085
      cvmfs::DirectoryHandles::const_iterator i =
2086
        cvmfs::directory_handles_->begin();
2087
      for (; i != cvmfs::directory_handles_->end(); ++i) {
2088
        if (i->first >= cvmfs::next_directory_handle_)
2089
          cvmfs::next_directory_handle_ = i->first + 1;
2090
      }
2091
2092
      SendMsg2Socket(fd_progress,
2093
        StringifyInt(cvmfs::directory_handles_->size()) + " handles\n");
2094
    }
2095
2096
    if (saved_states[i]->state_id == loader::kStateGlueBuffer) {
2097
      SendMsg2Socket(fd_progress, "Migrating inode tracker (v1 to v4)... ");
2098
      compat::inode_tracker::InodeTracker *saved_inode_tracker =
2099
        (compat::inode_tracker::InodeTracker *)saved_states[i]->state;
2100
      compat::inode_tracker::Migrate(
2101
        saved_inode_tracker, cvmfs::mount_point_->inode_tracker());
2102
      SendMsg2Socket(fd_progress, " done\n");
2103
    }
2104
2105
    if (saved_states[i]->state_id == loader::kStateGlueBufferV2) {
2106
      SendMsg2Socket(fd_progress, "Migrating inode tracker (v2 to v4)... ");
2107
      compat::inode_tracker_v2::InodeTracker *saved_inode_tracker =
2108
        (compat::inode_tracker_v2::InodeTracker *)saved_states[i]->state;
2109
      compat::inode_tracker_v2::Migrate(saved_inode_tracker,
2110
                                        cvmfs::mount_point_->inode_tracker());
2111
      SendMsg2Socket(fd_progress, " done\n");
2112
    }
2113
2114
    if (saved_states[i]->state_id == loader::kStateGlueBufferV3) {
2115
      SendMsg2Socket(fd_progress, "Migrating inode tracker (v3 to v4)... ");
2116
      compat::inode_tracker_v3::InodeTracker *saved_inode_tracker =
2117
        (compat::inode_tracker_v3::InodeTracker *)saved_states[i]->state;
2118
      compat::inode_tracker_v3::Migrate(saved_inode_tracker,
2119
                                        cvmfs::mount_point_->inode_tracker());
2120
      SendMsg2Socket(fd_progress, " done\n");
2121
    }
2122
2123
    if (saved_states[i]->state_id == loader::kStateGlueBufferV4) {
2124
      SendMsg2Socket(fd_progress, "Restoring inode tracker... ");
2125
      cvmfs::mount_point_->inode_tracker()->~InodeTracker();
2126
      glue::InodeTracker *saved_inode_tracker =
2127
        (glue::InodeTracker *)saved_states[i]->state;
2128
      new (cvmfs::mount_point_->inode_tracker())
2129
        glue::InodeTracker(*saved_inode_tracker);
2130
      SendMsg2Socket(fd_progress, " done\n");
2131
    }
2132
2133
    ChunkTables *chunk_tables = cvmfs::mount_point_->chunk_tables();
2134
2135
    if (saved_states[i]->state_id == loader::kStateOpenChunks) {
2136
      SendMsg2Socket(fd_progress, "Migrating chunk tables (v1 to v4)... ");
2137
      compat::chunk_tables::ChunkTables *saved_chunk_tables =
2138
        (compat::chunk_tables::ChunkTables *)saved_states[i]->state;
2139
      compat::chunk_tables::Migrate(saved_chunk_tables, chunk_tables);
2140
      SendMsg2Socket(fd_progress,
2141
        StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2142
    }
2143
2144
    if (saved_states[i]->state_id == loader::kStateOpenChunksV2) {
2145
      SendMsg2Socket(fd_progress, "Migrating chunk tables (v2 to v4)... ");
2146
      compat::chunk_tables_v2::ChunkTables *saved_chunk_tables =
2147
        (compat::chunk_tables_v2::ChunkTables *)saved_states[i]->state;
2148
      compat::chunk_tables_v2::Migrate(saved_chunk_tables, chunk_tables);
2149
      SendMsg2Socket(fd_progress,
2150
        StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2151
    }
2152
2153
    if (saved_states[i]->state_id == loader::kStateOpenChunksV3) {
2154
      SendMsg2Socket(fd_progress, "Migrating chunk tables (v3 to v4)... ");
2155
      compat::chunk_tables_v3::ChunkTables *saved_chunk_tables =
2156
        (compat::chunk_tables_v3::ChunkTables *)saved_states[i]->state;
2157
      compat::chunk_tables_v3::Migrate(saved_chunk_tables, chunk_tables);
2158
      SendMsg2Socket(fd_progress,
2159
        StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2160
    }
2161
2162
    if (saved_states[i]->state_id == loader::kStateOpenChunksV4) {
2163
      SendMsg2Socket(fd_progress, "Restoring chunk tables... ");
2164
      chunk_tables->~ChunkTables();
2165
      ChunkTables *saved_chunk_tables = reinterpret_cast<ChunkTables *>(
2166
        saved_states[i]->state);
2167
      new (chunk_tables) ChunkTables(*saved_chunk_tables);
2168
      SendMsg2Socket(fd_progress, " done\n");
2169
    }
2170
2171
    if (saved_states[i]->state_id == loader::kStateInodeGeneration) {
2172
      SendMsg2Socket(fd_progress, "Restoring inode generation... ");
2173
      cvmfs::InodeGenerationInfo *old_info =
2174
        (cvmfs::InodeGenerationInfo *)saved_states[i]->state;
2175
      if (old_info->version == 1) {
2176
        // Migration
2177
        cvmfs::inode_generation_info_.initial_revision =
2178
          old_info->initial_revision;
2179
        cvmfs::inode_generation_info_.incarnation = old_info->incarnation;
2180
        // Note: in the rare case of inode generation being 0 before, inode
2181
        // can clash after reload before remount
2182
      } else {
2183
        cvmfs::inode_generation_info_ = *old_info;
2184
      }
2185
      ++cvmfs::inode_generation_info_.incarnation;
2186
      SendMsg2Socket(fd_progress, " done\n");
2187
    }
2188
2189
    if (saved_states[i]->state_id == loader::kStateOpenFilesCounter) {
2190
      SendMsg2Socket(fd_progress, "Restoring open files counter... ");
2191
      cvmfs::file_system_->no_open_files()->Set(*(reinterpret_cast<uint32_t *>(
2192
        saved_states[i]->state)));
2193
      SendMsg2Socket(fd_progress, " done\n");
2194
    }
2195
2196
    if (saved_states[i]->state_id == loader::kStateOpenFiles) {
2197
      cvmfs::file_system_->cache_mgr()->RestoreState(
2198
        fd_progress, saved_states[i]->state);
2199
    }
2200
  }
2201
  if (cvmfs::mount_point_->inode_annotation()) {
2202
    uint64_t saved_generation = cvmfs::inode_generation_info_.inode_generation;
2203
    cvmfs::mount_point_->inode_annotation()->IncGeneration(saved_generation);
2204
  }
2205
2206
  return true;
2207
}
2208
2209
2210
static void FreeSavedState(const int fd_progress,
2211
                           const loader::StateList &saved_states)
2212
{
2213
  for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
2214
    switch (saved_states[i]->state_id) {
2215
      case loader::kStateOpenDirs:
2216
        SendMsg2Socket(fd_progress, "Releasing saved open directory handles\n");
2217
        delete static_cast<cvmfs::DirectoryHandles *>(saved_states[i]->state);
2218
        break;
2219
      case loader::kStateGlueBuffer:
2220
        SendMsg2Socket(
2221
          fd_progress, "Releasing saved glue buffer (version 1)\n");
2222
        delete static_cast<compat::inode_tracker::InodeTracker *>(
2223
          saved_states[i]->state);
2224
        break;
2225
      case loader::kStateGlueBufferV2:
2226
        SendMsg2Socket(
2227
          fd_progress, "Releasing saved glue buffer (version 2)\n");
2228
        delete static_cast<compat::inode_tracker_v2::InodeTracker *>(
2229
          saved_states[i]->state);
2230
        break;
2231
      case loader::kStateGlueBufferV3:
2232
        SendMsg2Socket(
2233
          fd_progress, "Releasing saved glue buffer (version 3)\n");
2234
        delete static_cast<compat::inode_tracker_v3::InodeTracker *>(
2235
          saved_states[i]->state);
2236
        break;
2237
      case loader::kStateGlueBufferV4:
2238
        SendMsg2Socket(fd_progress, "Releasing saved glue buffer\n");
2239
        delete static_cast<glue::InodeTracker *>(saved_states[i]->state);
2240
        break;
2241
      case loader::kStateOpenChunks:
2242
        SendMsg2Socket(fd_progress, "Releasing chunk tables (version 1)\n");
2243
        delete static_cast<compat::chunk_tables::ChunkTables *>(
2244
          saved_states[i]->state);
2245
        break;
2246
      case loader::kStateOpenChunksV2:
2247
        SendMsg2Socket(fd_progress, "Releasing chunk tables (version 2)\n");
2248
        delete static_cast<compat::chunk_tables_v2::ChunkTables *>(
2249
          saved_states[i]->state);
2250
        break;
2251
      case loader::kStateOpenChunksV3:
2252
        SendMsg2Socket(fd_progress, "Releasing chunk tables (version 3)\n");
2253
        delete static_cast<compat::chunk_tables_v3::ChunkTables *>(
2254
          saved_states[i]->state);
2255
        break;
2256
      case loader::kStateOpenChunksV4:
2257
        SendMsg2Socket(fd_progress, "Releasing chunk tables\n");
2258
        delete static_cast<ChunkTables *>(saved_states[i]->state);
2259
        break;
2260
      case loader::kStateInodeGeneration:
2261
        SendMsg2Socket(fd_progress, "Releasing saved inode generation info\n");
2262
        delete static_cast<cvmfs::InodeGenerationInfo *>(
2263
          saved_states[i]->state);
2264
        break;
2265
      case loader::kStateOpenFiles:
2266
        cvmfs::file_system_->cache_mgr()->FreeState(
2267
          fd_progress, saved_states[i]->state);
2268
        break;
2269
      case loader::kStateOpenFilesCounter:
2270
        SendMsg2Socket(fd_progress, "Releasing open files counter\n");
2271
        delete static_cast<uint32_t *>(saved_states[i]->state);
2272
        break;
2273
      default:
2274
        break;
2275
    }
2276
  }
2277
}
2278
2279
2280
static void __attribute__((constructor)) LibraryMain() {
2281
  g_cvmfs_exports = new loader::CvmfsExports();
2282
  g_cvmfs_exports->so_version = PACKAGE_VERSION;
2283
  g_cvmfs_exports->fnAltProcessFlavor = AltProcessFlavor;
2284
  g_cvmfs_exports->fnInit = Init;
2285
  g_cvmfs_exports->fnSpawn = Spawn;
2286
  g_cvmfs_exports->fnFini = Fini;
2287
  g_cvmfs_exports->fnGetErrorMsg = GetErrorMsg;
2288
  g_cvmfs_exports->fnMaintenanceMode = MaintenanceMode;
2289
  g_cvmfs_exports->fnSaveState = SaveState;
2290
  g_cvmfs_exports->fnRestoreState = RestoreState;
2291
  g_cvmfs_exports->fnFreeSavedState = FreeSavedState;
2292
  cvmfs::SetCvmfsOperations(&g_cvmfs_exports->cvmfs_operations);
2293
}
2294
2295
2296
static void __attribute__((destructor)) LibraryExit() {
2297
  delete g_cvmfs_exports;
2298
  g_cvmfs_exports = NULL;
2299
}