/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 */
/*
 * iogen - a tool for generating file/sds io for a doio process
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#ifdef CRAY
#include <sys/file.h>
#include <sys/iosw.h>
#include <sys/listio.h>
#endif
#ifdef sgi
#include <sys/statvfs.h>
#include <sys/fs/xfs_itable.h>
#endif

#ifdef CRAY
#include "libkern.h"
#endif
#include "doio.h"
#include "bytes_by_prefix.h"
#include "string_to_tokens.h"
#include "open_flags.h"
#include "random_range.h"

#ifndef PATH_MAX
#define	PATH_MAX 512		/* ??? */
#endif

#ifndef BSIZE
#ifdef linux
#define BSIZE DEV_BSIZE
#else
#define BSIZE 512
#endif
#endif

#define RAW_IO(_flags_)	((_flags_) & (O_RAW | O_SSD))

#ifndef __linux__
extern char *sys_errlist[];
#endif
#define SYSERR	strerror(errno)

/*
 * Structure for retaining test file information
 */

struct file_info {
	char f_path[MAX_FNAME_LENGTH + 1];	/* file name (full path)    */
	int f_length;		/* length in bytes                      */
	int f_iou;		/* file iounit                          */
	int f_riou;		/* file raw iounit (for O_RAW/O_SSD)    */
	int f_dalign;		/* direct I/O alignment                 */
	int f_nextoff;		/* offset of end of last io operation   */
	int f_type;		/* file type S_IFREG, etc...            */
	int f_lastoffset;	/* offset of last io operation          */
	int f_lastlength;	/* length of last io operation          */
};

/*
 * Simple structure for associating strings with values - useful for converting
 * cmdline args to internal values, as well as printing internal values in
 * a human readable form.
 */

struct strmap {
	char *m_string;
	int m_value;
	int m_flags;
};

void startup_info(FILE * stream, int seed);
int init_output(void);
int form_iorequest(struct io_req *req);
int get_file_info(struct file_info *rec);
int create_file(char *path, int nbytes);
int str_to_value(struct strmap *map, char *str);
struct strmap *str_lookup(struct strmap *map, char *str);
char *value_to_string(struct strmap *map, int val);
int parse_cmdline(int argc, char **argv, char *opts);
int help(FILE * stream);
int usage(FILE * stream);

/*
 * Declare cmdline option flags/variables initialized in parse_cmdline()
 */

#define OPTS	"a:dhf:i:L:m:op:qr:s:t:T:O:N:"

int a_opt = 0;			/* async io comp. types supplied            */
int o_opt = 0;			/* form overlapping requests                */
int f_opt = 0;			/* test flags                               */
int i_opt = 0;			/* iterations - 0 implies infinite          */
int L_opt = 0;			/* listio min-max nstrides & nents          */
int m_opt = 0;			/* offset mode                              */
int O_opt = 0;			/* file creation Open flags                 */
int p_opt = 0;			/* output pipe - default is stdout          */
int r_opt = 0;			/* specify raw io multiple instead of       */
				/* getting it from the mounted on device.   */
				/* Only applies to regular files.           */
int s_opt = 0;			/* syscalls                                 */
int t_opt = 0;			/* min transfer size (bytes)                */
int T_opt = 0;			/* max transfer size (bytes)                */
int q_opt = 0;			/* quiet operation on startup               */
char TagName[40];		/* name of this iogen (see Monster)         */
struct strmap *Offset_Mode;	/* M_SEQUENTIAL, M_RANDOM, etc.             */
int Iterations;			/* # requests to generate (0 --> infinite)  */
int Time_Mode = 0;		/* non-zero if Iterations is in seconds     */
				/* (ie. -i arg was suffixed with 's')       */
char *Outpipe;			/* Pipe to write output to if p_opt         */
int Mintrans;			/* min io transfer size                     */
int Maxtrans;			/* max io transfer size                     */
int Rawmult;			/* raw/ssd io multiple (from -r)            */
int Minstrides;			/* min # of listio strides per request      */
int Maxstrides;			/* max # of listio strides per request      */
int Oflags;			/* open(2) flags for creating files         */
int Ocbits;			/* open(2) cbits for creating files         */
int Ocblks;			/* open(2) cblks for creating files         */
int Orealtime = 0;		/* flag set for -O REALTIME                 */
int Oextsize = 0;		/* real-time extent size                    */
int Oreserve = 1;		/* flag for -O [no]reserve                  */
int Oallocate = 0;		/* flag for -O allocate                     */
int Owrite = 1;			/* flag for -O nowrite                      */

int Nfiles = 0;			/* # files on cmdline                       */
struct file_info *File_List;	/* info about each file                     */
int Nflags = 0;			/* # flags on cmdline                       */
struct strmap *Flag_List[128];	/* flags selected from cmdline              */
int Nsyscalls = 0;		/* # syscalls on cmdline                    */
struct strmap *Syscall_List[128];	/* syscalls selected on cmdline          */
int Fileio = 0;			/* flag indicating that a file              */
				/* io syscall has been chosen.              */
int Naio_Strat_Types = 0;	/* # async io completion types              */
struct strmap *Aio_Strat_List[128];	/* Async io completion types           */

/*
 * Map async io completion modes (-a args) names to values.  Macros are
 * defined in doio.h.
 */

struct strmap Aio_Strat_Map[] = {
#ifndef linux
	{"poll", A_POLL},
	{"signal", A_SIGNAL},
#else
	{"none", 0},
#endif /* !linux */
#ifdef CRAY
#if _UMK || RELEASE_LEVEL >= 8000
	{"recall", A_RECALL},
#endif

#ifdef RECALL_SIZEOF
	{"recalla", A_RECALLA},
#endif
	{"recalls", A_RECALLS},
#endif /* CRAY */

#ifdef sgi
	{"suspend", A_SUSPEND},
	{"callback", A_CALLBACK},
#endif
	{NULL, -1}
};

/*
 * Offset_Mode #defines
 */

#define M_RANDOM    	1
#define M_SEQUENTIAL	2
#define M_REVERSE   	3

/*
 * Map offset mode (-m args) names to values
 */

struct strmap Omode_Map[] = {
	{"random", M_RANDOM},
	{"sequential", M_SEQUENTIAL},
	{"reverse", M_REVERSE},
	{NULL, -1}
};

/*
 * Map syscall names (-s args) to values - macros are defined in doio.h.
 */
#define	SY_ASYNC	00001
#define	SY_WRITE	00002
#define	SY_SDS		00010
#define	SY_LISTIO	00020
#define	SY_NENT		00100	/* multi entry vs multi stride >>> */

struct strmap Syscall_Map[] = {
	{"read", READ, 0},
	{"write", WRITE, SY_WRITE},
#ifdef CRAY
	{"reada", READA, SY_ASYNC},
	{"writea", WRITEA, SY_WRITE | SY_ASYNC},
#ifndef _CRAYMPP
	{"ssread", SSREAD, SY_SDS},
	{"sswrite", SSWRITE, SY_WRITE | SY_SDS},
#endif
	{"listio", LISTIO, SY_ASYNC},

	/* listio as 4 system calls */
	{"lread", LREAD, 0},
	{"lreada", LREADA, SY_ASYNC},
	{"lwrite", LWRITE, SY_WRITE},
	{"lwritea", LWRITEA, SY_WRITE | SY_ASYNC},

	/* listio with nstrides > 1 */
	{"lsread", LSREAD, 0},
	{"lsreada", LSREADA, SY_ASYNC},
	{"lswrite", LSWRITE, SY_WRITE},
	{"lswritea", LSWRITEA, SY_WRITE | SY_ASYNC},

	/* listio with nents > 1 */
	{"leread", LEREAD, 0 | SY_NENT},
	{"lereada", LEREADA, SY_ASYNC | SY_NENT},
	{"lewrite", LEWRITE, SY_WRITE | SY_NENT},
	{"lewritea", LEWRITEA, SY_WRITE | SY_ASYNC | SY_NENT},

