/*
 * Copyright 1998-2003 by Albert Cahalan; all rights resered.
 * This file may be used subject to the terms and conditions of the
 * GNU Library General Public License Version 2, or any later version
 * at your option, as published by the Free Software Foundation.
 * 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 Library General Public License for more details.
 */
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "sig.h"

/* Linux signals:
 *
 * SIGSYS is required by Unix98.
 * SIGEMT is part of SysV, BSD, and ancient UNIX tradition.
 *
 * They are provided by these Linux ports: alpha, mips, sparc, and sparc64.
 * You get SIGSTKFLT and SIGUNUSED instead on i386, m68k, ppc, and arm.
 * (this is a Linux & libc bug -- both must be fixed)
 *
 * Total garbage: SIGIO SIGINFO SIGIOT SIGLOST SIGCLD
 *                 (popular ones are handled as aliases)
 * Nearly garbage: SIGSTKFLT SIGUNUSED (nothing else to fill slots)
 */

/* Linux 2.3.29 replaces SIGUNUSED with the standard SIGSYS signal */
#ifndef SIGSYS
#  warning Standards require that <signal.h> define SIGSYS
#  define SIGSYS SIGUNUSED
#endif

/* If we see both, it is likely SIGSTKFLT (junk) was replaced. */
#ifdef SIGEMT
#  undef SIGSTKFLT
#endif

#ifndef SIGRTMIN
#  warning Standards require that <signal.h> define SIGRTMIN; assuming 32
#  define SIGRTMIN 32
#endif

/* It seems the SPARC libc does not know the kernel supports SIGPWR. */
#ifndef SIGPWR
#  warning Your header files lack SIGPWR. (assuming it is number 29)
#  define SIGPWR 29
#endif

typedef struct mapstruct {
  const char *name;
  int num;
} mapstruct;


static const mapstruct sigtable[] = {
  {"ABRT",   SIGABRT},  /* IOT */
  {"ALRM",   SIGALRM},
  {"BUS",    SIGBUS},
  {"CHLD",   SIGCHLD},  /* CLD */
  {"CONT",   SIGCONT},
#ifdef SIGEMT
  {"EMT",    SIGEMT},
#endif
  {"FPE",    SIGFPE},
  {"HUP",    SIGHUP},
  {"ILL",    SIGILL},
  {"INT",    SIGINT},
  {"KILL",   SIGKILL},
  {"PIPE",   SIGPIPE},
  {"POLL",   SIGPOLL},  /* IO */
  {"PROF",   SIGPROF},
  {"PWR",    SIGPWR},
  {"QUIT",   SIGQUIT},
  {"SEGV",   SIGSEGV},
#ifdef SIGSTKFLT
  {"STKFLT", SIGSTKFLT},
#endif
  {"STOP",   SIGSTOP},
  {"SYS",    SIGSYS},   /* UNUSED */
  {"TERM",   SIGTERM},
  {"TRAP",   SIGTRAP},
  {"TSTP",   SIGTSTP},
  {"TTIN",   SIGTTIN},
  {"TTOU",   SIGTTOU},
  {"URG",    SIGURG},
  {"USR1",   SIGUSR1},
  {"USR2",   SIGUSR2},
  {"VTALRM", SIGVTALRM},
  {"WINCH",  SIGWINCH},
  {"XCPU",   SIGXCPU},
  {"XFSZ",   SIGXFSZ}
};

static const int number_of_signals = sizeof(sigtable)/sizeof(mapstruct);

static int compare_signal_names(const void *a, const void *b){
  return strcasecmp( ((const mapstruct*)a)->name, ((const mapstruct*)b)->name );
}

