/*
 * Original copyright notice:
 *
 * Copyright (C) 2001                   Linux ATA Development
 *                                      Andre Hedrick <andre@linux-ide.org>
 *
 * Redistribution and use in source forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Linux ATA Development
 *      and its contributors.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Linux ATA Development
 *      and its contributors.
 * 4. Neither the name of Linux ATA Development nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 * 5. Modification to this file requires notification and all changes are
 *    to be submitted to the original copyright holder, Linux ATA Development.
 *
 *
 * THIS SOFTWARE IS PROVIDED BY LINUX ATA DEVELOPMENT ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL LAD OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

extern "C" {
  #include <unistd.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <error.h>
  #include <fcntl.h>
  #include <sys/types.h>
  #include <sys/mman.h>

#include <glib.h>
#include <unistd.h>
}

#include <iostream.h>
#include "common.h"

// !!! Need to rationalise RC values.

// The basic idea is that we're going to take a block of BLOCKSIZE
// bytes, fill it with random junk, and then write it in 512 byte
// block (basic disk sectors) to a device, reading the data back
// later to make sure its correct.  To make things a little more
// fun, the 512 byte segment written is going to be rotated thru the
// block, incrementing forward a byte with every block (and
// returning to the beginning on wrap).  This makes the data on each
// sector self-identifying within a fairly large range of sectors
// (512Meg).

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

#define WRITESIZE (64 * (KILO))

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// We need this because Raw IO needs page alligned buffers.

static char *aligned_alloc (size_t size)
{
	int fh;
	char * mem;
  
	if ((fh = open("/dev/zero", O_RDONLY)) == -1) {
		cerr << "Unable to open /dev/zero!" << endl;
		perror ("  Error");
		return NULL;
	}
	if ((mem = (char *)mmap ((caddr_t )0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fh, 0)) == (caddr_t )-1) {
		cerr << "Unable to mmap /dev/zero." << endl;
		perror ("  Error");
		close (fh);
		return NULL;
	}
	close (fh);
	return mem;
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

static int aligned_free (char *mem, size_t size)
{
	return munmap ((void *)mem, size);
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

int main (int i_arg, char *apc_arg[], char *apc_env[])
{
	GTimer *time1, *time2;
	int fh, errors = 0;
	off_t end;
	char *p_block = NULL;
	char ac[BLOCKSIZE];
	char *pc_dev = apc_arg[1];
	long long ndx = 0;
	long long ndx2 = 0;
	long long num_blocks = 0;
	int *pi;                     
	bool err;
	int rc = 0;
	char *p_out;
	size_t len;
//	int o_flags = O_RDWR | O_LARGEFILE | O_DIRECT;
	int o_flags = O_RDWR | O_LARGEFILE;

	// Init
  
	p_block = new char [RANDBLOCK];
	if ((fh = open ("/dev/urandom", O_RDONLY)) == -1) {
		cerr << "Could not open random device." << endl;;
		perror ("  Error");
		delete [] p_block;
		return rc = 2;
	}
	if (read (fh, p_block, RANDBLOCK) != RANDBLOCK) {
		cerr << endl << "Read failed on random device." << endl;
		perror ("  Error");
		delete [] p_block;
		return rc = 7;
	}
	close (fh);  
	pi = (int *)p_block;

#undef DISPLAY_BUFFER
#ifdef DISPLAY_BUFFER
	cout << "Data stream: " << endl;
	cout << hex;

	{
		unsigned int i;

		for (i = 0; i < (RANDBLOCK / sizeof (int)); i++) {
			if (!(i % 8)) {
				cout << endl;
			}
			cout << (int)(pi[i]) << "  ";
		}
		cout << dec << endl;
	}
#endif /* DISPLAY_BUFFER */

	if ((p_out = aligned_alloc (WRITESIZE)) == NULL) {
		return 40;
	}
  
	if ((fh = open (pc_dev, o_flags)) == -1) {
		cerr << "Could not open specified device: " <<  pc_dev << endl;
		perror ("  Error");
		aligned_free (p_out, BLOCKSIZE);
		delete [] p_block;
		return rc = 2;
	}
  
	if ((end = lseek (fh, (off_t)0, SEEK_SET))== -1) {
		cerr << "Could not find start of device: " << pc_dev << endl;
		perror ("  Error");
		delete [] p_block;
		close (fh);
		return rc = 3;
	}

	// Try to keep writing whole blocks -- we're going to presume that
	// a failure to write an entire block, or a write failure at all
	// means that we've reached the end of the device.  Note that we
	// make a special check for write failures at offset zero (0) just
	// on the never never that we actually did nothing.

	// For performance writes are gouped into WRITESIZE blocks.  We
	// pick up any drips at the end.

	time1 = g_timer_new();
	g_timer_start(time1);
 
	for (ndx = 0, err = false; !err; ndx += ndx2) {
    
		for (ndx2 = 0; ndx2 < ((WRITESIZE) / BLOCKSIZE); ndx2++) {
			memcpy (p_out + (ndx2 * BLOCKSIZE), p_block + ((ndx + ndx2) % NUMBLOCKS), BLOCKSIZE);
		}
    
		if (! (ndx % ((MEG) / (BLOCKSIZE)))) {
			cout <<  "Wrote " << ((ndx * BLOCKSIZE) / MEG) << " Meg / " << ndx << " blocks\r" << flush;
		}
		len = write (fh, p_out, WRITESIZE);
		if ((err = (len != WRITESIZE))) {
			ndx += (len % BLOCKSIZE);	// Catch any drips
			break;
		}
	}
	num_blocks = ndx;
	aligned_free (p_out, BLOCKSIZE);	// Done with it.
	g_timer_stop(time1); 
 
	// Have we done anything?
  
	if (num_blocks == 0) {
		cerr << endl << "Failed to write anything to device: " << pc_dev << endl;
		perror ("  Error");
		delete [] p_block;
		close (fh);
		return rc = 4;
	}

	// Where are we now?

	if ((end = lseek (fh, (off_t)0, SEEK_CUR)) == -1) {
		cerr << endl << "Could not find end of device: " << pc_dev << endl;
		perror ("  Error");
		delete [] p_block;
		close (fh);
		return rc = 5;
	}

	cout << "Wrote " << ((long long)end / (MEG)) << " Meg / " << ndx << " blocks" << endl;
	cout << "Device length: " << (long long)end << " Bytes / " <<  ((long long) end / MEG) << " Meg / " << ((long long)end / GIG) << " Gig" << endl;


	printf("Total Diameter Sequential Pattern Write Test" \
		" = %3.2f MB/Sec (%3.2f Seconds)\n",
		((ndx * BLOCKSIZE) / MEG) / g_timer_elapsed(time1,0),
		g_timer_elapsed(time1,0));
	g_timer_reset(time1);
	g_timer_destroy(time1);

	if (fdatasync (fh)== -1) {
		cerr << "Could not flush device: " << pc_dev << endl;
		perror ("  Error");
		delete [] p_block;
		close (fh);
		return rc = 18;
	}
	close (fh);
	sleep (5);

	if ((fh = open (pc_dev, o_flags)) == -1) {
		cerr << "Could not open specified device: " <<  pc_dev << endl;
		perror ("  Error");
		aligned_free (p_out, BLOCKSIZE);
		delete [] p_block;
		return rc = 2;
	}  

	// Now read

	time2 = g_timer_new();
	g_timer_start(time2);
                   
	for (ndx = 0; ndx != num_blocks; ndx++) {

		// DEBUG lseek() -- present as reads are failing and I want to
		// try and continue

		if ((end = lseek (fh, (off_t)(ndx * BLOCKSIZE), SEEK_SET)) == -1) {
			cerr << "Could not find block: " << ndx << " for reading." << endl;
			perror ("  Error");
			rc = 6;
			goto getout_read;
		}

		if ((len = read (fh, ac, BLOCKSIZE)) != BLOCKSIZE) {
			cerr << endl << "Read failed on block number " << ndx << " at offset " << ndx * BLOCKSIZE << " ( " << len << " / " << BLOCKSIZE << " )" << endl;
			perror ("  Error");
			rc = 7;
			goto getout_read;	// continue; // !!!
		}

		if (! (ndx % ((MEG) / (BLOCKSIZE)))) {
			cout << "Read " << ((ndx * BLOCKSIZE) / MEG) << "Meg (" << ndx << " blocks)\r" << flush;
		}
    
		if (memcmp (ac, p_block + (ndx % NUMBLOCKS), BLOCKSIZE)) {
			cerr << endl << "Data verify failed on block number " << ndx << " at offset " << ndx * BLOCKSIZE << endl;;
			perror ("  Error");
			rc = 8;
			errors = 1;
			continue;	// Go looking for the next error
		}    
	}

