#include "config.h"

#if defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#endif

#ifdef HAVE_STRPTIME
#define _XOPEN_SOURCE 500
#ifdef HAVE_FEATURES_H
#include <features.h>
#endif
#endif

#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/memory.h>
#include <caml/fail.h>
#include <caml/callback.h>
#include <caml/signals.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

/* Standard headers */
#include <ctype.h>
#include <limits.h>
#include <locale.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#ifdef HAVE_SYS_SENDFILE_H
#include <sys/sendfile.h>
#endif

#define NONE            Val_int(0)     
#define SOME            0             

#define Nothing 0

/* From the unix lib */
#define UNIX_BUFFER_SIZE 16384
extern void uerror(char *, value) Noreturn;

static char*
str_option(value v)
{
  if (v == NONE)
    return (char*) NULL;
  else 
    return String_val(Field(v,SOME));
}

struct locale_conversion {
  value hash;
  int ltype;
  const char *name;
};

struct locale_conversion contable[] = {
  {0, LC_ALL, "LC_ALL"},
  {0, LC_COLLATE, "LC_COLLATE"},
  {0, LC_CTYPE, "LC_CTYPE"},
  {0, LC_MONETARY, "LC_MONETARY"},
  {0, LC_MESSAGES, "LC_MESSAGES"},
  {0, LC_NUMERIC, "LC_NUMERIC"},
  {0, LC_TIME, "LC_TIME"},
  {0, 0, 0}
};
int contable_init = 0;

value signpos_conversion[6];
int signpos_set = 0;

value
stew_set_locale(value whatopt, value type) {
  CAMLparam2(type, whatopt);
  CAMLlocal2(ret, nl);
  char *newlocale;
  struct locale_conversion *c;
  char *locale = str_option(whatopt);
  int whattype = 0;

  if (contable_init == 0) {

    contable_init = 1;

    for (c = contable; c && c->name; c++)
      c->hash = hash_variant((char *)c->name);
  }
    
  for (c = contable; c && c->name; c++) {
    if (c->hash == type) {
      whattype = c->ltype;
      break;
    }
  }
  
  if (!(c && c->name))
    failwith("Locale.set: Unknown locale type");

  newlocale = setlocale(whattype, locale);

  if (!newlocale)
    CAMLreturn(NONE);

  nl = copy_string(newlocale);  
  ret = alloc_small(1, SOME);
  Field(ret, 0) = nl;
  
  CAMLreturn(ret);
}

/* The numeric bits of a struct lconv */
value
stew_localeconv_n (void) {
  CAMLparam0();
  CAMLlocal4(dp, ts, g, res);
  struct lconv *l;

  l = localeconv();

  dp = copy_string(l->decimal_point);
  ts = copy_string(l->thousands_sep);
  g = copy_string(l->grouping);

  res = alloc_tuple(3);
  Field(res, 0) = dp;
  Field(res, 1) = ts;
  Field(res, 2) = g;
  
  CAMLreturn(res);
}

