486 lines
14 KiB
C
486 lines
14 KiB
C
/*
|
|
* Copyright 1996-2001 Hans Reiser
|
|
*/
|
|
|
|
#include "fsck.h"
|
|
|
|
|
|
/* on pass2 we take leaves which could not be inserted into tree
|
|
during pass1 and insert each item separately. It is possible that
|
|
items of different objects with the same key can be found. We treat
|
|
that in the following way: we put it into tree with new key and
|
|
link it into /lost+found directory with name made of dir,oid. When
|
|
coming item is a directory - we delete object from the tree, put it
|
|
back with different key, link it to /lost+found directory and
|
|
insert directory as it is */
|
|
|
|
/* relocation rules: we have an item (it is taken from "non-insertable"
|
|
leaf). It has original key yet. We check to see if object with this
|
|
key is remapped. Object can be only remapped if it is not a piece
|
|
of directory */
|
|
|
|
|
|
/* in list of this structures we store what has been
|
|
relocated. */
|
|
struct relocated {
|
|
unsigned long old_dir_id;
|
|
unsigned long old_objectid;
|
|
unsigned long new_objectid;
|
|
/*mode_t mode;*/
|
|
struct relocated * next;
|
|
};
|
|
|
|
|
|
/* all relocated files will be linked into lost+found directory at the
|
|
beginning of semantic pass */
|
|
struct relocated * relocated_list;
|
|
|
|
|
|
/* return objectid the object has to be remapped with */
|
|
__u32 objectid_for_relocation (struct key * key)
|
|
{
|
|
struct relocated * cur;
|
|
|
|
cur = relocated_list;
|
|
|
|
while (cur) {
|
|
if (cur->old_dir_id == key->k_dir_id &&
|
|
cur->old_objectid == key->k_objectid)
|
|
/* object is relocated already */
|
|
return cur->new_objectid;
|
|
cur = cur->next;
|
|
}
|
|
|
|
cur = getmem (sizeof (struct relocated));
|
|
cur->old_dir_id = key->k_dir_id;
|
|
cur->old_objectid = key->k_objectid;
|
|
cur->new_objectid = get_unused_objectid (fs);
|
|
cur->next = relocated_list;
|
|
relocated_list = cur;
|
|
fsck_log ("relocation: (%K) is relocated to (%lu, %lu)\n",
|
|
key, key->k_dir_id, cur->new_objectid);
|
|
return cur->new_objectid;
|
|
}
|
|
|
|
|
|
/* this item is in tree. All unformatted pointer are correct. Do not
|
|
check them */
|
|
static void save_item_2 (struct si ** head, struct item_head * ih,
|
|
char * item, __u32 blocknr)
|
|
{
|
|
struct si * si, * cur;
|
|
|
|
si = getmem (sizeof (*si));
|
|
si->si_dnm_data = getmem (ih_item_len(ih));
|
|
/*si->si_blocknr = blocknr;*/
|
|
memcpy (&(si->si_ih), ih, IH_SIZE);
|
|
memcpy (si->si_dnm_data, item, ih_item_len(ih));
|
|
|
|
if (*head == 0)
|
|
*head = si;
|
|
else {
|
|
cur = *head;
|
|
while (cur->si_next)
|
|
cur = cur->si_next;
|
|
cur->si_next = si;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
struct si * save_and_delete_file_item (struct si * si, struct path * path)
|
|
{
|
|
struct buffer_head * bh = PATH_PLAST_BUFFER (path);
|
|
struct item_head * ih = PATH_PITEM_HEAD (path);
|
|
|
|
save_item_2 (&si, ih, B_I_PITEM (bh, ih), bh->b_blocknr);
|
|
|
|
/* delete item temporary - do not free unformatted nodes */
|
|
reiserfsck_delete_item (path, 1/*temporary*/);
|
|
return si;
|
|
}
|
|
|
|
|
|
/* check whether there are any directory items with this key */
|
|
static int should_relocate (struct item_head * ih)
|
|
{
|
|
struct key key;
|
|
struct key * rkey;
|
|
struct path path;
|
|
struct item_head * path_ih;
|
|
|
|
|
|
/* starting with the leftmost item with this key */
|
|
key = ih->ih_key;
|
|
set_type_and_offset (KEY_FORMAT_1, &key, SD_OFFSET, TYPE_STAT_DATA);
|
|
|
|
while (1) {
|
|
usearch_by_key (fs, &key, &path);
|
|
if (get_item_pos (&path) == B_NR_ITEMS (get_bh (&path))) {
|
|
rkey = uget_rkey (&path);
|
|
if (rkey && !not_of_one_file (&key, rkey)) {
|
|
/* file continues in the right neighbor */
|
|
key = *rkey;
|
|
pathrelse (&path);
|
|
continue;
|
|
}
|
|
/* there is no more items with this key */
|
|
pathrelse (&path);
|
|
break;
|
|
}
|
|
|
|
path_ih = get_ih (&path);
|
|
if (not_of_one_file (&key, &(path_ih->ih_key))) {
|
|
/* there are no more item with this key */
|
|
pathrelse (&path);
|
|
break;
|
|
}
|
|
|
|
/* ok, item found, but make sure that it is not a directory one */
|
|
if ((is_stat_data_ih (path_ih) && !not_a_directory (get_item (&path))) ||
|
|
(is_direntry_ih (path_ih))) {
|
|
/* item of directory found. so, we have to relocate the file */
|
|
pathrelse (&path);
|
|
return 1;
|
|
}
|
|
key = path_ih->ih_key;
|
|
set_offset (KEY_FORMAT_1, &key, get_offset (&key) + 1);
|
|
pathrelse (&path);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* delete all items (but directory ones) with the same key 'ih' has
|
|
(including stat data of not a directory) and put them back at the
|
|
other place */
|
|
void relocate_file (struct item_head * ih, int change_ih)
|
|
{
|
|
struct key key;
|
|
struct key * rkey;
|
|
struct path path;
|
|
struct item_head * path_ih;
|
|
struct si * si;
|
|
__u32 new_objectid;
|
|
|
|
|
|
/* starting with the leftmost one - look for all items of file,
|
|
store them and delete */
|
|
key = ih->ih_key;
|
|
set_type_and_offset (KEY_FORMAT_1, &key, SD_OFFSET, TYPE_STAT_DATA);
|
|
|
|
si = 0;
|
|
while (1) {
|
|
usearch_by_key (fs, &key, &path);
|
|
if (get_item_pos (&path) == B_NR_ITEMS (get_bh (&path))) {
|
|
rkey = uget_rkey (&path);
|
|
if (rkey && !not_of_one_file (&key, rkey)) {
|
|
/* file continues in the right neighbor */
|
|
key = *rkey;
|
|
pathrelse (&path);
|
|
continue;
|
|
}
|
|
/* there is no more items with this key */
|
|
pathrelse (&path);
|
|
break;
|
|
}
|
|
|
|
path_ih = get_ih (&path);
|
|
if (not_of_one_file (&key, &(path_ih->ih_key))) {
|
|
/* there are no more item with this key */
|
|
pathrelse (&path);
|
|
break;
|
|
}
|
|
|
|
/* ok, item found, but make sure that it is not a directory one */
|
|
if ((is_stat_data_ih (path_ih) && !not_a_directory (get_item (&path))) ||
|
|
(is_direntry_ih (path_ih))) {
|
|
/* item of directory found. Leave it in the tree */
|
|
key = path_ih->ih_key;
|
|
set_offset (KEY_FORMAT_1, &key, get_offset (&key) + 1);
|
|
pathrelse (&path);
|
|
continue;
|
|
}
|
|
|
|
si = save_and_delete_file_item (si, &path);
|
|
}
|
|
|
|
|
|
if (si || change_ih) {
|
|
int moved_items;
|
|
struct key old, new;
|
|
|
|
/* get new objectid for relocation or get objectid with which file
|
|
was relocated already */
|
|
new_objectid = objectid_for_relocation (&ih->ih_key);
|
|
if (change_ih)
|
|
ih->ih_key.k_objectid = new_objectid;
|
|
|
|
moved_items = 0;
|
|
|
|
/* put all items removed back into tree */
|
|
while (si) {
|
|
/*fsck_log ("relocate_file: move %H to ", &si->si_ih);*/
|
|
old = si->si_ih.ih_key;
|
|
si->si_ih.ih_key.k_objectid = new_objectid;
|
|
new = si->si_ih.ih_key;
|
|
/*fsck_log ("%H\n", &si->si_ih);*/
|
|
insert_item_separately (&(si->si_ih), si->si_dnm_data, 1/*was in tree*/);
|
|
si = remove_saved_item (si);
|
|
moved_items ++;
|
|
}
|
|
if (moved_items)
|
|
fsck_log ("relocate_file: %d items of file %K moved to %K\n",
|
|
moved_items, &old, &new);
|
|
}
|
|
}
|
|
|
|
|
|
/* this works for both new and old stat data */
|
|
#define st_mode(sd) le16_to_cpu(((struct stat_data *)(sd))->sd_mode)
|
|
|
|
#define st_mtime_v1(sd) le32_to_cpu(((struct stat_data_v1 *)(sd))->sd_mtime)
|
|
#define st_mtime_v2(sd) le32_to_cpu(((struct stat_data *)(sd))->sd_mtime)
|
|
|
|
static void overwrite_stat_data (struct item_head * new_ih,
|
|
void * new_item, struct path * path)
|
|
{
|
|
if (stat_data_v1 (new_ih)) {
|
|
if (st_mtime_v1 (new_item) > st_mtime_v1 (get_item (path))) {
|
|
memcpy (get_item (path), new_item, SD_V1_SIZE);
|
|
mark_buffer_dirty (get_bh (path));
|
|
}
|
|
} else {
|
|
if (st_mtime_v2 (new_item) > st_mtime_v2 (get_item (path))) {
|
|
memcpy (get_item (path), new_item, SD_SIZE);
|
|
mark_buffer_dirty (get_bh (path));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/* insert sd item if it does not exist, overwrite it otherwise */
|
|
static void put_sd_into_tree (struct item_head * new_ih, char * new_item)
|
|
{
|
|
struct path path;
|
|
|
|
if (!not_a_directory (new_item)) {
|
|
/* new item is a stat data of a directory. So we have to
|
|
relocate all items which have the same short key and are of
|
|
not a directory */
|
|
relocate_file (new_ih, 0/*do not change new_ih*/);
|
|
} else {
|
|
/* new item is a stat data of something else but directory. If
|
|
there are items of directory - we have to relocate the file */
|
|
if (should_relocate (new_ih))
|
|
relocate_file (new_ih, 1/*change new_ih*/);
|
|
}
|
|
|
|
/* if we will have to insert item into tree - it is ready */
|
|
zero_nlink (new_ih, new_item);
|
|
mark_item_unreachable (new_ih);
|
|
|
|
/* we are sure now that if we are inserting stat data of a
|
|
directory - there are no items with the same key which are not
|
|
items of a directory, and that if we are inserting stat data is
|
|
of not a directory - it either has new key already or there are
|
|
no items with this key which are items of a directory */
|
|
if (usearch_by_key (fs, &(new_ih->ih_key), &path) == ITEM_FOUND) {
|
|
/* this stat data is found */
|
|
if (ih_key_format (get_ih(&path)) != ih_key_format (new_ih)) {
|
|
/* in tree stat data and a new one are of different
|
|
formats */
|
|
fsck_log ("put_sd_into_tree: inserting stat data %K (%M)..",
|
|
&(new_ih->ih_key), st_mode (new_item));
|
|
if (stat_data_v1 (new_ih)) {
|
|
/* sd to be inserted is of V1, where as sd in the tree
|
|
is of V2 */
|
|
fsck_log ("found newer in the tree (%M), skip inserting\n",
|
|
st_mode (get_item (&path)));
|
|
} else {
|
|
/* the stat data in the tree is sd_v1 */
|
|
fsck_log ("older sd (%M) is replaced with it\n",
|
|
st_mode (get_item (&path)));
|
|
reiserfsck_delete_item (&path, 0/*not temporary*/);
|
|
|
|
usearch_by_key (fs, &new_ih->ih_key, &path);
|
|
reiserfsck_insert_item (&path, new_ih, new_item);
|
|
}
|
|
} else {
|
|
/* both stat data are of the same version */
|
|
overwrite_stat_data (new_ih, new_item, &path);
|
|
pathrelse (&path);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* item not found, insert a new one */
|
|
reiserfsck_insert_item (&path, new_ih, new_item);
|
|
}
|
|
|
|
|
|
/* this tries to put each item entry to the tree, if there is no items
|
|
of the directory, insert item containing 1 entry */
|
|
static void put_directory_item_into_tree (struct item_head * comingih, char * item)
|
|
{
|
|
struct reiserfs_de_head * deh;
|
|
int i;
|
|
char * buf;
|
|
char * name;
|
|
int namelen;
|
|
|
|
/* if there are anything ith this key but a directory - move it
|
|
somewhere else */
|
|
relocate_file (comingih, 0/* do not change ih */);
|
|
|
|
deh = (struct reiserfs_de_head *)item;
|
|
|
|
for (i = 0; i < ih_entry_count (comingih); i ++, deh ++) {
|
|
name = name_in_entry (deh, i);
|
|
namelen = name_length (comingih, deh, i);
|
|
|
|
if (!is_properly_hashed (fs, name, namelen, deh_offset (deh)))
|
|
reiserfs_panic ("put_directory_item_into_tree: should be hashed properly ()");
|
|
|
|
asprintf (&buf, "%.*s", namelen, name);
|
|
/* 1 for fsck is important: if there is no any items of this
|
|
directory in the tree yet - new item will be inserted
|
|
marked not reached */
|
|
reiserfs_add_entry (fs, &(comingih->ih_key), buf, (struct key *)&(deh->deh_dir_id), 1/*fsck_need*/);
|
|
free (buf);
|
|
}
|
|
|
|
/* make a directory */
|
|
}
|
|
|
|
|
|
/* relocated files get added into lost+found with slightly different names */
|
|
static void link_one (struct relocated * file)
|
|
{
|
|
char * name;
|
|
struct key obj_key;
|
|
|
|
asprintf (&name, "%lu,%lu", file->old_dir_id, file->new_objectid);
|
|
obj_key.k_dir_id = file->old_dir_id;
|
|
obj_key.k_objectid = file->new_objectid;
|
|
|
|
/* 0 for fsck_need does not mean too much - it would make effect
|
|
if there were no this directory yet. But /lost_found is there
|
|
already */
|
|
reiserfs_add_entry (fs, &lost_found_dir_key, name, &obj_key, 0/*fsck_need*/);
|
|
stats(fs)->relocated ++;
|
|
free (name);
|
|
}
|
|
|
|
|
|
void link_relocated_files (void)
|
|
{
|
|
struct relocated * tmp;
|
|
int count;
|
|
|
|
count = 0;
|
|
while (relocated_list) {
|
|
link_one (relocated_list);
|
|
tmp = relocated_list;
|
|
relocated_list = relocated_list->next;
|
|
freemem (tmp);
|
|
count ++;
|
|
}
|
|
}
|
|
|
|
|
|
void insert_item_separately (struct item_head * ih,
|
|
char * item, int was_in_tree)
|
|
{
|
|
if (ih->ih_key.k_dir_id == ih->ih_key.k_objectid)
|
|
reiserfs_panic ("insert_item_separately: can not insert bad item %H", ih);
|
|
|
|
if (is_stat_data_ih (ih)) {
|
|
put_sd_into_tree (ih, item);
|
|
} else if (is_direntry_ih (ih)) {
|
|
put_directory_item_into_tree (ih, item);
|
|
} else {
|
|
if (should_relocate (ih))
|
|
relocate_file (ih, 1/*change new_ih*/);
|
|
|
|
reiserfsck_file_write (ih, item, was_in_tree);
|
|
}
|
|
}
|
|
|
|
|
|
static void put_items (struct buffer_head * bh)
|
|
{
|
|
int i;
|
|
struct item_head * ih;
|
|
|
|
ih = B_N_PITEM_HEAD (bh, 0);
|
|
for (i = 0; i < B_NR_ITEMS (bh); i ++, ih ++) {
|
|
if (i && bad_pair (fs, bh, i)) {
|
|
/* skip item if it is in wrong order */
|
|
continue;
|
|
}
|
|
insert_item_separately (ih, B_I_PITEM (bh, ih), 0/*was in tree*/);
|
|
}
|
|
}
|
|
|
|
|
|
/* uninsertable blocks are marked by 0s in uninsertable_leaf_bitmap
|
|
during the pass 1. They must be not in the tree */
|
|
void pass_2_take_bad_blocks_put_into_tree (void)
|
|
{
|
|
struct buffer_head * bh;
|
|
unsigned long j;
|
|
unsigned long bb_counter = 0;
|
|
int what_node;
|
|
|
|
|
|
if (!stats(fs)->uninsertable_leaves)
|
|
return;
|
|
|
|
if (fsck_log_file (fs) != stderr)
|
|
fsck_log ("####### Pass 2 #######\n");
|
|
|
|
fsck_progress ("\nPass2:\n");
|
|
|
|
j = 0;
|
|
while (reiserfs_bitmap_find_zero_bit (uninsertable_leaf_bitmap, &j) == 0) {
|
|
bh = bread (fs->s_dev, j, fs->s_blocksize);
|
|
if (bh == 0) {
|
|
fsck_log ("pass_2_take_bad_blocks_put_into_tree: "
|
|
"unable to read %lu block on device 0x%x\n",
|
|
j, fs->s_dev);
|
|
goto next;
|
|
}
|
|
|
|
if (is_block_used (bh->b_blocknr)) {
|
|
fsck_log ("pass_2_take_bad_blocks_put_into_tree: "
|
|
"block %d can not be in tree\n", bh->b_blocknr);
|
|
goto next;
|
|
}
|
|
/* this must be leaf */
|
|
what_node = who_is_this (bh->b_data, fs->s_blocksize);
|
|
if (what_node != THE_LEAF) { // || B_IS_KEYS_LEVEL(bh)) {
|
|
fsck_log ("take_bad_blocks_put_into_tree: buffer (%b %z) must contain leaf\n", bh, bh);
|
|
goto next;
|
|
}
|
|
|
|
/*fsck_log ("block %lu is being inserted\n", bh->b_blocknr);*/
|
|
put_items (bh);
|
|
|
|
print_how_far (&bb_counter, stats(fs)->uninsertable_leaves, 1, fsck_quiet (fs));
|
|
|
|
next:
|
|
brelse (bh);
|
|
j ++;
|
|
}
|
|
|
|
fsck_progress ("\n");
|
|
|
|
|
|
if (bb_counter != stats(fs)->uninsertable_leaves)
|
|
die ("take_bad_blocks_put_into_tree: found bad block %d, must be %d",
|
|
bb_counter, stats(fs)->uninsertable_leaves);
|
|
|
|
}
|