mirror of
https://github.com/moibenko/mtx.git
synced 2025-12-23 05:55:13 +00:00
2028 lines
57 KiB
C
2028 lines
57 KiB
C
/* MTX -- SCSI Tape Attached Medium Changer Control Program
|
|
|
|
Copyright 1997-1998 by Leonard N. Zubkoff.
|
|
Copyright 1999-2006 by Eric Lee Green.
|
|
Copyright 2007-2008 by Robert Nelson <robertn@the-nelsons.org>
|
|
|
|
$Date: 2008-08-19 03:03:38 -0700 (Tue, 19 Aug 2008) $
|
|
$Revision: 193 $
|
|
|
|
This file created Feb 2000 by Eric Lee Green <eric@badtux.org> from pieces
|
|
extracted from mtx.c, plus a near total re-write of most of the beast.
|
|
|
|
This program is free software; you may redistribute and/or modify it under
|
|
the terms of the GNU General Public License Version 2 as published by the
|
|
Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
|
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for complete details.
|
|
*/
|
|
|
|
|
|
/*
|
|
* FatalError: changed Feb. 2000 elg@badtux.org to eliminate a buffer
|
|
* overflow :-(. That could be important if mtxl is SUID for some reason.
|
|
*/
|
|
|
|
#include "mtx.h"
|
|
#include "mtxl.h"
|
|
|
|
/* #define DEBUG_NSM 1 */
|
|
/* #define DEBUG_BARCODE */
|
|
/* #define DEBUG_MODE_SENSE 1 */
|
|
/* #define DEBUG */
|
|
/* #define DEBUG_SCSI */
|
|
#define __WEIRD_CHAR_SUPPRESS 1
|
|
|
|
/* zap the following define when we finally add real import/export support */
|
|
#define IMPORT_EXPORT_HACK 1 /* for the moment, import/export == storage */
|
|
|
|
/* only so many element per SCSI Read Element Status */
|
|
#define SCSI_RES_ELEMENTS 5000
|
|
|
|
/* First, do some SCSI routines: */
|
|
|
|
/* the camlib is used on FreeBSD. */
|
|
#if HAVE_CAMLIB_H
|
|
# include "scsi_freebsd.c"
|
|
#endif
|
|
|
|
/* the scsi_ctl interface is used on HP/UX. */
|
|
#if HAVE_SYS_SCSI_CTL_H
|
|
# include "scsi_hpux.c"
|
|
#endif
|
|
|
|
/* the 'sg' interface is used on Linux. */
|
|
#if HAVE_SCSI_SG_H
|
|
# include "scsi_linux.c"
|
|
#endif
|
|
|
|
/* the IOCTL_SCSI_PASSTHROUGH interface is used on Windows. */
|
|
#if HAVE_DDK_NTDDSCSI_H || defined(_MSC_VER)
|
|
# include "scsi_win32.c"
|
|
#endif
|
|
|
|
/* The 'uscsi' interface is used on Solaris. */
|
|
#if HAVE_SYS_SCSI_IMPL_USCSI_H
|
|
# include "scsi_sun.c"
|
|
#endif
|
|
|
|
/* The 'gsc' interface, is used on AIX. */
|
|
#if HAVE_SYS_GSCDDS_H
|
|
# include "scsi_aix.c"
|
|
#endif
|
|
|
|
/* The 'dslib' interface is used on SGI. */
|
|
#if HAVE_DSLIB_H
|
|
# include "scsi_sgi.c"
|
|
#endif
|
|
|
|
/* Hmm, dunno what to do about Digital Unix at the moment. */
|
|
#ifdef DIGITAL_UNIX
|
|
# include "du/scsi.c"
|
|
#endif
|
|
|
|
#ifdef VMS
|
|
# include "[.vms]scsi.c"
|
|
#endif
|
|
|
|
extern char *argv0; /* something to let us do good error messages. */
|
|
|
|
/* create a global RequestSenseT value. */
|
|
RequestSense_T scsi_error_sense;
|
|
|
|
/* Now for some low-level SCSI stuff: */
|
|
|
|
Inquiry_T *RequestInquiry(DEVICE_TYPE fd, RequestSense_T *RequestSense)
|
|
{
|
|
Inquiry_T *Inquiry;
|
|
CDB_T CDB;
|
|
|
|
Inquiry = (Inquiry_T *) xmalloc(sizeof(Inquiry_T));
|
|
|
|
CDB[0] = 0x12; /* INQUIRY */
|
|
CDB[1] = 0; /* EVPD = 0 */
|
|
CDB[2] = 0; /* Page Code */
|
|
CDB[3] = 0; /* Reserved */
|
|
CDB[4] = sizeof(Inquiry_T); /* Allocation Length */
|
|
CDB[5] = 0; /* Control */
|
|
|
|
/* set us a very short timeout, sigh... */
|
|
SCSI_Set_Timeout(30); /* 30 seconds, sigh! */
|
|
|
|
if (SCSI_ExecuteCommand(fd, Input, &CDB, 6, Inquiry, sizeof(Inquiry_T), RequestSense) != 0)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "SCSI Inquiry Command failed\n");
|
|
#endif
|
|
free(Inquiry);
|
|
return NULL; /* sorry! */
|
|
}
|
|
return Inquiry;
|
|
}
|
|
|
|
|
|
#if defined(DEBUG_NSM) || defined(DEBUG_EXCHANGE)
|
|
/* DEBUG */
|
|
static void dump_cdb(unsigned char *CDB, int len)
|
|
{
|
|
fprintf(stderr,"CDB:");
|
|
PrintHex(1, CDB, len);
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(DEBUG_NSM) || defined(DEBUG_ADIC)
|
|
/* DEBUG */
|
|
static void dump_data(unsigned char *data, int len)
|
|
{
|
|
if (!len)
|
|
{
|
|
fprintf(stderr,"**NO DATA**\n");
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr,"DATA:");
|
|
PrintHex(1, data, len);
|
|
}
|
|
#endif
|
|
|
|
|
|
int BigEndian16(unsigned char *BigEndianData)
|
|
{
|
|
return (BigEndianData[0] << 8) + BigEndianData[1];
|
|
}
|
|
|
|
|
|
int BigEndian24(unsigned char *BigEndianData)
|
|
{
|
|
return (BigEndianData[0] << 16) + (BigEndianData[1] << 8) + BigEndianData[2];
|
|
}
|
|
|
|
|
|
void FatalError(char *ErrorMessage, ...)
|
|
{
|
|
#define FORMAT_BUF_LEN 1024
|
|
|
|
char FormatBuffer[FORMAT_BUF_LEN];
|
|
char *SourcePointer;
|
|
char *TargetPointer = FormatBuffer;
|
|
char Character, LastCharacter = '\0';
|
|
int numchars = 0;
|
|
|
|
va_list ArgumentPointer;
|
|
va_start(ArgumentPointer, ErrorMessage);
|
|
/* SourcePointer = "mtx: "; */
|
|
sprintf(TargetPointer,"%s: ",argv0);
|
|
numchars=strlen(TargetPointer);
|
|
|
|
while ((Character = *ErrorMessage++) != '\0')
|
|
{
|
|
if (LastCharacter == '%')
|
|
{
|
|
if (Character == 'm')
|
|
{
|
|
SourcePointer = strerror(errno);
|
|
while ((Character = *SourcePointer++) != '\0')
|
|
{
|
|
*TargetPointer++ = Character;
|
|
numchars++;
|
|
if (numchars == FORMAT_BUF_LEN - 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (numchars == FORMAT_BUF_LEN - 1)
|
|
{
|
|
break; /* break outer loop... */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*TargetPointer++ = '%';
|
|
*TargetPointer++ = Character;
|
|
numchars++;
|
|
if (numchars == FORMAT_BUF_LEN - 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
LastCharacter = '\0';
|
|
}
|
|
else
|
|
{
|
|
if (Character != '%')
|
|
{
|
|
*TargetPointer++ = Character;
|
|
numchars++;
|
|
if (numchars == FORMAT_BUF_LEN-1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
LastCharacter = Character;
|
|
}
|
|
}
|
|
|
|
*TargetPointer = '\0'; /* works even if we had to break from above... */
|
|
vfprintf(stderr, FormatBuffer, ArgumentPointer);
|
|
va_end(ArgumentPointer);
|
|
|
|
#ifndef VMS
|
|
//exit(1);
|
|
return; /* if used as library routine it can not exit, because this would terminate the caller */
|
|
#else
|
|
sys$exit(VMS_ExitCode);
|
|
#endif
|
|
}
|
|
|
|
/* This is a really slow and stupid 'bzero' implementation'... */
|
|
void slow_bzero(char *buffer, int numchars)
|
|
{
|
|
while (numchars--)
|
|
{
|
|
*buffer++ = 0;
|
|
}
|
|
}
|
|
|
|
/* malloc some memory while checking for out-of-memory conditions. */
|
|
void *xmalloc(size_t Size)
|
|
{
|
|
void *Result = (void *) malloc(Size);
|
|
if (Result == NULL)
|
|
{
|
|
FatalError("cannot allocate %d bytes of memory\n", Size);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/* malloc some memory, zeroing it too: */
|
|
void *xzmalloc(size_t Size)
|
|
{
|
|
void *Result = (void *)xmalloc(Size);
|
|
|
|
slow_bzero(Result, Size);
|
|
return Result;
|
|
}
|
|
|
|
|
|
int min(int x, int y)
|
|
{
|
|
return (x < y ? x : y);
|
|
}
|
|
|
|
|
|
int max(int x, int y)
|
|
{
|
|
return (x > y ? x : y);
|
|
}
|
|
|
|
|
|
/* Okay, this is a hack for the NSM modular jukebox series, which
|
|
* uses the "SEND DIAGNOSTIC" command to do shit.
|
|
*/
|
|
|
|
int SendNSMHack(DEVICE_TYPE MediumChangerFD, NSM_Param_T *nsm_command,
|
|
int param_len, int timeout)
|
|
{
|
|
CDB_T CDB;
|
|
int list_len = param_len + sizeof(NSM_Param_T) - 2048;
|
|
|
|
/* Okay, now for the command: */
|
|
CDB[0] = 0x1D;
|
|
CDB[1] = 0x10;
|
|
CDB[2] = 0;
|
|
CDB[3] = (unsigned char)(list_len >> 8);
|
|
CDB[4] = (unsigned char)list_len;
|
|
CDB[5] = 0;
|
|
|
|
#ifdef DEBUG_NSM
|
|
dump_cdb(&CDB,6);
|
|
dump_data(nsm_command,list_len);
|
|
#endif
|
|
fflush(stderr);
|
|
/* Don't set us any timeout unless timeout is > 0 */
|
|
if (timeout > 0)
|
|
{
|
|
SCSI_Set_Timeout(timeout); /* 30 minutes, sigh! */
|
|
}
|
|
|
|
/* Now send the command: */
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Output, &CDB, 6, nsm_command, list_len, &scsi_error_sense))
|
|
{
|
|
return -1; /* we failed */
|
|
}
|
|
return 0; /* Did it! */
|
|
}
|
|
|
|
NSM_Result_T *RecNSMHack( DEVICE_TYPE MediumChangerFD,
|
|
int param_len, int timeout)
|
|
{
|
|
CDB_T CDB;
|
|
|
|
NSM_Result_T *retval = (NSM_Result_T *)xzmalloc(sizeof(NSM_Result_T));
|
|
|
|
int list_len = param_len + sizeof(NSM_Result_T) - 0xffff;
|
|
|
|
/* Okay, now for the command: */
|
|
CDB[0] = 0x1C;
|
|
CDB[1] = 0x00;
|
|
CDB[2] = 0;
|
|
CDB[3] = (unsigned char)(list_len >> 8);
|
|
CDB[4] = (unsigned char)list_len;
|
|
CDB[5] = 0;
|
|
|
|
#ifdef DEBUG_NSM
|
|
dump_cdb(&CDB,6);
|
|
#endif
|
|
|
|
/* Don't set us any timeout unless timeout is > 0 */
|
|
if (timeout > 0)
|
|
{
|
|
SCSI_Set_Timeout(timeout);
|
|
}
|
|
|
|
/* Now send the command: */
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, retval, list_len, &scsi_error_sense))
|
|
{
|
|
return NULL; /* we failed */
|
|
}
|
|
|
|
#ifdef DEBUG_NSM
|
|
fprintf(stderr,
|
|
"page_code=%02x page_len=%d command_code=%s\n",
|
|
retval->page_code,
|
|
(int) ((retval->page_len_msb << 8) + retval->page_len_lsb),
|
|
retval->command_code);
|
|
#endif
|
|
|
|
return retval; /* Did it! (maybe!)*/
|
|
}
|
|
|
|
/* Routine to inventory the library. Needed by, e.g., some Breece Hill
|
|
* loaders. Sends an INITIALIZE_ELEMENT_STATUS command. This command
|
|
* has no parameters, such as a range to scan :-(.
|
|
*/
|
|
|
|
int Inventory(DEVICE_TYPE MediumChangerFD)
|
|
{
|
|
CDB_T CDB;
|
|
|
|
/* okay, now for the command: */
|
|
CDB[0] = 0x07;
|
|
CDB[1] = CDB[2] = CDB[3] = CDB[4] = CDB[5] = 0;
|
|
|
|
/* set us a very long timeout, sigh... */
|
|
SCSI_Set_Timeout(30 * 60); /* 30 minutes, sigh! */
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,NULL,0,&scsi_error_sense) != 0)
|
|
{
|
|
/* If error is UNIT ATTENTION then retry the request */
|
|
if (scsi_error_sense.ErrorCode != 0x70 || scsi_error_sense.SenseKey != 6 ||
|
|
ClearUnitAttention(MediumChangerFD, &scsi_error_sense) != 0 ||
|
|
SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,NULL,0,&scsi_error_sense) != 0)
|
|
{
|
|
PrintRequestSense(&scsi_error_sense);
|
|
fprintf(stderr, "Initialize Element Status (0x07) failed\n");
|
|
return -1; /* could not do! */
|
|
}
|
|
}
|
|
return 0; /* did do! */
|
|
}
|
|
|
|
/* Routine to read the Mode Sense Element Address Assignment Page */
|
|
/* We try to read the page. If we can't read the page, we return NULL.
|
|
* Our caller really isn't too worried about why we could not read the
|
|
* page, it will simply default to some kind of default values.
|
|
*/
|
|
ElementModeSense_T *ReadAssignmentPage(DEVICE_TYPE MediumChangerFD)
|
|
{
|
|
CDB_T CDB;
|
|
ElementModeSense_T *retval; /* processed SCSI. */
|
|
unsigned char input_buffer[136];
|
|
ElementModeSensePage_T *sense_page; /* raw SCSI. */
|
|
|
|
/* okay, now for the command: */
|
|
CDB[0] = 0x1A; /* Mode Sense(6) */
|
|
CDB[1] = 0x08;
|
|
CDB[2] = 0x1D; /* Mode Sense Element Address Assignment Page */
|
|
CDB[3] = 0;
|
|
CDB[4] = 136; /* allocation_length... */
|
|
CDB[5] = 0;
|
|
|
|
/* clear the data buffer: */
|
|
slow_bzero((char *)&scsi_error_sense, sizeof(RequestSense_T));
|
|
slow_bzero((char *)input_buffer, sizeof(input_buffer));
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6,
|
|
&input_buffer, sizeof(input_buffer), &scsi_error_sense) != 0)
|
|
{
|
|
/* If error is UNIT ATTENTION then retry the request */
|
|
if (scsi_error_sense.ErrorCode != 0x70 || scsi_error_sense.SenseKey != 6 ||
|
|
ClearUnitAttention(MediumChangerFD, &scsi_error_sense) != 0 ||
|
|
SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6,
|
|
&input_buffer, sizeof(input_buffer), &scsi_error_sense) != 0)
|
|
{
|
|
PrintRequestSense(&scsi_error_sense);
|
|
fprintf(stderr,"Mode sense (0x1A) for Page 0x1D failed\n");
|
|
fflush(stderr);
|
|
return NULL; /* sorry, couldn't do it. */
|
|
}
|
|
}
|
|
|
|
/* Could do it, now build return value: */
|
|
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintHex(0, input_buffer, 30);
|
|
#endif
|
|
|
|
/* first, skip past: mode data header, and block descriptor header if any */
|
|
sense_page=(ElementModeSensePage_T *)(input_buffer+4+input_buffer[3]);
|
|
|
|
#ifdef DEBUG_MODE_SENSE
|
|
fprintf(stderr,"*sense_page=%x %x\n",*((unsigned char *)sense_page),sense_page->PageCode);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
retval = (ElementModeSense_T *)xzmalloc(sizeof(ElementModeSense_T));
|
|
|
|
/* Remember that all SCSI values are big-endian: */
|
|
retval->MediumTransportStart =
|
|
(((int)sense_page->MediumTransportStartHi) << 8) +
|
|
sense_page->MediumTransportStartLo;
|
|
|
|
retval->NumMediumTransport =
|
|
(((int)(sense_page->NumMediumTransportHi))<<8) +
|
|
sense_page->NumMediumTransportLo;
|
|
|
|
/* HACK! Some HP autochangers don't report NumMediumTransport right! */
|
|
/* ARG! TAKE OUT THE %#@!# HACK! */
|
|
#ifdef STUPID_DUMB_IDIOTIC_HP_LOADER_HACK
|
|
if (!retval->NumMediumTransport)
|
|
{
|
|
retval->NumMediumTransport = 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_MODE_SENSE
|
|
fprintf(stderr, "rawNumStorage= %d %d rawNumImportExport= %d %d\n",
|
|
sense_page->NumStorageHi, sense_page->NumStorageLo,
|
|
sense_page->NumImportExportHi, sense_page->NumImportExportLo);
|
|
fprintf(stderr, "rawNumTransport=%d %d rawNumDataTransfer=%d %d\n",
|
|
sense_page->NumMediumTransportHi, sense_page->NumMediumTransportLo,
|
|
sense_page->NumDataTransferHi, sense_page->NumDataTransferLo);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
retval->StorageStart =
|
|
((int)sense_page->StorageStartHi << 8) + sense_page->StorageStartLo;
|
|
|
|
retval->NumStorage =
|
|
((int)sense_page->NumStorageHi << 8) + sense_page->NumStorageLo;
|
|
|
|
retval->ImportExportStart =
|
|
((int)sense_page->ImportExportStartHi << 8) + sense_page->ImportExportStartLo;
|
|
|
|
retval->NumImportExport =
|
|
((int)sense_page->NumImportExportHi << 8) + sense_page->NumImportExportLo;
|
|
|
|
retval->DataTransferStart =
|
|
((int)sense_page->DataTransferStartHi << 8) + sense_page->DataTransferStartLo;
|
|
|
|
retval->NumDataTransfer =
|
|
((int)sense_page->NumDataTransferHi << 8) + sense_page->NumDataTransferLo;
|
|
|
|
/* allocate a couple spares 'cause some HP autochangers and maybe others
|
|
* don't properly report the robotics arm(s) count here...
|
|
*/
|
|
retval->NumElements =
|
|
retval->NumStorage+retval->NumImportExport +
|
|
retval->NumDataTransfer+retval->NumMediumTransport;
|
|
|
|
retval->MaxReadElementStatusData =
|
|
(sizeof(ElementStatusDataHeader_T) +
|
|
4 * sizeof(ElementStatusPage_T) +
|
|
retval->NumElements * sizeof(TransportElementDescriptor_T));
|
|
|
|
#ifdef IMPORT_EXPORT_HACK
|
|
retval->NumStorage = retval->NumStorage+retval->NumImportExport;
|
|
#endif
|
|
|
|
#ifdef DEBUG_MODE_SENSE
|
|
fprintf(stderr, "NumMediumTransport=%d\n", retval->NumMediumTransport);
|
|
fprintf(stderr, "NumStorage=%d\n", retval->NumStorage);
|
|
fprintf(stderr, "NumImportExport=%d\n", retval->NumImportExport);
|
|
fprintf(stderr, "NumDataTransfer=%d\n", retval->NumDataTransfer);
|
|
fprintf(stderr, "MaxReadElementStatusData=%d\n", retval->MaxReadElementStatusData);
|
|
fprintf(stderr, "NumElements=%d\n", retval->NumElements);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
void FreeElementData(ElementStatus_T *data)
|
|
{
|
|
if (!data)
|
|
{
|
|
return;
|
|
}
|
|
free(data->DataTransferElementAddress);
|
|
free(data->DataTransferElementSourceStorageElementNumber);
|
|
free(data->DataTransferElementPhysicalLocation);
|
|
free(data->DataTransferElementProductId);
|
|
free(data->DataTransferElementSerialNumber);
|
|
free(data->StorageElementPhysicalLocation);
|
|
free(data->DataTransferPrimaryVolumeTag);
|
|
free(data->DataTransferAlternateVolumeTag);
|
|
free(data->PrimaryVolumeTag);
|
|
free(data->AlternateVolumeTag);
|
|
free(data->StorageElementAddress);
|
|
free(data->StorageElementIsImportExport);
|
|
free(data->StorageElementFull);
|
|
free(data->DataTransferElementFull);
|
|
}
|
|
|
|
|
|
/* allocate data */
|
|
|
|
static ElementStatus_T *AllocateElementData(ElementModeSense_T *mode_sense)
|
|
{
|
|
ElementStatus_T *retval;
|
|
|
|
retval = (ElementStatus_T *)xzmalloc(sizeof(ElementStatus_T));
|
|
|
|
/* now for the invidual component arrays.... */
|
|
|
|
retval->DataTransferElementAddress =
|
|
(int *)xzmalloc(sizeof(int) * (mode_sense->NumDataTransfer + 1));
|
|
retval->DataTransferElementSourceStorageElementNumber =
|
|
(int *)xzmalloc(sizeof(int) * (mode_sense->NumDataTransfer + 1));
|
|
retval->DataTransferElementPhysicalLocation =
|
|
(int *)xzmalloc(sizeof(int) * (mode_sense->NumDataTransfer + 1));
|
|
retval->DataTransferElementProductId =
|
|
(serialnumber *)xzmalloc(sizeof(serialnumber) * (mode_sense->NumDataTransfer + 1));
|
|
retval->DataTransferElementSerialNumber =
|
|
(serialnumber *)xzmalloc(sizeof(serialnumber) * (mode_sense->NumDataTransfer + 1));
|
|
retval->StorageElementPhysicalLocation =
|
|
(barcode *)xzmalloc(sizeof(barcode) * (mode_sense->NumStorage + 1));
|
|
retval->DataTransferPrimaryVolumeTag =
|
|
(barcode *)xzmalloc(sizeof(barcode) * (mode_sense->NumDataTransfer + 1));
|
|
retval->DataTransferAlternateVolumeTag =
|
|
(barcode *)xzmalloc(sizeof(barcode) * (mode_sense->NumDataTransfer + 1));
|
|
retval->PrimaryVolumeTag =
|
|
(barcode *)xzmalloc(sizeof(barcode) * (mode_sense->NumStorage + 1));
|
|
retval->AlternateVolumeTag =
|
|
(barcode *)xzmalloc(sizeof(barcode) * (mode_sense->NumStorage + 1));
|
|
retval->StorageElementAddress =
|
|
(int *)xzmalloc(sizeof(int) * (mode_sense->NumStorage + 1));
|
|
retval->StorageElementIsImportExport =
|
|
(boolean *)xzmalloc(sizeof(boolean) * (mode_sense->NumStorage + 1));
|
|
retval->StorageElementFull =
|
|
(boolean *)xzmalloc(sizeof(boolean) * (mode_sense->NumStorage + 1));
|
|
retval->DataTransferElementFull =
|
|
(boolean *)xzmalloc(sizeof(boolean) * (mode_sense->NumDataTransfer + 1));
|
|
|
|
return retval; /* sigh! */
|
|
}
|
|
|
|
|
|
void copy_barcode(unsigned char *src, unsigned char *dest)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < 36; i++)
|
|
{
|
|
*dest = *src++;
|
|
|
|
if ((*dest < 32) || (*dest > 127))
|
|
{
|
|
*dest = '\0';
|
|
}
|
|
|
|
dest++;
|
|
}
|
|
*dest = 0; /* null-terminate */
|
|
}
|
|
|
|
void copy_physical_location(unsigned char *src, unsigned char *dest)
|
|
{
|
|
while ((*src< 32) || (*src > 127)) {
|
|
src++;
|
|
}
|
|
strcpy((char *)dest, (char *)src);
|
|
}
|
|
|
|
void copy_char_buffer(unsigned char *src, unsigned char *dest, int num)
|
|
{
|
|
int i;
|
|
while ((*src< 32) || (*src > 127)) {
|
|
src++;
|
|
}
|
|
for (i=0; i < num; i++)
|
|
{
|
|
*dest = *src++;
|
|
|
|
if ((*dest < 32) || (*dest > 127))
|
|
{
|
|
*dest = 0;
|
|
break;
|
|
}
|
|
dest++;
|
|
}
|
|
*dest = 0;
|
|
}
|
|
|
|
/* This #%!@# routine has more parameters than I can count! */
|
|
static unsigned char *SendElementStatusRequestActual(
|
|
DEVICE_TYPE MediumChangerFD,
|
|
RequestSense_T *RequestSense,
|
|
Inquiry_T *inquiry_info,
|
|
SCSI_Flags_T *flags,
|
|
int ElementStart,
|
|
int NumElements,
|
|
int NumBytes
|
|
)
|
|
{
|
|
CDB_T CDB;
|
|
boolean is_attached = false;
|
|
|
|
unsigned char *DataBuffer; /* size of data... */
|
|
|
|
#ifdef HAVE_GET_ID_LUN
|
|
scsi_id_t *scsi_id;
|
|
#endif
|
|
if (inquiry_info->MChngr &&
|
|
inquiry_info->PeripheralDeviceType != MEDIUM_CHANGER_TYPE)
|
|
{
|
|
is_attached = true;
|
|
}
|
|
|
|
if (flags->no_attached)
|
|
{
|
|
/* override, sigh */
|
|
is_attached = false;
|
|
}
|
|
|
|
DataBuffer = (unsigned char *)xzmalloc(NumBytes + 1);
|
|
|
|
slow_bzero((char *)RequestSense, sizeof(RequestSense_T));
|
|
|
|
#ifdef HAVE_GET_ID_LUN
|
|
scsi_id = SCSI_GetIDLun(MediumChangerFD);
|
|
#endif
|
|
|
|
CDB[0] = 0xB8; /* READ ELEMENT STATUS */
|
|
|
|
if (is_attached)
|
|
{
|
|
CDB[0] = 0xB4; /* whoops, READ_ELEMENT_STATUS_ATTACHED! */
|
|
}
|
|
|
|
#ifdef HAVE_GET_ID_LUN
|
|
CDB[1] = (scsi_id->lun << 5) | ((flags->no_barcodes) ?
|
|
0 : 0x10) | flags->elementtype; /* Lun + VolTag + Type code */
|
|
free(scsi_id);
|
|
#else
|
|
/* Element Type Code = 0, VolTag = 1 */
|
|
CDB[1] = (unsigned char)((flags->no_barcodes ? 0 : 0x10) | flags->elementtype);
|
|
#endif
|
|
/* Starting Element Address */
|
|
CDB[2] = (unsigned char)(ElementStart >> 8);
|
|
CDB[3] = (unsigned char)ElementStart;
|
|
|
|
/* Number Of Elements */
|
|
CDB[4]= (unsigned char)(NumElements >> 8);
|
|
CDB[5]= (unsigned char)NumElements;
|
|
|
|
CDB[6] = 0; /* Reserved */
|
|
if (((flags->elementtype == DataTransferElement || flags->elementtype == StorageElement)) && (flags->absolute_addressing == 1)) {
|
|
CDB[6] = 0x01; /* set DVCID to read physical location */
|
|
}
|
|
|
|
/* allocation length */
|
|
CDB[7]= (unsigned char)(NumBytes >> 16);
|
|
CDB[8]= (unsigned char)(NumBytes >> 8);
|
|
CDB[9]= (unsigned char)NumBytes;
|
|
|
|
CDB[10] = 0; /* Reserved */
|
|
CDB[11] = 0; /* Control */
|
|
|
|
#ifdef DEBUG_BARCODE
|
|
fprintf(stderr,"CDB:\n");
|
|
PrintHex(2, CDB, 12);
|
|
#endif
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 12,
|
|
DataBuffer,NumBytes, RequestSense) != 0)
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "Read Element Status (0x%02X) failed\n", CDB[0]);
|
|
#endif
|
|
|
|
/*
|
|
First see if we have sense key of 'illegal request',
|
|
additional sense code of '24', additional sense qualfier of
|
|
'0', and field in error of '4'. This means that we issued a request
|
|
w/bar code reader and did not have one, thus must re-issue the request
|
|
w/out barcode :-(.
|
|
*/
|
|
|
|
/*
|
|
Most autochangers and tape libraries set a sense key here if
|
|
they do not have a bar code reader. For some reason, the ADIC DAT
|
|
uses a different sense key? Let's retry w/o bar codes for *ALL*
|
|
sense keys.
|
|
*/
|
|
|
|
if (RequestSense->SenseKey > 1)
|
|
{
|
|
/* we issued a code requesting bar codes, there is no bar code reader? */
|
|
/* clear out our sense buffer first... */
|
|
slow_bzero((char *)RequestSense, sizeof(RequestSense_T));
|
|
|
|
CDB[1] &= ~0x10; /* clear bar code flag! */
|
|
|
|
#ifdef DEBUG_BARCODE
|
|
fprintf(stderr,"CDB:\n");
|
|
PrintHex(2, CDB, 12);
|
|
#endif
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 12,
|
|
DataBuffer, NumBytes, RequestSense) != 0)
|
|
{
|
|
free(DataBuffer);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
free(DataBuffer);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_BARCODE
|
|
/* print a bunch of extra debug data :-(. */
|
|
PrintRequestSense(RequestSense); /* see what it sez :-(. */
|
|
fprintf(stderr,"Data:\n");
|
|
PrintHex(2, DataBuffer, 100);
|
|
#endif
|
|
return DataBuffer; /* we succeeded! */
|
|
}
|
|
|
|
|
|
unsigned char *SendElementStatusRequest(DEVICE_TYPE MediumChangerFD,
|
|
RequestSense_T *RequestSense,
|
|
Inquiry_T *inquiry_info,
|
|
SCSI_Flags_T *flags,
|
|
int ElementStart,
|
|
int NumElements,
|
|
int NumBytes
|
|
)
|
|
{
|
|
unsigned char *DataBuffer; /* size of data... */
|
|
int real_numbytes;
|
|
|
|
DataBuffer = SendElementStatusRequestActual(MediumChangerFD,
|
|
RequestSense,
|
|
inquiry_info,
|
|
flags,
|
|
ElementStart,
|
|
NumElements,
|
|
NumBytes
|
|
);
|
|
/*
|
|
One weird loader wants either 8 or BYTE_COUNT_OF_REPORT
|
|
values for the ALLOCATION_LENGTH. Give it what it wants
|
|
if we get an Sense Key of 05 Illegal Request with a
|
|
CDB position of 7 as the field in error.
|
|
*/
|
|
|
|
if (DataBuffer == NULL &&
|
|
RequestSense->SenseKey == 5 &&
|
|
RequestSense->CommandData &&
|
|
RequestSense->BitPointer == 7)
|
|
{
|
|
NumBytes=8; /* send an 8 byte request */
|
|
DataBuffer=SendElementStatusRequestActual( MediumChangerFD,
|
|
RequestSense,
|
|
inquiry_info,
|
|
flags,
|
|
ElementStart,
|
|
NumElements,
|
|
NumBytes
|
|
);
|
|
}
|
|
|
|
/* the above code falls thru into this: */
|
|
if (DataBuffer != NULL)
|
|
{
|
|
/* see if we need to retry with a bigger NumBytes: */
|
|
real_numbytes = ((int)DataBuffer[5] << 16) +
|
|
((int)DataBuffer[6] << 8) +
|
|
(int)DataBuffer[7] + 8;
|
|
|
|
if (real_numbytes > NumBytes)
|
|
{
|
|
/* uh-oh, retry! */
|
|
free(DataBuffer); /* solve memory leak */
|
|
DataBuffer = SendElementStatusRequestActual(MediumChangerFD,
|
|
RequestSense,
|
|
inquiry_info,
|
|
flags,
|
|
ElementStart,
|
|
NumElements,
|
|
real_numbytes
|
|
);
|
|
}
|
|
}
|
|
|
|
return DataBuffer;
|
|
}
|
|
|
|
|
|
|
|
/******************* ParseElementStatus ***********************************/
|
|
/* This does the actual grunt work of parsing element status data. It fills
|
|
* in appropriate pieces of its input data. It may be called multiple times
|
|
* while we are gathering element status.
|
|
*/
|
|
|
|
static void ParseElementStatus( int *EmptyStorageElementAddress,
|
|
int *EmptyStorageElementCount,
|
|
unsigned char *DataBuffer,
|
|
ElementStatus_T *ElementStatus,
|
|
ElementModeSense_T *mode_sense,
|
|
int *pNextElement,
|
|
boolean absolute_address
|
|
)
|
|
{
|
|
unsigned char *DataPointer = DataBuffer;
|
|
TransportElementDescriptor_T TEBuf;
|
|
TransportElementDescriptor_T *TransportElementDescriptor;
|
|
ElementStatusDataHeader_T *ElementStatusDataHeader;
|
|
Element2StatusPage_T *ElementStatusPage;
|
|
Element2StatusPage_T ESBuf;
|
|
int ElementCount;
|
|
int TransportElementDescriptorLength;
|
|
int BytesAvailable;
|
|
int ImportExportIndex;
|
|
|
|
ElementStatusDataHeader = (ElementStatusDataHeader_T *) DataPointer;
|
|
DataPointer += sizeof(ElementStatusDataHeader_T);
|
|
ElementCount =
|
|
BigEndian16(ElementStatusDataHeader->NumberOfElementsAvailable);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"ElementCount=%d\n",ElementCount);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
while (ElementCount > 0)
|
|
{
|
|
#ifdef DEBUG
|
|
int got_element_num=0;
|
|
|
|
fprintf(stderr,"Working on element # %d Element Count %d\n",got_element_num,ElementCount);
|
|
got_element_num++;
|
|
#endif
|
|
|
|
memcpy(&ESBuf, DataPointer, sizeof(ElementStatusPage_T));
|
|
ElementStatusPage = &ESBuf;
|
|
DataPointer += sizeof(ElementStatusPage_T);
|
|
|
|
TransportElementDescriptorLength =
|
|
BigEndian16(ElementStatusPage->ElementDescriptorLength);
|
|
|
|
if (TransportElementDescriptorLength <
|
|
sizeof(TransportElementDescriptorShort_T))
|
|
{
|
|
/* Foo, Storage Element Descriptors can be 4 bytes?! */
|
|
if ((ElementStatusPage->ElementTypeCode != MediumTransportElement &&
|
|
ElementStatusPage->ElementTypeCode != StorageElement &&
|
|
ElementStatusPage->ElementTypeCode != ImportExportElement ) ||
|
|
TransportElementDescriptorLength < 4)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"boom! ElementTypeCode=%d\n",ElementStatusPage->ElementTypeCode);
|
|
#endif
|
|
FatalError("Transport Element Descriptor Length too short: %d\n", TransportElementDescriptorLength);
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Transport Element Descriptor Length=%d\n",TransportElementDescriptorLength);
|
|
#endif
|
|
BytesAvailable =
|
|
BigEndian24(ElementStatusPage->ByteCountOfDescriptorDataAvailable);
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%d bytes of descriptor data available in descriptor\n",
|
|
BytesAvailable);
|
|
#endif
|
|
/* work around a bug in ADIC DAT loaders */
|
|
if (BytesAvailable <= 0)
|
|
{
|
|
ElementCount--; /* sorry :-( */
|
|
}
|
|
while (BytesAvailable > 0)
|
|
{
|
|
/* TransportElementDescriptor =
|
|
(TransportElementDescriptor_T *) DataPointer; */
|
|
memcpy(&TEBuf, DataPointer,
|
|
(TransportElementDescriptorLength <= sizeof(TEBuf)) ?
|
|
TransportElementDescriptorLength :
|
|
sizeof(TEBuf));
|
|
TransportElementDescriptor = &TEBuf;
|
|
|
|
if (pNextElement != NULL)
|
|
{
|
|
if (BigEndian16(TransportElementDescriptor->ElementAddress) != 0 || *pNextElement == 0)
|
|
{
|
|
(*pNextElement) = BigEndian16(TransportElementDescriptor->ElementAddress) + 1;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
DataPointer += TransportElementDescriptorLength;
|
|
BytesAvailable -= TransportElementDescriptorLength;
|
|
ElementCount--;
|
|
|
|
switch (ElementStatusPage->ElementTypeCode)
|
|
{
|
|
case MediumTransportElement:
|
|
ElementStatus->TransportElementAddress = BigEndian16(TransportElementDescriptor->ElementAddress);
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"TransportElementAddress=%d\n",ElementStatus->TransportElementAddress);
|
|
#endif
|
|
break;
|
|
|
|
/* we treat ImportExport elements as if they were normal
|
|
* storage elements now, sigh...
|
|
*/
|
|
case ImportExportElement:
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"ImportExportElement=%d\n",ElementStatus->StorageElementCount);
|
|
#endif
|
|
if (ElementStatus->ImportExportCount >= mode_sense->NumImportExport)
|
|
{
|
|
fprintf(stderr,"Warning:Too Many Import/Export Elements Reported (expected %d, now have %d\n",
|
|
mode_sense->NumImportExport,
|
|
ElementStatus->ImportExportCount + 1);
|
|
fflush(stderr);
|
|
return; /* we're done :-(. */
|
|
}
|
|
|
|
ImportExportIndex = mode_sense->NumStorage - mode_sense->NumImportExport + ElementStatus->ImportExportCount;
|
|
|
|
ElementStatus->StorageElementAddress[ImportExportIndex] =
|
|
BigEndian16(TransportElementDescriptor->ElementAddress);
|
|
ElementStatus->StorageElementFull[ImportExportIndex] =
|
|
TransportElementDescriptor->Full;
|
|
|
|
if ( (TransportElementDescriptorLength > 11) &&
|
|
(ElementStatusPage->VolBits & E2_AVOLTAG))
|
|
{
|
|
copy_barcode(TransportElementDescriptor->AlternateVolumeTag,
|
|
ElementStatus->AlternateVolumeTag[ImportExportIndex]);
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->AlternateVolumeTag[ImportExportIndex][0] = 0; /* null string. */;
|
|
}
|
|
if ((TransportElementDescriptorLength > 11) &&
|
|
(ElementStatusPage->VolBits & E2_PVOLTAG))
|
|
{
|
|
copy_barcode(TransportElementDescriptor->PrimaryVolumeTag,
|
|
ElementStatus->PrimaryVolumeTag[ImportExportIndex]);
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->PrimaryVolumeTag[ImportExportIndex][0]=0; /* null string. */
|
|
}
|
|
|
|
ElementStatus->StorageElementIsImportExport[ImportExportIndex] = 1;
|
|
|
|
ElementStatus->ImportExportCount++;
|
|
break;
|
|
|
|
case StorageElement:
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"StorageElementCount=%d ElementAddress = %d ",ElementStatus->StorageElementCount,BigEndian16(TransportElementDescriptor->ElementAddress));
|
|
#endif
|
|
/* ATL/Exabyte kludge -- skip slots that aren't installed :-( */
|
|
if (TransportElementDescriptor->AdditionalSenseCode==0x83 &&
|
|
TransportElementDescriptor->AdditionalSenseCodeQualifier==0x02)
|
|
continue;
|
|
|
|
ElementStatus->StorageElementAddress[ElementStatus->StorageElementCount] =
|
|
BigEndian16(TransportElementDescriptor->ElementAddress);
|
|
ElementStatus->StorageElementFull[ElementStatus->StorageElementCount] =
|
|
TransportElementDescriptor->Full;
|
|
#ifdef DEBUG
|
|
if (TransportElementDescriptor->Except)
|
|
fprintf(stderr,"ASC,ASCQ = 0x%x,0x%x ",TransportElementDescriptor->AdditionalSenseCode,TransportElementDescriptor->AdditionalSenseCodeQualifier);
|
|
fprintf(stderr,"TransportElement->Full = %d\n",TransportElementDescriptor->Full);
|
|
#endif
|
|
if (!TransportElementDescriptor->Full)
|
|
{
|
|
EmptyStorageElementAddress[(*EmptyStorageElementCount)++] =
|
|
ElementStatus->StorageElementCount; /* slot idx. */
|
|
/* ElementStatus->StorageElementAddress[ElementStatus->StorageElementCount]; */
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->AlternateVolumeTag[ElementStatus->StorageElementCount][0]=0; /* null string. */;
|
|
}
|
|
if ((TransportElementDescriptorLength > 11) &&
|
|
(ElementStatusPage->VolBits & E2_PVOLTAG))
|
|
{
|
|
copy_barcode(TransportElementDescriptor->PrimaryVolumeTag,
|
|
ElementStatus->PrimaryVolumeTag[ElementStatus->StorageElementCount]);
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->PrimaryVolumeTag[ElementStatus->StorageElementCount][0]=0; /* null string. */
|
|
}
|
|
|
|
if (absolute_address){
|
|
copy_physical_location(TransportElementDescriptor->AlternateVolumeTag, ElementStatus->StorageElementPhysicalLocation[ElementStatus->StorageElementCount]);
|
|
}
|
|
|
|
ElementStatus->StorageElementCount++;
|
|
/*
|
|
Note that the original mtx had no check here for
|
|
buffer overflow, though some drives might mistakingly
|
|
do one...
|
|
*/
|
|
|
|
if (ElementStatus->StorageElementCount > mode_sense->NumStorage)
|
|
{
|
|
fprintf(stderr,"Warning:Too Many Storage Elements Reported (expected %d, now have %d\n",
|
|
mode_sense->NumStorage,
|
|
ElementStatus->StorageElementCount);
|
|
fflush(stderr);
|
|
return; /* we're done :-(. */
|
|
}
|
|
break;
|
|
|
|
case DataTransferElement:
|
|
/* tape drive not installed, go back to top of loop */
|
|
|
|
/* if (TransportElementDescriptor->Except) continue ; */
|
|
|
|
/* Note: This is for Exabyte tape libraries that improperly
|
|
report that they have a 2nd tape drive when they don't. We
|
|
could generalize this in an ideal world, but my attempt to
|
|
do so failed with dual-drive Exabyte tape libraries that
|
|
*DID* have the second drive. Sigh.
|
|
*/
|
|
if (TransportElementDescriptor->AdditionalSenseCode==0x83 &&
|
|
TransportElementDescriptor->AdditionalSenseCodeQualifier==0x04)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* generalize it. Does it work? Let's try it! */
|
|
/*
|
|
No, dammit, following does not work on dual-drive Exabyte
|
|
'cause if a tape is in the drive, it sets the AdditionalSense
|
|
code to something (sigh).
|
|
*/
|
|
/* if (TransportElementDescriptor->AdditionalSenseCode!=0)
|
|
continue;
|
|
*/
|
|
|
|
if (absolute_address){
|
|
ElementStatus->DataTransferElementPhysicalLocation[ElementStatus->DataTransferElementCount] =
|
|
BigEndian16(TransportElementDescriptor->SourceStorageElementAddress);
|
|
InquiryShort_T *inqs;
|
|
inqs = (InquiryShort_T *) TransportElementDescriptor->PrimaryVolumeTag;
|
|
copy_char_buffer(inqs->SerialNumber, ElementStatus->DataTransferElementSerialNumber[ElementStatus->DataTransferElementCount], 12);
|
|
copy_char_buffer(inqs->ProductIdentification+2, ElementStatus->DataTransferElementProductId[ElementStatus->DataTransferElementCount], 12);
|
|
ElementStatus->DataTransferElementCount++;
|
|
break;
|
|
}
|
|
ElementStatus->DataTransferElementAddress[ElementStatus->DataTransferElementCount] =
|
|
BigEndian16(TransportElementDescriptor->ElementAddress);
|
|
ElementStatus->DataTransferElementFull[ElementStatus->DataTransferElementCount] =
|
|
TransportElementDescriptor->Full;
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[ElementStatus->DataTransferElementCount] =
|
|
BigEndian16(TransportElementDescriptor->SourceStorageElementAddress);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "%d: ElementAddress = %d, Full = %d, SourceElement = %d\n",
|
|
ElementStatus->DataTransferElementCount,
|
|
ElementStatus->DataTransferElementAddress[ElementStatus->DataTransferElementCount],
|
|
ElementStatus->DataTransferElementFull[ElementStatus->DataTransferElementCount],
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[ElementStatus->DataTransferElementCount]);
|
|
#endif
|
|
if (ElementStatus->DataTransferElementCount >= mode_sense->NumDataTransfer)
|
|
{
|
|
FatalError("Too many Data Transfer Elements Reported\n");
|
|
}
|
|
|
|
if (ElementStatusPage->VolBits & E2_PVOLTAG)
|
|
{
|
|
copy_barcode(TransportElementDescriptor->PrimaryVolumeTag,
|
|
ElementStatus->DataTransferPrimaryVolumeTag[ElementStatus->DataTransferElementCount]);
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->DataTransferPrimaryVolumeTag[ElementStatus->DataTransferElementCount][0]=0; /* null string */
|
|
}
|
|
|
|
if (ElementStatusPage->VolBits & E2_AVOLTAG)
|
|
{
|
|
copy_barcode(TransportElementDescriptor->AlternateVolumeTag,
|
|
ElementStatus->DataTransferAlternateVolumeTag[ElementStatus->DataTransferElementCount]);
|
|
}
|
|
else
|
|
{
|
|
ElementStatus->DataTransferAlternateVolumeTag[ElementStatus->DataTransferElementCount][0]=0; /* null string */
|
|
}
|
|
|
|
ElementStatus->DataTransferElementCount++;
|
|
|
|
/* 0 actually is a usable element address */
|
|
/* if (DataTransferElementAddress == 0) */
|
|
/* FatalError( */
|
|
/* "illegal Data Transfer Element Address %d reported\n", */
|
|
/* DataTransferElementAddress); */
|
|
break;
|
|
|
|
default:
|
|
FatalError("illegal Element Type Code %d reported\n",
|
|
ElementStatusPage->ElementTypeCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (pNextElement)
|
|
fprintf(stderr,"Next start element will be %d\n",*pNextElement);
|
|
#endif
|
|
}
|
|
|
|
|
|
/********************* Real ReadElementStatus ********************* */
|
|
|
|
/*
|
|
* We no longer do the funky trick to figure out ALLOCATION_LENGTH.
|
|
* Instead, we use the SCSI Generic command rather than SEND_SCSI_COMMAND
|
|
* under Linux, which gets around the @#%@ 4k buffer size in Linux.
|
|
* We still have the restriction that Linux cuts off the last two
|
|
* bytes of the SENSE DATA (Q#@$%@#$^ Linux!). Which means that the
|
|
* verbose widget won't work :-(.
|
|
|
|
* We now look for that "attached" bit in the inquiry_info to see whether
|
|
* to use READ_ELEMENT_ATTACHED or plain old READ_ELEMENT. In addition, we
|
|
* look at the device type in the inquiry_info to see whether it is a media
|
|
* changer or tape device, and if it's a media changer device, we ignore the
|
|
* attached bit (one beta tester found an old 4-tape DAT changer that set
|
|
* the attached bit for both the tape device AND the media changer device).
|
|
|
|
*/
|
|
|
|
ElementStatus_T *ReadElementStatus(DEVICE_TYPE MediumChangerFD, RequestSense_T *RequestSense, Inquiry_T *inquiry_info, SCSI_Flags_T *flags)
|
|
{
|
|
ElementStatus_T *ElementStatus;
|
|
|
|
unsigned char *DataBuffer; /* size of data... */
|
|
|
|
int EmptyStorageElementCount=0;
|
|
int *EmptyStorageElementAddress; /* [MAX_STORAGE_ELEMENTS]; */
|
|
|
|
int empty_idx = 0;
|
|
boolean is_attached = false;
|
|
int i,j;
|
|
int FirstElem, NumElements, NumThisRES;
|
|
|
|
ElementModeSense_T *mode_sense = NULL;
|
|
|
|
if (inquiry_info->MChngr && inquiry_info->PeripheralDeviceType != MEDIUM_CHANGER_TYPE)
|
|
{
|
|
is_attached = true;
|
|
}
|
|
|
|
if (flags->no_attached)
|
|
{
|
|
/* override, sigh */
|
|
is_attached = false;
|
|
}
|
|
|
|
if (!is_attached)
|
|
{
|
|
mode_sense = ReadAssignmentPage(MediumChangerFD);
|
|
}
|
|
|
|
if (!mode_sense)
|
|
{
|
|
mode_sense = (ElementModeSense_T *)xmalloc(sizeof(ElementModeSense_T));
|
|
mode_sense->NumMediumTransport = MAX_TRANSPORT_ELEMENTS;
|
|
mode_sense->NumStorage = MAX_STORAGE_ELEMENTS;
|
|
mode_sense->NumDataTransfer = MAX_TRANSFER_ELEMENTS;
|
|
mode_sense->MaxReadElementStatusData =
|
|
(sizeof(ElementStatusDataHeader_T) + 3 * sizeof(ElementStatusPage_T) +
|
|
(MAX_STORAGE_ELEMENTS+MAX_TRANSFER_ELEMENTS+MAX_TRANSPORT_ELEMENTS) *
|
|
sizeof(TransportElementDescriptor_T));
|
|
|
|
/* don't care about the others anyhow at the moment... */
|
|
}
|
|
|
|
ElementStatus = AllocateElementData(mode_sense);
|
|
|
|
/* Now to initialize it (sigh). */
|
|
ElementStatus->StorageElementCount = 0;
|
|
ElementStatus->DataTransferElementCount = 0;
|
|
|
|
/* first, allocate some empty storage stuff: Note that we pass this
|
|
* down to ParseElementStatus (sigh!)
|
|
*/
|
|
|
|
EmptyStorageElementAddress = (int *)xzmalloc((mode_sense->NumStorage+1)*sizeof(int));
|
|
for (i = 0; i < mode_sense->NumStorage; i++)
|
|
{
|
|
EmptyStorageElementAddress[i] = -1;
|
|
}
|
|
|
|
/* Okay, now to send some requests for the various types of stuff: */
|
|
|
|
/* -----------STORAGE ELEMENTS---------------- */
|
|
/* Let's start with storage elements: */
|
|
|
|
for (i = 0; i < mode_sense->NumDataTransfer; i++)
|
|
{
|
|
/* initialize them to an illegal # so that we can fix later... */
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[i] = -1;
|
|
}
|
|
|
|
if (flags->querytype == MTX_ELEMENTSTATUS_ORIGINAL)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Using original element status polling method (storage, import/export, drivers etc independantly)\n");
|
|
fprintf(stderr,"Storage start %d, Num %d, max %d\n", mode_sense->StorageStart, mode_sense->NumStorage - mode_sense->NumImportExport, mode_sense->MaxReadElementStatusData);
|
|
#endif
|
|
flags->elementtype = StorageElement; /* sigh! */
|
|
flags->absolute_addressing = 1;
|
|
|
|
NumElements = mode_sense->NumStorage - mode_sense->NumImportExport;
|
|
FirstElem = mode_sense->StorageStart;
|
|
|
|
do
|
|
{
|
|
NumThisRES = (NumElements > SCSI_RES_ELEMENTS ? SCSI_RES_ELEMENTS : NumElements);
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
|
|
inquiry_info, flags,
|
|
FirstElem,
|
|
/* adjust for import/export. */
|
|
NumThisRES,
|
|
SCSI_RES_ELEMENTS * 52 +120);
|
|
|
|
if (!DataBuffer)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Had no elements!\n");
|
|
#endif
|
|
/* darn. Free up stuff and return. */
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintRequestSense(RequestSense);
|
|
#endif
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "Parsing storage elements\n");
|
|
#endif
|
|
ParseElementStatus(EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer,ElementStatus,mode_sense,NULL, true);
|
|
|
|
free(DataBuffer); /* sigh! */
|
|
FirstElem += SCSI_RES_ELEMENTS;
|
|
NumElements -= SCSI_RES_ELEMENTS;
|
|
} while ( NumElements > 0 );
|
|
|
|
flags->absolute_addressing = 0;
|
|
/* --------------IMPORT/EXPORT--------------- */
|
|
/* Next let's see if we need to do Import/Export: */
|
|
if (mode_sense->NumImportExport > 0)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Sending request for Import/Export status\n");
|
|
#endif
|
|
flags->elementtype = ImportExportElement;
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD,RequestSense,
|
|
inquiry_info, flags,
|
|
mode_sense->ImportExportStart,
|
|
mode_sense->NumImportExport,
|
|
SCSI_RES_ELEMENTS * 52 +120);
|
|
if (!DataBuffer)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Had no input/export element!\n");
|
|
#endif
|
|
/* darn. Free up stuff and return. */
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintRequestSense(RequestSense);
|
|
#endif
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Parsing inport/export element status\n");
|
|
#endif
|
|
#ifdef DEBUG_ADIC
|
|
dump_data(DataBuffer, 100); /* dump some data :-(. */
|
|
#endif
|
|
ParseElementStatus( EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer, ElementStatus, mode_sense, NULL, false);
|
|
free(DataBuffer);
|
|
ElementStatus->StorageElementCount += ElementStatus->ImportExportCount;
|
|
}
|
|
|
|
/* ----------------- DRIVES ---------------------- */
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Sending request for data transfer element (drive) status\n");
|
|
#endif
|
|
flags->elementtype = DataTransferElement; /* sigh! */
|
|
flags->absolute_addressing = 0;
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
|
|
inquiry_info, flags,
|
|
mode_sense->DataTransferStart,
|
|
mode_sense->NumDataTransfer,
|
|
SCSI_RES_ELEMENTS * 52 +120);
|
|
if (!DataBuffer)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"No data transfer element status.");
|
|
#endif
|
|
/* darn. Free up stuff and return. */
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintRequestSense(RequestSense);
|
|
#endif
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Parsing data for data transfer element (drive) status\n");
|
|
#endif
|
|
ParseElementStatus( EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer,ElementStatus, mode_sense, NULL, false);
|
|
|
|
free(DataBuffer); /* sigh! */
|
|
|
|
flags->absolute_addressing = 1;
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
|
|
inquiry_info, flags,
|
|
mode_sense->DataTransferStart,
|
|
mode_sense->NumDataTransfer,
|
|
SCSI_RES_ELEMENTS * 52 +120);
|
|
if (!DataBuffer)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"No data transfer element status.");
|
|
#endif
|
|
/* darn. Free up stuff and return. */
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintRequestSense(RequestSense);
|
|
#endif
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
ElementStatus->DataTransferElementCount = 0;
|
|
ParseElementStatus( EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer,ElementStatus, mode_sense, NULL, true);
|
|
|
|
free(DataBuffer); /* sigh! */
|
|
|
|
/* ----------------- Robot Arm(s) -------------------------- */
|
|
|
|
/* grr, damned brain dead HP doesn't report that it has any! */
|
|
if (!mode_sense->NumMediumTransport)
|
|
{
|
|
ElementStatus->TransportElementAddress = 0; /* default it sensibly :-(. */
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Sending request for robot arm status\n");
|
|
#endif
|
|
flags->elementtype = MediumTransportElement; /* sigh! */
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
|
|
inquiry_info, flags,
|
|
mode_sense->MediumTransportStart,
|
|
1, /* only get 1, sigh. */
|
|
SCSI_RES_ELEMENTS * 52 +120);
|
|
if (!DataBuffer)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Loader reports no robot arm!\n");
|
|
#endif
|
|
/* darn. Free up stuff and return. */
|
|
#ifdef DEBUG_MODE_SENSE
|
|
PrintRequestSense(RequestSense);
|
|
#endif
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Parsing robot arm data\n");
|
|
#endif
|
|
ParseElementStatus( EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer, ElementStatus, mode_sense, NULL, false);
|
|
|
|
free(DataBuffer);
|
|
}
|
|
flags->absolute_addressing = 0;
|
|
}
|
|
else
|
|
{
|
|
int nLastEl=-1, nNextEl=0;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"Using alternative element status polling method (all elements)\n");
|
|
#endif
|
|
/* ----------------- ALL Elements ---------------------- */
|
|
/* Just keep asking for elements till no more are returned
|
|
- increment our starting address as we go acording to the
|
|
number of elements returned from the last call
|
|
*/
|
|
|
|
while(nLastEl!=nNextEl)
|
|
{
|
|
flags->elementtype = AllElementTypes;//StorageElement; /* sigh! */ /*XL1B2 firewire changer does not seem to respond to specific types so just use all elements*/
|
|
DataBuffer = SendElementStatusRequest( MediumChangerFD,
|
|
RequestSense,
|
|
inquiry_info,
|
|
flags,
|
|
nNextEl,//mode_sense->StorageStart,
|
|
/* adjust for import/export. */
|
|
mode_sense->NumStorage - mode_sense->NumImportExport,//FIX ME:this should be a more sensible value
|
|
mode_sense->MaxReadElementStatusData);
|
|
if (!DataBuffer)
|
|
{
|
|
if (RequestSense->AdditionalSenseCode == 0x21 &&
|
|
RequestSense->AdditionalSenseCodeQualifier == 0x01)
|
|
{
|
|
/* Error is invalid element address, we've probably just hit the end */
|
|
break;
|
|
}
|
|
|
|
/* darn. Free up stuff and return. */
|
|
FreeElementData(ElementStatus);
|
|
return NULL;
|
|
}
|
|
|
|
nLastEl = nNextEl;
|
|
|
|
ParseElementStatus( EmptyStorageElementAddress, &EmptyStorageElementCount,
|
|
DataBuffer, ElementStatus, mode_sense, &nNextEl, false);
|
|
|
|
free(DataBuffer); /* sigh! */
|
|
}
|
|
|
|
ElementStatus->StorageElementCount += ElementStatus->ImportExportCount;
|
|
}
|
|
|
|
/*---------------------- Sanity Checking ------------------- */
|
|
|
|
if (ElementStatus->DataTransferElementCount == 0)
|
|
FatalError("no Data Transfer Element reported\n");
|
|
|
|
if (ElementStatus->StorageElementCount == 0)
|
|
FatalError("no Storage Elements reported\n");
|
|
|
|
|
|
/* ---------------------- Reset SourceStorageElementNumbers ------- */
|
|
|
|
/*
|
|
* Re-write the SourceStorageElementNumber code *AGAIN*.
|
|
*
|
|
* Pass1:
|
|
* Translate from raw element # to our translated # (if possible).
|
|
* First, check the SourceStorageElementNumbers against the list of
|
|
* filled slots. If the slots indicated are empty, we accept that list as
|
|
* valid. Otherwise decide the SourceStorageElementNumbers are invalid.
|
|
*
|
|
* Pass2:
|
|
* If we had some invalid (or unknown) SourceStorageElementNumbers
|
|
* then we must search for free slots, and assign SourceStorageElementNumbers
|
|
* to those free slots. We happen to already built a list of free
|
|
* slots as part of the process of reading the storage element numbers
|
|
* from the tape. So that's easy enough to do!
|
|
*/
|
|
|
|
#ifdef DEBUG_TAPELIST
|
|
fprintf(stderr, "empty slots: %d\n", EmptyStorageElementCount);
|
|
if (EmptyStorageElementCount)
|
|
{
|
|
for (i = 0; i < EmptyStorageElementCount; i++)
|
|
{
|
|
fprintf(stderr, "empty: %d\n", EmptyStorageElementAddress[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Now we re-assign origin slots if the "real" origin slot
|
|
* is obviously defective:
|
|
*/
|
|
/* pass one: */
|
|
for (i = 0; i < ElementStatus->DataTransferElementCount; i++)
|
|
{
|
|
int elnum;
|
|
|
|
/* if we have an element, then ... */
|
|
if (ElementStatus->DataTransferElementFull[i])
|
|
{
|
|
elnum = ElementStatus->DataTransferElementSourceStorageElementNumber[i];
|
|
/* if we have an element number, then ... */
|
|
if (elnum >= 0)
|
|
{
|
|
/* Now to translate the elnum: */
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[i] = -1;
|
|
for (j = 0; j < ElementStatus->StorageElementCount; j++)
|
|
{
|
|
if (elnum == ElementStatus->StorageElementAddress[j])
|
|
{
|
|
/* now see if the element # is already occupied:*/
|
|
if (!ElementStatus->StorageElementFull[j])
|
|
{
|
|
/* properly set the source... */
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[i] = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Pass2: */
|
|
/* We have invalid sources, so let's see what they should be: */
|
|
/* Note: If EmptyStorageElementCount is < # of drives, the leftover
|
|
* drives will be assigned a -1 (see the initialization loop for
|
|
* EmptyStorageElementAddress above), which will be reported as "slot 0"
|
|
* by the user interface. This is an invalid value, but more useful for us
|
|
* to have than just crapping out here :-(.
|
|
*/
|
|
empty_idx=0;
|
|
for (i = 0; i < ElementStatus->DataTransferElementCount; i++)
|
|
{
|
|
if (ElementStatus->DataTransferElementFull[i] &&
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[i] < 0)
|
|
{
|
|
#ifdef DEBUG_TAPELIST
|
|
fprintf(stderr,"for drive %d, changing to %d (empty slot #%d)\n",
|
|
i,
|
|
EmptyStorageElementAddress[empty_idx],
|
|
empty_idx);
|
|
#endif
|
|
ElementStatus->DataTransferElementSourceStorageElementNumber[i] =
|
|
EmptyStorageElementAddress[empty_idx++];
|
|
}
|
|
}
|
|
|
|
/* and done! */
|
|
free(mode_sense);
|
|
free(EmptyStorageElementAddress);
|
|
return ElementStatus;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
RequestSense_T *PositionElement(DEVICE_TYPE MediumChangerFD,
|
|
int DestinationAddress,
|
|
ElementStatus_T *ElementStatus)
|
|
{
|
|
RequestSense_T *RequestSense = xmalloc(sizeof(RequestSense_T));
|
|
CDB_T CDB;
|
|
|
|
CDB[0] = 0x2b;
|
|
CDB[1] = 0;
|
|
CDB[2] = (unsigned char)(ElementStatus->TransportElementAddress >> 8);
|
|
CDB[3] = (unsigned char)ElementStatus->TransportElementAddress;
|
|
CDB[4] = (unsigned char)(DestinationAddress >> 8);
|
|
CDB[5] = (unsigned char)DestinationAddress;
|
|
CDB[6] = 0;
|
|
CDB[7] = 0;
|
|
CDB[8] = 0;
|
|
CDB[9] = 0;
|
|
|
|
if(SCSI_ExecuteCommand( MediumChangerFD, Output, &CDB, 10,
|
|
NULL, 0, RequestSense) != 0)
|
|
{
|
|
return RequestSense;
|
|
}
|
|
free(RequestSense);
|
|
return NULL; /* success */
|
|
}
|
|
|
|
|
|
/* Now the actual media movement routine! */
|
|
RequestSense_T *MoveMedium( DEVICE_TYPE MediumChangerFD, int SourceAddress,
|
|
int DestinationAddress,
|
|
ElementStatus_T *ElementStatus,
|
|
Inquiry_T *inquiry_info, SCSI_Flags_T *flags)
|
|
{
|
|
RequestSense_T *RequestSense = xmalloc(sizeof(RequestSense_T));
|
|
CDB_T CDB;
|
|
|
|
if (inquiry_info->MChngr && inquiry_info->PeripheralDeviceType != MEDIUM_CHANGER_TYPE)
|
|
{
|
|
/* if using the ATTACHED API */
|
|
CDB[0] = 0xA7; /* MOVE_MEDIUM_ATTACHED */
|
|
}
|
|
else
|
|
{
|
|
CDB[0] = 0xA5; /* MOVE MEDIUM */
|
|
}
|
|
|
|
CDB[1] = 0; /* Reserved */
|
|
|
|
/* Transport Element Address */
|
|
CDB[2] = (unsigned char)(ElementStatus->TransportElementAddress >> 8);
|
|
CDB[3] = (unsigned char)ElementStatus->TransportElementAddress;
|
|
|
|
/* Source Address */
|
|
CDB[4] = (unsigned char)(SourceAddress >> 8);
|
|
CDB[5] = (unsigned char)SourceAddress;
|
|
|
|
/* Destination Address */
|
|
CDB[6] = (unsigned char)(DestinationAddress >> 8);
|
|
CDB[7] = (unsigned char)DestinationAddress;
|
|
|
|
CDB[8] = 0; /* Reserved */
|
|
CDB[9] = 0; /* Reserved */
|
|
|
|
if (flags->invert)
|
|
{
|
|
CDB[10] = 1; /* Reserved */
|
|
}
|
|
else
|
|
{
|
|
CDB[10] = 0;
|
|
}
|
|
/* eepos controls the tray for import/export elements, sometimes. */
|
|
CDB[11] = flags->eepos << 6; /* Control */
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Output, &CDB, 12,
|
|
NULL, 0, RequestSense) != 0)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "Move Medium (0x%02X) failed\n", CDB[0]);
|
|
#endif
|
|
return RequestSense;
|
|
}
|
|
|
|
free(RequestSense);
|
|
return NULL; /* success! */
|
|
}
|
|
|
|
|
|
/* Now the actual Exchange Medium routine! */
|
|
RequestSense_T *ExchangeMedium( DEVICE_TYPE MediumChangerFD, int SourceAddress,
|
|
int DestinationAddress, int Dest2Address,
|
|
ElementStatus_T *ElementStatus,
|
|
SCSI_Flags_T *flags)
|
|
{
|
|
RequestSense_T *RequestSense = xmalloc(sizeof(RequestSense_T));
|
|
CDB_T CDB;
|
|
|
|
CDB[0] = 0xA6; /* EXCHANGE MEDIUM */
|
|
CDB[1] = 0; /* Reserved */
|
|
|
|
/* Transport Element Address */
|
|
CDB[2] = (unsigned char)(ElementStatus->TransportElementAddress >> 8);
|
|
CDB[3] = (unsigned char)ElementStatus->TransportElementAddress;
|
|
|
|
/* Source Address */
|
|
CDB[4] = (unsigned char)(SourceAddress >> 8);
|
|
CDB[5] = (unsigned char)SourceAddress;
|
|
|
|
/* Destination Address */
|
|
CDB[6] = (unsigned char)(DestinationAddress >> 8);
|
|
CDB[7] = (unsigned char)DestinationAddress;
|
|
|
|
/* move destination back to source? */
|
|
CDB[8] = (unsigned char)(Dest2Address >> 8);
|
|
CDB[9] = (unsigned char)Dest2Address;
|
|
CDB[10] = 0;
|
|
|
|
if (flags->invert)
|
|
{
|
|
CDB[10] |= 2; /* INV2 */
|
|
}
|
|
|
|
if (flags->invert2)
|
|
{
|
|
CDB[1] |= 1; /* INV1 */
|
|
}
|
|
|
|
/* eepos controls the tray for import/export elements, sometimes. */
|
|
CDB[11] = flags->eepos << 6; /* Control */
|
|
|
|
#ifdef DEBUG_EXCHANGE
|
|
dump_cdb(&CDB,12);
|
|
#endif
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Output, &CDB, 12,
|
|
NULL, 0, RequestSense) != 0)
|
|
{
|
|
return RequestSense;
|
|
}
|
|
free(RequestSense);
|
|
return NULL; /* success! */
|
|
}
|
|
|
|
|
|
/*
|
|
* for Linux, this creates a way to do a short erase... the @#$%@ st.c
|
|
* driver defaults to doing a long erase!
|
|
*/
|
|
|
|
RequestSense_T *Erase(DEVICE_TYPE MediumChangerFD)
|
|
{
|
|
RequestSense_T *RequestSense = xmalloc(sizeof(RequestSense_T));
|
|
CDB_T CDB;
|
|
|
|
CDB[0] = 0x19;
|
|
CDB[1] = 0; /* Short! */
|
|
CDB[2] = CDB[3] = CDB[4] = CDB[5] = 0;
|
|
|
|
if (SCSI_ExecuteCommand(MediumChangerFD, Output, &CDB, 6, NULL, 0, RequestSense) != 0)
|
|
{
|
|
/* If error is UNIT ATTENTION then retry the request */
|
|
if (RequestSense->ErrorCode != 0x70 || RequestSense->SenseKey != 6 ||
|
|
ClearUnitAttention(MediumChangerFD, RequestSense) != 0 ||
|
|
SCSI_ExecuteCommand(MediumChangerFD, Output, &CDB, 6, NULL, 0, RequestSense) != 0)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "Erase (0x19) failed\n");
|
|
#endif
|
|
return RequestSense;
|
|
}
|
|
}
|
|
|
|
free(RequestSense);
|
|
return NULL; /* Success! */
|
|
}
|
|
|
|
/* Routine to send an LOAD/UNLOAD from the MMC/SSC spec to a device.
|
|
* For tapes and changers this can be used either to eject a tape
|
|
* or to eject a magazine (on some Seagate changers, when sent to LUN 1 ).
|
|
* For CD/DVDs this is used to Load or Unload a disc which is required by
|
|
* some media changers.
|
|
*/
|
|
|
|
int LoadUnload(DEVICE_TYPE fd, int bLoad)
|
|
{
|
|
CDB_T CDB;
|
|
/* okay, now for the command: */
|
|
|
|
CDB[0] = 0x1B;
|
|
CDB[4] = bLoad ? 3 : 2;
|
|
CDB[1] = CDB[2] = CDB[3] = CDB[5] = 0;
|
|
|
|
if (SCSI_ExecuteCommand(fd, Input, &CDB, 6, NULL, 0, &scsi_error_sense) != 0)
|
|
{
|
|
/* If error is UNIT ATTENTION then retry the request */
|
|
if (scsi_error_sense.ErrorCode != 0x70 || scsi_error_sense.SenseKey != 6 ||
|
|
ClearUnitAttention(fd, &scsi_error_sense) != 0 ||
|
|
SCSI_ExecuteCommand(fd, Input, &CDB, 6, NULL, 0, &scsi_error_sense) != 0)
|
|
{
|
|
PrintRequestSense(&scsi_error_sense);
|
|
fprintf(stderr, "Eject (0x1B) failed\n");
|
|
return -1; /* could not do! */
|
|
}
|
|
}
|
|
return 0; /* did do! */
|
|
}
|
|
|
|
/* Routine to send an START/STOP from the MMC/SSC spec to a device.
|
|
* For tape drives this may be required prior to using the changer
|
|
* Load or Unload commands.
|
|
* For CD/DVD drives this is used to Load or Unload a disc which may be
|
|
* required by some media changers.
|
|
*/
|
|
|
|
int StartStop(DEVICE_TYPE fd, int bStart)
|
|
{
|
|
CDB_T CDB;
|
|
/* okay, now for the command: */
|
|
|
|
CDB[0] = 0x1B;
|
|
CDB[4] = bStart ? 1 : 0;
|
|
CDB[1] = CDB[2] = CDB[3] = CDB[5] = 0;
|
|
|
|
if (SCSI_ExecuteCommand(fd, Input, &CDB, 6,NULL, 0, &scsi_error_sense) != 0)
|
|
{
|
|
PrintRequestSense(&scsi_error_sense);
|
|
fprintf(stderr, "Eject (0x1B) failed\n");
|
|
return -1; /* could not do! */
|
|
}
|
|
return 0; /* did do! */
|
|
}
|
|
|
|
/* Routine to send a LOCK/UNLOCK from the SSC/MMC spec to a device.
|
|
* This can be used to prevent or allow the Tape or CD/DVD from being
|
|
* removed.
|
|
*/
|
|
|
|
int LockUnlock(DEVICE_TYPE fd, int bLock)
|
|
{
|
|
CDB_T CDB;
|
|
/* okay, now for the command: */
|
|
|
|
CDB[0] = 0x1E;
|
|
CDB[1] = CDB[2] = CDB[3] = CDB[5] = 0;
|
|
CDB[4] = (char)bLock;
|
|
|
|
if (SCSI_ExecuteCommand(fd, Input, &CDB, 6, NULL, 0, &scsi_error_sense) != 0)
|
|
{
|
|
PrintRequestSense(&scsi_error_sense);
|
|
fprintf(stderr, "Lock/Unlock (0x1E) failed\n");
|
|
return -1; /* could not do! */
|
|
}
|
|
return 0; /* did do! */
|
|
}
|
|
|
|
int ClearUnitAttention(DEVICE_TYPE fd, RequestSense_T *RequestSense)
|
|
{
|
|
CDB_T CDB;
|
|
int RetryCount = 10; /* Unit Attentions may be stacked */
|
|
RequestSense_T unit_attention_sense;
|
|
|
|
CDB[0] = 0x03; /* Request Sense */
|
|
CDB[4] = (char)sizeof(*RequestSense);
|
|
CDB[1] = CDB[2] = CDB[3] = CDB[5] = 0;
|
|
|
|
while (RetryCount-- > 0)
|
|
{
|
|
if (SCSI_ExecuteCommand(fd, Input, &CDB, 6,
|
|
&unit_attention_sense, sizeof(unit_attention_sense),
|
|
RequestSense) != 0)
|
|
{
|
|
fprintf(stderr, "RequestSense (0x03) failed\n");
|
|
return -1; /* could not do! */
|
|
}
|
|
|
|
if (unit_attention_sense.SenseKey == 0)
|
|
{
|
|
/* If SenseKey is NO SENSE then we are done. */
|
|
return 0;
|
|
}
|
|
}
|
|
return -1; /* did do! */
|
|
|
|
}
|
|
|
|
/* Test unit ready: This will tell us whether the tape drive
|
|
* is currently ready to read or write.
|
|
*/
|
|
|
|
int testUnitReady(DEVICE_TYPE fd)
|
|
{
|
|
RequestSense_T sense;
|
|
CDB_T CDB;
|
|
unsigned char buffer[6];
|
|
|
|
CDB[0] = 0x00; /* TEST_UNIT_READY */
|
|
CDB[1] = 0;
|
|
CDB[2] = 0;
|
|
CDB[3] = 0; /* 1-5 all unused. */
|
|
CDB[4] = 0;
|
|
CDB[5] = 0;
|
|
|
|
slow_bzero((char *)&sense,sizeof(RequestSense_T));
|
|
return SCSI_ExecuteCommand(fd,Input,&CDB,6,buffer,0,&sense);
|
|
}
|
|
|
|
static char Spaces[] = " ";
|
|
|
|
void PrintHex(int Indent, unsigned char *Buffer, int Length)
|
|
{
|
|
int idxBuffer;
|
|
int idxAscii;
|
|
int PadLength;
|
|
char cAscii;
|
|
|
|
for (idxBuffer = 0; idxBuffer < Length; idxBuffer++)
|
|
{
|
|
if ((idxBuffer % 16) == 0)
|
|
{
|
|
if (idxBuffer > 0)
|
|
{
|
|
fputc('\'', stderr);
|
|
|
|
for (idxAscii = idxBuffer - 16; idxAscii < idxBuffer; idxAscii++)
|
|
{
|
|
cAscii = Buffer[idxAscii] >= 0x20 && Buffer[idxAscii] < 0x7F ? Buffer[idxAscii] : '.';
|
|
fputc(cAscii, stderr);
|
|
}
|
|
fputs("'\n", stderr);
|
|
}
|
|
fprintf(stderr, "%.*s%04X: ", Indent, Spaces, idxBuffer);
|
|
}
|
|
fprintf(stderr, "%02X ", (unsigned char)Buffer[idxBuffer]);
|
|
}
|
|
|
|
PadLength = 16 - (Length % 16);
|
|
|
|
if (PadLength > 0)
|
|
{
|
|
fprintf(stderr, "%.*s'", 3 * PadLength, Spaces);
|
|
|
|
for (idxAscii = idxBuffer - (16 - PadLength); idxAscii < idxBuffer; idxAscii++)
|
|
{
|
|
cAscii = Buffer[idxAscii] >= 0x20 && Buffer[idxAscii] < 0x80 ? Buffer[idxAscii] : '.';
|
|
fputc(cAscii, stderr);
|
|
}
|
|
fputs("'\n", stderr);
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|
|
|
|
static char *sense_keys[] =
|
|
{
|
|
"No Sense", /* 00 */
|
|
"Recovered Error", /* 01 */
|
|
"Not Ready", /* 02 */
|
|
"Medium Error", /* 03 */
|
|
"Hardware Error", /* 04 */
|
|
"Illegal Request", /* 05 */
|
|
"Unit Attention", /* 06 */
|
|
"Data Protect", /* 07 */
|
|
"Blank Check", /* 08 */
|
|
"0x09", /* 09 */
|
|
"0x0a", /* 0a */
|
|
"Aborted Command", /* 0b */
|
|
"0x0c", /* 0c */
|
|
"Volume Overflow", /* 0d */
|
|
"Miscompare", /* 0e */
|
|
"0x0f" /* 0f */
|
|
};
|
|
|
|
static char Yes[] = "yes";
|
|
static char No[] = "no";
|
|
|
|
void PrintRequestSense(RequestSense_T *RequestSense)
|
|
{
|
|
char *msg;
|
|
|
|
fprintf(stderr, "mtx: Request Sense: Long Report=yes\n");
|
|
fprintf(stderr, "mtx: Request Sense: Valid Residual=%s\n", RequestSense->Valid ? Yes : No);
|
|
|
|
if (RequestSense->ErrorCode == 0x70)
|
|
{
|
|
msg = "Current" ;
|
|
}
|
|
else if (RequestSense->ErrorCode == 0x71)
|
|
{
|
|
msg = "Deferred" ;
|
|
}
|
|
else
|
|
{
|
|
msg = "Unknown?!" ;
|
|
}
|
|
|
|
fprintf(stderr, "mtx: Request Sense: Error Code=%0x (%s)\n", RequestSense->ErrorCode, msg);
|
|
fprintf(stderr, "mtx: Request Sense: Sense Key=%s\n", sense_keys[RequestSense->SenseKey]);
|
|
fprintf(stderr, "mtx: Request Sense: FileMark=%s\n", RequestSense->Filemark ? Yes : No);
|
|
fprintf(stderr, "mtx: Request Sense: EOM=%s\n", RequestSense->EOM ? Yes : No);
|
|
fprintf(stderr, "mtx: Request Sense: ILI=%s\n", RequestSense->ILI ? Yes : No);
|
|
|
|
if (RequestSense->Valid)
|
|
{
|
|
fprintf(stderr, "mtx: Request Sense: Residual = %02X %02X %02X %02X\n",RequestSense->Information[0],RequestSense->Information[1],RequestSense->Information[2],RequestSense->Information[3]);
|
|
}
|
|
|
|
fprintf(stderr,"mtx: Request Sense: Additional Sense Code = %02X\n", RequestSense->AdditionalSenseCode);
|
|
fprintf(stderr,"mtx: Request Sense: Additional Sense Qualifier = %02X\n", RequestSense->AdditionalSenseCodeQualifier);
|
|
|
|
if (RequestSense->SKSV)
|
|
{
|
|
fprintf(stderr,"mtx: Request Sense: Field in Error = %02X\n", RequestSense->BitPointer);
|
|
}
|
|
|
|
fprintf(stderr, "mtx: Request Sense: BPV=%s\n", RequestSense->BPV ? Yes : No);
|
|
fprintf(stderr, "mtx: Request Sense: Error in CDB=%s\n", RequestSense->CommandData ? Yes : No);
|
|
fprintf(stderr, "mtx: Request Sense: SKSV=%s\n", RequestSense->SKSV ? Yes : No);
|
|
|
|
if (RequestSense->BPV || RequestSense -> SKSV)
|
|
{
|
|
fprintf(stderr, "mtx: Request Sense: Field Pointer = %02X %02X\n",
|
|
RequestSense->FieldData[0], RequestSense->FieldData[1]);
|
|
}
|
|
|
|
fflush(stderr);
|
|
}
|