rpm/rpmio/expression.c

1079 lines
22 KiB
C

/** \ingroup rpmbuild
* \file build/expression.c
* Simple logical expression parser.
* This module implements a basic expression parser with support for
* integer and string datatypes. For ease of programming, we use the
* top-down "recursive descent" method of parsing. While a
* table-driven bottom-up parser might be faster, it does not really
* matter for the expressions we will be parsing.
*
* Copyright (C) 1998 Tom Dyas <tdyas@eden.rutgers.edu>
* This work is provided under the GPL or LGPL at your choice.
*/
#include "system.h"
#include <vector>
#include <string>
#include <string.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmmacro.h>
#include <rpm/rpmstring.h>
#include <rpm/rpmver.h>
#include "rpmmacro_internal.h"
#include "rpmhook.h"
#include "rpmlua.h"
#include "debug.h"
/* #define DEBUG_PARSER 1 */
#ifdef DEBUG_PARSER
#include <stdio.h>
#define DEBUG(x) do { x ; } while (0)
#else
#define DEBUG(x)
#endif
typedef enum {
VALUE_TYPE_INTEGER,
VALUE_TYPE_STRING,
VALUE_TYPE_VERSION,
} valueType;
/**
* Encapsulation of a "value"
*/
typedef struct value_s {
valueType type;
union {
char *s;
int i;
rpmver v;
} data;
} *Value;
typedef int (*valueCmp)(Value v1, Value v2);
static int valueCmpInteger(Value v1, Value v2)
{
return v1->data.i - v2->data.i;
}
static int valueCmpString(Value v1, Value v2)
{
return strcmp(v1->data.s, v2->data.s);
}
static int valueCmpVersion(Value v1, Value v2)
{
return rpmverCmp(v1->data.v, v2->data.v);
}
static void valueReset(Value v)
{
if (v->type == VALUE_TYPE_STRING)
v->data.s = _free(v->data.s);
else if (v->type == VALUE_TYPE_VERSION)
v->data.v = rpmverFree(v->data.v);
}
/**
*/
static Value valueMakeInteger(int i)
{
Value v = new value_s {};
v->type = VALUE_TYPE_INTEGER;
v->data.i = i;
return v;
}
/**
*/
static Value valueMakeString(char *s)
{
Value v = new value_s {};
v->type = VALUE_TYPE_STRING;
v->data.s = s;
return v;
}
static Value valueMakeVersion(const char *s)
{
Value v = NULL;
rpmver rv = rpmverParse(s);
if (rv) {
v = new value_s {};
v->type = VALUE_TYPE_VERSION;
v->data.v = rv;
}
return v;
}
/**
*/
static void valueSetInteger(Value v, int i)
{
valueReset(v);
v->type = VALUE_TYPE_INTEGER;
v->data.i = i;
}
/**
*/
static void valueSetString(Value v, std::string & s)
{
valueReset(v);
v->type = VALUE_TYPE_STRING;
v->data.s = xstrdup(s.c_str());
}
/**
*/
static void valueFree( Value v)
{
if (v) {
valueReset(v);
delete v;
}
}
/**
*/
static int boolifyValue(Value v)
{
if (v && v->type == VALUE_TYPE_INTEGER)
return v->data.i != 0;
if (v && v->type == VALUE_TYPE_STRING)
return v->data.s[0] != '\0';
return 0;
}
#ifdef DEBUG_PARSER
static void valueDump(const char *msg, Value v, FILE *fp)
{
if (msg)
fprintf(fp, "%s ", msg);
if (v) {
if (v->type == VALUE_TYPE_INTEGER)
fprintf(fp, "INTEGER %d\n", v->data.i);
else if (v->type == VALUE_TYPE_VERSION) {
char *evr = rpmverEVR(v->data.v);
fprintf(fp, "VERSION %s\n", evr);
free(evr);
} else
fprintf(fp, "STRING '%s'\n", v->data.s);
} else
fprintf(fp, "NULL\n");
}
#endif
#define valueIsInteger(v) ((v)->type == VALUE_TYPE_INTEGER)
#define valueIsString(v) ((v)->type == VALUE_TYPE_STRING)
#define valueIsVersion(v) ((v)->type == VALUE_TYPE_VERSION)
#define valueSameType(v1,v2) ((v1)->type == (v2)->type)
/**
* Parser state.
*/
typedef struct _parseState {
char *str; /*!< expression string */
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,
const char *p)
{
const char *newLine = strchr(state->str,'\n');
if (newLine && (*(newLine+1) != '\0'))
p = NULL;
rpmlog(RPMLOG_ERR, "%s: %s\n", msg, state->str);
if (p) {
int l = p - state->str + strlen(msg) + 2;
rpmlog(RPMLOG_ERR, "%*s\n", l, "^");
}
}
/**
* \name Parser tokens
*/
#define TOK_EOF 1
#define TOK_INTEGER 2
#define TOK_STRING 3
#define TOK_ADD 4
#define TOK_MINUS 5
#define TOK_MULTIPLY 6
#define TOK_DIVIDE 7
#define TOK_OPEN_P 8
#define TOK_CLOSE_P 9
#define TOK_EQ 10
#define TOK_NEQ 11
#define TOK_LT 12
#define TOK_LE 13
#define TOK_GT 14
#define TOK_GE 15
#define TOK_NOT 16
#define TOK_LOGICAL_AND 17
#define TOK_LOGICAL_OR 18
#define TOK_TERNARY_COND 19
#define TOK_TERNARY_ALT 20
#define TOK_VERSION 21
#define TOK_COMMA 22
#define TOK_FUNCTION 23
#if defined(DEBUG_PARSER)
typedef struct exprTokTableEntry {
const char *name;
int val;
} ETTE_t;
ETTE_t exprTokTable[] = {
{ "EOF", TOK_EOF },
{ "I", TOK_INTEGER },
{ "S", TOK_STRING },
{ "+", TOK_ADD },
{ "-", TOK_MINUS },
{ "*", TOK_MULTIPLY },
{ "/", TOK_DIVIDE },
{ "( ", TOK_OPEN_P },
{ " )", TOK_CLOSE_P },
{ "==", TOK_EQ },
{ "!=", TOK_NEQ },
{ "<", TOK_LT },
{ "<=", TOK_LE },
{ ">", TOK_GT },
{ ">=", TOK_GE },
{ "!", TOK_NOT },
{ "&&", TOK_LOGICAL_AND },
{ "||", TOK_LOGICAL_OR },
{ "?", TOK_TERNARY_COND },
{ ":", TOK_TERNARY_ALT},
{ "V", TOK_VERSION},
{ ",", TOK_COMMA},
{ "f( ", TOK_FUNCTION},
{ NULL, 0 }
};
static const char *prToken(int val)
{
ETTE_t *et;
for (et = exprTokTable; et->name != NULL; et++) {
if (val == et->val)
return et->name;
}
return "???";
}
#endif /* DEBUG_PARSER */
#define RPMEXPR_DISCARD ((unsigned)1 << 31) /* internal, discard result */
static char *getValuebuf(ParseState state, const char *p, size_t size)
{
char *temp;
if ((state->flags & RPMEXPR_DISCARD) != 0)
size = 0;
temp = (char *)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;
}
static const char *
isFunctionCall(const char *p)
{
if (!risalpha(*p++) && *p != '_')
return NULL;
while (risalpha(*p) || risdigit(*p) || *p == '_' || *p == '.' || *p == ':')
p++;
return *p == '(' ? p : NULL;
}
/**
* @param state expression parser state
*/
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++;
switch (*p) {
case '\0':
token = TOK_EOF;
p--;
break;
case '+':
token = TOK_ADD;
break;
case '-':
token = TOK_MINUS;
break;
case '*':
token = TOK_MULTIPLY;
break;
case '/':
token = TOK_DIVIDE;
break;
case '(':
token = TOK_OPEN_P;
break;
case ')':
token = TOK_CLOSE_P;
break;
case '=':
if (p[1] == '=') {
token = TOK_EQ;
p++;
} else {
exprErr(state, _("syntax error while parsing =="), p+2);
goto err;
}
break;
case '!':
if (p[1] == '=') {
token = TOK_NEQ;
p++;
} else
token = TOK_NOT;
break;
case '<':
if (p[1] == '=') {
token = TOK_LE;
p++;
} else
token = TOK_LT;
break;
case '>':
if (p[1] == '=') {
token = TOK_GE;
p++;
} else
token = TOK_GT;
break;
case '&':
if (p[1] == '&') {
token = TOK_LOGICAL_AND;
p++;
} else {
exprErr(state, _("syntax error while parsing &&"), p+2);
goto err;
}
break;
case '|':
if (p[1] == '|') {
token = TOK_LOGICAL_OR;
p++;
} else {
exprErr(state, _("syntax error while parsing ||"), p+2);
goto err;
}
break;
case '?':
token = TOK_TERNARY_COND;
break;
case ':':
token = TOK_TERNARY_ALT;
break;
case ',':
token = TOK_COMMA;
break;
default:
if (risdigit(*p) || (*p == '%' && expand)) {
char *temp;
size_t ts;
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));
free(temp);
} else if (*p == '\"' || (*p == 'v' && *(p+1) == '\"')) {
char *temp;
size_t ts;
int qtok;
if (*p == 'v') {
qtok = TOK_VERSION;
p += 2;
} else {
qtok = TOK_STRING;
p++;
}
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 = getValuebuf(state, p, ts);
if (!temp)
goto err;
p += ts;
token = TOK_STRING;
if (qtok == TOK_STRING) {
v = valueMakeString(temp);
} else {
v = valueMakeVersion(state->flags & RPMEXPR_DISCARD ? "0" : temp);
free(temp); /* version doesn't take ownership of the string */
if (v == 0) {
exprErr(state, _("invalid version"), p+1);
goto err;
}
}
} else if (risalpha(*p)) {
const char *pe = isFunctionCall(p);
if (pe && *pe == '(') {
v = valueMakeString(rstrndup(p, pe - p));
p = pe;
token = TOK_FUNCTION;
break;
}
exprErr(state, _("bare words are no longer supported, please use \"...\""), p+1);
goto err;
} else {
exprErr(state, _("parse error in expression"), p+1);
goto err;
}
break;
}
state->p = p + 1;
state->nextToken = token;
state->tokenValue = v;
DEBUG(printf("rdToken: \"%s\" (%d)\n", prToken(token), token));
DEBUG(valueDump("rdToken:", state->tokenValue, stdout));
return 0;
err:
valueFree(v);
return -1;
}
static Value doTernary(ParseState state);
/* always returns a string for now */
static Value doLuaFunction(ParseState state, const char *name, int argc, Value *argv)
{
rpmlua lua = NULL; /* Global state. */
rpmhookArgs args = NULL;
Value v = NULL;
char *result = NULL;
std::vector<char> argt(argc + 1);
if (state->flags & RPMEXPR_DISCARD)
return valueMakeString(xstrdup(""));
args = rpmhookArgsNew(argc);
for (int i = 0; i < argc; i++) {
switch (argv[i]->type) {
case VALUE_TYPE_INTEGER:
argt[i] = 'i';
args->argv[i].i = argv[i]->data.i;
break;
case VALUE_TYPE_STRING:
argt[i] = 's';
args->argv[i].s = argv[i]->data.s;
break;
default:
exprErr(state, _("unsupported function argument type"), state->p);
goto exit;
}
}
argt[argc] = 0;
args->argt = argt.data();
result = rpmluaCallStringFunction(lua, name, args);
if (result)
v = valueMakeString(result);
exit:
rpmhookArgsFree(args);
return v;
}
static Value doFunction(ParseState state)
{
Value vname = state->tokenValue;
Value v = NULL;
std::vector<Value> varg {};
if (rdToken(state))
goto exit;
/* gather args */
while (state->nextToken != TOK_CLOSE_P) {
varg.push_back(doTernary(state));
if (!varg.back())
goto exit;
if (state->nextToken == TOK_CLOSE_P)
break;
if (state->nextToken != TOK_COMMA) {
exprErr(state, _("syntax error in expression"), state->p);
goto exit;
}
if (rdToken(state)) {
goto exit;
}
if (state->nextToken == TOK_CLOSE_P) {
exprErr(state, _("syntax error in expression"), state->p);
goto exit;
}
}
if (rdToken(state))
goto exit;
/* do the call... */
if (!strncmp(vname->data.s, "lua:", 4))
v = doLuaFunction(state, vname->data.s + 4, varg.size(), varg.data());
else
exprErr(state, _("unsupported funcion"), state->p);
exit:
for (auto & v : varg)
valueFree(v);
valueFree(vname);
return v;
}
/**
* @param state expression parser state
*/
static Value doPrimary(ParseState state)
{
Value v = NULL;
const char *p = state->p;
DEBUG(printf("doPrimary()\n"));
switch (state->nextToken) {
case TOK_FUNCTION:
v = doFunction(state);
if (v == NULL)
goto err;
break;
case TOK_OPEN_P:
if (rdToken(state))
goto err;
v = doTernary(state);
if (state->nextToken != TOK_CLOSE_P) {
exprErr(state, _("unmatched ("), p);
goto err;
}
if (rdToken(state))
goto err;
break;
case TOK_INTEGER:
case TOK_STRING:
v = state->tokenValue;
if (rdToken(state))
goto err;
break;
case TOK_MINUS:
if (rdToken(state))
goto err;
v = doPrimary(state);
if (v == NULL)
goto err;
if (! valueIsInteger(v)) {
exprErr(state, _("- only on numbers"), p);
goto err;
}
valueSetInteger(v, - v->data.i);
break;
case TOK_NOT:
if (rdToken(state))
goto err;
v = doPrimary(state);
if (v == NULL)
goto err;
valueSetInteger(v, ! boolifyValue(v));
break;
case TOK_EOF:
exprErr(state, _("unexpected end of expression"), state->p);
goto err;
default:
exprErr(state, _("syntax error in expression"), state->p);
goto err;
break;
}
DEBUG(valueDump("doPrimary:", v, stdout));
return v;
err:
valueFree(v);
return NULL;
}
/**
* @param state expression parser state
*/
static Value doMultiplyDivide(ParseState state)
{
Value v1 = NULL, v2 = NULL;
DEBUG(printf("doMultiplyDivide()\n"));
v1 = doPrimary(state);
if (v1 == NULL)
goto err;
while (state->nextToken == TOK_MULTIPLY
|| state->nextToken == TOK_DIVIDE) {
int op = state->nextToken;
const char *p = state->p;
if (rdToken(state))
goto err;
if (v2) valueFree(v2);
v2 = doPrimary(state);
if (v2 == NULL)
goto err;
if (! valueSameType(v1, v2)) {
exprErr(state, _("types must match"), NULL);
goto err;
}
if (valueIsInteger(v1)) {
int i1 = v1->data.i, i2 = v2->data.i;
if ((state->flags & RPMEXPR_DISCARD) != 0)
continue; /* just use v1 in discard mode */
if ((i2 == 0) && (op == TOK_DIVIDE)) {
exprErr(state, _("division by zero"), p);
goto err;
}
if (op == TOK_MULTIPLY)
valueSetInteger(v1, i1 * i2);
else
valueSetInteger(v1, i1 / i2);
} else if (valueIsVersion(v1)) {
exprErr(state, _("* and / not supported for versions"), p);
goto err;
} else {
exprErr(state, _("* and / not supported for strings"), p);
goto err;
}
}
if (v2) valueFree(v2);
return v1;
err:
valueFree(v1);
valueFree(v2);
return NULL;
}
/**
* @param state expression parser state
*/
static Value doAddSubtract(ParseState state)
{
Value v1 = NULL, v2 = NULL;
DEBUG(printf("doAddSubtract()\n"));
v1 = doMultiplyDivide(state);
if (v1 == NULL)
goto err;
while (state->nextToken == TOK_ADD || state->nextToken == TOK_MINUS) {
int op = state->nextToken;
const char *p = state->p;
if (rdToken(state))
goto err;
if (v2) valueFree(v2);
v2 = doMultiplyDivide(state);
if (v2 == NULL)
goto err;
if (! valueSameType(v1, v2)) {
exprErr(state, _("types must match"), NULL);
goto err;
}
if (valueIsInteger(v1)) {
int i1 = v1->data.i, i2 = v2->data.i;
if (op == TOK_ADD)
valueSetInteger(v1, i1 + i2);
else
valueSetInteger(v1, i1 - i2);
} else if (valueIsVersion(v1)) {
exprErr(state, _("+ and - not supported for versions"), p);
goto err;
} else {
if (op == TOK_MINUS) {
exprErr(state, _("- not supported for strings"), p);
goto err;
}
std::string copy = v1->data.s;
copy += v2->data.s;
valueSetString(v1, copy);
}
}
if (v2) valueFree(v2);
return v1;
err:
valueFree(v1);
valueFree(v2);
return NULL;
}
/**
* @param state expression parser state
*/
static Value doRelational(ParseState state)
{
Value v1 = NULL, v2 = NULL;
DEBUG(printf("doRelational()\n"));
v1 = doAddSubtract(state);
if (v1 == NULL)
goto err;
while (state->nextToken >= TOK_EQ && state->nextToken <= TOK_GE) {
int op = state->nextToken;
int r = 0;
valueCmp cmp;
if (rdToken(state))
goto err;
if (v2) valueFree(v2);
v2 = doAddSubtract(state);
if (v2 == NULL)
goto err;
if (! valueSameType(v1, v2)) {
exprErr(state, _("types must match"), NULL);
goto err;
}
if (valueIsInteger(v1))
cmp = valueCmpInteger;
else if (valueIsVersion(v1))
cmp = valueCmpVersion;
else
cmp = valueCmpString;
switch (op) {
case TOK_EQ:
r = (cmp(v1,v2) == 0);
break;
case TOK_NEQ:
r = (cmp(v1,v2) != 0);
break;
case TOK_LT:
r = (cmp(v1,v2) < 0);
break;
case TOK_LE:
r = (cmp(v1,v2) <= 0);
break;
case TOK_GT:
r = (cmp(v1,v2) > 0);
break;
case TOK_GE:
r = (cmp(v1,v2) >= 0);
break;
default:
break;
}
valueSetInteger(v1, r);
}
if (v2) valueFree(v2);
return v1;
err:
valueFree(v1);
valueFree(v2);
return NULL;
}
/**
* @param state expression parser state
*/
static Value doLogical(ParseState state)
{
Value v1 = NULL, v2 = NULL;
int oldflags = state->flags;
DEBUG(printf("doLogical()\n"));
v1 = doRelational(state);
if (v1 == NULL)
goto err;
while (state->nextToken == TOK_LOGICAL_AND
|| state->nextToken == TOK_LOGICAL_OR) {
int op = state->nextToken;
int b1 = boolifyValue(v1);
if ((op == TOK_LOGICAL_AND && !b1) || (op == TOK_LOGICAL_OR && b1))
state->flags |= RPMEXPR_DISCARD; /* short-circuit */
if (rdToken(state))
goto err;
if (v2) valueFree(v2);
v2 = doRelational(state);
if (v2 == NULL)
goto err;
if (! valueSameType(v1, v2)) {
exprErr(state, _("types must match"), NULL);
goto err;
}
if ((op == TOK_LOGICAL_AND && b1) || (op == TOK_LOGICAL_OR && !b1)) {
Value vtmp = v1;
v1 = v2;
v2 = vtmp;
}
state->flags = oldflags;
}
if (v2) valueFree(v2);
return v1;
err:
valueFree(v1);
valueFree(v2);
state->flags = oldflags;
return NULL;
}
static Value doTernary(ParseState state)
{
Value v1 = NULL, v2 = NULL;
int oldflags = state->flags;
DEBUG(printf("doTernary()\n"));
v1 = doLogical(state);
if (v1 == NULL)
goto err;
if (state->nextToken == TOK_TERNARY_COND) {
int cond = boolifyValue(v1);;
if (!cond)
state->flags |= RPMEXPR_DISCARD; /* short-circuit */
if (rdToken(state))
goto err;
valueFree(v1);
v1 = doTernary(state);
if (v1 == NULL)
goto err;
if (state->nextToken != TOK_TERNARY_ALT) {
exprErr(state, _("syntax error in expression"), state->p);
goto err;
}
state->flags = oldflags;
if (cond)
state->flags |= RPMEXPR_DISCARD; /* short-circuit */
if (rdToken(state))
goto err;
v2 = doTernary(state);
if (v2 == NULL)
goto err;
state->flags = oldflags;
if (! valueSameType(v1, v2)) {
exprErr(state, _("types must match"), NULL);
goto err;
}
valueFree(cond ? v2 : v1);
return cond ? v1 : v2;
}
return v1;
err:
valueFree(v1);
valueFree(v2);
state->flags = oldflags;
return NULL;
}
int rpmExprBoolFlags(const char *expr, int flags)
{
struct _parseState state;
int result = -1;
Value v = NULL;
DEBUG(printf("parseExprBoolean(?, '%s')\n", expr));
/* Initialize the expression parser state. */
state.p = state.str = xstrdup(expr);
state.nextToken = 0;
state.tokenValue = NULL;
state.flags = flags;
if (rdToken(&state))
goto exit;
/* Parse the expression. */
v = doTernary(&state);
if (!v)
goto exit;
/* If the next token is not TOK_EOF, we have a syntax error. */
if (state.nextToken != TOK_EOF) {
exprErr(&state, _("syntax error in expression"), state.p);
goto exit;
}
DEBUG(valueDump("parseExprBoolean:", v, stdout));
result = boolifyValue(v);
exit:
state.str = _free(state.str);
valueFree(v);
return result;
}
char *rpmExprStrFlags(const char *expr, int flags)
{
struct _parseState state;
char *result = NULL;
Value v = NULL;
DEBUG(printf("parseExprString(?, '%s')\n", expr));
/* Initialize the expression parser state. */
state.p = state.str = xstrdup(expr);
state.nextToken = 0;
state.tokenValue = NULL;
state.flags = flags;
if (rdToken(&state))
goto exit;
/* Parse the expression. */
v = doTernary(&state);
if (!v)
goto exit;
/* If the next token is not TOK_EOF, we have a syntax error. */
if (state.nextToken != TOK_EOF) {
exprErr(&state, _("syntax error in expression"), state.p);
goto exit;
}
DEBUG(valueDump("parseExprString:", v, stdout));
switch (v->type) {
case VALUE_TYPE_INTEGER: {
rasprintf(&result, "%d", v->data.i);
} break;
case VALUE_TYPE_STRING:
result = xstrdup(v->data.s);
break;
case VALUE_TYPE_VERSION:
result = rpmverEVR(v->data.v);
break;
default:
break;
}
exit:
state.str = _free(state.str);
valueFree(v);
return result;
}
int rpmExprBool(const char *expr)
{
return rpmExprBoolFlags(expr, 0);
}
char *rpmExprStr(const char *expr)
{
return rpmExprStrFlags(expr, 0);
}