archival/*: move "kbuild:" snippets into .c files
[platform/upstream/busybox.git] / archival / rpm.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini rpm applet for busybox
4  *
5  * Copyright (C) 2001,2002 by Laurence Anderson
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9
10 //kbuild:lib-$(CONFIG_RPM) += rpm.o
11
12 //usage:#define rpm_trivial_usage
13 //usage:       "-i PACKAGE.rpm; rpm -qp[ildc] PACKAGE.rpm"
14 //usage:#define rpm_full_usage "\n\n"
15 //usage:       "Manipulate RPM packages\n"
16 //usage:     "\nCommands:"
17 //usage:     "\n        -i      Install package"
18 //usage:     "\n        -qp     Query package"
19 //usage:     "\n        -qpi    Show information"
20 //usage:     "\n        -qpl    List contents"
21 //usage:     "\n        -qpd    List documents"
22 //usage:     "\n        -qpc    List config files"
23
24 #include "libbb.h"
25 #include "bb_archive.h"
26 #include "rpm.h"
27
28 #define RPM_CHAR_TYPE           1
29 #define RPM_INT8_TYPE           2
30 #define RPM_INT16_TYPE          3
31 #define RPM_INT32_TYPE          4
32 /* #define RPM_INT64_TYPE       5   ---- These aren't supported (yet) */
33 #define RPM_STRING_TYPE         6
34 #define RPM_BIN_TYPE            7
35 #define RPM_STRING_ARRAY_TYPE   8
36 #define RPM_I18NSTRING_TYPE     9
37
38 #define TAG_NAME                1000
39 #define TAG_VERSION             1001
40 #define TAG_RELEASE             1002
41 #define TAG_SUMMARY             1004
42 #define TAG_DESCRIPTION         1005
43 #define TAG_BUILDTIME           1006
44 #define TAG_BUILDHOST           1007
45 #define TAG_SIZE                1009
46 #define TAG_VENDOR              1011
47 #define TAG_LICENSE             1014
48 #define TAG_PACKAGER            1015
49 #define TAG_GROUP               1016
50 #define TAG_URL                 1020
51 #define TAG_PREIN               1023
52 #define TAG_POSTIN              1024
53 #define TAG_FILEFLAGS           1037
54 #define TAG_FILEUSERNAME        1039
55 #define TAG_FILEGROUPNAME       1040
56 #define TAG_SOURCERPM           1044
57 #define TAG_PREINPROG           1085
58 #define TAG_POSTINPROG          1086
59 #define TAG_PREFIXS             1098
60 #define TAG_DIRINDEXES          1116
61 #define TAG_BASENAMES           1117
62 #define TAG_DIRNAMES            1118
63
64 #define RPMFILE_CONFIG          (1 << 0)
65 #define RPMFILE_DOC             (1 << 1)
66
67 enum rpm_functions_e {
68         rpm_query = 1,
69         rpm_install = 2,
70         rpm_query_info = 4,
71         rpm_query_package = 8,
72         rpm_query_list = 16,
73         rpm_query_list_doc = 32,
74         rpm_query_list_config = 64
75 };
76
77 typedef struct {
78         uint32_t tag; /* 4 byte tag */
79         uint32_t type; /* 4 byte type */
80         uint32_t offset; /* 4 byte offset */
81         uint32_t count; /* 4 byte count */
82 } rpm_index;
83
84 struct globals {
85         void *map;
86         rpm_index **mytags;
87         int tagcount;
88 } FIX_ALIASING;
89 #define G (*(struct globals*)&bb_common_bufsiz1)
90 #define INIT_G() do { } while (0)
91
92 static void extract_cpio(int fd, const char *source_rpm)
93 {
94         archive_handle_t *archive_handle;
95
96         if (source_rpm != NULL) {
97                 /* Binary rpm (it was built from some SRPM), install to root */
98                 xchdir("/");
99         } /* else: SRPM, install to current dir */
100
101         /* Initialize */
102         archive_handle = init_handle();
103         archive_handle->seek = seek_by_read;
104         archive_handle->action_data = data_extract_all;
105 #if 0 /* For testing (rpm -i only lists the files in internal cpio): */
106         archive_handle->action_header = header_list;
107         archive_handle->action_data = data_skip;
108 #endif
109         archive_handle->ah_flags = ARCHIVE_RESTORE_DATE | ARCHIVE_CREATE_LEADING_DIRS
110                 /* compat: overwrite existing files.
111                  * try "rpm -i foo.src.rpm" few times in a row -
112                  * standard rpm will not complain.
113                  */
114                 | ARCHIVE_REPLACE_VIA_RENAME;
115         archive_handle->src_fd = fd;
116         /*archive_handle->offset = 0; - init_handle() did it */
117
118         setup_unzip_on_fd(archive_handle->src_fd, /*fail_if_not_detected:*/ 1);
119         while (get_header_cpio(archive_handle) == EXIT_SUCCESS)
120                 continue;
121 }
122
123 static rpm_index **rpm_gettags(int fd, int *num_tags)
124 {
125         /* We should never need more than 200 (shrink via realloc later) */
126         rpm_index **tags = xzalloc(200 * sizeof(tags[0]));
127         int pass, tagindex = 0;
128
129         xlseek(fd, 96, SEEK_CUR); /* Seek past the unused lead */
130
131         /* 1st pass is the signature headers, 2nd is the main stuff */
132         for (pass = 0; pass < 2; pass++) {
133                 struct rpm_header header;
134                 rpm_index *tmpindex;
135                 int storepos;
136
137                 xread(fd, &header, sizeof(header));
138                 if (header.magic_and_ver != htonl(RPM_HEADER_MAGICnVER))
139                         return NULL; /* Invalid magic, or not version 1 */
140                 header.size = ntohl(header.size);
141                 header.entries = ntohl(header.entries);
142                 storepos = xlseek(fd, 0, SEEK_CUR) + header.entries * 16;
143
144                 while (header.entries--) {
145                         tmpindex = tags[tagindex++] = xmalloc(sizeof(*tmpindex));
146                         xread(fd, tmpindex, sizeof(*tmpindex));
147                         tmpindex->tag = ntohl(tmpindex->tag);
148                         tmpindex->type = ntohl(tmpindex->type);
149                         tmpindex->count = ntohl(tmpindex->count);
150                         tmpindex->offset = storepos + ntohl(tmpindex->offset);
151                         if (pass == 0)
152                                 tmpindex->tag -= 743;
153                 }
154                 storepos = xlseek(fd, header.size, SEEK_CUR); /* Seek past store */
155                 /* Skip padding to 8 byte boundary after reading signature headers */
156                 if (pass == 0)
157                         xlseek(fd, (-storepos) & 0x7, SEEK_CUR);
158         }
159         /* realloc tags to save space */
160         tags = xrealloc(tags, tagindex * sizeof(tags[0]));
161         *num_tags = tagindex;
162         /* All done, leave the file at the start of the gzipped cpio archive */
163         return tags;
164 }
165
166 static int bsearch_rpmtag(const void *key, const void *item)
167 {
168         int *tag = (int *)key;
169         rpm_index **tmp = (rpm_index **) item;
170         return (*tag - tmp[0]->tag);
171 }
172
173 static int rpm_getcount(int tag)
174 {
175         rpm_index **found;
176         found = bsearch(&tag, G.mytags, G.tagcount, sizeof(struct rpmtag *), bsearch_rpmtag);
177         if (!found)
178                 return 0;
179         return found[0]->count;
180 }
181
182 static char *rpm_getstr(int tag, int itemindex)
183 {
184         rpm_index **found;
185         found = bsearch(&tag, G.mytags, G.tagcount, sizeof(struct rpmtag *), bsearch_rpmtag);
186         if (!found || itemindex >= found[0]->count)
187                 return NULL;
188         if (found[0]->type == RPM_STRING_TYPE
189          || found[0]->type == RPM_I18NSTRING_TYPE
190          || found[0]->type == RPM_STRING_ARRAY_TYPE
191         ) {
192                 int n;
193                 char *tmpstr = (char *) G.map + found[0]->offset;
194                 for (n = 0; n < itemindex; n++)
195                         tmpstr = tmpstr + strlen(tmpstr) + 1;
196                 return tmpstr;
197         }
198         return NULL;
199 }
200
201 static int rpm_getint(int tag, int itemindex)
202 {
203         rpm_index **found;
204         char *tmpint;
205
206         /* gcc throws warnings here when sizeof(void*)!=sizeof(int) ...
207          * it's ok to ignore it because tag won't be used as a pointer */
208         found = bsearch(&tag, G.mytags, G.tagcount, sizeof(struct rpmtag *), bsearch_rpmtag);
209         if (!found || itemindex >= found[0]->count)
210                 return -1;
211
212         tmpint = (char *) G.map + found[0]->offset;
213         if (found[0]->type == RPM_INT32_TYPE) {
214                 tmpint += itemindex*4;
215                 return ntohl(*(int32_t*)tmpint);
216         }
217         if (found[0]->type == RPM_INT16_TYPE) {
218                 tmpint += itemindex*2;
219                 return ntohs(*(int16_t*)tmpint);
220         }
221         if (found[0]->type == RPM_INT8_TYPE) {
222                 tmpint += itemindex;
223                 return *(int8_t*)tmpint;
224         }
225         return -1;
226 }
227
228 static void fileaction_dobackup(char *filename, int fileref)
229 {
230         struct stat oldfile;
231         int stat_res;
232         char *newname;
233         if (rpm_getint(TAG_FILEFLAGS, fileref) & RPMFILE_CONFIG) {
234                 /* Only need to backup config files */
235                 stat_res = lstat(filename, &oldfile);
236                 if (stat_res == 0 && S_ISREG(oldfile.st_mode)) {
237                         /* File already exists  - really should check MD5's etc to see if different */
238                         newname = xasprintf("%s.rpmorig", filename);
239                         copy_file(filename, newname, FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS);
240                         remove_file(filename, FILEUTILS_RECUR | FILEUTILS_FORCE);
241                         free(newname);
242                 }
243         }
244 }
245
246 static void fileaction_setowngrp(char *filename, int fileref)
247 {
248         /* real rpm warns: "user foo does not exist - using <you>" */
249         struct passwd *pw = getpwnam(rpm_getstr(TAG_FILEUSERNAME, fileref));
250         int uid = pw ? pw->pw_uid : getuid(); /* or euid? */
251         struct group *gr = getgrnam(rpm_getstr(TAG_FILEGROUPNAME, fileref));
252         int gid = gr ? gr->gr_gid : getgid();
253         chown(filename, uid, gid);
254 }
255
256 static void loop_through_files(int filetag, void (*fileaction)(char *filename, int fileref))
257 {
258         int count = 0;
259         while (rpm_getstr(filetag, count)) {
260                 char* filename = xasprintf("%s%s",
261                         rpm_getstr(TAG_DIRNAMES, rpm_getint(TAG_DIRINDEXES, count)),
262                         rpm_getstr(TAG_BASENAMES, count));
263                 fileaction(filename, count++);
264                 free(filename);
265         }
266 }
267
268 int rpm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
269 int rpm_main(int argc, char **argv)
270 {
271         int opt, func = 0;
272         const unsigned pagesize = getpagesize();
273
274         while ((opt = getopt(argc, argv, "iqpldc")) != -1) {
275                 switch (opt) {
276                 case 'i': /* First arg: Install mode, with q: Information */
277                         if (!func) func = rpm_install;
278                         else func |= rpm_query_info;
279                         break;
280                 case 'q': /* First arg: Query mode */
281                         if (func) bb_show_usage();
282                         func = rpm_query;
283                         break;
284                 case 'p': /* Query a package */
285                         func |= rpm_query_package;
286                         break;
287                 case 'l': /* List files in a package */
288                         func |= rpm_query_list;
289                         break;
290                 case 'd': /* List doc files in a package (implies list) */
291                         func |= rpm_query_list;
292                         func |= rpm_query_list_doc;
293                         break;
294                 case 'c': /* List config files in a package (implies list) */
295                         func |= rpm_query_list;
296                         func |= rpm_query_list_config;
297                         break;
298                 default:
299                         bb_show_usage();
300                 }
301         }
302         argv += optind;
303         //argc -= optind;
304         if (!argv[0]) {
305                 bb_show_usage();
306         }
307
308         while (*argv) {
309                 int rpm_fd;
310                 unsigned mapsize;
311                 const char *source_rpm;
312
313                 rpm_fd = xopen(*argv++, O_RDONLY);
314                 G.mytags = rpm_gettags(rpm_fd, &G.tagcount);
315                 if (!G.mytags)
316                         bb_error_msg_and_die("error reading rpm header");
317                 mapsize = xlseek(rpm_fd, 0, SEEK_CUR);
318                 mapsize = (mapsize + pagesize) & -(int)pagesize;
319                 /* Some NOMMU systems prefer MAP_PRIVATE over MAP_SHARED */
320                 G.map = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, rpm_fd, 0);
321 //FIXME: error check?
322
323                 source_rpm = rpm_getstr(TAG_SOURCERPM, 0);
324
325                 if (func & rpm_install) {
326                         /* Backup any config files */
327                         loop_through_files(TAG_BASENAMES, fileaction_dobackup);
328                         /* Extact the archive */
329                         extract_cpio(rpm_fd, source_rpm);
330                         /* Set the correct file uid/gid's */
331                         loop_through_files(TAG_BASENAMES, fileaction_setowngrp);
332                 }
333                 else if ((func & (rpm_query|rpm_query_package)) == (rpm_query|rpm_query_package)) {
334                         if (!(func & (rpm_query_info|rpm_query_list))) {
335                                 /* If just a straight query, just give package name */
336                                 printf("%s-%s-%s\n", rpm_getstr(TAG_NAME, 0), rpm_getstr(TAG_VERSION, 0), rpm_getstr(TAG_RELEASE, 0));
337                         }
338                         if (func & rpm_query_info) {
339                                 /* Do the nice printout */
340                                 time_t bdate_time;
341                                 struct tm *bdate_ptm;
342                                 char bdatestring[50];
343                                 const char *p;
344
345                                 printf("%-12s: %s\n", "Name"        , rpm_getstr(TAG_NAME, 0));
346                                 /* TODO compat: add "Epoch" here */
347                                 printf("%-12s: %s\n", "Version"     , rpm_getstr(TAG_VERSION, 0));
348                                 printf("%-12s: %s\n", "Release"     , rpm_getstr(TAG_RELEASE, 0));
349                                 /* add "Architecture" */
350                                 printf("%-12s: %s\n", "Install Date", "(not installed)");
351                                 printf("%-12s: %s\n", "Group"       , rpm_getstr(TAG_GROUP, 0));
352                                 printf("%-12s: %d\n", "Size"        , rpm_getint(TAG_SIZE, 0));
353                                 printf("%-12s: %s\n", "License"     , rpm_getstr(TAG_LICENSE, 0));
354                                 /* add "Signature" */
355                                 printf("%-12s: %s\n", "Source RPM"  , source_rpm ? source_rpm : "(none)");
356                                 bdate_time = rpm_getint(TAG_BUILDTIME, 0);
357                                 bdate_ptm = localtime(&bdate_time);
358                                 strftime(bdatestring, 50, "%a %d %b %Y %T %Z", bdate_ptm);
359                                 printf("%-12s: %s\n", "Build Date"  , bdatestring);
360                                 printf("%-12s: %s\n", "Build Host"  , rpm_getstr(TAG_BUILDHOST, 0));
361                                 p = rpm_getstr(TAG_PREFIXS, 0);
362                                 printf("%-12s: %s\n", "Relocations" , p ? p : "(not relocatable)");
363                                 /* add "Packager" */
364                                 p = rpm_getstr(TAG_VENDOR, 0);
365                                 printf("%-12s: %s\n", "Vendor"      , p ? p : "(none)");
366                                 printf("%-12s: %s\n", "URL"         , rpm_getstr(TAG_URL, 0));
367                                 printf("%-12s: %s\n", "Summary"     , rpm_getstr(TAG_SUMMARY, 0));
368                                 printf("Description :\n%s\n", rpm_getstr(TAG_DESCRIPTION, 0));
369                         }
370                         if (func & rpm_query_list) {
371                                 int count, it, flags;
372                                 count = rpm_getcount(TAG_BASENAMES);
373                                 for (it = 0; it < count; it++) {
374                                         flags = rpm_getint(TAG_FILEFLAGS, it);
375                                         switch (func & (rpm_query_list_doc|rpm_query_list_config)) {
376                                         case rpm_query_list_doc:
377                                                 if (!(flags & RPMFILE_DOC)) continue;
378                                                 break;
379                                         case rpm_query_list_config:
380                                                 if (!(flags & RPMFILE_CONFIG)) continue;
381                                                 break;
382                                         case rpm_query_list_doc|rpm_query_list_config:
383                                                 if (!(flags & (RPMFILE_CONFIG|RPMFILE_DOC))) continue;
384                                                 break;
385                                         }
386                                         printf("%s%s\n",
387                                                 rpm_getstr(TAG_DIRNAMES, rpm_getint(TAG_DIRINDEXES, it)),
388                                                 rpm_getstr(TAG_BASENAMES, it));
389                                 }
390                         }
391                 }
392                 munmap(G.map, mapsize);
393                 free(G.mytags);
394                 close(rpm_fd);
395         }
396         return 0;
397 }