/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple LNXALL binary logger interface
 *
 * 2024/12/23
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <sys/uio.h>
#include <endian.h>

#include "lnxbinlog.h"
#include "binlog_format.h"

static int lnxbinlog_fd;
static pthread_mutex_t logfd_lock;

static void lnxall_binlog_init(void)
	__attribute__((constructor, used));

static int lnxall_binlog_lockfd(int verbose)
{
	int ret;
	ret = pthread_mutex_lock(&logfd_lock);
	if (ret == 0)
		return 0;

	if (verbose != 0) {
		fprintf(stderr, "Error, failed to lock binlog: %d\n", ret);
		fflush(stderr);
	}
	return ret;
}

int lnxbinlog_getfile(char * fpath, unsigned int flen, uint64_t * cursize)
{
	ssize_t ret;
	int locked, fd;
	struct stat fst;
	char buf[128], tbuf[768];

	memset(&fst, 0, sizeof(fst));
	locked = lnxall_binlog_lockfd(1);
	fd = lnxbinlog_fd;
	if (fd != -1 && cursize != NULL)
		fstat(fd, &fst);
	if (locked == 0)
		pthread_mutex_unlock(&logfd_lock);

	if (cursize != NULL)
		*cursize = (uint64_t) fst.st_size;
	snprintf(buf, sizeof(buf), "/proc/%ld/fd/%d", (long) getpid(), fd);
	ret = readlink(buf, tbuf, sizeof(tbuf) - 1);
	if (ret <= 0)
		return -1;
	if (ret >= sizeof(tbuf))
		ret = sizeof(tbuf) - 1;
	tbuf[ret] = '\0';

	strncpy(fpath, tbuf, (size_t) flen);
	return 0;
}

int lnxbinlog_flush(uint64_t * cursize)
{
	int ret, fd, locked;
	struct stat fst;

	ret = 0;
	locked = lnxall_binlog_lockfd(1);

	fd = lnxbinlog_fd;
	if (fd < 0) {
		if (locked == 0)
			pthread_mutex_unlock(&logfd_lock);
		errno = EBADF;
		return -1;
	}

	ret = fsync(fd);
	if (cursize != 0) {
		memset(&fst, 0, sizeof(fst));
		ret = fstat(fd, &fst);
		if (ret == 0)
			*cursize = (uint64_t) fst.st_size;
	}

	if (locked == 0)
		pthread_mutex_unlock(&logfd_lock);
	return ret;
}

void lnxall_binlog_init(void)
{
	int ret;
	pthread_mutexattr_t mattr;

	lnxbinlog_fd = -1;
	memset(&logfd_lock, 0, sizeof(logfd_lock));

	memset(&mattr, 0, sizeof(mattr));
	pthread_mutexattr_init(&mattr);
	pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
	pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);

	ret = pthread_mutex_init(&logfd_lock, &mattr);
	pthread_mutexattr_destroy(&mattr);
	if (ret != 0) {
		fprintf(stderr, "Error, failed to initialize binlog mutex: %d\n", ret);
		fflush(stderr);
	}
}

int lnxbinlog_setfile(const char * logfile)
{
	int locked;
	int fd, oldfd, error, i;

	if (logfile == NULL)
		return -1;
	fd = open(logfile, O_WRONLY | O_CLOEXEC | O_CREAT | O_APPEND, 0644);
	if (fd < 0) {
		error = errno;
		fprintf(stderr, "Error, failed to open file '%s': %s\n", logfile, strerror(error));
		fflush(stderr);
		errno = error;
		return -2;
	}

	for (i = 512; i > 3; i--) {
		int ret;
		struct stat fst;

		ret = fstat(i, &fst);
		error = errno;
		if (ret == -1 && error == EBADF) {
			if (i == dup3(fd, i, O_CLOEXEC)) {
				close(fd);
				fd = i;
			}
			break;
		}
	}

	locked = lnxall_binlog_lockfd(1);
	oldfd = lnxbinlog_fd;
	lnxbinlog_fd = -1;
	if (oldfd != -1) {
		struct timespec ts;
		ts.tv_sec = 0;
		ts.tv_nsec = 200000000;
		nanosleep(&ts, NULL);
	}

	lnxbinlog_fd = fd;
	if (oldfd != fd)
		close(oldfd);

	if (locked == 0)
		pthread_mutex_unlock(&logfd_lock);
	return 0;
}

