/* Copyright 2001 Enhanced Software Technologies Inc.
 *   Released under terms of the GNU General Public License as
 * required by the license on 'mtxl.c'.
 * $Date: 2001/12/14 16:06:28 $
 * $Revision: 1.2 $
 */

/* This is a generic SCSI tape control program. It operates by
 * directly sending commands to the tape drive. If you are going
 * through your operating system's SCSI tape driver, do *NOT* use 
 * this program! If, on the other hand, you are using raw READ and WRITE
 * commands through your operating system's generic SCSI interface (or
 * through our built-in 'read' and 'write'), this is the place for you. 
 */

/*#define DEBUG_PARTITION */
/*#define DEBUG 1 */

/* 
   Commands:
         setblk <n> -- set the block size to <n>
         fsf <n> -- go forward by <n> filemarks
         bsf <n> -- go backward by <n> filemarks
         eod  -- go to end of data
         rewind -- rewind back to start of data
         eject  -- rewind, then eject the tape. 
	 erase  -- (short) erase the tape (we have no long erase)
         mark <n> -- write <n> filemarks.
         seek <n> -- seek to position <n>.

	 write <blksize> <-- write blocks from stdin to the tape 
         read  [<blksize>] [<#blocks/#bytes>] -- read blocks from tape, write to stdout. 

   See the 'tapeinfo' program for status info about the tape drive.

 */

#include <stdio.h>
#include <string.h>

#include "mtx.h"
#include "mtxl.h"

#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mtio.h> /* will try issuing some ioctls for Solaris, sigh. */

void Usage(void) {
  FatalError("Usage: scsitape -f <generic-device> <command> where <command> is:\n setblk <n> | fsf <n> | bsf <n> | eod | rewind | eject | mark <n> |\n  seek <n> | read [<blksize> [<numblocks]] | write [<blocksize>] \n");
}

#define arg1 (arg[0])  /* for backward compatibility, sigh */
static int arg[4];  /* the argument for the command, sigh. */

/* the device handle we're operating upon, sigh. */
static unsigned char *device;  /* the text of the device thingy. */
static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) 0;

static int S_write(void);
static int S_vwrite(void);
static int S_real_write(unsigned char opcode);

struct command_table_struct {
  int num_args;
  char *name;
  int (*command)(void);
} command_table[] = {
  { 2, "writenulls",S_write },
  { 2, "vwritenulls",S_vwrite },
  { 0, NULL, NULL } /* terminate list */
};

char *argv0;

/* A table for printing out the peripheral device type as ASCII. */ 
static char *PeripheralDeviceType[32] = {
  "Disk Drive",
  "Tape Drive",
  "Printer",
  "Processor",
  "Write-once",
  "CD-ROM",
  "Scanner",
  "Optical",
  "Medium Changer",
  "Communications",
  "ASC IT8",
  "ASC IT8",
  "RAID Array",
  "Enclosure Services",
  "OCR/W",
  "Bridging Expander", /* 0x10 */
  "Reserved",  /* 0x11 */
  "Reserved", /* 0x12 */
  "Reserved",  /* 0x13 */
  "Reserved",  /* 0x14 */
  "Reserved",  /* 0x15 */
  "Reserved",  /* 0x16 */
  "Reserved",  /* 0x17 */
  "Reserved",  /* 0x18 */
  "Reserved",  /* 0x19 */
  "Reserved",  /* 0x1a */
  "Reserved",  /* 0x1b */
  "Reserved",  /* 0x1c */
  "Reserved",  /* 0x1d */
  "Reserved",  /* 0x1e */
  "Unknown"    /* 0x1f */
};



/* open_device() -- set the 'fh' variable.... */
void open_device(void) {

  if (MediumChangerFD) {
    SCSI_CloseDevice("Unknown",MediumChangerFD);  /* close it, sigh...  new device now! */
  }

  MediumChangerFD = SCSI_OpenDevice(device);

}

static int get_arg(char *arg) {
  int retval=-1;

  if (*arg < '0' || *arg > '9') {
    return -1;  /* sorry! */
  }

  retval=atoi(arg);
  return retval;
}


