[net.sources] Source and Documentation for 'Grab'

donn@sdchema.UUCP (Donn Seeley) (08/10/83)

#
# This is a C-shell archive for 'grab' sources.  Make a source
# directory for 'grab', change to that directory and run 'csh -f SCRIPT'
# where SCRIPT is the name of this file.
#

#-------------------------------------------------------------------
echo extracting grab.1
cat << 'EOFEOF' > grab.1
.\"	$Header:   RCS/grab.1.v  Revision 1.2  83/07/01  19:34:59  donn  Exp$
.\"	$Log:	RCS/grab.1.v $
.\"	Revision 1.2  83/07/01  19:34:59  donn
.\"	Added descriptions of '-i' and '-L' options for inode grabbing
.\"	and dumping.
.\"							Donn
.\"	
.\"
.TH GRAB 1 5/18/82
.UC 4
.SH NAME
grab \- get files from filesystems
.SH SYNOPSIS
.B grab
[ -246pxvilLtb ] filesystem [ name ... ]
.SH DESCRIPTION
.PP
.I Grab
is a program which reads Unix filesystem-format devices
and extracts named files.
.I Grab
will run on Version 6 Unix, 2.8 BSD Unix or 4.1 BSD Unix and
will read filesystems made on any of these three.
It is useful for reading filesystems that
cannot be mounted for various reasons
(e.g. there is damage,
system mountable filesystem parameter is set too low,
you are using dual-ported disks or controllers,
the filesystem is not the same type as the one
.I grab
is running on, etc.)
It can also be used as a fast disk copy program,
especially for big chunks of file systems,
since it can restore ownerships and permissions on files,
recreate links and make directories and devices.
.PP
.I Grab
needs to be supplied with the name of a filesystem format file or device,
and a list of pathnames for files on that filesystem
starting at the filesystem root.
For example if you need to extract a file
/mnt/donn/c/quote.c where /mnt is normally mounted
on /dev/xy2, then you type
.br
     grab /dev/xy2 donn/c/quote.c
.br
and this will copy the remote file into a file named
.I quote.c
in your current directory.
(Mounted devices can be listed using
.IR df (1)
or
.IR mount (8).)
If a directory is given as a name,
all the files in that directory are (recursively) extracted
into a subdirectory in the current directory
that has the same name as the remote directory.
If any files extracted are linked together
on the remote filesystem,
the copies
.I grab
makes will also be linked.
The permission information on the files is copied
except for owner, setuid, setgid and sticky information
which must be requested specifically by the
.B \-p
option.
Device nodes will be copied for the super-user.
Here are the options in detail:
.TP 8
.B  -2
The remote filesystem is a 2.8 BSD filesystem.
(Note: By default the remote filesystem
is assumed to be the same type as the host system.)
.TP 8
.B -4
The remote filesystem is a 4.1 BSD filesystem.
.TP 8
.B -6
The remote filesystem is a Version 6 filesystem.
(Note: On version 6 grab cannot consistently read filesystems
made on Version 7 that use more than 65K small blocks
since the V6 I/O system uses 16 bits to store block numbers...)
.TP 8
.B  -p
Restore owner and group, setuid, setgid and sticky bits,
and access and modification times.
Only the superuser can reliably copy owner and group.
.I Grab
will not allow protected files on remote filesystems to be read
except by the super-user.
.TP 8
.B  -v
Verbose option -- the names of copied files
are announced as they are finished being copied.
.TP 8
.B  -x
Print option -- the requested files are copied
to the standard output.
If the standard output is a file then
this is the default.
.TP 8
.B  -i
Inode option -- the arguments must be inode numbers
and the program looks at the files indicated by the given inodes.
If files are created without a name being available,
the inode number is converted into a name.
This is useful for recovering files on screwed-up filesystems
when the inode numbers are known through (say)
.IR dcheck (8).
.TP 8
.B  -l
List option -- the names of files in the requested directories are listed.
This is useful if you don't know or can't remember
the name of a file to extract.
.TP 8
.B  -L
Long listing option -- the program lists files individually
and displays useful information about them:
their name, inode number, mode word, link count, uid and gid,
access and modify times, and within-inode block numbers.
.TP 8
.B  -t
.I Tar
option -- the requested files are placed on the standard output
in a format identical to that of the
.IR tar (1)
program.
Digits following the letter
.B t
are taken to be a blocking factor for the tape;
the default blocksize is 20.
To put a Version 6 directory on tape you might type:
.br
   grab -6tv /dev/rxp0d /etc > /dev/rmt0
.TP 8
.B -b
This option allows you to set the size of the buffer
.I grab
uses for reading and writing.
Digits following the
.B b
are taken to be a buffer size in 512-byte blocks.
The default is 128 blocks on the VAX, 32 on PDPs.
Increasing the buffer size generally increases
the speed but you have to be careful not to exceed
the bounds of your machine.
.SH BUGS
Checking uids of remote files is
rather silly if the remote system
is not the same as the host system.
.sp
No warning is given about clobbering pre-existing files.
.sp
Version 6 is obsolete.
'EOFEOF'
#-------------------------------------------------------------------
echo extracting grab.1.v6
cat << 'EOFEOF' > grab.1.v6
.\"	$Header:   RCS/grab.1.v  Revision 1.2  83/07/01  19:34:59  donn  Exp$
.\"	$Log:	RCS/grab.1.v $
.\"	Revision 1.2  83/07/01  19:34:59  donn
.\"	Added descriptions of '-i' and '-L' options for inode grabbing
.\"	and dumping.
.\"							Donn
.\"	
.\"
.th GRAB I 5/18/82
.sh NAME
grab \- get files from filesystems
.sh SYNOPSIS
.bd grab
[ -246pxvilLtb ] filesystem [ name ... ]
.sh DESCRIPTION
.it Grab
is a program which reads Unix filesystem-format devices
and extracts named files.
.it Grab
will run on Version 6 Unix, 2.8 BSD Unix or 4.1 BSD Unix and
will read filesystems made on any of these three.
It is useful for reading filesystems that
cannot be mounted for various reasons
(e.g. there is damage,
system mountable filesystem parameter is set too low,
you are using dual-ported disks or controllers,
the filesystem is not the same type as the one
.it grab
is running on, etc.)
It can also be used as a fast disk copy program,
especially for big chunks of file systems,
since it can restore ownerships and permissions on files,
recreate links and make directories and devices.
.s3
.it Grab
needs to be supplied with the name of a filesystem format file or device,
and a list of pathnames for files on that filesystem
starting at the filesystem root.
For example if you need to extract a file
/mnt/donn/c/quote.c where /mnt is normally mounted
on /dev/xy2, then you type
.br
     grab /dev/xy2 donn/c/quote.c
.br
and this will copy the remote file into a file named
.it quote.c
in your current directory.
(Mounted devices can be listed using
.it df(I)
or
.it mount(VIII).)
If a directory is given as a name,
all the files in that directory are (recursively) extracted
into a subdirectory in the current directory
that has the same name as the remote directory.
If any files extracted are linked together
on the remote filesystem,
the copies
.it grab
makes will also be linked.
The permission information on the files is copied
except for owner, setuid, setgid and sticky information
which must be requested specifically by the
.bd \-p
option.
Device nodes will be copied for the super-user.
Here are the options in detail:
.s3
.lp +4 4
\fB-2\fR	The remote filesystem is a 2.8 BSD filesystem.
(Note: By default the remote filesystem
is assumed to be the same type as the host system.)
.s3
.lp +4 4
\fB-4\fR	The remote filesystem is a 4.1 BSD filesystem.
.s3
.lp +4 4
\fB-6\fR	The remote filesystem is a Version 6 filesystem.
(Note: On version 6 grab cannot consistently read filesystems
made on Version 7 that use more than 65K small blocks
since the V6 I/O system uses 16 bits to store block numbers...)
.s3
.lp +4 4
\fB-p\fR	Restore owner and group, setuid, setgid and sticky bits,
and access and modification times.
Only the superuser can reliably copy owner and group.
.it Grab
will not allow protected files on remote filesystems to be read
except by the super-user.
.s3
.lp +4 4
\fB-v\fR	Verbose option -- the names of copied files
are announced as they are finished being copied.
.s3
.lp +4 4
\fB-x\fR	Print option -- the requested files are copied
to the standard output.
If the standard output is a file then
this is the default.
.s3
.lp +4 4
\fB-i\fR	Inode option -- the arguments must be inode numbers
and the program looks at the files indicated by the given inodes.
If files are created without a name being available,
the inode number is converted into a name.
This is useful for recovering files on screwed-up filesystems
when the inode numbers are known through (say)
.it dcheck(VIII).
.s3
.lp +4 4
\fB-l\fR	List option -- the names of files in the requested directories are listed.
This is useful if you don't know or can't remember
the name of a file to extract.
.s3
.lp +4 4
\fB-L\fR	Long listing option -- the program lists files individually
and displays useful information about them:
their name, inode number, mode word, link count, uid and gid,
access and modify times, and within-inode block numbers.
.s3
.lp +4 4
\fB-t\fR	\fITar\fR
option -- the requested files are placed on the standard output
in a format identical to that of the
.it tar(I)
program.
Digits following the letter
.bd t
are taken to be a blocking factor for the tape;
the default blocksize is 20.
To put a Version 6 directory on tape you might type:
.br
   grab -6tv /dev/rxp0d /etc > /dev/rmt0
