/* * ufs_alloc_data - supports allocating space and reads or writes * that involve changes to file length or space allocation. * * This function is more expensive, because of the UFS log transaction, * so ufs_rdwr_data() should be used when space or file length changes * will not occur. * * Inputs: * fdb - A null pointer instructs this function to only allocate * space for the specified offset and length. * An actual fdbuffer instructs this function to perform * the read or write operation. * flags - defaults (zero value) to synchronous write * B_READ - indicates read operation * B_ASYNC - indicates perform operation asynchronously */ int ufs_alloc_data( vnode_t *vnodep, u_offset_t offset, size_t *len, fdbuffer_t *fdbp, int flags, cred_t *credp) { struct inode *ip = VTOI(vnodep); size_t done_len, io_len; int contig; u_offset_t uoff, io_off; int error = 0; /* No error occured */ int offsetn; /* Start point this IO */ int nbytes; /* Number bytes in this IO */ daddr_t bn; struct fs *fs; struct ufsvfs *ufsvfsp = ip->i_ufsvfs; int i_size_changed = 0; u_offset_t old_i_size; struct ulockfs *ulp; int trans_size; int issync; /* UFS Log transaction */ /* synchronous when non-zero */ int io_started = 0; /* No IO started */ uint_t protp = PROT_ALL; ASSERT((flags & B_WRITE) == 0); /* * Obey the lockfs protocol */ error = ufs_lockfs_begin_getpage(ufsvfsp, &ulp, segkmap, 0, &protp); if (error) { if ((fdbp != NULL) && (flags & B_ASYNC)) { fdb_ioerrdone(fdbp, error); } return (error); } if (ulp) { /* * Try to begin a UFS log transaction */ trans_size = TOP_GETPAGE_SIZE(ip); TRANS_TRY_BEGIN_CSYNC(ufsvfsp, issync, TOP_GETPAGE, trans_size, error); if (error == EWOULDBLOCK) { ufs_lockfs_end(ulp); if ((fdbp != NULL) && (flags & B_ASYNC)) { fdb_ioerrdone(fdbp, EDEADLK); } return (EDEADLK); } } uoff = offset; io_off = offset; io_len = *len; done_len = 0; DEBUGF((CE_CONT, "?ufs_alloc: off %llx len %lx size %llx fdb: %p\n", uoff, (io_len - done_len), ip->i_size, (void *)fdbp)); rw_enter(&ip->i_ufsvfs->vfs_dqrwlock, RW_READER); rw_enter(&ip->i_contents, RW_WRITER); ASSERT((ip->i_mode & IFMT) == IFREG); fs = ip->i_fs; while (error == 0 && done_len < io_len) { uoff = (u_offset_t)(io_off + done_len); offsetn = (int)blkoff(fs, uoff); nbytes = (int)MIN(fs->fs_bsize - offsetn, io_len - done_len); DEBUGF((CE_CONT, "?ufs_alloc_data: offset: %llx len %x\n", uoff, nbytes)); if (uoff + nbytes > ip->i_size) { /* * We are extending the length of the file. * bmap is used so that we are sure that * if we need to allocate new blocks, that it * is done here before we up the file size. */ DEBUGF((CE_CONT, "?ufs_alloc_data: grow %llx -> %llx\n", ip->i_size, uoff + nbytes)); error = bmap_write(ip, uoff, (offsetn + nbytes), BI_ALLOC_ONLY, NULL, credp); if (ip->i_flag & (ICHG|IUPD)) ip->i_seq++; if (error) { DEBUGF((CE_CONT, "?ufs_alloc_data: grow " "failed err: %d\n", error)); break; } if (fdbp != NULL) { if (uoff >= ip->i_size) { /* * Desired offset is past end of bytes * in file, so we have a hole. */ fdb_add_hole(fdbp, uoff - offset, nbytes); } else { int contig; buf_t *bp; error = bmap_read(ip, uoff, &bn, &contig); if (error) { break; } contig = ip->i_size - uoff; contig = P2ROUNDUP(contig, DEV_BSIZE); bp = fdb_iosetup(fdbp, uoff - offset, contig, vnodep, flags); bp->b_edev = ip->i_dev; bp->b_dev = cmpdev(ip->i_dev); bp->b_blkno = bn; bp->b_file = ip->i_vnode; bp->b_offset = (offset_t)uoff; if (ufsvfsp->vfs_snapshot) { fssnap_strategy( &ufsvfsp->vfs_snapshot, bp); } else { (void) bdev_strategy(bp); } io_started = 1; lwp_stat_update(LWP_STAT_OUBLK, 1); if ((flags & B_ASYNC) == 0) { error = biowait(bp); fdb_iodone(bp); if (error) { break; } } if (contig > (ip->i_size - uoff)) { contig -= ip->i_size - uoff; fdb_add_hole(fdbp, ip->i_size - offset, contig); } } } i_size_changed = 1; old_i_size = ip->i_size; UFS_SET_ISIZE(uoff + nbytes, ip); TRANS_INODE(ip->i_ufsvfs, ip); /* * file has grown larger than 2GB. Set flag * in superblock to indicate this, if it * is not already set. */ if ((ip->i_size > MAXOFF32_T) && !(fs->fs_flags & FSLARGEFILES)) { ASSERT(ufsvfsp->vfs_lfflags & UFS_LARGEFILES); mutex_enter(&ufsvfsp->vfs_lock); fs->fs_flags |= FSLARGEFILES; ufs_sbwrite(ufsvfsp); mutex_exit(&ufsvfsp->vfs_lock); } } else { /* * The file length is not being extended. */ error = bmap_read(ip, uoff, &bn, &contig); if (error) { DEBUGF((CE_CONT, "?ufs_alloc_data: " "bmap_read err: %d\n", error)); break; } if (bn != UFS_HOLE) { /* * Did not map a hole in the file */ int contig = P2ROUNDUP(nbytes, DEV_BSIZE); buf_t *bp; if (fdbp != NULL) { bp = fdb_iosetup(fdbp, uoff - offset, contig, vnodep, flags); bp->b_edev = ip->i_dev; bp->b_dev = cmpdev(ip->i_dev); bp->b_blkno = bn; bp->b_file = ip->i_vnode; bp->b_offset = (offset_t)uoff; if (ufsvfsp->vfs_snapshot) { fssnap_strategy( &ufsvfsp->vfs_snapshot, bp); } else { (void) bdev_strategy(bp); } io_started = 1; lwp_stat_update(LWP_STAT_OUBLK, 1); if ((flags & B_ASYNC) == 0) { error = biowait(bp); fdb_iodone(bp); if (error) { break; } } } } else { /* * We read a hole in the file. * We have to allocate blocks for the hole. */ error = bmap_write(ip, uoff, (offsetn + nbytes), BI_ALLOC_ONLY, NULL, credp); if (ip->i_flag & (ICHG|IUPD)) ip->i_seq++; if (error) { DEBUGF((CE_CONT, "?ufs_alloc_data: fill" " hole failed error: %d\n", error)); break; } if (fdbp != NULL) { fdb_add_hole(fdbp, uoff - offset, nbytes); } } } done_len += nbytes; } if (error) { if (i_size_changed) { /* * Allocation of the blocks for the file failed. * So truncate the file size back to its original size. */ (void) ufs_itrunc(ip, old_i_size, 0, credp); } } DEBUGF((CE_CONT, "?ufs_alloc: uoff %llx len %lx\n", uoff, (io_len - done_len))); if ((offset + *len) < (NDADDR * fs->fs_bsize)) { *len = (size_t)(roundup(offset + *len, fs->fs_fsize) - offset); } else { *len = (size_t)(roundup(offset + *len, fs->fs_bsize) - offset); } /* * Flush cached pages. * * XXX - There should be no pages involved, since the I/O was performed * through the device strategy routine and the page cache was bypassed. * However, testing has demonstrated that this VOP_PUTPAGE is * necessary. Without this, data might not always be read back as it * was written. * */ (void) VOP_PUTPAGE(vnodep, 0, 0, B_INVAL, credp); rw_exit(&ip->i_contents); rw_exit(&ip->i_ufsvfs->vfs_dqrwlock); if ((fdbp != NULL) && (flags & B_ASYNC)) { /* * Show that no more asynchronous IO will be added */ fdb_ioerrdone(fdbp, error); } if (ulp) { /* * End the UFS Log transaction */ TRANS_END_CSYNC(ufsvfsp, error, issync, TOP_GETPAGE, trans_size); ufs_lockfs_end(ulp); } if (io_started && (flags & B_ASYNC)) { return (0); } else { return (error); } }
/* * Set the quota file up for a particular file system. * Called as the result of a quotaon (Q_QUOTAON) ioctl. */ static int opendq( struct ufsvfs *ufsvfsp, struct vnode *vp, /* quota file */ struct cred *cr) { struct inode *qip; struct dquot *dqp; int error; int quotaon = 0; if (secpolicy_fs_quota(cr, ufsvfsp->vfs_vfs) != 0) return (EPERM); VN_HOLD(vp); /* * Check to be sure its a regular file. */ if (vp->v_type != VREG) { VN_RELE(vp); return (EACCES); } rw_enter(&ufsvfsp->vfs_dqrwlock, RW_WRITER); /* * We have vfs_dqrwlock as writer, so if quotas are disabled, * then vfs_qinod should be NULL or we have a race somewhere. */ ASSERT((ufsvfsp->vfs_qflags & MQ_ENABLED) || (ufsvfsp->vfs_qinod == 0)); if ((ufsvfsp->vfs_qflags & MQ_ENABLED) != 0) { /* * Quotas are already enabled on this file system. * * If the "quotas" file was replaced (different inode) * while quotas were enabled we don't want to re-enable * them with a new "quotas" file. Simply print a warning * message to the console, release the new vnode, and * return. * XXX - The right way to fix this is to return EBUSY * for the ioctl() issued by 'quotaon'. */ if (VTOI(vp) != ufsvfsp->vfs_qinod) { cmn_err(CE_WARN, "Previous quota file still in use." " Disable quotas on %s before enabling.\n", VTOI(vp)->i_fs->fs_fsmnt); VN_RELE(vp); rw_exit(&ufsvfsp->vfs_dqrwlock); return (0); } (void) quotasync(ufsvfsp, /* do_lock */ 0); /* remove extra hold on quota file */ VN_RELE(vp); quotaon++; qip = ufsvfsp->vfs_qinod; } else { int qlen; ufsvfsp->vfs_qinod = VTOI(vp); qip = ufsvfsp->vfs_qinod; /* * Force the file to have no partially allocated blocks * to prevent a realloc from changing the location of * the data. We must do this even if not logging in * case we later remount to logging. */ qlen = qip->i_fs->fs_bsize * NDADDR; /* * Largefiles: i_size needs to be atomically accessed now. */ rw_enter(&qip->i_contents, RW_WRITER); if (qip->i_size < qlen) { if (ufs_itrunc(qip, (u_offset_t)qlen, (int)0, cr) != 0) cmn_err(CE_WARN, "opendq failed to remove frags" " from quota file\n"); rw_exit(&qip->i_contents); (void) VOP_PUTPAGE(vp, (offset_t)0, (size_t)qip->i_size, B_INVAL, kcred, NULL); } else { rw_exit(&qip->i_contents); } TRANS_MATA_IGET(ufsvfsp, qip); } /* * The file system time limits are in the dquot for uid 0. * The time limits set the relative time the other users * can be over quota for this file system. * If it is zero a default is used (see quota.h). */ error = getdiskquota((uid_t)0, ufsvfsp, 1, &dqp); if (error == 0) { mutex_enter(&dqp->dq_lock); ufsvfsp->vfs_btimelimit = (dqp->dq_btimelimit? dqp->dq_btimelimit: DQ_BTIMELIMIT); ufsvfsp->vfs_ftimelimit = (dqp->dq_ftimelimit? dqp->dq_ftimelimit: DQ_FTIMELIMIT); ufsvfsp->vfs_qflags = MQ_ENABLED; /* enable quotas */ vfs_setmntopt(ufsvfsp->vfs_vfs, MNTOPT_QUOTA, NULL, 0); dqput(dqp); mutex_exit(&dqp->dq_lock); } else if (!quotaon) { /* * Some sort of I/O error on the quota file, and quotas were * not already on when we got here so clean up. */ ufsvfsp->vfs_qflags = 0; ufsvfsp->vfs_qinod = NULL; VN_RELE(ITOV(qip)); } /* * If quotas are enabled update all valid inodes in the * cache with quota information. */ if (ufsvfsp->vfs_qflags & MQ_ENABLED) { (void) ufs_scan_inodes(0, opendq_scan_inode, ufsvfsp, ufsvfsp); } rw_exit(&ufsvfsp->vfs_dqrwlock); return (error); }