/*
 * Copyright (c) 2002-2006 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagsnap.c 10455 2008-12-05 03:30:44Z dlim $
 */

/* Endace headers. */
#include "dagapi.h"
#include "dagutil.h"
#include "dagclarg.h"


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagsnap.c 10455 2008-12-05 03:30:44Z dlim $";
static const char* const kRevisionString = "$Revision: 10455 $";


#if defined(_WIN32) 
static int outfile;
#elif defined (__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
static FILE *outfile;
#endif /* Platform-specific code. */


#define OPTBUFSIZE ONE_KIBI
#define BUFSIZE 256
static int mbytes = 0;
static int rtime = 0;
static int waittime = 0;
static char buffer[OPTBUFSIZE];

static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static char fname_buf[BUFSIZE];
static char dlay_buf[BUFSIZE];

static int dagstream;
static int dagfd;
static char* ofname;
static int doreport = 1;
static int maximize = 0;
static uint64_t diff;
static int64_t fileoff;
static time_t seconds = 0;
static time_t waitseconds = 0;
static int maxwrite = 4 * ONE_MEBI; /* 4MiB default */
static int delay_msecs=0;

static int exit_now = 0;	/* exit loop flag set by signal handler */

/* Commandline argument codes. */
enum
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	CLA_PERFORMANCE,
#endif
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_MAXDATA,
	CLA_FNAME,
	CLA_RTIME,
	CLA_DELAY,
	CLA_WAITTIME
};


/* Internal routines. */
static void anysig(int sig);
static void alarmsig(int sig);
static void report(void);
#if defined(_WIN32)
static DWORD WINAPI ReportThread(LPVOID lpParameter);
static void SetTimerFn(void);
#endif /* _WIN32 */



#if defined(_WIN32)
/* In Windows we use a separate thread for timeout scheduling. */

static DWORD WINAPI
ReportThread(LPVOID lpParameter)
{
	while(TRUE)
	{
		Sleep(1000);
		doreport++;
	}
	
	return 0;
}

static void
SetTimerFn(void)
{
	if(CreateThread(
		NULL,
		0,
		ReportThread,
		NULL,
		0,
		NULL) == NULL)
		panic("unable to start the report function\n");
}
#endif /* _WIN32 */

#if 0
static void
print_version(void)
{
	printf("dagsnap (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}
#endif


static void
print_usage(ClArgPtr clarg)
{
	//print_version();
	printf("dagsnap - Endace DAG card capture utility.\n");
	printf("Usage: dagsnap [options]\n");
	dagclarg_display_usage(clarg, stdout);
	printf("With -v three columns are printed per second.\n");
	printf("    1. The cumulative total of data written out.\n");
	printf("    2. The buffer occupancy. Small values indicate no packet loss.\n");
	printf("    3. The rate at which data is currently being written.\n");
}


int
dagsnap_main(int argc, char *argv[])
{
	void* bounce_buffer = NULL;
	FILE* errorfile = NULL;
	uint32_t written;
	struct timeval maxwait;
	struct timeval poll;
	uint8_t *bottom = NULL;
	uint8_t *top = NULL;
	ClArgPtr clarg = NULL;
	int code;
	int result;
	int argindex;
	int index;

#if defined(_WIN32) 

	HANDLE hFile;
	outfile = -1;

#elif defined (__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	outfile = stdout;

#endif /* Platform-specific code. */


	dagutil_set_progname("dagsnap");

	/* Set up default DAG device. */
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char * const *)argv);

	dagclarg_add(clarg, "this page.", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "display version information.", "--version", 'V', CLA_VERSION);
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))	
	dagclarg_add(clarg, "maximize disk write performance - will only write in chunks", "--maxwrite", 'j', CLA_PERFORMANCE);