.s3
.lp +4 4
\fB-b\fR	This option allows you to set the size of the buffer
.it grab
uses for reading and writing.
Digits following the
.bd b
are taken to be a buffer size in 512-byte blocks.
The default is 128 blocks on the VAX, 32 on PDPs.
Increasing the buffer size generally increases
the speed but you have to be careful not to exceed
the bounds of your machine.
.i0
.sh BUGS
Checking uids of remote files is
rather silly if the remote system
is not the same as the host system.
.s3
No warning is given about clobbering pre-existing files.
.s3
Version 6 is obsolete.
'EOFEOF'
#-------------------------------------------------------------------
echo extracting Makefile.2.8
cat << 'EOFEOF' > Makefile.2.8
#
# Makefile for grab
# 2.8 BSD
#
#----------------------------------------------------------------------------
#
# $Header$
# $Log$
#

CFLAGS=-O -DPDP
OFILES=grab.o find.o readi.o bread.o tmode.o
LIBES=

grab:	${OFILES}
	${CC} ${OFILES} ${LIBES} -o grab
'EOFEOF'
#-------------------------------------------------------------------
echo extracting Makefile.4.1
cat << 'EOFEOF' > Makefile.4.1
#
# Makefile for grab
# 4.1 BSD
#
#----------------------------------------------------------------------------
#
# $Header$
# $Log$
#

CFLAGS=-O -DVAX -DCLR_SETUID -DMPXFILES
OFILES=grab.o find.o readi.o bread.o tmode.o
LIBES=

grab:	${OFILES}
	${CC} ${OFILES} ${LIBES} -o grab
'EOFEOF'
#-------------------------------------------------------------------
echo extracting Makefile.4.1a
cat << 'EOFEOF' > Makefile.4.1a
#
# Makefile for grab
# 4.1a BSD
#
#----------------------------------------------------------------------------
#
# $Header$
# $Log$
#

CFLAGS=-O -DVAX -DCLR_SETUID -DSYMLINKS
OFILES=grab.o find.o readi.o bread.o tmode.o
LIBES=

grab:	${OFILES}
	${CC} ${OFILES} ${LIBES} -o grab
'EOFEOF'
#-------------------------------------------------------------------
echo extracting Makefile.v6
cat << 'EOFEOF' > Makefile.v6
#
# Makefile for grab
# Version 6 Unix
#
#----------------------------------------------------------------------------
#
# $Header$
# $Log$
#

CFLAGS=-O -DPDPV6
OFILES=grab.o find.o readi.o bread.o tmode.o
LIBES=-lS

grab:	${OFILES}
	${CC} ${OFILES} ${LIBES} -o grab
'EOFEOF'
#-------------------------------------------------------------------
echo extracting grab.h
cat << 'EOFEOF' > grab.h
/*
 * grab.h
 *
 * Include file for grab -- contains structure definitions,
 *	external definitions.
 *
 *---------------------------------------------------------------------------
 *
 * $Header:   RCS/grab.h.v  Revision 1.2  83/07/01  19:41:34  donn  Exp$
 * $Log:	RCS/grab.h.v $
 * Revision 1.2  83/07/01  19:41:34  donn
 * Added stuff to support '-i' and '-L' flags.
 * 							Donn
 * 
 */

# include	<stdio.h>



extern int	target_system;	/* Type of file system to be read */
extern int	fsbsiz;		/* File system block size */
extern int	fsys;		/* File descriptor for file system */
extern int	pflag;		/* 1 if file protections are restored */
extern int	xflag;		/* 1 if output goes to stdout */
extern int	vflag;		/* 1 for verifying files during copy */
extern int	iflag;		/* 1 for grabbing by inode */
extern int	lflag;		/* >=1 for listing files in a directory */
extern int	Lflag;		/* 1 for long listings of files */
extern int	tflag;		/* 1 for producing "tar" output */
extern int	nblock;		/* "Tar" output blocking factor */

# define	V7_2BSD		0
# define	V7_4BSD		1
# define	V6		2


/*
 * Default settings for host system.
 * Set PDPV6 for V6, PDP for V7_2BSD and VAX for V7_4BSD.
 */
# ifndef	VAX
# ifndef	PDP

# define	PDPV6		1

# endif
# endif


# ifdef PDPV6

# define	PDP		1

# define	TARGET_DEFAULT	V6
# define	FSB_DEFAULT	512

# define	geteuid()	((getuid() >> 8) & 0xff)
# define	getegid()	((getgid() >> 8) & 0xff)

# define	CHOWN(s,u,g)	chown( (s), ((g)<<8) | ((u)&0xff) )

# define	CONVMODE(m)	((m) & V6_IFMT)

# else

# ifdef		VAX
# define	TARGET_DEFAULT	V7_4BSD
# else
# define	TARGET_DEFAULT	V7_2BSD
# endif

# define	FSB_DEFAULT	1024

# define	CHOWN		chown

# define	CONVMODE(m)	((m)&(target_system==V6?V6_IFMT:V7_IFMT))

# endif PDPV6



/*
 * Generic inode structure.
 * DEFICIENCY: this structure is used to recover the mode word
 *	from a "stat" call...
 */
struct inode
{
	long	i_size;
	short	i_mode;
	short	i_nlink;
	short	i_uid;
	short	i_gid;
	long	i_addr[13];
	long	i_atime;
	long	i_mtime;
};



/*
 * Version 7 disk inode.
 * Kluge for 2.8's 1/2 size inodes added.
 */

# define	UCB_SMINO

# ifdef UCB_SMINO
# define	V7NADDR		(target_system==V7_2BSD?7:13)
# else
# define	V7NADDR		13
# endif

struct v7dinode
{
	short	v7di_mode;		/* mode and type of file */
	short	v7di_nlink;		/* number of links to file */
	short	v7di_uid;		/* owner's user id */
	short	v7di_gid;		/* owner's group id */
	long	v7di_size;		/* number of bytes in file */
	char	v7di_addr[40];		/* disk block addresses */
	long	v7di_atime;		/* time last accessed */
	long	v7di_mtime;		/* time last modified */
	long	v7di_ctime;		/* time created */
};


/*
 * Version 6 disk inode.
 */

# define	V6NADDR		8

struct v6dinode
{
	short	v6di_mode;		/* flags */
	char	v6di_nlink;		/* number of links to file */
	char	v6di_uid;		/* user ID of owner */
	char	v6di_gid;		/* group ID of owner */
	char	v6di_sz0;		/* high byte of 24-bit size */
	short	v6di_sz1;		/* low word of 24-bit size */
	short	v6di_addr[8];		/* block numbers or device numbers */
	long	v6di_atime;		/* time of last access */
	long	v6di_mtime;		/* time of last modification */
};



/*
 * Mode bits.
 * It is assumed that the protection bits 0-11 are the same on all systems.
 */
#define	V7_IFMT		0170000		/* type of file */
#define		V7_IFCHR	0020000	/* character special */
#define		V7_IFBLK	0060000	/* block special */
#define		V7_IFMPC	0030000	/* multiplex char special */
#define		V7_IFMPB	0070000	/* multiplex blk special */
#define		V7_IFDIR	0040000	/* directory */
#define		V7_IFLNK	0120000	/* symbolic link */
#define		V7_IFREG	0100000	/* regular */
#define V6_IFMT		0060000		/* type of file */
#define		V6_IFCHR	0020000	/* character special */
#define		V6_IFBLK	0060000	/* block special */
#define		V6_IFDIR	0040000	/* directory */
#define		V6_IFREG	0000000	/* regular */
#define	V6_ILARGE	0010000		/* large file */
#define	ISUID		0004000		/* setuid */
#define	ISGID		0002000		/* setgid */
#define	ISVTX		0001000		/* sticky */



/*
 * Block map structure.  Used to keep track of a sequential read
 * through the blocks of an inode.
 */
struct bmap
{
	short	b_type;		/* Type of block: NORMAL/HOLE */
	short	b_mode;		/* File mode */
	long	b_size;		/* File size */
	long	b_offset;	/* Offset within file (bytes) */
	int	b_len;		/* Size of buffer */
	int	b_cc;		/* Buffer character count */
	long	b_lbno;		/* Logical block number in file */
	long   *b_iaddr;	/* Block numbers in an inode */
	char   *b_data;		/* Contents of current buffer */
	char   *b_indir[3];	/* Contents of indirect blocks */
	int	b_count[3];	/* Index into an indirect block */
};


/*
 * Block types, buffer types and size.
 */
# define	B_NORMAL	0
# define	B_HOLE		1
# define	B_SMALL		0
# define	B_BIG		1
# ifdef VAX
# define	B_BUFSIZE	128
# else	PDP
# define	B_BUFSIZE	32
# endif


/*
 * Size of a disk sector.
 */
# define	DBSIZE		512


/*
 * Default blocking factor for "tar"-style output, and other "tar" stuff.
 */
# define	TAR_BS_DEFAULT	20
# define	TBLOCK		512
# define	NAMSIZ		100

/*
 * Tar header structure.
 */
union hblock {
	char dummy[TBLOCK];
	struct header {
		char name[NAMSIZ];
		char mode[8];
		char uid[8];
		char gid[8];
		char size[12];
		char mtime[12];
		char chksum[8];
		char linkflag;
		char linkname[NAMSIZ];
	} dbuf;
};



/*
 * CLRBUF(a,b) -- Zero out b bytes starting at a.
 * CPYBUF(a,b,c) -- Copy c bytes into a from b.
 *	These are just experiments to make use of the SOB instructions...
 *	The counts are left at 0, the pointers end up at the end of the data.
 */
