/*------------------------------------------------------------------------------
 *
 * Copyright (c) 2011-2025, EURid vzw. All rights reserved.
 * The YADIFA TM software product is provided under the BSD 3-clause license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *        * Redistributions of source code must retain the above copyright
 *          notice, this list of conditions and the following disclaimer.
 *        * Redistributions in binary form must reproduce the above copyright
 *          notice, this list of conditions and the following disclaimer in the
 *          documentation and/or other materials provided with the distribution.
 *        * Neither the name of EURid nor the names of its contributors may be
 *          used to endorse or promote products derived from this software
 *          without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------*/

/**-----------------------------------------------------------------------------
 * @defgroup
 * @ingroup
 * @brief
 *
 *
 *
 * @{
 *----------------------------------------------------------------------------*/

#include "dnscore/dnscore_config.h"
#include "dnscore/logger.h"
#include "dnscore/service.h"
#include "dnscore/config_settings.h"
#include "dnscore/fdtools.h"
#include "dnscore/thread_pool.h"
#include "dnscore/logger_output_stream.h"
#include "dnscore/chroot.h"

#include "dnscore/pid.h"
#include "dnscore/identity.h"

#include "dnscore/server_setup.h"
#include "dnscore/mt_output_stream.h"

#include "dnscore/process.h"
#include "dnscore/signals.h"

#include <fcntl.h>
#if __unix__
#include <sys/resource.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#if DNSCORE_HAS_LOG_THREAD_TAG
void thread_tag_log_tags();
#endif

/*------------------------------------------------------------------------------
 * GLOBAL VARIABLES */

extern logger_handle_t *g_system_logger;
#define MODULE_MSG_HANDLE g_system_logger

/*------------------------------------------------------------------------------
 * FUNCTIONS */

