/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <smbios.h>

#include <fm/fmd_api.h>

#include "util.h"
#include "disk_monitor.h"

extern log_class_t g_verbose;

static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

static void
verror(const char *fmt, va_list ap)
{
	int error = errno;

	dm_assert(pthread_mutex_lock(&log_mutex) == 0);
	fmd_hdl_vdebug(g_fm_hdl, fmt, ap);

	if (fmt[strlen(fmt) - 1] != '\n')
		fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));

	dm_assert(pthread_mutex_unlock(&log_mutex) == 0);
}

static void
vwarn_e(const char *fmt, va_list ap)
{
	int error = errno;

	dm_assert(pthread_mutex_lock(&log_mutex) == 0);
	fmd_hdl_debug(g_fm_hdl, "WARNING: ");
	fmd_hdl_vdebug(g_fm_hdl, fmt, ap);

	if (fmt[strlen(fmt) - 1] != '\n')
		fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));

	dm_assert(pthread_mutex_unlock(&log_mutex) == 0);
}

static void
vwarn(const char *fmt, va_list ap)
{
	dm_assert(pthread_mutex_lock(&log_mutex) == 0);
	fmd_hdl_debug(g_fm_hdl, "WARNING: ");
	fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
	dm_assert(pthread_mutex_unlock(&log_mutex) == 0);
}

void
vcont(log_class_t cl, const char *fmt, va_list ap)
{
	int error = errno;

	if ((g_verbose & cl) != cl)
		return;

	dm_assert(pthread_mutex_lock(&log_mutex) == 0);
	fmd_hdl_vdebug(g_fm_hdl, fmt, ap);

	if (fmt[strlen(fmt) - 1] != '\n')
		fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));

	dm_assert(pthread_mutex_unlock(&log_mutex) == 0);
}

void
log_msg(log_class_t cl, const char *fmt, ...)
{
	va_list ap;

	if ((g_verbose & cl) != cl)
		return;

	dm_assert(pthread_mutex_lock(&log_mutex) == 0);
	va_start(ap, fmt);
	fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
	va_end(ap);
	dm_assert(pthread_mutex_unlock(&log_mutex) == 0);
}

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

	if ((g_verbose & MM_ERR) != MM_ERR)
		return;

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

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

	if ((g_verbose & MM_WARN) != MM_WARN)
		return;

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

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

	if ((g_verbose & MM_WARN) != MM_WARN)
		return;

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

void
dfree(void *p, size_t sz)
{
	fmd_hdl_free(g_fm_hdl, p, sz);
}

void
dstrfree(char *s)
{
	fmd_hdl_strfree(g_fm_hdl, s);
}

void *
dmalloc(size_t sz)
{
	return (fmd_hdl_alloc(g_fm_hdl, sz, FMD_SLEEP));
}

void *
dzmalloc(size_t sz)
{
	return (fmd_hdl_zalloc(g_fm_hdl, sz, FMD_SLEEP));
}


char *
dstrdup(const char *s)
{
	return (fmd_hdl_strdup(g_fm_hdl, s, FMD_SLEEP));
}

void
queue_add(qu_t *qp, void *data)
{
	struct q_node *qnp =
	    (struct q_node *)qp->nalloc(sizeof (struct q_node));
	struct q_node *nodep;

	qnp->data = data;
	qnp->next = NULL;
	dm_assert(pthread_mutex_lock(&qp->mutex) == 0);

	if (qp->nodep == NULL)
		qp->nodep = qnp;
	else {
		nodep = qp->nodep;

		while (nodep->next != NULL)
			nodep = nodep->next;

		nodep->next = qnp;
	}

	/* If the queue was empty, we need to wake people up */
	if (qp->boe && qp->nodep == qnp)
		dm_assert(pthread_cond_broadcast(&qp->cvar) == 0);
	dm_assert(pthread_mutex_unlock(&qp->mutex) == 0);
}

void *
queue_remove(qu_t *qp)
{
	void *rv = NULL;
	struct q_node *nextnode;

	dm_assert(pthread_mutex_lock(&qp->mutex) == 0);

	/* Wait while the queue is empty */
	while (qp->boe && qp->nodep == NULL) {
		(void) pthread_cond_wait(&qp->cvar, &qp->mutex);
	}

	/*
	 * If Block-On-Empty is false, the queue may be empty
	 */
	if (qp->nodep != NULL) {
		rv = qp->nodep->data;
		nextnode = qp->nodep->next;
		qp->nfree(qp->nodep, sizeof (struct q_node));
		qp->nodep = nextnode;
	}

	dm_assert(pthread_mutex_unlock(&qp->mutex) == 0);
	return (rv);
}

qu_t *
new_queue(boolean_t block_on_empty, void *(*nodealloc)(size_t),
    void (*nodefree)(void *, size_t), void (*data_deallocator)(void *))
{
	qu_t *newqp = (qu_t *)dmalloc(sizeof (qu_t));

	newqp->boe = block_on_empty;
	newqp->nalloc = nodealloc;
	newqp->nfree = nodefree;
	newqp->data_dealloc = data_deallocator;
	dm_assert(pthread_mutex_init(&newqp->mutex, NULL) == 0);
	dm_assert(pthread_cond_init(&newqp->cvar, NULL) == 0);
	newqp->nodep = NULL;

	return (newqp);
}

void
queue_free(qu_t **qpp)
{
	qu_t *qp = *qpp;
	void *item;

	dm_assert(pthread_mutex_destroy(&qp->mutex) == 0);
	dm_assert(pthread_cond_destroy(&qp->cvar) == 0);

	qp->boe = B_FALSE;

	while ((item = queue_remove(qp)) != NULL) {
		qp->data_dealloc(item);
	}

	dm_assert(qp->nodep == NULL);

	dfree(qp, sizeof (qu_t));
	*qpp = NULL;
}

int
_dm_assert(const char *assertion, const char *file, int line, const char *func)
{
	/*
	 * No newline is appended to the assertion message so that
	 * errno can be translated for us by fmd_hdl_abort().
	 */
	if (func)
		fmd_hdl_abort(g_fm_hdl, "Assertion failed: "
		    "%s, file: %s, line: %d, function: %s", assertion, file,
		    line, func);
	else
		fmd_hdl_abort(g_fm_hdl, "Assertion failed: "
		    "%s, file: %s, line: %d", assertion, file, line);
	/*NOTREACHED*/
	return (0);
}