/* RPM - Copyright (C) 1995 Red Hat Software
 * 
 * spec.c - routines for parsing a spec file
 */

/*****************************
TODO:

. should be able to drop the -n in non-%package parts

******************************/

#include "config.h"
#include "miscfn.h"

#if HAVE_ALLOCA_H
# include <alloca.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <limits.h>
#include <ctype.h>

#include "header.h"
#include "spec.h"
#include "specP.h"
#include "messages.h"
#include "rpmlib.h"
#include "stringbuf.h"
#include "misc.h"
#include "reqprov.h"
#include "trigger.h"
#include "macro.h"

#define LINE_BUF_SIZE 1024
#define FREE(x) { if (x) free(x); }

static struct PackageRec *new_packagerec(void);
static int read_line(FILE *f, char *line);
static int match_arch(char *s);
static int match_os(char *s);
static void free_packagerec(struct PackageRec *p);
static void generateNames(Spec s);
static void reset_spec(void);
static int find_preamble_line(char *line, char **s);
static int check_part(char *line, char **s);
static int lookup_package(Spec s, struct PackageRec **pr,
			  char *name, int flags);
static void dumpPackage(struct PackageRec *p, FILE *f);

static int dateToTimet(const char * datestr, time_t * secs);
static void addChangelogEntry(Header h, int time, char *name, char *text);
static int addChangelog(Header h, StringBuf sb);

static int parseProvides(struct PackageRec *p, char *line, int tag);
static int parseRequiresConflicts(struct PackageRec *p, char *line,
				  int flag);
static void free_reqprov(struct ReqProv *p);
static int noSourcePatch(Spec s, char *line, int_32 tag);

static void addListEntry(Header h, int_32 tag, char *line);
static int finishCurrentPart(Spec spec, StringBuf sb,
			     struct PackageRec *cur_package,
			     int cur_part, char *triggerArgs,
			     char *scriptProg);

Spec parseSpecAux(FILE *f, char *specfile, char *buildRootOverride,
		  char ***buildArchs);

/**********************************************************************/
/*                                                                    */
/* Source and patch structure creation/deletion/lookup                */
/*                                                                    */
/**********************************************************************/

static int addSource(Spec spec, char *line)
{
    struct sources *p;
    char *s, *s1, c;
    char *file;
    unsigned long int x;
    char name[1024], expansion[1024];

    p = malloc(sizeof(struct sources));
    p->next = spec->sources;
    spec->sources = p;

    if (! strncasecmp(line, "source", 6)) {
	spec->numSources++;
	p->ispatch = 0;
	s = line + 6;
    } else if (! strncasecmp(line, "patch", 5)) {
	spec->numPatches++;
	p->ispatch = 1;
	s = line + 5;
    } else {
	rpmError(RPMERR_BADSPEC, "Not a source/patch line: %s\n", line);
	return(RPMERR_BADSPEC);
    }

    s += strspn(s, " \t\n");
    p->num = 0;
    if (*s != ':') {
        x = strspn(s, "0123456789");
	if (! x) {
	    rpmError(RPMERR_BADSPEC, "Bad source/patch line: %s\n", line);
	    return(RPMERR_BADSPEC);
	}
	c = s[x];
	s[x] = '\0';
	s1 = NULL;
	p->num = strtoul(s, &s1, 10);
	if ((*s1) || (s1 == s) || (p->num == ULONG_MAX)) {
	    s[x] = c;
	    rpmError(RPMERR_BADSPEC, "Bad source/patch number: %s\n", s);
	    return(RPMERR_BADSPEC);
	}
	s[x] = c;
	s += x;
	/* skip spaces */
	s += strspn(s, " \t\n");
    }

    if (*s != ':') {
	rpmError(RPMERR_BADSPEC, "Bad source/patch line: %s\n", line);
	return(RPMERR_BADSPEC);
    }
    
    /* skip to actual source */
    s++;
    s += strspn(s, " \t\n");

    file = strtok(s, " \t\n");
    if (! file) {
	rpmError(RPMERR_BADSPEC, "Bad source/patch line: %s\n", line);
	return(RPMERR_BADSPEC);
    }
    p->fullSource = strdup(file);
    p->source = strrchr(p->fullSource, '/');
    if (p->source) {
	p->source++;
    } else {
	p->source = p->fullSource;
    }

    sprintf(expansion, "%s/%s", rpmGetVar(RPMVAR_SOURCEDIR), p->source);
    sprintf(name, "%s%d", (p->ispatch) ? "PATCH" : "SOURCE", p->num);
    addMacro(name, expansion);
    sprintf(name, "%sURL%d", (p->ispatch) ? "PATCH" : "SOURCE", p->num);
    addMacro(name, p->fullSource);
    
    if (p->ispatch) {
	rpmMessage(RPMMESS_DEBUG, "Patch(%d) = %s\n", p->num, p->fullSource);
    } else {
	rpmMessage(RPMMESS_DEBUG, "Source(%d) = %s\n", p->num, p->fullSource);
    }
    
    return 0;
}

static void freeSources(Spec s)
{
    struct sources *p1, *p2;

    p1 = s->sources;
    while (p1) {
	p2 = p1;
	p1 = p1->next;
	free(p2->fullSource);
	free(p2);
    }
}

char *getSource(Spec s, int ispatch, int num)
{
    struct sources *p = s->sources;

    while (p) {
	if ((ispatch == p->ispatch) &&
	    (num == p->num)) {
	    break;
	} else {
	    p = p->next;
	}
    }

    if (p) {
	return(p->source);
    } else {
	return(NULL);
    }
}

char *getFullSource(Spec s, int ispatch, int num)
{
    struct sources *p = s->sources;

    while (p) {
	if ((ispatch == p->ispatch) &&
	    (num == p->num)) {
	    break;
	} else {
	    p = p->next;
	}
    }

    if (p) {
	return(p->fullSource);
    } else {
	return(NULL);
    }
}

int noSourcePatch(Spec s, char *line, int_32 tag)
{
    int_32 array[1024];  /* XXX - max 1024 sources or patches */
    int_32 num;
    int count;
    char *t, *te;

    if (((tag == RPMTAG_NOSOURCE) && s->numNoSource) ||
	((tag == RPMTAG_NOPATCH) && s->numNoPatch)) {
	rpmError(RPMERR_BADSPEC, "Only one nosource/nopatch line allowed\n");
	return(RPMERR_BADSPEC);
    }
    
    count = 0;
    while ((t = strtok(line, ", \t"))) {
	num = strtoul(t, &te, 10);
	if ((*te) || (te == t) || (num == ULONG_MAX)) {
	    rpmError(RPMERR_BADSPEC, "Bad source/patch number: %s\n", t);
	    return(RPMERR_BADSPEC);
	}
	array[count++] = num;
	rpmMessage(RPMMESS_DEBUG, "Skipping source/patch number: %d\n", num);
	line = NULL;
    }

    if (count) {
	if (tag == RPMTAG_NOSOURCE) {
	    s->numNoSource = count;
	    s->noSource = malloc(sizeof(int_32) * count);
	    memcpy(s->noSource, array, sizeof(int_32) * count);
	} else {
	    s->numNoPatch = count;
	    s->noPatch = malloc(sizeof(int_32) * count);
	    memcpy(s->noPatch, array, sizeof(int_32) * count);
	}
    }

    return 0;
}

