[comp.os.minix] Major upgrade to 1.3c vol.c

nick@nswitgould.OZ (Nick Andrew) (12/14/88)

	The distributed Vol command has several flaws. These are:

- Vol does not know the correct length of its input from diskette. Thus, in
  a situation like 'vol | somecommand', Vol relies on Somecommand knowing
  when the input is complete. Tarfiles contain a byte sequence which signify
  the end of file. Compressed files do not, so 'vol | zcat' does not work.

- Vol does not detect when, on reading or writing, diskettes are mounted in
  the wrong order or the wrong diskette is mounted.

	I have modified the 1.3c source to fix the above problems. A
sharfile is included below.

Vol now writes a one-block header at the start of every output volume. This
header contains a magic number, a timestamp (unique for the set of diskettes
written by one execution of Vol), a diskette sequence number, data length,
and a last volume indicator.

This header block lets Vol detect some common errors. These are:

- inserting the wrong diskette when reading (out of order, or a diskette
  which wasn't written with vol, or a diskette which belongs to some other
  volume set)

- Overwriting a prior diskette of the current volume set. Not such a common
  error, but frustrating, as it ruins the whole set.  NOTE that a test
  against overwriting volumes of some other set may save the user from
  destroying some other vital data, but I haven't included it, because I
  prefer the ability to overwrite volume backups with later versions.

The length field in the header makes a construct such as 'vol | zcat' or
even 'vol | more' (whatever for?) workable. A future version of vol may
allow different-sized volumes within a set. Now, however, for each volume
but the last, the length field contains the size in bytes of the diskette
(360k or 1.2mb) less BLOCK_SIZE.

I have spent some time experimenting with Vol as a tool to help backup my
hard disk. Using tar | compress | vol, on a 10 Mhz AT clone and 1.2 Mb
floppies, I timed backing up of a 10 Mb hard disk partition at about 45
minutes. This is pitiful, and even slower when compress is not used.
Compress did a lot of number crunching (probably in parallel with
outstanding disk I/O requests) and reduced the amount of data to write,
so the net effect was an improvement. On the whole though, 3 hours to
back up my entire hard disk is unworkable. Find is moderately useful at
finding recently modified files, and I have had some luck with doing
incremental backups, but I have not attempted to automate the process yet.

My second observation is regarding direct disk I/O. In Minix, this goes
through the block cache, and repeated read()s of block zero are read
straight from the cache. This sort-of stuffs up the volume id checking I
have coded, and sends Vol into a loop waiting for the correct volume. Sync
doesn't help. Given that direct disk I/O comes from the cache for a reason
(ie it is correct, for I know this has been mentioned before here), there
should be a way to flush the block cache on demand. Two possibilities arise:

1) The cache could be flushed during an lseek() on the device. I think this
   is a bit of a kludge. BTW I am only talking about those blocks in the
   cache which belong to the disk device.

2) The cache could be flushed by the code which turns off the disk drive
   motor, or the code which starts up the motor. The assumption here is
   if the drive stops and starts again, another disk could be inserted. The
   buffer need not be physically flushed by the motor on/off code; a flag
   could be set which would mark all those blocks as invalid.

Regards to all, Nick.

----------------------------------------------------------------------------
echo x - vol.c
sed '/^X/s///' > vol.c << '/'
X/* vol - break stdin into volumes	Author: Andy Tanenbaum */
X/* Modified to retain file length and other info - Nick Andrew */
X
X/* This program reads standard input and writes it onto diskettes, pausing
X * at the start of each one.  It's main use is for saving files that are
X * larger than a single diskette.  Vol just writes its standard input onto
X * a diskette, and prompts for a new one when it is full.  This mechanism
X * is transparent to the process producing vol's standard input. For example,
X *	tar c - . | vol 360 /dev/fd0
X * puts the tar output as as many diskettes as needed.  To read them back in,
X * use
X *	vol -u 360 /dev/fd0 | tar x -
X *
X * Vol now uses the first block of each disk as information about the data
X *  stored on the disk. Vol will now output the exact number of bytes
X *  previously saved on the disk. There are also integrity checks.
X */
X
X#include <fcntl.h>
X#include <sys/stat.h>
X#include <minix/blocksize.h>
X#include <signal.h>
X#include <stdio.h>
X
X#define	VOLMAGIC	0176543L
X
Xextern int errno;
Xextern char *itoa();
Xextern long time();
X
Xstruct {
X	long	volmagic;
X	long	voltime;
X	int	volnumber;
X	long	vollength;		/* length of data on this volume */
X	int	vollast;		/* 1 == this is last volume */
X	char	voljunk[BLOCK_SIZE - 16];
X} vol_hdr;
X
Xstruct	stat	stb;
X
Xlong	now,filesize;
X
Xint	volume = 1, in_fd, out_fd, size, reading, tty;
X
Xchar buffer[BLOCK_SIZE];
Xchar *p, *name;
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X  int	fd;
X
X  if (sizeof vol_hdr != BLOCK_SIZE)
X	message("Debug: vol_hdr wrong size. Recompile vol.c !\n","");
X  now = time(0);
X
X  signal(SIGPIPE, SIG_IGN);
X
X  /* Fetch and verify the arguments. */
X  if (argc != 3 && argc != 4) 
X	message("Usage: vol [-u] size block-special\n", "");
X  p = argv[1];
X  reading = (*p == '-' && *(p+1) == 'u' ? 1 : 0);
X  size = atoi(argv[reading + 1]);
X  name = argv[reading + 2];
X  tty = open("/dev/tty", 0);
X
X  if (size <= 2) 
X	message("vol: bad volume size %d\n", argv[reading+1]);
X  if (stat(name, &stb) < 0) 
X	message("vol: cannot stat %s\n", name);
X  if ( (stb.st_mode & S_IFMT)  != S_IFBLK) 
X	message("vol: %s is not a block special file\n", name);
X  if (tty < 0)
X	message("vol: cannot open /dev/tty\n", "");
X
X  while (1) {
X	/* Open the special file. */
X	fd = open(name, reading ? O_RDONLY : O_RDWR );
X	if (fd < 0)
X		message("vol: cannot open %s\n", name);
X
X	in_fd = (reading ? fd : 0);
X	out_fd = (reading ? 1 : fd);
X
X	initial();	/* handle the initial block */
X
X	/* Read or write the requisite number of blocks. */
X	if (reading)
X		diskio(size, name, "stdout");	/* vol -u | tar xf -*/
X	else
X		diskio(size, "stdin", name);	/* tar cf - | vol */
X	++volume;
X	close(fd);
X	if (vol_hdr.vollast) break;	/* last volume was just processed */
X  }
X}
X
Xdiskio(size, errstr1, errstr2)
Xint size;
Xchar *errstr1, *errstr2;
X{
X/* Read 'size' blocks from in_fd and write them on out_fd.  Watch out for
X * the fact that reads on pipes can return less than the desired data.
X */
X
X  int n, m, count;
X  long needed;
X
X  needed = (long) BLOCK_SIZE * (long) (size-1);		/* maximum size */
X  if (reading && (filesize < needed))
X	needed = filesize;
X
X  while (needed > 0L) {
X	count = (needed > (long) BLOCK_SIZE ? BLOCK_SIZE : (int) needed);
X	n = read(in_fd, buffer, count);
X	if (n == 0) finish();
X	if (n < 0)
X		 message("Error encountered while reading %s\n", errstr1);
X	m = write(out_fd, buffer, n);
X	if (m < 0 && errno == SIGPIPE) exit(0);
X	if (m >= 0 && m != n) message("Output error on %s\n", errstr2);
X	if (m < 0) message("Error encountered while writing %s\n", errstr2);
X	needed -= n;
X	if (!reading) filesize += n;
X	else filesize -= n;
X  }
X}
X
Xfinish() {
X	if (!reading) {
X		lseek(out_fd,0L,0);
X		readblock(out_fd,&vol_hdr);
X		vol_hdr.vollength = filesize;
X		vol_hdr.vollast = 1;
X		lseek(out_fd,0L,0);
X		writeblock(out_fd,&vol_hdr);
X		close(out_fd);
X	}
X	exit(0);
X}
X
Xmessage(s1, s2)
Xchar *s1, *s2;
X{
X  printf(s1, s2);
X  exit(1);
X}
X
Xinitial() {
X  /* prompt for volume, and process the initial block of the volume */
X  while (1) {
X    sync();
X    fprintf(stderr,"\007Please insert volume %3d and hit return\n",
X	volume);
X    read(tty, buffer, BLOCK_SIZE);
X
X    /* read or write initial block data */
X    if (reading) {
X	lseek(in_fd,0L,0);
X	readblock(in_fd,&vol_hdr);
X	if (vol_hdr.volmagic != VOLMAGIC) {
X	    fprintf(stderr,"This volume was not written with Vol. Try again.\n");
X	    continue;
X	}
X	if (volume == 1) {
X	    now = vol_hdr.voltime;
X	} else {
X	    if (vol_hdr.voltime != now) {
X		fprintf(stderr,"This volume is not part of the set you are reading. Try again.\n");
X		continue;
X	    }
X	}
X	if (vol_hdr.volnumber != volume) {
X	    fprintf(stderr,"I need volume %d, this is volume %d\n",
X		volume,vol_hdr.volnumber);
X	    continue;
X	}
X	filesize = vol_hdr.vollength;
X	return;
X    } else /* writing */ {
X	lseek(out_fd,0L,0);
X	readblock(out_fd,&vol_hdr);
X	if (vol_hdr.volmagic == VOLMAGIC && vol_hdr.voltime == now) {
X	    fprintf(stderr,"Don't overwrite volume %d of this set. Try again.\n",
X		vol_hdr.volnumber);
X	    continue;
X	}
X	lseek(out_fd,0L,0);		/* rewind device */
X
X	vol_hdr.volmagic = VOLMAGIC;
X	vol_hdr.voltime  = now;
X	vol_hdr.volnumber = volume;
X	/* avoid rewriting block 0 if the device fills up */
X	vol_hdr.vollength = (long) (size-1) * (long) BLOCK_SIZE;
X	vol_hdr.vollast = 0;
X	filesize = 0L;
X
X	writeblock(out_fd,&vol_hdr);
X	return;
X    }
X  }
X}
X
Xreadblock(fd,buf)
Xint	fd;
Xchar	*buf;
X{
X  int n;
X
X  n = read(fd, buf, BLOCK_SIZE);
X  if (n == BLOCK_SIZE) return;
X  if (n < 0) message("Error while reading first block (errno=%d)\n",errno);
X  message("Could not read first block\n","");
X}
X
Xwriteblock(fd,buf)
Xint	fd;
Xchar	*buf;
X{
X  int n;
X
X  n = write(fd,buf,BLOCK_SIZE);
X  if (n == BLOCK_SIZE) return;
X  if (n < 0) message("Error while writing first block (errno=%d)\n",errno);
X  message("Could not write first block\n","");
X}
/
exit 0