/*
 *
 * p o s i x . c			-- Provide some POSIX.1 functions 
 *
 * Copyright © 1993-1999 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
 * 
 *
 * Permission to use, copy, modify, distribute,and license this
 * software and its documentation for any purpose is hereby granted,
 * provided that existing copyright notices are retained in all
 * copies and that this notice is included verbatim in any
 * distributions.  No written agreement, license, or royalty fee is
 * required for any of the authorized uses.
 * This software is provided ``AS IS'' without express or implied
 * warranty.
 *
 *
 *           Author: Erick Gallesio [eg@kaolin.unice.fr]
 *    Creation date: 14-Mar-1995 20:14
 * Last file update:  3-Sep-1999 20:21 (eg)
 *
 * This file contains also contains code additions from Shiro Kawai 
 * <shiro@sqush.squareusa.com>
 */
 

#include <stk.h>
#include <sys/types.h>
#include <sys/utsname.h>

#define DefineConst(c) {STk_define_scheme_variable(#c, STk_makeinteger(c));}

/******************************************************************************
 * 
 * Error management
 *
 ******************************************************************************/
extern int errno;

static SCM get_errno(char *s)
{
  return STk_makeinteger((long) errno);
}

static void set_errno(char *s, SCM value)
{
  long n = STk_integer_value_no_overflow(value);

  if (n == LONG_MIN) Err("setting *errno*: bad integer", value);
  errno = n;
}

static PRIMITIVE posix_perror(SCM str)
{
  if (NSTRINGP(str)) Err("posix-perror: bad string", str);
  perror(CHARS(str));
  return UNDEFINED;
}

/******************************************************************************
 *
 * File and Directory functions
 *
 ******************************************************************************/
#include <sys/stat.h>

static int Cpointer_stat;

static PRIMITIVE posix_stat(SCM filename)
{
  struct stat *p;

  if (NSTRINGP(filename)) Err("posix-stat: bad string", filename);
  
  p = (struct stat *) must_malloc(sizeof(struct stat));
  if (stat(CHARS(filename), p) == -1) return Ntruth;
  
  return STk_make_Cpointer(Cpointer_stat, (void *) p, FALSE);
}

static PRIMITIVE posix_stat2vector(SCM descr)
{
  SCM z;
  struct stat *info;

  if (NCPOINTERP(descr) || EXTID(descr) != Cpointer_stat) 
    Err("posix-stat->vector: bad structure ", descr);
    
  info = (struct stat *) EXTDATA(descr);

  z = STk_makevect(10, NIL);	/* NIL because some integers are bignums */
  VECT(z)[0] = STk_makeinteger((long)info->st_dev);
  VECT(z)[1] = STk_makeinteger((long)info->st_ino);
  VECT(z)[2] = STk_makeinteger((long)info->st_mode);
  VECT(z)[3] = STk_makeinteger((long)info->st_nlink);
  VECT(z)[4] = STk_makeinteger((long)info->st_uid);
  VECT(z)[5] = STk_makeinteger((long)info->st_gid);
  VECT(z)[6] = STk_makeinteger((long)info->st_size);
  VECT(z)[7] = STk_makeinteger((long)info->st_atime);
  VECT(z)[8] = STk_makeinteger((long)info->st_mtime);
  VECT(z)[9] = STk_makeinteger((long)info->st_ctime);

  return z;
}

static PRIMITIVE posix_access(SCM filename, SCM mode)
{
  long m;

  if (NSTRINGP(filename)) Err("posix-access?: bad string", filename);
  if ((m=STk_integer_value_no_overflow(mode)) == LONG_MIN)
    Err("posix-access?: bad integer", mode);
  return (access(CHARS(filename), (int) m) == 0) ? Truth: Ntruth;
}

static PRIMITIVE posix_pipe(void)
{
  int fd[2];
  FILE *f0, *f1;

  if (pipe(fd) == -1) return Ntruth;
  
  if ((f0 = fdopen(fd[0], "r")) == NULL || (f1 = fdopen(fd[1], "w")) == NULL) {
    fclose(f0);   fclose(f1);
    close(fd[0]); close(fd[1]);
    return Ntruth;
  }

  return Cons(STk_Cfile2port("pipe (input)",  f0, tc_iport, 0),
	      STk_Cfile2port("pipe (output)", f1, tc_oport, 0));
}

/** Added by Shiro K. **/

