/*
 * This file handles compression aspects of crash dump files
 * for ia64 based systems.  Most of this is taken from the
 * IRIX compression code, with exceptions to how the index
 * is created, because the file format is different with Linux.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <klib.h>
#include <asm/kl_cmp.h>
#include <zlib.h>

static int cmp_debug = 0;
static int cache_count = 0;
static int cache_water_mark = CMP_HIGH_WATER_MARK, cache_flags = CMP_VM_CACHED;
static ptableentry *cache_head, *cache_tail, **page_table;
static ptableindex **page_index;  /* global page index */
static dump_header_t dump_header; /* dump header for compression library */

static void __cmppinsert(paddr_t in_addr, char *buffer, int nbytes, int flags);
static int __cmppget(int fd, paddr_t in_addr, char *buffer,
	unsigned int nbytes, unsigned int flags);
static ptableindex *__cmppindex(paddr_t in_addr);
static int __cmppread(int fd, paddr_t in_addr, char *buffer,
	unsigned int nbytes, unsigned int flags);
static void __cmpsaveindex(char *filename, int flags);
static int __cmpuncompress_page_rle(unsigned char *cbuf, unsigned char *ucbuf,
	int flags, kaddr_t blk_size, int *new_size);
static int __cmpuncompress_page_gzip(unsigned char *cbuf, unsigned char *ucbuf,
	int flags, kaddr_t blk_size, int *new_size);
static void __cmpcleanindex(void);
static int __cmploadindex(char *indexname);
static paddr_t __cmpconvertaddr(dump_page_t *dp);
static paddr_t __cmpphashbase(paddr_t in_addr, int hash);
static int __cmpphash(paddr_t addr);
static int __cmpcheckpageheader(int fd, dump_page_t *dp);
static int __cmppindexcreate(int fd, char *indexname, int flags);
static int __cmpcheckheader(int fd,meminfo_t *mip);

int cmpinit(meminfo_t *mip, int fd, char *indexname, int flags);
int cmpreadmem(int fd, paddr_t in_addr, char *buffer,
        unsigned int nbytes, unsigned int flags);

/*
 * Name: kl_print_dump_header()
 * Func: Print out the dump header on the screen.  This is not
 *       the best way to do it, but it'll work for now.  This whole
 *       library should be changed to use kl_ syntax for non-static
 *       functions.
 */
void
kl_print_dump_header(FILE *ofp)
{
	fprintf(ofp,
		"System information:\n");
	fprintf(ofp,
		"\tSystemname: %s\n", dump_header.dh_utsname_sysname);
	fprintf(ofp,
		"\t  Nodename: %s\n", dump_header.dh_utsname_nodename);
	fprintf(ofp,
		"\t   Release: %s\n", dump_header.dh_utsname_release);
	fprintf(ofp,
		"\t   Version: %s\n", dump_header.dh_utsname_version);
	fprintf(ofp,
		"\t   Machine: %s\n", dump_header.dh_utsname_machine);
	fprintf(ofp,
		"\tDomainname: %s\n", dump_header.dh_utsname_domainname);
	fprintf(ofp,
		"Physical memory:\n");
	fprintf(ofp,
		"\tStart: 0x%"FMT64"x\n", dump_header.dh_memory_start);
	fprintf(ofp,
		"\t  End: 0x%"FMT64"x\n", dump_header.dh_memory_end);
	fprintf(ofp,
		"\t Size: %"FMT64"d\n", dump_header.dh_memory_size);
	fprintf(ofp,
		"Time of dump: %s\n", ctime(&(dump_header.dh_time.tv_sec)));
	fprintf(ofp,
		"Panic string: %s\n", dump_header.dh_panic_string);
	fprintf(ofp,
		"Number of hw pages in dump: %d\n", dump_header.dh_num_dump_pages);
	fprintf(ofp,
		"Kernel hw page size: %d\n", dump_header.dh_dump_page_size);
	fprintf(ofp,
		"Dump header version number: %d\n", dump_header.dh_version);
	fprintf(ofp,
		"Magic number: 0x%"FMT64"x\n", dump_header.dh_magic_number);
	fprintf(ofp,
		"Dump header size: %d\n", dump_header.dh_header_size);
	fprintf(ofp,
		"Dump level: 0x%d\n", dump_header.dh_dump_level);
}

/*
 * __cmppinsert()
 *
 * Insert a directory entry / buffer into a table
 *
 * This is so it can be hashed on later. Note that this will also
 * contain caching flags in the future.
 */
