/****************************************************************************
 *									    *
 *			  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:	dtwrite.c
 * Author:	Robin T. Miller
 *
 * Description:
 *	Write routines for generic data test program.
 */
#include "dt.h"
#if !defined(_QNX_SOURCE)
#  include <sys/file.h>
#endif /* !defined(_QNX_SOURCE) */

/*
 * Modification History:
 *
 * October 21st, 2004 by Robin Miller.
 *      For variable record lengths, ensure we prime the first size
 * to ensure it meets device size alignment requirements.
 *
 * June 23rd, 2004 by Robin Miller.
 *      Added support for triggers on corruption.
 *
 * February 13th, 2004 by Robin Miller.
 *      Factor in the file position when doing reverse I/O, to avoid
 * writing into that area (which is now deemed protected).
 *
 * 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.
 *
 * September 27th, 2003 by Robin Miller.
 *      Added support for AIX.
 *
 * June 25th, 2001 by Robin Miller.
 *	Restructured code associated with Tru64 Unix EEI, so we obtain
 * the EEI status for all tape errors, not just EIO errors.
 *
 * February 24th, 2001 by Robin Miller.
 *	Add conditionalization for QNX RTP (Neutrino).
 *
 * January 26th, 2001 by Robin Miller.
 *	Added support for reverse writing.
 *
 * January 24th, 2001 by Robin Miller.
 *	Add support for variable I/O requests sizes.
 *
 * 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.
 *
 * March 28th, 2000 by Robin Miller.
 *	Modified calls to file position functions which now accept a
 * device information parameter.
 *
 * February 18th, 2000 by Robin Miller.
 *	Fix a problem where the records written value was not updated
 * when the data limit was reached first.
 *
 * January 22nd, 2000 by Robin Miller.
 *	Added support for Cygwin tape devices for Windows/NT.
 *
 * January 6th, 2000 by Robin Miller.
 *	Added support for multi-volume media.
 *
 * January 1st, 2000 by Robin Miller.
 *	Added read after write support.
 *
 * December 30th, 1999 by Robin Miller.
 *	Modify call to do_random() to pass the transfer size.
 *	Fix lbdata problem when using step option (wrong lba).
 *
 * July 22nd, 1999 by Robin Miller.
 *	Added support for IOT (DJ's) test pattern.
 * 
 * May 27, 1999 by Robin Miller.
 *	Added support for micro-second delays.
 *
 * March 1, 1999 by Robin Miller.
 *	For tapes when Debug is enabled, report the file number.
 *
 * January 18, 1999 by Robin Miller.
 *	Modified logic in check_write() of partial record writes, so
 * these warnings can be turned off by "disable=verbose".  This change
 * is also consistent with logic done in check_read().
 *
 * December 21, 1998 by Robin Miller.
 *	Add hooks to handle tape device resets (DUNIX specific).
 *
 * October 26, 1998 by Robin Miller.
 *	When random I/O and lbdata options are both enabled, use the
 * file offset seeked to as the starting lbdata address.
 *
 * January 9, 1998 by Robin.
 *	Don't initialize data buffer being written for "disable=compare"
 * which yields better performance.
 *
 * September 6, 1996 by Robin Miller.
 *	Modified write_record() to properly check write errors!.
 *
 * February 28, 1996 by Robin Miller.
 *	Added function for copying records to device or file.
 *	Modified logic so write errors honor users' error limit.
 *
 * February 23, 1996 by Robin Miller.
 *	Only report partial record warning for sequential I/O testing.
 *	Random I/O can position us towards the end of media often, and
 *	partial transfers are likely especially with large block sizes.
 *
 * July 15, 1995 by Robin Miller.
 *	Fix end of media error handling (ENOSPC), and cleanup code.
 *
 * September 17, 1993 by Robin Miller.
 *	Report record number on warning errors (for debug).
 *
 * September 4, 1993 by Robin Miller.
 *	Moved memory mapped I/O logic to seperate module.
 *
 * Septemeber 1, 1993 by Robin Miller.
 *	Add ability to write variable record sizes.
 *
 * August 31, 1993 by Robin Miller.
 *	Rotate starting data buffer address through sizeof(long).
 *
 * August 27, 1993 by Robin MIller.
 *	Added support for DEC OSF/1 POSIX Asynchronous I/O (AIO).
 *
 * August 18, 1992 by Robin Miller.
 *	If "step=" option was specified, then seek that many bytes
 *	before the next write request (for disks).
 *
 * September 11, 1992 by Robin Miller.
 *	Ensure data limit specified by user is not exceeded, incase
 *	the block size isn't modulo the data limit.
 *
 * September 5, 1992 by Robin Miller.
 *	Initial port to QNX 4.1 Operating System.
 *
 * 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 7, 1990 by Robin Miller.
 *	If "skip=n" option is specified, then skip that many records
 *	before starting to write.  The "skip=n" option skips records
 *	by reading, while "seek=n" seeks past records (for disks).
 */

/************************************************************************
 *									*
 * write_data() - Write specified data to the output file.		*
 *									*
 * Inputs:	dip = The device information pointer.			*
 *									*
 * Outputs:	Returns SUCCESS/FAILURE = Ok/Error.			*
 *									*
 ************************************************************************/
int
write_data (struct dinfo *dip)
{
    register ssize_t count;
    register size_t bsize, dsize;
    int status = SUCCESS;
    u_int32 lba;

    /*
     * For variable length records, initialize to minimum record size.
     */
    if (min_size) {
        if (variable_flag) {
            dsize = get_variable (dip);
        } else {
	    dsize = min_size;
        }
    } else {
	dsize = block_size;
    }
    if (dip->di_random_access) {
	if (io_dir == REVERSE) {
	    (void)set_position(dip, (off_t)rdata_limit);
	}
	lba = get_lba(dip);
	dip->di_offset = get_position(dip);
    } else {
	lba = make_lbdata (dip, dip->di_offset);
    }

    /*
     * Now write the specifed number of records.
     */
    while ( (error_count < error_limit) &&
	    (dip->di_fbytes_written < data_limit) &&
	    (dip->di_records_written < record_limit) ) {

	if (volumes_flag && (multi_volume >= volume_limit) &&
		  (dip->di_volume_records >= volume_records)) {
	    break;
	}

	if (wdelay_count) {			/* Optional write delay	*/
	    mySleep (wdelay_count);
	}

	/*
	 * If data limit was specified, ensure we don't exceed it.
	 */
	if ( (dip->di_fbytes_written + dsize) > data_limit) {
	    bsize = (size_t)(data_limit - dip->di_fbytes_written);
	} else {
	    bsize = dsize;
	}

	if (io_dir == REVERSE) {
	    bsize = MIN((dip->di_offset-file_position), bsize);
	    dip->di_offset = set_position(dip, (off_t)(dip->di_offset - bsize));
	} else if (io_type == RANDOM_IO) {
	    dip->di_offset = do_random (dip, TRUE, bsize);
	}

        if (debug_flag && (bsize != dsize) && !variable_flag) {
            Printf ("Record #%lu, Writing a partial record of %lu bytes...\n",
                                    (dip->di_records_written + 1), bsize);
        }

	if (iot_pattern || lbdata_flag) {
	    lba = make_lbdata (dip, (off_t)(dip->di_volume_bytes + dip->di_offset));
	}

	/*
	 * If requested, rotate the data buffer through ROTATE_SIZE
	 * bytes to force various unaligned buffer accesses.
	 */
	if (rotate_flag) {
	    data_buffer = (base_buffer + (rotate_offset++ % ROTATE_SIZE));
	}

	/*
	 * Initialize the data buffer with a pattern.
	 */
	if ((io_mode == TEST_MODE) && compare_flag) {
	    if (iot_pattern) {
		lba = init_iotdata(bsize, lba, lbdata_size);
	    }
	    fill_buffer (data_buffer, bsize, pattern);
	}

	/*
	 * Initialize the logical block data (if enabled).
	 */
	if (lbdata_flag && lbdata_size && !iot_pattern) {
	    lba = init_lbdata (data_buffer, bsize, lba, lbdata_size);
	}

	if (Debug_flag) {
	    u_int32 iolba = NO_LBA;
	    if (dip->di_random_access) {
		iolba = (get_position(dip) / dip->di_dsize);
	    } else if (lbdata_flag || iot_pattern) {
		iolba = make_lbdata (dip, (off_t)(dip->di_volume_bytes + dip->di_offset));
	    }
	    report_record(dip, (dip->di_files_written + 1), (dip->di_records_written + 1),
				iolba, WRITE_MODE, data_buffer, bsize);
	}

	count = write_record (dip, data_buffer, bsize, dsize, &status);
	if (end_of_file) break;	/* Stop writing at end of file. */
	if ( (status == FAILURE) && (error_count >= error_limit) ) break;

	/*
	 * If we had a partial transfer, perhaps due to an error, adjust
	 * the logical block address in preparation for the next request.
	 */
	if (iot_pattern && ((size_t)count < bsize)) {
	    size_t resid = (bsize - count);
	    lba -= howmany(resid, lbdata_size);
	}

	if ( (count > (ssize_t) 0) && raw_flag) {
	    status = write_verify(dip, data_buffer, count, dsize, dip->di_offset);
	    if ( (status == FAILURE) && (error_count >= error_limit) ) break;
	}

	/*
	 * For variable length records, adjust the next record size.
	 */
	if (min_size) {
	    if (variable_flag) {
		dsize = get_variable (dip);
	    } else {
		dsize += incr_count;
	        if (dsize > max_size) dsize = min_size;
	    }
	}

	dip->di_records_written++;
	dip->di_volume_records++;

	if (io_dir == FORWARD) {
	    dip->di_offset += count;	/* Maintain our own position too! */
	} else if ( (io_type == SEQUENTIAL_IO) &&
		    (dip->di_offset == (off_t) file_position) ) {
	    set_Eof(dip);
	    break;
	}

	if (step_offset) {
	    if (io_dir == FORWARD) {
		dip->di_offset = incr_position (dip, step_offset);
	    } else if ((dip->di_offset -= step_offset) <= (off_t) file_position) {
		set_Eof(dip);
		break;
	    }
	}
    }
    return (status);
}