static uint64_t lnxbinlog_time(const struct timespec * timp)
{
	uint64_t ret;
	ret = (uint64_t) timp->tv_sec;
	ret = ret * 1000 + (uint64_t) (timp->tv_nsec / 1000000);
	return ret;
}

int lnxbinlog_push(const struct binlog_type * bltype,
	const char * logdat, unsigned int loglen)
{
	size_t len, dlen;
	uint64_t msec;
	struct blog_head * blh;
	struct timespec ts1, ts2;
	unsigned char hbuf[sizeof(struct blog_head) + 256];

	ssize_t wlen;
	int iovnum, fd, locked, error;
	struct iovec iov[3];
	unsigned char padbuf[BLOG_DATA_ALIGN] = { 0 };

	blh = NULL;
	if (bltype == NULL) {
		errno = EINVAL;
		return -1;
	}

	if (logdat == NULL)
		loglen = 0;

	clock_gettime(CLOCK_MONOTONIC, &ts1);
	clock_gettime(CLOCK_REALTIME, &ts2);

	len = sizeof(*blh);
	dlen = (size_t) bltype->desc_len;
	if (bltype->desc_data == NULL)
		dlen = 0;
	blh = (struct blog_head *) hbuf;

	blh->magic0     = BLOG_MAGIC_0;
	blh->desc_len   = bltype->desc_len;
	blh->data_len   = htobe16((uint16_t) loglen);
	blh->log_type   = htobe16(bltype->log_type);

	msec = lnxbinlog_time(&ts1);
	blh->upmsec_h   = htobe16((uint16_t) (msec >> 32));
	blh->upmsec_l   = htobe32((uint32_t) msec);

	blh->magic1     = BLOG_MAGIC_1;
	blh->magic2     = BLOG_MAGIC_2;

	msec = lnxbinlog_time(&ts2);
	blh->utcmsec_h  = htobe16((uint16_t) (msec >> 32));
	blh->utcmsec_l  = htobe32((uint32_t) msec);

	if (dlen > 0) {
		len += dlen;
		memcpy(&hbuf[sizeof(*blh)], bltype->desc_data, dlen);
	}

	iov[0].iov_base = (void *) hbuf;
	iov[0].iov_len = len;
	iovnum = 1;

	if (loglen > 0) {
		iov[iovnum].iov_base = (void *) logdat;
		iov[iovnum].iov_len = (size_t) loglen;
		iovnum++;
		dlen += (size_t) loglen;
	}

	if (dlen & (BLOG_DATA_ALIGN - 0x1)) {
		iov[iovnum].iov_base = padbuf;
		iov[iovnum].iov_len = BLOG_DATA_ALIGN - (dlen & (BLOG_DATA_ALIGN - 1));

		iovnum++;
		dlen = BLOG_DATA_ALIGN + (dlen & ~(BLOG_DATA_ALIGN - 0x1));
	}

	locked = lnxall_binlog_lockfd(1);
	fd = lnxbinlog_fd;
	if (fd < 0) {
		if (locked == 0)
			pthread_mutex_unlock(&logfd_lock);
		errno = EBADF;
		return -1;
	}

	errno = 0;
	wlen = writev(fd, iov, iovnum);
	error = errno;

	if (locked == 0)
		pthread_mutex_unlock(&logfd_lock);
	if (error != 0)
		errno = error;
	return (int) wlen;
}
