summaryrefslogtreecommitdiffstats
path: root/fs/hfs/bfind.c
blob: d8d7e933d4d2f075545c3d29f6a8dae470949fce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
 * linux/fs/hfs/bfind.c
 *
 * Copyright (C) 1995, 1996  Paul H. Hargrove
 * This file may be distributed under the terms of the GNU 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;
}