GCC Code Coverage Report


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