rpm.c
author Kristian H?gsberg <krh@redhat.com>
Fri Nov 16 17:14:51 2007 -0500 (2007-11-16)
changeset 83 43ef0eb1b603
parent 75 93278d8ec39c
child 84 6883222205ad
permissions -rw-r--r--
Fix file tree traversal to not skip the last entry.
     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <sys/stat.h>
     4 #include <sys/mman.h>
     5 #include <fcntl.h>
     6 #include <unistd.h>
     7 #include <arpa/inet.h>
     8 #include <rpm/rpmlib.h>
     9 #include <zlib.h>
    10 
    11 #include "razor.h"
    12 
    13 #define	RPM_LEAD_SIZE 96
    14 
    15 struct rpm_lead {
    16 	unsigned char magic[4];
    17 	unsigned char major;
    18 	unsigned char minor;
    19 	short type;
    20 	short archnum;
    21 	char name[66];
    22 	short osnum;
    23 	short signature_type;
    24 	char reserved[16];
    25 };
    26 
    27 struct rpm_header {
    28 	unsigned char magic[4];
    29 	unsigned char reserved[4];
    30 	int nindex;
    31 	int hsize;
    32 };
    33 
    34 struct rpm_header_index {
    35 	int tag;
    36 	int type;
    37 	int offset;
    38 	int count;
    39 };
    40 
    41 struct properties {
    42 	struct rpm_header_index *name;
    43 	struct rpm_header_index *version;
    44 	struct rpm_header_index *flags;
    45 };
    46 
    47 struct razor_rpm {
    48 	struct rpm_header *signature;
    49 	struct rpm_header *header;
    50 
    51 	struct rpm_header_index *name;
    52 	struct rpm_header_index *version;
    53 	struct rpm_header_index *release;
    54 
    55 	struct rpm_header_index *dirnames;
    56 	struct rpm_header_index *dirindexes;
    57 	struct rpm_header_index *basenames;
    58 	struct rpm_header_index *filesizes;
    59 	struct rpm_header_index *filemodes;
    60 	struct rpm_header_index *filestates;
    61 	const char **dirs;
    62 
    63 	struct properties provides;
    64 	struct properties requires;
    65 	struct properties obsoletes;
    66 	struct properties conflicts;
    67 
    68 	const char *pool;
    69 	void *map;
    70 	size_t size;
    71 	void *payload;
    72 };
    73 
    74 #define ALIGN(value, base) (((value) + (base - 1)) & ~((base) - 1))
    75 
    76 static void
    77 import_properties(struct razor_importer *importer,
    78 		  struct properties *properties,
    79 		  const char *pool, unsigned long type)
    80 {
    81 	const char *name, *version;
    82 	int i, count;
    83 
    84 	/* assert: count is the same for all arrays */
    85 
    86 	if (properties->name == NULL)
    87 		return;
    88 
    89 	count = ntohl(properties->name->count);
    90 	name = pool + ntohl(properties->name->offset);
    91 	version = pool + ntohl(properties->version->offset);
    92 	for (i = 0; i < count; i++) {
    93 		razor_importer_add_property(importer, name, version, type);
    94 		name += strlen(name) + 1;
    95 		version += strlen(version) + 1;
    96 	}
    97 }
    98 
    99 static void
   100 import_files(struct razor_importer *importer, struct razor_rpm *rpm)
   101 {
   102 	const char *name;
   103 	unsigned long *index;
   104 	int i, count;
   105 	char buffer[256];
   106 
   107 	/* assert: count is the same for all arrays */
   108 
   109 	if (rpm->dirnames == NULL)
   110 		return;
   111 
   112 	count = ntohl(rpm->basenames->count);
   113 	index = (unsigned long *) (rpm->pool + ntohl(rpm->dirindexes->offset));
   114 	name = rpm->pool + ntohl(rpm->basenames->offset);
   115 	for (i = 0; i < count; i++) {
   116 		snprintf(buffer, sizeof buffer,
   117 			 "%s%s", rpm->dirs[ntohl(*index)], name);
   118 		razor_importer_add_file(importer, buffer);
   119 		name += strlen(name) + 1;
   120 		index++;
   121 	}
   122 }
   123 
   124 struct razor_rpm *
   125 razor_rpm_open(const char *filename)
   126 {
   127 	struct razor_rpm *rpm;
   128 	struct rpm_header_index *base, *index;
   129 	struct stat buf;
   130 	int fd, nindex, hsize, i, count;
   131 	const char *name;
   132 
   133 	rpm = malloc(sizeof *rpm);
   134 	memset(rpm, 0, sizeof *rpm);
   135 	if (stat(filename, &buf) < 0) {
   136 		fprintf(stderr, "no such file %s (%m)\n", filename);
   137 		return NULL;
   138 	}
   139 
   140 	fd = open(filename, O_RDONLY);
   141 	if (fd < 0) {
   142 		fprintf(stderr, "couldn't open %s\n", filename);
   143 		return NULL;
   144 	}
   145 	rpm->size = buf.st_size;
   146 	rpm->map = mmap(NULL, rpm->size, PROT_READ, MAP_PRIVATE, fd, 0);
   147 	if (rpm->map == MAP_FAILED) {
   148 		fprintf(stderr, "couldn't mmap %s\n", filename);
   149 		return NULL;
   150 	}
   151 	close(fd);
   152 
   153 	rpm->signature = rpm->map + RPM_LEAD_SIZE;
   154 	nindex = ntohl(rpm->signature->nindex);
   155 	hsize = ntohl(rpm->signature->hsize);
   156 	rpm->header = (void *) (rpm->signature + 1) +
   157 		ALIGN(nindex * sizeof *index + hsize, 8);
   158 	nindex = ntohl(rpm->header->nindex);
   159 	hsize = ntohl(rpm->header->hsize);
   160 	rpm->payload = (void *) (rpm->header + 1) +
   161 		nindex * sizeof *index + hsize;
   162 
   163 	base = (struct rpm_header_index *) (rpm->header + 1);
   164 	rpm->pool = (void *) base + nindex * sizeof *index;
   165 
   166 	for (i = 0; i < nindex; i++) {
   167 		index = base + i;
   168 		switch (ntohl(index->tag)) {
   169 		case RPMTAG_NAME:
   170 			rpm->name = index;
   171 			break;
   172 		case RPMTAG_VERSION:
   173 			rpm->version = index;
   174 			break;
   175 		case RPMTAG_RELEASE:
   176 			rpm->release = index;
   177 			break;
   178 
   179 		case RPMTAG_REQUIRENAME:
   180 			rpm->requires.name = index;
   181 			break;
   182 		case RPMTAG_REQUIREVERSION:
   183 			rpm->requires.version = index;
   184 			break;
   185 		case RPMTAG_REQUIREFLAGS:
   186 			rpm->requires.flags = index;
   187 			break;
   188 
   189 		case RPMTAG_PROVIDENAME:
   190 			rpm->provides.name = index;
   191 			break;
   192 		case RPMTAG_PROVIDEVERSION:
   193 			rpm->provides.version = index;
   194 			break;
   195 		case RPMTAG_PROVIDEFLAGS:
   196 			rpm->provides.flags = index;
   197 			break;
   198 
   199 		case RPMTAG_OBSOLETENAME:
   200 			rpm->obsoletes.name = index;
   201 			break;
   202 		case RPMTAG_OBSOLETEVERSION:
   203 			rpm->obsoletes.version = index;
   204 			break;
   205 		case RPMTAG_OBSOLETEFLAGS:
   206 			rpm->obsoletes.flags = index;
   207 			break;
   208 
   209 		case RPMTAG_CONFLICTNAME:
   210 			rpm->conflicts.name = index;
   211 			break;
   212 		case RPMTAG_CONFLICTVERSION:
   213 			rpm->conflicts.version = index;
   214 			break;
   215 		case RPMTAG_CONFLICTFLAGS:
   216 			rpm->conflicts.flags = index;
   217 			break;
   218 
   219 		case RPMTAG_DIRINDEXES:
   220 			rpm->dirindexes = index;
   221 			break;
   222 		case RPMTAG_BASENAMES:
   223 			rpm->basenames = index;
   224 			break;
   225 		case RPMTAG_DIRNAMES:
   226 			rpm->dirnames = index;
   227 			break;
   228 		case RPMTAG_FILESIZES:
   229 			rpm->filesizes = index;
   230 			break;
   231 		case RPMTAG_FILEMODES:
   232 			rpm->filemodes = index;
   233 			break;
   234 		case RPMTAG_FILESTATES:
   235 			rpm->filestates = index;
   236 			break;
   237 		}
   238 	}
   239 
   240 	/* Look up dir names now so we can index them directly. */
   241 	if (rpm->dirnames != NULL) {
   242 		count = ntohl(rpm->dirnames->count);
   243 		rpm->dirs = calloc(count, sizeof *rpm->dirs);
   244 		name = rpm->pool + ntohl(rpm->dirnames->offset);
   245 		for (i = 0; i < count; i++) {
   246 			rpm->dirs[i] = name;
   247 			name += strlen(name) + 1;
   248 		}
   249 	}
   250 
   251 	return rpm;
   252 }
   253 
   254 struct cpio_file_header {
   255 	char magic[6];
   256 	char inode[8];
   257 	char mode[8];
   258 	char uid[8];
   259 	char gid[8];
   260 	char nlink[8];
   261 	char mtime[8];
   262 	char filesize[8];
   263 	char devmajor[8];
   264 	char devminor[8];
   265 	char rdevmajor[8];
   266 	char rdevminor[8];
   267 	char namesize[8];
   268 	char checksum[8];
   269 	char filename[0];
   270 };
   271 
   272 /* gzip flags */
   273 #define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
   274 #define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
   275 #define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
   276 #define ORIG_NAME    0x08 /* bit 3 set: original file name present */
   277 #define COMMENT      0x10 /* bit 4 set: file comment present */
   278 #define RESERVED     0xE0 /* bits 5..7: reserved */
   279 
   280 static int
   281 create_path(const char *root, const char *path,
   282 	    const char *name, unsigned mode, int *fd)
   283 {
   284 	char buffer[256], *p;
   285 	const char *slash, *next;
   286 	struct stat buf;
   287 
   288 	/* Create all sub-directories in dir and then create name. We
   289 	 * know root exists and is a dir, root does not end in a '/',
   290 	 * and path has a leading '/'. */
   291 
   292 	strcpy(buffer, root);
   293 	p = buffer + strlen(buffer);
   294 	slash = path;
   295 	for (slash = path; slash[1] != '\0'; slash = next) {
   296 		next = strchr(slash + 1, '/');
   297 		memcpy(p, slash, next - slash);
   298 		p += next - slash;
   299 		*p = '\0';
   300 
   301 		if (stat(buffer, &buf) == 0) {
   302 			if (!S_ISDIR(buf.st_mode)) {
   303 				fprintf(stderr,
   304 					"%s exists but is not a directory\n",
   305 					buffer);
   306 				return -1;
   307 			}
   308 		} else if (mkdir(buffer, 0777) < 0) {
   309 			fprintf(stderr, "failed to make directory %s: %m\n",
   310 				buffer);
   311 			return -1;
   312 		}
   313 		/* FIXME: permissions */
   314 	}
   315 
   316 	*p++ = '/';
   317 	strcpy(p, name);
   318 
   319 	switch (mode >> 12) {
   320 	case REG:
   321 	default:
   322 		*fd = open(buffer, O_WRONLY | O_CREAT | O_TRUNC, mode & 0x1ff);
   323 		return *fd;
   324 	case XDIR:
   325 		*fd = -1;
   326 		return mkdir(buffer, mode & 0x1ff);
   327 	}
   328 }
   329 
   330 int
   331 razor_rpm_install(struct razor_rpm *rpm, const char *root)
   332 {
   333 	z_stream stream;
   334 	unsigned char payload[32768], *gz_header;
   335 	char buffer[256];
   336 	int err, method, flags, count, i, fd, written;
   337 	struct cpio_file_header *header;
   338 	unsigned long *size, *index, rest, length;
   339 	unsigned short *mode;
   340 	const char *name, *dir;
   341 	struct stat buf;
   342 
   343 	if (stat(root, &buf) < 0 || !S_ISDIR(buf.st_mode)) {
   344 		fprintf(stderr,
   345 			"root installation directory \"%s\" does not exist\n",
   346 			root);
   347 		return -1;
   348 	}
   349 
   350 	gz_header = rpm->payload;
   351 	if (gz_header[0] != 0x1f || gz_header[1] != 0x8b) {
   352 		fprintf(stderr, "payload section doesn't have gz header\n");
   353 		return -1;
   354 	}
   355 
   356 	method = gz_header[2];
   357 	flags = gz_header[3];
   358 
   359 	if (method != Z_DEFLATED || flags != 0) {
   360 		fprintf(stderr,
   361 			"unknown payload compression method or flags set\n");
   362 		return -1;
   363 	}
   364 
   365 	stream.zalloc = NULL;
   366 	stream.zfree = NULL;
   367 	stream.opaque = NULL;
   368 
   369 	stream.next_in  = gz_header + 10;
   370 	stream.avail_in = (rpm->map + rpm->size) - (void *) stream.next_in;
   371 	stream.next_out = NULL;
   372 	stream.avail_out = 0;
   373 
   374 	err = inflateInit2(&stream, -MAX_WBITS);
   375 	if (err != Z_OK) {
   376 		fprintf(stderr, "inflateInit error: %d\n", err);
   377 		return -1;
   378 	}
   379 
   380 	count = ntohl(rpm->basenames->count);
   381 	size = (unsigned long *) (rpm->pool + ntohl(rpm->filesizes->offset));
   382 	index = (unsigned long *) (rpm->pool + ntohl(rpm->dirindexes->offset));
   383 	mode = (unsigned short *) (rpm->pool + ntohl(rpm->filemodes->offset));
   384 	name = rpm->pool + ntohl(rpm->basenames->offset);
   385 	for (i = 0; i < count; i++) {
   386 		dir = rpm->dirs[ntohl(*index)];
   387 		snprintf(buffer, sizeof buffer, "%s%s", dir, name);
   388 
   389 		stream.next_out = payload;
   390 		/* Plus two for the leading '.' and the terminating NUL. */
   391 		stream.avail_out =
   392 			ALIGN(sizeof *header + strlen(buffer) + 2, 4);
   393 		err = inflate(&stream, Z_SYNC_FLUSH);
   394 		if (err != Z_OK) {
   395 			fprintf(stderr, "inflate error: %d\n", err);
   396 			return -1;
   397 		}
   398 	    
   399 		header = (struct cpio_file_header *) payload;
   400 
   401 		/* FIXME: Figure out if it's a symlink, device file,
   402 		 * directorys or whatever.  Maybe do this upfront. */
   403 		if (create_path(root, dir, name, ntohs(*mode), &fd) < 0)
   404 			return -1;
   405 		if (ntohs(*mode) >> 12 == XDIR)
   406 			rest = 0;
   407 		else
   408 			rest = ntohl(*size);
   409 
   410 		while (rest > 0) {
   411 			if (ALIGN(rest, 4) > sizeof payload)
   412 				length = sizeof payload;
   413 			else
   414 				length = rest;
   415 			stream.next_out = payload;
   416 			stream.avail_out = ALIGN(length, 4);
   417 			err = inflate(&stream, Z_SYNC_FLUSH);
   418 			if (err != Z_OK && err != Z_STREAM_END) {
   419 				fprintf(stderr,
   420 					"inflate error: %d (%m)\n", err);
   421 				return -1;
   422 			}
   423 			rest -= length;
   424 			stream.next_out = payload;
   425 			while (length > 0) {
   426 				written = write(fd, stream.next_out, length);
   427 				if (written < 0) {
   428 					fprintf(stderr, "write error: %m\n");
   429 					return -1;
   430 				}
   431 				length -= written;
   432 			}
   433 		}
   434 		if (fd > 0 && close(fd) < 0) {
   435 			fprintf(stderr, "failed to close \"%s/%s%s\": %m\n",
   436 				root, dir, name);
   437 			return -1;
   438 		}
   439 		name += strlen(name) + 1;
   440 		index++;
   441 		size++;
   442 		mode++;
   443 	}
   444 
   445 	err = inflateEnd(&stream);
   446 
   447 	if (err != Z_OK) {
   448 		fprintf(stderr, "inflateEnd error: %d\n", err);
   449 		return -1;
   450 	}	    
   451 
   452 	return 0;
   453 }
   454 
   455 int
   456 razor_rpm_close(struct razor_rpm *rpm)
   457 {
   458 	int err;
   459 
   460 	free(rpm->dirs);
   461 	err = munmap(rpm->map, rpm->size);
   462 	free(rpm);
   463 
   464 	return err;
   465 }
   466 
   467 int
   468 razor_importer_add_rpm(struct razor_importer *importer, struct razor_rpm *rpm)
   469 {
   470 	razor_importer_begin_package(importer,
   471 				     rpm->pool + ntohl(rpm->name->offset),
   472 				     rpm->pool + ntohl(rpm->version->offset));
   473 
   474 	import_properties(importer, &rpm->requires,
   475 			  rpm->pool, RAZOR_PROPERTY_REQUIRES);
   476 	import_properties(importer, &rpm->provides,
   477 			  rpm->pool, RAZOR_PROPERTY_PROVIDES);
   478 	import_properties(importer, &rpm->conflicts,
   479 			  rpm->pool, RAZOR_PROPERTY_CONFLICTS);
   480 	import_properties(importer, &rpm->obsoletes,
   481 			  rpm->pool, RAZOR_PROPERTY_OBSOLETES);
   482 	import_files(importer, rpm);
   483 
   484 	razor_importer_finish_package(importer);
   485 
   486 	return 0;
   487 }