/* * Vnode op for writing. */ int ext2fs_write(void *v) { struct vop_write_args *ap = v; struct vnode *vp; struct uio *uio; struct inode *ip; struct m_ext2fs *fs; struct buf *bp; int32_t lbn; off_t osize; int blkoffset, error, flags, ioflag, size, xfersize; ssize_t resid, overrun; ioflag = ap->a_ioflag; uio = ap->a_uio; vp = ap->a_vp; ip = VTOI(vp); #ifdef DIAGNOSTIC if (uio->uio_rw != UIO_WRITE) panic("%s: mode", "ext2fs_write"); #endif /* * If writing 0 bytes, succeed and do not change * update time or file offset (standards compliance) */ if (uio->uio_resid == 0) return (0); switch (vp->v_type) { case VREG: if (ioflag & IO_APPEND) uio->uio_offset = ext2fs_size(ip); if ((ip->i_e2fs_flags & EXT2_APPEND) && uio->uio_offset != ext2fs_size(ip)) return (EPERM); /* FALLTHROUGH */ case VLNK: break; case VDIR: if ((ioflag & IO_SYNC) == 0) panic("%s: nonsync dir write", "ext2fs_write"); break; default: panic("%s: type", "ext2fs_write"); } fs = ip->i_e2fs; if (uio->uio_offset < 0 || (u_int64_t)uio->uio_offset + uio->uio_resid > ((u_int64_t)0x80000000 * fs->e2fs_bsize - 1)) return (EFBIG); /* do the filesize rlimit check */ if ((error = vn_fsizechk(vp, uio, ioflag, &overrun))) return (error); resid = uio->uio_resid; osize = ext2fs_size(ip); flags = ioflag & IO_SYNC ? B_SYNC : 0; for (error = 0; uio->uio_resid > 0;) { lbn = lblkno(fs, uio->uio_offset); blkoffset = blkoff(fs, uio->uio_offset); xfersize = fs->e2fs_bsize - blkoffset; if (uio->uio_resid < xfersize) xfersize = uio->uio_resid; if (fs->e2fs_bsize > xfersize) flags |= B_CLRBUF; else flags &= ~B_CLRBUF; error = ext2fs_buf_alloc(ip, lbn, blkoffset + xfersize, ap->a_cred, &bp, flags); if (error) break; if (uio->uio_offset + xfersize > ext2fs_size(ip)) { error = ext2fs_setsize(ip, uio->uio_offset + xfersize); if (error) break; uvm_vnp_setsize(vp, ip->i_e2fs_size); } uvm_vnp_uncache(vp); size = fs->e2fs_bsize - bp->b_resid; if (size < xfersize) xfersize = size; error = uiomove((char *)bp->b_data + blkoffset, (int)xfersize, uio); if (ioflag & IO_SYNC) (void)bwrite(bp); else if (xfersize + blkoffset == fs->e2fs_bsize) { if (doclusterwrite) cluster_write(bp, &ip->i_ci, ext2fs_size(ip)); else bawrite(bp); } else bdwrite(bp); if (error || xfersize == 0) break; ip->i_flag |= IN_CHANGE | IN_UPDATE; } /* * If we successfully wrote any data, and we are not the superuser * we clear the setuid and setgid bits as a precaution against * tampering. */ if (resid > uio->uio_resid && ap->a_cred && ap->a_cred->cr_uid != 0) ip->i_e2fs_mode &= ~(ISUID | ISGID); if (error) { if (ioflag & IO_UNIT) { (void)ext2fs_truncate(ip, osize, ioflag & IO_SYNC, ap->a_cred); uio->uio_offset -= resid - uio->uio_resid; uio->uio_resid = resid; } } else if (resid > uio->uio_resid && (ioflag & IO_SYNC)) { error = ext2fs_update(ip, NULL, NULL, 1); } /* correct the result for writes clamped by vn_fsizechk() */ uio->uio_resid += overrun; return (error); }
/* * Truncate the inode oip to at most length size, freeing the * disk blocks. */ int ext2fs_truncate(struct inode *oip, off_t length, int flags, struct ucred *cred) { struct vnode *ovp = ITOV(oip); int32_t lastblock; int32_t bn, lbn, lastiblock[NIADDR], indir_lbn[NIADDR]; int32_t oldblks[NDADDR + NIADDR], newblks[NDADDR + NIADDR]; struct m_ext2fs *fs; struct buf *bp; int offset, size, level; long count, nblocks, vflags, blocksreleased = 0; int i; int aflags, error, allerror; off_t osize; if (length < 0) return (EINVAL); if (ovp->v_type != VREG && ovp->v_type != VDIR && ovp->v_type != VLNK) return (0); if (ovp->v_type == VLNK && ext2fs_size(oip) < EXT2_MAXSYMLINKLEN) { #ifdef DIAGNOSTIC if (length != 0) panic("ext2fs_truncate: partial truncate of symlink"); #endif memset(&oip->i_e2din->e2di_shortlink, 0, ext2fs_size(oip)); (void)ext2fs_setsize(oip, 0); oip->i_flag |= IN_CHANGE | IN_UPDATE; return (ext2fs_update(oip, 1)); } if (ext2fs_size(oip) == length) { oip->i_flag |= IN_CHANGE | IN_UPDATE; return (ext2fs_update(oip, 0)); } fs = oip->i_e2fs; osize = ext2fs_size(oip); /* * Lengthen the size of the file. We must ensure that the * last byte of the file is allocated. Since the smallest * value of osize is 0, length will be at least 1. */ if (osize < length) { #if 0 /* XXX */ if (length > fs->fs_maxfilesize) return (EFBIG); #endif offset = blkoff(fs, length - 1); lbn = lblkno(fs, length - 1); aflags = B_CLRBUF; if (flags & IO_SYNC) aflags |= B_SYNC; error = ext2fs_buf_alloc(oip, lbn, offset + 1, cred, &bp, aflags); if (error) return (error); (void)ext2fs_setsize(oip, length); uvm_vnp_setsize(ovp, length); uvm_vnp_uncache(ovp); if (aflags & B_SYNC) bwrite(bp); else bawrite(bp); oip->i_flag |= IN_CHANGE | IN_UPDATE; return (ext2fs_update(oip, 1)); } /* * Shorten the size of the file. If the file is not being * truncated to a block boundry, the contents of the * partial block following the end of the file must be * zero'ed in case it ever become accessible again because * of subsequent file growth. */ offset = blkoff(fs, length); if (offset == 0) { (void)ext2fs_setsize(oip, length); } else { lbn = lblkno(fs, length); aflags = B_CLRBUF; if (flags & IO_SYNC) aflags |= B_SYNC; error = ext2fs_buf_alloc(oip, lbn, offset, cred, &bp, aflags); if (error) return (error); (void)ext2fs_setsize(oip, length); size = fs->e2fs_bsize; uvm_vnp_setsize(ovp, length); uvm_vnp_uncache(ovp); memset(bp->b_data + offset, 0, size - offset); bp->b_bcount = size; if (aflags & B_SYNC) bwrite(bp); else bawrite(bp); } /* * Calculate index into inode's block list of * last direct and indirect blocks (if any) * which we want to keep. Lastblock is -1 when * the file is truncated to 0. */ lastblock = lblkno(fs, length + fs->e2fs_bsize - 1) - 1; lastiblock[SINGLE] = lastblock - NDADDR; lastiblock[DOUBLE] = lastiblock[SINGLE] - NINDIR(fs); lastiblock[TRIPLE] = lastiblock[DOUBLE] - NINDIR(fs) * NINDIR(fs); nblocks = btodb(fs->e2fs_bsize); /* * Update file and block pointers on disk before we start freeing * blocks. If we crash before free'ing blocks below, the blocks * will be returned to the free list. lastiblock values are also * normalized to -1 for calls to ext2fs_indirtrunc below. */ memcpy(oldblks, &oip->i_e2fs_blocks[0], sizeof(oldblks)); for (level = TRIPLE; level >= SINGLE; level--) if (lastiblock[level] < 0) { oip->i_e2fs_blocks[NDADDR + level] = 0; lastiblock[level] = -1; } for (i = NDADDR - 1; i > lastblock; i--) oip->i_e2fs_blocks[i] = 0; oip->i_flag |= IN_CHANGE | IN_UPDATE; if ((error = ext2fs_update(oip, 1)) != 0) allerror = error; /* * Having written the new inode to disk, save its new configuration * and put back the old block pointers long enough to process them. * Note that we save the new block configuration so we can check it * when we are done. */ memcpy(newblks, &oip->i_e2fs_blocks[0], sizeof(newblks)); memcpy(&oip->i_e2fs_blocks[0], oldblks, sizeof(oldblks)); (void)ext2fs_setsize(oip, osize); vflags = ((length > 0) ? V_SAVE : 0) | V_SAVEMETA; allerror = vinvalbuf(ovp, vflags, cred, curproc, 0, 0); /* * Indirect blocks first. */ indir_lbn[SINGLE] = -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 = letoh32(oip->i_e2fs_blocks[NDADDR + level]); if (bn != 0) { error = ext2fs_indirtrunc(oip, indir_lbn[level], fsbtodb(fs, bn), lastiblock[level], level, &count); if (error) allerror = error; blocksreleased += count; if (lastiblock[level] < 0) { oip->i_e2fs_blocks[NDADDR + level] = 0; ext2fs_blkfree(oip, bn); blocksreleased += nblocks; } } if (lastiblock[level] >= 0) goto done; } /* * All whole direct blocks or frags. */ for (i = NDADDR - 1; i > lastblock; i--) { bn = letoh32(oip->i_e2fs_blocks[i]); if (bn == 0) continue; oip->i_e2fs_blocks[i] = 0; ext2fs_blkfree(oip, bn); blocksreleased += btodb(fs->e2fs_bsize); } done: #ifdef DIAGNOSTIC for (level = SINGLE; level <= TRIPLE; level++) if (newblks[NDADDR + level] != oip->i_e2fs_blocks[NDADDR + level]) panic("ext2fs_truncate1"); for (i = 0; i < NDADDR; i++) if (newblks[i] != oip->i_e2fs_blocks[i]) panic("ext2fs_truncate2"); if (length == 0 && (!LIST_EMPTY(&ovp->v_cleanblkhd) || !LIST_EMPTY(&ovp->v_dirtyblkhd))) panic("ext2fs_truncate3"); #endif /* DIAGNOSTIC */ /* * Put back the real size. */ (void)ext2fs_setsize(oip, length); if (blocksreleased >= oip->i_e2fs_nblock) oip->i_e2fs_nblock = 0; else oip->i_e2fs_nblock -= blocksreleased; oip->i_flag |= IN_CHANGE; return (allerror); }