#include "opt_quota.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/lock.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/buf2.h>
#include "quota.h"
#include "inode.h"
#include "ufs_extern.h"
#include "fs.h"
#include "ffs_extern.h"
int
ffs_balloc(struct vop_balloc_args *ap)
{
struct inode *ip;
ufs_daddr_t lbn;
int size;
struct ucred *cred;
int flags;
struct fs *fs;
ufs_daddr_t nb;
struct buf *bp, *nbp, *dbp;
struct vnode *vp;
struct indir indirs[UFS_NIADDR + 2];
ufs_daddr_t newb, *bap, pref;
int deallocated, osize, nsize, num, i, error;
ufs_daddr_t *allocib, *blkp, *allocblk, allociblk[UFS_NIADDR + 1];
ufs_daddr_t *lbns_remfree, lbns[UFS_NIADDR + 1];
int unwindidx;
int seqcount;
vp = ap->a_vp;
ip = VTOI(vp);
fs = ip->i_fs;
lbn = lblkno(fs, ap->a_startoffset);
size = blkoff(fs, ap->a_startoffset) + ap->a_size;
if (size > fs->fs_bsize)
panic("ffs_balloc: blk too big");
*ap->a_bpp = NULL;
if (lbn < 0)
return (EFBIG);
cred = ap->a_cred;
flags = ap->a_flags;
if (vn_islocked(vp) != LK_EXCLUSIVE) {
panic("ffs_balloc: vnode %p not exclusively locked!", vp);
}
nb = lblkno(fs, ip->i_size);
if (nb < UFS_NDADDR && nb < lbn) {
osize = blksize(fs, ip, nb);
if (osize < fs->fs_bsize && osize > 0) {
error = ffs_realloccg(ip, nb,
ffs_blkpref(ip, nb, (int)nb, &ip->i_db[0]),
osize, (int)fs->fs_bsize, cred, &bp);
if (error)
return (error);
if (DOINGSOFTDEP(vp))
softdep_setup_allocdirect(ip, nb,
dofftofsb(fs, bp->b_bio2.bio_offset),
ip->i_db[nb], fs->fs_bsize, osize, bp);
ip->i_size = smalllblktosize(fs, nb + 1);
ip->i_db[nb] = dofftofsb(fs, bp->b_bio2.bio_offset);
ip->i_flag |= IN_CHANGE | IN_UPDATE;
if (flags & B_SYNC)
bwrite(bp);
else
bawrite(bp);
}
}
if (lbn < UFS_NDADDR) {
nb = ip->i_db[lbn];
if (nb != 0 && ip->i_size >= smalllblktosize(fs, lbn + 1)) {
error = bread(vp, lblktodoff(fs, lbn), fs->fs_bsize, &bp);
if (error) {
brelse(bp);
return (error);
}
bp->b_bio2.bio_offset = fsbtodoff(fs, nb);
*ap->a_bpp = bp;
return (0);
}
if (nb != 0) {
osize = fragroundup(fs, blkoff(fs, ip->i_size));
nsize = fragroundup(fs, size);
if (nsize <= osize) {
error = bread(vp, lblktodoff(fs, lbn),
osize, &bp);
if (error) {
brelse(bp);
return (error);
}
bp->b_bio2.bio_offset = fsbtodoff(fs, nb);
} else {
error = ffs_realloccg(ip, lbn,
ffs_blkpref(ip, lbn, (int)lbn,
&ip->i_db[0]), osize, nsize, cred, &bp);
if (error)
return (error);
if (DOINGSOFTDEP(vp))
softdep_setup_allocdirect(ip, lbn,
dofftofsb(fs, bp->b_bio2.bio_offset),
nb, nsize, osize, bp);
}
} else {
if (ip->i_size < smalllblktosize(fs, lbn + 1))
nsize = fragroundup(fs, size);
else
nsize = fs->fs_bsize;
error = ffs_alloc(ip, lbn,
ffs_blkpref(ip, lbn, (int)lbn, &ip->i_db[0]),
nsize, cred, &newb);
if (error)
return (error);
bp = getblk(vp, lblktodoff(fs, lbn), nsize, 0, 0);
bp->b_bio2.bio_offset = fsbtodoff(fs, newb);
if (flags & B_CLRBUF)
vfs_bio_clrbuf(bp);
if (DOINGSOFTDEP(vp))
softdep_setup_allocdirect(ip, lbn, newb, 0,
nsize, 0, bp);
}
ip->i_db[lbn] = dofftofsb(fs, bp->b_bio2.bio_offset);
ip->i_flag |= IN_CHANGE | IN_UPDATE;
*ap->a_bpp = bp;
return (0);
}
pref = 0;
if ((error = ufs_getlbns(vp, lbn, indirs, &num)) != 0)
return(error);
#ifdef DIAGNOSTIC
if (num < 1)
panic ("ffs_balloc: ufs_bmaparray returned indirect block");
#endif
dbp = getblk(vp, lblktodoff(fs, lbn), fs->fs_bsize, 0, 0);
allocib = NULL;
allocblk = allociblk;
lbns_remfree = lbns;
unwindidx = -1;
--num;
nb = ip->i_ib[indirs[0].in_off];
if (nb == 0) {
pref = ffs_blkpref(ip, lbn, 0, NULL);
if ((error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize,
cred, &newb)) != 0)
goto fail2;
nb = newb;
*allocblk++ = nb;
*lbns_remfree++ = indirs[1].in_lbn;
bp = getblk(vp, lblktodoff(fs, indirs[1].in_lbn),
fs->fs_bsize, 0, 0);
bp->b_bio2.bio_offset = fsbtodoff(fs, nb);
vfs_bio_clrbuf(bp);
if (DOINGSOFTDEP(vp)) {
softdep_setup_allocdirect(ip,
UFS_NDADDR + indirs[0].in_off,
newb, 0, fs->fs_bsize, 0, bp);
bdwrite(bp);
} else {
if (DOINGASYNC(vp))
bdwrite(bp);
else if ((error = bwrite(bp)) != 0)
goto fail;
}
allocib = &ip->i_ib[indirs[0].in_off];
*allocib = nb;
ip->i_flag |= IN_CHANGE | IN_UPDATE;
}
for (i = 1;;) {
error = bread(vp, lblktodoff(fs, indirs[i].in_lbn), (int)fs->fs_bsize, &bp);
if (error) {
brelse(bp);
goto fail;
}
bap = (ufs_daddr_t *)bp->b_data;
nb = bap[indirs[i].in_off];
if (i == num)
break;
i += 1;
if (nb != 0) {
bqrelse(bp);
continue;
}
if (pref == 0)
pref = ffs_blkpref(ip, lbn, 0, NULL);
if ((error =
ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, cred, &newb)) != 0) {
brelse(bp);
goto fail;
}
nb = newb;
*allocblk++ = nb;
*lbns_remfree++ = indirs[i].in_lbn;
nbp = getblk(vp, lblktodoff(fs, indirs[i].in_lbn),
fs->fs_bsize, 0, 0);
nbp->b_bio2.bio_offset = fsbtodoff(fs, nb);
vfs_bio_clrbuf(nbp);
if (DOINGSOFTDEP(vp)) {
softdep_setup_allocindir_meta(nbp, ip, bp,
indirs[i - 1].in_off, nb);
bdwrite(nbp);
} else {
if ((error = bwrite(nbp)) != 0) {
brelse(bp);
goto fail;
}
}
bap[indirs[i - 1].in_off] = nb;
if (allocib == NULL && unwindidx < 0)
unwindidx = i - 1;
if (flags & B_SYNC) {
bwrite(bp);
} else {
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
bdwrite(bp);
}
}
if (nb == 0) {
pref = ffs_blkpref(ip, lbn, indirs[i].in_off, &bap[0]);
error = ffs_alloc(ip,
lbn, pref, (int)fs->fs_bsize, cred, &newb);
if (error) {
brelse(bp);
goto fail;
}
nb = newb;
*allocblk++ = nb;
*lbns_remfree++ = lbn;
dbp->b_bio2.bio_offset = fsbtodoff(fs, nb);
if (flags & B_CLRBUF)
vfs_bio_clrbuf(dbp);
if (DOINGSOFTDEP(vp))
softdep_setup_allocindir_page(ip, lbn, bp,
indirs[i].in_off, nb, 0, dbp);
bap[indirs[i].in_off] = nb;
if (flags & B_SYNC) {
bwrite(bp);
} else {
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
bdwrite(bp);
}
*ap->a_bpp = dbp;
return (0);
}
brelse(bp);
if (flags & B_CLRBUF) {
if ((dbp->b_flags & B_CACHE) == 0) {
bqrelse(dbp);
seqcount = (flags & B_SEQMASK) >> B_SEQSHIFT;
if (seqcount &&
(vp->v_mount->mnt_flag & MNT_NOCLUSTERR) == 0) {
error = cluster_read(vp, (off_t)ip->i_size,
lblktodoff(fs, lbn),
(int)fs->fs_bsize,
fs->fs_bsize,
seqcount * MAXBSIZE,
&dbp);
} else {
error = bread(vp, lblktodoff(fs, lbn),
(int)fs->fs_bsize, &dbp);
}
if (error)
goto fail;
} else {
dbp->b_bio2.bio_offset = fsbtodoff(fs, nb);
}
} else {
dbp->b_bio2.bio_offset = fsbtodoff(fs, nb);
}
*ap->a_bpp = dbp;
return (0);
fail:
VOP_FSYNC(vp, MNT_WAIT, 0);
for (deallocated = 0, blkp = allociblk, lbns_remfree = lbns;
blkp < allocblk; blkp++, lbns_remfree++) {
bp = getblk(vp, lblktodoff(fs, *lbns_remfree), fs->fs_bsize, 0, 0);
bp->b_flags |= (B_INVAL | B_RELBUF);
brelse(bp);
deallocated += fs->fs_bsize;
}
if (allocib != NULL) {
*allocib = 0;
} else if (unwindidx >= 0) {
int r;
r = bread(vp, lblktodoff(fs, indirs[unwindidx].in_lbn), (int)fs->fs_bsize, &bp);
if (r) {
panic("Could not unwind indirect block, error %d", r);
brelse(bp);
} else {
bap = (ufs_daddr_t *)bp->b_data;
bap[indirs[unwindidx].in_off] = 0;
if (flags & B_SYNC) {
bwrite(bp);
} else {
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
bdwrite(bp);
}
}
}
if (deallocated) {
#ifdef QUOTA
(void) ufs_chkdq(ip, (long)-btodb(deallocated), cred, FORCE);
#endif
ip->i_blocks -= btodb(deallocated);
ip->i_flag |= IN_CHANGE | IN_UPDATE;
}
VOP_FSYNC(vp, MNT_WAIT, 0);
for (blkp = allociblk; blkp < allocblk; blkp++) {
ffs_blkfree(ip, *blkp, fs->fs_bsize);
}
fail2:
brelse(dbp);
return (error);
}