/*
 * vim: filetype=c:tabstop=2:softtabstop=2:shiftwidth=2:ai:expandtab
 * errnum.c - 2025-10-05 - Version 1.0.1
 * SPDX-License-Identifier: Multics or MIT-0
 * Copyright (c) 2025 Jeffrey H. Johnson
 * scspell-id: 3a9b1772-98e3-11f0-b710-80ee73e9b8e7
 */

/*
 * Tested on MinGW/Windows, AIX, PASE/OS400, Haiku, FreeBSD, OpenBSD, NetBSD, macOS, Solaris, illumos, and Linux.
 */

#if defined (_POSIX_C_SOURCE)
# undef _POSIX_C_SOURCE
#endif

#define _POSIX_C_SOURCE 200809L

#if !defined (_GNU_SOURCE)
# define _GNU_SOURCE
#endif

#if !defined (_NETBSD_SOURCE)
# define _NETBSD_SOURCE
#endif

#if !defined (_OPENBSD_SOURCE)
# define _OPENBSD_SOURCE
#endif

#if !defined (_DARWIN_C_SOURCE)
# define _DARWIN_C_SOURCE
#endif

#if !defined (__BSD_VISIBLE)
# define __BSD_VISIBLE 1
#endif

#if !defined (__EXTENSIONS__)
# define __EXTENSIONS__
#endif

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

#if defined (__APPLE__)
# include <xlocale.h>
#endif

#if defined (__APPLE__) || defined (__FreeBSD__) || defined (__OpenBSD__) || defined (__NetBSD__) || \
    defined (__illumos__) || ((defined (__sun) || defined (__sun__)) && (defined (__SVR4) || defined (__svr4__)))
# include <sys/signal.h>
#endif

#if defined (__GLIBC__) && defined (__GLIBC_PREREQ)
# if 0 + __GLIBC_PREREQ (2, 32)
#  define GLIBC_232
# endif
#endif

#if !defined (NSIG)
# if defined (_NSIG)
#  define NSIG _NSIG
# endif
#endif

#if !defined (NSIG)
# error NSIG undefined
#endif

#if NSIG < 128
# undef NSIG
# define NSIG 128
#endif

#define rwrap(type_str, val) \
  ret = ret ? ret : (((n_buf = snprintf (buf, sizeof buf, "Unknown %s %d", type_str, val)) < 0 || (size_t)n_buf >= sizeof buf) \
            ? ((void)fprintf (stderr, "FATAL: snprintf buffer overflow at %s[%s:%d]\n", __func__, __FILE__, __LINE__), \
               exit (EXIT_FAILURE), (const char *)0) : buf)

#define DEF_EMAXLEN 32767

//-V:xstrerror_l:576

static const char *
xstrerror_l (int errnum)
{
  int saved = errno, n_buf;
  const char * ret = NULL;
  static char buf [DEF_EMAXLEN];

#if defined (__APPLE__) || defined (_AIX) || defined (__MINGW32__) || defined (__MINGW64__)
# if defined (__MINGW32__) || defined (__MINGW64__)
  (0 == strerror_s (buf, sizeof buf, errnum)) ? (ret = buf) : (void)0;
# else
  (0 == strerror_r (errnum, buf, sizeof buf)) ? (ret = buf) : (void)0;
# endif
#else
# if defined (__NetBSD__)
  locale_t loc = LC_GLOBAL_LOCALE;
# else
  locale_t loc = uselocale ((locale_t)0);
# endif
  locale_t copy = (loc == LC_GLOBAL_LOCALE ? duplocale (loc) : loc);
  /*LINTED: E_EXPR_NULL_EFFECT*/
  ret = copy ? (ret = strerror_l (errnum, copy), (loc == LC_GLOBAL_LOCALE ? (freelocale (copy), 0) : 0), ret) : ret;
#endif

#if defined (__OpenBSD__)
  ret = (ret && (strstr (ret, "strerror_r failure") || strstr (ret, "Unknown error"))) ? NULL : ret;
#endif

  rwrap ("error", errnum);

  errno = saved;

  return ret;
}

static const char *
xstrsignal (int sig)
{
  static char buf [DEF_EMAXLEN];
#if defined (__MINGW32__) || defined (__MINGW64__)
  (void)snprintf (buf, sizeof buf, "Signal %d", sig);

  return buf;
#else
  int saved = errno, n_buf;
  const char * ret = strsignal (sig);

  rwrap ("signal", sig);

  errno = saved;

  return ret;
#endif
}

