/*
 * Copyright 1996-2004 by Hans Reiser, licensing governed by
 * reiserfsprogs/README
 */

#include "fsck.h"

int screen_width;
static int screen_curr_pos;
char *screen_savebuffer;
int screen_savebuffer_len;

#define MIN(a, b) (((a)>(b))?(b):(a))

/* semantic pass progress */
void print_name(char *name, int len)
{
	int i;

	if (fsck_quiet(fs))
		return;

	if (len + screen_curr_pos + 1 > screen_savebuffer_len) {
		char *t;

		t = expandmem(screen_savebuffer, screen_savebuffer_len + 1,
			      len + screen_curr_pos - screen_savebuffer_len +
			      1);
		if (!t) {
			fsck_progress("\nOut of memory\n");
			return;	// ????
		}
		screen_savebuffer = t;
		screen_savebuffer_len = len + screen_curr_pos + 1;
	}
	strcat(screen_savebuffer, "/");
	strncat(screen_savebuffer, name, len);
	i = screen_curr_pos;
	screen_curr_pos += len + 1;
	for (; i < screen_curr_pos; i++)
		if (screen_savebuffer[i] < 32)
			screen_savebuffer[i] = '?';
	screen_savebuffer[screen_curr_pos] = 0;

	if (screen_width < screen_curr_pos) {
		printf("\r... %.*s", screen_width - 4,
		       screen_savebuffer + (screen_curr_pos -
					    (screen_width - 4)));
	} else
		printf("/%.*s", len, screen_savebuffer + screen_curr_pos - len);

	fflush(stdout);
}

void erase_name(int len)
{
	int i;

	if (fsck_quiet(fs))
		return;

	if (screen_curr_pos < screen_width) {

		screen_curr_pos -= len + 1;
		screen_savebuffer[screen_curr_pos] = 0;

		for (i = 0; i <= len; i++)
			printf("\b");
		for (i = 0; i <= len + 1; i++)
			printf(" ");
		for (i = 0; i <= len + 1; i++)
			printf("\b");
	} else {
		screen_curr_pos -= len + 1;
		if (screen_curr_pos < 0)
			die("%s: Get out of buffer's data!\n", __FUNCTION__);

		screen_savebuffer[screen_curr_pos] = 0;
		printf("\r");
		if (screen_curr_pos >= screen_width) {
			int t_preface = MIN(screen_curr_pos - screen_width, 4);
			printf("%.*s%.*s", t_preface, "... ",
			       screen_width - t_preface - 1,
			       screen_savebuffer + (screen_curr_pos -
						    (screen_width -
						     t_preface)) + 1);
		} else {
			int space_used = printf("%s", screen_savebuffer);
			for (i = space_used; i < screen_width; i++)
				printf(" ");
			for (i = space_used; i < screen_width; i++)
				printf("\b");
		}
	}

	fflush(stdout);
}

/* *size is "real" file size, sd_size - size from stat data */
int wrong_st_size(const struct reiserfs_key *key,
		  unsigned long long max_file_size,
		  int blocksize, __u64 * size, __u64 sd_size, int type)
{
	if (sd_size <= max_file_size) {
		if (sd_size == *size)
			return 0;

		if (type == TYPE_DIRENTRY) {
			/* directory size must match to the sum of length of its entries */
			fsck_log
			    ("vpf-10650: The directory %K has the wrong size in the StatData "
			     "(%Lu)%s(%Lu)\n", key, sd_size,
			     fsck_mode(fs) ==
			     FSCK_CHECK ? ", should be " : " - corrected to ",
			     *size);
			return 1;
		}

		if (sd_size > *size) {
			/* size in stat data can be bigger than size calculated by items */
			if (fsck_adjust_file_size(fs) || type == TYPE_SYMLINK) {
				/* but it -o is given - fix that */
				fsck_log
				    ("vpf-10660: The file %K has too big size in the StatData "
				     "(%Lu)%s(%Lu)\n", key, sd_size,
				     fsck_mode(fs) ==
				     FSCK_CHECK ? ", should be " :
				     " - corrected to ", *size);
				sem_pass_stat(fs)->fixed_sizes++;
				return 1;
			}
			*size = sd_size;
			return 0;
		}

		if (!(*size % blocksize)) {
			/* last item is indirect */
			if (((sd_size & ~(blocksize - 1)) ==
			     (*size - blocksize)) && sd_size % blocksize) {
				/* size in stat data is correct */
				*size = sd_size;
				return 0;
			}
		} else {
			/* last item is a direct one */
			if (!(*size % 8)) {
				if (((sd_size & ~7) == (*size - 8))
				    && sd_size % 8) {
					/* size in stat data is correct */
					*size = sd_size;
					return 0;
				}
			}
		}
	}

	fsck_log
	    ("vpf-10670: The file %K has the wrong size in the StatData (%Lu)%s(%Lu)\n",
	     key, sd_size,
	     fsck_mode(fs) == FSCK_CHECK ? ", should be " : " - corrected to ",
	     *size);
	sem_pass_stat(fs)->fixed_sizes++;
	return 1;
}

