/* Copyright (C) 1994, 1995, 1998 Free Software Foundation, Inc.
 * 
 * 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, 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 software; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * As a special exception, the Free Software Foundation gives permission
 * for additional uses of the text contained in its release of GUILE.
 *
 * The exception is that, if you link the GUILE library with other files
 * to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the GUILE library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the
 * Free Software Foundation under the name GUILE.  If you copy
 * code from other Free Software Foundation releases into a copy of
 * GUILE, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for GUILE, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.  
 */

/* "posix.c" functions only in Posix (unix).
   Author: Aubrey Jaffer */

/* I probably don't need to test for most of these.  Oh well. */

#include "scm.h"

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
/* added by Denys Duchier: for kill */
#include <signal.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#elif !defined(STDC_HEADERS)
	char *ttyname P((int fd));
	FILE *popen P((const char* command, const char* type));
	int pclose P((FILE* stream));
#endif

#ifdef HAVE_CHOWN
     /* Only the superuser can successfully execute this call */
static char s_chown[] = "chown";
SCM l_chown(path, owner, group)
     SCM path, owner, group;
{
  int val;
  ASSERT(NIMP(path) && STRINGP(path), path, ARG1, s_chown);
  ASSERT(INUMP(owner), owner, ARG2, s_chown);
  ASSERT(INUMP(group), group, ARG3, s_chown);
  SYSCALL(val = chown(CHARS(path), INUM(owner), INUM(group)););
  return val ? BOOL_F : BOOL_T;
}
#endif

#ifdef HAVE_LINK
static char s_link[] = "link";
SCM l_link(oldpath, newpath)
     SCM oldpath, newpath;
{
  int val;
  ASSERT(NIMP(oldpath) && STRINGP(oldpath), oldpath, ARG1, s_link);
  ASSERT(NIMP(newpath) && STRINGP(newpath), newpath, ARG2, s_link);
  SYSCALL(val = link(CHARS(oldpath), CHARS(newpath)););
  return val ? BOOL_F : BOOL_T;
}
#endif

#ifdef HAVE_PIPE
SCM l_pipe()
{
  int fd[2], ret;
  FILE *f_rd, *f_wt;
  SCM p_rd, p_wt;
  NEWCELL(p_rd); NEWCELL(p_wt);
  SYSCALL(ret = pipe(fd););
  if (ret) {ALLOW_INTS; return BOOL_F;}
  SYSCALL(f_rd = fdopen(fd[0], "r"););
  if (!f_rd) {
    close(fd[0]);
    goto errout;
  }
  SYSCALL(f_wt = fdopen(fd[1], "w"););
  if (!f_wt) {
    fclose(f_rd);
  errout:
    close(fd[1]);
    wta(UNDEFINED, (char *)NALLOC, s_port_type);
  }
  CAR(p_rd) = tc16_fport | mode_bits("r");
  CAR(p_wt) = tc16_fport | mode_bits("w");
  SETSTREAM(p_rd, f_rd);
  SETSTREAM(p_wt, f_wt);
  ALLOW_INTS;
  return cons(p_rd, p_wt);
}
#endif

#ifdef HAVE_POPEN
char	s_op_pipe[] = "open-pipe";
SCM open_pipe(pipestr, modes)
     SCM pipestr, modes;
{
	FILE *f;
	register SCM z;
	ASSERT(NIMP(pipestr) && STRINGP(pipestr), pipestr, ARG1, s_op_pipe);
	ASSERT(NIMP(modes) && STRINGP(modes), modes, ARG2, s_op_pipe);
	NEWCELL(z);
	/* DEFER_INTS, SYSCALL, and ALLOW_INTS are probably paranoid here*/
	DEFER_INTS;
	ignore_signals();
	SYSCALL(f = popen(CHARS(pipestr), CHARS(modes)););
	unignore_signals();
	if (!f) z = BOOL_F;
	else {
	  CAR(z) = tc16_pipe | OPN | (strchr(CHARS(modes), 'r') ? RDNG : WRTNG);
	  SETSTREAM(z, f);
	}
	ALLOW_INTS;
	return z;
}
static int prinpipe(exp, port, writing)
     SCM exp; SCM port; int writing;
{
  prinport(exp, port, s_pipe);
  return !0;
}
#endif

#ifdef HAVE_GETGROUPS
static char scm_s_getgroups[] = "getgroups";
SCM scm_getgroups()
{
  SCM grps, ans;
  int ngroups = getgroups(0, 0);
  if (!ngroups) return BOOL_F;
  NEWCELL(grps);
  DEFER_INTS;
  /* grps is used as a gc protect, its type used to be tc7_string, but
     strings are now checked for null termination during gc. */
  grps = must_malloc_cell(ngroups * sizeof(gid_t), scm_s_getgroups);
  /* length need not be exactly right */
  SETLENGTH(grps, (0L + ngroups * sizeof(gid_t))/sizeof(long), tc7_uvect);
  ALLOW_INTS;
  {
    gid_t *groups = (gid_t *)CHARS(grps);
    int val = getgroups(ngroups, groups);
    if (val < 0) return BOOL_F;
    ans = make_vector(MAKINUM(ngroups), UNDEFINED);
    while (--ngroups >= 0) VELTS(ans)[ngroups] = MAKINUM(groups[ngroups]);
    return ans;
  }
}  
#endif