/* The monetary bits of a struct lconv */
value
stew_localeconv_m (void) {
  CAMLparam0();
  CAMLlocal5(ics, cs, dp, ts, g);
  CAMLlocal3(ps, ns, res);
  struct lconv *l;

  if (!signpos_set) {
    signpos_set = 1;
    signpos_conversion[0] = hash_variant("SurroundBoth");
    signpos_conversion[1] = hash_variant("SignPrecedesBoth");
    signpos_conversion[2] = hash_variant("SignSucceedsBoth");
    signpos_conversion[3] = hash_variant("SignPrecedesCS");
    signpos_conversion[4] = hash_variant("SignSucceedsCS");
    signpos_conversion[5] = hash_variant("UnknownOrder");
  }

  l = localeconv();

  ics = copy_string(l->int_curr_symbol);
  cs = copy_string(l->currency_symbol);
  dp = copy_string(l->mon_decimal_point);
  ts = copy_string(l->mon_thousands_sep);
  g = copy_string(l->mon_grouping);
  ps = copy_string(l->positive_sign);
  ns = copy_string(l->negative_sign);

  res = alloc_tuple(15);
  Field(res, 0) = ics;
  Field(res, 1) = cs;
  Field(res, 2) = dp;
  Field(res, 3) = ts;
  Field(res, 4) = g;
  Field(res, 5) = ps;
  Field(res, 6) = ns;
  Field(res, 7) = Val_int(l->int_frac_digits);
  Field(res, 8) = Val_int(l->int_frac_digits);
  Field(res, 9) = Val_bool(l->p_cs_precedes);
  Field(res, 10) = Val_bool(l->p_sep_by_space);
  Field(res, 11) = Val_bool(l->n_cs_precedes);
  Field(res, 12) = Val_bool(l->n_sep_by_space);
  if (l->p_sign_posn > 4)
    Field(res, 13) = signpos_conversion[5];
  else
    Field(res, 13) = signpos_conversion[(int)l->p_sign_posn];
  if (l->n_sign_posn > 4)
    Field(res, 14) = signpos_conversion[5];
  else
    Field(res, 14) = signpos_conversion[(int)l->n_sign_posn];

  CAMLreturn(res);
}