/* sd_blocks is 32 bit only */
/* old stat data shares sd_block and sd_dev - do not wipe sd_rdev out */
/* we should fix it as following:
|------------------------------------------------------------------|
|                    |        3.6                   |       3.5    |
|---------------------------------------------------|              |
|                    |  blocks |  r_dev|generation  | blocks/r_dev |
|------------------------------------------------------------------|
|   fifo, sockets    |    0    |    generation      |      0       |
|   chr/blk_dev      |    0    |     maj:min        |   maj:min    |
|   file, dir, link  |  blocks |    generation      |   blocks     |
|------------------------------------------------------------------|
*/
int wrong_st_blocks(const struct reiserfs_key *key, __u32 * blocks,
		    __u32 sd_blocks, __u16 mode, int new_format)
{
	int ret = 0;

	if (S_ISREG(mode) || S_ISLNK(mode) || S_ISDIR(mode)) {
		if ((!S_ISLNK(mode) && *blocks != sd_blocks) ||
		    (S_ISLNK(mode) && *blocks != sd_blocks
		     && (ROUND_UP(*blocks) != sd_blocks))) {
			fsck_log
			    ("vpf-10680: The %s %K has the wrong block count in the StatData "
			     "(%u)%s(%u)\n",
			     S_ISDIR(mode) ? "directory" : S_ISREG(mode) ?
			     "file" : "link", key, sd_blocks,
			     fsck_mode(fs) ==
			     FSCK_CHECK ? ", should be " : " - corrected to ",
			     *blocks);
			ret = 1;
		}
	} else if (new_format || (S_ISFIFO(mode) || S_ISSOCK(mode))) {
		if (sd_blocks != 0) {
			fsck_log
			    ("vpf-10690: The object %K has the wrong block count in the StatData "
			     "(%u)%s(%u)\n", key, sd_blocks,
			     fsck_mode(fs) ==
			     FSCK_CHECK ? ", should be " : " - corrected to ",
			     0);
			*blocks = 0;
			ret = 1;
		}
	}

	return ret;
}

/*
int wrong_st_rdev (struct reiserfs_key *key, __u32 * sd_rdev, __u16 mode, int new_format)
{
    int ret = 0;

    if (!new_format)
	return 0;

    if (!S_ISCHR (mode) && !S_ISBLK (mode)) {
	if (*sd_rdev != 0) {
	    fsck_log ("%s %K has wrong sd_rdev %u, has to be 0\n",
		S_ISDIR (mode) ? "dir" : "file", key, *sd_rdev);
	    *sd_rdev = 0;
	    ret = 1;
	}
    }
    return ret;
}
*/
/* only regular files and symlinks may have items but stat
   data. Symlink should have body */
int wrong_mode(const struct reiserfs_key *key, __u16 * mode, __u64 real_size,
	       int symlink)
{
	int retval = 0;
	if (S_ISLNK(*mode) && !symlink) {
		fsck_log
		    ("The file %K (%M) is too big to be the symlink%s regfile\n",
		     key, *mode,
		     fsck_mode(fs) ==
		     FSCK_CHECK ? ", should be the" : " - corrected " "to the");
		*mode &= ~S_IFMT;
		*mode |= S_IFREG;
		retval = 1;
	}

	if (ftypelet(*mode) != '?') {
		/* mode looks reasonable */
		if (S_ISREG(*mode) || S_ISLNK(*mode))
			return retval;

		/* device, pipe, socket have no items */
		if (!real_size)
			return retval;
	}
	/* there are items, so change file mode to regular file. Otherwise
	   - file bodies do not get deleted */
	if (fsck_mode(fs) == FSCK_CHECK) {
		fsck_log("The object %K has wrong mode (%M)\n", key, *mode);
	} else {
		fsck_log
		    ("The object %K has wrong mode (%M) - corrected to %M\n",
		     key, *mode, (S_IFREG | 0600));
	}
	*mode = (S_IFREG | 0600);
	return 1;
}

/* key is a key of last file item */
int wrong_first_direct_byte(const struct reiserfs_key *key, int blocksize,
			    __u32 * first_direct_byte,
			    __u32 sd_first_direct_byte, __u32 size)
{
	if (!size || is_indirect_key(key)) {
		/* there is no direct item */
		*first_direct_byte = NO_BYTES_IN_DIRECT_ITEM;
		if (sd_first_direct_byte != NO_BYTES_IN_DIRECT_ITEM) {
			if (fsck_mode(fs) == FSCK_CHECK) {
				fsck_log
				    ("The file %K: The wrong info about the tail in the StatData, "
				     "first_direct_byte (%d) - should be (%d)\n",
				     key, sd_first_direct_byte,
				     *first_direct_byte);
			} else {
				fsck_log
				    ("The file %K: The wrong info about the tail in the StatData, "
				     "first_direct_byte (%d) - corrected to (%d)\n",
				     key, sd_first_direct_byte,
				     *first_direct_byte);
			}
			return 1;
		}
		return 0;
	}

	/* there is direct item */
	*first_direct_byte = (get_offset(key) & ~(blocksize - 1)) + 1;
	if (*first_direct_byte != sd_first_direct_byte) {
		if (fsck_mode(fs) == FSCK_CHECK) {
			fsck_log
			    ("The file %K: The wrong info about the tail in the StatData, "
			     "first_direct_byte (%d) - should be (%d)\n", key,
			     sd_first_direct_byte, *first_direct_byte);
		} else {
			fsck_log
			    ("The file %K: The wrong info about the tail in the StatData, "
			     "first_direct_byte (%d) - corrected to (%d)\n",
			     key, sd_first_direct_byte, *first_direct_byte);
		}

		return 1;
	}
	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_dir(struct item_head *ih, int change_ih)
{
	struct reiserfs_key key;
	struct reiserfs_key *rkey;
	struct reiserfs_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 = NULL;
	while (1) {
		reiserfs_search_by_key_4(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 of a directory */
			pathrelse(&path);
			break;
		}

		if (is_stat_data_ih(tp_item_head(&path)))
			fix_obviously_wrong_sd_mode(&path);

		path_ih = tp_item_head(&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(tp_item_body(&path)))
		    || is_direct_ih(path_ih) || is_indirect_ih(path_ih)) {
			/* item of not a directory found. Leave it in the
			   tree. FIXME: should not happen */
			key = path_ih->ih_key;
			set_offset(KEY_FORMAT_1, &key, get_offset(&key) + 1);
			pathrelse(&path);
			continue;
		}

		/* directory stat data ro directory item */
		si = save_and_delete_file_item(si, &path);
	}

	if (!si) {
		fsck_log
		    ("relocate_dir: WARNING: No one item of the directory %K found\n",
		     &key);
		return;
	}

	/* get new objectid for relocation or get objectid with which file
	   was relocated already */
	new_objectid = objectid_for_relocation(&ih->ih_key);
	set_key_objectid(&ih->ih_key, new_objectid);

	/* put all items removed back into tree */
	while (si) {
		fsck_log("relocate_dir: Moving %k to ", &si->si_ih.ih_key);
		set_key_objectid(&si->si_ih.ih_key, new_objectid);
		fsck_log("%k\n", &si->si_ih.ih_key);
		if (get_offset(&(si->si_ih.ih_key)) == DOT_OFFSET) {
			/* fix "." entry to point to a directtory properly */
			struct reiserfs_de_head *deh;

			deh = (struct reiserfs_de_head *)si->si_dnm_data;
			set_deh_objectid(deh, new_objectid);
		}
		insert_item_separately(&(si->si_ih), si->si_dnm_data,
				       1 /*was in tree */ );
		si = remove_saved_item(si);
	}
}

