Add directory hashed signed/unsigned hint to superblock

The e2fsprogs and kernel implementation of directory hash tree has a
bug which causes the implementation to be dependent on whether
characters are signed or unsigned.  Platforms such as the PowerPC,
Arm, and S/390 have signed characters by default, which means that
hash directories on those systems are incompatible with hash
directories on other systems, such as the x86.

To fix this we add a new flags field to the superblock, and define two
new bits in that field to indicate whether or not the directory should
be signed or unsigned.  If the bits are not set, e2fsck and fixed
kernels will set them to the signed/unsigned value of the currently
running platform, and then respect those bits when calculating the
directory hash.  This allows compatibility with current filesystems,
as well as allowing cross-architectural compatibility.

Addresses Debian Bug: #389772

Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
bitmap-optimize
Theodore Ts'o 2006-11-11 22:32:35 -05:00
parent 52325593b1
commit f77704e416
32 changed files with 226 additions and 23 deletions

2
debian/rules vendored
View File

@ -64,7 +64,7 @@ CFGBFSTAMP=${STAMPSDIR}/configure-bf-stamp
BUILDSTDSTAMP=${STAMPSDIR}/build-std-stamp
BUILDBFSTAMP=${STAMPSDIR}/build-bf-stamp
CCOPTS = -g -fsigned-char
CCOPTS = -g
INSTALL = install
INSTALL_PROGRAM = $(INSTALL) -p -o root -g root -m 0755

View File

@ -1,3 +1,12 @@
2006-11-11 Theodore Tso <tytso@mit.edu>
* set_fields.c: Add the ability to use set_super_value to set the
superblock flags field.
* htree.c (htree_dump_leaf_node): Check the superblock flags to
determine whether to use the signed or unsigned version of
the hash should be used.
2006-10-01 Theodore Tso <tytso@mit.edu>
* Makefile.in (DEPLIBBLKID): Use DEPLIBBLKID not LIBBLKID to

View File

@ -39,6 +39,7 @@ static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino,
char tmp[EXT2_NAME_LEN + 16];
blk_t pblk;
ext2_dirhash_t hash;
int hash_alg;
errcode = ext2fs_bmap(fs, ino, inode, buf, 0, blk, &pblk);
if (errcode) {
@ -53,6 +54,10 @@ static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino,
"while reading block %u\n", blk);
return;
}
hash_alg = rootnode->hash_version;
if ((hash_alg <= EXT2_HASH_TEA) &&
(fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH))
hash_alg += 3;
while (offset < fs->blocksize) {
dirent = (struct ext2_dir_entry *) (buf + offset);
@ -67,7 +72,7 @@ static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino,
(dirent->name_len & 0xFF) : EXT2_NAME_LEN;
strncpy(name, dirent->name, thislen);
name[thislen] = '\0';
errcode = ext2fs_dirhash(rootnode->hash_version, name,
errcode = ext2fs_dirhash(hash_alg, name,
thislen, fs->super->s_hash_seed,
&hash, 0);
if (errcode)

View File

@ -110,6 +110,7 @@ static struct field_set_info super_fields[] = {
{ "mkfs_time", &set_sb.s_mkfs_time, 4, parse_time },
{ "jnl_blocks", &set_sb.s_jnl_blocks[0], 4, parse_uint, FLAG_ARRAY,
17 },
{ "flags", &set_sb.s_flags, 4, parse_uint },
{ 0, 0, 0, 0 }
};

View File

@ -1,5 +1,16 @@
2006-11-11 Theodore Tso <tytso@mit.edu>
* super.c (e2fsck_fix_dirhash_hint, check_super_block): If neither
the signed or unsigned dirhash hint, set it based on
default signed vs. unsigned character type in use by the
platform.
* problem.c, problem.h (PR_0_DIRHASH_HINT): Add new problem code.
* pass2.c (check_dir_block), rehash.c (fill_dir_block): Check the
superblock flags to determine whether to use the signed or
unsigned version of the hash should be used.
* problem.c, problem.h (PR_2_BLOCKS_HI_ZERO): Add new problem code.
* pass1.c (e2fsck_pass1), pass2.c (e2fsck_process_bad_inode):

View File

@ -801,8 +801,11 @@ static int check_dir_block(ext2_filsys fs,
clear_htree(ctx, ino);
dx_dir->numblocks = 0;
dx_db = 0;
}
}
dx_dir->hashversion = root->hash_version;
if ((dx_dir->hashversion <= EXT2_HASH_TEA) &&
(fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH))
dx_dir->hashversion += 3;
dx_dir->depth = root->indirect_levels + 1;
} else if ((dirent->inode == 0) &&
(dirent->rec_len == fs->blocksize) &&

View File

@ -346,6 +346,11 @@ static struct e2fsck_problem problem_table[] = {
N_("@S hint for external superblock @s %X. "),
PROMPT_FIX, PR_PREEN_OK },
/* Adding dirhash hint */
{ PR_0_DIRHASH_HINT,
N_("Adding dirhash hint to @f.\n\n"),
PROMPT_NONE, 0 },
/* Pass 1 errors */
/* Pass 1: Checking inodes, blocks, and sizes */

View File

@ -193,6 +193,9 @@ struct problem_context {
/* Superblock hint for external journal incorrect */
#define PR_0_EXTERNAL_JOURNAL_HINT 0x000033
/* Superblock hint for external journal incorrect */
#define PR_0_DIRHASH_HINT 0x000034
/*
* Pass 1 errors
*/

View File

@ -88,6 +88,7 @@ static int fill_dir_block(ext2_filsys fs,
struct ext2_dir_entry *dirent;
char *dir;
unsigned int offset, dir_offset;
int hash_alg;
if (blockcnt < 0)
return 0;
@ -107,6 +108,10 @@ static int fill_dir_block(ext2_filsys fs,
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) {
@ -145,8 +150,7 @@ static int fill_dir_block(ext2_filsys fs,
if (fd->compress)
ent->hash = ent->minor_hash = 0;
else {
fd->err = ext2fs_dirhash(fs->super->s_def_hash_version,
dirent->name,
fd->err = ext2fs_dirhash(hash_alg, dirent->name,
dirent->name_len & 0xFF,
fs->super->s_hash_seed,
&ent->hash, &ent->minor_hash);
@ -323,10 +327,16 @@ static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs,
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;
@ -363,8 +373,7 @@ static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs,
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(fs->super->s_def_hash_version,
ent->dir->name,
ext2fs_dirhash(hash_alg, ent->dir->name,
ent->dir->name_len & 0xFF,
fs->super->s_hash_seed,
&ent->hash, &ent->minor_hash);

View File

@ -436,6 +436,36 @@ cleanup:
}
/*
* This function checks the dirhash signed/unsigned hint if necessary.
*/
void e2fsck_fix_dirhash_hint(e2fsck_t ctx)
{
struct ext2_super_block *sb = ctx->fs->super;
struct problem_context pctx;
problem_t problem;
int retval;
char c;
if ((ctx->options & E2F_OPT_READONLY) ||
!(sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) ||
(sb->s_flags & (EXT2_FLAGS_SIGNED_HASH|EXT2_FLAGS_UNSIGNED_HASH)))
return;
c = (char) 255;
clear_problem_context(&pctx);
if (fix_problem(ctx, PR_0_DIRHASH_HINT, &pctx)) {
if (((int) c) == -1) {
sb->s_flags |= EXT2_FLAGS_SIGNED_HASH;
} else {
sb->s_flags |= EXT2_FLAGS_UNSIGNED_HASH;
}
ext2fs_mark_super_dirty(ctx->fs);
}
}
void check_super_block(e2fsck_t ctx)
{
ext2_filsys fs = ctx->fs;
@ -732,5 +762,10 @@ void check_super_block(e2fsck_t ctx)
*/
e2fsck_fix_ext3_journal_hint(ctx);
/*
* Add dirhash hint if necessary
*/
e2fsck_fix_dirhash_hint(ctx);
return;
}

View File

@ -1,3 +1,8 @@
2006-11-11 Theodore Tso <tytso@mit.edu>
* ls.c (print_super_flags, list_super2): Print the signed/unsigned
dirhash information from the superblock flags field.
2006-09-29 Theodore Tso <tytso@mit.edu>
* percent.c (e2p_percent): Fix bug which caused e2p_percent to

View File

@ -138,6 +138,28 @@ static void print_mntopts(struct ext2_super_block * s, FILE *f)
#endif
}
static void print_super_flags(struct ext2_super_block * s, FILE *f)
{
int flags_found = 0;
if (s->s_flags == 0)
return;
fputs("Filesystem flags: ", f);
if (s->s_flags & EXT2_FLAGS_SIGNED_HASH) {
fputs("signed directory hash ", f);
flags_found++;
}
if (s->s_flags & EXT2_FLAGS_UNSIGNED_HASH) {
fputs("unsigned directory hash ", f);
flags_found++;
}
if (flags_found)
fputs("\n", f);
else
fputs("(none)\n", f);
}
#ifndef EXT2_INODE_SIZE
#define EXT2_INODE_SIZE(s) sizeof(struct ext2_inode)
@ -181,6 +203,7 @@ void list_super2(struct ext2_super_block * sb, FILE *f)
} else
fprintf(f, " (unknown)\n");
print_features(sb, f);
print_super_flags(sb, f);
print_mntopts(sb, f);
fprintf(f, "Filesystem state: ");
print_fs_state (f, sb->s_state);

View File

@ -1,5 +1,19 @@
2006-11-11 Theodore Tso <tytso@mit.edu>
* dirhash.c (str2hashbuf, ext2fs_dirhash): Add support for
calculating the unsigned version of the directory hash.
* initialize.c (ext2fs_initialize): Set the dirhash
signed/unsigned hint in s_flags.
* swapfs.c (ext2fs_swap_super): Byte swap the s_flags superblock
field.
* ext2_fs.h: Define a new superblock field, s_flags, which is used
to store a signed vs. unsigned dirhash hint. Define new
HTREE hash algorithm numbers to pass to userspace if the
unsigned algorithm are required.
* swapfs.c (ext2fs_swap_super):
ext2_fs.h: Add definition of EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE,
which adds s_min_extra_isize and s_want_extra_isize fields

View File

@ -116,11 +116,20 @@ static void halfMD4Transform (__u32 buf[4], __u32 const in[])
#undef K3
/* The old legacy hash */
static ext2_dirhash_t dx_hack_hash (const char *name, int len)
static ext2_dirhash_t dx_hack_hash (const char *name, int len,
int unsigned_flag)
{
__u32 hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
const unsigned char *ucp = (const unsigned char *) name;
const signed char *scp = (const signed char *) name;
int c;
while (len--) {
__u32 hash = hash1 + (hash0 ^ (*name++ * 7152373));
if (unsigned_flag)
c = (int) *ucp++;
else
c = (int) *scp++;
hash = hash1 + (hash0 ^ (c * 7152373));
if (hash & 0x80000000) hash -= 0x7fffffff;
hash1 = hash0;
@ -129,10 +138,13 @@ static ext2_dirhash_t dx_hack_hash (const char *name, int len)
return (hash0 << 1);
}
static void str2hashbuf(const char *msg, int len, __u32 *buf, int num)
static void str2hashbuf(const char *msg, int len, __u32 *buf, int num,
int unsigned_flag)
{
__u32 pad, val;
int i;
int i, c;
const unsigned char *ucp = (const unsigned char *) msg;
const signed char *scp = (const signed char *) msg;
pad = (__u32)len | ((__u32)len << 8);
pad |= pad << 16;
@ -143,7 +155,12 @@ static void str2hashbuf(const char *msg, int len, __u32 *buf, int num)
for (i=0; i < len; i++) {
if ((i % 4) == 0)
val = pad;
val = msg[i] + (val << 8);
if (unsigned_flag)
c = (int) ucp[i];
else
c = (int) scp[i];
val = c + (val << 8);
if ((i % 4) == 3) {
*buf++ = val;
val = pad;
@ -179,6 +196,7 @@ errcode_t ext2fs_dirhash(int version, const char *name, int len,
const char *p;
int i;
__u32 in[8], buf[4];
int unsigned_flag = 0;
/* Initialize the default seed for the hash checksum functions */
buf[0] = 0x67452301;
@ -197,13 +215,17 @@ errcode_t ext2fs_dirhash(int version, const char *name, int len,
}
switch (version) {
case EXT2_HASH_LEGACY_UNSIGNED:
unsigned_flag++;
case EXT2_HASH_LEGACY:
hash = dx_hack_hash(name, len);
hash = dx_hack_hash(name, len, unsigned_flag);
break;
case EXT2_HASH_HALF_MD4_UNSIGNED:
unsigned_flag++;
case EXT2_HASH_HALF_MD4:
p = name;
while (len > 0) {
str2hashbuf(p, len, in, 8);
str2hashbuf(p, len, in, 8, unsigned_flag);
halfMD4Transform(buf, in);
len -= 32;
p += 32;
@ -211,10 +233,12 @@ errcode_t ext2fs_dirhash(int version, const char *name, int len,
minor_hash = buf[2];
hash = buf[1];
break;
case EXT2_HASH_TEA_UNSIGNED:
unsigned_flag++;
case EXT2_HASH_TEA:
p = name;
while (len > 0) {
str2hashbuf(p, len, in, 4);
str2hashbuf(p, len, in, 4, unsigned_flag);
TEA_transform(buf, in);
len -= 16;
p += 16;

View File

@ -193,9 +193,12 @@ struct ext2_dx_root_info {
__u8 unused_flags;
};
#define EXT2_HASH_LEGACY 0
#define EXT2_HASH_HALF_MD4 1
#define EXT2_HASH_TEA 2
#define EXT2_HASH_LEGACY 0
#define EXT2_HASH_HALF_MD4 1
#define EXT2_HASH_TEA 2
#define EXT2_HASH_LEGACY_UNSIGNED 3 /* reserved for userspace lib */
#define EXT2_HASH_HALF_MD4_UNSIGNED 4 /* reserved for userspace lib */
#define EXT2_HASH_TEA_UNSIGNED 5 /* reserved for userspace lib */
#define EXT2_HASH_FLAG_INCOMPAT 0x1
@ -448,6 +451,12 @@ struct ext2_inode_large {
#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */
#define EXT2_ERROR_FS 0x0002 /* Errors detected */
/*
* Misc. filesystem flags
*/
#define EXT2_FLAGS_SIGNED_HASH 0x0001 /* Signed dirhash in use */
#define EXT2_FLAGS_UNSIGNED_HASH 0x0002 /* Unsigned dirhash in use */
/*
* Mount flags
*/
@ -557,7 +566,8 @@ struct ext2_super_block {
__u32 s_free_blocks_hi; /* Free blocks count */
__u16 s_min_extra_isize; /* All inodes have at least # bytes */
__u16 s_want_extra_isize; /* New inodes should reserve # bytes */
__u32 s_reserved[168]; /* Padding to the end of the block */
__u32 s_flags; /* Miscellaneous flags */
__u32 s_reserved[167]; /* Padding to the end of the block */
};
/*

View File

@ -105,6 +105,7 @@ errcode_t ext2fs_initialize(const char *name, int flags,
int rsv_gdt;
int io_flags;
char *buf;
char c;
if (!param || !param->s_blocks_count)
return EXT2_ET_INVALID_ARGUMENT;
@ -373,6 +374,13 @@ ipg_retry:
fs->group_desc[i].bg_used_dirs_count = 0;
}
c = (char) 255;
if (((int) c) == -1) {
super->s_flags |= EXT2_FLAGS_SIGNED_HASH;
} else {
super->s_flags |= EXT2_FLAGS_UNSIGNED_HASH;
}
ext2fs_mark_super_dirty(fs);
ext2fs_mark_bb_dirty(fs);
ext2fs_mark_ib_dirty(fs);

View File

@ -69,6 +69,7 @@ void ext2fs_swap_super(struct ext2_super_block * sb)
sb->s_free_blocks_hi = ext2fs_swab32(sb->s_free_blocks_hi);
sb->s_min_extra_isize = ext2fs_swab16(sb->s_min_extra_isize);
sb->s_want_extra_isize = ext2fs_swab16(sb->s_want_extra_isize);
sb->s_flags = ext2fs_swab32(sb->s_flags);
for (i=0; i < 4; i++)
sb->s_hash_seed[i] = ext2fs_swab32(sb->s_hash_seed[i]);
for (i=0; i < 17; i++)

View File

@ -1,5 +1,20 @@
2006-11-11 Theodore Tso <tytso@mit.edu>
* filter_dumpe2fs: Filter out the filesystem flags field since it
will be different for filesystems created with unsigned
char types.
* f_dup_de, f_h_badnode, f_h_badroot, f_h_reindex: Set the
directory hash bits so that these images are known to be
using the standard signed dirhash algorithm.
* f_h_normal: Add an 8-bit filename so we can test to make sure the
directory hash is working correctly when using the signed
dirhash algorithm.
* f_h_unsigned: New test to test calculating an unsigned directory
hash algorithm.
* Makefile.in (check-failed): New target which automatically
re-runs any failed tests

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,5 +3,5 @@ Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
test_filesys: 47729/100192 files (0.0% non-contiguous), 13687/31745 blocks
test_filesys: 47730/100192 files (0.0% non-contiguous), 13378/31745 blocks
Exit status is 0

View File

@ -3,5 +3,5 @@ Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
test_filesys: 47729/100192 files (0.0% non-contiguous), 13687/31745 blocks
test_filesys: 47730/100192 files (0.0% non-contiguous), 13378/31745 blocks
Exit status is 0

Binary file not shown.

View File

@ -1 +1 @@
Normal HTREE directory
Normal (signed) HTREE directory

Binary file not shown.

View File

@ -0,0 +1,7 @@
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
test_filesys: 47730/100192 files (0.0% non-contiguous), 13378/31745 blocks
Exit status is 0

View File

@ -0,0 +1,7 @@
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
test_filesys: 47730/100192 files (0.0% non-contiguous), 13378/31745 blocks
Exit status is 0

BIN
tests/f_h_unsigned/image.gz Normal file

Binary file not shown.

1
tests/f_h_unsigned/name Normal file
View File

@ -0,0 +1 @@
Unsigned HTREE directory

View File

@ -0,0 +1,6 @@
if test "$HTREE"x = yx ; then
. $cmd_dir/run_e2fsck
else
rm -f $test_name.ok $test_name.failed
echo "skipped"
fi

View File

@ -1,6 +1,7 @@
1s/^.*$//
/^Filesystem UUID:/d
/^Filesystem created:/d
/^Filesystem flags:/d
/^Last write time:/d
/^Last mount time:/d
/^Last checked:/d