static void
__cmppinsert(paddr_t in_addr, char *buffer, int nbytes, int flags)
{
	int hash, thash;
	ptableentry *tmpptr, *tmpptr2;

	tmpptr = (ptableentry *)NULL;

	/* Since we know where the page is, and it is inserted, let's
	 * go ahead and insert it into the cache (if we should...)
	 */
	if (cache_flags == CMP_VM_CACHED) {
		if (cache_count > cache_water_mark) {

			if (cmp_debug) {
				fprintf(KL_ERRORFP,
					"__cmppinsert(): Cleaning excess "
					"page out of cache! (0x%"FMTPTR"x) [%d]"
					"...\n", in_addr, cache_count);
			}

			tmpptr = cache_head;
			if (tmpptr) {

				/* First, take the first block off the list.
				 * It's easier to take off the head (and a
				 * tad bit faster...)  Keep a copy of it,
				 * because we have to remove it from the
				 * regular set of blocks.  We also want to
				 * use its data space for later (why malloc()
				 * if we don't need to?)
				 */
				cache_head = cache_head->nextcache;
				if (cache_head) {
					cache_head->prevcache = (ptableentry *)NULL;
				}
				cache_count--;

				tmpptr->nextcache = (ptableentry *)NULL;
				tmpptr->prevcache = (ptableentry *)NULL;

				/* Next, take the block out of the pointer
				 * list, because it's not needed.
				 */
				if ((tmpptr->next == (ptableentry *)NULL) &&
					(tmpptr->prev != (ptableentry *)NULL)) {

					/* We are removing the last block on a list.
					 * Assuming it isn't the only block, just back up one.
					 */ 
					tmpptr->prev->next = (ptableentry *)NULL;
				} else if (tmpptr->prev == (ptableentry *)NULL) {

					/* We are at the head of a list.  In this case,
					 * we have to find the page_table hash entry,
					 * move it forward, reset pointers, etc.
					 */
					thash = __cmpphash(tmpptr->addr);
					if (page_table[thash] == tmpptr) {
						page_table[thash] = page_table[thash]->next;
						if (page_table[thash] != (ptableentry *)NULL) {
							page_table[thash]->prev = (ptableentry *)NULL;
						}
					} else {
						if (cmp_debug) {
							fprintf(KL_ERRORFP,
								"__cmppinsert(): hash table at head!\n");
						}
					}
				} else if ((tmpptr->next != (ptableentry *)NULL) &&
					(tmpptr->prev != (ptableentry *)NULL)) {

						/* We're in the middle of a list.  Just set our
						 * neighbor's pointers around us.
						 */
						tmpptr2 = tmpptr->next;
						tmpptr->prev->next = tmpptr->next;
						tmpptr2->prev = tmpptr->prev;
				}
				tmpptr->cached = 0;
			}
		}
	}

	/* At this point, we *might* have a valid buffer.  If we
	 * do, use it!  Then, stick it back into the cache and the
	 * hash table.
	 */
	if (tmpptr == (ptableentry *)NULL) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, 
				"__cmppinsert(): Malloc occurred! [%d]\n",
				cache_count);
		}
		tmpptr = (ptableentry *)malloc(sizeof(ptableentry));
		tmpptr->data = (char *)malloc(nbytes);
	}
	memcpy((void *)tmpptr->data, (const void *)buffer, nbytes);

	tmpptr->addr = __cmpphashbase(in_addr, 1);
	tmpptr->flags = flags;
	tmpptr->length = nbytes;
	tmpptr->next = (ptableentry *)NULL;
	tmpptr->prev = (ptableentry *)NULL;
	tmpptr->nextcache = (ptableentry *)NULL;
	tmpptr->prevcache = (ptableentry *)NULL;
	tmpptr->cached = 0;

	hash = __cmpphash(in_addr);

	/* Insert the page into the page table, as normal.
	 * We do this whether we are caching or not.
	 */
	if (page_table[hash] != (ptableentry *)NULL) {
		tmpptr->next = page_table[hash];
		page_table[hash]->prev = tmpptr;
		page_table[hash] = tmpptr;
	} else {
		page_table[hash] = tmpptr;
	}

	/* Now, if we are using a caching scheme, store the page
	 * at the end of the chain.
	 */
	if (cache_flags == CMP_VM_CACHED) {
		page_table[hash]->cached = 1;
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppinsert(): Inserting "
				"page into cache! (0x%"FMTPTR"x) "
				"[%d]...\n", in_addr, cache_count);
		}
		if (cache_tail != (ptableentry *)NULL) {
			cache_tail->nextcache = tmpptr;
			tmpptr->prevcache = cache_tail;
			cache_tail = cache_tail->nextcache;
		} else {
			cache_head = tmpptr;
			cache_tail = tmpptr;
			cache_head->nextcache = (ptableentry *)NULL;
			cache_head->prevcache = (ptableentry *)NULL;
		}
		cache_count++;
	}
}

/*
 * __cmppget()
 *
 * Try to get the page of data hashed out.  
 * 
 * This will search through the hash table looking for our page. If
 * we find it, and it is all we need, copy it into the buffer and
 * return. Otherwise, read the rest from __cmppread() again and return.
 */
