diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:05:47 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:05:47 +0000 |
commit | 27cfca1ec98e91261b1a5355d10a8996464b63af (patch) | |
tree | 8e895a53e372fa682b4c0a585b9377d67ed70d0e /fs/hfs/bnode.c | |
parent | 6a76fb7214c477ccf6582bd79c5b4ccc4f9c41b1 (diff) |
Look Ma' what I found on my harddisk ...
o New faster syscalls for 2.1.x, too
o Upgrade to 2.1.89.
Don't try to run this. It's flaky as hell. But feel free to debug ...
Diffstat (limited to 'fs/hfs/bnode.c')
-rw-r--r-- | fs/hfs/bnode.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c new file mode 100644 index 000000000..762278667 --- /dev/null +++ b/fs/hfs/bnode.c @@ -0,0 +1,540 @@ +/* + * linux/fs/hfs/bnode.c + * + * Copyright (C) 1995-1997 Paul H. Hargrove + * This file may be distributed under the terms of the GNU Public License. + * + * This file contains the code to access nodes in the B-tree structure. + * + * "XXX" in a comment is a note to myself to consider changing something. + * + * In function preconditions the term "valid" applied to a pointer to + * a structure means that the pointer is non-NULL and the structure it + * points to has all fields initialized to consistent values. + * + * The code in this file initializes some structures which contain + * pointers by calling memset(&foo, 0, sizeof(foo)). + * This produces the desired behavior only due to the non-ANSI + * assumption that the machine representation of NULL is all zeros. + */ + +#include "hfs_btree.h" + +/*================ File-local variables ================*/ + +/* debugging statistics */ +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) +int bnode_count = 0; +#endif + +/*================ Global functions ================*/ + +/* + * hfs_bnode_delete() + * + * Description: + * This function is called to remove a bnode from the cache and + * release its resources. + * Input Variable(s): + * struct hfs_bnode *bn: Pointer to the (struct hfs_bnode) to be + * removed from the cache. + * Output Variable(s): + * NONE + * Returns: + * void + * Preconditions: + * 'bn' points to a "valid" (struct hfs_bnode). + * Postconditions: + * The node 'bn' is removed from the cache, its memory freed and its + * buffer (if any) released. + */ +void hfs_bnode_delete(struct hfs_bnode *bn) +{ +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) + --bnode_count; +#endif + /* join neighbors */ + if (bn->next) { + bn->next->prev = bn->prev; + } + if (bn->prev) { + bn->prev->next = bn->next; + } + /* fix cache slot if necessary */ + if (bhash(bn->tree, bn->node) == bn) { + bhash(bn->tree, bn->node) = bn->next; + } + /* release resources */ + hfs_buffer_put(bn->buf); /* safe: checks for NULL argument */ + HFS_DELETE(bn); +} + + +/* + * hfs_bnode_read() + * + * Description: + * This function creates a (struct hfs_bnode) and, if appropriate, + * inserts it in the cache. + * Input Variable(s): + * struct hfs_bnode *bnode: pointer to the new bnode. + * struct hfs_btree *tree: pointer to the (struct hfs_btree) + * containing the desired node + * hfs_u32 node: the number of the desired node. + * int sticky: the value to assign to the 'sticky' field. + * Output Variable(s): + * NONE + * Returns: + * (struct hfs_bnode *) pointing to the newly created bnode or NULL. + * Preconditions: + * 'bnode' points to a "valid" (struct hfs_bnode). + * 'tree' points to a "valid" (struct hfs_btree). + * 'node' is an existing node number in the B-tree. + * Postconditions: + * The following are true of 'bnode' upon return: + * The 'magic' field is set to indicate a valid (struct hfs_bnode). + * The 'sticky', 'tree' and 'node' fields are initialized to the + * values of the of the corresponding arguments. + * If the 'sticky' argument is zero then the fields 'prev' and + * 'next' are initialized by inserting the (struct hfs_bnode) in the + * linked list of the appropriate cache slot; otherwise they are + * initialized to NULL. + * The data is read from disk (or buffer cache) and the 'buf' field + * points to the buffer for that data. + * If no other processes tried to access this node while this + * process was waiting on disk I/O (if necessary) then the + * remaining fields are zero ('count', 'resrv', 'lock') or NULL + * ('wqueue', 'rqueue') corresponding to no accesses. + * If there were access attempts during I/O then they were blocked + * until the I/O was complete, and the fields 'count', 'resrv', + * 'lock', 'wqueue' and 'rqueue' reflect the results of unblocking + * those processes when the I/O was completed. + */ +void hfs_bnode_read(struct hfs_bnode *bnode, struct hfs_btree *tree, + hfs_u32 node, int sticky) +{ + struct NodeDescriptor *nd; + int block, lcv; + hfs_u16 curr, prev, limit; + + /* Initialize the structure */ + memset(bnode, 0, sizeof(*bnode)); + bnode->magic = HFS_BNODE_MAGIC; + bnode->tree = tree; + bnode->node = node; + bnode->sticky = sticky; + + if (sticky == HFS_NOT_STICKY) { + /* Insert it in the cache if appropriate */ + if ((bnode->next = bhash(tree, node))) { + bnode->next->prev = bnode; + } + bhash(tree, node) = bnode; + } + + /* Make the bnode look like it is being + modified so other processes will wait for + the I/O to complete */ + bnode->count = bnode->resrv = bnode->lock = 1; + + /* Read in the node, possibly causing a schedule() + call. If the I/O fails then emit a warning. Each + process that was waiting on the bnode (including + the current one) will notice the failure and + hfs_bnode_relse() the node. The last hfs_bnode_relse() + will call hfs_bnode_delete() and discard the bnode. */ + + block = hfs_extent_map(&tree->entry.u.file.data_fork, node, 0); + if (!block) { + hfs_warn("hfs_bnode_read: bad node number 0x%08x\n", node); + } else if (hfs_buffer_ok(bnode->buf = + hfs_buffer_get(tree->sys_mdb, block, 1))) { + /* read in the NodeDescriptor */ + nd = (struct NodeDescriptor *)hfs_buffer_data(bnode->buf); + bnode->ndFLink = hfs_get_hl(nd->ndFLink); + bnode->ndBLink = hfs_get_hl(nd->ndBLink); + bnode->ndType = nd->ndType; + bnode->ndNHeight = nd->ndNHeight; + bnode->ndNRecs = hfs_get_hs(nd->ndNRecs); + + /* verify the integrity of the node */ + prev = sizeof(struct NodeDescriptor); + limit = HFS_SECTOR_SIZE - sizeof(hfs_u16)*(bnode->ndNRecs + 1); + for (lcv=1; lcv <= (bnode->ndNRecs + 1); ++lcv) { + curr = hfs_get_hs(RECTBL(bnode, lcv)); + if ((curr < prev) || (curr > limit)) { + hfs_warn("hfs_bnode_read: corrupt node " + "number 0x%08x\n", node); + hfs_buffer_put(bnode->buf); + bnode->buf = NULL; + break; + } + prev = curr; + } + } + + /* Undo our fakery with the lock state and + hfs_wake_up() anyone who we managed to trick */ + --bnode->count; + bnode->resrv = bnode->lock = 0; + hfs_wake_up(&bnode->rqueue); +} + +/* + * hfs_bnode_lock() + * + * Description: + * This function does the locking of a bnode. + * Input Variable(s): + * struct hfs_bnode *bn: pointer to the (struct hfs_bnode) to lock + * int lock_type: the type of lock desired + * Output Variable(s): + * NONE + * Returns: + * void + * Preconditions: + * 'bn' points to a "valid" (struct hfs_bnode). + * 'lock_type' is a valid hfs_lock_t + * Postconditions: + * The 'count' field of 'bn' is incremented by one. If 'lock_type' + * is HFS_LOCK_RESRV the 'resrv' field is also incremented. + */ +void hfs_bnode_lock(struct hfs_bnode_ref *bnr, int lock_type) +{ + struct hfs_bnode *bn = bnr->bn; + + if ((lock_type == bnr->lock_type) || !bn) { + return; + } + + if (bnr->lock_type == HFS_LOCK_WRITE) { + hfs_bnode_commit(bnr->bn); + } + + switch (lock_type) { + default: + goto bail; + break; + + case HFS_LOCK_READ: + /* We may not obtain read access if any process is + currently modifying or waiting to modify this node. + If we can't obtain access we wait on the rqueue + wait queue to be woken up by the modifying process + when it relinquishes its lock. */ + switch (bnr->lock_type) { + default: + goto bail; + break; + + case HFS_LOCK_NONE: + while (bn->lock || bn->wqueue) { + hfs_sleep_on(&bn->rqueue); + } + ++bn->count; + break; + } + break; + + case HFS_LOCK_RESRV: + /* We may not obtain a reservation (read access with + an option to write later), if any process currently + holds a reservation on this node. That includes + any process which is currently modifying this node. + If we can't obtain access, then we wait on the + rqueue wait queue to e woken up by the + reservation-holder when it calls hfs_bnode_relse. */ + switch (bnr->lock_type) { + default: + goto bail; + break; + + case HFS_LOCK_NONE: + while (bn->resrv) { + hfs_sleep_on(&bn->rqueue); + } + bn->resrv = 1; + ++bn->count; + break; + + case HFS_LOCK_WRITE: + bn->lock = 0; + hfs_wake_up(&bn->rqueue); + break; + } + break; + + case HFS_LOCK_WRITE: + switch (bnr->lock_type) { + default: + goto bail; + break; + + case HFS_LOCK_NONE: + while (bn->resrv) { + hfs_sleep_on(&bn->rqueue); + } + bn->resrv = 1; + ++bn->count; + case HFS_LOCK_RESRV: + while (bn->count > 1) { + hfs_sleep_on(&bn->wqueue); + } + bn->lock = 1; + break; + } + break; + + case HFS_LOCK_NONE: + switch (bnr->lock_type) { + default: + goto bail; + break; + + case HFS_LOCK_READ: + /* This process was reading this node. If + there is now exactly one other process using + the node then hfs_wake_up() a (potentially + nonexistent) waiting process. Note that I + refer to "a" process since the reservation + system ensures that only one process can + get itself on the wait queue. */ + if (bn->count == 2) { + hfs_wake_up(&bn->wqueue); + } + break; + + case HFS_LOCK_WRITE: + /* This process was modifying this node. + Unlock the node and fall-through to the + HFS_LOCK_RESRV case, since a 'reservation' + is a prerequisite for HFS_LOCK_WRITE. */ + bn->lock = 0; + case HFS_LOCK_RESRV: + /* This process had placed a 'reservation' on + this node, indicating an intention to + possibly modify the node. We can get to + this spot directly (if the 'reservation' + not converted to a HFS_LOCK_WRITE), or by + falling through from the above case if the + reservation was converted. + Since HFS_LOCK_RESRV and HFS_LOCK_WRITE + both block processes that want access + (HFS_LOCK_RESRV blocks other processes that + want reservations but allow HFS_LOCK_READ + accesses, while HFS_LOCK_WRITE must have + exclusive access and thus blocks both + types) we hfs_wake_up() any processes that + might be waiting for access. If multiple + processes are waiting for a reservation + then the magic of process scheduling will + settle the dispute. */ + bn->resrv = 0; + hfs_wake_up(&bn->rqueue); + break; + } + --bn->count; + break; + } + bnr->lock_type = lock_type; + return; + +bail: + hfs_warn("hfs_bnode_lock: invalid lock change: %d->%d.\n", + bnr->lock_type, lock_type); + return; +} + +/* + * hfs_bnode_relse() + * + * Description: + * This function is called when a process is done using a bnode. If + * the proper conditions are met then we call hfs_bnode_delete() to remove + * it from the cache. If it is not deleted then we update its state + * to reflect one less process using it. + * Input Variable(s): + * struct hfs_bnode *bn: pointer to the (struct hfs_bnode) to release. + * int lock_type: The type of lock held by the process releasing this node. + * Output Variable(s): + * NONE + * Returns: + * void + * Preconditions: + * 'bn' is NULL or points to a "valid" (struct hfs_bnode). + * Postconditions: + * If 'bn' meets the appropriate conditions (see below) then it is + * kept in the cache and all fields are set to consistent values + * which reflect one less process using the node than upon entry. + * If 'bn' does not meet the conditions then it is deleted (see + * hfs_bnode_delete() for postconditions). + * In either case, if 'lock_type' is HFS_LOCK_WRITE + * then the corresponding buffer is dirtied. + */ +void hfs_bnode_relse(struct hfs_bnode_ref *bnr) +{ + struct hfs_bnode *bn; + + if (!bnr || !(bn = bnr->bn)) { + return; + } + + /* We update the lock state of the node if it is still in use + or if it is "sticky" (such as the B-tree head and root). + Otherwise we just delete it. */ + if ((bn->count > 1) || (bn->rqueue) || (bn->sticky != HFS_NOT_STICKY)) { + hfs_bnode_lock(bnr, HFS_LOCK_NONE); + } else { + /* dirty buffer if we (might) have modified it */ + if (bnr->lock_type == HFS_LOCK_WRITE) { + hfs_bnode_commit(bn); + } + hfs_bnode_delete(bn); + bnr->lock_type = HFS_LOCK_NONE; + } + bnr->bn = NULL; +} + +/* + * hfs_bnode_find() + * + * Description: + * This function is called to obtain a bnode. The cache is + * searched for the node. If it not found there it is added to + * the cache by hfs_bnode_read(). There are two special cases node=0 + * (the header node) and node='tree'->bthRoot (the root node), in + * which the nodes are obtained from fields of 'tree' without + * consulting or modifying the cache. + * Input Variable(s): + * struct hfs_tree *tree: pointer to the (struct hfs_btree) from + * which to get a node. + * int node: the node number to get from 'tree'. + * int lock_type: The kind of access (HFS_LOCK_READ, or + * HFS_LOCK_RESRV) to obtain to the node + * Output Variable(s): + * NONE + * Returns: + * (struct hfs_bnode_ref) Reference to the requested node. + * Preconditions: + * 'tree' points to a "valid" (struct hfs_btree). + * Postconditions: + * If 'node' refers to a valid node in 'tree' and 'lock_type' has + * one of the values listed above and no I/O errors occur then the + * value returned refers to a valid (struct hfs_bnode) corresponding + * to the requested node with the requested access type. The node + * is also added to the cache if not previously present and not the + * root or header. + * If the conditions given above are not met, the bnode in the + * returned reference is NULL. + */ +struct hfs_bnode_ref hfs_bnode_find(struct hfs_btree *tree, + hfs_u32 node, int lock_type) +{ + struct hfs_bnode *bn; + struct hfs_bnode *empty = NULL; + struct hfs_bnode_ref bnr; + + bnr.lock_type = HFS_LOCK_NONE; + bnr.bn = NULL; + +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) + hfs_warn("hfs_bnode_find: %c %d:%d\n", + lock_type==HFS_LOCK_READ?'R': + (lock_type==HFS_LOCK_RESRV?'V':'W'), + (int)ntohl(tree->entry.cnid), node); +#endif + + /* check special cases */ + if (!node) { + bn = &tree->head; + goto return_it; + } else if (node == tree->bthRoot) { + bn = tree->root; + goto return_it; + } + +restart: + /* look for the node in the cache. */ + bn = bhash(tree, node); + while (bn && (bn->magic == HFS_BNODE_MAGIC)) { + if (bn->node == node) { + goto found_it; + } + bn = bn->next; + } + + if (!empty) { +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) + ++bnode_count; +#endif + if (HFS_NEW(empty)) { + goto restart; + } + return bnr; + } + bn = empty; + hfs_bnode_read(bn, tree, node, HFS_NOT_STICKY); + goto return_it; + +found_it: + /* check validity */ + if (bn->magic != HFS_BNODE_MAGIC) { + /* If we find a corrupt bnode then we return + NULL. However, we don't try to remove it + from the cache or release its resources + since we have no idea what kind of trouble + we could get into that way. */ + hfs_warn("hfs_bnode_find: bnode cache is corrupt.\n"); + return bnr; + } + if (empty) { +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) + --bnode_count; +#endif + HFS_DELETE(empty); + } + +return_it: + /* Wait our turn */ + bnr.bn = bn; + hfs_bnode_lock(&bnr, lock_type); + + /* Check for failure to read the node from disk */ + if (!hfs_buffer_ok(bn->buf)) { + hfs_bnode_relse(&bnr); + } + +#if defined(DEBUG_BNODES) || defined(DEBUG_ALL) + if (!bnr.bn) { + hfs_warn("hfs_bnode_find: failed\n"); + } else { + hfs_warn("hfs_bnode_find: use %d(%d) lvl %d [%d]\n", bn->count, + bn->buf->b_count, bn->ndNHeight, bnode_count); + } +#endif + + return bnr; +} + +/* + * hfs_bnode_commit() + * + * Called to write a possibly dirty bnode back to disk. + */ +void hfs_bnode_commit(struct hfs_bnode *bn) +{ + if (hfs_buffer_ok(bn->buf)) { + struct NodeDescriptor *nd; + nd = (struct NodeDescriptor *)hfs_buffer_data(bn->buf); + + hfs_put_hl(bn->ndFLink, nd->ndFLink); + hfs_put_hl(bn->ndBLink, nd->ndBLink); + nd->ndType = bn->ndType; + nd->ndNHeight = bn->ndNHeight; + hfs_put_hs(bn->ndNRecs, nd->ndNRecs); + hfs_buffer_dirty(bn->buf); + + /* increment write count */ + hfs_mdb_dirty(bn->tree->sys_mdb); + } +} |