# ifdef VAX

# define CLRBUF(a,b)	if (b>0) { do *a++ = '\0'; while ( --b > 0 ); }
# define CPYBUF(a,b,c)	if (c>0) { do *a++ = *b++; while ( --c > 0 ); }

# else

# define CLRBUF(a,b)	if (b>0) { do *a++ = '\0'; while ( --b ); }
# define CPYBUF(a,b,c)	if (c>0) { do *a++ = *b++; while ( --c ); }

# endif



/*
 * Macros for decoding block and inode numbers.
 * DEFICIENCY: This uses on the fact that there are just as many
 *	inodes per block under V6 as under V7 with big blocks.
 */
# define	INOPB		16
# define	fsbtodb(n)	(target_system==V6?(n):(n)<<1)
# define	itod(n)		((long)((((unsigned short)(n)+2*INOPB-1)/INOPB)))
# define	itoo(n)		((int)(((unsigned short)(n)+2*INOPB-1)%INOPB))



/*
 * Form of directory entries.
 */
#define	DIRSIZ	14
#define ROOTINO	(target_system==V6?1:2)

struct	direct
{
	unsigned short	d_ino;
	char		d_name[DIRSIZ];
};


/*
 * Miscellaneous.
 */
# define	min(a,b)	((a)<(b)?(a):(b))
# define	max(a,b)	((a)>(b)?(a):(b))
# define	STDOUT		1
# define	TFILE		STDOUT
'EOFEOF'
#-------------------------------------------------------------------
echo extracting bread.c
cat << 'EOFEOF' > bread.c
/*
 * bread.c
 *
 * Routines to sequentially retrieve blocks from a Unix file.
 *
 *---------------------------------------------------------------------------
 *
 * $Header:   RCS/bread.c.v  Revision 1.3  83/08/01  17:05:47  donn  Exp$
 * $Log:	RCS/bread.c.v $
 * Revision 1.3  83/08/01  17:05:47  donn
 * Fixed bug that led to extra zero-filled blocks in tar output,
 * ended up simplifying main loop of bput.
 * 							Donn
 * 
 * Revision 1.2  83/07/01  17:30:24  donn
 * Changed idump to make it usable with new -L and -I options -- it's much
 * more helpful now, and cleaner.
 * 							Donn
 * 
 */

# include	"grab.h"



/*
 * bopen( name, ip, bp ) -- Prepare to output a file called name, using
 *	inode ip to allocate a bmap bp.
 */
int
bopen( name, ip, bp )
    char                  *name;
    register struct inode *ip;
    register struct bmap  *bp;
{
	int	ofile;

	if ( xflag )
		/*
		 * Send file to the standard output.
		 */
		ofile	= STDOUT;
	else if ( tflag ) {
		/*
		 * Put out a "tar"-style header record.
		 */
		theader( TFILE, name, ip );
		ofile	= TFILE;
	} else {
		/*
		 * Create a file to copy into.
		 */
		ofile	= creat( name, ip->i_mode & (pflag ? 07777 : 0777) );
		if ( ofile < 0 ) {
			fprintf( stderr, "grab: can't create %s\n", name );
			return ( -1 );
		}
		if ( pflag )
			CHOWN( name, ip->i_uid, ip->i_gid );
	}

	allocbuf( ip, bp, B_BIG );

	return ( ofile );
}



/*
 * bread( bp ) -- Fill the buffer of bmap bp with blocks from its file.
 */
bread( bp )
    register struct bmap *bp;
{
	register struct bstr
	    {
		long	bn_bno;
		int	bn_index;
	    }	
		       *bb;
	long		bno;
	long		v7bnext(), v6bnext();
	register int	i;
	int		nbytes, nblocks;
	int		bcomp();
	char	       *start;
	char	       *malloc();

	/*
	 * End of file?  Buffer wraparound?
	 */
	if ( bp->b_offset >= bp->b_size )
		return ( 0 );
	if ( bp->b_cc >= bp->b_len )
		bp->b_cc	= 0;

	/*
	 * A minor optimization: blocks are sorted by block number
	 * before reading.  Here we figure out how many blocks to read and
	 * allocate a buffer to hold the block numbers for sorting.
	 */
	nbytes		= min( bp->b_size-bp->b_offset, bp->b_len-bp->b_cc );
	nblocks		= (nbytes + (fsbsiz-1)) / fsbsiz;
	bb		= (struct bstr *) malloc( nblocks * sizeof (struct bstr) );
	if ( bb == NULL ) {
		fprintf( stderr, "grab: Out of memory\n" );
		exit( 12 );
	}

	/*
	 * Find out which file system block(s) to read in.
	 */
	for ( i = 0; i < nblocks; ++i ) {
		switch ( target_system ) {
		case V7_2BSD:
		case V7_4BSD:
			bno	= v7bnext( bp );
			break;
		case V6:
			bno	= v6bnext( bp );
			break;
		}
		if ( bno == 0 )
			/*
			 * A hole -- stop here and let bput do the work.
			 */
			break;
		bb[i].bn_index	= i;
		bb[i].bn_bno	= bno;
	}
	nblocks		= i;
	nbytes		= i * fsbsiz;

	/*
	 * Sort the block numbers.
	 */
	qsort( bb, nblocks, sizeof (struct bstr), bcomp );

	/*
	 * Read the blocks off, in order.
	 */
	start		= bp->b_data + bp->b_cc;
	for ( i = 0; i < nblocks; ++i ) {
		lseek( fsys, fsbtodb( bb[i].bn_bno ) * DBSIZE, 0 );
		if ( read( fsys, start + (bb[i].bn_index * fsbsiz), fsbsiz ) != fsbsiz ) {
			fprintf( stderr, "grab: error reading filesystem\n" );
			exit( 10 );
		}
	}
	free( bb );

	/*
	 * Update the bmap.  Pad out holes.
	 */
	if ( bno == 0L ) {
		bp->b_type	= B_HOLE;
		nbytes		+= fsbsiz;
	} else
		bp->b_type	= B_NORMAL;
	bp->b_cc	+= nbytes;
	bp->b_offset	+= nbytes;
# ifdef	DEBUG
	bdump( bp );
# endif
	return ( nbytes );
}



/*
 * bcomp( b1, b2 ) -- Compare block numbers in bstr's b1 and b2.
 */
bcomp( b1, b2 )
    struct bstr
    {
	long	bn_bno;
	int	bn_index;
    }
	*b1, *b2;
{
	if ( b1->bn_bno < b2->bn_bno )
		return( -1 );
	return( b1->bn_bno > b2->bn_bno );
}



/*
 * bput( ofile, bp ) -- Do a buffered transfer to ofile using data in bp.
 */
bput( ofile, bp )
    int                   ofile;
    register struct bmap *bp;
{
	register int	n;
	register char  *cp;
	register char  *dp;
	long		count;
	int		err;
	int		saveblen;
	long		saveboffset;

# ifdef	DEBUG
	fprintf( stderr, "Entering bput\n" );
# endif
	/*
	 * Special case for holes in files...  Bleah.
	 */
	if ( bp->b_type == B_HOLE )
		if ( tflag || xflag ) {
			/*
			 * Deal with tape output.  In this case we need to zero
			 * out the last block brought in since holes in tape are
			 * inconvenient (to say the least)...
			 */
			cp		= bp->b_data+(bp->b_cc-fsbsiz);
			n		= fsbsiz;

			CLRBUF( cp, n );
		} else {
			/*
			 * Regular files need holes made in them.  Force all
			 * current buffered data out now & pick up again later.
			 * Note that b_offset, b_cc are >= fsbsiz after bread.
			 */
			saveblen	= bp->b_len;
			saveboffset	= bp->b_offset;
			bp->b_offset	-= fsbsiz;
			bp->b_len	= bp->b_cc - fsbsiz;
			bp->b_cc	= bp->b_len;
		}

	/*
	 * Output the buffer, if necessary.
	 */
	cp		= bp->b_data;
	dp		= bp->b_data + bp->b_len;
	n		= bp->b_cc - bp->b_len;
	if ( bp->b_size < bp->b_offset )
		/*
		 * Correct the count for overshooting.
		 */
		count		= bp->b_cc - (bp->b_offset - bp->b_size);
	else
		count		= bp->b_cc;

	while ( count > 0 ) {
		if ( count < bp->b_len ) {
			if ( tflag ) {
				/*
				 * Only write full buffers.
				 */
				break;
			}
			err	= write( ofile, bp->b_data, (int) count );
			count		= 0;
		} else {
			err	= write( ofile, bp->b_data, bp->b_len );
			count		-= bp->b_len;
		}
		if ( err < 0 ) {
			fprintf( stderr, "grab: write error\n" );
			exit( 11 );
		}
		if ( count > 0 )
			CPYBUF( cp, dp, n );
	}
	bp->b_cc	= count;

	/*
	 * Finish dirty work with holes.
	 */
	if ( bp->b_type == B_HOLE && ! (tflag || xflag) ) {
		lseek( ofile, min( (long) fsbsiz, bp->b_size-bp->b_offset ), 1 );
		bp->b_offset	= saveboffset;
		bp->b_len	= saveblen;
	}
}



/*
 * bclose( ofile, name, ip, bp ) -- Wrap up a file named name with output
 *	channel ofile and inode ip and deallocate the buffers of its bmap bp.
 */
