/*
 * 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: dagsnapbis.c 13559 2010-12-21 00:53:02Z karthik.sharma $
 */

/* System headers. */
#ifdef _WIN32
	#include <direct.h>
#endif
#include <math.h>

/* Endace headers. */
#include "dagapi.h"
#include "dagutil.h"
#include "dagclarg.h"
#include "dag_platform.h"
#include "adt/adt_list.h"
#include <sys/stat.h>

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagsnapbis.c 13559 2010-12-21 00:53:02Z karthik.sharma $";
static const char* const kRevisionString = "$Revision: 13559 $";


/** Defines the application name */
#define PROGRAM_NAME      "dagsnapbis"



/** The size of the buffer to write the runname into */
#ifndef _MAX_FNAME
	#define _MAX_FNAME    128
#endif

/** The maximum size of the path supplied to the capture directory option */
#ifndef _MAX_DIR
	#define _MAX_DIR      1024
#endif

/** The maximum size of an individual path string */
#ifndef _MAX_PATH
	#define _MAX_PATH     (_MAX_DIR + _MAX_FNAME + 1)
#endif



/** The system slash character used by the OS, different for Windows and unix style */
#if defined(_WIN32) 
	#define FILE_SEP_STR    "\\"
	#define FILE_SEP_CHR    '\\'
#else
	#define FILE_SEP_STR    "/"
	#define FILE_SEP_CHR    '/'
#endif



/**
 * Macro that wraps up the difference between the windows and posix style
 * file write operations. This is used to remove the ugly #if / #else / #endif
 * macros from the source code.
 *
 */
#if defined(_WIN32) 

	#define WRITE_FILE(file, buf, count, written) \
		WriteFile (file, buf, count, &written, NULL);

	/* On windows we get an error when trying to do an unbuffered disk write with
	 * buffer sizes above about 16MB, something about insufficent resources. At
	 * this point in time I have no idea why, there is nothing in the documentation
	 * or knowledge base about this limitation.
	 * So anyway I've restricted the maximum unbuffered write size to 4MB, this seems
	 * to be a valid compromise between performance and the mythical max size. This
	 * assumes that 4MB is a multiple of the sector size, however I think this is a
	 * pretty safe bet.
	 */
	#define DIRECT_WRITE_SIZE	(4 * ONE_MEBI)

	#define WRITE_FILE_DIRECT(file, buf, count, written) \
		{ \
			off_t  offset = 0; \
			size_t amount_left = count; \
			size_t total_written = 0; \
			written = DIRECT_WRITE_SIZE; \
			while (amount_left > DIRECT_WRITE_SIZE ) \
			{ \
				WriteFile (file, (buf + offset), DIRECT_WRITE_SIZE, &written, NULL); \
				if ( written != DIRECT_WRITE_SIZE ) \
					break; \
				offset += DIRECT_WRITE_SIZE; \
				amount_left -= DIRECT_WRITE_SIZE; \
			} \
			if ( (written == DIRECT_WRITE_SIZE) && (amount_left > 0) ) \
			{ \
				WriteFile (file, (buf + offset), amount_left, &written, NULL); \
				if ( written == amount_left ) \
					written = count; \
			} \
		}
	

	/* When files are opened in un-buffered mode, we can't move the file pointer
	 * to a non-page boundary, which unfortunatly renders the SetEndOfFile useless.
	 * The way around this is to close and reopen the file in non-buffered mode,
	 * and then set the new file size based on byte alignment.
	 */
	#define TRUNCATE_FILE(file, filepath, size) \
		CloseHandle (file); \
		file = CreateFile (filepath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); \
		if ( file != INVALID_HANDLE_VALUE ) \
		{ \
        	LARGE_INTEGER li; \
			li.QuadPart = size; \
			SetFilePointer (file, li.LowPart, &li.HighPart, FILE_BEGIN); \
			SetEndOfFile (file); \
		}
		

	#define CLOSE_FILE(file) \
		if ( file != INVALID_HANDLE_VALUE ) \
			CloseHandle (file);
		
		
#elif defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

	#define WRITE_FILE(file, buf, count, written) \
		written = write (file, buf, count);
		
	#define WRITE_FILE_DIRECT(file, buf, count, written) \
		written = write (file, buf, count);

	#define TRUNCATE_FILE(file, filepath, size) \
		ftruncate (file, size);

	#define CLOSE_FILE(file) \
		if ( file > 0 ) \
			close (file);

#else
	
	#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.

#endif







/** Commandline argument codes. */
enum
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	CLA_SYSLOG,
#endif
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_MAXDATA,
	CLA_RUNNAME,
	CLA_INDV_TIMELIMIT,
	CLA_INDV_SIZELIMIT,
	CLA_ABSMAX_FILES,
	CLA_ABSMAX_SIZE,
	CLA_CAPTURE_DIR,
	CLA_MAX_PERFORMANCE
	
};


/** Possible bitmask options that can be OR'ed into the g_valid_options varaible */
enum
{
	OPTION_INDIV_TIMELIMT  = 0x00000001,
	OPTION_INDIV_SIZELIMIT = 0x00000002,
	OPTION_MAX_NUMFILES    = 0x00000004,
	OPTION_MAX_SIZE        = 0x00000008

};



/** Bitmask containing flags to indicate which of the following capture parameters are valid.
 *  The follwoing flags may be OR'ed together:
 *     OPTION_INDIV_TIMELIMT
 *     OPTION_INDIV_SIZELIMIT
 *     OPTION_MAX_NUMFILES
 *     OPTION_MAX_SIZE
 *
 */
static uint32_t  g_valid_options = 0;


/** The individul file time limit for capture in seconds. */
static uint32_t  g_indiv_timelimit = 0;

/** The individual file size limit for capture in mebibytes */
static uint64_t  g_indiv_sizelimit = 0;


/** The absoulte maximum limit of the number of files that can be created before the files are rotated */
static uint32_t  g_max_numfiles = 0;

/** The absolute maximum limit to the amount of data that can be captured before files are rotated */
static uint64_t  g_max_size = 0;



/** Boolean value that indicates if the report loop thread should be killed or not */
static int       g_kill_report_thread = 0;

/** Indicator of whether or not the verbosity output report should be updated or not */
static int       g_do_report = 0;

/** Indicator to tell the capture loop to stop the capture */
static int       g_stop_capture = 0;


