summaryrefslogtreecommitdiff
path: root/mtxl.c
diff options
context:
space:
mode:
Diffstat (limited to 'mtxl.c')
-rw-r--r--mtxl.c1907
1 files changed, 1907 insertions, 0 deletions
diff --git a/mtxl.c b/mtxl.c
new file mode 100644
index 0000000..3ae77a6
--- /dev/null
+++ b/mtxl.c
@@ -0,0 +1,1907 @@
+/* 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_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 */
+
+/* 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);
+#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;
+}
+
+static void FreeElementData(ElementStatus_T *data)
+{
+ free(data->DataTransferElementAddress);
+ free(data->DataTransferElementSourceStorageElementNumber);
+ 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->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 */
+}
+
+/* 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 */
+
+ /* 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, 40);
+#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
+ )
+{
+ 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]; */
+ }
+ if ((TransportElementDescriptorLength > 11) &&
+ (ElementStatusPage->VolBits & E2_AVOLTAG))
+ {
+ copy_barcode(TransportElementDescriptor->AlternateVolumeTag,
+ ElementStatus->AlternateVolumeTag[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. */
+ }
+
+ 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;
+ */
+
+ ElementStatus->DataTransferElementAddress[ElementStatus->DataTransferElementCount] =
+ BigEndian16(TransportElementDescriptor->ElementAddress);
+ ElementStatus->DataTransferElementFull[ElementStatus->DataTransferElementCount] =
+ TransportElementDescriptor->Full;
+ ElementStatus->DataTransferElementSourceStorageElementNumber[ElementStatus->DataTransferElementCount] =
+ BigEndian16(TransportElementDescriptor->SourceStorageElementAddress);
+
+#if 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;
+
+ 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");
+#endif
+ flags->elementtype = StorageElement; /* sigh! */
+ DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
+ inquiry_info, flags,
+ mode_sense->StorageStart,
+ /* adjust for import/export. */
+ mode_sense->NumStorage - mode_sense->NumImportExport,
+ mode_sense->MaxReadElementStatusData);
+
+ 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);
+
+ free(DataBuffer); /* sigh! */
+
+ /* --------------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,
+ mode_sense->MaxReadElementStatusData);
+
+ 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);
+
+ ElementStatus->StorageElementCount += ElementStatus->ImportExportCount;
+ }
+
+ /* ----------------- DRIVES ---------------------- */
+
+#ifdef DEBUG
+ fprintf(stderr,"Sending request for data transfer element (drive) status\n");
+#endif
+ flags->elementtype = DataTransferElement; /* sigh! */
+ DataBuffer = SendElementStatusRequest( MediumChangerFD, RequestSense,
+ inquiry_info, flags,
+ mode_sense->DataTransferStart,
+ mode_sense->NumDataTransfer,
+ mode_sense->MaxReadElementStatusData);
+ 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);
+
+ 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. */
+ mode_sense->MaxReadElementStatusData);
+ 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);
+
+ free(DataBuffer);
+ }
+ }
+ 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);
+
+ 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! */
+
+}
+
+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);
+}