/* -*- mode: C; mode: fold; -*- */
/*
Copyright (C) 2007 John E. Davis

This file is part of the S-Lang grace Module

The S-Lang grace Module 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.

The S-Lang grace Module 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 library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.

*/

/* Note: grace_np is not used.  This is because it contains 
 * system calls that do not check for errno==EINTR, which can occur
 * if a system call is interrupted by a signal and the disposition of
 * the signal is set to no restart the system calls. 
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <string.h>

#include <limits.h>		       /* OPEN_MAX */

#include <slang.h>

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

#include <errno.h>

#ifdef __cplusplus
extern "C" 
{
#endif
SLANG_MODULE(grace);
#ifdef __cplusplus
}
#endif

#include "version.h"

#ifndef OPEN_MAX
# define OPEN_MAX 256
#endif

static int Grace_Type_Id = -1;

static int is_interrupt (int e, int run_hooks)
{
   int ok = 0;

#ifdef EINTR
   if (e == EINTR) ok++;
#endif
#ifdef EAGAIN
   if (e == EAGAIN) ok++;
#endif
#ifdef ERESTARTSYS
   if (e == ERESTARTSYS) ok++;
#endif
   
   if (ok)
     {
	if (run_hooks && (-1 == SLang_handle_interrupt ()))
	  return 0;
	return 1;
     }

   SLerrno_set_errno (e);
   return 0;
}

static int signal_safe_close (int fd, int run_hooks)
{
   while (-1 == close (fd))
     {
	if (is_interrupt (errno, run_hooks))
	  continue;

	return -1;
     }
   return 0;
}

static int open_grace (int argc, char **argv, pid_t *pidp)
{
   int fds[2];
   pid_t pid;
   
   while (-1 == pipe(fds))
     {
	if (0 == is_interrupt (errno, 1))
	  return -1;
     }

   while (-1 == (int)(pid = fork ()))
     {
	if (0 == is_interrupt (errno, 1))
	  {
	     signal_safe_close (fds[0], 1);
	     signal_safe_close (fds[1], 1);
	     return -1;
	  }
     }
   
   if (pid == 0)
     {
	/* child */
	int i, fd;
	char **args;
	char buf[32];
	/* Insert -dpipe N ... NULL into the arg list --- need 3 additional elements */
	if (NULL == (args = (char **) SLmalloc ((argc+3)*sizeof(char **))))
	  {
	     (void) fprintf (stderr, "grace: out of memory\n");
	     _exit(127);
	  }
	fd = fds[0];
	(void) sprintf (buf, "%d", fd);
	args[0] = argv[0];
	args[1] = "-dpipe";
	args[2] = buf;
	for (i = 1; i < argc; i++)
	  args[2+i] = argv[i];
	args[2+argc] = NULL;
	
	/* Now close the unwanted open descriptors */
	for (i = 3; i < OPEN_MAX; i++)
	  {
	     if (i != fd)
	       (void) signal_safe_close (i, 0);
	  }
	(void) setpgid (0,0);
	(void) execvp (args[0], args);
	_exit (127);
     }

   /* parent */
   signal_safe_close (fds[0], 1);
   *pidp = pid;
   
   return fds[1];
}

typedef struct _Grace_Type
{
   pid_t pid;
   int fd;
   int kill_on_exit;
   struct _Grace_Type *next;
}
Grace_Type;

static Grace_Type *Grace_Root = NULL;

static int perform_write (int fd, char *buf, int buflen)
{
   int len;

   len = 0;
   while (len < buflen)
     {
	int dlen;
	while (-1 == (dlen = write (fd, buf + len, buflen - len)))
	  {
	     if (1 == is_interrupt (errno, 1))
	       continue;
	     
	     return len;
	  }

	len += dlen;
     }

   return len;
}

static int send_command (Grace_Type *g, char *cmd)
{
   int len;

   if (g->fd == -1)
     return -1;
   
   len = (int) strlen (cmd);
   if (len != perform_write (g->fd, cmd, len))
     return -1;
   
   return 0;
}

static void close_this_grace (Grace_Type *g, int reap_pid)
{
   if (g->fd != -1)
     {
	if (-1 == send_command (g, "exit\n"))
	  (void) kill (g->pid, SIGTERM);
	(void) signal_safe_close (g->fd, 0);
	if (reap_pid)
	  {
	     int status;
	     while (-1 == (int) waitpid (g->pid, &status, 0))
	       {
		  if (is_interrupt (errno, 1))
		    continue;
		  break;	       /* be a zombie */
	       }
	  }
     }
   SLfree ((char *)g);
}

static void close_grace (Grace_Type *g)
{
   Grace_Type *g1;
   
   g1 = Grace_Root;
   if (g == g1)
     {
	Grace_Root = g->next;
     }
   else while (g1 != NULL)
     {
	if (g1->next != g)
	  {
	     g1 = g1->next;
	     continue;
	  }
	g1->next = g->next;
	break;
     }
   close_this_grace (g, 1);
}

static void cleanup_grace_jobs (void)
{
   Grace_Type *g = Grace_Root;

   while (g != NULL)
     {
	Grace_Type *gnext = g->next;
	if (g->kill_on_exit)
	  close_this_grace (g, 0);
	g = gnext;
     }
}

