/*
 * Copyright (C) 2000-2003 ASANO Masahiro
 */

#include "def.h"

#define __KERNEL__
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/mount.h>	/* for vfsmount */
#include <linux/pagemap.h>	/* for as.flags */

#include "printdev.h"
#include "flags_fs.h"

void
mprint_dev_t(dev)
	dev_t dev;
{
	mprintf("%3d,%-3d", MAJOR(dev), MINOR(dev));
}

void
prhead_dentry()
{
	mprintf(SPTR" CNT "SPTR" "SPTR" HLCSA "SPTR" "SPTR" VF NAME\n",
		"ADDR", "INODE", "PARENT", "D_OP", "SB");
}

addr_t
print_dentry(addr, sw, subdir, parent)
	addr_t addr;
	int sw, subdir, parent;
{
	struct dentry de;
	addr_t child, next;

	switch (sw) {
	case 1:	/* hash */
		addr -= OFFSET(struct dentry, d_hash);
		break;
	case 2:	/* lru */
		addr -= OFFSET(struct dentry, d_lru);
		break;
	}
	memread(addr, sizeof(de), &de, "dentry");

	if (subdir) {
		for (next = (addr_t)de.d_subdirs.next; next != (addr_t)&((struct dentry *)addr)->d_subdirs; next = (addr_t)de.d_child.next) {
			child = next - OFFSET(struct dentry, d_child);
			memread(child, sizeof(de), &de, "dentry");
			(void)print_dentry(child, 0, 0, 0);
		}
		return 0;
	}

	mprintf(FPTR, addr);
	mprintf(" %3x " FPTR " " FPTR, ATOMIC_READ(de.d_count), de.d_inode, de.d_parent);
	mprintf(" %c%c%c%c%c ",
		(de.d_hash.next   !=&((struct dentry *)addr)->d_hash   )? 'H': '-',
		(de.d_lru.next    !=&((struct dentry *)addr)->d_lru    )? 'L': '-',
		(de.d_child.next  !=&((struct dentry *)addr)->d_child  )? 'C': '-',
		(de.d_subdirs.next!=&((struct dentry *)addr)->d_subdirs)? 'S': '-',
		(de.d_alias.next  !=&((struct dentry *)addr)->d_alias  )? 'A': '-');
	mprintf(FPTR " " FPTR " %2lx ", de.d_op, de.d_sb, de.d_vfs_flags);

	if (de.d_name.name == ((struct dentry *)addr)->d_iname)
		mprintstr(de.d_iname, sizeof(de.d_iname));
	else if (de.d_name.name == NULL)
		mprintf("[null]");
	else {
		char qbuf[256];
		int len = de.d_name.len < sizeof(qbuf)? de.d_name.len: sizeof(qbuf);
		memread((addr_t)de.d_name.name, len, qbuf, "dentry.d_name");
		mprintstr(qbuf, len);
	}
	mprintf("\n");

	if (parent) {
		if (de.d_parent && (addr_t)de.d_parent != addr) {
			return print_dentry(de.d_parent, 0, 0, 1);
		}
		return 0;
	}

	if (sw == 2)
		return (addr_t)de.d_lru.next;
	else
		return (addr_t)de.d_hash.next;
}

void
prhead_file()
{
	mprintf(SPTR" "SPTR" "SPTR" MOD       POS CNT   UID   GID ERR "SPTR" FLAGS\n",
		"ADDR", "DENTRY", "F_OP", "PRIVATE");
}

