GCC Code Coverage Report


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