GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/loader.cc
Date: 2026-09-20 02:39:58
Exec Total Coverage
Lines: 0 641 0.0%
Branches: 0 1144 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 ✗ vector<string> library_paths; // TODO(rmeusel): C++11 initializer
488 ✗ if (library_paths.empty()) {
489 ✗ library_paths.push_back(local_lib_path + library_name);
490 ✗ library_paths.push_back("/usr/lib/" + library_name);
491 ✗ library_paths.push_back("/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 library_paths.push_back("/usr/local/lib/" + library_name);
496 #endif
497 }
498
499 ✗ vector<string>::const_iterator i = library_paths.begin();
500 ✗ const vector<string>::const_iterator iend = library_paths.end();
501 ✗ for (; i != iend; ++i) { // TODO(rmeusel): C++11 range based for
502 ✗ library_handle_ = OpenLibrary(*i);
503 ✗ if (library_handle_ != NULL) {
504 ✗ break;
505 }
506
507 ✗ error_messages += string(dlerror()) + "\n";
508 }
509
510 ✗ if (!library_handle_) {
511 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
512 "failed to load cvmfs library, tried: '%s'\n%s",
513 JoinStrings(library_paths, "' '").c_str(), error_messages.c_str());
514 ✗ return NULL;
515 }
516
517 CvmfsExports **exports_ptr = reinterpret_cast<CvmfsExports **>(
518 ✗ dlsym(library_handle_, "g_cvmfs_exports"));
519 ✗ if (!exports_ptr)
520 ✗ return NULL;
521
522 ✗ if (loader_exports) {
523 ✗ LoadEvent *load_event = new LoadEvent();
524 ✗ load_event->timestamp = time(NULL);
525 ✗ load_event->so_version = (*exports_ptr)->so_version;
526 ✗ loader_exports->history.push_back(load_event);
527 }
528
529 ✗ return *exports_ptr;
530 }
531
532
533 ✗ Failures Reload(const int fd_progress, const bool stop_and_go,
534 const ReloadMode reload_mode) {
535 int retval;
536
537 // for legacy call we take the current state of debug_mode_
538 ✗ if (reload_mode == kReloadDebug) {
539 ✗ debug_mode_ = true;
540 ✗ } else if (reload_mode == kReloadNoDebug) {
541 ✗ debug_mode_ = false;
542 }
543
544 ✗ retval = cvmfs_exports_->fnMaintenanceMode(fd_progress);
545 ✗ if (!retval)
546 ✗ return kFailMaintenanceMode;
547
548 ✗ SendMsg2Socket(fd_progress, "Blocking new file system calls\n");
549 ✗ fence_reload_->Close();
550
551 ✗ SendMsg2Socket(fd_progress, "Waiting for active file system calls\n");
552 ✗ fence_reload_->Drain();
553
554 ✗ retval = cvmfs_exports_->fnSaveState(fd_progress,
555 ✗ &loader_exports_->saved_states);
556 ✗ if (!retval)
557 ✗ return kFailSaveState;
558
559 ✗ SendMsg2Socket(fd_progress, "Unloading Fuse module\n");
560 ✗ cvmfs_exports_->fnFini();
561 ✗ CloseLibrary();
562
563 ✗ if (stop_and_go) {
564 ✗ CreateFile(*socket_path_ + ".paused", 0600);
565 ✗ SendMsg2Socket(fd_progress, "Waiting for the delivery of SIGUSR1...\n");
566 ✗ WaitForSignal(SIGUSR1);
567 ✗ unlink((*socket_path_ + ".paused").c_str());
568 }
569
570 ✗ SendMsg2Socket(fd_progress, "Re-Loading Fuse module\n");
571 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, loader_exports_);
572 ✗ if (!cvmfs_exports_)
573 ✗ return kFailLoadLibrary;
574 ✗ retval = cvmfs_exports_->fnInit(loader_exports_);
575 ✗ if (retval != kFailOk) {
576 ✗ const string msg_progress = cvmfs_exports_->fnGetErrorMsg() + " ("
577 ✗ + StringifyInt(retval) + ")\n";
578 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s", msg_progress.c_str());
579 ✗ SendMsg2Socket(fd_progress, msg_progress);
580 ✗ return (Failures)retval;
581 }
582
583 ✗ retval = cvmfs_exports_->fnRestoreState(fd_progress,
584 ✗ loader_exports_->saved_states);
585 ✗ if (!retval)
586 ✗ return kFailRestoreState;
587 ✗ cvmfs_exports_->fnFreeSavedState(fd_progress, loader_exports_->saved_states);
588 ✗ for (unsigned i = 0, l = loader_exports_->saved_states.size(); i < l; ++i) {
589 ✗ delete loader_exports_->saved_states[i];
590 }
591 ✗ loader_exports_->saved_states.clear();
592
593 ✗ SendMsg2Socket(fd_progress, "Activating Fuse module\n");
594 ✗ cvmfs_exports_->fnSpawn();
595
596 ✗ fence_reload_->Open();
597 ✗ return kFailOk;
598 }
599
600 } // namespace loader
601
602
603 using namespace loader; // NOLINT(build/namespaces)
604
605 ✗ int FuseMain(int argc, char *argv[]) {
606 // Set a decent umask for new files (no write access to group/everyone).
607 // We want to allow group write access for the talk-socket.
608 ✗ umask(007);
609 // SIGUSR1 is used for the stop_and_go mode during reload
610 ✗ BlockSignal(SIGUSR1);
611
612 int retval;
613
614 // Jump into alternative process flavors (e.g. shared cache manager)
615 // We are here due to a fork+execve (ManagedExec in util.cc) or due to
616 // utility calls of cvmfs2
617 ✗ if ((argc > 1) && (strstr(argv[1], "__") == argv[1])) {
618 ✗ if (string(argv[1]) == string("__RELOAD__")) {
619 ✗ if (argc < 3)
620 ✗ return 1;
621 ✗ bool stop_and_go = false;
622 ✗ if ((argc > 3) && (string(argv[3]) == "stop_and_go"))
623 ✗ stop_and_go = true;
624
625 // always last param of the cvmfs2 __RELOAD__ command
626 // check if debug mode is requested
627 // NOTE:
628 // debug mode is decided based on CVMFS_DEBUGLOG being set or not
629 // this means: reloading is now always based on CVMFS_DEBUGLOG, and
630 // reload ignores the current state
631 //
632 // if you mount with debug but do not set CVMFS_DEBUGLOG and reload,
633 // debug will be turned off
634 ✗ if (std::string(argv[argc - 1]) == std::string("--debug")) {
635 ✗ debug_mode_ = true;
636 } else {
637 ✗ debug_mode_ = false;
638 }
639 ✗ retval = loader_talk::MainReload(argv[2], stop_and_go, debug_mode_);
640
641 ✗ if ((retval != 0) && (stop_and_go)) {
642 ✗ CreateFile(string(argv[2]) + ".paused.crashed", 0600);
643 }
644 ✗ return retval;
645 }
646
647 ✗ if (string(argv[1]) == string("__MK_ALIEN_CACHE__")) {
648 ✗ if (argc < 5)
649 ✗ return 1;
650 ✗ const string alien_cache_dir = argv[2];
651 ✗ const sanitizer::PositiveIntegerSanitizer sanitizer;
652 ✗ if (!sanitizer.IsValid(argv[3]) || !sanitizer.IsValid(argv[4]))
653 ✗ return 1;
654 ✗ const uid_t uid_owner = String2Uint64(argv[3]);
655 ✗ const gid_t gid_owner = String2Uint64(argv[4]);
656
657 ✗ int retval = MkdirDeep(alien_cache_dir, 0770);
658 ✗ if (!retval) {
659 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to create %s",
660 alien_cache_dir.c_str());
661 ✗ return 1;
662 }
663 ✗ retval = chown(alien_cache_dir.c_str(), uid_owner, gid_owner);
664 ✗ if (retval != 0) {
665 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to set owner of %s to %d:%d",
666 alien_cache_dir.c_str(), uid_owner, gid_owner);
667 ✗ return 1;
668 }
669 ✗ retval = SwitchCredentials(uid_owner, gid_owner, false /* temporarily */);
670 ✗ if (!retval) {
671 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to impersonate %d:%d",
672 uid_owner, gid_owner);
673 ✗ return 1;
674 }
675 // Allow access to user and group
676 ✗ retval = MakeCacheDirectories(alien_cache_dir, 0770);
677 ✗ if (!retval) {
678 ✗ LogCvmfs(kLogCvmfs, kLogStderr, "Failed to create cache skeleton");
679 ✗ return 1;
680 }
681 ✗ return 0;
682 }
683
684 ✗ debug_mode_ = getenv("__CVMFS_DEBUG_MODE__") != NULL;
685 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, NULL);
686 ✗ if (!cvmfs_exports_)
687 ✗ return kFailLoadLibrary;
688 ✗ return cvmfs_exports_->fnAltProcessFlavor(argc, argv);
689 }
690
691 // Option parsing
692 struct fuse_args *mount_options;
693 ✗ mount_options = ParseCmdLine(argc, argv);
694 ✗ if (!mount_options) {
695 ✗ Usage(argv[0]);
696 ✗ return kFailOptions;
697 }
698
699 ✗ string parameter;
700 OptionsManager *options_manager;
701 ✗ bool restore_origids = false;
702 uid_t origuid;
703 gid_t origgid;
704 ✗ if (simple_options_parsing_) {
705 ✗ options_manager = new SimpleOptionsParser(
706 ✗ new DefaultOptionsTemplateManager(*repository_name_));
707 } else {
708 ✗ if ((uid_ != 0) || (gid_ != 0)) {
709 // temporarily switch to requested uid/gid while running bash parser
710 ✗ origuid = geteuid();
711 ✗ origgid = getegid();
712 ✗ if ((uid_ != origuid) || (gid_ != origgid)) {
713 ✗ if (!SwitchCredentials(uid_, gid_, true /* temporarily */)) {
714 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
715 "Failed to switch credentials for options parser");
716 ✗ return kFailPermission;
717 }
718 ✗ restore_origids = true;
719 }
720 }
721 ✗ options_manager = new BashOptionsManager(
722 ✗ new DefaultOptionsTemplateManager(*repository_name_));
723 }
724 ✗ if (config_files_) {
725 ✗ vector<string> tokens = SplitString(*config_files_, ':');
726 ✗ for (unsigned i = 0, s = tokens.size(); i < s; ++i) {
727 ✗ options_manager->ParsePath(tokens[i], false);
728 }
729 ✗ } else {
730 ✗ options_manager->ParseDefault(*repository_name_);
731 }
732 ✗ if (restore_origids) {
733 ✗ if (!SwitchCredentials(origuid, origgid, true /* temporarily */)) {
734 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
735 "Failed to switch credentials back after options parser");
736 ✗ return kFailPermission;
737 }
738 }
739
740 ✗ if (options_manager->GetValue("CVMFS_PREMOUNT_FUSE", &parameter)
741 ✗ && options_manager->IsOff(parameter)) {
742 ✗ premount_fuse_ = false;
743 }
744
745 ✗ if (options_manager->GetValue("CVMFS_REPOSITORIES_NOMOUNT", &parameter)
746 ✗ && !parameter.empty()) {
747 ✗ const vector<string> ignored_repos = SplitString(parameter, ',');
748 ✗ for (unsigned i = 0, s = ignored_repos.size(); i < s; ++i) {
749 ✗ if (Trim(ignored_repos[i]) == *repository_name_) {
750 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
751 "CernVM-FS: mount attempt for %s ignored "
752 "(listed in CVMFS_REPOSITORIES_NOMOUNT)",
753 repository_name_->c_str());
754 ✗ delete options_manager;
755 ✗ fuse_opt_free_args(mount_options);
756 ✗ delete mount_options;
757 ✗ return kFailIgnoredMount;
758 }
759 }
760 }
761
762 #ifdef __APPLE__
763 string volname = "-ovolname=" + *repository_name_;
764 fuse_opt_add_arg(mount_options, volname.c_str());
765 // Allow for up to 5 minute "hangs" before OS X may kill cvmfs
766 fuse_opt_add_arg(mount_options, "-odaemon_timeout=300");
767 fuse_opt_add_arg(mount_options, "-onoapplexattr");
768 // Should libfuse be single-threaded? See CVM-871, CVM-855
769 // single_threaded_ = true;
770 #endif
771 ✗ if (options_manager->GetValue("CVMFS_MOUNT_RW", &parameter)
772 ✗ && options_manager->IsOn(parameter)) {
773 ✗ fuse_opt_add_arg(mount_options, "-orw");
774 } else {
775 ✗ fuse_opt_add_arg(mount_options, "-oro");
776 }
777 ✗ fuse_opt_add_arg(mount_options, "-onodev");
778 ✗ if (options_manager->GetValue("CVMFS_SUID", &parameter)
779 ✗ && options_manager->IsOn(parameter)) {
780 ✗ suid_mode_ = true;
781 }
782 ✗ if (suid_mode_) {
783 ✗ if (getuid() != 0) {
784 ✗ PANIC(kLogStderr | kLogSyslogErr,
785 "must be root to mount with suid option");
786 }
787 ✗ fuse_opt_add_arg(mount_options, "-osuid");
788 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running with suid support");
789 }
790
791 ✗ if (options_manager->GetValue("CVMFS_CPU_AFFINITY", &parameter)) {
792 #ifndef __APPLE__
793 cpu_set_t mask;
794 ✗ vector<string> cpus = SplitString(parameter, ',');
795 ✗ CPU_ZERO(&mask);
796 ✗ for (vector<string>::iterator i = cpus.begin(); i != cpus.end(); i++) {
797 ✗ CPU_SET(String2Uint64(Trim(*i)), &mask);
798 }
799 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: setting CPU Affinity to %s",
800 parameter.c_str());
801 ✗ const int err = sched_setaffinity(0, sizeof(mask), &mask);
802 ✗ if (err != 0) {
803 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogErr,
804 "Setting CPU Affinity failed with error %d", errno);
805 }
806 #else
807 LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogErr,
808 "CPU affinity setting not supported on macOS");
809 #endif
810 }
811 ✗ loader_exports_ = new LoaderExports();
812 ✗ loader_exports_->loader_version = CVMFS_VERSION;
813 ✗ loader_exports_->boot_time = time(NULL);
814 ✗ loader_exports_->program_name = argv[0];
815 ✗ loader_exports_->foreground = foreground_;
816 ✗ loader_exports_->repository_name = *repository_name_;
817 ✗ loader_exports_->mount_point = *mount_point_;
818 ✗ loader_exports_->device_id = "0:0"; // initially unknown, set after mount
819 ✗ loader_exports_->disable_watchdog = disable_watchdog_;
820 ✗ loader_exports_->simple_options_parsing = simple_options_parsing_;
821 ✗ loader_exports_->fuse_passthrough = fuse_passthrough_;
822 ✗ if (options_manager->GetValue("CVMFS_FUSE_PASSTHROUGH", &parameter)) {
823 // CVMFS_FUSE_PASSTHROUGH set to on in configs enables the feature.
824 // Presence of mount option can also enable the feature (but not disable it).
825 ✗ loader_exports_->fuse_passthrough |= options_manager->IsOn(parameter);
826 }
827 ✗ if (config_files_)
828 ✗ loader_exports_->config_files = *config_files_;
829 else
830 ✗ loader_exports_->config_files = "";
831
832 ✗ if (parse_options_only_) {
833 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "# CernVM-FS parameters:\n%s",
834 options_manager->Dump().c_str());
835 ✗ return 0;
836 }
837
838 // Logging
839 ✗ if (options_manager->GetValue("CVMFS_SYSLOG_LEVEL", &parameter))
840 ✗ SetLogSyslogLevel(String2Uint64(parameter));
841 else
842 ✗ SetLogSyslogLevel(3);
843 ✗ if (options_manager->GetValue("CVMFS_SYSLOG_FACILITY", &parameter))
844 ✗ SetLogSyslogFacility(String2Int64(parameter));
845 ✗ SetLogSyslogPrefix(*repository_name_);
846 // Deferr setting usyslog until credentials are dropped
847
848 // Permissions check
849 ✗ if (options_manager->GetValue("CVMFS_CHECK_PERMISSIONS", &parameter)) {
850 ✗ if (options_manager->IsOn(parameter)) {
851 ✗ fuse_opt_add_arg(mount_options, "-odefault_permissions");
852 }
853 }
854
855 ✗ if (!premounted_ && !DirectoryExists(*mount_point_)) {
856 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
857 "Mount point %s does not exist", mount_point_->c_str());
858 ✗ return kFailPermission;
859 }
860
861 // Number of file descriptors
862 ✗ if (options_manager->GetValue("CVMFS_NFILES", &parameter)) {
863 ✗ const int retval = SetLimitNoFile(String2Uint64(parameter));
864 ✗ if (retval == -2) {
865 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running under valgrind");
866 ✗ } else if (retval == -1) {
867 ✗ if (system_mount_) {
868 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
869 "Failed to set maximum number of open files, "
870 "insufficient permissions");
871 ✗ return kFailPermission;
872 }
873 unsigned soft_limit, hard_limit;
874 ✗ GetLimitNoFile(&soft_limit, &hard_limit);
875 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogWarn,
876 "Failed to set requested number of open files, "
877 "using maximum number %u",
878 hard_limit);
879 ✗ if (hard_limit > soft_limit) {
880 ✗ (void)SetLimitNoFile(hard_limit);
881 }
882 }
883 }
884
885 // Apply OOM score adjustment
886 ✗ if (options_manager->GetValue("CVMFS_OOM_SCORE_ADJ", &parameter)) {
887 ✗ const string proc_path = "/proc/" + StringifyInt(getpid())
888 ✗ + "/oom_score_adj";
889 ✗ const int fd_oom = open(proc_path.c_str(), O_WRONLY);
890 ✗ if (fd_oom < 0) {
891 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn, "failed to open %s",
892 proc_path.c_str());
893 } else {
894 ✗ const bool retval = SafeWrite(fd_oom, parameter.data(),
895 parameter.length());
896 ✗ if (!retval) {
897 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
898 "failed to set OOM score adjustment to %s", parameter.c_str());
899 }
900 ✗ close(fd_oom);
901 }
902 }
903
904 // Protect the process from being killed by systemd
905 ✗ if (options_manager->GetValue("CVMFS_SYSTEMD_NOKILL", &parameter)
906 ✗ && options_manager->IsOn(parameter)) {
907 ✗ argv[0][0] = '@';
908 }
909
910 // Grab mountpoint
911 ✗ if (grab_mountpoint_) {
912 ✗ if ((chown(mount_point_->c_str(), uid_, gid_) != 0)
913 ✗ || (chmod(mount_point_->c_str(), 0755) != 0)) {
914 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
915 "Failed to grab mountpoint %s (%d)", mount_point_->c_str(),
916 errno);
917 ✗ return kFailPermission;
918 }
919 }
920
921
922 // these need to be declared before start using goto
923 ✗ int fd_mountinfo = -1;
924 ✗ const bool delegated_unmount = (!suid_mode_ && !disable_watchdog_);
925 ✗ bool dounmount = false;
926 ✗ int premount_fd = -1;
927 ✗ struct fuse_session *session = NULL;
928
929 // Drop credentials, most likely temporarily since by default there is
930 // a watchdog
931 ✗ if ((uid_ != 0) || (gid_ != 0)) {
932 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: running with credentials %d:%d",
933 uid_, gid_);
934 ✗ const bool retrievable = (suid_mode_ || !disable_watchdog_);
935 ✗ if (!SwitchCredentials(uid_, gid_, retrievable)) {
936 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
937 "Failed to drop credentials");
938 ✗ retval = kFailPermission;
939 ✗ goto cleanup;
940 }
941 }
942 ✗ if (disable_watchdog_) {
943 LogCvmfs(kLogCvmfs, kLogDebug, "No watchdog, enabling core files");
944 ✗ if (!platform_set_dumpable()) {
945 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogWarning, "Failed to set process dumpable");
946 }
947 }
948
949 // Only set usyslog now, otherwise file permissions are wrong
950 ✗ usyslog_path_ = new string();
951 ✗ if (options_manager->GetValue("CVMFS_USYSLOG", &parameter))
952 ✗ *usyslog_path_ = parameter;
953 ✗ SetLogMicroSyslog(*usyslog_path_);
954
955 ✗ if (single_threaded_) {
956 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
957 "CernVM-FS: running in single threaded mode");
958 }
959 ✗ if (debug_mode_) {
960 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogSyslogWarn,
961 "CernVM-FS: running in debug mode");
962 }
963
964 #ifndef FUSE_CAP_POSIX_ACL
965 if (options_manager->GetValue("CVMFS_ENFORCE_ACLS", &parameter)
966 && options_manager->IsOn(parameter)) {
967 LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
968 "CernVM-FS: ACL support requested but not available in this "
969 "version of libfuse");
970 retval = kFailPermission;
971 goto cleanup;
972 }
973 #endif
974
975 // Initialize the loader socket, connections are not accepted until Spawn()
976 ✗ socket_path_ = new string("/var/run/cvmfs");
977 ✗ if (options_manager->GetValue("CVMFS_RELOAD_SOCKETS", &parameter))
978 ✗ *socket_path_ = MakeCanonicalPath(parameter);
979 ✗ *socket_path_ += "/cvmfs." + *repository_name_;
980 ✗ retval = loader_talk::Init(*socket_path_);
981 ✗ if (!retval) {
982 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
983 "Failed to initialize loader socket");
984 ✗ retval = kFailLoaderTalk;
985 ✗ goto cleanup;
986 }
987
988 // TODO(jblomer): we probably want to apply a default setting related to the
989 // number of cores.
990 ✗ if (options_manager->GetValue("CVMFS_FUSE3_MAX_THREADS", &parameter)) {
991 ✗ fuse3_max_threads_ = String2Int64(parameter);
992 }
993 ✗ if (options_manager->GetValue("CVMFS_FUSE3_IDLE_THREADS", &parameter)) {
994 ✗ fuse3_idle_threads_ = String2Int64(parameter);
995 }
996 #ifdef CVMFS_ENABLE_FUSE3_LOOP_CONFIG
997 if (fuse3_max_threads_) {
998 LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: Fuse3 max_threads=%d",
999 fuse3_max_threads_);
1000 }
1001 if (fuse3_idle_threads_) {
1002 LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: Fuse3 min_idle_threads=%d",
1003 fuse3_idle_threads_);
1004 }
1005 #else
1006 ✗ if (fuse3_max_threads_ || fuse3_idle_threads_) {
1007 ✗ LogCvmfs(kLogCvmfs, kLogStdout,
1008 "CernVM-FS: ignoring fuse3 thread settings (libfuse too old)");
1009 }
1010 #endif
1011
1012 // Options are not needed anymore
1013 ✗ delete options_manager;
1014 ✗ options_manager = NULL;
1015
1016 ✗ loader_exports_->fuse_session = &session;
1017
1018 // Load and initialize cvmfs library
1019 ✗ LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak,
1020 "CernVM-FS: loading Fuse module... ");
1021 ✗ cvmfs_exports_ = LoadLibrary(debug_mode_, loader_exports_);
1022 ✗ if (!cvmfs_exports_) {
1023 ✗ retval = kFailLoadLibrary;
1024 ✗ goto cleanup;
1025 }
1026 ✗ retval = cvmfs_exports_->fnInit(loader_exports_);
1027 ✗ if (retval != kFailOk) {
1028 ✗ if (retval == kFailDoubleMount) {
1029 ✗ LogCvmfs(kLogCvmfs, kLogStderr,
1030 "\nCernVM-FS: repository %s already mounted on %s",
1031 loader_exports_->repository_name.c_str(),
1032 loader_exports_->mount_point.c_str());
1033 ✗ return 0;
1034 }
1035 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr, "%s (%d - %s)",
1036 cvmfs_exports_->fnGetErrorMsg().c_str(), retval,
1037 Code2Ascii((Failures)retval));
1038 ✗ cvmfs_exports_->fnFini();
1039 ✗ goto cleanup;
1040 }
1041 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "done");
1042
1043 // Mount
1044 ✗ fence_reload_ = new Fence();
1045
1046 ✗ if (suid_mode_) {
1047 ✗ const bool retrievable = true;
1048 ✗ if (!SwitchCredentials(0, getgid(), retrievable)) {
1049 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1050 "failed to re-gain root permissions for mounting");
1051 ✗ cvmfs_exports_->fnFini();
1052 ✗ retval = kFailPermission;
1053 ✗ goto cleanup;
1054 }
1055 }
1056
1057 #ifndef __APPLE__
1058 ✗ if (!premounted_ && !suid_mode_ && getuid() == 0 && premount_fuse_) {
1059 // If not already premounted or using suid mode, premount the fuse
1060 // mountpoint to avoid the need for fusermount.
1061 // Requires libfuse >= 3.3.0.
1062 //
1063 ✗ if ((uid_ != 0) || (gid_ != 0)) {
1064 ✗ if (!SwitchCredentials(0, getgid(), true /* temporarily */)) {
1065 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1066 "failed to re-gain root permissions for mounting");
1067 ✗ retval = kFailPermission;
1068 ✗ goto cleanup;
1069 }
1070 }
1071 platform_stat64 info;
1072 // Need to know if it is a directory or not
1073 ✗ if (platform_stat(mount_point_->c_str(), &info) != 0) {
1074 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1075 "Failed to stat mountpoint %s (%d)", mount_point_->c_str(),
1076 errno);
1077 ✗ return kFailPermission;
1078 }
1079 ✗ premount_fd = open("/dev/fuse", O_RDWR);
1080 ✗ if (premount_fd == -1) {
1081 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1082 "Failed to open /dev/fuse (%d)", errno);
1083 ✗ return kFailPermission;
1084 }
1085 ✗ dounmount = true;
1086 char opts[128];
1087 ✗ snprintf(
1088 opts, sizeof(opts), "fd=%i,rootmode=%o,user_id=0,group_id=0%s%s",
1089 ✗ premount_fd, info.st_mode & S_IFMT,
1090 ✗ MatchFuseOption(mount_options, "default_permissions")
1091 ? ",default_permissions"
1092 : "",
1093 ✗ MatchFuseOption(mount_options, "allow_other") ? ",allow_other" : "");
1094 ✗ unsigned long flags = MS_NOSUID | MS_NODEV | MS_RELATIME;
1095 // Note that during the handling of the `CVMFS_MOUNT_RW` option, we ensure
1096 // that at least one of `rw` or `ro` is part of the mount option string (we
1097 // won't have both unset). If both `rw` and `ro` are set, the read-only
1098 // option takes precedence.
1099 ✗ if (MatchFuseOption(mount_options, "ro")) {
1100 ✗ flags |= MS_RDONLY;
1101 }
1102 ✗ if (mount("cvmfs2", mount_point_->c_str(), "fuse", flags, opts) == -1) {
1103 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1104 "Failed to mount -t fuse -o %s cvmfs2 %s (%d)", opts,
1105 mount_point_->c_str(), errno);
1106 ✗ return kFailPermission;
1107 }
1108
1109 // Drop credentials
1110 ✗ if ((uid_ != 0) || (gid_ != 0)) {
1111 ✗ if (!SwitchCredentials(uid_, gid_, true /*retrievable*/)) {
1112 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1113 "Failed to drop credentials");
1114 ✗ retval = kFailPermission;
1115 ✗ goto cleanup;
1116 }
1117 }
1118 }
1119 #endif
1120
1121 struct fuse_lowlevel_ops loader_operations;
1122 ✗ SetFuseOperations(&loader_operations);
1123 ✗ if (cvmfs_exports_->cvmfs_operations.forget_multi)
1124 ✗ loader_operations.forget_multi = stub_forget_multi;
1125
1126 ✗ session = fuse_session_new(mount_options, &loader_operations,
1127 sizeof(loader_operations), NULL);
1128 ✗ if (!session) {
1129 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1130 "failed to create Fuse session");
1131 ✗ cvmfs_exports_->fnFini();
1132 ✗ retval = kFailMount;
1133 ✗ goto cleanup;
1134 }
1135 ✗ if (premount_fd >= 0) {
1136 char premount_str[64];
1137 ✗ snprintf(premount_str, sizeof(premount_str), "/dev/fd/%d", premount_fd);
1138 ✗ retval = fuse_session_mount(session, premount_str);
1139 } else {
1140 ✗ retval = fuse_session_mount(session, mount_point_->c_str());
1141 }
1142 ✗ if (retval != 0) {
1143 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1144 "failed to mount file system");
1145 ✗ cvmfs_exports_->fnFini();
1146 ✗ retval = kFailMount;
1147 ✗ goto cleanup;
1148 }
1149
1150 ✗ if (suid_mode_) {
1151 // Drop credentials again for now
1152 ✗ const bool retrievable = !disable_watchdog_;
1153 ✗ if (!SwitchCredentials(uid_, gid_, retrievable)) {
1154 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1155 "failed to drop permissions after mounting");
1156 ✗ cvmfs_exports_->fnFini();
1157 ✗ retval = kFailPermission;
1158 ✗ goto cleanup;
1159 }
1160 }
1161 #ifndef __APPLE__
1162 ✗ else if (getuid() != geteuid()) {
1163 // Switch to using only capabilities before starting threads
1164 // because switching the uid after threads are created affects
1165 // all threads and causes race conditions.
1166 ✗ platform_keepcaps(true);
1167 ✗ if (!SwitchCredentials(uid_, gid_, false /* temporarily */)) {
1168 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1169 "failed to switch to only capabilities after mounting");
1170 ✗ cvmfs_exports_->fnFini();
1171 ✗ retval = kFailPermission;
1172 ✗ goto cleanup;
1173 }
1174 ✗ platform_keepcaps(false);
1175 }
1176 #endif
1177
1178 // Determine device id
1179 ✗ fd_mountinfo = open("/proc/self/mountinfo", O_RDONLY);
1180 ✗ if (fd_mountinfo > 0) {
1181 ✗ std::string line;
1182 ✗ while (GetLineFd(fd_mountinfo, &line)) {
1183 ✗ std::vector<std::string> tokens = SplitString(line, ' ');
1184 ✗ if (tokens.size() < 5)
1185 ✗ continue;
1186 ✗ if (tokens[4] != loader_exports_->mount_point)
1187 ✗ continue;
1188 ✗ unsigned i = 5;
1189 ✗ for (; i < tokens.size(); ++i) {
1190 ✗ if (tokens[i] == "-")
1191 ✗ break;
1192 }
1193 ✗ if (tokens.size() < i + 3)
1194 ✗ continue;
1195 ✗ if (tokens[i + 2] != "cvmfs2")
1196 ✗ continue;
1197 ✗ loader_exports_->device_id = tokens[2];
1198 ✗ break;
1199 }
1200 ✗ close(fd_mountinfo);
1201 }
1202
1203 ✗ if (!premounted_) {
1204 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "CernVM-FS: mounted cvmfs on %s",
1205 mount_point_->c_str());
1206 }
1207 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: linking %s to repository %s",
1208 mount_point_->c_str(), repository_name_->c_str());
1209 ✗ if (!foreground_)
1210 ✗ Daemonize();
1211
1212 // Note that this has a side effect of significantly reducing capabilities
1213 // after it starts things that need more privileges
1214 ✗ cvmfs_exports_->fnSpawn();
1215
1216 ✗ loader_talk::Spawn();
1217
1218 ✗ if (delegated_unmount) {
1219 // Unmounting in this case might be delegated to the watchdog process.
1220 // Allow ptracing by the watchdog.
1221 ✗ if (!platform_set_dumpable()) {
1222 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogWarning, "Failed to set process dumpable");
1223 }
1224 // but still disallow core dump
1225 ✗ if (!SetLimitCore(0)) {
1226 LogCvmfs(kLogCvmfs, kLogDebug, "Failed to set core dump limit to 0");
1227 }
1228 }
1229
1230 ✗ SetLogMicroSyslog("");
1231 ✗ retval = fuse_set_signal_handlers(session);
1232 ✗ assert(retval == 0);
1233 ✗ if (single_threaded_) {
1234 ✗ retval = fuse_session_loop(session);
1235 } else {
1236 #ifdef CVMFS_ENABLE_FUSE3_LOOP_CONFIG
1237 struct fuse_loop_config *fuse_loop_cfg = fuse_loop_cfg_create();
1238
1239 fuse_loop_cfg_set_clone_fd(fuse_loop_cfg, 1);
1240
1241 if (fuse3_max_threads_ > 0) {
1242 fuse_loop_cfg_set_max_threads(fuse_loop_cfg, fuse3_max_threads_);
1243 }
1244 if (fuse3_idle_threads_ > 0) {
1245 fuse_loop_cfg_set_idle_threads(fuse_loop_cfg, fuse3_idle_threads_);
1246 }
1247
1248 retval = fuse_session_loop_mt(session, fuse_loop_cfg);
1249 fuse_loop_cfg_destroy(fuse_loop_cfg);
1250 #else
1251 ✗ retval = fuse_session_loop_mt(session, 1 /* use fd per thread */);
1252 #endif // CVMFS_ENABLE_FUSE3_LOOP_CONFIG
1253 }
1254 ✗ SetLogMicroSyslog(*usyslog_path_);
1255
1256 // Decide whether we will need to do our own unmount.
1257 // Decide here because we need to know at this point before tearing down
1258 // the cvmfs module with fnFini() whether or not the unmount will be
1259 // delegated to the watchdog, because when the cvmfs module shuts down
1260 // it sends a message to the watchdog.
1261 // The fuse main loop exits with 0 either when it is already unmounted,
1262 // or when the filesystem connection is aborted.
1263 // In the first case we don't need to attempt to unmount, and
1264 // the second case we can ignore because it only ever happens on
1265 // admin intervention.
1266 // Otherwise it exits non-zero and we should unmount if it was premounted.
1267 ✗ if (retval != 0) {
1268 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "CernVM-FS: fuse loop exited with error %i",
1269 retval);
1270 ✗ retval = kFailFuseLoop;
1271 } else {
1272 ✗ retval = kFailOk;
1273 // already unmounted
1274 ✗ dounmount = false;
1275 }
1276
1277 ✗ if (dounmount && delegated_unmount) {
1278 ✗ if (cvmfs_exports_->version == 1) {
1279 // This can happen if the cvmfs version was downgraded. The
1280 // watchdog won't know how to do a clean unmount, so the best we
1281 // can do is to force a crash so it will do a crash cleanup.
1282 ✗ LogCvmfs(kLogCvmfs, kLogSyslog,
1283 "Watchdog too old for clean error unmount on %s, forcing crash",
1284 mount_point_->c_str());
1285 ✗ assert(false);
1286 }
1287 ✗ } else if (cvmfs_exports_->version > 1) {
1288 // This tells the watchdog to not unmount.
1289 // The default if this is not run (when version > 1) is for it to unmount.
1290 ✗ cvmfs_exports_->fnClearExit();
1291 }
1292
1293 ✗ loader_talk::Fini();
1294 ✗ cvmfs_exports_->fnFini();
1295
1296 ✗ if (!delegated_unmount) {
1297 // Restore privileges for unmount even if we don't need to do it
1298 // ourselves, because the fuse library might need it.
1299 ✗ if (!ObtainSysAdminCapability()) {
1300 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslogWarn,
1301 "Failed to regain SYS_ADMIN capability for doing unmount");
1302 }
1303 }
1304
1305 // fuse functions will unmount if they can and think they need to.
1306 // They won't ever unmount if it has been premounted.
1307 ✗ fuse_remove_signal_handlers(session);
1308 ✗ fuse_session_unmount(session);
1309 ✗ fuse_session_destroy(session);
1310 ✗ fuse_opt_free_args(mount_options);
1311 ✗ delete mount_options;
1312 ✗ session = NULL;
1313 ✗ mount_options = NULL;
1314
1315 ✗ CloseLibrary();
1316
1317 ✗ delete fence_reload_;
1318 ✗ delete loader_exports_;
1319 ✗ delete config_files_;
1320 ✗ delete socket_path_;
1321 ✗ fence_reload_ = NULL;
1322 ✗ loader_exports_ = NULL;
1323 ✗ config_files_ = NULL;
1324 ✗ socket_path_ = NULL;
1325
1326 ✗ if (dounmount) {
1327 ✗ goto cleanup;
1328 } else {
1329 ✗ if (premount_fd >= 0) close(premount_fd);
1330 }
1331
1332 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: unmounted %s (%s) (exit success)",
1333 mount_point_->c_str(), repository_name_->c_str());
1334
1335 ✗ delete repository_name_;
1336 ✗ delete mount_point_;
1337 ✗ repository_name_ = NULL;
1338 ✗ mount_point_ = NULL;
1339
1340 ✗ return retval;
1341
1342 ✗ cleanup:
1343 #ifndef __APPLE__
1344 ✗ if (dounmount && !delegated_unmount) {
1345 ✗ if (!SwitchCredentials(0, getgid(), true /* temporarily */)) {
1346 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1347 "failed to re-gain root permissions for umounting");
1348 ✗ retval = kFailPermission;
1349 // do lazy unmount and ignore if it is already unmounted
1350 ✗ } else if (umount2(mount_point_->c_str(), MNT_DETACH) < 0 && errno != EINVAL && errno != ENOENT) {
1351 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr,
1352 "failed to umount %s (%d)", mount_point_->c_str(), errno);
1353 } else {
1354 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "CernVM-FS: unmounted %s (%s) (error cleanup) ",
1355 mount_point_->c_str(), repository_name_->c_str());
1356 }
1357 }
1358 #endif
1359 ✗ if (premount_fd >= 0) close(premount_fd);
1360
1361 ✗ delete repository_name_;
1362 ✗ delete mount_point_;
1363 ✗ repository_name_ = NULL;
1364 ✗ mount_point_ = NULL;
1365
1366 ✗ return retval;
1367 }
1368
1369
1370 __attribute__((visibility("default")))
1371 CvmfsStubExports *g_cvmfs_stub_exports = NULL;
1372
1373 ✗ static void __attribute__((constructor)) LibraryMain() {
1374 ✗ g_cvmfs_stub_exports = new CvmfsStubExports();
1375 ✗ g_cvmfs_stub_exports->fn_main = FuseMain;
1376 }
1377
1378 ✗ static void __attribute__((destructor)) LibraryExit() {
1379 ✗ delete g_cvmfs_stub_exports;
1380 ✗ g_cvmfs_stub_exports = NULL;
1381 }
1382