| /* |
| * linux/fs/hfs/bfind.c |
| * |
| * Copyright (C) 1995, 1996 Paul H. Hargrove |
| * This file may be distributed under the terms of the GNU General Public License. |
| * |
| * This file contains the code to access records in a btree. |
| * |
| * "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. |
| */ |
| |
| #include "hfs_btree.h" |
| |
| /*================ Global functions ================*/ |
| |
| /* |
| * hfs_brec_relse() |
| * |
| * Description: |
| * This function releases some of the nodes associated with a brec. |
| * Input Variable(s): |
| * struct hfs_brec *brec: pointer to the brec to release some nodes from. |
| * struct hfs_belem *elem: the last node to release or NULL for all |
| * Output Variable(s): |
| * NONE |
| * Returns: |
| * void |
| * Preconditions: |
| * 'brec' points to a "valid" (struct hfs_brec) |
| * Postconditions: |
| * All nodes between the indicated node and the beginning of the path |
| * are released. |
| */ |
| void hfs_brec_relse(struct hfs_brec *brec, struct hfs_belem *elem) |
| { |
| if (!elem) { |
| elem = brec->bottom; |
| } |
| |
| while (brec->top <= elem) { |
| hfs_bnode_relse(&brec->top->bnr); |
| ++brec->top; |
| } |
| } |
| |
| /* |
| * hfs_bfind() |
| * |
| * Description: |
| * This function has sole responsibility for locating existing |
| * records in a B-tree. Given a B-tree and a key it locates the |
| * "greatest" record "less than or equal to" the given key. The |
| * exact behavior is determined by the bits of the flags variable as |
| * follows: |
| * ('flags' & HFS_LOCK_MASK): |
| * The lock_type argument to be used when calling hfs_bnode_find(). |
| * HFS_BFIND_EXACT: only accept an exact match, otherwise take the |
| * "largest" record less than 'target' as a "match" |
| * HFS_BFIND_LOCK: request HFS_LOCK_WRITE access to the node containing |
| * the "matching" record when it is located |
| * HFS_BPATH_FIRST: keep access to internal nodes when accessing their |
| * first child. |
| * HFS_BPATH_OVERFLOW: keep access to internal nodes when the accessed |
| * child is too full to insert another pointer record. |
| * HFS_BPATH_UNDERFLOW: keep access to internal nodes when the accessed |
| * child is would be less than half full upon removing a pointer record. |
| * Input Variable(s): |
| * struct hfs_brec *brec: pointer to the (struct hfs_brec) to hold |
| * the search results. |
| * struct hfs_bkey *target: pointer to the (struct hfs_bkey) |
| * to search for |
| * int flags: bitwise OR of flags which determine the function's behavior |
| * Output Variable(s): |
| * 'brec' contains the results of the search on success or is invalid |
| * on failure. |
| * Returns: |
| * int: 0 or 1 on success or an error code on failure: |
| * -EINVAL: one of the input variables was NULL. |
| * -ENOENT: tree is valid but empty or no "matching" record was located. |
| * If the HFS_BFIND_EXACT bit of 'flags' is not set then the case of no |
| * matching record will give a 'brec' with a 'record' field of zero |
| * rather than returning this error. |
| * -EIO: an I/O operation or an assertion about the structure of a |
| * valid B-tree failed indicating corruption of either the B-tree |
| * structure on the disk or one of the in-core structures representing |
| * the B-tree. |
| * (This could also be returned if a kmalloc() call failed in a |
| * subordinate routine that is intended to get the data from the |
| * disk or the buffer cache.) |
| * Preconditions: |
| * 'brec' is NULL or points to a (struct hfs_brec) with a 'tree' field |
| * which points to a valid (struct hfs_btree). |
| * 'target' is NULL or points to a "valid" (struct hfs_bkey) |
| * Postconditions: |
| * If 'brec', 'brec->tree' or 'target' is NULL then -EINVAL is returned. |
| * If 'brec', 'brec->tree' and 'target' are non-NULL but the tree |
| * is empty then -ENOENT is returned. |
| * If 'brec', 'brec->tree' and 'target' are non-NULL but the call to |
| * hfs_brec_init() fails then '*brec' is NULL and -EIO is returned. |
| * If 'brec', 'brec->tree' and 'target' are non-NULL and the tree is |
| * non-empty then the tree is searched as follows: |
| * If any call to hfs_brec_next() fails or returns a node that is |
| * neither an index node nor a leaf node then -EIO is returned to |
| * indicate that the B-tree or buffer-cache are corrupted. |
| * If every record in the tree is "greater than" the given key |
| * and the HFS_BFIND_EXACT bit of 'flags' is set then -ENOENT is returned. |
| * If every record in the tree is "greater than" the given key |
| * and the HFS_BFIND_EXACT bit of 'flags' is clear then 'brec' refers |
| * to the first leaf node in the tree and has a 'record' field of |
| * zero, and 1 is returned. |
| * If a "matching" record is located with key "equal to" 'target' |
| * then the return value is 0 and 'brec' indicates the record. |
| * If a "matching" record is located with key "greater than" 'target' |
| * then the behavior is determined as follows: |
| * If the HFS_BFIND_EXACT bit of 'flags' is not set then 1 is returned |
| * and 'brec' refers to the "matching" record. |
| * If the HFS_BFIND_EXACT bit of 'flags' is set then -ENOENT is returned. |
| * If the return value is non-negative and the HFS_BFIND_LOCK bit of |
| * 'flags' is set then hfs_brec_lock() is called on the bottom element |
| * of 'brec' before returning. |
| */ |
| int hfs_bfind(struct hfs_brec *brec, struct hfs_btree *tree, |
| const struct hfs_bkey *target, int flags) |
| { |
| struct hfs_belem *curr; |
| struct hfs_bkey *key; |
| struct hfs_bnode *bn; |
| int result, ntype; |
| |
| /* check for invalid arguments */ |
| if (!brec || (tree->magic != HFS_BTREE_MAGIC) || !target) { |
| return -EINVAL; |
| } |
| |
| /* check for empty tree */ |
| if (!tree->root || !tree->bthNRecs) { |
| return -ENOENT; |
| } |
| |
| /* start search at root of tree */ |
| if (!(curr = hfs_brec_init(brec, tree, flags))) { |
| return -EIO; |
| } |
| |
| /* traverse the tree */ |
| do { |
| bn = curr->bnr.bn; |
| |
| if (!curr->record) { |
| hfs_warn("hfs_bfind: empty bnode\n"); |
| hfs_brec_relse(brec, NULL); |
| return -EIO; |
| } |
| |
| /* reverse linear search yielding largest key "less |
| than or equal to" 'target'. |
| It is questionable whether a binary search would be |
| significantly faster */ |
| do { |
| key = belem_key(curr); |
| if (!key->KeyLen) { |
| hfs_warn("hfs_bfind: empty key\n"); |
| hfs_brec_relse(brec, NULL); |
| return -EIO; |
| } |
| result = (tree->compare)(target, key); |
| } while ((result<0) && (--curr->record)); |
| |
| ntype = bn->ndType; |
| |
| /* see if all keys > target */ |
| if (!curr->record) { |
| if (bn->ndBLink) { |
| /* at a node other than the left-most at a |
| given level it means the parent had an |
| incorrect key for this child */ |
| hfs_brec_relse(brec, NULL); |
| hfs_warn("hfs_bfind: corrupted b-tree %d.\n", |
| (int)ntohl(tree->entry.cnid)); |
| return -EIO; |
| } |
| if (flags & HFS_BFIND_EXACT) { |
| /* we're not going to find it */ |
| hfs_brec_relse(brec, NULL); |
| return -ENOENT; |
| } |
| if (ntype == ndIndxNode) { |
| /* since we are at the left-most node at |
| the current level and looking for the |
| predecessor of 'target' keep going down */ |
| curr->record = 1; |
| } else { |
| /* we're at first leaf so fall through */ |
| } |
| } |
| |
| /* get next node if necessary */ |
| if ((ntype == ndIndxNode) && !(curr = hfs_brec_next(brec))) { |
| return -EIO; |
| } |
| } while (ntype == ndIndxNode); |
| |
| if (key->KeyLen > tree->bthKeyLen) { |
| hfs_warn("hfs_bfind: oversized key\n"); |
| hfs_brec_relse(brec, NULL); |
| return -EIO; |
| } |
| |
| if (ntype != ndLeafNode) { |
| hfs_warn("hfs_bfind: invalid node type %02x in node %d of " |
| "btree %d\n", bn->ndType, bn->node, |
| (int)ntohl(tree->entry.cnid)); |
| hfs_brec_relse(brec, NULL); |
| return -EIO; |
| } |
| |
| if ((flags & HFS_BFIND_EXACT) && result) { |
| hfs_brec_relse(brec, NULL); |
| return -ENOENT; |
| } |
| |
| if (!(flags & HFS_BPATH_MASK)) { |
| hfs_brec_relse(brec, brec->bottom-1); |
| } |
| |
| if (flags & HFS_BFIND_LOCK) { |
| hfs_brec_lock(brec, brec->bottom); |
| } |
| |
| brec->key = brec_key(brec); |
| brec->data = bkey_record(brec->key); |
| |
| return result ? 1 : 0; |
| } |
| |
| /* |
| * hfs_bsucc() |
| * |
| * Description: |
| * This function overwrites '*brec' with its successor in the B-tree, |
| * obtaining the same type of access. |
| * Input Variable(s): |
| * struct hfs_brec *brec: address of the (struct hfs_brec) to overwrite |
| * with its successor |
| * Output Variable(s): |
| * struct hfs_brec *brec: address of the successor of the original |
| * '*brec' or to invalid data |
| * Returns: |
| * int: 0 on success, or one of -EINVAL, -EIO, or -EINVAL on failure |
| * Preconditions: |
| * 'brec' pointers to a "valid" (struct hfs_brec) |
| * Postconditions: |
| * If the given '*brec' is not "valid" -EINVAL is returned and |
| * '*brec' is unchanged. |
| * If the given 'brec' is "valid" but has no successor then -ENOENT |
| * is returned and '*brec' is invalid. |
| * If a call to hfs_bnode_find() is necessary to find the successor, |
| * but fails then -EIO is returned and '*brec' is invalid. |
| * If none of the three previous conditions prevents finding the |
| * successor of '*brec', then 0 is returned, and '*brec' is overwritten |
| * with the (struct hfs_brec) for its successor. |
| * In the cases when '*brec' is invalid, the old records is freed. |
| */ |
| int hfs_bsucc(struct hfs_brec *brec, int count) |
| { |
| struct hfs_belem *belem; |
| struct hfs_bnode *bn; |
| |
| if (!brec || !(belem = brec->bottom) || (belem != brec->top) || |
| !(bn = belem->bnr.bn) || (bn->magic != HFS_BNODE_MAGIC) || |
| !bn->tree || (bn->tree->magic != HFS_BTREE_MAGIC) || |
| !hfs_buffer_ok(bn->buf)) { |
| hfs_warn("hfs_bsucc: invalid/corrupt arguments.\n"); |
| return -EINVAL; |
| } |
| |
| while (count) { |
| int left = bn->ndNRecs - belem->record; |
| |
| if (left < count) { |
| struct hfs_bnode_ref old; |
| hfs_u32 node; |
| |
| /* Advance to next node */ |
| if (!(node = bn->ndFLink)) { |
| hfs_brec_relse(brec, belem); |
| return -ENOENT; |
| } |
| if (node == bn->node) { |
| hfs_warn("hfs_bsucc: corrupt btree\n"); |
| hfs_brec_relse(brec, belem); |
| return -EIO; |
| } |
| old = belem->bnr; |
| belem->bnr = hfs_bnode_find(brec->tree, node, |
| belem->bnr.lock_type); |
| hfs_bnode_relse(&old); |
| if (!(bn = belem->bnr.bn)) { |
| return -EIO; |
| } |
| belem->record = 1; |
| count -= (left + 1); |
| } else { |
| belem->record += count; |
| break; |
| } |
| } |
| brec->key = belem_key(belem); |
| brec->data = bkey_record(brec->key); |
| |
| if (brec->key->KeyLen > brec->tree->bthKeyLen) { |
| hfs_warn("hfs_bsucc: oversized key\n"); |
| hfs_brec_relse(brec, NULL); |
| return -EIO; |
| } |
| |
| return 0; |
| } |