[alt.sources] bundle: a new working version

davidsen@crdos1.crd.ge.COM (Wm E Davidsen Jr) (01/10/90)

  Thanks to suggestions from several people I have added some new
features to unbundle. It now splits into two processes and uses pipes to
send data, giving double buffering. I also added an (optional) argument
for the size of the input volume, for one person who has a driver which
messes up on end of tape, and another who only wants to use the outer
tracks of a floppy for critical backup.

  The prompts are now consistent, and the documentation is expanded and
corrected. I think that's about all the enhancement this program can
stand, but if someone has a thought for something *useful* I'll consider
it. This will be posted to sources.misc in a few weeks unless someone
finds problems.

:
#!/bin/sh
# shar+ created from directory /u/local/src/bundle
# 10:44 on Wed Jan 10, 1990 by local
echo 'x - whats.new (text)'
sed << 'E!O!F' 's/^X//' > whats.new
X                 bundle change history 1.2 revised 1/10/90
X
X01-10-90
X
X  Added input volume size limit to unbundle. This allows reading a fixed
Xquantity of data from each volume. Ubdated the documentation to match
Xall this.
X
X01-06-90
X
X  Added fork and pipe operations to unbundle. Speedup is very slight on
Xthe test systems, but may be larger on selected hardware.
X
X01-02-90
X
X  Added fork and pipe operations to bundle to make it faster.
E!O!F
newsize=`wc -c < whats.new`
if [ $newsize -ne 437 ]
then echo "File whats.new was $newsize bytes, 437 expected"
fi
echo 'x - bundle.1 (text)'
sed << 'E!O!F' 's/^X//' > bundle.1
X'\"  Copyright 1986,87,90 by Bill Davidsen. This code may be used for
X'\"  personal or commercial purposes. It may be freely distributed
X'\"  in unmodified source form providing this notice is kept intact.
X.TH bundle 1 "1990 release"
X'\" bundle doc v1.5 (1/10/90)
X.SH NAME
X  bundle \- buffer and copy \fIstdin\fR to a physical device
X  unbundle \- buffer and copy a physical device to \fIstdout\fR
X.SH SYNOPSIS
X\fBbundle\fR special dev_size buf_size
X.br
X\fBunbundle\fR special buf_size
X.br
X\fBunbundle\fP special dev_size buf_size
X.SH DESCRIPTION
XThese programs allow a physical device to be the head or end of a pipe,
Xallows software which does not know about volumes to operate on
Xmulti-volume sets, and improves the performance of programs such as
X\fItar\fR and \fIcpio\fR.
X.P
X\fBSpecial\fR is the name of a raw device, such as /dev/rmt0.
X\fBdev_size\fR is the size of a volume the device, given in bytes or
Xkilobytes. When \fBdev_size\fR bytes have been written to the output
Xdevice, the operator will be prompted for a media change. The dev_size
Xis optional for unbundle, and is only used when a fixed quantity of data
Xis written on a volume. This is sometimes done when (a) tape drivers
Xdon't handle end of tape correctly, or (b) for best reliability on
Xfloppy disk, using only the outer tracks.
X.P
X\fBBuf_size\fR is the size of the buffer for the write or read. For
Xtape this should be the desired tape block size. For disk this should
Xthe size of a cylinder. For disk output writing a cylinder at a time
Xrather than odd sectors can reduce real time by a factor of three.
X.SH EXAMPLES
X  # \fIwriting to 395k floppy volumes\fP
X  ls *.c | cpio -o | bundle /dev/rfp021 395k 5k
X  unbundle /dev/rfp021 5k | cpio -idm
X.sp
X  # \fIwriting 4MB tape volumes\fP
X  tar cf - /usr/local | bundle /dev/rmt0 4000k 8k
X  unbundle /dev/rmt0 8k | tar cf -
X.sp
X  # \fIusing only part of a floppy disk\fP
X  find . -depth -print | bundle /dev/rfd096 800k 15k
X  unbundle /dev/rfd096 800k 15k | cpio -icdm
X.SH WARNINGS
XThe volume promts are written to the controlling terminal. If other
Xprograms running, such as \fBtar\fP or \fBcpio\fP produce output the
Xterminal messages are mingles. Use of the \fBv\fP option with these
Xprograms is often very confusing. \fIunbundle\fR normally reads to EOF
Xon the input device. If the device does not handle EOF well there may
Xbe errors. If the \fBdev_size\fR is not a multiple of the
X\fBbuf_size\fR, bizarre errors may occur.
X.SH FILES
X/dev/\fBspecial\fR
X.SH SEE ALSO
Xtar, cpio.
X.SH AUTHOR
XBill Davidsen (davidsen@crdos1.crd.ge.com)
X.SH COPYRIGHT
XCopyright 1986,87,90 by Bill Davidsen, all rights reserved. This program
Xmay be distributed in any manner providing that further distribution is
Xnot restricted and the package is kept intact. Modified versions must
Xinclude the original source and documentation.
X'\" For more details, see man(7), as well as man(1), manroff(1), and mmt(1)
E!O!F
newsize=`wc -c < bundle.1`
if [ $newsize -ne 2906 ]
then echo "File bundle.1 was $newsize bytes, 2906 expected"
fi
echo 'x - bundle.c (text)'
sed << 'E!O!F' 's/^X//' > bundle.c
X/*****************************************************************
X |  Program name:  bundle.c
X |----------------------------------------------------------------
X |  accept standard input and write to a device named on the command
X |  line. The total bytes on the device and the buffer size are
X |  given on the command line. When the device is full, prompt
X |  for a new medium in the device and continue.
X |
X |  Command form:
X |   bundle device Dsize Bsize
X |----------------------------------------------------------------
X |  Author: Bill Davidsen, 8/16/86
X |  Version 1.8
X |  Last modified: 1/10/90
X |----------------------------------------------------------------
X |  Copyright 1986,87 by Bill Davidsen. This code may be used for
X |  personal or commercial purposes. It may be freely distributed
X |  in source form providing this notice is kept intact.
X *****************************************************************/
X
X#include <stdio.h>
X
X#ifdef DEBUG
X#define debug(f,v) fprintf(stderr, f, v)
X#else
X#define debug(f,v)
X#endif
X
Xstatic char *SCCS = "@(#) bundle 1.8";
X
Xmain (argc, argv)
X    int  argc;
X    char *argv[];
X{
X    char *buffer,		/* i/o buffer */
X         *malloc ();
X    register  long left;	/* room left in the buffer */
X    long  bufsize, devsize;	/* buffer and device size */
X    long  devleft = 0;		/* room left on device */
X    int  istat,			/* stdin read status */
X         ostat,			/* device write status */
X         dev,			/* device name from open */
X         MediaNum = 1;		/* sequential media # */
X    FILE *tty, *fopen ();
X    long getsize ();		/* get size from argument */
X    int pipex[2];               /* pipe for double buffering */
X    int wklen;			/* working pie read length */
X
X /* validate arguments */
X    if (argc != 4)
X    { /* invalid */
X	printf ("Format:\n bundle device DevSize BufSize\n");
X	printf ("Where size may be bytes (as 5120) or k (as 5k)\n");
X	exit (1);
X    }
X
X    devsize = getsize (argv[2]);
X    debug ("Device size %ld", devsize);
X    bufsize = getsize (argv[3]);
X    debug (", buffer size %ld\n", bufsize);
X    if (devsize < 1 || devsize < bufsize)
X    { /* invalid specification of size */
X	printf ("Invalid buffer and/or device size\n");
X	exit (1);
X    }
X
X /* open the terminal for prompt */
X    tty = fopen ("/dev/tty", "r");
X    if (tty == NULL)
X    { /* mystery failure */
X	printf ("Unable to open /dev/tty for prompt\n");
X	exit (1);
X    }
X
X /* open the buffer */
X    buffer = malloc (bufsize);
X    debug ("Buffer allocated\n", 0);
X    if (buffer == NULL)
X    { /* can't mallocate */
X	printf ("Can't allocate space for the buffer\n");
X	exit (1);
X    }
X
X /* see if you can open the device */
X    printf ("Mount volume #1 on %s and press RETURN: ", argv[1]);
X    while (getc (tty) != '\n');
X    dev = open (argv[1], 1);
X    if (dev < 0)
X    { /* invalid name */
X	printf ("Can't access device %s\n", argv[1]);
X	exit (1);
X    }
X    devleft = devsize; /* set media ready */
X    debug ("Enter copy loop\n\n", 0);
X
X    /* open the pipe */
X    if (pipe(pipex)) {
X        fprintf(stderr, "Can't open pipe for double buffering\n");
X        exit(1);
X    }
X
X    if (fork() == 0) {
X        /* child - copy stdin to pipe */
X        for (wklen = 0;
X	    (istat = read(0, buffer+wklen, (int)(bufsize - wklen))) > 0 
X	        || wklen;
X	) {
X	    if (istat > 0) wklen += istat;
X            debug("Child read %5d bytes\n", istat);
X	    debug("  buffer has %d bytes\n", wklen);
X
X            /* output the size read, then the data */
X            if (istat <= 0 || wklen == bufsize) {
X	        write(pipex[1], (char *)&wklen, sizeof(int));
X                if (write(pipex[1], buffer, wklen) != wklen) {
X                    fprintf(stderr, "Child can't write pipe!\n");
X                    exit(1);
X                }
X                else debug("Child wrote %d bytes\n", wklen);
X	        wklen = 0;
X            }
X        }
X
X        /* output a size of zero */
X	istat = 0;
X	write(pipex[1], (char *)&istat, sizeof(int));
X    }
X    else {
X        /* main copy loop */
X        while (read(pipex[0], (char *)&istat, sizeof(int)), istat) {
X            /* show the size factor and read those bytes */
X    	    debug ("Input read size %d\n", istat);
X	    for (wklen = 0; wklen < istat; ) {
X	    	wklen += read(pipex[0], buffer+wklen, istat-wklen);
X	    }
X
X    	    if (devleft < istat)
X	    { /* change media */
X	        close (dev);
X	        printf ("\007Mount volume #%d on %s and press RETURN: ",
X	            ++MediaNum, argv[1]);
X	        while (getc (tty) != '\n');
X	        dev = open (argv[1], 1);
X	        devleft = devsize;
X	    }
X
X        /* write the buffer */
X	    ostat = write (dev, buffer, bufsize);
X	    debug ("Output write size %d\n", ostat);
X
X	    if (bufsize != ostat)
X	    { /* error on write */
X	        printf ("Error on device write!\n");
X	        exit (1);
X	    }
X	    devleft -= ostat;
X        }
X    }
X
X    exit(0);
X}
X
X/*****************************************************************
X |  Procedure:  getsize
X |----------------------------------------------------------------
X |  Convert the size string to bytes. If the last character is
X |  'k', multiply by 1024
X ****************************************************************/
X
Xlong
Xgetsize (string)
X    char *string;
X{
X    register long  len = strlen (string),
X                   val = atol (string);
X
X    if (string[len - 1] == 'k')
X	val *= 1024;
X
X    return val;
X}
E!O!F
newsize=`wc -c < bundle.c`
if [ $newsize -ne 5387 ]
then echo "File bundle.c was $newsize bytes, 5387 expected"
fi
echo 'x - unbundle.c (text)'
sed << 'E!O!F' 's/^X//' > unbundle.c
X/*****************************************************************
X |	Program: unbundle
X |	  accept input from a device and write to stdout
X |----------------------------------------------------------------
X |	Arguments:
X |	 1) device name
X |	 2) block size in bytes or k
X |	Version: 1.9
X |	Last modified: 1/10/90
X |----------------------------------------------------------------
X |	Copyright 1986,87 by Bill Davidsen. This code may be used for
X |	personal or commercial purposes. It may be freely distributed
X |	in source form providing this notice is kept intact.
X ****************************************************************/
X
X#include <stdio.h>
X#include <signal.h>
X
X/* define signal for BSD or USG */
X#ifndef	SIGCHLD
X#ifdef	SIGCLD
X#define SIGCHLD	SIGCLD
X#endif
X#endif
X
X#ifdef DEBUG
X#define debug(f,v) fprintf(stderr, f, v)
X#else
X#define debug(f,v)
X#endif
X
Xextern wrapup();
X
Xmain (argc, argv)
X	int  argc;
X	char *argv[];
X{
X	char *buf, *malloc ();
X	int  bufsize, dn, mnum = 1, ch, readsize;
X	int pid;					/* child process id */
X	long vol_limit = 0;			/* max volume size */
X	long vol_size;				/* read on this volume */
X	static char *SCCSid = "@(#)unbundle 1.9";
X	long getsize ();			/* size of buffer */
X	int pipes[2];				/* hold read and write pipes */
X
X	if (argc < 3 || argc > 4)
X	{ /* bad calling sequence */
X		fprintf (stderr, "%s: wrong # of arguments\n", argv[0]);
X		fprintf (stderr, "Calling seq:\n  %s device [volsize] bufsize\n", 
X				argv[0]);
X		exit (1);
X	}
X
X /* try to open the device */
X	dn = open (argv[1], 0);
X	if (dn < 0)
X	{ /* open failed */
X		fprintf (stderr, "%s: can't open device %s\n", argv[0], argv[1]);
X		exit (1);
X	}
X	close (dn); /* so I can reopen */
X
X	/* open the pipe */
X	if (pipe(pipes)) {
X		fprintf(stderr, "Can't create pipes (out of inodes?)\n");
X		exit(1);
X	}
X
X	/* allocate the buffer */
X	if (argc == 4) {
X		/* have volume and buffer size */
X		vol_limit = getsize(argv[2]);
X		bufsize = getsize(argv[3]);
X	}
X	else {
X		/* just the buffer size */
X		bufsize = getsize (argv[2]);
X	}
X	debug("Volsize: %ld, ", vol_limit);
X	debug ("Bufsize: %d\n", bufsize);
X
X	buf = malloc (bufsize);
X	if (buf == NULL)
X	{ /* can't allocate the buffer */
X		fprintf (stderr, "Can't allocate %d byte buffer\n", bufsize);
X		exit (1);
X	}
X
X	/* here I split the process */
X	pid = fork();
X	if (pid) {
X		/* setup for signal when child done */
X		signal(SIGCHLD, wrapup);
X	}
X
X	for (;;)
X	{ /* get a fresh mount and read it */
X		if (pid) {
X			/* parent will read the input device */
X			fprintf (stderr, "Mount input volume #%d on %s and press return: ",
X					mnum++, argv[1]);
X			read (0, &ch, 1);
X			dn = open (argv[1], 0);
X
X			if (vol_limit) {
X				/* must read a limited amount from each volume */
X				vol_size = 0;
X				while (vol_size < vol_limit) {
X					/* calculate max read size */
X					readsize = vol_limit - vol_size;
X					if (readsize > bufsize) readsize = bufsize;
X
X					/* now read the data and copy */
X					readsize = read (dn, buf, bufsize);
X					if (readsize < 1) break;
X					write(pipes[1], buf, readsize);
X					vol_size += readsize;
X				}
X			}
X			else {
X				/* just copy the data */
X				while (readsize = read (dn, buf, bufsize)) {
X					write (pipes[1], buf, readsize);
X				}
X			}
X			close (dn);
X		}
X		else {
X			/* child will do pipe => stdout */
X			while ((readsize = read(pipes[0], buf, bufsize))) {
X				if (write(1, buf, readsize) < 1) break;
X			}
X		}
X	}
X
X	/* return good status */
X	exit(0);
X}
X
X/*
X |	Procedure:	getsize
X |-
X |	Convert the size string to bytes. If the last character is
X |	'k', multiply by 1024
X */
X
Xlong
Xgetsize (string)
X	char *string;
X{
X	register int len = strlen (string);
X	register long val = atol (string);
X
X	if (string[len - 1] == 'k')
X		val *= 1024;
X
X	return val;
X}
X
X/* wrapup parent on signal */
X
Xwrapup() {
X	exit(0);
X}
E!O!F
newsize=`wc -c < unbundle.c`
if [ $newsize -ne 3767 ]
then echo "File unbundle.c was $newsize bytes, 3767 expected"
fi
echo 'x - Makefile (text)'
sed << 'E!O!F' 's/^X//' > Makefile
X# makefile for bundle items v1.11 1/10/90
X
X# ================ this you might wnat to change
X
X# for compressed backup with date preservation uncomment one of these
XBACKUP		= zoo
X#BACKUP		= tar
X
X# ================ this is all you want to change!
X
X# distribution and source files
XDIST		= whats.new bundle.1 bundle.c unbundle.c Makefile
XSCCS		= s.bundle.c s.unbundle.c s.Makefile s.bundle.1 \
X		s.whats.new
XOTHER		= buntest.c unbuntest.c
X
XRESULTS		= bundle unbundle
XTESTPROG	= buntest unbuntest
X
X# if a library is needed for nap()
XNAPLIB		= -lx
X
X# which shar to use
XSHAR	= shar
X
X# makerule for SCCS which doesn't leave writable files on error
X.c~.c:
X	get $?
X
X# start making here
X
Xall		: $(RESULTS) $(TESTPROG)
X
Xtest		: all buntest
X
Xbundle		: bundle.o
X	$(CC) -o bundle bundle.o
X
Xunbundle	: unbundle.o
X	$(CC) -o unbundle unbundle.o
X
Xbuntest		: buntest.o
X	$(CC) -o buntest buntest.o $(NAPLIB)
X
Xunbuntest	: unbuntest.o
X	$(CC) -o unbuntest unbuntest.o $(NAPLIB)
X
X# things with now reasonable makerules by default
Xbundle.1	:
X	get s.bundle.1
X
Xwhats.new	:
X	get s.whats.new
X
X# shar for distribution
Xshar		: bundle.shar
Xbundle.shar	: $(DIST)
X	shar $(DIST) > bundle.shar
X
X# zoo backup - what the author uses
Xzoo		: check1 $(SCCS) $(OTHER)
X	zoo aunPP bundle.zoo $(SCCS) $(OTHER)
X
X# check for any files not in SCCS
Xcheck1		:
X	ls p.* && echo "Modified versions!" && exit 1 || exit 0
X
X# if you like backup via compressed tar files
Xtar		: check1 $(SCCS) $(OTHER)
X	tar cvf - $(SCCS) $(OTHER) | compress -v > bundle.tar.Z
X
X# cleanup all the files other than the shar, zoo, and executables
Xclean		: $(BACKUP) shar
X	@echo "Removing working files";\
X	rm -f $(DIST) $(SCCS) $(OTHER) bundle.bak *.o buntest unbuntest
E!O!F
newsize=`wc -c < Makefile`
if [ $newsize -ne 1690 ]
then echo "File Makefile was $newsize bytes, 1690 expected"
fi
exit 0

-- 
bill davidsen	(davidsen@crdos1.crd.GE.COM -or- uunet!crdgw1!crdos1!davidsen)
"The world is filled with fools. They blindly follow their so-called
'reason' in the face of the church and common sense. Any fool can see
that the world is flat!" - anon