/*
 * Copyright 1999 - 2002 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2002 Free Software Foundation, Inc. All rights reserved.
 */
#include <klib.h>

extern int check_type_dev(char *);
extern int check_type_dir(char *);
extern void *malloc(size_t);
int atoi(const char *);
int close(int);
ssize_t write(int, const void *, size_t);
off_t lseek(int, off_t, int);
ssize_t read(int, void *, size_t);

static int progress_tick, progress_last_percent, progress_pos;

/*
 * Name: __kl_get_bounds()
 * Func: Get the bounds number from the bounds file located in <dumpdir>
 *       Returns the bounds number, or 0 if no bounds file exists.
 */
static int
__kl_bounds_retrieve(char *dumpdir)
{
	int dirf, bounds;
	char *tfile;

	tfile = (char *)malloc(strlen(dumpdir) + 10);
	if (!tfile) {
		return (0);
	}

	sprintf(tfile, "%s/bounds", dumpdir);
	if ((dirf = open(tfile, O_RDONLY, 0)) < 0) {
		return (0);
	}

	(void)read(dirf, tfile, strlen(tfile));
	bounds = atoi(tfile);
	close(dirf);
	return (bounds);
}

/*
 * Name: __kl_bounds_update()
 * Func: Update the bounds number with a new value.  The function takes
 *       the <dumpdir> along with the new bounds number.  Returns 0 for
 *       failure, 1 for success.
 */
static void
__kl_bounds_update(char *dumpdir, int bounds)
{
	int dirf;
	char *tfile;

	tfile = (char *)malloc(strlen(dumpdir) + 10);
	if (!tfile) {
		return;
	}

	sprintf(tfile, "%s/bounds", dumpdir);
	if ((dirf = open(tfile, O_RDWR|O_CREAT,
		(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))) < 0) {
			fprintf(KL_ERRORFP, "Warning: could not "
				"create new bounds file in %s!\n", dumpdir);
			return;
	}

	sprintf(tfile, "%d\n", bounds);
	if (write(dirf, tfile, strlen(tfile)) != strlen(tfile)) {
		fprintf(KL_ERRORFP, "Warning: could not "
			"write to bounds file in %s!\n", dumpdir);
		close(dirf);
		return;
	}
	close(dirf);
	return;
}

static void
init_progress(void)
{
	progress_tick = progress_pos = 0;
	progress_last_percent = -1;
}

static void
update_progress(int cur, int max)
{
	static const char spinner[] = "\\|/-";
	int	tick;
	float	percent;
	struct timeval	tv;

	/*
	 * Calculate the new progress position.  If the
	 * percentage hasn't changed, then we skip out right
	 * away. 
	 */
	percent = ((float) cur / (float) max) * 100.0;
	if (progress_last_percent == (int) 10 * percent)
		return;
	progress_last_percent = (int) 10 * percent;
	
	/*
	 * If we've already updated the spinner once within
	 * the last 1/8th of a second, no point doing it
	 * again.
	 */
	gettimeofday(&tv, NULL);
	tick = (tv.tv_sec << 3) + (tv.tv_usec / (1000000 / 8));
	if ((tick == progress_tick) &&
	    (cur != max) && (cur != 0))
		return;
	progress_tick = tick;

	/*
	 * Advance the spinner and display the progress
	 */
	progress_pos = (progress_pos+1) & 3;
	printf("   ");
	if (percent == 100.0)
		fputc('|', stdout);
	else
		fputc(spinner[progress_pos & 3], stdout);
	printf(" %4.1f%%   \r", percent);
	fflush(stdout);
}


/*
 * Name: __kl_dump_retrieve()
 * Func: Gather a dump from a given device and save it into the dump
 *       directory.  The dump directory points to the possibility of
 *       a "bounds" file, which would be read, and be used to save the
 *       index number of the dump.  Returns 0 on failure, 1 if the
 *       core dump is short or if there was a problem finishing the
 *       read/write of the dump, or 2 on success.
 */
