//-*-c++-*------------------------------------------------------------- 
// $Source: /projects/HMS/nsr/udfCore/devs/nsrRMSdv.cpp,v $
// $Revision: 1.20 $    $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 96/05/30 16:41:24 $

//      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.

// This contains the code for the AC device object.

// Some macros

#define HPUX_90X_700
#define S700_90X_DISK_RAW 47
#define IS_MAILSLOT(elt,addr)  (((elt) >= (addr).first_import_export) && \
                          ((elt) < ((addr).first_import_export + \
                                    (addr).num_import_exports)))

#define FIRST_TRANSPORT(addr)  (addr).first_transport
#define FIRST_STORAGE(addr)    (addr).first_storage
                         
#define MEDIUM_CHANGER_DEVICE           0x08

#include <nsrLib.h>
#include <nsrRMSdv.h>
#include <sys/stat.h>
#include "acptlib.h"

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::hpACDev                                    --JKH
//
// The constructor for the hpACDev object.
//
// ARGUMENTS:
//
// const String& devfile IN  Specifies the pathname of the device file
//                           corresponding to the autochanger's controller.
//
// RETURNS:  None.
//
// NOTE:  This class assumes that the autochanger is under the exclusive use
// of this object!  If there are other processes using the autochanger,
// the results will be unpredictible and probably catastrophic!
//
// PRECONDITIONS:
//
// --The pathname contained in the argument String devfile corresponds to a
//   valid (i.e., existing) autochanger 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 hpACDev object is
//   destructed) for read and write.
//
// ERRORS:
//
// error_None:  Operation completed successfully
// error_Empty_path_element:  NULL devfile argument
// error_File_not_found:  Device file passed as argument cannot be opened.
// error_No_free_store:  Cannot allocate memory for internal data structures.
//-----------------------------------------------------------------------------

