You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
11 KiB
443 lines
11 KiB
/* |
|
* htree.c --- hash tree routines |
|
* |
|
* Copyright (C) 2002 Theodore Ts'o. This file may be redistributed |
|
* under the terms of the GNU Public License. |
|
*/ |
|
|
|
#include "config.h" |
|
#include <stdio.h> |
|
#include <unistd.h> |
|
#include <stdlib.h> |
|
#include <ctype.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#ifdef HAVE_ERRNO_H |
|
#include <errno.h> |
|
#endif |
|
#include <sys/types.h> |
|
#ifdef HAVE_GETOPT_H |
|
#include <getopt.h> |
|
#else |
|
extern int optind; |
|
extern char *optarg; |
|
#endif |
|
|
|
#include "debugfs.h" |
|
#include "uuid/uuid.h" |
|
#include "e2p/e2p.h" |
|
|
|
static FILE *pager; |
|
|
|
static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino, |
|
struct ext2_inode *inode, |
|
struct ext2_dx_root_info * rootnode, |
|
blk64_t blk, char *buf) |
|
{ |
|
errcode_t errcode; |
|
struct ext2_dir_entry *dirent; |
|
int thislen, col = 0; |
|
unsigned int offset = 0; |
|
char name[EXT2_NAME_LEN + 1]; |
|
char tmp[EXT2_NAME_LEN + 64]; |
|
blk64_t pblk; |
|
ext2_dirhash_t hash, minor_hash; |
|
unsigned int rec_len; |
|
int hash_alg; |
|
int csum_size = 0; |
|
|
|
if (ext2fs_has_feature_metadata_csum(fs->super)) |
|
csum_size = sizeof(struct ext2_dir_entry_tail); |
|
|
|
errcode = ext2fs_bmap2(fs, ino, inode, buf, 0, blk, 0, &pblk); |
|
if (errcode) { |
|
com_err("htree_dump_leaf_node", errcode, |
|
"while mapping logical block %llu\n", blk); |
|
return; |
|
} |
|
|
|
fprintf(pager, "Reading directory block %llu, phys %llu\n", blk, pblk); |
|
errcode = ext2fs_read_dir_block4(current_fs, pblk, buf, 0, ino); |
|
if (errcode) { |
|
com_err("htree_dump_leaf_node", errcode, |
|
"while reading block %llu (%llu)\n", |
|
blk, pblk); |
|
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); |
|
errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); |
|
if (errcode) { |
|
com_err("htree_dump_leaf_inode", errcode, |
|
"while getting rec_len for block %lu", |
|
(unsigned long) blk); |
|
return; |
|
} |
|
thislen = ext2fs_dirent_name_len(dirent); |
|
if (((offset + rec_len) > fs->blocksize) || |
|
(rec_len < 8) || |
|
((rec_len % 4) != 0) || |
|
((unsigned) thislen + 8 > rec_len)) { |
|
fprintf(pager, "Corrupted directory block (%llu)!\n", |
|
blk); |
|
break; |
|
} |
|
strncpy(name, dirent->name, thislen); |
|
name[thislen] = '\0'; |
|
errcode = ext2fs_dirhash(hash_alg, name, |
|
thislen, fs->super->s_hash_seed, |
|
&hash, &minor_hash); |
|
if (errcode) |
|
com_err("htree_dump_leaf_node", errcode, |
|
"while calculating hash"); |
|
if ((offset == fs->blocksize - csum_size) && |
|
(dirent->inode == 0) && |
|
(dirent->rec_len == csum_size) && |
|
(dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) { |
|
struct ext2_dir_entry_tail *t; |
|
|
|
t = (struct ext2_dir_entry_tail *) dirent; |
|
|
|
snprintf(tmp, EXT2_NAME_LEN + 64, |
|
"leaf block checksum: 0x%08x ", |
|
t->det_checksum); |
|
} else { |
|
snprintf(tmp, EXT2_NAME_LEN + 64, |
|
"%u 0x%08x-%08x (%d) %s ", |
|
dirent->inode, hash, minor_hash, |
|
rec_len, name); |
|
} |
|
thislen = strlen(tmp); |
|
if (col + thislen > 80) { |
|
fprintf(pager, "\n"); |
|
col = 0; |
|
} |
|
fprintf(pager, "%s", tmp); |
|
col += thislen; |
|
offset += rec_len; |
|
} |
|
fprintf(pager, "\n"); |
|
} |
|
|
|
|
|
static void htree_dump_int_block(ext2_filsys fs, ext2_ino_t ino, |
|
struct ext2_inode *inode, |
|
struct ext2_dx_root_info * rootnode, |
|
blk64_t blk, char *buf, int level); |
|
|
|
|
|
static void htree_dump_int_node(ext2_filsys fs, ext2_ino_t ino, |
|
struct ext2_inode *inode, |
|
struct ext2_dx_root_info * rootnode, |
|
struct ext2_dx_entry *ent, |
|
char *buf, int level) |
|
{ |
|
struct ext2_dx_countlimit dx_countlimit; |
|
struct ext2_dx_tail *tail; |
|
int hash, i; |
|
int limit, count; |
|
int remainder; |
|
|
|
dx_countlimit = *((struct ext2_dx_countlimit *) ent); |
|
count = ext2fs_le16_to_cpu(dx_countlimit.count); |
|
limit = ext2fs_le16_to_cpu(dx_countlimit.limit); |
|
|
|
fprintf(pager, "Number of entries (count): %d\n", count); |
|
fprintf(pager, "Number of entries (limit): %d\n", limit); |
|
|
|
remainder = fs->blocksize - (limit * sizeof(struct ext2_dx_entry)); |
|
if (ent == (struct ext2_dx_entry *)(rootnode + 1)) |
|
remainder -= sizeof(struct ext2_dx_root_info) + 24; |
|
else |
|
remainder -= 8; |
|
if (ext2fs_has_feature_metadata_csum(fs->super) && |
|
remainder == sizeof(struct ext2_dx_tail)) { |
|
tail = (struct ext2_dx_tail *)(ent + limit); |
|
fprintf(pager, "Checksum: 0x%08x\n", |
|
ext2fs_le32_to_cpu(tail->dt_checksum)); |
|
} |
|
|
|
for (i=0; i < count; i++) { |
|
hash = i ? ext2fs_le32_to_cpu(ent[i].hash) : 0; |
|
fprintf(pager, "Entry #%d: Hash 0x%08x%s, block %u\n", i, |
|
hash, (hash & 1) ? " (**)" : "", |
|
ext2fs_le32_to_cpu(ent[i].block)); |
|
} |
|
|
|
fprintf(pager, "\n"); |
|
|
|
for (i=0; i < count; i++) { |
|
unsigned int hashval, block; |
|
|
|
hashval = ext2fs_le32_to_cpu(ent[i].hash); |
|
block = ext2fs_le32_to_cpu(ent[i].block); |
|
fprintf(pager, "Entry #%d: Hash 0x%08x, block %u\n", i, |
|
i ? hashval : 0, block); |
|
if (level) |
|
htree_dump_int_block(fs, ino, inode, rootnode, |
|
block, buf, level-1); |
|
else |
|
htree_dump_leaf_node(fs, ino, inode, rootnode, |
|
block, buf); |
|
} |
|
|
|
fprintf(pager, "---------------------\n"); |
|
} |
|
|
|
static void htree_dump_int_block(ext2_filsys fs, ext2_ino_t ino, |
|
struct ext2_inode *inode, |
|
struct ext2_dx_root_info * rootnode, |
|
blk64_t blk, char *buf, int level) |
|
{ |
|
char *cbuf; |
|
errcode_t errcode; |
|
blk64_t pblk; |
|
|
|
cbuf = malloc(fs->blocksize); |
|
if (!cbuf) { |
|
fprintf(pager, "Couldn't allocate child block.\n"); |
|
return; |
|
} |
|
|
|
errcode = ext2fs_bmap2(fs, ino, inode, buf, 0, blk, 0, &pblk); |
|
if (errcode) { |
|
com_err("htree_dump_int_block", errcode, |
|
"while mapping logical block %llu\n", blk); |
|
goto errout; |
|
} |
|
|
|
errcode = io_channel_read_blk64(current_fs->io, pblk, 1, buf); |
|
if (errcode) { |
|
com_err("htree_dump_int_block", errcode, |
|
"while reading block %llu\n", blk); |
|
goto errout; |
|
} |
|
|
|
htree_dump_int_node(fs, ino, inode, rootnode, |
|
(struct ext2_dx_entry *) (buf+8), |
|
cbuf, level); |
|
errout: |
|
free(cbuf); |
|
} |
|
|
|
|
|
|
|
void do_htree_dump(int argc, char *argv[]) |
|
{ |
|
ext2_ino_t ino; |
|
struct ext2_inode inode; |
|
blk64_t blk; |
|
char *buf = NULL; |
|
struct ext2_dx_root_info *rootnode; |
|
struct ext2_dx_entry *ent; |
|
errcode_t errcode; |
|
|
|
if (check_fs_open(argv[0])) |
|
return; |
|
|
|
pager = open_pager(); |
|
|
|
if (common_inode_args_process(argc, argv, &ino, 0)) |
|
goto errout; |
|
|
|
if (debugfs_read_inode(ino, &inode, argv[1])) |
|
goto errout; |
|
|
|
if (!LINUX_S_ISDIR(inode.i_mode)) { |
|
com_err(argv[0], 0, "Not a directory"); |
|
goto errout; |
|
} |
|
|
|
if ((inode.i_flags & EXT2_BTREE_FL) == 0) { |
|
com_err(argv[0], 0, "Not a hash-indexed directory"); |
|
goto errout; |
|
} |
|
|
|
buf = malloc(2*current_fs->blocksize); |
|
if (!buf) { |
|
com_err(argv[0], 0, "Couldn't allocate htree buffer"); |
|
goto errout; |
|
} |
|
|
|
errcode = ext2fs_bmap2(current_fs, ino, &inode, buf, 0, 0, 0, &blk); |
|
if (errcode) { |
|
com_err("do_htree_block", errcode, |
|
"while mapping logical block 0\n"); |
|
goto errout; |
|
} |
|
|
|
errcode = io_channel_read_blk64(current_fs->io, blk, |
|
1, buf); |
|
if (errcode) { |
|
com_err(argv[0], errcode, "Error reading root node"); |
|
goto errout; |
|
} |
|
|
|
rootnode = (struct ext2_dx_root_info *) (buf + 24); |
|
|
|
fprintf(pager, "Root node dump:\n"); |
|
fprintf(pager, "\t Reserved zero: %u\n", rootnode->reserved_zero); |
|
fprintf(pager, "\t Hash Version: %d\n", rootnode->hash_version); |
|
fprintf(pager, "\t Info length: %d\n", rootnode->info_length); |
|
fprintf(pager, "\t Indirect levels: %d\n", rootnode->indirect_levels); |
|
fprintf(pager, "\t Flags: %d\n", rootnode->unused_flags); |
|
|
|
ent = (struct ext2_dx_entry *) (buf + 24 + rootnode->info_length); |
|
|
|
htree_dump_int_node(current_fs, ino, &inode, rootnode, ent, |
|
buf + current_fs->blocksize, |
|
rootnode->indirect_levels); |
|
|
|
errout: |
|
free(buf); |
|
close_pager(pager); |
|
} |
|
|
|
/* |
|
* This function prints the hash of a given file. |
|
*/ |
|
void do_dx_hash(int argc, char *argv[]) |
|
{ |
|
ext2_dirhash_t hash, minor_hash; |
|
errcode_t err; |
|
int c; |
|
int hash_version = 0; |
|
__u32 hash_seed[4]; |
|
|
|
hash_seed[0] = hash_seed[1] = hash_seed[2] = hash_seed[3] = 0; |
|
|
|
reset_getopt(); |
|
while ((c = getopt (argc, argv, "h:s:")) != EOF) { |
|
switch (c) { |
|
case 'h': |
|
hash_version = e2p_string2hash(optarg); |
|
if (hash_version < 0) |
|
hash_version = atoi(optarg); |
|
break; |
|
case 's': |
|
if (uuid_parse(optarg, (unsigned char *) hash_seed)) { |
|
fprintf(stderr, "Invalid UUID format: %s\n", |
|
optarg); |
|
return; |
|
} |
|
break; |
|
default: |
|
goto print_usage; |
|
} |
|
} |
|
if (optind != argc-1) { |
|
print_usage: |
|
com_err(argv[0], 0, "usage: dx_hash [-h hash_alg] " |
|
"[-s hash_seed] filename"); |
|
return; |
|
} |
|
err = ext2fs_dirhash(hash_version, argv[optind], strlen(argv[optind]), |
|
hash_seed, &hash, &minor_hash); |
|
if (err) { |
|
com_err(argv[0], err, "while caclulating hash"); |
|
return; |
|
} |
|
printf("Hash of %s is 0x%0x (minor 0x%0x)\n", argv[optind], |
|
hash, minor_hash); |
|
} |
|
|
|
/* |
|
* Search for particular directory entry (useful for debugging very |
|
* large hash tree directories that have lost some blocks from the |
|
* btree index). |
|
*/ |
|
struct process_block_struct { |
|
char *search_name; |
|
char *buf; |
|
int len; |
|
}; |
|
|
|
static int search_dir_block(ext2_filsys fs, blk64_t *blocknr, |
|
e2_blkcnt_t blockcnt, blk64_t ref_blk, |
|
int ref_offset, void *priv_data); |
|
|
|
void do_dirsearch(int argc, char *argv[]) |
|
{ |
|
ext2_ino_t inode; |
|
struct process_block_struct pb; |
|
|
|
if (check_fs_open(argv[0])) |
|
return; |
|
|
|
if (argc != 3) { |
|
com_err(0, 0, "Usage: dirsearch dir filename"); |
|
return; |
|
} |
|
|
|
inode = string_to_inode(argv[1]); |
|
if (!inode) |
|
return; |
|
|
|
pb.buf = malloc(current_fs->blocksize); |
|
if (!pb.buf) { |
|
com_err("dirsearch", 0, "Couldn't allocate buffer"); |
|
return; |
|
} |
|
pb.search_name = argv[2]; |
|
pb.len = strlen(pb.search_name); |
|
|
|
ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY, 0, |
|
search_dir_block, &pb); |
|
|
|
free(pb.buf); |
|
} |
|
|
|
|
|
static int search_dir_block(ext2_filsys fs, blk64_t *blocknr, |
|
e2_blkcnt_t blockcnt, |
|
blk64_t ref_blk EXT2FS_ATTR((unused)), |
|
int ref_offset EXT2FS_ATTR((unused)), |
|
void *priv_data) |
|
{ |
|
struct process_block_struct *p; |
|
struct ext2_dir_entry *dirent; |
|
errcode_t errcode; |
|
unsigned int offset = 0; |
|
unsigned int rec_len; |
|
|
|
if (blockcnt < 0) |
|
return 0; |
|
|
|
p = (struct process_block_struct *) priv_data; |
|
|
|
errcode = io_channel_read_blk64(current_fs->io, *blocknr, 1, p->buf); |
|
if (errcode) { |
|
com_err("search_dir_block", errcode, |
|
"while reading block %lu", (unsigned long) *blocknr); |
|
return BLOCK_ABORT; |
|
} |
|
|
|
while (offset < fs->blocksize) { |
|
dirent = (struct ext2_dir_entry *) (p->buf + offset); |
|
errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); |
|
if (errcode) { |
|
com_err("htree_dump_leaf_inode", errcode, |
|
"while getting rec_len for block %lu", |
|
(unsigned long) *blocknr); |
|
return BLOCK_ABORT; |
|
} |
|
if (dirent->inode && |
|
p->len == ext2fs_dirent_name_len(dirent) && |
|
strncmp(p->search_name, dirent->name, |
|
p->len) == 0) { |
|
printf("Entry found at logical block %lld, " |
|
"phys %llu, offset %u\n", (long long)blockcnt, |
|
*blocknr, offset); |
|
printf("offset %u\n", offset); |
|
return BLOCK_ABORT; |
|
} |
|
offset += rec_len; |
|
} |
|
return 0; |
|
} |
|
|
|
|