Add support for primary expansion to the expression parser

And also wire it to %[ ... ] as new syntax to expand expressions.

We'll add short-circuit support in the next commit.
This commit is contained in:
Michael Schroeder 2019-09-18 15:31:08 +02:00 committed by Panu Matilainen
parent bebd15081a
commit cb4e5e755a
8 changed files with 235 additions and 27 deletions

View File

@ -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"),

View File

@ -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

View File

@ -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

View File

@ -15,6 +15,7 @@
#include <rpm/rpmlog.h>
#include <rpm/rpmmacro.h>
#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;

View File

@ -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);

View File

@ -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

27
rpmio/rpmmacro_internal.h Normal file
View File

@ -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 */

View File

@ -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([