/* * 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; }