/** The minimum chunk size, basically sets the number of bytes to copy in one file write.
 *  This variable may be scaled depending on the sze of the memory hole.
 */
static uint32_t  g_min_chunk_size = (8 * ONE_MEBI);


/** String buffer used to store error strings */
static char      g_err_str_p[256];




/**
 * Simple function that prints the version information for this tool.
 * The information is printed to stdout.
 * 
 *
 */
static void
print_version(void)
{
	printf(PROGRAM_NAME " (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}




/**
 * Simple function that prints the usage information for this tool.
 * The information is printed to stdout.
 *
 * @param[in]  clarg        A handle to the command line argument state.
 *
 */
static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf(PROGRAM_NAME " - Endace DAG card multi-file capture utility.\n");
	printf("Usage: " PROGRAM_NAME " -d<device> [options]\n");
	
	dagclarg_display_usage(clarg, stdout);
	
	printf("\n");
	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");
}




/**
 * Little utility function for converting strings with a number and a size
 * specifier after them into an actual byte count value. The possible options
 * that may be specified after the number are:
 *
 *     t, T     - Terrabytes
 *     g, G     - Gigabytes
 *     m, M     - Megabytes
 *     k, K     - Kilobytes
 *
 *
 * @param[in]  str          Pointer to the string to parse.
 *
 * @returns                 The byte counter converted from the string.
 *
 */
static uint64_t
parse_size_str(const char *str)
{
	uint64_t  val;
	char     *more_p;

#if defined(_WIN32)	
	val = _strtoui64 (str, &more_p, 10);
#else
	val = strtoull (str, &more_p, 10);
#endif
	switch (more_p[0])
	{
		case 'T':
		case 't':
			val *= ONE_TEBI;
			break;

		case 'G':
		case 'g':
			val *= ONE_GIBI;
			break;
					
		case '\0':
		case 'M':
		case 'm':
			val *= ONE_MEBI;
			break;
					
		case 'K':
		case 'k':
			val *= ONE_KIBI;
			break;
					
		default:
			break;
	}
	
	return val;
}




/**
 * Thread procdure that sleeps for a second and then wakes up to increment
 * a global variable that tells the main cpature thread to display some statistics
 * about the current capture.
 *
 * This whole mechanism count have been replaced by a timer signal, however on Windows
 * this is not possible (well we could use the timeSetEvent API function, but that
 * uses another thread anyway), so here we use a thread to tell the main loop when
 * to update the print out. 
 *
 * @param[in]  arg          User defined pointer, currently unsued.
 *
 * @returns                 The result code of the thread.
 *
 */
static dagutil_thread_return_t
report_thread_proc(void *arg)
{
	while ( !g_kill_report_thread )
	{
		sleep (1);
		g_do_report++;
	}
	
	return 0;
}





/**
 * Returns the data & time in the format required for the prefix of the individual
 * files. An optional format argument can be supplied, which allows the user to define
 * the format of the date string. The format must be of the same format supplied to
 * the strftime function.
 *
 * @param[in]  buf          Pointer to a buffer to store the string in.
 * @param[in]  maxsize      The maximum number of characters to store in the buffer.
 * @param[in]  format       An optional format string that defines the format for the
 *                          date time string.
 *
 *
 */
static void
get_data_time_str(char buf[], size_t maxsize, const char *format)
{
	static uint32_t seq_num = 0;
	static time_t   prev_now = 0; 
	time_t          now = time(NULL);
	char            seq_num_buf[32];

	
	/* check if the timestamp is going to be the same in which case increment the sequence number */
	if ( prev_now != now )
		seq_num = 0;
	else
		seq_num++;
	prev_now = now;
	

	/* format the string */
	if ( format )
		strftime(buf, maxsize, format, localtime(&now));
	else
		strftime(buf, maxsize, "%Y%m%d-%H%M%S", localtime(&now));
	
	
	/* append the sequence number */
	snprintf (seq_num_buf, 32, "-%02d", seq_num);
	strncat (buf, seq_num_buf, maxsize-1);
	
	
	/* ensure the string is terminated */
	buf[maxsize-1] = '\0';
}



/**
 * Returns a pointer to a string describing the last error that occuried. On unix
 * platforms this function just calls the strerror function with errno, on windows
 * the GetLastError() function is called and the string formated by FormatMessage
 * function.
 *
 * @returns                    A pointer to the error string, this is actually the
 *                             global g_err_str_p array.
 *
 */
static const char *
get_last_error_str(void)
{
#if defined(_WIN32) 
	FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), g_err_str_p, 256, NULL);
	g_err_str_p[255] = '\0';
#else
	strncpy (g_err_str_p, strerror(errno), 256);
	g_err_str_p[255] = '\0';
#endif

	return g_err_str_p;
}


/**
 * Creates a new capture file and returns a handle to the file. If there is
 * an error creating the file an error message is logged and the function
 * returns the error code. If a file with the same name already exists it
 * is overwritten by this function, and a warning message is logged.
 *
 * @param[out] file_p          Pointer to a variable that receives the file
 *                             handle created.
 * @param[in]  str_dir_p       The name of the directory to create the file in.
 * @param[in]  str_runname_p   The name of the run to use as the filename.
 * @param[out] str_filepath_pp An optional pointer to a char* pointer that
 *                             upon return will contain the path to the file
 *                             created. Memory is allocated for the file path,
 *                             it is the callers responsiblity to free the memory.
 *                             If this argument is NULL it is ignored.
 * @param[out] str_filename_pp An optional pointer that upon return will point to
 *                             the name portion of the filepath in str_filepath_pp.
 *                             If str_filepath_pp is NULL this argument is ignored.
 *                             If this parameter is NULL it is ignored.
 * @param[in]  direct          A non-zero value indicates that the file will
 *                             be created with un-buffered file writes (O_DIRECT)
 *                             enabled. On Windows this parameter is ignored.
 *
 * @returns                    An error code indicating success or failure.
 */
