diff options
Diffstat (limited to 'e2fsprogs/e2fsck/rehash.c')
-rw-r--r-- | e2fsprogs/e2fsck/rehash.c | 840 |
1 files changed, 0 insertions, 840 deletions
diff --git a/e2fsprogs/e2fsck/rehash.c b/e2fsprogs/e2fsck/rehash.c deleted file mode 100644 index 727e08c2b..000000000 --- a/e2fsprogs/e2fsck/rehash.c +++ /dev/null | |||
@@ -1,840 +0,0 @@ | |||
1 | /* | ||
2 | * rehash.c --- rebuild hash tree directories | ||
3 | * | ||
4 | * Copyright (C) 2002 Theodore Ts'o | ||
5 | * | ||
6 | * %Begin-Header% | ||
7 | * This file may be redistributed under the terms of the GNU Public | ||
8 | * License. | ||
9 | * %End-Header% | ||
10 | * | ||
11 | * This algorithm is designed for simplicity of implementation and to | ||
12 | * pack the directory as much as possible. It however requires twice | ||
13 | * as much memory as the size of the directory. The maximum size | ||
14 | * directory supported using a 4k blocksize is roughly a gigabyte, and | ||
15 | * so there may very well be problems with machines that don't have | ||
16 | * virtual memory, and obscenely large directories. | ||
17 | * | ||
18 | * An alternate algorithm which is much more disk intensive could be | ||
19 | * written, and probably will need to be written in the future. The | ||
20 | * design goals of such an algorithm are: (a) use (roughly) constant | ||
21 | * amounts of memory, no matter how large the directory, (b) the | ||
22 | * directory must be safe at all times, even if e2fsck is interrupted | ||
23 | * in the middle, (c) we must use minimal amounts of extra disk | ||
24 | * blocks. This pretty much requires an incremental approach, where | ||
25 | * we are reading from one part of the directory, and inserting into | ||
26 | * the front half. So the algorithm will have to keep track of a | ||
27 | * moving block boundary between the new tree and the old tree, and | ||
28 | * files will need to be moved from the old directory and inserted | ||
29 | * into the new tree. If the new directory requires space which isn't | ||
30 | * yet available, blocks from the beginning part of the old directory | ||
31 | * may need to be moved to the end of the directory to make room for | ||
32 | * the new tree: | ||
33 | * | ||
34 | * -------------------------------------------------------- | ||
35 | * | new tree | | old tree | | ||
36 | * -------------------------------------------------------- | ||
37 | * ^ ptr ^ptr | ||
38 | * tail new head old | ||
39 | * | ||
40 | * This is going to be a pain in the tuckus to implement, and will | ||
41 | * require a lot more disk accesses. So I'm going to skip it for now; | ||
42 | * it's only really going to be an issue for really, really big | ||
43 | * filesystems (when we reach the level of tens of millions of files | ||
44 | * in a single directory). It will probably be easier to simply | ||
45 | * require that e2fsck use VM first. | ||
46 | */ | ||
47 | |||
48 | #include <string.h> | ||
49 | #include <ctype.h> | ||
50 | #include <errno.h> | ||
51 | #include "e2fsck.h" | ||
52 | #include "problem.h" | ||
53 | |||
54 | struct fill_dir_struct { | ||
55 | char *buf; | ||
56 | struct ext2_inode *inode; | ||
57 | int err; | ||
58 | e2fsck_t ctx; | ||
59 | struct hash_entry *harray; | ||
60 | int max_array, num_array; | ||
61 | int dir_size; | ||
62 | int compress; | ||
63 | ino_t parent; | ||
64 | }; | ||
65 | |||
66 | struct hash_entry { | ||
67 | ext2_dirhash_t hash; | ||
68 | ext2_dirhash_t minor_hash; | ||
69 | struct ext2_dir_entry *dir; | ||
70 | }; | ||
71 | |||
72 | struct out_dir { | ||
73 | int num; | ||
74 | int max; | ||
75 | char *buf; | ||
76 | ext2_dirhash_t *hashes; | ||
77 | }; | ||
78 | |||
79 | static int fill_dir_block(ext2_filsys fs, | ||
80 | blk_t *block_nr, | ||
81 | e2_blkcnt_t blockcnt, | ||
82 | blk_t ref_block EXT2FS_ATTR((unused)), | ||
83 | int ref_offset EXT2FS_ATTR((unused)), | ||
84 | void *priv_data) | ||
85 | { | ||
86 | struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data; | ||
87 | struct hash_entry *new_array, *ent; | ||
88 | struct ext2_dir_entry *dirent; | ||
89 | char *dir; | ||
90 | unsigned int offset, dir_offset; | ||
91 | |||
92 | if (blockcnt < 0) | ||
93 | return 0; | ||
94 | |||
95 | offset = blockcnt * fs->blocksize; | ||
96 | if (offset + fs->blocksize > fd->inode->i_size) { | ||
97 | fd->err = EXT2_ET_DIR_CORRUPTED; | ||
98 | return BLOCK_ABORT; | ||
99 | } | ||
100 | dir = (fd->buf+offset); | ||
101 | if (HOLE_BLKADDR(*block_nr)) { | ||
102 | memset(dir, 0, fs->blocksize); | ||
103 | dirent = (struct ext2_dir_entry *) dir; | ||
104 | dirent->rec_len = fs->blocksize; | ||
105 | } else { | ||
106 | fd->err = ext2fs_read_dir_block(fs, *block_nr, dir); | ||
107 | if (fd->err) | ||
108 | return BLOCK_ABORT; | ||
109 | } | ||
110 | /* While the directory block is "hot", index it. */ | ||
111 | dir_offset = 0; | ||
112 | while (dir_offset < fs->blocksize) { | ||
113 | dirent = (struct ext2_dir_entry *) (dir + dir_offset); | ||
114 | if (((dir_offset + dirent->rec_len) > fs->blocksize) || | ||
115 | (dirent->rec_len < 8) || | ||
116 | ((dirent->rec_len % 4) != 0) || | ||
117 | (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { | ||
118 | fd->err = EXT2_ET_DIR_CORRUPTED; | ||
119 | return BLOCK_ABORT; | ||
120 | } | ||
121 | dir_offset += dirent->rec_len; | ||
122 | if (dirent->inode == 0) | ||
123 | continue; | ||
124 | if (!fd->compress && ((dirent->name_len&0xFF) == 1) && | ||
125 | (dirent->name[0] == '.')) | ||
126 | continue; | ||
127 | if (!fd->compress && ((dirent->name_len&0xFF) == 2) && | ||
128 | (dirent->name[0] == '.') && (dirent->name[1] == '.')) { | ||
129 | fd->parent = dirent->inode; | ||
130 | continue; | ||
131 | } | ||
132 | if (fd->num_array >= fd->max_array) { | ||
133 | new_array = realloc(fd->harray, | ||
134 | sizeof(struct hash_entry) * (fd->max_array+500)); | ||
135 | if (!new_array) { | ||
136 | fd->err = ENOMEM; | ||
137 | return BLOCK_ABORT; | ||
138 | } | ||
139 | fd->harray = new_array; | ||
140 | fd->max_array += 500; | ||
141 | } | ||
142 | ent = fd->harray + fd->num_array++; | ||
143 | ent->dir = dirent; | ||
144 | fd->dir_size += EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); | ||
145 | if (fd->compress) | ||
146 | ent->hash = ent->minor_hash = 0; | ||
147 | else { | ||
148 | fd->err = ext2fs_dirhash(fs->super->s_def_hash_version, | ||
149 | dirent->name, | ||
150 | dirent->name_len & 0xFF, | ||
151 | fs->super->s_hash_seed, | ||
152 | &ent->hash, &ent->minor_hash); | ||
153 | if (fd->err) | ||
154 | return BLOCK_ABORT; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | /* Used for sorting the hash entry */ | ||
162 | static EXT2_QSORT_TYPE name_cmp(const void *a, const void *b) | ||
163 | { | ||
164 | const struct hash_entry *he_a = (const struct hash_entry *) a; | ||
165 | const struct hash_entry *he_b = (const struct hash_entry *) b; | ||
166 | int ret; | ||
167 | int min_len; | ||
168 | |||
169 | min_len = he_a->dir->name_len; | ||
170 | if (min_len > he_b->dir->name_len) | ||
171 | min_len = he_b->dir->name_len; | ||
172 | |||
173 | ret = strncmp(he_a->dir->name, he_b->dir->name, min_len); | ||
174 | if (ret == 0) { | ||
175 | if (he_a->dir->name_len > he_b->dir->name_len) | ||
176 | ret = 1; | ||
177 | else if (he_a->dir->name_len < he_b->dir->name_len) | ||
178 | ret = -1; | ||
179 | else | ||
180 | ret = he_b->dir->inode - he_a->dir->inode; | ||
181 | } | ||
182 | return ret; | ||
183 | } | ||
184 | |||
185 | /* Used for sorting the hash entry */ | ||
186 | static EXT2_QSORT_TYPE hash_cmp(const void *a, const void *b) | ||
187 | { | ||
188 | const struct hash_entry *he_a = (const struct hash_entry *) a; | ||
189 | const struct hash_entry *he_b = (const struct hash_entry *) b; | ||
190 | int ret; | ||
191 | |||
192 | if (he_a->hash > he_b->hash) | ||
193 | ret = 1; | ||
194 | else if (he_a->hash < he_b->hash) | ||
195 | ret = -1; | ||
196 | else { | ||
197 | if (he_a->minor_hash > he_b->minor_hash) | ||
198 | ret = 1; | ||
199 | else if (he_a->minor_hash < he_b->minor_hash) | ||
200 | ret = -1; | ||
201 | else | ||
202 | ret = name_cmp(a, b); | ||
203 | } | ||
204 | return ret; | ||
205 | } | ||
206 | |||
207 | static errcode_t alloc_size_dir(ext2_filsys fs, struct out_dir *outdir, | ||
208 | int blocks) | ||
209 | { | ||
210 | void *new_mem; | ||
211 | |||
212 | if (outdir->max) { | ||
213 | new_mem = realloc(outdir->buf, blocks * fs->blocksize); | ||
214 | if (!new_mem) | ||
215 | return ENOMEM; | ||
216 | outdir->buf = new_mem; | ||
217 | new_mem = realloc(outdir->hashes, | ||
218 | blocks * sizeof(ext2_dirhash_t)); | ||
219 | if (!new_mem) | ||
220 | return ENOMEM; | ||
221 | outdir->hashes = new_mem; | ||
222 | } else { | ||
223 | outdir->buf = malloc(blocks * fs->blocksize); | ||
224 | outdir->hashes = malloc(blocks * sizeof(ext2_dirhash_t)); | ||
225 | outdir->num = 0; | ||
226 | } | ||
227 | outdir->max = blocks; | ||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | static void free_out_dir(struct out_dir *outdir) | ||
232 | { | ||
233 | if (outdir->buf) | ||
234 | free(outdir->buf); | ||
235 | if (outdir->hashes) | ||
236 | free(outdir->hashes); | ||
237 | outdir->max = 0; | ||
238 | outdir->num =0; | ||
239 | } | ||
240 | |||
241 | static errcode_t get_next_block(ext2_filsys fs, struct out_dir *outdir, | ||
242 | char ** ret) | ||
243 | { | ||
244 | errcode_t retval; | ||
245 | |||
246 | if (outdir->num >= outdir->max) { | ||
247 | retval = alloc_size_dir(fs, outdir, outdir->max + 50); | ||
248 | if (retval) | ||
249 | return retval; | ||
250 | } | ||
251 | *ret = outdir->buf + (outdir->num++ * fs->blocksize); | ||
252 | memset(*ret, 0, fs->blocksize); | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | * This function is used to make a unique filename. We do this by | ||
258 | * appending ~0, and then incrementing the number. However, we cannot | ||
259 | * expand the length of the filename beyond the padding available in | ||
260 | * the directory entry. | ||
261 | */ | ||
262 | static void mutate_name(char *str, __u16 *len) | ||
263 | { | ||
264 | int i; | ||
265 | __u16 l = *len & 0xFF, h = *len & 0xff00; | ||
266 | |||
267 | /* | ||
268 | * First check to see if it looks the name has been mutated | ||
269 | * already | ||
270 | */ | ||
271 | for (i = l-1; i > 0; i--) { | ||
272 | if (!isdigit(str[i])) | ||
273 | break; | ||
274 | } | ||
275 | if ((i == l-1) || (str[i] != '~')) { | ||
276 | if (((l-1) & 3) < 2) | ||
277 | l += 2; | ||
278 | else | ||
279 | l = (l+3) & ~3; | ||
280 | str[l-2] = '~'; | ||
281 | str[l-1] = '0'; | ||
282 | *len = l | h; | ||
283 | return; | ||
284 | } | ||
285 | for (i = l-1; i >= 0; i--) { | ||
286 | if (isdigit(str[i])) { | ||
287 | if (str[i] == '9') | ||
288 | str[i] = '0'; | ||
289 | else { | ||
290 | str[i]++; | ||
291 | return; | ||
292 | } | ||
293 | continue; | ||
294 | } | ||
295 | if (i == 1) { | ||
296 | if (str[0] == 'z') | ||
297 | str[0] = 'A'; | ||
298 | else if (str[0] == 'Z') { | ||
299 | str[0] = '~'; | ||
300 | str[1] = '0'; | ||
301 | } else | ||
302 | str[0]++; | ||
303 | } else if (i > 0) { | ||
304 | str[i] = '1'; | ||
305 | str[i-1] = '~'; | ||
306 | } else { | ||
307 | if (str[0] == '~') | ||
308 | str[0] = 'a'; | ||
309 | else | ||
310 | str[0]++; | ||
311 | } | ||
312 | break; | ||
313 | } | ||
314 | } | ||
315 | |||
316 | static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, | ||
317 | ext2_ino_t ino, | ||
318 | struct fill_dir_struct *fd) | ||
319 | { | ||
320 | struct problem_context pctx; | ||
321 | struct hash_entry *ent, *prev; | ||
322 | int i, j; | ||
323 | int fixed = 0; | ||
324 | char new_name[256]; | ||
325 | __u16 new_len; | ||
326 | |||
327 | clear_problem_context(&pctx); | ||
328 | pctx.ino = ino; | ||
329 | |||
330 | for (i=1; i < fd->num_array; i++) { | ||
331 | ent = fd->harray + i; | ||
332 | prev = ent - 1; | ||
333 | if (!ent->dir->inode || | ||
334 | ((ent->dir->name_len & 0xFF) != | ||
335 | (prev->dir->name_len & 0xFF)) || | ||
336 | (strncmp(ent->dir->name, prev->dir->name, | ||
337 | ent->dir->name_len & 0xFF))) | ||
338 | continue; | ||
339 | pctx.dirent = ent->dir; | ||
340 | if ((ent->dir->inode == prev->dir->inode) && | ||
341 | fix_problem(ctx, PR_2_DUPLICATE_DIRENT, &pctx)) { | ||
342 | e2fsck_adjust_inode_count(ctx, ent->dir->inode, -1); | ||
343 | ent->dir->inode = 0; | ||
344 | fixed++; | ||
345 | continue; | ||
346 | } | ||
347 | memcpy(new_name, ent->dir->name, ent->dir->name_len & 0xFF); | ||
348 | new_len = ent->dir->name_len; | ||
349 | mutate_name(new_name, &new_len); | ||
350 | for (j=0; j < fd->num_array; j++) { | ||
351 | if ((i==j) || | ||
352 | ((ent->dir->name_len & 0xFF) != | ||
353 | (fd->harray[j].dir->name_len & 0xFF)) || | ||
354 | (strncmp(new_name, fd->harray[j].dir->name, | ||
355 | new_len & 0xFF))) | ||
356 | continue; | ||
357 | mutate_name(new_name, &new_len); | ||
358 | |||
359 | j = -1; | ||
360 | } | ||
361 | new_name[new_len & 0xFF] = 0; | ||
362 | pctx.str = new_name; | ||
363 | if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE, &pctx)) { | ||
364 | memcpy(ent->dir->name, new_name, new_len & 0xFF); | ||
365 | ent->dir->name_len = new_len; | ||
366 | ext2fs_dirhash(fs->super->s_def_hash_version, | ||
367 | ent->dir->name, | ||
368 | ent->dir->name_len & 0xFF, | ||
369 | fs->super->s_hash_seed, | ||
370 | &ent->hash, &ent->minor_hash); | ||
371 | fixed++; | ||
372 | } | ||
373 | } | ||
374 | return fixed; | ||
375 | } | ||
376 | |||
377 | |||
378 | static errcode_t copy_dir_entries(ext2_filsys fs, | ||
379 | struct fill_dir_struct *fd, | ||
380 | struct out_dir *outdir) | ||
381 | { | ||
382 | errcode_t retval; | ||
383 | char *block_start; | ||
384 | struct hash_entry *ent; | ||
385 | struct ext2_dir_entry *dirent; | ||
386 | int i, rec_len, left; | ||
387 | ext2_dirhash_t prev_hash; | ||
388 | int offset; | ||
389 | |||
390 | outdir->max = 0; | ||
391 | retval = alloc_size_dir(fs, outdir, | ||
392 | (fd->dir_size / fs->blocksize) + 2); | ||
393 | if (retval) | ||
394 | return retval; | ||
395 | outdir->num = fd->compress ? 0 : 1; | ||
396 | offset = 0; | ||
397 | outdir->hashes[0] = 0; | ||
398 | prev_hash = 1; | ||
399 | if ((retval = get_next_block(fs, outdir, &block_start))) | ||
400 | return retval; | ||
401 | dirent = (struct ext2_dir_entry *) block_start; | ||
402 | left = fs->blocksize; | ||
403 | for (i=0; i < fd->num_array; i++) { | ||
404 | ent = fd->harray + i; | ||
405 | if (ent->dir->inode == 0) | ||
406 | continue; | ||
407 | rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF); | ||
408 | if (rec_len > left) { | ||
409 | if (left) | ||
410 | dirent->rec_len += left; | ||
411 | if ((retval = get_next_block(fs, outdir, | ||
412 | &block_start))) | ||
413 | return retval; | ||
414 | offset = 0; | ||
415 | } | ||
416 | left = fs->blocksize - offset; | ||
417 | dirent = (struct ext2_dir_entry *) (block_start + offset); | ||
418 | if (offset == 0) { | ||
419 | if (ent->hash == prev_hash) | ||
420 | outdir->hashes[outdir->num-1] = ent->hash | 1; | ||
421 | else | ||
422 | outdir->hashes[outdir->num-1] = ent->hash; | ||
423 | } | ||
424 | dirent->inode = ent->dir->inode; | ||
425 | dirent->name_len = ent->dir->name_len; | ||
426 | dirent->rec_len = rec_len; | ||
427 | memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF); | ||
428 | offset += rec_len; | ||
429 | left -= rec_len; | ||
430 | if (left < 12) { | ||
431 | dirent->rec_len += left; | ||
432 | offset += left; | ||
433 | left = 0; | ||
434 | } | ||
435 | prev_hash = ent->hash; | ||
436 | } | ||
437 | if (left) | ||
438 | dirent->rec_len += left; | ||
439 | |||
440 | return 0; | ||
441 | } | ||
442 | |||
443 | |||
444 | static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, | ||
445 | ext2_ino_t ino, ext2_ino_t parent) | ||
446 | { | ||
447 | struct ext2_dir_entry *dir; | ||
448 | struct ext2_dx_root_info *root; | ||
449 | struct ext2_dx_countlimit *limits; | ||
450 | int filetype = 0; | ||
451 | |||
452 | if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) | ||
453 | filetype = EXT2_FT_DIR << 8; | ||
454 | |||
455 | memset(buf, 0, fs->blocksize); | ||
456 | dir = (struct ext2_dir_entry *) buf; | ||
457 | dir->inode = ino; | ||
458 | dir->name[0] = '.'; | ||
459 | dir->name_len = 1 | filetype; | ||
460 | dir->rec_len = 12; | ||
461 | dir = (struct ext2_dir_entry *) (buf + 12); | ||
462 | dir->inode = parent; | ||
463 | dir->name[0] = '.'; | ||
464 | dir->name[1] = '.'; | ||
465 | dir->name_len = 2 | filetype; | ||
466 | dir->rec_len = fs->blocksize - 12; | ||
467 | |||
468 | root = (struct ext2_dx_root_info *) (buf+24); | ||
469 | root->reserved_zero = 0; | ||
470 | root->hash_version = fs->super->s_def_hash_version; | ||
471 | root->info_length = 8; | ||
472 | root->indirect_levels = 0; | ||
473 | root->unused_flags = 0; | ||
474 | |||
475 | limits = (struct ext2_dx_countlimit *) (buf+32); | ||
476 | limits->limit = (fs->blocksize - 32) / sizeof(struct ext2_dx_entry); | ||
477 | limits->count = 0; | ||
478 | |||
479 | return root; | ||
480 | } | ||
481 | |||
482 | |||
483 | static struct ext2_dx_entry *set_int_node(ext2_filsys fs, char *buf) | ||
484 | { | ||
485 | struct ext2_dir_entry *dir; | ||
486 | struct ext2_dx_countlimit *limits; | ||
487 | |||
488 | memset(buf, 0, fs->blocksize); | ||
489 | dir = (struct ext2_dir_entry *) buf; | ||
490 | dir->inode = 0; | ||
491 | dir->rec_len = fs->blocksize; | ||
492 | |||
493 | limits = (struct ext2_dx_countlimit *) (buf+8); | ||
494 | limits->limit = (fs->blocksize - 8) / sizeof(struct ext2_dx_entry); | ||
495 | limits->count = 0; | ||
496 | |||
497 | return (struct ext2_dx_entry *) limits; | ||
498 | } | ||
499 | |||
500 | /* | ||
501 | * This function takes the leaf nodes which have been written in | ||
502 | * outdir, and populates the root node and any necessary interior nodes. | ||
503 | */ | ||
504 | static errcode_t calculate_tree(ext2_filsys fs, | ||
505 | struct out_dir *outdir, | ||
506 | ext2_ino_t ino, | ||
507 | ext2_ino_t parent) | ||
508 | { | ||
509 | struct ext2_dx_root_info *root_info; | ||
510 | struct ext2_dx_entry *root, *dx_ent = 0; | ||
511 | struct ext2_dx_countlimit *root_limit, *limit; | ||
512 | errcode_t retval; | ||
513 | char * block_start; | ||
514 | int i, c1, c2, nblks; | ||
515 | int limit_offset, root_offset; | ||
516 | |||
517 | root_info = set_root_node(fs, outdir->buf, ino, parent); | ||
518 | root_offset = limit_offset = ((char *) root_info - outdir->buf) + | ||
519 | root_info->info_length; | ||
520 | root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); | ||
521 | c1 = root_limit->limit; | ||
522 | nblks = outdir->num; | ||
523 | |||
524 | /* Write out the pointer blocks */ | ||
525 | if (nblks-1 <= c1) { | ||
526 | /* Just write out the root block, and we're done */ | ||
527 | root = (struct ext2_dx_entry *) (outdir->buf + root_offset); | ||
528 | for (i=1; i < nblks; i++) { | ||
529 | root->block = ext2fs_cpu_to_le32(i); | ||
530 | if (i != 1) | ||
531 | root->hash = | ||
532 | ext2fs_cpu_to_le32(outdir->hashes[i]); | ||
533 | root++; | ||
534 | c1--; | ||
535 | } | ||
536 | } else { | ||
537 | c2 = 0; | ||
538 | limit = 0; | ||
539 | root_info->indirect_levels = 1; | ||
540 | for (i=1; i < nblks; i++) { | ||
541 | if (c1 == 0) | ||
542 | return ENOSPC; | ||
543 | if (c2 == 0) { | ||
544 | if (limit) | ||
545 | limit->limit = limit->count = | ||
546 | ext2fs_cpu_to_le16(limit->limit); | ||
547 | root = (struct ext2_dx_entry *) | ||
548 | (outdir->buf + root_offset); | ||
549 | root->block = ext2fs_cpu_to_le32(outdir->num); | ||
550 | if (i != 1) | ||
551 | root->hash = | ||
552 | ext2fs_cpu_to_le32(outdir->hashes[i]); | ||
553 | if ((retval = get_next_block(fs, outdir, | ||
554 | &block_start))) | ||
555 | return retval; | ||
556 | dx_ent = set_int_node(fs, block_start); | ||
557 | limit = (struct ext2_dx_countlimit *) dx_ent; | ||
558 | c2 = limit->limit; | ||
559 | root_offset += sizeof(struct ext2_dx_entry); | ||
560 | c1--; | ||
561 | } | ||
562 | dx_ent->block = ext2fs_cpu_to_le32(i); | ||
563 | if (c2 != limit->limit) | ||
564 | dx_ent->hash = | ||
565 | ext2fs_cpu_to_le32(outdir->hashes[i]); | ||
566 | dx_ent++; | ||
567 | c2--; | ||
568 | } | ||
569 | limit->count = ext2fs_cpu_to_le16(limit->limit - c2); | ||
570 | limit->limit = ext2fs_cpu_to_le16(limit->limit); | ||
571 | } | ||
572 | root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); | ||
573 | root_limit->count = ext2fs_cpu_to_le16(root_limit->limit - c1); | ||
574 | root_limit->limit = ext2fs_cpu_to_le16(root_limit->limit); | ||
575 | |||
576 | return 0; | ||
577 | } | ||
578 | |||
579 | struct write_dir_struct { | ||
580 | struct out_dir *outdir; | ||
581 | errcode_t err; | ||
582 | e2fsck_t ctx; | ||
583 | int cleared; | ||
584 | }; | ||
585 | |||
586 | /* | ||
587 | * Helper function which writes out a directory block. | ||
588 | */ | ||
589 | static int write_dir_block(ext2_filsys fs, | ||
590 | blk_t *block_nr, | ||
591 | e2_blkcnt_t blockcnt, | ||
592 | blk_t ref_block EXT2FS_ATTR((unused)), | ||
593 | int ref_offset EXT2FS_ATTR((unused)), | ||
594 | void *priv_data) | ||
595 | { | ||
596 | struct write_dir_struct *wd = (struct write_dir_struct *) priv_data; | ||
597 | blk_t blk; | ||
598 | char *dir; | ||
599 | |||
600 | if (*block_nr == 0) | ||
601 | return 0; | ||
602 | if (blockcnt >= wd->outdir->num) { | ||
603 | e2fsck_read_bitmaps(wd->ctx); | ||
604 | blk = *block_nr; | ||
605 | ext2fs_unmark_block_bitmap(wd->ctx->block_found_map, blk); | ||
606 | ext2fs_block_alloc_stats(fs, blk, -1); | ||
607 | *block_nr = 0; | ||
608 | wd->cleared++; | ||
609 | return BLOCK_CHANGED; | ||
610 | } | ||
611 | if (blockcnt < 0) | ||
612 | return 0; | ||
613 | |||
614 | dir = wd->outdir->buf + (blockcnt * fs->blocksize); | ||
615 | wd->err = ext2fs_write_dir_block(fs, *block_nr, dir); | ||
616 | if (wd->err) | ||
617 | return BLOCK_ABORT; | ||
618 | return 0; | ||
619 | } | ||
620 | |||
621 | static errcode_t write_directory(e2fsck_t ctx, ext2_filsys fs, | ||
622 | struct out_dir *outdir, | ||
623 | ext2_ino_t ino, int compress) | ||
624 | { | ||
625 | struct write_dir_struct wd; | ||
626 | errcode_t retval; | ||
627 | struct ext2_inode inode; | ||
628 | |||
629 | retval = e2fsck_expand_directory(ctx, ino, -1, outdir->num); | ||
630 | if (retval) | ||
631 | return retval; | ||
632 | |||
633 | wd.outdir = outdir; | ||
634 | wd.err = 0; | ||
635 | wd.ctx = ctx; | ||
636 | wd.cleared = 0; | ||
637 | |||
638 | retval = ext2fs_block_iterate2(fs, ino, 0, 0, | ||
639 | write_dir_block, &wd); | ||
640 | if (retval) | ||
641 | return retval; | ||
642 | if (wd.err) | ||
643 | return wd.err; | ||
644 | |||
645 | e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); | ||
646 | if (compress) | ||
647 | inode.i_flags &= ~EXT2_INDEX_FL; | ||
648 | else | ||
649 | inode.i_flags |= EXT2_INDEX_FL; | ||
650 | inode.i_size = outdir->num * fs->blocksize; | ||
651 | inode.i_blocks -= (fs->blocksize / 512) * wd.cleared; | ||
652 | e2fsck_write_inode(ctx, ino, &inode, "rehash_dir"); | ||
653 | |||
654 | return 0; | ||
655 | } | ||
656 | |||
657 | errcode_t e2fsck_rehash_dir(e2fsck_t ctx, ext2_ino_t ino) | ||
658 | { | ||
659 | ext2_filsys fs = ctx->fs; | ||
660 | errcode_t retval; | ||
661 | struct ext2_inode inode; | ||
662 | char *dir_buf = 0; | ||
663 | struct fill_dir_struct fd; | ||
664 | struct out_dir outdir; | ||
665 | |||
666 | outdir.max = outdir.num = 0; | ||
667 | outdir.buf = 0; | ||
668 | outdir.hashes = 0; | ||
669 | e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); | ||
670 | |||
671 | retval = ENOMEM; | ||
672 | fd.harray = 0; | ||
673 | dir_buf = malloc(inode.i_size); | ||
674 | if (!dir_buf) | ||
675 | goto errout; | ||
676 | |||
677 | fd.max_array = inode.i_size / 32; | ||
678 | fd.num_array = 0; | ||
679 | fd.harray = malloc(fd.max_array * sizeof(struct hash_entry)); | ||
680 | if (!fd.harray) | ||
681 | goto errout; | ||
682 | |||
683 | fd.ctx = ctx; | ||
684 | fd.buf = dir_buf; | ||
685 | fd.inode = &inode; | ||
686 | fd.err = 0; | ||
687 | fd.dir_size = 0; | ||
688 | fd.compress = 0; | ||
689 | if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || | ||
690 | (inode.i_size / fs->blocksize) < 2) | ||
691 | fd.compress = 1; | ||
692 | fd.parent = 0; | ||
693 | |||
694 | /* Read in the entire directory into memory */ | ||
695 | retval = ext2fs_block_iterate2(fs, ino, 0, 0, | ||
696 | fill_dir_block, &fd); | ||
697 | if (fd.err) { | ||
698 | retval = fd.err; | ||
699 | goto errout; | ||
700 | } | ||
701 | |||
702 | #if 0 | ||
703 | printf("%d entries (%d bytes) found in inode %d\n", | ||
704 | fd.num_array, fd.dir_size, ino); | ||
705 | #endif | ||
706 | |||
707 | /* Sort the list */ | ||
708 | resort: | ||
709 | if (fd.compress) | ||
710 | qsort(fd.harray+2, fd.num_array-2, | ||
711 | sizeof(struct hash_entry), name_cmp); | ||
712 | else | ||
713 | qsort(fd.harray, fd.num_array, | ||
714 | sizeof(struct hash_entry), hash_cmp); | ||
715 | |||
716 | /* | ||
717 | * Look for duplicates | ||
718 | */ | ||
719 | if (duplicate_search_and_fix(ctx, fs, ino, &fd)) | ||
720 | goto resort; | ||
721 | |||
722 | if (ctx->options & E2F_OPT_NO) { | ||
723 | retval = 0; | ||
724 | goto errout; | ||
725 | } | ||
726 | |||
727 | /* | ||
728 | * Copy the directory entries. In a htree directory these | ||
729 | * will become the leaf nodes. | ||
730 | */ | ||
731 | retval = copy_dir_entries(fs, &fd, &outdir); | ||
732 | if (retval) | ||
733 | goto errout; | ||
734 | |||
735 | free(dir_buf); dir_buf = 0; | ||
736 | |||
737 | if (!fd.compress) { | ||
738 | /* Calculate the interior nodes */ | ||
739 | retval = calculate_tree(fs, &outdir, ino, fd.parent); | ||
740 | if (retval) | ||
741 | goto errout; | ||
742 | } | ||
743 | |||
744 | retval = write_directory(ctx, fs, &outdir, ino, fd.compress); | ||
745 | if (retval) | ||
746 | goto errout; | ||
747 | |||
748 | errout: | ||
749 | if (dir_buf) | ||
750 | free(dir_buf); | ||
751 | if (fd.harray) | ||
752 | free(fd.harray); | ||
753 | |||
754 | free_out_dir(&outdir); | ||
755 | return retval; | ||
756 | } | ||
757 | |||
758 | void e2fsck_rehash_directories(e2fsck_t ctx) | ||
759 | { | ||
760 | struct problem_context pctx; | ||
761 | #ifdef RESOURCE_TRACK | ||
762 | struct resource_track rtrack; | ||
763 | #endif | ||
764 | struct dir_info *dir; | ||
765 | ext2_u32_iterate iter; | ||
766 | ext2_ino_t ino; | ||
767 | errcode_t retval; | ||
768 | int i, cur, max, all_dirs, dir_index, first = 1; | ||
769 | |||
770 | #ifdef RESOURCE_TRACK | ||
771 | init_resource_track(&rtrack); | ||
772 | #endif | ||
773 | |||
774 | all_dirs = ctx->options & E2F_OPT_COMPRESS_DIRS; | ||
775 | |||
776 | if (!ctx->dirs_to_hash && !all_dirs) | ||
777 | return; | ||
778 | |||
779 | e2fsck_get_lost_and_found(ctx, 0); | ||
780 | |||
781 | clear_problem_context(&pctx); | ||
782 | |||
783 | dir_index = ctx->fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX; | ||
784 | cur = 0; | ||
785 | if (all_dirs) { | ||
786 | i = 0; | ||
787 | max = e2fsck_get_num_dirinfo(ctx); | ||
788 | } else { | ||
789 | retval = ext2fs_u32_list_iterate_begin(ctx->dirs_to_hash, | ||
790 | &iter); | ||
791 | if (retval) { | ||
792 | pctx.errcode = retval; | ||
793 | fix_problem(ctx, PR_3A_OPTIMIZE_ITER, &pctx); | ||
794 | return; | ||
795 | } | ||
796 | max = ext2fs_u32_list_count(ctx->dirs_to_hash); | ||
797 | } | ||
798 | while (1) { | ||
799 | if (all_dirs) { | ||
800 | if ((dir = e2fsck_dir_info_iter(ctx, &i)) == 0) | ||
801 | break; | ||
802 | ino = dir->ino; | ||
803 | } else { | ||
804 | if (!ext2fs_u32_list_iterate(iter, &ino)) | ||
805 | break; | ||
806 | } | ||
807 | if (ino == ctx->lost_and_found) | ||
808 | continue; | ||
809 | pctx.dir = ino; | ||
810 | if (first) { | ||
811 | fix_problem(ctx, PR_3A_PASS_HEADER, &pctx); | ||
812 | first = 0; | ||
813 | } | ||
814 | #if 0 | ||
815 | fix_problem(ctx, PR_3A_OPTIMIZE_DIR, &pctx); | ||
816 | #endif | ||
817 | pctx.errcode = e2fsck_rehash_dir(ctx, ino); | ||
818 | if (pctx.errcode) { | ||
819 | end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); | ||
820 | fix_problem(ctx, PR_3A_OPTIMIZE_DIR_ERR, &pctx); | ||
821 | } | ||
822 | if (ctx->progress && !ctx->progress_fd) | ||
823 | e2fsck_simple_progress(ctx, "Rebuilding directory", | ||
824 | 100.0 * (float) (++cur) / (float) max, ino); | ||
825 | } | ||
826 | end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); | ||
827 | if (!all_dirs) | ||
828 | ext2fs_u32_list_iterate_end(iter); | ||
829 | |||
830 | if (ctx->dirs_to_hash) | ||
831 | ext2fs_u32_list_free(ctx->dirs_to_hash); | ||
832 | ctx->dirs_to_hash = 0; | ||
833 | |||
834 | #ifdef RESOURCE_TRACK | ||
835 | if (ctx->options & E2F_OPT_TIME2) { | ||
836 | e2fsck_clear_progbar(ctx); | ||
837 | print_resource_track("Pass 3A", &rtrack); | ||
838 | } | ||
839 | #endif | ||
840 | } | ||