GNU Linux-libre 4.9.287-gnu1
[releases.git] / fs / hfs / bfind.c
1 /*
2  *  linux/fs/hfs/bfind.c
3  *
4  * Copyright (C) 2001
5  * Brad Boyer (flar@allandria.com)
6  * (C) 2003 Ardis Technologies <roman@ardistech.com>
7  *
8  * Search routines for btrees
9  */
10
11 #include <linux/slab.h>
12 #include "btree.h"
13
14 int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
15 {
16         void *ptr;
17
18         fd->tree = tree;
19         fd->bnode = NULL;
20         ptr = kmalloc(tree->max_key_len * 2 + 4, GFP_KERNEL);
21         if (!ptr)
22                 return -ENOMEM;
23         fd->search_key = ptr;
24         fd->key = ptr + tree->max_key_len + 2;
25         hfs_dbg(BNODE_REFS, "find_init: %d (%p)\n",
26                 tree->cnid, __builtin_return_address(0));
27         switch (tree->cnid) {
28         case HFS_CAT_CNID:
29                 mutex_lock_nested(&tree->tree_lock, CATALOG_BTREE_MUTEX);
30                 break;
31         case HFS_EXT_CNID:
32                 mutex_lock_nested(&tree->tree_lock, EXTENTS_BTREE_MUTEX);
33                 break;
34         case HFS_ATTR_CNID:
35                 mutex_lock_nested(&tree->tree_lock, ATTR_BTREE_MUTEX);
36                 break;
37         default:
38                 return -EINVAL;
39         }
40         return 0;
41 }
42
43 void hfs_find_exit(struct hfs_find_data *fd)
44 {
45         hfs_bnode_put(fd->bnode);
46         kfree(fd->search_key);
47         hfs_dbg(BNODE_REFS, "find_exit: %d (%p)\n",
48                 fd->tree->cnid, __builtin_return_address(0));
49         mutex_unlock(&fd->tree->tree_lock);
50         fd->tree = NULL;
51 }
52
53 /* Find the record in bnode that best matches key (not greater than...)*/
54 int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd)
55 {
56         int cmpval;
57         u16 off, len, keylen;
58         int rec;
59         int b, e;
60         int res;
61
62         b = 0;
63         e = bnode->num_recs - 1;
64         res = -ENOENT;
65         do {
66                 rec = (e + b) / 2;
67                 len = hfs_brec_lenoff(bnode, rec, &off);
68                 keylen = hfs_brec_keylen(bnode, rec);
69                 if (keylen == 0) {
70                         res = -EINVAL;
71                         goto fail;
72                 }
73                 hfs_bnode_read(bnode, fd->key, off, keylen);
74                 cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
75                 if (!cmpval) {
76                         e = rec;
77                         res = 0;
78                         goto done;
79                 }
80                 if (cmpval < 0)
81                         b = rec + 1;
82                 else
83                         e = rec - 1;
84         } while (b <= e);
85         if (rec != e && e >= 0) {
86                 len = hfs_brec_lenoff(bnode, e, &off);
87                 keylen = hfs_brec_keylen(bnode, e);
88                 if (keylen == 0) {
89                         res = -EINVAL;
90                         goto fail;
91                 }
92                 hfs_bnode_read(bnode, fd->key, off, keylen);
93         }
94 done:
95         fd->record = e;
96         fd->keyoffset = off;
97         fd->keylength = keylen;
98         fd->entryoffset = off + keylen;
99         fd->entrylength = len - keylen;
100 fail:
101         return res;
102 }
103
104 /* Traverse a B*Tree from the root to a leaf finding best fit to key */
105 /* Return allocated copy of node found, set recnum to best record */
106 int hfs_brec_find(struct hfs_find_data *fd)
107 {
108         struct hfs_btree *tree;
109         struct hfs_bnode *bnode;
110         u32 nidx, parent;
111         __be32 data;
112         int height, res;
113
114         tree = fd->tree;
115         if (fd->bnode)
116                 hfs_bnode_put(fd->bnode);
117         fd->bnode = NULL;
118         nidx = tree->root;
119         if (!nidx)
120                 return -ENOENT;
121         height = tree->depth;
122         res = 0;
123         parent = 0;
124         for (;;) {
125                 bnode = hfs_bnode_find(tree, nidx);
126                 if (IS_ERR(bnode)) {
127                         res = PTR_ERR(bnode);
128                         bnode = NULL;
129                         break;
130                 }
131                 if (bnode->height != height)
132                         goto invalid;
133                 if (bnode->type != (--height ? HFS_NODE_INDEX : HFS_NODE_LEAF))
134                         goto invalid;
135                 bnode->parent = parent;
136
137                 res = __hfs_brec_find(bnode, fd);
138                 if (!height)
139                         break;
140                 if (fd->record < 0)
141                         goto release;
142
143                 parent = nidx;
144                 hfs_bnode_read(bnode, &data, fd->entryoffset, 4);
145                 nidx = be32_to_cpu(data);
146                 hfs_bnode_put(bnode);
147         }
148         fd->bnode = bnode;
149         return res;
150
151 invalid:
152         pr_err("inconsistency in B*Tree (%d,%d,%d,%u,%u)\n",
153                height, bnode->height, bnode->type, nidx, parent);
154         res = -EIO;
155 release:
156         hfs_bnode_put(bnode);
157         return res;
158 }
159
160 int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
161 {
162         int res;
163
164         res = hfs_brec_find(fd);
165         if (res)
166                 return res;
167         if (fd->entrylength > rec_len)
168                 return -EINVAL;
169         hfs_bnode_read(fd->bnode, rec, fd->entryoffset, fd->entrylength);
170         return 0;
171 }
172
173 int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
174 {
175         struct hfs_btree *tree;
176         struct hfs_bnode *bnode;
177         int idx, res = 0;
178         u16 off, len, keylen;
179
180         bnode = fd->bnode;
181         tree = bnode->tree;
182
183         if (cnt < 0) {
184                 cnt = -cnt;
185                 while (cnt > fd->record) {
186                         cnt -= fd->record + 1;
187                         fd->record = bnode->num_recs - 1;
188                         idx = bnode->prev;
189                         if (!idx) {
190                                 res = -ENOENT;
191                                 goto out;
192                         }
193                         hfs_bnode_put(bnode);
194                         bnode = hfs_bnode_find(tree, idx);
195                         if (IS_ERR(bnode)) {
196                                 res = PTR_ERR(bnode);
197                                 bnode = NULL;
198                                 goto out;
199                         }
200                 }
201                 fd->record -= cnt;
202         } else {
203                 while (cnt >= bnode->num_recs - fd->record) {
204                         cnt -= bnode->num_recs - fd->record;
205                         fd->record = 0;
206                         idx = bnode->next;
207                         if (!idx) {
208                                 res = -ENOENT;
209                                 goto out;
210                         }
211                         hfs_bnode_put(bnode);
212                         bnode = hfs_bnode_find(tree, idx);
213                         if (IS_ERR(bnode)) {
214                                 res = PTR_ERR(bnode);
215                                 bnode = NULL;
216                                 goto out;
217                         }
218                 }
219                 fd->record += cnt;
220         }
221
222         len = hfs_brec_lenoff(bnode, fd->record, &off);
223         keylen = hfs_brec_keylen(bnode, fd->record);
224         if (keylen == 0) {
225                 res = -EINVAL;
226                 goto out;
227         }
228         fd->keyoffset = off;
229         fd->keylength = keylen;
230         fd->entryoffset = off + keylen;
231         fd->entrylength = len - keylen;
232         hfs_bnode_read(bnode, fd->key, off, keylen);
233 out:
234         fd->bnode = bnode;
235         return res;
236 }