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