/*
 * Module for generating compressed system crash dumps from live 
 * system memory. Does not require that the kernel be built with 
 * CONFIG_VMDUMP defined. This module was adapted from kernel module
 *
 * Any changes to the kernel vmdump.c  module should also be 
 * reflected here.
 *
 * Copyright 2000 Silicon Graphics, Inc. All rights reserved.
 */
#include <lcrash.h>

/* static variables 
 */
static dump_header_t dump_header;
static dump_header_asm_t dump_header_asm;
static char *dpcpage = NULL;
static void *dump_page_buf = NULL;

/* Forward declarations
 */
static int init_vmdump_header(int);

/*
 * init_live_vmdump()
 */
static int
init_live_vmdump(int dump_compress_pages)
{
	if (!dpcpage) {
		dpcpage = (char*)kl_alloc_block(dump_page_size, K_PERM);
	}

	/* set up one-time dump header values 
	 */
	if (init_vmdump_header(dump_compress_pages)) {
		fprintf(KL_ERRORFP, "init_vmdump_header() failed!\n");
		return(1);
	}
	return(0);
}

#ifdef NOT_USED
/*
 * Name: vmdump_open()
 * Func: Open the vmdump file
 */
static void
vmdump_open(FILE *dump_file, uint64_t memory_size)
{
	/* set the memory size now 
	 */
	dump_header.dh_memory_size = memory_size;
	return;
}
#endif

/*
 * Name: vmdump_close()
 * Func: Close the vmdump file.
 */
static void
vmdump_close(FILE *dump_file)
{
	if (dump_file) {
		fclose(dump_file);
	}
}

/*
 * Name: vmdump_page_valid()
 * Func: Make sure the page address passed in is valid in the physical
 *       address space.  Right now, we validate all pages.
 */
static int
vmdump_page_valid(uint64_t mem_offset)
{
	if (kl_valid_physmem(mem_offset)) {
		return(1);
	} else {
		return(0);
	}
}

static uint64_t
vmdump_next_valid_page(uint64_t mem_offset)
{
	uint64_t vaddr;

	vaddr = kl_next_valid_physaddr(mem_offset);
	vaddr += KL_PAGE_OFFSET;
	return(vaddr);
}

/*
 * Name: vmdump_write()
 * Func: Write out memory to vmdump file.
 */
static int
vmdump_write(FILE *dfp, char *buf, int len)
{
	if (fwrite(buf, len, 1, dfp) != 1) {
		return(1);
	} else {
		return(0);
	}
}

/*
 * Name: vmdump_compress()
 * Func: Compress a dump_page_size page down to something more reasonable,
 *       if possible.  This is the same routine we use in IRIX.
 *
 *       XXX - this needs to be changed for greater than 32-bit systems.
 */
static uint32_t
vmdump_compress(char *old_addr, char *new, int size)
{
	int ri, wi, count = 0;
	u_char value = 0, cur_byte;

	/*
	 * If the block should happen to "compress" to larger than the
	 * buffer size, allocate a larger one and change cur_buf_size.
	 */

	wi = ri = 0;

	while (ri < size) {
		if (!ri) {
			cur_byte = value = old_addr[ri];
			count = 0;
		} else {
			if (count == 255) {
				if (wi + 3 > size) {
					return size;
				}
				new[wi++] = 0;
				new[wi++] = count;
				new[wi++] = value;
				value = cur_byte = old_addr[ri];
				count = 0;
			} else { 
				if ((cur_byte = old_addr[ri]) == value) {
					count++;
				} else {
					if (count > 1) {
						if (wi + 3 > size) {
							return size;
						}
						new[wi++] = 0;
						new[wi++] = count;
						new[wi++] = value;
					} else if (count == 1) {
						if (value == 0) {
							if (wi + 3 > size) {
								return size;
							}
							new[wi++] = 0;
							new[wi++] = 1;
							new[wi++] = 0;
						} else {
							if (wi + 2 > size) {
								return size;
							}
							new[wi++] = value;
							new[wi++] = value;
						}
					} else { /* count == 0 */
						if (value == 0) {
							if (wi + 2 > size) {
								return size;
							}
							new[wi++] = value;
							new[wi++] = value;
						} else {
							if (wi + 1 > size) {
								return size;
							}
							new[wi++] = value;
						}
					} /* if count > 1 */

					value = cur_byte;
					count = 0;

				} /* if byte == value */

			} /* if count == 255 */

		} /* if ri == 0 */
		ri++;
	}
	if (count > 1) {
		if (wi + 3 > size) {
			return size;
		}
		new[wi++] = 0;
		new[wi++] = count;
		new[wi++] = value;
	} else if (count == 1) {
		if (value == 0) {
			if (wi + 3 > size)
				return size;
			new[wi++] = 0;
			new[wi++] = 1;
			new[wi++] = 0;
		} else {
			if (wi + 2 > size)
				return size;
			new[wi++] = value;
			new[wi++] = value;
		}
	} else { /* count == 0 */
		if (value == 0) {
			if (wi + 2 > size)
				return size;
			new[wi++] = value;
			new[wi++] = value;
		} else {
			if (wi + 1 > size)
				return size;
			new[wi++] = value;
		}
	} /* if count > 1 */

	value = cur_byte;
	count = 0;
	return wi;
}

