/*
 * log.c	Functions in the library call radlib_log() which
 *		does internal logging.
 *
 * Version:	$Id: c7e32564d7b1c3201f8c6ec650b683a575199d84 $
 *
 *   This 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.
 *
 *   This 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 this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2000,2006  The FreeRADIUS server project
 */

RCSID("$Id: c7e32564d7b1c3201f8c6ec650b683a575199d84 $")

#include <freeradius-devel/libradius.h>

/*
 *	Are we using glibc or a close relative?
 */
#ifdef HAVE_FEATURES_H
#  include <features.h>
#endif

#define FR_STRERROR_BUFSIZE (2048)

fr_thread_local_setup(char *, fr_strerror_buffer)	/* macro */
fr_thread_local_setup(char *, fr_syserror_buffer)	/* macro */

#ifndef NDEBUG
/** POSIX-2008 errno macros
 *
 * Non-POSIX macros may be added, but you must check they're defined.
 */
static char const *fr_errno_macro_names[] = {
	[E2BIG] = "E2BIG",
	[EACCES] = "EACCES",
	[EADDRINUSE] = "EADDRINUSE",
	[EADDRNOTAVAIL] = "EADDRNOTAVAIL",
	[EAFNOSUPPORT] = "EAFNOSUPPORT",
#if EWOULDBLOCK == EAGAIN
	[EWOULDBLOCK] = "EWOULDBLOCK or EAGAIN",
#else
	[EAGAIN] = "EAGAIN",
	[EWOULDBLOCK] = "EWOULDBLOCK",
#endif
	[EALREADY] = "EALREADY",
	[EBADF] = "EBADF",
	[EBADMSG] = "EBADMSG",
	[EBUSY] = "EBUSY",
	[ECANCELED] = "ECANCELED",
	[ECHILD] = "ECHILD",
	[ECONNABORTED] = "ECONNABORTED",
	[ECONNREFUSED] = "ECONNREFUSED",
	[ECONNRESET] = "ECONNRESET",
	[EDEADLK] = "EDEADLK",
	[EDESTADDRREQ] = "EDESTADDRREQ",
	[EDOM] = "EDOM",
	[EDQUOT] = "EDQUOT",
	[EEXIST] = "EEXIST",
	[EFAULT] = "EFAULT",
	[EFBIG] = "EFBIG",
	[EHOSTUNREACH] = "EHOSTUNREACH",
	[EIDRM] = "EIDRM",
	[EILSEQ] = "EILSEQ",
	[EINPROGRESS] = "EINPROGRESS",
	[EINTR] = "EINTR",
	[EINVAL] = "EINVAL",
	[EIO] = "EIO",
	[EISCONN] = "EISCONN",
	[EISDIR] = "EISDIR",
	[ELOOP] = "ELOOP",
	[EMFILE] = "EMFILE",
	[EMLINK] = "EMLINK",
	[EMSGSIZE] = "EMSGSIZE",
	[EMULTIHOP] = "EMULTIHOP",
	[ENAMETOOLONG] = "ENAMETOOLONG",
	[ENETDOWN] = "ENETDOWN",
	[ENETRESET] = "ENETRESET",
	[ENETUNREACH] = "ENETUNREACH",
	[ENFILE] = "ENFILE",
	[ENOBUFS] = "ENOBUFS",
#ifdef ENODATA
	[ENODATA] = "ENODATA",
#endif
	[ENODEV] = "ENODEV",
	[ENOENT] = "ENOENT",
	[ENOEXEC] = "ENOEXEC",
	[ENOLCK] = "ENOLCK",
	[ENOLINK] = "ENOLINK",
	[ENOMEM] = "ENOMEM",
	[ENOMSG] = "ENOMSG",
	[ENOPROTOOPT] = "ENOPROTOOPT",
	[ENOSPC] = "ENOSPC",
#ifdef ENOSR
	[ENOSR] = "ENOSR",
#endif
#ifdef ENOSTR
	[ENOSTR] = "ENOSTR",
#endif
	[ENOSYS] = "ENOSYS",
	[ENOTCONN] = "ENOTCONN",
	[ENOTDIR] = "ENOTDIR",
	[ENOTEMPTY] = "ENOTEMPTY",
#ifdef ENOTRECOVERABLE
	[ENOTRECOVERABLE] = "ENOTRECOVERABLE",
#endif
	[ENOTSOCK] = "ENOTSOCK",
	[ENOTSUP] = "ENOTSUP",
#if ENOTSUP != EOPNOTSUPP
	[EOPNOTSUPP] = "EOPNOTSUPP",
#endif
	[ENOTTY] = "ENOTTY",
	[ENXIO] = "ENXIO",
	[EOVERFLOW] = "EOVERFLOW",
#ifdef EOWNERDEAD
	[EOWNERDEAD] = "EOWNERDEAD",
#endif
	[EPERM] = "EPERM",
	[EPIPE] = "EPIPE",
	[EPROTO] = "EPROTO",
	[EPROTONOSUPPORT] = "EPROTONOSUPPORT",
	[EPROTOTYPE] = "EPROTOTYPE",
	[ERANGE] = "ERANGE",
	[EROFS] = "EROFS",
	[ESPIPE] = "ESPIPE",
	[ESRCH] = "ESRCH",
	[ESTALE] = "ESTALE",
#ifdef ETIME
	[ETIME] = "ETIME",
#endif
	[ETIMEDOUT] = "ETIMEDOUT",
	[ETXTBSY] = "ETXTBSY",
	[EXDEV] = "EXDEV"
};
#endif