bclose( ofile, name, ip, bp )
    int			   ofile;
    char		  *name;
    register struct inode *ip;
    register struct bmap  *bp;
{
	freebuf( bp );

	if ( ! xflag && ! tflag ) {
		close( ofile );
# ifdef CLR_SETUID
		if ( pflag && (ip->i_mode & (ISUID|ISGID|ISVTX)) )
			chmod( name, ip->i_mode & 07777 );
# endif CLR_SETUID
# ifndef PDPV6
		if ( pflag )
			utime( name, &ip->i_atime );
# endif
	}

	if ( vflag ) {
		printf( "%s\n", name );
		fflush( stdout );
	}
}



/*
 * v7bnext( bp ) -- get the next file system block from the list
 *	associated with bp.  [Version 7]
 */
long
v7bnext( bp )
    register struct bmap *bp;
{
	long	lbn = bp->b_lbno++;

	/*
	 * Blocks 0 thru V7NADDR-4 are direct blocks.
	 */
	if ( lbn < V7NADDR - 3 )
		return ( bp->b_iaddr[lbn] );

	/*
	 * An indirect block is needed.
	 */
	++bp->b_count[0];
	v7indir( bp, 0 );
	return ( ((long *) bp->b_indir[0]) [ bp->b_count[0] ] );
}



/*
 * v7indir( bp, level ) -- make sure that the needed block number is
 *	available from the indirect blocks.
 */
v7indir( bp, level )
    register struct bmap   *bp;
    register int            level;
{
	long	bno;
	char   *malloc();

	if ( level >= 3 ) {
		/*
		 * Max. three levels of indirection!
		 */
		fprintf( stderr, "grab: file overflow\n" );
		exit( 12 );
	}

	/*
	 * Has an indirect block of this level been looked at yet?
	 */
	if ( bp->b_indir[level] == NULL ) {
		bp->b_indir[level]	= malloc( fsbsiz );
		if ( bp->b_indir[level] == NULL ) {
			fprintf( stderr, "grab: out of memory\n" );
			exit( 13 );
		}
		bno	= bp->b_iaddr[(V7NADDR-3) + level];
	}

	/*
	 * Have we looked at all the blocks in this indirect block?
	 */
	else
	if ( bp->b_count[level] >= fsbsiz / sizeof (long) ) {
		++bp->b_count[level + 1];
		v7indir( bp, level + 1 );
		bno	= ((long *) bp->b_indir[level+1]) [ bp->b_count[level+1] ];
	}

	/*
	 * This indirect block is still valid.
	 */
	else
		return;

	/*
	 * We need to read in a new indirect block.
	 */
	lseek( fsys, fsbtodb( bno ) * DBSIZE, 0 );
	if ( read( fsys, bp->b_indir[level], fsbsiz ) < 0 ) {
		fprintf( stderr, "grab: error reading filesystem\n" );
		exit( 14 );
	}
	bp->b_count[level]	= 0;

	/*
	 * Swap words if necessary.
	 */
	switch ( target_system ) {
	case V7_2BSD:
# ifndef PDP
		wswap( bp->b_indir[level], fsbsiz / sizeof (long) );
# endif
		break;
	case V7_4BSD:
# ifdef PDP
		wswap( bp->b_indir[level], fsbsiz / sizeof (long) );
# endif
		break;
	}
}




/*
 * v6bnext( bp ) -- get the next file system block number from the list
 *	associated with bp.  [Version 6]
 */
long
v6bnext( bp )
    register struct bmap *bp;
{
	long	lbn = bp->b_lbno++;

	/*
	 * If the file has small format then we use the direct blocks.
	 */
	if ( (bp->b_mode & V6_ILARGE) == 0 ) {
		if ( lbn >= 8 ) {
			fprintf( stderr, "grab: V6 small format file too big\n" );
			return ( 0 );
		}
		return ( (unsigned short) bp->b_iaddr[lbn] );
	}

	/*
	 * An indirect block is needed.
	 */
	++bp->b_count[0];
	v6indir( bp, 0 );
	return ( ((unsigned short *) bp->b_indir[0]) [ bp->b_count[0] ] );
}



/*
 * v6indir( bp, level ) -- make sure that the needed block number is
 *	available from the indirect blocks.
 */
v6indir( bp, level )
    register struct bmap   *bp;
    register int            level;
{
	long	bno;
	char   *malloc();

	if ( level >= 2 ) {
		fprintf( stderr, "grab: file overflow\n" );
		exit( 15 );
	}

	/*
	 * Has an indirect block of this level been looked at yet?
	 */
	if ( bp->b_indir[level] == NULL ) {
		bp->b_indir[level]	= malloc( fsbsiz );
		if ( bp->b_indir[level] == NULL ) {
			fprintf( stderr, "grab: out of memory\n" );
			exit( 16 );
		}
		bno	= (unsigned short) bp->b_iaddr[ level == 0 ? 0 : 7 ];
	}

	/*
	 * Have we looked at all the blocks in this indirect block?
	 */
	else
	if ( bp->b_count[level] >= fsbsiz / sizeof (short) ) {
		register int	n = bp->b_lbno / (fsbsiz / sizeof(short));
		if ( n < 7 )
			/*
			 * One of 7 singly indirect blocks in the inode.
			 */
			bno	= (unsigned short) bp->b_iaddr[ n ];
		else {
			++bp->b_count[level + 1];
			v6indir( bp, level + 1 );
			bno	= ((unsigned short *) bp->b_indir[level+1]) [ bp->b_count[level+1] ];
		}
	}

	/*
	 * This indirect block is still valid.
	 */
	else
		return;

	/*
	 * We need to read in a new indirect block.
	 */
	lseek( fsys, fsbtodb( bno ) * DBSIZE, 0 );
	if ( read( fsys, bp->b_indir[level], fsbsiz ) < 0 ) {
		fprintf( stderr, "grab: error reading file system\n" );
		exit( 17 );
	}
	bp->b_count[level]	= 0;
}



/*
 * allocbuf( ip, bp, flag ) -- associate a block map structure bp and a buffer
 *	with ip, using flag to determine buffering mode.  Savetcc ought to be
 *	known only here but actually tflush uses it for a kluge...  Sigh.
 */

char   *bigbuf	= NULL;
int	savetcc	= 0;

allocbuf( ip, bp, flag )
    register struct inode *ip;
    register struct bmap  *bp;
    int		           flag;
{
	char	       *malloc();

	bp->b_type	= B_NORMAL;
	bp->b_mode	= ip->i_mode;
	bp->b_size	= ip->i_size;
	bp->b_offset	= 0L;
	bp->b_iaddr	= ip->i_addr;
	bp->b_lbno	= 0L;
	if ( flag == B_SMALL ) {
		bp->b_len	= fsbsiz;
		bp->b_cc	= 0;
		bp->b_data	= malloc( fsbsiz );
		if ( bp->b_data == NULL ) {
			fprintf( stderr, "grab: out of memory\n" );
			exit( 18 );
		}
	} else {
		bp->b_len	= nblock * TBLOCK;
		if ( tflag ) {
			/*
			 * Make sure to get tail end of last file in buffer.
			 * Also round up size to nearest TBLOCK.
			 */
			bp->b_size	= ((bp->b_size + (TBLOCK-1))/TBLOCK) * TBLOCK;
			bp->b_cc	= savetcc;
		} else
			bp->b_cc	= 0;
		if ( bigbuf == NULL ) {
			/*
			 * Allocate a large data buffer.  Leave enough slop for
			 * odd-numbered blocksize outputs so that input doesn't
			 * get truncated...
			 */
			bigbuf		= malloc( bp->b_len + fsbsiz );
			if ( bigbuf == NULL ) {
				fprintf( stderr, "grab: out of memory\n" );
				exit( 19 );
			}
		}
		bp->b_data	= bigbuf;
	}
	bp->b_indir[0]	= NULL;
	bp->b_indir[1]	= NULL;
	bp->b_indir[2]	= NULL;
	bp->b_count[0]	= -1;
	bp->b_count[1]	= -1;
	bp->b_count[2]	= -1;
}



/*
 * freebuf( bp ) -- release all the data and indirect block buffers
 *	that are tied up with bp.  Don't reallocate a big data buffer.
 */
freebuf( bp )
    register struct bmap *bp;
{
	register int	n;

	if ( bp->b_data == bigbuf )
		savetcc	= bp->b_cc;
	else if ( bp->b_data != NULL )
		free( bp->b_data );
	for ( n = 0; n < 3; ++n )
		if ( bp->b_indir[n] != NULL )
			free( bp->b_indir[n] );
}



/*
 * Some debugging aids.  Idump() is also used for -I and -L options.
 */


/*
 * bdump( bp ) -- Print out the printable contents of bmap bp.
 */
bdump( bp )
    register struct bmap *bp;
{
	fprintf(stderr, "type=%d, mode=%o, b_size=%D, b_offset=%D\n",
		bp->b_type, (unsigned) bp->b_mode, bp->b_size, bp->b_offset);
	fprintf(stderr, "cc=%d, len=%d, lbno=%D, data=%o\n",
		bp->b_cc, bp->b_len, bp->b_lbno, bp->b_data );
}



/*
 * ddump( dp ) -- Print out the contents of directory entry dp.
 */
ddump( dp )
    struct direct *dp;
{
	fprintf( stderr, "--%d\t%s\n", (unsigned) dp->d_ino, dp->d_name );
}



/*
 * idump( ip, ino, name ) -- Print out the contents of inode ip with i-number
 *	ino and name name.
 */
