GCC Code Coverage Report


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