/* * Allocate a block in the file system. * * this takes the framework from ffs_alloc. To implement the * actual allocation, it calls ext2_new_block, the ported version * of the same Linux routine. * * we note that this is always called in connection with ext2_blkpref * * preallocation is done as Linux does it */ int ext2_alloc(struct inode *ip, daddr_t lbn, daddr_t bpref, int size, struct ucred *cred, daddr_t *bnp) { struct ext2_sb_info *fs; daddr_t bno; #if QUOTA int error; #endif *bnp = 0; fs = ip->i_e2fs; #if DIAGNOSTIC if ((u_int)size > fs->s_blocksize || blkoff(fs, size) != 0) { kprintf("dev = %s, bsize = %lu, size = %d, fs = %s\n", devtoname(ip->i_dev), fs->s_blocksize, size, fs->fs_fsmnt); panic("ext2_alloc: bad size"); } if (cred == NOCRED) panic("ext2_alloc: missing credential"); #endif /* DIAGNOSTIC */ if (size == fs->s_blocksize && fs->s_es->s_free_blocks_count == 0) goto nospace; if (cred->cr_uid != 0 && fs->s_es->s_free_blocks_count < fs->s_es->s_r_blocks_count) goto nospace; #if QUOTA if ((error = ext2_chkdq(ip, (long)btodb(size), cred, 0)) != 0) return (error); #endif if (bpref >= fs->s_es->s_blocks_count) bpref = 0; /* call the Linux code */ #ifdef EXT2_PREALLOCATE /* To have a preallocation hit, we must * - have at least one block preallocated * - and our preferred block must have that block number or one below */ if (ip->i_prealloc_count && (bpref == ip->i_prealloc_block || bpref + 1 == ip->i_prealloc_block)) { bno = ip->i_prealloc_block++; ip->i_prealloc_count--; /* ext2_debug ("preallocation hit (%lu/%lu).\n", ++alloc_hits, ++alloc_attempts); */ /* Linux gets, clears, and releases the buffer at this point - we don't have to that; we leave it to the caller */ } else { ext2_discard_prealloc (ip); /* ext2_debug ("preallocation miss (%lu/%lu).\n", alloc_hits, ++alloc_attempts); */ if (S_ISREG(ip->i_mode)) bno = ext2_new_block (ITOV(ip)->v_mount, bpref, &ip->i_prealloc_count, &ip->i_prealloc_block); else bno = (daddr_t)ext2_new_block(ITOV(ip)->v_mount, bpref, 0, 0); } #else bno = (daddr_t)ext2_new_block(ITOV(ip)->v_mount, bpref, 0, 0); #endif if (bno > 0) { /* set next_alloc fields as done in block_getblk */ ip->i_next_alloc_block = lbn; ip->i_next_alloc_goal = bno; ip->i_blocks += btodb(size); ip->i_flag |= IN_CHANGE | IN_UPDATE; *bnp = bno; return (0); } #if QUOTA /* * Restore user's disk quota because allocation failed. */ ext2_chkdq(ip, (long)-btodb(size), cred, FORCE); #endif nospace: ext2_fserr(fs, cred->cr_uid, "file system full"); uprintf("\n%s: write failed, file system is full\n", fs->fs_fsmnt); return (ENOSPC); }
/* * Truncate the inode oip to at most length size, freeing the * disk blocks. */ int ext2_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred) { struct vnode *ovp = vp; daddr_t lastblock; struct inode *oip; daddr_t bn, lbn, lastiblock[NIADDR], indir_lbn[NIADDR]; daddr_t oldblks[NDADDR + NIADDR], newblks[NDADDR + NIADDR]; struct ext2_sb_info *fs; struct buf *bp; int offset, size, level; long count, nblocks, blocksreleased = 0; int i; int aflags, error, allerror; off_t osize; /* kprintf("ext2_truncate called %d to %d\n", VTOI(ovp)->i_number, length); */ /* * negative file sizes will totally break the code below and * are not meaningful anyways. */ if (length < 0) return EFBIG; oip = VTOI(ovp); if (ovp->v_type == VLNK && oip->i_size < ovp->v_mount->mnt_maxsymlinklen) { #if DIAGNOSTIC if (length != 0) panic("ext2_truncate: partial truncate of symlink"); #endif bzero((char *)&oip->i_shortlink, (u_int)oip->i_size); oip->i_size = 0; oip->i_flag |= IN_CHANGE | IN_UPDATE; return (EXT2_UPDATE(ovp, 1)); } if (oip->i_size == length) { oip->i_flag |= IN_CHANGE | IN_UPDATE; return (EXT2_UPDATE(ovp, 0)); } #if QUOTA if ((error = ext2_getinoquota(oip)) != 0) return (error); #endif fs = oip->i_e2fs; osize = oip->i_size; ext2_discard_prealloc(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) { offset = blkoff(fs, length - 1); lbn = lblkno(fs, length - 1); aflags = B_CLRBUF; if (flags & IO_SYNC) aflags |= B_SYNC; vnode_pager_setsize(ovp, length); error = ext2_balloc(oip, lbn, offset + 1, cred, &bp, aflags); if (error) { vnode_pager_setsize(ovp, osize); return (error); } oip->i_size = length; if (aflags & IO_SYNC) bwrite(bp); else bawrite(bp); oip->i_flag |= IN_CHANGE | IN_UPDATE; return (EXT2_UPDATE(ovp, 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 accessable again because * of subsequent file growth. */ /* I don't understand the comment above */ offset = blkoff(fs, length); if (offset == 0) { oip->i_size = length; } else { lbn = lblkno(fs, length); aflags = B_CLRBUF; if (flags & IO_SYNC) aflags |= B_SYNC; error = ext2_balloc(oip, lbn, offset, cred, &bp, aflags); if (error) return (error); oip->i_size = length; size = blksize(fs, oip, lbn); bzero((char *)bp->b_data + offset, (u_int)(size - offset)); allocbuf(bp, size); if (aflags & IO_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->s_blocksize - 1) - 1; lastiblock[SINGLE] = lastblock - NDADDR; lastiblock[DOUBLE] = lastiblock[SINGLE] - NINDIR(fs); lastiblock[TRIPLE] = lastiblock[DOUBLE] - NINDIR(fs) * NINDIR(fs); nblocks = btodb(fs->s_blocksize); /* * 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 ext2_indirtrunc below. */ bcopy((caddr_t)&oip->i_db[0], (caddr_t)oldblks, sizeof oldblks); for (level = TRIPLE; level >= SINGLE; level--) if (lastiblock[level] < 0) { oip->i_ib[level] = 0; lastiblock[level] = -1; } for (i = NDADDR - 1; i > lastblock; i--) oip->i_db[i] = 0; oip->i_flag |= IN_CHANGE | IN_UPDATE; allerror = EXT2_UPDATE(ovp, 1); /* * 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. */ bcopy((caddr_t)&oip->i_db[0], (caddr_t)newblks, sizeof newblks); bcopy((caddr_t)oldblks, (caddr_t)&oip->i_db[0], sizeof oldblks); oip->i_size = osize; error = vtruncbuf(ovp, length, (int)fs->s_blocksize); if (error && (allerror == 0)) allerror = error; /* * 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 = oip->i_ib[level]; if (bn != 0) { error = ext2_indirtrunc(oip, indir_lbn[level], fsbtodoff(fs, bn), lastiblock[level], level, &count); if (error) allerror = error; blocksreleased += count; if (lastiblock[level] < 0) { oip->i_ib[level] = 0; ext2_blkfree(oip, bn, fs->s_frag_size); blocksreleased += nblocks; } } if (lastiblock[level] >= 0) goto done; } /* * All whole direct blocks or frags. */ for (i = NDADDR - 1; i > lastblock; i--) { long bsize; bn = oip->i_db[i]; if (bn == 0) continue; oip->i_db[i] = 0; bsize = blksize(fs, oip, i); ext2_blkfree(oip, bn, bsize); blocksreleased += btodb(bsize); } if (lastblock < 0) goto done; /* * Finally, look for a change in size of the * last direct block; release any frags. */ bn = oip->i_db[lastblock]; if (bn != 0) { long oldspace, newspace; /* * Calculate amount of space we're giving * back as old block size minus new block size. */ oldspace = blksize(fs, oip, lastblock); oip->i_size = length; newspace = blksize(fs, oip, lastblock); if (newspace == 0) panic("itrunc: newspace"); if (oldspace - newspace > 0) { /* * Block number of space to be free'd is * the old block # plus the number of frags * required for the storage we're keeping. */ bn += numfrags(fs, newspace); ext2_blkfree(oip, bn, oldspace - newspace); blocksreleased += btodb(oldspace - newspace); } } done: #if DIAGNOSTIC for (level = SINGLE; level <= TRIPLE; level++) if (newblks[NDADDR + level] != oip->i_ib[level]) panic("itrunc1"); for (i = 0; i < NDADDR; i++) if (newblks[i] != oip->i_db[i]) panic("itrunc2"); if (length == 0 && (!RB_EMPTY(&ovp->v_rbdirty_tree) || !RB_EMPTY(&ovp->v_rbclean_tree))) panic("itrunc3"); #endif /* DIAGNOSTIC */ /* * Put back the real size. */ oip->i_size = length; oip->i_blocks -= blocksreleased; if (oip->i_blocks < 0) /* sanity */ oip->i_blocks = 0; oip->i_flag |= IN_CHANGE; vnode_pager_setsize(ovp, length); #if QUOTA ext2_chkdq(oip, -blocksreleased, NOCRED, 0); #endif return (allerror); }