/* These 2 routines are not protected against `entry' being reused
   before access to that structure is completed */

#ifdef HAVE_GETPWENT
static char s_pwinfo[] = "getpw";
SCM l_pwinfo(user)
     SCM user;
{
  SCM ans = make_vector(MAKINUM(7), UNSPECIFIED);
  struct passwd *entry;
  SCM *ve = VELTS(ans);
  DEFER_INTS;
  if UNBNDP(user) SYSCALL(entry = getpwent(););
  else if INUMP(user) SYSCALL(entry = getpwuid(INUM(user)););
  else {
    ASSERT(NIMP(user) && STRINGP(user), user, ARG1, s_pwinfo);
    SYSCALL(entry = getpwnam(CHARS(user)););
  }
  ALLOW_INTS;
  if (!entry) return BOOL_F;
  ve[ 0] = makfrom0str(entry->pw_name);
  ve[ 1] = makfrom0str(entry->pw_passwd);
  ve[ 2] = ulong2num((unsigned long)entry->pw_uid);
  ve[ 3] = ulong2num((unsigned long)entry->pw_gid);
  ve[ 4] = makfrom0str(entry->pw_gecos);
  ve[ 5] = makfrom0str(entry->pw_dir);
  ve[ 6] = makfrom0str(entry->pw_shell);
  return ans;
}
#endif

#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_GETGRENT
static char s_grinfo[] = "getgr";
SCM l_grinfo(name)
     SCM name;
{
  SCM ans = make_vector(MAKINUM(4), UNSPECIFIED);
  struct group *entry;
  SCM *ve = VELTS(ans);
  DEFER_INTS;
  if UNBNDP(name) SYSCALL(entry = getgrent(););
  else if INUMP(name) SYSCALL(entry = getgrgid(INUM(name)););
  else {
    ASSERT(NIMP(name) && STRINGP(name), name, ARG1, s_grinfo);
    SYSCALL(entry = getgrnam(CHARS(name)););
  }
  ALLOW_INTS;
  if (!entry) return BOOL_F;
  ve[ 0] = makfrom0str(entry->gr_name);
  ve[ 1] = makfrom0str(entry->gr_passwd);
  ve[ 2] = ulong2num((unsigned long)entry->gr_gid);
  ve[ 3] = makfromstrs(-1, entry->gr_mem);
  return ans;
}
SCM l_setgr(arg)
     SCM arg;
{
  if (UNBNDP(arg) || FALSEP(arg)) endgrent();
  else setgrent();
  return UNSPECIFIED;
}
#endif

#ifdef HAVE_GETPWENT
SCM l_setpw(arg)
     SCM arg;
{
  if (UNBNDP(arg) || FALSEP(arg)) endpwent();
  else setpwent();
  return UNSPECIFIED;
}
#endif

#ifdef HAVE_KILL
static char s_kill[] = "kill";
SCM l_kill(pid, sig)
     SCM pid, sig;
{
  int i;
  ASSERT(INUMP(pid), pid, ARG1, s_kill);
  ASSERT(INUMP(sig), sig, ARG2, s_kill);
  SYSCALL(i = kill((int)INUM(pid), (int)INUM(sig)););
  return MAKINUM(0L+i);
}
#endif

#ifdef HAVE_WAITPID
static char s_waitpid[] = "waitpid";
SCM l_waitpid(pid, options)
     SCM pid, options;
{
  int i, status;
  ASSERT(INUMP(pid), pid, ARG1, s_waitpid);
  ASSERT(INUMP(options), options, ARG2, s_waitpid);
  SYSCALL(i = waitpid(INUM(pid), &status, INUM(options)););
  return i < 0 ? BOOL_F : MAKINUM(0L+status);
}
#endif

#ifdef HAVE_GETPPID
SCM l_getppid()
{
  return MAKINUM(0L+getppid());
}
#endif

#ifdef HAVE_GETUID
SCM l_getuid()
{
  return MAKINUM(0L+getuid());
}
SCM l_getgid()
{
  return MAKINUM(0L+getgid());
}
#endif

#ifdef HAVE_GETEUID
SCM l_geteuid()
{
  return MAKINUM(0L+geteuid());
}
SCM l_getegid()
{
  return MAKINUM(0L+getegid());
}
#endif

#ifdef HAVE_SETUID
static char s_setuid[] = "setuid";
SCM l_setuid(id)
     SCM id;
{
  ASSERT(INUMP(id), id, ARG1, s_setuid);
  return setuid(INUM(id)) ? BOOL_F : BOOL_T;
}
static char s_setgid[] = "setgid";
SCM l_setgid(id)
     SCM id;
{
  ASSERT(INUMP(id), id, ARG1, s_setgid);
  return setgid(INUM(id)) ? BOOL_F : BOOL_T;
}
#endif

