GCC Code Coverage Report


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