/**********************************************************************/
/*                                                                    */
/* Provide/Require handling                                           */
/*                                                                    */
/**********************************************************************/

static void free_reqprov(struct ReqProv *p)
{
    struct ReqProv *s;
    
    while (p) {
	s = p;
	p = p->next;
	FREE(s->name);
	FREE(s->version);
	free(s);
    }
}

struct ReqComp ReqComparisons[] = {
    { "<=", RPMSENSE_LESS | RPMSENSE_EQUAL},
    { "<=S", RPMSENSE_LESS | RPMSENSE_EQUAL | RPMSENSE_SERIAL},
    { "=<", RPMSENSE_LESS | RPMSENSE_EQUAL},
    { "=<S", RPMSENSE_LESS | RPMSENSE_EQUAL | RPMSENSE_SERIAL},
    { "<", RPMSENSE_LESS},
    { "<S", RPMSENSE_LESS | RPMSENSE_SERIAL},

    { "=", RPMSENSE_EQUAL},
    { "=S", RPMSENSE_EQUAL | RPMSENSE_SERIAL},
    
    { ">=", RPMSENSE_GREATER | RPMSENSE_EQUAL},
    { ">=S", RPMSENSE_GREATER | RPMSENSE_EQUAL | RPMSENSE_SERIAL},
    { "=>", RPMSENSE_GREATER | RPMSENSE_EQUAL},
    { "=>S", RPMSENSE_GREATER | RPMSENSE_EQUAL | RPMSENSE_SERIAL},
    { ">", RPMSENSE_GREATER},
    { ">S", RPMSENSE_GREATER | RPMSENSE_SERIAL},
    { NULL, 0 },
};

static int parseRequiresConflicts(struct PackageRec *p, char *line,
				  int flag)
{
    char *req = NULL;
    char *version = NULL;
    int flags;
    struct ReqComp *rc;

    while (req || (req = strtok(line, " ,\t\n"))) {
	switch (flag) {
	  case RPMTAG_CONFLICTFLAGS:
	    flags = RPMSENSE_CONFLICTS;
	    break;
	  case RPMTAG_PREREQ:
	    flags = RPMSENSE_PREREQ;
	    break;
	  default:
	    flags = RPMSENSE_ANY;
	    break;
	}
	if (flag == RPMTAG_CONFLICTFLAGS && req[0] == '/') {
	    rpmError(RPMERR_BADSPEC,
		     "No file names in Conflicts: %s", req);
	    return RPMERR_BADSPEC;
	}
	if ((version = strtok(NULL, " ,\t\n"))) {
	    rc = ReqComparisons;
	    while (rc->token && strcmp(version, rc->token)) {
		rc++;
	    }
	    if (rc->token) {
		if (req[0] == '/') {
		    rpmError(RPMERR_BADSPEC,
			     "No versions on file names in Requires: %s", req);
		    return RPMERR_BADSPEC;
		}
		if (flag == RPMTAG_PREREQ) {
		    rpmError(RPMERR_BADSPEC,
			     "No versions in PreReq: %s", req);
		    return RPMERR_BADSPEC;
		}
		/* read a version */
		flags |= rc->flags;
		version = strtok(NULL, " ,\t\n");
	    }
	}
	if ((flags & RPMSENSE_SENSEMASK) && !version) {
	    rpmError(RPMERR_BADSPEC, "Version required in require/conflict");
	    return RPMERR_BADSPEC;
	}

	addReqProv(p, flags, req,
		   (flags & RPMSENSE_SENSEMASK) ? version : NULL);

	req = NULL;
	if (! (flags & RPMSENSE_SENSEMASK)) {
	    /* No version -- we just read a name */
	    req = version;
	}
	line = NULL;
    }
    
    return 0;
}

static int parseProvides(struct PackageRec *p, char *line, int tag)
{
    char *prov;
    int flags;

    flags = (tag == RPMTAG_PROVIDES) ? RPMSENSE_PROVIDES : RPMSENSE_OBSOLETES;
    
    while ((prov = strtok(line, " ,\t\n"))) {
	if (prov[0] == '/') {
	    rpmError(RPMERR_BADSPEC,
		     "No file names in %s: %s",
		     (tag == RPMTAG_PROVIDES) ? "provides" : "obsoletes",
		     prov);
	    return RPMERR_BADSPEC;
	}
	addReqProv(p, flags, prov, NULL);
	line = NULL;
    }
    return 0;
}

/**********************************************************************/
/*                                                                    */
/* Spec and package structure creation/deletion/lookup                */
/*                                                                    */
/**********************************************************************/

static struct PackageRec *new_packagerec(void)
{
    struct PackageRec *p = malloc(sizeof(struct PackageRec));

    p->subname = NULL;
    p->newname = NULL;
    p->icon = NULL;
    p->header = headerNew();
    p->filelist = newStringBuf();
    p->files = -1;  /* -1 means no %files, thus no package */
    p->fileFile = NULL;
    p->doc = newStringBuf();
    p->reqprov = NULL;
    p->numReq = 0;
    p->numProv = 0;
    p->numConflict = 0;
    p->numPreReq = 0;
    p->numObsoletes = 0;
    p->trigger.alloced = 0;
    p->trigger.used = 0;
    p->trigger.triggerScripts = NULL;
    p->trigger.trigger = NULL;
    p->trigger.triggerCount = 0;
    p->next = NULL;

    return p;
}

void free_packagerec(struct PackageRec *p)
{
    if (! p ) return;
    
    headerFree(p->header);
    freeStringBuf(p->filelist);
    freeStringBuf(p->doc);
    FREE(p->subname);
    FREE(p->newname);
    FREE(p->icon);
    FREE(p->fileFile);
    free_reqprov(p->reqprov);
    freeTriggers(p->trigger);
    if (p->next) {
        free_packagerec(p->next);
    }
    free(p);
}

void freeSpec(Spec s)
{
    FREE(s->name);
    FREE(s->specfile);
    FREE(s->noSource);
    FREE(s->noPatch);
    FREE(s->buildroot);
    FREE(s->buildArch);
    freeSources(s);
    freeStringBuf(s->prep);
    freeStringBuf(s->build);
    freeStringBuf(s->install);
    freeStringBuf(s->doc);
    freeStringBuf(s->clean);
    free_packagerec(s->packages);
    free(s);
}