/*
 * Name: init_vmdump_header()
 * Func: set the dump header initial values
 */
static int      
init_vmdump_header(int dump_compress_pages)
{
	char *utsname;
	int entry_size;
	unsigned long memory_size;
	syment_t *sp;

	if (!(sp = kl_lkup_symname("high_memory"))) {
		return(-1);
	}
	GET_BLOCK(sp->s_addr, sizeof(unsigned long), &memory_size);
	/* dump the entire physical memory */
	memory_size = (memory_size - KL_PAGE_OFFSET);

	if(!(utsname = get_utsname())){
		return(-1);
	}

	/* Need to fill in data that normally is initialized by
	 * the kernel, such as start and size of memory, etc.
	 *
	 * NB: Perhaps we should use KL_PAGE_OFFSET for
	 *     the dh_memory_start.
	 */
	dump_header.dh_memory_size = memory_size;
	dump_header.dh_memory_start = KL_PAGE_OFFSET;
	dump_header.dh_memory_end = KL_PAGE_OFFSET + memory_size;

	/*
	 * In the case where we're compressing the pages, mark it as such.
	 */
	if (dump_compress_pages) {
		dump_header.dh_dump_compress = DUMP_COMPRESS_RLE;
	}

	/* write dump header initial values 
	 */
	dump_header.dh_magic_number = DUMP_MAGIC_LIVE;
	dump_header.dh_version = DUMP_VERSION_NUMBER;
	dump_header.dh_header_size = sizeof(dump_header_t);
	dump_header.dh_dump_page_size = dump_page_size;
	gettimeofday(&dump_header.dh_time, NULL);

	if( UTSNAME_ENTRY_SZ <
	    (entry_size = kl_member_size(NEW_UTSNAME, "sysname")) ) {
		entry_size = UTSNAME_ENTRY_SZ;
	}

	memcpy((void *)dump_header.dh_utsname_sysname,
	       K_PTR(utsname, NEW_UTSNAME, "sysname"), entry_size);
	memcpy((void *)dump_header.dh_utsname_nodename,
	       K_PTR(utsname, NEW_UTSNAME, "nodename"), entry_size);
	memcpy((void *)dump_header.dh_utsname_release,
	       K_PTR(utsname, NEW_UTSNAME, "release"), entry_size);
	memcpy((void *)dump_header.dh_utsname_version,
	       K_PTR(utsname, NEW_UTSNAME, "version"), entry_size);
	memcpy((void *)dump_header.dh_utsname_machine,
	       K_PTR(utsname, NEW_UTSNAME, "machine"), entry_size);
	memcpy((void *)dump_header.dh_utsname_domainname,
	       K_PTR(utsname, NEW_UTSNAME, "domainname"), entry_size);

	dump_header_asm.dha_magic_number = DUMP_ASM_MAGIC_NUMBER;
	dump_header_asm.dha_version = DUMP_ASM_VERSION_NUMBER;
	dump_header_asm.dha_header_size = sizeof(dump_header_asm_t);

	/* make sure the dump header isn't TOO big 
	 */
	if ((sizeof(dump_header_t) + sizeof(dump_header_asm_t))
	    > dump_buffer_size) {
		fprintf(KL_ERRORFP, "init_vmdump_header(): combined "
			"headers larger than dump_buffer_size!\n");
                kl_free_block(utsname);
		return (-1);
	}

	kl_free_block(utsname);
	return(0);
}

