GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/monitor.cc
Date: 2026-10-25 02:40:14
Exec Total Coverage
Lines: 94 388 24.2%
Branches: 64 590 10.8%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This module forks a watchdog process that listens on
5 * a pipe and prints a stackstrace into syslog, when cvmfs
6 * fails.
7 *
8 * Also, it handles getting and setting the maximum number of file descriptors.
9 */
10
11
12 #include "monitor.h"
13
14 #include <errno.h>
15 #include <execinfo.h>
16 #include <fcntl.h>
17 #include <poll.h>
18 #include <pthread.h>
19 #include <signal.h>
20 #include <sys/resource.h>
21 #include <sys/types.h>
22 #ifdef __APPLE__
23 #include <sys/ucontext.h>
24 #else
25 #include <ucontext.h>
26 #endif
27 #include <sys/uio.h>
28 #include <sys/wait.h>
29 #include <syslog.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include <cassert>
34 #include <cstdio>
35 #include <cstdlib>
36 #include <cstring>
37 #include <map>
38 #include <set>
39 #include <string>
40 #include <vector>
41
42 #if defined(CVMFS_FUSE_MODULE)
43 #include "cvmfs.h"
44 #endif
45 #include "util/capabilities.h"
46 #include "util/exception.h"
47 #include "util/logging.h"
48 #include "util/posix.h"
49 #include "util/smalloc.h"
50 #include "util/string.h"
51
52 // Used for address offset calculation
53 #if defined(CVMFS_FUSE_MODULE)
54 extern loader::CvmfsExports *g_cvmfs_exports;
55 #endif
56
57 using namespace std; // NOLINT
58
59 Watchdog *Watchdog::instance_ = NULL;
60
61 int Watchdog::g_suppressed_signals[] = {
62 SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE,
63 SIGUSR1, SIGSEGV, SIGUSR2, SIGTERM, SIGXCPU, SIGXFSZ};
64
65 int Watchdog::g_crash_signals[] = {SIGQUIT, SIGILL, SIGABRT, SIGFPE,
66 SIGSEGV, SIGBUS, SIGPIPE, SIGXFSZ};
67
68 49 Watchdog *Watchdog::Create(FnOnExit on_exit,
69 bool needs_read_environ,
70 WatchdogState *saved_state,
71 const std::string &oom_score_adj) {
72
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 assert(instance_ == NULL);
73
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 instance_ = new Watchdog(on_exit, oom_score_adj);
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (saved_state != NULL)
75 ✗ instance_->RestoreState(saved_state);
76 else
77 49 instance_->Fork(needs_read_environ);
78 49 return instance_;
79 }
80
81
82 /**
83 * Uses an external shell and gdb to create a full stack trace of the dying
84 * process. The same shell is used to force-quit the client afterwards.
85 */
86 ✗ string Watchdog::GenerateStackTrace(pid_t pid) {
87 int retval;
88 ✗ string result = "";
89
90 // Get capability to ptrace the dead main cvmfs2 process.
91 // This is often necessary because the main process can have its own
92 // elevated capability which would otherwise block ptrace.
93 ✗ if (!ObtainSysPtraceCapability()) {
94 ✗ result += "failed to gain ptrace capability... still give it a try\n";
95 }
96
97 // run gdb and attach to the dying process
98 int fd_stdin;
99 int fd_stdout;
100 int fd_stderr;
101 ✗ vector<string> argv;
102 ✗ argv.push_back("-p");
103 ✗ argv.push_back(StringifyInt(pid));
104 ✗ pid_t gdb_pid = 0;
105 ✗ const bool double_fork = false;
106 ✗ retval = ExecuteBinary(&fd_stdin,
107 &fd_stdout,
108 &fd_stderr,
109 #ifdef __APPLE__
110 "lldb",
111 #else
112 "gdb",
113 #endif
114 argv,
115 double_fork,
116 &gdb_pid);
117 ✗ assert(retval);
118
119
120 // Skip the gdb startup output
121 ✗ ReadUntilGdbPrompt(fd_stdout);
122
123 // Send stacktrace command to gdb
124 #ifdef __APPLE__
125 const string gdb_cmd = "bt all\n"
126 "quit\n";
127 #else
128 const string gdb_cmd = "thread apply all bt\n"
129 ✗ "quit\n";
130 #endif
131 // The execve can have failed, which can't be detected in ExecuteBinary.
132 // Instead, writing to the pipe will fail.
133 ✗ const ssize_t nbytes = write(fd_stdin, gdb_cmd.data(), gdb_cmd.length());
134 ✗ if ((nbytes < 0) || (static_cast<unsigned>(nbytes) != gdb_cmd.length())) {
135 ✗ result += "failed to start gdb/lldb (" + StringifyInt(nbytes)
136 ✗ + " bytes "
137 "written, errno "
138 ✗ + StringifyInt(errno) + ")\n";
139 ✗ return result;
140 }
141
142 // Read the stack trace from the stdout of our gdb process
143 #ifdef __APPLE__
144 // lldb has one more prompt
145 result += ReadUntilGdbPrompt(fd_stdout);
146 #endif
147 ✗ result += ReadUntilGdbPrompt(fd_stdout) + "\n\n";
148
149 // Check for output on stderr
150 ✗ string result_err;
151 ✗ Block2Nonblock(fd_stderr);
152 char cbuf;
153 ✗ while (read(fd_stderr, &cbuf, 1) == 1)
154 ✗ result_err.push_back(cbuf);
155 ✗ if (!result_err.empty())
156 ✗ result += "\nError output:\n" + result_err + "\n";
157
158 // Close the connection to the terminated gdb process
159 ✗ close(fd_stderr);
160 ✗ close(fd_stdout);
161 ✗ close(fd_stdin);
162
163 // Make sure gdb has terminated (wait for it for a short while)
164 ✗ unsigned int timeout = 15;
165 int statloc;
166 ✗ while (timeout > 0 && waitpid(gdb_pid, &statloc, WNOHANG) != gdb_pid) {
167 ✗ --timeout;
168 ✗ SafeSleepMs(1000);
169 }
170
171 // when the timeout expired, gdb probably hangs... we need to kill it
172 ✗ if (timeout == 0) {
173 ✗ result += "gdb did not exit as expected. sending SIGKILL... ";
174 ✗ result += (kill(gdb_pid, SIGKILL) != 0) ? "failed\n" : "okay\n";
175 }
176
177 ✗ return result;
178 }
179
180
181 ✗ pid_t Watchdog::GetPid() {
182 ✗ if (instance_ != NULL) {
183 ✗ return instance_->watchdog_pid_;
184 }
185 ✗ return getpid();
186 }
187
188 /**
189 * Log a string to syslog and into the crash dump file.
190 * We expect ideally nothing to be logged, so that file is created on demand.
191 */
192 ✗ void Watchdog::LogEmergency(string msg) {
193 char ctime_buffer[32];
194
195 ✗ if (!crash_dump_path_.empty()) {
196 ✗ FILE *fp = fopen(crash_dump_path_.c_str(), "a");
197 ✗ if (fp) {
198 ✗ const time_t now = time(NULL);
199 ✗ msg += "\nTimestamp: " + string(ctime_r(&now, ctime_buffer));
200 ✗ if (fwrite(&msg[0], 1, msg.length(), fp) != msg.length()) {
201 ✗ msg += " (failed to report into crash dump file " + crash_dump_path_
202 ✗ + ")";
203 } else {
204 ✗ msg += "\n Crash logged also on file: " + crash_dump_path_ + "\n";
205 }
206 ✗ fclose(fp);
207 } else {
208 ✗ msg += " (failed to open crash dump file " + crash_dump_path_ + ")";
209 }
210 }
211 ✗ LogCvmfs(kLogMonitor, kLogSyslogErr, "%s", msg.c_str());
212 }
213
214 /**
215 * Reads from the file descriptor until the specific gdb prompt is reached or
216 * the pipe gets broken.
217 *
218 * @param fd_pipe the file descriptor of the pipe to be read
219 * @return the data read from the pipe
220 */
221 ✗ string Watchdog::ReadUntilGdbPrompt(int fd_pipe) {
222 #ifdef __APPLE__
223 static const string gdb_prompt = "(lldb)";
224 #else
225 ✗ static const string gdb_prompt = "\n(gdb) ";
226 #endif
227
228 ✗ string result;
229 char mini_buffer;
230 int chars_io;
231 ✗ unsigned int ring_buffer_pos = 0;
232
233 // read from stdout of gdb until gdb prompt occurs --> (gdb)
234 while (1) {
235 ✗ chars_io = read(fd_pipe, &mini_buffer, 1);
236
237 // in case something goes wrong...
238 ✗ if (chars_io <= 0)
239 ✗ break;
240
241 ✗ result += mini_buffer;
242
243 // find the gdb_promt in the stdout data
244 ✗ if (mini_buffer == gdb_prompt[ring_buffer_pos]) {
245 ✗ ++ring_buffer_pos;
246 ✗ if (ring_buffer_pos == gdb_prompt.size()) {
247 ✗ break;
248 }
249 } else {
250 ✗ ring_buffer_pos = 0;
251 }
252 }
253
254 ✗ return result;
255 }
256
257
258 /**
259 * Generates useful information from the backtrace log in the pipe.
260 */
261 ✗ string Watchdog::ReportStacktrace() {
262 CrashData crash_data;
263 ✗ if (!pipe_watchdog_->TryRead<CrashData>(&crash_data)) {
264 ✗ return "failed to read crash data (" + StringifyInt(errno) + ")";
265 }
266
267 ✗ string debug = "--\n";
268 ✗ debug += "Signal: " + StringifyInt(crash_data.signal);
269 ✗ debug += ", errno: " + StringifyInt(crash_data.sys_errno);
270 ✗ debug += ", version: " + string(CVMFS_VERSION);
271 ✗ debug += ", PID: " + StringifyInt(crash_data.pid) + "\n";
272 ✗ debug += "Executable path: " + exe_path_ + "\n";
273
274 ✗ debug += GenerateStackTrace(crash_data.pid);
275
276 // Give the dying process the finishing stroke
277 ✗ if (kill(crash_data.pid, SIGKILL) != 0) {
278 ✗ debug += "Failed to kill cvmfs client! (";
279 ✗ switch (errno) {
280 ✗ case EINVAL:
281 ✗ debug += "invalid signal";
282 ✗ break;
283 ✗ case EPERM:
284 ✗ debug += "permission denied";
285 ✗ break;
286 ✗ case ESRCH:
287 ✗ debug += "no such process";
288 ✗ break;
289 ✗ default:
290 ✗ debug += "unknown error " + StringifyInt(errno);
291 }
292 ✗ debug += ")\n\n";
293 }
294
295 ✗ return debug;
296 }
297
298
299 ✗ void Watchdog::ReportSignalAndContinue(int sig, siginfo_t *siginfo,
300 void * /* context */) {
301 ✗ LogCvmfs(kLogMonitor, kLogSyslogErr,
302 "watchdog: received unexpected signal %d from PID %d / UID %d", sig,
303 siginfo->si_pid, siginfo->si_uid);
304 }
305
306
307 ✗ void Watchdog::SendTrace(int sig, siginfo_t *siginfo, void *context) {
308 ✗ const int send_errno = errno;
309 ✗ if (platform_spinlock_trylock(&Me()->lock_handler_) != 0) {
310 // Concurrent call, wait for the first one to exit the process
311 ✗ while (true) {
312 }
313 }
314
315 // Set the original signal handler for the raised signal in
316 // SIGQUIT (watchdog process will raise SIGQUIT)
317 ✗ (void)sigaction(SIGQUIT, &(Me()->old_signal_handlers_[sig]), NULL);
318
319 // Inform the watchdog that CernVM-FS crashed
320 ✗ if (!Me()->pipe_watchdog_->Write(ControlFlow::kProduceStacktrace)) {
321 ✗ _exit(1);
322 }
323
324 // Send crash information to the watchdog
325 CrashData crash_data;
326 ✗ crash_data.signal = sig;
327 ✗ crash_data.sys_errno = send_errno;
328 ✗ crash_data.pid = getpid();
329 ✗ if (!Me()->pipe_watchdog_->Write<CrashData>(crash_data)) {
330 ✗ _exit(1);
331 }
332
333 // Do not die before the stack trace was generated
334 // kill -SIGQUIT <pid> will finish this
335 ✗ int counter = 0;
336 while (true) {
337 ✗ SafeSleepMs(100);
338 // quit anyway after 30 seconds
339 ✗ if (++counter == 300) {
340 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "stack trace generation failed");
341 // Last attempt to log something useful
342 #if defined(CVMFS_FUSE_MODULE)
343 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "Signal %d, errno %d", sig,
344 send_errno);
345 void *addr[kMaxBacktrace];
346 // Note: this doesn't work due to the signal stack on OS X (it works on
347 // Linux). Since anyway lldb is supposed to produce the backtrace, we
348 // consider it more important to protect cvmfs against stack overflows.
349 ✗ const int num_addr = backtrace(addr, kMaxBacktrace);
350 ✗ char **symbols = backtrace_symbols(addr, num_addr);
351 ✗ string backtrace = "Backtrace (" + StringifyInt(num_addr)
352 ✗ + " symbols):\n";
353 ✗ for (int i = 0; i < num_addr; ++i)
354 ✗ backtrace += string(symbols[i]) + "\n";
355 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "%s", backtrace.c_str());
356 ✗ LogCvmfs(kLogCvmfs, kLogSyslogErr, "address of g_cvmfs_exports: %p",
357 &g_cvmfs_exports);
358 #endif
359
360 ✗ _exit(1);
361 }
362 }
363
364 _exit(1);
365 }
366
367
368 /**
369 * Sets the signal handlers of the current process according to the ones
370 * defined in the given SigactionMap.
371 *
372 * @param signal_handlers a map of SIGNAL -> struct sigaction
373 * @return a SigactionMap containing the old handlers
374 */
375 49 Watchdog::SigactionMap Watchdog::SetSignalHandlers(
376 const SigactionMap &signal_handlers) {
377 49 SigactionMap old_signal_handlers;
378 49 SigactionMap::const_iterator i = signal_handlers.begin();
379 49 const SigactionMap::const_iterator iend = signal_handlers.end();
380
2/2
✓ Branch 1 taken 637 times.
✓ Branch 2 taken 49 times.
686 for (; i != iend; ++i) {
381 struct sigaction old_signal_handler;
382
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 637 times.
637 if (sigaction(i->first, &i->second, &old_signal_handler) != 0) {
383 ✗ PANIC(NULL);
384 }
385
1/2
✓ Branch 2 taken 637 times.
✗ Branch 3 not taken.
637 old_signal_handlers[i->first] = old_signal_handler;
386 }
387
388 98 return old_signal_handlers;
389 }
390
391
392 /**
393 * Fork the watchdog process and put it on hold until Spawn() is called.
394 */
395 49 void Watchdog::Fork(bool needs_read_environ) {
396
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 Pipe<kPipeWatchdogPid> pipe_pid;
397 98 pipe_watchdog_ = std::unique_ptr<Pipe<kPipeWatchdog> >(
398
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
98 new Pipe<kPipeWatchdog>());
399 98 pipe_listener_ = std::unique_ptr<Pipe<kPipeWatchdogSupervisor> >(
400
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
98 new Pipe<kPipeWatchdogSupervisor>());
401
402 pid_t pid;
403 int statloc;
404
1/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
49 switch (pid = fork()) {
405 ✗ case -1:
406 ✗ PANIC(NULL);
407 ✗ case 0:
408 // Double fork to avoid zombie
409 ✗ switch (fork()) {
410 ✗ case -1:
411 ✗ _exit(1);
412 ✗ case 0: {
413 ✗ pipe_watchdog_->CloseWriteFd();
414 ✗ Daemonize();
415 #ifndef __APPLE__
416
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
49 if (!oom_score_adj_.empty()) {
417 // After seteuid() the process is non-dumpable and its /proc
418 // entries are owned by root, so temporarily regain root if possible
419 ✗ const uid_t euid = geteuid();
420 ✗ const gid_t egid = getegid();
421 ✗ const bool regain_root = (euid != 0) && (getuid() == 0);
422 ✗ if (regain_root)
423 ✗ SwitchCredentials(0, egid, true /* temporarily */);
424 ✗ const int fd_oom = open("/proc/self/oom_score_adj", O_WRONLY);
425 // Lowering the score requires CAP_SYS_RESOURCE, so failure is
426 // expected for unprivileged mounts
427 ✗ if ((fd_oom < 0)
428 ✗ || !SafeWrite(fd_oom, oom_score_adj_.data(),
429 oom_score_adj_.length())) {
430 ✗ LogCvmfs(kLogMonitor, kLogDebug,
431 "watchdog: failed to set oom_score_adj to %s",
432 oom_score_adj_.c_str());
433 }
434 ✗ if (fd_oom >= 0)
435 ✗ close(fd_oom);
436 ✗ if (regain_root && !SwitchCredentials(euid, egid, true)) {
437 ✗ PANIC(kLogStderr | kLogSyslogErr,
438 "Failed to drop watchdog credentials");
439 }
440 }
441 #endif
442
4/8
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 49 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 49 times.
49 if ((geteuid() != 0) && SetuidCapabilityPermitted()) {
443 ✗ const std::vector<cap_value_t> nocaps;
444 ✗ if (on_exit_) {
445 // Reduce to minimum capabilities, which unfortunately is
446 // still quite powerful.
447 // CAP_SYS_ADMIN is needed to unmount, and CAP_SYS_PTRACE
448 // is needed when needs_read_environ is true because then
449 // the main Fuse process has elevated capabilities and
450 // ptrace (needed for collecting a stack trace) is not
451 // allowed on a process with more capabilities.
452 ✗ if (needs_read_environ) {
453 const std::vector<cap_value_t> reservecaps = {CAP_SYS_ADMIN,
454 ✗ CAP_SYS_PTRACE};
455 ✗ const std::vector<cap_value_t> inheritcaps = {CAP_SYS_PTRACE};
456 ✗ if (!ClearPermittedCapabilities(reservecaps, inheritcaps))
457 ✗ PANIC(kLogStderr | kLogSyslogErr,
458 "Failed to reduce watchdog process capabilities");
459 ✗ } else {
460 ✗ const std::vector<cap_value_t> reservecaps = {CAP_SYS_ADMIN};
461 ✗ if (!ClearPermittedCapabilities(reservecaps, nocaps))
462 ✗ PANIC(kLogStderr | kLogSyslogErr,
463 "Failed to reduce watchdog process capabilities");
464 }
465 } else {
466 // Only need to be able to do the stack trace, and the
467 // main process needs no extra capabilities, so we can
468 // drop all capabilities.
469 ✗ if (!ClearPermittedCapabilities(nocaps, nocaps))
470 ✗ PANIC(kLogStderr | kLogSyslogErr,
471 "Failed to clear watchdog process capabilities");
472 }
473 }
474 // send the watchdog PID to the supervisee
475 49 const pid_t watchdog_pid = getpid();
476
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 pipe_pid.Write(watchdog_pid);
477
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 pipe_pid.CloseWriteFd();
478 // Close all unused file descriptors
479 // close also usyslog, only get it back if necessary
480 // string usyslog_save = GetLogMicroSyslog();
481
1/3
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
49 const string debuglog_save = GetLogDebugFile();
482
2/4
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 49 times.
✗ Branch 6 not taken.
49 SetLogDebugFile("");
483
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 const string usyslog_save = GetLogMicroSyslog();
484
2/4
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 49 times.
✗ Branch 6 not taken.
49 SetLogMicroSyslog("");
485 // Gracefully close the syslog before closing all fds. The next call
486 // to syslog will reopen it.
487
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 closelog();
488 // Let's keep stdin, stdout, stderr open at /dev/null (daemonized)
489 // in order to prevent accidental outputs from messing with another
490 // file descriptor
491 49 std::set<int> preserve_fds;
492
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 preserve_fds.insert(0);
493
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 preserve_fds.insert(1);
494
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 preserve_fds.insert(2);
495
1/2
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
49 preserve_fds.insert(pipe_watchdog_->GetReadFd());
496
1/2
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
49 preserve_fds.insert(pipe_listener_->GetWriteFd());
497
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 CloseAllFildes(preserve_fds);
498
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 SetLogMicroSyslog(usyslog_save); // no-op if usyslog not used
499
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 SetLogDebugFile(debuglog_save); // no-op if debug log not used
500
501
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
49 if (WaitForSupervisee())
502 ✗ Supervise();
503
504
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 pipe_watchdog_->CloseReadFd();
505
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 pipe_listener_->CloseWriteFd();
506 49 exit(0);
507 }
508 ✗ default:
509 ✗ _exit(0);
510 }
511 49 default:
512
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 pipe_watchdog_->CloseReadFd();
513
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 pipe_listener_->CloseWriteFd();
514
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 pipe_pid.CloseWriteFd();
515
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
49 if (waitpid(pid, &statloc, 0) != pid)
516 ✗ PANIC(NULL);
517
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
49 if (!WIFEXITED(statloc) || WEXITSTATUS(statloc))
518 ✗ PANIC(NULL);
519 }
520
521 // retrieve the watchdog PID from the pipe
522
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 pipe_pid.Read(&watchdog_pid_);
523
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 pipe_pid.CloseReadFd();
524 49 }
525
526
527 49 bool Watchdog::WaitForSupervisee() {
528 // We want broken pipes not to raise a signal but handle the error in the
529 // read/write code
530 49 platform_sighandler_t rv_sig = signal(SIGPIPE, SIG_IGN);
531
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 assert(rv_sig != SIG_ERR);
532
533 // The watchdog is not supposed to receive signals. If it does, report it.
534 struct sigaction sa;
535 49 memset(&sa, 0, sizeof(sa));
536 49 sa.sa_sigaction = ReportSignalAndContinue;
537 49 sa.sa_flags = SA_SIGINFO;
538 49 sigfillset(&sa.sa_mask);
539
540 49 SigactionMap signal_handlers;
541
2/2
✓ Branch 0 taken 637 times.
✓ Branch 1 taken 49 times.
686 for (size_t i = 0; i < sizeof(g_suppressed_signals) / sizeof(int); i++) {
542
1/2
✓ Branch 1 taken 637 times.
✗ Branch 2 not taken.
637 signal_handlers[g_suppressed_signals[i]] = sa;
543 }
544
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 SetSignalHandlers(signal_handlers);
545
546 49 ControlFlow::Flags control_flow = ControlFlow::kUnknown;
547
548
3/4
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 48 times.
✓ Branch 5 taken 1 times.
49 if (!pipe_watchdog_->TryRead(&control_flow)) {
549
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 LogCvmfs(kLogMonitor, kLogDebug, "supervisee canceled watchdog");
550 48 return false;
551 }
552
553
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 switch (control_flow) {
554 1 case ControlFlow::kQuit:
555 1 return false;
556 ✗ case ControlFlow::kQuitWithExit:
557 ✗ if (on_exit_)
558 ✗ on_exit_(false);
559 ✗ return false;
560 ✗ case ControlFlow::kSupervise:
561 ✗ break;
562 ✗ default:
563 ✗ LogEmergency("Internal error: invalid control flow");
564 ✗ return false;
565 }
566
567 size_t size;
568 ✗ pipe_watchdog_->Read(&size);
569 ✗ crash_dump_path_.resize(size);
570 ✗ if (size > 0) {
571 ✗ pipe_watchdog_->Read(&crash_dump_path_[0], size);
572
573 ✗ const int retval = chdir(GetParentPath(crash_dump_path_).c_str());
574 ✗ if (retval != 0) {
575 ✗ LogEmergency(std::string("Cannot change to crash dump directory: ")
576 ✗ + crash_dump_path_);
577 ✗ return false;
578 }
579 ✗ crash_dump_path_ = GetFileName(crash_dump_path_);
580 }
581 ✗ return true;
582 49 }
583
584
585 /**
586 * Set up the signal handling and kick off the supervision.
587 */
588 ✗ void Watchdog::Spawn(const std::string &crash_dump_path) {
589 // lower restrictions for ptrace
590 ✗ if (!platform_allow_ptrace(watchdog_pid_)) {
591 ✗ LogCvmfs(kLogMonitor, kLogSyslogWarn,
592 "failed to allow ptrace() for watchdog (PID: %d). "
593 "Post crash stacktrace might not work",
594 watchdog_pid_);
595 }
596
597 // Extra stack for signal handlers
598 ✗ const int stack_size = kSignalHandlerStacksize; // 2 MB
599 ✗ sighandler_stack_.ss_sp = smalloc(stack_size);
600 ✗ sighandler_stack_.ss_size = stack_size;
601 ✗ sighandler_stack_.ss_flags = 0;
602 ✗ if (sigaltstack(&sighandler_stack_, NULL) != 0)
603 ✗ PANIC(NULL);
604
605 // define our crash signal handler
606 struct sigaction sa;
607 ✗ memset(&sa, 0, sizeof(sa));
608 ✗ sa.sa_sigaction = SendTrace;
609 ✗ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
610 ✗ sigfillset(&sa.sa_mask);
611
612 ✗ SigactionMap signal_handlers;
613 ✗ for (size_t i = 0; i < sizeof(g_crash_signals) / sizeof(int); i++) {
614 ✗ signal_handlers[g_crash_signals[i]] = sa;
615 }
616 ✗ old_signal_handlers_ = SetSignalHandlers(signal_handlers);
617
618 ✗ pipe_terminate_ = std::unique_ptr<Pipe<kPipeThreadTerminator> >(
619 ✗ new Pipe<kPipeThreadTerminator>());
620 ✗ const int retval = pthread_create(&thread_listener_, NULL,
621 MainWatchdogListener, this);
622 ✗ assert(retval == 0);
623
624 ✗ if (spawned_) {
625 // This happens after a reload, when the watchdog process is
626 // already running so we can exit here.
627 ✗ return;
628 }
629
630 ✗ pipe_watchdog_->Write(ControlFlow::kSupervise);
631 ✗ const size_t path_size = crash_dump_path.size();
632 ✗ pipe_watchdog_->Write(path_size);
633 ✗ if (path_size > 0) {
634 ✗ pipe_watchdog_->Write(crash_dump_path.data(), path_size);
635 }
636
637 ✗ spawned_ = true;
638 }
639
640
641 ✗ void *Watchdog::MainWatchdogListener(void *data) {
642 ✗ Watchdog *watchdog = static_cast<Watchdog *>(data);
643 ✗ LogCvmfs(kLogMonitor, kLogDebug, "starting watchdog listener");
644
645 ✗ if ((getuid() != 0) && SetuidCapabilityPermitted()) {
646 // Drop all capabilities, none are needed in the listener
647 ✗ const std::vector<cap_value_t> nocaps;
648 ✗ if (!ClearPermittedCapabilities(nocaps, nocaps))
649 ✗ PANIC(kLogStderr | kLogSyslogErr,
650 "Failed to clear watchdog listener capabilities");
651 }
652
653 struct pollfd watch_fds[2];
654 ✗ watch_fds[0].fd = watchdog->pipe_listener_->GetReadFd();
655 ✗ watch_fds[0].events = 0; // Only check for POLL[ERR,HUP,NVAL] in revents
656 ✗ watch_fds[0].revents = 0;
657 ✗ watch_fds[1].fd = watchdog->pipe_terminate_->GetReadFd();
658 ✗ watch_fds[1].events = POLLIN | POLLPRI;
659 ✗ watch_fds[1].revents = 0;
660 while (true) {
661 ✗ const int retval = poll(watch_fds, 2, -1);
662 ✗ if (retval < 0) {
663 ✗ continue;
664 }
665
666 // Terminate I/O thread
667 ✗ if (watch_fds[1].revents)
668 ✗ break;
669
670 ✗ if (watch_fds[0].revents) {
671 ✗ if ((watch_fds[0].revents & POLLERR) || (watch_fds[0].revents & POLLHUP)
672 ✗ || (watch_fds[0].revents & POLLNVAL)) {
673 ✗ LogCvmfs(kLogMonitor, kLogDebug | kLogSyslogErr,
674 "watchdog disappeared, disabling stack trace reporting "
675 "(revents: %d / %d|%d|%d)",
676 ✗ watch_fds[0].revents, POLLERR, POLLHUP, POLLNVAL);
677 ✗ watchdog->SetSignalHandlers(watchdog->old_signal_handlers_);
678 ✗ PANIC(kLogDebug | kLogSyslogErr, "watchdog disappeared, aborting");
679 }
680 ✗ PANIC(NULL);
681 }
682 }
683
684 ✗ LogCvmfs(kLogMonitor, kLogDebug, "stopping watchdog listener");
685 ✗ return NULL;
686 }
687
688
689 ✗ void Watchdog::Supervise() {
690 ✗ ControlFlow::Flags control_flow = ControlFlow::kUnknown;
691
692 ✗ if (!pipe_watchdog_->TryRead<ControlFlow::Flags>(&control_flow)) {
693 ✗ LogEmergency("watchdog: unexpected termination ("
694 ✗ + StringifyInt(control_flow) + ")");
695 ✗ if (on_exit_)
696 ✗ on_exit_(true /* crashed */);
697 } else {
698 ✗ switch (control_flow) {
699 ✗ case ControlFlow::kProduceStacktrace:
700 ✗ LogEmergency(ReportStacktrace());
701 ✗ if (on_exit_)
702 ✗ on_exit_(true /* crashed */);
703 ✗ break;
704
705 ✗ case ControlFlow::kQuitWithExit:
706 ✗ if (on_exit_)
707 ✗ on_exit_(false /* crashed */);
708 ✗ break;
709
710 ✗ case ControlFlow::kQuit:
711 ✗ break;
712
713 ✗ default:
714 ✗ LogEmergency("watchdog: unexpected error");
715 ✗ break;
716 }
717 }
718 }
719
720
721 /**
722 * Save the state of the watchdog listener thread before reload.
723 */
724 ✗ void Watchdog::SaveState(WatchdogState *saved_state) {
725 ✗ saved_state->spawned = spawned_;
726 ✗ saved_state->pid = watchdog_pid_;
727 ✗ if (spawned_) {
728 ✗ saved_state->watchdog_write_fd = pipe_watchdog_->GetWriteFd();
729 ✗ saved_state->listener_read_fd = pipe_listener_->GetReadFd();
730 }
731 }
732
733
734 /**
735 * Restore the state of the watchdog listener reload
736 */
737 ✗ void Watchdog::RestoreState(WatchdogState *saved_state) {
738 ✗ watchdog_pid_ = saved_state->pid;
739 ✗ if (!saved_state->spawned) {
740 ✗ return;
741 }
742 ✗ pipe_watchdog_ = std::unique_ptr<Pipe<kPipeWatchdog> >(
743 ✗ new Pipe<kPipeWatchdog>(-1, saved_state->watchdog_write_fd));
744 ✗ pipe_listener_ = std::unique_ptr<Pipe<kPipeWatchdogSupervisor> >(
745 ✗ new Pipe<kPipeWatchdogSupervisor>(saved_state->listener_read_fd, -1));
746 ✗ spawned_ = true;
747 }
748
749
750 49 Watchdog::Watchdog(FnOnExit on_exit, const std::string &oom_score_adj)
751 49 : spawned_(false)
752 49 , maintenance_mode_(false)
753
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 , exe_path_(string(platform_getexepath()))
754
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 , oom_score_adj_(oom_score_adj)
755 49 , watchdog_pid_(0)
756 98 , on_exit_(on_exit) {
757 49 const int retval = platform_spinlock_init(&lock_handler_, 0);
758
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 assert(retval == 0);
759 49 memset(&sighandler_stack_, 0, sizeof(sighandler_stack_));
760 49 }
761
762
763 1 Watchdog::~Watchdog() {
764
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (spawned_) {
765 // Reset signal handlers
766 ✗ signal(SIGQUIT, SIG_DFL);
767 ✗ signal(SIGILL, SIG_DFL);
768 ✗ signal(SIGABRT, SIG_DFL);
769 ✗ signal(SIGFPE, SIG_DFL);
770 ✗ signal(SIGSEGV, SIG_DFL);
771 ✗ signal(SIGBUS, SIG_DFL);
772 ✗ signal(SIGPIPE, SIG_DFL);
773 ✗ signal(SIGXFSZ, SIG_DFL);
774 ✗ free(sighandler_stack_.ss_sp);
775 ✗ sighandler_stack_.ss_size = 0;
776
777 // The watchdog listener thread exits on any message received
778 ✗ pipe_terminate_->Write(ControlFlow::kQuit);
779 ✗ pthread_join(thread_listener_, NULL);
780 ✗ pipe_terminate_->Close();
781 }
782
783
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!maintenance_mode_) {
784 // Shutdown the watchdog except when doing a reload
785
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (on_exit_) {
786 ✗ pipe_watchdog_->Write(ControlFlow::kQuitWithExit);
787 } else {
788 1 pipe_watchdog_->Write(ControlFlow::kQuit);
789 }
790 1 pipe_watchdog_->CloseWriteFd();
791 1 pipe_listener_->CloseReadFd();
792 } else {
793 // Release the references to the watchdog pipes without closing them
794 ✗ pipe_watchdog_.release();
795 ✗ pipe_listener_.release();
796 }
797
798 1 platform_spinlock_destroy(&lock_handler_);
799 1 LogCvmfs(kLogMonitor, kLogDebug, "monitor stopped");
800 1 instance_ = NULL;
801 1 }
802