#define LP_CREATE           1
#define LP_FAIL_EXISTS     (1 << 1)
#define LP_SUBNAME         (1 << 2)
#define LP_NEWNAME         (1 << 3)

int lookup_package(Spec s, struct PackageRec **pr, char *name, int flags)
{
    struct PackageRec *package;
    struct PackageRec **ppp;

    package = s->packages;
    while (package) {
	if (flags & LP_SUBNAME) {
	    if (! package->subname) {
	        package = package->next;
	        continue;
	    }
	    if (! strcmp(package->subname, name)) {
		break;
	    }
	} else if (flags & LP_NEWNAME) {
	    if (! package->newname) {
	        package = package->next;
	        continue;
	    }
	    if (! strcmp(package->newname, name)) {
		break;
	    }
	} else {
	    /* Base package */
	    if ((! package->newname) && (! package->subname)) {
		break;
	    }
	}
	package = package->next;
    }
    
    if (package && (flags & LP_FAIL_EXISTS)) {
	return 0;
    }

    if (package) {
	*pr = package;
	return 1;
    }

    /* At this point the package does not exist */

    if (! (flags & LP_CREATE)) {
	return 0;
    }

    /* Create it */
    package = new_packagerec();
    if (name) {
	if (flags & LP_SUBNAME) {
	    package->subname = strdup(name);
	} else if (flags & LP_NEWNAME) {
	    package->newname = strdup(name);
	}
    }

    /* Link it in to the spec */
    ppp = &(s->packages);
    while (*ppp) {
	ppp = &((*ppp)->next);
    }
    *ppp = package;

    *pr = package;
    return 1;
}

static void generateNames(Spec s)
{
    struct PackageRec *package;
    char buf[1024];
    char *name;

    package = s->packages;
    while (package) {
	if (package->subname) {
	    sprintf(buf, "%s-%s", s->name, package->subname);
	    name = buf;
	} else if (package->newname) {
	    name = package->newname;
	} else {
	    /* Must be the main package */
	    name = s->name;
	}
	headerAddEntry(package->header, RPMTAG_NAME, RPM_STRING_TYPE, name, 1);
	
	package = package->next;
    }
}

/* datestr is of the form 'Wed Jan 1 1997' */
static int dateToTimet(const char * datestr, time_t * secs)
{
    struct tm time;
    char * chptr, * end, ** idx;
    char * date = strcpy(alloca(strlen(datestr) + 1), datestr);
    static char * days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 
				NULL };
    static char * months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
			     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
    static char lengths[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    
    memset(&time, 0, sizeof(time));

    end = chptr = date;

    /* day of week */
    if ((chptr = strtok(date, " \t\n")) == NULL) return -1;
    idx = days;
    while (*idx && strcmp(*idx, chptr)) idx++;
    if (!*idx) return -1;

    /* month */
    if ((chptr = strtok(NULL, " \t\n")) == NULL) return -1;
    idx = months;
    while (*idx && strcmp(*idx, chptr)) idx++;
    if (!*idx) return -1;

    time.tm_mon = idx - months;

    /* day */
    if ((chptr = strtok(NULL, " \t\n")) == NULL) return -1;

    /* make this noon so the day is always right (as we make this UTC) */
    time.tm_hour = 12;

    time.tm_mday = strtol(chptr, &chptr, 10);
    if (*chptr) return -1;
    if (time.tm_mday < 0 || time.tm_mday > lengths[time.tm_mon]) return -1;

    /* year */
    if ((chptr = strtok(NULL, " \t\n")) == NULL) return -1;

    time.tm_year = strtol(chptr, &chptr, 10);
    if (*chptr) return -1;
    if (time.tm_year < 1997 || time.tm_year >= 3000) return -1;
    time.tm_year -= 1900;

    *secs = mktime(&time);
    if (*secs == -1) return -1;

    /* adjust to GMT */
    *secs += timezone;

    return 0;
}

static void addChangelogEntry(Header h, int time, char *name, char *text)
{
    if (headerIsEntry(h, RPMTAG_CHANGELOGTIME)) {
	headerAppendEntry(h, RPMTAG_CHANGELOGTIME, RPM_INT32_TYPE,
			  &time, 1);
	headerAppendEntry(h, RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY_TYPE,
			  &name, 1);
	headerAppendEntry(h, RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY_TYPE,
			 &text, 1);
    } else {
	headerAddEntry(h, RPMTAG_CHANGELOGTIME, RPM_INT32_TYPE,
		       &time, 1);
	headerAddEntry(h, RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY_TYPE,
		       &name, 1);
	headerAddEntry(h, RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY_TYPE,
		       &text, 1);
    }
}

static int addChangelog(Header h, StringBuf sb)
{
    char *s;
    int i;
    int time, lastTime = 0;
    char *date, *name, *text, *next;

    s = getStringBuf(sb);

    /* skip space */
    while (*s && isspace(*s)) s++;

    while (*s) {
	if (*s != '*') {
	    rpmError(RPMERR_BADSPEC, "%%changelog entries must start with *");
	    return RPMERR_BADSPEC;
	}

	/* find end of line */
	date = s;
	while (*s && *s != '\n') s++;
	if (! *s) {
	    rpmError(RPMERR_BADSPEC, "incomplete %%changelog entry");
	    return RPMERR_BADSPEC;
	}
	*s = '\0';
	text = s + 1;
	
	/* 4 fields of date */
	date++;
	s = date;
	for (i = 0; i < 4; i++) {
	    while (*s && isspace(*s)) s++;
	    while (*s && !isspace(*s)) s++;
	}
	while (isspace(*date)) date++;
	if (dateToTimet(date, (time_t *)&time)) {
	    rpmError(RPMERR_BADSPEC, "bad date in %%changelog: %s", date);
	    return RPMERR_BADSPEC;
	}
	if (lastTime && lastTime < time) {
	    rpmError(RPMERR_BADSPEC,
		     "%%changelog not in decending chronological order");
	    return RPMERR_BADSPEC;
	}
	lastTime = time;

	/* skip space to the name */
	while (*s && isspace(*s)) s++;
	if (! *s) {
	    rpmError(RPMERR_BADSPEC, "missing name in %%changelog");
	    return RPMERR_BADSPEC;
	}

	/* name */
	name = s;
	while (*s) s++;
	while (s > name && isspace(*s)) {
	    *s-- = '\0';
	}
	if (s == name) {
	    rpmError(RPMERR_BADSPEC, "missing name in %%changelog");
	    return RPMERR_BADSPEC;
	}

	/* text */
	while (*text && isspace(*text)) text++;
	if (! *text) {
	    rpmError(RPMERR_BADSPEC, "no description in %%changelog");
	    return RPMERR_BADSPEC;
	}
	    
	/* find the next leading '*' (or eos) */
	s = text;
	do {
	   s++;
	} while (*s && (*(s-1) != '\n' || *s != '*'));
	next = s;
	s--;

	/* backup to end of description */
	while ((s > text) && isspace(*s)) {
	    *s-- = '\0';
	}
	
	addChangelogEntry(h, time, name, text);
	s = next;
    }

    return 0;
}

