2 * $Id: chg-scsi-chio.c,v 1.1.2.3.6.3 2003/01/26 19:20:56 martinea Exp $
4 * chg-scsi.c -- generic SCSI changer driver
6 * This program provides a driver to control generic
7 * SCSI changers, no matter what platform. The host/OS
8 * specific portions of the interface are implemented
9 * in libscsi.a, which contains a module for each host/OS.
10 * The actual interface for HP/UX is in scsi-hpux.c;
11 * chio is in scsi-chio.c, etc.. A prototype system
12 * dependent scsi interface file is in scsi-proto.c.
14 * Copyright 1997, 1998 Eric Schnoebelen <eric@cirr.com>
16 * This module based upon seagate-changer, by Larry Pyeatt
17 * <pyeatt@cs.colostate.edu>
19 * The original introductory comments follow:
21 * This program was written to control the Seagate/Conner/Archive
22 * autoloading DAT drive. This drive normally has 4 tape capacity
23 * but can be expanded to 12 tapes with an optional tape cartridge.
24 * This program may also work on onther drives. Try it and let me
25 * know of successes/failures.
27 * I have attempted to conform to the requirements for Amanda tape
28 * changer interface. There could be some bugs.
30 * This program works for me under Linux with Gerd Knorr's
31 * <kraxel@cs.tu-berlin.de> SCSI media changer driver installed
32 * as a kernel module. The kernel module is available at
33 * http://sunsite.unc.edu/pub/Linux/kernel/patches/scsi/scsi-changer*
34 * Since the Linux media changer is based on NetBSD, this program
35 * should also work for NetBSD, although I have not tried it.
36 * It may be necessary to change the IOCTL calls to work on other
39 * (c) 1897 Larry Pyeatt, pyeatt@cs.colostate.edu
40 * All Rights Reserved.
42 * Permission to use, copy, modify, distribute, and sell this software and its
43 * documentation for any purpose is hereby granted without fee, provided that
44 * the above copyright notice appear in all copies and that both that
45 * copyright notice and this permission notice appear in supporting
46 * documentation. The author makes no representations about the
47 * suitability of this software for any purpose. It is provided "as is"
48 * without express or implied warranty.
50 * Michael C. Povel 03.06.98 added ejetct_tape and sleep for external tape
51 * devices, and changed some code to allow multiple drives to use their
52 * own slots. Also added support for reserverd slots.
53 * At the moment these parameters are hard coded and only tested under Linux
62 char *tapestatfile = NULL;
64 /*----------------------------------------------------------------------------*/
65 /* Some stuff for our own configurationfile */
66 typedef struct { /* The information we can get for any drive (configuration) */
67 int drivenum; /* Which drive to use in the library */
68 int start; /* Which is the first slot we may use */
69 int end; /* The last slot we are allowed to use */
70 int cleanslot; /* Where the cleaningcartridge stays */
71 char *scsitapedev; /* Where can we send raw SCSI commands to the tape */
72 char *device; /* Which device is associated to the drivenum */
73 char *slotfile; /* Where we should have our memory */
74 char *cleanfile; /* Where we count how many cleanings we did */
75 char *timefile; /* Where we count the time the tape was used*/
76 char *tapestatfile;/* Where can we place some drive stats */
80 int number_of_configs; /* How many different configurations are used */
81 int eject; /* Do the drives need an eject-command */
82 int sleep; /* How many seconds to wait for the drive to get ready */
83 int cleanmax; /* How many runs could be done with one cleaning tape */
84 char *device; /* Which device is our changer */
89 NUMDRIVE,EJECT,SLEEP,CLEANMAX,DRIVE,START,END,CLEAN,DEVICE,STATFILE,CLEANFILE,DRIVENUM,
90 CHANGERDEV,USAGECOUNT,SCSITAPEDEV, TAPESTATFILE
98 tokentable_t t_table[]={
99 { "number_configs",NUMDRIVE},
102 { "cleanmax",CLEANMAX},
106 { "cleancart",CLEAN},
108 { "statfile",STATFILE},
109 { "cleanfile",CLEANFILE},
110 { "drivenum",DRIVENUM},
111 { "changerdev",CHANGERDEV},
112 { "usagecount",USAGECOUNT},
113 { "scsitapedev", SCSITAPEDEV},
114 { "tapestatus", TAPESTATFILE},
118 void init_changer_struct(changer_t *chg,int number_of_config)
119 /* Initialize datasructures with default values */
123 chg->number_of_configs = number_of_config;
128 chg->conf = malloc(sizeof(config_t)*number_of_config);
129 if (chg->conf != NULL){
130 for (i=0; i < number_of_config; i++){
131 chg->conf[i].drivenum = 0;
132 chg->conf[i].start = -1;
133 chg->conf[i].end = -1;
134 chg->conf[i].cleanslot = -1;
135 chg->conf[i].device = NULL;
136 chg->conf[i].slotfile = NULL;
137 chg->conf[i].cleanfile = NULL;
138 chg->conf[i].timefile = NULL;
139 chg->conf[i].scsitapedev = NULL;
140 chg->conf[i].tapestatfile = NULL;
145 void dump_changer_struct(changer_t chg)
146 /* Dump of information for debug */
150 dbprintf(("Number of configurations: %d\n",chg.number_of_configs));
151 dbprintf(("Tapes need eject: %s\n",(chg.eject>0?"Yes":"No")));
152 dbprintf(("Tapes need sleep: %d seconds\n",chg.sleep));
153 dbprintf(("Cleancycles : %d\n",chg.cleanmax));
154 dbprintf(("Changerdevice : %s\n",chg.device));
155 for (i=0; i<chg.number_of_configs; i++){
156 dbprintf(("Tapeconfig Nr: %d\n",i));
157 dbprintf((" Drivenumber : %d\n",chg.conf[i].drivenum));
158 dbprintf((" Startslot : %d\n",chg.conf[i].start));
159 dbprintf((" Endslot : %d\n",chg.conf[i].end));
160 dbprintf((" Cleanslot : %d\n",chg.conf[i].cleanslot));
161 if (chg.conf[i].device != NULL)
162 dbprintf((" Devicename : %s\n",chg.conf[i].device));
164 dbprintf((" Devicename : none\n"));
165 if (chg.conf[i].scsitapedev != NULL)
166 dbprintf((" SCSITapedev : %s\n",chg.conf[i].scsitapedev));
168 dbprintf((" SCSITapedev : none\n"));
169 if (chg.conf[i].tapestatfile != NULL)
170 dbprintf((" statfile : %s\n", chg.conf[i].tapestatfile));
172 dbprintf((" statfile : none\n"));
173 if (chg.conf[i].slotfile != NULL)
174 dbprintf((" Slotfile : %s\n",chg.conf[i].slotfile));
176 dbprintf((" Slotfile : none\n"));
177 if (chg.conf[i].cleanfile != NULL)
178 dbprintf((" Cleanfile : %s\n",chg.conf[i].cleanfile));
180 dbprintf((" Cleanfile : none\n"));
181 if (chg.conf[i].timefile != NULL)
182 dbprintf((" Usagecount : %s\n",chg.conf[i].timefile));
184 dbprintf((" Usagecount : none\n"));
188 void free_changer_struct(changer_t *chg)
189 /* Free all allocated memory */
193 if (chg->device != NULL)
195 for (i=0; i<chg->number_of_configs; i++){
196 if (chg->conf[i].device != NULL)
197 free(chg->conf[i].device);
198 if (chg->conf[i].slotfile != NULL)
199 free(chg->conf[i].slotfile);
200 if (chg->conf[i].cleanfile != NULL)
201 free(chg->conf[i].cleanfile);
202 if (chg->conf[i].timefile != NULL)
203 free(chg->conf[i].timefile);
205 if (chg->conf != NULL)
211 void parse_line(char *linebuffer,int *token,char **value)
212 /* This function parses a line, and returns the token an value */
217 *token = -1; /* No Token found */
218 tok=strtok(linebuffer," \t\n");
220 while ((tok != NULL) && (tok[0]!='#')&&(ready==0)){
226 while ((t_table[i].word != NULL)&&(*token==-1)){
227 if (0==strcasecmp(t_table[i].word,tok)){
228 *token=t_table[i].token;
233 tok=strtok(NULL," \t\n");
238 int read_config(char *configfile, changer_t *chg)
239 /* This function reads the specified configfile and fills the structure */
245 char *linebuffer = NULL;
249 numconf = 1; /* At least one configuration is assumed */
250 /* If there are more, it should be the first entry in the configurationfile */
252 if (NULL==(file=fopen(configfile,"r"))){
257 while (NULL!=(linebuffer=agets(file))){
258 parse_line(linebuffer,&token,&value);
261 if (token != NUMDRIVE){
262 init_changer_struct(chg,numconf);
264 numconf = atoi(value);
265 init_changer_struct(chg,numconf);
270 case NUMDRIVE: if (atoi(value) != numconf)
271 fprintf(stderr,"Error: number_drives at wrong place, should be "\
275 chg->eject = atoi(value);
278 chg->sleep = atoi(value);
281 chg->device = stralloc(value);
284 chg->conf[drivenum].scsitapedev = stralloc(value);
287 chg->conf[drivenum].tapestatfile = stralloc(value);
290 chg->cleanmax = atoi(value);
293 drivenum = atoi(value);
294 if(drivenum >= numconf){
295 fprintf(stderr,"Error: drive must be less than number_drives\n");
299 if (drivenum < numconf){
300 chg->conf[drivenum].drivenum = atoi(value);
302 fprintf(stderr,"Error: drive is not less than number_drives"\
303 " drivenum ignored\n");
307 if (drivenum < numconf){
308 chg->conf[drivenum].start = atoi(value);
310 fprintf(stderr,"Error: drive is not less than number_drives"\
311 " startuse ignored\n");
315 if (drivenum < numconf){
316 chg->conf[drivenum].end = atoi(value);
318 fprintf(stderr,"Error: drive is not less than number_drives"\
319 " enduse ignored\n");
323 if (drivenum < numconf){
324 chg->conf[drivenum].cleanslot = atoi(value);
326 fprintf(stderr,"Error: drive is not less than number_drives"\
327 " cleanslot ignored\n");
331 if (drivenum < numconf){
332 chg->conf[drivenum].device = stralloc(value);
334 fprintf(stderr,"Error: drive is not less than number_drives"\
335 " device ignored\n");
339 if (drivenum < numconf){
340 chg->conf[drivenum].slotfile = stralloc(value);
342 fprintf(stderr,"Error: drive is not less than number_drives"\
343 " slotfile ignored\n");
347 if (drivenum < numconf){
348 chg->conf[drivenum].cleanfile = stralloc(value);
350 fprintf(stderr,"Error: drive is not less than number_drives"\
351 " cleanfile ignored\n");
355 if (drivenum < numconf){
356 chg->conf[drivenum].timefile = stralloc(value);
358 fprintf(stderr,"Error: drive is not less than number_drives"\
359 " usagecount ignored\n");
363 fprintf(stderr,"Error: Unknown token\n");
375 /*----------------------------------------------------------------------------*/
377 /* The tape drive does not have an idea of current slot so
378 * we use a file to store the current slot. It is not ideal
379 * but it gets the job done
381 int get_current_slot(char *count_file)
385 if ((inf=fopen(count_file,"r")) == NULL) {
386 fprintf(stderr, "%s: unable to open current slot file (%s)\n",
387 get_pname(), count_file);
390 fscanf(inf,"%d",&retval);
395 void put_current_slot(char *count_file,int slot)
399 if ((inf=fopen(count_file,"w")) == NULL) {
400 fprintf(stderr, "%s: unable to open current slot file (%s)\n",
401 get_pname(), count_file);
404 fprintf(inf, "%d\n", slot);
408 /* ----------------------------------------------------------------------
409 This stuff deals with parsing the command line */
411 typedef struct com_arg
419 typedef struct com_stru
426 /* major command line args */
433 argument argdefs[]={{"-slot",COM_SLOT,1},
434 {"-info",COM_INFO,0},
435 {"-reset",COM_RESET,0},
436 {"-eject",COM_EJECT,0},
437 {"-clean",COM_CLEAN,0}};
440 /* minor command line args */
447 argument slotdefs[]={{"current",SLOT_CUR,0},
448 {"next",SLOT_NEXT,0},
449 {"prev",SLOT_PREV,0},
450 {"first",SLOT_FIRST,0},
451 {"last",SLOT_LAST,0}};
453 int is_positive_number(char *tmp) /* is the string a valid positive int? */
456 if ((tmp==NULL)||(tmp[0]==0))
458 while ((tmp[i]>='0')&&(tmp[i]<='9')&&(tmp[i]!=0))
466 void usage(char *argv[])
469 printf("%s: Usage error.\n", argv[0]);
470 for (cnt=0; cnt < COMCOUNT; cnt++){
471 printf(" %s %s",argv[0],argdefs[cnt].str);
472 if (argdefs[cnt].takesparam)
473 printf(" <param>\n");
481 void parse_args(int argc, char *argv[],command *rval)
484 if ((argc<2)||(argc>3))
486 while ((i<COMCOUNT)&&(strcmp(argdefs[i].str,argv[1])))
490 rval->command_code = argdefs[i].command_code;
491 if (argdefs[i].takesparam) {
494 rval->parameter=argv[2];
503 /* used to find actual slot number from keywords next, prev, first, etc */
504 int get_relative_target(int fd,int nslots,char *parameter,int loaded,
505 char *changer_file,int slot_offset,int maxslot)
508 if (changer_file != NULL)
510 current_slot=get_current_slot(changer_file);
512 current_slot = GetCurrentSlot(fd, 0);
514 if (current_slot > maxslot){
515 current_slot = slot_offset;
517 if (current_slot < slot_offset){
518 current_slot = slot_offset;
522 while((i<SLOTCOUNT)&&(strcmp(slotdefs[i].str,parameter)))
530 if (++current_slot==nslots+slot_offset)
536 if (--current_slot<slot_offset)
548 printf("<none> no slot `%s'\n",parameter);
554 int ask_clean(char *tapedev)
555 /* This function should ask the drive if it wants to be cleaned */
557 return get_clean_state(tapedev);
560 void clean_tape(int fd,char *tapedev,char *cnt_file, int drivenum,
561 int cleancart, int maxclean,char *usagetime)
562 /* This function should move the cleaning cartridge into the drive */
565 if (cleancart == -1 ){
568 /* Now we should increment the counter */
569 if (cnt_file != NULL){
570 counter = get_current_slot(cnt_file);
572 if (counter>=maxclean){
573 /* Now we should inform the administrator */
576 mail_cmd = vstralloc(MAILER,
577 " -s", " \"", "AMANDA PROBLEM: PLEASE FIX", "\"",
578 " ", getconf_str(CNF_MAILTO),
580 if((mailf = popen(mail_cmd, "w")) == NULL){
581 error("could not open pipe to \"%s\": %s",
582 mail_cmd, strerror(errno));
583 printf("Mail failed\n");
586 fprintf(mailf,"\nThe usage count of your cleaning tape in slot %d",
588 fprintf(mailf,"\nis more than %d. (cleanmax)",maxclean);
589 fprintf(mailf,"\nTapedrive %s needs to be cleaned",tapedev);
590 fprintf(mailf,"\nPlease insert a new cleaning tape and reset");
591 fprintf(mailf,"\nthe countingfile %s",cnt_file);
593 if(pclose(mailf) != 0)
594 error("mail command failed: %s", mail_cmd);
598 put_current_slot(cnt_file,counter);
600 load(fd,drivenum,cleancart);
602 if (drive_loaded(fd, drivenum))
603 unload(fd,drivenum,cleancart);
606 /* ----------------------------------------------------------------------*/
608 int main(int argc, char *argv[])
610 int loaded,target,oldtarget;
611 command com; /* a little DOS joke */
615 * drive_num really should be something from the config file, but..
616 * for now, it is set to zero, since most of the common changers
617 * used by amanda only have one drive ( until someone wants to
618 * use an EXB60/120, or a Breece Hill Q45.. )
621 int need_eject = 0; /* Does the drive need an eject command ? */
622 int need_sleep = 0; /* How many seconds to wait for the drive to get ready */
625 char *clean_file=NULL;
626 char *time_file=NULL;
632 int fd, rc, slotcnt, drivecnt;
634 char *changer_dev, *tape_device;
635 char *changer_file = NULL;
636 char *scsitapedevice = NULL;
638 set_pname("chg-scsi");
640 parse_args(argc,argv,&com);
642 if(read_conffile(CONFFILE_NAME)) {
643 fprintf(stderr, "%s: could not find config file \"%s\"",
644 changer_dev, conffile);
648 changer_dev = getconf_str(CNF_CHNGRDEV);
649 changer_file = getconf_str(CNF_CHNGRFILE);
650 tape_device = getconf_str(CNF_TAPEDEV);
652 /* Get the configuration parameters */
653 if (strlen(tape_device)==1){
654 read_config(changer_file,&chg);
655 confnum=atoi(tape_device);
656 use_slots = chg.conf[confnum].end-chg.conf[confnum].start+1;
657 slot_offset = chg.conf[confnum].start;
658 drive_num = chg.conf[confnum].drivenum;
659 need_eject = chg.eject;
660 need_sleep = chg.sleep;
661 clean_file = stralloc(chg.conf[confnum].cleanfile);
662 clean_slot = chg.conf[confnum].cleanslot;
663 maxclean = chg.cleanmax;
664 if (NULL != chg.conf[confnum].timefile)
665 time_file = stralloc(chg.conf[confnum].timefile);
666 if (NULL != chg.conf[confnum].slotfile)
667 changer_file = stralloc(chg.conf[confnum].slotfile);
670 if (NULL != chg.conf[confnum].device)
671 tape_device = stralloc(chg.conf[confnum].device);
672 if (NULL != chg.device)
673 changer_dev = stralloc(chg.device);
674 if (NULL != chg.conf[confnum].scsitapedev)
675 scsitapedevice = stralloc(chg.conf[confnum].scsitapedev);
676 if (NULL != chg.conf[confnum].tapestatfile)
677 tapestatfile = stralloc(chg.conf[confnum].tapestatfile);
678 dump_changer_struct(chg);
679 /* get info about the changer */
680 if (-1 == (fd = OpenDevice(changer_dev, "changer_dev"))) {
681 int localerr = errno;
682 fprintf(stderr, "%s: open: %s: %s\n", get_pname(),
683 changer_dev, strerror(localerr));
684 printf("%s open: %s: %s\n", "<none>", changer_dev, strerror(localerr));
685 dbprintf(("%s: open: %s: %s\n", get_pname(),
686 changer_dev, strerror(localerr)));
690 if (tape_device == NULL)
692 tape_device = stralloc(changer_dev);
695 if (scsitapedevice == NULL)
697 scsitapedevice = stralloc(tape_device);
700 if ((chg.conf[confnum].end == -1) || (chg.conf[confnum].start == -1)){
701 slotcnt = get_slot_count(fd);
705 free_changer_struct(&chg);
707 /* get info about the changer */
708 if (-1 == (fd = OpenDevice(changer_dev))) {
709 int localerr = errno;
710 fprintf(stderr, "%s: open: %s: %s\n", get_pname(),
711 changer_dev, strerror(localerr));
712 printf("%s open: %s: %s\n", "<none>", changer_dev, strerror(localerr));
713 dbprintf(("%s: open: %s: %s\n", get_pname(),
714 changer_dev, strerror(localerr)));
717 slotcnt = get_slot_count(fd);
725 drivecnt = get_drive_count(fd);
727 if (drive_num > drivecnt) {
728 printf("%s drive number error (%d > %d)\n", "<none>",
729 drive_num, drivecnt);
730 fprintf(stderr, "%s: requested drive number (%d) greater than "
731 "number of supported drives (%d)\n", get_pname(),
732 drive_num, drivecnt);
733 dbprintf(("%s: requested drive number (%d) greater than "
734 "number of supported drives (%d)\n", get_pname(),
735 drive_num, drivecnt));
740 loaded = drive_loaded(fd, drive_num);
742 switch(com.command_code) {
743 case COM_SLOT: /* slot changing command */
744 if (is_positive_number(com.parameter)) {
745 if ((target = atoi(com.parameter))>=use_slots) {
746 printf("<none> no slot `%d'\n",target);
751 target = target+slot_offset;
754 target=get_relative_target(fd, use_slots,
757 changer_file,slot_offset,slot_offset+use_slots);
759 if (changer_file != NULL)
761 oldtarget=get_current_slot(changer_file);
763 oldtarget = GetCurrentSlot(fd, drive_num);
765 if ((oldtarget)!=target) {
767 eject_tape(scsitapedevice, need_eject);
768 (void)unload(fd, drive_num, oldtarget);
769 if (ask_clean(scsitapedevice))
770 clean_tape(fd,tape_device,clean_file,drive_num,
771 clean_slot,maxclean,time_file);
775 if (changer_file != NULL)
777 put_current_slot(changer_file, target);
779 if (!loaded && isempty(fd, target)) {
780 printf("%d slot %d is empty\n",target-slot_offset,
787 if (load(fd, drive_num, target) != 0) {
788 printf("%d slot %d move failed\n",target-slot_offset,
795 Tape_Ready(scsitapedevice, need_sleep);
796 printf("%d %s\n", target-slot_offset, tape_device);
800 if (changer_file != NULL)
802 printf("%d ", get_current_slot(changer_file)-slot_offset);
804 printf("%d ", GetCurrentSlot(fd, drive_num)-slot_offset);
806 printf("%d 1\n", use_slots);
810 if (changer_file != NULL)
812 target=get_current_slot(changer_file);
814 target = GetCurrentSlot(fd, drive_num);
817 if (!isempty(fd, target))
818 target=find_empty(fd,0 ,0);
820 eject_tape(scsitapedevice, need_eject);
821 (void)unload(fd, drive_num, target);
822 if (ask_clean(scsitapedevice))
823 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
827 if (isempty(fd, slot_offset)) {
828 printf("0 slot 0 is empty\n");
834 if (load(fd, drive_num, slot_offset) != 0) {
835 printf("%d slot %d move failed\n",slot_offset,
841 if (changer_file != NULL)
843 put_current_slot(changer_file, slot_offset);
846 Tape_Ready(scsitapedevice, need_sleep);
847 if (changer_file != NULL)
849 printf("%d %s\n", get_current_slot(changer_file), tape_device);
851 printf("%d %s\n", GetCurrentSlot(fd, drive_num), tape_device);
857 if (changer_file != NULL)
859 target=get_current_slot(changer_file);
861 target = GetCurrentSlot(fd, drive_num);
864 eject_tape(scsitapedevice, need_eject);
865 (void)unload(fd, drive_num, target);
866 if (ask_clean(scsitapedevice))
867 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
869 printf("%d %s\n", target, tape_device);
871 printf("%d %s\n", target, "drive was not loaded");
877 if (changer_file != NULL)
879 target=get_current_slot(changer_file);
881 target = GetCurrentSlot(fd, drive_num);
884 eject_tape(scsitapedevice, need_eject);
885 (void)unload(fd, drive_num, target);
887 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
889 printf("%s cleaned\n", tape_device);
899 * indent-tabs-mode: nil