idump( ip, ino, name )
   struct inode *ip;
   int		 ino;
   char		*name;
{
	register int	n;

	if ( name != NULL )
		fprintf( stderr, "Name:\t\t%.14s\n", name );
	if ( ino != 0 )
		fprintf( stderr, "Inode number:\t%u\n", ino );
	fprintf( stderr, "\tMode:\t\t%o\n", (unsigned short) ip->i_mode );
	fprintf( stderr, "\tSize:\t\t%D\n", ip->i_size );
	fprintf( stderr, "\tLink count:\t%d\n", ip->i_nlink );
	fprintf( stderr, "\tUid:\t\t%d\n", ip->i_uid );
	fprintf( stderr, "\tGid:\t\t%d\n", ip->i_gid );
	fprintf( stderr, "\tAccess time:\t%s", ctime( &(ip->i_atime) ) );
	fprintf( stderr, "\tModify time:\t%s", ctime( &(ip->i_mtime) ) );
	fprintf( stderr, "\tBlock numbers:" );
	for ( n = 0; n < (target_system == V6 ? V6NADDR : V7NADDR); ++n ) {
		if ( n % 4 == 0 )
			fprintf( stderr, "\n\t\t" );
		fprintf( stderr, "%10D  ", ip->i_addr[n] );
	}
	fprintf( stderr, "\n\n" );
}
'EOFEOF'
#-------------------------------------------------------------------
echo extracting find.c
cat << 'EOFEOF' > find.c
/*
 * find.c
 *
 * Routines for descending the directory tree of a pathname.
 *
 *---------------------------------------------------------------------------
 *
 * $Header$
 * $Log$
 */

# include	"grab.h"



/*
 * tail( path ) -- return pointer to the terminal name of path.
 */
char *
tail( path )
    register char *path;
{
	register char *name	= path;

	while ( *path != '\0' )
		if ( *path++ == '/' )
			name	= path;
	return ( name );
}



/*
 * find( path ) -- return inode number corresponding to path.
 */
int
find( path )
    char   *path;
{
	register char  *p, *q;
	char		tmp;
	int		ino;
	struct inode	i;

	if ( path == NULL || *path == '\0' ) {
		fprintf( stderr, "grab: null filename\n" );
		return ( 0 );
	}
	p	= path;

	/*
	 * Fetch the root inode information from the disk.
	 */
	geti( ROOTINO, &i );

	/*
	 * Search the directories in the path for
	 * each pathname element in turn.
	 */
	while ( *p != '\0' ) {

		/*
		 * Find and separate out a pathname element.
		 */
		while ( *p == '/' )
			++p;
		q	= p;
		while ( *q != '/' && *q != '\0' )
			++q;
		tmp	= *q;
		*q	= '\0';

		/*
		 * Look in the directory.
		 */
		if ( ! isdir( &i ) ) {
			fprintf( stderr, "grab: bad directory in %s\n", path );
			return ( 0 );
		}

		if ( (ino = dlook( p, path, &i )) == 0 )
			return ( 0 );

		if ( tmp == '\0' )
			/*
			 * Found the terminal element.
			 */
			break;
		geti( ino, &i );
		*q	= tmp;
		p	= q;
	}

	return( ino );
}



/*
 * dlook( name, path, ip ) -- look for name in directory corresponding to the
 *	inode whose incore structure is pointed to by ip.  Path is used for
 *	error reporting.
 */
int
dlook( name, path,  ip )
    char           *name;
    char           *path;
    struct inode   *ip;
{
	register int		j, k;
	int			n, dpb, ino;
	register struct direct *dp;
	struct bmap		b;

	if ( ! chkaccess( ip ) ) {
		fprintf( stderr, "grab: %s read-protected\n", path );
		return ( 0 );
	}

	ino	= 0;
	n	= ip->i_size / sizeof (struct direct);
	dpb	= fsbsiz / sizeof (struct direct);

	allocbuf( ip, &b, B_SMALL );

	for ( j = 0; j < n; ++j ) {
		k	= j % dpb;
		if ( k == 0 )
			bread( &b );
		dp	= &((struct direct *) b.b_data)[ k ];
		if ( dp->d_ino == 0 )
			continue;
		if ( strncmp( name, dp->d_name, DIRSIZ ) == 0 ) {
			ino	= dp->d_ino;
			break;
		}
	}

	freebuf( &b );

	/*
	 * Did we find it?
	 */
	if ( ino == 0 )
		fprintf( stderr, "grab: %s not found\n", path );

	return ( ino );
}



/*
 * isreg( ip ) -- return true iff ip's inode corresponds to a regular file.
 */
int
isreg( ip )
    struct inode *ip;
{
	switch ( target_system ) {
	case V7_2BSD:
	case V7_4BSD:
		return ( (ip->i_mode & V7_IFMT) == V7_IFREG );
	case V6:
		return ( (ip->i_mode & V6_IFMT) == V6_IFREG );
	}
}




/*
 * isdir( ip ) -- return true iff ip's inode corresponds to a directory file.
 */
int
isdir( ip )
    struct inode *ip;
{
	switch ( target_system ) {
	case V7_2BSD:
	case V7_4BSD:
		return ( (ip->i_mode & V7_IFMT) == V7_IFDIR );
	case V6:
		return ( (ip->i_mode & V6_IFMT) == V6_IFDIR );
	}
}



/*
 * chkaccess( ip ) -- check permissions on readability of the given
 *	inode on the foreign system.  Return 1 if readable.
 */
int
chkaccess( ip )
    struct inode *ip;
{
	static int	uid = -1, gid = -1;
	int		iuid, igid, mode, ckmode;

	if ( uid == -1 ) {
		uid	= geteuid();
		gid	= getegid();
	}
	iuid	= ip->i_uid;
	igid	= ip->i_gid;

	if ( uid == 0 )
		return ( 1 );
	
	if ( isdir( ip ) )
		ckmode	= 05;		/* Read and execute on a directory */
	else
		ckmode	= 04;		/* Read on regular files */
	mode	= ip->i_mode & 0777;

	if ( uid == iuid && ((mode >> 6) & ckmode) == ckmode )
		return ( 1 );
	if ( gid == igid && ((mode >> 3) & ckmode) == ckmode )
		return ( 1 );
	if ( (mode & ckmode) == ckmode )
		return ( 1 );
	return ( 0 );
}
'EOFEOF'
#-------------------------------------------------------------------
echo extracting grab.c
cat << 'EOFEOF' > grab.c
/*
 * grab.c
 *
 * Name:	grab
 * Purpose:	extract files from remote filesystems
 * Usage:	grab [-246pxvilLtb#] <filesystem> <files...>
 * Environment:	V6 or V7 (2.8BSD, 4.1BSD) Unix
 * Compile:	cc -O -s -D<type> grab.c find.c readi.c bread.c tmode.c -o grab
 * Date:	4/29/82
 * Author:	Donn Seeley RRCF UCSD
 * Remarks:
 *	This is meant to be used with dual-ported disks or controllers
 *	when it is either impossible or undesirable to mount another
 *	file system on that medium.  It should allow any Version 6 or
 *	Version 7 (2.8BSD, 4.1BSD) system to read filesystems from any
 *	Version 6 or Version 7 (2.8BSD, 4.1BSD) system even if the systems
 *	are not of the same type.  Version 6 systems will need at least
 *	a Phototypesetter level C compiler to make "grab".  "Grab" will
 *	copy any conceivable (?) object on a filesystem including directories
 *	(recursively), links, file holes, setuid/setgid/sticky files and
 *	device nodes.  "Grab" tries to prevent unauthorized readers from
 *	gazing at remote files but the only reliable way of maintaining
 *	security is to make "grab" setgid and then make fs's readable by
 *	group and not by other.  ("Df" should work this way too.)
 *
 *	To compile a V6 grab, define PDPV6.  To compile a 2.8 grab define
 *	PDP, and to compile a 4.1 grab define VAX.  If your machine has
 *	multiplex files, define MPXFILES, and if it has symbolic links
 *	you should define SYMLINKS.
 *
 *	Options:
 *		-246	2.8, 4.1 or V6 system on the remote end.
 *		-p	Preserve all permissions and ownerships, instead of
 *			just some of them (should be su).
 *		-x	Act like "cat(1)" from the remote fs (but still
 *			recursively on directories).
 *		-v	Verbose mode: print filenames as they are copied.
 *		-i	The arguments are inode numbers; treat them as
 *			files with those inode numbers and named by the numbers.
 *		-l	Act like a rudimentary "ls(1)" on a remote fs.
 *		-L	Long version of -l: gives a gruesome inode dump.
 *		-t	Act like "tar(1)" on a remote fs.  Output goes to
 *			the standard output.  Digits following the "t" are
 *			interpreted as a blocksize.
 *		-b	Set blocksize independently: this manipulates the
 *			internal write buffer size.  Digits following, etc.
 *
 *---------------------------------------------------------------------------
 *
 * $Header:   RCS/grab.c.v  Revision 1.2  83/07/01  19:29:13  donn  Exp$
 * $Log:	RCS/grab.c.v $
 * Revision 1.2  83/07/01  19:29:13  donn
 * Added -i and -L flags for inode grabbing and inode dumping.
 * 							Donn
 * 
 */

# include	"grab.h"

int	target_system	= TARGET_DEFAULT;
int	fsbsiz		= FSB_DEFAULT;
int	fsys;
int	pflag		= 0;
int	xflag		= 0;
int	vflag		= 0;
int	iflag		= 0;
int	lflag		= 0;
int	Lflag		= 0;
int	tflag		= 0;
int	nblock		= B_BUFSIZE;




