/*
 * Copyright (c) 2015-21  David Lamparter, for NetDEF, Inc.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* when you work on this code, please install a fuzzer (e.g. AFL) and run
 * tests/lib/fuzz_zlog.c
 *
 * The most likely type of bug in this code is an off-by-one error in the
 * buffer management pieces, and this isn't easily covered by an unit test
 * or topotests.  Fuzzing is the best tool here, but the CI can't do that
 * since it's quite resource intensive.
 */

#include "zebra.h"

#include "zlog_5424.h"

#include <sys/un.h>
#include <syslog.h>

#include "memory.h"
#include "frrcu.h"
#include "printfrr.h"
#include "typerb.h"
#include "frr_pthread.h"
#include "command.h"
#include "monotime.h"
#include "thread.h"

#include "lib/version.h"
#include "lib/lib_errors.h"

DEFINE_MTYPE_STATIC(LOG, LOG_5424, "extended log target");
DEFINE_MTYPE_STATIC(LOG, LOG_5424_ROTATE, "extended log rotate helper");

/* the actual log target data structure
 *
 * remember this is RCU'd by the core zlog functions.  Changing anything
 * works by allocating a new struct, filling it, adding it, and removing the
 * old one.
 */
struct zlt_5424 {
	struct zlog_target zt;

	atomic_uint_fast32_t fd;

	enum zlog_5424_format fmt;
	uint32_t ts_flags;
	int facility;

	/* the various extra pieces to add... */
	bool kw_version : 1;
	bool kw_location : 1;
	bool kw_uid : 1;
	bool kw_ec : 1;
	bool kw_args : 1;

	/* some formats may or may not include the trailing \n */
	bool use_nl : 1;

	/* for DGRAM & SEQPACKET sockets, send 1 log message per packet, since
	 * the socket preserves packet boundaries.  On Linux, this uses
	 * sendmmsg() for efficiency, on other systems we need a syscall each.
	 */
	bool packets : 1;

	/* for DGRAM, in order to not have to reconnect, we need to use
	 * sendto()/sendmsg() with the destination given; otherwise we'll get
	 * ENOTCONN.  (We do a connect(), which serves to verify the type of
	 * socket, but if the receiver goes away, the kernel disconnects the
	 * socket so writev() no longer works since the destination is now
	 * unspecified.)
	 */
	struct sockaddr_storage sa;
	socklen_t sa_len;

	/* these are both getting set, but current_err is cleared on success,
	 * so we know whether the error is current or past.
	 */
	int last_err, current_err;
	atomic_size_t lost_msgs;
	struct timeval last_err_ts;

	struct rcu_head_close head_close;
};

static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type);

/* rough header length estimate
 * ============================
 *
 *   ^ = might grow
 *
 *  49^ longest filename (pceplib/test/pcep_utils_double_linked_list_test.h)
 *   5^ highest line number (48530, bgpd/bgp_nb_config.c)
 *  65^ longest function name
 *      (lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy)
 *  11  unique id ("XXXXX-XXXXX")
 *  10  EC ("4294967295" or "0xffffffff")
 *  35  ISO8601 TS at full length ("YYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ")
 * ---
 * 175
 *
 * rarely used (hopefully...):
 *  26^ FRR_VERSION ("10.10.10-dev-gffffffffffff")
 * ---
 * 201
 *
 * x16  highest number of format parameters currently
 *  40  estimate for hostname + 2*daemon + pid
 *
 * specific format overhead:
 *
 * RFC3164 - shorter than the others
 * RFC5424 - 175 + "<999>1 "=7 + 52 (location@50145) + 40 (host/...)
 *	rarely: + 65 + 26 (for [origin])
 *	args:  16 * (8 + per-arg (20?)) = ~448
 *
 * so without "args@", origin or (future) keywords, around 256 seems OK
 * with args@ and/or origin and/or keywords, 512 seems more reasonable
 *
 * but - note the code allocates this amount multiplied by the number of
 * messages in the incoming batch (minimum 3), this means short messages and
 * long messages smooth each other out.
 *
 * Since the code handles space-exceeded by grabbing a bunch of stack memory,
 * a reasonable middle ground estimate is desirable here, so ...
 * *drumroll*
 * let's go with 128 + args?128.  (remember the minimum 3 multiplier)
 *
 * low_space is the point where we don't try to fit another message in & just
 * submit what we have to the kernel.
 *
 * The zlog code only buffers debug & informational messages, so in production
 * usage most of the calls will be writing out only 1 message.  This makes
 * the min *3 multiplier quite useful.
 */