	/* listio with nents > 1 & nstrides > 1 */

	/* all listio system calls under one name */
	{"listio+", LREAD, 0},
	{"listio+", LREADA, SY_ASYNC},
	{"listio+", LWRITE, SY_WRITE},
	{"listio+", LWRITEA, SY_WRITE | SY_ASYNC},
	{"listio+", LSREAD, 0},
	{"listio+", LSREADA, SY_ASYNC},
	{"listio+", LSWRITE, SY_WRITE},
	{"listio+", LSWRITEA, SY_WRITE | SY_ASYNC},
	{"listio+", LEREAD, 0 | SY_NENT},
	{"listio+", LEREADA, SY_ASYNC | SY_NENT},
	{"listio+", LEWRITE, SY_WRITE | SY_NENT},
	{"listio+", LEWRITEA, SY_WRITE | SY_ASYNC | SY_NENT},
#endif

#ifdef sgi
	{"pread", PREAD},
	{"pwrite", PWRITE, SY_WRITE},
	{"aread", AREAD, SY_ASYNC},
	{"awrite", AWRITE, SY_WRITE | SY_ASYNC},
#if 0
	/* not written yet */
	{"llread", LLREAD, 0},
	{"llaread", LLAREAD, SY_ASYNC},
	{"llwrite", LLWRITE, 0},
	{"llawrite", LLAWRITE, SY_ASYNC},
#endif
	{"resvsp", RESVSP, SY_WRITE},
	{"unresvsp", UNRESVSP, SY_WRITE},
	{"reserve", RESVSP, SY_WRITE},
	{"unreserve", UNRESVSP, SY_WRITE},
	{"ffsync", DFFSYNC, SY_WRITE},
#endif /* SGI */

#ifndef CRAY
	{"readv", READV},
	{"writev", WRITEV, SY_WRITE},
	{"mmread", MMAPR},
	{"mmwrite", MMAPW, SY_WRITE},
	{"fsync2", FSYNC2, SY_WRITE},
	{"fdatasync", FDATASYNC, SY_WRITE},
#endif

	{NULL, -1}
};

/*
 * Map open flags (-f args) to values
 */
#define	FLG_RAW		00001

struct strmap Flag_Map[] = {
	{"buffered", 0, 0},
	{"sync", O_SYNC, 0},
#ifdef CRAY
	{"raw", O_RAW, FLG_RAW},
	{"raw+wf", O_RAW | O_WELLFORMED, FLG_RAW},
	{"raw+wf+ldraw", O_RAW | O_WELLFORMED | O_LDRAW, FLG_RAW},
	{"raw+wf+ldraw+sync", O_RAW | O_WELLFORMED | O_LDRAW | O_SYNC, FLG_RAW},
#ifdef O_SSD
	{"ssd", O_SSD, FLG_RAW},
#endif
#ifdef O_LDRAW
	{"ldraw", O_LDRAW, 0},
#endif
#ifdef O_PARALLEL
	{"parallel", O_PARALLEL | O_RAW | O_WELLFORMED,
	 FLG_RAW},
	{"parallel+sync", O_PARALLEL | O_RAW | O_WELLFORMED | O_SYNC,
	 FLG_RAW},
	{"parallel+ldraw", O_PARALLEL | O_RAW | O_WELLFORMED | O_LDRAW,
	 FLG_RAW},
	{"parallel+ldraw+sync",
	 O_PARALLEL | O_RAW | O_WELLFORMED | O_LDRAW | O_SYNC,
	 FLG_RAW},
#endif
#endif /* CRAY */

#ifdef sgi
	{"direct", O_DIRECT, FLG_RAW},
	{"dsync", O_DSYNC},	/* affects writes */
	{"rsync", O_RSYNC},	/* affects reads */
	{"rsync+dsync", O_RSYNC | O_DSYNC},
#endif
	{NULL, -1}
};

/*
 * Map file types to strings
 */

struct strmap Ftype_Map[] = {
	{"regular", S_IFREG},
	{"blk-spec", S_IFBLK},
	{"chr-spec", S_IFCHR},
	{NULL, 0}
};

/*
 * Misc declarations
 */

int Sds_Avail;

char Byte_Patterns[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z'
};

int main(int argc, char **argv)
{
	int rseed, outfd, infinite;
	time_t start_time;
	struct io_req req;

	umask(0);

#ifdef CRAY
	Sds_Avail = sysconf(_SC_CRAY_SDS);
#else
	Sds_Avail = 0;
#endif

	TagName[0] = '\0';
	parse_cmdline(argc, argv, OPTS);

	/*
	 * Initialize output descriptor.
	 */
	if (!p_opt) {
		outfd = 1;
	} else {
		outfd = init_output();
	}

	rseed = getpid();
	random_range_seed(rseed);	/* initialize random number generator */

	/*
	 * Print out startup information, unless we're running in quiet mode
	 */
	if (!q_opt)
		startup_info(stderr, rseed);
	{
		struct timeval ts;
		gettimeofday(&ts, NULL);
		start_time = ts.tv_sec;
	}
	/*
	 * While iterations (or forever if Iterations == 0) - compute an
	 * io request, and write the structure to the output descriptor
	 */

	infinite = !Iterations;
	struct timeval ts;
	gettimeofday(&ts, NULL);
	while (infinite ||
	       (!Time_Mode && Iterations--) ||
	       (Time_Mode && (ts.tv_sec - start_time <= Iterations))) {
		gettimeofday(&ts, NULL);
		memset(&req, 0, sizeof(struct io_req));
		if (form_iorequest(&req) == -1) {
			fprintf(stderr, "iogen%s:  form_iorequest() failed\n",
				TagName);
			continue;
		}

		req.r_magic = DOIO_MAGIC;
		if (write(outfd, (char *)&req, sizeof(req)) == -1)
			perror("Warning: Could not write");
	}

	exit(0);

}				/* main */

void startup_info(FILE * stream, int seed)
{
	char *value_to_string(), *type;
	int i;

	fprintf(stream, "\n");
	fprintf(stream, "iogen%s starting up with the following:\n", TagName);
	fprintf(stream, "\n");

	fprintf(stream, "Out-pipe:              %s\n",
		p_opt ? Outpipe : "stdout");

	if (Iterations) {
		fprintf(stream, "Iterations:            %d", Iterations);
		if (Time_Mode)
			fprintf(stream, " seconds");

		fprintf(stream, "\n");
	} else {
		fprintf(stream, "Iterations:            Infinite\n");
	}

	fprintf(stream, "Seed:                  %d\n", seed);

	fprintf(stream, "Offset-Mode:           %s\n", Offset_Mode->m_string);

	fprintf(stream, "Overlap Flag:          %s\n", o_opt ? "on" : "off");

	fprintf(stream,
		"Mintrans:              %-11d (%d blocks)\n",
		Mintrans, (Mintrans + BSIZE - 1) / BSIZE);

	fprintf(stream,
		"Maxtrans:              %-11d (%d blocks)\n",
		Maxtrans, (Maxtrans + BSIZE - 1) / BSIZE);

	if (!r_opt)
		fprintf(stream,
			"O_RAW/O_SSD Multiple:  (Determined by device)\n");
	else
		fprintf(stream,
			"O_RAW/O_SSD Multiple:  %-11d (%d blocks)\n",
			Rawmult, (Rawmult + BSIZE - 1) / BSIZE);

	fprintf(stream, "Syscalls:              ");
	for (i = 0; i < Nsyscalls; i++)
		fprintf(stream, "%s ", Syscall_List[i]->m_string);
	fprintf(stream, "\n");

	fprintf(stream, "Aio completion types:  ");
	for (i = 0; i < Naio_Strat_Types; i++)
		fprintf(stream, "%s ", Aio_Strat_List[i]->m_string);
	fprintf(stream, "\n");

	if (Fileio) {
		fprintf(stream, "Flags:                 ");
		for (i = 0; i < Nflags; i++)
			fprintf(stream, "%s ", Flag_List[i]->m_string);

		fprintf(stream, "\n");
		fprintf(stream, "\n");
		fprintf(stream, "Test Files:  \n");
		fprintf(stream, "\n");
		fprintf(stream,
			"Path                                          Length    iou   raw iou file\n");
		fprintf(stream,
			"                                              (bytes) (bytes) (bytes) type\n");
		fprintf(stream,
			"-----------------------------------------------------------------------------\n");

		for (i = 0; i < Nfiles; i++) {
			type = value_to_string(Ftype_Map, File_List[i].f_type);
			fprintf(stream, "%-40s %12d %7d %7d %s\n",
				File_List[i].f_path, File_List[i].f_length,
				File_List[i].f_iou, File_List[i].f_riou, type);
		}
	}
}

