3 MTX -- SCSI Tape Attached Medium Changer Control Program
4 $Date: 2001/11/06 21:20:40 $
7 Copyright 1997-1998 by Leonard N. Zubkoff <lnz@dandelion.com>
8 Now maintained by Eric Lee Green <eric@estinc.com>
10 This program is free software; you may redistribute and/or modify it under
11 the terms of the GNU General Public License Version 2 as published by the
12 Free Software Foundation.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
16 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 The author respectfully requests that any modifications to this software be
20 sent directly to him for evaluation and testing.
22 Thanks to Philip A. Prindeville <philipp@enteka.com> of Enteka Enterprise
23 Technology Service for porting MTX to Solaris/SPARC.
25 Thanks to Carsten Koch <Carsten.Koch@icem.de> for porting MTX to SGI IRIX.
27 Thanks to TECSys Development, Inc. for porting MTX to Digital Unix and
30 Changes Feb 2000 Eric Lee Green <eric@estinc.com> to add support for
31 multi-drive tape changers, extract out library stuff into mtxl.c,
32 and otherwise bring things up to date for dealing with LARGE tape jukeboxes
33 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] = {
52 "Medium Changer", /* 8 */
53 "Communications", /* 9 */
57 "Enclosure Services", /* d */
58 "RBC Simplified Disk", /* e */
60 "Bridging Expander", /* 0x10 */
61 "Reserved", /* 0x11 */
62 "Reserved", /* 0x12 */
63 "Reserved", /* 0x13 */
64 "Reserved", /* 0x14 */
65 "Reserved", /* 0x15 */
66 "Reserved", /* 0x16 */
67 "Reserved", /* 0x17 */
68 "Reserved", /* 0x18 */
69 "Reserved", /* 0x19 */
70 "Reserved", /* 0x1a */
71 "Reserved", /* 0x1b */
72 "Reserved", /* 0x1c */
73 "Reserved", /* 0x1d */
74 "Reserved", /* 0x1e */
84 static char *device=NULL; /* the device name passed as argument */
86 /* Unfortunately this must be true for SGI, because SGI does not
90 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) 0;
91 static int device_opened = 0; /* okay, replace check here. */
93 /* was: static int MediumChangerFD=-1; *//* open filehandle to that device */
94 static int arg1=-1; /* first arg to command */
95 static int arg2=-1; /* second arg to command */
97 static SCSI_Flags_T SCSI_Flags = { 0, 0, 0,0 };
99 /* static int invert_bit=0;*/ /* we by default do not invert... */
100 /* static int eepos=0; */ /* the extend thingy for import/export. */
101 static Inquiry_T *inquiry_info; /* needed by MoveMedium etc... */
102 static ElementStatus_T *ElementStatus = NULL;
105 /* pre-defined commands: */
106 static void ReportInquiry(void);
107 static void Status(void);
108 static void Load(void);
109 static void Unload(void);
110 static void First(void);
111 static void Last(void);
112 static void Next(void);
113 /* static void Previous(void); */
114 static void InvertCommand(void);
115 static void Transfer(void);
116 static void Eepos(void);
117 static void NoAttach(void);
118 static void Version(void);
119 static void do_Inventory(void);
120 static void do_Unload(void);
121 static void do_Erase(void);
122 static void NoBarCode(void);
124 struct command_table_struct {
127 void (*command)(void);
130 } command_table[] = {
131 { 0, "inquiry",ReportInquiry, 1,0},
132 { 0, "status", Status, 1,1 },
133 { 0, "invert", InvertCommand, 0,0},
134 { 0, "noattach",NoAttach,0,0},
135 { 1, "eepos", Eepos, 0,0},
136 { 2, "load", Load, 1,1 },
137 { 2, "unload", Unload, 1,1 },
138 { 2, "transfer", Transfer, 1,1 },
139 { 1, "first", First, 1,1 },
140 { 1, "last", Last, 1,1 },
141 { 1, "next", Next, 1,1 },
142 { 0, "--version", Version, 0,0 },
143 { 0, "inventory", do_Inventory, 1,0},
144 { 0, "eject", do_Unload, 1, 0},
145 { 0, "erase", do_Erase, 1, 0},
146 { 0, "nobarcode", NoBarCode, 0,0},
152 fprintf(stderr, "Usage:\n\
154 mtx [ -f <loader-dev> ] noattach <more commands>\n\
155 mtx [ -f <loader-dev> ] inquiry | inventory \n\
156 mtx [ -f <loader-dev> ] [nobarcode] status\n\
157 mtx [ -f <loader-dev> ] first [<drive#>]\n\
158 mtx [ -f <loader-dev> ] last [<drive#>]\n\
159 mtx [ -f <loader-dev> ] next [<drive#>]\n\
160 mtx [ -f <loader-dev> ] previous [<drive#>]\n\
161 mtx [ -f <loader-dev> ] [invert] load <storage-element-number> [<drive#>]\n\
162 mtx [ -f <loader-dev> ] [invert] unload [<storage-element-number>][<drive#>]\n\
163 mtx [ -f <loader-dev> ] [eepos eepos-number] transfer <storage-element-number> <storage-element-number>\n\
164 mtx [ -f <device> ] eject\n");
169 sys$exit(VMS_ExitCode);
175 static void Version(void) {
176 fprintf(stderr,"mtx version %s\n\n",VERSION);
180 static void NoAttach(void) {
181 SCSI_Flags.no_attached=1;
184 static void InvertCommand(void) {
189 static void NoBarCode(void) {
190 SCSI_Flags.no_barcodes=1; /* don't request barcodes, sigh! */
193 /* First and Last are easy. Next is the bitch. */
194 static void First(void){
196 /* okay, first see if we have a drive#: */
197 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
202 /* now see if there's anything *IN* that drive: */
203 if (ElementStatus->DataTransferElementFull[driveno]) {
204 /* if so, then unload it... */
205 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
207 printf("loading...done.\n"); /* it already has tape #1 in it! */
213 /* and now to actually do the Load(): */
216 Load(); /* and voila! */
220 static void Last(void) {
222 /* okay, first see if we have a drive#: */
223 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
229 /* now see if there's anything *IN* that drive: */
230 if (ElementStatus->DataTransferElementFull[driveno]) {
231 /* if so, then unload it... */
232 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
233 if (arg1==ElementStatus->StorageElementCount) {
234 printf("loading...done.\n"); /* it already has last tape in it! */
241 arg1 = ElementStatus->StorageElementCount; /* the last slot... */
243 Load(); /* and load it, sigh! */
248 static void Next(void) {
251 /* okay, first see if we have a drive#: */
252 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
258 /* Now to see if there's anything in that drive! */
259 if (ElementStatus->DataTransferElementFull[driveno]) {
260 /* if so, unload it! */
261 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
267 /* okay, now to load, if we can... */
269 if (current > ElementStatus->StorageElementCount) { /* the last slot... */
270 FatalError("No More Tapes\n");
277 static void do_Inventory(void)
280 i=Inventory(MediumChangerFD);
282 fprintf(stderr,"mtx:inventory failed\n");
284 exit(1); /* exit with an error status. */
286 return; /* if it failed, well, sigh.... */
289 /* For Linux, this allows us to do a short erase on a tape (sigh!).
290 * Note that you'll need to do a 'mt status' on the tape afterwards in
291 * order to get the tape driver in sync with the tape drive again. Also
292 * note that on other OS's, this might do other evil things to the tape
293 * driver. Note that to do an erase, you must first rewind using the OS's
296 static void do_Erase(void) {
297 RequestSense_T *RequestSense;
298 RequestSense=Erase(MediumChangerFD);
300 PrintRequestSense(RequestSense);
301 exit(1); /* exit with an error status. */
307 /* This should eject a tape or magazine, depending upon the device sent
310 static void do_Unload(void)
313 i=Eject(MediumChangerFD);
315 fprintf(stderr,"mtx:eject failed\n");
318 return; /* if it failed, well, sigh.... */
321 static void ReportInquiry(void)
323 RequestSense_T RequestSense;
327 Inquiry = RequestInquiry(MediumChangerFD,&RequestSense);
330 PrintRequestSense(&RequestSense);
331 FatalError("INQUIRY Command Failed\n");
334 printf("Product Type: %s\n",PeripheralDeviceType[Inquiry->PeripheralDeviceType]);
335 printf("Vendor ID: '");
336 for (i = 0; i < sizeof(Inquiry->VendorIdentification); i++)
337 printf("%c", Inquiry->VendorIdentification[i]);
338 printf("'\nProduct ID: '");
339 for (i = 0; i < sizeof(Inquiry->ProductIdentification); i++)
340 printf("%c", Inquiry->ProductIdentification[i]);
341 printf("'\nRevision: '");
342 for (i = 0; i < sizeof(Inquiry->ProductRevisionLevel); i++)
343 printf("%c", Inquiry->ProductRevisionLevel[i]);
345 if (Inquiry->MChngr) { /* check the attached-media-changer bit... */
346 printf("Attached Changer: Yes\n");
348 printf("Attached Changer: No\n");
350 free(Inquiry); /* well, we're about to exit, but ... */
353 static void Status(void)
355 int StorageElementNumber;
356 int TransferElementNumber;
357 printf(" Storage Changer %s:%d Drives, %d Slots ( %d Import/Export )\n",
359 ElementStatus->DataTransferElementCount,
360 ElementStatus->StorageElementCount,
361 ElementStatus->ImportExportCount);
364 for (TransferElementNumber=0;TransferElementNumber <
365 ElementStatus->DataTransferElementCount;TransferElementNumber++) {
367 printf("Data Transfer Element %d:",TransferElementNumber);
368 if (ElementStatus->DataTransferElementFull[TransferElementNumber])
370 if (ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber] > -1)
371 printf("Full (Storage Element %d Loaded)",
372 ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber]+1);
373 else printf("Full (Unknown Storage Element Loaded)");
375 (ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber][0])
376 printf(":VolumeTag = %s",ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber]);
378 (ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber][0])
380 printf(":AlternateVolumeTag = %s",ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber]);
383 else printf("Empty\n");
386 for (StorageElementNumber = 0;
387 StorageElementNumber < ElementStatus->StorageElementCount;
388 StorageElementNumber++) {
389 printf(" Storage Element %d%s:%s", StorageElementNumber+1,
390 (ElementStatus->StorageElementIsImportExport[StorageElementNumber]) ? " IMPORT/EXPORT" : "",
391 (ElementStatus->StorageElementFull[StorageElementNumber] ? "Full " : "Empty"));
392 if (ElementStatus->PrimaryVolumeTag[StorageElementNumber][0]) {
393 printf(":VolumeTag=%s",ElementStatus->PrimaryVolumeTag[StorageElementNumber]);
395 if (ElementStatus->AlternateVolumeTag[StorageElementNumber][0]) {
396 printf(":AlternateVolumeTag=%s",ElementStatus->AlternateVolumeTag[StorageElementNumber]);
402 VMS_DefineStatusSymbols();
406 void Move(int src, int dest) {
407 RequestSense_T *result; /* from MoveMedium */
409 result=MoveMedium(MediumChangerFD,src,dest,ElementStatus,inquiry_info,&SCSI_Flags);
410 if (result) /* we have an error! */
412 if (result->AdditionalSenseCode == 0x3B &&
413 result->AdditionalSenseCodeQualifier == 0x0E)
414 FatalError("source Element Address %d is Empty\n", src);
415 if (result->AdditionalSenseCode == 0x3B &&
416 result->AdditionalSenseCodeQualifier == 0x0D)
417 FatalError("destination Element Address %d is Already Full\n",
419 if (result->AdditionalSenseCode == 0x30 &&
420 result->AdditionalSenseCodeQualifier == 0x03)
421 FatalError("Cleaning Cartridge Installed and Ejected\n");
422 PrintRequestSense(result);
423 FatalError("MOVE MEDIUM from Element Address %d to %d Failed\n",
428 /* okay, now for the Load, Unload, etc. logic: */
430 static void Load(void) {
432 /* okay, check src, dest: arg1=src, arg2=dest */
434 FatalError("No source specified\n");
437 arg2 = 0; /* default to 1st drive :-( */
439 arg1--; /* we use zero-based arrays, sigh, not 1 base like some lusers */
440 /* check for filehandle: */
441 if (!device_opened) {
442 FatalError("No Media Changer Device Specified\n");
444 /* okay, we should be there: */
445 if (arg1 < 0 || arg1 >= ElementStatus->StorageElementCount) {
447 "illegal <storage-element-number> argument '%s' to 'load' command\n",
450 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount) {
452 "illegal <drive-number> argument '%s' to 'load' command\n",
455 if (ElementStatus->DataTransferElementFull[arg2]) {
456 FatalError("Drive %d Full (Storage Element %d loaded)\n",arg2,
457 ElementStatus->DataTransferElementSourceStorageElementNumber[arg2]+1);
459 /* Now look up the actual devices: */
460 dest=ElementStatus->DataTransferElementAddress[arg2];
461 src=ElementStatus->StorageElementAddress[arg1];
462 Move(src,dest); /* load it into the particular slot, if possible! */
463 /* now set the status for further command son this line... */
464 ElementStatus->StorageElementFull[arg1] = false;
465 ElementStatus->DataTransferElementFull[arg2] = true;
467 return; /* and done! */
470 static void Transfer(void) {
473 FatalError("No source specified\n");
476 FatalError("No destination specified\n");
478 if (arg1 > ElementStatus->StorageElementCount) {
479 FatalError("Invalid source\n");
481 if (arg2 > ElementStatus->StorageElementCount) {
482 FatalError("Invalid destination\n");
484 /* okay, that's done... */
485 src=ElementStatus->StorageElementAddress[arg1-1];
486 dest=ElementStatus->StorageElementAddress[arg2-1];
490 static void Eepos(void) {
491 if (arg1 < 0 || arg1 > 3) {
492 FatalError("eepos equires argument between 0 and 3.\n");
494 SCSI_Flags.eepos=arg1;
498 static void Unload(void) {
499 int src,dest; /* the actual SCSI-level numbers */
501 arg2 = 0; /* default to 1st drive :-( */
503 /* check for filehandle: */
504 if (!device_opened) {
505 FatalError("No Media Changer Device Specified\n");
507 /* okay, we should be there: */
509 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
511 FatalError("No Source for tape in drive %d!\n");
514 arg1--; /* go from bogus 1-base to zero-base */
517 if (arg1 >= ElementStatus->StorageElementCount) {
519 "illegal <storage-element-number> argument '%s' to 'unload' command\n",
523 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount) {
525 "illegal <drive-number> argument '%s' to 'unload' command\n",
528 if (ElementStatus->DataTransferElementFull[arg2] < 0 ) {
529 FatalError("Data Transfer Element %d is Empty\n", arg2);
531 /* Now see if something already lives where we wanna go... */
532 if (ElementStatus->StorageElementFull[arg1]) {
533 FatalError("Storage Element %d is Already Full\n", arg1+1);
535 /* okay, now to get src, dest: */
536 src=ElementStatus->DataTransferElementAddress[arg2];
538 dest=ElementStatus->StorageElementAddress[arg1];
540 dest=ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
542 if (dest < 0) { /* we STILL don't know, sigh... */
543 FatalError("Do not know which slot to unload tape into!\n");
545 fprintf(stderr, "Unloading Data Transfer Element into Storage Element %d...", arg1+1);
546 fflush(stderr); /* make it real-time :-( */
548 fprintf(stderr, "done\n");
549 ElementStatus->StorageElementFull[arg1] = true;
550 ElementStatus->DataTransferElementFull[arg2] = false;
553 /*****************************************************************
554 ** ARGUMENT PARSING SUBROUTINES: Parse arguments, dispatch.
555 *****************************************************************/
560 * If we have an actual argument at the index position indicated (i.e. we
561 * have not gone off the edge of the world), we return
562 * its number. If we don't, or it's not a numeric argument,
563 * we return -1. Note that 'get_arg' is kind of misleading, we only accept
564 * numeric arguments, not any other kind.
566 int get_arg(int idx) {
570 if (idx >= argc) return -1; /* sorry! */
572 if (*arg < '0' || *arg > '9') {
573 return -1; /* sorry! */
580 /* open_device() -- set the 'fh' variable.... */
581 void open_device(void) {
585 SCSI_CloseDevice("Unknown",MediumChangerFD); /* close it, sigh... new device now! */
588 MediumChangerFD = SCSI_OpenDevice(device);
589 device_opened=1; /* SCSI_OpenDevice does an exit() if not. */
593 /* we see if we've got a file open. If not, we open one :-(. Then
594 * we execute the actual command. Or not :-(.
596 void execute_command(struct command_table_struct *command) {
597 RequestSense_T RequestSense;
600 if ((device==NULL) && command->need_device) {
601 /* try to get it from TAPE environment variable... */
602 device=getenv("CHANGER");
604 device=getenv("TAPE");
606 device="/dev/changer"; /* Usage(); */
611 if (!ElementStatus && command->need_status) {
612 inquiry_info=RequestInquiry(MediumChangerFD,&RequestSense);
614 PrintRequestSense(&RequestSense);
615 FatalError("INQUIRY command Failed\n");
617 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense,inquiry_info,&SCSI_Flags);
618 if (!ElementStatus) {
619 PrintRequestSense(&RequestSense);
620 FatalError("READ ELEMENT STATUS Command Failed\n");
624 /* okay, now to execute the command... */
629 * Basically, we are parsing argv/argc. We can have multiple commands
630 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
631 * load in another tape into drive 0, and we execute these commands one
632 * at a time as we come to them. If we don't have a -f at the start, we
633 * barf. If we leave out a drive #, we default to drive 0 (the first drive
637 int parse_args(void) {
639 struct command_table_struct *command;
643 if (strcmp(argv[i],"-f") == 0) {
649 open_device(); /* open the device and do a status scan on it... */
652 command=&command_table[0]; /* default to the first command... */
653 command=&command_table[cmd_tbl_idx];
654 while (command->name) {
655 if (!strcmp(command->name,argv[i])) {
656 /* we have a match... */
659 /* otherwise we don't have a match... */
661 command=&command_table[cmd_tbl_idx];
663 /* if it's not a command, exit.... */
664 if (!command->name) {
667 i++; /* go to the next argument, if possible... */
668 /* see if we need to gather arguments, though! */
669 if (command->num_args == 0) {
670 execute_command(command); /* execute_command handles 'stuff' */
673 arg1=get_arg(i); /* checks i... */
677 if (command->num_args==2 && arg1 != -1) {
683 execute_command(command);
689 return 0; /* should never get here. */
694 int main(int ArgCount,
701 RequestSense_T RequestSense;
704 /* save these where we can get at them elsewhere... */
713 parse_args(); /* also executes them as it sees them, sigh. */
717 SCSI_CloseDevice(device, MediumChangerFD);
721 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense);
722 if (!ElementStatus) {
723 PrintRequestSense(&RequestSense);
724 FatalError("READ ELEMENT STATUS Command Failed\n");
726 VMS_DefineStatusSymbols();
727 SCSI_CloseDevice(device, MediumChangerFD);
736 *Revision 1.2.2.1 2001/11/06 21:20:40 elgreen
737 *Hopefully a fix to the problem with the 0 return for open in crontabs
739 *Revision 1.2 2001/06/09 17:26:26 elgreen
740 *Added 'nobarcode' command to mtx (to skip the initial request asking for
741 *barcodes for mtx status purposes).
743 *Revision 1.1.1.1 2001/06/05 17:10:22 elgreen
744 *Initial import into SourceForge
746 *Revision 1.15 2001/04/18 16:32:59 eric
747 *Cleaned up all -Wall messages.
749 *Revision 1.14 2001/04/18 16:08:08 eric
750 *after re-arranging & fixing bugs
752 *Revision 1.13 2001/03/06 01:37:05 eric
755 *Revision 1.12 2001/01/24 22:04:08 eric
756 *added /dev/changer as default file name.
758 *Revision 1.11 2001/01/15 22:21:06 eric
759 *added autoconf stuff.
761 *Revision 1.10 2000/11/30 20:37:17 eric
762 *Added quicky 'erase' command because I needed blank tapes and Linux
763 *only does long erases. (Not documented on purpose, because of rather
764 *bizarre interactions with the native tape driver if you don't know
765 *what you are doing).
767 *Revision 1.9 2000/11/28 01:10:50 eric
768 *Fixed syntax error in mtx.c...
770 *Revision 1.8 2000/10/28 00:10:35 eric
773 *Revision 1.7 2000/10/27 23:36:57 eric
774 *make mtx inventory return an error code if the inventory fails, so that
775 *we can wait for inventory to be completed at system startup