/* we see if we've got a file open. If not, we open one :-(. Then
 * we execute the actual command. Or not :-(. 
 */ 
int execute_command(struct command_table_struct *command) {

  /* if the device is not already open, then open it from the 
   * environment.
   */
  if (!MediumChangerFD) {
    /* try to get it from STAPE or TAPE environment variable... */
    device=getenv("STAPE");
    if (device==NULL) {
      device=getenv("TAPE");
      if (device==NULL) {
	Usage();
      }
    }
    open_device();
  }


  /* okay, now to execute the command... */
  return command->command();
}


/* parse_args():
 *   Basically, we are parsing argv/argc. We can have multiple commands
 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
 * load in another tape into drive 0, and we execute these commands one
 * at a time as we come to them. If we don't have a -f at the start, we
 * barf. If we leave out a drive #, we default to drive 0 (the first drive
 * in the cabinet). 
 */ 

int parse_args(int argc,char **argv) {
  int i,cmd_tbl_idx,retval,arg_idx;
  struct command_table_struct *command;

  i=1;
  arg_idx=0;
  while (i<argc) {
    if (strcmp(argv[i],"-f") == 0) {
      i++;
      if (i>=argc) {
	Usage();
      }
      device=argv[i++];
      open_device(); /* open the device and do a status scan on it... */
    } else {
      cmd_tbl_idx=0;
      command=&command_table[0]; /* default to the first command... */
      command=&command_table[cmd_tbl_idx];
      while (command->name) {
	if (!strcmp(command->name,argv[i])) {
	  /* we have a match... */
	  break;
	}
	/* otherwise we don't have a match... */
	cmd_tbl_idx++;
	command=&command_table[cmd_tbl_idx];
      }
      /* if it's not a command, exit.... */
      if (!command->name) {
	Usage();
      }
      i++;  /* go to the next argument, if possible... */
      /* see if we need to gather arguments, though! */
      arg1=-1; /* default it to something */
      for (arg_idx=0;arg_idx < command->num_args ; arg_idx++) {
	if (i < argc) {
	  arg[arg_idx]=get_arg(argv[i]);
	  if (arg[arg_idx] !=  -1) {
	    i++; /* increment i over the next cmd. */
	  }
	} else {
	  arg[arg_idx]=0; /* default to 0 setmarks or whatever */
	} 
      }
      retval=execute_command(command);  /* execute_command handles 'stuff' */
      exit(retval);
    }
  }
  return 0; /* should never get here */
}

#ifdef MTSRSZ
static int Solaris_setblk(int fh,int count) {
  /* we get here only if we have a MTSRSZ, which means Solaris. */
  struct mtop mt_com;  /* the struct used for the MTIOCTOP ioctl */
  int result;

  /* okay, we have fh and count.... */
  
  /* Now to try the ioctl: */
  mt_com.mt_op=MTSRSZ;
  mt_com.mt_count=count;

  /* surround the actual ioctl to enable threading, since fsf/etc. can be
   * big time consumers and we want other threads to be able to run too. 
   */
 
  result=ioctl(fh, MTIOCTOP, (char *)&mt_com);

  if (result < 0) {
    return errno;
  }

  /* okay, we did okay. Return a value of None... */
  return 0;
}
#endif  


/*************************************************************************/
/* SCSI read/write calls. These are mostly pulled out of BRU 16.1, 
 * modified to work within the mtxl.h framework rather than the
 * scsi_lowlevel.h framework. 
 *************************************************************************/ 

#define MAX_READ_SIZE 128*1024  /* max size of a variable-block read */

#define READ_OK 0
#define READ_FILEMARK 1
#define READ_EOD 2
#define READ_EOP 3
#define READ_SHORT 5
#define READ_ERROR 255

#define WRITE_OK 0
#define WRITE_ERROR 1
#define WRITE_EOM 2
#define WRITE_EOV 3


/* These are copied out of BRU 16.1, with all the boolean masks changed
 * to our bitmasks.
*/
#define S_NO_SENSE(s) ((s).SenseKey == 0x0)
#define S_RECOVERED_ERROR(s) ((s).SenseKey == 0x1)

