475 lines
13 KiB
C
475 lines
13 KiB
C
/* This files manages inodes allocation and deallocation.
|
|
*
|
|
* The entry points into this file are:
|
|
* alloc_inode: allocate a new, unused inode.
|
|
* free_inode: mark an inode as available for a new file.
|
|
*
|
|
* Created (alloc_inode/free_inode/wipe_inode are from MFS):
|
|
* June 2010 (Evgeniy Ivanov)
|
|
*/
|
|
|
|
#include "fs.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <minix/com.h>
|
|
#include <minix/u64.h>
|
|
#include "buf.h"
|
|
#include "inode.h"
|
|
#include "super.h"
|
|
#include "const.h"
|
|
|
|
|
|
static bit_t alloc_inode_bit(struct super_block *sp, struct inode
|
|
*parent, int is_dir);
|
|
static void free_inode_bit(struct super_block *sp, bit_t bit_returned,
|
|
int is_dir);
|
|
static void wipe_inode(struct inode *rip);
|
|
|
|
|
|
/*===========================================================================*
|
|
* alloc_inode *
|
|
*===========================================================================*/
|
|
struct inode *alloc_inode(struct inode *parent, pmode_t bits)
|
|
{
|
|
/* Allocate a free inode on parent's dev, and return a pointer to it. */
|
|
|
|
register struct inode *rip;
|
|
register struct super_block *sp;
|
|
int inumb;
|
|
bit_t b;
|
|
static int print_oos_msg = 1;
|
|
|
|
sp = get_super(parent->i_dev); /* get pointer to super_block */
|
|
if (sp->s_rd_only) { /* can't allocate an inode on a read only device. */
|
|
err_code = EROFS;
|
|
return(NULL);
|
|
}
|
|
|
|
/* Acquire an inode from the bit map. */
|
|
b = alloc_inode_bit(sp, parent, (bits & I_TYPE) == I_DIRECTORY);
|
|
if (b == NO_BIT) {
|
|
err_code = ENOSPC;
|
|
if (print_oos_msg)
|
|
ext2_debug("Out of i-nodes on device %d/%d\n",
|
|
major(sp->s_dev), minor(sp->s_dev));
|
|
print_oos_msg = 0; /* Don't repeat message */
|
|
return(NULL);
|
|
}
|
|
print_oos_msg = 1;
|
|
|
|
inumb = (int) b; /* be careful not to pass unshort as param */
|
|
|
|
/* Try to acquire a slot in the inode table. */
|
|
if ((rip = get_inode(NO_DEV, inumb)) == NULL) {
|
|
/* No inode table slots available. Free the inode just allocated. */
|
|
free_inode_bit(sp, b, (bits & I_TYPE) == I_DIRECTORY);
|
|
} else {
|
|
/* An inode slot is available. Put the inode just allocated into it. */
|
|
rip->i_mode = bits; /* set up RWX bits */
|
|
rip->i_links_count = NO_LINK; /* initial no links */
|
|
rip->i_uid = caller_uid; /* file's uid is owner's */
|
|
rip->i_gid = caller_gid; /* ditto group id */
|
|
rip->i_dev = parent->i_dev; /* mark which device it is on */
|
|
rip->i_sp = sp; /* pointer to super block */
|
|
|
|
/* Fields not cleared already are cleared in wipe_inode(). They have
|
|
* been put there because truncate() needs to clear the same fields if
|
|
* the file happens to be open while being truncated. It saves space
|
|
* not to repeat the code twice.
|
|
*/
|
|
wipe_inode(rip);
|
|
}
|
|
|
|
return(rip);
|
|
}
|
|
|
|
|
|
/*===========================================================================*
|
|
* free_inode *
|
|
*===========================================================================*/
|
|
void free_inode(
|
|
register struct inode *rip /* inode to free */
|
|
)
|
|
{
|
|
/* Return an inode to the pool of unallocated inodes. */
|
|
register struct super_block *sp;
|
|
dev_t dev = rip->i_dev;
|
|
bit_t b = rip->i_num;
|
|
u16_t mode = rip->i_mode;
|
|
|
|
/* Locate the appropriate super_block. */
|
|
sp = get_super(dev);
|
|
|
|
if (b <= NO_ENTRY || b > sp->s_inodes_count)
|
|
return;
|
|
free_inode_bit(sp, b, (mode & I_TYPE) == I_DIRECTORY);
|
|
|
|
rip->i_mode = I_NOT_ALLOC; /* clear I_TYPE field */
|
|
}
|
|
|
|
|
|
static int find_group_dir(struct super_block *sp);
|
|
static int find_group_hashalloc(struct super_block *sp, struct inode
|
|
*parent);
|
|
static int find_group_any(struct super_block *sp);
|
|
static int find_group_orlov(struct super_block *sp, struct inode
|
|
*parent);
|
|
|
|
|
|
/*===========================================================================*
|
|
* alloc_inode_bit *
|
|
*===========================================================================*/
|
|
static bit_t alloc_inode_bit(sp, parent, is_dir)
|
|
struct super_block *sp; /* the filesystem to allocate from */
|
|
struct inode *parent; /* parent of newly allocated inode */
|
|
int is_dir; /* inode will be a directory if it is TRUE */
|
|
{
|
|
int group;
|
|
pino_t inumber = NO_BIT;
|
|
bit_t bit;
|
|
struct buf *bp;
|
|
struct group_desc *gd;
|
|
|
|
if (sp->s_rd_only)
|
|
panic("can't alloc inode on read-only filesys.");
|
|
|
|
if (opt.mfsalloc) {
|
|
group = find_group_any(sp);
|
|
} else {
|
|
if (is_dir) {
|
|
if (opt.use_orlov) {
|
|
group = find_group_orlov(sp, parent);
|
|
} else {
|
|
group = find_group_dir(sp);
|
|
}
|
|
} else {
|
|
group = find_group_hashalloc(sp, parent);
|
|
}
|
|
}
|
|
/* Check if we have a group where to allocate an inode */
|
|
if (group == -1)
|
|
return(NO_BIT); /* no bit could be allocated */
|
|
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc block");
|
|
|
|
/* find_group_* should always return either a group with
|
|
* a free inode slot or -1, which we checked earlier.
|
|
*/
|
|
ASSERT(gd->free_inodes_count);
|
|
|
|
bp = get_block(sp->s_dev, gd->inode_bitmap, NORMAL);
|
|
bit = setbit(b_bitmap(bp), sp->s_inodes_per_group, 0);
|
|
ASSERT(bit != -1); /* group definitly contains free inode */
|
|
|
|
inumber = group * sp->s_inodes_per_group + bit + 1;
|
|
|
|
/* Extra checks before real allocation.
|
|
* Only major bug can cause problems. Since setbit changed
|
|
* bp->b_bitmap there is no way to recover from this bug.
|
|
* Should never happen.
|
|
*/
|
|
if (inumber > sp->s_inodes_count) {
|
|
panic("ext2: allocator returned inum greater, than\
|
|
total number of inodes.\n");
|
|
}
|
|
|
|
if (inumber < EXT2_FIRST_INO(sp)) {
|
|
panic("ext2: allocator tryed to use reserved inode.\n");
|
|
}
|
|
|
|
lmfs_markdirty(bp);
|
|
put_block(bp, MAP_BLOCK);
|
|
|
|
gd->free_inodes_count--;
|
|
sp->s_free_inodes_count--;
|
|
if (is_dir) {
|
|
gd->used_dirs_count++;
|
|
sp->s_dirs_counter++;
|
|
}
|
|
|
|
group_descriptors_dirty = 1;
|
|
|
|
/* Almost the same as previous 'group' ASSERT */
|
|
ASSERT(inumber != NO_BIT);
|
|
return inumber;
|
|
}
|
|
|
|
|
|
/*===========================================================================*
|
|
* free_inode_bit *
|
|
*===========================================================================*/
|
|
static void free_inode_bit(struct super_block *sp, bit_t bit_returned,
|
|
int is_dir)
|
|
{
|
|
/* Return an inode by turning off its bitmap bit. */
|
|
int group; /* group number of bit_returned */
|
|
int bit; /* bit_returned number within its group */
|
|
struct buf *bp;
|
|
struct group_desc *gd;
|
|
|
|
if (sp->s_rd_only)
|
|
panic("can't free bit on read-only filesys.");
|
|
|
|
/* At first search group, to which bit_returned belongs to
|
|
* and figure out in what word bit is stored.
|
|
*/
|
|
if (bit_returned > sp->s_inodes_count ||
|
|
bit_returned < EXT2_FIRST_INO(sp))
|
|
panic("trying to free inode %d beyond inodes scope.", bit_returned);
|
|
|
|
group = (bit_returned - 1) / sp->s_inodes_per_group;
|
|
bit = (bit_returned - 1) % sp->s_inodes_per_group; /* index in bitmap */
|
|
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc block");
|
|
|
|
bp = get_block(sp->s_dev, gd->inode_bitmap, NORMAL);
|
|
|
|
if (unsetbit(b_bitmap(bp), bit))
|
|
panic("Tried to free unused inode %d", bit_returned);
|
|
|
|
lmfs_markdirty(bp);
|
|
put_block(bp, MAP_BLOCK);
|
|
|
|
gd->free_inodes_count++;
|
|
sp->s_free_inodes_count++;
|
|
|
|
if (is_dir) {
|
|
gd->used_dirs_count--;
|
|
sp->s_dirs_counter--;
|
|
}
|
|
|
|
group_descriptors_dirty = 1;
|
|
|
|
if (group < sp->s_igsearch)
|
|
sp->s_igsearch = group;
|
|
}
|
|
|
|
|
|
/* it's implemented very close to the linux' find_group_dir() */
|
|
static int find_group_dir(struct super_block *sp)
|
|
{
|
|
int avefreei = sp->s_free_inodes_count / sp->s_groups_count;
|
|
struct group_desc *gd, *best_gd = NULL;
|
|
int group, best_group = -1;
|
|
|
|
for (group = 0; group < sp->s_groups_count; ++group) {
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count == 0)
|
|
continue;
|
|
if (gd->free_inodes_count < avefreei)
|
|
continue;
|
|
if (!best_gd ||
|
|
gd->free_blocks_count > best_gd->free_blocks_count) {
|
|
best_gd = gd;
|
|
best_group = group;
|
|
}
|
|
}
|
|
|
|
return best_group; /* group or -1 */
|
|
}
|
|
|
|
|
|
/* Analog of ffs_hashalloc() from *BSD.
|
|
* 1) Check parent's for free inodes and blocks.
|
|
* 2) Quadradically rehash on the group number.
|
|
* 3) Make a linear search for free inode.
|
|
*/
|
|
static int find_group_hashalloc(struct super_block *sp, struct inode *parent)
|
|
{
|
|
int ngroups = sp->s_groups_count;
|
|
struct group_desc *gd;
|
|
int group, i;
|
|
int parent_group = (parent->i_num - 1) / sp->s_inodes_per_group;
|
|
|
|
/* Try to place new inode in its parent group */
|
|
gd = get_group_desc(parent_group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count && gd->free_blocks_count)
|
|
return parent_group;
|
|
|
|
/* We can't allocate inode in the parent's group.
|
|
* Now we will try to place it in another blockgroup.
|
|
* The main idea is still to keep files from the same
|
|
* directory together and use different blockgroups for
|
|
* files from another directory, which lives in the same
|
|
* blockgroup as our parent.
|
|
* Thus we will spread things on the disk.
|
|
*/
|
|
group = (parent_group + parent->i_num) % ngroups;
|
|
|
|
/* Make quadratic probing to find a group with free inodes and blocks. */
|
|
for (i = 1; i < ngroups; i <<= 1) {
|
|
group += i;
|
|
if (group >= ngroups)
|
|
group -= ngroups;
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count && gd->free_blocks_count)
|
|
return group;
|
|
}
|
|
|
|
/* Still no group for new inode, try linear search.
|
|
* Also check parent again (but for free inodes only).
|
|
*/
|
|
group = parent_group;
|
|
for (i = 0; i < ngroups; i++, group++) {
|
|
if (group >= ngroups)
|
|
group = 0;
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count)
|
|
return group;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Find first group which has free inode slot.
|
|
* This is similar to what MFS does.
|
|
*/
|
|
static int find_group_any(struct super_block *sp)
|
|
{
|
|
int ngroups = sp->s_groups_count;
|
|
struct group_desc *gd;
|
|
int group = sp->s_igsearch;
|
|
|
|
for (; group < ngroups; group++) {
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count) {
|
|
sp->s_igsearch = group;
|
|
return group;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* We try to spread first-level directories (i.e. directories in the root
|
|
* or in the directory marked as TOPDIR).
|
|
* If there are blockgroups with counts for blocks and inodes less than average
|
|
* we return a group with lowest directory count. Otherwise we either
|
|
* return a group with good free inodes and blocks counts or just a group
|
|
* with free inode.
|
|
*
|
|
* For other directories we try to find a 'good' group, we consider a group as
|
|
* a 'good' if it has enough blocks and inodes (greater than min_blocks and
|
|
* min_inodes).
|
|
*
|
|
*/
|
|
static int find_group_orlov(struct super_block *sp, struct inode *parent)
|
|
{
|
|
int avefreei = sp->s_free_inodes_count / sp->s_groups_count;
|
|
int avefreeb = sp->s_free_blocks_count / sp->s_groups_count;
|
|
|
|
int group = -1;
|
|
int fallback_group = -1; /* Group with at least 1 free inode */
|
|
struct group_desc *gd;
|
|
int i;
|
|
|
|
if (parent->i_num == ROOT_INODE ||
|
|
parent->i_flags & EXT2_TOPDIR_FL) {
|
|
int best_group = -1;
|
|
int best_avefree_group = -1; /* Best value of avefreei/avefreeb */
|
|
int best_ndir = sp->s_inodes_per_group;
|
|
|
|
group = (unsigned int)random();
|
|
for (i = 0; i < sp->s_groups_count; i++, group++) {
|
|
if (group >= sp->s_groups_count)
|
|
group = 0;
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count == 0)
|
|
continue;
|
|
|
|
fallback_group = group;
|
|
|
|
if (gd->free_inodes_count < avefreei ||
|
|
gd->free_blocks_count < avefreeb)
|
|
continue;
|
|
|
|
best_avefree_group = group;
|
|
|
|
if (gd->used_dirs_count >= best_ndir)
|
|
continue;
|
|
best_ndir = gd->used_dirs_count;
|
|
best_group = group;
|
|
}
|
|
if (best_group >= 0)
|
|
return best_group;
|
|
if (best_avefree_group >= 0)
|
|
return best_avefree_group;
|
|
return fallback_group;
|
|
} else {
|
|
int parent_group = (parent->i_num - 1) / sp->s_inodes_per_group;
|
|
/* 2 is kind of random thing for now,
|
|
* but performance results are still good.
|
|
*/
|
|
int min_blocks = avefreeb / 2;
|
|
int min_inodes = avefreei / 2;
|
|
|
|
group = parent_group;
|
|
for (i = 0; i < sp->s_groups_count; i++, group++) {
|
|
if (group >= sp->s_groups_count)
|
|
group = 0;
|
|
gd = get_group_desc(group);
|
|
if (gd == NULL)
|
|
panic("can't get group_desc to alloc inode");
|
|
if (gd->free_inodes_count == 0)
|
|
continue;
|
|
|
|
fallback_group = group;
|
|
|
|
if (gd->free_inodes_count >= min_inodes &&
|
|
gd->free_blocks_count >= min_blocks)
|
|
return group;
|
|
}
|
|
return fallback_group;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*===========================================================================*
|
|
* wipe_inode *
|
|
*===========================================================================*/
|
|
static void wipe_inode(
|
|
register struct inode *rip /* the inode to be erased */
|
|
)
|
|
{
|
|
/* Erase some fields in the inode. This function is called from alloc_inode()
|
|
* when a new inode is to be allocated, and from truncate(), when an existing
|
|
* inode is to be truncated.
|
|
*/
|
|
|
|
register int i;
|
|
|
|
rip->i_size = 0;
|
|
rip->i_update = ATIME | CTIME | MTIME; /* update all times later */
|
|
rip->i_blocks = 0;
|
|
rip->i_flags = 0;
|
|
rip->i_generation = 0;
|
|
rip->i_file_acl = 0;
|
|
rip->i_dir_acl = 0;
|
|
rip->i_faddr = 0;
|
|
|
|
for (i = 0; i < EXT2_N_BLOCKS; i++)
|
|
rip->i_block[i] = NO_BLOCK;
|
|
rip->i_block[0] = NO_BLOCK;
|
|
|
|
rip->i_dirt = IN_DIRTY;
|
|
}
|