addr_t
print_file(addr, full)
	addr_t addr;
	int full;
{
	struct file f;
	static const struct bitname fflags[] = {
		{ O_APPEND,	"append" },
		{ O_NONBLOCK,	"nonblock" },
		{ O_SYNC,	"sync" },
		{ FASYNC,	"fasync" },
		{ O_DIRECT,	"direct" },
		{ O_LARGEFILE,	"largefile" },
		{ 0,		NULL }
	};

	memread(addr, sizeof(f), &f, "file");
	mprintf(FPTR " ", addr);

	mprintf(FPTR " " FPTR " ", f.f_dentry, f.f_op);
	switch (f.f_mode) {
	case 0:	mprintf(" -");	break;
	case 1: mprintf("r ");	break;
	case 2: mprintf(" w");	break;
	case 3: mprintf("rw");	break;
	default:mprintf(" ?");	break;
	}
	mprintf(" %10Lx %3x %5d %5d %3d", f.f_pos, ATOMIC_READ(f.f_count), f.f_uid, f.f_gid, f.f_error);
	mprintf(" " FPTR, f.private_data);
	mprintbit(fflags, f.f_flags & ~3);
	mprintf("\n");

	if (full) {
		mprintf("\tvfsmnt:     " FPTR "\n", f.f_vfsmnt);
		mprintf("\towener.pid: %x\n", f.f_owner.pid);
		mprintf("\tversion:    %lx\n", f.f_version);
		mprintf("\tsecurity    " FPTR "\n", f.f_security);
	}
	return (addr_t)f.f_list.next;
}

void
prhead_filesystem()
{
	mprintf(SPTR"        NAME "SPTR" "SPTR" "SPTR" S FLAG\n",
		"ADDR", "GET_SB", "KILL_SB", "OWNER");
}

addr_t
print_file_system_type(addr)
	addr_t addr;
{
	struct file_system_type fs;
	char buf[16];

	mprintf(FPTR, addr);
	memread(addr, sizeof(fs), &fs, "file_system_type");
	memread((addr_t)fs.name, sizeof(buf), buf, "file system type name");

	mprintf(" %11s " FPTR " " FPTR " " FPTR, buf, fs.get_sb, fs.kill_sb, fs.owner);
	if ((addr_t)fs.fs_supers.next == addr + OFFSET(struct file_system_type,fs_supers))
		mprintf(" 0");
	else if (fs.fs_supers.next == fs.fs_supers.prev)
		mprintf(" 1");
	else
		mprintf(" *");
	mprintbit(fsflags, fs.fs_flags);
	mprintf("\n");
	return (addr_t)fs.next;
}

void
prhead_inode()
{
	mprintf(SPTR"        INO CNT   DEV  LINK MODE   UID       SIZE "SPTR" HLDD\n",
		"ADDR", "MAPPING");
}

addr_t
print_inode(addr, full)
	addr_t addr;
	int full;
{
	struct inode inode;
	const char *p;

	memread(addr, sizeof(struct inode), &inode, "inode");
	mprintf(FPTR " ", addr);
	mprintf("%10ld %3x ", inode.i_ino, ATOMIC_READ(inode.i_count));
	if (inode.i_sb) {
		struct super_block sb;
		memread((addr_t)inode.i_sb, sizeof(struct super_block), &sb, "super_block");
		mprint_dev_t(sb.s_dev);
	} else {
		mprint_dev_t(-1);
	}
	mprintf(" %3x ", inode.i_nlink);
	mprint_mode(inode.i_mode);
	mprintf(" %5d %10Lx", inode.i_uid, inode.i_size);
	mprintf(" " FPTR, inode.i_mapping);
	mprintf(" %c%c%c\n",
		(inode.i_hash.pprev        !=NULL)? 'H': '-',
		(inode.i_list.next         !=&((struct inode *)addr)->i_list         )? 'L': '-',
		(inode.i_dentry.next       !=&((struct inode *)addr)->i_dentry       )? 'D': '-');

	if (full) {
		mprintf("\trdev:      ");
		mprint_dev_t(inode.i_rdev);
		mprintf("\n");
		mprintf("\t[amc]time: %08lx  %08lx  %08lx\n",
			inode.i_atime.tv_sec,
			inode.i_mtime.tv_sec,
			inode.i_ctime.tv_sec);
		mprintf("\tblocks:    %ld  (* blksize %ld)\n", inode.i_blocks, inode.i_blksize);
		mprintf("\tversion:   %ld\n", inode.i_version);
		mprintf("\tsem:       ");
		mprint_semaphore(&inode.i_sem);
		mprintf("\n");
		mprintf("\top:        " FPTR "  ", inode.i_op);
		p = getsymstr((addr_t)inode.i_op);
		if (p)
			mprintf("(%s)\n", p);
		else
			mprintf("\n");
		mprintf("\tfop:       " FPTR "  ", inode.i_fop);
		if (p)
			mprintf("(%s)\n", p);
		else
			mprintf("\n");
		mprintf("\tsb:        " FPTR "\n", inode.i_sb);
		mprintf("\tflock:     " FPTR "\n", inode.i_flock);
		if (inode.i_pipe)
		mprintf("\tpipe:      " FPTR "\n", inode.i_pipe);
		if (inode.i_bdev)
		mprintf("\tbdev:      " FPTR "\n", inode.i_bdev);
		if (inode.i_cdev)
		mprintf("\tcdev:      " FPTR "\n", inode.i_cdev);
		mprintf("\tstate:     %lx ", inode.i_state);
		mprintbit(istate, inode.i_state);
		mprintf("\n\tflags:     %x\n", inode.i_flags);
		mprintf("\tgen:       %u\n", inode.i_generation);
		mprintf("\tgeneric_ip:" FPTR "\n", inode.u.generic_ip);
		mprintf("\n");
	}
	return (addr_t)inode.i_hash.next;
}

