/* $Id: loslibext.c 1013 2008-02-14 00:09:02Z oneiros $ */

#include "luatex-api.h"
#include <ptexlib.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

/* An attempt to figure out the basic platform, does not
  care about niceties like version numbers yet,
  and ignores platforms where luatex is unlikely to 
  successfully compile without major prorting effort 
  (amiga|mac|os2|vms) */

#if defined(_WIN32) || defined(WIN32) || defined(__NT__)
#define OS_PLATTYPE "windows"
#define OS_PLATNAME "windows"
#elif defined(__GO32__) || defined(__DJGPP__) || defined(__DOS__) 
#define OS_PLATTYPE "msdos"
#define OS_PLATNAME "msdos"
#else  /* assume everything else is unix-y */
#define OS_PLATTYPE "unix"
/* this is just a first guess */
#if defined(__BSD__) 
  #define OS_PLATNAME "bsd"
#elif defined(__SYSV__) 
  #define OS_PLATNAME "sysv"
#else
  #define OS_PLATNAME "generic"
#endif
/* attempt to be more precise */
#if defined(__LINUX__) || defined (__linux)
  #undef OS_PLATNAME
  #define OS_PLATNAME "linux"
#elif defined(__FREEBSD__) || defined(__FreeBSD)
  #undef OS_PLATNAME
  #define OS_PLATNAME "freebsd"
#elif defined(__OPENBSD__)  || defined(__OpenBSD)
  #undef OS_PLATNAME
  #define OS_PLATNAME "openbsd"
#elif defined(__SOLARIS__)
  #undef OS_PLATNAME
  #define OS_PLATNAME "solaris"
#elif defined(__SUNOS__) ||  defined(__SUN__) ||  defined(sun)
  #undef OS_PLATNAME
  #define OS_PLATNAME "sunos"
#elif defined(HPUX) || defined(__hpux)
  #undef OS_PLATNAME
  #define OS_PLATNAME "hpux"
#elif defined(__sgi)
  #undef OS_PLATNAME
  #define OS_PLATNAME "irix"
#elif defined(__MACH__) && defined(__APPLE__)
  #undef OS_PLATNAME
  #define OS_PLATNAME "macosx"
#endif 
#endif





/* there could be more platforms that don't have these two,
 *  but win32 and sunos are for sure.
 * gettimeofday() for win32 is using an alternative definition
 */

#if (! defined(WIN32)) && (! defined(__SUNOS__))
#include <sys/time.h>   /* gettimeofday() */
#include <sys/times.h>  /* times() */
#include <sys/wait.h>
#endif 

/* set this to one for spawn instead of exec on windows */

#define DONT_REALLY_EXIT 1

#ifdef WIN32
#include <process.h>
#define spawn_command(a,b,c) _spawnvpe(_P_WAIT,(const char *)a,(const char* const*)b,(const char* const*)c)
#if DONT_REALLY_EXIT
#define exec_command(a,b,c) exit(spawn_command((a),(b),(c)))
#else
#define exec_command(a,b,c) _execvpe((const char *)a,(const char* const*)b,(const char* const*)c)
#endif
#else
#include <unistd.h>
#define DEFAULT_PATH 	"/bin:/usr/bin:."

static int 
exec_command(const char *file, char *const *argv, char *const *envp)
{
	char path[PATH_MAX];
	const char *searchpath, *esp;
	size_t prefixlen, filelen, totallen;

	if (strchr(file, '/'))	/* Specific path */
		return execve(file, argv, envp);

	filelen = strlen(file);

	searchpath = getenv("PATH");
	if (!searchpath)
		searchpath = DEFAULT_PATH;

	errno = ENOENT;		/* Default errno, if execve() doesn't  change it */

	do {
		esp = strchr(searchpath, ':');
		if (esp)
			prefixlen = esp - searchpath;
		else
			prefixlen = strlen(searchpath);

		if (prefixlen == 0 || searchpath[prefixlen - 1] == '/') {
			totallen = prefixlen + filelen;
			if (totallen >= PATH_MAX)
				continue;
			memcpy(path, searchpath, prefixlen);
			memcpy(path + prefixlen, file, filelen);
		} else {
			totallen = prefixlen + filelen + 1;
			if (totallen >= PATH_MAX)
				continue;
			memcpy(path, searchpath, prefixlen);
			path[prefixlen] = '/';
			memcpy(path + prefixlen + 1, file, filelen);
		}
		path[totallen] = '\0';

		execve(path, argv, envp);
		if (errno == E2BIG  || errno == ENOEXEC ||
		    errno == ENOMEM || errno == ETXTBSY)
			break;	/* Report this as an error, no more search */

		searchpath = esp + 1;
	} while (esp);

	return -1;
}