static void server_setup_daemon_reopen_console_fds()
{
#if DEBUG
    // It has been asked to fix the "tmpfile vulnerability" on DEBUG builds.
    // Although this mode is meant for us (Gery & I), we can imagine that
    // someone else could have some interest in it too.
    // So the output file name now includes high-precision time and PID and
    // will break if the file exists already.
    // Later, the "server" part will also be modifiable by the caller.

    char output_file[PATH_MAX];
    snformat(output_file, sizeof(output_file), "/tmp/server-%013x-%05x.std.txt", timeus(), getpid());
    debug_osformatln(termout, "redirecting all to '%s'\n", output_file);
    int file_flags = O_RDWR | O_CREAT | O_EXCL; // ensure no overwrite
#else
    const char *output_file = DEV_NULL_PATH;
    int         file_flags = O_RDWR;
#endif

    /* Attach file descriptors 0, 1, and 2 to /dev/null */

    flushout();
    flusherr();

    int tmpfd;
    if((tmpfd = open_create_ex(output_file, file_flags, 0660)) < 0)
    {
        fprintf(stderr, "couldn't get file for redirection: %s '%s'\n", strerror(errno), output_file);
        fflush(NULL);
        exit(EXIT_FAILURE);
    }

    int tmpfdin;

#if DEBUG
    if((tmpfdin = open_create_ex(DEV_NULL_PATH, O_WRONLY, 0440)) < 0)
    {
        fprintf(stderr, "couldn't get file for redirection: %s '%s'\n", strerror(errno), output_file);
        fflush(NULL);
        exit(EXIT_FAILURE);
    }
#else
    tmpfdin = tmpfd;
#endif

    {
        int fd;
        if((fd = dup2_ex(tmpfdin, 0)) < 0)
        {
            log_err("dup2 failed from %i to %i: %s", tmpfdin, 0, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if(fd != 0)
        {
            log_err("expected fd %d instead of %d", 0, fd);
            exit(EXIT_FAILURE);
        }
    }

    for(int_fast32_t i = 1; i <= 2; i++)
    {
        int fd;
        if((fd = dup2_ex(tmpfd, i)) < 0)
        {
            log_err("dup2 failed from %i to %i: %s", tmpfd, i, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if(fd != i)
        {
            log_err("expected fd %d instead of %d", i, fd);
            exit(EXIT_FAILURE);
        }
    }
}

/** \brief Damonize the program and set the correct system limitations
 *
 *  @param[in] config is a config_data structure needed for \b "pid file"
 *
 *  @return OK
 *  @return Otherwise log_quit will stop the program
 */
int server_setup_daemon_go_ex(bool parent_exit, bool child_logger_ownership)
{
#if __unix__
    mode_t           mask = 0;
    pid_t            pid;
    struct sigaction sa;

    /*    ------------------------------------------------------------    */

    log_info("daemonizing");

    if(!config_logger_isconfigured())
    {
        osformatln(termerr, "warning: daemonize enabled with default logging on tty output, no output will be available.");
        flusherr();
    }

    log_debug("daemonize: stop timer");
    logger_flush();

    dnscore_stop_timer();

    log_debug("daemonize: stop services");
    logger_flush();

    service_stop_all();

    log_debug("daemonize: stop thread pools");
    logger_flush();

    ya_result error_code;

    if(FAIL(error_code = thread_pool_stop_all()))
    {
        osformatln(termerr, "daemonize: unable to stop all thread pools: %r", error_code);
        flusherr();

        exit(EXIT_FAILURE);
    }

#if DNSCORE_HAS_LOG_THREAD_TAG
    thread_tag_log_tags();
#endif

    logger_flush();
    logger_stop();

    /* Clear file creation mask */
    umask(mask);

#if DEBUG
    debug_println("releasing ownership of the logger");
    flushout();
#endif

    logger_release_ownership();

    /* Become a session leader to lose controlling TTYs */
    if((pid = fork()) < 0)
    {
        osformatln(termerr, "[%i] cannot fork: %r", getpid(), ERRNO_ERROR);
        flusherr();

        exit(EXIT_FAILURE);
    }

    // if the logger was running, disconnect ownership from the parent and give it to the child

    if(pid != 0) /* parent */
    {
#if DEBUG
        formatln("[%i] first level parent done", getpid());
        flushout();
#endif
        if(parent_exit)
        {
            exit(EXIT_SUCCESS);
        }

        // else re-enable everything and return

        logger_start();

#if DNSCORE_HAS_LOG_THREAD_TAG
        thread_tag_log_tags();
#endif
        dnscore_reset_timer();
        thread_pool_start_all();
        service_start_all();
        logger_flush();

        return pid;
    }

    /* Set program in new session */
    setsid();

#if __unix__
    /* Ensure future opens won't allocate controlling TTYs */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if(sigaction(SIGHUP, &sa, NULL) < 0)
    {
        osformatln(termerr, "sigaction error: %r", ERRNO_ERROR);
        flusherr();

        exit(EXIT_FAILURE);
    }
#endif

    /* Stevens way of detaching the program from the parent process,
     * forking twice
     */
    if((pid = fork()) < 0)
    {
        osformatln(termerr, "cannot fork (2nd level): %r", ERRNO_ERROR);
        flusherr();

        exit(EXIT_FAILURE);
    }

    if(pid != 0) /* parent */
    {
#if DEBUG
        formatln("[%i] second level parent done", getpid());
        flushout();
#endif
        exit(EXIT_SUCCESS);
    }

#if DEBUG
    debug_osformatln(termout, "taking ownership of the logger (%i)", getpid());
    flushout();
#endif

    g_pid = getpid();

    if(child_logger_ownership)
    {
        logger_take_ownership(getpid());
    }

    logger_start();

#if DNSCORE_HAS_LOG_THREAD_TAG
    thread_tag_log_tags();
#endif

    log_debug("daemonize: start timer");

    dnscore_reset_timer();

    server_setup_daemon_reopen_console_fds();

#if DEBUG
    debug_osformatln(termout, "detaching from console");
    flushout();
#endif

    log_debug("daemonize: start thread pools");

    thread_pool_start_all();

    log_debug("daemonize: start services");

    service_start_all();

    /* Change the current working directory to the root so
     * we won't prevent file systems from being unmounted.
     */

#if DEBUG
    debug_println("daemonized");
    flushout();
#endif

    log_info("daemonized");

    logger_flush();

    return pid;
#else
    server_setup_daemon_reopen_console_fds();
    FreeConsole();
    return 0;
#endif
}

void server_setup_daemon_go() { server_setup_daemon_go_ex(true, true); }

/// @NOTE 20201105 edf -- pid_file_pathp must be a char** because of managed paths, it cannot be a const char** either

ya_result server_setup_env(pid_t *pidp, char **pid_file_pathp, uid_t uid, gid_t gid, uint32_t setup_flags)
{
    if((pid_file_pathp == NULL) || (*pid_file_pathp == NULL))
    {
        return UNEXPECTED_NULL_ARGUMENT_ERROR;
    }

#if __unix__
    ya_result return_code;

    if(setup_flags & SETUP_CORE_LIMITS)
    {
        struct rlimit core_limits = {RLIM_INFINITY, RLIM_INFINITY};

        if(setrlimit(RLIMIT_CORE, &core_limits) < 0)
        {
            ttylog_err("unable to set core dump limit: %r", ERRNO_ERROR);
        }
#if DEBUG
        else
        {
            log_debug("core no-limit set");
        }
#endif
    }

    if(setup_flags & SETUP_ROOT_CHANGE)
    {
        log_info("going to jail");

        if(FAIL(return_code = chroot_jail())) /* Chroot to new path */
        {
            log_err("failed to jail: %r", return_code);
            return return_code;
        }
    }

    if(setup_flags & SETUP_ID_CHANGE)
    {
        /* Change uid and gid */

        if(FAIL(return_code = identity_change(uid, gid)))
        {
            ttylog_err("unable to change identity: %r", return_code);

            return return_code;
        }
    }

    if(setup_flags & SETUP_CREATE_PID_FILE)
    {
        /* Setup environment */

        if(FAIL(return_code = pid_file_create(*pid_file_pathp, pidp, uid, gid)))
        {
            ttylog_err("unable to create pid file '%s': %r", *pid_file_pathp, return_code);

            return return_code;
        }
    }
#endif
    return SUCCESS;
}

/** @} */