/**********************************************************************/
/*                                                                    */
/* Line reading                                                       */
/*                                                                    */
/**********************************************************************/

static int match_arch(char *s)
{
    char *tok, *arch;
    int sense, match;

    rpmGetArchInfo(&arch, NULL);
    match = 0;
    
    tok = strtok(s, " \n\t");
    sense = (! strcmp(tok, "%ifarch")) ? 1 : 0;

    while ((tok = strtok(NULL, " \n\t"))) {
	if (! strcmp(tok, arch)) {
	    match |= 1;
	}
    }

    return (sense == match);
}

static int match_os(char *s)
{
    char *tok, *os;
    int sense, match;

    rpmGetOsInfo(&os, NULL);
    match = 0;
    
    tok = strtok(s, " \n\t");
    sense = (! strcmp(tok, "%ifos")) ? 1 : 0;

    while ((tok = strtok(NULL, " \n\t"))) {
	if (! strcmp(tok, os)) {
	    match |= 1;
	}
    }

    return (sense == match);
}

static struct read_level_entry {
    int reading;  /* 1 if we are reading at this level */
    struct read_level_entry *next;
} *read_level = NULL;

static int read_line(FILE *f, char *line)
{
    static struct read_level_entry *rl;
    int gotline;
    char *r;
    char *firstChar;

    do {
        gotline = 0;
	if (! fgets(line, LINE_BUF_SIZE, f)) {
	    /* the end */
	    if (read_level->next) {
		rpmError(RPMERR_UNMATCHEDIF, "Unclosed %%if");
	        return RPMERR_UNMATCHEDIF;
	    } else {
	        return 0;
	    }
	}
	firstChar = line;
	while (*firstChar && isspace(*firstChar)) {
	    firstChar++;
	}
	if ((! strncmp("%ifarch", firstChar, 7)) ||
	    (! strncmp("%ifnarch", firstChar, 8))) {
	    expandMacros(line);
	    rl = malloc(sizeof(struct read_level_entry));
	    rl->next = read_level;
	    rl->reading = read_level->reading && match_arch(line);
	    read_level = rl;
	} else if ((! strncmp("%ifos", firstChar, 5)) ||
		   (! strncmp("%ifnos", firstChar, 6))) {
	    expandMacros(line);
	    rl = malloc(sizeof(struct read_level_entry));
	    rl->next = read_level;
	    rl->reading = read_level->reading && match_os(line);
	    read_level = rl;
	} else if (! strncmp("%else", firstChar, 5)) {
	    expandMacros(line);
	    if (! read_level->next) {
	        /* Got an else with no %if ! */
		rpmError(RPMERR_UNMATCHEDIF, "Got a %%else with no if");
	        return RPMERR_UNMATCHEDIF;
	    }
	    read_level->reading =
	        read_level->next->reading && ! read_level->reading;
	} else if (! strncmp("%endif", firstChar, 6)) {
	    expandMacros(line);
	    if (! read_level->next) {
		rpmError(RPMERR_UNMATCHEDIF, "Got a %%endif with no if");
		return RPMERR_UNMATCHEDIF;
	    }
	    rl = read_level;
	    read_level = rl->next;
	    free(rl);
	} else {
	    if (read_level->reading) {
		expandMacros(line);
	    }
            gotline = 1;
        }
    } while (! (gotline && read_level->reading));
    
    r = line + (strlen(line)) - 1;
    while (isspace(*r)) {
        *(r--) = '\0';
    }
    return 1;
}

/**********************************************************************/
/*                                                                    */
/* Line parsing                                                       */
/*                                                                    */
/**********************************************************************/

struct preamble_line {
    int tag;
    int len;
    char *token;
} preamble_spec[] = {
    {RPMTAG_NAME,          0, "name"},
    {RPMTAG_VERSION,       0, "version"},
    {RPMTAG_RELEASE,       0, "release"},
    {RPMTAG_SERIAL,        0, "serial"},
    {RPMTAG_DESCRIPTION,   0, "description"},
    {RPMTAG_SUMMARY,       0, "summary"},
    {RPMTAG_COPYRIGHT,     0, "copyright"},
    {RPMTAG_DISTRIBUTION,  0, "distribution"},
    {RPMTAG_VENDOR,        0, "vendor"},
    {RPMTAG_GROUP,         0, "group"},
    {RPMTAG_PACKAGER,      0, "packager"},
    {RPMTAG_URL,           0, "url"},
    {RPMTAG_ROOT,          0, "root"},
    {RPMTAG_SOURCE,        0, "source"},
    {RPMTAG_PATCH,         0, "patch"},
    {RPMTAG_NOSOURCE,      0, "nosource"},
    {RPMTAG_NOPATCH,       0, "nopatch"},
    {RPMTAG_EXCLUDEARCH,   0, "excludearch"},
    {RPMTAG_EXCLUSIVEARCH, 0, "exclusivearch"},
    {RPMTAG_EXCLUDEOS,     0, "excludeos"},
    {RPMTAG_EXCLUSIVEOS,   0, "exclusiveos"},
    {RPMTAG_EXCLUDE,       0, "exclude"},
    {RPMTAG_EXCLUSIVE,     0, "exclusive"},
    {RPMTAG_ICON,          0, "icon"},
    {RPMTAG_PROVIDES,      0, "provides"},
    {RPMTAG_REQUIREFLAGS,  0, "requires"},
    {RPMTAG_PREREQ,        0, "prereq"},
    {RPMTAG_CONFLICTFLAGS, 0, "conflicts"},
    {RPMTAG_OBSOLETES,     0, "obsoletes"},
    {RPMTAG_DEFAULTPREFIX, 0, "prefix"},
    {RPMTAG_BUILDROOT,     0, "buildroot"},
    {RPMTAG_BUILDARCHS,    0, "buildarchitectures"},
    {RPMTAG_AUTOREQPROV,   0, "autoreqprov"},
    {0, 0, 0}
};

static int find_preamble_line(char *line, char **s)
{
    struct preamble_line *p = preamble_spec;

    while (p->token && strncasecmp(line, p->token, p->len)) {
        p++;
    }
    if (!p->token) return 0;
    *s = line + p->len;

    /* Unless this is a source or a patch, a ':' better be next */
    if (p->tag != RPMTAG_SOURCE && p->tag != RPMTAG_PATCH) {
	*s += strspn(*s, " \t");
	if (**s != ':') {
	    return 0;
	}
    }
    
    *s += strspn(*s, ": \t");
    return p->tag;
}