static inline size_t zlog_5424_bufsz(struct zlt_5424 *zte, size_t nmsgs,
				     size_t *low_space)
{
	size_t ret = 128;

	if (zte->kw_args)
		ret += 128;
	*low_space = ret;
	return ret * MAX(nmsgs, 3);
}

struct state {
	struct fbuf *fbuf;
	struct iovec *iov;
};

/* stack-based keyword support is likely to bump this to 3 or 4 */
#define IOV_PER_MSG 2
_Static_assert(IOV_MAX >= IOV_PER_MSG,
	       "this code won't work with IOV_MAX < IOV_PER_MSG");

/* the following functions are quite similar, but trying to merge them just
 * makes a big mess.  check the others when touching one.
 *
 *		timestamp	keywords	hostname
 * RFC5424	ISO8601		yes		yes
 * RFC3164	RFC3164		no		yes
 * local	RFC3164		no		no
 * journald	ISO8601(unused)	yes		(unused)
 */

static size_t zlog_5424_one(struct zlt_5424 *zte, struct zlog_msg *msg,
			    struct state *state)
{
	size_t textlen;
	struct fbuf *fbuf = state->fbuf;
	char *orig_pos = fbuf->pos;
	size_t need = 0;
	int prio = zlog_msg_prio(msg);
	intmax_t pid, tid;

	zlog_msg_pid(msg, &pid, &tid);

	need += bprintfrr(fbuf, "<%d>1 ", prio | zte->facility);
	need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
	need += bprintfrr(fbuf, " %s %s %jd %.*s ", cmd_hostname_get() ?: "-",
			  zlog_progname, pid, (int)(zlog_prefixsz - 2),
			  zlog_prefix);

	if (zte->kw_version)
		need += bprintfrr(
			fbuf,
			"[origin enterpriseId=\"50145\" software=\"FRRouting\" swVersion=\"%s\"]",
			FRR_VERSION);

	const struct xref_logmsg *xref;
	struct xrefdata *xrefdata;

	need += bprintfrr(fbuf, "[location@50145 tid=\"%jd\"", tid);
	if (zlog_instance > 0)
		need += bprintfrr(fbuf, " instance=\"%d\"", zlog_instance);

	xref = zlog_msg_xref(msg);
	xrefdata = xref ? xref->xref.xrefdata : NULL;
	if (xrefdata) {
		if (zte->kw_uid)
			need += bprintfrr(fbuf, " id=\"%s\"", xrefdata->uid);
		if (zte->kw_ec && prio <= LOG_WARNING)
			need += bprintfrr(fbuf, " ec=\"%u\"", xref->ec);
		if (zte->kw_location)
			need += bprintfrr(
				fbuf, " file=\"%s\" line=\"%d\" func=\"%s\"",
				xref->xref.file, xref->xref.line,
				xref->xref.func);
	}
	need += bputch(fbuf, ']');

	size_t hdrlen, n_argpos;
	const struct fmt_outpos *argpos;
	const char *text;