static int
#if defined(_WIN32) 
create_capture_file(HANDLE *file_p, const char *str_dir_p, const char *str_runname_p, char **str_filepath_pp, char **str_filename_pp, int direct)
#else
create_capture_file(int *file_p, const char *str_dir_p, const char *str_runname_p, char **str_filepath_pp, char **str_filename_pp, int direct)
#endif
{
	char            filename[_MAX_PATH] = "." FILE_SEP_STR;
	char            datetime[128];
#if defined(_WIN32) 
	DWORD           flags;
#else
	struct stat     status;
	int             flags;
#endif
	
	
	/* prepend the directory (if specified) */
	if ( str_dir_p )
	{
		strncpy (filename, str_dir_p, _MAX_PATH);
		strncat (filename, FILE_SEP_STR, _MAX_PATH);
	}
	
	/* add the data / time stamp */
	get_data_time_str (datetime, 128, NULL);
	strncat (filename, datetime, _MAX_PATH-1);
	strncat (filename, ".", _MAX_PATH-1);
	
	/* finally append the capture run name and terminate*/
	strncat (filename, str_runname_p, _MAX_PATH-1);
	filename[_MAX_PATH - 1] = '\0';
	
	
#if defined(_WIN32) 

	/* set the creation flags */
	flags = FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
	if ( direct )
		flags |= FILE_FLAG_NO_BUFFERING;


	/* create the new file */
	*file_p = CreateFile (filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, flags, NULL);
	if ( *file_p == INVALID_HANDLE_VALUE )
	{
		dagutil_error ("CreateFile(): %s\n", get_last_error_str());
		return -1;
	}


	/* check if a file with the same name existed when we created the new file */
	if ( GetLastError() == ERROR_ALREADY_EXISTS )
		dagutil_warning ("overwrote the file called \"%s\".\n", filename);

#else

	/* check if a file with the same name exists, this is only used for logging a warning */
	if ( stat(filename, &status) == 0 )
		dagutil_warning ("overwrote the file called \"%s\".\n", filename);
		
	
	/* set the creation flags */
	flags = O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE;
	if ( direct )
		flags |= O_DIRECT;
		
	
	/* create the new file */
	*file_p = open(filename, flags, 0664);
	if ( *file_p < 0 )
	{
		dagutil_error("open %s: %s\n", filename, strerror(errno));
		return errno;
	}

#endif


	/* debugging */
	dagutil_verbose_level (2, "created new capture file \"%s\"\n", filename);


	/* if the user has supplied a pointer to store the actual filename in, then we should return it */
	if ( str_filepath_pp )
	{
		*str_filepath_pp = (char*) malloc( strlen(filename) + 1 );
		strcpy (*str_filepath_pp, filename);
	}

	/* get just the filename portion of the new file */
	if ( str_filepath_pp && *str_filepath_pp && str_filename_pp )
	{
		*str_filename_pp = strrchr(*str_filepath_pp, FILE_SEP_CHR);
		(*str_filename_pp)++;
	}

	return 0;
}


/**
 * Gets the required block size for direct disk access, this is needed when
 * performing unbuffered disk writes on both windows and unix style platforms.
 * The block size returned is gauranteed to be a binary multiple, i.e 256, 512
 * 1024 ... etc.
 *
 * If any error occurs, an error message is output to stdout and -1 is returned.
 *
 * @param[in]  filename_p     Pointer to a NULL terminated string containing the
 *                            name of an existing file on the current drive that
 *                            is being used for unbuffered disk access.
 *
 * @returns                   A minimum block size / sector size that should be
 *                            used for direct disk access. If this function fails
 *                            -1 is returned.
 *                            
 */
static size_t
get_directio_block_size(const char *filepath_p)
{
	double       blk_size_log2;
	double       unused;

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

	struct stat  status;

	/* get the stats of the file and therefore the optimal block size */
	if ( stat(filepath_p, &status) != 0 )
	{
		dagutil_error ("stat %s : %s\n", filepath_p, strerror(errno));
		return (size_t)-1;
	}
	
	/* sanity check the block size is on a binary boundary, if not we are doomed */
	//blk_size_log2 = log2 (status.st_blksize);
	blk_size_log2 = (log10(status.st_blksize) / log10(2));
	if ( modf(blk_size_log2, &unused) != 0.0f )
	{
		dagutil_error ("direct file IO block size (%d) is not a binary multiple, aborting capture run\n", (unsigned int)status.st_blksize);
		return (size_t)-1;
	}

	return (size_t)status.st_blksize;

#else

	char    *strdiv_p = NULL;
	char    *strroot_p = NULL;
	char     strroot[_MAX_DIR + 1];
	DWORD    sector_size;
	size_t   amount;

	/* get the root path of the file */
	strdiv_p = strstr (filepath_p, FILE_SEP_STR);
	if ( !strdiv_p )
	{
		strroot_p = NULL;
	}
	else
	{
		amount = min(((size_t)(strdiv_p - filepath_p) + 1), _MAX_DIR);
		strncpy (strroot, filepath_p, amount);
		strroot[amount] = '\0';
		strroot_p = strroot;
	}

	/* get the sector size for the root path of the file */
	if ( !GetDiskFreeSpace(strroot_p, NULL, &sector_size, NULL, NULL) )
	{
		dagutil_error ("GetDiskFreeSpace %s : %s\n", strroot_p, get_last_error_str());
		return (size_t)-1;
	}

	/* sanity check the block size is on a binary boundary, if not we are doomed */
	blk_size_log2 = (log((double)sector_size) / log(2));
	if ( modf(blk_size_log2, &unused) != 0.0f )
	{
		dagutil_error ("direct file IO block size (%d) is not a binary multiple, aborting capture run\n", sector_size);
		return (size_t)-1;
	}

	return (size_t)sector_size;

#endif
}





/**
 * Prints out a report of the current capture to stdout, this function is designed
 * to be called roughly every second when the verbose option is supplied as a command
 * line option.
 *
 * @param[in]  dagname         The name of the dag card we are currently capturing on.
 * @param[in]  cur_filename_p  Pointer to a string buffer containing the file we are
 *                             are currently capturing to.
 * @param[in]  written         Should contain the number of bytes written to file during
 *                             the capture process.
 * @param[out] memhole_diff    The difference between the top and bottom pointers at the
 *                             time the function was called. This is used to give an
 *                             indication of the utilisation of the memory hole.
 *
 * @returns                    Nothing.
 */
