www.pudn.com > cryptfs030905.rar > mmap.c


/*
 * Copyright (c) 1997-2003 Erez Zadok
 * Copyright (c) 2001-2003 Stony Brook University
 *
 * For specific licensing information, see the COPYING file distributed with
 * this package, or get one from ftp://ftp.filesystems.org/pub/fist/COPYING.
 *
 * This Copyright notice must be kept intact and distributed with all
 * fistgen sources INCLUDING sources generated by fistgen.
 */
/*
 *  $Id: mmap.c,v 1.29 2003/07/29 18:17:20 ezk Exp $
 */

#ifdef HAVE_CONFIG_H
# include 
#endif /* HAVE_CONFIG_H */
#ifdef FISTGEN
# include "fist_cryptfs.h"
#endif /* FISTGEN */
#include "fist.h"
#include "cryptfs.h"


#ifdef FIST_COUNT_WRITES
/* for counting writes in the middle vs. regular writes */
unsigned long count_writes = 0, count_writes_middle = 0;
#endif /* FIST_COUNT_WRITES */

/* forward declaration of commit write and prepare write */
STATIC int cryptfs_commit_write(file_t *file, page_t *page, unsigned from, unsigned to);
STATIC int cryptfs_prepare_write(file_t *file, page_t *page, unsigned from, unsigned to);


/*
 * Function for handling creation of holes when lseek-ing past the
 * end of the file and then writing some data.
 */
int
cryptfs_fill_zeros(file_t* file, page_t *page, unsigned from)
{
    int err = 0;
    dentry_t *dentry = file->f_dentry;
    inode_t *inode = dentry->d_inode;
    page_t *tmp_page;
    int index;

    print_entry_location();

    for (index = inode->i_size >> PAGE_CACHE_SHIFT; index < page->index; index++) {
	tmp_page = cryptfs_get1page(file, index);
	if (IS_ERR(tmp_page)) {
	    err = PTR_ERR(tmp_page);
	    goto out;
	}

	/*
	 * zero out rest of the contents of the page between the appropriate
	 * offsets.
	 */
	memset((char*)page_address(tmp_page) + (inode->i_size & ~PAGE_CACHE_MASK), 0, PAGE_CACHE_SIZE - (inode->i_size & ~PAGE_CACHE_MASK));

	if (! (err = cryptfs_prepare_write(file, tmp_page, 0, PAGE_CACHE_SIZE)))
            err = cryptfs_commit_write(file, tmp_page, 0, PAGE_CACHE_SIZE);

	page_cache_release(tmp_page);
	if (err < 0)
		goto out;
        if (current->need_resched)
            schedule();
    }

    /* zero out appropriate parts of last page */


    if ((from - (inode->i_size & ~PAGE_CACHE_MASK)) > 0) {

	memset((char*)page_address(page) + (inode->i_size & ~PAGE_CACHE_MASK), 0, from - (inode->i_size & ~PAGE_CACHE_MASK));
	if (! (err = cryptfs_prepare_write(file, page, 0, PAGE_CACHE_SIZE)))
            err = cryptfs_commit_write(file, page, 0, PAGE_CACHE_SIZE);

	if (err < 0)
		goto out;
        if (current->need_resched)
            schedule();
    }

out:
    print_exit_status(err);
    return err;
}



STATIC int
cryptfs_writepage(page_t *page)
{
    int err = -EIO;
    inode_t *inode;
    inode_t *hidden_inode;
    page_t *hidden_page;
    char *kaddr, *hidden_kaddr;

    print_entry_location();

    inode = page->mapping->host;
    hidden_inode = itohi(inode);

    /*
     * writepage is called when shared mmap'ed files need to write
     * their pages, while prepare/commit_write are called from the
     * non-paged write() interface.  (However, in 2.3 the two interfaces
     * share the same cache, while in 2.2 they didn't.)
     *
     * So we pretty much have to duplicate much of what commit_write does.
     */

    /* find lower page (returns a locked page) */
    hidden_page = grab_cache_page(hidden_inode->i_mapping, page->index);
    if (!hidden_page)
	goto out;

    /* get page address, and encode it */
    kaddr = (char *) kmap(page);
    hidden_kaddr = (char*) kmap(hidden_page);
    cryptfs_encode_block(kaddr, hidden_kaddr, PAGE_CACHE_SIZE, inode, inode->i_sb, page->index);
    /* if encode_block could fail, then return error */
    kunmap(page);
    kunmap(hidden_page);

    /* call lower writepage (expects locked page) */
    err = hidden_inode->i_mapping->a_ops->writepage(hidden_page);

    /*
     * update mtime and ctime of lower level file system
     * cryptfs' mtime and ctime are updated by generic_file_write
     */
    hidden_inode->i_mtime = hidden_inode->i_ctime = CURRENT_TIME;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,1)
    UnlockPage(hidden_page);	/* b/c grab_cache_page locked it */
