/*
 * kvmdump.c
 *
 * Copyright (C) 2009, 2010, 2011 David Anderson
 * Copyright (C) 2009, 2010, 2011 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "defs.h"
#include "kvmdump.h"

static struct kvmdump_data kvmdump_data = { 0 };  
struct kvmdump_data *kvm = &kvmdump_data;
static int cache_page(physaddr_t);
static int kvmdump_mapfile_exists(void);
static off_t mapfile_offset(uint64_t);
static void kvmdump_mapfile_create(char *);
static void kvmdump_mapfile_append(void);
static char *mapfile_in_use(void);
static void write_mapfile_registers(void);
static void write_mapfile_trailer(void);
static void read_mapfile_trailer(void);
static void read_mapfile_registers(void);

#define RAM_OFFSET_COMPRESSED (~(off_t)255)
#define QEMU_COMPRESSED       ((WRITE_ERROR)-1)
#define CACHE_UNUSED          (1ULL)

int 
is_kvmdump(char *filename)
{
	int i;
	ulong *ptr;
	off_t eof;
	ulonglong csum;
	struct mapinfo_trailer trailer;
	char buf[CHKSUM_SIZE];

	if (!is_qemu_vm_file(filename))
		return FALSE;

	if (lseek(kvm->vmfd, 0, SEEK_SET) < 0) {
		error(INFO, "%s: read: %s\n", filename, strerror(errno));
		return FALSE;
	}
	if (read(kvm->vmfd, buf, CHKSUM_SIZE) != CHKSUM_SIZE) {
		error(INFO, "%s: read: %s\n", filename, strerror(errno));
		return FALSE;
        }

	ptr = (ulong *)&buf[0];	
	for (i = csum = 0; i < (CHKSUM_SIZE/sizeof(ulong)); i++, ptr++)
		csum += *ptr;

	eof = lseek(kvm->vmfd, 0, SEEK_END);
	if (lseek(kvm->vmfd, eof - sizeof(trailer), SEEK_SET) < 0) {
		error(INFO, "%s: lseek: %s\n", filename, strerror(errno));
		return FALSE;
	} 
	if (read(kvm->vmfd, &trailer, sizeof(trailer)) != sizeof(trailer)) {
		error(INFO, "%s: read: %s\n", filename, strerror(errno));
		return FALSE;
	}
	if (trailer.magic == MAPFILE_MAGIC) {
		kvm->mapinfo.map_start_offset = trailer.map_start_offset;
		kvm->flags |= MAPFILE_APPENDED;
	}

	kvm->mapinfo.checksum = csum;

	return TRUE;
}

int 
kvmdump_init(char *filename, FILE *fptr)
{
	int i, page_size;
        struct command_table_entry *cp;
	char *cachebuf;
	FILE *tmpfp;

	if (!machine_type("X86") && !machine_type("X86_64")) {
		error(FATAL, 
		    "invalid or unsupported host architecture for KVM: %s\n",
			MACHINE_TYPE);
		return FALSE;
	}

	kvm->ofp = fptr;
	kvm->debug = &pc->debug;
	page_size = memory_page_size();

#ifdef X86_64
	kvm->kvbase = __START_KERNEL_map;
#endif

	switch (kvm->flags & (TMPFILE|MAPFILE|MAPFILE_APPENDED))
	{
	case MAPFILE_APPENDED:
		kvm->mapfd = kvm->vmfd;
		break;

	case MAPFILE|MAPFILE_APPENDED:
	case MAPFILE:
		break;

	default:
		if (kvmdump_mapfile_exists())
			break;

		if ((tmpfp = tmpfile()) == NULL) 
			error(FATAL, 
			    "cannot create tmpfile for KVM file offsets: %s\n", 
				strerror(errno));

		kvm->mapfd = fileno(tmpfp);
		kvm->flags |= TMPFILE;
		break;
	}

        if ((cachebuf = calloc(1, KVMDUMP_CACHED_PAGES * page_size)) == NULL)
                error(FATAL, "%s: cannot malloc KVM page_cache_buf\n");

	for (i = 0; i < KVMDUMP_CACHED_PAGES; i++) {
		kvm->page_cache[i].paddr = CACHE_UNUSED;
		kvm->page_cache[i].bufptr = cachebuf + (i * page_size);
	}

	kvmdump_regs_store(KVMDUMP_REGS_START, NULL);

	if (qemu_init(filename)) {
		switch (kvm->flags & (TMPFILE|MAPFILE|MAPFILE_APPENDED))
		{
		case TMPFILE:
			kvmdump_regs_store(KVMDUMP_REGS_END, NULL);
			write_mapfile_trailer();
			break;

		case MAPFILE:
		case MAPFILE_APPENDED:
		case MAPFILE|MAPFILE_APPENDED:
			read_mapfile_trailer();
			kvmdump_regs_store(KVMDUMP_REGS_END, NULL);
			break;
		}

		for (cp = pc->cmd_table; cp->name; cp++) {
			if (STREQ(cp->name, "map")) {
				cp->flags &= ~HIDDEN_COMMAND;
				break;
			}
		}

		kvm->flags |= KVMDUMP_LOCAL; 
		return TRUE;
	} else 
		return FALSE;
}

int 
read_kvmdump(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
	switch (cache_page(PHYSPAGEBASE(paddr)))
	{
	case READ_ERROR:
		return READ_ERROR;

	case SEEK_ERROR:
		return SEEK_ERROR;

	case QEMU_COMPRESSED:
		memset(bufptr, kvm->un.compressed, cnt);
		break;

	default:
		memcpy(bufptr, kvm->un.curbufptr + PAGEOFFSET(paddr), cnt);
		break;
	}

	return cnt;
}


int 
write_kvmdump(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
	return SEEK_ERROR;
}

/*
 *  kvmdump_free_memory(), and kvmdump_memory_used()
 *  are debug only, and typically unnecessary to implement.
 */