static Grace_Type *allocate_grace_type (int fd, pid_t pid, int kill_on_exit)
{
   Grace_Type *g;

   if (NULL == (g = (Grace_Type *) SLmalloc (sizeof (Grace_Type))))
     return NULL;
   
   g->pid = pid;
   g->fd = fd;
   g->next = Grace_Root;
   g->kill_on_exit = kill_on_exit;
   Grace_Root = g;
   return g;
}

static Grace_Type *grace_from_fd (SLFile_FD_Type *f)
{
   Grace_Type *g;

   if (-1 == SLfile_get_clientdata (f, Grace_Type_Id, (VOID_STAR *)(void *)&g))
     {
	SLang_verror (SL_TypeMismatch_Error, "File descriptor does not represent a Grace one");
	return NULL;
     }
   return g;
}
						     
/* This function gets called when the variable goes out of scope to close
 * the grace_type attached to the descriptor. However, we want an
 * explicit call to grace_close to close the descriptor. So here, we
 * do nothing, unless kill_on_exit is non-zero.
 */
static int close_grace_callback (VOID_STAR cd)
{
   Grace_Type *g = (Grace_Type *)cd;

   if (g == NULL)
     return 0;

   if (g->kill_on_exit)
     {
	close_grace (g);
	return 0;
     }
   return -1;
}

static void close_grace_intrin (SLFile_FD_Type *f)
{
   Grace_Type *g;
   
   if (NULL == (g = grace_from_fd (f)))
     return;

   /* we are done with this descriptor so remove if from f */
   (void) SLfile_set_clientdata (f, NULL, NULL, Grace_Type_Id);
   close_grace (g);
}

static SLFile_FD_Type *grace_to_fd (int fd, pid_t pid, int kill_on_exit)
{
   SLFile_FD_Type *f;
   Grace_Type *g;
   
   if (NULL == (g = allocate_grace_type (fd, pid, kill_on_exit)))
     return NULL;

   if (NULL == (f = SLfile_create_fd ("*grace*", fd)))
     {
	close_grace (g);
	return NULL;
     }
      
   (void) SLfile_set_clientdata (f, NULL, (VOID_STAR)g, Grace_Type_Id);
   (void) SLfile_set_close_method (f, close_grace_callback);
   
   return f;
}

static void open_grace_intrin (void)
{
   SLFile_FD_Type *f;
   SLang_Array_Type *at = NULL;
   char *pgm;
   int is_batch = 0;
   int fd;
   pid_t pid;

   switch (SLang_Num_Function_Args)
     {
      case 1:
	if (-1 == SLang_pop_array_of_type (&at, SLANG_STRING_TYPE))
	  return;
	if (at->num_elements == 0)
	  {
	     SLang_verror (SL_INVALID_PARM, "grace_open: argument list is empty");
	     SLang_free_array (at);
	     return;
	  }

	pgm = *(char **) at->data;
	pgm = SLpath_basename (pgm);
	if ((pgm != NULL) && (0 == strcmp (pgm, "gracebat")))
	  is_batch = 1;

	fd = open_grace (at->num_elements, (char **)at->data, &pid);
	SLang_free_array (at);
	break;

      default:
	SLang_verror (SL_Usage_Error, "Usage: fd = grace_open (argv)");
	return;
     }
   
   if (fd == -1)
     {
	SLang_push_null ();
	return;
     }

   if (NULL == (f = grace_to_fd (fd, pid, is_batch)))
     {
	signal_safe_close (fd, 1);
	return;
     }

   if (-1 == SLfile_push_fd (f))
     close_grace_intrin (f);
   
   SLfile_free_fd (f);
}

static SLang_Intrin_Fun_Type Module_Intrinsics [] =
{
   MAKE_INTRINSIC_0("_grace_open", open_grace_intrin, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_1("_grace_close", close_grace_intrin, SLANG_VOID_TYPE, SLANG_FILE_FD_TYPE),
   SLANG_END_INTRIN_FUN_TABLE
};

static SLang_Intrin_Var_Type Module_Variables [] =
{
   MAKE_VARIABLE("_grace_module_version_string", &Module_Version_String, SLANG_STRING_TYPE, 1),
   SLANG_END_INTRIN_VAR_TABLE
};

static SLang_IConstant_Type Module_IConstants [] =
{
   MAKE_ICONSTANT("_grace_module_version", MODULE_VERSION_NUMBER),
   SLANG_END_ICONST_TABLE
};

int init_grace_module_ns (char *ns_name)
{
   SLang_NameSpace_Type *ns = SLns_create_namespace (ns_name);
   if (ns == NULL)
     return -1;

   if (Grace_Type_Id == -1)
     {
	(void) SLfile_create_clientdata_id (&Grace_Type_Id);
	(void) SLang_add_cleanup_function (cleanup_grace_jobs);
     }

   if (
       (-1 == SLns_add_intrin_var_table (ns, Module_Variables, NULL))
       || (-1 == SLns_add_intrin_fun_table (ns, Module_Intrinsics, NULL))
       || (-1 == SLns_add_iconstant_table (ns, Module_IConstants, NULL))
       )
     return -1;

   return 0;
}

/* This function is optional */
void deinit_grace_module (void)
{
}


syntax highlighted by Code2HTML, v. 0.9.1