static void
do_report(/*const char *dagname,*/ const char *cur_filename_p, uint64_t written, size_t memhole_diff)
{
	static struct timeval tv_last;
	static uint64_t       written_last_time;

	struct timeval        tv_now;
	struct timeval        tv_diff;
	uint64_t              written_diff;
	double                rate;


	/* get the current time and check if the last time was set */
	gettimeofday (&tv_now, NULL);
	if (0 == timerisset(&tv_last))
	{
		/* save the new last time */
		tv_last = tv_now;
		return;
	}

	/* calculate the time difference */
	timersub (&tv_now, &tv_last, &tv_diff);
	
	
	/* cacluate the number of bytes written to file since the last update */
	written_diff = (written - written_last_time);
	
	
	/* Bytes/mebisecond == Mebibytes/second. */
	if (tv_diff.tv_sec == 0 && tv_diff.tv_usec == 0 )
		rate = 0;
	else
		rate = ((double) written_diff) / (((double) tv_diff.tv_sec * ONE_MEBI) + tv_diff.tv_usec);
	
	
	/* do the actual print out */
	printf ("%s%10.3f MiBytes %8.3f MiBytes %8.3f MiBytes/sec (%d Mbps)\n", cur_filename_p, (((double)written) / ONE_MEBI), (((double)memhole_diff) / ONE_MEBI), rate, (unsigned int) (8 * rate));


	/* update the static previous state */
	written_last_time = written;
	tv_last = tv_now;
}





/**
 * This is the extended capture loop function, it continously loops through the data
 * in the memory buffer and writes it out to file. This function performs file
 * rotation and management. What differs this function from the standard capture loop
 * is the guarantee that all file writes are performed on page boundaries, a side
 * effect of this is that pad records may need to be inserted at the start of capture
 * files when new ones are created. Also all writes are performed using unbuffered
 * disk IO, which significantly increases performance of the application.
 *
 * @param[in]  dagfd          Descriptor handle for the DAG card.
 * @param[in]  dagstream      The stream to read from.
 * @param[in]  str_dir_p      Pointer to the string buffer containing the directory
 *                            to write all the files into.
 * @param[in]  str_runname_p  Pointer to the string to append onto the end of every
 *                            capture file.
 *
 *
 */