/* None of these can be 0 !! */
#define PREAMBLE_PART       1
#define PREP_PART           2
#define BUILD_PART          3
#define INSTALL_PART        4
#define CLEAN_PART          5
#define PREIN_PART          6
#define POSTIN_PART         7
#define PREUN_PART          8
#define POSTUN_PART         9
#define FILES_PART         10
#define CHANGELOG_PART     11
#define DESCRIPTION_PART   12
#define TRIGGERIN_PART     13
#define TRIGGERUN_PART     14
#define VERIFYSCRIPT_PART  15

static struct part_rec {
    int part;
    int len;
    char *s;
} part_list[] = {
    {PREAMBLE_PART,    0, "%package"},
    {PREP_PART,        0, "%prep"},
    {BUILD_PART,       0, "%build"},
    {INSTALL_PART,     0, "%install"},
    {CLEAN_PART,       0, "%clean"},
    {PREUN_PART,       0, "%preun"},
    {POSTUN_PART,      0, "%postun"},
    {PREIN_PART,       0, "%pre"},
    {POSTIN_PART,      0, "%post"},
    {FILES_PART,       0, "%files"},
    {CHANGELOG_PART,   0, "%changelog"},
    {DESCRIPTION_PART, 0, "%description"},
    {TRIGGERUN_PART,   0, "%triggerun"},
    {TRIGGERIN_PART,   0, "%trigger"},
    {VERIFYSCRIPT_PART, 0, "%verifyscript"},
    {0, 0, 0}
};

static int check_part(char *line, char **s)
{
    struct part_rec *p = part_list;

    while (p->s && strncmp(line, p->s, p->len)) {
        p++;
    }
    if (!p) return 0;
    *s = line + p->len;
    *s += strspn(*s, " \t");
    if (**s == '\0') {
        *s = NULL;
    }
    return p->part;
}

#if 0
static char *chop_line(char *s)
{
    char *p, *e;

    p = s;
    p += strspn(s, " \t");
    if (*p == '\0') {
	return NULL;
    }
    e = s + strlen(s) - 1;
    while (index(" \t", *e)) {
	e--;
    }
    return p;
}
#endif

static void addListEntry(Header h, int_32 tag, char *line)
{
    int argc;
    char **argv;
    char **argvs;
    char *s;

    argvs = argv = malloc(strlen(line) * sizeof(char *));
    argc = 0;
    while ((s = strtok(line, " \t"))) {
	*argv = s;
	argc++;
	argv++;
	line = NULL;
    }
    if (argc) {
	headerAddEntry(h, tag, RPM_STRING_ARRAY_TYPE, argvs, argc);
    }
    free(argvs);
}

static int finishCurrentPart(Spec spec, StringBuf sb,
			     struct PackageRec *cur_package,
			     int cur_part, char *triggerArgs,
			     char *scriptProg)
{
    int t1 = 0;
    int t2 = 0;

    stripTrailingBlanksStringBuf(sb);

    switch (cur_part) {
      case PREIN_PART:
	t1 = RPMTAG_PREIN;
	t2 = RPMTAG_PREINPROG;
	break;
      case POSTIN_PART:
	t1 = RPMTAG_POSTIN;
	t2 = RPMTAG_POSTINPROG;
	break;
      case PREUN_PART:
	t1 = RPMTAG_PREUN;
	t2 = RPMTAG_PREUNPROG;
	break; 
      case POSTUN_PART:
	t1 = RPMTAG_POSTUN;
	t2 = RPMTAG_POSTUNPROG;
	break;
      case VERIFYSCRIPT_PART:
	t1 = RPMTAG_VERIFYSCRIPT;
	break;
      case DESCRIPTION_PART:
	t1 = RPMTAG_DESCRIPTION;
	break;
      case CHANGELOG_PART:
	/* %changelog is a little special.  It goes in the   */
	/* "main" package no matter where it appears, and it */
	/* ends up in all the packages.                      */
	if (addChangelog(spec->packages->header, sb)) {
	    return 1;
	}
	break;
      case TRIGGERIN_PART:
	if (addTrigger(cur_package, RPMSENSE_TRIGGER_IN,
		       getStringBuf(sb), triggerArgs)) {
	    return 1;
	}
	break;
      case TRIGGERUN_PART:
	if (addTrigger(cur_package, RPMSENSE_TRIGGER_UN,
		       getStringBuf(sb), triggerArgs)) {
	    return 1;
	}
	break;
    }
    if (t1 && (*(getStringBuf(sb)) != '\0')) {
	headerAddEntry(cur_package->header, t1,
		       RPM_STRING_TYPE, getStringBuf(sb), 1);
    }
    if (t2) {
	addReqProv(cur_package, RPMSENSE_PREREQ, scriptProg, NULL);
	headerAddEntry(cur_package->header, t2,
		       RPM_STRING_TYPE, scriptProg, 1);
    }
    return 0;
}

/**********************************************************************/
/*                                                                    */
/* Main specfile parsing routine                                      */
/*                                                                    */
/**********************************************************************/

Spec *parseSpec(FILE *f, char *specfile, char *buildRootOverride)
{
    Spec *res;
    Spec s;
    char **archs = NULL;
    char **arch;
    int i, count;
    
    s = parseSpecAux(f, specfile, buildRootOverride, &archs);

    if (s) {
	/* No BuildArchitectures field */
	res = (Spec *) malloc(2 * sizeof(Spec));
	res[0] = s;
	res[1] = NULL;
	return res;
    }

    if (! archs) {
	/* Error */
	return NULL;
    }

    /* We have a BuildArchitectures field */
    count = 0;
    while (archs[count]) {
	count++;
    }
    res = (Spec *) malloc(count * sizeof(Spec));

    i = 0;
    arch = archs;
    while (*arch) {
	if (rpmMachineScore(RPM_MACHTABLE_BUILDARCH, *arch)) {
	    rewind(f);
	    rpmSetMachine(*arch, NULL);
	    res[i] = parseSpecAux(f, specfile, buildRootOverride, NULL);
	    if (! res[i]) {
		/* Error */
		freeSplitString(archs);
		while (i) {
		    i--;
		    freeSpec(res[i]);
		}
		free(res);
		return NULL;
	    }
	    headerAddEntry(res[i]->packages->header, RPMTAG_BUILDARCHS,
			   RPM_STRING_ARRAY_TYPE, archs, count);
	    res[i]->buildArch = strdup(*arch);
	    i++;
	}
	arch++;
    }
    res[i] = NULL;

    freeSplitString(archs);
    
    return res;
}

