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