/*
 *  zgetdump
 *    Description: The zgetdump tool takes as input the dump device
 *                 and writes its contents to standard output,
 *                 which you can redirect to a specific file.
 *
 *    Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Despina Papadopoulou
 */

#include "zgetdump.h"
#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <linux/mtio.h>
#include <fcntl.h> 
#include <ctype.h>
#include <string.h>
#include <time.h>

#define HEADER_SIZE 4096
#define BLOCK_SIZE 32768
#define IS_DASD -1

#define OPTION_HELP      "-h"
#define OPTION_HELP_LONG "--help"
#define OPTION_INFO      "-i"
#define OPTION_INFO_LONG "--info"
#define OPTION_CHECK_ALL "-a"
#define OPTION_CHECK_ALL_LONG "--all"

#if defined(__s390x__)
	#define FMT64 "l"
#else
	#define FMT64 "ll"
#endif

/*  definitions  */

char *help_text=
"\nThe zgetdump tool takes as input the dump device\n"\
"and writes its contents to standard output,\n"\
"which you can redirect to a specific file.\n\n"\
"Usage:\n"\
"Copy dump from <dumpdevice> to stdout:\n"\
"       > zgetdump <dumpdevice>\n"\
"Print dump header and check if dump is valid - for single tape or DASD:\n"\
"       > zgetdump [-i | --info] <dumpdevice>\n"\
"Print dump header and check if dump is valid - for all volumes of a mv tape dump:\n"\
"       > zgetdump [-i | --info] [-a | --all] <dumpdevice>\n"\
"Print this text:\n"\
"       > zgetdump [-h | --help]\n\n"\
"Example:\n"\
"> zgetdump /dev/dasd/0193/part1 > dump_file\n\n";

char *usage_note=
"Usage:\n"\
"> zgetdump <dumpdevice>\n"\
"> zgetdump -i <dumpdevice>\n"\
"> zgetdump -i -a <dumpdevice>\n"\
"More info:\n"\
"> zgetdump -h\n";

/* global variables */

s390_dump_header_t  header;
s390_dump_end_marker_t  end_marker;
char read_buffer[BLOCK_SIZE];
struct timeval h_time_begin, h_time_end;

/* prototypes */

int open_dump(char *);
void get_header(int fd, int d_type);
void write_header(int fd);
int get_dump(int fd, int d_type);
void s390_tod_to_timeval(uint64_t todval, struct timeval *xtime);

/* end of definitions  */

void s390_tod_to_timeval(uint64_t todval, struct timeval *xtime)
{
    /* adjust todclock to 1970 */
    todval -= 0x8126d60e46000000LL - (0x3c26700LL * 1000000 * 4096);

    todval >>= 12;
    xtime->tv_sec  = todval / 1000000;
    xtime->tv_usec = todval % 1000000;
}


int open_dump(char *pathname)
{
        int fd;
	fd = open(pathname, O_RDONLY);
	if (fd == -1){
		fprintf(stderr, "\nCannot open dump device %s. \n\n", pathname);
		exit(1);
	}
	else
		fprintf(stderr, "\nDump device: %s\n", pathname);
        return fd;
}


/*					*/
/*	check if device is dasd or tape	*/
/*					*/

int dev_type(int fd)		
{
	int ret;
	struct mtget mymtget;

	ret = ioctl(fd,MTIOCGET,&mymtget);
	return ret;
}

/*                                      */
/*      print lkcd header information   */
/*                                      */