/* path is path to stat data. If file will be relocated - new_ih will contain
   a key file was relocated with */
int rebuild_check_regular_file(struct reiserfs_path *path, void *sd,
			       struct item_head *new_ih)
{
	int is_new_file;
//    struct reiserfs_key sd_key;
	__u16 mode;
	__u32 nlink;
	__u64 real_size, saved_size;
	__u32 blocks, saved_blocks;	/* proper values and value in stat data */
	__u32 first_direct_byte, saved_first_direct_byte;

	struct buffer_head *bh;
	struct item_head *ih, sd_ih;
	int fix_sd;
	int symlnk = 0;
	int retval;

	retval = OK;

	/* stat data of a file */
	ih = tp_item_head(path);
	bh = get_bh(path);

	if (new_ih) {
		/* this objectid is used already */
		*new_ih = *ih;
		pathrelse(path);
		rewrite_file(new_ih, 1, 1);
		linked_already(&new_ih->ih_key);
		sem_pass_stat(fs)->oid_sharing_files_relocated++;
		retval = RELOCATED;
		if (reiserfs_search_by_key_4(fs, &(new_ih->ih_key), path) ==
		    ITEM_NOT_FOUND)
			reiserfs_panic
			    ("%s: Could not find the StatData of the relocated file %k",
			     __FUNCTION__, &(new_ih->ih_key));
		/* stat data is marked unreachable again due to relocation, fix that */
		ih = tp_item_head(path);
		bh = get_bh(path);
		mark_item_reachable(ih, bh);
		sd = tp_item_body(path);

	}

	id_map_mark(semantic_id_map(fs), get_key_objectid(&ih->ih_key));

	/* check and set nlink first */
	get_sd_nlink(ih, sd, &nlink);
	nlink++;
	set_sd_nlink(ih, sd, &nlink);
	mark_buffer_dirty(bh);

	if (nlink > 1)
		return retval;

	/* firts name of a file found */
	if (get_ih_item_len(ih) == SD_SIZE)
		is_new_file = 1;
	else
		is_new_file = 0;

	get_sd_mode(ih, sd, &mode);
	get_sd_size(ih, sd, &saved_size);
	get_sd_blocks(ih, sd, &saved_blocks);
	if (!is_new_file)
		get_sd_first_direct_byte(ih, sd, &saved_first_direct_byte);

	/* we met this file first time */
	if (S_ISREG(mode)) {
		sem_pass_stat(fs)->regular_files++;
	} else if (S_ISLNK(mode)) {
		symlnk = 1;
		sem_pass_stat(fs)->symlinks++;
	} else {
		sem_pass_stat(fs)->others++;
	}

	sd_ih = *ih;
//    sd_key = sd_ih.ih_key;
	pathrelse(path);

	if (are_file_items_correct
	    (&sd_ih, sd, &real_size, &blocks, 1 /*mark items reachable */ ,
	     &symlnk) != 1) {
		/* unpassed items will be deleted in pass 4 as they left unaccessed */
		sem_pass_stat(fs)->broken_files++;
	}

	fix_sd = 0;

	fix_sd +=
	    wrong_mode( /*&sd_key, */ &sd_ih.ih_key, &mode, real_size, symlnk);

	if (!is_new_file)
		fix_sd +=
		    wrong_first_direct_byte(&sd_ih.ih_key, fs->fs_blocksize,
					    &first_direct_byte,
					    saved_first_direct_byte, real_size);

	fix_sd += wrong_st_size( /*&sd_key, */ &sd_ih.ih_key,
				is_new_file ? MAX_FILE_SIZE_V2 :
				MAX_FILE_SIZE_V1, fs->fs_blocksize, &real_size,
				saved_size, symlnk ? TYPE_SYMLINK : 0);

	fix_sd +=
	    wrong_st_blocks(&sd_ih.ih_key, &blocks, saved_blocks, mode,
			    is_new_file);

	if (fix_sd) {
		/* find stat data and correct it */
		set_type_and_offset(KEY_FORMAT_1, &sd_ih.ih_key, SD_OFFSET,
				    TYPE_STAT_DATA);
		if (reiserfs_search_by_key_4(fs, &sd_ih.ih_key, path) !=
		    ITEM_FOUND)
			reiserfs_panic
			    ("%s: The StatData of the file %k could not be found",
			     __FUNCTION__, &sd_ih.ih_key);

		bh = get_bh(path);
		ih = tp_item_head(path);
		sd = tp_item_body(path);
		set_sd_size(ih, sd, &real_size);
		set_sd_blocks(ih, sd, &blocks);
		set_sd_mode(ih, sd, &mode);
		if (!is_new_file)
			set_sd_first_direct_byte(ih, sd, &first_direct_byte);
		mark_buffer_dirty(bh);
	}