/*
 * Name: write_vmdump_header()
 * Func: Write out the dump header to the dump device.  This is done
 *       in almost every case, unless the dump_level is 0.
 */
static int
write_vmdump_header(FILE *dfp)
{
	/* clear the dump page buffer 
	 */
	bzero(dump_page_buf, dump_buffer_size);

	/* copy the dump header directly into the dump page buffer 
	 */
	bcopy((const void *)&dump_header, (void *)dump_page_buf, 
		sizeof(dump_header_t));
	bcopy((const void *)&dump_header_asm, 
		(void *)(dump_page_buf + dump_header.dh_header_size),
		sizeof(dump_header_asm_t));

	/* Set the vmdump file pointer to the start of the file
	 */
	(void)fseek(dfp, 0L, SEEK_SET);

	/* write the header out to disk 
	 */
	if (vmdump_write(dfp, (char *)dump_page_buf, dump_buffer_size)) {
		return (-1);
	}

	/* Reset the vmdump file pointer back to the end of the file
	 */
	(void)fseek(dfp, 0L, SEEK_END);
	return (0);
}

/*
 * Name: live_dump()
 * Func: Architecture specific live system dumping routine.  This allows 
 *       various system types to come up with their own crash dump 
 *       implementation without stepping changing the basic process.
 */
