[mod.sources] v06i004: vol: create volume headers for tar

sources-request@mirror.UUCP (06/18/86)

Submitted by: cca!harvard!clyde!infopro!bty!yost (Brian Host)
Mod.sources: Volume 6, Issue 4
Archive-name: vol

[ There was some discussion on the net a few weeks ago about the
  abilities of cpio and tar to handle multi-volume dumps.  With
  this front-end, both programs can.  Synchronicity.  --r$]

----- Cut here -------------------------------------------------
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	vol.1
#	vol.c
#	voltar
echo x - extracting README
sed 's/^X//' << 'SHAR_EOF' > README
X
XThis distribution contains the first release of the `vol' command,
Xwhich creates volume header files for multivolume `tar' archives.
XA man page is included.
X
XThe following items may be of interest for porting:
X
X	1.  It was compiled undex XENIX 3.0 originally.
X	2.  My compiler has the 8-character limit on names.
X	3.  I used upper and lowercase in names.
X	4.  It reads directories and uses stat(2) and time(2).
X
XHere's my copy of /etc/default/vol:
X# tape/disk volume sizes
XVOLSIZE=1400
XDISKSIZE=1400
XTAPESIZE=60000
X
XI'd be very interested in hearing any comments, and of course I'd
Xappreciate receiving any modifications, bug fixes, ports, etc.
X
XBrian Yost     {clyde,topaz}!infopro!bty!yost       14 June 1986
SHAR_EOF
echo x - extracting vol.1
sed 's/^X//' << 'SHAR_EOF' > vol.1
X.nh
X.TH VOL 1 
X.SH NAME
Xvol  \-  create volume header files for
X.IR tar (1)
X.SH SYNOPSIS
X.B vol
X[ -ptd ] [ -s
X.B "maxblocks"
X] pathname...
X.SH DESCRIPTION
XBecause multivolume
X.IR tar (1)
Xarchives are sequential in nature and cannot be accessed individually,
Xa number of difficulties can arise.
XThe most common problem is that one must search through ten floppies
Xin order to retrieve a file from the eleventh.
XSearching through two cartridge tapes to get at something on the third
Xis no fun, either,
Xespecially when it takes 30 minutes or more per tape.
XA much more devastating problem occurs when the third of fifteen floppy
Xdisks gets trashed or lost,
Xrendering disks 4 through 15 effectively inaccessible.
X.PP
XThe use of
X.I vol
Xeliminates these problems by keeping the amount of data written
Xto each volume within the capacity of the medium being used.
X.I Vol
Xtakes a maximum capacity value (in blocks) and a list of pathnames,
Xand creates a sequence of volume header files of the form
X.nf
X.sp 1
X             Vol_1ofN, Vol_2ofN, ..., Vol_NofN
X.sp 1
X.fi
XEach of these files contains
X1) its own name,
X2) a subset of the pathnames specified, and
X3) a dummy file `TOTAL-NNNNN-Blocks-MM/DD/YY'.
XThe total space in blocks required by this subset of files should
Xbe some value NNNNN which is less than or equal to the maximum
Xcapacity value.  Here's a sample volume header file for a
Xdiskette that has a 1400-block capacity:
X.nf
X.sp 1
X             $ cat Vol_1of3
X             ./Vol_1of3
X             /usr2/yost/c
X             /usr2/yost/news
X             /usr2/yost/calendar
X             TOTAL-1387-Blocks-06/02/86
X             $
X.sp 1
X.fi
X.PP
XThese volume header files may then be used in conjunction with the
Xcommand evaluation capability of
X.IR sh (1),
Xas in the following examples:
X.nf
X.sp 1
X             $ tar cv `cat Vol_1of3`
X             $ ls -ls `cat Vol_1of3`
X             $ rm -rf `cat Vol_1of3`
X.sp 1
X.fi
X.PP
X.I Vol
Xruns
X.IR du (1)
Xon each pathname supplied on the command line to determine how
Xmuch space it requires.
XIf any pathname is larger than the maximum capacity,
Xand it is a directory,
X.I vol
Xprocesses each of the files within that directory separately,
Xrather than the directory as a whole.
XFor example, if the directory /usr is too big to fit on a single magtape,
X.I vol
Xwill split it up into tape-sized chunks.
XIf a regular file is larger than the maximum capacity,
X.I vol
Xwill print an error message and skip the file.
X.PP
XOnce all of the pathnames on the command line have been processed
Xin this way,
X.I vol
Xbegins arranging them into volume header files.
XThe default algorithm takes great liberties in rearranging the
Xsequence of the pathnames in order to minimize the amount of
Xwasted space on each volume.
XThe -p option tells
X.I vol
Xto preserve the original sequence of pathnames,
Xregardless of space considerations.
X.PP
XThe maximum capacity value defaults to the value of `VOLSIZE' in
Xthe file /etc/default/vol,
Xor the environment variable `VOLSIZE'.
XThe -t option to use `TAPESIZE' rather than `VOLSIZE';
Xsimilarly, -d means use `TAPESIZE'.
XCommand-line options take priority over environment variables,
Xand variables take priority over the values in /etc/default/vol.
X.PP
X.I Vol
Xwill run much faster if you do some of its work for it.
XFor example, if you wish to split up /usr into magtape-sized
Xvolumes, and you know it will take more than one, use the
Xcommand
X.nf
X
X             $ vol -t /usr/*
X
Xrather than
X
X             $ vol -t /usr
X
X.fi
XThis will save
X.I vol
Xthe trouble of doing the initial
X.IR du (1)
Xon /usr, only to find out that it's too big.
X.SH DIAGNOSTICS
X.I Vol
Xexits with status 1 if any errors are encountered.
X.SH FILES
X/etc/default/vol /tmp/vol* ./Vol_*of*
X.SH "SEE ALSO"
Xtar(1)
Xdu(1)
Xsh(1)
X.SH BUGS
X.I Vol
Xneglects to consider the size of the header files in its calculations.
X.PP
XMultiple links to a file are treated as separate files.
X.SH AUTHOR
XBrian Yost (bty!yost)
SHAR_EOF
echo x - extracting vol.c
sed 's/^X//' << 'SHAR_EOF' > vol.c
X
X/*
X**  vol.c - create volume header files for `tar'
X**
X**  I hereby release this source code into the public domain.  Anyone
X**  may use, copy, or modify this source code, as long as:
X**
X**	1) it is not sold for profit;
X**	2) modifications are clearly identified with the date,
X**         author's name, and e-mail address; and
X**	3) the entire text of this comment remains intact.
X**
X**  I would also appreciate it if ports, modifications and/or flames
X**  were sent to me at the address below.
X**
X**  Brian Yost    {topaz,clyde}!infopro!bty!yost    14 June 1986
X*/
X
X#include <stdio.h>
X#include <string.h>
X#include <signal.h>
X#include <time.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X
X#define OKAY		0
X#define ERROR		1
X#define	TOLERANCE	10	/* how close to actually come to limit */
X#define MAX_HEADER	5000L	/* maximum size of header file (bytes) */
X#define UNDER_10	"./Vol_%dof%d"
X#define OVER_9		"./Vol_%02dof%02d"
X
X#define SIGNAL(S,A)	if (signal((S),SIG_IGN) != SIG_IGN) signal((S),(A))
X
Xtypedef enum {FALSE, TRUE} boolean;
X
Xchar *ProgName;
Xchar *Default = "/etc/default/vol";
Xchar *Usage = "Usage: %s [-ptd] [-s maxblocks] pathname...\n";
Xchar *Size = "VOLSIZE";
X
Xboolean Preserve = FALSE;	/* ok to rearrange file sequence */
X
XFILE *TempFile;
Xchar *TempName = "/tmp/volXXXXXX";
X
Xlong MaxBlocks = 0L;
Xlong N_Records = 0L;
X
Xextern int CreateVol();	    /* top level */
Xextern void GetSize();	    /* determines size of a pathname */
Xextern void ProcessDir();   /* run GetSize() on directory contents */
Xextern void AddList();	    /* adds to the list of processed pathnames */
Xextern char *GetDate();	    /* returns today's date in MM/DD/YY format */
Xextern int rm_temps();	    /* interrupt handler */
X
Xmain(ac, av)
Xint ac;
Xchar *av[];
X{
X	int status, option;
X	FILE *deflt;
X	extern char *optarg;
X	extern int optind;
X	extern long atol();
X	extern char *getenv(), *fgets();
X
X	ProgName = av[0];
X	while ((option = getopt(ac, av, "tds:ph")) != EOF)
X		switch (option) {
X		case 't':	/* tape */
X			Size = "TAPESIZE";
X			break;
X		case 'd':	/* disk */
X			Size = "DISKSIZE";
X			break;
X		case 's':	/* size */
X			MaxBlocks = atol(optarg);
X			break;
X		case 'p':	/* preserve file sequence */
X			Preserve = TRUE;
X			break;
X		case 'h':	/* help */
X		case '?':	/* error */
X		default:
X			fprintf(stderr, Usage, ProgName);
X			exit(ERROR);
X			break;
X		}
X	
X	if (ac - optind < 1) {	/* not enough args */
X		fprintf(stderr, Usage, ProgName);
X		exit(ERROR);
X	}
X
X	/* Look for environment variable */
X	if (!MaxBlocks) {
X		char *s;
X
X		if ((s = getenv(Size)) != NULL)
X			MaxBlocks = atol(s);
X	}
X	
X	/* Check DEFAULT file */
X	if (!MaxBlocks)
X		if ((deflt = fopen(Default, "r")) != NULL) {
X			int len = strlen(Size);
X			char buf[BUFSIZ];
X
X			while (fgets(buf, BUFSIZ, deflt))
X			  if (strncmp(buf, Size, len) == 0)
X			    if (buf[len] == '=')
X			      if (MaxBlocks = atol(buf+len+1))
X			        break;
X			fclose(deflt);
X		}
X	
X
X	if (!MaxBlocks) {	/* just give up */
X		fprintf(stderr, "%s: you must specify block size\n", ProgName);
X		fprintf(stderr, Usage, ProgName);
X		exit(ERROR);
X	} else if (MaxBlocks < 0L) {	/* no way */
X		fprintf(stderr, "%s: invalid block size %ld\n",
X			ProgName, MaxBlocks);
X		exit(ERROR);
X	}
X	printf("Volume size = %ld blocks\n", MaxBlocks);
X
X	SIGNAL(SIGHUP, rm_temps);
X	SIGNAL(SIGINT, rm_temps);
X	SIGNAL(SIGQUIT, rm_temps);
X	SIGNAL(SIGTERM, rm_temps);
X
X	mktemp(TempName);
X	if ((TempFile = fopen(TempName, "w+")) == NULL) {
X		fprintf(stderr, "%s: can't open %s\n", ProgName, TempName);
X		exit(ERROR);
X	} else {
X		setbuf(TempFile, (char*)NULL);
X		status = CreateVol(ac-optind, av+optind);
X		fclose(TempFile);
X		exit(status);
X	}
X}
X
Xint
XCreateVol(n, pathname)
Xint n;
Xchar *pathname[];
X{
X	int i, c, volume;
X	long total, grandtotal, blocks, filepos, filesize;
X	char volfile[BUFSIZ], oldfile[BUFSIZ], name[BUFSIZ], buf[BUFSIZ];
X	FILE *vol, *old;
X	extern long ftell();
X
X	for (i = 0; i < n; i++)
X		GetSize(pathname[i]);
X	
X	if (Preserve != TRUE) {
X		/*
X		** Since we only make one pass through the list, sort
X		** it by blocksize descending.  This should get us as
X		** close to MaxBlocks as possible.
X		*/
X		sprintf(buf, "sort -n -r %s -o %s", TempName, TempName);
X		if (system(buf) != 0)
X			fprintf(stderr, "%s: error sorting %s\n",
X				ProgName, TempName);
X	}
X	unlink(TempName);	/* (after last close) */
X
X	/*
X	** Move items from the list into the volume header files
X	*/
X	volume = 0;
X	grandtotal = 0L;
X	while (N_Records) {
X		volume++;
X		total = 0L;
X
X		sprintf(volfile, "%s-%d", TempName, volume);
X		if ((vol = fopen(volfile, "w")) == NULL) {
X			fprintf(stderr, "%s: fatal error -- can't open %s\n",
X				ProgName, volfile);
X			return ERROR;
X		}
X
X		rewind(TempFile);
X		filepos = ftell(TempFile);
X		filesize = 0L;
X		while (fgets(buf, BUFSIZ, TempFile)) {
X			if (sscanf(buf, "%ld %s", &blocks, name) != 2) {
X				fprintf(stderr, "%s: error reading %s\n",
X					ProgName, TempName);
X				return ERROR;
X			}
X
X			if (blocks >= 0L)	/* hasn't been deleted */
X			    if ((total + blocks) <= (MaxBlocks - TOLERANCE)
X				&& (filesize + strlen(name)+1) < MAX_HEADER) {
X				   total += blocks;
X				   filesize += strlen(name) + 1;
X				   fprintf(vol, "%s\n", name);
X   
X				   /* delete from tempfile */
X				   fseek(TempFile, filepos, 0);
X				   fprintf(TempFile, " -1    ");
X				   fgets(buf, BUFSIZ, TempFile);
X				   N_Records--;
X			    } else if (Preserve) /* can't rearrange files */
X				   break;
X			
X			filepos = ftell(TempFile);
X		}
X
X		fprintf(vol, "TOTAL-%ld-blocks-%s\n", total, GetDate());
X		fclose(vol);
X		printf("Volume %d: %ld blocks\n", volume, total);
X		grandtotal += total;
X	}
X
X	/*
X	** Now we know how many were needed altogether, so
X	** we can create the proper volume header file names.
X	*/
X	for (i = 1; i <= volume; i++) {
X		sprintf(oldfile, "%s-%d", TempName, i);
X		if (volume < 10)
X			sprintf(volfile, UNDER_10, i, volume);
X		else
X			sprintf(volfile, OVER_9, i, volume);
X		
X		if ((vol = fopen(volfile, "w")) == NULL) {
X			fprintf(stderr, "%s: fatal error -- can't open %s\n",
X				ProgName, volfile);
X			return ERROR;
X		} else if ((old = fopen(oldfile, "r")) == NULL) {
X			fprintf(stderr, "%s: fatal error -- can't open %s\n",
X				ProgName, volfile);
X			return ERROR;
X		}
X		
X		fprintf(vol, "%s\n", volfile);
X		while ((c = getc(old)) != EOF)
X			putc(c, vol);
X		
X		fclose(old);
X		fclose(vol);
X		unlink(oldfile);
X	}
X
X	printf("Total %ld blocks in %d volume%c\n",
X		grandtotal, volume, (volume == 1? ' ' : 's'));
X
X	return OKAY;
X}
X
Xvoid
XGetSize(pathname)
Xchar *pathname;
X{
X	struct stat stat_buf;
X	char du_pipe[BUFSIZ], buf[BUFSIZ];
X	FILE *du, *popen();
X
X	if (stat(pathname, &stat_buf) != 0) {
X		fprintf(stderr, "%s: can't stat %s\n",
X			ProgName, pathname);
X		return;
X	} else if ((stat_buf.st_mode & S_IFDIR) == 0
X		   && (stat_buf.st_mode & S_IFREG) == 0) {
X		fprintf(stderr, "%s: %s not a file or directory\n",
X			ProgName, pathname);
X		return;
X	}
X
X	sprintf(du_pipe, "du -s %s", pathname);
X	if ((du = popen(du_pipe, "r")) == NULL)
X		fprintf(stderr, "%s: can't popen du\n", ProgName);
X	else {
X		if (fgets(buf, BUFSIZ, du)) {
X			pclose(du);	/* before recursion */
X			if (atol(buf) > (MaxBlocks - TOLERANCE))
X				if (stat_buf.st_mode & S_IFDIR)
X					ProcessDir(pathname);
X				else
X					fprintf(stderr, "%s: %s too big\n", 
X						ProgName, pathname);
X			else
X				AddList(buf);
X		} else {
X			pclose(du);
X			fprintf(stderr, "%s: du not cooperating\n",
X				ProgName);
X		}
X	}
X
X	return;
X}
X
Xvoid
XProcessDir(pathname)
Xchar *pathname;
X{
X	FILE *dir;
X	char buf[BUFSIZ];
X	struct direct dir_buf;
X
X	if ((dir = fopen(pathname, "r")) == NULL)
X		fprintf(stderr, "%s: can't read directory %s\n",
X			ProgName, pathname);
X	else {
X		while (fread((char*)&dir_buf, sizeof(dir_buf), 1, dir) == 1)
X			if (dir_buf.d_ino
X			    && strcmp(dir_buf.d_name, ".") != 0
X			    && strcmp(dir_buf.d_name, "..") != 0) {
X				if (pathname[strlen(pathname)-1] == '/')
X					sprintf(buf, "%s%.14s",
X						pathname, dir_buf.d_name);
X				else
X					sprintf(buf, "%s/%.14s",
X						pathname, dir_buf.d_name);
X				GetSize(buf);
X			}
X		fclose(dir);
X	}
X	return;
X}
X
Xvoid
XAddList(buf)
Xchar *buf;
X{
X	long blocks;
X	char pathname[BUFSIZ];
X
X	if (sscanf(buf, "%ld %s", &blocks, pathname) != 2)
X		fprintf(stderr, "%s: unexpected data from du - \"%s\"\n",
X			ProgName, buf);
X	else if (blocks >= 0) {
X		fprintf(TempFile, "%07ld %s\n", blocks, pathname);
X		N_Records++;
X	}
X
X	return;
X}
X
Xchar *
XGetDate()
X{
X	long now, time();
X	struct tm *tm, *localtime();
X	static char date[9];
X
X	if (!(*date)) {
X		now = time((long *)0);
X		tm = localtime(&now);
X		sprintf(date, "%02d/%02d/%02d",
X			tm->tm_mon+1, tm->tm_mday, tm->tm_year);
X	}
X	return date;
X}
X
X
Xint
Xrm_temps(signum)
Xint signum;
X{
X	char buf[BUFSIZ];
X
X	signal(signum, SIG_IGN);
X	sprintf(buf, "rm -f %s*", TempName);
X	system(buf);
X
X	switch (signum) {
X	case SIGHUP:
X		fprintf(stderr, "%s: line gone\n", ProgName);
X		exit(ERROR);
X		break;
X	case SIGINT:
X		fprintf(stderr, "%s: terminated by user\n", ProgName);
X		exit(ERROR);
X		break;
X	case SIGQUIT:
X		signal(SIGQUIT, SIG_DFL);
X		kill(getpid(), SIGQUIT);
X		break;
X	case SIGTERM:
X		fprintf(stderr, "%s: killed\n", ProgName);
X		exit(ERROR);
X		break;
X	default:
X		return;
X	}
X}
SHAR_EOF
echo x - extracting voltar.sh
sed 's/^X//' << 'SHAR_EOF' > voltar.sh
X: voltar.sh
X#
X# Run vol to create volume header files.
X# Run tar with the volume header files.
X#
X
XBEEP=""
X
XTAPECMD="tar cvbf 126 /dev/rct"
XDISKCMD="tar cv"
X
XTAR_CMD=$DISKCMD
XOPTIONS="-d"
XPROMPT="floppy diskette"
X
XFILES=
X
Xif test $# -eq 0
Xthen echo "Usage: voltar [ -dt ] pathnames..."; exit
Xfi
X
Xfor i
Xdo
X	case $i in
X
X		-d)	TAR_CMD=$DISKCMD
X			OPTIONS="-d"
X			PROMPT="floppy diskette"
X			;;
X
X		-t)	TAR_CMD=$TAPECMD
X			OPTIONS="-t"
X			PROMPT="magnetic tape"
X			;;
X
X		*)	FILES="$FILES $i"
X			;;
X	esac
Xdone
X
Xrm -f Vol_*
Xvol $OPTIONS $FILES
X
Xfor i in Vol_*
Xdo
X	echo $BEEP
X	echo -n "$i: Insert $PROMPT, then type <return>: "
X	read line
X	$TAR_CMD `cat $i`
Xdone
SHAR_EOF
exit 0