int 
kvmdump_free_memory(void)
{
	return 0;
}

int 
kvmdump_memory_used(void)
{
	return 0;
}

/*
 *  This function is dump-type independent, used here to
 *  to dump the kvmdump_data structure contents.
 */
int 
kvmdump_memory_dump(FILE *ofp)
{
	int i, others;
	struct mapinfo_trailer trailer;
	off_t eof;

	fprintf(ofp, "            flags: %lx (", kvm->flags);
	others = 0;
	if (kvm->flags & KVMDUMP_LOCAL)
		fprintf(ofp, "%sKVMDUMP_LOCAL", others++ ? "|" : "");
	if (kvm->flags & TMPFILE)
		fprintf(ofp, "%sTMPFILE", others++ ? "|" : "");
	if (kvm->flags & MAPFILE)
		fprintf(ofp, "%sMAPFILE", others++ ? "|" : "");
	if (kvm->flags & MAPFILE_FOUND)
		fprintf(ofp, "%sMAPFILE_FOUND", others++ ? "|" : "");
	if (kvm->flags & MAPFILE_APPENDED)
		fprintf(ofp, "%sMAPFILE_APPENDED", others++ ? "|" : "");
	if (kvm->flags & NO_PHYS_BASE)
		fprintf(ofp, "%sNO_PHYS_BASE", others++ ? "|" : "");
	if (kvm->flags & KVMHOST_32)
		fprintf(ofp, "%sKVMHOST_32", others++ ? "|" : "");
	if (kvm->flags & KVMHOST_64)
		fprintf(ofp, "%sKVMHOST_64", others++ ? "|" : "");
	if (kvm->flags & REGS_FROM_MAPFILE)
		fprintf(ofp, "%sREGS_FROM_MAPFILE", others++ ? "|" : "");
	if (kvm->flags & REGS_FROM_DUMPFILE)
		fprintf(ofp, "%sREGS_FROM_DUMPFILE", others++ ? "|" : "");
	if (kvm->flags & REGS_NOT_AVAIL)
		fprintf(ofp, "%sREGS_NOT_AVAIL", others++ ? "|" : "");
	fprintf(ofp, ")\n");

	fprintf(ofp, "            mapfd: %d\n", kvm->mapfd);
	fprintf(ofp, "             vmfd: %d\n", kvm->vmfd);
	fprintf(ofp, "              vmp: %lx (fd: %d)\n", (ulong)kvm->vmp, 
		fileno(kvm->vmp));
	fprintf(ofp, "              ofp: %lx\n", (ulong)kvm->ofp);
	fprintf(ofp, "            debug: %lx\n", (ulong)kvm->debug);
	if (machine_type("X86_64"))
        	fprintf(ofp, "           kvbase: %llx\n", (ulonglong)kvm->kvbase);
	else
        	fprintf(ofp, "           kvbase: (unused)\n");
	fprintf(ofp, "          mapinfo:\n");
        fprintf(ofp, "              magic: %llx %s\n", (ulonglong)kvm->mapinfo.magic,
		kvm->mapinfo.magic == MAPFILE_MAGIC ?  "(MAPFILE_MAGIC)" : "");
        fprintf(ofp, "          phys_base: %llx %s\n", (ulonglong)kvm->mapinfo.phys_base,
		machine_type("X86") ? "(unused)" : "");
        fprintf(ofp, "     cpu_version_id: %ld\n", (ulong)kvm->mapinfo.cpu_version_id);
        fprintf(ofp, "     ram_version_id: %ld\n", (ulong)kvm->mapinfo.ram_version_id);
        fprintf(ofp, "   map_start_offset: %llx\n", (ulonglong)kvm->mapinfo.map_start_offset);
        fprintf(ofp, "           checksum: %llx\n", (ulonglong)kvm->mapinfo.checksum);

	fprintf(ofp, "        curbufptr: %lx\n", (ulong)kvm->un.curbufptr);
	fprintf(ofp, "      evict_index: %d\n", kvm->evict_index);
	fprintf(ofp, "         accesses: %ld\n", kvm->accesses);
	fprintf(ofp, "        hit_count: %ld ", kvm->hit_count);
	if (kvm->accesses)
		fprintf(ofp, "(%ld%%)\n",
			kvm->hit_count * 100 / kvm->accesses);
	else
		fprintf(ofp, "\n");
	fprintf(ofp, "       compresses: %ld ", kvm->compresses);
	if (kvm->accesses)
		fprintf(ofp, "(%ld%%)\n",
			kvm->compresses * 100 / kvm->accesses);
	else
		fprintf(ofp, "\n");

	for (i = 0; i < KVMDUMP_CACHED_PAGES; i++) {
		if (kvm->page_cache[i].paddr == CACHE_UNUSED)
			fprintf(ofp, "   %spage_cache[%d]: CACHE_UNUSED\n", 
				i < 10 ? " " : "", i);
		else
			fprintf(ofp, 
			    "   %spage_cache[%d]: bufptr: %lx  addr: %llx\n",
				i < 10 ? " " : "", i,
				(ulong)kvm->page_cache[i].bufptr,
				(ulonglong)kvm->page_cache[i].paddr);
	}

	fprintf(ofp, "      cpu_devices: %d\n", kvm->cpu_devices);
	fprintf(ofp, "           iohole: %llx (%llx - %llx)\n", 
		(ulonglong)kvm->iohole, 0x100000000ULL - kvm->iohole,
		0x100000000ULL);

	fprintf(ofp, "        registers: %s\n",
		kvm->registers ? "" : "(not used)");
	for (i = 0; i < kvm->cpu_devices; i++) {
		fprintf(ofp, "  CPU %d:\n", i);
		kvmdump_display_regs(i, ofp);
	}
	fprintf(ofp, "\n");

	dump_qemu_header(ofp);

	fprintf(ofp, "\n%s: mapinfo trailer:\n\n", mapfile_in_use());

	eof = lseek(kvm->mapfd, 0, SEEK_END);
	if (lseek(kvm->mapfd, eof - sizeof(trailer), SEEK_SET) < 0)
		error(FATAL, "%s: lseek: %s\n", mapfile_in_use(), 
			strerror(errno));
	if (read(kvm->mapfd, &trailer, sizeof(trailer)) != sizeof(trailer))
		error(FATAL, "%s: read: %s\n", mapfile_in_use(), 
			strerror(errno));

	fprintf(ofp, "             magic: %llx %s\n", (ulonglong)trailer.magic,
		trailer.magic == MAPFILE_MAGIC ? "(MAPFILE_MAGIC)" : "");
	fprintf(ofp, "         phys_base: %llx %s\n", (ulonglong)trailer.phys_base,
		machine_type("X86") ? "(unused)" : "");
	fprintf(ofp, "    cpu_version_id: %ld\n", (ulong)trailer.cpu_version_id);
	fprintf(ofp, "    ram_version_id: %ld\n", (ulong)trailer.ram_version_id);
        fprintf(ofp, "  map_start_offset: %llx\n", (ulonglong)trailer.map_start_offset);
	fprintf(ofp, "          checksum: %llx\n\n", (ulonglong)trailer.checksum);

	return TRUE;
}