main( argc, argv )
    int	    argc;
    char   *argv[];
{
	char   *tail();
	char   *name;
	int	ino;

	if ( argc < 3 ) {
		fprintf( stderr, "grab: too few arguments\n" );
		fprintf( stderr, "Usage: grab [-246pxvilLtb] <file-system> <files>\n" );
		exit( 1 );
	}

	/*
	 * Process the command line.
	 */
	while ( *argv[1] == '-' ) {

		while ( *++argv[1] != '\0' )
			switch ( *argv[1] ) {

			/*
			 * Select type of filesystem to read.
			 */
			case '2':
				target_system	= V7_2BSD;
				fsbsiz		= 1024;
				break;
			case '4':
				target_system	= V7_4BSD;
				fsbsiz		= 1024;
				break;
			case '6':
				target_system	= V6;
				fsbsiz		= 512;
				break;

			/*
			 * Restore previous owner, group & protections.
			 */
			case 'p':
				++pflag;
# ifndef PDPV6
				umask( 0 );
# endif
				break;

			/*
			 * Put files on the standard output.
			 */
			case 'x':
				++xflag;
				break;

			/*
			 * Print names of copied files.
			 */
			case 'v':
				++vflag;
				break;

			/*
			 * Grab by inode number.
			 */
			case 'i':
				++iflag;
				break;

			/*
			 * List directories.
			 */
			case 'l':
				/*
				 * If only one file then we leave out header.
				 */
				lflag	= argc - 3;
				Lflag	= 0;
				break;

			/*
			 * Long listing.
			 */
			case 'L':
				++Lflag;
				lflag	= 0;
				break;

			/*
			 * Output files in "tar" format.
			 */
			case 't':
				++tflag;
				nblock	= TAR_BS_DEFAULT;
				/* FALL THRU */

			/*
			 * Pick up blocking factor (if any).
			 */
			case 'b':
				if ( *(argv[1] + 1) >= '0' &&
				     *(argv[1] + 1) <= '9' )
					nblock	= atoi( ++argv[1] );
				while ( *(argv[1]+1) >= '0' && *(argv[1]+1) <= '9' )
					++argv[1];

				break;

			default:
				fprintf( stderr, "grab: unknown option -%c\n", argv[1][1] );
				fprintf( stderr, "Usage: grab [-246pxvilLtb] <file-system> <files>\n" );
				exit( 2 );
			}
		++argv;
		--argc;
	}

	if ( argc < 3 ) {
		fprintf( stderr, "grab: too few arguments\n" );
		fprintf( stderr, "Usage: grab [-246pxvilLtb] <file-system> <files>\n" );
		exit( 3 );
	}



	/*
	 * If the standard output is a file, assume -x by default.
	 * This is for backward compatibility with Arthur Olsen's grab
	 * and may go away.
	 */
	if ( ! (xflag || tflag || lflag || Lflag) ) {
		int	statbuf[32];

		if ( fstat( STDOUT, statbuf ) == 0
# ifdef PDPV6
		 && (((struct inode *) statbuf)->i_mode & V6_IFMT) == V6_IFREG )
# else
		 && (((struct inode *) statbuf)->i_mode & V7_IFMT) == V7_IFREG )
# endif
			++xflag;
	}



	/*
	 * Try to separate file output and verbose mode output!
	 * The method here is a bit nonportable...
	 */
	if ( tflag || xflag )
		fileno( stdout )	= fileno( stderr );



	/*
	 * Open the file system device.
	 */
	fsys = open( argv[1], 0 );
	if ( fsys < 0 ) {
		fprintf( stderr, "grab: can't open %s\n", argv[1] );
		exit( 4 );
	}



	/*
	 * Set id to real id.  (V6 isn't setuid because nobody cares.)
	 */
# ifndef PDPV6
	setuid( getuid() );
	setgid( getgid() );
# endif VAX



	/*
	 * Find the files corresponding to the file arguments
	 * and read them off.
	 */
	while ( argc > 2 ) {
		if ( iflag ) {
			name	= NULL;
			ino	= atoi( argv[2] );
		} else {
			name	= tail( argv[2] );
			ino	= find( argv[2] );
		}
		readi( ino, name );
		++argv, --argc;
	}



	/*
	 * Clean up on tape output.
	 * Tar puts out two blank records followed by junk
	 * from the buffer when it flushes the buffer...
	 */
	if ( tflag )
		tflush( TFILE );

	exit( 0 );
}
'EOFEOF'
#-------------------------------------------------------------------
echo extracting readi.c
cat << 'EOFEOF' > readi.c
/*
 * readi.c
 *
 * Routines dealing with file system I/O.
 *
 *---------------------------------------------------------------------------
 *
 * $Header:   RCS/readi.c.v  Revision 1.3  83/08/01  17:09:48  donn  Exp$
 * $Log:	RCS/readi.c.v $
 * Revision 1.3  83/08/01  17:09:48  donn
 * Added code for symbolic links, made MPX code conditionally compiled;
 * did some general prettying up.
 * 							Donn
 * 
 * Revision 1.2  83/07/01  19:21:48  donn
 * Added code to support -i, -I and -L flags.  This allows you to grab by
 * inode number and to display the contents of inodes in a useful way.
 * 							Donn
 * 
 */

# include	"grab.h"



/*
 * readi( ino, name ) -- read file corresponding to ino off of fsys and
 *	put it in file "name".
 */
readi( ino, name )
    int     ino;
    char   *name;
{
	int		ofile;
	struct inode	i;
	struct bmap	b;
	char		iname[DIRSIZ + 1];

	/*
	 * Get the inode.
	 * Make sure that the permissions are ok.
	 */
	if ( ino == 0 )
		return;
	geti( ino, &i );
	if ( ! chkaccess( &i ) ) {
		fprintf( stderr, "grab: %s read-protected\n", name );
		return;
	}

	/*
	 * Is it a directory? If so, then recurse.
	 */
	if ( isdir( &i ) ) {
		dodirs( name, ino, &i, &b );
		return;
	}

	/*
	 * Take care of listings for individual files.
	 */
	if ( Lflag ) {
		idump( &i, ino, name );
		return;
	}
	if ( lflag ) {
		fprintf( stderr, "grab: %s not a directory\n", name );
		return;
	}

	/*
	 * If we have no name for a file, we call it by its inode number.
	 */
	if ( name == NULL ) {
		if ( ! iflag ) {
			fprintf( stderr, "grab: Internal error (see a guru)\n" );
			exit( -1 );
		}
		name	= &iname[0];
		sprintf( name, "%u", ino );
	}

	/*
	 * Deal with links to files.
	 */
	if ( i.i_nlink > 1 && dolinks( name, ino ) )
		return;

# ifdef	SYMLINKS
	if ( target_system == V7_4BSD && (i.i_mode & V7_IFMT) == V7_IFLNK ) {
		dosymlinks( name, &i, &b );
		return;
	}
# endif	SYMLINKS

	/*
	 * Deal with non-directory special files.
	 */
	if ( ! isreg( &i ) ) {
		maknode( name, &i );
		return;
	}

	/*
	 * Copy the file.
	 */
	if ( (ofile = bopen( name, &i, &b )) == -1 )
		return;

	while ( bread( &b ) > 0 )
		bput( ofile, &b );

	bclose( ofile, name, &i, &b );
}



/*
 * geti( ino, ip ) -- read inode number ino and use it to fill the generic
 *	inode structure pointed to by ip.
 */
geti( ino, ip )
    int                     ino;
    register struct inode  *ip;
{
	char	       *malloc();
	char	       *ilist;
	register int	n;
	long		bn;
	register struct v6dinode *v6dp;
	register struct v7dinode *v7dp;

	ilist	= malloc( fsbsiz );
	if ( ilist == NULL ) {
		fprintf( stderr, "grab: out of memory\n" );
		exit( 5 );
	}
	bn	= itod( ino );
	bn	= fsbtodb( bn );
	lseek( fsys, bn * DBSIZE, 0 );

	if ( read( fsys, ilist, fsbsiz ) != fsbsiz ) {
		fprintf( stderr, "grab: read I/O error\n" );
		exit( 6 );
	}

	switch ( target_system ) {

	case V7_2BSD:
		v7dp		= (struct v7dinode *) ilist;
		v7dp		= &v7dp[ itoo( ino ) ];
		ip->i_mode	= v7dp->v7di_mode;
		ip->i_nlink	= v7dp->v7di_nlink;
		ip->i_size	= v7dp->v7di_size;
		ip->i_uid	= v7dp->v7di_uid;
		ip->i_gid	= v7dp->v7di_gid;
		l3tol( (char *)ip->i_addr, (char *)v7dp->v7di_addr, V7NADDR );
		ip->i_atime	= v7dp->v7di_atime;
		ip->i_mtime	= v7dp->v7di_mtime;
# ifndef PDP
		wswap( (char *)&ip->i_size, 1 );
		wswap( (char *)ip->i_addr, V7NADDR );
		wswap( (char *)&ip->i_atime, 1 );
		wswap( (char *)&ip->i_mtime, 1 );
# endif
		break;

	case V7_4BSD:
		v7dp		= (struct v7dinode *) ilist;
		v7dp		= &v7dp[ itoo( ino ) ];
		ip->i_mode	= v7dp->v7di_mode;
		ip->i_nlink	= v7dp->v7di_nlink;
		ip->i_size	= v7dp->v7di_size;
		ip->i_uid	= v7dp->v7di_uid;
		ip->i_gid	= v7dp->v7di_gid;
		l3tol( (char *)ip->i_addr, (char *)v7dp->v7di_addr, V7NADDR );
		ip->i_atime	= v7dp->v7di_atime;
		ip->i_mtime	= v7dp->v7di_mtime;
# ifdef PDP
		wswap( (char *)&ip->i_size, 1 );
		wswap( (char *)ip->i_addr, V7NADDR );
		wswap( (char *)&ip->i_atime, 1 );
		wswap( (char *)&ip->i_mtime, 1 );
# endif
		break;

	case V6:
		v6dp		= (struct v6dinode *) ilist;
		v6dp		= &v6dp[ itoo( ino ) ];
		ip->i_mode	= v6dp->v6di_mode;
		ip->i_nlink	= v6dp->v6di_nlink & 0xff;
		ip->i_size	= (unsigned short) v6dp->v6di_sz1;
		ip->i_size	+= ((long) (v6dp->v6di_sz0 & 0xff)) * 0x10000L;
		ip->i_uid	= v6dp->v6di_uid & 0xff;
		ip->i_gid	= v6dp->v6di_gid & 0xff;
		for ( n = 0; n < V6NADDR; ++n )
			ip->i_addr[n]	= (unsigned short) v6dp->v6di_addr[n];
		ip->i_atime	= v6dp->v6di_atime;
		ip->i_mtime	= v6dp->v6di_mtime;
# ifndef PDP
		wswap( (char *)&ip->i_atime, 1 );
		wswap( (char *)&ip->i_mtime, 1 );
# endif
		break;
	}

	free( ilist );
}



/*
 * dodirs( name, ino, ip, bp ) -- Recursively extract directory contents.
 */
dodirs( name, ino, ip, bp )
    char         *name;
    int		  ino;
    struct inode *ip;
    struct bmap  *bp;
{
	register struct direct *dp;
	register int		j, k, l = 0;
	struct inode	       *dirip;
	int			n, dpb;
	int			dcomp();
	char		       *s	= NULL;
	char                   *malloc();


	/*
	 * If we are doing listings, list the directory itself.
	 */
	if ( Lflag ) {
		fprintf( stderr, "Directory:\n\n" );
		idump( ip, ino, name );
		fprintf( stderr, "Contents of directory:\n\n" );
		dirip	= (struct inode *) malloc( sizeof (struct inode) );
	}
	if ( lflag > 1 )
		printf( "\n%s:\n", name );

	/*
	 * If we only have the inode, we must simulate the name.
	 */
	if ( name == NULL ) {
		if ( ! iflag ) {
			fprintf( stderr, "grab: Internal error (see a guru)\n" );
			exit( -1 );
		}
		name	= malloc( DIRSIZ + 1 );
		sprintf( name, "%u", ino );
	}

	/*
	 * Tar header for a directory.
	 */
	if ( tflag )
		theader( TFILE, name, ip );

	/*
	 * Allocate a buffer to hold the pathname.
	 */
	j	= strlen( name ) + DIRSIZ + 2;
	s	= malloc( j );
	if ( s == NULL ) {
		fprintf( stderr, "grab: out of memory\n" );
		exit( 7 );
	}
	s[j-1]	= '\0';

	/*
	 * If we aren't merely printing files, make the directory.
	 */
	if ( ! (xflag || lflag || Lflag || tflag ) )
		if ( makdir( name, ip ) < 0 )
			return;

	/*
	 * Iterate over the target directory's entries.
	 */
	n	= ip->i_size / sizeof (struct direct);
	if ( lflag || Lflag ) {
		allocbuf( ip, bp, B_BIG );
		dpb	= (nblock * TBLOCK) / sizeof (struct direct);
	} else {
		allocbuf( ip, bp, B_SMALL );
		dpb	= fsbsiz / sizeof (struct direct);
	}

	for ( j = 0; j < n; ++j ) {
		/*
		 * Read the directory (if necessary).
		 */
		k	= j % dpb;
		if ( k == 0 ) {
			bread( bp );
			if ( lflag )
				/*
				 * Sort by directory name.
				 */
				qsort( bp->b_data, min( dpb, n-j ), sizeof (struct direct), dcomp );
		}

		/*
		 * Find directory entry.
		 */
		dp	= &((struct direct *) bp->b_data)[ k ];
		if ( dp->d_ino == 0 )
			continue;
		if ( strncmp( ".", dp->d_name, DIRSIZ ) == 0 ||
		     strncmp( "..", dp->d_name, DIRSIZ ) == 0 )
			continue;

		/*
		 * If -l or -L flag was selected, list the directory.
		 */
		if ( lflag ) {
			if ( ++l % 5 != 0 )
				printf( "%-14.14s  ", dp->d_name );
			else
				printf( "%-.14s\n", dp->d_name );
			continue;
		}
		if ( Lflag ) {
			geti( dp->d_ino, dirip );
			idump( dirip, dp->d_ino, dp->d_name );
			continue;
		}

		/*
		 * Recursively extract files.
		 */
		strcpy( s, name );
		strncat( s, "/", 2 );
		strncat( s, dp->d_name, DIRSIZ );
		readi( (unsigned int) dp->d_ino, s );
	}

	if ( lflag && l % 5 != 0 )
		putchar( '\n' );

	freebuf( bp );

	free( s );
	if ( Lflag )
		free( dirip );

	/*
	 * Repair volatile attributes of the directory.
	 */
	if ( xflag || lflag || Lflag || tflag )
		return;
# ifdef CLR_SETUID
	if ( pflag && (ip->i_mode & (ISUID|ISGID|ISVTX)) )
		chmod( name, ip->i_mode & 07777 );
# endif CLR_SETUID
# ifndef PDPV6
	if ( pflag )
		utime( name, &ip->i_atime );
# endif

	return;
}



/*
 * dcomp( d1, d2 ) -- Order two directory entries by name.
 */
dcomp( d1, d2 )
    register struct direct *d1, *d2;
{
	return ( strncmp( d1->d_name, d2->d_name, DIRSIZ ) );
}



/*
 * dolinks( name, ino ) -- Copy links.  Return 1 if link was made.
 *	Strategy: search link records, each containing an inumber,
 *	a link to the next record and a pathname.
 */

struct link
{
	short		ino;
	short		pad;
	struct link    *next;
	char		path;
};

int
dolinks( name, ino )
    char   *name;
    int     ino;
{
	char		       *malloc();
	static struct link     *root	= NULL;
	struct link	       *lp	= root;

	if ( xflag )
		return ( 0 );

	/*
	 * Look for ino in list.
	 */
	while ( lp != NULL ) {
		if ( lp->ino == (short) ino ) {
			/*
			 * Found it.  Emit a "tar" link or a real link.
			 */
			if ( tflag )
				tlink( TFILE, ino, name, &(lp->path) );
			else
			if ( link( &(lp->path), name ) == -1 ) {
				fprintf( stderr, "grab: couldn't link %s to %s\n",
					name, &(lp->path) );
				return ( 0 );
			}
			if ( vflag ) {
				printf( "%s (linked to %s)\n", name, &(lp->path) );
				fflush( stdout );
			}
			return ( 1 );
		}
		lp	= lp->next;
	}

	/*
	 * A new name.  Store it away.
	 */
	lp		= (struct link *) malloc( (2 * sizeof (short))
				+ sizeof (struct link *) + strlen( name ) + 1 );
	if ( lp == NULL ) {
		fprintf( stderr, "grab: out of memory\n" );
		exit( 8 );
	}
	lp->ino		= (short) ino;
	lp->next	= root;
	strcpy( &(lp->path), name );
	root		= lp;

	return ( 0 );
}



/*
 * makdir( name, ip ) -- create a directory named name, using mkdir.
 */
makdir( name, ip )
    char         *name;
    struct inode *ip;
{
	short	statbuf[16];
	int	pid, deadpid, status;

	if ( stat( name, statbuf ) == 0 ) {
		/*
		 * File exists -- if it's a directory, okay;
		 * otherwise we complain.
		 */
# ifdef	PDPV6
		if ( (((struct inode *) statbuf)->i_mode & V6_IFMT) == V6_IFDIR )
# else
		if ( (((struct inode *) statbuf)->i_mode & V7_IFMT) == V7_IFDIR )
# endif
			return ( 0 );
		fprintf( stderr, "grab: %s: not a directory\n", name );
		return ( -1 );
	}