int
__kl_dump_retrieve(char *dumpdev, char *dumpdir, int progress, int local_debug)
{
	dump_page_t dp;
	dump_header_t dh;
	dump_header_asm_t dha;
	int bounds, devf, outf;
	u_char *tfile;
	uint64_t byte_offset;
	uint32_t page_index;
	int page_nbr = 0;

	/* check the types */
	if (check_type_dev(dumpdev) <= 0) {
		fprintf(KL_ERRORFP,
			"Error: kl_check_type() on dumpdev failed!\n");
		return (0);
	}

	/* check the types */
	if (check_type_dir(dumpdir) <= 0) {
		fprintf(KL_ERRORFP,
			"Error: kl_check_type() on dumpdir failed!\n");
		DUMP_BP();
		return (0);
	}
	
	/* initialize progress count */
	if (progress)
		init_progress();

	/* allocate the buffer for file data */
	tfile = (char *)malloc(dump_page_size);

	/* try to read the bounds file */
	bounds = __kl_bounds_retrieve(dumpdir);

	/* try to open the output file */
	if ((devf = open(dumpdev, O_RDONLY, 0)) < 0) {
		fprintf(KL_ERRORFP, "Error: open() on dumpdev failed!\n");
		DUMP_BP();
		return (0);
	}

	/* 
	 * move beyond the swap header 
	 */
	if (lseek(devf, dump_header_offset, SEEK_SET) < 0) {
		fprintf(KL_ERRORFP, "Error: lseek() on dumpdev failed!\n");
		close(devf);
		DUMP_BP();
		return (0);
	}

	/* try to read the dump header */
	if (read(devf, (char *)&dh,
		sizeof(dump_header_t)) < sizeof(dump_header_t)) {
			fprintf(KL_ERRORFP,
				"Error: read() of dump header failed!\n");
			close(devf);
			DUMP_BP();
			return (0);
	}

	/* try to read the dump header */
	if (read(devf, (char *)&dha,
		sizeof(dump_header_asm_t)) < sizeof(dump_header_asm_t)) {
			fprintf(KL_ERRORFP,
				"Error: read() of dump header failed!\n");
			close(devf);
			DUMP_BP();
			return (0);
	}

	/* validate the dump header */
	if (dh.dh_magic_number != DUMP_MAGIC_NUMBER) {
		if (local_debug) {
			fprintf(KL_ERRORFP,
				"Error: DUMP_MAGIC_NUMBER in "
				"dump header invalid!\n");
		}
		close(devf);
		DUMP_BP();
		return (2);
	}

	/* validate the architecture-specific dump header */
	if (dha.dha_magic_number != DUMP_ASM_MAGIC_NUMBER) {
		if (local_debug) {
			fprintf(KL_ERRORFP,
				"Error: DUMP_ASM_MAGIC_NUMBER in "
				"dump header invalid!\n");
		}
		close(devf);
		DUMP_BP();
		return (2);
	}

	/* make the new filename */
	sprintf(tfile, "%s/dump.%d", dumpdir, bounds);
	if ((outf = open(tfile, O_CREAT|O_RDWR|O_TRUNC,
		(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))) < 0) {
			fprintf(KL_ERRORFP,
				"Error: open() of dump file \"%s\" failed!\n",
				tfile);
			close(devf);
			DUMP_BP();
			return (0);
	}

	/* write out the dump header */
	if (write(outf, (char *)&dh,
		sizeof(dump_header_t)) != sizeof(dump_header_t)) {
			fprintf(KL_ERRORFP,
				"Error: write() of dump header to dump file "
				" \"%s\" failed! (Perhaps Dumpdir File System is Full?)\n", tfile);

			DUMP_BP();
			close(devf);
			close(outf);
			return (0);
	}

	/* write out the architecture-specific dump header */
	if (write(outf, (char *)&dha,
		sizeof(dump_header_asm_t)) != sizeof(dump_header_asm_t)) {
			fprintf(KL_ERRORFP,
				"Error: write() of architecture-specific dump header to dump file "
				"\"%s\" failed! (Perhaps Dumpdir File System is Full)\n", tfile);

			DUMP_BP();
			close(devf);
			close(outf);
			return (0);
	}

	/* now make sure we have more than just a header -- if not, leave */
	if (dh.dh_dump_level == DUMP_LEVEL_HEADER) {
		close(devf);
		close(outf);
		__kl_bounds_update(dumpdir, bounds + 1);
		return (1);
	}

	/* 
	 * move to the right offset in the output and device file 
	 */
	(void)lseek(devf, dump_header_size + dump_header_offset, SEEK_SET);
	(void)lseek(outf, dump_header_size, SEEK_SET);

	/* now start rolling through the blocks */
	for (page_index = 0, byte_offset = dump_header_offset + dump_page_size;
	      1; 
	      page_index++, byte_offset += (sizeof(dp) + dp.dp_size) ) {

		/* try to read in the dump page header */
		if (read(devf, (char *)&dp, sizeof(dp)) < sizeof(dp)) {
			/* if this fails, we may have run out of space */
			fprintf(KL_ERRORFP,
				"Warning: short dump in dumpdev!\n");
			close(devf);
			close(outf);
			__kl_bounds_update(dumpdir, bounds + 1);
			DUMP_BP();
			return (1);
		}

		if (local_debug > 1) {
			fprintf(KL_ERRORFP,
				"dp.dp_address = 0x%"FMT64"x, "
				"dp.dp_flags = 0x%x, dp.dp_size = %d\n",
				dp.dp_address, dp.dp_flags, dp.dp_size);
		}

#if DUMP_DEBUG >= 6
		/* DEBUG sanity check */
		if( dp.dp_page_index != page_index ) {
			DUMP_BP();
		}

		/* DEBUG sanity check */
		if( dp.dp_byte_offset != byte_offset ) {
			DUMP_BP();
		}
#endif
		/* sanity check */
		if ((!dp.dp_flags) || (dp.dp_flags >= DUMP_DH_NOT_USED) ||
			((!dp.dp_size) && (!(dp.dp_flags & DUMP_DH_END))) ||
			(dp.dp_size > dh.dh_dump_page_size)) {
				fprintf(KL_ERRORFP,
					"Error: dump flags in dumpdev "
					"page index invalid!\n");

				fprintf(KL_ERRORFP, "NOTE: Likely a Dirty Truncated Dump\n");

				DUMP_BP();
				close(devf);
				close(outf);
				__kl_bounds_update(dumpdir, bounds + 1);
				DUMP_BP();
				return (1);
		}

#if DUMP_DEBUG >= 6
		/*
		 * The dump file doesn't include the page we skipped over
		 * that has the swap info in it.
		 *
		 * Check the debuging information and adjust for no swap header.
		 * This will make it possible to matchup the byte_offset with 
		 * the cur_addr in __cmppindexcreate() while building the index file.
		 */
		if( dp.dp_byte_offset != byte_offset ) {
			DUMP_BP();
		}
		if( dp.dp_page_index != page_index ) {
			DUMP_BP();
		}
		dp.dp_byte_offset -= dump_header_offset;
#endif
		/* write out the dump page header */
		if (write(outf, (char *)&dp, sizeof(dp)) != sizeof(dp)) {
			/* if this fails, we may have run out of space */
			fprintf(KL_ERRORFP, "Warning: write() to dump "
				"file failed!\n");
			close(devf);
			close(outf);
			__kl_bounds_update(dumpdir, bounds + 1);
			return (1);
		}

		/* 
		 * make sure we aren't at the end of the dump 
		 */
		if (dp.dp_flags & DUMP_DH_END) {
			/* update progress count */
			if (progress)
				update_progress(dh.dh_num_dump_pages, dh.dh_num_dump_pages);

			close(devf);
			close(outf);
			__kl_bounds_update(dumpdir, bounds + 1);

			if(dp.dp_flags & DUMP_DH_TRUNCATED) {
				fprintf(KL_ERRORFP, "NOTE: Dump Truncated (Clean)\n");
				return (1);
			}
			return (2);
		}

		/* try to read in the dump page itself */
		if (read(devf, tfile, dp.dp_size) < dp.dp_size) {
			/* if this fails, we may have run out of space */
			fprintf(KL_ERRORFP,
				"Warning: short dump in dumpdev!\n");
			close(devf);
			close(outf);
			__kl_bounds_update(dumpdir, bounds + 1);
			return (1);
		}

#if DUMP_DEBUG
		/* test for test pattern */
		if(dp.dp_flags & DUMP_DH_TEST_PATTERN) {
			int i;

			/* 
			 * Debugging test pattern is for each byte
			 * to have the page_index in it,
			 */
			for ( i = 0; i < dp.dp_size; i++ ) {
				if( tfile[i] != (page_index & 0xff) ) {
					DUMP_BP();
				}
			}
		}
#endif

		/* try to write out the dump page itself */
		if (write(outf, (char *)tfile, dp.dp_size) != dp.dp_size) {
			/* if this fails, we may have run out of space */
			fprintf(KL_ERRORFP, "Warning: write() to dump "
				"file failed!\n");
			close(devf);
			close(outf);
			__kl_bounds_update(dumpdir, bounds + 1);
			DUMP_BP();
			return (1);
		}
		/* update progress count */
		if (progress)
			update_progress(page_nbr++, dh.dh_num_dump_pages);
	}

	/* NOTREACHED */
}

