static char rcsid[] = "$Id: chg-scsi.c,v 1.52.2.2 2006/12/12 14:56:37 martinea Exp $"; /* * * * chg-scsi.c -- generic SCSI changer driver * * This program provides the framework to control * SCSI changers. It is based on the original chg-scsi * from Eric Schnoebelen (Original copyright below) * The device dependent part is handled by scsi-changer-driver.c * The SCSI OS interface is handled by scsi-ostype.c * * Original copyrights: * * This program provides a driver to control generic * SCSI changers, no matter what platform. The host/OS * specific portions of the interface are implemented * in libscsi.a, which contains a module for each host/OS. * The actual interface for HP/UX is in scsi-hpux.c; * chio is in scsi-chio.c, etc.. A prototype system * dependent scsi interface file is in scsi-proto.c. * * Copyright 1997, 1998 Eric Schnoebelen * * This module based upon seagate-changer, by Larry Pyeatt * * * The original introductory comments follow: * * This program was written to control the Seagate/Conner/Archive * autoloading DAT drive. This drive normally has 4 tape capacity * but can be expanded to 12 tapes with an optional tape cartridge. * This program may also work on onther drives. Try it and let me * know of successes/failures. * * I have attempted to conform to the requirements for Amanda tape * changer interface. There could be some bugs. * * This program works for me under Linux with Gerd Knorr's * SCSI media changer driver installed * as a kernel module. The kernel module is available at * http://sunsite.unc.edu/pub/Linux/kernel/patches/scsi/scsi-changer* * Since the Linux media changer is based on NetBSD, this program * should also work for NetBSD, although I have not tried it. * It may be necessary to change the IOCTL calls to work on other * OS's. * * (c) 1897 Larry Pyeatt, pyeatt@cs.colostate.edu * All Rights Reserved. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. The author makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * Michael C. Povel 03.06.98 added ejetct_tape and sleep for external tape * devices, and changed some code to allow multiple drives to use their * own slots. Also added support for reserverd slots. * At the moment these parameters are hard coded and only tested under Linux * */ #include "config.h" #include "amanda.h" #include "conffile.h" #include "libscsi.h" #include "scsi-defs.h" #include "tapeio.h" char *tapestatfile = NULL; FILE *debug_file = NULL; /* * So we have 3 devices, here will all the infos be stored after an * successfull open */ OpenFiles_T *pDev = NULL; /* Defined in scsi-changer-driver.c */ extern int ElementStatusValid; extern ElementInfo_T *pMTE; /*Medium Transport Element */ extern ElementInfo_T *pSTE; /*Storage Element */ extern ElementInfo_T *pIEE; /*Import Export Element */ extern ElementInfo_T *pDTE; /*Data Transfer Element */ extern size_t MTE; /*Counter for the above element types */ extern size_t STE; extern size_t IEE; extern size_t DTE; int do_inventory = 0; /* Set if load/unload functions thinks an inventory should be done */ int clean_slot = -1; typedef enum{ NUMDRIVE,EJECT,SLEEP,CLEANMAX,DRIVE,START,END,CLEAN,DEVICE,STATFILE,CLEANFILE,DRIVENUM, CHANGERDEV,USAGECOUNT,SCSITAPEDEV, TAPESTATFILE, LABELFILE, CHANGERIDENT, TAPEIDENT, EMUBARCODE, HAVEBARCODE, DEBUGLEVEL, AUTOINV } token_t; typedef struct { char *word; token_t token; } tokentable_t; tokentable_t t_table[] = { { "number_configs", NUMDRIVE}, { "autoinv", AUTOINV}, { "eject", EJECT}, { "sleep", SLEEP}, { "cleanmax", CLEANMAX}, { "config", DRIVE}, { "startuse", START}, { "enduse", END}, { "cleancart", CLEAN}, { "dev", DEVICE}, { "statfile", STATFILE}, { "cleanfile", CLEANFILE}, { "drivenum", DRIVENUM}, { "changerdev", CHANGERDEV}, { "usagecount", USAGECOUNT}, { "scsitapedev", SCSITAPEDEV}, { "tapestatus", TAPESTATFILE}, { "labelfile", LABELFILE}, { "changerident", CHANGERIDENT}, { "tapeident", TAPEIDENT}, { "emubarcode", EMUBARCODE}, { "havebarcode", HAVEBARCODE}, { "debuglevel", DEBUGLEVEL}, { NULL, -1 } }; changer_t *changer; int ask_clean(char *tapedev); int get_current_slot(char *count_file); int get_relative_target(int fd, int nslots, char *parameter, int param_index, int loaded, char *slot_file, int slot_offset, int maxslot); int is_positive_number(char *tmp); int MapBarCode(char *labelfile, MBC_T *result); int read_config(char *configfile, changer_t *chg); void clean_tape(int fd, char *tapedev, char *cnt_file, int drivenum, int cleancart, int maxclean, char *usagetime); void dump_changer_struct(changer_t *chg); void free_changer_struct(changer_t **chg); void init_changer_struct(changer_t *chg, int number_of_config); void parse_line(char *linebuffer, int *token,char **value); void put_current_slot(char *count_file, int slot); void usage(char *argv[]); int main(int argc, char *argv[]); /* Initialize data structures with default values */ void init_changer_struct( changer_t *chg, int number_of_config) { int i; memset(chg, 0, SIZEOF(*chg)); chg->number_of_configs = number_of_config; chg->eject = 1; chg->conf = alloc(SIZEOF(config_t) * (size_t)number_of_config); for (i=0; i < number_of_config; i++){ chg->conf[i].drivenum = 0; chg->conf[i].start = -1; chg->conf[i].end = -1; chg->conf[i].cleanslot = -1; chg->conf[i].device = NULL; chg->conf[i].slotfile = NULL; chg->conf[i].cleanfile = NULL; chg->conf[i].timefile = NULL; chg->conf[i].scsitapedev = NULL; chg->conf[i].tapestatfile = NULL; chg->conf[i].changerident = NULL; chg->conf[i].tapeident = NULL; } } /* Dump of information for debug */ void dump_changer_struct( changer_t *chg) { int i; dbprintf(("Number of configurations: %d\n",chg->number_of_configs)); dbprintf(("Tapes need eject: %s\n",(chg->eject>0?"Yes":"No"))); dbprintf (("\traw: %d\n",chg->eject)); dbprintf(("Inv. auto update: %s\n",(chg->autoinv>0?"Yes":"No"))); dbprintf (("\traw: %d\n",chg->autoinv)); dbprintf(("barcode reader : %s\n",(chg->havebarcode>0?"Yes":"No"))); dbprintf (("\traw: %d\n",chg->havebarcode)); dbprintf(("Emulate Barcode : %s\n",(chg->emubarcode>0?"Yes":"No"))); dbprintf (("\traw: %d\n",chg->emubarcode)); if (chg->debuglevel != NULL) dbprintf(("debug level : %s\n", chg->debuglevel)); dbprintf(("Tapes need sleep: %d seconds\n", (int)chg->sleep)); dbprintf(("Cleancycles : %d\n",chg->cleanmax)); dbprintf(("Changerdevice : %s\n",chg->device)); if (chg->labelfile != NULL) dbprintf(("Labelfile : %s\n", chg->labelfile)); for (i=0; inumber_of_configs; i++){ dbprintf(("Tapeconfig Nr: %d\n",i)); dbprintf((" Drivenumber : %d\n",chg->conf[i].drivenum)); dbprintf((" Startslot : %d\n",chg->conf[i].start)); dbprintf((" Endslot : %d\n",chg->conf[i].end)); dbprintf((" Cleanslot : %d\n",chg->conf[i].cleanslot)); if (chg->conf[i].device != NULL) dbprintf((" Devicename : %s\n",chg->conf[i].device)); else dbprintf((" Devicename : none\n")); if (chg->conf[i].changerident != NULL) dbprintf((" changerident : %s\n",chg->conf[i].changerident)); else dbprintf((" changerident : none\n")); if (chg->conf[i].scsitapedev != NULL) dbprintf((" SCSITapedev : %s\n",chg->conf[i].scsitapedev)); else dbprintf((" SCSITapedev : none\n")); if (chg->conf[i].tapeident != NULL) dbprintf((" tapeident : %s\n",chg->conf[i].tapeident)); else dbprintf((" tapeident : none\n")); if (chg->conf[i].tapestatfile != NULL) dbprintf((" statfile : %s\n", chg->conf[i].tapestatfile)); else dbprintf((" statfile : none\n")); if (chg->conf[i].slotfile != NULL) dbprintf((" Slotfile : %s\n",chg->conf[i].slotfile)); else dbprintf((" Slotfile : none\n")); if (chg->conf[i].cleanfile != NULL) dbprintf((" Cleanfile : %s\n",chg->conf[i].cleanfile)); else dbprintf((" Cleanfile : none\n")); if (chg->conf[i].timefile != NULL) dbprintf((" Usagecount : %s\n",chg->conf[i].timefile)); else dbprintf((" Usagecount : none\n")); } } /* Free all allocated memory */ void free_changer_struct( changer_t **changer) { changer_t *chg; int i; chg = *changer; if (chg->device != NULL) amfree(chg->device); for (i = 0; i < chg->number_of_configs; i++){ if (chg->conf[i].device != NULL) amfree(chg->conf[i].device); if (chg->conf[i].slotfile != NULL) amfree(chg->conf[i].slotfile); if (chg->conf[i].cleanfile != NULL) amfree(chg->conf[i].cleanfile); if (chg->conf[i].timefile != NULL) amfree(chg->conf[i].timefile); } if (chg->conf != NULL) amfree(chg->conf); chg->conf = NULL; chg->device = NULL; amfree(*changer); } /* This function parses a line, and returns a token and value */ void parse_line( char *linebuffer, int *token, char **value) { char *tok; int i; int ready = 0; *token = -1; /* No Token found */ tok=strtok(linebuffer," \t\n"); while ((tok != NULL) && (tok[0]!='#')&&(ready==0)){ if (*token != -1){ *value=tok; ready=1; } else { i=0; while ((t_table[i].word != NULL)&&(*token==-1)){ if (0==strcasecmp(t_table[i].word,tok)){ *token=t_table[i].token; } i++; } } tok=strtok(NULL," \t\n"); } } /* This function reads the specified configfile and fills the structure */ int read_config( char *configfile, changer_t *chg) { int numconf; FILE *file; int init_flag = 0; int drivenum=0; char *linebuffer; int token; char *value; char *p; numconf = 1; /* At least one configuration is assumed */ /* If there are more, it should be the first entry in the configurationfile */ assert(chg != NULL); if ((file=fopen(configfile,"r")) == NULL) { return (-1); } while ((NULL != (linebuffer = agets(file)))) { if (linebuffer[0] == '\0') { amfree(linebuffer); continue; } parse_line(linebuffer, &token, &value); if (token != -1){ if (value == NULL) value = "0"; if (init_flag == 0) { if (token != NUMDRIVE){ init_changer_struct(chg, numconf); } else { numconf = atoi(value); if (numconf < 1 || numconf > 100) { fprintf(stderr,"numconf %d is bad\n", numconf); numconf = 1; } init_changer_struct(chg, numconf); } init_flag=1; } switch (token) { case NUMDRIVE: if (atoi(value) != numconf) fprintf(stderr,"Error: number_drives at wrong place, should be "\ "first in file\n"); break; case AUTOINV: chg->autoinv = 1; break; case EMUBARCODE: chg->emubarcode = 1; break; case DEBUGLEVEL: chg->debuglevel = stralloc(value); break; case EJECT: chg->eject = atoi(value); break; case HAVEBARCODE: chg->havebarcode = atoi(value); break; case SLEEP: chg->sleep = (unsigned)atoi(value); break; case LABELFILE: chg->labelfile = stralloc(value); break; case CHANGERDEV: chg->device = stralloc(value); break; case SCSITAPEDEV: chg->conf[drivenum].scsitapedev = stralloc(value); break; case TAPESTATFILE: chg->conf[drivenum].tapestatfile = stralloc(value); break; case CHANGERIDENT: chg->conf[drivenum].changerident = stralloc(value); if (drivenum < 0 || drivenum > 100) { fprintf(stderr,"drivenum %d is bad\n", drivenum); drivenum = 0; } if (strcmp(chg->conf[drivenum].changerident,"generic_changer") != 0) { p = chg->conf[drivenum].changerident; while (*p != '\0') { if (*p == '_') { *p=' '; } p++; } } break; case TAPEIDENT: chg->conf[drivenum].tapeident = stralloc(value); break; case CLEANMAX: chg->cleanmax = atoi(value); break; case DRIVE: drivenum = atoi(value); if (drivenum < 0) { fprintf(stderr,"Error: drive must be >= 0\n"); drivenum = 0; } else if (drivenum >= numconf) { fprintf(stderr,"Error: drive must be less than number_drives\n"); drivenum = numconf; } break; case DRIVENUM: if (drivenum < numconf){ chg->conf[drivenum].drivenum = atoi(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " drivenum ignored\n"); } break; case START: if (drivenum < numconf){ chg->conf[drivenum].start = atoi(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " startuse ignored\n"); } break; case END: if (drivenum < numconf){ chg->conf[drivenum].end = atoi(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " enduse ignored\n"); } break; case CLEAN: if (drivenum < numconf){ chg->conf[drivenum].cleanslot = atoi(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " cleanslot ignored\n"); } break; case DEVICE: if (drivenum < numconf){ chg->conf[drivenum].device = stralloc(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " device ignored\n"); } break; case STATFILE: if (drivenum < numconf){ chg->conf[drivenum].slotfile = stralloc(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " slotfile ignored\n"); } break; case CLEANFILE: if (drivenum < numconf){ chg->conf[drivenum].cleanfile = stralloc(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " cleanfile ignored\n"); } break; case USAGECOUNT: if (drivenum < numconf){ chg->conf[drivenum].timefile = stralloc(value); } else { fprintf(stderr,"Error: drive is not less than number_drives"\ " usagecount ignored\n"); } break; default: fprintf(stderr,"Error: Unknown token\n"); break; } } amfree(linebuffer); } amfree(linebuffer); fclose(file); return 0; } /*----------------------------------------------------------------------------*/ /* The tape drive does not have an idea of current slot so * we use a file to store the current slot. It is not ideal * but it gets the job done */ int get_current_slot( char *count_file) { FILE *inf; int retval = -1; int ret; /* return value for the fscanf function */ if ((inf=fopen(count_file,"r")) == NULL) { fprintf(stderr, "%s: unable to open (%s)\n", get_pname(), count_file); exit(2); } ret = fscanf(inf,"%d",&retval); fclose(inf); /* * check if we got an result * if no set retval to -1 */ if (ret == 0 || ret == EOF) { retval = -1; } if (retval < 0 || retval > 10000) { retval = -1; } return retval; } void put_current_slot( char *count_file, int slot) { FILE *inf; if (!count_file) return; if ((inf=fopen(count_file,"w")) == NULL) { fprintf(stderr, "%s: unable to open current slot file (%s)\n", get_pname(), count_file); exit(2); } fprintf(inf, "%d\n", slot); fclose(inf); } /* * Here we handle the inventory DB * With this it should be possible to do an mapping * Barcode -> Volume label * Volume Label -> Barcode * Volume label -> Slot number * Return Values: * 1 -> Action was ok * 0 -> Action failed * * The passed struct MBC_T will hold the found entry in the DB */ int MapBarCode( char *labelfile, MBC_T *result) { FILE *fp; int version; LabelV2_T *plabelv2; long unusedpos= 0; int unusedrec = 0; int record = 0; int loop = 1; size_t rsize; long pos; int rc; DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : Parameter\n"); DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"labelfile -> %s, vol -> %s, barcode -> %s, action -> %c, slot -> %d, from -> %d\n", labelfile, result->data.voltag, result->data.barcode, result->action, result->data.slot, result->data.from); if (labelfile == NULL) { DebugPrint(DEBUG_ERROR,SECTION_MAP_BARCODE,"Got empty labelfile (NULL)\n"); ChgExit("MapBarCode", "MapBarCode name of labelfile is not set\n",FATAL); /*NOTREACHED*/ } if (access(labelfile, F_OK) == -1) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE, "MapBarCode : creating %s", labelfile); if ((fp = fopen(labelfile, "w+")) == NULL) { DebugPrint(DEBUG_ERROR,SECTION_MAP_BARCODE," failed\n"); ChgExit("MapBarCode", "MapBarCode, creating labelfile failed\n", FATAL); /*NOTREACHED*/ } fprintf(fp,":%d:", LABEL_DB_VERSION); fclose(fp); } if ((fp = fopen(labelfile, "r+")) == NULL) { DebugPrint(DEBUG_ERROR,SECTION_MAP_BARCODE,"MapBarCode : failed to open %s\n", labelfile); ChgExit("MapBarCode", "MapBarCode, opening labelfile for read/write failed\n", FATAL); /*NOTREACHED*/ } if (fscanf(fp,":%d:", &version) != 1) { ChgExit("MapBarCode", "MapBarCode, DB Version unreadable.\n", FATAL); /*NOTREACHED*/ } DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : DB version %d\n", version); pos = ftell(fp); if (version != LABEL_DB_VERSION) { ChgExit("MapBarCode", "MapBarCode, DB Version does not match\n", FATAL); /*NOTREACHED*/ } if (( plabelv2 = (LabelV2_T *)alloc(SIZEOF(LabelV2_T))) == NULL) { DebugPrint(DEBUG_ERROR,SECTION_MAP_BARCODE,"MapBarCode : alloc failed\n"); ChgExit("MapBarCode", "MapBarCode alloc failed\n", FATAL); /*NOTREACHED*/ } memset(plabelv2, 0, SIZEOF(LabelV2_T)); while(feof(fp) == 0 && loop == 1) { rsize = fread(plabelv2, 1, SIZEOF(LabelV2_T), fp); if (rsize == SIZEOF(LabelV2_T)) { record++; DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : (%d) VolTag \"%s\", BarCode %s, inuse %d, slot %d, from %d, loadcount %d\n",record, plabelv2->voltag, plabelv2->barcode, plabelv2->valid, plabelv2->slot, plabelv2->from, plabelv2->LoadCount); switch (result->action) { /* * Only dump the info */ case BARCODE_DUMP: printf("Slot -> %d, from -> %d, valid -> %d, Tag -> %s, Barcode -> %s, Loadcount %u\n", plabelv2->slot, plabelv2->from, plabelv2->valid, plabelv2->voltag, plabelv2->barcode, plabelv2->LoadCount ); break; /* * Set all the record to invalid, used by the Inventory function */ case RESET_VALID: plabelv2->valid = 0; if (fseek(fp, pos, SEEK_SET) == -1) { fclose(fp); amfree(plabelv2); return 0; /* Fail */ } if (fwrite(plabelv2, 1, SIZEOF(LabelV2_T), fp) < SIZEOF(LabelV2_T)) { fclose(fp); amfree(plabelv2); return 0; /* Fail */ } break; /* * Add an entry */ case BARCODE_PUT: /* * If it is an invalid record we can use it, * so save the record number. * This value is used at the end if no other * record/action matches. */ if (plabelv2->valid == 0) { unusedpos = pos; unusedrec = record; } /* * OK this record matches the barcode label * so use/update it */ if (strcmp(plabelv2->barcode, result->data.barcode) == 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : update entry\n"); if (fseek(fp, pos, SEEK_SET) == -1) { fclose(fp); amfree(plabelv2); return 0; /* Fail */ } plabelv2->valid = 1; plabelv2->from = result->data.from; plabelv2->slot = result->data.slot; plabelv2->LoadCount = plabelv2->LoadCount + result->data.LoadCount; strncpy(plabelv2->voltag, result->data.voltag, SIZEOF(plabelv2->voltag)); strncpy(plabelv2->barcode, result->data.barcode, SIZEOF(plabelv2->barcode)); rc = (fwrite(plabelv2, 1, SIZEOF(LabelV2_T), fp) < SIZEOF(LabelV2_T)); fclose(fp); amfree(plabelv2); return(rc); } break; /* * Look for an entry an return the entry * if the voltag (the tape name) matches */ case FIND_SLOT: if (strcmp(plabelv2->voltag, result->data.voltag) == 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode FIND_SLOT : \n"); memcpy(&(result->data), plabelv2, SIZEOF(LabelV2_T)); amfree(plabelv2); return(1); } break; /* * Update the entry, * reason can be an load, incr the LoadCount * or an new tape */ case UPDATE_SLOT: if (strcmp(plabelv2->voltag, result->data.voltag) == 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode UPDATE_SLOT : update entry\n"); if (fseek(fp, pos, SEEK_SET) == -1) { fclose(fp); amfree(plabelv2); return 0; /* Fail */ } strncpy(plabelv2->voltag, result->data.voltag, SIZEOF(plabelv2->voltag)); strncpy(plabelv2->barcode, result->data.barcode, SIZEOF(plabelv2->barcode)); plabelv2->valid = 1; plabelv2->slot = result->data.slot; plabelv2->from = result->data.from; plabelv2->LoadCount = plabelv2->LoadCount + result->data.LoadCount; rc = (fwrite(plabelv2, 1, SIZEOF(LabelV2_T), fp) < SIZEOF(LabelV2_T)); fclose(fp); amfree(plabelv2); return(rc); } break; /* * Look for the barcode label of an given volume label * return the slot number and the barcode label. * If the entry is not valid return -1 as slot number */ case BARCODE_VOL: /* * DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode: (%d) inside BARCODE_VOL\n", record); DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"file value: %s, searched for value: %s\n", plabelv2->voltag, result->data.voltag); */ if (strcmp(plabelv2->voltag, result->data.voltag) == 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : VOL %s match\n", result->data.voltag); fclose(fp); memcpy(&(result->data), plabelv2, SIZEOF(LabelV2_T)); amfree(plabelv2); return(1); } break; /* * Look for an entry which matches the passed * barcode label */ case BARCODE_BARCODE: if (strcmp(plabelv2->barcode, result->data.barcode) == 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : BARCODE %s match\n", result->data.barcode); fclose(fp); memcpy(&(result->data), plabelv2, SIZEOF(LabelV2_T)); amfree(plabelv2); return(1); } break; default: DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : unknown action\n"); break; } pos = ftell(fp); } else { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : feof (%d)\n", feof(fp)); DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : error in read record expect %d, got %d\n",SIZEOF(LabelV2_T), rsize); loop=0; } } /* * OK, if we come here and the action is either * PUT or update it seems that we have to create a new * record, becuae none of the exsisting records matches */ if (result->action == BARCODE_PUT || result->action == UPDATE_SLOT ) { /* * If we have an entry where the valid flag was set to 0 * we can use this record, so seek to this position * If we have no record for reuse the new record will be written to the end. */ if (unusedpos != 0) { DebugPrint(DEBUG_INFO,SECTION_MAP_BARCODE,"MapBarCode : reuse record %d\n", unusedrec); if (fseek(fp, unusedpos, SEEK_SET) == -1) { fclose(fp); amfree(plabelv2); return 0; /* Fail */ } } /* * Set all values to zero */ memset(plabelv2, 0, SIZEOF(LabelV2_T)); strncpy(plabelv2->voltag, result->data.voltag, SIZEOF(plabelv2->voltag)); strncpy(plabelv2->barcode, result->data.barcode, SIZEOF(plabelv2->barcode)); plabelv2->valid = 1; plabelv2->from = result->data.from; plabelv2->slot = result->data.slot; rc = (fwrite(plabelv2, 1, SIZEOF(LabelV2_T), fp) < SIZEOF(LabelV2_T)); fclose(fp); amfree(plabelv2); return(rc); } /* * If we hit this point nothing was * found, so return an 0 */ fclose(fp); amfree(plabelv2); return(0); } /* ---------------------------------------------------------------------- This stuff deals with parsing the command line */ typedef struct com_arg { char *str; int command_code; int takesparam; } argument; typedef struct com_stru { int command_code; char *parameter; } command; void parse_args(int argc, char *argv[],command *rval); /* major command line args */ #define COMCOUNT 13 #define COM_SLOT 0 #define COM_INFO 1 #define COM_RESET 2 #define COM_EJECT 3 #define COM_CLEAN 4 #define COM_LABEL 5 #define COM_SEARCH 6 #define COM_STATUS 7 #define COM_TRACE 8 #define COM_INVENTORY 9 #define COM_DUMPDB 10 #define COM_SCAN 11 #define COM_GEN_CONF 12 argument argdefs[]={{"-slot",COM_SLOT,1}, {"-info",COM_INFO,0}, {"-reset",COM_RESET,0}, {"-eject",COM_EJECT,0}, {"-clean",COM_CLEAN,0}, {"-label",COM_LABEL,1}, {"-search",COM_SEARCH,1}, {"-status",COM_STATUS,1}, {"-trace",COM_TRACE,1}, {"-inventory", COM_INVENTORY,0}, {"-dumpdb", COM_DUMPDB,0}, {"-scan", COM_SCAN,0}, {"-genconf", COM_GEN_CONF,0} }; /* minor command line args */ #define SLOT_CUR 0 #define SLOT_NEXT 1 #define SLOT_PREV 2 #define SLOT_FIRST 3 #define SLOT_LAST 4 #define SLOT_ADVANCE 5 argument slotdefs[]={{"current",SLOT_CUR,0}, {"next",SLOT_NEXT,0}, {"prev",SLOT_PREV,0}, {"first",SLOT_FIRST,0}, {"last",SLOT_LAST,0}, {"advance",SLOT_ADVANCE,0}, }; #define SLOTCOUNT (int)(sizeof(slotdefs) / sizeof(slotdefs[0])) /* is the string a valid positive int? */ int is_positive_number( char *tmp) { int i=0; if ((tmp==NULL)||(tmp[0]==0)) return 0; while ((tmp[i]>='0')&&(tmp[i]<='9')&&(tmp[i]!=0)) i++; if (tmp[i]==0) return 1; else return 0; } void usage( char *argv[]) { int cnt; printf("%s: Usage error.\n", argv[0]); for (cnt=0; cnt < COMCOUNT; cnt++){ printf(" %s %s",argv[0],argdefs[cnt].str); if (argdefs[cnt].takesparam) printf(" \n"); else printf("\n"); } exit(2); } void parse_args( int argc, char *argv[], command *rval) { int i; for (i=0; i < argc; i++) dbprintf(("ARG [%d] : %s\n", i, argv[i])); i = 0; if ((argc<2)||(argc>3)) usage(argv); while ((icommand_code = argdefs[i].command_code; if (argdefs[i].takesparam) { if (argc<3) usage(argv); rval->parameter=argv[2]; } else { if (argc>2) usage(argv); rval->parameter=0; } } /* used to find actual slot number from keywords next, prev, first, etc */ int get_relative_target( int fd, int nslots, char *parameter, int param_index, int loaded, char *slot_file, int slot_offset, int maxslot) { int current_slot; (void)loaded; /* Quiet unused parameter warning */ current_slot = get_current_slot(slot_file); if (current_slot > maxslot) { current_slot = slot_offset; } if (current_slot < slot_offset) { current_slot = slot_offset; } switch(param_index) { case SLOT_CUR: return current_slot; case SLOT_NEXT: case SLOT_ADVANCE: if (++current_slot==nslots+slot_offset) return slot_offset; return current_slot; case SLOT_PREV: if (--current_slot no slot `%s'\n",parameter); close(fd); exit(2); /*NOTREACHED*/ } /* This function should ask the drive if it wants to be cleaned */ int ask_clean( char *tapedev) { int ret; ret = get_clean_state(tapedev); if (ret < 0) /* < 0 means query does not work ... */ { return(0); } return ret; } /* This function should move the cleaning cartridge into the drive */ void clean_tape( int fd, char *tapedev, char *cnt_file, int drivenum, int cleancart, int maxclean, char *usagetime) { int counter; if (cleancart == -1 ){ return; } /* Now we should increment the counter */ if (cnt_file != NULL){ counter = get_current_slot(cnt_file); counter++; if (counter>=maxclean){ /* Now we should inform the administrator */ char *mail_cmd = NULL; FILE *mailf = NULL; int mail_pipe_opened = 1; if(getconf_seen(CNF_MAILTO) && strlen(getconf_str(CNF_MAILTO)) > 0 && validate_mailto(getconf_str(CNF_MAILTO))) { mail_cmd = vstralloc(MAILER, " -s", " \"", "AMANDA PROBLEM: PLEASE FIX", "\"", " ", getconf_str(CNF_MAILTO), NULL); if((mailf = popen(mail_cmd, "w")) == NULL){ printf("Mail failed\n"); error("could not open pipe to \"%s\": %s", mail_cmd, strerror(errno)); /*NOTREACHED*/ } } else { mail_pipe_opened = 0; mailf = stderr; fprintf(mailf, "\nNo mail recipient specified, output redirected to stderr"); } fprintf(mailf,"\nThe usage count of your cleaning tape in slot %d", cleancart); fprintf(mailf,"\nis more than %d. (cleanmax)",maxclean); fprintf(mailf,"\nTapedrive %s needs to be cleaned",tapedev); fprintf(mailf,"\nPlease insert a new cleaning tape and reset"); fprintf(mailf,"\nthe countingfile %s",cnt_file); if(mail_pipe_opened == 1 && pclose(mailf) != 0) { error("mail command failed: %s", mail_cmd); /*NOTREACHED*/ } return; } put_current_slot(cnt_file, counter); } load(fd,drivenum,cleancart); /* * Hack, sleep for some time */ sleep(60); if (drive_loaded(fd, drivenum)) unload(fd, drivenum, cleancart); if (usagetime) unlink(usagetime); } /* ----------------------------------------------------------------------*/ int main( int argc, char *argv[]) { int loaded; int target, oldtarget; command com; /* a little DOS joke */ int x; MBC_T *pbarcoderes; /* * drive_num really should be something from the config file, but.. * for now, it is set to zero, since most of the common changers * used by amanda only have one drive ( until someone wants to * use an EXB60/120, or a Breece Hill Q45.. ) */ unsigned char emubarcode; int drive_num; int need_eject; /* Does the drive need an eject command ? */ time_t need_sleep; /* How many seconds to wait for the drive to get ready */ int maxclean; char *clean_file; char *time_file; /* * For the emubarcode stuff */ int use_slots; int slot_offset; int confnum; int fd; int slotcnt; int drivecnt; int endstatus = 0; char *changer_dev; char *tape_device; char *chg_scsi_conf; /* The config file for us */ char *slot_file; /* Where we will place the info which * slot is loaded */ char *scsitapedevice; int param_index = 0; changer = alloc(SIZEOF(changer_t)); pbarcoderes = alloc(SIZEOF(MBC_T)); memset(pbarcoderes, 0 , SIZEOF(MBC_T)); changer->number_of_configs = 0; changer->eject = 0; changer->sleep = 0; changer->cleanmax = 0; changer->device = NULL; changer->labelfile = NULL; changer->conf = NULL; #ifdef CHG_SCSI_STANDALONE printf("Ups standalone\n"); #else set_pname("chg-scsi"); /* Don't die when child closes pipe */ signal(SIGPIPE, SIG_IGN); dbopen(DBG_SUBDIR_SERVER); dbprintf(("chg-scsi: %s\n", rcsid)); ChangerDriverVersion(); if (debug_file == NULL) { debug_file = dbfp(); } parse_args(argc,argv,&com); pDev = (OpenFiles_T *)alloc(SIZEOF(OpenFiles_T) * CHG_MAXDEV); memset(pDev, 0, SIZEOF(OpenFiles_T) * CHG_MAXDEV ); switch(com.command_code) { case COM_SCAN: ScanBus(1); return(0); case COM_GEN_CONF: ScanBus(0); PrintConf(); return(0); default: break; } if(read_conffile(CONFFILE_NAME)) { perror(CONFFILE_NAME); exit(1); /*NOTREACHED*/ } chg_scsi_conf = getconf_str(CNF_CHNGRFILE); tape_device = getconf_str(CNF_TAPEDEV); /* Get the configuration parameters */ /* Attention, this will not support more than 10 tape devices 0-9 */ /* */ if (strlen(tape_device)==1){ if (read_config(chg_scsi_conf, changer) == -1) { fprintf(stderr, "%s open: of %s failed\n", get_pname(), chg_scsi_conf); return (2); } confnum=atoi(tape_device); if (changer->number_of_configs == 0) { fprintf(stderr,"%s: changer->conf[%d] == NULL\n", get_pname(), confnum); return (2); } if (confnum >= changer->number_of_configs) { fprintf(stderr,"%s: Configuration %s config # out of range (%d >= %d)\n", get_pname(), chg_scsi_conf, confnum, changer->number_of_configs); return (2); } use_slots = changer->conf[confnum].end-changer->conf[confnum].start+1; slot_offset = changer->conf[confnum].start; drive_num = changer->conf[confnum].drivenum; need_eject = changer->eject; need_sleep = changer->sleep; if ( NULL != changer->conf[confnum].cleanfile) clean_file = stralloc(changer->conf[confnum].cleanfile); else clean_file = NULL; clean_slot = changer->conf[confnum].cleanslot; maxclean = changer->cleanmax; emubarcode = changer->emubarcode; if (NULL != changer->conf[confnum].timefile) time_file = stralloc(changer->conf[confnum].timefile); else time_file = NULL; if (NULL != changer->conf[confnum].slotfile) slot_file = stralloc(changer->conf[confnum].slotfile); else slot_file = NULL; if (NULL != changer->conf[confnum].device) tape_device = stralloc(changer->conf[confnum].device); else tape_device = NULL; if (NULL != changer->device) changer_dev = stralloc(changer->device); else changer_dev = NULL; if (NULL != changer->conf[confnum].scsitapedev) scsitapedevice = stralloc(changer->conf[confnum].scsitapedev); else scsitapedevice = NULL; if (NULL != changer->conf[confnum].tapestatfile) tapestatfile = stralloc(changer->conf[confnum].tapestatfile); else tapestatfile = NULL; dump_changer_struct(changer); /* * The changer device. * If we can't open it fail with a message */ if (OpenDevice(INDEX_CHANGER , changer_dev, "changer_dev", changer->conf[confnum].changerident) == 0) { int localerr = errno; fprintf(stderr, "%s: open: %s: %s\n", get_pname(), changer_dev, strerror(localerr)); printf("%s open: %s: %s\n", "", changer_dev, strerror(localerr)); dbprintf(("%s: open: %s: %s\n", get_pname(), changer_dev, strerror(localerr))); return 2; } fd = INDEX_CHANGER; /* * The tape device. * We need it for: * eject if eject is set * inventory (reading of the labels) if emubarcode (not yet) */ if (tape_device != NULL) { if (OpenDevice(INDEX_TAPE, tape_device, "tape_device", changer->conf[confnum].tapeident) == 0) { dbprintf(("warning open of %s: failed\n", tape_device)); } } /* * This is for the status pages of the SCSI tape, nice to have but no must.... */ if (scsitapedevice != NULL) { if (OpenDevice(INDEX_TAPECTL, scsitapedevice, "scsitapedevice", changer->conf[confnum].tapeident) == 0) { dbprintf(("warning open of %s: failed\n", scsitapedevice)); } } /* * So if we need eject we need either an raw device to eject with an ioctl, * or an SCSI device to send the SCSI eject. */ if (need_eject != 0 ) { if (pDev[INDEX_TAPE].avail == 0 && pDev[INDEX_TAPECTL].avail == 0) { printf("No device found for tape eject"); return(2); } } if ((changer->conf[confnum].end == -1) || (changer->conf[confnum].start == -1)){ slotcnt = get_slot_count(fd); use_slots = slotcnt; slot_offset = 0; } /* * Now check if we have all what we need * If either emubarcode is set or the changer support barcode * we need an label file */ if ( changer->emubarcode == 1 || BarCode(INDEX_CHANGER) == 1) { if (changer->labelfile == NULL) { printf("labelfile param not set in your config\n"); return(2); } } if (slot_file == NULL) { printf("slotfile param. not set in your config\n"); return(2); } if (access(slot_file,R_OK|W_OK) != 0) { printf("slotfile %s does not exsist or is not read/write\n", slot_file); return(2); } } else { /* if (strlen(tape_device)==1) */ printf("please check your config and use a config file for chg-scsi\n"); return(2); } drivecnt = get_drive_count(fd); if (drive_num > drivecnt) { printf("%s drive number error (%d > %d)\n", "", drive_num, drivecnt); fprintf(stderr, "%s: requested drive number (%d) greater than " "number of supported drives (%d)\n", get_pname(), drive_num, drivecnt); dbprintf(("%s: requested drive number (%d) greater than " "number of supported drives (%d)\n", get_pname(), drive_num, drivecnt)); return 2; } loaded = (int)drive_loaded(fd, drive_num); target = -1; switch(com.command_code) { /* This is only for the experts ;-) */ case COM_TRACE: ChangerReplay(com.parameter); break; /* */ case COM_DUMPDB: pbarcoderes->action = BARCODE_DUMP; MapBarCode(changer->labelfile, pbarcoderes); break; case COM_STATUS: ChangerStatus(com.parameter, changer->labelfile, BarCode(fd), slot_file, changer_dev, tape_device); break; case COM_LABEL: /* Update BarCode/Label mapping file */ pbarcoderes->action = BARCODE_PUT; strncpy(pbarcoderes->data.voltag, com.parameter, SIZEOF(pbarcoderes->data.voltag)); strncpy(pbarcoderes->data.barcode, pDTE[drive_num].VolTag, SIZEOF(pbarcoderes->data.barcode)); MapBarCode(changer->labelfile, pbarcoderes); printf("0 0 0\n"); break; /* * Inventory does an scan of the library and updates the mapping in the label DB */ case COM_INVENTORY: do_inventory = 1; /* Tell the label check not to exit on label errors */ if (loaded) { oldtarget = get_current_slot(slot_file); if (oldtarget < 0) { dbprintf(("COM_INVENTORY: get_current_slot %d\n", oldtarget)); oldtarget = find_empty(fd, slot_offset, use_slots); dbprintf(("COM_INVENTORY: find_empty %d\n", oldtarget)); } if (need_eject) { eject_tape(scsitapedevice, need_eject); } else { if (pDev[INDEX_TAPECTL].avail == 1 && pDev[INDEX_TAPE].avail == 1) { LogSense(INDEX_TAPE); } } (void)unload(fd, drive_num, oldtarget); if (ask_clean(scsitapedevice)) clean_tape(fd,tape_device,clean_file,drive_num, clean_slot, maxclean, time_file); } Inventory(changer->labelfile, drive_num, need_eject, 0, 0, clean_slot); do_inventory = 0; /* If set on exit the labeldb will be set to invalid ..... */ break; /* * Search for the tape, the index is the volume label */ case COM_SEARCH: /* * If we have an barcode reader use * this way */ if (BarCode(fd) == 1 && emubarcode != 1) { dbprintf(("search : look for %s\n", com.parameter)); pbarcoderes->action = BARCODE_VOL; pbarcoderes->data.slot = -1; strncpy(pbarcoderes->data.voltag, com.parameter, SIZEOF(pbarcoderes->data.voltag)); if (MapBarCode(changer->labelfile, pbarcoderes) == 1) { /* * If both values are unset we have an problem * so leave the program */ if (pbarcoderes->data.slot == -1 && pbarcoderes->data.barcode == NULL) { printf("Label %s not found (1)\n",com.parameter); endstatus = 2; close(fd); break; } /* * Let's see, if we got an barcode check if it is * in the current inventory */ if (pbarcoderes->data.barcode != NULL) { for (x = 0; x < (int)STE; x++) { if (strcmp(pSTE[x].VolTag, pbarcoderes->data.barcode) == 0) { dbprintf(("search : found slot %d\n", x)); target = x; } } /* * Not found in the STE slots * my be in the DTE (tape) * If we find it check if it is in the right drive * if we have more than one drive. */ for (x = 0; x < (int)DTE; x++) { if (strcmp(pDTE[x].VolTag, pbarcoderes->data.barcode) == 0) { dbprintf(("search : found in tape %d\n", x)); /* */ if (x == drive_num) { oldtarget = get_current_slot(slot_file); printf("%d %s\n", oldtarget - slot_offset, tape_device); return(0); } else { printf("LABEL in wrong tape Unit\n"); return(2); } } } /* * not found, so do an exit... */ if (target == -1) { printf("Label %s not found (2) \n",com.parameter); close(fd); endstatus = 2; break; } } /* if barcode[0] != 0 */ /* * If we didn't find anything we will try the info * from the DB. A reason for not finding anything in the inventory * might be an unreadable barcode label */ if (target == -1 && pbarcoderes->data.slot != -1) { target = pbarcoderes->data.slot; } /* * OK, if target is still -1 do the exit */ if (target == -1) { printf("Label %s not found (3)\n",com.parameter); close(fd); endstatus = 2; break; } } } /* * And now if we have emubarcode set and no barcode reader * use this one */ if (emubarcode == 1 && BarCode(fd) != 1) { dbprintf(("search : look for %s\n", com.parameter)); pbarcoderes->action = FIND_SLOT; pbarcoderes->data.slot = -1; strncpy(pbarcoderes->data.voltag, com.parameter, SIZEOF(pbarcoderes->data.voltag)); if (MapBarCode(changer->labelfile, pbarcoderes) == 1) { if (pbarcoderes->data.valid == 1) { target = pbarcoderes->data.slot; } else { printf("Barcode DB out of sync \n"); close(fd); endstatus=2; break; } } else { printf("Label %s not found \n",com.parameter); close(fd); endstatus = 2; break; } } /* * The slot changing command */ case COM_SLOT: if (target == -1) { if (is_positive_number(com.parameter)) { if ((target = atoi(com.parameter))>=use_slots) { printf(" no slot `%d'\n",target); close(fd); endstatus = 2; break; } else { target = target + slot_offset; } } else { param_index=0; while((param_index < SLOTCOUNT) && (strcmp(slotdefs[param_index].str,com.parameter))) { param_index++; } target=get_relative_target(fd, use_slots, com.parameter,param_index, loaded, slot_file, slot_offset, (slot_offset + use_slots - 1)); } } if (loaded) { oldtarget = get_current_slot(slot_file); if (oldtarget < 0) { dbprintf(("COM_SLOT: get_current_slot %d\n", oldtarget)); oldtarget = find_empty(fd, slot_offset, use_slots); dbprintf(("COM_SLOT: find_empty %d\n", oldtarget)); } /* * TODO check if the request slot for the unload is empty */ if ((oldtarget)!=target) { if (need_eject) { eject_tape(scsitapedevice, need_eject); } else { /* * If we have an SCSI path to the tape and an raw io path * try to read the Error Counter and the label */ if (pDev[INDEX_TAPECTL].avail == 1 && pDev[INDEX_TAPE].avail == 1) { LogSense(INDEX_TAPE); } } (void)unload(fd, drive_num, oldtarget); if (ask_clean(scsitapedevice)) clean_tape(fd, tape_device, clean_file, drive_num, clean_slot, maxclean, time_file); loaded=0; } } put_current_slot(slot_file, target); if (!loaded && isempty(fd, target)) { printf("%d slot %d is empty\n",target - slot_offset, target - slot_offset); close(fd); endstatus = 1; break; } if (!loaded && param_index != SLOT_ADVANCE) { if (ask_clean(scsitapedevice)) clean_tape(fd, tape_device, clean_file, drive_num, clean_slot, maxclean, time_file); if (load(fd, drive_num, target) != 0) { printf("%d slot %d move failed\n",target - slot_offset, target - slot_offset); close(fd); endstatus = 2; break; } } if (need_sleep) { if (pDev[INDEX_TAPECTL].inqdone == 1) { if (Tape_Ready(INDEX_TAPECTL, need_sleep) == -1) { printf("tape not ready\n"); endstatus = 2; break; } } else { if (Tape_Ready(INDEX_TAPECTL, need_sleep) == -1) { printf("tape not ready\n"); endstatus = 2; break; } } } printf("%d %s\n", target - slot_offset, tape_device); break; case COM_INFO: loaded = (int)get_current_slot(slot_file); if (loaded < 0) { loaded = find_empty(fd, slot_offset, use_slots); } loaded = loaded - (int)slot_offset; printf("%d %d 1", loaded, use_slots); if (BarCode(fd) == 1 || emubarcode == 1) { printf(" 1\n"); } else { printf(" 0\n"); } break; case COM_RESET: target=get_current_slot(slot_file); if (target < 0) { dbprintf(("COM_RESET: get_current_slot %d\n", target)); target = find_empty(fd, slot_offset, use_slots); dbprintf(("COM_RESET: find_empty %d\n", target)); } if (loaded) { if (!isempty(fd, target)) target = find_empty(fd, slot_offset, use_slots); if (need_eject) { eject_tape(scsitapedevice, need_eject); } else { /* * If we have an SCSI path to the tape and an raw io path * try to read the Error Counter and the label */ if (pDev[INDEX_TAPECTL].avail == 1 && pDev[INDEX_TAPE].avail == 1) { LogSense(INDEX_TAPE); } } (void)unload(fd, drive_num, target); if (ask_clean(scsitapedevice)) clean_tape(fd,tape_device, clean_file, drive_num, clean_slot, maxclean, time_file); } if (isempty(fd, slot_offset)) { printf("0 slot 0 is empty\n"); close(fd); endstatus = 1; break; } if (load(fd, drive_num, slot_offset) != 0) { printf("%d slot %d move failed\n", drive_num, slot_offset); close(fd); put_current_slot(slot_file, slot_offset); endstatus = 2; break; } put_current_slot(slot_file, slot_offset); if (need_sleep) { if (pDev[INDEX_TAPECTL].inqdone == 1) { if (Tape_Ready(INDEX_TAPECTL, need_sleep) == -1) { printf("tape not ready\n"); endstatus = 2; break; } } else { if (Tape_Ready(INDEX_TAPECTL, need_sleep) == -1) { printf("tape not ready\n"); endstatus = 2; break; } } } printf("%d %s\n", slot_offset, tape_device); break; case COM_EJECT: if (loaded) { target = get_current_slot(slot_file); if (target < 0) { dbprintf(("COM_EJECT: get_current_slot %d\n", target)); target = find_empty(fd, slot_offset, use_slots); dbprintf(("COM_EJECT: find_empty %d\n", target)); } if (need_eject) { eject_tape(scsitapedevice, need_eject); } else { if (pDev[INDEX_TAPECTL].avail == 1 && pDev[INDEX_TAPE].avail == 1) { LogSense(INDEX_TAPE); } } (void)unload(fd, drive_num, target); if (ask_clean(scsitapedevice)) clean_tape(fd, tape_device, clean_file, drive_num, clean_slot, maxclean, time_file); printf("%d %s\n", target, tape_device); } else { printf("%d %s\n", target, "drive was not loaded"); endstatus = 1; } break; case COM_CLEAN: if (loaded) { target = get_current_slot(slot_file); if (target < 0) { dbprintf(("COM_CLEAN: get_current_slot %d\n", target)); target = find_empty(fd, slot_offset, use_slots); dbprintf(("COM_CLEAN: find_empty %d\n",target)); } if (need_eject) { eject_tape(scsitapedevice, need_eject); } else { if (pDev[INDEX_TAPECTL].avail == 1 && pDev[INDEX_TAPE].avail == 1) { LogSense(INDEX_TAPE); } } (void)unload(fd, drive_num, target); } clean_tape(fd, tape_device, clean_file, drive_num, clean_slot, maxclean, time_file); printf("%s cleaned\n", tape_device); break; }; /* FIX ME, should be an function to close the device */ /* if (pChangerDev != NULL) */ /* close(pChangerDev->fd); */ /* if (pTapeDev != NULL) */ /* close(pTapeDev->fd); */ /* if (pTapeDevCtl != NULL) */ /* close(pTapeDevCtl->fd); */ #endif if (do_inventory == 1 && endstatus == 0 && changer->labelfile != NULL) { if (changer->autoinv == 1) { DebugPrint(DEBUG_INFO,SECTION_INFO, "Do an inventory \n"); Inventory(changer->labelfile, drive_num, changer->eject, 0, 0, clean_slot); } else { DebugPrint(DEBUG_INFO,SECTION_INFO, "Set all entrys in DB to invalid\n"); memset(pbarcoderes, 0 , SIZEOF(MBC_T)); pbarcoderes->action = RESET_VALID; MapBarCode(changer->labelfile,pbarcoderes); } } DebugPrint(DEBUG_INFO,SECTION_INFO,"Exit status -> %d\n", endstatus); dbclose(); return endstatus; } /* * Local variables: * indent-tabs-mode: nil * tab-width: 4 * End: */