/* @(#)osh.c	19.1 (ES0-DMD) 02/25/03 13:56:12 */
/*===========================================================================
  Copyright (C) 1995 European Southern Observatory (ESO)
 
  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., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.TYPE        Module
.NAME        osh
.LANGUAGE    C
.AUTHOR      IPG-ESO Garching
.CATEGORY    Host operating system interfaces. Host services.
.ENVIRONMENT	VAX/VMS Version
.COMMENTS    
	Provide miscellaneous host services. 
	The routines return time and date stamps,
	return cpu time used by the current process, 
	translate logicals (environment)
	executes host commands.
	Time resolution is host dependent parameter.
	The routines return always
	a non-negative integer number on successful return.
	Otherwise, a value of -1 is set to indicate an error
	condition and the variable ``oserror'' contains the 
	symbolic error code.
.VERSION  0.0 25-Aug-1986   Definition.       J. D. Ponz
.VERSION  1.8 10-Feb-1988   VMS Version. F. ochsenbein. 
				Difference oshgmt / oshtime
.VERSION  1.9 05-Sep-1989	Changed some `unclever' macros...
.VERSION  2.0 08-Aug-1990	Added oshtm / oshtl (time conversions)
------------------------------------------------------------*/

#define DEBUG	0

#include <osdefos.h>
#include <macrogen.h>
#ifdef EOF
#undef EOF
#endif

MID_EXTERN int oserror;
MID_EXTERN int vmserror;

#include <time.h>	/* System	*/
#include <errno.h>	/* System	*/
#include <ssdef.h>	/* System	*/
#include <descrip.h>	/* System	*/
#include <lnmdef.h>	/* System	*/
#define stime	long
#define TR_MAX	LNM$C_NAMLENGTH		/* Maximal length translation */
#define len	dsc$w_length
#define s	dsc$a_pointer

typedef struct dsc$descriptor descript;
#define void_string	{0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL_PTR(char)}   
#define InitDescript(text) 	\
			{sizeof(text)-1, DSC$K_DTYPE_T, DSC$K_CLASS_S,text}   

#define SET_STRING(dsc,str)	dsc.s = str, dsc.len = strlen(str) \
				/* Set address & length of a string    */
	struct item {		/* For TRNLNM system service	*/
		unsigned short int len;
		unsigned short int item_code;
		void *addr;
		void *return_len;
	};
static long returned_len = 0;

static char tr_buf[TR_MAX+1] = "";

#define FINISH		goto FIN
#define RETURN(x)	FIN: return (oserror ? -1 : x)

/*==========================================================================
 *		Logical Name Translations
 *==========================================================================*/

/*==========================================================================*/
int oshset(logname, translation)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE
    	Inserts a logical name in the Environment.
.RETURNS 0 (success) / -1 (failure)
.REMARKS 
------------------------------------------------------------*/
	char *logname;	/* IN : logical name */
	char *translation;	/* IN : Equivalence String */
{
	static descript sdv_table = InitDescript("LNM$PROCESS_TABLE");
	static descript sdv_in = void_string;
	static struct {
		struct item Item;		/* Equivalence string */
		long end;
	   }     item_eq = {
		{0, LNM$_STRING, 0, &returned_len},
		 0 };

  oserror = 0;

  SET_STRING(sdv_in, logname);
  item_eq.Item.addr = translation, item_eq.Item.len = strlen(translation);

  vmserror = SYS$CRELNM(0,&sdv_table,&sdv_in,0,&item_eq);
  if ((vmserror&1) == 0)	{ oserror = ENXIO; FINISH;}

  FIN:
  return(oserror ? -1 : 0);
}

/*==========================================================================*/
char *oshenv(logname, table)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE
    Translate logical names into physical names., according to a table.
.RETURNS Address of equivalence name / NULL when logname not found 
.REMARKS table is used only in VMS environment (default is LNM$FILE_DEV).
	Translation is not case-dependent on VMS. Use recursive translation.
