diff options
Diffstat (limited to 'sys/gnu/ext2fs/ext2_alloc.c')
-rw-r--r-- | sys/gnu/ext2fs/ext2_alloc.c | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/sys/gnu/ext2fs/ext2_alloc.c b/sys/gnu/ext2fs/ext2_alloc.c new file mode 100644 index 0000000..6a0f5d3 --- /dev/null +++ b/sys/gnu/ext2fs/ext2_alloc.c @@ -0,0 +1,572 @@ +/* + * modified for Lites 1.1 + * + * Aug 1995, Godmar Back (gback@cs.utah.edu) + * University of Utah, Department of Computer Science + */ +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ext2_alloc.c 8.8 (Berkeley) 2/21/94 + */ + +#if !defined(__FreeBSD__) +#include "quota.h" +#include "diagnostic.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/vnode.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <vm/vm.h> + +#include <ufs/ufs/quota.h> +#include <ufs/ufs/inode.h> + +#include <gnu/ext2fs/ext2_fs.h> +#include <gnu/ext2fs/ext2_fs_sb.h> +#include <gnu/ext2fs/fs.h> +#include <gnu/ext2fs/ext2_extern.h> + +extern u_long nextgennumber; + +static void ext2_fserr __P((struct ext2_sb_info *, u_int, char *)); + +/* + * Linux calls this functions at the following locations: + * (1) the inode is freed + * (2) a preallocation miss occurs + * (3) truncate is called + * (4) release_file is called and f_mode & 2 + * + * I call it in ext2_inactive, ext2_truncate, ext2_vfree and in (2) + * the call in vfree might be redundant + */ +void ext2_discard_prealloc (struct inode * ip) +{ +#ifdef EXT2_PREALLOCATE + if (ip->i_prealloc_count) { + int i = ip->i_prealloc_count; + ip->i_prealloc_count = 0; + ext2_free_blocks (ITOV(ip)->v_mount, + ip->i_prealloc_block, + i); + } +#endif +} + +/* + * 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(ip, lbn, bpref, size, cred, bnp) + register struct inode *ip; + daddr_t lbn, bpref; + int size; + struct ucred *cred; + daddr_t *bnp; +{ + register struct ext2_sb_info *fs; + daddr_t bno; + int cg, error; + + *bnp = 0; + fs = ip->i_e2fs; +#if DIAGNOSTIC + if ((u_int)size > fs->s_blocksize || blkoff(fs, size) != 0) { + printf("dev = 0x%x, bsize = %d, size = %d, fs = %s\n", + ip->i_dev, fs->s_blocksize, size, fs->fs_fsmnt); + panic("ext2_alloc: bad size"); + } + if (cred == NOCRED) + panic("ext2_alloc: missing credential\n"); +#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 = chkdq(ip, (long)btodb(size), cred, 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. + */ + (void) 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); +} + +/* + * Reallocate a sequence of blocks into a contiguous sequence of blocks. + * + * The vnode and an array of buffer pointers for a range of sequential + * logical blocks to be made contiguous is given. The allocator attempts + * to find a range of sequential blocks starting as close as possible to + * an fs_rotdelay offset from the end of the allocation for the logical + * block immediately preceeding the current range. If successful, the + * physical block numbers in the buffer pointers and in the inode are + * changed to reflect the new allocation. If unsuccessful, the allocation + * is left unchanged. The success in doing the reallocation is returned. + * Note that the error return is not reflected back to the user. Rather + * the previous block allocation will be used. + */ +#include <sys/sysctl.h> +static int doasyncfree = 1; +#ifdef OPT_DEBUG +struct ctldebug debug14 = { "doasyncfree", &doasyncfree }; +#endif /* OPT_DEBUG */ +int +ext2_reallocblks(ap) + struct vop_reallocblks_args /* { + struct vnode *a_vp; + struct cluster_save *a_buflist; + } */ *ap; +{ +#ifndef FANCY_REALLOC +/* printf("ext2_reallocblks not implemented\n"); */ +return ENOSPC; +#else + + struct ext2_sb_info *fs; + struct inode *ip; + struct vnode *vp; + struct buf *sbp, *ebp; + daddr_t *bap, *sbap, *ebap; + struct cluster_save *buflist; + daddr_t start_lbn, end_lbn, soff, eoff, newblk, blkno; + struct indir start_ap[NIADDR + 1], end_ap[NIADDR + 1], *idp; + int i, len, start_lvl, end_lvl, pref, ssize; + + vp = ap->a_vp; + ip = VTOI(vp); + fs = ip->i_e2fs; +#ifdef UNKLAR + if (fs->fs_contigsumsize <= 0) + return (ENOSPC); +#endif + buflist = ap->a_buflist; + len = buflist->bs_nchildren; + start_lbn = buflist->bs_children[0]->b_lblkno; + end_lbn = start_lbn + len - 1; +#if DIAGNOSTIC + for (i = 1; i < len; i++) + if (buflist->bs_children[i]->b_lblkno != start_lbn + i) + panic("ext2_reallocblks: non-cluster"); +#endif + /* + * If the latest allocation is in a new cylinder group, assume that + * the filesystem has decided to move and do not force it back to + * the previous cylinder group. + */ + if (dtog(fs, dbtofsb(fs, buflist->bs_children[0]->b_blkno)) != + dtog(fs, dbtofsb(fs, buflist->bs_children[len - 1]->b_blkno))) + return (ENOSPC); + if (ufs_getlbns(vp, start_lbn, start_ap, &start_lvl) || + ufs_getlbns(vp, end_lbn, end_ap, &end_lvl)) + return (ENOSPC); + /* + * Get the starting offset and block map for the first block. + */ + if (start_lvl == 0) { + sbap = &ip->i_db[0]; + soff = start_lbn; + } else { + idp = &start_ap[start_lvl - 1]; + if (bread(vp, idp->in_lbn, (int)fs->s_blocksize, NOCRED, &sbp)) { + brelse(sbp); + return (ENOSPC); + } + sbap = (daddr_t *)sbp->b_data; + soff = idp->in_off; + } + /* + * Find the preferred location for the cluster. + */ + pref = ext2_blkpref(ip, start_lbn, soff, sbap); + /* + * If the block range spans two block maps, get the second map. + */ + if (end_lvl == 0 || (idp = &end_ap[end_lvl - 1])->in_off + 1 >= len) { + ssize = len; + } else { +#if DIAGNOSTIC + if (start_ap[start_lvl-1].in_lbn == idp->in_lbn) + panic("ext2_reallocblk: start == end"); +#endif + ssize = len - (idp->in_off + 1); + if (bread(vp, idp->in_lbn, (int)fs->s_blocksize, NOCRED, &ebp)) + goto fail; + ebap = (daddr_t *)ebp->b_data; + } + /* + * Search the block map looking for an allocation of the desired size. + */ + if ((newblk = (daddr_t)ext2_hashalloc(ip, dtog(fs, pref), (long)pref, + len, (u_long (*)())ext2_clusteralloc)) == 0) + goto fail; + /* + * We have found a new contiguous block. + * + * First we have to replace the old block pointers with the new + * block pointers in the inode and indirect blocks associated + * with the file. + */ + blkno = newblk; + for (bap = &sbap[soff], i = 0; i < len; i++, blkno += fs->s_frags_per_block) { + if (i == ssize) + bap = ebap; +#if DIAGNOSTIC + if (buflist->bs_children[i]->b_blkno != fsbtodb(fs, *bap)) + panic("ext2_reallocblks: alloc mismatch"); +#endif + *bap++ = blkno; + } + /* + * Next we must write out the modified inode and indirect blocks. + * For strict correctness, the writes should be synchronous since + * the old block values may have been written to disk. In practise + * they are almost never written, but if we are concerned about + * strict correctness, the `doasyncfree' flag should be set to zero. + * + * The test on `doasyncfree' should be changed to test a flag + * that shows whether the associated buffers and inodes have + * been written. The flag should be set when the cluster is + * started and cleared whenever the buffer or inode is flushed. + * We can then check below to see if it is set, and do the + * synchronous write only when it has been cleared. + */ + if (sbap != &ip->i_db[0]) { + if (doasyncfree) + bdwrite(sbp); + else + bwrite(sbp); + } else { +#if !defined(__FreeBSD__) + struct timeval time; + get_time(&time); +#endif + ip->i_flag |= IN_CHANGE | IN_UPDATE; + if (!doasyncfree) + VOP_UPDATE(vp, &time, &time, MNT_WAIT); + } + if (ssize < len) + if (doasyncfree) + bdwrite(ebp); + else + bwrite(ebp); + /* + * Last, free the old blocks and assign the new blocks to the buffers. + */ + for (blkno = newblk, i = 0; i < len; i++, blkno += fs->s_frags_per_block) { + ext2_blkfree(ip, dbtofsb(fs, buflist->bs_children[i]->b_blkno), + fs->s_blocksize); + buflist->bs_children[i]->b_blkno = fsbtodb(fs, blkno); + } + return (0); + +fail: + if (ssize < len) + brelse(ebp); + if (sbap != &ip->i_db[0]) + brelse(sbp); + return (ENOSPC); + +#endif /* FANCY_REALLOC */ +} + +/* + * Allocate an inode in the file system. + * + * we leave the actual allocation strategy to the (modified) + * ext2_new_inode(), to make sure we get the policies right + */ +int +ext2_valloc(ap) + struct vop_valloc_args /* { + struct vnode *a_pvp; + int a_mode; + struct ucred *a_cred; + struct vnode **a_vpp; + } */ *ap; +{ + register struct vnode *pvp = ap->a_pvp; + register struct inode *pip; + register struct ext2_sb_info *fs; + register struct inode *ip; + mode_t mode = ap->a_mode; + ino_t ino, ipref; + int i, error; +#if !defined(__FreeBSD__) + struct timeval time; +#endif + + *ap->a_vpp = NULL; + pip = VTOI(pvp); + fs = pip->i_e2fs; + if (fs->s_es->s_free_inodes_count == 0) + goto noinodes; + + /* call the Linux routine - it returns the inode number only */ + ino = ext2_new_inode(pip, mode); + + if (ino == 0) + goto noinodes; + error = VFS_VGET(pvp->v_mount, ino, ap->a_vpp); + if (error) { + VOP_VFREE(pvp, ino, mode); + return (error); + } + ip = VTOI(*ap->a_vpp); + + /* + the question is whether using VGET was such good idea at all - + Linux doesn't read the old inode in when it's allocating a + new one. I will set at least i_size & i_blocks the zero. + */ + ip->i_mode = 0; + ip->i_size = 0; + ip->i_blocks = 0; + ip->i_flags = 0; + /* now we want to make sure that the block pointers are zeroed out */ + for(i = 0; i < EXT2_NDIR_BLOCKS; i++) + ip->i_db[i] = 0; + + /* + * Set up a new generation number for this inode. + * XXX check if this makes sense in ext2 + */ +#if !defined(__FreeBSD__) + get_time(&time); +#endif + if (++nextgennumber < (u_long)time.tv_sec) + nextgennumber = time.tv_sec; + ip->i_gen = nextgennumber; +/* +printf("ext2_valloc: allocated inode %d\n", ino); +*/ + return (0); +noinodes: + ext2_fserr(fs, ap->a_cred->cr_uid, "out of inodes"); + uprintf("\n%s: create/symlink failed, no inodes free\n", fs->fs_fsmnt); + return (ENOSPC); +} + +/* + * Select the desired position for the next block in a file. + * + * we try to mimic what Remy does in inode_getblk/block_getblk + * + * we note: blocknr == 0 means that we're about to allocate either + * a direct block or a pointer block at the first level of indirection + * (In other words, stuff that will go in i_db[] or i_ib[]) + * + * blocknr != 0 means that we're allocating a block that is none + * of the above. Then, blocknr tells us the number of the block + * that will hold the pointer + */ +daddr_t +ext2_blkpref(ip, lbn, indx, bap, blocknr) + struct inode *ip; + daddr_t lbn; + int indx; + daddr_t *bap; + daddr_t blocknr; +{ + register struct ext2_sb_info *fs; + int tmp; + + /* if the next block is actually what we thought it is, + then set the goal to what we thought it should be + */ + if(ip->i_next_alloc_block == lbn) + return ip->i_next_alloc_goal; + + /* now check whether we were provided with an array that basically + tells us previous blocks to which we want to stay closeby + */ + if(bap) + for (tmp = indx - 1; tmp >= 0; tmp--) + if (bap[tmp]) + return bap[tmp]; + + /* else let's fall back to the blocknr, or, if there is none, + follow the rule that a block should be allocated near it's inode + */ + return blocknr ? blocknr : + (daddr_t)(ip->i_block_group * + EXT2_BLOCKS_PER_GROUP(ip->i_e2fs)) + + ip->i_e2fs->s_es->s_first_data_block; +} + +/* + * Free a block or fragment. + * + * pass on to the Linux code + */ +void +ext2_blkfree(ip, bno, size) + register struct inode *ip; + daddr_t bno; + long size; +{ + register struct ext2_sb_info *fs; + + fs = ip->i_e2fs; + /* + * call Linux code with mount *, block number, count + */ + ext2_free_blocks(ITOV(ip)->v_mount, bno, size / fs->s_frag_size); +} + +/* + * Free an inode. + * + * the maintenance of the actual bitmaps is again up to the linux code + */ +int +ext2_vfree(ap) + struct vop_vfree_args /* { + struct vnode *a_pvp; + ino_t a_ino; + int a_mode; + } */ *ap; +{ + register struct ext2_sb_info *fs; + register struct inode *pip; + ino_t ino = ap->a_ino; + int mode; + + pip = VTOI(ap->a_pvp); + fs = pip->i_e2fs; + if ((u_int)ino >= fs->s_inodes_per_group * fs->s_groups_count) + panic("ifree: range: dev = 0x%x, ino = %d, fs = %s\n", + pip->i_dev, ino, fs->fs_fsmnt); + +/* ext2_debug("ext2_vfree (%d, %d) called\n", pip->i_number, ap->a_mode); + */ + ext2_discard_prealloc(pip); + + /* we need to make sure that ext2_free_inode can adjust the + used_dir_counts in the group summary information - I'd + really like to know what the rationale behind this + 'set i_mode to zero to denote an unused inode' is + */ + mode = pip->i_mode; + pip->i_mode = ap->a_mode; + ext2_free_inode(pip); + pip->i_mode = mode; + return (0); +} + +/* + * Fserr prints the name of a file system with an error diagnostic. + * + * The form of the error message is: + * fs: error message + */ +static void +ext2_fserr(fs, uid, cp) + struct ext2_sb_info *fs; + u_int uid; + char *cp; +{ + + log(LOG_ERR, "uid %d on %s: %s\n", uid, fs->fs_fsmnt, cp); +} |