/*
* Soft: Keepalived is a failover program for the LVS project
* <www.linuxvirtualserver.org>. It monitor & manipulate
* a loadbalanced server pool using multi-layer checks.
*
* Part: Main program structure.
*
* Author: Alexandre Cassen, <acassen@linux-vs.org>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
*/
#include "config.h"
#include <stdlib.h>
#include <sys/utsname.h>
#include <sys/resource.h>
#include <stdbool.h>
#ifdef HAVE_SIGNALFD
#include <sys/signalfd.h>
#endif
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
#include <linux/version.h>
#include <ctype.h>
#include "main.h"
#include "global_data.h"
#include "daemon.h"
#include "config.h"
#include "git-commit.h"
#include "utils.h"
#include "signals.h"
#include "pidfile.h"
#include "bitops.h"
#include "logger.h"
#include "parser.h"
#include "notify.h"
#include "utils.h"
#ifdef _WITH_LVS_
#include "check_parser.h"
#include "check_daemon.h"
#endif
#ifdef _WITH_VRRP_
#include "vrrp_daemon.h"
#include "vrrp_parser.h"
#include "vrrp_if.h"
#ifdef _WITH_JSON_
#include "vrrp_json.h"
#endif
#endif
#ifdef _WITH_BFD_
#include "bfd_daemon.h"
#include "bfd_parser.h"
#endif
#include "global_parser.h"
#if HAVE_DECL_CLONE_NEWNET
#include "namespaces.h"
#endif
#include "scheduler.h"
#include "keepalived_netlink.h"
#include "git-commit.h"
#if defined THREAD_DUMP || defined _EPOLL_DEBUG_ || defined _EPOLL_THREAD_DUMP_
#include "scheduler.h"
#endif
#include "process.h"
#ifdef _TIMER_CHECK_
#include "timer.h"
#endif
#ifdef _SMTP_ALERT_DEBUG_
#include "smtp.h"
#endif
#if defined _REGEX_DEBUG_ || defined _WITH_REGEX_TIMERS_
#include "check_http.h"
#endif
#ifdef _TSM_DEBUG_
#include "vrrp_scheduler.h"
#endif
/* musl libc doesn't define the following */
#ifndef W_EXITCODE
#define W_EXITCODE(ret, sig) ((ret) << 8 | (sig))
#endif
#ifndef WCOREFLAG
#define WCOREFLAG ((int32_t)WCOREDUMP(0xffffffff))
#endif
#define VERSION_STRING PACKAGE_NAME " v" PACKAGE_VERSION " (" GIT_DATE ")"
#define COPYRIGHT_STRING "Copyright(C) 2001-" GIT_YEAR " Alexandre Cassen, <acassen@gmail.com>"
#define CHILD_WAIT_SECS 5
/* global var */
const char *version_string = VERSION_STRING; /* keepalived version */
char *conf_file = KEEPALIVED_CONFIG_FILE; /* Configuration file */
int log_facility = LOG_DAEMON; /* Optional logging facilities */
bool reload; /* Set during a reload */
char *main_pidfile; /* overrule default pidfile */
static bool free_main_pidfile;
#ifdef _WITH_LVS_
pid_t checkers_child; /* Healthcheckers child process ID */
char *checkers_pidfile; /* overrule default pidfile */
static bool free_checkers_pidfile;
#endif
#ifdef _WITH_VRRP_
pid_t vrrp_child; /* VRRP child process ID */
char *vrrp_pidfile; /* overrule default pidfile */
static bool free_vrrp_pidfile;
#endif
#ifdef _WITH_BFD_
pid_t bfd_child; /* BFD child process ID */
char *bfd_pidfile; /* overrule default pidfile */
static bool free_bfd_pidfile;
#endif
unsigned long daemon_mode; /* VRRP/CHECK/BFD subsystem selection */
#ifdef _WITH_SNMP_
bool snmp; /* Enable SNMP support */
const char *snmp_socket; /* Socket to use for SNMP agent */
#endif
static char *syslog_ident; /* syslog ident if not default */
bool use_pid_dir; /* Put pid files in /var/run/keepalived or @localstatedir@/run/keepalived */
unsigned os_major; /* Kernel version */
unsigned os_minor;
unsigned os_release;
char *hostname; /* Initial part of hostname */
#if HAVE_DECL_CLONE_NEWNET
static char *override_namespace; /* If namespace specified on command line */
#endif
unsigned child_wait_time = CHILD_WAIT_SECS; /* Time to wait for children to exit */
/* Log facility table */
static struct {
int facility;
} LOG_FACILITY[] = {
{LOG_LOCAL0}, {LOG_LOCAL1}, {LOG_LOCAL2}, {LOG_LOCAL3},
{LOG_LOCAL4}, {LOG_LOCAL5}, {LOG_LOCAL6}, {LOG_LOCAL7}
};
#define LOG_FACILITY_MAX ((sizeof(LOG_FACILITY) / sizeof(LOG_FACILITY[0])) - 1)
/* umask settings */
bool umask_cmdline;
static mode_t umask_val = S_IXUSR | S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH;
/* Control producing core dumps */
static bool set_core_dump_pattern = false;
static bool create_core_dump = false;
static const char *core_dump_pattern = "core";
static char *orig_core_dump_pattern = NULL;
/* debug flags */
#if defined _TIMER_CHECK_ || defined _SMTP_ALERT_DEBUG_ || defined _EPOLL_DEBUG_ || defined _EPOLL_THREAD_DUMP_ || defined _REGEX_DEBUG_ || defined _WITH_REGEX_TIMERS_ || defined _TSM_DEBUG_ || defined _VRRP_FD_DEBUG_ || defined _NETLINK_TIMERS_
#define WITH_DEBUG_OPTIONS 1
#endif
#ifdef _TIMER_CHECK_
static char timer_debug;
#endif
#ifdef _SMTP_ALERT_DEBUG_
static char smtp_debug;
#endif
#ifdef _EPOLL_DEBUG_
static char epoll_debug;
#endif
#ifdef _EPOLL_THREAD_DUMP_
static char epoll_thread_debug;
#endif
#ifdef _REGEX_DEBUG_
static char regex_debug;
#endif
#ifdef _WITH_REGEX_TIMERS_
static char regex_timers;
#endif
#ifdef _TSM_DEBUG_
static char tsm_debug;
#endif
#ifdef _VRRP_FD_DEBUG_
static char vrrp_fd_debug;
#endif
#ifdef _NETLINK_TIMERS_
static char netlink_timer_debug;
#endif
void
free_parent_mallocs_startup(bool am_child)
{
if (am_child) {
#if HAVE_DECL_CLONE_NEWNET
free_dirname();
#endif
#ifndef _MEM_CHECK_LOG_
FREE_PTR(syslog_ident);
#else
free(syslog_ident);
#endif
syslog_ident = NULL;
FREE_PTR(orig_core_dump_pattern);
}
if (free_main_pidfile) {
FREE_PTR(main_pidfile);
free_main_pidfile = false;
}
}
void
free_parent_mallocs_exit(void)
{
#ifdef _WITH_VRRP_
if (free_vrrp_pidfile)
FREE_PTR(vrrp_pidfile);
#endif
#ifdef _WITH_LVS_
if (free_checkers_pidfile)
FREE_PTR(checkers_pidfile);
#endif
#ifdef _WITH_BFD_
if (free_bfd_pidfile)
FREE_PTR(bfd_pidfile);
#endif
FREE_PTR(config_id);
}
char *
make_syslog_ident(const char* name)
{
size_t ident_len = strlen(name) + 1;
char *ident;
#if HAVE_DECL_CLONE_NEWNET
if (global_data->network_namespace)
ident_len += strlen(global_data->network_namespace) + 1;
#endif
if (global_data->instance_name)
ident_len += strlen(global_data->instance_name) + 1;
/* If we are writing MALLOC/FREE info to the log, we have
* trouble FREEing the syslog_ident */
#ifndef _MEM_CHECK_LOG_
ident = MALLOC(ident_len);
#else
ident = malloc(ident_len);
#endif
if (!ident)
return NULL;
strcpy(ident, name);
#if HAVE_DECL_CLONE_NEWNET
if (global_data->network_namespace) {
strcat(ident, "_");
strcat(ident, global_data->network_namespace);
}
#endif
if (global_data->instance_name) {
strcat(ident, "_");
strcat(ident, global_data->instance_name);
}
return ident;
}
static char *
make_pidfile_name(const char* start, const char* instance, const char* extn)
{
size_t len;
char *name;
len = strlen(start) + 1;
if (instance)
len += strlen(instance) + 1;
if (extn)
len += strlen(extn);
name = MALLOC(len);
if (!name) {
log_message(LOG_INFO, "Unable to make pidfile name for %s", start);
return NULL;
}
strcpy(name, start);
if (instance) {
strcat(name, "_");
strcat(name, instance);
}
if (extn)
strcat(name, extn);
return name;
}
#ifdef _WITH_VRRP_
bool
running_vrrp(void)
{
return (__test_bit(DAEMON_VRRP, &daemon_mode) &&
(global_data->have_vrrp_config ||
__test_bit(RUN_ALL_CHILDREN, &daemon_mode)));
}
#endif
#ifdef _WITH_LVS_
bool
running_checker(void)
{
return (__test_bit(DAEMON_CHECKERS, &daemon_mode) &&
(global_data->have_checker_config ||
__test_bit(RUN_ALL_CHILDREN, &daemon_mode)));
}
#endif
#ifdef _WITH_BFD_
bool
running_bfd(void)
{
return (__test_bit(DAEMON_BFD, &daemon_mode) &&
(global_data->have_bfd_config ||
__test_bit(RUN_ALL_CHILDREN, &daemon_mode)));
}
#endif
static char const *
find_keepalived_child_name(pid_t pid)
{
#ifdef _WITH_LVS_
if (pid == checkers_child)
return PROG_CHECK;
#endif
#ifdef _WITH_VRRP_
if (pid == vrrp_child)
return PROG_VRRP;
#endif
#ifdef _WITH_BFD_
if (pid == bfd_child)
return PROG_BFD;
#endif
return NULL;
}
static vector_t *
global_init_keywords(void)
{
/* global definitions mapping */
init_global_keywords(true);
#ifdef _WITH_VRRP_
init_vrrp_keywords(false);
#endif
#ifdef _WITH_LVS_
init_check_keywords(false);
#endif
#ifdef _WITH_BFD_
init_bfd_keywords(false);
#endif
return keywords;
}
static void
read_config_file(void)
{
init_data(conf_file, global_init_keywords);
}
/* Daemon stop sequence */
void
stop_keepalived(void)
{
#ifndef _DEBUG_
/* Just cleanup memory & exit */
thread_destroy_master(master);
#ifdef _WITH_VRRP_
if (__test_bit(DAEMON_VRRP, &daemon_mode))
pidfile_rm(vrrp_pidfile);
#endif
#ifdef _WITH_LVS_
if (__test_bit(DAEMON_CHECKERS, &daemon_mode))
pidfile_rm(checkers_pidfile);
#endif
#ifdef _WITH_BFD_
if (__test_bit(DAEMON_BFD, &daemon_mode))
pidfile_rm(bfd_pidfile);
#endif
pidfile_rm(main_pidfile);
#endif
}
/* Daemon init sequence */
static int
start_keepalived(void)
{
bool have_child = false;
#ifdef _WITH_BFD_
/* must be opened before vrrp and bfd start */
open_bfd_pipes();
#endif
#ifdef _WITH_LVS_
/* start healthchecker child */
if (running_checker()) {
start_check_child();
have_child = true;
}
#endif
#ifdef _WITH_VRRP_
/* start vrrp child */
if (running_vrrp()) {
start_vrrp_child();
have_child = true;
}
#endif
#ifdef _WITH_BFD_
/* start bfd child */
if (running_bfd()) {
start_bfd_child();
have_child = true;
}
#endif
return have_child;
}
static void
validate_config(void)
{
#ifdef _WITH_VRRP_
kernel_netlink_read_interfaces();
#endif
#ifdef _WITH_LVS_
/* validate healthchecker config */
#ifndef _DEBUG_
prog_type = PROG_TYPE_CHECKER;
#endif
check_validate_config();
#endif
#ifdef _WITH_VRRP_
/* validate vrrp config */
#ifndef _DEBUG_
prog_type = PROG_TYPE_VRRP;
#endif
vrrp_validate_config();
#endif
#ifdef _WITH_BFD_
/* validate bfd config */
#ifndef _DEBUG_
prog_type = PROG_TYPE_BFD;
#endif
bfd_validate_config();
#endif
}
static void
config_test_exit(void)
{
config_err_t config_err = get_config_status();
switch (config_err) {
case CONFIG_OK:
exit(KEEPALIVED_EXIT_OK);
case CONFIG_FILE_NOT_FOUND:
case CONFIG_BAD_IF:
case CONFIG_FATAL:
exit(KEEPALIVED_EXIT_CONFIG);
case CONFIG_SECURITY_ERROR:
exit(KEEPALIVED_EXIT_CONFIG_TEST_SECURITY);
default:
exit(KEEPALIVED_EXIT_CONFIG_TEST);
}
}
#ifndef _DEBUG_
static bool reload_config(void)
{
bool unsupported_change = false;
log_message(LOG_INFO, "Reloading ...");
/* Make sure there isn't an attempt to change the network namespace or instance name */
old_global_data = global_data;
global_data = NULL;
global_data = alloc_global_data();
read_config_file();
init_global_data(global_data, old_global_data);
#if HAVE_DECL_CLONE_NEWNET
if (!!old_global_data->network_namespace != !!global_data->network_namespace ||
(global_data->network_namespace && strcmp(old_global_data->network_namespace, global_data->network_namespace))) {
log_message(LOG_INFO, "Cannot change network namespace at a reload - please restart %s", PACKAGE);
unsupported_change = true;
}
FREE_PTR(global_data->network_namespace);
global_data->network_namespace = old_global_data->network_namespace;
old_global_data->network_namespace = NULL;
#endif
if (!!old_global_data->instance_name != !!global_data->instance_name ||
(global_data->instance_name && strcmp(old_global_data->instance_name, global_data->instance_name))) {
log_message(LOG_INFO, "Cannot change instance name at a reload - please restart %s", PACKAGE);
unsupported_change = true;
}
FREE_PTR(global_data->instance_name);
global_data->instance_name = old_global_data->instance_name;
old_global_data->instance_name = NULL;
if (unsupported_change) {
/* We cannot reload the configuration, so continue with the old config */
free_global_data (global_data);
global_data = old_global_data;
}
else
free_global_data (old_global_data);
return !unsupported_change;
}
/* SIGHUP/USR1/USR2 handler */
static void
propagate_signal(__attribute__((unused)) void *v, int sig)
{
if (sig == SIGHUP) {
if (!reload_config())
return;
}
/* Signal child processes */
#ifdef _WITH_VRRP_
if (vrrp_child > 0)
kill(vrrp_child, sig);
else if (sig == SIGHUP && running_vrrp())
start_vrrp_child();
#endif
#ifdef _WITH_LVS_
if (sig == SIGHUP) {
if (checkers_child > 0)
kill(checkers_child, sig);
else if (running_checker())
start_check_child();
}
#endif
#ifdef _WITH_BFD_
if (sig == SIGHUP) {
if (bfd_child > 0)
kill(bfd_child, sig);
else if (running_bfd())
start_bfd_child();
}
#endif
}
/* Terminate handler */
static void
sigend(__attribute__((unused)) void *v, __attribute__((unused)) int sig)
{
int status;
int ret;
int wait_count = 0;
struct timeval start_time, now;
#ifdef HAVE_SIGNALFD
struct timeval timeout = {
.tv_sec = child_wait_time,
.tv_usec = 0
};
int signal_fd = master->signal_fd;
fd_set read_set;
struct signalfd_siginfo siginfo;
sigset_t sigmask;
#else
sigset_t old_set, child_wait;
struct timespec timeout = {
.tv_sec = child_wait_time,
.tv_nsec = 0
};
#endif
/* register the terminate thread */
thread_add_terminate_event(master);
log_message(LOG_INFO, "Stopping");
#ifdef HAVE_SIGNALFD
/* We only want to receive SIGCHLD now */
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGCHLD);
signalfd(signal_fd, &sigmask, 0);
FD_ZERO(&read_set);
#else
sigmask_func(0, NULL, &old_set);
if (!sigismember(&old_set, SIGCHLD)) {
sigemptyset(&child_wait);
sigaddset(&child_wait, SIGCHLD);
sigmask_func(SIG_BLOCK, &child_wait, NULL);
}
#endif
#ifdef _WITH_VRRP_
if (vrrp_child > 0) {
if (kill(vrrp_child, SIGTERM)) {
/* ESRCH means no such process */
if (errno == ESRCH)
vrrp_child = 0;
}
else
wait_count++;
}
#endif
#ifdef _WITH_LVS_
if (checkers_child > 0) {
if (kill(checkers_child, SIGTERM)) {
if (errno == ESRCH)
checkers_child = 0;
}
else
wait_count++;
}
#endif
#ifdef _WITH_BFD_
if (bfd_child > 0) {
if (kill(bfd_child, SIGTERM)) {
if (errno == ESRCH)
bfd_child = 0;
}
else
wait_count++;
}
#endif
gettimeofday(&start_time, NULL);
while (wait_count) {
#ifdef HAVE_SIGNALFD
FD_SET(signal_fd, &read_set);
ret = select(signal_fd + 1, &read_set, NULL, NULL, &timeout);
if (ret == 0)
break;
if (ret == -1) {
if (errno == EINTR)
continue;
log_message(LOG_INFO, "Terminating select returned errno %d", errno);
break;
}
if (!FD_ISSET(signal_fd, &read_set)) {
log_message(LOG_INFO, "Terminating select did not return select_fd");
continue;
}
if (read(signal_fd, &siginfo, sizeof(siginfo)) != sizeof(siginfo)) {
log_message(LOG_INFO, "Terminating signal read did not read entire siginfo");
break;
}
status = siginfo.ssi_code == CLD_EXITED ? W_EXITCODE(siginfo.ssi_status, 0) :
siginfo.ssi_code == CLD_KILLED ? W_EXITCODE(0, siginfo.ssi_status) :
WCOREFLAG;
#ifdef _WITH_VRRP_
if (vrrp_child > 0 && vrrp_child == (pid_t)siginfo.ssi_pid) {
report_child_status(status, vrrp_child, PROG_VRRP);
vrrp_child = 0;
wait_count--;
}
#endif
#ifdef _WITH_LVS_
if (checkers_child > 0 && checkers_child == (pid_t)siginfo.ssi_pid) {
report_child_status(status, checkers_child, PROG_CHECK);
checkers_child = 0;
wait_count--;
}
#endif
#ifdef _WITH_BFD_
if (bfd_child > 0 && bfd_child == (pid_t)siginfo.ssi_pid) {
report_child_status(status, bfd_child, PROG_BFD);
bfd_child = 0;
wait_count--;
}
#endif
#else
ret = sigtimedwait(&child_wait, NULL, &timeout);
if (ret == -1) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
break;
}
#ifdef _WITH_VRRP_
if (vrrp_child > 0 && vrrp_child == waitpid(vrrp_child, &status, WNOHANG)) {
report_child_status(status, vrrp_child, PROG_VRRP);
vrrp_child = 0;
wait_count--;
}
#endif
#ifdef _WITH_LVS_
if (checkers_child > 0 && checkers_child == waitpid(checkers_child, &status, WNOHANG)) {
report_child_status(status, checkers_child, PROG_CHECK);
checkers_child = 0;
wait_count--;
}
#endif
#ifdef _WITH_BFD_
if (bfd_child > 0 && bfd_child == waitpid(bfd_child, &status, WNOHANG)) {
report_child_status(status, bfd_child, PROG_BFD);
bfd_child = 0;
wait_count--;
}
#endif
#endif
if (wait_count) {
gettimeofday(&now, NULL);
timeout.tv_sec = child_wait_time - (now.tv_sec - start_time.tv_sec);
#ifdef HAVE_SIGNALFD
timeout.tv_usec = (start_time.tv_usec - now.tv_usec);
if (timeout.tv_usec < 0) {
timeout.tv_usec += 1000000L;
timeout.tv_sec--;
}
#else
timeout.tv_nsec = (start_time.tv_usec - now.tv_usec) * 1000;
if (timeout.tv_nsec < 0) {
timeout.tv_nsec += 1000000000L;
timeout.tv_sec--;
}
#endif
if (timeout.tv_sec < 0)
break;
}
}
/* A child may not have terminated, so force its termination */
#ifdef _WITH_VRRP_
if (vrrp_child) {
log_message(LOG_INFO, "vrrp process failed to die - forcing termination");
kill(vrrp_child, SIGKILL);
}
#endif
#ifdef _WITH_LVS_
if (checkers_child) {
log_message(LOG_INFO, "checker process failed to die - forcing termination");
kill(checkers_child, SIGKILL);
}
#endif
#ifdef _WITH_BFD_
if (bfd_child) {
log_message(LOG_INFO, "bfd process failed to die - forcing termination");
kill(bfd_child, SIGKILL);
}
#endif
#ifndef HAVE_SIGNALFD
if (!sigismember(&old_set, SIGCHLD))
sigmask_func(SIG_UNBLOCK, &child_wait, NULL);
#endif
}
#endif
/* Initialize signal handler */
static void
signal_init(void)
{
#ifndef _DEBUG_
signal_set(SIGHUP, propagate_signal, NULL);
signal_set(SIGUSR1, propagate_signal, NULL);
signal_set(SIGUSR2, propagate_signal, NULL);
#ifdef _WITH_JSON_
signal_set(SIGJSON, propagate_signal, NULL);
#endif
signal_set(SIGINT, sigend, NULL);
signal_set(SIGTERM, sigend, NULL);
#endif
signal_ignore(SIGPIPE);
}
/* To create a core file when abrt is running (a RedHat distribution),
* and keepalived isn't installed from an RPM package, edit the file
* “/etc/abrt/abrt.conf”, and change the value of the field
* “ProcessUnpackaged” to “yes”.
*
* Alternatively, use the -M command line option. */
static void
update_core_dump_pattern(const char *pattern_str)
{
int fd;
bool initialising = (orig_core_dump_pattern == NULL);
/* CORENAME_MAX_SIZE in kernel source include/linux/binfmts.h defines
* the maximum string length, * see core_pattern[CORENAME_MAX_SIZE] in
* fs/coredump.c. Currently (Linux 4.10) defines it to be 128, but the
* definition is not exposed to user-space. */
#define CORENAME_MAX_SIZE 128
if (initialising)
orig_core_dump_pattern = MALLOC(CORENAME_MAX_SIZE);
fd = open ("/proc/sys/kernel/core_pattern", O_RDWR);
if (fd == -1 ||
(initialising && read(fd, orig_core_dump_pattern, CORENAME_MAX_SIZE - 1) == -1) ||
write(fd, pattern_str, strlen(pattern_str)) == -1) {
log_message(LOG_INFO, "Unable to read/write core_pattern");
if (fd != -1)
close(fd);
FREE(orig_core_dump_pattern);
return;
}
close(fd);
if (!initialising)
FREE_PTR(orig_core_dump_pattern);
}
static void
core_dump_init(void)
{
struct rlimit orig_rlim, rlim;
if (set_core_dump_pattern) {
/* If we set the core_pattern here, we will attempt to restore it when we
* exit. This will be fine if it is a child of ours that core dumps,
* but if we ourself core dump, then the core_pattern will not be restored */
update_core_dump_pattern(core_dump_pattern);
}
if (create_core_dump) {
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
if (getrlimit(RLIMIT_CORE, &orig_rlim) == -1)
log_message(LOG_INFO, "Failed to get core file size");
else if (setrlimit(RLIMIT_CORE, &rlim) == -1)
log_message(LOG_INFO, "Failed to set core file size");
else
set_child_rlimit(RLIMIT_CORE, &orig_rlim);
}
}
static mode_t
set_umask(const char *optarg)
{
long umask_long;
mode_t umask_val;
char *endptr;
umask_long = strtoll(optarg, &endptr, 0);
if (*endptr || umask_long < 0 || umask_long & ~0777L) {
fprintf(stderr, "Invalid --umask option %s", optarg);
return 0;
}
umask_val = umask_long & 0777;
umask(umask_val);
umask_cmdline = true;
return umask_val;
}
void
initialise_debug_options(void)
{
#if defined WITH_DEBUG_OPTIONS && !defined _DEBUG_
char mask = 0;
if (prog_type == PROG_TYPE_PARENT)
mask = 1 << PROG_TYPE_PARENT;
#if _WITH_BFD_
else if (prog_type == PROG_TYPE_BFD)
mask = 1 << PROG_TYPE_BFD;
#endif
#if _WITH_LVS_
else if (prog_type == PROG_TYPE_CHECKER)
mask = 1 << PROG_TYPE_CHECKER;
#endif
#if _WITH_VRRP_
else if (prog_type == PROG_TYPE_VRRP)
mask = 1 << PROG_TYPE_VRRP;
#endif
#ifdef _TIMER_CHECK_
do_timer_check = !!(timer_debug & mask);
#endif
#ifdef _SMTP_ALERT_DEBUG_
do_smtp_alert_debug = !!(smtp_debug & mask);
#endif
#ifdef _EPOLL_DEBUG_
do_epoll_debug = !!(epoll_debug & mask);
#endif
#ifdef _EPOLL_THREAD_DUMP_
do_epoll_thread_dump = !!(epoll_thread_debug & mask);
#endif
#ifdef _REGEX_DEBUG_
do_regex_debug = !!(regex_debug & mask);
#endif
#ifdef _WITH_REGEX_TIMERS_
do_regex_timers = !!(regex_timers & mask);
#endif
#ifdef _TSM_DEBUG_
do_tsm_debug = !!(tsm_debug & mask);
#endif
#ifdef _VRRP_FD_DEBUG_
do_vrrp_fd_debug = !!(vrrp_fd_debug & mask);
#endif
#ifdef _NETLINK_TIMERS_
do_netlink_timers = !!(netlink_timer_debug & mask);
#endif
#endif
}
#ifdef WITH_DEBUG_OPTIONS
static void
set_debug_options(const char *options)
{
char all_processes, processes;
char opt;
const char *opt_p = options;
#ifdef _DEBUG_
all_processes = 1;
#else
all_processes = (1 << PROG_TYPE_PARENT);
#if _WITH_BFD_
all_processes |= (1 << PROG_TYPE_BFD);
#endif
#if _WITH_LVS_
all_processes |= (1 << PROG_TYPE_CHECKER);
#endif
#if _WITH_VRRP_
all_processes |= (1 << PROG_TYPE_VRRP);
#endif
#endif
if (!options) {
#ifdef _TIMER_CHECK_
timer_debug = all_processes;
#endif
#ifdef _SMTP_ALERT_DEBUG_
smtp_debug = all_processes;
#endif
#ifdef _EPOLL_DEBUG_
epoll_debug = all_processes;
#endif
#ifdef _EPOLL_THREAD_DUMP_
epoll_thread_debug = all_processes;
#endif
#ifdef _REGEX_DEBUG_
regex_debug = all_processes;
#endif
#ifdef _WITH_REGEX_TIMERS_
regex_timers = all_processes;
#endif
#ifdef _TSM_DEBUG_
tsm_debug = all_processes;
#endif
#ifdef _VRRP_FD_DEBUG_
vrrp_fd_debug = all_processes;
#endif
#ifdef _NETLINK_TIMERS_
netlink_timer_debug = all_processes;
#endif
return;
}
opt_p = options;
do {
if (!isupper(*opt_p)) {
fprintf(stderr, "Unknown debug option'%c' in '%s'\n", *opt_p, options);
return;
}
opt = *opt_p++;
#ifdef _DEBUG_
processes = all_processes;
#else
if (!*opt_p || isupper(*opt_p))
processes = all_processes;
else {
processes = 0;
while (*opt_p && !isupper(*opt_p)) {
switch (*opt_p) {
case 'p':
processes |= (1 << PROG_TYPE_PARENT);
break;
#if _WITH_BFD_
case 'b':
processes |= (1 << PROG_TYPE_BFD);
break;
#endif
#if _WITH_LVS_
case 'c':
processes |= (1 << PROG_TYPE_CHECKER);
break;
#endif
#if _WITH_VRRP_
case 'v':
processes |= (1 << PROG_TYPE_VRRP);
break;
#endif
default:
fprintf(stderr, "Unknown debug process '%c' in '%s'\n", *opt_p, options);
return;
}
opt_p++;
}
}
#endif
switch (opt) {
#ifdef _TIMER_CHECK_
case 'T':
timer_debug = processes;
break;
#endif
#ifdef _SMTP_ALERT_DEBUG_
case 'M':
smtp_debug = processes;
break;
#endif
#ifdef _EPOLL_DEBUG_
case 'E':
epoll_debug = processes;
break;
#endif
#ifdef _EPOLL_THREAD_DUMP_
case 'D':
epoll_thread_debug = processes;
break;
#endif
#ifdef _REGEX_DEBUG_
case 'R':
regex_debug = processes;
break;
#endif
#ifdef _WITH_REGEX_TIMERS_
case 'X':
regex_timers = processes;
break;
#endif
#ifdef _TSM_DEBUG_
case 'S':
tsm_debug = processes;
break;
#endif
#ifdef _VRRP_FD_DEBUG_
case 'F':
vrrp_fd_debug = processes;
break;
#endif
#ifdef _NETLINK_TIMERS_
case 'N':
netlink_timer_debug = processes;
break;
#endif
default:
fprintf(stderr, "Unknown debug type '%c' in '%s'\n", opt, options);
return;
}
} while (opt_p && *opt_p);
}
#endif
/* Usage function */
static void
usage(const char *prog)
{
fprintf(stderr, "Usage: %s [OPTION...]\n", prog);
fprintf(stderr, " -f, --use-file=FILE Use the specified configuration file\n");
#if defined _WITH_VRRP_ && defined _WITH_LVS_
fprintf(stderr, " -P, --vrrp Only run with VRRP subsystem\n");
fprintf(stderr, " -C, --check Only run with Health-checker subsystem\n");
#endif
#ifdef _WITH_BFD_
fprintf(stderr, " -B, --no_bfd Don't run BFD subsystem\n");
#endif
fprintf(stderr, " --all Force all child processes to run, even if have no configuration\n");
fprintf(stderr, " -l, --log-console Log messages to local console\n");
fprintf(stderr, " -D, --log-detail Detailed log messages\n");
fprintf(stderr, " -S, --log-facility=[0-7] Set syslog facility to LOG_LOCAL[0-7]\n");
#ifdef ENABLE_LOG_TO_FILE
fprintf(stderr, " -g, --log-file=FILE Also log to FILE (default /tmp/keepalived.log)\n");
fprintf(stderr, " --flush-log-file Flush log file on write\n");
#endif
fprintf(stderr, " -G, --no-syslog Don't log via syslog\n");
fprintf(stderr, " -u, --umask=MASK umask for file creation (in numeric form)\n");
#ifdef _WITH_VRRP_
fprintf(stderr, " -X, --release-vips Drop VIP on transition from signal.\n");
fprintf(stderr, " -V, --dont-release-vrrp Don't remove VRRP VIPs and VROUTEs on daemon stop\n");
#endif
#ifdef _WITH_LVS_
fprintf(stderr, " -I, --dont-release-ipvs Don't remove IPVS topology on daemon stop\n");
#endif
fprintf(stderr, " -R, --dont-respawn Don't respawn child processes\n");
fprintf(stderr, " -n, --dont-fork Don't fork the daemon process\n");
fprintf(stderr, " -d, --dump-conf Dump the configuration data\n");
fprintf(stderr, " -p, --pid=FILE Use specified pidfile for parent process\n");
#ifdef _WITH_VRRP_
fprintf(stderr, " -r, --vrrp_pid=FILE Use specified pidfile for VRRP child process\n");
#endif
#ifdef _WITH_LVS_
fprintf(stderr, " -c, --checkers_pid=FILE Use specified pidfile for checkers child process\n");
fprintf(stderr, " -a, --address-monitoring Report all address additions/deletions notified via netlink\n");
#endif
#ifdef _WITH_BFD_
fprintf(stderr, " -b, --bfd_pid=FILE Use specified pidfile for BFD child process\n");
#endif
#ifdef _WITH_SNMP_
fprintf(stderr, " -x, --snmp Enable SNMP subsystem\n");
fprintf(stderr, " -A, --snmp-agent-socket=FILE Use the specified socket for master agent\n");
#endif
#if HAVE_DECL_CLONE_NEWNET
fprintf(stderr, " -s, --namespace=NAME Run in network namespace NAME (overrides config)\n");
#endif
fprintf(stderr, " -m, --core-dump Produce core dump if terminate abnormally\n");
fprintf(stderr, " -M, --core-dump-pattern=PATN Also set /proc/sys/kernel/core_pattern to PATN (default 'core')\n");
#ifdef _MEM_CHECK_LOG_
fprintf(stderr, " -L, --mem-check-log Log malloc/frees to syslog\n");
#endif
fprintf(stderr, " -i, --config-id id Skip any configuration lines beginning '@' that don't match id\n"
" or any lines beginning @^ that do match.\n"
" The config-id defaults to the node name if option not used\n");
fprintf(stderr, " --signum=SIGFUNC Return signal number for STOP, RELOAD, DATA, STATS"
#ifdef _WITH_JSON_
", JSON"
#endif
"\n");
fprintf(stderr, " -t, --config-test[=LOG_FILE] Check the configuration for obvious errors, output to\n"
" stderr by default\n");
#ifdef _WITH_PERF_
fprintf(stderr, " --perf[=PERF_TYPE] Collect perf data, PERF_TYPE=all, run(default) or end\n");
#endif
#ifdef WITH_DEBUG_OPTIONS
fprintf(stderr, " --debug[=...] Enable debug options. p, b, c, v specify parent, bfd, checker and vrrp processes\n");
#ifdef _TIMER_CHECK_
fprintf(stderr, " T - timer debug\n");
#endif
#ifdef _SMTP_ALERT_DEBUG_
fprintf(stderr, " M - email alert debug\n");
#endif
#ifdef _EPOLL_DEBUG_
fprintf(stderr, " E - epoll debug\n");
#endif
#ifdef _EPOLL_THREAD_DUMP_
fprintf(stderr, " D - epoll thread dump debug\n");
#endif
#ifdef _VRRP_FD_DEBUG
fprintf(stderr, " F - vrrp fd dump debug\n");
#endif
#ifdef _REGEX_DEBUG_
fprintf(stderr, " R - regex debug\n");
#endif
#ifdef _WITH_REGEX_TIMERS_
fprintf(stderr, " X - regex timers\n");
#endif
#ifdef _TSM_DEBUG_
fprintf(stderr, " S - TSM debug\n");
#endif
#ifdef _NETLINK_TIMERS_
fprintf(stderr, " N - netlink timer debug\n");
#endif
fprintf(stderr, " Example --debug=TpMEvcp\n");
#endif
fprintf(stderr, " -v, --version Display the version number\n");
fprintf(stderr, " -h, --help Display this help message\n");
}
/* Command line parser */
static bool
parse_cmdline(int argc, char **argv)
{
int c;
bool reopen_log = false;
int signum;
struct utsname uname_buf;
int longindex;
int curind;
bool bad_option = false;
unsigned facility;
mode_t new_umask_val;
struct option long_options[] = {
{"use-file", required_argument, NULL, 'f'},
#if defined _WITH_VRRP_ && defined _WITH_LVS_
{"vrrp", no_argument, NULL, 'P'},
{"check", no_argument, NULL, 'C'},
#endif
#ifdef _WITH_BFD_
{"no_bfd", no_argument, NULL, 'B'},
#endif
{"all", no_argument, NULL, 3 },
{"log-console", no_argument, NULL, 'l'},
{"log-detail", no_argument, NULL, 'D'},
{"log-facility", required_argument, NULL, 'S'},
{"log-file", optional_argument, NULL, 'g'},
#ifdef ENABLE_LOG_TO_FILE
{"flush-log-file", no_argument, NULL, 2 },
#endif
{"no-syslog", no_argument, NULL, 'G'},
{"umask", required_argument, NULL, 'u'},
#ifdef _WITH_VRRP_
{"release-vips", no_argument, NULL, 'X'},
{"dont-release-vrrp", no_argument, NULL, 'V'},
#endif
#ifdef _WITH_LVS_
{"dont-release-ipvs", no_argument, NULL, 'I'},
#endif
{"dont-respawn", no_argument, NULL, 'R'},
{"dont-fork", no_argument, NULL, 'n'},
{"dump-conf", no_argument, NULL, 'd'},
{"pid", required_argument, NULL, 'p'},
#ifdef _WITH_VRRP_
{"vrrp_pid", required_argument, NULL, 'r'},
#endif
#ifdef _WITH_LVS_
{"checkers_pid", required_argument, NULL, 'c'},
{"address-monitoring", no_argument, NULL, 'a'},
#endif
#ifdef _WITH_BFD_
{"bfd_pid", required_argument, NULL, 'b'},
#endif
#ifdef _WITH_SNMP_
{"snmp", no_argument, NULL, 'x'},
{"snmp-agent-socket", required_argument, NULL, 'A'},
#endif
{"core-dump", no_argument, NULL, 'm'},
{"core-dump-pattern", optional_argument, NULL, 'M'},
#ifdef _MEM_CHECK_LOG_
{"mem-check-log", no_argument, NULL, 'L'},
#endif
#if HAVE_DECL_CLONE_NEWNET
{"namespace", required_argument, NULL, 's'},
#endif
{"config-id", required_argument, NULL, 'i'},
{"signum", required_argument, NULL, 4 },
{"config-test", optional_argument, NULL, 't'},
#ifdef _WITH_PERF_
{"perf", optional_argument, NULL, 5 },
#endif
#ifdef WITH_DEBUG_OPTIONS
{"debug", optional_argument, NULL, 6 },
#endif
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0 }
};
/* Unfortunately, if a short option is used, getopt_long() doesn't change the value
* of longindex, so we need to ensure that before calling getopt_long(), longindex
* is set to a known invalid value */
curind = optind;
while (longindex = -1, (c = getopt_long(argc, argv, ":vhlndu:DRS:f:p:i:mM::g::Gt::"
#if defined _WITH_VRRP_ && defined _WITH_LVS_
"PC"
#endif
#ifdef _WITH_VRRP_
"r:VX"
#endif
#ifdef _WITH_LVS_
"ac:I"
#endif
#ifdef _WITH_BFD_
"Bb:"
#endif
#ifdef _WITH_SNMP_
"xA:"
#endif
#ifdef _MEM_CHECK_LOG_
"L"
#endif
#if HAVE_DECL_CLONE_NEWNET
"s:"
#endif
, long_options, &longindex)) != -1) {
/* Check for an empty option argument. For example --use-file= returns
* a 0 length option, which we don't want */
if (longindex >= 0 && long_options[longindex].has_arg == required_argument && optarg && !optarg[0]) {
c = ':';
optarg = NULL;
}
switch (c) {
case 'v':
fprintf(stderr, "%s", version_string);
#ifdef GIT_COMMIT
fprintf(stderr, ", git commit %s", GIT_COMMIT);
#endif
fprintf(stderr, "\n\n%s\n\n", COPYRIGHT_STRING);
fprintf(stderr, "Built with kernel headers for Linux %d.%d.%d\n",
(LINUX_VERSION_CODE >> 16) & 0xff,
(LINUX_VERSION_CODE >> 8) & 0xff,
(LINUX_VERSION_CODE ) & 0xff);
uname(&uname_buf);
fprintf(stderr, "Running on %s %s %s\n\n", uname_buf.sysname, uname_buf.release, uname_buf.version);
fprintf(stderr, "configure options: %s\n\n", KEEPALIVED_CONFIGURE_OPTIONS);
fprintf(stderr, "Config options: %s\n\n", CONFIGURATION_OPTIONS);
fprintf(stderr, "System options: %s\n", SYSTEM_OPTIONS);
exit(0);
break;
case 'h':
usage(argv[0]);
exit(0);
break;
case 'l':
__set_bit(LOG_CONSOLE_BIT, &debug);
reopen_log = true;
break;
case 'n':
__set_bit(DONT_FORK_BIT, &debug);
break;
case 'd':
__set_bit(DUMP_CONF_BIT, &debug);
break;
#ifdef _WITH_VRRP_
case 'V':
__set_bit(DONT_RELEASE_VRRP_BIT, &debug);
break;
#endif
#ifdef _WITH_LVS_
case 'I':
__set_bit(DONT_RELEASE_IPVS_BIT, &debug);
break;
#endif
case 'D':
if (__test_bit(LOG_DETAIL_BIT, &debug))
__set_bit(LOG_EXTRA_DETAIL_BIT, &debug);
else
__set_bit(LOG_DETAIL_BIT, &debug);
break;
case 'R':
__set_bit(DONT_RESPAWN_BIT, &debug);
break;
#ifdef _WITH_VRRP_
case 'X':
__set_bit(RELEASE_VIPS_BIT, &debug);
break;
#endif
case 'S':
if (!read_unsigned(optarg, &facility, 0, LOG_FACILITY_MAX, false))
fprintf(stderr, "Invalid log facility '%s'\n", optarg);
else {
log_facility = LOG_FACILITY[facility].facility;
reopen_log = true;
}
break;
case 'g':
#ifdef ENABLE_LOG_TO_FILE
if (optarg && optarg[0])
log_file_name = optarg;
else
log_file_name = "/tmp/keepalived.log";
open_log_file(log_file_name, NULL, NULL, NULL);
#else
fprintf(stderr, "-g requires configure option --enable-log-file\n");
bad_option = true;
#endif
break;
#ifdef ENABLE_LOG_TO_FILE
case 2: /* --flush-log-file */
set_flush_log_file();
break;
#endif
case 'G':
__set_bit(NO_SYSLOG_BIT, &debug);
reopen_log = true;
break;
case 'u':
new_umask_val = set_umask(optarg);
if (umask_cmdline)
umask_val = new_umask_val;
break;
case 't':
__set_bit(CONFIG_TEST_BIT, &debug);
__set_bit(DONT_RESPAWN_BIT, &debug);
__set_bit(DONT_FORK_BIT, &debug);
__set_bit(NO_SYSLOG_BIT, &debug);
if (optarg && optarg[0]) {
int fd = open(optarg, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd == -1) {
fprintf(stderr, "Unable to open config-test log file %s\n", optarg);
exit(EXIT_FAILURE);
}
dup2(fd, STDERR_FILENO);
close(fd);
}
break;
case 'f':
conf_file = optarg;
break;
#if defined _WITH_VRRP_ && defined _WITH_LVS_
case 'P':
__clear_bit(DAEMON_CHECKERS, &daemon_mode);
break;
case 'C':
__clear_bit(DAEMON_VRRP, &daemon_mode);
break;
#endif
#ifdef _WITH_BFD_
case 'B':
__clear_bit(DAEMON_BFD, &daemon_mode);
break;
#endif
case 'p':
main_pidfile = optarg;
break;
#ifdef _WITH_LVS_
case 'c':
checkers_pidfile = optarg;
break;
case 'a':
__set_bit(LOG_ADDRESS_CHANGES, &debug);
break;
#endif
#ifdef _WITH_VRRP_
case 'r':
vrrp_pidfile = optarg;
break;
#endif
#ifdef _WITH_BFD_
case 'b':
bfd_pidfile = optarg;
break;
#endif
#ifdef _WITH_SNMP_
case 'x':
snmp = 1;
break;
case 'A':
snmp_socket = optarg;
break;
#endif
case 'M':
set_core_dump_pattern = true;
if (optarg && optarg[0])
core_dump_pattern = optarg;
/* ... falls through ... */
case 'm':
create_core_dump = true;
break;
#ifdef _MEM_CHECK_LOG_
case 'L':
__set_bit(MEM_CHECK_LOG_BIT, &debug);
break;
#endif
#if HAVE_DECL_CLONE_NEWNET
case 's':
override_namespace = MALLOC(strlen(optarg) + 1);
strcpy(override_namespace, optarg);
break;
#endif
case 'i':
FREE_PTR(config_id);
config_id = MALLOC(strlen(optarg) + 1);
strcpy(config_id, optarg);
break;
case 4: /* --signum */
signum = get_signum(optarg);
if (signum == -1) {
fprintf(stderr, "Unknown sigfunc %s\n", optarg);
exit(1);
}
printf("%d\n", signum);
exit(0);
break;
case 3: /* --all */
__set_bit(RUN_ALL_CHILDREN, &daemon_mode);
#ifdef _WITH_VRRP_
__set_bit(DAEMON_VRRP, &daemon_mode);
#endif
#ifdef _WITH_LVS_
__set_bit(DAEMON_CHECKERS, &daemon_mode);
#endif
#ifdef _WITH_BFD_
__set_bit(DAEMON_BFD, &daemon_mode);
#endif
break;
#ifdef _WITH_PERF_
case 5:
if (optarg && optarg[0]) {
if (!strcmp(optarg, "run"))
perf_run = PERF_RUN;
else if (!strcmp(optarg, "all"))
perf_run = PERF_ALL;
else if (!strcmp(optarg, "end"))
perf_run = PERF_END;
else
log_message(LOG_INFO, "Unknown perf start point %s", optarg);
}
else
perf_run = PERF_RUN;
break;
#endif
#ifdef WITH_DEBUG_OPTIONS
case 6:
set_debug_options(optarg && optarg[0] ? optarg : NULL);
break;
#endif
case '?':
if (optopt && argv[curind][1] != '-')
fprintf(stderr, "Unknown option -%c\n", optopt);
else
fprintf(stderr, "Unknown option %s\n", argv[curind]);
bad_option = true;
break;
case ':':
if (optopt && argv[curind][1] != '-')
fprintf(stderr, "Missing parameter for option -%c\n", optopt);
else
fprintf(stderr, "Missing parameter for option --%s\n", long_options[longindex].name);
bad_option = true;
break;
default:
exit(1);
break;
}
curind = optind;
}
if (optind < argc) {
printf("Unexpected argument(s): ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}
if (bad_option)
exit(1);
return reopen_log;
}
#ifdef THREAD_DUMP
static void
register_parent_thread_addresses(void)
{
register_scheduler_addresses();
register_signal_thread_addresses();
#ifdef _WITH_LVS_
register_check_parent_addresses();
#endif
#ifdef _WITH_VRRP_
register_vrrp_parent_addresses();
#endif
#ifdef _WITH_BFD_
register_bfd_parent_addresses();
#endif
#ifndef _DEBUG_
register_signal_handler_address("propagate_signal", propagate_signal);
register_signal_handler_address("sigend", sigend);
#endif
register_signal_handler_address("thread_child_handler", thread_child_handler);
}
#endif
/* Entry point */
int
keepalived_main(int argc, char **argv)
{
bool report_stopped = true;
struct utsname uname_buf;
char *end;
int exit_code = KEEPALIVED_EXIT_OK;
/* Ensure time_now is set. We then don't have to check anywhere
* else if it is set. */
set_time_now();
/* Save command line options in case need to log them later */
save_cmd_line_options(argc, argv);
/* Init debugging level */
debug = 0;
/* We are the parent process */
#ifndef _DEBUG_
prog_type = PROG_TYPE_PARENT;
#endif
/* Initialise daemon_mode */
#ifdef _WITH_VRRP_
__set_bit(DAEMON_VRRP, &daemon_mode);
#endif
#ifdef _WITH_LVS_
__set_bit(DAEMON_CHECKERS, &daemon_mode);
#endif
#ifdef _WITH_BFD_
__set_bit(DAEMON_BFD, &daemon_mode);
#endif
/* Set default file creation mask */
umask(022);
/* Open log with default settings so we can log initially */
openlog(PACKAGE_NAME, LOG_PID, log_facility);
#ifdef _MEM_CHECK_
mem_log_init(PACKAGE_NAME, "Parent process");
#endif
/* Some functionality depends on kernel version, so get the version here */
if (uname(&uname_buf))
log_message(LOG_INFO, "Unable to get uname() information - error %d", errno);
else {
os_major = (unsigned)strtoul(uname_buf.release, &end, 10);
if (*end != '.')
os_major = 0;
else {
os_minor = (unsigned)strtoul(end + 1, &end, 10);
if (*end != '.')
os_major = 0;
else {
if (!isdigit(end[1]))
os_major = 0;
else
os_release = (unsigned)strtoul(end + 1, &end, 10);
}
}
if (!os_major)
log_message(LOG_INFO, "Unable to parse kernel version %s", uname_buf.release);
/* config_id defaults to hostname */
if (!config_id) {
end = strchrnul(uname_buf.nodename, '.');
config_id = MALLOC((size_t)(end - uname_buf.nodename) + 1);
strncpy(config_id, uname_buf.nodename, (size_t)(end - uname_buf.nodename));
config_id[end - uname_buf.nodename] = '\0';
}
}
/*
* Parse command line and set debug level.
* bits 0..7 reserved by main.c
*/
if (parse_cmdline(argc, argv)) {
closelog();
if (!__test_bit(NO_SYSLOG_BIT, &debug))
openlog(PACKAGE_NAME, LOG_PID | ((__test_bit(LOG_CONSOLE_BIT, &debug)) ? LOG_CONS : 0) , log_facility);
}
if (__test_bit(LOG_CONSOLE_BIT, &debug))
enable_console_log();
#ifdef GIT_COMMIT
log_message(LOG_INFO, "Starting %s, git commit %s", version_string, GIT_COMMIT);
#else
log_message(LOG_INFO, "Starting %s", version_string);
#endif
/* Handle any core file requirements */
core_dump_init();
if (os_major) {
if (KERNEL_VERSION(os_major, os_minor, os_release) < LINUX_VERSION_CODE) {
/* keepalived was build for a later kernel version */
log_message(LOG_INFO, "WARNING - keepalived was build for newer Linux %d.%d.%d, running on %s %s %s",
(LINUX_VERSION_CODE >> 16) & 0xff,
(LINUX_VERSION_CODE >> 8) & 0xff,
(LINUX_VERSION_CODE ) & 0xff,
uname_buf.sysname, uname_buf.release, uname_buf.version);
} else {
/* keepalived was build for a later kernel version */
log_message(LOG_INFO, "Running on %s %s %s (built for Linux %d.%d.%d)",
uname_buf.sysname, uname_buf.release, uname_buf.version,
(LINUX_VERSION_CODE >> 16) & 0xff,
(LINUX_VERSION_CODE >> 8) & 0xff,
(LINUX_VERSION_CODE ) & 0xff);
}
}
#ifndef _DEBUG_
log_command_line(0);
#endif
/* Check we can read the configuration file(s).
NOTE: the working directory will be / if we
forked, but will be the current working directory
when keepalived was run if we haven't forked.
This means that if any config file names are not
absolute file names, the behaviour will be different
depending on whether we forked or not. */
if (!check_conf_file(conf_file)) {
if (__test_bit(CONFIG_TEST_BIT, &debug))
config_test_exit();
exit_code = KEEPALIVED_EXIT_NO_CONFIG;
goto end;
}
global_data = alloc_global_data();
global_data->umask = umask_val;
read_config_file();
init_global_data(global_data, NULL);
#if HAVE_DECL_CLONE_NEWNET
if (override_namespace) {
if (global_data->network_namespace) {
log_message(LOG_INFO, "Overriding config net_namespace '%s' with command line namespace '%s'", global_data->network_namespace, override_namespace);
FREE(global_data->network_namespace);
}
global_data->network_namespace = override_namespace;
override_namespace = NULL;
}
#endif
if (!__test_bit(CONFIG_TEST_BIT, &debug) &&
(global_data->instance_name
#if HAVE_DECL_CLONE_NEWNET
|| global_data->network_namespace
#endif
)) {
if ((syslog_ident = make_syslog_ident(PACKAGE_NAME))) {
log_message(LOG_INFO, "Changing syslog ident to %s", syslog_ident);
closelog();
openlog(syslog_ident, LOG_PID | ((__test_bit(LOG_CONSOLE_BIT, &debug)) ? LOG_CONS : 0), log_facility);
}
else
log_message(LOG_INFO, "Unable to change syslog ident");
use_pid_dir = true;
#ifdef ENABLE_LOG_TO_FILE
open_log_file(log_file_name,
NULL,
#if HAVE_DECL_CLONE_NEWNET
global_data->network_namespace,
#else
NULL,
#endif
global_data->instance_name);
#endif
}
/* Initialise pointer to child finding function */
set_child_finder_name(find_keepalived_child_name);
if (!__test_bit(CONFIG_TEST_BIT, &debug)) {
if (use_pid_dir) {
/* Create the directory for pid files */
create_pid_dir();
}
}
#if HAVE_DECL_CLONE_NEWNET
if (global_data->network_namespace) {
if (global_data->network_namespace && !set_namespaces(global_data->network_namespace)) {
log_message(LOG_ERR, "Unable to set network namespace %s - exiting", global_data->network_namespace);
goto end;
}
}
#endif
if (!__test_bit(CONFIG_TEST_BIT, &debug)) {
if (global_data->instance_name) {
if (!main_pidfile && (main_pidfile = make_pidfile_name(KEEPALIVED_PID_DIR KEEPALIVED_PID_FILE, global_data->instance_name, PID_EXTENSION)))
free_main_pidfile = true;
#ifdef _WITH_LVS_
if (!checkers_pidfile && (checkers_pidfile = make_pidfile_name(KEEPALIVED_PID_DIR CHECKERS_PID_FILE, global_data->instance_name, PID_EXTENSION)))
free_checkers_pidfile = true;
#endif
#ifdef _WITH_VRRP_
if (!vrrp_pidfile && (vrrp_pidfile = make_pidfile_name(KEEPALIVED_PID_DIR VRRP_PID_FILE, global_data->instance_name, PID_EXTENSION)))
free_vrrp_pidfile = true;
#endif
#ifdef _WITH_BFD_
if (!bfd_pidfile && (bfd_pidfile = make_pidfile_name(KEEPALIVED_PID_DIR VRRP_PID_FILE, global_data->instance_name, PID_EXTENSION)))
free_bfd_pidfile = true;
#endif
}
if (use_pid_dir) {
if (!main_pidfile)
main_pidfile = KEEPALIVED_PID_DIR KEEPALIVED_PID_FILE PID_EXTENSION;
#ifdef _WITH_LVS_
if (!checkers_pidfile)
checkers_pidfile = KEEPALIVED_PID_DIR CHECKERS_PID_FILE PID_EXTENSION;
#endif
#ifdef _WITH_VRRP_
if (!vrrp_pidfile)
vrrp_pidfile = KEEPALIVED_PID_DIR VRRP_PID_FILE PID_EXTENSION;
#endif
#ifdef _WITH_BFD_
if (!bfd_pidfile)
bfd_pidfile = KEEPALIVED_PID_DIR BFD_PID_FILE PID_EXTENSION;
#endif
}
else
{
if (!main_pidfile)
main_pidfile = PID_DIR KEEPALIVED_PID_FILE PID_EXTENSION;
#ifdef _WITH_LVS_
if (!checkers_pidfile)
checkers_pidfile = PID_DIR CHECKERS_PID_FILE PID_EXTENSION;
#endif
#ifdef _WITH_VRRP_
if (!vrrp_pidfile)
vrrp_pidfile = PID_DIR VRRP_PID_FILE PID_EXTENSION;
#endif
#ifdef _WITH_BFD_
if (!bfd_pidfile)
bfd_pidfile = PID_DIR BFD_PID_FILE PID_EXTENSION;
#endif
}
/* Check if keepalived is already running */
if (keepalived_running(daemon_mode)) {
log_message(LOG_INFO, "daemon is already running");
report_stopped = false;
goto end;
}
}
/* daemonize process */
if (!__test_bit(DONT_FORK_BIT, &debug) &&
xdaemon(false, false, true) > 0) {
closelog();
FREE_PTR(config_id);
FREE_PTR(orig_core_dump_pattern);
close_std_fd();
exit(0);
}
#ifdef _MEM_CHECK_
enable_mem_log_termination();
#endif
if (__test_bit(CONFIG_TEST_BIT, &debug)) {
validate_config();
config_test_exit();
}
/* write the father's pidfile */
if (!pidfile_write(main_pidfile, getpid()))
goto end;
/* Create the master thread */
master = thread_make_master();
/* Signal handling initialization */
signal_init();
/* Init daemon */
if (!start_keepalived())
log_message(LOG_INFO, "Warning - keepalived has no configuration to run");
initialise_debug_options();
#ifdef THREAD_DUMP
register_parent_thread_addresses();
#endif
/* Launch the scheduling I/O multiplexer */
launch_thread_scheduler(master);
/* Finish daemon process */
stop_keepalived();
#ifdef THREAD_DUMP
deregister_thread_addresses();
#endif
/*
* Reached when terminate signal catched.
* finally return from system
*/
end:
if (report_stopped) {
#ifdef GIT_COMMIT
log_message(LOG_INFO, "Stopped %s, git commit %s", version_string, GIT_COMMIT);
#else
log_message(LOG_INFO, "Stopped %s", version_string);
#endif
}
#if HAVE_DECL_CLONE_NEWNET
if (global_data && global_data->network_namespace)
clear_namespaces();
#endif
if (use_pid_dir)
remove_pid_dir();
/* Restore original core_pattern if necessary */
if (orig_core_dump_pattern)
update_core_dump_pattern(orig_core_dump_pattern);
free_parent_mallocs_startup(false);
free_parent_mallocs_exit();
free_global_data(global_data);
closelog();
#ifndef _MEM_CHECK_LOG_
FREE_PTR(syslog_ident);
#else
if (syslog_ident)
free(syslog_ident);
#endif
close_std_fd();
exit(exit_code);
}