static void
capture_loop_ex (int dagfd, int dagstream, const char *str_dir_p, const char *str_runname_p)
{
	int                  err;
	uint32_t             written;
	struct stat          stats;
	struct timeval       maxwait;
	struct timeval       poll;
	uint8_t             *bottom_p = NULL;
	uint8_t             *top_p = NULL;
	size_t               diff;
	off_t                amount_to_write;
	off_t                align_offset;
	off_t                align_amount;
	time_t               start_time;
	uint64_t             file_size;
	ListPtr              file_list = NULL;
	char                *filepath_p = NULL;
	char                *filename_p = NULL;
	char                *first_filepath_p = NULL;
	uint32_t             stream_size;
	int                  delete_first_file;
	int                  force_write;
	int                  new_file;
	uint8_t             *align_buffer_mem_p = NULL;
	uint8_t             *align_buffer_p = NULL;
	size_t               block_size;

	uint64_t             total_run_size;                   /**< The total number of bytes captured during the run, this value
	                                                        *   will decrease when a file is deleted during file rotation.	*/
	uint32_t             total_file_count;                 /**< The total number of files in the run, this is decremented when
	                                                        *   a file is deleted from the run.                             */
	uint64_t             total_bytes_written;              /**< The absolute total number of bytes written, this value only
	                                                            increases and is unaffected by file rotation, used for stats. */

	int tmp;
#if defined(_WIN32) 
	HANDLE               file = INVALID_HANDLE_VALUE;
#else
	int                  file = 0;
#endif
	


	/* create the first capture file */
	if ( (err = create_capture_file(&file, str_dir_p, str_runname_p, &filepath_p, &filename_p, 1)) != 0 )
		return;

	
	

	/* get the optimal block size, this is used for direct (non-buffered) disk IO */
	block_size = get_directio_block_size (filepath_p);
	if ( block_size == (size_t)-1 )
		return;

	dagutil_verbose_level (2, "Using a minimum block size of 0x%04X(%u) to optimize disk write performance.\n", (unsigned int)block_size, (unsigned int)block_size);


	/* ensure the block size doesn't exceed 32k, this is because pad records can be no bigger than 64k */
	if ( block_size > (64 * ONE_KIBI) )
	{
		dagutil_warning ("Shrinking the optimal file block size from %u to %u.\n", (unsigned int)block_size, (32 * ONE_KIBI));
		block_size = (32 * ONE_KIBI);
	}
	


	
	/* create an ADT list that stores the file names, this is only needed if their is a maximum
	 * file count or total run size.
	 */
	if ( (g_valid_options & OPTION_MAX_NUMFILES) || (g_valid_options & OPTION_MAX_SIZE) )
	{
		file_list = adt_list_init(kListInterfacePlain, kListRepresentationArray, free);
		if ( file_list == NULL )
		{
			dagutil_error ("adt_list_init : %s\n", strerror(errno));
			return;
		}
		adt_list_add_first (file_list, (AdtPtr)filepath_p);
	}



	/* get the stream size and then determine what the minimum write chunk size will be */
	stream_size = (uint32_t) dag_get_stream_buffer_size (dagfd, dagstream);
	if ( stream_size < (ONE_MEBI * 2) )
		g_min_chunk_size = (32 * ONE_KIBI);
	else if ( ((stream_size - ONE_MEBI) < g_min_chunk_size) )
		g_min_chunk_size = (stream_size - ONE_MEBI);

		
	/* start the capture stream */
	if (dag_start_stream(dagfd, dagstream) < 0)
	{
		dagutil_error ("dag_start_stream : %s\n", strerror(errno));
		return;
	}
	

	/* create a 4 * block_size buffer for direct IO alignment, this buffer needs to be aligned to a block_size boundary */
	align_buffer_mem_p = dagutil_malloc( 4 * block_size );
	if ( !align_buffer_mem_p )
	{
		dagutil_error ("dagutil_malloc : %s\n", strerror(errno));
		return;
	}
	
	/* align and initialise the buffer */
	memset (align_buffer_mem_p, 0x00, (4 * block_size));
	align_buffer_p = (uint8_t*) (((uintptr_t)align_buffer_mem_p + block_size) & ~(block_size - 1));
	align_buffer_p[8] = TYPE_PAD;




	/* 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, g_min_chunk_size/*(32 * ONE_KIBI)*/, &maxwait, &poll);
	
	
	
	/* initialise the loop varaibles */
	start_time = time(NULL);
	file_size = 0;
	
	total_bytes_written = 0;
	total_run_size = 0;
	total_file_count = 0;


	/* reset loop state variables */
	delete_first_file = 0;
	force_write = 0;
	new_file = 0;



	/* infinite loop that performs the capture run */
	while (!g_stop_capture)
	{
		/* call nonblocking to permit reporting in the absence of packets. */
		top_p = dag_advance_stream (dagfd, dagstream, &bottom_p);
		if (NULL == top_p)
		{
			dagutil_error("dag_advance_stream %u: %s\n", dagstream, strerror(dag_get_last_error()));
			break;
		}
		
		
		/* calculate the amount of data in the buffer */
		diff = top_p - bottom_p;
		
		
		/* update the report print out if required */
		if ( g_do_report > 0 )
		{
			do_report (filename_p, total_bytes_written, diff);
			g_do_report = 0;
		}


		/* check if a the capture time for this file has been exceeded */
		if ( (g_valid_options & OPTION_INDIV_TIMELIMT) && (difftime(time(NULL), start_time) >= g_indiv_timelimit) )
		{
			force_write = 1;
			new_file = 1;
		}
		
		/* check if the new chunk of data will exceed the file size limit */
		else if ( (g_valid_options & OPTION_INDIV_SIZELIMIT) && ((file_size + diff) >= g_indiv_sizelimit) )
		{
			force_write = 1;
			new_file = 1;
		}
		
		
		
		
		/* check if the chunk size is significant enough to initate a write to the memory buffer */
		if ( (force_write && (diff > 0)) || (diff >= g_min_chunk_size) )
		{
			/* first check if the bottom_p pointer is aligned, if not we need to insert pad records */
			align_offset = ((uintptr_t)bottom_p & (block_size - 1));
			if ( align_offset )
			{
			
				/* calculate the amount of data to read from the hole that will be appended onto the end of the alignment buffer */
				align_amount = (block_size - align_offset);
				if ( align_offset < 16 )
					align_offset += block_size;
			
				/* copy the start of the packet into the end of the alignment buffer and set the pad record length */
				memcpy ((align_buffer_p + align_offset), bottom_p, align_amount);
				*((uint16_t*) (align_buffer_p + 10)) = ntohs( (uint16_t)(align_offset & 0xFFFF) );
				
				/* write the buffer into the file */
				WRITE_FILE_DIRECT(file, align_buffer_p, (align_offset + align_amount), written);
				if ( written != (align_offset + align_amount) )
				{
					dagutil_error("file write failed on the file \"%s\" because %s. Aborting capture run.\n", filepath_p, get_last_error_str());
					break;
				}
				
				/* adjust the bottom pointer and the diff calculation */
				bottom_p += align_amount;
				diff -= align_amount;

				/* add the paritial record byte count to the managed statistics */
				file_size += (align_offset + align_amount);
				total_run_size += align_amount;
				total_bytes_written += align_amount;
			}
		
		
			/* adjust the diff again so the amount to write is a multiple of 1k, the catch is that if we
			 * are going to create a new file after the write, then we need to round up the amount to write
			 * rather than round down.
			 */
			amount_to_write = diff;
			if ( new_file )
				amount_to_write += (block_size - 1);
			amount_to_write &= ~(block_size - 1);
			
		
			/* perform an aligned write operation */
			WRITE_FILE_DIRECT(file, bottom_p, amount_to_write, written);
			if ( written != amount_to_write )
			{
				dagutil_error("file write failed on the file \"%s\" because %s. Aborting capture run\n", filepath_p, get_last_error_str());
				break;
			}
			
			
			/* if we are creating a new file, then the amount written to the file is actually larger
			 * the amount of data in the buffer. This is done for direct IO, which requires fixed
			 * size memory buffers. The side effect of this is that we need to update the statistics
			 * and pointers differently, depending on whether we rounded the amount to write up or
			 * or down.
			 */
			if ( new_file )
			{
				/* rounded up so use the orginal difference between the bottom and top pointers */
				file_size += diff;
				total_run_size += diff;
				total_bytes_written += diff;

				/* update the bottom pointer */
				bottom_p = (uint8_t*) ((uintptr_t)bottom_p + diff);
			}
			else
			{
				/* rounded down so use the amount actually written to the file */
				file_size += written;
				total_run_size += written;
				total_bytes_written += written;
				
				/* update the bottom pointer */
				bottom_p = (uint8_t*) ((uintptr_t)bottom_p + written);
			}
			
		}
		
		
		
		/* check if we should be creating a new file */
		if ( new_file )
		{
			/* truncate the file to the correct size, as we may have add extra padding at the end to maintain alignment */
			tmp = TRUNCATE_FILE(file, filepath_p, file_size);

			/* close the current file */
			CLOSE_FILE(file);
			
		
		
			/* check if the first file in the capture sequence should be deleted and replaced */
			if ( (g_valid_options & OPTION_MAX_NUMFILES) && (++total_file_count >= g_max_numfiles) )
				delete_first_file = 1;
			
			/* check if the maximum capture size for the run has been exceeded */
			else if ( (g_valid_options & OPTION_MAX_SIZE) && (++total_run_size >= g_max_size) )
				delete_first_file = 1;
		
		
			
			/* delete the first file in the capture sequence */
			if ( delete_first_file && file_list )
			{
				/* get the path to the first file */
				first_filepath_p = (char*) adt_list_remove_last (file_list);
				if ( first_filepath_p == NULL )
					dagutil_warning ("failed to get the name of the first file in the capture run.\n");
					
				else
				{
					/* get the stats for the file we are deleting, and therefore also adjust the maximum file size */
					if ( stat(first_filepath_p, &stats) < 0 )
						dagutil_warning ("failed to read the stats for the file named \"%s\".\n", first_filepath_p);
					else
						total_run_size -= stats.st_size;
				
					/* decrement the total number of capture files we have */
					total_file_count--;
					
			
					/* attempt to delete the file */
					if ( remove(first_filepath_p) < 0 )
						dagutil_error ("failed to delete the file \"%s\" during rotation.\n", first_filepath_p);
				
					/* free the memory allocated for the file path */
					free (first_filepath_p);
				}
			}
			

			/* if not using an ADT list to store the files in then we have to free the memory allocated for the filename */
			if ( !file_list && filepath_p )
			{
				dagutil_free(filepath_p);
				filepath_p = NULL;
				filename_p = NULL;
			}
			

			/* create the new file */
			if ( (err = create_capture_file(&file, str_dir_p, str_runname_p, &filepath_p, &filename_p, 1)) != 0 )
				break;
			
			/* reset the per file variables */
			start_time = time(NULL);
			file_size = 0;

			/* add the new file to the head of the capture loop, if we are limiting the number / size of the files */
			if ( file_list )
				adt_list_add_first (file_list, (AdtPtr)filepath_p);

		}
		
		
		
		/* reset loop variables */
		delete_first_file = 0;
		force_write = 0;
		new_file = 0;
	}




	/* because we are copying in chunks rather than ERF records we need to add the last bit
	 * of the last packet to the file. The top pointer should still be accurate from the
	 * last time through the loop, so we can still use that. The only trick is we may need
	 * to round up to a block boundary and then truncate the file.
	 */
	diff = top_p - bottom_p;
	if ( diff != 0 )
	{
		/* calculate the amount to write in the end of the file (rounded up to a block boundary) */
		amount_to_write = diff + (block_size - 1);
		amount_to_write &= ~(block_size - 1);

		/* write the last bit on the record */
		WRITE_FILE_DIRECT(file, bottom_p, amount_to_write, written);
		if ( written != amount_to_write )
			dagutil_error("final file write failed on the file \"%s\" because %s. The last record in the file is truncated\n", filepath_p, get_last_error_str());
		else
			file_size += diff;
			
		/* truncate the file to the correct size, as we may have add extra padding at the end to maintain alignment */
		tmp = TRUNCATE_FILE(file, filepath_p, file_size);
	}	
	


	/* close the file */
	CLOSE_FILE( file );

	/* clean up the alignment buffer */
	if ( align_buffer_mem_p )
		dagutil_free (align_buffer_mem_p);
	

	/* clean up the history file list */
	if ( file_list )
		adt_list_dispose (file_list);
	else
		dagutil_free(filepath_p);



	/* stop the capture stream */
	if (dag_stop_stream(dagfd, dagstream) < 0)
		dagutil_error ("dag_stop_stream : %s\n", strerror(errno));

}