/* return -1 on failure */
int signal_name_to_number(const char *restrict name){
  long val;
  int offset;

  /* clean up name */
  if(!strncasecmp(name,"SIG",3)) name += 3;

  if(!strcasecmp(name,"CLD")) return SIGCHLD;
  if(!strcasecmp(name,"IO"))  return SIGPOLL;
  if(!strcasecmp(name,"IOT")) return SIGABRT;

  /* search the table */
  {
    const mapstruct ms = {name,0};
    const mapstruct *restrict const ptr = bsearch(
      &ms,
      sigtable,
      number_of_signals,
      sizeof(mapstruct),
      compare_signal_names
    );
    if(ptr) return ptr->num;
  }

  if(!strcasecmp(name,"RTMIN")) return SIGRTMIN;
  if(!strcasecmp(name,"EXIT"))  return 0;
  if(!strcasecmp(name,"NULL"))  return 0;

  offset = 0;
  if(!strncasecmp(name,"RTMIN+",6)){
    name += 6;
    offset = SIGRTMIN;
  }

  /* not found, so try as a number */
  {
    char *endp;
    val = strtol(name,&endp,10);
    if(*endp || endp==name) return -1; /* not valid */
  }
  if(val+SIGRTMIN>127) return -1; /* not valid */
  return val+offset;
}

const char *signal_number_to_name(int signo){
  static char buf[32];
  int n = number_of_signals;
  signo &= 0x7f; /* need to process exit values too */
  while(n--){
    if(sigtable[n].num==signo) return sigtable[n].name;
  }
  if(signo == SIGRTMIN) return "RTMIN";
  if(signo) sprintf(buf, "RTMIN+%d", signo-SIGRTMIN);
  else      strcpy(buf,"0");  /* AIX has NULL; Solaris has EXIT */
  return buf;
}

int print_given_signals(int argc, const char *restrict const *restrict argv, int max_line){
  char buf[1280]; /* 128 signals, "RTMIN+xx" is largest */
  int ret = 0;  /* to be used as exit code by caller */
  int place = 0; /* position on this line */
  int amt;
  if(argc > 128) return 1;
  while(argc--){
    char tmpbuf[16];
    const char *restrict const txt = *argv;
    if(*txt >= '0' && *txt <= '9'){
      long val;
      char *endp;
      val = strtol(txt,&endp,10);
      if(*endp){
        fprintf(stderr, "Signal \"%s\" not known.\n", txt);
        ret = 1;
        goto end;
      }
      amt = sprintf(tmpbuf, "%s", signal_number_to_name(val));
    }else{
      int sno;
      sno = signal_name_to_number(txt);
      if(sno == -1){
        fprintf(stderr, "Signal \"%s\" not known.\n", txt);
        ret = 1;
        goto end;
      }
      amt = sprintf(tmpbuf, "%d", sno);
    }

    if(!place){
      strcpy(buf,tmpbuf);
      place = amt;
      goto end;
    }
    if(amt+place+1 > max_line){
      printf("%s\n", buf);
      strcpy(buf,tmpbuf);
      place = amt;
      goto end;
    }
    sprintf(buf+place, " %s", tmpbuf);
    place += amt+1;
end:
    argv++;
  }
  if(place) printf("%s\n", buf);
  return ret;
}

void pretty_print_signals(void){
  int i = 0;
  while(++i <= number_of_signals){
    int n;
    n = printf("%2d %s", i, signal_number_to_name(i));
    if(n>0 && i%7) printf("           \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + n);
    else printf("\n");
  }
  if((i-1)%7) printf("\n");
}

void unix_print_signals(void){
  int pos = 0;
  int i = 0;
  while(++i <= number_of_signals){
    if(i-1) printf("%c", (pos>73)?(pos=0,'\n'):(pos++,' ') );
    pos += printf("%s", signal_number_to_name(i));
  }
  printf("\n");
}

/* sanity check */
static int init_signal_list(void) __attribute__((constructor));
static int init_signal_list(void){
  if(number_of_signals != 31){
    fprintf(stderr, "WARNING: %d signals -- adjust and recompile.\n", number_of_signals);
  }
  return 0;
}