/*
 *	Explicitly cleanup the memory allocated to the error buffer,
 *	just in case valgrind complains about it.
 */
static void _fr_logging_free(void *arg)
{
	free(arg);
}

/** Log to thread local error buffer
 *
 * @param fmt printf style format string. If NULL sets the 'new' byte to false,
 *	  effectively clearing the last message.
 */
void fr_strerror_printf(char const *fmt, ...)
{
	va_list ap;

	char *buffer;

	buffer = fr_thread_local_init(fr_strerror_buffer, _fr_logging_free);
	if (!buffer) {
		int ret;

		/*
		 *	malloc is thread safe, talloc is not
		 */
		buffer = calloc((FR_STRERROR_BUFSIZE * 2) + 1, sizeof(char));	/* One byte extra for status */
		if (!buffer) {
			fr_perror("Failed allocating memory for libradius error buffer");
			return;
		}

		ret = fr_thread_local_set(fr_strerror_buffer, buffer);
		if (ret != 0) {
			fr_perror("Failed setting up thread-local storage for libradius error buffer: %s", fr_syserror(ret));
			free(buffer);
			return;
		}
	}

	/*
	 *	NULL has a special meaning, setting the new bit to false.
	 */
	if (!fmt) {
		buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06;
		return;
	}

	va_start(ap, fmt);
	/*
	 *	Alternate where we write the message, so we can do:
	 *	fr_strerror_printf("Additional error: %s", fr_strerror());
	 */
	switch (buffer[FR_STRERROR_BUFSIZE * 2] & 0x06) {
	default:
		vsnprintf(buffer + FR_STRERROR_BUFSIZE, FR_STRERROR_BUFSIZE, fmt, ap);
		buffer[FR_STRERROR_BUFSIZE * 2] = 0x05;			/* Flip the 'new' bit to true */
		break;

	case 0x04:
		vsnprintf(buffer, FR_STRERROR_BUFSIZE, fmt, ap);
		buffer[FR_STRERROR_BUFSIZE * 2] = 0x03;			/* Flip the 'new' bit to true */
		break;
	}
	va_end(ap);
}

/** Get the last library error
 *
 * Will only return the last library error once, after which it will return a zero length string.
 *
 * @return library error or zero length string
 */
char const *fr_strerror(void)
{
	char *buffer;

	buffer = fr_thread_local_get(fr_strerror_buffer);
	if (!buffer) return "";

	switch (buffer[FR_STRERROR_BUFSIZE * 2]) {
	default:
		return "";

	case 0x03:
		buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06;		/* Flip the 'new' bit to false */
		return buffer;

	case 0x05:
		buffer[FR_STRERROR_BUFSIZE * 2] &= 0x06;		/* Flip the 'new' bit to false */
		return buffer + FR_STRERROR_BUFSIZE;
	}
}

/** Guaranteed to be thread-safe version of strerror
 *
 * @param num errno as returned by function or from global errno.
 * @return local specific error string relating to errno.
 */
char const *fr_syserror(int num)
{
	char *buffer, *p, *end;
	int ret;

	buffer = fr_thread_local_init(fr_syserror_buffer, _fr_logging_free);
	if (!buffer) {
		/*
		 *	malloc is thread safe, talloc is not
		 */
		buffer = malloc(sizeof(char) * FR_STRERROR_BUFSIZE);
		if (!buffer) {
			fr_perror("Failed allocating memory for system error buffer");
			return NULL;
		}

		ret = fr_thread_local_set(fr_syserror_buffer, buffer);
		if (ret != 0) {
			fr_perror("Failed setting up thread-local storage for system error buffer");
			free(buffer);
			return NULL;
		}
	}

	if (!num) return "No error";

	p = buffer;
	end = p + FR_STRERROR_BUFSIZE;

#ifndef NDEBUG
	/*
	 *	Prefix system errors with the macro name and number
	 *	if we're debugging.
	 */
	if (num < (int)(sizeof(fr_errno_macro_names) / sizeof(*fr_errno_macro_names))) {
		p += snprintf(p, end - p, "%s: ", fr_errno_macro_names[num]);
	} else {
		p += snprintf(p, end - p, "errno %i: ", num);
	}
	if (p >= end) return p;
#endif

	/*
	 *	XSI-Compliant version
	 */
#if !defined(HAVE_FEATURES_H) || !defined(__GLIBC__) || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 500) && ! _GNU_SOURCE)
	ret = strerror_r(num, p, end - p);
	if (ret != 0) {
#  ifndef NDEBUG
		fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p (%zu bytes), "
			"returned %i: %s\n", num, buffer, (size_t) FR_STRERROR_BUFSIZE, ret, strerror(ret));
#  endif
		buffer[0] = '\0';
	}
	return buffer;
	/*
	 *	GNU Specific version
	 *
	 *	The GNU Specific version returns a char pointer. That pointer may point
	 *	the buffer you just passed in, or to an immutable static string.
	 */
#else
	{
		p = strerror_r(num, p, end - p);
		if (!p) {
#  ifndef NDEBUG
			fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p "
				"(%zu bytes): %s\n", num, buffer, (size_t) FR_STRERROR_BUFSIZE, strerror(errno));
#  endif
			buffer[0] = '\0';
			return buffer;
		}

		return p;
	}
#endif

}

void fr_perror(char const *fmt, ...)
{
	char const *error;
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);

	error = fr_strerror();
	if (error && (error[0] != '\0')) {
		fprintf(stderr, ": %s\n", error);
	} else {
		fputs("\n", stderr);
	}

	va_end(ap);
}
