/*
 * Debugfs.c --- a program which allows you to attach an ext2fs
 * filesystem and play with it.
 *
 * Original by Ted Ts'o <tytso@athena.mit.edu>
 * Modifications by Robert Sanders <gt8134b@prism.gatech.edu>
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/ext2_fs.h>

#include "ext2fs/ext2fs.h"
#include "et/com_err.h"
#include "ss/ss.h"

extern ss_request_table debug_cmds;

ext2_filsys fs = NULL;
ino_t	root, cwd;

#define streq(a,b) (!strcmp(a,b))

FILE *open_pager(void)
{
	FILE *outfile;
	char *pager = getenv("PAGER");

	if (!pager)
		outfile = stdout;
	else {
		outfile = popen(pager, "w");
		if (!outfile) outfile = stdout;
	}
	return (outfile);
}

void close_pager(FILE *stream)
{
	if (stream && stream != stdout) fclose(stream);
}

/*
 * This routine is used whenever a command needs to turn a string into
 * an inode.
 */
ino_t string_to_inode(char *str)
{
	ino_t	ino;
	int	len = strlen(str);
	int	i;
	int	retval;

	/*
	 * If the string is of the form <ino>, then treat it as an
	 * inode number.
	 */
	if ((len > 2) && (str[0] == '<') && (str[len-1] == '>')) {
		for (i = 1; i < len-1; i++)
			if (!isdigit(str[i]))
				break;
		if (i == len-1)
			return(atoi(str+1));
	}

	retval = ext2fs_namei(fs, root, cwd, str, &ino);
	if (retval) {
		com_err(str, retval, "");
		return 0;
	}
	return ino;
}

/*
 * This routine returns 1 if the filesystem is not open, and prints an
 * error message to that effect.
 */
inline int check_fs_open(char *name)
{
	if (!fs) {
		com_err(name, 0, "Filesystem not open");
		return 1;
	}
	return 0;
}

void open_filesystem(char *device, int open_flags)
{
	int	retval;
	
	retval = ext2fs_open(device, open_flags, 0, 0, unix_io_manager, &fs);
	if (retval) {
		com_err(device, retval, "while opening filesystem");
		fs = NULL;
		return;
	}
	retval = ext2fs_read_inode_bitmap(fs);
	if (retval) {
		com_err(device, retval, "while reading inode bitmap");
		goto errout;
	}
	retval = ext2fs_read_block_bitmap(fs);
	if (retval) {
		com_err(device, retval, "while reading block bitmap");
		goto errout;
	}
	root = cwd = EXT2_ROOT_INO;
	return;

errout:
	retval = ext2fs_close(fs);
	if (retval)
		com_err(device, retval, "while trying to close filesystem");
	fs = NULL;
}

void do_open_filesys(int argc, char **argv)
{
	char	*usage = "Usage: open [-w] <device>";
	char	c;
	int open_flags = 0;
	
	optind = 0;
	while ((c = getopt (argc, argv, "w")) != EOF) {
		switch (c) {
		case 'w':
			open_flags = EXT2_FLAG_RW;
			break;
		default:
			com_err(argv[0], 0, usage);
			return;
		}
	}
	if (optind != argc-1) {
		com_err(argv[0], 0, usage);
		return;
	}
	if (fs) {
		printf("Filesystem %s is still open.  Close it first.\n",
		       fs->device_name);
		return;
	}
	open_filesystem(argv[optind], open_flags);
}

void close_filesystem()
{
	int	retval;
	
	if (fs->flags & EXT2_FLAG_IB_DIRTY) {
		retval = ext2fs_write_inode_bitmap(fs);
		if (retval)
			com_err("ext2fs_write_inode_bitmap", retval, "");
	}
	if (fs->flags & EXT2_FLAG_BB_DIRTY) {
		retval = ext2fs_write_block_bitmap(fs);
		if (retval)
			com_err("ext2fs_write_block_bitmap", retval, "");
	}
	retval = ext2fs_close(fs);
	if (retval)
		com_err("ext2fs_close", retval, "");
	fs = NULL;
	return;
}

