/*
 *  linux/fs/supermount/filemap.c
 *
 *  Initial version for kernel 2.4.21 (C) 2003 Andrey Borzenkov
 *                                             (arvidjaar@mail.ru)
 *
 *  $Id: filemap.c,v 1.5 2003/07/13 19:23:31 bor Exp $
 */

#define S_DBG_TRACE_CURRENT S_DBG_TRACE_FILEMAP
#include "supermount.h"

/*
 * Some rationale and justification for this file
 *
 * We play dirty tricks with mm management for mmaped files on supermount.
 * Address space points to subinode but vm area is associated with superfile.
 * When media change is detected, subinode together with all associated
 * pages goes away as well (at least, I hope so ...) Now we must prevent
 * any attempt to access no more existing address space. To do so we save
 * original vm_ops in private field and replace them with vm_ops in this file.
 * They check if file is stale, if yes, they try to return sensible error.
 * There is some doubt about possibility to block here ... OTOH any write
 * lock on subfs is hold in context where no mm lock is expected.
 */
static void
supermount_vm_open(struct vm_area_struct *area)
{
	struct file *file = area->vm_file, *subfile;
	struct super_block *sb = file->f_dentry->d_sb;
	struct supermount_file_info *sfi;

	ENTER(sb, "vm=%p", area);

	if (subfs_check_disk_change(sb))
		goto out;

	subfile = get_subfs_file(file);
	if (IS_ERR(subfile))
		goto out;

	sfi = supermount_f(file);
	if (sfi->vm_ops && sfi->vm_ops->open)
		sfi->vm_ops->open(area);

	fput(subfile);
out:
	LEAVE(sb, "vm=%p", area);

	return;
}

static void
supermount_vm_close(struct vm_area_struct *area)
{
	struct file *file = area->vm_file, *subfile;
	struct super_block *sb = file->f_dentry->d_sb;
	struct supermount_file_info *sfi;

	ENTER(sb, "vm=%p", area);
	
	if (subfs_check_disk_change(sb))
		goto out;

	subfile = get_subfs_file(file);
	if (IS_ERR(subfile))
		goto out;

	sfi = supermount_f(file);
	if (sfi->vm_ops && sfi->vm_ops->close)
		sfi->vm_ops->close(area);

	fput(subfile);
out:
	LEAVE(sb, "vm=%p", area);

	return;
}

static struct page *
supermount_vm_nopage(struct vm_area_struct *area, unsigned long address, int unused)
{
	struct file *file = area->vm_file, *subfile;
	struct super_block *sb = file->f_dentry->d_sb;
	struct supermount_file_info *sfi;
	struct page *page = 0;

	ENTER(sb, "vm=%p addr=%lx", area, address);
	
	/*
	 * this is called with mm semaphore down read and pagetable
	 * spinlock released. So it _appears_ safe to sleep ...
	 */
	if (subfs_check_disk_change(sb))
		goto out;

	subfile = get_subfs_file(file);
	if (IS_ERR(subfile))
		goto out;

	sfi = supermount_f(file);
	page = sfi->vm_ops->nopage(area, address, unused);

	fput(subfile);
out:
	LEAVE(sb, "vm=%p page=%p", area, page);

	return page;
}

#ifdef HAVE_MPROTECT
static int
supermount_vm_mprotect(struct vm_area_struct *area, unsigned int newflags)
{
	struct file *file = area->vm_file, *subfile;
	struct super_block *sb = file->f_dentry->d_sb;
	struct supermount_file_info *sfi;
	struct page *page = 0
	int rc;

	ENTER(sb, "vm=%p flags=%u", area, newflags);
	
	rc = -ESTALE;
	if (subfs_check_disk_change(sb))
		goto out;

	subfile = get_subfs_file(file);
	rc = PTR_ERR(subfile);
	if (IS_ERR(subfile))
		goto out;

	rc = -ENOSYS;
	sfi = supermount_f(file);
	if (sfi->vm_ops && sfi->vm_ops->mprotect)
		rc = sfi->vm_ops->mprotect(area, newflags);

	fput(subfile);
out:
	LEAVE(sb, "vm=%p rc=%d", area, rc);

	return rc;
}
#endif

int
supermount_file_mmap(struct file *file, struct vm_area_struct *vma)
{

	struct super_block *sb = file->f_dentry->d_sb;
	struct inode *inode = file->f_dentry->d_inode;
	struct supermount_file_info *sfi;
	struct file *subfile;
	int write_on = NEED_WRITE_ATIME(inode);
	int rc;

	ENTER(sb, "file=%p vm=%p", file, vma);
	
	rc = -ESTALE;
	if (subfs_check_disk_change(sb))
		goto out;

	subfile = get_subfs_file(file);
	rc = PTR_ERR(subfile);
	if (IS_ERR(subfile))
		goto out;

	sfi = supermount_f(file);

	rc = -ENODEV;
	if (!subfile->f_op || !subfile->f_op->mmap)
		goto put_subfile;

	rc = subfs_get_access(inode, write_on);
	if (rc)
		goto put_subfile;

	rc = subfile->f_op->mmap(subfile, vma);
	subfs_put_access(inode, write_on);
	if (rc)
		goto put_subfile;
	/*
	 * we cannot deal with anonymous mapping
	 */
	if (!vma->vm_ops || !vma->vm_ops->nopage) {
		rc = -ENOSYS;
		goto put_subfile;
	}

	/*
	 * now do the nasty trick
	 */
	sfi->vm_ops = vma->vm_ops;
	vma->vm_ops = &supermount_vm_ops;

put_subfile:
	fput(subfile);
out:
	LEAVE(sb, "file=%p vm=%p rc=%d", file, vma, rc);

	return rc;
}

struct  vm_operations_struct supermount_vm_ops = {
	.open		= supermount_vm_open,
	.close		= supermount_vm_close,
	.nopage		= supermount_vm_nopage,
#ifdef HAVE_MPROTECT
	.mprotect	= supermount_vm_mprotect,
#endif
};
