2.9BSD/usr/contrib/grab/src/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" );
}