GCC Code Coverage Report


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