/*
 * emptydir.c --- clear empty directory blocks
 *
 * Copyright (C) 1998 Theodore Ts'o
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the GNU Public
 * License.
 * %End-Header%
 *
 * This file has the necessary routines to search for empty directory
 * blocks and get rid of them.
 */

#include "config.h"
#include "e2fsck.h"
#include "problem.h"

/*
 * For e2fsck.h
 */
struct empty_dir_info_struct {
	ext2_dblist empty_dblist;
	ext2fs_block_bitmap empty_dir_blocks;
	ext2fs_inode_bitmap dir_map;
	char *block_buf;
	ext2_ino_t ino;
	struct ext2_inode inode;
	blk64_t	logblk;
	blk64_t	freed_blocks;
};

typedef struct empty_dir_info_struct *empty_dir_info;

extern empty_dir_info init_empty_dir(e2fsck_t ctx);
extern void free_empty_dirblock(empty_dir_info edi);
extern void add_empty_dirblock(empty_dir_info edi,
			       struct ext2_db_entry2 *db);
extern void process_empty_dirblock(e2fsck_t ctx, empty_dir_info edi);


empty_dir_info init_empty_dir(e2fsck_t ctx)
{
	empty_dir_info	edi;
	errcode_t	retval;

	edi = malloc(sizeof(struct empty_dir_info_struct));
	if (!edi)
		return NULL;

	memset(edi, 0, sizeof(struct empty_dir_info_struct));

	retval = ext2fs_init_dblist(ctx->fs, &edi->empty_dblist);
	if (retval)
		goto errout;

	retval = ext2fs_allocate_block_bitmap(ctx->fs, _("empty dirblocks"),
					      &edi->empty_dir_blocks);
	if (retval)
		goto errout;

	retval = ext2fs_allocate_inode_bitmap(ctx->fs, _("empty dir map"),
					      &edi->dir_map);
	if (retval)
		goto errout;

	return (edi);

errout:
	free_empty_dirblock(edi);
	return NULL;
}

void free_empty_dirblock(empty_dir_info edi)
{
	if (!edi)
		return;
	if (edi->empty_dblist)
		ext2fs_free_dblist(edi->empty_dblist);
	if (edi->empty_dir_blocks)
		ext2fs_free_block_bitmap(edi->empty_dir_blocks);
	if (edi->dir_map)
		ext2fs_free_inode_bitmap(edi->dir_map);

	memset(edi, 0, sizeof(struct empty_dir_info_struct));
	free(edi);
}

void add_empty_dirblock(empty_dir_info edi,
			struct ext2_db_entry2 *db)
{
	if (!edi || !db)
		return;

	if (db->ino == 11)
		return;		/* Inode number 11 is usually lost+found */

	printf(_("Empty directory block %u (#%d) in inode %u\n"),
	       db->blk, db->blockcnt, db->ino);

	ext2fs_mark_block_bitmap2(edi->empty_dir_blocks, db->blk);
	if (ext2fs_test_inode_bitmap(edi->dir_map, db->ino))
		return;
	ext2fs_mark_inode_bitmap(edi->dir_map, db->ino);

	ext2fs_add_dir_block2(edi->empty_dblist, db->ino,
			      db->blk, db->blockcnt);
}

/*
 * Helper function used by fix_directory.
 *
 * XXX need to finish this.  General approach is to use bmap to
 * iterate over all of the logical blocks using the bmap function, and
 * copy the block reference as necessary.  Big question --- what do
 * about error recovery?
 *
 * Also question --- how to free the indirect blocks.
 */
int empty_pass1(ext2_filsys fs, blk64_t *block_nr, e2_blkcnt_t blockcnt,
		blk64_t ref_block, int ref_offset, void *priv_data)
{
	empty_dir_info edi = (empty_dir_info) priv_data;
	blk64_t	block, new_block;
	errcode_t	retval;

	if (blockcnt < 0)
		return 0;
	block = *block_nr;
	do {
		retval = ext2fs_bmap2(fs, edi->ino, &edi->inode,
				      edi->block_buf, 0, edi->logblk, 0,
				      &new_block);
		if (retval)
			return DIRENT_ABORT;   /* XXX what to do? */
		if (new_block == 0)
			break;
		edi->logblk++;
	} while (ext2fs_test_block_bitmap2(edi->empty_dir_blocks, new_block));

	if (new_block == block)
		return 0;
	if (new_block == 0)
		edi->freed_blocks++;
	*block_nr = new_block;
	return BLOCK_CHANGED;
}

static int fix_directory(ext2_filsys fs,
			 struct ext2_db_entry2 *db,
			 void *priv_data)
{
	errcode_t	retval;

	empty_dir_info edi = (empty_dir_info) priv_data;

	edi->logblk = 0;
	edi->freed_blocks = 0;
	edi->ino = db->ino;

	retval = ext2fs_read_inode(fs, db->ino, &edi->inode);
	if (retval)
		return 0;

	retval = ext2fs_block_iterate3(fs, db->ino, 0, edi->block_buf,
				       empty_pass1, edi);
	if (retval)
		return 0;

	if (edi->freed_blocks) {
		edi->inode.i_size -= edi->freed_blocks * fs->blocksize;
		ext2fs_iblk_add_blocks(fs, &edi->inode, edi->freed_blocks);
		retval = ext2fs_write_inode(fs, db->ino, &edi->inode);
		if (retval)
			return 0;
	}
	return 0;
}

void process_empty_dirblock(e2fsck_t ctx, empty_dir_info edi)
{
	if (!edi)
		return;

	edi->block_buf = malloc(ctx->fs->blocksize * 3);

	if (edi->block_buf) {
		(void) ext2fs_dblist_iterate2(edi->empty_dblist,
					      fix_directory, &edi);
	}
	free(edi->block_buf);
	free_empty_dirblock(edi);
}