	return retval;
}

static int is_rootdir_key(const struct reiserfs_key *key)
{
	if (comp_keys(key, &root_dir_key))
		return 0;
	return 1;
}

/* returns buffer, containing found directory item.*/
static char *get_next_directory_item(struct reiserfs_key *key,	/* on return this will
								   contain key of next item
								   in the tree */
				     const struct reiserfs_key *parent,
				     struct item_head *ih,	/*not in tree */
				     __u32 * pos_in_item)
{
	INITIALIZE_REISERFS_PATH(path);
	char *dir_item;
	struct reiserfs_key *rdkey;
	struct buffer_head *bh;
	struct reiserfs_de_head *deh;
	int i;
	int retval;

	if ((retval =
	     reiserfs_search_by_entry_key(fs, key, &path)) != POSITION_FOUND)
		reiserfs_panic
		    ("get_next_directory_item: The current directory %k cannot be "
		     "found", key);

	/* leaf containing directory item */
	bh = PATH_PLAST_BUFFER(&path);
	*pos_in_item = path.pos_in_item;
	*ih = *tp_item_head(&path);
	deh = B_I_DEH(bh, ih);

	/* make sure, that ".." exists as well */
	if (get_offset(key) == DOT_OFFSET) {
		if (get_ih_entry_count(ih) < 2 ||
		    name_in_entry_length(ih, deh + 1, 1) != 2 ||
		    strncmp(name_in_entry(deh + 1, 1), "..", 2)) {
			fsck_log
			    ("get_next_directory_item: The entry \"..\" cannot be found in %k\n",
			     &ih->ih_key);
			pathrelse(&path);
			return NULL;
		}
	}

	/* mark hidden entries as visible, set "." and ".." correctly */
	deh += *pos_in_item;
	for (i = *pos_in_item; i < get_ih_entry_count(ih); i++, deh++) {
		int namelen;
		char *name;

		name = name_in_entry(deh, i);
		namelen = name_in_entry_length(ih, deh, i);
		if (de_hidden(deh))
			reiserfs_panic
			    ("get_next_directory_item: item %k: hidden entry %d \'%.*s\'\n",
			     key, i, namelen, name);

		if (get_deh_offset(deh) == DOT_OFFSET) {
			if (not_of_one_file(&(deh->deh2_dir_id), key)) {
				/* "." must point to the directory it is in */

				//deh->deh_objectid != REISERFS_ROOT_PARENT_OBJECTID)/*????*/ {
				fsck_log
				    ("get_next_directory_item: The entry \".\" of the directory %K pointes to %K, instead of %K",
				     key,
				     (struct reiserfs_key
				      *)(&(deh->deh2_dir_id)), key);
				set_deh_dirid(deh, get_key_dirid(key));
				set_deh_objectid(deh, get_key_objectid(key));
				mark_buffer_dirty(bh);
				fsck_log(" - corrected\n");
			}
		}

		if (get_deh_offset(deh) == DOT_DOT_OFFSET) {
			/* set ".." so that it points to the correct parent directory */
			if (comp_short_keys(&(deh->deh2_dir_id), parent)) {
				fsck_log
				    ("get_next_directory_item: The entry \"..\" of the directory %K pointes to %K, instead of %K",
				     key,
				     (struct reiserfs_key
				      *)(&(deh->deh2_dir_id)), parent);
				set_deh_dirid(deh, get_key_dirid(parent));
				set_deh_objectid(deh, get_key_objectid(parent));
				mark_buffer_dirty(bh);
				fsck_log(" - corrected\n");
			}
		}
	}

	/* copy directory item to the temporary buffer */
	dir_item = getmem(get_ih_item_len(ih));
	memcpy(dir_item, ih_item_body(bh, ih), get_ih_item_len(ih));

	/* next item key */
	if (PATH_LAST_POSITION(&path) == (B_NR_ITEMS(bh) - 1) &&
	    (rdkey = uget_rkey(&path)))
		copy_key(key, rdkey);
	else {
		set_key_dirid(key, 0);
		set_key_objectid(key, 0);
	}

	if (fsck_mode(fs) == FSCK_REBUILD)
		mark_item_reachable(tp_item_head(&path), bh);
	pathrelse(&path);

	return dir_item;
}

// get key of an object pointed by direntry and the key of the entry itself
void get_object_key(struct reiserfs_de_head *deh, struct reiserfs_key *key,
		    struct reiserfs_key *entry_key, struct item_head *ih)
{
	/* entry points to this key */
	set_key_dirid(key, get_deh_dirid(deh));
	set_key_objectid(key, get_deh_objectid(deh));
	set_key_offset_v1(key, SD_OFFSET);
	set_key_uniqueness(key, 0);

	/* key of entry */
	set_key_dirid(entry_key, get_key_dirid(&ih->ih_key));
	set_key_objectid(entry_key, get_key_objectid(&ih->ih_key));
	set_key_offset_v1(entry_key, get_deh_offset(deh));
	set_key_uniqueness(entry_key, DIRENTRY_UNIQUENESS);
}

