diff --git a/build/parseSpec.c b/build/parseSpec.c index ed5c3ef63..780f2f5d8 100644 --- a/build/parseSpec.c +++ b/build/parseSpec.c @@ -501,7 +501,7 @@ retry: else checkCondition = spec->readStack->readable; if (checkCondition) { - match = rpmExprBool(s); + match = rpmExprBool(s, 0); if (match < 0) { rpmlog(RPMLOG_ERR, _("%s:%d: bad %s condition: %s\n"), diff --git a/doc/manual/macros b/doc/manual/macros index 19c5d17e2..9ffb6940e 100644 --- a/doc/manual/macros +++ b/doc/manual/macros @@ -50,12 +50,7 @@ if the flag was present. The negative form, "%{!-f:Y}", expanding to (the expansion of) Y if -f was *not* present, is also supported. In addition to the "%{...}" form, shell expansion can be performed -using "%(shell command)". The expansion of "%(...)" is the output of -(the expansion of) ... fed to /bin/sh. For example, "%(date -+%%y%%m%%d)" expands to the string "YYMMDD" (final newline is -deleted). Note the 2nd % needed to escape the arguments to /bin/date. -There is currently an 8K limit on the size that this macro can expand -to. +using "%(shell command)". \section macros_builtin Builtin Macros @@ -232,6 +227,45 @@ replaced by the first argument to the macro, but only if the macro is invoked as "%mymacro 5". Invoking as "%{mymacro} 5" will not work as desired in this case. +\section macros_shell_expansion Shell Expansion + +Shell expansion can be performed using "%(shell command)". The expansion +of "%(...)" is the output of (the expansion of) ... fed to /bin/sh. +For example, "%(date +%%y%%m%%d)" expands to the string "YYMMDD" (final +newline is deleted). Note the 2nd % needed to escape the arguments to +/bin/date. + +\section macros_expression_expansion Expression Expansion + +Expression expansion can be performed using "%[expression]". An +expression consists of terms that can be combined using +operators. Rpm supports two kinds of terms, numbers made up +from digits and strings enclosed in double quotes. Rpm will +expand macros when evaluating terms. + +You can use the standard operators to combine terms: logical +operators &&, ||, !, relational operators !=, ==, <, > , <=, >=, +arithmetic operators +, -, /, *, the ternary operator ? :, and +parentheses. For example, "%[ 3 + 4 * (1 + %two) ]" will expand +to "15" if "%two" expands to "2". + +Note that the "%[expression]" expansion is different to the +"%{expr:expression}" macro. With the latter, the macros in the +expression are expanded first and then the expression is +evaluated (without re-expanding the terms). Thus +\verbatim + rpm --define 'foo 1 + 2' --eval '%{expr:%foo}' +\endverbatim +will print "3". Using '%[%foo]' instead will result in the +error that "1 + 2" is not a number. + +Doing the macro expansion when evaluating the terms has two +advantages. First, it allows rpm to do correct short-circuit +processing when evaluation logical operators. Second, the +expansion result does not influence the expression parsing, +e.g. '%["%file"] will even work if the "%file" macro expands +to a string that contains a double quote. + \section macros_commandline Command Line Options When the command line option "--define 'macroname value'" allows the diff --git a/rpmio/Makefile.am b/rpmio/Makefile.am index 63de85d89..34af18c89 100644 --- a/rpmio/Makefile.am +++ b/rpmio/Makefile.am @@ -22,7 +22,7 @@ librpmio_la_SOURCES = \ rpmpgp.c rpmsq.c rpmsw.c url.c \ rpmio_internal.h rpmhook.h \ rpmstring.c rpmfileutil.c rpmglob.c \ - rpmkeyring.c rpmstrpool.c + rpmkeyring.c rpmstrpool.c rpmmacro_internal.h if WITH_BEECRYPT librpmio_la_SOURCES += digest_beecrypt.c diff --git a/rpmio/expression.c b/rpmio/expression.c index 3da1bead3..53f7e4477 100644 --- a/rpmio/expression.c +++ b/rpmio/expression.c @@ -15,6 +15,7 @@ #include #include +#include "rpmio/rpmmacro_internal.h" #include "debug.h" /* #define DEBUG_PARSER 1 */ @@ -105,6 +106,7 @@ typedef struct _parseState { const char *p; /*!< current position in expression string */ int nextToken; /*!< current lookahead token */ Value tokenValue; /*!< valid when TOK_INTEGER or TOK_STRING */ + int flags; /*!< parser flags */ } *ParseState; static void exprErr(const struct _parseState *state, const char *msg, @@ -188,6 +190,40 @@ static const char *prToken(int val) } #endif /* DEBUG_PARSER */ +static char *getValuebuf(ParseState state, const char *p, size_t size) +{ + char *temp; + temp = xmalloc(size + 1); + memcpy(temp, p, size); + temp[size] = '\0'; + if (size && (state->flags & RPMEXPR_EXPAND) != 0) { + char *temp2 = NULL; + rpmExpandMacros(NULL, temp, &temp2, 0); + free(temp); + temp = temp2; + } + return temp; +} + +static size_t skipMacro(const char *p, size_t ts) +{ + const char *pe; + if (p[ts] == '%') + return ts + 1; /* %% is special */ + pe = findMacroEnd(p + ts); + return pe ? pe - p : strlen(p); +} + +static int wellformedInteger(const char *p) +{ + if (*p == '-') + p++; + for (; *p; p++) + if (!risdigit(*p)) + return 0; + return 1; +} + /** * @param state expression parser state */ @@ -196,6 +232,7 @@ static int rdToken(ParseState state) int token; Value v = NULL; const char *p = state->p; + int expand = (state->flags & RPMEXPR_EXPAND) != 0; /* Skip whitespace before the next token. */ while (*p && risspace(*p)) p++; @@ -279,14 +316,29 @@ static int rdToken(ParseState state) break; default: - if (risdigit(*p)) { + if (risdigit(*p) || (*p == '%' && expand)) { char *temp; size_t ts; - for (ts=1; p[ts] && risdigit(p[ts]); ts++); - temp = xmalloc(ts+1); - memcpy(temp, p, ts); - temp[ts] = '\0'; + for (ts=0; p[ts]; ts++) { + if (p[ts] == '%' && expand) + ts = skipMacro(p, ts + 1) - 1; + else if (!risdigit(p[ts])) + break; + } + temp = getValuebuf(state, p, ts); + if (!temp) + goto err; + /* make sure that the expanded buffer only contains digits */ + if (expand && !wellformedInteger(temp)) { + if (risalpha(*temp)) + exprErr(state, _("macro expansion returned a bare word, please use \"...\""), p + 1); + else + exprErr(state, _("macro expansion did not return an integer"), p + 1); + rpmlog(RPMLOG_ERR, _("expanded string: %s\n"), temp); + free(temp); + goto err; + } p += ts-1; token = TOK_INTEGER; v = valueMakeInteger(atoi(temp)); @@ -301,14 +353,19 @@ static int rdToken(ParseState state) size_t ts; p++; - for (ts=0; p[ts] && p[ts] != '\"'; ts++); + for (ts=0; p[ts]; ts++) { + if (p[ts] == '%' && expand) + ts = skipMacro(p, ts + 1) - 1; + else if (p[ts] == '\"') + break; + } if (p[ts] != '\"') { exprErr(state, _("unterminated string in expression"), p + ts + 1); goto err; } - temp = xmalloc(ts+1); - memcpy(temp, p, ts); - temp[ts] = '\0'; + temp = getValuebuf(state, p, ts); + if (!temp) + goto err; p += ts; token = TOK_STRING; v = valueMakeString( temp ); @@ -732,7 +789,7 @@ err: return NULL; } -int rpmExprBool(const char *expr) +int rpmExprBool(const char *expr, int flags) { struct _parseState state; int result = -1; @@ -744,6 +801,7 @@ int rpmExprBool(const char *expr) state.p = state.str = xstrdup(expr); state.nextToken = 0; state.tokenValue = NULL; + state.flags = flags; if (rdToken(&state)) goto exit; @@ -777,7 +835,7 @@ exit: return result; } -char *rpmExprStr(const char *expr) +char *rpmExprStr(const char *expr, int flags) { struct _parseState state; char *result = NULL; @@ -789,6 +847,7 @@ char *rpmExprStr(const char *expr) state.p = state.str = xstrdup(expr); state.nextToken = 0; state.tokenValue = NULL; + state.flags = flags; if (rdToken(&state)) goto exit; diff --git a/rpmio/macro.c b/rpmio/macro.c index 2012ade74..898afabe8 100644 --- a/rpmio/macro.c +++ b/rpmio/macro.c @@ -37,6 +37,7 @@ extern int optind; #include "rpmio/rpmlua.h" #endif +#include "rpmio/rpmmacro_internal.h" #include "debug.h" enum macroFlags_e { @@ -221,7 +222,7 @@ rdcl(char * buf, size_t size, FILE *f) char *q = buf - 1; /* initialize just before buffer. */ size_t nb = 0; size_t nread = 0; - int pc = 0, bc = 0; + int pc = 0, bc = 0, xc = 0; int nlines = 0; char *p = buf; @@ -247,6 +248,7 @@ rdcl(char * buf, size_t size, FILE *f) switch (*(p+1)) { case '{': p++, bc++; break; case '(': p++, pc++; break; + case '[': p++, xc++; break; case '%': p++; break; } break; @@ -254,9 +256,11 @@ rdcl(char * buf, size_t size, FILE *f) case '}': if (bc > 0) bc--; break; case '(': if (pc > 0) pc++; break; case ')': if (pc > 0) pc--; break; + case '[': if (xc > 0) xc++; break; + case ']': if (xc > 0) xc--; break; } } - if (nb == 0 || (*q != '\\' && !bc && !pc) || *(q+1) == '\0') { + if (nb == 0 || (*q != '\\' && !bc && !pc && !xc) || *(q+1) == '\0') { *(++q) = '\0'; /* trim trailing \r, \n */ break; } @@ -499,6 +503,28 @@ exit: _free(buf); } +/** + * Expand an expression into target buffer. + * @param mb macro expansion state + * @param expr expression + * @param len no. bytes in expression + */ +static void doExpressionExpansion(MacroBuf mb, const char * expr, size_t len) +{ + char *buf = xmalloc(len + 1); + char *result; + strncpy(buf, expr, len); + buf[len] = 0; + result = rpmExprStr(buf, RPMEXPR_EXPAND); + if (!result) { + mb->error = 1; + goto exit; + } + mbAppendStr(mb, result); + free(result); +exit: + _free(buf); +} static unsigned int getncpus(void) { @@ -663,7 +689,7 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody) be += strlen(b); s = se; /* move scan forward */ } else { /* otherwise free-field */ - int bc = 0, pc = 0; + int bc = 0, pc = 0, xc = 0; while (*s && (bc || pc || !iseol(*s))) { switch (*s) { case '\\': @@ -676,6 +702,7 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody) switch (*(s+1)) { case '{': *be++ = *s++; bc++; break; case '(': *be++ = *s++; pc++; break; + case '[': *be++ = *s++; xc++; break; case '%': *be++ = *s++; break; } break; @@ -683,12 +710,14 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody) case '}': if (bc > 0) bc--; break; case '(': if (pc > 0) pc++; break; case ')': if (pc > 0) pc--; break; + case '[': if (xc > 0) xc++; break; + case ']': if (xc > 0) xc--; break; } *be++ = *s++; } *be = '\0'; - if (bc || pc) { + if (bc || pc || xc) { mbErr(mb, 1, _("Macro %%%s has unterminated body\n"), n); se = s; /* XXX W2DO? */ goto exit; @@ -1099,7 +1128,7 @@ doFoo(MacroBuf mb, int chkexist, int negate, const char * f, size_t fn, } else if (STREQ("expand", f, fn) || STREQ("verbose", f, fn)) { b = buf; } else if (STREQ("expr", f, fn)) { - char *expr = rpmExprStr(buf); + char *expr = rpmExprStr(buf, 0); if (expr) { free(buf); b = buf = expr; @@ -1227,13 +1256,21 @@ static const char *setNegateAndCheck(const char *str, int *pnegate, int *pchkexi return str; } -static const char *findMacroEnd(const char *str) +/** + * Find the end of a macro call + * @param str pointer to the character after the initial '%' + * @return pointer to the next character after the macro + */ +RPM_GNUC_INTERNAL +const char *findMacroEnd(const char *str) { int c; if (*str == '(') return matchchar(str, *str, ')'); if (*str == '{') return matchchar(str, *str, '}'); + if (*str == '[') + return matchchar(str, *str, ']'); while (*str == '?' || *str == '!') str++; if (*str == '-') /* %-f */ @@ -1349,6 +1386,13 @@ expandMacro(MacroBuf mb, const char *src, size_t slen) doShellEscape(mb, s, (se - 1 - s)); s = se; continue; + case '[': /* %[...] expression expansion */ + if (mb->macro_trace) + printMacro(mb, s, se); + s++; /* skip [ */ + doExpressionExpansion(mb, s, (se - 1 - s)); + s = se; + continue; case '{': /* %{...}/%{...:...} substitution */ f = s+1; /* skip { */ f = setNegateAndCheck(f, &negate, &chkexist); diff --git a/rpmio/rpmmacro.h b/rpmio/rpmmacro.h index a6f65d930..ff6abaa22 100644 --- a/rpmio/rpmmacro.h +++ b/rpmio/rpmmacro.h @@ -49,6 +49,9 @@ extern const char * macrofiles; #define addMacro(_mc, _n, _o, _b, _l) rpmPushMacro(_mc, _n, _o, _b, _l) #define delMacro(_mc, _n) rpmPopMacro(_mc, _n) +/* rpm expression parser flags */ +#define RPMEXPR_EXPAND (1 << 0) /*!< expand primary terms */ + /** \ingroup rpmmacro * Print macros to file stream. * @param mc macro context (NULL uses global context). @@ -156,16 +159,18 @@ const char *rpmConfigDir(void); /** \ingroup rpmmacro * Evaluate boolean expression. * @param expr expression to parse + * @param flags parser flags * @return */ -int rpmExprBool(const char * expr); +int rpmExprBool(const char * expr, int flags); /** \ingroup rpmmacro * Evaluate string expression. * @param expr expression to parse + * @param flags parser flags * @return */ -char * rpmExprStr(const char * expr); +char * rpmExprStr(const char * expr, int flags); #ifdef __cplusplus diff --git a/rpmio/rpmmacro_internal.h b/rpmio/rpmmacro_internal.h new file mode 100644 index 000000000..a59671be8 --- /dev/null +++ b/rpmio/rpmmacro_internal.h @@ -0,0 +1,27 @@ +#ifndef _H_MACRO_INTERNAL +#define _H_MACRO_INTERNAL + +/** \ingroup rpmio + * \file rpmio/rpmmacro_internal.h + * + * Internal Macro API + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** \ingroup rpmmacro + * Find the end of a macro call + * @param str pointer to the character after the initial '%' + * @return pointer to the next character after the macro + */ +RPM_GNUC_INTERNAL +const char *findMacroEnd(const char *str); + + +#ifdef __cplusplus +} +#endif + +#endif /* _H_ MACRO_INTERNAL */ diff --git a/tests/rpmmacro.at b/tests/rpmmacro.at index 8ebea7758..f5e1af85d 100644 --- a/tests/rpmmacro.at +++ b/tests/rpmmacro.at @@ -336,6 +336,45 @@ runroot rpm \ ]) AT_CLEANUP +AT_SETUP([expression expansion 1]) +AT_KEYWORDS([macros]) +AT_CHECK([[ +runroot rpm \ + --define "aaa 5" \ + --define "bbb 0" \ + --eval '%[4*1024]' \ + --eval '%[5 < 1]' \ + --eval '%[%aaa]' \ + --eval '%[%{aaa}]' \ + --eval '%["%{aaa}"]' \ + --eval '%[%{?ccc}]' \ +]], +[0], +[4096 +0 +5 +5 +5 +0 +], +[]) +AT_CLEANUP + +AT_SETUP([expression expansion 2]) +AT_KEYWORDS([macros]) +AT_CHECK([[ +runroot rpm --define "aaa hello" --eval '%[%aaa]' +runroot rpm --eval '%[%{foo]' +]], +[1], +[], +[error: macro expansion returned a bare word, please use "...": %aaa +error: ^ +error: expanded string: hello +error: Unterminated {: {foo +]) +AT_CLEANUP + AT_SETUP([simple lua --eval]) AT_KEYWORDS([macros lua]) AT_CHECK([