void
kvmdump_display_regs(int cpu, FILE *ofp)
{
	struct register_set *rp;

	if (cpu >= kvm->cpu_devices) {
		error(INFO, "registers not collected for cpu %d\n", cpu);
		return;
	}

	rp = &kvm->registers[cpu];

	if (machine_type("X86_64")) {
		fprintf(ofp, 
		    "    RIP: %016llx  RSP: %016llx  RFLAGS: %08llx\n"
		    "    RAX: %016llx  RBX: %016llx  RCX: %016llx\n"
		    "    RDX: %016llx  RSI: %016llx  RDI: %016llx\n"
		    "    RBP: %016llx   R8: %016llx   R9: %016llx\n"
		    "    R10: %016llx  R11: %016llx  R12: %016llx\n"
		    "    R13: %016llx  R14: %016llx  R15: %016llx\n"
		    "    CS: %04x  SS: %04x\n",
			(ulonglong)rp->ip,
			(ulonglong)rp->regs[R_ESP],
			(ulonglong)rp->flags,
			(ulonglong)rp->regs[R_EAX], 
			(ulonglong)rp->regs[R_EBX],
			(ulonglong)rp->regs[R_ECX], 
			(ulonglong)rp->regs[R_EDX],
			(ulonglong)rp->regs[R_ESI],
			(ulonglong)rp->regs[R_EDI],
			(ulonglong)rp->regs[R_EBP],
			(ulonglong)rp->regs[8], 
			(ulonglong)rp->regs[9],
			(ulonglong)rp->regs[10], 
			(ulonglong)rp->regs[11],
			(ulonglong)rp->regs[12], 
			(ulonglong)rp->regs[13],
			(ulonglong)rp->regs[14], 
			(ulonglong)rp->regs[15],
			rp->cs,
			rp->ss);
	}

	if (machine_type("X86")) {
		fprintf(ofp,
		    "    EAX: %08llx  EBX: %08llx  ECX: %08llx  EDX: %08llx\n"
		    "    DS:  %04x      ESI: %08llx  ES:  %04x      EDI: %08llx\n"
		    "    SS:  %04x      ESP: %08llx  EBP: %08llx  GS:  %04x\n"
		    "    CS:  %04x      EIP: %08llx  EFLAGS: %08llx\n",
			(ulonglong)rp->regs[R_EAX], 
			(ulonglong)rp->regs[R_EBX],
			(ulonglong)rp->regs[R_ECX], 
			(ulonglong)rp->regs[R_EDX],
			rp->ds,
			(ulonglong)rp->regs[R_ESI], 
			rp->ds,
			(ulonglong)rp->regs[R_EDI],
			rp->ss,
			(ulonglong)rp->regs[R_ESP], 
			(ulonglong)rp->regs[R_EBP],
			rp->gs,
			rp->cs,
			(ulonglong)rp->ip,
			(ulonglong)rp->flags);
	}
}

