// Copyright (C) 1999-2005 Open Source Telecom Corporation.
// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015-2020 Cherokees of Idaho.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++.  If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

#include <ucommon-config.h>
#include <commoncpp/config.h>
#include <commoncpp/export.h>
#include <commoncpp/thread.h>
#include <commoncpp/process.h>

#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <csignal>

#ifdef  HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef  MACOSX
#undef  _POSIX_PRIORITY_SCHEDULING
#endif

#ifndef _MSWINDOWS_
#ifdef  HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef  HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <pwd.h>
#include <grp.h>

#ifdef  SIGTSTP
#include <sys/file.h>
#include <sys/ioctl.h>
#endif

#ifndef _PATH_TTY
#define _PATH_TTY "/dev/tty"
#endif
#endif

#ifdef  _MSWINDOWS_
#include <process.h>
#include <stdio.h>
#endif

namespace ost {

bool Process::rtflag = false;

#ifdef  _MSWINDOWS_

static SYSTEM_INFO sysinfo;
static LPSYSTEM_INFO lpSysInfo = NULL;

static void init_sysinfo(void)
{
    if(!lpSysInfo) {
        lpSysInfo = &sysinfo;
        memset(&sysinfo, 0, sizeof(sysinfo));
        GetSystemInfo(lpSysInfo);
    }
}

const char *Process::getUser(void)
{
    static char userid[65];
    DWORD length = sizeof(userid);

    if(GetUserName(userid, &length))
        return userid;

    return NULL;
}

size_t Process::getPageSize(void)
{
    init_sysinfo();
    return (size_t) lpSysInfo->dwPageSize;
}

int Process::spawn(const char *exename, const char **args, bool wait)
{
    int mode = P_NOWAIT;

    if(wait)
            mode = P_WAIT;

    return (int)::spawnvp(mode, (char *)exename, (char **)args);
}

int Process::join(int pid)
{
    int status, result;

    if(pid == -1)
        return pid;

    result = (int)cwait(&status, pid, WAIT_CHILD);
    if(status & 0x0)
        return -1;
    return result;
}

bool Process::cancel(int pid, int sig)
{
    HANDLE hPid = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
    bool rtn = true;

    if(!hPid)
        return false;

    switch(sig) {
    case SIGABRT:
    case SIGTERM:
        if(!TerminateProcess(hPid, -1))
            rtn = false;
    case 0:
        break;
    default:
        rtn = false;
    }
    CloseHandle(hPid);
    return rtn;
}

void Process::setPriority(int pri)
{
    DWORD pc = NORMAL_PRIORITY_CLASS;
    DWORD pid = GetCurrentProcessId();
    HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, TRUE, pid);

#ifdef  BELOW_NORMAL_PRIORITY_CLASS
    if(pri == -1)
        pc = BELOW_NORMAL_PRIORITY_CLASS;
    else if(pri == 1)
        pc = ABOVE_NORMAL_PRIORITY_CLASS;
    else
#endif
    if(pri == 2)
        pc = HIGH_PRIORITY_CLASS;
    else if(pri > 2)
        pc = REALTIME_PRIORITY_CLASS;
    else if(pri < -1)
        pc = IDLE_PRIORITY_CLASS;

    SetPriorityClass(hProcess, pc);
    CloseHandle(hProcess);
}

void Process::setScheduler(const char *cp)
{
    if(!stricmp(cp, "fifo"))
        setPriority(3);
    else if(!stricmp(cp, "rr"))
        setPriority(2);
    else if(!stricmp(cp, "idle"))
        setPriority(-2);
    else
        setPriority(0);
}

void Process::setRealtime(int pri)
{
    setPriority(3);
}

bool Process::isScheduler(void)
{
    return false;
}

#else

#ifndef WEXITSTATUS
#define WEXITSTATUS(status) ((unsigned)(status) >> 8)
#endif

#ifndef WIFEXITED
#define WIFEXITED(status) (((status) & 255) == 0)
#endif

#ifndef WTERMSIG
#define WTERMSIG(status) (((unsigned)(status)) & 0x7F)
#endif

#ifndef WIFSIGNALLED
#define WIFSIGNALLED(status) (((status) & 255) != 0)
#endif

#ifndef WCOREDUMP
#define WCOREDUMP(status) (((status) & 0x80) != 0)
#endif

static char *_pUser = NULL;
static char *_pHome = NULL;

static void lookup(void)
{
    struct passwd *pw = NULL;
#ifdef  HAVE_GETPWUID_R
    struct passwd pwd;
    char buffer[1024];

    ::getpwuid_r(geteuid(), &pwd, buffer, 1024, &pw);
#else
    pw = ::getpwuid(geteuid());
#endif

    if(_pHome)
        delString(_pHome);
    if(_pUser)
        delString(_pUser);

    _pUser = _pHome = NULL;

    if(pw != NULL && pw->pw_dir != NULL)
        _pHome = newString(pw->pw_dir);

    if(pw != NULL && pw->pw_name != NULL)
        _pUser = newString(pw->pw_name);

    endpwent();
}

