aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorAllison Henderson <achender@linux.vnet.ibm.com>2011-09-03 11:51:09 -0400
committerTheodore Ts'o <tytso@mit.edu>2011-09-03 11:51:09 -0400
commit4e96b2dbbf1d7e81f22047a50f862555a6cb87cb (patch)
tree1f9c1d6712d1d16d3c6d04635019d2d1774c54d4 /fs
parent5930ea643805feb50a2f8383ae12eb6f10935e49 (diff)
ext4: Add new ext4_discard_partial_page_buffers routines
This patch adds two new routines: ext4_discard_partial_page_buffers and ext4_discard_partial_page_buffers_no_lock. The ext4_discard_partial_page_buffers routine is a wrapper function to ext4_discard_partial_page_buffers_no_lock. The wrapper function locks the page and passes it to ext4_discard_partial_page_buffers_no_lock. Calling functions that already have the page locked can call ext4_discard_partial_page_buffers_no_lock directly. The ext4_discard_partial_page_buffers_no_lock function zeros a specified range in a page, and unmaps the corresponding buffer heads. Only block aligned regions of the page will have their buffer heads unmapped. Unblock aligned regions will be mapped if needed so that they can be updated with the partial zero out. This function is meant to be used to update a page and its buffer heads to be zeroed and unmapped when the corresponding blocks have been released or will be released. This routine is used in the following scenarios: * A hole is punched and the non page aligned regions of the head and tail of the hole need to be discarded * The file is truncated and the partial page beyond EOF needs to be discarded * The end of a hole is in the same page as EOF. After the page is flushed, the partial page beyond EOF needs to be discarded. * A write operation begins or ends inside a hole and the partial page appearing before or after the write needs to be discarded * A write operation extends EOF and the partial page beyond EOF needs to be discarded This function takes a flag EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED which is used when a write operation begins or ends in a hole. When the EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED flag is used, only buffer heads that are already unmapped will have the corresponding regions of the page zeroed. Signed-off-by: Allison Henderson <achender@linux.vnet.ibm.com> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs')
-rw-r--r--fs/ext4/ext4.h11
-rw-r--r--fs/ext4/inode.c224
2 files changed, 235 insertions, 0 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 5c38120c389c..ccfa81f33bb0 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -529,6 +529,11 @@ struct ext4_new_group_data {
529#define EXT4_FREE_BLOCKS_NO_QUOT_UPDATE 0x0008 529#define EXT4_FREE_BLOCKS_NO_QUOT_UPDATE 0x0008
530 530
531/* 531/*
532 * Flags used by ext4_discard_partial_page_buffers
533 */
534#define EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED 0x0001
535
536/*
532 * ioctl commands 537 * ioctl commands
533 */ 538 */
534#define EXT4_IOC_GETFLAGS FS_IOC_GETFLAGS 539#define EXT4_IOC_GETFLAGS FS_IOC_GETFLAGS
@@ -1838,6 +1843,12 @@ extern int ext4_block_truncate_page(handle_t *handle,
1838 struct address_space *mapping, loff_t from); 1843 struct address_space *mapping, loff_t from);
1839extern int ext4_block_zero_page_range(handle_t *handle, 1844extern int ext4_block_zero_page_range(handle_t *handle,
1840 struct address_space *mapping, loff_t from, loff_t length); 1845 struct address_space *mapping, loff_t from, loff_t length);
1846extern int ext4_discard_partial_page_buffers(handle_t *handle,
1847 struct address_space *mapping, loff_t from,
1848 loff_t length, int flags);
1849extern int ext4_discard_partial_page_buffers_no_lock(handle_t *handle,
1850 struct inode *inode, struct page *page, loff_t from,
1851 loff_t length, int flags);
1841extern int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf); 1852extern int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf);
1842extern qsize_t *ext4_get_reserved_space(struct inode *inode); 1853extern qsize_t *ext4_get_reserved_space(struct inode *inode);
1843extern void ext4_da_update_reserve_space(struct inode *inode, 1854extern void ext4_da_update_reserve_space(struct inode *inode,
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index b84f127c085d..d1b1ef71e5b7 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2966,6 +2966,230 @@ void ext4_set_aops(struct inode *inode)
2966 inode->i_mapping->a_ops = &ext4_journalled_aops; 2966 inode->i_mapping->a_ops = &ext4_journalled_aops;
2967} 2967}
2968 2968
2969
2970/*
2971 * ext4_discard_partial_page_buffers()
2972 * Wrapper function for ext4_discard_partial_page_buffers_no_lock.
2973 * This function finds and locks the page containing the offset
2974 * "from" and passes it to ext4_discard_partial_page_buffers_no_lock.
2975 * Calling functions that already have the page locked should call
2976 * ext4_discard_partial_page_buffers_no_lock directly.
2977 */
2978int ext4_discard_partial_page_buffers(handle_t *handle,
2979 struct address_space *mapping, loff_t from,
2980 loff_t length, int flags)
2981{
2982 struct inode *inode = mapping->host;
2983 struct page *page;
2984 int err = 0;
2985
2986 page = find_or_create_page(mapping, from >> PAGE_CACHE_SHIFT,
2987 mapping_gfp_mask(mapping) & ~__GFP_FS);
2988 if (!page)
2989 return -EINVAL;
2990
2991 err = ext4_discard_partial_page_buffers_no_lock(handle, inode, page,
2992 from, length, flags);
2993
2994 unlock_page(page);
2995 page_cache_release(page);
2996 return err;
2997}
2998
2999/*
3000 * ext4_discard_partial_page_buffers_no_lock()
3001 * Zeros a page range of length 'length' starting from offset 'from'.
3002 * Buffer heads that correspond to the block aligned regions of the
3003 * zeroed range will be unmapped. Unblock aligned regions
3004 * will have the corresponding buffer head mapped if needed so that
3005 * that region of the page can be updated with the partial zero out.
3006 *
3007 * This function assumes that the page has already been locked. The
3008 * The range to be discarded must be contained with in the given page.
3009 * If the specified range exceeds the end of the page it will be shortened
3010 * to the end of the page that corresponds to 'from'. This function is
3011 * appropriate for updating a page and it buffer heads to be unmapped and
3012 * zeroed for blocks that have been either released, or are going to be
3013 * released.
3014 *
3015 * handle: The journal handle
3016 * inode: The files inode
3017 * page: A locked page that contains the offset "from"
3018 * from: The starting byte offset (from the begining of the file)
3019 * to begin discarding
3020 * len: The length of bytes to discard
3021 * flags: Optional flags that may be used:
3022 *
3023 * EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED
3024 * Only zero the regions of the page whose buffer heads
3025 * have already been unmapped. This flag is appropriate
3026 * for updateing the contents of a page whose blocks may
3027 * have already been released, and we only want to zero
3028 * out the regions that correspond to those released blocks.
3029 *
3030 * Returns zero on sucess or negative on failure.
3031 */
3032int ext4_discard_partial_page_buffers_no_lock(handle_t *handle,
3033 struct inode *inode, struct page *page, loff_t from,
3034 loff_t length, int flags)
3035{
3036 ext4_fsblk_t index = from >> PAGE_CACHE_SHIFT;
3037 unsigned int offset = from & (PAGE_CACHE_SIZE-1);
3038 unsigned int blocksize, max, pos;
3039 unsigned int end_of_block, range_to_discard;
3040 ext4_lblk_t iblock;
3041 struct buffer_head *bh;
3042 int err = 0;
3043
3044 blocksize = inode->i_sb->s_blocksize;
3045 max = PAGE_CACHE_SIZE - offset;
3046
3047 if (index != page->index)
3048 return -EINVAL;
3049
3050 /*
3051 * correct length if it does not fall between
3052 * 'from' and the end of the page
3053 */
3054 if (length > max || length < 0)
3055 length = max;
3056
3057 iblock = index << (PAGE_CACHE_SHIFT - inode->i_sb->s_blocksize_bits);
3058
3059 if (!page_has_buffers(page)) {
3060 /*
3061 * If the range to be discarded covers a partial block
3062 * we need to get the page buffers. This is because
3063 * partial blocks cannot be released and the page needs
3064 * to be updated with the contents of the block before
3065 * we write the zeros on top of it.
3066 */
3067 if (!(from & (blocksize - 1)) ||
3068 !((from + length) & (blocksize - 1))) {
3069 create_empty_buffers(page, blocksize, 0);
3070 } else {
3071 /*
3072 * If there are no partial blocks,
3073 * there is nothing to update,
3074 * so we can return now
3075 */
3076 return 0;
3077 }
3078 }
3079
3080 /* Find the buffer that contains "offset" */
3081 bh = page_buffers(page);
3082 pos = blocksize;
3083 while (offset >= pos) {
3084 bh = bh->b_this_page;
3085 iblock++;
3086 pos += blocksize;
3087 }
3088
3089 pos = offset;
3090 while (pos < offset + length) {
3091 err = 0;
3092
3093 /* The length of space left to zero and unmap */
3094 range_to_discard = offset + length - pos;
3095
3096 /* The length of space until the end of the block */
3097 end_of_block = blocksize - (pos & (blocksize-1));
3098
3099 /*
3100 * Do not unmap or zero past end of block
3101 * for this buffer head
3102 */
3103 if (range_to_discard > end_of_block)
3104 range_to_discard = end_of_block;
3105
3106
3107 /*
3108 * Skip this buffer head if we are only zeroing unampped
3109 * regions of the page
3110 */
3111 if (flags & EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED &&
3112 buffer_mapped(bh))
3113 goto next;
3114
3115 /* If the range is block aligned, unmap */
3116 if (range_to_discard == blocksize) {
3117 clear_buffer_dirty(bh);
3118 bh->b_bdev = NULL;
3119 clear_buffer_mapped(bh);
3120 clear_buffer_req(bh);
3121 clear_buffer_new(bh);
3122 clear_buffer_delay(bh);
3123 clear_buffer_unwritten(bh);
3124 clear_buffer_uptodate(bh);
3125 zero_user(page, pos, range_to_discard);
3126 BUFFER_TRACE(bh, "Buffer discarded");
3127 goto next;
3128 }
3129
3130 /*
3131 * If this block is not completely contained in the range
3132 * to be discarded, then it is not going to be released. Because
3133 * we need to keep this block, we need to make sure this part
3134 * of the page is uptodate before we modify it by writeing
3135 * partial zeros on it.
3136 */
3137 if (!buffer_mapped(bh)) {
3138 /*
3139 * Buffer head must be mapped before we can read
3140 * from the block
3141 */
3142 BUFFER_TRACE(bh, "unmapped");
3143 ext4_get_block(inode, iblock, bh, 0);
3144 /* unmapped? It's a hole - nothing to do */
3145 if (!buffer_mapped(bh)) {
3146 BUFFER_TRACE(bh, "still unmapped");
3147 goto next;
3148 }
3149 }
3150
3151 /* Ok, it's mapped. Make sure it's up-to-date */
3152 if (PageUptodate(page))
3153 set_buffer_uptodate(bh);
3154
3155 if (!buffer_uptodate(bh)) {
3156 err = -EIO;
3157 ll_rw_block(READ, 1, &bh);
3158 wait_on_buffer(bh);
3159 /* Uhhuh. Read error. Complain and punt.*/
3160 if (!buffer_uptodate(bh))
3161 goto next;
3162 }
3163
3164 if (ext4_should_journal_data(inode)) {
3165 BUFFER_TRACE(bh, "get write access");
3166 err = ext4_journal_get_write_access(handle, bh);
3167 if (err)
3168 goto next;
3169 }
3170
3171 zero_user(page, pos, range_to_discard);
3172
3173 err = 0;
3174 if (ext4_should_journal_data(inode)) {
3175 err = ext4_handle_dirty_metadata(handle, inode, bh);
3176 } else {
3177 if (ext4_should_order_data(inode) &&
3178 EXT4_I(inode)->jinode)
3179 err = ext4_jbd2_file_inode(handle, inode);
3180 mark_buffer_dirty(bh);
3181 }
3182
3183 BUFFER_TRACE(bh, "Partial buffer zeroed");
3184next:
3185 bh = bh->b_this_page;
3186 iblock++;
3187 pos += range_to_discard;
3188 }
3189
3190 return err;
3191}
3192
2969/* 3193/*
2970 * ext4_block_truncate_page() zeroes out a mapping from file offset `from' 3194 * ext4_block_truncate_page() zeroes out a mapping from file offset `from'
2971 * up to the end of the block which corresponds to `from'. 3195 * up to the end of the block which corresponds to `from'.