/* vol - break stdin into volumes Author: Andy Tanenbaum */ /* This program reads standard input and writes it onto diskettes, pausing * at the start of each one. It's main use is for saving files that are * larger than a single diskette. Vol just writes its standard input onto * a diskette, and prompts for a new one when it is full. This mechanism * is transparent to the process producing vol's standard input. For example, * tar cf - . | vol -w 360 /dev/fd0 * puts the tar output as as many diskettes as needed. To read them back in, * use * vol -r 360 /dev/fd0 | tar xf - * * Changed 17 Nov 1993 by Kees J. Bot to handle buffering to slow devices. * Changed 27 Jul 1994 by Kees J. Bot to auto discover data direction + -rw. * Changed 19 Sep 1995 by Kees J. Bot to do better buffering to tapes. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Preferred block size to variable block length tapes, block devices or files. */ #define VAR_BLKSIZ 8192 /* Required block size multiple of fixed block size tapes (usually updated by * 'mt status' data) and character devices. */ #define FIX_BLKSIZ 512 /* Maximum multiple block size. */ #if __minix_vmd #define MULT_MAX 1048576 #else #define MULT_MAX ((ssize_t) (SSIZE_MAX < 65536L ? SSIZE_MAX : 65536L)) #endif char *buffer = NULL; size_t block_size = 0, mult_max = 0; size_t buffer_size; long volume_size; char *str_vol_size; int rflag = 0, wflag = 0, oneflag = 0, variable = 0; _PROTOTYPE(int main, (int argc, char **argv)); _PROTOTYPE(void usage, (void)); _PROTOTYPE(long str2size, (char *name, char *str, long min, long max, int assume_kb)); _PROTOTYPE(void tape_inquire, (char *name, int fd)); _PROTOTYPE(void allocate_buffer, (void)); _PROTOTYPE(void diskio, (int fd1, int fd2, char *file1, char *file2)); int main(argc, argv) int argc; char *argv[]; { int volume = 1, fd, tty, i, init, autovolsize; char *p, *name; struct stat stb; struct partition part; char key; /* Fetch and verify the arguments. */ i = 1; while (i < argc && argv[i][0] == '-') { p = argv[i++] + 1; if (p[0] == '-' && p[1] == 0) { /* -- */ i++; break; } while (*p != '\0') { switch (*p++) { case 'r': case 'u': rflag = 1; break; case 'w': wflag = 1; break; case '1': oneflag = 1; break; case 'b': if (*p == 0) { if (i == argc) usage(); p = argv[i++]; } block_size = str2size("block", p, 1L, (long) SSIZE_MAX, 0); p= ""; break; case 'm': if (*p == 0) { if (i == argc) usage(); p = argv[i++]; } mult_max = str2size("maximum", p, 1L, (long) SSIZE_MAX, 0); p= ""; break; default: usage(); } } } if (i < argc - 1) { str_vol_size = argv[i++]; volume_size = str2size("volume", str_vol_size, 1L, LONG_MAX, 1); autovolsize = 0; } else { volume_size = 0; /* unlimited (long tape) or use DIOCGETP */ autovolsize = 1; } if (i >= argc) usage(); name = argv[i]; if (!rflag && !wflag) { /* Auto direction. If there is a terminal at one side then data is * to go out at the other side. */ if (isatty(0)) rflag = 1; if (isatty(1)) wflag = 1; } if (rflag == wflag) { fprintf(stderr, "vol: should %s be read or written?\n", name); usage(); } if (stat(name, &stb) < 0) { fprintf(stderr, "vol: %s: %s\n", name, strerror(errno)); exit(1); } if (!S_ISBLK(stb.st_mode) && !S_ISCHR(stb.st_mode)) { fprintf(stderr, "vol: %s is not a device\n", name); exit(1); } variable = !S_ISCHR(stb.st_mode); if (!oneflag) { tty = open("/dev/tty", O_RDONLY); if (tty < 0) { fprintf(stderr, "vol: cannot open /dev/tty\n"); exit(1); } } /* Buffer initializations are yet to be done. */ init = 0; while (1) { sleep(1); if (oneflag) { if (volume != 1) { if (rflag) exit(0); fprintf(stderr, "vol: can't continue, volume is full\n"); exit(1); } } else { fprintf(stderr, "\007Please insert %sput volume %d and hit return\n", rflag ? "in" : "out", volume); while (read(tty, &key, sizeof(key)) == 1 && key != '\n') {} } /* Open the special file. */ fd = open(name, rflag ? O_RDONLY : O_WRONLY); if (fd < 0) { fprintf(stderr, "vol: %s: %s\n", name, strerror(errno)); exit(1); } if (!init) { /* Ask for the tape block size and allocate a buffer. */ if (S_ISCHR(stb.st_mode)) tape_inquire(name, fd); allocate_buffer(); init = 1; } if (autovolsize) { /* Ask the driver how big the volume is. */ if (ioctl(fd, DIOCGETP, &part) < 0) { autovolsize = 0; } else { volume_size = cv64ul(part.size); } } /* Read or write the requisite number of blocks. */ if (rflag) { diskio(fd, 1, name, "stdout"); /* vol -r | tar xf - */ } else { diskio(0, fd, "stdin", name); /* tar cf - | vol -w */ } close(fd); volume++; } } void usage() { fprintf(stderr, "Usage: vol [-rw1] [-b blocksize] [-m max] [size] block-special\n"); exit(1); } long str2size(name, str, min, max, assume_kb) char *name; char *str; long min, max; int assume_kb; { /* Convert a string to a size. The number may be followed by 'm', 'k', 'b' * or 'w' to multiply the size as shown below. If 'assume_kb' is set then * kilobytes is the default. */ long size, factor; char *ptr; int bad; errno = 0; size = strtol(str, &ptr, 10); bad = (errno != 0 || ptr == str || size < min || size > max); if (*ptr == 0 && assume_kb) ptr = "k"; while (!bad && *ptr != 0) { switch (*ptr++) { case 'm': case 'M': factor = 1024*1024L; break; case 'k': case 'K': factor = 1024; break; case 'b': case 'B': factor = 512; break; case 'w': case 'W': factor = 2; break; default: factor = 1; bad = 1; } if (size <= max / factor) size *= factor; else bad = 1; } if (bad) { fprintf(stderr, "vol: bad %s size '%s'\n", name, str); exit(1); } return size; } void tape_inquire(name, fd) char *name; int fd; { /* If the device happens to be a tape, then what is its block size? */ struct mtget mtget; if (ioctl(fd, MTIOCGET, &mtget) < 0) { if (errno != ENOTTY) { fprintf(stderr, "vol: %s: %s\n", name, strerror(errno)); exit(1); } } else { if (mtget.mt_blksize > SSIZE_MAX) { fprintf(stderr, "vol: %s: tape block size (%lu) is too large to handle\n", name, (unsigned long) mtget.mt_blksize); exit(1); } if (mtget.mt_blksize == 0) { variable = 1; } else { /* fixed */ block_size = mtget.mt_blksize; } } } void allocate_buffer() { /* Set block size and maximum multiple. */ if (block_size == 0) block_size = variable ? 1 : FIX_BLKSIZ; if (mult_max == 0) mult_max = variable ? VAR_BLKSIZ : MULT_MAX; /* Stretch the buffer size to the max. */ buffer_size = mult_max / block_size * block_size; if (buffer_size == 0) buffer_size = block_size; if (volume_size % block_size != 0) { fprintf(stderr, "vol: volume size (%s) is not a multiple of the block size (%lu)\n", str_vol_size, (unsigned long) block_size); exit(1); } buffer = (char *) malloc(buffer_size); if (buffer == NULL) { fprintf(stderr, "vol: cannot allocate a %luk buffer\n", (unsigned long) buffer_size / 1024); exit(1); } } void diskio(fd1, fd2, file1, file2) int fd1, fd2; char *file1, *file2; { /* Read 'volume_size' bytes from 'fd1' and write them on 'fd2'. Watch out for * the fact that reads on pipes can return less than the desired data. */ ssize_t n, in_needed, in_count, out_count; long needed = volume_size; int eof = 0; for (;;) { if (volume_size == 0) needed = buffer_size; if (needed == 0) break; in_count = 0; in_needed = needed > buffer_size ? buffer_size : needed; while (in_count < in_needed) { n = in_needed - in_count; n = eof ? 0 : read(fd1, buffer + in_count, n); if (n == 0) { eof = 1; if ((n = in_count % block_size) > 0) { n = block_size - n; memset(buffer + in_count, '\0', n); if ((in_count += n) > in_needed) in_count = in_needed; } break; } if (n < 0) { fprintf(stderr, "vol: %s: %s\n", file1, strerror(errno)); exit(1); } in_count += n; } if (in_count == 0) exit(0); /* EOF */ out_count = 0; while (out_count < in_count) { n = in_count - out_count; n = write(fd2, buffer + out_count, n); if (n < 0) { fprintf(stderr, "vol: %s: %s\n", file2, strerror(errno)); exit(1); } out_count += n; } needed -= in_count; } }