diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/sysv/itree.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/sysv/itree.c')
-rw-r--r-- | fs/sysv/itree.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/fs/sysv/itree.c b/fs/sysv/itree.c new file mode 100644 index 000000000000..86f5f8d43d0f --- /dev/null +++ b/fs/sysv/itree.c | |||
@@ -0,0 +1,475 @@ | |||
1 | /* | ||
2 | * linux/fs/sysv/itree.c | ||
3 | * | ||
4 | * Handling of indirect blocks' trees. | ||
5 | * AV, Sep--Dec 2000 | ||
6 | */ | ||
7 | |||
8 | #include <linux/buffer_head.h> | ||
9 | #include <linux/mount.h> | ||
10 | #include <linux/string.h> | ||
11 | #include "sysv.h" | ||
12 | |||
13 | enum {DIRECT = 10, DEPTH = 4}; /* Have triple indirect */ | ||
14 | |||
15 | static inline void dirty_indirect(struct buffer_head *bh, struct inode *inode) | ||
16 | { | ||
17 | mark_buffer_dirty_inode(bh, inode); | ||
18 | if (IS_SYNC(inode)) | ||
19 | sync_dirty_buffer(bh); | ||
20 | } | ||
21 | |||
22 | static int block_to_path(struct inode *inode, long block, int offsets[DEPTH]) | ||
23 | { | ||
24 | struct super_block *sb = inode->i_sb; | ||
25 | struct sysv_sb_info *sbi = SYSV_SB(sb); | ||
26 | int ptrs_bits = sbi->s_ind_per_block_bits; | ||
27 | unsigned long indirect_blocks = sbi->s_ind_per_block, | ||
28 | double_blocks = sbi->s_ind_per_block_2; | ||
29 | int n = 0; | ||
30 | |||
31 | if (block < 0) { | ||
32 | printk("sysv_block_map: block < 0\n"); | ||
33 | } else if (block < DIRECT) { | ||
34 | offsets[n++] = block; | ||
35 | } else if ( (block -= DIRECT) < indirect_blocks) { | ||
36 | offsets[n++] = DIRECT; | ||
37 | offsets[n++] = block; | ||
38 | } else if ((block -= indirect_blocks) < double_blocks) { | ||
39 | offsets[n++] = DIRECT+1; | ||
40 | offsets[n++] = block >> ptrs_bits; | ||
41 | offsets[n++] = block & (indirect_blocks - 1); | ||
42 | } else if (((block -= double_blocks) >> (ptrs_bits * 2)) < indirect_blocks) { | ||
43 | offsets[n++] = DIRECT+2; | ||
44 | offsets[n++] = block >> (ptrs_bits * 2); | ||
45 | offsets[n++] = (block >> ptrs_bits) & (indirect_blocks - 1); | ||
46 | offsets[n++] = block & (indirect_blocks - 1); | ||
47 | } else { | ||
48 | /* nothing */; | ||
49 | } | ||
50 | return n; | ||
51 | } | ||
52 | |||
53 | static inline int block_to_cpu(struct sysv_sb_info *sbi, sysv_zone_t nr) | ||
54 | { | ||
55 | return sbi->s_block_base + fs32_to_cpu(sbi, nr); | ||
56 | } | ||
57 | |||
58 | typedef struct { | ||
59 | sysv_zone_t *p; | ||
60 | sysv_zone_t key; | ||
61 | struct buffer_head *bh; | ||
62 | } Indirect; | ||
63 | |||
64 | static DEFINE_RWLOCK(pointers_lock); | ||
65 | |||
66 | static inline void add_chain(Indirect *p, struct buffer_head *bh, sysv_zone_t *v) | ||
67 | { | ||
68 | p->key = *(p->p = v); | ||
69 | p->bh = bh; | ||
70 | } | ||
71 | |||
72 | static inline int verify_chain(Indirect *from, Indirect *to) | ||
73 | { | ||
74 | while (from <= to && from->key == *from->p) | ||
75 | from++; | ||
76 | return (from > to); | ||
77 | } | ||
78 | |||
79 | static inline sysv_zone_t *block_end(struct buffer_head *bh) | ||
80 | { | ||
81 | return (sysv_zone_t*)((char*)bh->b_data + bh->b_size); | ||
82 | } | ||
83 | |||
84 | /* | ||
85 | * Requires read_lock(&pointers_lock) or write_lock(&pointers_lock) | ||
86 | */ | ||
87 | static Indirect *get_branch(struct inode *inode, | ||
88 | int depth, | ||
89 | int offsets[], | ||
90 | Indirect chain[], | ||
91 | int *err) | ||
92 | { | ||
93 | struct super_block *sb = inode->i_sb; | ||
94 | Indirect *p = chain; | ||
95 | struct buffer_head *bh; | ||
96 | |||
97 | *err = 0; | ||
98 | add_chain(chain, NULL, SYSV_I(inode)->i_data + *offsets); | ||
99 | if (!p->key) | ||
100 | goto no_block; | ||
101 | while (--depth) { | ||
102 | int block = block_to_cpu(SYSV_SB(sb), p->key); | ||
103 | bh = sb_bread(sb, block); | ||
104 | if (!bh) | ||
105 | goto failure; | ||
106 | if (!verify_chain(chain, p)) | ||
107 | goto changed; | ||
108 | add_chain(++p, bh, (sysv_zone_t*)bh->b_data + *++offsets); | ||
109 | if (!p->key) | ||
110 | goto no_block; | ||
111 | } | ||
112 | return NULL; | ||
113 | |||
114 | changed: | ||
115 | brelse(bh); | ||
116 | *err = -EAGAIN; | ||
117 | goto no_block; | ||
118 | failure: | ||
119 | *err = -EIO; | ||
120 | no_block: | ||
121 | return p; | ||
122 | } | ||
123 | |||
124 | static int alloc_branch(struct inode *inode, | ||
125 | int num, | ||
126 | int *offsets, | ||
127 | Indirect *branch) | ||
128 | { | ||
129 | int blocksize = inode->i_sb->s_blocksize; | ||
130 | int n = 0; | ||
131 | int i; | ||
132 | |||
133 | branch[0].key = sysv_new_block(inode->i_sb); | ||
134 | if (branch[0].key) for (n = 1; n < num; n++) { | ||
135 | struct buffer_head *bh; | ||
136 | int parent; | ||
137 | /* Allocate the next block */ | ||
138 | branch[n].key = sysv_new_block(inode->i_sb); | ||
139 | if (!branch[n].key) | ||
140 | break; | ||
141 | /* | ||
142 | * Get buffer_head for parent block, zero it out and set | ||
143 | * the pointer to new one, then send parent to disk. | ||
144 | */ | ||
145 | parent = block_to_cpu(SYSV_SB(inode->i_sb), branch[n-1].key); | ||
146 | bh = sb_getblk(inode->i_sb, parent); | ||
147 | lock_buffer(bh); | ||
148 | memset(bh->b_data, 0, blocksize); | ||
149 | branch[n].bh = bh; | ||
150 | branch[n].p = (sysv_zone_t*) bh->b_data + offsets[n]; | ||
151 | *branch[n].p = branch[n].key; | ||
152 | set_buffer_uptodate(bh); | ||
153 | unlock_buffer(bh); | ||
154 | dirty_indirect(bh, inode); | ||
155 | } | ||
156 | if (n == num) | ||
157 | return 0; | ||
158 | |||
159 | /* Allocation failed, free what we already allocated */ | ||
160 | for (i = 1; i < n; i++) | ||
161 | bforget(branch[i].bh); | ||
162 | for (i = 0; i < n; i++) | ||
163 | sysv_free_block(inode->i_sb, branch[i].key); | ||
164 | return -ENOSPC; | ||
165 | } | ||
166 | |||
167 | static inline int splice_branch(struct inode *inode, | ||
168 | Indirect chain[], | ||
169 | Indirect *where, | ||
170 | int num) | ||
171 | { | ||
172 | int i; | ||
173 | |||
174 | /* Verify that place we are splicing to is still there and vacant */ | ||
175 | write_lock(&pointers_lock); | ||
176 | if (!verify_chain(chain, where-1) || *where->p) | ||
177 | goto changed; | ||
178 | *where->p = where->key; | ||
179 | write_unlock(&pointers_lock); | ||
180 | |||
181 | inode->i_ctime = CURRENT_TIME_SEC; | ||
182 | |||
183 | /* had we spliced it onto indirect block? */ | ||
184 | if (where->bh) | ||
185 | dirty_indirect(where->bh, inode); | ||
186 | |||
187 | if (IS_SYNC(inode)) | ||
188 | sysv_sync_inode(inode); | ||
189 | else | ||
190 | mark_inode_dirty(inode); | ||
191 | return 0; | ||
192 | |||
193 | changed: | ||
194 | write_unlock(&pointers_lock); | ||
195 | for (i = 1; i < num; i++) | ||
196 | bforget(where[i].bh); | ||
197 | for (i = 0; i < num; i++) | ||
198 | sysv_free_block(inode->i_sb, where[i].key); | ||
199 | return -EAGAIN; | ||
200 | } | ||
201 | |||
202 | static int get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create) | ||
203 | { | ||
204 | int err = -EIO; | ||
205 | int offsets[DEPTH]; | ||
206 | Indirect chain[DEPTH]; | ||
207 | struct super_block *sb = inode->i_sb; | ||
208 | Indirect *partial; | ||
209 | int left; | ||
210 | int depth = block_to_path(inode, iblock, offsets); | ||
211 | |||
212 | if (depth == 0) | ||
213 | goto out; | ||
214 | |||
215 | reread: | ||
216 | read_lock(&pointers_lock); | ||
217 | partial = get_branch(inode, depth, offsets, chain, &err); | ||
218 | read_unlock(&pointers_lock); | ||
219 | |||
220 | /* Simplest case - block found, no allocation needed */ | ||
221 | if (!partial) { | ||
222 | got_it: | ||
223 | map_bh(bh_result, sb, block_to_cpu(SYSV_SB(sb), | ||
224 | chain[depth-1].key)); | ||
225 | /* Clean up and exit */ | ||
226 | partial = chain+depth-1; /* the whole chain */ | ||
227 | goto cleanup; | ||
228 | } | ||
229 | |||
230 | /* Next simple case - plain lookup or failed read of indirect block */ | ||
231 | if (!create || err == -EIO) { | ||
232 | cleanup: | ||
233 | while (partial > chain) { | ||
234 | brelse(partial->bh); | ||
235 | partial--; | ||
236 | } | ||
237 | out: | ||
238 | return err; | ||
239 | } | ||
240 | |||
241 | /* | ||
242 | * Indirect block might be removed by truncate while we were | ||
243 | * reading it. Handling of that case (forget what we've got and | ||
244 | * reread) is taken out of the main path. | ||
245 | */ | ||
246 | if (err == -EAGAIN) | ||
247 | goto changed; | ||
248 | |||
249 | left = (chain + depth) - partial; | ||
250 | err = alloc_branch(inode, left, offsets+(partial-chain), partial); | ||
251 | if (err) | ||
252 | goto cleanup; | ||
253 | |||
254 | if (splice_branch(inode, chain, partial, left) < 0) | ||
255 | goto changed; | ||
256 | |||
257 | set_buffer_new(bh_result); | ||
258 | goto got_it; | ||
259 | |||
260 | changed: | ||
261 | while (partial > chain) { | ||
262 | brelse(partial->bh); | ||
263 | partial--; | ||
264 | } | ||
265 | goto reread; | ||
266 | } | ||
267 | |||
268 | static inline int all_zeroes(sysv_zone_t *p, sysv_zone_t *q) | ||
269 | { | ||
270 | while (p < q) | ||
271 | if (*p++) | ||
272 | return 0; | ||
273 | return 1; | ||
274 | } | ||
275 | |||
276 | static Indirect *find_shared(struct inode *inode, | ||
277 | int depth, | ||
278 | int offsets[], | ||
279 | Indirect chain[], | ||
280 | sysv_zone_t *top) | ||
281 | { | ||
282 | Indirect *partial, *p; | ||
283 | int k, err; | ||
284 | |||
285 | *top = 0; | ||
286 | for (k = depth; k > 1 && !offsets[k-1]; k--) | ||
287 | ; | ||
288 | |||
289 | write_lock(&pointers_lock); | ||
290 | partial = get_branch(inode, k, offsets, chain, &err); | ||
291 | if (!partial) | ||
292 | partial = chain + k-1; | ||
293 | /* | ||
294 | * If the branch acquired continuation since we've looked at it - | ||
295 | * fine, it should all survive and (new) top doesn't belong to us. | ||
296 | */ | ||
297 | if (!partial->key && *partial->p) { | ||
298 | write_unlock(&pointers_lock); | ||
299 | goto no_top; | ||
300 | } | ||
301 | for (p=partial; p>chain && all_zeroes((sysv_zone_t*)p->bh->b_data,p->p); p--) | ||
302 | ; | ||
303 | /* | ||
304 | * OK, we've found the last block that must survive. The rest of our | ||
305 | * branch should be detached before unlocking. However, if that rest | ||
306 | * of branch is all ours and does not grow immediately from the inode | ||
307 | * it's easier to cheat and just decrement partial->p. | ||
308 | */ | ||
309 | if (p == chain + k - 1 && p > chain) { | ||
310 | p->p--; | ||
311 | } else { | ||
312 | *top = *p->p; | ||
313 | *p->p = 0; | ||
314 | } | ||
315 | write_unlock(&pointers_lock); | ||
316 | |||
317 | while (partial > p) { | ||
318 | brelse(partial->bh); | ||
319 | partial--; | ||
320 | } | ||
321 | no_top: | ||
322 | return partial; | ||
323 | } | ||
324 | |||
325 | static inline void free_data(struct inode *inode, sysv_zone_t *p, sysv_zone_t *q) | ||
326 | { | ||
327 | for ( ; p < q ; p++) { | ||
328 | sysv_zone_t nr = *p; | ||
329 | if (nr) { | ||
330 | *p = 0; | ||
331 | sysv_free_block(inode->i_sb, nr); | ||
332 | mark_inode_dirty(inode); | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | |||
337 | static void free_branches(struct inode *inode, sysv_zone_t *p, sysv_zone_t *q, int depth) | ||
338 | { | ||
339 | struct buffer_head * bh; | ||
340 | struct super_block *sb = inode->i_sb; | ||
341 | |||
342 | if (depth--) { | ||
343 | for ( ; p < q ; p++) { | ||
344 | int block; | ||
345 | sysv_zone_t nr = *p; | ||
346 | if (!nr) | ||
347 | continue; | ||
348 | *p = 0; | ||
349 | block = block_to_cpu(SYSV_SB(sb), nr); | ||
350 | bh = sb_bread(sb, block); | ||
351 | if (!bh) | ||
352 | continue; | ||
353 | free_branches(inode, (sysv_zone_t*)bh->b_data, | ||
354 | block_end(bh), depth); | ||
355 | bforget(bh); | ||
356 | sysv_free_block(sb, nr); | ||
357 | mark_inode_dirty(inode); | ||
358 | } | ||
359 | } else | ||
360 | free_data(inode, p, q); | ||
361 | } | ||
362 | |||
363 | void sysv_truncate (struct inode * inode) | ||
364 | { | ||
365 | sysv_zone_t *i_data = SYSV_I(inode)->i_data; | ||
366 | int offsets[DEPTH]; | ||
367 | Indirect chain[DEPTH]; | ||
368 | Indirect *partial; | ||
369 | sysv_zone_t nr = 0; | ||
370 | int n; | ||
371 | long iblock; | ||
372 | unsigned blocksize; | ||
373 | |||
374 | if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || | ||
375 | S_ISLNK(inode->i_mode))) | ||
376 | return; | ||
377 | |||
378 | blocksize = inode->i_sb->s_blocksize; | ||
379 | iblock = (inode->i_size + blocksize-1) | ||
380 | >> inode->i_sb->s_blocksize_bits; | ||
381 | |||
382 | block_truncate_page(inode->i_mapping, inode->i_size, get_block); | ||
383 | |||
384 | n = block_to_path(inode, iblock, offsets); | ||
385 | if (n == 0) | ||
386 | return; | ||
387 | |||
388 | if (n == 1) { | ||
389 | free_data(inode, i_data+offsets[0], i_data + DIRECT); | ||
390 | goto do_indirects; | ||
391 | } | ||
392 | |||
393 | partial = find_shared(inode, n, offsets, chain, &nr); | ||
394 | /* Kill the top of shared branch (already detached) */ | ||
395 | if (nr) { | ||
396 | if (partial == chain) | ||
397 | mark_inode_dirty(inode); | ||
398 | else | ||
399 | dirty_indirect(partial->bh, inode); | ||
400 | free_branches(inode, &nr, &nr+1, (chain+n-1) - partial); | ||
401 | } | ||
402 | /* Clear the ends of indirect blocks on the shared branch */ | ||
403 | while (partial > chain) { | ||
404 | free_branches(inode, partial->p + 1, block_end(partial->bh), | ||
405 | (chain+n-1) - partial); | ||
406 | dirty_indirect(partial->bh, inode); | ||
407 | brelse (partial->bh); | ||
408 | partial--; | ||
409 | } | ||
410 | do_indirects: | ||
411 | /* Kill the remaining (whole) subtrees (== subtrees deeper than...) */ | ||
412 | while (n < DEPTH) { | ||
413 | nr = i_data[DIRECT + n - 1]; | ||
414 | if (nr) { | ||
415 | i_data[DIRECT + n - 1] = 0; | ||
416 | mark_inode_dirty(inode); | ||
417 | free_branches(inode, &nr, &nr+1, n); | ||
418 | } | ||
419 | n++; | ||
420 | } | ||
421 | inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; | ||
422 | if (IS_SYNC(inode)) | ||
423 | sysv_sync_inode (inode); | ||
424 | else | ||
425 | mark_inode_dirty(inode); | ||
426 | } | ||
427 | |||
428 | static unsigned sysv_nblocks(struct super_block *s, loff_t size) | ||
429 | { | ||
430 | struct sysv_sb_info *sbi = SYSV_SB(s); | ||
431 | int ptrs_bits = sbi->s_ind_per_block_bits; | ||
432 | unsigned blocks, res, direct = DIRECT, i = DEPTH; | ||
433 | blocks = (size + s->s_blocksize - 1) >> s->s_blocksize_bits; | ||
434 | res = blocks; | ||
435 | while (--i && blocks > direct) { | ||
436 | blocks = ((blocks - direct - 1) >> ptrs_bits) + 1; | ||
437 | res += blocks; | ||
438 | direct = 1; | ||
439 | } | ||
440 | return blocks; | ||
441 | } | ||
442 | |||
443 | int sysv_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) | ||
444 | { | ||
445 | struct super_block *s = mnt->mnt_sb; | ||
446 | generic_fillattr(dentry->d_inode, stat); | ||
447 | stat->blocks = (s->s_blocksize / 512) * sysv_nblocks(s, stat->size); | ||
448 | stat->blksize = s->s_blocksize; | ||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | static int sysv_writepage(struct page *page, struct writeback_control *wbc) | ||
453 | { | ||
454 | return block_write_full_page(page,get_block,wbc); | ||
455 | } | ||
456 | static int sysv_readpage(struct file *file, struct page *page) | ||
457 | { | ||
458 | return block_read_full_page(page,get_block); | ||
459 | } | ||
460 | static int sysv_prepare_write(struct file *file, struct page *page, unsigned from, unsigned to) | ||
461 | { | ||
462 | return block_prepare_write(page,from,to,get_block); | ||
463 | } | ||
464 | static sector_t sysv_bmap(struct address_space *mapping, sector_t block) | ||
465 | { | ||
466 | return generic_block_bmap(mapping,block,get_block); | ||
467 | } | ||
468 | struct address_space_operations sysv_aops = { | ||
469 | .readpage = sysv_readpage, | ||
470 | .writepage = sysv_writepage, | ||
471 | .sync_page = block_sync_page, | ||
472 | .prepare_write = sysv_prepare_write, | ||
473 | .commit_write = generic_commit_write, | ||
474 | .bmap = sysv_bmap | ||
475 | }; | ||