void 
get_kvmdump_regs(struct bt_info *bt, ulong *ipp, ulong *spp)
{
	ulong ip, sp;
	struct register_set *rp;

	ip = sp = 0;

	if (!is_task_active(bt->task)) {
		machdep->get_stack_frame(bt, ipp, spp);
		return;
	}

	bt->flags |= BT_DUMPFILE_SEARCH;
	if (machine_type("X86_64"))
		machdep->get_stack_frame(bt, ipp, spp);
	else if (machine_type("X86"))
		get_netdump_regs_x86(bt, ipp, spp);
	if (bt->flags & BT_DUMPFILE_SEARCH)
		return;

	if ((kvm->registers == NULL) ||
	    (bt->tc->processor >= kvm->cpu_devices))
		return;

	rp = &kvm->registers[bt->tc->processor];
	ip = (ulong)rp->ip;
	sp = (ulong)rp->regs[R_ESP];
	if (is_kernel_text(ip) &&
	    (((sp >= GET_STACKBASE(bt->task)) &&
	      (sp < GET_STACKTOP(bt->task))) ||
	    in_alternate_stack(bt->tc->processor, sp))) {
		*ipp = ip; 
		*spp = sp;
		bt->flags |= BT_KERNEL_SPACE;
		return;
	}

	if (!is_kernel_text(ip) && 
	    in_user_stack(bt->tc->task, sp))
		bt->flags |= BT_USER_SPACE;
}


ulong
get_kvmdump_panic_task(void)
{
	int i;
	struct bt_info *bt;
	ulong panic_task, task, rip, rsp;
	char *sym;

	if (machine_type("X86") || !get_active_set())
		return NO_TASK;

	bt = (struct bt_info *)GETBUF(sizeof(struct bt_info));

	for (i = 0, panic_task = NO_TASK; i < NR_CPUS; i++) {
		if (!(task = tt->active_set[i]) ||
		    !(bt->tc = task_to_context(task)))
			continue;

		bt->task = task;
		bt->stackbase = GET_STACKBASE(task);
		bt->stacktop = GET_STACKTOP(task);
		if (!bt->stackbuf)
                	bt->stackbuf = GETBUF(bt->stacktop - bt->stackbase);
		alter_stackbuf(bt);

		bt->flags |= BT_DUMPFILE_SEARCH;
		machdep->get_stack_frame(bt, &rip, &rsp);
		if (!(bt->flags & BT_DUMPFILE_SEARCH))
			continue;

		sym = closest_symbol(rip);
		if (STREQ(sym, "panic") ||
		    STREQ(sym, "die") ||
		    STREQ(sym, "die_nmi") ||
		    STREQ(sym, "sysrq_handle_crash")) {
			if (CRASHDEBUG(1))
				fprintf(fp, "get_kvmdump_panic_task: %lx\n", 
					task);
			panic_task = task;
			break;
		}
	}

	if (bt->stackbuf)
		FREEBUF(bt->stackbuf);
	FREEBUF(bt);

	return panic_task;
}

int
kvmdump_phys_base(unsigned long *phys_base)
{
        if (KVMDUMP_VALID()) {
		if (CRASHDEBUG(1) && (kvm->mapinfo.cpu_version_id > 9)) 
			error(NOTE, 
			    "KVM/QEMU CPU_SAVE_VERSION %d is greater than"
			    " supported version 9\n\n",
				kvm->mapinfo.cpu_version_id);

                *phys_base = kvm->mapinfo.phys_base;

		return (kvm->flags & NO_PHYS_BASE ? FALSE : TRUE);
        }

        return FALSE;
}

static int
cache_page(physaddr_t paddr)
{
	int idx, err;
	struct kvm_page_cache_hdr *pgc;
	size_t page_size;
	off_t offset;

	kvm->accesses++;

	for (idx = 0; idx < KVMDUMP_CACHED_PAGES; idx++) {
		pgc = &kvm->page_cache[idx];

		if (pgc->paddr == CACHE_UNUSED)
			continue;

		if (pgc->paddr == paddr) {
			kvm->hit_count++;
			kvm->un.curbufptr = pgc->bufptr;
			return idx;
		}
	}

	if ((err = load_mapfile_offset(paddr, &offset)) < 0)
		return err;

        if ((offset & RAM_OFFSET_COMPRESSED) == RAM_OFFSET_COMPRESSED) {
                kvm->un.compressed = (unsigned char)(offset & 255);
		kvm->compresses++;
		return QEMU_COMPRESSED;
	}

	idx = kvm->evict_index;
	pgc = &kvm->page_cache[idx];
        page_size = memory_page_size();

	if (lseek(kvm->vmfd, offset, SEEK_SET) < 0) {
		pgc->paddr = CACHE_UNUSED;
		return SEEK_ERROR;
	}
	if (read(kvm->vmfd, pgc->bufptr, page_size) != page_size) {
		pgc->paddr = CACHE_UNUSED;
		return READ_ERROR;
	}

	kvm->evict_index = (idx+1) % KVMDUMP_CACHED_PAGES;

	pgc->paddr = paddr;
	kvm->un.curbufptr = pgc->bufptr;

	return idx;
}

static off_t 
mapfile_offset(uint64_t physaddr)
{
	off_t offset = 0;

	switch (kvm->flags & (TMPFILE|MAPFILE|MAPFILE_APPENDED))
	{
	case TMPFILE:
	case TMPFILE|MAPFILE_APPENDED:
	case MAPFILE:
	case MAPFILE|MAPFILE_APPENDED:
		offset = (off_t)(((((uint64_t)physaddr/(uint64_t)4096)) 
			* sizeof(off_t))); 
		break;
	
	case MAPFILE_APPENDED:
		offset = (off_t)(((((uint64_t)physaddr/(uint64_t)4096)) 
			* sizeof(off_t)) + kvm->mapinfo.map_start_offset); 
                break;
	}

	return offset;
}

