/* 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 #endif #include #include #include #include #include #include #include #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}, { "==", 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 */ 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 , or simply . */ /* 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"); }