#ifdef HAVE_SETEUID
static char s_seteuid[] = "seteuid";
SCM l_seteuid(id)
     SCM id;
{
  ASSERT(INUMP(id), id, ARG1, s_seteuid);
  return seteuid(INUM(id)) ? BOOL_F : BOOL_T;
}
static char s_setegid[] = "setegid";
SCM l_setegid(id)
     SCM id;
{
  ASSERT(INUMP(id), id, ARG1, s_setegid);
  return setegid(INUM(id)) ? BOOL_F : BOOL_T;
}
#endif

#ifdef HAVE_TTYNAME
static char s_ttyname[] = "ttyname";
SCM l_ttyname(port)
     SCM port;
{
  char *ans;
  ASSERT(NIMP(port) && OPPORTP(port), port, ARG1, s_ttyname);
  if (tc16_fport != TYP16(port)) return BOOL_F;
  SYSCALL(ans = ttyname(fileno(STREAM(port))););
  /* ans could be overwritten by another call to ttyname */
  return ans ? makfrom0str(ans) : BOOL_F;
}
#endif

#ifdef HAVE_FORK
SCM l_fork()
{
  long pid = 0L + fork();
  return -1L==pid ? BOOL_F : MAKINUM(pid);
}
#endif

#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_UNAME
SCM l_uname()
{
  struct utsname buf;
  SCM ans = make_vector(MAKINUM(5), UNSPECIFIED);
  SCM *ve = VELTS(ans);
  if (uname(&buf) < 0) return BOOL_F;
  ve[ 0] = makfrom0str(buf.sysname);
  ve[ 1] = makfrom0str(buf.nodename);
  ve[ 2] = makfrom0str(buf.release);
  ve[ 3] = makfrom0str(buf.version);
  ve[ 4] = makfrom0str(buf.machine);
  /* ve[ 5] = makfrom0str(buf.domainname); */
  return ans;
}
#endif

#ifdef HAVE_SETSID
SCM l_setsid(void)
{
  return long2num(setsid());
}
#endif

static iproc subr0s[] = {
#ifdef HAVE_PIPE
	{"pipe", l_pipe},
#endif
#ifdef HAVE_GETGROUPS
	{scm_s_getgroups, scm_getgroups},
#endif
#ifdef HAVE_GETPPID
	{"getppid", l_getppid},
#endif
#ifdef HAVE_GETUID
	{"getuid", l_getuid},
	{"getgid", l_getgid},
#endif
#ifdef HAVE_GETEUID
	{"getegid", l_getegid},
	{"geteuid", l_geteuid},
#endif
#ifdef HAVE_UNAME
	{"uname", l_uname},
#endif
#ifdef HAVE_FORK
	{"fork", l_fork},
#endif
#ifdef HAVE_SETSID
	{"setsid", l_setsid},
#endif
	{0, 0}};

static iproc subr1os[] = {
#ifdef HAVE_GETPWENT
	{s_pwinfo, l_pwinfo},
	{"setpwent", l_setpw},
#endif
#ifdef HAVE_GETGRENT
	{s_grinfo, l_grinfo},
	{"setgrent", l_setgr},
#endif
	{0, 0}};

static iproc subr1s[] = {
#ifdef HAVE_SETUID
	{"setuid", l_setuid},
	{"setgid", l_setgid},
#endif
#ifdef HAVE_SETEUID
	{"setegid", l_setegid},
	{"seteuid", l_seteuid},
#endif
#ifdef HAVE_TTYNAME
	{s_ttyname, l_ttyname},
#endif
	{0, 0}};

static iproc subr2s[] = {
#ifdef HAVE_LINK
	{s_link, l_link},
#endif
#ifdef HAVE_KILL
	{s_kill, l_kill},
#endif
#ifdef HAVE_WAITPID
	{s_waitpid, l_waitpid},
#endif
#ifdef HAVE_POPEN
	{s_op_pipe, open_pipe},
#endif
	{0, 0}};

static iproc subr3s[] = {
#ifdef HAVE_CHOWN
	{s_chown, l_chown},
#endif
	{0, 0}};

void init_posix()
{
	init_iprocs(subr0s, tc7_subr_0);
	init_iprocs(subr1s, tc7_subr_1);
	init_iprocs(subr1os, tc7_subr_1o);
	init_iprocs(subr2s, tc7_subr_2);
	init_iprocs(subr3s, tc7_subr_3);
	add_feature("posix");
#ifdef HAVE_POPEN
	ptobs[0x0ff & (tc16_pipe>>8)].fclose = pclose;
	ptobs[0x0ff & (tc16_pipe>>8)].free = pclose;
	ptobs[0x0ff & (tc16_pipe>>8)].print = prinpipe;
	add_feature(s_pipe);
#endif
	scm_ldstr("\n\
(define (open-input-pipe cmd) (open-pipe cmd \"r\"))\n\
(define (open-output-pipe cmd) (open-pipe cmd \"w\"))\n\
");
}