void print_lkcd_header(int fd)
{
	dump_header_4_1_t dump_header;

	lseek(fd,0,SEEK_SET);
	read(fd,&dump_header,sizeof(dump_header));	

	fprintf(stderr,"\nThis is a lkcd dump:\n\n");
        fprintf(stderr,
                "Memory start   : 0x%"FMT64"x\n", dump_header.dh_memory_start);
        fprintf(stderr,
                "Memory end     : 0x%"FMT64"x\n", dump_header.dh_memory_end);
        fprintf(stderr,
                "Physical memory: %"FMT64"d\n", dump_header.dh_memory_size);
        fprintf(stderr,
                "Panic string   : %s\n", dump_header.dh_panic_string);
        fprintf(stderr,
                "Number of pages: %d\n", dump_header.dh_num_dump_pages);
        fprintf(stderr,
                "Page size      : %d\n", dump_header.dh_dump_page_size);
        fprintf(stderr,
                "Magic number   : 0x%"FMT64"x\n", dump_header.dh_magic_number);
        fprintf(stderr,
                "Version number : %d\n", dump_header.dh_version);
}	

/*                                      */
/*	print header information	*/
/*                                      */

void get_header(int fd, int d_type)
{
	ssize_t	n_read;

        n_read = read(fd, &header, HEADER_SIZE);
	if(n_read == -1){
		perror("Cannot read dump header");
		close(fd);
		exit(1);
	}
	if((d_type == IS_DASD) && ((header.dh_magic_number == DUMP_MAGIC_LKCD) || (header.dh_magic_number == DUMP_MAGIC_LIVE))){
		print_lkcd_header(fd);
		exit(0);
	}

        s390_tod_to_timeval(header.dh_tod, &h_time_begin);

/*	as from version 2 of the dump tools	*/
/*	volume numbers are used			*/

        if ((d_type != IS_DASD) && (header.dh_version >= 2)) 
                fprintf(stderr, "\nTape Volume %i", header.dh_volnr);
	if (header.dh_volnr != 0)
                fprintf(stderr, " of a multi volume dump.\n");
	else
		fprintf(stderr, "\n");

/*	don't print header		*/
/*	for all subsequent tapes	*/
/*	of a multi-volume tape dump	*/

        if ((d_type == IS_DASD) || (header.dh_volnr == 0)){
		if(header.dh_magic_number != DUMP_MAGIC_S390){
			fprintf(stderr, "===================================================\n");
			fprintf(stderr, "WARNING: This does not look like a valid s390 dump!\n");
			fprintf(stderr, "===================================================\n");
			
		}

		fprintf(stderr, "\n>>>  Dump header information  <<<\n");
		fprintf(stderr, "Dump created on: %s\n",ctime(&h_time_begin.tv_sec));
		fprintf(stderr, "Magic number:\t 0x%"FMT64"x\n", header.dh_magic_number);
	        fprintf(stderr, "Version number:\t %d\n",header.dh_version);
	        fprintf(stderr, "Header size:\t %d\n",header.dh_header_size);
	        fprintf(stderr, "Page size:\t %d\n",header.dh_page_size);
	        fprintf(stderr, "Dumped memory:\t %"FMT64"d\n", header.dh_memory_size);
	        fprintf(stderr, "Dumped pages:\t %u\n", header.dh_num_pages);
		if(header.dh_version >= 3){
			fprintf(stderr, "Real memory:\t %"FMT64"d\n", header.dh_real_memory_size);
		}
	        fprintf(stderr, "cpu id:\t\t 0x%"FMT64"x\n", header.dh_cpu_id);
                if(header.dh_version >= 2){
                        switch(header.dh_arch){
                                case 1: fprintf(stderr, "System Arch:\t s390 (ESA)\n");
                                        break;
                                case 2: fprintf(stderr, "System Arch:\t s390x (ESAME)\n");
                                        break;
                                default:
                                        fprintf(stderr, "System Arch:\t <unknown>\n");
                                        break;
                        }
                        switch(header.dh_build_arch){
				case 1: fprintf(stderr, "Build Arch:\t s390 (ESA)\n");
				break;
				case 2: fprintf(stderr, "Build Arch:\t s390x (ESAME)\n");
                                        break;
				default:
                                        fprintf(stderr, "Build Arch:\t <unknown>\n");
                                        break;
                        }
                }

		fprintf(stderr, ">>>  End of Dump header  <<<\n\n");
	}
}