	/*
	 * File doesn't exist: fork and exec mkdir.
	 */
	pid	= fork();
	if ( pid == 0 ) {
		execl( "/bin/mkdir", "mkdir", name, 0 );
		execl( "/usr/bin/mkdir", "mkdir", name, 0 );
		fprintf( stderr, "grab: can't exec mkdir\n" );
		exit( 9 );
	}
	while ( (deadpid = wait( &status )) >= 0 && deadpid != pid )
		;
# ifdef PDPV6
	/*
	 * Sigh.  V6 mkdir doesn't return a useful status.
	 */
	if ( deadpid < 0 ) {
# else
	if ( deadpid < 0 || status != 0 ) {
# endif
		fprintf( stderr, "grab: can't create directory %s\n", name );
		return ( -1 );
	}

	/*
	 * Copy directory permissions.
	 */
	chmod( name, ip->i_mode & (pflag ? 07777 : 0777 ) );
	if ( pflag )
		CHOWN( name, ip->i_uid, ip->i_gid );

	return ( 0 );
}



# ifdef	SYMLINKS
/*
 * dosymlinks( name, ip, bp ) -- Make a symbolic link from name to the file
 *	whose name is contained in the file with inode *ip, using bmap *bp.
 */
dosymlinks( name, ip, bp )
    register char         *name;
    register struct inode *ip;
    register struct bmap  *bp;
{
	allocbuf( ip, bp, B_SMALL );
	bread( bp );
	if ( symlink( bp->b_data, name ) == -1 )
		fprintf( stderr, "grab: couldn't make symbolic link from %s to %s\n", name, bp->b_data );
	else if ( vflag ) {
		printf( "%s (symbolic link to %s)\n", name, bp->b_data );
		fflush( stdout );
	}
	freebuf( bp );
}
# endif	SYMLINKS



/*
 * maknode( name, ip ) -- Create a device entry.
 */
maknode( name, ip )
    char         *name;
    struct inode *ip;
{
	long	addr		= ip->i_addr[0];
	short	nodemode	= ip->i_mode & (pflag ? 07777 : 0777);

	if ( xflag ) {
		fprintf( stderr, "grab: Can't print special file %s!\n", name );
		return;
	} else if ( tflag ) {
		fprintf( stderr, "grab: %s: Can't put on tar tape\n", name );
		return;
	}

	/*
	 * Check legality of modes.
	 */
# ifndef MPXFILES
	if ( target_system != V6 &&
	     ( (ip->i_mode & V7_IFMT) == V7_IFMPC ||
	       (ip->i_mode & V7_IFMT) == V7_IFMPB ) ) {
		fprintf( stderr, "grab: %s: can't make multiplex files\n", name );
		return;
	}
# endif MPXFILES
# ifndef SYMLINKS
	if ( target_system == V7_4BSD && (ip->i_mode & V7_IFMT) == V7_IFLNK ) {
		fprintf( stderr, "grab: %s: can't make symbolic links\n", name );
		return;
	}
# endif	SYMLINKS

	/*
	 * Convert modes.
	 */
	nodemode	|= CONVMODE( ip->i_mode );

	/*
	 * Create the special file.
	 */
	if ( mknod( name, nodemode, addr ) == -1 ) {
		fprintf( stderr, "grab: can't make special file %s\n", name );
		return;
	}
	if ( pflag ) {
		CHOWN( name, ip->i_uid, ip->i_gid );
# ifdef CLR_SETUID
		if ( ip->i_mode & (ISUID|ISGID|ISVTX) )
			chmod( name, ip->i_mode & 07777 );
# endif CLR_SETUID
# ifndef PDPV6
		utime( name, &ip->i_atime );
# endif
	}

	if ( vflag )
		printf( "%s (special file)\n", name );
}



/*
 * l3tol( lp, cp, n ) -- convert the n 3-byte integers pointed to by cp
 *	into longs and put them in the array pointed to by lp.
 *
 * PDP long integer brain damage strikes again -- on a VAX a 3-byte long
 * is the first three bytes of a true long but on a PDP it's got a hole in
 * it...  thus the library routine can't be used.
 */
l3tol(lp, cp, n)
    long   *lp;
    char   *cp;
    int     n;
{
	register i;
	register char *a, *b;

	a = (char *)lp;
	b = cp;
	for(i=0;i<n;i++) {
		if ( target_system != V7_4BSD ) {
			*a++ = *b++;
			*a++ = 0;
			*a++ = *b++;
			*a++ = *b++;
		} else {
			*a++ = *b++;
			*a++ = *b++;
			*a++ = *b++;
			*a++ = 0;
		}
	}
}



/*
 * wswap( longlist, count ) -- swap the words of count long integers residing
 *	in longlist.
 */
wswap( longlist, count )
    char          *longlist;
    register int   count;
{
	register short *wptr, tmp;

	wptr	= (short *) longlist;
	while ( count-- ) {
		tmp	= *wptr;
		*wptr	= *(wptr+1);
		*++wptr	= tmp;
		++wptr;
	}
}
'EOFEOF'
#-------------------------------------------------------------------
echo extracting tmode.c
cat << 'EOFEOF' > tmode.c
/*
 * tmode.c
 *
 * Routines for producing blocked "tar"-style output.
 *
 *---------------------------------------------------------------------------
 *
 * $Header$
 * $Log$
 */

# include	"grab.h"



/*
 * theader( ofile, name, ip ) -- Write a "tar" header for a file called "name"
 *	on file "ofile" using inode information pointed to by "ip".
 */
theader( ofile, name, ip )
    int             ofile;
    char           *name;
    struct inode   *ip;
{
	union hblock    h;
	register char  *cp;
	register int	n	= TBLOCK;

	/*
	 * Clear out the buffer.
	 */
	cp	= h.dummy;
	CLRBUF( cp, n );

	/*
	 * Insert name.
	 */
	n	= strlen( name );
	if ( n > NAMSIZ ) {
	    fprintf( stderr, "grab: name too long for tar output: %s\n", name );
	    fprintf( stderr, "grab: name truncated to: %.*s\n", NAMSIZ, name );
	}
	strncpy( h.dbuf.name, name, NAMSIZ );
	if ( isdir( ip ) )
		h.dbuf.name[ min( n, NAMSIZ-1 ) ]	= '/';

	/*
	 * Insert modes.
	 */
	tomodes( &h, ip );

	tput( ofile, h.dummy, TBLOCK );
}



/*
 * tlink( ofile, ino, name, linkname ) -- Write a "tar" header for a file named
 *	name with inode ino which is linked to an earlier file called linkname.
 */
tlink( ofile, ino, name, linkname )
    int     ofile;
    int     ino;
    char   *name;
    char   *linkname;
{
	union hblock	h;
	struct inode	i;
	register char  *cp;
	register int	n	= TBLOCK;

	/*
	 * Clear out the buffer.
	 */
	cp	= h.dummy;
	CLRBUF( cp, n );

	/*
	 * Insert names.
	 */
	if ( strlen( name ) > NAMSIZ ) {
	    fprintf( stderr, "grab: name too long for tar output: %s\n", name );
	    fprintf( stderr, "grab: name truncated to: %.*s\n", NAMSIZ, name );
	}
	strncpy( h.dbuf.name, name, NAMSIZ );
	strncpy( h.dbuf.linkname, linkname, NAMSIZ );
	h.dbuf.linkflag	= '1';

	/*
	 * Get modes.
	 */
	geti( ino, &i );
	tomodes( &h, &i );

	tput( ofile, h.dummy, TBLOCK );
}



/*
 * tomodes( hp, ip ) -- Store modes from inode ip in header block hp.
 */
tomodes( hp, ip )
    union hblock *hp;
    struct inode *ip;
{
	sprintf( hp->dbuf.mode, "%6o ", ip->i_mode & 07777 );
	sprintf( hp->dbuf.uid, "%6o ", ip->i_uid );
	sprintf( hp->dbuf.gid, "%6o ", ip->i_gid );
	sprintf( hp->dbuf.size, "%11lo ", isdir( ip ) ? 0 : ip->i_size );
	sprintf( hp->dbuf.mtime, "%11lo ", ip->i_mtime );
	checksum( hp );
}



/*
 * checksum( hp ) -- Insert tar-style checksum in header block *hp.
 */
checksum( hp )
    union hblock *hp;
{
	register int	i;
	register char  *cp	= hp->dummy;
	register char  *last	= hp->dummy + TBLOCK;

	for ( i = 0; i < sizeof hp->dbuf.chksum; ++i )
		hp->dbuf.chksum[i]	= ' ';

	i	= 0;
	do
		i	+= *cp++;
	while ( cp < last );

	sprintf( hp->dbuf.chksum, "%6o", i );
}



/*
 * tput( ofile, data, size ) -- Twiddle the buffer queuing mechanism to output
 *	tape data on ofile.
 */
tput( ofile, data, size )
    int              ofile;
    register char   *data;
    int              size;
{
	struct inode	i;
	struct bmap	b;
	register int	n;
	register char  *cp;

	/*
	 * Get access to the big data buffer.
	 * Notice that it really doesn't matter much what's in inode i!
	 */
	i.i_size	= size;
	allocbuf( &i, &b, B_BIG );

	/*
	 * Get the data in place and fiddle the bmap parameters to make
	 * bput think it's got something to do.
	 */
	cp		= b.b_data + b.b_cc;
	n		= size;
	CPYBUF( cp, data, n );

	b.b_offset	= 0;
	b.b_cc		+= size;

	/*
	 * Send out the data.
	 */
	bput( ofile, &b );

	/*
	 * Wrap up.  Simple, huh?
	 */
	freebuf( &b );
}



/*
 * tflush( ofile ) -- Flush remaining tape record, with two zero blocks
 *	appended.
 */
tflush( ofile )
    int     ofile;
{
	char		buf[TBLOCK];
	register char  *cp;
	register int	n;
	extern int	savetcc;

	/*
	 * Make an empty buffer.
	 */
	cp	= buf;
	n	= TBLOCK;
	CLRBUF( cp, n );

	/*
	 * Cleverly put out 2 empty buffers and write.
	 * The chicanery in the final tput forces a write because it makes
	 * bput think it has a full buffer.
	 */
	tput( ofile, buf, TBLOCK );
	tput( ofile, buf, TBLOCK );
	savetcc	= nblock * TBLOCK;
	tput( ofile, buf, 0 );
}
'EOFEOF'