1 /** \ingroup rpmrc rpmio
2  * \file rpmio/macro.c
3  */
4 
5 #include "system.h"
6 #include <stdarg.h>
7 #include <pthread.h>
8 #include <errno.h>
9 #ifdef HAVE_GETOPT_H
10 #include <getopt.h>
11 #else
12 extern char *optarg;
13 extern int optind;
14 #endif
15 #if HAVE_SCHED_GETAFFINITY
16 #include <sched.h>
17 #endif
18 
19 #if !defined(isblank)
20 #define	isblank(_c)	((_c) == ' ' || (_c) == '\t')
21 #endif
22 #define	iseol(_c)	((_c) == '\n' || (_c) == '\r')
23 
24 #define	STREQ(_t, _f, _fn)	((_fn) == (sizeof(_t)-1) && rstreqn((_t), (_f), (_fn)))
25 
26 #define MACROBUFSIZ (BUFSIZ * 2)
27 
28 #include <rpm/rpmio.h>
29 #include <rpm/rpmstring.h>
30 #include <rpm/rpmfileutil.h>
31 #include <rpm/rpmurl.h>
32 #include <rpm/rpmlog.h>
33 #include <rpm/rpmmacro.h>
34 #include <rpm/argv.h>
35 
36 #ifdef	WITH_LUA
37 #include "rpmio/rpmlua.h"
38 #endif
39 
40 #include "rpmio/rpmmacro_internal.h"
41 #include "debug.h"
42 
43 enum macroFlags_e {
44     ME_NONE	= 0,
45     ME_AUTO	= (1 << 0),
46     ME_USED	= (1 << 1),
47     ME_LITERAL	= (1 << 2),
48 };
49 
50 /*! The structure used to store a macro. */
51 struct rpmMacroEntry_s {
52     struct rpmMacroEntry_s *prev;/*!< Macro entry stack. */
53     const char *name;  	/*!< Macro name. */
54     const char *opts;  	/*!< Macro parameters (a la getopt) */
55     const char *body;	/*!< Macro body. */
56     int flags;		/*!< Macro state bits. */
57     int level;          /*!< Scoping level. */
58     char arena[];   	/*!< String arena. */
59 };
60 
61 /*! The structure used to store the set of macros in a context. */
62 struct rpmMacroContext_s {
63     rpmMacroEntry *tab;  /*!< Macro entry table (array of pointers). */
64     int n;      /*!< No. of macros. */
65     int depth;		 /*!< Depth tracking when recursing from Lua  */
66     int level;		 /*!< Scope level tracking when recursing from Lua  */
67     pthread_mutex_t lock;
68     pthread_mutexattr_t lockattr;
69 };
70 
71 
72 static struct rpmMacroContext_s rpmGlobalMacroContext_s;
73 rpmMacroContext rpmGlobalMacroContext = &rpmGlobalMacroContext_s;
74 
75 static struct rpmMacroContext_s rpmCLIMacroContext_s;
76 rpmMacroContext rpmCLIMacroContext = &rpmCLIMacroContext_s;
77 
78 /*
79  * The macro engine internals do not require recursive mutexes but Lua
80  * macro bindings which can get called from the internals use the external
81  * interfaces which do perform locking. Until that is fixed somehow
82  * we'll just have to settle for recursive mutexes.
83  * Unfortunately POSIX doesn't specify static initializers for recursive
84  * mutexes so we need to have a separate PTHREAD_ONCE initializer just
85  * to initialize the otherwise static macro context mutexes. Pooh.
86  */
87 static pthread_once_t locksInitialized = PTHREAD_ONCE_INIT;
88 
initLocks(void)89 static void initLocks(void)
90 {
91     rpmMacroContext mcs[] = { rpmGlobalMacroContext, rpmCLIMacroContext, NULL };
92 
93     for (rpmMacroContext *mcp = mcs; *mcp; mcp++) {
94 	rpmMacroContext mc = *mcp;
95 	pthread_mutexattr_init(&mc->lockattr);
96 	pthread_mutexattr_settype(&mc->lockattr, PTHREAD_MUTEX_RECURSIVE);
97 	pthread_mutex_init(&mc->lock, &mc->lockattr);
98     }
99 }
100 
101 /**
102  * Macro expansion state.
103  */
104 typedef struct MacroBuf_s {
105     char * buf;			/*!< Expansion buffer. */
106     size_t tpos;		/*!< Current position in expansion buffer */
107     size_t nb;			/*!< No. bytes remaining in expansion buffer. */
108     int depth;			/*!< Current expansion depth. */
109     int level;			/*!< Current scoping level */
110     int error;			/*!< Errors encountered during expansion? */
111     int macro_trace;		/*!< Pre-print macro to expand? */
112     int expand_trace;		/*!< Post-print macro expansion? */
113     int flags;			/*!< Flags to control behavior */
114     rpmMacroContext mc;
115 } * MacroBuf;
116 
117 #define	_MAX_MACRO_DEPTH	64
118 static int max_macro_depth = _MAX_MACRO_DEPTH;
119 
120 #define	_PRINT_MACRO_TRACE	0
121 static int print_macro_trace = _PRINT_MACRO_TRACE;
122 
123 #define	_PRINT_EXPAND_TRACE	0
124 static int print_expand_trace = _PRINT_EXPAND_TRACE;
125 
126 typedef void (*macroFunc)(MacroBuf mb, int chkexist, int negate,
127 			const char * f, size_t fn, const char * g, size_t gn);
128 typedef const char *(*parseFunc)(MacroBuf mb, const char * se);
129 
130 /* forward ref */
131 static int expandMacro(MacroBuf mb, const char *src, size_t slen);
132 static void pushMacro(rpmMacroContext mc,
133 	const char * n, const char * o, const char * b, int level, int flags);
134 static void popMacro(rpmMacroContext mc, const char * n);
135 static int loadMacroFile(rpmMacroContext mc, const char * fn);
136 static void doBody(MacroBuf mb, int chkexist, int negate,
137 		const char * f, size_t fn, const char * g, size_t gn);
138 static void doExpand(MacroBuf mb, int chkexist, int negate,
139 		    const char * f, size_t fn, const char * g, size_t gn);
140 static void doFoo(MacroBuf mb, int chkexist, int negate,
141 		    const char * f, size_t fn, const char * g, size_t gn);
142 static void doLoad(MacroBuf mb, int chkexist, int negate,
143 		    const char * f, size_t fn, const char * g, size_t gn);
144 static void doLua(MacroBuf mb, int chkexist, int negate,
145 		    const char * f, size_t fn, const char * g, size_t gn);
146 static void doOutput(MacroBuf mb, int chkexist, int negate,
147 		    const char * f, size_t fn, const char * g, size_t gn);
148 static void doSP(MacroBuf mb, int chkexist, int negate,
149 		    const char * f, size_t fn, const char * g, size_t gn);
150 static void doTrace(MacroBuf mb, int chkexist, int negate,
151 		    const char * f, size_t fn, const char * g, size_t gn);
152 static void doUncompress(MacroBuf mb, int chkexist, int negate,
153 		    const char * f, size_t fn, const char * g, size_t gn);
154 static void doVerbose(MacroBuf mb, int chkexist, int negate,
155 		    const char * f, size_t fn, const char * g, size_t gn);
156 
157 static const char * doDef(MacroBuf mb, const char * se);
158 static const char * doGlobal(MacroBuf mb, const char * se);
159 static const char * doDump(MacroBuf mb, const char * se);
160 static const char * doUndefine(MacroBuf mb, const char * se);
161 /* =============================================================== */
162 
rpmmctxAcquire(rpmMacroContext mc)163 static rpmMacroContext rpmmctxAcquire(rpmMacroContext mc)
164 {
165     if (mc == NULL)
166 	mc = rpmGlobalMacroContext;
167     pthread_once(&locksInitialized, initLocks);
168     pthread_mutex_lock(&mc->lock);
169     return mc;
170 }
171 
rpmmctxRelease(rpmMacroContext mc)172 static rpmMacroContext rpmmctxRelease(rpmMacroContext mc)
173 {
174     pthread_mutex_unlock(&mc->lock);
175     return NULL;
176 }
177 
178 /**
179  * Find entry in macro table.
180  * @param mc		macro context
181  * @param name		macro name
182  * @param namelen	no. of bytes
183  * @param pos		found/insert position
184  * @return		address of slot in macro table with name (or NULL)
185  */
186 static rpmMacroEntry *
findEntry(rpmMacroContext mc,const char * name,size_t namelen,size_t * pos)187 findEntry(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos)
188 {
189     /* bsearch */
190     int cmp = 1;
191     size_t l = 0;
192     size_t u = mc->n;
193     size_t i = 0;
194     while (l < u) {
195 	i = (l + u) / 2;
196 	rpmMacroEntry me = mc->tab[i];
197 	if (namelen == 0)
198 	    cmp = strcmp(me->name, name);
199 	else {
200 	    cmp = strncmp(me->name, name, namelen);
201 	    /* longer me->name compares greater */
202 	    if (cmp == 0)
203 		cmp = (me->name[namelen] != '\0');
204 	}
205 	if (cmp < 0)
206 	    l = i + 1;
207 	else if (cmp > 0)
208 	    u = i;
209 	else
210 	    break;
211     }
212 
213     if (pos)
214 	*pos = (cmp < 0) ? i + 1 : i;
215     if (cmp == 0)
216 	return &mc->tab[i];
217     return NULL;
218 }
219 
220 /* =============================================================== */
221 
222 /**
223  * fgets(3) analogue that reads \ continuations. Last newline always trimmed.
224  * @param buf		input buffer
225  * @param size		inbut buffer size (bytes)
226  * @param f		file handle
227  * @return		number of lines read, or 0 on end-of-file
228  */
229 static int
rdcl(char * buf,size_t size,FILE * f)230 rdcl(char * buf, size_t size, FILE *f)
231 {
232     char *q = buf - 1;		/* initialize just before buffer. */
233     size_t nb = 0;
234     size_t nread = 0;
235     int pc = 0, bc = 0, xc = 0;
236     int nlines = 0;
237     char *p = buf;
238 
239     if (f != NULL)
240     do {
241 	*(++q) = '\0';			/* terminate and move forward. */
242 	if (fgets(q, size, f) == NULL)	/* read next line. */
243 	    break;
244 	nlines++;
245 	nb = strlen(q);
246 	nread += nb;			/* trim trailing \r and \n */
247 	for (q += nb - 1; nb > 0 && iseol(*q); q--)
248 	    nb--;
249 	for (; p <= q; p++) {
250 	    switch (*p) {
251 		case '\\':
252 		    switch (*(p+1)) {
253 			case '\0': break;
254 			default: p++; break;
255 		    }
256 		    break;
257 		case '%':
258 		    switch (*(p+1)) {
259 			case '{': p++, bc++; break;
260 			case '(': p++, pc++; break;
261 			case '[': p++, xc++; break;
262 			case '%': p++; break;
263 		    }
264 		    break;
265 		case '{': if (bc > 0) bc++; break;
266 		case '}': if (bc > 0) bc--; break;
267 		case '(': if (pc > 0) pc++; break;
268 		case ')': if (pc > 0) pc--; break;
269 		case '[': if (xc > 0) xc++; break;
270 		case ']': if (xc > 0) xc--; break;
271 	    }
272 	}
273 	if (nb == 0 || (*q != '\\' && !bc && !pc && !xc) || *(q+1) == '\0') {
274 	    *(++q) = '\0';		/* trim trailing \r, \n */
275 	    break;
276 	}
277 	q++; nb++;			/* copy newline too */
278 	size -= nb;
279 	if (*q == '\r')			/* XXX avoid \r madness */
280 	    *q = '\n';
281     } while (size > 0);
282     return nlines;
283 }
284 
285 /**
286  * Return text between pl and matching pr characters.
287  * @param p		start of text
288  * @param pl		left char, i.e. '[', '(', '{', etc.
289  * @param pr		right char, i.e. ']', ')', '}', etc.
290  * @return		address of char after pr (or NULL)
291  */
292 static const char *
matchchar(const char * p,char pl,char pr)293 matchchar(const char * p, char pl, char pr)
294 {
295     int lvl = 0;
296     char c;
297 
298     while ((c = *p++) != '\0') {
299 	if (c == '\\') {		/* Ignore escaped chars */
300 	    p++;
301 	    continue;
302 	}
303 	if (c == pr) {
304 	    if (--lvl <= 0)	return p;
305 	} else if (c == pl)
306 	    lvl++;
307     }
308     return (const char *)NULL;
309 }
310 
mbErr(MacroBuf mb,int error,const char * fmt,...)311 static void mbErr(MacroBuf mb, int error, const char *fmt, ...)
312 {
313     char *emsg = NULL;
314     int n;
315     va_list ap;
316 
317     va_start(ap, fmt);
318     n = rvasprintf(&emsg, fmt, ap);
319     va_end(ap);
320 
321     if (n >= -1) {
322 	/* XXX should have an non-locking version for this */
323 	char *pfx = rpmExpand("%{?__file_name:%{__file_name}: }",
324 			      "%{?__file_lineno:line %{__file_lineno}: }",
325 			      NULL);
326 	rpmlog(error ? RPMLOG_ERR : RPMLOG_WARNING, "%s%s", pfx, emsg);
327 	free(pfx);
328     }
329 
330     if (error)
331 	mb->error = error;
332 
333     free(emsg);
334 }
335 
336 /**
337  * Pre-print macro expression to be expanded.
338  * @param mb		macro expansion state
339  * @param s		current expansion string
340  * @param se		end of string
341  */
342 static void
printMacro(MacroBuf mb,const char * s,const char * se)343 printMacro(MacroBuf mb, const char * s, const char * se)
344 {
345     const char *senl;
346 
347     if (s >= se) {	/* XXX just in case */
348 	fprintf(stderr, _("%3d>%*s(empty)\n"), mb->depth,
349 		(2 * mb->depth + 1), "");
350 	return;
351     }
352 
353     if (s[-1] == '{')
354 	s--;
355 
356     /* Print only to first end-of-line (or end-of-string). */
357     for (senl = se; *senl && !iseol(*senl); senl++)
358 	{};
359 
360     /* Substitute caret at end-of-macro position */
361     fprintf(stderr, "%3d>%*s%%%.*s^", mb->depth,
362 	(2 * mb->depth + 1), "", (int)(se - s), s);
363     if (se[0] != '\0' && se[1] != '\0' && (senl - (se+1)) > 0)
364 	fprintf(stderr, "%-.*s", (int)(senl - (se+1)), se+1);
365     fprintf(stderr, "\n");
366 }
367 
368 /**
369  * Post-print expanded macro expression.
370  * @param mb		macro expansion state
371  * @param t		current expansion string result
372  * @param te		end of string
373  */
374 static void
printExpansion(MacroBuf mb,const char * t,const char * te)375 printExpansion(MacroBuf mb, const char * t, const char * te)
376 {
377     if (!(te > t)) {
378 	rpmlog(RPMLOG_DEBUG, _("%3d<%*s(empty)\n"), mb->depth, (2 * mb->depth + 1), "");
379 	return;
380     }
381 
382     /* Shorten output which contains newlines */
383     while (te > t && iseol(te[-1]))
384 	te--;
385     if (mb->depth > 0) {
386 	const char *tenl;
387 
388 	/* Skip to last line of expansion */
389 	while ((tenl = strchr(t, '\n')) && tenl < te)
390 	    t = ++tenl;
391 
392     }
393 
394     rpmlog(RPMLOG_DEBUG,"%3d<%*s", mb->depth, (2 * mb->depth + 1), "");
395     if (te > t)
396 	rpmlog(RPMLOG_DEBUG, "%.*s", (int)(te - t), t);
397     rpmlog(RPMLOG_DEBUG, "\n");
398 }
399 
400 #define	SKIPBLANK(_s, _c)	\
401 	while (((_c) = *(_s)) && isblank(_c)) \
402 		(_s)++;		\
403 
404 #define	SKIPNONBLANK(_s, _c)	\
405 	while (((_c) = *(_s)) && !(isblank(_c) || iseol(_c))) \
406 		(_s)++;		\
407 
408 #define	COPYNAME(_ne, _s, _c)	\
409     {	SKIPBLANK(_s,_c);	\
410 	while (((_c) = *(_s)) && (risalnum(_c) || (_c) == '_')) \
411 		*(_ne)++ = *(_s)++; \
412 	*(_ne) = '\0';		\
413     }
414 
415 #define	COPYOPTS(_oe, _s, _c)	\
416     { \
417 	while (((_c) = *(_s)) && (_c) != ')') \
418 		*(_oe)++ = *(_s)++; \
419 	*(_oe) = '\0';		\
420     }
421 
422 /**
423  * Macro-expand string src, return result in dynamically allocated buffer.
424  * @param mb		macro expansion state
425  * @param src		string to expand
426  * @param slen		input string length (or 0 for strlen())
427  * @retval target	pointer to expanded string (malloced)
428  * @return		result of expansion
429  */
430 static int
expandThis(MacroBuf mb,const char * src,size_t slen,char ** target)431 expandThis(MacroBuf mb, const char * src, size_t slen, char **target)
432 {
433     struct MacroBuf_s umb;
434 
435     /* Copy other state from "parent", but we want a buffer of our own */
436     umb = *mb;
437     umb.buf = NULL;
438     umb.error = 0;
439     /* In case of error, flag it in the "parent"... */
440     if (expandMacro(&umb, src, slen))
441 	mb->error = 1;
442     *target = umb.buf;
443 
444     /* ...but return code for this operation specifically */
445     return umb.error;
446 }
447 
mbAppend(MacroBuf mb,char c)448 static void mbAppend(MacroBuf mb, char c)
449 {
450     if (mb->nb < 1) {
451 	mb->buf = xrealloc(mb->buf, mb->tpos + MACROBUFSIZ + 1);
452 	mb->nb += MACROBUFSIZ;
453     }
454     mb->buf[mb->tpos++] = c;
455     mb->buf[mb->tpos] = '\0';
456     mb->nb--;
457 }
458 
mbAppendStr(MacroBuf mb,const char * str)459 static void mbAppendStr(MacroBuf mb, const char *str)
460 {
461     size_t len = strlen(str);
462     if (len > mb->nb) {
463 	mb->buf = xrealloc(mb->buf, mb->tpos + mb->nb + MACROBUFSIZ + len + 1);
464 	mb->nb += MACROBUFSIZ + len;
465     }
466     memcpy(mb->buf+mb->tpos, str, len + 1);
467     mb->tpos += len;
468     mb->nb -= len;
469 }
470 
doDnl(MacroBuf mb,const char * se)471 static const char * doDnl(MacroBuf mb, const char * se)
472 {
473     const char *s = se;
474     while (*s && !iseol(*s))
475 	s++;
476     return (*s != '\0') ? s + 1 : s;
477 }
478 
479 /**
480  * Expand output of shell command into target buffer.
481  * @param mb		macro expansion state
482  * @param cmd		shell command
483  * @param clen		no. bytes in shell command
484  */
485 static void
doShellEscape(MacroBuf mb,const char * cmd,size_t clen)486 doShellEscape(MacroBuf mb, const char * cmd, size_t clen)
487 {
488     char *buf = NULL;
489     FILE *shf;
490     int c;
491 
492     if (expandThis(mb, cmd, clen, &buf))
493 	goto exit;
494 
495     if ((shf = popen(buf, "r")) == NULL) {
496 	mbErr(mb, 1, _("Failed to open shell expansion pipe for command: "
497 		"%s: %m \n"), buf);
498 	goto exit;
499     }
500 
501     size_t tpos = mb->tpos;
502     while ((c = fgetc(shf)) != EOF) {
503 	mbAppend(mb, c);
504     }
505     (void) pclose(shf);
506 
507     /* Delete trailing \r \n */
508     while (mb->tpos > tpos && iseol(mb->buf[mb->tpos-1])) {
509 	mb->buf[--mb->tpos] = '\0';
510 	mb->nb++;
511     }
512 
513 exit:
514     _free(buf);
515 }
516 
517 /**
518  * Expand an expression into target buffer.
519  * @param mb		macro expansion state
520  * @param expr		expression
521  * @param len		no. bytes in expression
522  */
doExpressionExpansion(MacroBuf mb,const char * expr,size_t len)523 static void doExpressionExpansion(MacroBuf mb, const char * expr, size_t len)
524 {
525     char *buf = xmalloc(len + 1);
526     char *result;
527     strncpy(buf, expr, len);
528     buf[len] = 0;
529     result = rpmExprStrFlags(buf, RPMEXPR_EXPAND);
530     if (!result) {
531 	mb->error = 1;
532 	goto exit;
533     }
534     mbAppendStr(mb, result);
535     free(result);
536 exit:
537     _free(buf);
538 }
539 
getncpus(void)540 static unsigned int getncpus(void)
541 {
542     unsigned int ncpus = 0;
543 #if HAVE_SCHED_GETAFFINITY
544     cpu_set_t set;
545     if (sched_getaffinity (0, sizeof(set), &set) == 0)
546 	ncpus = CPU_COUNT(&set);
547 #endif
548     /* Fallback to sysconf() if the above isn't supported or didn't work */
549     if (ncpus < 1)
550 	ncpus = sysconf(_SC_NPROCESSORS_ONLN);
551     /* If all else fails, there's always the one we're running on... */
552     if (ncpus < 1)
553 	ncpus = 1;
554     return ncpus;
555 }
556 
557 #define STR_AND_LEN(_str) (_str), sizeof((_str))-1
558 
559 /* Names in the table must be in ASCII-code order */
560 static struct builtins_s {
561     const char * name;
562     size_t len;
563     macroFunc func;
564     parseFunc parse;
565     int havearg;
566 } const builtinmacros[] = {
567     { STR_AND_LEN("P"),		doSP,		NULL,		1 },
568     { STR_AND_LEN("S"),		doSP,		NULL,		1 },
569     { STR_AND_LEN("basename"),	doFoo,		NULL,		1 },
570     { STR_AND_LEN("define"),	NULL,		doDef,		0 },
571     { STR_AND_LEN("dirname"),	doFoo,		NULL,		1 },
572     { STR_AND_LEN("dnl"),	NULL,		doDnl,		0 },
573     { STR_AND_LEN("dump"), 	NULL,		doDump,		0 },
574     { STR_AND_LEN("echo"),	doOutput,	NULL,		1 },
575     { STR_AND_LEN("error"),	doOutput,	NULL,		1 },
576     { STR_AND_LEN("expand"),	doExpand,	NULL,		1 },
577     { STR_AND_LEN("expr"),	doFoo,		NULL,		1 },
578     { STR_AND_LEN("getconfdir"),doFoo,		NULL,		0 },
579     { STR_AND_LEN("getenv"),	doFoo,		NULL,		1 },
580     { STR_AND_LEN("getncpus"),	doFoo,		NULL,		0 },
581     { STR_AND_LEN("global"),	NULL,		doGlobal,	0 },
582     { STR_AND_LEN("load"),	doLoad,		NULL,		1 },
583     { STR_AND_LEN("lua"),	doLua,		NULL,		1 },
584     { STR_AND_LEN("macrobody"),	doBody,		NULL,		1 },
585     { STR_AND_LEN("quote"),	doFoo,		NULL,		1 },
586     { STR_AND_LEN("shrink"),	doFoo,		NULL,		1 },
587     { STR_AND_LEN("suffix"),	doFoo,		NULL,		1 },
588     { STR_AND_LEN("trace"),	doTrace,	NULL,		0 },
589     { STR_AND_LEN("u2p"),	doFoo,		NULL,		1 },
590     { STR_AND_LEN("uncompress"),doUncompress,	NULL,		1 },
591     { STR_AND_LEN("undefine"),	NULL,		doUndefine,	0 },
592     { STR_AND_LEN("url2path"),	doFoo,		NULL,		1 },
593     { STR_AND_LEN("verbose"),	doVerbose,	NULL,		1 },
594     { STR_AND_LEN("warn"),	doOutput,	NULL,		1 },
595 };
596 static const size_t numbuiltins = sizeof(builtinmacros)/sizeof(*builtinmacros);
597 
namecmp(const void * name1,const void * name2)598 static int namecmp(const void *name1, const void *name2)
599 {
600     struct builtins_s *n1 = (struct builtins_s *)name1;
601     struct builtins_s *n2 = (struct builtins_s *)name2;
602 
603     int rc = strncmp(n1->name, n2->name, n1->len);
604     if (rc == 0)
605 	rc = n1->len - n2->len;
606     return rc;
607 }
608 
609 /**
610  * Return a pointer to the built-in macro with the given name
611  * @param name		macro name
612  * @param nlen		name length
613  * @return		pointer to the built-in macro or NULL if not found
614  */
lookupBuiltin(const char * name,size_t nlen)615 static const struct builtins_s* lookupBuiltin(const char *name, size_t nlen)
616 {
617     struct builtins_s macro = {
618 	.name = name,
619 	.len = nlen,
620     };
621 
622     return bsearch(&macro, builtinmacros, numbuiltins, sizeof(*builtinmacros),
623 		    namecmp);
624 }
625 
626 static int
validName(MacroBuf mb,const char * name,size_t namelen,const char * action)627 validName(MacroBuf mb, const char *name, size_t namelen, const char *action)
628 {
629     int rc = 0;
630     int c;
631 
632     /* Names must start with alphabetic or _ and be at least 3 chars */
633     if (!((c = *name) && (risalpha(c) || c == '_') && (namelen) > 2)) {
634 	mbErr(mb, 1, _("Macro %%%s has illegal name (%s)\n"), name, action);
635 	goto exit;
636     }
637 
638     if (lookupBuiltin(name, namelen)) {
639 	mbErr(mb, 1, _("Macro %%%s is a built-in (%s)\n"), name, action);
640 	goto exit;
641     }
642 
643     rc = 1;
644 
645 exit:
646     return rc;
647 }
648 
649 /**
650  * Parse (and execute) new macro definition.
651  * @param mb		macro expansion state
652  * @param se		macro definition to parse
653  * @param level		macro recursion level
654  * @param expandbody	should body be expanded?
655  * @return		address to continue parsing
656  */
657 static const char *
doDefine(MacroBuf mb,const char * se,int level,int expandbody)658 doDefine(MacroBuf mb, const char * se, int level, int expandbody)
659 {
660     const char *s = se;
661     char *buf = xmalloc(strlen(s) + 3); /* Some leeway for termination issues... */
662     char *n = buf, *ne = n;
663     char *o = NULL, *oe;
664     char *b, *be, *ebody = NULL;
665     int c;
666     int oc = ')';
667     const char *sbody; /* as-is body start */
668     int rc = 1; /* assume failure */
669 
670     /* Copy name */
671     COPYNAME(ne, s, c);
672 
673     /* Copy opts (if present) */
674     oe = ne + 1;
675     if (*s == '(') {
676 	s++;	/* skip ( */
677 	/* Options must be terminated with ')' */
678 	if (strchr(s, ')')) {
679 	    o = oe;
680 	    COPYOPTS(oe, s, oc);
681 	    s++;	/* skip ) */
682 	} else {
683 	    mbErr(mb, 1, _("Macro %%%s has unterminated opts\n"), n);
684 	    goto exit;
685 	}
686     }
687 
688     /* Copy body, skipping over escaped newlines */
689     b = be = oe + 1;
690     sbody = s;
691     SKIPBLANK(s, c);
692     if (c == '{') {	/* XXX permit silent {...} grouping */
693 	if ((se = matchchar(s, c, '}')) == NULL) {
694 	    mbErr(mb, 1, _("Macro %%%s has unterminated body\n"), n);
695 	    se = s;	/* XXX W2DO? */
696 	    goto exit;
697 	}
698 	s++;	/* XXX skip { */
699 	strncpy(b, s, (se - 1 - s));
700 	b[se - 1 - s] = '\0';
701 	be += strlen(b);
702 	s = se;	/* move scan forward */
703     } else {	/* otherwise free-field */
704 	int bc = 0, pc = 0, xc = 0;
705 	while (*s && (bc || pc || !iseol(*s))) {
706 	    switch (*s) {
707 		case '\\':
708 		    switch (*(s+1)) {
709 			case '\0': break;
710 			default: s++; break;
711 		    }
712 		    break;
713 		case '%':
714 		    switch (*(s+1)) {
715 			case '{': *be++ = *s++; bc++; break;
716 			case '(': *be++ = *s++; pc++; break;
717 			case '[': *be++ = *s++; xc++; break;
718 			case '%': *be++ = *s++; break;
719 		    }
720 		    break;
721 		case '{': if (bc > 0) bc++; break;
722 		case '}': if (bc > 0) bc--; break;
723 		case '(': if (pc > 0) pc++; break;
724 		case ')': if (pc > 0) pc--; break;
725 		case '[': if (xc > 0) xc++; break;
726 		case ']': if (xc > 0) xc--; break;
727 	    }
728 	    *be++ = *s++;
729 	}
730 	*be = '\0';
731 
732 	if (bc || pc || xc) {
733 	    mbErr(mb, 1, _("Macro %%%s has unterminated body\n"), n);
734 	    se = s;	/* XXX W2DO? */
735 	    goto exit;
736 	}
737 
738 	/* Trim trailing blanks/newlines */
739 	while (--be >= b && (c = *be) && (isblank(c) || iseol(c)))
740 	    {};
741 	*(++be) = '\0';	/* one too far */
742     }
743 
744     /* Move scan over body */
745     while (iseol(*s))
746 	s++;
747     se = s;
748 
749     if (!validName(mb, n, ne - n, expandbody ? "%global": "%define"))
750 	goto exit;
751 
752     if ((be - b) < 1) {
753 	mbErr(mb, 1, _("Macro %%%s has empty body\n"), n);
754 	goto exit;
755     }
756 
757     if (!isblank(*sbody) && !(*sbody == '\\' && iseol(sbody[1])))
758 	mbErr(mb, 0, _("Macro %%%s needs whitespace before body\n"), n);
759 
760     if (expandbody) {
761 	if (expandThis(mb, b, 0, &ebody)) {
762 	    mbErr(mb, 1, _("Macro %%%s failed to expand\n"), n);
763 	    goto exit;
764 	}
765 	b = ebody;
766     }
767 
768     pushMacro(mb->mc, n, o, b, level, ME_NONE);
769     rc = 0;
770 
771 exit:
772     if (rc)
773 	mb->error = 1;
774     _free(buf);
775     _free(ebody);
776     return se;
777 }
778 
779 /**
780  * Parse (and execute) macro undefinition.
781  * @param mb		macro expansion state
782  * @param se		macro name to undefine
783  * @return		address to continue parsing
784  */
785 static const char *
doUndefine(MacroBuf mb,const char * se)786 doUndefine(MacroBuf mb, const char * se)
787 {
788     const char *s = se;
789     char *buf = xmalloc(strlen(s) + 1);
790     char *n = buf, *ne = n;
791     int c;
792 
793     COPYNAME(ne, s, c);
794 
795     /* Move scan over body */
796     while (iseol(*s))
797 	s++;
798     se = s;
799 
800     if (!validName(mb, n, ne - n, "%undefine")) {
801 	mb->error = 1;
802 	goto exit;
803     }
804 
805     popMacro(mb->mc, n);
806 
807 exit:
808     _free(buf);
809     return se;
810 }
811 
doDef(MacroBuf mb,const char * se)812 static const char * doDef(MacroBuf mb, const char * se)
813 {
814     return doDefine(mb, se, mb->level, 0);
815 }
816 
doGlobal(MacroBuf mb,const char * se)817 static const char * doGlobal(MacroBuf mb, const char * se)
818 {
819     return doDefine(mb, se, RMIL_GLOBAL, 1);
820 }
821 
doDump(MacroBuf mb,const char * se)822 static const char * doDump(MacroBuf mb, const char * se)
823 {
824     rpmDumpMacroTable(mb->mc, NULL);
825     while (iseol(*se))
826 	se++;
827     return se;
828 }
829 
830 
831 /**
832  * Free parsed arguments for parameterized macro.
833  * @param mb		macro expansion state
834  */
835 static void
freeArgs(MacroBuf mb)836 freeArgs(MacroBuf mb)
837 {
838     rpmMacroContext mc = mb->mc;
839 
840     /* Delete dynamic macro definitions */
841     for (int i = 0; i < mc->n; i++) {
842 	rpmMacroEntry me = mc->tab[i];
843 	assert(me);
844 	if (me->level < mb->level)
845 	    continue;
846 	/* Warn on defined but unused non-automatic, scoped macros */
847 	if (!(me->flags & (ME_AUTO|ME_USED))) {
848 	    mbErr(mb, 0, _("Macro %%%s defined but not used within scope\n"),
849 			me->name);
850 	    /* Only whine once */
851 	    me->flags |= ME_USED;
852 	}
853 
854 	/* compensate if the slot is to go away */
855 	if (me->prev == NULL)
856 	    i--;
857 	popMacro(mc, me->name);
858     }
859     mb->level--;
860 }
861 
splitQuoted(ARGV_t * av,const char * str,const char * seps)862 static void splitQuoted(ARGV_t *av, const char * str, const char * seps)
863 {
864     const int qchar = 0x1f; /* ASCII unit separator */
865     const char *s = str;
866     const char *start = str;
867     int quoted = 0;
868 
869     while (start != NULL) {
870 	if (!quoted && strchr(seps, *s)) {
871 	    size_t slen = s - start;
872 	    /* quoted arguments are always kept, otherwise skip empty args */
873 	    if (slen > 0) {
874 		char *d, arg[slen + 1];
875 		const char *t;
876 		for (d = arg, t = start; t - start < slen; t++) {
877 		    if (*t == qchar)
878 			continue;
879 		    *d++ = *t;
880 		}
881 		arg[d - arg] = '\0';
882 		argvAdd(av, arg);
883 	    }
884 	    start = s + 1;
885 	}
886 	if (*s == qchar)
887 	    quoted = !quoted;
888 	else if (*s == '\0')
889 	    start = NULL;
890 	s++;
891     }
892 }
893 
894 /**
895  * Parse arguments (to next new line) for parameterized macro.
896  * @todo Use popt rather than getopt to parse args.
897  * @param mb		macro expansion state
898  * @param me		macro entry slot
899  * @param se		arguments to parse
900  * @param lastc		stop parsing at lastc
901  * @return		address to continue parsing
902  */
903 static const char *
grabArgs(MacroBuf mb,const rpmMacroEntry me,const char * se,const char * lastc)904 grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
905 		const char * lastc)
906 {
907     const char *cont = NULL;
908     const char *opts;
909     char *args = NULL;
910     ARGV_t argv = NULL;
911     int argc = 0;
912     int c;
913 
914     /*
915      * Prepare list of call arguments, starting with macro name as argv[0].
916      * Make a copy of se up to lastc string that we can pass to argvSplit().
917      * Append the results to main argv.
918      */
919     argvAdd(&argv, me->name);
920     if (lastc) {
921 	char *s = NULL;
922 
923 	/* Expand possible macros in arguments */
924 	expandThis(mb, se, lastc-se, &s);
925 	splitQuoted(&argv, s, " \t");
926 	free(s);
927 
928 	cont = (*lastc == '\0') || (*lastc == '\n' && *(lastc-1) != '\\') ?
929 	       lastc : lastc + 1;
930     }
931 
932     /* Bump call depth on entry before first macro define */
933     mb->level++;
934 
935     /* Setup macro name as %0 */
936     pushMacro(mb->mc, "0", NULL, me->name, mb->level, ME_AUTO | ME_LITERAL);
937 
938     /*
939      * The macro %* analoguous to the shell's $* means "Pass all non-macro
940      * parameters." Consequently, there needs to be a macro that means "Pass all
941      * (including macro parameters) options". This is useful for verifying
942      * parameters during expansion and yet transparently passing all parameters
943      * through for higher level processing (e.g. %description and/or %setup).
944      * This is the (potential) justification for %{**} ...
945     */
946     args = argvJoin(argv + 1, " ");
947     pushMacro(mb->mc, "**", NULL, args, mb->level, ME_AUTO | ME_LITERAL);
948     free(args);
949 
950     /*
951      * POSIX states optind must be 1 before any call but glibc uses 0
952      * to (re)initialize getopt structures, eww.
953      */
954 #ifdef __GLIBC__
955     optind = 0;
956 #else
957     optind = 1;
958 #endif
959 
960     opts = me->opts;
961     argc = argvCount(argv);
962 
963     /* Define option macros. */
964     while ((c = getopt(argc, argv, opts)) != -1)
965     {
966 	char *name = NULL, *body = NULL;
967 	if (c == '?' || strchr(opts, c) == NULL) {
968 	    mbErr(mb, 1, _("Unknown option %c in %s(%s)\n"),
969 			(char)optopt, me->name, opts);
970 	    goto exit;
971 	}
972 
973 	rasprintf(&name, "-%c", c);
974 	if (optarg) {
975 	    rasprintf(&body, "-%c %s", c, optarg);
976 	} else {
977 	    rasprintf(&body, "-%c", c);
978 	}
979 	pushMacro(mb->mc, name, NULL, body, mb->level, ME_AUTO | ME_LITERAL);
980 	free(name);
981 	free(body);
982 
983 	if (optarg) {
984 	    rasprintf(&name, "-%c*", c);
985 	    pushMacro(mb->mc, name, NULL, optarg, mb->level, ME_AUTO | ME_LITERAL);
986 	    free(name);
987 	}
988     }
989 
990     /* Add argument count (remaining non-option items) as macro. */
991     {	char *ac = NULL;
992     	rasprintf(&ac, "%d", (argc - optind));
993 	pushMacro(mb->mc, "#", NULL, ac, mb->level, ME_AUTO | ME_LITERAL);
994 	free(ac);
995     }
996 
997     /* Add macro for each argument */
998     if (argc - optind) {
999 	for (c = optind; c < argc; c++) {
1000 	    char *name = NULL;
1001 	    rasprintf(&name, "%d", (c - optind + 1));
1002 	    pushMacro(mb->mc, name, NULL, argv[c], mb->level, ME_AUTO | ME_LITERAL);
1003 	    free(name);
1004 	}
1005     }
1006 
1007     /* Add concatenated unexpanded arguments as yet another macro. */
1008     args = argvJoin(argv + optind, " ");
1009     pushMacro(mb->mc, "*", NULL, args ? args : "", mb->level, ME_AUTO | ME_LITERAL);
1010     free(args);
1011 
1012 exit:
1013     argvFree(argv);
1014     return cont;
1015 }
1016 
doBody(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1017 static void doBody(MacroBuf mb, int chkexist, int negate,
1018 		const char * f, size_t fn, const char * g, size_t gn)
1019 {
1020     if (gn > 0) {
1021 	char *buf = NULL;
1022 	if (expandThis(mb, g, gn, &buf) == 0) {
1023 	    rpmMacroEntry *mep = findEntry(mb->mc, buf, 0, NULL);
1024 	    if (mep) {
1025 		mbAppendStr(mb, (*mep)->body);
1026 	    } else {
1027 		mbErr(mb, 1, _("no such macro: '%s'\n"), buf);
1028 	    }
1029 	    free(buf);
1030 	}
1031     }
1032 }
1033 
doOutput(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1034 static void doOutput(MacroBuf mb, int chkexist, int negate, const char * f, size_t fn, const char * g, size_t gn)
1035 {
1036     char *buf = NULL;
1037     int loglevel = RPMLOG_NOTICE; /* assume echo */
1038     if (STREQ("error", f, fn)) {
1039 	loglevel = RPMLOG_ERR;
1040 	mb->error = 1;
1041     } else if (STREQ("warn", f, fn)) {
1042 	loglevel = RPMLOG_WARNING;
1043     }
1044     if (gn == 0)
1045 	g = "";
1046 
1047     (void) expandThis(mb, g, gn, &buf);
1048     rpmlog(loglevel, "%s\n", buf);
1049     _free(buf);
1050 }
1051 
doLua(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1052 static void doLua(MacroBuf mb, int chkexist, int negate, const char * f, size_t fn, const char * g, size_t gn)
1053 {
1054 #ifdef WITH_LUA
1055     rpmlua lua = NULL; /* Global state. */
1056     char *scriptbuf = xmalloc(gn + 1);
1057     char *printbuf;
1058     rpmMacroContext mc = mb->mc;
1059     int odepth = mc->depth;
1060     int olevel = mc->level;
1061 
1062     if (g != NULL && gn > 0)
1063 	memcpy(scriptbuf, g, gn);
1064     scriptbuf[gn] = '\0';
1065     rpmluaPushPrintBuffer(lua);
1066     mc->depth = mb->depth;
1067     mc->level = mb->level;
1068     if (rpmluaRunScript(lua, scriptbuf, NULL) == -1)
1069 	mb->error = 1;
1070     mc->depth = odepth;
1071     mc->level = olevel;
1072     printbuf = rpmluaPopPrintBuffer(lua);
1073     if (printbuf) {
1074 	mbAppendStr(mb, printbuf);
1075 	free(printbuf);
1076     }
1077     free(scriptbuf);
1078 #else
1079     mbErr(mb, 1, _("<lua> scriptlet support not built in\n"));
1080 #endif
1081 }
1082 
1083 static void
doSP(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1084 doSP(MacroBuf mb, int chkexist, int negate,
1085 	    const char * f, size_t fn, const char * g, size_t gn)
1086 {
1087     const char *b = "";
1088     char *buf = NULL;
1089     char *s = NULL;
1090 
1091     if (gn > 0) {
1092 	expandThis(mb, g, gn, &buf);
1093 	b = buf;
1094     }
1095 
1096     s = rstrscat(NULL, (*f == 'S') ? "%SOURCE" : "%PATCH", b, NULL);
1097     expandMacro(mb, s, 0);
1098     free(s);
1099     free(buf);
1100 }
1101 
doUncompress(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1102 static void doUncompress(MacroBuf mb, int chkexist, int negate,
1103 		const char * f, size_t fn, const char * g, size_t gn)
1104 {
1105     rpmCompressedMagic compressed = COMPRESSED_OTHER;
1106     char *b, *be, *buf = NULL;
1107     int c;
1108 
1109     if (gn) {
1110 	expandThis(mb, g, gn, &buf);
1111 	for (b = buf; (c = *b) && isblank(c);)
1112 	    b++;
1113 	for (be = b; (c = *be) && !isblank(c);)
1114 	    be++;
1115 	*be++ = '\0';
1116     }
1117 
1118     if (gn == 0 || *b == '\0')
1119 	goto exit;
1120 
1121     if (rpmFileIsCompressed(b, &compressed))
1122 	mb->error = 1;
1123 
1124     switch (compressed) {
1125     default:
1126     case COMPRESSED_NOT:
1127 	expandMacro(mb, "%__cat ", 0);
1128 	break;
1129     case COMPRESSED_OTHER:
1130 	expandMacro(mb, "%__gzip -dc ", 0);
1131 	break;
1132     case COMPRESSED_BZIP2:
1133 	expandMacro(mb, "%__bzip2 -dc ", 0);
1134 	break;
1135     case COMPRESSED_ZIP:
1136 	expandMacro(mb, "%__unzip ", 0);
1137 	break;
1138     case COMPRESSED_LZMA:
1139     case COMPRESSED_XZ:
1140 	expandMacro(mb, "%__xz -dc ", 0);
1141 	break;
1142     case COMPRESSED_LZIP:
1143 	expandMacro(mb, "%__lzip -dc ", 0);
1144 	break;
1145     case COMPRESSED_LRZIP:
1146 	expandMacro(mb, "%__lrzip -dqo- ", 0);
1147 	break;
1148     case COMPRESSED_7ZIP:
1149 	expandMacro(mb, "%__7zip x ", 0);
1150 	break;
1151     case COMPRESSED_ZSTD:
1152 	expandMacro(mb, "%__zstd -dc ", 0);
1153 	break;
1154     }
1155     mbAppendStr(mb, buf);
1156 
1157 exit:
1158     free(buf);
1159 }
1160 
doExpand(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1161 static void doExpand(MacroBuf mb, int chkexist, int negate,
1162 		    const char * f, size_t fn, const char * g, size_t gn)
1163 {
1164     if (gn > 0) {
1165 	char *buf;
1166 	expandThis(mb, g, gn, &buf);
1167 	expandMacro(mb, buf, 0);
1168 	free(buf);
1169     }
1170 }
1171 
doVerbose(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1172 static void doVerbose(MacroBuf mb, int chkexist, int negate,
1173 		const char * f, size_t fn, const char * g, size_t gn)
1174 {
1175     int verbose = (rpmIsVerbose() != 0);
1176     /* Don't expand %{verbose:...} argument on false condition */
1177     if (verbose != negate) {
1178 	char *buf = NULL;
1179 	expandThis(mb, g, gn, &buf);
1180 	mbAppendStr(mb, buf);
1181 	free(buf);
1182     }
1183 }
1184 
1185 /**
1186  * Execute macro primitives.
1187  * @param mb		macro expansion state
1188  * @param chkexist	unused
1189  * @param negate	should logic be inverted?
1190  * @param f		beginning of field f
1191  * @param fn		length of field f
1192  * @param g		beginning of field g
1193  * @param gn		length of field g
1194  */
1195 static void
doFoo(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1196 doFoo(MacroBuf mb, int chkexist, int negate, const char * f, size_t fn,
1197 		const char * g, size_t gn)
1198 {
1199     char *buf = NULL;
1200     char *b = NULL;
1201     int expand = (g != NULL && gn > 0);
1202 
1203     if (expand) {
1204 	(void) expandThis(mb, g, gn, &buf);
1205     } else {
1206 	buf = xmalloc(MACROBUFSIZ + fn + gn);
1207 	buf[0] = '\0';
1208     }
1209     if (STREQ("basename", f, fn)) {
1210 	if ((b = strrchr(buf, '/')) == NULL)
1211 	    b = buf;
1212 	else
1213 	    b++;
1214     } else if (STREQ("dirname", f, fn)) {
1215 	if ((b = strrchr(buf, '/')) != NULL)
1216 	    *b = '\0';
1217 	b = buf;
1218     } else if (STREQ("shrink", f, fn)) {
1219 	/*
1220 	 * shrink body by removing all leading and trailing whitespaces and
1221 	 * reducing intermediate whitespaces to a single space character.
1222 	 */
1223 	size_t i = 0, j = 0;
1224 	size_t buflen = strlen(buf);
1225 	int was_space = 0;
1226 	while (i < buflen) {
1227 	    if (risspace(buf[i])) {
1228 		was_space = 1;
1229 		i++;
1230 		continue;
1231 	    } else if (was_space) {
1232 		was_space = 0;
1233 		if (j > 0) /* remove leading blanks at all */
1234 		    buf[j++] = ' ';
1235 	    }
1236 	    buf[j++] = buf[i++];
1237 	}
1238 	buf[j] = '\0';
1239 	b = buf;
1240     } else if (STREQ("quote", f, fn)) {
1241 	char *quoted = NULL;
1242 	rasprintf(&quoted, "%c%s%c", 0x1f, buf, 0x1f);
1243 	free(buf);
1244 	b = buf = quoted;
1245     } else if (STREQ("suffix", f, fn)) {
1246 	if ((b = strrchr(buf, '.')) != NULL)
1247 	    b++;
1248     } else if (STREQ("expr", f, fn)) {
1249 	char *expr = rpmExprStrFlags(buf, 0);
1250 	if (expr) {
1251 	    free(buf);
1252 	    b = buf = expr;
1253 	} else {
1254 	    mb->error = 1;
1255 	}
1256     } else if (STREQ("url2path", f, fn) || STREQ("u2p", f, fn)) {
1257 	(void)urlPath(buf, (const char **)&b);
1258 	if (*b == '\0') b = "/";
1259     } else if (STREQ("getenv", f, fn)) {
1260 	b = getenv(buf);
1261     } else if (STREQ("getconfdir", f, fn)) {
1262 	sprintf(buf, "%s", rpmConfigDir());
1263 	b = buf;
1264     } else if (STREQ("getncpus", f, fn)) {
1265 	sprintf(buf, "%u", getncpus());
1266 	b = buf;
1267     }
1268 
1269     if (b) {
1270 	mbAppendStr(mb, b);
1271     }
1272     free(buf);
1273 }
1274 
doLoad(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1275 static void doLoad(MacroBuf mb, int chkexist, int negate,
1276 		    const char * f, size_t fn, const char * g, size_t gn)
1277 {
1278     char *arg = NULL;
1279     if (g && gn > 0 && expandThis(mb, g, gn, &arg) == 0) {
1280 	/* Print failure iff %{load:...} or %{!?load:...} */
1281 	if (loadMacroFile(mb->mc, arg) && chkexist == negate) {
1282 	    mbErr(mb, 1, _("failed to load macro file %s\n"), arg);
1283 	}
1284     }
1285     free(arg);
1286 }
1287 
doTrace(MacroBuf mb,int chkexist,int negate,const char * f,size_t fn,const char * g,size_t gn)1288 static void doTrace(MacroBuf mb, int chkexist, int negate,
1289 		    const char * f, size_t fn, const char * g, size_t gn)
1290 {
1291     mb->expand_trace = mb->macro_trace = (negate ? 0 : mb->depth);
1292     if (mb->depth == 1) {
1293 	print_macro_trace = mb->macro_trace;
1294 	print_expand_trace = mb->expand_trace;
1295     }
1296 }
1297 
setNegateAndCheck(const char * str,int * pnegate,int * pchkexist)1298 static const char *setNegateAndCheck(const char *str, int *pnegate, int *pchkexist) {
1299 
1300     *pnegate = 0;
1301     *pchkexist = 0;
1302     while ((*str == '?') || (*str == '!')) {
1303 	if (*str == '!')
1304 	    *pnegate = !*pnegate;
1305 	else
1306 	    (*pchkexist)++;
1307 	str++;
1308     }
1309     return str;
1310 }
1311 
1312 /**
1313  * Find the end of a macro call
1314  * @param str           pointer to the character after the initial '%'
1315  * @return              pointer to the next character after the macro
1316  */
1317 RPM_GNUC_INTERNAL
findMacroEnd(const char * str)1318 const char *findMacroEnd(const char *str)
1319 {
1320     int c;
1321     if (*str == '(')
1322 	return matchchar(str, *str, ')');
1323     if (*str == '{')
1324 	return matchchar(str, *str, '}');
1325     if (*str == '[')
1326 	return matchchar(str, *str, ']');
1327     while (*str == '?' || *str == '!')
1328 	str++;
1329     if (*str == '-')				/* %-f */
1330 	str++;
1331     while ((c = *str) && (risalnum(c) || c == '_'))
1332 	str++;
1333     if (*str == '*' && str[1] == '*')		/* %** */
1334 	str += 2;
1335     else if (*str == '*' || *str == '#')	/* %* and %# */
1336 	str++;
1337     return str;
1338 }
1339 
1340 /**
1341  * The main macro recursion loop.
1342  * @param mb		macro expansion state
1343  * @param src		string to expand
1344  * @param slen		length of string buffer
1345  * @return		0 on success, 1 on failure
1346  */
1347 static int
expandMacro(MacroBuf mb,const char * src,size_t slen)1348 expandMacro(MacroBuf mb, const char *src, size_t slen)
1349 {
1350     rpmMacroEntry *mep;
1351     rpmMacroEntry me;
1352     const char *s = src, *se;
1353     const char *f, *fe;
1354     const char *g, *ge;
1355     size_t fn, gn, tpos;
1356     int c;
1357     int negate;
1358     const char * lastc;
1359     int chkexist;
1360     char *source = NULL;
1361     int store_macro_trace;
1362     int store_expand_trace;
1363 
1364     /*
1365      * Always make a (terminated) copy of the source string.
1366      * Beware: avoiding the copy when src is known \0-terminated seems like
1367      * an obvious opportunity for optimization, but doing that breaks
1368      * a special case of macro undefining itself.
1369      */
1370     if (!slen)
1371 	slen = strlen(src);
1372     source = xmalloc(slen + 1);
1373     strncpy(source, src, slen);
1374     source[slen] = '\0';
1375     s = source;
1376 
1377     if (mb->buf == NULL) {
1378 	size_t blen = MACROBUFSIZ + slen;
1379 	mb->buf = xmalloc(blen + 1);
1380 	mb->buf[0] = '\0';
1381 	mb->tpos = 0;
1382 	mb->nb = blen;
1383     }
1384     tpos = mb->tpos; /* save expansion pointer for printExpand */
1385     store_macro_trace = mb->macro_trace;
1386     store_expand_trace = mb->expand_trace;
1387 
1388     if (++mb->depth > max_macro_depth) {
1389 	mbErr(mb, 1,
1390 		_("Too many levels of recursion in macro expansion. It is likely caused by recursive macro declaration.\n"));
1391 	mb->depth--;
1392 	mb->expand_trace = 1;
1393 	goto exit;
1394     }
1395 
1396     while (mb->error == 0 && (c = *s) != '\0') {
1397 	const struct builtins_s* builtin = NULL;
1398 	s++;
1399 	/* Copy text until next macro */
1400 	switch (c) {
1401 	case '%':
1402 	    if (*s) {	/* Ensure not end-of-string. */
1403 		if (*s != '%')
1404 		    break;
1405 		s++;	/* skip first % in %% */
1406 	    }
1407 	default:
1408 	    mbAppend(mb, c);
1409 	    continue;
1410 	    break;
1411 	}
1412 
1413 	/* Expand next macro */
1414 	f = fe = NULL;
1415 	g = ge = NULL;
1416 	if (mb->depth > 1)	/* XXX full expansion for outermost level */
1417 	    tpos = mb->tpos;	/* save expansion pointer for printExpand */
1418 	lastc = NULL;
1419 	if ((se = findMacroEnd(s)) == NULL) {
1420 	    mbErr(mb, 1, _("Unterminated %c: %s\n"), (char)*s, s);
1421 	    continue;
1422 	}
1423 
1424 	switch (*s) {
1425 	default:		/* %name substitution */
1426 	    f = s = setNegateAndCheck(s, &negate, &chkexist);
1427 	    fe = se;
1428 	    /* For "%name " macros ... */
1429 	    if ((c = *fe) && isblank(c))
1430 		if ((lastc = strchr(fe,'\n')) == NULL)
1431 		    lastc = strchr(fe, '\0');
1432 	    break;
1433 	case '(':		/* %(...) shell escape */
1434 	    if (mb->macro_trace)
1435 		printMacro(mb, s, se);
1436 	    s++;	/* skip ( */
1437 	    doShellEscape(mb, s, (se - 1 - s));
1438 	    s = se;
1439 	    continue;
1440 	case '[':		/* %[...] expression expansion */
1441 	    if (mb->macro_trace)
1442 		printMacro(mb, s, se);
1443 	    s++;	/* skip [ */
1444 	    doExpressionExpansion(mb, s, (se - 1 - s));
1445 	    s = se;
1446 	    continue;
1447 	case '{':		/* %{...}/%{...:...} substitution */
1448 	    f = s+1;	/* skip { */
1449 	    f = setNegateAndCheck(f, &negate, &chkexist);
1450 	    for (fe = f; (c = *fe) && !strchr(" :}", c);)
1451 		fe++;
1452 	    switch (c) {
1453 	    case ':':
1454 		g = fe + 1;
1455 		ge = se - 1;
1456 		break;
1457 	    case ' ':
1458 		lastc = se-1;
1459 		break;
1460 	    default:
1461 		break;
1462 	    }
1463 	    break;
1464 	}
1465 
1466 	/* XXX Everything below expects fe > f */
1467 	fn = (fe - f);
1468 	gn = (ge - g);
1469 	if ((fe - f) <= 0) {
1470 	    /* XXX Process % in unknown context */
1471 	    c = '%';	/* XXX only need to save % */
1472 	    mbAppend(mb, c);
1473 #if 0
1474 	    mbErr(mb, 1,
1475 		    _("A %% is followed by an unparseable macro\n"));
1476 #endif
1477 	    continue;
1478 	}
1479 
1480 	if (mb->macro_trace)
1481 	    printMacro(mb, s, se);
1482 
1483 	/* Expand builtin macros */
1484 	if ((builtin = lookupBuiltin(f, fn))) {
1485 	    if (builtin->havearg != (g != NULL)) {
1486 		mbErr(mb, 1, "%%%s: %s\n", builtin->name, builtin->havearg ?
1487 			_("argument expected") : _("unexpected argument"));
1488 		continue;
1489 	    }
1490 	    if (builtin->parse) {
1491 		s = builtin->parse(mb, se);
1492 	    } else {
1493 		builtin->func(mb, chkexist, negate, f, fn, g, gn);
1494 		s = se;
1495 	    }
1496 	    continue;
1497 	}
1498 
1499 	/* Expand defined macros */
1500 	mep = findEntry(mb->mc, f, fn, NULL);
1501 	me = (mep ? *mep : NULL);
1502 
1503 	if (me) {
1504 	    if ((me->flags & ME_AUTO) && mb->level > me->level) {
1505 		/* Ignore out-of-scope automatic macros */
1506 		me = NULL;
1507 	    } else {
1508 		/* If we looked up a macro, consider it used */
1509 		me->flags |= ME_USED;
1510 	    }
1511 	}
1512 
1513 	/* XXX Special processing for flags and existance test */
1514 	if (*f == '-' || chkexist) {
1515 	    if ((me == NULL && !negate) ||	/* Without existance, skip %{?...} */
1516 		    (me != NULL && negate)) {	/* With existance, skip %{!?...} */
1517 		s = se;
1518 		continue;
1519 	    }
1520 
1521 	    if (g && g < ge) {		/* Expand X in %{...:X} */
1522 		expandMacro(mb, g, gn);
1523 	    } else
1524 		if (me && me->body && *me->body) {/* Expand macro body */
1525 		    if ((me->flags & ME_LITERAL) != 0)
1526 			mbAppendStr(mb, me->body);
1527 		    else
1528 			expandMacro(mb, me->body, 0);
1529 		}
1530 	    s = se;
1531 	    continue;
1532 	}
1533 
1534 	if (me == NULL) {	/* leave unknown %... as is */
1535 	    /* XXX hack to permit non-overloaded %foo to be passed */
1536 	    c = '%';	/* XXX only need to save % */
1537 	    mbAppend(mb, c);
1538 	    continue;
1539 	}
1540 
1541 	/* Setup args for "%name " macros with opts */
1542 	if (me && me->opts != NULL) {
1543 	    const char *xe = grabArgs(mb, me, fe, lastc);
1544 	    if (xe != NULL)
1545 		se = xe;
1546 	}
1547 
1548 	/* Recursively expand body of macro */
1549 	if (me->body && *me->body) {
1550 	    if ((me->flags & ME_LITERAL) != 0)
1551 		mbAppendStr(mb, me->body);
1552 	    else
1553 		expandMacro(mb, me->body, 0);
1554 	}
1555 
1556 	/* Free args for "%name " macros with opts */
1557 	if (me->opts != NULL)
1558 	    freeArgs(mb);
1559 
1560 	s = se;
1561     }
1562 
1563     mb->buf[mb->tpos] = '\0';
1564     mb->depth--;
1565     if (mb->error != 0 || mb->expand_trace)
1566 	printExpansion(mb, mb->buf+tpos, mb->buf+mb->tpos);
1567     mb->macro_trace = store_macro_trace;
1568     mb->expand_trace = store_expand_trace;
1569 exit:
1570     _free(source);
1571     return mb->error;
1572 }
1573 
1574 
1575 /* =============================================================== */
1576 
doExpandMacros(rpmMacroContext mc,const char * src,int flags,char ** target)1577 static int doExpandMacros(rpmMacroContext mc, const char *src, int flags,
1578 			char **target)
1579 {
1580     MacroBuf mb = xcalloc(1, sizeof(*mb));
1581     int rc = 0;
1582 
1583     mb->buf = NULL;
1584     mb->depth = mc->depth;
1585     mb->level = mc->level;
1586     mb->macro_trace = print_macro_trace;
1587     mb->expand_trace = print_expand_trace;
1588     mb->mc = mc;
1589     mb->flags = flags;
1590 
1591     rc = expandMacro(mb, src, 0);
1592 
1593     mb->buf[mb->tpos] = '\0';	/* XXX just in case */
1594     /* expanded output is usually much less than alloced buffer, downsize */
1595     *target = xrealloc(mb->buf, mb->tpos + 1);
1596 
1597     _free(mb);
1598     return rc;
1599 }
1600 
pushMacro(rpmMacroContext mc,const char * n,const char * o,const char * b,int level,int flags)1601 static void pushMacro(rpmMacroContext mc,
1602 	const char * n, const char * o, const char * b, int level, int flags)
1603 {
1604     /* new entry */
1605     rpmMacroEntry me;
1606     /* pointer into me */
1607     char *p;
1608     /* calculate sizes */
1609     size_t olen = o ? strlen(o) : 0;
1610     size_t blen = b ? strlen(b) : 0;
1611     size_t mesize = sizeof(*me) + blen + 1 + (olen ? olen + 1 : 0);
1612 
1613     size_t pos;
1614     rpmMacroEntry *mep = findEntry(mc, n, 0, &pos);
1615     if (mep) {
1616 	/* entry with shared name */
1617 	me = xmalloc(mesize);
1618 	/* copy body */
1619 	me->body = p = me->arena;
1620 	if (blen)
1621 	    memcpy(p, b, blen + 1);
1622 	else
1623 	    *p = '\0';
1624 	p += blen + 1;
1625 	/* set name */
1626 	me->name = (*mep)->name;
1627     }
1628     else {
1629 	/* extend macro table */
1630 	const int delta = 256;
1631 	if (mc->n % delta == 0)
1632 	    mc->tab = xrealloc(mc->tab, sizeof(me) * (mc->n + delta));
1633 	/* shift pos+ entries to the right */
1634 	memmove(mc->tab + pos + 1, mc->tab + pos, sizeof(me) * (mc->n - pos));
1635 	mc->n++;
1636 	/* make slot */
1637 	mc->tab[pos] = NULL;
1638 	mep = &mc->tab[pos];
1639 	/* entry with new name */
1640 	size_t nlen = strlen(n);
1641 	me = xmalloc(mesize + nlen + 1);
1642 	/* copy body */
1643 	me->body = p = me->arena;
1644 	if (blen)
1645 	    memcpy(p, b, blen + 1);
1646 	else
1647 	    *p = '\0';
1648 	p += blen + 1;
1649 	/* copy name */
1650 	me->name = memcpy(p, n, nlen + 1);
1651 	p += nlen + 1;
1652     }
1653 
1654     /* copy options */
1655     if (olen)
1656 	me->opts = memcpy(p, o, olen + 1);
1657     else
1658 	me->opts = o ? "" : NULL;
1659     /* initialize */
1660     me->flags = flags;
1661     me->flags &= ~(ME_USED);
1662     me->level = level;
1663     /* push over previous definition */
1664     me->prev = *mep;
1665     *mep = me;
1666 }
1667 
popMacro(rpmMacroContext mc,const char * n)1668 static void popMacro(rpmMacroContext mc, const char * n)
1669 {
1670     size_t pos;
1671     rpmMacroEntry *mep = findEntry(mc, n, 0, &pos);
1672     if (mep == NULL)
1673 	return;
1674     /* parting entry */
1675     rpmMacroEntry me = *mep;
1676     assert(me);
1677     /* detach/pop definition */
1678     mc->tab[pos] = me->prev;
1679     /* shrink macro table */
1680     if (me->prev == NULL) {
1681 	mc->n--;
1682 	/* move pos+ elements to the left */
1683 	memmove(mc->tab + pos, mc->tab + pos + 1, sizeof(me) * (mc->n - pos));
1684 	/* deallocate */
1685 	if (mc->n == 0)
1686 	    mc->tab = _free(mc->tab);
1687     }
1688     /* comes in a single chunk */
1689     free(me);
1690 }
1691 
defineMacro(rpmMacroContext mc,const char * macro,int level)1692 static int defineMacro(rpmMacroContext mc, const char * macro, int level)
1693 {
1694     MacroBuf mb = xcalloc(1, sizeof(*mb));
1695     int rc;
1696 
1697     /* XXX just enough to get by */
1698     mb->mc = mc;
1699     (void) doDefine(mb, macro, level, 0);
1700     rc = mb->error;
1701     _free(mb);
1702     return rc;
1703 }
1704 
loadMacroFile(rpmMacroContext mc,const char * fn)1705 static int loadMacroFile(rpmMacroContext mc, const char * fn)
1706 {
1707     FILE *fd = fopen(fn, "r");
1708     size_t blen = MACROBUFSIZ;
1709     char *buf = xmalloc(blen);
1710     int rc = -1;
1711     int nfailed = 0;
1712     int lineno = 0;
1713     int nlines = 0;
1714 
1715     if (fd == NULL)
1716 	goto exit;
1717 
1718     pushMacro(mc, "__file_name", NULL, fn, RMIL_MACROFILES, ME_LITERAL);
1719 
1720     buf[0] = '\0';
1721     while ((nlines = rdcl(buf, blen, fd)) > 0) {
1722 	char c, *n;
1723 	char lnobuf[16];
1724 
1725 	lineno += nlines;
1726 	n = buf;
1727 	SKIPBLANK(n, c);
1728 
1729 	if (c != '%')
1730 		continue;
1731 	n++;	/* skip % */
1732 
1733 	snprintf(lnobuf, sizeof(lnobuf), "%d", lineno);
1734 	pushMacro(mc, "__file_lineno", NULL, lnobuf, RMIL_MACROFILES, ME_LITERAL);
1735 	if (defineMacro(mc, n, RMIL_MACROFILES))
1736 	    nfailed++;
1737 	popMacro(mc, "__file_lineno");
1738     }
1739     fclose(fd);
1740     popMacro(mc, "__file_name");
1741 
1742     rc = (nfailed > 0);
1743 
1744 exit:
1745     _free(buf);
1746     return rc;
1747 }
1748 
copyMacros(rpmMacroContext src,rpmMacroContext dst,int level)1749 static void copyMacros(rpmMacroContext src, rpmMacroContext dst, int level)
1750 {
1751     for (int i = 0; i < src->n; i++) {
1752 	rpmMacroEntry me = src->tab[i];
1753 	assert(me);
1754 	pushMacro(dst, me->name, me->opts, me->body, level, me->flags);
1755     }
1756 }
1757 
1758 /* External interfaces */
1759 
rpmExpandMacros(rpmMacroContext mc,const char * sbuf,char ** obuf,int flags)1760 int rpmExpandMacros(rpmMacroContext mc, const char * sbuf, char ** obuf, int flags)
1761 {
1762     char *target = NULL;
1763     int rc;
1764 
1765     mc = rpmmctxAcquire(mc);
1766     rc = doExpandMacros(mc, sbuf, flags, &target);
1767     rpmmctxRelease(mc);
1768 
1769     if (rc) {
1770 	free(target);
1771 	return -1;
1772     } else {
1773 	*obuf = target;
1774 	return 1;
1775     }
1776 }
1777 
1778 void
rpmDumpMacroTable(rpmMacroContext mc,FILE * fp)1779 rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
1780 {
1781     mc = rpmmctxAcquire(mc);
1782     if (fp == NULL) fp = stderr;
1783 
1784     fprintf(fp, "========================\n");
1785     for (int i = 0; i < mc->n; i++) {
1786 	rpmMacroEntry me = mc->tab[i];
1787 	assert(me);
1788 	fprintf(fp, "%3d%c %s", me->level,
1789 		    ((me->flags & ME_USED) ? '=' : ':'), me->name);
1790 	if (me->opts && *me->opts)
1791 		fprintf(fp, "(%s)", me->opts);
1792 	if (me->body && *me->body)
1793 		fprintf(fp, "\t%s", me->body);
1794 	fprintf(fp, "\n");
1795     }
1796     fprintf(fp, _("======================== active %d empty %d\n"),
1797 		mc->n, 0);
1798     rpmmctxRelease(mc);
1799 }
1800 
rpmPushMacroFlags(rpmMacroContext mc,const char * n,const char * o,const char * b,int level,rpmMacroFlags flags)1801 int rpmPushMacroFlags(rpmMacroContext mc,
1802 	      const char * n, const char * o, const char * b,
1803 	      int level, rpmMacroFlags flags)
1804 {
1805     mc = rpmmctxAcquire(mc);
1806     pushMacro(mc, n, o, b, level, flags & RPMMACRO_LITERAL ? ME_LITERAL : ME_NONE);
1807     rpmmctxRelease(mc);
1808     return 0;
1809 }
1810 
rpmPushMacro(rpmMacroContext mc,const char * n,const char * o,const char * b,int level)1811 int rpmPushMacro(rpmMacroContext mc,
1812 	      const char * n, const char * o, const char * b, int level)
1813 {
1814     return rpmPushMacroFlags(mc, n, o, b, level, RPMMACRO_DEFAULT);
1815 }
1816 
rpmPopMacro(rpmMacroContext mc,const char * n)1817 int rpmPopMacro(rpmMacroContext mc, const char * n)
1818 {
1819     mc = rpmmctxAcquire(mc);
1820     popMacro(mc, n);
1821     rpmmctxRelease(mc);
1822     return 0;
1823 }
1824 
1825 int
rpmDefineMacro(rpmMacroContext mc,const char * macro,int level)1826 rpmDefineMacro(rpmMacroContext mc, const char * macro, int level)
1827 {
1828     int rc;
1829     mc = rpmmctxAcquire(mc);
1830     rc = defineMacro(mc, macro, level);
1831     rpmmctxRelease(mc);
1832     return rc;
1833 }
1834 
rpmMacroIsDefined(rpmMacroContext mc,const char * n)1835 int rpmMacroIsDefined(rpmMacroContext mc, const char *n)
1836 {
1837     int defined = 0;
1838     if ((mc = rpmmctxAcquire(mc)) != NULL) {
1839 	if (findEntry(mc, n, 0, NULL))
1840 	    defined = 1;
1841 	rpmmctxRelease(mc);
1842     }
1843     return defined;
1844 }
1845 
rpmMacroIsParametric(rpmMacroContext mc,const char * n)1846 int rpmMacroIsParametric(rpmMacroContext mc, const char *n)
1847 {
1848     int parametric = 0;
1849     if ((mc = rpmmctxAcquire(mc)) != NULL) {
1850 	rpmMacroEntry *mep = findEntry(mc, n, 0, NULL);
1851 	if (mep && (*mep)->opts)
1852 	    parametric = 1;
1853 	rpmmctxRelease(mc);
1854     }
1855     return parametric;
1856 }
1857 
1858 void
rpmLoadMacros(rpmMacroContext mc,int level)1859 rpmLoadMacros(rpmMacroContext mc, int level)
1860 {
1861     rpmMacroContext gmc;
1862     if (mc == NULL || mc == rpmGlobalMacroContext)
1863 	return;
1864 
1865     gmc = rpmmctxAcquire(NULL);
1866     mc = rpmmctxAcquire(mc);
1867 
1868     copyMacros(mc, gmc, level);
1869 
1870     rpmmctxRelease(mc);
1871     rpmmctxRelease(gmc);
1872 }
1873 
1874 int
rpmLoadMacroFile(rpmMacroContext mc,const char * fn)1875 rpmLoadMacroFile(rpmMacroContext mc, const char * fn)
1876 {
1877     int rc;
1878 
1879     mc = rpmmctxAcquire(mc);
1880     rc = loadMacroFile(mc, fn);
1881     rpmmctxRelease(mc);
1882 
1883     return rc;
1884 }
1885 
1886 void
rpmInitMacros(rpmMacroContext mc,const char * macrofiles)1887 rpmInitMacros(rpmMacroContext mc, const char * macrofiles)
1888 {
1889     ARGV_t pattern, globs = NULL;
1890     rpmMacroContext climc;
1891 
1892     if (macrofiles == NULL)
1893 	return;
1894 
1895     argvSplit(&globs, macrofiles, ":");
1896     mc = rpmmctxAcquire(mc);
1897     for (pattern = globs; *pattern; pattern++) {
1898 	ARGV_t path, files = NULL;
1899 
1900 	/* Glob expand the macro file path element, expanding ~ to $HOME. */
1901 	if (rpmGlob(*pattern, NULL, &files) != 0) {
1902 	    continue;
1903 	}
1904 
1905 	/* Read macros from each file. */
1906 	for (path = files; *path; path++) {
1907 	    if (rpmFileHasSuffix(*path, ".rpmnew") ||
1908 		rpmFileHasSuffix(*path, ".rpmsave") ||
1909 		rpmFileHasSuffix(*path, ".rpmorig")) {
1910 		continue;
1911 	    }
1912 	    (void) loadMacroFile(mc, *path);
1913 	}
1914 	argvFree(files);
1915     }
1916     argvFree(globs);
1917 
1918     /* Reload cmdline macros */
1919     climc = rpmmctxAcquire(rpmCLIMacroContext);
1920     copyMacros(climc, mc, RMIL_CMDLINE);
1921     rpmmctxRelease(climc);
1922 
1923     rpmmctxRelease(mc);
1924 }
1925 
1926 void
rpmFreeMacros(rpmMacroContext mc)1927 rpmFreeMacros(rpmMacroContext mc)
1928 {
1929     mc = rpmmctxAcquire(mc);
1930     while (mc->n > 0) {
1931 	/* remove from the end to avoid memmove */
1932 	rpmMacroEntry me = mc->tab[mc->n - 1];
1933 	popMacro(mc, me->name);
1934     }
1935     rpmmctxRelease(mc);
1936 }
1937 
1938 char *
rpmExpand(const char * arg,...)1939 rpmExpand(const char *arg, ...)
1940 {
1941     size_t blen = 0;
1942     char *buf = NULL, *ret = NULL;
1943     char *pe;
1944     const char *s;
1945     va_list ap;
1946     rpmMacroContext mc;
1947 
1948     if (arg == NULL) {
1949 	ret = xstrdup("");
1950 	goto exit;
1951     }
1952 
1953     /* precalculate unexpanded size */
1954     va_start(ap, arg);
1955     for (s = arg; s != NULL; s = va_arg(ap, const char *))
1956 	blen += strlen(s);
1957     va_end(ap);
1958 
1959     buf = xmalloc(blen + 1);
1960     buf[0] = '\0';
1961 
1962     va_start(ap, arg);
1963     for (pe = buf, s = arg; s != NULL; s = va_arg(ap, const char *))
1964 	pe = stpcpy(pe, s);
1965     va_end(ap);
1966 
1967     mc = rpmmctxAcquire(NULL);
1968     (void) doExpandMacros(mc, buf, 0, &ret);
1969     rpmmctxRelease(mc);
1970 
1971     free(buf);
1972 exit:
1973     return ret;
1974 }
1975 
1976 int
rpmExpandNumeric(const char * arg)1977 rpmExpandNumeric(const char *arg)
1978 {
1979     char *val;
1980     int rc;
1981 
1982     if (arg == NULL)
1983 	return 0;
1984 
1985     val = rpmExpand(arg, NULL);
1986     if (!(val && *val != '%'))
1987 	rc = 0;
1988     else if (*val == 'Y' || *val == 'y')
1989 	rc = 1;
1990     else if (*val == 'N' || *val == 'n')
1991 	rc = 0;
1992     else {
1993 	char *end;
1994 	rc = strtol(val, &end, 0);
1995 	if (!(end && *end == '\0'))
1996 	    rc = 0;
1997     }
1998     free(val);
1999 
2000     return rc;
2001 }
2002