------------------------------------------------------------*/
	char *logname;	/* IN : logical name */
	char *table;	/* IN : name of the table to look for (VMS only) */
{
	static unsigned int lnmlength = 0; 
	static unsigned int attr_case = LNM$M_CASE_BLIND; 
	static unsigned int attr_list = 0;
	static descript sdv_table = void_string;
	static descript sdv_in    = void_string;
	static descript	sdv_out   = InitDescript(tr_buf);
	static struct {
		struct item Item;		/* Equivalence string */
		long end;
	   }  	item_eq = {
		{TR_MAX, LNM$_STRING, tr_buf, &sdv_out},
		 0 };
	static struct {
		struct item Item;		/* Attributes of logical */
		long end;
	  }	item_attr = {
		{sizeof(attr_list), LNM$_ATTRIBUTES, &attr_list, &lnmlength},
		 0 };
	register char *p;

  oserror = 0;

  SET_STRING(sdv_in, logname);
  p = (table ? table : "LNM$FILE_DEV");
  p = (*p  ? p : "LNM$FILE_DEV");	/* In case of void string */
  SET_STRING(sdv_table, p);		/* Write info as descriptors	*/
  p = logname;

  vmserror = SYS$TRNLNM(&attr_case,&sdv_table,&sdv_in,0,&item_attr);
  if (vmserror != SS$_NORMAL)	{ oserror = ENXIO; FINISH;}

		/* Continue Translations while a translation exists */
  while (attr_list & (LNM$M_EXISTS))
  {	SYS$TRNLNM(&attr_case,&sdv_table,&sdv_in,0,&item_eq);
	tr_buf[sdv_out.len] = EOS;	/* Append the EOS character	*/
	p = tr_buf;
	if (*p == 033)	p += 4;		/* Terminal translation		*/
	if (tr_buf[--(sdv_out.len)] != ':')	(sdv_out.len)++;
	sdv_in.s = sdv_out.s; sdv_in.len = sdv_out.len;
	if (SYS$TRNLNM(&attr_case,&sdv_table,&sdv_in,0,&item_attr) 
		!= SS$_NORMAL) 					FINISH;
  	if (attr_list & (LNM$M_CONCEALED | LNM$M_TERMINAL))	FINISH;
  }

  FIN:
  return(oserror ? NULL_PTR(char) : p);
}

#if 0
/*==========================================================================*/
int oshtr(logname, phname, size)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE
    Translate logical names into physical names. i.e. $KEYS ==> myfile.key
.RETURNS Value 0 on normal return, -1 when logname not found (phname then
	contains an empty string)
.REMARKS Simply use oshenv.
------------------------------------------------------------*/
	char *logname;			/* IN : logical name */
	char *phname;			/* OUT : logical physical name */
	int  size;			/* IN : size of phname	*/
{
	register char *p, *q;

  if (size < 2)		{ oserror = EINVAL; FINISH; }	/* Output too small */
  *phname = EOS;

  p = oshenv(logname, NULL_PTR(char));
  if (!p)			FINISH;		/* No translation... */
  if (strlen(p) >= (size-1))			/* Translation is too long */
  	{ oserror = ERANGE; FINISH;}

  for (q=phname; *p; p++, q++) 	*q = *p;
  *q = EOS;

  return (oserror ? -1 : 0);
}
#endif

/*==========================================================================
 *		Date / Time Services
 *==========================================================================*/

/*======================================================================*/
static long vmstime(q)
/*+++++++++++++ 
.PURPOSE	Converts the VAX time to standard
.RETURNS	Time as seconds elapsed since Jan. 1, 1970
.REMARKS	The VAX time is 64 bits; origin = Nov. 17, 1858; units = 100ns.
		(40587 days before Jan 1, 1970)
--------------*/
	unsigned long *q;	/* IN: VAX time	*/
{
	register double tt;
	register long    t;

  tt = q[1] * 4294967296e0;
  tt = tt + q[0];
  tt = tt / 1.e7 - (40587.e0 * 86400.e0);

  t = tt;
  
  return(t);
}

/*==========================================================================*/
long  oshtime() 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieves current (local!) time in seconds elapsed since Jan. 1, 1970, 0h.
.RETURNS The time in seconds.
.REMARKS 
------------------------------------------------------------*/
{
	static long qtime[2];
	
  SYS$GETTIM(qtime);

  return(vmstime(qtime));

}

#define Jan1_1970	135140L	/* Date in 400-yr cycle */
#define Jan1_1980	138792L	/* Date in 400-yr cycle */
#define Jan1_2000	0L	/* Date in 400-yr cycle */

long  oshtl(T) 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Convert time structure into seconds since Jan. 1, 1970
.RETURNS The time in seconds.
.REMARKS Computation is done here, system call seems to be non-existent...
------------------------------------------------------------*/
	struct tm *T;	/* IN: Time structure to convert	*/
{
	register long int j, y, t;

