/* -*- mode: c; c-basic-offset: 8; -*-
 * vim: noexpandtab sw=8 ts=8 sts=0:
 *
 * dir.c
 *
 * directory corruptions
 *
 * Copyright (C) 2006 Oracle.  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 as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301 USA.
 *
 */

/* This file will create corruption for directory.
 *
 * Directory inode error: DIR_ZERO
 *
 * Dirent dot error:	DIRENT_DOTTY_DUP, DIRENT_NOT_DOTTY, DIRENT_DOT_INODE,
 *			DIRENT_DOT_EXCESS
 *
 * Dirent field error: 	DIRENT_ZERO, DIRENT_NAME_CHARS,DIRENT_INODE_RANGE,
 *			DIRENT_INODE_FREE, DIRENT_TYPE, DIRENT_DUPLICATE,
 *			DIRENT_LENGTH
 *
 * Directory parent duplicate error: DIR_PARENT_DUP
 *
 * Directory not connected error: DIR_NOT_CONNECTED
 *
 */

#include "main.h"

extern char *progname;

void create_directory(ocfs2_filesys *fs,
				uint64_t parentblk, uint64_t *blkno)
{
	errcode_t ret;
	char random_name[OCFS2_MAX_FILENAME_LEN];

	memset(random_name, 0, sizeof(random_name));
	sprintf(random_name, "testXXXXXX");
	
	/* Don't use mkstemp since it will create a file 
	 * in the working directory which is no use.
	 * Use mktemp instead Although there is a compiling warning.
	 * mktemp fails to work in some implementations follow BSD 4.3,
	 * but currently ocfs2 will only support linux,
	 * so it will not affect us.
	 */
	if (!mktemp(random_name))
		FSWRK_COM_FATAL(progname, errno);

	ret = ocfs2_lookup(fs, parentblk, random_name, strlen(random_name),
			   NULL, blkno);
	if (!ret)
		return;
	else if (ret != OCFS2_ET_FILE_NOT_FOUND)
		FSWRK_COM_FATAL(progname, ret);

	ret  = ocfs2_new_inode(fs, blkno, S_IFDIR | 0755);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	ret = ocfs2_init_dir(fs, *blkno, parentblk);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	ret = ocfs2_link(fs, parentblk, random_name, *blkno, OCFS2_FT_DIR);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	return;
}

struct dirent_corrupt_struct  {

	const char	*oldname;
	const char      *name;
	int             namelen;
	int		oldnamelen;
	int             done;
	int		reserved;
};

static int corrupt_match_dirent(struct dirent_corrupt_struct *dcs,
				struct ocfs2_dir_entry *dirent)
{
	if (!dcs->oldname)
		return 1;

	if (((dirent->name_len & 0xFF) != dcs->oldnamelen))
		return 0;

	if (strncmp(dcs->oldname, dirent->name, dirent->name_len & 0xFF))
		return 0;

	return 1;
}

static int rename_dirent_proc(struct ocfs2_dir_entry *dirent,
			      uint64_t   blocknr,
			      int        offset,
			      int        blocksize,
			      char       *buf,
			      void       *priv_data)
{
	struct  dirent_corrupt_struct *dcs = (struct dirent_corrupt_struct *) priv_data;

	if (!corrupt_match_dirent(dcs, dirent))
		return 0;
	
	if (dcs->namelen <= (dirent->rec_len -
			     offsetof(struct ocfs2_dir_entry, name))) {
		strcpy(dirent->name, dcs->name);
		dirent->name_len = dcs->namelen;
	} else
		FSWRK_FATAL("The lenght of new name for target dirent you"
			    "want to rename didn't fit the old one.\n");
	dcs->done++;

	return OCFS2_DIRENT_ABORT|OCFS2_DIRENT_CHANGED;
}

static int rename_dirent(ocfs2_filesys *fs, uint64_t dir,
			 const char *name, const char *oldname)
{
	errcode_t       rc;
	struct dirent_corrupt_struct dcs;

	if (!(fs->fs_flags & OCFS2_FLAG_RW))
		return OCFS2_ET_RO_FILESYS;

	dcs.name = name;
	dcs.oldname = oldname;
	dcs.namelen = name ? strlen(name) : 0;
	dcs.oldnamelen = oldname ? strlen(oldname) : 0;
	dcs.done = 0;

	rc = ocfs2_dir_iterate(fs, dir, 0, 0, rename_dirent_proc, &dcs);
	if (rc)
		return rc;

	return (dcs.done) ? 0 : OCFS2_ET_DIR_NO_SPACE;
}

static int corrupt_dirent_ino_proc(struct ocfs2_dir_entry *dirent,
				   uint64_t   blocknr,
				   int        offset,
				   int        blocksize,
				   char       *buf,
				   void       *priv_data)
{
	struct  dirent_corrupt_struct *dcs = (struct dirent_corrupt_struct*) priv_data;

	if (!corrupt_match_dirent(dcs, dirent))
		return 0;

	dirent->inode += dcs->reserved;

	dcs->reserved = dirent->inode;

	dcs->done++;

	return OCFS2_DIRENT_ABORT|OCFS2_DIRENT_CHANGED;
}

static int corrupt_dirent_ino(ocfs2_filesys *fs, uint64_t dir,
			      const char *name, uint64_t *new_ino, int inc)
{
	errcode_t       rc;
	struct dirent_corrupt_struct dcs;

	if (!(fs->fs_flags & OCFS2_FLAG_RW))
		return OCFS2_ET_RO_FILESYS;

	dcs.oldname = name;
	dcs.oldnamelen = name ? strlen(name) : 0;
	dcs.done = 0;
	dcs.reserved = inc;

	rc = ocfs2_dir_iterate(fs, dir, 0, 0, corrupt_dirent_ino_proc, &dcs);
	if (rc)
		return rc;

	*new_ino = dcs.reserved;

	return (dcs.done) ? 0 : OCFS2_ET_DIR_NO_SPACE;
}

static int corrupt_dirent_reclen_proc(struct ocfs2_dir_entry *dirent,
				      uint64_t   blocknr,
				      int        offset,
				      int        blocksize,
				      char       *buf,
				      void       *priv_data)
{
	struct  dirent_corrupt_struct *dcs = (struct dirent_corrupt_struct*) priv_data;

	if (!corrupt_match_dirent(dcs, dirent))
		return 0;

	dirent->rec_len += dcs->reserved;

	dcs->done++;

	dcs->reserved = dirent->rec_len;

	return OCFS2_DIRENT_ABORT|OCFS2_DIRENT_CHANGED;
}

static int corrupt_dirent_reclen(ocfs2_filesys *fs, uint64_t dir,
				 const char *name, uint64_t *new_reclen,
				 int inc)
{
	errcode_t       rc;
	struct dirent_corrupt_struct dcs;

	if (!(fs->fs_flags & OCFS2_FLAG_RW))
		return OCFS2_ET_RO_FILESYS;

	dcs.oldname = name;
	dcs.oldnamelen = name ? strlen(name) : 0;
	dcs.done = 0;
	dcs.reserved = inc;

	rc = ocfs2_dir_iterate(fs, dir, 0, 0, corrupt_dirent_reclen_proc, &dcs);
	if (rc)
		return rc;

	*new_reclen = dcs.reserved;

	return (dcs.done) ? 0 : OCFS2_ET_DIR_NO_SPACE;
}

static void damage_dir_content(ocfs2_filesys *fs, uint64_t dir,
				enum fsck_type type)
{
	errcode_t ret;
	uint64_t tmp_blkno, tmp_no;
	char name[OCFS2_MAX_FILENAME_LEN];
	mode_t mode;

	memset(name, 0, sizeof(name));
	sprintf(name, "testXXXXXX");
	if (!mktemp(name))
		FSWRK_COM_FATAL(progname, errno);