hpACDev::hpACDev
( 
    const NSR::String& devfile 
) : fd_(-1), error_(NSR::error_None), sleepMode_(SLEEP_MODE_OFF), 
    senseLength_(0)
{
    NSR::INT32                  i;
    NSR::CONST_CHAR_PTR         cptr;
    struct element_status       eltstat;


    error_ = NSR::error_None;

    cptr = (NSR::CONST_CHAR_PTR) devfile;
    if(cptr == NULL) {
        error_ = NSR::error_Empty_path_element;
        return;
    }

    fd_ = open(cptr,O_RDWR);
    if(fd_ < 0) {
        error_ = NSR::error_File_not_found;
        return;
    }

    // Perform some test unit ready's to clear any unit attention type 
    // conditions (such as bus reset or poweron).

    if(testUnitReady(3)) {              // AC not ready
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_Autochanger_not_ready;
        return;
    }

    // Make sure that the autochanger is supported.  Since mode sense data
    // in particular is vendor-specific, we will only proceed with the 
    // construction if we're reasonably sure we're talking to an autochanger
    // we're familiar with (namely, an HP autochanger).

    if(!supportedAC()) {
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_Autochanger_not_supported;
        return;
    }

    // Set up internal data structures.  First we determine the number of
    // storage slots and drives so that we can create the mapping table and
    // the drive table.

    if(getEltAddr()) {          // Element addresses failed
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_Elt_addresses_failed;
        return;
    }

    // Verify that all transports are empty.  If any are full, we're stuck,
    // and abort the rest of the construction.

    for(i = 0; i < eltaddr_.num_transports; i++) {
        if(getEltStatus(eltaddr_.first_transport + i,&eltstat)) {
            close(fd_);
            fd_ = -1;
            error_ = NSR::error_Element_status_failed;
            return;
        }
        if(eltstat.full) {
            close(fd_);
            fd_ = -1;
            error_ = NSR::error_Transport_element_full;
            return;
        }
    }

    // Allocate memory for the maptable, drivetable, and bitmap.

    mapTable_.resize(eltaddr_.num_storages*2);
    if(mapTable_.size_ < eltaddr_.num_storages*2) {
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_No_free_store;
        return;
    }

    driveTable_.resize(eltaddr_.num_data_transfers);
    if(driveTable_.size_ < eltaddr_.num_data_transfers) {
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_No_free_store;
        return;
    }

    // Now that we have the drivetable, initialize the entries.  We need to
    // put away any cartridges that may already be in drives, or spare drives
    // that have cartridges with invalid sources.

    for(i = 0; i < eltaddr_.num_data_transfers; i++) {
        if(getEltStatus(eltaddr_.first_data_transfer + i,&eltstat)) {
            close(fd_);
            error_ = NSR::error_Element_status_failed;
            return;
        }
        driveTable_.table_[i].tgt_ = eltstat.bus_address;
        driveTable_.table_[i].lun_ = eltstat.lun;
        driveTable_.table_[i].element_ = eltstat.element;

        // Perform drive sparing, if necessary.  If drive is full with valid
        // non-mailslot source, put the surface away; if it's full but its 
        // source is the mailslot, or otherwise invalid, mark the drive as
        // permanently unavailable.

        if(eltstat.full) {
            if(eltstat.source_valid && 
               !IS_MAILSLOT(eltstat.source_element,eltaddr_)) {
                if(moveMedium(0,
                              eltstat.element,
                              eltstat.source_element,
                              eltstat.invert)) {
                    close(fd_);
                    fd_ = -1;
                    error_ = NSR::error_Move_failed;
                    return;
                }
            }
            else {              // Need to spare the drive
                driveTable_.table_[i].sid_ = SID_T_SPARED;
            }
        }
    }

    // Check to make sure we have at least one non-spared drive.  If we
    // don't, we're in an error state.

    for(i = 0; i < eltaddr_.num_data_transfers; i++) {
        if(driveTable_.table_[i].sid_ != SID_T_SPARED) {
            break;
        }
    }

    if(i >= eltaddr_.num_data_transfers) {
        error_ = NSR::error_All_drives_spared;
    }
    if(bitmap_.resize(2*eltaddr_.num_storages)) {
        close(fd_);
        fd_ = -1;
        error_ = NSR::error_No_free_store;
    }
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::~hpACDev                                   --JKH
//
// The destructor for the hpACDev object.
//
// ARGUMENTS:  None.
// PRECONDITIONS:  None.
// POSTCONDITIONS: 
//
// --The file descriptor corresponding to the autochanger device bound to this
//   object, if open, will be closed.
// --Any surfaces still in drives, allocated or not, will be put away to their
//   home slots.
//
// NOTES:  It is expected that when the hpACDev goes out of scope, the user
// will have freed all drives allocated by the object.  This method does NOT
// automatically free the SSDevice objects created by allocDrive().
//
// ERRORS:  None.
//-----------------------------------------------------------------------------

hpACDev::~hpACDev( void )
{
    NSR::INT32   i;
    ACMapEntry   *mptr;
    ACDriveEntry *dptr;

    if(fd_ >= 0) {

        // Go through the drive table and put away anything that's still in
        // a drive, allocated or not.

        for(i = 0; i < driveTable_.size_; i++) {
            dptr = &(driveTable_.table_[i]);
            if(dptr->sid_ != SID_T_INVALID) {   // A surface is in the drive
                mptr = mapTable_.lookup(dptr->sid_);
                if(mptr == NULL) {              // Shouldn't happen
                    continue;
                }
                (void) moveMedium(FIRST_TRANSPORT(eltaddr_),
                                  dptr->element_,
                                  FIRST_STORAGE(eltaddr_) + mptr->slot_ - 1,
                                  mptr->side_);
            }
        }
        (void) close(fd_);
    }

}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::numSectors                                 --JKH
//
// Returns the number of sectors on the specified surface.
//
// ARGUMENTS:
//
// sid_t sid  IN  surface identifier
//
// RETURNS:
//
// Returns the number of sectors on the specified surface.  Returns 0 if the
// argument sid is invalid.
//
// PRECONDITIONS:
//
// --The hpACDev was successfully constructed.
//
// POSTCONDITIONS:  see return values.
//
// ERRORS:  
//
// error_None:  Operation completed successfully.
//-----------------------------------------------------------------------------

NSR::UINT32
hpACDev::numSectors( NSR::sid_t sid ) 
{
    ACMapEntry  *mptr;
    NSR::SSDevice *ddevp;

    if( fd_ < 0) {                      // Not properly constructed
        return 0;
    }

    error_ = NSR::error_None;

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {
        error_ = NSR::error_Invalid_sid;
        return 0;
    }

    // If the numSectors_ is 0, we have to go get the number of sectors.

    if(mptr->numSectors_ > 0) {
        return mptr->numSectors_;
    }

    ddevp = (NSR::SSDevice *) allocDrive(sid);
    if(ddevp == NULL) {
        return 0;
    }
    mptr->numSectors_ = ddevp->numSectors();
    mptr->sectorSize_ = ddevp->sectorSize();

    (void) freeDrive(ddevp);
    return mptr->numSectors_;
}
    
//-----------------------------------------------------------------------------
// METHOD:  hpACDev::sectorSize                                 --JKH
//
// Returns the size, in bytes, of the specified surface.
//
// ARGUMENTS:
//
// sid_t sid  IN  surface identifier
//
// RETURNS:
//
// Returns the size, in bytes, of a sector of the specified surface.  Returns
// 0 if the argument sid is invalid.
//
// PRECONDITIONS:
//
// --The hpACDev was successfully constructed.
//
// POSTCONDITIONS:  see return values
//
// ERRORS:  see return values.
//
//-----------------------------------------------------------------------------

NSR::UINT32
hpACDev::sectorSize( NSR::sid_t sid ) 
{
    ACMapEntry  *mptr;
    NSR::SSDevice *ddevp;

    if( fd_ < 0) {                      // Not properly constructed
        return 0;
    }

    error_ = NSR::error_None;

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {
        error_ = NSR::error_Invalid_sid;
        return 0;
    }

    // If the sectorSize_ is 0, we have to go get the sector size.

    if(mptr->sectorSize_ > 0) {
        return mptr->sectorSize_;
    }

    ddevp = (NSR::SSDevice *) allocDrive(sid);
    if(ddevp == NULL) {
        return 0;
    }

    mptr->sectorSize_ = ddevp->sectorSize();
    mptr->numSectors_ = ddevp->numSectors();

    (void) freeDrive(ddevp);
    return mptr->sectorSize_;
}

//----------------------------------------------------------------------------
// METHOD:  hpACDev::read                                       --JKH
//
// Reads the specified number of sectors into the buffer barr from the argument
// surface sid.  Buffering will be performed if the request length is not
// equal to a sector multiple.
//
// ARGUMENTS:
//
// sid_t sid           IN      Surface identifier
// UINT32 startsector  IN      Starting sector for the read
// UINT32 nbytes       IN      Number of bytes to read
// BYTE * buf          IN/OUT  Buffer for read data
//
// RETURNS:
//
// 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
// condition occurred, a "short count" will be returned.
//
// PRE-CONDS:
//
// --The hpACDev object was successfully constructed
// --The argument sid corresponds to surface within the autochanger
// --The start sector/nbytes parameters are within physical media boundaries
//
// POST-CONDS:
//
// --The number of bytes specified by the nbytes parameter will be read from
//   disk and stored in the user's buffer.
//
// ERRORS:
//
// error_None:  function completed successfully
// error_Invalid_sid:  Invalid sid
// 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_Drive_read:  Generic I/O failure on read
//----------------------------------------------------------------------------

NSR::INT32
hpACDev::read
( 
    NSR::sid_t sid,
    NSR::UINT32 start,
    NSR::UINT32 nbytes,
    NSR::BYTE *buf 
)
{
    ACDriveEntry *dptr;
    NSR::SSDevice  *ddevp;
    NSR::INT32   rval;
    NSR::Error   err;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;
    
    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the read will fail as well.

    if(!this->numSectors(sid)) {
        return -1;
    }

    // Put surface in drive

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid or drive not available
        return -1;
    }

    // At this point, we should have a valid SSDevice.  Pass the read down.

    rval = ddevp->read(start,nbytes,buf,err);
    (void) freeDrive(ddevp);  // Have to throw away the error value. . .
    error_ = err;
    return rval;
}

//----------------------------------------------------------------------------
// METHOD:  hpACDev::write                                      --JKH
//
// Writes the specified number of bytes from the buffer barr to argument
// surface and starting sector.
//
// ARGS:
//
// sid_t sid           IN  Surface identifier
// UINT32 startsector  IN  Starting sector for write
// UINT32 nbytes       IN  Number of bytes to write
// BYTE * buf          IN  Buffer of data to write
//
// 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 condition occurred,
// a "short count" will be returned.  NOTE:  if the hpACDev is in verify
// mode, and the hpACDev 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 hpACDev object was successfully constructed and contains media
// --The start parameter is in the range [0,number sectors)
// --The device is writable (e.g., not write-protected, and if WORM, the
//   sectors are unwritten.
//
// POST-CONDS:
//
// --The number of bytes specified by nbytes will be written to disk.
//
// 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_Drive_write:  Generic I/O failure on write
// error_Set_verify_failed:  Cannot set verify mode.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::write
( 
    NSR::sid_t sid,
    NSR::UINT32 start,
    NSR::UINT32 nbytes,
    NSR::BYTE *buf 
)
{
    ACMapEntry   *mptr;
    ACDriveEntry *dptr;
    NSR::SSDevice  *ddevp;
    NSR::INT32   rval;
    NSR::Error   err;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;

    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the write will fail as well.

    if(!this->numSectors(sid)) {
        return -1;
    }

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid or no drive available.
        return -1;
    }

    // At this point, we should have a valid SSDevice.  Pass the write down,
    // putting the drive in verify mode if necessary.

    mptr = mapTable_.lookup(sid);
    (void) ddevp->verifyMode(mptr->verifyMode_);

    rval = ddevp->write(start,nbytes,buf,err);
    (void) freeDrive(ddevp);  // Have to throw away the error value. . .
    error_ = err;
    return rval;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::writeWOE                                   --JKH