void
live_dump(int dump_level, int dump_compress_pages)
{
	int ifd = -1, ofd = -1, count, bounds = 0;
	dump_page_t dp;
	uint64_t mem_loc;
	uint32_t buf_loc = 0;
	uint32_t cloop = 0, size, psize = sizeof(dump_page_t);
	FILE *dump_file;
	char *buf;
	char dname[128], mname[128], kname[128];

	/* if they don't want to dump live system memory, bail out 
	 */
	if (dump_level == DUMP_LEVEL_NONE) {
		return;
	}

	/* Make sure that this is a live system
	 */
	if (MIP->core_type != dev_kmem) {
		fprintf(KL_ERRORFP, "Current vmdump is not /dev/kmem!\n");
		return;
	}

	/* 
	 * Allocate and clear some memory for the dump_page_buf and buf
	 */
	buf = (void*)malloc(dump_page_size);
	dump_page_buf = (void*)malloc(dump_buffer_size + dump_page_size + psize);
	bzero(dump_page_buf, dump_buffer_size);

	/* Check to see if a file named live_vmdump.0 exists. If it does, 
	 * then bump the number and keep testing until a match is not 
	 * found. Use the resulting filename as the dump file name.
	 */
	while (1) {
		sprintf(dname, "%s.%d", "live_vmdump", bounds);
		if ((ofd = open(dname, O_RDONLY)) == -1) {
			break;
		}
		bounds++;
		close(ofd);
	} 

	/* Copy the System.map file, adding the appropriate bounds number
	 */
	sprintf(mname, "live_map.%d", bounds);

	if ((ifd = open("/boot/System.map", O_RDONLY)) == -1) {
		fprintf(KL_ERRORFP, "Could not open /boot/System.map "
			"for reading!\n");
		goto cleanup;
	}
	if ((ofd = open(mname, O_WRONLY|O_CREAT, 0644)) == -1) {
		fprintf(KL_ERRORFP, "Could not open %s for writing!\n", mname);
		goto cleanup;
	}
	while((count = read(ifd, dump_page_buf, dump_buffer_size))) {
		if (count == -1) {
			fprintf(KL_ERRORFP, 
				"Error reading /boot/System.map (%d)!\n",
				errno);
			goto cleanup;
		} 
		if (write(ofd, dump_page_buf, count) == -1) {
			fprintf(KL_ERRORFP, "Error writing to %s (%d)\n", 
				mname, errno);
			goto cleanup;
		}
	}

	/* Copy the Kerntypes file, adding the appropriate bounds number
	 */
	sprintf(kname, "live_Kerntypes.%d", bounds);

	if ((ifd = open("/boot/Kerntypes", O_RDONLY)) == -1) {
		fprintf(KL_ERRORFP, "Could not open /boot/Kerntypes "
			"for reading!\n");
		goto cleanup;
	}
	if ((ofd = open(kname, O_WRONLY|O_CREAT, 0644)) == -1) {
		fprintf(KL_ERRORFP, "Could not open %s for writing!\n", kname);
		goto cleanup;
	}
	while((count = read(ifd, dump_page_buf, dump_buffer_size))) {
		if (count == -1) {
			fprintf(KL_ERRORFP, 
				"Error reading /boot/Kerntypes (%d)!\n", errno);
			goto cleanup;
		} 
		if (write(ofd, dump_page_buf, count) == -1) {
			fprintf(KL_ERRORFP, "Error writing to %s (%d)\n", 
				kname, errno);
			goto cleanup;
		}
	}

	/* Open the live_vmdump file for output
	 */
	if (!(dump_file = fopen(dname, "w"))) {
		fprintf(KL_ERRORFP, 
			"Could not open %s for writing!\n", dname);
		return;
	}
	if (fchmod(fileno(dump_file), 0664)) {
		fprintf(KL_ERRORFP,
			"Could not set file mode for %s!\n", dname);
		return;
	}

	if (init_live_vmdump(dump_compress_pages)) {
		fprintf(KL_ERRORFP, "init_live_vmdump() failed!\n");
		goto cleanup;
	}

	dump_header.dh_dump_level = dump_level;

	fprintf(KL_ERRORFP, "Creating live system dump...\n");
	fprintf(KL_ERRORFP, "map = %s, vmdump = %s, kerntypes = %s\n", 
		mname, dname, kname);

	/* dump out the header 
	 */
	fprintf(KL_ERRORFP, "Writing dump header ...");
	if (write_vmdump_header(dump_file) < 0) {
		fprintf(KL_ERRORFP, "write_vmdump_header() failed!\n");
		goto cleanup;
	}

	/* if we only want the header, return 
	 */
	if (dump_level == DUMP_LEVEL_HEADER) {
		fprintf(KL_ERRORFP, "\nDump complete.\n");
		goto cleanup;
	}

	fprintf(KL_ERRORFP, "\nWriting dump pages ...");

	/* get the first memory page from the start of memory, aligned 
	 */
	mem_loc = dump_header.dh_memory_start;

	/* Start walking through each page of memory, dumping it out
	 * as you go. We need to fit as much as possible into a 64K 
	 * write().  So we write dump page header, then the page
	 * itself (which may be compressed), etc., etc., etc., until
	 * we can't fit another compressed page into the write buffer.
	 * We then write the entire page out to the dump device.
	 */
	while (mem_loc < dump_header.dh_memory_end) {
		paddr_t tmp;

		/* make sure the address is valid in the physical space
		 */
		if (!vmdump_page_valid(mem_loc)) {
			mem_loc = vmdump_next_valid_page(mem_loc);
			continue;
		}

		/* Create the dump header. Since we are walking through
		 * virtual address space, we have to convert mem_loc to
		 * a physical address before we put it into the page 
		 * header. Since the conversion of virtual to physical 
		 * address is very architecture specific, we make a call 
		 * to kl_virtop() to take care of this.
		 */
		/* introduced tmp here to be able to use this stuff
		 * on big endian 32bit machines, too
		 */
		if (kl_virtop((kaddr_t)mem_loc, NULL, &tmp)) {
			fprintf(KL_ERRORFP, 
				"Error converting to physical address!\n");
			goto cleanup;
		}
		dp.dp_address = tmp;
		dp.dp_flags = DUMP_DH_RAW;
		dp.dp_size = dump_page_size;

		GET_BLOCK(mem_loc, dump_page_size, buf);
		if (KL_ERROR) {
			fprintf(KL_ERRORFP, 
				"Error reading memory block!\n");
			goto cleanup;
		}

		/* see if we want to compress the pages or not 
		 */
		if (dump_compress_pages) {
			dp.dp_flags = DUMP_DH_COMPRESSED;
			bzero(dpcpage, dump_page_size);

			/* get the new compressed page size 
			 */
			size = vmdump_compress((char *)buf,
					(char *)dpcpage, dump_page_size);

			/* if compressed page is same size, skip it 
			 */
			if (size == dump_page_size) {
				dp.dp_flags = DUMP_DH_RAW;
				dp.dp_size = dump_page_size;
			} else {
				dp.dp_size = size;
			}
		}

		/* copy the page header 
		 */
		bcopy((const void *)&dp, 
			(void *)(dump_page_buf + buf_loc), psize);
		buf_loc += psize;

		/* copy the page of memory 
		 */
		if (dp.dp_flags & DUMP_DH_COMPRESSED) {
			/* copy the compressed page 
			 */
			bcopy((const void *)dpcpage, 
				(void *)(dump_page_buf + buf_loc), dp.dp_size);
		} else {
			/* copy directly from memory 
			 */
			bcopy((const void *)buf, 
				(void *)(dump_page_buf + buf_loc), dp.dp_size);
		}
		buf_loc += dp.dp_size;

		/* see if we need to write out the buffer 
		 */
		if (buf_loc >= dump_buffer_size) {

			if (vmdump_write(dump_file, dump_page_buf,
				dump_buffer_size) < 0) {
					fprintf(KL_ERRORFP, 
						"dump write error!\n");
					goto cleanup;
			}

			/* After writing out a set of pages, be sure to go
			 * through and update the dump header with the new
			 * count for dh_num_pages.  This is so lcrash knows
			 * there are at least some valid pages in the dump.
			 *
			 * This is also important for dumps that reach the
			 * end of the partition and fill up - if we don't
			 * update the dh_num_pages at least once, we won't
			 * know how many pages are in the dump at all.
			 */
			if (write_vmdump_header(dump_file) < 0) {
				fprintf(KL_ERRORFP, "Incremental dump header "
					"update failed!\n");
				goto cleanup;
			}

			if (buf_loc > dump_buffer_size) {

				/* clear the dump page buffer 
				 */
				bzero(dump_page_buf, dump_buffer_size);

				/* copy the dump page buffer overrun 
				 */
				bcopy((const void *)(dump_page_buf + 
					dump_buffer_size), (void *)dump_page_buf,
					buf_loc - dump_buffer_size);

				/* set the new buffer location 
				 */
				buf_loc -= dump_buffer_size;
			} else {
				buf_loc = 0;
			}

			/* see if we want to print out a '.' 
			 */
			cloop++;
			if (!(cloop & 0xff)) {
				fprintf(KL_ERRORFP, ".");
			}
		}

		/* update the page count 
		 */
		dump_header.dh_num_dump_pages++;

		/* increment to the next page 
		 */
		mem_loc += dump_page_size;
	}

	/* set up the dump page header with DUMP_DH_END 
	 */
	dp.dp_address = 0x0;
	dp.dp_flags = DUMP_DH_END;
	dp.dp_size = 0x0;

	/* copy the current buffer 
	 */
	bcopy((const void *)&dp, (void *)(dump_page_buf + buf_loc), psize);

	/* increment the buffer size 
	 */
	buf_loc += psize;

	/* write out now that we have the DUMP_DH_END header in there 
	 */
	if (vmdump_write(dump_file, dump_page_buf, dump_buffer_size) < 0) {
		fprintf(KL_ERRORFP, " dump write error!\n");
		goto cleanup;
	}

	/* we have something left in the buffer to write out ... 
	 */
	if (buf_loc > dump_buffer_size) {
		/* clear the dump page buffer 
		 */
		bzero(dump_page_buf, dump_buffer_size);

		/* copy the dump page buffer remnants 
		 */
		bcopy((const void *)(dump_page_buf + dump_buffer_size),
			(void *)dump_page_buf,
			buf_loc - dump_buffer_size);

		/* set the new buffer location 
		 */
		buf_loc -= dump_buffer_size;

		/* write out that _last_ bit of buffer! 
		 */
		if (vmdump_write(dump_file, dump_page_buf, dump_buffer_size) < 0) {
			fprintf(KL_ERRORFP, " dump write error!\n");
			goto cleanup;
		}
	}

	/* terminate the dots ... 
	 */
	fprintf(KL_ERRORFP, "\n");

	/* dump out the dump header again - reset the file position! 
	 */
	if (write_vmdump_header(dump_file) < 0) {
		fprintf(KL_ERRORFP, "Final dump header update failed!\n");
		goto cleanup;
	}

	fprintf(KL_ERRORFP, "Dump complete.\n");

cleanup:

	/* close the dump device 
	 */
	if (ifd != -1) {
		close (ifd);
	}
	if (ofd != -1) {
		close (ofd);
	}
	vmdump_close(dump_file);
	if (buf) {
		free(buf);
	}
	if (dump_page_buf) {
		free(dump_page_buf);
		dump_page_buf = NULL;
	}
	return;
}