int 
store_mapfile_offset(uint64_t physaddr, off_t *entry_ptr)
{
        if (lseek(kvm->mapfd, mapfile_offset(physaddr), SEEK_SET) < 0) {
		error(INFO, "store_mapfile_offset: "
	    	    "lseek error: physaddr: %llx  %s offset: %llx\n", 
			(unsigned long long)physaddr, mapfile_in_use(),
			(unsigned long long)mapfile_offset(physaddr));
		return SEEK_ERROR;
	}

        if (write(kvm->mapfd, entry_ptr, sizeof(off_t)) != sizeof(off_t)) {
		error(INFO, "store_mapfile_offset: "
	    	    "write error: physaddr: %llx  %s offset: %llx\n", 
			(unsigned long long)physaddr, mapfile_in_use(),
			(unsigned long long)mapfile_offset(physaddr));
		return WRITE_ERROR;
	}
	
	return 0;
}

int 
load_mapfile_offset(uint64_t physaddr, off_t *entry_ptr)
{
	uint64_t kvm_addr = physaddr;

	switch (kvm->iohole)
	{
	case 0x20000000ULL:
		if (physaddr >= 0xe0000000ULL) {
			if (physaddr < 0x100000000ULL)
				return SEEK_ERROR;   /* In 512MB I/O hole */
			kvm_addr -= kvm->iohole;
		}
		break;

	case 0x40000000ULL:
		if (physaddr >= 0xc0000000ULL) {
			if (physaddr < 0x100000000ULL)
				return SEEK_ERROR;   /* In 1GB I/O hole */
			kvm_addr -= kvm->iohole;
		}
		break;
	}
 
	if (lseek(kvm->mapfd, mapfile_offset(kvm_addr), SEEK_SET) < 0) {
		if (CRASHDEBUG(1))
			error(INFO, "load_mapfile_offset: "
		    	    "lseek error: physical: %llx  %s offset: %llx\n", 
				(unsigned long long)physaddr, mapfile_in_use(),
				(unsigned long long)mapfile_offset(kvm_addr));
		return SEEK_ERROR;
	}

	if (read(kvm->mapfd, entry_ptr, sizeof(off_t)) != sizeof(off_t)) {
		if (CRASHDEBUG(1)) 
			error(INFO, "load_mapfile_offset: "
		    	    "read error: physical: %llx  %s offset: %llx\n", 
				(unsigned long long)physaddr, mapfile_in_use(),
				(unsigned long long)mapfile_offset(kvm_addr));
		return READ_ERROR;
	}

	return 0;
}

static void
kvmdump_mapfile_create(char *filename)
{
	int fdmem, n;
	off_t offset;
	char buf[4096];

	if (kvm->flags & MAPFILE) {
		error(INFO, "%s: mapfile in use\n", pc->kvmdump_mapfile);
		return;
	}

	if (file_exists(filename, NULL)) {
		error(INFO, 
		    "%s: file already exists!\n", filename);
		return;
	}

	if ((fdmem = open(filename, O_CREAT|O_RDWR, 0644)) < 0) {
		error(INFO, "%s: open: %s\n", filename, strerror(errno));
		return;
	}

	offset = kvm->mapinfo.map_start_offset;

	if (lseek(kvm->mapfd, offset, SEEK_SET) < 0) {
		error(INFO, "%s: leek: %s\n", 
			mapfile_in_use(), strerror(errno));
		return;
	}

	while ((n = read(kvm->mapfd, buf, 4096)) > 0) {
		if (write(fdmem, buf, n) != n) {
			error(INFO, "%s: write: %s\n", filename, 
				strerror(errno));
			break;
		}
	}

	close(fdmem);

	fprintf(fp, "MAP FILE CREATED: %s\n", filename);
}

static void
kvmdump_mapfile_append(void)
{
	int n, fdcore; 
	ulong round_bytes;
	struct stat statbuf;
	uint64_t map_start_offset;
	off_t eof, orig_dumpfile_size;
	char buf[4096];

	if (kvm->flags & MAPFILE_APPENDED)
		error(FATAL, "mapfile already appended to %s\n",
			pc->dumpfile);

	if (access(pc->dumpfile, W_OK) != 0)
		error(FATAL, 
		    "%s: cannot append map information to this file\n",
			pc->dumpfile);

	if (stat(pc->dumpfile, &statbuf) < 0)
		error(FATAL, "%s: stat: %s\n",
			pc->dumpfile, strerror(errno));

	round_bytes = (sizeof(uint64_t) - (statbuf.st_size % sizeof(uint64_t)))
		% sizeof(uint64_t);

	if ((fdcore = open(pc->dumpfile, O_WRONLY)) < 0)
		error(FATAL, "%s: open: %s\n", 
			pc->dumpfile, strerror(errno));

	if ((orig_dumpfile_size = lseek(fdcore, 0, SEEK_END)) < 0) {
		error(INFO, "%s: lseek: %s\n", pc->dumpfile, strerror(errno));
		goto bailout1;
	}

	if (round_bytes) {
		BZERO(buf, round_bytes);

		if (write(fdcore, buf, round_bytes) != round_bytes) {
			error(INFO, "%s: write: %s\n", 
				pc->dumpfile, strerror(errno));
			goto bailout2;
		}

	}

	map_start_offset = orig_dumpfile_size + round_bytes;

	if (lseek(kvm->mapfd, 0, SEEK_SET) != 0) {
		error(INFO, "%s: lseek: %s\n", 
			mapfile_in_use(), strerror(errno));
		goto bailout2;
	}

	while ((n = read(kvm->mapfd, buf, 4096)) > 0) {
		if (write(fdcore, buf, n) != n) {
			error(INFO, "%s: write: %s\n", pc->dumpfile, 
				strerror(errno));
			goto bailout2;
		}
	}

	/*
	 *  Overwrite the map_start_offset value in the trailer to reflect
	 *  its location in the appended-to dumpfile.
	 */
        eof = lseek(fdcore, 0, SEEK_END);
        if (lseek(fdcore, eof - sizeof(struct mapinfo_trailer), SEEK_SET) < 0) {
		error(INFO, "%s: write: %s\n", pc->dumpfile, strerror(errno));
		goto bailout2;
	}
	if (write(fdcore, &map_start_offset, sizeof(uint64_t)) != sizeof(uint64_t)) { 
		error(INFO, "%s: write: %s\n", pc->dumpfile, strerror(errno));
		goto bailout2;
	}

	close(fdcore);

	kvm->flags |= MAPFILE_APPENDED;
	fprintf(fp, "MAP FILE APPENDED TO: %s\n", pc->dumpfile);

	return;

bailout2:
	if (ftruncate(fdcore, (off_t)orig_dumpfile_size) < 0)
		error(INFO, "%s: ftruncate: %s\n", 
			pc->dumpfile, strerror(errno));
bailout1:
	close(fdcore);
	error(INFO, "failed to append map to %s\n", pc->dumpfile);
}

