/* block.c : a multi-volume archive utility, v 1.0
 * 
 * Written for Linux 0.96a by J.Leppajarvi / jml@stekt.oulu.fi
 *
 * Public domain. No rights reserved.
 *
 * This program has no warranty of any kind.
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

/* For now, the io buffer size is static, 2 k. This suggests 
 * a performance hit since the optimum would be the size of 
 * a floppy track. In practice this seems to have no actual
 * effect on the performance, probably due to disk buffering.
 * 2 k is the greatest 'nice and round' number, since the 
 * maximum block that can be read at a time from a Linux
 * pipe is 4096-1 bytes.
 */

#define IOBUFSIZE 2048

/* cpvol() copies one volume worth of data
 *
 * from, to : input / output file descriptors
 * nbytes   : volume size
 *
 * returns  : -1 if end of input file
 *             0 if data remains to be read
 *            >0 io error
 */

int cpvol(from,to,nbytes)
int from,to,nbytes;
{
 int rdbytes;
 char buf[IOBUFSIZE];

 while (nbytes > 0)
 {
  rdbytes = read(from,buf,sizeof(buf) < nbytes ? sizeof(buf) : nbytes);
  if (rdbytes <= 0)
   return(rdbytes == 0 ? -1 : 1);
  if (write(to,buf,rdbytes) < rdbytes)
   return(2);
  nbytes -= rdbytes;
 }
 return(0);
}

/* ioloop() copies data using cpvol(). sync() is called
 * to flush each output volume.
 *
 * mode : 0 : to is associated with the block device
 *        1 : from is associated with the block device
 *
 * from, to : input / output file descriptors
 * nbytes   : volume size
 *
 * returns  :  0 : ok
 *            !0 : io error
 */

int ioloop(mode,from,to,nbytes)
int mode,from,to,nbytes;
{
 int i, ret;

 for(i = 1, ret = 0; !ret ;i++)
 {
  if (i > 1)
  {
   fprintf(stderr,"block: Insert volume %.3d and press Enter",i);
   getchar();
  }

  if (lseek(mode ? from : to,0,SEEK_SET) > 0)
   ret = 3;
  else
   ret = cpvol(from,to,nbytes);
  
  if (!mode)
   sync();
 }
 return(ret == -1 ? 0 : ret);
}

/* volsize() returns the volume size of a block device file.
 * This is used to 'autoconfigure' the program.
 *
 * Currently 1.2 M and 1.44 M floppies are supported with Linux
 *
 * returns : the volume size of a block device associated with fd
 *           -1 : fd not associated with a block device
 *            0 : block device with unknown size
 *           
 */

int volsize(fd)
int fd;
{
 struct stat sstat;

 fstat(fd,&sstat);

 if (S_ISBLK(sstat.st_mode))
 {
  switch(sstat.st_rdev)
  {
   case 0x0208:
   case 0x0209:
    return(1228800); /* 5.25" 1.2 M */
   case 0x021c:
   case 0x021d:
    return(1474560); /* 3.5" 1.44 M */
  }
  return(0);
 }
 return(-1);
}

void usage()
{
 fprintf(stderr,"Usage: block [-s volume size in kb] <in >out\n" \
		"Either in or out must be a block device.\n");
 exit(1);
}

/* sigpipe() handles clean exit on SIGPIPE. 
 * This is in fact the standard termination method
 * when an archive is being read and piped further
 * to tar.
 */

void sigpipe()
{
 exit(0);
}

main(argc,argv)
int argc;
char **argv;
{
 int from,mode = -1,nbytes = -1;
 int i,j;

 /* replace stdin for keyboard input */

 from = dup(0);
 i = open("/dev/tty",O_RDONLY);
 dup2(i,0);
 close(i);

 signal(SIGPIPE,sigpipe);

 /*
  * get info - this is used to find out which file
  * is associated with the block device and for
  * 'autoconfiguration' of the volume size
  */

 i = volsize(from);
 j = volsize(1);

 if (argc == 3 && argv[1][0] == '-' && argv[1][1] == 's')
 {
  /* explicit volume size specified */

  nbytes = atoi(argv[2]) * 1024; 

  if (i >= 0 && j < 0)
   mode = 1;
  else if (i < 0 && j >= 0)
   mode = 0;
 }
 else if (argc == 1)
 {
  /* 'autoconfigure' */

  if (i > 0 && j < 0)
  {
   mode = 1;
   nbytes = i;
  }
  else if (i < 0 && j > 0)
  {
   mode = 0;
   nbytes = j;
  }
 }

 if (mode < 0 || nbytes <= 0)
  usage();

 if (!(i = ioloop(mode,from,1,nbytes)))
  exit(0); /* success */

 fprintf(stderr,"block: I/O error (%d)\n",i);
 exit(1);  /* failed */
}
