/* truncate.c - handle truncation of logfiles
 */

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define TRUNC_MESSAGE	"  \035CR(truncated)\035CA\n"


/* Truncate the log file opened for reading and writing on "fd" if it is
 * over "maxsize" bytes long; it will be truncated to at most "truncto"
 * bytes, and all discarded data will be written to "ofd", if it is not -1.
 *
 * The message "(truncated)" is prepended to the truncated file, in red.
 *
 * If "cm" is nonzero, the file is treated as a comment file - all data
 * before the first "Message:" line is retained, including the Message: line
 * itself.
 *
 * Returns nonzero on error. The error is not reported. Note that data is
 * copied to ofd before any change is made to the file on fd, so an error
 * will not result in data loss if ofd is being used; only possible
 * duplication, which proper use of datestamps should help.
 */
int log_truncate(int fd, int ofd, long maxsize, long truncto, int cm)
{
	struct stat sb;
	char buf[1024];
	long filesize, endstart, startat, premsg, todo, chunk, r, w;
	int foundmsg, line_start;
	char *a;

	if (fstat(fd, &sb))
		return (1);
	filesize = sb.st_size;		    /* get file size */

	if (filesize <= maxsize)
		return (0);		    /* not big enough; return OK */

	fsync(fd);

	endstart = (filesize - truncto) + strlen(TRUNC_MESSAGE);
	if (endstart > filesize)
		endstart = filesize;

	do {				    /* find first newline after endstart */
		if (lseek(fd, endstart, SEEK_SET) < 0)
			return (2);
		r = read(fd, buf, sizeof(buf) - 1);
		if (r < 0)
			return (3);
		a = memchr(buf, '\n', r);
		if (!a)
			endstart += r;
	} while ((!a) && (endstart < filesize));

	if (a) {
		*a = 0;
		endstart += strlen(buf) + 1;
	}

	if (lseek(fd, 0, SEEK_SET) < 0)
		return (4);		    /* look at first line */
	startat = 0;
	premsg = 0;

	if (cm) {			    /* commentfile - look for end of header */
		line_start = 1;
		foundmsg = 0;
		while (!foundmsg) {
			if (lseek(fd, startat, SEEK_SET) < 0)
				return (4);
			r = read(fd, buf, sizeof(buf) - 1);
			if (r <= 0)
				return (0); /* reached end of file! */
			buf[r] = 0;
			if (line_start) {
				if (!strncmp(buf, "Message:", 8))
					foundmsg = 1;
			}
			a = memchr(buf, '\n', r);
			if (a) {	    /* found a newline */
				*a = 0;
				startat += strlen(buf) + 1;
				premsg += strlen(buf) + 1;
				line_start = 1;
			} else {	    /* didn't find a newline */
				startat += r;
				premsg += r;
				line_start = 0;
			}
		}
	}

	if (lseek(fd, startat, SEEK_SET) < 0)
		return (4);

	r = read(fd, buf, strlen(TRUNC_MESSAGE));
	if (r < 0)
		return (5);
	buf[r] = 0;
	if (!strcmp(buf, TRUNC_MESSAGE))
		startat += strlen(buf);

	if (ofd >= 0) {			    /* copy doomed data to ofd first */
		if (lseek(fd, startat, SEEK_SET) < 0)
			return (6);
		todo = endstart - startat;
		while (todo > 0) {
			chunk = todo;
			if (chunk > sizeof(buf))
				chunk = sizeof(buf);
			r = read(fd, buf, chunk);
			if (r <= 0)
				return (7); /* file error - abort */
			todo -= r;
			a = buf;
			while (r > 0) {
				w = write(ofd, a, r);
				if (w <= 0)
					return (8);	/* file error - abort */
				r -= w;
				a += w;
			}
		}
		fsync(ofd);
	}

	todo = filesize - endstart;	    /* truncate the file */

	lseek(fd, premsg, SEEK_SET);
	write(fd, TRUNC_MESSAGE, strlen(TRUNC_MESSAGE));
	startat = premsg + strlen(TRUNC_MESSAGE);

	while (todo > 0) {
		if (lseek(fd, endstart, SEEK_SET) < 0)
			return (9);	    /* read from end */
		chunk = todo;
		if (chunk > sizeof(buf))
			chunk = sizeof(buf);
		r = read(fd, buf, chunk);
		if (r <= 0)
			return (10);	    /* read error */
		todo -= r;
		if (lseek(fd, startat, SEEK_SET) < 0)
			return (10);	    /* write to start */
		endstart += r;
		startat += r;
		a = buf;
		while (r > 0) {
			w = write(fd, a, r);
			if (w <= 0)
				return (11);	/* write error */
			r -= w;
			a += w;
		}
	}

	fsync(fd);

	return (ftruncate(fd, startat));    /* make file shorter */
}

/* EOF */