#define S_NOT_READY(s) ((s).SenseKey == 0x2)
#define S_MEDIUM_ERROR(s) ((s).SenseKey == 0x3)
#define S_HARDWARE_ERROR(s) ((s).SenseKey == 0x4)
#define S_UNIT_ATTENTION(s) ((s).SenseKey == 0x6)
#define S_BLANK_CHECK(s) ((s).SenseKey == 0x8)
#define S_VOLUME_OVERFLOW(s) ((s).SenseKey == 0xd)

#define DEFAULT_TIMEOUT 3*60  /* 3 minutes here */

#define HIT_FILEMARK(s) (S_NO_SENSE((s)) && (s).Filemark && (s).Valid)
/* Sigh, the T-10 SSC spec says all of the following is needed to
 * detect a short read while in variable block mode. We'll see.
 */
#define SHORT_READ(s) (S_NO_SENSE((s)) && (s).ILI && (s).Valid &&  (s).AdditionalSenseCode==0  && (s).AdditionalSenseCodeQualifier==0)
		       
#define HIT_EOD(s) (S_BLANK_CHECK((s)) && (s).Valid)
#define HIT_EOP(s) (S_MEDIUM_ERROR((s)) && (s).EOM && (s).Valid)
#define HIT_EOM(s) ((s).EOM && (s).Valid)
#define BECOMING_READY(s) (S_UNIT_ATTENTION((s)) && (s).AdditionalSenseCode == 0x28 && (s).AdditionalSenseCodeQualifier == 0)

/* Low level SCSI write. Modified from BRU 16.1,  with much BRU smarts
 * taken out and with the various types changed to mtx types rather than
 * BRU types.
 */  
int SCSI_writet(DEVICE_TYPE fd, unsigned char command, char * buf, unsigned int location, 
                int blocksize,
                unsigned int *len, 
		unsigned int timeout) {
  CDB_T cmd;

  int numblocks = (*len)/blocksize;
  
  int rtnval=0;
  RequestSense_T RequestSense;

  fprintf(stderr,"Writing %d blocks at location %d\n",numblocks,location);
  
  memset(&cmd, 0, sizeof(CDB_T));
  cmd[0] = command; /* WRITE10 */
  cmd[1]=0;
  cmd[2]=(location >> 24) & 0xff;
  cmd[3]=(location >> 16) & 0xff;
  cmd[4]=(location >> 8) & 0xff;
  cmd[5]=(location) & 0xff;
  cmd[6]=0;
  cmd[7]=(numblocks >> 8) & 0xff;
  cmd[8]=(numblocks) & 0xff;
  cmd[9]=0;
  


  if (SCSI_ExecuteCommand(fd,Output,&cmd,10,buf, *len, &RequestSense)) {
      PrintRequestSense(&RequestSense);
      exit(WRITE_ERROR);
  } else {
    rtnval = *len; /* worked! */
  }
  return(rtnval);
}

static int S_write(void) {
  return S_real_write(0x2a);
}

static int S_vwrite(void) {
  return S_real_write(0x2e);
}

static int S_real_write(unsigned char opcode) {
  unsigned char *buffer; /* the buffer we're gonna read/write out of. */
  int buffersize;
  int len; /* the length of the data in the buffer */
  int blocksize=arg[0];
  int numblocks=arg[1];

  int blockstart = 1000;  /* where we're starting. */

  int result;
  int eof_input;
#define NUMBLOCKS 64
  buffersize=blocksize*NUMBLOCKS;
  len=buffersize;

  /* sigh, make it oversized just to have some */  
  buffer=malloc(buffersize+8); 

  memset(buffer,buffersize,0); 

  while (numblocks >0) {
    result=SCSI_writet(MediumChangerFD,opcode, buffer, blockstart,blocksize,&len,DEFAULT_TIMEOUT);
    if (!result) {
      return 1; /* at end of tape! */
    }
    numblocks-=NUMBLOCKS;
    blockstart+=NUMBLOCKS;
  }
  /* and done! */
  return 0;
}

/* See parse_args for the scoop. parse_args does all. */
int main(int argc, char **argv) {
  argv0=argv[0];
  parse_args(argc,argv);

  if (device) 
    SCSI_CloseDevice(device,MediumChangerFD);

  exit(0);
}
      
