GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/loader.cc
Date: 2026-10-25 02:40:14
Exec Total Coverage
Lines: 0 639 0.0%
Branches: 0 1142 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * Implements stub callback functions for Fuse. Their purpose is to
5 * redirect calls to the cvmfs shared library and to block calls during the
6 * update of the library.
7 *
8 * The main executable and the cvmfs shared library _must not_ share any
9 * symbols.
10 */
11
12 #define ENOATTR ENODATA /**< instead of including attr/xattr.h */
13 #define _FILE_OFFSET_BITS 64
14
15
16 #include "loader.h"
17
18 #include <dlfcn.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <sched.h>
22 #include <signal.h>
23 #include <stddef.h>
24 #include <sys/resource.h>
25 #include <time.h>
26 #include <unistd.h>
27 // If valgrind headers are present on the build system, then we can detect
28 // valgrind at runtime.
29 #ifdef HAS_VALGRIND_HEADERS
30 #include <valgrind/valgrind.h>
31 #endif
32
33 #include <cassert>
34 #include <cstdlib>
35 #include <cstring>
36 #include <string>
37 #include <vector>
38
39 #include "fence.h"
40 #include "fuse_main.h"
41 #include "loader_talk.h"
42 #include "options.h"
43 #include "sanitizer.h"
44 #include "util/capabilities.h"
45 #include "util/platform.h"
46 #include "util/exception.h"
47 #include "util/logging.h"
48 #include "util/posix.h"
49 #include "util/string.h"
50 #ifdef CVMFS_NFS_FRONTEND
51 #include "nfs4/fuse_shim_api.h"
52 #endif
53
54 using namespace std; // NOLINT
55
56 // Client library of this stub's frontend, set by cvmfs_add_frontend()
57 #ifndef CVMFS_FRONTEND_LIBRARY
58 #define CVMFS_FRONTEND_LIBRARY "cvmfs_fuse3"
59 #endif
60
61 namespace loader {
62
63 // Follow the fuse convention for option parsing
64 struct CvmfsOptions {
65 char *config;
66 int uid;
67 int gid;
68 int system_mount;
69 int grab_mountpoint;
70 int cvmfs_suid;
71 int disable_watchdog;
72 int simple_options_parsing;
73 int foreground;
74 int fuse_debug;
75 int fuse_passthrough;
76 int backend_fskit;
77
78 // Ignored options
79 int ign_netdev;
80 int ign_user;
81 int ign_nouser;
82 int ign_users;
83 int ign_auto;
84 int ign_noauto;
85 int ign_libfuse;
86 };
87
88 enum {
89 KEY_HELP,
90 KEY_VERSION,
91 KEY_FOREGROUND,
92 KEY_SINGLETHREAD,
93 KEY_FUSE_DEBUG,
94 KEY_CVMFS_DEBUG,
95 KEY_OPTIONS_PARSE,
96 };
97 #define CVMFS_OPT(t, p, v) {t, offsetof(struct CvmfsOptions, p), v}
98 #define CVMFS_SWITCH(t, p) {t, offsetof(struct CvmfsOptions, p), 1}
99 static struct fuse_opt cvmfs_array_opts[] = {
100 CVMFS_OPT("config=%s", config, 0),
101 CVMFS_OPT("uid=%d", uid, 0),
102 CVMFS_OPT("gid=%d", gid, 0),
103 CVMFS_SWITCH("system_mount", system_mount),
104 CVMFS_SWITCH("grab_mountpoint", grab_mountpoint),
105 CVMFS_SWITCH("cvmfs_suid", cvmfs_suid),
106 CVMFS_SWITCH("disable_watchdog", disable_watchdog),
107 CVMFS_SWITCH("simple_options_parsing", simple_options_parsing),
108 CVMFS_SWITCH("foreground", foreground),
109 CVMFS_SWITCH("fuse_debug", fuse_debug),
110 CVMFS_SWITCH("fuse_passthrough", fuse_passthrough),
111 CVMFS_SWITCH("fuse_passthru", fuse_passthrough),
112 CVMFS_SWITCH("obackend=fskit", backend_fskit),
113
114 // Ignore these options
115 CVMFS_SWITCH("_netdev", ign_netdev),
116 CVMFS_SWITCH("user", ign_user),
117 CVMFS_SWITCH("nouser", ign_nouser),
118 CVMFS_SWITCH("users", ign_users),
119 CVMFS_SWITCH("auto", ign_auto),
120 CVMFS_SWITCH("noauto", ign_noauto),
121 // Kept for backwards compatibility with fstab entries written when both
122 // libfuse2 and libfuse3 were supported
123 CVMFS_OPT("libfuse=%d", ign_libfuse, 0),
124
125 FUSE_OPT_KEY("-V", KEY_VERSION),
126 FUSE_OPT_KEY("--version", KEY_VERSION),
127 FUSE_OPT_KEY("-h", KEY_HELP),
128 FUSE_OPT_KEY("--help", KEY_HELP),
129 FUSE_OPT_KEY("-f", KEY_FOREGROUND),
130 FUSE_OPT_KEY("-d", KEY_FUSE_DEBUG),
131 FUSE_OPT_KEY("debug", KEY_CVMFS_DEBUG),
132 FUSE_OPT_KEY("-s", KEY_SINGLETHREAD),
133 FUSE_OPT_KEY("parse", KEY_OPTIONS_PARSE),
134 FUSE_OPT_KEY("-k", KEY_OPTIONS_PARSE),
135 {0, 0, 0},
136 };
137
138
139 string *repository_name_ = NULL;
140 string *mount_point_ = NULL;
141 string *config_files_ = NULL;
142 string *socket_path_ = NULL;
143 string *usyslog_path_ = NULL;
144 int fuse3_max_threads_ = 0;
145 int fuse3_idle_threads_ = 0;
146 uid_t uid_ = 0;
147 gid_t gid_ = 0;
148 bool single_threaded_ = false;
149 bool foreground_ = false;
150 bool debug_mode_ = false;
151 bool system_mount_ = false;
152 bool grab_mountpoint_ = false;
153 bool parse_options_only_ = false;
154 bool suid_mode_ = false;
155 bool premounted_ = false;
156 bool premount_fuse_ = true;
157 bool disable_watchdog_ = false;
158 bool simple_options_parsing_ = false;
159 bool fuse_passthrough_ = false;
160 void *library_handle_;
161 Fence *fence_reload_;
162 CvmfsExports *cvmfs_exports_;
163 LoaderExports *loader_exports_;
164
165
166 ✗ static void Usage(const string &exename) {
167 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
168 "Usage: %s [-h] [-V] [-s] [-f] [-d] [-k] [-o mount_options] "
169 "<repository_name> <mount_point>\n\n"
170 "Mounts a CernVM-FS with the given repository at the given mountpoint.\n"
171 "Usually invoked via autofs or 'mount -t cvmfs <repository_name> <mount_point>'\n\n"
172 "CernVM-FS general options:\n"
173 " -h, --help Print Help output (this)\n"
174 " -V, --version Print CernVM-FS version\n"
175 " -s Run singlethreaded\n"
176 " -f Run in foreground\n"
177 " -d Enable debugging\n"
178 " -k Parse options\n"
179 "CernVM-FS mount options:\n"
180 " -o config=FILES colon-separated path list of config files\n"
181 " -o uid=UID Drop credentials to another user\n"
182 " -o gid=GID Drop credentials to another group\n"
183 " -o system_mount Indicate that mount is system-wide\n"
184 " -o grab_mountpoint give ownership of the mountpoint to the user "
185 "before mounting (required for autofs)\n"
186 " -o parse Parse and print cvmfs parameters\n"
187 " -o cvmfs_suid Enable suid mode\n"
188 " -o debug Enable debug to CVMFS_DEBUGLOG\n"
189 " -o disable_watchdog Do not spawn a post mortem crash handler\n"
190 " -o foreground Run in foreground\n"
191 " -o fuse_passthrough Enables FUSE passthrough (read requests bypass userspace, improves performance)\n"
192 " -o fuse_passthru Alias for fuse_passthrough\n"
193 " -o obackend=fskit Use the fskit object backend\n"
194 "Fuse mount options:\n"
195 " -o allow_other allow access to other users\n"
196 " -o allow_root allow access to root\n"
197 " -o nonempty allow mounts over non-empty directory\n",
198 exename.c_str());
199 }
200
201 /**
202 * For an premounted mountpoint, the argument is the file descriptor to
203 * /dev/fuse provided in the form /dev/fd/%d
204 */
205 ✗ bool CheckPremounted(const std::string &mountpoint) {
206 int len;
207 unsigned fd;
208 ✗ const bool retval = (sscanf(mountpoint.c_str(), "/dev/fd/%u%n", &fd, &len)
209 == 1)
210 ✗ && (len >= 0)
211 ✗ && (static_cast<unsigned>(len) == mountpoint.length());
212 ✗ if (retval) {
213 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
214 "CernVM-FS: pre-mounted on file descriptor %d", fd);
215 ✗ return true;
216 }
217 ✗ return false;
218 }
219
220
221 ✗ static void stub_init(void *userdata, struct fuse_conn_info *conn) {
222 ✗ const FenceGuard fence_guard(fence_reload_);
223 ✗ cvmfs_exports_->cvmfs_operations.init(userdata, conn);
224 }
225
226
227 ✗ static void stub_destroy(void *userdata) {
228 ✗ const FenceGuard fence_guard(fence_reload_);
229 ✗ cvmfs_exports_->cvmfs_operations.destroy(userdata);
230 }
231
232
233 ✗ static void stub_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
234 ✗ const FenceGuard fence_guard(fence_reload_);
235 ✗ cvmfs_exports_->cvmfs_operations.lookup(req, parent, name);
236 }
237
238
239 ✗ static void stub_getattr(fuse_req_t req, fuse_ino_t ino,
240 struct fuse_file_info *fi) {
241 ✗ const FenceGuard fence_guard(fence_reload_);
242 ✗ cvmfs_exports_->cvmfs_operations.getattr(req, ino, fi);
243 }
244
245
246 ✗ static void stub_readlink(fuse_req_t req, fuse_ino_t ino) {
247 ✗ const FenceGuard fence_guard(fence_reload_);
248 ✗ cvmfs_exports_->cvmfs_operations.readlink(req, ino);
249 }
250
251
252 ✗ static void stub_opendir(fuse_req_t req, fuse_ino_t ino,
253 struct fuse_file_info *fi) {
254 ✗ const FenceGuard fence_guard(fence_reload_);
255 ✗ cvmfs_exports_->cvmfs_operations.opendir(req, ino, fi);
256 }
257
258
259 ✗ static void stub_releasedir(fuse_req_t req, fuse_ino_t ino,
260 struct fuse_file_info *fi) {
261 ✗ const FenceGuard fence_guard(fence_reload_);
262 ✗ cvmfs_exports_->cvmfs_operations.releasedir(req, ino, fi);
263 }
264
265
266 ✗ static void stub_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
267 struct fuse_file_info *fi) {
268 ✗ const FenceGuard fence_guard(fence_reload_);
269 ✗ cvmfs_exports_->cvmfs_operations.readdir(req, ino, size, off, fi);
270 }
271
272
273 ✗ static void stub_open(fuse_req_t req, fuse_ino_t ino,
274 struct fuse_file_info *fi) {
275 ✗ const FenceGuard fence_guard(fence_reload_);
276 ✗ cvmfs_exports_->cvmfs_operations.open(req, ino, fi);
277 }
278
279
280 ✗ static void stub_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
281 struct fuse_file_info *fi) {
282 ✗ const FenceGuard fence_guard(fence_reload_);
283 ✗ cvmfs_exports_->cvmfs_operations.read(req, ino, size, off, fi);
284 }
285
286
287 ✗ static void stub_release(fuse_req_t req, fuse_ino_t ino,
288 struct fuse_file_info *fi) {
289 ✗ const FenceGuard fence_guard(fence_reload_);
290 ✗ cvmfs_exports_->cvmfs_operations.release(req, ino, fi);
291 }
292
293
294 ✗ static void stub_statfs(fuse_req_t req, fuse_ino_t ino) {
295 ✗ const FenceGuard fence_guard(fence_reload_);
296 ✗ cvmfs_exports_->cvmfs_operations.statfs(req, ino);
297 }
298
299
300 ✗ static void stub_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
301 size_t size) {
302 ✗ const FenceGuard fence_guard(fence_reload_);
303 ✗ cvmfs_exports_->cvmfs_operations.getxattr(req, ino, name, size);
304 }
305
306
307 ✗ static void stub_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
308 ✗ const FenceGuard fence_guard(fence_reload_);
309 ✗ cvmfs_exports_->cvmfs_operations.listxattr(req, ino, size);
310 }
311
312
313 ✗ static void stub_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
314 ✗ const FenceGuard fence_guard(fence_reload_);
315 ✗ cvmfs_exports_->cvmfs_operations.forget(req, ino, nlookup);
316 }
317
318
319 ✗ static void stub_forget_multi(fuse_req_t req,
320 size_t count,
321 struct fuse_forget_data *forgets) {
322 ✗ const FenceGuard fence_guard(fence_reload_);
323 ✗ cvmfs_exports_->cvmfs_operations.forget_multi(req, count, forgets);
324 }
325
326
327 /**
328 * The callback used when fuse is parsing all the options
329 * We separate CVMFS options from FUSE options here.
330 *
331 * \return On success zero, else non-zero
332 */
333 ✗ static int ParseFuseOptions(void *data __attribute__((unused)), const char *arg,
334 int key, struct fuse_args *outargs) {
335 ✗ unsigned arglen = 0;
336 ✗ if (arg)
337 ✗ arglen = strlen(arg);
338 ✗ switch (key) {
339 ✗ case FUSE_OPT_KEY_OPT:
340 // Check if it a cvmfs option
341 ✗ if ((arglen > 0) && (arg[0] != '-')) {
342 const char **o;
343 ✗ for (o = (const char **)cvmfs_array_opts; *o; o++) {
344 ✗ const unsigned olen = strlen(*o);
345 ✗ if ((arglen > olen && arg[olen] == '=')
346 ✗ && (strncasecmp(arg, *o, olen) == 0))
347 ✗ return 0;
348 }
349 }
350 ✗ return 1;
351
352 ✗ case FUSE_OPT_KEY_NONOPT:
353 // first: repository name, second: mount point
354 ✗ assert(arg != NULL);
355 ✗ if (!repository_name_) {
356 ✗ repository_name_ = new string(arg);
357 } else {
358 ✗ if (mount_point_)
359 ✗ return 1;
360 ✗ mount_point_ = new string(arg);
361 ✗ premounted_ = CheckPremounted(*mount_point_);
362 }
363 ✗ return 0;
364
365 ✗ case KEY_HELP:
366 ✗ Usage(outargs->argv[0]);
367 ✗ exit(0);
368 ✗ case KEY_VERSION:
369 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS version %s\n", CVMFS_VERSION);
370 ✗ exit(0);
371 ✗ case KEY_FOREGROUND:
372 ✗ foreground_ = true;
373 ✗ return 0;
374 ✗ case KEY_SINGLETHREAD:
375 ✗ single_threaded_ = true;
376 ✗ return 0;
377 ✗ case KEY_FUSE_DEBUG:
378 ✗ fuse_opt_add_arg(outargs, "-d");
379 ✗ case KEY_CVMFS_DEBUG:
380 ✗ debug_mode_ = true;
381 ✗ return 0;
382 ✗ case KEY_OPTIONS_PARSE:
383 ✗ parse_options_only_ = true;
384 ✗ return 0;
385 ✗ default:
386 ✗ PANIC(kLogStderr, "internal option parsing error");
387 }
388 }
389
390 ✗ static fuse_args *ParseCmdLine(int argc, char *argv[]) {
391 ✗ struct fuse_args *mount_options = new fuse_args();
392 CvmfsOptions cvmfs_options;
393 ✗ memset(&cvmfs_options, 0, sizeof(cvmfs_options));
394
395 ✗ mount_options->argc = argc;
396 ✗ mount_options->argv = argv;
397 ✗ mount_options->allocated = 0;
398 ✗ if ((fuse_opt_parse(mount_options, &cvmfs_options, cvmfs_array_opts,
399 ParseFuseOptions)
400 != 0)
401 ✗ || !mount_point_ || !repository_name_) {
402 ✗ delete mount_options;
403 ✗ return NULL;
404 }
405 ✗ if (cvmfs_options.config) {
406 ✗ config_files_ = new string(cvmfs_options.config);
407 ✗ free(cvmfs_options.config);
408 }
409 ✗ uid_ = cvmfs_options.uid;
410 ✗ gid_ = cvmfs_options.gid;
411 ✗ system_mount_ = cvmfs_options.system_mount;
412 ✗ grab_mountpoint_ = cvmfs_options.grab_mountpoint;
413 ✗ suid_mode_ = cvmfs_options.cvmfs_suid;
414 ✗ disable_watchdog_ = cvmfs_options.disable_watchdog;
415 ✗ simple_options_parsing_ = cvmfs_options.simple_options_parsing;
416 ✗ if (cvmfs_options.foreground) {
417 ✗ foreground_ = true;
418 }
419 ✗ if (cvmfs_options.fuse_debug) {
420 ✗ fuse_opt_add_arg(mount_options, "-d");
421 }
422 ✗ fuse_passthrough_ = cvmfs_options.fuse_passthrough;
423 ✗ if (cvmfs_options.backend_fskit) {
424 ✗ fuse_opt_add_arg(mount_options, "-obackend=fskit");
425 // FSKit manages the mount lifecycle; daemonizing would break the
426 // FSKit connection, so we force foreground mode.
427 ✗ foreground_ = true;
428 ✗ disable_watchdog_ = true;
429 }
430
431 ✗ return mount_options;
432 }
433
434 ✗ static bool MatchFuseOption(const fuse_args *mount_options, const char *opt) {
435 ✗ for (int i = 0; i < mount_options->argc; i++) {
436 ✗ char *arg = mount_options->argv[i];
437 ✗ char *p = strstr(arg, opt);
438 ✗ if (p != NULL) {
439 ✗ if (p == arg)
440 ✗ return true;
441 ✗ const char c = *(p - 1);
442 ✗ if ((c == ',') || (c == ' '))
443 ✗ return true;
444 ✗ if ((c == 'o') && (p >= arg + 2) && (*(p - 2) == '-'))
445 ✗ return true;
446 }
447 }
448 ✗ return false;
449 }
450
451 ✗ static void SetFuseOperations(struct fuse_lowlevel_ops *loader_operations) {
452 ✗ memset(loader_operations, 0, sizeof(*loader_operations));
453
454 ✗ loader_operations->init = stub_init;
455 ✗ loader_operations->destroy = stub_destroy;
456
457 ✗ loader_operations->lookup = stub_lookup;
458 ✗ loader_operations->getattr = stub_getattr;
459 ✗ loader_operations->readlink = stub_readlink;
460 ✗ loader_operations->open = stub_open;
461 ✗ loader_operations->read = stub_read;
462 ✗ loader_operations->release = stub_release;
463 ✗ loader_operations->opendir = stub_opendir;
464 ✗ loader_operations->readdir = stub_readdir;
465 ✗ loader_operations->releasedir = stub_releasedir;
466 ✗ loader_operations->statfs = stub_statfs;
467 ✗ loader_operations->getxattr = stub_getxattr;
468 ✗ loader_operations->listxattr = stub_listxattr;
469 ✗ loader_operations->forget = stub_forget;
470 }
471
472
473 ✗ static void *OpenLibrary(const string &path) {
474 ✗ return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
475 }
476
477
478 ✗ static void CloseLibrary() {
479 #ifdef HAS_VALGRIND_HEADERS
480 // If the libcvmfs_fuse library is unloaded, valgrind can't resolve the
481 // symbols anymore. We skip under valgrind.
482 ✗ if (!RUNNING_ON_VALGRIND) {
483 #endif
484 ✗ dlclose(library_handle_);
485 ✗ library_handle_ = NULL;
486 #ifdef HAS_VALGRIND_HEADERS
487 }
488 #endif
489 }
490
491
492 ✗ static CvmfsExports *LoadLibrary(const bool debug_mode,
493 LoaderExports *loader_exports) {
494 ✗ std::string local_lib_path = "./";
495 ✗ if (getenv("CVMFS_LIBRARY_PATH") != NULL) {
496 ✗ local_lib_path = getenv("CVMFS_LIBRARY_PATH");
497 ✗ if (!local_lib_path.empty() && (*local_lib_path.rbegin() != '/'))
498 ✗ local_lib_path.push_back('/');
499 }
500
501 string library_name =
502 ✗ string(CVMFS_FRONTEND_LIBRARY) + ((debug_mode) ? "_debug" : "");
503 ✗ library_name = platform_libname(library_name);
504 ✗ string error_messages;
505
506 // build search paths for library
507 const vector<string> library_paths = {
508 local_lib_path + library_name,
509 "/usr/lib/" + library_name,
510 "/usr/lib64/" + library_name,
511 #ifdef __APPLE__
512 // Since OS X El Capitan (10.11) came with SIP, we needed to relocate our
513 // binaries from /usr/... to /usr/local/...
514 "/usr/local/lib/" + library_name,
515 #endif
516 ✗ };
517
518 // dlopen the library at the first good search path
519 ✗ for (const string &path : library_paths) {
520 ✗ library_handle_ = OpenLibrary(path);
521 ✗ if (nullptr != library_handle_) {
522 ✗ break;
523 }
524 ✗ error_messages += string(dlerror()) + "\n";
525 }
526
527 ✗ if (!library_handle_) {
528 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
529 "failed to load cvmfs library, tried: '%s'\n%s",
530 JoinStrings(library_paths, "' '").c_str(), error_messages.c_str());
531 ✗ return NULL;
532 }
533
534 CvmfsExports **exports_ptr = reinterpret_cast<CvmfsExports **>(
535 ✗ dlsym(library_handle_, "g_cvmfs_exports"));
536 ✗ if (!exports_ptr)
537 ✗ return NULL;
538
539 ✗ if (loader_exports) {
540 ✗ LoadEvent *load_event = new LoadEvent();
541 ✗ load_event->timestamp = time(NULL);
542 ✗ load_event->so_version = (*exports_ptr)->so_version;
543 ✗ loader_exports->history.push_back(load_event);
544 }
545
546 ✗ return *exports_ptr;
547 }
548
549
550 ✗ Failures Reload(const int fd_progress, const bool stop_and_go,
551 const ReloadMode reload_mode) {
552 int retval;
553
554 // for legacy call we take the current state of debug_mode_
555 ✗ if (reload_mode == kReloadDebug) {
556 ✗ debug_mode_ = true;
557 ✗ } else if (reload_mode == kReloadNoDebug) {
558 ✗ debug_mode_ = false;
559 }
560
561 ✗ retval = cvmfs_exports_->fnMaintenanceMode(fd_progress);
562 ✗ if (!retval)
563 ✗ return kFailMaintenanceMode;
564
565 ✗ SendMsg2Socket(fd_progress, "Blocking new file system calls\n");
566 ✗ fence_reload_->Close();
567
568 ✗ SendMsg2Socket(fd_progress, "Waiting for active file system calls\n");
569 ✗ fence_reload_->Drain();
570
571 ✗ retval = cvmfs_exports_->fnSaveState(fd_progress,
572 ✗ &loader_exports_->saved_states);
573 ✗ if (!retval)
574 ✗ return kFailSaveState;
575
576 ✗ SendMsg2Socket(fd_progress, "Unloading Fuse module\n");
577 ✗ cvmfs_exports_->fnFini();
578 ✗ CloseLibrary();
579
580 ✗ if (stop_and_go) {
581 ✗ CreateFile(*socket_path_ + ".paused", 0600);
582 ✗ SendMsg2Socket(fd_progress, "Waiting for the delivery of SIGUSR1...\n");
583 ✗ WaitForSignal(SIGUSR1);
584 ✗ unlink((*socket_path_ + ".paused").c_str());
585 }
586
587 ✗ SendMsg2Socket(fd_progress, "Re-Loading Fuse module\n");
588 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, loader_exports_);
589 ✗ if (!cvmfs_exports_)
590 ✗ return kFailLoadLibrary;
591 ✗ retval = cvmfs_exports_->fnInit(loader_exports_);
592 ✗ if (retval != kFailOk) {
593 ✗ const string msg_progress = cvmfs_exports_->fnGetErrorMsg() + " ("
594 ✗ + StringifyInt(retval) + ")\n";
595 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s", msg_progress.c_str());
596 ✗ SendMsg2Socket(fd_progress, msg_progress);
597 ✗ return (Failures)retval;
598 }
599
600 ✗ retval = cvmfs_exports_->fnRestoreState(fd_progress,
601 ✗ loader_exports_->saved_states);
602 ✗ if (!retval)
603 ✗ return kFailRestoreState;
604 ✗ cvmfs_exports_->fnFreeSavedState(fd_progress, loader_exports_->saved_states);
605 ✗ for (unsigned i = 0, l = loader_exports_->saved_states.size(); i < l; ++i) {
606 ✗ delete loader_exports_->saved_states[i];
607 }
608 ✗ loader_exports_->saved_states.clear();
609
610 ✗ SendMsg2Socket(fd_progress, "Activating Fuse module\n");
611 ✗ cvmfs_exports_->fnSpawn();
612
613 ✗ fence_reload_->Open();
614 ✗ return kFailOk;
615 }
616
617 } // namespace loader
618
619
620 using namespace loader; // NOLINT(build/namespaces)
621
622 #ifdef CVMFS_NFS_FRONTEND
623 /**
624 * Detaches like Daemonize(), but the calling process exits only once the
625 * daemon reports that the NFS mount is in place, with a matching exit code.
626 * Until then the daemon keeps stdout/stderr, so that startup errors still
627 * reach the terminal; libcvmfs_nfsfuse redirects them once the mount is ready.
628 */
629 static void DaemonizeUntilMounted() {
630 int pipe_ready[2];
631 MakePipe(pipe_ready);
632 pid_t pid = fork();
633 assert(pid >= 0);
634 if (pid > 0) {
635 close(pipe_ready[1]);
636 char status = 1;
637 const ssize_t retval = read(pipe_ready[0], &status, 1);
638 _exit((retval == 1 && status == 0) ? kFailOk : kFailMount);
639 }
640 close(pipe_ready[0]);
641 const int retval = setsid();
642 assert(retval != -1);
643 pid = fork();
644 assert(pid >= 0);
645 if (pid > 0)
646 _exit(0);
647 cvmfs_nfsfuse_set_ready_fd(pipe_ready[1]);
648 }
649 #endif
650
651
652 ✗ int FuseMain(int argc, char *argv[]) {
653 // Set a decent umask for new files (no write access to group/everyone).
654 // We want to allow group write access for the talk-socket.
655 ✗ umask(007);
656 // SIGUSR1 is used for the stop_and_go mode during reload
657 ✗ BlockSignal(SIGUSR1);
658
659 int retval;
660
661 // Jump into alternative process flavors (e.g. shared cache manager)
662 // We are here due to a fork+execve (ManagedExec in util.cc) or due to
663 // utility calls of cvmfs2
664 ✗ if ((argc > 1) && (strstr(argv[1], "__") == argv[1])) {
665 ✗ if (string(argv[1]) == string("__RELOAD__")) {
666 ✗ if (argc < 3)
667 ✗ return 1;
668 ✗ bool stop_and_go = false;
669 ✗ if ((argc > 3) && (string(argv[3]) == "stop_and_go"))
670 ✗ stop_and_go = true;
671
672 // always last param of the cvmfs2 __RELOAD__ command
673 // check if debug mode is requested
674 // NOTE:
675 // debug mode is decided based on CVMFS_DEBUGLOG being set or not
676 // this means: reloading is now always based on CVMFS_DEBUGLOG, and
677 // reload ignores the current state
678 //
679 // if you mount with debug but do not set CVMFS_DEBUGLOG and reload,
680 // debug will be turned off
681 ✗ if (std::string(argv[argc - 1]) == std::string("--debug")) {
682 ✗ debug_mode_ = true;
683 } else {
684 ✗ debug_mode_ = false;
685 }
686 ✗ retval = loader_talk::MainReload(argv[2], stop_and_go, debug_mode_);
687
688 ✗ if ((retval != 0) && (stop_and_go)) {
689 ✗ CreateFile(string(argv[2]) + ".paused.crashed", 0600);
690 }
691 ✗ return retval;
692 }
693
694 ✗ if (string(argv[1]) == string("__MK_ALIEN_CACHE__")) {
695 ✗ if (argc < 5)
696 ✗ return 1;
697 ✗ const string alien_cache_dir = argv[2];
698 ✗ const sanitizer::PositiveIntegerSanitizer sanitizer;
699 ✗ if (!sanitizer.IsValid(argv[3]) || !sanitizer.IsValid(argv[4]))
700 ✗ return 1;
701 ✗ const uid_t uid_owner = String2Uint64(argv[3]);
702 ✗ const gid_t gid_owner = String2Uint64(argv[4]);
703
704 ✗ int retval = MkdirDeep(alien_cache_dir, 0770);
705 ✗ if (!retval) {
706 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to create %s",
707 alien_cache_dir.c_str());
708 ✗ return 1;
709 }
710 ✗ retval = chown(alien_cache_dir.c_str(), uid_owner, gid_owner);
711 ✗ if (retval != 0) {
712 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to set owner of %s to %d:%d",
713 alien_cache_dir.c_str(), uid_owner, gid_owner);
714 ✗ return 1;
715 }
716 ✗ retval = SwitchCredentials(uid_owner, gid_owner, false /* temporarily */);
717 ✗ if (!retval) {
718 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to impersonate %d:%d",
719 uid_owner, gid_owner);
720 ✗ return 1;
721 }
722 // Allow access to user and group
723 ✗ retval = MakeCacheDirectories(alien_cache_dir, 0770);
724 ✗ if (!retval) {
725 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to create cache skeleton");
726 ✗ return 1;
727 }
728 ✗ return 0;
729 }
730
731 ✗ debug_mode_ = getenv("__CVMFS_DEBUG_MODE__") != NULL;
732 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, NULL);
733 ✗ if (!cvmfs_exports_)
734 ✗ return kFailLoadLibrary;
735 ✗ return cvmfs_exports_->fnAltProcessFlavor(argc, argv);
736 }
737
738 // Option parsing
739 struct fuse_args *mount_options;
740 ✗ mount_options = ParseCmdLine(argc, argv);
741 ✗ if (!mount_options) {
742 ✗ Usage(argv[0]);
743 ✗ return kFailOptions;
744 }
745
746 ✗ string parameter;
747 OptionsManager *options_manager;
748 ✗ bool restore_origids = false;
749 uid_t origuid;
750 gid_t origgid;
751 ✗ if (simple_options_parsing_) {
752 ✗ options_manager = new SimpleOptionsParser(
753 ✗ new DefaultOptionsTemplateManager(*repository_name_));
754 } else {
755 ✗ if ((uid_ != 0) || (gid_ != 0)) {
756 // temporarily switch to requested uid/gid while running bash parser
757 ✗ origuid = geteuid();
758 ✗ origgid = getegid();
759 ✗ if ((uid_ != origuid) || (gid_ != origgid)) {
760 ✗ if (!SwitchCredentials(uid_, gid_, true /* temporarily */)) {
761 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
762 "Failed to switch credentials for options parser");
763 ✗ return kFailPermission;
764 }
765 ✗ restore_origids = true;
766 }
767 }
768 ✗ options_manager = new BashOptionsManager(
769 ✗ new DefaultOptionsTemplateManager(*repository_name_));
770 }
771 ✗ if (config_files_) {
772 ✗ const vector<string> tokens = SplitString(*config_files_, ':');
773 ✗ for (unsigned i = 0, s = tokens.size(); i < s; ++i) {
774 ✗ options_manager->ParsePath(tokens[i], false);
775 }
776 ✗ } else {
777 ✗ options_manager->ParseDefault(*repository_name_);
778 }
779 ✗ if (restore_origids) {
780 ✗ if (!SwitchCredentials(origuid, origgid, true /* temporarily */)) {
781 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
782 "Failed to switch credentials back after options parser");
783 ✗ return kFailPermission;
784 }
785 }
786
787 ✗ if (options_manager->GetValue("CVMFS_PREMOUNT_FUSE", &parameter)
788 ✗ && options_manager->IsOff(parameter)) {
789 ✗ premount_fuse_ = false;
790 }
791 #ifdef CVMFS_NFS_FRONTEND
792 // The file system is mounted through the kernel's NFS client
793 premount_fuse_ = false;
794 // Settings of libcvmfs_nfsfuse, which only sees the environment
795 if (options_manager->GetValue("CVMFS_NFS_DELEGATIONS", &parameter))
796 setenv("CVMFS_NFS_DELEGATIONS", parameter.c_str(), 1);
797 if (options_manager->GetValue("CVMFS_NFS_MOUNT_OPTIONS", &parameter))
798 setenv("CVMFS_NFS_MOUNT_OPTIONS", parameter.c_str(), 1);
799 #endif
800
801 ✗ if (options_manager->GetValue("CVMFS_REPOSITORIES_NOMOUNT", &parameter)
802 ✗ && !parameter.empty()) {
803 ✗ const vector<string> ignored_repos = SplitString(parameter, ',');
804 ✗ for (unsigned i = 0, s = ignored_repos.size(); i < s; ++i) {
805 ✗ if (Trim(ignored_repos[i]) == *repository_name_) {
806 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
807 "CernVM-FS: mount attempt for %s ignored "
808 "(listed in CVMFS_REPOSITORIES_NOMOUNT)",
809 repository_name_->c_str());
810 ✗ delete options_manager;
811 ✗ fuse_opt_free_args(mount_options);
812 ✗ delete mount_options;
813 ✗ return kFailIgnoredMount;
814 }
815 }
816 }
817
818 #ifdef __APPLE__
819 string volname = "-ovolname=" + *repository_name_;
820 fuse_opt_add_arg(mount_options, volname.c_str());
821 // Allow for up to 5 minute "hangs" before OS X may kill cvmfs
822 fuse_opt_add_arg(mount_options, "-odaemon_timeout=300");
823 fuse_opt_add_arg(mount_options, "-onoapplexattr");
824 // Should libfuse be single-threaded? See CVM-871, CVM-855
825 // single_threaded_ = true;
826 #endif
827 ✗ if (options_manager->GetValue("CVMFS_MOUNT_RW", &parameter)
828 ✗ && options_manager->IsOn(parameter)) {
829 ✗ fuse_opt_add_arg(mount_options, "-orw");
830 } else {
831 ✗ fuse_opt_add_arg(mount_options, "-oro");
832 }
833 ✗ fuse_opt_add_arg(mount_options, "-onodev");
834 ✗ if (options_manager->GetValue("CVMFS_SUID", &parameter)
835 ✗ && options_manager->IsOn(parameter)) {
836 ✗ suid_mode_ = true;
837 }
838 ✗ if (suid_mode_) {
839 ✗ if (getuid() != 0) {
840 ✗ PANIC(kLogStderr | kLogSyslogErr,
841 "must be root to mount with suid option");
842 }
843 ✗ fuse_opt_add_arg(mount_options, "-osuid");
844 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running with suid support");
845 }
846
847 ✗ if (options_manager->GetValue("CVMFS_CPU_AFFINITY", &parameter)) {
848 #ifndef __APPLE__
849 cpu_set_t mask;
850 ✗ const vector<string> cpus = SplitString(parameter, ',');
851 ✗ CPU_ZERO(&mask);
852 ✗ for (const string &cpu : cpus) {
853 ✗ CPU_SET(String2Uint64(Trim(cpu)), &mask);
854 }
855 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: setting CPU Affinity to %s",
856 parameter.c_str());
857 ✗ const int err = sched_setaffinity(0, sizeof(mask), &mask);
858 ✗ if (err != 0) {
859 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogErr,
860 "Setting CPU Affinity failed with error %d", errno);
861 }
862 #else
863 LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogErr,
864 "CPU affinity setting not supported on macOS");
865 #endif
866 }
867 ✗ loader_exports_ = new LoaderExports();
868 ✗ loader_exports_->loader_version = CVMFS_VERSION;
869 ✗ loader_exports_->boot_time = time(NULL);
870 ✗ loader_exports_->program_name = argv[0];
871 ✗ loader_exports_->foreground = foreground_;
872 ✗ loader_exports_->repository_name = *repository_name_;
873 ✗ loader_exports_->mount_point = *mount_point_;
874 ✗ loader_exports_->device_id = "0:0"; // initially unknown, set after mount
875 ✗ loader_exports_->disable_watchdog = disable_watchdog_;
876 ✗ loader_exports_->simple_options_parsing = simple_options_parsing_;
877 ✗ loader_exports_->fuse_passthrough = fuse_passthrough_;
878 ✗ if (options_manager->GetValue("CVMFS_FUSE_PASSTHROUGH", &parameter)) {
879 // CVMFS_FUSE_PASSTHROUGH set to on in configs enables the feature.
880 // Presence of mount option can also enable the feature (but not disable it).
881 ✗ loader_exports_->fuse_passthrough |= options_manager->IsOn(parameter);
882 }
883 ✗ if (config_files_)
884 ✗ loader_exports_->config_files = *config_files_;
885 else
886 ✗ loader_exports_->config_files = "";
887
888 ✗ if (parse_options_only_) {
889 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "# CernVM-FS parameters:\n%s",
890 options_manager->Dump().c_str());
891 ✗ return 0;
892 }
893
894 // Logging
895 ✗ if (options_manager->GetValue("CVMFS_SYSLOG_LEVEL", &parameter))
896 ✗ SetLogSyslogLevel(String2Uint64(parameter));
897 else
898 ✗ SetLogSyslogLevel(3);
899 ✗ if (options_manager->GetValue("CVMFS_SYSLOG_FACILITY", &parameter))
900 ✗ SetLogSyslogFacility(String2Int64(parameter));
901 ✗ SetLogSyslogPrefix(*repository_name_);
902 // Deferr setting usyslog until credentials are dropped
903
904 // Permissions check
905 ✗ if (options_manager->GetValue("CVMFS_CHECK_PERMISSIONS", &parameter)) {
906 ✗ if (options_manager->IsOn(parameter)) {
907 ✗ fuse_opt_add_arg(mount_options, "-odefault_permissions");
908 }
909 }
910
911 ✗ if (!premounted_ && !DirectoryExists(*mount_point_)) {
912 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
913 "Mount point %s does not exist", mount_point_->c_str());
914 ✗ return kFailPermission;
915 }
916
917 // Number of file descriptors
918 ✗ if (options_manager->GetValue("CVMFS_NFILES", &parameter)) {
919 ✗ const int retval = SetLimitNoFile(String2Uint64(parameter));
920 ✗ if (retval == -2) {
921 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running under valgrind");
922 ✗ } else if (retval == -1) {
923 ✗ if (system_mount_) {
924 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
925 "Failed to set maximum number of open files, "
926 "insufficient permissions");
927 ✗ return kFailPermission;
928 }
929 unsigned soft_limit, hard_limit;
930 ✗ GetLimitNoFile(&soft_limit, &hard_limit);
931 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogWarn,
932 "Failed to set requested number of open files, "
933 "using maximum number %u",
934 hard_limit);
935 ✗ if (hard_limit > soft_limit) {
936 ✗ (void)SetLimitNoFile(hard_limit);
937 }
938 }
939 }
940
941 // Apply OOM score adjustment
942 ✗ if (options_manager->GetValue("CVMFS_OOM_SCORE_ADJ", &parameter)) {
943 ✗ const string proc_path = "/proc/" + StringifyInt(getpid())
944 ✗ + "/oom_score_adj";
945 ✗ const int fd_oom = open(proc_path.c_str(), O_WRONLY);
946 ✗ if (fd_oom < 0) {
947 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn, "failed to open %s",
948 proc_path.c_str());
949 } else {
950 ✗ const bool retval = SafeWrite(fd_oom, parameter.data(),
951 parameter.length());
952 ✗ if (!retval) {
953 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
954 "failed to set OOM score adjustment to %s", parameter.c_str());
955 }
956 ✗ close(fd_oom);
957 }
958 }
959
960 // Protect the process from being killed by systemd
961 ✗ if (options_manager->GetValue("CVMFS_SYSTEMD_NOKILL", &parameter)
962 ✗ && options_manager->IsOn(parameter)) {
963 ✗ argv[0][0] = '@';
964 }
965
966 // Grab mountpoint
967 ✗ if (grab_mountpoint_) {
968 ✗ if ((chown(mount_point_->c_str(), uid_, gid_) != 0)
969 ✗ || (chmod(mount_point_->c_str(), 0755) != 0)) {
970 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
971 "Failed to grab mountpoint %s (%d)", mount_point_->c_str(),
972 errno);
973 ✗ return kFailPermission;
974 }
975 }
976
977
978 // these need to be declared before start using goto
979 ✗ int fd_mountinfo = -1;
980 ✗ const bool delegated_unmount = (!suid_mode_ && !disable_watchdog_);
981 ✗ bool dounmount = false;
982 ✗ int premount_fd = -1;
983 ✗ struct fuse_session *session = NULL;
984
985 // Drop credentials, most likely temporarily since by default there is
986 // a watchdog
987 ✗ if ((uid_ != 0) || (gid_ != 0)) {
988 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running with credentials %d:%d",
989 uid_, gid_);
990 ✗ const bool retrievable = (suid_mode_ || !disable_watchdog_);
991 ✗ if (!SwitchCredentials(uid_, gid_, retrievable)) {
992 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
993 "Failed to drop credentials");
994 ✗ retval = kFailPermission;
995 ✗ goto cleanup;
996 }
997 }
998 ✗ if (disable_watchdog_) {
999 LogCvmfs(kLogCvmfs, kLogDebug, "No watchdog, enabling core files");
1000 ✗ if (!platform_set_dumpable()) {
1001 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogWarning, "Failed to set process dumpable");
1002 }
1003 }
1004
1005 // Only set usyslog now, otherwise file permissions are wrong
1006 ✗ usyslog_path_ = new string();
1007 ✗ if (options_manager->GetValue("CVMFS_USYSLOG", &parameter))
1008 ✗ *usyslog_path_ = parameter;
1009 ✗ SetLogMicroSyslog(*usyslog_path_);
1010
1011 ✗ if (single_threaded_) {
1012 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
1013 "CernVM-FS: running in single threaded mode");
1014 }
1015 ✗ if (debug_mode_) {
1016 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogWarn,
1017 "CernVM-FS: running in debug mode");
1018 }
1019
1020 #ifndef FUSE_CAP_POSIX_ACL
1021 if (options_manager->GetValue("CVMFS_ENFORCE_ACLS", &parameter)
1022 && options_manager->IsOn(parameter)) {
1023 LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1024 "CernVM-FS: ACL support requested but not available in this "
1025 "version of libfuse");
1026 retval = kFailPermission;
1027 goto cleanup;
1028 }
1029 #endif
1030
1031 // Initialize the loader socket, connections are not accepted until Spawn()
1032 ✗ socket_path_ = new string("/var/run/cvmfs");
1033 ✗ if (options_manager->GetValue("CVMFS_RELOAD_SOCKETS", &parameter))
1034 ✗ *socket_path_ = MakeCanonicalPath(parameter);
1035 ✗ *socket_path_ += "/cvmfs." + *repository_name_;
1036 ✗ retval = loader_talk::Init(*socket_path_);
1037 ✗ if (!retval) {
1038 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1039 "Failed to initialize loader socket");
1040 ✗ retval = kFailLoaderTalk;
1041 ✗ goto cleanup;
1042 }
1043
1044 // TODO(jblomer): we probably want to apply a default setting related to the
1045 // number of cores.
1046 ✗ if (options_manager->GetValue("CVMFS_FUSE3_MAX_THREADS", &parameter)) {
1047 ✗ fuse3_max_threads_ = String2Int64(parameter);
1048 }
1049 ✗ if (options_manager->GetValue("CVMFS_FUSE3_IDLE_THREADS", &parameter)) {
1050 ✗ fuse3_idle_threads_ = String2Int64(parameter);
1051 }
1052 #ifdef CVMFS_ENABLE_FUSE3_LOOP_CONFIG
1053 if (fuse3_max_threads_) {
1054 LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: Fuse3 max_threads=%d",
1055 fuse3_max_threads_);
1056 }
1057 if (fuse3_idle_threads_) {
1058 LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: Fuse3 min_idle_threads=%d",
1059 fuse3_idle_threads_);
1060 }
1061 #else
1062 ✗ if (fuse3_max_threads_ || fuse3_idle_threads_) {
1063 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
1064 "CernVM-FS: ignoring fuse3 thread settings (libfuse too old)");
1065 }
1066 #endif
1067
1068 // Options are not needed anymore
1069 ✗ delete options_manager;
1070 ✗ options_manager = NULL;
1071
1072 #ifdef CVMFS_NFS_FRONTEND
1073 // Fork before the cvmfs library opens its databases: macOS' libsqlite3
1074 // invalidates connections that are used again after fork()
1075 if (!foreground_)
1076 DaemonizeUntilMounted();
1077 #endif
1078 ✗ loader_exports_->fuse_session = &session;
1079
1080 // Load and initialize cvmfs library
1081 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak,
1082 "CernVM-FS: loading Fuse module... ");
1083 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, loader_exports_);
1084 ✗ if (!cvmfs_exports_) {
1085 ✗ retval = kFailLoadLibrary;
1086 ✗ goto cleanup;
1087 }
1088 ✗ retval = cvmfs_exports_->fnInit(loader_exports_);
1089 ✗ if (retval != kFailOk) {
1090 ✗ if (retval == kFailDoubleMount) {
1091 ✗ LogCvmfs(kLogCvmfs, kLogStderr,
1092 "\nCernVM-FS: repository %s already mounted on %s",
1093 loader_exports_->repository_name.c_str(),
1094 loader_exports_->mount_point.c_str());
1095 ✗ return 0;
1096 }
1097 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr, "%s (%d - %s)",
1098 cvmfs_exports_->fnGetErrorMsg().c_str(), retval,
1099 Code2Ascii((Failures)retval));
1100 ✗ cvmfs_exports_->fnFini();
1101 ✗ goto cleanup;
1102 }
1103 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "done");
1104
1105 // Mount
1106 ✗ fence_reload_ = new Fence();
1107
1108 ✗ if (suid_mode_) {
1109 ✗ const bool retrievable = true;
1110 ✗ if (!SwitchCredentials(0, getgid(), retrievable)) {
1111 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1112 "failed to re-gain root permissions for mounting");
1113 ✗ cvmfs_exports_->fnFini();
1114 ✗ retval = kFailPermission;
1115 ✗ goto cleanup;
1116 }
1117 }
1118
1119 #ifndef __APPLE__
1120 ✗ if (!premounted_ && !suid_mode_ && getuid() == 0 && premount_fuse_) {
1121 // If not already premounted or using suid mode, premount the fuse
1122 // mountpoint to avoid the need for fusermount.
1123 // Requires libfuse >= 3.3.0.
1124 //
1125 ✗ if ((uid_ != 0) || (gid_ != 0)) {
1126 ✗ if (!SwitchCredentials(0, getgid(), true /* temporarily */)) {
1127 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1128 "failed to re-gain root permissions for mounting");
1129 ✗ retval = kFailPermission;
1130 ✗ goto cleanup;
1131 }
1132 }
1133 platform_stat64 info;
1134 // Need to know if it is a directory or not
1135 ✗ if (platform_stat(mount_point_->c_str(), &info) != 0) {
1136 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1137 "Failed to stat mountpoint %s (%d)", mount_point_->c_str(),
1138 errno);
1139 ✗ return kFailPermission;
1140 }
1141 ✗ premount_fd = open("/dev/fuse", O_RDWR);
1142 ✗ if (premount_fd == -1) {
1143 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1144 "Failed to open /dev/fuse (%d)", errno);
1145 ✗ return kFailPermission;
1146 }
1147 ✗ dounmount = true;
1148 char opts[128];
1149 ✗ snprintf(
1150 opts, sizeof(opts), "fd=%i,rootmode=%o,user_id=0,group_id=0%s%s",
1151 ✗ premount_fd, info.st_mode & S_IFMT,
1152 ✗ MatchFuseOption(mount_options, "default_permissions")
1153 ? ",default_permissions"
1154 : "",
1155 ✗ MatchFuseOption(mount_options, "allow_other") ? ",allow_other" : "");
1156 ✗ unsigned long flags = MS_NOSUID | MS_NODEV | MS_RELATIME;
1157 // Note that during the handling of the `CVMFS_MOUNT_RW` option, we ensure
1158 // that at least one of `rw` or `ro` is part of the mount option string (we
1159 // won't have both unset). If both `rw` and `ro` are set, the read-only
1160 // option takes precedence.
1161 ✗ if (MatchFuseOption(mount_options, "ro")) {
1162 ✗ flags |= MS_RDONLY;
1163 }
1164 ✗ if (mount("cvmfs2", mount_point_->c_str(), "fuse", flags, opts) == -1) {
1165 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1166 "Failed to mount -t fuse -o %s cvmfs2 %s (%d)", opts,
1167 mount_point_->c_str(), errno);
1168 ✗ return kFailPermission;
1169 }
1170
1171 // Drop credentials
1172 ✗ if ((uid_ != 0) || (gid_ != 0)) {
1173 ✗ if (!SwitchCredentials(uid_, gid_, true /*retrievable*/)) {
1174 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1175 "Failed to drop credentials");
1176 ✗ retval = kFailPermission;
1177 ✗ goto cleanup;
1178 }
1179 }
1180 }
1181 #endif
1182
1183 struct fuse_lowlevel_ops loader_operations;
1184 ✗ SetFuseOperations(&loader_operations);
1185 ✗ if (cvmfs_exports_->cvmfs_operations.forget_multi)
1186 ✗ loader_operations.forget_multi = stub_forget_multi;
1187
1188 ✗ session = fuse_session_new(mount_options, &loader_operations,
1189 sizeof(loader_operations), NULL);
1190 ✗ if (!session) {
1191 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1192 "failed to create Fuse session");
1193 ✗ cvmfs_exports_->fnFini();
1194 ✗ retval = kFailMount;
1195 ✗ goto cleanup;
1196 }
1197 ✗ if (premount_fd >= 0) {
1198 char premount_str[64];
1199 ✗ snprintf(premount_str, sizeof(premount_str), "/dev/fd/%d", premount_fd);
1200 ✗ retval = fuse_session_mount(session, premount_str);
1201 } else {
1202 ✗ retval = fuse_session_mount(session, mount_point_->c_str());
1203 }
1204 ✗ if (retval != 0) {
1205 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1206 "failed to mount file system");
1207 ✗ cvmfs_exports_->fnFini();
1208 ✗ retval = kFailMount;
1209 ✗ goto cleanup;
1210 }
1211
1212 ✗ if (suid_mode_) {
1213 // Drop credentials again for now
1214 ✗ const bool retrievable = !disable_watchdog_;
1215 ✗ if (!SwitchCredentials(uid_, gid_, retrievable)) {
1216 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1217 "failed to drop permissions after mounting");
1218 ✗ cvmfs_exports_->fnFini();
1219 ✗ retval = kFailPermission;
1220 ✗ goto cleanup;
1221 }
1222 }
1223 #ifndef __APPLE__
1224 ✗ else if (getuid() != geteuid()) {
1225 // Switch to using only capabilities before starting threads
1226 // because switching the uid after threads are created affects
1227 // all threads and causes race conditions.
1228 ✗ platform_keepcaps(true);
1229 ✗ if (!SwitchCredentials(uid_, gid_, false /* temporarily */)) {
1230 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1231 "failed to switch to only capabilities after mounting");
1232 ✗ cvmfs_exports_->fnFini();
1233 ✗ retval = kFailPermission;
1234 ✗ goto cleanup;
1235 }
1236 ✗ platform_keepcaps(false);
1237 }
1238 #endif
1239
1240 // Determine device id
1241 ✗ fd_mountinfo = open("/proc/self/mountinfo", O_RDONLY);
1242 ✗ if (fd_mountinfo > 0) {
1243 ✗ std::string line;
1244 ✗ while (GetLineFd(fd_mountinfo, &line)) {
1245 ✗ std::vector<std::string> tokens = SplitString(line, ' ');
1246 ✗ if (tokens.size() < 5)
1247 ✗ continue;
1248 ✗ if (tokens[4] != loader_exports_->mount_point)
1249 ✗ continue;
1250 ✗ unsigned i = 5;
1251 ✗ for (; i < tokens.size(); ++i) {
1252 ✗ if (tokens[i] == "-")
1253 ✗ break;
1254 }
1255 ✗ if (tokens.size() < i + 3)
1256 ✗ continue;
1257 ✗ if (tokens[i + 2] != "cvmfs2")
1258 ✗ continue;
1259 ✗ loader_exports_->device_id = tokens[2];
1260 ✗ break;
1261 }
1262 ✗ close(fd_mountinfo);
1263 }
1264
1265 ✗ if (!premounted_) {
1266 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: mounted cvmfs on %s",
1267 mount_point_->c_str());
1268 }
1269 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: linking %s to repository %s",
1270 mount_point_->c_str(), repository_name_->c_str());
1271 #ifndef CVMFS_NFS_FRONTEND
1272 ✗ if (!foreground_)
1273 ✗ Daemonize();
1274 #endif
1275
1276 // Note that this has a side effect of significantly reducing capabilities
1277 // after it starts things that need more privileges
1278 ✗ cvmfs_exports_->fnSpawn();
1279
1280 ✗ loader_talk::Spawn();
1281
1282 ✗ if (delegated_unmount) {
1283 // Unmounting in this case might be delegated to the watchdog process.
1284 // Allow ptracing by the watchdog.
1285 ✗ if (!platform_set_dumpable()) {
1286 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogWarning, "Failed to set process dumpable");
1287 }
1288 // but still disallow core dump
1289 ✗ if (!SetLimitCore(0)) {
1290 LogCvmfs(kLogCvmfs, kLogDebug, "Failed to set core dump limit to 0");
1291 }
1292 }
1293
1294 ✗ SetLogMicroSyslog("");
1295 ✗ retval = fuse_set_signal_handlers(session);
1296 ✗ assert(retval == 0);
1297 ✗ if (single_threaded_) {
1298 ✗ retval = fuse_session_loop(session);
1299 } else {
1300 #ifdef CVMFS_ENABLE_FUSE3_LOOP_CONFIG
1301 struct fuse_loop_config *fuse_loop_cfg = fuse_loop_cfg_create();
1302
1303 fuse_loop_cfg_set_clone_fd(fuse_loop_cfg, 1);
1304
1305 if (fuse3_max_threads_ > 0) {
1306 fuse_loop_cfg_set_max_threads(fuse_loop_cfg, fuse3_max_threads_);
1307 }
1308 if (fuse3_idle_threads_ > 0) {
1309 fuse_loop_cfg_set_idle_threads(fuse_loop_cfg, fuse3_idle_threads_);
1310 }
1311
1312 retval = fuse_session_loop_mt(session, fuse_loop_cfg);
1313 fuse_loop_cfg_destroy(fuse_loop_cfg);
1314 #else
1315 ✗ retval = fuse_session_loop_mt(session, 1 /* use fd per thread */);
1316 #endif // CVMFS_ENABLE_FUSE3_LOOP_CONFIG
1317 }
1318 ✗ SetLogMicroSyslog(*usyslog_path_);
1319
1320 // Decide whether we will need to do our own unmount.
1321 // Decide here because we need to know at this point before tearing down
1322 // the cvmfs module with fnFini() whether or not the unmount will be
1323 // delegated to the watchdog, because when the cvmfs module shuts down
1324 // it sends a message to the watchdog.
1325 // The fuse main loop exits with 0 either when it is already unmounted,
1326 // or when the filesystem connection is aborted.
1327 // In the first case we don't need to attempt to unmount, and
1328 // the second case we can ignore because it only ever happens on
1329 // admin intervention.
1330 // Otherwise it exits non-zero and we should unmount if it was premounted.
1331 ✗ if (retval != 0) {
1332 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "CernVM-FS: fuse loop exited with error %i",
1333 retval);
1334 ✗ retval = kFailFuseLoop;
1335 } else {
1336 ✗ retval = kFailOk;
1337 // already unmounted
1338 ✗ dounmount = false;
1339 }
1340
1341 ✗ if (dounmount && delegated_unmount) {
1342 ✗ if (cvmfs_exports_->version == 1) {
1343 // This can happen if the cvmfs version was downgraded. The
1344 // watchdog won't know how to do a clean unmount, so the best we
1345 // can do is to force a crash so it will do a crash cleanup.
1346 ✗ LogCvmfs(kLogCvmfs, kLogSyslog,
1347 "Watchdog too old for clean error unmount on %s, forcing crash",
1348 mount_point_->c_str());
1349 ✗ assert(false);
1350 }
1351 ✗ } else if (cvmfs_exports_->version > 1) {
1352 // This tells the watchdog to not unmount.
1353 // The default if this is not run (when version > 1) is for it to unmount.
1354 ✗ cvmfs_exports_->fnClearExit();
1355 }
1356
1357 ✗ loader_talk::Fini();
1358 ✗ cvmfs_exports_->fnFini();
1359
1360 ✗ if (!delegated_unmount) {
1361 // Restore privileges for unmount even if we don't need to do it
1362 // ourselves, because the fuse library might need it.
1363 ✗ if (!ObtainSysAdminCapability()) {
1364 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
1365 "Failed to regain SYS_ADMIN capability for doing unmount");
1366 }
1367 }
1368
1369 // fuse functions will unmount if they can and think they need to.
1370 // They won't ever unmount if it has been premounted.
1371 ✗ fuse_remove_signal_handlers(session);
1372 ✗ fuse_session_unmount(session);
1373 ✗ fuse_session_destroy(session);
1374 ✗ fuse_opt_free_args(mount_options);
1375 ✗ delete mount_options;
1376 ✗ session = NULL;
1377 ✗ mount_options = NULL;
1378
1379 ✗ CloseLibrary();
1380
1381 ✗ delete fence_reload_;
1382 ✗ delete loader_exports_;
1383 ✗ delete config_files_;
1384 ✗ delete socket_path_;
1385 ✗ fence_reload_ = NULL;
1386 ✗ loader_exports_ = NULL;
1387 ✗ config_files_ = NULL;
1388 ✗ socket_path_ = NULL;
1389
1390 ✗ if (dounmount) {
1391 ✗ goto cleanup;
1392 } else {
1393 ✗ if (premount_fd >= 0) close(premount_fd);
1394 }
1395
1396 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: unmounted %s (%s) (exit success)",
1397 mount_point_->c_str(), repository_name_->c_str());
1398
1399 ✗ delete repository_name_;
1400 ✗ delete mount_point_;
1401 ✗ repository_name_ = NULL;
1402 ✗ mount_point_ = NULL;
1403
1404 ✗ return retval;
1405
1406 ✗ cleanup:
1407 #ifndef __APPLE__
1408 ✗ if (dounmount && !delegated_unmount) {
1409 ✗ if (!SwitchCredentials(0, getgid(), true /* temporarily */)) {
1410 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1411 "failed to re-gain root permissions for umounting");
1412 ✗ retval = kFailPermission;
1413 // do lazy unmount and ignore if it is already unmounted
1414 ✗ } else if (umount2(mount_point_->c_str(), MNT_DETACH) < 0 && errno != EINVAL && errno != ENOENT) {
1415 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1416 "failed to umount %s (%d)", mount_point_->c_str(), errno);
1417 } else {
1418 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: unmounted %s (%s) (error cleanup) ",
1419 mount_point_->c_str(), repository_name_->c_str());
1420 }
1421 }
1422 #endif
1423 ✗ if (premount_fd >= 0) close(premount_fd);
1424
1425 ✗ delete repository_name_;
1426 ✗ delete mount_point_;
1427 ✗ repository_name_ = NULL;
1428 ✗ mount_point_ = NULL;
1429
1430 ✗ return retval;
1431 }
1432
1433
1434 __attribute__((visibility("default")))
1435 CvmfsStubExports *g_cvmfs_stub_exports = NULL;
1436
1437 ✗ static void __attribute__((constructor)) LibraryMain() {
1438 ✗ g_cvmfs_stub_exports = new CvmfsStubExports();
1439 ✗ g_cvmfs_stub_exports->fn_main = FuseMain;
1440 }
1441
1442 ✗ static void __attribute__((destructor)) LibraryExit() {
1443 ✗ delete g_cvmfs_stub_exports;
1444 ✗ g_cvmfs_stub_exports = NULL;
1445 }
1446