libreoffice-online/common/SigUtil.cpp
Ashod Nakashian efe874f89c wsd: simplify shutdown and termination flagging
With the use of a single flag for both, the
logic is now less ambiguous, as we cannot have
termination flagged without also implying
shutting down.
The assertions are no longer needed.

Now that setting the termination flag
explicitly implies having the shut down flag
as well, the checks are simpler. We only
need to check that the shutdown is not set
to continue running as normal, since having
the termination flag must perfoce mean shut
down is also set, there is no need to check
both.

Change-Id: I99e22f5668385182b0594040a8e3354b55e74642
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
2023-08-14 16:32:50 +02:00

592 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config.h>
#include "SigUtil.hpp"
#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
# include <execinfo.h>
#endif
#include <csignal>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/uio.h>
#include <unistd.h>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <array>
#include <Socket.hpp>
#include "Common.hpp"
#include "Log.hpp"
namespace
{
#ifndef IOS
/// The valid states of the process.
enum class RunState : char
{
Run = 0, //< Normal up-and-running state.
ShutDown, //< Request to shut down gracefully.
Terminate //< Immediate termination.
};
/// Single flag to control the current run state.
static std::atomic<RunState> RunStateFlag(RunState::Run);
#if !MOBILEAPP
static std::atomic<bool> DumpGlobalState(false);
static std::atomic<bool> ForwardSigUsr2Flag(false); //< Flags to forward SIG_USR2 to children.
#endif
#endif
static size_t ActivityStringIndex = 0;
static std::array<std::string, 8> ActivityStrings;
static bool UnattendedRun = false;
static int SignalLogFD = STDERR_FILENO; //< The FD where signalLogs are dumped.
static char* VersionInfo = nullptr;
static char FatalGdbString[256] = { '\0' };
} // namespace
namespace SigUtil
{
#ifndef IOS
bool getShutdownRequestFlag() { return RunStateFlag >= RunState::ShutDown; }
bool getTerminationFlag() { return RunStateFlag >= RunState::Terminate; }
void setTerminationFlag()
{
// Set the forced-termination flag.
RunStateFlag = RunState::Terminate;
#if !MOBILEAPP
// And wake-up the thread.
SocketPoll::wakeupWorld();
#endif
}
void requestShutdown()
{
RunState oldState = RunState::Run;
if (RunStateFlag.compare_exchange_strong(oldState, RunState::ShutDown))
SocketPoll::wakeupWorld();
}
#if MOBILEAPP
void resetTerminationFlags() { RunStateFlag = RunState::Run; }
#endif
#endif // !IOS
void checkDumpGlobalState(GlobalDumpStateFn dumpState)
{
#if !MOBILEAPP
assert(dumpState && "Invalid callback for checkDumpGlobalState");
if (DumpGlobalState)
{
DumpGlobalState = false;
dumpState();
}
#else
(void) dumpState;
#endif
}
void checkForwardSigUsr2(ForwardSigUsr2Fn forwardSigUsr2)
{
#if !MOBILEAPP
assert(forwardSigUsr2 && "Invalid callback for checkForwardSigUsr2");
if (ForwardSigUsr2Flag)
{
ForwardSigUsr2Flag = false;
forwardSigUsr2();
}
#else
(void) forwardSigUsr2;
#endif
}
void addActivity(const std::string &message)
{
ActivityStrings[ActivityStringIndex++ % ActivityStrings.size()] = message;
}
void setUnattended()
{
UnattendedRun = true;
}
#if !MOBILEAPP
/// Open the signalLog file.
void signalLogOpen()
{
// Always default to stderr.
SignalLogFD = STDERR_FILENO;
}
/// Close the signalLog file.
void signalLogClose()
{
fsync(SignalLogFD);
}
void signalLogPrefix()
{
char buffer[1024];
Log::prefix<sizeof(buffer) - 1>(buffer, "SIG");
signalLog(buffer);
}
// We need a signal safe means of writing messages
// $ man 7 signal
void signalLog(const char *message)
{
while (true)
{
const int length = std::strlen(message);
const int written = write(SignalLogFD, message, length);
if (written < 0)
{
if (errno == EINTR)
continue; // ignore.
else
break;
}
message += written;
if (message[0] == '\0')
break;
}
}
// We need a signal safe means of writing messages
// $ man 7 signal
void signalLogNumber(std::size_t num, int base)
{
int i;
char buf[22];
if (num == 0)
{
signalLog("0");
return;
}
buf[21] = '\0';
assert (base == 10 || base == 16);
for (i = 20; i > 0 && num > 0; --i)
{
int d = num % base;
buf[i] = (d < 10) ? ('0' + d) : ('a' + d - 10);
num /= base;
}
signalLog(buf + i + 1);
}
/// This traps the signal-handler so we don't _Exit
/// while dumping stack trace. It's re-entrant.
/// Used to safely increment and decrement the signal-handler trap.
class SigHandlerTrap
{
static std::atomic<int> SigHandling;
public:
SigHandlerTrap() { ++SigHandlerTrap::SigHandling; }
~SigHandlerTrap() { --SigHandlerTrap::SigHandling; }
/// Check that we have exclusive access to the trap.
/// Otherwise, there is another signal in progress.
bool isExclusive() const
{
// Return true if we are alone.
return SigHandlerTrap::SigHandling == 1;
}
/// Wait for the trap to clear.
static void wait()
{
while (SigHandlerTrap::SigHandling)
sleep(1);
}
};
std::atomic<int> SigHandlerTrap::SigHandling;
void waitSigHandlerTrap()
{
SigHandlerTrap::wait();
}
const char *signalName(const int signo)
{
// LCOV_EXCL_START Coverage for these is not very useful.
switch (signo)
{
#define CASE(x) case SIG##x: return "SIG" #x
CASE(HUP);
CASE(INT);
CASE(QUIT);
CASE(ILL);
CASE(ABRT);
CASE(FPE);
CASE(KILL);
CASE(SEGV);
CASE(PIPE);
CASE(ALRM);
CASE(TERM);
CASE(USR1);
CASE(USR2);
CASE(CHLD);
CASE(CONT);
CASE(STOP);
CASE(TSTP);
CASE(TTIN);
CASE(TTOU);
CASE(BUS);
#ifdef SIGPOLL
CASE(POLL);
#endif
CASE(PROF);
CASE(SYS);
CASE(TRAP);
CASE(URG);
CASE(VTALRM);
CASE(XCPU);
CASE(XFSZ);
#ifdef SIGEMT
CASE(EMT);
#endif
#ifdef SIGSTKFLT
CASE(STKFLT);
#endif
#if defined(SIGIO) && defined(SIGPOLL) && SIGIO != SIGPOLL
CASE(IO);
#endif
#ifdef SIGPWR
CASE(PWR);
#endif
#ifdef SIGLOST
CASE(LOST);
#endif
CASE(WINCH);
#if defined(SIGINFO) && defined(SIGPWR) && SIGINFO != SIGPWR
CASE(INFO);
#endif
#undef CASE
default:
return "unknown";
}
// LCOV_EXCL_STOP Coverage for these is not very useful.
}
static
void handleTerminationSignal(const int signal)
{
const auto onrre = errno; // Save.
bool hardExit = false;
const char* domain;
RunState oldState = RunState::Run;
if ((signal == SIGINT || signal == SIGTERM) &&
RunStateFlag.compare_exchange_strong(oldState, RunState::ShutDown))
{
domain = " Shutdown signal received: ";
}
else
{
assert(RunStateFlag > RunState::Run && "Must have had Terminate flag");
oldState = RunState::ShutDown;
if (RunStateFlag.compare_exchange_strong(oldState, RunState::Terminate))
{
domain = " Forced-Termination signal received: ";
}
else
{
assert(RunStateFlag == RunState::Terminate && "Must have had Terminate flag");
domain = " ok, ok - hard-termination signal received: ";
hardExit = true;
}
}
signalLogOpen();
signalLogPrefix();
signalLog(domain);
signalLog(signalName(signal));
signalLog("\n");
signalLogClose();
if (!hardExit)
SocketPoll::wakeupWorld();
else
{
#if CODE_COVERAGE
__gcov_dump();
#endif
::signal (signal, SIG_DFL);
errno = onrre; // Restore.
::raise (signal);
}
errno = onrre; // Restore.
}
static
void handleFatalSignal(const int signal, siginfo_t *info, void * /* uctxt */)
{
SigHandlerTrap guard;
const bool bReEntered = !guard.isExclusive();
if (!bReEntered)
signalLogOpen();
signalLogPrefix();
// Heap corruption can re-enter through backtrace.
if (bReEntered)
signalLog(" Fatal double signal received: ");
else
signalLog(" Fatal signal received: ");
signalLog(signalName(signal));
if (info)
{
signalLog(" code: ");
signalLogNumber(info->si_code);
signalLog(" for address: 0x");
signalLogNumber((size_t)info->si_addr, 16);
}
signalLog("\n");
signalLog("Recent activity:\n");
for (size_t i = 0; i < ActivityStrings.size(); ++i)
{
size_t idx = (ActivityStringIndex + i) % ActivityStrings.size();
if (!ActivityStrings[idx].empty())
{
// no plausible impl. will heap allocate in c_str.
signalLog("\t");
signalLog(ActivityStrings[idx].c_str());
signalLog("\n");
}
}
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_DFL;
sigaction(signal, &action, nullptr);
if (!bReEntered)
{
dumpBacktrace();
signalLogClose();
}
// let default handler process the signal
::raise(signal);
}
void dumpBacktrace()
{
#if !defined(__ANDROID__)
signalLog("\nBacktrace ");
signalLogNumber(getpid());
if (VersionInfo)
{
signalLog(" - ");
signalLog(VersionInfo);
}
signalLog(":\n");
const int maxSlots = 50;
void *backtraceBuffer[maxSlots];
const int numSlots = backtrace(backtraceBuffer, maxSlots);
if (numSlots > 0)
{
backtrace_symbols_fd(backtraceBuffer, numSlots, SignalLogFD);
}
#else
LOG_INF("Backtrace not available on Android.");
#endif
#if !ENABLE_DEBUG
if (std::getenv("COOL_DEBUG"))
#endif
{
if (UnattendedRun)
{
static constexpr auto msg =
"Crashed in unattended run and won't wait for debugger. Re-run without "
"--unattended to attach a debugger.";
std::cerr << msg << std::endl;
}
else
{
signalLog(FatalGdbString);
std::cerr << "Sleeping 60s to allow debugging: attach " << getpid() << std::endl;
sleep(60);
std::cerr << "Finished sleeping to allow debugging of: " << getpid() << std::endl;
}
}
}
void setVersionInfo(const std::string &versionInfo)
{
if (VersionInfo)
free (VersionInfo);
VersionInfo = strdup(versionInfo.c_str());
}
void setFatalSignals(const std::string &versionInfo)
{
struct sigaction action;
setVersionInfo(versionInfo);
// Set up the fatal-signal handler. (N.B. three-argument handler)
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = handleFatalSignal;
sigaction(SIGSEGV, &action, nullptr);
sigaction(SIGBUS, &action, nullptr);
sigaction(SIGABRT, &action, nullptr);
sigaction(SIGILL, &action, nullptr);
sigaction(SIGFPE, &action, nullptr);
// Set up the terminatio-signal handler. (N.B. single-argument handler)
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = handleTerminationSignal;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
sigaction(SIGQUIT, &action, nullptr);
sigaction(SIGHUP, &action, nullptr);
// Prepare this in advance just in case.
std::ostringstream stream;
stream << "\nERROR: Fatal signal! Attach debugger with:\n"
<< "sudo gdb --pid=" << getpid() << "\n or \n"
<< "sudo gdb --q --n --ex 'thread apply all backtrace full' --batch --pid="
<< getpid() << '\n';
std::string streamStr = stream.str();
assert (sizeof (FatalGdbString) > strlen(streamStr.c_str()) + 1);
strncpy(FatalGdbString, streamStr.c_str(), sizeof(FatalGdbString)-1);
FatalGdbString[sizeof(FatalGdbString)-1] = '\0';
}
static
void handleUserSignal(const int signal)
{
signalLogOpen();
signalLogPrefix();
signalLog(" User signal received: ");
signalLog(signalName(signal));
signalLog("\n");
if (signal == SIGUSR1)
{
DumpGlobalState = true;
}
else if (signal == SIGUSR2)
{
constexpr int maxSlots = 250;
void* backtraceBuffer[maxSlots];
const int numSlots = backtrace(backtraceBuffer, maxSlots);
if (numSlots > 0)
backtrace_symbols_fd(backtraceBuffer, numSlots, SignalLogFD);
ForwardSigUsr2Flag = true;
}
signalLogClose();
SocketPoll::wakeupWorld();
}
static
void handleDebuggerSignal(const int /*signal*/)
{}
void setUserSignals()
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = handleUserSignal;
sigaction(SIGUSR1, &action, nullptr);
sigaction(SIGUSR2, &action, nullptr);
#if !defined(__ANDROID__)
// Prime backtrace to make sure libgcc is loaded.
constexpr int maxSlots = 1;
void* backtraceBuffer[maxSlots + 1];
backtrace(backtraceBuffer, maxSlots);
#endif
}
void setDebuggerSignal()
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = handleDebuggerSignal;
sigaction(SIGUSR1, &action, nullptr);
}
/// Kill the given pid with SIGKILL as default. Returns true when the pid does not exist any more.
bool killChild(const int pid, const int signal)
{
LOG_DBG("Killing PID: " << pid << " with " << signalName(signal));
// Don't kill anything in the fuzzer case: pid == 0 would kill the fuzzer itself, and
// killing random other processes is not a great idea, either.
if (Util::isFuzzing() || kill(pid, signal) == 0 || errno == ESRCH)
{
// Killed or doesn't exist.
return true;
}
LOG_SYS("Error when trying to kill PID: " << pid << ". Will wait for termination.");
constexpr int sleepMs = 50;
constexpr int count = std::max(CHILD_REBALANCE_INTERVAL_MS / sleepMs, 2);
for (int i = 0; i < count; ++i)
{
if (kill(pid, 0) == 0 || errno == ESRCH)
{
// Doesn't exist.
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleepMs));
}
return false;
}
#endif // !MOBILEAPP
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */