GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cvmfs.cc
Date: 2025-02-09 02:34:19
Exec Total Coverage
Lines: 0 1580 0.0%
Branches: 0 2284 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, "EIO (01) on %s", name);
640 perf::Inc(file_system_->n_eio_total());
641 perf::Inc(file_system_->n_eio_01());
642
643 fuse_reply_err(req, EIO);
644 }
645
646
647 /**
648 *
649 */
650 static void cvmfs_forget(
651 fuse_req_t req,
652 fuse_ino_t ino,
653 #if CVMFS_USE_LIBFUSE == 2
654 unsigned long nlookup // NOLINT
655 #else
656 uint64_t nlookup
657 #endif
658 ) {
659 HighPrecisionTimer guard_timer(file_system_->hist_fs_forget());
660
661 perf::Inc(file_system_->n_fs_forget());
662
663 // The libfuse high-level library does the same
664 if (ino == FUSE_ROOT_ID) {
665 fuse_reply_none(req);
666 return;
667 }
668
669 // Ensure that we don't need to call catalog_mgr()->MangleInode(ino)
670 assert(ino > mount_point_->catalog_mgr()->kInodeOffset);
671
672 LogCvmfs(kLogCvmfs, kLogDebug, "forget on inode %" PRIu64 " by %" PRIu64,
673 uint64_t(ino), nlookup);
674
675 if (!file_system_->IsNfsSource()) {
676 bool removed =
677 mount_point_->inode_tracker()->GetVfsPutRaii().VfsPut(ino, nlookup);
678 if (removed)
679 mount_point_->page_cache_tracker()->GetEvictRaii().Evict(ino);
680 }
681
682 fuse_reply_none(req);
683 }
684
685
686 #if (FUSE_VERSION >= 29)
687 static void cvmfs_forget_multi(
688 fuse_req_t req,
689 size_t count,
690 struct fuse_forget_data *forgets
691 ) {
692 HighPrecisionTimer guard_timer(file_system_->hist_fs_forget_multi());
693
694 perf::Xadd(file_system_->n_fs_forget(), count);
695 if (file_system_->IsNfsSource()) {
696 fuse_reply_none(req);
697 return;
698 }
699
700 {
701 glue::InodeTracker::VfsPutRaii vfs_put_raii =
702 mount_point_->inode_tracker()->GetVfsPutRaii();
703 glue::PageCacheTracker::EvictRaii evict_raii =
704 mount_point_->page_cache_tracker()->GetEvictRaii();
705 for (size_t i = 0; i < count; ++i) {
706 if (forgets[i].ino == FUSE_ROOT_ID) {
707 continue;
708 }
709
710 // Ensure that we don't need to call catalog_mgr()->MangleInode(ino)
711 assert(forgets[i].ino > mount_point_->catalog_mgr()->kInodeOffset);
712 LogCvmfs(kLogCvmfs, kLogDebug, "forget on inode %" PRIu64 " by %" PRIu64,
713 forgets[i].ino, forgets[i].nlookup);
714
715 bool removed = vfs_put_raii.VfsPut(forgets[i].ino, forgets[i].nlookup);
716 if (removed)
717 evict_raii.Evict(forgets[i].ino);
718 }
719 }
720
721 fuse_reply_none(req);
722 }
723 #endif // FUSE_VERSION >= 29
724
725
726 /**
727 * Looks into dirent to decide if this is an EIO negative reply or an
728 * ENOENT negative reply. We do not need to store the reply in the negative
729 * cache tracker because ReplyNegative is called on inode queries. Inodes,
730 * however, change anyway when a new catalog is applied.
731 */
732 static void ReplyNegative(const catalog::DirectoryEntry &dirent,
733 fuse_req_t req)
734 {
735 if (dirent.GetSpecial() == catalog::kDirentNegative) {
736 fuse_reply_err(req, ENOENT);
737 } else {
738 const char * name = dirent.name().c_str();
739 const char * link = dirent.symlink().c_str();
740
741 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
742 "EIO (02) name=%s symlink=%s",
743 name ? name: "<unset>",
744 link ? link: "<unset>");
745
746 perf::Inc(file_system_->n_eio_total());
747 perf::Inc(file_system_->n_eio_02());
748 fuse_reply_err(req, EIO);
749 }
750 }
751
752
753 /**
754 * Transform a cvmfs dirent into a struct stat.
755 */
756 static void cvmfs_getattr(fuse_req_t req, fuse_ino_t ino,
757 struct fuse_file_info *fi)
758 {
759 HighPrecisionTimer guard_timer(file_system_->hist_fs_getattr());
760
761 perf::Inc(file_system_->n_fs_stat());
762 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
763 FuseInterruptCue ic(&req);
764 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
765 fuse_remounter_->TryFinish();
766
767 fuse_remounter_->fence()->Enter();
768 ino = mount_point_->catalog_mgr()->MangleInode(ino);
769 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_getattr (stat) for inode: %" PRIu64,
770 uint64_t(ino));
771
772 if (!CheckVoms(*fuse_ctx)) {
773 fuse_remounter_->fence()->Leave();
774 fuse_reply_err(req, EACCES);
775 return;
776 }
777 catalog::DirectoryEntry dirent;
778 bool found = GetDirentForInode(ino, &dirent);
779 TraceInode(Tracer::kEventGetAttr, ino, "getattr()");
780 if ((!found && (dirent.inode() == ino)) || MayBeInPageCacheTracker(dirent)) {
781 // Serve retired inode from page cache tracker; even if we find it in the
782 // catalog, we replace the dirent by the page cache tracker version to
783 // not confuse open file handles
784 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_getattr %" PRIu64 " "
785 "served from page cache tracker", ino);
786 shash::Any hash;
787 struct stat info;
788 bool is_open =
789 mount_point_->page_cache_tracker()->GetInfoIfOpen(ino, &hash, &info);
790 if (is_open) {
791 fuse_remounter_->fence()->Leave();
792 if (found && HasDifferentContent(dirent, hash, info)) {
793 // We should from now on provide the new inode information instead
794 // of the stale one. To this end, we need to invalidate the dentry to
795 // trigger a fresh LOOKUP call
796 uint64_t parent_ino;
797 NameString name;
798 if (mount_point_->inode_tracker()->FindDentry(
799 dirent.inode(), &parent_ino, &name))
800 {
801 fuse_remounter_->InvalidateDentry(parent_ino, name);
802 }
803 perf::Inc(file_system_->n_fs_stat_stale());
804 }
805 fuse_reply_attr(req, &info, GetKcacheTimeout());
806 return;
807 }
808 }
809 fuse_remounter_->fence()->Leave();
810
811 if (!found) {
812 ReplyNegative(dirent, req);
813 return;
814 }
815
816 struct stat info = dirent.GetStatStructure();
817
818 fuse_reply_attr(req, &info, GetKcacheTimeout());
819 }
820
821
822 /**
823 * Reads a symlink from the catalog. Environment variables are expanded.
824 */
825 static void cvmfs_readlink(fuse_req_t req, fuse_ino_t ino) {
826 HighPrecisionTimer guard_timer(file_system_->hist_fs_readlink());
827
828 perf::Inc(file_system_->n_fs_readlink());
829 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
830 FuseInterruptCue ic(&req);
831 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
832
833 fuse_remounter_->fence()->Enter();
834 ino = mount_point_->catalog_mgr()->MangleInode(ino);
835 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_readlink on inode: %" PRIu64,
836 uint64_t(ino));
837
838 catalog::DirectoryEntry dirent;
839 const bool found = GetDirentForInode(ino, &dirent);
840 TraceInode(Tracer::kEventReadlink, ino, "readlink()");
841 fuse_remounter_->fence()->Leave();
842
843 if (!found) {
844 ReplyNegative(dirent, req);
845 return;
846 }
847
848 if (!dirent.IsLink()) {
849 fuse_reply_err(req, EINVAL);
850 return;
851 }
852
853 fuse_reply_readlink(req, dirent.symlink().c_str());
854 }
855
856
857 static void AddToDirListing(const fuse_req_t req,
858 const char *name, const struct stat *stat_info,
859 BigVector<char> *listing)
860 {
861 LogCvmfs(kLogCvmfs, kLogDebug, "Add to listing: %s, inode %" PRIu64,
862 name, uint64_t(stat_info->st_ino));
863 size_t remaining_size = listing->capacity() - listing->size();
864 const size_t entry_size = fuse_add_direntry(req, NULL, 0, name, stat_info, 0);
865
866 while (entry_size > remaining_size) {
867 listing->DoubleCapacity();
868 remaining_size = listing->capacity() - listing->size();
869 }
870
871 char *buffer;
872 bool large_alloc;
873 listing->ShareBuffer(&buffer, &large_alloc);
874 fuse_add_direntry(req, buffer + listing->size(),
875 remaining_size, name, stat_info,
876 listing->size() + entry_size);
877 listing->SetSize(listing->size() + entry_size);
878 }
879
880
881 /**
882 * Open a directory for listing.
883 */
884 static void cvmfs_opendir(fuse_req_t req, fuse_ino_t ino,
885 struct fuse_file_info *fi)
886 {
887 HighPrecisionTimer guard_timer(file_system_->hist_fs_opendir());
888
889 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
890 FuseInterruptCue ic(&req);
891 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
892 fuse_remounter_->TryFinish();
893
894 fuse_remounter_->fence()->Enter();
895 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
896 ino = catalog_mgr->MangleInode(ino);
897 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64,
898 uint64_t(ino));
899 if (!CheckVoms(*fuse_ctx)) {
900 fuse_remounter_->fence()->Leave();
901 fuse_reply_err(req, EACCES);
902 return;
903 }
904
905 TraceInode(Tracer::kEventOpenDir, ino, "opendir()");
906 PathString path;
907 catalog::DirectoryEntry d;
908 bool found = GetPathForInode(ino, &path);
909 if (!found) {
910 fuse_remounter_->fence()->Leave();
911 fuse_reply_err(req, ENOENT);
912 return;
913 }
914 found = GetDirentForInode(ino, &d);
915
916 if (!found) {
917 fuse_remounter_->fence()->Leave();
918 ReplyNegative(d, req);
919 return;
920 }
921 if (!d.IsDirectory()) {
922 fuse_remounter_->fence()->Leave();
923 fuse_reply_err(req, ENOTDIR);
924 return;
925 }
926
927 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_opendir on inode: %" PRIu64 ", path %s",
928 uint64_t(ino), path.c_str());
929
930 // Build listing
931 BigVector<char> fuse_listing(512);
932
933 // Add current directory link
934 struct stat info;
935 info = d.GetStatStructure();
936 AddToDirListing(req, ".", &info, &fuse_listing);
937
938 // Add parent directory link
939 catalog::DirectoryEntry p;
940 if (d.inode() != catalog_mgr->GetRootInode() &&
941 (GetDirentForPath(GetParentPath(path), &p) > 0))
942 {
943 info = p.GetStatStructure();
944 AddToDirListing(req, "..", &info, &fuse_listing);
945 }
946
947 // Add all names
948 catalog::StatEntryList listing_from_catalog;
949 bool retval = catalog_mgr->ListingStat(path, &listing_from_catalog);
950
951 if (!retval) {
952 fuse_remounter_->fence()->Leave();
953 fuse_listing.Clear(); // Buffer is shared, empty manually
954
955 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
956 "EIO (03) on %s", path.c_str());
957 perf::Inc(file_system_->n_eio_total());
958 perf::Inc(file_system_->n_eio_03());
959 fuse_reply_err(req, EIO);
960 return;
961 }
962 for (unsigned i = 0; i < listing_from_catalog.size(); ++i) {
963 // Fix inodes
964 PathString entry_path;
965 entry_path.Assign(path);
966 entry_path.Append("/", 1);
967 entry_path.Append(listing_from_catalog.AtPtr(i)->name.GetChars(),
968 listing_from_catalog.AtPtr(i)->name.GetLength());
969
970 catalog::DirectoryEntry entry_dirent;
971 if (!GetDirentForPath(entry_path, &entry_dirent)) {
972 LogCvmfs(kLogCvmfs, kLogDebug, "listing entry %s vanished, skipping",
973 entry_path.c_str());
974 continue;
975 }
976
977 struct stat fixed_info = listing_from_catalog.AtPtr(i)->info;
978 fixed_info.st_ino = entry_dirent.inode();
979 AddToDirListing(req, listing_from_catalog.AtPtr(i)->name.c_str(),
980 &fixed_info, &fuse_listing);
981 }
982 fuse_remounter_->fence()->Leave();
983
984 DirectoryListing stream_listing;
985 stream_listing.size = fuse_listing.size();
986 stream_listing.capacity = fuse_listing.capacity();
987 bool large_alloc;
988 fuse_listing.ShareBuffer(&stream_listing.buffer, &large_alloc);
989 if (large_alloc)
990 stream_listing.capacity = 0;
991
992 // Save the directory listing and return a handle to the listing
993 {
994 MutexLockGuard m(&lock_directory_handles_);
995 LogCvmfs(kLogCvmfs, kLogDebug,
996 "linking directory handle %lu to dir inode: %" PRIu64,
997 next_directory_handle_, uint64_t(ino));
998 (*directory_handles_)[next_directory_handle_] = stream_listing;
999 fi->fh = next_directory_handle_;
1000 ++next_directory_handle_;
1001 }
1002 perf::Inc(file_system_->n_fs_dir_open());
1003 perf::Inc(file_system_->no_open_dirs());
1004
1005 #if (FUSE_VERSION >= 30)
1006 #ifdef CVMFS_ENABLE_FUSE3_CACHE_READDIR
1007 // This affects only reads on the same open directory handle (e.g. multiple
1008 // reads with rewinddir() between them). A new opendir on the same directory
1009 // will trigger readdir calls independently of this setting.
1010 fi->cache_readdir = 1;
1011 #endif
1012 #endif
1013 fuse_reply_open(req, fi);
1014 }
1015
1016
1017 /**
1018 * Release a directory.
1019 */
1020 static void cvmfs_releasedir(fuse_req_t req, fuse_ino_t ino,
1021 struct fuse_file_info *fi)
1022 {
1023 HighPrecisionTimer guard_timer(file_system_->hist_fs_releasedir());
1024
1025 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1026 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_releasedir on inode %" PRIu64
1027 ", handle %lu", uint64_t(ino), fi->fh);
1028
1029 int reply = 0;
1030
1031 {
1032 MutexLockGuard m(&lock_directory_handles_);
1033 DirectoryHandles::iterator iter_handle = directory_handles_->find(fi->fh);
1034 if (iter_handle != directory_handles_->end()) {
1035 if (iter_handle->second.capacity == 0)
1036 smunmap(iter_handle->second.buffer);
1037 else
1038 free(iter_handle->second.buffer);
1039 directory_handles_->erase(iter_handle);
1040 perf::Dec(file_system_->no_open_dirs());
1041 } else {
1042 reply = EINVAL;
1043 }
1044 }
1045
1046 fuse_reply_err(req, reply);
1047 }
1048
1049
1050 /**
1051 * Very large directory listings have to be sent in slices.
1052 */
1053 static void ReplyBufferSlice(const fuse_req_t req, const char *buffer,
1054 const size_t buffer_size, const off_t offset,
1055 const size_t max_size)
1056 {
1057 if (offset < static_cast<int>(buffer_size)) {
1058 fuse_reply_buf(req, buffer + offset,
1059 std::min(static_cast<size_t>(buffer_size - offset), max_size));
1060 } else {
1061 fuse_reply_buf(req, NULL, 0);
1062 }
1063 }
1064
1065
1066 /**
1067 * Read the directory listing.
1068 */
1069 static void cvmfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
1070 off_t off, struct fuse_file_info *fi)
1071 {
1072 HighPrecisionTimer guard_timer(file_system_->hist_fs_readdir());
1073
1074 LogCvmfs(kLogCvmfs, kLogDebug,
1075 "cvmfs_readdir on inode %" PRIu64 " reading %lu bytes from offset %ld",
1076 static_cast<uint64_t>(mount_point_->catalog_mgr()->MangleInode(ino)),
1077 size, off);
1078
1079 DirectoryListing listing;
1080
1081 MutexLockGuard m(&lock_directory_handles_);
1082 DirectoryHandles::const_iterator iter_handle =
1083 directory_handles_->find(fi->fh);
1084 if (iter_handle != directory_handles_->end()) {
1085 listing = iter_handle->second;
1086
1087 ReplyBufferSlice(req, listing.buffer, listing.size, off, size);
1088 return;
1089 }
1090
1091 fuse_reply_err(req, EINVAL);
1092 }
1093
1094 static void FillOpenFlags(const glue::PageCacheTracker::OpenDirectives od,
1095 struct fuse_file_info *fi)
1096 {
1097 assert(!TestBit(glue::PageCacheTracker::kBitDirectIo, fi->fh));
1098 fi->keep_cache = od.keep_cache;
1099 fi->direct_io = od.direct_io;
1100 if (fi->direct_io)
1101 SetBit(glue::PageCacheTracker::kBitDirectIo, &fi->fh);
1102 }
1103
1104
1105 #ifdef __APPLE__
1106 // On macOS, xattr on a symlink opens and closes the file (with O_SYMLINK)
1107 // around the actual getxattr call. In order to not run into an I/O error
1108 // we use a special file handle for symlinks, from which one cannot read.
1109 static const uint64_t kFileHandleIgnore = static_cast<uint64_t>(2) << 60;
1110 #endif
1111
1112 /**
1113 * Open a file from cache. If necessary, file is downloaded first.
1114 *
1115 * \return Read-only file descriptor in fi->fh or kChunkedFileHandle for
1116 * chunked files
1117 */
1118 static void cvmfs_open(fuse_req_t req, fuse_ino_t ino,
1119 struct fuse_file_info *fi)
1120 {
1121 HighPrecisionTimer guard_timer(file_system_->hist_fs_open());
1122
1123 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1124 FuseInterruptCue ic(&req);
1125 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
1126 fuse_remounter_->fence()->Enter();
1127 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1128 ino = catalog_mgr->MangleInode(ino);
1129 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_open on inode: %" PRIu64,
1130 uint64_t(ino));
1131
1132 int fd = -1;
1133 catalog::DirectoryEntry dirent;
1134 PathString path;
1135
1136 bool found = GetPathForInode(ino, &path);
1137 if (!found) {
1138 fuse_remounter_->fence()->Leave();
1139 fuse_reply_err(req, ENOENT);
1140 return;
1141 }
1142 found = GetDirentForInode(ino, &dirent);
1143 if (!found) {
1144 fuse_remounter_->fence()->Leave();
1145 ReplyNegative(dirent, req);
1146 return;
1147 }
1148
1149 if (!CheckVoms(*fuse_ctx)) {
1150 fuse_remounter_->fence()->Leave();
1151 fuse_reply_err(req, EACCES);
1152 return;
1153 }
1154
1155 mount_point_->tracer()->Trace(Tracer::kEventOpen, path, "open()");
1156 // Don't check. Either done by the OS or one wants to purposefully work
1157 // around wrong open flags
1158 // if ((fi->flags & 3) != O_RDONLY) {
1159 // fuse_reply_err(req, EROFS);
1160 // return;
1161 // }
1162 #ifdef __APPLE__
1163 if ((fi->flags & O_SHLOCK) || (fi->flags & O_EXLOCK)) {
1164 fuse_remounter_->fence()->Leave();
1165 fuse_reply_err(req, EOPNOTSUPP);
1166 return;
1167 }
1168 if (fi->flags & O_SYMLINK) {
1169 fuse_remounter_->fence()->Leave();
1170 fi->fh = kFileHandleIgnore;
1171 fuse_reply_open(req, fi);
1172 return;
1173 }
1174 #endif
1175 if (fi->flags & O_EXCL) {
1176 fuse_remounter_->fence()->Leave();
1177 fuse_reply_err(req, EEXIST);
1178 return;
1179 }
1180
1181 perf::Inc(file_system_->n_fs_open()); // Count actual open / fetch operations
1182
1183 glue::PageCacheTracker::OpenDirectives open_directives;
1184 if (!dirent.IsChunkedFile()) {
1185 if (dirent.IsDirectIo()) {
1186 open_directives = mount_point_->page_cache_tracker()->OpenDirect();
1187 } else {
1188 open_directives =
1189 mount_point_->page_cache_tracker()->Open(
1190 ino, dirent.checksum(), dirent.GetStatStructure());
1191 }
1192 fuse_remounter_->fence()->Leave();
1193 } else {
1194 LogCvmfs(kLogCvmfs, kLogDebug,
1195 "chunked file %s opened (download delayed to read() call)",
1196 path.c_str());
1197
1198 if (!IncAndCheckNoOpenFiles()) {
1199 perf::Dec(file_system_->no_open_files());
1200 fuse_remounter_->fence()->Leave();
1201 LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
1202 fuse_reply_err(req, EMFILE);
1203 perf::Inc(file_system_->n_emfile());
1204 return;
1205 }
1206
1207 // Figure out unique inode from annotated catalog
1208 // TODO(jblomer): we only need to lookup if the inode is not from the
1209 // current generation
1210 catalog::DirectoryEntry dirent_origin;
1211 if (!catalog_mgr->LookupPath(path, catalog::kLookupDefault,
1212 &dirent_origin)) {
1213 fuse_remounter_->fence()->Leave();
1214 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1215 "chunked file %s vanished unexpectedly", path.c_str());
1216 fuse_reply_err(req, ENOENT);
1217 return;
1218 }
1219 const uint64_t unique_inode = dirent_origin.inode();
1220
1221 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1222 chunk_tables->Lock();
1223 if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
1224 chunk_tables->Unlock();
1225
1226 // Retrieve File chunks from the catalog
1227 UniquePtr<FileChunkList> chunks(new FileChunkList());
1228 if (!catalog_mgr->ListFileChunks(path, dirent.hash_algorithm(),
1229 chunks.weak_ref()) ||
1230 chunks->IsEmpty())
1231 {
1232 fuse_remounter_->fence()->Leave();
1233 LogCvmfs(kLogCvmfs, kLogDebug| kLogSyslogErr,
1234 "EIO (04) file %s is marked as 'chunked', but no chunks found.",
1235 path.c_str());
1236 perf::Inc(file_system_->n_eio_total());
1237 perf::Inc(file_system_->n_eio_04());
1238 fuse_reply_err(req, EIO);
1239 return;
1240 }
1241 fuse_remounter_->fence()->Leave();
1242
1243 chunk_tables->Lock();
1244 // Check again to avoid race
1245 if (!chunk_tables->inode2chunks.Contains(unique_inode)) {
1246 chunk_tables->inode2chunks.Insert(
1247 unique_inode, FileChunkReflist(chunks.Release(), path,
1248 dirent.compression_algorithm(),
1249 dirent.IsExternalFile()));
1250 chunk_tables->inode2references.Insert(unique_inode, 1);
1251 } else {
1252 uint32_t refctr;
1253 bool retval =
1254 chunk_tables->inode2references.Lookup(unique_inode, &refctr);
1255 assert(retval);
1256 chunk_tables->inode2references.Insert(unique_inode, refctr+1);
1257 }
1258 } else {
1259 fuse_remounter_->fence()->Leave();
1260 uint32_t refctr;
1261 bool retval =
1262 chunk_tables->inode2references.Lookup(unique_inode, &refctr);
1263 assert(retval);
1264 chunk_tables->inode2references.Insert(unique_inode, refctr+1);
1265 }
1266
1267 // Update the chunk handle list
1268 LogCvmfs(kLogCvmfs, kLogDebug,
1269 "linking chunk handle %lu to unique inode: %" PRIu64,
1270 chunk_tables->next_handle, uint64_t(unique_inode));
1271 chunk_tables->handle2fd.Insert(chunk_tables->next_handle, ChunkFd());
1272 chunk_tables->handle2uniqino.Insert(chunk_tables->next_handle,
1273 unique_inode);
1274
1275 // Generate artificial content hash as hash over chunk hashes
1276 // TODO(jblomer): we may want to cache the result in the chunk tables
1277 FileChunkReflist chunk_reflist;
1278 bool retval =
1279 chunk_tables->inode2chunks.Lookup(unique_inode, &chunk_reflist);
1280 assert(retval);
1281
1282 fi->fh = chunk_tables->next_handle;
1283 if (dirent.IsDirectIo()) {
1284 open_directives = mount_point_->page_cache_tracker()->OpenDirect();
1285 } else {
1286 open_directives = mount_point_->page_cache_tracker()->Open(
1287 ino, chunk_reflist.HashChunkList(), dirent.GetStatStructure());
1288 }
1289 FillOpenFlags(open_directives, fi);
1290 fi->fh = static_cast<uint64_t>(-static_cast<int64_t>(fi->fh));
1291 ++chunk_tables->next_handle;
1292 chunk_tables->Unlock();
1293
1294 fuse_reply_open(req, fi);
1295 return;
1296 }
1297
1298 Fetcher *this_fetcher = dirent.IsExternalFile()
1299 ? mount_point_->external_fetcher()
1300 : mount_point_->fetcher();
1301 CacheManager::Label label;
1302 label.path = path.ToString();
1303 label.size = dirent.size();
1304 label.zip_algorithm = dirent.compression_algorithm();
1305 if (mount_point_->catalog_mgr()->volatile_flag())
1306 label.flags |= CacheManager::kLabelVolatile;
1307 if (dirent.IsExternalFile())
1308 label.flags |= CacheManager::kLabelExternal;
1309 fd =
1310 this_fetcher->Fetch(CacheManager::LabeledObject(dirent.checksum(), label));
1311
1312 if (fd >= 0) {
1313 if (IncAndCheckNoOpenFiles()) {
1314 LogCvmfs(kLogCvmfs, kLogDebug, "file %s opened (fd %d)",
1315 path.c_str(), fd);
1316 fi->fh = fd;
1317 FillOpenFlags(open_directives, fi);
1318 fuse_reply_open(req, fi);
1319 return;
1320 } else {
1321 if (file_system_->cache_mgr()->Close(fd) == 0)
1322 perf::Dec(file_system_->no_open_files());
1323 LogCvmfs(kLogCvmfs, kLogSyslogErr, "open file descriptor limit exceeded");
1324 // not returning an fd, so close the page cache tracker entry if required
1325 if (!dirent.IsDirectIo()) {
1326 fuse_remounter_->fence()->Enter();
1327 mount_point_->page_cache_tracker()->Close(ino);
1328 fuse_remounter_->fence()->Leave();
1329 }
1330 fuse_reply_err(req, EMFILE);
1331 perf::Inc(file_system_->n_emfile());
1332 return;
1333 }
1334 assert(false);
1335 }
1336
1337 // fd < 0
1338 // the download has failed. Close the page cache tracker entry if required
1339 if (!dirent.IsDirectIo()) {
1340 fuse_remounter_->fence()->Enter();
1341 mount_point_->page_cache_tracker()->Close(ino);
1342 fuse_remounter_->fence()->Leave();
1343 }
1344
1345 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1346 "failed to open inode: %" PRIu64 ", CAS key %s, error code %d",
1347 uint64_t(ino), dirent.checksum().ToString().c_str(), errno);
1348 if (errno == EMFILE) {
1349 fuse_reply_err(req, EMFILE);
1350 perf::Inc(file_system_->n_emfile());
1351 return;
1352 }
1353
1354 mount_point_->backoff_throttle()->Throttle();
1355
1356 mount_point_->file_system()->io_error_info()->AddIoError();
1357 if (EIO == errno || EIO == -fd) {
1358 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1359 "EIO (06) on %s", path.c_str() );
1360 perf::Inc(file_system_->n_eio_total());
1361 perf::Inc(file_system_->n_eio_06());
1362 }
1363
1364 fuse_reply_err(req, -fd);
1365 }
1366
1367
1368 /**
1369 * Redirected to pread into cache.
1370 */
1371 static void cvmfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
1372 struct fuse_file_info *fi)
1373 {
1374 HighPrecisionTimer guard_timer(file_system_->hist_fs_read());
1375
1376 LogCvmfs(kLogCvmfs, kLogDebug,
1377 "cvmfs_read inode: %" PRIu64 " reading %lu bytes from offset %ld "
1378 "fd %lu", uint64_t(mount_point_->catalog_mgr()->MangleInode(ino)),
1379 size, off, fi->fh);
1380 perf::Inc(file_system_->n_fs_read());
1381
1382 #ifdef __APPLE__
1383 if (fi->fh == kFileHandleIgnore) {
1384 fuse_reply_err(req, EBADF);
1385 return;
1386 }
1387 #endif
1388
1389 // Get data chunk (<=128k guaranteed by Fuse)
1390 char *data = static_cast<char *>(alloca(size));
1391 unsigned int overall_bytes_fetched = 0;
1392
1393 int64_t fd = static_cast<int64_t>(fi->fh);
1394 uint64_t abs_fd = (fd < 0) ? -fd : fd;
1395 ClearBit(glue::PageCacheTracker::kBitDirectIo, &abs_fd);
1396
1397 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1398 FuseInterruptCue ic(&req);
1399 const ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
1400
1401 // Do we have a a chunked file?
1402 if (fd < 0) {
1403 const uint64_t chunk_handle = abs_fd;
1404 uint64_t unique_inode;
1405 ChunkFd chunk_fd;
1406 FileChunkReflist chunks;
1407 bool retval;
1408
1409 // Fetch unique inode, chunk list and file descriptor
1410 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1411 chunk_tables->Lock();
1412 retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1413 if (!retval) {
1414 LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1415 unique_inode = ino;
1416 }
1417 retval = chunk_tables->inode2chunks.Lookup(unique_inode, &chunks);
1418 assert(retval);
1419 chunk_tables->Unlock();
1420
1421 unsigned chunk_idx = chunks.FindChunkIdx(off);
1422
1423 // Lock chunk handle
1424 pthread_mutex_t *handle_lock = chunk_tables->Handle2Lock(chunk_handle);
1425 MutexLockGuard m(handle_lock);
1426 chunk_tables->Lock();
1427 retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1428 assert(retval);
1429 chunk_tables->Unlock();
1430
1431 // Fetch all needed chunks and read the requested data
1432 off_t offset_in_chunk = off - chunks.list->AtPtr(chunk_idx)->offset();
1433 do {
1434 // Open file descriptor to chunk
1435 if ((chunk_fd.fd == -1) || (chunk_fd.chunk_idx != chunk_idx)) {
1436 if (chunk_fd.fd != -1) file_system_->cache_mgr()->Close(chunk_fd.fd);
1437 Fetcher *this_fetcher = chunks.external_data
1438 ? mount_point_->external_fetcher()
1439 : mount_point_->fetcher();
1440 CacheManager::Label label;
1441 label.path = chunks.path.ToString();
1442 label.size = chunks.list->AtPtr(chunk_idx)->size();
1443 label.zip_algorithm = chunks.compression_alg;
1444 label.flags |= CacheManager::kLabelChunked;
1445 if (mount_point_->catalog_mgr()->volatile_flag())
1446 label.flags |= CacheManager::kLabelVolatile;
1447 if (chunks.external_data) {
1448 label.flags |= CacheManager::kLabelExternal;
1449 label.range_offset = chunks.list->AtPtr(chunk_idx)->offset();
1450 }
1451 chunk_fd.fd = this_fetcher->Fetch(CacheManager::LabeledObject(
1452 chunks.list->AtPtr(chunk_idx)->content_hash(), label));
1453 if (chunk_fd.fd < 0) {
1454 chunk_fd.fd = -1;
1455 chunk_tables->Lock();
1456 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1457 chunk_tables->Unlock();
1458
1459 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1460 "EIO (05) on %s", chunks.path.ToString().c_str() );
1461 perf::Inc(file_system_->n_eio_total());
1462 perf::Inc(file_system_->n_eio_05());
1463 fuse_reply_err(req, EIO);
1464 return;
1465 }
1466 chunk_fd.chunk_idx = chunk_idx;
1467 }
1468
1469 LogCvmfs(kLogCvmfs, kLogDebug, "reading from chunk fd %d",
1470 chunk_fd.fd);
1471 // Read data from chunk
1472 const size_t bytes_to_read = size - overall_bytes_fetched;
1473 const size_t remaining_bytes_in_chunk =
1474 chunks.list->AtPtr(chunk_idx)->size() - offset_in_chunk;
1475 size_t bytes_to_read_in_chunk =
1476 std::min(bytes_to_read, remaining_bytes_in_chunk);
1477 const int64_t bytes_fetched = file_system_->cache_mgr()->Pread(
1478 chunk_fd.fd,
1479 data + overall_bytes_fetched,
1480 bytes_to_read_in_chunk,
1481 offset_in_chunk);
1482
1483 if (bytes_fetched < 0) {
1484 LogCvmfs(kLogCvmfs, kLogSyslogErr, "read err no %" PRId64 " (%s)",
1485 bytes_fetched, chunks.path.ToString().c_str());
1486 chunk_tables->Lock();
1487 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1488 chunk_tables->Unlock();
1489 if ( EIO == errno || EIO == -bytes_fetched ) {
1490 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1491 "EIO (07) on %s", chunks.path.ToString().c_str() );
1492 perf::Inc(file_system_->n_eio_total());
1493 perf::Inc(file_system_->n_eio_07());
1494 }
1495 fuse_reply_err(req, -bytes_fetched);
1496 return;
1497 }
1498 overall_bytes_fetched += bytes_fetched;
1499
1500 // Proceed to the next chunk to keep on reading data
1501 ++chunk_idx;
1502 offset_in_chunk = 0;
1503 } while ((overall_bytes_fetched < size) &&
1504 (chunk_idx < chunks.list->size()));
1505
1506 // Update chunk file descriptor
1507 chunk_tables->Lock();
1508 chunk_tables->handle2fd.Insert(chunk_handle, chunk_fd);
1509 chunk_tables->Unlock();
1510 LogCvmfs(kLogCvmfs, kLogDebug, "released chunk file descriptor %d",
1511 chunk_fd.fd);
1512 } else {
1513 int64_t nbytes = file_system_->cache_mgr()->Pread(abs_fd, data, size, off);
1514 if (nbytes < 0) {
1515 if ( EIO == errno || EIO == -nbytes ) {
1516 PathString path;
1517 bool found = GetPathForInode(ino, &path);
1518 if ( found ) {
1519 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1520 "EIO (08) on %s", path.ToString().c_str() );
1521 } else {
1522 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
1523 "EIO (08) on <unknown inode>");
1524 }
1525 perf::Inc(file_system_->n_eio_total());
1526 perf::Inc(file_system_->n_eio_08());
1527 }
1528 fuse_reply_err(req, -nbytes);
1529 return;
1530 }
1531 overall_bytes_fetched = nbytes;
1532 }
1533
1534 // Push it to user
1535 fuse_reply_buf(req, data, overall_bytes_fetched);
1536 LogCvmfs(kLogCvmfs, kLogDebug, "pushed %d bytes to user",
1537 overall_bytes_fetched);
1538 }
1539
1540
1541 /**
1542 * File close operation, redirected into cache.
1543 */
1544 static void cvmfs_release(fuse_req_t req, fuse_ino_t ino,
1545 struct fuse_file_info *fi)
1546 {
1547 HighPrecisionTimer guard_timer(file_system_->hist_fs_release());
1548
1549 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1550 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_release on inode: %" PRIu64,
1551 uint64_t(ino));
1552
1553 #ifdef __APPLE__
1554 if (fi->fh == kFileHandleIgnore) {
1555 fuse_reply_err(req, 0);
1556 return;
1557 }
1558 #endif
1559
1560 int64_t fd = static_cast<int64_t>(fi->fh);
1561 uint64_t abs_fd = (fd < 0) ? -fd : fd;
1562 if (!TestBit(glue::PageCacheTracker::kBitDirectIo, abs_fd)) {
1563 mount_point_->page_cache_tracker()->Close(ino);
1564 }
1565 ClearBit(glue::PageCacheTracker::kBitDirectIo, &abs_fd);
1566
1567 // do we have a chunked file?
1568 if (fd < 0) {
1569 const uint64_t chunk_handle = abs_fd;
1570 LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk handle %" PRIu64,
1571 chunk_handle);
1572 uint64_t unique_inode;
1573 ChunkFd chunk_fd;
1574 FileChunkReflist chunks;
1575 uint32_t refctr;
1576 bool retval;
1577
1578 ChunkTables *chunk_tables = mount_point_->chunk_tables();
1579 chunk_tables->Lock();
1580 retval = chunk_tables->handle2uniqino.Lookup(chunk_handle, &unique_inode);
1581 if (!retval) {
1582 LogCvmfs(kLogCvmfs, kLogDebug, "no unique inode, fall back to fuse ino");
1583 unique_inode = ino;
1584 } else {
1585 chunk_tables->handle2uniqino.Erase(chunk_handle);
1586 }
1587 retval = chunk_tables->handle2fd.Lookup(chunk_handle, &chunk_fd);
1588 assert(retval);
1589 chunk_tables->handle2fd.Erase(chunk_handle);
1590
1591 retval = chunk_tables->inode2references.Lookup(unique_inode, &refctr);
1592 assert(retval);
1593 refctr--;
1594 if (refctr == 0) {
1595 LogCvmfs(kLogCvmfs, kLogDebug, "releasing chunk list for inode %" PRIu64,
1596 uint64_t(unique_inode));
1597 FileChunkReflist to_delete;
1598 retval = chunk_tables->inode2chunks.Lookup(unique_inode, &to_delete);
1599 assert(retval);
1600 chunk_tables->inode2references.Erase(unique_inode);
1601 chunk_tables->inode2chunks.Erase(unique_inode);
1602 delete to_delete.list;
1603 } else {
1604 chunk_tables->inode2references.Insert(unique_inode, refctr);
1605 }
1606 chunk_tables->Unlock();
1607
1608 if (chunk_fd.fd != -1)
1609 file_system_->cache_mgr()->Close(chunk_fd.fd);
1610 perf::Dec(file_system_->no_open_files());
1611 } else {
1612 if (file_system_->cache_mgr()->Close(abs_fd) == 0) {
1613 perf::Dec(file_system_->no_open_files());
1614 }
1615 }
1616 fuse_reply_err(req, 0);
1617 }
1618
1619 /**
1620 * Returns information about a mounted filesystem. In this case it returns
1621 * information about the local cache occupancy of cvmfs.
1622 *
1623 * Note: If the elements of the struct statvfs *info are set to 0, it will cause
1624 * it to be ignored in commandline tool "df".
1625 */
1626 static void cvmfs_statfs(fuse_req_t req, fuse_ino_t ino) {
1627 ino = mount_point_->catalog_mgr()->MangleInode(ino);
1628 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_statfs on inode: %" PRIu64,
1629 uint64_t(ino));
1630
1631 TraceInode(Tracer::kEventStatFs, ino, "statfs()");
1632
1633 perf::Inc(file_system_->n_fs_statfs());
1634
1635 // Unmanaged cache (no lock needed - statfs is never modified)
1636 if (!file_system_->cache_mgr()->quota_mgr()->HasCapability(
1637 QuotaManager::kCapIntrospectSize))
1638 {
1639 LogCvmfs(kLogCvmfs, kLogDebug, "QuotaManager does not support statfs");
1640 fuse_reply_statfs(req, (mount_point_->statfs_cache()->info()));
1641 return;
1642 }
1643
1644 MutexLockGuard m(mount_point_->statfs_cache()->lock());
1645
1646 const uint64_t deadline = *mount_point_->statfs_cache()->expiry_deadline();
1647 struct statvfs *info = mount_point_->statfs_cache()->info();
1648
1649 // cached version still valid
1650 if ( platform_monotonic_time() < deadline ) {
1651 perf::Inc(file_system_->n_fs_statfs_cached());
1652 fuse_reply_statfs(req, info);
1653 return;
1654 }
1655
1656 uint64_t available = 0;
1657 uint64_t size = file_system_->cache_mgr()->quota_mgr()->GetSize();
1658 uint64_t capacity = file_system_->cache_mgr()->quota_mgr()->GetCapacity();
1659 // Fuse/OS X doesn't like values < 512
1660 info->f_bsize = info->f_frsize = 512;
1661
1662 if (capacity == (uint64_t)(-1)) {
1663 // Unknown capacity, set capacity = size
1664 info->f_blocks = size / info->f_bsize;
1665 } else {
1666 // Take values from LRU module
1667 info->f_blocks = capacity / info->f_bsize;
1668 available = capacity - size;
1669 }
1670
1671 info->f_bfree = info->f_bavail = available / info->f_bsize;
1672
1673 // Inodes / entries
1674 fuse_remounter_->fence()->Enter();
1675 uint64_t all_inodes = mount_point_->catalog_mgr()->all_inodes();
1676 uint64_t loaded_inode = mount_point_->catalog_mgr()->loaded_inodes();
1677 info->f_files = all_inodes;
1678 info->f_ffree = info->f_favail = all_inodes - loaded_inode;
1679 fuse_remounter_->fence()->Leave();
1680
1681 *mount_point_->statfs_cache()->expiry_deadline() =
1682 platform_monotonic_time()
1683 + mount_point_->statfs_cache()->cache_timeout();
1684
1685 fuse_reply_statfs(req, info);
1686 }
1687
1688 #ifdef __APPLE__
1689 static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1690 size_t size, uint32_t position)
1691 #else
1692 static void cvmfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
1693 size_t size)
1694 #endif
1695 {
1696 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1697 FuseInterruptCue ic(&req);
1698 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
1699
1700 fuse_remounter_->fence()->Enter();
1701 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1702 ino = catalog_mgr->MangleInode(ino);
1703 LogCvmfs(kLogCvmfs, kLogDebug,
1704 "cvmfs_getxattr on inode: %" PRIu64 " for xattr: %s",
1705 uint64_t(ino), name);
1706 if (!CheckVoms(*fuse_ctx)) {
1707 fuse_remounter_->fence()->Leave();
1708 fuse_reply_err(req, EACCES);
1709 return;
1710 }
1711 TraceInode(Tracer::kEventGetXAttr, ino, "getxattr()");
1712
1713 vector<string> tokens_mode_machine = SplitString(name, '~');
1714 vector<string> tokens_mode_human = SplitString(name, '@');
1715
1716 int32_t attr_req_page = 0;
1717 MagicXattrMode xattr_mode = kXattrMachineMode;
1718 string attr;
1719
1720 bool attr_req_is_valid = false;
1721 const sanitizer::PositiveIntegerSanitizer page_num_sanitizer;
1722
1723 if (tokens_mode_human.size() > 1) {
1724 const std::string token = tokens_mode_human[tokens_mode_human.size() - 1];
1725 if (token == "?") {
1726 attr_req_is_valid = true;
1727 attr_req_page = -1;
1728 } else {
1729 if (page_num_sanitizer.IsValid(token)) {
1730 attr_req_is_valid = true;
1731 attr_req_page = static_cast<int32_t>(String2Uint64(token));
1732 }
1733 }
1734 xattr_mode = kXattrHumanMode;
1735 attr = tokens_mode_human[0];
1736 } else if (tokens_mode_machine.size() > 1) {
1737 const std::string token =
1738 tokens_mode_machine[tokens_mode_machine.size() - 1];
1739 if (token == "?") {
1740 attr_req_is_valid = true;
1741 attr_req_page = -1;
1742 } else {
1743 if (page_num_sanitizer.IsValid(token)) {
1744 attr_req_is_valid = true;
1745 attr_req_page = static_cast<int32_t>(String2Uint64(token));
1746 }
1747 }
1748 xattr_mode = kXattrMachineMode;
1749 attr = tokens_mode_machine[0];
1750
1751 } else {
1752 attr_req_is_valid = true;
1753 attr = tokens_mode_machine[0];
1754 }
1755
1756 if (!attr_req_is_valid) {
1757 fuse_remounter_->fence()->Leave();
1758 fuse_reply_err(req, ENODATA);
1759 return;
1760 }
1761
1762 catalog::DirectoryEntry d;
1763 const bool found = GetDirentForInode(ino, &d);
1764
1765 if (!found) {
1766 fuse_remounter_->fence()->Leave();
1767 ReplyNegative(d, req);
1768 return;
1769 }
1770
1771 bool retval;
1772 XattrList xattrs;
1773 PathString path;
1774 retval = GetPathForInode(ino, &path);
1775
1776 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1777 "cvmfs_statfs: Race condition? "
1778 "GetPathForInode did not succeed for path %s "
1779 "(path might have not been set)",
1780 path.c_str())) {
1781 fuse_remounter_->fence()->Leave();
1782 fuse_reply_err(req, ESTALE);
1783 return;
1784 }
1785
1786 if (d.IsLink()) {
1787 catalog::LookupOptions lookup_options = static_cast<catalog::LookupOptions>(
1788 catalog::kLookupDefault | catalog::kLookupRawSymlink);
1789 catalog::DirectoryEntry raw_symlink;
1790 retval = catalog_mgr->LookupPath(path, lookup_options, &raw_symlink);
1791
1792 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1793 "cvmfs_statfs: Race condition? "
1794 "LookupPath did not succeed for path %s",
1795 path.c_str())) {
1796 fuse_remounter_->fence()->Leave();
1797 fuse_reply_err(req, ESTALE);
1798 return;
1799 }
1800
1801 d.set_symlink(raw_symlink.symlink());
1802 }
1803 if (d.HasXattrs()) {
1804 retval = catalog_mgr->LookupXattrs(path, &xattrs);
1805
1806 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1807 "cvmfs_statfs: Race condition? "
1808 "LookupXattrs did not succeed for path %s",
1809 path.c_str())) {
1810 fuse_remounter_->fence()->Leave();
1811 fuse_reply_err(req, ESTALE);
1812 return;
1813 }
1814 }
1815
1816 bool magic_xattr_success = true;
1817 MagicXattrRAIIWrapper magic_xattr(mount_point_->magic_xattr_mgr()->GetLocked(
1818 attr, path, &d));
1819 if (!magic_xattr.IsNull()) {
1820 magic_xattr_success = magic_xattr->
1821 PrepareValueFencedProtected(fuse_ctx->gid);
1822 }
1823
1824 fuse_remounter_->fence()->Leave();
1825
1826 if (!magic_xattr_success) {
1827 fuse_reply_err(req, ENOATTR);
1828 return;
1829 }
1830
1831 std::pair<bool, std::string> attribute_result;
1832
1833 if (!magic_xattr.IsNull()) {
1834 attribute_result = magic_xattr->GetValue(attr_req_page, xattr_mode);
1835 } else {
1836 if (!xattrs.Get(attr, &attribute_result.second)) {
1837 fuse_reply_err(req, ENOATTR);
1838 return;
1839 }
1840 attribute_result.first = true;
1841 }
1842
1843 if (!attribute_result.first) {
1844 fuse_reply_err(req, ENODATA);
1845 } else if (size == 0) {
1846 fuse_reply_xattr(req, attribute_result.second.length());
1847 } else if (size >= attribute_result.second.length()) {
1848 fuse_reply_buf(req, &attribute_result.second[0],
1849 attribute_result.second.length());
1850 } else {
1851 fuse_reply_err(req, ERANGE);
1852 }
1853 }
1854
1855
1856 static void cvmfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
1857 const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
1858 FuseInterruptCue ic(&req);
1859 ClientCtxGuard ctx_guard(fuse_ctx->uid, fuse_ctx->gid, fuse_ctx->pid, &ic);
1860
1861 fuse_remounter_->fence()->Enter();
1862 catalog::ClientCatalogManager *catalog_mgr = mount_point_->catalog_mgr();
1863 ino = catalog_mgr->MangleInode(ino);
1864 TraceInode(Tracer::kEventListAttr, ino, "listxattr()");
1865 LogCvmfs(kLogCvmfs, kLogDebug,
1866 "cvmfs_listxattr on inode: %" PRIu64 ", size %zu [visibility %d]",
1867 uint64_t(ino), size,
1868 mount_point_->magic_xattr_mgr()->visibility());
1869
1870 catalog::DirectoryEntry d;
1871 const bool found = GetDirentForInode(ino, &d);
1872 XattrList xattrs;
1873 if (d.HasXattrs()) {
1874 PathString path;
1875 bool retval = GetPathForInode(ino, &path);
1876
1877 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1878 "cvmfs_listxattr: Race condition? "
1879 "GetPathForInode did not succeed for ino %lu",
1880 ino)) {
1881 fuse_remounter_->fence()->Leave();
1882 fuse_reply_err(req, ESTALE);
1883 return;
1884 }
1885
1886 retval = catalog_mgr->LookupXattrs(path, &xattrs);
1887 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
1888 "cvmfs_listxattr: Race condition? "
1889 "LookupXattrs did not succeed for ino %lu",
1890 ino)) {
1891 fuse_remounter_->fence()->Leave();
1892 fuse_reply_err(req, ESTALE);
1893 return;
1894 }
1895 }
1896 fuse_remounter_->fence()->Leave();
1897
1898 if (!found) {
1899 ReplyNegative(d, req);
1900 return;
1901 }
1902
1903 string attribute_list;
1904 attribute_list = mount_point_->magic_xattr_mgr()->GetListString(&d);
1905 attribute_list += xattrs.ListKeysPosix(attribute_list);
1906
1907 if (size == 0) {
1908 fuse_reply_xattr(req, attribute_list.length());
1909 } else if (size >= attribute_list.length()) {
1910 if (attribute_list.empty())
1911 fuse_reply_buf(req, NULL, 0);
1912 else
1913 fuse_reply_buf(req, &attribute_list[0], attribute_list.length());
1914 } else {
1915 fuse_reply_err(req, ERANGE);
1916 }
1917 }
1918
1919 bool Evict(const string &path) {
1920 catalog::DirectoryEntry dirent;
1921 fuse_remounter_->fence()->Enter();
1922 const bool found = (GetDirentForPath(PathString(path), &dirent) > 0);
1923
1924 if (!found || !dirent.IsRegular()) {
1925 fuse_remounter_->fence()->Leave();
1926 return false;
1927 }
1928
1929 if (!dirent.IsChunkedFile()) {
1930 fuse_remounter_->fence()->Leave();
1931 } else {
1932 FileChunkList chunks;
1933 mount_point_->catalog_mgr()->ListFileChunks(
1934 PathString(path), dirent.hash_algorithm(), &chunks);
1935 fuse_remounter_->fence()->Leave();
1936 for (unsigned i = 0; i < chunks.size(); ++i) {
1937 file_system_->cache_mgr()->quota_mgr()
1938 ->Remove(chunks.AtPtr(i)->content_hash());
1939 }
1940 }
1941 file_system_->cache_mgr()->quota_mgr()->Remove(dirent.checksum());
1942 return true;
1943 }
1944
1945
1946 bool Pin(const string &path) {
1947 catalog::DirectoryEntry dirent;
1948 fuse_remounter_->fence()->Enter();
1949 const bool found = (GetDirentForPath(PathString(path), &dirent) > 0);
1950 if (!found || !dirent.IsRegular()) {
1951 fuse_remounter_->fence()->Leave();
1952 return false;
1953 }
1954
1955 Fetcher *this_fetcher = dirent.IsExternalFile()
1956 ? mount_point_->external_fetcher()
1957 : mount_point_->fetcher();
1958
1959 if (!dirent.IsChunkedFile()) {
1960 fuse_remounter_->fence()->Leave();
1961 } else {
1962 FileChunkList chunks;
1963 mount_point_->catalog_mgr()->ListFileChunks(
1964 PathString(path), dirent.hash_algorithm(), &chunks);
1965 fuse_remounter_->fence()->Leave();
1966 for (unsigned i = 0; i < chunks.size(); ++i) {
1967 bool retval =
1968 file_system_->cache_mgr()->quota_mgr()->Pin(
1969 chunks.AtPtr(i)->content_hash(),
1970 chunks.AtPtr(i)->size(),
1971 "Part of " + path,
1972 false);
1973 if (!retval)
1974 return false;
1975 int fd = -1;
1976 CacheManager::Label label;
1977 label.path = path;
1978 label.size = chunks.AtPtr(i)->size();
1979 label.zip_algorithm = dirent.compression_algorithm();
1980 label.flags |= CacheManager::kLabelPinned;
1981 label.flags |= CacheManager::kLabelChunked;
1982 if (dirent.IsExternalFile()) {
1983 label.flags |= CacheManager::kLabelExternal;
1984 label.range_offset = chunks.AtPtr(i)->offset();
1985 }
1986 fd = this_fetcher->Fetch(CacheManager::LabeledObject(
1987 chunks.AtPtr(i)->content_hash(), label));
1988 if (fd < 0) {
1989 return false;
1990 }
1991 file_system_->cache_mgr()->Close(fd);
1992 }
1993 return true;
1994 }
1995
1996 bool retval = file_system_->cache_mgr()->quota_mgr()->Pin(
1997 dirent.checksum(), dirent.size(), path, false);
1998 if (!retval)
1999 return false;
2000 CacheManager::Label label;
2001 label.flags = CacheManager::kLabelPinned;
2002 label.size = dirent.size();
2003 label.path = path;
2004 label.zip_algorithm = dirent.compression_algorithm();
2005 int fd = this_fetcher->Fetch(
2006 CacheManager::LabeledObject(dirent.checksum(), label));
2007 if (fd < 0) {
2008 return false;
2009 }
2010 file_system_->cache_mgr()->Close(fd);
2011 return true;
2012 }
2013
2014
2015 /**
2016 * Do after-daemon() initialization
2017 */
2018 static void cvmfs_init(void *userdata, struct fuse_conn_info *conn) {
2019 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_init");
2020
2021 // NFS support
2022 #ifdef CVMFS_NFS_SUPPORT
2023 conn->want |= FUSE_CAP_EXPORT_SUPPORT;
2024 #endif
2025
2026 if (mount_point_->enforce_acls()) {
2027 #ifdef FUSE_CAP_POSIX_ACL
2028 if ((conn->capable & FUSE_CAP_POSIX_ACL) == 0) {
2029 PANIC(kLogDebug | kLogSyslogErr,
2030 "FUSE: ACL support requested but missing fuse kernel support, "
2031 "aborting");
2032 }
2033 conn->want |= FUSE_CAP_POSIX_ACL;
2034 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslog, "enforcing ACLs");
2035 #else
2036 PANIC(kLogDebug | kLogSyslogErr,
2037 "FUSE: ACL support requested but not available in this version of "
2038 "libfuse %d, aborting", FUSE_VERSION);
2039 #endif
2040 }
2041
2042 if ( mount_point_->cache_symlinks() ) {
2043 #ifdef FUSE_CAP_CACHE_SYMLINKS
2044 if ((conn->capable & FUSE_CAP_CACHE_SYMLINKS) == FUSE_CAP_CACHE_SYMLINKS) {
2045 conn->want |= FUSE_CAP_CACHE_SYMLINKS;
2046 LogCvmfs(kLogCvmfs, kLogDebug, "FUSE: Enable symlink caching");
2047 #ifndef FUSE_CAP_EXPIRE_ONLY
2048 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2049 "FUSE: Symlink caching enabled but no support for fuse_expire_entry. "
2050 "Symlinks will be cached but mountpoints on top of symlinks will "
2051 "break! "
2052 "Current libfuse %d is too old; required: libfuse >= 3.16, "
2053 "kernel >= 6.2-rc1",
2054 FUSE_VERSION);
2055 #endif
2056 } else {
2057 mount_point_->DisableCacheSymlinks();
2058 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2059 "FUSE: Symlink caching requested but missing fuse kernel support, "
2060 "falling back to no caching");
2061 }
2062 #else
2063 mount_point_->DisableCacheSymlinks();
2064 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2065 "FUSE: Symlink caching requested but missing libfuse support, "
2066 "falling back to no caching. Current libfuse %d",
2067 FUSE_VERSION);
2068 #endif
2069 }
2070
2071 #ifdef FUSE_CAP_EXPIRE_ONLY
2072 if ((conn->capable & FUSE_CAP_EXPIRE_ONLY) == FUSE_CAP_EXPIRE_ONLY &&
2073 FUSE_VERSION >= FUSE_MAKE_VERSION(3, 16)) {
2074 mount_point_->EnableFuseExpireEntry();
2075 LogCvmfs(kLogCvmfs, kLogDebug, "FUSE: Enable fuse_expire_entry ");
2076 } else if (mount_point_->cache_symlinks()) {
2077 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2078 "FUSE: Symlink caching enabled but no support for fuse_expire_entry. "
2079 "Symlinks will be cached but mountpoints on top of symlinks will break! "
2080 "Current libfuse %d; required: libfuse >= 3.16, kernel >= 6.2-rc1",
2081 FUSE_VERSION);
2082 }
2083 #endif
2084 }
2085
2086 static void cvmfs_destroy(void *unused __attribute__((unused))) {
2087 // The debug log is already closed at this point
2088 LogCvmfs(kLogCvmfs, kLogDebug, "cvmfs_destroy");
2089 }
2090
2091 /**
2092 * Puts the callback functions in one single structure
2093 */
2094 static void SetCvmfsOperations(struct fuse_lowlevel_ops *cvmfs_operations) {
2095 memset(cvmfs_operations, 0, sizeof(*cvmfs_operations));
2096
2097 // Init/Fini
2098 cvmfs_operations->init = cvmfs_init;
2099 cvmfs_operations->destroy = cvmfs_destroy;
2100
2101 cvmfs_operations->lookup = cvmfs_lookup;
2102 cvmfs_operations->getattr = cvmfs_getattr;
2103 cvmfs_operations->readlink = cvmfs_readlink;
2104 cvmfs_operations->open = cvmfs_open;
2105 cvmfs_operations->read = cvmfs_read;
2106 cvmfs_operations->release = cvmfs_release;
2107 cvmfs_operations->opendir = cvmfs_opendir;
2108 cvmfs_operations->readdir = cvmfs_readdir;
2109 cvmfs_operations->releasedir = cvmfs_releasedir;
2110 cvmfs_operations->statfs = cvmfs_statfs;
2111 cvmfs_operations->getxattr = cvmfs_getxattr;
2112 cvmfs_operations->listxattr = cvmfs_listxattr;
2113 cvmfs_operations->forget = cvmfs_forget;
2114 #if (FUSE_VERSION >= 29)
2115 cvmfs_operations->forget_multi = cvmfs_forget_multi;
2116 #endif
2117 }
2118
2119 // Called by cvmfs_talk when switching into read-only cache mode
2120 void UnregisterQuotaListener() {
2121 if (cvmfs::unpin_listener_) {
2122 quota::UnregisterListener(cvmfs::unpin_listener_);
2123 cvmfs::unpin_listener_ = NULL;
2124 }
2125 if (cvmfs::watchdog_listener_) {
2126 quota::UnregisterListener(cvmfs::watchdog_listener_);
2127 cvmfs::watchdog_listener_ = NULL;
2128 }
2129 }
2130
2131 bool SendFuseFd(const std::string &socket_path) {
2132 int fuse_fd;
2133 #if (FUSE_VERSION >= 30)
2134 fuse_fd = fuse_session_fd(*reinterpret_cast<struct fuse_session**>(
2135 loader_exports_->fuse_channel_or_session));
2136 #else
2137 fuse_fd = fuse_chan_fd(*reinterpret_cast<struct fuse_chan**>(
2138 loader_exports_->fuse_channel_or_session));
2139 #endif
2140 assert(fuse_fd >= 0);
2141 int sock_fd = ConnectSocket(socket_path);
2142 if (sock_fd < 0) {
2143 LogCvmfs(kLogCvmfs, kLogDebug, "cannot connect to socket %s: %d",
2144 socket_path.c_str(), errno);
2145 return false;
2146 }
2147 bool retval = SendFd2Socket(sock_fd, fuse_fd);
2148 close(sock_fd);
2149 return retval;
2150 }
2151
2152 } // namespace cvmfs
2153
2154
2155 string *g_boot_error = NULL;
2156
2157 __attribute__((visibility("default")))
2158 loader::CvmfsExports *g_cvmfs_exports = NULL;
2159
2160 /**
2161 * Begin section of cvmfs.cc-specific magic extended attributes
2162 */
2163
2164 class ExpiresMagicXattr : public BaseMagicXattr {
2165 time_t catalogs_valid_until_;
2166
2167 virtual bool PrepareValueFenced() {
2168 catalogs_valid_until_ = cvmfs::fuse_remounter_->catalogs_valid_until();
2169 return true;
2170 }
2171
2172 virtual void FinalizeValue() {
2173 if (catalogs_valid_until_ == MountPoint::kIndefiniteDeadline) {
2174 result_pages_.push_back("never (fixed root catalog)");
2175 return;
2176 } else {
2177 time_t now = time(NULL);
2178 result_pages_.push_back(StringifyInt((catalogs_valid_until_ - now) / 60));
2179 }
2180 }
2181 };
2182
2183 class InodeMaxMagicXattr : public BaseMagicXattr {
2184 virtual void FinalizeValue() {
2185 result_pages_.push_back(StringifyInt(
2186 cvmfs::inode_generation_info_.inode_generation +
2187 xattr_mgr_->mount_point()->catalog_mgr()->inode_gauge()));
2188 }
2189 };
2190
2191 class MaxFdMagicXattr : public BaseMagicXattr {
2192 virtual void FinalizeValue() {
2193 result_pages_.push_back(StringifyInt(
2194 cvmfs::max_open_files_ - cvmfs::kNumReservedFd));
2195 }
2196 };
2197
2198 class PidMagicXattr : public BaseMagicXattr {
2199 virtual void FinalizeValue() {
2200 result_pages_.push_back(StringifyInt(cvmfs::pid_)); }
2201 };
2202
2203 class UptimeMagicXattr : public BaseMagicXattr {
2204 virtual void FinalizeValue(){
2205 time_t now = time(NULL);
2206 uint64_t uptime = now - cvmfs::loader_exports_->boot_time;
2207 result_pages_.push_back(StringifyUint(uptime / 60));
2208 }
2209 };
2210
2211 /**
2212 * Register cvmfs.cc-specific magic extended attributes to mountpoint's
2213 * magic xattribute manager
2214 */
2215 static void RegisterMagicXattrs() {
2216 MagicXattrManager *mgr = cvmfs::mount_point_->magic_xattr_mgr();
2217 mgr->Register("user.expires", new ExpiresMagicXattr());
2218 mgr->Register("user.inode_max", new InodeMaxMagicXattr());
2219 mgr->Register("user.pid", new PidMagicXattr());
2220 mgr->Register("user.maxfd", new MaxFdMagicXattr());
2221 mgr->Register("user.uptime", new UptimeMagicXattr());
2222
2223 mgr->Freeze();
2224 }
2225
2226 /**
2227 * Construct a file system but prevent hanging when already mounted. That
2228 * means: at most one "system" mount of any given repository name.
2229 */
2230 static FileSystem *InitSystemFs(
2231 const string &mount_path,
2232 const string &fqrn,
2233 FileSystem::FileSystemInfo fs_info)
2234 {
2235 fs_info.wait_workspace = false;
2236 FileSystem *file_system = FileSystem::Create(fs_info);
2237
2238 if (file_system->boot_status() == loader::kFailLockWorkspace) {
2239 string fqrn_from_xattr;
2240 int retval = platform_getxattr(mount_path, "user.fqrn", &fqrn_from_xattr);
2241 if (!retval) {
2242 // Cvmfs not mounted anymore, but another cvmfs process is still in
2243 // shutdown procedure. Try again and wait for lock
2244 delete file_system;
2245 fs_info.wait_workspace = true;
2246 file_system = FileSystem::Create(fs_info);
2247 } else {
2248 if (fqrn_from_xattr == fqrn) {
2249 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
2250 "repository already mounted on %s", mount_path.c_str());
2251 file_system->set_boot_status(loader::kFailDoubleMount);
2252 } else {
2253 LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogErr,
2254 "CernVM-FS repository %s already mounted on %s",
2255 fqrn.c_str(), mount_path.c_str());
2256 file_system->set_boot_status(loader::kFailOtherMount);
2257 }
2258 }
2259 }
2260
2261 return file_system;
2262 }
2263
2264
2265 static void InitOptionsMgr(const loader::LoaderExports *loader_exports) {
2266 if (loader_exports->version >= 3 && loader_exports->simple_options_parsing) {
2267 cvmfs::options_mgr_ = new SimpleOptionsParser(
2268 new DefaultOptionsTemplateManager(loader_exports->repository_name));
2269 } else {
2270 cvmfs::options_mgr_ = new BashOptionsManager(
2271 new DefaultOptionsTemplateManager(loader_exports->repository_name));
2272 }
2273
2274 if (loader_exports->config_files != "") {
2275 vector<string> tokens = SplitString(loader_exports->config_files, ':');
2276 for (unsigned i = 0, s = tokens.size(); i < s; ++i) {
2277 cvmfs::options_mgr_->ParsePath(tokens[i], false);
2278 }
2279 } else {
2280 cvmfs::options_mgr_->ParseDefault(loader_exports->repository_name);
2281 }
2282 }
2283
2284
2285 static unsigned CheckMaxOpenFiles() {
2286 static unsigned max_open_files;
2287 static bool already_done = false;
2288
2289 // check number of open files (lazy evaluation)
2290 if (!already_done) {
2291 unsigned soft_limit = 0;
2292 unsigned hard_limit = 0;
2293 GetLimitNoFile(&soft_limit, &hard_limit);
2294
2295 if (soft_limit < cvmfs::kMinOpenFiles) {
2296 LogCvmfs(kLogCvmfs, kLogSyslogWarn | kLogDebug,
2297 "Warning: current limits for number of open files are "
2298 "(%u/%u)\n"
2299 "CernVM-FS is likely to run out of file descriptors, "
2300 "set ulimit -n to at least %u",
2301 soft_limit, hard_limit, cvmfs::kMinOpenFiles);
2302 }
2303 max_open_files = soft_limit;
2304 already_done = true;
2305 }
2306
2307 return max_open_files;
2308 }
2309
2310
2311 static int Init(const loader::LoaderExports *loader_exports) {
2312 g_boot_error = new string("unknown error");
2313 cvmfs::loader_exports_ = loader_exports;
2314
2315 crypto::SetupLibcryptoMt();
2316
2317 InitOptionsMgr(loader_exports);
2318
2319 // We need logging set up before forking the watchdog
2320 FileSystem::SetupLoggingStandalone(
2321 *cvmfs::options_mgr_, loader_exports->repository_name);
2322
2323 // Monitor, check for maximum number of open files
2324 if (cvmfs::UseWatchdog()) {
2325 auto_umount::SetMountpoint(loader_exports->mount_point);
2326 cvmfs::watchdog_ = Watchdog::Create(auto_umount::UmountOnCrash);
2327 if (cvmfs::watchdog_ == NULL) {
2328 *g_boot_error = "failed to initialize watchdog.";
2329 return loader::kFailMonitor;
2330 }
2331 }
2332 cvmfs::max_open_files_ = CheckMaxOpenFiles();
2333
2334 FileSystem::FileSystemInfo fs_info;
2335 fs_info.type = FileSystem::kFsFuse;
2336 fs_info.name = loader_exports->repository_name;
2337 fs_info.exe_path = loader_exports->program_name;
2338 fs_info.options_mgr = cvmfs::options_mgr_;
2339 fs_info.foreground = loader_exports->foreground;
2340 cvmfs::file_system_ = InitSystemFs(
2341 loader_exports->mount_point,
2342 loader_exports->repository_name,
2343 fs_info);
2344 if (!cvmfs::file_system_->IsValid()) {
2345 *g_boot_error = cvmfs::file_system_->boot_error();
2346 return cvmfs::file_system_->boot_status();
2347 }
2348 if ((cvmfs::file_system_->cache_mgr()->id() == kPosixCacheManager) &&
2349 dynamic_cast<PosixCacheManager *>(
2350 cvmfs::file_system_->cache_mgr())->do_refcount())
2351 {
2352 cvmfs::check_fd_overflow_ = false;
2353 }
2354
2355 cvmfs::mount_point_ = MountPoint::Create(loader_exports->repository_name,
2356 cvmfs::file_system_);
2357 if (!cvmfs::mount_point_->IsValid()) {
2358 *g_boot_error = cvmfs::mount_point_->boot_error();
2359 return cvmfs::mount_point_->boot_status();
2360 }
2361
2362 RegisterMagicXattrs();
2363
2364 cvmfs::directory_handles_ = new cvmfs::DirectoryHandles();
2365 cvmfs::directory_handles_->set_empty_key((uint64_t)(-1));
2366 cvmfs::directory_handles_->set_deleted_key((uint64_t)(-2));
2367
2368 LogCvmfs(kLogCvmfs, kLogDebug, "fuse inode size is %lu bits",
2369 sizeof(fuse_ino_t) * 8);
2370
2371 cvmfs::inode_generation_info_.initial_revision =
2372 cvmfs::mount_point_->catalog_mgr()->GetRevision();
2373 cvmfs::inode_generation_info_.inode_generation =
2374 cvmfs::mount_point_->inode_annotation()->GetGeneration();
2375 LogCvmfs(kLogCvmfs, kLogDebug, "root inode is %" PRIu64,
2376 uint64_t(cvmfs::mount_point_->catalog_mgr()->GetRootInode()));
2377
2378 void **channel_or_session = NULL;
2379 if (loader_exports->version >= 4) {
2380 channel_or_session = loader_exports->fuse_channel_or_session;
2381 }
2382
2383 bool fuse_notify_invalidation = true;
2384 std::string buf;
2385 if (cvmfs::options_mgr_->GetValue("CVMFS_FUSE_NOTIFY_INVALIDATION",
2386 &buf)) {
2387 if (!cvmfs::options_mgr_->IsOn(buf)) {
2388 fuse_notify_invalidation = false;
2389 cvmfs::mount_point_->dentry_tracker()->Disable();
2390 }
2391 }
2392 cvmfs::fuse_remounter_ =
2393 new FuseRemounter(cvmfs::mount_point_, &cvmfs::inode_generation_info_,
2394 channel_or_session, fuse_notify_invalidation);
2395
2396 // Control & command interface
2397 cvmfs::talk_mgr_ = TalkManager::Create(
2398 cvmfs::mount_point_->talk_socket_path(),
2399 cvmfs::mount_point_,
2400 cvmfs::fuse_remounter_);
2401 if ((cvmfs::mount_point_->talk_socket_uid() != 0) ||
2402 (cvmfs::mount_point_->talk_socket_gid() != 0))
2403 {
2404 uid_t tgt_uid = cvmfs::mount_point_->talk_socket_uid();
2405 gid_t tgt_gid = cvmfs::mount_point_->talk_socket_gid();
2406 int rvi = chown(cvmfs::mount_point_->talk_socket_path().c_str(),
2407 tgt_uid, tgt_gid);
2408 if (rvi != 0) {
2409 *g_boot_error = std::string("failed to set talk socket ownership - ")
2410 + "target " + StringifyInt(tgt_uid) + ":" + StringifyInt(tgt_uid)
2411 + ", user " + StringifyInt(geteuid()) + ":" + StringifyInt(getegid());
2412 return loader::kFailTalk;
2413 }
2414 }
2415 if (cvmfs::talk_mgr_ == NULL) {
2416 *g_boot_error = "failed to initialize talk socket (" +
2417 StringifyInt(errno) + ")";
2418 return loader::kFailTalk;
2419 }
2420
2421 // Notification system client
2422 {
2423 OptionsManager* options = cvmfs::file_system_->options_mgr();
2424 if (options->IsDefined("CVMFS_NOTIFICATION_SERVER")) {
2425 std::string config;
2426 options->GetValue("CVMFS_NOTIFICATION_SERVER", &config);
2427 const std::string repo_name = cvmfs::mount_point_->fqrn();
2428 cvmfs::notification_client_ =
2429 new NotificationClient(config, repo_name, cvmfs::fuse_remounter_,
2430 cvmfs::mount_point_->download_mgr(),
2431 cvmfs::mount_point_->signature_mgr());
2432 }
2433 }
2434
2435 return loader::kFailOk;
2436 }
2437
2438
2439 /**
2440 * Things that have to be executed after fork() / daemon()
2441 */
2442 static void Spawn() {
2443 // First thing: kick off the watchdog while we still have a single-threaded
2444 // well-defined state
2445 cvmfs::pid_ = getpid();
2446 if (cvmfs::watchdog_) {
2447 cvmfs::watchdog_->Spawn(GetCurrentWorkingDirectory() + "/stacktrace." +
2448 cvmfs::mount_point_->fqrn());
2449 }
2450
2451 cvmfs::fuse_remounter_->Spawn();
2452 if (cvmfs::mount_point_->dentry_tracker()->is_active()) {
2453 cvmfs::mount_point_->dentry_tracker()->SpawnCleaner(
2454 // Usually every minute
2455 static_cast<unsigned int>(cvmfs::mount_point_->kcache_timeout_sec()));
2456 }
2457
2458 cvmfs::mount_point_->download_mgr()->Spawn();
2459 cvmfs::mount_point_->external_download_mgr()->Spawn();
2460 if (cvmfs::mount_point_->resolv_conf_watcher() != NULL) {
2461 cvmfs::mount_point_->resolv_conf_watcher()->Spawn();
2462 }
2463 QuotaManager *quota_mgr = cvmfs::file_system_->cache_mgr()->quota_mgr();
2464 quota_mgr->Spawn();
2465 if (quota_mgr->HasCapability(QuotaManager::kCapListeners)) {
2466 cvmfs::watchdog_listener_ = quota::RegisterWatchdogListener(
2467 quota_mgr,
2468 cvmfs::mount_point_->uuid()->uuid() + "-watchdog");
2469 cvmfs::unpin_listener_ = quota::RegisterUnpinListener(
2470 quota_mgr,
2471 cvmfs::mount_point_->catalog_mgr(),
2472 cvmfs::mount_point_->uuid()->uuid() + "-unpin");
2473 }
2474 cvmfs::mount_point_->tracer()->Spawn();
2475 cvmfs::talk_mgr_->Spawn();
2476
2477 if (cvmfs::notification_client_ != NULL) {
2478 cvmfs::notification_client_->Spawn();
2479 }
2480
2481 if (cvmfs::file_system_->nfs_maps() != NULL) {
2482 cvmfs::file_system_->nfs_maps()->Spawn();
2483 }
2484
2485 cvmfs::file_system_->cache_mgr()->Spawn();
2486
2487 if (cvmfs::mount_point_->telemetry_aggr() != NULL) {
2488 cvmfs::mount_point_->telemetry_aggr()->Spawn();
2489 }
2490 }
2491
2492
2493 static string GetErrorMsg() {
2494 if (g_boot_error)
2495 return *g_boot_error;
2496 return "";
2497 }
2498
2499
2500 /**
2501 * Called alone at the end of SaveState; it performs a Fini() half way through,
2502 * enough to delete the catalog manager, so that no more open file handles
2503 * from file catalogs are active.
2504 */
2505 static void ShutdownMountpoint() {
2506 delete cvmfs::talk_mgr_;
2507 cvmfs::talk_mgr_ = NULL;
2508
2509 delete cvmfs::notification_client_;
2510 cvmfs::notification_client_ = NULL;
2511
2512 // The remonter has a reference to the mount point and the inode generation
2513 delete cvmfs::fuse_remounter_;
2514 cvmfs::fuse_remounter_ = NULL;
2515
2516 // The unpin listener requires the catalog, so this must be unregistered
2517 // before the catalog manager is removed
2518 if (cvmfs::unpin_listener_ != NULL) {
2519 quota::UnregisterListener(cvmfs::unpin_listener_);
2520 cvmfs::unpin_listener_ = NULL;
2521 }
2522 if (cvmfs::watchdog_listener_ != NULL) {
2523 quota::UnregisterListener(cvmfs::watchdog_listener_);
2524 cvmfs::watchdog_listener_ = NULL;
2525 }
2526
2527 delete cvmfs::directory_handles_;
2528 delete cvmfs::mount_point_;
2529 cvmfs::directory_handles_ = NULL;
2530 cvmfs::mount_point_ = NULL;
2531 }
2532
2533
2534 static void Fini() {
2535 ShutdownMountpoint();
2536
2537 delete cvmfs::file_system_;
2538 delete cvmfs::options_mgr_;
2539 cvmfs::file_system_ = NULL;
2540 cvmfs::options_mgr_ = NULL;
2541
2542 delete cvmfs::watchdog_;
2543 cvmfs::watchdog_ = NULL;
2544
2545 delete g_boot_error;
2546 g_boot_error = NULL;
2547 auto_umount::SetMountpoint("");
2548
2549 crypto::CleanupLibcryptoMt();
2550 }
2551
2552
2553 static int AltProcessFlavor(int argc, char **argv) {
2554 if (strcmp(argv[1], "__cachemgr__") == 0) {
2555 return PosixQuotaManager::MainCacheManager(argc, argv);
2556 }
2557 if (strcmp(argv[1], "__wpad__") == 0) {
2558 return download::MainResolveProxyDescription(argc, argv);
2559 }
2560 return 1;
2561 }
2562
2563
2564 static bool MaintenanceMode(const int fd_progress) {
2565 SendMsg2Socket(fd_progress, "Entering maintenance mode\n");
2566 string msg_progress = "Draining out kernel caches (";
2567 if (FuseInvalidator::HasFuseNotifyInval())
2568 msg_progress += "up to ";
2569 msg_progress += StringifyInt(static_cast<int>(
2570 cvmfs::mount_point_->kcache_timeout_sec())) +
2571 "s)\n";
2572 SendMsg2Socket(fd_progress, msg_progress);
2573 cvmfs::fuse_remounter_->EnterMaintenanceMode();
2574 return true;
2575 }
2576
2577
2578 static bool SaveState(const int fd_progress, loader::StateList *saved_states) {
2579 string msg_progress;
2580
2581 unsigned num_open_dirs = cvmfs::directory_handles_->size();
2582 if (num_open_dirs != 0) {
2583 #ifdef DEBUGMSG
2584 for (cvmfs::DirectoryHandles::iterator i =
2585 cvmfs::directory_handles_->begin(),
2586 iEnd = cvmfs::directory_handles_->end(); i != iEnd; ++i)
2587 {
2588 LogCvmfs(kLogCvmfs, kLogDebug, "saving dirhandle %lu", i->first);
2589 }
2590 #endif
2591
2592 msg_progress = "Saving open directory handles (" +
2593 StringifyInt(num_open_dirs) + " handles)\n";
2594 SendMsg2Socket(fd_progress, msg_progress);
2595
2596 // TODO(jblomer): should rather be saved just in a malloc'd memory block
2597 cvmfs::DirectoryHandles *saved_handles =
2598 new cvmfs::DirectoryHandles(*cvmfs::directory_handles_);
2599 loader::SavedState *save_open_dirs = new loader::SavedState();
2600 save_open_dirs->state_id = loader::kStateOpenDirs;
2601 save_open_dirs->state = saved_handles;
2602 saved_states->push_back(save_open_dirs);
2603 }
2604
2605 if (!cvmfs::file_system_->IsNfsSource()) {
2606 msg_progress = "Saving inode tracker\n";
2607 SendMsg2Socket(fd_progress, msg_progress);
2608 glue::InodeTracker *saved_inode_tracker =
2609 new glue::InodeTracker(*cvmfs::mount_point_->inode_tracker());
2610 loader::SavedState *state_glue_buffer = new loader::SavedState();
2611 state_glue_buffer->state_id = loader::kStateGlueBufferV4;
2612 state_glue_buffer->state = saved_inode_tracker;
2613 saved_states->push_back(state_glue_buffer);
2614 }
2615
2616 msg_progress = "Saving negative entry cache\n";
2617 SendMsg2Socket(fd_progress, msg_progress);
2618 glue::DentryTracker *saved_dentry_tracker =
2619 new glue::DentryTracker(*cvmfs::mount_point_->dentry_tracker());
2620 loader::SavedState *state_dentry_tracker = new loader::SavedState();
2621 state_dentry_tracker->state_id = loader::kStateDentryTracker;
2622 state_dentry_tracker->state = saved_dentry_tracker;
2623 saved_states->push_back(state_dentry_tracker);
2624
2625 msg_progress = "Saving page cache entry tracker\n";
2626 SendMsg2Socket(fd_progress, msg_progress);
2627 glue::PageCacheTracker *saved_page_cache_tracker =
2628 new glue::PageCacheTracker(*cvmfs::mount_point_->page_cache_tracker());
2629 loader::SavedState *state_page_cache_tracker = new loader::SavedState();
2630 state_page_cache_tracker->state_id = loader::kStatePageCacheTracker;
2631 state_page_cache_tracker->state = saved_page_cache_tracker;
2632 saved_states->push_back(state_page_cache_tracker);
2633
2634 msg_progress = "Saving chunk tables\n";
2635 SendMsg2Socket(fd_progress, msg_progress);
2636 ChunkTables *saved_chunk_tables = new ChunkTables(
2637 *cvmfs::mount_point_->chunk_tables());
2638 loader::SavedState *state_chunk_tables = new loader::SavedState();
2639 state_chunk_tables->state_id = loader::kStateOpenChunksV4;
2640 state_chunk_tables->state = saved_chunk_tables;
2641 saved_states->push_back(state_chunk_tables);
2642
2643 msg_progress = "Saving inode generation\n";
2644 SendMsg2Socket(fd_progress, msg_progress);
2645 cvmfs::inode_generation_info_.inode_generation +=
2646 cvmfs::mount_point_->catalog_mgr()->inode_gauge();
2647 cvmfs::InodeGenerationInfo *saved_inode_generation =
2648 new cvmfs::InodeGenerationInfo(cvmfs::inode_generation_info_);
2649 loader::SavedState *state_inode_generation = new loader::SavedState();
2650 state_inode_generation->state_id = loader::kStateInodeGeneration;
2651 state_inode_generation->state = saved_inode_generation;
2652 saved_states->push_back(state_inode_generation);
2653
2654 msg_progress = "Saving fuse state\n";
2655 SendMsg2Socket(fd_progress, msg_progress);
2656 cvmfs::FuseState *saved_fuse_state = new cvmfs::FuseState();
2657 saved_fuse_state->cache_symlinks = cvmfs::mount_point_->cache_symlinks();
2658 saved_fuse_state->has_dentry_expire =
2659 cvmfs::mount_point_->fuse_expire_entry();
2660 loader::SavedState *state_fuse = new loader::SavedState();
2661 state_fuse->state_id = loader::kStateFuse;
2662 state_fuse->state = saved_fuse_state;
2663 saved_states->push_back(state_fuse);
2664
2665 // Close open file catalogs
2666 ShutdownMountpoint();
2667
2668 loader::SavedState *state_cache_mgr = new loader::SavedState();
2669 state_cache_mgr->state_id = loader::kStateOpenFiles;
2670 state_cache_mgr->state =
2671 cvmfs::file_system_->cache_mgr()->SaveState(fd_progress);
2672 saved_states->push_back(state_cache_mgr);
2673
2674 msg_progress = "Saving open files counter\n";
2675 uint32_t *saved_num_fd =
2676 new uint32_t(cvmfs::file_system_->no_open_files()->Get());
2677 loader::SavedState *state_num_fd = new loader::SavedState();
2678 state_num_fd->state_id = loader::kStateOpenFilesCounter;
2679 state_num_fd->state = saved_num_fd;
2680 saved_states->push_back(state_num_fd);
2681
2682 return true;
2683 }
2684
2685
2686 static bool RestoreState(const int fd_progress,
2687 const loader::StateList &saved_states)
2688 {
2689 // If we have no saved version of the page cache tracker, it is unsafe
2690 // to start using it. The page cache tracker has to run for the entire
2691 // lifetime of the mountpoint or not at all.
2692 cvmfs::mount_point_->page_cache_tracker()->Disable();
2693
2694 for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
2695 if (saved_states[i]->state_id == loader::kStateOpenDirs) {
2696 SendMsg2Socket(fd_progress, "Restoring open directory handles... ");
2697 delete cvmfs::directory_handles_;
2698 cvmfs::DirectoryHandles *saved_handles =
2699 (cvmfs::DirectoryHandles *)saved_states[i]->state;
2700 cvmfs::directory_handles_ = new cvmfs::DirectoryHandles(*saved_handles);
2701 cvmfs::file_system_->no_open_dirs()->Set(
2702 cvmfs::directory_handles_->size());
2703 cvmfs::DirectoryHandles::const_iterator i =
2704 cvmfs::directory_handles_->begin();
2705 for (; i != cvmfs::directory_handles_->end(); ++i) {
2706 if (i->first >= cvmfs::next_directory_handle_)
2707 cvmfs::next_directory_handle_ = i->first + 1;
2708 }
2709
2710 SendMsg2Socket(fd_progress,
2711 StringifyInt(cvmfs::directory_handles_->size()) + " handles\n");
2712 }
2713
2714 if (saved_states[i]->state_id == loader::kStateGlueBuffer) {
2715 SendMsg2Socket(fd_progress, "Migrating inode tracker (v1 to v4)... ");
2716 compat::inode_tracker::InodeTracker *saved_inode_tracker =
2717 (compat::inode_tracker::InodeTracker *)saved_states[i]->state;
2718 compat::inode_tracker::Migrate(
2719 saved_inode_tracker, cvmfs::mount_point_->inode_tracker());
2720 SendMsg2Socket(fd_progress, " done\n");
2721 }
2722
2723 if (saved_states[i]->state_id == loader::kStateGlueBufferV2) {
2724 SendMsg2Socket(fd_progress, "Migrating inode tracker (v2 to v4)... ");
2725 compat::inode_tracker_v2::InodeTracker *saved_inode_tracker =
2726 (compat::inode_tracker_v2::InodeTracker *)saved_states[i]->state;
2727 compat::inode_tracker_v2::Migrate(saved_inode_tracker,
2728 cvmfs::mount_point_->inode_tracker());
2729 SendMsg2Socket(fd_progress, " done\n");
2730 }
2731
2732 if (saved_states[i]->state_id == loader::kStateGlueBufferV3) {
2733 SendMsg2Socket(fd_progress, "Migrating inode tracker (v3 to v4)... ");
2734 compat::inode_tracker_v3::InodeTracker *saved_inode_tracker =
2735 (compat::inode_tracker_v3::InodeTracker *)saved_states[i]->state;
2736 compat::inode_tracker_v3::Migrate(saved_inode_tracker,
2737 cvmfs::mount_point_->inode_tracker());
2738 SendMsg2Socket(fd_progress, " done\n");
2739 }
2740
2741 if (saved_states[i]->state_id == loader::kStateGlueBufferV4) {
2742 SendMsg2Socket(fd_progress, "Restoring inode tracker... ");
2743 cvmfs::mount_point_->inode_tracker()->~InodeTracker();
2744 glue::InodeTracker *saved_inode_tracker =
2745 (glue::InodeTracker *)saved_states[i]->state;
2746 new (cvmfs::mount_point_->inode_tracker())
2747 glue::InodeTracker(*saved_inode_tracker);
2748 SendMsg2Socket(fd_progress, " done\n");
2749 }
2750
2751 if (saved_states[i]->state_id == loader::kStateDentryTracker) {
2752 SendMsg2Socket(fd_progress, "Restoring dentry tracker... ");
2753 cvmfs::mount_point_->dentry_tracker()->~DentryTracker();
2754 glue::DentryTracker *saved_dentry_tracker =
2755 static_cast<glue::DentryTracker *>(saved_states[i]->state);
2756 new (cvmfs::mount_point_->dentry_tracker())
2757 glue::DentryTracker(*saved_dentry_tracker);
2758 SendMsg2Socket(fd_progress, " done\n");
2759 }
2760
2761 if (saved_states[i]->state_id == loader::kStatePageCacheTracker) {
2762 SendMsg2Socket(fd_progress, "Restoring page cache entry tracker... ");
2763 cvmfs::mount_point_->page_cache_tracker()->~PageCacheTracker();
2764 glue::PageCacheTracker *saved_page_cache_tracker =
2765 (glue::PageCacheTracker *)saved_states[i]->state;
2766 new (cvmfs::mount_point_->page_cache_tracker())
2767 glue::PageCacheTracker(*saved_page_cache_tracker);
2768 SendMsg2Socket(fd_progress, " done\n");
2769 }
2770
2771 ChunkTables *chunk_tables = cvmfs::mount_point_->chunk_tables();
2772
2773 if (saved_states[i]->state_id == loader::kStateOpenChunks) {
2774 SendMsg2Socket(fd_progress, "Migrating chunk tables (v1 to v4)... ");
2775 compat::chunk_tables::ChunkTables *saved_chunk_tables =
2776 (compat::chunk_tables::ChunkTables *)saved_states[i]->state;
2777 compat::chunk_tables::Migrate(saved_chunk_tables, chunk_tables);
2778 SendMsg2Socket(fd_progress,
2779 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2780 }
2781
2782 if (saved_states[i]->state_id == loader::kStateOpenChunksV2) {
2783 SendMsg2Socket(fd_progress, "Migrating chunk tables (v2 to v4)... ");
2784 compat::chunk_tables_v2::ChunkTables *saved_chunk_tables =
2785 (compat::chunk_tables_v2::ChunkTables *)saved_states[i]->state;
2786 compat::chunk_tables_v2::Migrate(saved_chunk_tables, chunk_tables);
2787 SendMsg2Socket(fd_progress,
2788 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2789 }
2790
2791 if (saved_states[i]->state_id == loader::kStateOpenChunksV3) {
2792 SendMsg2Socket(fd_progress, "Migrating chunk tables (v3 to v4)... ");
2793 compat::chunk_tables_v3::ChunkTables *saved_chunk_tables =
2794 (compat::chunk_tables_v3::ChunkTables *)saved_states[i]->state;
2795 compat::chunk_tables_v3::Migrate(saved_chunk_tables, chunk_tables);
2796 SendMsg2Socket(fd_progress,
2797 StringifyInt(chunk_tables->handle2fd.size()) + " handles\n");
2798 }
2799
2800 if (saved_states[i]->state_id == loader::kStateOpenChunksV4) {
2801 SendMsg2Socket(fd_progress, "Restoring chunk tables... ");
2802 chunk_tables->~ChunkTables();
2803 ChunkTables *saved_chunk_tables = reinterpret_cast<ChunkTables *>(
2804 saved_states[i]->state);
2805 new (chunk_tables) ChunkTables(*saved_chunk_tables);
2806 SendMsg2Socket(fd_progress, " done\n");
2807 }
2808
2809 if (saved_states[i]->state_id == loader::kStateInodeGeneration) {
2810 SendMsg2Socket(fd_progress, "Restoring inode generation... ");
2811 cvmfs::InodeGenerationInfo *old_info =
2812 (cvmfs::InodeGenerationInfo *)saved_states[i]->state;
2813 if (old_info->version == 1) {
2814 // Migration
2815 cvmfs::inode_generation_info_.initial_revision =
2816 old_info->initial_revision;
2817 cvmfs::inode_generation_info_.incarnation = old_info->incarnation;
2818 // Note: in the rare case of inode generation being 0 before, inode
2819 // can clash after reload before remount
2820 } else {
2821 cvmfs::inode_generation_info_ = *old_info;
2822 }
2823 ++cvmfs::inode_generation_info_.incarnation;
2824 SendMsg2Socket(fd_progress, " done\n");
2825 }
2826
2827 if (saved_states[i]->state_id == loader::kStateOpenFilesCounter) {
2828 SendMsg2Socket(fd_progress, "Restoring open files counter... ");
2829 cvmfs::file_system_->no_open_files()->Set(*(reinterpret_cast<uint32_t *>(
2830 saved_states[i]->state)));
2831 SendMsg2Socket(fd_progress, " done\n");
2832 }
2833
2834 if (saved_states[i]->state_id == loader::kStateOpenFiles) {
2835 int old_root_fd = cvmfs::mount_point_->catalog_mgr()->root_fd();
2836
2837 // TODO(jblomer): make this less hacky
2838
2839 CacheManagerIds saved_type =
2840 cvmfs::file_system_->cache_mgr()->PeekState(saved_states[i]->state);
2841 int fixup_root_fd = -1;
2842
2843 if ((saved_type == kStreamingCacheManager) &&
2844 (cvmfs::file_system_->cache_mgr()->id() != kStreamingCacheManager))
2845 {
2846 // stick to the streaming cache manager
2847 StreamingCacheManager *new_cache_mgr = new
2848 StreamingCacheManager(cvmfs::max_open_files_,
2849 cvmfs::file_system_->cache_mgr(),
2850 cvmfs::mount_point_->download_mgr(),
2851 cvmfs::mount_point_->external_download_mgr(),
2852 StreamingCacheManager::kDefaultBufferSize,
2853 cvmfs::file_system_->statistics());
2854 fixup_root_fd = new_cache_mgr->PlantFd(old_root_fd);
2855 cvmfs::file_system_->ReplaceCacheManager(new_cache_mgr);
2856 cvmfs::mount_point_->fetcher()->ReplaceCacheManager(new_cache_mgr);
2857 cvmfs::mount_point_->external_fetcher()->ReplaceCacheManager(
2858 new_cache_mgr);
2859 }
2860
2861 if ((cvmfs::file_system_->cache_mgr()->id() == kStreamingCacheManager) &&
2862 (saved_type != kStreamingCacheManager))
2863 {
2864 // stick to the cache manager wrapped into the streaming cache
2865 CacheManager *wrapped_cache_mgr = dynamic_cast<StreamingCacheManager *>(
2866 cvmfs::file_system_->cache_mgr())->MoveOutBackingCacheMgr(
2867 &fixup_root_fd);
2868 delete cvmfs::file_system_->cache_mgr();
2869 cvmfs::file_system_->ReplaceCacheManager(wrapped_cache_mgr);
2870 cvmfs::mount_point_->fetcher()->ReplaceCacheManager(wrapped_cache_mgr);
2871 cvmfs::mount_point_->external_fetcher()->ReplaceCacheManager(
2872 wrapped_cache_mgr);
2873 }
2874
2875 int new_root_fd = cvmfs::file_system_->cache_mgr()->RestoreState(
2876 fd_progress, saved_states[i]->state);
2877 LogCvmfs(kLogCvmfs, kLogDebug, "new root file catalog descriptor @%d",
2878 new_root_fd);
2879 if (new_root_fd >= 0) {
2880 cvmfs::file_system_->RemapCatalogFd(old_root_fd, new_root_fd);
2881 } else if (fixup_root_fd >= 0) {
2882 LogCvmfs(kLogCvmfs, kLogDebug,
2883 "new root file catalog descriptor (fixup) @%d", fixup_root_fd);
2884 cvmfs::file_system_->RemapCatalogFd(old_root_fd, fixup_root_fd);
2885 }
2886 }
2887
2888 if (saved_states[i]->state_id == loader::kStateFuse) {
2889 SendMsg2Socket(fd_progress, "Restoring fuse state... ");
2890 cvmfs::FuseState *fuse_state =
2891 static_cast<cvmfs::FuseState *>(saved_states[i]->state);
2892 if (!fuse_state->cache_symlinks)
2893 cvmfs::mount_point_->DisableCacheSymlinks();
2894 if (fuse_state->has_dentry_expire)
2895 cvmfs::mount_point_->EnableFuseExpireEntry();
2896 SendMsg2Socket(fd_progress, " done\n");
2897 }
2898 }
2899 if (cvmfs::mount_point_->inode_annotation()) {
2900 uint64_t saved_generation = cvmfs::inode_generation_info_.inode_generation;
2901 cvmfs::mount_point_->inode_annotation()->IncGeneration(saved_generation);
2902 }
2903
2904 return true;
2905 }
2906
2907
2908 static void FreeSavedState(const int fd_progress,
2909 const loader::StateList &saved_states)
2910 {
2911 for (unsigned i = 0, l = saved_states.size(); i < l; ++i) {
2912 switch (saved_states[i]->state_id) {
2913 case loader::kStateOpenDirs:
2914 SendMsg2Socket(fd_progress, "Releasing saved open directory handles\n");
2915 delete static_cast<cvmfs::DirectoryHandles *>(saved_states[i]->state);
2916 break;
2917 case loader::kStateGlueBuffer:
2918 SendMsg2Socket(
2919 fd_progress, "Releasing saved glue buffer (version 1)\n");
2920 delete static_cast<compat::inode_tracker::InodeTracker *>(
2921 saved_states[i]->state);
2922 break;
2923 case loader::kStateGlueBufferV2:
2924 SendMsg2Socket(
2925 fd_progress, "Releasing saved glue buffer (version 2)\n");
2926 delete static_cast<compat::inode_tracker_v2::InodeTracker *>(
2927 saved_states[i]->state);
2928 break;
2929 case loader::kStateGlueBufferV3:
2930 SendMsg2Socket(
2931 fd_progress, "Releasing saved glue buffer (version 3)\n");
2932 delete static_cast<compat::inode_tracker_v3::InodeTracker *>(
2933 saved_states[i]->state);
2934 break;
2935 case loader::kStateGlueBufferV4:
2936 SendMsg2Socket(fd_progress, "Releasing saved glue buffer\n");
2937 delete static_cast<glue::InodeTracker *>(saved_states[i]->state);
2938 break;
2939 case loader::kStateDentryTracker:
2940 SendMsg2Socket(fd_progress, "Releasing saved dentry tracker\n");
2941 delete static_cast<glue::DentryTracker *>(saved_states[i]->state);
2942 break;
2943 case loader::kStatePageCacheTracker:
2944 SendMsg2Socket(fd_progress, "Releasing saved page cache entry cache\n");
2945 delete static_cast<glue::PageCacheTracker *>(saved_states[i]->state);
2946 break;
2947 case loader::kStateOpenChunks:
2948 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 1)\n");
2949 delete static_cast<compat::chunk_tables::ChunkTables *>(
2950 saved_states[i]->state);
2951 break;
2952 case loader::kStateOpenChunksV2:
2953 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 2)\n");
2954 delete static_cast<compat::chunk_tables_v2::ChunkTables *>(
2955 saved_states[i]->state);
2956 break;
2957 case loader::kStateOpenChunksV3:
2958 SendMsg2Socket(fd_progress, "Releasing chunk tables (version 3)\n");
2959 delete static_cast<compat::chunk_tables_v3::ChunkTables *>(
2960 saved_states[i]->state);
2961 break;
2962 case loader::kStateOpenChunksV4:
2963 SendMsg2Socket(fd_progress, "Releasing chunk tables\n");
2964 delete static_cast<ChunkTables *>(saved_states[i]->state);
2965 break;
2966 case loader::kStateInodeGeneration:
2967 SendMsg2Socket(fd_progress, "Releasing saved inode generation info\n");
2968 delete static_cast<cvmfs::InodeGenerationInfo *>(
2969 saved_states[i]->state);
2970 break;
2971 case loader::kStateOpenFiles:
2972 cvmfs::file_system_->cache_mgr()->FreeState(
2973 fd_progress, saved_states[i]->state);
2974 break;
2975 case loader::kStateOpenFilesCounter:
2976 SendMsg2Socket(fd_progress, "Releasing open files counter\n");
2977 delete static_cast<uint32_t *>(saved_states[i]->state);
2978 break;
2979 case loader::kStateFuse:
2980 SendMsg2Socket(fd_progress, "Releasing fuse state\n");
2981 delete static_cast<cvmfs::FuseState *>(saved_states[i]->state);
2982 break;
2983 default:
2984 break;
2985 }
2986 }
2987 }
2988
2989
2990 static void __attribute__((constructor)) LibraryMain() {
2991 g_cvmfs_exports = new loader::CvmfsExports();
2992 g_cvmfs_exports->so_version = CVMFS_VERSION;
2993 g_cvmfs_exports->fnAltProcessFlavor = AltProcessFlavor;
2994 g_cvmfs_exports->fnInit = Init;
2995 g_cvmfs_exports->fnSpawn = Spawn;
2996 g_cvmfs_exports->fnFini = Fini;
2997 g_cvmfs_exports->fnGetErrorMsg = GetErrorMsg;
2998 g_cvmfs_exports->fnMaintenanceMode = MaintenanceMode;
2999 g_cvmfs_exports->fnSaveState = SaveState;
3000 g_cvmfs_exports->fnRestoreState = RestoreState;
3001 g_cvmfs_exports->fnFreeSavedState = FreeSavedState;
3002 cvmfs::SetCvmfsOperations(&g_cvmfs_exports->cvmfs_operations);
3003 }
3004
3005
3006 static void __attribute__((destructor)) LibraryExit() {
3007 delete g_cvmfs_exports;
3008 g_cvmfs_exports = NULL;
3009 }
3010