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