/* -*- 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