int fix_obviously_wrong_sd_mode(struct reiserfs_path *path)
{
	struct reiserfs_key *next_key = NULL;
	__u16 mode;
	int retval = 0;

	next_key = reiserfs_next_key(path);

	if (!next_key || not_of_one_file(next_key, &tp_item_head(path)->ih_key))
		return 0;

	/* next item exists and of the same file. Fix the SD mode */

	if (not_a_directory(tp_item_body(path)) && is_direntry_key(next_key)) {
		/* make SD mode SD of dir */
		get_sd_mode(tp_item_head(path), tp_item_body(path), &mode);
		fsck_log("The directory %K had wrong mode %M",
			 &tp_item_head(path)->ih_key, mode);

		if (fsck_mode(fs) != FSCK_CHECK) {
			mode &= ~S_IFMT;
			mode |= S_IFDIR;
			fsck_log(" - corrected to %M\n", mode);
			set_sd_mode(tp_item_head(path), tp_item_body(path), &mode);
			mark_buffer_dirty(get_bh(path));
		} else {
			fsck_log("\n");
			retval = 1;
		}
	} else if (!not_a_directory(tp_item_body(path))
		   && !is_direntry_key(next_key)) {
		/* make SD mode SD of regular file */
		get_sd_mode(tp_item_head(path), tp_item_body(path), &mode);
		fsck_log("The file %K had wrong mode %M", &tp_item_head(path)->ih_key,
			 mode);
		if (fsck_mode(fs) != FSCK_CHECK) {
			mode &= ~S_IFMT;
			mode |= S_IFREG;
			fsck_log(" - corrected to %M\n", mode);
			set_sd_mode(tp_item_head(path), tp_item_body(path), &mode);
			mark_buffer_dirty(get_bh(path));
		} else {
			fsck_log("\n");
			retval = 1;
		}

	}

	return retval;
}

/* check recursively the semantic tree. Returns OK if entry points to good
   object, STAT_DATA_NOT_FOUND if stat data was not found or RELOCATED when
   file was relocated because its objectid was already marked as used by
   another file */
