GCC Code Coverage Report


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