rpm.c
author Kristian H?gsberg <krh@redhat.com>
Thu Dec 27 17:45:18 2007 -0500 (2007-12-27)
changeset 86 e44ccd213756
parent 85 c5041a93b91a
child 87 c1e04d4e3bc9
permissions -rw-r--r--
Extend installer implementation further.
     1 #include <stdio.h>
     2 #include <stddef.h>
     3 #include <string.h>
     4 #include <sys/stat.h>
     5 #include <sys/mman.h>
     6 #include <sys/types.h>
     7 #include <sys/wait.h>
     8 #include <fcntl.h>
     9 #include <unistd.h>
    10 #include <arpa/inet.h>
    11 #include <rpm/rpmlib.h>
    12 #include <zlib.h>
    13 
    14 #include "razor.h"
    15 
    16 #define	RPM_LEAD_SIZE 96
    17 
    18 struct rpm_lead {
    19 	unsigned char magic[4];
    20 	unsigned char major;
    21 	unsigned char minor;
    22 	short type;
    23 	short archnum;
    24 	char name[66];
    25 	short osnum;
    26 	short signature_type;
    27 	char reserved[16];
    28 };
    29 
    30 struct rpm_header {
    31 	unsigned char magic[4];
    32 	unsigned char reserved[4];
    33 	int nindex;
    34 	int hsize;
    35 };
    36 
    37 struct rpm_header_index {
    38 	int tag;
    39 	int type;
    40 	int offset;
    41 	int count;
    42 };
    43 
    44 struct properties {
    45 	struct rpm_header_index *name;
    46 	struct rpm_header_index *version;
    47 	struct rpm_header_index *flags;
    48 };
    49 
    50 struct razor_rpm {
    51 	struct rpm_header *signature;
    52 	struct rpm_header *header;
    53 
    54 	struct rpm_header_index *name;
    55 	struct rpm_header_index *version;
    56 	struct rpm_header_index *release;
    57 
    58 	struct rpm_header_index *dirnames;
    59 	struct rpm_header_index *dirindexes;
    60 	struct rpm_header_index *basenames;
    61 	struct rpm_header_index *filesizes;
    62 	struct rpm_header_index *filemodes;
    63 	struct rpm_header_index *fileflags;
    64 	const char **dirs;
    65 
    66 	struct properties provides;
    67 	struct properties requires;
    68 	struct properties obsoletes;
    69 	struct properties conflicts;
    70 
    71 	const char *pool;
    72 	void *map;
    73 	size_t size;
    74 	void *payload;
    75 };
    76 
    77 #define ALIGN(value, base) (((value) + (base - 1)) & ~((base) - 1))
    78 
    79 static void
    80 import_properties(struct razor_importer *importer,
    81 		  struct properties *properties,
    82 		  const char *pool, unsigned long type)
    83 {
    84 	const char *name, *version;
    85 	int i, count;
    86 
    87 	/* assert: count is the same for all arrays */
    88 
    89 	if (properties->name == NULL)
    90 		return;
    91 
    92 	count = ntohl(properties->name->count);
    93 	name = pool + ntohl(properties->name->offset);
    94 	version = pool + ntohl(properties->version->offset);
    95 	for (i = 0; i < count; i++) {
    96 		razor_importer_add_property(importer, name, version, type);
    97 		name += strlen(name) + 1;
    98 		version += strlen(version) + 1;
    99 	}
   100 }
   101 
   102 static void
   103 import_files(struct razor_importer *importer, struct razor_rpm *rpm)
   104 {
   105 	const char *name;
   106 	unsigned long *index;
   107 	int i, count;
   108 	char buffer[256];
   109 
   110 	/* assert: count is the same for all arrays */
   111 
   112 	if (rpm->dirnames == NULL)
   113 		return;
   114 
   115 	count = ntohl(rpm->basenames->count);
   116 	index = (unsigned long *) (rpm->pool + ntohl(rpm->dirindexes->offset));
   117 	name = rpm->pool + ntohl(rpm->basenames->offset);
   118 	for (i = 0; i < count; i++) {
   119 		snprintf(buffer, sizeof buffer,
   120 			 "%s%s", rpm->dirs[ntohl(*index)], name);
   121 		razor_importer_add_file(importer, buffer);
   122 		name += strlen(name) + 1;
   123 		index++;
   124 	}
   125 }
   126 
   127 #define MAP_ENTRY(field, tag) { offsetof(struct razor_rpm, field), tag }
   128 
   129 static struct index_map {
   130 	unsigned int offset;
   131 	unsigned int tag;
   132 } index_map[] =	{
   133 	MAP_ENTRY(name, RPMTAG_NAME),
   134 	MAP_ENTRY(version, RPMTAG_VERSION),
   135 	MAP_ENTRY(release, RPMTAG_RELEASE),
   136 	MAP_ENTRY(requires.name, RPMTAG_REQUIRENAME),
   137 	MAP_ENTRY(requires.version, RPMTAG_REQUIREVERSION),
   138 	MAP_ENTRY(requires.flags, RPMTAG_REQUIREFLAGS),
   139 	MAP_ENTRY(provides.name, RPMTAG_PROVIDENAME),
   140 	MAP_ENTRY(provides.version, RPMTAG_PROVIDEVERSION),
   141 	MAP_ENTRY(provides.flags, RPMTAG_PROVIDEFLAGS),
   142 	MAP_ENTRY(obsoletes.name, RPMTAG_OBSOLETENAME),
   143 	MAP_ENTRY(obsoletes.version, RPMTAG_OBSOLETEVERSION),
   144 	MAP_ENTRY(obsoletes.flags, RPMTAG_OBSOLETEFLAGS),
   145 	MAP_ENTRY(conflicts.name, RPMTAG_CONFLICTNAME),
   146 	MAP_ENTRY(conflicts.version, RPMTAG_CONFLICTVERSION),
   147 	MAP_ENTRY(conflicts.flags, RPMTAG_CONFLICTFLAGS),
   148 	MAP_ENTRY(dirindexes, RPMTAG_DIRINDEXES),
   149 	MAP_ENTRY(basenames, RPMTAG_BASENAMES),
   150 	MAP_ENTRY(dirnames, RPMTAG_DIRNAMES),
   151 	MAP_ENTRY(filesizes, RPMTAG_FILESIZES),
   152 	MAP_ENTRY(filemodes, RPMTAG_FILEMODES),
   153 	MAP_ENTRY(fileflags, RPMTAG_FILEFLAGS),
   154 };
   155 
   156 static struct rpm_header_index *
   157 razor_rpm_get_header(struct razor_rpm *rpm, unsigned int tag)
   158 {
   159 	struct rpm_header_index *index, *end;
   160 
   161 	index = (struct rpm_header_index *) (rpm->header + 1);
   162 	end = index + ntohl(rpm->header->nindex);
   163 	while (index < end) {
   164 		if (ntohl(index->tag) == tag)
   165 			return index;
   166 		index++;
   167 	}
   168 
   169 	return NULL;
   170 }
   171 
   172 struct razor_rpm *
   173 razor_rpm_open(const char *filename)
   174 {
   175 	struct razor_rpm *rpm;
   176 	struct rpm_header_index *base, *index;
   177 	struct stat buf;
   178 	int fd, nindex, hsize, i, j, count;
   179 	const char *name;
   180 
   181 	rpm = malloc(sizeof *rpm);
   182 	memset(rpm, 0, sizeof *rpm);
   183 	if (stat(filename, &buf) < 0) {
   184 		fprintf(stderr, "no such file %s (%m)\n", filename);
   185 		return NULL;
   186 	}
   187 
   188 	fd = open(filename, O_RDONLY);
   189 	if (fd < 0) {
   190 		fprintf(stderr, "couldn't open %s\n", filename);
   191 		return NULL;
   192 	}
   193 	rpm->size = buf.st_size;
   194 	rpm->map = mmap(NULL, rpm->size, PROT_READ, MAP_PRIVATE, fd, 0);
   195 	if (rpm->map == MAP_FAILED) {
   196 		fprintf(stderr, "couldn't mmap %s\n", filename);
   197 		return NULL;
   198 	}
   199 	close(fd);
   200 
   201 	rpm->signature = rpm->map + RPM_LEAD_SIZE;
   202 	nindex = ntohl(rpm->signature->nindex);
   203 	hsize = ntohl(rpm->signature->hsize);
   204 	rpm->header = (void *) (rpm->signature + 1) +
   205 		ALIGN(nindex * sizeof *index + hsize, 8);
   206 	nindex = ntohl(rpm->header->nindex);
   207 	hsize = ntohl(rpm->header->hsize);
   208 	rpm->payload = (void *) (rpm->header + 1) +
   209 		nindex * sizeof *index + hsize;
   210 
   211 	base = (struct rpm_header_index *) (rpm->header + 1);
   212 	rpm->pool = (void *) base + nindex * sizeof *index;
   213 
   214 	for (i = 0; i < nindex; i++) {
   215 		index = base + i;
   216 		for (j = 0; j < ARRAY_SIZE(index_map); j++) {
   217 			struct rpm_header_index **p;
   218 			if (index_map[j].tag == ntohl(index->tag)) {
   219 				p = (void *) rpm + index_map[j].offset;
   220 				*p = index;
   221 			}
   222 		}				 
   223 	}
   224 
   225 	/* Look up dir names now so we can index them directly. */
   226 	if (rpm->dirnames != NULL) {
   227 		count = ntohl(rpm->dirnames->count);
   228 		rpm->dirs = calloc(count, sizeof *rpm->dirs);
   229 		name = rpm->pool + ntohl(rpm->dirnames->offset);
   230 		for (i = 0; i < count; i++) {
   231 			rpm->dirs[i] = name;
   232 			name += strlen(name) + 1;
   233 		}
   234 	}
   235 
   236 	return rpm;
   237 }
   238 
   239 struct cpio_file_header {
   240 	char magic[6];
   241 	char inode[8];
   242 	char mode[8];
   243 	char uid[8];
   244 	char gid[8];
   245 	char nlink[8];
   246 	char mtime[8];
   247 	char filesize[8];
   248 	char devmajor[8];
   249 	char devminor[8];
   250 	char rdevmajor[8];
   251 	char rdevminor[8];
   252 	char namesize[8];
   253 	char checksum[8];
   254 	char filename[0];
   255 };
   256 
   257 /* gzip flags */
   258 #define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
   259 #define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
   260 #define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
   261 #define ORIG_NAME    0x08 /* bit 3 set: original file name present */
   262 #define COMMENT      0x10 /* bit 4 set: file comment present */
   263 #define RESERVED     0xE0 /* bits 5..7: reserved */
   264 
   265 struct installer {
   266 	const char *root;
   267 	struct razor_rpm *rpm;
   268 	z_stream stream;
   269 	unsigned char buffer[32768];
   270 	size_t rest, length;
   271 };
   272 
   273 static int
   274 installer_inflate(struct installer *installer)
   275 {
   276 	size_t length;
   277 	int err;
   278 
   279 	if (ALIGN(installer->rest, 4) > sizeof installer->buffer)
   280 		length = sizeof installer->buffer;
   281 	else
   282 		length = installer->rest;
   283 
   284 	installer->stream.next_out = installer->buffer;
   285 	installer->stream.avail_out = ALIGN(length, 4);
   286 	err = inflate(&installer->stream, Z_SYNC_FLUSH);
   287 	if (err != Z_OK && err != Z_STREAM_END) {
   288 		fprintf(stderr, "inflate error: %d (%m)\n", err);
   289 		return -1;
   290 	}
   291 
   292 	installer->rest -= length;
   293 	installer->length = length;
   294 
   295 	return 0;
   296 }
   297 
   298 static int
   299 xwrite(int fd, const void *data, size_t size)
   300 {
   301 	size_t rest;
   302 	ssize_t written;
   303 	const unsigned char *p;
   304 
   305 	rest = size;
   306 	p = data;
   307 	while (rest > 0) {
   308 		written = write(fd, p, rest);
   309 		if (written < 0) {
   310 			fprintf(stderr, "write error: %m\n");
   311 			return -1;
   312 		}
   313 		rest -= written;
   314 		p += written;
   315 	}
   316 
   317 	return 0;
   318 }
   319 
   320 static int
   321 create_path(struct installer *installer,
   322 	    const char *path, const char *name, unsigned int mode)
   323 {
   324 	char buffer[256], *p;
   325 	const char *slash, *next;
   326 	struct stat buf;
   327 	int fd;
   328 
   329 	/* Create all sub-directories in dir and then create name. We
   330 	 * know root exists and is a dir, root does not end in a '/',
   331 	 * and path has a leading '/'. */
   332 
   333 	strcpy(buffer, installer->root);
   334 	p = buffer + strlen(buffer);
   335 	slash = path;
   336 	for (slash = path; slash[1] != '\0'; slash = next) {
   337 		next = strchr(slash + 1, '/');
   338 		memcpy(p, slash, next - slash);
   339 		p += next - slash;
   340 		*p = '\0';
   341 
   342 		if (stat(buffer, &buf) == 0) {
   343 			if (!S_ISDIR(buf.st_mode)) {
   344 				fprintf(stderr,
   345 					"%s exists but is not a directory\n",
   346 					buffer);
   347 				return -1;
   348 			}
   349 		} else if (mkdir(buffer, 0777) < 0) {
   350 			fprintf(stderr, "failed to make directory %s: %m\n",
   351 				buffer);
   352 			return -1;
   353 		}
   354 		/* FIXME: permissions */
   355 	}
   356 
   357 	*p++ = '/';
   358 	strcpy(p, name);
   359 
   360 	switch (mode >> 12) {
   361 	case REG:
   362 		fd = open(buffer, O_WRONLY | O_CREAT | O_TRUNC, mode & 0x1ff);
   363 		if (fd < 0){
   364 			fprintf(stderr, "failed to create file %s\n", buffer);
   365 			return -1;
   366 		}
   367 		while (installer->rest > 0) {
   368 			if (installer_inflate(installer)) {
   369 				fprintf(stderr, "failed to inflate\n");
   370 				return -1;
   371 			}
   372 			if (xwrite(fd, installer->buffer, installer->length)) {
   373 				fprintf(stderr, "failed to write payload\n");
   374 				return -1;
   375 			}
   376 		}
   377 		if (close(fd) < 0) {
   378 			fprintf(stderr, "failed to close %s: %m\n", buffer);
   379 			return -1;
   380 		}
   381 		return 0;
   382 	case XDIR:
   383 		return mkdir(buffer, mode & 0x1ff);
   384 	case PIPE:
   385 	case CDEV:
   386 	case BDEV:
   387 	case SOCK:
   388 		printf("%s: unhandled file type %d\n", buffer, mode >> 12);
   389 		return 0;
   390 	case LINK:
   391 		if (installer_inflate(installer)) {
   392 			fprintf(stderr, "failed to inflate\n");
   393 			return -1;
   394 		}
   395 		if (installer->length >= sizeof installer->buffer) {
   396 			fprintf(stderr, "link name too long\n");
   397 			return -1;
   398 		}
   399 		installer->buffer[installer->length] = '\0';
   400 		if (symlink((const char *) installer->buffer, buffer)) {
   401 			fprintf(stderr, "failed to create symlink, %m\n");
   402 			return -1;
   403 		}
   404 		return 0;
   405 	default:
   406 		printf("%s: unknown file type %d\n", buffer, mode >> 12);
   407 		return 0;
   408 	}
   409 }
   410 
   411 static int
   412 run_script(struct installer *installer,
   413 	   unsigned int program_tag, unsigned int script_tag)
   414 {
   415 	struct rpm_header_index *index;
   416 	int pid, status, fd[2];
   417 	const char *script = NULL, *program = NULL;
   418 
   419 	index = razor_rpm_get_header(installer->rpm, program_tag);
   420 	if (index != NULL)
   421 		program = installer->rpm->pool + ntohl(index->offset);
   422 
   423 	index = razor_rpm_get_header(installer->rpm, script_tag);
   424 	if (index != NULL)
   425 		script = installer->rpm->pool + ntohl(index->offset);
   426 
   427 	if (program == NULL && script == NULL) {
   428 		printf("no script or program for tags %d and %d\n",
   429 		       program_tag, script_tag);
   430 		return -1;
   431 	} else if (program == NULL) {
   432 		program = "/bin/sh";
   433 	}
   434 
   435 	if (pipe(fd) < 0) {
   436 		fprintf(stderr, "failed to create pipe\n");
   437 		return -1;
   438 	}
   439 	pid = fork();
   440 	if (pid < 0) {
   441 		fprintf(stderr, "failed to fork, %m\n");
   442 	} else if (pid == 0) {
   443 		if (dup2(fd[0], STDIN_FILENO) < 0) {
   444 			fprintf(stderr, "failed redirect stdin, %m\n");
   445 			return -1;
   446 		}
   447 		if (close(fd[0]) < 0 || close(fd[1]) < 0) {
   448 			fprintf(stderr, "failed to close pipe, %m\n");
   449 			exit(-1);
   450 		}
   451 		if (chroot(installer->root) < 0) {
   452 			fprintf(stderr, "failed to chroot to %s, %m\n",
   453 				installer->root);
   454 			return -1;
   455 		}
   456 		printf("executing program %s in chroot %s\n",
   457 		       program, installer->root);
   458 		if (execl(program, program, NULL)) {
   459 			fprintf(stderr, "failed to exec %s, %m\n", program);
   460 			return -1;
   461 		}
   462 	} else {
   463 		if (script && write(fd[1], script, strlen(script)) < 0) {
   464 			fprintf(stderr, "failed to pipe script, %m\n");
   465 			return -1;
   466 		}
   467 		if (close(fd[0]) || close(fd[1])) {
   468 			fprintf(stderr, "failed to close pipe, %m\n");
   469 			return -1;
   470 		}
   471 		if (wait(&status) < 0) {
   472 			fprintf(stderr, "wait for child failed, %m");
   473 			return -1;
   474 		}
   475 		printf("script exited with status %d\n", status);
   476 	}
   477 
   478 	return 0;
   479 }
   480 
   481 static int
   482 installer_init(struct installer *installer)
   483 {
   484 	unsigned char *gz_header;
   485 	int method, flags, err;
   486 
   487 	gz_header = installer->rpm->payload;
   488 	if (gz_header[0] != 0x1f || gz_header[1] != 0x8b) {
   489 		fprintf(stderr, "payload section doesn't have gz header\n");
   490 		return -1;
   491 	}
   492 
   493 	method = gz_header[2];
   494 	flags = gz_header[3];
   495 
   496 	if (method != Z_DEFLATED || flags != 0) {
   497 		fprintf(stderr,
   498 			"unknown payload compression method or flags set\n");
   499 		return -1;
   500 	}
   501 
   502 	installer->stream.zalloc = NULL;
   503 	installer->stream.zfree = NULL;
   504 	installer->stream.opaque = NULL;
   505 
   506 	installer->stream.next_in  = gz_header + 10;
   507 	installer->stream.avail_in =
   508 		(installer->rpm->map + installer->rpm->size) -
   509 		(void *) installer->stream.next_in;
   510 	installer->stream.next_out = NULL;
   511 	installer->stream.avail_out = 0;
   512 
   513 	err = inflateInit2(&installer->stream, -MAX_WBITS);
   514 	if (err != Z_OK) {
   515 		fprintf(stderr, "inflateInit error: %d\n", err);
   516 		return -1;
   517 	}
   518 
   519 	return 0;
   520 }
   521 
   522 static int
   523 installer_finish(struct installer *installer)
   524 {
   525 	int err;
   526 
   527 	err = inflateEnd(&installer->stream);
   528 
   529 	if (err != Z_OK) {
   530 		fprintf(stderr, "inflateEnd error: %d\n", err);
   531 		return -1;
   532 	}	    
   533 
   534 	return 0;
   535 }
   536 
   537 int
   538 razor_rpm_install(struct razor_rpm *rpm, const char *root)
   539 {
   540 	struct installer installer;
   541 	int count, i;
   542 	struct cpio_file_header *header;
   543 	unsigned long *size, *index, length, *flags;
   544 	unsigned short *mode;
   545 	const char *name, *dir;
   546 	struct stat buf;
   547 
   548 	installer.rpm = rpm;
   549 	installer.root = root;
   550 
   551 	/* FIXME: Only do this before a transaction, not per rpm. */
   552 	if (stat(root, &buf) < 0 || !S_ISDIR(buf.st_mode)) {
   553 		fprintf(stderr,
   554 			"root installation directory \"%s\" does not exist\n",
   555 			root);
   556 		return -1;
   557 	}
   558 
   559 	if (installer_init(&installer))
   560 		return -1;
   561 
   562 	run_script(&installer, RPMTAG_PREINPROG, RPMTAG_PREIN);
   563 
   564 	count = ntohl(rpm->basenames->count);
   565 	size = (unsigned long *) (rpm->pool + ntohl(rpm->filesizes->offset));
   566 	index = (unsigned long *) (rpm->pool + ntohl(rpm->dirindexes->offset));
   567 	mode = (unsigned short *) (rpm->pool + ntohl(rpm->filemodes->offset));
   568 	flags = (unsigned long *) (rpm->pool + ntohl(rpm->fileflags->offset));
   569 
   570 	name = rpm->pool + ntohl(rpm->basenames->offset);
   571 	for (i = 0; i < count; i++) {
   572 		dir = rpm->dirs[ntohl(*index)];
   573 
   574 		/* Skip past the cpio header block unless it's a ghost file,
   575 		 * in which case doesn't appear in the cpio archive. */
   576 		if (!(ntohl(*flags) & RPMFILE_GHOST)) {
   577 			/* Plus two for the leading '.' and the terminating NUL. */
   578 			length = sizeof *header + strlen(dir) + strlen(name) + 2;
   579 			installer.rest = ALIGN(length, 4);
   580 			if (installer_inflate(&installer))
   581 				return -1;
   582 		}
   583 
   584 		installer.rest = ntohl(*size);
   585 		if (create_path(&installer, dir, name, ntohs(*mode)) < 0)
   586 			return -1;
   587 
   588 		name += strlen(name) + 1;
   589 		index++;
   590 		size++;
   591 		mode++;
   592 		flags++;
   593 	}
   594 
   595 	if (installer_finish(&installer))
   596 		return -1;
   597 
   598 	run_script(&installer, RPMTAG_POSTINPROG, RPMTAG_POSTIN);
   599 
   600 	return 0;
   601 }
   602 
   603 int
   604 razor_rpm_close(struct razor_rpm *rpm)
   605 {
   606 	int err;
   607 
   608 	free(rpm->dirs);
   609 	err = munmap(rpm->map, rpm->size);
   610 	free(rpm);
   611 
   612 	return err;
   613 }
   614 
   615 int
   616 razor_importer_add_rpm(struct razor_importer *importer, struct razor_rpm *rpm)
   617 {
   618 	razor_importer_begin_package(importer,
   619 				     rpm->pool + ntohl(rpm->name->offset),
   620 				     rpm->pool + ntohl(rpm->version->offset));
   621 
   622 	import_properties(importer, &rpm->requires,
   623 			  rpm->pool, RAZOR_PROPERTY_REQUIRES);
   624 	import_properties(importer, &rpm->provides,
   625 			  rpm->pool, RAZOR_PROPERTY_PROVIDES);
   626 	import_properties(importer, &rpm->conflicts,
   627 			  rpm->pool, RAZOR_PROPERTY_CONFLICTS);
   628 	import_properties(importer, &rpm->obsoletes,
   629 			  rpm->pool, RAZOR_PROPERTY_OBSOLETES);
   630 	import_files(importer, rpm);
   631 
   632 	razor_importer_finish_package(importer);
   633 
   634 	return 0;
   635 }