void
prhead_superblock()
{
	mprintf(SPTR"   DEV   BSIZE D        TYPE "SPTR" "SPTR" "SPTR" LDIF FLAGS\n",
		"ADDR", "S_OP", "ROOT", "BDEV");
}

addr_t
print_superblock(addr, full)
	addr_t addr;
	int full;
{
	struct super_block sb;
	struct file_system_type fstype;
	char buf[16];

	memread(addr, sizeof(struct super_block), &sb, "super_block");
	mprintf(FPTR " ", addr);

	mprint_dev_t(sb.s_dev);
	mprintf(" %5lx %x ", sb.s_blocksize, sb.s_dirt);

	if (sb.s_type) {
		memread((addr_t)sb.s_type, sizeof(fstype), &fstype, "file system type");
		memread((addr_t)fstype.name, sizeof(buf), buf, "file system type name");
		mprintf("%11s", buf);
	} else {
		mprintf("%11s", "-");
	}

	mprintf(" " FPTR " " FPTR " " FPTR, sb.s_op, sb.s_root, sb.s_bdev);

#define ISEMPTY(x)	((addr_t)sb.x.next == addr+OFFSET(struct super_block,x))
	mprintf(" %c%c%c%c",
		ISEMPTY(s_list)  ? '-': 'l',
		ISEMPTY(s_dirty) ? '-': 'd',
		ISEMPTY(s_io)    ? '-': 'i',
		ISEMPTY(s_files) ? '-': 'f');

	mprintbit(sflags, sb.s_flags);
	mprintf("\n");

	if (full) {
		mprintf("\tmaxbytes:         %llx\n", sb.s_maxbytes);
		mprintf("\tdquot_operations: " FPTR "\n", sb.dq_op);
		mprintf("\tmagic:            %lx\n", sb.s_magic);
		mprintf("\tlock:             ");
		mprint_semaphore(&sb.s_lock);
		mprintf("\n");
		mprintf("\tid:               ");
		mprintstr(sb.s_id, sizeof(sb.s_id));
		mprintf("\n");
		mprintf("\tcount:            %x\n", sb.s_count);
		mprintf("\tactive:           %x\n", ATOMIC_READ(sb.s_active));
#define SEMPTY(list) \
	(addr + OFFSET(struct super_block, list) == (addr_t)sb.list.next)
		mprintf("\tdirty:            ");
		if (SEMPTY(s_dirty))
			mprintf(SPTR " " SPTR "\n", "-", "-");
		else
			mprintf(FPTR " " FPTR "\n",
				sb.s_dirty.next, sb.s_dirty.prev);
		mprintf("\tio:               ");
		if (SEMPTY(s_io))
			mprintf(SPTR " " SPTR "\n", "-", "-");
		else
			mprintf(FPTR " " FPTR "\n",
				sb.s_io.next, sb.s_io.prev);
		mprintf("\tfiles:            ");
		if (SEMPTY(s_files))
			mprintf(SPTR " " SPTR "\n", "-", "-");
		else
			mprintf(FPTR " " FPTR "\n",
				sb.s_files.next, sb.s_files.prev);
		mprintf("\tfs_info           " FPTR "\n", sb.s_fs_info);
	}

	if (full >= 2) {
		addr_t faddr = (addr_t)sb.s_files.next;
		prhead_file();
		while (faddr != addr + OFFSET(struct super_block, s_files)) {
			faddr = print_file(faddr - OFFSET(struct file, f_list),
0);
		}
		mprintf("\n");
	}

	return (addr_t)sb.s_list.next;
}

