/* * 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. */ /* * Copyright (C) 2004, 2005 Markus Klotzbuecher * * 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. */ /* * $Id$ */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include "fist.h" #include "mini_fo.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 mini_fo_commit_write(file_t *file, page_t *page, unsigned from, unsigned to); STATIC int mini_fo_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 mini_fo_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 = mini_fo_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 = mini_fo_prepare_write(file, tmp_page, 0, PAGE_CACHE_SIZE))) err = mini_fo_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 the encoding type is block, then adjust the 'from' (where the * zeroing will start) offset appropriately */ from = from & (~(FIST_ENCODING_BLOCKSIZE - 1)); 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 = mini_fo_prepare_write(file, page, 0, PAGE_CACHE_SIZE))) err = mini_fo_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 mini_fo_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); mini_fo_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 * mini_fo' 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 * mini_fo_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 * mini_fo_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 mini_fo_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) { mini_fo_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 mini_fo_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 mini_fo_readpage(file_t *file, page_t *page) { int err; print_entry_location(); err = mini_fo_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 mini_fo_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 = mini_fo_do_readpage(file, page); out: print_exit_status(err); return err; } STATIC int mini_fo_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 = mini_fo_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 = mini_fo_prepare_write(file, tmp_page, 0, PAGE_CACHE_SIZE))) err = mini_fo_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); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_inode->i_mutex); #else down(&hidden_inode->i_sem); #endif /* 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 = from & (~(FIST_ENCODING_BLOCKSIZE - 1)); hidden_to = ((to + FIST_ENCODING_BLOCKSIZE - 1) & (~(FIST_ENCODING_BLOCKSIZE - 1))); 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 * zero out bytes from 'size_of_file%pagesize' to 'from'. */ if ((hidden_from - (inode->i_size & ~PAGE_CACHE_MASK)) > 0) memset((char*)page_address(page) + (inode->i_size & ~PAGE_CACHE_MASK), 0, hidden_from - (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); mini_fo_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 * mini_fo' 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); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_inode->i_mutex); #else up(&hidden_inode->i_sem); #endif print_exit_status(err); return err; /* assume all is ok */ } STATIC int mini_fo_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 mini_fo_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; }