/**
 * This is the main capture loop function, it continously loops through the data
 * in the memory buffer and writes it out to file. This function performs file
 * rotation and management.
 *
 * This function differs from capture_loop_ex in that it doesn't use the O_DIRECT
 * flag and doesn't insert pad records.
 *
 * @param[in]  dagfd          Descriptor handle for the DAg card.
 * @param[in]  dagstream      The stream to read from.
 * @param[in]  str_dir_p      Pointer to the string buffer containing the directory
 *                            to write all the files into.
 * @param[in]  str_runname_p  Pointer to the string to append onto the end of every
 *                            capture file.
 *
 *
 */
static void
capture_loop (int dagfd, int dagstream, const char *str_dir_p, const char *str_runname_p)
{
	int                  err;
	uint32_t             written;
	struct stat          stats;
	struct timeval       maxwait;
	struct timeval       poll;
	uint8_t             *bottom_p = NULL;
	uint8_t             *top_p = NULL;
	size_t               diff;
	time_t               start_time;
	off_t                file_size;
	ListPtr              file_list = NULL;
	char                *filepath_p = NULL;
	char                *filename_p = NULL;
	char                *first_filepath_p = NULL;
	uint32_t             stream_size;
	uint64_t             total_run_size;
	uint32_t             total_file_count;
	uint64_t             total_bytes_written;
	int                  delete_first_file;
	int                  force_write;
	int                  new_file;

#if defined(_WIN32) 
	HANDLE               file = INVALID_HANDLE_VALUE;
#else
	int                  file = 0;
#endif
	

	/* create the first capture file */
	if ( (err = create_capture_file(&file, str_dir_p, str_runname_p, &filepath_p, &filename_p, 0)) != 0 )
		return;
	

	
	/* create an ADT list that stores the file names, this is only needed if their is a maximum
	 * file count or total run size.
	 */
	if ( (g_valid_options & OPTION_MAX_NUMFILES) || (g_valid_options & OPTION_MAX_SIZE) )
	{
		file_list = adt_list_init(kListInterfacePlain, kListRepresentationArray, free);
		if ( file_list == NULL )
		{
			dagutil_error ("adt_list_init : %s\n", strerror(errno));
			return;
		}
		adt_list_add_first (file_list, (AdtPtr)filepath_p);
	}



	/* get the stream size and then determine what the minimum write chunk size will be */
	stream_size = (uint32_t) dag_get_stream_buffer_size (dagfd, dagstream);
	if ( stream_size < (ONE_MEBI * 2) )
		g_min_chunk_size = (32 * ONE_KIBI);
	else if ( ((stream_size - ONE_MEBI) < g_min_chunk_size) )
		g_min_chunk_size = (stream_size - ONE_MEBI);

		
	/* start the capture stream */
	if (dag_start_stream(dagfd, dagstream) < 0)
	{
		dagutil_error ("dag_start_stream : %s\n", strerror(errno));
		return;
	}
	




	/* 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);
	
	
	
	/* initialise the loop varaibles */
	start_time = time(NULL);
	file_size = 0;
	
	total_bytes_written = 0;
	total_run_size = 0;
	total_file_count = 0;


	/* reset loop state variables */
	delete_first_file = 0;
	force_write = 0;
	new_file = 0;



	/* infinite loop that performs the capture run */
	while (!g_stop_capture)
	{
		/* call nonblocking to permit reporting in the absence of packets. */
		top_p = dag_advance_stream (dagfd, dagstream, &bottom_p);
		if (NULL == top_p)
		{
			dagutil_error("dag_advance_stream %u: %s\n", dagstream, strerror(dag_get_last_error()));
			break;
		}
		
		
		/* calculate the amount of data in the buffer */
		diff = top_p - bottom_p;
		
		
		/* update the report print out if required */
		if ( g_do_report > 0 )
		{
			do_report (filename_p, total_bytes_written, diff);
			g_do_report = 0;
		}


		/* check if a the capture time for this file has been exceeded */
		if ( (g_valid_options & OPTION_INDIV_TIMELIMT) && (difftime(time(NULL), start_time) >= g_indiv_timelimit) )
		{
			force_write = 1;
			new_file = 1;
		}
		
		/* check if the new chunk of data will exceed the file size limit */
		else if ( (g_valid_options & OPTION_INDIV_SIZELIMIT) && ((file_size + diff) >= g_indiv_sizelimit) )
		{
			force_write = 1;
			new_file = 1;
		}
		
		
		
		/* check if the chunk size is significant enough to initate a write to the memory buffer */
		if ( (force_write && (diff > 0)) || (diff >= g_min_chunk_size) )
		{
			/* perform a write operation */
			WRITE_FILE(file, bottom_p, diff, written);
			if ( written != diff )
			{
				dagutil_error("file write failed on the file \"%s\" because %s. Aborting capture run\n", filepath_p, get_last_error_str());
				break;
			}
			
			/* update the file size */
			file_size += diff;
			total_run_size += diff;
			total_bytes_written += diff;
			
			/* update the bottom pointer */
			bottom_p += written;
		}
		
		
		
		/* check if we should be creating a new file */
		if ( new_file )
		{
			/* close the current file */
			CLOSE_FILE(file);
			
		
			/* check if the first file in the capture sequence should be deleted and replaced */
			if ( (g_valid_options & OPTION_MAX_NUMFILES) && (++total_file_count >= g_max_numfiles) )
				delete_first_file = 1;
			
			/* check if the maximum capture size for the run has been exceeded */
			else if ( (g_valid_options & OPTION_MAX_SIZE) && (++total_run_size >= g_max_size) )
				delete_first_file = 1;
		
		
			
			/* delete the first file in the capture sequence */
			if ( delete_first_file )
			{
				/* get the path to the first file */
				first_filepath_p = (char*) adt_list_remove_last (file_list);
				if ( first_filepath_p == NULL )
					dagutil_warning ("failed to get the name of the first file in the capture run.\n");
					
				else
				{
					/* get the stats for the file we are deleting, and therefore also adjust the maximum file size */
					if ( stat(first_filepath_p, &stats) < 0 )
						dagutil_warning ("failed to read the stats for the file named \"%s\".\n", first_filepath_p);
					else
						total_run_size -= stats.st_size;
				
					/* decrement the total number of capture files we have */
					total_file_count--;
					
			
					/* attempt to delete the file */
					if ( remove(first_filepath_p) < 0 )
						dagutil_error ("failed to delete the file \"%s\" during rotation.\n", first_filepath_p);
				
					/* free the memory allocated for the file path */
					free (first_filepath_p);
				}
			}
			
			
			/* if not using an ADT list to store the files in then we have to free the memory allocated for the filename */
			if ( !file_list && filepath_p )
			{
				dagutil_free(filepath_p);
				filepath_p = NULL;
				filename_p = NULL;
			}

			/* create the new file */
			if ( (err = create_capture_file(&file, str_dir_p, str_runname_p, &filepath_p, &filename_p, 0)) != 0 )
				break;
			
			/* reset the per file variables */
			start_time = time(NULL);
			file_size = 0;

			/* add the new file to the head of the capture loop, if we are limiting the number / size of the files */
			if ( file_list )
				adt_list_add_first (file_list, (AdtPtr)filepath_p);

		}
		
		
		
		/* reset loop variables */
		delete_first_file = 0;
		force_write = 0;
		new_file = 0;
	}
	
	
	/* close the file */
	CLOSE_FILE( file );


	/* clean up the history file list */
	if ( file_list )
		adt_list_dispose (file_list);
	else if ( filepath_p )
		dagutil_free (filepath_p);
		


	/* stop the capture stream */
	if (dag_stop_stream(dagfd, dagstream) < 0)
		dagutil_error ("dag_stop_stream : %s\n", strerror(errno));

}