addr_t
print_superblock_union(addr, fstype, func, arg)
	addr_t addr;
	addr_t fstype;
	addr_t (*func)();
	int arg;
{
	struct super_block sb;

	memread(addr, sizeof(struct super_block), &sb, "super_block");

	if ((addr_t)sb.s_type == fstype)
		func(sb.s_fs_info, sb.s_fs_info, arg);
	return (addr_t)sb.s_list.next;
}

addr_t
print_super_block_filelist(addr, full)
	addr_t addr;
	int full;
{
	struct super_block sb;
	addr_t faddr;

	memread(addr, sizeof(struct super_block), &sb, "super_block");
	faddr = (addr_t)sb.s_files.next;
	while (faddr && faddr != addr + OFFSET(struct super_block, s_files)) {
		faddr = print_file(faddr, full);
	}
	return (addr_t)sb.s_list.next;
}

void
prhead_vfsmount()
{
	mprintf(SPTR" "SPTR" "SPTR" "SPTR" "SPTR"  CNT  F  DEVICE\n",
		"ADDR", "MOUNT-D", "ROOT-D", "PARENT", "SB");
}

addr_t
print_vfsmount(addr, chain)
	addr_t addr;
	int chain;
{
	struct vfsmount m;
	char path[PATH_MAX];

	if (chain) {
		addr -= OFFSET(struct vfsmount, mnt_hash);
	}

	memread(addr, sizeof(struct vfsmount), &m, "vfsmount");
	mprintf(FPTR " ", addr);
	mprintf(FPTR " " FPTR " " FPTR " " FPTR " ",
		m.mnt_mountpoint, m.mnt_root, m.mnt_parent, m.mnt_sb);
	mprintf("%4x %2x  ", ATOMIC_READ(m.mnt_count), m.mnt_flags);

	if (m.mnt_devname) {
		memread((addr_t)m.mnt_devname, sizeof(path), path, "mnt_devname");
		mprintf("%s", path);
	} else {
		mprintf("-");
	}
	mprintf("\n");

	if (chain) {
		return (addr_t)m.mnt_hash.next;
	}
	return (addr_t)m.mnt_list.next - OFFSET(struct vfsmount, mnt_list);
}

void
print_files_stat(addr)
	addr_t addr;
{
	struct files_stat_struct filst;

	memread(addr, sizeof(filst), &filst, "files_stat");
	mprintf("nr_files:      %5x  (%d)\n", filst.nr_files, filst.nr_files);
	mprintf("nr_free_files: %5x  (%d)\n", filst.nr_free_files, filst.nr_free_files);
	mprintf("max_files:     %5x  (%d)\n", filst.max_files, filst.max_files);
}

