//-*-c++-*--------------------------------------------------------------
//  nsrRSSdv.cpp
//   Source file for the Drive Device object
//
//      Unpublished Confidential Information of Hewlett-Packard
//      Company. Do not disclose.  Copyright (c) Hewlett-Packard
//      Company 1995. All rights reserved
// 
// The contents of this software are proprietary and confidential to
// the Hewlett- Packard Company, and are limited in distribution to
// those with a direct need to know.  Individuals having access to
// this software are responsible for maintaining the confidentiality
// of the content and for keeping the software secure when not in use.
// Transfer to any party is strictly forbidden other than as expressly
// permitted in writing by Hewlett-Packard Company.  Unauthorized
// transfer to or possession by any unauthorized party may be a
// criminal offense.
//
//                  RESTRICTED RIGHTS LEGEND
//
//      Use,  duplication,  or disclosure by the U. S. Government is
//      subject to restrictions as set forth in subparagraph (c) (1)
//      (ii) in the Rights in Technical Data and Computer Software
//      clause in DFARS 252.227-7013 or any other successor clause.
//
//                      HEWLETT-PACKARD COMPANY
//                      3000 Hanover St.
//                      Palo Alto, CA  94304 U.S.A.
//
//      Rights for non-DOD U.S. Government Departments and Agencies
//      are as set forth in FAR 52.227-19 (c) (1,2) or any other
//      successor clause.
//
//  CLASSES:
//   DriveDevice
//  
// Systems:  series 9000/700, series 9000/800
// OS:       HP-UX 9.0x, 10.0x
//
//----------------------------------------------------------------------
//
// $Source: /projects/HMS/nsr/udfCore/devs/nsrRSSdv.cpp,v $
// $Revision: 1.51 $    $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 96/03/19 16:26:06 $
//

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/diskio.h>
#include <nsrscsi.h>

#include "nsrLib.h"
#include "nsrRSSdv.h"

#define XSENSE_ILLEGAL_REQUEST  0x05    // For mediaType() method
#define SENSE_KEY_BLANK_CHECK   0x08    // For isBlankCheck() method
#define SET_LUN_EXCLUSIVE       1       // For SIOC_EXLUSIVE ioctl
#define CLEAR_LUN_EXCLUSIVE     0

#define WWOE_ON                 1       // for setWWOE() method
#define WWOE_OFF                0       // for setWWOE() method
#define VERIFY_ON               1       // for setVMode() method
#define VERIFY_OFF              0       // for setVMode() method

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::DriveDevice                            --JKH
// 
// Constructor for the DriveDevice object.
//
// ARGUMENTS:  
//
// devfile      IN      String reference containing pathname of the device 
//                      file to which the DriveDevice object should be bound.  
//
// RETURNS:  None.
//
// PRECONDITIONS:
//     
// --The pathname contained in the argument String devfile corresponds to a
//   valid (i.e., existing) drive device.  
// --The executing process must have read/write access to this device. 
// --No other process must have the device opened in EXCLUSIVE mode.
//
// POSTCONDITIONS:
//
// --The device will be opened (and held open until the DriveDevice object
//   is destructed) for read and write.  
// --Capacity and sectorSize information will be stored in the object's
//   data members.
//
// ERRORS:
//
// error_None:  Function completed successfully.
// error_Empty_path_element:  NULL devfile argument.
// error_File_not_found:  Device file passed as argument cannot be opened.
// error_Capacity_failed:  Cannot get numSectors_ information.
//
// ISSUE:  DriveDevice::DriveDevice():  Should no media in drive be an error?
//         Currently, this condition will not impede successful construction,
//         though obviously no read/write/erase operation will subsequently
//         be successful.
//-----------------------------------------------------------------------------

