/* VTreeFS - inode.c - inode management */ #include "inc.h" /* The number of inodes and hash table slots. */ static unsigned int nr_inodes; /* The table of all the inodes. */ static struct inode *inode; /* The list of unused inodes. */ static TAILQ_HEAD(unused_head, inode) unused_inodes; /* The hash tables for lookup of and to inode. */ static LIST_HEAD(name_head, inode) *parent_name_head; static LIST_HEAD(index_head, inode) *parent_index_head; /* Internal integrity check. */ #define CHECK_INODE(node) \ do { \ assert(node >= &inode[0] && node < &inode[nr_inodes]); \ assert((unsigned int)(node - &inode[0]) == node->i_num);\ assert(node == &inode[0] || node->i_parent != NULL || \ (node->i_flags & I_DELETED)); \ } while (0); /* * Initialize the inode-related state. */ int init_inodes(unsigned int inodes, struct inode_stat * stat, index_t nr_indexed_entries) { struct inode *node; unsigned int i; assert(inodes > 0); assert(nr_indexed_entries >= 0); nr_inodes = inodes; /* Allocate the inode and hash tables. */ if ((inode = malloc(nr_inodes * sizeof(inode[0]))) == NULL) return ENOMEM; parent_name_head = malloc(nr_inodes * sizeof(parent_name_head[0])); if (parent_name_head == NULL) { free(inode); return ENOMEM; } parent_index_head = malloc(nr_inodes * sizeof(parent_index_head[0])); if (parent_index_head == NULL) { free(parent_name_head); free(inode); return ENOMEM; } #if DEBUG printf("VTREEFS: allocated %zu+%zu+%zu bytes\n", nr_inodes * sizeof(inode[0]), nr_inodes * sizeof(parent_name_head[0]), nr_inodes * sizeof(parent_index_head[0])); #endif /* Initialize the free/unused list. */ TAILQ_INIT(&unused_inodes); /* Add free inodes to the unused/free list. Skip the root inode. */ for (i = 1; i < nr_inodes; i++) { node = &inode[i]; node->i_num = i; node->i_parent = NULL; node->i_count = 0; TAILQ_INIT(&node->i_children); TAILQ_INSERT_HEAD(&unused_inodes, node, i_unused); } /* Initialize the hash lists. */ for (i = 0; i < nr_inodes; i++) { LIST_INIT(&parent_name_head[i]); LIST_INIT(&parent_index_head[i]); } /* Initialize the root inode. */ node = &inode[0]; node->i_num = 0; node->i_parent = NULL; node->i_count = 0; TAILQ_INIT(&node->i_children); node->i_flags = 0; node->i_index = NO_INDEX; set_inode_stat(node, stat); node->i_indexed = nr_indexed_entries; node->i_cbdata = NULL; return OK; } /* * Clean up the inode-related state. */ void cleanup_inodes(void) { /* Free the inode and hash tables. */ free(parent_index_head); free(parent_name_head); free(inode); } /* * Return the hash value of tuple. */ static int parent_name_hash(const struct inode * parent, const char *name) { unsigned int name_hash; /* Use the sdbm algorithm to hash the name. */ name_hash = sdbm_hash(name, strlen(name)); /* The parent hash is a simple array entry. */ return (parent->i_num ^ name_hash) % nr_inodes; } /* * Return the hash value of a tuple. */ static int parent_index_hash(const struct inode * parent, index_t index) { return (parent->i_num ^ index) % nr_inodes; } /* * Delete a deletable inode to make room for a new inode. */ static void purge_inode(struct inode * parent) { /* * An inode is deletable if: * - it is in use; * - it is indexed; * - it is not the given parent inode; * - it has a zero reference count; * - it does not have any children. * The first point is true for all inodes, or we would not be here. * The latter two points also imply that I_DELETED is not set. */ static int last_checked = 0; struct inode *node; unsigned int count; assert(TAILQ_EMPTY(&unused_inodes)); /* * This should not happen often enough to warrant an extra linked list, * especially as maintenance of that list would be rather error-prone.. */ for (count = 0; count < nr_inodes; count++) { node = &inode[last_checked]; last_checked = (last_checked + 1) % nr_inodes; if (node != parent && node->i_index != NO_INDEX && node->i_count == 0 && TAILQ_EMPTY(&node->i_children)) { assert(!(node->i_flags & I_DELETED)); delete_inode(node); break; } } } /* * Add an inode. */ struct inode * add_inode(struct inode * parent, const char * name, index_t index, const struct inode_stat * stat, index_t nr_indexed_entries, cbdata_t cbdata) { struct inode *newnode; int slot; CHECK_INODE(parent); assert(S_ISDIR(parent->i_stat.mode)); assert(!(parent->i_flags & I_DELETED)); assert(strlen(name) <= PNAME_MAX); assert(index >= 0 || index == NO_INDEX); assert(stat != NULL); assert(nr_indexed_entries >= 0); assert(get_inode_by_name(parent, name) == NULL); /* Get a free inode. Free one up if necessary. */ if (TAILQ_EMPTY(&unused_inodes)) purge_inode(parent); assert(!TAILQ_EMPTY(&unused_inodes)); newnode = TAILQ_FIRST(&unused_inodes); TAILQ_REMOVE(&unused_inodes, newnode, i_unused); assert(newnode->i_count == 0); /* Copy the relevant data to the inode. */ newnode->i_parent = parent; newnode->i_flags = 0; newnode->i_index = index; newnode->i_stat = *stat; newnode->i_indexed = nr_indexed_entries; newnode->i_cbdata = cbdata; strlcpy(newnode->i_name, name, sizeof(newnode->i_name)); /* Clear the extra data for this inode, if present. */ clear_inode_extra(newnode); /* Add the inode to the list of children inodes of the parent. */ TAILQ_INSERT_HEAD(&parent->i_children, newnode, i_siblings); /* Add the inode to the hash table. */ slot = parent_name_hash(parent, name); LIST_INSERT_HEAD(&parent_name_head[slot], newnode, i_hname); /* Add the inode to the hash table. */ if (index != NO_INDEX) { slot = parent_index_hash(parent, index); LIST_INSERT_HEAD(&parent_index_head[slot], newnode, i_hindex); } return newnode; } /* * Return the file system's root inode. */ struct inode * get_root_inode(void) { /* The root node is always the first node in the inode table. */ return &inode[0]; } /* * Return the name that an inode has in its parent directory. */ const char * get_inode_name(const struct inode * node) { CHECK_INODE(node); return node->i_name; } /* * Return the index that an inode has in its parent directory. */ index_t get_inode_index(const struct inode * node) { CHECK_INODE(node); return node->i_index; } /* * Return the number of indexed slots for the given (directory) inode. */ index_t get_inode_slots(const struct inode * node) { CHECK_INODE(node); return node->i_indexed; } /* * Return the callback data associated with the given inode. */ cbdata_t get_inode_cbdata(const struct inode * node) { CHECK_INODE(node); return node->i_cbdata; } /* * Return an inode's parent inode. */ struct inode * get_parent_inode(const struct inode * node) { CHECK_INODE(node); /* The root inode does not have parent. */ if (node == &inode[0]) return NULL; return node->i_parent; } /* * Return a directory's first (non-deleted) child inode. */ struct inode * get_first_inode(const struct inode * parent) { struct inode *node; CHECK_INODE(parent); assert(S_ISDIR(parent->i_stat.mode)); node = TAILQ_FIRST(&parent->i_children); while (node != NULL && (node->i_flags & I_DELETED)) node = TAILQ_NEXT(node, i_siblings); return node; } /* * Return a directory's next (non-deleted) child inode. */ struct inode * get_next_inode(const struct inode * previous) { struct inode *node; CHECK_INODE(previous); node = TAILQ_NEXT(previous, i_siblings); while (node != NULL && (node->i_flags & I_DELETED)) node = TAILQ_NEXT(node, i_siblings); return node; } /* * Return the inode number of the given inode. */ int get_inode_number(const struct inode * node) { CHECK_INODE(node); return node->i_num + 1; } /* * Retrieve an inode's status. */ void get_inode_stat(const struct inode * node, struct inode_stat * stat) { CHECK_INODE(node); *stat = node->i_stat; } /* * Set an inode's status. */ void set_inode_stat(struct inode * node, struct inode_stat * stat) { CHECK_INODE(node); node->i_stat = *stat; } /* * Look up an inode using a tuple. */ struct inode * get_inode_by_name(const struct inode * parent, const char * name) { struct inode *node; int slot; CHECK_INODE(parent); assert(strlen(name) <= PNAME_MAX); assert(S_ISDIR(parent->i_stat.mode)); /* Get the hash value, and search for the inode. */ slot = parent_name_hash(parent, name); LIST_FOREACH(node, &parent_name_head[slot], i_hname) { if (parent == node->i_parent && !strcmp(name, node->i_name)) return node; /* found */ } return NULL; } /* * Look up an inode using a tuple. */ struct inode * get_inode_by_index(const struct inode * parent, index_t index) { struct inode *node; int slot; CHECK_INODE(parent); assert(S_ISDIR(parent->i_stat.mode)); assert(index >= 0 && index < parent->i_indexed); /* Get the hash value, and search for the inode. */ slot = parent_index_hash(parent, index); LIST_FOREACH(node, &parent_index_head[slot], i_hindex) { if (parent == node->i_parent && index == node->i_index) return node; /* found */ } return NULL; } /* * Retrieve an inode by inode number. */ struct inode * find_inode(ino_t num) { struct inode *node; node = &inode[num - 1]; CHECK_INODE(node); return node; } /* * Retrieve an inode by inode number, and increase its reference count. */ struct inode * get_inode(ino_t num) { struct inode *node; if ((node = find_inode(num)) == NULL) return NULL; node->i_count++; return node; } /* * Decrease an inode's reference count. */ void put_inode(struct inode * node) { CHECK_INODE(node); assert(node->i_count > 0); node->i_count--; /* * If the inode is scheduled for deletion, and has no more references, * actually delete it now. */ if ((node->i_flags & I_DELETED) && node->i_count == 0) delete_inode(node); } /* * Increase an inode's reference count. */ void ref_inode(struct inode * node) { CHECK_INODE(node); node->i_count++; } /* * Unlink the given node from its parent, if it is still linked in. */ static void unlink_inode(struct inode * node) { struct inode *parent; assert(node->i_flags & I_DELETED); parent = node->i_parent; if (parent == NULL) return; /* Delete the node from the parent list. */ node->i_parent = NULL; TAILQ_REMOVE(&parent->i_children, node, i_siblings); /* Optionally recheck if the parent can now be deleted. */ if (parent->i_flags & I_DELETED) delete_inode(parent); } /* * Delete the given inode. If its reference count is nonzero, or it still has * children that cannot be deleted for the same reason, keep the inode around * for the time being. If the node is a directory, keep around its parent so * that we can still do a "cd .." out of it. For these reasons, this function * may be called on an inode more than once before it is actually deleted. */ void delete_inode(struct inode * node) { struct inode *cnode, *ctmp; CHECK_INODE(node); assert(node != &inode[0]); /* * If the inode was not already scheduled for deletion, partially * remove the node. */ if (!(node->i_flags & I_DELETED)) { /* Remove any children first (before I_DELETED is set!). */ TAILQ_FOREACH_SAFE(cnode, &node->i_children, i_siblings, ctmp) delete_inode(cnode); /* Unhash the inode from the table. */ LIST_REMOVE(node, i_hname); /* Unhash the inode from the table if needed. */ if (node->i_index != NO_INDEX) LIST_REMOVE(node, i_hindex); node->i_flags |= I_DELETED; /* * If this inode is not a directory, we don't care about being * able to find its parent. Unlink it from the parent now. */ if (!S_ISDIR(node->i_stat.mode)) unlink_inode(node); } if (node->i_count == 0 && TAILQ_EMPTY(&node->i_children)) { /* * If this inode still has a parent at this point, unlink it * now; noone can possibly refer to it anymore. */ if (node->i_parent != NULL) unlink_inode(node); /* Delete the actual node. */ TAILQ_INSERT_HEAD(&unused_inodes, node, i_unused); } } /* * Return whether the given inode has been deleted. */ int is_inode_deleted(const struct inode * node) { return (node->i_flags & I_DELETED); } /* * Find the inode specified by the request message, and decrease its reference * count. */ int fs_putnode(ino_t ino_nr, unsigned int count) { struct inode *node; /* Get the inode specified by its number. */ if ((node = find_inode(ino_nr)) == NULL) return EINVAL; /* Decrease the reference count. */ assert(node->i_count >= count); node->i_count -= count - 1; put_inode(node); return OK; }