2 * $Id: chg-scsi-chio.c,v 1.12 2006/07/25 18:18:46 martinea Exp $
4 * chg-scsi-chio.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
61 #include "scsi-defs.h"
62 int Tape_Ready1 ( char *tapedev , int wait);
64 char *tapestatfile = NULL;
66 /*----------------------------------------------------------------------------*/
69 NUMDRIVE,EJECT,SLEEP,CLEANMAX,DRIVE,START,END,CLEAN,DEVICE,STATFILE,CLEANFILE,DRIVENUM,
70 CHANGERDEV,USAGECOUNT,SCSITAPEDEV, TAPESTATFILE
78 tokentable_t t_table[]={
79 { "number_configs", NUMDRIVE},
82 { "cleanmax", CLEANMAX},
86 { "cleancart", CLEAN},
88 { "statfile", STATFILE},
89 { "cleanfile", CLEANFILE},
90 { "drivenum", DRIVENUM},
91 { "changerdev", CHANGERDEV},
92 { "usagecount", USAGECOUNT},
93 { "scsitapedev", SCSITAPEDEV},
94 { "tapestatus", TAPESTATFILE},
100 void init_changer_struct(changer_t *chg, size_t number_of_config);
101 void dump_changer_struct(changer_t *chg);
102 void free_changer_struct(changer_t **changer);
103 void parse_line(char *linebuffer,int *token,char **value);
104 int read_config(char *configfile, changer_t *chg);
105 int get_current_slot(char *count_file);
106 void put_current_slot(char *count_file,int slot);
107 void usage(char *argv[]);
108 int get_relative_target(int fd,int nslots,char *parameter,int loaded,
109 char *changer_file,int slot_offset,int maxslot);
110 int is_positive_number(char *tmp);
111 int ask_clean(char *tapedev);
112 void clean_tape(int fd,char *tapedev,char *cnt_file, int drivenum,
113 int cleancart, int maxclean,char *usagetime);
114 int main(int argc, char *argv[]);
118 * Initialize data structures with default values
123 size_t number_of_config)
127 memset(chg, 0, SIZEOF(*chg));
128 chg->number_of_configs = number_of_config;
133 chg->conf = alloc(SIZEOF(config_t) * number_of_config);
134 for (i=0; i < number_of_config; i++){
135 chg->conf[i].drivenum = 0;
136 chg->conf[i].start = -1;
137 chg->conf[i].end = -1;
138 chg->conf[i].cleanslot = -1;
139 chg->conf[i].device = NULL;
140 chg->conf[i].slotfile = NULL;
141 chg->conf[i].cleanfile = NULL;
142 chg->conf[i].timefile = NULL;
143 chg->conf[i].scsitapedev = NULL;
144 chg->conf[i].tapestatfile = NULL;
145 chg->conf[i].changerident = NULL;
146 chg->conf[i].tapeident = NULL;
151 * Dump of information for debug
159 dbprintf(_("Number of configurations: %d\n"), chg->number_of_configs);
160 dbprintf(_("Tapes need eject: %s\n"), (chg->eject>0 ? _("Yes") : _("No")));
161 dbprintf(_("Tapes need sleep: %lld seconds\n"), (long long)chg->sleep);
162 dbprintf(_("Clean cycles : %d\n"), chg->cleanmax);
163 dbprintf(_("Changer device : %s\n"), chg->device);
164 for (i = 0; i < chg->number_of_configs; i++){
165 dbprintf(_("Tape config Nr: %d\n"), i);
166 dbprintf(_(" Drive number : %d\n"), chg->conf[i].drivenum);
167 dbprintf(_(" Start slot : %d\n"), chg->conf[i].start);
168 dbprintf(_(" End slot : %d\n"), chg->conf[i].end);
169 dbprintf(_(" Clean slot : %d\n"), chg->conf[i].cleanslot);
170 if (chg->conf[i].device != NULL)
171 dbprintf(_(" Device name : %s\n"), chg->conf[i].device);
173 dbprintf(_(" Device name : none\n"));
174 if (chg->conf[i].scsitapedev != NULL)
175 dbprintf(_(" SCSI Tape dev : %s\n"), chg->conf[i].scsitapedev);
177 dbprintf(_(" SCSI Tape dev : none\n"));
178 if (chg->conf[i].tapestatfile != NULL)
179 dbprintf(_(" stat file : %s\n"), chg->conf[i].tapestatfile);
181 dbprintf(_(" stat file : none\n"));
182 if (chg->conf[i].slotfile != NULL)
183 dbprintf(_(" Slot file : %s\n"), chg->conf[i].slotfile);
185 dbprintf(_(" Slot file : none\n"));
186 if (chg->conf[i].cleanfile != NULL)
187 dbprintf(_(" Clean file : %s\n"), chg->conf[i].cleanfile);
189 dbprintf(_(" Clean file : none\n"));
190 if (chg->conf[i].timefile != NULL)
191 dbprintf(_(" Usage count : %s\n"), chg->conf[i].timefile);
193 dbprintf(_(" Usage count : none\n"));
198 * Free all allocated memory
207 assert(changer != NULL);
208 assert(*changer != NULL);
211 if (chg->device != NULL)
213 for (i=0; i<chg->number_of_configs; i++){
214 if (chg->conf[i].device != NULL)
215 amfree(chg->conf[i].device);
216 if (chg->conf[i].slotfile != NULL)
217 amfree(chg->conf[i].slotfile);
218 if (chg->conf[i].cleanfile != NULL)
219 amfree(chg->conf[i].cleanfile);
220 if (chg->conf[i].timefile != NULL)
221 amfree(chg->conf[i].timefile);
223 if (chg->conf != NULL)
231 * This function parses a line, and returns a token and value
242 *token = -1; /* No Token found */
243 tok=strtok(linebuffer," \t\n");
245 while ((tok != NULL) && (tok[0]!='#')&&(ready==0)){
251 while ((t_table[i].word != NULL)&&(*token==-1)){
252 if (0==strcasecmp(t_table[i].word,tok)){
253 *token=t_table[i].token;
258 tok=strtok(NULL," \t\n");
264 * This function reads the specified configfile and fills the structure
279 numconf = 1; /* At least one configuration is assumed */
280 /* If there are more, it should be the first entry in the configurationfile */
282 if (NULL==(file=fopen(configfile,"r"))){
286 while (NULL != (linebuffer = agets(file))) {
287 if (linebuffer[0] == '\0') {
291 parse_line(linebuffer,&token,&value);
294 if (token != NUMDRIVE){
295 init_changer_struct(chg, numconf);
297 numconf = atoi(value);
298 init_changer_struct(chg, numconf);
303 case NUMDRIVE: if ((size_t)atoi(value) != numconf)
304 g_fprintf(stderr,_("Error: number_drives at wrong place, should be "
308 chg->eject = atoi(value);
311 chg->sleep = atoi(value);
314 chg->device = stralloc(value);
317 chg->conf[drivenum].scsitapedev = stralloc(value);
320 chg->conf[drivenum].tapestatfile = stralloc(value);
323 chg->cleanmax = atoi(value);
326 drivenum = atoi(value);
327 if(drivenum >= numconf){
328 g_fprintf(stderr,_("Error: drive must be less than number_drives\n"));
332 if (drivenum < numconf){
333 chg->conf[drivenum].drivenum = atoi(value);
335 g_fprintf(stderr,_("Error: drive is not less than number_drives"
336 " drivenum ignored\n"));
340 if (drivenum < numconf){
341 chg->conf[drivenum].start = atoi(value);
343 g_fprintf(stderr,_("Error: drive is not less than number_drives"
344 " startuse ignored\n"));
348 if (drivenum < numconf){
349 chg->conf[drivenum].end = atoi(value);
351 g_fprintf(stderr,_("Error: drive is not less than number_drives"
352 " enduse ignored\n"));
356 if (drivenum < numconf){
357 chg->conf[drivenum].cleanslot = atoi(value);
359 g_fprintf(stderr,_("Error: drive is not less than number_drives"
360 " cleanslot ignored\n"));
364 if (drivenum < numconf){
365 chg->conf[drivenum].device = stralloc(value);
367 g_fprintf(stderr,_("Error: drive is not less than number_drives"
368 " device ignored\n"));
372 if (drivenum < numconf){
373 chg->conf[drivenum].slotfile = stralloc(value);
375 g_fprintf(stderr,_("Error: drive is not less than number_drives"
376 " slotfile ignored\n"));
380 if (drivenum < numconf){
381 chg->conf[drivenum].cleanfile = stralloc(value);
383 g_fprintf(stderr,_("Error: drive is not less than number_drives"
384 " cleanfile ignored\n"));
388 if (drivenum < numconf){
389 chg->conf[drivenum].timefile = stralloc(value);
391 g_fprintf(stderr,_("Error: drive is not less than number_drives"
392 " usagecount ignored\n"));
396 g_fprintf(stderr,_("Error: Unknown token\n"));
408 /*----------------------------------------------------------------------------*/
410 /* The tape drive does not have an idea of current slot so
411 * we use a file to store the current slot. It is not ideal
412 * but it gets the job done
414 int get_current_slot(char *count_file)
418 if ((inf=fopen(count_file,"r")) == NULL) {
419 g_fprintf(stderr, _("%s: unable to open current slot file (%s)\n"),
420 get_pname(), count_file);
424 if (fscanf(inf, "%d", &retval) != 1) {
425 g_fprintf(stderr, _("%s: unable to read current slot file (%s)\n"),
426 get_pname(), count_file);
434 void put_current_slot(char *count_file,int slot)
438 if ((inf=fopen(count_file,"w")) == NULL) {
439 g_fprintf(stderr, _("%s: unable to open current slot file (%s)\n"),
440 get_pname(), count_file);
443 g_fprintf(inf, "%d\n", slot);
447 /* ----------------------------------------------------------------------
448 This stuff deals with parsing the command line */
450 typedef struct com_arg
458 typedef struct com_stru
465 void parse_args(int argc, char *argv[], command *rval);
467 /* major command line args */
474 argument argdefs[]={{"-slot",COM_SLOT,1},
475 {"-info",COM_INFO,0},
476 {"-reset",COM_RESET,0},
477 {"-eject",COM_EJECT,0},
478 {"-clean",COM_CLEAN,0}};
481 /* minor command line args */
488 argument slotdefs[]={{"current",SLOT_CUR,0},
489 {"next",SLOT_NEXT,0},
490 {"prev",SLOT_PREV,0},
491 {"first",SLOT_FIRST,0},
492 {"last",SLOT_LAST,0}};
494 int is_positive_number(char *tmp) /* is the string a valid positive int? */
497 if ((tmp==NULL)||(tmp[0]==0))
499 while ((tmp[i]>='0')&&(tmp[i]<='9')&&(tmp[i]!=0))
507 void usage(char *argv[])
510 g_printf(_("%s: Usage error.\n"), argv[0]);
511 for (cnt=0; cnt < COMCOUNT; cnt++){
512 g_printf(" %s %s",argv[0],argdefs[cnt].str);
513 if (argdefs[cnt].takesparam)
514 g_printf(_(" <param>\n"));
522 void parse_args(int argc, char *argv[],command *rval)
525 if ((argc < 2) || (argc > 3)) {
530 while ((i<COMCOUNT)&&(strcmp(argdefs[i].str,argv[1])))
536 rval->command_code = argdefs[i].command_code;
537 if (argdefs[i].takesparam) {
542 rval->parameter=argv[2];
553 /* used to find actual slot number from keywords next, prev, first, etc */
566 (void)loaded; /* Quiet unused warning */
567 if (changer_file != NULL)
569 current_slot=get_current_slot(changer_file);
571 current_slot = GetCurrentSlot(fd, 0);
573 if (current_slot > maxslot){
574 current_slot = slot_offset;
576 if (current_slot < slot_offset){
577 current_slot = slot_offset;
581 while((i < SLOTCOUNT) && (strcmp(slotdefs[i].str,parameter)))
589 if (++current_slot==nslots+slot_offset)
594 if (--current_slot<slot_offset)
605 g_printf(_("<none> no slot `%s'\n"),parameter);
614 * This function should ask the drive if it wants to be cleaned
620 return get_clean_state(tapedev);
624 * This function should move the cleaning cartridge into the drive
639 if (cleancart == -1 ){
643 mailer = getconf_str(CNF_MAILER);
645 /* Now we should increment the counter */
646 if (cnt_file != NULL){
647 counter = get_current_slot(cnt_file);
649 if (counter>=maxclean){
650 /* Now we should inform the administrator */
653 int mail_pipe_opened = 1;
654 if (mailer && *mailer != '\0') {
655 if (getconf_seen(CNF_MAILTO) && strlen(getconf_str(CNF_MAILTO)) > 0 &&
656 validate_mailto(getconf_str(CNF_MAILTO))) {
657 mail_cmd = vstralloc(mailer,
658 " -s", " \"", _("AMANDA PROBLEM: PLEASE FIX"), "\"",
659 " ", getconf_str(CNF_MAILTO),
661 if ((mailf = popen(mail_cmd, "w")) == NULL) {
662 g_printf(_("Mail failed\n"));
663 error(_("could not open pipe to \"%s\": %s"),
664 mail_cmd, strerror(errno));
668 mail_pipe_opened = 0;
670 g_fprintf(mailf, _("\nNo mail recipient specified, output redirected to stderr"));
673 mail_pipe_opened = 0;
675 g_fprintf(mailf, _("\nNo mailer specified; output redirected to stderr"));
677 g_fprintf(mailf, _("\nThe usage count of your cleaning tape in slot %d"),
679 g_fprintf(mailf,_("\nis more than %d. (cleanmax)"),maxclean);
680 g_fprintf(mailf,_("\nTapedrive %s needs to be cleaned"),tapedev);
681 g_fprintf(mailf,_("\nPlease insert a new cleaning tape and reset"));
682 g_fprintf(mailf,_("\nthe countingfile %s"),cnt_file);
684 if(mail_pipe_opened == 1 && pclose(mailf) != 0) {
685 error(_("mail command failed: %s"), mail_cmd);
692 put_current_slot(cnt_file,counter);
694 load(fd,drivenum,cleancart);
696 if (drive_loaded(fd, drivenum))
697 unload(fd,drivenum,cleancart);
700 /* ----------------------------------------------------------------------*/
710 command com; /* a little DOS joke */
713 * drive_num really should be something from the config file, but..
714 * for now, it is set to zero, since most of the common changers
715 * used by amanda only have one drive ( until someone wants to
716 * use an EXB60/120, or a Breece Hill Q45.. )
719 int need_eject = 0; /* Does the drive need an eject command ? */
720 unsigned need_sleep = 0; /* How many seconds to wait for the drive to get ready */
723 char *clean_file=NULL;
724 char *time_file=NULL;
730 int fd, slotcnt, drivecnt;
732 char *changer_dev = NULL;
733 char *tape_device = NULL;
734 char *changer_file = NULL;
735 char *scsitapedevice = NULL;
738 * Configure program for internationalization:
739 * 1) Only set the message locale for now.
740 * 2) Set textdomain for all amanda related programs to "amanda"
741 * We don't want to be forced to support dozens of message catalogs.
743 setlocale(LC_MESSAGES, "C");
744 textdomain("amanda");
746 set_pname("chg-scsi");
748 /* Don't die when child closes pipe */
749 signal(SIGPIPE, SIG_IGN);
751 dbopen(DBG_SUBDIR_SERVER);
752 parse_args(argc,argv,&com);
754 changer = alloc(SIZEOF(changer_t));
755 config_init(CONFIG_INIT_USE_CWD, NULL);
757 if (config_errors(NULL) >= CFGERR_WARNINGS) {
758 config_print_errors();
759 if (config_errors(NULL) >= CFGERR_ERRORS) {
760 g_critical(_("errors processing config file"));
764 changer_dev = getconf_str(CNF_CHANGERDEV);
765 changer_file = getconf_str(CNF_CHANGERFILE);
766 tape_device = getconf_str(CNF_TAPEDEV);
768 /* Get the configuration parameters */
770 if (strlen(tape_device)==1){
771 read_config(changer_file, changer);
772 confnum=atoi(tape_device);
773 use_slots = changer->conf[confnum].end-changer->conf[confnum].start+1;
774 slot_offset = changer->conf[confnum].start;
775 drive_num = changer->conf[confnum].drivenum;
776 need_eject = changer->eject;
777 need_sleep = changer->sleep;
778 clean_file = stralloc(changer->conf[confnum].cleanfile);
779 clean_slot = changer->conf[confnum].cleanslot;
780 maxclean = changer->cleanmax;
781 if (NULL != changer->conf[confnum].timefile)
782 time_file = stralloc(changer->conf[confnum].timefile);
783 if (NULL != changer->conf[confnum].slotfile)
784 changer_file = stralloc(changer->conf[confnum].slotfile);
787 if (NULL != changer->conf[confnum].device)
788 tape_device = stralloc(changer->conf[confnum].device);
789 if (NULL != changer->device)
790 changer_dev = stralloc(changer->device);
791 if (NULL != changer->conf[confnum].scsitapedev)
792 scsitapedevice = stralloc(changer->conf[confnum].scsitapedev);
793 if (NULL != changer->conf[confnum].tapestatfile)
794 tapestatfile = stralloc(changer->conf[confnum].tapestatfile);
795 dump_changer_struct(changer);
796 /* get info about the changer */
797 fd = OpenDevice(INDEX_CHANGER , changer_dev,
798 "changer_dev", changer->conf[confnum].changerident);
800 int localerr = errno;
801 g_fprintf(stderr, _("%s: open: %s: %s\n"), get_pname(),
802 changer_dev, strerror(localerr));
803 g_printf(_("%s open: %s: %s\n"), "<none>", changer_dev, strerror(localerr));
804 dbprintf(_("open: %s: %s\n"), changer_dev, strerror(localerr));
808 if (tape_device == NULL)
810 tape_device = stralloc(changer_dev);
813 if (scsitapedevice == NULL)
815 scsitapedevice = stralloc(tape_device);
818 if ((changer->conf[confnum].end == -1) || (changer->conf[confnum].start == -1)){
819 slotcnt = get_slot_count(fd);
823 free_changer_struct(&changer);
825 /* get info about the changer */
827 fd = OpenDevice(INDEX_CHANGER , changer_dev,
828 "changer_dev", changer->conf[confnum].changerident);
830 int localerr = errno;
831 g_fprintf(stderr, _("%s: open: %s: %s\n"), get_pname(),
832 changer_dev, strerror(localerr));
833 g_printf(_("%s open: %s: %s\n"), _("<none>"), changer_dev, strerror(localerr));
834 dbprintf(_("open: %s: %s\n"), changer_dev, strerror(localerr));
837 slotcnt = get_slot_count(fd);
845 drivecnt = get_drive_count(fd);
847 if (drive_num > drivecnt) {
848 g_printf(_("%s drive number error (%d > %d)\n"), _("<none>"),
849 drive_num, drivecnt);
850 g_fprintf(stderr, _("%s: requested drive number (%d) greater than "
851 "number of supported drives (%d)\n"), get_pname(),
852 drive_num, drivecnt);
853 dbprintf(_("requested drive number (%d) greater than "
854 "number of supported drives (%d)\n"), drive_num, drivecnt);
859 loaded = drive_loaded(fd, drive_num);
861 switch(com.command_code) {
862 case COM_SLOT: /* slot changing command */
863 if (is_positive_number(com.parameter)) {
864 if ((target = atoi(com.parameter))>=use_slots) {
865 g_printf(_("<none> no slot `%d'\n"),target);
870 target = target+slot_offset;
873 target=get_relative_target(fd, use_slots,
876 changer_file,slot_offset,slot_offset+use_slots);
878 if (changer_file != NULL)
880 oldtarget=get_current_slot(changer_file);
882 oldtarget = GetCurrentSlot(fd, drive_num);
884 if ((oldtarget)!=target) {
886 eject_tape(scsitapedevice, need_eject);
887 (void)unload(fd, drive_num, oldtarget);
888 if (ask_clean(scsitapedevice))
889 clean_tape(fd,tape_device,clean_file,drive_num,
890 clean_slot,maxclean,time_file);
894 if (changer_file != NULL)
896 put_current_slot(changer_file, target);
898 if (!loaded && isempty(fd, target)) {
899 g_printf(_("%d slot %d is empty\n"),target-slot_offset,
906 if (load(fd, drive_num, target) != 0) {
907 g_printf(_("%d slot %d move failed\n"),target-slot_offset,
914 Tape_Ready1(scsitapedevice, need_sleep);
915 g_printf(_("%d %s\n"), target-slot_offset, tape_device);
919 if (changer_file != NULL)
921 g_printf("%d ", get_current_slot(changer_file)-slot_offset);
923 g_printf("%d ", GetCurrentSlot(fd, drive_num)-slot_offset);
925 g_printf("%d 1\n", use_slots);
929 if (changer_file != NULL)
931 target=get_current_slot(changer_file);
933 target = GetCurrentSlot(fd, drive_num);
936 if (!isempty(fd, target))
937 target=find_empty(fd,0 ,0);
939 eject_tape(scsitapedevice, need_eject);
940 (void)unload(fd, drive_num, target);
941 if (ask_clean(scsitapedevice))
942 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
946 if (isempty(fd, slot_offset)) {
947 g_printf(_("0 slot 0 is empty\n"));
953 if (load(fd, drive_num, slot_offset) != 0) {
954 g_printf(_("%d slot %d move failed\n"),slot_offset,
960 if (changer_file != NULL)
962 put_current_slot(changer_file, slot_offset);
965 Tape_Ready1(scsitapedevice, need_sleep);
966 if (changer_file != NULL)
968 g_printf("%d %s\n", get_current_slot(changer_file), tape_device);
970 g_printf("%d %s\n", GetCurrentSlot(fd, drive_num), tape_device);
976 if (changer_file != NULL)
978 target=get_current_slot(changer_file);
980 target = GetCurrentSlot(fd, drive_num);
983 eject_tape(scsitapedevice, need_eject);
984 (void)unload(fd, drive_num, target);
985 if (ask_clean(scsitapedevice))
986 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
988 g_printf("%d %s\n", target, tape_device);
990 g_printf(_("%d drive was not loaded\n"), target);
996 if (changer_file != NULL)
998 target=get_current_slot(changer_file);
1000 target = GetCurrentSlot(fd, drive_num);
1003 eject_tape(scsitapedevice, need_eject);
1004 (void)unload(fd, drive_num, target);
1006 clean_tape(fd,tape_device,clean_file,drive_num,clean_slot,
1007 maxclean,time_file);
1008 g_printf(_("%s cleaned\n"), tape_device);
1018 * indent-tabs-mode: nil