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