/* sig2str.c -- convert between signal names and numbers

   Copyright (C) 2002, 2004 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 program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Written by Paul Eggert.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "sig2str.h"

#ifndef SIGRTMIN
# define SIGRTMIN 0
# undef SIGRTMAX
#endif
#ifndef SIGRTMAX
# define SIGRTMAX (SIGRTMIN - 1)
#endif

#define NUMNAME(name) { SIG##name, #name }

/* Signal names and numbers.  Put the preferred name first.  */
static struct numname { int num; char const name[8]; } numname_table[] =
  {
    /* Signals required by POSIX 1003.1-2001 base, listed in
       traditional numeric order.  */
#ifdef SIGHUP
    NUMNAME (HUP),
#endif
#ifdef SIGINT
    NUMNAME (INT),
#endif
#ifdef SIGQUIT
    NUMNAME (QUIT),
#endif
#ifdef SIGILL
    NUMNAME (ILL),
#endif
#ifdef SIGTRAP
    NUMNAME (TRAP),
#endif
#ifdef SIGABRT
    NUMNAME (ABRT),
#endif
#ifdef SIGFPE
    NUMNAME (FPE),
#endif
#ifdef SIGKILL
    NUMNAME (KILL),
#endif
#ifdef SIGBUS
    NUMNAME (BUS),
#endif
#ifdef SIGSEGV
    NUMNAME (SEGV),
#endif
#ifdef SIGPIPE
    NUMNAME (PIPE),
#endif
#ifdef SIGALRM
    NUMNAME (ALRM),
#endif
#ifdef SIGTERM
    NUMNAME (TERM),
#endif
#ifdef SIGUSR1
    NUMNAME (USR1),
#endif
#ifdef SIGUSR2
    NUMNAME (USR2),
#endif
#ifdef SIGCHLD
    NUMNAME (CHLD),
#endif
#ifdef SIGURG
    NUMNAME (URG),
#endif
#ifdef SIGSTOP
    NUMNAME (STOP),
#endif
#ifdef SIGTSTP
    NUMNAME (TSTP),
#endif
#ifdef SIGCONT
    NUMNAME (CONT),
#endif
#ifdef SIGTTIN
    NUMNAME (TTIN),
#endif
#ifdef SIGTTOU
    NUMNAME (TTOU),
#endif

    /* Signals required by POSIX 1003.1-2001 with the XSI extension.  */
#ifdef SIGSYS
    NUMNAME (SYS),
#endif
#ifdef SIGPOLL
    NUMNAME (POLL),
#endif
#ifdef SIGVTALRM
    NUMNAME (VTALRM),
#endif
#ifdef SIGPROF
    NUMNAME (PROF),
#endif
#ifdef SIGXCPU
    NUMNAME (XCPU),
#endif
#ifdef SIGXFSZ
    NUMNAME (XFSZ),
#endif

    /* Unix Version 7.  */
#ifdef SIGIOT
    NUMNAME (IOT),	/* Older name for ABRT.  */
#endif
#ifdef SIGEMT
    NUMNAME (EMT),
#endif

    /* USG Unix.  */
#ifdef SIGPHONE
    NUMNAME (PHONE),
#endif
#ifdef SIGWIND
    NUMNAME (WIND),
#endif

    /* Unix System V.  */
#ifdef SIGCLD
    NUMNAME (CLD),
#endif
#ifdef SIGPWR
    NUMNAME (PWR),
#endif

    /* GNU/Linux 2.2 and Solaris 8.  */
#ifdef SIGCANCEL
    NUMNAME (CANCEL),
#endif
#ifdef SIGLWP
    NUMNAME (LWP),
#endif
#ifdef SIGWAITING
    NUMNAME (WAITING),
#endif
#ifdef SIGFREEZE
    NUMNAME (FREEZE),
#endif
#ifdef SIGTHAW
    NUMNAME (THAW),
#endif
#ifdef SIGLOST
    NUMNAME (LOST),
#endif
#ifdef SIGWINCH
    NUMNAME (WINCH),
#endif

    /* GNU/Linux 2.2.  */
#ifdef SIGINFO
    NUMNAME (INFO),
#endif
#ifdef SIGIO
    NUMNAME (IO),
#endif
#ifdef SIGSTKFLT
    NUMNAME (STKFLT),
#endif

    /* AIX 5L.  */
#ifdef SIGDANGER
    NUMNAME (DANGER),
#endif
#ifdef SIGGRANT
    NUMNAME (GRANT),
#endif
#ifdef SIGMIGRATE
    NUMNAME (MIGRATE),
#endif
#ifdef SIGMSG
    NUMNAME (MSG),
#endif
#ifdef SIGPRE
    NUMNAME (PRE),
#endif
#ifdef SIGRETRACT
    NUMNAME (RETRACT),
#endif
#ifdef SIGSAK
    NUMNAME (SAK),
#endif
#ifdef SIGSOUND
    NUMNAME (SOUND),
#endif

    /* Older AIX versions.  */
#ifdef SIGALRM1
    NUMNAME (ALRM1),	/* unknown; taken from Bash 2.05 */
#endif
#ifdef SIGKAP
    NUMNAME (KAP),	/* Older name for SIGGRANT.  */
#endif
#ifdef SIGVIRT
    NUMNAME (VIRT),	/* unknown; taken from Bash 2.05 */
#endif
#ifdef SIGWINDOW
    NUMNAME (WINDOW),	/* Older name for SIGWINCH.  */
#endif

    /* BeOS */
#ifdef SIGKILLTHR
    NUMNAME (KILLTHR),
#endif

    /* Older HP-UX versions.  */
#ifdef SIGDIL
    NUMNAME (DIL),
#endif

    /* Korn shell and Bash, of uncertain vintage.  */
    { 0, "EXIT" }
  };

