text: /*
   * jim-signal.c
   *
   */
  
  #include <signal.h>
  #include <string.h>
  #include <ctype.h>
  
  #include "jimautoconf.h"
  #ifdef HAVE_UNISTD_H
      #include <unistd.h>
  #endif
  #include <jim-subcmd.h>
  #include <jim-signal.h>
  
  #define MAX_SIGNALS_WIDE (sizeof(jim_wide) * 8)
  #if defined(NSIG)
      #define MAX_SIGNALS ((NSIG < MAX_SIGNALS_WIDE) ? NSIG : MAX_SIGNALS_WIDE)
  #else
      #define MAX_SIGNALS MAX_SIGNALS_WIDE
  #endif
  
  static jim_wide *sigloc;
  static jim_wide sigsblocked;
  static struct sigaction *sa_old;
  static struct {
      int status;
      const char *name;
  } siginfo[MAX_SIGNALS];
  
  /* Make sure to do this as a wide, not int */
  #define sig_to_bit(SIG) ((jim_wide)1 << (SIG))
  
  static void signal_handler(int sig)
  {
      /* We just remember which signals occurred. Jim_Eval() will
       * notice this as soon as it can and throw an error
       */
      *sigloc |= sig_to_bit(sig);
  }
  
  static void signal_ignorer(int sig)
  {
      /* We just remember which signals occurred */
      sigsblocked |= sig_to_bit(sig);
  }
  
  static void signal_init_names(void)
  {
  #define SET_SIG_NAME(SIG) siginfo[SIG].name = #SIG
  
      SET_SIG_NAME(SIGABRT);
      SET_SIG_NAME(SIGALRM);
      SET_SIG_NAME(SIGBUS);
      SET_SIG_NAME(SIGCHLD);
      SET_SIG_NAME(SIGCONT);
      SET_SIG_NAME(SIGFPE);
      SET_SIG_NAME(SIGHUP);
      SET_SIG_NAME(SIGILL);
      SET_SIG_NAME(SIGINT);
  #ifdef SIGIO
      SET_SIG_NAME(SIGIO);
  #endif
      SET_SIG_NAME(SIGKILL);
      SET_SIG_NAME(SIGPIPE);
      SET_SIG_NAME(SIGPROF);
      SET_SIG_NAME(SIGQUIT);
      SET_SIG_NAME(SIGSEGV);
      SET_SIG_NAME(SIGSTOP);
      SET_SIG_NAME(SIGSYS);
      SET_SIG_NAME(SIGTERM);
      SET_SIG_NAME(SIGTRAP);
      SET_SIG_NAME(SIGTSTP);
      SET_SIG_NAME(SIGTTIN);
      SET_SIG_NAME(SIGTTOU);
      SET_SIG_NAME(SIGURG);
      SET_SIG_NAME(SIGUSR1);
      SET_SIG_NAME(SIGUSR2);
      SET_SIG_NAME(SIGVTALRM);
      SET_SIG_NAME(SIGWINCH);
      SET_SIG_NAME(SIGXCPU);
      SET_SIG_NAME(SIGXFSZ);
  #ifdef SIGPWR
      SET_SIG_NAME(SIGPWR);
  #endif
  #ifdef SIGCLD
      SET_SIG_NAME(SIGCLD);
  #endif
  #ifdef SIGEMT
      SET_SIG_NAME(SIGEMT);
  #endif
  #ifdef SIGLOST
      SET_SIG_NAME(SIGLOST);
  #endif
  #ifdef SIGPOLL
      SET_SIG_NAME(SIGPOLL);
  #endif
  #ifdef SIGINFO
      SET_SIG_NAME(SIGINFO);
  #endif
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_SignalId --
   *
   *      Return a textual identifier for a signal number.
   *
   * Results:
   *      This procedure returns a machine-readable textual identifier
   *      that corresponds to sig.  The identifier is the same as the
   *      #define name in signal.h.
   *
   * Side effects:
   *      None.
   *
   *----------------------------------------------------------------------
   */
  const char *Jim_SignalId(int sig)
  {
      if (sig >=0 && sig < MAX_SIGNALS) {
          if (siginfo[sig].name) {
              return siginfo[sig].name;
          }
      }
      return "unknown signal";
  }
  
  const char *Jim_SignalName(int sig)
  {
  #ifdef HAVE_SYS_SIGLIST
      if (sig >= 0 && sig < NSIG) {
          return sys_siglist[sig];
      }
  #endif
      return Jim_SignalId(sig);
  }
  
  /**
   * Given the name of a signal, returns the signal value if found,
   * or returns -1 (and sets an error) if not found.
   * We accept -SIGINT, SIGINT, INT or any lowercase version or a number,
   * either positive or negative.
   */
  static int find_signal_by_name(Jim_Interp *interp, const char *name)
  {
      int i;
      const char *pt = name;
  
      /* Remove optional - and SIG from the front of the name */
      if (*pt == '-') {
          pt++;
      }
      if (strncasecmp(name, "sig", 3) == 0) {
          pt += 3;
      }
      if (isdigit(UCHAR(pt[0]))) {
          i = atoi(pt);
          if (i > 0 && i < MAX_SIGNALS) {
              return i;
          }
      }
      else {
          for (i = 1; i < MAX_SIGNALS; i++) {
              /* Jim_SignalId() returns names such as SIGINT, and
               * returns "unknown signal" if unknown, so this will work
               */
              if (strcasecmp(Jim_SignalId(i) + 3, pt) == 0) {
                  return i;
              }
          }
      }
      Jim_SetResultFormatted(interp, "unknown signal %s", name);
  
      return -1;
  }
  
  #define SIGNAL_ACTION_HANDLE 1
  #define SIGNAL_ACTION_IGNORE -1
  #define SIGNAL_ACTION_DEFAULT 0
  
  static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *const *argv)
  {
      struct sigaction sa;
      int i;
  
      if (argc == 0) {
          Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0));
          for (i = 1; i < MAX_SIGNALS; i++) {
              if (siginfo[i].status == action) {
                  /* Add signal name to the list  */
                  Jim_ListAppendElement(interp, Jim_GetResult(interp),
                      Jim_NewStringObj(interp, Jim_SignalId(i), -1));
              }
          }
          return JIM_OK;
      }
  
      /* Catch all the signals we care about */
      if (action != SIGNAL_ACTION_DEFAULT) {
          memset(&sa, 0, sizeof(sa));
          if (action == SIGNAL_ACTION_HANDLE) {
              sa.sa_handler = signal_handler;
          }
          else {
              sa.sa_handler = signal_ignorer;
          }
      }
  
      /* Iterate through the provided signals */
      for (i = 0; i < argc; i++) {
          int sig = find_signal_by_name(interp, Jim_String(argv[i]));
  
          if (sig < 0) {
              return JIM_ERR;
          }
          if (action != siginfo[sig].status) {
              /* Need to change the action for this signal */
              switch (action) {
                  case SIGNAL_ACTION_HANDLE:
                  case SIGNAL_ACTION_IGNORE:
                      if (siginfo[sig].status == SIGNAL_ACTION_DEFAULT) {
                          if (!sa_old) {
                              /* Allocate the structure the first time through */
                              sa_old = Jim_Alloc(sizeof(*sa_old) * MAX_SIGNALS);
                          }
                          sigaction(sig, &sa, &sa_old[sig]);
                      }
                      else {
                          sigaction(sig, &sa, 0);
                      }
                      break;
  
                  case SIGNAL_ACTION_DEFAULT:
                      /* Restore old handler */
                      if (sa_old) {
                          sigaction(sig, &sa_old[sig], 0);
                      }
              }
              siginfo[sig].status = action;
          }
      }
  
      return JIM_OK;
  }
  
  static int signal_cmd_handle(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      return do_signal_cmd(interp, SIGNAL_ACTION_HANDLE, argc, argv);
  }
  
  static int signal_cmd_ignore(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      return do_signal_cmd(interp, SIGNAL_ACTION_IGNORE, argc, argv);
  }
  
  static int signal_cmd_default(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      return do_signal_cmd(interp, SIGNAL_ACTION_DEFAULT, argc, argv);
  }
  
  static int signal_set_sigmask_result(Jim_Interp *interp, jim_wide sigmask)
  {
      int i;
      Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
  
      for (i = 0; i < MAX_SIGNALS; i++) {
          if (sigmask & sig_to_bit(i)) {
              Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, Jim_SignalId(i), -1));
          }
      }
      Jim_SetResult(interp, listObj);
      return JIM_OK;
  }
  
  static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      int clear = 0;
      jim_wide mask = 0;
      jim_wide blocked;
  
      if (argc > 0 && Jim_CompareStringImmediate(interp, argv[0], "-clear")) {
          clear++;
      }
      if (argc > clear) {
          int i;
  
          /* Signals specified */
          for (i = clear; i < argc; i++) {
              int sig = find_signal_by_name(interp, Jim_String(argv[i]));
  
              if (sig < 0 || sig >= MAX_SIGNALS) {
                  return -1;
              }
              mask |= sig_to_bit(sig);
          }
      }
      else {
          /* No signals specified, so check/clear all */
          mask = ~mask;
      }
  
      if ((sigsblocked & mask) == 0) {
          /* No matching signals, so empty result and nothing to do */
          return JIM_OK;
      }
      /* Be careful we don't have a race condition where signals are cleared but not returned */
      blocked = sigsblocked & mask;
      if (clear) {
          sigsblocked &= ~blocked;
      }
      /* Set the result */
      signal_set_sigmask_result(interp, blocked);
      return JIM_OK;
  }
  
  static int signal_cmd_throw(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      int sig = SIGINT;
  
      if (argc == 1) {
          if ((sig = find_signal_by_name(interp, Jim_String(argv[0]))) < 0) {
              return JIM_ERR;
          }
      }
  
      /* If the signal is ignored (blocked) ... */
      if (siginfo[sig].status == SIGNAL_ACTION_IGNORE) {
          sigsblocked |= sig_to_bit(sig);
          return JIM_OK;
      }
  
      /* Just set the signal */
      interp->sigmask |= sig_to_bit(sig);
  
      /* Set the canonical name of the signal as the result */
      Jim_SetResultString(interp, Jim_SignalId(sig), -1);
  
      /* And simply say we caught the signal */
      return JIM_SIGNAL;
  }
  
  /*
   *-----------------------------------------------------------------------------
   *
   * Jim_SignalCmd --
   *     Implements the TCL signal command:
   *         signal handle|ignore|default|throw ?signals ...?
   *         signal throw signal
   *
   *     Specifies which signals are handled by Tcl code.
   *     If the one of the given signals is caught, it causes a JIM_SIGNAL
   *     exception to be thrown which can be caught by catch.
   *
   *     Use 'signal ignore' to ignore the signal(s)
   *     Use 'signal default' to go back to the default behaviour
   *     Use 'signal throw signal' to raise the given signal
   *
   *     If no arguments are given, returns the list of signals which are being handled
   *
   * Results:
   *      Standard TCL results.
   *
   *-----------------------------------------------------------------------------
   */
  static const jim_subcmd_type signal_command_table[] = {
      {   "handle",
          "?signals ...?",
          signal_cmd_handle,
          0,
          -1,
          /* Description: Lists handled signals, or adds to handled signals */
      },
      {   "ignore",
          "?signals ...?",
          signal_cmd_ignore,
          0,
          -1,
          /* Description: Lists ignored signals, or adds to ignored signals */
      },
      {   "default",
          "?signals ...?",
          signal_cmd_default,
          0,
          -1,
          /* Description: Lists defaulted signals, or adds to defaulted signals */
      },
      {   "check",
          "?-clear? ?signals ...?",
          signal_cmd_check,
          0,
          -1,
          /* Description: Returns ignored signals which have occurred, and optionally clearing them */
      },
      {   "throw",
          "?signal?",
          signal_cmd_throw,
          0,
          1,
          /* Description: Raises the given signal (default SIGINT) */
      },
      { NULL }
  };
  
  static int Jim_AlarmCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      int ret;
  
      if (argc != 2) {
          Jim_WrongNumArgs(interp, 1, argv, "seconds");
          return JIM_ERR;
      }
      else {
  #ifdef HAVE_UALARM
          double t;
  
          ret = Jim_GetDouble(interp, argv[1], &t);
          if (ret == JIM_OK) {
              if (t < 1) {
                  ualarm(t * 1e6, 0);
              }
              else {
                  alarm(t);
              }
          }
  #else
          long t;
  
          ret = Jim_GetLong(interp, argv[1], &t);
          if (ret == JIM_OK) {
              alarm(t);
          }
  #endif
      }
  
      return ret;
  }
  
  static int Jim_SleepCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      int ret;
  
      if (argc != 2) {
          Jim_WrongNumArgs(interp, 1, argv, "seconds");
          return JIM_ERR;
      }
      else {
          double t;
  
          ret = Jim_GetDouble(interp, argv[1], &t);
          if (ret == JIM_OK) {
  #ifdef HAVE_USLEEP
              usleep((int)((t - (int)t) * 1e6));
  #endif
              sleep(t);
          }
      }
  
      return ret;
  }
  
  static int Jim_KillCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
  {
      int sig;
      long pid;
      Jim_Obj *pidObj;
      const char *signame;
  
      if (argc != 2 && argc != 3) {
          Jim_WrongNumArgs(interp, 1, argv, "?SIG|-0? pid");
          return JIM_ERR;
      }
  
      if (argc == 2) {
          sig = SIGTERM;
          pidObj = argv[1];
      }
      else {
          signame = Jim_String(argv[1]);
          pidObj = argv[2];
  
          /* Special 'kill -0 pid' to determine if a pid exists */
          if (strcmp(signame, "-0") == 0 || strcmp(signame, "0") == 0) {
              sig = 0;
          }
          else {
              sig = find_signal_by_name(interp, signame);
              if (sig < 0) {
                  return JIM_ERR;
              }
          }
      }
  
      if (Jim_GetLong(interp, pidObj, &pid) != JIM_OK) {
          return JIM_ERR;
      }
  
      if (kill(pid, sig) == 0) {
          return JIM_OK;
      }
  
      Jim_SetResultString(interp, "kill: Failed to deliver signal", -1);
      return JIM_ERR;
  }
  
  int Jim_signalInit(Jim_Interp *interp)
  {
      if (Jim_PackageProvide(interp, "signal", "1.0", JIM_ERRMSG))
          return JIM_ERR;
  
      signal_init_names();
  
      /* Teach the jim core how to set a result from a sigmask */
      interp->signal_set_result = signal_set_sigmask_result;
  
      /* Make sure we know where to store the signals which occur */
      sigloc = &interp->sigmask;
  
      Jim_CreateCommand(interp, "signal", Jim_SubCmdProc, (void *)signal_command_table, NULL);
      Jim_CreateCommand(interp, "alarm", Jim_AlarmCmd, 0, 0);
      Jim_CreateCommand(interp, "kill", Jim_KillCmd, 0, 0);
  
      /* Sleep is slightly dubious here */
      Jim_CreateCommand(interp, "sleep", Jim_SleepCmd, 0, 0);
      return JIM_OK;
  }
  