/*
 * Copyright (c) 1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 *
 *	@(#)ufs_bmap.c	1.2 (2.11BSD) 1996/9/19
 */

#include "param.h"
#include "../machine/seg.h"

#include "systm.h"
#include "conf.h"
#include "dir.h"
#include "inode.h"
#include "user.h"
#include "buf.h"
#include "fs.h"
#include "mount.h"
#include "uio.h"

/*
 * Bmap defines the structure of file system storage
 * by returning the physical block number on a device given the
 * inode and the logical block number in a file.
 * When convenient, it also leaves the physical
 * block number of the next block of the file in rablock
 * for use in read-ahead.
 */
daddr_t
bmap(ip, bn, rwflg, flags)
	register struct inode *ip;
	daddr_t bn;
	int rwflg, flags;
{
	register int i;
	register struct buf *bp;
	struct buf *nbp;
	int j, sh;
	daddr_t nb, *bap, ra;
	int async = ip->i_fs->fs_flags & MNT_ASYNC;

	if (bn < 0) {
		u.u_error = EFBIG;
		return((daddr_t)0);
	}
	ra = rablock = 0;

	/*
	 * blocks 0..NADDR-4 are direct blocks
	 */
	if (bn < NADDR-3) {
		i = bn;
		nb = ip->i_addr[i];
		if (nb == 0) {
			if (rwflg == B_READ || (bp = balloc(ip, flags)) == NULL)
				return((daddr_t)-1);
			nb = dbtofsb(bp->b_blkno);
/*
 * directory blocks are usually the only thing written synchronously at this
 * point (so they never appear with garbage in them on the disk).  This is
 * overridden if the filesystem was mounted 'async'.
*/
			if (flags & B_SYNC)
				bwrite(bp);
			else
				bdwrite(bp);
			ip->i_addr[i] = nb;
			ip->i_flag |= IUPD|ICHG;
		}
		if (i < NADDR-4)
			rablock = ip->i_addr[i+1];
		return(nb);
	}

	/*
	 * addresses NADDR-3, NADDR-2, and NADDR-1
	 * have single, double, triple indirect blocks.
	 * the first step is to determine
	 * how many levels of indirection.
	 */
	sh = 0;
	nb = 1;
	bn -= NADDR-3;
	for (j = 3;j > 0;j--) {
		sh += NSHIFT;
		nb <<= NSHIFT;
		if (bn < nb)
			break;
		bn -= nb;
	}
	if (j == 0) {
		u.u_error = EFBIG;
		return((daddr_t)0);
	}

	/*
	 * fetch the first indirect block
	 */
	nb = ip->i_addr[NADDR-j];
	if (nb == 0) {
		if (rwflg == B_READ || (bp = balloc(ip, flags | B_CLRBUF)) == NULL)
			return((daddr_t) -1);
		nb = dbtofsb(bp->b_blkno);
		/*
		 * Write synchronously if requested so that indirect blocks
		 * never point at garbage.
		 */
		if (async)
			bdwrite(bp);
		else
			bwrite(bp);
		ip->i_addr[NADDR-j] = nb;
		ip->i_flag |= IUPD|ICHG;
	}

	/*
	 * fetch through the indirect blocks
	 */
	for(;j <= 3;j++) {
		bp = bread(ip->i_dev, nb);
		if ((bp->b_flags & B_ERROR) || bp->b_resid) {
			brelse(bp);
			return((daddr_t)0);
		}
		bap = (daddr_t *) mapin(bp);
		sh -= NSHIFT;
		i = (bn>>sh) & NMASK;
		nb = bap[i];
		/*
		 * calculate read-ahead
		 */
		if (i < NINDIR-1)
			ra = bap[i+1];
		mapout(bp);
		if (nb == 0) {
			if (rwflg == B_READ || (nbp = balloc(ip, flags | B_CLRBUF)) == NULL) {
				brelse(bp);
				return((daddr_t) -1);
			}
			nb = dbtofsb(nbp->b_blkno);
/*
 * Write synchronously so indirect blocks never point at garbage and blocks
 * in directories never contain garbage.  This check used to be based on the
 * type of inode, if it was a directory then 'sync' writes were done.  See the
 * comments earlier about filesystems being mounted 'async'.
*/
			if (!async && (j < 3 || (flags & B_SYNC)))
				bwrite(nbp);
			else
				bdwrite(nbp);
			bap = (daddr_t *) mapin(bp);
			bap[i] = nb;
			mapout(bp);
			bdwrite(bp);
		} else
			brelse(bp);
	}
	rablock = ra;
	return(nb);
}