getout_read:

	g_timer_stop(time2);

	// Where are we now?

	if ((end = lseek (fh, (off_t)0, SEEK_CUR)) == -1) {
		cerr << endl << "Could not find end of device: " << pc_dev << endl;
		perror ("  Error");
		delete [] p_block;
		close (fh);
		return rc = 5;
	}

	cout << "Read " << ((ndx * BLOCKSIZE) / MEG) << "Meg (" << ndx << " blocks)" << endl;
	cout << "Device length: " << (long long)end << " Bytes / " <<  ((long long) end / MEG) << " Meg / " << ((long long)end / GIG) << " Gig" << endl;

	printf("Total Diameter Sequential Pattern Read Test " \
		" = %3.2f MB/Sec (%3.2f Seconds)\n",
		((ndx * BLOCKSIZE) / MEG) / g_timer_elapsed(time2,0),
		g_timer_elapsed(time2,0));

	g_timer_reset(time2);
	g_timer_destroy(time2);

	// Done

	if (!rc) {
		cout << "Device passed CLEAN!" << endl;
	} else if (rc == 6) {
		cout << "Device passed! Seek OverRun!" << endl;
	} else if (rc == 7) {
		cout << "Device passed! Attempted to Read Beyond Device Limits!" << endl;
	} else {
		cout << "Device passed with ERRORS!" << endl;
	}
	delete [] p_block;
	close (fh);
	return (errors) ? 8 : rc;
}