static int
__cmppget(int fd, paddr_t in_addr, char *buffer,
	unsigned int nbytes, unsigned int flags)
{
	ptableentry *tmpptr;
	int hash;
	unsigned int offset = 0;
	unsigned int bytes_left = 0;

	/* First things first, snag the hash index for this item.
	 */
	hash = __cmpphash(in_addr);

	/* Now, see if the item exists in the list.  If the first
	 * pointer doesn't even exist, just return.
	 */
	if ((hash < 0) || (hash >= NUM_BUCKETS) || (!page_table[hash])) {
		return -1;
	}

	/* Otherwise, we've got a valid pointer.  Look for the hashed
	 * page in the table.
	 */
	tmpptr = page_table[hash];
	while (tmpptr) {
		if ((in_addr >= tmpptr->addr) &&
			(in_addr <= tmpptr->addr + dump_page_size)) {

				/* Check out if this page is on the cache list
				 * or not.  If it is, then go ahead and move it
				 * to the end of the list.  We only need to play
				 * with the cache list, we don't have to touch
				 * its position in the hash table.
				 */
				if ((cache_flags == CMP_VM_CACHED) && (tmpptr->cached)) {

					/* Move the page in the cache.  First, take it
					 * from where it is currently located.  Note that
					 * there is no reason to re-insert if we are
					 * already at the end of the list!
					 */
					if (tmpptr->nextcache != (ptableentry *)NULL) {

						/* See if we are at the head of the list.
						 */
						if (tmpptr->prevcache == (ptableentry *)NULL) {
							if (cache_head) {
								cache_head = cache_head->nextcache;
								if (cache_head) {
									cache_head->prevcache =
										(ptableentry *)NULL;
								}
							}
						} else {
							tmpptr->prevcache->nextcache = tmpptr->nextcache;
							tmpptr->nextcache->prevcache = tmpptr->prevcache;
						}
						tmpptr->nextcache = (ptableentry *)NULL;
						tmpptr->prevcache = (ptableentry *)NULL;

						/* Now, re-insert it at the end of the list.
						 */
						if (cache_tail != (ptableentry *)NULL) {
							cache_tail->nextcache = tmpptr;
							tmpptr->prevcache = cache_tail;
							cache_tail = cache_tail->nextcache;
						} else {
							cache_head = tmpptr;
							cache_tail = tmpptr;
							cache_head->nextcache = (ptableentry *)NULL;
							cache_head->prevcache = (ptableentry *)NULL;
						}
					}
				}

				/* If we have the buffer where the address is located,
				 * determine how much of it needs to be copied, and
				 * from which offset.
				 */
				offset = (int)(in_addr - tmpptr->addr);
				bytes_left = (int)((tmpptr->addr + dump_page_size) - in_addr);
				if (in_addr + nbytes > tmpptr->addr + dump_page_size) {
					if (cmp_debug) {
						fprintf(KL_ERRORFP, "__cmppget(): reading more data "
							"(bytes_left = %d, nbytes = %d)\n",
							bytes_left, nbytes);
					}
					memcpy((void *)buffer,
						(const void *)(tmpptr->data + offset),
						bytes_left);
					return(__cmppread(fd, tmpptr->addr + dump_page_size,
						buffer + bytes_left, nbytes - bytes_left,
						flags));
				}
				if (cmp_debug) {
					fprintf(KL_ERRORFP, "__cmppget(): copying page of data "
						"(nbytes = %d, offset = %d, in_addr = 0x%"FMTPTR"x)\n",
						nbytes, offset, in_addr);
				}
				memcpy((void *)buffer,
					(const void *)(tmpptr->data + offset),
					nbytes);
				return 1;
		}
		tmpptr = tmpptr->next;
	}
	return -1;
}

/*
 * __cmppindex()
 * 
 * This function is responsible for grabbing a page out of our index
 * list, and returning the directory entry header at that location.
 */
static ptableindex *
__cmppindex(paddr_t in_addr)
{
	ptableindex *tmpptr;
	int hash;

	/* Grab the hash value based on the address passed in,
	 * after the address is processed based on a 0x1000 page.
	 */
	hash = __cmpphash(in_addr);

	/* Now, see if the page exists in the page table index.
	 * We already know it isn't in the page table itself,
	 * but if the page doesn't exist in the core dump, we
	 * want to return -1.
	 */
	if (cmp_debug) {
		fprintf(KL_ERRORFP, "__cmppindex(): hash = %6d, "
			"addr = 0x%"FMTPTR"x\n", hash, in_addr);
	}
	tmpptr = page_index[hash];
	while (tmpptr != (ptableindex *)NULL) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppindex(): addr = "
				"0x%"FMTPTR"x, tmpptr->addr = 0x%"FMTPTR"x\n", 
				in_addr, tmpptr->addr);
		}
		if ((in_addr >= tmpptr->addr) &&
			(in_addr <= tmpptr->addr + dump_page_size)) {

			/* We found the address we want.  Return the
			 * directory entry pointer.
			 */
			return (tmpptr);
		}
		tmpptr = tmpptr->next;
	}
	return ((ptableindex *)NULL);
}

/*
 * __cmppread()
 *
 * Read a page of a compressed core dump.
 */