value
stew_is_alpha(value c) {
  if (isalpha(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_space(value c) {
  if (isspace(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_number(value c) {
  if (isdigit(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_lower(value c) {
  if (islower(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_upper(value c) {
  if (isupper(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_punct(value c) {
  if (ispunct(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_print(value c) {
  if (isprint(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_graph(value c) {
  if (isgraph(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_is_xdigit(value c) {
  if (isxdigit(Int_val(c)))
    return Val_true;
  else
    return Val_false;
}

value
stew_to_lower(value c) {
  return Val_int(tolower(Int_val(c)));
}

value
stew_to_upper(value c) {
  return Val_int(toupper(Int_val(c)));
}

static value
alloc_tm(struct tm *tm)
{
  value res;
  res = alloc_tuple(9);
  Field(res,0) = Val_int(tm->tm_sec);
  Field(res,1) = Val_int(tm->tm_min);
  Field(res,2) = Val_int(tm->tm_hour);
  Field(res,3) = Val_int(tm->tm_mday);
  Field(res,4) = Val_int(tm->tm_mon);
  Field(res,5) = Val_int(tm->tm_year);
  Field(res,6) = Val_int(tm->tm_wday);
  Field(res,7) = Val_int(tm->tm_yday);
  Field(res,8) = tm->tm_isdst ? Val_true : Val_false;
  return res;
}

static struct tm*
tm_val(value t, struct tm *tm) {
  if (!tm)
    return NULL;
  tm->tm_sec = Int_val(Field(t, 0));
  tm->tm_min = Int_val(Field(t, 1));
  tm->tm_hour = Int_val(Field(t, 2));
  tm->tm_mday = Int_val(Field(t, 3));
  tm->tm_mon = Int_val(Field(t, 4));
  tm->tm_year = Int_val(Field(t, 5));
  tm->tm_wday = Int_val(Field(t, 6));
  tm->tm_yday = Int_val(Field(t, 7));
  tm->tm_isdst = -1; /* tm.tm_isdst = Bool_val(Field(t, 8)); */
  return tm;
}

value
stew_time_int32(value unit) {
  time_t t;

  time(&t);

  return copy_int32(t);
}

value
stew_ctime(value for_time) {
  CAMLparam1(for_time);
  CAMLlocal1(ret);
  time_t t1 = (time_t)Int32_val(for_time);
  char buf[50];

  ctime_r(&t1, buf);

  ret = copy_string(buf);

  CAMLreturn(ret);
}

value
stew_asctime(value for_time) {
  CAMLparam1(for_time);
  CAMLlocal1(ret);
  struct tm tm;
  char buf[50];

  tm_val(for_time, &tm);

  asctime_r(&tm, buf);

  ret = copy_string(buf);

  CAMLreturn(ret);
}

/* 
value
stew_strftime_time(value fmt, value for_time) {
  CAMLparam2(fmt, for_time);
  CAMLlocal1(ret);
  time_t t1 = (time_t)Int32_val(for_time);
  struct tm t2;
  char buf[8192];

  localtime_r(&t1, &t2);

  strftime(buf, sizeof buf, String_val(fmt), &t2);

  ret = copy_string(buf);

  CAMLreturn(ret);
}
*/

value
stew_strftime_tm(value fmt, value for_time) {
  CAMLparam2(fmt, for_time);
  CAMLlocal1(ret);
  struct tm tm = {0};
  char buf[8192];

  tm_val(for_time, &tm);
  
  strftime(buf, sizeof buf, String_val(fmt), &tm);

  ret = copy_string(buf);

  CAMLreturn(ret);
}

value
stew_strptime_tm(value time_str, value fmt)
{
#ifdef HAVE_STRPTIME
  CAMLparam2(time_str, fmt);
  CAMLlocal1(ret);
  struct tm tm = {0};
  char *s;

  s = strptime(String_val(time_str), String_val(fmt), &tm);

  if (s == NULL || *s != '\0') {
    failwith("extern C function stew_strptime_tm failed");
  }

  ret = alloc_tm(&tm);

  CAMLreturn(ret);
#else
  failwith("stew_strptime_tm not implemented");
#endif
}

value stew_gmtime(value t)
{
  time_t tyme;
  struct tm tm;
  tyme = (time_t) Int32_val(t);
  gmtime_r(&tyme, &tm);
  return alloc_tm(&tm);
}

value stew_localtime(value t)
{
  time_t tyme;
  struct tm tm;
  tyme = (time_t) Int32_val(t);
  localtime_r(&tyme, &tm);
  return alloc_tm(&tm);
}

value stew_mktime(value t)
{
  CAMLparam1(t);
  CAMLlocal3(res, clockval, tmval);
  struct tm tm;
  time_t tyme;
  tm_val(t, &tm);
  tyme = mktime(&tm);
  tmval = alloc_tm(&tm);
  clockval = copy_int32(tyme);
  res = alloc_tuple(2);
  Field(res, 0) = clockval;
  Field(res, 1) = tmval;
  CAMLreturn(res);
}

value stew_difftime(value a, value b) {
  return copy_double(difftime((time_t)Int32_val(a), (time_t)Int32_val(b)));
}

value stew_sendfile(value o, value i, value off, value count) {
  size_t len;
  off_t offset, wrote;
  int infd, outfd;


  infd = Int_val(o);
  outfd = Int_val(i);
  offset = Int_val(off);
  len = Int_val(count);

#if defined(linux)
  enter_blocking_section();
  len = sendfile(outfd, infd, &offset, len);
  leave_blocking_section();
  if (len >= 0)
    return Val_int(len);
  else
    uerror("sendfile", Nothing);
#elif defined(__FreeBSD__)
  enter_blocking_section();
  len = sendfile(outfd, infd, offset, len,
		 NULL, &wrote, 0);
  leave_blocking_section();
  if (len == 0)
    return Val_int(wrote);
  else
    uerror("sendfile", Nothing);
#else
      failwith("sendfile unimplemented!");
#endif

}

/* From getserv.c */
#ifdef HAVE_GETSERVENT
static value alloc_service_entry(struct servent *entry)
{
  value res;
  value name = Val_unit, aliases = Val_unit, proto = Val_unit;

  Begin_roots3 (name, aliases, proto);
    name = copy_string(entry->s_name);
    aliases = copy_string_array((const char**)entry->s_aliases);
    proto = copy_string(entry->s_proto);
    res = alloc_tuple(4);
    Field(res,0) = name;
    Field(res,1) = aliases;
    Field(res,2) = Val_int(ntohs(entry->s_port));
    Field(res,3) = proto;
  End_roots();
  return res;
}
#endif

value stew_getservent(value unit) {
#ifdef HAVE_GETSERVENT
  struct servent *s = getservent();
  if (s == NULL)
    raise_not_found();
  return alloc_service_entry(s);
#else
  failwith("getservent unimplemented");
#endif
}

value stew_setservent(value stay) {
#ifdef HAVE_SETSERVENT
  setservent(Bool_val(stay));
  return Val_unit;
#else
  failwith("setservent unimplemented");
#endif
}

value stew_endservent(value unit) {
#ifdef HAVE_ENDSERVENT
  endservent();
  return Val_unit;
#else
  failwith("endservent unimplemented");
#endif
}

value stew_hash_variant(value s) {
  return hash_variant(String_val(s));
}

value stew_strcoll(value a, value b) {
  int r;

  r = strcoll(String_val(a), String_val(b));

  if (r < 0)
    return Val_int(-1);
  else if (r == 0)
    return Val_int(0);
  else 
    return Val_int(1);
}

value stew_pread(value fd, value buf, value vofs, value len) {
#ifdef HAVE_PREAD
  long numbytes, ofs;
  int ret;
  char iobuf[UNIX_BUFFER_SIZE];
  
  Begin_root(buf);
  numbytes = Long_val(len);
  ofs = Long_val(vofs);
  if (numbytes > UNIX_BUFFER_SIZE) numbytes = UNIX_BUFFER_SIZE;
  enter_blocking_section();
  ret = pread(Int_val(fd), iobuf, ofs, (int) numbytes);
  leave_blocking_section();
  if (ret == -1) uerror("pread", Nothing);
  memmove(&Byte(buf, ofs), iobuf, ret);
  End_roots();
  return Val_int(ret);
#else
  failwith("pread unimplemented");
#endif
}

value stew_pwrite(value fd, value buf, value vofs, value vlen) {
#ifdef HAVE_PWRITE
  long ofs, len, written;
  int numbytes, ret;
  char iobuf[UNIX_BUFFER_SIZE];
  
  Begin_root(buf);
  ofs = Long_val(vofs);
  len = Long_val(vlen);
  written = 0;
  while (len > 0) {
    numbytes = len > UNIX_BUFFER_SIZE ? UNIX_BUFFER_SIZE : len;
    memmove(iobuf, &Byte(buf, ofs), numbytes);
    enter_blocking_section();
    ret = pwrite(Int_val(fd), iobuf, ofs, numbytes);
    leave_blocking_section();
    if (ret == -1) {
      if ((errno == EAGAIN || errno == EWOULDBLOCK) && written > 0)
	break;
      uerror("pwrite", Nothing);
    }
    written += ret;
    ofs += ret;
    len -= ret;
  }
  End_roots();
  return Val_long(written);
#else
  failwith("pwrite unimplemented");
#endif
}

value stew_gettimeofday(value u) {
#ifdef HAVE_GETTIMEOFDAY
  struct timeval t;
  CAMLparam1(u);
  CAMLlocal3(res, sec, usec);

  if (gettimeofday(&t, NULL))
    uerror("gettimeofday", Nothing);

  sec = copy_int32(t.tv_sec);
  usec = copy_int32(t.tv_usec);
  res = alloc_tuple(2);
  Field(res, 0) = sec;
  Field(res, 1) = usec;
  CAMLreturn(res);
#else
  failwith("gettimeofday unimplemented");
#endif
}

value stew_getrusage(value whov) {
#ifdef HAVE_GETRUSAGE
  struct rusage r;
  int who;
  CAMLparam1(whov);
  CAMLlocal5(res, utime, stime, sec, usec);

  if (Int_val(whov) == 0)
    who = RUSAGE_SELF;
  else
    who = RUSAGE_CHILDREN;

  if (getrusage(who, &r))
    uerror("getrusage", Nothing);

  sec = copy_int32(r.ru_utime.tv_sec);
  usec = copy_int32(r.ru_utime.tv_usec);
  utime = alloc_tuple(2);
  Field(utime, 0) = sec;
  Field(utime, 1) = usec;

  sec = copy_int32(r.ru_stime.tv_sec);
  usec = copy_int32(r.ru_stime.tv_usec);
  stime = alloc_tuple(2);
  Field(stime, 0) = sec;
  Field(stime, 1) = usec;

  res = alloc_tuple(16);
  Field(res, 0) = utime;
  Field(res, 1) = stime;
    
#ifdef __APPLE__
  /* For some reason, the OS X headers only define these
     structures if _POSIX_C_SOURCE is undefined. However, since
     the headers seem to always define this, #undefing it doesn't
     do any good. *sigh*
  */
  {
    int n;
    for (n = 2; n < 16; n++)
      Field(res, n) = Val_long(0);
  }
#else
  Field(res, 2) = Val_long(r.ru_maxrss);
  Field(res, 3) = Val_long(r.ru_ixrss);
  Field(res, 4) = Val_long(r.ru_idrss);
  Field(res, 5) = Val_long(r.ru_isrss);
  Field(res, 6) = Val_long(r.ru_minflt);
  Field(res, 7) = Val_long(r.ru_majflt);
  Field(res, 8) = Val_long(r.ru_nswap);
  Field(res, 9) = Val_long(r.ru_inblock);
  Field(res, 10) = Val_long(r.ru_oublock);
  Field(res, 11) = Val_long(r.ru_msgsnd);
  Field(res, 12) = Val_long(r.ru_msgrcv);
  Field(res, 13) = Val_long(r.ru_nsignals);
  Field(res, 14) = Val_long(r.ru_nvcsw);
  Field(res, 15) = Val_long(r.ru_nivcsw);
#endif
  CAMLreturn(res);

#else
  failwith("getrusage unimplemented");
#endif
}

value stew_getrlimit(value resource) {
#ifdef HAVE_GETRLIMIT
  CAMLparam1(resource);
  CAMLlocal1(retval);
  int r;
  struct rlimit lim;

  switch (Int_val(resource)) {
  case 0:
    r = RLIMIT_CPU;
    break;
  case 1:
    r = RLIMIT_FSIZE;
    break;
  case 2:
    r = RLIMIT_DATA;
    break;
  case 3:
    r = RLIMIT_STACK;
    break;
  case 4:
    r = RLIMIT_CORE;
    break;
  case 5:
#ifdef RLIMIT_RSS
    r = RLIMIT_RSS;
#else
    failwith("RSS is not defined by your os");
#endif
    break;
  case 6:
#ifdef RLIMIT_NPROC
    r = RLIMIT_NPROC;
#else
    failwith("NPROC not implimented by your OS");
#endif
    break;
  case 7:
#ifdef RLIMIT_NOFILE
    r = RLIMIT_NOFILE;
#else
    r = RLIMIT_OFILE:
#endif
    break;
  case 8:
#ifdef RLIMIT_MEMLOCK
    r = RLIMIT_MEMLOCK;
#else
    failwith("MEMLOCK is not defined by your os");
#endif
    break;
#if defined(linux)
  case 9:
    r = RLIMIT_AS;
    break;
#endif
  default:
    errno = EINVAL;
    uerror("getrlimit", Nothing);    
  }

  if (getrlimit(r, &lim) < 0) 
    uerror("getrlimit", Nothing);

  
  retval = alloc_tuple(2);
  Field(retval, 0) = Val_int(lim.rlim_cur);
  Field(retval, 1) = Val_int(lim.rlim_max);

  CAMLreturn(retval);

#else
  failwith("getrlimit unimplemented");
#endif
}

value stew_setrlimit(value resource, value rlimit) {
#ifdef HAVE_SETRLIMIT
  int r;
  struct rlimit lim;

  switch (Int_val(resource)) {
  case 0:
    r = RLIMIT_CPU;
    break;
  case 1:
    r = RLIMIT_FSIZE;
    break;
  case 2:
    r = RLIMIT_DATA;
    break;
  case 3:
    r = RLIMIT_STACK;
    break;
  case 4:
    r = RLIMIT_CORE;
    break;
  case 5:
#ifdef RLIMIT_RSS
    r = RLIMIT_RSS;
#else
    failwith("RSS is not defined by your os");
#endif
    break;
  case 6:
#ifdef RLIMIT_NPROC
    r = RLIMIT_NPROC;
#else
    failwith("NPROC is not defined by your os");
#endif
    break;
  case 7:
#ifdef RLIMIT_NOFILE
    r = RLIMIT_NOFILE;
#else
    r = RLIMIT_OFILE:
#endif
    break;
  case 8:
#ifdef RLIMIT_MEMLOCK
    r = RLIMIT_MEMLOCK;
#else
    failwith("MEMLOCK is not defined by your os");
#endif
    break;
#if defined(linux)
  case 9:
    r = RLIMIT_AS;
    break;
#endif
  default:
    errno = EINVAL;
    uerror("getrlimit", Nothing);    
  }

  lim.rlim_cur = Int_val(Field(rlimit, 0));
  lim.rlim_max = Int_val(Field(rlimit, 1));

  if (setrlimit(r, &lim) < 0) 
    uerror("setrlimit", Nothing);

  return Val_unit;

#else
  failwith("getrlimit unimplemented");
#endif
}

value
stew_setpgid(value pid, value pgid) {
#ifdef HAVE_SETPGID
  if (setpgid(Int_val(pid), Int_val(pgid)) < 0)
    uerror("setpgid", Nothing);
  return Val_unit;
#else
  failwith("setgpid unimplemented");
#endif
}

value
stew_getpgid(value pid) {
#ifdef HAVE_GETPGID
  int ret = getpgid(Int_val(pid));
  if (ret < 0)
    uerror("getpgid", Nothing);
  return Val_int(ret);
#else
  failwith("getgpid unimplemented");
#endif
}

value
stew_getpgrp(value unit) {
#ifdef HAVE_GETPGRP
  int ret = getpgrp();
  if (ret < 0)
    uerror("getpgrp", Nothing);
  return Val_int(ret);
#else
  failwith("getpgrp unimplemented");
#endif
}

value
stew_setpgrp(value unit) {
#if defined(HAVE_SETPGRP)
  /* This needs a better test to tell if we have 0, 1, or 2 argument
     forms of setpgrp() */
#if defined(__FreeBSD__) || defined(__APPLE__)
  if (setpgid(0,0) < 0) 
#else
  if (setpgrp() < 0) 
#endif
    uerror("setpgrp", Nothing);
  return Val_unit;
#else /* !HAVE_SETPGRP */
  failwith("setpgrp unimplemented");
#endif
}


#if defined(__APPLE__) && !defined(makedev)
#define major(x)        ((int32_t)(((u_int32_t)(x) >> 24) & 0xff))
#define minor(x)        ((int32_t)((x) & 0xffffff))
#define makedev(x,y)    ((dev_t)(((x) << 24) | (y)))
#endif


value
stew_mknod(value fname, value mod, value type, value major, value minor) {
#ifdef HAVE_MKNOD
  dev_t dev;
  mode_t mode = Int_val(mod);

  dev = makedev(Int_val(major), Int_val(minor));

  if (Int_val(type) == 0) 
    mode |= S_IFBLK;
  else
    mode |= S_IFCHR;

  if (mknod(String_val(fname), mode, dev) < 0)
    uerror("mknod", fname);

  return Val_unit;
#else
  failwith("mknod unimplemented");
#endif
}



syntax highlighted by Code2HTML, v. 0.9.1