blob: d545a5a02823a09abc9409cf48d7fcb40e2a1d7f [file] [log] [blame]
#include <new>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <new>
#include <stdexcept>
#include <memory>
extern "C"
{
#include <time.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/utsname.h>
}
#include <DrbdMon.h>
#include <MessageLog.h>
// LOG_CAPACITY must be >= 1
const size_t LOG_CAPACITY {10};
// Clear screen escape sequence
const char* ANSI_CLEAR = "\x1b[H\x1b[2J";
// Delay of 3 seconds for delayed restarts
static const time_t DELAY_SECS = 3;
static const long DELAY_NANOSECS = 0;
static void reset_delay(struct timespec& delay) noexcept;
static void clear_screen() noexcept;
static void cond_print_error_header(bool& error_header_printed, const std::string* const node_name) noexcept;
static bool adjust_ids(MessageLog* log, bool& ids_safe);
static void query_node_name(std::unique_ptr<std::string>& node_name);
int main(int argc, char* argv[])
{
int exit_code {0};
struct timespec delay;
struct timespec remaining;
reset_delay(delay);
reset_delay(remaining);
DrbdMon::fail_info fail_data {DrbdMon::fail_info::NONE};
DrbdMon::finish_action fin_action {DrbdMon::finish_action::RESTART_DELAYED};
std::unique_ptr<MessageLog> log;
std::unique_ptr<std::string> node_name;
bool ids_safe {false};
while (fin_action != DrbdMon::finish_action::TERMINATE &&
fin_action != DrbdMon::finish_action::TERMINATE_NO_CLEAR)
{
bool error_header_printed {false};
try
{
if (log == nullptr)
{
// std::out_of_range exception not handled, as it is
// only thrown if LOG_CAPACITY < 1
log = std::unique_ptr<MessageLog>(new MessageLog(LOG_CAPACITY));
}
if (node_name == nullptr)
{
query_node_name(node_name);
}
if (ids_safe || adjust_ids(log.get(), ids_safe))
{
const std::unique_ptr<DrbdMon> dm_instance(
new DrbdMon(argc, argv, *log, fail_data, node_name.get())
);
dm_instance->run();
fin_action = dm_instance->get_fin_action();
if (fin_action != DrbdMon::finish_action::TERMINATE_NO_CLEAR)
{
clear_screen();
}
}
else
{
fin_action = DrbdMon::finish_action::TERMINATE;
}
}
catch (std::bad_alloc& out_of_memory_exc)
{
clear_screen();
fin_action = DrbdMon::finish_action::RESTART_DELAYED;
fail_data = DrbdMon::fail_info::OUT_OF_MEMORY;
}
// Display any log messages
if (log != nullptr)
{
if (log->has_entries())
{
cond_print_error_header(error_header_printed, node_name.get());
std::fputs("** DrbdMon messages log\n\n", stdout);
log->display_messages(stderr);
fputc('\n', stdout);
}
}
if (fail_data == DrbdMon::fail_info::OUT_OF_MEMORY)
{
cond_print_error_header(error_header_printed, node_name.get());
std::fputs("** DrbdMon: Out of memory, trying to restart\n", stdout);
}
// Cleanup any zombie child processes
pid_t child_pid {0};
do
{
child_pid = waitpid(-1, nullptr, WNOHANG);
}
while (child_pid > 0);
if (fin_action == DrbdMon::finish_action::RESTART_DELAYED)
{
cond_print_error_header(error_header_printed, node_name.get());
std::fprintf(stdout, "** DrbdMon: Reinitializing in %u seconds\n",
static_cast<unsigned int> (delay.tv_sec));
// Attempt to unblock signals before waiting, so one can
// easily exit the program immediately by hitting Ctrl-C,
// closing the terminal or sending a TERM signal
sigset_t signal_mask;
if (sigemptyset(&signal_mask) == 0)
{
// These calls are unchecked; if adding a signal fails,
// it cannot be fixed anyway
sigaddset(&signal_mask, SIGTERM);
sigaddset(&signal_mask, SIGINT);
sigaddset(&signal_mask, SIGHUP);
sigprocmask(SIG_UNBLOCK, &signal_mask, nullptr);
}
// Suspend to delay the restart
while (nanosleep(&delay, &remaining) != 0)
{
delay = remaining;
}
reset_delay(delay);
}
else
if (fin_action == DrbdMon::finish_action::RESTART_IMMED)
{
cond_print_error_header(error_header_printed, node_name.get());
std::fputs("** DrbdMon: Reinitializing immediately\n", stdout);
}
}
return exit_code;
}
static void reset_delay(struct timespec& delay) noexcept
{
delay.tv_sec = DELAY_SECS;
delay.tv_nsec = DELAY_NANOSECS;
}
static void clear_screen() noexcept
{
std::fputs(ANSI_CLEAR, stdout);
std::fflush(stdout);
}
static void cond_print_error_header(bool& error_header_printed, const std::string* const node_name) noexcept
{
if (!error_header_printed)
{
std::fprintf(stdout, "** DrbdMon v%s\n", DrbdMon::VERSION.c_str());
if (node_name != nullptr)
{
std::fprintf(stdout, " Node %s\n", node_name->c_str());
}
error_header_printed = true;
}
}
// @throws std::bad_alloc
static bool adjust_ids(MessageLog* log, bool& ids_safe)
{
try
{
// Get the user ids of the current process
uid_t real_user {0};
uid_t eff_user {0};
uid_t saved_user {0};
if (getresuid(&real_user, &eff_user, &saved_user) != 0)
{
throw SysException();
}
// Get the group ids of the current process
gid_t real_group {0};
gid_t eff_group {0};
gid_t saved_group {0};
if (getresgid(&real_group, &eff_group, &saved_group) != 0)
{
throw SysException();
}
// Unless already equal, set effective user id = real user id, or
// if the real user id is root, then this program is probably setuid
// to some non-root account and being executed by root in an attempt
// to drop root privileges, therefore set the real user id to the
// effective user id in this case.
if (eff_user != real_user)
{
if (real_user == 0)
{
real_user = eff_user;
}
else
{
eff_user = real_user;
}
if (setresuid(real_user, eff_user, -1) != 0)
{
throw SysException();
}
}
// Set saved user id = real user id unless equal already
if (saved_user != real_user)
{
// Set saved user id = real user id
saved_user = real_user;
if (setresuid(-1, -1, saved_user) != 0)
{
throw SysException();
}
}
// Analogous to adjusting the user ids, adjust the group ids to
// non-root if real group id and effective group id do not match
if (eff_group != real_group)
{
if (real_group == 0)
{
real_group = eff_group;
}
else
{
eff_group = real_group;
}
if (setresgid(real_group, eff_group, -1) != 0)
{
throw SysException();
}
}
// Set saved group id = real group id unless equal already
if (saved_group != real_group)
{
// Set saved group id = real group id
saved_group = real_group;
if (setresgid(-1, -1, saved_group) != 0)
{
throw SysException();
}
}
// Cross-check adjusted set user & group ids
if (getresuid(&real_user, &eff_user, &saved_user) != 0 ||
getresgid(&real_group, &eff_group, &saved_group) != 0)
{
throw SysException();
}
if (real_user == eff_user && real_user == saved_user &&
real_group == eff_group && real_group == saved_group)
{
ids_safe = true;
}
else
{
throw SysException();
}
}
catch (SysException&)
{
log->add_entry(
MessageLog::log_level::ALERT,
"Adjusting the process' user/group ids failed"
);
}
return ids_safe;
}
// @throws std::bad_alloc
static void query_node_name(std::unique_ptr<std::string>& node_name)
{
std::unique_ptr<struct utsname> uname_buffer;
uname_buffer = std::unique_ptr<struct utsname>(new struct utsname);
if (uname(uname_buffer.get()) == 0)
{
node_name = std::unique_ptr<std::string>(new std::string(uname_buffer->nodename));
}
}