/*
 * __kl_dump_erase() -- Erase a crash dump from <dumpdev>.  This just
 *                      clobbers over the dump header.
 */
int
__kl_dump_erase(char *dumpdev)
{
	int devf;
	dump_header_t dh;

	/* check the types */
	if (check_type_dev(dumpdev) <= 0) {
		fprintf(KL_ERRORFP,
			"Error: kl_check_type() on dumpdev failed!\n");
		return (0);
	}

	/* try to open the output file */
	if ((devf = open(dumpdev, O_RDWR,
		(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))) < 0) {
			fprintf(KL_ERRORFP,
				"Error: open() on dumpdev failed!\n");
			return (0);
	}

	/* 
	 * move beyond the swap header 
	 */
	if (lseek(devf, dump_header_offset, SEEK_SET) < 0) {
		fprintf(KL_ERRORFP, "Error: lseek() on dumpdev failed!\n");
		close(devf);
		return (0);
	}

	/* try to read the dump header */
	if (read(devf, (char *)&dh, sizeof(dh)) < sizeof(dh)) {
		fprintf(KL_ERRORFP, "Error: read() of dump header failed!\n");
		close(devf);
		return (0);
	}

	/* check the magic number */
	if (dh.dh_magic_number != DUMP_MAGIC_NUMBER) {
		close(devf);
		return (0);
	}

	/* write a goofy number for sanity checking */
	dh.dh_magic_number = (uint64_t)0xdeadbeef;

	/* 
	 * move beyond the swap header
	 */
	if (lseek(devf, dump_header_offset, SEEK_SET) < 0) {
		fprintf(KL_ERRORFP, "Error: lseek() on dumpdev failed!\n");
		close(devf);
		return (0);
	}

	/* try to re-write the dump header */
	if (write(devf, (char *)&dh, sizeof(dh)) != sizeof(dh)) {
		fprintf(KL_ERRORFP, "Error: write() of dump header failed!\n");
		close(devf);
		return (0);
	}

	close(devf);
	return (1);
}