# endif /* kernel older than 2.4.1 */
    page_cache_release(hidden_page); /* b/c grab_cache_page increased refcnt */

    if (err)
	ClearPageUptodate(page);
    else
	SetPageUptodate(page);
out:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,1)
    UnlockPage(page);
# endif /* kernel 2.4.1 and newer */
    print_exit_status(err);
    return err;
}


/*
 * get one page from cache or lower f/s, return error otherwise.
 * returns unlocked, up-to-date page (if ok), with increased refcnt.
 */
page_t *
cryptfs_get1page(file_t *file, int index)
{
    page_t *page;
    dentry_t *dentry;
    inode_t *inode;
    struct address_space *mapping;
    int err;

    print_entry_location();

    dentry = file->f_dentry; /* CPW: Moved below print_entry_location */
    inode = dentry->d_inode;
    mapping = inode->i_mapping;

    fist_dprint(8, "%s: read page index %d pid %d\n", __FUNCTION__, index, current->pid);
    if (index < 0) {
	printk("%s BUG: index=%d\n", __FUNCTION__, index);
	page = ERR_PTR(-EIO);
	goto out;
    }
    page = read_cache_page(mapping,
			   index,
			   (filler_t *) mapping->a_ops->readpage,
			   (void *) file);
    if (IS_ERR(page))
	goto out;
    wait_on_page(page);
    if (!Page_Uptodate(page)) {
	lock_page(page);
	err = mapping->a_ops->readpage(file, page);
	if (err) {
	    page = ERR_PTR(err);
	    goto out;
	}
	wait_on_page(page);
	if (!Page_Uptodate(page)) {
	    page = ERR_PTR(-EIO);
	    goto out;
	}
    }

 out:
    print_exit_pointer(page);
    return page;
}


/*
 * get one page from cache or lower f/s, return error otherwise.
 * similar to get1page, but doesn't guarantee that it will return
 * an unlocked page.
 */
page_t *
cryptfs_get1page_cached(file_t *file, int index)
{
    page_t *page;
    dentry_t *dentry;
    inode_t *inode;
    struct address_space *mapping;
    int err;

    print_entry_location();

    dentry = file->f_dentry; /* CPW: Moved below print_entry_location */
    inode = dentry->d_inode;
    mapping = inode->i_mapping;

    fist_dprint(8, "%s: read page index %d pid %d\n", __FUNCTION__, index, current->pid);
    if (index < 0) {
	printk("%s BUG: index=%d\n", __FUNCTION__, index);
	page = ERR_PTR(-EIO);
	goto out;
    }
    page = read_cache_page(mapping,
			   index,
			   (filler_t *) mapping->a_ops->readpage,
			   (void *) file);
    if (IS_ERR(page))
	goto out;

 out:
    print_exit_pointer(page);
    return page;
}


/*
 * readpage is called from generic_page_read and the fault handler.
 * If your file system uses generic_page_read for the read op, it
 * must implement readpage.
 *
 * Readpage expects a locked page, and must unlock it.
 */
STATIC int
cryptfs_do_readpage(file_t *file, page_t *page)
{
    int err = -EIO;
    dentry_t *dentry;
    file_t *hidden_file = NULL;
    dentry_t *hidden_dentry;
    inode_t *inode;
    inode_t *hidden_inode;
    char *page_data;
    page_t *hidden_page;
    char *hidden_page_data;
    int real_size;

    print_entry_location();

    dentry = file->f_dentry; /* CPW: Moved below print_entry_location */
    if (ftopd(file) != NULL)
	hidden_file = ftohf(file);
    hidden_dentry = dtohd(dentry);
    inode = dentry->d_inode;
    hidden_inode = itohi(inode);

    fist_dprint(7, "%s: requesting page %d from file %s\n", __FUNCTION__, page->index, dentry->d_name.name);

    MALLOC_PAGE_POINTERS(hidden_pages, num_hidden_pages);
    MALLOC_PAGEDATA_POINTERS(hidden_pages_data, num_hidden_pages);
    FOR_EACH_PAGE
	CURRENT_HIDDEN_PAGE = NULL;

    /* find lower page (returns a locked page) */
    FOR_EACH_PAGE {
	fist_dprint(8, "%s: Current page index = %d\n", __FUNCTION__, CURRENT_HIDDEN_PAGEINDEX);
	CURRENT_HIDDEN_PAGE = read_cache_page(hidden_inode->i_mapping,
					      CURRENT_HIDDEN_PAGEINDEX,
					      (filler_t *) hidden_inode->i_mapping->a_ops->readpage,
					      (void *) hidden_file);
	if (IS_ERR(CURRENT_HIDDEN_PAGE)) {
	    err = PTR_ERR(CURRENT_HIDDEN_PAGE);
	    CURRENT_HIDDEN_PAGE = NULL;
	    goto out_release;
	}
    }

    /*
     * wait for the page data to show up
     * (signaled by readpage as unlocking the page)
     */
    FOR_EACH_PAGE {
	wait_on_page(CURRENT_HIDDEN_PAGE);
	if (!Page_Uptodate(CURRENT_HIDDEN_PAGE)) {
	    /*
	     * call readpage() again if we returned from wait_on_page with a
	     * page that's not up-to-date; that can happen when a partial
	     * page has a few buffers which are ok, but not the whole
	     * page.
	     */
	    lock_page(CURRENT_HIDDEN_PAGE);
	    err = hidden_inode->i_mapping->a_ops->readpage(hidden_file,
							   CURRENT_HIDDEN_PAGE);
	    if (err) {
		CURRENT_HIDDEN_PAGE = NULL;
		goto out_release;
	    }
	    wait_on_page(CURRENT_HIDDEN_PAGE);
	    if (!Page_Uptodate(CURRENT_HIDDEN_PAGE)) {
		err = -EIO;
		goto out_release;
	    }
	}
    }

    /* map pages, get their addresses */
    page_data = (char *) kmap(page);
    FOR_EACH_PAGE
	CURRENT_HIDDEN_PAGEDATA = (char *) kmap(CURRENT_HIDDEN_PAGE);

    /* if decode_block could fail, then return error */
    err = 0;
    real_size = hidden_inode->i_size - (page->index << PAGE_CACHE_SHIFT);
    if (real_size <= 0)
	memset(page_data, 0, PAGE_CACHE_SIZE);
    else if (real_size < PAGE_CACHE_SIZE) {
	cryptfs_decode_block(hidden_page_data, page_data, real_size, inode, inode->i_sb, page->index);
	memset(page_data + real_size, 0, PAGE_CACHE_SIZE - real_size);
    } else
	cryptfs_decode_block(hidden_page_data, page_data, PAGE_CACHE_SIZE, inode, inode->i_sb, page->index);

    FOR_EACH_PAGE
	kunmap(CURRENT_HIDDEN_PAGE);
    kunmap(page);

out_release:
    FOR_EACH_PAGE
	if (CURRENT_HIDDEN_PAGE)
	    page_cache_release(CURRENT_HIDDEN_PAGE); /* undo read_cache_page */

    FREE_PAGE_POINTERS(hidden_pages, num_hidden_pages);
    FREE_PAGEDATA_POINTERS(hidden_pages_data, num_hidden_pages);

out:
    if (err == 0)
	SetPageUptodate(page);
    else
	ClearPageUptodate(page);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_readpage(file_t *file, page_t *page)
{
    int err;
    print_entry_location();

    err = cryptfs_do_readpage(file, page);

    /*
     * we have to unlock our page, b/c we _might_ have gotten a locked page.
     * but we no longer have to wakeup on our page here, b/c UnlockPage does
     * it
     */
    UnlockPage(page);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_prepare_write(file_t *file, page_t *page, unsigned from, unsigned to)
{
    int err = 0;

    print_entry_location();

    /*
     * we call kmap(page) only here, and do the kunmap
     * and the actual downcalls, including unlockpage and uncache
     * in commit_write.
     */
    kmap(page);

    /* fast path for whole page writes */
    if (from == 0 && to == PAGE_CACHE_SIZE)
	goto out;
    /* read the page to "revalidate" our data */
    /* call the helper function which doesn't unlock the page */
    if (!Page_Uptodate(page))
        err = cryptfs_do_readpage(file, page);

 out:
    print_exit_status(err);
    return err;
}



STATIC int
cryptfs_commit_write(file_t *file, page_t *page, unsigned from, unsigned to)
{
    int err = -ENOMEM;
    inode_t *inode;
    inode_t *hidden_inode;
    page_t *hidden_page;
    file_t *hidden_file = NULL;
    loff_t pos;
    unsigned bytes = to - from;
    unsigned hidden_from, hidden_to, hidden_bytes;

    print_entry_location();

    inode = page->mapping->host; /* CPW: Moved below print_entry_location */
    hidden_inode = itohi(inode);

    ASSERT(file != NULL);
    /*
     * here we have a kmapped page, with data from the user copied
     * into it.  we need to encode_block it, and then call the lower
     * commit_write.  We also need to simulate same behavior of
     * generic_file_write, and call prepare_write on the lower f/s first.
     */
#ifdef FIST_COUNT_WRITES
    count_writes++;
# endif /* FIST_COUNT_WRITES */

    /* this is append and/or extend -- we can't have holes so fill them in */
    if (page->index > (hidden_inode->i_size >> PAGE_CACHE_SHIFT)) {
	page_t *tmp_page;
	int index;
	for (index = hidden_inode->i_size >> PAGE_CACHE_SHIFT; index < page->index; index++) {
	    tmp_page = cryptfs_get1page(file, index);
	    if (IS_ERR(tmp_page)) {
		err = PTR_ERR(tmp_page);
		goto out;
	    }
	    /* zero out the contents of the page at the appropriate offsets */
	    memset((char*)page_address(tmp_page) + (inode->i_size & ~PAGE_CACHE_MASK), 0, PAGE_CACHE_SIZE - (inode->i_size & ~PAGE_CACHE_MASK));
	    if (!(err = cryptfs_prepare_write(file, tmp_page, 0, PAGE_CACHE_SIZE)))
		err = cryptfs_commit_write(file, tmp_page, 0, PAGE_CACHE_SIZE);
	    page_cache_release(tmp_page);
	    if (err < 0)
		goto out;
	    if (current->need_resched)
		schedule();
	}
    }

    if (ftopd(file) != NULL)
	hidden_file = ftohf(file);

    down(&hidden_inode->i_sem);
    /* find lower page (returns a locked page) */
    hidden_page = grab_cache_page(hidden_inode->i_mapping, page->index);
    if (!hidden_page)
	goto out;

#if FIST_ENCODING_BLOCKSIZE > 1
#  error encoding_blocksize greater than 1 is not yet supported
# endif /* FIST_ENCODING_BLOCKSIZE > 1 */

    hidden_from = 0;
    if ((page->index << PAGE_CACHE_SHIFT) + to > hidden_inode->i_size) {

	/*
	 * If this call to commit_write had introduced holes and the code
	 * for handling holes was invoked, then the beginning of this page
	 * must be zeroed out as well:
	 *	zero out bytes from 'size_of_file%pagesize' to 'from'.
	 */
	if ((from - (inode->i_size & ~PAGE_CACHE_MASK)) > 0)
                 memset((char*)page_address(page) + (inode->i_size & ~PAGE_CACHE_MASK), 0, from - (inode->i_size & ~PAGE_CACHE_MASK));
	hidden_to = to;
    }
    else if (((page->index + 1) << PAGE_CACHE_SHIFT) <= hidden_inode->i_size)
	hidden_to = PAGE_CACHE_SIZE;
    else
	hidden_to = hidden_inode->i_size & ~PAGE_CACHE_MASK;
    hidden_bytes = hidden_to - hidden_from;

    /* call lower prepare_write */
    err = -EINVAL;
    if (hidden_inode->i_mapping &&
	hidden_inode->i_mapping->a_ops &&
	hidden_inode->i_mapping->a_ops->prepare_write)
	err = hidden_inode->i_mapping->a_ops->prepare_write(hidden_file,
							    hidden_page,
							    hidden_from,
							    hidden_to);
    if (err)
	/* don't leave locked pages behind, esp. on an ENOSPC */
	goto out_unlock;

    fist_dprint(8, "%s: encoding %d bytes\n", __FUNCTION__, hidden_bytes);
    cryptfs_encode_block((char *) page_address(page) + hidden_from, (char*) page_address(hidden_page) + hidden_from, hidden_bytes, inode, inode->i_sb, page->index);
    /* if encode_block could fail, then goto unlock and return error */

    /* call lower commit_write */
    err = hidden_inode->i_mapping->a_ops->commit_write(hidden_file,
						       hidden_page,
						       hidden_from,
						       hidden_to);

    if (err < 0)
	goto out_unlock;

    err = bytes;	/* convert error to no. of bytes */

    inode->i_blocks = hidden_inode->i_blocks;
    /* we may have to update i_size */
    pos = (page->index << PAGE_CACHE_SHIFT) + to;
    if (pos > inode->i_size)
	inode->i_size = pos;

    /*
     * update mtime and ctime of lower level file system
     * cryptfs' mtime and ctime are updated by generic_file_write
     */
    hidden_inode->i_mtime = hidden_inode->i_ctime = CURRENT_TIME;

    mark_inode_dirty_sync(inode);

out_unlock:
    UnlockPage(hidden_page);
    page_cache_release(hidden_page);
    kunmap(page);		/* kmap was done in prepare_write */
out:
    /* we must set our page as up-to-date */
    if (err < 0)
	ClearPageUptodate(page);
    else
	SetPageUptodate(page);
    up(&hidden_inode->i_sem);
    print_exit_status(err);
    return err;			/* assume all is ok */
}


STATIC int
cryptfs_bmap(struct address_space *mapping, long block)
{
    int err = 0;
    inode_t *inode;
    inode_t *hidden_inode;

    print_entry_location();

    inode = (inode_t *) mapping->host;
    hidden_inode = itohi(inode);

    if (hidden_inode->i_mapping->a_ops->bmap)
	err = hidden_inode->i_mapping->a_ops->bmap(hidden_inode->i_mapping, block);
    print_exit_location();
    return err;
}


/*
 * This function is copied verbatim from mm/filemap.c.
 * XXX: It should be simply moved to some header file instead -- bug Al about it!
 */
static inline int sync_page(struct page *page)
{
	struct address_space *mapping = page->mapping;

	if (mapping && mapping->a_ops && mapping->a_ops->sync_page)
		return mapping->a_ops->sync_page(page);
	return 0;
}


/*
 * XXX: we may not need this function if not FIST_FILTER_DATA.
 * FIXME: for FIST_FILTER_SCA, get all lower pages and sync them each.
 */
STATIC int
cryptfs_sync_page(page_t *page)
{
    int err = 0;
    inode_t *inode;
    inode_t *hidden_inode;
    page_t *hidden_page;

    print_entry_location();

    inode = page->mapping->host; /* CPW: Moved below print_entry_location */
    hidden_inode = itohi(inode);

    /* find lower page (returns a locked page) */
    hidden_page = grab_cache_page(hidden_inode->i_mapping, page->index);
    if (!hidden_page)
	goto out;

    err = sync_page(hidden_page);

    UnlockPage(hidden_page);	/* b/c grab_cache_page locked it */
    page_cache_release(hidden_page); /* b/c grab_cache_page increased refcnt */

out:
    print_exit_status(err);
    return err;
}


struct address_space_operations cryptfs_aops =
{
    writepage:		cryptfs_writepage,
    readpage:		cryptfs_readpage,
    prepare_write:	cryptfs_prepare_write,
    commit_write:	cryptfs_commit_write,
    bmap:		cryptfs_bmap,
    sync_page:		cryptfs_sync_page,
};

/*
 * Local variables:
 * c-basic-offset: 4
 * End:
 */