/************************************************************************
 *									*
 * check_write() - Check status of last write operation.		*
 *									*
 * Inputs:	dip = The device information pointer.			*
 *		count = Number of bytes read.				*
 *		size  = Number of bytes expected.			*
 *									*
 * Outputs:	Returns SUCCESS/FAILURE/WARNING = Ok/Error/Warning	*
 *									*
 ************************************************************************/
int
check_write (struct dinfo *dip, ssize_t count, size_t size)
{
    int status = SUCCESS;

    if ((size_t)count != size) {
	if (count == FAILURE) {
	    report_error ("write", FALSE);
	    ReportDeviceInfo (dip, 0, 0, (bool)(errno == EIO));
            ExecuteTrigger(dip, "write");
	} else {
	    /*
	     * For writes at end of file or writes at end of block
	     * devices, we'll write less than the requested count.
	     * In this case, we'll treat this as a warning since
	     * this is to be expected.
	     *
	     * NOTE:  The raw device should be used for disks.
	     */
	    if ( (debug_flag || verbose_flag || ((size_t)count > size)) &&
		 (io_mode == TEST_MODE) /*&& (io_type == SEQUENTIAL_IO)*/ ) {
		Printf(
	"WARNING: Record #%lu, attempted to write %lu bytes, wrote only %lu bytes.\n",
					(dip->di_records_written + 1), size, count);
	    }
	    if ((size_t)count < size) {	/* Partial write is a warning. */
		warning_errors++;
		return (WARNING);
	    }
	    ReportDeviceInfo (dip, count, 0, FALSE);
	}
	(void)RecordError();
	dip->di_write_errors++;
	status = FAILURE;
    }
    return (status);
}

/************************************************************************
 *									*
 * copy_record() - Copy record to device or file.			*
 *									*
 * Inputs:	dip = The device information pointer.			*
 *		buffer = The data buffer to write.			*
 *		bsize = The number of bytes to write.			*
 *									*
 * Outputs:	Returns SUCCESS/FAILURE/WARNING = Ok/Error/Warning	*
 *									*
 ************************************************************************/
int
copy_record (	struct dinfo	*dip,
		u_char		*buffer,
		size_t		bsize )
{
    ssize_t count;
    int status;

    count = write_record (dip, buffer, bsize, bsize, &status);
    /* TODO: Get this into write_record() where it belongs! */
    if (count > (ssize_t) 0) dip->di_records_written++;
    return (status);
}

/************************************************************************
 *									*
 * write_record() - Write record to device or file.			*
 *									*
 * Inputs:	dip = The device information pointer.			*
 *		buffer = The data buffer to write.			*
 *		bsize = The number of bytes to write.			*
 *		dsize = The users' requested size.			*
 *		status = Pointer to status variable.			*
 *									*
 * Outputs:	status = SUCCESS/FAILURE/WARNING = Ok/Error/Warning	*
 *		Return value is number of bytes from write() request.	*
 *									*
 ************************************************************************/
ssize_t
write_record(
	struct dinfo	*dip,
	u_char		*buffer,
	size_t		bsize,
	size_t		dsize,
	int		*status )
{
    ssize_t count;

retry:
    *status = SUCCESS;
    count = write (dip->di_fd, buffer, bsize);

#if defined(EEI)
    if ( (count == FAILURE) &&
	 (dip->di_dtype->dt_dtype == DT_TAPE) ) {
	if ( (errno == EIO) && eei_resets) {
	    if ( HandleTapeResets(dip) ) {
		goto retry;
	    }
	} else if (eei_flag) {
	    (void) get_eei_status(dip->di_fd, dip->di_mt);
	}
    }
#endif /* defined(EEI) */

    if ( is_Eof (dip, count, status) ) {
	if (multi_flag) {
	    *status = HandleMultiVolume (dip);
	    dip->di_offset = (off_t) 0;
	    if (*status == SUCCESS) goto retry;
	}
    } else {
	if (count > (ssize_t) 0) {
	    dip->di_dbytes_written += count;
	    dip->di_fbytes_written += count;
	    dip->di_vbytes_written += count;
	    if ((size_t)count == dsize) {
		records_processed++;
	    } else {
		partial_records++;
	    }
	}
	*status = check_write (dip, count, bsize);
    }
    return (count);
}

