From: caywood@teb.larc.nasa.gov (John Caywood)
Newsgroups: comp.protocols.appletalk
Subject: Re: Dumping Mac disks to unix hosted tape (long)
Date: 5 Jan 1993 19:58:27 GMT
Organization: NASA Langley Research Center, Hampton, VA  USA
Distribution: world
NNTP-Posting-Host: teb.larc.nasa.gov
Keywords: dump backup

In article <1hnmj6INNb8l@rave.larc.nasa.gov>, caywood@teb.larc.nasa.gov (John Caywood) writes:
|> I'm looking for a Macintosh network backup solution.  I have a 150M tape
|> and a 2.3G tape on a Sun SPARC 2, but no good way to dump Macs to either
|> tape.  I have MacDumper v3.2, but this version only dumps to disk, not
|> to tape, and has some other limitations.  Just checked rutgers.edu and
|> found that pub/src/macdump.tar.Z is older than the version I have, dating
|> from (mostly) July, 1989.  Got my version from a friend who no longer
|> works here, so I don't know the source but files are dated Sept. 1990
|> and Feb. 1991.  Is there a newer version somewhere?
|> 

I have two partial solutions so far.  Thanks to Steve Faull
(steve@neopath.wa.com) and Dave Platt (dplatt@ntg.com) for pointing me
to bbn.com as the source for MacDump.  I got the source on 12/29/92 and
compiled it with only minor problems; used mcvert and capit (from
cap60/contrib/AufsTools) to convert the Dumper.Hqx and Restore.Hqx
files from BBN, and it works.  The current versions are Dumper 3.3 and
Restore 2.3.

Dave Platt also sent patches to macdump.c to enable macdump to write
directly to tape, to verify a dump tape, and to extract the dump file
back to a UNIX disk.  Hence the "partial solution".  In Dave's words

	There is not, currently, any way to do a "selective" restore from the
	tape dump to Unix disk - you must read back the whole tape, create a
	macrestore-compatible full-dump file, and then restore portions of it to
	the Mac over the network by using Restore.  Maybe someday I'll get
	around to adding the ability to extract selected files from the
	tape-dump and write them to Unix disk in CAP/Aufs or AppleSingle or
	MacBinary format.

Dave's dump-to-tape patches work; I've only done a few tests so far, but I
was able to successfully restore to a Mac files that had been dumped to
tape with Dave's enhanced macdump.  I sent a reply to Dave, but haven't
heard from him on how we can distribute his patches.

Nigel Gilbert (Nigel.Gilbert@soc.surrey.ac.uk) sent a different partial
solution.  He modified macdump to accept a -directory=folder_name option
so that you can dump just one folder from a Macintosh volume -- another
useful feature.  It appears that Nigel's modifications are to an earlier
version of macdump, not the current version at bbn.com, so merging his
modifications with Dave's is not straightforward.  Nigel agreed to let me
post his modifications, but the file is large and not suitable for posting.

The latest README for the MacDump distribution on bbn.com asserts that no
further updates will be released by BBN.  Judging from the 7 email requests
I received requesting that I post this summary, there's a clear need for
these improvements.  I have enclosed Dave Platt's patches, plus my own
patch to the macdump man page documenting the changes, but I prefer not to
volunteer to maintain the package.  Is anyone else willing?  Any other
suggestions on maintaining and distributing these improvements?

Thanks to all who replied.

  ==================================================================
>From dplatt@ntg.com Mon Dec 28 20:13:33 1992
>From: dplatt@ntg.com (Dave Platt)
Date: Mon, 28 Dec 1992 17:05:53 PST
X-Mailer: Mail User's Shell (7.1.1 5/02/90)
To: caywood@teb.larc.nasa.gov
Subject: Macintosh dump-to-tape
Content-Length: 30643

If you apply the following diffs to the most recent version of macdump
(the one currently on bbn.com) you'll get the ability to dump
directly from a Mac to tape (bypassing the Unix-disk stage).

The modified macdump accepts the -tape, -read, -verify, and -blocks
commands.  "-tape" means that it should spool the dump session (the
AppleTalk packet stream, essentially) to standard-output rather than
writing a disk file.  "-read" means that it should read the packet
stream from standard input and create a dump file which can be processed
by the macrestore daemon.  "-verify" means that it should simply verify
the input stream rather than creating a dumpfile.  "-blocks" specifies
the number of 512-byte blocks per write to standard-out.

You can dump to tape in either of two ways:  direct to the device,
or via a pipe to "dd".  The latter method a bit more complex but
may give better performance, as it allows dd to be writing one block
to tape while macdump is filling another block over the network.

For example:

	macdump -mac=joe -disk=Big -full -tape -blocks=20 | \
	  dd ibs=20b obs=200b of=/dev/rst9

Then, to create a macrestore-compatible file:

	dd if=/dev/rst9 ibs=200b obs=20b | \
	   macdump -mac=joe -user=joe -read

There is not, currently, any way to do a "selective" restore from the
tape dump to Unix disk - you must read back the whole tape, create a
macrestore-compatible full-dump file, and then restore portions of it to
the Mac over the network by using Restore.  Maybe someday I'll get
around to adding the ability to extract selected files from the
tape-dump and write them to Unix disk in CAP/Aufs or AppleSingle or
MacBinary format.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  Makefile.diff macdump.c.diff
# Wrapped by dplatt@ntg on Mon Dec 28 16:58:12 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Makefile.diff' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile.diff'\"
else
echo shar: Extracting \"'Makefile.diff'\" \(922 characters\)
sed "s/^X//" >'Makefile.diff' <<'END_OF_FILE'
X*** macdump-bbn/Makefile	Thu Feb 21 13:12:01 1991
X--- macdump-merge/Makefile	Wed Jun 24 14:15:41 1992
X***************
X*** 6,11 ****
X--- 6,12 ----
X  LIBDIR = /usr/local/lib/cap
X  BINDIR = /usr/local/bin
X  OPERDIR = /usr/ucc/bin
X+ DUMPFLAGS = -DDOUBLEBUFFER
X  # define this if you want to have a special user that is allowed in with no 
X  # password. This if all dumps are stored under a single user name (and if
X  # security is not a problem).
X***************
X*** 39,45 ****
X  	$(LD) macdump.o print_rec.o $(LDFLAGS)
X  
X  macdump.o:	macdump.c macdump.h /usr/include/netat/appletalk.h
X! 	$(CC) $(CFLAGS) macdump.c 
X  
X  print_rec.o:	print_rec.c macdump.h
X  	$(CC) $(CFLAGS) print_rec.c
X--- 40,46 ----
X  	$(LD) macdump.o print_rec.o $(LDFLAGS)
X  
X  macdump.o:	macdump.c macdump.h /usr/include/netat/appletalk.h
X! 	$(CC) $(CFLAGS) $(DUMPFLAGS) macdump.c 
X  
X  print_rec.o:	print_rec.c macdump.h
X  	$(CC) $(CFLAGS) print_rec.c
END_OF_FILE
if test 922 -ne `wc -c <'Makefile.diff'`; then
    echo shar: \"'Makefile.diff'\" unpacked with wrong size!
fi
# end of 'Makefile.diff'
fi
if test -f 'macdump.c.diff' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'macdump.c.diff'\"
else
echo shar: Extracting \"'macdump.c.diff'\" \(25771 characters\)
sed "s/^X//" >'macdump.c.diff' <<'END_OF_FILE'
X*** macdump-bbn/macdump.c	Tue Aug  7 10:51:14 1990
X--- macdump-merge/macdump.c	Fri Aug 21 16:31:36 1992
X***************
X*** 42,55 ****
X--- 42,66 ----
X  
X  /* Definitions */
X  #include <stdio.h>
X+ #include <malloc.h>
X  #include <sys/types.h>
X  #include <sys/file.h>
X  #include <pwd.h>
X  #include <netat/appletalk.h>
X  #include <time.h>
X+ #include <sys/time.h>
X  #include <assert.h>
X  #include <sys/stat.h>
X  
X+ #ifdef DOUBLEBUFFER
X+ # include <sys/asynch.h>
X+ # include <unistd.h>
X+ # include <sys/filio.h>
X+ # include <fcntl.h>
X+ # include <signal.h>
X+ # include <errno.h>
X+ #endif
X+ 
X  #include "macdump.h"
X  #include "dumpprotocol.h"
X  
X***************
X*** 67,72 ****
X--- 78,88 ----
X  int forcefull = 0;		/* true to force a full dump */
X  int forceincr = 0;		/* true to force an incremental */
X  int getdisknames = 0;		/* true to find volume names */
X+ int tapewrite = 0;              /* true if writing to tape rather than disk */
X+ int taperead = 0;               /* true if reading tape */
X+ int verify = 0;
X+ int istape = 0;
X+ time_t timenow;
X  char *progname;
X  AddrBlock macaddr;		/* address of macintosh to dump */
X  ABusRecord abr;			/* record for remote access */
X***************
X*** 90,95 ****
X--- 106,138 ----
X  char fullname[256];
X  char *dumpname;
X  char tmpname[256];
X+ char tapeheader[256];
X+ char magicheader[] = "!macdumptape0";
X+ 
X+ #define BYTESPERBLOCK 512
X+ 
X+ #ifndef TAPEBUFFERSIZE
X+ # define TAPEBUFFERSIZE 10*BYTESPERBLOCK
X+ #endif
X+ 
X+ char *tapebuffer;
X+ int tapebuffersize = TAPEBUFFERSIZE;
X+ int tapebuffercount;
X+ int tapebufferavail;
X+ 
X+ #ifdef DOUBLEBUFFER
X+ char *writebuffer;
X+ aio_result_t write_result;
X+ int write_in_progress;
X+ int write_completed;
X+ int write_offset;
X+ int write_trace;
X+ void wait_for_io();
X+ extern int aiowrite();
X+ extern aio_result_t *aiowait();
X+ #endif
X+ 
X+ 
X  #define TMPNAME "TMPXXXXXX"
X  
X  #include "dumperrors.h"
X***************
X*** 96,101 ****
X--- 139,147 ----
X  
X  char *CtoPStr(), *PtoCStr();
X  
X+ void initbuffer();
X+ void flushbuffer();
X+ 
X  main(argc, argv) char **argv;
X  {
X    EntityName en;
X***************
X*** 105,110 ****
X--- 151,157 ----
X    time_t now;
X    struct tm *tmnow, *localtime();
X    int err;
X+   int i;
X    struct stat fullstatb, incrstatb;
X    char *strcpy();
X    char *p;
X***************
X*** 134,139 ****
X--- 181,208 ----
X        forceincr = 1;
X      else if (strcmp(*argv, "-disknames", 3) == 0)
X        getdisknames = 1;
X+     else if (strncmp(*argv, "-tape", 5) == 0)
X+       tapewrite = 1;
X+     else if (strncmp(*argv, "-read", 5) == 0)
X+       taperead = 1;
X+     else if (strncmp(*argv, "-blocks=", 8) == 0) {
X+       i = atoi(*argv+8);
X+       if (i > 0 && i <= 9999) {
X+ 	tapebuffersize = i * BYTESPERBLOCK;
X+       } else {
X+ 	fprintf(stderr, "%s: blocks must be between 1 and 9999\n", progname);
X+ 	clean_exit(EXIT_USER);
X+       }
X+     }
X+     else if (strncmp(*argv, "-verify", 5) == 0) {
X+       taperead = 1;
X+       verify = 1;
X+     }
X+ #ifdef DOUBLEBUFFER
X+     else if (strncmp(*argv, "-writetrace", 11) == 0) {
X+       write_trace = 1;
X+     }
X+ #endif
X      else if (strncmp(*argv, "-d", 2) == 0) {
X        ++debug;
X        if (*argv+2 != '\0') dbugarg(*argv+2);
X***************
X*** 147,182 ****
X    if (macname == 0)
X      macname = user;
X  
X    if (!getdisknames) {
X!     if (disk == 0 && directory == 0) {
X        fprintf(stderr, "%s: you must specify a disk volume or directory\n",
X  	      progname);
X        clean_exit(EXIT_USER);
X      }
X! 
X!     if ((pwd = getpwnam(user)) == 0) {
X!       fprintf(stderr, "%s: unknown user %s\n", progname, user);
X!       clean_exit(EXIT_SETUP);
X      }
X!     if (chdir(pwd->pw_dir) < 0) {
X!       fprintf(stderr, "%s: failed to connect to directory %s\n", pwd->pw_dir);
X!       clean_exit(EXIT_SETUP);
X      }
X-     if (chdir("macdumps") < 0) {
X-       fprintf(stderr, "%s: %s has no 'macdumps' directory\n", progname, user);
X-       clean_exit(EXIT_SETUP);
X-     }
X  
X-     /* Run as this user */
X-     setgid(pwd->pw_gid);
X-     setuid(pwd->pw_uid);
X- 
X-     /* Decide whether we are doing a full or an incremental */
X-     strcpy(tmpname, TMPNAME);
X-     mktemp(tmpname);		/* temp storage */
X- 
X    }
X  
X    /* Upper case the mac and disk names */
X    for(p = macname; *p; ++p)
X      if (*p >= 'a' && *p <= 'z') *p -= ' ';
X--- 216,266 ----
X    if (macname == 0)
X      macname = user;
X  
X+   if (taperead && tapewrite) {
X+     fprintf(stderr, "Cannot both read and write tape at once!\n");
X+     clean_exit(EXIT_USER);
X+   }
X+ 
X+   istape = taperead | tapewrite;
X+ 
X    if (!getdisknames) {
X!     if (!verify && (disk == 0 && directory == 0)) {
X        fprintf(stderr, "%s: you must specify a disk volume or directory\n",
X  	      progname);
X        clean_exit(EXIT_USER);
X      }
X!     
X!     if (!istape) {
X!       if ((pwd = getpwnam(user)) == 0) {
X! 	fprintf(stderr, "%s: unknown user %s\n", progname, user);
X! 	clean_exit(EXIT_SETUP);
X!       }
X!       if (chdir(pwd->pw_dir) < 0) {
X! 	fprintf(stderr, "%s: failed to connect to directory %s\n", pwd->pw_dir);
X! 	clean_exit(EXIT_SETUP);
X!       }
X!       if (chdir("macdumps") < 0) {
X! 	fprintf(stderr, "%s: %s has no 'macdumps' directory\n", progname, user);
X! 	clean_exit(EXIT_SETUP);
X!       }
X!       
X!       /* Run as this user */
X!       setgid(pwd->pw_gid);
X!       setuid(pwd->pw_uid);
X      }
X!     
X!     /* Decide whether we are doing a full or an incremental */
X!     if (verify) {
X!       strcat(tmpname, "/dev/null");
X!     } else {
X!       strcpy(tmpname, TMPNAME);
X!       mktemp(tmpname);		/* temp storage */
X      }
X  
X    }
X  
X+   initbuffer();
X+ 
X    /* Upper case the mac and disk names */
X    for(p = macname; *p; ++p)
X      if (*p >= 'a' && *p <= 'z') *p -= ' ';
X***************
X*** 223,317 ****
X        full = 0;
X      }
X  
X!     fprintf(stderr, "Doing %s dump to %s\n",
X! 	    directory == 0 ? (full ? "full" : "incremental") : "directory",
X! 	    dumpname);
X    }
X  
X    /* Init appletalk */
X!   abInit(debug);
X! 
X!   /* Look up the macintosh */
X!   nbpInit();
X!   strcpy(&en.objStr, macname);
X!   strcpy(&en.typeStr, "DUMPDEV");
X!   strcpy(&en.zoneStr, zone);
X! 
X!   nbpr.nbpRetransmitInfo.retransInterval = 8;
X!   nbpr.nbpRetransmitInfo.retransCount = 20;
X!   nbpr.nbpBufPtr = nbpt;
X!   nbpr.nbpBufSize = sizeof(nbpt);
X!   nbpr.nbpDataField = 1;
X!   nbpr.nbpEntityPtr = &en;
X!   err = NBPLookup(&nbpr, FALSE);
X!   if (err != noErr || nbpr.nbpDataField <= 0) {
X!     fprintf(stderr, "NBP Lookup of %s failed\n", macname);
X!     clean_exit(EXIT_NOMAC);
X!   }
X!   /* Extract the first item */
X!   NBPExtract(nbpt, nbpr.nbpDataField, 1, &en, &macaddr);
X!   
X!   /* No longer need the NBP socket */
X!   nbpShutdown();
X! 
X!   if (debug)
X      printf("Dump device at %d/%d:%02x\n", htons(macaddr.net), macaddr.node,
X! 	 macaddr.skt);
X  
X!   if (getdisknames) {
X!     int gotname = 0;
X!     register int i;
X!     struct VolNameRecord *vnp;
X! 
X!     for(i = 1; ; ++i) {
X!       register short *ip = (short *)buffer;
X!       *ip = htons(i);
X! 
X!       if (send_cmd(VOLUME_NAME, buffer, sizeof(short)) != ACK) {
X! 	if (!gotname) 
X! 	  /* convert response to a C string */
X! 	  fprintf(stderr, "%s: Response: %s\n", progname, PtoCStr(recbuffer));
X! 	break;
X        }
X!       gotname = 1;
X!       vnp = (struct VolNameRecord *)recbuffer;
X!       printf("Volume index %d, disk=\"%s\" %dMB\n", i,
X! 	     PtoCStr(vnp->volname), htonl(vnp->size)/(1024*1024));
X      }
X! 
X!     /* Have seen all volumes */
X!     clean_exit(EXIT_OK);
X    }
X  
X!   /* Try to access the right disk volume on the mac */
X!   if (directory != 0) {
X!     buffer[0] = strlen(directory);
X!     strcpy(&buffer[1], directory);
X    } else {
X!     buffer[0] = strlen(disk);
X!     strcpy(&buffer[1], disk);
X    }
X-   if (send_cmd(directory == 0 ? 
X- 	       (full ? BEGIN_FULL : BEGIN_INCR) : BEGIN_DIRECTORY,
X- 	       buffer, buffer[0]+1) != ACK) {
X-     /* convert response to a C string */
X-     fprintf(stderr, "%s: Failed to dump %s. Response: %s\n", progname,
X- 	    directory == 0 ? disk : directory, PtoCStr(recbuffer));
X-     clean_exit(EXIT_REJECT);
X-   }
X- 
X-   /* If success, open the dump file */
X-   if ((dumpfd = creat(tmpname, 0666)) < 0) {
X-     fprintf(stderr, "%s: Failed to create dump file %s: %s\n",
X- 	    progname, tmpname, sys_errlist[errno]);
X-     clean_exit(EXIT_SYSTEM);
X-   }
X-   close(dumpfd);
X-   if ((dumpfd = open(tmpname, O_RDWR)) < 0) {
X-     fprintf(stderr, "%s: Failed to re-open dump file %s: %s\n",
X- 	    progname, tmpname, sys_errlist[errno]);
X-     clean_exit(EXIT_SYSTEM);
X-   }
X    dumpindex = 0;		/* start at index 0 */
X  
X    if (directory != 0) {
X--- 307,431 ----
X        full = 0;
X      }
X  
X!     if (tapewrite) {
X!       fprintf(stderr, "Doing %s dump of %s volume %s to tape\n",
X! 	      directory == 0 ? (full ? "full" : "incremental") : "directory",
X! 	      macname, directory != 0 ? directory : disk);
X!       timenow = time(NULL);
X!       sprintf(tapeheader+1, "Dump of %s on %s", dumpname, ctime(&timenow));
X!       tapeheader[0] = strlen(tapeheader+1);
X!     } else if (!taperead) {
X!       fprintf(stderr, "Doing %s dump to %s\n",
X! 	      directory == 0 ? (full ? "full" : "incremental") : "directory",
X! 	      dumpname);
X!     }
X    }
X  
X+   if (taperead) {
X+     readbytes(stdin, tapeheader, strlen(magicheader));
X+     if (strcmp(magicheader, tapeheader) != 0) {
X+       fprintf(stderr, "Bad header: %s\n", tapeheader);
X+       clean_exit(EXIT_USER);
X+     }
X+     readbytes(stdin, tapeheader, 1);
X+     readbytes(stdin, tapeheader+1, tapeheader[0]);
X+     fprintf(stderr, "%s", tapeheader+1);
X+   } else {
X    /* Init appletalk */
X!     abInit(debug);
X!     
X!     /* Look up the macintosh */
X!     nbpInit();
X!     strcpy(&en.objStr, macname);
X!     strcpy(&en.typeStr, "DUMPDEV");
X!     strcpy(&en.zoneStr, zone);
X!     
X!     nbpr.nbpRetransmitInfo.retransInterval = 8;
X!     nbpr.nbpRetransmitInfo.retransCount = 20;
X!     nbpr.nbpBufPtr = nbpt;
X!     nbpr.nbpBufSize = sizeof(nbpt);
X!     nbpr.nbpDataField = 1;
X!     nbpr.nbpEntityPtr = &en;
X!     err = NBPLookup(&nbpr, FALSE);
X!     if (err != noErr || nbpr.nbpDataField <= 0) {
X!       fprintf(stderr, "NBP Lookup of %s failed\n", macname);
X!       clean_exit(EXIT_NOMAC);
X!     }
X!     /* Extract the first item */
X!     NBPExtract(nbpt, nbpr.nbpDataField, 1, &en, &macaddr);
X!     
X!     /* No longer need the NBP socket */
X!     nbpShutdown();
X!     
X      printf("Dump device at %d/%d:%02x\n", htons(macaddr.net), macaddr.node,
X! 	   macaddr.skt);
X  
X!     if (getdisknames) {
X!       int gotname = 0;
X!       register int i;
X!       struct VolNameRecord *vnp;
X!       
X!       for(i = 1; ; ++i) {
X! 	register short *ip = (short *)buffer;
X! 	*ip = htons(i);
X! 	
X! 	if (send_cmd(VOLUME_NAME, buffer, sizeof(short)) != ACK) {
X! 	  if (!gotname) 
X! 	    /* convert response to a C string */
X! 	    fprintf(stderr, "%s: Response: %s\n", progname, PtoCStr(recbuffer));
X! 	  break;
X! 	}
X! 	gotname = 1;
X! 	vnp = (struct VolNameRecord *)recbuffer;
X! 	printf("Volume index %d, disk=\"%s\" %dMB\n", i,
X! 	       PtoCStr(vnp->volname), htonl(vnp->size)/(1024*1024));
X        }
X!       /* Have seen all volumes */
X!       clean_exit(EXIT_OK);
X      }
X!     
X!   /* Try to access the right disk volume on the mac */
X!     if (directory != 0) {
X!       buffer[0] = strlen(directory);
X!       strcpy(&buffer[1], directory);
X!     } else {
X!       buffer[0] = strlen(disk);
X!       strcpy(&buffer[1], disk);
X!     }
X!     if (send_cmd(directory == 0 ? 
X! 		 (full ? BEGIN_FULL : BEGIN_INCR) : BEGIN_DIRECTORY,
X! 		 buffer, buffer[0]+1) != ACK) {
X!       /* convert response to a C string */
X!       fprintf(stderr, "%s: Failed to dump %s. Response: %s\n", progname,
X! 	      directory == 0 ? disk : directory, PtoCStr(recbuffer));
X!       clean_exit(EXIT_REJECT);
X!     }
X    }
X  
X!   /* If success, open the dump file or get set for stdout */
X! 
X!   if (tapewrite) {
X!     dumpfd = fileno(stdout);
X! #ifdef DOUBLEBUFFER
X!     configure_nowait(dumpfd);
X! #endif
X!     writebytes(dumpfd, magicheader, strlen(magicheader));
X!     writebytes(dumpfd, tapeheader, tapeheader[0]+1);
X    } else {
X!     if (!verify) {
X!       if ((dumpfd = creat(tmpname, 0666)) < 0) {
X! 	fprintf(stderr, "%s: Failed to create dump file %s: %s\n",
X! 		progname, tmpname, sys_errlist[errno]);
X! 	clean_exit(EXIT_SYSTEM);
X!       }
X!       close(dumpfd);
X!       if ((dumpfd = open(tmpname, O_RDWR)) < 0) {
X! 	fprintf(stderr, "%s: Failed to re-open dump file %s: %s\n",
X! 		progname, tmpname, sys_errlist[errno]);
X! 	clean_exit(EXIT_SYSTEM);
X!       }
X!     }
X    }
X    dumpindex = 0;		/* start at index 0 */
X  
X    if (directory != 0) {
X***************
X*** 342,350 ****
X        clean_exit(EXIT_BADREC);
X      }
X      lastrec.nxtdir = htonl(dumpindex); /* set the link field */
X!     lseek(dumpfd, 0L, 0);
X!     write(dumpfd, &lastrec, htonl(lastrec.reclen));
X!     lseek(dumpfd, dumpindex, L_SET);
X    } else {
X      /* Pull over the records to make the dump */
X      next_record();		/* get the volume record */
X--- 456,466 ----
X        clean_exit(EXIT_BADREC);
X      }
X      lastrec.nxtdir = htonl(dumpindex); /* set the link field */
X!     if (!tapewrite && !verify) {
X!       lseek(dumpfd, 0L, 0);
X!       write(dumpfd, &lastrec, htonl(lastrec.reclen));
X!       lseek(dumpfd, dumpindex, L_SET);
X!     }
X    } else {
X      /* Pull over the records to make the dump */
X      next_record();		/* get the volume record */
X***************
X*** 362,381 ****
X        fprintf(stderr, "%s: Dump of %s failed\n", progname, disk);
X        clean_exit(exitcode);
X      }
X  
X!     /* Set the number of files seen into the volume record */
X!     /* (the MAC only keeps track of files on root) */
X!     set_filecount(dumpfd, totalfiles);
X    }
X!   close(dumpfd);
X! 
X!   /* Rename the temp file to the correct name */
X!   unlink(dumpname);
X!   link(tmpname, dumpname);
X!   unlink(tmpname);
X!   /* If full make sure there is no old incr */
X!   if (full) unlink(incrname);
X! 
X    clean_exit(EXIT_OK);
X  }
X  
X--- 478,507 ----
X        fprintf(stderr, "%s: Dump of %s failed\n", progname, disk);
X        clean_exit(exitcode);
X      }
X+     
X+     if (!tapewrite && !verify) {
X+       /* Set the number of files seen into the volume record */
X+       /* (the MAC only keeps track of files on root) */
X+       set_filecount(dumpfd, totalfiles);
X+     }
X  
X!     if (tapewrite) {
X!       flushbuffer();
X! #ifdef DOUBLEBUFFER
X!       wait_for_io();
X! #endif
X!     }
X!     
X!     close(dumpfd);
X!     
X!     /* Rename the temp file to the correct name */
X!     unlink(dumpname);
X!     link(tmpname, dumpname);
X!     unlink(tmpname);
X!     /* If full make sure there is no old incr */
X!     if (full) unlink(incrname);
X    }
X!   
X    clean_exit(EXIT_OK);
X  }
X  
X***************
X*** 402,426 ****
X        ++totalfiles;		/* a directory counts for our purposes */
X        retval = dumpdir(lastrec.DirID);  /* dump this directory recursively */
X  
X!       /* Update the link field in this directory record */
X!       lseek(dumpfd, thisdirrec, L_SET);
X!       if (retval == NAK) return (NAK);
X!       if (read(dumpfd, &lastrec, sizeof(lastrec)) < sizeof(lastrec) - 32) {
X! 	fprintf(stderr,
X! 		"%s: error re-reading directory record: %s\n",
X! 		progname, sys_errlist[errno]);
X! 	clean_exit(EXIT_SYSTEM);
X!       } else if ( htonl(lastrec.rectype) != REC_DIR) {
X! 	fprintf(stderr,
X! 		"%s: error re-reading directory record, got type %d\n",
X! 		progname, htonl(lastrec.rectype));
X! 	clean_exit(EXIT_SYSTEM);
X        }
X- 
X-       lastrec.nxtdir = htonl(dumpindex); /* set the link field */
X-       lseek(dumpfd, thisdirrec, 0);
X-       write(dumpfd, &lastrec, htonl(lastrec.reclen));
X-       lseek(dumpfd, dumpindex, L_SET);
X      } else if (htonl(lastrec.rectype) == REC_EODIR) {
X        if (lastrec.DirID != thisdirnum) {
X  	fprintf(stderr, "%s: wrong directory number in END_OF_DIR record\n",
X--- 528,554 ----
X        ++totalfiles;		/* a directory counts for our purposes */
X        retval = dumpdir(lastrec.DirID);  /* dump this directory recursively */
X  
X!       if (!tapewrite && !verify) {
X! 	/* Update the link field in this directory record */
X! 	lseek(dumpfd, thisdirrec, L_SET);
X! 	if (retval == NAK) return (NAK);
X! 	if (read(dumpfd, &lastrec, sizeof(lastrec)) < sizeof(lastrec) - 32) {
X! 	  fprintf(stderr,
X! 		  "%s: error re-reading directory record: %s\n",
X! 		  progname, sys_errlist[errno]);
X! 	  clean_exit(EXIT_SYSTEM);
X! 	} else if ( htonl(lastrec.rectype) != REC_DIR) {
X! 	  fprintf(stderr,
X! 		  "%s: error re-reading directory record, got type %d\n",
X! 		  progname, htonl(lastrec.rectype));
X! 	  clean_exit(EXIT_SYSTEM);
X! 	}
X! 	
X! 	lastrec.nxtdir = htonl(dumpindex); /* set the link field */
X! 	lseek(dumpfd, thisdirrec, 0);
X! 	write(dumpfd, &lastrec, htonl(lastrec.reclen));
X! 	lseek(dumpfd, dumpindex, L_SET);
X        }
X      } else if (htonl(lastrec.rectype) == REC_EODIR) {
X        if (lastrec.DirID != thisdirnum) {
X  	fprintf(stderr, "%s: wrong directory number in END_OF_DIR record\n",
X***************
X*** 487,518 ****
X   */
X  next_record()
X  {
X!   register int i, j;
X!   register int partial;		/* set on partial record received */
X  
X! /* exitcode set in send_cmd when explicit in this routine */
X!   if (send_cmd(NEXT_RECORD, 0, 0) == NAK) {
X!     printf("NAK: %s\n", PtoCStr(recbuffer));
X!     return(NAK);
X    }
X-   if (debug) print_rec(stdout, recbuffer, 1);
X  
X    /* Save first path of the record */
X    bcopy(recbuffer, &lastrec, sizeof(lastrec));
X-   do {
X-     partial = (((u_char *)&recBDS[0].userData)[0] == PARTIAL);
X  
X      for(i = 0; i < recNBDS; ++i) {
X        dumpindex += recBDS[i].dataSize;
X!       j = recBDS[i].dataSize;
X!       if (write(dumpfd, recBDS[i].buffPtr, j) != j) {
X! 	exitcode = EXIT_WRITEFAIL;
X! 	return(NAK);
X        }
X      }
X!     if (partial && send_cmd(CONTINUE_RECORD, 0, 0) == NAK) return(NAK);
X!   } while(partial);
X! 
X    return(ACK);
X  }
X      
X--- 615,711 ----
X   */
X  next_record()
X  {
X!   register int i, j, k;
X!   char partial, moretocome;		/* set on partial record received */
X!   register char *p;
X  
X!   if (taperead) {
X!     i = readbytes(stdin, &partial, sizeof partial);
X!     if (i != sizeof partial) return (NAK);
X!     if (partial == -1) return NAK;
X!     i = readbytes(stdin, &recNBDS, sizeof recNBDS);
X!     if (i != sizeof recNBDS) return (NAK);
X!     for(i = 0, p = recbuffer; i < atpMaxNum; ++i, p += atpMaxData) {
X!       recBDS[i].buffPtr = p;
X!       recBDS[i].buffSize = atpMaxData;
X!     }
X!     for (j = 0; j < recNBDS; j++) {
X!       i = readbytes(stdin, &recBDS[j].dataSize, sizeof recBDS[j].dataSize);
X!       if (i != sizeof recBDS[j].dataSize) return (NAK);
X!       i = readbytes(stdin, recBDS[j].buffPtr, recBDS[j].dataSize);
X!       if (i != recBDS[j].dataSize) return (NAK);
X!       if (debug > 1) {
X! 	fprintf(stderr, "Read block: %d bytes\n", recBDS[j].dataSize);
X!       }
X!     }
X!   } else {
X!     if (send_cmd(NEXT_RECORD, 0, 0) == NAK) {
X!       printf("NAK: %s\n", PtoCStr(recbuffer));
X!       return(NAK);
X!     }
X!     partial = (((u_char *)&recBDS[0].userData)[0] == PARTIAL);
X    }
X  
X+   if (debug) print_rec(stderr, recbuffer, 1);
X+ 
X    /* Save first path of the record */
X    bcopy(recbuffer, &lastrec, sizeof(lastrec));
X  
X+   /* Loop through, writing the data to the disk or tape buffer, and
X+      reading the next segment if the previous read was for a partial
X+      record.
X+   */
X+ 
X+   moretocome = 1;
X+   while (moretocome) {
X+     moretocome = partial;
X+     if (debug > 1) {
X+       fprintf(stderr, "Write record: partial %d, NBDS %d\n", partial, recNBDS);
X+     }
X+     if (tapewrite) {
X+       writebytes(dumpfd, &partial, sizeof partial);
X+       writebytes(dumpfd, &recNBDS, sizeof recNBDS);
X+     }
X      for(i = 0; i < recNBDS; ++i) {
X+       if (tapewrite) {
X+ 	writebytes(dumpfd, &recBDS[i].dataSize, sizeof recBDS[i].dataSize);
X+ 	writebytes(dumpfd, recBDS[i].buffPtr, recBDS[i].dataSize);
X+       } else {
X+ 	write(dumpfd, recBDS[i].buffPtr, recBDS[i].dataSize);
X+       }
X+       if (debug > 1) {
X+ 	fprintf(stderr, "  Write block: %d bytes at %d\n", recBDS[i].dataSize,
X+ 		dumpindex);
X+       }
X        dumpindex += recBDS[i].dataSize;
X!     }
X!     if (partial) {
X!       if (taperead) {
X! 	i = readbytes(stdin, &partial, sizeof partial);
X! 	if (i != sizeof partial) return (NAK);
X! 	if (partial == -1) return NAK;
X! 	i = readbytes(stdin, &recNBDS, sizeof recNBDS);
X! 	if (i != sizeof recNBDS) return (NAK);
X! 	for (j = 0; j < recNBDS; j++) {
X! 	  i = readbytes(stdin, &recBDS[j].dataSize,
X! 		   sizeof recBDS[j].dataSize);
X! 	  if (i != sizeof recBDS[j].dataSize) return (NAK);
X! 	  i = readbytes(stdin, recBDS[j].buffPtr, recBDS[j].dataSize);
X! 	  if (i != recBDS[j].dataSize) return (NAK);
X! 	}
X!       } else {
X! 	if (send_cmd(CONTINUE_RECORD, 0, 0) == NAK) {
X! 	  if (tapewrite) {
X! 	    partial = -1;
X! 	    writebytes(dumpfd, &partial, sizeof partial);
X! 	  }
X! 	  return(NAK);
X! 	}
X! 	partial = (((u_char *)&recBDS[0].userData)[0] == PARTIAL);
X        }
X      }
X!   }
X!   
X    return(ACK);
X  }
X      
X***************
X*** 546,556 ****
X   */
X  clean_exit(code)
X  {
X!   if (tmpname[0]) unlink(tmpname);
X    exit(code);
X  }
X  
X  /*
X   * CtoPStr - C String to Pascal string in place.
X   */
X  char *CtoPStr(p)
X--- 739,784 ----
X   */
X  clean_exit(code)
X  {
X!   if (tmpname[0] && !verify) unlink(tmpname);
X    exit(code);
X  }
X  
X  /*
X+  * readbytes - read bytes from a file or socket, guaranteeing that we get
X+  * as many as we ask for unless EOF occurs.  Use the big buffer.
X+  */
X+ 
X+ int readbytes(theFile, buffer, howMany)
X+      FILE *theFile;
X+      char *buffer;
X+      int howMany;
X+ {
X+   int remaining, got, total;
X+   remaining = howMany;
X+   total = 0;
X+   while (remaining > 0) {
X+     if (tapebufferavail == 0) {
X+       tapebufferavail = read(fileno(theFile), tapebuffer, tapebuffersize);
X+       if (tapebufferavail == 0) return total;
X+       if (tapebufferavail < 0) {
X+ 	perror("Error reading from standard input");
X+ 	clean_exit(EXIT_SYSTEM);
X+       }
X+       tapebuffercount = 0;
X+     }
X+     got = tapebufferavail;
X+     if (got > remaining) got = remaining;
X+     memcpy(buffer, tapebuffer+tapebuffercount, got);
X+     total += got;
X+     buffer += got;
X+     tapebuffercount += got;
X+     tapebufferavail -= got;
X+     remaining -= got;
X+   }
X+   return total;
X+ }
X+ 
X+ /*
X   * CtoPStr - C String to Pascal string in place.
X   */
X  char *CtoPStr(p)
X***************
X*** 579,582 ****
X--- 807,960 ----
X    *p0++ = 0;
X  
X    return(p);
X+ }
X+ 
X+ void initbuffer()
X+ {
X+   int flags;
X+   tapebuffer = malloc(tapebuffersize);
X+   if (!tapebuffer) {
X+     fprintf(stderr, "Could not allocate tape buffer\n");
X+     clean_exit(EXIT_SYSTEM);
X+   }
X+   tapebuffercount = 0;
X+   tapebufferavail = 0;
X+ #ifdef DOUBLEBUFFER
X+   writebuffer = malloc(tapebuffersize);
X+   if (!writebuffer) {
X+     fprintf(stderr, "Could not allocate tape buffer\n");
X+     clean_exit(EXIT_SYSTEM);
X+   }
X+   write_in_progress = 0;
X+   write_offset = 0;
X+   write_completed = 0;
X+ #endif
X+   return;
X+ }
X+ 
X+ 
X+ #ifdef DOUBLEBUFFER
X+ 
X+ int configure_nowait(fd)
X+      int fd;
X+ {
X+   int flags;
X+   flags = fcntl(fd, F_GETFL, 0);
X+   if (fcntl(fd, F_SETFL, flags | FNDELAY) == -1) {
X+     return 0;
X+   }
X+   flags = fcntl(fd, F_GETFL, 0);
X+   if (!(flags & FNDELAY)) {
X+     fprintf("Failed to set FNDELAY flag!\n");
X+     return;
X+   }
X+   fprintf(stderr, "Successfully configured for asynchronous writes\n");
X+ }
X+ 
X+ /*
X+   pushbytes() returns -1 if a "can't write, would block" condition
X+   exists when it tries to write, and >= 0 otherwise.
X+ */
X+ 
X+ int pushbytes(fd)
X+ {
X+   int result, push;
X+   if (!write_in_progress) {
X+     return 0;
X+   }
X+   push = tapebuffersize-write_offset;
X+   if (write_trace) {
X+     fprintf(stderr, "Writing %d bytes... ", push);
X+     fflush(stderr);
X+   }
X+   result = write(fd, writebuffer+write_offset, push);
X+   if (result < 0) {
X+     if (errno != EWOULDBLOCK) {
X+       perror("Write error");
X+       clean_exit(EXIT_SYSTEM);
X+     }
X+     if (write_trace) {
X+       fprintf(stderr, "blocked.\n");
X+       fflush(stderr);
X+     }
X+   } else {
X+     if (write_trace) {
X+       fprintf(stderr, "wrote %d\n", result);
X+       fflush(stderr);
X+     }
X+     write_offset += result;
X+     if (write_offset >= tapebuffersize) {
X+       write_in_progress = 0;
X+     }
X+   }
X+   return result;
X+ }
X+ 
X+ void wait_for_io(fd)
X+      int fd;
X+ {
X+   int status;
X+   while (write_in_progress) {
X+     status = pushbytes(fd);
X+     if (status == -1) {
X+       if (write_trace) {
X+ 	fprintf(stderr, "Issuing wait\n");
X+ 	fflush(stderr);
X+       }
X+       sleep(1);
X+     }
X+   }
X+ }
X+ 
X+ #endif
X+ 
X+ void flushbuffer(fd)
X+      int fd;
X+ {
X+   int result;
X+   if (tapebuffercount > 0) {
X+     if (tapebuffercount < tapebuffersize) {
X+       memset(tapebuffer+tapebuffercount, 0, tapebuffersize - tapebuffercount);
X+     }
X+ #ifdef DOUBLEBUFFER
X+     wait_for_io(fd);
X+     memcpy(writebuffer, tapebuffer, tapebuffersize);
X+     write_offset = 0;
X+     write_in_progress = 1;
X+     pushbytes(fd);
X+ #else
X+     if (write(fd, tapebuffer, tapebuffersize) == -1) {
X+       perror("write() failure");
X+       clean_exit(EXIT_SYSTEM);
X+     }
X+ #endif
X+     tapebuffercount = 0;
X+   }
X+ }
X+ 
X+ int writebytes(fd, data, count)
X+      int fd;
X+      char *data;
X+      int count;
X+ {
X+   int now;
X+ #ifdef DOUBLEBUFFER
X+   if (write_in_progress) {
X+     (void) pushbytes(fd);
X+   }
X+ #endif
X+   while (count > 0) {
X+     if (tapebuffercount == tapebuffersize) {
X+       flushbuffer(fd);
X+     }
X+     now = tapebuffersize - tapebuffercount;
X+     if (now > count) {
X+       now = count;
X+     }
X+     memcpy(tapebuffer + tapebuffercount, data, now);
X+     data += now;
X+     count -= now;
X+     tapebuffercount += now;
X+   }
X+   return 0;
X  }
END_OF_FILE
if test 25771 -ne `wc -c <'macdump.c.diff'`; then
    echo shar: \"'macdump.c.diff'\" unpacked with wrong size!
fi
# end of 'macdump.c.diff'
fi
echo shar: End of shell archive.
exit 0

  ==============================================================
# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	macdump.man.diff
#
echo x - macdump.man.diff
sed 's/^X//' >macdump.man.diff << 'END-of-macdump.man.diff'
X*** macdump-merge/macdump.8	Tue Jan 10 11:52:18 1989
X--- macdump-bbn/macdump.8	Tue Dec 29 14:54:08 1992
X***************
X*** 3,14 ****
X  macdump - perform an automated backup of a Macintosh
X  .SH SYNTAX
X  .B macdump
X! \-user=name \-disk=name [ \-mac=name ] [ \-zone=name ] [ \-full ] [ \-incr ] [ \-debug ] [ \-disknames ]
X  .SH DESCRIPTION
X  .I macdump
X  is run to perform a backup operation of a Macintosh on Appletalk.
X  The Macintosh must be running the network dump server.
X  .PP
X  The 
X  .B \-user=
X  parameter specifies the user name that the dump files will be owned by.
X--- 3,24 ----
X  macdump - perform an automated backup of a Macintosh
X  .SH SYNTAX
X  .B macdump
X! \-user=name \-disk=name [ \-mac=name ] [ \-zone=name ] [ \-full ] [ \-incr ] [ \-debug ] [ \-disknames ] [ \-tape ] [ \-read ] [ \-blocks=n ] [ \-verify ] [ \-writetrace ]
X  .SH DESCRIPTION
X  .I macdump
X  is run to perform a backup operation of a Macintosh on Appletalk.
X  The Macintosh must be running the network dump server.
X+ If the
X+ .B \-tape
X+ option is specified, the dump image is written to the standard output
X+ (which may be redirected to a tape device);
X+ otherwise the image will be written to a dumpfile.
X  .PP
X+ There is not, currently, any way to do a "selective" restore from the
X+ tape dump to Unix disk \(em you must read back the whole tape, create a
X+ \fImacrestore\fP\^-compatible full-dump file, and then restore portions of it to
X+ the Mac over the network by using Restore.
X+ .PP
X  The 
X  .B \-user=
X  parameter specifies the user name that the dump files will be owned by.
X***************
X*** 45,50 ****
X--- 55,159 ----
X  options when using the
X  .B \-diskname
X  option.
X+ .PP
X+ The
X+ .B \-tape
X+ option instructs
X+ .I macdump
X+ to write the dump image to the standard output;
X+ if the command line redirects this to a tape device, the dump image will
X+ be written directly to tape instead of to a disk file.
X+ .PP
X+ The
X+ .B \-read
X+ option instructs
X+ .I macdump
X+ to read from the standard input, which is typically redirected to a tape device,
X+ and create a dump file that is compatible with the
X+ .I macrestore
X+ program.
X+ If both the
X+ .B \-tape
X+ and
X+ .B \-read
X+ options are present, the program exits with an error indication.
X+ .PP
X+ .B \-verify
X+ presumes the
X+ .B \-read
X+ option, but no dump file is written;
X+ when an execution of
X+ .I macdump
X+ .B \-tape
X+ is followed by
X+ .I macdump
X+ .B \-verify
X+ the program verifies that all data on the tape is readable.
X+ It
X+ .I "does not"
X+ verify the data on the tape against the data on the Macintosh.
X+ .PP
X+ .B \-blocks=
X+ specifies the number of 512-byte blocks to read or write when using tape;
X+ the default is 10 blocks.
X+ Specifying less than 1 block or more than 9999 blocks will cause
X+ .I macdump
X+ to exit with an error indication.
X+ .PP
X+ The
X+ .B \-writetrace
X+ option provides additional debugging for tape operation.
X+ .SH EXAMPLES
X+ .PP
X+ Execute
X+ .I macdump
X+ with these options to dump the disk
X+ .B "Macintosh HD"
X+ on a Macintosh named
X+ .I Joe
X+ to a disk file in the home directory of user
X+ .I oper.
X+ .IP
X+ .B
X+ macdump -user=oper -disk="Macintosh HD" -mac=Joe -full
X+ .PP
X+ The same disk can be dumped to tape in either of two ways:
X+ direct to the device, or via a pipe to
X+ .I dd.
X+ The latter method a bit more complex but
X+ may give better performance, as it allows
X+ .I dd
X+ to be writing one block
X+ to tape while
X+ .I macdump
X+ is filling another block over the network.
X+ To use the QIC-150 tapedrive
X+ .I rst0,
X+ .IP
X+ .B
X+ macdump -disk="Macintosh HD" -mac=Joe -full -tape -blocks=20 > /dev/rst0
X+ .PP
X+ or
X+ .IP
X+ .B
X+ macdump -disk="Macintosh HD" -mac=Joe -full -tape -blocks=20 | \\
X+ .IP
X+ .B
X+ dd ibs=20b obs=200b of=/dev/rst0
X+ .PP
X+ To verify that dump
X+ .IP
X+ .B
X+ macdump -disk="Macintosh HD" -mac=Joe -verify -blocks=20 < /dev/rst0
X+ .PP
X+ To create a disk dump file for use by
X+ .I macrestore,
X+ .IP
X+ .B
X+ dd if=/dev/rst0 ibs=200b obs=20b | macdump -disk="Macintosh HD" -mac=Joe -read
X+ .PP
X+ Note the use of double quotes to preserve the spaces in the hard disk
X+ volume name in these examples.
X  .SH SEE ALSO
X  macrestore(8), macdumpdir(8)
X  
END-of-macdump.man.diff
exit