/*-------------------------------------------------------------------------
 *
 * strerror.c
 *	  Replacements for standard strerror() and strerror_r() functions
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/port/strerror.c
 *
 *-------------------------------------------------------------------------
 */
#include "c.h"

/*
 * Within this file, "strerror" means the platform's function not pg_strerror,
 * and likewise for "strerror_r"
 */
#undef strerror
#undef strerror_r

static char *gnuish_strerror_r(int errnum, char *buf, size_t buflen);
static char *get_errno_symbol(int errnum);
#ifdef WIN32
static char *win32_socket_strerror(int errnum, char *buf, size_t buflen);
#endif


/*
 * A slightly cleaned-up version of strerror()
 */
char *
pg_strerror(int errnum)
{
	static char errorstr_buf[PG_STRERROR_R_BUFLEN];

	return pg_strerror_r(errnum, errorstr_buf, sizeof(errorstr_buf));
}

/*
 * A slightly cleaned-up version of strerror_r()
 */
char *
pg_strerror_r(int errnum, char *buf, size_t buflen)
{
	char	   *str;

	/* If it's a Windows Winsock error, that needs special handling */
#ifdef WIN32
	/* Winsock error code range, per WinError.h */
	if (errnum >= 10000 && errnum <= 11999)
		return win32_socket_strerror(errnum, buf, buflen);
#endif

	/* Try the platform's strerror_r(), or maybe just strerror() */
	str = gnuish_strerror_r(errnum, buf, buflen);

	/*
	 * Some strerror()s return an empty string for out-of-range errno.  This
	 * is ANSI C spec compliant, but not exactly useful.  Also, we may get
	 * back strings of question marks if libc cannot transcode the message to
	 * the codeset specified by LC_CTYPE.  If we get nothing useful, first try
	 * get_errno_symbol(), and if that fails, print the numeric errno.
	 */
	if (str == NULL || *str == '\0' || *str == '?')
		str = get_errno_symbol(errnum);

	if (str == NULL)
	{
		snprintf(buf, buflen, _("operating system error %d"), errnum);
		str = buf;
	}

	return str;
}

/*
 * Simple wrapper to emulate GNU strerror_r if what the platform provides is
 * POSIX.  Also, if platform lacks strerror_r altogether, fall back to plain
 * strerror; it might not be very thread-safe, but tough luck.
 */
static char *
gnuish_strerror_r(int errnum, char *buf, size_t buflen)
{
#ifdef HAVE_STRERROR_R
#ifdef STRERROR_R_INT
	/* POSIX API */
	if (strerror_r(errnum, buf, buflen) == 0)
		return buf;
	return NULL;				/* let caller deal with failure */
#else
	/* GNU API */
	return strerror_r(errnum, buf, buflen);
#endif
#else							/* !HAVE_STRERROR_R */
	char	   *sbuf = strerror(errnum);

	if (sbuf == NULL)			/* can this still happen anywhere? */
		return NULL;
	/* To minimize thread-unsafety hazard, copy into caller's buffer */
	strlcpy(buf, sbuf, buflen);
	return buf;
#endif
}

/*
 * Returns a symbol (e.g. "ENOENT") for an errno code.
 * Returns NULL if the code is unrecognized.
 */
