GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cvmfs.cc
Date: 2026-08-09 02:40:25
Exec Total Coverage
Lines: 0 1672 0.0%
Branches: 0 2418 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
27 // sys/xattr.h conflicts with linux/xattr.h and needs to be loaded very early
28 // clang-format off
29 #include <sys/xattr.h> // NOLINT
30 // clang-format on
31
32
33 #include "cvmfs.h"
34
35 #include <alloca.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <inttypes.h>
39 #include <pthread.h>
40 #include <stddef.h>
41 #include <stdint.h>
42 #include <sys/errno.h>
43 #include <sys/statvfs.h>
44 #include <sys/types.h>
45 #include <unistd.h>
46
47 #include <algorithm>
48 #include <cassert>
49 #include <cstdio>
50 #include <cstdlib>
51 #include <cstring>
52 #include <ctime>
53 #include <google/dense_hash_map>
54 #include <string>
55 #include <utility>
56 #include <vector>
57
58 #include "authz/authz_fetch.h"
59 #include "authz/authz_session.h"
60 #include "auto_umount.h"
61 #include "backoff.h"
62 #include "bigvector.h"
63 #include "bundle_mgr.h"
64 #include "cache.h"
65 #include "cache_posix.h"
66 #include "cache_stream.h"
67 #include "catalog_mgr.h"
68 #include "catalog_mgr_client.h"
69 #include "clientctx.h"
70 #include "compat.h"
71 #include "compression/compression.h"
72 #include "crypto/crypto_util.h"
73 #include "crypto/hash.h"
74 #include "directory_entry.h"
75 //#include "duplex_fuse.h"
76 #include "fence.h"
77 #include "fetch.h"
78 #include "file_chunk.h"
79 #include "fuse_evict.h"
80 #include "fuse_inode_gen.h"
81 #include "fuse_remount.h"
82 #include "glue_buffer.h"
83 #include "interrupt.h"
84 #include "loader.h"
85 #include "lru_md.h"
86 #include "magic_xattr.h"
87 #include "manifest_fetch.h"
88 #include "monitor.h"
89 #include "mountpoint.h"
90 #include "network/download.h"
91 #include "nfs_maps.h"
92 #include "notification_client.h"
93 #include "options.h"
94 #include "quota_listener.h"
95 #include "quota_posix.h"
96 #include "sanitizer.h"
97 #include "shortstring.h"
98 #include "sqlitevfs.h"
99 #include "statistics.h"
100 #include "talk.h"
101 #include "telemetry_aggregator.h"
102 #include "tracer.h"
103 #include "util/algorithm.h"
104 #include "util/capabilities.h"
105 #include "util/exception.h"
106 #include "util/logging.h"
107 #include "util/mutex.h"
108 #include "util/pointer.h"
109 #include "util/posix.h"
110 #include "util/smalloc.h"
111 #include "util/string.h"
112 #include "util/testing.h"
113 #include "util/uuid.h"
114 #include "wpad.h"
115 #include "xattr.h"
116
117 using namespace std; // NOLINT
118
119 namespace cvmfs {
120
121 #ifndef __TEST_CVMFS_MOCKFUSE // will be mocked in tests
122 FileSystem *file_system_ = NULL;
123 MountPoint *mount_point_ = NULL;
124 TalkManager *talk_mgr_ = NULL;
125 NotificationClient *notification_client_ = NULL;
126 Watchdog *watchdog_ = NULL;
127 FuseRemounter *fuse_remounter_ = NULL;
128 InodeGenerationInfo inode_generation_info_;
129 #endif // __TEST_CVMFS_MOCKFUSE
130
131 #ifdef FUSE_CAP_PASSTHROUGH
132 typedef struct fuse_passthru_ctx {
133 int backing_id;
134 int refcount;
135 } fuse_passthru_ctx_t;
136 static std::unordered_map<fuse_ino_t, fuse_passthru_ctx_t> *fuse_passthru_tracker = NULL;
137 pthread_mutex_t fuse_passthru_tracker_lock = PTHREAD_MUTEX_INITIALIZER;
138 #endif
139
140 /**
141 * For cvmfs_opendir / cvmfs_readdir
142 * TODO: use mmap for very large listings
143 */
144 struct DirectoryListing {
145 char *buffer; /**< Filled by fuse_add_direntry */
146
147 // Not really used anymore. But directory listing needs to be migrated during
148 // hotpatch. If buffer is allocated by smmap, capacity is zero.
149 size_t size;
150 size_t capacity;
151
152 DirectoryListing() : buffer(NULL), size(0), capacity(0) { }
153 };
154
155 const loader::LoaderExports *loader_exports_ = NULL;
156 OptionsManager *options_mgr_ = NULL;
157 pid_t pid_ = 0; /**< will be set after daemon() */
158 quota::ListenerHandle *quota_watchdog_listener_ = NULL;
159 quota::ListenerHandle *quota_unpin_listener_ = NULL;
160
161
162 typedef google::dense_hash_map<uint64_t, DirectoryListing,
163 hash_murmur<uint64_t> >
164 DirectoryHandles;
165 DirectoryHandles *directory_handles_ = NULL;
166 pthread_mutex_t lock_directory_handles_ = PTHREAD_MUTEX_INITIALIZER;
167 uint64_t next_directory_handle_ = 0;
168
169 unsigned int max_open_files_; /**< maximum allowed number of open files */
170 /**
171 * The refcounted cache manager should suppress checking the current number
172 * of files opened through cvmfs_open() against the process' file descriptor
173 * limit.
174 */
175 bool check_fd_overflow_ = true;
176 /**
177 * Number of reserved file descriptors for internal use
178 */
179 const int kNumReservedFd = 512;
180 /**
181 * Warn if the process has a lower limit for the number of open file descriptors
182 */
183 const unsigned int kMinOpenFiles = 8192;
184
185
186 class FuseInterruptCue : public InterruptCue {
187 public:
188 explicit FuseInterruptCue(fuse_req_t *r) : req_ptr_(r) { }
189 virtual ~FuseInterruptCue() { }
190 virtual bool IsCanceled() { return fuse_req_interrupted(*req_ptr_); }
191
192 private:
193 fuse_req_t *req_ptr_;
194 };
195
196 /**
197 * Options related to the fuse kernel connection. The capabilities are
198 * determined only once at mount time. If the capability trigger certain
199 * behavior of the cvmfs fuse module, it needs to be re-triggered on reload.
200 * Used in SaveState and RestoreState to store the details of symlink caching.
201 */
202 struct FuseState {
203 FuseState() : version(0), cache_symlinks(false), has_dentry_expire(false) { }
204 unsigned version;
205 bool cache_symlinks;
206 bool has_dentry_expire;
207 };
208
209
210 /**
211 * Atomic increase of the open files counter. If we use a non-refcounted
212 * POSIX cache manager, check for open fd overflow. Return false if too many
213 * files are opened. Otherwise return true (success).
214 */
215 static inline bool IncAndCheckNoOpenFiles() {
216 const int64_t no_open_files = perf::Xadd(file_system_->no_open_files(), 1);
217 if (!check_fd_overflow_)
218 return true;
219 return no_open_files < (static_cast<int>(max_open_files_) - kNumReservedFd);
220 }
221
222 static inline double GetKcacheTimeout() {
223 if (!fuse_remounter_->IsCaching())
224 return 0.0;
225 return mount_point_->kcache_timeout_sec();
226 }
227
228
229 void GetReloadStatus(bool *drainout_mode, bool *maintenance_mode) {
230 *drainout_mode = fuse_remounter_->IsInDrainoutMode();
231 *maintenance_mode = fuse_remounter_->IsInMaintenanceMode();
232 }
233
234 #ifndef __TEST_CVMFS_MOCKFUSE // will be mocked in tests
235 // returns whether or not to start a watchdog
236 static bool ShouldStartWatchdog() {
237 assert(loader_exports_ != NULL);
238
239 if (loader_exports_->version < 2) {
240 return true; // spawn watchdog by default during reload
241 // Note: with library versions before 2.1.8 it might not
242 // create stack traces properly in all cases
243 }
244
245 if (loader_exports_->saved_states.size() == 0) {
246 // This is the initial loader run, not a reload
247 return !loader_exports_->disable_watchdog;
248 }
249
250 // This is a reload
251
252 if ((loader_exports_->version < 6) && !loader_exports_->disable_watchdog) {
253 // This is an older loader so need to start a watchdog
254 return true;
255 }
256
257 // Newer loader so the watchdog should have kept running through reload
258 return false;
259 }
260 #endif
261
262 std::string PrintInodeGeneration() {
263 return "init-catalog-revision: "
264 + StringifyInt(inode_generation_info_.initial_revision) + " "
265 + "current-catalog-revision: "
266 + StringifyInt(mount_point_->catalog_mgr()->GetRevision()) + " "
267 + "incarnation: " + StringifyInt(inode_generation_info_.incarnation)
268 + " " + "inode generation: "
269 + StringifyInt(inode_generation_info_.inode_generation) + "\n";
270 }
271
272
273 static bool CheckVoms(const fuse_ctx &fctx) {
274 if (!mount_point_->has_membership_req())
275 return true;
276 const string mreq = mount_point_->membership_req();
277 LogCvmfs(kLogCvmfs, kLogDebug,
278 "Got VOMS authz %s from filesystem "
279 "properties",
280 mreq.c_str());
281
282 if (fctx.uid == 0)
283 return true;
284
285 return mount_point_->authz_session_mgr()->IsMemberOf(fctx.pid, mreq);
286 }
287
288 static bool MayBeInPageCacheTracker(const catalog::DirectoryEntry &dirent) {
289 return dirent.IsRegular()
290 && (dirent.inode() < mount_point_->catalog_mgr()->GetRootInode());
291 }
292
293 static bool HasDifferentContent(const catalog::DirectoryEntry &dirent,
294 const shash::Any &hash,
295 const struct stat &info) {
296 if (hash == dirent.checksum())
297 return false;
298 // For chunked files, we don't want to load the full list of chunk hashes
299 // so we only check the last modified timestamp
300 if (dirent.IsChunkedFile() && (info.st_mtime == dirent.mtime()))
301 return false;
302 return true;
303 }
304
305 #ifndef __TEST_CVMFS_MOCKFUSE
306 /**
307 * When we lookup an inode (cvmfs_lookup(), cvmfs_opendir()), we usually provide
308 * the live inode, i.e. the one in the inode tracker. However, if the inode
309 * refers to an open file that has a different content then the one from the
310 * current catalogs, we will replace the live inode in the tracker by the one
311 * from the current generation.
312 *
313 * To still access the old inode, e.g. for fstat() on the open file, the stat
314 * structure connected to this inode is taken from the page cache tracker.
315 */
316 static bool FixupOpenInode(const PathString &path,
317 catalog::DirectoryEntry *dirent) {
318 if (!MayBeInPageCacheTracker(*dirent))
319 return false;
320
321 CVMFS_TEST_INJECT_BARRIER("_CVMFS_TEST_BARRIER_INODE_REPLACE");
322
323 const bool is_stale = mount_point_->page_cache_tracker()->IsStale(*dirent);
324
325 if (is_stale) {
326 // Overwrite dirent with inode from current generation
327 const bool found = mount_point_->catalog_mgr()->LookupPath(
328 path, catalog::kLookupDefault, dirent);
329 assert(found);
330 }
331
332 return is_stale;
333 }
334
335 static bool GetDirentForInode(const fuse_ino_t ino,
336 catalog::DirectoryEntry *dirent) {
337 // Lookup inode in cache
338 if (mount_point_->inode_cache()->Lookup(ino, dirent))
339 return true;
340
341 // Look in the catalogs in 2 steps: lookup inode->path, lookup path
342 static const catalog::DirectoryEntry
343 dirent_negative = catalog::DirectoryEntry(catalog::kDirentNegative);
344 // Reset directory entry. If the function returns false and dirent is no
345 // the kDirentNegative, it was an I/O error
346 *dirent = catalog::DirectoryEntry();
347
348 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
349
350 if (file_system_->IsNfsSource()) {
351 // NFS mode
352 PathString path;
353 const bool retval = file_system_->nfs_maps()->GetPath(ino, &path);
354 if (!retval) {
355 *dirent = dirent_negative;
356 return false;
357 }
358 if (catalog_mgr->LookupPath(path, catalog::kLookupDefault, dirent)) {
359 // Fix inodes
360 dirent->set_inode(ino);
361 mount_point_->inode_cache()->Insert(ino, *dirent);
362 return true;
363 }
364 return false; // Not found in catalog or catalog load error
365 }
366
367 // Non-NFS mode
368 PathString path;
369 if (ino == catalog_mgr->GetRootInode()) {
370 const bool retval = catalog_mgr->LookupPath(
371 PathString(), catalog::kLookupDefault, dirent);
372
373 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
374 "GetDirentForInode: Race condition? Not found dirent %s",
375 dirent->name().c_str())) {
376 return false;
377 }
378
379 dirent->set_inode(ino);
380 mount_point_->inode_cache()->Insert(ino, *dirent);
381 return true;
382 }
383
384 glue::InodeEx inode_ex(ino, glue::InodeEx::kUnknownType);
385 const bool retval = mount_point_->inode_tracker()->FindPath(&inode_ex, &path);
386 if (!retval) {
387 // This may be a retired inode whose stat information is only available
388 // in the page cache tracker because there is still an open file
389 LogCvmfs(kLogCvmfs, kLogDebug,
390 "GetDirentForInode inode lookup failure %" PRId64, ino);
391 *dirent = dirent_negative;
392 // Indicate that the inode was not found in the tracker rather than not
393 // found in the catalog
394 dirent->set_inode(ino);
395 return false;
396 }
397 if (catalog_mgr->LookupPath(path, catalog::kLookupDefault, dirent)) {
398 if (!inode_ex.IsCompatibleFileType(dirent->mode())) {
399 LogCvmfs(kLogCvmfs, kLogDebug,
400 "Warning: inode %" PRId64 " (%s) changed file type", ino,
401 path.c_str());
402 // TODO(jblomer): we detect this issue but let it continue unhandled.
403 // Fix me.
404 }
405
406 // Fix inodes
407 dirent->set_inode(ino);
408 mount_point_->inode_cache()->Insert(ino, *dirent);
409 return true;
410 }
411
412 // Can happen after reload of catalogs or on catalog load failure
413 LogCvmfs(kLogCvmfs, kLogDebug, "GetDirentForInode path lookup failure");
414 return false;
415 }
416
417
418 /**
419 * Returns 0 if the path does not exist
420 * 1 if the live inode is returned
421 * >1 the live inode, which is then stale and the inode in dirent
422 * comes from the catalog in the current generation
423 * (see FixupOpenInode)
424 */
425 static uint64_t GetDirentForPath(const PathString &path,
426 catalog::DirectoryEntry *dirent) {
427 uint64_t live_inode = 0;
428 if (!file_system_->IsNfsSource())
429 live_inode = mount_point_->inode_tracker()->FindInode(path);
430
431 LogCvmfs(kLogCvmfs, kLogDebug,
432 "GetDirentForPath: live inode for %s: %" PRIu64, path.c_str(),
433 live_inode);
434
435 const shash::Md5 md5path(path.GetChars(), path.GetLength());
436 if (mount_point_->md5path_cache()->Lookup(md5path, dirent)) {
437 if (dirent->GetSpecial() == catalog::kDirentNegative)
438 return false;
439 // We may have initially stored the entry with an old inode in the
440 // md5path cache and now should update it with the new one.
441 if (!file_system_->IsNfsSource() && (live_inode != 0))
442 dirent->set_inode(live_inode);
443 return 1;
444 }
445
446 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
447
448 // Lookup inode in catalog TODO: not twice md5 calculation
449 bool retval;
450 retval = catalog_mgr->LookupPath(path, catalog::kLookupDefault, dirent);
451 if (retval) {
452 if (file_system_->IsNfsSource()) {
453 dirent->set_inode(file_system_->nfs_maps()->GetInode(path));
454 } else if (live_inode != 0) {
455 dirent->set_inode(live_inode);
456 if (FixupOpenInode(path, dirent)) {
457 LogCvmfs(kLogCvmfs, kLogDebug,
458 "content of %s change, replacing inode %" PRIu64
459 " --> %" PRIu64,
460 path.c_str(), live_inode, dirent->inode());
461 return live_inode;
462 // Do not populate the md5path cache until the inode tracker is fixed
463 }
464 }
465 mount_point_->md5path_cache()->Insert(md5path, *dirent);
466 return 1;
467 }
468
469 LogCvmfs(kLogCvmfs, kLogDebug, "GetDirentForPath, no entry");
470 // Only insert ENOENT results into negative cache. Otherwise it was an
471 // error loading nested catalogs
472 if (dirent->GetSpecial() == catalog::kDirentNegative)
473 mount_point_->md5path_cache()->InsertNegative(md5path);
474 return 0;
475 }
476 #endif
477
478
479 static bool GetPathForInode(const fuse_ino_t ino, PathString *path) {
480 // Check the path cache first
481 if (mount_point_->path_cache()->Lookup(ino, path))
482 return true;
483
484 if (file_system_->IsNfsSource()) {
485 // NFS mode, just a lookup
486 LogCvmfs(kLogCvmfs, kLogDebug, "MISS %lu - lookup in NFS maps", ino);
487 if (file_system_->nfs_maps()->GetPath(ino, path)) {
488 mount_point_->path_cache()->Insert(ino, *path);
489 return true;
490 }
491 return false;
492 }
493
494 if (ino == mount_point_->catalog_mgr()->GetRootInode())
495 return true;
496
497 LogCvmfs(kLogCvmfs, kLogDebug, "MISS %lu - looking in inode tracker", ino);
498 glue::InodeEx inode_ex(ino, glue::InodeEx::kUnknownType);
499 const bool retval = mount_point_->inode_tracker()->FindPath(&inode_ex, path);
500
501 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
502 "GetPathForInode: Race condition? "
503 "Inode not found in inode tracker at path %s",
504 path->c_str())) {
505 return false;
506 }
507
508
509 mount_point_->path_cache()->Insert(ino, *path);
510 return true;
511 }
512
513 static void DoTraceInode(const int event,
514 fuse_ino_t ino,
515 const std::string &msg) {
516 PathString path;
517 const bool found = GetPathForInode(ino, &path);
518 if (!found) {
519 LogCvmfs(kLogCvmfs, kLogDebug,
520 "Tracing: Could not find path for inode %" PRIu64, uint64_t(ino));
521 mount_point_->tracer()->Trace(event, PathString("@UNKNOWN"), msg);
522 } else {
523 mount_point_->tracer()->Trace(event, path, msg);
524 }
525 }
526
527 static void inline TraceInode(const int event,
528 fuse_ino_t ino,
529 const std::string &msg) {
530 if (mount_point_->tracer()->IsActive())
531 DoTraceInode(event, ino, msg);
532 }
533
534 /**
535 * Find the inode number of a file name in a directory given by inode.
536 * This or getattr is called as kind of prerequisite to every operation.
537 * We do check catalog TTL here (and reload, if necessary).
538 */
539 static void cvmfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
540 const HighPrecisionTimer guard_timer(file_system_->hist_fs_lookup());
541
542 perf::Inc(file_system_->n_fs_lookup());
543 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
544 FuseInterruptCue ic(&req);
545 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
546 &ic);
547 fuse_remounter_->TryFinish();
548
549 fuse_remounter_->fence()->Enter();
550 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
551
552 const fuse_ino_t parent_fuse = parent;
553 parent = catalog_mgr->MangleInode(parent);
554 LogCvmfs(kLogCvmfs, kLogDebug,
555 "cvmfs_lookup in parent inode: %" PRIu64 " for name: %s",
556 uint64_t(parent), name);
557
558 PathString path;
559 PathString parent_path;
560 uint64_t live_inode = 0;
561 catalog::DirectoryEntry dirent;
562 struct fuse_entry_param result;
563
564 memset(&result, 0, sizeof(result));
565 const double timeout = GetKcacheTimeout();
566 result.attr_timeout = timeout;
567 result.entry_timeout = timeout;
568
569 // Special NFS lookups: . and ..
570 if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) {
571 if (GetDirentForInode(parent, &dirent)) {
572 if (strcmp(name, ".") == 0) {
573 goto lookup_reply_positive;
574 } else {
575 // Lookup for ".."
576 if (dirent.inode() == catalog_mgr->GetRootInode()) {
577 dirent.set_inode(1);
578 goto lookup_reply_positive;
579 }
580 if (!GetPathForInode(parent, &parent_path))
581 goto lookup_reply_negative;
582 if (GetDirentForPath(GetParentPath(parent_path), &dirent) > 0)
583 goto lookup_reply_positive;
584 }
585 }
586 // No entry for "." or no entry for ".."
587 if (dirent.GetSpecial() == catalog::kDirentNegative)
588 goto lookup_reply_negative;
589 else
590 goto lookup_reply_error;
591 assert(false);
592 }
593
594 if (!GetPathForInode(parent, &parent_path)) {
595 LogCvmfs(kLogCvmfs, kLogDebug, "no path for parent inode found");
596 goto lookup_reply_negative;
597 }
598
599 path.Assign(parent_path);
600 path.Append("/", 1);
601 path.Append(name, strlen(name));
602 live_inode = GetDirentForPath(path, &dirent);
603 if (live_inode == 0) {
604 if (dirent.GetSpecial() == catalog::kDirentNegative)
605 goto lookup_reply_negative;
606 else
607 goto lookup_reply_error;
608 }
609
610 lookup_reply_positive:
611 mount_point_->tracer()->Trace(Tracer::kEventLookup, path, "lookup()");
612 if (!file_system_->IsNfsSource()) {
613 if (live_inode > 1) {
614 // live inode is stale (open file), we replace it
615 assert(dirent.IsRegular());
616 assert(dirent.inode() != live_inode);
617
618 // The new inode is put in the tracker with refcounter == 0
619 const bool replaced = mount_point_->inode_tracker()->ReplaceInode(
620 live_inode, glue::InodeEx(dirent.inode(), dirent.mode()));
621 if (replaced)
622 perf::Inc(file_system_->n_fs_inode_replace());
623 }
624 mount_point_->inode_tracker()->VfsGet(
625 glue::InodeEx(dirent.inode(), dirent.mode()), path);
626 }
627 // We do _not_ track (and evict) positive replies; among other things, test
628 // 076 fails with the following line uncommented
629 //
630 // WARNING! ENABLING THIS BREAKS ANY TYPE OF MOUNTPOINT POINTING TO THIS INODE
631 //
632 // only safe if fuse_expire_entry is available
633 if (mount_point_->fuse_expire_entry()
634 || (mount_point_->cache_symlinks() && dirent.IsLink())) {
635 LogCvmfs(kLogCache, kLogDebug, "Dentry to evict: %s", name);
636 mount_point_->dentry_tracker()->Add(parent_fuse, name,
637 static_cast<uint64_t>(timeout));
638 }
639
640 fuse_remounter_->fence()->Leave();
641 result.ino = dirent.inode();
642 result.attr = dirent.GetStatStructure();
643 fuse_reply_entry(req, &result);
644 return;
645
646 lookup_reply_negative:
647 mount_point_->tracer()->Trace(Tracer::kEventLookup, path,
648 "lookup()-NOTFOUND");
649 // Will be a no-op if there is no fuse cache eviction
650 mount_point_->dentry_tracker()->Add(parent_fuse, name, uint64_t(timeout));
651 fuse_remounter_->fence()->Leave();
652 perf::Inc(file_system_->n_fs_lookup_negative());
653 result.ino = 0;
654 fuse_reply_entry(req, &result);
655 return;
656
657 lookup_reply_error:
658 mount_point_->tracer()->Trace(Tracer::kEventLookup, path,
659 "lookup()-NOTFOUND");
660 fuse_remounter_->fence()->Leave();
661
662 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
663 "EIO (01): lookup failed for %s", name);
664 perf::Inc(file_system_->n_eio_total());
665 perf::Inc(file_system_->n_eio_01());
666
667 fuse_reply_err(req, EIO);
668 }
669
670
671 /**
672 *
673 */
674 static void cvmfs_forget(fuse_req_t req,
675 fuse_ino_t ino,
676 #if CVMFS_USE_LIBFUSE == 2
677 unsigned long nlookup // NOLINT
678 #else
679 uint64_t nlookup
680 #endif
681 ) {
682 const HighPrecisionTimer guard_timer(file_system_->hist_fs_forget());
683
684 perf::Inc(file_system_->n_fs_forget());
685
686 // The libfuse high-level library does the same
687 if (ino == FUSE_ROOT_ID) {
688 fuse_reply_none(req);
689 return;
690 }
691
692 // Ensure that we don't need to call catalog_mgr()->MangleInode(ino)
693 assert(ino > mount_point_->catalog_mgr()->kInodeOffset);
694
695 LogCvmfs(kLogCvmfs, kLogDebug, "forget on inode %" PRIu64 " by %" PRIu64,
696 uint64_t(ino), nlookup);
697
698 if (!file_system_->IsNfsSource()) {
699 const bool removed = mount_point_->inode_tracker()->GetVfsPutRaii().VfsPut(
700 ino, nlookup);
701 if (removed)
702 mount_point_->page_cache_tracker()->GetEvictRaii().Evict(ino);
703 }
704
705 fuse_reply_none(req);
706 }
707
708
709 #if (FUSE_VERSION >= 29)
710 static void cvmfs_forget_multi(fuse_req_t req,
711 size_t count,
712 struct fuse_forget_data *forgets) {
713 const HighPrecisionTimer guard_timer(file_system_->hist_fs_forget_multi());
714
715 perf::Xadd(file_system_->n_fs_forget(), count);
716 if (file_system_->IsNfsSource()) {
717 fuse_reply_none(req);
718 return;
719 }
720
721 {
722 glue::InodeTracker::VfsPutRaii vfs_put_raii = mount_point_->inode_tracker()
723 ->GetVfsPutRaii();
724 glue::PageCacheTracker::EvictRaii
725 evict_raii = mount_point_->page_cache_tracker()->GetEvictRaii();
726 for (size_t i = 0; i < count; ++i) {
727 if (forgets[i].ino == FUSE_ROOT_ID) {
728 continue;
729 }
730
731 // Ensure that we don't need to call catalog_mgr()->MangleInode(ino)
732 assert(forgets[i].ino > mount_point_->catalog_mgr()->kInodeOffset);
733 LogCvmfs(kLogCvmfs, kLogDebug, "forget on inode %" PRIu64 " by %" PRIu64,
734 forgets[i].ino, forgets[i].nlookup);
735
736 const bool removed = vfs_put_raii.VfsPut(forgets[i].ino,
737 forgets[i].nlookup);
738 if (removed)
739 evict_raii.Evict(forgets[i].ino);
740 }
741 }
742
743 fuse_reply_none(req);
744 }
745 #endif // FUSE_VERSION >= 29
746
747
748 /**
749 * Looks into dirent to decide if this is an EIO negative reply or an
750 * ENOENT negative reply. We do not need to store the reply in the negative
751 * cache tracker because ReplyNegative is called on inode queries. Inodes,
752 * however, change anyway when a new catalog is applied.
753 */
754 static void ReplyNegative(const catalog::DirectoryEntry &dirent,
755 fuse_req_t req) {
756 if (dirent.GetSpecial() == catalog::kDirentNegative) {
757 fuse_reply_err(req, ENOENT);
758 } else {
759 // name() and symlink() return by value, so the strings have to be kept
760 // alive across the log call; c_str() on the temporaries would dangle.
761 const NameString name = dirent.name();
762 const LinkString link = dirent.symlink();
763
764 LogCvmfs(
765 kLogCvmfs, kLogDebug | kLogSyslogErr,
766 "EIO (02): CVMFS-specific metadata not found for name=%s symlink=%s",
767 name.c_str(), link.c_str());
768
769 perf::Inc(file_system_->n_eio_total());
770 perf::Inc(file_system_->n_eio_02());
771 fuse_reply_err(req, EIO);
772 }
773 }
774
775
776 /**
777 * Transform a cvmfs dirent into a struct stat.
778 */
779 static void cvmfs_getattr(fuse_req_t req, fuse_ino_t ino,
780 struct fuse_file_info *fi) {
781 const HighPrecisionTimer guard_timer(file_system_->hist_fs_getattr());
782
783 perf::Inc(file_system_->n_fs_stat());
784 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
785 FuseInterruptCue ic(&req);
786 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
787 &ic);
788 fuse_remounter_->TryFinish();
789
790 fuse_remounter_->fence()->Enter();
791 ino = mount_point_->catalog_mgr()->MangleInode(ino);
792 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_getattr (stat) for inode: %" PRIu64,
793 uint64_t(ino));
794
795 if (!CheckVoms(*fuse_ctx)) {
796 fuse_remounter_->fence()->Leave();
797 fuse_reply_err(req, EACCES);
798 return;
799 }
800 catalog::DirectoryEntry dirent;
801 const bool found = GetDirentForInode(ino, &dirent);
802 TraceInode(Tracer::kEventGetAttr, ino, "getattr()");
803 if ((!found && (dirent.inode() == ino)) || MayBeInPageCacheTracker(dirent)) {
804 // Serve retired inode from page cache tracker; even if we find it in the
805 // catalog, we replace the dirent by the page cache tracker version to
806 // not confuse open file handles
807 LogCvmfs(kLogCvmfs, kLogDebug,
808 "cvmfs_getattr %" PRIu64 " "
809 "served from page cache tracker",
810 ino);
811 shash::Any hash;
812 struct stat info;
813 const bool is_open = mount_point_->page_cache_tracker()->GetInfoIfOpen(
814 ino, &hash, &info);
815 if (is_open) {
816 fuse_remounter_->fence()->Leave();
817 if (found && HasDifferentContent(dirent, hash, info)) {
818 // We should from now on provide the new inode information instead
819 // of the stale one. To this end, we need to invalidate the dentry to
820 // trigger a fresh LOOKUP call
821 uint64_t parent_ino;
822 NameString name;
823 if (mount_point_->inode_tracker()->FindDentry(dirent.inode(),
824 &parent_ino, &name)) {
825 fuse_remounter_->InvalidateDentry(parent_ino, name);
826 }
827 perf::Inc(file_system_->n_fs_stat_stale());
828 }
829 fuse_reply_attr(req, &info, GetKcacheTimeout());
830 return;
831 }
832 }
833 fuse_remounter_->fence()->Leave();
834
835 if (!found) {
836 ReplyNegative(dirent, req);
837 return;
838 }
839
840 struct stat info = dirent.GetStatStructure();
841
842 // Partial replica in fail mode: mark excluded entries as unreadable so
843 // users get a clear indication rather than a confusing EIO at read time.
844 if (mount_point_->partial_replica_fail_mode()
845 && mount_point_->partial_inclusion_spec() != NULL) {
846 // Find the path for this inode to check against the inclusion spec.
847 glue::InodeEx inode_ex(ino, glue::InodeEx::kUnknownType);
848 PathString inode_path;
849 if (mount_point_->inode_tracker()->FindPath(&inode_ex, &inode_path)) {
850 const string path_str(inode_path.GetChars(), inode_path.GetLength());
851 if (mount_point_->partial_inclusion_spec()->IsExcluded(path_str)) {
852 // Present the entry with no permissions and a special uid/gid
853 // (65534 = traditional "nobody"/"nogroup").
854 info.st_mode &= ~static_cast<mode_t>(S_IRWXU | S_IRWXG | S_IRWXO);
855 info.st_uid = 65534;
856 info.st_gid = 65534;
857 }
858 }
859 }
860
861 fuse_reply_attr(req, &info, GetKcacheTimeout());
862 }
863
864
865 /**
866 * Reads a symlink from the catalog. Environment variables are expanded.
867 */
868 static void cvmfs_readlink(fuse_req_t req, fuse_ino_t ino) {
869 const HighPrecisionTimer guard_timer(file_system_->hist_fs_readlink());
870
871 perf::Inc(file_system_->n_fs_readlink());
872 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
873 FuseInterruptCue ic(&req);
874 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
875 &ic);
876
877 fuse_remounter_->fence()->Enter();
878 ino = mount_point_->catalog_mgr()->MangleInode(ino);
879 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_readlink on inode: %" PRIu64,
880 uint64_t(ino));
881
882 catalog::DirectoryEntry dirent;
883 const bool found = GetDirentForInode(ino, &dirent);
884 TraceInode(Tracer::kEventReadlink, ino, "readlink()");
885 fuse_remounter_->fence()->Leave();
886
887 if (!found) {
888 ReplyNegative(dirent, req);
889 return;
890 }
891
892 if (!dirent.IsLink()) {
893 fuse_reply_err(req, EINVAL);
894 return;
895 }
896
897 fuse_reply_readlink(req, dirent.symlink().c_str());
898 }
899
900
901 static void AddToDirListing(const fuse_req_t req, const char *name,
902 const struct stat *stat_info,
903 BigVector<char> *listing) {
904 LogCvmfs(kLogCvmfs, kLogDebug, "Add to listing: %s, inode %" PRIu64, name,
905 uint64_t(stat_info->st_ino));
906 size_t remaining_size = listing->capacity() - listing->size();
907 const size_t entry_size = fuse_add_direntry(req, NULL, 0, name, stat_info, 0);
908
909 while (entry_size > remaining_size) {
910 listing->DoubleCapacity();
911 remaining_size = listing->capacity() - listing->size();
912 }
913
914 char *buffer;
915 bool large_alloc;
916 listing->ShareBuffer(&buffer, &large_alloc);
917 fuse_add_direntry(req, buffer + listing->size(), remaining_size, name,
918 stat_info, listing->size() + entry_size);
919 listing->SetSize(listing->size() + entry_size);
920 }
921
922
923 /**
924 * Open a directory for listing.
925 */
926 static void cvmfs_opendir(fuse_req_t req, fuse_ino_t ino,
927 struct fuse_file_info *fi) {
928 const HighPrecisionTimer guard_timer(file_system_->hist_fs_opendir());
929
930 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
931 FuseInterruptCue ic(&req);
932 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
933 &ic);
934 fuse_remounter_->TryFinish();
935
936 fuse_remounter_->fence()->Enter();
937 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
938 ino = catalog_mgr->MangleInode(ino);
939 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64,
940 uint64_t(ino));
941 if (!CheckVoms(*fuse_ctx)) {
942 fuse_remounter_->fence()->Leave();
943 fuse_reply_err(req, EACCES);
944 return;
945 }
946
947 TraceInode(Tracer::kEventOpenDir, ino, "opendir()");
948 PathString path;
949 catalog::DirectoryEntry d;
950 bool found = GetPathForInode(ino, &path);
951 if (!found) {
952 fuse_remounter_->fence()->Leave();
953 fuse_reply_err(req, ENOENT);
954 return;
955 }
956 found = GetDirentForInode(ino, &d);
957
958 if (!found) {
959 fuse_remounter_->fence()->Leave();
960 ReplyNegative(d, req);
961 return;
962 }
963 if (!d.IsDirectory()) {
964 fuse_remounter_->fence()->Leave();
965 fuse_reply_err(req, ENOTDIR);
966 return;
967 }
968
969 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64 ", path %s",
970 uint64_t(ino), path.c_str());
971
972 // Build listing
973 BigVector<char> fuse_listing(512);
974
975 // Add current directory link
976 struct stat info;
977 info = d.GetStatStructure();
978 AddToDirListing(req, ".", &info, &fuse_listing);
979
980 // Add parent directory link
981 catalog::DirectoryEntry p;
982 if (d.inode() != catalog_mgr->GetRootInode()
983 && (GetDirentForPath(GetParentPath(path), &p) > 0)) {
984 info = p.GetStatStructure();
985 AddToDirListing(req, "..", &info, &fuse_listing);
986 }
987
988 // Add all names
989 catalog::StatEntryList listing_from_catalog;
990 const bool retval = catalog_mgr->ListingStat(path, &listing_from_catalog);
991
992 if (!retval) {
993 fuse_remounter_->fence()->Leave();
994 fuse_listing.Clear(); // Buffer is shared, empty manually
995
996 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
997 "EIO (03): failed to open directory at %s", path.c_str());
998 perf::Inc(file_system_->n_eio_total());
999 perf::Inc(file_system_->n_eio_03());
1000 fuse_reply_err(req, EIO);
1001 return;
1002 }
1003 for (unsigned i = 0; i < listing_from_catalog.size(); ++i) {
1004 // Fix inodes
1005 PathString entry_path;
1006 entry_path.Assign(path);
1007 entry_path.Append("/", 1);
1008 entry_path.Append(listing_from_catalog.AtPtr(i)->name.GetChars(),
1009 listing_from_catalog.AtPtr(i)->name.GetLength());
1010
1011 catalog::DirectoryEntry entry_dirent;
1012 if (!GetDirentForPath(entry_path, &entry_dirent)) {
1013 LogCvmfs(kLogCvmfs, kLogDebug, "listing entry %s vanished, skipping",
1014 entry_path.c_str());
1015 continue;
1016 }
1017
1018 struct stat fixed_info = listing_from_catalog.AtPtr(i)->info;
1019 fixed_info.st_ino = entry_dirent.inode();
1020 AddToDirListing(req, listing_from_catalog.AtPtr(i)->name.c_str(),
1021 &fixed_info, &fuse_listing);
1022 }
1023 fuse_remounter_->fence()->Leave();
1024
1025 DirectoryListing stream_listing;
1026 stream_listing.size = fuse_listing.size();
1027 stream_listing.capacity = fuse_listing.capacity();
1028 bool large_alloc;
1029 fuse_listing.ShareBuffer(&stream_listing.buffer, &large_alloc);
1030 if (large_alloc)
1031 stream_listing.capacity = 0;
1032
1033 // Save the directory listing and return a handle to the listing
1034 {
1035 const MutexLockGuard m(&lock_directory_handles_);
1036 LogCvmfs(kLogCvmfs, kLogDebug,
1037 "linking directory handle %lu to dir inode: %" PRIu64,
1038 next_directory_handle_, uint64_t(ino));
1039 (*directory_handles_)[next_directory_handle_] = stream_listing;
1040 fi->fh = next_directory_handle_;
1041 ++next_directory_handle_;
1042 }
1043 perf::Inc(file_system_->n_fs_dir_open());
1044 perf::Inc(file_system_->no_open_dirs());
1045
1046 #if (FUSE_VERSION >= 30)
1047 #ifdef CVMFS_ENABLE_FUSE3_CACHE_READDIR
1048 // This affects only reads on the same open directory handle (e.g. multiple
1049 // reads with rewinddir() between them). A new opendir on the same directory
1050 // will trigger readdir calls independently of this setting.
1051 fi->cache_readdir = 1;
1052 #endif
1053 #endif
1054 fuse_reply_open(req, fi);
1055 }
1056
1057
1058 /**
1059 * Release a directory.
1060 */
1061 static void cvmfs_releasedir(fuse_req_t req, fuse_ino_t ino,
1062 struct fuse_file_info *fi) {
1063 const HighPrecisionTimer guard_timer(file_system_->hist_fs_releasedir());
1064
1065 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1066 LogCvmfs(kLogCvmfs, kLogDebug,
1067 "cvmfs_releasedir on inode %" PRIu64 ", handle %lu", uint64_t(ino),
1068 fi->fh);
1069
1070 int reply = 0;
1071
1072 {
1073 const MutexLockGuard m(&lock_directory_handles_);
1074 const DirectoryHandles::iterator iter_handle = directory_handles_->find(
1075 fi->fh);
1076 if (iter_handle != directory_handles_->end()) {
1077 if (iter_handle->second.capacity == 0)
1078 smunmap(iter_handle->second.buffer);
1079 else
1080 free(iter_handle->second.buffer);
1081 directory_handles_->erase(iter_handle);
1082 perf::Dec(file_system_->no_open_dirs());
1083 } else {
1084 reply = EINVAL;
1085 }
1086 }
1087
1088 fuse_reply_err(req, reply);
1089 }
1090
1091
1092 /**
1093 * Very large directory listings have to be sent in slices.
1094 */
1095 static void ReplyBufferSlice(const fuse_req_t req, const char *buffer,
1096 const size_t buffer_size, const off_t offset,
1097 const size_t max_size) {
1098 if (offset < static_cast<int>(buffer_size)) {
1099 fuse_reply_buf(
1100 req, buffer + offset,
1101 std::min(static_cast<size_t>(buffer_size - offset), max_size));
1102 } else {
1103 fuse_reply_buf(req, NULL, 0);
1104 }
1105 }
1106
1107
1108 /**
1109 * Read the directory listing.
1110 */
1111 static void cvmfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
1112 off_t off, struct fuse_file_info *fi) {
1113 const HighPrecisionTimer guard_timer(file_system_->hist_fs_readdir());
1114
1115 LogCvmfs(kLogCvmfs, kLogDebug,
1116 "cvmfs_readdir on inode %" PRIu64
1117 " reading %lu bytes from offset %ld",
1118 static_cast<uint64_t>(mount_point_->catalog_mgr()->MangleInode(ino)),
1119 size, off);
1120
1121 DirectoryListing listing;
1122
1123 const MutexLockGuard m(&lock_directory_handles_);
1124 const DirectoryHandles::const_iterator iter_handle = directory_handles_->find(
1125 fi->fh);
1126 if (iter_handle != directory_handles_->end()) {
1127 listing = iter_handle->second;
1128
1129 ReplyBufferSlice(req, listing.buffer, listing.size, off, size);
1130 return;
1131 }
1132
1133 fuse_reply_err(req, EINVAL);
1134 }
1135
1136 static void FillOpenFlags(const glue::PageCacheTracker::OpenDirectives od,
1137 struct fuse_file_info *fi) {
1138 assert(!TestBit(glue::PageCacheTracker::kBitDirectIo, fi->fh));
1139 fi->keep_cache = od.keep_cache;
1140 fi->direct_io = od.direct_io;
1141 if (fi->direct_io)
1142 SetBit(glue::PageCacheTracker::kBitDirectIo, &fi->fh);
1143 }
1144
1145
1146 #ifdef __APPLE__
1147 // On macOS, xattr on a symlink opens and closes the file (with O_SYMLINK)
1148 // around the actual getxattr call. In order to not run into an I/O error
1149 // we use a special file handle for symlinks, from which one cannot read.
1150 static const uint64_t kFileHandleIgnore = static_cast<uint64_t>(2) << 60;
1151 #endif
1152
1153 /**
1154 * Open a file from cache. If necessary, file is downloaded first.
1155 *
1156 * \return Read-only file descriptor in fi->fh or kChunkedFileHandle for
1157 * chunked files
1158 */
1159 static void cvmfs_open(fuse_req_t req, fuse_ino_t ino,
1160 struct fuse_file_info *fi) {
1161 const HighPrecisionTimer guard_timer(file_system_->hist_fs_open());
1162
1163 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1164 FuseInterruptCue ic(&req);
1165 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
1166 &ic);
1167 fuse_remounter_->fence()->Enter();
1168 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1169 ino = catalog_mgr->MangleInode(ino);
1170 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_open on inode: %" PRIu64,
1171 uint64_t(ino));
1172
1173 int fd = -1;
1174 catalog::DirectoryEntry dirent;
1175 PathString path;
1176
1177 bool found = GetPathForInode(ino, &path);
1178 if (!found) {
1179 fuse_remounter_->fence()->Leave();
1180 fuse_reply_err(req, ENOENT);
1181 return;
1182 }
1183 found = GetDirentForInode(ino, &dirent);
1184 if (!found) {
1185 fuse_remounter_->fence()->Leave();
1186 ReplyNegative(dirent, req);
1187 return;
1188 }
1189
1190 if (!CheckVoms(*fuse_ctx)) {
1191 fuse_remounter_->fence()->Leave();
1192 fuse_reply_err(req, EACCES);
1193 return;
1194 }
1195
1196 mount_point_->tracer()->Trace(Tracer::kEventOpen, path, "open()");
1197 // Don't check. Either done by the OS or one wants to purposefully work
1198 // around wrong open flags
1199 // if ((fi->flags & 3) != O_RDONLY) {
1200 // fuse_reply_err(req, EROFS);
1201 // return;
1202 // }
1203 #ifdef __APPLE__
1204 if ((fi->flags & O_SHLOCK) || (fi->flags & O_EXLOCK)) {
1205 fuse_remounter_->fence()->Leave();
1206 fuse_reply_err(req, EOPNOTSUPP);
1207 return;
1208 }
1209 if (fi->flags & O_SYMLINK) {
1210 fuse_remounter_->fence()->Leave();
1211 fi->fh = kFileHandleIgnore;
1212 fuse_reply_open(req, fi);
1213 return;
1214 }
1215 #endif
1216 if (fi->flags & O_EXCL) {
1217 fuse_remounter_->fence()->Leave();
1218 fuse_reply_err(req, EEXIST);
1219 return;
1220 }
1221
1222 perf::Inc(file_system_->n_fs_open()); // Count actual open / fetch operations
1223
1224 if (dirent.IsBundleTrigger() and (mount_point_->bundle_mgr() != nullptr)) {
1225 // Hand the trigger over to the long-lived prefetcher; spec loading and
1226 // dependency downloads happen on its background threads, so the open()
1227 // does not wait for them (and does not hold the remount fence hostage).
1228 mount_point_->bundle_mgr()->ScheduleTrigger(path);
1229 }
1230
1231 glue::PageCacheTracker::OpenDirectives open_directives;
1232 if (!dirent.IsChunkedFile()) {
1233 if (dirent.IsDirectIo()) {
1234 open_directives = mount_point_->page_cache_tracker()->OpenDirect();
1235 } else {
1236 open_directives = mount_point_->page_cache_tracker()->Open(
1237 ino, dirent.checksum(), dirent.GetStatStructure());
1238 }
1239 fuse_remounter_->fence()->Leave();
1240 } else {
1241 LogCvmfs(kLogCvmfs, kLogDebug,
1242 "chunked file %s opened (download delayed to read() call)",
1243 path.c_str());
1244
1245 if (!IncAndCheckNoOpenFiles()) {
1246 perf::Dec(file_system_->no_open_files());
1247 fuse_remounter_->fence()->Leave();
1248 LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
1249 fuse_reply_err(req, EMFILE);
1250 perf::Inc(file_system_->n_emfile());
1251 return;
1252 }
1253
1254 // Figure out unique inode from annotated catalog
1255 // TODO(jblomer): we only need to lookup if the inode is not from the
1256 // current generation
1257 catalog::DirectoryEntry dirent_origin;
1258 if (!catalog_mgr->LookupPath(path, catalog::kLookupDefault,
1259 &dirent_origin)) {
1260 fuse_remounter_->fence()->Leave();
1261 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1262 "chunked file %s vanished unexpectedly", path.c_str());
1263 fuse_reply_err(req, ENOENT);
1264 return;
1265 }
1266 const uint64_t unique_inode = dirent_origin.inode();
1267
1268 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1269 chunk_tables->Lock();
1270 if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
1271 chunk_tables->Unlock();
1272
1273 // Retrieve File chunks from the catalog
1274 UniquePtr<FileChunkList> chunks(new FileChunkList());
1275 if (!catalog_mgr->ListFileChunks(path, dirent.hash_algorithm(),
1276 chunks.weak_ref())
1277 || chunks->IsEmpty()) {
1278 fuse_remounter_->fence()->Leave();
1279 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1280 "EIO (04): failed to open file %s. "
1281 "It is marked as 'chunked', but no chunks found.",
1282 path.c_str());
1283 perf::Inc(file_system_->n_eio_total());
1284 perf::Inc(file_system_->n_eio_04());
1285 fuse_reply_err(req, EIO);
1286 return;
1287 }
1288
1289 chunk_tables->Lock();
1290 // Check again to avoid race
1291 if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
1292 chunk_tables->inode2chunks.Insert(
1293 unique_inode, FileChunkReflist(chunks.Release(), path,
1294 dirent.compression_algorithm(),
1295 dirent.IsExternalFile()));
1296 chunk_tables->inode2references.Insert(unique_inode, 1);
1297 } else {
1298 uint32_t refctr;
1299 const bool retval = chunk_tables->inode2references.Lookup(unique_inode,
1300 &refctr);
1301 assert(retval);
1302 chunk_tables->inode2references.Insert(unique_inode, refctr + 1);
1303 }
1304 } else {
1305 uint32_t refctr;
1306 const bool retval = chunk_tables->inode2references.Lookup(unique_inode,
1307 &refctr);
1308 assert(retval);
1309 chunk_tables->inode2references.Insert(unique_inode, refctr + 1);
1310 }
1311
1312 // Update the chunk handle list
1313 LogCvmfs(kLogCvmfs, kLogDebug,
1314 "linking chunk handle %lu to unique inode: %" PRIu64,
1315 chunk_tables->next_handle, uint64_t(unique_inode));
1316 chunk_tables->handle2fd.Insert(chunk_tables->next_handle, ChunkFd());
1317 chunk_tables->handle2uniqino.Insert(chunk_tables->next_handle,
1318 unique_inode);
1319
1320 // Generate artificial content hash as hash over chunk hashes
1321 // TODO(jblomer): we may want to cache the result in the chunk tables
1322 FileChunkReflist chunk_reflist;
1323 const bool retval = chunk_tables->inode2chunks.Lookup(unique_inode,
1324 &chunk_reflist);
1325 assert(retval);
1326
1327 // The following block used to be outside the remount fence.
1328 // Now that the issue is fixed, we must not use the barrier anymore
1329 // because the remount will then never take place in test 708.
1330 // CVMFS_TEST_INJECT_BARRIER("_CVMFS_TEST_BARRIER_OPEN_CHUNKED");
1331 if (dirent.IsDirectIo()) {
1332 open_directives = mount_point_->page_cache_tracker()->OpenDirect();
1333 } else {
1334 open_directives = mount_point_->page_cache_tracker()->Open(
1335 ino, chunk_reflist.HashChunkList(), dirent.GetStatStructure());
1336 }
1337 FillOpenFlags(open_directives, fi);
1338
1339 fuse_remounter_->fence()->Leave();
1340
1341 fi->fh = chunk_tables->next_handle;
1342 fi->fh = static_cast<uint64_t>(-static_cast<int64_t>(fi->fh));
1343 ++chunk_tables->next_handle;
1344 chunk_tables->Unlock();
1345
1346 fuse_reply_open(req, fi);
1347 return;
1348 }
1349
1350 Fetcher *this_fetcher = dirent.IsExternalFile()
1351 ? mount_point_->external_fetcher()
1352 : mount_point_->fetcher();
1353 CacheManager::Label label;
1354 label.path = path.ToString();
1355 label.size = dirent.size();
1356 label.zip_algorithm = dirent.compression_algorithm();
1357 if (mount_point_->catalog_mgr()->volatile_flag())
1358 label.flags |= CacheManager::kLabelVolatile;
1359 if (dirent.IsExternalFile())
1360 label.flags |= CacheManager::kLabelExternal;
1361 fd = this_fetcher->Fetch(
1362 CacheManager::LabeledObject(dirent.checksum(), label));
1363
1364 if (fd >= 0) {
1365 if (IncAndCheckNoOpenFiles()) {
1366 LogCvmfs(kLogCvmfs, kLogDebug, "file %s opened (fd %d)", path.c_str(),
1367 fd);
1368 fi->fh = fd;
1369 FillOpenFlags(open_directives, fi);
1370 #ifdef FUSE_CAP_PASSTHROUGH
1371 if (loader_exports_->fuse_passthrough) {
1372 if(!dirent.IsChunkedFile()) {
1373 /* "Currently there should be only one backing id per node / backing file."
1374 * So says libfuse documentation on fuse_passthrough_open().
1375 * So we reuse and refcount backing id based on inode.
1376 * Passthrough can be used with libfuse methods open, opendir, create,
1377 * but since CVMFS is read-only and has synthesizes its directories,
1378 * we only need to handle it in `open`. */
1379 int backing_id;
1380 pthread_mutex_lock(&fuse_passthru_tracker_lock);
1381 auto iter = fuse_passthru_tracker->find(ino);
1382 if (iter == fuse_passthru_tracker->end()) {
1383 auto pair_with_iterator = fuse_passthru_tracker->emplace(ino, fuse_passthru_ctx_t());
1384 assert(pair_with_iterator.second == true);
1385 iter = pair_with_iterator.first;
1386 fuse_passthru_ctx_t &entry = iter->second;
1387
1388 backing_id = fuse_passthrough_open(req, fd);
1389 assert(backing_id != 0);
1390 entry.backing_id = backing_id;
1391 entry.refcount++;
1392 } else {
1393 fuse_passthru_ctx_t &entry = iter->second;
1394 assert(entry.refcount > 0);
1395 backing_id = entry.backing_id;
1396 entry.refcount++;
1397 }
1398 pthread_mutex_unlock(&fuse_passthru_tracker_lock);
1399
1400 fi->backing_id = backing_id;
1401
1402 /* according to libfuse example/passthrough_hp.cc:
1403 * "open in passthrough mode must drop old page cache" */
1404 fi->keep_cache = false;
1405 }
1406 }
1407 #endif
1408 fuse_reply_open(req, fi);
1409 return;
1410 } else {
1411 if (file_system_->cache_mgr()->Close(fd) == 0)
1412 perf::Dec(file_system_->no_open_files());
1413 LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
1414 // not returning an fd, so close the page cache tracker entry if required
1415 if (!dirent.IsDirectIo() && !open_directives.direct_io) {
1416 mount_point_->page_cache_tracker()->Close(ino);
1417 }
1418 fuse_reply_err(req, EMFILE);
1419 perf::Inc(file_system_->n_emfile());
1420 return;
1421 }
1422 assert(false);
1423 }
1424
1425 // fd < 0
1426 // the download has failed. Close the page cache tracker entry if required
1427 if (!dirent.IsDirectIo() && !open_directives.direct_io) {
1428 mount_point_->page_cache_tracker()->Close(ino);
1429 }
1430
1431 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1432 "failed to open inode: %" PRIu64 ", CAS key %s, error code %d",
1433 uint64_t(ino), dirent.checksum().ToString().c_str(), errno);
1434 if (errno == EMFILE) {
1435 LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
1436 fuse_reply_err(req, EMFILE);
1437 perf::Inc(file_system_->n_emfile());
1438 return;
1439 }
1440
1441 mount_point_->backoff_throttle()->Throttle();
1442
1443 mount_point_->file_system()->io_error_info()->AddIoError();
1444 if (EIO == errno || EIO == -fd) {
1445 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1446 "EIO (06): Failed to open file %s", path.c_str());
1447 perf::Inc(file_system_->n_eio_total());
1448 perf::Inc(file_system_->n_eio_06());
1449 }
1450
1451 fuse_reply_err(req, -fd);
1452 }
1453
1454
1455 /**
1456 * Redirected to pread into cache.
1457 */
1458 static void cvmfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
1459 struct fuse_file_info *fi) {
1460 const HighPrecisionTimer guard_timer(file_system_->hist_fs_read());
1461
1462 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1463 FuseInterruptCue ic(&req);
1464 const ClientCtxGuard ctxgd(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
1465
1466 LogCvmfs(kLogCvmfs, kLogDebug,
1467 "cvmfs_read inode: %" PRIu64 " reading %lu bytes from offset %ld "
1468 "fd %lu",
1469 uint64_t(mount_point_->catalog_mgr()->MangleInode(ino)), size, off,
1470 fi->fh);
1471 perf::Inc(file_system_->n_fs_read());
1472
1473 #ifdef __APPLE__
1474 if (fi->fh == kFileHandleIgnore) {
1475 fuse_reply_err(req, EBADF);
1476 return;
1477 }
1478 #endif
1479
1480 // Get data chunk (<=128k guaranteed by Fuse)
1481 char *data = static_cast<char *>(alloca(size));
1482 unsigned int overall_bytes_fetched = 0;
1483
1484 const int64_t fd = static_cast<int64_t>(fi->fh);
1485 uint64_t abs_fd = (fd < 0) ? -fd : fd;
1486 ClearBit(glue::PageCacheTracker::kBitDirectIo, &abs_fd);
1487
1488 // Do we have a a chunked file?
1489 if (fd < 0) {
1490 const uint64_t chunk_handle = abs_fd;
1491 uint64_t unique_inode;
1492 ChunkFd chunk_fd;
1493 FileChunkReflist chunks;
1494 bool retval;
1495
1496 // Fetch unique inode, chunk list and file descriptor
1497 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1498 chunk_tables->Lock();
1499 retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1500 if (!retval) {
1501 LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1502 unique_inode = ino;
1503 }
1504 retval = chunk_tables->inode2chunks.Lookup(unique_inode, &chunks);
1505 assert(retval);
1506 chunk_tables->Unlock();
1507
1508 unsigned chunk_idx = chunks.FindChunkIdx(off);
1509
1510 // Lock chunk handle
1511 pthread_mutex_t *handle_lock = chunk_tables->Handle2Lock(chunk_handle);
1512 const MutexLockGuard m(handle_lock);
1513 chunk_tables->Lock();
1514 retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1515 assert(retval);
1516 chunk_tables->Unlock();
1517
1518 // Fetch all needed chunks and read the requested data
1519 off_t offset_in_chunk = off - chunks.list->AtPtr(chunk_idx)->offset();
1520 do {
1521 // Open file descriptor to chunk
1522 if ((chunk_fd.fd == -1) || (chunk_fd.chunk_idx != chunk_idx)) {
1523 if (chunk_fd.fd != -1)
1524 file_system_->cache_mgr()->Close(chunk_fd.fd);
1525 Fetcher *this_fetcher = chunks.external_data
1526 ? mount_point_->external_fetcher()
1527 : mount_point_->fetcher();
1528 CacheManager::Label label;
1529 label.path = chunks.path.ToString();
1530 label.size = chunks.list->AtPtr(chunk_idx)->size();
1531 label.zip_algorithm = chunks.compression_alg;
1532 label.flags |= CacheManager::kLabelChunked;
1533 if (mount_point_->catalog_mgr()->volatile_flag())
1534 label.flags |= CacheManager::kLabelVolatile;
1535 if (chunks.external_data) {
1536 label.flags |= CacheManager::kLabelExternal;
1537 label.range_offset = chunks.list->AtPtr(chunk_idx)->offset();
1538 }
1539 chunk_fd.fd = this_fetcher->Fetch(CacheManager::LabeledObject(
1540 chunks.list->AtPtr(chunk_idx)->content_hash(), label));
1541 if (chunk_fd.fd < 0) {
1542 chunk_fd.fd = -1;
1543 chunk_tables->Lock();
1544 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1545 chunk_tables->Unlock();
1546
1547 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1548 "EIO (05): Failed to fetch chunk %d from file %s", chunk_idx,
1549 chunks.path.ToString().c_str());
1550 perf::Inc(file_system_->n_eio_total());
1551 perf::Inc(file_system_->n_eio_05());
1552 fuse_reply_err(req, EIO);
1553 return;
1554 }
1555 chunk_fd.chunk_idx = chunk_idx;
1556 }
1557
1558 LogCvmfs(kLogCvmfs, kLogDebug, "reading from chunk fd %d", chunk_fd.fd);
1559 // Read data from chunk
1560 const size_t bytes_to_read = size - overall_bytes_fetched;
1561 const size_t remaining_bytes_in_chunk = chunks.list->AtPtr(chunk_idx)
1562 ->size()
1563 - offset_in_chunk;
1564 const size_t bytes_to_read_in_chunk = std::min(bytes_to_read,
1565 remaining_bytes_in_chunk);
1566 const int64_t bytes_fetched = file_system_->cache_mgr()->Pread(
1567 chunk_fd.fd,
1568 data + overall_bytes_fetched,
1569 bytes_to_read_in_chunk,
1570 offset_in_chunk);
1571
1572 if (bytes_fetched < 0) {
1573 LogCvmfs(kLogCvmfs, kLogSyslogErr, "read err no %" PRId64 " (%s)",
1574 bytes_fetched, chunks.path.ToString().c_str());
1575 chunk_tables->Lock();
1576 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1577 chunk_tables->Unlock();
1578 if (EIO == errno || EIO == -bytes_fetched) {
1579 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1580 "EIO (07): Failed to read chunk %d from file %s", chunk_idx,
1581 chunks.path.ToString().c_str());
1582 perf::Inc(file_system_->n_eio_total());
1583 perf::Inc(file_system_->n_eio_07());
1584 }
1585 fuse_reply_err(req, -bytes_fetched);
1586 return;
1587 }
1588 overall_bytes_fetched += bytes_fetched;
1589
1590 // Proceed to the next chunk to keep on reading data
1591 ++chunk_idx;
1592 offset_in_chunk = 0;
1593 } while ((overall_bytes_fetched < size)
1594 && (chunk_idx < chunks.list->size()));
1595
1596 // Update chunk file descriptor
1597 chunk_tables->Lock();
1598 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1599 chunk_tables->Unlock();
1600 LogCvmfs(kLogCvmfs, kLogDebug, "released chunk file descriptor %d",
1601 chunk_fd.fd);
1602 } else {
1603 const int64_t nbytes = file_system_->cache_mgr()->Pread(abs_fd, data, size,
1604 off);
1605 if (nbytes < 0) {
1606 if (EIO == errno || EIO == -nbytes) {
1607 PathString path;
1608 const bool found = GetPathForInode(ino, &path);
1609 if (found) {
1610 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1611 "EIO (08): Failed to read file %s", path.ToString().c_str());
1612 } else {
1613 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1614 "EIO (08): Failed to read from %s - <unknown inode>",
1615 path.ToString().c_str());
1616 }
1617 perf::Inc(file_system_->n_eio_total());
1618 perf::Inc(file_system_->n_eio_08());
1619 }
1620 fuse_reply_err(req, -nbytes);
1621 return;
1622 }
1623 overall_bytes_fetched = nbytes;
1624 }
1625
1626 // Push it to user
1627 fuse_reply_buf(req, data, overall_bytes_fetched);
1628 LogCvmfs(kLogCvmfs, kLogDebug, "pushed %d bytes to user",
1629 overall_bytes_fetched);
1630 }
1631
1632
1633 /**
1634 * File close operation, redirected into cache.
1635 */
1636 static void cvmfs_release(fuse_req_t req, fuse_ino_t ino,
1637 struct fuse_file_info *fi) {
1638 const HighPrecisionTimer guard_timer(file_system_->hist_fs_release());
1639
1640 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1641 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_release on inode: %" PRIu64,
1642 uint64_t(ino));
1643
1644 #ifdef __APPLE__
1645 if (fi->fh == kFileHandleIgnore) {
1646 fuse_reply_err(req, 0);
1647 return;
1648 }
1649 #endif
1650
1651 const int64_t fd = static_cast<int64_t>(fi->fh);
1652 uint64_t abs_fd = (fd < 0) ? -fd : fd;
1653 if (!TestBit(glue::PageCacheTracker::kBitDirectIo, abs_fd)) {
1654 mount_point_->page_cache_tracker()->Close(ino);
1655 }
1656 ClearBit(glue::PageCacheTracker::kBitDirectIo, &abs_fd);
1657
1658 // do we have a chunked file?
1659 if (fd < 0) {
1660 const uint64_t chunk_handle = abs_fd;
1661 LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk handle %" PRIu64,
1662 chunk_handle);
1663 uint64_t unique_inode;
1664 ChunkFd chunk_fd;
1665 const FileChunkReflist chunks;
1666 uint32_t refctr;
1667 bool retval;
1668
1669 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1670 chunk_tables->Lock();
1671 retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1672 if (!retval) {
1673 LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1674 unique_inode = ino;
1675 } else {
1676 chunk_tables->handle2uniqino.Erase(chunk_handle);
1677 }
1678 retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1679 assert(retval);
1680 chunk_tables->handle2fd.Erase(chunk_handle);
1681
1682 retval = chunk_tables->inode2references.Lookup(unique_inode, &refctr);
1683 assert(retval);
1684 refctr--;
1685 if (refctr == 0) {
1686 LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk list for inode %" PRIu64,
1687 uint64_t(unique_inode));
1688 FileChunkReflist to_delete;
1689 retval = chunk_tables->inode2chunks.Lookup(unique_inode, &to_delete);
1690 assert(retval);
1691 chunk_tables->inode2references.Erase(unique_inode);
1692 chunk_tables->inode2chunks.Erase(unique_inode);
1693 delete to_delete.list;
1694 } else {
1695 chunk_tables->inode2references.Insert(unique_inode, refctr);
1696 }
1697 chunk_tables->Unlock();
1698
1699 if (chunk_fd.fd != -1)
1700 file_system_->cache_mgr()->Close(chunk_fd.fd);
1701 perf::Dec(file_system_->no_open_files());
1702 } else {
1703 if (file_system_->cache_mgr()->Close(abs_fd) == 0) {
1704 perf::Dec(file_system_->no_open_files());
1705 }
1706 #ifdef FUSE_CAP_PASSTHROUGH
1707 if (loader_exports_->fuse_passthrough) {
1708
1709 if (fi->backing_id != 0) {
1710 int ret;
1711 pthread_mutex_lock(&fuse_passthru_tracker_lock);
1712 auto iter = fuse_passthru_tracker->find(ino);
1713 assert(iter != fuse_passthru_tracker->end());
1714 fuse_passthru_ctx_t &entry = iter->second;
1715 assert(entry.refcount > 0);
1716 assert(entry.backing_id == fi->backing_id);
1717 entry.refcount--;
1718 if (entry.refcount == 0) {
1719 ret = fuse_passthrough_close(req, fi->backing_id);
1720 if (ret < 0) {
1721 LogCvmfs(kLogCvmfs, kLogDebug, "fuse_passthrough_close(fd=%ld) failed: %d", fd, ret);
1722 assert(false);
1723 }
1724 fuse_passthru_tracker->erase(iter);
1725 }
1726 pthread_mutex_unlock(&fuse_passthru_tracker_lock);
1727 }
1728 }
1729 #endif
1730 }
1731 fuse_reply_err(req, 0);
1732 }
1733
1734 /**
1735 * Returns information about a mounted filesystem. In this case it returns
1736 * information about the local cache occupancy of cvmfs.
1737 *
1738 * Note: If the elements of the struct statvfs *info are set to 0, it will cause
1739 * it to be ignored in commandline tool "df".
1740 */
1741 static void cvmfs_statfs(fuse_req_t req, fuse_ino_t ino) {
1742 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1743 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_statfs on inode: %" PRIu64,
1744 uint64_t(ino));
1745
1746 TraceInode(Tracer::kEventStatFs, ino, "statfs()");
1747
1748 perf::Inc(file_system_->n_fs_statfs());
1749
1750 // Unmanaged cache (no lock needed - statfs is never modified)
1751 if (!file_system_->cache_mgr()->quota_mgr()->HasCapability(
1752 QuotaManager::kCapIntrospectSize)) {
1753 LogCvmfs(kLogCvmfs, kLogDebug, "QuotaManager does not support statfs");
1754 fuse_reply_statfs(req, (mount_point_->statfs_cache()->info()));
1755 return;
1756 }
1757
1758 const MutexLockGuard m(mount_point_->statfs_cache()->lock());
1759
1760 const uint64_t deadline = *mount_point_->statfs_cache()->expiry_deadline();
1761 struct statvfs *info = mount_point_->statfs_cache()->info();
1762
1763 // cached version still valid
1764 if (platform_monotonic_time() < deadline) {
1765 perf::Inc(file_system_->n_fs_statfs_cached());
1766 fuse_reply_statfs(req, info);
1767 return;
1768 }
1769
1770 uint64_t available = 0;
1771 const uint64_t size = file_system_->cache_mgr()->quota_mgr()->GetSize();
1772 const uint64_t
1773 capacity = file_system_->cache_mgr()->quota_mgr()->GetCapacity();
1774 // Fuse/OS X doesn't like values < 512
1775 info->f_bsize = info->f_frsize = 512;
1776
1777 if (capacity == (uint64_t)(-1)) {
1778 // Unknown capacity, set capacity = size
1779 info->f_blocks = size / info->f_bsize;
1780 } else {
1781 // Take values from LRU module
1782 info->f_blocks = capacity / info->f_bsize;
1783 available = capacity - size;
1784 }
1785
1786 info->f_bfree = info->f_bavail = available / info->f_bsize;
1787
1788 // Inodes / entries
1789 fuse_remounter_->fence()->Enter();
1790 const uint64_t all_inodes = mount_point_->catalog_mgr()->all_inodes();
1791 const uint64_t loaded_inode = mount_point_->catalog_mgr()->loaded_inodes();
1792 info->f_files = all_inodes;
1793 info->f_ffree = info->f_favail = all_inodes - loaded_inode;
1794 fuse_remounter_->fence()->Leave();
1795
1796 *mount_point_->statfs_cache()
1797 ->expiry_deadline() = platform_monotonic_time()
1798 + mount_point_->statfs_cache()->cache_timeout();
1799
1800 fuse_reply_statfs(req, info);
1801 }
1802
1803 #ifdef __APPLE__
1804 static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1805 size_t size, uint32_t position)
1806 #else
1807 static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1808 size_t size)
1809 #endif
1810 {
1811 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1812 FuseInterruptCue ic(&req);
1813 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
1814 &ic);
1815
1816 fuse_remounter_->fence()->Enter();
1817 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1818 ino = catalog_mgr->MangleInode(ino);
1819 LogCvmfs(kLogCvmfs, kLogDebug,
1820 "cvmfs_getxattr on inode: %" PRIu64 " for xattr: %s", uint64_t(ino),
1821 name);
1822 if (!CheckVoms(*fuse_ctx)) {
1823 fuse_remounter_->fence()->Leave();
1824 fuse_reply_err(req, EACCES);
1825 return;
1826 }
1827 TraceInode(Tracer::kEventGetXAttr, ino, "getxattr()");
1828
1829 vector<string> tokens_mode_machine = SplitString(name, '~');
1830 vector<string> tokens_mode_human = SplitString(name, '@');
1831
1832 int32_t attr_req_page = 0;
1833 MagicXattrMode xattr_mode = kXattrMachineMode;
1834 string attr;
1835
1836 bool attr_req_is_valid = false;
1837 const sanitizer::PositiveIntegerSanitizer page_num_sanitizer;
1838
1839 if (tokens_mode_human.size() > 1) {
1840 const std::string token = tokens_mode_human[tokens_mode_human.size() - 1];
1841 if (token == "?") {
1842 attr_req_is_valid = true;
1843 attr_req_page = -1;
1844 } else {
1845 if (page_num_sanitizer.IsValid(token)) {
1846 attr_req_is_valid = true;
1847 attr_req_page = static_cast<int32_t>(String2Uint64(token));
1848 }
1849 }
1850 xattr_mode = kXattrHumanMode;
1851 attr = tokens_mode_human[0];
1852 } else if (tokens_mode_machine.size() > 1) {
1853 const std::string
1854 token = tokens_mode_machine[tokens_mode_machine.size() - 1];
1855 if (token == "?") {
1856 attr_req_is_valid = true;
1857 attr_req_page = -1;
1858 } else {
1859 if (page_num_sanitizer.IsValid(token)) {
1860 attr_req_is_valid = true;
1861 attr_req_page = static_cast<int32_t>(String2Uint64(token));
1862 }
1863 }
1864 xattr_mode = kXattrMachineMode;
1865 attr = tokens_mode_machine[0];
1866
1867 } else {
1868 attr_req_is_valid = true;
1869 attr = tokens_mode_machine[0];
1870 }
1871
1872 if (!attr_req_is_valid) {
1873 fuse_remounter_->fence()->Leave();
1874 fuse_reply_err(req, ENODATA);
1875 return;
1876 }
1877
1878 catalog::DirectoryEntry d;
1879 const bool found = GetDirentForInode(ino, &d);
1880
1881 if (!found) {
1882 fuse_remounter_->fence()->Leave();
1883 ReplyNegative(d, req);
1884 return;
1885 }
1886
1887 bool retval;
1888 XattrList xattrs;
1889 PathString path;
1890 retval = GetPathForInode(ino, &path);
1891
1892 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1893 "cvmfs_statfs: Race condition? "
1894 "GetPathForInode did not succeed for path %s "
1895 "(path might have not been set)",
1896 path.c_str())) {
1897 fuse_remounter_->fence()->Leave();
1898 fuse_reply_err(req, ESTALE);
1899 return;
1900 }
1901
1902 if (d.IsLink()) {
1903 const catalog::LookupOptions
1904 lookup_options = static_cast<catalog::LookupOptions>(
1905 catalog::kLookupDefault | catalog::kLookupRawSymlink);
1906 catalog::DirectoryEntry raw_symlink;
1907 retval = catalog_mgr->LookupPath(path, lookup_options, &raw_symlink);
1908
1909 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1910 "cvmfs_statfs: Race condition? "
1911 "LookupPath did not succeed for path %s",
1912 path.c_str())) {
1913 fuse_remounter_->fence()->Leave();
1914 fuse_reply_err(req, ESTALE);
1915 return;
1916 }
1917
1918 d.set_symlink(raw_symlink.symlink());
1919 }
1920 if (d.HasXattrs()) {
1921 retval = catalog_mgr->LookupXattrs(path, &xattrs);
1922
1923 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1924 "cvmfs_statfs: Race condition? "
1925 "LookupXattrs did not succeed for path %s",
1926 path.c_str())) {
1927 fuse_remounter_->fence()->Leave();
1928 fuse_reply_err(req, ESTALE);
1929 return;
1930 }
1931 }
1932
1933 bool magic_xattr_success = true;
1934 const MagicXattrRAIIWrapper magic_xattr(
1935 mount_point_->magic_xattr_mgr()->GetLocked(attr, path, &d));
1936 if (!magic_xattr.IsNull()) {
1937 magic_xattr_success = magic_xattr->PrepareValueFencedProtected(
1938 fuse_ctx->gid);
1939 }
1940
1941 fuse_remounter_->fence()->Leave();
1942
1943 if (!magic_xattr_success) {
1944 fuse_reply_err(req, ENOATTR);
1945 return;
1946 }
1947
1948 std::pair<bool, std::string> attribute_result;
1949
1950 if (!magic_xattr.IsNull()) {
1951 attribute_result = magic_xattr->GetValue(attr_req_page, xattr_mode);
1952 } else {
1953 if (!xattrs.Get(attr, &attribute_result.second)) {
1954 fuse_reply_err(req, ENOATTR);
1955 return;
1956 }
1957 attribute_result.first = true;
1958 }
1959
1960 if (!attribute_result.first) {
1961 fuse_reply_err(req, ENODATA);
1962 } else if (size == 0) {
1963 fuse_reply_xattr(req, attribute_result.second.length());
1964 } else if (size >= attribute_result.second.length()) {
1965 fuse_reply_buf(req, &attribute_result.second[0],
1966 attribute_result.second.length());
1967 } else {
1968 fuse_reply_err(req, ERANGE);
1969 }
1970 }
1971
1972
1973 static void cvmfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
1974 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1975 FuseInterruptCue ic(&req);
1976 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid,
1977 &ic);
1978
1979 fuse_remounter_->fence()->Enter();
1980 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1981 ino = catalog_mgr->MangleInode(ino);
1982 TraceInode(Tracer::kEventListAttr, ino, "listxattr()");
1983 LogCvmfs(kLogCvmfs, kLogDebug,
1984 "cvmfs_listxattr on inode: %" PRIu64 ", size %zu [visibility %d]",
1985 uint64_t(ino), size, mount_point_->magic_xattr_mgr()->visibility());
1986
1987 catalog::DirectoryEntry d;
1988 const bool found = GetDirentForInode(ino, &d);
1989 XattrList xattrs;
1990 if (d.HasXattrs()) {
1991 PathString path;
1992 bool retval = GetPathForInode(ino, &path);
1993
1994 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1995 "cvmfs_listxattr: Race condition? "
1996 "GetPathForInode did not succeed for ino %lu",
1997 ino)) {
1998 fuse_remounter_->fence()->Leave();
1999 fuse_reply_err(req, ESTALE);
2000 return;
2001 }
2002
2003 retval = catalog_mgr->LookupXattrs(path, &xattrs);
2004 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
2005 "cvmfs_listxattr: Race condition? "
2006 "LookupXattrs did not succeed for ino %lu",
2007 ino)) {
2008 fuse_remounter_->fence()->Leave();
2009 fuse_reply_err(req, ESTALE);
2010 return;
2011 }
2012 }
2013 fuse_remounter_->fence()->Leave();
2014
2015 if (!found) {
2016 ReplyNegative(d, req);
2017 return;
2018 }
2019
2020 string attribute_list;
2021 attribute_list = mount_point_->magic_xattr_mgr()->GetListString(&d);
2022 attribute_list += xattrs.ListKeysPosix(attribute_list);
2023
2024 if (size == 0) {
2025 fuse_reply_xattr(req, attribute_list.length());
2026 } else if (size >= attribute_list.length()) {
2027 if (attribute_list.empty())
2028 fuse_reply_buf(req, NULL, 0);
2029 else
2030 fuse_reply_buf(req, &attribute_list[0], attribute_list.length());
2031 } else {
2032 fuse_reply_err(req, ERANGE);
2033 }
2034 }
2035
2036 bool Evict(const string &path) {
2037 catalog::DirectoryEntry dirent;
2038 fuse_remounter_->fence()->Enter();
2039 const bool found = (GetDirentForPath(PathString(path), &dirent) > 0);
2040
2041 if (!found || !dirent.IsRegular()) {
2042 fuse_remounter_->fence()->Leave();
2043 return false;
2044 }
2045
2046 if (!dirent.IsChunkedFile()) {
2047 fuse_remounter_->fence()->Leave();
2048 } else {
2049 FileChunkList chunks;
2050 mount_point_->catalog_mgr()->ListFileChunks(
2051 PathString(path), dirent.hash_algorithm(), &chunks);
2052 fuse_remounter_->fence()->Leave();
2053 for (unsigned i = 0; i < chunks.size(); ++i) {
2054 file_system_->cache_mgr()->quota_mgr()->Remove(
2055 chunks.AtPtr(i)->content_hash());
2056 }
2057 }
2058 file_system_->cache_mgr()->quota_mgr()->Remove(dirent.checksum());
2059 return true;
2060 }
2061
2062
2063 bool Pin(const string &path) {
2064 catalog::DirectoryEntry dirent;
2065 fuse_remounter_->fence()->Enter();
2066 const bool found = (GetDirentForPath(PathString(path), &dirent) > 0);
2067 if (!found || !dirent.IsRegular()) {
2068 fuse_remounter_->fence()->Leave();
2069 return false;
2070 }
2071
2072 Fetcher *this_fetcher = dirent.IsExternalFile()
2073 ? mount_point_->external_fetcher()
2074 : mount_point_->fetcher();
2075
2076 if (!dirent.IsChunkedFile()) {
2077 fuse_remounter_->fence()->Leave();
2078 } else {
2079 FileChunkList chunks;
2080 mount_point_->catalog_mgr()->ListFileChunks(
2081 PathString(path), dirent.hash_algorithm(), &chunks);
2082 fuse_remounter_->fence()->Leave();
2083 for (unsigned i = 0; i < chunks.size(); ++i) {
2084 const bool retval = file_system_->cache_mgr()->quota_mgr()->Pin(
2085 chunks.AtPtr(i)->content_hash(), chunks.AtPtr(i)->size(),
2086 "Part of " + path, false);
2087 if (!retval)
2088 return false;
2089 int fd = -1;
2090 CacheManager::Label label;
2091 label.path = path;
2092 label.size = chunks.AtPtr(i)->size();
2093 label.zip_algorithm = dirent.compression_algorithm();
2094 label.flags |= CacheManager::kLabelPinned;
2095 label.flags |= CacheManager::kLabelChunked;
2096 if (dirent.IsExternalFile()) {
2097 label.flags |= CacheManager::kLabelExternal;
2098 label.range_offset = chunks.AtPtr(i)->offset();
2099 }
2100 fd = this_fetcher->Fetch(
2101 CacheManager::LabeledObject(chunks.AtPtr(i)->content_hash(), label));
2102 if (fd < 0) {
2103 return false;
2104 }
2105 file_system_->cache_mgr()->Close(fd);
2106 }
2107 return true;
2108 }
2109
2110 const bool retval = file_system_->cache_mgr()->quota_mgr()->Pin(
2111 dirent.checksum(), dirent.size(), path, false);
2112 if (!retval)
2113 return false;
2114 CacheManager::Label label;
2115 label.flags = CacheManager::kLabelPinned;
2116 label.size = dirent.size();
2117 label.path = path;
2118 label.zip_algorithm = dirent.compression_algorithm();
2119 const int fd = this_fetcher->Fetch(
2120 CacheManager::LabeledObject(dirent.checksum(), label));
2121 if (fd < 0) {
2122 return false;
2123 }
2124 file_system_->cache_mgr()->Close(fd);
2125 return true;
2126 }
2127
2128
2129 /**
2130 * Do after-daemon() initialization
2131 */
2132 static void cvmfs_init(void *userdata, struct fuse_conn_info *conn) {
2133 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_init");
2134
2135 // NFS support
2136 #ifdef CVMFS_NFS_SUPPORT
2137 conn->want |= FUSE_CAP_EXPORT_SUPPORT;
2138 #endif
2139
2140 if (mount_point_->enforce_acls()) {
2141 #ifdef FUSE_CAP_POSIX_ACL
2142 if ((conn->capable & FUSE_CAP_POSIX_ACL) == 0) {
2143 PANIC(kLogDebug | kLogSyslogErr,
2144 "FUSE: ACL support requested but missing fuse kernel support, "
2145 "aborting");
2146 }
2147 conn->want |= FUSE_CAP_POSIX_ACL;
2148 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslog, "enforcing ACLs");
2149 #else
2150 PANIC(kLogDebug | kLogSyslogErr,
2151 "FUSE: ACL support requested but not available in this version of "
2152 "libfuse %d, aborting",
2153 FUSE_VERSION);
2154 #endif
2155 }
2156
2157 if (mount_point_->cache_symlinks()) {
2158 #ifdef FUSE_CAP_CACHE_SYMLINKS
2159 if ((conn->capable & FUSE_CAP_CACHE_SYMLINKS) == FUSE_CAP_CACHE_SYMLINKS) {
2160 conn->want |= FUSE_CAP_CACHE_SYMLINKS;
2161 LogCvmfs(kLogCvmfs, kLogDebug, "FUSE: Enable symlink caching");
2162 #ifndef FUSE_CAP_EXPIRE_ONLY
2163 LogCvmfs(
2164 kLogCvmfs, kLogDebug | kLogSyslogWarn,
2165 "FUSE: Symlink caching enabled but no support for fuse_expire_entry. "
2166 "Symlinks will be cached but mountpoints on top of symlinks will "
2167 "break! "
2168 "Current libfuse %d is too old; required: libfuse >= 3.16, "
2169 "kernel >= 6.2-rc1",
2170 FUSE_VERSION);
2171 #endif
2172 } else {
2173 mount_point_->DisableCacheSymlinks();
2174 LogCvmfs(
2175 kLogCvmfs, kLogDebug | kLogSyslogWarn,
2176 "FUSE: Symlink caching requested but missing fuse kernel support, "
2177 "falling back to no caching");
2178 }
2179 #else
2180 mount_point_->DisableCacheSymlinks();
2181 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2182 "FUSE: Symlink caching requested but missing libfuse support, "
2183 "falling back to no caching. Current libfuse %d",
2184 FUSE_VERSION);
2185 #endif
2186 }
2187
2188 #ifdef FUSE_CAP_EXPIRE_ONLY
2189 if ((conn->capable & FUSE_CAP_EXPIRE_ONLY) == FUSE_CAP_EXPIRE_ONLY
2190 && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 16)) {
2191 mount_point_->EnableFuseExpireEntry();
2192 LogCvmfs(kLogCvmfs, kLogDebug, "FUSE: Enable fuse_expire_entry ");
2193 } else if (mount_point_->cache_symlinks()) {
2194 LogCvmfs(
2195 kLogCvmfs, kLogDebug | kLogSyslogWarn,
2196 "FUSE: Symlink caching enabled but no support for fuse_expire_entry. "
2197 "Symlinks will be cached but mountpoints on top of symlinks will "
2198 "break! "
2199 "Current libfuse %d; required: libfuse >= 3.16, kernel >= 6.2-rc1",
2200 FUSE_VERSION);
2201 }
2202 #endif
2203
2204 #ifdef FUSE_CAP_PASSTHROUGH
2205 if (conn->capable & FUSE_CAP_PASSTHROUGH) {
2206 if (loader_exports_->fuse_passthrough) {
2207 conn->want |= FUSE_CAP_PASSTHROUGH;
2208 /* "Passthrough and writeback cache are conflicting modes"
2209 * libfuse example/passthrough_hp.cc says,
2210 * but we don't use writeback cache mode in CVMFS. */
2211 pthread_mutex_lock(&fuse_passthru_tracker_lock);
2212 assert(!fuse_passthru_tracker);
2213 fuse_passthru_tracker = new std::unordered_map<fuse_ino_t,
2214 fuse_passthru_ctx_t>();
2215 pthread_mutex_unlock(&fuse_passthru_tracker_lock);
2216 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2217 "FUSE: Passthrough enabled.");
2218 } else {
2219 LogCvmfs(kLogCvmfs, kLogDebug,
2220 "FUSE: Passthrough enabled in build, available at runtime, but "
2221 "not enabled by the config option.");
2222 }
2223 } else {
2224 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2225 "FUSE: Passthrough enabled in build but unavailable at runtime.");
2226 }
2227 #else
2228 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2229 "FUSE: Passthrough disabled in this build.");
2230 #endif
2231 }
2232
2233 static void cvmfs_destroy(void *unused __attribute__((unused))) {
2234 // The debug log is already closed at this point
2235 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_destroy");
2236 #ifdef FUSE_CAP_PASSTHROUGH
2237 pthread_mutex_lock(&fuse_passthru_tracker_lock);
2238 assert(fuse_passthru_tracker);
2239 delete fuse_passthru_tracker;
2240 fuse_passthru_tracker = NULL;
2241 pthread_mutex_unlock(&fuse_passthru_tracker_lock);
2242 #endif
2243 }
2244
2245 /**
2246 * Puts the callback functions in one single structure
2247 */
2248 static void SetCvmfsOperations(struct fuse_lowlevel_ops *cvmfs_operations) {
2249 memset(cvmfs_operations, 0, sizeof(*cvmfs_operations));
2250
2251 // Init/Fini
2252 cvmfs_operations->init = cvmfs_init;
2253 cvmfs_operations->destroy = cvmfs_destroy;
2254
2255 cvmfs_operations->lookup = cvmfs_lookup;
2256 cvmfs_operations->getattr = cvmfs_getattr;
2257 cvmfs_operations->readlink = cvmfs_readlink;
2258 cvmfs_operations->open = cvmfs_open;
2259 cvmfs_operations->read = cvmfs_read;
2260 cvmfs_operations->release = cvmfs_release;
2261 cvmfs_operations->opendir = cvmfs_opendir;
2262 cvmfs_operations->readdir = cvmfs_readdir;
2263 cvmfs_operations->releasedir = cvmfs_releasedir;
2264 cvmfs_operations->statfs = cvmfs_statfs;
2265 cvmfs_operations->getxattr = cvmfs_getxattr;
2266 cvmfs_operations->listxattr = cvmfs_listxattr;
2267 cvmfs_operations->forget = cvmfs_forget;
2268 #if (FUSE_VERSION >= 29)
2269 cvmfs_operations->forget_multi = cvmfs_forget_multi;
2270 #endif
2271 }
2272
2273 // Called by cvmfs_talk when switching into read-only cache mode
2274 void UnregisterQuotaListener() {
2275 if (cvmfs::quota_unpin_listener_) {
2276 quota::UnregisterListener(cvmfs::quota_unpin_listener_);
2277 cvmfs::quota_unpin_listener_ = NULL;
2278 }
2279 if (cvmfs::quota_watchdog_listener_) {
2280 quota::UnregisterListener(cvmfs::quota_watchdog_listener_);
2281 cvmfs::quota_watchdog_listener_ = NULL;
2282 }
2283 }
2284
2285 bool SendFuseFd(const std::string &socket_path) {
2286 int fuse_fd;
2287 #if (FUSE_VERSION >= 30)
2288 fuse_fd = fuse_session_fd(*reinterpret_cast<struct fuse_session **>(
2289 loader_exports_->fuse_channel_or_session));
2290 #else
2291 fuse_fd = fuse_chan_fd(*reinterpret_cast<struct fuse_chan **>(
2292 loader_exports_->fuse_channel_or_session));
2293 #endif
2294 assert(fuse_fd >= 0);
2295 const int sock_fd = ConnectSocket(socket_path);
2296 if (sock_fd < 0) {
2297 LogCvmfs(kLogCvmfs, kLogDebug, "cannot connect to socket %s: %d",
2298 socket_path.c_str(), errno);
2299 return false;
2300 }
2301 const bool retval = SendFd2Socket(sock_fd, fuse_fd);
2302 close(sock_fd);
2303 return retval;
2304 }
2305
2306 } // namespace cvmfs
2307
2308
2309 string *g_boot_error = NULL;
2310
2311 __attribute__((
2312 visibility("default"))) loader::CvmfsExports *g_cvmfs_exports = NULL;
2313
2314
2315 #ifndef __TEST_CVMFS_MOCKFUSE // will be mocked in tests
2316 /**
2317 * Begin section of cvmfs.cc-specific magic extended attributes
2318 */
2319
2320 class ExpiresMagicXattr : public BaseMagicXattr {
2321 time_t catalogs_valid_until_;
2322
2323 virtual bool PrepareValueFenced() {
2324 catalogs_valid_until_ = cvmfs::fuse_remounter_->catalogs_valid_until();
2325 return true;
2326 }
2327
2328 virtual void FinalizeValue() {
2329 if (catalogs_valid_until_ == MountPoint::kIndefiniteDeadline) {
2330 result_pages_.push_back("never (fixed root catalog)");
2331 return;
2332 } else {
2333 const time_t now = time(NULL);
2334 result_pages_.push_back(StringifyInt((catalogs_valid_until_ - now) / 60));
2335 }
2336 }
2337 };
2338
2339 class InodeMaxMagicXattr : public BaseMagicXattr {
2340 virtual void FinalizeValue() {
2341 result_pages_.push_back(StringifyInt(
2342 cvmfs::inode_generation_info_.inode_generation
2343 + xattr_mgr_->mount_point()->catalog_mgr()->inode_gauge()));
2344 }
2345 };
2346
2347 class MaxFdMagicXattr : public BaseMagicXattr {
2348 virtual void FinalizeValue() {
2349 result_pages_.push_back(
2350 StringifyInt(cvmfs::max_open_files_ - cvmfs::kNumReservedFd));
2351 }
2352 };
2353
2354 class PidMagicXattr : public BaseMagicXattr {
2355 virtual void FinalizeValue() {
2356 result_pages_.push_back(StringifyInt(cvmfs::pid_));
2357 }
2358 };
2359
2360 class UptimeMagicXattr : public BaseMagicXattr {
2361 virtual void FinalizeValue() {
2362 const time_t now = time(NULL);
2363 const uint64_t uptime = now - cvmfs::loader_exports_->boot_time;
2364 result_pages_.push_back(StringifyUint(uptime / 60));
2365 }
2366 };
2367
2368 /**
2369 * Register cvmfs.cc-specific magic extended attributes to mountpoint's
2370 * magic xattribute manager
2371 */
2372 static void RegisterMagicXattrs() {
2373 MagicXattrManager *mgr = cvmfs::mount_point_->magic_xattr_mgr();
2374 mgr->Register("user.expires", new ExpiresMagicXattr());
2375 mgr->Register("user.inode_max", new InodeMaxMagicXattr());
2376 mgr->Register("user.pid", new PidMagicXattr());
2377 mgr->Register("user.maxfd", new MaxFdMagicXattr());
2378 mgr->Register("user.uptime", new UptimeMagicXattr());
2379
2380 mgr->Freeze();
2381 }
2382
2383 /**
2384 * Construct a file system but prevent hanging when already mounted. That
2385 * means: at most one "system" mount of any given repository name.
2386 */
2387 static FileSystem *InitSystemFs(const string &mount_path,
2388 const string &fqrn,
2389 FileSystem::FileSystemInfo fs_info) {
2390 fs_info.wait_workspace = false;
2391 FileSystem *file_system = FileSystem::Create(fs_info);
2392
2393 if (file_system->boot_status() == loader::kFailLockWorkspace) {
2394 string fqrn_from_xattr;
2395 const int retval = platform_getxattr(mount_path, "user.fqrn",
2396 &fqrn_from_xattr);
2397 if (!retval) {
2398 // Cvmfs not mounted anymore, but another cvmfs process is still in
2399 // shutdown procedure. Try again and wait for lock
2400 delete file_system;
2401 fs_info.wait_workspace = true;
2402 file_system = FileSystem::Create(fs_info);
2403 } else {
2404 if (fqrn_from_xattr == fqrn) {
2405 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2406 "repository already mounted on %s", mount_path.c_str());
2407 file_system->set_boot_status(loader::kFailDoubleMount);
2408 } else {
2409 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
2410 "CernVM-FS repository %s already mounted on %s", fqrn.c_str(),
2411 mount_path.c_str());
2412 file_system->set_boot_status(loader::kFailOtherMount);
2413 }
2414 }
2415 }
2416
2417 return file_system;
2418 }
2419
2420
2421 static void InitOptionsMgr(const loader::LoaderExports *loader_exports) {
2422 if (loader_exports->version >= 3 && loader_exports->simple_options_parsing) {
2423 cvmfs::options_mgr_ = new SimpleOptionsParser(
2424 new DefaultOptionsTemplateManager(loader_exports->repository_name));
2425 } else {
2426 cvmfs::options_mgr_ = new BashOptionsManager(
2427 new DefaultOptionsTemplateManager(loader_exports->repository_name));
2428 }
2429
2430 if (loader_exports->config_files != "") {
2431 vector<string> tokens = SplitString(loader_exports->config_files, ':');
2432 for (unsigned i = 0, s = tokens.size(); i < s; ++i) {
2433 cvmfs::options_mgr_->ParsePath(tokens[i], false);
2434 }
2435 } else {
2436 cvmfs::options_mgr_->ParseDefault(loader_exports->repository_name);
2437 }
2438 }
2439
2440
2441 static unsigned CheckMaxOpenFiles() {
2442 static unsigned max_open_files;
2443 static bool already_done = false;
2444
2445 // check number of open files (lazy evaluation)
2446 if (!already_done) {
2447 unsigned soft_limit = 0;
2448 unsigned hard_limit = 0;
2449 GetLimitNoFile(&soft_limit, &hard_limit);
2450
2451 if (soft_limit < cvmfs::kMinOpenFiles) {
2452 LogCvmfs(kLogCvmfs, kLogSyslogWarn | kLogDebug,
2453 "Warning: current limits for number of open files are "
2454 "(%u/%u)\n"
2455 "CernVM-FS is likely to run out of file descriptors, "
2456 "set ulimit -n to at least %u",
2457 soft_limit, hard_limit, cvmfs::kMinOpenFiles);
2458 }
2459 max_open_files = soft_limit;
2460 already_done = true;
2461 }
2462
2463 return max_open_files;
2464 }
2465
2466
2467 static bool NeedsReadEnviron() {
2468 return MountPoint::NeedsReadEnviron(cvmfs::options_mgr_);
2469 }
2470
2471
2472 static int Init(const loader::LoaderExports *loader_exports) {
2473 g_boot_error = new string("unknown error");
2474 cvmfs::loader_exports_ = loader_exports;
2475
2476 crypto::SetupLibcryptoMt();
2477
2478 InitOptionsMgr(loader_exports);
2479
2480 // We need logging set up before forking the watchdog
2481 FileSystem::SetupLoggingStandalone(*cvmfs::options_mgr_,
2482 loader_exports->repository_name);
2483
2484 // Start a watchdog if this is the first time or if this is a reload with
2485 // an old loader that expected the FUSE module to start a watchdog
2486 if (cvmfs::ShouldStartWatchdog()) {
2487 auto_umount::SetMountpoint(loader_exports->mount_point);
2488 cvmfs::watchdog_ = Watchdog::Create(auto_umount::UmountOnExit,
2489 NeedsReadEnviron());
2490 if (cvmfs::watchdog_ == NULL) {
2491 *g_boot_error = "failed to initialize watchdog.";
2492 return loader::kFailMonitor;
2493 }
2494 }
2495
2496 cvmfs::max_open_files_ = CheckMaxOpenFiles();
2497
2498 FileSystem::FileSystemInfo fs_info;
2499 fs_info.type = FileSystem::kFsFuse;
2500 fs_info.name = loader_exports->repository_name;
2501 fs_info.exe_path = loader_exports->program_name;
2502 fs_info.options_mgr = cvmfs::options_mgr_;
2503 fs_info.foreground = loader_exports->foreground;
2504 cvmfs::file_system_ = InitSystemFs(loader_exports->mount_point,
2505 loader_exports->repository_name, fs_info);
2506 if (!cvmfs::file_system_->IsValid()) {
2507 *g_boot_error = cvmfs::file_system_->boot_error();
2508 return cvmfs::file_system_->boot_status();
2509 }
2510 if ((cvmfs::file_system_->cache_mgr()->id() == kPosixCacheManager)
2511 && dynamic_cast<PosixCacheManager *>(cvmfs::file_system_->cache_mgr())
2512 ->do_refcount()) {
2513 cvmfs::check_fd_overflow_ = false;
2514 }
2515 if (cvmfs::file_system_->cache_mgr()->id() == kPosixCacheManager) {
2516 PosixCacheManager *pcm = dynamic_cast<PosixCacheManager *>(
2517 cvmfs::file_system_->cache_mgr());
2518 if (pcm != nullptr) {
2519 PosixQuotaManager *pqm = dynamic_cast<PosixQuotaManager *>(
2520 pcm->quota_mgr());
2521 if (pqm != nullptr) {
2522 pqm->RegisterMountpoint(loader_exports->mount_point);
2523 }
2524 }
2525 }
2526
2527 cvmfs::mount_point_ = MountPoint::Create(loader_exports->repository_name,
2528 cvmfs::file_system_);
2529 if (!cvmfs::mount_point_->IsValid()) {
2530 *g_boot_error = cvmfs::mount_point_->boot_error();
2531 return cvmfs::mount_point_->boot_status();
2532 }
2533
2534 RegisterMagicXattrs();
2535
2536 cvmfs::directory_handles_ = new cvmfs::DirectoryHandles();
2537 cvmfs::directory_handles_->set_empty_key((uint64_t)(-1));
2538 cvmfs::directory_handles_->set_deleted_key((uint64_t)(-2));
2539
2540 LogCvmfs(kLogCvmfs, kLogDebug, "fuse inode size is %lu bits",
2541 sizeof(fuse_ino_t) * 8);
2542
2543 cvmfs::inode_generation_info_
2544 .initial_revision = cvmfs::mount_point_->catalog_mgr()->GetRevision();
2545 cvmfs::inode_generation_info_.inode_generation = cvmfs::mount_point_
2546 ->inode_annotation()
2547 ->GetGeneration();
2548 LogCvmfs(kLogCvmfs, kLogDebug, "root inode is %" PRIu64,
2549 uint64_t(cvmfs::mount_point_->catalog_mgr()->GetRootInode()));
2550
2551 void **channel_or_session = NULL;
2552 if (loader_exports->version >= 4) {
2553 channel_or_session = loader_exports->fuse_channel_or_session;
2554 }
2555
2556 bool fuse_notify_invalidation = true;
2557 std::string buf;
2558 if (cvmfs::options_mgr_->GetValue("CVMFS_FUSE_NOTIFY_INVALIDATION", &buf)) {
2559 if (!cvmfs::options_mgr_->IsOn(buf)) {
2560 fuse_notify_invalidation = false;
2561 cvmfs::mount_point_->dentry_tracker()->Disable();
2562 }
2563 }
2564 cvmfs::fuse_remounter_ = new FuseRemounter(
2565 cvmfs::mount_point_, &cvmfs::inode_generation_info_, channel_or_session,
2566 fuse_notify_invalidation);
2567
2568 // Control & command interface
2569 cvmfs::talk_mgr_ = TalkManager::Create(
2570 cvmfs::mount_point_->talk_socket_path(),
2571 cvmfs::mount_point_,
2572 cvmfs::fuse_remounter_);
2573 if ((cvmfs::mount_point_->talk_socket_uid() != 0)
2574 || (cvmfs::mount_point_->talk_socket_gid() != 0)) {
2575 const uid_t tgt_uid = cvmfs::mount_point_->talk_socket_uid();
2576 const gid_t tgt_gid = cvmfs::mount_point_->talk_socket_gid();
2577 const int rvi = chown(cvmfs::mount_point_->talk_socket_path().c_str(),
2578 tgt_uid, tgt_gid);
2579 if (rvi != 0) {
2580 *g_boot_error = std::string("failed to set talk socket ownership - ")
2581 + "target " + StringifyInt(tgt_uid) + ":"
2582 + StringifyInt(tgt_uid) + ", user "
2583 + StringifyInt(geteuid()) + ":" + StringifyInt(getegid());
2584 return loader::kFailTalk;
2585 }
2586 }
2587 if (cvmfs::talk_mgr_ == NULL) {
2588 *g_boot_error = "failed to initialize talk socket (" + StringifyInt(errno)
2589 + ")";
2590 return loader::kFailTalk;
2591 }
2592
2593 // Notification system client
2594 {
2595 OptionsManager *options = cvmfs::file_system_->options_mgr();
2596 if (options->IsDefined("CVMFS_NOTIFICATION_SERVER")) {
2597 std::string config;
2598 options->GetValue("CVMFS_NOTIFICATION_SERVER", &config);
2599 const std::string repo_name = cvmfs::mount_point_->fqrn();
2600 cvmfs::notification_client_ = new NotificationClient(
2601 config, repo_name, cvmfs::fuse_remounter_,
2602 cvmfs::mount_point_->download_mgr(),
2603 cvmfs::mount_point_->signature_mgr());
2604 }
2605 }
2606
2607 return loader::kFailOk;
2608 }
2609 #endif // __TEST_CVMFS_MOCKFUSE
2610
2611
2612 /**
2613 * Things that have to be executed after fork() / daemon().
2614 * Reduces capabilities for the processes or threads that don't need
2615 * them, including the current thread.
2616 */
2617 static void Spawn() {
2618 // Start the first threads in this process
2619 // This is called at initialization time or after reload
2620
2621 // If there's a watchdog, kick it off first thing while we still have a
2622 // single-threaded well-defined state and before dropping privileges
2623 // at initialization time.
2624 cvmfs::pid_ = getpid();
2625 if (cvmfs::watchdog_) {
2626 cvmfs::watchdog_->Spawn(GetCurrentWorkingDirectory() + "/stacktrace."
2627 + cvmfs::mount_point_->fqrn());
2628 }
2629
2630 // Start the helper before dropping capabilities, if it isn't running
2631 cvmfs::mount_point_->authz_fetcher()->CheckHelper(
2632 cvmfs::mount_point_->membership_req());
2633
2634 if ((getuid() != 0) && SetuidCapabilityPermitted()) {
2635 LogCvmfs(kLogCvmfs, kLogDebug, "Reducing to minimum capabilities");
2636 // Earlier switched to using elevated capabilities without real uid root,
2637 // now reduce to minimum capabilities.
2638 const std::vector<cap_value_t> nocaps;
2639 if (NeedsReadEnviron()) {
2640 // Reserve the capabilities to read process environments
2641 const std::vector<cap_value_t> reservecaps = {CAP_DAC_READ_SEARCH, CAP_SYS_PTRACE};
2642 assert(ClearPermittedCapabilities(reservecaps, nocaps));
2643 } else {
2644 assert(ClearPermittedCapabilities(nocaps, nocaps));
2645 }
2646 } else {
2647 LogCvmfs(kLogCvmfs, kLogDebug, "Not clearing capabilities, uid %d euid%d",
2648 getuid(), geteuid());
2649 }
2650
2651 cvmfs::fuse_remounter_->Spawn();
2652 if (cvmfs::mount_point_->dentry_tracker()->is_active()) {
2653 cvmfs::mount_point_->dentry_tracker()->SpawnCleaner(
2654 // Usually every minute
2655 static_cast<unsigned int>(cvmfs::mount_point_->kcache_timeout_sec()));
2656 }
2657
2658 cvmfs::mount_point_->download_mgr()->Spawn();
2659 cvmfs::mount_point_->external_download_mgr()->Spawn();
2660 if (cvmfs::mount_point_->full_replica_download_mgr() != NULL)
2661 cvmfs::mount_point_->full_replica_download_mgr()->Spawn();
2662 if (cvmfs::mount_point_->resolv_conf_watcher() != NULL) {
2663 cvmfs::mount_point_->resolv_conf_watcher()->Spawn();
2664 }
2665 QuotaManager *quota_mgr = cvmfs::file_system_->cache_mgr()->quota_mgr();
2666 quota_mgr->Spawn();
2667 if (quota_mgr->HasCapability(QuotaManager::kCapListeners)) {
2668 cvmfs::quota_watchdog_listener_ = quota::RegisterWatchdogListener(
2669 quota_mgr, cvmfs::mount_point_->uuid()->uuid() + "-watchdog");
2670 cvmfs::quota_unpin_listener_ = quota::RegisterUnpinListener(
2671 quota_mgr,
2672 cvmfs::mount_point_->catalog_mgr(),
2673 cvmfs::mount_point_->uuid()->uuid() + "-unpin");
2674 }
2675 cvmfs::mount_point_->tracer()->Spawn();
2676 cvmfs::talk_mgr_->Spawn();
2677
2678 if (cvmfs::notification_client_ != NULL) {
2679 cvmfs::notification_client_->Spawn();
2680 }
2681
2682 if (cvmfs::file_system_->nfs_maps() != NULL) {
2683 cvmfs::file_system_->nfs_maps()->Spawn();
2684 }
2685
2686 cvmfs::file_system_->cache_mgr()->Spawn();
2687
2688 if (cvmfs::mount_point_->telemetry_aggr() != NULL) {
2689 cvmfs::mount_point_->telemetry_aggr()->Spawn();
2690 }
2691
2692 if (cvmfs::mount_point_->bundle_mgr() != NULL) {
2693 cvmfs::mount_point_->bundle_mgr()->Spawn();
2694 }
2695 }
2696
2697
2698 static string GetErrorMsg() {
2699 if (g_boot_error)
2700 return *g_boot_error;
2701 return "";
2702 }
2703
2704
2705 /**
2706 * Called alone at the end of SaveState; it performs a Fini() half way through,
2707 * enough to delete the catalog manager, so that no more open file handles
2708 * from file catalogs are active.
2709 */
2710 static void ShutdownMountpoint() {
2711 delete cvmfs::talk_mgr_;
2712 cvmfs::talk_mgr_ = NULL;
2713
2714 delete cvmfs::notification_client_;
2715 cvmfs::notification_client_ = NULL;
2716
2717 // The remounter has a reference to the mount point and the inode generation
2718 delete cvmfs::fuse_remounter_;
2719 cvmfs::fuse_remounter_ = NULL;
2720
2721 // The unpin listener requires the catalog, so this must be unregistered
2722 // before the catalog manager is removed
2723 if (cvmfs::quota_unpin_listener_ != NULL) {
2724 quota::UnregisterListener(cvmfs::quota_unpin_listener_);
2725 cvmfs::quota_unpin_listener_ = NULL;
2726 }
2727 if (cvmfs::quota_watchdog_listener_ != NULL) {
2728 quota::UnregisterListener(cvmfs::quota_watchdog_listener_);
2729 cvmfs::quota_watchdog_listener_ = NULL;
2730 }
2731
2732 delete cvmfs::directory_handles_;
2733 delete cvmfs::mount_point_;
2734 cvmfs::directory_handles_ = NULL;
2735 cvmfs::mount_point_ = NULL;
2736 }
2737
2738
2739 static void ClearExit() {
2740 if (cvmfs::watchdog_ != NULL) {
2741 cvmfs::watchdog_->ClearOnExitFn();
2742 }
2743 }
2744
2745
2746 static void Fini() {
2747 ShutdownMountpoint();
2748
2749 delete cvmfs::file_system_;
2750 delete cvmfs::options_mgr_;
2751 cvmfs::file_system_ = NULL;
2752 cvmfs::options_mgr_ = NULL;
2753
2754 if (cvmfs::loader_exports_->version < 6) {
2755 ClearExit();
2756 }
2757 delete cvmfs::watchdog_;
2758 cvmfs::watchdog_ = NULL;
2759
2760 delete g_boot_error;
2761 g_boot_error = NULL;
2762 auto_umount::SetMountpoint("");
2763
2764 crypto::CleanupLibcryptoMt();
2765 }
2766
2767
2768 static int AltProcessFlavor(int argc, char **argv) {
2769 if (strcmp(argv[1], "__cachemgr__") == 0) {
2770 return PosixQuotaManager::MainCacheManager(argc, argv);
2771 }
2772 if (strcmp(argv[1], "__wpad__") == 0) {
2773 return download::MainResolveProxyDescription(argc, argv);
2774 }
2775 return 1;
2776 }
2777
2778
2779 static bool MaintenanceMode(const int fd_progress) {
2780 SendMsg2Socket(fd_progress, "Entering maintenance mode\n");
2781 string msg_progress = "Draining out kernel caches (";
2782 if (FuseInvalidator::HasFuseNotifyInval())
2783 msg_progress += "up to ";
2784 msg_progress += StringifyInt(static_cast<int>(
2785 cvmfs::mount_point_->kcache_timeout_sec()))
2786 + "s)\n";
2787 SendMsg2Socket(fd_progress, msg_progress);
2788 if (cvmfs::watchdog_ != NULL && cvmfs::loader_exports_->version >= 6) {
2789 cvmfs::watchdog_->EnterMaintenanceMode();
2790 }
2791 cvmfs::fuse_remounter_->EnterMaintenanceMode();
2792 return true;
2793 }
2794
2795 #ifndef __TEST_CVMFS_MOCKFUSE
2796 static bool SaveState(const int fd_progress, loader::StateList *saved_states) {
2797 string msg_progress;
2798
2799 const unsigned num_open_dirs = cvmfs::directory_handles_->size();
2800 if (num_open_dirs != 0) {
2801 #ifdef DEBUGMSG
2802 for (cvmfs::DirectoryHandles::iterator
2803 i = cvmfs::directory_handles_->begin(),
2804 iEnd = cvmfs::directory_handles_->end();
2805 i != iEnd;
2806 ++i) {
2807 LogCvmfs(kLogCvmfs, kLogDebug, "saving dirhandle %lu", i->first);
2808 }
2809 #endif
2810
2811 msg_progress = "Saving open directory handles ("
2812 + StringifyInt(num_open_dirs) + " handles)\n";
2813 SendMsg2Socket(fd_progress, msg_progress);
2814
2815 // TODO(jblomer): should rather be saved just in a malloc'd memory block
2816 cvmfs::DirectoryHandles *saved_handles = new cvmfs::DirectoryHandles(
2817 *cvmfs::directory_handles_);
2818 loader::SavedState *save_open_dirs = new loader::SavedState();
2819 save_open_dirs->state_id = loader::kStateOpenDirs;
2820 save_open_dirs->state = saved_handles;
2821 saved_states->push_back(save_open_dirs);
2822 }
2823
2824 if (!cvmfs::file_system_->IsNfsSource()) {
2825 msg_progress = "Saving inode tracker\n";
2826 SendMsg2Socket(fd_progress, msg_progress);
2827 glue::InodeTracker *saved_inode_tracker = new glue::InodeTracker(
2828 *cvmfs::mount_point_->inode_tracker());
2829 loader::SavedState *state_glue_buffer = new loader::SavedState();
2830 state_glue_buffer->state_id = loader::kStateGlueBufferV4;
2831 state_glue_buffer->state = saved_inode_tracker;
2832 saved_states->push_back(state_glue_buffer);
2833 }
2834
2835 msg_progress = "Saving negative entry cache\n";
2836 SendMsg2Socket(fd_progress, msg_progress);
2837 glue::DentryTracker *saved_dentry_tracker = new glue::DentryTracker(
2838 *cvmfs::mount_point_->dentry_tracker());
2839 loader::SavedState *state_dentry_tracker = new loader::SavedState();
2840 state_dentry_tracker->state_id = loader::kStateDentryTracker;
2841 state_dentry_tracker->state = saved_dentry_tracker;
2842 saved_states->push_back(state_dentry_tracker);
2843
2844 msg_progress = "Saving page cache entry tracker\n";
2845 SendMsg2Socket(fd_progress, msg_progress);
2846 glue::PageCacheTracker *saved_page_cache_tracker = new glue::PageCacheTracker(
2847 *cvmfs::mount_point_->page_cache_tracker());
2848 loader::SavedState *state_page_cache_tracker = new loader::SavedState();
2849 state_page_cache_tracker->state_id = loader::kStatePageCacheTracker;
2850 state_page_cache_tracker->state = saved_page_cache_tracker;
2851 saved_states->push_back(state_page_cache_tracker);
2852
2853 msg_progress = "Saving chunk tables\n";
2854 SendMsg2Socket(fd_progress, msg_progress);
2855 ChunkTables *saved_chunk_tables = new ChunkTables(
2856 *cvmfs::mount_point_->chunk_tables());
2857 loader::SavedState *state_chunk_tables = new loader::SavedState();
2858 state_chunk_tables->state_id = loader::kStateOpenChunksV4;
2859 state_chunk_tables->state = saved_chunk_tables;
2860 saved_states->push_back(state_chunk_tables);
2861
2862 msg_progress = "Saving inode generation\n";
2863 SendMsg2Socket(fd_progress, msg_progress);
2864 cvmfs::inode_generation_info_
2865 .inode_generation += cvmfs::mount_point_->catalog_mgr()->inode_gauge();
2866 cvmfs::InodeGenerationInfo
2867 *saved_inode_generation = new cvmfs::InodeGenerationInfo(
2868 cvmfs::inode_generation_info_);
2869 loader::SavedState *state_inode_generation = new loader::SavedState();
2870 state_inode_generation->state_id = loader::kStateInodeGeneration;
2871 state_inode_generation->state = saved_inode_generation;
2872 saved_states->push_back(state_inode_generation);
2873
2874 msg_progress = "Saving fuse state\n";
2875 SendMsg2Socket(fd_progress, msg_progress);
2876 cvmfs::FuseState *saved_fuse_state = new cvmfs::FuseState();
2877 saved_fuse_state->cache_symlinks = cvmfs::mount_point_->cache_symlinks();
2878 saved_fuse_state->has_dentry_expire = cvmfs::mount_point_
2879 ->fuse_expire_entry();
2880 loader::SavedState *state_fuse = new loader::SavedState();
2881 state_fuse->state_id = loader::kStateFuse;
2882 state_fuse->state = saved_fuse_state;
2883 saved_states->push_back(state_fuse);
2884
2885 if (cvmfs::watchdog_ != NULL && cvmfs::loader_exports_->version >= 6) {
2886 msg_progress = "Saving watchdog listener state\n";
2887 SendMsg2Socket(fd_progress, msg_progress);
2888 WatchdogState *saved_watchdog_state = new WatchdogState();
2889 cvmfs::watchdog_->SaveState(saved_watchdog_state);
2890 loader::SavedState *state_watchdog = new loader::SavedState();
2891 state_watchdog->state_id = loader::kStateWatchdog;
2892 state_watchdog->state = saved_watchdog_state;
2893 saved_states->push_back(state_watchdog);
2894 }
2895
2896 // Close open file catalogs
2897 ShutdownMountpoint();
2898
2899 loader::SavedState *state_cache_mgr = new loader::SavedState();
2900 state_cache_mgr->state_id = loader::kStateOpenFiles;
2901 state_cache_mgr->state = cvmfs::file_system_->cache_mgr()->SaveState(
2902 fd_progress);
2903 saved_states->push_back(state_cache_mgr);
2904
2905 msg_progress = "Saving open files counter\n";
2906 uint32_t *saved_num_fd = new uint32_t(
2907 cvmfs::file_system_->no_open_files()->Get());
2908 loader::SavedState *state_num_fd = new loader::SavedState();
2909 state_num_fd->state_id = loader::kStateOpenFilesCounter;
2910 state_num_fd->state = saved_num_fd;
2911 saved_states->push_back(state_num_fd);
2912
2913 return true;
2914 }
2915
2916
2917 static bool RestoreState(const int fd_progress,
2918 const loader::StateList &saved_states) {
2919 // If we have no saved version of the page cache tracker, it is unsafe
2920 // to start using it. The page cache tracker has to run for the entire
2921 // lifetime of the mountpoint or not at all.
2922 cvmfs::mount_point_->page_cache_tracker()->Disable();
2923
2924 for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
2925 if (saved_states[i]->state_id == loader::kStateOpenDirs) {
2926 SendMsg2Socket(fd_progress, "Restoring open directory handles... ");
2927 delete cvmfs::directory_handles_;
2928 cvmfs::DirectoryHandles
2929 *saved_handles = (cvmfs::DirectoryHandles *)saved_states[i]->state;
2930 cvmfs::directory_handles_ = new cvmfs::DirectoryHandles(*saved_handles);
2931 cvmfs::file_system_->no_open_dirs()->Set(
2932 cvmfs::directory_handles_->size());
2933 cvmfs::DirectoryHandles::const_iterator i = cvmfs::directory_handles_
2934 ->begin();
2935 for (; i != cvmfs::directory_handles_->end(); ++i) {
2936 if (i->first >= cvmfs::next_directory_handle_)
2937 cvmfs::next_directory_handle_ = i->first + 1;
2938 }
2939
2940 SendMsg2Socket(
2941 fd_progress,
2942 StringifyInt(cvmfs::directory_handles_->size()) + " handles\n");
2943 }
2944
2945 if (saved_states[i]->state_id == loader::kStateGlueBuffer) {
2946 SendMsg2Socket(fd_progress, "Migrating inode tracker (v1 to v4)... ");
2947 compat::inode_tracker::InodeTracker
2948 *saved_inode_tracker = (compat::inode_tracker::InodeTracker *)
2949 saved_states[i]
2950 ->state;
2951 compat::inode_tracker::Migrate(saved_inode_tracker,
2952 cvmfs::mount_point_->inode_tracker());
2953 SendMsg2Socket(fd_progress, " done\n");
2954 }
2955
2956 if (saved_states[i]->state_id == loader::kStateGlueBufferV2) {
2957 SendMsg2Socket(fd_progress, "Migrating inode tracker (v2 to v4)... ");
2958 compat::inode_tracker_v2::InodeTracker
2959 *saved_inode_tracker = (compat::inode_tracker_v2::InodeTracker *)
2960 saved_states[i]
2961 ->state;
2962 compat::inode_tracker_v2::Migrate(saved_inode_tracker,
2963 cvmfs::mount_point_->inode_tracker());
2964 SendMsg2Socket(fd_progress, " done\n");
2965 }
2966
2967 if (saved_states[i]->state_id == loader::kStateGlueBufferV3) {
2968 SendMsg2Socket(fd_progress, "Migrating inode tracker (v3 to v4)... ");
2969 compat::inode_tracker_v3::InodeTracker
2970 *saved_inode_tracker = (compat::inode_tracker_v3::InodeTracker *)
2971 saved_states[i]
2972 ->state;
2973 compat::inode_tracker_v3::Migrate(saved_inode_tracker,
2974 cvmfs::mount_point_->inode_tracker());
2975 SendMsg2Socket(fd_progress, " done\n");
2976 }
2977
2978 if (saved_states[i]->state_id == loader::kStateGlueBufferV4) {
2979 SendMsg2Socket(fd_progress, "Restoring inode tracker... ");
2980 cvmfs::mount_point_->inode_tracker()->~InodeTracker();
2981 glue::InodeTracker
2982 *saved_inode_tracker = (glue::InodeTracker *)saved_states[i]->state;
2983 new (cvmfs::mount_point_->inode_tracker())
2984 glue::InodeTracker(*saved_inode_tracker);
2985 SendMsg2Socket(fd_progress, " done\n");
2986 }
2987
2988 if (saved_states[i]->state_id == loader::kStateDentryTracker) {
2989 SendMsg2Socket(fd_progress, "Restoring dentry tracker... ");
2990 cvmfs::mount_point_->dentry_tracker()->~DentryTracker();
2991 glue::DentryTracker
2992 *saved_dentry_tracker = static_cast<glue::DentryTracker *>(
2993 saved_states[i]->state);
2994 new (cvmfs::mount_point_->dentry_tracker())
2995 glue::DentryTracker(*saved_dentry_tracker);
2996 SendMsg2Socket(fd_progress, " done\n");
2997 }
2998
2999 if (saved_states[i]->state_id == loader::kStatePageCacheTracker) {
3000 SendMsg2Socket(fd_progress, "Restoring page cache entry tracker... ");
3001 cvmfs::mount_point_->page_cache_tracker()->~PageCacheTracker();
3002 glue::PageCacheTracker
3003 *saved_page_cache_tracker = (glue::PageCacheTracker *)saved_states[i]
3004 ->state;
3005 new (cvmfs::mount_point_->page_cache_tracker())
3006 glue::PageCacheTracker(*saved_page_cache_tracker);
3007 SendMsg2Socket(fd_progress, " done\n");
3008 }
3009
3010 ChunkTables *chunk_tables = cvmfs::mount_point_->chunk_tables();
3011
3012 if (saved_states[i]->state_id == loader::kStateOpenChunks) {
3013 SendMsg2Socket(fd_progress, "Migrating chunk tables (v1 to v4)... ");
3014 compat::chunk_tables::ChunkTables
3015 *saved_chunk_tables = (compat::chunk_tables::ChunkTables *)
3016 saved_states[i]
3017 ->state;
3018 compat::chunk_tables::Migrate(saved_chunk_tables, chunk_tables);
3019 SendMsg2Socket(
3020 fd_progress,
3021 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
3022 }
3023
3024 if (saved_states[i]->state_id == loader::kStateOpenChunksV2) {
3025 SendMsg2Socket(fd_progress, "Migrating chunk tables (v2 to v4)... ");
3026 compat::chunk_tables_v2::ChunkTables
3027 *saved_chunk_tables = (compat::chunk_tables_v2::ChunkTables *)
3028 saved_states[i]
3029 ->state;
3030 compat::chunk_tables_v2::Migrate(saved_chunk_tables, chunk_tables);
3031 SendMsg2Socket(
3032 fd_progress,
3033 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
3034 }
3035
3036 if (saved_states[i]->state_id == loader::kStateOpenChunksV3) {
3037 SendMsg2Socket(fd_progress, "Migrating chunk tables (v3 to v4)... ");
3038 compat::chunk_tables_v3::ChunkTables
3039 *saved_chunk_tables = (compat::chunk_tables_v3::ChunkTables *)
3040 saved_states[i]
3041 ->state;
3042 compat::chunk_tables_v3::Migrate(saved_chunk_tables, chunk_tables);
3043 SendMsg2Socket(
3044 fd_progress,
3045 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
3046 }
3047
3048 if (saved_states[i]->state_id == loader::kStateOpenChunksV4) {
3049 SendMsg2Socket(fd_progress, "Restoring chunk tables... ");
3050 chunk_tables->~ChunkTables();
3051 ChunkTables *saved_chunk_tables = reinterpret_cast<ChunkTables *>(
3052 saved_states[i]->state);
3053 new (chunk_tables) ChunkTables(*saved_chunk_tables);
3054 SendMsg2Socket(fd_progress, " done\n");
3055 }
3056
3057 if (saved_states[i]->state_id == loader::kStateInodeGeneration) {
3058 SendMsg2Socket(fd_progress, "Restoring inode generation... ");
3059 cvmfs::InodeGenerationInfo
3060 *old_info = (cvmfs::InodeGenerationInfo *)saved_states[i]->state;
3061 if (old_info->version == 1) {
3062 // Migration
3063 cvmfs::inode_generation_info_.initial_revision = old_info
3064 ->initial_revision;
3065 cvmfs::inode_generation_info_.incarnation = old_info->incarnation;
3066 // Note: in the rare case of inode generation being 0 before, inode
3067 // can clash after reload before remount
3068 } else {
3069 cvmfs::inode_generation_info_ = *old_info;
3070 }
3071 ++cvmfs::inode_generation_info_.incarnation;
3072 SendMsg2Socket(fd_progress, " done\n");
3073 }
3074
3075 if (saved_states[i]->state_id == loader::kStateOpenFilesCounter) {
3076 SendMsg2Socket(fd_progress, "Restoring open files counter... ");
3077 cvmfs::file_system_->no_open_files()->Set(
3078 *(reinterpret_cast<uint32_t *>(saved_states[i]->state)));
3079 SendMsg2Socket(fd_progress, " done\n");
3080 }
3081
3082 if (saved_states[i]->state_id == loader::kStateOpenFiles) {
3083 const int old_root_fd = cvmfs::mount_point_->catalog_mgr()->root_fd();
3084
3085 // TODO(jblomer): make this less hacky
3086
3087 const CacheManagerIds saved_type = cvmfs::file_system_->cache_mgr()
3088 ->PeekState(
3089 saved_states[i]->state);
3090 int fixup_root_fd = -1;
3091
3092 if ((saved_type == kStreamingCacheManager)
3093 && (cvmfs::file_system_->cache_mgr()->id()
3094 != kStreamingCacheManager)) {
3095 // stick to the streaming cache manager
3096 StreamingCacheManager *new_cache_mgr = new StreamingCacheManager(
3097 cvmfs::max_open_files_,
3098 cvmfs::file_system_->cache_mgr(),
3099 cvmfs::mount_point_->download_mgr(),
3100 cvmfs::mount_point_->external_download_mgr(),
3101 StreamingCacheManager::kDefaultBufferSize,
3102 cvmfs::file_system_->statistics());
3103 fixup_root_fd = new_cache_mgr->PlantFd(old_root_fd);
3104 cvmfs::file_system_->ReplaceCacheManager(new_cache_mgr);
3105 cvmfs::mount_point_->fetcher()->ReplaceCacheManager(new_cache_mgr);
3106 cvmfs::mount_point_->external_fetcher()->ReplaceCacheManager(
3107 new_cache_mgr);
3108 }
3109
3110 if ((cvmfs::file_system_->cache_mgr()->id() == kStreamingCacheManager)
3111 && (saved_type != kStreamingCacheManager)) {
3112 // stick to the cache manager wrapped into the streaming cache
3113 CacheManager *wrapped_cache_mgr = dynamic_cast<StreamingCacheManager *>(
3114 cvmfs::file_system_->cache_mgr())
3115 ->MoveOutBackingCacheMgr(
3116 &fixup_root_fd);
3117 delete cvmfs::file_system_->cache_mgr();
3118 cvmfs::file_system_->ReplaceCacheManager(wrapped_cache_mgr);
3119 cvmfs::mount_point_->fetcher()->ReplaceCacheManager(wrapped_cache_mgr);
3120 cvmfs::mount_point_->external_fetcher()->ReplaceCacheManager(
3121 wrapped_cache_mgr);
3122 }
3123
3124 const int new_root_fd = cvmfs::file_system_->cache_mgr()->RestoreState(
3125 fd_progress, saved_states[i]->state);
3126 LogCvmfs(kLogCvmfs, kLogDebug, "new root file catalog descriptor @%d",
3127 new_root_fd);
3128 if (new_root_fd >= 0) {
3129 cvmfs::file_system_->RemapCatalogFd(old_root_fd, new_root_fd);
3130 } else if (fixup_root_fd >= 0) {
3131 LogCvmfs(kLogCvmfs, kLogDebug,
3132 "new root file catalog descriptor (fixup) @%d", fixup_root_fd);
3133 cvmfs::file_system_->RemapCatalogFd(old_root_fd, fixup_root_fd);
3134 }
3135 }
3136
3137 if (saved_states[i]->state_id == loader::kStateFuse) {
3138 SendMsg2Socket(fd_progress, "Restoring fuse state... ");
3139 cvmfs::FuseState *fuse_state = static_cast<cvmfs::FuseState *>(
3140 saved_states[i]->state);
3141 if (!fuse_state->cache_symlinks)
3142 cvmfs::mount_point_->DisableCacheSymlinks();
3143 if (fuse_state->has_dentry_expire)
3144 cvmfs::mount_point_->EnableFuseExpireEntry();
3145 SendMsg2Socket(fd_progress, " done\n");
3146 }
3147
3148 if (saved_states[i]->state_id == loader::kStateWatchdog) {
3149 SendMsg2Socket(fd_progress, "Restoring watchdog listener state... ");
3150 WatchdogState *watchdog_state = static_cast<WatchdogState *>(
3151 saved_states[i]->state);
3152 cvmfs::watchdog_ = Watchdog::Create(auto_umount::UmountOnExit,
3153 NeedsReadEnviron(),
3154 watchdog_state);
3155 assert(cvmfs::watchdog_ != NULL);
3156 SendMsg2Socket(fd_progress, " done\n");
3157 }
3158 }
3159
3160 if (cvmfs::mount_point_->inode_annotation()) {
3161 const uint64_t saved_generation = cvmfs::inode_generation_info_
3162 .inode_generation;
3163 cvmfs::mount_point_->inode_annotation()->IncGeneration(saved_generation);
3164 }
3165
3166 return true;
3167 }
3168
3169
3170 static void FreeSavedState(const int fd_progress,
3171 const loader::StateList &saved_states) {
3172 for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
3173 switch (saved_states[i]->state_id) {
3174 case loader::kStateOpenDirs:
3175 SendMsg2Socket(fd_progress, "Releasing saved open directory handles\n");
3176 delete static_cast<cvmfs::DirectoryHandles *>(saved_states[i]->state);
3177 break;
3178 case loader::kStateGlueBuffer:
3179 SendMsg2Socket(fd_progress,
3180 "Releasing saved glue buffer (version 1)\n");
3181 delete static_cast<compat::inode_tracker::InodeTracker *>(
3182 saved_states[i]->state);
3183 break;
3184 case loader::kStateGlueBufferV2:
3185 SendMsg2Socket(fd_progress,
3186 "Releasing saved glue buffer (version 2)\n");
3187 delete static_cast<compat::inode_tracker_v2::InodeTracker *>(
3188 saved_states[i]->state);
3189 break;
3190 case loader::kStateGlueBufferV3:
3191 SendMsg2Socket(fd_progress,
3192 "Releasing saved glue buffer (version 3)\n");
3193 delete static_cast<compat::inode_tracker_v3::InodeTracker *>(
3194 saved_states[i]->state);
3195 break;
3196 case loader::kStateGlueBufferV4:
3197 SendMsg2Socket(fd_progress, "Releasing saved glue buffer\n");
3198 delete static_cast<glue::InodeTracker *>(saved_states[i]->state);
3199 break;
3200 case loader::kStateDentryTracker:
3201 SendMsg2Socket(fd_progress, "Releasing saved dentry tracker\n");
3202 delete static_cast<glue::DentryTracker *>(saved_states[i]->state);
3203 break;
3204 case loader::kStatePageCacheTracker:
3205 SendMsg2Socket(fd_progress, "Releasing saved page cache entry cache\n");
3206 delete static_cast<glue::PageCacheTracker *>(saved_states[i]->state);
3207 break;
3208 case loader::kStateOpenChunks:
3209 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 1)\n");
3210 delete static_cast<compat::chunk_tables::ChunkTables *>(
3211 saved_states[i]->state);
3212 break;
3213 case loader::kStateOpenChunksV2:
3214 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 2)\n");
3215 delete static_cast<compat::chunk_tables_v2::ChunkTables *>(
3216 saved_states[i]->state);
3217 break;
3218 case loader::kStateOpenChunksV3:
3219 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 3)\n");
3220 delete static_cast<compat::chunk_tables_v3::ChunkTables *>(
3221 saved_states[i]->state);
3222 break;
3223 case loader::kStateOpenChunksV4:
3224 SendMsg2Socket(fd_progress, "Releasing chunk tables\n");
3225 delete static_cast<ChunkTables *>(saved_states[i]->state);
3226 break;
3227 case loader::kStateInodeGeneration:
3228 SendMsg2Socket(fd_progress, "Releasing saved inode generation info\n");
3229 delete static_cast<cvmfs::InodeGenerationInfo *>(
3230 saved_states[i]->state);
3231 break;
3232 case loader::kStateOpenFiles:
3233 cvmfs::file_system_->cache_mgr()->FreeState(fd_progress,
3234 saved_states[i]->state);
3235 break;
3236 case loader::kStateOpenFilesCounter:
3237 SendMsg2Socket(fd_progress, "Releasing open files counter\n");
3238 delete static_cast<uint32_t *>(saved_states[i]->state);
3239 break;
3240 case loader::kStateFuse:
3241 SendMsg2Socket(fd_progress, "Releasing fuse state\n");
3242 delete static_cast<cvmfs::FuseState *>(saved_states[i]->state);
3243 break;
3244 case loader::kStateWatchdog:
3245 SendMsg2Socket(fd_progress, "Releasing watchdog listener state\n");
3246 delete static_cast<WatchdogState *>(saved_states[i]->state);
3247 break;
3248 default:
3249 break;
3250 }
3251 }
3252 }
3253 #endif
3254
3255
3256 static void __attribute__((constructor)) LibraryMain() {
3257 g_cvmfs_exports = new loader::CvmfsExports();
3258 g_cvmfs_exports->so_version = CVMFS_VERSION;
3259 g_cvmfs_exports->fnAltProcessFlavor = AltProcessFlavor;
3260 g_cvmfs_exports->fnInit = Init;
3261 g_cvmfs_exports->fnSpawn = Spawn;
3262 g_cvmfs_exports->fnFini = Fini;
3263 g_cvmfs_exports->fnGetErrorMsg = GetErrorMsg;
3264 g_cvmfs_exports->fnMaintenanceMode = MaintenanceMode;
3265 #ifndef __TEST_CVMFS_MOCKFUSE
3266 g_cvmfs_exports->fnSaveState = SaveState;
3267 g_cvmfs_exports->fnRestoreState = RestoreState;
3268 g_cvmfs_exports->fnFreeSavedState = FreeSavedState;
3269 #endif
3270 cvmfs::SetCvmfsOperations(&g_cvmfs_exports->cvmfs_operations);
3271 g_cvmfs_exports->fnClearExit = ClearExit;
3272 }
3273
3274
3275 static void __attribute__((destructor)) LibraryExit() {
3276 delete g_cvmfs_exports;
3277 g_cvmfs_exports = NULL;
3278 }
3279
3280