/*
 * ausearch-checkpt.c - ausearch checkpointing feature
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include "ausearch-checkpt.h"

#define	DBG	0	/* set to non-zero for debug */
#if SIZEOF_LONG < SIZEOF_TIME_T
#define TIME_T_SPECIFIER "%lld"
#else
#define TIME_T_SPECIFIER "%ld"
#endif

/* Remember why we failed */
unsigned checkpt_failure = 0;

/*
 * Remember the file we were processing when we had incomplete events.
 * We remember this via it's dev and inode
 */
static dev_t checkpt_dev = (dev_t)NULL;
static ino_t checkpt_ino = (ino_t)NULL;

/* Remember the last event output */
static event last_event = {0, 0, 0, NULL, 0};

/* Loaded values from a given checkpoint file */
dev_t chkpt_input_dev = (dev_t)NULL;
ino_t chkpt_input_ino = (ino_t)NULL;
event chkpt_input_levent = {0, 0, 0, NULL, 0};

/*
 * Record the dev_t and ino_t of the given file
 *
 * Returns:
 * 1	Failed to get status
 * 0	OK
 */
int set_ChkPtFileDetails(const char *fn)
{
	struct stat sbuf;

	if (stat(fn, &sbuf) != 0) {
		fprintf(stderr, "Cannot stat audit file for checkpoint "
			"details - %s: %s\n", fn, strerror(errno));
		checkpt_failure |= CP_STATFAILED;
		return 1;
	}
	checkpt_dev = sbuf.st_dev;
	checkpt_ino = sbuf.st_ino;

	return 0;
}

/*
 * Save the given event in the last_event record
 * Returns:
 * 1	no memory
 * 0	OK
 */
int set_ChkPtLastEvent(const event *e)
{
	/* Set the event node if necessary */
	if (e->node) {
		if (last_event.node) {
			if (strcmp(e->node, last_event.node) != 0) {
				free((void *)last_event.node);
				last_event.node = strdup(e->node);
			}
		} else
			last_event.node = strdup(e->node);
		if (last_event.node == NULL) {
			fprintf(stderr, "No memory to allocate "
					"checkpoint last event node name\n");
			return 1;
		}
	} else {
		if (last_event.node)
			free((void *)last_event.node);
		last_event.node = NULL;
	}
	last_event.sec = e->sec;
	last_event.milli = e->milli;
	last_event.serial = e->serial;
	last_event.type = e->type;

	return 0;
}

/* Free all checkpoint memory */
void free_ChkPtMemory(void)
{
	if (last_event.node)
		(void)free((void *)last_event.node);
	last_event.node = NULL;
	if (chkpt_input_levent.node)
		(void)free((void *)chkpt_input_levent.node);
	chkpt_input_levent.node = NULL;
}

/*
 * Save the checkpoint to the given file
 * Returns:
 * 1	io error
 * 0	OK
 */
void save_ChkPt(const char *fn)
{
	FILE *fd;

	if ((fd = fopen(fn, "w")) == NULL) {
		fprintf(stderr, "Cannot open checkpoint file - %s: %s\n",
			fn, strerror(errno));
		checkpt_failure |= CP_STATUSIO;
		return;
	}
	// Write the inode in decimal to make ls -i easier to use.
	fprintf(fd, "dev=0x%" PRIX64 "\ninode=%" PRIu64 "\n",
		(uint64_t)checkpt_dev, (uint64_t)checkpt_ino);
	fprintf(fd, "output=%s %lld.%03u:%lu 0x%X\n",
		last_event.node ? last_event.node : "-",
		(long long int)last_event.sec, last_event.milli,
		last_event.serial, last_event.type);
	fclose(fd);
}

/*
 * Parse a checkpoint file "output=" record
 * Returns
 * 1	failed to parse or no memory
 * 0	parsed OK
 */
static int parse_checkpt_event(char *lbuf, int ndix, event *e)
{
	char *rest;

	/*
 	 * Find the space after the node, then make it '\0' so
 	 * we terminate the node value. We leave 'rest' at the start
 	 * of the event time/serial element
 	 */
	rest = strchr(&lbuf[ndix], ' ');
	if (rest == NULL) {
		fprintf(stderr, "Malformed output/event checkpoint line "
				"near node - [%s]\n", lbuf);
		checkpt_failure |= CP_STATUSBAD;
		return 1;
	}
	*rest++ = '\0';

	if (lbuf[ndix] == '-')
		e->node = NULL;
	else {
		e->node = strdup(&lbuf[ndix]);
		if (e->node == NULL) {
			fprintf(stderr, "No memory for node when loading "
					"checkpoint line - [%s]\n", lbuf);
			checkpt_failure |= CP_NOMEM;
			return 1;
		}
	}
	if (sscanf(rest, TIME_T_SPECIFIER ".%03u:%lu 0x%X", &e->sec, &e->milli,
						&e->serial, &e->type) != 4) {
		fprintf(stderr, "Malformed output/event checkpoint line "
			"after node - [%s]\n", lbuf);
		checkpt_failure |= CP_STATUSBAD;
		return 1;
	}

	return 0;
}

/*
 * Load the checkpoint from the given file
 * Returns:
 *  < -1	error
 * == -1	no file present
 * == 0		loaded data
 */
int load_ChkPt(const char *fn)
{
#define	MAX_LN	1023
	FILE *fd;
	char lbuf[MAX_LN];

	if ((fd = fopen(fn, "r")) == NULL) {
		if (errno == ENOENT)
			return -1;
		fprintf(stderr, "Cannot open checkpoint file - %s: %s\n",
			fn, strerror(errno));
		return -2;
	}
	chkpt_input_levent.node = NULL;
	while (fgets(lbuf, MAX_LN, fd) != NULL) {
		size_t len = strlen(lbuf);

		if (len && lbuf[len - 1] == '\n')	/* drop the newline */
			lbuf[len - 1] = '\0';

		if (strncmp(lbuf, "dev=", 4) == 0) {
			errno = 0;
			chkpt_input_dev = (dev_t)strtoull(&lbuf[4], NULL, 16);
			if (errno) {
				fprintf(stderr, "Malformed dev checkpoint "
						"line - [%s]\n", lbuf);
				checkpt_failure |= CP_STATUSBAD;
				break;
			}
		} else if (strncmp(lbuf, "inode=", 6) == 0) {
			errno = 0;
			chkpt_input_ino = (ino_t)strtoull(&lbuf[6], NULL, 0);
			if (errno) {
				fprintf(stderr, "Malformed inode checkpoint "
						"line - [%s]\n", lbuf);
				checkpt_failure |= CP_STATUSBAD;
				break;
			}
		} else if (strncmp(lbuf, "output=", 7) == 0) {
			free((void *)chkpt_input_levent.node);
			chkpt_input_levent.node = NULL;
			if (parse_checkpt_event(lbuf, 7, &chkpt_input_levent))
				break;
		} else {
			fprintf(stderr, "Unknown checkpoint line - [%s]\n",
				lbuf);
			checkpt_failure |= CP_STATUSBAD;
			break;
		}
	}
	if (	(chkpt_input_ino == (ino_t)NULL) ||
		(chkpt_input_dev == (dev_t)NULL) ) {
		fprintf(stderr, "Missing dev/inode lines from checkpoint "
				"file %s\n", fn);
		checkpt_failure |= CP_STATUSBAD;
	}
	fclose(fd);

	if (checkpt_failure)
		return -3;

#if	DBG
	{
		fprintf(stderr, "Loaded %s - dev: 0x%X, ino: 0x%X\n",
			fn, chkpt_input_dev, chkpt_input_ino);
		fprintf(stderr, "output:%s %lld.%03d:%lu 0x%X\n",
			chkpt_input_levent.node ? chkpt_input_levent.node : "-",
			(long long int)chkpt_input_levent.sec, chkpt_input_levent.milli,
			chkpt_input_levent.serial, chkpt_input_levent.type);
	}
#endif	/* DBG */
	return 0;
}

