/*
 * 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 http://www.opensolaris.org/os/licensing.
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <scsi/libses.h>
#include "ses_impl.h"

__thread ses_errno_t _ses_errno;
__thread char _ses_errmsg[1024];
__thread char _ses_nverr_member[256];

static void ses_vpanic(const char *, va_list) __NORETURN;

static void
ses_vpanic(const char *fmt, va_list ap)
{
	int oserr = errno;
	char msg[BUFSIZ];
	size_t len;

	(void) snprintf(msg, sizeof (msg), "ABORT: ");
	len = strlen(msg);
	(void) vsnprintf(msg + len, sizeof (msg) - len, fmt, ap);

	if (strchr(fmt, '\n') == NULL) {
		len = strlen(msg);
		(void) snprintf(msg + len, sizeof (msg) - len, ": %s\n",
		    strerror(oserr));
	}

	(void) write(STDERR_FILENO, msg, strlen(msg));

abort:
	abort();
	_exit(1);
}

/*PRINTFLIKE1*/
void
ses_panic(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	ses_vpanic(fmt, ap);
	va_end(ap);
}

int
ses_assert(const char *expr, const char *file, int line)
{
	ses_panic("\"%s\", line %d: assertion failed: %s\n", file, line, expr);

	/*NOTREACHED*/
	return (0);
}

int
nvlist_add_fixed_string(nvlist_t *nvl, const char *name,
    const char *buf, size_t len)
{
	char *str = alloca(len + 1);
	bcopy(buf, str, len);
	str[len] = '\0';

	return (nvlist_add_string(nvl, name, str));
}

/*
 * Like fixed_string, but clears any leading or trailing spaces.
 */
int
nvlist_add_fixed_string_trunc(nvlist_t *nvl, const char *name,
    const char *buf, size_t len)
{
	while (buf[0] == ' ' && len > 0) {
		buf++;
		len--;
	}

	while (len > 0 && buf[len - 1] == ' ')
		len--;

	return (nvlist_add_fixed_string(nvl, name, buf, len));
}

ses_errno_t
ses_errno(void)
{
	return (_ses_errno);
}

const char *
ses_errmsg(void)
{
	if (_ses_errmsg[0] == '\0')
		(void) snprintf(_ses_errmsg, sizeof (_ses_errmsg), "%s",
		    ses_strerror(_ses_errno));

	return (_ses_errmsg);
}

const char *
ses_nv_error_member(void)
{
	if (_ses_nverr_member[0] != '\0')
		return (_ses_nverr_member);
	else
		return (NULL);
}

static int
__ses_set_errno(ses_errno_t err, const char *nvm)
{
	if (nvm == NULL) {
		_ses_nverr_member[0] = '\0';
	} else {
		(void) strlcpy(_ses_nverr_member, nvm,
		    sizeof (_ses_nverr_member));
	}
	_ses_errmsg[0] = '\0';
	_ses_errno = err;

	return (-1);
}

int
ses_set_errno(ses_errno_t err)
{
	return (__ses_set_errno(err, NULL));
}

int
ses_set_nverrno(int err, const char *member)
{
	ses_errno_t se = (err == ENOMEM || err == EAGAIN) ?
	    ESES_NOMEM : ESES_NVL;

	/*
	 * If the error is ESES_NVL, then we should always have a member
	 * available.  The only time 'member' is NULL is when nvlist_alloc()
	 * fails, which should only be possible if memory allocation fails.
	 */
	assert(se == ESES_NOMEM || member != NULL);

	return (__ses_set_errno(se, member));
}

static int
ses_verror(ses_errno_t err, const char *fmt, va_list ap)
{
	int syserr = errno;
	size_t n;
	char *errmsg;

	errmsg = alloca(sizeof (_ses_errmsg));
	(void) vsnprintf(errmsg, sizeof (_ses_errmsg), fmt, ap);
	(void) ses_set_errno(err);

	n = strlen(errmsg);

	while (n != 0 && errmsg[n - 1] == '\n')
		errmsg[--n] = '\0';

	bcopy(errmsg, _ses_errmsg, sizeof (_ses_errmsg));
	errno = syserr;

	return (-1);
}

