/* MTX -- SCSI Tape Attached Medium Changer Control Program Copyright 1997-1998 by Leonard N. Zubkoff 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. The author respectfully requests that any modifications to this software be sent directly to him for evaluation and testing. Thanks to Philip A. Prindeville of Enteka Enterprise Technology Service for porting MTX to Solaris/SPARC. Thanks to Carsten Koch for porting MTX to SGI IRIX. Thanks to TECSys Development, Inc. for porting MTX to Digital Unix and OpenVMS. */ #ifdef __linux__ #include #include #include #include #include #include #include #include #endif #ifdef __sun__ #include #include #include #include #include #include #include #include #endif #ifdef __sgi #include #include #include #include #include #include #include #include #include #include #endif #if ((defined(__alpha) && defined(__osf__)) || \ defined(ultrix) || defined(__ultrix)) #include "du/defs.h" #endif #ifdef VMS #include "[.vms]defs.h" #endif #ifdef __i386__ #define LITTLE_ENDIAN_BITFIELDS #endif #ifdef __alpha__ #define LITTLE_ENDIAN_BITFIELDS #endif #ifdef __sparc__ #define BIG_ENDIAN_BITFIELDS #endif #ifdef __mips__ #define BIG_ENDIAN_BITFIELDS #endif #ifdef DIGITAL_UNIX #define LITTLE_ENDIAN_BITFIELDS #endif #ifdef VMS #define LITTLE_ENDIAN_BITFIELDS #endif #define MaxStorageElements 16 typedef enum { false, true } boolean; typedef enum { Input, Output } Direction_T; typedef unsigned char CDB_T[12]; typedef struct Inquiry { #ifdef LITTLE_ENDIAN_BITFIELDS unsigned char PeripheralDeviceType:5; /* Byte 0 Bits 0-4 */ unsigned char PeripheralQualifier:3; /* Byte 0 Bits 5-7 */ unsigned char DeviceTypeModifier:7; /* Byte 1 Bits 0-6 */ boolean RMB:1; /* Byte 1 Bit 7 */ unsigned char ANSI_ApprovedVersion:3; /* Byte 2 Bits 0-2 */ unsigned char ECMA_Version:3; /* Byte 2 Bits 3-5 */ unsigned char ISO_Version:2; /* Byte 2 Bits 6-7 */ unsigned char ResponseDataFormat:4; /* Byte 3 Bits 0-3 */ unsigned char :2; /* Byte 3 Bits 4-5 */ boolean TrmIOP:1; /* Byte 3 Bit 6 */ boolean AENC:1; /* Byte 3 Bit 7 */ #else unsigned char PeripheralQualifier:3; /* Byte 0 Bits 5-7 */ unsigned char PeripheralDeviceType:5; /* Byte 0 Bits 0-4 */ boolean RMB:1; /* Byte 1 Bit 7 */ unsigned char DeviceTypeModifier:7; /* Byte 1 Bits 0-6 */ unsigned char ISO_Version:2; /* Byte 2 Bits 6-7 */ unsigned char ECMA_Version:3; /* Byte 2 Bits 3-5 */ unsigned char ANSI_ApprovedVersion:3; /* Byte 2 Bits 0-2 */ boolean AENC:1; /* Byte 3 Bit 7 */ boolean TrmIOP:1; /* Byte 3 Bit 6 */ unsigned char :2; /* Byte 3 Bits 4-5 */ unsigned char ResponseDataFormat:4; /* Byte 3 Bits 0-3 */ #endif unsigned char AdditionalLength; /* Byte 4 */ unsigned char :8; /* Byte 5 */ unsigned char :8; /* Byte 6 */ #ifdef LITTLE_ENDIAN_BITFIELDS boolean SftRe:1; /* Byte 7 Bit 0 */ boolean CmdQue:1; /* Byte 7 Bit 1 */ boolean :1; /* Byte 7 Bit 2 */ boolean Linked:1; /* Byte 7 Bit 3 */ boolean Sync:1; /* Byte 7 Bit 4 */ boolean WBus16:1; /* Byte 7 Bit 5 */ boolean WBus32:1; /* Byte 7 Bit 6 */ boolean RelAdr:1; /* Byte 7 Bit 7 */ #else boolean RelAdr:1; /* Byte 7 Bit 7 */ boolean WBus32:1; /* Byte 7 Bit 6 */ boolean WBus16:1; /* Byte 7 Bit 5 */ boolean Sync:1; /* Byte 7 Bit 4 */ boolean Linked:1; /* Byte 7 Bit 3 */ boolean :1; /* Byte 7 Bit 2 */ boolean CmdQue:1; /* Byte 7 Bit 1 */ boolean SftRe:1; /* Byte 7 Bit 0 */ #endif unsigned char VendorIdentification[8]; /* Bytes 8-15 */ unsigned char ProductIdentification[16]; /* Bytes 16-31 */ unsigned char ProductRevisionLevel[4]; /* Bytes 32-35 */ } Inquiry_T; typedef struct RequestSense { #ifdef LITTLE_ENDIAN_BITFIELDS unsigned char ErrorCode:7; /* Byte 0 Bits 0-6 */ boolean Valid:1; /* Byte 0 Bit 7 */ #else boolean Valid:1; /* Byte 0 Bit 7 */ unsigned char ErrorCode:7; /* Byte 0 Bits 0-6 */ #endif unsigned char SegmentNumber; /* Byte 1 */ #ifdef LITTLE_ENDIAN_BITFIELDS unsigned char SenseKey:4; /* Byte 2 Bits 0-3 */ unsigned char :1; /* Byte 2 Bit 4 */ boolean ILI:1; /* Byte 2 Bit 5 */ boolean EOM:1; /* Byte 2 Bit 6 */ boolean Filemark:1; /* Byte 2 Bit 7 */ #else boolean Filemark:1; /* Byte 2 Bit 7 */ boolean EOM:1; /* Byte 2 Bit 6 */ boolean ILI:1; /* Byte 2 Bit 5 */ unsigned char :1; /* Byte 2 Bit 4 */ unsigned char SenseKey:4; /* Byte 2 Bits 0-3 */ #endif unsigned char Information[4]; /* Bytes 3-6 */ unsigned char AdditionalSenseLength; /* Byte 7 */ unsigned char CommandSpecificInformation[4]; /* Bytes 8-11 */ unsigned char AdditionalSenseCode; /* Byte 12 */ unsigned char AdditionalSenseCodeQualifier; /* Byte 13 */ unsigned char :8; /* Byte 14 */ unsigned char :8; /* Byte 15 */ unsigned char :8; /* Byte 16 */ unsigned char :8; /* Byte 17 */ } RequestSense_T; typedef enum ElementTypeCode { AllElementTypes = 0, MediumTransportElement = 1, StorageElement = 2, ImportExportElement = 3, DataTransferElement = 4 } ElementTypeCode_T; typedef struct ElementStatusDataHeader { unsigned char FirstElementAddressReported[2]; /* Bytes 0-1 */ unsigned char NumberOfElementsAvailable[2]; /* Bytes 2-3 */ unsigned char :8; /* Byte 4 */ unsigned char ByteCountOfReportAvailable[3]; /* Bytes 5-7 */ } ElementStatusDataHeader_T; typedef struct ElementStatusPage { ElementTypeCode_T ElementTypeCode:8; /* Byte 0 */ #ifdef LITTLE_ENDIAN_BITFIELDS unsigned char :6; /* Byte 1 Bits 0-5 */ boolean AVolTag:1; /* Byte 1 Bit 6 */ boolean PVolTag:1; /* Byte 1 Bit 7 */ #else boolean PVolTag:1; /* Byte 1 Bit 7 */ boolean AVolTag:1; /* Byte 1 Bit 6 */ unsigned char :6; /* Byte 1 Bits 0-5 */ #endif unsigned char ElementDescriptorLength[2]; /* Bytes 2-3 */ unsigned char :8; /* Byte 4 */ unsigned char ByteCountOfDescriptorDataAvailable[3]; /* Bytes 5-7 */ } ElementStatusPage_T; typedef struct TransportElementDescriptor { unsigned char ElementAddress[2]; /* Bytes 0-1 */ #ifdef LITTLE_ENDIAN_BITFIELDS boolean Full:1; /* Byte 2 Bit 0 */ unsigned char :1; /* Byte 2 Bit 1 */ boolean Except:1; /* Byte 2 Bit 2 */ unsigned char :5; /* Byte 2 Bits 3-7 */ #else unsigned char :5; /* Byte 2 Bits 3-7 */ boolean Except:1; /* Byte 2 Bit 2 */ unsigned char :1; /* Byte 2 Bit 1 */ boolean Full:1; /* Byte 2 Bit 0 */ #endif unsigned char :8; /* Byte 3 */ unsigned char AdditionalSenseCode; /* Byte 4 */ unsigned char AdditionalSenseCodeQualifer; /* Byte 5 */ unsigned char :8; /* Byte 6 */ unsigned char :8; /* Byte 7 */ unsigned char :8; /* Byte 8 */ #ifdef LITTLE_ENDIAN_BITFIELDS unsigned char :6; /* Byte 9 Bits 0-5 */ boolean SValid:1; /* Byte 9 Bit 6 */ boolean Invert:1; /* Byte 9 Bit 7 */ #else boolean Invert:1; /* Byte 9 Bit 7 */ boolean SValid:1; /* Byte 9 Bit 6 */ unsigned char :6; /* Byte 9 Bits 0-5 */ #endif unsigned char SourceStorageElementAddress[2]; /* Bytes 10-11 */ } TransportElementDescriptor_T; static boolean DataTransferElementFull, StorageElementFull[1+MaxStorageElements]; static int MediumChangerFD, StorageElementCount, DataTransferElementAddress, DataTransferElementSourceStorageElementNumber, StorageElementAddress[1+MaxStorageElements]; static void FatalError(char *ErrorMessage, ...) { char FormatBuffer[1024], *SourcePointer, *TargetPointer = FormatBuffer; int Character, LastCharacter = '\0'; va_list ArgumentPointer; va_start(ArgumentPointer, ErrorMessage); SourcePointer = "mtx: "; while ((Character = *SourcePointer++) != '\0') *TargetPointer++ = Character; while ((Character = *ErrorMessage++) != '\0') if (LastCharacter == '%') { if (Character == 'm') { SourcePointer = strerror(errno); while ((Character = *SourcePointer++) != '\0') *TargetPointer++ = Character; } else { *TargetPointer++ = '%'; *TargetPointer++ = Character; } LastCharacter = '\0'; } else { if (Character != '%') *TargetPointer++ = Character; LastCharacter = Character; } *TargetPointer = '\0'; vfprintf(stderr, FormatBuffer, ArgumentPointer); va_end(ArgumentPointer); #ifndef VMS exit(1); #else sys$exit(VMS_ExitCode); #endif } static void *xmalloc(Size) size_t Size; { void *Result = (void *) malloc(Size); if (Result == NULL) FatalError("cannot allocate %d bytes of memory\n", Size); return Result; } static int min(int x, int y) { return (x < y ? x : y); } static int max(int x, int y) { return (x > y ? x : y); } #ifdef __linux__ static int SCSI_OpenDevice(char *DeviceName) { int DeviceFD = open(DeviceName, O_RDWR); if (DeviceFD < 0) FatalError("cannot open SCSI device '%s' - %m\n", DeviceName); return DeviceFD; } static void SCSI_CloseDevice(char *DeviceName, int DeviceFD) { if (close(DeviceFD) < 0) FatalError("cannot close SCSI device '%s' - %m\n", DeviceName); } static int SCSI_ExecuteCommand(int DeviceFD, Direction_T Direction, CDB_T CDB, int CDB_Length, void *DataBuffer, int DataBufferLength, RequestSense_T *RequestSense) { unsigned char *Command; int Zero = 0, Result; memset(RequestSense, 0, sizeof(RequestSense_T)); switch (Direction) { case Input: Command = (unsigned char *) xmalloc(8 + max(DataBufferLength, sizeof(RequestSense_T))); memcpy(&Command[0], &Zero, 4); memcpy(&Command[4], &DataBufferLength, 4); memcpy(&Command[8], CDB, CDB_Length); break; case Output: Command = (unsigned char *) xmalloc(8 + max(CDB_Length + DataBufferLength, sizeof(RequestSense_T))); memcpy(&Command[0], &DataBufferLength, 4); memcpy(&Command[4], &Zero, 4); memcpy(&Command[8], CDB, CDB_Length); memcpy(&Command[8 + CDB_Length], DataBuffer, DataBufferLength); break; } Result = ioctl(DeviceFD, SCSI_IOCTL_SEND_COMMAND, Command); if (Result != 0) memcpy(RequestSense, &Command[8], sizeof(RequestSense_T)); else if (Direction == Input) memcpy(DataBuffer, &Command[8], DataBufferLength); free(Command); return Result; } #endif /* __linux__ */ #ifdef __sun__ static int SCSI_OpenDevice(char *DeviceName) { int DeviceFD = open(DeviceName, O_RDWR | O_NDELAY); if (DeviceFD < 0) FatalError("cannot open SCSI device '%s' - %m\n", DeviceName); return DeviceFD; } static void SCSI_CloseDevice(char *DeviceName, int DeviceFD) { if (close(DeviceFD) < 0) FatalError("cannot close SCSI device '%s' - %m\n", DeviceName); } static int SCSI_ExecuteCommand(int DeviceFD, Direction_T Direction, CDB_T CDB, int CDB_Length, void *DataBuffer, int DataBufferLength, RequestSense_T *RequestSense) { struct uscsi_cmd Command; memset(&Command, 0, sizeof(struct uscsi_cmd)); memset(RequestSense, 0, sizeof(RequestSense_T)); switch (Direction) { case Input: if (DataBufferLength > 0) memset(DataBuffer, 0, DataBufferLength); Command.uscsi_flags = USCSI_DIAGNOSE | USCSI_ISOLATE | USCSI_READ | USCSI_RQENABLE; break; case Output: Command.uscsi_flags = USCSI_DIAGNOSE | USCSI_ISOLATE | USCSI_WRITE | USCSI_RQENABLE; break; } /* Set timeout to 5 minutes. */ Command.uscsi_timeout = 300; Command.uscsi_cdb = (caddr_t) CDB; Command.uscsi_cdblen = CDB_Length; Command.uscsi_bufaddr = DataBuffer; Command.uscsi_buflen = DataBufferLength; Command.uscsi_rqbuf = (caddr_t) RequestSense; Command.uscsi_rqlen = sizeof(RequestSense_T); return ioctl(DeviceFD, USCSICMD, &Command); } #endif /* __sun__ */ #ifdef __sgi static int SCSI_OpenDevice(char *DeviceName) { dsreq_t *DeviceFD = dsopen(DeviceName, O_RDWR | O_EXCL); if (DeviceFD == 0) FatalError("cannot open SCSI device '%s' - %m\n", DeviceName); return (int) DeviceFD; } static void SCSI_CloseDevice(char *DeviceName, int DeviceFD) { dsclose((dsreq_t *) DeviceFD); } static int SCSI_ExecuteCommand(int DeviceFD, Direction_T Direction, CDB_T CDB, int CDB_Length, void *DataBuffer, int DataBufferLength, RequestSense_T *RequestSense) { dsreq_t *dsp = (dsreq_t *) DeviceFD; int Result; memset(RequestSense, 0, sizeof(RequestSense_T)); memcpy(CMDBUF(dsp), CDB, CDB_Length); if (Direction == Input) { memset(DataBuffer, 0, DataBufferLength); filldsreq(dsp, (unsigned char *) DataBuffer, DataBufferLength, DSRQ_READ | DSRQ_SENSE); } else filldsreq(dsp, (unsigned char *) DataBuffer, DataBufferLength, DSRQ_WRITE | DSRQ_SENSE); /* Set 5 minute timeout. */ TIME(dsp) = 300 * 1000; Result = doscsireq(getfd((dsp)), dsp); if (SENSESENT(dsp) > 0) memcpy(RequestSense, SENSEBUF(dsp), min(sizeof(RequestSense_T), SENSESENT(dsp))); return Result; } #endif /* __sgi */ #ifdef DIGITAL_UNIX #include "du/scsi.c" #endif #ifdef VMS #include "[.vms]scsi.c" #endif static void Usage() { fprintf(stderr, "Usage:\n\ mtx [ -f ] inquiry | status | first | last | next | previous\n\ mtx [ -f ] load \n\ mtx [ -f ] unload [ ]\n"); #ifndef VMS exit(1); #else sys$exit(VMS_ExitCode); #endif } static int BigEndian16(unsigned char *BigEndianData) { return (BigEndianData[0] << 8) + BigEndianData[1]; } static int BigEndian24(unsigned char *BigEndianData) { return (BigEndianData[0] << 16) + (BigEndianData[1] << 8) + BigEndianData[2]; } static void PrintRequestSense(RequestSense_T *RequestSense) { int i; fprintf(stderr, "mtx: Request Sense: %02X", ((unsigned char *) RequestSense)[0]); for (i = 1; i < sizeof(RequestSense_T); i++) fprintf(stderr, " %02X", ((unsigned char *) RequestSense)[i]); fprintf(stderr, "\n"); } static void ReportInquiry(void) { RequestSense_T RequestSense; Inquiry_T Inquiry; CDB_T CDB; int i; CDB[0] = 0x12; /* INQUIRY */ CDB[1] = 0; /* EVPD = 0 */ CDB[2] = 0; /* Page Code */ CDB[3] = 0; /* Reserved */ CDB[4] = sizeof(Inquiry); /* Allocation Length */ CDB[5] = 0; /* Control */ if (SCSI_ExecuteCommand(MediumChangerFD, Input, CDB, 6, &Inquiry, sizeof(Inquiry), &RequestSense) != 0) { PrintRequestSense(&RequestSense); FatalError("INQUIRY Command Failed\n"); } printf("Vendor ID: '"); for (i = 0; i < sizeof(Inquiry.VendorIdentification); i++) printf("%c", Inquiry.VendorIdentification[i]); printf("', Product ID: '"); for (i = 0; i < sizeof(Inquiry.ProductIdentification); i++) printf("%c", Inquiry.ProductIdentification[i]); printf("', Revision: '"); for (i = 0; i < sizeof(Inquiry.ProductRevisionLevel); i++) printf("%c", Inquiry.ProductRevisionLevel[i]); printf("'\n"); } #define MaxReadElementStatusData \ (sizeof(ElementStatusDataHeader_T) \ + 3 * sizeof(ElementStatusPage_T) \ + (MaxStorageElements+2) \ * sizeof(TransportElementDescriptor_T)) static void ReadElementStatus() { ElementStatusDataHeader_T *ElementStatusDataHeader; ElementStatusPage_T *ElementStatusPage; TransportElementDescriptor_T *TransportElementDescriptor; RequestSense_T RequestSense; unsigned char DataBuffer[MaxReadElementStatusData]; unsigned char *DataPointer = DataBuffer; boolean DataTransferElementSeen = false; int EmptyStorageElementCount, EmptyStorageElementAddress; int DataTransferElementSourceStorageElementAddress; int TransportElementDescriptorLength; int ElementCount, BytesAvailable, i; CDB_T CDB; CDB[0] = 0xB8; /* READ ELEMENT STATUS */ CDB[1] = 0; /* Element Type Code = 0, VolTag = 0 */ CDB[2] = 0; /* Starting Element Address MSB */ CDB[3] = 0; /* Starting Element Address LSB */ CDB[4] = 0; /* Number Of Elements MSB */ CDB[5] = MaxStorageElements+2; /* Number Of Elements LSB */ CDB[6] = 0; /* Reserved */ CDB[7] = 0; /* Allocation Length MSB */ CDB[8] = (MaxReadElementStatusData >> 8) & 0xFF; /* Allocation Length */ CDB[9] = MaxReadElementStatusData & 0xFF; /* Allocation Length LSB */ CDB[10] = 0; /* Reserved */ CDB[11] = 0; /* Control */ if (SCSI_ExecuteCommand(MediumChangerFD, Input, CDB, 12, DataBuffer, sizeof(DataBuffer), &RequestSense) != 0) { PrintRequestSense(&RequestSense); FatalError("READ ELEMENT STATUS Command Failed\n"); } StorageElementCount = 0; DataTransferElementSourceStorageElementNumber = 0; EmptyStorageElementCount = 0; ElementStatusDataHeader = (ElementStatusDataHeader_T *) DataPointer; DataPointer += sizeof(ElementStatusDataHeader_T); ElementCount = BigEndian16(ElementStatusDataHeader->NumberOfElementsAvailable); while (ElementCount > 0) { ElementStatusPage = (ElementStatusPage_T *) DataPointer; DataPointer += sizeof(ElementStatusPage_T); TransportElementDescriptorLength = BigEndian16(ElementStatusPage->ElementDescriptorLength); if (TransportElementDescriptorLength < sizeof(TransportElementDescriptor_T)) FatalError("Transport Element Descriptor Length too short\n"); BytesAvailable = BigEndian24(ElementStatusPage->ByteCountOfDescriptorDataAvailable); while (BytesAvailable > 0) { TransportElementDescriptor = (TransportElementDescriptor_T *) DataPointer; DataPointer += TransportElementDescriptorLength; BytesAvailable -= TransportElementDescriptorLength; ElementCount--; switch (ElementStatusPage->ElementTypeCode) { case MediumTransportElement: break; case StorageElement: StorageElementCount++; StorageElementAddress[StorageElementCount] = BigEndian16(TransportElementDescriptor->ElementAddress); StorageElementFull[StorageElementCount] = TransportElementDescriptor->Full; if (!TransportElementDescriptor->Full) { EmptyStorageElementCount++; EmptyStorageElementAddress = StorageElementAddress[StorageElementCount]; } break; case ImportExportElement: FatalError("unexpected Import/Export Element reported\n"); case DataTransferElement: if (DataTransferElementSeen) FatalError("multiple Data Transfer Elements reported\n"); DataTransferElementSeen = true; DataTransferElementAddress = BigEndian16(TransportElementDescriptor->ElementAddress); DataTransferElementFull = TransportElementDescriptor->Full; DataTransferElementSourceStorageElementAddress = BigEndian16(TransportElementDescriptor ->SourceStorageElementAddress); 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); } } } if (!DataTransferElementSeen) FatalError("no Data Transfer Element reported\n"); if (StorageElementCount == 0) FatalError("no Storage Elements reported\n"); if (DataTransferElementFull) { if (DataTransferElementSourceStorageElementAddress == 0 && EmptyStorageElementCount == 1) DataTransferElementSourceStorageElementAddress = EmptyStorageElementAddress; if (DataTransferElementSourceStorageElementAddress > 0) { int StorageElementNumber; for (StorageElementNumber = 1; StorageElementNumber <= StorageElementCount; StorageElementNumber++) if (StorageElementAddress[StorageElementNumber] == DataTransferElementSourceStorageElementAddress) { DataTransferElementSourceStorageElementNumber = StorageElementNumber; break; } } } } static void MoveMedium(int SourceAddress, int DestinationAddress) { RequestSense_T RequestSense; CDB_T CDB; CDB[0] = 0xA5; /* MOVE MEDIUM */ CDB[1] = 0; /* Reserved */ CDB[2] = 0; /* Transport Element Address MSB */ CDB[3] = 0; /* Transport Element Address LSB */ CDB[4] = (SourceAddress >> 8) & 0xFF; /* Source Address MSB */ CDB[5] = SourceAddress & 0xFF; /* Source Address MSB */ CDB[6] = (DestinationAddress >> 8) & 0xFF; /* Destination Address MSB */ CDB[7] = DestinationAddress & 0xFF; /* Destination Address MSB */ CDB[8] = 0; /* Reserved */ CDB[9] = 0; /* Reserved */ CDB[10] = 0; /* Reserved */ CDB[11] = 0; /* Control */ if (SCSI_ExecuteCommand(MediumChangerFD, Input, CDB, 12, NULL, 0, &RequestSense) != 0) { if (RequestSense.AdditionalSenseCode == 0x3B && RequestSense.AdditionalSenseCodeQualifier == 0x0E) FatalError("source Element Address %d is Empty\n", SourceAddress); if (RequestSense.AdditionalSenseCode == 0x3B && RequestSense.AdditionalSenseCodeQualifier == 0x0D) FatalError("destination Element Address %d is Already Full\n", DestinationAddress); if (RequestSense.AdditionalSenseCode == 0x30 && RequestSense.AdditionalSenseCodeQualifier == 0x03) FatalError("Cleaning Cartridge Installed and Ejected\n"); PrintRequestSense(&RequestSense); FatalError("MOVE MEDIUM from Element Address %d to %d Failed\n", SourceAddress, DestinationAddress); } } static void ReportStatus(void) { int StorageElementNumber; printf("Data Transfer Element: "); if (DataTransferElementFull) { if (DataTransferElementSourceStorageElementNumber > 0) printf("Full (Storage Element %d Loaded)\n", DataTransferElementSourceStorageElementNumber); else printf("Full (Unknown Storage Element Loaded)\n"); } else printf("Empty\n"); for (StorageElementNumber = 1; StorageElementNumber <= StorageElementCount; StorageElementNumber++) printf("Storage Element %d: %s\n", StorageElementNumber, (StorageElementFull[StorageElementNumber] ? "Full" : "Empty")); #ifdef VMS VMS_DefineStatusSymbols(); #endif } static void LoadDataTransferElement(int StorageElementNumber) { if (DataTransferElementFull) FatalError("Data Transfer Element is Already Full\n"); if (!StorageElementFull[StorageElementNumber]) FatalError("Storage Element %d is Empty\n", StorageElementNumber); fprintf(stderr, "Loading Storage Element %d into Data Transfer Element...", StorageElementNumber); MoveMedium(StorageElementAddress[StorageElementNumber], DataTransferElementAddress); fprintf(stderr, "done\n"); StorageElementFull[StorageElementNumber] = false; DataTransferElementFull = true; DataTransferElementSourceStorageElementNumber = StorageElementNumber; } static void UnloadDataTransferElement(int StorageElementNumber) { if (!DataTransferElementFull) FatalError("Data Transfer Element is Empty\n"); if (StorageElementNumber == 0) FatalError("Data Transfer Element Source Storage Element is Unknown\n"); if (StorageElementFull[StorageElementNumber]) FatalError("Storage Element %d is Already Full\n", StorageElementNumber); fprintf(stderr, "Unloading Data Transfer Element into Storage Element %d...", StorageElementNumber); MoveMedium(DataTransferElementAddress, StorageElementAddress[StorageElementNumber]); fprintf(stderr, "done\n"); DataTransferElementFull = false; StorageElementFull[StorageElementNumber] = true; } int main(int ArgCount, char *ArgVector[], char *Environment[]) { int FirstArgument = 1, ArgsRemaining; char *DeviceName = NULL; if (ArgCount >= 3 && strcmp(ArgVector[1], "-f") == 0) { DeviceName = ArgVector[2]; FirstArgument = 3; } else if ((DeviceName = getenv("TAPE")) == NULL) Usage(); ArgsRemaining = ArgCount - FirstArgument; if (ArgsRemaining < 1 || ArgsRemaining > 2) Usage(); MediumChangerFD = SCSI_OpenDevice(DeviceName); if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "inquiry") == 0) ArgsRemaining = 0; else ReadElementStatus(); if (ArgsRemaining == 0) ReportInquiry(); else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "status") == 0) ReportStatus(); else if (ArgsRemaining == 2 && strcmp(ArgVector[FirstArgument], "load") == 0) { int StorageElementNumber = strtol(ArgVector[FirstArgument+1], NULL, 0); if (StorageElementNumber < 1 || StorageElementNumber > StorageElementCount) FatalError( "illegal argument '%s' to 'load' command\n", ArgVector[FirstArgument+1]); LoadDataTransferElement(StorageElementNumber); } else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "unload") == 0) UnloadDataTransferElement(DataTransferElementSourceStorageElementNumber); else if (ArgsRemaining == 2 && strcmp(ArgVector[FirstArgument], "unload") == 0) { int StorageElementNumber = strtol(ArgVector[FirstArgument+1], NULL, 0); if (StorageElementNumber < 1 || StorageElementNumber > StorageElementCount) FatalError( "illegal argument '%s' to 'unload' command\n", ArgVector[FirstArgument+1]); UnloadDataTransferElement(StorageElementNumber); } else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "first") == 0) { int FirstStorageElementNumber = 0; while (++FirstStorageElementNumber <= StorageElementCount) if (StorageElementFull[FirstStorageElementNumber] || FirstStorageElementNumber == DataTransferElementSourceStorageElementNumber) break; if (FirstStorageElementNumber > StorageElementCount) FatalError("no First Storage Element\n"); if (StorageElementFull[FirstStorageElementNumber]) { if (DataTransferElementFull) UnloadDataTransferElement( DataTransferElementSourceStorageElementNumber); LoadDataTransferElement(FirstStorageElementNumber); } else fprintf(stderr, "Storage Element %d already loaded into Data Transfer Element\n", FirstStorageElementNumber); } else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "last") == 0) { int LastStorageElementNumber = StorageElementCount+1; while (--LastStorageElementNumber >= 1) if (StorageElementFull[LastStorageElementNumber] || LastStorageElementNumber == DataTransferElementSourceStorageElementNumber) break; if (LastStorageElementNumber < 1) FatalError("no last Storage Element\n"); if (StorageElementFull[LastStorageElementNumber]) { if (DataTransferElementFull) UnloadDataTransferElement( DataTransferElementSourceStorageElementNumber); LoadDataTransferElement(LastStorageElementNumber); } else fprintf(stderr, "Storage Element %d already loaded into Data Transfer Element\n", LastStorageElementNumber); } else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "next") == 0) { int NextStorageElementNumber = DataTransferElementSourceStorageElementNumber; while (++NextStorageElementNumber <= StorageElementCount) if (StorageElementFull[NextStorageElementNumber]) break; UnloadDataTransferElement(DataTransferElementSourceStorageElementNumber); if (NextStorageElementNumber <= StorageElementCount) LoadDataTransferElement(NextStorageElementNumber); else FatalError("no Next Storage Element\n"); } else if (ArgsRemaining == 1 && strcmp(ArgVector[FirstArgument], "previous") == 0) { int PreviousStorageElementNumber = DataTransferElementSourceStorageElementNumber; while (--PreviousStorageElementNumber >= 1) if (StorageElementFull[PreviousStorageElementNumber]) break; UnloadDataTransferElement(DataTransferElementSourceStorageElementNumber); if (PreviousStorageElementNumber >= 1) LoadDataTransferElement(PreviousStorageElementNumber); else FatalError("no Previous Storage Element\n"); } else Usage(); #ifndef VMS SCSI_CloseDevice(DeviceName, MediumChangerFD); return 0; #else ReadElementStatus(); VMS_DefineStatusSymbols(); SCSI_CloseDevice(DeviceName, MediumChangerFD); return SS$_NORMAL; #endif }