GCC Code Coverage Report


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