/* Copyright (C) 2009, 2010 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <errno.h>
#include <libintl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <not-cancel.h>


/* Defined in sys_siglist.c.  */
extern const char *const _sys_siglist[];
extern const char *const _sys_siglist_internal[] attribute_hidden;


#define MF(l) MF1 (l)
#define MF1(l) str_##l
#define C(s1, s2) C1 (s1, s2)
#define C1(s1, s2) s1##s2

#define NOW SIGILL
#include "psiginfo-define.h"

#define NOW SIGFPE
#include "psiginfo-define.h"

#define NOW SIGSEGV
#include "psiginfo-define.h"

#define NOW SIGBUS
#include "psiginfo-define.h"

#define NOW SIGTRAP
#include "psiginfo-define.h"

#define NOW SIGCLD
#include "psiginfo-define.h"

#define NOW SIGPOLL
#include "psiginfo-define.h"


/* Print out on stderr a line consisting of the test in S, a colon, a space,
   a message describing the meaning of the signal number PINFO and a newline.
   If S is NULL or "", the colon and space are omitted.  */
void
psiginfo (const siginfo_t *pinfo, const char *s)
{
  char buf[512];
  FILE *fp = fmemopen (buf, sizeof (buf), "w");
  if (fp == NULL)
    {
      const char *colon;

      if (s == NULL || *s == '\0')
	s = colon = "";
      else
	colon = ": ";

      __fxprintf (NULL, "%s%ssignal %d\n", s, colon, pinfo->si_signo);
      return;
    }

  if (s != NULL && *s != '\0')
    fprintf (fp, "%s: ", s);

  const char *desc;
  if (pinfo->si_signo >= 0 && pinfo->si_signo < NSIG
      && ((desc = INTUSE(_sys_siglist)[pinfo->si_signo]) != NULL
	  || (pinfo->si_signo >= SIGRTMIN && pinfo->si_signo < SIGRTMAX)))
    {
      if (desc == NULL)
	{
	  if (pinfo->si_signo - SIGRTMIN < SIGRTMAX - pinfo->si_signo)
	    {
	      if (pinfo->si_signo == SIGRTMIN)
		fprintf (fp, "SIGRTMIN (");
	      else
		fprintf (fp, "SIGRTMIN+%d (", pinfo->si_signo - SIGRTMIN);
	    }
	  else
	    {
	      if (pinfo->si_signo == SIGRTMAX)
		fprintf (fp, "SIGRTMAX (");
	      else
		fprintf (fp, "SIGRTMAX-%d (", SIGRTMAX - pinfo->si_signo);
	    }
	}
      else
	fprintf (fp, "%s (", _(desc));

      const char *base = NULL;
      const uint8_t *offarr = NULL;
      size_t offarr_len = 0;
      switch (pinfo->si_signo)
	{
#define H(sig) \
	case sig:							      \
	  base = C(codestrs_, sig).str;					      \
	  offarr = C (codes_, sig);					      \
	  offarr_len = sizeof (C (codes_, sig)) / sizeof (C (codes_, sig)[0]);\
	  break

	  H (SIGILL);
	  H (SIGFPE);
	  H (SIGSEGV);
	  H (SIGBUS);
	  H (SIGTRAP);
	  H (SIGCHLD);
	  H (SIGPOLL);
	}

      const char *str = NULL;
      if (offarr != NULL
	  && pinfo->si_code >= 1 && pinfo->si_code <= offarr_len)
	str = base + offarr[pinfo->si_code - 1];
      else
	switch (pinfo->si_code)
	  {
	  case SI_USER:
	    str = N_("Signal sent by kill()");
	    break;
	  case SI_QUEUE:
	    str = N_("Signal sent by sigqueue()");
	    break;
	  case SI_TIMER:
	    str = N_("Signal generated by the expiration of a timer");
	    break;
	  case SI_ASYNCIO:
	    str = N_("\
Signal generated by the completion of an asynchronous I/O request");
	    break;
	  case SI_MESGQ:
	    str = N_("\
Signal generated by the arrival of a message on an empty message queue");
	    break;
#ifdef SI_TKILL
	  case SI_TKILL:
	    str = N_("Signal sent by tkill()");
	    break;
#endif
#ifdef SI_ASYNCNL
	  case SI_ASYNCNL:
	    str = N_("\
Signal generated by the completion of an asynchronous name lookup request");
	    break;
#endif
#ifdef SI_SIGIO
	  case SI_SIGIO:
	    str = N_("\
Signal generated by the completion of an I/O request");
	    break;
#endif
#ifdef SI_KERNEL
	  case SI_KERNEL:
	    str = N_("Signal sent by the kernel");
	    break;
#endif
	  }

      if (str != NULL)
	fprintf (fp, "%s ", _(str));
      else
	fprintf (fp, "%d ", pinfo->si_code);

      if (pinfo->si_signo == SIGILL || pinfo->si_signo == SIGFPE
	  || pinfo->si_signo == SIGSEGV || pinfo->si_signo == SIGBUS)
	fprintf (fp, "[%p])\n", pinfo->si_addr);
      else if (pinfo->si_signo == SIGCHLD)
	fprintf (fp, "%ld %d %ld)\n",
		 (long int) pinfo->si_pid, pinfo->si_status,
		 (long int) pinfo->si_uid);
      else if (pinfo->si_signo == SIGPOLL)
	fprintf (fp, "%ld)\n", (long int) pinfo->si_band);
      else
	fprintf (fp, "%ld %ld)\n",
		 (long int) pinfo->si_pid, (long int) pinfo->si_uid);
    }
  else
    fprintf (fp, _("Unknown signal %d\n"),  pinfo->si_signo);

  fclose (fp);

  write_not_cancel (STDERR_FILENO, buf, strlen (buf));
}