void
print_address_space(addr, plist)
	addr_t addr;
	int plist;
{
	struct address_space as;
	extern void print_gfp();
	addr_t paddr, top;
	addr_t print_page();
	void prhead_page();
	static const struct bitname asflags[] = {
		{ 1 << AS_ENOSPC,	"ENOSPC" },
		{ 1 << AS_EIO,		"EIO" },
		{ 0,			NULL }
	};

	mprintf("ADDR:         " FPTR "\n", addr);
	memread(addr, sizeof(as), &as, "address_space");

#undef ISEMPTY
#define ISEMPTY(list) \
	((addr_t)as.list.next == addr + OFFSET(struct address_space, list))

	mprintf("clean_pages:  ");
	if (ISEMPTY(clean_pages))
		mprintf(SPTR "  " SPTR "\n", "-", "-");
	else
		mprintf(FPTR "  " FPTR "\n",
			as.clean_pages.next, as.clean_pages.prev);

	mprintf("dirty_pages:  ");
	if (ISEMPTY(dirty_pages))
		mprintf(SPTR "  " SPTR "\n", "-", "-");
	else
		mprintf(FPTR "  " FPTR "\n",
			as.dirty_pages.next, as.dirty_pages.prev);

	mprintf("locked_pages: ");
	if (ISEMPTY(locked_pages))
		mprintf(SPTR "  " SPTR "\n", "-", "-");
	else
		mprintf(FPTR "  " FPTR "\n",
			as.locked_pages.next, as.locked_pages.prev);

	mprintf("io_pages:     ");
	if (ISEMPTY(io_pages))
		mprintf(SPTR "  " SPTR "\n", "-", "-");
	else
		mprintf(FPTR "  " FPTR "\n",
			as.io_pages.next, as.io_pages.prev);

	mprintf("nrpages:      %8lx\n", as.nrpages);
	mprintf("ops:          " FPTR "\n", as.a_ops);
	mprintf("host:         " FPTR "\n", as.host);
	mprintf("mmap:         " FPTR "\n", as.i_mmap.next);
	mprintf("mmap_shared:  " FPTR "\n", as.i_mmap_shared.next);
	mprintf("dirty_when:   %lx\n", as.dirtied_when);
	mprintf("gfp_mask:    ");
	mprintbit(asflags, as.flags & ~__GFP_BITS_MASK);
	print_gfp(as.flags & __GFP_BITS_MASK);

	if (plist & 1) {
		prhead_page();
		top = addr + OFFSET(struct address_space, clean_pages) - OFFSET(struct page, list);
		paddr = (addr_t)as.clean_pages.next - OFFSET(struct page, list);
		while (paddr && paddr != top) {
			paddr = print_page(paddr, 1);
		}
	}
	if (plist & 2) {
		prhead_page();
		top = addr + OFFSET(struct address_space, dirty_pages) - OFFSET(struct page, list);
		paddr = (addr_t)as.dirty_pages.next - OFFSET(struct page, list);
		while (paddr && paddr != top) {
			paddr = print_page(paddr, 1);
		}
	}
	if (plist & 4) {
		prhead_page();
		top = addr + OFFSET(struct address_space, locked_pages) - OFFSET(struct page, list);
		paddr = (addr_t)as.locked_pages.next - OFFSET(struct page, list);
		while (paddr && paddr != top) {
			paddr = print_page(paddr, 1);
		}
	}
}

void
prhead_block_device()
{
	mprintf(SPTR"   DEV  OPEN BLKSZ "SPTR" "SPTR" "SPTR" "SPTR" SEM C SLP "SPTR"  INODES..\n", "ADDR", "HOLDER", "CONTAINS", "PART", "DISK", "INODE");
}

int offset_block_device_bd_list = OFFSET(struct block_device, bd_list);

addr_t
print_block_device(addr, eflag)
	addr_t addr;
	int eflag;
{
	struct block_device bd;
	addr_t iaddr;
	struct inode inode;

	memread(addr, sizeof(bd), &bd, "block_device");
	if (eflag == 0 && bd.bd_openers == 0)
		return (addr_t)bd.bd_list.next;

	mprintf(FPTR " ", addr);
	mprint_dev_t(bd.bd_dev);
	mprintf("%4x %5x", bd.bd_openers, bd.bd_block_size);
	mprintf(" " FPTR " " FPTR, bd.bd_holder, bd.bd_contains);
	mprintf(" " FPTR " " FPTR, bd.bd_part, bd.bd_disk);
	mprintf(" %5d %3d", ATOMIC_READ(bd.bd_sem.count), bd.bd_sem.sleepers);

	mprintf(" " FPTR " ", bd.bd_inode);
	iaddr = (addr_t)bd.bd_inodes.next;
	while (iaddr && iaddr != addr + OFFSET(struct block_device, bd_inodes)) {
		iaddr -= OFFSET(struct inode, i_devices);
		mprintf(" " FPTR, iaddr);
		memread(iaddr, sizeof(inode), &inode, "inode");
		iaddr = (addr_t)inode.i_devices.next;
	}
	mprintf("\n");
	return (addr_t)bd.bd_list.next;
}

void
prhead_file_lock()
{
	mprintf(SPTR " " SPTR "   PID " SPTR " T      START        END FLAGS\n",
		"ADDR", "OWNER", "FILE");
}

addr_t
print_file_lock(addr, full, chain)
	addr_t addr;
	int full;
	int chain;
{
	struct file_lock fl;

	switch (chain) {
	case 1:
		addr -= OFFSET(struct file_lock, fl_link);
		break;
	}
	memread(addr, sizeof(fl), &fl, "file_lock");
	mprintf(FPTR " " FPTR " %5d " FPTR " ",
		addr, fl.fl_owner, fl.fl_pid, fl.fl_file);
	switch (fl.fl_type) {
	case F_UNLCK:			mprintf("-");	break;
	case F_WRLCK:			mprintf("W");	break;
	case F_RDLCK:			mprintf("R");	break;
	case F_WRLCK | F_INPROGRESS:	mprintf("w");	break;
	case F_RDLCK | F_INPROGRESS:	mprintf("r");	break;
	default:			mprintf("?");	break;
	}
	mprintf(" %10llx", fl.fl_start);
	if (fl.fl_end == OFFSET_MAX) {
		mprintf(" offset_max");
	} else {
		mprintf(" %10llx", fl.fl_end);
	}
	mprintbit(flflags, fl.fl_flags);
	mprintf("\n");

	if (full) {
		mprintf("  next:       " FPTR "\n", fl.fl_next);
		mprintf("  block:      ");
		if ((addr_t)fl.fl_block.next == addr + OFFSET(struct file_lock, fl_block)) {
			mprintf(SPTR " " SPTR "\n", "-", "-");
		} else {
			mprintf(FPTR " " FPTR "\n",
			fl.fl_block.next, fl.fl_block.prev);
		}
		mprintf("  wait:       ");
		if ((addr_t)fl.fl_wait.task_list.next == addr + OFFSET(struct file_lock, fl_wait.task_list)) {
			mprintf(SPTR " " SPTR "\n", "-", "-");
		} else {
			mprintf(FPTR " " FPTR "\n",
				fl.fl_wait.task_list.prev,
				fl.fl_wait.task_list.next);
		}
		mprintf("  not/ins/rem:" FPTR " " FPTR " " FPTR "\n",
			fl.fl_notify, fl.fl_insert, fl.fl_remove);
		mprintf("  fasync:     " FPTR "\n", fl.fl_fasync);
		mprintf("  break_time: %8lx\n", fl.fl_break_time);
		mprintf("  nfs_fl:     %x  %x  %lx\n",
			fl.fl_u.nfs_fl.state,
			fl.fl_u.nfs_fl.flags,
			fl.fl_u.nfs_fl.host);
	}

	switch (chain) {
	default:
	case 0:	return (addr_t) fl.fl_next;
	case 1:	return (addr_t) fl.fl_link.next;
	case 2:	return (addr_t) fl.fl_block.next - OFFSET(struct file_lock, fl_block);
	}
}
