//	utf-8

/*	Copyright  (c)	Günter Woigk   2001-2003
  					mailto:kio@little-bat.de

	This file is free software

 	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.

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission. The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.

	2003-08-08 kio	enhanced SysLoad() for all (?) platforms
	2003-08-09 kio	ExecCmd()
*/


#define	SAFE	3
#define	LOG		1

#include	"config.h"
#include	<sys/time.h>
#include	<unistd.h>
#include	<sys/param.h>
#include	<sys/resource.h>
#include	<stdlib.h>
#include	"os_utilities.h"
#if defined(_BSD)
#include	<machine/vmparam.h>
#endif
#if !defined(_SOLARIS)
#include	<sys/sysctl.h>
#include	<netdb.h>
#endif
#if defined(_LINUX)
//#include	<linux/timex.h>
#endif
#include	<sys/wait.h>


extern char **environ;		// required by: Mac OSX 


/* ====	date&time =============================================
		note: time_t is always in UTC (i hope)
*/

inline bool IsDigit ( char a )
{
	return (a>='0'&&a<='9');
}

static uint NextNumber ( cuptr& c, cuptr e )
{
	uint  n = 0;
	while ( c<e && !IsDigit(*c) ) { c++; }
	while ( c<e && IsDigit(*c) ) n = n*10 + *c++ -'0';
	return n;
}


cstr DateStr ( time_t secs )
{
	tm& dt = *localtime(&secs);
	return Using( "%04hd-%02hd-%02hd %02hd:%02hd:%02hd", dt.tm_year+1900, dt.tm_mon+1, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec );
}


cstr TimeStr ( time_t secs )
{	int s,m,h,d;

	s = secs;			if (s<=300)	return Using("%u sec.",          s);
	m = s/60; s = s%60; if (m<60)	return Using("%um:%us",        m,s);
	h = m/60; m = m%60;	if (h<24)	return Using("%uh:%um:%us",  h,m,s);
	d = h/24; h = h%24;				return Using("%ud:%uh:%um",d,h,m  );
}


time_t DateVal ( cstr date )
{	tm d;
	const uchar* c = (cuptr)date;
	const uchar* e = (cuptr)strchr(date,0);
	
	uint n = NextNumber(c,e);
	if (n<70) n+=2000; else if (n<100) n+=1900;
	d.tm_year = n-1900;
	
	d.tm_mon  = NextNumber(c,e) - 1;
	d.tm_mday = NextNumber(c,e);
	d.tm_hour = NextNumber(c,e);
	d.tm_min  = NextNumber(c,e);
	d.tm_sec  = NextNumber(c,e);

	d.tm_wday   = 0;
	d.tm_yday   = 0;
	d.tm_isdst  = 0;
#if !defined(_SOLARIS)
	d.tm_gmtoff = 0;
	d.tm_zone   = NULL;
#endif

	return mktime(&d);
}



/* ====	machine information ==================================
		lots can be accessed via sysctl() !!!
*/
cstr HostName ( )
{
	char s[MAXHOSTNAMELEN];
	gethostname(s,MAXHOSTNAMELEN);		// this should not fail...
	return DupStr(s);
}


void SysLoad ( double load[3] )			// ranges: 1, 5, and 15 minutes
{
	// 2003-08-08 kio: disabled MACOSX version. should work with version below too.
#if 0 && defined(VM_LOADAVG)
	// VM_LOADAVG is defined for: MACOSX
	// sets errno if this code is bollux
	int mib[2] = { CTL_VM, VM_LOADAVG };
	struct loadavg data;

	size_t size = sizeof(data);
	sysctl ( mib, NELEM(mib), &data, &size, NULL, 0 );

	load[0] = double(data.ldavg[0])/data.fscale;
	load[1] = double(data.ldavg[1])/data.fscale;
	load[2] = double(data.ldavg[2])/data.fscale;
#else
	// new: 2003-08-08 kio
	int n = getloadavg ( load, 3 );
	if(n!=3)
	{
		Log("   SysLoad(): getloadavg() returned %i\n",n);
		if(n<1)load[0]=1.0;
		if(n<2)load[1]=load[0];
		load[2]=load[1];
	}
#endif
}


