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