int 
is_kvmdump_mapfile(char *filename)
{
	int fd;
	struct mapinfo_trailer trailer;
	off_t eof;

        if ((fd = open(filename, O_RDONLY)) < 0) {
                error(INFO, "%s: open: %s\n", filename, strerror(errno));
                return FALSE;
        }

	if ((eof = lseek(fd, 0, SEEK_END)) == -1)
		goto bailout;

	if (lseek(fd, eof - sizeof(trailer), SEEK_SET) < 0) {
                error(INFO, "%s: lseek: %s\n", filename, strerror(errno));
		goto bailout;
	}

        if (read(fd, &trailer, sizeof(trailer)) != sizeof(trailer)) {
                error(INFO, "%s: read: %s\n", filename, strerror(errno));
		goto bailout;
        }

	if (trailer.magic == MAPFILE_MAGIC) {
		if (pc->dumpfile && (trailer.checksum != kvm->mapinfo.checksum)) {
			error(kvm->flags & MAPFILE_FOUND ? INFO : FATAL,
			    "checksum mismatch between %s and %s\n\n",
				pc->dumpfile, filename);
			goto bailout;
		}
		kvm->mapfd = fd;
		kvm->flags |= MAPFILE;
		return TRUE;
	} 

bailout:
	close(fd);
	return FALSE;
}

static int
kvmdump_mapfile_exists(void)
{
	char *filename;
	struct stat stat;

	if (!(filename = malloc(strlen(pc->dumpfile) + strlen(".map") + 10))) 
		return FALSE;

	sprintf(filename, "%s.map", pc->dumpfile);

	if (!file_exists(filename, &stat) || !S_ISREG(stat.st_mode))
		return FALSE;

	if (is_kvmdump_mapfile(filename)) {
		pc->kvmdump_mapfile = filename;
		kvm->flags |= MAPFILE_FOUND;
		return TRUE;
	}

	free(filename);
	return FALSE;
}

void
cmd_map(void)
{
	int c;
	int append, file, specified;
	char *mapfile;

	append = file = specified = 0;
	mapfile = NULL;

        while ((c = getopt(argcnt, args, "af")) != EOF) {
                switch(c)
		{
		case 'a':
			append++;
			break;
		case 'f':
			file++;
			break;
		default:
			argerrs++;
			break;
		}
	}

	if (argerrs)
		cmd_usage(pc->curcmd, SYNOPSIS);

        while (args[optind]) {
		if (!mapfile) {
			mapfile = args[optind];	
			specified++;
		} else
			cmd_usage(pc->curcmd, SYNOPSIS);
		optind++;
	}

	if (file && !specified) {
		mapfile = GETBUF(strlen(pc->dumpfile)+10);
		sprintf(mapfile, "%s.map", pc->dumpfile);
	}

	if (append)
		kvmdump_mapfile_append();

	if (file) {
		kvmdump_mapfile_create(mapfile);
		if (!specified)
			FREEBUF(mapfile);
	}

	if (!file && !append)
		fprintf(fp, "MAP FILE IN USE: %s\n", mapfile_in_use());
}

static char *
mapfile_in_use(void)
{
	char *name;

	switch (kvm->flags & (TMPFILE|MAPFILE|MAPFILE_APPENDED))
	{
	default:
	case TMPFILE:
	case TMPFILE|MAPFILE_APPENDED:
		name = "(tmpfile)";
		break;
	case MAPFILE:
	case MAPFILE|MAPFILE_APPENDED:
		name = pc->kvmdump_mapfile;
		break;
	case MAPFILE_APPENDED:
		name = pc->dumpfile;
		break;
	}

	return name;
}

static void
write_mapfile_trailer(void)
{
	if (kvm->cpu_devices)
		write_mapfile_registers();

        kvm->mapinfo.magic = MAPFILE_MAGIC;

        if (lseek(kvm->mapfd, 0, SEEK_END) < 0)
		error(FATAL, "%s: lseek: %s\n", mapfile_in_use(), strerror(errno));

	if (write(kvm->mapfd, &kvm->mapinfo, sizeof(struct mapinfo_trailer)) 
	    != sizeof(struct mapinfo_trailer))
		error(FATAL, "%s: write: %s\n", mapfile_in_use(), strerror(errno));
}