	text = zlog_msg_text(msg, &textlen);
	zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);

	if (zte->kw_args && n_argpos) {
		need += bputs(fbuf, "[args@50145");

		for (size_t i = 0; i < n_argpos; i++) {
			int len = argpos[i].off_end - argpos[i].off_start;

			need += bprintfrr(fbuf, " arg%zu=%*pSQsq", i + 1, len,
					  text + argpos[i].off_start);
		}

		need += bputch(fbuf, ']');
	}

	need += bputch(fbuf, ' ');

	if (orig_pos + need > fbuf->buf + fbuf->len) {
		/* not enough space in the buffer for headers.  the loop in
		 * zlog_5424() will flush other messages that are already in
		 * the buffer, grab a bigger buffer if needed, and try again.
		 */
		fbuf->pos = orig_pos;
		return need;
	}

	/* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
	state->iov->iov_base = orig_pos;
	state->iov->iov_len = fbuf->pos - orig_pos;
	state->iov++;

	state->iov->iov_base = (char *)text + hdrlen;
	state->iov->iov_len = textlen - hdrlen + zte->use_nl;
	state->iov++;
	return 0;
}

static size_t zlog_3164_one(struct zlt_5424 *zte, struct zlog_msg *msg,
			    struct state *state)
{
	size_t textlen;
	struct fbuf *fbuf = state->fbuf;
	char *orig_pos = fbuf->pos;
	size_t need = 0;
	int prio = zlog_msg_prio(msg);
	intmax_t pid, tid;

	zlog_msg_pid(msg, &pid, &tid);

	need += bprintfrr(fbuf, "<%d>", prio | zte->facility);
	need += zlog_msg_ts_3164(msg, fbuf, zte->ts_flags);
	if (zte->fmt != ZLOG_FMT_LOCAL) {
		need += bputch(fbuf, ' ');
		need += bputs(fbuf, cmd_hostname_get() ?: "-");
	}
	need += bprintfrr(fbuf, " %s[%jd]: ", zlog_progname, pid);

	if (orig_pos + need > fbuf->buf + fbuf->len) {
		/* not enough space in the buffer for headers.  loop in
		 * zlog_5424() will flush other messages that are already in
		 * the buffer, grab a bigger buffer if needed, and try again.
		 */
		fbuf->pos = orig_pos;
		return need;
	}

	/* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
	state->iov->iov_base = orig_pos;
	state->iov->iov_len = fbuf->pos - orig_pos;
	state->iov++;

	state->iov->iov_base = (char *)zlog_msg_text(msg, &textlen);
	state->iov->iov_len = textlen + zte->use_nl;
	state->iov++;
	return 0;
}

static size_t zlog_journald_one(struct zlt_5424 *zte, struct zlog_msg *msg,
				struct state *state)
{
	size_t textlen;
	struct fbuf *fbuf = state->fbuf;
	char *orig_pos = fbuf->pos;
	size_t need = 0;
	int prio = zlog_msg_prio(msg);
	intmax_t pid, tid;

	zlog_msg_pid(msg, &pid, &tid);

	need += bprintfrr(fbuf,
			  "PRIORITY=%d\n"
			  "SYSLOG_FACILITY=%d\n"
			  "TID=%jd\n"
			  "FRR_DAEMON=%s\n"
			  "SYSLOG_TIMESTAMP=",
			  prio, zte->facility, tid, zlog_progname);
	need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
	need += bputch(fbuf, '\n');
	if (zlog_instance > 0)
		need += bprintfrr(fbuf, "FRR_INSTANCE=%d\n", zlog_instance);

	const struct xref_logmsg *xref;
	struct xrefdata *xrefdata;

	xref = zlog_msg_xref(msg);
	xrefdata = xref ? xref->xref.xrefdata : NULL;
	if (xrefdata) {
		if (zte->kw_uid && xrefdata->uid[0])
			need += bprintfrr(fbuf, "FRR_ID=%s\n", xrefdata->uid);
		if (zte->kw_ec && prio <= LOG_WARNING)
			need += bprintfrr(fbuf, "FRR_EC=%d\n", xref->ec);
		if (zte->kw_location)
			need += bprintfrr(fbuf,
					  "CODE_FILE=%s\n"
					  "CODE_LINE=%d\n"
					  "CODE_FUNC=%s\n",
					  xref->xref.file, xref->xref.line,
					  xref->xref.func);
	}

	size_t hdrlen, n_argpos;
	const struct fmt_outpos *argpos;
	const char *text;

	text = zlog_msg_text(msg, &textlen);
	zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);

	if (zte->kw_args && n_argpos) {
		for (size_t i = 0; i < n_argpos; i++) {
			int len = argpos[i].off_end - argpos[i].off_start;

			/* rather than escape the value, we could use
			 * journald's binary encoding, but that seems a bit
			 * excessive/unnecessary.  99% of things we print here
			 * will just output 1:1 with %pSE.
			 */
			need += bprintfrr(fbuf, "FRR_ARG%zu=%*pSE\n", i + 1,
					  len, text + argpos[i].off_start);
		}
	}

	need += bputs(fbuf, "MESSAGE=");

	if (orig_pos + need > fbuf->buf + fbuf->len) {
		/* not enough space in the buffer for headers.  loop in
		 * zlog_5424() will flush other messages that are already in
		 * the buffer, grab a bigger buffer if needed, and try again.
		 */
		fbuf->pos = orig_pos;
		return need;
	}

	/* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
	state->iov->iov_base = orig_pos;
	state->iov->iov_len = fbuf->pos - orig_pos;
	state->iov++;

	state->iov->iov_base = (char *)text + hdrlen;
	state->iov->iov_len = textlen - hdrlen + 1;
	state->iov++;
	return 0;
}

static size_t zlog_one(struct zlt_5424 *zte, struct zlog_msg *msg,
		       struct state *state)
{
	switch (zte->fmt) {
	case ZLOG_FMT_5424:
		return zlog_5424_one(zte, msg, state);
	case ZLOG_FMT_3164:
	case ZLOG_FMT_LOCAL:
		return zlog_3164_one(zte, msg, state);
	case ZLOG_FMT_JOURNALD:
		return zlog_journald_one(zte, msg, state);
	}
	return 0;
}

static void zlog_5424_err(struct zlt_5424 *zte, size_t count)
{
	if (!count) {
		zte->current_err = 0;
		return;
	}

	/* only the counter is atomic because otherwise it'd be meaningless */
	atomic_fetch_add_explicit(&zte->lost_msgs, count, memory_order_relaxed);

	/* these are non-atomic and can provide wrong results when read, but
	 * since they're only for debugging / display, that's OK.
	 */
	zte->current_err = zte->last_err = errno;
	monotime(&zte->last_err_ts);
}

