GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cvmfs.cc
Date: 2026-03-15 02:35:27
Exec Total Coverage
Lines: 0 1650 0.0%
Branches: 0 2383 0.0%

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