static void
write_mapfile_registers(void)
{
	size_t regs_size;
	uint64_t magic;

        if (lseek(kvm->mapfd, 0, SEEK_END) < 0)
		error(FATAL, "%s: lseek: %s\n", mapfile_in_use(), strerror(errno));

	regs_size = sizeof(struct register_set) * kvm->cpu_devices;
	if (write(kvm->mapfd, &kvm->registers[0], regs_size) != regs_size)
		error(FATAL, "%s: write: %s\n", mapfile_in_use(), strerror(errno));

	if (write(kvm->mapfd, &kvm->cpu_devices, sizeof(uint64_t)) != sizeof(uint64_t))
		error(FATAL, "%s: write: %s\n", mapfile_in_use(), strerror(errno));

	magic = REGS_MAGIC;
	if (write(kvm->mapfd, &magic, sizeof(uint64_t)) != sizeof(uint64_t))
		error(FATAL, "%s: write: %s\n", mapfile_in_use(), strerror(errno));
}

static void
read_mapfile_trailer(void)
{
	off_t eof;
	struct mapinfo_trailer trailer;

	if ((eof = lseek(kvm->mapfd, 0, SEEK_END)) < 0)
		error(FATAL, "%s: lseek: %s\n", 
			mapfile_in_use(), strerror(errno));

	if (lseek(kvm->mapfd, eof - sizeof(trailer), SEEK_SET) < 0)
		error(FATAL, "%s: lseek: %s\n", 
			mapfile_in_use(), strerror(errno));

	if (read(kvm->mapfd, &trailer, sizeof(trailer)) != sizeof(trailer))
		error(FATAL, "%s: read: %s\n", 
			mapfile_in_use(), strerror(errno));

	if (kvm->mapinfo.checksum != trailer.checksum)
		error(FATAL, "checksum mismatch between %s and %s\n",
			pc->dumpfile, mapfile_in_use());

	kvm->mapinfo = trailer;

	read_mapfile_registers();
}

static void
read_mapfile_registers(void)
{
	size_t regs_size;
	uint64_t ncpus, magic;
	off_t offset;

	if ((offset = lseek(kvm->mapfd, 0, SEEK_END)) < 0)
		error(FATAL, "%s: lseek: %s\n", 
			mapfile_in_use(), strerror(errno));

	offset -= sizeof(struct mapinfo_trailer) + 
		sizeof(magic) + sizeof(ncpus);

        if (lseek(kvm->mapfd, offset, SEEK_SET) < 0)
                error(FATAL, "%s: lseek: %s\n",
                        mapfile_in_use(), strerror(errno));

	if (read(kvm->mapfd, &ncpus, sizeof(uint64_t)) != sizeof(uint64_t))
		error(FATAL, "%s: read: %s\n", 
			mapfile_in_use(), strerror(errno));

	if (read(kvm->mapfd, &magic, sizeof(uint64_t)) != sizeof(uint64_t))
		error(FATAL, "%s: read: %s\n", 
			mapfile_in_use(), strerror(errno));

	if ((magic != REGS_MAGIC) || (ncpus >= NR_CPUS)) {
		kvm->flags |= REGS_NOT_AVAIL;
		return;
	}

	regs_size = sizeof(struct register_set) * ncpus;
	offset -= regs_size;
        if (lseek(kvm->mapfd, offset, SEEK_SET) < 0)
                error(FATAL, "%s: lseek: %s\n",
                        mapfile_in_use(), strerror(errno));

	if (read(kvm->mapfd, &kvm->registers[0], regs_size) != regs_size)
		error(FATAL, "%s: read: %s\n", 
			mapfile_in_use(), strerror(errno));

	kvm->cpu_devices = ncpus;
	kvm->flags |= REGS_FROM_MAPFILE;
}

void
set_kvmhost_type(char *host)
{
	if (!machine_type("X86")) {
		error(INFO, 
		    "--kvmhost is only applicable to the X86 architecture\n");
		return;
	}

	if (STREQ(host, "32")) {
		kvm->flags &= ~KVMHOST_64;
		kvm->flags |= KVMHOST_32;
	} else if (STREQ(host, "64")) {
		kvm->flags &= ~KVMHOST_32;
		kvm->flags |= KVMHOST_64;
	} else
		error(INFO, "invalid --kvmhost argument: %s\n", host);
}

/*
 *  set_kvm_iohole() is called from main() with a command line argument,
 *  or from the x86/x86_64_init functions for assistance in determining
 *  the I/O hole size.
 */