int rebuild_semantic_pass(struct reiserfs_key *key,
			  const struct reiserfs_key *parent,
			  int dot_dot, struct item_head *new_ih)
{
	struct reiserfs_path path;
	void *sd;
	int is_new_dir;
	__u32 nlink;
	struct buffer_head *bh;
	struct item_head *ih;
	int retval, retval1;
	char *dir_item;
	__u32 pos_in_item;
	struct item_head tmp_ih;
	struct reiserfs_key item_key, entry_key, object_key;
	__u64 dir_size;
	__u32 blocks;
	__u64 saved_size;
	__u32 saved_blocks;
	int fix_sd;
	int relocate;
	int dir_format = 0;
	__u16 mode;

	retval = OK;

start_again:			/* when directory was relocated */

	if (!KEY_IS_STAT_DATA_KEY(key))
		reiserfs_panic
		    ("rebuild_semantic_pass: The key %k must be key of a StatData",
		     key);

	/* look for stat data of an object */
	if (reiserfs_search_by_key_4(fs, key, &path) == ITEM_NOT_FOUND) {
		pathrelse(&path);
		if (is_rootdir_key(key))
			/* root directory has to exist at this point */
			reiserfs_panic
			    ("rebuild_semantic_pass: The root directory StatData was not found");

		return STAT_DATA_NOT_FOUND;
	}

	/* stat data has been found */
	bh = get_bh(&path);
	ih = tp_item_head(&path);
	sd = tp_item_body(&path);

	/* */
	get_sd_nlink(ih, sd, &nlink);

	relocate = 0;
	if (!nlink) {
		/* we reached the stat data for the first time */
		if (id_map_mark
		    (semantic_id_map(fs), get_key_objectid(&ih->ih_key))) {
			/* calculate number of found files/dirs who are using objectid
			   which is used by another file */
			sem_pass_stat(fs)->oid_sharing++;
			if (1 /*fsck_adjust_file_size (fs) */ )
				/* this works for files only */
				relocate = 1;
		}

		mark_item_reachable(ih, bh);
	}

	fix_obviously_wrong_sd_mode(&path);

	if (not_a_directory(sd)) {
		retval =
		    rebuild_check_regular_file(&path, sd,
					       relocate ? new_ih : NULL);
		pathrelse(&path);
		return retval;
	}

	if (relocate) {
		if (!new_ih)
			reiserfs_panic
			    ("rebuild_semantic_pass: Memory is not prepared for relocation of %K",
			     &ih->ih_key);
		*new_ih = *ih;
		pathrelse(&path);
		sem_pass_stat(fs)->oid_sharing_dirs_relocated++;
		relocate_dir(new_ih, 1);
		linked_already(&new_ih->ih_key);
		*key = new_ih->ih_key;
		retval = RELOCATED;

		goto start_again;
	}

	/* it looks like stat data of a directory found */
	if (nlink) {
		/* we saw this directory already */
		if (!dot_dot) {
			/* this name is not ".."  - and hard links are not allowed on
			   directories */
			pathrelse(&path);
			return STAT_DATA_NOT_FOUND;
		} else {
			/* ".." found */
			nlink++;
			set_sd_nlink(ih, sd, &nlink);
			mark_buffer_dirty(bh);
			pathrelse(&path);
			return OK;
		}
	}

	/* we see the directory first time */
	sem_pass_stat(fs)->directories++;
	nlink = 2;
	if (get_key_objectid(key) == REISERFS_ROOT_OBJECTID)
		nlink++;
	set_sd_nlink(ih, sd, &nlink);
	mark_buffer_dirty(bh);

	if (get_ih_item_len(ih) == SD_SIZE)
		is_new_dir = 1;
	else
		is_new_dir = 0;

/*
    {
	struct reiserfs_key *tmp;

	// check next item: if it is not a directory item of the same file
	// therefore sd_mode is corrupted so we just reset it to regular file
	// mode
	tmp = reiserfs_next_key (&path);
	if (tmp && !not_of_one_file (tmp, key) && !is_direntry_key (tmp)) {
	    __u16 mode;

	    get_sd_mode (ih, sd, &mode);
	    fsck_log ("file %K had broken mode %M, ", key, mode);
	    mode &= ~S_IFMT;
	    mode |= S_IFREG;
	    fsck_log ("fixed to %M\n", mode);
	    set_sd_mode (ih, sd, &mode);

	    nlink = 0;
	    set_sd_nlink (ih, sd, &nlink);
	
	    retval = rebuild_check_regular_file (&path, sd, 0); //no relocate
	    pathrelse (&path);
	    return retval;
	}
    }
*/

	dir_format =
	    (get_ih_item_len(tp_item_head(&path)) ==
	     SD_SIZE) ? KEY_FORMAT_2 : KEY_FORMAT_1;
	/* save stat data's size and st_blocks */
	get_sd_size(ih, sd, &saved_size);
	get_sd_blocks(ih, sd, &saved_blocks);
	get_sd_mode(ih, sd, &mode);

	/* release path pointing to stat data */
	pathrelse(&path);

	/* make sure that "." and ".." exist */
	reiserfs_add_entry(fs, key, ".", name_length(".", dir_format), key,
			   1 << IH_Unreachable);
	reiserfs_add_entry(fs, key, "..", name_length("..", dir_format), parent,
			   1 << IH_Unreachable);

	set_key_dirid(&item_key, get_key_dirid(key));
	set_key_objectid(&item_key, get_key_objectid(key));
	set_key_offset_v1(&item_key, DOT_OFFSET);
	set_key_uniqueness(&item_key, DIRENTRY_UNIQUENESS);

	dir_size = 0;
	while ((dir_item =
		get_next_directory_item(&item_key, parent, &tmp_ih,
					&pos_in_item)) != NULL) {
		/* dir_item is copy of the item in separately allocated memory,
		   item_key is a key of next item in the tree */
		int i;
		char *name = NULL;
		int namelen, entry_len;
		struct reiserfs_de_head *deh =
		    (struct reiserfs_de_head *)dir_item + pos_in_item;

		for (i = pos_in_item; i < get_ih_entry_count(&tmp_ih);
		     i++, deh++) {
			struct item_head relocated_ih;

			if (name) {
				free(name);
				name = NULL;
			}

			namelen = name_in_entry_length(&tmp_ih, deh, i);
			asprintf(&name, "%.*s", namelen, name_in_entry(deh, i));

			entry_len = entry_length(&tmp_ih, deh, i);

			get_object_key(deh, &object_key, &entry_key, &tmp_ih);

			if ((dir_format == KEY_FORMAT_2)
			    && (entry_len % 8 != 0)) {
				/* not alighed directory of new format - delete it */
				fsck_log
				    ("Entry %K (\"%.*s\") in the directory %K is not formated properly - fixed.\n",
				     (struct reiserfs_key *)&(deh->deh2_dir_id),
				     namelen, name, &tmp_ih.ih_key);
				reiserfs_remove_entry(fs, &entry_key);
				entry_len = name_length(name, dir_format);
				reiserfs_add_entry(fs, key, name, entry_len,
						   (struct reiserfs_key *)
						   &(deh->deh2_dir_id), 0);
			}
/*	
	    if ((dir_format == KEY_FORMAT_1) && (namelen != entry_len)) {
	    	// aligned entry in directory of old format - remove and insert it back
		fsck_log ("Entry %K (\"%.*s\") in the directory %K is not formated properly - deleted\n",
		    (struct reiserfs_key *)&(deh->deh2_dir_id), namelen, name, &tmp_ih.ih_key);
		reiserfs_remove_entry (fs, &entry_key);
		entry_len = name_length (name, dir_format);
		reiserfs_add_entry (fs, key, name, entry_len,
				(struct reiserfs_key *)&(deh->deh2_dir_id), 0);
	    }
*/
			if (is_dot(name, namelen)) {
				dir_size += DEH_SIZE + entry_len;
				continue;
			}

			print_name(name, namelen);

			if (!is_properly_hashed
			    (fs, name, namelen, get_deh_offset(deh)))
				reiserfs_panic
				    ("rebuild_semantic_pass: Hash mismatch detected for (\"%.*s\") in directory %K\n",
				     namelen, name, &tmp_ih.ih_key);

			retval1 =
			    rebuild_semantic_pass(&object_key, key,
						  is_dot_dot(name, namelen),
						  &relocated_ih);

			erase_name(namelen);

			switch (retval1) {
			case OK:
				dir_size += DEH_SIZE + entry_len;
				break;

			case STAT_DATA_NOT_FOUND:
			case DIRECTORY_HAS_NO_ITEMS:
				if (get_offset(&entry_key) == DOT_DOT_OFFSET &&
				    get_key_objectid(&object_key) ==
				    REISERFS_ROOT_PARENT_OBJECTID) {
					/* ".." of root directory can not be found */
					dir_size += DEH_SIZE + entry_len;
					continue;
				}
				fsck_log
				    ("%s: The entry %K (\"%.*s\") in directory %K points to nowhere - is removed\n",
				     __FUNCTION__, &object_key, namelen, name,
				     &tmp_ih.ih_key);
				reiserfs_remove_entry(fs, &entry_key);
				sem_pass_stat(fs)->deleted_entries++;
				break;

			case RELOCATED:
				/* file was relocated, update key in corresponding directory entry */
				if (reiserfs_search_by_entry_key
				    (fs, &entry_key, &path) != POSITION_FOUND) {
					fsck_log
					    ("WARNING: Cannot find the name of the relocated file %K in the directory %K\n",
					     &object_key, &tmp_ih.ih_key);
				} else {
					/* update key dir entry points to */
					struct reiserfs_de_head *tmp_deh;

					tmp_deh =
					    B_I_DEH(get_bh(&path),
						    tp_item_head(&path)) +
					    path.pos_in_item;
					fsck_log
					    ("The entry %K (\"%.*s\") in directory %K updated to point to ",
					     &object_key, namelen, name,
					     &tmp_ih.ih_key);
					set_deh_dirid(tmp_deh,
						      get_key_dirid
						      (&relocated_ih.ih_key));
					set_deh_objectid(tmp_deh,
							 get_key_objectid
							 (&relocated_ih.
							  ih_key));

					fsck_log("%K\n", &tmp_deh->deh2_dir_id);
					mark_buffer_dirty(get_bh(&path));
				}
				dir_size += DEH_SIZE + entry_len;
				pathrelse(&path);
				break;
			}
		}		/* for */

		freemem(dir_item);
		free(name);
		name = NULL;

		if (not_of_one_file(&item_key, key))
			/* next key is not of this directory */
			break;

	}			/* while (dir_item) */

	if (dir_size == 0)
		/* FIXME: is it possible? */
		return DIRECTORY_HAS_NO_ITEMS;

	/* calc correct value of sd_blocks field of stat data */
	blocks = dir_size2st_blocks(dir_size);

	fix_sd = 0;
	fix_sd += wrong_st_blocks(key, &blocks, saved_blocks, mode, is_new_dir);
	fix_sd +=
	    wrong_st_size(key, is_new_dir ? MAX_FILE_SIZE_V2 : MAX_FILE_SIZE_V1,
			  fs->fs_blocksize, &dir_size, saved_size,
			  TYPE_DIRENTRY);

	if (fix_sd) {
		/* we have to fix either sd_size or sd_blocks, so look for stat data again */
		if (reiserfs_search_by_key_4(fs, key, &path) != ITEM_FOUND)
			reiserfs_panic
			    ("rebuild_semantic_pass: The StatData of the file %K was not found",
			     key);

		bh = get_bh(&path);
		ih = tp_item_head(&path);
		sd = tp_item_body(&path);

		set_sd_size(ih, sd, &dir_size);
		set_sd_blocks(ih, sd, &blocks);
		mark_buffer_dirty(bh);
		pathrelse(&path);
	}

	return retval;
}

