/*
 * MTD Oops/Panic log extraction program
 *
 * Copyright (C) 2007 Nokia Corporation. All rights reserved.
 *
 * Author: Richard Purdie <rpurdie@openedhand.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <errno.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/fs.h>

#define OOPS_PAGE_SIZE 4096

int main(const int argc, const char *argv[])
{
	u_int32_t *count, maxcount = 0xffffffff;
	unsigned long size;
	const char *device;
	struct stat sbuf;
	int i, j, maxpos;
	char *charbuf;
	void *buf;
	int fd;

	fprintf(stderr, "Oops Log extractor (build %s %s).\n", __DATE__, __TIME__);
	if (argc < 2) {
		fprintf (stderr, "Usage: %s devicefile\n", argv[0]);
		return -1;
	}

	device = argv[1];

	buf = malloc(OOPS_PAGE_SIZE);
	if (!buf) {
		fprintf(stderr, "Unable to allocate memory\n");
		return -1;
	}

	if (stat(device, &sbuf)) {
		fprintf(stderr, "Unable to stat file '%s': %d\n", device, errno);
		return -errno;
	}

	fd = open(device, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Unable to open file '%s': %d\n", device, errno);
		return -errno;
	}

	if (sbuf.st_mode & S_IFBLK) {
		if (ioctl(fd, BLKGETSIZE, &size) < 0) {
			fprintf(stderr, "BLKGETSIZE ioctl failed for file '%s'\n", device);
			return -1;
		}
		size = size * 512;
	} else {
		size = sbuf.st_size;
	}

	charbuf = buf;	
	count = (u_int32_t *) buf;

	for (i = 0; i < (size / OOPS_PAGE_SIZE); i++) {
		pread(fd, buf, OOPS_PAGE_SIZE, i * OOPS_PAGE_SIZE);
		if (*count == 0xffffffff)
			continue;
		if (maxcount == 0xffffffff) {
			maxcount = *count;
			maxpos = i;
		} else if ((*count < 0x40000000) && (maxcount > 0xc0000000)) {
			maxcount = *count;
			maxpos = i;
		} else if ((*count > maxcount) && (*count < 0xc0000000)) {
			maxcount = *count;
			maxpos = i;
		} else if ((*count > maxcount) && (*count > 0xc0000000) 
					&& (maxcount > 0x80000000)) {
			maxcount = *count;
			maxpos = i;
		}
	}

	if (maxcount == 0xffffffff) {
		fprintf(stderr, "No logs present\n");
		return 0;
	}
	fprintf(stderr, "Last log is at position %d with count %d\n", maxpos, maxcount);

	for (i = 0; i < (size / OOPS_PAGE_SIZE); i++) {
		int bufend;

		maxpos++;
		if ((maxpos * OOPS_PAGE_SIZE) >= size)
			maxpos = 0;

		pread(fd, buf, OOPS_PAGE_SIZE, maxpos * OOPS_PAGE_SIZE);
		if (*count == 0xffffffff)
			continue;

		fprintf(stdout, "Log Entry %d (at position %d)\n", *count, maxpos);
		for (j = OOPS_PAGE_SIZE - 1; j > 3; j--) {
			if (charbuf[j] != 0xff)
				break;
		}
		bufend = j;
		for (j = 4; j <= bufend; j++)
			fprintf(stdout, "%c", charbuf[j]);
		fprintf(stdout, "\n");
	}
	return 0;
}