static int
ses_vnverror(int err, const char *member, const char *fmt,
    va_list ap)
{
	int syserr = errno;
	size_t n;
	char *errmsg;

	errmsg = alloca(sizeof (_ses_errmsg));
	(void) vsnprintf(errmsg, sizeof (_ses_errmsg), fmt, ap);
	(void) ses_set_nverrno(err, member);

	n = strlen(errmsg);

	while (n != 0 && errmsg[n - 1] == '\n')
		errmsg[--n] = '\0';

	(void) snprintf(errmsg + n, sizeof (_ses_errmsg) - n, ": %s",
	    strerror(err));

	bcopy(errmsg, _ses_errmsg, sizeof (_ses_errmsg));
	errno = syserr;

	return (-1);
}

int
ses_error(ses_errno_t err, const char *fmt, ...)
{
	va_list ap;
	int rv;

	va_start(ap, fmt);
	rv = ses_verror(err, fmt, ap);
	va_end(ap);

	return (rv);
}

int
ses_nverror(int err, const char *member, const char *fmt, ...)
{
	va_list ap;
	int rv;

	va_start(ap, fmt);
	rv = ses_vnverror(err, member, fmt, ap);
	va_end(ap);

	return (rv);
}

int
ses_libscsi_error(libscsi_hdl_t *shp, const char *fmt, ...)
{
	va_list ap;
	char errmsg[LIBSES_ERRMSGLEN];
	libscsi_errno_t se = libscsi_errno(shp);
	ses_errno_t e;

	switch (se) {
	case ESCSI_NONE:
		return (0);
	case ESCSI_NOMEM:
		e = ESES_NOMEM;
		break;
	case ESCSI_NOTSUP:
		e = ESES_NOTSUP;
		break;
	case ESCSI_ZERO_LENGTH:
	case ESCSI_VERSION:
	case ESCSI_BADFLAGS:
	case ESCSI_BOGUSFLAGS:
	case ESCSI_BADLENGTH:
	case ESCSI_NEEDBUF:
		va_start(ap, fmt);
		(void) vsnprintf(errmsg, sizeof (errmsg), fmt, ap);
		va_end(ap);
		ses_panic("%s: unexpected libscsi error %s: %s", errmsg,
		    libscsi_errname(se), libscsi_errmsg(shp));
		break;
	case ESCSI_UNKNOWN:
		e = ESES_UNKNOWN;
		break;
	default:
		e = ESES_LIBSCSI;
		break;
	}

	va_start(ap, fmt);
	(void) vsnprintf(errmsg, sizeof (errmsg), fmt, ap);
	va_end(ap);

	return (ses_error(e, "%s: %s", errmsg, libscsi_errmsg(shp)));
}

int
ses_scsi_error(libscsi_action_t *ap, const char *fmt, ...)
{
	va_list args;
	char errmsg[LIBSES_ERRMSGLEN];
	uint64_t asc = 0, ascq = 0, key = 0;
	const char *code, *keystr;

	va_start(args, fmt);
	(void) vsnprintf(errmsg, sizeof (errmsg), fmt, args);
	va_end(args);

	if (libscsi_action_parse_sense(ap, &key, &asc, &ascq, NULL) != 0)
		return (ses_error(ESES_LIBSCSI,
		    "%s: SCSI status %d (no sense data available)", errmsg,
		    libscsi_action_get_status(ap)));

	code = libscsi_sense_code_name(asc, ascq);
	keystr = libscsi_sense_key_name(key);

	return (ses_error(ESES_LIBSCSI, "%s: SCSI status %d sense key %llu "
	    "(%s) additional sense code 0x%llx/0x%llx (%s)", errmsg,
	    libscsi_action_get_status(ap), key, keystr ? keystr : "<unknown>",
	    asc, ascq, code ? code : "<unknown>"));
}

void *
ses_alloc(size_t sz)
{
	void *p;

	if (sz == 0)
		ses_panic("attempted zero-length allocation");

	if ((p = malloc(sz)) == NULL)
		(void) ses_set_errno(ESES_NOMEM);

	return (p);
}

void *
ses_zalloc(size_t sz)
{
	void *p;

	if ((p = ses_alloc(sz)) != NULL)
		bzero(p, sz);

	return (p);
}

char *
ses_strdup(const char *s)
{
	char *p;
	size_t len;

	if (s == NULL)
		ses_panic("attempted zero-length allocation");

	len = strlen(s) + 1;

	if ((p = ses_alloc(len)) != NULL)
		bcopy(s, p, len);

	return (p);
}

void *
ses_realloc(void *p, size_t sz)
{
	if (sz == 0)
		ses_panic("attempted zero-length allocation");

	if ((p = realloc(p, sz)) == NULL)
		(void) ses_set_errno(ESES_NOMEM);

	return (p);
}

/*ARGSUSED*/
void
ses_free(void *p)
{
	free(p);
}