/***************************************************************************//**
  @file main.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   1.02.2012

  Файл содержит логику инициализации, основной цикл работы и закрытие ltrd,
  а так же обработку глобальных событий (сигналов UNIX, запросов на перезапуск и
  закрытие сервера).
  *****************************************************************************/

#include "config.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_eth.h"
#include "ltrsrv_log.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_client.h"
#include "ltrsrv_opt_parse.h"
#ifdef LTRD_DAEMON_ENABLED
    #include "ltrsrv_daemon.h"
#endif
#ifdef LTRD_LOGPROTO_ENABLED
    #include "ltrsrv_log_proto.h"
#endif


#ifdef LTRD_USB_ENABLED
    #include "ltrsrv_usb.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>



#ifdef LTRD_SYSTEMD_ENABLED
    #include <systemd/sd-daemon.h>
#endif

#ifndef _WIN32
#include <signal.h>
#include <unistd.h>

#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#endif




/** флаги, определяющие спецзапросы к ltrd */
typedef enum {
    LTRSRV_FLAGS_REQ_CLOSE = 1, /** Запрос на завершение работы сервера */
    LTRSRV_FLAGS_REQ_RESTART = 2 /** Запрос на перезапуск сервера */
} t_ltrsrv_flags;

static int f_flags = 0;
t_ltrsrv_opts g_opts;

/** Установить запрос на завершение работы сервера */
void ltrsrv_req_server_close(void) {
    f_flags |= LTRSRV_FLAGS_REQ_CLOSE;
}

/** Установить запрос на перезагрузку сервера */
void ltrsrv_req_server_restart(void) {
    f_flags |= LTRSRV_FLAGS_REQ_RESTART;
}

#ifndef _WIN32
static void ltrsrv_sig_handler(int sig) {
    ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, LTRSRV_LOGSRC_SERVER, 0,
                   LTRD_PROGRAMM_NAME " catch signal %d", sig);
    if (sig == SIGHUP) {
        ltrsrv_req_server_restart();
    } else {
        ltrsrv_req_server_close();
    }
}


void ltrsrv_setup_sighandlers(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    /* устанавливаем обрабочики на обытия завершения и обновления настроек */
    sa.sa_handler = ltrsrv_sig_handler;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);
    /* игнорируем SIGPIPE, так как невозможность записи отслеживаем по ошибкам send */
    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, NULL);
}




/* Функция устанавливает пользователя и группу от имени которого будет выполняться
 * процесс. Используется, чтобы демон выполнялся не от имени root-пользователя */
static int f_set_user(const t_ltrsrv_opts* opts) {
    struct passwd *pw = NULL;
    struct group * gr = NULL;
    int err = 0;

    if (!err && opts->user && !(pw = getpwnam(opts->user))) {
        err = LTRSRV_ERR_FIND_USER;
    }

    if (!err && opts->group && !(gr = getgrnam(opts->group))) {
        err = LTRSRV_ERR_FIND_GROUP;
    }

    if (!err && opts->user && opts->group) {
#ifdef HAVE_INITGROUPS
        if (initgroups(opts->user, gr->gr_gid) != 0)
            err = LTRSRV_ERR_CHANGE_GROUP_LIST;
#else
        err = LTRSRV_ERR_CHANGE_GROUP_LIST;
#endif
    }

    if (!err && opts->group && ((setgid(gr->gr_gid) < 0) || (setegid(gr->gr_gid) < 0))) {
        err = LTRSRV_ERR_CHANGE_GID;
    }

    if (!err && opts->user && ((setuid(pw->pw_uid) < 0) || (seteuid(pw->pw_uid) < 0))) {
        err = LTRSRV_ERR_CHANGE_GID;
    }

    if (err)
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR_FATAL, LTRSRV_LOGSRC_SERVER, err,"Initialization error");

    return err;
}
#endif






#ifdef LTRD_SYSTEMD_ENABLED
static void f_systemd_watchdog_cb(t_socket sock, int event, void* data) {
    sd_notify(0, "WATCHDOG=1");
}
#endif