/************************************************************************
 *									*
 * write_verify() - Verify the record just written.			*
 *									*
 * Inputs:	dip = The device information pointer.			*
 *		buffer = The data buffer written.			*
 *		bsize = The number of bytes written.			*
 *		dsize = The users' requested size.			*
 *		pos = The starting device/file position.		*
 *									*
 * Outputs:	status = SUCCESS/FAILURE/WARNING = Ok/Error/Warning	*
 *									*
 ************************************************************************/
int
write_verify(
	struct dinfo	*dip,
	u_char		*buffer,
	size_t		bsize,
	size_t		dsize,
	off_t		pos )
{
    u_char *vbuffer = verify_buffer;
    ssize_t count;
    u_int32 lba = 0;
    int status = SUCCESS;

    if (rdelay_count) {			/* Optional read delay.	*/
	mySleep (rdelay_count);
    }

    if (dip->di_dtype->dt_dtype == DT_TAPE) {
#if !defined(__NUTC__) && !defined(__QNXNTO__) && !defined(AIX)
	status = DoBackwardSpaceRecord(dip, 1);
	if (status) return (status);
#endif /* !defined(__NUTC__) && !defined(__QNXNTO__) && !defined(AIX) */
    } else { /* assume random access */
	off_t npos = set_position (dip, pos);
	if (npos != pos) {
	    Fprintf("ERROR: Wrong seek position, (npos " FUF " != pos)" FUF "!\n",
						npos, (pos - bsize));
	    return (FAILURE);
	}
    }
    if (iot_pattern || lbdata_flag) {
	lba = make_lbdata(dip, (off_t)(dip->di_volume_bytes + pos));
    }

    if (rotate_flag) {
	vbuffer = (verify_buffer + ((rotate_offset -1) % ROTATE_SIZE));
    }

    /*
     * If we'll be doing a data compare after the read, then
     * fill the data buffer with the inverted pattern to ensure
     * the buffer actually gets written into (driver debug mostly).
     */
    if ((io_mode == TEST_MODE) && compare_flag) {
	init_buffer (vbuffer, bsize, ~pattern);
	init_padbytes (vbuffer, bsize, ~pattern);
	if (iot_pattern) {
	    lba = init_iotdata (bsize, lba, lbdata_size);
	}
    }

    if (Debug_flag) {
	u_int32 iolba = NO_LBA;
	if (dip->di_random_access) {
	    iolba = (get_position(dip) / dip->di_dsize);
	} else if (lbdata_flag || iot_pattern) {
	    iolba = make_lbdata (dip, (off_t)(dip->di_volume_bytes + pos));
	}
	report_record(dip, (dip->di_files_written + 1), (dip->di_records_read + 1),
			iolba, READ_MODE, vbuffer, bsize);
    }

    count = read_record (dip, vbuffer, bsize, dsize, &status);
    if (end_of_file) {
	report_error ("read", FALSE);
	ReportDeviceInfo (dip, 0, 0, FALSE);
	(void)RecordError();
	if (dip->di_dtype->dt_dtype != DT_TAPE) {
	    (void) set_position (dip, pos);
	}
	return (FAILURE);
    }

    /*
     * Verify the data (unless disabled).
     */
    if ( (status != FAILURE) && compare_flag && (io_mode == TEST_MODE) ) {
	ssize_t vsize = count;
	if (iot_pattern || lbdata_flag) {
	    status = verify_lbdata(dip, buffer, vbuffer, vsize, &lba);
	}
	if (status == SUCCESS) {
	    status = verify_buffers(dip, buffer, vbuffer, vsize);
	}
	/*
	 * Verify the pad bytes (if enabled).
	 */
	if ( (status == SUCCESS) && pad_check) {
	    (void) verify_padbytes (dip, vbuffer, vsize, ~pattern, bsize);
	}
    }

    /*
     * We expect to read as much as we wrote, or else we've got a problem!
     */
    if ((size_t)count < bsize) {
	/* check_read() reports info regarding the short record read. */
	ReportDeviceInfo (dip, count, 0, FALSE);
	(void)RecordError();
	status = FAILURE;
	if (dip->di_dtype->dt_dtype != DT_TAPE) {
	    (void) set_position (dip, pos);
	}
    }
    dip->di_records_read++;
    return (status);
}


syntax highlighted by Code2HTML, v. 0.9.1