3 MTX -- SCSI Tape Attached Medium Changer Control Program
4 $Date: 2007-03-24 18:14:01 -0700 (Sat, 24 Mar 2007) $
7 Copyright 1997-1998 by Leonard N. Zubkoff.
8 Copyright 1999-2006 by Eric Lee Green.
9 Copyright 2007 by Robert Nelson <robertn@the-nelsons.org>
11 This program is free software; you may redistribute and/or modify it under
12 the terms of the GNU General Public License Version 2 as published by the
13 Free Software Foundation.
15 This program is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
17 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 The author respectfully requests that any modifications to this software be
21 sent directly to him for evaluation and testing.
23 Thanks to Philip A. Prindeville <philipp@enteka.com> of Enteka Enterprise
24 Technology Service for porting MTX to Solaris/SPARC.
26 Thanks to Carsten Koch <Carsten.Koch@icem.de> for porting MTX to SGI IRIX.
28 Thanks to TECSys Development, Inc. for porting MTX to Digital Unix and
31 Near complete re-write Feb 2000 Eric Lee Green <eric@badtux.org> to add support for
32 multi-drive tape changers, extract out library stuff into mtxl.c,
33 and otherwise bring things up to date for dealing with LARGE tape jukeboxes
34 and other such enterprise-class storage subsystems.
39 #include "mtx.h" /* various defines for bit order etc. */
42 /* A table for printing out the peripheral device type as ASCII. */
43 static char *PeripheralDeviceType[32] =
53 "Medium Changer", /* 8 */
54 "Communications", /* 9 */
58 "Enclosure Services", /* d */
59 "RBC Simplified Disk", /* e */
61 "Bridging Expander", /* 0x10 */
62 "Reserved", /* 0x11 */
63 "Reserved", /* 0x12 */
64 "Reserved", /* 0x13 */
65 "Reserved", /* 0x14 */
66 "Reserved", /* 0x15 */
67 "Reserved", /* 0x16 */
68 "Reserved", /* 0x17 */
69 "Reserved", /* 0x18 */
70 "Reserved", /* 0x19 */
71 "Reserved", /* 0x1a */
72 "Reserved", /* 0x1b */
73 "Reserved", /* 0x1c */
74 "Reserved", /* 0x1d */
75 "Reserved", /* 0x1e */
82 static char *device=NULL; /* the device name passed as argument */
84 /* Unfortunately this must be true for SGI, because SGI does not
88 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) -1;
89 static int device_opened = 0; /* okay, replace check here. */
91 static int arg1 = -1; /* first arg to command */
92 static int arg2 = -1; /* second arg to command */
93 static int arg3 = -1; /* third arg to command, if exchange. */
95 static SCSI_Flags_T SCSI_Flags = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
97 static Inquiry_T *inquiry_info; /* needed by MoveMedium etc... */
98 static ElementStatus_T *ElementStatus = NULL;
99 void Position(int dest);
101 /* pre-defined commands: */
102 static void ReportInquiry(void);
103 static void Status(void);
104 static void Load(void);
105 static void Unload(void);
106 static void First(void);
107 static void Last(void);
108 static void Next(void);
109 static void Previous(void);
110 static void InvertCommand(void);
111 static void Transfer(void);
112 static void Eepos(void);
113 static void NoAttach(void);
114 static void Version(void);
115 static void do_Inventory(void);
116 static void do_Unload(void);
117 static void do_Erase(void);
118 static void NoBarCode(void);
119 static void do_Position(void);
120 static void Invert2(void);
121 static void Exchange(void);
122 static void AltReadElementStatus(void);
124 struct command_table_struct
128 void (*command)(void);
134 { 0, "inquiry",ReportInquiry, 1,0},
135 { 0, "status", Status, 1,1 },
136 { 0, "invert", InvertCommand, 0,0},
137 { 0, "noattach",NoAttach,0,0},
138 { 1, "eepos", Eepos, 0,0},
139 { 2, "load", Load, 1,1 },
140 { 2, "unload", Unload, 1,1 },
141 { 2, "transfer", Transfer, 1,1 },
142 { 1, "first", First, 1,1 },
143 { 1, "last", Last, 1,1 },
144 { 1, "previous", Previous, 1,1 },
145 { 1, "next", Next, 1,1 },
146 { 0, "--version", Version, 0,0 },
147 { 0, "inventory", do_Inventory, 1,0},
148 { 0, "eject", do_Unload, 1, 0},
149 { 0, "erase", do_Erase, 1, 0},
150 { 0, "nobarcode", NoBarCode, 0,0},
151 { 1, "position", do_Position, 1, 1},
152 { 0, "invert2", Invert2, 0, 0},
153 { 3, "exchange", Exchange, 1, 1 },
154 { 0, "altres", AltReadElementStatus, 0,0},
160 fprintf(stderr, "Usage:\n\
162 mtx [ -f <loader-dev> ] noattach <more commands>\n\
163 mtx [ -f <loader-dev> ] inquiry | inventory \n\
164 mtx [ -f <loader-dev> ] [altres] [nobarcode] status\n\
165 mtx [ -f <loader-dev> ] [altres] first [<drive#>]\n\
166 mtx [ -f <loader-dev> ] [altres] last [<drive#>]\n\
167 mtx [ -f <loader-dev> ] [altres] previous [<drive#>]\n\
168 mtx [ -f <loader-dev> ] [altres] next [<drive#>]\n\
169 mtx [ -f <loader-dev> ] [altres] [invert] load <storage-element-number> [<drive#>]\n\
170 mtx [ -f <loader-dev> ] [altres] [invert] unload [<storage-element-number>][<drive#>]\n\
171 mtx [ -f <loader-dev> ] [altres] [eepos eepos-number] transfer <storage-element-number> <storage-element-number>\n\
172 mtx [ -f <loader-dev> ] [altres] [eepos eepos-number][invert][invert2] exchange <storage-element-number> <storage-element-number>\n\
173 mtx [ -f <loader-dev> ] [altres] position <storage-element-number>\n\
174 mtx [ -f <loader-dev> ] eject\n");
179 sys$exit(VMS_ExitCode);
184 static void Version(void)
186 fprintf(stderr, "mtx version %s\n\n", VERSION);
191 static void NoAttach(void)
193 SCSI_Flags.no_attached = 1;
197 static void InvertCommand(void)
199 SCSI_Flags.invert = 1; /* invert_bit=1;*/
203 static void Invert2(void)
205 SCSI_Flags.invert2 = 1; /* invert2_bit=1;*/
209 static void NoBarCode(void)
211 SCSI_Flags.no_barcodes = 1; /* don't request barcodes */
215 static void do_Position(void)
219 if (arg1 >= 0 && arg1 <= ElementStatus->StorageElementCount)
228 src = ElementStatus->StorageElementAddress[driveno];
233 static void AltReadElementStatus(void)
235 /* use alternative way to read element status from device - used to support XL1B2 */
236 SCSI_Flags.querytype = MTX_ELEMENTSTATUS_READALL;
240 /* First and Last are easy. Next is the bitch. */
241 static void First(void)
244 /* okay, first see if we have a drive#: */
245 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
254 /* now see if there's anything *IN* that drive: */
255 if (ElementStatus->DataTransferElementFull[driveno])
257 /* if so, then unload it... */
258 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno] + 1;
261 printf("loading...done.\n"); /* it already has tape #1 in it! */
268 /* and now to actually do the Load(): */
269 arg1 = 1; /* first! */
271 Load(); /* and voila! */
274 static void Last(void)
278 /* okay, first see if we have a drive#: */
279 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
288 /* now see if there's anything *IN* that drive: */
289 if (ElementStatus->DataTransferElementFull[driveno])
291 /* if so, then unload it... */
292 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno] + 1;
293 if (arg1 >= (ElementStatus->StorageElementCount - ElementStatus->ImportExportCount))
295 printf("loading...done.\n"); /* it already has last tape in it! */
302 arg1 = ElementStatus->StorageElementCount - ElementStatus->ImportExportCount; /* the last slot... */
308 static void Previous(void)
311 int current = ElementStatus->StorageElementCount - ElementStatus->ImportExportCount + 1;
313 /* okay, first see if we have a drive#: */
314 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
323 /* Now to see if there's anything in that drive! */
324 if (ElementStatus->DataTransferElementFull[driveno])
326 /* if so, unload it! */
327 current = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno];
330 FatalError("No More Media\n"); /* Already at the 1st slot...*/
332 arg1 = current + 1; /* Args are 1 based */
337 /* Position current to previous element */
338 for (current--; current >= 0; current--)
340 if (ElementStatus->StorageElementFull[current])
349 FatalError("No More Media\n"); /* First slot */
353 static void Next(void)
358 /* okay, first see if we have a drive#: */
359 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
368 /* Now to see if there's anything in that drive! */
369 if (ElementStatus->DataTransferElementFull[driveno])
371 /* if so, unload it! */
372 current = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno];
380 current < (ElementStatus->StorageElementCount - ElementStatus->ImportExportCount);
383 if (ElementStatus->StorageElementFull[current])
392 FatalError("No More Media\n"); /* last slot */
395 static void do_Inventory(void)
397 if (Inventory(MediumChangerFD) < 0)
399 fprintf(stderr,"mtx:inventory failed\n");
401 exit(1); /* exit with an error status. */
406 * For Linux, this allows us to do a short erase on a tape (sigh!).
407 * Note that you'll need to do a 'mt status' on the tape afterwards in
408 * order to get the tape driver in sync with the tape drive again. Also
409 * note that on other OS's, this might do other evil things to the tape
410 * driver. Note that to do an erase, you must first rewind using the OS's
413 static void do_Erase(void)
415 RequestSense_T *RequestSense;
416 RequestSense = Erase(MediumChangerFD);
419 PrintRequestSense(RequestSense);
420 exit(1); /* exit with an error status. */
425 /* This should eject a tape or magazine, depending upon the device sent
428 static void do_Unload(void)
430 if (LoadUnload(MediumChangerFD, 0) < 0)
432 fprintf(stderr, "mtx:eject failed\n");
437 static void ReportInquiry(void)
439 RequestSense_T RequestSense;
443 Inquiry = RequestInquiry(MediumChangerFD,&RequestSense);
446 PrintRequestSense(&RequestSense);
447 FatalError("INQUIRY Command Failed\n");
450 printf("Product Type: %s\n", PeripheralDeviceType[Inquiry->PeripheralDeviceType]);
451 printf("Vendor ID: '");
452 for (i = 0; i < sizeof(Inquiry->VendorIdentification); i++)
454 printf("%c", Inquiry->VendorIdentification[i]);
457 printf("'\nProduct ID: '");
458 for (i = 0; i < sizeof(Inquiry->ProductIdentification); i++)
460 printf("%c", Inquiry->ProductIdentification[i]);
463 printf("'\nRevision: '");
464 for (i = 0; i < sizeof(Inquiry->ProductRevisionLevel); i++)
466 printf("%c", Inquiry->ProductRevisionLevel[i]);
472 /* check the attached-media-changer bit... */
473 printf("Attached Changer API: Yes\n");
477 printf("Attached Changer API: No\n");
480 free(Inquiry); /* well, we're about to exit, but ... */
483 static void Status(void)
485 int StorageElementNumber;
486 int TransferElementNumber;
488 printf( " Storage Changer %s:%d Drives, %d Slots ( %d Import/Export )\n",
490 ElementStatus->DataTransferElementCount,
491 ElementStatus->StorageElementCount,
492 ElementStatus->ImportExportCount);
495 for (TransferElementNumber = 0;
496 TransferElementNumber < ElementStatus->DataTransferElementCount;
497 TransferElementNumber++)
500 printf("Data Transfer Element %d:", TransferElementNumber);
501 if (ElementStatus->DataTransferElementFull[TransferElementNumber])
503 if (ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber] > -1)
505 printf("Full (Storage Element %d Loaded)",
506 ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber]+1);
510 printf("Full (Unknown Storage Element Loaded)");
513 if (ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber][0])
515 printf(":VolumeTag = %s", ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber]);
518 if (ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber][0])
520 printf(":AlternateVolumeTag = %s", ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber]);
530 for (StorageElementNumber = 0;
531 StorageElementNumber < ElementStatus->StorageElementCount;
532 StorageElementNumber++)
534 printf( " Storage Element %d%s:%s", StorageElementNumber + 1,
535 (ElementStatus->StorageElementIsImportExport[StorageElementNumber]) ? " IMPORT/EXPORT" : "",
536 (ElementStatus->StorageElementFull[StorageElementNumber] ? "Full " : "Empty"));
538 if (ElementStatus->PrimaryVolumeTag[StorageElementNumber][0])
540 printf(":VolumeTag=%s", ElementStatus->PrimaryVolumeTag[StorageElementNumber]);
543 if (ElementStatus->AlternateVolumeTag[StorageElementNumber][0])
545 printf(":AlternateVolumeTag=%s", ElementStatus->AlternateVolumeTag[StorageElementNumber]);
551 VMS_DefineStatusSymbols();
555 void Position(int dest)
557 if (PositionElement(MediumChangerFD,dest,ElementStatus) != NULL)
559 FatalError("Could not position transport\n");
563 void Move(int src, int dest) {
564 RequestSense_T *result; /* from MoveMedium */
566 result = MoveMedium(MediumChangerFD, src, dest, ElementStatus, inquiry_info, &SCSI_Flags);
569 /* we have an error! */
571 if (result->AdditionalSenseCode == 0x30 &&
572 result->AdditionalSenseCodeQualifier == 0x03)
574 FatalError("Cleaning Cartridge Installed and Ejected\n");
577 if (result->AdditionalSenseCode == 0x3A &&
578 result->AdditionalSenseCodeQualifier == 0x00)
580 FatalError("Drive needs offline before move\n");
583 if (result->AdditionalSenseCode == 0x3B &&
584 result->AdditionalSenseCodeQualifier == 0x0D)
586 FatalError("Destination Element Address %d is Already Full\n", dest);
589 if (result->AdditionalSenseCode == 0x3B &&
590 result->AdditionalSenseCodeQualifier == 0x0E)
592 FatalError("Source Element Address %d is Empty\n", src);
595 PrintRequestSense(result);
596 FatalError("MOVE MEDIUM from Element Address %d to %d Failed\n", src, dest);
601 /* okay, now for the Load, Unload, etc. logic: */
603 static void Load(void)
607 /* okay, check src, dest: arg1=src, arg2=dest */
610 FatalError("No source specified\n");
615 arg2 = 0; /* default to 1st drive :-( */
618 arg1--; /* we use zero-based arrays */
622 FatalError("No Media Changer Device Specified\n");
625 if (arg1 < 0 || arg1 >= ElementStatus->StorageElementCount)
627 FatalError( "Invalid <storage-element-number> argument '%d' to 'load' command\n",
631 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount)
633 FatalError( "illegal <drive-number> argument '%d' to 'load' command\n",
637 if (ElementStatus->DataTransferElementFull[arg2])
639 FatalError( "Drive %d Full (Storage Element %d loaded)\n", arg2,
640 ElementStatus->DataTransferElementSourceStorageElementNumber[arg2] + 1);
643 /* Now look up the actual devices: */
644 src = ElementStatus->StorageElementAddress[arg1];
645 dest = ElementStatus->DataTransferElementAddress[arg2];
647 fprintf(stdout, "Loading media from Storage Element %d into drive %d...", arg1 + 1, arg2);
650 Move(src,dest); /* load it into the particular slot, if possible! */
652 fprintf(stdout,"done\n");
655 /* now set the status for further commands on this line... */
656 ElementStatus->StorageElementFull[arg1] = false;
657 ElementStatus->DataTransferElementFull[arg2] = true;
660 static void Transfer(void)
666 FatalError("No source specified\n");
671 FatalError("No destination specified\n");
674 if (arg1 > ElementStatus->StorageElementCount)
676 FatalError("Invalid source\n");
679 if (arg2 > ElementStatus->StorageElementCount)
681 FatalError("Invalid destination\n");
684 src = ElementStatus->StorageElementAddress[arg1 - 1];
685 dest = ElementStatus->StorageElementAddress[arg2 - 1];
689 /****************************************************************
690 * Exchange() -- exchange medium in two slots, if so
691 * supported by the jukebox in question.
692 ***************************************************************/
694 static void Exchange(void)
696 RequestSense_T *result; /* from ExchangeMedium */
701 FatalError("No source specified\n");
706 FatalError("No destination specified\n");
709 if (arg1 > ElementStatus->StorageElementCount)
711 FatalError("Invalid source\n");
714 if (arg2 > ElementStatus->StorageElementCount)
716 FatalError("Invalid destination\n");
721 arg3 = arg1; /* true exchange of medium */
724 src = ElementStatus->StorageElementAddress[arg1 - 1];
725 dest = ElementStatus->StorageElementAddress[arg2 - 1];
726 dest2 = ElementStatus->StorageElementAddress[arg3 - 1];
728 result = ExchangeMedium(MediumChangerFD, src, dest, dest2, ElementStatus, &SCSI_Flags);
731 /* we have an error! */
732 if (result->AdditionalSenseCode == 0x30 &&
733 result->AdditionalSenseCodeQualifier == 0x03)
735 FatalError("Cleaning Cartridge Installed and Ejected\n");
738 if (result->AdditionalSenseCode == 0x3A &&
739 result->AdditionalSenseCodeQualifier == 0x00)
741 FatalError("Drive needs offline before move\n");
744 if (result->AdditionalSenseCode == 0x3B &&
745 result->AdditionalSenseCodeQualifier == 0x0D)
747 FatalError("Destination Element Address %d is Already Full\n", dest);
750 if (result->AdditionalSenseCode == 0x3B &&
751 result->AdditionalSenseCodeQualifier == 0x0E)
753 FatalError("Source Element Address %d is Empty\n", src);
756 PrintRequestSense(result);
758 FatalError("EXCHANGE MEDIUM from Element Address %d to %d Failed\n", src, dest);
762 static void Eepos(void)
764 if (arg1 < 0 || arg1 > 3)
766 FatalError("eepos equires argument between 0 and 3.\n");
769 SCSI_Flags.eepos = (unsigned char)arg1;
773 static void Unload(void)
775 int src, dest; /* the actual SCSI-level numbers */
779 arg2 = 0; /* default to 1st drive :-( */
782 /* check for filehandle: */
785 FatalError("No Media Changer Device Specified\n");
788 /* okay, we should be there: */
791 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
794 FatalError("No Source for tape in drive %d!\n",arg2);
799 arg1--; /* go from bogus 1-base to zero-base */
802 if (arg1 >= ElementStatus->StorageElementCount)
804 FatalError( "illegal <storage-element-number> argument '%d' to 'unload' command\n",
808 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount)
810 FatalError( "illegal <drive-number> argument '%d' to 'unload' command\n",
814 if (!ElementStatus->DataTransferElementFull[arg2])
816 FatalError("Data Transfer Element %d is Empty\n", arg2);
819 /* Now see if something already lives where we wanna go... */
820 if (ElementStatus->StorageElementFull[arg1])
822 FatalError("Storage Element %d is Already Full\n", arg1 + 1);
825 /* okay, now to get src, dest: */
826 src=ElementStatus->DataTransferElementAddress[arg2];
829 dest = ElementStatus->StorageElementAddress[arg1];
833 dest = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
838 /* we STILL don't know... */
839 FatalError("Do not know which slot to unload tape into!\n");
842 fprintf(stdout, "Unloading drive %d into Storage Element %d...", arg2, arg1 + 1);
843 fflush(stdout); /* make it real-time :-( */
847 fprintf(stdout, "done\n");
850 ElementStatus->StorageElementFull[arg1] = true;
851 ElementStatus->DataTransferElementFull[arg2] = false;
854 /*****************************************************************
855 ** ARGUMENT PARSING SUBROUTINES: Parse arguments, dispatch.
856 *****************************************************************/
861 * If we have an actual argument at the index position indicated (i.e. we
862 * have not gone off the edge of the world), we return
863 * its number. If we don't, or it's not a numeric argument,
864 * we return -1. Note that 'get_arg' is kind of misleading, we only accept
865 * numeric arguments, not any other kind.
874 return -1; /* sorry! */
878 if (*arg < '0' || *arg > '9')
880 return -1; /* sorry! */
887 /* open_device() -- set the 'fh' variable.... */
888 void open_device(void)
892 SCSI_CloseDevice("Unknown", MediumChangerFD); /* close it, sigh... new device now! */
895 MediumChangerFD = SCSI_OpenDevice(device);
896 device_opened = 1; /* SCSI_OpenDevice does an exit() if not. */
900 /* we see if we've got a file open. If not, we open one :-(. Then
901 * we execute the actual command. Or not :-(.
903 void execute_command(struct command_table_struct *command)
905 RequestSense_T RequestSense;
907 if (device == NULL && command->need_device)
909 /* try to get it from TAPE environment variable... */
910 device = getenv("CHANGER");
913 device = getenv("TAPE");
916 device = "/dev/changer"; /* Usage(); */
922 if (!ElementStatus && command->need_status)
924 inquiry_info = RequestInquiry(MediumChangerFD,&RequestSense);
927 PrintRequestSense(&RequestSense);
928 FatalError("INQUIRY command Failed\n");
931 ElementStatus = ReadElementStatus(MediumChangerFD, &RequestSense, inquiry_info, &SCSI_Flags);
934 PrintRequestSense(&RequestSense);
935 FatalError("READ ELEMENT STATUS Command Failed\n");
939 /* okay, now to execute the command... */
944 * Basically, we are parsing argv/argc. We can have multiple commands
945 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
946 * load in another tape into drive 0, and we execute these commands one
947 * at a time as we come to them. If we don't have a -f at the start, we
948 * barf. If we leave out a drive #, we default to drive 0 (the first drive
955 struct command_table_struct *command;
960 if (strcmp(argv[i], "-f") == 0)
969 open_device(); /* open the device and do a status scan on it... */
973 cmd_tbl_idx = 0; /* default to the first command... */
974 command = &command_table[cmd_tbl_idx];
976 while (command->name != NULL)
978 if (strcmp(command->name, argv[i]) == 0)
980 /* we have a match... */
983 /* otherwise we don't have a match... */
985 command = &command_table[cmd_tbl_idx];
988 /* if it's not a command, exit.... */
994 i++; /* go to the next argument, if possible... */
995 /* see if we need to gather arguments, though! */
996 if (command->num_args == 0)
998 execute_command(command); /* execute_command handles 'stuff' */
1002 arg1 = get_arg(i); /* checks i... */
1009 if (command->num_args>=2 && arg1 != -1)
1017 if (command->num_args==3 && arg2 != -1)
1026 execute_command(command);
1034 /* should never get here. */
1040 int main(int ArgCount, char *ArgVector[])
1043 RequestSense_T RequestSense;
1046 /* save these where we can get at them elsewhere... */
1052 parse_args(); /* also executes them as it sees them */
1057 SCSI_CloseDevice(device, MediumChangerFD);
1063 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense);
1066 PrintRequestSense(&RequestSense);
1067 FatalError("READ ELEMENT STATUS Command Failed\n");
1069 VMS_DefineStatusSymbols();
1070 SCSI_CloseDevice(device, MediumChangerFD);