Page MenuHomeClusterLabs Projects

metawalk.c
No OneTemporary

metawalk.c

/*****************************************************************************
*******************************************************************************
**
** Copyright (C) 2005 Red Hat, Inc. All rights reserved.
**
** This copyrighted material is made available to anyone wishing to use,
** modify, copy, or redistribute it subject to the terms and conditions
** of the GNU General Public License v.2.
**
*******************************************************************************
******************************************************************************/
#include <inttypes.h>
#include <linux_endian.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "fsck_incore.h"
#include "fsck.h"
#include "bio.h"
#include "fs_dir.h"
#include "inode.h"
#include "util.h"
#include "hash.h"
#include "metawalk.h"
int check_entries(struct fsck_inode *ip, struct buffer_head *bh, int index,
int type, int *update, uint16_t *count,
struct metawalk_fxns *pass)
{
struct gfs2_leaf *leaf = NULL;
struct gfs2_dirent *dent;
struct gfs2_dirent de, *prev;
int error = 0;
char *bh_end;
char *filename;
int first = 1;
bh_end = BH_DATA(bh) + BH_SIZE(bh);
if(type == DIR_LINEAR) {
dent = (struct gfs2_dirent *)(BH_DATA(bh)
+ sizeof(struct gfs2_dinode));
}
else if (type == DIR_EXHASH) {
dent = (struct gfs2_dirent *)(BH_DATA(bh)
+ sizeof(struct gfs2_leaf));
leaf = (struct gfs2_leaf *)BH_DATA(bh);
log_debug("Checking leaf %"PRIu64"\n", BH_BLKNO(bh));
}
else {
log_err("Invalid directory type %d specified\n", type);
return -1;
}
prev = NULL;
if(!pass->check_dentry) {
return 0;
}
while(1) {
memset(&de, 0, sizeof(struct gfs2_dirent));
gfs2_dirent_in(&de, (char *)dent);
filename = (char *)dent + sizeof(struct gfs2_dirent);
if (!de.de_inum.no_addr){
if(first){
log_debug("First dirent is a sentinel (place holder).\n");
first = 0;
} else {
/* FIXME: Do something about this */
log_err("Directory entry with inode number of zero in leaf %"PRIu64" of directory %"PRIu64"!\n", BH_BLKNO(bh), ip->i_di.di_num.no_addr);
return 1;
}
} else {
error = pass->check_dentry(ip, dent, prev, bh,
filename, update,
count,
pass->private);
if(error < 0) {
stack;
return -1;
}
/*if(error > 0) {
return 1;
}*/
}
if ((char *)dent + de.de_rec_len >= bh_end){
log_debug("Last entry processed.\n");
break;
}
/* If we didn't clear the dentry, or if we did, but it
* was the first dentry, set prev */
if(!error || first) {
prev = dent;
}
first = 0;
dent = (struct gfs2_dirent *)((char *)dent + de.de_rec_len);
}
return 0;
}
/* Checks exthash directory entries */
int check_leaf(struct fsck_inode *ip, int *update, struct metawalk_fxns *pass)
{
int error;
struct gfs2_leaf leaf;
uint64_t leaf_no, old_leaf;
struct buffer_head *lbh;
int index;
struct fsck_sb *sbp = ip->i_sbd;
uint16_t count;
int ref_count = 0, exp_count = 0;
old_leaf = 0;
for(index = 0; index < (1 << ip->i_di.di_depth); index++) {
if(get_leaf_nr(ip, index, &leaf_no)) {
log_err("Unable to get leaf block number in dir %"
PRIu64"\n"
"\tDepth = %u\n"
"\tindex = %u\n",
ip->i_num.no_addr,
ip->i_di.di_depth,
index);
return -1;
}
/* GFS has multiple indirect pointers to the same leaf
* until those extra pointers are needed, so skip the
* dups */
if(old_leaf == leaf_no) {
ref_count++;
continue;
} else {
if(ref_count != exp_count){
log_err("Dir #%"PRIu64" has an incorrect number "
"of pointers to leaf #%"PRIu64"\n"
"\tFound: %u, Expected: %u\n",
ip->i_num.no_addr,
old_leaf,
ref_count,
exp_count);
return 1;
}
ref_count = 1;
}
count = 0;
do {
/* FIXME: Do other checks (see old
* pass3:dir_exhash_scan() */
lbh = NULL;
if(pass->check_leaf) {
error = pass->check_leaf(ip, leaf_no, &lbh,
pass->private);
if(error < 0) {
stack;
relse_buf(sbp, lbh);
return -1;
}
if(error > 0) {
relse_buf(sbp, lbh);
lbh = NULL;
return 1;
}
}
if (!lbh){
if(get_and_read_buf(sbp, leaf_no,
&lbh, 0)){
log_err("Unable to read leaf block #%"
PRIu64" for "
"directory #%"PRIu64".\n",
leaf_no,
ip->i_di.di_num.no_addr);
/* FIXME: should i error out
* if this fails? */
break;
}
}
gfs2_leaf_in(&leaf, BH_DATA(lbh));
exp_count = (1 << (ip->i_di.di_depth - leaf.lf_depth));
log_debug("expected count %u - %u %u\n", exp_count,
ip->i_di.di_depth, leaf.lf_depth);
if(pass->check_dentry &&
S_ISDIR(ip->i_di.di_mode)) {
error = check_entries(ip, lbh, index,
DIR_EXHASH, update,
&count,
pass);
/* Since the buffer possibly got
updated directly, release it now,
and grab it again later if we need
it */
relse_buf(sbp, lbh);
if(error < 0) {
stack;
return -1;
}
if(error > 0) {
return 1;
}
if(update && (count != leaf.lf_entries)) {
if(get_and_read_buf(sbp, leaf_no,
&lbh, 0)){
log_err("Unable to read leaf block #%"
PRIu64" for "
"directory #%"PRIu64".\n",
leaf_no,
ip->i_di.di_num.no_addr);
return -1;
}
gfs2_leaf_in(&leaf, BH_DATA(lbh));
log_err("Leaf(%"PRIu64") entry count in directory %"PRIu64" doesn't match number of entries found - is %u, found %u\n", leaf_no, ip->i_num.no_addr, leaf.lf_entries, count);
if(query(sbp, "Update leaf entry count? (y/n) ")) {
leaf.lf_entries = count;
gfs2_leaf_out(&leaf, BH_DATA(lbh));
write_buf(sbp, lbh, 0);
log_warn("Leaf entry count updated\n");
} else {
log_err("Leaf entry count left in inconsistant state\n");
}
relse_buf(sbp, lbh);
}
/* FIXME: Need to get entry count and
* compare it against
* leaf->lf_entries */
break;
} else {
relse_buf(sbp, lbh);
if(!leaf.lf_next) {
break;
}
leaf_no = leaf.lf_next;
log_debug("Leaf chain detected.\n");
}
} while(1);
old_leaf = leaf_no;
}
return 0;
}
static int check_eattr_entries(struct fsck_inode *ip, struct buffer_head *bh,
struct metawalk_fxns *pass)
{
struct gfs2_ea_header *ea_hdr, *ea_hdr_prev = NULL;
uint64_t *ea_data_ptr = NULL;
int i;
int error = 0;
if(!pass->check_eattr_entry) {
return 0;
}
ea_hdr = (struct gfs2_ea_header *)(BH_DATA(bh) +
sizeof(struct gfs2_meta_header));
while(1){
error = pass->check_eattr_entry(ip, bh, ea_hdr, ea_hdr_prev,
pass->private);
if(error < 0) {
stack;
return -1;
}
if(error == 0) {
if(pass->check_eattr_extentry && ea_hdr->ea_num_ptrs) {
ea_data_ptr = ((uint64_t *)((char *)ea_hdr +
sizeof(struct gfs2_ea_header) +
((ea_hdr->ea_name_len + 7) & ~7)));
/* It is possible when a EA is shrunk
** to have ea_num_ptrs be greater than
** the number required for ** data.
** In this case, the EA ** code leaves
** the blocks ** there for **
** reuse........... */
for(i = 0; i < ea_hdr->ea_num_ptrs; i++){
if(pass->check_eattr_extentry(ip,
ea_data_ptr,
bh, ea_hdr,
ea_hdr_prev,
pass->private)) {
stack;
return -1;
}
ea_data_ptr++;
}
}
}
if(ea_hdr->ea_flags & GFS2_EAFLAG_LAST){
/* FIXME: better equal the end of the block */
break;
}
/* FIXME: be sure this doesn't go beyond the end */
ea_hdr_prev = ea_hdr;
ea_hdr = (struct gfs2_ea_header *)
((char *)(ea_hdr) +
gfs2_32_to_cpu(ea_hdr->ea_rec_len));
}
return 0;
}
/**
* check_leaf_eattr
* @ip: the inode the eattr comes from
* @block: block number of the leaf
*
* Returns: 0 on success, -1 if removal is needed
*/
static int check_leaf_eattr(struct fsck_inode *ip, uint64_t block,
uint64_t parent, struct metawalk_fxns *pass)
{
struct buffer_head *bh = NULL;
int error = 0;
log_debug("Checking EA leaf block #%"PRIu64".\n", block);
if(pass->check_eattr_leaf) {
error = pass->check_eattr_leaf(ip, block, parent,
&bh, pass->private);
if(error < 0) {
stack;
return -1;
}
if(error > 0) {
relse_buf(ip->i_sbd, bh);
return 1;
}
}
check_eattr_entries(ip, bh, pass);
relse_buf(ip->i_sbd, bh);
return 0;
}
/**
* check_indirect_eattr
* @ip: the inode the eattr comes from
* @indirect_block
*
* Returns: 0 on success -1 on error
*/
static int check_indirect_eattr(struct fsck_inode *ip, uint64_t indirect,
struct metawalk_fxns *pass){
int error = 0;
uint64_t *ea_leaf_ptr, *end;
uint64_t block;
struct buffer_head *indirect_buf = NULL;
struct fsck_sb *sdp = ip->i_sbd;
log_debug("Checking EA indirect block #%"PRIu64".\n", indirect);
if (!pass->check_eattr_indir ||
!pass->check_eattr_indir(ip, indirect, ip->i_di.di_num.no_addr,
&indirect_buf, pass->private)) {
ea_leaf_ptr = (uint64_t *)(BH_DATA(indirect_buf)
+ sizeof(struct gfs2_meta_header));
end = ea_leaf_ptr
+ ((sdp->sb.sb_bsize
- sizeof(struct gfs2_meta_header)) / 8);
while(*ea_leaf_ptr && (ea_leaf_ptr < end)){
block = gfs2_64_to_cpu(*ea_leaf_ptr);
/* FIXME: should I call check_leaf_eattr if we
* find a dup? */
error = check_leaf_eattr(ip, block, indirect, pass);
ea_leaf_ptr++;
}
}
relse_buf(sdp, indirect_buf);
return error;
}
/**
* check_inode_eattr - check the EA's for a single inode
* @ip: the inode whose EA to check
*
* Returns: 0 on success, -1 on error
*/
int check_inode_eattr(struct fsck_inode *ip, struct metawalk_fxns *pass)
{
int error = 0;
if(!ip->i_di.di_eattr){
return 0;
}
log_debug("Extended attributes exist for inode #%"PRIu64".\n",
ip->i_num.no_formal_ino);
if(ip->i_di.di_flags & GFS2_DIF_EA_INDIRECT){
if((error = check_indirect_eattr(ip, ip->i_di.di_eattr, pass)))
stack;
} else {
if((error = check_leaf_eattr(ip, ip->i_di.di_eattr,
ip->i_di.di_num.no_addr, pass)))
stack;
}
return error;
}
/**
* build_metalist
* @ip:
* @mlp:
*
*/
static int build_metalist(struct fsck_inode *ip, osi_list_t *mlp,
struct metawalk_fxns *pass)
{
uint32_t height = ip->i_di.di_height;
struct buffer_head *bh, *nbh;
osi_list_t *prev_list, *cur_list, *tmp;
int i, head_size;
uint64_t *ptr, block;
int err;
if(get_and_read_buf(ip->i_sbd, ip->i_di.di_num.no_addr, &bh, 0)) {
stack;
return -1;
}
osi_list_add(&bh->b_list, &mlp[0]);
/* if(<there are no indirect blocks to check>) */
if (height < 2) {
return 0;
}
for (i = 1; i < height; i++){
prev_list = &mlp[i - 1];
cur_list = &mlp[i];
for (tmp = prev_list->next; tmp != prev_list; tmp = tmp->next){
bh = osi_list_entry(tmp, struct buffer_head, b_list);
head_size = (i > 1 ?
sizeof(struct gfs2_meta_header) :
sizeof(struct gfs2_dinode));
for (ptr = (uint64_t *)(bh->b_data + head_size);
(char *)ptr < (bh->b_data + bh->b_size);
ptr++) {
nbh = NULL;
if (!*ptr)
continue;
block = gfs2_64_to_cpu(*ptr);
err = pass->check_metalist(ip, block, &nbh,
pass->private);
if(err < 0) {
stack;
goto fail;
}
if(err > 0) {
log_debug("Skipping block %"PRIu64
"\n", block);
continue;
}
if(!nbh) {
if(get_and_read_buf(ip->i_sbd, block,
&nbh, 0)) {
stack;
goto fail;
}
}
osi_list_add(&nbh->b_list, cur_list);
}
}
}
return 0;
fail:
for (i = 0; i < GFS2_MAX_META_HEIGHT; i++)
{
osi_list_t *list;
list = &mlp[i];
while (!osi_list_empty(list))
{
bh = osi_list_entry(list->next, struct buffer_head, b_list);
osi_list_del(&bh->b_list);
relse_buf(ip->i_sbd, bh);
}
}
return -1;
}
/**
* check_metatree
* @ip:
* @rgd:
*
*/
int check_metatree(struct fsck_inode *ip, struct metawalk_fxns *pass)
{
osi_list_t metalist[GFS2_MAX_META_HEIGHT];
osi_list_t *list, *tmp;
struct buffer_head *bh;
uint64_t block, *ptr;
uint32_t height = ip->i_di.di_height;
int i, head_size;
int update = 0;
int error = 0;
if (!height)
goto end;
for (i = 0; i < GFS2_MAX_META_HEIGHT; i++)
osi_list_init(&metalist[i]);
/* create metalist for each level */
if(build_metalist(ip, &metalist[0], pass)){
stack;
return -1;
}
/* We don't need to record directory blocks - they will be
* recorded later...i think... */
if (S_ISDIR(ip->i_di.di_mode)) {
log_debug("Directory with height > 0 at %"PRIu64"\n",
ip->i_di.di_num.no_addr);
}
/* check data blocks */
list = &metalist[height - 1];
for (tmp = list->next; tmp != list; tmp = tmp->next)
{
bh = osi_list_entry(tmp, struct buffer_head, b_list);
head_size = (height != 1 ? sizeof(struct gfs2_meta_header) : sizeof(struct gfs2_dinode));
ptr = (uint64_t *)(bh->b_data + head_size);
for ( ; (char *)ptr < (bh->b_data + bh->b_size); ptr++)
{
if (!*ptr)
continue;
block = gfs2_64_to_cpu(*ptr);
if(pass->check_data &&
(pass->check_data(ip, block, pass->private) < 0)) {
stack;
return -1;
}
}
}
/* free metalists */
for (i = 0; i < GFS2_MAX_META_HEIGHT; i++)
{
list = &metalist[i];
while (!osi_list_empty(list))
{
bh = osi_list_entry(list->next, struct buffer_head, b_list);
osi_list_del(&bh->b_list);
relse_buf(ip->i_sbd, bh);
}
}
end:
if (S_ISDIR(ip->i_di.di_mode)) {
/* check validity of leaf blocks and leaf chains */
if (ip->i_di.di_flags & GFS2_DIF_EXHASH) {
error = check_leaf(ip, &update, pass);
if(error < 0)
return -1;
if(error > 0)
return 1;
}
}
return 0;
}
/* Checks stuffed inode directories */
int check_linear_dir(struct fsck_inode *ip, struct buffer_head *bh, int *update,
struct metawalk_fxns *pass)
{
int error = 0;
uint16_t count = 0;
error = check_entries(ip, bh, 0, DIR_LINEAR, update, &count, pass);
if(error < 0) {
stack;
return -1;
}
return error;
}
int check_dir(struct fsck_sb *sbp, uint64_t block, struct metawalk_fxns *pass)
{
struct buffer_head *bh;
struct fsck_inode *ip;
int update = 0;
int error = 0;
if(get_and_read_buf(sbp, block, &bh, 0)){
log_err("Unable to retrieve block #%"PRIu64"\n",
block);
block_set(sbp->bl, block, meta_inval);
return -1;
}
if(copyin_inode(sbp, bh, &ip)) {
stack;
relse_buf(sbp, bh);
return -1;
}
if(ip->i_di.di_flags & GFS2_DIF_EXHASH) {
error = check_leaf(ip, &update, pass);
if(error < 0) {
stack;
free_inode(&ip);
relse_buf(sbp, bh);
return -1;
}
}
else {
error = check_linear_dir(ip, bh, &update, pass);
if(error < 0) {
stack;
free_inode(&ip);
relse_buf(sbp, bh);
return -1;
}
}
free_inode(&ip);
relse_buf(sbp, bh);
return error;
}
static int remove_dentry(struct fsck_inode *ip, struct gfs2_dirent *dent,
struct gfs2_dirent *prev_de,
struct buffer_head *bh, char *filename, int *update,
uint16_t *count,
void *private)
{
/* the metawalk_fxn's private field must be set to the dentry
* block we want to clear */
uint64_t *dentryblock = (uint64_t *) private;
struct gfs2_dirent dentry, *de;
memset(&dentry, 0, sizeof(struct gfs2_dirent));
gfs2_dirent_in(&dentry, (char *)dent);
de = &dentry;
if(de->de_inum.no_addr == *dentryblock) {
*update = 1;
if(dirent_del(ip, bh, prev_de, dent)) {
stack;
return -1;
}
}
else {
(*count)++;
*update = 1;
}
return 0;
}
int remove_dentry_from_dir(struct fsck_sb *sbp, uint64_t dir,
uint64_t dentryblock)
{
struct metawalk_fxns remove_dentry_fxns = {0};
struct block_query q;
int error;
log_debug("Removing dentry %"PRIu64" from directory %"PRIu64"\n",
dentryblock, dir);
if(check_range(sbp, dir)) {
log_err("Parent directory out of range\n");
return 1;
}
remove_dentry_fxns.private = &dentryblock;
remove_dentry_fxns.check_dentry = remove_dentry;
if(block_check(sbp->bl, dir, &q)) {
stack;
return -1;
}
if(q.block_type != inode_dir) {
log_info("Parent block is not a directory...ignoring\n");
return 1;
}
/* Need to run check_dir with a private var of dentryblock,
* and fxns that remove that dentry if found */
error = check_dir(sbp, dir, &remove_dentry_fxns);
return error;
}
/* FIXME: These should be merged with the hash routines in inode_hash.c */
static uint32_t dinode_hash(uint64_t block_no)
{
unsigned int h;
h = fsck_hash(&block_no, sizeof (uint64_t));
h &= FSCK_HASH_MASK;
return h;
}
int find_di(struct fsck_sb *sbp, uint64_t childblock, struct dir_info **dip)
{
osi_list_t *bucket = &sbp->dir_hash[dinode_hash(childblock)];
osi_list_t *tmp;
struct dir_info *di = NULL;
osi_list_foreach(tmp, bucket) {
di = osi_list_entry(tmp, struct dir_info, list);
if(di->dinode == childblock) {
*dip = di;
return 0;
}
}
*dip = NULL;
return -1;
}
int dinode_hash_insert(osi_list_t *buckets, uint64_t key, struct dir_info *di)
{
osi_list_t *tmp;
osi_list_t *bucket = &buckets[dinode_hash(key)];
struct dir_info *dtmp = NULL;
if(osi_list_empty(bucket)) {
osi_list_add(&di->list, bucket);
return 0;
}
osi_list_foreach(tmp, bucket) {
dtmp = osi_list_entry(tmp, struct dir_info, list);
if(dtmp->dinode < key) {
continue;
}
else {
osi_list_add_prev(&di->list, tmp);
return 0;
}
}
osi_list_add_prev(&di->list, bucket);
return 0;
}
int dinode_hash_remove(osi_list_t *buckets, uint64_t key)
{
osi_list_t *tmp;
osi_list_t *bucket = &buckets[dinode_hash(key)];
struct dir_info *dtmp = NULL;
if(osi_list_empty(bucket)) {
return -1;
}
osi_list_foreach(tmp, bucket) {
dtmp = osi_list_entry(tmp, struct dir_info, list);
if(dtmp->dinode == key) {
osi_list_del(tmp);
return 0;
}
}
return -1;
}

File Metadata

Mime Type
text/x-c
Expires
Tue, Feb 25, 6:40 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1464931
Default Alt Text
metawalk.c (17 KB)

Event Timeline