Spec parseSpecAux(FILE *f, char *specfile, char *buildRootOverride,
		  char ***buildArchs)
{
    char buf[LINE_BUF_SIZE]; /* read buffer          */
    char buf2[LINE_BUF_SIZE];
    char fileFile[LINE_BUF_SIZE];
    char scriptProg[LINE_BUF_SIZE];
    char triggerArgs[LINE_BUF_SIZE];
    char *line;              /* "parsed" read buffer */
    
    int x, serial, tag, cur_part;
    int lookupopts;
    StringBuf sb;
    char *s = NULL;
    char *s1, *s2;
    int gotBuildroot = 0;
    int gotRoot = 0;
    int versionMacroSet = 0;
    int releaseMacroSet = 0;

    struct PackageRec *cur_package = NULL;
    Spec spec = (struct SpecRec *) malloc(sizeof(struct SpecRec));

    spec->name = NULL;
    spec->specfile = strdup(specfile);
    spec->numSources = 0;
    spec->numPatches = 0;
    spec->sources = NULL;
    spec->prep = newStringBuf();
    spec->build = newStringBuf();
    spec->install = newStringBuf();
    spec->doc = newStringBuf();
    spec->clean = newStringBuf();
    spec->packages = NULL;
    spec->noSource = NULL;
    spec->noPatch = NULL;
    spec->numNoSource = 0;
    spec->numNoPatch = 0;
    spec->buildroot = NULL;
    spec->autoReqProv = 1;
    spec->buildArch = NULL;

    sb = newStringBuf();
    reset_spec();         /* Reset the parser */

    scriptProg[0] = '\0';
    cur_part = PREAMBLE_PART;
    while ((x = read_line(f, buf)) > 0) {
	line = buf;
	s = NULL;
        if ((tag = check_part(line, &s))) {
	    rpmMessage(RPMMESS_DEBUG, "Switching to part: %d\n", tag);
	    if (finishCurrentPart(spec, sb, cur_package,
				 cur_part, triggerArgs, scriptProg)) {
		return NULL;
	    }
	    cur_part = tag;
	    truncStringBuf(sb);

	    /* Now switch the current package to s */
	    if (s) {
	        switch (tag) {
		  case PREP_PART:
		  case BUILD_PART:
		  case INSTALL_PART:
		  case CLEAN_PART:
		  case CHANGELOG_PART:
		    rpmError(RPMERR_BADARG, "Tag takes no arguments: %s", s);
		    return NULL;
	        }
	    }

	    /* Rip through s for -f in %files */
	    /* not only is this code disgusting, but it allows -f on any tag */
	    fileFile[0] = '\0';
	    s1 = NULL;
	    if (s &&
		((s1 = strstr(s, " -f ")) ||
		 (!strncmp(s, "-f ", 3)))) {
		if (s1) {
		    s1[0] = ' ';
		    s1++;
		} else {
		    s1 = s;
		}
		s1[0] = ' '; s1[1] = ' '; s1[2] = ' ';
		s1 += 3;
		while (isspace(*s1)) {
		    s1++;
		}

		s2 = fileFile;
		while (*s1 && !isspace(*s1)) {
		    *s2 = *s1;
		    *s1 = ' ';
		    s1++;
		    s2++;
		}
		*s2 = '\0';
		while (isspace(*s)) {
		    s++;
		}
		if (! *s) {
		    s = NULL;
		}
	    }

	    rpmMessage(RPMMESS_DEBUG, "fileFile = %s\n", 
			fileFile[0] ? fileFile : "(null)");

	    /* If trigger, pull off the args */
	    if (tag == TRIGGERIN_PART || tag == TRIGGERUN_PART) {
		s1 = strstr(s, "--");
		if (s1) {
		    strcpy(triggerArgs, s1+2);
		    *s1 = '\0';
		    s = strtok(s, " \n\t");
		} else {
		    strcpy(triggerArgs, s);
		    s = NULL;
		}
	    }

	    /* find possible -p <prog> */
	    if ((tag == PREIN_PART) ||
		(tag == POSTIN_PART) ||
		(tag == PREUN_PART) ||
		(tag == POSTUN_PART)) {

		scriptProg[0] = '\0';
		s1 = NULL;

		if (s &&
		    ((s1 = strstr(s, " -p ")) ||
		     (!strncmp(s, "-p ", 3)))) {
		    
		    if (s1) {
			s1[0] = ' ';
			s1++;
		    } else {
			s1 = s;
		    }
		    s1[0] = ' '; s1[1] = ' '; s1[2] = ' ';
		    s1 += 3;
		    while (isspace(*s1)) {
			s1++;
		    }
		    
		    s2 = scriptProg;
		    while (*s1 && !isspace(*s1)) {
			*s2 = *s1;
			*s1 = ' ';
			s1++;
			s2++;
		    }

		    *s2 = '\0';
		    while (isspace(*s)) {
			s++;
		    }
		    if (! *s) {
			s = NULL;
		    }
		}
		
		/* defaults to /bin/sh */
		if (! scriptProg[0]) {
		    strcpy(scriptProg, "/bin/sh");
		} else {
		    if (scriptProg[0] != '/') {
			rpmError(RPMERR_BADSPEC, "pre/post -p arg must begin with \'/\': %s", scriptProg);
			return NULL;
		    }
		}
		rpmMessage(RPMMESS_DEBUG, "scriptProg = %s\n", scriptProg);
	    }
	    
	    /* At this point s is the remaining args, which can only */
	    /* be -n <pkg>, or simply <pkg>.                         */
	    
	    /* Handle -n in part tags */
	    lookupopts = 0;
	    if (s) {
		if (!strncmp(s, "-n", 2)) {
		    s += 2;
		    s += strspn(s, ": \t");
		    if (*s == '\0') {
			rpmError(RPMERR_BADARG, "-n takes argument");
			return NULL;
		    }
		    lookupopts = LP_NEWNAME;
		} else {
		    lookupopts = LP_SUBNAME;
		}
	        /* Handle trailing whitespace */
	        s1 = s + strlen(s) - 1;
	        while (*s1 == ' ' || *s1 == '\n' || *s1 == '\t') {
		   s1--;
		}
	        s1++;
	        *s1 = '\0';
	    }
	    
	    switch (tag) {
	      case PREP_PART:
	      case BUILD_PART:
	      case INSTALL_PART:
	      case CLEAN_PART:
	      case CHANGELOG_PART:
	        /* Do not switch parts for these */
	        break;
	      case PREAMBLE_PART:
	        lookupopts |= LP_CREATE | LP_FAIL_EXISTS;
	        /* Fall through */
	      default:
	        /* XXX - should be able to drop the -n in non-%package parts */
	        if (! lookup_package(spec, &cur_package, s, lookupopts)) {
		    rpmError(RPMERR_INTERNAL, "Package lookup failed: %s",
			     (s) ? s : "(main)");
		    return NULL;
	        }
	        rpmMessage(RPMMESS_DEBUG, "Switched to package: %s\n", 
			s ? s : "(main)");
	    }

	    if (cur_part == FILES_PART) {
		/* set files to 0 (current -1 means no %files, no package */
		cur_package->files = 0;
		if (fileFile[0]) {
		    cur_package->fileFile = strdup(fileFile);
		}
	    }

	    /* This line has no content -- it was just a control line */
	    continue;
        }

	/* Check for implicit "base" package.                        */
        /* That means that the specfile does not start with %package */
	if (! cur_package) {
	    lookupopts = 0;
	    if (cur_part == PREAMBLE_PART) {
		lookupopts = LP_CREATE | LP_FAIL_EXISTS;
	    }
	    if (! lookup_package(spec, &cur_package, NULL, lookupopts)) {
		rpmError(RPMERR_INTERNAL, "Base package lookup failed!");
		return NULL;
	    }
	    rpmMessage(RPMMESS_DEBUG, "Switched to BASE package\n");
	}

	switch (cur_part) {
	  case PREAMBLE_PART:
	    if ((tag = find_preamble_line(line, &s))) {
	        switch (tag) {
		  case RPMTAG_EXCLUDE:
		  case RPMTAG_EXCLUSIVE:
		      rpmMessage(RPMMESS_WARNING,
			      "Exclude/Exclusive are depricated.\n"
			      "Use ExcludeArch/ExclusiveArch instead.\n");
		      sprintf(buf2, "%s %s",
			      (tag == RPMTAG_EXCLUDE) ? "%ifarch" : "%ifnarch",
			      s);
		      if (match_arch(buf2)) {
			  rpmError(RPMERR_BADARCH, "Arch mismatch!");
			  return NULL;
		      }
		      addListEntry(cur_package->header,
				   (tag == RPMTAG_EXCLUDE) ?
				   RPMTAG_EXCLUDEARCH : RPMTAG_EXCLUSIVEARCH,
				   s);
		      break;
		  case RPMTAG_EXCLUDEARCH:	
		  case RPMTAG_EXCLUSIVEARCH:
		    sprintf(buf2, "%s %s", (tag == RPMTAG_EXCLUDEARCH) ?
			    "%ifarch" : "%ifnarch",  s);
		    if (match_arch(buf2)) {
			rpmError(RPMERR_BADARCH, "Arch mismatch!");
			return NULL;
		    }
		    addListEntry(cur_package->header, tag, s);
		    break;
		  case RPMTAG_EXCLUDEOS:
		  case RPMTAG_EXCLUSIVEOS:
		    sprintf(buf2, "%s %s", (tag == RPMTAG_EXCLUDEOS) ?
			    "%ifos" : "%ifnos",  s);
		    if (match_os(buf2)) {
			rpmError(RPMERR_BADOS, "OS mismatch!");
			return NULL;
		    }
		    addListEntry(cur_package->header, tag, s);
		    break;
		  case RPMTAG_NAME:
		    s1 = s;
		    while (*s1 && *s1 != ' ' && *s1 != '\t') s1++;
		    *s1 = '\0';
		    if (!spec->name) {
			spec->name = strdup(s);
		    }
		    /* The NAME entries must be generated after */
		    /* the whole spec file is parsed.           */
		    break;
		  case RPMTAG_VERSION:
		  case RPMTAG_RELEASE:
		    s1 = s;
		    while (*s1 && *s1 != ' ' && *s1 != '\t') s1++;
		    *s1 = '\0';
		    if (s1 == s) {
			rpmError(RPMERR_BADSPEC, (tag == RPMTAG_VERSION) ?
				 "Empty version field." :
				 "Empty release field.");
			return NULL;
		    }
		    if (tag == RPMTAG_VERSION) {
			if (! versionMacroSet) {
			    versionMacroSet = 1;
			    addMacro("PACKAGE_VERSION", s);
			}
		    } else {
			if (! releaseMacroSet) {
			    releaseMacroSet = 1;
			    addMacro("PACKAGE_RELEASE", s);
			}
		    }
		  case RPMTAG_SUMMARY:
		  case RPMTAG_DISTRIBUTION:
		  case RPMTAG_VENDOR:
		  case RPMTAG_COPYRIGHT:
		  case RPMTAG_PACKAGER:
		  case RPMTAG_GROUP:
		  case RPMTAG_URL:
		    headerAddEntry(cur_package->header, tag, RPM_STRING_TYPE, s, 1);
		    break;
		  case RPMTAG_BUILDARCHS:
		    if (buildArchs) {
			*buildArchs = splitString(s, strlen(s), ' ');
			freeSpec(spec);
			freeStringBuf(sb);
			return NULL;
		    }
		    break;
		  case RPMTAG_BUILDROOT:
		    gotBuildroot = 1;
		    spec->buildroot = strdup(s);
		    break;
		  case RPMTAG_DEFAULTPREFIX:
		    headerAddEntry(cur_package->header, tag, RPM_STRING_TYPE, s, 1);
		    break;
		  case RPMTAG_SERIAL:
		    serial = atoi(s);
		    headerAddEntry(cur_package->header, tag, RPM_INT32_TYPE, &serial, 1);
		    break;
		  case RPMTAG_DESCRIPTION:
		    /* Special case -- need to handle backslash */
		    truncStringBuf(sb);
		    while (1) {
		        s1 = s + strlen(s) - 1;
			if (*s1 != '\\') {
			    break;
			}
		        *s1 = '\0';
			appendLineStringBuf(sb, s);
			read_line(f, buf);
			s = buf;
		    }
		    appendStringBuf(sb, s);
		    headerAddEntry(cur_package->header, RPMTAG_DESCRIPTION,
			     RPM_STRING_TYPE, getStringBuf(sb), 1);
		    break;
		  case RPMTAG_ROOT:
		    /* special case */
		    gotRoot = 1;
		    rpmMessage(RPMMESS_DEBUG, "Got root: %s\n", s);
		    rpmMessage(RPMMESS_WARNING, "The Root: tag is depricated.  Use Buildroot: instead\n");
		    rpmSetVar(RPMVAR_ROOT, s);
		    break;
		  case RPMTAG_ICON:
		      cur_package->icon = strdup(s);
		      break;
		  case RPMTAG_NOPATCH:
		  case RPMTAG_NOSOURCE:
		      if (noSourcePatch(spec, s, tag)) {
			  return NULL;
		      }
		      break;
		  case RPMTAG_SOURCE:
		  case RPMTAG_PATCH:
		      if (addSource(spec, line)) {
			  return NULL;
		      }
		      break;
		  case RPMTAG_OBSOLETES:
		  case RPMTAG_PROVIDES:
		      if (parseProvides(cur_package, s, tag)) {
			  return NULL;
		      }
		      break;
		  case RPMTAG_REQUIREFLAGS:
		  case RPMTAG_CONFLICTFLAGS:
		  case RPMTAG_PREREQ:
		      if (parseRequiresConflicts(cur_package, s, tag)) {
			  return NULL;
		      }
		      break;
		  case RPMTAG_AUTOREQPROV:
		    s1 = strtok(s, " \t\n");
		    if (!s1) {
			spec->autoReqProv = 0;
		    } else if (s1[0] == 'n' || s1[0] == 'N') {
			spec->autoReqProv = 0;
		    } else if (!strcasecmp(s1, "false")) {
			spec->autoReqProv = 0;
		    } else if (!strcasecmp(s1, "off")) {
			spec->autoReqProv = 0;
		    } else if (!strcmp(s1, "0")) {
			spec->autoReqProv = 0;
		    }
		    break;
		  default:
		      /* rpmMessage(RPMMESS_DEBUG, "Skipping: %s\n", line); */
		      /* This shouldn't happen? */
		      rpmError(RPMERR_INTERNAL, "Bogus token");
		      return NULL;
		}		
	    } else {
	        /* Not a recognized preamble part */
		s1 = line;
		while (*s1 && (*s1 == ' ' || *s1 == '\t')) s1++;
		/* Handle blanks lines and comments */
		if (*s1 && (*s1 != '#')) {
		    /*rpmMessage(RPMMESS_WARNING, "Unknown Field: %s\n", line);*/
		    rpmError(RPMERR_BADSPEC, "Unknown Field: %s\n", line);
		    return NULL;
		}
	    }
	    break;
	  case PREP_PART:
	    appendLineStringBuf(spec->prep, line);
	    break;
	  case BUILD_PART:
	    appendLineStringBuf(spec->build, line);
	    break;
	  case INSTALL_PART:
	    appendLineStringBuf(spec->install, line);
	    break;
	  case CLEAN_PART:
	    appendLineStringBuf(spec->clean, line);
	    break;
	  case DESCRIPTION_PART:
	  case CHANGELOG_PART:
	  case PREIN_PART:
	  case POSTIN_PART:
	  case PREUN_PART:
	  case POSTUN_PART:
	  case VERIFYSCRIPT_PART:
	    appendLineStringBuf(sb, line);
	    break;
	  case TRIGGERIN_PART:
	  case TRIGGERUN_PART:
	    appendLineStringBuf(sb, line);
	    break;
	  case FILES_PART:
	      s1 = line;
	      while (*s1 && (*s1 == ' ' || *s1 == '\t')) s1++;
	      /* Handle blanks lines and comments */
	      if (*s1 && (*s1 != '#')) {
		  cur_package->files++;
		  appendLineStringBuf(cur_package->filelist, line);
	      }
	    break;
	  default:
	    rpmError(RPMERR_INTERNAL, "Bad part");
	    return NULL;
	} /* switch */
    }
    if (x < 0) {
	return NULL;
    }

    /* finish current part */
    if (finishCurrentPart(spec, sb, cur_package,
			  cur_part, triggerArgs,
			  scriptProg)) {
	return NULL;
    }

    freeStringBuf(sb);
    
    if (gotRoot && gotBuildroot) {
	freeSpec(spec);
	rpmError(RPMERR_BADSPEC,
	      "Spec file can not have both Root: and Buildroot:");
	return NULL;
    }
    if (spec->buildroot) {
	/* This package can do build roots */
	if (buildRootOverride) {
	    rpmSetVar(RPMVAR_ROOT, buildRootOverride);
	    rpmSetVar(RPMVAR_BUILDROOT, buildRootOverride);
	} else {
	    if ((s = rpmGetVar(RPMVAR_BUILDROOT))) {
		/* Take build prefix from rpmrc */
		rpmSetVar(RPMVAR_ROOT, s);
	    } else {
		/* Use default */
		rpmSetVar(RPMVAR_ROOT, spec->buildroot);
		rpmSetVar(RPMVAR_BUILDROOT, spec->buildroot);
	    }
	}
    } else {
	/* Package can not do build prefixes */
	if (buildRootOverride) {
	    freeSpec(spec);
	    rpmError(RPMERR_BADARG, "Package can not do build prefixes");
	    return NULL;
	}
    }

    generateNames(spec);
    return spec;
}