static void zlog_5424(struct zlog_target *zt, struct zlog_msg *msgs[],
		      size_t nmsgs)
{
	size_t i;
	struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
	int fd, ret;
	size_t niov = MIN(IOV_PER_MSG * nmsgs, IOV_MAX);
	struct iovec iov[niov], *iov_last = iov + niov;
	struct mmsghdr mmsg[zte->packets ? nmsgs : 1], *mpos = mmsg;
	size_t count = 0;

	/* refer to size estimate at top of file */
	size_t low_space;
	char hdr_buf[zlog_5424_bufsz(zte, nmsgs, &low_space)];
	struct fbuf hdr_pos = {
		.buf = hdr_buf,
		.pos = hdr_buf,
		.len = sizeof(hdr_buf),
	};
	struct state state = {
		.fbuf = &hdr_pos,
		.iov = iov,
	};

	fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);

	memset(mmsg, 0, sizeof(mmsg));
	if (zte->sa_len) {
		for (i = 0; i < array_size(mmsg); i++) {
			mmsg[i].msg_hdr.msg_name = (struct sockaddr *)&zte->sa;
			mmsg[i].msg_hdr.msg_namelen = zte->sa_len;
		}
	}
	mmsg[0].msg_hdr.msg_iov = iov;

	for (i = 0; i < nmsgs; i++) {
		int prio = zlog_msg_prio(msgs[i]);
		size_t need = 0;

		if (prio <= zte->zt.prio_min) {
			if (zte->packets)
				mpos->msg_hdr.msg_iov = state.iov;

			need = zlog_one(zte, msgs[i], &state);

			if (zte->packets) {
				mpos->msg_hdr.msg_iovlen =
					state.iov - mpos->msg_hdr.msg_iov;
				mpos++;
			}
			count++;
		}

		/* clang-format off */
		if ((need
		     || (size_t)(hdr_pos.buf + hdr_pos.len - hdr_pos.pos)
				< low_space
		     || i + 1 == nmsgs
		     || state.iov + IOV_PER_MSG > iov_last)
		    && state.iov > iov) {
			/* clang-format on */

			if (zte->packets) {
				struct mmsghdr *sendpos;

				for (sendpos = mmsg; sendpos < mpos;) {
					ret = sendmmsg(fd, sendpos,
						       mpos - sendpos, 0);
					if (ret <= 0)
						break;
					sendpos += ret;
				}
				zlog_5424_err(zte, mpos - sendpos);
				mpos = mmsg;
			} else {
				if (!zte->sa_len)
					ret = writev(fd, iov, state.iov - iov);
				else {
					mpos->msg_hdr.msg_iovlen =
						state.iov - iov;
					ret = sendmsg(fd, &mpos->msg_hdr, 0);
				}

				if (ret < 0)
					zlog_5424_err(zte, count);
				else
					zlog_5424_err(zte, 0);
			}

			count = 0;
			hdr_pos.pos = hdr_buf;
			state.iov = iov;
		}

		/* if need == 0, we just put a message (or nothing) in the
		 * buffer and are continuing for more to batch in a single
		 * writev()
		 */
		if (need == 0)
			continue;

		if (need && need <= sizeof(hdr_buf)) {
			/* don't need to allocate, just try this msg
			 * again without other msgs already using up
			 * buffer space
			 */
			i--;
			continue;
		}

		/* need > sizeof(hdr_buf), need to grab some memory.  Taking
		 * it off the stack is fine here.
		 */
		char buf2[need];
		struct fbuf fbuf2 = {
			.buf = buf2,
			.pos = buf2,
			.len = sizeof(buf2),
		};

		state.fbuf = &fbuf2;
		need = zlog_one(zte, msgs[i], &state);
		assert(need == 0);

		if (!zte->sa_len)
			ret = writev(fd, iov, state.iov - iov);
		else {
			mpos->msg_hdr.msg_iovlen = state.iov - iov;
			ret = sendmsg(fd, &mpos->msg_hdr, 0);
		}

		if (ret < 0)
			zlog_5424_err(zte, 1);
		else
			zlog_5424_err(zte, 0);

		count = 0;
		state.fbuf = &hdr_pos;
		state.iov = iov;
		mpos = mmsg;
	}

	assert(state.iov == iov);
}