static int
__cmppread(int fd, paddr_t in_addr, char *buffer,
	unsigned int nbytes, unsigned int flags)
{
	char *ptr;
	static char *compr_page, *uncompr_page;
	int tmpflags, new_size = 0, len;
	static int first_time = 0;
	ptableindex *pindexitem;

	/* Print out a message that we are searching.
	 */
	if (cmp_debug) {
		fprintf(KL_ERRORFP, 
			"__cmppread(): initiating search for 0x%"FMTPTR"x\n", 
				in_addr);
	}

	/* If we haven't created these pages yet, do so now.  This
	 * changes because of dump_page_size.
	 */
	if (first_time == 0) {
		if ((compr_page = (char *)malloc(dump_page_size)) == (char *)NULL) {
			fprintf(KL_ERRORFP, "__cmpuncompress_page: out of memory!\n");
			return (-1);
		}

		if ((uncompr_page = (char *)malloc(dump_page_size)) == (char *)NULL) {
			fprintf(KL_ERRORFP, "__cmpuncompress_page: out of memory!\n");
			return (-1);
		}
		first_time = 1;
	}

	/* First things first: try to go through our dump page hash table
	 * and get a dump page of data.  It might not contain the entire
	 * set of data that we are looking for, but it will grab
	 * any additional pages we might need.
	 */
	if (__cmppget(fd, in_addr, buffer, nbytes, flags) >= 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, 
				"__cmppread(): found the item in the hash table!\n");
		}
		return 1;
	}

	/* If we get to here, we didn't find the dump page in our hash table.
	 * So, search through the compressed core dump index to see if
	 * the address exists in the core dump's list.
	 */
	if ((pindexitem = __cmppindex(in_addr)) == (ptableindex *)NULL) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, 
				"__cmppread(): dump page not found! "
				"(0x%"FMTPTR"x)\n", in_addr);
		}
		bzero(buffer, nbytes);
		return 0;
	} else {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, 
				"__cmppread(): found the dump page in the page index!\n");
		}
	}

	/* If we get to here, we have found the page we want.
	 * So, let's seek to the location and read it out.
	 */
	if (lseek(fd, pindexitem->coreaddr, SEEK_SET) < 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppread(): couldn't seek "
				"to dump page 0x%"FMTPTR"x\n", in_addr);
		}
		return -1;
	}

	/* Now that we're here, let's grab the page.
	 */
	tmpflags = pindexitem->dir.dp_flags;
	len = read(fd, (void *)compr_page, pindexitem->dir.dp_size);
	if (len != pindexitem->dir.dp_size) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppread(): warning: "
				"uncompressed file may be incomplete.\n");
		}
		return 1;
	}

	/* If this is a raw page, go ahead and read in the whole thing.
	 * Otherwise, uncompress it and read it in.
	 */
	if (tmpflags & DUMP_DH_RAW) {
		if (len != dump_page_size) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmppread(): warning: RAW page isn't "
					"the right size (%d)!\n", len);
			}
			return -1;
		}

		/* Set the pointer to the appropriate char * data buffer, and
		 * set the size appropriately.
		 */
		ptr = compr_page;
		new_size = dump_page_size;
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "0x%"FMTPTR"x: %d -> %d RAW, "
				"writing %d bytes\n", pindexitem->addr, 
				len, new_size, dump_page_size);
		}
	} else {

		/* Uncompress the dump page.
		 */
		switch (dump_header.dh_dump_compress) {
			case DUMP_COMPRESS_RLE:
				if (!__cmpuncompress_page_rle(
					(unsigned char*)compr_page, 
					(unsigned char*)uncompr_page,
					tmpflags, len, &new_size)) {
						fprintf(KL_ERRORFP,
							"__cmppread(): "
							"invalid page "
							"decompression!\n");
						return (-1);
				}
				break;

			case DUMP_COMPRESS_GZIP:
				if (!__cmpuncompress_page_gzip(
					(unsigned char*)compr_page, 
					(unsigned char*)uncompr_page,
					tmpflags, len, &new_size)) {
						fprintf(KL_ERRORFP,
							"__cmppread(): "
							"invalid page "
							"decompression!\n");
						return (-1);
				}
				break;

			default:
				fprintf(KL_ERRORFP, "__cmppread(): "
					"unknown compression type!\n");
				return (-1);
		}

		/* Check to make sure that the length is right.  If the length
		 * is not the same as dump_page_size, something's wrong.
		 */
		if (new_size != dump_page_size) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmppread(): warning: COMPRESSED page "
					"isn't the right size (%d)!\n", new_size);
			}
			return -1;
		}

		/* Set the pointer to the appropriate char * data buffer.
		 */
		ptr = uncompr_page;
		if (cmp_debug) {
			fprintf(KL_ERRORFP, 
				"0x%"FMTPTR"x: %d -> %d COMPRESSED, "
				"writing %d bytes\n",
				pindexitem->addr, len, new_size, dump_page_size);
		}
	}

	/* Now, insert the dump page into the hash table.
	 */
	__cmppinsert(in_addr, ptr, new_size, tmpflags);

	/* Officially grab the page now that we have
	 * placed it into the table.
	 */
	if (__cmppget(fd, in_addr, buffer, nbytes, flags) >= 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppread(): found the item in the hash "
				"table second time!\n");
		}
		return 1;
	}

	/* Return that everything succeeded.
	 */
	return 1;
}