/**
 * Signal handler called when the user sends a signal to this running program, all
 * signals are handled by this function. This function will then signal the main
 * loop to stop the capture.
 *
 * @param[in]  sig             The signal number received.
 *
 *
 */
static void
signal_recv(int sig)
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

    /* restore the default signal handlers, so the next interrupt will have the default effect (e.g. exit) */
    dagutil_set_signal_handler(SIG_DFL);

#endif /* Platform-specific code. */

    /* tell the main capture loop to exit */
    g_stop_capture = 1;
}





/**
 * The effective entry point for the application. This function handles the command
 * line processing and opening a capture stream, it then calls the capture_loop function
 * which performs the actual capture.
 *
 * @param[in]  argc         The number of command line arguments.
 * @param[in]  argv         A pointer to an array of command line arguments.
 *
 * @returns                 The result of the program, either EXIT_SUCCESS or
 *                          EXIT_FAILURE.
 *
 */
int
dagsnapbis_main(int argc, char *argv[])
{
	char                 dagname_buf[DAGNAME_BUFSIZE] = "dag0";
	char                 dagname[DAGNAME_BUFSIZE];
	int                  dagfd;
	int                  dagstream;

	ClArgPtr             clarg = NULL;
	FILE                *errorfile = NULL;
	int                  code;
	int                  result;
	int                  argindex;
	
	int                  max_performance = 0;
	char                 run_name_buf[_MAX_FNAME];
	char                 capture_dir_buf[_MAX_DIR];
	char                 filesize_buf[128];
	char                 maxsize_buf[128];
	dagutil_thread_id_t  thread_id;
	int                  err;
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	int                  use_syslog = 0;
#endif
	int tmp;
	
	
	/* set the default options */
#if defined(_WIN32)
	_getcwd (capture_dir_buf, _MAX_DIR);
#else
	tmp = (intptr_t) getcwd (capture_dir_buf, _MAX_DIR);
#endif
	strcpy (run_name_buf, "erf");
	strcpy (dagname_buf, "dag0");


	/* set the name of program to use for all the verboe output messages */
	dagutil_set_progname (PROGRAM_NAME);


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

	/* the usual options */
	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 (clarg, "increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	dagclarg_add (clarg, "route all capture messages to syslog.", "--syslog", 'l', CLA_SYSLOG);
#endif
	dagclarg_add (clarg, "display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add_string (clarg, "DAG device to use (default is dag0).", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add (clarg, "maximize disk write performance - may insert pad records at the start of files.", "--maxwrite", 'j', CLA_MAX_PERFORMANCE);

	dagclarg_add_string (clarg, "capture directory, all capture files will be stored in this directory (default is \"."FILE_SEP_STR"\").", "--capture-dir", 'f', "path", capture_dir_buf, _MAX_DIR, CLA_CAPTURE_DIR);
	dagclarg_add_string (clarg, "capture run name, appended after the timestamp on all filenames (default is \"erf\").", "--name-of-run", 'o', "runname", run_name_buf, _MAX_FNAME, CLA_RUNNAME);

	/* individual file options */
	dagclarg_add_uint (clarg, "capture time limit per individual file.", "--time-limit", 's', "seconds", &g_indiv_timelimit, CLA_INDV_TIMELIMIT);
	dagclarg_add_string (clarg, "file size limit per individual file.\n"
	"                                    k, m, g, t suffixes for kilobytes, megabytes, gigabytes, terabytes", "--size-limit", 'm', "N[k|m|g|t]", filesize_buf, 128, CLA_INDV_SIZELIMIT);

	/* absolute limits */
	dagclarg_add_uint (clarg, "the maximum number of files to create before rotating files.", "--max-files", 'r', "numfiles", &g_max_numfiles, CLA_ABSMAX_FILES);
	dagclarg_add_string (clarg, "the maximum number of bytes to capture before rotating files.\n"
	"                                    k, m, g, t suffixes for kilobytes, megabytes, gigabytes, terabytes", "--max-size", 't', "N[k|m|g|t]", maxsize_buf, 128, CLA_ABSMAX_SIZE);
	
	


	/* 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;

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

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				break;

			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;

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

			case CLA_MAX_PERFORMANCE:
				max_performance = 1;
				break;

			case CLA_RUNNAME:
				break;
				
			case CLA_CAPTURE_DIR:
				break;

			case CLA_INDV_TIMELIMIT:
				g_valid_options |= OPTION_INDIV_TIMELIMT;
				break;

			case CLA_INDV_SIZELIMIT:
				g_valid_options |= OPTION_INDIV_SIZELIMIT;
				g_indiv_sizelimit = parse_size_str(filesize_buf);
				break;

			case CLA_ABSMAX_FILES:
				g_valid_options |= OPTION_MAX_NUMFILES;
				break;

			case CLA_ABSMAX_SIZE:
				g_valid_options |= OPTION_MAX_SIZE;
				g_max_size = parse_size_str(maxsize_buf);
				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);
	}


	/* sanity check the arguments */
	if ( (g_valid_options & OPTION_INDIV_SIZELIMIT) && (g_indiv_sizelimit > (4 * (uint64_t)ONE_GIBI)) )
	{
		dagutil_error("invalid option for the maximum size of file (-m), it should be less than 4GB.\n"); 
		return EXIT_FAILURE;
	}
	if ( (g_valid_options & OPTION_INDIV_SIZELIMIT) && (g_indiv_sizelimit < ONE_MEBI) )
	{
		dagutil_error("invalid option for the maximum size of file (-m), it should be greater than 1MB.\n"); 
		return EXIT_FAILURE;
	}
	
	if ( (g_valid_options & OPTION_MAX_NUMFILES) && (g_max_numfiles <= 1) )
	{
		dagutil_error("invalid option for the maximum number of files (-r), it should be greater than 1.\n"); 
		return EXIT_FAILURE;
	}
	if ( (g_valid_options & OPTION_MAX_SIZE) && (g_valid_options & OPTION_INDIV_SIZELIMIT) && (g_max_size <= (g_indiv_sizelimit * 2)) )
	{
		dagutil_error("invalid option for the maximum capture run size (-t), it should be greater than twice the individual file size limit.\n"); 
		return EXIT_FAILURE;
	}
	

	/* the following are non-fatal argument settings but may result in unexpected behaviour */
	if ( (g_valid_options & OPTION_MAX_SIZE) && !((g_valid_options & OPTION_INDIV_SIZELIMIT) || (g_valid_options & OPTION_INDIV_TIMELIMT)) )
	{
		dagutil_warning("the maximum capture size (-t) option must be specified with either the individual file size option(-m) or the individual time limit option(-s), otherwise it has no effect.\n"); 
	}
	if ( (g_valid_options & OPTION_MAX_NUMFILES) && !((g_valid_options & OPTION_INDIV_SIZELIMIT) || (g_valid_options & OPTION_INDIV_TIMELIMT)) )
	{
		dagutil_warning("the maximum file count (-r) option must be specified with either the individual file size option(-m) or the individual time limit option(-s), otherwise it has no effect.\n"); 
	}



	/* attempt to open a connection to the card and attach to the stream */
	if((dagfd = dag_open(dagname)) < 0)
	{
		dagutil_error("dag_open %s: %s\n", dagname, strerror(errno));
		return EXIT_FAILURE;
	}
	if(dag_attach_stream(dagfd, dagstream, 0, 0) < 0)
	{
		dagutil_error("dag_attach_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));
		return EXIT_FAILURE;
	}



	/* if the verbosity option was specified then we need to create another thread for reporting the capture status */
	if ( dagutil_get_verbosity() > 0 )
	{
		err = dagutil_thread_create(report_thread_proc, NULL, &thread_id);
		if ( err != 0 )
		{
			dagutil_error("dagutil_thread_create: %s\n", strerror(err));
			return EXIT_FAILURE;
		}
	}



	/* setup a signal handler so the last file write is commited before we exit */
	dagutil_set_signal_handler(signal_recv);


	/* enable message routing to syslog if requested by user */
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	if ( use_syslog )
		dagutil_daemon(LOG_CONS, LOG_USER);
#endif
		
	
	
	/* start the capture loop */
	if ( max_performance )
		capture_loop_ex (dagfd, dagstream, capture_dir_buf, run_name_buf);
	else
		capture_loop (dagfd, dagstream, capture_dir_buf, run_name_buf);


	/* kill the thread */
	g_kill_report_thread = 1;
	
	
	/* close the handle to the dag card */
	if (dag_detach_stream(dagfd, dagstream) < 0)
	{
		dagutil_error("dag_detach_stream %s:%u: %s\n", dagname, dagstream, strerror(errno));
		return EXIT_FAILURE;
	}
	if (dag_close(dagfd) < 0)
	{
		dagutil_error("dag_close %s: %s\n", dagname, strerror(errno));
		return EXIT_FAILURE;
	}
	
	
	return EXIT_SUCCESS;
}





/**
 * Used to get around the unit test framework that doesn't accurately check
 * the return code off programs executed remotely.
 *
 * @param[in]  argc         The number of command line arguments.
 * @param[in]  argv         A pointer to an array of command line arguments.
 *
 * @returns                 The result of the program, either EXIT_SUCCESS or
 *                          EXIT_FAILURE.
 */
#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagsnapbis_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */


