/*****************************************************************************/
/* The development of this program is partly supported by IPA                */
/* (Information-Technology Promotion Agency, Japan).                         */
/*****************************************************************************/

/*****************************************************************************/
/*  liveinfo.c - gathering live information (ext2/3 common function)         */
/*  Copyright: Copyright (c) Hitachi, Ltd. 2004-2008                         */
/*             Authors: Yumiko Sugita (yumiko.sugita.yf@hitachi.com),        */
/*                      Satoshi Fujiwara (sa-fuji@sdl.hitachi.co.jp)         */
/*                                                                           */
/*  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., 59 Temple Place - Suite 330, Boston, MA 02111 USA      */
/*****************************************************************************/

#include <linux/sched.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include "liveinfo.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
#include <linux/buffer_head.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
#include <linux/fdtable.h>
#endif


MODULE_AUTHOR("Satoshi Fujiwara");
MODULE_DESCRIPTION("Gathering file blocks information");
MODULE_LICENSE("GPL");

static int	open = 0;	/* is device open? */
static int	req_fd = -1;	/* request fd */



int write_fbinfo(struct fblocks_info *fbinfo, char **buf, char *max) {
	int	size = sizeof(struct fblocks_info);

#if 0
	printk("write_fbinfo:bn: %lu, cnt: %lu, p: %p, max: %p\n",
	       fbinfo->bn, fbinfo->cnt, *buf, max);
#endif
	if (!fbinfo->cnt)
		return 0;
	if (*buf + size > max)
		return -EXFULL;		/* buffer overflow */
	if (copy_to_user(*buf, fbinfo, size))
		return -EFAULT;
	*buf += size;
	return 0;
}

size_t get_fbinfo(struct inode *inode, u32 *p, int n, int depth,
		  int addr_per_blk, struct fblocks_info *fbinfo,
		  char **buf, char *max) {
	int			rc;
	unsigned long		bn;
	struct buffer_head	*bh;
	u32			*q = p + n;

	for (; p < q; p++) {
		bn = le32_to_cpu(*p);
		if (!bn)
			continue;
#if 0
		printk("get_fbinfo:bn: %lu, bbn: %lu, bcnt: %lu",
		       bn, fbinfo->bn, fbinfo->cnt);
#endif
		if (fbinfo->bn + fbinfo->cnt == bn)
			fbinfo->cnt++;
		else if ((rc = write_fbinfo (fbinfo, buf, max)) < 0)
			return rc;
		else {
			fbinfo->bn = *p;
			fbinfo->cnt = 1;
		}
#if 0
		printk(", abn: %lu, acnt: %lu\n",
		       fbinfo->bn, fbinfo->cnt);
#endif
		if (depth) {
			bh = sb_bread(inode->i_sb, bn);
			if (!bh)
				return -EIO;	/* block read failed */
			rc = get_fbinfo(inode, (u32*)bh->b_data, addr_per_blk,
					depth - 1, addr_per_blk, fbinfo,
					buf, max);
			bforget(bh);
			if (rc)
				return rc;
		}
	}
	return 0;
}



static int dev_open(struct inode *inode, struct file *file) {
	if (open)
		return -EBUSY;
	open++;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
	try_module_get(THIS_MODULE);
#else
	MOD_INC_USE_COUNT;
#endif
	return 0;
}

static int dev_release(struct inode *inode, struct file *file) {
	open--;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
	module_put(THIS_MODULE);
#else
	MOD_DEC_USE_COUNT;
#endif
	return 0;
}

static ssize_t dev_read(struct file *f, char *buf, size_t len,
			loff_t *offset) {
	struct file		*file;
	struct inode		*inode;
	char			*fs_name;
	ssize_t			rc, xattr_len = 0;

	if (*offset || len <= 0)
		return -EINVAL;

	if (req_fd < 0)
		return -EBADF;

	/* get inode */
	file = fget(req_fd);
	if (!file)
		return -EBADF;
	inode = file->f_dentry->d_inode;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
	mutex_lock(&inode->i_mutex);
#else
	down(&inode->i_sem);
#endif
	fs_name = (char*)inode->i_sb->s_type->name;
	if (strcmp(fs_name, "ext3") == 0) {
		rc = ext3_get_xattr_binfo(inode, buf, len);
		if (rc < 0 || !inode->i_blocks)
			goto END;
		xattr_len = rc;
		rc = ext3_get_fbinfo(inode, buf + xattr_len, len - xattr_len);
	} else if (strcmp(fs_name, "ext2") == 0) {
		rc = ext2_get_xattr_binfo(inode, buf, len);
		if (rc < 0 || !inode->i_blocks)
			goto END;
		xattr_len = rc;
		rc = ext2_get_fbinfo(inode, buf + xattr_len, len - xattr_len);
	} else
		rc = 0;
END:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
	mutex_unlock(&inode->i_mutex);
#else
	up(&inode->i_sem);
#endif
	if (rc < 0 && rc != -EXFULL)
		req_fd = rc;
	fput(file);

	return (rc < 0 ? rc : rc + xattr_len);
}

static ssize_t dev_write(struct file *file, const char *buf, size_t len,
			 loff_t *offset) {
	int			fd, rc;
	struct files_struct	*files = current->files;

	if (len != sizeof(int))
		return -EINVAL;
	if (*offset)
		return -EINVAL;
	rc = get_user(fd, (int*)buf);
	if (rc)
		return rc;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	if (!FD_ISSET(fd, files_fdtable(files)->open_fds)) {
#else
	if (!FD_ISSET(fd, files->open_fds)) {
#endif
		req_fd = -EINVAL;
		return req_fd;
	}
	req_fd = fd;

	return len;
}

static struct file_operations fops = {
	.read =		dev_read,
	.write =	dev_write,
	.open =		dev_open,
	.release =	dev_release,
};

int liveinfo_init(void) {
	int	rc;
	
	rc = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &fops);
	if (rc < 0) {
		printk("DAVL_LIVEINFO: register_chrdev failed.(%d)\n", rc);
		return rc;
	}
	return 0;
}

void liveinfo_exit(void) {
	unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);
}

module_init(liveinfo_init);
module_exit(liveinfo_exit);
