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