/* -*- mode: c; c-basic-offset: 8; -*-
 * vim: noexpandtab sw=8 ts=8 sts=0:
 *
 * feature_indexed_dirs.c
 *
 * ocfs2 tune utility for enabling and disabling the directory indexing
 * feature.
 *
 * Copyright (C) 2009, 2010 Novell.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
#include <inttypes.h>
#include <assert.h>

#include "ocfs2/ocfs2.h"

#include "libocfs2ne.h"

struct dx_dirs_inode {
	struct list_head list;
	uint64_t ino;
};

struct dx_dirs_context {
	errcode_t ret;
	uint64_t dx_dirs_nr;
	struct list_head inodes;
	struct tools_progress *prog;
};

/*
 * If an indexed-dirs disabled directory has an indexed tree,
 * this tree is unreliable. it must be truncated and rebuilt.
 */
static errcode_t build_dx_dir(ocfs2_filesys *fs, struct ocfs2_dinode *di,
				void *user_data)
{
	errcode_t ret = 0;
	struct dx_dirs_context *ctxt = (struct dx_dirs_context *)user_data;

	if (!S_ISDIR(di->i_mode))
		goto bail;

	if (di->i_dyn_features & OCFS2_INDEXED_DIR_FL) {
		verbosef(VL_APP,
			"Directory inode %llu already has an indexed tree, "
			"rebuild the indexed tree.\n", di->i_blkno);
		ret = ocfs2_dx_dir_truncate(fs, di->i_blkno);
		if (ret) {
			ret = TUNEFS_ET_DX_DIRS_TRUNCATE_FAILED;
			tcom_err(ret, "while rebuild indexed tree");
		}
	}

	ret = tunefs_install_dir_trailer(fs, di, NULL);
	if (ret) {
		ret = TUNEFS_ET_INSTALL_DIR_TRAILER_FAILED;
		tcom_err(ret, "while enable indexed-dirs");
		goto bail;
	}

	ret = ocfs2_dx_dir_build(fs, di->i_blkno);
	if (ret) {
		ret = TUNEFS_ET_DX_DIRS_BUILD_FAILED;
		tcom_err(ret, "while enable indexed-dirs");
	}

bail:
	tools_progress_step(ctxt->prog, 1);
	return ret;
}

static int enable_indexed_dirs(ocfs2_filesys *fs, int flags)
{
	errcode_t ret = 0;
	struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super);
	struct tools_progress *prog = NULL;
	struct dx_dirs_context ctxt;

	if (ocfs2_supports_indexed_dirs(super)) {
		verbosef(VL_APP,
			 "Directory indexing feature is already enabled; "
			 "nothing to enable\n");
		goto out;
	}

	if (!tools_interact("Enable the directory indexing feature on "
			    "device \"%s\"? ",
			    fs->fs_devname))
		goto out;

	prog = tools_progress_start("Enable directory indexing", "dir idx", 2);
	if (!prog) {
		ret = TUNEFS_ET_NO_MEMORY;
		tcom_err(ret, "while initializing the progress display");
		goto out;
	}

	memset(&ctxt, 0, sizeof(struct dx_dirs_context));
	ctxt.prog = tools_progress_start("Building indexed trees", "building", 0);
	if (!ctxt.prog) {
		ret = TUNEFS_ET_NO_MEMORY;
		goto out;
	}

	/* s_uuid_hash is also used by xattr */
	if (!OCFS2_HAS_INCOMPAT_FEATURE(super, OCFS2_FEATURE_INCOMPAT_XATTR))
		super->s_uuid_hash =
			ocfs2_xattr_uuid_hash((unsigned char *)super->s_uuid);
	/* set seed for indexed dir hash */
	srand48(time(NULL));
	super->s_dx_seed[0] = mrand48();
	super->s_dx_seed[1] = mrand48();
	super->s_dx_seed[2] = mrand48();

	OCFS2_SET_INCOMPAT_FEATURE(super,
				   OCFS2_FEATURE_INCOMPAT_INDEXED_DIRS);

	tunefs_block_signals();
	ret = ocfs2_write_super(fs);
	if (ret) {
		ret = TUNEFS_ET_IO_WRITE_FAILED;
		tcom_err(ret, "while writing out the superblock");
		goto unblock_out;
	}
	tools_progress_step(prog, 1);
	ret = tunefs_foreach_inode(fs, build_dx_dir, &ctxt);
	if (ret)
		tcom_err(ret, "while building indexed trees");
unblock_out:
	tunefs_unblock_signals();
	tools_progress_step(prog, 1);
	if (ctxt.prog)
		tools_progress_stop(ctxt.prog);
out:
	if (prog)
		tools_progress_stop(prog);

	return ret;
}

static errcode_t dx_dir_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *di,
				void *user_data)
{
	errcode_t ret = 0;
	struct dx_dirs_inode *dx_di = NULL;
	struct dx_dirs_context *ctxt= (struct dx_dirs_context *)user_data;

	if (!S_ISDIR(di->i_mode))
		goto bail;

	if (!(di->i_dyn_features & OCFS2_INDEXED_DIR_FL))
		goto bail;

	ret = ocfs2_malloc0(sizeof(struct dx_dirs_inode), &dx_di);
	if (ret) {
		ret = TUNEFS_ET_NO_MEMORY;
		goto bail;
	}

	dx_di->ino = di->i_blkno;
	ctxt->dx_dirs_nr ++;
	list_add_tail(&dx_di->list, &ctxt->inodes);

	tools_progress_step(ctxt->prog, 1);

bail:
	return ret;
}


static errcode_t find_indexed_dirs(ocfs2_filesys *fs,
				   struct dx_dirs_context *ctxt)
{
	errcode_t ret;

	ctxt->prog = tools_progress_start("Scanning filesystem", "scanning", 0);
	if (!ctxt->prog) {
		ret = TUNEFS_ET_NO_MEMORY;
		goto bail;
	}

	ret = tunefs_foreach_inode(fs, dx_dir_iterate, ctxt);
	if (ret) {
		if (ret != TUNEFS_ET_NO_MEMORY)
			ret = TUNEFS_ET_DX_DIRS_SCAN_FAILED;
		goto bail;
	}

	verbosef(VL_APP,
		"We have %"PRIu64" indexed %s to truncate.\n",
		ctxt->dx_dirs_nr,
		(ctxt->dx_dirs_nr > 1)?"directories":"directory");

bail:
	if (ctxt->prog)
		tools_progress_stop(ctxt->prog);

	return ret;
}

static errcode_t clean_indexed_dirs(ocfs2_filesys *fs,
				    struct dx_dirs_context *ctxt)
{
	errcode_t ret = 0;
	struct list_head *pos;
	struct dx_dirs_inode *dx_di;
	struct tools_progress *prog;
	uint64_t dirs_truncated = 0;

	prog = tools_progress_start("Truncating indexed dirs", "truncating",
				    ctxt->dx_dirs_nr);
	if (!prog) {
		ret = TUNEFS_ET_NO_MEMORY;
		goto bail;
	}

	list_for_each(pos, &ctxt->inodes) {
		dx_di = list_entry(pos, struct dx_dirs_inode, list);

		ret = ocfs2_dx_dir_truncate(fs, dx_di->ino);
		if (ret) {
			verbosef(VL_APP,
				"Truncate directory (ino \"%"PRIu64"\") failed.",
				dx_di->ino);
			ret = TUNEFS_ET_DX_DIRS_TRUNCATE_FAILED;
			goto bail;
		}
		dirs_truncated ++;
		tools_progress_step(prog, 1);
	}

bail:
	tools_progress_stop(prog);
	verbosef(VL_APP,
		"\"%"PRIu64"\" from \"%"PRIu64"\" indexed %s truncated.",
		dirs_truncated, ctxt->dx_dirs_nr,
		(dirs_truncated <= 1) ? "directory is" : "directories are");

	return ret;
}

static void release_dx_dirs_context(struct dx_dirs_context *ctxt)
{
	struct list_head *pos, *n;
	struct dx_dirs_inode *dx_di;

	list_for_each_safe(pos, n, &ctxt->inodes) {
		dx_di = list_entry(pos, struct dx_dirs_inode, list);
		list_del(&dx_di->list);
		ocfs2_free(&dx_di);
	}
}

static int disable_indexed_dirs(ocfs2_filesys *fs, int flags)
{
	errcode_t ret = 0;
	struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super);
	struct dx_dirs_context ctxt;
	struct tools_progress *prog = NULL;

	if (!ocfs2_supports_indexed_dirs(super)) {
		verbosef(VL_APP,
			"Directory indexing feature is not enabled; "
			"nothing to disable\n");
		goto out;
	}

	if (!tools_interact("Disabling the directory indexing feature on "
			    "device \"%s\"? ",
			    fs->fs_devname))
		goto out;

	prog = tools_progress_start("Disable directory indexing", "no dir idx", 2);
	if (!prog) {
		ret = TUNEFS_ET_NO_MEMORY;
		tcom_err(ret, "while initializing the progress display");
		goto out;
	}

	memset(&ctxt, 0, sizeof (struct dx_dirs_context));
	INIT_LIST_HEAD(&ctxt.inodes);
	ret = find_indexed_dirs(fs, &ctxt);
	if (ret) {
		tcom_err(ret, "while scanning indexed directories");
		goto out_cleanup;
	}

	tools_progress_step(prog, 1);

	tunefs_block_signals();
	ret = clean_indexed_dirs(fs, &ctxt);
	if (ret) {
		tcom_err(ret, "while truncate indexed directories");
	}

	/* We already touched file system, must disable dx dirs flag here.
	 * fsck.ocfs2 will handle the orphan indexed trees. */
	OCFS2_CLEAR_INCOMPAT_FEATURE(super,
				     OCFS2_FEATURE_INCOMPAT_INDEXED_DIRS);

	/* s_uuid_hash is also used by xattr */
	if (!OCFS2_HAS_INCOMPAT_FEATURE(super, OCFS2_FEATURE_INCOMPAT_XATTR))
		super->s_uuid_hash = 0;
	super->s_dx_seed[0] = super->s_dx_seed[1] = super->s_dx_seed[2] = 0;

	ret = ocfs2_write_super(fs);
	tunefs_unblock_signals();

	if (ret) {
		ret = TUNEFS_ET_IO_WRITE_FAILED;
		tcom_err(ret, "while writing super block");
	}

	tools_progress_step(prog, 1);
out_cleanup:
	release_dx_dirs_context(&ctxt);
out:
	if (prog)
		tools_progress_stop(prog);

	return ret;
}

/*
 * TUNEFS_FLAG_ALLOCATION because disabling will want to dealloc
 * blocks.
 */
DEFINE_TUNEFS_FEATURE_INCOMPAT(indexed_dirs,
			       OCFS2_FEATURE_INCOMPAT_INDEXED_DIRS,
			       TUNEFS_FLAG_RW | TUNEFS_FLAG_ALLOCATION,
			       enable_indexed_dirs,
			       disable_indexed_dirs);

#ifdef DEBUG_EXE
int main(int argc, char *argv[])
{
	return tunefs_feature_main(argc, argv, &indexed_dirs_feature);
}
#endif