#define NUMNAME_ENTRIES (sizeof numname_table / sizeof numname_table[0])

/* ISDIGIT differs from isdigit, as follows:
   - Its arg may be any int or unsigned int; it need not be an unsigned char.
   - It's guaranteed to evaluate its argument exactly once.
   - It's typically faster.
   POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
   ISDIGIT_LOCALE unless it's important to use the locale's definition
   of `digit' even when the host does not conform to POSIX.  */
#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)

/* Convert the signal name SIGNAME to a signal number.  Return the
   signal number if successful, -1 otherwise.  */

static int
str2signum (char const *signame)
{
  if (ISDIGIT (*signame))
    {
      char *endp;
      long int n = strtol (signame, &endp, 10);
      if (! *endp && n <= SIGNUM_BOUND)
	return n;
    }
  else
    {
      unsigned int i;
      for (i = 0; i < NUMNAME_ENTRIES; i++)
	if (strcmp (numname_table[i].name, signame) == 0)
	  return numname_table[i].num;

      {
	char *endp;
	int rtmin = SIGRTMIN;
	int rtmax = SIGRTMAX;

	if (0 < rtmin && strncmp (signame, "RTMIN", 5) == 0)
	  {
	    long int n = strtol (signame + 5, &endp, 10);
	    if (! *endp && 0 <= n && n <= rtmax - rtmin)
	      return rtmin + n;
	  }
	else if (0 < rtmax && strncmp (signame, "RTMAX", 5) == 0)
	  {
	    long int n = strtol (signame + 5, &endp, 10);
	    if (! *endp && rtmin - rtmax <= n && n <= 0)
	      return rtmax + n;
	  }
      }
    }

  return -1;
}

/* Convert the signal name SIGNAME to the signal number *SIGNUM.
   Return 0 if successful, -1 otherwise.  */

int
str2sig (char const *signame, int *signum)
{
  *signum = str2signum (signame);
  return *signum < 0 ? -1 : 0;
}

/* Convert SIGNUM to a signal name in SIGNAME.  SIGNAME must point to
   a buffer of at least SIG2STR_MAX bytes.  Return 0 if successful, -1
   otherwise.  */

int
sig2str (int signum, char *signame)
{
  unsigned int i;
  for (i = 0; i < NUMNAME_ENTRIES; i++)
    if (numname_table[i].num == signum)
      {
	strcpy (signame, numname_table[i].name);
	return 0;
      }

  {
    int rtmin = SIGRTMIN;
    int rtmax = SIGRTMAX;

    if (! (rtmin <= signum && signum <= rtmax))
      return -1;

    if (signum <= rtmin + (rtmax - rtmin) / 2)
      {
	int delta = signum - rtmin;
	sprintf (signame, delta ? "RTMIN+%d" : "RTMIN", delta);
      }
    else
      {
	int delta = rtmax - signum;
	sprintf (signame, delta ? "RTMAX-%d" : "RTMAX", delta);
      }

    return 0;
  }
}