/*				*/
/*	copy header to stdout	*/
/*				*/

void write_header(int fd)
{
	ssize_t rc;
        memcpy(read_buffer,&header,sizeof(header));
	rc = write(STDOUT_FILENO, read_buffer, header.dh_header_size);
	if(rc == -1) {
		perror("\nwrite failed");
		exit(1);
	}
	if(rc < header.dh_header_size) {
		fprintf(stderr,"\nwrite failed: No space left on device\n");
		exit(1);
	}
}
 

/*				*/
/*	copy the dump to stdout	*/
/*				*/

int get_dump(int fd, int d_type)
{
	int ret, bsr;
	ssize_t n_read, n_written;
	struct mtop mymtop;
	uint64_t i;	

	ret = 0;
	if (d_type == -1){	/* device is DASD */
		i = 0;
		do {
			n_read = read(fd, read_buffer, BLOCK_SIZE);
			n_written = write(STDOUT_FILENO, read_buffer, n_read);
			if(n_written == -1) {
				perror("\nwrite failed");
				exit(1);
			}
			if(n_written < n_read) {
				fprintf(stderr,"\nwrite failed: No space left on device\n");
				exit(1);
			}
	               	i += n_read;
                	if (i % (header.dh_memory_size/32) == 0)
        	        	fprintf(stderr, ".");
	        } while (i<header.dh_memory_size && n_read != 0 && n_written >= 0);
	}
	else {	

	/*	device is tape			*/
	/*      write to stdout while not	*/
	/*      ENDOFVOL or DUMP_END		*/

		if (header.dh_volnr != 0)
			fprintf(stderr, "Reading dump content ");
                for (i=0;i<(header.dh_memory_size/BLOCK_SIZE);i++){
                        n_read = read(fd, read_buffer, BLOCK_SIZE);
			if (i % ((header.dh_memory_size/BLOCK_SIZE)/32) == 0)
                        	fprintf(stderr, ".");
			if (strncmp(read_buffer,"ENDOFVOL",8) == 0){
				fprintf(stderr, "\nEnd of Volume reached.\n");
				ret=1;
				break;
			}
			else if (strncmp(read_buffer,"DUMP_END",8) == 0){
				ret = 2;
				break;
			}
			else {
				ssize_t rc;
				rc = write(STDOUT_FILENO, read_buffer, n_read);
				if(rc == -1){
					perror("\nwrite failed");
					exit(1);
				}
				if(rc < n_read){
					fprintf(stderr,"\nwrite failed: No space left on device\n");
					exit(1);
				}
			}
		}
		if (ret == 2) {			/* we go back a record, so dump_end_times gets called */
			mymtop.mt_count=1;
			mymtop.mt_op=MTBSR;
			bsr = ioctl(fd,MTIOCTOP,&mymtop);
			if (bsr != 0){
		                fprintf(stderr, "Tape operation MTBSR failed.\n");
		                exit(1);
        		}
		}
			
		
        }
	return ret;
}


/*	check for DUMP_END and see		*/
/*	if dump ended after it started (!!!)	*/

int dump_end_times(int fd)
{
	int ret;
	read(fd, (char *)&end_marker, sizeof(end_marker));
        s390_tod_to_timeval(end_marker.end_time, &h_time_end);
	if ((strncmp(end_marker.end_string,"DUMP_END", 8)==0) && ((h_time_end.tv_sec - h_time_begin.tv_sec) >= 0)){
		fprintf(stderr, "\nDump ended on:\t %s\n", ctime(&h_time_end.tv_sec));
		ret=0;
	}
	else
		ret=-1;
	return ret;
}


/*	if a tape is part of the dump (not the last)	*/
/*	it should have and ENDOFVOL marker		*/

int vol_end(int fd)
{
        int ret;
        ret = strncmp(end_marker.end_string,"ENDOFVOL", 8);
        return ret;
}

/*	position the tape in front of an end marker	*/
/*	with FSFM and BSR				*/
	
