/** \ingroup rpmrc rpmio * \file rpmio/macro.c */ #include "system.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SCHED_GETAFFINITY #include #endif #if defined(__linux__) #include #endif #if !defined(isblank) #define isblank(_c) ((_c) == ' ' || (_c) == '\t') #endif #define iseol(_c) ((_c) == '\n' || (_c) == '\r') #define MACROBUFSIZ (BUFSIZ * 2) #include #include #include #include #include #include #include #include "rpmlua.h" #include "rpmmacro_internal.h" #include "debug.h" using std::string; enum macroFlags_e { ME_NONE = 0, ME_AUTO = (1 << 0), ME_USED = (1 << 1), ME_LITERAL = (1 << 2), ME_PARSE = (1 << 3), ME_FUNC = (1 << 4), ME_QUOTED = (1 << 5), }; /*! The structure used to store a macro. */ struct rpmMacroEntry_s { const char *name; /*!< Macro name. */ const char *opts; /*!< Macro parameters (a la getopt) */ const char *body; /*!< Macro body. */ macroFunc func; /*!< Macro function (builtin macros) */ void *priv; /*!< Private user data (aux macros) */ int nargs; /*!< Number of required args */ int flags; /*!< Macro state bits. */ int level; /*!< Scoping level. */ string sbody; string sopts; }; using macroTable = std::map>; using wrlock = std::lock_guard; /*! The structure used to store the set of macros in a context. */ /* * 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. */ struct rpmMacroContext_s { macroTable tab {}; /*!< Map of macro entry stacks */ int depth {}; /*!< Depth tracking on external recursion */ int level {}; /*!< Scope level tracking when on external recursion */ std::recursive_mutex mutex {}; }; static struct rpmMacroContext_s rpmGlobalMacroContext_s; rpmMacroContext rpmGlobalMacroContext = &rpmGlobalMacroContext_s; static struct rpmMacroContext_s rpmCLIMacroContext_s; rpmMacroContext rpmCLIMacroContext = &rpmCLIMacroContext_s; /** * Macro expansion state. */ struct rpmMacroBuf_s { std::string buf; /*!< 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 flags; /*!< Flags to control behavior */ rpmMacroEntry me; /*!< Current macro (or NULL if anonymous) */ ARGV_t args; /*!< Current macro arguments (or NULL) */ rpmMacroContext mc; }; /** * Expansion data for a scoping level */ typedef struct MacroExpansionData_s { size_t tpos; int macro_trace; int expand_trace; } MacroExpansionData; #define _MAX_MACRO_DEPTH 64 static int max_macro_depth = _MAX_MACRO_DEPTH; #define _PRINT_MACRO_TRACE 0 static int print_macro_trace = _PRINT_MACRO_TRACE; #define _PRINT_EXPAND_TRACE 0 static int print_expand_trace = _PRINT_EXPAND_TRACE; /* forward ref */ static int expandMacro(rpmMacroBuf mb, const char *src, size_t slen); static int expandQuotedMacro(rpmMacroBuf mb, const char *src); 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); /* =============================================================== */ static void freeMacros(rpmMacroContext mc) { mc->tab.clear(); } static rpmMacroContext rpmmctxAcquire(rpmMacroContext mc) { if (mc == NULL) mc = rpmGlobalMacroContext; return mc; } /** * Find entry in macro table. * @param mc macro context * @param name macro name * @param namelen no. of bytes * @param pos found/insert position * @return address of slot in macro table with name (or NULL) */ static rpmMacroEntry findEntry(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos) { if (namelen == 0) namelen = strlen(name); string n(name, namelen); auto const & entry = mc->tab.find(n); if (entry == mc->tab.end()) return NULL; return &entry->second.top(); } /* =============================================================== */ /** * fgets(3) analogue that reads \ continuations. Last newline always trimmed. * @param buf input buffer * @param size inbut buffer size (bytes) * @param f file handle * @return number of lines read, or 0 on end-of-file */ static int rdcl(char * buf, size_t size, FILE *f) { size_t nb = 0; int pc = 0, bc = 0, xc = 0; int nlines = 0; char *p = buf; char *q = buf; if (f != NULL) do { *q = '\0'; /* terminate */ if (fgets(q, size, f) == NULL) /* read next line. */ break; nlines++; nb = strlen(q); for (q += nb; nb > 0 && iseol(q[-1]); q--) nb--; if (*q == 0) break; /* no newline found, EOF */ if (p == buf) { while (*p && isblank(*p)) p++; if (*p != '%') { /* only parse actual macro */ *q = '\0'; /* trim trailing \r, \n */ break; } } for (; p < q; p++) { switch (*p) { case '\\': switch (*(p+1)) { case '\0': break; default: p++; break; } break; case '%': switch (*(p+1)) { case '{': p++, bc++; break; case '(': p++, pc++; break; case '[': p++, xc++; break; case '%': p++; break; } break; case '{': if (bc > 0) bc++; break; 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[-1] != '\\') && !bc && !pc && !xc) { *q = '\0'; /* trim trailing \r, \n */ break; } q++; nb++; /* copy newline too */ size -= nb; if (q[-1] == '\r') /* XXX avoid \r madness */ q[-1] = '\n'; } while (size > 0); return nlines; } /** * Return text between pl and matching pr characters. * @param p start of text * @param pl left char, i.e. '[', '(', '{', etc. * @param pr right char, i.e. ']', ')', '}', etc. * @return address of char after pr (or NULL) */ static const char * matchchar(const char * p, char pl, char pr) { int lvl = 0; char c; while ((c = *p++) != '\0') { if (c == '\\') { /* Ignore escaped chars */ p++; continue; } if (c == pr) { if (--lvl <= 0) return p; } else if (c == pl) lvl++; } return (const char *)NULL; } void rpmMacroBufErr(rpmMacroBuf mb, int error, const char *fmt, ...) { char *emsg = NULL; int n; va_list ap; va_start(ap, fmt); n = rvasprintf(&emsg, fmt, ap); va_end(ap); if (n >= -1) { /* XXX should have an non-locking version for this */ char *pfx = rpmExpand("%{?__file_name:%{__file_name}: }", "%{?__file_lineno:line %{__file_lineno}: }", NULL); rpmlog(error ? RPMLOG_ERR : RPMLOG_WARNING, "%s%s", pfx, emsg); free(pfx); } if (error) mb->error = error; free(emsg); } /** * Pre-print macro expression to be expanded. * @param mb macro expansion state * @param s current expansion string * @param se end of string */ static void printMacro(rpmMacroBuf mb, const char * s, const char * se) { const char *senl; if (s >= se) { /* XXX just in case */ fprintf(stderr, _("%3d>%*s(empty)\n"), mb->depth, (2 * mb->depth + 1), ""); return; } if (s[-1] == '{') s--; /* Print only to first end-of-line (or end-of-string). */ for (senl = se; *senl && !iseol(*senl); senl++) {}; /* Substitute caret at end-of-macro position */ fprintf(stderr, "%3d>%*s%%%.*s^", mb->depth, (2 * mb->depth + 1), "", (int)(se - s), s); if (se[0] != '\0' && se[1] != '\0' && (senl - (se+1)) > 0) fprintf(stderr, "%-.*s", (int)(senl - (se+1)), se+1); fprintf(stderr, "\n"); } /** * Post-print expanded macro expression. * @param mb macro expansion state * @param t current expansion string result * @param te end of string */ static void printExpansion(rpmMacroBuf mb, rpmMacroEntry me, const char * t, const char * te) { const char *mname = me ? me->name : ""; if (!(te > t)) { fprintf(stderr, "%3d<%*s (%%%s)\n", mb->depth, (2 * mb->depth + 1), "", mname); return; } /* Shorten output which contains newlines */ while (te > t && iseol(te[-1])) te--; if (mb->depth > 0) { const char *tenl; /* Skip to last line of expansion */ while ((tenl = strchr(t, '\n')) && tenl < te) t = ++tenl; } fprintf(stderr, "%3d<%*s (%%%s)\n", mb->depth, (2 * mb->depth + 1), "", mname); if (te > t) fprintf(stderr, "%.*s", (int)(te - t), t); fprintf(stderr, "\n"); } #define SKIPBLANK(_s, _c) \ while (((_c) = *(_s)) && isblank(_c)) \ (_s)++; \ #define SKIPNONBLANK(_s, _c) \ while (((_c) = *(_s)) && !(isblank(_c) || iseol(_c))) \ (_s)++; \ #define COPYNAME(_ne, _s, _c) \ { SKIPBLANK(_s,_c); \ while (((_c) = *(_s)) && (risalnum(_c) || (_c) == '_')) \ *(_ne)++ = *(_s)++; \ *(_ne) = '\0'; \ } #define COPYOPTS(_oe, _s, _c) \ { \ while (((_c) = *(_s)) && (_c) != ')') \ *(_oe)++ = *(_s)++; \ *(_oe) = '\0'; \ } /** * Macro-expand string src, return result in dynamically allocated buffer. * @param mb macro expansion state * @param src string to expand * @param slen input string length (or 0 for strlen()) * @param[out] target pointer to expanded string (malloced) * @return result of expansion */ static int expandThis(rpmMacroBuf mb, const char * src, size_t slen, char **target, int *flagsp) { struct rpmMacroBuf_s umb; /* Copy other state from "parent", but we want a buffer of our own */ umb = *mb; umb.buf = ""; umb.error = 0; umb.flags &= ~(RPMEXPAND_HAVE_QUOTED | RPMEXPAND_KEEP_QUOTED); if (flagsp) umb.flags = *flagsp; /* In case of error, flag it in the "parent"... */ if (expandMacro(&umb, src, slen)) mb->error = 1; *target = xstrdup(umb.buf.c_str()); if (flagsp) *flagsp = umb.flags; /* ...but return code for this operation specifically */ return umb.error; } static rpmMacroBuf mbCreate(rpmMacroContext mc, int flags) { rpmMacroBuf mb = new rpmMacroBuf_s {}; 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; return mb; } static int mbInit(rpmMacroBuf mb, MacroExpansionData *med, size_t slen) { if (slen > 0) mb->buf.reserve(slen); if (++mb->depth > max_macro_depth) { mb->depth--; /* ensure error message can be rendered */ mb->mc->depth = 0; rpmMacroBufErr(mb, 1, _("Too many levels of recursion in macro expansion. It is likely caused by recursive macro declaration.\n")); return -1; } med->tpos = mb->buf.size(); /* save expansion pointer for printExpand */ med->macro_trace = mb->macro_trace; med->expand_trace = mb->expand_trace; return 0; } static void mbFini(rpmMacroBuf mb, rpmMacroEntry me, MacroExpansionData *med) { mb->depth--; if (mb->error && rpmIsVerbose()) mb->expand_trace = 1; if (mb->expand_trace) { printExpansion(mb, me, mb->buf.c_str() + med->tpos, mb->buf.c_str() + mb->buf.size()); } mb->macro_trace = med->macro_trace; mb->expand_trace = med->expand_trace; } void rpmMacroBufAppend(rpmMacroBuf mb, char c) { mb->buf += c; } void rpmMacroBufAppendStr(rpmMacroBuf mb, const char *str) { mb->buf += str; } static void mbUnquote(rpmMacroBuf mb, size_t off) { const int qchar = 0x1f; /* ASCII unit separator */ while (off < mb->buf.size()) { if (mb->buf[off] == qchar) { mb->buf.erase(off, 1); } else { off++; } } } static void doDnl(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { const char *se = argv[1]; const char *start = se, *end; const char *s = se; while (*s && !iseol(*s)) s++; end = (*s != '\0') ? s + 1 : s; if (parsed) *parsed += end - start; } /** * Expand output of shell command into target buffer. * @param mb macro expansion state * @param cmd shell command * @param clen no. bytes in shell command */ static void doShellEscape(rpmMacroBuf mb, const char * cmd, size_t clen) { char *buf = NULL; FILE *shf; int c; size_t tpos; if (expandThis(mb, cmd, clen, &buf, NULL)) goto exit; if ((shf = popen(buf, "r")) == NULL) { rpmMacroBufErr(mb, 1, _("Failed to open shell expansion pipe for command: " "%s: %m \n"), buf); goto exit; } tpos = mb->buf.size(); while ((c = fgetc(shf)) != EOF) { rpmMacroBufAppend(mb, c); } (void) pclose(shf); /* Delete trailing \r \n */ while (mb->buf.size() > tpos && iseol(mb->buf.back())) mb->buf.pop_back(); 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(rpmMacroBuf mb, const char * expr, size_t len) { char *buf = rstrndup(expr, len); char *result; rpmMacroContext mc = mb->mc; int odepth = mc->depth; int olevel = mc->level; mc->depth = mb->depth; mc->level = mb->level; result = rpmExprStrFlags(buf, RPMEXPR_EXPAND); mc->depth = odepth; mc->level = olevel; if (!result) { mb->error = 1; goto exit; } rpmMacroBufAppendStr(mb, result); free(result); exit: _free(buf); } static unsigned int getncpus(void) { unsigned int ncpus = 0; #ifdef HAVE_SCHED_GETAFFINITY cpu_set_t set; if (sched_getaffinity (0, sizeof(set), &set) == 0) ncpus = CPU_COUNT(&set); #endif /* Fallback to sysconf() if the above isn't supported or didn't work */ if (ncpus < 1) ncpus = sysconf(_SC_NPROCESSORS_ONLN); /* If all else fails, there's always the one we're running on... */ if (ncpus < 1) ncpus = 1; return ncpus; } static int validName(rpmMacroBuf mb, const char *name, size_t namelen, const char *action) { rpmMacroEntry mep; int rc = 0; int c; /* Names must start with alphabetic, or _ and be at least 2 chars */ if (!((c = *name) && (risalpha(c) || (c == '_' && namelen > 1)))) { rpmMacroBufErr(mb, 1, _("Macro %%%s has illegal name (%s)\n"), name, action); goto exit; } mep = findEntry(mb->mc, name, namelen, NULL); if (mep && mep->flags & (ME_FUNC|ME_AUTO)) { rpmMacroBufErr(mb, 1, _("Macro %%%s is a built-in (%s)\n"), name, action); goto exit; } rc = 1; exit: return rc; } /** * Parse (and execute) new macro definition. * @param mb macro expansion state * @param se macro definition to parse * @param level macro recursion level * @param expandbody should body be expanded? * @return number of consumed characters */ static void doDefine(rpmMacroBuf mb, const char * se, int level, int expandbody, size_t *parsed) { const char *start = se; const char *s = se; char *buf = (char *)xmalloc(strlen(s) + 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 */ int flags = ME_NONE; /* Copy name */ COPYNAME(ne, s, c); /* Copy opts (if present) */ oe = ne + 1; if (*s == '(') { s++; /* skip ( */ /* Options must be terminated with ')' */ if (strchr(s, ')')) { o = oe; COPYOPTS(oe, s, oc); s++; /* skip ) */ } else { rpmMacroBufErr(mb, 1, _("Macro %%%s has unterminated opts\n"), n); goto exit; } } /* Copy body, skipping over escaped newlines */ b = be = oe + 1; sbody = s; SKIPBLANK(s, c); if (!parsed) { strcpy(b, s); be = b + strlen(b); s += strlen(s); } else if (c == '{') { /* XXX permit silent {...} grouping */ if ((se = matchchar(s, c, '}')) == NULL) { rpmMacroBufErr(mb, 1, _("Macro %%%s has unterminated body\n"), n); se = s; /* XXX W2DO? */ goto exit; } s++; /* XXX skip { */ strncpy(b, s, (se - 1 - s)); b[se - 1 - s] = '\0'; be += strlen(b); s = se; /* move scan forward */ } else { /* otherwise free-field */ int bc = 0, pc = 0, xc = 0; while (*s && (bc || pc || !iseol(*s))) { switch (*s) { case '\\': switch (*(s+1)) { case '\0': break; default: s++; break; } break; case '%': switch (*(s+1)) { case '{': *be++ = *s++; bc++; break; case '(': *be++ = *s++; pc++; break; case '[': *be++ = *s++; xc++; break; case '%': *be++ = *s++; break; } break; case '{': if (bc > 0) bc++; break; 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 || xc) { rpmMacroBufErr(mb, 1, _("Macro %%%s has unterminated body\n"), n); se = s; /* XXX W2DO? */ goto exit; } /* Trim trailing blanks/newlines */ while (--be >= b && (c = *be) && (isblank(c) || iseol(c))) {}; *(++be) = '\0'; /* one too far */ } /* Move scan over body */ while (iseol(*s)) s++; se = s; if (!validName(mb, n, ne - n, expandbody ? "%global": "%define")) goto exit; if ((be - b) < 1) { rpmMacroBufErr(mb, 1, _("Macro %%%s has empty body\n"), n); goto exit; } if (!isblank(*sbody) && !(*sbody == '\\' && iseol(sbody[1]))) rpmMacroBufErr(mb, 0, _("Macro %%%s needs whitespace before body\n"), n); if (expandbody) { int eflags = RPMEXPAND_KEEP_QUOTED; if (expandThis(mb, b, 0, &ebody, &eflags)) { rpmMacroBufErr(mb, 1, _("Macro %%%s failed to expand\n"), n); goto exit; } b = ebody; if (eflags & RPMEXPAND_HAVE_QUOTED) flags |= ME_QUOTED; } pushMacro(mb->mc, n, o, b, level, flags); rc = 0; exit: if (rc) mb->error = 1; _free(buf); _free(ebody); if (parsed) *parsed += se - start; } /** * Parse (and execute) macro undefinition. * @param mb macro expansion state * @param argv macro arguments * @return number of consumed characters */ static void doUndefine(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { const char *n = argv[1]; if (!validName(mb, n, strlen(n), "%undefine")) { mb->error = 1; } else { popMacro(mb->mc, n); } } static void doArgvDefine(rpmMacroBuf mb, ARGV_t argv, int level, int expand, size_t *parsed) { char *args = NULL; const char *se = argv[1]; /* handle the "programmatic" case where macro name is arg1 and body arg2 */ if (argv[2]) se = args = rstrscat(NULL, argv[1], " ", argv[2], NULL); doDefine(mb, se, level, expand, parsed); free(args); } static void doDef(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { doArgvDefine(mb, argv, mb->level, 0, parsed); } static void doGlobal(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { doArgvDefine(mb, argv, RMIL_GLOBAL, 1, parsed); } static void doDump(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { rpmDumpMacroTable(mb->mc, NULL); } static int mbopt(int c, const char *oarg, int oint, void *data) { rpmMacroBuf mb = (rpmMacroBuf)data; char *name = NULL, *body = NULL; /* Define option macros. */ rasprintf(&name, "-%c", c); if (oarg) { rasprintf(&body, "-%c %s", c, oarg); } else { rasprintf(&body, "-%c", c); } pushMacro(mb->mc, name, NULL, body, mb->level, ME_AUTO | ME_LITERAL); free(name); free(body); if (oarg) { rasprintf(&name, "-%c*", c); pushMacro(mb->mc, name, NULL, oarg, mb->level, ME_AUTO | ME_LITERAL); free(name); } return 0; } /** * Setup arguments for parameterized macro. * @todo Use popt rather than getopt to parse args. * @param mb macro expansion state * @param me macro entry slot * @param argv parsed arguments for the macro */ static void setupArgs(rpmMacroBuf mb, const rpmMacroEntry me, ARGV_t argv) { char *args = NULL; int argc; int ind; int c; /* 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 | ME_LITERAL); /* * The macro %* analoguous to the shell's $* means "Pass all non-macro * parameters." Consequently, there needs to be a macro that means "Pass all * (including macro parameters) options". This is useful for verifying * parameters during expansion and yet transparently passing all parameters * through for higher level processing (e.g. %description and/or %setup). * This is the (potential) justification for %{**} ... */ args = unsplitQuoted(argv + 1, " "); pushMacro(mb->mc, "**", NULL, args, mb->level, ME_AUTO | ME_LITERAL | ME_QUOTED); free(args); argc = argvCount(argv); ind = rgetopt(argc, argv, me->opts, mbopt, mb); if (ind < 0) { rpmMacroBufErr(mb, 1, _("Unknown option %c in %s(%s)\n"), -ind, me->name, me->opts); goto exit; } /* Add argument count (remaining non-option items) as macro. */ { char *ac = NULL; rasprintf(&ac, "%d", (argc - ind)); pushMacro(mb->mc, "#", NULL, ac, mb->level, ME_AUTO | ME_LITERAL); free(ac); } /* Add macro for each argument */ if (argc - ind) { for (c = ind; c < argc; c++) { char *name = NULL; rasprintf(&name, "%d", (c - ind + 1)); pushMacro(mb->mc, name, NULL, argv[c], mb->level, ME_AUTO | ME_LITERAL); free(name); } } /* Add concatenated unexpanded arguments as yet another macro. */ args = argvJoin(argv + ind, " "); pushMacro(mb->mc, "*", NULL, args ? args : "", mb->level, ME_AUTO | ME_LITERAL); free(args); exit: mb->me = me; mb->args = argv; } /** * Free parsed arguments for parameterized macro. * @param mb macro expansion state */ static void freeArgs(rpmMacroBuf mb) { rpmMacroContext mc = mb->mc; /* Delete dynamic macro definitions */ auto it = mc->tab.begin(); while (it != mc->tab.end()) { auto & stack = it->second; auto & me = stack.top(); if (me.level < mb->level) { ++it; continue; } /* Warn on defined but unused non-automatic, scoped macros */ if (!(me.flags & (ME_AUTO|ME_USED))) { rpmMacroBufErr(mb, 0, _("Macro %%%s defined but not used within scope\n"), me.name); /* Only whine once */ me.flags |= ME_USED; } do { stack.pop(); } while (stack.empty() == false && stack.top().level >= mb->level); if (stack.empty()) it = mc->tab.erase(it); else ++it; } mb->level--; mb->args = NULL; mb->me = NULL; } RPM_GNUC_INTERNAL 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++; } } RPM_GNUC_INTERNAL char *unsplitQuoted(ARGV_const_t av, const char *sep) { const int qchar = 0x1f; /* ASCII unit separator */ size_t len = 0, seplen; char *b, *buf; ARGV_const_t av2; if (!av || !*av) return xstrdup(""); seplen = av[1] ? strlen(sep) : 0; for (av2 = av; *av2; av2++) len += strlen(*av2) + 2 + seplen; b = buf = (char *)xmalloc(len + 1 - seplen); for (av2 = av; *av2; av2++) { *b++ = qchar; strcpy(b, *av2); b += strlen(b); *b++ = qchar; if (av2[1]) { strcpy(b, sep); b += seplen; } } *b = 0; return buf; } /** * Parse arguments (to next new line) for parameterized macro. * @param mb macro expansion state * @param me macro entry slot * @param argvp pointer to argv array to store the result * @param se arguments to parse * @param lastc stop parsing at lastc * @param splitargs split into words * @return address to continue parsing */ static const char * grabArgs(rpmMacroBuf mb, const rpmMacroEntry me, ARGV_t *argvp, const char * se, const char * lastc, int splitargs) { if ((me->flags & ME_PARSE) != 0) { /* Always unsplit and unexpanded */ argvAddN(argvp, se, lastc - se); } else { char *s = NULL; int eflags = mb->flags | (splitargs ? RPMEXPAND_KEEP_QUOTED : 0); /* Expand possible macros in arguments */ expandThis(mb, lastc == se ? "" : se, lastc - se, &s, &eflags); if (splitargs) splitQuoted(argvp, s, " \t"); else argvAdd(argvp, s); free(s); } return (*lastc == '\0') || (*lastc == '\n' && *(lastc-1) != '\\') ? lastc : lastc + 1; } static void doBody(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { if (*argv[1]) { rpmMacroEntry mep = findEntry(mb->mc, argv[1], 0, NULL); if (mep) { rpmMacroBufAppendStr(mb, mep->body); } else { rpmMacroBufErr(mb, 1, _("no such macro: '%s'\n"), argv[1]); } } } static void doOutput(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { int loglevel = RPMLOG_NOTICE; /* assume echo */ if (rstreq("error", me->name)) { loglevel = RPMLOG_ERR; mb->error = 1; } else if (rstreq("warn", me->name)) { loglevel = RPMLOG_WARNING; } rpmlog(loglevel, "%s\n", argv[1]); } static void doLua(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { rpmlua lua = NULL; /* Global state. */ const char *scriptbuf = argv[1]; char *printbuf; rpmMacroContext mc = mb->mc; rpmMacroEntry mbme = mb->me; int odepth = mc->depth; int olevel = mc->level; const char *opts = NULL; const char *name = NULL; ARGV_t args = NULL; if (mbme) { opts = mbme->opts; name = mbme->name; if (mb->args) args = mb->args; } rpmluaPushPrintBuffer(lua); mc->depth = mb->depth; mc->level = mb->level; if (rpmluaRunScript(lua, scriptbuf, name, opts, args) == -1) mb->error = 1; mc->depth = odepth; mc->level = olevel; printbuf = rpmluaPopPrintBuffer(lua); if (printbuf) { rpmMacroBufAppendStr(mb, printbuf); free(printbuf); } } /* * Wrap a call to Lua string functions. * Note extra parentheses to force only one result returned, multi-value * returns such as from string.gsub() make no sense in this context. */ static void doString(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { rpmlua lua = NULL; /* Global state. */ char *printbuf = NULL; char *s = rstrscat(NULL, "return (string.", argv[0], "(table.unpack(arg)))", NULL); rpmluaPushPrintBuffer(lua); if (rpmluaRunScript(lua, s, argv[0], NULL, argv+1) == -1) mb->error = 1; printbuf = rpmluaPopPrintBuffer(lua); if (printbuf) { rpmMacroBufAppendStr(mb, printbuf); free(printbuf); } free(s); } static void doSP(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { char *s = NULL; s = rstrscat(NULL, (*(me->name) == 'S') ? "%SOURCE" : "%PATCH", argv[1], NULL); expandMacro(mb, s, 0); free(s); } static void doUncompress(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { if (*argv[1]) { expandMacro(mb, "%__rpmuncompress ", 0); rpmMacroBufAppendStr(mb, argv[1]); } } static void doExpand(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { if (*argv[1]) expandMacro(mb, argv[1], 0); } static void doVerbose(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { if (parsed || !argv || !argv[1]) { rpmMacroBufAppend(mb, rpmIsVerbose() ? '1' : '0'); } else if (rpmIsVerbose() && *argv[1]) { expandMacro(mb, argv[1], 0); } } static void doShescape(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { int i; for (i = 1 ; argv[i]; i++) { if (i != 1) rpmMacroBufAppend(mb, ' '); rpmMacroBufAppend(mb, '\''); for (const char *s = argv[i]; *s != '\0'; s++) { if (*s == '\'') { rpmMacroBufAppendStr(mb, "'\\''"); } else { rpmMacroBufAppend(mb, *s); } } rpmMacroBufAppend(mb, '\''); } } static uint64_t getmem_total(void) { uint64_t mem = 0; long int pagesize = sysconf(_SC_PAGESIZE); long int pages = sysconf(_SC_PHYS_PAGES); if (pagesize <= 0) pagesize = 4096; if (pages > 0) { /* Cast needed to force 64bit calculation on 32bit systems */ mem = (uint64_t)pages * pagesize; } return mem; } static uint64_t getmem_proc(int thread) { uint64_t mem = getmem_total(); /* * Conservative estimates for thread use on 32bit systems where address * space is an issue: 2GB for bare metal, 3GB for a 32bit process * on a 64bit system. */ if (thread) { uint64_t vmem = mem; #if __WORDSIZE == 32 vmem = UINT32_MAX / 2; #else #if defined(__linux__) if ((personality(0xffffffff) & PER_MASK) == PER_LINUX32) vmem = (UINT32_MAX / 4) * 3; #endif #endif if (vmem < mem) mem = vmem; } /* Fixup to get nice even numbers */ mem = mem / (1024*1024) + 1; return mem; } static void doGetncpus(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { const char *sizemacro = NULL; const char *arg = (argv && argv[1]) ? argv[1] : "total"; char buf[32]; unsigned int ncpus = getncpus(); uint64_t mem = 0; if (rstreq(arg, "total")) { /* nothing */ } else if (rstreq(arg, "proc")) { mem = getmem_proc(0); sizemacro = "%{?_smp_tasksize_proc}"; } else if (rstreq(arg, "thread")) { mem = getmem_proc(1); sizemacro = "%{?_smp_tasksize_thread}"; } else { rpmMacroBufErr(mb, 1, _("invalid argument: %s\n"), arg); return; } if (sizemacro) { unsigned int mcpus; unsigned long tasksize = rpmExpandNumeric(sizemacro); if (tasksize == 0) tasksize = 512; if (mem == 0) { rpmMacroBufErr(mb, 1, _("failed to get available memory for %s\n"), arg); return; } mcpus = mem / tasksize; if (mcpus < ncpus) ncpus = mcpus; } /* Ensure at least one CPU, no matter how starved */ if (ncpus < 1) ncpus = 1; sprintf(buf, "%u", ncpus); rpmMacroBufAppendStr(mb, buf); } /* * shrink body by removing all leading and trailing whitespaces and * reducing intermediate whitespaces to a single space character. */ static char *doShrink(const char *arg) { char *b, *p, c, last_c = ' '; char *buf = b = p = xstrdup(arg); while ((c = *p++) != 0) { if (risspace(c)) { if (last_c == ' ') continue; c = ' '; } *b++ = last_c = c; } if (b != buf && b[-1] == ' ') b--; *b = 0; return buf; } static void doFoo(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { char *buf = NULL; const char *b = NULL; if (rstreq("basename", me->name)) { buf = xstrdup(argv[1]); b = basename(buf); } else if (rstreq("dirname", me->name)) { buf = xstrdup(argv[1]); b = dirname(buf); } else if (rstreq("shrink", me->name)) { b = buf = doShrink(argv[1]); } else if (rstreq("quote", me->name)) { if (mb->flags & RPMEXPAND_KEEP_QUOTED) { b = buf = unsplitQuoted(argv + 1, " "); if (b && *b) mb->flags |= RPMEXPAND_HAVE_QUOTED; } else { for (argv++; *argv; argv++) { rpmMacroBufAppendStr(mb, *argv); if (argv[1]) rpmMacroBufAppend(mb, ' '); } } } else if (rstreq("suffix", me->name)) { buf = xstrdup(argv[1]); if ((b = strrchr(buf, '.')) != NULL) b++; } else if (rstreq("expr", me->name)) { b = buf = rpmExprStrFlags(argv[1], 0); if (!b) mb->error = 1; } else if (rstreq("url2path", me->name) || rstreq("u2p", me->name)) { buf = xstrdup(argv[1]); (void)urlPath(buf, (const char **)&b); if (*b == '\0') b = "/"; } else if (rstreq("getenv", me->name)) { b = getenv(argv[1]); } else if (rstreq("getconfdir", me->name)) { b = (char *)rpmConfigDir(); } else if (rstreq("exists", me->name)) { buf = xstrdup((access(argv[1], F_OK) == 0) ? "1" : "0"); b = buf; } if (b) { rpmMacroBufAppendStr(mb, b); } free(buf); } static void doLoad(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { if (loadMacroFile(mb->mc, argv[1])) { rpmMacroBufErr(mb, 1, _("failed to load macro file %s\n"), argv[1]); } } static void doTrace(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { mb->expand_trace = mb->macro_trace = mb->depth; if (mb->depth == 1) { print_macro_trace = mb->macro_trace; print_expand_trace = mb->expand_trace; } } static void doRpmver(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed) { rpmMacroBufAppendStr(mb, VERSION); } static struct builtins_s { const char * name; macroFunc func; int nargs; int flags; } const builtinmacros[] = { { "P", doSP, 1, 0 }, { "S", doSP, 1, 0 }, { "basename", doFoo, 1, 0 }, { "define", doDef, 1, ME_PARSE }, { "dirname", doFoo, 1, 0 }, { "dnl", doDnl, 1, ME_PARSE }, { "dump", doDump, 0, 0 }, { "echo", doOutput, 1, 0 }, { "error", doOutput, 1, 0 }, { "exists", doFoo, 1, 0 }, { "expand", doExpand, 1, 0 }, { "expr", doFoo, 1, 0 }, { "getconfdir", doFoo, 0, 0 }, { "getenv", doFoo, 1, 0 }, { "getncpus", doGetncpus, -1, 0 }, { "global", doGlobal, 1, ME_PARSE }, { "gsub", doString, 1, 0 }, { "len", doString, 1, 0 }, { "load", doLoad, 1, 0 }, { "lower", doString, 1, 0 }, { "lua", doLua, 1, ME_PARSE }, { "macrobody", doBody, 1, 0 }, { "quote", doFoo, 1, 0 }, { "rep", doString, 1, 0 }, { "reverse", doString, 1, 0 }, { "rpmversion", doRpmver, 0, 0 }, { "shrink", doFoo, 1, 0 }, { "sub", doString, 1, 0 }, { "suffix", doFoo, 1, 0 }, { "trace", doTrace, 0, 0 }, { "u2p", doFoo, 1, 0 }, { "shescape", doShescape, 1, 0 }, { "uncompress", doUncompress, 1, 0 }, { "undefine", doUndefine, 1, 0 }, { "upper", doString, 1, 0 }, { "url2path", doFoo, 1, 0 }, { "verbose", doVerbose, -1, ME_PARSE }, { "warn", doOutput, 1, 0 }, { NULL, NULL, 0 } }; static const char *setNegateAndCheck(const char *str, int *pnegate, int *pchkexist) { *pnegate = 0; *pchkexist = 0; while ((*str == '?') || (*str == '!')) { if (*str == '!') *pnegate = !*pnegate; else (*pchkexist)++; str++; } return 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 */ str++; while ((c = *str) && (risalnum(c) || c == '_')) str++; if (*str == '*' && str[1] == '*') /* %** */ str += 2; else if (*str == '*' || *str == '#') /* %* and %# */ str++; return str; } /** * Expand a single macro entry * @param mb macro expansion state * @param me macro entry slot * @param args arguments for parametric macros * @param parsed how many characters ME_PARSE parsed (or NULL) */ static void doMacro(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t args, size_t *parsed) { rpmMacroEntry prevme = mb->me; ARGV_t prevarg = mb->args; size_t old_tpos = mb->buf.size(); /* Recursively expand body of macro */ if (me->flags & ME_FUNC) { int nargs = args && args[0] ? (argvCount(args) - 1) : 0; int needarg = (me->nargs != 0); int havearg = (nargs > 0); if (me->nargs >= 0 && needarg != havearg) { rpmMacroBufErr(mb, 1, "%%%s: %s\n", me->name, needarg ? _("argument expected") : _("unexpected argument")); goto exit; } me->func(mb, me, args, parsed); } else if (me->flags & ME_LITERAL) { if (me->body && *me->body) rpmMacroBufAppendStr(mb, me->body); } else if (me->body && *me->body) { /* Setup args for "%name " macros with opts */ if (args != NULL) setupArgs(mb, me, args); if ((me->flags & ME_QUOTED) && (mb->flags & RPMEXPAND_KEEP_QUOTED) != 0) expandQuotedMacro(mb, me->body); else expandMacro(mb, me->body, 0); /* Free args for "%name " macros with opts */ if (args != NULL) freeArgs(mb); } /* postprocess macros that contain quotes */ if ((me->flags & ME_QUOTED) != 0) { if ((mb->flags & RPMEXPAND_KEEP_QUOTED) == 0) mbUnquote(mb, old_tpos); else mb->flags |= RPMEXPAND_HAVE_QUOTED; } exit: mb->args = prevarg; mb->me = prevme; } /** * Expand a string that contains quote characters * @param mb macro expansion state * @param src string to expand * @return 0 on success, 1 on failure */ static int expandQuotedMacro(rpmMacroBuf mb, const char *src) { const int qchar = 0x1f; /* ASCII unit separator */ const char *p, *sp; char *source = xstrdup(src); for (sp = p = source; *p; p++) { if (*p == qchar) { if (p > sp) expandMacro(mb, sp, p - sp); rpmMacroBufAppend(mb, qchar); mb->flags ^= RPMEXPAND_KEEP_QUOTED; sp = p + 1; } } if (p > sp) expandMacro(mb, sp, p - sp); mb->flags |= RPMEXPAND_KEEP_QUOTED; free(source); return mb->error; } /** * 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 expandMacro(rpmMacroBuf mb, const char *src, size_t slen) { rpmMacroEntry me = NULL; const char *s = src, *se; const char *f, *fe; const char *g, *ge; size_t fn, gn; int c; int negate; const char * lastc; int chkexist; char *source = NULL; MacroExpansionData med; /* * Always make a (terminated) copy of the source string. * Beware: avoiding the copy when src is known \0-terminated seems like * an obvious opportunity for optimization, but doing that breaks * a special case of macro undefining itself. */ if (!slen) slen = strlen(src); source = rstrndup(src, slen); s = source; if (mbInit(mb, &med, slen) != 0) goto exit; while (mb->error == 0 && (c = *s) != '\0') { s++; /* Copy text until next macro */ switch (c) { case '%': if (*s) { /* Ensure not end-of-string. */ if (*s != '%') break; s++; /* skip first % in %% */ } default: rpmMacroBufAppend(mb, c); continue; break; } /* Expand next macro */ f = fe = NULL; g = ge = NULL; if (mb->depth > 1) /* XXX full expansion for outermost level */ med.tpos = mb->buf.size(); /* save expansion pointer for printExpand */ lastc = NULL; if ((se = findMacroEnd(s)) == NULL) { rpmMacroBufErr(mb, 1, _("Unterminated %c: %s\n"), (char)*s, s); continue; } switch (*s) { default: /* %name substitution */ f = s = setNegateAndCheck(s, &negate, &chkexist); 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 (mb->macro_trace) printMacro(mb, s, se); s++; /* skip ( */ 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); 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 % */ rpmMacroBufAppend(mb, c); #if 0 rpmMacroBufErr(mb, 1, _("A %% is followed by an unparseable macro\n")); #endif continue; } if (mb->macro_trace) printMacro(mb, s, se); /* Expand defined macros */ me = findEntry(mb->mc, f, fn, 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 and existance test */ if (*f == '-' || chkexist) { if ((me == NULL && !negate) || /* Without existance, skip %{?...} */ (me != NULL && negate)) { /* With existance, skip %{!?...} */ s = se; continue; } if (g && g < ge) { /* Expand X in %{...:X} */ expandMacro(mb, g, gn); } else if (me) { doMacro(mb, me, NULL, NULL); } 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 % */ rpmMacroBufAppend(mb, c); continue; } if (me->opts == NULL && !(me->flags & ME_FUNC)) { if (g || (lastc && *lastc == '}')) { /* XXX TODO: Make this an error in a few years */ rpmMacroBufErr(mb, 0, _("unexpected argument to non-parametric macro %%%s\n"), me->name); } /* Simple non-parametric macro */ doMacro(mb, me, NULL, NULL); s = se; continue; } if (me->opts == NULL && fe == se) lastc = NULL; /* do not parse arguments coming after %foo */ ARGV_t args = NULL; argvAdd(&args, me->name); if ((me->flags & ME_PARSE) != 0 && fe == se) { /* Special free-field macros define/global/dnl */ size_t fwd = 0; argvAdd(&args, se); doMacro(mb, me, args, &fwd); se += fwd; } else { /* Grab args for parametric macros */ if (g) se = grabArgs(mb, me, &args, g, ge, 0); else if (lastc) se = grabArgs(mb, me, &args, fe, lastc, 1); doMacro(mb, me, args, NULL); } argvFree(args); s = se; } mbFini(mb, me, &med); exit: _free(source); return mb->error; } /** * Expand a single macro * @param mb macro expansion state * @param me macro entry slot * @param args arguments for parametric macros * @param flags expandsion flags * @return 0 on success, 1 on failure */ static int expandThisMacro(rpmMacroBuf mb, rpmMacroEntry me, ARGV_const_t args, int flags) { MacroExpansionData med; ARGV_t optargs = NULL; if (mbInit(mb, &med, 0) != 0) goto exit; if (mb->macro_trace) { ARGV_const_t av = args; fprintf(stderr, "%3d>%*s (%%%s)", mb->depth, (2 * mb->depth + 1), "", me->name); for (av = args; av && *av; av++) fprintf(stderr, " %s", *av); fprintf(stderr, "\n"); } /* prepare arguments for parametric macros */ if (me->opts) { argvAdd(&optargs, me->name); if ((flags & RPMEXPAND_EXPAND_ARGS) != 0) { ARGV_const_t av = args; for (av = args; av && *av; av++) { char *s = NULL; expandThis(mb, *av, 0, &s, NULL); argvAdd(&optargs, s); free(s); } } else { argvAppend(&optargs, args); } } doMacro(mb, me, optargs, NULL); if (optargs) argvFree(optargs); mbFini(mb, me, &med); exit: return mb->error; } /* =============================================================== */ static int doExpandMacros(rpmMacroContext mc, const char *src, int flags, char **target) { rpmMacroBuf mb = mbCreate(mc, flags); int rc = 0; rc = expandMacro(mb, src, 0); *target = xstrdup(mb->buf.c_str()); delete mb; return rc; } static void pushMacroAny(rpmMacroContext mc, const char * n, const char * o, const char * b, macroFunc f, void *priv, int nargs, int level, int flags) { auto res = mc->tab.insert({n, {}}); auto & entry = res.first; auto & stack = entry->second; /* push an empty entry to the stack and fillup */ stack.push({}); auto & me = stack.top(); /* name is the map key */ me.name = entry->first.c_str(); /* copy body and opts */ me.sbody = b; me.body = me.sbody.c_str(); if (o) { me.sopts = o; me.opts = me.sopts.c_str(); } /* initialize */ me.func = f; me.priv = priv; me.nargs = nargs; me.flags = flags; me.flags &= ~(ME_USED); me.level = level; } static void pushMacro(rpmMacroContext mc, const char * n, const char * o, const char * b, int level, int flags) { return pushMacroAny(mc, n, o, b, NULL, NULL, 0, level, flags); } /* Return pointer to the _previous_ macro definition (or NULL) */ static void popMacro(rpmMacroContext mc, const char * n) { auto const & entry = mc->tab.find(n); if (entry == mc->tab.end()) return; auto & stack = entry->second; stack.pop(); if (stack.empty()) mc->tab.erase(entry); } static int defineMacro(rpmMacroContext mc, const char * macro, int level) { rpmMacroBuf mb = new rpmMacroBuf_s {}; int rc; size_t parsed = 0; /* XXX just enough to get by */ mb->mc = mc; doDefine(mb, macro, level, 0, &parsed); rc = mb->error; delete mb; return rc; } static void linenoMacro(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t margs, size_t *parsed) { int *lineno = (int *)rpmMacroEntryPriv(me); if (lineno) { char lnobuf[16]; snprintf(lnobuf, sizeof(lnobuf), "%d", *lineno); rpmMacroBufAppendStr(mb, lnobuf); } } static int loadMacroFile(rpmMacroContext mc, const char * fn) { FILE *fd = fopen(fn, "r"); size_t blen = MACROBUFSIZ; std::vector buf(blen); int rc = -1; int nfailed = 0; int lineno = 0; int nlines = 0; if (fd == NULL) goto exit; pushMacro(mc, "__file_name", NULL, fn, RMIL_MACROFILES, ME_LITERAL); pushMacroAny(mc, "__file_lineno", NULL, "", linenoMacro, &lineno, 0, RMIL_MACROFILES, ME_FUNC); buf[0] = '\0'; while ((nlines = rdcl(buf.data(), blen, fd)) > 0) { char c, *n; lineno += nlines; n = buf.data(); SKIPBLANK(n, c); if (c != '%') continue; n++; /* skip % */ if (defineMacro(mc, n, RMIL_MACROFILES)) nfailed++; } fclose(fd); popMacro(mc, "__file_name"); popMacro(mc, "__file_lineno"); rc = (nfailed > 0); exit: return rc; } static void copyMacros(rpmMacroContext src, rpmMacroContext dst, int level) { for (auto const & entry : src->tab) { auto const & me = entry.second.top(); 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); wrlock lock(mc->mutex); rc = doExpandMacros(mc, sbuf, flags, &target); if (rc) { free(target); return -1; } else { *obuf = target; return 1; } } int rpmExpandThisMacro(rpmMacroContext mc, const char *n, ARGV_const_t args, char ** obuf, int flags) { rpmMacroEntry mep; char *target = NULL; int rc = 1; /* assume failure */ mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); mep = findEntry(mc, n, 0, NULL); if (mep) { rpmMacroBuf mb = mbCreate(mc, flags); rc = expandThisMacro(mb, mep, args, flags); target = xstrdup(mb->buf.c_str()); delete mb; } if (rc) { free(target); return -1; } else { *obuf = target; return 1; } } void rpmDumpMacroTable(rpmMacroContext mc, FILE * fp) { mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); if (fp == NULL) fp = stderr; fprintf(fp, "========================\n"); for (auto & entry : mc->tab) { auto const & me = entry.second.top(); 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 %zu empty %d\n"), mc->tab.size(), 0); } int rpmPushMacroFlags(rpmMacroContext mc, const char * n, const char * o, const char * b, int level, rpmMacroFlags flags) { mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); pushMacro(mc, n, o, b, level, flags & RPMMACRO_LITERAL ? ME_LITERAL : ME_NONE); return 0; } int rpmPushMacro(rpmMacroContext mc, const char * n, const char * o, const char * b, int level) { return rpmPushMacroFlags(mc, n, o, b, level, RPMMACRO_DEFAULT); } int rpmPushMacroAux(rpmMacroContext mc, const char * n, const char * o, macroFunc f, void *priv, int nargs, int level, rpmMacroFlags flags) { mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); pushMacroAny(mc, n, nargs ? "" : NULL, "", f, priv, nargs, level, flags|ME_FUNC); return 0; } int rpmPopMacro(rpmMacroContext mc, const char * n) { mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); popMacro(mc, n); return 0; } int rpmDefineMacro(rpmMacroContext mc, const char * macro, int level) { int rc; mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); rc = defineMacro(mc, macro, level); return rc; } int rpmMacroIsDefined(rpmMacroContext mc, const char *n) { int defined = 0; if ((mc = rpmmctxAcquire(mc)) != NULL) { wrlock lock(mc->mutex); if (findEntry(mc, n, 0, NULL)) defined = 1; } return defined; } int rpmMacroIsParametric(rpmMacroContext mc, const char *n) { int parametric = 0; if ((mc = rpmmctxAcquire(mc)) != NULL) { wrlock lock(mc->mutex); rpmMacroEntry mep = findEntry(mc, n, 0, NULL); if (mep && mep->opts) parametric = 1; } return parametric; } void *rpmMacroEntryPriv(rpmMacroEntry me) { return (me != NULL) ? me->priv : NULL; } void rpmLoadMacros(rpmMacroContext mc, int level) { rpmMacroContext gmc; if (mc == NULL || mc == rpmGlobalMacroContext) return; gmc = rpmmctxAcquire(NULL); mc = rpmmctxAcquire(mc); wrlock glock(gmc->mutex); wrlock lock(mc->mutex); copyMacros(mc, gmc, level); } int rpmLoadMacroFile(rpmMacroContext mc, const char * fn) { int rc; mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); rc = loadMacroFile(mc, fn); return rc; } void rpmInitMacros(rpmMacroContext mc, const char * macrofiles) { ARGV_t pattern, globs = NULL; rpmMacroContext climc; mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); /* Define built-in macros */ for (const struct builtins_s *b = builtinmacros; b->name; b++) { pushMacroAny(mc, b->name, b->nargs ? "" : NULL, "", b->func, NULL, b->nargs, RMIL_BUILTIN, b->flags | ME_FUNC); } argvSplit(&globs, macrofiles, ":"); for (pattern = globs; pattern && *pattern; pattern++) { ARGV_t path, files = NULL; /* Glob expand the macro file path element, expanding ~ to $HOME. */ if (rpmGlob(*pattern, NULL, &files) != 0) { continue; } /* Read macros from each file. */ for (path = files; *path; path++) { if (rpmFileHasSuffix(*path, ".rpmnew") || rpmFileHasSuffix(*path, ".rpmsave") || rpmFileHasSuffix(*path, ".rpmorig")) { continue; } (void) loadMacroFile(mc, *path); } argvFree(files); } argvFree(globs); /* Reload cmdline macros */ climc = rpmmctxAcquire(rpmCLIMacroContext); wrlock clilock(climc->mutex); copyMacros(climc, mc, RMIL_CMDLINE); } void rpmFreeMacros(rpmMacroContext mc) { mc = rpmmctxAcquire(mc); wrlock lock(mc->mutex); freeMacros(mc); } char * rpmExpand(const char *arg, ...) { if (arg == NULL) return xstrdup(""); std::string buf; char *ret = NULL; va_list ap; va_start(ap, arg); for (const char *s = arg; s != NULL; s = va_arg(ap, const char *)) buf += s; va_end(ap); rpmMacroContext mc = rpmmctxAcquire(NULL); wrlock lock(mc->mutex); (void) doExpandMacros(mc, buf.c_str(), 0, &ret); return ret; } int rpmExpandNumeric(const char *arg) { char *val; int rc; if (arg == NULL) return 0; val = rpmExpand(arg, NULL); if (!(val && *val != '%')) rc = 0; else if (*val == 'Y' || *val == 'y') rc = 1; else if (*val == 'N' || *val == 'n') rc = 0; else { char *end; rc = strtol(val, &end, 0); if (!(end && *end == '\0')) rc = 0; } free(val); return rc; }