/*
 * cmpreadmem() -- Read the compressed core dump 
 * 
 *   The core dump is read through to the size of the buffer. We will
 *   call __cmppread() as appropriate to get the page of data as we need
 *   it.
 */
int
cmpreadmem(int fd, paddr_t in_addr, char *buffer,
	unsigned int nbytes, unsigned int flags)
{
	int offset = 0;

	/* First, see if we want a size greater than dump_page_size.
	 */
	if (nbytes <= dump_page_size) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "cmpreadmem(): %d bytes, "
				"0x%"FMTPTR"x (just a page)\n", 
				nbytes, in_addr);
		}
		return (__cmppread(fd, in_addr, buffer, nbytes, flags)); 
	} else if (nbytes > dump_page_size) {

		/* Otherwise, just read in the data dump_page_size at a time.
		 */
		while (offset < nbytes) {
			if ((nbytes - offset) <= dump_page_size) {

				/* Read the leftovers.
				 */
				if (cmp_debug) {
					fprintf(KL_ERRORFP, "cmpreadmem(): reading %d bytes, "
						"0x%"FMTPTR"x (leftovers)\n",
						nbytes - offset, in_addr + offset);
				}
				return (__cmppread(fd, in_addr + offset,
					buffer + offset, nbytes - offset, flags));
			} else {

				/* Read a page in at a time.
				 */
				if (cmp_debug) {
					fprintf(KL_ERRORFP, "cmpreadmem(): "
						"reading %d bytes, "
						"0x%"FMTPTR"x (a new page)\n", 
						dump_page_size, in_addr + offset);
				}
				if (__cmppread(fd, in_addr + offset,
					buffer + offset, dump_page_size, flags) < 0) {
					return -1;
				}
			}

			/* Increment the offset by dump_page_size.
			 */
			offset += dump_page_size;
		}
		/* NOT REACHED */
		return 1;
	}
	/* NOT REACHED */
	return 1;
}

/*
 * __cmpsaveindex()
 *
 * Save the index for the compressed core dump.
 */
static void
__cmpsaveindex(char *filename, int flags)
{
	int i, ifd;
	ptableindex *tmpptr;
	dump_index_t dump_index;

	/* if flags are set here, we're running a report -- don't save */
	if (flags) {
		return;
	}

	if (cmp_debug) {
		fprintf(KL_ERRORFP, "\nAttempting to save index \"%s\" ... ",
			filename);
	}

	if ((ifd = open(filename, O_RDWR|O_CREAT, 0644)) < 0) {
		fprintf(KL_ERRORFP,
			"\n__cmpsaveindex(): open() of index \"%s\" failed!\n",
			filename);
		return;
	}

	/* create the dump index */
	dump_index.magic_number = DUMP_INDEX_MAGIC;
	dump_index.version_number = DUMP_INDEX_VERSION;
	dump_index.timebuf.tv_sec = dump_header.dh_time.tv_sec;
	dump_index.timebuf.tv_usec = dump_header.dh_time.tv_usec;

	/* write the index header */
	if (write(ifd, (const void *)&dump_index,
		sizeof(dump_index_t)) < 0) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "\n__cmpsaveindex(): "
					"write() of index \"%s\" failed!\n",
					filename);
			}
			(void)close(ifd);
			unlink(filename);
			return;
	}

	for (i = 0; i < NUM_BUCKETS; i++) {
		tmpptr = page_index[i];
		while (tmpptr) {
			if (write(ifd, (const void *)tmpptr,
				sizeof(ptableindex)) != sizeof(ptableindex)) {
					fprintf(KL_ERRORFP,
						"\n__cmpsaveindex(): save of "
						"index \"%s\" failed!\n",
						filename);
					(void)close(ifd);
					unlink(filename);
					return;
			}
			tmpptr = tmpptr->next;
		}
	}
	if (cmp_debug) {
		fprintf(KL_ERRORFP, "complete.\n");
	}
	(void)close(ifd);
}

/*
 * __cmpuncompress_page_rle() -- Uncompress a buffer of data using
 *                               the run length encoding mechanism.
 *
 * Return the flags, size, and the number of bytes read. It also
 * verifies the compression type.
 */
static int
__cmpuncompress_page_rle(unsigned char *cbuf, unsigned char *ucbuf,
	int flags, kaddr_t blk_size, int *new_size)
{
	int i;
	unsigned char value, count, cur_byte;
	kaddr_t ri, wi;

	/* initialize the read / write indices */
	ri = wi = 0;

	/* otherwise decompress using run length encoding */
	while(ri < blk_size) {
		cur_byte = cbuf[ri++];
		if (cur_byte == 0) {
			count = cbuf[ri++];
			if (count == 0) {
				ucbuf[wi++] = 0;
			} else {
				value = cbuf[ri++];
				for (i = 0; i <= count; i++) {
					ucbuf[wi++] = value;
				}
			}
		} else {
			ucbuf[wi++] = cur_byte;
		}

		/* if our write index is beyond the page size, exit out */
		if (wi > dump_page_size) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP,
					"ERROR: Attempted to decompress beyond"
					" page boundaries: file corrupted!\n");
			}
			return (0);
		}
	}

	/* set return size to be equal to uncompressed size (in bytes) */
	*new_size = wi;
	return 1;
}

/*
 * __cmpuncompress_page_gzip() -- Uncompress a buffer of data using
 *                                the uncompress() gzip function.
 *
 * Return the flags, size, and the number of bytes read. It also
 * verifies the compression type.
 */
static int
__cmpuncompress_page_gzip(unsigned char *cbuf, unsigned char *ucbuf,
	int flags, kaddr_t blk_size, int *new_size)
{
	ulong retlen = dump_page_size;

	/* use the gzip uncompress() function on a per-page basis */
	switch (uncompress(ucbuf, &retlen, cbuf, blk_size)) {
		case Z_OK:
			*new_size = retlen;
			if (retlen != dump_page_size) {
				fprintf(KL_ERRORFP,
					"__cmpuncompress_page_gzip(): "
					"returned invalid page length!\n");
				return (0);
			}
			break;

		case Z_MEM_ERROR:
			fprintf(KL_ERRORFP,
				"__cmpuncompress_page_gzip(): Z_MEM_ERROR "
				"(not enough memory)!\n");
			return (0);

		case Z_BUF_ERROR:
			fprintf(KL_ERRORFP,
				"__cmpuncompress_page_gzip(): Z_BUF_ERROR "
				"(not enough room in buffer output)!\n");
			return (0);

		case Z_DATA_ERROR:
			fprintf(KL_ERRORFP,
				"__cmpuncompress_page_gzip(): Z_DATA_ERROR "
				"(input data corrupted)!\n");
			return (0);

		default:
			/* unknown case? */
			return (0);
	}

	/* return success */
	return (1);
}

/* 
 * __cmpcleanindex()
 *
 * Clean out the current compression index data structures.
 */
static void
__cmpcleanindex(void)
{
	int i;
	ptableindex *ptr1, *ptr2;

	for (i = 0; i < NUM_BUCKETS; i++) {
		if (page_index[i]) {
			ptr1 = page_index[i];
			while (ptr1) {
				ptr2 = ptr1;
				ptr1 = ptr1->next;
				free(ptr2);
			}
		}
	}
}

/*
 * __cmploadindex()
 *
 * Load the crash dump index from disk -- this is done so we
 * can cache the page contents and start up quickly, instead
 * of reading through the entire crash dump again.
 */
static int
__cmploadindex(char *indexname)
{
	int ifd, result;
	ptableindex *ptr1, *ptr2;
	dump_index_t dump_index;

	/* if we aren't given an index name, bail */
	if ((!indexname) || (indexname[0] == '\0')) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"no index filename given!\n");
		}
		return (-1);
	}

	/* open up the file if possible */
	if ((ifd = open(indexname, O_RDONLY)) < 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"cannot open() index file [%d]: %s!\n",
				errno, strerror(errno));
		}
		return (-1);
	}

	/* grab the index header */
	if (read(ifd, (void *)&dump_index,
		sizeof(dump_index_t)) != sizeof(dump_index_t)) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmploadindex(): "
					"read() on index file failed!\n");
			}
			(void)close(ifd);
			return (-1);
	}

	/* make sure the dump index magic is valid */
	if (dump_index.magic_number != DUMP_INDEX_MAGIC) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"DUMP_INDEX_MAGIC in index file is wrong!\n");
		}
		(void)close(ifd);
		return (-1);
	}

	/* make sure the version number is up to date */
	if (dump_index.version_number < DUMP_INDEX_VERSION) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"DUMP_INDEX_VERSION in index file "
				"out of date!\n");
		}
		(void)close(ifd);
		return (-1);
	}

	/* validate the seconds */
	if (dump_index.timebuf.tv_sec != dump_header.dh_time.tv_sec) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"time (seconds) in index file wrong!\n");
		}
		(void)close(ifd);
		return (-1);
	}

	/* validate the microseconds (!) */
	if (dump_index.timebuf.tv_usec != dump_header.dh_time.tv_usec) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmploadindex(): "
				"time (microseconds) in index file wrong!\n");
		}
		(void)close(ifd);
		return (-1);
	}

	if (cmp_debug) {
		fprintf(KL_ERRORFP,
			"Attempting to load previous "
			"index \"%s\" ... ", indexname);
	}

	/* okee, we're good to go -- try to load the index */
	result = 1;
	while (result) {
		ptr1 = (ptableindex *)malloc(sizeof(ptableindex));
		result = read(ifd, (void *)ptr1, sizeof(ptableindex));
		if (!result) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "complete.\n");
			}
			(void)close(ifd);
			return (1);
		} else if (result < 0) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmploadindex(): "
					"index file read() failed!\n");
			}
			(void)close(ifd);
			return (-1);
		} else if (result != sizeof(ptableindex)) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmploadindex(): "
					"index file read() short!\n");
			}
			(void)close(ifd);
			return (-1);
		}
		ptr1->next = (ptableindex *)NULL;
		ptr2 = page_index[ptr1->hash];
		page_index[ptr1->hash] = ptr1;
		page_index[ptr1->hash]->next = ptr2;
	}

	if (cmp_debug) {
		fprintf(KL_ERRORFP,
			"__cmploadindex(): read() past end of index file!\n");
	}
	(void)close(ifd);
	return (-1);
}