const char *Process::getUser(void)
{
    if(!_pUser)
        lookup();

    return (const char *)_pUser;
}

const char *Process::getConfigDir(void)
{
#ifdef  ETC_CONFDIR
    return ETC_CONFDIR;
#else
    return "/etc";
#endif
}

const char *Process::getHomeDir(void)
{
    if(!_pHome)
        lookup();

    return (const char *)_pHome;
}

#ifdef  HAVE_GETPAGESIZE
size_t Process::getPageSize(void)
{
    return (size_t)getpagesize();
}

#else

size_t Process::getPageSize(void)
{
    return 1024;
}
#endif

bool Process::setUser(const char *id, bool grp)
{
    struct passwd *pw = NULL;
#ifdef  HAVE_GETPWNAM_R
    struct passwd pwd;
    char buffer[1024];

    ::getpwnam_r(id, &pwd, buffer, 1024, &pw);
#else
    pw = ::getpwnam(id);
#endif
    if(!pw)
        return false;

#ifdef  HAVE_SETGROUPS
    setgroups(0, NULL);
#endif

    if(grp)
        if(setgid(pw->pw_gid))
            return false;

    if(setuid(pw->pw_uid))
        return false;

    lookup();
    return true;
}

bool Process::setGroup(const char *id)
{
    struct group *group = NULL;
#ifdef  HAVE_GETGRNAM_R
    struct group grp;
    char buffer[2048];

    ::getgrnam_r(id, &grp, buffer, 1024, &group);
#else
    group = ::getgrnam(id);
#endif
    if(!group) {
#if HAVE_ENDGRENT
        endgrent();
#endif
        return false;
    }

#ifdef  HAVE_SETEGID
    setegid(group->gr_gid);
#endif
    if(setgid(group->gr_gid)) {
#if HAVE_ENDGRENT
        endgrent();
#endif
        return false;
    }

#if HAVE_ENDGRENT
    endgrent();
#endif
    return true;
}

bool Process::cancel(int pid, int sig)
{
    if(!sig)
        sig = SIGTERM;

    if(pid < 1)
        return false;

    if(::kill(pid, sig))
        return false;

    return true;
}

int Process::join(int pid)
{
    int status;

    if(pid < 1)
        return -1;

#ifdef  HAVE_WAITPID
    waitpid(pid, &status, 0);
#else
#ifdef  HAVE_WAIT4
    wait4(pid, &status, 0, NULL);
#else
    int result;
    while((result = ::wait(&status)) != pid && result != -1)
        ;
#endif
#endif

    if(WIFEXITED(status))
        return WEXITSTATUS(status);
    else if(WIFSIGNALLED(status))
        return -WTERMSIG(status);
    else
        return -1;
}

int Process::spawn(const char *exename, const char **args, bool wait)
{
    int pid;

    pid = fork();
    if(pid == -1)
        return -1;

    if(!pid) {
        execvp((char *)exename, (char **)args);
        _exit(-1);
    }

    if(!wait)
        return pid;

    return join(pid);
}

Process::Trap Process::setInterruptSignal(int signo, Trap func)
{
    struct  sigaction   sig_act, old_act;

    memset(&sig_act, 0, sizeof(sig_act));
    sig_act.sa_handler = func;
    sigemptyset(&sig_act.sa_mask);
    if(signo != SIGALRM)
        sigaddset(&sig_act.sa_mask, SIGALRM);

    sig_act.sa_flags = 0;
#ifdef  SA_INTERRUPT
    sig_act.sa_flags |= SA_INTERRUPT;
#endif
    if(sigaction(signo, &sig_act, &old_act) < 0)
        return SIG_ERR;

    return old_act.sa_handler;
}