/* strftime(), gmtime_r() and localtime_r() aren't AS-Safe (they access locale
 * data), but we need an AS-Safe timestamp below :(
 */
static void gmtime_assafe(time_t ts, struct tm *res)
{
	res->tm_sec = ts % 60;
	ts /= 60;
	res->tm_min = ts % 60;
	ts /= 60;
	res->tm_hour = ts % 24;
	ts /= 24;

	ts -= 11017; /* start on 2020-03-01, 11017 days since 1970-01-01 */

	/* 1461 days = 3 regular years + 1 leap year
	 * this works until 2100, which isn't a leap year
	 *
	 * struct tm.tm_year starts at 1900.
	 */
	res->tm_year = 2000 - 1900 + 4 * (ts / 1461);
	ts = ts % 1461;

	if (ts == 1460) {
		res->tm_year += 4;
		res->tm_mon = 1;
		res->tm_mday = 29;
		return;
	}
	res->tm_year += ts / 365;
	ts %= 365;

	/* note we're starting in march like the romans did... */
	if (ts >= 306) /* Jan 1 of next year */
		res->tm_year++;

	static unsigned int months[13] = {
		0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 365,
	};

	for (size_t i = 0; i < array_size(months); i++) {
		if ((unsigned int)ts < months[i + 1]) {
			res->tm_mon = ((i + 2) % 12);
			res->tm_mday = 1 + ts - months[i];
			break;
		}
	}
}

/* one of the greatest advantages of this logging target:  unlike syslog(),
 * which is not AS-Safe, we can send crashlogs to syslog here.
 */
static void zlog_5424_sigsafe(struct zlog_target *zt, const char *text,
			      size_t len)
{
	static const char *const months_3164[12] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};

	struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
	struct iovec iov[3], *iovp = iov;
	char buf[256];
	struct fbuf fbuf = {
		.buf = buf,
		.pos = buf,
		.len = sizeof(buf),
	};
	int fd;
	intmax_t pid = (intmax_t)getpid();
	struct tm tm;

	switch (zte->fmt) {
	case ZLOG_FMT_5424:
		gmtime_assafe(time(NULL), &tm);
		bprintfrr(
			&fbuf,
			"<%d>1 %04u-%02u-%02uT%02u:%02u:%02uZ - %s %jd %.*s  ",
			zte->facility | LOG_CRIT, tm.tm_year + 1900,
			tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
			tm.tm_sec, zlog_progname, pid, (int)(zlog_prefixsz - 2),
			zlog_prefix);
		break;

	case ZLOG_FMT_3164:
	case ZLOG_FMT_LOCAL:
		/* this will unfortuantely be wrong by the timezone offset
		 * if the user selected non-UTC.  But not much we can do
		 * about that...
		 */
		gmtime_assafe(time(NULL), &tm);
		bprintfrr(&fbuf, "<%d>%3s %2u %02u:%02u:%02u %s%s[%jd]: ",
			  zte->facility | LOG_CRIT, months_3164[tm.tm_mon],
			  tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
			  (zte->fmt == ZLOG_FMT_LOCAL) ? "" : "- ",
			  zlog_progname, pid);
		break;

	case ZLOG_FMT_JOURNALD:
		bprintfrr(&fbuf,
			  "PRIORITY=%d\n"
			  "SYSLOG_FACILITY=%d\n"
			  "FRR_DAEMON=%s\n"
			  "MESSAGE=",
			  LOG_CRIT, zte->facility, zlog_progname);
		break;
	}

	iovp->iov_base = fbuf.buf;
	iovp->iov_len = fbuf.pos - fbuf.buf;
	iovp++;

	iovp->iov_base = (char *)text;
	iovp->iov_len = len;
	iovp++;

	if (zte->use_nl) {
		iovp->iov_base = (char *)"\n";
		iovp->iov_len = 1;
		iovp++;
	}

	fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);

	if (!zte->sa_len)
		writev(fd, iov, iovp - iov);
	else {
		struct msghdr mh = {};

		mh.msg_name = (struct sockaddr *)&zte->sa;
		mh.msg_namelen = zte->sa_len;
		mh.msg_iov = iov;
		mh.msg_iovlen = iovp - iov;
		sendmsg(fd, &mh, 0);
	}
}