int
main (int argc, char * * argv)
{
  int errint;
  long errnum;
  char * eptr;
#if defined (GLIBC_232)
  char sbuf [DEF_EMAXLEN];
#endif
  char usage [DEF_EMAXLEN];

  (void)setlocale (LC_ALL, "");

  errno = 0;

  errnum = (2 == argc) ? strtol (argv [1], & eptr, 10) : 0;

  const char * program = (NULL != argv [0] && '\0' != argv [0] [0]) ? argv [0] : "errnum";

  (void)snprintf (usage, sizeof (usage), "Usage: %s <errno>", program);

  const char * fatal =
    (2 != argc)                            ? usage                           :
    (ERANGE == errno)                      ? "FATAL: Out of range."          :
    (argv [1] == eptr || '\0' != * eptr)   ? "FATAL: Invalid number."        :
    (errnum < INT_MIN || errnum > INT_MAX) ? "FATAL: Number exceeds limits." : NULL;

  /*LINTED: E_CAST_INT_TO_SMALL_INT*/
  errint = fatal ? 0 : (int)errnum;

  int xysh = (256 + 128 + 1 <= errint && errint < 256 + 128 + NSIG);
  int xksh = (! xysh && 256 + 1 <= errint && errint < 256 + NSIG);
  int xsig = (128 + 1 <= errint && errint < 128 + NSIG) || xksh || xysh;
  int errx = xsig ? (xysh ? errint - 256 - 128 : (xksh ? errint - 256 : errint - 128)) : errint;

  const char *(* msgfn)(int) = xsig ? xstrsignal : xstrerror_l;
  const char * lbl = xsig ? (xysh ? "yash signal" : xksh ? "ksh93 signal" : "signal") : "Error";
  const char * msg = msgfn (errx);

  (void)(fatal && fprintf (stderr, "%s\n", fatal));
  /*LINTED: E_CAST_INT_TO_SMALL_INT*/
  (void)(! fatal && ((getenv ("ERRNUM_EXIT") && ! xsig)
         ? ((errnum == 126 || errnum == 127)
           ? fprintf (stdout, "%s%s (Exit status %ld)\n", xstrerror_l (errnum == 126 ? EPERM : ENOENT),
#if defined(GLIBC_232)
             strerrorname_np (errnum == 126 ? EPERM : ENOENT)
               ? ((void)snprintf (sbuf, sizeof sbuf, " [%s]", strerrorname_np (errnum == 126 ? EPERM : ENOENT)), sbuf) : "",
#else
             "",
#endif
             errnum)
           : ((errnum == 64) ? fprintf (stdout, "Command line usage error [EX_USAGE] (Exit status %ld)\n", errnum)     :
              (errnum == 65) ? fprintf (stdout, "Data format error [EX_DATAERR] (Exit status %ld)\n", errnum)          :
              (errnum == 66) ? fprintf (stdout, "Cannot open input [EX_NOINPUT] (Exit status %ld)\n", errnum)          :
              (errnum == 67) ? fprintf (stdout, "Addressee unknown [EX_NOUSER] (Exit status %ld)\n", errnum)           :
              (errnum == 68) ? fprintf (stdout, "Host name unknown [EX_NOHOST] (Exit status %ld)\n", errnum)           :
              (errnum == 69) ? fprintf (stdout, "Service unavailable [EX_UNAVAILABLE] (Exit status %ld)\n", errnum)    :
              (errnum == 70) ? fprintf (stdout, "Internal software error [EX_SOFTWARE] (Exit status %ld)\n", errnum)   :
              (errnum == 71) ? fprintf (stdout, "System error [EX_OSERR] (Exit status %ld)\n", errnum)                 :
              (errnum == 72) ? fprintf (stdout, "Critical OS file missing [EX_OSFILE] (Exit status %ld)\n", errnum)    :
              (errnum == 73) ? fprintf (stdout, "Can't create output file [EX_CANTCREAT] (Exit status %ld)\n", errnum) :
              (errnum == 74) ? fprintf (stdout, "%s [EX_IOERR] (Exit status %ld)\n", xstrerror_l (EIO), errnum)        :
              (errnum == 75) ? fprintf (stdout, "%s [EX_TEMPFAIL] (Exit status %ld)\n", xstrerror_l (EAGAIN), errnum)  :
              (errnum == 76) ? fprintf (stdout, "Remote error in protocol [EX_PROTOCOL] (Exit status %ld)\n", errnum)  :
              (errnum == 77) ? fprintf (stdout, "%s [EX_NOPERM] (Exit status %ld)\n", xstrerror_l (EPERM), errnum)     :
              (errnum == 78) ? fprintf (stdout, "Configuration error [EX_CONFIG] (Exit status %ld)\n", errnum)         :
             fprintf (stdout, errnum ? "Exit status %d\n" : "Success\n", errx)))
           : fprintf (stdout, errnum ? "%s%s (%s %d)\n"   : "Success\n", errnum ? msg : 0,
#if defined (GLIBC_232)
           (xsig ? (sigabbrev_np (errx) ? ((void)snprintf (sbuf, sizeof sbuf, " [SIG%s]", sigabbrev_np (errx)), sbuf) : "")
              : (strerrorname_np (errx) ? ((void)snprintf (sbuf, sizeof sbuf, " [%s]", strerrorname_np (errx)), sbuf) : "")),
#else
         "",
#endif
         lbl, errx)));

  return fatal ? EXIT_FAILURE : EXIT_SUCCESS;
}
