Imported Upstream version 4.14.1
[platform/upstream/rpm.git] / rpmio / macro.c
index 09cb563..7858b10 100644 (file)
@@ -4,6 +4,8 @@
 
 #include "system.h"
 #include <stdarg.h>
+#include <pthread.h>
+#include <errno.h>
 #ifdef HAVE_GETOPT_H
 #include <getopt.h>
 #else
@@ -34,13 +36,19 @@ extern int optind;
 
 #include "debug.h"
 
+enum macroFlags_e {
+    ME_NONE    = 0,
+    ME_AUTO    = (1 << 0),
+    ME_USED    = (1 << 1),
+};
+
 /*! The structure used to store a macro. */
 struct rpmMacroEntry_s {
     struct rpmMacroEntry_s *prev;/*!< Macro entry stack. */
     const char *name;          /*!< Macro name. */
     const char *opts;          /*!< Macro parameters (a la getopt) */
     const char *body;  /*!< Macro body. */
-    int used;           /*!< No. of expansions. */
+    int flags;         /*!< Macro state bits. */
     int level;          /*!< Scoping level. */
     char arena[];      /*!< String arena. */
 };
@@ -49,6 +57,10 @@ struct rpmMacroEntry_s {
 struct rpmMacroContext_s {
     rpmMacroEntry *tab;  /*!< Macro entry table (array of pointers). */
     int n;      /*!< No. of macros. */
+    int depth;          /*!< Depth tracking when recursing from Lua  */
+    int level;          /*!< Scope level tracking when recursing from Lua  */
+    pthread_mutex_t lock;
+    pthread_mutexattr_t lockattr;
 };
 
 
@@ -58,6 +70,29 @@ rpmMacroContext rpmGlobalMacroContext = &rpmGlobalMacroContext_s;
 static struct rpmMacroContext_s rpmCLIMacroContext_s;
 rpmMacroContext rpmCLIMacroContext = &rpmCLIMacroContext_s;
 
+/*
+ * The macro engine internals do not require recursive mutexes but Lua
+ * macro bindings which can get called from the internals use the external
+ * interfaces which do perform locking. Until that is fixed somehow
+ * we'll just have to settle for recursive mutexes.
+ * Unfortunately POSIX doesn't specify static initializers for recursive
+ * mutexes so we need to have a separate PTHREAD_ONCE initializer just
+ * to initialize the otherwise static macro context mutexes. Pooh.
+ */
+static pthread_once_t locksInitialized = PTHREAD_ONCE_INIT;
+
+static void initLocks(void)
+{
+    rpmMacroContext mcs[] = { rpmGlobalMacroContext, rpmCLIMacroContext, NULL };
+
+    for (rpmMacroContext *mcp = mcs; *mcp; mcp++) {
+       rpmMacroContext mc = *mcp;
+       pthread_mutexattr_init(&mc->lockattr);
+       pthread_mutexattr_settype(&mc->lockattr, PTHREAD_MUTEX_RECURSIVE);
+       pthread_mutex_init(&mc->lock, &mc->lockattr);
+    }
+}
+
 /**
  * Macro expansion state.
  */
@@ -66,12 +101,16 @@ typedef struct MacroBuf_s {
     size_t tpos;               /*!< Current position in expansion buffer */
     size_t nb;                 /*!< No. bytes remaining in expansion buffer. */
     int depth;                 /*!< Current expansion depth. */
+    int level;                 /*!< Current scoping level */
+    int error;                 /*!< Errors encountered during expansion? */
     int macro_trace;           /*!< Pre-print macro to expand? */
     int expand_trace;          /*!< Post-print macro expansion? */
+    int escape;                        /*!< Preserve '%%' during expansion? */
+    int flags;                 /*!< Flags to control behavior */
     rpmMacroContext mc;
 } * MacroBuf;
 
-#define        _MAX_MACRO_DEPTH        16
+#define        _MAX_MACRO_DEPTH        64
 static int max_macro_depth = _MAX_MACRO_DEPTH;
 
 #define        _PRINT_MACRO_TRACE      0
@@ -82,29 +121,26 @@ static int print_expand_trace = _PRINT_EXPAND_TRACE;
 
 /* forward ref */
 static int expandMacro(MacroBuf mb, const char *src, size_t slen);
+static void pushMacro(rpmMacroContext mc,
+       const char * n, const char * o, const char * b, int level, int flags);
+static void popMacro(rpmMacroContext mc, const char * n);
+static int loadMacroFile(rpmMacroContext mc, const char * fn);
 
 /* =============================================================== */
 
-void
-rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
+static rpmMacroContext rpmmctxAcquire(rpmMacroContext mc)
 {
-    if (mc == NULL) mc = rpmGlobalMacroContext;
-    if (fp == NULL) fp = stderr;
-    
-    fprintf(fp, "========================\n");
-    for (int i = 0; i < mc->n; i++) {
-       rpmMacroEntry me = mc->tab[i];
-       assert(me);
-       fprintf(fp, "%3d%c %s", me->level,
-                   (me->used > 0 ? '=' : ':'), me->name);
-       if (me->opts && *me->opts)
-               fprintf(fp, "(%s)", me->opts);
-       if (me->body && *me->body)
-               fprintf(fp, "\t%s", me->body);
-       fprintf(fp, "\n");
-    }
-    fprintf(fp, _("======================== active %d empty %d\n"),
-               mc->n, 0);
+    if (mc == NULL)
+       mc = rpmGlobalMacroContext;
+    pthread_once(&locksInitialized, initLocks);
+    pthread_mutex_lock(&mc->lock);
+    return mc;
+}
+
+static rpmMacroContext rpmmctxRelease(rpmMacroContext mc)
+{
+    pthread_mutex_unlock(&mc->lock);
+    return NULL;
 }
 
 /**
@@ -155,7 +191,7 @@ findEntry(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos)
  * fgets(3) analogue that reads \ continuations. Last newline always trimmed.
  * @param buf          input buffer
  * @param size         inbut buffer size (bytes)
- * @param fd           file handle
+ * @param f            file handle
  * @return             buffer, or NULL on end-of-file
  */
 static char *
@@ -245,11 +281,9 @@ static void
 printMacro(MacroBuf mb, const char * s, const char * se)
 {
     const char *senl;
-    const char *ellipsis;
-    int choplen;
 
     if (s >= se) {     /* XXX just in case */
-       fprintf(stderr, _("%3d>%*s(empty)"), mb->depth,
+       fprintf(stderr, _("%3d>%*s(empty)\n"), mb->depth,
                (2 * mb->depth + 1), "");
        return;
     }
@@ -261,19 +295,11 @@ printMacro(MacroBuf mb, const char * s, const char * se)
     for (senl = se; *senl && !iseol(*senl); senl++)
        {};
 
-    /* Limit trailing non-trace output */
-    choplen = 61 - (2 * mb->depth);
-    if ((senl - s) > choplen) {
-       senl = s + choplen;
-       ellipsis = "...";
-    } else
-       ellipsis = "";
-
     /* Substitute caret at end-of-macro position */
     fprintf(stderr, "%3d>%*s%%%.*s^", mb->depth,
        (2 * mb->depth + 1), "", (int)(se - s), s);
-    if (se[1] != '\0' && (senl - (se+1)) > 0)
-       fprintf(stderr, "%-.*s%s", (int)(senl - (se+1)), se+1, ellipsis);
+    if (se[0] != '\0' && se[1] != '\0' && (senl - (se+1)) > 0)
+       fprintf(stderr, "%-.*s", (int)(senl - (se+1)), se+1);
     fprintf(stderr, "\n");
 }
 
@@ -286,9 +312,6 @@ printMacro(MacroBuf mb, const char * s, const char * se)
 static void
 printExpansion(MacroBuf mb, const char * t, const char * te)
 {
-    const char *ellipsis;
-    int choplen;
-
     if (!(te > t)) {
        rpmlog(RPMLOG_DEBUG, _("%3d<%*s(empty)\n"), mb->depth, (2 * mb->depth + 1), "");
        return;
@@ -297,7 +320,6 @@ printExpansion(MacroBuf mb, const char * t, const char * te)
     /* Shorten output which contains newlines */
     while (te > t && iseol(te[-1]))
        te--;
-    ellipsis = "";
     if (mb->depth > 0) {
        const char *tenl;
 
@@ -305,17 +327,11 @@ printExpansion(MacroBuf mb, const char * t, const char * te)
        while ((tenl = strchr(t, '\n')) && tenl < te)
            t = ++tenl;
 
-       /* Limit expand output */
-       choplen = 61 - (2 * mb->depth);
-       if ((te - t) > choplen) {
-           te = t + choplen;
-           ellipsis = "...";
-       }
     }
 
     rpmlog(RPMLOG_DEBUG,"%3d<%*s", mb->depth, (2 * mb->depth + 1), "");
     if (te > t)
-       rpmlog(RPMLOG_DEBUG, "%.*s%s", (int)(te - t), t, ellipsis);
+       rpmlog(RPMLOG_DEBUG, "%.*s", (int)(te - t), t);
     rpmlog(RPMLOG_DEBUG, "\n");
 }
 
@@ -329,14 +345,14 @@ printExpansion(MacroBuf mb, const char * t, const char * te)
 
 #define        COPYNAME(_ne, _s, _c)   \
     {  SKIPBLANK(_s,_c);       \
-       while(((_c) = *(_s)) && (risalnum(_c) || (_c) == '_')) \
+       while (((_c) = *(_s)) && (risalnum(_c) || (_c) == '_')) \
                *(_ne)++ = *(_s)++; \
        *(_ne) = '\0';          \
     }
 
 #define        COPYOPTS(_oe, _s, _c)   \
     { \
-       while(((_c) = *(_s)) && (_c) != ')') \
+       while (((_c) = *(_s)) && (_c) != ')') \
                *(_oe)++ = *(_s)++; \
        *(_oe) = '\0';          \
     }
@@ -353,15 +369,18 @@ static int
 expandThis(MacroBuf mb, const char * src, size_t slen, char **target)
 {
     struct MacroBuf_s umb;
-    int rc;
 
     /* Copy other state from "parent", but we want a buffer of our own */
     umb = *mb;
     umb.buf = NULL;
-    rc = expandMacro(&umb, src, slen);
+    umb.error = 0;
+    /* In case of error, flag it in the "parent"... */
+    if (expandMacro(&umb, src, slen))
+       mb->error = 1;
     *target = umb.buf;
 
-    return rc;
+    /* ...but return code for this operation specifically */
+    return umb.error;
 }
 
 static void mbAppend(MacroBuf mb, char c)
@@ -375,6 +394,7 @@ static void mbAppend(MacroBuf mb, char c)
     mb->nb--;
 }
 
+#ifdef WITH_LUA
 static void mbAppendStr(MacroBuf mb, const char *str)
 {
     size_t len = strlen(str);
@@ -386,32 +406,31 @@ static void mbAppendStr(MacroBuf mb, const char *str)
     mb->tpos += len;
     mb->nb -= len;
 }
+#endif
+
 /**
  * Expand output of shell command into target buffer.
  * @param mb           macro expansion state
  * @param cmd          shell command
  * @param clen         no. bytes in shell command
- * @return             result of expansion
  */
-static int
+static void
 doShellEscape(MacroBuf mb, const char * cmd, size_t clen)
 {
     char *buf = NULL;
     FILE *shf;
-    int rc = 0;
     int c;
 
-    rc = expandThis(mb, cmd, clen, &buf);
-    if (rc)
+    if (expandThis(mb, cmd, clen, &buf))
        goto exit;
 
     if ((shf = popen(buf, "r")) == NULL) {
-       rc = 1;
+       mb->error = 1;
        goto exit;
     }
 
     size_t tpos = mb->tpos;
-    while((c = fgetc(shf)) != EOF) {
+    while ((c = fgetc(shf)) != EOF) {
        mbAppend(mb, c);
     }
     (void) pclose(shf);
@@ -424,29 +443,29 @@ doShellEscape(MacroBuf mb, const char * cmd, size_t clen)
 
 exit:
     _free(buf);
-    return rc;
 }
 
 /**
  * Parse (and execute) new macro definition.
  * @param mb           macro expansion state
  * @param se           macro definition to parse
+ * @param slen         length of se argument
  * @param level                macro recursion level
  * @param expandbody   should body be expanded?
  * @return             address to continue parsing
  */
 static const char *
-doDefine(MacroBuf mb, const char * se, int level, int expandbody)
+doDefine(MacroBuf mb, const char * se, size_t slen, int level, int expandbody)
 {
     const char *s = se;
-    size_t blen = MACROBUFSIZ;
-    char *buf = xmalloc(blen);
+    char *buf = xmalloc(slen + 3); /* Some leeway for termination issues... */
     char *n = buf, *ne = n;
     char *o = NULL, *oe;
     char *b, *be, *ebody = NULL;
     int c;
     int oc = ')';
     const char *sbody; /* as-is body start */
+    int rc = 1; /* assume failure */
 
     /* Copy name */
     COPYNAME(ne, s, c);
@@ -455,9 +474,15 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
     oe = ne + 1;
     if (*s == '(') {
        s++;    /* skip ( */
-       o = oe;
-       COPYOPTS(oe, s, oc);
-       s++;    /* skip ) */
+       /* Options must be terminated with ')' */
+       if (strchr(s, ')')) {
+           o = oe;
+           COPYOPTS(oe, s, oc);
+           s++;        /* skip ) */
+       } else {
+           rpmlog(RPMLOG_ERR, _("Macro %%%s has unterminated opts\n"), n);
+           goto exit;
+       }
     }
 
     /* Copy body, skipping over escaped newlines */
@@ -523,14 +548,8 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
 
     /* Names must start with alphabetic or _ and be at least 3 chars */
     if (!((c = *n) && (risalpha(c) || c == '_') && (ne - n) > 2)) {
-       rpmlog(RPMLOG_ERR,
-               _("Macro %%%s has illegal name (%%define)\n"), n);
-       goto exit;
-    }
-
-    /* Options must be terminated with ')' */
-    if (o && oc != ')') {
-       rpmlog(RPMLOG_ERR, _("Macro %%%s has unterminated opts\n"), n);
+       rpmlog(RPMLOG_ERR, _("Macro %%%s has illegal name (%s)\n"),
+               n, expandbody ? "%global": "%define");
        goto exit;
     }
 
@@ -550,9 +569,12 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
        b = ebody;
     }
 
-    addMacro(mb->mc, n, o, b, (level - 1));
+    pushMacro(mb->mc, n, o, b, level, ME_NONE);
+    rc = 0;
 
 exit:
+    if (rc)
+       mb->error = 1;
     _free(buf);
     _free(ebody);
     return se;
@@ -560,15 +582,16 @@ exit:
 
 /**
  * Parse (and execute) macro undefinition.
- * @param mc           macro context
+ * @param mb           macro expansion state
  * @param se           macro name to undefine
+ * @param slen         length of se argument
  * @return             address to continue parsing
  */
 static const char *
-doUndefine(rpmMacroContext mc, const char * se)
+doUndefine(MacroBuf mb, const char * se, size_t slen)
 {
     const char *s = se;
-    char *buf = xmalloc(MACROBUFSIZ);
+    char *buf = xmalloc(slen + 1);
     char *n = buf, *ne = n;
     int c;
 
@@ -581,12 +604,12 @@ doUndefine(rpmMacroContext mc, const char * se)
 
     /* Names must start with alphabetic or _ and be at least 3 chars */
     if (!((c = *n) && (risalpha(c) || c == '_') && (ne - n) > 2)) {
-       rpmlog(RPMLOG_ERR,
-               _("Macro %%%s has illegal name (%%undefine)\n"), n);
+       rpmlog(RPMLOG_ERR, _("Macro %%%s has illegal name (%%undefine)\n"), n);
+       mb->error = 1;
        goto exit;
     }
 
-    delMacro(mc, n);
+    popMacro(mb->mc, n);
 
 exit:
     _free(buf);
@@ -604,25 +627,56 @@ freeArgs(MacroBuf mb)
 
     /* Delete dynamic macro definitions */
     for (int i = 0; i < mc->n; i++) {
-       int skiptest = 0;
        rpmMacroEntry me = mc->tab[i];
        assert(me);
-       if (me->level < mb->depth)
+       if (me->level < mb->level)
            continue;
-       if (strlen(me->name) == 1 && strchr("#*0", *me->name)) {
-           if (*me->name == '*' && me->used > 0)
-               skiptest = 1; /* XXX skip test for %# %* %0 */
-       } else if (!skiptest && me->used <= 0) {
-#if NOTYET
-           rpmlog(RPMLOG_ERR,
-                       _("Macro %%%s (%s) was not used below level %d\n"),
-                       me->name, me->body, me->level);
-#endif
+       /* Warn on defined but unused non-automatic, scoped macros */
+       if (!(me->flags & (ME_AUTO|ME_USED))) {
+           rpmlog(RPMLOG_WARNING,
+                       _("Macro %%%s defined but not used within scope\n"),
+                       me->name);
+           /* Only whine once */
+           me->flags |= ME_USED;
        }
+
        /* compensate if the slot is to go away */
        if (me->prev == NULL)
            i--;
-       delMacro(mc, me->name);
+       popMacro(mc, me->name);
+    }
+    mb->level--;
+}
+
+static void splitQuoted(ARGV_t *av, const char * str, const char * seps)
+{
+    const int qchar = 0x1f; /* ASCII unit separator */
+    const char *s = str;
+    const char *start = str;
+    int quoted = 0;
+
+    while (start != NULL) {
+       if (!quoted && strchr(seps, *s)) {
+           size_t slen = s - start;
+           /* quoted arguments are always kept, otherwise skip empty args */
+           if (slen > 0) {
+               char *d, arg[slen + 1];
+               const char *t;
+               for (d = arg, t = start; t - start < slen; t++) {
+                   if (*t == qchar)
+                       continue;
+                   *d++ = *t;
+               }
+               arg[d - arg] = '\0';
+               argvAdd(av, arg);
+           }
+           start = s + 1;
+       }
+       if (*s == qchar)
+           quoted = !quoted;
+       else if (*s == '\0')
+           start = NULL;
+       s++;
     }
 }
 
@@ -639,31 +693,41 @@ static const char *
 grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
                const char * lastc)
 {
+    const char *cont = NULL;
     const char *opts;
     char *args = NULL;
     ARGV_t argv = NULL;
     int argc = 0;
     int c;
 
-    /* Copy macro name as argv[0] */
-    argvAdd(&argv, me->name);
-    addMacro(mb->mc, "0", NULL, me->name, mb->depth);
-    
     /* 
+     * Prepare list of call arguments, starting with macro name as argv[0].
      * Make a copy of se up to lastc string that we can pass to argvSplit().
      * Append the results to main argv. 
      */
-    {  ARGV_t av = NULL;
-       char *s = xcalloc((lastc-se)+1, sizeof(*s));
-       memcpy(s, se, (lastc-se));
+    argvAdd(&argv, me->name);
+    if (lastc) {
+       int oescape = mb->escape;
+       char *s = NULL;
 
-       argvSplit(&av, s, " \t");
-       argvAppend(&argv, av);
+       /* Expand possible macros in arguments */
+       mb->escape = 1;
+       expandThis(mb, se, lastc-se, &s);
+       mb->escape = oescape;
 
-       argvFree(av);
+       splitQuoted(&argv, s, " \t");
        free(s);
+
+       cont = ((*lastc == '\0' || *lastc == '\n') && *(lastc-1) != '\\') ?
+              lastc : lastc + 1;
     }
 
+    /* Bump call depth on entry before first macro define */
+    mb->level++;
+
+    /* Setup macro name as %0 */
+    pushMacro(mb->mc, "0", NULL, me->name, mb->level, ME_AUTO);
+
     /*
      * The macro %* analoguous to the shell's $* means "Pass all non-macro
      * parameters." Consequently, there needs to be a macro that means "Pass all
@@ -673,7 +737,7 @@ grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
      * This is the (potential) justification for %{**} ...
     */
     args = argvJoin(argv + 1, " ");
-    addMacro(mb->mc, "**", NULL, args, mb->depth);
+    pushMacro(mb->mc, "**", NULL, args, mb->level, ME_AUTO);
     free(args);
 
     /*
@@ -690,12 +754,13 @@ grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
     argc = argvCount(argv);
 
     /* Define option macros. */
-    while((c = getopt(argc, argv, opts)) != -1)
+    while ((c = getopt(argc, argv, opts)) != -1)
     {
        char *name = NULL, *body = NULL;
        if (c == '?' || strchr(opts, c) == NULL) {
            rpmlog(RPMLOG_ERR, _("Unknown option %c in %s(%s)\n"),
                        (char)optopt, me->name, opts);
+           mb->error = 1;
            goto exit;
        }
 
@@ -705,13 +770,13 @@ grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
        } else {
            rasprintf(&body, "-%c", c);
        }
-       addMacro(mb->mc, name, NULL, body, mb->depth);
+       pushMacro(mb->mc, name, NULL, body, mb->level, ME_AUTO);
        free(name);
        free(body);
 
        if (optarg) {
            rasprintf(&name, "-%c*", c);
-           addMacro(mb->mc, name, NULL, optarg, mb->depth);
+           pushMacro(mb->mc, name, NULL, optarg, mb->level, ME_AUTO);
            free(name);
        }
     }
@@ -719,7 +784,7 @@ grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
     /* Add argument count (remaining non-option items) as macro. */
     {  char *ac = NULL;
        rasprintf(&ac, "%d", (argc - optind));
-       addMacro(mb->mc, "#", NULL, ac, mb->depth);
+       pushMacro(mb->mc, "#", NULL, ac, mb->level, ME_AUTO);
        free(ac);
     }
 
@@ -728,41 +793,70 @@ grabArgs(MacroBuf mb, const rpmMacroEntry me, const char * se,
        for (c = optind; c < argc; c++) {
            char *name = NULL;
            rasprintf(&name, "%d", (c - optind + 1));
-           addMacro(mb->mc, name, NULL, argv[c], mb->depth);
+           pushMacro(mb->mc, name, NULL, argv[c], mb->level, ME_AUTO);
            free(name);
        }
     }
 
     /* Add concatenated unexpanded arguments as yet another macro. */
     args = argvJoin(argv + optind, " ");
-    addMacro(mb->mc, "*", NULL, args ? args : "", mb->depth);
+    pushMacro(mb->mc, "*", NULL, args ? args : "", mb->level, ME_AUTO);
     free(args);
 
 exit:
     argvFree(argv);
-    return *lastc ? lastc + 1 : lastc; 
+    return cont;
 }
 
 /**
  * Perform macro message output
  * @param mb           macro expansion state
  * @param waserror     use rpmlog()?
- * @param msg          message to ouput
+ * @param msg          message to output
  * @param msglen       no. of bytes in message
  */
 static void
-doOutput(MacroBuf mb, int waserror, const char * msg, size_t msglen)
+doOutput(MacroBuf mb, int loglevel, const char * msg, size_t msglen)
 {
     char *buf = NULL;
 
     (void) expandThis(mb, msg, msglen, &buf);
-    if (waserror)
-       rpmlog(RPMLOG_ERR, "%s\n", buf);
-    else
-       fprintf(stderr, "%s", buf);
+    rpmlog(loglevel, "%s\n", buf);
     _free(buf);
 }
 
+static void doLua(MacroBuf mb, const char * f, size_t fn, const char * g, size_t gn)
+{
+#ifdef WITH_LUA
+    rpmlua lua = NULL; /* Global state. */
+    char *scriptbuf = xmalloc(gn + 1);
+    char *printbuf;
+    rpmMacroContext mc = mb->mc;
+    int odepth = mc->depth;
+    int olevel = mc->level;
+
+    if (g != NULL && gn > 0)
+       memcpy(scriptbuf, g, gn);
+    scriptbuf[gn] = '\0';
+    rpmluaPushPrintBuffer(lua);
+    mc->depth = mb->depth;
+    mc->level = mb->level;
+    if (rpmluaRunScript(lua, scriptbuf, NULL) == -1)
+       mb->error = 1;
+    mc->depth = odepth;
+    mc->level = olevel;
+    printbuf = rpmluaPopPrintBuffer(lua);
+    if (printbuf) {
+       mbAppendStr(mb, printbuf);
+       free(printbuf);
+    }
+    free(scriptbuf);
+#else
+    rpmlog(RPMLOG_ERR, _("<lua> scriptlet support not built in\n"));
+    mb->error = 1;
+#endif
+}
+
 /**
  * Execute macro primitives.
  * @param mb           macro expansion state
@@ -779,8 +873,14 @@ doFoo(MacroBuf mb, int negate, const char * f, size_t fn,
     char *buf = NULL;
     char *b = NULL, *be;
     int c;
+    int verbose = (rpmIsVerbose() != 0);
+    int expand = (g != NULL && gn > 0);
+
+    /* Don't expand %{verbose:...} argument on false condition */
+    if (STREQ("verbose", f, fn) && (verbose == negate))
+       expand = 0;
 
-    if (g != NULL && gn > 0) {
+    if (expand) {
        (void) expandThis(mb, g, gn, &buf);
     } else {
        buf = xmalloc(MACROBUFSIZ + fn + gn);
@@ -795,16 +895,38 @@ doFoo(MacroBuf mb, int negate, const char * f, size_t fn,
        if ((b = strrchr(buf, '/')) != NULL)
            *b = '\0';
        b = buf;
+    } else if (STREQ("shrink", f, fn)) {
+       /*
+        * shrink body by removing all leading and trailing whitespaces and
+        * reducing intermediate whitespaces to a single space character.
+        */
+       size_t i = 0, j = 0;
+       size_t buflen = strlen(buf);
+       int was_space = 0;
+       while (i < buflen) {
+           if (risspace(buf[i])) {
+               was_space = 1;
+               i++;
+               continue;
+           } else if (was_space) {
+               was_space = 0;
+               if (j > 0) /* remove leading blanks at all */
+                   buf[j++] = ' ';
+           }
+           buf[j++] = buf[i++];
+       }
+       buf[j] = '\0';
+       b = buf;
+    } else if (STREQ("quote", f, fn)) {
+       char *quoted = NULL;
+       rasprintf(&quoted, "%c%s%c", 0x1f, buf, 0x1f);
+       free(buf);
+       b = buf = quoted;
     } else if (STREQ("suffix", f, fn)) {
        if ((b = strrchr(buf, '.')) != NULL)
            b++;
-    } else if (STREQ("expand", f, fn)) {
+    } else if (STREQ("expand", f, fn) || STREQ("verbose", f, fn)) {
        b = buf;
-    } else if (STREQ("verbose", f, fn)) {
-       if (negate)
-           b = (rpmIsVerbose() ? NULL : buf);
-       else
-           b = (rpmIsVerbose() ? buf : NULL);
     } else if (STREQ("url2path", f, fn) || STREQ("u2p", f, fn)) {
        (void)urlPath(buf, (const char **)&b);
        if (*b == '\0') b = "/";
@@ -816,7 +938,7 @@ doFoo(MacroBuf mb, int negate, const char * f, size_t fn,
            be++;
        *be++ = '\0';
        (void) rpmFileIsCompressed(b, &compressed);
-       switch(compressed) {
+       switch (compressed) {
        default:
        case COMPRESSED_NOT:
            sprintf(be, "%%__cat %s", b);
@@ -843,6 +965,9 @@ doFoo(MacroBuf mb, int negate, const char * f, size_t fn,
        case COMPRESSED_7ZIP:
            sprintf(be, "%%__7zip x %s", b);
            break;
+       case COMPRESSED_ZSTD:
+           sprintf(be, "%%__zstd -dc %s", b);
+           break;
        }
        b = be;
     } else if (STREQ("getenv", f, fn)) {
@@ -881,6 +1006,7 @@ doFoo(MacroBuf mb, int negate, const char * f, size_t fn,
  * The main macro recursion loop.
  * @param mb           macro expansion state
  * @param src          string to expand
+ * @param slen         length of string buffer
  * @return             0 on success, 1 on failure
  */
 static int
@@ -893,11 +1019,12 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
     const char *g, *ge;
     size_t fn, gn, tpos;
     int c;
-    int rc = 0;
     int negate;
     const char * lastc;
     int chkexist;
     char *source = NULL;
+    int store_macro_trace;
+    int store_expand_trace;
 
     /*
      * Always make a (terminated) copy of the source string.
@@ -920,30 +1047,34 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
        mb->nb = blen;
     }
     tpos = mb->tpos; /* save expansion pointer for printExpand */
+    store_macro_trace = mb->macro_trace;
+    store_expand_trace = mb->expand_trace;
 
     if (++mb->depth > max_macro_depth) {
        rpmlog(RPMLOG_ERR,
                _("Too many levels of recursion in macro expansion. It is likely caused by recursive macro declaration.\n"));
        mb->depth--;
        mb->expand_trace = 1;
-       _free(source);
-       return 1;
+       mb->error = 1;
+       goto exit;
     }
 
-    while (rc == 0 && (c = *s) != '\0') {
+    while (mb->error == 0 && (c = *s) != '\0') {
        s++;
        /* Copy text until next macro */
-       switch(c) {
+       switch (c) {
        case '%':
-               if (*s) {       /* Ensure not end-of-string. */
-                   if (*s != '%')
-                       break;
-                   s++;        /* skip first % in %% */
-               }
+           if (*s) {   /* Ensure not end-of-string. */
+               if (*s != '%')
+                   break;
+               s++;    /* skip first % in %% */
+               if (mb->escape)
+                   mbAppend(mb, c);
+           }
        default:
-               mbAppend(mb, c);
-               continue;
-               break;
+           mbAppend(mb, c);
+           continue;
+           break;
        }
 
        /* Expand next macro */
@@ -956,182 +1087,183 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
        chkexist = 0;
        switch ((c = *s)) {
        default:                /* %name substitution */
-               while (strchr("!?", *s) != NULL) {
-                       switch(*s++) {
-                       case '!':
-                               negate = ((negate + 1) % 2);
-                               break;
-                       case '?':
-                               chkexist++;
-                               break;
-                       }
-               }
-               f = se = s;
-               if (*se == '-')
-                       se++;
-               while((c = *se) && (risalnum(c) || c == '_'))
-                       se++;
-               /* Recognize non-alnum macros too */
-               switch (*se) {
-               case '*':
-                       se++;
-                       if (*se == '*') se++;
-                       break;
-               case '#':
-                       se++;
+           while (*s != '\0' && strchr("!?", *s) != NULL) {
+               switch (*s++) {
+                   case '!':
+                       negate = ((negate + 1) % 2);
                        break;
-               default:
+                   case '?':
+                       chkexist++;
                        break;
                }
-               fe = se;
-               /* For "%name " macros ... */
-               if ((c = *fe) && isblank(c))
-                       if ((lastc = strchr(fe,'\n')) == NULL)
-                lastc = strchr(fe, '\0');
+           }
+           f = se = s;
+           if (*se == '-')
+               se++;
+           while ((c = *se) && (risalnum(c) || c == '_'))
+               se++;
+           /* Recognize non-alnum macros too */
+           switch (*se) {
+           case '*':
+               se++;
+               if (*se == '*') se++;
+               break;
+           case '#':
+               se++;
+               break;
+           default:
                break;
+           }
+           fe = se;
+           /* For "%name " macros ... */
+           if ((c = *fe) && isblank(c))
+               if ((lastc = strchr(fe,'\n')) == NULL)
+                   lastc = strchr(fe, '\0');
+           break;
        case '(':               /* %(...) shell escape */
-               if ((se = matchchar(s, c, ')')) == NULL) {
-                       rpmlog(RPMLOG_ERR,
-                               _("Unterminated %c: %s\n"), (char)c, s);
-                       rc = 1;
-                       continue;
-               }
-               if (mb->macro_trace)
-                       printMacro(mb, s, se+1);
+           if ((se = matchchar(s, c, ')')) == NULL) {
+               rpmlog(RPMLOG_ERR, _("Unterminated %c: %s\n"), (char)c, s);
+               mb->error = 1;
+               continue;
+           }
+           if (mb->macro_trace)
+               printMacro(mb, s, se+1);
 
-               s++;    /* skip ( */
-               rc = doShellEscape(mb, s, (se - s));
-               se++;   /* skip ) */
+           s++;        /* skip ( */
+           doShellEscape(mb, s, (se - s));
+           se++;       /* skip ) */
 
-               s = se;
-               continue;
-               break;
+           s = se;
+           continue;
+           break;
        case '{':               /* %{...}/%{...:...} substitution */
-               if ((se = matchchar(s, c, '}')) == NULL) {
-                       rpmlog(RPMLOG_ERR,
-                               _("Unterminated %c: %s\n"), (char)c, s);
-                       rc = 1;
-                       continue;
-               }
-               f = s+1;/* skip { */
-               se++;   /* skip } */
-               while (strchr("!?", *f) != NULL) {
-                       switch(*f++) {
-                       case '!':
-                               negate = ((negate + 1) % 2);
-                               break;
-                       case '?':
-                               chkexist++;
-                               break;
-                       }
-               }
-               for (fe = f; (c = *fe) && !strchr(" :}", c);)
-                       fe++;
-               switch (c) {
-               case ':':
-                       g = fe + 1;
-                       ge = se - 1;
-                       break;
-               case ' ':
-                       lastc = se-1;
-                       break;
-               default:
-                       break;
+           if ((se = matchchar(s, c, '}')) == NULL) {
+               rpmlog(RPMLOG_ERR, _("Unterminated %c: %s\n"), (char)c, s);
+               mb->error = 1;
+               continue;
+           }
+           f = s+1;/* skip { */
+           se++;       /* skip } */
+           while (strchr("!?", *f) != NULL) {
+               switch (*f++) {
+               case '!':
+                   negate = ((negate + 1) % 2);
+                   break;
+               case '?':
+                   chkexist++;
+                   break;
                }
+           }
+           for (fe = f; (c = *fe) && !strchr(" :}", c);)
+               fe++;
+           switch (c) {
+           case ':':
+               g = fe + 1;
+               ge = se - 1;
+               break;
+           case ' ':
+               lastc = se-1;
                break;
+           default:
+               break;
+           }
+           break;
        }
 
        /* XXX Everything below expects fe > f */
        fn = (fe - f);
        gn = (ge - g);
        if ((fe - f) <= 0) {
-/* XXX Process % in unknown context */
-               c = '%';        /* XXX only need to save % */
-               mbAppend(mb, c);
+           /* XXX Process % in unknown context */
+           c = '%';    /* XXX only need to save % */
+           mbAppend(mb, c);
 #if 0
-               rpmlog(RPMLOG_ERR,
-                       _("A %% is followed by an unparseable macro\n"));
+           rpmlog(RPMLOG_ERR,
+                   _("A %% is followed by an unparseable macro\n"));
 #endif
-               s = se;
-               continue;
+           s = se;
+           continue;
        }
 
        if (mb->macro_trace)
-               printMacro(mb, s, se);
+           printMacro(mb, s, se);
 
        /* Expand builtin macros */
+       if (STREQ("load", f, fn)) {
+           char *arg = NULL;
+           if (g && gn > 0 && expandThis(mb, g, gn, &arg) == 0) {
+               /* Print failure iff %{load:...} or %{!?load:...} */
+               if (loadMacroFile(mb->mc, arg) && chkexist == negate) {
+                   rpmlog(RPMLOG_ERR, _("failed to load macro file %s"), arg);
+                   mb->error = 1;
+               }
+           }
+           free(arg);
+           s = se;
+           continue;
+       }
        if (STREQ("global", f, fn)) {
-               s = doDefine(mb, se, RMIL_GLOBAL, 1);
-               continue;
+           s = doDefine(mb, se, slen - (se - s), RMIL_GLOBAL, 1);
+           continue;
        }
        if (STREQ("define", f, fn)) {
-               s = doDefine(mb, se, mb->depth, 0);
-               continue;
+           s = doDefine(mb, se, slen - (se - s), mb->level, 0);
+           continue;
        }
        if (STREQ("undefine", f, fn)) {
-               s = doUndefine(mb->mc, se);
-               continue;
+           s = doUndefine(mb, se, slen - (se - s));
+           continue;
        }
 
        if (STREQ("echo", f, fn) ||
-           STREQ("warn", f, fn) ||
-           STREQ("error", f, fn)) {
-               int waserror = 0;
-               if (STREQ("error", f, fn))
-                       waserror = 1;
-               if (g != NULL && g < ge)
-                       doOutput(mb, waserror, g, gn);
-               else
-                       doOutput(mb, waserror, f, fn);
-               s = se;
-               continue;
+               STREQ("warn", f, fn) ||
+               STREQ("error", f, fn)) {
+           int loglevel = RPMLOG_NOTICE; /* assume echo */
+           if (STREQ("error", f, fn)) {
+               loglevel = RPMLOG_ERR;
+               mb->error = 1;
+           } else if (STREQ("warn", f, fn)) {
+               loglevel = RPMLOG_WARNING;
+           }
+           if (g != NULL && g < ge)
+               doOutput(mb, loglevel, g, gn);
+           else
+               doOutput(mb, loglevel, "", 0);
+           s = se;
+           continue;
        }
 
        if (STREQ("trace", f, fn)) {
-               /* XXX TODO restore expand_trace/macro_trace to 0 on return */
-               mb->expand_trace = mb->macro_trace = (negate ? 0 : mb->depth);
-               if (mb->depth == 1) {
-                       print_macro_trace = mb->macro_trace;
-                       print_expand_trace = mb->expand_trace;
-               }
-               s = se;
-               continue;
+           /* XXX TODO restore expand_trace/macro_trace to 0 on return */
+           mb->expand_trace = mb->macro_trace = (negate ? 0 : mb->depth);
+           if (mb->depth == 1) {
+               print_macro_trace = mb->macro_trace;
+               print_expand_trace = mb->expand_trace;
+           }
+           s = se;
+           continue;
        }
 
        if (STREQ("dump", f, fn)) {
-               rpmDumpMacroTable(mb->mc, NULL);
-               while (iseol(*se))
-                       se++;
-               s = se;
-               continue;
+           rpmDumpMacroTable(mb->mc, NULL);
+           while (iseol(*se))
+               se++;
+           s = se;
+           continue;
        }
 
-#ifdef WITH_LUA
        if (STREQ("lua", f, fn)) {
-               rpmlua lua = NULL; /* Global state. */
-               char *scriptbuf = xmalloc(gn + 1);
-               char *printbuf;
-               if (g != NULL && gn > 0) 
-                   memcpy(scriptbuf, g, gn);
-               scriptbuf[gn] = '\0';
-               rpmluaPushPrintBuffer(lua);
-               if (rpmluaRunScript(lua, scriptbuf, NULL) == -1)
-                   rc = 1;
-               printbuf = rpmluaPopPrintBuffer(lua);
-               if (printbuf) {
-                   mbAppendStr(mb, printbuf);
-                   free(printbuf);
-               }
-               free(scriptbuf);
-               s = se;
-               continue;
+           doLua(mb, f, fn, g, gn);
+           s = se;
+           continue;
        }
-#endif
 
        /* XXX necessary but clunky */
        if (STREQ("basename", f, fn) ||
            STREQ("dirname", f, fn) ||
+           STREQ("shrink", f, fn) ||
            STREQ("suffix", f, fn) ||
+           STREQ("quote", f, fn) ||
            STREQ("expand", f, fn) ||
            STREQ("verbose", f, fn) ||
            STREQ("uncompress", f, fn) ||
@@ -1141,110 +1273,116 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
            STREQ("getconfdir", f, fn) ||
            STREQ("S", f, fn) ||
            STREQ("P", f, fn) ||
-           STREQ("F", f, fn)) {
-               /* FIX: verbose may be set */
-               doFoo(mb, negate, f, fn, g, gn);
-               s = se;
-               continue;
+           STREQ("F", f, fn))
+       {
+           /* FIX: verbose may be set */
+           doFoo(mb, negate, f, fn, g, gn);
+           s = se;
+           continue;
        }
 
        /* Expand defined macros */
        mep = findEntry(mb->mc, f, fn, NULL);
        me = (mep ? *mep : NULL);
 
+       if (me) {
+           if ((me->flags & ME_AUTO) && mb->level > me->level) {
+               /* Ignore out-of-scope automatic macros */
+               me = NULL;
+           } else {
+               /* If we looked up a macro, consider it used */
+               me->flags |= ME_USED;
+           }
+       }
+
        /* XXX Special processing for flags */
        if (*f == '-') {
-               if (me)
-                       me->used++;     /* Mark macro as used */
-               if ((me == NULL && !negate) ||  /* Without -f, skip %{-f...} */
+           if ((me == NULL && !negate) ||      /* Without -f, skip %{-f...} */
                    (me != NULL && negate)) {   /* With -f, skip %{!-f...} */
-                       s = se;
-                       continue;
-               }
+               s = se;
+               continue;
+           }
 
-               if (g && g < ge) {              /* Expand X in %{-f:X} */
-                       rc = expandMacro(mb, g, gn);
-               } else
+           if (g && g < ge) {          /* Expand X in %{-f:X} */
+               expandMacro(mb, g, gn);
+           } else
                if (me && me->body && *me->body) {/* Expand %{-f}/%{-f*} */
-                       rc = expandMacro(mb, me->body, 0);
+                   expandMacro(mb, me->body, 0);
                }
-               s = se;
-               continue;
+           s = se;
+           continue;
        }
 
        /* XXX Special processing for macro existence */
        if (chkexist) {
-               if ((me == NULL && !negate) ||  /* Without -f, skip %{?f...} */
+           if ((me == NULL && !negate) ||      /* Without -f, skip %{?f...} */
                    (me != NULL && negate)) {   /* With -f, skip %{!?f...} */
-                       s = se;
-                       continue;
-               }
-               if (g && g < ge) {              /* Expand X in %{?f:X} */
-                       rc = expandMacro(mb, g, gn);
-               } else
-               if (me && me->body && *me->body) { /* Expand %{?f}/%{?f*} */
-                       rc = expandMacro(mb, me->body, 0);
-               }
                s = se;
                continue;
+           }
+           if (g && g < ge) {          /* Expand X in %{?f:X} */
+               expandMacro(mb, g, gn);
+           } else
+               if (me && me->body && *me->body) { /* Expand %{?f}/%{?f*} */
+                   expandMacro(mb, me->body, 0);
+               }
+           s = se;
+           continue;
        }
        
        if (me == NULL) {       /* leave unknown %... as is */
-               /* XXX hack to permit non-overloaded %foo to be passed */
-               c = '%';        /* XXX only need to save % */
-               mbAppend(mb, c);
-               continue;
+           /* XXX hack to permit non-overloaded %foo to be passed */
+           c = '%';    /* XXX only need to save % */
+           mbAppend(mb, c);
+           continue;
        }
 
        /* Setup args for "%name " macros with opts */
        if (me && me->opts != NULL) {
-               if (lastc != NULL) {
-                       se = grabArgs(mb, me, fe, lastc);
-               } else {
-                       addMacro(mb->mc, "**", NULL, "", mb->depth);
-                       addMacro(mb->mc, "*", NULL, "", mb->depth);
-                       addMacro(mb->mc, "#", NULL, "0", mb->depth);
-                       addMacro(mb->mc, "0", NULL, me->name, mb->depth);
-               }
+           const char *xe = grabArgs(mb, me, fe, lastc);
+           if (xe != NULL)
+               se = xe;
        }
 
        /* Recursively expand body of macro */
        if (me->body && *me->body) {
-               rc = expandMacro(mb, me->body, 0);
-               if (rc == 0)
-                       me->used++;     /* Mark macro as used */
+           expandMacro(mb, me->body, 0);
        }
 
        /* Free args for "%name " macros with opts */
        if (me->opts != NULL)
-               freeArgs(mb);
+           freeArgs(mb);
 
        s = se;
     }
 
     mb->buf[mb->tpos] = '\0';
     mb->depth--;
-    if (rc != 0 || mb->expand_trace)
+    if (mb->error != 0 || mb->expand_trace)
        printExpansion(mb, mb->buf+tpos, mb->buf+mb->tpos);
+    mb->macro_trace = store_macro_trace;
+    mb->expand_trace = store_expand_trace;
+exit:
     _free(source);
-    return rc;
+    return mb->error;
 }
 
 
 /* =============================================================== */
 
-static int doExpandMacros(rpmMacroContext mc, const char *src, char **target)
+static int doExpandMacros(rpmMacroContext mc, const char *src, int flags,
+                       char **target)
 {
     MacroBuf mb = xcalloc(1, sizeof(*mb));
     int rc = 0;
 
-    if (mc == NULL) mc = rpmGlobalMacroContext;
-
     mb->buf = NULL;
-    mb->depth = 0;
+    mb->depth = mc->depth;
+    mb->level = mc->level;
     mb->macro_trace = print_macro_trace;
     mb->expand_trace = print_expand_trace;
     mb->mc = mc;
+    mb->flags = flags;
 
     rc = expandMacro(mb, src, 0);
 
@@ -1256,22 +1394,9 @@ static int doExpandMacros(rpmMacroContext mc, const char *src, char **target)
     return rc;
 }
 
-int expandMacros(void * spec, rpmMacroContext mc, char * sbuf, size_t slen)
-{
-    char *target = NULL;
-    int rc = doExpandMacros(mc, sbuf, &target);
-    rstrlcpy(sbuf, target, slen);
-    free(target);
-    return rc;
-}
-
-void
-addMacro(rpmMacroContext mc,
-       const char * n, const char * o, const char * b, int level)
+static void pushMacro(rpmMacroContext mc,
+       const char * n, const char * o, const char * b, int level, int flags)
 {
-    if (mc == NULL)
-       mc = rpmGlobalMacroContext;
-
     /* new entry */
     rpmMacroEntry me;
     /* pointer into me */
@@ -1328,19 +1453,16 @@ addMacro(rpmMacroContext mc,
     else
        me->opts = o ? "" : NULL;
     /* initialize */
-    me->used = 0;
+    me->flags = flags;
+    me->flags &= ~(ME_USED);
     me->level = level;
     /* push over previous definition */
     me->prev = *mep;
     *mep = me;
 }
 
-void
-delMacro(rpmMacroContext mc, const char * n)
+static void popMacro(rpmMacroContext mc, const char * n)
 {
-    if (mc == NULL)
-       mc = rpmGlobalMacroContext;
-
     size_t pos;
     rpmMacroEntry *mep = findEntry(mc, n, 0, &pos);
     if (mep == NULL)
@@ -1363,34 +1485,20 @@ delMacro(rpmMacroContext mc, const char * n)
     free(me);
 }
 
-int
-rpmDefineMacro(rpmMacroContext mc, const char * macro, int level)
+static int defineMacro(rpmMacroContext mc, const char * macro, int level)
 {
     MacroBuf mb = xcalloc(1, sizeof(*mb));
+    int rc;
 
     /* XXX just enough to get by */
-    mb->mc = (mc ? mc : rpmGlobalMacroContext);
-    (void) doDefine(mb, macro, level, 0);
+    mb->mc = mc;
+    (void) doDefine(mb, macro, strlen(macro), level, 0);
+    rc = mb->error;
     _free(mb);
-    return 0;
-}
-
-void
-rpmLoadMacros(rpmMacroContext mc, int level)
-{
-
-    if (mc == NULL || mc == rpmGlobalMacroContext)
-       return;
-
-    for (int i = 0; i < mc->n; i++) {
-       rpmMacroEntry me = mc->tab[i];
-       assert(me);
-       addMacro(NULL, me->name, me->opts, me->body, (level - 1));
-    }
+    return rc;
 }
 
-int
-rpmLoadMacroFile(rpmMacroContext mc, const char * fn)
+static int loadMacroFile(rpmMacroContext mc, const char * fn)
 {
     FILE *fd = fopen(fn, "r");
     size_t blen = MACROBUFSIZ;
@@ -1400,11 +1508,8 @@ rpmLoadMacroFile(rpmMacroContext mc, const char * fn)
     if (fd == NULL)
        goto exit;
 
-    /* XXX Assume new fangled macro expansion */
-    max_macro_depth = 16;
-
     buf[0] = '\0';
-    while(rdcl(buf, blen, fd) != NULL) {
+    while (rdcl(buf, blen, fd) != NULL) {
        char c, *n;
 
        n = buf;
@@ -1413,8 +1518,9 @@ rpmLoadMacroFile(rpmMacroContext mc, const char * fn)
        if (c != '%')
                continue;
        n++;    /* skip % */
-       rc = rpmDefineMacro(mc, n, RMIL_MACROFILES);
+       rc = defineMacro(mc, n, RMIL_MACROFILES);
     }
+
     rc = fclose(fd);
 
 exit:
@@ -1422,15 +1528,124 @@ exit:
     return rc;
 }
 
+static void copyMacros(rpmMacroContext src, rpmMacroContext dst, int level)
+{
+    for (int i = 0; i < src->n; i++) {
+       rpmMacroEntry me = src->tab[i];
+       assert(me);
+       pushMacro(dst, me->name, me->opts, me->body, level, me->flags);
+    }
+}
+
+/* External interfaces */
+
+int rpmExpandMacros(rpmMacroContext mc, const char * sbuf, char ** obuf, int flags)
+{
+    char *target = NULL;
+    int rc;
+
+    mc = rpmmctxAcquire(mc);
+    rc = doExpandMacros(mc, sbuf, flags, &target);
+    rpmmctxRelease(mc);
+
+    if (rc) {
+       free(target);
+       return -1;
+    } else {
+       *obuf = target;
+       return 1;
+    }
+}
+
+void
+rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
+{
+    mc = rpmmctxAcquire(mc);
+    if (fp == NULL) fp = stderr;
+    
+    fprintf(fp, "========================\n");
+    for (int i = 0; i < mc->n; i++) {
+       rpmMacroEntry me = mc->tab[i];
+       assert(me);
+       fprintf(fp, "%3d%c %s", me->level,
+                   ((me->flags & ME_USED) ? '=' : ':'), me->name);
+       if (me->opts && *me->opts)
+               fprintf(fp, "(%s)", me->opts);
+       if (me->body && *me->body)
+               fprintf(fp, "\t%s", me->body);
+       fprintf(fp, "\n");
+    }
+    fprintf(fp, _("======================== active %d empty %d\n"),
+               mc->n, 0);
+    rpmmctxRelease(mc);
+}
+
+int rpmPushMacro(rpmMacroContext mc,
+             const char * n, const char * o, const char * b, int level)
+{
+    mc = rpmmctxAcquire(mc);
+    pushMacro(mc, n, o, b, level, ME_NONE);
+    rpmmctxRelease(mc);
+    return 0;
+}
+
+int rpmPopMacro(rpmMacroContext mc, const char * n)
+{
+    mc = rpmmctxAcquire(mc);
+    popMacro(mc, n);
+    rpmmctxRelease(mc);
+    return 0;
+}
+
+int
+rpmDefineMacro(rpmMacroContext mc, const char * macro, int level)
+{
+    int rc;
+    mc = rpmmctxAcquire(mc);
+    rc = defineMacro(mc, macro, level);
+    rpmmctxRelease(mc);
+    return rc;
+}
+
+void
+rpmLoadMacros(rpmMacroContext mc, int level)
+{
+    rpmMacroContext gmc;
+    if (mc == NULL || mc == rpmGlobalMacroContext)
+       return;
+
+    gmc = rpmmctxAcquire(NULL);
+    mc = rpmmctxAcquire(mc);
+
+    copyMacros(mc, gmc, level);
+
+    rpmmctxRelease(mc);
+    rpmmctxRelease(gmc);
+}
+
+int
+rpmLoadMacroFile(rpmMacroContext mc, const char * fn)
+{
+    int rc;
+
+    mc = rpmmctxAcquire(mc);
+    rc = loadMacroFile(mc, fn);
+    rpmmctxRelease(mc);
+
+    return rc;
+}
+
 void
 rpmInitMacros(rpmMacroContext mc, const char * macrofiles)
 {
     ARGV_t pattern, globs = NULL;
+    rpmMacroContext climc;
 
     if (macrofiles == NULL)
        return;
 
     argvSplit(&globs, macrofiles, ":");
+    mc = rpmmctxAcquire(mc);
     for (pattern = globs; *pattern; pattern++) {
        ARGV_t path, files = NULL;
     
@@ -1446,26 +1661,30 @@ rpmInitMacros(rpmMacroContext mc, const char * macrofiles)
                rpmFileHasSuffix(*path, ".rpmorig")) {
                continue;
            }
-           (void) rpmLoadMacroFile(mc, *path);
+           (void) loadMacroFile(mc, *path);
        }
        argvFree(files);
     }
     argvFree(globs);
 
     /* Reload cmdline macros */
-    rpmLoadMacros(rpmCLIMacroContext, RMIL_CMDLINE);
+    climc = rpmmctxAcquire(rpmCLIMacroContext);
+    copyMacros(climc, mc, RMIL_CMDLINE);
+    rpmmctxRelease(climc);
+
+    rpmmctxRelease(mc);
 }
 
 void
 rpmFreeMacros(rpmMacroContext mc)
 {
-    if (mc == NULL)
-       mc = rpmGlobalMacroContext;
+    mc = rpmmctxAcquire(mc);
     while (mc->n > 0) {
        /* remove from the end to avoid memmove */
        rpmMacroEntry me = mc->tab[mc->n - 1];
-       delMacro(mc, me->name);
+       popMacro(mc, me->name);
     }
+    rpmmctxRelease(mc);
 }
 
 char * 
@@ -1476,6 +1695,7 @@ rpmExpand(const char *arg, ...)
     char *pe;
     const char *s;
     va_list ap;
+    rpmMacroContext mc;
 
     if (arg == NULL) {
        ret = xstrdup("");
@@ -1496,7 +1716,9 @@ rpmExpand(const char *arg, ...)
        pe = stpcpy(pe, s);
     va_end(ap);
 
-    (void) doExpandMacros(NULL, buf, &ret);
+    mc = rpmmctxAcquire(NULL);
+    (void) doExpandMacros(mc, buf, 0, &ret);
+    rpmmctxRelease(mc);
 
     free(buf);
 exit: