mirror of
https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git
synced 2024-12-04 15:33:38 +08:00
82372e32de
Commit 07307114de
didn't correctly handle the lost+found directory
when it added support for metadata checksums. First of all,
e2fsck_get_lost_and_found() assumed that the inode_dir_map bitmap was
initialized, and it wasn't when it was called earlier by a change in
that commit. Secondly, it's important that lost+found dirctory is
processed in case its directory checksums are incorrect, but should
preserve any empty dirctory blocks so there space available for e2fsck
to reconnect any orphan inodes.
Fix these problems, to fix test failures: f_holedir2 and f_rehash_dir
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
964 lines
24 KiB
C
964 lines
24 KiB
C
/*
|
|
* rehash.c --- rebuild hash tree directories
|
|
*
|
|
* Copyright (C) 2002 Theodore Ts'o
|
|
*
|
|
* %Begin-Header%
|
|
* This file may be redistributed under the terms of the GNU Public
|
|
* License.
|
|
* %End-Header%
|
|
*
|
|
* This algorithm is designed for simplicity of implementation and to
|
|
* pack the directory as much as possible. It however requires twice
|
|
* as much memory as the size of the directory. The maximum size
|
|
* directory supported using a 4k blocksize is roughly a gigabyte, and
|
|
* so there may very well be problems with machines that don't have
|
|
* virtual memory, and obscenely large directories.
|
|
*
|
|
* An alternate algorithm which is much more disk intensive could be
|
|
* written, and probably will need to be written in the future. The
|
|
* design goals of such an algorithm are: (a) use (roughly) constant
|
|
* amounts of memory, no matter how large the directory, (b) the
|
|
* directory must be safe at all times, even if e2fsck is interrupted
|
|
* in the middle, (c) we must use minimal amounts of extra disk
|
|
* blocks. This pretty much requires an incremental approach, where
|
|
* we are reading from one part of the directory, and inserting into
|
|
* the front half. So the algorithm will have to keep track of a
|
|
* moving block boundary between the new tree and the old tree, and
|
|
* files will need to be moved from the old directory and inserted
|
|
* into the new tree. If the new directory requires space which isn't
|
|
* yet available, blocks from the beginning part of the old directory
|
|
* may need to be moved to the end of the directory to make room for
|
|
* the new tree:
|
|
*
|
|
* --------------------------------------------------------
|
|
* | new tree | | old tree |
|
|
* --------------------------------------------------------
|
|
* ^ ptr ^ptr
|
|
* tail new head old
|
|
*
|
|
* This is going to be a pain in the tuckus to implement, and will
|
|
* require a lot more disk accesses. So I'm going to skip it for now;
|
|
* it's only really going to be an issue for really, really big
|
|
* filesystems (when we reach the level of tens of millions of files
|
|
* in a single directory). It will probably be easier to simply
|
|
* require that e2fsck use VM first.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include "e2fsck.h"
|
|
#include "problem.h"
|
|
|
|
/* Schedule a dir to be rebuilt during pass 3A. */
|
|
void e2fsck_rehash_dir_later(e2fsck_t ctx, ext2_ino_t ino)
|
|
{
|
|
if (!ctx->dirs_to_hash)
|
|
ext2fs_u32_list_create(&ctx->dirs_to_hash, 50);
|
|
if (ctx->dirs_to_hash)
|
|
ext2fs_u32_list_add(ctx->dirs_to_hash, ino);
|
|
}
|
|
|
|
/* Ask if a dir will be rebuilt during pass 3A. */
|
|
int e2fsck_dir_will_be_rehashed(e2fsck_t ctx, ext2_ino_t ino)
|
|
{
|
|
if (ctx->options & E2F_OPT_COMPRESS_DIRS)
|
|
return 1;
|
|
if (!ctx->dirs_to_hash)
|
|
return 0;
|
|
return ext2fs_u32_list_test(ctx->dirs_to_hash, ino);
|
|
}
|
|
|
|
struct fill_dir_struct {
|
|
char *buf;
|
|
struct ext2_inode *inode;
|
|
int err;
|
|
e2fsck_t ctx;
|
|
struct hash_entry *harray;
|
|
int max_array, num_array;
|
|
int dir_size;
|
|
int compress;
|
|
ino_t parent;
|
|
ext2_ino_t dir;
|
|
};
|
|
|
|
struct hash_entry {
|
|
ext2_dirhash_t hash;
|
|
ext2_dirhash_t minor_hash;
|
|
ino_t ino;
|
|
struct ext2_dir_entry *dir;
|
|
};
|
|
|
|
struct out_dir {
|
|
int num;
|
|
int max;
|
|
char *buf;
|
|
ext2_dirhash_t *hashes;
|
|
};
|
|
|
|
static int fill_dir_block(ext2_filsys fs,
|
|
blk64_t *block_nr,
|
|
e2_blkcnt_t blockcnt,
|
|
blk64_t ref_block EXT2FS_ATTR((unused)),
|
|
int ref_offset EXT2FS_ATTR((unused)),
|
|
void *priv_data)
|
|
{
|
|
struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data;
|
|
struct hash_entry *new_array, *ent;
|
|
struct ext2_dir_entry *dirent;
|
|
char *dir;
|
|
unsigned int offset, dir_offset, rec_len;
|
|
int hash_alg;
|
|
|
|
if (blockcnt < 0)
|
|
return 0;
|
|
|
|
offset = blockcnt * fs->blocksize;
|
|
if (offset + fs->blocksize > fd->inode->i_size) {
|
|
fd->err = EXT2_ET_DIR_CORRUPTED;
|
|
return BLOCK_ABORT;
|
|
}
|
|
dir = (fd->buf+offset);
|
|
if (HOLE_BLKADDR(*block_nr)) {
|
|
memset(dir, 0, fs->blocksize);
|
|
dirent = (struct ext2_dir_entry *) dir;
|
|
(void) ext2fs_set_rec_len(fs, fs->blocksize, dirent);
|
|
} else {
|
|
fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
|
|
fd->err = ext2fs_read_dir_block4(fs, *block_nr, dir, 0,
|
|
fd->dir);
|
|
fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
|
|
if (fd->err)
|
|
return BLOCK_ABORT;
|
|
}
|
|
hash_alg = fs->super->s_def_hash_version;
|
|
if ((hash_alg <= EXT2_HASH_TEA) &&
|
|
(fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH))
|
|
hash_alg += 3;
|
|
/* While the directory block is "hot", index it. */
|
|
dir_offset = 0;
|
|
while (dir_offset < fs->blocksize) {
|
|
dirent = (struct ext2_dir_entry *) (dir + dir_offset);
|
|
(void) ext2fs_get_rec_len(fs, dirent, &rec_len);
|
|
if (((dir_offset + rec_len) > fs->blocksize) ||
|
|
(rec_len < 8) ||
|
|
((rec_len % 4) != 0) ||
|
|
(((dirent->name_len & 0xFF)+8) > rec_len)) {
|
|
fd->err = EXT2_ET_DIR_CORRUPTED;
|
|
return BLOCK_ABORT;
|
|
}
|
|
dir_offset += rec_len;
|
|
if (dirent->inode == 0)
|
|
continue;
|
|
if (!fd->compress && ((dirent->name_len&0xFF) == 1) &&
|
|
(dirent->name[0] == '.'))
|
|
continue;
|
|
if (!fd->compress && ((dirent->name_len&0xFF) == 2) &&
|
|
(dirent->name[0] == '.') && (dirent->name[1] == '.')) {
|
|
fd->parent = dirent->inode;
|
|
continue;
|
|
}
|
|
if (fd->num_array >= fd->max_array) {
|
|
new_array = realloc(fd->harray,
|
|
sizeof(struct hash_entry) * (fd->max_array+500));
|
|
if (!new_array) {
|
|
fd->err = ENOMEM;
|
|
return BLOCK_ABORT;
|
|
}
|
|
fd->harray = new_array;
|
|
fd->max_array += 500;
|
|
}
|
|
ent = fd->harray + fd->num_array++;
|
|
ent->dir = dirent;
|
|
fd->dir_size += EXT2_DIR_REC_LEN(dirent->name_len & 0xFF);
|
|
ent->ino = dirent->inode;
|
|
if (fd->compress)
|
|
ent->hash = ent->minor_hash = 0;
|
|
else {
|
|
fd->err = ext2fs_dirhash(hash_alg, dirent->name,
|
|
dirent->name_len & 0xFF,
|
|
fs->super->s_hash_seed,
|
|
&ent->hash, &ent->minor_hash);
|
|
if (fd->err)
|
|
return BLOCK_ABORT;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Used for sorting the hash entry */
|
|
static EXT2_QSORT_TYPE ino_cmp(const void *a, const void *b)
|
|
{
|
|
const struct hash_entry *he_a = (const struct hash_entry *) a;
|
|
const struct hash_entry *he_b = (const struct hash_entry *) b;
|
|
|
|
return (he_a->ino - he_b->ino);
|
|
}
|
|
|
|
/* Used for sorting the hash entry */
|
|
static EXT2_QSORT_TYPE name_cmp(const void *a, const void *b)
|
|
{
|
|
const struct hash_entry *he_a = (const struct hash_entry *) a;
|
|
const struct hash_entry *he_b = (const struct hash_entry *) b;
|
|
int ret;
|
|
int min_len;
|
|
|
|
min_len = he_a->dir->name_len;
|
|
if (min_len > he_b->dir->name_len)
|
|
min_len = he_b->dir->name_len;
|
|
|
|
ret = strncmp(he_a->dir->name, he_b->dir->name, min_len);
|
|
if (ret == 0) {
|
|
if (he_a->dir->name_len > he_b->dir->name_len)
|
|
ret = 1;
|
|
else if (he_a->dir->name_len < he_b->dir->name_len)
|
|
ret = -1;
|
|
else
|
|
ret = he_b->dir->inode - he_a->dir->inode;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Used for sorting the hash entry */
|
|
static EXT2_QSORT_TYPE hash_cmp(const void *a, const void *b)
|
|
{
|
|
const struct hash_entry *he_a = (const struct hash_entry *) a;
|
|
const struct hash_entry *he_b = (const struct hash_entry *) b;
|
|
int ret;
|
|
|
|
if (he_a->hash > he_b->hash)
|
|
ret = 1;
|
|
else if (he_a->hash < he_b->hash)
|
|
ret = -1;
|
|
else {
|
|
if (he_a->minor_hash > he_b->minor_hash)
|
|
ret = 1;
|
|
else if (he_a->minor_hash < he_b->minor_hash)
|
|
ret = -1;
|
|
else
|
|
ret = name_cmp(a, b);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static errcode_t alloc_size_dir(ext2_filsys fs, struct out_dir *outdir,
|
|
int blocks)
|
|
{
|
|
void *new_mem;
|
|
|
|
if (outdir->max) {
|
|
new_mem = realloc(outdir->buf, blocks * fs->blocksize);
|
|
if (!new_mem)
|
|
return ENOMEM;
|
|
outdir->buf = new_mem;
|
|
new_mem = realloc(outdir->hashes,
|
|
blocks * sizeof(ext2_dirhash_t));
|
|
if (!new_mem)
|
|
return ENOMEM;
|
|
outdir->hashes = new_mem;
|
|
} else {
|
|
outdir->buf = malloc(blocks * fs->blocksize);
|
|
outdir->hashes = malloc(blocks * sizeof(ext2_dirhash_t));
|
|
outdir->num = 0;
|
|
}
|
|
outdir->max = blocks;
|
|
return 0;
|
|
}
|
|
|
|
static void free_out_dir(struct out_dir *outdir)
|
|
{
|
|
free(outdir->buf);
|
|
free(outdir->hashes);
|
|
outdir->max = 0;
|
|
outdir->num =0;
|
|
}
|
|
|
|
static errcode_t get_next_block(ext2_filsys fs, struct out_dir *outdir,
|
|
char ** ret)
|
|
{
|
|
errcode_t retval;
|
|
|
|
if (outdir->num >= outdir->max) {
|
|
retval = alloc_size_dir(fs, outdir, outdir->max + 50);
|
|
if (retval)
|
|
return retval;
|
|
}
|
|
*ret = outdir->buf + (outdir->num++ * fs->blocksize);
|
|
memset(*ret, 0, fs->blocksize);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function is used to make a unique filename. We do this by
|
|
* appending ~0, and then incrementing the number. However, we cannot
|
|
* expand the length of the filename beyond the padding available in
|
|
* the directory entry.
|
|
*/
|
|
static void mutate_name(char *str, __u16 *len)
|
|
{
|
|
int i;
|
|
__u16 l = *len & 0xFF, h = *len & 0xff00;
|
|
|
|
/*
|
|
* First check to see if it looks the name has been mutated
|
|
* already
|
|
*/
|
|
for (i = l-1; i > 0; i--) {
|
|
if (!isdigit(str[i]))
|
|
break;
|
|
}
|
|
if ((i == l-1) || (str[i] != '~')) {
|
|
if (((l-1) & 3) < 2)
|
|
l += 2;
|
|
else
|
|
l = (l+3) & ~3;
|
|
str[l-2] = '~';
|
|
str[l-1] = '0';
|
|
*len = l | h;
|
|
return;
|
|
}
|
|
for (i = l-1; i >= 0; i--) {
|
|
if (isdigit(str[i])) {
|
|
if (str[i] == '9')
|
|
str[i] = '0';
|
|
else {
|
|
str[i]++;
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
if (i == 1) {
|
|
if (str[0] == 'z')
|
|
str[0] = 'A';
|
|
else if (str[0] == 'Z') {
|
|
str[0] = '~';
|
|
str[1] = '0';
|
|
} else
|
|
str[0]++;
|
|
} else if (i > 0) {
|
|
str[i] = '1';
|
|
str[i-1] = '~';
|
|
} else {
|
|
if (str[0] == '~')
|
|
str[0] = 'a';
|
|
else
|
|
str[0]++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs,
|
|
ext2_ino_t ino,
|
|
struct fill_dir_struct *fd)
|
|
{
|
|
struct problem_context pctx;
|
|
struct hash_entry *ent, *prev;
|
|
int i, j;
|
|
int fixed = 0;
|
|
char new_name[256];
|
|
__u16 new_len;
|
|
int hash_alg;
|
|
|
|
clear_problem_context(&pctx);
|
|
pctx.ino = ino;
|
|
|
|
hash_alg = fs->super->s_def_hash_version;
|
|
if ((hash_alg <= EXT2_HASH_TEA) &&
|
|
(fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH))
|
|
hash_alg += 3;
|
|
|
|
for (i=1; i < fd->num_array; i++) {
|
|
ent = fd->harray + i;
|
|
prev = ent - 1;
|
|
if (!ent->dir->inode ||
|
|
((ent->dir->name_len & 0xFF) !=
|
|
(prev->dir->name_len & 0xFF)) ||
|
|
(strncmp(ent->dir->name, prev->dir->name,
|
|
ent->dir->name_len & 0xFF)))
|
|
continue;
|
|
pctx.dirent = ent->dir;
|
|
if ((ent->dir->inode == prev->dir->inode) &&
|
|
fix_problem(ctx, PR_2_DUPLICATE_DIRENT, &pctx)) {
|
|
e2fsck_adjust_inode_count(ctx, ent->dir->inode, -1);
|
|
ent->dir->inode = 0;
|
|
fixed++;
|
|
continue;
|
|
}
|
|
memcpy(new_name, ent->dir->name, ent->dir->name_len & 0xFF);
|
|
new_len = ent->dir->name_len;
|
|
mutate_name(new_name, &new_len);
|
|
for (j=0; j < fd->num_array; j++) {
|
|
if ((i==j) ||
|
|
((new_len & 0xFF) !=
|
|
(fd->harray[j].dir->name_len & 0xFF)) ||
|
|
(strncmp(new_name, fd->harray[j].dir->name,
|
|
new_len & 0xFF)))
|
|
continue;
|
|
mutate_name(new_name, &new_len);
|
|
|
|
j = -1;
|
|
}
|
|
new_name[new_len & 0xFF] = 0;
|
|
pctx.str = new_name;
|
|
if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE, &pctx)) {
|
|
memcpy(ent->dir->name, new_name, new_len & 0xFF);
|
|
ent->dir->name_len = new_len;
|
|
ext2fs_dirhash(hash_alg, ent->dir->name,
|
|
ent->dir->name_len & 0xFF,
|
|
fs->super->s_hash_seed,
|
|
&ent->hash, &ent->minor_hash);
|
|
fixed++;
|
|
}
|
|
}
|
|
return fixed;
|
|
}
|
|
|
|
|
|
static errcode_t copy_dir_entries(e2fsck_t ctx,
|
|
struct fill_dir_struct *fd,
|
|
struct out_dir *outdir,
|
|
ext2_ino_t ino)
|
|
{
|
|
ext2_filsys fs = ctx->fs;
|
|
errcode_t retval;
|
|
char *block_start;
|
|
struct hash_entry *ent;
|
|
struct ext2_dir_entry *dirent;
|
|
unsigned int rec_len, prev_rec_len;
|
|
int i, left;
|
|
ext2_dirhash_t prev_hash;
|
|
int offset, slack;
|
|
int csum_size = 0;
|
|
struct ext2_dir_entry_tail *t;
|
|
|
|
if (ctx->htree_slack_percentage == 255) {
|
|
profile_get_uint(ctx->profile, "options",
|
|
"indexed_dir_slack_percentage",
|
|
0, 20,
|
|
&ctx->htree_slack_percentage);
|
|
if (ctx->htree_slack_percentage > 100)
|
|
ctx->htree_slack_percentage = 20;
|
|
}
|
|
|
|
if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
|
|
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
|
|
csum_size = sizeof(struct ext2_dir_entry_tail);
|
|
|
|
outdir->max = 0;
|
|
retval = alloc_size_dir(fs, outdir,
|
|
(fd->dir_size / fs->blocksize) + 2);
|
|
if (retval)
|
|
return retval;
|
|
outdir->num = fd->compress ? 0 : 1;
|
|
offset = 0;
|
|
outdir->hashes[0] = 0;
|
|
prev_hash = 1;
|
|
if ((retval = get_next_block(fs, outdir, &block_start)))
|
|
return retval;
|
|
dirent = (struct ext2_dir_entry *) block_start;
|
|
prev_rec_len = 0;
|
|
rec_len = 0;
|
|
left = fs->blocksize - csum_size;
|
|
slack = fd->compress ? 12 :
|
|
((fs->blocksize - csum_size) * ctx->htree_slack_percentage)/100;
|
|
if (slack < 12)
|
|
slack = 12;
|
|
for (i = 0; i < fd->num_array; i++) {
|
|
ent = fd->harray + i;
|
|
if (ent->dir->inode == 0)
|
|
continue;
|
|
rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF);
|
|
if (rec_len > left) {
|
|
if (left) {
|
|
left += prev_rec_len;
|
|
retval = ext2fs_set_rec_len(fs, left, dirent);
|
|
if (retval)
|
|
return retval;
|
|
}
|
|
if (csum_size) {
|
|
t = EXT2_DIRENT_TAIL(block_start,
|
|
fs->blocksize);
|
|
ext2fs_initialize_dirent_tail(fs, t);
|
|
}
|
|
if ((retval = get_next_block(fs, outdir,
|
|
&block_start)))
|
|
return retval;
|
|
offset = 0;
|
|
}
|
|
left = (fs->blocksize - csum_size) - offset;
|
|
dirent = (struct ext2_dir_entry *) (block_start + offset);
|
|
if (offset == 0) {
|
|
if (ent->hash == prev_hash)
|
|
outdir->hashes[outdir->num-1] = ent->hash | 1;
|
|
else
|
|
outdir->hashes[outdir->num-1] = ent->hash;
|
|
}
|
|
dirent->inode = ent->dir->inode;
|
|
dirent->name_len = ent->dir->name_len;
|
|
retval = ext2fs_set_rec_len(fs, rec_len, dirent);
|
|
if (retval)
|
|
return retval;
|
|
prev_rec_len = rec_len;
|
|
memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF);
|
|
offset += rec_len;
|
|
left -= rec_len;
|
|
if (left < slack) {
|
|
prev_rec_len += left;
|
|
retval = ext2fs_set_rec_len(fs, prev_rec_len, dirent);
|
|
if (retval)
|
|
return retval;
|
|
offset += left;
|
|
left = 0;
|
|
}
|
|
prev_hash = ent->hash;
|
|
}
|
|
if (left)
|
|
retval = ext2fs_set_rec_len(fs, rec_len + left, dirent);
|
|
if (csum_size) {
|
|
t = EXT2_DIRENT_TAIL(block_start, fs->blocksize);
|
|
ext2fs_initialize_dirent_tail(fs, t);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf,
|
|
ext2_ino_t ino, ext2_ino_t parent)
|
|
{
|
|
struct ext2_dir_entry *dir;
|
|
struct ext2_dx_root_info *root;
|
|
struct ext2_dx_countlimit *limits;
|
|
int filetype = 0;
|
|
int csum_size = 0;
|
|
|
|
if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE)
|
|
filetype = EXT2_FT_DIR << 8;
|
|
|
|
memset(buf, 0, fs->blocksize);
|
|
dir = (struct ext2_dir_entry *) buf;
|
|
dir->inode = ino;
|
|
dir->name[0] = '.';
|
|
dir->name_len = 1 | filetype;
|
|
dir->rec_len = 12;
|
|
dir = (struct ext2_dir_entry *) (buf + 12);
|
|
dir->inode = parent;
|
|
dir->name[0] = '.';
|
|
dir->name[1] = '.';
|
|
dir->name_len = 2 | filetype;
|
|
dir->rec_len = fs->blocksize - 12;
|
|
|
|
root = (struct ext2_dx_root_info *) (buf+24);
|
|
root->reserved_zero = 0;
|
|
root->hash_version = fs->super->s_def_hash_version;
|
|
root->info_length = 8;
|
|
root->indirect_levels = 0;
|
|
root->unused_flags = 0;
|
|
|
|
if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
|
|
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
|
|
csum_size = sizeof(struct ext2_dx_tail);
|
|
|
|
limits = (struct ext2_dx_countlimit *) (buf+32);
|
|
limits->limit = (fs->blocksize - (32 + csum_size)) /
|
|
sizeof(struct ext2_dx_entry);
|
|
limits->count = 0;
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
static struct ext2_dx_entry *set_int_node(ext2_filsys fs, char *buf)
|
|
{
|
|
struct ext2_dir_entry *dir;
|
|
struct ext2_dx_countlimit *limits;
|
|
int csum_size = 0;
|
|
|
|
memset(buf, 0, fs->blocksize);
|
|
dir = (struct ext2_dir_entry *) buf;
|
|
dir->inode = 0;
|
|
(void) ext2fs_set_rec_len(fs, fs->blocksize, dir);
|
|
|
|
if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
|
|
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
|
|
csum_size = sizeof(struct ext2_dx_tail);
|
|
|
|
limits = (struct ext2_dx_countlimit *) (buf+8);
|
|
limits->limit = (fs->blocksize - (8 + csum_size)) /
|
|
sizeof(struct ext2_dx_entry);
|
|
limits->count = 0;
|
|
|
|
return (struct ext2_dx_entry *) limits;
|
|
}
|
|
|
|
/*
|
|
* This function takes the leaf nodes which have been written in
|
|
* outdir, and populates the root node and any necessary interior nodes.
|
|
*/
|
|
static errcode_t calculate_tree(ext2_filsys fs,
|
|
struct out_dir *outdir,
|
|
ext2_ino_t ino,
|
|
ext2_ino_t parent)
|
|
{
|
|
struct ext2_dx_root_info *root_info;
|
|
struct ext2_dx_entry *root, *dx_ent = 0;
|
|
struct ext2_dx_countlimit *root_limit, *limit;
|
|
errcode_t retval;
|
|
char * block_start;
|
|
int i, c1, c2, nblks;
|
|
int limit_offset, root_offset;
|
|
|
|
root_info = set_root_node(fs, outdir->buf, ino, parent);
|
|
root_offset = limit_offset = ((char *) root_info - outdir->buf) +
|
|
root_info->info_length;
|
|
root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset);
|
|
c1 = root_limit->limit;
|
|
nblks = outdir->num;
|
|
|
|
/* Write out the pointer blocks */
|
|
if (nblks-1 <= c1) {
|
|
/* Just write out the root block, and we're done */
|
|
root = (struct ext2_dx_entry *) (outdir->buf + root_offset);
|
|
for (i=1; i < nblks; i++) {
|
|
root->block = ext2fs_cpu_to_le32(i);
|
|
if (i != 1)
|
|
root->hash =
|
|
ext2fs_cpu_to_le32(outdir->hashes[i]);
|
|
root++;
|
|
c1--;
|
|
}
|
|
} else {
|
|
c2 = 0;
|
|
limit = 0;
|
|
root_info->indirect_levels = 1;
|
|
for (i=1; i < nblks; i++) {
|
|
if (c1 == 0)
|
|
return ENOSPC;
|
|
if (c2 == 0) {
|
|
if (limit)
|
|
limit->limit = limit->count =
|
|
ext2fs_cpu_to_le16(limit->limit);
|
|
root = (struct ext2_dx_entry *)
|
|
(outdir->buf + root_offset);
|
|
root->block = ext2fs_cpu_to_le32(outdir->num);
|
|
if (i != 1)
|
|
root->hash =
|
|
ext2fs_cpu_to_le32(outdir->hashes[i]);
|
|
if ((retval = get_next_block(fs, outdir,
|
|
&block_start)))
|
|
return retval;
|
|
dx_ent = set_int_node(fs, block_start);
|
|
limit = (struct ext2_dx_countlimit *) dx_ent;
|
|
c2 = limit->limit;
|
|
root_offset += sizeof(struct ext2_dx_entry);
|
|
c1--;
|
|
}
|
|
dx_ent->block = ext2fs_cpu_to_le32(i);
|
|
if (c2 != limit->limit)
|
|
dx_ent->hash =
|
|
ext2fs_cpu_to_le32(outdir->hashes[i]);
|
|
dx_ent++;
|
|
c2--;
|
|
}
|
|
limit->count = ext2fs_cpu_to_le16(limit->limit - c2);
|
|
limit->limit = ext2fs_cpu_to_le16(limit->limit);
|
|
}
|
|
root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset);
|
|
root_limit->count = ext2fs_cpu_to_le16(root_limit->limit - c1);
|
|
root_limit->limit = ext2fs_cpu_to_le16(root_limit->limit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct write_dir_struct {
|
|
struct out_dir *outdir;
|
|
errcode_t err;
|
|
e2fsck_t ctx;
|
|
int cleared;
|
|
ext2_ino_t dir;
|
|
};
|
|
|
|
/*
|
|
* Helper function which writes out a directory block.
|
|
*/
|
|
static int write_dir_block(ext2_filsys fs,
|
|
blk64_t *block_nr,
|
|
e2_blkcnt_t blockcnt,
|
|
blk64_t ref_block EXT2FS_ATTR((unused)),
|
|
int ref_offset EXT2FS_ATTR((unused)),
|
|
void *priv_data)
|
|
{
|
|
struct write_dir_struct *wd = (struct write_dir_struct *) priv_data;
|
|
blk64_t blk;
|
|
char *dir, *buf = 0;
|
|
|
|
if (*block_nr == 0)
|
|
return 0;
|
|
if (blockcnt < 0)
|
|
return 0;
|
|
if (blockcnt < wd->outdir->num)
|
|
dir = wd->outdir->buf + (blockcnt * fs->blocksize);
|
|
else if (wd->ctx->lost_and_found == wd->dir) {
|
|
/* Don't release any extra directory blocks for lost+found */
|
|
wd->err = ext2fs_new_dir_block(fs, 0, 0, &buf);
|
|
if (wd->err)
|
|
return BLOCK_ABORT;
|
|
dir = buf;
|
|
wd->outdir->num++;
|
|
} else {
|
|
/* We don't need this block, so release it */
|
|
e2fsck_read_bitmaps(wd->ctx);
|
|
blk = *block_nr;
|
|
ext2fs_unmark_block_bitmap2(wd->ctx->block_found_map, blk);
|
|
ext2fs_block_alloc_stats2(fs, blk, -1);
|
|
*block_nr = 0;
|
|
wd->cleared++;
|
|
return BLOCK_CHANGED;
|
|
}
|
|
|
|
wd->err = ext2fs_write_dir_block4(fs, *block_nr, dir, 0, wd->dir);
|
|
if (buf)
|
|
ext2fs_free_mem(&buf);
|
|
|
|
if (wd->err)
|
|
return BLOCK_ABORT;
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t write_directory(e2fsck_t ctx, ext2_filsys fs,
|
|
struct out_dir *outdir,
|
|
ext2_ino_t ino, int compress)
|
|
{
|
|
struct write_dir_struct wd;
|
|
errcode_t retval;
|
|
struct ext2_inode inode;
|
|
|
|
retval = e2fsck_expand_directory(ctx, ino, -1, outdir->num);
|
|
if (retval)
|
|
return retval;
|
|
|
|
wd.outdir = outdir;
|
|
wd.err = 0;
|
|
wd.ctx = ctx;
|
|
wd.cleared = 0;
|
|
wd.dir = ino;
|
|
|
|
retval = ext2fs_block_iterate3(fs, ino, 0, 0,
|
|
write_dir_block, &wd);
|
|
if (retval)
|
|
return retval;
|
|
if (wd.err)
|
|
return wd.err;
|
|
|
|
e2fsck_read_inode(ctx, ino, &inode, "rehash_dir");
|
|
if (compress)
|
|
inode.i_flags &= ~EXT2_INDEX_FL;
|
|
else
|
|
inode.i_flags |= EXT2_INDEX_FL;
|
|
inode.i_size = outdir->num * fs->blocksize;
|
|
ext2fs_iblk_sub_blocks(fs, &inode, wd.cleared);
|
|
e2fsck_write_inode(ctx, ino, &inode, "rehash_dir");
|
|
|
|
return 0;
|
|
}
|
|
|
|
errcode_t e2fsck_rehash_dir(e2fsck_t ctx, ext2_ino_t ino)
|
|
{
|
|
ext2_filsys fs = ctx->fs;
|
|
errcode_t retval;
|
|
struct ext2_inode inode;
|
|
char *dir_buf = 0;
|
|
struct fill_dir_struct fd;
|
|
struct out_dir outdir;
|
|
|
|
outdir.max = outdir.num = 0;
|
|
outdir.buf = 0;
|
|
outdir.hashes = 0;
|
|
e2fsck_read_inode(ctx, ino, &inode, "rehash_dir");
|
|
|
|
retval = ENOMEM;
|
|
fd.harray = 0;
|
|
dir_buf = malloc(inode.i_size);
|
|
if (!dir_buf)
|
|
goto errout;
|
|
|
|
fd.max_array = inode.i_size / 32;
|
|
fd.num_array = 0;
|
|
fd.harray = malloc(fd.max_array * sizeof(struct hash_entry));
|
|
if (!fd.harray)
|
|
goto errout;
|
|
|
|
fd.ctx = ctx;
|
|
fd.buf = dir_buf;
|
|
fd.inode = &inode;
|
|
fd.err = 0;
|
|
fd.dir_size = 0;
|
|
fd.compress = 0;
|
|
fd.dir = ino;
|
|
if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) ||
|
|
(inode.i_size / fs->blocksize) < 2)
|
|
fd.compress = 1;
|
|
fd.parent = 0;
|
|
|
|
retry_nohash:
|
|
/* Read in the entire directory into memory */
|
|
retval = ext2fs_block_iterate3(fs, ino, 0, 0,
|
|
fill_dir_block, &fd);
|
|
if (fd.err) {
|
|
retval = fd.err;
|
|
goto errout;
|
|
}
|
|
|
|
/*
|
|
* If the entries read are less than a block, then don't index
|
|
* the directory
|
|
*/
|
|
if (!fd.compress && (fd.dir_size < (fs->blocksize - 24))) {
|
|
fd.compress = 1;
|
|
fd.dir_size = 0;
|
|
fd.num_array = 0;
|
|
goto retry_nohash;
|
|
}
|
|
|
|
#if 0
|
|
printf("%d entries (%d bytes) found in inode %d\n",
|
|
fd.num_array, fd.dir_size, ino);
|
|
#endif
|
|
|
|
/* Sort the list */
|
|
resort:
|
|
if (fd.compress)
|
|
qsort(fd.harray+2, fd.num_array-2, sizeof(struct hash_entry),
|
|
hash_cmp);
|
|
else
|
|
qsort(fd.harray, fd.num_array, sizeof(struct hash_entry),
|
|
hash_cmp);
|
|
|
|
/*
|
|
* Look for duplicates
|
|
*/
|
|
if (duplicate_search_and_fix(ctx, fs, ino, &fd))
|
|
goto resort;
|
|
|
|
if (ctx->options & E2F_OPT_NO) {
|
|
retval = 0;
|
|
goto errout;
|
|
}
|
|
|
|
/* Sort non-hashed directories by inode number */
|
|
if (fd.compress)
|
|
qsort(fd.harray+2, fd.num_array-2,
|
|
sizeof(struct hash_entry), ino_cmp);
|
|
|
|
/*
|
|
* Copy the directory entries. In a htree directory these
|
|
* will become the leaf nodes.
|
|
*/
|
|
retval = copy_dir_entries(ctx, &fd, &outdir, ino);
|
|
if (retval)
|
|
goto errout;
|
|
|
|
free(dir_buf); dir_buf = 0;
|
|
|
|
if (!fd.compress) {
|
|
/* Calculate the interior nodes */
|
|
retval = calculate_tree(fs, &outdir, ino, fd.parent);
|
|
if (retval)
|
|
goto errout;
|
|
}
|
|
|
|
retval = write_directory(ctx, fs, &outdir, ino, fd.compress);
|
|
if (retval)
|
|
goto errout;
|
|
|
|
errout:
|
|
free(dir_buf);
|
|
free(fd.harray);
|
|
|
|
free_out_dir(&outdir);
|
|
return retval;
|
|
}
|
|
|
|
void e2fsck_rehash_directories(e2fsck_t ctx)
|
|
{
|
|
struct problem_context pctx;
|
|
#ifdef RESOURCE_TRACK
|
|
struct resource_track rtrack;
|
|
#endif
|
|
struct dir_info *dir;
|
|
ext2_u32_iterate iter;
|
|
struct dir_info_iter * dirinfo_iter = 0;
|
|
ext2_ino_t ino;
|
|
errcode_t retval;
|
|
int cur, max, all_dirs, dir_index, first = 1;
|
|
|
|
init_resource_track(&rtrack, ctx->fs->io);
|
|
all_dirs = ctx->options & E2F_OPT_COMPRESS_DIRS;
|
|
|
|
if (!ctx->dirs_to_hash && !all_dirs)
|
|
return;
|
|
|
|
(void) e2fsck_get_lost_and_found(ctx, 0);
|
|
|
|
clear_problem_context(&pctx);
|
|
|
|
dir_index = ctx->fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX;
|
|
cur = 0;
|
|
if (all_dirs) {
|
|
dirinfo_iter = e2fsck_dir_info_iter_begin(ctx);
|
|
max = e2fsck_get_num_dirinfo(ctx);
|
|
} else {
|
|
retval = ext2fs_u32_list_iterate_begin(ctx->dirs_to_hash,
|
|
&iter);
|
|
if (retval) {
|
|
pctx.errcode = retval;
|
|
fix_problem(ctx, PR_3A_OPTIMIZE_ITER, &pctx);
|
|
return;
|
|
}
|
|
max = ext2fs_u32_list_count(ctx->dirs_to_hash);
|
|
}
|
|
while (1) {
|
|
if (all_dirs) {
|
|
if ((dir = e2fsck_dir_info_iter(ctx,
|
|
dirinfo_iter)) == 0)
|
|
break;
|
|
ino = dir->ino;
|
|
} else {
|
|
if (!ext2fs_u32_list_iterate(iter, &ino))
|
|
break;
|
|
}
|
|
|
|
pctx.dir = ino;
|
|
if (first) {
|
|
fix_problem(ctx, PR_3A_PASS_HEADER, &pctx);
|
|
first = 0;
|
|
}
|
|
#if 0
|
|
fix_problem(ctx, PR_3A_OPTIMIZE_DIR, &pctx);
|
|
#endif
|
|
pctx.errcode = e2fsck_rehash_dir(ctx, ino);
|
|
if (pctx.errcode) {
|
|
end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR);
|
|
fix_problem(ctx, PR_3A_OPTIMIZE_DIR_ERR, &pctx);
|
|
}
|
|
if (ctx->progress && !ctx->progress_fd)
|
|
e2fsck_simple_progress(ctx, "Rebuilding directory",
|
|
100.0 * (float) (++cur) / (float) max, ino);
|
|
}
|
|
end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR);
|
|
if (all_dirs)
|
|
e2fsck_dir_info_iter_end(ctx, dirinfo_iter);
|
|
else
|
|
ext2fs_u32_list_iterate_end(iter);
|
|
|
|
if (ctx->dirs_to_hash)
|
|
ext2fs_u32_list_free(ctx->dirs_to_hash);
|
|
ctx->dirs_to_hash = 0;
|
|
|
|
print_resource_track(ctx, "Pass 3A", &rtrack, ctx->fs->io);
|
|
}
|