static PRIMITIVE posix_unlink(SCM filename)
{
  int r;
  
  if (NSTRINGP(filename)) Err("posix-unlink: bad string", filename);
  r = unlink(CHARS(filename));
  return (r < 0)? Ntruth : Truth;
}

static PRIMITIVE posix_symlink(SCM to, SCM from)
{
  int r;
  if (NSTRINGP(from)) Err("posix-symlink: bad string", from);
  if (NSTRINGP(to))   Err("posix-symlink: bad string", to);
  r = symlink(CHARS(to), CHARS(from));
  return (r < 0)? Ntruth : Truth;
}


static PRIMITIVE posix_chmod(SCM path, SCM mode)
{
  int r;
  
  if (NSTRINGP(path)) Err("posix-chmod: bad string", path);
  if (NINTEGERP(mode)) Err("posix-chmod: bad mode", mode);
  r = chmod(CHARS(path), (mode_t)INTEGER(mode));
  return (r < 0)? Ntruth : Truth;
}

static PRIMITIVE posix_rename(SCM old, SCM new)
{
  int r;
  
  if (NSTRINGP(old)) Err("posix-rename: bad string", old);
  if (NSTRINGP(new)) Err("posix-rename: bad string", new);
  r = rename(CHARS(old), CHARS(new));
  return (r < 0)? Ntruth : Truth;
}

static PRIMITIVE posix_getlogin()
{
  char *s = getlogin();
  
  return (s == NULL) ?  Ntruth : STk_makestring(s);
}

static PRIMITIVE posix_mkdir(SCM path, SCM mode)
{
    int r;
  
    if (NSTRINGP(path))  Err("posix-mkdir: bad path", path);
    if (NINTEGERP(mode)) Err("posix-mkdir: bad mode", mode);
    r = mkdir(CHARS(path), (mode_t) INTEGER(mode));
    return (r < 0)? Ntruth : Truth;
}

static PRIMITIVE posix_rmdir(SCM path)
{
    int r;
    
    if (NSTRINGP(path))  Err("posix-rmdir: bad path", path);
    r = rmdir(CHARS(path));
    return (r < 0)? Ntruth : Truth;
}
/** End of addition by Shiro K. **/

/******************************************************************************
 *
 * Time functions 
 *
 ******************************************************************************/
#include <time.h>

#ifdef SUNOS4
#define mktime(c) timegm(c)
#endif

static int Cpointer_tm;

static void display_Cpointer_tm(SCM obj, SCM port, int mode)
{
  struct tm *p = (struct tm *) EXTDATA(obj);

  sprintf(STk_tkbuffer, "#<C-struct tm %02d/%02d/%02d %02d:%02d:%02d>", 
	  		p->tm_mon,  p->tm_mday, p->tm_year,
	  		p->tm_hour, p->tm_min,  p->tm_sec);
  Puts(STk_tkbuffer, port);
}

static PRIMITIVE posix_time(void)
{
  return STk_makeinteger((long) time(NULL));
}

static PRIMITIVE posix_ctime(SCM seconds)
{
  long sec;

  sec = (seconds == UNBOUND) ? time(NULL)
			     : STk_integer_value_no_overflow(seconds);
  if (sec == LONG_MIN) Err("posix-ctime: bad time value", seconds);
  
  return STk_makestring(ctime((time_t *) &sec));
}


static PRIMITIVE posix_localtime(SCM timer)
{
  long t = STk_integer_value_no_overflow(timer);

  if (t == LONG_MIN) Err("posix-localtime: bad time value", timer);

  return STk_make_Cpointer(Cpointer_tm, (void *) localtime((time_t *) &t), TRUE);
}

static PRIMITIVE posix_gmtime(SCM timer)
{
  long t = STk_integer_value_no_overflow(timer);

  if (t == LONG_MIN) Err("posix-gmtime: bad time value", timer);

  return STk_make_Cpointer(Cpointer_tm, (void *) gmtime((time_t *) &t), TRUE);
}

static PRIMITIVE posix_mktime(SCM t)
{
  time_t sec;
  if (NCPOINTERP(t) || EXTID(t) != Cpointer_tm) 
    Err("posix-mktime: bad time structure", t);
  
  sec = (time_t) mktime(EXTDATA(t));
  return STk_makeinteger((long) sec);
}

