/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*LINTLIBRARY*/
#ident	"@(#)srchcfile.c	1.13	96/04/05 SMI"	/* SVr4.0 1.7.1.1 */

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <pkglib.h>
#include <pkgstrct.h>
#include "pkglocale.h"

#define	ERROR(s) \
	{ \
		errstr = pkg_gt(s); \
		(void) getend(fpin); \
		return (-1); \
	}

static int	 eatwhite(FILE *fp);
static int	 getend(FILE *fp);
static int	 getnum(FILE *fp, int base, long *d, long bad);
static int	 getstr(FILE *fp, char *sep, int n, char *str);

static char	mypath[PATH_MAX];
static char	mylocal[PATH_MAX];

int
srchcfile(struct cfent *ept, char *path, FILE *fpin, FILE *fpout)
{
	struct pinfo *pinfo, *lastpinfo;
	long	pos;
	char	*pt,
		pkgname[PKGSIZ+1],
		classname[CLSSIZ+1];
	int	c, n, rdpath, anypath;

	/*
	 * this code uses goto's instead of nested subroutines because
	 * execution time of this routine is especially critical to
	 * installation
	 */

	errstr = NULL;
	ept->volno = 0;
	ept->ftype = BADFTYPE;
	(void) strcpy(ept->pkg_class, BADCLASS);
	ept->pkg_class_idx = -1;
	ept->path = NULL;
	ept->ainfo.local = NULL;
	ept->ainfo.mode = BADMODE;
	(void) strcpy(ept->ainfo.owner, BADOWNER);
	(void) strcpy(ept->ainfo.group, BADGROUP);
	ept->cinfo.size = ept->cinfo.cksum = ept->cinfo.modtime = BADCONT;
	ept->pinfo = NULL;
	ept->npkgs = 0;

	/*
	 * if path to search for is "*", then we will return the first path
	 * we encounter as a match, otherwise we return an error
	 */
	anypath = 0;
	if (path && (path[0] != '/')) {
		if (strcmp(path, "*") == 0)
			anypath++;
		else {
			errstr = "illegal search path specified";
			return (-1);
		}
	}

	rdpath = 0;
	for (;;) {
		if (feof(fpin))
			return (0); /* no more entries */

		/* save current position in file */
		pos = ftell(fpin);

		/* grab path from first entry */
		c = getc(fpin);
		if (c != '/') {
			/*
			 * we check for EOF inside this if statement to
			 * reduce normal execution time
			 */
			if (c == EOF)
				return (0); /* no more entries */
			else if (isspace(c) || (c == '#') || (c == ':')) {
				/* line is a comment */
				(void) getend(fpin);
				continue;
			}

			/*
			 * we need to read this entry in the format which
			 * specifies
			 *	ftype class path
			 * so we set the rdpath variable and immediately
			 * jump to the code which will parse this format.
			 * When done, that code will return to Path_Done below
			 */
			ungetc(c, fpin);
			rdpath = 1;
			break;
		}

		/* copy first token into path element of passed structure */
		pt = mypath;
		do {
			if (strchr("= \t\n", c))
				break;
			*pt++ = (char) c;
		} while ((c = getc(fpin)) != EOF);
		*pt = '\0';

		if (c == EOF)
			ERROR("incomplete entry")
		ept->path = mypath;

Path_Done:
		/*
		 * determine if we have read the pathname which identifies
		 * the entry we are searching for
		 */
		if (anypath)
			n = 0; /* any pathname will do */
		else if (path)
			n = strcmp(path, ept->path);
		else
			n = 1; /* no pathname will match */

		if (n == 0) {
			/*
			 * we want to return information about this path in
			 * the structure provided, so parse any local path
			 * and jump to code which parses rest of the input line
			 */
			if (c == '=') {
				/* parse local path specification */
				if (getstr(fpin, NULL, PATH_MAX, mylocal))
					ERROR("unable to read local/link path")
				ept->ainfo.local = mylocal;
			}
			break; /* scan into a structure */
		} else if (n < 0) {
			/*
			 * the entry we want would fit BEFORE the one we just
			 * read, so we need to unread what we've read by
			 * seeking back to the start of this entry
			 */
			if (fseek(fpin, pos, 0)) {
				errstr = pkg_gt("failure attempting fseek");
				return (-1);
			}
			return (2); /* path would insert here */
		}

		if (fpout) {
			/*
			 * copy what we've read and the rest of this line
			 * onto the specified output stream
			 */
			(void) fprintf(fpout, "%s%c", ept->path, c);
			if (rdpath) {
				(void) fprintf(fpout, "%c %s", ept->ftype,
					ept->pkg_class);
			}
			while ((c = getc(fpin)) != EOF) {
				putc(c, fpout);
				if (c == '\n')
					break;
			}
		} else {
			/*
			 * since this isn't the entry we want, just read the
			 * stream until we find the end of this entry and
			 * then start this search loop again
			 */
			while ((c = getc(fpin)) != EOF) {
				if (c == '\n')
					break;
			}
			if (c == EOF)
				ERROR("missing newline at end of entry")
		}
	}

	if (rdpath < 2) {
		/*
		 * since we are processing an oldstyle entry and we have
		 * already read ftype, class, and path we just jump into
		 * reading the other info
		 */

		switch (c = eatwhite(fpin)) {
		    case EOF:
			errstr = pkg_gt("incomplete entry");
			return (-1);

		    case '0':
		    case '1':
		    case '2':
		    case '3':
		    case '4':
		    case '5':
		    case '6':
		    case '7':
		    case '8':
		    case '9':
			ERROR("volume number not expected")

		    case 'i':
			ERROR("ftype <i> not expected")

		    case '?':
		    case 'f':
		    case 'v':
		    case 'e':
		    case 'l':
		    case 's':
		    case 'p':
		    case 'c':
		    case 'b':
		    case 'd':
		    case 'x':
			ept->ftype = (char) c;
			if (getstr(fpin, NULL, CLSSIZ, ept->pkg_class))
				ERROR("unable to read class token")
			if (!rdpath)
				break; /* we already read the pathname */

			if (getstr(fpin, "=", PATH_MAX, mypath))
				ERROR("unable to read pathname field")
			ept->path = mypath;

			c = getc(fpin);
			rdpath++;
			goto Path_Done;

		    default:
			errstr = pkg_gt("unknown ftype");
	Error:
			(void) getend(fpin);
			return (-1);
		}
	}

	if (strchr("sl", ept->ftype) && (ept->ainfo.local == NULL))
		ERROR("no link source specified");

	if (strchr("cb", ept->ftype)) {
#ifdef SUNOS41
		ept->ainfo.xmajor = BADMAJOR;
		ept->ainfo.xminor = BADMINOR;
		if (getnum(fpin, 10, (long *)&ept->ainfo.xmajor, BADMAJOR) ||
		    getnum(fpin, 10, (long *)&ept->ainfo.xminor, BADMINOR))
#else
		ept->ainfo.major = BADMAJOR;
		ept->ainfo.minor = BADMINOR;
		if (getnum(fpin, 10, (long *)&ept->ainfo.major, BADMAJOR) ||
		    getnum(fpin, 10, (long *)&ept->ainfo.minor, BADMINOR))
#endif
			ERROR("unable to read major/minor device numbers")
	}

	if (strchr("cbdxpfve", ept->ftype)) {
		/* mode, owner, group should be here */
		if (getnum(fpin, 8, (long *)&ept->ainfo.mode, BADMODE) ||
		    getstr(fpin, NULL, ATRSIZ, ept->ainfo.owner) ||
		    getstr(fpin, NULL, ATRSIZ, ept->ainfo.group))
			ERROR("unable to read mode/owner/group")
	}

	if (strchr("ifve", ept->ftype)) {
		/* look for content description */
		if (getnum(fpin, 10, (long *)&ept->cinfo.size, BADCONT) ||
		    getnum(fpin, 10, (long *)&ept->cinfo.cksum, BADCONT) ||
		    getnum(fpin, 10, (long *)&ept->cinfo.modtime, BADCONT))
			ERROR("unable to read content info")
	}

	if (ept->ftype == 'i') {
		if (getend(fpin)) {
			errstr = pkg_gt("extra tokens on input line");
			return (-1);
		}
		return (1);
	}

	/* determine list of packages which reference this entry */
	lastpinfo = (struct pinfo *)0;
	while ((c = getstr(fpin, ":\\", PKGSIZ, pkgname)) <= 0) {
		if (c < 0)
			ERROR("package name too long")
		else if (c == 0) {
			/* a package was listed */
			pinfo =
			    (struct pinfo *)calloc(1, sizeof (struct pinfo));
			if (!pinfo)
				ERROR("no memory for package information")
			if (!lastpinfo)
				ept->pinfo = pinfo; /* first one */
			else
				lastpinfo->next = pinfo; /* link list */
			lastpinfo = pinfo;

			if (strchr("-+*~!%", pkgname[0])) {
				pinfo->status = pkgname[0];
				(void) strcpy(pinfo->pkg, pkgname+1);
			} else
				(void) strcpy(pinfo->pkg, pkgname);

			/* pkg/[:[ftype][:class] */
			c = getc(fpin);
			if (c == '\\') {
				/* get alternate ftype */
				pinfo->editflag++;
				c = getc(fpin);
			}

			if (c == ':') {
				/* get special classname */
				(void) getstr(fpin, "", 12, classname);
				(void) strcpy(pinfo->aclass, classname);
				c = getc(fpin);
			}
			ept->npkgs++;

			if ((c == '\n') || (c == EOF))
				return (1);
			else if (!isspace(c))
				ERROR("bad end of entry")
		}
	}

	if (getend(fpin) && ept->pinfo) {
		errstr = pkg_gt("extra token(s) on input line");
		return (-1);
	}
	return (1);
}

