/* @(#)dumpdate.c	1.11 05/02/07 Copyright 2003-2005 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)dumpdate.c	1.11 05/02/07 Copyright 2003-2005 J. Schilling";
#endif
/*
 *	Copyright (c) 2003-2005 J. Schilling
 */
/*
 * Copyright Jörg Schilling. All rights reserved.
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only.
 * See the file CDDL.Schily.txt in this distribution or
 * http://opensource.org/licenses/cddl1.php for details.
 */

#include <mconfig.h>
#include <stdio.h>
#include <stdxlib.h>
#include <unixstd.h>
#include <strdefs.h>
#include <errno.h>
#include <utypes.h>
#include <standard.h>
#include <schily.h>

#include "dumpdate.h"
#include "starsubs.h"

/*#define	DEBUG*/

LOCAL	dumpd_t	*dumpdates;
LOCAL	dumpd_t	**dumptail = &dumpdates;

EXPORT	void	initdumpdates	__PR((char *fname));
EXPORT	void	writedumpdates	__PR((char *fname, int level, int dflags,
							struct timeval *date));
LOCAL	void	outentry	__PR((FILE *f, char *name, int level, int dflags,
							struct timeval *date));
EXPORT	char	*dumpdate	__PR((struct timeval *date));
LOCAL	char	*skipwht	__PR((char *p));
LOCAL	BOOL	getentry	__PR((char *line));
EXPORT	BOOL	getdumptime	__PR((char *p, struct timeval *tvp));
EXPORT	dumpd_t *checkdumpdates	__PR((const char *name, int level, int dflags));
LOCAL	dumpd_t *_checkdumpdates __PR((const char *name, int level, int dflags));
EXPORT	void	adddumpdates	__PR((const char *name, int level, int dflags,
							struct timeval *date,
								BOOL useold));
LOCAL	dumpd_t *newdumpdates	__PR((const char *name, int level, int dflags,
							struct timeval *date));

EXPORT void
initdumpdates(fname)
	char	*fname;
{
	FILE	*f;
	char	buf[4096];
	int	line = 0;

	f = fileopen(fname, "r");
	if (f == NULL) {
		if (geterrno() == ENOENT) {
			errmsg("Warning no %s.\n", fname);
			return;
		}
		comerr("Cannot open %s.\n", fname);
	}
	/*
	 * XXX Locking ???
	 */
	while (fgetline(f, buf, sizeof (buf)) >= 0) {
		line++;

		if (!getentry(buf)) {
			if (*skipwht(buf) != '\0') {
				errmsgno(EX_BAD,
					"Unknown format in '%s' line %d\n",
					fname, line);
			}
			continue;
		}
	}
	fclose(f);
}

EXPORT void
writedumpdates(fname, level, dflags, date)
	char		*fname;
	int		level;
	int		dflags;
	struct timeval	*date;
{
	FILE	*f;
	dumpd_t	*dp;
	off_t	fsize;
	off_t	fpos;

	f = fileopen(fname, "wc");
	if (f == NULL) {
		errmsg("Cannot open '%s'.\n", fname);
		return;
	}
	fsize = filesize(f);
	for (dp = dumpdates; dp; dp = dp->next) {
		outentry(f, dp->name, dp->level, dp->flags, &dp->date);
	}
	fflush(f);
	fpos = filepos(f);
	while (fpos++ < fsize)
		putc(' ', f);
	fclose(f);
	error("Dump record  level %d%s dump: %s written\n",
			level, (dflags & DD_PARTIAL) ? "P":" ",
			dumpdate(date));
}

LOCAL void
outentry(f, name, level, dflags, date)
	FILE		*f;
	char		*name;
	int		level;
	int		dflags;
	struct timeval	*date;
{
	int	len;
	time_t	t = date->tv_sec; /* FreeBSD/MacOS X -> broken tv_sec/time_t */

	len = strlen(name);
	if ((len % 8) == 0)
		len += 8;
	len += 7;
	len &= ~7;
	len = 41 -len;
	if (len < 1)
		len = 1;

	fprintf(f, "%s\t%*s%2d%s %10lld.%6.6lld %s",
		name,
		len,
		"",
		level,
		(dflags & DD_PARTIAL) ? "P":"",
		(Llong)date->tv_sec,
		(Llong)date->tv_usec,
		ctime(&t));
}

EXPORT char *
dumpdate(date)
	struct timeval	*date;
{
	char	*p;
	time_t	t = date->tv_sec; /* FreeBSD/MacOS X -> broken tv_sec/time_t */

	if (date->tv_sec == 0)
		return ("the epoch");
	p = ctime(&t);
	p[24] = '\0';
	return (p);
}

LOCAL char *
skipwht(p)
	char	*p;
{
	while (*p && (*p == ' ' || *p == '\t'))
		p++;

	return (p);
}

LOCAL BOOL
getentry(line)
	char	*line;
{
	char	*p;
	int	level;
	int	dflags = 0;
	struct timeval	tdate;

	p = line;
	do {
		p = strchr(++p, '\t');
	} while (p && p[0] != '\0' && p[1] != ' ');

	if (p == NULL || p[0] == '\0') {
		errmsgno(EX_BAD, "Error parsing mount point\n");
		return (FALSE);
	}
	*p++ = '\0';

	p = skipwht(p);
	p = astoi(p, &level);
	if (*p == 'P') {
		dflags |= DD_PARTIAL;
		p++;
	}
	if (*p != ' ') {
		errmsgno(EX_BAD, "Error parsing dump level\n");
		return (FALSE);
	}
	p = skipwht(p);
	if (!getdumptime(p, &tdate))
		return (FALSE);
	adddumpdates(line, level, dflags, &tdate, FALSE);

	return (TRUE);
}

EXPORT BOOL
getdumptime(p, tvp)
	char		*p;
	struct timeval	*tvp;
{
	Llong		date;
	Llong		dfrac;

	p = astollb(p, &date, 10);
	if (*p == '.') {
		int	l;
		char	*s = ++p;

		p = astollb(s, &dfrac, 10);
		l = p - s;
		if (l > 9) {		/* Number too big, limit to nsec */
			char	*p2 = p;

			l = s[9];
			s[9] = '\0';
			p = astollb(s, &dfrac, 10);
			s[9] = l;
			l = p - s;
			p = p2;
		}
		while (l < 6) {		/* Convert to usecs */
			dfrac  *= 10;
			l++;
		}
		while (l > 6) {		/* Convert to usecs */
			dfrac  /= 10;
			l--;
		}
	}
	if (*p != ' ' && *p != '\0') {
		errmsgno(EX_BAD, "Error parsing dump date\n");
		return (FALSE);
	}
	tvp->tv_sec = date;
	tvp->tv_usec = dfrac;
	return (TRUE);
}

EXPORT dumpd_t *
checkdumpdates(name, level, dflags)
	const char	*name;
	int		level;
	int		dflags;
{
	dumpd_t	*rp = NULL;
	dumpd_t	*rp2 = NULL;

	rp = _checkdumpdates(name, level, dflags & ~DD_CUMULATIVE);
	rp2 = _checkdumpdates(name, level, dflags);

	if (rp && rp2 && (rp2->date.tv_sec > rp->date.tv_sec))
		return (rp2);
	return (rp);
}

LOCAL dumpd_t *
_checkdumpdates(name, level, dflags)
	const char	*name;
	int		level;
	int		dflags;
{
	dumpd_t	*dp = dumpdates;
	dumpd_t	*rp = NULL;

	for (; dp; dp = dp->next) {
		if (!streql(name, dp->name))
			continue;
		if ((dp->flags & DD_PARTIAL) != (dflags & DD_PARTIAL))
			continue;
		if ((dflags & DD_CUMULATIVE) && level == dp->level) {
			rp = dp;
			break;
		}
		/*
		 * We are not interested in tardump entries for
		 * this level or higher, so skip them.
		 */
		if (level <= dp->level)
			continue;

		/*
		 * If we did already find a more recent entry in tardumps
		 * we consider this one outdated.
		 */
		if (rp && rp->date.tv_sec > dp->date.tv_sec)
			continue;
		rp = dp;
	}
#ifdef	DEBUG
	if (rp)
		outentry(stderr, rp->name, rp->level, rp->flags, &rp->date);
#endif
	return (rp);
}

EXPORT void
adddumpdates(name, level, dflags, date, useold)
	const char	*name;
	int		level;
	int		dflags;
	struct timeval	*date;
	BOOL		useold;
{
	dumpd_t	*dp = dumpdates;

	for (; dp; dp = dp->next) {
		if (streql(name, dp->name) && level == dp->level &&
		    (dp->flags & DD_PARTIAL) == (dflags & DD_PARTIAL)) {
			if (useold) {
				dp->date = *date;
				return;
			}
			errmsgno(EX_BAD,
				"Duplicate tardumps entry '%s %d%s %lld'.\n",
				dp->name, dp->level,
				(dflags & DD_PARTIAL) ? "P":"",
				(Llong)dp->date.tv_sec);

			if (date->tv_sec == dp->date.tv_sec)
				return;
			comerrno(EX_BAD, "Timestamps differs - aborting.\n");
		}
	}
	dp = newdumpdates(name, level, dflags, date);
	*dumptail = dp;
	dumptail = &dp->next;
}

LOCAL dumpd_t *
newdumpdates(name, level, dflags, date)
	const char	*name;
	int		level;
	int		dflags;
	struct timeval	*date;
{
	dumpd_t	*dp;

	dp	  = __malloc(sizeof (*dp), "tardumps entry");
	dp->next  = NULL;
	dp->name  = __savestr(name);
	dp->level = level;
	dp->flags = dflags;
	dp->date  = *date;

	return (dp);
}