#endif
	dagclarg_add_int(clarg, "maximum amount of data to write per call in MiB (default 4).", "--maxdata", 'm', "mebibytes", &mbytes, CLA_MAXDATA);
	dagclarg_add_string(clarg, "output file name (default is stdout).", "--fname", 'o', "filename", fname_buf, BUFSIZE, CLA_FNAME);
	dagclarg_add_int(clarg, "runtime in seconds.", "--runtime", 's', "seconds", &rtime, CLA_RTIME);
	dagclarg_add_int(clarg, "delay(wait) in seconds before capture and aftre the stream is initialized.", "--wait", 'w', "waitseconds", &waittime, CLA_WAITTIME);
	dagclarg_add_string(clarg, "delay processing by an extra n milliseconds on each burst", "--delay", 'D', "n", dlay_buf, BUFSIZE, CLA_DELAY);

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				break;

			case CLA_DEVICE:
				if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
				{
					dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
				}
				break;

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				break;

			case CLA_VERSION:
				//print_version();
				return EXIT_SUCCESS;
				break;

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
			case CLA_PERFORMANCE:
				maximize = 1;
				break;
#endif

			case CLA_MAXDATA:
				maxwrite = mbytes * ONE_MEBI;
				break;

			case CLA_FNAME:
				ofname = fname_buf;
				outfile = 0;
				break;

			case CLA_RTIME:
				seconds = rtime;
				break;
			case CLA_DELAY:
				delay_msecs = strtoul(dlay_buf, NULL, 0);
				break;

			case CLA_WAITTIME:
				waitseconds = waittime;
				break;

			default:
				
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[argindex]); 
					print_usage(clarg);
					return EXIT_FAILURE;
				
		}
		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
    
#if defined(_WIN32)
	if(outfile < 0)
		dagutil_panic("Stdout can not be used with Windows. Please specify the filename to Write to\n");
#endif

	 /* Display unprocessed arguments if verbose flag was given. */
	argv = dagclarg_get_unprocessed_args(clarg, &argc);
	if ((NULL != argv) && (0 < argc) && (1 < dagutil_get_verbosity()))
	{
		for (index = 0; index < argc; index++)
		{
			dagutil_verbose_level(2, "unprocessed argument: '%s'\n", argv[index]);
		}
		dagutil_panic("Too many options!\n");
	}

#if defined(_WIN32)
	/* If we don't set stdout to binary mode, redirections will not work correctly */
	_setmode(_fileno (stdout), _O_BINARY);
#endif /* _WIN32 */
	if(ofname)
	{
		int flags = O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE;

		if (maximize)
		{
			flags |= O_DIRECT;
		}

		(void) close(STDOUT_FILENO);

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

		if (open(ofname, flags, 0664) != STDOUT_FILENO)

#elif defined(_WIN32)

	hFile = CreateFile(ofname,                 /* name of file to create */
				GENERIC_READ | GENERIC_WRITE,  /* permissions of file to create */
				FILE_SHARE_READ,               /* sharing permissions */
				NULL,                          /* security attributes for inheritance */
				CREATE_ALWAYS,                 /* action to take on files that exist */
				FILE_ATTRIBUTE_NORMAL,         /* file flags and attributes */
				NULL);                         /* file template for creation attributes */

	if (INVALID_HANDLE_VALUE == hFile)
	{
		LPTSTR message_buffer;
		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language*/
			&message_buffer,
			0,
			NULL);

		fprintf(stderr, "open(): %s\n", message_buffer);
		return EXIT_FAILURE;
	}

	outfile = _open_osfhandle((intptr_t) hFile, _O_APPEND | _O_CREAT | _O_TRUNC);

	if(outfile == -1)
