From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085551 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5AECCC4332F for ; Sat, 31 Dec 2022 01:59:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236113AbiLaB7O (ORCPT ); Fri, 30 Dec 2022 20:59:14 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51168 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236108AbiLaB7N (ORCPT ); Fri, 30 Dec 2022 20:59:13 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 613CD1C430 for ; Fri, 30 Dec 2022 17:59:12 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id F060161C63 for ; Sat, 31 Dec 2022 01:59:11 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 58C34C433D2; Sat, 31 Dec 2022 01:59:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672451951; bh=60Aa8kLwcxl1JFcfdqnLAU0sQB63EmIk8WV/gAj7c7k=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=squ3xetuE8lnO0Ng3C0Frp8XzHFjg39myXWYjDtkVeddn8VHQE/7r4iJr0SJrzhVV jlPxk4rLg9JQi+9AUckt08rmnulLsXY0ETeTFaVZw5hW5+GvGU3PlSPSDu6b9gPwxQ bKCxxk92zWuEeS6Mf0Q/4/tPzRPIWc0Exbh5Nc3D3MqXk7bZmOfxgEOwJqLBeAQ1HV P8xKi7q1O1lRFIQp4PUCGj9FO8MLvO6Xc5QhevbBhYxqj2NKFVD/TSv3Eqpg7nGEn4 8I4NJpbsNir8XtZ/l66PMpnFwku5zmfjsnpZRsD5ZVhqWBGM0pIk6a7dKX2+SoqJYy p+6Oe36dPzY8Q== Subject: [PATCH 1/9] vfs: explicitly pass the block size to the remap prep function From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871817.718512.8634564486988370537.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong Make it so that filesystems can pass an explicit blocksize to the remap prep function. This enables filesystems whose fundamental allocation units are /not/ the same as the blocksize to ensure that the remapping checks are aligned properly. Signed-off-by: Darrick J. Wong --- fs/dax.c | 5 ++++- fs/remap_range.c | 30 ++++++++++++++++++------------ include/linux/fs.h | 3 ++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index c48a3a93ab29..9ec07a06f49c 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -2035,7 +2035,10 @@ int dax_remap_file_range_prep(struct file *file_in, loff_t pos_in, loff_t *len, unsigned int remap_flags, const struct iomap_ops *ops) { + unsigned int blocksize = file_inode(file_out)->i_sb->s_blocksize; + return __generic_remap_file_range_prep(file_in, pos_in, file_out, - pos_out, len, remap_flags, ops); + pos_out, len, remap_flags, ops, + blocksize); } EXPORT_SYMBOL_GPL(dax_remap_file_range_prep); diff --git a/fs/remap_range.c b/fs/remap_range.c index 469d53fb42e9..8a43038dc3e7 100644 --- a/fs/remap_range.c +++ b/fs/remap_range.c @@ -29,18 +29,18 @@ */ static int generic_remap_checks(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, - loff_t *req_count, unsigned int remap_flags) + loff_t *req_count, unsigned int remap_flags, + unsigned int blocksize) { struct inode *inode_in = file_in->f_mapping->host; struct inode *inode_out = file_out->f_mapping->host; uint64_t count = *req_count; uint64_t bcount; loff_t size_in, size_out; - loff_t bs = inode_out->i_sb->s_blocksize; int ret; /* The start of both ranges must be aligned to an fs block. */ - if (!IS_ALIGNED(pos_in, bs) || !IS_ALIGNED(pos_out, bs)) + if (!IS_ALIGNED(pos_in, blocksize) || !IS_ALIGNED(pos_out, blocksize)) return -EINVAL; /* Ensure offsets don't wrap. */ @@ -74,10 +74,10 @@ static int generic_remap_checks(struct file *file_in, loff_t pos_in, */ if (pos_in + count == size_in && (!(remap_flags & REMAP_FILE_DEDUP) || pos_out + count == size_out)) { - bcount = ALIGN(size_in, bs) - pos_in; + bcount = ALIGN(size_in, blocksize) - pos_in; } else { - if (!IS_ALIGNED(count, bs)) - count = ALIGN_DOWN(count, bs); + if (!IS_ALIGNED(count, blocksize)) + count = ALIGN_DOWN(count, blocksize); bcount = count; } @@ -125,9 +125,10 @@ static int generic_remap_check_len(struct inode *inode_in, struct inode *inode_out, loff_t pos_out, loff_t *len, - unsigned int remap_flags) + unsigned int remap_flags, + unsigned int blocksize) { - u64 blkmask = i_blocksize(inode_in) - 1; + u64 blkmask = blocksize - 1; loff_t new_len = *len; if ((*len & blkmask) == 0) @@ -268,7 +269,8 @@ int __generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *len, unsigned int remap_flags, - const struct iomap_ops *dax_read_ops) + const struct iomap_ops *dax_read_ops, + unsigned int blocksize) { struct inode *inode_in = file_inode(file_in); struct inode *inode_out = file_inode(file_out); @@ -303,7 +305,7 @@ __generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, /* Check that we don't violate system file offset limits. */ ret = generic_remap_checks(file_in, pos_in, file_out, pos_out, len, - remap_flags); + remap_flags, blocksize); if (ret || *len == 0) return ret; @@ -344,7 +346,7 @@ __generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, } ret = generic_remap_check_len(inode_in, inode_out, pos_out, len, - remap_flags); + remap_flags, blocksize); if (ret || *len == 0) return ret; @@ -354,13 +356,17 @@ __generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, return ret; } +EXPORT_SYMBOL(__generic_remap_file_range_prep); int generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *len, unsigned int remap_flags) { + unsigned int blocksize = file_inode(file_out)->i_sb->s_blocksize; + return __generic_remap_file_range_prep(file_in, pos_in, file_out, - pos_out, len, remap_flags, NULL); + pos_out, len, remap_flags, NULL, + blocksize); } EXPORT_SYMBOL(generic_remap_file_range_prep); diff --git a/include/linux/fs.h b/include/linux/fs.h index cd86ac22c339..5f8f4b11dc28 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2204,7 +2204,8 @@ extern ssize_t generic_copy_file_range(struct file *file_in, loff_t pos_in, int __generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *len, unsigned int remap_flags, - const struct iomap_ops *dax_read_ops); + const struct iomap_ops *dax_read_ops, + unsigned int block_size); int generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *count, unsigned int remap_flags); From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085552 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8988DC4332F for ; Sat, 31 Dec 2022 01:59:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236108AbiLaB7b (ORCPT ); Fri, 30 Dec 2022 20:59:31 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51216 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236118AbiLaB7a (ORCPT ); Fri, 30 Dec 2022 20:59:30 -0500 Received: from ams.source.kernel.org (ams.source.kernel.org [145.40.68.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6EB461C900 for ; Fri, 30 Dec 2022 17:59:29 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id 1C79DB81DED for ; Sat, 31 Dec 2022 01:59:28 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id DBD86C433EF; Sat, 31 Dec 2022 01:59:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672451966; bh=NDa7sWjMOAth0FgBAHhpqBHDbEcdpUHtN5FZiLgG2Y4=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=ZWp8QTg8MftwvumbMiEz/1lBLXOzNrRrOwgBMzZe3P05xv1kVo38M/Jzq7cAjQW7+ 3jrjGbUNWam7dm+j0HU4chfe+32r0YtbtRMWpoVkyvQOcMQSQUf2v+Qj/JeGlDnLOF 6Xies/RIU+bYXIiyg4HktvYqs3w8Y8caSL7QRTDpcjxmUmoUMgcc8AWtoByFxV/RIP B8WIYvgHABCJsxJ/XSCRKBghRkYfbbkWyNb44bgHFczI/lcbwfrZEsIKYZF/c+Ltl9 TALUGF7zEqsNtzKVWnfLjQGRMTsdMgHXsYkkG63xKWXwYORDXEGrNBC1y6/UDf+8aI owbn6bj7XajAw== Subject: [PATCH 2/9] iomap: set up for COWing around pages From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871832.718512.8291440482430734344.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong In anticipation of enabling reflink on the realtime volume where the allocation unit is larger than a page, create an iomap function to dirty arbitrary parts of a file's page cache so that when we dirty part of a file that could undergo a COW extent, we can dirty an entire allocation unit's worth of pages. Signed-off-by: Darrick J. Wong --- fs/iomap/buffered-io.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/iomap.h | 2 ++ 2 files changed, 57 insertions(+) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 356193e44cf0..da5a5d28e2ee 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -1141,6 +1141,61 @@ iomap_file_unshare(struct inode *inode, loff_t pos, loff_t len, } EXPORT_SYMBOL_GPL(iomap_file_unshare); +static loff_t iomap_dirty_iter(struct iomap_iter *iter) +{ + loff_t pos = iter->pos; + loff_t length = iomap_length(iter); + long status = 0; + loff_t written = 0; + + do { + unsigned long offset = offset_in_page(pos); + unsigned long bytes = min_t(loff_t, PAGE_SIZE - offset, length); + struct folio *folio; + + status = iomap_write_begin(iter, pos, bytes, &folio); + if (unlikely(status)) + return status; + + folio_mark_accessed(folio); + + status = iomap_write_end(iter, pos, bytes, bytes, folio); + if (WARN_ON_ONCE(status == 0)) + return -EIO; + + cond_resched(); + + pos += status; + written += status; + length -= status; + + balance_dirty_pages_ratelimited(iter->inode->i_mapping); + } while (length); + + return written; +} + +int +iomap_dirty_range(struct inode *inode, loff_t pos, u64 len, + const struct iomap_ops *ops) +{ + struct iomap_iter iter = { + .inode = inode, + .pos = pos, + .len = len, + .flags = IOMAP_WRITE, + }; + int ret; + + if (IS_DAX(inode)) + return -EINVAL; + + while ((ret = iomap_iter(&iter, ops)) > 0) + iter.processed = iomap_dirty_iter(&iter); + return ret; +} +EXPORT_SYMBOL_GPL(iomap_dirty_range); + static loff_t iomap_zero_iter(struct iomap_iter *iter, bool *did_zero) { const struct iomap *srcmap = iomap_iter_srcmap(iter); diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 0983dfc9a203..4d911d780165 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -264,6 +264,8 @@ bool iomap_release_folio(struct folio *folio, gfp_t gfp_flags); void iomap_invalidate_folio(struct folio *folio, size_t offset, size_t len); int iomap_file_unshare(struct inode *inode, loff_t pos, loff_t len, const struct iomap_ops *ops); +int iomap_dirty_range(struct inode *inode, loff_t pos, u64 len, + const struct iomap_ops *ops); int iomap_zero_range(struct inode *inode, loff_t pos, loff_t len, bool *did_zero, const struct iomap_ops *ops); int iomap_truncate_page(struct inode *inode, loff_t pos, bool *did_zero, From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085553 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C996FC4332F for ; Sat, 31 Dec 2022 01:59:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236116AbiLaB7p (ORCPT ); Fri, 30 Dec 2022 20:59:45 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51260 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236043AbiLaB7o (ORCPT ); Fri, 30 Dec 2022 20:59:44 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [139.178.84.217]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 77F8B1C430 for ; Fri, 30 Dec 2022 17:59:43 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 07A8661C44 for ; Sat, 31 Dec 2022 01:59:43 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6B092C433D2; Sat, 31 Dec 2022 01:59:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672451982; bh=iql+fvS7EP6D1klVfUP7nuUtyQSa5ntEDqRifMyvqhI=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=iBZF49Dk8xDy6ZyKUx/YhCbsyJYdhaeroGpHyY9zAXy4kBFDbCiOWPGaOd3H0ViHh VnvM+xC+22IQOAnoHE+6r4t66vlAMFwWPdJEUTK2zOC1qUM8Gg5+lbwxIWjX6/KW4p pETO8mFS8qN7pmw4UsmjFFE4ZwYqn3FZ+4cipts7MM/4raEeAQt4XkK8jagS0W0CPQ rJ82GtpuQaLFyf+e3rrOI1uDruTt2REk0hnRQnR6fkZY47CTHVOYD2yuvpoECmRE7O nI6uci9ypIH3biU94yJ9XnKDSPT0l/dozPIdPGB0NgfKcTEMTSTM/B/0zRdng18Yfy BRGC1KjZtaXUQ== Subject: [PATCH 3/9] xfs: enable CoW when rt extent size is larger than 1 block From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871846.718512.5400138730721927261.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong Copy on write encounters a major plot twist when the file being CoW'd lives on the realtime volume and the realtime extent size is larger than a single filesystem block. XFS can only unmap and remap full rt extents, which means that allocations are always done in units of full rt extents, and a request to unmap less than one extent is treated as a request to convert an extent to unwritten status. This behavioral quirk is not compatible with the existing CoW mechanism, so we have to intercept every path through which files can be modified to ensure that we dirty an entire rt extent at once so that we can remap a full rt extent. Use the existing VFS unshare functions to dirty the page cache to set that up. Signed-off-by: Darrick J. Wong --- fs/xfs/xfs_file.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_inode.h | 9 +++ fs/xfs/xfs_iops.c | 15 ++++ fs/xfs/xfs_reflink.c | 39 +++++++++++ fs/xfs/xfs_trace.h | 1 5 files changed, 235 insertions(+) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 87dfb05640a8..e172ca1b18df 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -32,6 +32,7 @@ #include #include #include +#include static const struct vm_operations_struct xfs_file_vm_ops; @@ -396,6 +397,13 @@ xfs_file_write_checks( goto restart; } + if (xfs_inode_needs_cow_around(ip)) { + error = xfs_file_cow_around(ip, isize, + iocb->ki_pos - isize); + if (error) + return error; + } + trace_xfs_zero_eof(ip, isize, iocb->ki_pos - isize); error = xfs_zero_range(ip, isize, iocb->ki_pos - isize, NULL); if (error) @@ -508,6 +516,7 @@ xfs_file_dio_write_aligned( struct iov_iter *from) { unsigned int iolock = XFS_IOLOCK_SHARED; + size_t count = iov_iter_count(from); ssize_t ret; ret = xfs_ilock_iocb(iocb, iolock); @@ -517,6 +526,17 @@ xfs_file_dio_write_aligned( if (ret) goto out_unlock; + /* + * We can't unshare a partial rt extent yet, which means that we can't + * handle direct writes that are block-aligned but not rtextent-aligned. + */ + if (xfs_inode_needs_cow_around(ip) && + !xfs_is_falloc_aligned(ip, iocb->ki_pos, count)) { + trace_xfs_reflink_bounce_dio_write(iocb, from); + ret = -ENOTBLK; + goto out_unlock; + } + /* * We don't need to hold the IOLOCK exclusively across the IO, so demote * the iolock back to shared if we had to take the exclusive lock in @@ -753,6 +773,68 @@ xfs_file_buffered_write( return ret; } +/* Unshare the rtextent at the given file position. */ +static inline int +xfs_file_unshare_at( + struct xfs_inode *ip, + loff_t isize, + unsigned int extsize, + loff_t pos) +{ + loff_t len = extsize; + uint32_t mod; + + div_u64_rem(pos, extsize, &mod); + if (mod == 0) + return 0; + + pos -= mod; + if (pos >= isize) + return 0; + + if (pos + len > isize) + len = isize - pos; + + trace_xfs_file_cow_around(ip, pos, len); + + return iomap_file_unshare(VFS_I(ip), pos, len, + &xfs_buffered_write_iomap_ops); +} + +/* + * Dirty the pages on either side of a write request as needed to satisfy + * alignment requirements if we're going to perform a copy-write. + * + * This is only needed for realtime files when the rt extent size is larger + * than 1 fs block, because we don't allow a logical rt extent in a file to map + * to multiple physical rt extents. In other words, we can only map and unmap + * full rt extents. Note that page cache doesn't exist above EOF, so be + * careful to stay below EOF. + */ +int +xfs_file_cow_around( + struct xfs_inode *ip, + loff_t pos, + long long int count) +{ + unsigned int extsize = xfs_inode_alloc_unitsize(ip); + loff_t isize = i_size_read(VFS_I(ip)); + int error; + + if (xfs_is_falloc_aligned(ip, pos, count)) + return 0; + + inode_dio_wait(VFS_I(ip)); + + /* Unshare at the start of the extent. */ + error = xfs_file_unshare_at(ip, isize, extsize, pos); + if (error) + return error; + + /* Unshare at the end. */ + return xfs_file_unshare_at(ip, isize, extsize, pos + count); +} + STATIC ssize_t xfs_file_write_iter( struct kiocb *iocb, @@ -774,6 +856,16 @@ xfs_file_write_iter( if (IS_DAX(inode)) return xfs_file_dax_write(iocb, from); + if (xfs_inode_needs_cow_around(ip)) { + ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_EXCL); + if (ret) + return ret; + ret = xfs_file_cow_around(ip, iocb->ki_pos, ocount); + xfs_iunlock(ip, XFS_IOLOCK_EXCL); + if (ret) + return ret; + } + if (iocb->ki_flags & IOCB_DIRECT) { /* * Allow a directio write to fall back to a buffered @@ -929,6 +1021,13 @@ xfs_file_fallocate( goto out_unlock; if (mode & FALLOC_FL_PUNCH_HOLE) { + /* Unshare around the region to punch, if needed. */ + if (xfs_inode_needs_cow_around(ip)) { + error = xfs_file_cow_around(ip, offset, len); + if (error) + goto out_unlock; + } + error = xfs_free_file_space(ip, offset, len); if (error) goto out_unlock; @@ -999,6 +1098,14 @@ xfs_file_fallocate( trace_xfs_zero_file_space(ip); + /* Unshare around the region to zero, if needed. */ + if (xfs_inode_needs_cow_around(ip)) { + error = xfs_file_cow_around(ip, offset, + len); + if (error) + goto out_unlock; + } + error = xfs_free_file_space(ip, offset, len); if (error) goto out_unlock; @@ -1007,6 +1114,26 @@ xfs_file_fallocate( round_down(offset, blksize); offset = round_down(offset, blksize); } else if (mode & FALLOC_FL_UNSHARE_RANGE) { + /* + * Enlarge the unshare region to align to a full + * allocation unit. + */ + if (xfs_inode_needs_cow_around(ip)) { + loff_t isize = i_size_read(VFS_I(ip)); + unsigned int rextsize; + uint32_t mod; + + rextsize = xfs_inode_alloc_unitsize(ip); + div_u64_rem(offset, rextsize, &mod); + offset -= mod; + len += mod; + + div_u64_rem(offset + len, rextsize, &mod); + if (mod) + len += rextsize - mod; + if (offset + len > isize) + len = isize - offset; + } error = xfs_reflink_unshare(ip, offset, len); if (error) goto out_unlock; @@ -1341,6 +1468,35 @@ xfs_dax_fault( } #endif +static int +xfs_filemap_fault_around( + struct vm_fault *vmf, + struct inode *inode) +{ + struct folio *folio = page_folio(vmf->page); + loff_t pos; + ssize_t len; + int error; + + if (!xfs_inode_needs_cow_around(XFS_I(inode))) + return 0; + + folio_lock(folio); + len = folio_mkwrite_check_truncate(folio, inode); + if (len < 0) { + folio_unlock(folio); + return len; + } + pos = folio_pos(folio); + folio_unlock(folio); + + error = xfs_file_cow_around(XFS_I(inode), pos, len); + if (error) + return error; + + return 0; +} + /* * Locking for serialisation of IO during page faults. This results in a lock * ordering of: @@ -1378,7 +1534,21 @@ __xfs_filemap_fault( xfs_iunlock(XFS_I(inode), XFS_MMAPLOCK_SHARED); } else { if (write_fault) { + int error; + xfs_ilock(XFS_I(inode), XFS_MMAPLOCK_SHARED); + + /* + * Unshare all the blocks in this rt extent surrounding + * this page. + */ + error = xfs_filemap_fault_around(vmf, inode); + if (error) { + xfs_iunlock(XFS_I(inode), XFS_MMAPLOCK_SHARED); + ret = block_page_mkwrite_return(error); + goto out; + } + ret = iomap_page_mkwrite(vmf, &xfs_page_mkwrite_iomap_ops); xfs_iunlock(XFS_I(inode), XFS_MMAPLOCK_SHARED); @@ -1387,6 +1557,7 @@ __xfs_filemap_fault( } } +out: if (write_fault) sb_end_pagefault(inode->i_sb); return ret; diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index ca7ebb07efc7..32a1d114dfaf 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -309,6 +309,12 @@ static inline bool xfs_inode_has_bigrtextents(struct xfs_inode *ip) return XFS_IS_REALTIME_INODE(ip) && ip->i_mount->m_sb.sb_rextsize > 1; } +/* Decide if we need to unshare the blocks around a range that we're writing. */ +static inline bool xfs_inode_needs_cow_around(struct xfs_inode *ip) +{ + return xfs_is_reflink_inode(ip) && xfs_inode_has_bigrtextents(ip); +} + /* * Return the buftarg used for data allocations on a given inode. */ @@ -636,4 +642,7 @@ int xfs_icreate_dqalloc(const struct xfs_icreate_args *args, struct xfs_dquot **udqpp, struct xfs_dquot **gdqpp, struct xfs_dquot **pdqpp); +int xfs_file_cow_around(struct xfs_inode *ip, loff_t pos, + long long int count); + #endif /* __XFS_INODE_H__ */ diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 626ce6c4e2bf..c0a827b23948 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -26,6 +26,7 @@ #include "xfs_ioctl.h" #include "xfs_xattr.h" #include "xfs_bmap.h" +#include "xfs_reflink.h" #include #include @@ -861,10 +862,24 @@ xfs_setattr_size( * truncate. */ if (newsize > oldsize) { + if (xfs_inode_needs_cow_around(ip)) { + error = xfs_file_cow_around(ip, oldsize, + newsize - oldsize); + if (error) + return error; + } + trace_xfs_zero_eof(ip, oldsize, newsize - oldsize); error = xfs_zero_range(ip, oldsize, newsize - oldsize, &did_zeroing); } else { + if (xfs_inode_needs_cow_around(ip)) { + error = xfs_file_cow_around(ip, newsize, + oldsize - newsize); + if (error) + return error; + } + /* * iomap won't detect a dirty page over an unwritten block (or a * cow block over a hole) and subsequently skips zeroing the diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 13a613c077df..8690017beb9b 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -34,6 +34,7 @@ #include "xfs_rtalloc.h" #include "xfs_rtgroup.h" #include "xfs_imeta.h" +#include "xfs_rtbitmap.h" /* * Copy on Write of Shared Blocks @@ -297,9 +298,26 @@ xfs_reflink_convert_cow_locked( struct xfs_iext_cursor icur; struct xfs_bmbt_irec got; struct xfs_btree_cur *dummy_cur = NULL; + struct xfs_mount *mp = ip->i_mount; int dummy_logflags; int error = 0; + /* + * We can only remap full rt extents, so make sure that we convert the + * entire extent. The caller must ensure that this is either a direct + * write that's aligned to the rt extent size, or a buffered write for + * which we've dirtied extra pages to make this work properly. + */ + if (xfs_inode_needs_cow_around(ip)) { + xfs_fileoff_t new_off; + + new_off = xfs_rtb_rounddown_rtx(mp, offset_fsb); + count_fsb += offset_fsb - new_off; + offset_fsb = new_off; + + count_fsb = xfs_rtb_roundup_rtx(mp, count_fsb); + } + if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got)) return 0; @@ -635,11 +653,21 @@ xfs_reflink_cancel_cow_blocks( bool cancel_real) { struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK); + struct xfs_mount *mp = ip->i_mount; struct xfs_bmbt_irec got, del; struct xfs_iext_cursor icur; bool isrt = XFS_IS_REALTIME_INODE(ip); int error = 0; + /* + * Shrink the range that we're cancelling if they don't align to the + * realtime extent size, since we can only free full extents. + */ + if (xfs_inode_needs_cow_around(ip)) { + offset_fsb = xfs_rtb_roundup_rtx(mp, offset_fsb); + end_fsb = xfs_rtb_rounddown_rtx(mp, end_fsb); + } + if (!xfs_inode_has_cow_data(ip)) return 0; if (!xfs_iext_lookup_extent_before(ip, ifp, &end_fsb, &icur, &got)) @@ -942,6 +970,7 @@ xfs_reflink_end_cow( xfs_off_t offset, xfs_off_t count) { + struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t offset_fsb; xfs_fileoff_t end_fsb; int error = 0; @@ -951,6 +980,16 @@ xfs_reflink_end_cow( offset_fsb = XFS_B_TO_FSBT(ip->i_mount, offset); end_fsb = XFS_B_TO_FSB(ip->i_mount, offset + count); + /* + * Make sure the end is aligned with a rt extent (if desired), since + * the end of the range could be EOF. The _convert_cow function should + * have set us up to swap only full rt extents. + */ + if (xfs_inode_needs_cow_around(ip)) { + offset_fsb = xfs_rtb_rounddown_rtx(mp, offset_fsb); + end_fsb = xfs_rtb_roundup_rtx(mp, end_fsb); + } + /* * Walk forwards until we've remapped the I/O range. The loop function * repeatedly cycles the ILOCK to allocate one transaction per remapped diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index d07947451ec9..d5b0dc3c5a0d 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -3888,6 +3888,7 @@ TRACE_EVENT(xfs_ioctl_clone, /* unshare tracepoints */ DEFINE_SIMPLE_IO_EVENT(xfs_reflink_unshare); +DEFINE_SIMPLE_IO_EVENT(xfs_file_cow_around); DEFINE_INODE_ERROR_EVENT(xfs_reflink_unshare_error); #ifdef CONFIG_XFS_RT DEFINE_SIMPLE_IO_EVENT(xfs_rtfile_convert_unwritten); From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085554 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DF833C4332F for ; Sat, 31 Dec 2022 02:00:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236118AbiLaCAD (ORCPT ); Fri, 30 Dec 2022 21:00:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51292 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236043AbiLaCAC (ORCPT ); Fri, 30 Dec 2022 21:00:02 -0500 Received: from sin.source.kernel.org (sin.source.kernel.org [IPv6:2604:1380:40e1:4800::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 657461C430 for ; Fri, 30 Dec 2022 18:00:01 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by sin.source.kernel.org (Postfix) with ESMTPS id B4AFBCE19E6 for ; Sat, 31 Dec 2022 01:59:59 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 04CC9C433EF; Sat, 31 Dec 2022 01:59:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672451998; bh=bdZVzcx7TM743pMqModRBCcFr8EA8SWIYJEWReq1mSM=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=aTlDfDsO8BxcFs2s2fJI5u79y5qzvGmR/zDDhx9NQYdZAp9pm6xxQQrb/EOMxXm64 SyqFUpSEJJbYfvQyXJoiSA+/cEGPkn8H8nfPKGsmYBm+epYi+7jdLQwvn8vVywQjKV DLFwJzuCTiE8G0A3BtqyXqL94hGG/AG1fFb2G6gaijaoivmI/SvdW++0H3sMJv60VI loiXDT+ksc/N3eXFGOuHukkuekOAAZ+NZ0XTkTvMjgWq9CdzSD3q/opC+4KpXIdh7E aEyLdEbAkPmk11ZoUMzKCNjwJPMc+Oy4x9ElN+Qj3XdGpEdxkSRD7o7k80U3dQgi3l 1mNlK10Cl+eoQ== Subject: [PATCH 4/9] xfs: forcibly convert unwritten blocks within an rt extent before sharing From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871861.718512.15989906047218636590.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong As noted in the previous patch, XFS can only unmap and map full rt extents. This means that we cannot stop mid-extent for any reason, including stepping around unwritten/written extents. Second, the reflink and CoW mechanisms were not designed to handle shared unwritten extents, so we have to do something to get rid of them. If the user asks us to remap two files, we must scan both ranges beforehand to convert any unwritten extents that are not aligned to rt extent boundaries into zeroed written extents before sharing. Signed-off-by: Darrick J. Wong --- fs/xfs/xfs_reflink.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 8690017beb9b..b9f47bdbe383 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1689,6 +1689,25 @@ xfs_reflink_remap_prep( if (ret) goto out_unlock; + /* + * Now that we've marked both inodes for reflink, make sure that all + * possible rt extents in both files' ranges are either wholly written, + * wholly unwritten, or holes. The bmap code requires that we align + * all unmap and remap requests to a rt extent boundary. We've already + * flushed the page cache and finished directio for the range that's + * being remapped, so we can convert the extents directly. + */ + if (xfs_inode_has_bigrtextents(src)) { + ret = xfs_rtfile_convert_unwritten(src, pos_in, *len); + if (ret) + goto out_unlock; + } + if (xfs_inode_has_bigrtextents(dest)) { + ret = xfs_rtfile_convert_unwritten(dest, pos_out, *len); + if (ret) + goto out_unlock; + } + /* * If pos_out > EOF, we may have dirtied blocks between EOF and * pos_out. In that case, we need to extend the flush and unmap to cover From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085555 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7C8B3C4332F for ; Sat, 31 Dec 2022 02:00:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236043AbiLaCAS (ORCPT ); Fri, 30 Dec 2022 21:00:18 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51308 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236120AbiLaCAQ (ORCPT ); Fri, 30 Dec 2022 21:00:16 -0500 Received: from ams.source.kernel.org (ams.source.kernel.org [145.40.68.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2E49C1C430 for ; Fri, 30 Dec 2022 18:00:16 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id D827AB81DF8 for ; Sat, 31 Dec 2022 02:00:14 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 97696C433EF; Sat, 31 Dec 2022 02:00:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672452013; bh=N7FfoUGu7e4T+0ipGOtxdhtzzvJEIL6t7LRRRrO4C0I=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=MQj/pw6gYC7JxakuZcpfh6Ym4DA9MpceqJpHl6Pe0sTv+Uh/27My7ulUd8bODvGXb 8Bq0zXx0y5rlFlwPD2ra+L7sx2L3T8uuoZ0Z67gJavmPZNYmPeIkNhHiR0QY1BAdg8 5KGrIAWu5Do1qB/5PgIcinzVVKaJtarNmZHmrY7QtTTqrW8XyjS4W1HkzUPqh7iNmE EoMaxu4kPyQt5podvbwr6EZ7IZkGxmpPqsw4C04qs9waOhQBPiTpRzU9hunAxZ+wAk x7c+9o+RKBVt+9+ux/whc00oCxZcP6xCaKnBKQGfghuhH1NcZAXcJtMX1oaxAbOnWC 6ySem9IaUpC6A== Subject: [PATCH 5/9] xfs: extend writeback requests to handle rt cow correctly From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871875.718512.10357363440477128738.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong If we have shared realtime files and the rt extent size is larger than a single fs block, we need to extend writeback requests to be aligned to rt extent size granularity because we cannot share partial rt extents. The front end should have set us up for this by dirtying the relevant ranges. Signed-off-by: Darrick J. Wong --- fs/xfs/xfs_aops.c | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index c3a9df0c0eab..af5c854a72dc 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -488,12 +488,41 @@ static const struct iomap_writeback_ops xfs_writeback_ops = { .discard_folio = xfs_discard_folio, }; +/* + * Extend the writeback range to allocation unit granularity and alignment. + * This is a requirement for blocksize > pagesize scenarios such as realtime + * copy on write, since we can only share full rt extents. + */ +static void +xfs_vm_writepage_extend( + struct xfs_inode *ip, + struct writeback_control *wbc) +{ + unsigned int bsize = xfs_inode_alloc_unitsize(ip); + long long int pages_to_write; + + wbc->range_start = rounddown_64(wbc->range_start, bsize); + if (wbc->range_end != LLONG_MAX) + wbc->range_end = roundup_64(wbc->range_end, bsize); + + if (wbc->nr_to_write == LONG_MAX) + return; + + pages_to_write = roundup_64(wbc->range_end - wbc->range_start, + PAGE_SIZE); + if (pages_to_write >= LONG_MAX) + pages_to_write = LONG_MAX; + if (wbc->nr_to_write < pages_to_write) + wbc->nr_to_write = pages_to_write; +} + STATIC int xfs_vm_writepages( - struct address_space *mapping, - struct writeback_control *wbc) + struct address_space *mapping, + struct writeback_control *wbc) { - struct xfs_writepage_ctx wpc = { }; + struct xfs_writepage_ctx wpc = { }; + struct xfs_inode *ip = XFS_I(mapping->host); /* * Writing back data in a transaction context can result in recursive @@ -502,7 +531,10 @@ xfs_vm_writepages( if (WARN_ON_ONCE(current->journal_info)) return 0; - xfs_iflags_clear(XFS_I(mapping->host), XFS_ITRUNCATED); + if (xfs_inode_needs_cow_around(ip)) + xfs_vm_writepage_extend(ip, wbc); + + xfs_iflags_clear(ip, XFS_ITRUNCATED); return iomap_writepages(mapping, wbc, &wpc.ctx, &xfs_writeback_ops); } From patchwork Fri Dec 30 22:18:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085556 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A2D6AC4332F for ; Sat, 31 Dec 2022 02:00:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236121AbiLaCAb (ORCPT ); Fri, 30 Dec 2022 21:00:31 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51324 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236120AbiLaCAa (ORCPT ); Fri, 30 Dec 2022 21:00:30 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2E6FE1C438 for ; Fri, 30 Dec 2022 18:00:30 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id BF78C61C5B for ; Sat, 31 Dec 2022 02:00:29 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 2602AC433EF; Sat, 31 Dec 2022 02:00:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672452029; bh=482r1WQSyvTSKfMN7OaT+cZrXg8z80qxIGmmX5w7K54=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=kxnGkmKy2NlA1xGGtgcpfMVfa8W9aCKbQlb0gkBWS0SkH1naUD8CdtrXoqj1EO5d6 2trnrtIlWkVroBqxG6ixtYw0EJbABkor2tm0pTNfxvPXAFZT6fdO4PNmgo6q2JuLZv 0Imor4AJKxnTTZZCQVrAtyBMQIUI9zF5jx+dgFP6P3U8wDzC4K/WP0w0ZZy+uGiIKw 8RiGgbnDmtSrPRtz+Kgb7rrS8ObfueXL/RxeEBTsaOiTZAH3svQG8k7S9uKE3JyNub WA7PRgUFdF/7j8FIi1y/ntpgVUfkqFHaJjkRWDgCQu3I0ZB4wjVpIHRsdvoM10gg7I fOPUzvp1G1BSQ== Subject: [PATCH 6/9] xfs: enable extent size hints for CoW when rtextsize > 1 From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:38 -0800 Message-ID: <167243871889.718512.5550194735087623208.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong CoW extent size hints are not allowed on filesystems that have large realtime extents because we only want to perform the minimum required amount of write-around (aka write amplification) for shared extents. On filesystems where rtextsize > 1, allocations can only be done in units of full rt extents, which means that we can only map an entire rt extent's worth of blocks into the data fork. Hole punch requests become conversions to unwritten if the request isn't aligned properly. Because a copy-write fundamentally requires remapping, this means that we also can only do copy-writes of a full rt extent. This is too expensive for large hint sizes, since it's all or nothing. Signed-off-by: Darrick J. Wong --- fs/xfs/libxfs/xfs_bmap.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index b2bc39b1f9b7..053d72063999 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -6451,6 +6451,28 @@ xfs_get_cowextsz_hint( if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) a = ip->i_cowextsize; if (XFS_IS_REALTIME_INODE(ip)) { + /* + * For realtime files, the realtime extent is the fundamental + * unit of allocation. This means that data sharing and CoW + * remapping can only be done in those units. For filesystems + * where the extent size is larger than one block, write + * requests that are not aligned to an extent boundary employ + * an unshare-around strategy to ensure that all pages for a + * shared extent are fully dirtied. + * + * Because the remapping alignment requirement applies equally + * to all CoW writes, any regular overwrites that could be + * turned (by a speculative CoW preallocation) into a CoW write + * must either employ this dirty-around strategy, or be smart + * enough to ignore the CoW fork mapping unless the entire + * extent is dirty or becomes shared by writeback time. Doing + * the first would dramatically increase write amplification, + * and the second would require deeper insight into the state + * of the page cache during a writeback request. For now, we + * ignore the hint. + */ + if (ip->i_mount->m_sb.sb_rextsize > 1) + return ip->i_mount->m_sb.sb_rextsize; b = 0; if (ip->i_diflags & XFS_DIFLAG_EXTSIZE) b = ip->i_extsize; From patchwork Fri Dec 30 22:18:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085557 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9A93CC4332F for ; Sat, 31 Dec 2022 02:00:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236130AbiLaCAu (ORCPT ); Fri, 30 Dec 2022 21:00:50 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51522 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236120AbiLaCAt (ORCPT ); Fri, 30 Dec 2022 21:00:49 -0500 Received: from ams.source.kernel.org (ams.source.kernel.org [145.40.68.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7B360C49 for ; Fri, 30 Dec 2022 18:00:47 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id 05BC4B81DF8 for ; Sat, 31 Dec 2022 02:00:46 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id A2EDCC433D2; Sat, 31 Dec 2022 02:00:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672452044; bh=HperrF5r7kGhMFDFJvPHR+nQrrhOglDa9T98tYWj+4k=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=DnxG4ZXa+gHUAmY4YTt2oFrlRT7baFIMn+7SWEPUr6LrEZ86w+pps7rDcNuu5G9tm 1pGMvYHmRimocEEqo45FcT6xElFv1gxfEulYrlSaiDgihZLs1P/yezbZFHsmQbGIKO sfup6BQhk7Tti9BrF25HKJJWgkGGJeWjd2lohEZ8uxylV3y2a7rPqOK4K5bEKud6yu 17pLU8MiTSBs7+vvQ3GyBPap7n+Xx2mOYuZAoZia4i1LxJQCcmT6/4atqjxM/mTcpI FvM3up4UNOTJ7+HGSWH1CTH9l5U3566SCIu2BBzjZdbDTvR70vojm7Oh6O3+NDbj25 TaNz8W6kLwdjQ== Subject: [PATCH 7/9] xfs: allow reflink on the rt volume when extent size is larger than 1 rt block From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:39 -0800 Message-ID: <167243871903.718512.17290898717615584521.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong Make the necessary tweaks to the reflink remapping code to support remapping on the realtime volume when the rt extent size is larger than a single rt block. We need to check that the remap arguments from userspace are aligned to a rt extent boundary, and that the length is always aligned, even if the kernel tried to round it up to EOF for us. XFS can only map and remap full rt extents, so we have to be a little more strict about the alignment there. Signed-off-by: Darrick J. Wong --- fs/xfs/xfs_reflink.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++---- fs/xfs/xfs_rtalloc.c | 2 + fs/xfs/xfs_super.c | 19 +++++++++--- fs/xfs/xfs_trace.h | 3 ++ 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index b9f47bdbe383..28fe946ecd08 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1530,6 +1530,13 @@ xfs_reflink_remap_blocks( len = min_t(xfs_filblks_t, XFS_B_TO_FSB(mp, remap_len), XFS_MAX_FILEOFF); + /* + * Make sure the end is aligned with a rt extent (if desired), since + * the end of the range could be EOF. + */ + if (xfs_inode_has_bigrtextents(dest)) + len = xfs_rtb_roundup_rtx(mp, len); + trace_xfs_reflink_remap_blocks(src, srcoff, len, dest, destoff); while (len > 0) { @@ -1603,6 +1610,54 @@ xfs_reflink_zero_posteof( return xfs_zero_range(ip, isize, pos - isize, NULL); } +#ifdef CONFIG_XFS_RT +/* Adjust the length of the remap operation to end on a rt extent boundary. */ +STATIC int +xfs_reflink_remap_adjust_rtlen( + struct xfs_inode *src, + loff_t pos_in, + struct xfs_inode *dest, + loff_t pos_out, + loff_t *len, + unsigned int remap_flags) +{ + struct xfs_mount *mp = src->i_mount; + uint32_t mod; + + div_u64_rem(*len, XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize), &mod); + + /* + * We previously checked the rtextent alignment of both offsets, so we + * now have to check the alignment of the length. The VFS remap prep + * function can change the length on us, so we can only make length + * adjustments after that. If the length is aligned to an rtextent, + * we're trivially good to go. + * + * Otherwise, the length is not aligned to an rt extent. If the source + * file's range ends at EOF, the VFS ensured that the dest file's range + * also ends at EOF. The actual remap function will round the (byte) + * length up to the nearest rtextent unit, so we're ok here too. + */ + if (mod == 0 || pos_in + *len == i_size_read(VFS_I(src))) + return 0; + + /* + * Otherwise, the only thing we can do is round the request length down + * to an rt extent boundary. If the caller doesn't allow that, we are + * finished. + */ + if (!(remap_flags & REMAP_FILE_CAN_SHORTEN)) + return -EINVAL; + + /* Back off by a single extent. */ + (*len) -= mod; + trace_xfs_reflink_remap_adjust_rtlen(src, pos_in, *len, dest, pos_out); + return 0; +} +#else +# define xfs_reflink_remap_adjust_rtlen(...) (0) +#endif /* CONFIG_XFS_RT */ + /* * Prepare two files for range cloning. Upon a successful return both inodes * will have the iolock and mmaplock held, the page cache of the out file will @@ -1645,6 +1700,7 @@ xfs_reflink_remap_prep( struct xfs_inode *src = XFS_I(inode_in); struct inode *inode_out = file_inode(file_out); struct xfs_inode *dest = XFS_I(inode_out); + const struct iomap_ops *dax_read_ops = NULL; int ret; /* Lock both files against IO */ @@ -1662,15 +1718,25 @@ xfs_reflink_remap_prep( if (IS_DAX(inode_in) != IS_DAX(inode_out)) goto out_unlock; - if (!IS_DAX(inode_in)) - ret = generic_remap_file_range_prep(file_in, pos_in, file_out, - pos_out, len, remap_flags); - else - ret = dax_remap_file_range_prep(file_in, pos_in, file_out, - pos_out, len, remap_flags, &xfs_read_iomap_ops); + ASSERT(is_power_of_2(xfs_inode_alloc_unitsize(dest))); + + if (IS_DAX(inode_in)) + dax_read_ops = &xfs_read_iomap_ops; + + ret = __generic_remap_file_range_prep(file_in, pos_in, file_out, + pos_out, len, remap_flags, dax_read_ops, + xfs_inode_alloc_unitsize(dest)); if (ret || *len == 0) goto out_unlock; + /* Make sure the end is aligned with a rt extent. */ + if (xfs_inode_has_bigrtextents(src)) { + ret = xfs_reflink_remap_adjust_rtlen(src, pos_in, dest, + pos_out, len, remap_flags); + if (ret || *len == 0) + goto out_unlock; + } + /* Attach dquots to dest inode before changing block map */ ret = xfs_qm_dqattach(dest); if (ret) diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 75d39c3274df..7c1edd5c2554 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -1312,7 +1312,7 @@ xfs_growfs_rt( return -EOPNOTSUPP; if (xfs_has_quota(mp)) return -EOPNOTSUPP; - if (xfs_has_reflink(mp) && in->extsize != 1) + if (xfs_has_reflink(mp) && !is_power_of_2(mp->m_sb.sb_rextsize)) return -EOPNOTSUPP; nrblocks = in->newblocks; diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index a3a0011272e5..31c1690ed847 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -1657,13 +1657,22 @@ xfs_fs_fill_super( if (xfs_has_reflink(mp)) { /* - * Reflink doesn't support rt extent sizes larger than a single - * block because we would have to perform unshare-around for - * rtext-unaligned write requests. + * Reflink doesn't support pagecache pages that span multiple + * realtime extents because iomap doesn't track subpage dirty + * state. This means that we cannot dirty all the pages + * backing an rt extent without dirtying the adjoining rt + * extents. If those rt extents are shared and extend into + * other pages, this leads to crazy write amplification. The + * VFS remap_range checks assume power-of-two block sizes. + * + * Hence we only support rt extent sizes that are an integer + * power of two because we know those will align with the page + * size. */ - if (xfs_has_realtime(mp) && mp->m_sb.sb_rextsize != 1) { + if (xfs_has_realtime(mp) && + !is_power_of_2(mp->m_sb.sb_rextsize)) { xfs_alert(mp, - "reflink not compatible with realtime extent size %u!", + "reflink not compatible with non-power-of-2 realtime extent size %u!", mp->m_sb.sb_rextsize); error = -EINVAL; goto out_filestream_unmount; diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index d5b0dc3c5a0d..00716f112f4e 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -3848,6 +3848,9 @@ TRACE_EVENT(xfs_reflink_remap_blocks, __entry->dest_lblk) ); DEFINE_DOUBLE_IO_EVENT(xfs_reflink_remap_range); +#ifdef CONFIG_XFS_RT +DEFINE_DOUBLE_IO_EVENT(xfs_reflink_remap_adjust_rtlen); +#endif /* CONFIG_XFS_RT */ DEFINE_INODE_ERROR_EVENT(xfs_reflink_remap_range_error); DEFINE_INODE_ERROR_EVENT(xfs_reflink_set_inode_flag_error); DEFINE_INODE_ERROR_EVENT(xfs_reflink_update_inode_size_error); From patchwork Fri Dec 30 22:18:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085558 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CDC6AC4332F for ; Sat, 31 Dec 2022 02:01:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236122AbiLaCBG (ORCPT ); Fri, 30 Dec 2022 21:01:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51550 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236120AbiLaCBE (ORCPT ); Fri, 30 Dec 2022 21:01:04 -0500 Received: from ams.source.kernel.org (ams.source.kernel.org [145.40.68.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CC3072AF8 for ; Fri, 30 Dec 2022 18:01:02 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id 8597CB81DF0 for ; Sat, 31 Dec 2022 02:01:01 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3AAC8C433D2; Sat, 31 Dec 2022 02:01:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672452060; bh=atRtfC095HroKonDjde/rb2/8U2I0OYnsP/Ppe6aebI=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=Urv0ulZRs+dlX2gNfhetNjyXr9wz8+4k2nz4NpPa3RhXsUi2acDZ4PBeN94NizryO MG6YkI8dNVOtv2MBV7LQVtaFN2QyvKGkBhjhfdjSs0kdniN7calK31iwzZoqF6HPz2 h5Sw10VVGMjkWmk34a2DlW0vsEiRon5oPKzH8CM45hEGEna/8QHqQ5Sm9qm8U9TjxJ 90CEbhwWjOOOJLE+jp1YRlkxV/hZTh4fGjf2VG0gsJfn/mBwGA9pXcz6htDfji32i0 cA6wVlcE2+E6cd0z5fAya69EWmOL22oO9joJNj9QX1MZjdGXZIGx0/jFCxxYN7Kqi3 oOkGP0yaULARQ== Subject: [PATCH 8/9] xfs: fix integer overflow when validating extent size hints From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:39 -0800 Message-ID: <167243871918.718512.6490928769487694300.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong Both file extent size hints are stored as 32-bit quantities, in units of filesystem blocks. As part of validating the hints, we convert these quantities to bytes to ensure that the hint is congruent with the file's allocation size. The maximum possible hint value is 2097151 (aka XFS_MAX_BMBT_EXTLEN). If the file allocation unit is larger than 2048, the unit conversion will exceed 32 bits in size, which overflows the uint32_t used to store the value used in the comparison. This isn't a problem for files on the data device since the hint will always be a multiple of the block size. However, this is a problem for realtime files because the rtextent size can be any integer number of fs blocks, and truncation of upper bits changes the outcome of division. Eliminate the overflow by performing the congruency check in units of blocks, not bytes. Otherwise, we get errors like this: $ truncate -s 500T /tmp/a $ mkfs.xfs -f -N /tmp/a -d extszinherit=2097151,rtinherit=1 -r extsize=28k illegal extent size hint 2097151, must be less than 2097151 and a multiple of 7. Signed-off-by: Darrick J. Wong --- fs/xfs/libxfs/xfs_inode_buf.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index 09dafa8a9ab2..6f2ae73559d1 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -740,13 +740,11 @@ xfs_inode_validate_extsize( bool rt_flag; bool hint_flag; bool inherit_flag; - uint32_t extsize_bytes; - uint32_t blocksize_bytes; + uint32_t alloc_unit = 1; rt_flag = (flags & XFS_DIFLAG_REALTIME); hint_flag = (flags & XFS_DIFLAG_EXTSIZE); inherit_flag = (flags & XFS_DIFLAG_EXTSZINHERIT); - extsize_bytes = XFS_FSB_TO_B(mp, extsize); /* * This comment describes a historic gap in this verifier function. @@ -775,9 +773,7 @@ xfs_inode_validate_extsize( */ if (rt_flag) - blocksize_bytes = XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize); - else - blocksize_bytes = mp->m_sb.sb_blocksize; + alloc_unit = mp->m_sb.sb_rextsize; if ((hint_flag || inherit_flag) && !(S_ISDIR(mode) || S_ISREG(mode))) return __this_address; @@ -795,7 +791,7 @@ xfs_inode_validate_extsize( if (mode && !(hint_flag || inherit_flag) && extsize != 0) return __this_address; - if (extsize_bytes % blocksize_bytes) + if (extsize % alloc_unit) return __this_address; if (extsize > XFS_MAX_BMBT_EXTLEN) @@ -830,12 +826,10 @@ xfs_inode_validate_cowextsize( { bool rt_flag; bool hint_flag; - uint32_t cowextsize_bytes; - uint32_t blocksize_bytes; + uint32_t alloc_unit = 1; rt_flag = (flags & XFS_DIFLAG_REALTIME); hint_flag = (flags2 & XFS_DIFLAG2_COWEXTSIZE); - cowextsize_bytes = XFS_FSB_TO_B(mp, cowextsize); /* * Similar to extent size hints, a directory can be configured to @@ -850,9 +844,7 @@ xfs_inode_validate_cowextsize( */ if (rt_flag) - blocksize_bytes = XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize); - else - blocksize_bytes = mp->m_sb.sb_blocksize; + alloc_unit = mp->m_sb.sb_rextsize; if (hint_flag && !xfs_has_reflink(mp)) return __this_address; @@ -867,7 +859,7 @@ xfs_inode_validate_cowextsize( if (mode && !hint_flag && cowextsize != 0) return __this_address; - if (cowextsize_bytes % blocksize_bytes) + if (cowextsize % alloc_unit) return __this_address; if (cowextsize > XFS_MAX_BMBT_EXTLEN) From patchwork Fri Dec 30 22:18:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 13085559 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 47CB4C4332F for ; Sat, 31 Dec 2022 02:01:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236134AbiLaCBU (ORCPT ); Fri, 30 Dec 2022 21:01:20 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51566 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236120AbiLaCBT (ORCPT ); Fri, 30 Dec 2022 21:01:19 -0500 Received: from ams.source.kernel.org (ams.source.kernel.org [IPv6:2604:1380:4601:e00::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 69B9C2AF8 for ; Fri, 30 Dec 2022 18:01:18 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id 1F2F1B81DED for ; Sat, 31 Dec 2022 02:01:17 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id C8D02C433D2; Sat, 31 Dec 2022 02:01:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1672452075; bh=6pqZwELMJP34cAJ4OwI73I/8KEUNmgqjziWNYXfCq3s=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=HFQZ+jL0JZ45CCwLHFLY/DzqNeoNTNIh4G78VwOrla45FO4PK3Q9ENqtywN1JB9x9 H+ADVHZCpC2ay0yZZc0j43fDvrsUBtljWm3Wqtu/Obq3acQsqNqs+hujCNPVvW7Zm1 FCqzgoLz1hsgodRUf8iUsYmw6C3fTLLtKvskcmLZVPbW8qm4Qf47n1HYTP0ahLUJ0S G4kzrJeTI86N+nzAKOduEAuOwxRdew6NRyQGaTme0fbRCC58UdZFFDwNzvHDCbx24q V7G8CAPa1+BRjtxlz2S2ydRkwMthA/Q12mX2RzqLhamQevg5f+URkTPoMMD2OmzPAe ZwB3hufmvJmRg== Subject: [PATCH 9/9] xfs: support realtime reflink with an extent size that isn't a power of 2 From: "Darrick J. Wong" To: djwong@kernel.org Cc: linux-xfs@vger.kernel.org Date: Fri, 30 Dec 2022 14:18:39 -0800 Message-ID: <167243871932.718512.16891050272429194261.stgit@magnolia> In-Reply-To: <167243871792.718512.13170681692847163098.stgit@magnolia> References: <167243871792.718512.13170681692847163098.stgit@magnolia> User-Agent: StGit/0.19 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org From: Darrick J. Wong Add the necessary alignment checking code to the reflink remap code to ensure that remap requests are aligned to rt extent boundaries if the realtime extent size isn't a power of two. The VFS helpers assume that they can use the usual (blocksize - 1) masking to avoid slow 64-bit division, but since XFS is special we won't make everyone pay that cost for our weird edge case. Signed-off-by: Darrick J. Wong --- fs/xfs/xfs_reflink.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++-- fs/xfs/xfs_rtalloc.c | 3 +- fs/xfs/xfs_super.c | 12 +++---- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 28fe946ecd08..1ec00204b33f 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1658,6 +1658,83 @@ xfs_reflink_remap_adjust_rtlen( # define xfs_reflink_remap_adjust_rtlen(...) (0) #endif /* CONFIG_XFS_RT */ +/* + * Check the alignment of a remap request when the allocation unit size isn't a + * power of two. The VFS helpers use (fast) bitmask-based alignment checks, + * but here we have to use slow long division. + */ +static int +xfs_reflink_remap_check_rtalign( + struct xfs_inode *ip_in, + loff_t pos_in, + struct xfs_inode *ip_out, + loff_t pos_out, + loff_t *req_len, + unsigned int remap_flags) +{ + struct xfs_mount *mp = ip_in->i_mount; + uint32_t rextbytes; + loff_t in_size, out_size; + loff_t new_length, length = *req_len; + loff_t blen; + + rextbytes = XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize); + in_size = i_size_read(VFS_I(ip_in)); + out_size = i_size_read(VFS_I(ip_out)); + + /* The start of both ranges must be aligned to a rt extent. */ + if (!isaligned_64(pos_in, rextbytes) || + !isaligned_64(pos_out, rextbytes)) + return -EINVAL; + + if (length == 0) + length = in_size - pos_in; + + /* + * If the user wanted us to exchange up to the infile's EOF, round up + * to the next block boundary for this check. + * + * Otherwise, reject the range length if it's not extent aligned. We + * already confirmed the starting offsets' extent alignment. + */ + if (pos_in + length == in_size) + blen = roundup_64(in_size, rextbytes) - pos_in; + else + blen = rounddown_64(length, rextbytes); + + /* Don't allow overlapped remappings within the same file. */ + if (ip_in == ip_out && + pos_out + blen > pos_in && + pos_in + blen > pos_out) + return -EINVAL; + + /* + * Ensure that we don't exchange a partial EOF extent into the middle + * of another file. + */ + if (isaligned_64(length, rextbytes)) + return 0; + + new_length = length; + if (pos_out + length < out_size) + new_length = rounddown_64(new_length, rextbytes); + + if (new_length == length) + return 0; + + /* + * Return the shortened request if the caller permits it. If the + * request was shortened to zero rt extents, we know that the original + * arguments weren't valid in the first place. + */ + if ((remap_flags & REMAP_FILE_CAN_SHORTEN) && new_length > 0) { + *req_len = new_length; + return 0; + } + + return (remap_flags & REMAP_FILE_DEDUP) ? -EBADE : -EINVAL; +} + /* * Prepare two files for range cloning. Upon a successful return both inodes * will have the iolock and mmaplock held, the page cache of the out file will @@ -1701,6 +1778,7 @@ xfs_reflink_remap_prep( struct inode *inode_out = file_inode(file_out); struct xfs_inode *dest = XFS_I(inode_out); const struct iomap_ops *dax_read_ops = NULL; + unsigned int alloc_unit = xfs_inode_alloc_unitsize(dest); int ret; /* Lock both files against IO */ @@ -1718,14 +1796,22 @@ xfs_reflink_remap_prep( if (IS_DAX(inode_in) != IS_DAX(inode_out)) goto out_unlock; - ASSERT(is_power_of_2(xfs_inode_alloc_unitsize(dest))); + /* Check non-power of two alignment issues, if necessary. */ + if (XFS_IS_REALTIME_INODE(dest) && !is_power_of_2(alloc_unit)) { + ret = xfs_reflink_remap_check_rtalign(src, pos_in, dest, + pos_out, len, remap_flags); + if (ret) + goto out_unlock; + + /* Do the VFS checks with the regular block alignment. */ + alloc_unit = src->i_mount->m_sb.sb_blocksize; + } if (IS_DAX(inode_in)) dax_read_ops = &xfs_read_iomap_ops; ret = __generic_remap_file_range_prep(file_in, pos_in, file_out, - pos_out, len, remap_flags, dax_read_ops, - xfs_inode_alloc_unitsize(dest)); + pos_out, len, remap_flags, dax_read_ops, alloc_unit); if (ret || *len == 0) goto out_unlock; diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 7c1edd5c2554..5e27cb7fce36 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -1312,7 +1312,8 @@ xfs_growfs_rt( return -EOPNOTSUPP; if (xfs_has_quota(mp)) return -EOPNOTSUPP; - if (xfs_has_reflink(mp) && !is_power_of_2(mp->m_sb.sb_rextsize)) + if (xfs_has_reflink(mp) && !is_power_of_2(mp->m_sb.sb_rextsize) && + (XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize) & ~PAGE_MASK)) return -EOPNOTSUPP; nrblocks = in->newblocks; diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 31c1690ed847..627fa40bbc5b 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -1662,17 +1662,17 @@ xfs_fs_fill_super( * state. This means that we cannot dirty all the pages * backing an rt extent without dirtying the adjoining rt * extents. If those rt extents are shared and extend into - * other pages, this leads to crazy write amplification. The - * VFS remap_range checks assume power-of-two block sizes. + * other pages, this leads to crazy write amplification. * * Hence we only support rt extent sizes that are an integer - * power of two because we know those will align with the page - * size. + * power of two or an integer multiple of the page size because + * we know those will align with the page size. */ if (xfs_has_realtime(mp) && - !is_power_of_2(mp->m_sb.sb_rextsize)) { + !is_power_of_2(mp->m_sb.sb_rextsize) && + (XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize) & ~PAGE_MASK)) { xfs_alert(mp, - "reflink not compatible with non-power-of-2 realtime extent size %u!", + "reflink not compatible with realtime extent size %u!", mp->m_sb.sb_rextsize); error = -EINVAL; goto out_filestream_unmount;