static PRIMITIVE posix_tm2vector(SCM t)
{
  SCM z;
  struct tm *p;
  
  if (NCPOINTERP(t) || EXTID(t) != Cpointer_tm) 
    Err("posix-tm->vector: bad time structure", t);
  
  z = STk_makevect(9, NIL); 	/* NULL could work here. But this is safer */
  p = (struct tm *) EXTDATA(t);

  VECT(z)[0] = STk_makeinteger(p->tm_sec);
  VECT(z)[1] = STk_makeinteger(p->tm_min);
  VECT(z)[2] = STk_makeinteger(p->tm_hour); 
  VECT(z)[3] = STk_makeinteger(p->tm_mday);
  VECT(z)[4] = STk_makeinteger(p->tm_mon);
  VECT(z)[5] = STk_makeinteger(p->tm_year);
  VECT(z)[6] = STk_makeinteger(p->tm_wday);
  VECT(z)[7] = STk_makeinteger(p->tm_yday);
  VECT(z)[8] = (p->tm_isdst) ? Truth: Ntruth;
 
  return z;
}

static PRIMITIVE vector2posix_tm(SCM v)
{
  struct tm *p;

  if (NVECTORP(v) || VECTSIZE(v) != 9)
    Err("vector->posix-tm: bad vector", v);

  p = (struct tm *) must_malloc(sizeof(struct tm));
  p->tm_sec   = STk_integer_value_no_overflow(VECT(v)[0]);
  p->tm_min   = STk_integer_value_no_overflow(VECT(v)[1]);
  p->tm_hour  = STk_integer_value_no_overflow(VECT(v)[2]);
  p->tm_mday  = STk_integer_value_no_overflow(VECT(v)[3]);
  p->tm_mon   = STk_integer_value_no_overflow(VECT(v)[4]);
  p->tm_year  = STk_integer_value_no_overflow(VECT(v)[5]);
  p->tm_wday  = STk_integer_value_no_overflow(VECT(v)[6]);
  p->tm_yday  = STk_integer_value_no_overflow(VECT(v)[7]);
  p->tm_isdst = (VECT(v)[8] == Truth);
  
  return STk_make_Cpointer(Cpointer_tm, p, FALSE);
}

static PRIMITIVE posix_strftime(SCM format, SCM t)
{
  char buffer[1024];
  struct tm *p;
  int len;

  if (NSTRINGP(format)) 
    Err("posix-strftime: Bad string", format);
    
  /* If t is not provided, assume that we want current localtime */
  if (t == UNBOUND) {
    time_t t = time(NULL);
    p = localtime(&t);
  }
  else {
    if (NCPOINTERP(t) || EXTID(t) != Cpointer_tm) 
      Err("posix-strftime: bad time structure", t);
    p = EXTDATA(t);
  }
  
  if (len=strftime(buffer, 1023, CHARS(format), p))
    return STk_makestring(buffer);
  else
    Err("posix-strftime: buffer too short", NIL);
  return UNDEFINED; /* never reached */
}

/******************************************************************************
 *
 * Processes stuff
 *
 ******************************************************************************/

static PRIMITIVE posix_fork(void)
{
  pid_t pid = fork();

  if (pid == 0) {
    /* Child process must delete Tcl interpreter (if it exists) */
    STk_delete_Tcl_child_Interp();
  }
  
  return (pid == -1) ? Ntruth: STk_makeinteger((long) pid);
}

static PRIMITIVE posix_wait(void)
{
  pid_t pid;
  int status;
  
  pid = wait(&status);
  if (pid == -1)
    return Ntruth;
  else
    return Cons(STk_makeinteger((long) pid), 
		STk_makeinteger((long) status));
}

/******************************************************************************
 *
 * System infos
 *     gethostname and getdomainname:  POSIX.1  does  not define these
 *     functions, but ISO/IEC 9945-1:1990 mentions them in B.4.4.1. 
 *			-- Linux documentation
 *
 ******************************************************************************/

static PRIMITIVE posix_uname(void)
{
  struct utsname buff;
  SCM v;

  if (uname(&buff) == -1)
    Err("posix-uname: cannot stat", NIL);
  
  v = STk_makevect(5, NIL);
  VECT(v)[0] = STk_makestring(buff.sysname);
  VECT(v)[1] = STk_makestring(buff.nodename);
  VECT(v)[2] = STk_makestring(buff.release);
  VECT(v)[3] = STk_makestring(buff.version);
  VECT(v)[4] = STk_makestring(buff.machine);

  return v;
}