/* 
   It is not possible to mimic |spawnve()| completely. The main problem is
   that the |fork|--|waitpid| combination cannot really do identical error 
   reporting to the parent process, because it has to pass all the possible
   error conditions as well as the actual process return status through a 
   single 8-bit value.

   The current implementation tries to give back meaningful results for |execve()|
   errors in the child, for the cases that could also be returned by |spawnve()|,
   and for |ETXTBSY|, because that can be triggered by our path searching routine.

   This implementation does not differentiate abnormal status conditions reported 
   by |waitpid()|, but will simply return a single error indication value.

   For all this, hyjacking a bunch of numbers in the range 1...255 is needed. 
   The chance of collisions is hopefully diminished by using a rather random
   range in the 8-bit section.
*/

#define INVALID_RET_E2BIG   143 
#define INVALID_RET_ENOENT  144
#define INVALID_RET_ENOEXEC 145
#define INVALID_RET_ENOMEM  146
#define INVALID_RET_ETXTBSY 147
#define INVALID_RET_UNKNOWN 148
#define INVALID_RET_INTR    149

static int 
spawn_command(const char *file, char *const *argv, char *const *envp)
{
  pid_t pid, wait_pid;
  int status;
  pid = fork();
  if (pid<0) {
    return -1; /* fork failed */
  }
  if (pid>0) { /* parent */
    status = 0;
    wait_pid = waitpid (pid, &status,0);
    if (wait_pid == pid) {
      if (WIFEXITED(status))
	return WEXITSTATUS(status);
      else
	return INVALID_RET_INTR;
    } else {
      return -1; /* some waitpid error */
    }
  } else {
    int f;
    /* somewhat random upper limit. ignore errors on purpose */
    for (f = 0; f<256; f++) 
      (void)fsync(f);

    if (exec_command(file, argv, envp)) {
      /* let's hope no-one uses these values  */
      switch (errno) {
      case E2BIG:    exit(INVALID_RET_E2BIG);
      case ETXTBSY:  exit(INVALID_RET_ETXTBSY);
      case ENOENT:   exit(INVALID_RET_ENOENT);
      case ENOEXEC:  exit(INVALID_RET_ENOEXEC);
      case ENOMEM:   exit(INVALID_RET_ENOMEM);
      default:       exit(INVALID_RET_UNKNOWN);
      }
      return -1;
    }
  }
}

#endif

extern char **environ;

static char **
do_split_command(char *maincmd)
{
    char *piece, *start_piece;
    char *cmd;
    char **cmdline = NULL;
    unsigned int i, j;
    int ret = 0;
    int in_string = 0;
    int quoted = 0;
    if (strlen(maincmd) == 0)
	return NULL;
    /* allocate the array of options first. it will probably be 
       be a little bit too big, but better too much than to little */
    j=2;
    for (i = 0; i < strlen(maincmd); i++) { if (maincmd[i]==' ') j++; }
    cmdline = malloc(sizeof(char *) * j);
    for (i = 0; i < j; i++) { cmdline[i] = NULL; }
    cmd = maincmd;
    i = 0;
    while (cmd[i] == ' ') i++; /* skip leading spaces */
    start_piece = malloc(strlen(cmd)+1); /* a buffer */
    piece = start_piece;
    for (; i <= strlen(maincmd); i++) {
      if (cmd[i]=='\\' && 
	  (cmd[i+1] == '\\' || cmd[i+1] == '\'' || cmd[i+1] == '"')) {
	quoted =1;
	continue;
      }
      if (in_string && cmd[i] == in_string  && !quoted) { 
	in_string = 0; 
	continue; 
      } 
      if ((cmd[i] == '"' || cmd[i] == '\'')&& !quoted) { 
	in_string = cmd[i]; 
	continue; 
      } 
      if ((in_string==0 && cmd[i] == ' ') || cmd[i]==0) {
	*piece = 0; 
	cmdline[ret++] = xstrdup(start_piece);
	piece = start_piece;
	while (i < strlen(maincmd) && cmd[(i + 1)] == ' ')
	  i++;
	continue;
      }
      *piece++ = cmd[i];
      quoted=0;
    }
    xfree(start_piece);
    return cmdline;
}

