rpm  4.5
parseSpec.c
Go to the documentation of this file.
1 
6 #include "system.h"
7 
8 #include <rpmio_internal.h>
9 #include <rpmbuild.h>
10 #include "rpmds.h"
11 #include "rpmts.h"
12 #include "debug.h"
13 
14 /*@access FD_t @*/ /* compared with NULL */
15 
18 /*@unchecked@*/
19 static struct PartRec {
20  int part;
21  int len;
22 /*@observer@*/ /*@null@*/
23  const char * token;
24 } partList[] = {
25  { PART_PREAMBLE, 0, "%package"},
26  { PART_PREP, 0, "%prep"},
27  { PART_BUILD, 0, "%build"},
28  { PART_INSTALL, 0, "%install"},
29  { PART_CHECK, 0, "%check"},
30  { PART_CLEAN, 0, "%clean"},
31  { PART_PREUN, 0, "%preun"},
32  { PART_POSTUN, 0, "%postun"},
33  { PART_PRETRANS, 0, "%pretrans"},
34  { PART_POSTTRANS, 0, "%posttrans"},
35  { PART_PRE, 0, "%pre"},
36  { PART_POST, 0, "%post"},
37  { PART_FILES, 0, "%files"},
38  { PART_CHANGELOG, 0, "%changelog"},
39  { PART_DESCRIPTION, 0, "%description"},
40  { PART_TRIGGERPOSTUN, 0, "%triggerpostun"},
41  { PART_TRIGGERPREIN, 0, "%triggerprein"},
42  { PART_TRIGGERUN, 0, "%triggerun"},
43  { PART_TRIGGERIN, 0, "%triggerin"},
44  { PART_TRIGGERIN, 0, "%trigger"},
45  { PART_VERIFYSCRIPT, 0, "%verifyscript"},
46  {0, 0, 0}
47 };
48 
51 static inline void initParts(struct PartRec *p)
52  /*@modifies p->len @*/
53 {
54  for (; p->token != NULL; p++)
55  p->len = strlen(p->token);
56 }
57 
58 rpmParseState isPart(const char *line)
59 {
60  struct PartRec *p;
61 
62 /*@-boundsread@*/
63  if (partList[0].len == 0)
65 /*@=boundsread@*/
66 
67  for (p = partList; p->token != NULL; p++) {
68  char c;
69  if (xstrncasecmp(line, p->token, p->len))
70  continue;
71 /*@-boundsread@*/
72  c = *(line + p->len);
73 /*@=boundsread@*/
74  if (c == '\0' || xisspace(c))
75  break;
76  }
77 
78  return (p->token ? p->part : PART_NONE);
79 }
80 
83 static int matchTok(const char *token, const char *line)
84  /*@*/
85 {
86  const char *b, *be = line;
87  size_t toklen = strlen(token);
88  int rc = 0;
89 
90 /*@-boundsread@*/
91  while ( *(b = be) != '\0' ) {
92  SKIPSPACE(b);
93  be = b;
94  SKIPNONSPACE(be);
95  if (be == b)
96  break;
97  if (toklen != (be-b) || xstrncasecmp(token, b, (be-b)))
98  continue;
99  rc = 1;
100  break;
101  }
102 /*@=boundsread@*/
103 
104  return rc;
105 }
106 
107 /*@-boundswrite@*/
108 void handleComments(char *s)
109 {
110  SKIPSPACE(s);
111  if (*s == '#')
112  *s = '\0';
113 }
114 /*@=boundswrite@*/
115 
118 static void forceIncludeFile(Spec spec, const char * fileName)
119  /*@modifies spec->fileStack @*/
120 {
121  OFI_t * ofi;
122 
123  ofi = newOpenFileInfo();
124  ofi->fileName = xstrdup(fileName);
125  ofi->next = spec->fileStack;
126  spec->fileStack = ofi;
127 }
128 
131 /*@-boundswrite@*/
132 static int copyNextLine(Spec spec, OFI_t *ofi, int strip)
133  /*@globals rpmGlobalMacroContext, h_errno,
134  fileSystem @*/
135  /*@modifies spec->nextline, spec->nextpeekc, spec->lbuf, spec->line,
136  ofi->readPtr,
137  rpmGlobalMacroContext, fileSystem @*/
138 {
139  char *last;
140  char ch;
141 
142  /* Restore 1st char in (possible) next line */
143  if (spec->nextline != NULL && spec->nextpeekc != '\0') {
144  *spec->nextline = spec->nextpeekc;
145  spec->nextpeekc = '\0';
146  }
147  /* Expand next line from file into line buffer */
148  if (!(spec->nextline && *spec->nextline)) {
149  int pc = 0, bc = 0, nc = 0;
150  char *from, *to, *p;
151  to = spec->lbufPtr ? spec->lbufPtr : spec->lbuf;
152  from = ofi->readPtr;
153  ch = ' ';
154  while (*from && ch != '\n')
155  ch = *to++ = *from++;
156 /*@-mods@*/
157  spec->lbufPtr = to;
158 /*@=mods@*/
159  *to++ = '\0';
160  ofi->readPtr = from;
161 
162  /* Check if we need another line before expanding the buffer. */
163  for (p = spec->lbuf; *p; p++) {
164  switch (*p) {
165  case '\\':
166  switch (*(p+1)) {
167  case '\n': p++, nc = 1; /*@innerbreak@*/ break;
168  case '\0': /*@innerbreak@*/ break;
169  default: p++; /*@innerbreak@*/ break;
170  }
171  /*@switchbreak@*/ break;
172  case '\n': nc = 0; /*@switchbreak@*/ break;
173  case '%':
174  switch (*(p+1)) {
175  case '{': p++, bc++; /*@innerbreak@*/ break;
176  case '(': p++, pc++; /*@innerbreak@*/ break;
177  case '%': p++; /*@innerbreak@*/ break;
178  }
179  /*@switchbreak@*/ break;
180  case '{': if (bc > 0) bc++; /*@switchbreak@*/ break;
181  case '}': if (bc > 0) bc--; /*@switchbreak@*/ break;
182  case '(': if (pc > 0) pc++; /*@switchbreak@*/ break;
183  case ')': if (pc > 0) pc--; /*@switchbreak@*/ break;
184  }
185  }
186 
187  /* If it doesn't, ask for one more line. We need a better
188  * error code for this. */
189  if (pc || bc || nc ) {
190 /*@-observertrans -readonlytrans@*/
191  spec->nextline = "";
192 /*@=observertrans =readonlytrans@*/
193  return RPMERR_UNMATCHEDIF;
194  }
195 /*@-mods@*/
196  spec->lbufPtr = spec->lbuf;
197 /*@=mods@*/
198 
199  /* Don't expand macros (eg. %define) in false branch of %if clause */
200  /* Also don't expand macros in %changelog where we set STRIP_NOEXPAND flag */
201  /* (first line is ommited, so if there is e.g. %date macro, it will be expanded */
202  if (!(strip & STRIP_NOEXPAND)) {
203  if (spec->readStack->reading &&
204  expandMacros(spec, spec->macros, spec->lbuf, sizeof(spec->lbuf))) {
205  rpmError(RPMERR_BADSPEC, _("line %d: %s\n"),
206  spec->lineNum, spec->lbuf);
207  return RPMERR_BADSPEC;
208  }
209  }
210  spec->nextline = spec->lbuf;
211  }
212 
213  /* Find next line in expanded line buffer */
214  spec->line = last = spec->nextline;
215  ch = ' ';
216  while (*spec->nextline && ch != '\n') {
217  ch = *spec->nextline++;
218  if (!xisspace(ch))
219  last = spec->nextline;
220  }
221 
222  /* Save 1st char of next line in order to terminate current line. */
223  if (*spec->nextline != '\0') {
224  spec->nextpeekc = *spec->nextline;
225  *spec->nextline = '\0';
226  }
227 
228  if (strip & STRIP_COMMENTS)
229  handleComments(spec->line);
230 
231  if (strip & STRIP_TRAILINGSPACE)
232  *last = '\0';
233 
234  return 0;
235 }
236 /*@=boundswrite@*/
237 
238 static const char *getAlternateArch(const char *arch)
239 {
240  const char *alternate_arch = NULL;
241  if (! strncmp("x86_64", arch, sizeof("x86_64")-1))
242  alternate_arch = "amd64";
243  else if (! strncmp("amd64", arch, sizeof("amd64")-1))
244  alternate_arch = "x86_64";
245  return alternate_arch;
246 }
247 
248 /*@-boundswrite@*/
249 int readLine(Spec spec, int strip)
250 {
251 #ifdef DYING
252  const char *arch;
253  const char *os;
254 #endif
255  char *s;
256  int match;
257  struct ReadLevelEntry *rl;
258  OFI_t *ofi = spec->fileStack;
259  int rc;
260 
261 retry:
262  /* Make sure the current file is open */
263  /*@-branchstate@*/
264  if (ofi->fd == NULL) {
265  ofi->fd = Fopen(ofi->fileName, "r.fpio");
266  if (ofi->fd == NULL || Ferror(ofi->fd)) {
267  /* XXX Fstrerror */
268  rpmError(RPMERR_BADSPEC, _("Unable to open %s: %s\n"),
269  ofi->fileName, Fstrerror(ofi->fd));
270  return RPMERR_BADSPEC;
271  }
272  spec->lineNum = ofi->lineNum = 0;
273  }
274  /*@=branchstate@*/
275 
276  /* Make sure we have something in the read buffer */
277  if (!(ofi->readPtr && *(ofi->readPtr))) {
278  /*@-type@*/ /* FIX: cast? */
279  FILE * f = fdGetFp(ofi->fd);
280  /*@=type@*/
281  if (f == NULL || !fgets(ofi->readBuf, BUFSIZ, f)) {
282  /* EOF */
283  if (spec->readStack->next) {
284  rpmError(RPMERR_UNMATCHEDIF, _("Unclosed %%if\n"));
285  return RPMERR_UNMATCHEDIF;
286  }
287 
288  /* remove this file from the stack */
289  spec->fileStack = ofi->next;
290  (void) Fclose(ofi->fd);
291  ofi->fileName = _free(ofi->fileName);
292  ofi = _free(ofi);
293 
294  /* only on last file do we signal EOF to caller */
295  ofi = spec->fileStack;
296  if (ofi == NULL)
297  return 1;
298 
299  /* otherwise, go back and try the read again. */
300  goto retry;
301  }
302  ofi->readPtr = ofi->readBuf;
303  ofi->lineNum++;
304  spec->lineNum = ofi->lineNum;
305  if (spec->sl) {
306  speclines sl = spec->sl;
307  if (sl->sl_nlines == sl->sl_nalloc) {
308  sl->sl_nalloc += 100;
309  sl->sl_lines = (char **) xrealloc(sl->sl_lines,
310  sl->sl_nalloc * sizeof(*(sl->sl_lines)));
311  }
312  sl->sl_lines[sl->sl_nlines++] = xstrdup(ofi->readBuf);
313  }
314  }
315 
316  /* Copy next file line into the spec line buffer */
317  if ((rc = copyNextLine(spec, ofi, strip)) != 0) {
318  if (rc == RPMERR_UNMATCHEDIF)
319  goto retry;
320  return rc;
321  }
322 
323  s = spec->line;
324  SKIPSPACE(s);
325 
326  match = -1;
327  if (! (strip & STRIP_NOEXPAND)) {
328  if (!spec->readStack->reading && !strncmp("%if", s, sizeof("%if")-1)) {
329  match = 0;
330  } else if (! strncmp("%ifarch", s, sizeof("%ifarch")-1)) {
331  const char *arch = rpmExpand("%{_target_cpu}", NULL);
332  const char *alternate_arch = getAlternateArch(arch);
333  s += 7;
334  match = matchTok(arch, s) || (alternate_arch && matchTok(alternate_arch, s));
335  arch = _free(arch);
336  } else if (! strncmp("%ifnarch", s, sizeof("%ifnarch")-1)) {
337  const char *arch = rpmExpand("%{_target_cpu}", NULL);
338  const char *alternate_arch = getAlternateArch(arch);
339  s += 8;
340  match = !matchTok(arch, s) && (!alternate_arch || !matchTok(alternate_arch, s));
341  arch = _free(arch);
342  } else if (! strncmp("%ifos", s, sizeof("%ifos")-1)) {
343  const char *os = rpmExpand("%{_target_os}", NULL);
344  s += 5;
345  match = matchTok(os, s);
346  os = _free(os);
347  } else if (! strncmp("%ifnos", s, sizeof("%ifnos")-1)) {
348  const char *os = rpmExpand("%{_target_os}", NULL);
349  s += 6;
350  match = !matchTok(os, s);
351  os = _free(os);
352  } else if (! strncmp("%if", s, sizeof("%if")-1)) {
353  s += 3;
354  match = parseExpressionBoolean(spec, s);
355  if (match < 0) {
357  _("%s:%d: parseExpressionBoolean returns %d\n"),
358  ofi->fileName, ofi->lineNum, match);
359  return RPMERR_BADSPEC;
360  }
361  } else if (! strncmp("%else", s, sizeof("%else")-1)) {
362  s += 5;
363  if (! spec->readStack->next) {
364  /* Got an else with no %if ! */
366  _("%s:%d: Got a %%else with no %%if\n"),
367  ofi->fileName, ofi->lineNum);
368  return RPMERR_UNMATCHEDIF;
369  }
370  spec->readStack->reading =
371  spec->readStack->next->reading && ! spec->readStack->reading;
372  spec->line[0] = '\0';
373  } else if (! strncmp("%endif", s, sizeof("%endif")-1)) {
374  s += 6;
375  if (! spec->readStack->next) {
376  /* Got an end with no %if ! */
378  _("%s:%d: Got a %%endif with no %%if\n"),
379  ofi->fileName, ofi->lineNum);
380  return RPMERR_UNMATCHEDIF;
381  }
382  rl = spec->readStack;
383  spec->readStack = spec->readStack->next;
384  free(rl);
385  spec->line[0] = '\0';
386  } else if (! strncmp("%include", s, sizeof("%include")-1)) {
387  char *fileName, *endFileName, *p;
388 
389  s += 8;
390  fileName = s;
391  if (! xisspace(*fileName)) {
392  rpmError(RPMERR_BADSPEC, _("malformed %%include statement\n"));
393  return RPMERR_BADSPEC;
394  }
395  SKIPSPACE(fileName);
396  endFileName = fileName;
397  SKIPNONSPACE(endFileName);
398  p = endFileName;
399  SKIPSPACE(p);
400  if (*p != '\0') {
401  rpmError(RPMERR_BADSPEC, _("malformed %%include statement\n"));
402  return RPMERR_BADSPEC;
403  }
404  *endFileName = '\0';
405 
406  forceIncludeFile(spec, fileName);
407 
408  ofi = spec->fileStack;
409  goto retry;
410  }
411  }
412 
413  if (match != -1) {
414  rl = xmalloc(sizeof(*rl));
415  rl->reading = spec->readStack->reading && match;
416  rl->next = spec->readStack;
417  spec->readStack = rl;
418  spec->line[0] = '\0';
419  }
420 
421  if (! spec->readStack->reading) {
422  spec->line[0] = '\0';
423  }
424 
425  /*@-compmempass@*/ /* FIX: spec->readStack->next should be dependent */
426  return 0;
427  /*@=compmempass@*/
428 }
429 /*@=boundswrite@*/
430 
431 void closeSpec(Spec spec)
432 {
433  OFI_t *ofi;
434 
435  while (spec->fileStack) {
436  ofi = spec->fileStack;
437  spec->fileStack = spec->fileStack->next;
438  if (ofi->fd) (void) Fclose(ofi->fd);
439  ofi->fileName = _free(ofi->fileName);
440  ofi = _free(ofi);
441  }
442 }
443 
444 /*@-redecl@*/
445 /*@unchecked@*/
446 extern int noLang; /* XXX FIXME: pass as arg */
447 /*@=redecl@*/
448 
449 /*@todo Skip parse recursion if os is not compatible. @*/
450 /*@-boundswrite@*/
451 int parseSpec(rpmts ts, const char *specFile, const char *rootURL,
452  int recursing, const char *passPhrase,
453  char *cookie, int anyarch, int force, int verify)
454 {
455  rpmParseState parsePart = PART_PREAMBLE;
456  int initialPackage = 1;
457  Package pkg;
458  Spec spec;
459 
460  /* Set up a new Spec structure with no packages. */
461  spec = newSpec();
462 
463  /*
464  * Note: rpmGetPath should guarantee a "canonical" path. That means
465  * that the following pathologies should be weeded out:
466  * //bin//sh
467  * //usr//bin/
468  * /.././../usr/../bin//./sh (XXX FIXME: dots not handled yet)
469  */
470  spec->specFile = rpmGetPath(specFile, NULL);
471  spec->fileStack = newOpenFileInfo();
472  spec->fileStack->fileName = xstrdup(spec->specFile);
473 
474  spec->recursing = recursing;
475  spec->anyarch = anyarch;
476  spec->force = force;
477 
478  if (rootURL)
479  spec->rootURL = xstrdup(rootURL);
480  if (passPhrase)
481  spec->passPhrase = xstrdup(passPhrase);
482  if (cookie)
483  spec->cookie = xstrdup(cookie);
484 
485  spec->timeCheck = rpmExpandNumeric("%{_timecheck}");
486 
487  /* XXX %_docdir should be set somewhere else. */
488  addMacro(NULL, "_docdir", NULL, "%{_defaultdocdir}", RMIL_SPEC);
489 
490  /* All the parse*() functions expect to have a line pre-read */
491  /* in the spec's line buffer. Except for parsePreamble(), */
492  /* which handles the initial entry into a spec file. */
493 
494  /*@-infloops@*/ /* LCL: parsePart is modified @*/
495  while (parsePart < PART_LAST && parsePart != PART_NONE) {
496  switch (parsePart) {
497  case PART_PREAMBLE:
498  parsePart = parsePreamble(spec, initialPackage);
499  initialPackage = 0;
500  /*@switchbreak@*/ break;
501  case PART_PREP:
502  parsePart = parsePrep(spec, verify);
503  /*@switchbreak@*/ break;
504  case PART_BUILD:
505  case PART_INSTALL:
506  case PART_CHECK:
507  case PART_CLEAN:
508  parsePart = parseBuildInstallClean(spec, parsePart);
509  /*@switchbreak@*/ break;
510  case PART_CHANGELOG:
511  parsePart = parseChangelog(spec);
512  /*@switchbreak@*/ break;
513  case PART_DESCRIPTION:
514  parsePart = parseDescription(spec);
515  /*@switchbreak@*/ break;
516 
517  case PART_PRE:
518  case PART_POST:
519  case PART_PREUN:
520  case PART_POSTUN:
521  case PART_PRETRANS:
522  case PART_POSTTRANS:
523  case PART_VERIFYSCRIPT:
524  case PART_TRIGGERPREIN:
525  case PART_TRIGGERIN:
526  case PART_TRIGGERUN:
527  case PART_TRIGGERPOSTUN:
528  parsePart = parseScript(spec, parsePart);
529  /*@switchbreak@*/ break;
530 
531  case PART_FILES:
532  parsePart = parseFiles(spec);
533  /*@switchbreak@*/ break;
534 
535  case PART_NONE: /* XXX avoid gcc whining */
536  case PART_LAST:
538  /*@switchbreak@*/ break;
539  }
540 
541  if (parsePart >= PART_LAST) {
542  spec = freeSpec(spec);
543  return parsePart;
544  }
545 
546  if (parsePart == PART_BUILDARCHITECTURES) {
547  int index;
548  int x;
549 
550  closeSpec(spec);
551 
552  /* LCL: sizeof(spec->BASpecs[0]) -nullderef whine here */
553  spec->BASpecs = xcalloc(spec->BACount, sizeof(*spec->BASpecs));
554  index = 0;
555  if (spec->BANames != NULL)
556  for (x = 0; x < spec->BACount; x++) {
557 
558  /* XXX DIEDIEDIE: filter irrelevant platforms here. */
559 
560  /* XXX there's more to do than set the macro. */
561  addMacro(NULL, "_target_cpu", NULL, spec->BANames[x], RMIL_RPMRC);
562  spec->BASpecs[index] = NULL;
563  if (parseSpec(ts, specFile, spec->rootURL, 1,
564  passPhrase, cookie, anyarch, force, verify)
565  || (spec->BASpecs[index] = rpmtsSetSpec(ts, NULL)) == NULL)
566  {
567  spec->BACount = index;
568 /*@-nullstate@*/
569  spec = freeSpec(spec);
570  return RPMERR_BADSPEC;
571 /*@=nullstate@*/
572  }
573 
574  /* XXX there's more to do than delete the macro. */
575  delMacro(NULL, "_target_cpu");
576  index++;
577  }
578 
579  spec->BACount = index;
580  if (! index) {
582  _("No compatible architectures found for build\n"));
583 /*@-nullstate@*/
584  spec = freeSpec(spec);
585  return RPMERR_BADSPEC;
586 /*@=nullstate@*/
587  }
588 
589  /*
590  * Return the 1st child's fully parsed Spec structure.
591  * The restart of the parse when encountering BuildArch
592  * causes problems for "rpm -q --specfile". This is
593  * still a hack because there may be more than 1 arch
594  * specified (unlikely but possible.) There's also the
595  * further problem that the macro context, particularly
596  * %{_target_cpu}, disagrees with the info in the header.
597  */
598  /*@-branchstate@*/
599  if (spec->BACount >= 1) {
600  Spec nspec = spec->BASpecs[0];
601  spec->BASpecs = _free(spec->BASpecs);
602  spec = freeSpec(spec);
603  spec = nspec;
604  }
605  /*@=branchstate@*/
606 
607  (void) rpmtsSetSpec(ts, spec);
608  return 0;
609  }
610  }
611  /*@=infloops@*/ /* LCL: parsePart is modified @*/
612 
613  /* Check for description in each package and add arch and os */
614  {
615  const char *platform = rpmExpand("%{_target_platform}", NULL);
616  const char *arch = rpmExpand("%{_target_cpu}", NULL);
617  const char *os = rpmExpand("%{_target_os}", NULL);
618 
619  for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
621  const char * name;
622  (void) headerNVR(pkg->header, &name, NULL, NULL);
623  rpmError(RPMERR_BADSPEC, _("Package has no %%description: %s\n"),
624  name);
625  spec = freeSpec(spec);
626  return RPMERR_BADSPEC;
627  }
628 
629  (void) headerAddEntry(pkg->header, RPMTAG_OS, RPM_STRING_TYPE, os, 1);
630  (void) headerAddEntry(pkg->header, RPMTAG_ARCH,
631  RPM_STRING_TYPE, arch, 1);
633  RPM_STRING_TYPE, platform, 1);
634 
636 
637  }
638 
639  platform = _free(platform);
640  arch = _free(arch);
641  os = _free(os);
642  }
643 
644  closeSpec(spec);
645  (void) rpmtsSetSpec(ts, spec);
646 
647  return 0;
648 }
649 /*@=boundswrite@*/