//
// This method writes the specified sectors to disk from the buffer using
// write-without-erase mode.
//
// 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.
//
// NOTE:  if the underlying drive cannot be placed into write-without-erase
// mode, the write will be attempted in write-with-erase mode and no special
// error will be flagged.
//
// ARGS:
//
// sid_t              IN  surface identifier
// UINT32 startsector IN  starting sector for the write
// UINT32 nbytes      IN  number of bytes to write
// BYTE *barr         IN  buffer to supply data for the write
//
// 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 condition occurred,
// a "short count" will be returned.  If the SSDevice 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 hpACDev object was successfully constructed.
// --The startsector and nbytes parameters specify a valid address range
// --The device is writable (e.g., not write-protected and, if WORM, the
//   sectors are unwritten.
// --The sectors to be written are erased.
//
// POST-CONDS:
//
// --The specified bytes will be written to disk.
//
// 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_Drive_write:  Generic I/O failure on write
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::writeWOE
( 
    NSR::sid_t sid,
    NSR::UINT32 startsector,
    NSR::UINT32 nbytes,
    NSR::BYTE *buf 
)
{
    ACMapEntry          *mptr;
    ACDriveEntry        *dptr;
    NSR::SSDevice               *ddevp;
    NSR::INT32          rval;
    NSR::Error          err;

    if( fd_ < -1) {                     // Not properly constructed
        return 0;
    }

    error_ = NSR::error_None;

    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the write will fail as well.

    if(!this->numSectors(sid)) {
        return -1;
    }

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid
        return -1;
    }

    // At this point, we should have a valid SSDevice.  Pass the write down,
    // putting the drive in verify mode if necessary.

    mptr = mapTable_.lookup(sid);
    (void) ddevp->verifyMode(mptr->verifyMode_);

    rval = ddevp->writeWOE(startsector,nbytes,buf,err);
    (void) freeDrive((NSR::SSDevice *) ddevp);  
    error_ = err;
    return rval;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::erase                                      --JKH
//
// Erases the specified sectors.
//
// ARGS:
//
// sid_t          sid  IN  Surface identifier
// UINT32 startsector  IN  Starting sector for erase
// UINT32 numsectors   IN  Number of sectors to erase
//
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --The hpACDev object was successfully created and contains media
// --The executing process is the only process that has the drive open
// --The arguments specify a range of sectors physically accessible on the
//   media
// --The media is erasable (e.g., not a hard disk, not WORM)
//
// POST-CONDS:
//
// --The specified sectors will be erased.
//
// ERRORS:
//
// error_None            Function completed successfully.
// error_Erase_failed    The erase failed due to an I/O error, or inability
//                       to put the drive in exclusive mode.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::erase
( 
    NSR::sid_t sid,
    NSR::UINT32 startsector,
    NSR::UINT32 numsectors 
)
{
    ACDriveEntry        *dptr;
    NSR::SSDevice       *ddevp;
    NSR::INT32          rval;
    NSR::Error          err;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;

    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the erase will fail as well.

    if(!this->numSectors(sid)) {
        return -1;
    }

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid
        return -1;
    }

    // At this point, we should have a valid SSDevice.  Pass the erase down.

    rval = ddevp->erase(startsector,numsectors,err);
    (void) freeDrive((NSR::SSDevice *) ddevp);  
    error_ = err;
    return rval;
}

//----------------------------------------------------------------------------
// METHOD:  hpACDev::mediaType                                  --JKH
//
// Determines whether the specified surface is WORM media.
//
// ARGUMENTS:  None.
//
// RETURNS:
//
// Returns the type of the media (NSR::media_Rewritable or NSR::media_Worm)
//
// PRE-CONDS:
//
// --The hpACDev object was successfully constructed
// --The argument sid corresponds to surface within the autochanger
//
// POST-CONDS:  See Returns.
//----------------------------------------------------------------------------

NSR::MediaType
hpACDev::mediaType
( 
    NSR::sid_t sid 
)
{
    ACDriveEntry *      dptr;
    NSR::SSDevice  *    ddevp;
    NSR::MediaType       rval;
    NSR::Error          err;

    if( fd_ < 0) {                      // Not properly constructed
        return NSR::media_Rewritable;
    }

    error_ = NSR::error_None;
    
    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return NSR::media_Rewritable;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the read will fail as well.

    if(!this->numSectors(sid)) {
        return NSR::media_Rewritable;
    }

    // Put surface in drive

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid or drive not available
        return NSR::media_Rewritable;
    }

    // At this point, we should have a valid SSDevice.  Pass the call down.

    rval = ddevp->mediaType();
    (void) freeDrive(ddevp);  // Have to throw away the error value. . .
    return rval;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::verifyBlank()                          --JKH
//
// Verifies that the given sectors are blank (erased).
//
// ARGUMENTS:
//
// sid_t   sid       IN   Surface identifier
// 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 hpACDev object was successfully constructed.
//
// POSTCONDITIONS:  see returns.
//-----------------------------------------------------------------------------

