GCC Code Coverage Report


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