rpm/tools/rpminject.c

541 lines
12 KiB
C

#include "system.h"
const char *__progname;
#include <err.h> /* XXX !HAVE_ERR_H: get from misc */
#include <rpm/rpmbuild.h>
#include <rpm/header.h>
#include "lib/rpmlead.h"
#include "build/buildio.h"
#include "debug.h"
typedef enum injmode_e { INJ_UNKNOWN, INJ_ADD, INJ_DELETE, INJ_MODIFY } injmode_t;
injmode_t injmode = INJ_UNKNOWN;
typedef struct cmd_s {
injmode_t injmode;
char * tag;
int32_t tagval;
int done;
int oldcnt;
int nvals;
char ** vals;
} cmd_t;
#define MAXCMDS 40
cmd_t *cmds[MAXCMDS];
int ncmds = 0;
static const char * pr_injmode(injmode_t injmode)
{
switch(injmode) {
case INJ_ADD: return("add");
case INJ_DELETE: return("delete");
case INJ_MODIFY: return("modify");
case INJ_UNKNOWN: return("unknown");
default: return("???");
}
}
enum cvtaction {CA_OLD, CA_NEW, CA_OMIT, CA_ERR};
static enum cvtaction convertAMD(enum cvtaction ca, rpmTagType type,
void ** nvalsp, rpm_count_t *ncountp, cmd_t *newc)
{
int i;
if (newc == NULL)
return ca;
if (!(nvalsp && ncountp))
return CA_ERR;
*nvalsp = NULL;
*ncountp = 0;
switch (ca) {
case CA_OLD:
case CA_OMIT:
case CA_ERR:
default:
break;
case CA_NEW:
switch (type) {
case RPM_INT32_TYPE:
{ int32_t *intp = xmalloc(newc->nvals * sizeof(*intp));
for (i = 0; i < newc->nvals; i++) {
long ival;
char *end;
end = NULL;
ival = strtol(newc->vals[i], &end, 0);
if (end && *end)
break;
if ((((unsigned long)ival) >> (8*sizeof(*intp))) != 0)
break;
intp[i] = ival;
}
if (i < newc->nvals) {
ca = CA_ERR;
free(intp);
break;
}
*nvalsp = intp;
*ncountp = newc->nvals;
} break;
case RPM_BIN_TYPE: /* icons & signatures */
case RPM_STRING_TYPE:
if (newc->nvals != 1) {
newc->done = 0;
ca = CA_ERR;
break;
}
*nvalsp = xstrdup(newc->vals[0]);
*ncountp = newc->nvals;
break;
case RPM_STRING_ARRAY_TYPE:
{ const char **av = xmalloc((newc->nvals+1) * sizeof(char *));
for (i = 0; i < newc->nvals; i++) {
av[i] = newc->vals[i];
}
av[newc->nvals] = NULL;
*nvalsp = av;
*ncountp = newc->nvals;
} break;
case RPM_NULL_TYPE:
case RPM_CHAR_TYPE:
case RPM_INT8_TYPE: /* arch & os */
case RPM_INT16_TYPE: /* file modes & rdevs */
case RPM_I18NSTRING_TYPE:
default: /* this conversion cannot be performed (yet) */
newc->done = 0;
ca = CA_ERR;
break;
}
break;
}
return ca;
}
static enum cvtaction convertExistingAMD(rpmTag tag, rpmTagType type,
rpm_data_t valsp, rpm_count_t *countp, void ** nvalsp, rpm_count_t *ncountp,
cmd_t *cmds[], int ncmds)
{
cmd_t *newc = NULL;
enum cvtaction ca = CA_OLD;
int i;
if (!((tag >= RPMTAG_NAME && tag < RPMTAG_FIRSTFREE_TAG)
|| tag >= RPMTAG_EXTERNAL_TAG))
return ca;
for (i = 0; i < ncmds; i++) {
cmd_t *c;
c = cmds[i];
if (tag != c->tagval)
continue;
if (c->done)
continue;
switch (c->injmode) {
case INJ_ADD:
if (ca != CA_OMIT) {/* old tag was deleted, now adding again */
c->done = -1;
continue;
}
ca = CA_NEW;
newc = c;
c->done = 1;
break;
case INJ_MODIFY: /* XXX for now, this is delete, then add */
if (ca == CA_OMIT) {/* old tag was deleted, can't modify */
c->done = -1;
continue;
}
ca = CA_NEW;
newc = c;
c->done = 1;
break;
case INJ_DELETE:
if (ca == CA_OMIT) {/* old tag was deleted, now deleting again */
c->done = -1;
continue;
}
ca = CA_OMIT;
newc = c;
c->done = 1;
break;
case INJ_UNKNOWN:
default:
c->done = -1;
break;
}
}
if (newc) {
ca = convertAMD(ca, type, nvalsp, ncountp, newc);
switch (ca) {
case CA_OMIT:
case CA_NEW:
newc->oldcnt = *countp;
break;
case CA_OLD:
case CA_ERR:
break;
}
}
return ca;
}
static
Header headerCopyWithConvert(Header h, cmd_t *cmds[], int ncmds)
{
HeaderIterator headerIter;
Header res = headerNew();
struct rpmtd_s td;
headerIter = headerInitIterator(h);
while (headerNext(headerIter, &td)) {
enum cvtaction ca;
struct rpmtd_s ntd = { .type = td.type, .tag = td.tag,
.count = 0, .data = NULL
};
ca = convertExistingAMD(td.tag, td.type, &td.data, &td.count,
&ntd.data, &ntd.count, cmds, ncmds);
switch (ca) {
case CA_ERR:
case CA_OLD: /* copy old tag and values to header */
default:
/* Don't copy the old changelog, we'll do that later. */
switch (td.tag) {
case RPMTAG_CHANGELOGTIME:
case RPMTAG_CHANGELOGNAME:
case RPMTAG_CHANGELOGTEXT:
break;
default:
headerPut(res, &td, HEADERPUT_DEFAULT);
break;
}
break;
case CA_NEW: /* copy new tag and values to header */
headerPut(res, &ntd, HEADERPUT_DEFAULT);
break;
case CA_OMIT: /* delete old tag and values from header */
break;
}
rpmtdFreeData(&td);
if (ntd.data)
free(ntd.data);
}
headerFreeIterator(headerIter);
return res;
}
static char * genChangelog(cmd_t *cmds[], int ncmds)
{
#define MYBUFSIZ (2*BUFSIZ)
char *b, *buf = xmalloc(MYBUFSIZ);
int i;
b = buf;
for (i = 0; i < ncmds; i++) {
cmd_t *c;
if ((c = cmds[i]) == NULL)
continue;
b += sprintf(b, "- %s tag %s(%d)",
pr_injmode(c->injmode), c->tag, c->tagval);
if (c->oldcnt || c->nvals) {
*b++ = '\t';
*b++ = '(';
if (c->oldcnt)
b += sprintf(b, "oldcnt %d", c->oldcnt);
if (c->oldcnt && c->nvals) {
*b++ = ',';
*b++ = ' ';
}
if (c->nvals)
b += sprintf(b, "nvals %d", c->nvals);
*b++ = ')';
}
*b++ = '\n';
}
*b = '\0';
return buf;
}
static int
headerInject(Header *hdrp, cmd_t *cmds[], int ncmds)
{
Header h;
int ec = 0;
int i;
if (!(hdrp && cmds && ncmds > 0))
return -1;
h = headerCopyWithConvert(*hdrp, cmds, ncmds);
for (i = 0; i < ncmds; i++) {
cmd_t *c;
int rc;
if ((c = cmds[i]) == NULL)
continue;
rc = headerIsEntry(h, c->tagval);
if (!rc && !c->done && c->injmode != INJ_DELETE) {
struct rpmtd_s td;
enum cvtaction ca;
td.type = (c->nvals > 0) ? RPM_STRING_ARRAY_TYPE : RPM_STRING_TYPE;
td.tag = c->tagval;
ca = convertAMD(CA_NEW, td.type, &td.data, &td.count, c);
if (ca == CA_NEW)
headerPut(h, &td, HEADERPUT_DEFAULT);
rc = headerIsEntry(h, c->tagval);
}
switch(c->injmode) {
case INJ_ADD:
if (!(rc && c->done > 0)) {
warnx("failed to add tag %s", rpmTagGetName(c->tagval));
ec = 1;
}
break;
case INJ_DELETE:
if (!(!rc && c->done > 0)) {
warnx("failed to delete tag %s", rpmTagGetName(c->tagval));
ec = 1;
}
break;
case INJ_MODIFY:
if (!(rc && c->done > 0)) {
warnx("failed to modify tag %s", rpmTagGetName(c->tagval));
ec = 1;
}
break;
case INJ_UNKNOWN:
default:
ec = 1;
break;
}
/* XXX possibly need strict mode to exit immediately here */
}
if (ec == 0 && *hdrp) {
static char name[512] = "";
static const char *text = NULL;
static rpmTag cltags[] = {
RPMTAG_CHANGELOGTIME,
RPMTAG_CHANGELOGNAME,
RPMTAG_CHANGELOGTEXT,
0
};
if (name[0] == '\0')
sprintf(name, "rpminject <%s@%s>", getUname(getuid()), buildHost());
if (text == NULL)
text = genChangelog(cmds, ncmds);
addChangelogEntry(h, *getBuildTime(), name, text);
headerCopyTags(*hdrp, h, cltags);
headerSort(h);
*hdrp = headerFree(*hdrp);
*hdrp = h;
} else {
h = headerFree(h);
}
return ec;
}
/* ========================================================================= */
static int
rewriteRPM(const char *fni, const char *fno, cmd_t *cmds[], int ncmds)
{
Header sigs;
rpmSpec spec;
struct cpioSourceArchive_s csabuf, *csa = &csabuf;
int rc;
csa->cpioArchiveSize = 0;
csa->cpioFdIn = fdNew("init (rewriteRPM)");
csa->cpioList = NULL;
/* Read rpm and (partially) recreate spec/pkg control structures */
if ((rc = readRPM(fni, &spec, &sigs, csa)) != 0)
return rc;
/* Inject new strings into header tags */
if ((rc = headerInject(&spec->packages->header, cmds, ncmds)) != 0)
goto exit;
/* Rewrite the rpm */
if (headerIsSource(spec->packages->header)) {
rc = writeRPM(&spec->packages->header, NULL, fno,
csa, spec->passPhrase, &(spec->cookie));
} else {
rc = writeRPM(&spec->packages->header, NULL, fno,
csa, spec->passPhrase, NULL);
}
exit:
Fclose(csa->cpioFdIn);
return rc;
}
/* ========================================================================= */
static int
do_inject(cmd_t *cmds[], int ncmds, const char *argv[])
{
const char *arg;
int ec = 0;
if (argv == NULL || *argv == NULL) {
/* XXX generate lead/header to stdout */
return 0;
}
while ((arg = *argv++) != NULL) {
char *fni = xmalloc(strlen(arg) + sizeof("-SAVE"));
const char *fno = arg;
strcpy(fni, arg);
strcat(fni, "-SAVE");
unlink(fni);
if (link(fno, fni)) {
warn("can't link temp input file %s", fni);
ec++;
continue;
}
if (rewriteRPM(fni, fno, cmds, ncmds)) {
unlink(fno);
if (rename(fni, fno))
warn("can't rename %s to %s", fni, fno);
ec++;
}
if (fni) free(fni);
}
return ec;
}
static struct poptOption optionsTable[] = {
{ "add", 'a', 0, 0, 'a', NULL, NULL },
{ "del", 'd', 0, 0, 'd', NULL, NULL },
{ "injtags", 'i', 0, 0, 'i', NULL, NULL },
{ "modify", 'm', 0, 0, 'm', NULL, NULL },
{ "tag", 't', POPT_ARG_STRING, 0, 't', NULL, NULL },
{ "value", 'v', POPT_ARG_STRING, 0, 'v', NULL, NULL },
{ NULL, 0, 0, 0, 0, NULL, NULL }
};
int
main(int argc, char *argv[])
{
poptContext optCon;
const char * optArg;
cmd_t *c = NULL;
int arg;
int ec = 0;
injmode_t lastmode = INJ_UNKNOWN;
#if HAVE_MCHECK_H && HAVE_MTRACE
mtrace(); /* Trace malloc only if MALLOC_TRACE=mtrace-output-file. */
#endif
setprogname(argv[0]); /* Retrofit glibc __progname */
#if defined(ENABLE_NLS)
(void)setlocale(LC_ALL, "" );
(void)bindtextdomain(PACKAGE, LOCALEDIR);
(void)textdomain(PACKAGE);
#endif
optCon = poptGetContext("rpminject", argc, (const char **) argv, optionsTable, 0);
#if RPM_USES_POPTREADDEFAULTCONFIG
poptReadDefaultConfig(optCon, 1);
#endif
while ((arg = poptGetNextOpt(optCon)) > 0) {
optArg = poptGetOptArg(optCon);
switch (arg) {
case 'a':
injmode = INJ_ADD;
break;
case 'd':
injmode = INJ_DELETE;
break;
case 'm':
injmode = INJ_MODIFY;
break;
case 't':
if (ncmds == 0 || c == NULL)
errx(EXIT_FAILURE, "missing inject mode before \"--tag %s\"", optArg);
if (c->tag) {
if (c->injmode != INJ_DELETE &&
(c->nvals <= 0 || c->vals == NULL))
errx(EXIT_FAILURE, "add/modify inject mode with \"--tag %s\" needs a value", c->tag);
cmds[ncmds] = c = xcalloc(1, sizeof(cmd_t));
cmds[ncmds]->injmode = cmds[ncmds-1]->injmode;
ncmds++;
}
c->tagval = rpmTagGetValue(optArg);
if (c->tagval == RPMTAG_NOT_FOUND)
errx(EXIT_FAILURE, "unknown rpm tag \"--tag %s\"", optArg);
c->tag = xstrdup(optArg);
break;
case 'v':
if (ncmds == 0 || c == NULL)
errx(EXIT_FAILURE, "missing inject mode before \"--value %s\"", optArg);
if (c->tag == NULL)
errx(EXIT_FAILURE, "missing tag name before \"--value %s\"", optArg);
if (c->nvals == 0 || c->vals == NULL) {
c->vals = xcalloc(2, sizeof(char *));
} else {
c->vals = xrealloc(c->vals,
(c->nvals+2)*sizeof(char *));
}
c->vals[c->nvals++] = xstrdup(optArg);
c->vals[c->nvals] = NULL;
break;
case 'i':
rpmDisplayQueryTags(stdout);
exit(EXIT_SUCCESS);
break;
default:
errx(EXIT_FAILURE, "unknown popt return (%d)", arg);
break;
}
if (injmode != lastmode) {
cmds[ncmds] = c = xcalloc(1, sizeof(cmd_t));
cmds[ncmds]->injmode = lastmode = injmode;
ncmds++;
}
}
/* XXX I don't want to read rpmrc */
addMacro(NULL, "_tmppath", NULL, "/tmp", RMIL_DEFAULT);
ec = do_inject(cmds, ncmds, poptGetArgs(optCon));
optCon = poptFreeContext(optCon);
return ec;
}