Skip to content

Commit 4e96b2d

Browse files
achendertytso
authored andcommitted
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 <[email protected]> Signed-off-by: "Theodore Ts'o" <[email protected]>
1 parent 5930ea6 commit 4e96b2d

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

fs/ext4/ext4.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@ struct ext4_new_group_data {
528528
#define EXT4_FREE_BLOCKS_VALIDATED 0x0004
529529
#define EXT4_FREE_BLOCKS_NO_QUOT_UPDATE 0x0008
530530

531+
/*
532+
* Flags used by ext4_discard_partial_page_buffers
533+
*/
534+
#define EXT4_DISCARD_PARTIAL_PG_ZERO_UNMAPPED 0x0001
535+
531536
/*
532537
* ioctl commands
533538
*/
@@ -1838,6 +1843,12 @@ extern int ext4_block_truncate_page(handle_t *handle,
18381843
struct address_space *mapping, loff_t from);
18391844
extern int ext4_block_zero_page_range(handle_t *handle,
18401845
struct address_space *mapping, loff_t from, loff_t length);
1846+
extern int ext4_discard_partial_page_buffers(handle_t *handle,
1847+
struct address_space *mapping, loff_t from,
1848+
loff_t length, int flags);
1849+
extern 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);
18411852
extern int ext4_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf);
18421853
extern qsize_t *ext4_get_reserved_space(struct inode *inode);
18431854
extern void ext4_da_update_reserve_space(struct inode *inode,

fs/ext4/inode.c

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,6 +2966,230 @@ void ext4_set_aops(struct inode *inode)
29662966
inode->i_mapping->a_ops = &ext4_journalled_aops;
29672967
}
29682968

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+
*/
2978+
int 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+
*/
3032+
int 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");
3184+
next:
3185+
bh = bh->b_this_page;
3186+
iblock++;
3187+
pos += range_to_discard;
3188+
}
3189+
3190+
return err;
3191+
}
3192+
29693193
/*
29703194
* ext4_block_truncate_page() zeroes out a mapping from file offset `from'
29713195
* up to the end of the block which corresponds to `from'.

0 commit comments

Comments
 (0)