DriveDevice::DriveDevice( const NSR::String& devfile ) : verifyMode_(0)
{
    NSR::INT32          err;
    disk_describe_type  describe;
    NSR::CONST_CHAR_PTR str;

    fd_ = -1;                   // Flag object as Bad initially 
    str = (NSR::CONST_CHAR_PTR) devfile;
    if(str == NULL) {
        dderror_ = NSR::error_Empty_path_element;
        return;
    }

    // Attempt to open the disk device for reading and writing

    fd_ = open(str,O_RDWR);     
    if(fd_ < 0) {                       // Open failed!
        dderror_ = NSR::error_File_not_found;
        return;
    }

    // Get numSectors_ information

    err = ioctl(fd_,DIOC_DESCRIBE,&describe); 
    if(err) {
        close(fd_);
        fd_ = -1;
        dderror_ = NSR::error_Capacity_failed;
        return;
    }

    sectorSize_ = describe.lgblksz;
    numSectors_ = describe.maxsva + 1;

    dderror_ = NSR::error_None;                 // Completed successfully!
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::~DriveDevice()                         --JKH
//
// Destructor for the DriveDevice object.
//
// ARGS:  none
// RETURNS:  none
// PRE-CONDS:  none
// POST-CONDS:  
//
// --The file descriptor corresponding to the drive device bound to this 
//   object, if open, will be closed.
//-----------------------------------------------------------------------------

DriveDevice::~DriveDevice()
{
    if(fd_ < 0) {
        return;
    }
    (void) close(fd_);
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::read()                                 --JKH
//
// This method reads the specified sectors from disk into the given buffer.
//
// ARGS:
//
//  UINT32 start             IN       Starting sector use for read
//  UINT32 nsectors          IN       Maximum number of sectors to read
//  const ByteArrayRef& buf  IN/OUT   The buffer to store the data.
//  Error& err               OUT      Error value to hold results of operation
//
// RETURNS:
//
// The function returns the number of bytes read.  If -1 is returned, then
// an actual I/O error (not a blank check) occurred.  If a blank check or
// read past EOM/EOLM condition occurred, a "short count" will be returned.
//
// PRE-CONDS:
//
// --The DriveDevice object was successfully constructed and contains media
// --The start parameter is in the range [0,numSectors_)
// --The nsectors parameter satisfies the following:
//              start + nsectors < numSectors_
//
// POST-CONDS:
//
// --The number of bytes specified in the length field of the argument
//   ByteArrayRef buf will be read from disk and stored in the memory referred 
//   to by buf
//
// ERRORS:
//
// error_None:  Function completed successfully.
// error_No_free_store:  Could not allocate memory for buffering.
// error_Empty_path_element:  Argument buffer is invalid.
// error_Drive_blank_check:  A blank sector was encountered during the read.
// error_Read_EOM:  Attempted to read past physical EOM
// error_Read_EOLM:  Attempted to read past logical EOM
// error_Drive_read:  Generic I/O failure on read
//
// NOTES:
//
// buf.length() indicates how many bytes to read; the sectors specified by
// start and nsectors only indicate which sectors may be used to satisfy
// the request.  nsectors may be a greater number of bytes than specified
// by buf; this is not an error condition.
//
// If the request is not a sector-multiple, the last sector of the read must
// be buffered.  This buffering requires dynamic allocation for the new
// buffer, and so may cause the entire read to fail due to no free store.
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::read
(
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    const NSR::ByteArrayRef& buf,
    NSR::Error& err
)
{
    NSR::INT32    rv;           // Return value from system call
    NSR::INT32    offset;       // For lseek(); offset in bytes
    NSR::INT32    extsize;
    NSR::INT32    residue;
    NSR::INT32    request_len;
    NSR::BYTE_PTR bptr;
    
    // Verify that the DriveDevice object has been successfully constructed.

    if(fd_ < 0) {
        err = dderror_;
        return -1;
    }

    err = dderror_ = NSR::error_None;

    if(buf.isValid() == NSR::False) {
        err = NSR::error_Empty_path_element;
        return -1;
    }

    // Set up read values.  

    extsize = nsectors * sectorSize_;
    request_len = ((extsize < buf.length()) ? extsize : buf.length());
    residue = request_len % sectorSize_;
    offset = start * sectorSize_;
    bptr = (NSR::BYTE_PTR ) buf;

    // Verify that the number of bytes to be read is a sector multiple.  If
    // it is not, then we split the read into two parts:  the first part
    // will be the first n-1 sectors of the request; the second part will
    // be the last sector, which must be buffered to prevent overrunning the
    // user's buffer.  Both of these reads will be done recursively.

    if(residue != 0) {
        NSR::ByteArrayRef       newbuf1(buf.theArray(),buf.arrayLen(),
                                        buf.offset(),
                                        buf.length() - residue);
        rv = 0;
        if(((NSR::INT32) buf.length() - (NSR::INT32) residue) > 0) {
            
            rv = read(start,nsectors,newbuf1,err);

            // If the read doesn't fully succeed, don't even try to do the last
            // sector's worth.  Makes error handling much easier to deal with.

            if(rv < (NSR::INT32) newbuf1.length()) {
                return rv;
            }
        }

        // Now set up to do the last sector.

        NSR::ByteArray          newbarr(sectorSize_);
        NSR::ByteArrayRef       newbuf2(newbarr);
        NSR::INT32              rv2;
        NSR::INT32              sector_offset = (buf.length() - residue) / 
                                                 sectorSize_;

        if(((NSR::BYTE_PTR) newbarr) == NULL) { // Can't buffer: we're hosed
            err = dderror_ = NSR::error_No_free_store;
            return rv;
        }

        rv2 = read(start+sector_offset,1,newbuf2,err);
        if(rv2 < (NSR::INT32) sectorSize_) {
            return rv2;                 // A short count!
        }

        newbuf1.offset(buf.offset() + buf.length() - residue);
        newbuf1.length(residue);
        newbuf1.fromMem((NSR::BYTE_PTR) newbuf2);
        return (rv + residue);
    }

    // Seek into the disk

    rv = lseek(fd_,offset,SEEK_SET);
    if(rv < 0)  {                       // The lseek() failed!
        err = dderror_ = NSR::error_Drive_read;
        return -1;
    }

    // Perform the read

    rv = ::read(fd_,bptr,((extsize < buf.length()) ? extsize : buf.length()));
    if(rv < (NSR::INT32 ) buf.length()) {               // Got a short count
        perror("read");
        if(rv == extsize) {                     // Extent too small
            err = dderror_ = NSR::error_Read_EOLM;
            return rv;
        }

        // Extent was big enough, presumably, so we need to check for physical
        // EOM

        if((offset + buf.length()) > numSectors_*sectorSize_) {
            err = dderror_ = NSR::error_Read_EOM;
            return rv;
        }

        // We didn't fall off the edge of the physical media, so our last 
        // chance is a blank check.

        if(isBlankCheck()) {
            err = dderror_ = NSR::error_Drive_blank_check;
            return rv;
        }

        // We didn't read past physical EOM, we didn't get a blank check,
        // but we received a short count.  According to the read(2) manpage,
        // this condition is undefined, so we report a generic I/O error.

        err = dderror_ = NSR::error_Drive_read;
        return rv;
    }

    // If we got here, then everything went smoothly:  success!

    err = dderror_ = NSR::error_None;
    return rv;
}


//-----------------------------------------------------------------------------
// Alternate interface for DriveDevice::read().
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::read
(
    NSR::UINT32 start,
    NSR::UINT32 nbytes,
    NSR::BYTE_PTR buf,
    NSR::Error& err
)
{
    NSR::INT32  rv;             // Return value from system call
    NSR::INT32  offset;         // For lseek(); offset in bytes
    NSR::INT32  residue;
    
    // Verify that the DriveDevice object has been successfully constructed.

    if(fd_ < 0) {
        err = dderror_;
        return -1;
    }

    err = dderror_ = NSR::error_None;

    if(buf == NULL) {
        err = NSR::error_Null_pointer;
        return -1;
    }

    err = NSR::error_None;

    // Set up read values.  

    residue = nbytes % sectorSize_;
    offset = start * sectorSize_;

    // Verify that the number of bytes to be read is a sector multiple.  If
    // it is not, then we split the read into two parts:  the first part
    // will be the first n-1 sectors of the request; the second part will
    // be the last sector, which must be buffered to prevent overrunning the
    // user's buffer.  Both of these reads will be done recursively.

    if(residue != 0) {
        
        rv = 0;
        if(((NSR::INT32) nbytes - (NSR::INT32) residue) > 0) {
            rv = read(start,nbytes - residue,buf,err);

            // If the read doesn't fully succeed, don't even try to do the last
            // sector's worth.  Makes error handling much easier to deal with.

            if(rv < (NSR::INT32) (nbytes - residue)) {
                return rv;
            }
        }

        // Now set up to do the last sector.

        NSR::BYTE_PTR           newbuf = new NSR::BYTE[sectorSize_];
        NSR::INT32              rv2;
        NSR::INT32              sector_offset = (nbytes - residue) / 
                                                sectorSize_;

        if(newbuf == NULL) {                    // Can't buffer: we're hosed
            err = dderror_ = NSR::error_No_free_store;
            return rv;
        }

        rv2 = read(start + sector_offset,sectorSize_,newbuf,err);
        if(rv2 < (NSR::INT32) sectorSize_) {
            delete newbuf;
            return rv;                  // A short count!
        }

        memcpy((NSR::VOID_PTR) (buf + nbytes - residue),newbuf,residue);
        delete newbuf;
        return (rv + residue);
    }

    // Seek into the disk

    rv = lseek(fd_,offset,SEEK_SET);
    if(rv < 0)  {                       // The lseek() failed!
        err = dderror_ = NSR::error_Drive_read;
        return -1;
    }

    // Perform the read

    rv = ::read(fd_,buf,nbytes);
    if(rv < (NSR::INT32) nbytes) {                      // Got a short count

        // We need to check for physical EOM

        if((offset + nbytes) > numSectors_*sectorSize_) {
            err = dderror_ = NSR::error_Read_EOM;
            return rv;
        }

        // We didn't fall off the edge of the physical media, so our last 
        // chance is a blank check.

        if(isBlankCheck()) {
            err = dderror_ = NSR::error_Drive_blank_check;
            return rv;
        }

        // We didn't read past physical EOM, we didn't get a blank check,
        // but we received a short count.  According to the read(2) manpage,
        // this condition is undefined, so we report a generic I/O error.

        err = dderror_ = NSR::error_Drive_read;
        return rv;
    }

    // If we got here, then everything went smoothly:  success!

    err = dderror_ = NSR::error_None;
    return rv;

}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::write()                                --JKH
//
// This method writes the specified sectors to disk from the given buffer.
//
// ARGS:
//
// UINT32 start            IN      Starting sector use for write
// UINT32 nsectors         IN      Maximum number of sectors to write
// const ByteArrayRef& buf IN/OUT  The buffer to supply the data.
// Error& err              OUT     Error value to hold results of operation
//
// RETURNS:
//
// The function returns the number of bytes written.  If -1 is returned, then
// an actual I/O error occurred.  If a write past EOM/EOLM condition occurred, 
// a "short count" will be returned.  NOTE:  if the DriveDevice is in verify
// mode, and the DriveDevice cannot successfully put itself in verify mode
// for the write, the write call will fail, returning -1 and setting error
// to error_Set_verify_failed.
//
// PRE-CONDS:
//
// --The DriveDevice object was successfully constructed and contains media
// --The start parameter is in the range [0,numSectors_)
// --The nsectors parameter satisfies the following:
//              start + nsectors < numSectors_
// --The device is writable (e.g., not write-protected and, if WORM, the
//   sectors are unwritten).
//
// POST-CONDS:
//
// --The number of bytes specified in the length field of the argument
//   ByteArrayRef buf will be written to disk and taken from the memory 
//   referred to by buf
//
// ERRORS:
//
// error_None:  Function completed successfully.
// error_No_free_store:  Could not allocate memory for buffering.
// error_Empty_path_element:  Argument buffer is invalid.
// error_Write_EOM:  Attempted to write past physical EOM
// error_Write_EOLM:  Attempted to write past logical EOM
// error_Drive_write:  Generic I/O failure on write
// error_Set_verify_failed:  Cannot set verify mode.
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::write
( 
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    const NSR::ByteArrayRef& buf,
    NSR::Error& err
)
{
    NSR::INT32     rv;          // Return value from system call
    NSR::INT32     offset;              // For lseek(); offset in bytes
    NSR::INT32     extsize;
    NSR::INT32     residue;
    NSR::INT32     request_len;
    NSR::CONST_BYTE_PTR bptr;
    
    // Verify that the DriveDevice object has been successfully constructed.

    if(fd_ < 0) {
        err = dderror_;
        return -1;
    }

    err = dderror_ = NSR::error_None;

    if(buf.isValid() == NSR::False) {
        err = NSR::error_Empty_path_element;
        return -1;
    }

    // Set up write values.

    extsize = nsectors * sectorSize_;
    request_len = ((extsize < buf.length()) ? extsize : buf.length());
    residue = request_len % sectorSize_;
    offset = start * sectorSize_;
    bptr = (NSR::CONST_BYTE_PTR) buf;

    // Verify that the number of bytes to be written is a sector multiple.  If
    // it is not, then we split the write into two parts:  the first part
    // will be the first n-1 sectors of the request; the second part will
    // be the last sector, which must be padded so that one whole sector
    // may be written.  This padding will be performed by zero-filling
    // the last sector.

    if(residue != 0) {
        rv = 0;
        if(((NSR::INT32) buf.length() - (NSR::INT32) residue) > 0) {
            NSR::ByteArrayRef   newbuf1(buf.theArray(),buf.arrayLen(),
                                        buf.offset(),
                                        buf.length() - residue);

            rv = write(start,nsectors,newbuf1,err);


            // If the write doesn't fully succeed, don't even try to do the last
            // sector's worth.  Makes error handling much easier to deal with.

            if(rv < (NSR::INT32) newbuf1.length()) {
                return rv;
            }
        }

        // Now set up to do the last sector.  We must allocate a sector-sized
        // buffer, copy the remaining user data into it, and zero-fill it.

        NSR::ByteArray  newbarr(sectorSize_);
        NSR::ByteArrayRef       newbuf2(newbarr);
        NSR::INT32      rv2;
        NSR::INT32      sector_offset = (buf.length() - residue) / sectorSize_;

        if(((NSR::BYTE_PTR) newbarr) == NULL) { // Can't buffer: we're hosed
            err = dderror_ = NSR::error_No_free_store;
            return rv;
        }

        memset((NSR::BYTE_PTR) newbuf2,0,sectorSize_); // zero-fill
        newbuf2.length(residue);
        newbuf2.fromMem(((NSR::BYTE_PTR) buf) + (buf.length() - residue));
        newbuf2.length(sectorSize_);
        
        rv2 = write(start+sector_offset,1,newbuf2,err);
        if(rv2 < (NSR::INT32) sectorSize_) {
            return rv;                  // A short count!
        }
        return rv + residue;
    }

    // Seek into the disk

    rv = lseek(fd_,offset,SEEK_SET);
    if(rv < 0)  {       // The lseek() failed!
        err = dderror_ = NSR::error_Drive_write;
        return -1;
    }

    // Perform the write.  Put the device in verify mode if necessary.

    if(verifyMode_) {
        if(setVMode(VERIFY_ON)) {
            err = dderror_ = NSR::error_Set_verify_failed;
            return -1;
        }
    }

    rv = ::write(fd_,bptr,((extsize < buf.length()) ? extsize : buf.length()));

    if(verifyMode_) {                   // Turn off verify mode
        (void) setVMode(VERIFY_OFF);
    }

    if(rv < (NSR::INT32) buf.length()) {                // Got a short count
        if(rv == extsize) {                     // Extent too small
            err = dderror_ = NSR::error_Write_EOLM;
            return rv;
        }
        // Extent was big enough, presumably, so we need to check for physical
        // EOM

        if((offset + buf.length()) > numSectors_*sectorSize_) {
            err = dderror_ = NSR::error_Write_EOM;
            return rv;
        }

        // We didn't fall off the edge of the physical media, so we got
        // a mysterious short count.  This condition is undefined by the
        // write(2) man page, so we just report a generic I/O problem.

        err = dderror_ = NSR::error_Drive_write;
        return rv;
    }

    // If we got here, then everything went smoothly:  success!

    err = dderror_ = NSR::error_None;
    return rv;
}

//-----------------------------------------------------------------------------
// Alternate interface for DriveDevice::write().
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::write
(
    NSR::UINT32 start,
    NSR::UINT32 nbytes,
    NSR::CONST_BYTE_PTR buf,
    NSR::Error& err
)
{
    NSR::INT32  rv;             // Return value from system call
    NSR::INT32  offset;         // For lseek(); offset in bytes
    NSR::INT32  residue;
    
    // Verify that the DriveDevice object has been successfully constructed.

    if(fd_ < 0) {
        err = dderror_;
        return -1;
    }
     
    err = dderror_ = NSR::error_None;

    if(buf == NULL) {
        err = NSR::error_Null_pointer;
        return -1;
    }

    // Set up write values.

    residue =  nbytes % sectorSize_;
    offset = start * sectorSize_;

    // Verify that the number of bytes to be written is a sector multiple.  If
    // it is not, then we split the write into two parts:  the first part
    // will be the first n-1 sectors of the request; the second part will
    // be the last sector, which must be padded so that one whole sector
    // may be written.  This padding will be performed by zero-filling
    // the last sector.

    if(residue != 0) {

        rv = 0;
        if(((NSR::INT32) nbytes - (NSR::INT32) residue) > 0) {
            rv = write(start,nbytes - residue,buf,err);


            // If the write doesn't fully succeed, don't even try to do the last
            // sector's worth.  Makes error handling much easier to deal with.

            if(rv < (NSR::INT32) (nbytes - residue)) {
                return rv;
            }
        }

        // Now set up to do the last sector.  We must allocate a sector-sized
        // buffer, copy the remaining user data into it, and zero-fill it.

        NSR::BYTE_PTR   newbuf = new NSR::BYTE[sectorSize_];
        NSR::INT32         rv2;
        NSR::INT32         sector_offset = (nbytes - residue) / sectorSize_;

        if(newbuf == NULL) {                    // Can't buffer: we're hosed
            err = dderror_ = NSR::error_No_free_store;
            return rv;
        }

        memset(newbuf,0,sectorSize_);           // zero-fill
        memcpy(newbuf,buf + (nbytes - residue),residue);
        
        rv2 = write(start + sector_offset,sectorSize_,newbuf,err);
        if(rv2 < (NSR::INT32) sectorSize_) {
            delete newbuf;
            return rv;                  // A short count!
        }
        delete newbuf;
        return rv + residue;
    }

    // Seek into the disk

    rv = lseek(fd_,offset,SEEK_SET);
    if(rv < 0)  {       // The lseek() failed!
        err = dderror_ = NSR::error_Drive_write;
        return -1;
    }

    // Perform the write.  Put the device in verify mode if necessary.

    if(verifyMode_) {
        if(setVMode(VERIFY_ON)) {
            err = dderror_ = NSR::error_Set_verify_failed;
            return -1;
        }
    }

    rv = ::write(fd_,buf,nbytes);

    if(verifyMode_) {                   // Turn off verify mode
        (void) setVMode(VERIFY_OFF);
    }

    if(rv < (NSR::INT32) nbytes) {                      // Got a short count
        
        // We need to check for physical EOM

        if((offset + nbytes) > numSectors_*sectorSize_) {
            err = dderror_ = NSR::error_Write_EOM;
            return rv;
        }

        // We didn't fall off the edge of the physical media, so we got
        // a mysterious short count.  This condition is undefined by the
        // write(2) man page, so we just report a generic I/O problem.

        err = dderror_ = NSR::error_Drive_write;
        return rv;
    }

    // If we got here, then everything went smoothly:  success!

    err = dderror_ = NSR::error_None;
    return rv;
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::writeWOE()                             --JKH
//
// This method writes the specified sectors to disk from the given buffer 
// using write-without-erase.  
//
// NOTE:  if the sectors to be written without erase are NOT in fact already 
// erased, they will be garbaged and henceforth unreadable, and no error will 
// be detected during the write!  It is very important that users using this 
// method are sure that the sectors in question are actually erased.
//
// ARGS:
//
// UINT32 start             IN      Starting sector use for write
// UINT32 nsectors          IN      Maximum number of sectors to write
// const ByteArrayRef& buf  IN/OUT  The buffer to supply the data.
// Error& err               OUT     Error value to hold results of operation
//
// RETURNS:
//
// The function returns the number of bytes written.  If -1 is returned, then
// an actual I/O error occurred.  If a write past EOM/EOLM condition occurred, 
// a "short count" will be returned.  If the DriveDevice cannot be put into
// write-without-erase mode successfully, the call will proceed in normal
// write-with-erase mode, and will not flag an error.
//
// PRE-CONDS:
//
// --The DriveDevice object was successfully constructed and contains media
// --The PhysExtent is valid (i.e., valid values)
// --The device is writable (e.g., not write-protected and, if WORM, the
//   sectors are unwritten).
// --The sectors specified by the PhysExtent are already erased.
//
// POST-CONDS:
//
// --The number of bytes specified in the length field of the argument
//   ByteArrayRef buf will be written to disk and taken from the memory 
//   referred to by buf
//
// ERRORS:
//
// error_None:  Function completed successfully.
// error_No_free_store:  Could not allocate memory for buffering.
// error_Empty_path_element:  Argument buffer is invalid.
// error_Write_EOM:  Attempted to write past physical EOM
// error_Write_EOLM:  Attempted to write past logical EOM
// error_Drive_write:  Generic I/O failure on write
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::writeWOE
( 
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    const NSR::ByteArrayRef& arr,
    NSR::Error& err
)
{
    NSR::INT32 rval;

    setWWOE(WWOE_ON);
    rval = write(start,nsectors,arr,err);
    setWWOE(WWOE_OFF);
    return rval;
}

//-----------------------------------------------------------------------------
// Alternate interface for DriveDevice::writeWOE().
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::writeWOE
(
    NSR::UINT32 start,
    NSR::UINT32 nbytes,
    NSR::CONST_BYTE_PTR buf,
    NSR::Error& err
)
{
    NSR::INT32 rval;

    setWWOE(WWOE_ON);
    rval = write(start,nbytes,buf,err);
    setWWOE(WWOE_OFF);
    return rval;
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::erase()                                --JKH
//
// Erases the sectors in the argument PhysExtent.
//
// ARGS:
//
// UINT32 start        IN  Starting sector to erase
// UINT32 nsectors     IN  Number of sectors to erase
// Error& err          OUT Error value to hold result of operation
//
// RETURNS:  Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --The DriveDevice object was successfully created and contains media
// --The executing process is the only process that has the drive open
// --The PhysExtent specifies a range of sectors physically accessible on
//   the media
// --The media is erasable (e.g., not a hard disk, not WORM)
//
// POST-CONDS:
//
// --The sectors specified by the argument PhysExtent will be erased.
// 
// ERRORS:
//
// error_None            Function completed successfully.
// error_Bad_erase_size  Extent size is not a multiple of the sector size.
// error_Erase_failed    The erase failed due to an I/O error, or inability
//                       to put the drive in exclusive mode.
//
// NOTES:  The erase capability provided by the SCSI-2 spec for MO drives
//         only allows for erasing whole sectors.  Therefore, the argument
//         PhysExtent MUST be a multiple of sector size in length.  If this
//         condition is not met, the erase call will fail.
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::erase
(
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    NSR::Error& err
)
{
    struct scsi_erase   erase;

    err = NSR::error_None;

    // Check to ensure that this object has been successfully created

    if(fd_ < 0) {
        err = dderror_;
        return -1;
    }


    // We need to get the device exclusive.  If we cannot, then the call
    // fails.  We get LUN exclusive for future support of LUN-supporting 
    // devices.

    NSR::INT32 flag = SET_LUN_EXCLUSIVE;        // Set LUN exclusive
    if(ioctl(fd_,SIOC_EXCLUSIVE,&flag)) {
        err = dderror_ = NSR::error_Erase_failed; // Separate error code here?
        return -1;
    }

    // Simply perform an SIOC_ERASE ioctl.  

    erase.start_lba = start;
    erase.block_cnt = nsectors;
    if(ioctl(fd_,SIOC_ERASE,&erase)) {
        flag = CLEAR_LUN_EXCLUSIVE;             // Clear LUN exclusive
        ioctl(fd_,SIOC_EXCLUSIVE,&flag);
        err = dderror_ = NSR::error_Erase_failed;
        return -1;
    }
    flag = CLEAR_LUN_EXCLUSIVE;                 // Clear LUN exclusive
    ioctl(fd_,SIOC_EXCLUSIVE,&flag);
    err = dderror_ = NSR::error_None;
    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::error()                                --JKH
//
// This method returns an Error value corresponding to the results of the last 
// call to the read(), write(), or erase() methods, or construction.
//
// ARGS:  none
//
// RETURNS:
//
// Returns an Error object corresponding to the results of the last call to
// the read(), write(), or erase() methods, or construction.
//
// PRE-CONDS:   none
// POST_CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::Error
DriveDevice::error(void) const
{
    return NSR::Error(dderror_);
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::checkValidity()                        --JKH
//  
// This method checks whether the DriveDevice object in in question is 
// valid--i.e, it was successfully constructed.
//
// ARGS:  none
//
// RETURN VALUES:  
//
// Returns an Error with one of the following values:
//
// error_None:                  object is valid.
// error_Empty_path_element:    Construction failed because the argument
//                              String to the constructor was invalid.
// error_File_not_found:        Construction failed; could not find specified
//                              device file.
// error_Capacity_found:        Construction failed; could not get numSectors_.
//
// PRE-CONDS:   none
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::Error
DriveDevice::checkValidity(void) const
{
    if(fd_ < 0) {
        return NSR::Error(dderror_);
    }
    return NSR::Error(NSR::error_None);
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::mediaType()                            --JKH
//
// Determines whether the current disk in the drive is WORM.
//
// NOTES:
//
// The method used to determine if a disk is WORM is to attempt to perform
// an erase of 0 sectors.  If the erase fails, we assume it is worm.  Note
// that for non-optical drives (e.g., a hard drive), this call will
// spuriously claim the disk is WORM.
//
// ARGUMENTS:  none
// RETURNS:
//
// Returns the type of the media (NSR::media_Rewritable or NSR::media_Worm)
//
// PRECONDITIONS:
//
// --The DriveDevice object was successfully constructed.
//
// POSTCONDITIONS:  see returns.
//-----------------------------------------------------------------------------

NSR::MediaType
DriveDevice::mediaType(void)
{
    NSR::Error          err;
    union sense_data    sense;

    // If the erase fails, it MIGHT be WORM.  We have to check the sense
    // data:  if it was an invalid opcode, then it was WORM.  Otherwise, it
    // was write-protected MO.

    if(erase(0,0,err)) {
        ioctl(fd_,SIOC_XSENSE,&sense);  // Not much we can do if it fails
        if(sense.r_sense2a.key == XSENSE_ILLEGAL_REQUEST) {
            return NSR::media_Worm;
        }
        else {
            return NSR::media_Rewritable;
        }
    }
    else {
        return NSR::media_Rewritable;
    }
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::verifyBlank()                          --JKH
//
// Verifies that the given sectors are blank (erased).
//
// ARGUMENTS:  
//
// UINT32  start     IN   Starting sector to verify
// UINT32  nsectors  IN   Number of sectors to verify
// UINT32 *where     OUT  If a written sector is encountered, this variable
//                        is filled in with the written sector address.
// RETURNS:
//
// Returns True if all of the sectors are blank, False otherwise.
//
// PRECONDITIONS:
//
// --The DriveDevice object was successfully constructed.
//
// POSTCONDITIONS:  see returns.
//-----------------------------------------------------------------------------

NSR::Boolean
DriveDevice::verifyBlank
(
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    NSR::UINT32 *where
)
{
    NSR::INT32          err;
    struct scsi_verify  verify;
    union sense_data    sense;

    verify.start_lba = start;
    verify.block_cnt = nsectors;

    err = ioctl(fd_,SIOC_VERIFY_BLANK,&verify);
    if(err) {                   // Determine which sector is written
        err = ioctl(fd_,SIOC_XSENSE,&sense);
        if(err) {               // Can't determine the problem!
            *where = 0;
            return NSR::False;
        }
        *where = (sense.r_sense2a.info[0] << 24) + 
                 (sense.r_sense2a.info[1] << 16) +
                 (sense.r_sense2a.info[2] << 8) +
                 (sense.r_sense2a.info[3]);
        return NSR::False;
    }

    return NSR::True;
}
    
//-----------------------------------------------------------------------------
//                               PRIVATE METHODS
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::isBlankCheck()                         --JKH
//
// This method queries the disk (using a SCSI request sense) to determine if 
// a blank check just occurred.
//
// ARGS:  none
//
// RETURNS:  
//
// Returns True if a blank check occurred, False otherwise.
//
// PRE-CONDS: 
//
// --The last operation performed by the object was a read()
//
// POST-CONDS:  see return values
//
// NOTES:
//
// This function uses the ioctl SIOC_XSENSE to perform a SCSI request sense,
// and uses the sense key to determine if a blank check just occurred.
//-----------------------------------------------------------------------------

NSR::Boolean
DriveDevice::isBlankCheck(void)
{
    NSR::INT32          err;
    union sense_data    sense;

    err = ioctl(fd_,SIOC_XSENSE,&sense);
    if(err) {           // If ioctl() fails, we're hosed:  return False
        return NSR::False;
    }
    if(sense.r_sense2a.key == SENSE_KEY_BLANK_CHECK) {
        return NSR::True;
    }

    return NSR::False;
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::setWWOE()                              --JKH
//
// This method sets the drive to the specified write-without-erase mode using
// the SIOC_WRITE_WOE ioctl.
//
// ARGS:  
//
// INT32 which          IN  If nonzero, sets write-without-erase on; otherwise
//                          turns it off.
//
// RETURNS:  none. 
//
// PRE-CONDS:  
//
// --the DriveDevice is valid (has valid file descriptor)
//
// POST-CONDS:  see above
//-----------------------------------------------------------------------------

void
DriveDevice::setWWOE
( 
    NSR::INT32 which
)
{
    NSR::INT32  flag;

    flag = (which ? 1 : 0);
    (void) ioctl(fd_,SIOC_WRITE_WOE,&flag);
}

//-----------------------------------------------------------------------------
// METHOD:  DriveDevice::setVMode()                             --JKH
//
// This method sets the drive to the specified verify mode using the 
// SIOC_VERIFY_WRITES ioctl.
//
// ARGS:  
//
// INT32 which          IN  If nonzero, sets verify on; otherwise turns it off.
//
// RETURNS:  0 on success, -1 on failure.
//
// PRE-CONDS:  
//
// --the DriveDevice is valid (has valid file descriptor)
//
// POST-CONDS:  see above
//-----------------------------------------------------------------------------

NSR::INT32
DriveDevice::setVMode
( 
    NSR::INT32 which
)
{
    NSR::INT32  flag;

    flag = (which ? 1 : 0);
    (void) ioctl(fd_,SIOC_VERIFY_WRITES,&flag);
}
