/*
 * Copyright 1998-2002 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;
}

static 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 (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;
}
