/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://opensource.org/licenses/CDDL-1.0.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include "libuutil_common.h"

#include <assert.h>
#include <errno.h>
#include <libintl.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/debug.h>
#include <unistd.h>
#include <ctype.h>

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif

/*
 * All of the old code under !defined(PTHREAD_ONCE_KEY_NP)
 * is here to enable the building of a native version of
 * libuutil.so when the build machine has not yet been upgraded
 * to a version of libc that provides pthread_key_create_once_np().
 * It should all be deleted when solaris_nevada ships.
 * The code is not MT-safe in a relaxed memory model.
 */

#if defined(PTHREAD_ONCE_KEY_NP)
static pthread_key_t	uu_error_key = PTHREAD_ONCE_KEY_NP;
#else	/* PTHREAD_ONCE_KEY_NP */
static pthread_key_t	uu_error_key = 0;
static pthread_mutex_t	uu_key_lock = PTHREAD_MUTEX_INITIALIZER;
#endif	/* PTHREAD_ONCE_KEY_NP */

static int		uu_error_key_setup = 0;

static pthread_mutex_t	uu_panic_lock = PTHREAD_MUTEX_INITIALIZER;
/* LINTED static unused */
static const char	*uu_panic_format;
/* LINTED static unused */
static va_list		uu_panic_args;
static pthread_t	uu_panic_thread;

static uint32_t		_uu_main_error;
static __thread int	_uu_main_thread = 0;

void
uu_set_error(uint_t code)
{
	if (_uu_main_thread) {
		_uu_main_error = code;
		return;
	}
#if defined(PTHREAD_ONCE_KEY_NP)
	if (pthread_key_create_once_np(&uu_error_key, NULL) != 0)
		uu_error_key_setup = -1;
	else
		uu_error_key_setup = 1;
#else	/* PTHREAD_ONCE_KEY_NP */
	if (uu_error_key_setup == 0) {
		(void) pthread_mutex_lock(&uu_key_lock);
		if (uu_error_key_setup == 0) {
			if (pthread_key_create(&uu_error_key, NULL) != 0)
				uu_error_key_setup = -1;
			else
				uu_error_key_setup = 1;
		}
		(void) pthread_mutex_unlock(&uu_key_lock);
	}
#endif	/* PTHREAD_ONCE_KEY_NP */
	if (uu_error_key_setup > 0)
		(void) pthread_setspecific(uu_error_key,
		    (void *)(uintptr_t)code);
}

uint32_t
uu_error(void)
{
	if (_uu_main_thread)
		return (_uu_main_error);

	if (uu_error_key_setup < 0)	/* can't happen? */
		return (UU_ERROR_UNKNOWN);

	/*
	 * Because UU_ERROR_NONE == 0, if uu_set_error() was
	 * never called, then this will return UU_ERROR_NONE:
	 */
	return ((uint32_t)(uintptr_t)pthread_getspecific(uu_error_key));
}

const char *
uu_strerror(uint32_t code)
{
	const char *str;

	switch (code) {
	case UU_ERROR_NONE:
		str = dgettext(TEXT_DOMAIN, "No error");
		break;

	case UU_ERROR_INVALID_ARGUMENT:
		str = dgettext(TEXT_DOMAIN, "Invalid argument");
		break;

	case UU_ERROR_UNKNOWN_FLAG:
		str = dgettext(TEXT_DOMAIN, "Unknown flag passed");
		break;

	case UU_ERROR_NO_MEMORY:
		str = dgettext(TEXT_DOMAIN, "Out of memory");
		break;

	case UU_ERROR_CALLBACK_FAILED:
		str = dgettext(TEXT_DOMAIN, "Callback-initiated failure");
		break;

	case UU_ERROR_NOT_SUPPORTED:
		str = dgettext(TEXT_DOMAIN, "Operation not supported");
		break;

	case UU_ERROR_EMPTY:
		str = dgettext(TEXT_DOMAIN, "No value provided");
		break;

	case UU_ERROR_UNDERFLOW:
		str = dgettext(TEXT_DOMAIN, "Value too small");
		break;

	case UU_ERROR_OVERFLOW:
		str = dgettext(TEXT_DOMAIN, "Value too large");
		break;

	case UU_ERROR_INVALID_CHAR:
		str = dgettext(TEXT_DOMAIN,
		    "Value contains unexpected character");
		break;

	case UU_ERROR_INVALID_DIGIT:
		str = dgettext(TEXT_DOMAIN,
		    "Value contains digit not in base");
		break;

	case UU_ERROR_SYSTEM:
		str = dgettext(TEXT_DOMAIN, "Underlying system error");
		break;

	case UU_ERROR_UNKNOWN:
		str = dgettext(TEXT_DOMAIN, "Error status not known");
		break;

	default:
		errno = ESRCH;
		str = NULL;
		break;
	}
	return (str);
}

void
uu_panic(const char *format, ...)
{
	va_list args;

	va_start(args, format);

	(void) pthread_mutex_lock(&uu_panic_lock);
	if (uu_panic_thread == 0) {
		uu_panic_thread = pthread_self();
		uu_panic_format = format;
		va_copy(uu_panic_args, args);
	}
	(void) pthread_mutex_unlock(&uu_panic_lock);

	(void) vfprintf(stderr, format, args);

	va_end(args);

	if (uu_panic_thread == pthread_self())
		abort();
	else
		for (;;)
			(void) pause();
}

static void
uu_lockup(void)
{
	(void) pthread_mutex_lock(&uu_panic_lock);
#if !defined(PTHREAD_ONCE_KEY_NP)
	(void) pthread_mutex_lock(&uu_key_lock);
#endif
	uu_avl_lockup();
	uu_list_lockup();
}

static void
uu_release(void)
{
	(void) pthread_mutex_unlock(&uu_panic_lock);
#if !defined(PTHREAD_ONCE_KEY_NP)
	(void) pthread_mutex_unlock(&uu_key_lock);
#endif
	uu_avl_release();
	uu_list_release();
}

static void
uu_release_child(void)
{
	uu_panic_format = NULL;
	uu_panic_thread = 0;

	uu_release();
}

#ifdef __GNUC__
static void
uu_init(void) __attribute__((constructor));
#else
#pragma init(uu_init)
#endif

static void
uu_init(void)
{
	_uu_main_thread = 1;
	(void) pthread_atfork(uu_lockup, uu_release, uu_release_child);
}