/* housekeeping & configuration */

void zlog_5424_init(struct zlog_cfg_5424 *zcf)
{
	pthread_mutex_init(&zcf->cfg_mtx, NULL);
}

static void zlog_5424_target_free(struct zlt_5424 *zlt)
{
	if (!zlt)
		return;

	rcu_close(&zlt->head_close, zlt->fd);
	rcu_free(MTYPE_LOG_5424, zlt, zt.rcu_head);
}

void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen)
{
	if (keepopen)
		zcf->active = NULL;

	if (zcf->active) {
		struct zlt_5424 *ztf;
		struct zlog_target *zt;

		zt = zlog_target_replace(&zcf->active->zt, NULL);
		ztf = container_of(zt, struct zlt_5424, zt);
		zlog_5424_target_free(ztf);
	}
	pthread_mutex_destroy(&zcf->cfg_mtx);
}

static void zlog_5424_cycle(struct zlog_cfg_5424 *zcf, int fd)
{
	struct zlog_target *old;
	struct zlt_5424 *zlt = NULL, *oldt;

	if (fd >= 0) {
		struct zlog_target *zt;

		/* all of this is swapped in by zlog_target_replace() below,
		 * the old target is RCU-freed afterwards.
		 */
		zt = zlog_target_clone(MTYPE_LOG_5424, &zcf->active->zt,
				       sizeof(*zlt));
		zlt = container_of(zt, struct zlt_5424, zt);

		zlt->fd = fd;
		zlt->kw_version = zcf->kw_version;
		zlt->kw_location = zcf->kw_location;
		zlt->kw_uid = zcf->kw_uid;
		zlt->kw_ec = zcf->kw_ec;
		zlt->kw_args = zcf->kw_args;
		zlt->use_nl = true;
		zlt->facility = zcf->facility;

		/* DGRAM & SEQPACKET = 1 log message per packet */
		zlt->packets = (zcf->sock_type == SOCK_DGRAM) ||
			       (zcf->sock_type == SOCK_SEQPACKET);
		zlt->sa = zcf->sa;
		zlt->sa_len = zcf->sa_len;
		zlt->fmt = zcf->fmt;
		zlt->zt.prio_min = zcf->prio_min;
		zlt->zt.logfn = zlog_5424;
		zlt->zt.logfn_sigsafe = zlog_5424_sigsafe;

		switch (zcf->fmt) {
		case ZLOG_FMT_5424:
		case ZLOG_FMT_JOURNALD:
			zlt->ts_flags = zcf->ts_flags;
			zlt->ts_flags &= ZLOG_TS_PREC | ZLOG_TS_UTC;
			zlt->ts_flags |= ZLOG_TS_ISO8601;
			break;
		case ZLOG_FMT_3164:
		case ZLOG_FMT_LOCAL:
			zlt->ts_flags = zcf->ts_flags & ZLOG_TS_UTC;
			if (zlt->packets)
				zlt->use_nl = false;
			break;
		}
	}

	old = zcf->active ? &zcf->active->zt : NULL;
	old = zlog_target_replace(old, &zlt->zt);
	zcf->active = zlt;

	/* oldt->fd == fd happens for zlog_5424_apply_meta() */
	oldt = container_of(old, struct zlt_5424, zt);
	if (oldt && oldt->fd != (unsigned int)fd)
		rcu_close(&oldt->head_close, oldt->fd);
	rcu_free(MTYPE_LOG_5424, oldt, zt.rcu_head);
}

static void zlog_5424_reconnect(struct thread *t)
{
	struct zlog_cfg_5424 *zcf = THREAD_ARG(t);
	int fd = THREAD_FD(t);
	char dummy[256];
	ssize_t ret;

	if (zcf->active) {
		ret = read(fd, dummy, sizeof(dummy));
		if (ret > 0) {
			/* logger is sending us something?!?! */
			thread_add_read(t->master, zlog_5424_reconnect, zcf, fd,
					&zcf->t_reconnect);
			return;
		}

		if (ret == 0)
			zlog_warn("logging socket %pSE closed by peer",
				  zcf->filename);
		else
			zlog_warn("logging socket %pSE error: %m",
				  zcf->filename);
	}

	/* do NOT close() anything here;  other threads may still be writing
	 * and their messages need to be lost rather than end up on a random
	 * other fd that got reassigned the same number, like a BGP session!
	 */
	fd = zlog_5424_open(zcf, -1);

	frr_with_mutex (&zcf->cfg_mtx) {
		zlog_5424_cycle(zcf, fd);
	}
}

static int zlog_5424_unix(struct sockaddr_un *suna, int sock_type)
{
	int fd;
	int size = 1 * 1024 * 1024, prev_size;
	socklen_t opt_size;
	int save_errno;

	fd = socket(AF_UNIX, sock_type, 0);
	if (fd < 0)
		return -1;

	if (connect(fd, (struct sockaddr *)suna, sizeof(*suna))) {
		/* zlog_5424_open() will print the error for connect() */
		save_errno = errno;
		close(fd);
		errno = save_errno;
		return -1;
	}

	opt_size = sizeof(prev_size);
	if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &prev_size, &opt_size))
		return fd;

	/* setsockopt_so_sendbuf() logs on error; we don't really care that
	 * much here.  Also, never shrink the buffer below the initial size.
	 */
	while (size > prev_size &&
	       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1)
		size /= 2;

	return fd;
}

static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type)
{
	int fd = -1;
	int flags = 0;
	int err;
	socklen_t optlen;
	bool do_chown = false;
	bool need_reconnect = false;
	static const int unix_types[] = {
		SOCK_STREAM,
		SOCK_SEQPACKET,
		SOCK_DGRAM,
	};
	struct sockaddr_un sa;

	zcf->sock_type = -1;
	zcf->sa_len = 0;

	switch (zcf->dst) {
	case ZLOG_5424_DST_NONE:
		break;

	case ZLOG_5424_DST_FD:
		fd = dup(zcf->fd);

		optlen = sizeof(sock_type);
		if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) {
			zcf->sock_type = sock_type;
			need_reconnect = (zcf->sock_type != SOCK_DGRAM);
		}
		break;

	case ZLOG_5424_DST_FIFO:
		if (!zcf->filename)
			break;

		if (!zcf->file_nocreate) {
			frr_with_privs (lib_privs) {
				mode_t prevmask;

				prevmask = umask(0777 ^ zcf->file_mode);
				err = mkfifo(zcf->filename, 0666);
				umask(prevmask);
			}
			if (err == 0)
				do_chown = true;
			else if (errno != EEXIST)
				break;
		}

		flags = O_NONBLOCK;
		/* fallthru */

	case ZLOG_5424_DST_FILE:
		if (!zcf->filename)
			break;

		frr_with_privs (lib_privs) {
			fd = open(zcf->filename, flags | O_WRONLY | O_APPEND |
							 O_CLOEXEC | O_NOCTTY);
		}
		if (fd >= 0)
			break;
		if (zcf->file_nocreate || flags) {
			flog_err_sys(EC_LIB_SYSTEM_CALL,
				     "could not open log file %pSE: %m",
				     zcf->filename);
			break;
		}

		frr_with_privs (lib_privs) {
			mode_t prevmask;

			prevmask = umask(0777 ^ zcf->file_mode);
			fd = open(zcf->filename,
				  O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY |
					  O_CREAT | O_EXCL,
				  zcf->file_mode);
			umask(prevmask);
		}
		if (fd >= 0) {
			do_chown = true;
			break;
		}

		frr_with_privs (lib_privs) {
			fd = open(zcf->filename,
				  O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY);
		}
		if (fd >= 0)
			break;

		flog_err_sys(EC_LIB_SYSTEM_CALL,
			     "could not open or create log file %pSE: %m",
			     zcf->filename);
		break;

	case ZLOG_5424_DST_UNIX:
		if (!zcf->filename)
			break;

		memset(&sa, 0, sizeof(sa));
		sa.sun_family = AF_UNIX;
		strlcpy(sa.sun_path, zcf->filename, sizeof(sa.sun_path));

		/* check if ZLOG_5424_DST_FD needs a touch when changing
		 * something here.  the user can pass in a pre-opened unix
		 * socket through a fd at startup.
		 */
		frr_with_privs (lib_privs) {
			if (sock_type != -1)
				fd = zlog_5424_unix(&sa, sock_type);
			else {
				for (size_t i = 0; i < array_size(unix_types);
				     i++) {
					fd = zlog_5424_unix(&sa, unix_types[i]);
					if (fd != -1) {
						zcf->sock_type = unix_types[i];
						break;
					}
				}
			}
		}
		if (fd == -1) {
			zcf->sock_type = -1;

			flog_err_sys(
				EC_LIB_SYSTEM_CALL,
				"could not connect to log unix path %pSE: %m",
				zcf->filename);
			need_reconnect = true;
		} else {
			/* datagram sockets are connectionless, restarting
			 * the receiver may lose some packets but will resume
			 * working afterwards without any action from us.
			 */
			need_reconnect = (zcf->sock_type != SOCK_DGRAM);
		}
		break;
	}

	/* viable on both DST_FD and DST_UNIX path */
	if (zcf->sock_type == SOCK_DGRAM) {
		zcf->sa_len = sizeof(zcf->sa);
		if (getpeername(fd, (struct sockaddr *)&zcf->sa,
				&zcf->sa_len)) {
			flog_err_sys(
				EC_LIB_SYSTEM_CALL,
				"could not get remote address for log socket.  logging may break if log receiver restarts.");
			zcf->sa_len = 0;
		}
	}

	if (do_chown) {
		uid_t uid = zcf->file_uid;
		gid_t gid = zcf->file_gid;

		if (uid != (uid_t)-1 || gid != (gid_t)-1) {
			frr_with_privs (lib_privs) {
				err = fchown(fd, uid, gid);
			}
			if (err)
				flog_err_sys(
					EC_LIB_SYSTEM_CALL,
					"failed to chown() log file %pSE: %m",
					zcf->filename);
		}
	}

	if (need_reconnect) {
		assert(zcf->master);

		if (fd != -1) {
			thread_add_read(zcf->master, zlog_5424_reconnect, zcf,
					fd, &zcf->t_reconnect);
			zcf->reconn_backoff_cur = zcf->reconn_backoff;

		} else {
			thread_add_timer_msec(zcf->master, zlog_5424_reconnect,
					      zcf, zcf->reconn_backoff_cur,
					      &zcf->t_reconnect);

			zcf->reconn_backoff_cur += zcf->reconn_backoff_cur / 2;
			if (zcf->reconn_backoff_cur > zcf->reconn_backoff_max)
				zcf->reconn_backoff_cur =
					zcf->reconn_backoff_max;
		}
	}

	return fd;
}

bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf)
{
	int fd = -1;

	thread_cancel(&zcf->t_reconnect);

	if (zcf->prio_min != ZLOG_DISABLED)
		fd = zlog_5424_open(zcf, -1);

	frr_with_mutex (&zcf->cfg_mtx) {
		zlog_5424_cycle(zcf, fd);
	}
	return fd != -1;
}


bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf)
{
	frr_with_mutex (&zcf->cfg_mtx) {
		if (zcf->active)
			zlog_5424_cycle(zcf, zcf->active->fd);
	}

	return true;
}

void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs,
		     int *last_errno, bool *stale_errno, struct timeval *err_ts)
{
	if (lost_msgs)
		*lost_msgs =
			zcf->active
				? atomic_load_explicit(&zcf->active->lost_msgs,
						       memory_order_relaxed)
				: 0;
	if (last_errno)
		*last_errno = zcf->active ? zcf->active->last_err : 0;
	if (stale_errno)
		*stale_errno = zcf->active ? !zcf->active->current_err : 0;
	if (err_ts && zcf->active)
		*err_ts = zcf->active->last_err_ts;
}

struct rcu_close_rotate {
	struct rcu_head_close head_close;
	struct rcu_head head_self;
};

bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf)
{
	struct rcu_close_rotate *rcr;
	int fd;

	frr_with_mutex (&zcf->cfg_mtx) {
		if (!zcf->active)
			return true;

		thread_cancel(&zcf->t_reconnect);

		/* need to retain the socket type because it also influences
		 * other fields (packets) and we can't atomically swap these
		 * out.  But we really want the atomic swap so we neither lose
		 * nor duplicate log messages, particularly for file targets.
		 *
		 * (zlog_5424_apply_dst / zlog_target_replace will cause
		 * duplicate log messages if another thread logs something
		 * while we're right in the middle of swapping out the log
		 * target)
		 */
		fd = zlog_5424_open(zcf, zcf->sock_type);
		if (fd < 0)
			return false;

		fd = atomic_exchange_explicit(&zcf->active->fd,
					      (uint_fast32_t)fd,
					      memory_order_relaxed);
	}

	rcr = XCALLOC(MTYPE_LOG_5424_ROTATE, sizeof(*rcr));
	rcu_close(&rcr->head_close, fd);
	rcu_free(MTYPE_LOG_5424_ROTATE, rcr, head_self);

	return true;
}