/**********************************************************************/
/*                                                                    */
/* Resets the parser                                                  */
/*                                                                    */
/**********************************************************************/

static void reset_spec()
{
    static done = 0;
    struct read_level_entry *rl;
    struct preamble_line *p = preamble_spec;
    struct part_rec *p1 = part_list;

    rpmSetVar(RPMVAR_ROOT, NULL);

    while (read_level) {
	rl = read_level;
        read_level = read_level->next;
	free(rl);
    }
    read_level = malloc(sizeof(struct read_level_entry));
    read_level->next = NULL;
    read_level->reading = 1;

    resetMacros();
    
    if (! done) {
        /* Put one time only things in here */
        while (p->tag) {
	    p->len = strlen(p->token);
	    p++;
	}
        while (p1->part) {
	    p1->len = strlen(p1->s);
	    p1++;
	}

	done = 1;
    }
}

/**********************************************************************/
/*                                                                    */
/* Spec struct dumping (for debugging)                                */
/*                                                                    */
/**********************************************************************/

void dumpSpec(Spec s, FILE *f)
{
    struct PackageRec *p;
    
    fprintf(f, "########################################################\n");
    fprintf(f, "SPEC NAME = (%s)\n", s->name);
    fprintf(f, "PREP =v\n");
    fprintf(f, "%s", getStringBuf(s->prep));
    fprintf(f, "PREP =^\n");
    fprintf(f, "BUILD =v\n");
    fprintf(f, "%s", getStringBuf(s->build));
    fprintf(f, "BUILD =^\n");
    fprintf(f, "INSTALL =v\n");
    fprintf(f, "%s", getStringBuf(s->install));
    fprintf(f, "INSTALL =^\n");
    fprintf(f, "CLEAN =v\n");
    fprintf(f, "%s", getStringBuf(s->clean));
    fprintf(f, "CLEAN =^\n");

    p = s->packages;
    while (p) {
	dumpPackage(p, f);
	p = p->next;
    }
}

static void dumpPackage(struct PackageRec *p, FILE *f)
{
    fprintf(f, "_________________________________________________________\n");
    fprintf(f, "SUBNAME = (%s)\n", p->subname);
    fprintf(f, "NEWNAME = (%s)\n", p->newname);
    fprintf(f, "FILES = %d\n", p->files);
    fprintf(f, "FILES =v\n");
    fprintf(f, "%s", getStringBuf(p->filelist));
    fprintf(f, "FILES =^\n");
    fprintf(f, "HEADER =v\n");
    headerDump(p->header, f, 1, rpmTagTable);
    fprintf(f, "HEADER =^\n");

}