/*
 * __cmpconvertaddr()
 *
 * Convert an address over to something that works for the compressed
 * page indexing scheme.
 */
static paddr_t
__cmpconvertaddr(dump_page_t *dp)
{
	paddr_t paddr;

	paddr = (paddr_t)dp->dp_address;
	return (paddr);
}

/*
 * cmpphashbase()
 *
 * Compute the hash algorithm based on the 64 vs. 32 bit field.  The
 * hash flag tells us whether we are using the return value for hashing,
 * or for page offset data.
 */
static paddr_t
__cmpphashbase(paddr_t in_addr, int hash)
{
   in_addr &= ~((((paddr_t)1) << dump_page_shift) -1);
   if (!hash)
	in_addr >>= dump_page_shift;
   return in_addr;
}

/*
 * __cmpphash()
 *
 * A hash function to determine the bucket for a given address.
 */
static int
__cmpphash(paddr_t addr)
{
	return (__cmpphashbase(addr, 0) % NUM_BUCKETS);
}

/*
 * __cmpcheckpageheader()
 *
 * Check the page header from the crash dump.  Pretty generic.
 */
static int
__cmpcheckpageheader(int fd, dump_page_t *dp)
{
	/* see if this is the end of the dump */
	if (dp->dp_flags & DUMP_DH_END) {
		return (0);
	}
	return (1);
}

/*
 * __cmppindexcreate()
 *
 * Create a new compression index file.  This handles the new file
 * format different from IRIX.
 */
static int
__cmppindexcreate(int fd, char *indexname, int flags)
{
	int i, counter = 0, psize = sizeof(dump_page_t);
	off_t cur_addr = (off_t)dump_header_size;
	ptableindex *ptr1, *ptr2;

	if (cmp_debug) {
		fprintf(KL_ERRORFP, "cmppindexcreate(): Number of pages in "
			"dump: %d\n", dump_header.dh_num_dump_pages);
		fprintf(KL_ERRORFP, "cmppindexcreate(): Dump size in "
			"dump: %d\n", dump_header.dh_dump_page_size);
	}

	/* lseek() to the right location ... */
	if (lseek(fd, cur_addr, SEEK_SET) < 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmppindexcreate(): lseek() "
				"to page location failed!\n");
		}
		return (-1);
	}

	/* start reading out blocks of pages and storing them ... */
	while (1) {
		/* allocate a page table index point */
		ptr1 = (ptableindex *)calloc(1, sizeof(ptableindex));
		if (!ptr1) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmppindexcreate(): "
					"calloc() of page index failed!\n");
			}
			return (-1);
		}

		/* get the next block */
		if ((i = read(fd, (char *)&(ptr1->dir), psize)) < 0) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmppindexcreate(): "
					"read() of page index failed!\n");
			}
			free(ptr1);
			return (-1);
		}

		/* did we get a full dump page? */
		if (i != psize) {
			free(ptr1);
			return (1);
		}

		/* increment the current address */
		cur_addr += psize;

		/* get the page header */
		if (!(__cmpcheckpageheader(fd, &(ptr1->dir)))) {
			free(ptr1);
			return (1);
		}

		/* insert the page table index */
		ptr1->addr = __cmpconvertaddr(&(ptr1->dir));
		ptr1->coreaddr = cur_addr;
		ptr1->hash = __cmpphash(ptr1->addr);
		ptr1->next = (ptableindex *)NULL;

		/* insert the pointer into the table */
		ptr2 = page_index[ptr1->hash];
		page_index[ptr1->hash] = ptr1;
		page_index[ptr1->hash]->next = ptr2;

		if (cmp_debug == 2) {
			fprintf(KL_ERRORFP,
				"__cmppindexcreate(): "
				"addr = 0x%"FMTPTR"x, hash = %-6d, "
				"counter = %-6d, cur_addr = 0x%"FMTPTR"x\n",
				ptr1->addr, ptr1->hash, counter, cur_addr);
		}

		if (!flags && !(counter & 0xfff)) {
			fprintf(KL_ERRORFP, ".");
		}

		/* seek to the next block */
		if (lseek(fd, ptr1->dir.dp_size, SEEK_CUR) < 0) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP, "__cmppindexcreate(): "
					"lseek() to next page "
					"index failed! errno=%d\n", errno);
			}
			free(ptr1);
			return (-1);
		}

		/* increment the buffer offset and counter */
		cur_addr += ptr1->dir.dp_size;
		counter++;
	}

	/* NOTREACHED */
	return (1);
}