static char **
do_flatten_command(lua_State *L, char **runcmd) {
   unsigned int i, j ;
   char *s;
   char **cmdline = NULL;
   *runcmd = NULL;

   for (j = 1;;j++) {
     lua_rawgeti(L,-1,j);
     if (lua_isnil(L,-1)) {
       lua_pop(L,1);
       break;
     }
     lua_pop(L,1);
   }
   if (j == 1)
     return NULL;
   cmdline = malloc(sizeof(char *) * j);
   for (i = 1; i <= j; i++) {
     cmdline[i] = NULL;
     lua_rawgeti(L,-1,i);
     if (lua_isnil(L,-1) || (s=(char *)lua_tostring(L,-1))==NULL) {
       lua_pop(L,1);
       if (i==1) {
		 xfree(cmdline) ;
         return NULL;
       } else {
         break;
       }
     } else {
       lua_pop(L,1);
       cmdline[(i-1)] = xstrdup(s);
     }
   }
   cmdline[i] = NULL;

   lua_rawgeti(L,-1,0);
   if (lua_isnil(L,-1) || (s=(char *)lua_tostring(L,-1))==NULL) {
	 *runcmd = cmdline[0];
   } else {
	 *runcmd = xstrdup(s);
   }
   lua_pop(L,1);

   return cmdline;
}


static int os_exec (lua_State *L) {
  char * maincmd, * runcmd;
  char ** cmdline = NULL;

  if (lua_gettop(L)!=1) {
    lua_pushnil(L);
    lua_pushliteral(L,"invalid arguments passed");
    return 2;
  }
  if (lua_type(L,1)==LUA_TSTRING) {
    maincmd =  (char *)lua_tostring(L, 1);
    cmdline = do_split_command(maincmd);
    runcmd = cmdline[0];
  } else if (lua_type(L,1)==LUA_TTABLE) {
    cmdline = do_flatten_command(L, & runcmd);
  }
  if (cmdline!=NULL) {
#if defined(WIN32) && DONT_REALLY_EXIT
    exec_command(runcmd, cmdline, environ);
#else
    if (exec_command(runcmd, cmdline, environ)==-1) {
      lua_pushnil(L);
      lua_pushfstring(L,"%s: %s",runcmd,  strerror(errno));
      lua_pushnumber(L, errno);
      return 3;
    }
#endif
  }
  lua_pushnil(L);
  lua_pushliteral(L,"invalid command line passed");
  return 2;
}

#define do_error_return(A,B) do {					\
    lua_pushnil(L);									\
    lua_pushfstring(L,"%s: %s",runcmd,(A));			\
    lua_pushnumber(L, B);							\
    return 3;										\
  } while (0)

static int os_spawn (lua_State *L) {
  char * maincmd, * runcmd;
  char ** cmdline = NULL;
  int i;

  if (lua_gettop(L)!=1) {
    lua_pushnil(L);
    lua_pushliteral(L,"invalid arguments passed");
    return 2;
  }
  if (lua_type(L,1)==LUA_TSTRING) {
    maincmd =  (char *)lua_tostring(L, 1);
    cmdline = do_split_command(maincmd);
    runcmd = cmdline[0];
  } else if (lua_type(L,1)==LUA_TTABLE) {
    cmdline = do_flatten_command(L, &runcmd);
  }
  if (cmdline!=NULL) {
    i = spawn_command(runcmd, cmdline, environ);
    if (i==0) {
      lua_pushnumber(L, i);
      return 1;
    } else if (i==-1)                  { 
      /* this branch covers WIN32 as well as fork() and waitpid() errors */
      do_error_return(strerror(errno),errno);
#ifndef WIN32
    } else if (i==INVALID_RET_E2BIG)   { do_error_return(strerror(E2BIG),i);
    } else if (i==INVALID_RET_ENOENT)  { do_error_return(strerror(ENOENT),i);
    } else if (i==INVALID_RET_ENOEXEC) { do_error_return(strerror(ENOEXEC),i);
    } else if (i==INVALID_RET_ENOMEM)  { do_error_return(strerror(ENOMEM),i);
    } else if (i==INVALID_RET_ETXTBSY) { do_error_return(strerror(ETXTBSY),i);
    } else if (i==INVALID_RET_UNKNOWN) { do_error_return("execution failed",i);
    } else if (i==INVALID_RET_INTR)    { do_error_return("execution interrupted",i);
#endif
    } else {
      lua_pushnumber(L, i);
      return 1;
    }
  } 
  lua_pushnil(L);
  lua_pushliteral(L,"invalid command line passed");
  return 2;
}

