/**************************************************************************** * * * COPYRIGHT (c) 1990 - 2004 * * This Software Provided * * By * * Robin's Nest Software Inc. * * * * Permission to use, copy, modify, distribute and sell this software and * * its documentation for any purpose and without fee is hereby granted, * * provided that the above copyright notice appear in all copies and that * * both that copyright notice and this permission notice appear in the * * supporting documentation, and that the name of the author not be used * * in advertising or publicity pertaining to distribution of the software * * without specific, written prior permission. * * * * THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * * NO EVENT SHALL HE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL * * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF * * THIS SOFTWARE. * * * ****************************************************************************/ /* * Module: dt.c * Author: Robin T. Miller * * Description: * Main line code for generic data test program 'dt'. */ #include "dt.h" #include #include #include #include #if !defined(_QNX_SOURCE) # if !defined(sun) # include # endif /* !defined(sun) */ # include # include # if defined(sun) || defined(_OSF_SOURCE) # include # endif /* defined(sun) || defined(_OSF_SOURCE) */ #endif /* !defined(_QNX_SOURCE) */ #include #if defined(DEC) # include #endif /* defined(DEC) */ #if defined(HP_UX) # include # if !defined(SCSI_MAX_Q_DEPTH) # define SCSI_MAX_Q_DEPTH 255 # endif #endif /* defined(HP_UX) */ /* * Modification History: * * October 21st, 2004 by Robin Miller. * Enable file system alignments by default for random I/O, * since variable sizes and offsets cause false data corruptions. * When logic is added to track previously written blocks, this * can be reverted. * * July 7th, 2004 by Robin Miller. * For HP-UX, add option qdepth=N" to set the queue depth. * * June 24th, 2004 by Robin Miller. * Handle errors from ctime_r() - two flavors are around! * Added "enable/disable=fsalign" option to control behaviour * of aligning FS offsets and variable record sizes to device size. * This option is off by default so random non-device sized values * will now be generated (useful for file system testing). This * option can be enabled to return to previous behaviour for FS's. * * June 22nd, 2004 by Robin Miller. * Added support for triggers on corruption. * * February 27th, 2004 by Robin Miller. * Switch ctime() to ctime_r() in preparation for threads. * * February 24th, 2004 by Robin Miller. * Allow parsing of aios=0, to ease supporting of OS's which * don't support POSIX AIO. Makes script writing a little simpler. * * December 6th, 2003 by Robin Miller. * Conditionalize to exclude tty code. * * November 17th, 2003 by Robin Miller. * Breakup output to stdout or stderr, rather than writing * all output to stderr. If output file is stdout ('-') or a log * file is specified, then all output reverts to stderr. * Parse AIO options, but then emit warning if not supported. * This allows portable scripts to be written without parameterizing * exlusion of AIO if a platform (OS) does not support it. * * October 31st, 2003 by Robin Miller. * In terminate(), don't attempt flush if device or file is * not open when we receive the alarm signal. * * September 27th, 2003 by Robin Miller. * Added support for AIX. * * June 9th, 2003 by Robin Miller. * When terminating as result of alarm, flush output data if we * were writing a file. Thanks to Hank Jakiela for reporting this. * * March 20th, 2003 by Robin Miller. * Fix bug or'ing O_LARGEFILE into open flags (thanks Kris Corwin!). * * March 15th, 2003 by Robin Miller. * Added "prefix=" option to support data pattern prefix string. * * March 14th, 2003 by Robin Miller. * Added "slice=" option to support testing an individual slice. * * February 23rd, 2002 by Robin Miller. * Make porting changes for HP-UX IA64. * * May 31st, 2001 by Robin Miller. * Don't allow different data patterns with multiple processes, * unless writing to a regular file (each process has own filename). * * April 13th, 2001 by Robin Miller. * Added "capacity=max" for Tru64 Unix, this will force using the * driver returned media capacity. Prevents lseek/read algorithm when * using random I/O too! Note: Will inhibit disk driver EOM testing. * * February 24th, 2001 by Robin Miller. * Add conditionalization for QNX RTP (Neutrino). * * February 21st, 2001 by Robin Miller. * Disable EOF/EOM exit status by default. This causes trouble for * normal testers. Use "enable=eof" to reenable previous behaviour. * * February 6th, 2001 by Robin Miller. * If input file and multiple slices, allow cycling through data * patterns, since the file is expected to have been written with unique * patterns in each slice region. Minor update to deleting files too. * * February 3rd, 2001 by Robin Miller. * Lift restriction of bs= or min= sizes being smaller than * sizeof(u_int32) for IOT pattern or lbdata option. Although this * isn't *really* correct, enforcing this breaks numerious scripts. * Although smaller works, it's only by luck and the fact we have * pad bytes at EOB, which prvented data corruption in the past! * * January 28th, 2001 by Robin Miller. * Added "slices=value" and "enable/disable=unique" options. * The slices option carves up a disk with each process exercising a * differnent range of blocks. The unique pattern option sets up a * unique pattern for each process started for slices & regular files. * * January 26th, 2001 by Robin Miller. * Added report_record() to the report record information. * Added "iodir={forward,reverse}" option to all reverse I/O * to rotating media. Add validation checks both before and after * a device is open (mostly random/reverse I/O checks). * * January 24th, 2001 by Robin Miller. * Added "dsize=value" to set the device block size. * Added "incr=var" option for variable request sizes. * Removed some dead code (#if 0 stuff), to cleanup a bit. * Updates to allow the IOT pattern to use non-modulo 512 byte * sizes. The logic is: 1) user defined "lbs=value", 2) device block * size (Tru64 Unix), or 3) default to 512 byte block (original default). * This change was initiated by Windows/NT IOT disc using 2KB sector size. * * January 18th, 2001 by Robin Miller. * When requesting multiple volumes, ensure the tape device gets * closed prior to retries, if the rewind operation fails. Otherwise, * the next tape open fails with EBUSY (exclusive open device). * * January 14th, 2001 by Robin Miller. * Added support for multiple volumes option. * * December 30th, 2000 by Robin Miller. * Make changes to build using MKS/NuTCracker product. * * November 19th, 2000 by Robin Miller. * Add missing enable/disable=fsync goto label statements. * * November 10th, 2000 by Robin Miller. * Added sanity check to warn user about unpredictable results * when writing to a disk with multiple procs and multiple passes. * * November 8th, 2000 by Robin Miller. * Added "disable=cerrors" to disable device close errors. * [ Note: This is really a workaround for the Linux tape driver. ] * In HandleMultiVolume(), if closing the device fails, then * return that failure to abort the test. With tapes, this means * flushing the buffered data or writing filemarks has failed. * * October 2nd, 2000 by Robin Miller. * Enhanced report_error() to display errno value (same as Scu). * * August 22nd, 2000 by Robin Miller. * Added boolean flags to track user set min, max, & incr values. * * July 14th, 2000 by Robin Miller. * Added fsync_flag to control sync'ing data to disk files. * * May 8th, 2000 by Robin Miller. * Adding parsing of "version" option, which is used to only * display the version string, dtversion() in file dtusage.c * * May 5th, 2000 by Robin Miller. * Set proper exit code in terminate(), when exiting due to * an alarm, i.e., runtime= option. Previously, the end of file * exit status was being ignored and we exited with failure status. * * April 25th, 2000 by Robin Miller. * Fix problem of not breaking out of test loop if the error * limit has been reached on previous pass. * * March 28th, 2000 by Robin Miller. * Modify "position=value" to accept a large numeric value, * otherwise we were limited to 32 bit value on non-64bit systems. * Do the same thing for "step=value" for 64-bit file offsets. * When using the "runtime=" option, when the alarm expires and * we call terminate() to exit, look at the exit_status variable * instead of error_count for errors, so we don't miss errors! * * March 27th, 2000 by Robin Miller. * o Fixed parsing of a couple flags, including "flags=direct". * o Added "capacity=value" option to set the drive capacity. * * March 20th, 2000 by Robin Miller. * Don't allow a pass limit of zero. This also avoids a * core dump when preparing statistics, since the active device * never got setup. * * March 2nd, 2000 by Robin Miller. * In HandleMultiVolume(), reset the exit status to SUCCESS, * since this got set to END_OF_FILE earlier. Failure to do this * means we exit with an END_OF_FILE (254) status (no good :-). * * February 17th, 2000 by Robin Miller. * Adding better support for multi-volume tape testing. * * January 17th, 2000 by Robin Miller. * In copy mode, is input file is stdin, don't do verify. * Enable random I/O, for ralign, rlimit, or rseed options. * * January 14th, 2000 by Robin Miller. * Don't delete the output file unless we're in test mode. * * January 6th, 2000 by Robin Miller. * Added support for multi-volume media (lot's of changes). * * January 1st, 2000 by Robin Miller. * Added read after write support, "enable=raw" option. * * December 31st, 1999 by Robin Miller. * Added "rseed=" option, so user can specify the random seed. * Modify reopen_file logic to use O_RDWR if skip_count exists. * * November 11th, 1999 by Robin Miller. * Added logging of diagnostic information to event logger. * * November 10th. 1999 by Robin Miller. * Fixed parsing of "dispose=delete". * * November 9th, 1999 by Robin Miller. * Modify logic associated with making stderr stream buffered. * * August 26th, 1999 by Robin Miller. * If during the write pass no data was transferred, don't do * the read pass since obviously this will result in a compare error. * * August 7th, 1999 by Robin Miller. * Allow enabling open w/O_LARGEFILE via "flags=large", incase * _FILE_OFFSET_BITS=64 does _not_ enable this by default. * * July 22nd, 1999 by Robin Miller. * Added support for IOT (DJ's) test pattern. * * July 19th, 1999 by Robin Miller. * Add flag to control log file header normally displayed. * * June 28, 1999 by Robin Miller. * For 32-bit systems, change count and limit variables from * u_long to double, since u_long limits us to 4GB (too small). * * May 27, 1999 by Robin Miller. * Merge in Goerge Bittner's changes for micro-second delays. * Conditionalized all common/output file open flags. * * April 8, 1999 by Robin Miller. * Merge in Jeff Detjen's changes for table()/sysinfo timing. * * April 7, 1999 by Robin Miller. * On DEC systems, obtain tick/second via sysconf(_SC_CLK_TCK). * * December 21, 1998 by Robin Miller. * Add pasing of "enable=resets", which enables tape repositioning * logic when bus/device resets are detected by DUNIX EEI status codes. * * December 19, 1998 by Robin Miller. * - Disable setbuf() for stderr... screws up log files. * - Write cmd line and version to log file for reference. * * December 16, 1998 by Robin Miller. * Merge in changes made by George Bittner: * - set random_seed using times() instead of time(0). * - remove DLM_VALB, per Dan Christians' recommendation. * * October 29, 1998 by Robin Miller. * Implement a random I/O data limit, instead of using the normal * data limit variable (not good to dual purpose this value). * * October 22, 1998 by Robin Miller. (Happy Birthday Mom!) * When specifying a runtime, ensure we break out of the * passes loop if we exceed the error limit. Previously, we'd * loop (possibly with error) for the duration of runtime :-) * * June 16, 1998 by Robin Miller. * Add common open "flags={excl,ndelay,nonblock,rsync}". * Add output (write) open "oflags={defer,dsync,trunc}". * * April 29, 1998 by Robin Miller. * Add support for an alternate device directory. * * April 7, 1998 by Robin Miller. * Setup the pattern as a pattern string, so non-modulo * sizeof(pattern) read counts will data compare properly. * * March 20, 1998 by Robin Miller. * When terminating, call close function before reporting stats, * since in the case of AIO the I/O rundown updates statistics * with outstanding requests, when we're aborted by a signal. * * May 14, 1997 by Robin Miller. * In report_error(), set exit value to FAILURE instead of errno * since this makes detecting errors easier in scripts. * * May 13, 1997 by Robin Miller. * If a "skip=" count is specified with an output file "of=", * open the file for R/W access since skips are accompished via * read()'s of record size. * * March 27, 1997 by Ali Eghlima. * Added cluster support, so more than one process or one system * can access a resource. dlm are being used to synchronize all * access. * * March 7, 1997 by Robin Miller. * If a copy or verify operation is selected, and a data limit * is specified, then double the data limit to account for double * the I/O's. Previously, only half of the desired data limit was * copied and/or verified. * * February 3, 1997 by Robin Miller. * Check status from closing file descriptor, and if it's not * SUCCESS, use this for the exit status. For tapes, deferred * writes and failure to write file marks occur at close time. * * March 30, 1996 by Robin Miller. * When writing, now default to an infinite record limit, so * writes stop when EOF is reached (disks and tapes only). * * February 29, 1996 by Robin Miller. * Added callouts to setup data limit for disk random I/O. * * February 28, 1996 by Robin Miller. * Added support for copying and verifying device/files. * * February 17, 1996 by Robin Miller. * Add "enable=spad" option to control checking of pad bytes * when long reads of short records is performed, since some * controllers corrupt data bytes following the short record. * [ NOTE: At DEC, this action was deemed acceptable, since * the requester stated the driver could trash a larger buf. ] * * December 6, 1995 by Robin Miller. * Add "ttymin=value" option to give user control over tty VMIN. * * November 19, 1995 by Robin Miller. * When the device type is a disk, default min_size & incr_count to * BLOCK_SIZE (512) instead of 1 (which is non-modulo for disks). * * November 18, 1995 by Robin Miller. * Removing unaligned data access (ade) test code (code cleanup). * * July 24, 1995 by Robin Miller. * Changed O_NDELAY to O_NONBLOCK which is defined by POSIX. They are * supposed to do the same thing, but they _do_ have different values. * [ NOTE: This is only used for serial line modem testing. ] * * July 22, 1995 by Robin Miller. * Add additional checking on "pattern=string" to avoid ambiguity * with 4-byte hex data pattern strings... which is misleading). * * July 15, 1995 by Robin Miller. * Add "oncerr=action" option to control child process * error processing. * * July 7, 1995 by Robin Miller. * Add "dlimit=value" option to override default dump limit. * * July 6, 1995 by Robin Miller. * Enable dumping of data bytes on compare errors by default, * since this program is used mainly as a diagnostic tool, and * we now limit the number of bytes dumped. * * November 5, 1994 by Robin Miller. * Don't set SIGCHLD signal to SIG_IGN (set to SIG_DFL) or else * waitpid() won't detect any child processes (OSF R1.3 and QNX). * * September 23, 1994 by Robin Miller. * Make changes necessary to build on QNX 4.21 release. This * required changing O_DSYNCH to O_DSYNC, and O_FSYNCH to O_SYNC. * * November 11, 1993 by Robin Miller. * Removed code which was inadvertantly disabling data compares * when there was no output file and verify was disabled. This * caused data comparisions to be disabled for terminal devices, * now that the code has been rewritten/restructured (shit!!!). * * October 31, 1993 by Robin Miller. * Enhance device type setup and honor user specified device type. * * October 28, 1993 by Robin Miller. * Correct problem during loopback testing, where the device info * structure fd's were not marked closed due to two places where * close() was called directly (oops, missed a couple changes). * After forking, display which process is the reader and writer. * * October 11, 1993 by Robin Miller. * Conditionalize SunOS code (4.1.2 used) to avoid using strerror() * function for obtaining error messages (return to perror() method). * * October 7, 1993 by Robin Miller. * Add appropriate casting to block size checking, use ssize_t to * prevent negative block sizes which don't work with read & write * system calls (even though the count arg is declared as size_t). * * September 16, 1993 by Robin Miller. * Properly report write pass count when read verify is disabled. * Only update data pattern when writing files or loopback enabled * when multiple passes specified (pattern cannot change for reads). * Support "enable=loop" to enable loopback operation, rather than * requiring both the same input & output devices (need only one). * Added "oflags={append,sync}" for DEC OSF/1 systems (write opts). * * September 15, 1993 by Robin Miller. * Added additional check for FIFO's prior to opening the device * since O_NONBLOCK flag must be set to keep open() from blocking. * * September 8, 1993 by Robin Miller. * Moved generic test functions into file dtgen.c. * * September 7, 1993 by Robin Miller. * Moved tty specific code into dttty.c. This code is dispatched * to automatically after being setup in the device info structure. * * September 4, 1993 by Robin Miller. * Lots of restructuring & code cleanup. Nearly all testing is * now appropriately parameterized (helps speed & new tests). * * September 3, 1993 by Robin Miller. * Dispatch to test functions via function lookup table in * preparation for device specific tests. * * September 1, 1993 by Robin Miller. * Added "min=value", "max=value", and "incr=value" options for * testing variable length records (mainly for tape devices). * * August 31, 1993 by Robin Miller. * Added "align=rotate" option to rotate the starting data buffer * address through sizeof(ptr) to force unaligned buffer accesses. * This feature tests special handling of unaligned DMA addresses. * Added "pattern=incr" option to use incrementing data pattern. * * August 27, 1993 by Robin MIller. * Added support for DEC OSF/1 POSIX Asynchronous I/O (AIO). * * August 20, 1993 by Robin Miller. * Added handling of reading & writing multiple tape files. * * August 18, 1993 by Robin Miller. * Added "step=" option to specify a step offset for seeks. * This is the number of bytes stepped after an I/O request. * * August 17, 1993 by Robin Miller. * Added reporting of start & end times, time of errors, and * added "runtime=" option to specify how long to run. * * August 13, 1993 by Robin Miller. * Added alternate data patterns to use with multiple passes. * * August 5, 1993 by Robin Miller. * Added "files=value" option for processing tape files. * * August 3, 1993 by Robin Miller. * Added "procs=value" option to create multiple processes. * * September 19, 1992 by Robin Miller. * Initialize flush_flag to TRUE so flushing of tty I/O queues * gets done by default to discard any left over junk. * * September 18, 1992 by Robin Miller. * Added calls to save & restore terminal characteristics. * * September 17, 1992 by Robin Miller. * Added parsing for "pattern=string" to allow ASCII string. * * September 12, 1992 by Robin Miller. * Ensure the stderr stream is fully buffered, so the total time * is not affected by flushing previous statistics. * * September 11, 1992 by Robin Miller. * Added "oflags={cache,dsync,fsync,temp}" options to allow testing * the affect of these open flags on QNX systems. * * September 10, 1992 by Robin Miller. * Added parsing of MARK & SPACE parity for QNX Operating System. * * September 9, 1992 by Robin Miller. * Allow loopback to same tty port (avoid exclusive open). * * September 5, 1992 by Robin Miller. * Initial port to QNX 4.1 Operating System. * * September 3, 1992 by Robin Miller. * Make changes necessary to remove BSD dependencies. * * August 19, 1992 by Robin Miller. * Added support for testing tty modem control. * * May 25, 1992 by Robin Miller. * Don't set exclusive open mode for terminal device if debug is * enabled, so "stty -a" can be done on terminal under test. * * May 22, 1992 by Robin Miller. * Control / force kernel address data exception via flag. * Added option "enable=ade" (address data exception). * * April 24, 1992 by Robin Miller. * Inform user that either a record count or a data limit must be * specified. Added flags to determine if we are reading stdin or * writing to stdout streams. * * March 11, 1992 by Robin Miller. * Changes necessary for port to 64-bit Alpha architecture. * * October 16, 1990 by Robin Miller. * Added "align=offset" option to align memory buffers at a specific * offset. The default is page aligned. * * October 9, 1990 by Robin Miller. * Added "hz=ticks" option to allow the ticks per second value to * be specified. On VAX systems, this is consistent (100), but on * MIPS systems it various and is setup at boot time in the machine * dependent code. * * August 21, 1990 by Robin Miller. * Changed exit status so scripts can detect and handle errors * based on the exit code. If not success, fatal error, or end * of file/tape, the exit code is the error number (errno). * * August 21, 1990 by Robin Miller. * Added "position=offset" option to position to specified offset * before starting test. * * August 16, 1990 by Robin Miller. * Added "enable=coredump" option so if exiting with error status, * we'll generate a core dump for analysis purposes. * * August 8, 1990 by Robin Miller. * Changed malloc() to valloc() to align buffer on page boundry. * On some archetectures, this results on better performance to * raw devices since the DMA is done directly to the users' buffer. * * August 2, 1990 by Robin Miller. * Added "pf=file" to specify a pattern file to use. In this * case, the contents of the specified file are used for the * data pattern. * * April 11, 1990 by Robin Miller. * Added "enable=mmap" option to enable memory mapped I/O. * * March 22, 1990 by Robin Miller. * Added "dispose={delete|keep}" option to control deleting the * output file at the end of testing. "delete" is the default * action, to avoid leaving test files around. * * Novemeber 9, 1989 by Robin Miller. * Add additional delay commands (cdelay=, rdelay=, wdelay=) to * help with testing and debugging various devices. These aren't * normally enabled and don't appear in the help section. * * Novemeber 6, 1989 by Robin Miller. * Allow start delay (sdelay=) and end delay (edelay=) to be used * with any device, not just terminals. The parallel output device * needs an end delay to allow the parallel input device to reopen * during multiple passes (can't disable cycle request). * * November 3, 1989 by Robin Miller. * Change open mode for output files from read/write (O_RDWR) to * write only (O_WRONLY) so write only devices can be tested. * * October 16, 1989 by Robin Miller. * Made flushing of input queue before testing optional via * the "enable=flush" command. This caused a timing problem * where occasionally the writer started before the reader. * * October 4, 1989 by Robin Miller. * Added fsync() system call to force in-core buffers to disk * before reporting statistics after writing the file. * * September 27, 1989 by Robin Miller. * Display statistics at end of each read/write pass by default. * Added enable/disable=pstats to control end of pass statistics. * Added enable/disable=compare to control data comparisions. * * September 18, 1989 by Robin Miller. * Allow both an input and an output device to be specified. * This was done mainly for terminal devices so a child could * be forked to allow proper synchronization. * * July 25, 1989 by Robin Miller. * Added support for testing terminal ports. New options are: * speed=, timeout=, parity=, flow=, sdelay=, edelay= * * January 25, 1989 by Robin Miller. * Added reporting of pass statistics if debugging is enabled * so write/read elapsed times can be determined. * * December 8, 1988 by Robin Miller. * Added catching of software terminate signal (SIGTERM) so the * final statistics will be displayed before exiting. */ #if defined(ultrix) || defined(sun) extern int sys_nerr; extern char *sys_errlist[]; #endif /* defined(sun) */ /* * Forward References: */ static u_long number(int base); /* Numeric conversion function. */ static large_t large_number(int base); /* Large numeric conversions. */ void parse_args(int argc, char **argv); static time_t time_value(void); /* Time conversion function. */ /* * Variable Declarations: */ #if !defined(AIO) int aio_bufs = 0; /* Normally declared in dtaio.c, but for parsing. */ #endif /* !defined(AIO) */ #if defined(MUNSA) bool munsa_flag = FALSE; /* if TRUE enable MUNSA features*/ dlm_lock_mode_t munsa_lock_type = DLM_NLMODE; /* default munsa lock type*/ dlm_lock_mode_t input_munsa_lock_type = DLM_PRMODE; /* lock for input file*/ dlm_lock_mode_t output_munsa_lock_type = DLM_PWMODE; /* lock for output file*/ char *resnam; int resnlen, i; dlm_lkid_t lkid; dlm_status_t l_stat; dlm_nsp_t nsp; #endif /* defined(MUNSA) */ u_int32 pattern = DEFAULT_PATTERN; /* Default data pattern. */ bool user_pattern = FALSE; /* Flags user specified pattern */ bool unique_pattern = TRUE; /* Unique pattern per process. */ char *pattern_string; /* Pointer to pattern string. */ char *prefix_string; /* User defined prefix string. */ bool aio_flag = FALSE; /* Asynchronous I/O (AIO) flag. */ int align_offset = 0; /* Align buffer at this offset. */ #if defined(TTY) speed_t baud_rate; /* The user selected baud rate. */ #endif /* defined(TTY) */ size_t block_size = BLOCK_SIZE; /* Default block size to use. */ size_t data_size; /* Data buffer size + pad bytes */ size_t dump_limit = 64; /* The dump buffer data limit. */ u_int32 device_size = 0; /* Default device block size. */ pid_t child_pid; /* For the child process ID. */ bool bypass_flag = FALSE; /* Bypass (some) sanity checks. */ bool cerrors_flag = TRUE; /* Report device close errors. */ bool compare_flag = TRUE; /* Controls data comparisions. */ bool core_dump = FALSE; /* Generate core dump on errors */ bool debug_flag = FALSE; /* Enable debug output flag. */ bool Debug_flag = FALSE; /* Verbose debug output flag. */ bool eDebugFlag = FALSE; /* End of file debug flag. */ bool rDebugFlag = FALSE; /* Random (seek) debug flag. */ bool dump_flag = TRUE; /* Dump data buffer on errors. */ #if defined(EEI) bool eei_flag = TRUE; /* Extended Error Information. */ bool eei_resets = FALSE; /* Handle device reset errors. */ #endif /* defined(EEI) */ v_bool end_of_file = FALSE; /* End of file detected. */ #if defined(_NT_SOURCE) /* * This workaround is being added for our Tcl/Tk test scripts. * The Windows TclX wait{} procedure, isn't returning exit status! */ bool eof_status = FALSE; /* Controls EOF exit status. */ #else /* !defined(_NT_SOURCE) */ bool eof_status = TRUE; /* Controls EOF exit status. */ #endif /* defined(_NT_SOURCE) */ vu_long error_count; /* Number of errors detected. */ u_long error_limit = 1; /* Number of errors tolerated. */ int exit_status = SUCCESS; /* Normal success exit status. */ u_long file_limit; /* # of tape files to process. */ bool forked_flag = FALSE; /* Forked child process flag. */ bool fsync_flag = UNINITIALIZED; /* fsync() after writes flag. */ bool fsalign_flag = FALSE; /* Align FS offsets and sizes. */ off_t file_position; /* File position to lseek to. */ off_t last_position; /* Last position lseeked to. */ off_t step_offset; /* Step offset for disk seeks. */ bool flush_flag = TRUE; /* Flush tty input/output queue */ bool keep_existing = TRUE; /* Don't delete existing files. */ bool header_flag = TRUE; /* The log file header flag. */ bool hazard_flag = FALSE; /* Emit Hazard RPCLOGn: prefix. */ bool user_incr = FALSE; /* User specified incr count. */ bool user_min = FALSE; /* User specified min size. */ bool user_max = FALSE; /* User specified max size. */ size_t incr_count; /* Record increment byte count. */ size_t min_size; /* The minimum record size. */ size_t max_size; /* The maximum record size. */ bool lbdata_flag = FALSE; /* Logical block data flag. */ u_int32 lbdata_addr = 0; /* Starting logical block addr. */ size_t lbdata_size = 0; /* Logical block data size. */ bool user_lbdata = FALSE; /* User specified starting lba. */ bool user_lbsize = FALSE; /* User specified lbdata size. */ bool user_position = FALSE; /* User specified file position.*/ bool incr_pattern = FALSE; /* Incrementing data pattern. */ bool iot_pattern = FALSE; /* IOT test pattern selected. */ bool logdiag_flag = FALSE; /* Log diagnostic messages. */ bool loopback = FALSE; /* Loopback to the same device. */ bool micro_flag = FALSE; /* Controls micro-second delay. */ bool mmap_flag = FALSE; /* Do memory mapped file I/O. */ #if defined(TTY) bool modem_flag = FALSE; /* Testing tty modem control. */ #endif /* defined(TTY) */ bool media_changed = FALSE; /* Shows when media changed. */ bool multi_flag = FALSE; /* Multi-volume media flag. */ v_int multi_volume = 1; /* Multi-volume media count. */ int open_flags = 0; /* Common file open flags. */ int wopen_flags = 0; /* Additional write open flags. */ int ropen_mode = O_RDONLY; /* The read open mode to use. */ int wopen_mode = O_WRONLY; /* The write open mode to use. */ bool pad_check = TRUE; /* Check data buffer pad bytes. */ bool spad_check = FALSE; /* Check short record pad bytes.*/ u_long pass_count; /* Number of passes completed. */ u_long pass_limit = 1UL; /* Default number of passes. */ u_long skip_count; /* # of input record to skip. */ u_long seek_count; /* # of output records to seek. */ large_t record_limit; /* Max # of records to process. */ vu_long records_processed; /* # of full records processed. */ vu_long partial_records; /* # of partial records proc'ed */ large_t data_limit; /* Total data limit per pass. */ u_long random_align = 0UL; /* Random I/O offset alignment. */ large_t rdata_limit = 0; /* The random I/O data limit. */ large_t total_bytes; /* Total bytes transferred. */ large_t total_bytes_read; /* Total bytes read. */ large_t total_bytes_written; /* Total bytes written. */ vu_long total_errors; /* Total errors (all passes). */ large_t total_files; /* Total files (all passes). */ large_t total_files_read; /* Total files read. */ large_t total_files_written; /* Total files written. */ large_t total_records; /* Total records (all passes). */ u_long total_partial; /* Total partial records. */ u_long warning_errors; /* Total non-fatal error count. */ bool pstats_flag = TRUE; /* Display per pass statistics. */ bool raw_flag = FALSE; /* The read after write flag. */ bool rotate_flag = FALSE; /* Force data buffer rotating. */ int rotate_offset = 0; /* Current rotate buffer offset */ bool stats_flag = TRUE; /* Display total statistics. */ bool stdin_flag = FALSE; /* Presume not reading stdin. */ bool stdout_flag = FALSE; /* Presume not writing stdout. */ bool terminating_flag = FALSE; /* Program terminating flag. */ bool ttyport_flag = FALSE; /* Input/output is a terminal. */ bool verbose_flag = TRUE; /* Verbose messages output. */ bool verify_flag = TRUE; /* Verify the read/write data. */ bool verify_only = FALSE; /* Verify of copied data flag. */ char *cmd_line; /* Copy of our command line. */ char *log_file; /* Pointer to log file name. */ char *log_buffer; /* Pointer to log file buffer. */ char *log_bufptr; /* Pointer into log buffer. */ char *msg_buffer; /* Diagnostic message buffer. */ char *input_file; /* Pointer to input file name. */ char *output_file; /* Pointer to output file name. */ char *pattern_file; /* Pointer to pattern file name */ u_char *pattern_buffer; /* Pointer to pattern buffer. */ u_char *pattern_bufptr; /* Pointer into pattern buffer. */ u_char *pattern_bufend; /* Pointer to end of pat buffer */ u_char *base_buffer; /* Base address of data buffer. */ u_char *data_buffer; /* Pointer to data buffer. */ u_char *mmap_buffer; /* Pointer to mmapped buffer. */ u_char *mmap_bufptr; /* Pointer into mmapped buffer. */ u_char *verify_buffer; /* The data verification buffer.*/ size_t patbuf_size; /* The pattern buffer size. */ int pattern_size; /* User specified pattern size. */ int prefix_size; /* User defined prefix size. */ int page_size = 0; /* Define number of bytes/page. */ #if defined(HP_UX) u_int qdepth = 0xFFFFFFFF; /* Value to set queue depth to. */ #endif u_int cdelay_count = 0; /* Delay before closing file. */ u_int edelay_count = 0; /* Delay between multiple passes*/ u_int rdelay_count = 0; /* Delay before reading record. */ u_int sdelay_count = 0; /* Delay before starting test. */ u_int tdelay_count = 1; /* Child terminate delay count. */ u_int wdelay_count = 0; /* Delay before writing record. */ u_int random_seed = 0; /* Seed for random # generator. */ bool user_rseed = FALSE; /* Flags user specified rseed. */ bool max_capacity = FALSE; /* Use max capacity from IOCTL. */ large_t user_capacity; /* The user set drive capacity. */ bool variable_flag = FALSE; /* Variable block size flag. */ bool volumes_flag = FALSE; /* Flags the volumes option. */ int volume_limit = 0; /* Number of volumes to process.*/ vu_long volume_records = 1; /* The last volume record limit.*/ #if defined(TTY) enum opt softcar_opt = OPT_NONE; /* Leave tty soft carrier alone.*/ enum flow flow_type = XON_XOFF; /* Terminal flow type to use. */ #endif /* defined(TTY) */ enum iodir io_dir = FORWARD; /* Default is forward I/O. */ enum iomode io_mode = TEST_MODE; /* Default to testing mode. */ enum iotype io_type = SEQUENTIAL_IO; /* Default to sequential I/O. */ enum dispose dispose_mode = DELETE_FILE; /* Output file dispose mode. */ enum onerrors oncerr_action = CONTINUE; /* The child error action. */ /* Error limit controls tests. */ enum trigger_type trigger = TRIGGER_NONE; /* Trigger for corruptions. */ char *trigger_cmd = NULL; /* The uSers' trigger command. */ #if defined(SOLARIS) || defined(OSFMK) || defined(__QNXNTO__) || defined(AIX) clock_t hz; #else clock_t hz = HZ; /* Default clock ticks / second */ #endif #if defined(TTY) /* * Values for Terminal (serial) Line Testing: */ u_short tty_timeout = 3*10; /* Default tty timeout (3 sec). */ /* VTIME = 0.10 second interval */ bool tty_minflag = FALSE; /* User specified VMIN value. */ u_short tty_minimum = 0; /* The tty minimum (VMIN) value */ #endif /* defined(TTY) */ FILE *efp; /* Default error data stream. */ FILE *ofp; /* Default output data stream. */ int pfd = NoFd; /* Pattern file descriptor. */ char *cmdname; /* Pointer to our program name. */ char *string; /* Pointer to argument string. */ /* * Pointers to various device information. */ struct dinfo *active_dinfo; /* Active device information. */ struct dinfo *input_dinfo; /* Input device information. */ struct dinfo *output_dinfo; /* Output device information. */ struct dtype *input_dtype; /* The input device type info. */ struct dtype *output_dtype; /* The output device type info. */ /* * System time information. */ clock_t start_time, end_time, pass_time; /* Per pass elapsed time. */ struct tms stimes, ptimes, etimes; /* For user / system times. */ #if defined(DEC) bool table_flag = FALSE; /* Table control flag. */ struct tbl_sysinfo s_table, p_table, e_table; /* Table information. */ #endif /* defined(DEC) */ #if defined(_BSD) union wait child_status; /* For child exit status. */ #else /* !defined(_BSD) */ int child_status; /* For child exit status. */ #endif /* defined(_BSD) */ /* * Program run time information. */ time_t runtime; /* The program run time. */ time_t elapsed_time; /* Amount of time program ran. */ time_t program_start, program_end; /* Program start & end times, */ time_t error_time; /* Time last error occurred. */ bool TimerActive; /* Set after timer activated. */ bool TimerExpired; /* Set after timer has expired. */ char *user_runtime; /* User specific runtime string */ /* * Data patterns used for multiple passes. */ u_int32 data_patterns[] = { DEFAULT_PATTERN, 0x00ff00ffU, 0x0f0f0f0fU, 0xc6dec6deU, 0x6db6db6dU, 0x55555555U, 0xaaaaaaaaU, /* Complement of previous data pattern. */ 0x33333333U, /* Continuous worst case pattern (media defects) */ 0x26673333U, /* Frequency burst worst case pattern #1. */ 0x66673326U, /* Frequency burst worst case pattern #2. */ 0x71c7c71cU, /* Dibit worst case data pattern. */ 0x00000000U, 0xffffffffU, }; int npatterns = sizeof(data_patterns) / sizeof(u_int32); /* * main() - Start of data transfer program. */ int main (int argc, char **argv) { struct dinfo *dip; struct dtfuncs *dtf; char *tmp; int status; efp = stderr; /* Initialize our error stream. */ ofp = stdout; /* Initialize our output stream.*/ #if defined(_BSD) tmp = rindex (argv[0], '/'); #else /* !defined(_BSD) */ tmp = strrchr (argv[0], '/'); #endif /* defined(_BSD) */ cmdname = tmp ? &(tmp[1]) : argv[0]; #if defined(_QNX_SOURCE) && !defined(_QNX_32BIT) page_size = 0; /* Presume page size doesn't matter. */ #elif defined(_QNX_SOURCE) && defined(_QNX_32BIT) page_size = 4096; /* Not sure how to query for this... */ #elif defined(DEC) || defined(SOLARIS) || defined(__linux__) || defined(SCO) || defined(__NUTC__) || defined(HP_UX) || defined(AIX) hz = sysconf(_SC_CLK_TCK); page_size = sysconf(_SC_PAGESIZE); #else /* !defined(_QNX_SOURCE) && !defined(_QNX_32BIT) */ page_size = getpagesize(); #endif /* defined(_QNX_SOURCE) && !defined(_QNX_32BIT) */ #if defined(OSFMK) || defined(__QNXNTO__) hz = CLK_TCK; /* Actually a libc function. */ #endif /* defined(OSFMK) || defined(__QNX_NTO__) */ #if defined(LOG_DIAG_INFO) /* * Allow DT_LOG_DIAG to enable logging diagnostic information. */ if (tmp = getenv ("DT_LOG_DIAG")) { logdiag_flag = TRUE; } #endif /* defined(LOG_DIAG_INFO) */ data_limit = INFINITY; /* Set to maximum limit. */ parse_args (argc, argv); /* * Options parsed, validate options, do initialization, and open * input & output files to be tested. */ program_start = time((time_t) 0); /* Record our start time. */ /* * If a log file was specified, redirect stderr to that file. */ if (log_file) { ofp = efp; /* Send all output to stderr. */ if (freopen (log_file, "a", efp) == NULL) { report_error (log_file, TRUE); exit (exit_status); } } /* * Make stderr buffered, so timing is not affected by output. */ if ((log_buffer = (char *) malloc (LOG_BUFSIZE)) == NULL) { LogMsg (efp, logLevelCrit, 0, "Unable to allocate log file buffer of %d bytes, exiting...\n", LOG_BUFSIZE); exit (ENOMEM); } /* * The concept here is simple, set stderr buffered so multiple processes * don't have their output intermixed. This piece of code has been very * problematic, so it you have problems with garbled output, remove it. */ log_bufptr = log_buffer; /* * Since stderr is normally unbuffered, we make it buffered here. */ if ( isatty(fileno(efp)) ) { char *stderr_buffer = (char *)malloc(LOG_BUFSIZE); if (stderr_buffer == NULL) { LogMsg (efp, logLevelCrit, 0, "Unable to allocate stderr buffer of %d bytes, exiting...\n", LOG_BUFSIZE); exit (ENOMEM); } /* * Can't use log buffer, or we get undesirable results :-) */ if (setvbuf(efp, stderr_buffer, _IOFBF, LOG_BUFSIZE) < 0) { report_error ("setvbuf", TRUE); exit (exit_status); } } /* * Write the command line to the log file, if one exists. */ if (log_file && header_flag) { int arg; /* * Write the command line for future reference. */ Lprintf("Command Line:\n\n %c ", getuid() ? '%' : '#'); for (arg = 0; arg < argc; arg++) { Lprintf("%s ", argv[arg]); } Lprintf("\n\n\t--> %s <--\n", version_str); Lflush(); } if (!input_file && !output_file) { LogMsg (efp, logLevelCrit, 0, "You must specify an input file, an output file, or both.\n"); exit (FATAL_ERROR); } /* * Disallow both seek type options, to simplify test loops. */ if ( (io_dir == REVERSE) && (io_type == RANDOM_IO) ) { LogMsg (efp, logLevelCrit, 0, "Please specify one of iodir=reverse or iotype=random, not both!\n"); exit (FATAL_ERROR); } if (slice_num) { if (!num_slices) { LogMsg (efp, logLevelCrit, 0, "Please specify number of slices with slice option!\n"); exit (FATAL_ERROR); } else if (slice_num > num_slices) { LogMsg (efp, logLevelCrit, 0, "Please specify slice (%d) <= max slices (%d)\n", slice_num, num_slices); exit (FATAL_ERROR); } } #if defined(MUNSA) if (munsa_flag) { if (input_file && !output_file) { input_munsa_lock_type = munsa_lock_type; if (debug_flag) { Printf ("input_munsa_lock_type = %d\n", input_munsa_lock_type); } } if (output_file && !input_file) { if ((munsa_lock_type == DLM_PWMODE) || (munsa_lock_type == DLM_EXMODE)) { output_munsa_lock_type = munsa_lock_type; if (debug_flag) { Printf ("output_munsa_lock_type = %d\n", output_munsa_lock_type); } } else { LogMsg (efp, logLevelCrit, 0, "invalid write lock type it should be pw,ex\n"); exit(FATAL_ERROR); } } if (input_file && output_file) { input_munsa_lock_type = DLM_PRMODE; output_munsa_lock_type = DLM_PWMODE; if (debug_flag) { Printf ("input_munsa_lock_type = %d\n", input_munsa_lock_type); Printf ("output_munsa_lock_type = %d\n", output_munsa_lock_type); } } } /* end if(munsa_flag) .... */ #endif /* defined(MUNSA) */ if ( (!input_file || !output_file) && ((io_mode == COPY_MODE) || (io_mode == VERIFY_MODE)) ) { LogMsg (efp, logLevelCrit, 0, "Copy/verify modes require both input and output devices.\n"); exit (FATAL_ERROR); } /* * When reading multiple tape files, don't require data or record * limits (these will vary). But when writing multiple tape files, * we need to know how many records or bytes to be written. */ if (input_file && !output_file && file_limit && !record_limit) { record_limit = INFINITY; } /* * Check the variable record size parameters. */ if (min_size && !max_size) max_size = block_size; if (block_size < max_size) block_size = max_size; /* NOTE: Other checks are done below now... */ /* * Calculate the data limit if it wasn't specified by the user. */ if ( (data_limit == INFINITY) && ( (record_limit != 0L) && (record_limit != INFINITY) ) ) { data_limit = (block_size * record_limit); } /* * Process the pattern file (if one was specified). */ if (pattern_file) { process_pfile (&pfd, pattern_file, O_RDONLY); } if (min_size && (max_size <= min_size)) { LogMsg (efp, logLevelCrit, 0, "Please specify max count > min count for record sizes.\n"); exit (FATAL_ERROR); } /* * Verify counts are large enough, to avoid false compare errors. */ #if 0 if ( !bypass_flag && (iot_pattern || lbdata_flag) && ((block_size < sizeof(u_int32)) || (min_size && (min_size <= sizeof(u_int32)))) ) { LogMsg (efp, logLevelCrit, 0, "Please specify block sizes > %d for IOT or Lbdata options!\n", sizeof(u_int32)); exit (FATAL_ERROR); } #endif if ( (iot_pattern || lbdata_flag) && (block_size < lbdata_size) ) { LogMsg (efp, logLevelCrit, 0, "Please specify a block size >= %u (lbdata size) for IOT or Lbdata options!\n", lbdata_size); exit (FATAL_ERROR); } if ( ((io_mode == COPY_MODE) || (io_mode == VERIFY_MODE)) && (iot_pattern || lbdata_flag) ) { LogMsg (efp, logLevelCrit, 0, "IOT and Lbdata options disallowed with Copy/Verify options!\n"); exit (FATAL_ERROR); } /* * Do special handling of IOT data pattern. */ if (iot_pattern) { size_t size = block_size; u_char *buffer = (u_char *) myalloc(size, 0); setup_pattern (buffer, size); pattern_string = "IOT Pattern"; /* IOT takes precedence! */ lbdata_flag = FALSE; user_lbdata = FALSE; } /* * When doing random I/O, enable file system alignments, to help * prevent false corruptions. This only affects regular files. */ if (io_type == RANDOM_IO) { fsalign_flag = TRUE; /* Align FS sizes & random offsets. */ } /* * Setup the pattern as a pattern string, so non-modulo * sizeof(u_int) read counts will data compare properly. */ if (!pattern_buffer) { size_t size = sizeof(u_int32); u_char *buffer = (u_char *) Malloc (size); copy_pattern (pattern, buffer); setup_pattern (buffer, size); } #if defined(TTY) /* * The following check was added for tty loopback to same port. * [ A future version may extend this support to other devices. ] */ if (input_file && output_file) { if (strcmp (input_file, output_file) == 0) { loopback = TRUE; /* Loopback to the same file. */ } } else if (loopback) { if (!input_file && output_file) input_file = output_file; if (input_file && !output_file) output_file = input_file; } #endif /* defined(TTY) */ /* * Setup the initial device information & validate options. */ if (input_file) { dip = setup_device_info (input_file, input_dtype); active_dinfo = input_dinfo = dip; dip->di_mode = READ_MODE; dip->di_ftype = INPUT_FILE; status = (*dip->di_funcs->tf_validate_opts)(dip); if (status == FAILURE) exit (FATAL_ERROR); } if (output_file) { dip = setup_device_info (output_file, output_dtype); active_dinfo = output_dinfo = dip; dip->di_mode = WRITE_MODE; dip->di_ftype = OUTPUT_FILE; status = (*dip->di_funcs->tf_validate_opts)(dip); if (status == FAILURE) exit (FATAL_ERROR); /* * The following problem was resolved by not switching patterns * when multiple processes are selected. So, this will go... */ #if 0 /* * Sanity Check: Major source of problem reports due to folks * using multiple processes with multiple passes, and subsequent * passes overwriting the previous data pattern! */ if ( (pass_limit > 1) && (num_procs > 1) && !user_pattern && dip->di_random_access ) { LogMsg (ofp, logLevelWarn, 0, "Warning: Multiple passes with multiple processes can cause unpredictable\n"); LogMsg (ofp, logLevelWarn, 0, "results due to process scheduling, since each pass uses a different pattern!\n"); } #endif } #if defined(sun) && defined(TTY) /* * Soft carrier existed on the Sun/386i (Roadrunner) system. * Setting O_NDELAY was necessary to open the terminal line. * * Normally, O_NDELAY must NOT be set, otherwise the terminal * driver returns EWOULDBLOCK on reads if no data is available. * * Note: Without O_NDELAY, the open will hang on modem lines * (-CLOCAL) if carrier is not asserted. */ if (ttyport_flag) { open_flags = O_NDELAY; /* Incase no soft carrier. */ } #endif /* defined(sun) && defined(TTY) */ /* * Do multiple slices processing. */ if (num_slices) { if (input_file && output_file) { LogMsg (efp, logLevelCrit, 0, "Please specify only an input or output file, not both!\n"); exit (FATAL_ERROR); } /* * Create multiple slices (if requested). */ active_dinfo = dip = (input_file) ? input_dinfo : output_dinfo; if (!dip->di_random_access) { LogMsg (efp, logLevelCrit, 0, "Multiple slices is only supported on random access devices!\n"); exit (FATAL_ERROR); } if ((status = FindCapacity (dip)) == FAILURE) { exit (FATAL_ERROR); } /* * The remaining work is done when starting the processes. */ if (slice_num) { init_slice(active_dinfo, slice_num); num_slices = 0; /* Operate on a single slice. */ } else { if ( start_slices() ) { await_procs(); exit (exit_status); } } } /* * Create multiple processes (if requested). */ if (num_procs && !loopback && !ttyport_flag) { if ( start_procs() ) { await_procs(); exit (exit_status); } } /* * Open device / Setup system / device specific test information. */ if (input_file) { int open_mode = (ropen_mode | open_flags); dip = active_dinfo = input_dinfo; if ((*dip->di_funcs->tf_open)(dip, open_mode) == FAILURE) { exit (exit_status); } system_device_info (dip); input_dtype = dip->di_dtype; status = (*dip->di_funcs->tf_validate_opts)(dip); if (status == FAILURE) exit (FATAL_ERROR); #if defined(MUNSA) if (munsa_flag) { /* first we need to join a namespace */ l_stat = dlm_nsjoin( getuid(), &nsp, DLM_USER); if ((l_stat != DLM_SUCCESS) && (l_stat != DLM_ATTACHED)) { Fprintf ("Can't join namespace\n"); dlm_error(0, l_stat); } resnam = input_file; /* now let DLM know what signal to use for blocking routines */ dlm_set_signal(SIGIO, &i); resnlen = strlen(resnam); if (debug_flag) { Printf ("dlm_set_signal: i %d\n", i); Printf ("resnam %s\n", resnam); Printf ("grab a NL mode lock\n"); } l_stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid, DLM_NLMODE, NULL, DLM_SYNCSTS, 0, 0, 0, 0); /* NL mode better be granted SYNC status */ if (l_stat != DLM_SYNCH) { if (debug_flag) { Fprintf ("dlm_lock failed\n"); } dlm_error(&lkid, l_stat); } } /* end if(munsa_flag)... */ #endif /* defined(MUNSA) */ /* * If disk device and random I/O selected, attempt to get * device / partition capacity to limit random I/O seeks. */ if ( user_capacity || ( ((io_dir == REVERSE) || (io_type == RANDOM_IO)) && (dip->di_random_access && !num_slices) ) ) { if ((status = FindCapacity (dip)) == FAILURE) { exit (exit_status); } } if (!record_limit) { record_limit = INFINITY; /* Read until EOF on reads. */ } } /* * Process the output device/file. */ if (output_file) { int open_mode; dip = active_dinfo = output_dinfo; /* * If a skip count was specified, open output file for R/W, * since skips are accomplished via read()'s. (for pelle) */ if (skip_count || raw_flag) { open_mode = (O_RDWR | wopen_flags | open_flags); } else { open_mode = (wopen_mode | wopen_flags | open_flags); } /* * Don't create files in the /dev directory (presume the file * should exist instead of creating file & misleading user). */ if ( (NEL (output_file, DEV_PREFIX, DEV_LEN)) && (NEL (output_file, ADEV_PREFIX, ADEV_LEN)) ) { open_mode |= O_CREAT; /* For creating test files. */ } /* * If verify mode, the output device is open for reads. */ if (io_mode == VERIFY_MODE) { open_mode = (ropen_mode | open_flags); dip->di_mode = READ_MODE; } if ((*dip->di_funcs->tf_open)(dip, open_mode) == FAILURE) { exit (exit_status); } #if defined(MUNSA) if (munsa_flag) { /* first we need to join a namespace */ l_stat = dlm_nsjoin( getuid(), &nsp, DLM_USER); if ((l_stat != DLM_SUCCESS) && (l_stat != DLM_ATTACHED)) { Fprintf ("Can't join namespace\n"); dlm_error(0, l_stat); } resnam = output_file; /* now let DLM know what signal to use for blocking routines */ dlm_set_signal(SIGIO, &i); /* do we need this ????? */ resnlen = strlen(resnam); if (debug_flag) { Printf ("dlm_set_signal: i %d\n", i); Printf ("resnam %s\n", resnam); Printf ("grab a NL mode lock\n"); } l_stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid, DLM_NLMODE, NULL, DLM_SYNCSTS, 0, 0, 0, 0); /* NL mode better be granted SYNC status */ if (l_stat != DLM_SYNCH) { Fprintf ("dlm_lock failed\n"); dlm_error(&lkid, l_stat); } } /* end if (munsa_flag)... */ #endif /* defined(MUNSA) */ system_device_info (dip); output_dtype = dip->di_dtype; open_flags &= ~O_CREAT; /* Only create on first open. */ status = (*dip->di_funcs->tf_validate_opts)(dip); if (status == FAILURE) exit (FATAL_ERROR); /* * If disk device and random I/O selected, attempt to get * device / partition capacity to limit random I/O seeks. */ if ( user_capacity || ( ((io_dir == REVERSE) || (io_type == RANDOM_IO)) && (dip->di_random_access && !num_slices) ) ) { if ((status = FindCapacity (dip)) == FAILURE) { exit (exit_status); } } /* * For disks and tapes, default writing until EOF is reached. */ if ( !record_limit && ((dip->di_dtype->dt_dtype == DT_DISK) || (dip->di_dtype->dt_dtype == DT_BLOCK) || (dip->di_dtype->dt_dtype == DT_TAPE)) ) { record_limit = INFINITY; } } /* * Set the default lbdata size, if not setup by the system * dependent functions above. Delaying this check to this * point allows the device sector size to be setup, instead * of forcing it to 512 byte blocks. At least this is true * on Tru64 Unix, where this disk information is available. */ if (!lbdata_size) lbdata_size = BLOCK_SIZE; /* * This is to catch me (dah!) as much as anyone else :-) */ if ( (rdata_limit || random_align) && ((io_dir != REVERSE) && (io_type != RANDOM_IO)) ) { Printf ("Warning, random options have no effect without iotype=random!\n"); } /* * If random I/O was selected, and a data limit isn't available, * inform the user we need one, and don't allow testing. */ if (rdata_limit == 0UL) rdata_limit = data_limit; if ( (rdata_limit == 0) && (io_type == RANDOM_IO) ) { LogMsg (efp, logLevelCrit, 0, "Please specify a record or data limit for random I/O.\n"); exit (FATAL_ERROR); } /* * Sanity check the random I/O data limits. */ if ( (io_type == RANDOM_IO) && ((file_position + block_size + random_align) > rdata_limit)) { LogMsg (efp, logLevelCrit, 0, "The max block size is too large for random data limits!\n"); if (Debug_flag) { Printf ("file position " FUF ", bs=%ld, ralign=%ld, rlimit=" LUF "\n", file_position, block_size, random_align, rdata_limit); } exit (FATAL_ERROR); } /* * Ensure either a data limit and/or a record count was specified. */ if (!record_limit) { LogMsg (efp, logLevelCrit, 0, "You must specify a data limit, a record count, or both.\n"); exit (FATAL_ERROR); } /* * Allocate buffer for pre-formatting messages. */ msg_buffer = (char *)Malloc(LOG_BUFSIZE); #if defined(LOG_DIAG_INFO) /* * If logging of diagnostic info is enabled, save copy of cmd line. */ if (logdiag_flag) { int arg; char *bp; bp = cmd_line = (char *)Malloc(LOG_BUFSIZE); for (arg = 0; arg < argc; arg++) { (void)sprintf(bp, "%s ", argv[arg]); bp += strlen(bp); } sprintf(bp, "\n"); } #endif /* defined(LOG_DIAG_INFO) */ /* * Catch a couple signals to do elegant cleanup. */ (void) signal (SIGHUP, terminate); (void) signal (SIGINT, terminate); (void) signal (SIGTERM, terminate); (void) signal (SIGPIPE, terminate); /* * If both an input and an output files were specified, then * fork and make child process the reader, parent the writer. */ if ( (io_mode == TEST_MODE) && input_file && output_file) { if ( (child_pid = fork()) == (pid_t) -1) { report_error ("fork", TRUE); exit (exit_status); } forked_flag = TRUE; if (child_pid) { /* Parent = writer. */ struct dinfo *dip = input_dinfo; (void) close_file (dip); input_file = NULL; if (debug_flag) { Printf ("Parent PID (Writer) = %d, Child PID (Reader) = %d\n", getpid(), child_pid); } #if !defined(__MSDOS__) || defined(__NUTC__) signal (SIGCHLD, terminate); #endif } else { /* Child = reader. */ struct dinfo *dip = output_dinfo; (void) close_file (dip); output_file = NULL; } } /* * Some drivers require the input device to open before we start * writing. For example, terminal devices must have speed, parity, * and flow control setup before we start writing. The parallel * input device must open before we send the "readya" interrupt. */ if (output_file && (sdelay_count != 0)) { mySleep (sdelay_count); /* Allow reader to start. */ } /* * Calculate size necessary for the data buffer & the pad bytes. */ data_size = (block_size + PADBUFR_SIZE); if (rotate_flag) data_size += ROTATE_SIZE; /* * For read-after-write (raw) option, we need a verification buffer. * Note: Other tests can benefit from this too, switch to later. */ if (raw_flag) { verify_buffer = myalloc(data_size, align_offset); } /* * Do the device / test specific initialization. * * This function is responsible for allocating the necessary * data buffers and performing special device setup/checking. */ if (input_file) { dip = input_dinfo; status = (*dip->di_funcs->tf_initialize)(dip); if (status == FAILURE) exit (FATAL_ERROR); } if (output_file) { dip = output_dinfo; status = (*dip->di_funcs->tf_initialize)(dip); if (status == FAILURE) exit (FATAL_ERROR); } /* * Re-adjust size of data buffer to avoid init'ing too much. */ if (rotate_flag) data_size -= ROTATE_SIZE; /* * Finally format the prefix string (if any), after the device is * setup and processes are forked so we can setup a unique prefix. */ if (prefix_string) { status = FmtPrefix(dip, &prefix_string, &prefix_size); if (status == FAILURE) exit (FATAL_ERROR); if ((prefix_size) > lbdata_size) { LogMsg (efp, logLevelCrit, 0, "The prefix size (%d) is larger than lbdata size (%d)!\n", prefix_size, lbdata_size); exit (FATAL_ERROR); } } /* * Start an alarm timer if the run time was specified. */ if (runtime) { (void) signal (SIGALRM, terminate); (void) alarm (runtime); TimerActive = TRUE; } /* * Start of main test loop. */ start_time = times (&stimes); #if defined(LOG_DIAG_INFO) if (logdiag_flag) { sprintf(msg_buffer, "Starting: %s", cmd_line); LogDiagMsg(msg_buffer); } #endif /* defined(LOG_DIAG_INFO) */ #if defined(DEC) if (table_flag) { status = table(TBL_SYSINFO,0,(char *)&s_table,1,sizeof(struct tbl_sysinfo)); if (status == FAILURE) report_error ("table", FALSE); } #endif /* defined(DEC) */ /* * We support multiple modes of operation: copy, test, & verify */ if ( (io_mode == COPY_MODE) || (io_mode == VERIFY_MODE) ) { /* * This is ugly, but I'm using pattern buffer as verify buffer. */ if ( (io_mode == VERIFY_MODE) || verify_flag) { pattern_buffer = myalloc (data_size, align_offset); setup_pattern (pattern_buffer, data_size); } pass_time = times (&ptimes); /* Start the pass timer */ #if defined(DEC) if (table_flag) { status = table(TBL_SYSINFO,0,(char *)&p_table,1,sizeof(struct tbl_sysinfo)); if (status == FAILURE) report_error ("table", FALSE); } #endif /* defined(DEC) */ dip = active_dinfo = input_dinfo; dtf = dip->di_funcs; dip->di_mode = READ_MODE; (void) (*dtf->tf_start_test)(dip); (void) (*dtf->tf_read_file)(dip); (void) (*dtf->tf_end_test)(dip); gather_stats(output_dinfo); /* * Now verify the data copied (if requested). */ if ( (io_mode == COPY_MODE) && verify_flag && !stdin_flag && (error_count < error_limit) ) { struct dinfo *odip = output_dinfo; int open_mode = (ropen_mode | open_flags); report_pass (dip, COPY_STATS); /* Report copy statistics. */ /* * Verify Pass. */ status = (*dtf->tf_reopen_file)(dip, open_mode); if (status != SUCCESS) terminate(status); odip->di_mode = READ_MODE; /* Switch to read mode. */ status = (*odip->di_funcs->tf_reopen_file)(odip, open_mode); if (status != SUCCESS) terminate(status); pass_time = times (&ptimes); /* Time the verify. */ #if defined(DEC) if (table_flag) { status = table(TBL_SYSINFO,0,(char *)&p_table,1,sizeof(struct tbl_sysinfo)); if (status == FAILURE) report_error ("table", FALSE); } #endif /* defined(DEC) */ io_mode = VERIFY_MODE; (void) (*dtf->tf_start_test)(dip); (void) (*dtf->tf_read_file)(dip); (void) (*dtf->tf_end_test)(dip); pass_count++; /* End of copy/verify pass. */ gather_stats(output_dinfo); /* Gather device statistics. */ report_pass (dip, VERIFY_STATS); /* Report the verify stats. */ } else { pass_count++; /* End of copy pass. */ } } else { /* not Copy or Verify modes, Test Mode! */ while ( (total_errors < error_limit) && ((pass_count < pass_limit) || runtime) ) { if (pattern_buffer) { pattern_bufptr = pattern_buffer; } /* * Use a different data pattern for each pass. */ if ( !user_pattern && !ttyport_flag && (loopback || output_file || stdin_flag || (input_file && num_slices)) ) { /* * Logic: * - If multiple slices, choose a different pattern * for each pass, factoring in the process number. * - Else, each pass gets a different data pattern * unless multiple processes were selected. */ if ( unique_pattern && ( (!num_procs && num_slices) || (num_procs && (dip->di_dtype->dt_dtype == DT_REGULAR)) ) ) { int pindex = ((cur_proc - 1) + pass_count); pattern = data_patterns[pindex % npatterns]; } else if (!num_procs) { pattern = data_patterns[pass_count % npatterns]; } if (pattern_buffer) copy_pattern (pattern, pattern_buffer); if (debug_flag) { Printf ("Using data pattern 0x%08x for pass %u\n", pattern, (pass_count + 1)); } } /* * Use time for random # generator seed so different areas * of disk get affected during multiple passes. Seed set * here, since it must be the same seed during read pass. * NOTE: Pid is added so seed different for each process. */ if ( (io_type == RANDOM_IO) || variable_flag) { if ( !user_rseed ) { random_seed = (u_int) times(&ptimes) + getpid(); } set_rseed (random_seed); } pass_time = times (&ptimes); /* Start the pass timer */ #if defined(DEC) if (table_flag) { status = table(TBL_SYSINFO,0,(char *)&p_table,1,sizeof(struct tbl_sysinfo)); if (status == FAILURE) report_error ("table", FALSE); } #endif /* defined(DEC) */ if (output_file) { /* Write/read the file. */ bool do_read_pass; dip = active_dinfo = output_dinfo; dtf = dip->di_funcs; dip->di_mode = WRITE_MODE; (void) (*dtf->tf_start_test)(dip); (void) (*dtf->tf_write_file)(dip); (void) (*dtf->tf_flush_data)(dip); (void) (*dtf->tf_end_test)(dip); if (error_count >= error_limit) break; do_read_pass = (dip->di_dbytes_written != (large_t) 0); /* * Now verify (read and data compare) the data just written. */ if (verify_flag && do_read_pass && !raw_flag) { /* Verify data written. */ int open_mode = (ropen_mode | open_flags); report_pass (dip, WRITE_STATS); /* Report write stats. */ if (multi_flag && media_changed) { status = RequestFirstVolume(dip, open_flags); } else { status = (*dtf->tf_reopen_file)(dip, open_mode); } if (status != SUCCESS) break; if ( (io_type == RANDOM_IO) || variable_flag) { set_rseed (random_seed); } pass_time = times (&ptimes); /* Time just the read. */ #if defined(DEC) if (table_flag) { status = table(TBL_SYSINFO,0,(char *)&p_table,1,sizeof(struct tbl_sysinfo)); if (status == FAILURE) report_error ("table", FALSE); } #endif /* defined(DEC) */ /*rotate_offset = 0;*/ if (pattern_buffer) { pattern_bufptr = pattern_buffer; } dip->di_mode = READ_MODE; (void) (*dtf->tf_start_test)(dip); (void) (*dtf->tf_read_file)(dip); (void) (*dtf->tf_end_test)(dip); pass_count++; /* End read/write pass. */ report_pass (dip, READ_STATS); /* Report read stats. */ } else { pass_count++; /* End of write pass. */ if ( (pass_limit > 1) || runtime) { /* Report write stats. */ if (raw_flag) { report_pass (dip, RAW_STATS); } else { report_pass (dip, WRITE_STATS); } } } if ( (pass_count < pass_limit) || runtime) { int open_mode; if (skip_count || raw_flag) { open_mode = (O_RDWR | wopen_flags | open_flags); } else { open_mode = (wopen_mode | wopen_flags | open_flags); } status = (*dtf->tf_reopen_file)(dip, open_mode); if (status != SUCCESS) break; } } else { /* Reading only. */ dip = active_dinfo = input_dinfo; dtf = dip->di_funcs; dip->di_mode = READ_MODE; (void) (*dtf->tf_start_test)(dip); (void) (*dtf->tf_read_file)(dip); (void) (*dtf->tf_end_test)(dip); pass_count++; /* End of read pass. */ /* * Prevent pass unless looping, since terminate reports * the total statistics when called (prevents dup stats). */ if ( (pass_limit > 1) || runtime) { report_pass (dip, READ_STATS); /* Report read stats. */ } if ( (total_errors < error_limit) && ((pass_count < pass_limit) || runtime) ) { int open_mode = (ropen_mode | open_flags); status = (*dtf->tf_reopen_file)(dip, open_mode); if (status != SUCCESS) break; } } #if defined(MUNSA) if (munsa_flag) { if (debug_flag) { Printf ("converting to dlm NL-> %d \n", DLM_NLMODE); } l_stat = dlm_cvt(&lkid, DLM_NLMODE, NULL, 0, 0, 0, NULL, 0); if (l_stat != DLM_SUCCESS) { Fprintf ("dlm_cvt failed\n"); dlm_error(&lkid, l_stat); /* exit with FATAL ERROR */ } } /* end if(munsa_flag)... */ #endif /* defined(MUNSA) */ } } /* end 'if ( (io_mode == COPY_MODE) || (io_mode == VERIFY_MODE) ) ' */ #if defined(MUNSA) if (munsa_flag) { if ((l_stat = dlm_unlock(&lkid, NULL, 0)) != DLM_SUCCESS) dlm_error(&lkid, l_stat); /* exit with FATAL ERROR */ if (debug_flag) { Printf("\n %s: unlocked...\n", resnam); } } /* end if(munsa_flag)... */ #endif /* defined(MUNSA) */ terminate (exit_status); /* Terminate with exit status. */ /*NOTREACHED*/ return (exit_status); /* Quiet certain compilers! */ } #if defined(MUNSA) /************************************************************************ * * * dlm_error() - Print all MUNSA dlm related error msg. * * * * Inputs: dlm_lkid_t *lk * * dlm_status_t stat * * * * If errors are detected, we simply exit with a fatal error. * * * ************************************************************************/ void dlm_error(dlm_lkid_t *lk, dlm_status_t err_stat) { dlm_rsbinfo_t rsb; /* used by dlm_sperrno() */ LogMsg (efp, logLevelCrit, 0, "lock error %s on lkid 0x%lx\n", dlm_sperrno(err_stat), *lk); exit (FATAL_ERROR); } #endif /* defined(MUNSA) */ /************************************************************************ * * * parse_args() - Parse 'dt' Program Arguments. * * * * Inputs: argc = The number of arguments. * * argv = Array pointer to arguments. * * * * If errors are detected, we simply exit with a fatal error. * * * ************************************************************************/ void parse_args (int argc, char **argv) { int i; if (argc == 1) dtusage(); for (i = 1; i < argc; i++) { string = argv[i]; if (match ("aios=")) { aio_bufs = (int)number(ANY_RADIX); if (aio_bufs) aio_flag = TRUE; continue; } if (match ("align=")) { if (match ("rotate")) { rotate_flag = TRUE; continue; } align_offset = (u_int) number(ANY_RADIX); continue; } if (match ("bs=")) { block_size = number(ANY_RADIX); if ((ssize_t)block_size <= (ssize_t) 0) { LogMsg (efp, logLevelCrit, 0, "block size must be positive and non-zero.\n"); exit (FATAL_ERROR); } continue; } if (match ("capacity=")) { if (match ("max")) { max_capacity = TRUE; } else { user_capacity = large_number(ANY_RADIX); } continue; } if (match ("dsize=")) { device_size = number(ANY_RADIX); continue; } if (match ("lba=")) { lbdata_flag = TRUE; lbdata_addr = number(ANY_RADIX); user_lbdata = TRUE; continue; } if (match ("lbs=")) { lbdata_flag = TRUE; lbdata_size = number(ANY_RADIX); user_lbsize = TRUE; if ((ssize_t)lbdata_size <= (ssize_t) 0) { LogMsg (efp, logLevelCrit, 0, "lbdata size must be positive and non-zero.\n"); exit (FATAL_ERROR); } continue; } if ( (match ("count=")) || (match ("records=")) ) { record_limit = number(ANY_RADIX); continue; } if (match ("cdelay=")) { cdelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("edelay=")) { edelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("rdelay=")) { rdelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("sdelay=")) { sdelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("tdelay=")) { tdelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("wdelay=")) { wdelay_count = (u_int) number(ANY_RADIX); continue; } if (match ("errors=")) { error_limit = number(ANY_RADIX); continue; } if (match ("files=")) { file_limit = number(ANY_RADIX); continue; } if (match ("hz=")) { hz = (u_int) number(ANY_RADIX); continue; } if (match ("incr=")) { user_incr = TRUE; if (match ("var")) { variable_flag = TRUE; } else { incr_count = number(ANY_RADIX); } continue; } if (match ("dlimit=")) { dump_limit = number(ANY_RADIX); continue; } if (match ("limit=")) { data_limit = large_number(ANY_RADIX); if (!record_limit) { record_limit = INFINITY; } continue; } if (match ("ralign=")) { io_type = RANDOM_IO; random_align = number(ANY_RADIX); continue; } if (match ("rlimit=")) { io_type = RANDOM_IO; rdata_limit = large_number(ANY_RADIX); continue; } if (match ("max=")) { user_max = TRUE; max_size = (size_t) number(ANY_RADIX); continue; } if (match ("min=")) { user_min = TRUE; min_size = (size_t) number(ANY_RADIX); continue; } if (match ("enable=")) { eloop: if (match(",")) goto eloop; if (*string == '\0') continue; if (match("aio")) { aio_flag = TRUE; goto eloop; } if (match("bypass")) { bypass_flag = TRUE; goto eloop; } if (match("cerrors")) { cerrors_flag = TRUE; goto eloop; } if (match("compare")) { compare_flag = TRUE; goto eloop; } if (match("coredump")) { core_dump = TRUE; goto eloop; } if (match("debug")) { debug_flag = TRUE; goto eloop; } if (match("Debug")) { Debug_flag = debug_flag = TRUE; goto eloop; } if (match("edebug")) { eDebugFlag = TRUE; goto eloop; } if (match("rdebug")) { rDebugFlag = TRUE; goto eloop; } if (match("diag")) { logdiag_flag = TRUE; goto eloop; } if (match("dump")) { dump_flag = TRUE; goto eloop; } if (match("eof")) { eof_status = TRUE; goto eloop; } #if defined(EEI) if (match("eei")) { eei_flag = TRUE; goto eloop; } if (match("resets")) { eei_resets = TRUE; goto eloop; } #endif /* defined(EEI) */ if (match("flush")) { flush_flag = TRUE; goto eloop; } if (match("fsync")) { fsync_flag = TRUE; goto eloop; } if (match("fsalign")) { fsalign_flag = TRUE; goto eloop; } if (match("header")) { header_flag = TRUE; goto eloop; } if (match("lbdata")) { lbdata_flag = TRUE; goto eloop; } if (match("loopback")) { loopback = TRUE; verify_flag = FALSE; goto eloop; } if (match("microdelay")) { micro_flag = TRUE; goto eloop; } #if defined(MMAP) if (match("mmap")) { mmap_flag = TRUE; wopen_mode = O_RDWR; /* MUST open read/write. */ goto eloop; } #endif /* defined(MMAP) */ #if defined(TTY) if (match("modem")) { open_flags = O_NONBLOCK; modem_flag = TRUE; goto eloop; } #endif /* defined(TTY) */ if (match("multi")) { multi_flag = TRUE; goto eloop; } if (match("pstats")) { pstats_flag = TRUE; goto eloop; } if (match("raw")) { raw_flag = TRUE; goto eloop; } if (match("hazard")) { efp = ofp; /* Write errors to stdout. */ hazard_flag = TRUE; goto eloop; } if (match("spad")) { spad_check = TRUE; goto eloop; } #if defined(sun) && defined(TTY) if (match("softcar")) { open_flags = O_NDELAY; softcar_opt = ON; goto eloop; } #endif /* defined(sun) && defined(TTY) */ #if defined(DEC) if (match("table")) { table_flag = TRUE; goto eloop; } #endif /* defined(DEC) */ #if defined(TTY) if (match("ttyport")) { ttyport_flag = TRUE; goto eloop; } #endif /* defined(TTY) */ if (match("unique")) { unique_pattern = TRUE; goto eloop; } if (match("verbose")) { verbose_flag = TRUE; goto eloop; } if (match("verify")) { verify_flag = TRUE; goto eloop; } LogMsg (efp, logLevelCrit, 0, "Invalid enable keyword: %s\n", string); exit (FATAL_ERROR); } if (match ("disable=")) { dloop: if (match(",")) goto dloop; if (*string == '\0') continue; if (match("aio")) { aio_flag = FALSE; goto dloop; } if (match("bypass")) { bypass_flag = FALSE; goto dloop; } if (match("cerrors")) { cerrors_flag = FALSE; goto dloop; } if (match("compare")) { compare_flag = FALSE; goto dloop; } if (match("diag")) { logdiag_flag = FALSE; goto dloop; } if (match("dump")) { dump_flag = TRUE; goto dloop; } if (match("eof")) { eof_status = FALSE; goto dloop; } #if defined(EEI) if (match("eei")) { eei_flag = FALSE; goto dloop; } if (match("resets")) { eei_resets = FALSE; goto dloop; } #endif /* defined(EEI) */ if (match("flush")) { flush_flag = FALSE; goto dloop; } if (match("fsync")) { fsync_flag = FALSE; goto dloop; } if (match("fsalign")) { fsalign_flag = FALSE; goto dloop; } if (match("header")) { header_flag = FALSE; goto dloop; } if (match("lbdata")) { lbdata_flag = FALSE; user_lbdata = FALSE; goto dloop; } if (match("loopback")) { loopback = FALSE; goto dloop; } if (match("microdelay")) { micro_flag = FALSE; goto dloop; } #if defined(MMAP) if (match("mmap")) { mmap_flag = FALSE; goto dloop; } #endif /* defined(MMAP) */ #if defined(TTY) if (match("modem")) { open_flags &= ~(O_NONBLOCK); modem_flag = FALSE; goto dloop; } #endif /* defined(TTY) */ if (match("pad")) { pad_check = FALSE; goto dloop; } if (match("pstats")) { pstats_flag = FALSE; goto dloop; } if (match("raw")) { raw_flag = FALSE; goto dloop; } if (match("hazard")) { hazard_flag = FALSE; goto dloop; } if (match("spad")) { spad_check = FALSE; goto dloop; } #if defined(TTY) if (match("softcar")) { softcar_opt = OFF; goto dloop; } #endif /* defined(TTY) */ if (match("stats")) { stats_flag = FALSE; goto dloop; } #if defined(DEC) if (match("table")) { table_flag = FALSE; goto dloop; } #endif /* defined(DEC) */ if (match("unique")) { unique_pattern = FALSE; goto dloop; } if (match("verbose")) { verbose_flag = FALSE; goto dloop; } if (match("verify")) { verify_flag = FALSE; goto dloop; } LogMsg (efp, logLevelCrit, 0, "Invalid disable keyword: %s\n", string); exit (FATAL_ERROR); } if (match ("dispose=")) { if (match("delete")) { keep_existing = FALSE; dispose_mode = DELETE_FILE; } else if (match("keep")) { keep_existing = TRUE; dispose_mode = KEEP_FILE; } else { LogMsg (efp, logLevelCrit, 0, "Dispose modes are 'delete' or 'keep'.\n", string); exit (FATAL_ERROR); } continue; } #if defined(MUNSA) if (match ("munsa=")) { munsa_flag = TRUE; flow_str = string; if (match("cr")) { munsa_lock_type = DLM_CRMODE; /* Concurrent Read */ } else if (match("pr")) { munsa_lock_type = DLM_PRMODE; /* Protected Read */ } else if (match("cw")) { munsa_lock_type = DLM_CWMODE; /* Concurrent Write */ } else if (match("pw")) { munsa_lock_type = DLM_PWMODE; /* Protected Write */ } else if (match("ex")) { munsa_lock_type = DLM_EXMODE; /* EXclusive mode */ } else { LogMsg (efp, logLevelCrit, 0, "Munsa lock types are 'cr', 'pr','cw', 'pw', or 'ex'.\n"); exit (FATAL_ERROR); } continue; } #endif /* defined(MUNSA) */ #if defined(TTY) if (match ("flow=")) { flow_str = string; if (match("none")) { flow_type = FLOW_NONE; /* No flow control. */ } else if (match("cts_rts")) { flow_type = CTS_RTS; /* CTS/RTS flow control. */ } else if (match("xon_xoff")) { flow_type = XON_XOFF; /* XON/XOFF flow control. */ } else { LogMsg (efp, logLevelCrit, 0, "Flow types are 'none', 'cts_rts', or 'xon_xoff'.\n"); exit (FATAL_ERROR); } continue; } #endif /* defined(TTY) */ if (match ("if=")) { input_file = string; continue; } if (match ("of=")) { output_file = string; continue; } if (match ("pf=")) { pattern_file = string; user_pattern = TRUE; continue; } if (match ("log=")) { log_file = string; continue; } if (match ("iodir=")) { if (match ("for")) { io_dir = FORWARD; } else if (match ("rev")) { io_dir = REVERSE; } else { LogMsg (efp, logLevelCrit, 0, "Valid I/O directions are: 'forward' or 'reverse'.\n"); exit (FATAL_ERROR); } continue; } if (match ("iomode=")) { if (match ("copy")) { dispose_mode = KEEP_FILE; io_mode = COPY_MODE; } else if (match ("test")) { io_mode = TEST_MODE; } else if (match ("verify")) { io_mode = VERIFY_MODE; verify_only = TRUE; } else { LogMsg (efp, logLevelCrit, 0, "Valid I/O modes are: 'copy', 'test', or verify.\n"); exit (FATAL_ERROR); } continue; } if (match ("iotype=")) { if (match ("random")) { io_type = RANDOM_IO; } else if (match ("sequential")) { io_type = SEQUENTIAL_IO; } else { LogMsg (efp, logLevelCrit, 0, "Valid I/O types are: 'random' or 'sequential'.\n"); exit (FATAL_ERROR); } continue; } /* * Flags which apply to read and write of a file. * * NOTE: I'm not sure all of flags applying to write only! */ if (match ("flags=")) { floop: if (match(",")) goto floop; if (*string == '\0') continue; #if defined(O_EXCL) if (match("excl")) { open_flags |= O_EXCL; /* Exclusive open. */ goto floop; } #endif /* defined(O_EXCL) */ #if defined(O_NDELAY) if (match("ndelay")) { open_flags |= O_NDELAY; /* Non-delay open. */ goto floop; } #endif /* defined(O_NDELAY) */ #if defined(O_NONBLOCK) if (match("nonblock")) { open_flags |= O_NONBLOCK; /* Non-blocking open. */ goto floop; } #endif /* defined(O_NONBLOCK) */ #if defined(O_CACHE) if (match("cache")) { /* QNX specific. */ open_flags |= O_CACHE; /* Keep data in cache. */ goto floop; } #endif /* defined(O_CACHE) */ #if defined(O_DIRECT) if (match("direct")) { /* LINUX specific. */ open_flags |= O_DIRECT; /* Direct disk access. */ goto floop; } #endif /* defined(O_DIRECT) */ #if defined(O_DIRECTIO) if (match("direct")) { /* Tru64 Unix (zulu). */ open_flags |= O_DIRECTIO; /* Directio disk access. */ goto floop; } #endif /* defined(O_DIRECTIO) */ #if defined(O_FSYNC) if (match("fsync")) { /* File integrity. */ open_flags |= O_FSYNC; /* Syncronize file I/O. */ goto floop; } #endif /* defined(O_FSYNC) */ #if defined(O_RSYNC) if (match("rsync")) { open_flags |= O_RSYNC; /* Read I/O integrity. */ goto floop; /* Use with O_DSYNC or O_SYNC. */ } #endif /* defined(O_RSYNC) */ #if defined(O_SYNC) if (match("sync")) { open_flags |= O_SYNC; /* Synchronous all data access. */ goto floop; /* Sync data & file attributes. */ } #endif /* defined(O_SYNC) */ #if defined(O_LARGEFILE) if (match("large")) { open_flags |= O_LARGEFILE; /* Enable large file support. */ goto floop; /* Same as _FILE_OFFSET_BITS=64 */ } #endif /* defined(O_LARGEFILE) */ } /* End if "flags=" option. */ /* * Flags which apply to opening a file for writes. */ if (match ("oflags=")) { oloop: if (match(",")) goto oloop; if (*string == '\0') continue; #if defined(O_APPEND) if (match("append")) { wopen_flags |= O_APPEND; /* Append to file. */ goto oloop; } #endif /* defined(O_APPEND) */ #if defined(O_DEFER) if (match("defer")) { wopen_flags |= O_DEFER; /* Defer updates. */ goto oloop; } #endif /* defined(O_DEFER) */ #if defined(O_DSYNC) if (match("dsync")) { /* Write data integrity. */ wopen_flags |= O_DSYNC; /* Synchronize data written. */ goto oloop; } #endif /* defined(O_DSYNC) */ #if defined(O_SYNC) if (match("sync")) { wopen_flags |= O_SYNC; /* Synchronous all data access. */ goto oloop; /* Sync data & file attributes. */ } #endif /* defined(O_SYNC) */ #if defined(O_TRUNC) if (match("trunc")) { wopen_flags |= O_TRUNC; /* Truncate output file. */ goto floop; } #endif /* defined(O_TRUNC) */ #if defined(O_TEMP) if (match("temp")) { wopen_flags |= O_TEMP; /* Temporary file. */ goto oloop; } #endif /* defined(O_TEMP) */ } /* End of "oflags=" option. */ #if defined(TTY) if (match ("parity=")) { parity_str = string; if (match("even")) { parity_code = PARENB; data_bits_code = CS7; } else if (match("odd")) { parity_code = (PARENB | PARODD); data_bits_code = CS7; } else if (match("none")) { parity_code = 0; data_bits_code = CS8; #if defined(_QNX_SOURCE) } else if (match("mark")) { parity_code = (PARENB | PARODD | PARSTK); data_bits_code = CS7; } else if (match("space")) { parity_code = (PARENB | PARSTK); data_bits_code = CS7; #endif /* defined(_QNX_SOURCE) */ } else { #if defined(_QNX_SOURCE) LogMsg (efp, logLevelCrit, 0, "Valid parity settings are: even, odd, mark, space, or none.\n"); #else /* !defined(_QNX_SOURCE) */ LogMsg (efp, logLevelCrit, 0, "Valid parity settings are: even, odd, or none.\n"); #endif /* defined(_QNX_SOURCE) */ exit (FATAL_ERROR); } if (data_bits_code == CS7) { if (pattern == (u_int32)DEFAULT_PATTERN) { pattern = (u_int32)ASCII_PATTERN; } else { pattern &= (u_int32)0x77777777; /* Strip off parity bits. */ } } continue; } #endif /* defined(TTY) */ if (match ("oncerr=")) { if (match("abort")) { oncerr_action = ABORT; } else if (match("continue")) { oncerr_action = CONTINUE; } else { LogMsg (efp, logLevelCrit, 0, "On error actions are 'abort' or 'continue'.\n"); exit (FATAL_ERROR); } continue; } if (match ("passes=")) { pass_limit = number(ANY_RADIX); if (pass_limit == 0) { LogMsg (efp, logLevelCrit, 0, "Please specify a pass limit greater than zero!\n"); exit (FATAL_ERROR); } continue; } if (match ("pattern=")) { /* TODO: Needs to be cleaned up. */ int size = strlen(string); if (size == 0) { LogMsg (efp, logLevelCrit, 0, "Please specify pattern of: { hex-pattern | incr | string }\n"); exit (FATAL_ERROR); } user_pattern = TRUE; if (match ("incr")) { /* Incrementing pattern. */ int v, size = 256; u_char *buffer = (u_char *) myalloc (size, 0); u_char *bp = buffer; for (v = 0; v < size; v++) { *bp++ = v; } incr_pattern = TRUE; setup_pattern (buffer, size); } else if ( (size == 3) && match("iot") ) { iot_pattern = TRUE; /* Allocate pattern buffer after parsing. */ } else if ( IS_HexString(string) && (size <= 10) ) { /* valid strings: XXXXXXXX or 0xXXXXXXXX */ pattern = (u_int32)number(HEX_RADIX); } else { /* Presume ASCII string for data pattern. */ u_char *buffer = (u_char *) myalloc (size, 0); size = StrCopy (buffer, string, size); pattern_size = size; pattern_string = string; setup_pattern (buffer, size); } continue; } if (match ("prefix=")) { prefix_size = strlen(string); if (prefix_size == 0) { LogMsg (efp, logLevelCrit, 0, "Please specify a non-empty prefix string!\n"); exit (FATAL_ERROR); } else if (prefix_size > BLOCK_SIZE) { LogMsg (efp, logLevelCrit, 0, "Please specify a prefix string < %d bytes!\n", prefix_size); exit (FATAL_ERROR); } prefix_string = Malloc(++prefix_size); /* plus NULL! */ (void)strcpy(prefix_string, string); continue; } if (match ("position=")) { file_position = (off_t)large_number(ANY_RADIX); user_position = TRUE; continue; } if (match ("procs=")) { num_procs = (u_short)number(ANY_RADIX); if (num_procs > MAX_PROCS) { LogMsg (efp, logLevelCrit, 0, "Please limit procs to <= %d!\n", MAX_PROCS); exit (FATAL_ERROR); } continue; } #if defined(HP_UX) if (match ("qdepth=")) { qdepth = (u_int)number(ANY_RADIX); if (qdepth > SCSI_MAX_Q_DEPTH) { LogMsg (efp, logLevelCrit, 0, "Please specify a SCSI queue depth <= %d!\n", SCSI_MAX_Q_DEPTH); exit (FATAL_ERROR); } continue; } #endif /* defined(HP_UX) */ if (match ("rseed=")) { random_seed = (u_int)number(ANY_RADIX); user_rseed = TRUE; continue; } if (match ("runtime=")) { user_runtime = string; runtime = time_value(); if (!record_limit) { record_limit = INFINITY; } continue; } if (match ("seek=")) { seek_count = number(ANY_RADIX); continue; } if (match ("skip=")) { skip_count = number(ANY_RADIX); continue; } if (match ("slice=")) { slice_num = (u_short)number(ANY_RADIX); if ( (slice_num < 1) || (slice_num > MAX_SLICES) ) { LogMsg (efp, logLevelCrit, 0, "Please limit slice to (1 - %d)!\n", MAX_SLICES); exit (FATAL_ERROR); } continue; } if (match ("slices=")) { num_slices = (u_short)number(ANY_RADIX); if (num_slices > MAX_SLICES) { LogMsg (efp, logLevelCrit, 0, "Please limit maximum slices to <= %d!\n", MAX_SLICES); exit (FATAL_ERROR); } continue; } if (match ("step=")) { step_offset = (off_t)large_number(ANY_RADIX); continue; } #if defined(TTY) if (match ("speed=")) { speed_str = string; baud_rate = (u_int32) number(ANY_RADIX); if (setup_baud_rate (baud_rate) == FAILURE) { exit (FATAL_ERROR); } continue; } if (match ("timeout=")) { tty_timeout = (u_short)number(ANY_RADIX); continue; } if (match ("ttymin=")) { tty_minflag = TRUE; tty_minimum = (u_short)number(ANY_RADIX); continue; } #endif /* defined(TTY) */ if (match ("dtype=")) { struct dtype *dtp; if ((dtp = setup_device_type (string)) == NULL) { exit (FATAL_ERROR); } input_dtype = output_dtype = dtp; continue; } if (match ("idtype=")) { if ((input_dtype = setup_device_type (string)) == NULL) { exit (FATAL_ERROR); } continue; } if (match ("odtype=")) { if ((output_dtype = setup_device_type (string)) == NULL) { exit (FATAL_ERROR); } continue; } if (match ("trigger=")) { if ((trigger = check_trigger_type (string)) == TRIGGER_INVALID) { exit (FATAL_ERROR); } continue; } if (match ("vrecords=")) { volume_records = number(ANY_RADIX); continue; } if (match ("volumes=")) { multi_flag = TRUE; volumes_flag = TRUE; volume_limit = number(ANY_RADIX); continue; } if (match ("help")) { dthelp(); } if (match ("version")) { dtversion(); } LogMsg (efp, logLevelCrit, 0, "Invalid option '%s' specified, use 'help' for valid options.\n", string); exit (FATAL_ERROR); } #if !defined(AIO) if (aio_flag) { Printf ("Warning, POSIX AIO is NOT supported on this platform, disabling AIO!\n"); aio_flag = FALSE; } #endif /* !defined(AIO) */ } /************************************************************************ * * * match() Match string against next input argument. * * * * Inputs: s = Pointer to string to match against. * * * * Outputs: Returns TRUE/FALSE = Match/No Match * * string = Updated input argument pointer. * * * ************************************************************************/ int match (char *s) { char *cs; cs = string; while (*cs++ == *s) { if (*s++ == '\0') { goto true; /* I didn't write this junk. */ } } if (*s != '\0') { return (FALSE); } true: cs--; string = cs; return (TRUE); } /************************************************************************ * * * number() Converts ASCII string into numeric value. * * * * Inputs: base = The base for numeric conversions. * * * * Outputs: Returns converted number, exits on invalid numbers. * * * ************************************************************************/ static u_long number (int base) { char *eptr, *str = string; u_long value; value = CvtStrtoValue (str, &eptr, base); if (*eptr != '\0') { LogMsg (efp, logLevelCrit, 0, "Invalid character detected in number: '%c'\n", *eptr); exit (FATAL_ERROR); } return (value); } static large_t large_number(int base) { char *eptr, *str = string; large_t value; value = CvtStrtoLarge (str, &eptr, base); if (*eptr != '\0') { LogMsg (efp, logLevelCrit, 0, "Invalid character detected in number: '%c'\n", *eptr); exit (FATAL_ERROR); } return (value); } static time_t time_value(void) { char *eptr, *str = string; time_t value; value = CvtTimetoValue (str, &eptr); if (*eptr != '\0') { LogMsg (efp, logLevelCrit, 0, "Invalid character detected in time string: '%c'\n", *eptr); exit (FATAL_ERROR); } return (value); } /************************************************************************ * * * report_error() - Report system error information. * * * * Inputs: error_info = Additional error info for perror. * * record_flag = Controls reporting error/time info. * * * ************************************************************************/ void report_error( char *error_info, int record_flag) { struct dinfo *dip = active_dinfo; int saved_errno = errno; if (dip) dip->di_errno = errno; #if defined(LOG_DIAG_INFO) if (logdiag_flag) { (void)sprintf(msg_buffer, "%s: '%s', errno = %d - %s\n", cmdname, error_info, errno, strerror(errno)); LogDiagMsg(msg_buffer); } #endif /* defined(LOG_DIAG_INFO) */ LogMsg (efp, logLevelCrit, 0, "'%s', errno = %d - %s\n", error_info, errno, strerror(errno)); exit_status = FAILURE; if (record_flag) { char fmt[SMALL_BUFFER_SIZE]; error_time = time((time_t *) 0); /* Two flavors of ctime_r(), this is a problem! */ if (ctime_r(&error_time, fmt) == NULL) { //perror("ctime_r()"); /* wrong ctime_r()? */ Fprintf("Error number %lu occurred on %s", ++error_count, ctime(&error_time)); } else { /* ctime_r() adds a newline '\n' to time stamp. */ Fprintf("Error number %lu occurred on %s", ++error_count, fmt); } } errno = saved_errno; } /* * General purpose display record information function. */ void report_record( struct dinfo *dip, u_long files, u_long records, u_int32 lba, enum test_mode mode, void *buffer, size_t bytes ) { char msg[STRING_BUFFER_SIZE]; char *bp = msg; if (dip->di_dtype->dt_dtype == DT_TAPE) { bp += sprintf(bp, "File #%lu, ", files); } bp += sprintf(bp, "Record #%lu", records); #if 0 if (lbdata_flag || iot_pattern || aio_flag) { #else if (lba != NO_LBA) { #endif bp += sprintf(bp, " (lba %u), ", lba); } else { bp += sprintf(bp, ", "); } bp += sprintf(bp, "%s %ld bytes %s buffer %#lx...\n", (mode == READ_MODE) ? "Reading" : "Writing", (long)bytes, (mode == READ_MODE) ? "into" : "from", (u_long)buffer); Printf (msg); } /* * terminate() - Terminate program with specified exit code. * * Inputs: * code = The exit code or signal number if kill done. */ void terminate (int code) { struct dinfo *dip = active_dinfo; int status; /* * If we enter here more than once, just exit to avoid * possible recursion. kernel should do I/O rundown :-) */ if (terminating_flag) { if (core_dump && (code != SUCCESS)) { abort(); /* Generate a core dump. */ } else { exit (code); /* get outta here now... */ } } else { terminating_flag++; /* Show we're terminating. */ } /* * If we're here due to our timer expiring, and we were writing * a file, then flush the data (to regular file). */ if ( (code == SIGALRM) && dip && (dip->di_mode == WRITE_MODE) ) { /* * We may get alarm while open() is in progress, so handle. */ if ( !dip->di_closing && (dip->di_fd != NoFd) ) { (void) (*dip->di_funcs->tf_flush_data)(dip); } } /* * If an alarm timer is active, cancel it and calculate * the elapsed run time. */ if (TimerActive) { elapsed_time = runtime - alarm(0); TimerActive = FALSE; code = exit_status; /* Set the exit status! */ } /* * We only come here for signals when executing multiple * processes, so abort active procs and continue waiting. */ if ((num_procs || num_slices) && child_pid) { abort_procs(); /* Abort any active procs. */ return; } #if !defined(__MSDOS__) || defined(__NUTC__) if (debug_flag && (code == SIGCHLD)) { Printf ("Child process exited prematurely, parent exiting...\n"); } else #endif /* * Report stats before waiting for the child process. */ #if !defined(__MSDOS__) || defined(__NUTC__) (void) signal (SIGCHLD, SIG_DFL); /* Don't deliver SIGCHLD now. */ #endif /* * Close file, which for AIO waits for outstanding I/O's, * before reporting statistics so they'll be correct. */ if (dip) { dip->di_proc_eei = FALSE; status = (*dip->di_funcs->tf_close)(dip); if (status != SUCCESS) code = status; } gather_stats(dip); /* Gather the device statistics. */ gather_totals(); /* Update the total statistics. */ report_stats(dip, TOTAL_STATS); if (child_pid) { /* Always wait for child status. */ pid_t wpid = (pid_t) 0; /* * If exiting with error, ensure the child gets killed. */ if (code != SUCCESS) { (void) kill (child_pid, SIGTERM); } if (debug_flag) { Printf ("Waiting for child PID %d to exit...\n", child_pid); } #if defined(_BSD) do { if ((wpid = wait (&child_status)) == FAILURE) { report_error ("wait", TRUE); break; } } while (wpid != child_pid); if ( (code == SUCCESS) && (wpid != FAILURE) ) { code = WEXITSTATUS (child_status); } #else /* !defined(_BSD) */ if ((wpid = waitpid (child_pid, &child_status, 0)) == FAILURE) { report_error ("waitpid", TRUE); } else if (code == SUCCESS) { code = WEXITSTATUS (child_status); } #endif /* defined(_BSD) */ if (debug_flag && (wpid != FAILURE)) { Printf("Child PID %d, exited with status = %d\n", wpid, WEXITSTATUS(child_status)); } } /* * Delete the output file, if requested to do so. */ if (output_file && (io_mode == TEST_MODE) && (dispose_mode == DELETE_FILE) && (dip && dip->di_dtype && (dip->di_dtype->dt_dtype == DT_REGULAR)) ) { (void) delete_file (dip); } #if defined(LOG_DIAG_INFO) if (logdiag_flag) { sprintf(msg_buffer, "Finished: %s", cmd_line); LogDiagMsg(msg_buffer); } #endif /* defined(LOG_DIAG_INFO) */ if (!eof_status && (code == END_OF_FILE)) { code = SUCCESS; /* Map end-of-file status to Success! */ } if (debug_flag && (code != SUCCESS)) { Printf ("Exiting with status code %d...\n", code); } if (core_dump && (code != SUCCESS) && (code != END_OF_FILE)) { abort(); /* Generate a core dump. */ } if (forked_flag && tdelay_count && (child_pid == (pid_t) 0)) { (void)sleep (tdelay_count); /* Allow parent to wait for us. */ } exit (code); } int nofunc (struct dinfo *dip) { return (SUCCESS); } static char *multi_prompt = "\nPlease insert volume #%d in drive %s, press ENTER when ready to proceed: \007"; static char *multi_nready = "The drive is NOT ready or encountered an error, Retry operation (Yes): \007"; int HandleMultiVolume (struct dinfo *dip) { int status; status = RequestMultiVolume (dip, FALSE, dip->di_oflags); if (status == FAILURE) return (status); if (dip->di_mode == READ_MODE) { dip->di_volume_bytes = (large_t)(dip->di_dbytes_read + total_bytes_read); if (verbose_flag) { if (dip->di_dtype->dt_dtype == DT_TAPE) { Print (" [ Continuing in file #%lu, record #%lu, bytes read so far " LUF "... ]\n", (dip->di_files_read + 1), (dip->di_records_read + 1), dip->di_volume_bytes); } else { Print (" [ Continuing at record #%lu, bytes read so far " LUF "... ]\n", (dip->di_records_read + 1), dip->di_volume_bytes); } } dip->di_vbytes_read = (v_large) 0; } else { dip->di_volume_bytes = (large_t)(dip->di_dbytes_written + total_bytes_written); if (verbose_flag) { if (dip->di_dtype->dt_dtype == DT_TAPE) { Print (" [ Continuing in file #%lu, record #%lu, bytes written so far " LUF "... ]\n", (dip->di_files_written + 1), (dip->di_records_written + 1), dip->di_volume_bytes); } else { Print (" [ Continuing at record #%lu, bytes written so far " LUF "... ]\n", (dip->di_records_written + 1), dip->di_volume_bytes); } } dip->di_vbytes_written = (v_large) 0; } (void)fflush(ofp); media_changed = TRUE; dip->di_volume_records = 0; if (exit_status == END_OF_FILE) { exit_status = SUCCESS; /* Ensure END_OF_FILE status is reset! */ } return (status); } int RequestFirstVolume (struct dinfo *dip, int oflags) { int status; multi_volume = 0; status = RequestMultiVolume (dip, TRUE, oflags); dip->di_volume_bytes = (large_t) 0; dip->di_volume_records = 0; return (status); } int RequestMultiVolume (struct dinfo *dip, bool reopen, int oflags) { struct dtfuncs *dtf = dip->di_funcs; char buffer[256]; char *bp = buffer; FILE *fp; int saved_exit_status; u_long saved_error_count; int status; if (terminating_flag) return (FAILURE); if ( (status = (*dtf->tf_close)(dip)) == FAILURE) { return (status); } if ( (fp = fopen("/dev/tty", "r+")) == NULL) { report_error("open of /dev/tty failed", FALSE); return (FAILURE); } multi_volume++; (void)sprintf(bp, multi_prompt, multi_volume, dip->di_dname); (void) fputs (bp, fp); fflush(fp); if (fgets (bp, sizeof(buffer), fp) == NULL) { Print ("\n"); status = FAILURE; /* eof or an error */ return (status); } saved_error_count = error_count; saved_exit_status = exit_status; /* * This is an important step, so allow the user to retry on errors. */ do { if (!reopen) { status = (*dtf->tf_open)(dip, oflags); } else { status = (*dtf->tf_reopen_file)(dip, oflags); } if (status == SUCCESS) { #if !defined(__NUTC__) && !defined(__QNXNTO__) && !defined(AIX) if (dip->di_dtype->dt_dtype == DT_TAPE) { status = DoRewindTape (dip); if (status == FAILURE) { (void)(*dtf->tf_close)(dip); } } #endif /* !defined(__NUTC__) && !defined(__QNX_NTO__) && !defined(AIX) */ } if (status == FAILURE) { (void) fputs (multi_nready, fp); fflush(fp); if (fgets (bp, sizeof(buffer), fp) == NULL) { Print ("\n"); break; } if ( (bp[0] == 'N') || (bp[0] == 'n') ) { break; } error_count = saved_error_count; exit_status = saved_exit_status; } else { break; /* device is ready! */ } } while (status == FAILURE); (void)fclose(fp); return (status); }