#include <sys/cdefs.h>
#include "opt_ufs.h"
#include "opt_quota.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/racct.h>
#include <sys/random.h>
#include <sys/resourcevar.h>
#include <sys/rwlock.h>
#include <sys/stat.h>
#include <sys/vmmeter.h>
#include <sys/vnode.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_object.h>
#include <ufs/ufs/extattr.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/dir.h>
#ifdef UFS_DIRHASH
#include <ufs/ufs/dirhash.h>
#endif
#include <ufs/ufs/ufs_extern.h>
#include <ufs/ffs/fs.h>
#include <ufs/ffs/ffs_extern.h>
static int ffs_indirtrunc(struct inode *, ufs2_daddr_t, ufs2_daddr_t,
ufs2_daddr_t, int, ufs2_daddr_t *);
static void
ffs_inode_bwrite(struct vnode *vp, struct buf *bp, int flags)
{
if ((flags & IO_SYNC) != 0)
bwrite(bp);
else if (DOINGASYNC(vp))
bdwrite(bp);
else
bawrite(bp);
}
int
ffs_update(struct vnode *vp, int waitfor)
{
struct fs *fs;
struct buf *bp;
struct inode *ip;
daddr_t bn;
int flags, error;
ASSERT_VOP_ELOCKED(vp, "ffs_update");
ufs_itimes(vp);
ip = VTOI(vp);
if ((ip->i_flag & IN_MODIFIED) == 0 && waitfor == 0)
return (0);
ip->i_flag &= ~(IN_LAZYACCESS | IN_LAZYMOD | IN_MODIFIED);
if (waitfor)
ip->i_flag &= ~(IN_SIZEMOD | IN_IBLKDATA);
fs = ITOFS(ip);
if (fs->fs_ronly)
return (0);
loop:
flags = 0;
if (IS_SNAPSHOT(ip))
flags = GB_LOCK_NOWAIT;
bn = fsbtodb(fs, ino_to_fsba(fs, ip->i_number));
error = ffs_breadz(VFSTOUFS(vp->v_mount), ITODEVVP(ip), bn, bn,
(int) fs->fs_bsize, NULL, NULL, 0, NOCRED, flags, NULL, &bp);
if (error != 0) {
if (error != EBUSY || (flags & GB_LOCK_NOWAIT) == 0)
return (error);
vref(vp);
VOP_UNLOCK(vp);
pause("ffsupd", 1);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
vrele(vp);
if (!IS_UFS(vp))
return (ENOENT);
goto loop;
}
if (DOINGSOFTDEP(vp))
softdep_update_inodeblock(ip, bp, waitfor);
else if (ip->i_effnlink != ip->i_nlink)
panic("ffs_update: bad link cnt");
if (I_IS_UFS1(ip)) {
*((struct ufs1_dinode *)bp->b_data +
ino_to_fsbo(fs, ip->i_number)) = *ip->i_din1;
random_harvest_queue(&(ip->i_din1), sizeof(ip->i_din1), RANDOM_FS_ATIME);
} else {
ffs_update_dinode_ckhash(fs, ip->i_din2);
*((struct ufs2_dinode *)bp->b_data +
ino_to_fsbo(fs, ip->i_number)) = *ip->i_din2;
random_harvest_queue(&(ip->i_din2), sizeof(ip->i_din2), RANDOM_FS_ATIME);
}
if (waitfor) {
error = bwrite(bp);
if (ffs_fsfail_cleanup(VFSTOUFS(vp->v_mount), error))
error = 0;
} else if (vm_page_count_severe() || buf_dirty_count_severe()) {
bawrite(bp);
error = 0;
} else {
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
bdwrite(bp);
error = 0;
}
return (error);
}
#define SINGLE 0
#define DOUBLE 1
#define TRIPLE 2
int
ffs_truncate(struct vnode *vp,
off_t length,
int flags,
struct ucred *cred)
{
struct inode *ip;
ufs2_daddr_t bn, lbn, lastblock, lastiblock[UFS_NIADDR];
ufs2_daddr_t indir_lbn[UFS_NIADDR], oldblks[UFS_NDADDR + UFS_NIADDR];
#ifdef INVARIANTS
ufs2_daddr_t newblks[UFS_NDADDR + UFS_NIADDR];
#endif
ufs2_daddr_t count, blocksreleased = 0, blkno;
struct bufobj *bo __diagused;
struct fs *fs;
struct buf *bp;
struct ufsmount *ump;
int softdeptrunc, journaltrunc;
int needextclean, extblocks;
int offset, size, level, nblocks;
int i, error, allerror, indiroff, waitforupdate;
uint64_t key;
off_t osize;
ip = VTOI(vp);
ump = VFSTOUFS(vp->v_mount);
fs = ump->um_fs;
bo = &vp->v_bufobj;
ASSERT_VOP_LOCKED(vp, "ffs_truncate");
if (length < 0)
return (EINVAL);
if (length > fs->fs_maxfilesize)
return (EFBIG);
#ifdef QUOTA
error = getinoquota(ip);
if (error)
return (error);
#endif
if ((flags & (IO_EXT | IO_NORMAL)) == 0)
flags |= IO_NORMAL;
if (!DOINGSOFTDEP(vp) && !DOINGASYNC(vp))
flags |= IO_SYNC;
waitforupdate = (flags & IO_SYNC) != 0 || !DOINGASYNC(vp);
allerror = 0;
needextclean = 0;
softdeptrunc = 0;
journaltrunc = DOINGSUJ(vp);
journaltrunc = 0;
if (journaltrunc == 0 && DOINGSOFTDEP(vp) && length == 0)
softdeptrunc = !softdep_slowdown(vp);
extblocks = 0;
if (fs->fs_magic == FS_UFS2_MAGIC && ip->i_din2->di_extsize > 0) {
extblocks = btodb(fragroundup(fs, ip->i_din2->di_extsize));
}
if ((flags & IO_EXT) && extblocks > 0) {
if (length != 0)
panic("ffs_truncate: partial trunc of extdata");
if (softdeptrunc || journaltrunc) {
if ((flags & IO_NORMAL) == 0)
goto extclean;
needextclean = 1;
} else {
if ((error = ffs_syncvnode(vp, MNT_WAIT, 0)) != 0)
return (error);
#ifdef QUOTA
(void) chkdq(ip, -extblocks, NOCRED, FORCE);
#endif
vinvalbuf(vp, V_ALT, 0, 0);
vn_pages_remove(vp,
OFF_TO_IDX(lblktosize(fs, -extblocks)), 0);
osize = ip->i_din2->di_extsize;
ip->i_din2->di_blocks -= extblocks;
ip->i_din2->di_extsize = 0;
for (i = 0; i < UFS_NXADDR; i++) {
oldblks[i] = ip->i_din2->di_extb[i];
ip->i_din2->di_extb[i] = 0;
}
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE);
if ((error = ffs_update(vp, waitforupdate)))
return (error);
for (i = 0; i < UFS_NXADDR; i++) {
if (oldblks[i] == 0)
continue;
ffs_blkfree(ump, fs, ITODEVVP(ip), oldblks[i],
sblksize(fs, osize, i), ip->i_number,
vp->v_type, NULL, SINGLETON_KEY);
}
}
}
if ((flags & IO_NORMAL) == 0)
return (0);
if (vp->v_type == VLNK && ip->i_size < ump->um_maxsymlinklen) {
#ifdef INVARIANTS
if (length != 0)
panic("ffs_truncate: partial truncate of symlink");
#endif
bzero(DIP(ip, i_shortlink), (uint64_t)ip->i_size);
ip->i_size = 0;
DIP_SET(ip, i_size, 0);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
if (needextclean)
goto extclean;
return (ffs_update(vp, waitforupdate));
}
if (ip->i_size == length) {
UFS_INODE_SET_FLAG(ip, IN_CHANGE | IN_UPDATE);
if (needextclean)
goto extclean;
return (ffs_update(vp, 0));
}
if (fs->fs_ronly)
panic("ffs_truncate: read-only filesystem");
if (IS_SNAPSHOT(ip))
ffs_snapremove(vp);
cluster_init_vn(&ip->i_clusterw);
osize = ip->i_size;
if (osize < length) {
vnode_pager_setsize(vp, length);
flags |= BA_CLRBUF;
error = UFS_BALLOC(vp, length - 1, 1, cred, flags, &bp);
if (error) {
vnode_pager_setsize(vp, osize);
return (error);
}
ip->i_size = length;
DIP_SET(ip, i_size, length);
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
ffs_inode_bwrite(vp, bp, flags);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
return (ffs_update(vp, waitforupdate));
}
lbn = lblkno(fs, length - 1);
if (length == 0) {
blkno = -1;
} else if (lbn < UFS_NDADDR) {
blkno = DIP(ip, i_db[lbn]);
} else {
error = UFS_BALLOC(vp, lblktosize(fs, (off_t)lbn), fs->fs_bsize,
cred, BA_METAONLY, &bp);
if (error)
return (error);
indiroff = (lbn - UFS_NDADDR) % NINDIR(fs);
if (I_IS_UFS1(ip))
blkno = ((ufs1_daddr_t *)(bp->b_data))[indiroff];
else
blkno = ((ufs2_daddr_t *)(bp->b_data))[indiroff];
if (blkno != 0)
brelse(bp);
else if (flags & IO_SYNC)
bwrite(bp);
else
bdwrite(bp);
}
if (blkno == 0 && (error = ffs_syncvnode(vp, MNT_WAIT, 0)) != 0)
return (error);
if (blkno != 0 && DOINGSOFTDEP(vp)) {
if (softdeptrunc == 0 && journaltrunc == 0) {
if ((error = ffs_syncvnode(vp, MNT_WAIT, 0)) != 0)
return (error);
} else {
flags = IO_NORMAL | (needextclean ? IO_EXT: 0);
if (journaltrunc)
softdep_journal_freeblocks(ip, cred, length,
flags);
else
softdep_setup_freeblocks(ip, length, flags);
ASSERT_VOP_LOCKED(vp, "ffs_truncate1");
if (journaltrunc == 0) {
UFS_INODE_SET_FLAG(ip, IN_CHANGE | IN_UPDATE);
error = ffs_update(vp, 0);
}
return (error);
}
}
offset = blkoff(fs, length);
if (blkno != 0 && offset == 0) {
ip->i_size = length;
DIP_SET(ip, i_size, length);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
#ifdef UFS_DIRHASH
if (vp->v_type == VDIR && ip->i_dirhash != NULL)
ufsdirhash_dirtrunc(ip, length);
#endif
} else {
lbn = lblkno(fs, length);
flags |= BA_CLRBUF;
error = UFS_BALLOC(vp, length - 1, 1, cred, flags, &bp);
if (error)
return (error);
ffs_inode_bwrite(vp, bp, flags);
if (DOINGSOFTDEP(vp) && lbn < UFS_NDADDR &&
fragroundup(fs, blkoff(fs, length)) < fs->fs_bsize &&
(error = ffs_syncvnode(vp, MNT_WAIT, 0)) != 0)
return (error);
error = UFS_BALLOC(vp, length - 1, 1, cred, flags, &bp);
if (error)
return (error);
ip->i_size = length;
DIP_SET(ip, i_size, length);
#ifdef UFS_DIRHASH
if (vp->v_type == VDIR && ip->i_dirhash != NULL)
ufsdirhash_dirtrunc(ip, length);
#endif
size = blksize(fs, ip, lbn);
if (vp->v_type != VDIR && offset != 0)
bzero((char *)bp->b_data + offset,
(uint64_t)(size - offset));
allocbuf(bp, size);
if (bp->b_bufsize == fs->fs_bsize)
bp->b_flags |= B_CLUSTEROK;
ffs_inode_bwrite(vp, bp, flags);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
}
lastblock = lblkno(fs, length + fs->fs_bsize - 1) - 1;
lastiblock[SINGLE] = lastblock - UFS_NDADDR;
lastiblock[DOUBLE] = lastiblock[SINGLE] - NINDIR(fs);
lastiblock[TRIPLE] = lastiblock[DOUBLE] - NINDIR(fs) * NINDIR(fs);
nblocks = btodb(fs->fs_bsize);
for (level = TRIPLE; level >= SINGLE; level--) {
oldblks[UFS_NDADDR + level] = DIP(ip, i_ib[level]);
if (lastiblock[level] < 0) {
DIP_SET(ip, i_ib[level], 0);
lastiblock[level] = -1;
}
}
for (i = 0; i < UFS_NDADDR; i++) {
oldblks[i] = DIP(ip, i_db[i]);
if (i > lastblock)
DIP_SET(ip, i_db[i], 0);
}
UFS_INODE_SET_FLAG(ip, IN_CHANGE | IN_UPDATE);
allerror = ffs_update(vp, waitforupdate);
for (i = 0; i < UFS_NDADDR; i++) {
#ifdef INVARIANTS
newblks[i] = DIP(ip, i_db[i]);
#endif
DIP_SET(ip, i_db[i], oldblks[i]);
}
for (i = 0; i < UFS_NIADDR; i++) {
#ifdef INVARIANTS
newblks[UFS_NDADDR + i] = DIP(ip, i_ib[i]);
#endif
DIP_SET(ip, i_ib[i], oldblks[UFS_NDADDR + i]);
}
ip->i_size = osize;
DIP_SET(ip, i_size, osize);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
error = vtruncbuf(vp, length, fs->fs_bsize);
if (error && (allerror == 0))
allerror = error;
indir_lbn[SINGLE] = -UFS_NDADDR;
indir_lbn[DOUBLE] = indir_lbn[SINGLE] - NINDIR(fs) - 1;
indir_lbn[TRIPLE] = indir_lbn[DOUBLE] - NINDIR(fs) * NINDIR(fs) - 1;
for (level = TRIPLE; level >= SINGLE; level--) {
bn = DIP(ip, i_ib[level]);
if (bn != 0) {
error = ffs_indirtrunc(ip, indir_lbn[level],
fsbtodb(fs, bn), lastiblock[level], level, &count);
if (error)
allerror = error;
blocksreleased += count;
if (lastiblock[level] < 0) {
DIP_SET(ip, i_ib[level], 0);
ffs_blkfree(ump, fs, ump->um_devvp, bn,
fs->fs_bsize, ip->i_number,
vp->v_type, NULL, SINGLETON_KEY);
blocksreleased += nblocks;
}
}
if (lastiblock[level] >= 0)
goto done;
}
key = ffs_blkrelease_start(ump, ump->um_devvp, ip->i_number);
for (i = UFS_NDADDR - 1; i > lastblock; i--) {
long bsize;
bn = DIP(ip, i_db[i]);
if (bn == 0)
continue;
DIP_SET(ip, i_db[i], 0);
bsize = blksize(fs, ip, i);
ffs_blkfree(ump, fs, ump->um_devvp, bn, bsize, ip->i_number,
vp->v_type, NULL, key);
blocksreleased += btodb(bsize);
}
ffs_blkrelease_finish(ump, key);
if (lastblock < 0)
goto done;
bn = DIP(ip, i_db[lastblock]);
if (bn != 0) {
long oldspace, newspace;
oldspace = blksize(fs, ip, lastblock);
ip->i_size = length;
DIP_SET(ip, i_size, length);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
newspace = blksize(fs, ip, lastblock);
if (newspace == 0)
panic("ffs_truncate: newspace");
if (oldspace - newspace > 0) {
bn += numfrags(fs, newspace);
ffs_blkfree(ump, fs, ump->um_devvp, bn,
oldspace - newspace, ip->i_number, vp->v_type,
NULL, SINGLETON_KEY);
blocksreleased += btodb(oldspace - newspace);
}
}
done:
#ifdef INVARIANTS
for (level = SINGLE; level <= TRIPLE; level++)
if (newblks[UFS_NDADDR + level] != DIP(ip, i_ib[level]))
panic("ffs_truncate1: level %d newblks %jd != i_ib %jd",
level, (intmax_t)newblks[UFS_NDADDR + level],
(intmax_t)DIP(ip, i_ib[level]));
for (i = 0; i < UFS_NDADDR; i++)
if (newblks[i] != DIP(ip, i_db[i]))
panic("ffs_truncate2: blkno %d newblks %jd != i_db %jd",
i, (intmax_t)newblks[UFS_NDADDR + level],
(intmax_t)DIP(ip, i_ib[level]));
BO_LOCK(bo);
if (length == 0 &&
(fs->fs_magic != FS_UFS2_MAGIC || ip->i_din2->di_extsize == 0) &&
(bo->bo_dirty.bv_cnt > 0 || bo->bo_clean.bv_cnt > 0))
panic("ffs_truncate3: vp = %p, buffers: dirty = %d, clean = %d",
vp, bo->bo_dirty.bv_cnt, bo->bo_clean.bv_cnt);
BO_UNLOCK(bo);
#endif
ip->i_size = length;
DIP_SET(ip, i_size, length);
if (DIP(ip, i_blocks) >= blocksreleased)
DIP_SET(ip, i_blocks, DIP(ip, i_blocks) - blocksreleased);
else
DIP_SET(ip, i_blocks, 0);
UFS_INODE_SET_FLAG(ip, IN_SIZEMOD | IN_CHANGE);
#ifdef QUOTA
(void) chkdq(ip, -blocksreleased, NOCRED, FORCE);
#endif
return (allerror);
extclean:
if (journaltrunc)
softdep_journal_freeblocks(ip, cred, length, IO_EXT);
else
softdep_setup_freeblocks(ip, length, IO_EXT);
return (ffs_update(vp, waitforupdate));
}
static int
ffs_indirtrunc(struct inode *ip,
ufs2_daddr_t lbn,
ufs2_daddr_t dbn,
ufs2_daddr_t lastbn,
int level,
ufs2_daddr_t *countp)
{
struct buf *bp;
struct fs *fs;
struct ufsmount *ump;
struct vnode *vp;
caddr_t copy = NULL;
uint64_t key;
int i, nblocks, error = 0, allerror = 0;
ufs2_daddr_t nb, nlbn, last;
ufs2_daddr_t blkcount, factor, blocksreleased = 0;
ufs1_daddr_t *bap1 = NULL;
ufs2_daddr_t *bap2 = NULL;
#define BAP(ip, i) (I_IS_UFS1(ip) ? bap1[i] : bap2[i])
fs = ITOFS(ip);
ump = ITOUMP(ip);
factor = lbn_offset(fs, level);
last = lastbn;
if (lastbn > 0)
last /= factor;
nblocks = btodb(fs->fs_bsize);
vp = ITOV(ip);
error = ffs_breadz(ump, vp, lbn, dbn, (int)fs->fs_bsize, NULL, NULL, 0,
NOCRED, 0, NULL, &bp);
if (error) {
*countp = 0;
return (error);
}
if (I_IS_UFS1(ip))
bap1 = (ufs1_daddr_t *)bp->b_data;
else
bap2 = (ufs2_daddr_t *)bp->b_data;
if (lastbn != -1) {
copy = malloc(fs->fs_bsize, M_TEMP, M_WAITOK);
bcopy((caddr_t)bp->b_data, copy, (uint64_t)fs->fs_bsize);
for (i = last + 1; i < NINDIR(fs); i++)
if (I_IS_UFS1(ip))
bap1[i] = 0;
else
bap2[i] = 0;
if (DOINGASYNC(vp)) {
bdwrite(bp);
} else {
error = bwrite(bp);
if (error)
allerror = error;
}
if (I_IS_UFS1(ip))
bap1 = (ufs1_daddr_t *)copy;
else
bap2 = (ufs2_daddr_t *)copy;
}
key = ffs_blkrelease_start(ump, ITODEVVP(ip), ip->i_number);
for (i = NINDIR(fs) - 1, nlbn = lbn + 1 - i * factor; i > last;
i--, nlbn += factor) {
nb = BAP(ip, i);
if (nb == 0)
continue;
if (level > SINGLE) {
if ((error = ffs_indirtrunc(ip, nlbn, fsbtodb(fs, nb),
(ufs2_daddr_t)-1, level - 1, &blkcount)) != 0)
allerror = error;
blocksreleased += blkcount;
}
ffs_blkfree(ump, fs, ITODEVVP(ip), nb, fs->fs_bsize,
ip->i_number, vp->v_type, NULL, key);
blocksreleased += nblocks;
}
ffs_blkrelease_finish(ump, key);
if (level > SINGLE && lastbn >= 0) {
last = lastbn % factor;
nb = BAP(ip, i);
if (nb != 0) {
error = ffs_indirtrunc(ip, nlbn, fsbtodb(fs, nb),
last, level - 1, &blkcount);
if (error)
allerror = error;
blocksreleased += blkcount;
}
}
if (copy != NULL) {
free(copy, M_TEMP);
} else {
bp->b_flags |= B_INVAL | B_NOCACHE;
brelse(bp);
}
*countp = blocksreleased;
return (allerror);
}
int
ffs_rdonly(struct inode *ip)
{
return (ITOFS(ip)->fs_ronly != 0);
}