void tape_forwards(int fd)
{
	int ret;
	struct mtop mymtop;

        mymtop.mt_count=1;
        mymtop.mt_op=MTFSFM;
	ret = ioctl(fd,MTIOCTOP,&mymtop);
        if (ret != 0){
                fprintf(stderr, "Tape operation FSFM failed.\n");
                exit(1);
        }

	mymtop.mt_count=1;
        mymtop.mt_op=MTBSR;
	ret = ioctl(fd,MTIOCTOP,&mymtop);
        if (ret != 0){
                fprintf(stderr, "Tape operation BSR failed.\n");
		exit(1);
	}
}


/*	put current tape offline	*/
/*	load & rewind next tape		*/

void load_next(fd)
{
	int ret;
        struct mtop mymtop;

	mymtop.mt_count=1;
	mymtop.mt_op=MTOFFL;
	ret = ioctl(fd,MTIOCTOP,&mymtop);
        if (ret != 0){
                fprintf(stderr, "Tape operation OFFL failed.\n");
		exit(1);
	}

	mymtop.mt_count=1;
	mymtop.mt_op=MTLOAD;
	ret = ioctl(fd,MTIOCTOP,&mymtop);
        if (ret != 0){
                fprintf(stderr, "Tape operation LOAD failed.\n");
		exit(1);
	}
	else
		fprintf(stderr, "done\n");

	mymtop.mt_count=1;
	mymtop.mt_op=MTREW;
	ret = ioctl(fd,MTIOCTOP,&mymtop);
        if (ret != 0){
                fprintf(stderr, "Tape operation REW failed.\n");
		exit(1);
	}

}



