From 256fe4162f8b5a1625b8603ca5f7ff79725bfb47 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 2 Jan 2023 20:14:47 +0100 Subject: udf: Do not update file length for failed writes to inline files When write to inline file fails (or happens only partly), we still updated length of inline data as if the whole write succeeded. Fix the update of length of inline data to happen only if the write succeeds. Reported-by: syzbot+0937935b993956ba28ab@syzkaller.appspotmail.com CC: stable@vger.kernel.org Signed-off-by: Jan Kara --- fs/udf/file.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 5c659e23e578..8be51161f3e5 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -149,26 +149,24 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) goto out; down_write(&iinfo->i_data_sem); - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - loff_t end = iocb->ki_pos + iov_iter_count(from); - - if (inode->i_sb->s_blocksize < - (udf_file_entry_alloc_offset(inode) + end)) { - err = udf_expand_file_adinicb(inode); - if (err) { - inode_unlock(inode); - udf_debug("udf_expand_adinicb: err=%d\n", err); - return err; - } - } else { - iinfo->i_lenAlloc = max(end, inode->i_size); - up_write(&iinfo->i_data_sem); + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && + inode->i_sb->s_blocksize < (udf_file_entry_alloc_offset(inode) + + iocb->ki_pos + iov_iter_count(from))) { + err = udf_expand_file_adinicb(inode); + if (err) { + inode_unlock(inode); + udf_debug("udf_expand_adinicb: err=%d\n", err); + return err; } } else up_write(&iinfo->i_data_sem); retval = __generic_file_write_iter(iocb, from); out: + down_write(&iinfo->i_data_sem); + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && retval > 0) + iinfo->i_lenAlloc = inode->i_size; + up_write(&iinfo->i_data_sem); inode_unlock(inode); if (retval > 0) { -- cgit v1.2.3 From 6a3b37e0ea20bcb50e519c75fae3387199f35400 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 19 Jan 2023 12:28:37 +0100 Subject: udf: Push i_data_sem locking into udf_expand_file_adinicb() The checks we do in udf_setsize() and udf_file_write_iter() are safe to do only with i_rwsem locked as it stabilizes both file type and file size. Hence we don't need to lock i_data_sem before we enter udf_expand_file_adinicb() which simplifies the locking somewhat. Signed-off-by: Jan Kara --- fs/udf/file.c | 11 +++++------ fs/udf/inode.c | 18 ++++++------------ 2 files changed, 11 insertions(+), 18 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 8be51161f3e5..60524814c594 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -148,7 +148,6 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (retval <= 0) goto out; - down_write(&iinfo->i_data_sem); if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && inode->i_sb->s_blocksize < (udf_file_entry_alloc_offset(inode) + iocb->ki_pos + iov_iter_count(from))) { @@ -158,15 +157,15 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) udf_debug("udf_expand_adinicb: err=%d\n", err); return err; } - } else - up_write(&iinfo->i_data_sem); + } retval = __generic_file_write_iter(iocb, from); out: - down_write(&iinfo->i_data_sem); - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && retval > 0) + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && retval > 0) { + down_write(&iinfo->i_data_sem); iinfo->i_lenAlloc = inode->i_size; - up_write(&iinfo->i_data_sem); + up_write(&iinfo->i_data_sem); + } inode_unlock(inode); if (retval > 0) { diff --git a/fs/udf/inode.c b/fs/udf/inode.c index adef328c4278..627cdcb2f456 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -247,7 +247,6 @@ const struct address_space_operations udf_aops = { /* * Expand file stored in ICB to a normal one-block-file * - * This function requires i_data_sem for writing and releases it. * This function requires i_mutex held */ int udf_expand_file_adinicb(struct inode *inode) @@ -259,6 +258,7 @@ int udf_expand_file_adinicb(struct inode *inode) WARN_ON_ONCE(!inode_is_locked(inode)); if (!iinfo->i_lenAlloc) { + down_write(&iinfo->i_data_sem); if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD)) iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT; else @@ -269,11 +269,6 @@ int udf_expand_file_adinicb(struct inode *inode) mark_inode_dirty(inode); return 0; } - /* - * Release i_data_sem so that we can lock a page - page lock ranks - * above i_data_sem. i_mutex still protects us against file changes. - */ - up_write(&iinfo->i_data_sem); page = find_or_create_page(inode->i_mapping, 0, GFP_NOFS); if (!page) @@ -1160,19 +1155,18 @@ int udf_setsize(struct inode *inode, loff_t newsize) iinfo = UDF_I(inode); if (newsize > inode->i_size) { - down_write(&iinfo->i_data_sem); if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { - if (bsize < + if (bsize >= (udf_file_entry_alloc_offset(inode) + newsize)) { - err = udf_expand_file_adinicb(inode); - if (err) - return err; down_write(&iinfo->i_data_sem); - } else { iinfo->i_lenAlloc = newsize; goto set_size; } + err = udf_expand_file_adinicb(inode); + if (err) + return err; } + down_write(&iinfo->i_data_sem); err = udf_extend_file(inode, newsize); if (err) { up_write(&iinfo->i_data_sem); -- cgit v1.2.3 From 96eeaaaea592079fcbf0b18a2b6f99165b32c942 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 19 Jan 2023 12:44:34 +0100 Subject: udf: Simplify error handling in udf_file_write_iter() When udf_expand_file_adinicb() fails, we can now use the standard exit path instead of implementing our own. Signed-off-by: Jan Kara --- fs/udf/file.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 60524814c594..596d703fb6c8 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -140,7 +140,6 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); struct udf_inode_info *iinfo = UDF_I(inode); - int err; inode_lock(inode); @@ -151,12 +150,9 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && inode->i_sb->s_blocksize < (udf_file_entry_alloc_offset(inode) + iocb->ki_pos + iov_iter_count(from))) { - err = udf_expand_file_adinicb(inode); - if (err) { - inode_unlock(inode); - udf_debug("udf_expand_adinicb: err=%d\n", err); - return err; - } + retval = udf_expand_file_adinicb(inode); + if (retval) + goto out; } retval = __generic_file_write_iter(iocb, from); -- cgit v1.2.3 From b9a861fd527ab123e76effb492b4eb7e8115d4ca Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 19 Jan 2023 12:46:09 +0100 Subject: udf: Protect truncate and file type conversion with invalidate_lock Protect truncate and file type conversion in udf_file_write_iter() with invalidate lock. That will allow us to serialize these paths with page faults so that the page fault can determine the file type in a racefree way. Signed-off-by: Jan Kara --- fs/udf/file.c | 2 ++ fs/udf/inode.c | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 596d703fb6c8..cf050bdffd9e 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -150,7 +150,9 @@ static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB && inode->i_sb->s_blocksize < (udf_file_entry_alloc_offset(inode) + iocb->ki_pos + iov_iter_count(from))) { + filemap_invalidate_lock(inode->i_mapping); retval = udf_expand_file_adinicb(inode); + filemap_invalidate_unlock(inode->i_mapping); if (retval) goto out; } diff --git a/fs/udf/inode.c b/fs/udf/inode.c index b4e4aacdaabc..f57ef7d0a207 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -1145,7 +1145,7 @@ struct buffer_head *udf_bread(struct inode *inode, udf_pblk_t block, int udf_setsize(struct inode *inode, loff_t newsize) { - int err; + int err = 0; struct udf_inode_info *iinfo; unsigned int bsize = i_blocksize(inode); @@ -1155,6 +1155,7 @@ int udf_setsize(struct inode *inode, loff_t newsize) if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) return -EPERM; + filemap_invalidate_lock(inode->i_mapping); iinfo = UDF_I(inode); if (newsize > inode->i_size) { if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { @@ -1167,11 +1168,11 @@ int udf_setsize(struct inode *inode, loff_t newsize) } err = udf_expand_file_adinicb(inode); if (err) - return err; + goto out_unlock; } err = udf_extend_file(inode, newsize); if (err) - return err; + goto out_unlock; set_size: truncate_setsize(inode, newsize); } else { @@ -1189,14 +1190,14 @@ set_size: err = block_truncate_page(inode->i_mapping, newsize, udf_get_block); if (err) - return err; + goto out_unlock; truncate_setsize(inode, newsize); down_write(&iinfo->i_data_sem); udf_clear_extent_cache(inode); err = udf_truncate_extents(inode); up_write(&iinfo->i_data_sem); if (err) - return err; + goto out_unlock; } update_time: inode->i_mtime = inode->i_ctime = current_time(inode); @@ -1204,7 +1205,9 @@ update_time: udf_sync_inode(inode); else mark_inode_dirty(inode); - return 0; +out_unlock: + filemap_invalidate_unlock(inode->i_mapping); + return err; } /* -- cgit v1.2.3 From 3c21204818ae45504b5d8ce8902748ef2306f0f3 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 19 Jan 2023 10:51:21 +0100 Subject: udf: Allocate blocks on write page fault Currently if file with holes is mapped, udf allocates blocks for dirtied pages during page writeback. This however creates problems when to truncate final extent to proper size and currently we leave the last extent untruncated which violates UDF standard. So allocate blocks on write page fault instead. In that case the last extent gets truncated the file is closed and everything is happy. Signed-off-by: Jan Kara --- fs/udf/file.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- fs/udf/inode.c | 1 - fs/udf/udfdecl.h | 1 + 3 files changed, 61 insertions(+), 2 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index cf050bdffd9e..322115c8369d 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -134,6 +134,57 @@ const struct address_space_operations udf_adinicb_aops = { .direct_IO = udf_adinicb_direct_IO, }; +static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct inode *inode = file_inode(vma->vm_file); + struct address_space *mapping = inode->i_mapping; + struct page *page = vmf->page; + loff_t size; + unsigned int end; + vm_fault_t ret = VM_FAULT_LOCKED; + int err; + + sb_start_pagefault(inode->i_sb); + file_update_time(vma->vm_file); + filemap_invalidate_lock_shared(mapping); + lock_page(page); + size = i_size_read(inode); + if (page->mapping != inode->i_mapping || page_offset(page) >= size) { + unlock_page(page); + ret = VM_FAULT_NOPAGE; + goto out_unlock; + } + /* Space is already allocated for in-ICB file */ + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + goto out_dirty; + if (page->index == size >> PAGE_SHIFT) + end = size & ~PAGE_MASK; + else + end = PAGE_SIZE; + err = __block_write_begin(page, 0, end, udf_get_block); + if (!err) + err = block_commit_write(page, 0, end); + if (err < 0) { + unlock_page(page); + ret = block_page_mkwrite_return(err); + goto out_unlock; + } +out_dirty: + set_page_dirty(page); + wait_for_stable_page(page); +out_unlock: + filemap_invalidate_unlock_shared(mapping); + sb_end_pagefault(inode->i_sb); + return ret; +} + +static const struct vm_operations_struct udf_file_vm_ops = { + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = udf_page_mkwrite, +}; + static ssize_t udf_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { ssize_t retval; @@ -238,11 +289,19 @@ static int udf_release_file(struct inode *inode, struct file *filp) return 0; } +static int udf_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + file_accessed(file); + vma->vm_ops = &udf_file_vm_ops; + + return 0; +} + const struct file_operations udf_file_operations = { .read_iter = generic_file_read_iter, .unlocked_ioctl = udf_ioctl, .open = generic_file_open, - .mmap = generic_file_mmap, + .mmap = udf_file_mmap, .write_iter = udf_file_write_iter, .release = udf_release_file, .fsync = generic_file_fsync, diff --git a/fs/udf/inode.c b/fs/udf/inode.c index f57ef7d0a207..bac4f905bbd1 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -68,7 +68,6 @@ static void udf_prealloc_extents(struct inode *, int, int, static void udf_merge_extents(struct inode *, struct kernel_long_ad *, int *); static int udf_update_extents(struct inode *, struct kernel_long_ad *, int, int, struct extent_position *); -static int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); static void __udf_clear_extent_cache(struct inode *inode) { diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 98b4d89b4368..5ba59ab90d48 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -160,6 +160,7 @@ extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); +int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); extern int udf_setup_indirect_aext(struct inode *inode, udf_pblk_t block, struct extent_position *epos); extern int __udf_add_aext(struct inode *inode, struct extent_position *epos, -- cgit v1.2.3 From b7c31e6f14779324a39927cdaf493dc193576da1 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 11:40:28 +0100 Subject: udf: Unify .read_folio for normal and in-ICB files Switching address_space_operations while a file is used is difficult to do in a race-free way. To be able to use single address_space_operations in UDF, make udf_read_folio() handle both normal and in-ICB files. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 15 +++------------ fs/udf/inode.c | 9 ++++++++- fs/udf/udfdecl.h | 2 ++ 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 322115c8369d..2666234a5204 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -38,7 +38,7 @@ #include "udf_i.h" #include "udf_sb.h" -static void __udf_adinicb_readpage(struct page *page) +void udf_adinicb_readpage(struct page *page) { struct inode *inode = page->mapping->host; char *kaddr; @@ -57,15 +57,6 @@ static void __udf_adinicb_readpage(struct page *page) kunmap_atomic(kaddr); } -static int udf_adinicb_read_folio(struct file *file, struct folio *folio) -{ - BUG_ON(!folio_test_locked(folio)); - __udf_adinicb_readpage(&folio->page); - folio_unlock(folio); - - return 0; -} - static int udf_adinicb_writepage(struct page *page, struct writeback_control *wbc) { @@ -100,7 +91,7 @@ static int udf_adinicb_write_begin(struct file *file, *pagep = page; if (!PageUptodate(page)) - __udf_adinicb_readpage(page); + udf_adinicb_readpage(page); return 0; } @@ -127,7 +118,7 @@ static int udf_adinicb_write_end(struct file *file, struct address_space *mappin const struct address_space_operations udf_adinicb_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, - .read_folio = udf_adinicb_read_folio, + .read_folio = udf_read_folio, .writepage = udf_adinicb_writepage, .write_begin = udf_adinicb_write_begin, .write_end = udf_adinicb_write_end, diff --git a/fs/udf/inode.c b/fs/udf/inode.c index ee440d16411e..a1816f067c14 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -191,8 +191,15 @@ static int udf_writepages(struct address_space *mapping, return mpage_writepages(mapping, wbc, udf_get_block_wb); } -static int udf_read_folio(struct file *file, struct folio *folio) +int udf_read_folio(struct file *file, struct folio *folio) { + struct udf_inode_info *iinfo = UDF_I(file_inode(file)); + + if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { + udf_adinicb_readpage(&folio->page); + folio_unlock(folio); + return 0; + } return mpage_read_folio(folio, udf_get_block); } diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 5ba59ab90d48..6b93b393cb46 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -138,6 +138,7 @@ static inline unsigned int udf_dir_entry_len(struct fileIdentDesc *cfi) /* file.c */ extern long udf_ioctl(struct file *, unsigned int, unsigned long); +void udf_adinicb_readpage(struct page *page); /* inode.c */ extern struct inode *__udf_iget(struct super_block *, struct kernel_lb_addr *, @@ -158,6 +159,7 @@ extern struct buffer_head *udf_bread(struct inode *inode, udf_pblk_t block, extern int udf_setsize(struct inode *, loff_t); extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); +int udf_read_folio(struct file *file, struct folio *folio); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); -- cgit v1.2.3 From 79d3c6dbada4a20193467f72c531ab99c173bf4f Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 11:54:08 +0100 Subject: udf: Convert in-ICB files to use udf_writepages() Switching address_space_operations while a file is used is difficult to do in a race-free way. To be able to use single address_space_operations in UDF, make in-ICB files use udf_writepages(). Reported-by: syzbot+c27475eb921c46bbdc62@syzkaller.appspotmail.com Reported-by: Christoph Hellwig Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 21 +-------------------- fs/udf/inode.c | 29 ++++++++++++++++++++++++++--- fs/udf/udfdecl.h | 1 + 3 files changed, 28 insertions(+), 23 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 2666234a5204..7a8dbad86e41 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -57,25 +57,6 @@ void udf_adinicb_readpage(struct page *page) kunmap_atomic(kaddr); } -static int udf_adinicb_writepage(struct page *page, - struct writeback_control *wbc) -{ - struct inode *inode = page->mapping->host; - char *kaddr; - struct udf_inode_info *iinfo = UDF_I(inode); - - BUG_ON(!PageLocked(page)); - - kaddr = kmap_atomic(page); - memcpy(iinfo->i_data + iinfo->i_lenEAttr, kaddr, i_size_read(inode)); - SetPageUptodate(page); - kunmap_atomic(kaddr); - mark_inode_dirty(inode); - unlock_page(page); - - return 0; -} - static int udf_adinicb_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, struct page **pagep, @@ -119,7 +100,7 @@ const struct address_space_operations udf_adinicb_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, .read_folio = udf_read_folio, - .writepage = udf_adinicb_writepage, + .writepages = udf_writepages, .write_begin = udf_adinicb_write_begin, .write_end = udf_adinicb_write_end, .direct_IO = udf_adinicb_direct_IO, diff --git a/fs/udf/inode.c b/fs/udf/inode.c index a1816f067c14..f0ab4dd0d8ce 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -185,10 +185,33 @@ static void udf_write_failed(struct address_space *mapping, loff_t to) } } -static int udf_writepages(struct address_space *mapping, - struct writeback_control *wbc) +static int udf_adinicb_writepage(struct page *page, + struct writeback_control *wbc, void *data) { - return mpage_writepages(mapping, wbc, udf_get_block_wb); + struct inode *inode = page->mapping->host; + char *kaddr; + struct udf_inode_info *iinfo = UDF_I(inode); + + BUG_ON(!PageLocked(page)); + + kaddr = kmap_atomic(page); + memcpy(iinfo->i_data + iinfo->i_lenEAttr, kaddr, i_size_read(inode)); + SetPageUptodate(page); + kunmap_atomic(kaddr); + unlock_page(page); + mark_inode_dirty(inode); + + return 0; +} + +int udf_writepages(struct address_space *mapping, struct writeback_control *wbc) +{ + struct inode *inode = mapping->host; + struct udf_inode_info *iinfo = UDF_I(inode); + + if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) + return mpage_writepages(mapping, wbc, udf_get_block_wb); + return write_cache_pages(mapping, wbc, udf_adinicb_writepage, NULL); } int udf_read_folio(struct file *file, struct folio *folio) diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 6b93b393cb46..48647eab26a6 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -160,6 +160,7 @@ extern int udf_setsize(struct inode *, loff_t); extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); int udf_read_folio(struct file *file, struct folio *folio); +int udf_writepages(struct address_space *mapping, struct writeback_control *wbc); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); -- cgit v1.2.3 From d5abfb1b7b26086db19ee430dea7282f01d4ef44 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 11:59:32 +0100 Subject: udf: Convert in-ICB files to use udf_direct_IO() Switching address_space_operations while a file is used is difficult to do in a race-free way. To be able to use single address_space_operations in UDF, make in-ICB files use udf_direct_IO(). Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 8 +------- fs/udf/inode.c | 5 ++++- fs/udf/udfdecl.h | 1 + 3 files changed, 6 insertions(+), 8 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 7a8dbad86e41..6bab6aa7770a 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -76,12 +76,6 @@ static int udf_adinicb_write_begin(struct file *file, return 0; } -static ssize_t udf_adinicb_direct_IO(struct kiocb *iocb, struct iov_iter *iter) -{ - /* Fallback to buffered I/O. */ - return 0; -} - static int udf_adinicb_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) @@ -103,7 +97,7 @@ const struct address_space_operations udf_adinicb_aops = { .writepages = udf_writepages, .write_begin = udf_adinicb_write_begin, .write_end = udf_adinicb_write_end, - .direct_IO = udf_adinicb_direct_IO, + .direct_IO = udf_direct_IO, }; static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf) diff --git a/fs/udf/inode.c b/fs/udf/inode.c index f0ab4dd0d8ce..d01b97e9e4f4 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -243,7 +243,7 @@ static int udf_write_begin(struct file *file, struct address_space *mapping, return ret; } -static ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) +ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; struct address_space *mapping = file->f_mapping; @@ -251,6 +251,9 @@ static ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) size_t count = iov_iter_count(iter); ssize_t ret; + /* Fallback to buffered IO for in-ICB files */ + if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) + return 0; ret = blockdev_direct_IO(iocb, inode, iter, udf_get_block); if (unlikely(ret < 0 && iov_iter_rw(iter) == WRITE)) udf_write_failed(mapping, iocb->ki_pos + count); diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 48647eab26a6..a851613465c6 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -161,6 +161,7 @@ extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); int udf_read_folio(struct file *file, struct folio *folio); int udf_writepages(struct address_space *mapping, struct writeback_control *wbc); +ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); int udf_get_block(struct inode *, sector_t, struct buffer_head *, int); -- cgit v1.2.3 From 60b99a1b9fa731453e1b69a3e0b3e4dcab7a6ea5 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 12:07:37 +0100 Subject: udf: Convert in-ICB files to use udf_write_begin() Switching address_space_operations while a file is used is difficult to do in a race-free way. To be able to use single address_space_operations in UDF, make in-ICB files use udf_write_begin(). Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 21 +-------------------- fs/udf/inode.c | 24 +++++++++++++++++++----- fs/udf/udfdecl.h | 3 +++ 3 files changed, 23 insertions(+), 25 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 6bab6aa7770a..16aecf4b2387 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -57,25 +57,6 @@ void udf_adinicb_readpage(struct page *page) kunmap_atomic(kaddr); } -static int udf_adinicb_write_begin(struct file *file, - struct address_space *mapping, loff_t pos, - unsigned len, struct page **pagep, - void **fsdata) -{ - struct page *page; - - if (WARN_ON_ONCE(pos >= PAGE_SIZE)) - return -EIO; - page = grab_cache_page_write_begin(mapping, 0); - if (!page) - return -ENOMEM; - *pagep = page; - - if (!PageUptodate(page)) - udf_adinicb_readpage(page); - return 0; -} - static int udf_adinicb_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) @@ -95,7 +76,7 @@ const struct address_space_operations udf_adinicb_aops = { .invalidate_folio = block_invalidate_folio, .read_folio = udf_read_folio, .writepages = udf_writepages, - .write_begin = udf_adinicb_write_begin, + .write_begin = udf_write_begin, .write_end = udf_adinicb_write_end, .direct_IO = udf_direct_IO, }; diff --git a/fs/udf/inode.c b/fs/udf/inode.c index d01b97e9e4f4..afe061d2020e 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -231,16 +231,30 @@ static void udf_readahead(struct readahead_control *rac) mpage_readahead(rac, udf_get_block); } -static int udf_write_begin(struct file *file, struct address_space *mapping, +int udf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, struct page **pagep, void **fsdata) { + struct udf_inode_info *iinfo = UDF_I(file_inode(file)); + struct page *page; int ret; - ret = block_write_begin(mapping, pos, len, pagep, udf_get_block); - if (unlikely(ret)) - udf_write_failed(mapping, pos + len); - return ret; + if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) { + ret = block_write_begin(mapping, pos, len, pagep, + udf_get_block); + if (unlikely(ret)) + udf_write_failed(mapping, pos + len); + return ret; + } + if (WARN_ON_ONCE(pos >= PAGE_SIZE)) + return -EIO; + page = grab_cache_page_write_begin(mapping, 0); + if (!page) + return -ENOMEM; + *pagep = page; + if (!PageUptodate(page)) + udf_adinicb_readpage(page); + return 0; } ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index a851613465c6..32decf6b6a21 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -161,6 +161,9 @@ extern void udf_evict_inode(struct inode *); extern int udf_write_inode(struct inode *, struct writeback_control *wbc); int udf_read_folio(struct file *file, struct folio *folio); int udf_writepages(struct address_space *mapping, struct writeback_control *wbc); +int udf_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, + struct page **pagep, void **fsdata); ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); -- cgit v1.2.3 From c694e40ba231e854fcddd25cce46db3918fca291 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 12:13:57 +0100 Subject: udf: Convert all file types to use udf_write_end() Switching address_space_operations while a file is used is difficult to do in a race-free way. To be able to use single address_space_operations in UDF, create udf_write_end() function that is able to handle both normal and in-ICB files. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 16 +--------------- fs/udf/inode.c | 22 +++++++++++++++++++++- fs/udf/udfdecl.h | 3 +++ 3 files changed, 25 insertions(+), 16 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 16aecf4b2387..8a37cd593883 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -57,27 +57,13 @@ void udf_adinicb_readpage(struct page *page) kunmap_atomic(kaddr); } -static int udf_adinicb_write_end(struct file *file, struct address_space *mapping, - loff_t pos, unsigned len, unsigned copied, - struct page *page, void *fsdata) -{ - struct inode *inode = page->mapping->host; - loff_t last_pos = pos + copied; - if (last_pos > inode->i_size) - i_size_write(inode, last_pos); - set_page_dirty(page); - unlock_page(page); - put_page(page); - return copied; -} - const struct address_space_operations udf_adinicb_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, .read_folio = udf_read_folio, .writepages = udf_writepages, .write_begin = udf_write_begin, - .write_end = udf_adinicb_write_end, + .write_end = udf_write_end, .direct_IO = udf_direct_IO, }; diff --git a/fs/udf/inode.c b/fs/udf/inode.c index afe061d2020e..094ac6f22c72 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -257,6 +257,26 @@ int udf_write_begin(struct file *file, struct address_space *mapping, return 0; } +int udf_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct page *page, void *fsdata) +{ + struct inode *inode = file_inode(file); + loff_t last_pos; + + if (UDF_I(inode)->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) + return generic_write_end(file, mapping, pos, len, copied, page, + fsdata); + last_pos = pos + copied; + if (last_pos > inode->i_size) + i_size_write(inode, last_pos); + set_page_dirty(page); + unlock_page(page); + put_page(page); + + return copied; +} + ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; @@ -286,7 +306,7 @@ const struct address_space_operations udf_aops = { .readahead = udf_readahead, .writepages = udf_writepages, .write_begin = udf_write_begin, - .write_end = generic_write_end, + .write_end = udf_write_end, .direct_IO = udf_direct_IO, .bmap = udf_bmap, .migrate_folio = buffer_migrate_folio, diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 32decf6b6a21..304c2ec81589 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -164,6 +164,9 @@ int udf_writepages(struct address_space *mapping, struct writeback_control *wbc) int udf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, struct page **pagep, void **fsdata); +int udf_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct page *page, void *fsdata); ssize_t udf_direct_IO(struct kiocb *iocb, struct iov_iter *iter); extern int8_t inode_bmap(struct inode *, sector_t, struct extent_position *, struct kernel_lb_addr *, uint32_t *, sector_t *); -- cgit v1.2.3 From 37a8a39f7ad34d4ad31c7f5cf6b2237113963c48 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 12:23:04 +0100 Subject: udf: Switch to single address_space_operations Now that udf_aops and udf_adiniicb_aops are functionally identical, just drop udf_adiniicb_aops. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 10 ---------- fs/udf/inode.c | 8 +------- fs/udf/namei.c | 10 ++-------- fs/udf/udfdecl.h | 1 - 4 files changed, 3 insertions(+), 26 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 8a37cd593883..84e0b241940d 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -57,16 +57,6 @@ void udf_adinicb_readpage(struct page *page) kunmap_atomic(kaddr); } -const struct address_space_operations udf_adinicb_aops = { - .dirty_folio = block_dirty_folio, - .invalidate_folio = block_invalidate_folio, - .read_folio = udf_read_folio, - .writepages = udf_writepages, - .write_begin = udf_write_begin, - .write_end = udf_write_end, - .direct_IO = udf_direct_IO, -}; - static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; diff --git a/fs/udf/inode.c b/fs/udf/inode.c index c65a5d76d15a..97662898704c 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -364,8 +364,6 @@ int udf_expand_file_adinicb(struct inode *inode) iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT; else iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG; - /* from now on we have normal address_space methods */ - inode->i_data.a_ops = &udf_aops; set_page_dirty(page); unlock_page(page); up_write(&iinfo->i_data_sem); @@ -379,7 +377,6 @@ int udf_expand_file_adinicb(struct inode *inode) kunmap_atomic(kaddr); unlock_page(page); iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB; - inode->i_data.a_ops = &udf_adinicb_aops; iinfo->i_lenAlloc = inode->i_size; up_write(&iinfo->i_data_sem); } @@ -1566,10 +1563,7 @@ reread: case ICBTAG_FILE_TYPE_REGULAR: case ICBTAG_FILE_TYPE_UNDEF: case ICBTAG_FILE_TYPE_VAT20: - if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; - else - inode->i_data.a_ops = &udf_aops; + inode->i_data.a_ops = &udf_aops; inode->i_op = &udf_file_inode_operations; inode->i_fop = &udf_file_operations; inode->i_mode |= S_IFREG; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 663b66014c98..51a518cb6fc2 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -377,10 +377,7 @@ static int udf_create(struct user_namespace *mnt_userns, struct inode *dir, if (IS_ERR(inode)) return PTR_ERR(inode); - if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; - else - inode->i_data.a_ops = &udf_aops; + inode->i_data.a_ops = &udf_aops; inode->i_op = &udf_file_inode_operations; inode->i_fop = &udf_file_operations; mark_inode_dirty(inode); @@ -396,10 +393,7 @@ static int udf_tmpfile(struct user_namespace *mnt_userns, struct inode *dir, if (IS_ERR(inode)) return PTR_ERR(inode); - if (UDF_I(inode)->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) - inode->i_data.a_ops = &udf_adinicb_aops; - else - inode->i_data.a_ops = &udf_aops; + inode->i_data.a_ops = &udf_aops; inode->i_op = &udf_file_inode_operations; inode->i_fop = &udf_file_operations; mark_inode_dirty(inode); diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 304c2ec81589..d8c0de3b224e 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -80,7 +80,6 @@ extern const struct inode_operations udf_file_inode_operations; extern const struct file_operations udf_file_operations; extern const struct inode_operations udf_symlink_inode_operations; extern const struct address_space_operations udf_aops; -extern const struct address_space_operations udf_adinicb_aops; extern const struct address_space_operations udf_symlink_aops; struct udf_fileident_iter { -- cgit v1.2.3 From 174cb748d88819d8a6420aa5040f9f065fcbdbab Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 24 Jan 2023 12:29:57 +0100 Subject: udf: Move udf_adinicb_readpage() to inode.c udf_adinicb_readpage() is only called from aops functions, move it to the same file as its callers and also drop the stale comment - invalidate_lock is protecting us against races with truncate. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara --- fs/udf/file.c | 19 ------------------- fs/udf/inode.c | 15 +++++++++++++++ fs/udf/udfdecl.h | 1 - 3 files changed, 15 insertions(+), 20 deletions(-) (limited to 'fs/udf/file.c') diff --git a/fs/udf/file.c b/fs/udf/file.c index 84e0b241940d..26592577b7da 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -38,25 +38,6 @@ #include "udf_i.h" #include "udf_sb.h" -void udf_adinicb_readpage(struct page *page) -{ - struct inode *inode = page->mapping->host; - char *kaddr; - struct udf_inode_info *iinfo = UDF_I(inode); - loff_t isize = i_size_read(inode); - - /* - * We have to be careful here as truncate can change i_size under us. - * So just sample it once and use the same value everywhere. - */ - kaddr = kmap_atomic(page); - memcpy(kaddr, iinfo->i_data + iinfo->i_lenEAttr, isize); - memset(kaddr + isize, 0, PAGE_SIZE - isize); - flush_dcache_page(page); - SetPageUptodate(page); - kunmap_atomic(kaddr); -} - static vm_fault_t udf_page_mkwrite(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; diff --git a/fs/udf/inode.c b/fs/udf/inode.c index 084b1f319713..b2bad81a253e 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -215,6 +215,21 @@ static int udf_writepages(struct address_space *mapping, return write_cache_pages(mapping, wbc, udf_adinicb_writepage, NULL); } +static void udf_adinicb_readpage(struct page *page) +{ + struct inode *inode = page->mapping->host; + char *kaddr; + struct udf_inode_info *iinfo = UDF_I(inode); + loff_t isize = i_size_read(inode); + + kaddr = kmap_atomic(page); + memcpy(kaddr, iinfo->i_data + iinfo->i_lenEAttr, isize); + memset(kaddr + isize, 0, PAGE_SIZE - isize); + flush_dcache_page(page); + SetPageUptodate(page); + kunmap_atomic(kaddr); +} + static int udf_read_folio(struct file *file, struct folio *folio) { struct udf_inode_info *iinfo = UDF_I(file_inode(file)); diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 337daf97d5b4..88692512a466 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h @@ -137,7 +137,6 @@ static inline unsigned int udf_dir_entry_len(struct fileIdentDesc *cfi) /* file.c */ extern long udf_ioctl(struct file *, unsigned int, unsigned long); -void udf_adinicb_readpage(struct page *page); /* inode.c */ extern struct inode *__udf_iget(struct super_block *, struct kernel_lb_addr *, -- cgit v1.2.3