void
set_kvm_iohole(char *optarg)
{
#define DEFAULT_IOHOLE() \
	((kvm->mapinfo.cpu_version_id <= 9) ? 0x40000000 : 0x20000000)
#define E820_RAM 1

	if (optarg) {
		ulong flags;
		ulonglong iohole;
		char *arg;
	
		flags = LONG_LONG;
		if (IS_A_NUMBER(&LASTCHAR(optarg)))
			flags |= HEX_BIAS;
	
		arg = strdup(optarg);
	
		if (!calculate(arg, NULL, &iohole, flags))
			error(FATAL, 
			    "invalid --kvm_iohole argument: %s\n", optarg);
	
		free(arg);
	
		/*
		 *  Only 512MB or 1GB have been used to date.
		 */
		if ((iohole != 0x20000000ULL) && (iohole != 0x40000000ULL))
			error(WARNING, "questionable --kvmio argument: %s\n", 
				optarg);

		kvm->iohole = iohole;

	} else {
	        int nr_map, i;
	        char *buf, *e820entry;
	        ulonglong addr, size, ending_addr;
	        uint type;

		if (kvm->iohole)
			return;   /* set by command line option below */

		kvm->iohole = DEFAULT_IOHOLE();

		if (!symbol_exists("e820"))
			return;

	        buf = (char *)GETBUF(SIZE(e820map));
	        if (!readmem(symbol_value("e820"), KVADDR, &buf[0], 
		    SIZE(e820map), "e820map", RETURN_ON_ERROR|QUIET)) {
			FREEBUF(buf);
			return;
		}

		nr_map = INT(buf + OFFSET(e820map_nr_map));

		for (i = 0; i < nr_map; i++) {
                	e820entry = buf + sizeof(int) + (SIZE(e820entry) * i);
                	addr = ULONGLONG(e820entry + OFFSET(e820entry_addr));
			size = ULONGLONG(e820entry + OFFSET(e820entry_size));
                	type = UINT(e820entry + OFFSET(e820entry_type));

			if (type != E820_RAM)
				continue;
			if (addr >= 0x100000000ULL)
				break;
			
			ending_addr = addr + size;
			if ((ending_addr > 0xc0000000ULL) && 
			    (ending_addr <= 0xe0000000ULL)) {
				kvm->iohole = 0x20000000ULL;
				break;
			}
        	}

		FREEBUF(buf);
	}
}

#include "qemu-load.h"

int
kvmdump_regs_store(uint32_t cpu, struct qemu_device_x86 *dx86)
{
	struct register_set *rp;
	int retval;

	retval = TRUE;

	switch (cpu)
	{
	case KVMDUMP_REGS_START:
		if ((kvm->registers = 
		    calloc(NR_CPUS, sizeof(struct register_set))) == NULL)
			error(FATAL, "kvmdump_regs_store: "
				"cannot malloc KVM register_set array\n");
		kvm->cpu_devices = 0;
		break;

	case KVMDUMP_REGS_END:
		if (kvm->cpu_devices == 0) {
			free(kvm->registers);
			kvm->registers = NULL;
		} else if ((kvm->registers = realloc(kvm->registers, 
		    	sizeof(struct register_set) * kvm->cpu_devices)) == NULL) 
			error(FATAL, "kvmdump_regs_store: "
				"cannot realloc KVM registers array\n");
		break;

	default:
		if (cpu >= NR_CPUS) {
			if (machine_type("X86") && 
		    	    !(kvm->flags & (KVMHOST_32|KVMHOST_64)))
				return FALSE;
			break;
		}

		rp = &kvm->registers[cpu];
		rp->ip = dx86->eip;
		rp->flags = dx86->eflags;
		rp->cs = dx86->cs.selector;
		rp->ss = dx86->ss.selector;
		rp->ds = dx86->ds.selector;
		rp->es = dx86->es.selector;
		rp->fs = dx86->fs.selector;
		rp->gs = dx86->gs.selector;
		BCOPY(dx86->regs, rp->regs, 16*sizeof(uint64_t));
		kvm->cpu_devices = cpu+1;
		kvm->flags |= REGS_FROM_DUMPFILE;

		if (machine_type("X86_64") || 
		    (kvm->flags & (KVMHOST_32|KVMHOST_64)))
			break;

		if ((rp->regs[R_EAX] & UPPER_32_BITS) ||
		    (rp->regs[R_EBX] & UPPER_32_BITS) ||
		    (rp->regs[R_ECX] & UPPER_32_BITS) ||
		    (rp->regs[R_EDX] & UPPER_32_BITS) ||
		    (rp->regs[R_ESI] & UPPER_32_BITS) ||
		    (rp->regs[R_EDI] & UPPER_32_BITS) ||
		    (rp->regs[R_ESP] & UPPER_32_BITS) ||
		    (rp->regs[R_EBP] & UPPER_32_BITS) ||
		    (rp->ip & UPPER_32_BITS))
			retval = FALSE;

		break;
	}

	return retval;
}

int 
get_kvm_register_set(int cpu, struct kvm_register_set *krs)
{
	struct register_set *rs = &kvm->registers[cpu];

	if (!krs)
		return FALSE;

	if (machine_type("X86") || machine_type("X86_64")) {
		krs->x86.cs = rs->cs;
		krs->x86.ss = rs->ss;
		krs->x86.ds = rs->ds;
		krs->x86.es = rs->es;
		krs->x86.fs = rs->fs;
		krs->x86.gs = rs->gs;
		krs->x86.ip = rs->ip;
		krs->x86.flags = rs->flags;
		krs->x86.regs[0] = rs->regs[0];
		krs->x86.regs[1] = rs->regs[1];
		krs->x86.regs[2] = rs->regs[2];
		krs->x86.regs[3] = rs->regs[3];
		krs->x86.regs[4] = rs->regs[4];
		krs->x86.regs[5] = rs->regs[5];
		krs->x86.regs[6] = rs->regs[6];
		krs->x86.regs[7] = rs->regs[7];
		krs->x86.regs[8] = rs->regs[8];
		krs->x86.regs[9] = rs->regs[9];
		krs->x86.regs[10] = rs->regs[10];
		krs->x86.regs[11] = rs->regs[11];
		krs->x86.regs[12] = rs->regs[12];
		krs->x86.regs[13] = rs->regs[13];
		krs->x86.regs[14] = rs->regs[14];
		krs->x86.regs[15] = rs->regs[15];
	
		return TRUE;
	}

	return FALSE;
}