int main(int argc, char *argv[])
{
        int cur_volnr;
        uint64_t cur_time;
        int fd;
	int d_type;

        if ((argc == 1) || (argc > 4))
                printf(usage_note);
        if (argc == 2){						/* copy the dump to stdout */
                if ((strcmp(argv[1],OPTION_HELP)==0) || 
                    (strcmp(argv[1],OPTION_HELP_LONG)==0))
                        printf(help_text);
                else {
                        fd = open_dump(argv[1]);
			d_type = dev_type(fd);
                        get_header(fd,d_type);
                        write_header(fd);
			fprintf(stderr, "Reading dump content ");

		/*	now get_dump returns 1 for all	*/
		/*	except the last tape of a multi-volume dump */

			while (get_dump(fd,d_type) == 1){
	        		fprintf(stderr, "\nWaiting for next volume to be loaded... ");
				load_next(fd);
				get_header(fd,d_type);
			}

		/*	if dev is DASD and dump is copied	*/
		/*	check if the dump is valid		*/

			if (d_type == IS_DASD)
				lseek(fd,header.dh_header_size + header.dh_memory_size,SEEK_SET);

                        if (dump_end_times(fd) == 0){
				ssize_t rc;
				rc = write(STDOUT_FILENO,&end_marker,sizeof(end_marker));
				if(rc == -1){
					perror("\nwrite failed");
					exit(1);
				}
				if(rc < sizeof(end_marker)) {
					fprintf(stderr,"\nwrite failed: No space left on device\n");
					exit(1);
				}
			        fprintf(stderr, "\nDump End Marker found: this dump is valid.\n");
			}
			else
				fprintf(stderr, "\nThis dump is NOT valid.\n");
         	}
	
        }
        if (argc==3){						/* "-i" option */

                if ((strcmp(argv[1],OPTION_INFO)==0) ||
                    (strcmp(argv[1],OPTION_INFO_LONG)==0)) {
                        fprintf(stderr, "\n> \"zgetdump -i\" checks if a dump on either\n");
                        fprintf(stderr, "> a dasd volume or single tape is valid.\n");
                        fprintf(stderr, "> If the tape is part of a multi-volume tape dump,\n");
                        fprintf(stderr, "> it checks if it is a valid portion of the dump.\n");
                        fd = open_dump(argv[2]);
                        d_type = dev_type(fd);
                        get_header(fd,d_type);
                        if (d_type == IS_DASD)
                                lseek(fd,header.dh_header_size + header.dh_memory_size,SEEK_SET);
                        else{
                                fprintf(stderr, "Checking if the dump is valid - this might take a while...\n");
                                tape_forwards(fd);
                        }
                        if (dump_end_times(fd) == 0){
                                fprintf(stderr, "Dump End Marker found: ");
                                if (header.dh_volnr != 0)
                                        fprintf(stderr, "this is a valid part of a dump.\n\n");
                                else
                                        fprintf(stderr, "this dump is valid.\n\n");
                                exit(0);
                        }
                        else if (d_type == IS_DASD){
                                fprintf(stderr, "Dump End Marker not found: this dump is NOT valid.\n\n");
                                exit(1);
                        }
                        else
                                fprintf(stderr, "Checking for End of Volume...\n");
                        if (vol_end(fd) != 0){
                                fprintf(stderr, "End of Volume not found: This dump is NOT valid.\n\n");
                                exit(1);
                        }
                        else {
                                fprintf(stderr, "Reached End of Volume %i of a multi-volume tape dump.\n",header.dh_volnr);
                                fprintf(stderr, "This part of the dump is valid.\n\n");
                                exit(0);
                        }
                }

                else printf(usage_note);
        }


        if (argc==4){						/* "-i -a" option */

                if (((strcmp(argv[1],OPTION_INFO)==0)|| (strcmp(argv[1],OPTION_INFO_LONG) == 0)) &&
			((strcmp(argv[2],OPTION_CHECK_ALL)==0)|| (strcmp(argv[2],OPTION_CHECK_ALL_LONG) == 0))){
                        fprintf(stderr, "\n> \"zgetdump -i -a\" checks if a multi-volume tape dump is valid.\n");
                        fprintf(stderr, "> Please make sure that all volumes are loaded in sequence.\n");
                        fd = open_dump(argv[3]);
                        d_type = dev_type(fd);
                        if (d_type == IS_DASD){
                                fprintf(stderr, "\"-i -a\" is used for validation of multi-volume tape dumps.\n\n");
                                exit(1);
                        }
                        get_header(fd,d_type);
                        cur_volnr = header.dh_volnr;
                        cur_time = header.dh_tod;
                        fprintf(stderr, "\nChecking if the dump is valid - this might take a while...\n");
                        tape_forwards(fd);
                        if (dump_end_times(fd) == 0){
                                fprintf(stderr, "Dump End Marker found: this dump is valid.\n\n");
                                exit(0);
                        }
                        else if (vol_end(fd) != 0){
                                fprintf(stderr, "End of Volume not found: This dump is NOT valid.\n\n");
                                exit(1);
                        }
                        while (vol_end(fd) == 0){
				cur_volnr += 1;
				fprintf(stderr, "Reached End of Volume %i.\n", header.dh_volnr);
				fprintf(stderr, "Waiting for Volume %i to be loaded... ",cur_volnr);
                                load_next(fd);
                                get_header(fd,0);
                                if (header.dh_volnr != cur_volnr){
                                        fprintf(stderr, "This is not Volume %i\n", cur_volnr);
                                        exit(1);
                                        }
                                else if (header.dh_tod != cur_time){
                                        fprintf(stderr, "Time stamp of this volume does not match the previous one.\n");
                                        exit(1);
                                }
                                tape_forwards(fd);
                                if (dump_end_times(fd) == 0){
                                        fprintf(stderr, "Dump End found: This dump is valid.\n\n");
                                        exit(0);
                                }
                                else if (vol_end(fd) != 0){
                                        fprintf(stderr, "End of Volume not found: This dump is NOT valid.\n\n");
                                        exit(1);
                                }

                        }
                }

                else printf(usage_note);
        }

        close(fd);
        return(0);
}