NSR::Boolean
hpACDev::verifyBlank
(
    NSR::sid_t sid,
    NSR::UINT32 start,
    NSR::UINT32 nsectors,
    NSR::UINT32 *where
)
{
    ACDriveEntry *      dptr;
    NSR::SSDevice  *    ddevp;
    NSR::Boolean         rval;
    NSR::Error          err;

    if( fd_ < 0) {                      // Not properly constructed
        return NSR::False;
    }

    error_ = NSR::error_None;
    
    if(sid >= 2*eltaddr_.num_storages) {
        error_ = NSR::error_Invalid_sid;
        return NSR::False;
    }

    // For performance reasons, perform a numSectors call().  This will
    // cache number of sectors information as well as sectorSize information.
    // numSectors() will change error_, but since at this point it should
    // be error_None anyway, it's okay.  If the numSectors call fails, we
    // abort here and now, since chances are good the read will fail as well.

    if(!this->numSectors(sid)) {
        return NSR::False;
    }

    // Put surface in drive

    ddevp = (NSR::SSDevice *) allocDrive(sid);

    if(ddevp == NULL) {                 // Bad sid or drive not available
        return NSR::False;
    }

    // At this point, we should have a valid SSDevice.  Pass the call down.

    rval = ddevp->verifyBlank(start,nsectors,where);
    (void) freeDrive(ddevp);  // Have to throw away the error value. . .
    return rval;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::verifyMode                                 --JKH
//
// Sets the verify mode of the specified surface.  A mode of 0 indicates
// write-without-verify (the default); a nonzero value indiicates all
// subsequent writes to that surface will be performed with verify.
//
// ARGS:
//
// sid_t sid      IN  Surface identifier
// INT32 newmode  IN  Mode flag (see above description)
//
// RETURNS:
//
// Returns the previous value of the verify mode for that surface.  If an
// invalid sid_t was specified, the function returns -1.
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
//
// POST-CONDS:
//
// --See above
//
// ERRORS:
//
// error_None:  Function completed successfully
// error_Invalid_sid:  Invalid sid specified
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::verifyMode( NSR::sid_t sid,NSR::INT32 newmode )
{
    ACMapEntry  *mptr;
    NSR::INT32  oldmode;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }

    oldmode = mptr->verifyMode_;
    mptr->verifyMode_ = (newmode ? VERIFY_MODE_ON : VERIFY_MODE_OFF);
    return oldmode;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::verifyMode                                 --JKH
//
// Returns the verify mode of the specified surface.  A mode of 0 indicates
// write-without-verify (the default); a nonzero value indiicates all
// subsequent writes to that surface will be performed with verify.
//
// ARGS:
//
// sid_t sid      IN  Surface identifier
//
// RETURNS:
//
// Returns the current value of the verify mode for that surface.  If an
// invalid sid_t was specified, the function returns -1.
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
//
// POST-CONDS:
//
// --See above
//
// ERRORS:
//
// error_None:  Function completed successfully
// error_Invalid_sid:  Invalid sid specified
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::verifyMode( NSR::sid_t sid ) 
{
    ACMapEntry  *mptr;
    NSR::INT32  oldmode;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {
        error_ = NSR::error_Invalid_sid;
        return -1;
    }
    return mptr->verifyMode_;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::allocDrive                                 --JKH
//
// Allocates an SSDevice object for the specified surface.  This allocation
// implies that I/O may be performed to the surface through the returned
// SSDevice object pointer.  Generally this requires that the surface be
// placed in a drive and kept there until the resource is freed, which
// consequently may cause that drive to be unavailable for I/O for the
// allocating hpACDev object.
//
// NOTE:  The user must NOT delete the returned SSDevice object!  When the
// user no longer needs it, the user must call freeDrive().
//
// The algorithm used here is that when a drive is allocated, the requested
// surface is moved into a drive (if it is not already in one).  When
// the drive is freed, the SSDevice is deleted but the surface is left
// in the drive, to prevent unnecessary swaps between read calls, for instance,
// to the same surface.
//
// ARGS:
//
// sid_t sid        IN  Surface identifier
//
// RETURNS:
//
// Upon success, returns a pointer to an SSDevice object.  On failure (no
// drives available, or an invalid sid_t), returns NULL.
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
//
// POST-CONDS:  See above
//-----------------------------------------------------------------------------

NSR::SSDevice *
hpACDev::allocDrive(NSR::sid_t sid)
{
    NSR::INT32          i;
    ACMapEntry          *mptr;
    ACMapEntry          *mptr2;
    ACDriveEntry        *dptr;
    NSR::SSDevice       *ddevp;
    NSR::Error          err;

    if(fd_ < 0) {
        return NULL;
    }

    error_ = NSR::error_None;

    // Verify that the argument sid is valid

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {
        error_ = NSR::error_Invalid_sid;
        return NULL;
    }

    dptr = driveTable_.lookup(sid);

    // At this point, we want to get the desired surface into a drive.  If
    // it is already there and is allocated, we just return; if it's not in
    // a drive, we do a move or exchange to get it into a drive.

    if(dptr == NULL) {
        dptr = driveTable_.findFree();
        if(dptr == NULL) {              // No drives are available!
            if(sleepMode_ == SLEEP_MODE_ON) {
                while((dptr = driveTable_.findFree()) == NULL) {
                    sleep(1);
                }
            }
            else {
                error_ = NSR::error_Drive_not_available;
                return NULL;
            }
        }
        // We have to move the desired sid.  We might have to do an exchange.

        if(dptr->sid_ == SID_T_INVALID) {               // Just do a move
            if(moveMedium(FIRST_TRANSPORT(eltaddr_),
                          FIRST_STORAGE(eltaddr_) + mptr->slot_ - 1,
                          dptr->element_,
                          mptr->side_)) {
                error_ = NSR::error_Move_failed;
                return NULL;
            }
        }
        else {                                          // Do an exchange
            mptr2 = mapTable_.lookup(dptr->sid_);

            // We have to make sure that the surfaces being exchanged aren't
            // merely sides A and B of the same cartridge.  If they are,
            // we just do a move and flip the cartridge in the drive.

            if(mptr->slot_ == mptr2->slot_) {
                if(moveMedium(FIRST_TRANSPORT(eltaddr_),         
                              dptr->element_, 
                              dptr->element_,
                              1)) {
                    error_ = NSR::error_Move_failed;
                    return NULL;
                }
            }
            else {
                if(exchangeMedia(FIRST_TRANSPORT(eltaddr_),
                                 FIRST_STORAGE(eltaddr_) + mptr->slot_ - 1,
                                 dptr->element_,
                                 FIRST_STORAGE(eltaddr_) + mptr2->slot_ - 1,
                                 mptr->side_,
                                 mptr2->side_))
                {
                    error_ = NSR::error_Exchange_failed;
                    return NULL;
                }
            }
        }
    }
    else {
        if(dptr->drivep_ != NULL) {     // Sid already allocated a drive
            return dptr->drivep_;
        }
    }

    // Now create a drive device.  This gets messy:  if the drive table
    // entry already has a device file, well and good; but if doesn't, we're
    // forced to create one in /tmp, and the mknod() will fail if we're not
    // root.  Yuck.  

    if(dptr->devfile_ != NULL) {                // Good!
        ddevp = new DriveDevice(*(dptr->devfile_));
    }
    else {                                      // Not so good.
        ddevp = (NSR::SSDevice *) makeDriveDevice(dptr,err);
    }
    if(ddevp == NULL) {
        error_ = err;

        // Put the surface away.  We throw away any failures that might
        // occur from the moveMedium because it's more important to report
        // the fact that we can't make a drive device.

        (void) moveMedium(FIRST_TRANSPORT(eltaddr_),
                          dptr->element_,
                          FIRST_STORAGE(eltaddr_) + mptr->slot_ - 1,
                          mptr->side_);
        return NULL;
    }
    if(ddevp->error() != (NSR::Error) NSR::error_None) {
        error_ = ddevp->error();
        return NULL;
    }

    // If we got here, all is well.  Update the drive table entry.

    dptr->sid_ = sid;
    dptr->drivep_ = ddevp;
    return (NSR::SSDevice *) ddevp;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::freeDrive                                  --JKH
//
// Frees a drive allocated by a previous call to allocDrive().
//
// ARGS:
//
// SSDevice *drive  IN  Drive to be freed.
//
// RETURN VALUES:
//
// Returns 0 on success, -1 on failure (invalid drive pointer)
//
// PRE-CONDS:
//
// --The argument SSDevice was allocated by a call to allocDrive()
//
// POST-CONDS:
//
// --The drive resource will be freed and made available for further allocation.
// --This method does NOT move the surface back to its home slot.
//
// ERRORS:
//
// error_None:  Function completed successfully
// error_Null_pointer:  Null pointer passed as argument
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::freeDrive(NSR::SSDevice *drivep)
{
    NSR::INT32          i;
    ACDriveEntry        *dptr;
    NSR::SSDevice       *ddevp;

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

    error_ = NSR::error_None;

    if(drivep == NULL) {
        error_ = NSR::error_Null_pointer;
        return -1;
    }

    // Find the appropriate entry in the drive table.

    for(i = 0; i < driveTable_.size_; i++) {
        dptr = &(driveTable_.table_[i]);
        if(dptr->drivep_ == drivep) {           // Found it
            ddevp = (NSR::SSDevice *) drivep;
            delete ddevp;
            dptr->drivep_ = NULL;
            return 0;
        }
    }

    // If we got here, we didn't find it.

    error_ = NSR::error_Argument_not_found;
    return -1;
}
            
//-----------------------------------------------------------------------------
// The following methods are "optional" methods--their interfaces are not
// specified by the virtual base class MSDevice, and hence are specific to
// the HP-UX implementation of the hpACDevice object.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::addSurface                                 --JKH
//
// Creates a mapping between an sid_t and specified surface given by slot
// position in the autochanger and side.
//
// ARGS:
//
// INT32 slot    IN  Which slot in the autochanger the cartridge resides in.
//                   Slots are numbered from 1 to increasing values.
// INT32 side    IN  Which side of the cartridge.  0 = A side, 1 = B side.
//
// RETURNS:
//
// Returns an allocated sid_t on success.  If the specified surface device
// file does not exist or is otherwise invalid, the call returns and sid_t
// value SID_T_INVALID.
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
// --The specified slot in the autochanger is occupied.
//
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::sid_t
hpACDev::addSurface
( 
    NSR::UINT32 slot,
    NSR::UINT32 side 
)
{
    NSR::INT32            i;
    struct element_status eltstat;
    ACMapEntry            *mptr;
    ACDriveEntry          *dptr;
    NSR::sid_t            sid;

    if(fd_ < 0) {               // Not properly constructed
        return SID_T_INVALID;
    }
    error_ = NSR::error_None;

    // Check to make sure the (slot,side) doesn't already have an sid_t 
    // allocated.  If it does, just return it.

    for(i = 0; i < mapTable_.size_; i++) {
        mptr = &(mapTable_.table_[i]);
        if((mptr->slot_ == slot) && (mptr->side_ == side) && 
           (mptr->sid_ != SID_T_INVALID)) {
            return mptr->sid_;
        }
    }

    // Verify that the given slot is occupied.

    if(getEltStatus(FIRST_STORAGE(eltaddr_) + slot - 1,&eltstat)) {
        error_ = NSR::error_Element_status_failed;
        return SID_T_INVALID;
    }

    if(!eltstat.full) {

        // Make sure that the cartridge in question isn't actually in a
        // drive.

        NSR::INT32      indrive = 0;

        for(i = 0; i < driveTable_.size_; i++) {
            dptr = &(driveTable_.table_[i]);
            if((dptr->sid_ == SID_T_INVALID) || (dptr->sid_ == SID_T_SPARED)) {
                continue;
            }
            mptr = &(mapTable_.table_[dptr->sid_]);
            if(mptr->slot_ == slot) {
                indrive = 1;
                break;
            }
        }
        if(!indrive) {
            error_ = NSR::error_Element_status_failed;
            return SID_T_INVALID;
        }
    }

    // Allocate an sid for it.  

    sid = (sid_t) bitmap_.findClearRange(1);
    bitmap_.setBit(sid);

    // Fill in the entry in the map table.

    mptr = &(mapTable_.table_[sid]);
    mptr->sid_ = sid;
    mptr->slot_ = slot;
    mptr->side_ = side;

    // We're done; return.

    return sid;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::removeSurface                              --JKH
//
// Removes a mapping between an sid_t and (slot,side) surface specification.
//
// ARGS:
//
// sid_t sid  IN  Surface identifier to be removed from the internal mapping
//
// RETURNS:
//
// returns 0 on success, -1 on failure (invalid sid_t)
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
// --The sid_t was allocated by a previous call to addSurface().
//
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::removeSurface
( 
    NSR::sid_t sid 
)
{
    ACMapEntry   *mptr;
    ACDriveEntry *dptr;

    if(fd_ < 0) {               // Not properly constructed
        return -1;
    }
    error_ = NSR::error_None;

    mptr = mapTable_.lookup(sid);
    if(mptr == NULL) {          // Not in the table
        error_ = NSR::error_Argument_not_found;
        return -1;
    }

    // If the surface whose sid we're freeing is in a drive, we have to
    // put it away.  If the drive is allocated, we fail the call entirely.

    dptr = driveTable_.lookup(sid);
    if(dptr != NULL) {          // Have to worry about it. . .
        if(dptr->drivep_ != NULL) {             // Drive is allocated!
            error_ = NSR::error_Surface_in_use;
            return -1;
        }
        else {                  // Put the surface away.
            if(moveMedium(FIRST_TRANSPORT(eltaddr_),
                          dptr->element_,
                          FIRST_STORAGE(eltaddr_) + mptr->slot_ - 1,
                          mptr->side_)) {
                error_ = NSR::error_Move_failed;
            }
            dptr->sid_ = SID_T_INVALID;
        }
    }

    // Clear the entry in the table.

    mptr->sid_ = SID_T_INVALID;
    mptr->slot_ = 0;
    mptr->side_ = 0;
    mptr->numSectors_ = 0;
    mptr->sectorSize_ = 0;
    mptr->verifyMode_ = 0;

    // Free its entry in the bitmap

    bitmap_.clearBit(sid);

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::sleepMode                                  --JKH
//
// Sets the sleep mode for the autochanger.  If nonzero, sleeping is allowed
// on any operation that requires a drive resource.  The hpACDev will go into
// a polling loop to wait for a drive resource.  If zero, the default, any
// operation that requires a drive resource that cannot be allocated one will
// fail immediately with an error_Drive_not_available.
//
// ARGS:
//
// INT32 newmode  IN  Mode flag (see above description)
//
// RETURNS:
//
// Returns the previous value of the sleep mode.  
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
//
// POST-CONDS:
//
// --See above
//
// ERRORS:
//
// error_None:  Function completed successfully
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::sleepMode
( 
    NSR::INT32 newmode 
)
{
    NSR::INT32  oldmode;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    error_ = NSR::error_None;

    oldmode = sleepMode_;
    sleepMode_ = (newmode ? SLEEP_MODE_ON : SLEEP_MODE_OFF);
    return oldmode;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::sleepMode                                  --JKH
//
// Returns the sleep mode for the autochanger.  If nonzero, sleeping is allowed
// on any operation that requires a drive resource.  The hpACDev will go into
// a polling loop to wait for a drive resource.  If zero, the default, any
// operation that requires a drive resource that cannot be allocated one will
// fail immediately with an error_Drive_not_available.
//
// ARGS:  None
//
// RETURNS:
//
// Returns the previous value of the sleep mode.  
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
//
// POST-CONDS:
//
// --See above
//
// ERRORS:
//
// error_None:  Function completed successfully
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::sleepMode( void ) const
{
    NSR::INT32  oldmode;

    if( fd_ < 0) {                      // Not properly constructed
        return -1;
    }

    return sleepMode_;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::addDriveMap                                --JKH
//
// Creates a mapping between a drive device file and its hardware address.
//
// ARGS:
//
// INT32 id       IN  SCSI target ID of the drive
// INT32 lun      IN  SCSI LUN ID of the drive
// const String&  IN  Pathname for the disk character device file
//
// RETURNS:
//
// 0 on success, -1 on failure (invalid hardware address specified.)
//
// PRE-CONDS:
//
// --The hpACDev was successfully constructed.
// --The specified drive is in the autochanger.
//
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::addDriveMap
( 
    NSR::INT32 id,
    NSR::INT32 lun, 
    const NSR::String& devfile 
)
{
    NSR::INT32          i;
    ACDriveEntry        *dptr;
    NSR::CONST_CHAR_PTR cptr;

    if(fd_ < 0) {               // Not properly constructed
        return SID_T_INVALID;
    }
    error_ = NSR::error_None;

    cptr = (NSR::CONST_CHAR_PTR) devfile;
    if(cptr == NULL) {
        error_ = NSR::error_Null_pointer;
        return -1;
    }

    // Verify that the given drive is in fact in the autochanger and is 
    // usable (not spared).

    for(i = 0; i < driveTable_.size_; i++) {
        dptr = &(driveTable_.table_[i]);
        if((dptr->tgt_ == id) && (dptr->lun_ == lun)) {  // Found it, good
            break;
        }
    }
    if(i >= driveTable_.size_) {
        error_ = NSR::error_Argument_not_found;
        return -1;
    }

    // Fill in the drive table entry will the device file.  If there's
    // already one there, delete it.

    if(dptr->devfile_ != NULL) {
        delete dptr->devfile_;
    }
    dptr->rmflag_ = 0;
    dptr->devfile_ = new NSR::String(cptr);
    if(dptr->devfile_ == NULL) {
        error_ = NSR::error_No_free_store;
        return -1;
    }

    // We're done; return.

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::elementAddresses
//
// Returns element addresses (mode sense) information.
//
// ARGS:
//
// struct element_addresses& el  OUT   Struct to hold element addresses info.
//
// RETURNS:
//
// Returns 0 on success, -1 on failure
//
// PRE-CONDS:
//
// --The autochanger was successfully constructed
//
// POST-CONDS:  See return values
//-----------------------------------------------------------------------------

NSR::INT32 
hpACDev::elementAddresses( struct element_addresses& eltaddr )
{
    if(fd_ < 0) {
        return -1;
    }
    memcpy(&eltaddr,&eltaddr_,sizeof(struct element_addresses));
    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::elementStatus
//
// Returns status information about an element in the autochanger
//
// ARGS:
//
// INT32  element                  IN   Element to get status for
// struct element_status& eltstat  OUT  Status information
//
// RETURNS:
//
// Returns 0 on success, -1 on failure
//
// PRE-CONDS:
//
// --The autochanger was successfully constructed
//
// POST-CONDS:  See return values
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::elementStatus
( 
    NSR::INT32 el,
    struct element_status& eltstat 
)
{
    if(fd_ < 0) {
        return -1;
    }
    return getEltStatus(el,&eltstat);
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::senseData
//
// Returns the sense data from the last SCSI command if any.
//
// ARGS:
//
// struct sense_2_aligned& sense  OUT  Struct for the sense data
//
// RETURNS:
//
// Returns the number of bytes of sense data available.  If 0, the last SCSI
// command executed succeeed.
//
// PRE-CONDS:
//
// --The autochanger was successfully constructed.
//
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

NSR::UINT32
hpACDev::senseData
( 
    struct sense_2_aligned& sense 
)
{
    if(fd_ < 0) {
        return 0;
    }
    if(senseLength_ != 0) {
        memcpy(&sense,&senseData_,senseLength_);
    }
    return senseLength_;
}

#ifdef DEBUG

void
hpACDev::print
( 
    NSR::UINT32 indent,
    const NSR::String& str 
)
{
    NSR::INT32          i;
    ACMapEntry          *mptr;
    ACDriveEntry        *dptr;

    printf("%s\n",(NSR::CONST_CHAR_PTR) str);
    printf("File descriptor:  %d\n",fd_);
    printf("Current error:  %s\n",error_.message());
    printf("Map Table:\n");
    printf("sid\tslot\tside\tnsect\tssize\tmode\n");
    for(i = 0; i < mapTable_.size_; i++) {
        mptr = &(mapTable_.table_[i]);
        if(mptr->sid_ != SID_T_INVALID) {
            printf("%d\t%d\t%d\t%d\t%d\t%d\n",mptr->sid_,mptr->slot_,
                   mptr->side_,mptr->numSectors_, mptr->sectorSize_,
                   mptr->verifyMode_);
        }
    }
    printf("Drive Table:\n");
    printf("sid\tdrivep  \ttgt\tlun\telement\tdevfile\n"); 
    for(i = 0; i < driveTable_.size_; i++) {
        dptr = &(driveTable_.table_[i]);
        printf("%d\t0x%08x\t%d\t%d\t%d\t%s\n",
               dptr->sid_,(NSR::UINT32) dptr->drivep_,dptr->tgt_,dptr->lun_,
               dptr->element_,(NSR::CHAR_PTR) *(dptr->devfile_));
    }
    printf("Bitmap:  \n");
    bitmap_.print(0,"",bitmap_.numBits());
    printf("sleepMode:  %d\n",sleepMode_);
    printf("First Transport:  %d\n",eltaddr_.first_transport);
    printf("Num Transports:   %d\n",eltaddr_.num_transports);
    printf("First Storage:    %d\n",eltaddr_.first_storage);
    printf("Num Storages:     %d\n",eltaddr_.num_storages);
    printf("First Drive:      %d\n",eltaddr_.first_data_transfer);
    printf("Num Drives:       %d\n",eltaddr_.num_data_transfers);
    printf("First Mailslot:   %d\n",eltaddr_.first_import_export);
    printf("Num Mailslots:    %d\n",eltaddr_.num_import_exports);
}

#endif

//-----------------------------------------------------------------------------
// The following methods are private methods.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::acCommand                                  --JKH
//
// Sends a command to the autochanger using the passthru driver.
//
// ARGS:  
//
// struct sctl_io *sctl_io  IN  Pointer to a formatted sctl_io struct to
//                              specify the passthru command
//
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
// ERRORS:
//
// error_None:  Function completed successfully.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::acCommand
( 
    struct sctl_io *sctl_io 
)
{
    if(ioctl(fd_,SIOC_IO,sctl_io)) {
        return -1;
    }

    // Check status here to make sure the command succeeded

    if(sctl_io->cdb_status != S_GOOD) {
        senseLength_ = sctl_io->sense_xfer;
        memcpy(&senseData_,sctl_io->sense,senseLength_);
        return -1;
    }
    senseLength_ = 0;
    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::supportedAC                                --JKH
//
// Gets SCSI Inquiry data from the autochanger to verify that it is a type
// the hpACDev object supports.
//
// ARGS:  none
// RETURNS:  
//
// Returns 1 if the autochanger is supported, 0 otherwise.
//
// PRE-CONDS:   none
// POST-CONDS:  see return values.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::supportedAC( void ) 
{
    struct inquiry_2 *  inq;
    struct sctl_io      sctl_io;
    NSR::BYTE   buffer[AC_INQUIRY_LENGTH];

    // Set up for call to the passthru driver

    memset(buffer,0,AC_INQUIRY_LENGTH);
    acpt_inquiry(&sctl_io,(NSR::CHAR_PTR) buffer,AC_INQUIRY_LENGTH);

    if(acCommand(&sctl_io)) {
        return 0;
    }

    // We now have the inquiry data.  Currently, we just support HP 
    // autochangers from Greeley.  To determine if the autochanger is HP,
    // we check the following things:
    //
    // Device type of 0x08 (medium changer device)
    // Vendor ID containing "HP"
    // Device type qualifier of 0 (to distinguish from the Nestor DDS tape
    // autoloader).

    inq = (struct inquiry_2 *) buffer;
    if(inq->dev_type != MEDIUM_CHANGER_DEVICE) {
        return 0;
    }
    if(strncmp(inq->vendor_id,"HP",2) != 0) {
        return 0;
    }
    if(inq->dtq != 0x00) {
        return 0;
    }

    return 1;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::testUnitReady                              --JKH
//
// Sends down up to the specified number of the Test Unit Ready SCSI commands.
// The method stops if a given Test Unit succeeds.
//
// ARGS:
//
// INT32 howmany  IN  How many Test Unit Ready commands to send
//
// RETURNS:
//
// Returns 0 on success (a test unit succeeded), -1 on failure.
//
// PRE-CONDS:   none
// POST-CONDS:  see above
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::testUnitReady( NSR::INT32 howmany )
{
    NSR::INT32          i;
    struct sctl_io      sctl_io;

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

    // Set up for call to the passthru driver

    acpt_test_unit(&sctl_io);

    for(i = 0; i < howmany; i++) {
        if(!acCommand(&sctl_io)) {
            return 0;                   // Test unit succeeded; we're done
        }
    }   

    return -1;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::getEltAddr                                 --JKH
//
// Gets element addresses (mode sense) information using the passthru driver.
//
// ARGS:  None
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
// --The data member eltaddr_ will be filled in with element addresses
//   information.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::getEltAddr(void)
{
    struct sctl_io      sctl_io;
    NSR::BYTE   buffer[ACMODE_ELTADDR_PAGE_LENGTH];

    if(fd_ < 0) {               // Not properly constructed
        return -1;              
    }
 
    // Set up for the call to the passthru driver.

    memset(buffer,0,ACMODE_ELTADDR_PAGE_LENGTH); 
    acpt_mode_sense(&sctl_io,ACMODE_ELTADDR_PAGE,(NSR::CHAR_PTR) buffer,
                    ACMODE_ELTADDR_PAGE_LENGTH);
    
    if(acCommand(&sctl_io)) {
        return -1;
    }

    // We need to parse the data returned from the mode sense and put it
    // into the user-friendly element_addresses struct.

    eltaddr_.first_transport = (buffer[6] << 8) + buffer[7];
    eltaddr_.num_transports = (buffer[8] << 8) + buffer[9];
    eltaddr_.first_storage = (buffer[10] << 8) + buffer[11];
    eltaddr_.num_storages = (buffer[12] << 8) + buffer[13];
    eltaddr_.first_import_export = (buffer[14] << 8) + buffer[15];
    eltaddr_.num_import_exports = (buffer[16] << 8) + buffer[17];
    eltaddr_.first_data_transfer = (buffer[18] << 8) + buffer[19];
    eltaddr_.num_data_transfers = (buffer[20] << 8) + buffer[21];

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::getEltStatus                               --JKH
//
// Gets element status  information using the passthru driver.
//
// ARGS:  
//
// INT32 element               IN   Which element to get status for
// struct element_status *esp  OUT  Struct to contain status data
//
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
// --The esp struct will be filled with element status information.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::getEltStatus
( 
    NSR::INT32 element,
    struct element_status *esp 
)
{
    NSR::INT32          len;
    struct sctl_io      sctl_io;
    NSR::BYTE   buffer[ELTSTAT_HEADER_LENGTH + ELTSTAT_MAX_DATA_LENGTH];

    if(fd_ < 0) {               // Not properly constructed
        return -1;              
    }
 
    if(esp == NULL) {
        return -1;
    }

    // Set up for the call to the passthru driver.

    memset(buffer,0,ELTSTAT_HEADER_LENGTH + ELTSTAT_MAX_DATA_LENGTH);
    acpt_element_status(&sctl_io,ELTSTAT_ALL_ELEMENTS,element,1,(NSR::CHAR_PTR) buffer,
                        ELTSTAT_HEADER_LENGTH + ELTSTAT_MAX_DATA_LENGTH);
    
    if(acCommand(&sctl_io)) {
        return -1;
    }

    // Copy the appropriate bytes to the esp struct.

    len = sctl_io.data_xfer - 16;
    if(len < 0) {
        return -1;
    }
    memcpy((void *) esp,(void *) (buffer + 16),len);

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::moveMedium                                 --JKH
//
// Moves media using the passthru driver.
//
// ARGS:  
//
// INT32 transport  IN  Transport (picker) element
// INT32 source     IN  Source element
// INT32 dest       IN  Destination element
// INT32 flip       IN  Flip media if nonzero
//
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
// The surface will be moved from source to destination.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::moveMedium
( 
    NSR::INT32 transport,
    NSR::INT32 source,
    NSR::INT32 dest,
    NSR::INT32 flip 
)
{
    struct sctl_io      sctl_io;

    if(fd_ < 0) {               // Not properly constructed
        return -1;              
    }
 
    // Set up for the call to the passthru driver.

    acpt_move(&sctl_io,transport,source,dest,flip);
    
    if(acCommand(&sctl_io)) {
        return -1;
    }

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::exchangeMedia                              --JKH
//
// Moves media using the passthru driver.
//
// ARGS:  
//
// INT32 transport  IN  Transport (picker) element
// INT32 source     IN  Source element
// INT32 dest1      IN  Destination element 1
// INT32 dest2      IN  Destination element 2
// INT32 flip1      IN  Flip media if nonzero
// INT32 flip2      IN  Flip media if nonzero
//
// RETURNS:
//
// Returns 0 on success, -1 on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
// Surfaces will be moved from source to dest1 and dest1 to dest2.
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::exchangeMedia
(
    NSR::INT32 transport,
    NSR::INT32 source,
    NSR::INT32 dest1,
    NSR::INT32 dest2,
    NSR::INT32 flip1,
    NSR::INT32 flip2 
)
{
    struct sctl_io      sctl_io;

    if(fd_ < 0) {               // Not properly constructed
        return -1;              
    }
 
    // Set up for the call to the passthru driver.

    acpt_exchange(&sctl_io,transport,source,dest1,dest2,flip1,flip2);
    
    if(acCommand(&sctl_io)) {
        return -1;
    }

    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  hpACDev::makeDriveDevice                            --JKH
//
// Generates a SSDevice.
//
// ARGS:  
//
// ACDriveEntry *dptr  IN  Specifies drive to make a device for
//
// RETURNS:
//
// Returns a pointer to a SSDevice on success, NULL on failure.
//
// PRE-CONDS:
//
// --hpACDev has been successfully constructed
//
// POST-CONDS:
//
//-----------------------------------------------------------------------------

NSR::SSDevice *
hpACDev::makeDriveDevice
( 
    ACDriveEntry *dptr,
    NSR::Error& err 
)
{
    NSR::CHAR   pathname[256];
    NSR::SSDevice *ddevp = NULL;
    dev_t       dev;

    if(fd_ < 0) {
        return NULL;
    }

    err = NSR::error_None;

    if(dptr == NULL) {
        err = NSR::error_Null_pointer;
        return NULL;
    }

    // Make a device file in /tmp.  Very probably this won't work since you
    // need to be root.

    // NEED TO IMPROVE THIS ALGORITHM, quick-and-dirty for now

    while(1) {
        struct stat statbuf;

        sprintf(pathname,"/tmp/msDevTest.%d",lrand48() % 65536);
        if(stat(pathname,&statbuf) != 0) {
            break;
        }
    }
#ifdef HPUX_90X_700
    dev = S700_90X_DISK_RAW << 24;
    dev |= 0x201 << 12;
    dev |= dptr->tgt_ << 8;
    dev |= dptr->lun_ << 4;
    if(mknod(pathname,S_IFCHR|S_IRUSR|S_IWUSR,dev)) {
        err = NSR::error_Cant_make_disk_device_file;
        return NULL;
    }
#endif

    dptr->devfile_ = new NSR::String(pathname);
    if(dptr->devfile_ == NULL) {
        err = NSR::error_No_free_store;
        unlink(pathname);
        return NULL;
    }
    dptr->rmflag_ = 1;
    ddevp = new DriveDevice(pathname);
    if(ddevp->error() != (NSR::Error) NSR::error_None) {
        err = ddevp->error();
        delete ddevp;
        ddevp = NULL;
    }
    
    return (NSR::SSDevice *) ddevp;
}

//-----------------------------------------------------------------------------
// STRUCT:  ACMapTable
//
// This struct is a struct private to the hpACDev and is used to keep track 
// of sid_t to surface mappings.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// METHOD:  ACMapTable::resize
//
// Resizes the ACMapTable.  Destroys any information currently in the object.
//
// ARGS:
//
// INT32 newsize  IN  The new size of the table
//
// RETURNS:
//
// Returns 0 on success, -1 on failure
//
// PRE-CONDS:  none
// POST-CONDS:  
//
// --Any old information will be deleted
// --The table will have newsize entries initialized to empty (null) values
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::ACMapTable::resize
( 
    NSR::UINT32 newsize 
)
{
    
    if(newsize == 0) {
        return -1;
    }
    if(table_ != NULL) {
        delete [] table_;
    }

    table_ = new ACMapEntry[newsize];
    if(table_ == NULL) {
        size_ = 0;
        return -1;
    }
    size_ = newsize;
    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  ACMapTable::lookup
//
// Looks up the argument sid_t and returns a pointer to its entry in the table
// if present.
//
// ARGS:
//
// sid_t sid  IN  sid_t to search for
//
// RETURNS:
//
// A pointer to an ACMapEntry on success, NULL on failure.
//
// PRE-CONDS:  none
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

hpACDev::ACMapEntry *
hpACDev::ACMapTable::lookup
( 
    NSR::sid_t sid 
)
{
    ACMapEntry  *mptr;

    if(size_ == 0) {
        return NULL;
    }
    if((sid == SID_T_INVALID) || (sid > size_)) {
        return NULL;
    }

    mptr = &(table_[sid]);
    if(mptr->sid_ != sid) {
        return NULL;
    }
    return mptr;
}


//-----------------------------------------------------------------------------
// STRUCT:  ACDriveTable
//
// This struct is a struct private to the hpACDev and is used to keep track 
// of drives in the autochanger.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// METHOD:  ACDriveTable::resize
//
// Resizes the ACDriveTable.  Destroys any information currently in the object.
//
// ARGS:
//
// INT32 newsize  IN  The new size of the table
//
// RETURNS:
//
// Returns 0 on success, -1 on failure
//
// PRE-CONDS:  none
// POST-CONDS:  
//
// --Any old information will be deleted
// --The table will have newsize entries initialized to empty (null) values
//-----------------------------------------------------------------------------

NSR::INT32
hpACDev::ACDriveTable::resize
( 
    NSR::UINT32 newsize 
)
{
    
    if(newsize == 0) {
        return -1;
    }
    if(table_ != NULL) {
        delete [] table_;
    }

    table_ = new ACDriveEntry[newsize];
    if(table_ == NULL) {
        size_ = 0;
        return -1;
    }
        
    size_ = newsize;
    return 0;
}

//-----------------------------------------------------------------------------
// METHOD:  ACDriveTable::lookup
//
// Looks up the argument sid_t and returns a pointer to its entry in the table
// if present.
//
// ARGS:
//
// sid_t sid  IN  sid_t to search for
//
// RETURNS:
//
// A pointer to an ACDriveEntry on success, NULL on failure.
//
// PRE-CONDS:  none
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

hpACDev::ACDriveEntry *
hpACDev::ACDriveTable::lookup
( 
    NSR::sid_t sid 
)
{
    NSR::INT32          i;
    ACDriveEntry        *dptr;

    if(size_ == 0) {
        return NULL;
    }
    if(sid == SID_T_INVALID)  {
        return NULL;
    }

    for(i = 0; i < size_; i++) {
        dptr = &(table_[i]);
        if(dptr->sid_ == sid) {
            return dptr;
        }
    }

    return NULL;
}

//-----------------------------------------------------------------------------
// METHOD:  ACDriveTable::findFree
//
// Tries to find a free drive in the drive table.  
//
// ARGS:  None
// RETURNS:
//
// A pointer to an ACDriveEntry on success, NULL on failure.
//
// PRE-CONDS:  none
// POST-CONDS:  see return values
//-----------------------------------------------------------------------------

hpACDev::ACDriveEntry *
hpACDev::ACDriveTable::findFree( void )
{
    NSR::INT32          i;
    ACDriveEntry        *dptr;

    if(size_ == 0) {
        return NULL;
    }

    for(i = 0; i < size_; i++) {
        dptr = &(table_[i]);
        if((dptr->drivep_ == NULL) && (dptr->sid_ != SID_T_SPARED)) {
            return dptr;
        }
    }

    return NULL;
}
