GCC Code Coverage Report


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