#endif /* Platform-specific code. */
		{
			dagutil_panic("open %s: %s\n", ofname, strerror(errno));
		}
	}
	else
	{
		ofname = "(stdout)";
	}

	if ((1 == maximize) && (maxwrite < 16 * ONE_MEBI))
	{
		maxwrite = 16 * ONE_MEBI;
	}

	if((dagfd = dag_open(dagname)) < 0)
		dagutil_panic("dag_open %s: %s\n", dagname, strerror(errno));

	if(dag_configure(dagfd, buffer) < 0)
		dagutil_panic("dag_configure %s: %s\n", dagname, strerror(errno));

	if(dag_attach_stream(dagfd, dagstream, 0, 0) < 0)
		dagutil_panic("dag_attach_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));

	if(dag_start_stream(dagfd, dagstream) < 0)
		dagutil_panic("dag_start_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));

	dagutil_set_signal_handler(anysig);

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	dagutil_set_timer_handler(alarmsig, 1);

#elif defined(_WIN32)

	SetTimerFn();

#endif /* Platform-specific code. */

	report(); /* initialization */

	if (seconds)
		seconds += time(NULL);

	if (waitseconds)
		waitseconds += time(NULL);
	/*
	 * Initialise DAG Polling parameters.
	 */
	timerclear(&maxwait);
	maxwait.tv_usec = 100 * 1000; /* 100ms timeout */
	timerclear(&poll);
	poll.tv_usec = 10 * 1000; /* 10ms poll interval */


	/* 32kB minimum data to return */
	dag_set_stream_poll(dagfd, dagstream, 32*ONE_KIBI, &maxwait, &poll);
	/* wait seconds before capturing starts */
	/* give possibility to fill the memory hole for
		different testings
	*/
	while (waitseconds && (time(NULL) < waitseconds))
	{
	}

	/*
	 * The main Dag capture loop, wait for data and deliver.
	 * Reporting on the side.
	 */
 
	for (;;)
	{
		/* Call nonblocking to permit reporting in the absence of packets. */
		top = dag_advance_stream(dagfd, dagstream, &bottom);

		if (NULL == top)
		{
			dagutil_panic("dag_advance_stream %s:%u: %s\n", dagname, dagstream, strerror(dag_get_last_error()));
		}
		diff = top - bottom;

		/* 
		 * If more than maxwrite data available, then just write maxwrite now
		 * and then call dag_advance_stream again to free maxwrite space
		 */
		if (diff > maxwrite)
		{
			if (dagutil_get_verbosity() > 1)
				dagutil_verbose("diff = 0x%"PRIu64" (greater than maxwrite) bottom = 0x%08"PRIxPTR", top = 0x%08"PRIxPTR"\n", diff, (uintptr_t) bottom, (uintptr_t) top);
			written = maxwrite;
		}
		else if (0 == maximize)
		{
			written = (uint32_t)diff;
		}
		else
		{
			/* With the maximize option given, we only write complete chunks. */
			if (dagutil_get_verbosity() > 1)
				dagutil_verbose("diff = 0x%"PRIu64"    (less than maxwrite) bottom = 0x%08"PRIxPTR", top = 0x%08"PRIxPTR"\n", diff, (uintptr_t) bottom, (uintptr_t) top);
			if (doreport)
				report();
			
			continue;
		}

		errno = 0; /* no error */

		if (1 == maximize)
		{
			const void* source = bottom;
			int result;
			
			if (NULL != bounce_buffer)
			{
				/* Copy data to the bounce buffer, then write it. */
				memcpy(bounce_buffer, bottom, written);
				
				source = bounce_buffer;
			}

			/* Try writing directly. */
			result = write(STDOUT_FILENO, source, written);
			if (result != written)
			{
				if ((EFAULT == errno) && (NULL == bounce_buffer))
				{
					/* Allocate bounce buffer for next time through.
					posix_memalign(&bounce_buffer, ONE_KIBI, maxwrite); */
				
				#if defined(__FreeBSD__) || defined(__NetBSD__) || (defined(__APPLE__) && defined(__ppc__)) || defined(_WIN32) || (defined(__SVR4) && defined(__sun))
				
					/* FreeBSD (4.9, 4.11) doesn't have posix_memalign(), so use malloc() with extra padding and then align manually. */
					bounce_buffer = dagutil_malloc(maxwrite + ONE_KIBI);
					if (NULL == bounce_buffer)
					{
						dagutil_panic("malloc() failed: %s\n", strerror(errno));
						return EXIT_FAILURE;
					}
				
#if defined (__LP64__)
                    /* Align to a 1024-byte boundary if necessary. */
                    if (0 != ((uint64_t) bounce_buffer & 0x3ffLL))
                    {
                        /* Round up to next 1024-byte boundary. */
                        bounce_buffer = (void*) (((uint64_t) bounce_buffer + 0x400) & 0xfffffffffffffc00LL);
                    }
#else
                    /* Align to a 1024-byte boundary if necessary. */
                    if (0 != ((uint32_t) bounce_buffer & 0x3ff))
                    {
                        /* Round up to next 1024-byte boundary. */
                        bounce_buffer = (void*) (((uint32_t) bounce_buffer + 0x400) & 0xfffffc00);
                    }
#endif
				#else
				
					/* Use system posix_memalign(). */
					int error = posix_memalign(&bounce_buffer, ONE_KIBI, maxwrite);
					
					if (0 != error)
					{
						dagutil_panic("posix_memalign() failed: %d\n", error);
						return EXIT_FAILURE;
					}
					
				#endif /* posix_memalign() */
					
					if (dagutil_get_verbosity() > 1)
						dagutil_verbose("posix_memalign(%u, %d) returned 0x%08"PRIxPTR"\n", ONE_KIBI, written, (uintptr_t) bounce_buffer);
					continue;
				}
				else
				{
					dagutil_warning("write(%d, 0x%08"PRIxPTR", %u) returned %d\n", STDOUT_FILENO, (uintptr_t) source, written, result);
					dagutil_panic("write %d bytes to %s: %s\n", written, ofname, strerror(errno));
				}
			}
		}
		else
		{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

			if (fwrite(bottom, 1, written, stdout) != written)
			{
				dagutil_panic("fwrite %d bytes to %s: %s\n", written, ofname, strerror(errno));
			}

#elif defined(_WIN32)

			if(_write( outfile , bottom , written) != written)
					dagutil_panic("write failed\n");

#endif /* Platform-specific code. */

			if (delay_msecs)
				dagutil_microsleep(delay_msecs*1000);
		}

		fileoff += written;

		if (doreport)
		{
			report();
		}
		
		if (diff)
			bottom += written;

		/* exit the loop if a signal was received, or the time has expired */
		if ((exit_now) || (seconds && (time(NULL) >= seconds)))
			break;
	}

	if (dag_stop_stream(dagfd, dagstream) < 0)
		dagutil_panic("dag_stop_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));

	if (dag_detach_stream(dagfd, dagstream) < 0)
		dagutil_panic("dag_detach_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));

	dag_close(dagfd);

#if defined(_WIN32)
	close(outfile);
#endif
	return EXIT_SUCCESS;
}

static void
anysig(int sig)
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
    /* Restore the default signal handlers, so the next interrupt will have the default effect (e.g. exit) */
    dagutil_set_signal_handler(SIG_DFL);

    /* Tell the main loop to exit */
    exit_now = 1;
    return;

#elif defined(_WIN32) 

	/* FIXME: shouldn't really call this from a signal handler. */
	if (dag_stop(dagfd) < 0)
		_exit(EXIT_FAILURE);

	_commit(outfile);
#endif /* Platform-specific code. */

	_exit(EXIT_SUCCESS);
}

static void
alarmsig(int sig)
{
	doreport++;
}

static void
report(void)
{
	static struct timeval last;
	static int64_t lastoff;
	static char label[80] = "";

	struct timeval now;
	struct timeval tdiff;
	uint32_t odiff;
	double rate;

	gettimeofday(&now, NULL);
	if (0 == timerisset(&last))
	{
		/*
		 * Initial call
		 */
		if (dagutil_get_verbosity() > 1)
			snprintf(label, sizeof(label), "%s\t", dagname);
		last = now;
		return;
	}

	timersub(&now, &last, &tdiff);
	odiff = (uint32_t) (fileoff - lastoff);
	/*
	 * Bytes/mebisecond == Mebibytes/second.
	 */
	if (tdiff.tv_sec == 0 && tdiff.tv_usec == 0 )
	{
		rate = 0;
	}
	else
	{
		#if 0
		rate = ((double) odiff) / (((double) tdiff.tv_sec * ONE_MEBI) + tdiff.tv_usec);
		#endif
		rate = ((double) odiff) / (((double) tdiff.tv_sec *1000*1000) + tdiff.tv_usec);
	
	}
	#if 0
	dagutil_verbose("%s%10.3f MiBytes %8.3f MiBytes %8.3f MiBytes/sec (%d Megabps)\n", label,
		((double) fileoff) / ONE_MEBI, ((double) diff) / ONE_MEBI, rate, (unsigned int) ((((8 * rate*1024)/1000)*1024)/1000));
	#endif
	dagutil_verbose("%s%10.3f MegaBytes %8.3f LineRate(MB/s): %8.3f PCI Rate(MB/s): %8.3f MB/sec (%d Mbps)\n", label,
               		(((double) fileoff) * 64)/88000000 ,((((double) odiff) / 88)*64)/1000000  ,((double) odiff) / (1000*1000), rate, (unsigned int) ((((8 * rate)))));

	lastoff = fileoff;
	last = now;
	doreport = 0;
}


#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagsnap_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */
