summaryrefslogtreecommitdiff
path: root/lib/headerfmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/headerfmt.c')
-rw-r--r--lib/headerfmt.c868
1 files changed, 868 insertions, 0 deletions
diff --git a/lib/headerfmt.c b/lib/headerfmt.c
new file mode 100644
index 0000000..49c7047
--- /dev/null
+++ b/lib/headerfmt.c
@@ -0,0 +1,868 @@
+/** \ingroup header
+ * \file lib/headerfmt.c
+ */
+
+#include "system.h"
+
+#include <rpm/header.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmpgp.h>
+#include "lib/misc.h" /* format function protos */
+
+#include "debug.h"
+
+#define PARSER_BEGIN 0
+#define PARSER_IN_ARRAY 1
+#define PARSER_IN_EXPR 2
+
+/** \ingroup header
+ */
+typedef struct sprintfTag_s * sprintfTag;
+struct sprintfTag_s {
+ headerTagFormatFunction fmt;
+ rpmTagVal tag;
+ int justOne;
+ char * format;
+ char * type;
+};
+
+typedef enum {
+ PTOK_NONE = 0,
+ PTOK_TAG,
+ PTOK_ARRAY,
+ PTOK_STRING,
+ PTOK_COND
+} ptokType;
+
+/** \ingroup header
+ */
+typedef struct sprintfToken_s * sprintfToken;
+struct sprintfToken_s {
+ ptokType type;
+ union {
+ struct sprintfTag_s tag; /*!< PTOK_TAG */
+ struct {
+ sprintfToken format;
+ int i;
+ int numTokens;
+ } array; /*!< PTOK_ARRAY */
+ struct {
+ char * string;
+ int len;
+ } string; /*!< PTOK_STRING */
+ struct {
+ sprintfToken ifFormat;
+ int numIfTokens;
+ sprintfToken elseFormat;
+ int numElseTokens;
+ struct sprintfTag_s tag;
+ } cond; /*!< PTOK_COND */
+ } u;
+};
+
+#define HASHTYPE tagCache
+#define HTKEYTYPE rpmTagVal
+#define HTDATATYPE rpmtd
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+/**
+ */
+typedef struct headerSprintfArgs_s {
+ Header h;
+ char * fmt;
+ const char * errmsg;
+ tagCache cache;
+ sprintfToken format;
+ HeaderIterator hi;
+ char * val;
+ size_t vallen;
+ size_t alloced;
+ int numTokens;
+ int i;
+ headerGetFlags hgflags;
+} * headerSprintfArgs;
+
+
+static char escapedChar(const char ch)
+{
+ switch (ch) {
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ default: return ch;
+ }
+}
+
+/**
+ * Destroy headerSprintf format array.
+ * @param format sprintf format array
+ * @param num number of elements
+ * @return NULL always
+ */
+static sprintfToken
+freeFormat( sprintfToken format, int num)
+{
+ int i;
+
+ if (format == NULL) return NULL;
+
+ for (i = 0; i < num; i++) {
+ switch (format[i].type) {
+ case PTOK_ARRAY:
+ format[i].u.array.format =
+ freeFormat(format[i].u.array.format,
+ format[i].u.array.numTokens);
+ break;
+ case PTOK_COND:
+ format[i].u.cond.ifFormat =
+ freeFormat(format[i].u.cond.ifFormat,
+ format[i].u.cond.numIfTokens);
+ format[i].u.cond.elseFormat =
+ freeFormat(format[i].u.cond.elseFormat,
+ format[i].u.cond.numElseTokens);
+ break;
+ case PTOK_NONE:
+ case PTOK_TAG:
+ case PTOK_STRING:
+ default:
+ break;
+ }
+ }
+ format = _free(format);
+ return NULL;
+}
+
+/**
+ * Initialize an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaInit(headerSprintfArgs hsa)
+{
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ hsa->i = 0;
+ if (tag != NULL && tag->tag == -2)
+ hsa->hi = headerInitIterator(hsa->h);
+ /* Normally with bells and whistles enabled, but raw dump on iteration. */
+ hsa->hgflags = (hsa->hi == NULL) ? HEADERGET_EXT : HEADERGET_RAW;
+}
+
+/**
+ * Return next hsa iteration item.
+ * @param hsa headerSprintf args
+ * @return next sprintfToken (or NULL)
+ */
+static sprintfToken hsaNext(headerSprintfArgs hsa)
+{
+ sprintfToken fmt = NULL;
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ if (hsa->i >= 0 && hsa->i < hsa->numTokens) {
+ fmt = hsa->format + hsa->i;
+ if (hsa->hi == NULL) {
+ hsa->i++;
+ } else {
+ tag->tag = headerNextTag(hsa->hi);
+ if (tag->tag == RPMTAG_NOT_FOUND)
+ fmt = NULL;
+ }
+ }
+
+ return fmt;
+}
+
+/**
+ * Finish an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaFini(headerSprintfArgs hsa)
+{
+ hsa->hi = headerFreeIterator(hsa->hi);
+ hsa->i = 0;
+}
+
+/**
+ * Reserve sufficient buffer space for next output value.
+ * @param hsa headerSprintf args
+ * @param need no. of bytes to reserve
+ * @return pointer to reserved space
+ */
+static char * hsaReserve(headerSprintfArgs hsa, size_t need)
+{
+ if ((hsa->vallen + need) >= hsa->alloced) {
+ if (hsa->alloced <= need)
+ hsa->alloced += need;
+ hsa->alloced <<= 1;
+ hsa->val = xrealloc(hsa->val, hsa->alloced+1);
+ }
+ return hsa->val + hsa->vallen;
+}
+
+/**
+ * Search tags for a name.
+ * @param hsa headerSprintf args
+ * @param token parsed fields
+ * @param name name to find
+ * @return 0 on success, 1 on not found
+ */
+static int findTag(headerSprintfArgs hsa, sprintfToken token, const char * name)
+{
+ const char *tagname = name;
+ sprintfTag stag = (token->type == PTOK_COND
+ ? &token->u.cond.tag : &token->u.tag);
+
+ stag->fmt = NULL;
+ stag->tag = RPMTAG_NOT_FOUND;
+
+ if (rstreq(tagname, "*")) {
+ stag->tag = -2;
+ goto bingo;
+ }
+
+ if (rstreqn("RPMTAG_", tagname, sizeof("RPMTAG_")-1)) {
+ tagname += sizeof("RPMTAG");
+ }
+
+ /* Search tag names. */
+ stag->tag = rpmTagGetValue(tagname);
+ if (stag->tag != RPMTAG_NOT_FOUND)
+ goto bingo;
+
+ return 1;
+
+bingo:
+ /* Search extensions for specific format. */
+ if (stag->type != NULL)
+ stag->fmt = rpmHeaderFormatFuncByName(stag->type);
+
+ return stag->fmt ? 0 : 1;
+}
+
+/* forward ref */
+/**
+ * Parse an expression.
+ * @param hsa headerSprintf args
+ * @param token token
+ * @param str string
+ * @param[out] *endPtr
+ * @return 0 on success
+ */
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str,char ** endPtr);
+
+/**
+ * Parse a headerSprintf term.
+ * @param hsa headerSprintf args
+ * @param str
+ * @retval *formatPtr
+ * @retval *numTokensPtr
+ * @retval *endPtr
+ * @param state
+ * @return 0 on success
+ */
+static int parseFormat(headerSprintfArgs hsa, char * str,
+ sprintfToken * formatPtr,int * numTokensPtr,
+ char ** endPtr, int state)
+{
+ char * chptr, * start, * next, * dst;
+ sprintfToken format;
+ sprintfToken token;
+ int numTokens;
+ int done = 0;
+
+ /* upper limit on number of individual formats */
+ numTokens = 0;
+ if (str != NULL)
+ for (chptr = str; *chptr != '\0'; chptr++)
+ if (*chptr == '%' || *chptr == '[') numTokens++;
+ numTokens = numTokens * 2 + 1;
+
+ format = xcalloc(numTokens, sizeof(*format));
+ if (endPtr) *endPtr = NULL;
+
+ dst = start = str;
+ numTokens = 0;
+ token = NULL;
+ if (start != NULL)
+ while (*start != '\0') {
+ switch (*start) {
+ case '%':
+ /* handle %% */
+ if (*(start + 1) == '%') {
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+ start++;
+ *dst++ = *start++;
+ break;
+ }
+
+ token = format + numTokens++;
+ *dst++ = '\0';
+ start++;
+
+ if (*start == '|') {
+ char * newEnd;
+
+ start++;
+ if (parseExpression(hsa, token, start, &newEnd)) {
+ goto errxit;
+ }
+ start = newEnd;
+ break;
+ }
+
+ token->u.tag.format = start;
+ token->u.tag.justOne = 0;
+
+ chptr = start;
+ while (*chptr && *chptr != '{' && *chptr != '%') chptr++;
+ if (!*chptr || *chptr == '%') {
+ hsa->errmsg = _("missing { after %");
+ goto errxit;
+ }
+
+ *chptr++ = '\0';
+
+ while (start < chptr) {
+ start++;
+ }
+
+ if (*start == '=') {
+ token->u.tag.justOne = 1;
+ start++;
+ } else if (*start == '#') {
+ token->u.tag.justOne = 1;
+ token->u.tag.type = "arraysize";
+ start++;
+ }
+
+ dst = next = start;
+ while (*next && *next != '}') next++;
+ if (!*next) {
+ hsa->errmsg = _("missing } after %{");
+ goto errxit;
+ }
+ *next++ = '\0';
+
+ chptr = start;
+ while (*chptr && *chptr != ':') chptr++;
+
+ if (*chptr != '\0') {
+ *chptr++ = '\0';
+ if (!*chptr) {
+ hsa->errmsg = _("empty tag format");
+ goto errxit;
+ }
+ token->u.tag.type = chptr;
+ }
+ /* default to string conversion if no formats found by now */
+ if (!token->u.tag.type) {
+ token->u.tag.type = "string";
+ }
+
+ if (!*start) {
+ hsa->errmsg = _("empty tag name");
+ goto errxit;
+ }
+
+ token->type = PTOK_TAG;
+
+ if (findTag(hsa, token, start)) {
+ hsa->errmsg = _("unknown tag");
+ goto errxit;
+ }
+
+ start = next;
+ break;
+
+ case '[':
+ *dst++ = '\0';
+ *start++ = '\0';
+ token = format + numTokens++;
+
+ if (parseFormat(hsa, start,
+ &token->u.array.format,
+ &token->u.array.numTokens,
+ &start, PARSER_IN_ARRAY)) {
+ goto errxit;
+ }
+
+ if (!start) {
+ hsa->errmsg = _("] expected at end of array");
+ goto errxit;
+ }
+
+ dst = start;
+
+ token->type = PTOK_ARRAY;
+
+ break;
+
+ case ']':
+ if (state != PARSER_IN_ARRAY) {
+ hsa->errmsg = _("unexpected ]");
+ goto errxit;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ case '}':
+ if (state != PARSER_IN_EXPR) {
+ hsa->errmsg = _("unexpected }");
+ goto errxit;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ default:
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+
+ if (*start == '\\') {
+ start++;
+ *dst++ = escapedChar(*start++);
+ } else {
+ *dst++ = *start++;
+ }
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ if (dst != NULL)
+ *dst = '\0';
+
+ for (int i = 0; i < numTokens; i++) {
+ token = format + i;
+ if (token->type == PTOK_STRING)
+ token->u.string.len = strlen(token->u.string.string);
+ }
+
+ *numTokensPtr = numTokens;
+ *formatPtr = format;
+ return 0;
+
+errxit:
+ freeFormat(format, numTokens);
+ return 1;
+}
+
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str, char ** endPtr)
+{
+ char * chptr;
+ char * end;
+
+ hsa->errmsg = NULL;
+ chptr = str;
+ while (*chptr && *chptr != '?') chptr++;
+
+ if (*chptr != '?') {
+ hsa->errmsg = _("? expected in expression");
+ return 1;
+ }
+
+ *chptr++ = '\0';;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after ? in expression");
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.ifFormat,
+ &token->u.cond.numIfTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{%}:{NAME}|\n'"*/
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != ':' && *chptr != '|') {
+ hsa->errmsg = _(": expected following ? subexpression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ if (*chptr == '|') {
+ if (parseFormat(hsa, NULL, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ {
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+ } else {
+ chptr++;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after : in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{a}:{%}|{NAME}\n'" */
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != '|') {
+ hsa->errmsg = _("| expected at end of expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ token->u.cond.elseFormat =
+ freeFormat(token->u.cond.elseFormat, token->u.cond.numElseTokens);
+ return 1;
+ }
+ }
+
+ chptr++;
+
+ *endPtr = chptr;
+
+ token->type = PTOK_COND;
+
+ (void) findTag(hsa, token, str);
+
+ return 0;
+}
+
+static rpmtd getCached(tagCache cache, rpmTagVal tag)
+{
+ rpmtd *res = NULL;
+ return tagCacheGetEntry(cache, tag, &res, NULL, NULL) ? res[0] : NULL;
+}
+
+/**
+ * Do headerGet() just once for given tag, cache results.
+ * @param hsa headerSprintf args
+ * @param tag
+ * @retval *typeptr
+ * @retval *data
+ * @retval *countptr
+ * @return 1 on success, 0 on failure
+ */
+static rpmtd getData(headerSprintfArgs hsa, rpmTagVal tag)
+{
+ rpmtd td = NULL;
+
+ if (!(td = getCached(hsa->cache, tag))) {
+ td = rpmtdNew();
+ if (!headerGet(hsa->h, tag, td, hsa->hgflags)) {
+ rpmtdFree(td);
+ return NULL;
+ }
+ tagCacheAddEntry(hsa->cache, tag, td);
+ }
+
+ return td;
+}
+
+/**
+ * formatValue
+ * @param hsa headerSprintf args
+ * @param tag
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * formatValue(headerSprintfArgs hsa, sprintfTag tag, int element)
+{
+ char * val = NULL;
+ size_t need = 0;
+ char * t, * te;
+ char buf[20];
+ rpmtd td;
+
+ memset(buf, 0, sizeof(buf));
+ if ((td = getData(hsa, tag->tag))) {
+ td->ix = element; /* Ick, use iterators instead */
+ stpcpy(stpcpy(buf, "%"), tag->format);
+ val = tag->fmt(td, buf);
+ } else {
+ stpcpy(buf, "%s");
+ val = xstrdup("(none)");
+ }
+
+ need = strlen(val);
+
+ if (val && need > 0) {
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, val);
+ hsa->vallen += (te - t);
+ }
+ free(val);
+
+ return (hsa->val + hsa->vallen);
+}
+
+/**
+ * Format a single headerSprintf item.
+ * @param hsa headerSprintf args
+ * @param token
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * singleSprintf(headerSprintfArgs hsa, sprintfToken token,
+ int element)
+{
+ char * t, * te;
+ int i, j, found;
+ rpm_count_t count, numElements;
+ sprintfToken spft;
+ int condNumFormats;
+ size_t need;
+
+ /* we assume the token and header have been validated already! */
+
+ switch (token->type) {
+ case PTOK_NONE:
+ break;
+
+ case PTOK_STRING:
+ need = token->u.string.len;
+ if (need == 0) break;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, token->u.string.string);
+ hsa->vallen += (te - t);
+ break;
+
+ case PTOK_TAG:
+ t = hsa->val + hsa->vallen;
+ te = formatValue(hsa, &token->u.tag,
+ (token->u.tag.justOne ? 0 : element));
+ if (te == NULL)
+ return NULL;
+ break;
+
+ case PTOK_COND:
+ if (getData(hsa, token->u.cond.tag.tag) ||
+ headerIsEntry(hsa->h, token->u.cond.tag.tag)) {
+ spft = token->u.cond.ifFormat;
+ condNumFormats = token->u.cond.numIfTokens;
+ } else {
+ spft = token->u.cond.elseFormat;
+ condNumFormats = token->u.cond.numElseTokens;
+ }
+
+ need = condNumFormats * 20;
+ if (spft == NULL || need == 0) break;
+
+ t = hsaReserve(hsa, need);
+ for (i = 0; i < condNumFormats; i++, spft++) {
+ te = singleSprintf(hsa, spft, element);
+ if (te == NULL)
+ return NULL;
+ }
+ break;
+
+ case PTOK_ARRAY:
+ numElements = 0;
+ found = 0;
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++)
+ {
+ rpmtd td = NULL;
+ if (spft->type != PTOK_TAG ||
+ spft->u.tag.justOne) continue;
+
+ if (!(td = getData(hsa, spft->u.tag.tag))) {
+ continue;
+ }
+
+ found = 1;
+ count = rpmtdCount(td);
+
+ if (numElements > 1 && count != numElements)
+ switch (td->type) {
+ default:
+ hsa->errmsg =
+ _("array iterator used with different sized arrays");
+ return NULL;
+ break;
+ case RPM_BIN_TYPE:
+ case RPM_STRING_TYPE:
+ break;
+ }
+ if (count > numElements)
+ numElements = count;
+ }
+
+ if (found) {
+ int isxml;
+
+ need = numElements * token->u.array.numTokens * 10;
+ if (need == 0) break;
+
+ spft = token->u.array.format;
+ isxml = (spft->type == PTOK_TAG && spft->u.tag.type != NULL &&
+ rstreq(spft->u.tag.type, "xml"));
+
+ if (isxml) {
+ const char * tagN = rpmTagGetName(spft->u.tag.tag);
+
+ need = sizeof(" <rpmTag name=\"\">\n") - 1;
+ if (tagN != NULL)
+ need += strlen(tagN);
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " <rpmTag name=\"");
+ if (tagN != NULL)
+ te = stpcpy(te, tagN);
+ te = stpcpy(te, "\">\n");
+ hsa->vallen += (te - t);
+ }
+
+ t = hsaReserve(hsa, need);
+ for (j = 0; j < numElements; j++) {
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++) {
+ te = singleSprintf(hsa, spft, j);
+ if (te == NULL)
+ return NULL;
+ }
+ }
+
+ if (isxml) {
+ need = sizeof(" </rpmTag>\n") - 1;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " </rpmTag>\n");
+ hsa->vallen += (te - t);
+ }
+
+ }
+ break;
+ }
+
+ return (hsa->val + hsa->vallen);
+}
+
+static int tagCmp(rpmTagVal a, rpmTagVal b)
+{
+ return (a != b);
+}
+
+static unsigned int tagId(rpmTagVal tag)
+{
+ return tag;
+}
+
+static rpmtd tagFree(rpmtd td)
+{
+ rpmtdFreeData(td);
+ rpmtdFree(td);
+ return NULL;
+}
+
+char * headerFormat(Header h, const char * fmt, errmsg_t * errmsg)
+{
+ struct headerSprintfArgs_s hsa;
+ sprintfToken nextfmt;
+ sprintfTag tag;
+ char * t, * te;
+ int isxml;
+ size_t need;
+
+ memset(&hsa, 0, sizeof(hsa));
+ hsa.h = headerLink(h);
+ hsa.fmt = xstrdup(fmt);
+ hsa.errmsg = NULL;
+
+ if (parseFormat(&hsa, hsa.fmt, &hsa.format, &hsa.numTokens, NULL, PARSER_BEGIN))
+ goto exit;
+
+ hsa.cache = tagCacheCreate(128, tagId, tagCmp, NULL, tagFree);
+ hsa.val = xstrdup("");
+
+ tag =
+ (hsa.format->type == PTOK_TAG
+ ? &hsa.format->u.tag :
+ (hsa.format->type == PTOK_ARRAY
+ ? &hsa.format->u.array.format->u.tag :
+ NULL));
+ isxml = (tag != NULL && tag->tag == -2 && tag->type != NULL && rstreq(tag->type, "xml"));
+
+ if (isxml) {
+ need = sizeof("<rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "<rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ hsaInit(&hsa);
+ while ((nextfmt = hsaNext(&hsa)) != NULL) {
+ te = singleSprintf(&hsa, nextfmt, 0);
+ if (te == NULL) {
+ hsa.val = _free(hsa.val);
+ break;
+ }
+ }
+ hsaFini(&hsa);
+
+ if (isxml) {
+ need = sizeof("</rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "</rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ if (hsa.val != NULL && hsa.vallen < hsa.alloced)
+ hsa.val = xrealloc(hsa.val, hsa.vallen+1);
+
+ hsa.cache = tagCacheFree(hsa.cache);
+ hsa.format = freeFormat(hsa.format, hsa.numTokens);
+
+exit:
+ if (errmsg)
+ *errmsg = hsa.errmsg;
+ hsa.h = headerFree(hsa.h);
+ hsa.fmt = _free(hsa.fmt);
+ return hsa.val;
+}
+