Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1164 → Rev 1165

/rebootd/trunk/rebootd.c
31,6 → 31,8
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
66,28 → 68,42
/* command line and config file parameters */
#define PARAM_HELP_1 "-h"
#define PARAM_HELP_2 "--help"
#define PARAM_VERSION_1 "-v"
#define PARAM_VERSION_2 "--version"
#define PARAM_INTERFACE_1 "-i"
#define PARAM_INTERFACE_2 "--interface"
#define PARAM_PORT_1 "-p"
#define PARAM_PORT_2 "--port"
#define PARAM_CONFIG_1 "-c"
#define PARAM_CONFIG_2 "--config"
#define PARAM_VERSION "--version"
#define PARAM_INTERFACE "--interface"
#define PARAM_PORT "--port"
#define PARAM_CONFIG "--config"
#define PARAM_PID_FILE "--pid"
#define CONFIG_INTERFACE "interface"
#define CONFIG_PORT "port"
#define CONFIG_PASSWORD "password"
#define CONFIG_PID_FILE "pid"
 
/* priority of defferent sources of options */
#define CFG_PRIO_CMD 1
#define CFG_PRIO_CONFIG 2
#define CFG_PRIO_DEFAULT 3
 
struct config {
char config_file[FILENAME_MAX];
char config_file_prio;
struct in_addr interface;
char interface_prio;
ushort port;
char port_prio;
char password[MAX_CONFIG_LINE];
char password_prio;
char pid_file[FILENAME_MAX];
char pid_file_prio;
};
static struct config global_cfg;
static int daemonized = 0; /* we are already a daemon */
volatile static int reconfig_needed = 0; /* we have to stop socket listening and reread config */
 
static int parse_cmd_line(struct config *cfg, int argc, const char* argv[]);
static int parse_config_file(struct config *cfg, const char *config_name);
static int get_revision(void);
static void check_return(int res);
 
static uint min(uint a, uint b)
{
134,10 → 150,12
for(;;) {
sa_len = sizeof(sa);
if((t = accept(soc, (struct sockaddr *)&sa, &sa_len)) < 0) {
if(errno == EINTR) /* EINTR might happen on accept(), */
continue; /* try again */
 
syslog(LOG_ERR, "cannot get connection, %s", strerror(errno));
if(errno == EINTR) { /* EINTR might happen on accept(), */
if(reconfig_needed) return;
}
else {
syslog(LOG_ERR, "cannot get connection, %s", strerror(errno));
}
continue;
}
 
183,6 → 201,22
printf("Then run from any other host:\necho -n \"some password\" | nc host_to_rebot 19\n");
}
 
static int print_error(const char *msg, const char *value)
{
if(daemonized) {
syslog(LOG_ERR, "%s", msg);
if(value) syslog(LOG_ERR, " - %s\n", value);
syslog(LOG_ERR, "\n");
}
else {
fprintf(stderr, "%s", msg);
if(value) fprintf(stderr, " - %s\n", value);
fprintf(stderr, "\n");
}
 
return RESULT_ERROR;
}
 
static int print_cmd_error(int argc, const char* argv[], const char *msg, const char *value)
{
fprintf(stderr, "%s", msg);
196,18 → 230,33
 
static int print_config_error(const char *config_name, int line, const char *msg, const char *value)
{
fprintf(stderr, "Error in %s, line %d: %s", config_name, line, msg);
if(value) fprintf(stderr, " - %s", value);
fprintf(stderr, "\n");
if(daemonized) {
syslog(LOG_ERR, "Error in %s, line %d: %s", config_name, line, msg);
if(value) syslog(LOG_ERR, " - %s", value);
syslog(LOG_ERR, "\n");
}
else {
fprintf(stderr, "Error in %s, line %d: %s", config_name, line, msg);
if(value) fprintf(stderr, " - %s", value);
fprintf(stderr, "\n");
}
 
return RESULT_ERROR;
}
 
/* return: 0 - no error, continue; 1 - no error, but exit; 2 - user error, exit; 3 - unexpected error, exit */
static int check_abs_path(const char *file_name)
{
if(file_name && file_name[0] != '\0' && file_name[0] != '/') {
return print_error("File must have an absolute path", file_name);
}
 
return RESULT_OK;
}
 
static int read_config(struct config *cfg, int argc, const char* argv[])
{
int res_cmd_line;
int res_config_file;
int res_cmd_line;
int res_config_file;
struct config cmd_cfg;
struct config file_cfg;
 
214,32 → 263,100
memset(cfg, 0, sizeof(struct config));
res_cmd_line = parse_cmd_line(&cmd_cfg, argc, argv);
if(res_cmd_line != RESULT_OK) return res_cmd_line;
if(check_abs_path(cmd_cfg.config_file) != RESULT_OK) return RESULT_ERROR;
 
res_config_file = parse_config_file(&file_cfg,
cmd_cfg.config_file[0] != '\0' ? cmd_cfg.config_file : DEFAULT_CFG_FILE);
if(res_config_file != RESULT_OK) return res_config_file;
 
if(file_cfg.password[0] == '\0') {
fprintf(stderr, "Password is not set\n");
return RESULT_ERROR;
if(file_cfg.password[0] == '\0')
return print_error("Password is not set", NULL);
 
/* save parsed values to general config */
if(cmd_cfg.config_file[0] != '\0') {
strncpy(cfg->config_file, cmd_cfg.config_file, sizeof(cfg->config_file));
cfg->config_file_prio = CFG_PRIO_CMD;
}
else {
strncpy(cfg->config_file, DEFAULT_CFG_FILE, sizeof(cfg->config_file));
cfg->config_file_prio = CFG_PRIO_DEFAULT;
}
 
if(cmd_cfg.interface.s_addr) {
cfg->interface = cmd_cfg.interface;
cfg->interface_prio = CFG_PRIO_CMD;
}
else if(file_cfg.interface.s_addr) {
cfg->interface = file_cfg.interface;
cfg->interface_prio = CFG_PRIO_CONFIG;
}
else {
cfg->interface_prio = CFG_PRIO_DEFAULT;
}
 
if(cmd_cfg.port) {
cfg->port = cmd_cfg.port;
cfg->port_prio = CFG_PRIO_CMD;
}
else if(file_cfg.port) {
cfg->port = file_cfg.port;
cfg->port_prio = CFG_PRIO_CONFIG;
}
else {
cfg->port = DEFAULT_PORT;
cfg->port_prio = CFG_PRIO_DEFAULT;
}
 
if(cmd_cfg.pid_file[0] != '\0') {
strncpy(cfg->pid_file, cmd_cfg.pid_file, sizeof(cfg->pid_file));
cfg->pid_file_prio = CFG_PRIO_CMD;
}
else if(file_cfg.pid_file[0] != '\0') {
strncpy(cfg->pid_file, file_cfg.pid_file, sizeof(cfg->pid_file));
cfg->pid_file_prio = CFG_PRIO_CONFIG;
}
else {
cfg->pid_file_prio = CFG_PRIO_DEFAULT;
}
if(check_abs_path(cfg->config_file) != RESULT_OK) return RESULT_ERROR;
 
strncpy(cfg->password, file_cfg.password, sizeof(cfg->password));
cfg->password_prio = CFG_PRIO_CONFIG;
 
return RESULT_OK;
}
 
static int reread_config(struct config *cfg, int *reinit_needed)
{
int res_config_file;
struct config file_cfg;
 
*reinit_needed = 0;
 
res_config_file = parse_config_file(&file_cfg, cfg->config_file);
if(res_config_file != RESULT_OK) return res_config_file;
 
if(file_cfg.password[0] == '\0')
return print_error("Password is not set", NULL);
 
/* save parsed values to general config */
strncpy(cfg->config_file, cmd_cfg.config_file, sizeof(cfg->config_file));
if(cfg->interface_prio >= CFG_PRIO_CONFIG && cfg->interface.s_addr != file_cfg.interface.s_addr)
{
*reinit_needed = 1;
cfg->interface = file_cfg.interface;
cfg->interface_prio = CFG_PRIO_CONFIG;
}
 
if(cmd_cfg.interface.s_addr)
cfg->interface = cmd_cfg.interface;
else if(file_cfg.interface.s_addr)
cfg->interface = file_cfg.interface;
if(cfg->port_prio >= CFG_PRIO_CONFIG && cfg->port != file_cfg.port && file_cfg.port) {
*reinit_needed = 1;
cfg->port = file_cfg.port;
cfg->port_prio = CFG_PRIO_CONFIG;
}
 
if(cmd_cfg.port > 0)
cfg->port = cmd_cfg.port;
else if(file_cfg.port > 0)
cfg->port = file_cfg.port;
else
cfg->port = DEFAULT_PORT;
if(check_abs_path(cfg->config_file) != RESULT_OK) return RESULT_ERROR;
 
strncpy(cfg->password, file_cfg.password, sizeof(cfg->password));
cfg->password_prio = CFG_PRIO_CONFIG;
 
return RESULT_OK;
}
275,7 → 392,7
memset(cfg, 0, sizeof(struct config));
 
for(i = 1; i < argc; i++) {
if(0 == strcmp(argv[i], PARAM_INTERFACE_1) || 0 == strcmp(argv[i], PARAM_INTERFACE_2)) {
if(0 == strcmp(argv[i], PARAM_INTERFACE)) {
if(cfg->interface.s_addr)
return print_cmd_error(argc, argv, "Interface is already set", NULL);
 
288,7 → 405,7
return print_cmd_error(argc, argv, "Interface expected", NULL);
}
}
else if(0 == strcmp(argv[i], PARAM_PORT_1) || 0 == strcmp(argv[i], PARAM_PORT_2)) {
else if(0 == strcmp(argv[i], PARAM_PORT)) {
if(cfg->port)
return print_cmd_error(argc, argv, "Port is already set", NULL);
 
303,21 → 420,35
return print_cmd_error(argc, argv, "Port number expected", NULL);
}
}
else if(0 == strcmp(argv[i], PARAM_CONFIG_1) || 0 == strcmp(argv[i], PARAM_CONFIG_2)) {
else if(0 == strcmp(argv[i], PARAM_CONFIG)) {
if(cfg->config_file[0] != '\0')
return print_cmd_error(argc, argv, "Config file is already set", NULL);
return print_cmd_error(argc, argv, "Config file name is already set", NULL);
 
if(++i < argc) {
strncpy(cfg->config_file, argv[i], MAX_CONFIG_LINE);
if(cfg->config_file[MAX_CONFIG_LINE - 1] != '\0')
strncpy(cfg->config_file, argv[i], FILENAME_MAX);
if(cfg->config_file[FILENAME_MAX - 1] != '\0')
return print_cmd_error(argc, argv,
"Config file name is too long", NULL);
}
else {
return print_cmd_error(argc, argv, "Config file expected", NULL);
return print_cmd_error(argc, argv, "Config file name expected", NULL);
}
}
else if(0 == strcmp(argv[i], PARAM_VERSION_1) || 0 == strcmp(argv[i], PARAM_VERSION_2)) {
else if(0 == strcmp(argv[i], PARAM_PID_FILE)) {
if(cfg->pid_file[0] != '\0')
return print_cmd_error(argc, argv, "PID file name is already set", NULL);
 
if(++i < argc) {
strncpy(cfg->pid_file, argv[i], FILENAME_MAX);
if(cfg->pid_file[FILENAME_MAX - 1] != '\0')
return print_cmd_error(argc, argv,
"PID file name is too long", NULL);
}
else {
return print_cmd_error(argc, argv, "PID file name expected", NULL);
}
}
else if(0 == strcmp(argv[i], PARAM_VERSION)) {
print_version();
return RESULT_EXIT;
}
360,18 → 491,35
return RESULT_OK;
}
 
static int extract_quoted(const char *config_name, const int count, char **subline, char *string, uint len)
static int extract_string_value(const char *config_name, const int count, char **subline, int must_quot,
char *string, uint len)
{
uint cur_len;
int quot;
 
while(*subline[0] != '\0' && isspace(*subline[0])) (*subline)++;
if('"' != *subline[0])
return print_config_error(config_name, count, "Open quot expected", NULL);
(*subline)++;
if('"' == *subline[0]) {
quot = 1;
(*subline)++;
}
else {
if(must_quot) {
return print_config_error(config_name, count, "Open quot expected", NULL);
}
else {
quot = 0;
/* skip spaces if not quoted */
while(*subline[0] != '\0' && isspace(*subline[0])) (*subline)++;
}
}
 
cur_len = 0;
while(cur_len < len && *subline[0] != '\0' && *subline[0] != '"')
while(*subline[0] != '\0')
{
if(cur_len >= len) return print_config_error(config_name, count, "Value too long", NULL);
if(quot && *subline[0] == '"') break;
if(!quot && isspace(*subline[0])) break;
 
string[0] = *subline[0];
string++;
(*subline)++;
379,9 → 527,11
}
 
string[0] = '\0';
if('"' != *subline[0])
return print_config_error(config_name, count, "Close quot expected", NULL);
(*subline)++;
if(quot) {
if('"' != *subline[0])
return print_config_error(config_name, count, "Close quot expected", NULL);
(*subline)++;
}
 
return RESULT_OK;
}
400,10 → 550,8
memset(cfg, 0, sizeof(struct config));
 
config_file = fopen(config_name, "r");
if(!config_file) {
fprintf(stderr, "Can not open config file %s: %s\n", config_name, strerror(errno));
return RESULT_ERROR;
}
if(!config_file)
return print_error("Can not open config file", strerror(errno));
 
count = 0;
while(fgets(buf, sizeof(buf), config_file)) {
450,23 → 598,30
if((res = validate_equal_sign(config_name, count, line,
sizeof(CONFIG_PASSWORD), &subline)) != RESULT_OK) return res;
 
if((res = extract_quoted(config_name, count, &subline,
if((res = extract_string_value(config_name, count, &subline, 1,
cfg->password, sizeof(cfg->password))) != RESULT_OK) return res;
 
if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
}
else if(strncmp(line, CONFIG_PID_FILE, min(sizeof(CONFIG_PID_FILE) - 1, len)) == 0) {
if((res = validate_equal_sign(config_name, count, line,
sizeof(CONFIG_PID_FILE), &subline)) != RESULT_OK) return res;
 
if((res = extract_string_value(config_name, count, &subline, 0,
cfg->pid_file, sizeof(cfg->pid_file))) != RESULT_OK) return res;
 
if((res = validate_eol(config_name, count, subline)) != RESULT_OK) return res;
}
else {
return print_config_error(config_name, count, "Unknown config parameter", line);
}
}
if(ferror(config_file)) {
fprintf(stderr, "Config file %s reading failed\n", config_name);
print_error("Config file reading failed", strerror(errno));
}
 
if(fclose(config_file) != 0) {
fprintf(stderr, "Can not close config file %s: %s\n", config_name, strerror(errno));
return RESULT_UNEXPECTED;
}
if(fclose(config_file) != 0)
return print_error("Can not close config file", strerror(errno));
 
return RESULT_OK;
}
485,6 → 640,40
return r;
}
 
static int save_pid(const char *pid_file, const pid_t pid)
{
int fd;
FILE *file;
 
if(!pid_file || pid_file[0] == '\0') return RESULT_OK;
unlink(pid_file);
 
fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if(fd < 0) {
syslog(LOG_ERR, "Can open PID file %s for write: %s\n", pid_file, strerror(errno));
return RESULT_ERROR;
}
 
file = fdopen(fd, "w");
if(!file) {
syslog(LOG_ERR, "Can open PID file %s for write: %s\n", pid_file, strerror(errno));
return RESULT_ERROR;
}
 
if(fprintf(file, "%d\n", pid) < 0) {
syslog(LOG_ERR, "Can write PID to file %s: %s\n", pid_file, strerror(errno));
return RESULT_ERROR;
}
 
if(fclose(file) != 0) {
syslog(LOG_ERR, "Can not close PID file %s: %s\n", pid_file, strerror(errno));
return RESULT_UNEXPECTED;
}
 
return RESULT_OK;
}
 
static int create_child(int soc)
{
pid_t child;
499,6 → 688,7
fprintf(stderr, "Cannot close socket: %s\n", strerror(errno));
return RESULT_UNEXPECTED; /* an unexpected error */
}
 
return RESULT_EXIT; /* we are the parent */
}
else {
515,30 → 705,104
return RESULT_OK;
}
 
static void reconfig_signal(void)
{
syslog(LOG_INFO, "reconfig");
reconfig_needed = 1;
}
 
static void delete_pid_file(void)
{
if(global_cfg.pid_file && global_cfg.pid_file[0] != '\0') {
if(unlink(global_cfg.pid_file) != 0) {
syslog(LOG_ERR, "Cannot delete PID file %s: %s\n",
global_cfg.pid_file, strerror(errno));
}
}
}
 
static void quit_signal(void)
{
delete_pid_file();
syslog(LOG_WARNING, "quit");
exit(0);
}
 
static void signal_handler(int sig)
{
int saved_errno = errno;
 
switch(sig) {
case SIGHUP:
reconfig_signal();
break;
 
case SIGINT:
case SIGTERM:
case SIGQUIT:
quit_signal();
break;
}
 
errno = saved_errno;
}
 
static int init_signal_handler(int sig)
{
struct sigaction sa;
 
sa.sa_flags = 0;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
 
if(sigaction(sig, &sa, (struct sigaction *)NULL) != 0) {
syslog(LOG_ERR, "Cannot register handler for signal %d: %s\n", sig, strerror(errno));
return RESULT_UNEXPECTED;
}
 
return RESULT_OK;
}
 
static int init_signals(void)
{
if(init_signal_handler(SIGHUP) != RESULT_OK) return RESULT_UNEXPECTED;
if(init_signal_handler(SIGINT) != RESULT_OK) return RESULT_UNEXPECTED;
if(init_signal_handler(SIGTERM) != RESULT_OK) return RESULT_UNEXPECTED;
if(init_signal_handler(SIGQUIT) != RESULT_OK) return RESULT_UNEXPECTED;
 
return RESULT_OK;
}
 
static void check_return(int res)
{
if(daemonized && res != RESULT_OK) {
delete_pid_file();
syslog(LOG_WARNING, "quit");
}
 
switch(res) {
case RESULT_EXIT: exit(EXIT_OK); return; /* no error but exit */
case RESULT_ERROR: exit(EXIT_USER_ERROR); return; /* user error */
case RESULT_UNEXPECTED: exit(EXIT_UNEXPECTED); return; /* unexpected error */
}
}
 
int main(int argc, const char* argv[])
{
int soc;
struct config cfg;
int soc;
int reinit_needed;
 
/* get config */
switch(read_config(&cfg, argc, argv)) {
case RESULT_EXIT: return EXIT_OK; /* no error but exit */
case RESULT_ERROR: return EXIT_USER_ERROR; /* user error */
case RESULT_UNEXPECTED: return EXIT_UNEXPECTED; /* unexpected error */
}
check_return(read_config(&global_cfg, argc, argv));
 
/* try to listen the port */
if((soc = establish(&cfg)) < 0) {
fprintf(stderr, "Cannot listen to port %i\n", cfg.port);
if((soc = establish(&global_cfg)) < 0) {
fprintf(stderr, "Cannot listen to port %i\n", global_cfg.port);
return EXIT_USER_ERROR;
}
 
/* fork and release the console */
switch(create_child(soc)) {
case RESULT_EXIT: return EXIT_OK; /* no error but exit */
case RESULT_ERROR: return EXIT_USER_ERROR; /* user error */
case RESULT_UNEXPECTED: return EXIT_UNEXPECTED; /* unexpected error */
}
check_return(create_child(soc));
 
/* continue as first child */
if(setsid() == -1) {
547,22 → 811,42
}
 
/* fork the second time */
switch(create_child(soc)) {
case RESULT_EXIT: return EXIT_OK; /* no error but exit */
case RESULT_ERROR: return EXIT_USER_ERROR; /* user error */
case RESULT_UNEXPECTED: return EXIT_UNEXPECTED; /* unexpected error */
check_return(create_child(soc));
 
/* continue as final child */
if(init_signals() != RESULT_OK) return EXIT_UNEXPECTED;
 
if(chdir("/") < 0) {
fprintf(stderr, "Cannot chdir to /: %s\n", strerror(errno));
return EXIT_UNEXPECTED;
}
umask(0);
check_return(save_pid(global_cfg.pid_file, getpid()));
daemonized = 1;
 
/* continue as final child, from now do not use console for error output */
chdir("/");
umask(0);
/* from now do not use console for error output */
if(close_descr(0) != RESULT_OK) return EXIT_UNEXPECTED;
if(close_descr(1) != RESULT_OK) return EXIT_UNEXPECTED;
if(close_descr(2) != RESULT_OK) return EXIT_UNEXPECTED;
 
syslog(LOG_INFO, "listen on %d", cfg.port);
listen_socket(&cfg, soc);
for(;;) {
syslog(LOG_INFO, "listen on %d", global_cfg.port);
listen_socket(&global_cfg, soc);
 
return EXIT_OK; /* unreachedable */
if(reconfig_needed) {
reconfig_needed = 0;
check_return(reread_config(&global_cfg, &reinit_needed));
if(reinit_needed) {
close(soc);
 
if((soc = establish(&global_cfg)) < 0) {
syslog(LOG_ERR, "Cannot listen to port %i\n", global_cfg.port);
check_return(RESULT_ERROR);
}
}
}
}
 
return EXIT_OK; /* unreacheable */
}
 
/rebootd/trunk/rebootd.conf
3,4 → 3,5
interface=127.0.0.1
port=2019
password="some password"
pid= /tmp/rebootd.pid