time_t BootTime ( )
{
#ifndef KERN_BOOTTIME
	#ifdef _LINUX
		// Better way to do is use jiffies or get_cycles function
		// time being will read /proc/uptime
		FILE *fp;
		long ut;
		if((fp=fopen("/proc/uptime","r"))==NULL)
		{
			printf("Cannot open file /proc/uptime\n");
			return(-1);
		}
		fscanf(fp,"%ld",&ut);
		fclose(fp);
		return CurrentTime()-ut;
	#else
		// task ticked created 2002-05-10
		Log("\n*** os_utilities.cp:\n*** BootTime() not implemented for this platform.\n");
		return CurrentTime();
	#endif
#else
	int mib[2] = { CTL_KERN, KERN_BOOTTIME };
	struct timeval data;

	size_t size = sizeof(data);
	sysctl ( mib, NELEM(mib), &data, &size, NULL, 0 );
	
	return data.tv_sec;
#endif
}


time_t CurrentTime ( )
{
	struct timeval tv;
	gettimeofday ( &tv, NULL );
	return tv.tv_sec;
}


double FloatCurrentTime ( )
{
	struct timeval tv;
	gettimeofday ( &tv, NULL );
	return tv.tv_sec + tv.tv_usec/1000000.0;
}


time_t UpTime ( )
{
	return CurrentTime()-BootTime();
}



/* ==== environment information ===========================
*/
cstr GetUser ( )
{
	// ••• use get[e]uid() ? •••
	
	cstr s = getenv("USER");
	return s?s:"nobody";
}


cstr GetGroup ( )
{
	// use ••• get[e]gid() ? •••
	
	cstr s = getenv("GROUP");
	return s?s:"no group";
}



/* ----	execute external command ----------------------------
		argv[]  must be a 0-terminated list
		argv[0]	hard path to command
		envv[]  is either NULL or a 0-terminated list of environment variables
		        in case of NULL the current global environ[] list is passed
		returns stdout output of command called
				returned string must be delete[]ed
		returns NULL if exec failed
				then errno is set
*/
str ExecCmd ( str const argv[], str const envv[] )
{
	XXTRAP(!argv||!argv[0]||!*argv[0]);

	int mypipe[2];
	const int R=0, W=1;
	if ( pipe(mypipe) != 0 ) return NULL;

	pid_t child_id = fork();
	switch ( child_id )
	{
	case -1:
		{	// error happened:
			close(mypipe[R]);
			close(mypipe[W]);
			return NULL;
		}
	case 0:
		{	// child process:
			close(mypipe[R]);	// close unused fd
			close(1);			// close stdout
			dup(mypipe[W]);		// becomes lowest unused fileid: stdout
			close(mypipe[W]);	// close unused dup

			// call cmd:
			execve(argv[0],argv,envv?envv:(str const *)environ);

			// call failed. try path completion
			str path = getenv("PATH");
			if (path&&*argv[0]!='/')
			{
				XXTRAP( strchr(path,'=') != 0 );		// ist der name teil des strings?

				for(;;)
				{
					while(*path==':') path++;
					char* dp = strchr(path,':');
					if (!dp) dp = strchr(path,0);
					if(dp==path) break;						// end of env.PATH reached: finally no success

					str cmd = CatStr(SubStr(path,dp),"/",argv[0]);
					execve(cmd,argv,envv?envv:(str const *)environ);
					path = dp;
				}
			}
			ABORT(Using("\nExecCmd(%s) failed: %s\n",argv[0],ErrorStr()));	// abort child
  }
	default:
		{	// parent process
			close(mypipe[W]);	// close unused fd

			ssize_t bsize = 0;
			ssize_t bused = 0;
			char*   bu    = NULL;

			for(;;)
			{
				if (bused+100>bsize)
				{
					bsize += bsize/8 + 4000;
					char* newbu = NewStr(bsize);
					memcpy ( newbu,bu,bused );
					delete[] bu;
					bu = newbu;
				}

				ssize_t n = read ( mypipe[R], bu+bused, bsize-bused-1 ); //Log("[%i]",(int)n);

				if(n>0)		  { bused += n; }
				else if(n==0) { if(waitpid(child_id,NULL,0)==child_id) break; }
				else		  { if (Error()!=EINTR&&Error()!=EAGAIN) break; }
			}

			close(mypipe[R]);	// close fd
			bu[bused] = 0;
			return bu;
		}
	}
}

str ExecCmd ( cstr cmd, ... )
{
	cstr* argv = new cstr[100];
	argv[0] = cmd;
	int argc = 1;

	va_list va;
	va_start(va,cmd);

	cstr arg;
	do
	{
		if ((argc%100)==0)
		{
			cstr* newargv = new cstr[argc+100];
			memcpy(newargv,argv,argc*sizeof(cstr));
			delete[]argv;
			argv=newargv;
		}

		arg = va_arg(va,cstr);
		argv[argc++] = arg;
	}
	while (arg);

	va_end(va);

	str result = ExecCmd((char*const*)argv);
	delete[]argv;
	return result;
}












