Eliminate newOpenFileInfo() from librpmbuild API
[platform/upstream/rpm.git] / tools / rpminject.c
1 #include "system.h"
2 const char *__progname;
3
4 #include <err.h>
5
6 #include <rpm/rpmbuild.h>
7 #include <rpm/header.h>
8
9 #include "lib/rpmlead.h"
10 #include "build/buildio.h"
11
12 #include "debug.h"
13
14 typedef enum injmode_e { INJ_UNKNOWN, INJ_ADD, INJ_DELETE, INJ_MODIFY } injmode_t;
15
16 injmode_t injmode = INJ_UNKNOWN;
17
18 typedef struct cmd_s {
19     injmode_t   injmode;
20     char *      tag;
21     int32_t     tagval;
22     int         done;
23     int         oldcnt;
24     int         nvals;
25     char **     vals;
26 } cmd_t;
27
28 #define MAXCMDS 40
29 cmd_t *cmds[MAXCMDS];
30 int ncmds = 0;
31
32 static const char * pr_injmode(injmode_t injmode)
33 {
34     switch(injmode) {
35     case INJ_ADD:       return("add");
36     case INJ_DELETE:    return("delete");
37     case INJ_MODIFY:    return("modify");
38     case INJ_UNKNOWN:   return("unknown");
39     default:            return("???");
40     }
41 }
42
43 enum cvtaction {CA_OLD, CA_NEW, CA_OMIT, CA_ERR};
44
45 static enum cvtaction convertAMD(enum cvtaction ca, rpmTagType type,
46         void ** nvalsp, rpm_count_t *ncountp, cmd_t *newc)
47 {
48     int i;
49
50     if (newc == NULL)
51         return ca;
52     if (!(nvalsp && ncountp))
53         return CA_ERR;
54
55     *nvalsp = NULL;
56     *ncountp = 0;
57
58     switch (ca) {
59     case CA_OLD:
60     case CA_OMIT:
61     case CA_ERR:
62     default:
63         break;
64     case CA_NEW:
65         switch (type) {
66         case RPM_INT32_TYPE:
67         {   int32_t *intp = xmalloc(newc->nvals * sizeof(*intp));
68             for (i = 0; i < newc->nvals; i++) {
69                 long ival;
70                 char *end;
71                 end = NULL;
72                 ival = strtol(newc->vals[i], &end, 0);
73                 if (end && *end)
74                     break;
75                 if ((((unsigned long)ival) >> (8*sizeof(*intp))) != 0)
76                     break;
77                 intp[i] = ival;
78             }
79             if (i < newc->nvals) {
80                 ca = CA_ERR;
81                 free(intp);
82                 break;
83             }
84             *nvalsp = intp;
85             *ncountp = newc->nvals;
86         }   break;
87         case RPM_BIN_TYPE:      /* icons & signatures */
88         case RPM_STRING_TYPE:
89             if (newc->nvals != 1) {
90                 newc->done = 0;
91                 ca = CA_ERR;
92                 break;
93             }
94             *nvalsp = xstrdup(newc->vals[0]);
95             *ncountp = newc->nvals;
96             break;
97         case RPM_STRING_ARRAY_TYPE:
98         {   const char **av = xmalloc((newc->nvals+1) * sizeof(char *));
99             for (i = 0; i < newc->nvals; i++) {
100                 av[i] = newc->vals[i];
101             }
102             av[newc->nvals] = NULL;
103             *nvalsp = av;
104             *ncountp = newc->nvals;
105         }   break;
106         case RPM_NULL_TYPE:
107         case RPM_CHAR_TYPE:
108         case RPM_INT8_TYPE:     /* arch & os */
109         case RPM_INT16_TYPE:    /* file modes & rdevs */
110         case RPM_I18NSTRING_TYPE:
111         default:        /* this conversion cannot be performed (yet) */
112             newc->done = 0;
113             ca = CA_ERR;
114             break;
115         }
116         break;
117     }
118
119     return ca;
120 }
121
122 static enum cvtaction convertExistingAMD(rpmTag tag, rpmTagType type,
123         rpm_data_t valsp, rpm_count_t *countp, void ** nvalsp, rpm_count_t *ncountp,
124         cmd_t *cmds[], int ncmds)
125 {
126     cmd_t *newc = NULL;
127     enum cvtaction ca = CA_OLD;
128     int i;
129
130     if (!((tag >= RPMTAG_NAME && tag < RPMTAG_FIRSTFREE_TAG)
131         || tag >= RPMTAG_EXTERNAL_TAG))
132         return ca;
133
134     for (i = 0; i < ncmds; i++) {
135         cmd_t *c;
136         c = cmds[i];
137
138         if (tag != c->tagval)
139             continue;
140         if (c->done)
141             continue;
142
143         switch (c->injmode) {
144         case INJ_ADD:
145             if (ca != CA_OMIT) {/* old tag was deleted, now adding again */
146                 c->done = -1;
147                 continue;
148             }
149             ca = CA_NEW;
150             newc = c;
151             c->done = 1;
152             break;
153         case INJ_MODIFY:        /* XXX for now, this is delete, then add */
154             if (ca == CA_OMIT) {/* old tag was deleted, can't modify */
155                 c->done = -1;
156                 continue;
157             }
158             ca = CA_NEW;
159             newc = c;
160             c->done = 1;
161             break;
162         case INJ_DELETE:
163             if (ca == CA_OMIT)  {/* old tag was deleted, now deleting again */
164                 c->done = -1;
165                 continue;
166             }
167             ca = CA_OMIT;
168             newc = c;
169             c->done = 1;
170             break;
171         case INJ_UNKNOWN:
172         default:
173             c->done = -1;
174             break;
175         }
176     }
177
178     if (newc) {
179         ca = convertAMD(ca, type, nvalsp, ncountp, newc);
180         switch (ca) {
181         case CA_OMIT:
182         case CA_NEW:
183             newc->oldcnt = *countp;
184             break;
185         case CA_OLD:
186         case CA_ERR:
187             break;
188         }
189     }
190     return ca;
191 }
192
193 static
194 Header headerCopyWithConvert(Header h, cmd_t *cmds[], int ncmds)
195 {
196     HeaderIterator headerIter;
197     Header res = headerNew();
198     struct rpmtd_s td;
199    
200     headerIter = headerInitIterator(h);
201
202     while (headerNext(headerIter, &td)) {
203         enum cvtaction ca;
204         struct rpmtd_s ntd = { .type = td.type, .tag = td.tag, 
205                                .count = 0, .data = NULL
206         };
207
208         ca = convertExistingAMD(td.tag, td.type, &td.data, &td.count, 
209                                 &ntd.data, &ntd.count, cmds, ncmds);
210         switch (ca) {
211         case CA_ERR:
212         case CA_OLD:            /* copy old tag and values to header */ 
213         default:
214             /* Don't copy the old changelog, we'll do that later. */
215             switch (td.tag) {
216             case RPMTAG_CHANGELOGTIME:
217             case RPMTAG_CHANGELOGNAME:
218             case RPMTAG_CHANGELOGTEXT:
219                 break;
220             default:
221                 headerPut(res, &td, HEADERPUT_DEFAULT);
222                 break;
223             }
224             break;
225         case CA_NEW:            /* copy new tag and values to header */ 
226             headerPut(res, &ntd, HEADERPUT_DEFAULT);
227             break;
228         case CA_OMIT:           /* delete old tag and values from header */
229             break;
230         }
231
232         rpmtdFreeData(&td);
233         if (ntd.data)
234             free(ntd.data);
235     }
236
237     headerFreeIterator(headerIter);
238
239     return res;
240 }
241
242 static char * genChangelog(cmd_t *cmds[], int ncmds)
243 {
244 #define MYBUFSIZ (2*BUFSIZ)
245     char *b, *buf = xmalloc(MYBUFSIZ);
246     int i;
247
248     b = buf;
249     for (i = 0; i < ncmds; i++) {
250         cmd_t *c;
251
252         if ((c = cmds[i]) == NULL)
253             continue;
254
255         b += sprintf(b, "- %s tag %s(%d)",
256                 pr_injmode(c->injmode), c->tag, c->tagval);
257
258         if (c->oldcnt || c->nvals) {
259             *b++ = '\t';
260             *b++ = '(';
261             if (c->oldcnt)
262                 b += sprintf(b, "oldcnt %d", c->oldcnt);
263             if (c->oldcnt && c->nvals) {
264                 *b++ = ',';
265                 *b++ = ' ';
266             }
267             if (c->nvals)
268                 b += sprintf(b, "nvals %d", c->nvals);
269             *b++ = ')';
270         }
271         *b++ = '\n';
272     }
273     *b = '\0';
274
275     return buf;
276 }
277
278 static int
279 headerInject(Header *hdrp, cmd_t *cmds[], int ncmds)
280 {
281     Header h;
282     int ec = 0;
283     int i;
284
285     if (!(hdrp && cmds && ncmds > 0))
286         return -1;
287
288     h = headerCopyWithConvert(*hdrp, cmds, ncmds);
289     for (i = 0; i < ncmds; i++) {
290         cmd_t *c;
291         int rc;
292
293         if ((c = cmds[i]) == NULL)
294             continue;
295
296         rc = headerIsEntry(h, c->tagval);
297         if (!rc && !c->done && c->injmode != INJ_DELETE) {
298             struct rpmtd_s td;
299             enum cvtaction ca;
300
301             td.type = (c->nvals > 0) ? RPM_STRING_ARRAY_TYPE : RPM_STRING_TYPE;
302             td.tag = c->tagval;
303             ca = convertAMD(CA_NEW, td.type, &td.data, &td.count, c);
304             if (ca == CA_NEW)
305                 headerPut(h, &td, HEADERPUT_DEFAULT);
306             rc = headerIsEntry(h, c->tagval);
307         }
308
309         switch(c->injmode) {
310         case INJ_ADD:
311             if (!(rc && c->done > 0)) {
312                 warnx("failed to add tag %s", rpmTagGetName(c->tagval));
313                 ec = 1;
314             }
315             break;
316         case INJ_DELETE:
317             if (!(!rc && c->done > 0)) {
318                 warnx("failed to delete tag %s", rpmTagGetName(c->tagval));
319                 ec = 1;
320             }
321             break;
322         case INJ_MODIFY:
323             if (!(rc && c->done > 0)) {
324                 warnx("failed to modify tag %s", rpmTagGetName(c->tagval));
325                 ec = 1;
326             }
327             break;
328         case INJ_UNKNOWN:
329         default:
330             ec = 1;
331             break;
332         }
333
334         /* XXX possibly need strict mode to exit immediately here */
335     }
336
337     if (ec == 0 && *hdrp) {
338         static char name[512] = "";
339         static const char *text = NULL;
340         static rpmTag cltags[] = {
341             RPMTAG_CHANGELOGTIME,
342             RPMTAG_CHANGELOGNAME,
343             RPMTAG_CHANGELOGTEXT,
344             0
345         };
346
347         if (name[0] == '\0')
348             sprintf(name, "rpminject <%s@%s>", getUname(getuid()), buildHost());
349         if (text == NULL)
350             text = genChangelog(cmds, ncmds);
351         
352         addChangelogEntry(h, *getBuildTime(), name, text);
353         headerCopyTags(*hdrp, h, cltags);
354         headerSort(h);
355         *hdrp = headerFree(*hdrp);
356         *hdrp = h;
357     } else {
358         h = headerFree(h);
359     }
360
361     return ec;
362 }
363
364 /* ========================================================================= */
365
366 static int
367 rewriteRPM(const char *fni, const char *fno, cmd_t *cmds[], int ncmds)
368 {
369     Header sigs;
370     rpmSpec spec;
371     struct cpioSourceArchive_s csabuf, *csa = &csabuf;
372     int rc;
373
374     csa->cpioArchiveSize = 0;
375     csa->cpioFdIn = fdNew();
376     csa->cpioList = NULL;
377
378     /* Read rpm and (partially) recreate spec/pkg control structures */
379     if ((rc = readRPM(fni, &spec, &sigs, csa)) != 0)
380         return rc;
381
382     /* Inject new strings into header tags */
383     if ((rc = headerInject(&spec->packages->header, cmds, ncmds)) != 0)
384         goto exit;
385
386     /* Rewrite the rpm */
387     if (headerIsSource(spec->packages->header)) {
388         rc = writeRPM(&spec->packages->header, NULL, fno, csa, &(spec->cookie));
389     } else {
390         rc = writeRPM(&spec->packages->header, NULL, fno, csa, NULL);
391     }
392
393 exit:
394     Fclose(csa->cpioFdIn);
395     return rc;
396
397 }
398
399 /* ========================================================================= */
400
401 static int
402 do_inject(cmd_t *cmds[], int ncmds, const char *argv[])
403 {
404     const char *arg;
405     int ec = 0;
406
407     if (argv == NULL || *argv == NULL) {
408         /* XXX generate lead/header to stdout */
409         return 0;
410     }
411
412     while ((arg = *argv++) != NULL) {
413         char *fni = xmalloc(strlen(arg) + sizeof("-SAVE"));
414         const char *fno = arg;
415
416         strcpy(fni, arg);
417         strcat(fni, "-SAVE");
418         unlink(fni);
419         if (link(fno, fni)) {
420             warn("can't link temp input file %s", fni);
421             ec++;
422             continue;
423         }
424         if (rewriteRPM(fni, fno, cmds, ncmds)) {
425             unlink(fno);
426             if (rename(fni, fno))
427                 warn("can't rename %s to %s", fni, fno);
428             ec++;
429         }
430         if (fni) free(fni);
431     }
432
433     return ec;
434 }
435
436 static struct poptOption optionsTable[] = {
437  { "add",       'a', 0, 0, 'a',                 NULL, NULL },
438  { "del",       'd', 0, 0, 'd',                 NULL, NULL },
439  { "injtags",   'i', 0, 0, 'i',                 NULL, NULL },
440  { "modify",    'm', 0, 0, 'm',                 NULL, NULL },
441  { "tag",       't', POPT_ARG_STRING, 0, 't',   NULL, NULL },
442  { "value",     'v', POPT_ARG_STRING, 0, 'v',   NULL, NULL },
443  { NULL,        0, 0, 0, 0,                     NULL, NULL }
444 };
445
446 int
447 main(int argc, char *argv[])
448 {
449     poptContext optCon;
450     const char * optArg;
451     cmd_t *c = NULL;
452     int arg;
453     int ec = 0;
454     injmode_t lastmode = INJ_UNKNOWN;
455
456     setprogname(argv[0]);       /* Retrofit glibc __progname */
457 #if defined(ENABLE_NLS)
458     (void)setlocale(LC_ALL, "" );
459
460     (void)bindtextdomain(PACKAGE, LOCALEDIR);
461     (void)textdomain(PACKAGE);
462 #endif
463
464     optCon = poptGetContext("rpminject", argc, (const char **) argv, optionsTable, 0);
465 #if RPM_USES_POPTREADDEFAULTCONFIG
466     poptReadDefaultConfig(optCon, 1);
467 #endif
468
469     while ((arg = poptGetNextOpt(optCon)) > 0) {
470         optArg = poptGetOptArg(optCon);
471         switch (arg) {
472         case 'a':
473             injmode = INJ_ADD;
474             break;
475         case 'd':
476             injmode = INJ_DELETE;
477             break;
478         case 'm':
479             injmode = INJ_MODIFY;
480             break;
481         case 't':
482             if (ncmds == 0 || c == NULL)
483                 errx(EXIT_FAILURE, "missing inject mode before \"--tag %s\"", optArg);
484             if (c->tag) {
485                 if (c->injmode != INJ_DELETE &&
486                   (c->nvals <= 0 || c->vals == NULL))
487                     errx(EXIT_FAILURE, "add/modify inject mode with \"--tag %s\" needs a value", c->tag);
488                 cmds[ncmds] = c = xcalloc(1, sizeof(cmd_t));
489                 cmds[ncmds]->injmode = cmds[ncmds-1]->injmode;
490                 ncmds++;
491             }
492             c->tagval = rpmTagGetValue(optArg);
493             if (c->tagval == RPMTAG_NOT_FOUND)  
494                 errx(EXIT_FAILURE, "unknown rpm tag \"--tag %s\"", optArg);
495             c->tag = xstrdup(optArg);
496             break;
497         case 'v':
498             if (ncmds == 0 || c == NULL)
499                 errx(EXIT_FAILURE, "missing inject mode before \"--value %s\"", optArg);
500             if (c->tag == NULL)
501                 errx(EXIT_FAILURE, "missing tag name before \"--value %s\"", optArg);
502             if (c->nvals == 0 || c->vals == NULL) {
503                 c->vals = xcalloc(2, sizeof(char *));
504             } else {
505                 c->vals = xrealloc(c->vals,
506                                 (c->nvals+2)*sizeof(char *));
507             }
508             c->vals[c->nvals++] = xstrdup(optArg);
509             c->vals[c->nvals] = NULL;
510             break;
511         case 'i':
512             rpmDisplayQueryTags(stdout);
513             exit(EXIT_SUCCESS);
514             break;
515         default:
516             errx(EXIT_FAILURE, "unknown popt return (%d)", arg);
517             break;
518         }
519
520         if (injmode != lastmode) {
521             cmds[ncmds] = c = xcalloc(1, sizeof(cmd_t));
522             cmds[ncmds]->injmode = lastmode = injmode;
523             ncmds++;
524         }
525     }
526
527     /* XXX I don't want to read rpmrc */
528     addMacro(NULL, "_tmppath", NULL, "/tmp", RMIL_DEFAULT);
529
530     ec = do_inject(cmds, ncmds, poptGetArgs(optCon));
531
532     optCon = poptFreeContext(optCon);
533     return ec;
534 }