Subversion Repositories general

Rev

Rev 1177 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/******************************************************************
 * rebootd.
 *
 * A daemon which waits on specified UDP port for special string
 * (password) and reboots the computer.
 *
 * Run "./rebootd --help" to see options and parameters.
 *
 * Copyleft 2005 Anatoli Klassen
 *
 ******************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/reboot.h>

#include "common.h"
#include "rebootd.h"
#include "config.h"
#include "signals.h"
#include "network.h"

int          daemonized        = 0; /* we are already a daemon */
volatile int reconfig_required = 0; /* we have to stop socket listening and reread config */
volatile int quit_required     = 0; /* we have to stop socket listening and exit */

void handle_command(const struct config *cfg, const char *cmd, const int cmd_len)               
{
        if(0 != strncmp(cmd, cfg->password, cmd_len)) return; /* wrong password */

        if(cfg->debug_mode) {
                syslog(LOG_INFO, "REBOOT, debug mode");
        }
        else {
                syslog(LOG_EMERG, "REBOOT");
                sleep(5);
                if(reboot(RB_AUTOBOOT) < 0) {
                        syslog(LOG_ERR, "cannot reboot, %s", strerror(errno));
                }
        }
}

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;

        child = fork();
        if(child == -1) {
                fprintf(stderr, "Cannot fork: %s\n", strerror(errno));
                return RESULT_UNEXPECTED; /* an unexpected error */
        }
        else if(child > 0) {
                if(close(soc) != 0) {
                        fprintf(stderr, "Cannot close socket: %s\n", strerror(errno));
                        return RESULT_UNEXPECTED; /* an unexpected error */
                }

                return RESULT_EXIT; /* we are the parent */
        }
        else {
                return RESULT_OK; /* we are the child */
        }
}

static int close_descr(int d)
{
        if(close(d) != 0) {
                syslog(LOG_ERR, "Cannot close descriptor %d: %s\n", d, strerror(errno));
                return RESULT_UNEXPECTED;
        }
        return RESULT_OK;
}

static void delete_pid_file(const char *pid_file)
{
        if(pid_file && pid_file[0] != '\0') {
                if(unlink(pid_file) != 0) {
                        syslog(LOG_ERR, "Cannot delete PID file %s: %s\n", pid_file, strerror(errno));
                }
        }
}

static void check_return(int res, const struct config *cfg)
{
        if(daemonized && res != RESULT_OK) {
                delete_pid_file(cfg->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;
        int           reinit_needed;
        struct config cfg;

        /* get config */
        check_return(read_config(&cfg, argc, argv), &cfg);

        /* try to listen the port */
        if((soc = establish(&cfg)) < 0) {
                fprintf(stderr, "Cannot listen to port %i\n", cfg.port);
                return EXIT_USER_ERROR;
        }

        /* fork and release the console */
        check_return(create_child(soc), &cfg);

        /* continue as first child */
        if(setsid() == -1) {
                fprintf(stderr, "Cannot create session: %s\n", strerror(errno));
                return EXIT_UNEXPECTED;
        }

        /* fork the second time */
        check_return(create_child(soc), &cfg);

        /* 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(cfg.pid_file, getpid()), &cfg);
        daemonized = 1;

        /* 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;

        for(;;) {
                syslog(LOG_INFO, "listen on %d", cfg.port);
                listen_socket(&cfg, soc);

                if(quit_required) {
                        break;
                }
                else if(reconfig_required) {
                        reconfig_required = 0;
                        check_return(reread_config(&cfg, &reinit_needed), &cfg);
                        if(reinit_needed) {
                                close(soc);

                                if((soc = establish(&cfg)) < 0) {
                                        syslog(LOG_ERR, "Cannot listen to port %i\n", cfg.port);
                                        check_return(RESULT_ERROR, &cfg);
                                }
                        }
                }
        }

        delete_pid_file(cfg.pid_file);
        return EXIT_OK;
}