Blame build/parseSpec.c

2ff057
/** \ingroup rpmbuild
2ff057
 * \file build/parseSpec.c
2ff057
 *  Top level dispatcher for spec file parsing.
2ff057
 */
2ff057
2ff057
#include "system.h"
2ff057
2ff057
#include <errno.h>
2ff057
#ifdef HAVE_ICONV
2ff057
#include <iconv.h>
2ff057
#endif
2ff057
2ff057
#include <rpm/rpmtypes.h>
2ff057
#include <rpm/rpmlib.h>		/* RPM_MACHTABLE & related */
2ff057
#include <rpm/rpmds.h>
2ff057
#include <rpm/rpmts.h>
2ff057
#include <rpm/rpmlog.h>
2ff057
#include <rpm/rpmfileutil.h>
2ff057
#include "build/rpmbuild_internal.h"
2ff057
#include "build/rpmbuild_misc.h"
2ff057
#include "debug.h"
2ff057
2ff057
#define SKIPSPACE(s) { while (*(s) && risspace(*(s))) (s)++; }
2ff057
#define SKIPNONSPACE(s) { while (*(s) && !risspace(*(s))) (s)++; }
2ff057
#define ISMACRO(s,m) (rstreqn((s), (m), sizeof((m))-1) && !risalpha((s)[sizeof((m))-1]))
2ff057
#define ISMACROWITHARG(s,m) (rstreqn((s), (m), sizeof((m))-1) && (risblank((s)[sizeof((m))-1]) || !(s)[sizeof((m))-1]))
2ff057
2ff057
#define LEN_AND_STR(_tag) (sizeof(_tag)-1), (_tag)
2ff057
2ff057
typedef struct OpenFileInfo {
2ff057
    char * fileName;
2ff057
    FILE *fp;
2ff057
    int lineNum;
2ff057
    char *readBuf;
2ff057
    size_t readBufLen;
2ff057
    const char * readPtr;
2ff057
    struct OpenFileInfo * next;
2ff057
} OFI_t;
2ff057
2ff057
static const struct PartRec {
2ff057
    int part;
2ff057
    size_t len;
2ff057
    const char * token;
2ff057
} partList[] = {
2ff057
    { PART_PREAMBLE,      LEN_AND_STR("%package")},
2ff057
    { PART_PREP,          LEN_AND_STR("%prep")},
2ff057
    { PART_BUILD,         LEN_AND_STR("%build")},
2ff057
    { PART_INSTALL,       LEN_AND_STR("%install")},
2ff057
    { PART_CHECK,         LEN_AND_STR("%check")},
2ff057
    { PART_CLEAN,         LEN_AND_STR("%clean")},
2ff057
    { PART_PREUN,         LEN_AND_STR("%preun")},
2ff057
    { PART_POSTUN,        LEN_AND_STR("%postun")},
2ff057
    { PART_PRETRANS,      LEN_AND_STR("%pretrans")},
2ff057
    { PART_POSTTRANS,     LEN_AND_STR("%posttrans")},
2ff057
    { PART_PRE,           LEN_AND_STR("%pre")},
2ff057
    { PART_POST,          LEN_AND_STR("%post")},
2ff057
    { PART_FILES,         LEN_AND_STR("%files")},
2ff057
    { PART_CHANGELOG,     LEN_AND_STR("%changelog")},
2ff057
    { PART_DESCRIPTION,   LEN_AND_STR("%description")},
2ff057
    { PART_TRIGGERPOSTUN, LEN_AND_STR("%triggerpostun")},
2ff057
    { PART_TRIGGERPREIN,  LEN_AND_STR("%triggerprein")},
2ff057
    { PART_TRIGGERUN,     LEN_AND_STR("%triggerun")},
2ff057
    { PART_TRIGGERIN,     LEN_AND_STR("%triggerin")},
2ff057
    { PART_TRIGGERIN,     LEN_AND_STR("%trigger")},
2ff057
    { PART_VERIFYSCRIPT,  LEN_AND_STR("%verifyscript")},
2ff057
    { PART_POLICIES,      LEN_AND_STR("%sepolicy")},
2ff057
    { PART_FILETRIGGERIN,	    LEN_AND_STR("%filetriggerin")},
2ff057
    { PART_FILETRIGGERIN,	    LEN_AND_STR("%filetrigger")},
2ff057
    { PART_FILETRIGGERUN,	    LEN_AND_STR("%filetriggerun")},
2ff057
    { PART_FILETRIGGERPOSTUN,	    LEN_AND_STR("%filetriggerpostun")},
2ff057
    { PART_TRANSFILETRIGGERIN,	    LEN_AND_STR("%transfiletriggerin")},
2ff057
    { PART_TRANSFILETRIGGERIN,	    LEN_AND_STR("%transfiletrigger")},
2ff057
    { PART_TRANSFILETRIGGERUN,	    LEN_AND_STR("%transfiletriggerun")},
2ff057
    { PART_TRANSFILETRIGGERUN,	    LEN_AND_STR("%transfiletriggerun")},
2ff057
    { PART_TRANSFILETRIGGERPOSTUN,  LEN_AND_STR("%transfiletriggerpostun")},
2ff057
    { PART_EMPTY,		    LEN_AND_STR("%end")},
2ff057
    {0, 0, 0}
2ff057
};
2ff057
2ff057
int isPart(const char *line)
2ff057
{
2ff057
    const struct PartRec *p;
2ff057
2ff057
    for (p = partList; p->token != NULL; p++) {
2ff057
	char c;
2ff057
	if (rstrncasecmp(line, p->token, p->len))
2ff057
	    continue;
2ff057
	c = *(line + p->len);
2ff057
	if (c == '\0' || risspace(c))
2ff057
	    break;
2ff057
    }
2ff057
2ff057
    return (p->token ? p->part : PART_NONE);
2ff057
}
2ff057
2ff057
/**
2ff057
 */