static PRIMITIVE posix_host_name(void)
{
  char name[100];
  
  if (gethostname(name, 100) != 0)
    Err("posix-host-name: cannot determine name", NIL);

  return STk_makestring(name);
}

static PRIMITIVE posix_domain_name(void)
{
  char name[100];
  
  if (getdomainname(name, 100) != 0)
    Err("posix-domain-name: cannot determine domain", NIL);
  
  return STk_makestring(name);
}


/******************************************************************************
 *
 * Initialization code
 *
 ******************************************************************************/

PRIMITIVE STk_init_posix(void)
{
  /* Error management */
  STk_define_C_variable("*errno*", get_errno, set_errno);
  STk_add_new_primitive("posix-perror",	      tc_subr_1,    posix_perror);

  /* File and directories */
  Cpointer_stat = STk_new_Cpointer_id(NULL);
  STk_add_new_primitive("posix-stat",	      tc_subr_1,    posix_stat);
  STk_add_new_primitive("posix-stat->vector", tc_subr_1,    posix_stat2vector);
  STk_add_new_primitive("posix-access?",      tc_subr_2,    posix_access);
  STk_add_new_primitive("posix-pipe",       tc_subr_0, 	    posix_pipe);

  DefineConst(F_OK);  	DefineConst(R_OK);  	DefineConst(W_OK);
  DefineConst(X_OK);

  /** Addition by SK **/
  STk_add_new_primitive("posix-unlink",       tc_subr_1,    posix_unlink);
  STk_add_new_primitive("posix-symlink",      tc_subr_2,    posix_symlink);
  STk_add_new_primitive("posix-chmod",        tc_subr_2,    posix_chmod);
  STk_add_new_primitive("posix-rename",       tc_subr_2,    posix_rename);
  STk_add_new_primitive("posix-getlogin",     tc_subr_0,    posix_getlogin);
  STk_add_new_primitive("posix-mkdir",        tc_subr_2,    posix_mkdir);
  STk_add_new_primitive("posix-rmdir",        tc_subr_1,    posix_rmdir);

  DefineConst(S_ISUID);
  DefineConst(S_ISGID);
  DefineConst(S_ISVTX);
  DefineConst(S_IRWXU);
  DefineConst(S_IRUSR);
  DefineConst(S_IWUSR);
  DefineConst(S_IXUSR);
  DefineConst(S_IRWXG);
  DefineConst(S_IRGRP);
  DefineConst(S_IWGRP);
  DefineConst(S_IXGRP);
  DefineConst(S_IRWXO);
  DefineConst(S_IROTH);
  DefineConst(S_IWOTH);
  DefineConst(S_IXOTH);
  DefineConst(S_IFMT);
  DefineConst(S_IFIFO);
  DefineConst(S_IFCHR);
  DefineConst(S_IFDIR);
  DefineConst(S_IFBLK);
  DefineConst(S_IFREG);
  DefineConst(S_IFLNK);
  DefineConst(S_IFSOCK);
  /** End addition by SK **/

  /* Time */
  Cpointer_tm = STk_new_Cpointer_id(display_Cpointer_tm);
  STk_add_new_primitive("posix-time",       tc_subr_0,      posix_time);
  STk_add_new_primitive("posix-ctime",      tc_subr_0_or_1, posix_ctime);  
  STk_add_new_primitive("posix-localtime",  tc_subr_1,      posix_localtime);
  STk_add_new_primitive("posix-gmtime",     tc_subr_1,      posix_gmtime);
  STk_add_new_primitive("posix-mktime",     tc_subr_1,      posix_mktime);
  STk_add_new_primitive("posix-tm->vector", tc_subr_1,      posix_tm2vector);
  STk_add_new_primitive("vector->posix-tm", tc_subr_1,      vector2posix_tm);
  STk_add_new_primitive("posix-strftime",   tc_subr_1_or_2, posix_strftime);

  /* Processes */
  STk_add_new_primitive("posix-fork",       tc_subr_0, 	    posix_fork);
  STk_add_new_primitive("posix-wait",       tc_subr_0, 	    posix_wait);
  
  /* System information */
  STk_add_new_primitive("posix-uname",      tc_subr_0, 	    posix_uname);
  STk_add_new_primitive("posix-host-name",  tc_subr_0, 	    posix_host_name);
  STk_add_new_primitive("posix-domain-name",tc_subr_0, 	    posix_domain_name);
  
  return UNDEFINED;
}
