/*
 *  Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
 *  Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
 *  Copyright (C) 2007 - 2018 Western Digital Corporation
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation, version 2
 *  of the License.
 *
 *  This program 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 General Public License for more details.
 */

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/time.h>
#include <string.h>

#include "iscsid.h"

int log_daemon = 1;
int log_level = 0;

void log_init(void)
{
	if (log_daemon)
		openlog("iscsi-scstd", 0, LOG_DAEMON);
}

static void dolog_nofunc(int prio, const char *fmt, va_list ap)
{
	if (log_daemon) {
		int len = strlen(fmt);
		char f[len+1+1];

		if (fmt[len] != '\n')
			sprintf(f, "%s\n", fmt);
		else
			sprintf(f, "%s", fmt);
		vsyslog(prio, f, ap);
	} else {
		struct timeval time;

		gettimeofday(&time, NULL);
		fprintf(stderr, "%ld.%06ld: ", time.tv_sec, time.tv_usec);
		vfprintf(stderr, fmt, ap);
		fprintf(stderr, "\n");
		fflush(stderr);
	}
}

static void dolog(int prio, const char *func, int line, const char *fmt, va_list ap)
{
	if (log_level == 0) {
		dolog_nofunc(prio, fmt, ap);
		return;
	}

	if (log_daemon) {
		int len = strlen(func) + strlen(fmt);
		char f[len+1+1];

		if (fmt[len] != '\n')
			sprintf(f, "%s:%d: %s %s\n", func, line,
				(prio == LOG_ERR) ? "ERROR:" : "", fmt);
		else
			sprintf(f, "%s:%d: %s %s", func, line,
				(prio == LOG_ERR) ? "ERROR: " : "", fmt);
		vsyslog(prio, f, ap);
	} else {
		struct timeval time;

		gettimeofday(&time, NULL);
		fprintf(stderr, "%ld.%06ld: %s:%d: ", time.tv_sec, time.tv_usec, func, line);
		vfprintf(stderr, fmt, ap);
		fprintf(stderr, "\n");
		fflush(stderr);
	}
}

void __log(const char *func, int line, int prio, int level, const char *fmt, ...)
{
	va_list ap;

	if (level) {
		prio = LOG_DEBUG;
		if (log_level < level)
			return;
	}

	va_start(ap, fmt);
	dolog(prio, func, line, fmt, ap);
	va_end(ap);
}

/* Definition for __log_pdu buffer */
#define BUFFER_SIZE 16

/*
 * size required for a hex dump of BUFFER_SIZE bytes (' ' + 2 chars = 3 chars
 * per byte) with a ' |' separator each 4th byte:
 */
#define LINE_SIZE (BUFFER_SIZE * 3 + BUFFER_SIZE / 4 * 2 + 1)

static void __dump_line(const char *func, int line_num, int level, unsigned char *buf, int *cp)
{
	char line[LINE_SIZE], *lp = line;
	int i, cnt;

	cnt = *cp;
	if (!cnt)
		return;
	for (i = 0; i < BUFFER_SIZE; i++) {
		if (i < cnt)
			lp += sprintf(lp, " %02x", buf[i]);
		else
			lp += sprintf(lp, "   ");
		if ((i % 4) == 3)
			lp += sprintf(lp, " |");
		if (i >= cnt || !isprint(buf[i]))
			buf[i] =  ' ';
	}

	/* buf is not \0-terminated! */
	__log(func, line_num, LOG_DEBUG, level, "%s %.*s |", line, BUFFER_SIZE, buf);
	*cp = 0;
}

static void __dump_char(const char *func, int line, int level, unsigned char *buf, int *cp, int ch)
{
	int cnt = (*cp)++;

	buf[cnt] = ch;
	if (cnt == BUFFER_SIZE - 1)
		__dump_line(func, line, level, buf, cp);
}

#define dump_line() __dump_line(func, line, level, char_buf, &char_cnt)
#define dump_char(ch) __dump_char(func, line, level, char_buf, &char_cnt, ch)

void __log_pdu(const char *func, int line, int level, struct PDU *pdu)
{
	unsigned char char_buf[BUFFER_SIZE];
	int char_cnt = 0;
	unsigned char *buf;
	int i;

	if (log_level < level)
		return;

	buf = (void *)&pdu->bhs;
	__log(func, line, LOG_DEBUG, level, "BHS: (%p)", buf);
	for (i = 0; i < BHS_SIZE; i++)
		dump_char(*buf++);
	dump_line();

	buf = (void *)pdu->ahs;
	__log(func, line, LOG_DEBUG, level, "AHS: (%p)", buf);
	for (i = 0; i < pdu->ahssize; i++)
		dump_char(*buf++);
	dump_line();

	buf = (void *)pdu->data;
	__log(func, line, LOG_DEBUG, level, "Data: (%p)", buf);
	for (i = 0; i < pdu->datasize; i++)
		dump_char(*buf++);
	dump_line();
}
