// Copyright (C) 1999-2005 Open Source Telecom Corporation.
//
// 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 <cc++/config.h>
#include <cc++/export.h>
#include <cc++/thread.h>
#include <cc++/process.h>
#include <cc++/strchar.h>

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

#ifdef	MACOSX
#undef	_POSIX_PRIORITY_SCHEDULING
#endif

#ifndef	WIN32
#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	WIN32
#include <process.h>
#include <stdio.h>
#endif

#ifdef	CCXX_NAMESPACES
namespace ost {
#endif

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

#ifdef	WIN32

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 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_PREFIX;
#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;

	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)
	{
		endgrent();
		return false;
	}

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

	endgrent();
	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 = vfork();
	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;
        int fd;

        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)
        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;

	sched_getparam(0, &p);

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

#ifdef	SCHED_RR
		if(!stricmp(pol, "rr"))
			policy = SCHED_RR;
#endif
#if !defined(SCHED_RR) && defined(SCHED_FIFO)
		if(!stricmp(pol, "rr"))
			policy = SCHED_FIFO;
#endif
#ifdef	SCHED_FIFO
		if(!stricmp(pol, "fifo"))
			policy = SCHED_FIFO;
#endif
#ifdef	SCHED_TS
		if(!stricmp(pol, "ts"))
			policy = SCHED_TS;
#endif	
#ifdef	SCHED_OTHER
		if(!stricmp(pol, "other"))
			policy = SCHED_OTHER;
#endif					
	}
	else
		policy = sched_getscheduler(0);
		
	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;
	
	sched_setscheduler(0, policy, &p);
#endif
}


void Process::setPriority(int pri)
{
#ifdef  _POSIX_PRIORITY_SCHEDULING
	struct sched_param p;
	int policy = sched_getscheduler(0);
	int min = sched_get_priority_min(policy);
	int max = sched_get_priority_max(policy);

	sched_getparam(0, &p);
	
	if(pri < min)
		pri = min;
	if(pri > max)
		pri = max;
	p.sched_priority = pri;
	sched_setparam(0, &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	// not win32

#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	CCXX_NAMESPACES
}
#endif

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 8
 * End:
 */