/*
 * __cmpcheckheader()
 *
 * Make sure the dump header is valid.  We store a static copy here
 * in the compression library to handle our own internal needs for
 * verifying the index file, knowing the size of the header, stat
 * information, etc.
 *
 * NOTE: The fd passed in is open already by the time we get here.
 */
static int
__cmpcheckheader(int fd, meminfo_t *mip)
{
	/* seek to the beginning of the file */
	if (lseek(fd, 0, SEEK_SET) < 0) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmpcheckheader(): failure "
				"to lseek() to beginning of dump!\n");
		}
		return (-1);
	}

	/* read the dump header out for checking purposes */
	if (read(fd, (char *)&dump_header, sizeof(dump_header_t)) !=
		sizeof(dump_header_t)) {
			if (cmp_debug) {
				fprintf(KL_ERRORFP,
					"__cmpcheckheader(): failure "
					"to read() dump header!\n");
			}
			return (-1);
	}

	/* make sure the MAGIC_NUMBER is valid */
        if (dump_header.dh_magic_number == DUMP_MAGIC_S390){
                mip->core_type = s390_core;
        }
	else if ((dump_header.dh_magic_number != DUMP_MAGIC_NUMBER) &&
		(dump_header.dh_magic_number != DUMP_MAGIC_LIVE)) {
		if (cmp_debug) {
			fprintf(KL_ERRORFP, "__cmpcheckheader(): "
				"DUMP_MAGIC_NUMBER in dump header - WRONG!\n");
		}
		return (-1);
	}

	/* if the dump has <= 1 page, return 0 */
	if (dump_header.dh_num_dump_pages <= 1) {
		return (0);
	}
	
	/* return success */
	return (1);
}

/*
 * cmpinit()
 *
 * Initialize the compression code.  This will open the crash
 * dump, read in the dump header and the chunks of pages, and
 * store them into our hash table.
 */
int
cmpinit(meminfo_t *mip, int fd, char *indexname, int flags)
{
	int mysize = 0, i = 0;
	char iname[BUFSIZ], *strtmp;

	/* configure the page entry tables */
	page_table = (ptableentry **)malloc((NUM_BUCKETS+1) *
		sizeof(ptableentry *));
	if (page_table == (ptableentry **)NULL) {
		fprintf(KL_ERRORFP, "Page table allocation failure!  "
			"Exiting!\n");
		exit(1);
	}

	/* configure the page index tables */
	page_index = (ptableindex **)malloc((NUM_BUCKETS+1) *
		sizeof(ptableindex *));
	if (page_index == (ptableindex **)NULL) {
		fprintf(KL_ERRORFP, "Page index allocation failure!  "
			"Exiting!\n");
		exit(1);
	}

	/* make sure the header is valid */
	mysize = __cmpcheckheader(fd,mip);
	if (mysize < 0) {
		KL_ERROR = KLE_INVALID_DUMP_HEADER;
		return (-1);
	} else if (!mysize) {
		KL_ERROR = KLE_DUMP_HEADER_ONLY;
		return (-1);
	}

        if(mip->core_type == s390_core){
                /* no initialization needed */
                return 1;
        }

	/* see if we're given a filename */
	memset(iname, 0, BUFSIZ);
	if ((!indexname) || (indexname[0] == '\0')) {
		/* rip up the name */
		strtmp = (char *)strtok(mip->dump, ".");
		if (!strtmp) {
			sprintf(iname, "index");
		} else {
			strtmp = (char *)strtok(NULL, ".");
			if (!strtmp) {
				sprintf(iname, "index");
			} else {
				/* make sure we have a valid number */
				mysize = 0;
				while ((strtmp[i] >= '0') &&
					(strtmp[i] <= '9')) {
						mysize *= 10;
						mysize += 
							(int)(strtmp[i] - '0');
						i++;
				}
			}
			if (dump_header.dh_magic_number == DUMP_MAGIC_LIVE) {
				sprintf(iname, "live_index.%d", mysize);
			} else {
				sprintf(iname, "index.%d", mysize);
			}
		}
	} else {
		strncpy(iname, indexname, strlen(indexname));
	}

	/* try to load the index from disk */
	if (__cmploadindex(iname) < 0) {

		/* no disk image -- clean the current one */
		__cmpcleanindex();

		/* create a new index */
		if (__cmppindexcreate(fd, iname, flags) < 0) {

			/* index couldn't be created -- bail! */
			KL_ERROR = KLE_DUMP_INDEX_CREATION;
			return (-1);
		}

		/* save the current index created */
		__cmpsaveindex(iname, flags);
	}

	/* return success */
	return (1);
}
