// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 * This file is part of libnvme.
 * Copyright (C) 2021 SUSE LLC
 *
 * Authors: Hannes Reinecke <hare@suse.de>
 *
 * This file implements basic logging functionality.
 */

#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <shared/compiler-attributes-util.h>
#include <shared/io-util.h>

#include <libnvme.h>

#include "cleanup.h"
#define LOG_FUNCNAME 1
#include "private.h"

#ifndef LOG_CLOCK
#define LOG_CLOCK CLOCK_MONOTONIC
#endif

void __libnvme_printf_format(4, 5)
__libnvme_msg(struct libnvme_global_ctx *ctx, int level,
	   const char *func, const char *format, ...)
{
	struct libnvme_log *l = &ctx->log;
	va_list ap;
	char pidbuf[16];
	char timebuf[32];
	static const char *const formats[] = {
		"%s%s%s",
		"%s%s%s: ",
		"%s<%s>%s ",
		"%s<%s> %s: ",
		"[%s] %s%s ",
		"[%s]%s %s: ",
		"[%s] <%s>%s ",
		"[%s] <%s> %s: ",
	};
	__cleanup_free char *header = NULL;
	__cleanup_free char *message = NULL;
	__cleanup_free char *log = NULL;
	int idx = 0;

	if (level > l->level)
		return;

	if (l->timestamp) {
		struct timespec now;

		clock_gettime(LOG_CLOCK, &now);
		snprintf(timebuf, sizeof(timebuf), "%6ld.%06ld",
			 (long)now.tv_sec, now.tv_nsec / 1000);
		idx |= 1 << 2;
	} else
		*timebuf = '\0';

	if (l->pid) {
		snprintf(pidbuf, sizeof(pidbuf), "%ld", (long)getpid());
		idx |= 1 << 1;
	} else
		*pidbuf = '\0';

	if (func)
		idx |= 1 << 0;

	if (asprintf(&header, formats[idx],
		     timebuf, pidbuf, func ? func : "") == -1)
		header = NULL;

	va_start(ap, format);
	if (vasprintf(&message, format, ap) == -1)
		message = NULL;
	va_end(ap);

	if (asprintf(&log, "%s%s", header ? header : "<error>",
			message ? message : "<error>") == -1)
		return;

	if (shr_write_all(l->fd, log, strlen(log)) < 0)
		perror("failed to write log entry");
}

__shr_public void libnvme_set_logging_level(
		struct libnvme_global_ctx *ctx, int log_level, bool log_pid,
		bool log_tstamp)
{
	ctx->log.level = log_level;
	ctx->log.pid = log_pid;
	ctx->log.timestamp = log_tstamp;
}

__shr_public int libnvme_get_logging_level(struct libnvme_global_ctx *ctx,
		bool *log_pid, bool *log_tstamp)
{
	if (log_pid)
		*log_pid = ctx->log.pid;
	if (log_tstamp)
		*log_tstamp = ctx->log.timestamp;
	return ctx->log.level;
}

__shr_public void libnvme_set_logging_file(struct libnvme_global_ctx *ctx,
		FILE *fp)
{
	ctx->log.fd = fp ? fileno(fp) : STDERR_FILENO;
}