/*
 * Initialize output descriptor.  If going to stdout, its easy,
 * otherwise, attempt to create a FIFO on path Outpipe.  Exit with an
 * error code if this cannot be done.
 */
int init_output(void)
{
	int outfd;
	struct stat sbuf;

	if (stat(Outpipe, &sbuf) == -1) {
		if (errno == ENOENT) {
			if (mkfifo(Outpipe, 0666) == -1) {
				fprintf(stderr,
					"iogen%s:  Could not mkfifo %s:  %s\n",
					TagName, Outpipe, SYSERR);
				exit(2);
			}
		} else {
			fprintf(stderr,
				"iogen%s:  Could not stat outpipe %s:  %s\n",
				TagName, Outpipe, SYSERR);
			exit(2);
		}
	} else {
		if (!S_ISFIFO(sbuf.st_mode)) {
			fprintf(stderr,
				"iogen%s:  Output file %s exists, but is not a FIFO\n",
				TagName, Outpipe);
			exit(2);
		}
	}

	if ((outfd = open(Outpipe, O_RDWR)) == -1) {
		fprintf(stderr,
			"iogen%s:  Couldn't open outpipe %s with flags O_RDWR: %s\n",
			TagName, Outpipe, SYSERR);
		exit(2);
	}

	return (outfd);
}

/*
 * Main io generation function.  form_iorequest() selects a system call to
 * do based on cmdline arguments, and proceeds to select parameters for that
 * system call.
 *
 * Returns 0 if req is filled in with a complete doio request, otherwise
 * returns -1.
 */