int is_dot(char *name, int namelen)
{
	return (namelen == 1 && name[0] == '.') ? 1 : 0;
}

int is_dot_dot(char *name, int namelen)
{
	return (namelen == 2 && name[0] == '.' && name[1] == '.') ? 1 : 0;
}

int not_a_directory(void *sd)
{
	/* mode is at the same place and of the same size in both stat
	   datas (v1 and v2) */
	struct stat_data_v1 *sd_v1 = sd;

	return !(S_ISDIR(le16_to_cpu(sd_v1->sd_mode)));
}

int not_a_regfile(void *sd)
{
	/* mode is at the same place and of the same size in both stat
	   datas (v1 and v2) */
	struct stat_data_v1 *sd_v1 = sd;

	return !(S_ISREG(le16_to_cpu(sd_v1->sd_mode)));
}

void zero_nlink(struct item_head *ih, void *sd)
{
	int zero = 0;

	if (get_ih_item_len(ih) == SD_V1_SIZE
	    && get_ih_key_format(ih) != KEY_FORMAT_1) {
		fsck_log
		    ("zero_nlink: The StatData %k of the wrong format version (%d) - corrected to (%d)\n",
		     ih, get_ih_key_format(ih), KEY_FORMAT_1);
		set_ih_key_format(ih, KEY_FORMAT_1);
	}
	if (get_ih_item_len(ih) == SD_SIZE
	    && get_ih_key_format(ih) != KEY_FORMAT_2) {
		fsck_log
		    ("zero_nlink: The StatData %k of the wrong format version (%d) - corrected to (%d)\n",
		     ih, get_ih_key_format(ih), KEY_FORMAT_2);
		set_ih_key_format(ih, KEY_FORMAT_2);
	}

	set_sd_nlink(ih, sd, &zero);
}

void modify_item(struct item_head *ih, void *item)
{
	zero_nlink(ih, item);
	mark_item_unreachable(ih);
}

/* mkreiserfs should have created this */
static void make_sure_lost_found_exists(reiserfs_filsys_t fs)
{
	int retval;
	INITIALIZE_REISERFS_PATH(path);
	unsigned int gen_counter;
	__u32 objectid;
	__u64 sd_size;
	__u32 sd_blocks;
	struct buffer_head *bh;
	void *sd;
	int item_len;

	/* look for "lost+found" in the root directory */
	retval = reiserfs_find_entry(fs, &root_dir_key,
				     "lost+found", &gen_counter,
				     &lost_found_dir_key);
	if (!retval) {
		objectid = id_map_alloc(proper_id_map(fs));
		if (!objectid) {
			fsck_progress
			    ("Could not allocate an objectid for \"/lost+found\", \
		lost files will not be linked\n");
			return;
		}
		set_key_dirid(&lost_found_dir_key, REISERFS_ROOT_OBJECTID);
		set_key_objectid(&lost_found_dir_key, objectid);
	}

	/* look for stat data of "lost+found" */
	retval = reiserfs_search_by_key_4(fs, &lost_found_dir_key, &path);
	if (retval == ITEM_NOT_FOUND)
		lost_found_dir_format =
		    create_dir_sd(fs, &path, &lost_found_dir_key, modify_item);
	else {
		struct item_head *ih = tp_item_head(&path);

		if (!is_stat_data_ih(ih))
			reiserfs_panic("It must be lost+found's stat data %k\n",
				       &ih->ih_key);

		fix_obviously_wrong_sd_mode(&path);

		if (not_a_directory(tp_item_body(&path))) {
			fsck_progress
			    ("\"/lost+found\" exists, but it is not a directory, \
		lost files will not be linked\n");
			set_key_objectid(&lost_found_dir_key, 0);
			pathrelse(&path);
			return;
		}

		lost_found_dir_format =
		    (get_ih_item_len(tp_item_head(&path)) ==
		     SD_SIZE) ? KEY_FORMAT_2 : KEY_FORMAT_1;

		pathrelse(&path);
	}

	/* add "." and ".." if any of them do not exist */
	reiserfs_add_entry(fs, &lost_found_dir_key, ".",
			   name_length(".", lost_found_dir_format),
			   &lost_found_dir_key, 1 << IH_Unreachable);
	reiserfs_add_entry(fs, &lost_found_dir_key, "..",
			   name_length("..", lost_found_dir_format),
			   &root_dir_key, 1 << IH_Unreachable);

	item_len = reiserfs_add_entry(fs, &root_dir_key, "lost+found",
				      name_length("lost+found",
						  root_dir_format),
				      &lost_found_dir_key, 1 << IH_Unreachable);

	if (item_len) {
		struct item_head *ih;
		if (reiserfs_search_by_key_4(fs, &root_dir_key, &path) ==
		    ITEM_NOT_FOUND)
			reiserfs_panic
			    ("%s: StatData of the root directory must exists",
			     __FUNCTION__);

		bh = get_bh(&path);
		ih = tp_item_head(&path);
		sd = tp_item_body(&path);

		get_sd_size(ih, sd, &sd_size);
		sd_size += item_len;
		set_sd_size(ih, sd, &sd_size);
		sd_blocks = dir_size2st_blocks(sd_size);
		set_sd_blocks(ih, sd, &sd_blocks);
		mark_buffer_dirty(bh);
		pathrelse(&path);
	}

	return;
}

