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> ] [invert] load <storage-element-number> [<drive#>]\n\
161 mtx [ -f <loader-dev> ] [invert] unload [<storage-element-number>][<drive#>]\n\
162 mtx [ -f <loader-dev> ] [eepos eepos-number] transfer <storage-element-number> <storage-element-number>\n\
163 mtx [ -f <device> ] eject\n");
168 sys$exit(VMS_ExitCode);
174 static void Version(void) {
175 fprintf(stderr,"mtx version %s\n\n",VERSION);
179 static void NoAttach(void) {
180 SCSI_Flags.no_attached=1;
183 static void InvertCommand(void) {
188 static void NoBarCode(void) {
189 SCSI_Flags.no_barcodes=1; /* don't request barcodes, sigh! */
192 /* First and Last are easy. Next is the bitch. */
193 static void First(void){
195 /* okay, first see if we have a drive#: */
196 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
201 /* now see if there's anything *IN* that drive: */
202 if (ElementStatus->DataTransferElementFull[driveno]) {
203 /* if so, then unload it... */
204 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
206 printf("loading...done.\n"); /* it already has tape #1 in it! */
212 /* and now to actually do the Load(): */
215 Load(); /* and voila! */
219 static void Last(void) {
221 /* okay, first see if we have a drive#: */
222 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
228 /* now see if there's anything *IN* that drive: */
229 if (ElementStatus->DataTransferElementFull[driveno]) {
230 /* if so, then unload it... */
231 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
232 if (arg1==ElementStatus->StorageElementCount) {
233 printf("loading...done.\n"); /* it already has last tape in it! */
240 arg1 = ElementStatus->StorageElementCount; /* the last slot... */
242 Load(); /* and load it, sigh! */
247 static void Next(void) {
250 /* okay, first see if we have a drive#: */
251 if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount) {
257 /* Now to see if there's anything in that drive! */
258 if (ElementStatus->DataTransferElementFull[driveno]) {
259 /* if so, unload it! */
260 arg1=ElementStatus->DataTransferElementSourceStorageElementNumber[driveno]+1;
266 /* okay, now to load, if we can... */
268 if (current > ElementStatus->StorageElementCount) { /* the last slot... */
269 FatalError("No More Tapes\n");
276 static void do_Inventory(void)
279 i=Inventory(MediumChangerFD);
281 fprintf(stderr,"mtx:inventory failed\n");
283 exit(1); /* exit with an error status. */
285 return; /* if it failed, well, sigh.... */
288 /* For Linux, this allows us to do a short erase on a tape (sigh!).
289 * Note that you'll need to do a 'mt status' on the tape afterwards in
290 * order to get the tape driver in sync with the tape drive again. Also
291 * note that on other OS's, this might do other evil things to the tape
292 * driver. Note that to do an erase, you must first rewind using the OS's
295 static void do_Erase(void) {
296 RequestSense_T *RequestSense;
297 RequestSense=Erase(MediumChangerFD);
299 PrintRequestSense(RequestSense);
300 exit(1); /* exit with an error status. */
306 /* This should eject a tape or magazine, depending upon the device sent
309 static void do_Unload(void)
312 i=Eject(MediumChangerFD);
314 fprintf(stderr,"mtx:eject failed\n");
317 return; /* if it failed, well, sigh.... */
320 static void ReportInquiry(void)
322 RequestSense_T RequestSense;
326 Inquiry = RequestInquiry(MediumChangerFD,&RequestSense);
329 PrintRequestSense(&RequestSense);
330 FatalError("INQUIRY Command Failed\n");
333 printf("Product Type: %s\n",PeripheralDeviceType[Inquiry->PeripheralDeviceType]);
334 printf("Vendor ID: '");
335 for (i = 0; i < sizeof(Inquiry->VendorIdentification); i++)
336 printf("%c", Inquiry->VendorIdentification[i]);
337 printf("'\nProduct ID: '");
338 for (i = 0; i < sizeof(Inquiry->ProductIdentification); i++)
339 printf("%c", Inquiry->ProductIdentification[i]);
340 printf("'\nRevision: '");
341 for (i = 0; i < sizeof(Inquiry->ProductRevisionLevel); i++)
342 printf("%c", Inquiry->ProductRevisionLevel[i]);
344 if (Inquiry->MChngr) { /* check the attached-media-changer bit... */
345 printf("Attached Changer: Yes\n");
347 printf("Attached Changer: No\n");
349 free(Inquiry); /* well, we're about to exit, but ... */
352 static void Status(void)
354 int StorageElementNumber;
355 int TransferElementNumber;
356 printf(" Storage Changer %s:%d Drives, %d Slots ( %d Import/Export )\n",
358 ElementStatus->DataTransferElementCount,
359 ElementStatus->StorageElementCount,
360 ElementStatus->ImportExportCount);
363 for (TransferElementNumber=0;TransferElementNumber <
364 ElementStatus->DataTransferElementCount;TransferElementNumber++) {
366 printf("Data Transfer Element %d:",TransferElementNumber);
367 if (ElementStatus->DataTransferElementFull[TransferElementNumber])
369 if (ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber] > -1)
370 printf("Full (Storage Element %d Loaded)",
371 ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber]+1);
372 else printf("Full (Unknown Storage Element Loaded)");
374 (ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber][0])
375 printf(":VolumeTag = %s",ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber]);
377 (ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber][0])
379 printf(":AlternateVolumeTag = %s",ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber]);
382 else printf("Empty\n");
385 for (StorageElementNumber = 0;
386 StorageElementNumber < ElementStatus->StorageElementCount;
387 StorageElementNumber++) {
388 printf(" Storage Element %d%s:%s", StorageElementNumber+1,
389 (ElementStatus->StorageElementIsImportExport[StorageElementNumber]) ? " IMPORT/EXPORT" : "",
390 (ElementStatus->StorageElementFull[StorageElementNumber] ? "Full " : "Empty"));
391 if (ElementStatus->PrimaryVolumeTag[StorageElementNumber][0]) {
392 printf(":VolumeTag=%s",ElementStatus->PrimaryVolumeTag[StorageElementNumber]);
394 if (ElementStatus->AlternateVolumeTag[StorageElementNumber][0]) {
395 printf(":AlternateVolumeTag=%s",ElementStatus->AlternateVolumeTag[StorageElementNumber]);
401 VMS_DefineStatusSymbols();
405 void Move(int src, int dest) {
406 RequestSense_T *result; /* from MoveMedium */
408 result=MoveMedium(MediumChangerFD,src,dest,ElementStatus,inquiry_info,&SCSI_Flags);
409 if (result) /* we have an error! */
411 if (result->AdditionalSenseCode == 0x3B &&
412 result->AdditionalSenseCodeQualifier == 0x0E)
413 FatalError("source Element Address %d is Empty\n", src);
414 if (result->AdditionalSenseCode == 0x3B &&
415 result->AdditionalSenseCodeQualifier == 0x0D)
416 FatalError("destination Element Address %d is Already Full\n",
418 if (result->AdditionalSenseCode == 0x30 &&
419 result->AdditionalSenseCodeQualifier == 0x03)
420 FatalError("Cleaning Cartridge Installed and Ejected\n");
421 PrintRequestSense(result);
422 FatalError("MOVE MEDIUM from Element Address %d to %d Failed\n",
427 /* okay, now for the Load, Unload, etc. logic: */
429 static void Load(void) {
431 /* okay, check src, dest: arg1=src, arg2=dest */
433 FatalError("No source specified\n");
436 arg2 = 0; /* default to 1st drive :-( */
438 arg1--; /* we use zero-based arrays, sigh, not 1 base like some lusers */
439 /* check for filehandle: */
440 if (!device_opened) {
441 FatalError("No Media Changer Device Specified\n");
443 /* okay, we should be there: */
444 if (arg1 < 0 || arg1 >= ElementStatus->StorageElementCount) {
446 "illegal <storage-element-number> argument '%s' to 'load' command\n",
449 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount) {
451 "illegal <drive-number> argument '%s' to 'load' command\n",
454 if (ElementStatus->DataTransferElementFull[arg2]) {
455 FatalError("Drive %d Full (Storage Element %d loaded)\n",arg2,
456 ElementStatus->DataTransferElementSourceStorageElementNumber[arg2]+1);
458 /* Now look up the actual devices: */
459 dest=ElementStatus->DataTransferElementAddress[arg2];
460 src=ElementStatus->StorageElementAddress[arg1];
461 Move(src,dest); /* load it into the particular slot, if possible! */
462 /* now set the status for further command son this line... */
463 ElementStatus->StorageElementFull[arg1] = false;
464 ElementStatus->DataTransferElementFull[arg2] = true;
466 return; /* and done! */
469 static void Transfer(void) {
472 FatalError("No source specified\n");
475 FatalError("No destination specified\n");
477 if (arg1 > ElementStatus->StorageElementCount) {
478 FatalError("Invalid source\n");
480 if (arg2 > ElementStatus->StorageElementCount) {
481 FatalError("Invalid destination\n");
483 /* okay, that's done... */
484 src=ElementStatus->StorageElementAddress[arg1-1];
485 dest=ElementStatus->StorageElementAddress[arg2-1];
489 static void Eepos(void) {
490 if (arg1 < 0 || arg1 > 3) {
491 FatalError("eepos equires argument between 0 and 3.\n");
493 SCSI_Flags.eepos=arg1;
497 static void Unload(void) {
498 int src,dest; /* the actual SCSI-level numbers */
500 arg2 = 0; /* default to 1st drive :-( */
502 /* check for filehandle: */
503 if (!device_opened) {
504 FatalError("No Media Changer Device Specified\n");
506 /* okay, we should be there: */
508 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
510 FatalError("No Source for tape in drive %d!\n");
513 arg1--; /* go from bogus 1-base to zero-base */
516 if (arg1 >= ElementStatus->StorageElementCount) {
518 "illegal <storage-element-number> argument '%s' to 'unload' command\n",
522 if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount) {
524 "illegal <drive-number> argument '%s' to 'unload' command\n",
527 if (ElementStatus->DataTransferElementFull[arg2] < 0 ) {
528 FatalError("Data Transfer Element %d is Empty\n", arg2);
530 /* Now see if something already lives where we wanna go... */
531 if (ElementStatus->StorageElementFull[arg1]) {
532 FatalError("Storage Element %d is Already Full\n", arg1+1);
534 /* okay, now to get src, dest: */
535 src=ElementStatus->DataTransferElementAddress[arg2];
537 dest=ElementStatus->StorageElementAddress[arg1];
539 dest=ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
541 if (dest < 0) { /* we STILL don't know, sigh... */
542 FatalError("Do not know which slot to unload tape into!\n");
544 fprintf(stderr, "Unloading Data Transfer Element into Storage Element %d...", arg1+1);
545 fflush(stderr); /* make it real-time :-( */
547 fprintf(stderr, "done\n");
548 ElementStatus->StorageElementFull[arg1] = true;
549 ElementStatus->DataTransferElementFull[arg2] = false;
552 /*****************************************************************
553 ** ARGUMENT PARSING SUBROUTINES: Parse arguments, dispatch.
554 *****************************************************************/
559 * If we have an actual argument at the index position indicated (i.e. we
560 * have not gone off the edge of the world), we return
561 * its number. If we don't, or it's not a numeric argument,
562 * we return -1. Note that 'get_arg' is kind of misleading, we only accept
563 * numeric arguments, not any other kind.
565 int get_arg(int idx) {
569 if (idx >= argc) return -1; /* sorry! */
571 if (*arg < '0' || *arg > '9') {
572 return -1; /* sorry! */
579 /* open_device() -- set the 'fh' variable.... */
580 void open_device(void) {
584 SCSI_CloseDevice("Unknown",MediumChangerFD); /* close it, sigh... new device now! */
587 MediumChangerFD = SCSI_OpenDevice(device);
588 device_opened=1; /* SCSI_OpenDevice does an exit() if not. */
592 /* we see if we've got a file open. If not, we open one :-(. Then
593 * we execute the actual command. Or not :-(.
595 void execute_command(struct command_table_struct *command) {
596 RequestSense_T RequestSense;
599 if ((device==NULL) && command->need_device) {
600 /* try to get it from TAPE environment variable... */
601 device=getenv("CHANGER");
603 device=getenv("TAPE");
605 device="/dev/changer"; /* Usage(); */
610 if (!ElementStatus && command->need_status) {
611 inquiry_info=RequestInquiry(MediumChangerFD,&RequestSense);
613 PrintRequestSense(&RequestSense);
614 FatalError("INQUIRY command Failed\n");
616 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense,inquiry_info,&SCSI_Flags);
617 if (!ElementStatus) {
618 PrintRequestSense(&RequestSense);
619 FatalError("READ ELEMENT STATUS Command Failed\n");
623 /* okay, now to execute the command... */
628 * Basically, we are parsing argv/argc. We can have multiple commands
629 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
630 * load in another tape into drive 0, and we execute these commands one
631 * at a time as we come to them. If we don't have a -f at the start, we
632 * barf. If we leave out a drive #, we default to drive 0 (the first drive
636 int parse_args(void) {
638 struct command_table_struct *command;
642 if (strcmp(argv[i],"-f") == 0) {
648 open_device(); /* open the device and do a status scan on it... */
651 command=&command_table[0]; /* default to the first command... */
652 command=&command_table[cmd_tbl_idx];
653 while (command->name) {
654 if (!strcmp(command->name,argv[i])) {
655 /* we have a match... */
658 /* otherwise we don't have a match... */
660 command=&command_table[cmd_tbl_idx];
662 /* if it's not a command, exit.... */
663 if (!command->name) {
666 i++; /* go to the next argument, if possible... */
667 /* see if we need to gather arguments, though! */
668 if (command->num_args == 0) {
669 execute_command(command); /* execute_command handles 'stuff' */
672 arg1=get_arg(i); /* checks i... */
676 if (command->num_args==2 && arg1 != -1) {
682 execute_command(command);
688 return 0; /* should never get here. */
693 int main(int ArgCount,
700 RequestSense_T RequestSense;
703 /* save these where we can get at them elsewhere... */
712 parse_args(); /* also executes them as it sees them, sigh. */
716 SCSI_CloseDevice(device, MediumChangerFD);
720 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense);
721 if (!ElementStatus) {
722 PrintRequestSense(&RequestSense);
723 FatalError("READ ELEMENT STATUS Command Failed\n");
725 VMS_DefineStatusSymbols();
726 SCSI_CloseDevice(device, MediumChangerFD);
735 *Revision 1.2.2.1 2001/11/06 21:20:40 elgreen
736 *Hopefully a fix to the problem with the 0 return for open in crontabs
738 *Revision 1.2 2001/06/09 17:26:26 elgreen
739 *Added 'nobarcode' command to mtx (to skip the initial request asking for
740 *barcodes for mtx status purposes).
742 *Revision 1.1.1.1 2001/06/05 17:10:22 elgreen
743 *Initial import into SourceForge
745 *Revision 1.15 2001/04/18 16:32:59 eric
746 *Cleaned up all -Wall messages.
748 *Revision 1.14 2001/04/18 16:08:08 eric
749 *after re-arranging & fixing bugs
751 *Revision 1.13 2001/03/06 01:37:05 eric
754 *Revision 1.12 2001/01/24 22:04:08 eric
755 *added /dev/changer as default file name.
757 *Revision 1.11 2001/01/15 22:21:06 eric
758 *added autoconf stuff.
760 *Revision 1.10 2000/11/30 20:37:17 eric
761 *Added quicky 'erase' command because I needed blank tapes and Linux
762 *only does long erases. (Not documented on purpose, because of rather
763 *bizarre interactions with the native tape driver if you don't know
764 *what you are doing).
766 *Revision 1.9 2000/11/28 01:10:50 eric
767 *Fixed syntax error in mtx.c...
769 *Revision 1.8 2000/10/28 00:10:35 eric
772 *Revision 1.7 2000/10/27 23:36:57 eric
773 *make mtx inventory return an error code if the inventory fails, so that
774 *we can wait for inventory to be completed at system startup