fix lintian issues
[debian/mtx] / mtx.c
1 /*
2
3         MTX -- SCSI Tape Attached Medium Changer Control Program
4         $Date: 2008-08-19 03:03:38 -0700 (Tue, 19 Aug 2008) $
5         $Revision: 193 $
6
7         Copyright 1997-1998 by Leonard N. Zubkoff.
8         Copyright 1999-2006 by Eric Lee Green.
9         Copyright 2007-2008 by Robert Nelson <robertn@the-nelsons.org>
10
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.
14
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
18         for complete details.
19
20         The author respectfully requests that any modifications to this software be
21         sent directly to him for evaluation and testing.
22
23         Thanks to Philip A. Prindeville <philipp@enteka.com> of Enteka Enterprise
24         Technology Service for porting MTX to Solaris/SPARC.
25
26         Thanks to Carsten Koch <Carsten.Koch@icem.de> for porting MTX to SGI IRIX.
27
28         Thanks to TECSys Development, Inc. for porting MTX to Digital Unix and
29         OpenVMS.
30
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. 
35 */
36
37 char *argv0;
38
39 #include "mtx.h"        /* various defines for bit order etc. */
40 #include "mtxl.h"
41
42 /* A table for printing out the peripheral device type as ASCII. */ 
43 static char *PeripheralDeviceType[32] =
44 {
45         "Disk Drive",                   /* 0 */
46         "Tape Drive",                   /* 1 */
47         "Printer",                              /* 2 */
48         "Processor",                    /* 3 */
49         "Write-once",                   /* 4 */
50         "CD-ROM",                               /* 5 */
51         "Scanner",                              /* 6 */
52         "Optical",                              /* 7 */ 
53         "Medium Changer",               /* 8 */
54         "Communications",               /* 9 */
55         "ASC IT8",                              /* a */ 
56         "ASC IT8",                              /* b */
57         "RAID Array",                   /* c */
58         "Enclosure Services",   /* d */
59         "RBC Simplified Disk",  /* e */
60         "OCR/W",                                /* f */
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 */
76         "Unknown"                               /* 0x1f */
77 };
78
79 static int argc;
80 static char **argv;
81
82 static char *device=NULL;               /* the device name passed as argument */
83
84 /*      Unfortunately this must be true for SGI, because SGI does not
85         use an int :-(.
86 */
87
88 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) -1;
89 static int device_opened = 0;   /* okay, replace check here. */
90
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. */
94
95 static SCSI_Flags_T SCSI_Flags = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
96
97 static Inquiry_T *inquiry_info;         /* needed by MoveMedium etc... */
98 static ElementStatus_T *ElementStatus = NULL;
99 void Position(int dest);
100
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);
123
124 struct command_table_struct
125 {
126         int num_args;
127         char *name;
128         void (*command)(void);
129         int need_device;
130         int need_status;
131 }
132 command_table[] =
133 {
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},
155         { 0, NULL, NULL }
156 };
157
158 static void Usage()
159 {
160         fprintf(stderr, "Usage:\n\
161         mtx --version\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");
175
176 #ifndef VMS
177         exit(1);
178 #else
179         sys$exit(VMS_ExitCode);
180 #endif
181 }
182
183
184 static void Version(void)
185 {
186         fprintf(stderr, "mtx version %s\n\n", VERSION);
187         Usage(); 
188 }
189
190
191 static void NoAttach(void)
192 {
193         SCSI_Flags.no_attached = 1;
194 }
195
196
197 static void InvertCommand(void)
198 {
199         SCSI_Flags.invert = 1;          /* invert_bit=1;*/
200 }
201
202
203 static void Invert2(void)
204 {
205         SCSI_Flags.invert2 = 1;         /* invert2_bit=1;*/
206 }
207
208
209 static void NoBarCode(void)
210 {
211         SCSI_Flags.no_barcodes = 1;     /* don't request barcodes */
212 }
213
214
215 static void do_Position(void)
216 {
217         int driveno,src;
218
219         if (arg1 >= 0 && arg1 <= ElementStatus->StorageElementCount)
220         {
221                 driveno = arg1 - 1;
222         }
223         else
224         {
225                 driveno = 0;
226         }
227
228         src = ElementStatus->StorageElementAddress[driveno];
229         Position(src);
230 }
231
232
233 static void AltReadElementStatus(void)
234 {
235         /* use alternative way to read element status from device - used to support XL1B2 */
236         SCSI_Flags.querytype = MTX_ELEMENTSTATUS_READALL;
237 }
238
239
240 /* First and Last are easy. Next is the bitch. */
241 static void First(void)
242 {
243         int driveno;
244         /* okay, first see if we have a drive#: */
245         if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
246         {
247                 driveno = arg1;
248         }
249         else
250         {
251                 driveno = 0;
252         }
253
254         /* now see if there's anything *IN* that drive: */
255         if (ElementStatus->DataTransferElementFull[driveno])
256         {
257                 /* if so, then unload it... */
258                 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno] + 1;
259                 if (arg1 == 1)
260                 {
261                         printf("loading...done.\n");  /* it already has tape #1 in it! */ 
262                         return;
263                 }
264                 arg2 = driveno;
265                 Unload();
266         }
267
268         /* and now to actually do the Load(): */
269         arg1 = 1;       /* first! */
270         arg2 = driveno;
271         Load();         /* and voila! */
272 }
273
274 static void Last(void)
275 {
276         int driveno;
277
278         /* okay, first see if we have a drive#: */
279         if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
280         {
281                 driveno = arg1;
282         }
283         else
284         {
285                 driveno = 0;
286         }
287
288         /* now see if there's anything *IN* that drive: */
289         if (ElementStatus->DataTransferElementFull[driveno])
290         {
291                 /* if so, then unload it... */
292                 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno] + 1;
293                 if (arg1 >= (ElementStatus->StorageElementCount - ElementStatus->ImportExportCount))
294                 {
295                         printf("loading...done.\n");    /* it already has last tape in it! */ 
296                         return;
297                 }
298                 arg2 = driveno;
299                 Unload();
300         }
301
302         arg1 = ElementStatus->StorageElementCount - ElementStatus->ImportExportCount;   /* the last slot... */
303         arg2 = driveno;
304         Load();
305 }
306
307
308 static void Previous(void)
309 {
310         int driveno;
311         int current = ElementStatus->StorageElementCount - ElementStatus->ImportExportCount + 1;
312
313         /* okay, first see if we have a drive#: */
314         if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
315         {
316                 driveno = arg1;
317         }
318         else
319         {
320                 driveno = 0;
321         }
322
323         /* Now to see if there's anything in that drive! */
324         if (ElementStatus->DataTransferElementFull[driveno])
325         {
326                 /* if so, unload it! */
327                 current = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno];
328                 if (current == 0)
329                 {
330                         FatalError("No More Media\n");          /* Already at the 1st slot...*/
331                 }
332                 arg1 = current + 1;             /* Args are 1 based */
333                 arg2 = driveno;
334                 Unload();
335         }
336
337         /* Position current to previous element */
338         for (current--; current >= 0; current--)
339         {
340                 if (ElementStatus->StorageElementFull[current])
341                 {
342                         arg1 = current + 1;
343                         arg2 = driveno;
344                         Load();
345                         return;
346                 }
347         }
348
349         FatalError("No More Media\n");          /* First slot */
350 }
351
352
353 static void Next(void)
354 {
355         int driveno;
356         int current = -1;
357
358         /* okay, first see if we have a drive#: */
359         if (arg1 >= 0 && arg1 < ElementStatus->DataTransferElementCount)
360         {
361                 driveno = arg1;
362         }
363         else
364         {
365                 driveno = 0;
366         }
367
368         /* Now to see if there's anything in that drive! */
369         if (ElementStatus->DataTransferElementFull[driveno])
370         {
371                 /* if so, unload it! */
372                 current = ElementStatus->DataTransferElementSourceStorageElementNumber[driveno];
373
374                 arg1 = current + 1;
375                 arg2 = driveno;
376                 Unload();
377         }
378
379         for (current++;
380                  current < (ElementStatus->StorageElementCount - ElementStatus->ImportExportCount);
381                  current++)
382         {
383                 if (ElementStatus->StorageElementFull[current])
384                 {
385                         arg1 = current + 1;
386                         arg2 = driveno;
387                         Load();
388                         return;
389                 }
390         }
391
392         FatalError("No More Media\n");          /* last slot */
393 }
394
395 static void do_Inventory(void) 
396 {
397         if (Inventory(MediumChangerFD) < 0)
398         {
399                 fprintf(stderr,"mtx:inventory failed\n");
400                 fflush(stderr);
401                 exit(1);        /*  exit with an error status. */
402         }
403 }
404
405 /*
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
411  * native tools!
412  */
413 static void do_Erase(void)
414 {
415         RequestSense_T *RequestSense;
416         RequestSense = Erase(MediumChangerFD);
417         if (RequestSense)
418         {
419                 PrintRequestSense(RequestSense);
420                 exit(1);        /* exit with an error status. */
421         }
422 }
423         
424
425 /* This should eject a tape or magazine, depending upon the device sent
426  * to.
427  */
428 static void do_Unload(void)
429 {
430         if (LoadUnload(MediumChangerFD, 0) < 0)
431         {
432                 fprintf(stderr, "mtx:eject failed\n");
433                 fflush(stderr);
434         }
435 }
436
437 static void ReportInquiry(void)
438 {
439         RequestSense_T RequestSense;
440         Inquiry_T *Inquiry;
441         int i;
442
443         Inquiry = RequestInquiry(MediumChangerFD,&RequestSense);
444         if (Inquiry == NULL) 
445         {
446                 PrintRequestSense(&RequestSense);
447                 FatalError("INQUIRY Command Failed\n");
448         }
449         
450         printf("Product Type: %s\n", PeripheralDeviceType[Inquiry->PeripheralDeviceType]);
451         printf("Vendor ID: '");
452         for (i = 0; i < sizeof(Inquiry->VendorIdentification); i++)
453         {
454                 printf("%c", Inquiry->VendorIdentification[i]);
455         }
456
457         printf("'\nProduct ID: '");
458         for (i = 0; i < sizeof(Inquiry->ProductIdentification); i++)
459         {
460                 printf("%c", Inquiry->ProductIdentification[i]);
461         }
462
463         printf("'\nRevision: '");
464         for (i = 0; i < sizeof(Inquiry->ProductRevisionLevel); i++)
465         {
466                 printf("%c", Inquiry->ProductRevisionLevel[i]);
467         }
468         printf("'\n");
469
470         if (Inquiry->MChngr)
471         {
472                 /* check the attached-media-changer bit... */
473                 printf("Attached Changer API: Yes\n");
474         }
475         else
476         {
477                 printf("Attached Changer API: No\n");
478         }
479
480         free(Inquiry);          /* well, we're about to exit, but ... */
481 }
482
483 static void Status(void)
484 {
485         int StorageElementNumber;
486         int TransferElementNumber;
487
488         printf( "  Storage Changer %s:%d Drives, %d Slots ( %d Import/Export )\n",
489                         device,
490                         ElementStatus->DataTransferElementCount,
491                         ElementStatus->StorageElementCount,
492                         ElementStatus->ImportExportCount);
493
494
495         for (TransferElementNumber = 0; 
496                  TransferElementNumber < ElementStatus->DataTransferElementCount;
497                  TransferElementNumber++)
498         {
499                 
500                 printf("Data Transfer Element %d:", TransferElementNumber);
501                 if (ElementStatus->DataTransferElementFull[TransferElementNumber])
502                 {
503                         if (ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber] > -1)
504                         {
505                                 printf("Full (Storage Element %d Loaded)",
506                                                 ElementStatus->DataTransferElementSourceStorageElementNumber[TransferElementNumber]+1);
507                         }
508                         else
509                         {
510                                 printf("Full (Unknown Storage Element Loaded)");
511                         }
512
513                         if (ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber][0])
514                         {
515                                 printf(":VolumeTag = %s", ElementStatus->DataTransferPrimaryVolumeTag[TransferElementNumber]);
516                         }
517
518                         if (ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber][0])
519                         {
520                                 printf(":AlternateVolumeTag = %s", ElementStatus->DataTransferAlternateVolumeTag[TransferElementNumber]); 
521                         }
522                         putchar('\n');
523                 }
524                 else
525                 {
526                         printf("Empty\n");
527                 }
528         }
529
530         for (StorageElementNumber = 0;
531                  StorageElementNumber < ElementStatus->StorageElementCount;
532                  StorageElementNumber++)
533         {
534                 printf( "      Storage Element %d%s:%s", StorageElementNumber + 1,
535                                 (ElementStatus->StorageElementIsImportExport[StorageElementNumber]) ? " IMPORT/EXPORT" : "",
536                                 (ElementStatus->StorageElementFull[StorageElementNumber] ? "Full " : "Empty"));
537
538                 if (ElementStatus->PrimaryVolumeTag[StorageElementNumber][0])
539                 {
540                         printf(":VolumeTag=%s", ElementStatus->PrimaryVolumeTag[StorageElementNumber]);
541                 }
542
543                 if (ElementStatus->AlternateVolumeTag[StorageElementNumber][0])
544                 {
545                         printf(":AlternateVolumeTag=%s", ElementStatus->AlternateVolumeTag[StorageElementNumber]);
546                 }
547                 putchar('\n');
548         }
549
550 #ifdef VMS
551         VMS_DefineStatusSymbols();
552 #endif
553 }
554
555 void Position(int dest)
556 {
557         if (PositionElement(MediumChangerFD,dest,ElementStatus) != NULL)
558         {
559                 FatalError("Could not position transport\n");
560         }
561 }
562
563 void Move(int src, int dest) {
564         RequestSense_T *result; /* from MoveMedium */
565         
566         result = MoveMedium(MediumChangerFD, src, dest, ElementStatus, inquiry_info, &SCSI_Flags);
567         if (result)
568         {
569                 /* we have an error! */
570
571                 if (result->AdditionalSenseCode == 0x30 &&
572                         result->AdditionalSenseCodeQualifier == 0x03)
573                 {
574                         FatalError("Cleaning Cartridge Installed and Ejected\n");
575                 }
576
577                 if (result->AdditionalSenseCode == 0x3A &&
578                         result->AdditionalSenseCodeQualifier == 0x00)
579                 {
580                         FatalError("Drive needs offline before move\n");
581                 }
582
583                 if (result->AdditionalSenseCode == 0x3B &&
584                         result->AdditionalSenseCodeQualifier == 0x0D)
585                 {
586                         FatalError("Destination Element Address %d is Already Full\n", dest);
587                 }
588
589                 if (result->AdditionalSenseCode == 0x3B &&
590                         result->AdditionalSenseCodeQualifier == 0x0E)
591                 {
592                         FatalError("Source Element Address %d is Empty\n", src);
593                 }
594
595                 PrintRequestSense(result);
596                 FatalError("MOVE MEDIUM from Element Address %d to %d Failed\n", src, dest);
597         }
598 }
599
600
601 /* okay, now for the Load, Unload, etc. logic: */
602
603 static void Load(void)
604 {
605         int src, dest;
606
607         /* okay, check src, dest: arg1=src, arg2=dest */
608         if (arg1 < 1)
609         {
610                 FatalError("No source specified\n");
611         }
612
613         if (arg2 < 0)
614         {
615                 arg2 = 0;       /* default to 1st drive :-( */
616         }
617
618         arg1--;                 /* we use zero-based arrays */
619
620         if (!device_opened)
621         {
622                 FatalError("No Media Changer Device Specified\n");
623         } 
624
625         if (arg1 < 0 || arg1 >= ElementStatus->StorageElementCount)
626         {
627                 FatalError(     "Invalid <storage-element-number> argument '%d' to 'load' command\n",
628                                         arg1 + 1);
629         }
630
631         if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount)
632         {
633                 FatalError(     "illegal <drive-number> argument '%d' to 'load' command\n",
634                                         arg2);
635         }
636
637         if (ElementStatus->DataTransferElementFull[arg2])
638         {
639                 FatalError(     "Drive %d Full (Storage Element %d loaded)\n", arg2,
640                                         ElementStatus->DataTransferElementSourceStorageElementNumber[arg2] + 1);
641         }
642
643         /* Now look up the actual devices: */
644         src = ElementStatus->StorageElementAddress[arg1];
645         dest = ElementStatus->DataTransferElementAddress[arg2];
646
647         fprintf(stdout, "Loading media from Storage Element %d into drive %d...", arg1 + 1, arg2);
648         fflush(stdout);
649
650         Move(src,dest);  /* load it into the particular slot, if possible! */
651
652         fprintf(stdout,"done\n");
653         fflush(stdout);
654
655         /* now set the status for further commands on this line... */
656         ElementStatus->StorageElementFull[arg1] = false;
657         ElementStatus->DataTransferElementFull[arg2] = true;
658 }
659
660 static void Transfer(void)
661 {
662         int src,dest;
663
664         if (arg1 < 1)
665         {
666                 FatalError("No source specified\n");
667         }
668
669         if (arg2 < 1)
670         {
671                 FatalError("No destination specified\n");
672         }
673
674         if (arg1 > ElementStatus->StorageElementCount)
675         {
676                 FatalError("Invalid source\n");
677         }
678
679         if (arg2 > ElementStatus->StorageElementCount)
680         {
681                 FatalError("Invalid destination\n");
682         }
683
684         src = ElementStatus->StorageElementAddress[arg1 - 1];
685         dest = ElementStatus->StorageElementAddress[arg2 - 1];
686         Move(src,dest);
687 }
688
689 /****************************************************************
690  * Exchange() -- exchange medium in two slots, if so
691  * supported by the jukebox in question.
692  ***************************************************************/
693
694 static void Exchange(void)
695 {
696         RequestSense_T *result; /* from ExchangeMedium */
697         int src,dest,dest2;
698
699         if (arg1 < 1)
700         {
701                 FatalError("No source specified\n");
702         }
703
704         if (arg2 < 1)
705         {
706                 FatalError("No destination specified\n");
707         }
708
709         if (arg1 > ElementStatus->StorageElementCount)
710         {
711                 FatalError("Invalid source\n");
712         }
713
714         if (arg2 > ElementStatus->StorageElementCount)
715         {
716                 FatalError("Invalid destination\n");
717         }
718
719         if (arg3 == -1)
720         {
721                 arg3 = arg1;  /* true exchange of medium */
722         }
723
724         src = ElementStatus->StorageElementAddress[arg1 - 1];
725         dest = ElementStatus->StorageElementAddress[arg2 - 1];
726         dest2 = ElementStatus->StorageElementAddress[arg3 - 1];
727         
728         result = ExchangeMedium(MediumChangerFD, src, dest, dest2, ElementStatus, &SCSI_Flags);
729         if (result)
730         {
731                 /* we have an error! */
732                 if (result->AdditionalSenseCode == 0x30 &&
733                         result->AdditionalSenseCodeQualifier == 0x03)
734                 {
735                         FatalError("Cleaning Cartridge Installed and Ejected\n");
736                 }
737
738                 if (result->AdditionalSenseCode == 0x3A &&
739                         result->AdditionalSenseCodeQualifier == 0x00)
740                 {
741                         FatalError("Drive needs offline before move\n");
742                 }
743
744                 if (result->AdditionalSenseCode == 0x3B &&
745                         result->AdditionalSenseCodeQualifier == 0x0D)
746                 {
747                         FatalError("Destination Element Address %d is Already Full\n", dest);
748                 }
749
750                 if (result->AdditionalSenseCode == 0x3B &&
751                         result->AdditionalSenseCodeQualifier == 0x0E)
752                 {
753                         FatalError("Source Element Address %d is Empty\n", src);
754                 }
755
756                 PrintRequestSense(result);
757
758                 FatalError("EXCHANGE MEDIUM from Element Address %d to %d Failed\n", src, dest);
759         }
760 }
761
762 static void Eepos(void)
763 {
764         if (arg1 < 0 || arg1 > 3)
765         {
766                 FatalError("eepos equires argument between 0 and 3.\n");
767         }
768
769         SCSI_Flags.eepos = (unsigned char)arg1;
770 }
771
772
773 static void Unload(void)
774 {
775         int src, dest;          /* the actual SCSI-level numbers */
776
777         if (arg2 < 0)
778         {
779                 arg2 = 0;               /* default to 1st drive :-( */
780         }
781
782         /* check for filehandle: */
783         if (!device_opened)
784         {
785                 FatalError("No Media Changer Device Specified\n");
786         } 
787
788         /* okay, we should be there: */
789         if (arg1 < 0)
790         {
791                 arg1 = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
792                 if (arg1 < 0)
793                 {
794                         FatalError("No Source for tape in drive %d!\n",arg2);
795                 }
796         }
797         else
798         {
799                 arg1--;         /* go from bogus 1-base to zero-base */
800         }
801
802         if (arg1 >= ElementStatus->StorageElementCount)
803         {
804                 FatalError( "illegal <storage-element-number> argument '%d' to 'unload' command\n",
805                                         arg1 + 1);
806         }
807
808         if (arg2 < 0 || arg2 >= ElementStatus->DataTransferElementCount)
809         {
810                 FatalError(     "illegal <drive-number> argument '%d' to 'unload' command\n",
811                                         arg2);
812         }
813
814         if (!ElementStatus->DataTransferElementFull[arg2])
815         {
816                 FatalError("Data Transfer Element %d is Empty\n", arg2);
817         }
818
819         /* Now see if something already lives where  we wanna go... */
820         if (ElementStatus->StorageElementFull[arg1])
821         {
822                 FatalError("Storage Element %d is Already Full\n", arg1 + 1);
823         }
824
825         /* okay, now to get src, dest: */
826         src=ElementStatus->DataTransferElementAddress[arg2];
827         if (arg1 >= 0)
828         {
829                 dest = ElementStatus->StorageElementAddress[arg1];
830         }
831         else
832         {
833                 dest = ElementStatus->DataTransferElementSourceStorageElementNumber[arg2];
834         }
835
836         if (dest < 0)
837         {
838                 /* we STILL don't know... */
839                 FatalError("Do not know which slot to unload tape into!\n");
840         }
841
842         fprintf(stdout, "Unloading drive %d into Storage Element %d...", arg2, arg1 + 1);
843         fflush(stdout); /* make it real-time :-( */ 
844
845         Move(src,dest);
846
847         fprintf(stdout, "done\n");
848         fflush(stdout);
849
850         ElementStatus->StorageElementFull[arg1] = true;
851         ElementStatus->DataTransferElementFull[arg2] = false;
852 }
853
854 /*****************************************************************
855  ** ARGUMENT PARSING SUBROUTINES: Parse arguments, dispatch. 
856  *****************************************************************/
857
858 /* ***
859  * int get_arg(idx):
860  *
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. 
866  */
867 int get_arg(int idx)
868 {
869         char *arg;
870         int retval = -1;
871
872         if (idx >= argc)
873         {
874                 return -1;  /* sorry! */
875         }
876
877         arg=argv[idx];
878         if (*arg < '0' || *arg > '9')
879         {
880                 return -1;  /* sorry! */
881         }
882
883         retval = atoi(arg);
884         return retval;
885 }
886
887 /* open_device() -- set the 'fh' variable.... */
888 void open_device(void)
889 {
890         if (device_opened)
891         {
892                 SCSI_CloseDevice("Unknown", MediumChangerFD);  /* close it, sigh...  new device now! */
893         }
894
895         MediumChangerFD = SCSI_OpenDevice(device);
896         device_opened = 1; /* SCSI_OpenDevice does an exit() if not. */
897 }
898
899
900 /* we see if we've got a file open. If not, we open one :-(. Then
901  * we execute the actual command. Or not :-(. 
902  */ 
903 void execute_command(struct command_table_struct *command)
904 {
905         RequestSense_T RequestSense;
906
907         if (device == NULL && command->need_device)
908         {
909                 /* try to get it from TAPE environment variable... */
910                 device = getenv("CHANGER");
911                 if (device == NULL)
912                 {
913                         device = getenv("TAPE");
914                         if (device == NULL)
915                         {
916                                 device = "/dev/changer"; /* Usage(); */
917                         }
918                 }
919                 open_device();
920         }
921
922         if (!ElementStatus && command->need_status)
923         {
924                 inquiry_info = RequestInquiry(MediumChangerFD,&RequestSense);
925                 if (!inquiry_info)
926                 {
927                         PrintRequestSense(&RequestSense);
928                         FatalError("INQUIRY command Failed\n");
929                 }
930
931                 ElementStatus = ReadElementStatus(MediumChangerFD, &RequestSense, inquiry_info, &SCSI_Flags);
932                 if (!ElementStatus)
933                 {
934                         PrintRequestSense(&RequestSense);
935                         FatalError("READ ELEMENT STATUS Command Failed\n"); 
936                 }
937         }
938
939         /* okay, now to execute the command... */
940         command->command();
941 }
942
943 /* parse_args():
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
949  * in the cabinet). 
950  */ 
951
952 int parse_args(void)
953 {
954         int i, cmd_tbl_idx;
955         struct command_table_struct *command;
956
957         i = 1;
958         while (i < argc)
959         {
960                 if (strcmp(argv[i], "-f") == 0)
961                 {
962                         i++;
963                         if (i >= argc)
964                         {
965                                 Usage();
966                         }
967
968                         device = argv[i++];
969                         open_device(); /* open the device and do a status scan on it... */
970                 }
971                 else
972                 {
973                         cmd_tbl_idx = 0;                /* default to the first command... */
974                         command = &command_table[cmd_tbl_idx];
975
976                         while (command->name != NULL)
977                         {
978                                 if (strcmp(command->name, argv[i]) == 0)
979                                 {
980                                         /* we have a match... */
981                                         break;
982                                 }
983                                 /* otherwise we don't have a match... */
984                                 cmd_tbl_idx++;
985                                 command = &command_table[cmd_tbl_idx];
986                         }
987
988                         /* if it's not a command, exit.... */
989                         if (!command->name)
990                         {
991                                 Usage();
992                         }
993
994                         i++;  /* go to the next argument, if possible... */
995                         /* see if we need to gather arguments, though! */
996                         if (command->num_args == 0)
997                         {
998                                 execute_command(command);       /* execute_command handles 'stuff' */
999                         }
1000                         else
1001                         {
1002                                 arg1 = get_arg(i);                      /* checks i... */
1003
1004                                 if (arg1 != -1)
1005                                 {
1006                                         i++;    /* next! */
1007                                 }
1008
1009                                 if (command->num_args>=2 && arg1 != -1)
1010                                 {
1011                                         arg2 = get_arg(i); 
1012                                         if (arg2 != -1)
1013                                         {
1014                                                 i++;
1015                                         }
1016
1017                                         if (command->num_args==3 && arg2 != -1)
1018                                         {
1019                                                 arg3 = get_arg(i);
1020                                                 if (arg3 != -1)
1021                                                 {
1022                                                         i++;
1023                                                 }
1024                                         }
1025                                 }
1026                                 execute_command(command);
1027                         }
1028                         arg1 = -1;
1029                         arg2 = -1;
1030                         arg3 = -1;
1031                 }
1032         }
1033
1034         /* should never get here. */
1035         return 0;
1036 }
1037
1038
1039
1040 int main(int ArgCount, char *ArgVector[])
1041 {
1042 #ifdef VMS
1043         RequestSense_T RequestSense;
1044 #endif
1045
1046         /* save these where we can get at them elsewhere... */
1047         argc = ArgCount;
1048         argv = ArgVector;
1049
1050         argv0 = argv[0];
1051
1052         parse_args();   /* also executes them as it sees them */
1053
1054 #ifndef VMS
1055         if (device)
1056         {
1057                 SCSI_CloseDevice(device, MediumChangerFD);
1058         }
1059         return 0;
1060 #else
1061         if (device)
1062         {
1063                 ElementStatus = ReadElementStatus(MediumChangerFD,&RequestSense);
1064                 if (!ElementStatus)
1065                 {
1066                         PrintRequestSense(&RequestSense);
1067                         FatalError("READ ELEMENT STATUS Command Failed\n"); 
1068                 }
1069                 VMS_DefineStatusSymbols();
1070                 SCSI_CloseDevice(device, MediumChangerFD);
1071         }
1072
1073         return SS$_NORMAL;
1074 #endif
1075 }