Process::Trap Process::setPosixSignal(int signo, Trap func)
{
    struct  sigaction   sig_act, old_act;

    memset(&sig_act, 0, sizeof(sig_act));
    sig_act.sa_handler = func;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = 0;
    if(signo == SIGALRM) {
#ifdef  SA_INTERRUPT
        sig_act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else {
        sigaddset(&sig_act.sa_mask, SIGALRM);
#ifdef  SA_RESTART
        sig_act.sa_flags |= SA_RESTART;
#endif
    }
    if(sigaction(signo, &sig_act, &old_act) < 0)
        return SIG_ERR;
    return old_act.sa_handler;
}

void    Process::detach(void)
{
    attach("/dev/null");
}

void    Process::attach(const char *dev)
{
    int pid;

    if(getppid() == 1)
        return;

    ::close(0);
    ::close(1);
    ::close(2);

#ifdef  SIGTTOU
    setPosixSignal(SIGTTOU, SIG_IGN);
#endif

#ifdef  SIGTTIN
    setPosixSignal(SIGTTIN, SIG_IGN);
#endif

#ifdef  SIGTSTP
    setPosixSignal(SIGTSTP, SIG_IGN);
#endif

    if((pid = fork()) < 0)
        THROW(pid);
    else if(pid > 0)
        exit(0);


#if defined(SIGTSTP) && defined(TIOCNOTTY)
    int fd;
    if(setpgid(0, getpid()) == -1)
        THROW(-1);

    if((fd = open(_PATH_TTY, O_RDWR)) >= 0) {
        ioctl(fd, TIOCNOTTY, NULL);
        close(fd);
    }
#else

#ifdef  HAVE_SETPGRP
    if(setpgrp() == -1)
        THROW(-1);
#else
    if(setpgid(0, getpid()) == -1)
        THROW(-1);
#endif

    setPosixSignal(SIGHUP, SIG_IGN);

    if((pid = fork()) < 0)
        THROW(-1);
    else if(pid > 0)
        exit(0);
#endif

    if(dev && *dev) {
        ::open(dev, O_RDWR);
        ::open(dev, O_RDWR);
        ::open(dev, O_RDWR);
    }
}

void Process::setScheduler(const char *pol)
{
#ifdef  _POSIX_PRIORITY_SCHEDULING
    struct sched_param p;
    int policy, orig;
    pthread_t ptid = pthread_self();

    if(pthread_getschedparam(ptid, &policy, &p))
        return;

    orig = policy;
    if(pol) {
#if defined(SCHED_TS)
        policy = SCHED_TS;
#elif defined(SCHED_OTHER)
        policy = SCHED_OTHER;
#else
        policy = 0;
#endif

#ifdef  SCHED_RR
        if(ucommon::eq_case(pol, "rr"))
            policy = SCHED_RR;
#endif
#if !defined(SCHED_RR) && defined(SCHED_FIFO)
        if(ucommon::eq_case(pol, "rr"))
            policy = SCHED_FIFO;
#endif
#ifdef  SCHED_FIFO
        if(ucommon::eq_case(pol, "fifo")) {
            rtflag = true;
            policy = SCHED_FIFO;
        }
#endif
#ifdef  SCHED_TS
        if(ucommon::eq_case(pol, "ts"))
            policy = SCHED_TS;
#endif
#ifdef  SCHED_OTHER
        if(ucommon::eq_case(pol, "other"))
            policy = SCHED_OTHER;
#endif
    }
    else
        policy = orig;

    int min = sched_get_priority_min(policy);
    int max = sched_get_priority_max(policy);

    if(p.sched_priority < min)
        p.sched_priority = min;
    else if(p.sched_priority > max)
        p.sched_priority = max;

    pthread_setschedparam(ptid, policy, &p);
#endif
}

void Process::setPriority(int pri)
{
#ifdef  _POSIX_PRIORITY_SCHEDULING
    struct sched_param p;
    int policy;
    pthread_t ptid = pthread_self();

    pthread_getschedparam(ptid, &policy, &p);

    int min = sched_get_priority_min(policy);
    int max = sched_get_priority_max(policy);

    if(pri < min)
        pri = min;
    if(pri > max)
        pri = max;
    p.sched_priority = pri;
    pthread_setschedparam(ptid, policy, &p);
#else
    if(pri < -20)
        pri = -20;
    if(pri > 20)
        pri = 20;
    nice(-pri);
#endif
}

bool Process::isScheduler(void)
{
#ifdef  _POSIX_PRIORITY_SCHEDULING
    return true;
#else
    return false;
#endif
}

void Process::setRealtime(int pri)
{
    if(pri < 1)
        pri = 1;

    setScheduler("rr");
    setPriority(pri);
}

#endif

#ifdef  _OSF_SOURCE
#undef  HAVE_SETENV
#endif

void Process::setEnv(const char *name, const char *value, bool overwrite)
{
#ifdef  HAVE_SETENV
    ::setenv(name, value, (int)overwrite);
#else
    char strbuf[256];

    snprintf(strbuf, sizeof(strbuf), "%s=%s", name, value);
    if(!overwrite)
        if(getenv(strbuf))
            return;

    ::putenv(strdup(strbuf));
#endif
}

const char *Process::getEnv(const char *name)
{
    return ::getenv(name);
}

#if defined(HAVE_MLOCKALL) && defined(MCL_FUTURE)

#include <sys/mman.h>

bool Process::lock(bool future)
{
    int rc;

    if(future)
        rc = mlockall(MCL_CURRENT | MCL_FUTURE);
    else
        rc = mlockall(MCL_CURRENT);
    if(rc)
        return false;

    return true;
}

void Process::unlock(void)
{
    munlockall();
}
#else

bool Process::lock(bool future)
{
    return false;
}

void Process::unlock(void)
{
}

#endif

#ifdef  _MSWINDOWS_

Lockfile::Lockfile()
{
    _mutex = INVALID_HANDLE_VALUE;
    _flagged = false;
}

Lockfile::Lockfile(const char *name)
{
    _mutex = INVALID_HANDLE_VALUE;
    _flagged = false;
    lock(name);
}

bool Lockfile::lock(const char *name)
{
    char mname[65];
    char *ext = strrchr((char *)name, '/');

    if(ext)
        name = ++ext;

    unlock();
    snprintf(mname, sizeof(mname) - 4, "_lock_%s", name);
    ext = strrchr(mname, '.');
    if(ext && !stricmp(ext, ".lock")) {
        *ext = 0;
        ext = NULL;
    }
    if(!ext)
        addString(mname, sizeof(mname), ".lck");
    _mutex = CreateMutex(NULL, FALSE, mname);
    if (!_mutex || _mutex == INVALID_HANDLE_VALUE)
        return false;
    if(WaitForSingleObject(_mutex, 200) == WAIT_OBJECT_0)
        _flagged = true;
    return _flagged;
}

void Lockfile::unlock(void)
{
    if(_mutex == INVALID_HANDLE_VALUE)
        return;

    if(_flagged)
        ReleaseMutex(_mutex);
    CloseHandle(_mutex);
    _flagged = false;
    _mutex = INVALID_HANDLE_VALUE;
}

bool Lockfile::isLocked(void)
{
    return _flagged;
}

#else
Lockfile::Lockfile()
{
    _path = NULL;
}

Lockfile::Lockfile(const char *name)
{
    _path = NULL;
    lock(name);
}

bool Lockfile::lock(const char *name)
{
    struct stat ino;
    int fd, pid, status;
    const char *ext;
    char buffer[128];
    bool rtn = true;
    size_t size;

    unlock();

    ext = strrchr(name, '/');
    if(ext)
        ext = strrchr(ext, '.');
    else
        ext = strrchr(name, '.');

    if(strchr(name, '/')) {
        size = strlen(name) + 1;
        _path = new char[size];
        String::set(_path, size, name);
    }
    else if(ext && !strcmp(ext, ".pid")) {
        if(stat("/var/run", &ino))
            snprintf(buffer, sizeof(buffer), "/tmp/.%s", name);
        else
            snprintf(buffer, sizeof(buffer), "/var/run/%s", name);
        size = strlen(buffer) + 1;
        _path = new char[size];
        String::set(_path, size, buffer);
    }
    else {
        if(!ext)
            ext = ".lock";
        if(stat("/var/lock", &ino))
            snprintf(buffer, sizeof(buffer), "/tmp/.%s%s", name, ext);
        else
            snprintf(buffer, sizeof(buffer), "/var/lock/%s%s", name, ext);

        size = strlen(buffer) + 1;
        _path = new char[size];
        String::set(_path, size, buffer);
    }

    for(;;) {
        fd = ::open(_path, O_WRONLY | O_CREAT | O_EXCL, 0660);
        if(fd >= 0) {
            pid = getpid();
            snprintf(buffer, sizeof(buffer), "%d\n", pid);
            if(::write(fd, buffer, strlen(buffer)))
                rtn = false;
            ::close(fd);
            return rtn;
        }
        if(fd < 0 && errno != EEXIST) {
            delete[] _path;
            return false;
        }

        fd = ::open(_path, O_RDONLY);
        if(fd < 0) {
            if(errno == ENOENT)
                continue;
            delete[] _path;
            return false;
        }

        Thread::sleep(2000);
        status = ::read(fd, buffer, sizeof(buffer) - 1);
        if(status < 1) {
            // ensure that buffer is null-terminated before continuing
            buffer[sizeof(buffer) - 1] = '\0';
            ::close(fd);
            continue;
        }

        buffer[status] = 0;
        pid = atoi(buffer);
        if(pid) {
            if(pid == getpid()) {
                status = -1;
                errno = 0;
            }
            else
                status = kill(pid, 0);

            if(!status || (errno == EPERM)) {
                ::close(fd);
                delete[] _path;
                return false;
            }
        }
        ::close(fd);
        ::unlink(_path);
    }
}

void Lockfile::unlock(void)
{
    if(_path) {
        remove(_path);
        delete[] _path;
        _path = NULL;
    }
}

bool Lockfile::isLocked(void)
{
    if(_path)
        return true;

    return false;
}

#endif

} // namespace ost
