#include <util/generic/yexception.h>

#include <cerrno>
#include <cstdlib>
#include <util/system/info.h>

#if defined(_win_)
    #include <io.h>
#else
    #include <sys/wait.h>
    #include <unistd.h>
    #include <fcntl.h>
#endif

#include "daemon.h"

#ifdef _unix_
using namespace NDaemonMaker;

static bool Fork(EParent parent) {
    pid_t pid = fork();

    if (pid > 0) {
        int status = 0;
        while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
        }
        if (parent == callExitFromParent) {
            _exit(0);
        } else {
            return true;
        }
    } else if (pid < 0) {
        ythrow TSystemError() << "Cannot fork";
    }

    if (setsid() < 0) {
        ythrow TSystemError() << "Cannot setsid";
    }

    pid = fork();

    if (pid > 0) {
        _exit(0);
    } else if (pid < 0) {
        ythrow TSystemError() << "Cannot second fork";
    }
    return false;
}

#endif

static void CloseFromToExcept(int from, int to, const int* except) {
    (void)from;
    (void)to;
    (void)except;

#ifdef _unix_
    int mfd = NSystemInfo::MaxOpenFiles();
    for (int s = from; s < mfd && (to == -1 || s < to); s++) {
        for (const int* ex = except; *ex >= 0; ++ex) {
            if (s == *ex) {
                goto dontclose;
            }
        }
        while (close(s) == -1) {
            if (errno == EBADF) {
                break;
            }
            if (errno != EINTR) {
                ythrow TSystemError() << "close(" << s << ") failed";
            }
        }
    dontclose:;
    }
#endif /* _unix_ */
}

bool NDaemonMaker::MakeMeDaemon(ECloseDescriptors cd, EStdIoDescriptors iod, EChDir chd, EParent parent) {
    (void)cd;
    (void)iod;
    (void)chd;

#ifdef _unix_
    if (Fork(parent)) {
        return true;
    }

    if (chd == chdirRoot) {
        if (chdir("/")) {
            ythrow TSystemError() << "chdir(\"/\") failed";
        }
    }

    int fd[4] = {-1, -1, -1, -1};
    switch (iod) {
        case openYandexStd:
            fd[0] = open("yandex.stdin", O_RDONLY);
            if (fd[0] < 0) {
                ythrow TSystemError() << "Cannot open 'yandex.stdin'";
            }
            fd[1] = open("yandex.stdout", O_WRONLY | O_APPEND | O_CREAT, 660);
            if (fd[1] < 0) {
                ythrow TSystemError() << "Cannot open 'yandex.stdout'";
            }
            fd[2] = open("yandex.stderr", O_WRONLY | O_APPEND | O_CREAT, 660);
            if (fd[2] < 0) {
                ythrow TSystemError() << "Cannot open 'yandex.stderr'";
            }
            break;
        case openDevNull:
            fd[0] = open("/dev/null", O_RDWR, 0);
            break;
        case openNone:
            break;
        default:
            ythrow yexception() << "Unknown open descriptors mode: " << (int)iod;
    }

    const int except[4] = {
        fd[0],
        fd[1],
        fd[2],
        -1};
    if (closeAll == cd) {
        CloseFromToExcept(0, -1, except);
    } else if (closeStdIoOnly == cd) {
        CloseFromToExcept(0, 3, except);
    } else {
        ythrow yexception() << "Unknown close descriptors mode: " << (int)cd;
    }

    switch (iod) {
        case openYandexStd:
            /* Assuming that open(2) acquires fds in order. */
            dup2(fd[0], STDIN_FILENO);
            if (fd[0] > 2) {
                close(fd[0]);
            }
            dup2(fd[1], STDOUT_FILENO);
            if (fd[1] > 2) {
                close(fd[1]);
            }
            dup2(fd[2], STDERR_FILENO);
            if (fd[2] > 2) {
                close(fd[2]);
            }
            break;
        case openDevNull:
            dup2(fd[0], STDIN_FILENO);
            dup2(fd[0], STDOUT_FILENO);
            dup2(fd[0], STDERR_FILENO);
            if (fd[0] > 2) {
                close(fd[0]);
            }
            break;
        default:
            break;
    }
    return false;
#else
    return true;
#endif
}

void NDaemonMaker::CloseFrom(int fd) {
    static const int except[1] = {-1};
    CloseFromToExcept(fd, -1, except);
}