int form_iorequest(struct io_req *req)
{
	int mult, offset = 0, length = 0, slength;
	int minlength, maxlength, laststart, lastend;
	int minoffset, maxoffset;
	int maxstride, nstrides;
	char pattern, *errp;
	struct strmap *flags, *sc, *aio_strat;
	struct file_info *fptr;
#ifdef CRAY
	int opcode, cmd;
#endif

	/*
	 * Choose system call, flags, and file
	 */

	sc = Syscall_List[random_range(0, Nsyscalls - 1, 1, NULL)];
	req->r_type = sc->m_value;

#ifdef CRAY
	if (sc->m_value == LISTIO) {
		opcode = random_range(0, 1, 1, NULL) ? LO_READ : LO_WRITE;
		cmd = random_range(0, 1, 1, NULL) ? LC_START : LC_WAIT;
	}
#endif

	if (sc->m_flags & SY_WRITE)
		pattern =
		    Byte_Patterns[random_range
				  (0, sizeof(Byte_Patterns) - 1, 1, NULL)];
	else
		pattern = 0;

#if CRAY
	/*
	 * If sds io, simply choose a length (possibly pattern) and return
	 */

	if (sc->m_flags & SY_SDS) {
		req->r_data.ssread.r_nbytes =
		    random_range(Mintrans, Maxtrans, BSIZE, NULL);
		if (sc->m_flags & SY_WRITE)
			req->r_data.sswrite.r_pattern = pattern;

		return 0;
	}
#endif

	/*
	 * otherwise, we're doing file io.  Choose starting offset, length,
	 * open flags, and possibly a pattern (for write/writea).
	 */

	fptr = &File_List[random_range(0, Nfiles - 1, 1, NULL)];
	flags = Flag_List[random_range(0, Nflags - 1, 1, NULL)];

	/*
	 * Choose offset/length multiple.  IO going to a device, or regular
	 * IO that is O_RAW or O_SSD must be aligned on the file r_iou.  Otherwise
	 * it must be aligned on the regular iou (normally 1).
	 */

	if (fptr->f_type == S_IFREG && (flags->m_flags & FLG_RAW))
		mult = fptr->f_riou;
	else
		mult = fptr->f_iou;

	/*
	 * Choose offset and length.  Both must be a multiple of mult
	 */

	/*
	 * Choose length first - it must be a multiple of mult
	 */

	laststart = fptr->f_lastoffset;
	lastend = fptr->f_lastoffset + fptr->f_lastlength - 1;

	minlength = (Mintrans > mult) ? Mintrans : mult;

	switch (Offset_Mode->m_value) {
	case M_SEQUENTIAL:
		if (o_opt && lastend > laststart)
			offset = random_range(laststart, lastend, 1, NULL);
		else
			offset = lastend + 1;
		if (offset && (offset % mult))
			offset += mult - (offset % mult);

		if (minlength > fptr->f_length - offset)
			offset = 0;

		maxlength = fptr->f_length - offset;
		if (maxlength > Maxtrans)
			maxlength = Maxtrans;

		length = random_range(minlength, maxlength, mult, &errp);
		if (errp != NULL) {
			fprintf(stderr,
				"iogen%s:  random_range(%d, %d, %d) failed\n",
				TagName, minlength, maxlength, mult);
			return -1;
		}

		break;

	case M_REVERSE:
		maxlength = laststart;

		if (maxlength > Maxtrans)
			maxlength = Maxtrans;

		if (minlength > maxlength) {
			laststart = fptr->f_length;
			lastend = fptr->f_length;
			maxlength = Maxtrans;
		}

		length = random_range(minlength, maxlength, mult, &errp);
		if (errp != NULL) {
			fprintf(stderr,
				"iogen%s:  random_range(%d, %d, %d) failed\n",
				TagName, minlength, maxlength, mult);
			return -1;
		}

		offset = laststart - length;

		if (o_opt && lastend > laststart)
			offset += random_range(1, lastend - laststart, 1, NULL);

		if (offset && (offset % mult))
			offset -= offset % mult;

		break;

	case M_RANDOM:
		length = random_range(Mintrans, Maxtrans, mult, NULL);

		if (o_opt && lastend > laststart) {
			minoffset = laststart - length + 1;
			if (minoffset < 0) {
				minoffset = 0;
			}

			if (lastend + length > fptr->f_length) {
				maxoffset = fptr->f_length - length;
			} else {
				maxoffset = lastend;
			}
		} else {
			minoffset = 0;
			maxoffset = fptr->f_length - length;
		}

		if (minoffset < 0)
			minoffset = 0;

		offset = random_range(minoffset, maxoffset, mult, &errp);
		if (errp != NULL) {
			fprintf(stderr,
				"iogen%s:  random_range(%d, %d, %d) failed\n",
				TagName, minoffset, maxoffset, mult);
			return -1;
		}
	}

	fptr->f_lastoffset = offset;
	fptr->f_lastlength = length;

	/*
	 * Choose an async io completion strategy if necessary
	 */
	if (sc->m_flags & SY_ASYNC)
		aio_strat = Aio_Strat_List[random_range(0, Naio_Strat_Types - 1,
							1, NULL)];
	else
		aio_strat = NULL;

	/*
	 * fill in specific syscall record data
	 */
	switch (sc->m_value) {
	case READ:
	case READA:
		strcpy(req->r_data.read.r_file, fptr->f_path);
		req->r_data.read.r_oflags = O_RDONLY | flags->m_value;
		req->r_data.read.r_offset = offset;
		req->r_data.read.r_nbytes = length;
		req->r_data.read.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
		req->r_data.read.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.read.r_nstrides = 1;
		req->r_data.read.r_nent = 1;
		break;

	case WRITE:
	case WRITEA:
		strcpy(req->r_data.write.r_file, fptr->f_path);
		req->r_data.write.r_oflags = O_WRONLY | flags->m_value;
		req->r_data.write.r_offset = offset;
		req->r_data.write.r_nbytes = length;
		req->r_data.write.r_pattern = pattern;
		req->r_data.write.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
		req->r_data.write.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.write.r_nstrides = 1;
		req->r_data.write.r_nent = 1;
		break;

	case READV:
	case AREAD:
	case PREAD:
	case WRITEV:
	case AWRITE:
	case PWRITE:

	case LREAD:
	case LREADA:
	case LWRITE:
	case LWRITEA:

	case RESVSP:
	case UNRESVSP:
	case DFFSYNC:
	case FSYNC2:
	case FDATASYNC:

		strcpy(req->r_data.io.r_file, fptr->f_path);
		req->r_data.io.r_oflags =
		    ((sc->m_flags & SY_WRITE) ? O_WRONLY : O_RDONLY) | flags->
		    m_value;
		req->r_data.io.r_offset = offset;
		req->r_data.io.r_nbytes = length;
		req->r_data.io.r_pattern = pattern;
		req->r_data.io.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
		req->r_data.io.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.io.r_nstrides = 1;
		req->r_data.io.r_nent = 1;
		break;

	case MMAPR:
	case MMAPW:
		strcpy(req->r_data.io.r_file, fptr->f_path);
		/* a subtle "feature" of mmap: a write-map requires
		   the file open read/write */
		req->r_data.io.r_oflags =
		    ((sc->m_flags & SY_WRITE) ? O_RDWR : O_RDONLY) | flags->
		    m_value;
		req->r_data.io.r_offset = offset;
		req->r_data.io.r_nbytes = length;
		req->r_data.io.r_pattern = pattern;
		req->r_data.io.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
		req->r_data.io.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.io.r_nstrides = 1;
		req->r_data.io.r_nent = 1;
		break;

	case LSREAD:
	case LSREADA:
	case LEREAD:
	case LEREADA:
	case LSWRITE:
	case LSWRITEA:
	case LEWRITE:
	case LEWRITEA:
		/* multi-strided */
		strcpy(req->r_data.io.r_file, fptr->f_path);
		req->r_data.io.r_oflags =
		    ((sc->m_flags & SY_WRITE) ? O_WRONLY : O_RDONLY) | flags->
		    m_value;
		req->r_data.io.r_offset = offset;
		req->r_data.io.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
		req->r_data.io.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.io.r_pattern = pattern;

		/* multi-strided request...
		 *  random number of strides (1...MaxStrides)
		 *  length of stride must be > minlength
		 *  length of stride must be % mult
		 *
		 * maxstrides = min(length / mult, overall.max#strides)
		 * nstrides = random #
		 * while (length / nstrides < minlength)
		 *      nstrides = new random #
		 */
		maxstride = length / mult;
		if (maxstride > Maxstrides)
			maxstride = Maxstrides;

		if (!Minstrides)
			Minstrides = 1;
		nstrides = random_range(Minstrides, maxstride, 1, &errp);
		if (errp != NULL) {
			fprintf(stderr,
				"iogen%s:  random_range(%d, %d, %d) failed\n",
				TagName, Minstrides, maxstride, 1);
			return -1;
		}

		slength = length / nstrides;
		if (slength % mult != 0) {
			if (mult > slength) {
				slength = mult;
			} else {
				slength -= slength % mult;
			}
			nstrides = length / slength;
			if (nstrides > Maxstrides)
				nstrides = Maxstrides;
		}

		req->r_data.io.r_nbytes = slength;
		if (sc->m_flags & SY_NENT) {
			req->r_data.io.r_nstrides = 1;
			req->r_data.io.r_nent = nstrides;
		} else {
			req->r_data.io.r_nstrides = nstrides;
			req->r_data.io.r_nent = 1;
		}
		break;

	case LISTIO:
#ifdef CRAY
		strcpy(req->r_data.listio.r_file, fptr->f_path);
		req->r_data.listio.r_offset = offset;
		req->r_data.listio.r_cmd = cmd;
		req->r_data.listio.r_aio_strat =
		    (aio_strat == NULL) ? 0 : aio_strat->m_value;
		req->r_data.listio.r_filestride = 0;
		req->r_data.listio.r_memstride = 0;
		req->r_data.listio.r_opcode = opcode;
		req->r_data.listio.r_nstrides = 1;
		req->r_data.listio.r_nbytes = length;
		req->r_data.listio.r_uflags =
		    (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;

		if (opcode == LO_WRITE) {
			req->r_data.listio.r_pattern = pattern;
			req->r_data.listio.r_oflags = O_WRONLY | flags->m_value;
		} else {
			req->r_data.listio.r_oflags = O_RDONLY | flags->m_value;
		}
#endif
		break;
	}

	return 0;
}

/*
 * Get information about a file that iogen uses to choose io length and
 * offset.  Information gathered is file length, iounit, and raw iounit.
 * For regurlar files, iounit is 1, and raw iounit is the iounit of the
 * device on which the file resides.  For block/character special files
 * the iounit and raw iounit are both the iounit of the device.
 *
 * Note:	buffered and osync io must be iounit aligned
 *		raw and ossd io must be raw iounit aligned
 */

int get_file_info(struct file_info *rec)
{
	struct stat sbuf;
#ifdef CRAY
	struct lk_device_info dinfo;
#endif
#ifdef sgi
	int fd;
	struct dioattr finfo;
#endif

	/*
	 * Figure out if the files is regular, block or character special.  Any
	 * other type is an error.
	 */

	if (stat(rec->f_path, &sbuf) == -1) {
		fprintf(stderr,
			"iogen%s: get_file_info():  Could not stat() %s:  %s\n",
			TagName, rec->f_path, SYSERR);
		return -1;
	}
#if _CRAY2
	if ((!S_ISREG(sbuf.st_mode)) || strncmp(rec->f_path, "/dev/", 5) == 0) {
		fprintf(stderr,
			"iogen%s:  device level io not supported on cray2\n",
			TagName);
		return -1;
	}
#endif

	rec->f_type = sbuf.st_mode & S_IFMT;

	/*
	 * If regular, iou is 1, and we must figure out the device on
	 * which the file resides.  riou is the iou (logical sector size) of
	 * this device.
	 */

	if (S_ISREG(sbuf.st_mode)) {
		rec->f_iou = 1;
		rec->f_length = sbuf.st_size;

		/*
		 * If -r used, take Rawmult as the raw/ssd io multiple.  Otherwise
		 * attempt to determine it by looking at the device the file
		 * resides on.
		 */

		if (r_opt) {
			rec->f_riou = Rawmult;
			return 0;
		}
#ifdef CRAY
		if (lk_rawdev(rec->f_path, dinfo.path, sizeof(dinfo.path), 0) ==
		    -1)
			return -1;

		if (lk_devinfo(&dinfo, 0) == -1) {
			/* can't get raw I/O unit -- use stat to fudge it */
			rec->f_riou = sbuf.st_blksize;
		} else {
			rec->f_riou = ctob(dinfo.iou);
		}
#endif
#ifdef linux
		rec->f_riou = BSIZE;
#endif
#ifdef sgi
		if ((fd = open(rec->f_path, O_RDWR | O_DIRECT, 0)) != -1) {
			if (fcntl(fd, F_DIOINFO, &finfo) != -1) {
				rec->f_riou = finfo.d_miniosz;
			} else {
				fprintf(stderr,
					"iogen%s: Error %s (%d) getting direct I/O info of file %s\n",
					TagName, strerror(errno), errno,
					rec->f_path);
			}
			close(fd);
		} else {
			rec->f_riou = BBSIZE;
		}
#endif /* SGI */

	} else {

#ifdef CRAY
		/*
		 * Otherwise, file is a device.  Use lk_devinfo() to get its logical
		 * sector size.  This is the iou and riou
		 */

		strcpy(dinfo.path, rec->f_path);

		if (lk_devinfo(&dinfo, 0) == -1) {
			fprintf(stderr, "iogen%s: %s:  %s\n", TagName,
				Lk_err_func, Lk_err_mesg);
			return -1;
		}

		rec->f_iou = ctob(dinfo.iou);
		rec->f_riou = ctob(dinfo.iou);
		rec->f_length = ctob(dinfo.length);
#else
#ifdef sgi
		rec->f_riou = BBSIZE;
		rec->f_length = BBSIZE;
#else
		rec->f_riou = BSIZE;
		rec->f_length = BSIZE;
#endif /* sgi */
#endif /* CRAY */
	}

	return 0;
}

/*
 * Create file path as nbytes long.  If path exists, the file will either be
 * extended or truncated to be nbytes long.  Returns final size of file,
 * or -1 if there was a failure.
 */

int create_file(char *path, int nbytes)
{
	int fd, rval;
	char c;
	struct stat sbuf;
#ifdef sgi
	int nb;
	struct flock f;
	struct fsxattr xattr;
	struct dioattr finfo;
	char *b, *buf;
#endif

	errno = 0;
	rval = stat(path, &sbuf);

	if (rval == -1) {
		if (errno == ENOENT) {
			sbuf.st_size = 0;
		} else {
			fprintf(stderr,
				"iogen%s:  Could not stat file %s:  %s (%d)\n",
				TagName, path, SYSERR, errno);
			return -1;
		}
	} else {
		if (!S_ISREG(sbuf.st_mode)) {
			fprintf(stderr,
				"iogen%s:  file %s exists, but is not a regular file - cannot modify length\n",
				TagName, path);
			return -1;
		}
	}

	if (sbuf.st_size == nbytes)
		return nbytes;

	Oflags |= O_CREAT | O_WRONLY;

	if ((fd = open(path, Oflags, 0666)) == -1) {
		fprintf(stderr,
			"iogen%s:  Could not create/open file %s: %s (%d)\n",
			TagName, path, SYSERR, errno);
		return -1;
	}

	/*
	 * Truncate file if it is longer than nbytes, otherwise attempt to
	 * pre-allocate file blocks.
	 */

	if (sbuf.st_size > nbytes) {
		if (ftruncate(fd, nbytes) == -1) {
			fprintf(stderr,
				"iogen%s:  Could not ftruncate() %s to %d bytes:  %s (%d)\n",
				TagName, path, nbytes, SYSERR, errno);
			close(fd);
			return -1;
		}
	} else {

#ifdef sgi
		/*
		 *  The file must be designated as Real-Time before any data
		 *  is allocated to it.
		 *
		 */
		if (Orealtime != 0) {
			memset(&xattr, 0x00, sizeof(xattr));
			xattr.fsx_xflags = XFS_XFLAG_REALTIME;
			/*fprintf(stderr, "set: fsx_xflags = 0x%x\n", xattr.fsx_xflags); */
			if (fcntl(fd, F_FSSETXATTR, &xattr) == -1) {
				fprintf(stderr,
					"iogen%s: Error %s (%d) setting XFS XATTR->Realtime on file %s\n",
					TagName, SYSERR, errno, path);
				close(fd);
				return -1;
			}
#ifdef DEBUG
			if (fcntl(fd, F_FSGETXATTR, &xattr) == -1) {
				fprintf(stderr,
					"iogen%s: Error getting realtime flag %s (%d)\n",
					TagName, SYSERR, errno);
				close(fd);
				return -1;
			} else {
				fprintf(stderr, "get: fsx_xflags = 0x%x\n",
					xattr.fsx_xflags);
			}
#endif
		}

		/*
		 * Reserve space with F_RESVSP
		 *
		 * Failure is ignored since F_RESVSP only works on XFS and the
		 * filesystem could be on EFS or NFS
		 */
		if (Oreserve) {
			f.l_whence = SEEK_SET;
			f.l_start = 0;
			f.l_len = nbytes;

			/*fprintf(stderr,
			   "create_file: fcntl(%d, F_RESVSP, { %d, %lld, %lld })\n",
			   fd, f.l_whence, (long long)f.l_start, (long long)f.l_len); */

			/* non-zeroing reservation */
			if (fcntl(fd, F_RESVSP, &f) == -1) {
				fprintf(stderr,
					"iogen%s:  Could not fcntl(F_RESVSP) %d bytes in file %s: %s (%d)\n",
					TagName, nbytes, path, SYSERR, errno);
				close(fd);
				return -1;
			}
		}

		if (Oallocate) {
			/* F_ALLOCSP allocates from the start of the file to l_start */
			f.l_whence = SEEK_SET;
			f.l_start = nbytes;
			f.l_len = 0;
			/*fprintf(stderr,
			   "create_file: fcntl(%d, F_ALLOCSP, { %d, %lld, %lld })\n",
			   fd, f.l_whence, (long long)f.l_start,
			   (long long)f.l_len); */

			/* zeroing reservation */
			if (fcntl(fd, F_ALLOCSP, &f) == -1) {
				fprintf(stderr,
					"iogen%s:  Could not fcntl(F_ALLOCSP) %d bytes in file %s: %s (%d)\n",
					TagName, nbytes, path, SYSERR, errno);
				close(fd);
				return -1;
			}
		}
#endif /* sgi */

		/*
		 * Write a byte at the end of file so that stat() sets the right
		 * file size.
		 */

#ifdef sgi
		if (Owrite == 2) {
			close(fd);
			if ((fd =
			     open(path, O_CREAT | O_RDWR | O_DIRECT,
				  0)) != -1) {
				if (fcntl(fd, F_DIOINFO, &finfo) == -1) {
					fprintf(stderr,
						"iogen%s: Error %s (%d) getting direct I/O info for file %s\n",
						TagName, SYSERR, errno, path);
					return -1;
				} else {
					/*fprintf(stderr, "%s: miniosz=%d\n",
					   path, finfo.d_miniosz); */
				}
			} else {
				fprintf(stderr,
					"iogen%s: Error %s (%d) opening file %s with flags O_CREAT|O_RDWR|O_DIRECT\n",
					TagName, SYSERR, errno, path);
				return -1;
			}

			/*
			 * nb is nbytes adjusted down by an even d_miniosz block
			 *
			 * Note: the first adjustment can cause iogen to print a warning
			 *  about not being able to create a file of <nbytes> length,
			 *  since the file will be shorter.
			 */
			nb = nbytes - finfo.d_miniosz;
			nb = nb - nb % finfo.d_miniosz;

			/*fprintf(stderr,
			   "create_file_ow2: lseek(%d, %d {%d %d}, SEEK_SET)\n",
			   fd, nb, nbytes, finfo.d_miniosz); */

			if (lseek(fd, nb, SEEK_SET) == -1) {
				fprintf(stderr,
					"iogen%s:  Could not lseek() to EOF of file %s: %s (%d)\n\tactual offset %d file size goal %d miniosz %lld\n",
					TagName, path, SYSERR, errno,
					nb, nbytes, (long long)finfo.d_miniosz);
				close(fd);
				return -1;
			}

			b = buf = (char *)malloc(finfo.d_miniosz + finfo.d_mem);

			if (((long)buf % finfo.d_mem != 0)) {
				buf += finfo.d_mem - ((long)buf % finfo.d_mem);
			}

			memset(buf, 0, finfo.d_miniosz);

			if ((rval =
			     write(fd, buf,
				   finfo.d_miniosz)) != finfo.d_miniosz) {
				fprintf(stderr,
					"iogen%s:  Could not write %d byte length file %s: %s (%d)\n",
					TagName, nb, path, SYSERR, errno);
				fprintf(stderr, "\twrite(%d, 0x%lx, %d) = %d\n",
					fd, (long)buf, finfo.d_miniosz, rval);
				fprintf(stderr,
					"\toffset %d file size goal %d, miniosz=%d\n",
					nb, nbytes, finfo.d_miniosz);
				close(fd);
				return -1;
			}
			free(b);
		} else
#endif /* sgi */
		if (Owrite) {
			/*fprintf(stderr,
			   "create_file_Owrite: lseek(%d, %d {%d}, SEEK_SET)\n",
			   fd, nbytes-1, nbytes); */

			if (lseek(fd, nbytes - 1, SEEK_SET) == -1) {
				fprintf(stderr,
					"iogen%s:  Could not lseek() to EOF in file %s:  %s (%d)\n\toffset goal %d\n",
					TagName, path, SYSERR, errno,
					nbytes - 1);
				close(fd);
				return -1;
			}

			if ((rval = write(fd, &c, 1)) != 1) {
				fprintf(stderr,
					"iogen%s:  Could not create a %d byte length file %s: %s (%d)\n",
					TagName, nbytes, path, SYSERR, errno);
				fprintf(stderr,
					"\twrite(%d, 0x%lx, %d) = %d\n",
					fd, (long)&c, 1, rval);
				fprintf(stderr,
					"\toffset %d file size goal %d\n",
					nbytes - 1, nbytes);
				close(fd);
				return -1;
			}
		}
	}

	fstat(fd, &sbuf);
	close(fd);

	return sbuf.st_size;
}

/*
 * Function to convert a string to its corresponding value in a strmap array.
 * If the string is not found in the array, the value corresponding to the
 * NULL string (the last element in the array) is returned.
 */

int str_to_value(struct strmap *map, char *str)
{
	struct strmap *mp;

	for (mp = map; mp->m_string != NULL; mp++)
		if (strcmp(mp->m_string, str) == 0)
			break;

	return mp->m_value;
}

/*
 * Function to convert a string to its corresponding entry in a strmap array.
 * If the string is not found in the array, a NULL is returned.
 */

struct strmap *str_lookup(struct strmap *map, char *str)
{
	struct strmap *mp;

	for (mp = map; mp->m_string != NULL; mp++)
		if (strcmp(mp->m_string, str) == 0)
			break;

	return ((mp->m_string == NULL) ? NULL : mp);
}

/*
 * Function to convert a value to its corresponding string in a strmap array.
 * If the value is not found in the array, NULL is returned.
 */

char *value_to_string(struct strmap *map, int val)
{
	struct strmap *mp;

	for (mp = map; mp->m_string != NULL; mp++)
		if (mp->m_value == val)
			break;

	return mp->m_string;
}

/*
 * Interpret cmdline options/arguments.  Exit with 1 if something on the
 * cmdline isn't kosher.
 */

int parse_cmdline(int argc, char **argv, char *opts)
{
	int o, len, nb, format_error;
	struct strmap *flgs, *sc;
	char *file, *cp, ch;
	extern int opterr;
	extern int optind;
	extern char *optarg;
	struct strmap *mp;
	struct file_info *fptr;
	int nopenargs;
	char *openargs[5];	/* Flags, cbits, cblks */
	char *errmsg;
	int str_to_int();
	opterr = 0;
#ifndef linux
	char *ranges;
	struct strmap *type;
#endif

	while ((o = getopt(argc, argv, opts)) != EOF) {
		switch ((char)o) {

		case 'a':
#ifdef linux
			fprintf(stderr,
				"iogen%s:  Unrecognized option -a on this platform\n",
				TagName);
			exit(2);
#else
			cp = strtok(optarg, ",");
			while (cp != NULL) {
				if ((type =
				     str_lookup(Aio_Strat_Map, cp)) == NULL) {
					fprintf(stderr,
						"iogen%s:  Unrecognized aio completion strategy:  %s\n",
						TagName, cp);
					exit(2);
				}

				Aio_Strat_List[Naio_Strat_Types++] = type;
				cp = strtok(NULL, ",");
			}
			a_opt++;
#endif
			break;

		case 'f':
			cp = strtok(optarg, ",");
			while (cp != NULL) {
				if ((flgs = str_lookup(Flag_Map, cp)) == NULL) {
					fprintf(stderr,
						"iogen%s:  Unrecognized flags:  %s\n",
						TagName, cp);
					exit(2);
				}

				cp = strtok(NULL, ",");

#ifdef O_SSD
				if (flgs->m_value & O_SSD && !Sds_Avail) {
					fprintf(stderr,
						"iogen%s:  Warning - no sds available, ignoring ssd flag\n",
						TagName);
					continue;
				}
#endif

				Flag_List[Nflags++] = flgs;
			}
			f_opt++;
			break;

		case 'h':
			help(stdout);
			exit(0);
			break;

		case 'i':
			format_error = 0;

			switch (sscanf(optarg, "%i%c", &Iterations, &ch)) {
			case 1:
				Time_Mode = 0;
				break;

			case 2:
				if (ch == 's')
					Time_Mode = 1;
				else
					format_error = 1;
				break;

			default:
				format_error = 1;
			}

			if (Iterations < 0)
				format_error = 1;

			if (format_error) {
				fprintf(stderr,
					"iogen%s:  Illegal -i arg (%s):  Must be of the format:  number[s]\n",
					TagName, optarg);
				fprintf(stderr,
					"        where 'number' is >= 0\n");
				exit(1);
			}

			i_opt++;
			break;

		case 'L':
#ifdef linux
			fprintf(stderr,
				"iogen%s:  Unrecognized option -L on this platform\n",
				TagName);
			exit(2);
#else
			if (parse_ranges(optarg, 1, 255, 1, NULL, &ranges,
					 &errmsg) == -1) {
				fprintf(stderr,
					"iogen%s: error parsing listio range '%s': %s\n",
					TagName, optarg, errmsg);
				exit(1);
			}

			Minstrides = range_min(ranges, 0);
			Maxstrides = range_max(ranges, 0);

			free(ranges);
			L_opt++;
#endif
			break;

		case 'm':
			if ((Offset_Mode =
			     str_lookup(Omode_Map, optarg)) == NULL) {
				fprintf(stderr,
					"iogen%s:  Illegal -m arg (%s)\n",
					TagName, optarg);
				exit(1);
			}

			m_opt++;
			break;

		case 'N':
			sprintf(TagName, "(%.39s)", optarg);
			break;

		case 'o':
			o_opt++;
			break;

		case 'O':

			nopenargs = string_to_tokens(optarg, openargs, 4, ":/");

#ifdef CRAY
			if (nopenargs)
				sscanf(openargs[1], "%i", &Ocbits);
			if (nopenargs > 1)
				sscanf(openargs[2], "%i", &Ocblks);

			Oflags = parse_open_flags(openargs[0], &errmsg);
			if (Oflags == -1) {
				fprintf(stderr, "iogen%s: -O %s error: %s\n",
					TagName, optarg, errmsg);
				exit(1);
			}
#endif
#ifdef linux
			Oflags = parse_open_flags(openargs[0], &errmsg);
			if (Oflags == -1) {
				fprintf(stderr, "iogen%s: -O %s error: %s\n",
					TagName, optarg, errmsg);
				exit(1);
			}
#endif
#ifdef sgi
			if (!strcmp(openargs[0], "realtime")) {
				/*
				 * -O realtime:extsize
				 */
				Orealtime = 1;
				if (nopenargs > 1)
					sscanf(openargs[1], "%i", &Oextsize);
				else
					Oextsize = 0;
			} else if (!strcmp(openargs[0], "allocate") ||
				   !strcmp(openargs[0], "allocsp")) {
				/*
				 * -O allocate
				 */
				Oreserve = 0;
				Oallocate = 1;
			} else if (!strcmp(openargs[0], "reserve")) {
				/*
				 * -O [no]reserve
				 */
				Oallocate = 0;
				Oreserve = 1;
			} else if (!strcmp(openargs[0], "noreserve")) {
				/* Oreserve=1 by default; this clears that default */
				Oreserve = 0;
			} else if (!strcmp(openargs[0], "nowrite")) {
				/* Owrite=1 by default; this clears that default */
				Owrite = 0;
			} else if (!strcmp(openargs[0], "direct")) {
				/* this means "use direct i/o to preallocate file" */
				Owrite = 2;
			} else {
				fprintf(stderr,
					"iogen%s: Error: -O %s error: unrecognized option\n",
					TagName, openargs[0]);
				exit(1);
			}
#endif

			O_opt++;
			break;

		case 'p':
			Outpipe = optarg;
			p_opt++;
			break;

		case 'r':
			if ((Rawmult = bytes_by_prefix(optarg)) == -1 ||
			    Rawmult < 11 || Rawmult % BSIZE) {
				fprintf(stderr,
					"iogen%s:  Illegal -r arg (%s).  Must be > 0 and multipe of BSIZE (%d)\n",
					TagName, optarg, BSIZE);
				exit(1);
			}

			r_opt++;
			break;

		case 's':
			cp = strtok(optarg, ",");
			while (cp != NULL) {
				if ((sc = str_lookup(Syscall_Map, cp)) == NULL) {
					fprintf(stderr,
						"iogen%s:  Unrecognized syscall:  %s\n",
						TagName, cp);
					exit(2);
				}

				do {
					/* >>> sc->m_flags & FLG_SDS */
					if (sc->m_value != SSREAD
					    && sc->m_value != SSWRITE)
						Fileio++;

					Syscall_List[Nsyscalls++] = sc;
				} while ((sc = str_lookup(++sc, cp)) != NULL);

				cp = strtok(NULL, ",");
			}
			s_opt++;
			break;

		case 't':
			if ((Mintrans = bytes_by_prefix(optarg)) == -1) {
				fprintf(stderr,
					"iogen%s:  Illegal -t arg (%s):  Must have the form num[bkm]\n",
					TagName, optarg);
				exit(1);
			}
			t_opt++;
			break;

		case 'T':
			if ((Maxtrans = bytes_by_prefix(optarg)) == -1) {
				fprintf(stderr,
					"iogen%s:  Illegal -T arg (%s):  Must have the form num[bkm]\n",
					TagName, optarg);
				exit(1);
			}
			T_opt++;
			break;

		case 'q':
			q_opt++;
			break;

		case '?':
			usage(stderr);
			exit(1);
		}
	}

	/*
	 * Supply defaults
	 */

	if (!L_opt) {
		Minstrides = 1;
		Maxstrides = 255;
	}

	if (!m_opt)
		Offset_Mode = str_lookup(Omode_Map, "sequential");

	if (!i_opt)
		Iterations = 0;

	if (!t_opt)
		Mintrans = 1;

	if (!T_opt)
		Maxtrans = 256 * BSIZE;

	if (!O_opt)
		Oflags = Ocbits = Ocblks = 0;

	/*
	 * Supply default async io completion strategy types.
	 */

	if (!a_opt) {
		for (mp = Aio_Strat_Map; mp->m_string != NULL; mp++) {
			Aio_Strat_List[Naio_Strat_Types++] = mp;
		}
	}

	/*
	 * Supply default syscalls.  Default is read,write,reada,writea,listio.
	 */

	if (!s_opt) {
		Nsyscalls = 0;
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "read");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "write");
#ifdef CRAY
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "reada");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "writea");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lread");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lreada");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lwrite");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lwritea");
#endif