/* Result of the rebuild pass will be saved in the state file which is needed to start
 * fsck again from the next pass. */
static void save_rebuild_semantic_result(reiserfs_filsys_t fs)
{
	FILE *file;
	int retval;

	file = open_file("temp_fsck_file.deleteme", "w+");
	if (!file)
		return;

	reiserfs_begin_stage_info_save(file, SEMANTIC_DONE);
	/*  Method not implemented yet.
	   reiserfs_objectid_map_save (file, semantic_id_map (fs));
	 */
	reiserfs_end_stage_info_save(file);
	close_file(file);
	retval = rename("temp_fsck_file.deleteme", state_dump_file(fs));
	if (retval != 0)
		fsck_progress
		    ("%s: Could not rename the temporary file temp_fsck_file.deleteme to %s",
		     __FUNCTION__, state_dump_file(fs));
}

/* we have nothing to load from a state file, but we have to fetch
   on-disk bitmap, copy it to allocable bitmap, and fetch objectid
   map */
void load_semantic_result(FILE * file, reiserfs_filsys_t fs)
{
	fsck_new_bitmap(fs) =
	    reiserfs_create_bitmap(get_sb_block_count(fs->fs_ondisk_sb));
	reiserfs_bitmap_copy(fsck_new_bitmap(fs), fs->fs_bitmap2);

	fsck_allocable_bitmap(fs) =
	    reiserfs_create_bitmap(get_sb_block_count(fs->fs_ondisk_sb));
	reiserfs_bitmap_copy(fsck_allocable_bitmap(fs), fs->fs_bitmap2);

	fs->block_allocator = reiserfsck_reiserfs_new_blocknrs;
	fs->block_deallocator = reiserfsck_reiserfs_free_block;

	/* we need objectid map on semantic pass to be able to relocate files */
	proper_id_map(fs) = id_map_init();
	/* Not implemented yet.
	   fetch_objectid_map (proper_id_map (fs), fs);
	   semantic_id_map (fs) = reiserfs_objectid_map_load (file);
	 */
}

static void before_pass_3(reiserfs_filsys_t fs)
{
	semantic_id_map(fs) = id_map_init();
}

static void after_pass_3(reiserfs_filsys_t fs)
{
	/* update super block: objectid map, fsck state */
	set_sb_fs_state(fs->fs_ondisk_sb, SEMANTIC_DONE);
	mark_buffer_dirty(fs->fs_super_bh);

	/* write all dirty blocks */
	fsck_progress("Flushing..");
	id_map_flush(proper_id_map(fs), fs);
	fs->fs_dirt = 1;
	reiserfs_flush_to_ondisk_bitmap(fsck_new_bitmap(fs), fs);
	reiserfs_flush(fs);
	fsck_progress("finished\n");

	stage_report(3, fs);

	if (!fsck_run_one_step(fs)) {
		if (fsck_user_confirmed(fs, "Continue? (Yes):", "Yes\n", 1))
			/* reiserfsck continues */
			return;
	}

	save_rebuild_semantic_result(fs);

	id_map_free(proper_id_map(fs));
	proper_id_map(fs) = NULL;

	fs->fs_dirt = 1;
	reiserfs_close(fs);
	exit(0);
}

/* this is part of rebuild tree */
void pass_3_semantic(reiserfs_filsys_t fs)
{
	before_pass_3(fs);

	fsck_progress("Pass 3 (semantic):\n");

	/* when warnings go not to stderr - separate them in the log */
	if (fsck_log_file(fs) != stderr)
		fsck_log("####### Pass 3 #########\n");

	if (!fs->fs_hash_function)
		reiserfs_panic("Hash function should be selected already");

	make_sure_root_dir_exists(fs, modify_item, 1 << IH_Unreachable);
	make_sure_lost_found_exists(fs);

	id_map_mark(proper_id_map(fs), get_key_objectid(&root_dir_key));
	id_map_mark(proper_id_map(fs), get_key_objectid(&lost_found_dir_key));

	/* link all relocated files into /lost+found directory */
	link_relocated_files();

	rebuild_semantic_pass(&root_dir_key, &parent_root_dir_key,
			      0 /*!dot_dot */ , NULL /*reloc_ih */ );

	add_badblock_list(fs, 1);

	after_pass_3(fs);
}