int daemon_main(void) {
    int err = 0;
#ifdef _WIN32
    WORD wVersionRequested = MAKEWORD(2, 2);
    WSADATA wsaData;
    if (WSAStartup(wVersionRequested, &wsaData))
        err = -1;
    if (wsaData.wVersion != wVersionRequested)
        err = - 2;
#endif

    if (!err) {
        int cfg_err = 0;
        do {
            /* разбираем конфигурационный файл */
            cfg_err = ltrsrv_load_settings(g_opts.cfg_file);

            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, LTRSRV_LOGSRC_SERVER, 0, LTRD_PROGRAMM_NAME " (v%d.%d.%d.%d) %s",
                           LTRD_VERSION_MAJOR, LTRD_VERSION_MINOR, LTRD_VERSION_REV, LTRD_VERSION_PATCH,
                           f_flags & LTRSRV_FLAGS_REQ_RESTART ? "restarted" : "started");
            f_flags = 0;
            /* если была ошибка при разборе конфигурации - выводим */
            if (cfg_err) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_SERVER, cfg_err,
                               "Cannot parse configuration file (%s)", g_opts.cfg_file);
            }

            err = ltrsrv_wait_init();
            /* инициализируем часть работы с клиентами (ltrapi) */
            if (!err)
                err = ltrsrv_clients_init();
            if (!err)
                err = ltrsrv_crates_init();
            if (!err)
                err = ltrsrv_eth_init();
#ifdef LTRD_USB_ENABLED
            /* ошибку инициализации USB не считаем критичной, чтобы можно
               было работать все равно хотя бы с Ethernet крейтами */
            if (!err)
                ltrsrv_usb_init();
#endif

#ifdef LTRD_SYSTEMD_ENABLED
            if (!err) {
                /* сообщаем  systemd, что инициализация завершена */
                if (g_opts.mode == LTRD_MODE_SYSTEMD)
                    sd_notify(0, "READY=1");
                ltrsrv_wait_add_tmr(&g_opts.mode, 0, g_opts.wdt_tout/(2*1000), f_systemd_watchdog_cb, NULL);
                ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, LTRSRV_LOGSRC_SERVER, 0, "systemd watchdog tout = %d",
                           g_opts.wdt_tout);
            }
#endif

            /* пока не поступить сигнал о закрытии - обрабатываем события от сокетов */
            for (;!err && !(f_flags & (LTRSRV_FLAGS_REQ_CLOSE | LTRSRV_FLAGS_REQ_RESTART));) {
                err = ltrsrv_wait_event(1000);
                if (!err) {
                    /* сохраняем настройки, если были изменены (проверяется внутри) */
                    int save_err = ltrsrv_save_settings();
                    if (save_err) {
                        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_SERVER, err,
                                       "cannot save ltrd settings to file %s", g_opts.cfg_file);
                    }
                }
            }


            ltrsrv_crates_close();
#ifdef LTRD_USB_ENABLED
            ltrsrv_usb_close();
#endif
            ltrsrv_eth_close();

            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, LTRSRV_LOGSRC_SERVER, 0, LTRD_PROGRAMM_NAME " %s",
                           f_flags & LTRSRV_FLAGS_REQ_RESTART ? "process restart request" : "stopped");

#ifdef LTRD_LOGPROTO_ENABLED
            ltrsrv_logproto_close();
#endif
            ltrsrv_clients_close();
            ltrsrv_wait_close();
            ltrsrv_save_settings();

        }  while (f_flags & LTRSRV_FLAGS_REQ_RESTART); /* если вышли по Restart - возвращаемся в начало */


    }

#ifdef LTRD_DAEMON_ENABLED
    if (g_opts.mode == LTRD_MODE_DAEMON)
        ltrsrv_daemon_close();
#endif

#ifdef _WIN32
    WSACleanup();
#endif
    ltrsrv_log_close();
    return err;
}



//===========================================================================
int main(int argc, char** argv) {
    int err = 0;

    memset(&g_opts, 0, sizeof(g_opts));


    /* разибраем опции командной строки */
    g_opts.cfg_file = LTRSRV_DEFAULT_CONFIG_FILE;
#ifdef LTRD_DAEMON_ENABLED
    g_opts.pid_file = "/var/run/ltrd/pid";
#endif
    g_opts.user = NULL;
    g_opts.group = NULL;
    g_opts.mode = LTRD_MODE_CONSOLE;

    err = ltrsrv_parse_opt(&g_opts, argc, argv);
    if (err) {
        fprintf(stderr, "Invalid usage!\n");
    }

#ifndef _WIN32
    if (!err)
        err = f_set_user(&g_opts);
#endif


    if (!err) {
#ifdef LTRD_DAEMON_ENABLED
        if (g_opts.mode == LTRD_MODE_DAEMON) {
            err = ltrsrv_daemon_start();
        } else if (g_opts.mode == LTRD_MODE_SVC_OP) {
            err = ltrsrv_daemon_svc_op(g_opts.svc_op);
        } else {
#endif        
#ifndef _WIN32
            ltrsrv_setup_sighandlers();
#endif
            err = daemon_main();
#ifdef LTRD_DAEMON_ENABLED
        }
#endif
    }

    return err;
}



