846e992a7ceaf125229ffd610136ece33a196197
[platform/upstream/rpm.git] / rpmio / macro.c
1 #include "system.h"
2
3 static int _debug = 0;
4
5 #include <assert.h>
6 #include <stdarg.h>
7
8 #if !defined(isblank)
9 #define isblank(_c)     ((_c) == ' ' || (_c) == '\t')
10 #endif
11 #define iseol(_c)       ((_c) == '\n' || (_c) == '\r')
12
13 #define STREQ(_t, _f, _fn)      ((_fn) == (sizeof(_t)-1) && !strncmp((_t), (_f), (_fn)))
14 #define FREE(_x)        { if (_x) free((void *)_x); (_x) = NULL; }
15
16 #ifdef DEBUG_MACROS
17 #include <sys/types.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #define rpmError fprintf
25 #define RPMERR_BADSPEC stderr
26 #undef  _
27 #define _(x)    x
28
29 #define vmefail()               (exit(1), NULL)
30 #define xfree(_p)               free((void *)_p)
31 #define urlPath(_xr, _r)        *(_r) = (_xr)
32
33 typedef FILE * FD_t;
34 #define Fopen(_path, _fmode)    fopen(_path, "r");
35 #define Ferror                  ferror
36 #define Fstrerror(_fd)          strerror(errno)
37 #define Fread                   fread
38 #define Fclose                  fclose
39
40 #else
41
42 #include <rpmlib.h>
43 #include <rpmio_internal.h>
44
45 #endif
46
47 #include <rpmmacro.h>
48
49 struct MacroContext rpmGlobalMacroContext;
50 struct MacroContext rpmCLIMacroContext;
51
52 typedef struct MacroBuf {
53         const char *s;          /* text to expand */
54         char *t;                /* expansion buffer */
55         size_t nb;              /* no. bytes remaining in expansion buffer */
56         int depth;              /* current expansion depth */
57         int macro_trace;        /* pre-print macro to expand? */
58         int expand_trace;       /* post-print macro expansion? */
59         void *spec;             /* (future) %file expansion info */
60         MacroContext *mc;
61 } MacroBuf;
62
63 #define SAVECHAR(_mb, _c) { *(_mb)->t = (_c), (_mb)->t++, (_mb)->nb--; }
64
65 static int expandMacro(MacroBuf *mb);
66
67 #define MAX_MACRO_DEPTH 16
68 int max_macro_depth = MAX_MACRO_DEPTH;
69
70 #ifdef  DEBUG_MACROS
71 int print_macro_trace = 0;
72 int print_expand_trace = 0;
73 #else
74 int print_macro_trace = 0;
75 int print_expand_trace = 0;
76 #endif
77
78 #define MACRO_CHUNK_SIZE        16
79
80 /* =============================================================== */
81
82 static int
83 compareMacroName(const void *ap, const void *bp)
84 {
85         MacroEntry *ame = *((MacroEntry **)ap);
86         MacroEntry *bme = *((MacroEntry **)bp);
87
88         if (ame == NULL && bme == NULL)
89                 return 0;
90         if (ame == NULL)
91                 return 1;
92         if (bme == NULL)
93                 return -1;
94         return strcmp(ame->name, bme->name);
95 }
96
97 static void
98 expandMacroTable(MacroContext *mc)
99 {
100         if (mc->macroTable == NULL) {
101                 mc->macrosAllocated = MACRO_CHUNK_SIZE;
102                 mc->macroTable = (MacroEntry **)
103                     xmalloc(sizeof(*(mc->macroTable)) * mc->macrosAllocated);
104                 mc->firstFree = 0;
105         } else {
106                 mc->macrosAllocated += MACRO_CHUNK_SIZE;
107                 mc->macroTable = (MacroEntry **)
108                     xrealloc(mc->macroTable, sizeof(*(mc->macroTable)) *
109                                 mc->macrosAllocated);
110         }
111         memset(&mc->macroTable[mc->firstFree], 0, MACRO_CHUNK_SIZE * sizeof(*(mc->macroTable)));
112 }
113
114 static void
115 sortMacroTable(MacroContext *mc)
116 {
117         int i;
118
119         qsort(mc->macroTable, mc->firstFree, sizeof(*(mc->macroTable)),
120                 compareMacroName);
121
122         /* Empty pointers are now at end of table. Reset first free index. */
123         for (i = 0; i < mc->firstFree; i++) {
124                 if (mc->macroTable[i] != NULL)
125                         continue;
126                 mc->firstFree = i;
127                 break;
128         }
129 }
130
131 void
132 rpmDumpMacroTable(MacroContext * mc, FILE * fp)
133 {
134         int i;
135         int nempty = 0;
136         int nactive = 0;
137
138         if (mc == NULL)
139                 mc = &rpmGlobalMacroContext;
140         if (fp == NULL)
141                 fp = stderr;
142     
143         fprintf(fp, "========================\n");
144         for (i = 0; i < mc->firstFree; i++) {
145                 MacroEntry *me;
146                 if ((me = mc->macroTable[i]) == NULL) {
147                         /* XXX this should never happen */
148                         nempty++;
149                         continue;
150                 }
151                 fprintf(fp, "%3d%c %s", me->level,
152                         (me->used > 0 ? '=' : ':'), me->name);
153                 if (me->opts && *me->opts)
154                         fprintf(fp, "(%s)", me->opts);
155                 if (me->body && *me->body)
156                         fprintf(fp, "\t%s", me->body);
157                 fprintf(fp, "\n");
158                 nactive++;
159         }
160         fprintf(fp, _("======================== active %d empty %d\n"),
161                 nactive, nempty);
162 }
163
164 static MacroEntry **
165 findEntry(MacroContext *mc, const char *name, size_t namelen)
166 {
167         MacroEntry keybuf, *key, **ret;
168         char namebuf[1024];
169
170         if (mc == NULL)
171                 mc = &rpmGlobalMacroContext;
172         if (! mc->firstFree)
173                 return NULL;
174
175         if (namelen > 0) {
176                 strncpy(namebuf, name, namelen);
177                 namebuf[namelen] = '\0';
178                 name = namebuf;
179         }
180     
181         key = &keybuf;
182         memset(key, 0, sizeof(*key));
183         key->name = (char *)name;
184         ret = (MacroEntry **)bsearch(&key, mc->macroTable, mc->firstFree,
185                         sizeof(*(mc->macroTable)), compareMacroName);
186         /* XXX TODO: find 1st empty slot and return that */
187         return ret;
188 }
189
190 /* =============================================================== */
191
192 /* fgets analogue that reads \ continuations. Last newline always trimmed. */
193
194 static char *
195 rdcl(char *buf, size_t size, FD_t fd, int escapes)
196 {
197         char *q = buf;
198         size_t nb = 0;
199         size_t nread = 0;
200
201         *q = '\0';
202         do {
203                 /* read next line */
204                 if (fgets(q, size, (FILE *)fdGetFp(fd)) == NULL)
205                         break;
206                 nb = strlen(q);
207                 nread += nb;
208                 for (q += nb - 1; nb > 0 && iseol(*q); q--)
209                         nb--;
210                 if (!(nb > 0 && *q == '\\')) {  /* continue? */
211                         *(++q) = '\0';          /* trim trailing \r, \n */
212                         break;
213                 }
214                 if (escapes) {                  /* copy escape too */
215                         q++;
216                         nb++;
217                 }
218                 size -= nb;
219                 if (*q == '\r')                 /* XXX avoid \r madness */
220                         *q = '\n';
221                 *(++q) = '\0';                  /* next char in buf */
222         } while (size > 0);
223         return (nread > 0 ? buf : NULL);
224 }
225
226 /* Return text between pl and matching pr */
227
228 static const char *
229 matchchar(const char *p, char pl, char pr)
230 {
231         int lvl = 0;
232         char c;
233
234         while ((c = *p++) != '\0') {
235                 if (c == '\\') {                /* Ignore escaped chars */
236                         p++;
237                         continue;
238                 }
239                 if (c == pr) {
240                         if (--lvl <= 0) return --p;
241                 } else if (c == pl)
242                         lvl++;
243         }
244         return (const char *)NULL;
245 }
246
247 static void
248 printMacro(MacroBuf *mb, const char *s, const char *se)
249 {
250         const char *senl;
251         const char *ellipsis;
252         int choplen;
253
254         if (s >= se) {  /* XXX just in case */
255                 fprintf(stderr, _("%3d>%*s(empty)"), mb->depth,
256                         (2 * mb->depth + 1), "");
257                 return;
258         }
259
260         if (s[-1] == '{')
261                 s--;
262
263         /* Print only to first end-of-line (or end-of-string). */
264         for (senl = se; *senl && !iseol(*senl); senl++)
265                 ;
266
267         /* Limit trailing non-trace output */
268         choplen = 61 - (2 * mb->depth);
269         if ((senl - s) > choplen) {
270                 senl = s + choplen;
271                 ellipsis = "...";
272         } else
273                 ellipsis = "";
274
275         /* Substitute caret at end-of-macro position */
276         fprintf(stderr, "%3d>%*s%%%.*s^", mb->depth,
277                 (2 * mb->depth + 1), "", (int)(se - s), s);
278         if (se[1] != '\0' && (senl - (se+1)) > 0)
279                 fprintf(stderr, "%-.*s%s", (int)(senl - (se+1)), se+1, ellipsis);
280         fprintf(stderr, "\n");
281 }
282
283 static void
284 printExpansion(MacroBuf *mb, const char *t, const char *te)
285 {
286         const char *ellipsis;
287         int choplen;
288
289         if (!(te > t)) {
290                 fprintf(stderr, _("%3d<%*s(empty)\n"), mb->depth, (2 * mb->depth + 1), "");
291                 return;
292         }
293
294         /* Shorten output which contains newlines */
295         while (te > t && iseol(te[-1]))
296                 te--;
297         ellipsis = "";
298         if (mb->depth > 0) {
299                 const char *tenl;
300
301                 /* Skip to last line of expansion */
302                 while ((tenl = strchr(t, '\n')) && tenl < te)
303                         t = ++tenl;
304
305                 /* Limit expand output */
306                 choplen = 61 - (2 * mb->depth);
307                 if ((te - t) > choplen) {
308                         te = t + choplen;
309                         ellipsis = "...";
310                 }
311         }
312
313         fprintf(stderr, "%3d<%*s", mb->depth, (2 * mb->depth + 1), "");
314         if (te > t)
315                 fprintf(stderr, "%.*s%s", (int)(te - t), t, ellipsis);
316         fprintf(stderr, "\n");
317 }
318
319 #define SKIPBLANK(_s, _c)       \
320         while (((_c) = *(_s)) && isblank(_c)) \
321                 (_s)++;
322
323 #define SKIPNONBLANK(_s, _c)    \
324         while (((_c) = *(_s)) && !(isblank(_c) || iseol(_c))) \
325                 (_s)++;
326
327 #define COPYNAME(_ne, _s, _c)   \
328     {   SKIPBLANK(_s,_c);       \
329         while(((_c) = *(_s)) && (isalnum(_c) || (_c) == '_')) \
330                 *(_ne)++ = *(_s)++; \
331         *(_ne) = '\0';          \
332     }
333
334 #define COPYOPTS(_oe, _s, _c)   \
335     {   while(((_c) = *(_s)) && (_c) != ')') \
336                 *(_oe)++ = *(_s)++; \
337         *(_oe) = '\0';          \
338     }
339
340 #define COPYBODY(_be, _s, _c)   \
341     {   while(((_c) = *(_s)) && !iseol(_c)) { \
342                 if ((_c) == '\\') \
343                         (_s)++; \
344                 *(_be)++ = *(_s)++; \
345         }                       \
346         *(_be) = '\0';          \
347     }
348
349 /* Save source and expand field into target */
350 static int
351 expandT(MacroBuf *mb, const char *f, size_t flen)
352 {
353         char *sbuf;
354         const char *s = mb->s;
355         int rc;
356
357         sbuf = alloca(flen + 1);
358         memset(sbuf, 0, (flen + 1));
359
360         strncpy(sbuf, f, flen);
361         sbuf[flen] = '\0';
362         mb->s = sbuf;
363         rc = expandMacro(mb);
364         mb->s = s;
365         return rc;
366 }
367
368 #if 0
369 /* Save target and expand sbuf into target */
370 static int
371 expandS(MacroBuf *mb, char *tbuf, size_t tbuflen)
372 {
373         const char *t = mb->t;
374         size_t nb = mb->nb;
375         int rc;
376
377         mb->t = tbuf;
378         mb->nb = tbuflen;
379         rc = expandMacro(mb);
380         mb->t = t;
381         mb->nb = nb;
382         return rc;
383 }
384 #endif
385
386 static int
387 expandU(MacroBuf *mb, char *u, size_t ulen)
388 {
389         const char *s = mb->s;
390         char *t = mb->t;
391         size_t nb = mb->nb;
392         char *tbuf;
393         int rc;
394
395         tbuf = alloca(ulen + 1);
396         memset(tbuf, 0, (ulen + 1));
397
398         mb->s = u;
399         mb->t = tbuf;
400         mb->nb = ulen;
401         rc = expandMacro(mb);
402
403         tbuf[ulen] = '\0';      /* XXX just in case */
404         if (ulen > mb->nb)
405                 strncpy(u, tbuf, (ulen - mb->nb + 1));
406
407         mb->s = s;
408         mb->t = t;
409         mb->nb = nb;
410
411         return rc;
412 }
413
414 static int
415 doShellEscape(MacroBuf *mb, const char *cmd, size_t clen)
416 {
417         char pcmd[BUFSIZ];
418         FILE *shf;
419         int rc;
420         int c;
421
422         strncpy(pcmd, cmd, clen);
423         pcmd[clen] = '\0';
424         rc = expandU(mb, pcmd, sizeof(pcmd));
425         if (rc)
426                 return rc;
427
428         if ((shf = popen(pcmd, "r")) == NULL)
429                 return 1;
430         while(mb->nb > 0 && (c = fgetc(shf)) != EOF)
431                 SAVECHAR(mb, c);
432         pclose(shf);
433
434         /* XXX delete trailing \r \n */
435         while (iseol(mb->t[-1])) {
436                 *(mb->t--) = '\0';
437                 mb->nb++;
438         }
439         return 0;
440 }
441
442 static const char *
443 doDefine(MacroBuf *mb, const char *se, int level, int expandbody)
444 {
445         const char *s = se;
446         char buf[BUFSIZ], *n = buf, *ne = n;
447         char *o = NULL, *oe;
448         char *b, *be;
449         int c;
450         int oc = ')';
451
452         /* Copy name */
453         COPYNAME(ne, s, c);
454
455         /* Copy opts (if present) */
456         oe = ne + 1;
457         if (*s == '(') {
458                 s++;    /* skip ( */
459                 o = oe;
460                 COPYOPTS(oe, s, oc);
461                 s++;    /* skip ) */
462         }
463
464         /* Copy body, skipping over escaped newlines */
465         b = be = oe + 1;
466         SKIPBLANK(s, c);
467         if (c == '{') { /* XXX permit silent {...} grouping */
468                 if ((se = matchchar(s, c, '}')) == NULL) {
469                         rpmError(RPMERR_BADSPEC, _("Macro %%%s has unterminated body"), n);
470                         se = s; /* XXX W2DO? */
471                         return se;
472                 }
473                 s++;    /* XXX skip { */
474                 strncpy(b, s, (se - s));
475                 b[se - s] = '\0';
476                 be += strlen(b);
477                 se++;   /* XXX skip } */
478                 s = se; /* move scan forward */
479         } else {        /* otherwise free-field */
480                 COPYBODY(be, s, c);
481
482                 /* Trim trailing blanks/newlines */
483                 while (--be >= b && (c = *be) && (isblank(c) || iseol(c)))
484                         ;
485                 *(++be) = '\0'; /* one too far */
486         }
487
488         /* Move scan over body */
489         while (iseol(*s))
490                 s++;
491         se = s;
492
493         /* Names must start with alphabetic or _ and be at least 3 chars */
494         if (!((c = *n) && (isalpha(c) || c == '_') && (ne - n) > 2)) {
495                 rpmError(RPMERR_BADSPEC, _("Macro %%%s has illegal name (%%define)"), n);
496                 return se;
497         }
498
499         /* Options must be terminated with ')' */
500         if (o && oc != ')') {
501                 rpmError(RPMERR_BADSPEC, _("Macro %%%s has unterminated opts"), n);
502                 return se;
503         }
504
505         if ((be - b) < 1) {
506                 rpmError(RPMERR_BADSPEC, _("Macro %%%s has empty body"), n);
507                 return se;
508         }
509
510         if (expandbody && expandU(mb, b, (&buf[sizeof(buf)] - b))) {
511                 rpmError(RPMERR_BADSPEC, _("Macro %%%s failed to expand"), n);
512                 return se;
513         }
514
515         addMacro(mb->mc, n, o, b, (level - 1));
516
517         return se;
518 }
519
520 static const char *
521 doUndefine(MacroContext *mc, const char *se)
522 {
523         const char *s = se;
524         char buf[BUFSIZ], *n = buf, *ne = n;
525         int c;
526
527         COPYNAME(ne, s, c);
528
529         /* Move scan over body */
530         while (iseol(*s))
531                 s++;
532         se = s;
533
534         /* Names must start with alphabetic or _ and be at least 3 chars */
535         if (!((c = *n) && (isalpha(c) || c == '_') && (ne - n) > 2)) {
536                 rpmError(RPMERR_BADSPEC, _("Macro %%%s has illegal name (%%undefine)"), n);
537                 return se;
538         }
539
540         delMacro(mc, n);
541
542         return se;
543 }
544
545 #ifdef  DYING
546 static void
547 dumpME(const char *msg, MacroEntry *me)
548 {
549         if (msg)
550                 fprintf(stderr, "%s", msg);
551         fprintf(stderr, "\tme %p", me);
552         if (me)
553                 fprintf(stderr,"\tname %p(%s) prev %p",
554                         me->name, me->name, me->prev);
555         fprintf(stderr, "\n");
556 }
557 #endif
558
559 static void
560 pushMacro(MacroEntry **mep, const char *n, const char *o, const char *b, int level)
561 {
562         MacroEntry *prev = (*mep ? *mep : NULL);
563         MacroEntry *me = (MacroEntry *) xmalloc(sizeof(*me));
564
565         me->prev = prev;
566         me->name = (prev ? prev->name : xstrdup(n));
567         me->opts = (o ? xstrdup(o) : NULL);
568         me->body = xstrdup(b ? b : "");
569         me->used = 0;
570         me->level = level;
571         *mep = me;
572 }
573
574 static void
575 popMacro(MacroEntry **mep)
576 {
577         MacroEntry *me = (*mep ? *mep : NULL);
578
579         if (me) {
580                 /* XXX cast to workaround const */
581                 if ((*mep = me->prev) == NULL)
582                         FREE(me->name);
583                 FREE(me->opts);
584                 FREE(me->body);
585                 FREE(me);
586         }
587 }
588
589 static void
590 freeArgs(MacroBuf *mb)
591 {
592         MacroContext *mc = mb->mc;
593         int ndeleted = 0;
594         int i;
595
596         /* Delete dynamic macro definitions */
597         for (i = 0; i < mc->firstFree; i++) {
598                 MacroEntry **mep, *me;
599                 int skiptest = 0;
600                 mep = &mc->macroTable[i];
601                 me = *mep;
602
603                 if (me == NULL)         /* XXX this should never happen */
604                         continue;
605                 if (me->level < mb->depth)
606                         continue;
607                 if (strlen(me->name) == 1 && strchr("#*0", *me->name)) {
608                         if (*me->name == '*' && me->used > 0)
609                                 skiptest = 1;
610                         /* XXX skip test for %# %* %0 */
611                 } else if (!skiptest && me->used <= 0) {
612 #if NOTYET
613                         rpmError(RPMERR_BADSPEC, _("Macro %%%s (%s) was not used below level %d"),
614                                 me->name, me->body, me->level);
615 #endif
616                 }
617                 popMacro(mep);
618                 if (!(mep && *mep))
619                         ndeleted++;
620         }
621
622         /* If any deleted macros, sort macro table */
623         if (ndeleted)
624                 sortMacroTable(mc);
625 }
626
627 static const char *
628 grabArgs(MacroBuf *mb, const MacroEntry *me, const char *se, char lastc)
629 {
630     char buf[BUFSIZ], *b, *be;
631     char aname[16];
632     const char *opts, *o;
633     int argc = 0;
634     const char **argv;
635     int optc = 0;
636     const char **optv;
637     int opte;
638     int c;
639     int saveoptind;     /* XXX optind must be saved on non-linux */
640
641     /* Copy macro name as argv[0] */
642     argc = 0;
643     b = be = buf;
644     strcpy(b, me->name);
645     be += strlen(b);
646     *be = '\0';
647     argc++;     /* XXX count argv[0] */
648
649     addMacro(mb->mc, "0", NULL, b, mb->depth);
650     
651     /* Copy args into buf until lastc */
652     *be++ = ' ';
653     b = be;     /* Save beginning of args */
654     while ((c = *se) != 0) {
655         char *a;
656         se++;
657         if (c == lastc)
658                 break;
659         if (isblank(c))
660                 continue;
661         if (argc > 1)
662                 *be++ = ' ';
663         a = be;
664         while (!(isblank(c) || c == lastc)) {
665                 *be++ = c;
666                 if ((c = *se) == '\0')
667                         break;
668                 se++;
669         }
670         *be = '\0';
671         argc++;
672     }
673
674 /*
675  * The macro %* analoguous to the shell's $* means "Pass all non-macro
676  * parameters." Consequently, there needs to be a macro that means "Pass all
677  * (including macro parameters) options". This is useful for verifying
678  * parameters during expansion and yet transparently passing all parameters
679  * through for higher level processing (e.g. %description and/or %setup).
680  * This is the (potential) justification for %{**} ...
681  */
682     /* Add unexpanded args as macro */
683     addMacro(mb->mc, "**", NULL, b, mb->depth);
684
685 #ifdef NOTYET
686     /* XXX if macros can be passed as args ... */
687     expandU(mb, buf, sizeof(buf));
688 #endif
689
690     /* Build argv array */
691     argv = (const char **)alloca((argc + 1) * sizeof(char *));
692     b = be = buf;
693     for (c = 0; c < argc; c++) {
694         b = be;
695         if ((be = strchr(b, ' ')) == NULL)
696                 be = b + strlen(b);
697         *be++ = '\0';
698         argv[c] = b;
699     }
700     argv[argc] = NULL;
701
702     opts = me->opts;
703
704     /* First count number of options ... */
705     saveoptind = optind;        /* XXX optind must be saved on non-linux */
706     optc = 0;
707     optc++;     /* XXX count argv[0] too */
708     while((c = getopt(argc, (char **)argv, opts)) != -1) {
709         if (!(c != '?' && (o = strchr(opts, c)))) {
710                 rpmError(RPMERR_BADSPEC, _("Unknown option %c in %s(%s)"),
711                         c, me->name, opts);
712                 return se;
713         }
714         optc++;
715     }
716
717     /* ... then allocate storage ... */
718     opte = optc + (argc - optind);
719     optv = (const char **)alloca((opte + 1) * sizeof(char *));
720     optv[0] = me->name;
721     optv[opte] = NULL;
722
723     /* ... and finally define option macros. */
724     optind = saveoptind;        /* XXX optind must be restored on non-linux */
725     optc = 0;
726     optc++;                     /* XXX count optv[0] */
727     while((c = getopt(argc, (char **)argv, opts)) != -1) {
728         o = strchr(opts, c);
729         b = be;
730         *be++ = '-';
731         *be++ = c;
732         if (o[1] == ':') {
733                 *be++ = ' ';
734                 strcpy(be, optarg);
735                 be += strlen(be);
736         }
737         *be++ = '\0';
738         sprintf(aname, "-%c", c);
739         addMacro(mb->mc, aname, NULL, b, mb->depth);
740         if (o[1] == ':') {
741                 sprintf(aname, "-%c*", c);
742                 addMacro(mb->mc, aname, NULL, optarg, mb->depth);
743         }
744         optv[optc] = b;
745         optc++;
746     }
747
748     /* Add macro for each arg. Concatenate args for !*. */
749     b = be;
750     for (c = optind; c < argc; c++) {
751         sprintf(aname, "%d", (c - optind + 1));
752         addMacro(mb->mc, aname, NULL, argv[c], mb->depth);
753
754         if (be > b) *be++ = ' ';
755         strcpy(be, argv[c]);
756         be += strlen(be);
757         *be = '\0';
758
759         optv[optc] = argv[c];
760         optc++;
761     }
762
763     /* Add arg count as macro. */
764     sprintf(aname, "%d", (argc - optind));
765     addMacro(mb->mc, "#", NULL, aname, mb->depth);
766
767     /* Add unexpanded args as macro. */
768     addMacro(mb->mc, "*", NULL, b, mb->depth);
769
770     return se;
771 }
772
773 static void
774 doOutput(MacroBuf *mb, int waserror, const char *msg, size_t msglen)
775 {
776         char buf[BUFSIZ];
777
778         strncpy(buf, msg, msglen);
779         buf[msglen] = '\0';
780         expandU(mb, buf, sizeof(buf));
781         if (waserror)
782                 rpmError(RPMERR_BADSPEC, "%s", buf);
783         else
784                 fprintf(stderr, "%s", buf);
785 }
786
787 static void
788 doFoo(MacroBuf *mb, int negate, const char *f, size_t fn, const char *g, size_t glen)
789 {
790         char buf[BUFSIZ], *b = NULL, *be;
791         int c;
792
793         buf[0] = '\0';
794         if (g) {
795                 strncpy(buf, g, glen);
796                 buf[glen] = '\0';
797                 expandU(mb, buf, sizeof(buf));
798         }
799         if (STREQ("basename", f, fn)) {
800                 if ((b = strrchr(buf, '/')) == NULL)
801                         b = buf;
802 #if NOTYET
803         /* XXX watchout for conflict with %dir */
804         } else if (STREQ("dirname", f, fn)) {
805                 if ((b = strrchr(buf, '/')) != NULL)
806                         *b = '\0';
807                 b = buf;
808 #endif
809         } else if (STREQ("suffix", f, fn)) {
810                 if ((b = strrchr(buf, '.')) != NULL)
811                         b++;
812         } else if (STREQ("expand", f, fn)) {
813                 b = buf;
814         } else if (STREQ("verbose", f, fn)) {
815                 if (negate)
816                     b = (rpmIsVerbose() ? NULL : buf);
817                 else
818                     b = (rpmIsVerbose() ? buf : NULL);
819         } else if (STREQ("url2path", f, fn) || STREQ("u2p", f, fn)) {
820                 (void)urlPath(buf, (const char **)&b);
821                 if (*b == '\0') b = "/";
822         } else if (STREQ("uncompress", f, fn)) {
823                 int compressed = 1;
824                 for (b = buf; (c = *b) && isblank(c);)
825                         b++;
826                 for (be = b; (c = *be) && !isblank(c);)
827                         be++;
828                 *be++ = '\0';
829 #ifndef DEBUG_MACROS
830                 isCompressed(b, &compressed);
831 #endif
832                 switch(compressed) {
833                 default:
834                 case 0: /* COMPRESSED_NOT */
835                         sprintf(be, "%%_cat %s", b);
836                         break;
837                 case 1: /* COMPRESSED_OTHER */
838                         sprintf(be, "%%_gzip -dc %s", b);
839                         break;
840                 case 2: /* COMPRESSED_BZIP2 */
841                         sprintf(be, "%%_bzip2 %s", b);
842                         break;
843                 }
844                 b = be;
845         } else if (STREQ("S", f, fn)) {
846                 for (b = buf; (c = *b) && isdigit(c);)
847                         b++;
848                 if (!c) {       /* digit index */
849                         b++;
850                         sprintf(b, "%%SOURCE%s", buf);
851                 } else
852                         b = buf;
853         } else if (STREQ("P", f, fn)) {
854                 for (b = buf; (c = *b) && isdigit(c);)
855                         b++;
856                 if (!c) {       /* digit index */
857                         b++;
858                         sprintf(b, "%%PATCH%s", buf);
859                 } else
860                         b = buf;
861         } else if (STREQ("F", f, fn)) {
862                 b = buf + strlen(buf) + 1;
863                 sprintf(b, "file%s.file", buf);
864         }
865
866         if (b) {
867                 expandT(mb, b, strlen(b));
868         }
869 }
870
871 /* The main recursion engine */
872
873 static int
874 expandMacro(MacroBuf *mb)
875 {
876     MacroEntry **mep;
877     MacroEntry *me;
878     const char *s = mb->s, *se;
879     const char *f, *fe;
880     const char *g, *ge;
881     size_t fn, gn;
882     char *t = mb->t;    /* save expansion pointer for printExpand */
883     int c;
884     int rc = 0;
885     int negate;
886     char grab;
887     int chkexist;
888
889     if (++mb->depth > max_macro_depth) {
890         rpmError(RPMERR_BADSPEC, _("Recursion depth(%d) greater than max(%d)"),
891                 mb->depth, max_macro_depth);
892         mb->depth--;
893         mb->expand_trace = 1;
894         return 1;
895     }
896
897     while (rc == 0 && mb->nb > 0 && (c = *s) != '\0') {
898         s++;
899         /* Copy text until next macro */
900         switch(c) {
901         case '%':
902                 if (*s != '%')
903                         break;
904                 s++;    /* skip first % in %% */
905                 /* fall thru */
906         default:
907                 SAVECHAR(mb, c);
908                 continue;
909                 /*@notreached@*/ break;
910         }
911
912         /* Expand next macro */
913         f = fe = NULL;
914         g = ge = NULL;
915         if (mb->depth > 1)      /* XXX full expansion for outermost level */
916                 t = mb->t;      /* save expansion pointer for printExpand */
917         negate = 0;
918         grab = '\0';
919         chkexist = 0;
920         switch ((c = *s)) {
921         default:                /* %name substitution */
922                 while (strchr("!?", *s) != NULL) {
923                         switch(*s++) {
924                         case '!':
925                                 negate = (++negate % 2);
926                                 break;
927                         case '?':
928                                 chkexist++;
929                                 break;
930                         }
931                 }
932                 f = se = s;
933                 if (*se == '-')
934                         se++;
935                 while((c = *se) && (isalnum(c) || c == '_'))
936                         se++;
937                 /* Recognize non-alnum macros too */
938                 switch (*se) {
939                 case '*':
940                         se++;
941                         if (*se == '*') se++;
942                         break;
943                 case '#':
944                         se++;
945                         break;
946                 default:
947                         break;
948                 }
949                 fe = se;
950                 /* For "%name " macros ... */
951                 if ((c = *fe) && isblank(c))
952                         grab = '\n';
953                 break;
954         case '(':               /* %(...) shell escape */
955                 if ((se = matchchar(s, c, ')')) == NULL) {
956                         rpmError(RPMERR_BADSPEC, _("Unterminated %c: %s"), c, s);
957                         rc = 1;
958                         continue;
959                 }
960                 if (mb->macro_trace)
961                         printMacro(mb, s, se+1);
962
963                 s++;    /* skip ( */
964                 rc = doShellEscape(mb, s, (se - s));
965                 se++;   /* skip ) */
966
967                 s = se;
968                 continue;
969                 /*@notreached@*/ break;
970         case '{':               /* %{...}/%{...:...} substitution */
971                 if ((se = matchchar(s, c, '}')) == NULL) {
972                         rpmError(RPMERR_BADSPEC, _("Unterminated %c: %s"), c, s);
973                         rc = 1;
974                         continue;
975                 }
976                 f = s+1;/* skip { */
977                 se++;   /* skip } */
978                 while (strchr("!?", *f) != NULL) {
979                         switch(*f++) {
980                         case '!':
981                                 negate = (++negate % 2);
982                                 break;
983                         case '?':
984                                 chkexist++;
985                                 break;
986                         }
987                 }
988                 for (fe = f; (c = *fe) && !strchr(" :}", c);)
989                         fe++;
990                 switch (c) {
991                 case ':':
992                         g = fe + 1;
993                         ge = se - 1;
994                         break;
995                 case ' ':
996                         grab = se[-1];
997                         break;
998                 default:
999                         break;
1000                 }
1001                 break;
1002         }
1003
1004         /* XXX Everything below expects fe > f */
1005         fn = (fe - f);
1006         gn = (ge - g);
1007         if (fn <= 0) {
1008 /* XXX Process % in unknown context */
1009                 c = '%';        /* XXX only need to save % */
1010                 SAVECHAR(mb, c);
1011 #if 0
1012                 rpmError(RPMERR_BADSPEC, _("A %% is followed by an unparseable macro"));
1013 #endif
1014                 s = se;
1015                 continue;
1016         }
1017
1018         if (mb->macro_trace)
1019                 printMacro(mb, s, se);
1020
1021         /* Expand builtin macros */
1022         if (STREQ("global", f, fn)) {
1023                 s = doDefine(mb, se, RMIL_GLOBAL, 1);
1024                 continue;
1025         }
1026         if (STREQ("define", f, fn)) {
1027                 s = doDefine(mb, se, mb->depth, 0);
1028                 continue;
1029         }
1030         if (STREQ("undefine", f, fn)) {
1031                 s = doUndefine(mb->mc, se);
1032                 continue;
1033         }
1034
1035         if (STREQ("echo", f, fn) ||
1036             STREQ("warn", f, fn) ||
1037             STREQ("error", f, fn)) {
1038                 int waserror = 0;
1039                 if (STREQ("error", f, fn))
1040                         waserror = 1;
1041                 if (g < ge)
1042                         doOutput(mb, waserror, g, gn);
1043                 else
1044                         doOutput(mb, waserror, f, fn);
1045                 s = se;
1046                 continue;
1047         }
1048
1049         if (STREQ("trace", f, fn)) {
1050                 /* XXX TODO restore expand_trace/macro_trace to 0 on return */
1051                 mb->expand_trace = mb->macro_trace = (negate ? 0 : mb->depth);
1052                 if (mb->depth == 1) {
1053                         print_macro_trace = mb->macro_trace;
1054                         print_expand_trace = mb->expand_trace;
1055                 }
1056                 s = se;
1057                 continue;
1058         }
1059
1060         if (STREQ("dump", f, fn)) {
1061                 rpmDumpMacroTable(mb->mc, NULL);
1062                 while (iseol(*se))
1063                         se++;
1064                 s = se;
1065                 continue;
1066         }
1067
1068         /* XXX necessary but clunky */
1069         if (STREQ("basename", f, fn) ||
1070             STREQ("suffix", f, fn) ||
1071             STREQ("expand", f, fn) ||
1072             STREQ("verbose", f, fn) ||
1073             STREQ("uncompress", f, fn) ||
1074             STREQ("url2path", f, fn) ||
1075             STREQ("u2p", f, fn) ||
1076             STREQ("S", f, fn) ||
1077             STREQ("P", f, fn) ||
1078             STREQ("F", f, fn)) {
1079                 doFoo(mb, negate, f, fn, g, gn);
1080                 s = se;
1081                 continue;
1082         }
1083
1084         /* Expand defined macros */
1085         mep = findEntry(mb->mc, f, fn);
1086         me = (mep ? *mep : NULL);
1087
1088         /* XXX Special processing for flags */
1089         if (*f == '-') {
1090                 if (me)
1091                         me->used++;     /* Mark macro as used */
1092                 if ((me == NULL && !negate) ||  /* Without -f, skip %{-f...} */
1093                     (me != NULL && negate)) {   /* With -f, skip %{!-f...} */
1094                         s = se;
1095                         continue;
1096                 }
1097
1098                 if (g && g < ge) {              /* Expand X in %{-f:X} */
1099                         rc = expandT(mb, g, gn);
1100                 } else
1101                 if (me->body && *me->body) {    /* Expand %{-f}/%{-f*} */
1102                         rc = expandT(mb, me->body, strlen(me->body));
1103                 }
1104                 s = se;
1105                 continue;
1106         }
1107
1108         /* XXX Special processing for macro existence */
1109         if (chkexist) {
1110                 if ((me == NULL && !negate) ||  /* Without -f, skip %{?f...} */
1111                     (me != NULL && negate)) {   /* With -f, skip %{!?f...} */
1112                         s = se;
1113                         continue;
1114                 }
1115                 if (g && g < ge) {              /* Expand X in %{?f:X} */
1116                         rc = expandT(mb, g, gn);
1117                 } else
1118                 if (me && me->body && *me->body) { /* Expand %{?f}/%{?f*} */
1119                         rc = expandT(mb, me->body, strlen(me->body));
1120                 }
1121                 s = se;
1122                 continue;
1123         }
1124         
1125         if (me == NULL) {       /* leave unknown %... as is */
1126 #ifndef HACK
1127 #if DEAD
1128                 /* XXX hack to skip over empty arg list */
1129                 if (fn == 1 && *f == '*') {
1130                         s = se;
1131                         continue;
1132                 }
1133 #endif
1134                 /* XXX hack to permit non-overloaded %foo to be passed */
1135                 c = '%';        /* XXX only need to save % */
1136                 SAVECHAR(mb, c);
1137 #else
1138                 rpmError(RPMERR_BADSPEC, _("Macro %%%.*s not found, skipping"), fn, f);
1139                 s = se;
1140 #endif
1141                 continue;
1142         }
1143
1144         /* Setup args for "%name " macros with opts */
1145         if (me && me->opts != NULL) {
1146                 if (grab) {
1147                         se = grabArgs(mb, me, fe, grab);
1148                 } else {
1149                         addMacro(mb->mc, "**", NULL, "", mb->depth);
1150                         addMacro(mb->mc, "*", NULL, "", mb->depth);
1151                         addMacro(mb->mc, "#", NULL, "0", mb->depth);
1152                         addMacro(mb->mc, "0", NULL, me->name, mb->depth);
1153                 }
1154         }
1155
1156         /* Recursively expand body of macro */
1157         if (me->body && *me->body) {
1158                 mb->s = me->body;
1159                 rc = expandMacro(mb);
1160                 if (rc == 0)
1161                         me->used++;     /* Mark macro as used */
1162         }
1163
1164         /* Free args for "%name " macros with opts */
1165         if (me->opts != NULL)
1166                 freeArgs(mb);
1167
1168         s = se;
1169     }
1170
1171     *mb->t = '\0';
1172     mb->s = s;
1173     mb->depth--;
1174     if (rc != 0 || mb->expand_trace)
1175         printExpansion(mb, t, mb->t);
1176     return rc;
1177 }
1178
1179 /* =============================================================== */
1180 /* XXX this is used only in build/expression.c and will go away. */
1181 const char *
1182 getMacroBody(MacroContext *mc, const char *name)
1183 {
1184     MacroEntry **mep = findEntry(mc, name, 0);
1185     MacroEntry *me = (mep ? *mep : NULL);
1186     return ( me ? me->body : (const char *)NULL );
1187 }
1188
1189 /* =============================================================== */
1190
1191 int
1192 expandMacros(void *spec, MacroContext *mc, char *s, size_t slen)
1193 {
1194         MacroBuf macrobuf, *mb = &macrobuf;
1195         char *tbuf;
1196         int rc;
1197
1198         if (s == NULL || slen <= 0)
1199                 return 0;
1200         if (mc == NULL)
1201                 mc = &rpmGlobalMacroContext;
1202
1203         tbuf = alloca(slen + 1);
1204         memset(tbuf, 0, (slen + 1));
1205
1206         mb->s = s;
1207         mb->t = tbuf;
1208         mb->nb = slen;
1209         mb->depth = 0;
1210         mb->macro_trace = print_macro_trace;
1211         mb->expand_trace = print_expand_trace;
1212
1213         mb->spec = spec;        /* (future) %file expansion info */
1214         mb->mc = mc;
1215
1216         rc = expandMacro(mb);
1217
1218         if (mb->nb <= 0)
1219                 rpmError(RPMERR_BADSPEC, _("Target buffer overflow"));
1220
1221         tbuf[slen] = '\0';      /* XXX just in case */
1222         strncpy(s, tbuf, (slen - mb->nb + 1));
1223
1224         return rc;
1225 }
1226
1227 void
1228 addMacro(MacroContext *mc, const char *n, const char *o, const char *b, int level)
1229 {
1230         MacroEntry **mep;
1231
1232         if (mc == NULL)
1233                 mc = &rpmGlobalMacroContext;
1234
1235         /* If new name, expand macro table */
1236         if ((mep = findEntry(mc, n, 0)) == NULL) {
1237                 if (mc->firstFree == mc->macrosAllocated)
1238                         expandMacroTable(mc);
1239                 mep = mc->macroTable + mc->firstFree++;
1240         }
1241
1242         /* Push macro over previous definition */
1243         pushMacro(mep, n, o, b, level);
1244
1245         /* If new name, sort macro table */
1246         if ((*mep)->prev == NULL)
1247                 sortMacroTable(mc);
1248 }
1249
1250 void
1251 delMacro(MacroContext *mc, const char *n)
1252 {
1253         MacroEntry **mep;
1254
1255         if (mc == NULL)
1256                 mc = &rpmGlobalMacroContext;
1257         /* If name exists, pop entry */
1258         if ((mep = findEntry(mc, n, 0)) != NULL) {
1259                 popMacro(mep);
1260                 /* If deleted name, sort macro table */
1261                 if (!(mep && *mep))
1262                         sortMacroTable(mc);
1263         }
1264 }
1265
1266 int
1267 rpmDefineMacro(MacroContext *mc, const char *macro, int level)
1268 {
1269         MacroBuf macrobuf, *mb = &macrobuf;
1270
1271         /* XXX just enough to get by */
1272         mb->mc = (mc ? mc : &rpmGlobalMacroContext);
1273         (void)doDefine(mb, macro, level, 0);
1274         return 0;
1275 }
1276
1277 /* Load a macro context into rpmGlobalMacroContext */
1278 void
1279 rpmLoadMacros(MacroContext * mc, int level)
1280 {
1281         int i;
1282
1283         if (mc == NULL || mc == &rpmGlobalMacroContext)
1284                 return;
1285
1286         for (i = 0; i < mc->firstFree; i++) {
1287                 MacroEntry **mep, *me;
1288                 mep = &mc->macroTable[i];
1289                 me = *mep;
1290
1291                 if (me == NULL)         /* XXX this should never happen */
1292                         continue;
1293                 addMacro(NULL, me->name, me->opts, me->body, (level - 1));
1294         }
1295 }
1296
1297 void
1298 rpmInitMacros(MacroContext *mc, const char *macrofiles)
1299 {
1300         char *m, *mfile, *me;
1301
1302         if (macrofiles == NULL)
1303                 return;
1304         if (mc == NULL)
1305                 mc = &rpmGlobalMacroContext;
1306
1307         for (mfile = m = xstrdup(macrofiles); *mfile; mfile = me) {
1308                 FD_t fd;
1309                 char buf[BUFSIZ];
1310
1311                 for (me = mfile; (me = strchr(me, ':')) != NULL; me++) {
1312                         if (!(me[1] == '/' && me[2] == '/'))
1313                                 break;
1314                 }
1315
1316                 if (me && *me == ':')
1317                         *me++ = '\0';
1318                 else
1319                         me = mfile + strlen(mfile);
1320
1321                 /* Expand ~/ to $HOME */
1322                 buf[0] = '\0';
1323                 if (mfile[0] == '~' && mfile[1] == '/') {
1324                         char *home;
1325                         if ((home = getenv("HOME")) != NULL) {
1326                                 mfile += 2;
1327                                 strncpy(buf, home, sizeof(buf));
1328                                 strncat(buf, "/", sizeof(buf) - strlen(buf));
1329                         }
1330                 }
1331                 strncat(buf, mfile, sizeof(buf) - strlen(buf));
1332                 buf[sizeof(buf)-1] = '\0';
1333
1334                 fd = Fopen(buf, "r.fpio");
1335                 if (fd == NULL || Ferror(fd)) {
1336                         if (fd) Fclose(fd);
1337                         continue;
1338                 }
1339
1340                 /* XXX Assume new fangled macro expansion */
1341                 max_macro_depth = 16;
1342
1343                 while(rdcl(buf, sizeof(buf), fd, 1) != NULL) {
1344                         char c, *n;
1345
1346                         n = buf;
1347                         SKIPBLANK(n, c);
1348
1349                         if (c != '%')
1350                                 continue;
1351                         n++;    /* skip % */
1352                         (void)rpmDefineMacro(NULL, n, RMIL_MACROFILES);
1353                 }
1354                 Fclose(fd);
1355         }
1356         if (m)
1357                 free(m);
1358
1359         /* Reload cmdline macros */
1360         rpmLoadMacros(&rpmCLIMacroContext, RMIL_CMDLINE);
1361 }
1362
1363 void
1364 rpmFreeMacros(MacroContext *mc)
1365 {
1366         int i;
1367     
1368         if (mc == NULL)
1369                 mc = &rpmGlobalMacroContext;
1370
1371         for (i = 0; i < mc->firstFree; i++) {
1372                 MacroEntry *me;
1373                 while ((me = mc->macroTable[i]) != NULL) {
1374                         /* XXX cast to workaround const */
1375                         if ((mc->macroTable[i] = me->prev) == NULL)
1376                                 FREE(me->name);
1377                         FREE(me->opts);
1378                         FREE(me->body);
1379                         FREE(me);
1380                 }
1381         }
1382         FREE(mc->macroTable);
1383         memset(mc, 0, sizeof(*mc));
1384 }
1385
1386 /* =============================================================== */
1387 int isCompressed(const char *file, int *compressed)
1388 {
1389     FD_t fd;
1390     ssize_t nb;
1391     int rc = -1;
1392     unsigned char magic[4];
1393
1394     *compressed = COMPRESSED_NOT;
1395
1396     fd = Fopen(file, "r.ufdio");
1397     if (fd == NULL || Ferror(fd)) {
1398         /* XXX Fstrerror */
1399         rpmError(RPMERR_BADSPEC, _("File %s: %s"), file, Fstrerror(fd));
1400         if (fd) Fclose(fd);
1401         return 1;
1402     }
1403     nb = Fread(magic, sizeof(char), sizeof(magic), fd);
1404     if (nb < 0) {
1405         rpmError(RPMERR_BADSPEC, _("File %s: %s"), file, Fstrerror(fd));
1406         rc = 1;
1407     } else if (nb < sizeof(magic)) {
1408         rpmError(RPMERR_BADSPEC, _("File %s is smaller than %d bytes"),
1409                 file, sizeof(magic));
1410         rc = 0;
1411     }
1412     Fclose(fd);
1413     if (rc >= 0)
1414         return rc;
1415
1416     rc = 0;
1417
1418     if (((magic[0] == 0037) && (magic[1] == 0213)) ||  /* gzip */
1419         ((magic[0] == 0037) && (magic[1] == 0236)) ||  /* old gzip */
1420         ((magic[0] == 0037) && (magic[1] == 0036)) ||  /* pack */
1421         ((magic[0] == 0037) && (magic[1] == 0240)) ||  /* SCO lzh */
1422         ((magic[0] == 0037) && (magic[1] == 0235)) ||  /* compress */
1423         ((magic[0] == 0120) && (magic[1] == 0113) &&
1424          (magic[2] == 0003) && (magic[3] == 0004))     /* pkzip */
1425         ) {
1426         *compressed = COMPRESSED_OTHER;
1427     } else if ((magic[0] == 'B') && (magic[1] == 'Z')) {
1428         *compressed = COMPRESSED_BZIP2;
1429     }
1430
1431     return rc;
1432 }
1433
1434 /* =============================================================== */
1435 /* Return concatenated and expanded macro list */
1436 char * 
1437 rpmExpand(const char *arg, ...)
1438 {
1439     char buf[BUFSIZ], *p, *pe;
1440     const char *s;
1441     va_list ap;
1442
1443     if (arg == NULL)
1444         return xstrdup("");
1445
1446     p = buf;
1447     strcpy(p, arg);
1448     pe = p + strlen(p);
1449     *pe = '\0';
1450
1451     va_start(ap, arg);
1452     while ((s = va_arg(ap, const char *)) != NULL) {
1453         strcpy(pe, s);
1454         pe += strlen(pe);
1455         *pe = '\0';
1456     }
1457     va_end(ap);
1458     expandMacros(NULL, NULL, buf, sizeof(buf));
1459     return xstrdup(buf);
1460 }
1461
1462 int
1463 rpmExpandNumeric(const char *arg)
1464 {
1465     const char *val;
1466     int rc;
1467
1468     if (arg == NULL)
1469         return 0;
1470
1471     val = rpmExpand(arg, NULL);
1472     if (!(val && *val != '%'))
1473         rc = 0;
1474     else if (*val == 'Y' || *val == 'y')
1475         rc = 1;
1476     else if (*val == 'N' || *val == 'n')
1477         rc = 0;
1478     else {
1479         char *end;
1480         rc = strtol(val, &end, 0);
1481         if (!(end && *end == '\0'))
1482             rc = 0;
1483     }
1484     xfree(val);
1485
1486     return rc;
1487 }
1488
1489 /* XXX FIXME: ../sbin/./../bin/ */
1490 char *rpmCleanPath(char * path)
1491 {
1492     const char *s;
1493     char *se, *t, *te;
1494
1495     s = t = te = path;
1496     while (*s) {
1497 /*fprintf(stderr, "*** got \"%.*s\"\trest \"%s\"\n", (t-path), path, s); */
1498         switch(*s) {
1499         case ':':                       /* handle url's */
1500             if (s[1] == '/' && s[2] == '/') {
1501                 *t++ = *s++;
1502                 *t++ = *s++;
1503             }
1504             break;
1505         case '/':
1506             /* Move parent dir forward */
1507             for (se = te + 1; se < t && *se != '/'; se++)
1508                 ;
1509             if (se < t && *se == '/') {
1510                 te = se;
1511 /*fprintf(stderr, "*** next pdir \"%.*s\"\n", (te-path), path); */
1512             }
1513             while (s[1] == '/')
1514                 s++;
1515             while (t > path && t[-1] == '/')
1516                 t--;
1517             break;
1518         case '.':
1519             /* Leading .. is special */
1520             if (t == path && s[1] == '.') {
1521                 *t++ = *s++;
1522                 break;
1523             }
1524             /* Single . is special */
1525             if (t == path && s[1] == '\0') {
1526                 break;
1527             }
1528             /* Trim leading ./ , embedded ./ , trailing /. */
1529             if ((t == path || t[-1] == '/') && (s[1] == '/' || s[1] == '\0')) {
1530 /*fprintf(stderr, "*** Trim leading ./ , embedded ./ , trailing /.\n"); */
1531                 s++;
1532                 continue;
1533             }
1534             /* Trim embedded /../ and trailing /.. */
1535             if (t > path && t[-1] == '/' && s[1] == '.' && (s[2] == '/' || s[2] == '\0')) {
1536                 t = te;
1537                 /* Move parent dir forward */
1538                 if (te > path)
1539                     for (--te; te > path && *te != '/'; te--)
1540                         ;
1541 /*fprintf(stderr, "*** prev pdir \"%.*s\"\n", (te-path), path); */
1542                 s++;
1543                 s++;
1544                 continue;
1545             }
1546             break;
1547         default:
1548             break;
1549         }
1550         *t++ = *s++;
1551     }
1552
1553     /* Trim trailing / (but leave single / alone) */
1554     if (t > &path[1] && t[-1] == '/')
1555         t--;
1556     *t = '\0';
1557
1558     return path;
1559 }
1560
1561 /* Return concatenated and expanded canonical path. */
1562 const char *
1563 rpmGetPath(const char *path, ...)
1564 {
1565     char buf[BUFSIZ];
1566     const char * s;
1567     char * t, * te;
1568     va_list ap;
1569
1570     if (path == NULL)
1571         return xstrdup("");
1572
1573     t = buf;
1574     te = stpcpy(t, path);
1575     *te = '\0';
1576
1577     va_start(ap, path);
1578     while ((s = va_arg(ap, const char *)) != NULL) {
1579         te = stpcpy(te, s);
1580         *te = '\0';
1581     }
1582     va_end(ap);
1583     expandMacros(NULL, NULL, buf, sizeof(buf));
1584
1585     return xstrdup( rpmCleanPath(buf) );
1586 }
1587
1588 /* Merge 3 args into path, any or all of which may be a url. */
1589
1590 const char * rpmGenPath(const char * urlroot, const char * urlmdir,
1591                 const char *urlfile)
1592 {
1593     const char * xroot = rpmGetPath(urlroot, NULL), * root = xroot;
1594     const char * xmdir = rpmGetPath(urlmdir, NULL), * mdir = xmdir;
1595     const char * xfile = rpmGetPath(urlfile, NULL), * file = xfile;
1596     const char * result;
1597     const char * url = NULL;
1598     int nurl = 0;
1599     int ut;
1600
1601 if (_debug)
1602 fprintf(stderr, "*** RGP xroot %s xmdir %s xfile %s\n", xroot, xmdir, xfile);
1603     ut = urlPath(xroot, &root);
1604     if (url == NULL && ut > URL_IS_DASH) {
1605         url = xroot;
1606         nurl = root - xroot;
1607 if (_debug)
1608 fprintf(stderr, "*** RGP ut %d root %s nurl %d\n", ut, root, nurl);
1609     }
1610     if (root == NULL || *root == '\0') root = "/";
1611
1612     ut = urlPath(xmdir, &mdir);
1613     if (url == NULL && ut > URL_IS_DASH) {
1614         url = xmdir;
1615         nurl = mdir - xmdir;
1616 if (_debug)
1617 fprintf(stderr, "*** RGP ut %d mdir %s nurl %d\n", ut, mdir, nurl);
1618     }
1619     if (mdir == NULL || *mdir == '\0') mdir = "/";
1620
1621     ut = urlPath(xfile, &file);
1622     if (url == NULL && ut > URL_IS_DASH) {
1623         url = xfile;
1624         nurl = file - xfile;
1625 if (_debug)
1626 fprintf(stderr, "*** RGP ut %d file %s nurl %d\n", ut, file, nurl);
1627     }
1628
1629     if (url && nurl > 0) {
1630         char *t = strncpy(alloca(nurl+1), url, nurl);
1631         t[nurl] = '\0';
1632         url = t;
1633     } else
1634         url = "";
1635
1636     result = rpmGetPath(url, root, "/", mdir, "/", file, NULL);
1637
1638     xfree(xroot);
1639     xfree(xmdir);
1640     xfree(xfile);
1641 if (_debug)
1642 fprintf(stderr, "*** RGP result %s\n", result);
1643     return result;
1644 }
1645
1646 /* =============================================================== */
1647
1648 #if defined(DEBUG_MACROS)
1649
1650 #if defined(EVAL_MACROS)
1651
1652 char *macrofiles = "/usr/lib/rpm/macros:/etc/rpm/macros:~/.rpmmacros";
1653
1654 int
1655 main(int argc, char *argv[])
1656 {
1657         int c;
1658         int errflg = 0;
1659         extern char *optarg;
1660         extern int optind;
1661
1662         while ((c = getopt(argc, argv, "f:")) != EOF ) {
1663             switch (c) {
1664             case 'f':
1665                 macrofiles = optarg;
1666                 break;
1667             case '?':
1668             default:
1669                 errflg++;
1670                 break;
1671             }
1672         }
1673         if (errflg || optind >= argc) {
1674             fprintf(stderr, "Usage: %s [-f macropath ] macro ...\n", argv[0]);
1675             exit(1);
1676         }
1677
1678         rpmInitMacros(NULL, macrofiles);
1679         for ( ; optind < argc; optind++) {
1680             const char *val;
1681
1682             val = rpmGetPath(argv[optind], NULL);
1683             if (val) {
1684                 fprintf(stdout, "%s:\t%s\n", argv[optind], val);
1685                 xfree(val);
1686             }
1687         }
1688         rpmFreeMacros(NULL);
1689         return 0;
1690 }
1691
1692 #else   /* !EVAL_MACROS */
1693
1694 char *macrofiles = "../macros:./testmacros";
1695 char *testfile = "./test";
1696
1697 int
1698 main(int argc, char *argv[])
1699 {
1700         char buf[BUFSIZ];
1701         FILE *fp;
1702         int x;
1703
1704         rpmInitMacros(NULL, macrofiles);
1705         rpmDumpMacroTable(NULL, NULL);
1706
1707         if ((fp = fopen(testfile, "r")) != NULL) {
1708                 while(rdcl(buf, sizeof(buf), fp, 1)) {
1709                         x = expandMacros(NULL, NULL, buf, sizeof(buf));
1710                         fprintf(stderr, "%d->%s\n", x, buf);
1711                         memset(buf, 0, sizeof(buf));
1712                 }
1713                 fclose(fp);
1714         }
1715
1716         while(rdcl(buf, sizeof(buf), stdin, 1)) {
1717                 x = expandMacros(NULL, NULL, buf, sizeof(buf));
1718                 fprintf(stderr, "%d->%s\n <-\n", x, buf);
1719                 memset(buf, 0, sizeof(buf));
1720         }
1721         rpmFreeMacros(NULL);
1722
1723         return 0;
1724 }
1725 #endif  /* EVAL_MACROS */
1726 #endif  /* DEBUG_MACROS */