				/* Compute Day of 400 yr cycle 	*/
  y = T->tm_year;
  if (y < 1000)	y += 1900;
  y %= 400;
  y += 400;			/* To be sure j is positive	*/
  j = y - (11 - T->tm_mon)/10;
  t = ((1461L*j)/4 + (306L * ((T->tm_mon+10)%12) + 5)/10
	- (3* ((j + 100L)/100L))/4 + T->tm_mday + 59L) % 146097L;
				/* Compute Week day	*/
  T->tm_wday = (t+6L)%7L;

  t -= Jan1_1970;		/* Origin = Jan 1, 1970		*/
  if (t < -24855)	t += 146097L;
  if (t >  24855)	t -= 146097L;


				/* Transform Day into seconds	*/
  if (t < -24855)	t = -24855;
  if (t >  24855)	t =  24855;
  t = t * 86400L + T->tm_hour * 3600L + T->tm_min * 60L + T->tm_sec;
  return(t);
}

struct tm *oshtm(clock) 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Convert time (long) as time structure
.RETURNS Equivalent structure with filled items
.REMARKS VMS localtime is completely wrong for dates < Jan 1, 1970 !!!
------------------------------------------------------------*/
	long	clock;	/* IN: Time as seconds since Jan. 1, 1970 */
{
	long t, days, y;
	static struct tm T;
	static unsigned char month[12]={31,28,31,30,31,30,31,31,30,31,30,31};

  t = clock;
  days	  = t/86400L;
			
  if (t < 0)	days--;
  t -= (days*86400L);
  days += (t/86400L); 	t %= 86400L;
  T.tm_sec  = t%60;	t /= 60;
  T.tm_min  = t%60;	
  T.tm_hour = t/60;	
	
				/* Compute Week day	*/
  T.tm_wday = (days+Jan1_1970+6L)%7L;

  days   += (70L*365L + 18L);		/* Offset 1900	*/
  y       = (4L*days)/1461L;
  T.tm_year = y;
  T.tm_yday = days - 365L*y - (y+3)/4;

					/* Compute mon + mday from yday	*/

  month[1] = (y&3 ? 28 : 29);

  for (y=T.tm_yday, t=0; y>=month[t]; y -= month[t], t++)	;
  T.tm_mon  = t;
  T.tm_mday = y+1;

  return(&T);
}

/*==========================================================================*/
long  oshgmt() 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieves GMT time in seconds elapsed since Jan. 1, 1970, 0h.
.RETURNS The time in seconds.
.REMARKS 
------------------------------------------------------------*/
{
	static long qtime[2] ;
	static int TU = 0;
	register char *p;
	char *oshenv();
	
  p = oshenv("TIMEZONE", NULL_PTR(char));
  if (p)	TU = atoi(p);			/* Get GMT difference */
  SYS$GETTIM(qtime);

  return(vmstime(qtime) - TU*60);
}

#if 0
/*==========================================================================*/
long  oshcpu(op, ftime) 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Gets CPU time. Precision is : hundreds of seconds.
	 Cpu time returned is the sum of the time spend for both system
	 tasks and process itself needed for the completion of the task.