	switch (type) {
	case DIRENT_DOTTY_DUP:
		/* add another "." at the end of the directory */
		sprintf(name, ".");
		ret = ocfs2_link(fs, dir, name, dir, OCFS2_FT_DIR);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_DOTTY_DUP: "
			"Corrupt directory#%"PRIu64
			", add another '.' to it.\n", dir);
		break;
	case DIRENT_NOT_DOTTY:
		/* rename the first ent from "." to "a". */
		sprintf(name, "a");
		rename_dirent(fs, dir, name, ".");
		fprintf(stdout, "DIRENT_NOT_DOTTY: "
			"Corrupt directory#%"PRIu64
			", change '.' to %s.\n", dir, name);
		break;
	case DIRENT_DOT_INODE:
		fprintf(stdout, "DIRENT_DOT_INODE: "
			"Corrupt directory#%"PRIu64
			", change dot inode to #%"PRIu64".\n", dir, (dir+10));
		corrupt_dirent_ino(fs, dir, ".", &tmp_no, 10);
		break;
	case DIRENT_DOT_EXCESS:
		corrupt_dirent_reclen(fs, dir, ".", &tmp_no, OCFS2_DIR_PAD);
		fprintf(stdout, "DIR_DOT_EXCESS: "
			"Corrupt directory#%"PRIu64","
			"change dot's dirent length from %"PRIu64" "
			"to %"PRIu64"\n",
			dir, tmp_no - OCFS2_DIR_PAD, tmp_no);
		break;
	case DIR_DOTDOT:
		corrupt_dirent_ino(fs, dir, "..", &tmp_no, 10);
		fprintf(stdout, "DIR_DOTDOT: "
			"Corrupt directory#%"PRIu64
			", change dotdot inode from %"PRIu64" to %"PRIu64".\n",
			dir, tmp_no - 10, tmp_no);
		break;
	case DIRENT_ZERO:
		memset(name, 0, 1);
		ret = ocfs2_link(fs, dir, name, dir + 100, OCFS2_FT_DIR);
                if (ret)
                        FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_ZERO: "
			"Corrupt directory#%"PRIu64
			", add an zero entry to it.\n", dir);
		break;
	case DIRENT_NAME_CHARS:
		name[0] = 47;
		mode = S_IFREG | 0755;
		ret = ocfs2_new_inode(fs, &tmp_blkno, mode);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_NAME_CHARS: "
			"Corrupt directory#%"PRIu64
			", add an invalid entry to it.\n", dir);
		break;
	case DIRENT_INODE_RANGE:
		tmp_blkno = fs->fs_blocks;
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		corrupt_dirent_ino(fs, dir, name, &tmp_no, 1);
		fprintf(stdout, "DIRENT_INODE_RANGE: "
			"Corrupt directory#%"PRIu64
			", add an entry whose inode exceeds"
			" the limits.\n", dir);
		break;
	case DIRENT_INODE_FREE:
		tmp_blkno = dir + 1000;
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_INODE_FREE: "
			"Corrupt directory#%"PRIu64
			", add an entry's inode#%"PRIu64
			" whose inode isn't used.\n", dir, tmp_blkno);
		break;
	case DIRENT_TYPE:
		mode = S_IFREG | 0755;
		ret = ocfs2_new_inode(fs, &tmp_blkno, mode);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_SYMLINK);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_TYPE: "
			"Corrupt directory#%"PRIu64
			", change an entry's mode from %u to %u.\n",
			dir, mode, S_IFLNK | 0755);
		break;
	case DIRENT_DUPLICATE:
		mode = S_IFREG | 0755;
		ret = ocfs2_new_inode(fs, &tmp_blkno, mode);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		fprintf(stdout, "DIRENT_DUPLICATE: "
			"Corrupt directory#%"PRIu64
			", add two entries with the same name '%s'.\n",
			dir, name);
		break;
	case DIRENT_LENGTH:
		mode = S_IFREG | 0755;
		ret = ocfs2_new_inode(fs, &tmp_blkno, mode);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		ret = ocfs2_link(fs, dir, name, tmp_blkno, OCFS2_FT_REG_FILE);
		if (ret)
			FSWRK_COM_FATAL(progname, ret);
		corrupt_dirent_reclen(fs, dir, name, &tmp_no, 1);
		fprintf(stdout, "DIRENT_LENGTH: "
			"Corrupt directory#%"PRIu64
			", modify entry#%"PRIu64" from %"PRIu64" "
			"to %"PRIu64".\n",
			dir, tmp_blkno, tmp_no - 1, tmp_no);
		break;
	default:
		FSWRK_FATAL("Invalid type = %d\n", type);	
	}

	return;
}

void mess_up_dir_ent(ocfs2_filesys *fs, enum fsck_type type, uint64_t blkno)
{
	uint64_t tmp_blkno;

	create_directory(fs, blkno, &tmp_blkno);
	damage_dir_content(fs, tmp_blkno, type);

	return;
}

void mess_up_dir_parent_dup(ocfs2_filesys *fs, enum fsck_type type,
			    uint64_t blkno)
{
	errcode_t ret;
	uint64_t parent1, parent2, tmp_blkno;
	char random_name[OCFS2_MAX_FILENAME_LEN];

	/* create 2 direcotories */
	create_directory(fs, blkno, &parent1);
	create_directory(fs, blkno, &parent2);

	/* create a directory under parent1, tmp_blkno indicates its inode. */
	create_directory(fs, parent1, &tmp_blkno);

	memset(random_name, 0, sizeof(random_name));
	sprintf(random_name, "testXXXXXX");
	/* Don't use mkstemp since it will create a file 
	 * in the working directory which is no use.
	 * Use mktemp instead Although there is a compiling warning.
	 * mktemp fails to work in some implementations follow BSD 4.3,
	 * but currently ocfs2 will only support linux,
	 * so it will not affect us.
	 */
	if (!mktemp(random_name))
		FSWRK_COM_FATAL(progname, errno);

	ret = ocfs2_link(fs, parent2, random_name, tmp_blkno, OCFS2_FT_DIR);
        if (ret)
		FSWRK_COM_FATAL(progname, ret);
	fprintf(stdout, "DIR_PARENT_DUP: "

		"Create a directory #%"PRIu64
		" which has two parents: #%"PRIu64" and #%"PRIu64".\n",
		tmp_blkno, parent1, parent2);

	return;
}

void mess_up_dir_inode(ocfs2_filesys *fs, enum fsck_type type, uint64_t blkno)
{
	errcode_t ret;
	char *buf = NULL, *buf2 = NULL;
	struct ocfs2_dinode *di, *di2;
	struct ocfs2_extent_list *el;
	uint64_t tmp_blkno, file_ino, dir2;
	int i;
	struct ocfs2_extent_rec *er;

	create_directory(fs, blkno, &tmp_blkno);

	ret = ocfs2_malloc_block(fs->fs_io, &buf);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	ret = ocfs2_malloc_block(fs->fs_io, &buf2);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	ret = ocfs2_read_inode(fs, tmp_blkno, buf);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	di = (struct ocfs2_dinode *)buf;

	if (!(di->i_flags & OCFS2_VALID_FL))
		FSWRK_FATAL("not a valid file");

	switch (type) {
	case DIR_HOLE:
		create_named_directory(fs, "tmp1", &dir2);
		for (i = 0; i < fs->fs_blocksize; i++) {
			if (i%2)
				create_file(fs, blkno, &file_ino);
			else
				create_file(fs, dir2, &file_ino);
		}
		ret = ocfs2_read_inode(fs, dir2, buf2);
		di2 = (struct ocfs2_dinode *)buf2;
		el = &(di2->id2.i_list);
		er = &(el->l_recs[0]);
		er->e_cpos += 2;
		er = &(el->l_recs[1]);
		er->e_cpos += 3;
		di2->i_size += 23 * fs->fs_clustersize;
		ret = ocfs2_write_inode(fs, dir2, buf2);
		fprintf(stdout, "DIR_HOLE: Messed up extent records of %"
				PRIu64" to show a hole.\n", dir2);
		break;
	case DIR_ZERO:
		if (di->i_dyn_features & OCFS2_INLINE_DATA_FL) {
			FSWRK_FATAL("Inlined directory");
		} else {
			el = &(di->id2.i_list);
			if (el->l_next_free_rec == 0)
				FSWRK_FATAL("directory empty");
			el->l_next_free_rec = 0;
		}
		fprintf(stdout, "DIR_ZERO: Corrupt directory#%"PRIu64
				", empty its content.\n", tmp_blkno);
		break;
	default:
		fprintf(stdout, "Invalid code passed for dir\n");
	}

	ret = ocfs2_write_inode(fs, tmp_blkno, buf);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);	

	if (buf)
		ocfs2_free(&buf);
	if (buf2)
		ocfs2_free(&buf2);
	return;
}

void mess_up_dir_not_connected(ocfs2_filesys *fs, enum fsck_type type,
			       uint64_t blkno)
{
	errcode_t ret;
	uint64_t tmp_blkno;

	ret  = ocfs2_new_inode(fs, &tmp_blkno, S_IFDIR | 0755);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	ret = ocfs2_init_dir(fs, tmp_blkno, blkno);
	if (ret)
		FSWRK_COM_FATAL(progname, ret);

	fprintf(stdout, "DIR_NOT_CONNECTED: "
		"create a directory#%"PRIu64" which has no connections.\n",
		tmp_blkno);

	return;
}
