/* -*- 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 #include #ifdef HAVE_UNISTD_H # include #endif #include #include /* OPEN_MAX */ #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_WAIT_H # include #endif #include #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) { }