.RETURNS Elapsed CPU time in `clock-ticks' (1/100 s) for VMS
         -1 if the operation to do is unknown or if 
         the get operation was not preceded by an initialization.
.REMARKS On MS-DOS, cpu time is equated to elapsed time...
------------------------------------------------------------*/
    int  op;  /* IN : operation to execute : INIT_CLOCK / GET_CLOCK */
    float *ftime;	/* OUT: CPU time in seconds */
{
	long lastime;
	static long firstime = 0;
	static int param = 2;
    
  oserror = 0;
  if (ftime)	*ftime = 0.e0;
  switch (op) 
  { case INIT_CLOCK : 	/**** Initialize timer */
	lastime = 0;
	vmserror = LIB$INIT_TIMER();
	if (!(vmserror&1))	oserror = EINTR;
	break;

    case GET_CLOCK : 	/**** get time 		*/
	vmserror = LIB$STAT_TIMER(&param,&lastime);
	if (ftime)	*ftime = lastime, *ftime /= 1.e2;
	if (!(vmserror&1))	oserror = EINTR;
        break;

    default  : 			/**** Operation code error */
	oserror = EINVAL;
	return(-1);
  }

  return (oserror ? -1 : lastime);
}

/*==========================================================================*/
long  oshela(op, ftime) 
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Gets ELAPSED time. Precision is : 1 seconds.
.RETURNS Elapsed time (s) on normal return.
         Value -1 if the operation to do is unknown or if 
         the get operation was not preceded by an initialization.
.REMARKS 
------------------------------------------------------------*/
    int  op; /* IN: operation INIT_CLOCK / GET_CLOCK */
    long *ftime;	/* OUT: Elapsed time in seconds */
{
	static long firstime = 0;
	register long lastime;

  if (ftime)	*ftime = 0;
  oserror = 0;
  switch (op) 
  { case INIT_CLOCK : 	/**** Initialize timer */
	lastime = 0;
	firstime = oshtime();
	break;
    case GET_CLOCK : 	/**** get time */
	lastime = oshtime() - firstime;
  	if (ftime)	*ftime = lastime;
	break;
    default  : 			/**** Operation code error */
	oserror = EINVAL;
    }

  return (oserror ? -1 : lastime);
}
#endif

/*==========================================================================*/
int  oshcmd(command, input, output, error)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Executes system command(maybe with arguments).
	The routine defines also
	the pathnames for standard input, output and error units.
	Enviroment variable SHELL must be defined.
.RETURNS Value 0 on normal return / -1 if error.
.REMARKS error parameter ignored for VMS.
------------------------------------------------------------*/
	char *command;			/* IN : system command */
	char *input;			/* IN : standard input stream */
	char *output;			/* IN : standard output stream */
	char *error;			/* IN : error output stream */
{ 
	static unsigned int attr_list = 0;
	static descript sdv_com   = void_string;
	static descript sdv_in    = void_string;
	descript  *in, *out;
	static descript	sdv_out = InitDescript(tr_buf);

  oserror = 0;
  SET_STRING(sdv_com, command);
  in  = (input  ? &sdv_in : 0);
  out = (output ? &sdv_out: 0);
  if (in)	SET_STRING(sdv_in,  input);
  if (out)	SET_STRING(sdv_out, output);
  vmserror = LIB$SPAWN(&sdv_com, in, out);
  if (!(vmserror&1))	oserror = EINTR;
	
  return (oserror ? -1 : 0);
}

/*==========================================================================*/
int  oshexec(command)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Executes system command(maybe with arguments).
.RETURNS Value 0 on normal return / -1 if error.
.REMARKS Stops the program !
------------------------------------------------------------*/
	char *command;			/* IN : system command */
{ 
	static descript sdv_com = void_string;

  oserror = 0;
  SET_STRING(sdv_com, command);
  vmserror = LIB$DO_COMMAND(&sdv_com);
  if (!(vmserror&1))	oserror = EINTR;
	
  return (oserror ? -1 : 0);
}

char * oshmcpy(s1, s2, n)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE copies n characters from memory area s2 to s1.
.RETURNS It returns s1.
.REMARKS System dependencies: none.
------------------------------------------------------------*/
register char *s1, *s2;
register int n;
{
        register char *os1 = s1;

        while (--n >= 0)
                *s1++ = *s2++;
        return (os1);
}

int oshdate(date_string, date_struct)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Gets date and time stamp. No special structure adopted.
.RETURNS A pointer to the time structure is defined in <time.h> (system)
	 {\begin{TeX} \begin{itemize} \item int tm_year number
	 \item int tm_mon number [0-11] \item int tm_mday [1-31]
	 \item int tm_yday [0-355]      \item int tm_wday [0-6] (Sunday = 0)
	 \item int tm_hour [0-23]           \item int tm_min [0-59]
	 \item int tm_sec  [0-59]. 	\item int tm_saving
	 \end{itemize} \end{TeX}}
.REMARKS System dependencies:
 --Not implemented for VMS
------------------------------------------------------------*/
   char  date_string[28];  /* OUT : date-time stamp  */
   struct tm *date_struct; /* OUT : date-time structure (see description) */
{
	return(-1);
}

oshchdir(path)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Change current working directory.
.RETURNS Value 0 on normal return.  -1 on failure and oserror set.
.REMARKS System dependencies:
 --UNIX: chdir(3)
------------------------------------------------------------*/
char *path;
{
  if (chdir(path) == -1) {
    oserror = errno;
    return(-1);
    }
  else
    return(0);
}