2ff057
static int matchTok(const char *token, const char *line)
2ff057
{
2ff057
    const char *b, *be = line;
2ff057
    size_t toklen = strlen(token);
2ff057
    int rc = 0;
2ff057
2ff057
    while ( *(b = be) != '\0' ) {
2ff057
	SKIPSPACE(b);
2ff057
	be = b;
2ff057
	SKIPNONSPACE(be);
2ff057
	if (be == b)
2ff057
	    break;
2ff057
	if (toklen != (be-b) || rstrncasecmp(token, b, (be-b)))
2ff057
	    continue;
2ff057
	rc = 1;
2ff057
	break;
2ff057
    }
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
int handleComments(char *s)
2ff057
{
2ff057
    SKIPSPACE(s);
2ff057
    if (*s == '#') {
2ff057
	*s = '\0';
2ff057
	return 1;
2ff057
    }
2ff057
    return 0;
2ff057
}
2ff057
2ff057
/* Push a file to spec's file stack, return the newly pushed entry */
2ff057
static OFI_t * pushOFI(rpmSpec spec, const char *fn)
2ff057
{
2ff057
    OFI_t *ofi = xcalloc(1, sizeof(*ofi));
2ff057
2ff057
    ofi->fp = NULL;
2ff057
    ofi->fileName = xstrdup(fn);
2ff057
    ofi->lineNum = 0;
2ff057
    ofi->readBufLen = BUFSIZ;
2ff057
    ofi->readBuf = xmalloc(ofi->readBufLen);
2ff057
    ofi->readBuf[0] = '\0';
2ff057
    ofi->readPtr = NULL;
2ff057
    ofi->next = spec->fileStack;
2ff057
2ff057
    spec->fileStack = ofi;
2ff057
    return spec->fileStack;
2ff057
}
2ff057
2ff057
/* Pop from spec's file stack */
2ff057
static OFI_t * popOFI(rpmSpec spec)
2ff057
{
2ff057
    if (spec->fileStack) {
2ff057
	OFI_t * ofi = spec->fileStack;
2ff057
2ff057
	spec->fileStack = ofi->next;
2ff057
	if (ofi->fp)
2ff057
	    fclose(ofi->fp);
2ff057
	free(ofi->fileName);
2ff057
	free(ofi->readBuf);
2ff057
	free(ofi);
2ff057
    }
2ff057
    return spec->fileStack;
2ff057
}
2ff057
2ff057
static int restoreFirstChar(rpmSpec spec)
2ff057
{
2ff057
    /* Restore 1st char in (possible) next line */
2ff057
    if (spec->nextline != NULL && spec->nextpeekc != '\0') {
2ff057
	*spec->nextline = spec->nextpeekc;
2ff057
	spec->nextpeekc = '\0';
2ff057
	return 1;
2ff057
    }
2ff057
    return 0;
2ff057
}
2ff057
2ff057
static int expandMacrosInSpecBuf(rpmSpec spec, int strip)
2ff057
{
2ff057
    char *lbuf = NULL;
2ff057
    int isComment = 0;
2ff057
2ff057
     /* Don't expand macros (eg. %define) in false branch of %if clause */
2ff057
    if (!spec->readStack->reading)
2ff057
	return 0;
2ff057
2ff057
    lbuf = spec->lbuf;
2ff057
    SKIPSPACE(lbuf);
2ff057
    if (lbuf[0] == '#')
2ff057
	isComment = 1;
2ff057
2ff057
2ff057
    if (rpmExpandMacros(spec->macros, spec->lbuf, &lbuf, 0) < 0) {
2ff057
	rpmlog(RPMLOG_ERR, _("line %d: %s\n"),
2ff057
		spec->lineNum, spec->lbuf);
2ff057
	return 1;
2ff057
    }
2ff057
2ff057
    if (strip & STRIP_COMMENTS && isComment) {
2ff057
	char *bufA = spec->lbuf;
2ff057
	char *bufB = lbuf;
2ff057
2ff057
	while (*bufA != '\0' && *bufB != '\0') {
2ff057
	    if (*bufA == '%' && *(bufA + 1) == '%')
2ff057
		bufA++;
2ff057
2ff057
	    if (*bufA != *bufB)
2ff057
		break;
2ff057
2ff057
	    bufA++;
2ff057
	    bufB++;
2ff057
	}
2ff057
2ff057
	if (*bufA != '\0' || *bufB != '\0')
2ff057
	    rpmlog(RPMLOG_WARNING,
2ff057
		_("Macro expanded in comment on line %d: %s\n"),
2ff057
		spec->lineNum, bufA);
2ff057
    }
2ff057
2ff057
    free(spec->lbuf);
2ff057
    spec->lbuf = lbuf;
2ff057
    spec->lbufSize = strlen(spec->lbuf) + 1;
2ff057
2ff057
    return 0;
2ff057
}
2ff057
2ff057
/* Return zero on success, 1 if we need to read more and -1 on errors. */
2ff057
static int copyNextLineFromOFI(rpmSpec spec, OFI_t *ofi, int strip)
2ff057
{
2ff057
    /* Expand next line from file into line buffer */
2ff057
    if (!(spec->nextline && *spec->nextline)) {
2ff057
	int pc = 0, bc = 0, nc = 0;
2ff057
	const char *from = ofi->readPtr;
2ff057
	char ch = ' ';
2ff057
	while (from && *from && ch != '\n') {
2ff057
	    ch = spec->lbuf[spec->lbufOff] = *from;
2ff057
	    spec->lbufOff++; from++;
2ff057
2ff057
	    if (spec->lbufOff >= spec->lbufSize) {
2ff057
		spec->lbufSize += BUFSIZ;
2ff057
		spec->lbuf = realloc(spec->lbuf, spec->lbufSize);
2ff057
	    }
2ff057
	}
2ff057
	spec->lbuf[spec->lbufOff] = '\0';
2ff057
	ofi->readPtr = from;
2ff057
2ff057
	/* Check if we need another line before expanding the buffer. */
2ff057
	for (const char *p = spec->lbuf; *p; p++) {
2ff057
	    switch (*p) {
2ff057
		case '\\':
2ff057
		    switch (*(p+1)) {
2ff057
			case '\n': p++, nc = 1; break;
2ff057
			case '\0': break;
2ff057
			default: p++; break;
2ff057
		    }
2ff057
		    break;
2ff057
		case '\n': nc = 0; break;
2ff057
		case '%':
2ff057
		    switch (*(p+1)) {
2ff057
			case '{': p++, bc++; break;
2ff057
			case '(': p++, pc++; break;
2ff057
			case '%': p++; break;
2ff057
		    }
2ff057
		    break;
2ff057
		case '{': if (bc > 0) bc++; break;
2ff057
		case '}': if (bc > 0) bc--; break;
2ff057
		case '(': if (pc > 0) pc++; break;
2ff057
		case ')': if (pc > 0) pc--; break;
2ff057
	    }
2ff057
	}
2ff057
	
2ff057
	/* If it doesn't, ask for one more line. */
2ff057
	if (pc || bc || nc ) {
2ff057
	    spec->nextline = "";
2ff057
	    return 1;
2ff057
	}
2ff057
	spec->lbufOff = 0;
2ff057
2ff057
	if (expandMacrosInSpecBuf(spec, strip))
2ff057
	    return -1;
2ff057
2ff057
	spec->nextline = spec->lbuf;
2ff057
    }
2ff057
    return 0;
2ff057
}
2ff057
2ff057
static void copyNextLineFinish(rpmSpec spec, int strip)
2ff057
{
2ff057
    char *last;
2ff057
    char ch;
2ff057
2ff057
    /* Find next line in expanded line buffer */
2ff057
    spec->line = last = spec->nextline;
2ff057
    ch = ' ';
2ff057
    while (*spec->nextline && ch != '\n') {
2ff057
	ch = *spec->nextline++;
2ff057
	if (!risspace(ch))
2ff057
	    last = spec->nextline;
2ff057
    }
2ff057
2ff057
    /* Save 1st char of next line in order to terminate current line. */
2ff057
    if (*spec->nextline != '\0') {
2ff057
	spec->nextpeekc = *spec->nextline;
2ff057
	*spec->nextline = '\0';
2ff057
    }
2ff057
    
2ff057
    if (strip & STRIP_COMMENTS)
2ff057
	handleComments(spec->line);
2ff057
    
2ff057
    if (strip & STRIP_TRAILINGSPACE)
2ff057
	*last = '\0';
2ff057
}
2ff057
2ff057
static int readLineFromOFI(rpmSpec spec, OFI_t *ofi)
2ff057
{
2ff057
retry:
2ff057
    /* Make sure the current file is open */
2ff057
    if (ofi->fp == NULL) {
2ff057
	ofi->fp = fopen(ofi->fileName, "r");
2ff057
	if (ofi->fp == NULL) {
2ff057
	    rpmlog(RPMLOG_ERR, _("Unable to open %s: %s\n"),
2ff057
		     ofi->fileName, strerror(errno));
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
	spec->lineNum = ofi->lineNum = 0;
2ff057
    }
2ff057
2ff057
    /* Make sure we have something in the read buffer */
2ff057
    if (!(ofi->readPtr && *(ofi->readPtr))) {
2ff057
	if (getline(&ofi->readBuf, &ofi->readBufLen, ofi->fp) <= 0) {
2ff057
	    /* EOF, remove this file from the stack */
2ff057
	    ofi = popOFI(spec);
2ff057
2ff057
	    /* only on last file do we signal EOF to caller */
2ff057
	    if (ofi == NULL)
2ff057
		return 1;
2ff057
2ff057
	    /* otherwise, go back and try the read again. */
2ff057
	    goto retry;
2ff057
	}
2ff057
	ofi->readPtr = ofi->readBuf;
2ff057
	ofi->lineNum++;
2ff057
	spec->lineNum = ofi->lineNum;
2ff057
    }
2ff057
    return 0;
2ff057
}
2ff057
2ff057
#define ARGMATCH(s,token,match) \
2ff057
do { \
2ff057
    char *os = s; \
2ff057
    char *exp = rpmExpand(token, NULL); \
2ff057
    while (*s && !risblank(*s)) s++; \
2ff057
    while (*s && risblank(*s)) s++; \
2ff057
    if (!*s) { \
2ff057
	rpmlog(RPMLOG_ERR, _("%s:%d: Argument expected for %s\n"), ofi->fileName, ofi->lineNum, os); \
2ff057
	free(exp); \
2ff057
	return PART_ERROR; \
2ff057
    } \
2ff057
    match = matchTok(exp, s); \
2ff057
    free(exp); \
2ff057
} while (0)
2ff057
2ff057
2ff057
int readLine(rpmSpec spec, int strip)
2ff057
{
2ff057
    char *s;
2ff057
    int match;
2ff057
    struct ReadLevelEntry *rl;
2ff057
    OFI_t *ofi = spec->fileStack;
2ff057
    int rc;
2ff057
    int startLine = 0;
2ff057
2ff057
    if (!restoreFirstChar(spec)) {
2ff057
    retry:
2ff057
	if ((rc = readLineFromOFI(spec, ofi)) != 0) {
2ff057
	    if (spec->readStack->next) {
2ff057
		rpmlog(RPMLOG_ERR, _("line %d: Unclosed %%if\n"),
2ff057
			spec->readStack->lineNum);
2ff057
		rc = PART_ERROR;
2ff057
	    } else if (startLine > 0) {
2ff057
		rpmlog(RPMLOG_ERR,
2ff057
		    _("line %d: unclosed macro or bad line continuation\n"),
2ff057
		    startLine);
2ff057
		rc = PART_ERROR;
2ff057
	    }
2ff057
	    return rc;
2ff057
	}
2ff057
	ofi = spec->fileStack;
2ff057
2ff057
	/* Copy next file line into the spec line buffer */
2ff057
	rc = copyNextLineFromOFI(spec, ofi, strip);
2ff057
	if (rc > 0) {
2ff057
	    if (startLine == 0)
2ff057
		startLine = spec->lineNum;
2ff057
	    goto retry;
2ff057
	} else if (rc < 0) {
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
    }
2ff057
2ff057
    copyNextLineFinish(spec, strip);
2ff057
2ff057
    s = spec->line;
2ff057
    SKIPSPACE(s);
2ff057
2ff057
    match = -1;
2ff057
    if (!spec->readStack->reading && ISMACROWITHARG(s, "%if")) {
2ff057
	match = 0;
2ff057
    } else if (ISMACROWITHARG(s, "%ifarch")) {
2ff057
	ARGMATCH(s, "%{_target_cpu}", match);
2ff057
    } else if (ISMACROWITHARG(s, "%ifnarch")) {
2ff057
	ARGMATCH(s, "%{_target_cpu}", match);
2ff057
	match = !match;
2ff057
    } else if (ISMACROWITHARG(s, "%ifos")) {
2ff057
	ARGMATCH(s, "%{_target_os}", match);
2ff057
    } else if (ISMACROWITHARG(s, "%ifnos")) {
2ff057
	ARGMATCH(s, "%{_target_os}", match);
2ff057
	match = !match;
2ff057
    } else if (ISMACROWITHARG(s, "%if")) {
2ff057
	s += 3;
2ff057
        match = parseExpressionBoolean(s);
2ff057
	if (match < 0) {
2ff057
	    rpmlog(RPMLOG_ERR,
2ff057
			_("%s:%d: bad %%if condition\n"),
2ff057
			ofi->fileName, ofi->lineNum);
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
    } else if (ISMACRO(s, "%else")) {
2ff057
	if (! spec->readStack->next) {
2ff057
	    /* Got an else with no %if ! */
2ff057
	    rpmlog(RPMLOG_ERR,
2ff057
			_("%s:%d: Got a %%else with no %%if\n"),
2ff057
			ofi->fileName, ofi->lineNum);
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
	spec->readStack->reading =
2ff057
	    spec->readStack->next->reading && ! spec->readStack->reading;
2ff057
	spec->line[0] = '\0';
2ff057
    } else if (ISMACRO(s, "%endif")) {
2ff057
	if (! spec->readStack->next) {
2ff057
	    /* Got an end with no %if ! */
2ff057
	    rpmlog(RPMLOG_ERR,
2ff057
			_("%s:%d: Got a %%endif with no %%if\n"),
2ff057
			ofi->fileName, ofi->lineNum);
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
	rl = spec->readStack;
2ff057
	spec->readStack = spec->readStack->next;
2ff057
	free(rl);
2ff057
	spec->line[0] = '\0';
2ff057
    } else if (spec->readStack->reading && ISMACROWITHARG(s, "%include")) {
2ff057
	char *fileName, *endFileName, *p;
2ff057
2ff057
	fileName = s+8;
2ff057
	SKIPSPACE(fileName);
2ff057
	endFileName = fileName;
2ff057
	do {
2ff057
	    SKIPNONSPACE(endFileName);
2ff057
	    p = endFileName;
2ff057
	    SKIPSPACE(p);
2ff057
	    if (*p != '\0') endFileName = p;
2ff057
	} while (*p != '\0');
2ff057
	if (*fileName == '\0') {
2ff057
	    rpmlog(RPMLOG_ERR, _("%s:%d: malformed %%include statement\n"),
2ff057
				ofi->fileName, ofi->lineNum);
2ff057
	    return PART_ERROR;
2ff057
	}
2ff057
	*endFileName = '\0';
2ff057
2ff057
	ofi = pushOFI(spec, fileName);
2ff057
	goto retry;
2ff057
    }
2ff057
2ff057
    if (match != -1) {
2ff057
	rl = xmalloc(sizeof(*rl));
2ff057
	rl->reading = spec->readStack->reading && match;
2ff057
	rl->next = spec->readStack;
2ff057
	rl->lineNum = ofi->lineNum;
2ff057
	spec->readStack = rl;
2ff057
	spec->line[0] = '\0';
2ff057
    }
2ff057
2ff057
    if (! spec->readStack->reading) {
2ff057
	spec->line[0] = '\0';
2ff057
    }
2ff057
2ff057
    /* Collect parsed line */
2ff057
    if (spec->parsed == NULL)
2ff057
	spec->parsed = newStringBuf();
2ff057
    appendStringBufAux(spec->parsed, spec->line,(strip & STRIP_TRAILINGSPACE));
2ff057
2ff057
    /* FIX: spec->readStack->next should be dependent */
2ff057
    return 0;
2ff057
}
2ff057
2ff057
void closeSpec(rpmSpec spec)
2ff057
{
2ff057
    while (popOFI(spec)) {};
2ff057
}
2ff057
2ff057
static const rpmTagVal sourceTags[] = {
2ff057
    RPMTAG_NAME,
2ff057
    RPMTAG_VERSION,
2ff057
    RPMTAG_RELEASE,
2ff057
    RPMTAG_EPOCH,
2ff057
    RPMTAG_SUMMARY,
2ff057
    RPMTAG_DESCRIPTION,
2ff057
    RPMTAG_PACKAGER,
2ff057
    RPMTAG_DISTRIBUTION,
2ff057
    RPMTAG_DISTURL,
2ff057
    RPMTAG_DISTTAG,
2ff057
    RPMTAG_VENDOR,
2ff057
    RPMTAG_LICENSE,
2ff057
    RPMTAG_GROUP,
2ff057
    RPMTAG_OS,
2ff057
    RPMTAG_ARCH,
2ff057
    RPMTAG_CHANGELOGTIME,
2ff057
    RPMTAG_CHANGELOGNAME,
2ff057
    RPMTAG_CHANGELOGTEXT,
2ff057
    RPMTAG_URL,
2ff057
    RPMTAG_BUGURL,
2ff057
    RPMTAG_HEADERI18NTABLE,
2ff057
    RPMTAG_VCS,
Florian Festi 74fe6f
    RPMTAG_MODULARITYLABEL,
2ff057
    0
2ff057
};
2ff057
2ff057
static void initSourceHeader(rpmSpec spec)
2ff057
{
2ff057
    Package sourcePkg = spec->sourcePackage;
2ff057
    struct Source *srcPtr;
2ff057
2ff057
    if (headerIsEntry(sourcePkg->header, RPMTAG_NAME))
2ff057
	return;
2ff057
2ff057
    /* Only specific tags are added to the source package header */
2ff057
    headerCopyTags(spec->packages->header, sourcePkg->header, sourceTags);
2ff057
2ff057
    /* Add the build restrictions */
2ff057
    for (int i=0; i
2ff057
	rpmdsPutToHeader(sourcePkg->dependencies[i], sourcePkg->header);
2ff057
    }
2ff057
2ff057
    {
2ff057
	HeaderIterator hi = headerInitIterator(spec->buildRestrictions);
2ff057
	struct rpmtd_s td;
2ff057
	while (headerNext(hi, &td)) {
2ff057
	    if (rpmtdCount(&td) > 0) {
2ff057
		(void) headerPut(sourcePkg->header, &td, HEADERPUT_DEFAULT);
2ff057
	    }
2ff057
	    rpmtdFreeData(&td);
2ff057
	}
2ff057
	headerFreeIterator(hi);
2ff057
    }
2ff057
2ff057
    if (spec->BANames && spec->BACount > 0) {
2ff057
	headerPutStringArray(sourcePkg->header, RPMTAG_BUILDARCHS,
2ff057
		  spec->BANames, spec->BACount);
2ff057
    }
2ff057
2ff057
    /* Add tags for sources and patches */
2ff057
    for (srcPtr = spec->sources; srcPtr != NULL; srcPtr = srcPtr->next) {
2ff057
	if (srcPtr->flags & RPMBUILD_ISSOURCE) {
2ff057
	    headerPutString(sourcePkg->header, RPMTAG_SOURCE, srcPtr->source);
2ff057
	    if (srcPtr->flags & RPMBUILD_ISNO) {
2ff057
		headerPutUint32(sourcePkg->header, RPMTAG_NOSOURCE,
2ff057
				&srcPtr->num, 1);
2ff057
	    }
2ff057
	}
2ff057
	if (srcPtr->flags & RPMBUILD_ISPATCH) {
2ff057
	    headerPutString(sourcePkg->header, RPMTAG_PATCH, srcPtr->source);
2ff057
	    if (srcPtr->flags & RPMBUILD_ISNO) {
2ff057
		headerPutUint32(sourcePkg->header, RPMTAG_NOPATCH,
2ff057
				&srcPtr->num, 1);
2ff057
	    }
2ff057
	}
2ff057
    }
2ff057
}
2ff057
2ff057
/* Add extra provides to package.  */
2ff057
void addPackageProvides(Package pkg)
2ff057
{
2ff057
    const char *arch, *name;
2ff057
    char *evr, *isaprov;
2ff057
    rpmsenseFlags pflags = RPMSENSE_EQUAL;
2ff057
2ff057
    /* <name> = <evr> provide */
2ff057
    name = headerGetString(pkg->header, RPMTAG_NAME);
2ff057
    arch = headerGetString(pkg->header, RPMTAG_ARCH);
2ff057
    evr = headerGetAsString(pkg->header, RPMTAG_EVR);
2ff057
    addReqProv(pkg, RPMTAG_PROVIDENAME, name, evr, pflags, 0);
2ff057
2ff057
    /*
2ff057
     * <name>(<isa>) = <evr> provide
2ff057
     * FIXME: noarch needs special casing for now as BuildArch: noarch doesn't
2ff057
     * cause reading in the noarch macros :-/ 
2ff057
     */
2ff057
    isaprov = rpmExpand(name, "%{?_isa}", NULL);
2ff057
    if (!rstreq(arch, "noarch") && !rstreq(name, isaprov)) {
2ff057
	addReqProv(pkg, RPMTAG_PROVIDENAME, isaprov, evr, pflags, 0);
2ff057
    }
2ff057
    free(isaprov);
2ff057
    free(evr);
2ff057
}
2ff057
2ff057
static void addTargets(Package Pkgs)
2ff057
{
2ff057
    char *platform = rpmExpand("%{_target_platform}", NULL);
2ff057
    char *arch = rpmExpand("%{_target_cpu}", NULL);
2ff057
    char *os = rpmExpand("%{_target_os}", NULL);
2ff057
    char *optflags = rpmExpand("%{optflags}", NULL);
2ff057
2ff057
    for (Package pkg = Pkgs; pkg != NULL; pkg = pkg->next) {
2ff057
	headerPutString(pkg->header, RPMTAG_OS, os);
2ff057
	/* noarch subpackages already have arch set here, leave it alone */
2ff057
	if (!headerIsEntry(pkg->header, RPMTAG_ARCH)) {
2ff057
	    headerPutString(pkg->header, RPMTAG_ARCH, arch);
2ff057
	}
2ff057
	headerPutString(pkg->header, RPMTAG_PLATFORM, platform);
2ff057
	headerPutString(pkg->header, RPMTAG_OPTFLAGS, optflags);
2ff057
2ff057
	/* Add manual dependencies early for rpmspec etc to look at */
2ff057
	addPackageProvides(pkg);
2ff057
	for (int i=0; i
2ff057
	    rpmdsPutToHeader(pkg->dependencies[i], pkg->header);
2ff057
	}
2ff057
2ff057
	pkg->ds = rpmdsThis(pkg->header, RPMTAG_REQUIRENAME, RPMSENSE_EQUAL);
2ff057
    }
2ff057
    free(platform);
2ff057
    free(arch);
2ff057
    free(os);
2ff057
    free(optflags);
2ff057
}
2ff057
2ff057
rpmRC checkForEncoding(Header h, int addtag)
2ff057
{
2ff057
    rpmRC rc = RPMRC_OK;
2ff057
#if HAVE_ICONV
2ff057
    const char *encoding = "utf-8";
2ff057
    rpmTagVal tag;
2ff057
    iconv_t ic;
2ff057
    char *dest = NULL;
2ff057
    size_t destlen = 0;
2ff057
    int strict = rpmExpandNumeric("%{_invalid_encoding_terminates_build}");
2ff057
    HeaderIterator hi = headerInitIterator(h);
2ff057
2ff057
    ic = iconv_open(encoding, encoding);
2ff057
    if (ic == (iconv_t) -1) {
2ff057
	rpmlog(RPMLOG_WARNING,
2ff057
		_("encoding %s not supported by system\n"), encoding);
2ff057
	goto exit;
2ff057
    }
2ff057
2ff057
    while ((tag = headerNextTag(hi)) != RPMTAG_NOT_FOUND) {
2ff057
	struct rpmtd_s td;
2ff057
	const char *src = NULL;
2ff057
2ff057
	if (rpmTagGetClass(tag) != RPM_STRING_CLASS)
2ff057
	    continue;
2ff057
2ff057
	headerGet(h, tag, &td, (HEADERGET_RAW|HEADERGET_MINMEM));
2ff057
	while ((src = rpmtdNextString(&td)) != NULL) {
2ff057
	    size_t srclen = strlen(src);
2ff057
	    size_t outlen, inlen = srclen;
2ff057
	    char *out, *in = (char *) src;
2ff057
2ff057
	    if (destlen < srclen) {
2ff057
		destlen = srclen * 2;
2ff057
		dest = xrealloc(dest, destlen);
2ff057
	    }
2ff057
	    out = dest;
2ff057
	    outlen = destlen;
2ff057
2ff057
	    /* reset conversion state */
2ff057
	    iconv(ic, NULL, &inlen, &out, &outlen);
2ff057
2ff057
	    if (iconv(ic, &in, &inlen, &out, &outlen) == (size_t) -1) {
2ff057
		rpmlog(strict ? RPMLOG_ERR : RPMLOG_WARNING,
2ff057
			_("Package %s: invalid %s encoding in %s: %s - %s\n"),
2ff057
			headerGetString(h, RPMTAG_NAME),
2ff057
			encoding, rpmTagGetName(tag), src, strerror(errno));
2ff057
		rc = RPMRC_FAIL;
2ff057
	    }
2ff057
2ff057
	}
2ff057
	rpmtdFreeData(&td);
2ff057
    }
2ff057
2ff057
    /* Stomp "known good utf" mark in header if requested */
2ff057
    if (rc == RPMRC_OK && addtag)
2ff057
	headerPutString(h, RPMTAG_ENCODING, encoding);
2ff057
    if (!strict)
2ff057
	rc = RPMRC_OK;
2ff057
2ff057
exit:
2ff057
    if (ic != (iconv_t) -1)
2ff057
	iconv_close(ic);
2ff057
    headerFreeIterator(hi);
2ff057
    free(dest);
2ff057
#endif /* HAVE_ICONV */
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int parseEmpty(rpmSpec spec, int prevParsePart)
2ff057
{
2ff057
    int res = PART_ERROR;
2ff057
    int nextPart, rc;
2ff057
    char *line;
2ff057
2ff057
    line = spec->line + sizeof("%end") - 1;
2ff057
    SKIPSPACE(line);
2ff057
    if (line[0] != '\0') {
2ff057
	rpmlog(RPMLOG_ERR,
2ff057
	    _("line %d: %%end doesn't take any arguments: %s\n"),
2ff057
	    spec->lineNum, spec->line);
2ff057
	goto exit;
2ff057
    }
2ff057
2ff057
    if (prevParsePart == PART_EMPTY) {
2ff057
	rpmlog(RPMLOG_ERR,
2ff057
	    _("line %d: %%end not expected here, no section to close: %s\n"),
2ff057
	    spec->lineNum, spec->line);
2ff057
	goto exit;
2ff057
    }
2ff057
2ff057
    if ((rc = readLine(spec, STRIP_TRAILINGSPACE|STRIP_COMMENTS)) > 0) {
2ff057
	nextPart = PART_NONE;
2ff057
    } else if (rc < 0) {
2ff057
	goto exit;
2ff057
    } else {
2ff057
	while (! (nextPart = isPart(spec->line))) {
2ff057
	    line = spec->line;
2ff057
	    SKIPSPACE(line);
2ff057
2ff057
	    if (line[0] != '\0') {
2ff057
		rpmlog(RPMLOG_ERR,
2ff057
		    _("line %d doesn't belong to any section: %s\n"),
2ff057
		    spec->lineNum, spec->line);
2ff057
		goto exit;
2ff057
	    }
2ff057
	    if ((rc = readLine(spec, STRIP_TRAILINGSPACE|STRIP_COMMENTS)) > 0) {
2ff057
		nextPart = PART_NONE;
2ff057
		break;
2ff057
	    } else if (rc < 0) {
2ff057
		goto exit;
2ff057
	    }
2ff057
	}
2ff057
    }
2ff057
2ff057
    res = nextPart;
2ff057
2ff057
exit:
2ff057
    return res;
2ff057
}
2ff057
2ff057
static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags,
2ff057
			 const char *buildRoot, int recursing)
2ff057
{
2ff057
    int parsePart = PART_PREAMBLE;
2ff057
    int prevParsePart = PART_EMPTY;
2ff057
    int storedParsePart;
2ff057
    int initialPackage = 1;
2ff057
    rpmSpec spec;
2ff057
    
2ff057
    /* Set up a new Spec structure with no packages. */
2ff057
    spec = newSpec();
2ff057
2ff057
    spec->specFile = rpmGetPath(specFile, NULL);
2ff057
    pushOFI(spec, spec->specFile);
2ff057
    /* If buildRoot not specified, use default %{buildroot} */
2ff057
    if (buildRoot) {
2ff057
	spec->buildRoot = xstrdup(buildRoot);
2ff057
    } else {
2ff057
	spec->buildRoot = rpmGetPath("%{?buildroot:%{buildroot}}", NULL);
2ff057
    }
2ff057
    rpmPushMacro(NULL, "_docdir", NULL, "%{_defaultdocdir}", RMIL_SPEC);
2ff057
    rpmPushMacro(NULL, "_licensedir", NULL, "%{_defaultlicensedir}", RMIL_SPEC);
2ff057
    spec->recursing = recursing;
2ff057
    spec->flags = flags;
2ff057
2ff057
    /* All the parse*() functions expect to have a line pre-read */
2ff057
    /* in the spec's line buffer.  Except for parsePreamble(),   */
2ff057
    /* which handles the initial entry into a spec file.         */
2ff057
    
2ff057
    while (parsePart != PART_NONE) {
2ff057
	int goterror = 0;
2ff057
	storedParsePart = parsePart;
2ff057
	switch (parsePart) {
2ff057
	case PART_ERROR: /* fallthrough */
2ff057
	default:
2ff057
	    goterror = 1;
2ff057
	    break;
2ff057
	case PART_EMPTY:
2ff057
	    parsePart = parseEmpty(spec, prevParsePart);
2ff057
	    break;
2ff057
	case PART_PREAMBLE:
2ff057
	    parsePart = parsePreamble(spec, initialPackage);
2ff057
	    initialPackage = 0;
2ff057
	    break;
2ff057
	case PART_PREP:
2ff057
	    parsePart = parsePrep(spec);
2ff057
	    break;
2ff057
	case PART_BUILD:
2ff057
	case PART_INSTALL:
2ff057
	case PART_CHECK:
2ff057
	case PART_CLEAN:
2ff057
	    parsePart = parseBuildInstallClean(spec, parsePart);
2ff057
	    break;
2ff057
	case PART_CHANGELOG:
2ff057
	    parsePart = parseChangelog(spec);
2ff057
	    break;
2ff057
	case PART_DESCRIPTION:
2ff057
	    parsePart = parseDescription(spec);
2ff057
	    break;
2ff057
2ff057
	case PART_PRE:
2ff057
	case PART_POST:
2ff057
	case PART_PREUN:
2ff057
	case PART_POSTUN:
2ff057
	case PART_PRETRANS:
2ff057
	case PART_POSTTRANS:
2ff057
	case PART_VERIFYSCRIPT:
2ff057
	case PART_TRIGGERPREIN:
2ff057
	case PART_TRIGGERIN:
2ff057
	case PART_TRIGGERUN:
2ff057
	case PART_TRIGGERPOSTUN:
2ff057
	case PART_FILETRIGGERIN:
2ff057
	case PART_FILETRIGGERUN:
2ff057
	case PART_FILETRIGGERPOSTUN:
2ff057
	case PART_TRANSFILETRIGGERIN:
2ff057
	case PART_TRANSFILETRIGGERUN:
2ff057
	case PART_TRANSFILETRIGGERPOSTUN:
2ff057
	    parsePart = parseScript(spec, parsePart);
2ff057
	    break;
2ff057
2ff057
	case PART_FILES:
2ff057
	    parsePart = parseFiles(spec);
2ff057
	    break;
2ff057
2ff057
	case PART_POLICIES:
2ff057
	    parsePart = parsePolicies(spec);
2ff057
	    break;
2ff057
2ff057
	case PART_NONE:		/* XXX avoid gcc whining */
2ff057
	case PART_LAST:
2ff057
	case PART_BUILDARCHITECTURES:
2ff057
	    break;
2ff057
	}
2ff057
	prevParsePart = storedParsePart;
2ff057
2ff057
	if (goterror || parsePart >= PART_LAST) {
2ff057
	    goto errxit;
2ff057
	}
2ff057
2ff057
	if (parsePart == PART_BUILDARCHITECTURES) {
2ff057
	    int index;
2ff057
	    int x;
2ff057
2ff057
	    closeSpec(spec);
2ff057
2ff057
	    spec->BASpecs = xcalloc(spec->BACount, sizeof(*spec->BASpecs));
2ff057
	    index = 0;
2ff057
	    if (spec->BANames != NULL)
2ff057
	    for (x = 0; x < spec->BACount; x++) {
2ff057
2ff057
		/* Skip if not arch is not compatible. */
2ff057
		if (!rpmMachineScore(RPM_MACHTABLE_BUILDARCH, spec->BANames[x]))
2ff057
		    continue;
2ff057
		rpmPushMacro(NULL, "_target_cpu", NULL, spec->BANames[x], RMIL_RPMRC);
2ff057
		spec->BASpecs[index] = parseSpec(specFile, flags, buildRoot, 1);
2ff057
		if (spec->BASpecs[index] == NULL) {
2ff057
			spec->BACount = index;
2ff057
			goto errxit;
2ff057
		}
2ff057
		rpmPopMacro(NULL, "_target_cpu");
2ff057
		index++;
2ff057
	    }
2ff057
2ff057
	    spec->BACount = index;
2ff057
	    if (! index) {
2ff057
		rpmlog(RPMLOG_ERR,
2ff057
			_("No compatible architectures found for build\n"));
2ff057
		goto errxit;
2ff057
	    }
2ff057
2ff057
	    /*
2ff057
	     * Return the 1st child's fully parsed Spec structure.
2ff057
	     * The restart of the parse when encountering BuildArch
2ff057
	     * causes problems for "rpm -q --specfile". This is
2ff057
	     * still a hack because there may be more than 1 arch
2ff057
	     * specified (unlikely but possible.) There's also the
2ff057
	     * further problem that the macro context, particularly
2ff057
	     * %{_target_cpu}, disagrees with the info in the header.
2ff057
	     */
2ff057
	    if (spec->BACount >= 1) {
2ff057
		rpmSpec nspec = spec->BASpecs[0];
2ff057
		spec->BASpecs = _free(spec->BASpecs);
2ff057
		rpmSpecFree(spec);
2ff057
		spec = nspec;
2ff057
	    }
2ff057
2ff057
	    goto exit;
2ff057
	}
2ff057
    }
2ff057
2ff057
    if (spec->clean == NULL) {
2ff057
	char *body = rpmExpand("%{?buildroot: %{__rm} -rf %{buildroot}}", NULL);
2ff057
	spec->clean = newStringBuf();
2ff057
	appendLineStringBuf(spec->clean, body);
2ff057
	free(body);
2ff057
    }
2ff057
2ff057
    /* Check for description in each package */
2ff057
    for (Package pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
2ff057
	if (!headerIsEntry(pkg->header, RPMTAG_DESCRIPTION)) {
2ff057
	    rpmlog(RPMLOG_ERR, _("Package has no %%description: %s\n"),
2ff057
		   headerGetString(pkg->header, RPMTAG_NAME));
2ff057
	    goto errxit;
2ff057
	}
2ff057
    }
2ff057
2ff057
    /* Add arch, os and platform, self-provides etc for each package */
2ff057
    addTargets(spec->packages);
2ff057
2ff057
    /* Check for encoding in each package unless disabled */
2ff057
    if (!(spec->flags & RPMSPEC_NOUTF8)) {
2ff057
	int badenc = 0;
2ff057
	for (Package pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
2ff057
	    if (checkForEncoding(pkg->header, 0) != RPMRC_OK) {
2ff057
		badenc = 1;
2ff057
	    }
2ff057
	}
2ff057
	if (badenc)
2ff057
	    goto errxit;
2ff057
    }
2ff057
2ff057
    closeSpec(spec);
2ff057
exit:
2ff057
    /* Assemble source header from parsed components */
2ff057
    initSourceHeader(spec);
2ff057
2ff057
    return spec;
2ff057
2ff057
errxit:
2ff057
    rpmSpecFree(spec);
2ff057
    return NULL;
2ff057
}
2ff057
2ff057
rpmSpec rpmSpecParse(const char *specFile, rpmSpecFlags flags,
2ff057
		     const char *buildRoot)
2ff057
{
2ff057
    return parseSpec(specFile, flags, buildRoot, 0);
2ff057
}