/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Portions of this source code were derived from Berkeley 4.3 BSD
* under license from the Regents of the University of California.
*/
#include <sys/sysmacros.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/t_lock.h>
#include <sys/uio.h>
#include <sys/kmem.h>
#include <sys/thread.h>
#include <sys/vfs.h>
#include <sys/errno.h>
#include <sys/buf.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_trans.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_fs.h>
#include <sys/fs/ufs_fsdir.h>
#include <sys/fs/ufs_quota.h>
#include <sys/fs/ufs_panic.h>
#include <sys/fs/ufs_bio.h>
#include <sys/fs/ufs_log.h>
#include <sys/cmn_err.h>
#include <sys/file.h>
#include <sys/debug.h>
extern kmutex_t ufsvfs_mutex;
extern struct ufsvfs *ufs_instances;
/*
* hlock any file systems w/errored logs
*/
int
ufs_trans_hlock()
{
struct ufsvfs *ufsvfsp;
struct lockfs lockfs;
int error;
int retry = 0;
/*
* find fs's that paniced or have errored logging devices
*/
mutex_enter(&ufsvfs_mutex);
for (ufsvfsp = ufs_instances; ufsvfsp; ufsvfsp = ufsvfsp->vfs_next) {
/*
* not mounted; continue
*/
if ((ufsvfsp->vfs_vfs == NULL) ||
(ufsvfsp->vfs_validfs == UT_UNMOUNTED))
continue;
/*
* disallow unmounts (hlock occurs below)
*/
if (TRANS_ISERROR(ufsvfsp))
ufsvfsp->vfs_validfs = UT_HLOCKING;
}
mutex_exit(&ufsvfs_mutex);
/*
* hlock the fs's that paniced or have errored logging devices
*/
again:
mutex_enter(&ufsvfs_mutex);
for (ufsvfsp = ufs_instances; ufsvfsp; ufsvfsp = ufsvfsp->vfs_next)
if (ufsvfsp->vfs_validfs == UT_HLOCKING)
break;
mutex_exit(&ufsvfs_mutex);
if (ufsvfsp == NULL)
return (retry);
/*
* hlock the file system
*/
(void) ufs_fiolfss(ufsvfsp->vfs_root, &lockfs);
if (!LOCKFS_IS_ELOCK(&lockfs)) {
lockfs.lf_lock = LOCKFS_HLOCK;
lockfs.lf_flags = 0;
lockfs.lf_comlen = 0;
lockfs.lf_comment = NULL;
error = ufs_fiolfs(ufsvfsp->vfs_root, &lockfs, 0);
/*
* retry after awhile; another app currently doing lockfs
*/
if (error == EBUSY || error == EINVAL)
retry = 1;
} else {
if (ufsfx_get_failure_qlen() > 0) {
if (mutex_tryenter(&ufs_fix.uq_mutex)) {
ufs_fix.uq_lowat = ufs_fix.uq_ne;
cv_broadcast(&ufs_fix.uq_cv);
mutex_exit(&ufs_fix.uq_mutex);
}
}
retry = 1;
}
/*
* allow unmounts
*/
ufsvfsp->vfs_validfs = UT_MOUNTED;
goto again;
}
/*ARGSUSED*/
void
ufs_trans_onerror()
{
mutex_enter(&ufs_hlock.uq_mutex);
ufs_hlock.uq_ne = ufs_hlock.uq_lowat;
cv_broadcast(&ufs_hlock.uq_cv);
mutex_exit(&ufs_hlock.uq_mutex);
}
void
ufs_trans_sbupdate(struct ufsvfs *ufsvfsp, struct vfs *vfsp, top_t topid)
{
if (curthread->t_flag & T_DONTBLOCK) {
sbupdate(vfsp);
return;
} else {
if (panicstr && TRANS_ISTRANS(ufsvfsp))
return;
curthread->t_flag |= T_DONTBLOCK;
TRANS_BEGIN_ASYNC(ufsvfsp, topid, TOP_SBUPDATE_SIZE);
sbupdate(vfsp);
TRANS_END_ASYNC(ufsvfsp, topid, TOP_SBUPDATE_SIZE);
curthread->t_flag &= ~T_DONTBLOCK;
}
}
void
ufs_trans_iupdat(struct inode *ip, int waitfor)
{
struct ufsvfs *ufsvfsp;
if (curthread->t_flag & T_DONTBLOCK) {
rw_enter(&ip->i_contents, RW_READER);
ufs_iupdat(ip, waitfor);
rw_exit(&ip->i_contents);
return;
} else {
ufsvfsp = ip->i_ufsvfs;
if (panicstr && TRANS_ISTRANS(ufsvfsp))
return;
curthread->t_flag |= T_DONTBLOCK;
TRANS_BEGIN_ASYNC(ufsvfsp, TOP_IUPDAT, TOP_IUPDAT_SIZE(ip));
rw_enter(&ip->i_contents, RW_READER);
ufs_iupdat(ip, waitfor);
rw_exit(&ip->i_contents);
TRANS_END_ASYNC(ufsvfsp, TOP_IUPDAT, TOP_IUPDAT_SIZE(ip));
curthread->t_flag &= ~T_DONTBLOCK;
}
}
void
ufs_trans_sbwrite(struct ufsvfs *ufsvfsp, top_t topid)
{
if (curthread->t_flag & T_DONTBLOCK) {
mutex_enter(&ufsvfsp->vfs_lock);
ufs_sbwrite(ufsvfsp);
mutex_exit(&ufsvfsp->vfs_lock);
return;
} else {
if (panicstr && TRANS_ISTRANS(ufsvfsp))
return;
curthread->t_flag |= T_DONTBLOCK;
TRANS_BEGIN_ASYNC(ufsvfsp, topid, TOP_SBWRITE_SIZE);
mutex_enter(&ufsvfsp->vfs_lock);
ufs_sbwrite(ufsvfsp);
mutex_exit(&ufsvfsp->vfs_lock);
TRANS_END_ASYNC(ufsvfsp, topid, TOP_SBWRITE_SIZE);
curthread->t_flag &= ~T_DONTBLOCK;
}
}
/*ARGSUSED*/
int
ufs_trans_push_si(ufsvfs_t *ufsvfsp, delta_t dtyp, int ignore)
{
struct fs *fs;
fs = ufsvfsp->vfs_fs;
mutex_enter(&ufsvfsp->vfs_lock);
TRANS_LOG(ufsvfsp, (char *)fs->fs_u.fs_csp,
ldbtob(fsbtodb(fs, fs->fs_csaddr)), fs->fs_cssize,
(caddr_t)fs->fs_u.fs_csp, fs->fs_cssize);
mutex_exit(&ufsvfsp->vfs_lock);
return (0);
}
/*ARGSUSED*/
int
ufs_trans_push_buf(ufsvfs_t *ufsvfsp, delta_t dtyp, daddr_t bno)
{
struct buf *bp;
bp = (struct buf *)UFS_GETBLK(ufsvfsp, ufsvfsp->vfs_dev, bno, 1);
if (bp == NULL)
return (ENOENT);
if (bp->b_flags & B_DELWRI) {
/*
* Do not use brwrite() here since the buffer is already
* marked for retry or not by the code that called
* TRANS_BUF().
*/
UFS_BWRITE(ufsvfsp, bp);
return (0);
}
/*
* If we did not find the real buf for this block above then
* clear the dev so the buf won't be found by mistake
* for this block later. We had to allocate at least a 1 byte
* buffer to keep brelse happy.
*/
if (bp->b_bufsize == 1) {
bp->b_dev = (o_dev_t)NODEV;
bp->b_edev = NODEV;
bp->b_flags = 0;
}
brelse(bp);
return (ENOENT);
}
/*ARGSUSED*/
int
ufs_trans_push_inode(ufsvfs_t *ufsvfsp, delta_t dtyp, ino_t ino)
{
int error;
struct inode *ip;
/*
* Grab the quota lock (if the file system has not been forcibly
* unmounted).
*/
if (ufsvfsp)
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
error = ufs_iget(ufsvfsp->vfs_vfs, ino, &ip, kcred);
if (ufsvfsp)
rw_exit(&ufsvfsp->vfs_dqrwlock);
if (error)
return (ENOENT);
if (ip->i_flag & (IUPD|IACC|ICHG|IMOD|IMODACC|IATTCHG)) {
rw_enter(&ip->i_contents, RW_READER);
ufs_iupdat(ip, 1);
rw_exit(&ip->i_contents);
VN_RELE(ITOV(ip));
return (0);
}
VN_RELE(ITOV(ip));
return (ENOENT);
}
#ifdef DEBUG
/*
* These routines maintain the metadata map (matamap)
*/
/*
* update the metadata map at mount
*/
static int
ufs_trans_mata_mount_scan(struct inode *ip, void *arg)
{
/*
* wrong file system; keep looking
*/
if (ip->i_ufsvfs != (struct ufsvfs *)arg)
return (0);
/*
* load the metadata map
*/
rw_enter(&ip->i_contents, RW_WRITER);
ufs_trans_mata_iget(ip);
rw_exit(&ip->i_contents);
return (0);
}
void
ufs_trans_mata_mount(struct ufsvfs *ufsvfsp)
{
struct fs *fs = ufsvfsp->vfs_fs;
ino_t ino;
int i;
/*
* put static metadata into matamap
* superblock
* cylinder groups
* inode groups
* existing inodes
*/
TRANS_MATAADD(ufsvfsp, ldbtob(SBLOCK), fs->fs_sbsize);
for (ino = i = 0; i < fs->fs_ncg; ++i, ino += fs->fs_ipg) {
TRANS_MATAADD(ufsvfsp,
ldbtob(fsbtodb(fs, cgtod(fs, i))), fs->fs_cgsize);
TRANS_MATAADD(ufsvfsp,
ldbtob(fsbtodb(fs, itod(fs, ino))),
fs->fs_ipg * sizeof (struct dinode));
}
(void) ufs_scan_inodes(0, ufs_trans_mata_mount_scan, ufsvfsp, ufsvfsp);
}
/*
* clear the metadata map at umount
*/
void
ufs_trans_mata_umount(struct ufsvfs *ufsvfsp)
{
top_mataclr(ufsvfsp);
}
/*
* summary info (may be extended during growfs test)
*/
void
ufs_trans_mata_si(struct ufsvfs *ufsvfsp, struct fs *fs)
{
TRANS_MATAADD(ufsvfsp, ldbtob(fsbtodb(fs, fs->fs_csaddr)),
fs->fs_cssize);
}
/*
* scan an allocation block (either inode or true block)
*/
static void
ufs_trans_mata_direct(
struct inode *ip,
daddr_t *fragsp,
daddr32_t *blkp,
unsigned int nblk)
{
int i;
daddr_t frag;
ulong_t nb;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
struct fs *fs = ufsvfsp->vfs_fs;
for (i = 0; i < nblk && *fragsp; ++i, ++blkp)
if ((frag = *blkp) != 0) {
if (*fragsp > fs->fs_frag) {
nb = fs->fs_bsize;
*fragsp -= fs->fs_frag;
} else {
nb = *fragsp * fs->fs_fsize;
*fragsp = 0;
}
TRANS_MATAADD(ufsvfsp, ldbtob(fsbtodb(fs, frag)), nb);
}
}
/*
* scan an indirect allocation block (either inode or true block)
*/
static void
ufs_trans_mata_indir(
struct inode *ip,
daddr_t *fragsp,
daddr_t frag,
int level)
{
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
struct fs *fs = ufsvfsp->vfs_fs;
int ne = fs->fs_bsize / (int)sizeof (daddr32_t);
int i;
struct buf *bp;
daddr32_t *blkp;
o_mode_t ifmt = ip->i_mode & IFMT;
bp = UFS_BREAD(ufsvfsp, ip->i_dev, fsbtodb(fs, frag), fs->fs_bsize);
if (bp->b_flags & B_ERROR) {
brelse(bp);
return;
}
blkp = bp->b_un.b_daddr;
if (level || (ifmt == IFDIR) || (ifmt == IFSHAD) ||
(ifmt == IFATTRDIR) || (ip == ip->i_ufsvfs->vfs_qinod))
ufs_trans_mata_direct(ip, fragsp, blkp, ne);
if (level)
for (i = 0; i < ne && *fragsp; ++i, ++blkp)
ufs_trans_mata_indir(ip, fragsp, *blkp, level-1);
brelse(bp);
}
/*
* put appropriate metadata into matamap for this inode
*/
void
ufs_trans_mata_iget(struct inode *ip)
{
int i;
daddr_t frags = dbtofsb(ip->i_fs, ip->i_blocks);
o_mode_t ifmt = ip->i_mode & IFMT;
if (frags && ((ifmt == IFDIR) || (ifmt == IFSHAD) ||
(ifmt == IFATTRDIR) || (ip == ip->i_ufsvfs->vfs_qinod)))
ufs_trans_mata_direct(ip, &frags, &ip->i_db[0], NDADDR);
if (frags)
ufs_trans_mata_direct(ip, &frags, &ip->i_ib[0], NIADDR);
for (i = 0; i < NIADDR && frags; ++i)
if (ip->i_ib[i])
ufs_trans_mata_indir(ip, &frags, ip->i_ib[i], i);
}
/*
* freeing possible metadata (block of user data)
*/
void
ufs_trans_mata_free(struct ufsvfs *ufsvfsp, offset_t mof, off_t nb)
{
top_matadel(ufsvfsp, mof, nb);
}
/*
* allocating metadata
*/
void
ufs_trans_mata_alloc(
struct ufsvfs *ufsvfsp,
struct inode *ip,
daddr_t frag,
ulong_t nb,
int indir)
{
struct fs *fs = ufsvfsp->vfs_fs;
o_mode_t ifmt = ip->i_mode & IFMT;
if (indir || ((ifmt == IFDIR) || (ifmt == IFSHAD) ||
(ifmt == IFATTRDIR) || (ip == ip->i_ufsvfs->vfs_qinod)))
TRANS_MATAADD(ufsvfsp, ldbtob(fsbtodb(fs, frag)), nb);
}
#endif /* DEBUG */
/*
* ufs_trans_dir is used to declare a directory delta
*/
int
ufs_trans_dir(struct inode *ip, off_t offset)
{
daddr_t bn;
int contig = 0, error;
ASSERT(ip);
ASSERT(RW_WRITE_HELD(&ip->i_contents));
error = bmap_read(ip, (u_offset_t)offset, &bn, &contig);
if (error || (bn == UFS_HOLE)) {
cmn_err(CE_WARN, "ufs_trans_dir - could not get block"
" number error = %d bn = %d\n", error, (int)bn);
if (error == 0) /* treat UFS_HOLE as an I/O error */
error = EIO;
return (error);
}
TRANS_DELTA(ip->i_ufsvfs, ldbtob(bn), DIRBLKSIZ, DT_DIR, 0, 0);
return (error);
}
/*ARGSUSED*/
int
ufs_trans_push_quota(ufsvfs_t *ufsvfsp, delta_t dtyp, struct dquot *dqp)
{
/*
* Lock the quota subsystem (ufsvfsp can be NULL
* if the DQ_ERROR is set).
*/
if (ufsvfsp)
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
mutex_enter(&dqp->dq_lock);
/*
* If this transaction has been cancelled by closedq_scan_inode(),
* then bail out now. We don't call dqput() in this case because
* it has already been done.
*/
if ((dqp->dq_flags & DQ_TRANS) == 0) {
mutex_exit(&dqp->dq_lock);
if (ufsvfsp)
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
if (dqp->dq_flags & DQ_ERROR) {
/*
* Paranoia to make sure that there is at least one
* reference to the dquot struct. We are done with
* the dquot (due to an error) so clear logging
* specific markers.
*/
ASSERT(dqp->dq_cnt >= 1);
dqp->dq_flags &= ~DQ_TRANS;
dqput(dqp);
mutex_exit(&dqp->dq_lock);
if (ufsvfsp)
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (1);
}
if (dqp->dq_flags & (DQ_MOD | DQ_BLKS | DQ_FILES)) {
ASSERT((dqp->dq_mof != UFS_HOLE) && (dqp->dq_mof != 0));
TRANS_LOG(ufsvfsp, (caddr_t)&dqp->dq_dqb,
dqp->dq_mof, (int)sizeof (struct dqblk), NULL, 0);
/*
* Paranoia to make sure that there is at least one
* reference to the dquot struct. Clear the
* modification flag because the operation is now in
* the log. Also clear the logging specific markers
* that were set in ufs_trans_quota().
*/
ASSERT(dqp->dq_cnt >= 1);
dqp->dq_flags &= ~(DQ_MOD | DQ_TRANS);
dqput(dqp);
}
/*
* At this point, the logging specific flag should be clear,
* but add paranoia just in case something has gone wrong.
*/
ASSERT((dqp->dq_flags & DQ_TRANS) == 0);
mutex_exit(&dqp->dq_lock);
if (ufsvfsp)
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
/*
* ufs_trans_quota take in a uid, allocates the disk space, placing the
* quota record into the metamap, then declares the delta.
*/
/*ARGSUSED*/
void
ufs_trans_quota(struct dquot *dqp)
{
struct inode *qip = dqp->dq_ufsvfsp->vfs_qinod;
ASSERT(qip);
ASSERT(MUTEX_HELD(&dqp->dq_lock));
ASSERT(dqp->dq_flags & DQ_MOD);
ASSERT(dqp->dq_mof != 0);
ASSERT(dqp->dq_mof != UFS_HOLE);
/*
* Mark this dquot to indicate that we are starting a logging
* file system operation for this dquot. Also increment the
* reference count so that the dquot does not get reused while
* it is on the mapentry_t list. DQ_TRANS is cleared and the
* reference count is decremented by ufs_trans_push_quota.
*
* If the file system is force-unmounted while there is a
* pending quota transaction, then closedq_scan_inode() will
* clear the DQ_TRANS flag and decrement the reference count.
*
* Since deltamap_add() drops multiple transactions to the
* same dq_mof and ufs_trans_push_quota() won't get called,
* we use DQ_TRANS to prevent repeat transactions from
* incrementing the reference count (or calling TRANS_DELTA()).
*/
if ((dqp->dq_flags & DQ_TRANS) == 0) {
dqp->dq_flags |= DQ_TRANS;
dqp->dq_cnt++;
TRANS_DELTA(qip->i_ufsvfs, dqp->dq_mof, sizeof (struct dqblk),
DT_QR, ufs_trans_push_quota, (ulong_t)dqp);
}
}
void
ufs_trans_dqrele(struct dquot *dqp)
{
struct ufsvfs *ufsvfsp = dqp->dq_ufsvfsp;
curthread->t_flag |= T_DONTBLOCK;
TRANS_BEGIN_ASYNC(ufsvfsp, TOP_QUOTA, TOP_QUOTA_SIZE);
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
dqrele(dqp);
rw_exit(&ufsvfsp->vfs_dqrwlock);
TRANS_END_ASYNC(ufsvfsp, TOP_QUOTA, TOP_QUOTA_SIZE);
curthread->t_flag &= ~T_DONTBLOCK;
}
int ufs_trans_max_resv = TOP_MAX_RESV; /* will be adjusted for testing */
long ufs_trans_avgbfree = 0; /* will be adjusted for testing */
#define TRANS_MAX_WRITE (1024 * 1024)
size_t ufs_trans_max_resid = TRANS_MAX_WRITE;
/*
* Calculate the log reservation for the given write or truncate
*/
static ulong_t
ufs_log_amt(struct inode *ip, offset_t offset, ssize_t resid, int trunc)
{
long ncg, last2blk;
long niblk = 0;
u_offset_t writeend, offblk;
int resv;
daddr_t nblk, maxfblk;
long avgbfree;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
struct fs *fs = ufsvfsp->vfs_fs;
long fni = NINDIR(fs);
int bsize = fs->fs_bsize;
/*
* Assume that the request will fit in 1 or 2 cg's,
* resv is the amount of log space to reserve (in bytes).
*/
resv = SIZECG(ip) * 2 + INODESIZE + 1024;
/*
* get max position of write in fs blocks
*/
writeend = offset + resid;
maxfblk = lblkno(fs, writeend);
offblk = lblkno(fs, offset);
/*
* request size in fs blocks
*/
nblk = lblkno(fs, blkroundup(fs, resid));
/*
* Adjust for sparse files
*/
if (trunc)
nblk = MIN(nblk, ip->i_blocks);
/*
* Adjust avgbfree (for testing)
*/
avgbfree = (ufs_trans_avgbfree) ? 1 : ufsvfsp->vfs_avgbfree + 1;
/*
* Calculate maximum number of blocks of triple indirect
* pointers to write.
*/
last2blk = NDADDR + fni + fni * fni;
if (maxfblk > last2blk) {
long nl2ptr;
long n3blk;
if (offblk > last2blk)
n3blk = maxfblk - offblk;
else
n3blk = maxfblk - last2blk;
niblk += roundup(n3blk * sizeof (daddr_t), bsize) / bsize + 1;
nl2ptr = roundup(niblk, fni) / fni + 1;
niblk += roundup(nl2ptr * sizeof (daddr_t), bsize) / bsize + 2;
maxfblk -= n3blk;
}
/*
* calculate maximum number of blocks of double indirect
* pointers to write.
*/
if (maxfblk > NDADDR + fni) {
long n2blk;
if (offblk > NDADDR + fni)
n2blk = maxfblk - offblk;
else
n2blk = maxfblk - NDADDR + fni;
niblk += roundup(n2blk * sizeof (daddr_t), bsize) / bsize + 2;
maxfblk -= n2blk;
}
/*
* Add in indirect pointer block write
*/
if (maxfblk > NDADDR) {
niblk += 1;
}
/*
* Calculate deltas for indirect pointer writes
*/
resv += niblk * (fs->fs_bsize + sizeof (struct delta));
/*
* maximum number of cg's needed for request
*/
ncg = nblk / avgbfree;
if (ncg > fs->fs_ncg)
ncg = fs->fs_ncg;
/*
* maximum amount of log space needed for request
*/
if (ncg > 2)
resv += (ncg - 2) * SIZECG(ip);
return (resv);
}
/*
* Calculate the amount of log space that needs to be reserved for this
* trunc request. If the amount of log space is too large, then
* calculate the the size that the requests needs to be split into.
*/
void
ufs_trans_trunc_resv(
struct inode *ip,
u_offset_t length,
int *resvp,
u_offset_t *residp)
{
ulong_t resv;
u_offset_t size, offset, resid;
int nchunks, flag;
/*
* *resvp is the amount of log space to reserve (in bytes).
* when nonzero, *residp is the number of bytes to truncate.
*/
*residp = 0;
if (length < ip->i_size) {
size = ip->i_size - length;
} else {
resv = SIZECG(ip) * 2 + INODESIZE + 1024;
/*
* truncate up, doesn't really use much space,
* the default above should be sufficient.
*/
goto done;
}
offset = length;
resid = size;
nchunks = 1;
flag = 0;
/*
* If this request takes too much log space, it will be split into
* "nchunks". If this split is not enough, linearly increment the
* nchunks in the next iteration.
*/
for (; (resv = ufs_log_amt(ip, offset, resid, 1)) > ufs_trans_max_resv;
offset = length + (nchunks - 1) * resid) {
if (!flag) {
nchunks = roundup(resv, ufs_trans_max_resv) /
ufs_trans_max_resv;
flag = 1;
} else {
nchunks++;
}
resid = size / nchunks;
}
if (nchunks > 1) {
*residp = resid;
}
done:
*resvp = resv;
}
int
ufs_trans_itrunc(struct inode *ip, u_offset_t length, int flags, cred_t *cr)
{
int err, issync, resv;
u_offset_t resid;
int do_block = 0;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
struct fs *fs = ufsvfsp->vfs_fs;
/*
* Not logging; just do the trunc
*/
if (!TRANS_ISTRANS(ufsvfsp)) {
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
rw_enter(&ip->i_contents, RW_WRITER);
err = ufs_itrunc(ip, length, flags, cr);
rw_exit(&ip->i_contents);
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (err);
}
/*
* within the lockfs protocol but *not* part of a transaction
*/
do_block = curthread->t_flag & T_DONTBLOCK;
curthread->t_flag |= T_DONTBLOCK;
/*
* Trunc the file (in pieces, if necessary)
*/
again:
ufs_trans_trunc_resv(ip, length, &resv, &resid);
TRANS_BEGIN_CSYNC(ufsvfsp, issync, TOP_ITRUNC, resv);
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
rw_enter(&ip->i_contents, RW_WRITER);
if (resid) {
/*
* resid is only set if we have to truncate in chunks
*/
ASSERT(length + resid < ip->i_size);
/*
* Partially trunc file down to desired size (length).
* Only retain I_FREE on the last partial trunc.
* Round up size to a block boundary, to ensure the truncate
* doesn't have to allocate blocks. This is done both for
* performance and to fix a bug where if the block can't be
* allocated then the inode delete fails, but the inode
* is still freed with attached blocks and non-zero size
* (bug 4348738).
*/
err = ufs_itrunc(ip, blkroundup(fs, (ip->i_size - resid)),
flags & ~I_FREE, cr);
ASSERT(ip->i_size != length);
} else
err = ufs_itrunc(ip, length, flags, cr);
if (!do_block)
curthread->t_flag &= ~T_DONTBLOCK;
rw_exit(&ip->i_contents);
rw_exit(&ufsvfsp->vfs_dqrwlock);
TRANS_END_CSYNC(ufsvfsp, err, issync, TOP_ITRUNC, resv);
if ((err == 0) && resid) {
ufsvfsp->vfs_avgbfree = fs->fs_cstotal.cs_nbfree / fs->fs_ncg;
goto again;
}
return (err);
}
/*
* Calculate the amount of log space that needs to be reserved for this
* write request. If the amount of log space is too large, then
* calculate the size that the requests needs to be split into.
* First try fixed chunks of size ufs_trans_max_resid. If that
* is too big, iterate down to the largest size that will fit.
* Pagein the pages in the first chunk here, so that the pagein is
* avoided later when the transaction is open.
*/
void
ufs_trans_write_resv(
struct inode *ip,
struct uio *uio,
int *resvp,
int *residp)
{
ulong_t resv;
offset_t offset;
ssize_t resid;
int nchunks;
*residp = 0;
offset = uio->uio_offset;
resid = MIN(uio->uio_resid, ufs_trans_max_resid);
resv = ufs_log_amt(ip, offset, resid, 0);
if (resv <= ufs_trans_max_resv) {
uio_prefaultpages(resid, uio);
if (resid != uio->uio_resid)
*residp = resid;
*resvp = resv;
return;
}
resid = uio->uio_resid;
nchunks = 1;
for (; (resv = ufs_log_amt(ip, offset, resid, 0)) > ufs_trans_max_resv;
offset = uio->uio_offset + (nchunks - 1) * resid) {
nchunks++;
resid = uio->uio_resid / nchunks;
}
uio_prefaultpages(resid, uio);
/*
* If this request takes too much log space, it will be split
*/
if (nchunks > 1)
*residp = resid;
*resvp = resv;
}
/*
* Issue write request.
*
* Split a large request into smaller chunks.
*/
int
ufs_trans_write(
struct inode *ip,
struct uio *uio,
int ioflag,
cred_t *cr,
int resv,
long resid)
{
long realresid;
int err;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
/*
* since the write is too big and would "HOG THE LOG" it needs to
* be broken up and done in pieces. NOTE, the caller will
* issue the EOT after the request has been completed
*/
realresid = uio->uio_resid;
again:
/*
* Perform partial request (uiomove will update uio for us)
* Request is split up into "resid" size chunks until
* "realresid" bytes have been transferred.
*/
uio->uio_resid = MIN(resid, realresid);
realresid -= uio->uio_resid;
err = wrip(ip, uio, ioflag, cr);
/*
* Error or request is done; caller issues final EOT
*/
if (err || uio->uio_resid || (realresid == 0)) {
uio->uio_resid += realresid;
return (err);
}
/*
* Generate EOT for this part of the request
*/
rw_exit(&ip->i_contents);
rw_exit(&ufsvfsp->vfs_dqrwlock);
if (ioflag & (FSYNC|FDSYNC)) {
TRANS_END_SYNC(ufsvfsp, err, TOP_WRITE_SYNC, resv);
} else {
TRANS_END_ASYNC(ufsvfsp, TOP_WRITE, resv);
}
/*
* Make sure the input buffer is resident before starting
* the next transaction.
*/
uio_prefaultpages(MIN(resid, realresid), uio);
/*
* Generate BOT for next part of the request
*/
if (ioflag & (FSYNC|FDSYNC)) {
int error;
TRANS_BEGIN_SYNC(ufsvfsp, TOP_WRITE_SYNC, resv, error);
ASSERT(!error);
} else {
TRANS_BEGIN_ASYNC(ufsvfsp, TOP_WRITE, resv);
}
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
rw_enter(&ip->i_contents, RW_WRITER);
/*
* Error during EOT (probably device error while writing commit rec)
*/
if (err)
return (err);
goto again;
}