#ifdef sgi
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "pread");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "pwrite");
		/*Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "aread"); */
		/*Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "awrite"); */
#endif

#ifndef CRAY
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "readv");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "writev");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "mmread");
		Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "mmwrite");
#endif

		Fileio = 1;
	}

	if (Fileio && (argc - optind < 1)) {
		fprintf(stderr, "iogen%s:  No files specified on the cmdline\n",
			TagName);
		exit(1);
	}

	/*
	 * Supply default file io flags - defaut is 'buffered,raw,sync,ldraw'.
	 */

	if (!f_opt && Fileio) {
		Nflags = 0;
		Flag_List[Nflags++] = str_lookup(Flag_Map, "buffered");
		Flag_List[Nflags++] = str_lookup(Flag_Map, "sync");
#ifdef CRAY
		Flag_List[Nflags++] = str_lookup(Flag_Map, "raw+wf");
		Flag_List[Nflags++] = str_lookup(Flag_Map, "ldraw");
#endif

#ifdef sgi
		/* Warning: cannot mix direct i/o with others! */
		Flag_List[Nflags++] = str_lookup(Flag_Map, "dsync");
		Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync");
		/* Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync+sync"); */
		/* Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync+dsync"); */
#endif
	}

	if (Fileio) {
		if (optind >= argc) {
			fprintf(stderr,
				"iogen%s:  No files listed on the cmdline\n",
				TagName);
			exit(1);
		}

		/*
		 * Initialize File_List[] - only necessary if doing file io.  First
		 * space for the File_List array, then fill it in.
		 */

		File_List = (struct file_info *)
		    malloc((argc - optind) * sizeof(struct file_info));

		if (File_List == NULL) {
			fprintf(stderr,
				"iogen%s:  Could not malloc space for %d file_info structures\n",
				TagName, argc - optind);
			exit(2);
		}

		memset(File_List, 0,
		       (argc - optind) * sizeof(struct file_info));

		Nfiles = 0;
		while (optind < argc) {
			len = -1;

			/*
			 * Pick off leading len: if it's there and create/extend/trunc
			 * the file to the desired length.  Otherwise, just make sure
			 * the file is accessable.
			 */

			if ((cp = strchr(argv[optind], ':')) != NULL) {
				*cp = '\0';
				if ((len = bytes_by_prefix(argv[optind])) == -1) {
					fprintf(stderr,
						"iogen%s:  illegal file length (%s) for file %s\n",
						TagName, argv[optind], cp + 1);
					exit(2);
				}
				*cp = ':';
				file = cp + 1;

				if (strlen(file) > MAX_FNAME_LENGTH) {
					fprintf(stderr,
						"iogen%s:  Max fname length is %d chars - ignoring file %s\n",
						TagName, MAX_FNAME_LENGTH,
						file);
					optind++;
					continue;
				}

				nb = create_file(file, len);

				if (nb < len) {
					fprintf(stderr,
						"iogen%s warning:  Couldn't create file %s of %d bytes\n",
						TagName, file, len);

					if (nb <= 0) {
						optind++;
						continue;
					}
				}
			} else {
				file = argv[optind];
				if (access(file, R_OK | W_OK) == -1) {
					fprintf(stderr,
						"iogen%s:  file %s cannot be accessed for reading and/or writing:  %s (%d)\n",
						TagName, file, SYSERR, errno);
					exit(2);
				}
			}

			/*
			 * get per-file information
			 */

			fptr = &File_List[Nfiles];

			if (file[0] == '/') {
				strcpy(fptr->f_path, file);
			} else {
				if (getcwd
				    (fptr->f_path,
				     sizeof(fptr->f_path) - 1) == NULL)
					perror
					    ("Could not get current working directory");
				strcat(fptr->f_path, "/");
				strcat(fptr->f_path, file);
			}

			if (get_file_info(fptr) == -1) {
				fprintf(stderr,
					"iogen%s warning:  Error getting file info for %s\n",
					TagName, file);
			} else {

				/*
				 * If the file length is smaller than our min transfer size,
				 * ignore it.
				 */

				if (fptr->f_length < Mintrans) {
					fprintf(stderr,
						"iogen%s warning:  Ignoring file %s\n",
						TagName, fptr->f_path);
					fprintf(stderr,
						"                length (%d) is < min transfer size (%d)\n",
						fptr->f_length, Mintrans);
					optind++;
					continue;
				}

				/*
				 * If the file length is smaller than our max transfer size,
				 * ignore it.
				 */

				if (fptr->f_length < Maxtrans) {
					fprintf(stderr,
						"iogen%s warning:  Ignoring file %s\n",
						TagName, fptr->f_path);
					fprintf(stderr,
						"                length (%d) is < max transfer size (%d)\n",
						fptr->f_length, Maxtrans);
					optind++;
					continue;
				}

				if (fptr->f_length > 0) {
					switch (Offset_Mode->m_value) {
					case M_SEQUENTIAL:
						fptr->f_lastoffset = 0;
						fptr->f_lastlength = 0;
						break;

					case M_REVERSE:
						fptr->f_lastoffset =
						    fptr->f_length;
						fptr->f_lastlength = 0;
						break;

					case M_RANDOM:
						fptr->f_lastoffset =
						    fptr->f_length / 2;
						fptr->f_lastlength = 0;
						break;
					}

					Nfiles++;
				}
			}

			optind++;
		}

		if (Nfiles == 0) {
			fprintf(stderr,
				"iogen%s:  Could not create, or gather info for any test files\n",
				TagName);
			exit(2);
		}
	}

	return 0;
}

int help(FILE * stream)
{
	usage(stream);
	fprintf(stream, "\n");
#ifndef linux
	fprintf(stream,
		"\t-a aio_type,...  Async io completion types to choose.  Supported types\n");
#ifdef CRAY
#if _UMK || RELEASE_LEVEL >= 8000
	fprintf(stream,
		"\t                 are:  poll, signal, recall, recalla, and recalls.\n");
#else
	fprintf(stream,
		"\t                 are:  poll, signal, recalla, and recalls.\n");
#endif
#else
	fprintf(stream,
		"\t                 are:  poll, signal, suspend, and callback.\n");
#endif
	fprintf(stream, "\t                 Default is all of the above.\n");
#else /* !linux */
	fprintf(stream, "\t-a               (Not used on Linux).\n");
#endif /* !linux */
	fprintf(stream,
		"\t-f flag,...      Flags to use for file IO.  Supported flags are\n");
#ifdef CRAY
	fprintf(stream,
		"\t                 raw, ssd, buffered, ldraw, sync,\n");
	fprintf(stream,
		"\t                 raw+wf, raw+wf+ldraw, raw+wf+ldraw+sync,\n");
	fprintf(stream,
		"\t                 and parallel (unicos/mk on MPP only).\n");
	fprintf(stream,
		"\t                 Default is 'raw,ldraw,sync,buffered'.\n");
#else
#ifdef sgi
	fprintf(stream,
		"\t                 buffered, direct, sync, dsync, rsync,\n");
	fprintf(stream, "\t                 rsync+dsync.\n");
	fprintf(stream,
		"\t                 Default is 'buffered,sync,dsync,rsync'.\n");
#else
	fprintf(stream, "\t                 buffered, sync.\n");
	fprintf(stream, "\t                 Default is 'buffered,sync'.\n");
#endif /* sgi */
#endif /* CRAY */
	fprintf(stream, "\t-h               This help.\n");
	fprintf(stream,
		"\t-i iterations[s] # of requests to generate.  0 means causes iogen\n");
	fprintf(stream,
		"\t                 to run until it's killed.  If iterations is suffixed\n");
	fprintf(stream,
		"\t                 with 's', then iterations is the number of seconds\n");
	fprintf(stream,
		"\t                 that iogen should run for.  Default is '0'.\n");
#ifndef linux
	fprintf(stream,
		"\t-L min:max       listio nstrides / nrequests range\n");
#else
	fprintf(stream, "\t-L               (Not used on Linux).\n");
#endif /* !linux */
	fprintf(stream,
		"\t-m offset-mode   The mode by which iogen chooses the offset for\n");
	fprintf(stream,
		"\t                 consectutive transfers within a given file.\n");
	fprintf(stream,
		"\t                 Allowed values are 'random', 'sequential',\n");
	fprintf(stream, "\t                 and 'reverse'.\n");
	fprintf(stream, "\t                 sequential is the default.\n");
	fprintf(stream, "\t-N tagname       Tag name, for Monster.\n");
	fprintf(stream,
		"\t-o               Form overlapping consecutive requests.\n");
	fprintf(stream, "\t-O               Open flags for creating files\n");
#ifdef CRAY
	fprintf(stream,
		"\t                 {O_PLACE,O_BIG,etc}[:CBITS[:CBLKS]]\n");
#endif
#ifdef sgi
	fprintf(stream,
		"\t                 realtime:extsize - put file on real-time volume\n");
	fprintf(stream,
		"\t                 allocate - allocate space with F_ALLOCSP\n");
	fprintf(stream,
		"\t                 reserve - reserve space with F_RESVSP (default)\n");
	fprintf(stream,
		"\t                 noreserve - do not reserve with F_RESVSP\n");
	fprintf(stream,
		"\t                 direct - use O_DIRECT I/O to write to the file\n");
#endif
#ifdef linux
	fprintf(stream, "\t                 {O_SYNC,etc}\n");
#endif
	fprintf(stream,
		"\t-p               Output pipe.  Default is stdout.\n");
	fprintf(stream,
		"\t-q               Quiet mode.  Normally iogen spits out info\n");
	fprintf(stream,
		"\t                 about test files, options, etc. before starting.\n");
	fprintf(stream,
		"\t-s syscall,...   Syscalls to do.  Supported syscalls are\n");
#ifdef sgi
	fprintf(stream,
		"\t                 read, write, pread, pwrite, readv, writev\n");
	fprintf(stream,
		"\t                 aread, awrite, resvsp, unresvsp, ffsync,\n");
	fprintf(stream,
		"\t                 mmread, mmwrite, fsync2, fdatasync,\n");
	fprintf(stream,
		"\t                 Default is 'read,write,pread,pwrite,readv,writev,mmread,mmwrite'.\n");
#endif
#ifdef CRAY
	fprintf(stream,
		"\t                 read, write, reada, writea, listio,\n");
	fprintf(stream,
		"\t                 ssread (PVP only), and sswrite (PVP only).\n");
	fprintf(stream,
		"\t                 Default is 'read,write,reada,writea,listio'.\n");
#endif
#ifdef linux
	fprintf(stream, "\t                 read, write, readv, writev,\n");
	fprintf(stream,
		"\t                 mmread, mmwrite, fsync2, fdatasync,\n");
	fprintf(stream,
		"\t                 Default is 'read,write,readv,writev,mmread,mmwrite'.\n");
#endif
	fprintf(stream, "\t-t mintrans      Min transfer length\n");
	fprintf(stream, "\t-T maxtrans      Max transfer length\n");
	fprintf(stream, "\n");
	fprintf(stream,
		"\t[len:]file,...   Test files to do IO against (note ssread/sswrite\n");
	fprintf(stream,
		"\t                 don't need a test file).  The len: syntax\n");
	fprintf(stream,
		"\t                 informs iogen to first create/expand/truncate the\n");
	fprintf(stream, "\t                 to the desired length.\n");
	fprintf(stream, "\n");
	fprintf(stream,
		"\tNote:  The ssd flag causes sds transfers to also be done.\n");
	fprintf(stream,
		"\t       To totally eliminate sds transfers, you must eleminate sds\n");
	fprintf(stream,
		"\t       from the flags (-f) and ssread,ssrite from the syscalls (-s)\n");
	fprintf(stream,
		"\tThe mintrans, maxtrans, and len: parameters are numbers of the\n");
	fprintf(stream,
		"\tform [0-9]+[bkm].  The optional trailing b, k, or m multiplies\n");
	fprintf(stream,
		"\tthe number by blocks, kilobytes, or megabytes.  If no trailing\n");
	fprintf(stream,
		"\tmultiplier is present, the number is interpreted as bytes\n");

	return 0;
}

/*
 * Obvious - usage clause
 */

int usage(FILE * stream)
{
	fprintf(stream,
		"usage%s:  iogen [-hoq] [-a aio_type,...] [-f flag[,flag...]] [-i iterations] [-p outpipe] [-m offset-mode] [-s syscall[,syscall...]] [-t mintrans] [-T maxtrans] [ -O file-create-flags ] [[len:]file ...]\n",
		TagName);
	return 0;
}
