#!mkcmd
# Since time stamp file format						(ksb)
# $Id: sstamp.m,v 1.16 2011/03/09 23:51:45 ksb Exp $
from '<sys/param.h>'
from '<sys/types.h>'
from '<fcntl.h>'
from '<unistd.h>'
from '<sys/stat.h>'
from '<sys/time.h>'
from '"machine.h"'

boolean 'n' {
	named "fUpdate"
	init "1"
	help "don't update target's length in the meta file"
}

require "util_errno.m" "util_ppm.m"

%c
static PPM_BUF since_PMLog;
%%
init 3 "util_ppm_init(& since_PMLog, 1, 0);"

%hi
#if !defined(SINCE_TS_VER)
#define SINCE_TS_VER	"2"	/* version of stamp file		*/

typedef struct SSnode {
	time_t wwhen;		/* last time we processed file		*/
	off_t wlength;		/* last length				*/
	ino_t wnode;		/* last inode number			*/
	dev_t wdevice;		/* last device number			*/
	short int ffound;	/* was a current stamp found? */
	short int istamp;	/* 0 == this lib, !0 == caller's	*/

	/* internal fields -- don't look, touch, fold, spindle, etc.
	 */
	short int ialloc;
	short fopen;
	int iFd;
	char *pcdb;
} SINCE_STAMP;

extern SINCE_STAMP *since_from_log(SINCE_STAMP *, char *, struct stat *);
extern int since_to_log(SINCE_STAMP *, char *);
#endif
%%

%c

/* since(1) time-stamp files have 3 line types:				(ksb)
 *	V%d\n
 *	%lx L%lx I%x D%x\n
 *	X____________________________________\n
 * the V line is the version marker
 * the %lx line is a valid (last we checked) time-stamp
 * the X_*\n line is free space in the file, if it is at the end
 *	we can truncate the file back to the last line
 *
 * We always flock(2) or lockf(2) the file to access it.
 */

/* aquire the log file with a lock on it				(ksb)
 */
static int
since_aq_log(char *pcName, int fcreate)
{
	register int fdRet;
	register int icreate;

	icreate = 0;
	if (fcreate)
		icreate = O_CREAT;

	/* LLL O_NONBLOCK should be a command line option */
#if USE_FLOCK
	if (-1 == (fdRet = open(pcName, O_RDWR|O_EXLOCK|icreate, 0666))) {
		return -1;
	}
#else
	if (-1 == (fdRet = open(pcName, O_RDWR|icreate, 0666))) {
		return -1;
	}
#if USE_LOCKF
	/* LLL F_TLOCK should be a command line option */
	if (-1 == lockf(fdRet, F_LOCK, 0)) {
		register int iKeep;

		iKeep = errno;
		close(fdRet);
		errno = iKeep;
		return -1;
	}
#endif
#endif /* lock type */
	return fdRet;
}

/* unlock the log file and close it					(ksb)
 */
static void
since_rl_log(int iFd)
{
	close(iFd);
}

/* build a few (1 or more) since structures				(ksb)
 * when you dispose of the first they all go away (calloc'd)
 */
SINCE_STAMP *
since_new(unsigned int iCount)
{
	register SINCE_STAMP *pSSRet;
	register unsigned int i;

	if ((SINCE_STAMP *)0 == (pSSRet = calloc(iCount, sizeof(SINCE_STAMP)))) {
		return (SINCE_STAMP *)0;
	}
	/* calloc zero'd the space, this is mostly redundant -- ksb */
	for (i = 0; i < iCount; ++i) {
		pSSRet[i].istamp = 0;
		pSSRet[i].ffound = 0;
		pSSRet[i].ialloc = 0;
		pSSRet[i].fopen = 0;
		pSSRet[i].iFd = -1;
		pSSRet[i].pcdb = (char *)0;
	}
	pSSRet[0].ialloc = 1;
	return pSSRet;
}

/* reclaim any memory we allocated for this since node			(ksb)
 */
void
since_dispose(SINCE_STAMP *pSSHold)
{
	if (0 != pSSHold->ialloc) {
		free((void *)pSSHold);
	}
}

/* Fill in the since struct for this file			(ksb,petef)
 * open the log (locked), find our record
 */
SINCE_STAMP *
since_from_log(SINCE_STAMP *pSS, char *pcLog, struct stat *pstThis)
{
	auto struct stat stLog;
	register char *pcRecord, *pcCursor, *pcNext;
	register int fIgnore;
	auto time_t wWhen;
	auto unsigned long wLength, wT1, wT2;
	auto ino_t wNode;
	auto dev_t wDevice;

	if ((SINCE_STAMP *)0 == pSS) {
		pSS = since_new(1);
		if ((SINCE_STAMP *)0 == pSS) {
			return (SINCE_STAMP *)0;
		}
	}
	if (-1 == (pSS->iFd = since_aq_log(pcLog, 0))) {
 bail:
		since_dispose(pSS);
		return (SINCE_STAMP *)0;
	}
	pSS->fopen = 1;

	/* find our file, or not in file
	 */
	if (-1 == fstat(pSS->iFd, & stLog)) {
		close(pSS->iFd);
		goto bail;
	}
	pSS->pcdb = util_ppm_size(& since_PMLog, (stLog.st_size|15)+1);
	if (stLog.st_size != read(pSS->iFd, pSS->pcdb, stLog.st_size)) {
		close(pSS->iFd);
		goto bail;
	}
	pSS->pcdb[stLog.st_size] = '\000';
	pSS->wnode = pstThis->st_ino;
	pSS->wdevice = pstThis->st_dev;
	pcRecord = "";
	for (pcCursor = pSS->pcdb; '\000' != *pcCursor; /* middle */) {
		pcRecord = pcCursor;
		pcNext = strchr(pcCursor, '\n');
		if ((char *)0 == pcNext)
			break;
		if ('V' != *pcCursor) {
			pcCursor = pcNext + 1;
			continue;
		}
		*pcNext = '\000';
		fIgnore = 0 != strcmp(SINCE_TS_VER, pcCursor+1);
		*pcNext++ = '\n';
		pcCursor = strchr(pcNext, '\n');
		++pcCursor;
		if (fIgnore)
			continue;
		if (4 != sscanf(pcNext, "%lx L%lx I%lx D%lx\n", &wWhen, &wLength, &wT1, &wT2))
			continue;
		pSS->wwhen = wWhen;
		wNode = (ino_t)wT1;
		wDevice = (dev_t)wT2;
		if (wNode != pSS->wnode || wDevice != pSS->wdevice)
			continue;
		pSS->ffound = 1;
		if (wLength > pstThis->st_size)
			wLength = 0;
		pSS->wlength = wLength;
		break;
	}

	/* if we're just looking (-n), no need to remove our sincedb
	 * entry -- just release the log
	 */
	if (! fUpdate) {
		since_rl_log(pSS->iFd);
		return pSS;
	}

	/* We have the entry. Delete it from our copy in memory.
	 * We'll put the others back in the file when we're done.
	 */
	if (pSS->ffound)
		memmove(pcRecord, pcCursor, strlen(pcCursor) + 1);
	return pSS;
}

/* Fill in the since file for this struct			(ksb,petef)
 * open the log (locked), add our record to the end
 */
int
since_to_log(SINCE_STAMP *pSS, char *pcLog)
{
	auto char acBuf[256];		/* Vver\n%x*4 + change\n */
	register int iFd;
	auto struct timeval tv;

	if (! fUpdate)
		return -1;
	if ((SINCE_STAMP *)0 == pSS)
		return ENOENT;
	if (pSS->fopen && 0 <= pSS->iFd) {
		iFd = pSS->iFd;
		lseek(iFd, 0, SEEK_SET);
	} else if (-1 == (iFd = since_aq_log(pcLog, 1))) {
		fprintf(stderr, "%s: %s: open: %s\n", progname, pcLog, strerror(errno));
		return errno;
	}
	ftruncate(iFd, 0);

	/* Write out our new record to the beginning of the file for easier
	 * searching in the future.
	 */
	if (pSS->istamp) {
		tv.tv_sec = pSS->wwhen;
	} else if (0 != gettimeofday(&tv, (struct timezone *)0)) {
		fprintf(stderr, "%s: gettimeofday: %s\n", progname, strerror(errno));
		return errno;
	}
	sprintf(acBuf, "V%s\n%lx L%lx I%lx D%lx\n", SINCE_TS_VER, (long)tv.tv_sec, (long)pSS->wlength, (long)pSS->wnode, (long)pSS->wdevice);
	write(iFd, acBuf, strlen(acBuf));

	/* Write out all of the saved records after it
	 */
	if (pSS->fopen)
		write(iFd, pSS->pcdb, strlen(pSS->pcdb));

	since_rl_log(iFd);
	return 0;
}
%%