static int
getnum(FILE *fp, int base, long *d, long bad)
{
	int c;

	/* leading white space ignored */
	c = eatwhite(fp);
	if (c == '?') {
		*d = bad;
		return (0);
	}

	if ((c == EOF) || (c == '\n') || !isdigit(c)) {
		(void) ungetc(c, fp);
		return (1);
	}

	*d = 0;
	while (isdigit(c)) {
		*d = (*d * base) + (c & 017);
		c = getc(fp);
	}
	(void) ungetc(c, fp);
	return (0);
}

static int
getstr(FILE *fp, char *sep, int n, char *str)
{
	int c;

	/* leading white space ignored */
	c = eatwhite(fp);
	if ((c == EOF) || (c == '\n')) {
		(void) ungetc(c, fp);
		return (1); /* nothing there */
	}

	/* fill up string until space, tab, or separator */
	while (!strchr(" \t", c) && (!sep || !strchr(sep, c))) {
		if (n-- < 1) {
			*str = '\0';
			return (-1); /* too long */
		}
		*str++ = (char) c;
		c = getc(fp);
		if ((c == EOF) || (c == '\n'))
			break; /* no more on this line */
	}
	*str = '\0';
	(void) ungetc(c, fp);
	return (0);
}

static int
getend(FILE *fp)
{
	int c;
	int n;

	n = 0;
	do {
		if ((c = getc(fp)) == EOF)
			return (n);
		if (!isspace(c))
			n++;
	} while (c != '\n');
	return (n);
}

static int
eatwhite(FILE *fp)
{
	int c;

	/* this test works around a side effect of getc() */
	if (feof(fp))
		return (EOF);
	do
		c = getc(fp);
	while ((c == ' ') || (c == '\t'));
	return (c);
}
