GCC Code Coverage Report


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