static char *
get_errno_symbol(int errnum)
{
	switch (errnum)
	{
		case E2BIG:
			return "E2BIG";
		case EACCES:
			return "EACCES";
		case EADDRINUSE:
			return "EADDRINUSE";
		case EADDRNOTAVAIL:
			return "EADDRNOTAVAIL";
		case EAFNOSUPPORT:
			return "EAFNOSUPPORT";
#ifdef EAGAIN
		case EAGAIN:
			return "EAGAIN";
#endif
#ifdef EALREADY
		case EALREADY:
			return "EALREADY";
#endif
		case EBADF:
			return "EBADF";
#ifdef EBADMSG
		case EBADMSG:
			return "EBADMSG";
#endif
		case EBUSY:
			return "EBUSY";
		case ECHILD:
			return "ECHILD";
		case ECONNABORTED:
			return "ECONNABORTED";
		case ECONNREFUSED:
			return "ECONNREFUSED";
		case ECONNRESET:
			return "ECONNRESET";
		case EDEADLK:
			return "EDEADLK";
		case EDOM:
			return "EDOM";
		case EEXIST:
			return "EEXIST";
		case EFAULT:
			return "EFAULT";
		case EFBIG:
			return "EFBIG";
		case EHOSTDOWN:
			return "EHOSTDOWN";
		case EHOSTUNREACH:
			return "EHOSTUNREACH";
		case EIDRM:
			return "EIDRM";
		case EINPROGRESS:
			return "EINPROGRESS";
		case EINTR:
			return "EINTR";
		case EINVAL:
			return "EINVAL";
		case EIO:
			return "EIO";
		case EISCONN:
			return "EISCONN";
		case EISDIR:
			return "EISDIR";
#ifdef ELOOP
		case ELOOP:
			return "ELOOP";
#endif
		case EMFILE:
			return "EMFILE";
		case EMLINK:
			return "EMLINK";
		case EMSGSIZE:
			return "EMSGSIZE";
		case ENAMETOOLONG:
			return "ENAMETOOLONG";
		case ENETDOWN:
			return "ENETDOWN";
		case ENETRESET:
			return "ENETRESET";
		case ENETUNREACH:
			return "ENETUNREACH";
		case ENFILE:
			return "ENFILE";
		case ENOBUFS:
			return "ENOBUFS";
		case ENODEV:
			return "ENODEV";
		case ENOENT:
			return "ENOENT";
		case ENOEXEC:
			return "ENOEXEC";
		case ENOMEM:
			return "ENOMEM";
		case ENOSPC:
			return "ENOSPC";
		case ENOSYS:
			return "ENOSYS";
		case ENOTCONN:
			return "ENOTCONN";
		case ENOTDIR:
			return "ENOTDIR";
		case ENOTEMPTY:
			return "ENOTEMPTY";
		case ENOTSOCK:
			return "ENOTSOCK";
#ifdef ENOTSUP
		case ENOTSUP:
			return "ENOTSUP";
#endif
		case ENOTTY:
			return "ENOTTY";
		case ENXIO:
			return "ENXIO";
#if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || (EOPNOTSUPP != ENOTSUP))
		case EOPNOTSUPP:
			return "EOPNOTSUPP";
#endif
#ifdef EOVERFLOW
		case EOVERFLOW:
			return "EOVERFLOW";
#endif
		case EPERM:
			return "EPERM";
		case EPIPE:
			return "EPIPE";
		case EPROTONOSUPPORT:
			return "EPROTONOSUPPORT";
		case ERANGE:
			return "ERANGE";
#ifdef EROFS
		case EROFS:
			return "EROFS";
#endif
		case ESRCH:
			return "ESRCH";
		case ETIMEDOUT:
			return "ETIMEDOUT";
#ifdef ETXTBSY
		case ETXTBSY:
			return "ETXTBSY";
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
		case EWOULDBLOCK:
			return "EWOULDBLOCK";
#endif
		case EXDEV:
			return "EXDEV";
	}

	return NULL;
}


#ifdef WIN32

/*
 * Windows' strerror() doesn't know the Winsock codes, so handle them this way
 */
static char *
win32_socket_strerror(int errnum, char *buf, size_t buflen)
{
	static HANDLE handleDLL = INVALID_HANDLE_VALUE;

	if (handleDLL == INVALID_HANDLE_VALUE)
	{
		handleDLL = LoadLibraryEx("netmsg.dll", NULL,
								  DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
		if (handleDLL == NULL)
		{
			snprintf(buf, buflen,
					 "winsock error %d (could not load netmsg.dll to translate: error code %lu)",
					 errnum, GetLastError());
			return buf;
		}
	}

	ZeroMemory(buf, buflen);
	if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
					  FORMAT_MESSAGE_FROM_SYSTEM |
					  FORMAT_MESSAGE_FROM_HMODULE,
					  handleDLL,
					  errnum,
					  MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
					  buf,
					  buflen - 1,
					  NULL) == 0)
	{
		/* Failed to get id */
		snprintf(buf, buflen, "unrecognized winsock error %d", errnum);
	}

	return buf;
}

#endif							/* WIN32 */