void do_close_filesys(int argc, char **argv)
{
	if (argc > 1) {
		com_err(argv[0], 0, "Usage: close_filesys");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	close_filesystem();
}

void do_show_super_stats(int argc, char *argv[])
{
	int	i;
	
	if (argc > 1) {
		com_err(argv[0], 0, "Usage: show_super");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	printf("Filesystem is read-%s\n", fs->flags & EXT2_FLAG_RW ?
	       "write" : "only");
	printf("Superblock size = %d\n", sizeof(struct ext2_super_block));
	printf("Block size = %d, fragment size = %d\n",
		EXT2_BLOCK_SIZE(fs->super), EXT2_FRAG_SIZE(fs->super));
	printf("%d inodes, %d free\n", fs->super->s_inodes_count,
	       fs->super->s_free_inodes_count);
	printf("%d blocks, %d free, %d free, first block = %d\n",
	       fs->super->s_blocks_count, fs->super->s_free_blocks_count,
	       fs->super->s_r_blocks_count, fs->super->s_first_data_block);
	printf("%d blocks per group\n", fs->super->s_blocks_per_group);
	printf("%d fragments per group\n", fs->super->s_frags_per_group);
	printf("%d inodes per group\n", fs->super->s_inodes_per_group);
	printf("%d inodes per block\n", EXT2_INODES_PER_BLOCK(fs->super));
	printf("%d group%s (%d descriptors block%s)\n",
		fs->group_desc_count, (fs->group_desc_count != 1) ? "s" : "",
		fs->desc_blocks, (fs->desc_blocks != 1) ? "s" : "");
	for (i = 0; i < fs->group_desc_count; i++)
		printf(" Group %2d: block bitmap at %d, "
		       "inode bitmap at %d, "
		       "inode table at %d\n"
		       "           %d free block%s, "
		       "%d free inode%s, "
		       "%d used director%s\n",
		       i, fs->group_desc[i].bg_block_bitmap,
		       fs->group_desc[i].bg_inode_bitmap,
		       fs->group_desc[i].bg_inode_table,
		       fs->group_desc[i].bg_free_blocks_count,
		       fs->group_desc[i].bg_free_blocks_count != 1 ? "s" : "",
		       fs->group_desc[i].bg_free_inodes_count,
		       fs->group_desc[i].bg_free_inodes_count != 1 ? "s" : "",
		       fs->group_desc[i].bg_used_dirs_count,
		       fs->group_desc[i].bg_used_dirs_count != 1 ? "ies" : "y");
}

/*
 * list directory
 */

struct list_dir_struct {
	int	col;
};

int list_dir_proc(struct ext2_dir_entry *dirent,
		  int	offset,
		  int	blocksize,
		  char	*buf,
		  void	*private)
{
	char	name[EXT2_NAME_LEN];
	char	tmp[EXT2_NAME_LEN + 16];

	struct list_dir_struct *ls = (struct list_dir_struct *) private;
	int	thislen;

	strncpy(name, dirent->name, dirent->name_len);
	name[dirent->name_len] = '\0';

	sprintf(tmp, "%d (%d) %s   ", dirent->inode, dirent->rec_len, name);
	thislen = strlen(tmp);

	if (ls->col + thislen > 80) {
		printf("\n");
		ls->col = 0;
	}
	printf("%s", tmp);
	ls->col += thislen;
		
	return 0;
}


struct list_blocks_struct {
	FILE	*f;
	int	total;
};

int list_blocks_proc(ext2_filsys fs, blk_t *blocknr, int blockcnt, void *private)
{
	struct list_blocks_struct *lb = (struct list_blocks_struct *) private;

	fprintf(lb->f, "%d ", *blocknr);
	lb->total++;
	return 0;
}


void dump_blocks(FILE *f, ino_t inode)
{
	struct list_blocks_struct lb;

	fprintf(f, "BLOCKS:\n");
	lb.total = 0;
	lb.f = f;
	ext2fs_block_iterate(fs,inode,0,NULL,list_blocks_proc,(void *)&lb);
	if (lb.total)
		fprintf(f, "\nTOTAL: %d\n", lb.total);
	fprintf(f,"\n");
}


void dump_inode(ino_t inode_num, struct ext2_inode inode)
{
	char *i_type;
	FILE	*out;
	
	out = open_pager();
	if (S_ISDIR(inode.i_mode)) i_type = "directory";
	else if (S_ISREG(inode.i_mode)) i_type = "regular";
	else if (S_ISLNK(inode.i_mode)) i_type = "symlink";
	else if (S_ISBLK(inode.i_mode)) i_type = "block special";
	else if (S_ISCHR(inode.i_mode)) i_type = "character special";
	else if (S_ISFIFO(inode.i_mode)) i_type = "FIFO";
	else if (S_ISSOCK(inode.i_mode)) i_type = "socket";
	else i_type = "bad type";
	fprintf(out, "Inode: %d   Type: %s    ", inode_num, i_type);
	fprintf(out, "Mode:  %04o   Flags: 0x%x\n", inode.i_mode & 0777,
		inode.i_flags);
	fprintf(out, "User: %5d   Group: %5d   Size: %d\n",  
		inode.i_uid, inode.i_gid, inode.i_size);
	fprintf(out, "File ACL: %d    Directory ACL: %d\n",
		inode.i_file_acl, inode.i_dir_acl);
	fprintf(out, "Links: %d   Blockcount: %d\n", inode.i_links_count,
		inode.i_blocks);
	fprintf(out, "Fragment:  Address: %d    Number: %d    Size: %d\n",
		inode.i_faddr, inode.i_frag, inode.i_fsize);
	fprintf(out, "ctime: 0x%08x -- %s", inode.i_ctime,
		ctime(&inode.i_ctime));
	fprintf(out, "atime: 0x%08x -- %s", inode.i_atime,
		ctime(&inode.i_atime));
	fprintf(out, "mtime: 0x%08x -- %s", inode.i_mtime,
		ctime(&inode.i_mtime));
	if (inode.i_dtime) 
	  fprintf(out, "dtime: 0x%08x -- %s", inode.i_dtime,
		  ctime(&inode.i_dtime));
	if (S_ISLNK(inode.i_mode) && inode.i_blocks == 0)
		fprintf(out, "Fast_link_dest: %s\n", (char *)inode.i_block);
	else
		dump_blocks(out, inode_num);
	close_pager(out);
}


void do_stat(int argc, char *argv[])
{
	ino_t	inode;
	struct ext2_inode inode_buf;
	int retval;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: stat <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	retval = ext2fs_read_inode(fs,inode,&inode_buf);
	if (retval) 
	  {
	    com_err(argv[0], 0, "Reading inode");
	    return;
	  }

	dump_inode(inode,inode_buf);
	return;
}

void do_chroot(int argc, char *argv[])
{
	ino_t inode;
	int retval;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: chroot <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	retval = ext2fs_check_directory(fs, inode);
	if (retval)  {
		com_err(argv[1], retval, "");
		return;
	}
	root = inode;
}



void do_clri(int argc, char *argv[])
{
	ino_t inode;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: clri <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	if (!(fs->flags & EXT2_FLAG_RW)) {
		com_err(argv[0], 0, "Filesystem opened read/only");
		return;
	}
	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	if (!ext2fs_test_inode_bitmap(fs,fs->inode_map,inode))
		com_err(argv[0], 0, "Warning: inode already clear");
	ext2fs_unmark_inode_bitmap(fs,fs->inode_map,inode);
	ext2fs_mark_ib_dirty(fs);
}

void do_seti(int argc, char *argv[])
{
	ino_t inode;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: seti <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	if (!(fs->flags & EXT2_FLAG_RW)) {
		com_err(argv[0], 0, "Filesystem opened read/only");
		return;
	}
	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	if (ext2fs_test_inode_bitmap(fs,fs->inode_map,inode))
		com_err(argv[0], 0, "Warning: inode already set");
	ext2fs_mark_inode_bitmap(fs,fs->inode_map,inode);
	ext2fs_mark_ib_dirty(fs);
}

void do_testi(int argc, char *argv[])
{
	ino_t inode;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: testi <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	if (ext2fs_test_inode_bitmap(fs,fs->inode_map,inode))
		printf("Inode %d is marked in use\n", inode);
	else
		printf("Inode %d is not in use\n", inode);
}


void do_clrb(int argc, char *argv[])
{
	blk_t block;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: clrb <block>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	if (!(fs->flags & EXT2_FLAG_RW)) {
		com_err(argv[0], 0, "Filesystem opened read/only");
		return;
	}
	block = strtoul(argv[1], NULL, 0);
	if (!block) {
		com_err(argv[0], 0, "No block 0");
		return;
	} 
	if (!ext2fs_test_block_bitmap(fs,fs->block_map,block))
		com_err(argv[0], 0, "Warning: block already clear");
	ext2fs_unmark_block_bitmap(fs,fs->block_map,block);
	ext2fs_mark_bb_dirty(fs);
}

void do_setb(int argc, char *argv[])
{
	blk_t block;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: setb <block>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	if (!(fs->flags & EXT2_FLAG_RW)) {
		com_err(argv[0], 0, "Filesystem opened read/only");
		return;
	}
	block = strtoul(argv[1], NULL, 0);
	if (!block) {
		com_err(argv[0], 0, "No block 0");
		return;
	} 
	if (ext2fs_test_block_bitmap(fs,fs->block_map,block))
		com_err(argv[0], 0, "Warning: block already set");
	ext2fs_mark_block_bitmap(fs,fs->block_map,block);
	ext2fs_mark_bb_dirty(fs);
}

void do_testb(int argc, char *argv[])
{
	blk_t block;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: testb <block>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	block = strtoul(argv[1], NULL, 0);
	if (!block) {
		com_err(argv[0], 0, "No block 0");
		return;
	} 
	if (ext2fs_test_block_bitmap(fs,fs->block_map,block))
		printf("Block %d marked in use\n", block);
	else printf("Block %d not in use\n", block);
}

void modify_char(char *prompt, char *format, u_char *val)
{
	char buf[200];
	
	sprintf(buf, format, *val);
	printf("%30s    [%s] ", prompt, buf);
	gets(buf);
	if (!buf[0])
		return;
	*val = strtol(buf, NULL, 0);
}

void modify_short(char *prompt, char *format, u_short *val)
{
	char buf[200];
	
	sprintf(buf, format, *val);
	printf("%30s    [%s] ", prompt, buf);
	gets(buf);
	if (!buf[0])
		return;
	*val = strtol(buf, NULL, 0);
}

void modify_long(char *prompt, char *format, u_long *val)
{
	char buf[200];
	
	sprintf(buf, format, *val);
	printf("%30s    [%s] ", prompt, buf);
	gets(buf);
	if (!buf[0])
		return;
	*val = strtol(buf, NULL, 0);
}


void do_modify_inode(int argc, char *argv[])
{
	struct ext2_inode inode;
	ino_t inode_num;
	int i;
	errcode_t	retval;
	char	buf[80];
	char *hex_format = "0x%x";
	char *octal_format = "0%o";
	char *decimal_format = "%d";
	
	if (argc != 2) {
		com_err(argv[0], 0, "Usage: modify_inode <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	if (!(fs->flags & EXT2_FLAG_RW)) {
		com_err(argv[0], 0, "Filesystem opened read/only");
		return;
	}

	inode_num = string_to_inode(argv[1]);
	if (!inode_num) 
		return;

	retval = ext2fs_read_inode(fs, inode_num, &inode);
	if (retval) {
		com_err(argv[1], retval, "while trying to read inode %d",
			inode_num);
		return;
	}
	
	modify_short("Mode", octal_format, &inode.i_mode);
	modify_short("User ID", decimal_format, &inode.i_uid);
	modify_short("Group ID", decimal_format, &inode.i_gid);
	modify_long("Size", decimal_format, &inode.i_size);
	modify_long("Creation time", decimal_format, &inode.i_ctime);
	modify_long("Modification time", decimal_format, &inode.i_mtime);
	modify_long("Access time", decimal_format, &inode.i_atime);
	modify_long("Deletion time", decimal_format, &inode.i_dtime);
	modify_short("Link count", decimal_format, &inode.i_links_count);
	modify_long("Block count", decimal_format, &inode.i_blocks);
	modify_long("File flags", hex_format, &inode.i_flags);
	modify_long("Reserved1", decimal_format, &inode.i_reserved1);
	modify_long("File acl", decimal_format, &inode.i_file_acl);
	modify_long("Directory acl", decimal_format, &inode.i_dir_acl);
	modify_long("Fragment address", decimal_format, &inode.i_faddr);
	modify_char("Fragment number", decimal_format, &inode.i_frag);
	modify_char("Fragment size", decimal_format, &inode.i_fsize);
	for (i=0;  i < EXT2_NDIR_BLOCKS; i++) {
		sprintf(buf, "Direct Block #%d", i);
		modify_long(buf, decimal_format, &inode.i_block[i]);
	}
	modify_long("Indirect Block", decimal_format,
		    &inode.i_block[EXT2_IND_BLOCK]);    
	modify_long("Double Indirect Block", decimal_format,
		    &inode.i_block[EXT2_DIND_BLOCK]);
	modify_long("Triple Indirect Block", decimal_format,
		    &inode.i_block[EXT2_TIND_BLOCK]);
	retval = ext2fs_write_inode(fs, inode_num, &inode);
	if (retval) {
		com_err(argv[1], retval, "while trying to write inode %d",
			inode_num);
		return;
	}
}

void do_list_dir(int argc, char *argv[])
{
	ino_t	inode;
	int	retval;
	struct list_dir_struct ls;
	
	if (argc > 2) {
		com_err(argv[0], 0, "Usage: list_dir [pathname]");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	if (argc == 2)
		inode = string_to_inode(argv[1]);
	else
		inode = cwd;
	if (!inode)
		return;

	ls.col = 0;
	retval = ext2fs_dir_iterate(fs, inode, DIRENT_FLAG_INCLUDE_EMPTY,
				    0, list_dir_proc, &ls);
	printf("\n");
	if (retval)
		com_err(argv[1], retval, "");

	return;
}

void do_change_working_dir(int argc, char *argv[])
{
	ino_t	inode;
	int	retval;
	
	if (argc != 2) {
		com_err(argv[0], 0, "Usage: cd <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	inode = string_to_inode(argv[1]);
	if (!inode) 
		return;

	retval = ext2fs_check_directory(fs, inode);
	if (retval) {
		com_err(argv[1], retval, "");
		return;
	}
	cwd = inode;
	return;
}

void do_iname(int argc, char *argv[])
{
	ino_t	inode;
	
	if (argc > 2) {
		com_err(argv[0], 0, "Usage: iname <inode>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	inode = strtoul(argv[1], NULL, 0);
	com_err(argv[0],0,"Function unimplemented");
	return;
}

void do_print_working_directory(int argc, char *argv[])
{
	int	retval;
	char	*pathname = NULL;
	
	if (argc > 1) {
		com_err(argv[0], 0, "Usage: print_working_directory");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	retval = ext2fs_get_pathname(fs, cwd, 0, &pathname);
	if (retval) {
		com_err(argv[0], retval,
			"while trying to get pathname of cwd");
	}
	printf("[pwd]   INODE: %6d  PATH: %s\n", cwd, pathname);
	free(pathname);
	retval = ext2fs_get_pathname(fs, root, 0, &pathname);
	if (retval) {
		com_err(argv[0], retval,
			"while trying to get pathname of root");
	}
	printf("[root]  INODE: %6d  PATH: %s\n", root, pathname);
	free(pathname);
	return;
}


void make_link(char *sourcename, char *destname)
{
	ino_t	inode;
	int	retval;
	ino_t	dir;
	char	*dest, *cp, *basename;

	/*
	 * Get the source inode
	 */
	inode = string_to_inode(sourcename);
	if (!inode)
		return;
	basename = strrchr(sourcename, '/');
	if (basename)
		basename++;
	else
		basename = sourcename;
	/*
	 * Figure out the destination.  First see if it exists and is
	 * a directory.  
	 */
	if (! (retval=ext2fs_namei(fs, root, cwd, destname, &dir)))
		dest = basename;
	else {
		/*
		 * OK, it doesn't exist.  See if it is
		 * '<dir>/basename' or 'basename'
		 */
		cp = strrchr(destname, '/');
		if (cp) {
			*cp = 0;
			dir = string_to_inode(destname);
			if (!dir)
				return;
			dest = cp+1;
		} else {
			dir = cwd;
			dest = destname;
		}
	}
	
	retval = ext2fs_link(fs, dir, dest, inode, 0);
	if (retval)
		com_err("make_link", retval, "");
	return;
}


void do_link(int argc, char *argv[])
{
	if (argc != 3) {
		com_err(argv[0], 0, "Usage: link <source_file> <dest_name>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	make_link(argv[1], argv[2]);
}


void unlink_file_by_name(char *filename)
{
	int	retval;
	ino_t	dir;
	char	*basename;
	
	basename = strrchr(filename, '/');
	if (basename) {
		*basename++ = '0';
		dir = string_to_inode(filename);
		if (!dir)
			return;
	} else {
		dir = cwd;
		basename = filename;
	}
	retval = ext2fs_unlink(fs, dir, basename, 0, 0);
	if (retval)
		com_err("unlink_file_by_name", retval, "");
	return;
}

void do_unlink(int argc, char *argv[])
{
	if (argc != 2) {
		com_err(argv[0], 0, "Usage: unlink <pathname>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	unlink_file_by_name(argv[1]);
}

void do_find_free_block(int argc, char *argv[])
{
	blk_t	free_blk, goal;
	errcode_t	retval;
	
	if (argc > 2 || *argv[1] == '?') {
		com_err(argv[0], 0, "Usage: find_free_block <goal>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	if (argc > 1)
		goal = strtol(argv[1], NULL, 0);
	else
		goal = fs->super->s_first_data_block;

	retval = ext2fs_new_block(fs, goal, 0, &free_blk);
	if (retval)
		com_err("ext2fs_new_block", retval, "");
	else
		printf("Free block found: %d\n", free_blk);

}

void do_find_free_inode(int argc, char *argv[])
{
	ino_t	free_inode, dir;
	int	mode;
	int	retval;
	
	if (argc > 3 || *argv[1] == '?') {
		com_err(argv[0], 0, "Usage: find_free_inode <dir> <mode>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	if (argc > 1)
		dir = strtol(argv[1], NULL, 0);
	else
		dir = root;
	if (argc > 2)
		mode = strtol(argv[2], NULL, 0);
	else
		mode = 010755;

	retval = ext2fs_new_inode(fs, dir, mode, 0, &free_inode);
	if (retval)
		com_err("ext2fs_new_inode", retval, "");
	else
		printf("Free inode found: %d\n", free_inode);
}

/*
 * Doesn't change directories count --->  add this later
 */

void do_mkdir(int argc, char *argv[])
{
	blk_t block_num;
	ino_t inode_num;
	struct ext2_inode inode;
	char	*cp;
	ino_t	parent;
	char	*name;
	char	*block;
	errcode_t retval;

	if (check_fs_open(argv[0]))
		return;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: mkdir <file>");
		return;
	}

	cp = strrchr(argv[1], '/');
	if (cp) {
		*cp = 0;
		parent = string_to_inode(argv[1]);
		if (!parent) {
			com_err(argv[1], ENOENT, "");
			return;
		}
		name = cp+1;
	} else {
		parent = cwd;
		name = argv[1];
	}

	retval = ext2fs_new_inode(fs, parent, S_IFDIR | 0755,
				  fs->inode_map, &inode_num);
	if (retval) {
		com_err("ext2fs_new_inode", retval, "");
		return;
	}

	retval = ext2fs_new_block(fs, 0, fs->block_map, &block_num);
	if (retval) {
		com_err("ext2fs_new_block", retval, "");
		return;
	}
	
	memset(&inode, 0, sizeof(struct ext2_inode));

	inode.i_mode = S_IFDIR | 0755;
	inode.i_uid = inode.i_gid = 0;
	inode.i_blocks = 2;
	inode.i_block[0] = block_num;
	inode.i_links_count = 2;
	inode.i_ctime = inode.i_atime = inode.i_mtime = time(NULL);
	inode.i_size = fs->blocksize;

	/*
	 *  create empty directory block...
	 */
	retval = ext2fs_new_dir_block(fs, inode_num, parent, &block);
	if (retval) {
		com_err("ext2fs_new_dir_block", retval, "");
		return;
	}
	retval = io_channel_write_blk(fs->io, block_num, 1, block);
	if (retval) {
		com_err("io_channel_write_blk", retval, "");
		return;
	}
	free(block);

	/*
	 * Now write out the inode...
	 */
	retval = ext2fs_write_inode(fs, inode_num, &inode); 
	if (retval) {
		com_err(argv[0], retval, "while writing directory inode");
		return;
	} 

	/* mark block used */
	if (ext2fs_test_block_bitmap(fs,fs->block_map,block_num))
		com_err(argv[0], 0, "Warning: block already in use!");
	ext2fs_mark_block_bitmap(fs,fs->block_map,block_num);
	ext2fs_mark_bb_dirty(fs);

	/* mark inode used */
	if (ext2fs_test_inode_bitmap(fs,fs->inode_map,inode_num))
		com_err(argv[0], 0, "Warning: inode already in use!");
	ext2fs_mark_inode_bitmap(fs,fs->inode_map,inode_num);
	ext2fs_mark_ib_dirty(fs);

	/* Update superblock counts */
	fs->super->s_free_blocks_count--;
	fs->super->s_free_inodes_count--;
	ext2fs_mark_super_dirty(fs);

	/* XXX Fix group descriptor's directory counts */

	/* Finally, make link in directory hierarchy */
	retval = ext2fs_link(fs, parent, name, inode_num, 0);
	if (retval) {
		com_err("ext2fs_link", retval, "");
		return;
	}

	/* Fix parent's link count */
	retval = ext2fs_read_inode(fs, parent, &inode);
	if (retval) {
		com_err("ext2fs_read_inode", retval,
			"while reading parent inode");
		return;
	}
	inode.i_links_count++;
	retval = ext2fs_write_inode(fs, parent, &inode);
	if (retval) {
		com_err("ext2fs_write_inode", retval,
			"while writing parent inode");
		return;
	}
}

void do_rmdir(int argc, char *argv[])
{
	printf("Unimplemented\n");
}


int release_blocks_proc(ext2_filsys fs, blk_t *blocknr, int blockcnt, void *private)
{
	printf("%d ", *blocknr);
	ext2fs_unmark_block_bitmap(fs,fs->block_map,*blocknr);
	return 0;
}

void kill_file_by_inode(ino_t inode)
{
	struct ext2_inode inode_buf;

	ext2fs_read_inode(fs, inode, &inode_buf);
	inode_buf.i_dtime = time(NULL);
	ext2fs_write_inode(fs, inode, &inode_buf);

	printf("Kill file by inode %d\n", inode);
	ext2fs_block_iterate(fs,inode,0,NULL,release_blocks_proc,NULL);
	ext2fs_unmark_inode_bitmap(fs,fs->inode_map,inode);

	ext2fs_mark_bb_dirty(fs);
	ext2fs_mark_ib_dirty(fs);
}


void do_kill_file(int argc, char *argv[])
{
	ino_t inode_num;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: kill_file <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	inode_num = string_to_inode(argv[1]);
	if (!inode_num) {
		com_err(argv[0], 0, "Cannot find file");
		return;
	}
	kill_file_by_inode(inode_num);
}

void do_rm(int argc, char *argv[])
{
	int retval;
	ino_t inode_num;
	struct ext2_inode inode;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: rm <filename>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;

	retval = ext2fs_namei(fs, root, cwd, argv[1], &inode_num);
	if (retval) {
		com_err(argv[0], 0, "Cannot find file");
		return;
	}

	retval = ext2fs_read_inode(fs,inode_num,&inode);
	if (retval) {
		com_err(argv[0], retval, "while reading file's inode");
		return;
	}

	if (S_ISDIR(inode.i_mode)) {
		com_err(argv[0], 0, "file is a directory");
		return;
	}

	--inode.i_links_count;
	retval = ext2fs_write_inode(fs,inode_num,&inode);
	if (retval) {
		com_err(argv[0], retval, "while writing inode");
		return;
	}

	unlink_file_by_name(argv[1]);
	if (inode.i_links_count == 0)
		kill_file_by_inode(inode_num);
}

void do_show_debugfs_params(int argc, char *argv[])
{
	FILE *out = stdout;

	fprintf(out, "Open mode: read-%s\n",
		fs->flags & EXT2_FLAG_RW ? "write" : "only");
	fprintf(out, "Filesystem in use: %s\n",
		fs ? fs->device_name : "--none--");
}

void do_expand_dir(int argc, char *argv[])
{
	ino_t inode;
	int retval;

	if (argc != 2) {
		com_err(argv[0], 0, "Usage: expand_dir <file>");
		return;
	}
	if (check_fs_open(argv[0]))
		return;
	inode = string_to_inode(argv[1]);
	if (!inode)
		return;

	retval = ext2fs_expand_dir(fs, inode);
	if (retval)
		com_err("ext2fs_expand_dir", retval, "");
	return;
}

void main(int argc, char **argv)
{
	int	retval;
	int	sci_idx;
	char	*usage = "Usage: debugfs [-w] [device]";
	char	c;
	int open_flags = 0;
	
	initialize_ext2_error_table();

	while ((c = getopt (argc, argv, "w")) != EOF) {
		switch (c) {
		case 'w':
			open_flags = EXT2_FLAG_RW;
			break;
		default:
			com_err(argv[0], 0, usage);
			return;
		}
	}
	if (optind < argc)
		open_filesystem(argv[optind], open_flags);
	
	sci_idx = ss_create_invocation("debugfs", "0.0", (char *) NULL,
				       &debug_cmds, &retval);
	if (retval) {
		ss_perror(sci_idx, retval, "creating invocation");
		exit(1);
	}

	(void) ss_add_request_table (sci_idx, &ss_std_requests, 1, &retval);
	if (retval) {
		ss_perror(sci_idx, retval, "adding standard requests");
		exit (1);
	}

	ss_listen(sci_idx);

	if (fs)
		close_filesystem();
	
	exit(0);
}