/*  Hans wants to set env values */

static int os_setenv (lua_State *L) {
  char *value, *key, *val;
  key =  (char *)luaL_optstring(L, 1, NULL);
  val =  (char *)luaL_optstring(L, 2, NULL);
  if (key) {
	if (val) {
	  value = xmalloc(strlen(key)+strlen(val)+2);
	  sprintf(value,"%s=%s",key,val);
	  if (putenv(value)) {
		return luaL_error(L, "unable to change environment");
	  }
	} else {
#if defined(WIN32) || defined(__sun__)
	  value = xmalloc(strlen(key)+2);
	  sprintf(value,"%s=",key);
	  if (putenv(value)) {
		return luaL_error(L, "unable to change environment");
	  } 
#else
	  (void)unsetenv(key);
#endif 
	}
  }
  lua_pushboolean (L, 1);
  return 1;
}


void find_env (lua_State *L){
  char *envitem, *envitem_orig;
  char *envkey;
  char **envpointer;
  envpointer = environ;
  lua_getglobal(L,"os");
  if (envpointer!=NULL && lua_istable(L,-1)) {
    luaL_checkstack(L,2,"out of stack space");
    lua_pushstring(L,"env"); 
    lua_newtable(L); 
    while (*envpointer) {
      /* TODO: perhaps a memory leak here  */
      luaL_checkstack(L,2,"out of stack space");
      envitem = xstrdup(*envpointer);
      envitem_orig = envitem;
      envkey=envitem;
      while (*envitem != '=') {
		envitem++;
      }
      *envitem=0;
      envitem++;
      lua_pushstring(L,envkey);
      lua_pushstring(L,envitem);
      lua_rawset(L,-3);
      envpointer++;
      free(envitem_orig);
    }
    lua_rawset(L,-3);
  }
  lua_pop(L,1);
}

static int ex_sleep(lua_State *L)
{
  lua_Number interval = luaL_checknumber(L, 1);
  lua_Number units = luaL_optnumber(L, 2, 1);
#ifdef WIN32
  Sleep(1e3 * interval / units);
#else /* assumes posix or bsd */
  usleep(1e6 * interval / units);
#endif
  return 0;
}

#if (! defined (WIN32))  && (! defined (__SUNOS__))
static int os_times (lua_State *L) {
  struct tms r;
  (void)times(&r);
  lua_newtable(L);
  lua_pushnumber(L, ((lua_Number)(r.tms_utime))/(lua_Number)sysconf(_SC_CLK_TCK));
  lua_setfield(L,-2,"utime");
  lua_pushnumber(L, ((lua_Number)(r.tms_stime))/(lua_Number)sysconf(_SC_CLK_TCK));
  lua_setfield(L,-2,"stime");
  lua_pushnumber(L, ((lua_Number)(r.tms_cutime))/(lua_Number)sysconf(_SC_CLK_TCK));
  lua_setfield(L,-2,"cutime");
  lua_pushnumber(L, ((lua_Number)(r.tms_cstime))/(lua_Number)sysconf(_SC_CLK_TCK));
  lua_setfield(L,-2,"cstime");
  return 1;
}
#endif

#if ! defined (__SUNOS__)

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif

static int os_gettimeofday (lua_State *L) {
  double v;
#ifndef WIN32
  struct timeval tv;
  gettimeofday(&tv, NULL);
  v = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
#else
  FILETIME ft;
  unsigned __int64 tmpres = 0;

  GetSystemTimeAsFileTime(&ft);

  tmpres |= ft.dwHighDateTime;
  tmpres <<= 32;
  tmpres |= ft.dwLowDateTime;
  tmpres /= 10;
  tmpres -= DELTA_EPOCH_IN_MICROSECS; /*converting file time to unix epoch*/
  v = (double)tmpres / 1000000.0; 
#endif
  lua_pushnumber(L, v);
  return 1;
}
#endif

static const char repl[] = "0123456789abcdefghijklmnopqrstuvwxyz";

static int dirs_made = 0;

#define MAXTRIES 36*36*36

char *
do_mkdtemp (char *tmpl)
{
  int count ;
  int value ;
  char *xes = &tmpl[strlen (tmpl) - 6];
  /* this is not really all that random, but it will do */
  if (dirs_made==0) {
	srand(time(NULL));
  }
  value = rand();
  for (count = 0; count < MAXTRIES; value += 8413, ++count) {
      int v = value;
      xes[0] = repl[v % 36];  v /= 36;
      xes[1] = repl[v % 36];  v /= 36;
      xes[2] = repl[v % 36];  v /= 36;
      xes[3] = repl[v % 36];  v /= 36;
      xes[4] = repl[v % 36];  v /= 36;
      xes[5] = repl[v % 36];
	  if (mkdir (tmpl, S_IRUSR | S_IWUSR | S_IXUSR) >= 0) {
		dirs_made++;
		return tmpl;
	  } else if (errno != EEXIST) {
		return NULL;
	  }
    }
  return NULL;
}

static int os_tmpdir (lua_State *L) {
  char *s, *tempdir; 
  char *tmp = (char *)luaL_optstring(L, 1, "luatex.XXXXXX");
  if (tmp==NULL || 
	  strlen(tmp)<6 ||
	  (strcmp(tmp+strlen(tmp)-6,"XXXXXX") != 0)) {
    lua_pushnil(L);
    lua_pushstring(L, "Invalid argument to os.tmpdir()");
    return 2;
  } else {
	tempdir = xstrdup(tmp);
  }
#ifdef HAVE_MKDTEMP
  s = mkdtemp(tempdir);
#else
  s = do_mkdtemp(tempdir);
#endif
  if (s==NULL) {
	int en = errno;  /* calls to Lua API may change this value */
	lua_pushnil(L);
	lua_pushfstring(L, "%s", strerror(en));
	return 2;
  } else {
	lua_pushstring(L,s);
	return 1;
  }
}


void
open_oslibext (lua_State *L, int safer_option) {

  find_env(L);

  lua_getglobal(L,"os");
  lua_pushcfunction(L, ex_sleep);
  lua_setfield(L,-2,"sleep");
  lua_getglobal(L,"os");
  lua_pushliteral(L, OS_PLATTYPE);
  lua_setfield(L,-2,"type");
  lua_getglobal(L,"os");
  lua_pushliteral(L, OS_PLATNAME);
  lua_setfield(L,-2,"name");
#if (! defined (WIN32))  && (! defined (__SUNOS__))
  lua_getglobal(L,"os");
  lua_pushcfunction(L, os_times);
  lua_setfield(L,-2,"times");
#endif
#if ! defined (__SUNOS__)
  lua_getglobal(L,"os");
  lua_pushcfunction(L, os_gettimeofday);
  lua_setfield(L,-2,"gettimeofday");
#endif
  if (!safer_option) {
	lua_getglobal(L,"os");
	lua_pushcfunction(L, os_setenv);
	lua_setfield(L,-2,"setenv");
	lua_getglobal(L,"os");
	lua_pushcfunction(L, os_exec);
	lua_setfield(L,-2,"exec");
	lua_getglobal(L,"os");
	lua_pushcfunction(L, os_spawn);
	lua_setfield(L,-2,"spawn");
	lua_getglobal(L,"os");
	lua_pushcfunction(L, os_tmpdir);
	lua_setfield(L,-2,"tmpdir");

  }


}
