* [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink
@ 2023-12-31 18:25 Darrick J. Wong
2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong
` (9 more replies)
0 siblings, 10 replies; 48+ messages in thread
From: Darrick J. Wong @ 2023-12-31 18:25 UTC (permalink / raw)
To: xfs, fstests
Hi everyone,
This fourth patchriver has two faces -- one way of looking at it is that
it is random odds and ends at the tail of my development tree. A second
interpretation is that it is necessary pieces for defragmenting free
space, which is a precursor for online shrink of XFS filesystems.
The kernel side isn't that exciting -- we export refcounting information
for space extents, and add a new fallocate mode for mapping exact
portions of free filesystem space into a file.
Userspace is where things get interesting! The free space defragmenter
is an iterative algorithm that assigns free space to a dummy file, and
then uses the GETFSMAP and GETFSREFCOUNTS information to target file
space extents in order of decreasing share counts. Once an extent has
been targeted, it uses reflinking to freeze the space, copies it
elsewhere, and uses FIDEDUPERANGE to remap existing file data until the
dummy file is the sole owner of the targetted space. If metadata are
involved, the defrag utility invokes online repair to rebuild the
metadata somewhere else.
Since last year, I've also merged in Dave's xfs_spaceman code to move
inodes. I've not had much time to play with it otherwise.
When the defragmenter finishes, all the free space has been isolated to
the dummy file, which can be unlinked and closed if defragmentation was
the goal; or it could be passed to a shrinkfs operation.
--D
^ permalink raw reply [flat|nested] 48+ messages in thread* [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong @ 2023-12-31 19:38 ` Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/3] xfs: only free posteof blocks on first close Darrick J. Wong ` (2 more replies) 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong ` (8 subsequent siblings) 9 siblings, 3 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:38 UTC (permalink / raw) To: djwong; +Cc: Dave Chinner, linux-xfs Hi all, Here's a few patches mostly from Dave to make XFS more aggressive about keeping post-eof speculative preallocations when closing files. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=reduce-eofblocks-gc-on-close --- fs/xfs/xfs_bmap_util.c | 9 ++++++--- fs/xfs/xfs_file.c | 14 ++++++++++++-- fs/xfs/xfs_inode.c | 13 ++++++------- fs/xfs/xfs_inode.h | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/3] xfs: only free posteof blocks on first close 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong @ 2023-12-31 22:00 ` Darrick J. Wong 2023-12-31 22:00 ` [PATCH 2/3] xfs: don't free EOF blocks on read close Darrick J. Wong 2023-12-31 22:00 ` [PATCH 3/3] xfs: Don't free EOF blocks on close when extent size hints are set Darrick J. Wong 2 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:00 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Certain workloads fragment files on XFS very badly, such as a software package that creates a number of threads, each of which repeatedly run the sequence: open a file, perform a synchronous write, and close the file, which defeats the speculative preallocation mechanism. We work around this problem by only deleting posteof blocks the /first/ time a file is closed to preserve the behavior that unpacking a tarball lays out files one after the other with no gaps. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/xfs_inode.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 334f87f7a8f3f..dc0710661013f 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1207,9 +1207,7 @@ xfs_release( if (error) goto out_unlock; - /* delalloc blocks after truncation means it really is dirty */ - if (ip->i_delayed_blks) - xfs_iflags_set(ip, XFS_IDIRTY_RELEASE); + xfs_iflags_set(ip, XFS_IDIRTY_RELEASE); } out_unlock: ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/3] xfs: don't free EOF blocks on read close 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/3] xfs: only free posteof blocks on first close Darrick J. Wong @ 2023-12-31 22:00 ` Darrick J. Wong 2023-12-31 22:00 ` [PATCH 3/3] xfs: Don't free EOF blocks on close when extent size hints are set Darrick J. Wong 2 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:00 UTC (permalink / raw) To: djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <dchinner@redhat.com> When we have a workload that does open/read/close in parallel with other allocation, the file becomes rapidly fragmented. This is due to close() calling xfs_release() and removing the speculative preallocation beyond EOF. The existing open/*/close heuristic in xfs_release() does not catch this as a sync writer does not leave delayed allocation blocks allocated on the inode for later writeback that can be detected in xfs_release() and hence XFS_IDIRTY_RELEASE never gets set. In xfs_file_release(), we know more about the released file context, and so we need to communicate some of the details to xfs_release() so it can do the right thing here and skip EOF block truncation. This defers the EOF block cleanup for synchronous write contexts to the background EOF block cleaner which will clean up within a few minutes. Before: Test 1: sync write fragmentation counts /mnt/scratch/file.0: 919 /mnt/scratch/file.1: 916 /mnt/scratch/file.2: 919 /mnt/scratch/file.3: 920 /mnt/scratch/file.4: 920 /mnt/scratch/file.5: 921 /mnt/scratch/file.6: 916 /mnt/scratch/file.7: 918 After: Test 1: sync write fragmentation counts /mnt/scratch/file.0: 24 /mnt/scratch/file.1: 24 /mnt/scratch/file.2: 11 /mnt/scratch/file.3: 24 /mnt/scratch/file.4: 3 /mnt/scratch/file.5: 24 /mnt/scratch/file.6: 24 /mnt/scratch/file.7: 23 Signed-off-by: Dave Chinner <dchinner@redhat.com> [darrick: wordsmithing, fix commit message] Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/xfs_file.c | 14 ++++++++++++-- fs/xfs/xfs_inode.c | 9 +++++---- fs/xfs/xfs_inode.h | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index ebdda286cb2a2..f2dd4daaa4e24 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1367,12 +1367,22 @@ xfs_dir_open( return error; } +/* + * When we release the file, we don't want it to trim EOF blocks if it is a + * readonly context. This avoids open/read/close workloads from removing + * EOF blocks that other writers depend upon to reduce fragmentation. + */ STATIC int xfs_file_release( struct inode *inode, - struct file *filp) + struct file *file) { - return xfs_release(XFS_I(inode)); + bool free_eof_blocks = true; + + if ((file->f_mode & (FMODE_WRITE | FMODE_READ)) == FMODE_READ) + free_eof_blocks = false; + + return xfs_release(XFS_I(inode), free_eof_blocks); } STATIC int diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index dc0710661013f..3408804bee9b2 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1137,10 +1137,11 @@ xfs_itruncate_extents_flags( int xfs_release( - xfs_inode_t *ip) + struct xfs_inode *ip, + bool want_free_eofblocks) { - xfs_mount_t *mp = ip->i_mount; - int error = 0; + struct xfs_mount *mp = ip->i_mount; + int error = 0; if (!S_ISREG(VFS_I(ip)->i_mode) || (VFS_I(ip)->i_mode == 0)) return 0; @@ -1182,7 +1183,7 @@ xfs_release( * another chance to drop them once the last reference to the inode is * dropped, so we'll never leak blocks permanently. */ - if (!xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) + if (!want_free_eofblocks || !xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) return 0; if (xfs_can_free_eofblocks(ip, false)) { diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index df8197fe4cb82..2779a353b4618 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -525,7 +525,7 @@ enum layout_break_reason { #define XFS_INHERIT_GID(pip) \ (xfs_has_grpid((pip)->i_mount) || (VFS_I(pip)->i_mode & S_ISGID)) -int xfs_release(struct xfs_inode *ip); +int xfs_release(struct xfs_inode *ip, bool want_free_eofblocks); int xfs_inactive(struct xfs_inode *ip); int xfs_lookup(struct xfs_inode *dp, const struct xfs_name *name, struct xfs_inode **ipp, struct xfs_name *ci_name); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 3/3] xfs: Don't free EOF blocks on close when extent size hints are set 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/3] xfs: only free posteof blocks on first close Darrick J. Wong 2023-12-31 22:00 ` [PATCH 2/3] xfs: don't free EOF blocks on read close Darrick J. Wong @ 2023-12-31 22:00 ` Darrick J. Wong 2 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:00 UTC (permalink / raw) To: djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <david@fromorbit.com> When we have a workload that does open/write/close on files with extent size hints set in parallel with other allocation, the file becomes rapidly fragmented. This is due to close() calling xfs_release() and removing the preallocated extent beyond EOF. This occurs for both buffered and direct writes that append to files with extent size hints. The existing open/write/close hueristic in xfs_release() does not catch this as writes to files using extent size hints do not use delayed allocation and hence do not leave delayed allocation blocks allocated on the inode that can be detected in xfs_release(). Hence XFS_IDIRTY_RELEASE never gets set. In xfs_file_release(), we can tell whether the inode has extent size hints set and skip EOF block truncation. We add this check to xfs_can_free_eofblocks() so that we treat the post-EOF preallocated extent like intentional preallocation and so are persistent unless directly removed by userspace. Before: Test 2: Extent size hint fragmentation counts /mnt/scratch/file.0: 1002 /mnt/scratch/file.1: 1002 /mnt/scratch/file.2: 1002 /mnt/scratch/file.3: 1002 /mnt/scratch/file.4: 1002 /mnt/scratch/file.5: 1002 /mnt/scratch/file.6: 1002 /mnt/scratch/file.7: 1002 After: Test 2: Extent size hint fragmentation counts /mnt/scratch/file.0: 4 /mnt/scratch/file.1: 4 /mnt/scratch/file.2: 4 /mnt/scratch/file.3: 4 /mnt/scratch/file.4: 4 /mnt/scratch/file.5: 4 /mnt/scratch/file.6: 4 /mnt/scratch/file.7: 4 Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/xfs_bmap_util.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 60992f8e86adf..a3b987fafcce6 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -710,12 +710,15 @@ xfs_can_free_eofblocks( return false; /* - * Do not free real preallocated or append-only files unless the file - * has delalloc blocks and we are forced to remove them. + * Do not free extent size hints, real preallocated or append-only files + * unless the file has delalloc blocks and we are forced to remove + * them. */ - if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) + if (xfs_get_extsz_hint(ip) || + (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND))) { if (!force || ip->i_delayed_blks == 0) return false; + } /* * Do not try to free post-EOF blocks if EOF is beyond the end of the ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET RFC 2/5] xfs: noalloc allocation groups 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong @ 2023-12-31 19:38 ` Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong ` (4 more replies) 2023-12-31 19:39 ` [PATCHSET 3/5] xfs: report refcount information to userspace Darrick J. Wong ` (7 subsequent siblings) 9 siblings, 5 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:38 UTC (permalink / raw) To: djwong; +Cc: linux-xfs Hi all, This series creates a new NOALLOC flag for allocation groups that causes the block and inode allocators to look elsewhere when trying to allocate resources. This is either the first part of a patchset to implement online shrinking (set noalloc on the last AGs, run fsr to move the files and directories) or freeze-free rmapbt rebuilding (set noalloc to prevent creation of new mappings, then hook deletion of old mappings). This is still totally a research project. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=noalloc-ags xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=noalloc-ags --- fs/xfs/Kconfig | 13 +++++ fs/xfs/libxfs/xfs_ag.c | 114 +++++++++++++++++++++++++++++++++++++++++++ fs/xfs/libxfs/xfs_ag.h | 8 +++ fs/xfs/libxfs/xfs_ag_resv.c | 27 +++++++++- fs/xfs/libxfs/xfs_defer.c | 14 +++++ fs/xfs/libxfs/xfs_fs.h | 5 ++ fs/xfs/libxfs/xfs_ialloc.c | 3 + fs/xfs/scrub/btree.c | 88 +++++++++++++++++++++++++++++++++ fs/xfs/scrub/common.c | 107 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/common.h | 1 fs/xfs/scrub/dabtree.c | 24 +++++++++ fs/xfs/scrub/fscounters.c | 3 + fs/xfs/scrub/inode.c | 4 ++ fs/xfs/scrub/scrub.c | 40 +++++++++++++++ fs/xfs/scrub/trace.c | 22 ++++++++ fs/xfs/scrub/trace.h | 2 + fs/xfs/xfs_fsops.c | 10 +++- fs/xfs/xfs_globals.c | 5 ++ fs/xfs/xfs_ioctl.c | 4 +- fs/xfs/xfs_super.c | 1 fs/xfs/xfs_sysctl.h | 1 fs/xfs/xfs_sysfs.c | 32 ++++++++++++ fs/xfs/xfs_trace.h | 65 +++++++++++++++++++++++++ fs/xfs/xfs_trans.c | 3 + fs/xfs/xfs_trans.h | 7 +++ 25 files changed, 597 insertions(+), 6 deletions(-) ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/5] xfs: track deferred ops statistics 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong @ 2023-12-31 22:00 ` Darrick J. Wong 2023-12-31 22:01 ` [PATCH 2/5] xfs: whine to dmesg when we encounter errors Darrick J. Wong ` (3 subsequent siblings) 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:00 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Track some basic statistics on how hard we're pushing the defer ops. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_defer.c | 14 ++++++++++++++ fs/xfs/xfs_trace.h | 19 +++++++++++++++++++ fs/xfs/xfs_trans.c | 3 +++ fs/xfs/xfs_trans.h | 7 +++++++ 4 files changed, 43 insertions(+) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index cd28b96b49ea9..bf83508edf822 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -617,6 +617,8 @@ xfs_defer_finish_one( /* Done with the dfp, free it. */ list_del(&dfp->dfp_list); kmem_cache_free(xfs_defer_pending_cache, dfp); + tp->t_dfops_nr--; + tp->t_dfops_finished++; out: if (ops->finish_cleanup) ops->finish_cleanup(tp, state, error); @@ -679,6 +681,9 @@ xfs_defer_finish_noroll( list_splice_init(&(*tp)->t_dfops, &dop_pending); + (*tp)->t_dfops_nr_max = max((*tp)->t_dfops_nr, + (*tp)->t_dfops_nr_max); + if (has_intents < 0) { error = has_intents; goto out_shutdown; @@ -720,6 +725,7 @@ xfs_defer_finish_noroll( xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); trace_xfs_defer_finish_error(*tp, error); xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending); + (*tp)->t_dfops_nr = 0; xfs_defer_cancel(*tp); return error; } @@ -767,6 +773,7 @@ xfs_defer_cancel( trace_xfs_defer_cancel(tp, _RET_IP_); xfs_defer_trans_abort(tp, &tp->t_dfops); xfs_defer_cancel_list(mp, &tp->t_dfops); + tp->t_dfops_nr = 0; } /* @@ -830,6 +837,7 @@ xfs_defer_alloc( dfp->dfp_ops = ops; INIT_LIST_HEAD(&dfp->dfp_work); list_add_tail(&dfp->dfp_list, &tp->t_dfops); + tp->t_dfops_nr++; return dfp; } @@ -942,6 +950,12 @@ xfs_defer_move( struct xfs_trans *stp) { list_splice_init(&stp->t_dfops, &dtp->t_dfops); + dtp->t_dfops_nr += stp->t_dfops_nr; + dtp->t_dfops_nr_max = stp->t_dfops_nr_max; + dtp->t_dfops_finished = stp->t_dfops_finished; + stp->t_dfops_nr = 0; + stp->t_dfops_nr_max = 0; + stp->t_dfops_finished = 0; /* * Low free space mode was historically controlled by a dfops field. diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 906e35eef223d..6c99bf56184b0 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -2711,6 +2711,25 @@ TRACE_EVENT(xfs_btree_free_block, /* deferred ops */ struct xfs_defer_pending; +TRACE_EVENT(xfs_defer_stats, + TP_PROTO(struct xfs_trans *tp), + TP_ARGS(tp), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(unsigned int, max) + __field(unsigned int, finished) + ), + TP_fast_assign( + __entry->dev = tp->t_mountp->m_super->s_dev; + __entry->max = tp->t_dfops_nr_max; + __entry->finished = tp->t_dfops_finished; + ), + TP_printk("dev %d:%d max %u finished %u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->max, + __entry->finished) +) + DECLARE_EVENT_CLASS(xfs_defer_class, TP_PROTO(struct xfs_trans *tp, unsigned long caller_ip), TP_ARGS(tp, caller_ip), diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 008380482777b..eb7d4272aef28 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -70,6 +70,9 @@ xfs_trans_free( xfs_extent_busy_sort(&tp->t_busy); xfs_extent_busy_clear(tp->t_mountp, &tp->t_busy, false); + if (tp->t_dfops_finished > 0) + trace_xfs_defer_stats(tp); + trace_xfs_trans_free(tp, _RET_IP_); xfs_trans_clear_context(tp); if (!(tp->t_flags & XFS_TRANS_NO_WRITECOUNT)) diff --git a/fs/xfs/xfs_trans.h b/fs/xfs/xfs_trans.h index 2046ee06fe88f..6017efe354adc 100644 --- a/fs/xfs/xfs_trans.h +++ b/fs/xfs/xfs_trans.h @@ -151,6 +151,13 @@ typedef struct xfs_trans { struct list_head t_busy; /* list of busy extents */ struct list_head t_dfops; /* deferred operations */ unsigned long t_pflags; /* saved process flags state */ + + /* Count of deferred ops attached to transaction. */ + unsigned int t_dfops_nr; + /* Maximum t_dfops_nr seen in a loop. */ + unsigned int t_dfops_nr_max; + /* Number of dfops finished. */ + unsigned int t_dfops_finished; } xfs_trans_t; /* ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/5] xfs: whine to dmesg when we encounter errors 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong @ 2023-12-31 22:01 ` Darrick J. Wong 2023-12-31 22:01 ` [PATCH 3/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong ` (2 subsequent siblings) 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:01 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Forward everything scrub whines about to dmesg. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/Kconfig | 13 ++++++ fs/xfs/scrub/btree.c | 88 +++++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/common.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/common.h | 1 fs/xfs/scrub/dabtree.c | 24 +++++++++++ fs/xfs/scrub/inode.c | 4 ++ fs/xfs/scrub/scrub.c | 40 ++++++++++++++++++ fs/xfs/scrub/trace.c | 22 ++++++++++ fs/xfs/scrub/trace.h | 2 + fs/xfs/xfs_globals.c | 5 ++ fs/xfs/xfs_sysctl.h | 1 fs/xfs/xfs_sysfs.c | 32 ++++++++++++++ 12 files changed, 339 insertions(+) diff --git a/fs/xfs/Kconfig b/fs/xfs/Kconfig index 0ed89b2381936..be17dbeb0eb43 100644 --- a/fs/xfs/Kconfig +++ b/fs/xfs/Kconfig @@ -172,6 +172,19 @@ config XFS_ONLINE_SCRUB_STATS If unsure, say N. +config XFS_ONLINE_SCRUB_WHINE + bool "XFS online metadata verbose logging by default" + default y + depends on XFS_ONLINE_SCRUB + help + If you say Y here, the kernel will by default log the outcomes of all + scrub and repair operations, as well as any corruptions found. This + may slow down scrub due to printk logging overhead timers. + + This value can be changed by editing /sys/fs/xfs/debug/scrub_whine + + If unsure, say N. + choice prompt "XFS hook implementation" depends on XFS_FS && XFS_LIVE_HOOKS && XFS_ONLINE_SCRUB diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c index 1935b9ce1885c..e1b22ac074d34 100644 --- a/fs/xfs/scrub/btree.c +++ b/fs/xfs/scrub/btree.c @@ -11,6 +11,8 @@ #include "xfs_mount.h" #include "xfs_inode.h" #include "xfs_btree.h" +#include "xfs_log_format.h" +#include "xfs_ag.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/btree.h" @@ -18,6 +20,62 @@ /* btree scrubbing */ +/* Figure out which block the btree cursor was pointing to. */ +static inline xfs_fsblock_t +xchk_btree_cur_fsbno( + struct xfs_btree_cur *cur, + int level) +{ + if (level < cur->bc_nlevels && cur->bc_levels[level].bp) + return XFS_DADDR_TO_FSB(cur->bc_mp, + xfs_buf_daddr(cur->bc_levels[level].bp)); + else if (level == cur->bc_nlevels - 1 && + (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE)) + return XFS_INO_TO_FSB(cur->bc_mp, cur->bc_ino.ip->i_ino); + else if (!(cur->bc_flags & XFS_BTREE_LONG_PTRS)) + return XFS_AGB_TO_FSB(cur->bc_mp, cur->bc_ag.pag->pag_agno, 0); + return NULLFSBLOCK; +} + +static inline void +process_error_whine( + struct xfs_scrub *sc, + struct xfs_btree_cur *cur, + int level, + int *error, + __u32 errflag, + void *ret_ip) +{ + xfs_fsblock_t fsbno = xchk_btree_cur_fsbno(cur, level); + + if (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) { + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s btnum %d level %d ptr %d agno 0x%x agbno 0x%x error %d errflag 0x%x ret_ip %pS", + cur->bc_ino.ip->i_ino, + cur->bc_ino.whichfork, + xchk_type_string(sc->sm->sm_type), + cur->bc_btnum, + level, + cur->bc_levels[level].ptr, + XFS_FSB_TO_AGNO(cur->bc_mp, fsbno), + XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno), + *error, + errflag, + ret_ip); + return; + } + + xchk_whine(sc->mp, "type %s btnum %d level %d ptr %d agno 0x%x agbno 0x%x error %d errflag 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + cur->bc_btnum, + level, + cur->bc_levels[level].ptr, + XFS_FSB_TO_AGNO(cur->bc_mp, fsbno), + XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno), + *error, + errflag, + ret_ip); +} + /* * Check for btree operation errors. See the section about handling * operational errors in common.c. @@ -44,9 +102,13 @@ __xchk_btree_process_error( case -EFSCORRUPTED: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + process_error_whine(sc, cur, level, error, errflag, ret_ip); *error = 0; fallthrough; default: + if (*error) + process_error_whine(sc, cur, level, error, errflag, + ret_ip); if (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) trace_xchk_ifork_btree_op_error(sc, cur, level, *error, ret_ip); @@ -92,11 +154,37 @@ __xchk_btree_set_corrupt( sc->sm->sm_flags |= errflag; if (cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) + { + xfs_fsblock_t fsbno = xchk_btree_cur_fsbno(cur, level); + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s btnum %d level %d ptr %d agno 0x%x agbno 0x%x errflag 0x%x ret_ip %pS", + cur->bc_ino.ip->i_ino, + cur->bc_ino.whichfork, + xchk_type_string(sc->sm->sm_type), + cur->bc_btnum, + level, + cur->bc_levels[level].ptr, + XFS_FSB_TO_AGNO(cur->bc_mp, fsbno), + XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno), + errflag, + ret_ip); trace_xchk_ifork_btree_error(sc, cur, level, ret_ip); + } else + { + xfs_fsblock_t fsbno = xchk_btree_cur_fsbno(cur, level); + xchk_whine(sc->mp, "type %s btnum %d level %d ptr %d agno 0x%x agbno 0x%x errflag 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + cur->bc_btnum, + level, + cur->bc_levels[level].ptr, + XFS_FSB_TO_AGNO(cur->bc_mp, fsbno), + XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno), + errflag, + ret_ip); trace_xchk_btree_error(sc, cur, level, ret_ip); + } } void diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c index a9801a5bb0383..2a9741235d310 100644 --- a/fs/xfs/scrub/common.c +++ b/fs/xfs/scrub/common.c @@ -105,9 +105,23 @@ __xchk_process_error( case -EFSCORRUPTED: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + xchk_whine(sc->mp, "type %s agno 0x%x agbno 0x%x error %d errflag 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + agno, + bno, + *error, + errflag, + ret_ip); *error = 0; fallthrough; default: + if (*error) + xchk_whine(sc->mp, "type %s agno 0x%x agbno 0x%x error %d ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + agno, + bno, + *error, + ret_ip); trace_xchk_op_error(sc, agno, bno, *error, ret_ip); break; } @@ -190,9 +204,25 @@ __xchk_fblock_process_error( case -EFSCORRUPTED: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s offset %llu error %d errflag 0x%x ret_ip %pS", + sc->ip->i_ino, + whichfork, + xchk_type_string(sc->sm->sm_type), + offset, + *error, + errflag, + ret_ip); *error = 0; fallthrough; default: + if (*error) + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s offset %llu error %d ret_ip %pS", + sc->ip->i_ino, + whichfork, + xchk_type_string(sc->sm->sm_type), + offset, + *error, + ret_ip); trace_xchk_file_op_error(sc, whichfork, offset, *error, ret_ip); break; @@ -264,6 +294,8 @@ xchk_set_corrupt( struct xfs_scrub *sc) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "type %s ret_ip %pS", xchk_type_string(sc->sm->sm_type), + __return_address); trace_xchk_fs_error(sc, 0, __return_address); } @@ -275,6 +307,11 @@ xchk_block_set_corrupt( { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; trace_xchk_block_error(sc, xfs_buf_daddr(bp), __return_address); + xchk_whine(sc->mp, "type %s agno 0x%x agbno 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + xfs_daddr_to_agno(sc->mp, xfs_buf_daddr(bp)), + xfs_daddr_to_agbno(sc->mp, xfs_buf_daddr(bp)), + __return_address); } #ifdef CONFIG_XFS_QUOTA @@ -286,6 +323,8 @@ xchk_qcheck_set_corrupt( xfs_dqid_t id) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "type %s dqtype %u id %u ret_ip %pS", + xchk_type_string(sc->sm->sm_type), dqtype, id, __return_address); trace_xchk_qcheck_error(sc, dqtype, id, __return_address); } #endif /* CONFIG_XFS_QUOTA */ @@ -298,6 +337,11 @@ xchk_block_xref_set_corrupt( { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_XCORRUPT; trace_xchk_block_error(sc, xfs_buf_daddr(bp), __return_address); + xchk_whine(sc->mp, "type %s agno 0x%x agbno 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + xfs_daddr_to_agno(sc->mp, xfs_buf_daddr(bp)), + xfs_daddr_to_agbno(sc->mp, xfs_buf_daddr(bp)), + __return_address); } /* @@ -311,6 +355,8 @@ xchk_ino_set_corrupt( xfs_ino_t ino) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "ino 0x%llx type %s ret_ip %pS", + ino, xchk_type_string(sc->sm->sm_type), __return_address); trace_xchk_ino_error(sc, ino, __return_address); } @@ -321,6 +367,8 @@ xchk_ino_xref_set_corrupt( xfs_ino_t ino) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_XCORRUPT; + xchk_whine(sc->mp, "ino 0x%llx type %s ret_ip %pS", + ino, xchk_type_string(sc->sm->sm_type), __return_address); trace_xchk_ino_error(sc, ino, __return_address); } @@ -332,6 +380,12 @@ xchk_fblock_set_corrupt( xfs_fileoff_t offset) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s offset %llu ret_ip %pS", + sc->ip->i_ino, + whichfork, + xchk_type_string(sc->sm->sm_type), + offset, + __return_address); trace_xchk_fblock_error(sc, whichfork, offset, __return_address); } @@ -343,6 +397,12 @@ xchk_fblock_xref_set_corrupt( xfs_fileoff_t offset) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_XCORRUPT; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s offset %llu ret_ip %pS", + sc->ip->i_ino, + whichfork, + xchk_type_string(sc->sm->sm_type), + offset, + __return_address); trace_xchk_fblock_error(sc, whichfork, offset, __return_address); } @@ -356,6 +416,8 @@ xchk_ino_set_warning( xfs_ino_t ino) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_WARNING; + xchk_whine(sc->mp, "ino 0x%llx type %s ret_ip %pS", + ino, xchk_type_string(sc->sm->sm_type), __return_address); trace_xchk_ino_warning(sc, ino, __return_address); } @@ -367,6 +429,12 @@ xchk_fblock_set_warning( xfs_fileoff_t offset) { sc->sm->sm_flags |= XFS_SCRUB_OFLAG_WARNING; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s offset %llu ret_ip %pS", + sc->ip->i_ino, + whichfork, + xchk_type_string(sc->sm->sm_type), + offset, + __return_address); trace_xchk_fblock_warning(sc, whichfork, offset, __return_address); } @@ -1312,6 +1380,10 @@ xchk_iget_for_scrubbing( out_cancel: xchk_trans_cancel(sc); out_error: + xchk_whine(mp, "type %s agno 0x%x agbno 0x%x error %d ret_ip %pS", + xchk_type_string(sc->sm->sm_type), agno, + XFS_INO_TO_AGBNO(mp, sc->sm->sm_ino), error, + __return_address); trace_xchk_op_error(sc, agno, XFS_INO_TO_AGBNO(mp, sc->sm->sm_ino), error, __return_address); return error; @@ -1453,6 +1525,10 @@ xchk_should_check_xref( } sc->sm->sm_flags |= XFS_SCRUB_OFLAG_XFAIL; + xchk_whine(sc->mp, "type %s xref error %d ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + *error, + __return_address); trace_xchk_xref_error(sc, *error, __return_address); /* @@ -1484,6 +1560,11 @@ xchk_buffer_recheck( return; sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; trace_xchk_block_error(sc, xfs_buf_daddr(bp), fa); + xchk_whine(sc->mp, "type %s agno 0x%x agbno 0x%x ret_ip %pS", + xchk_type_string(sc->sm->sm_type), + xfs_daddr_to_agno(sc->mp, xfs_buf_daddr(bp)), + xfs_daddr_to_agbno(sc->mp, xfs_buf_daddr(bp)), + fa); } static inline int @@ -1793,3 +1874,29 @@ xchk_inode_count_blocks( *count = btblocks - 1; return 0; } + +/* Complain about failures... */ +void +xchk_whine( + const struct xfs_mount *mp, + const char *fmt, + ...) +{ + struct va_format vaf; + va_list args; + + if (!xfs_globals.scrub_whine) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_INFO "XFS (%s) %pS: %pV\n", mp->m_super->s_id, + __return_address, &vaf); + va_end(args); + + if (xfs_error_level >= XFS_ERRLEVEL_HIGH) + xfs_stack_trace(); +} diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index ed22e1403d0f0..53f4de067369a 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -188,6 +188,7 @@ bool xchk_ilock_nowait(struct xfs_scrub *sc, unsigned int ilock_flags); void xchk_iunlock(struct xfs_scrub *sc, unsigned int ilock_flags); void xchk_buffer_recheck(struct xfs_scrub *sc, struct xfs_buf *bp); +void xchk_whine(const struct xfs_mount *mp, const char *fmt, ...); /* * Grab the inode at @inum. The caller must have created a scrub transaction diff --git a/fs/xfs/scrub/dabtree.c b/fs/xfs/scrub/dabtree.c index 056de4819f866..ae64db9f0bba2 100644 --- a/fs/xfs/scrub/dabtree.c +++ b/fs/xfs/scrub/dabtree.c @@ -47,9 +47,26 @@ xchk_da_process_error( case -EFSCORRUPTED: /* Note the badness but don't abort. */ sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s dablk 0x%llx error %d ret_ip %pS", + sc->ip->i_ino, + ds->dargs.whichfork, + xchk_type_string(sc->sm->sm_type), + xfs_dir2_da_to_db(ds->dargs.geo, + ds->state->path.blk[level].blkno), + *error, + __return_address); *error = 0; fallthrough; default: + if (*error) + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s dablk 0x%llx error %d ret_ip %pS", + sc->ip->i_ino, + ds->dargs.whichfork, + xchk_type_string(sc->sm->sm_type), + xfs_dir2_da_to_db(ds->dargs.geo, + ds->state->path.blk[level].blkno), + *error, + __return_address); trace_xchk_file_op_error(sc, ds->dargs.whichfork, xfs_dir2_da_to_db(ds->dargs.geo, ds->state->path.blk[level].blkno), @@ -72,6 +89,13 @@ xchk_da_set_corrupt( sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + xchk_whine(sc->mp, "ino 0x%llx fork %d type %s dablk 0x%llx ret_ip %pS", + sc->ip->i_ino, + ds->dargs.whichfork, + xchk_type_string(sc->sm->sm_type), + xfs_dir2_da_to_db(ds->dargs.geo, + ds->state->path.blk[level].blkno), + __return_address); trace_xchk_fblock_error(sc, ds->dargs.whichfork, xfs_dir2_da_to_db(ds->dargs.geo, ds->state->path.blk[level].blkno), diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index e52e12e9a1b4b..cb2530a93a001 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -217,6 +217,10 @@ xchk_setup_inode( out_cancel: xchk_trans_cancel(sc); out_error: + xchk_whine(mp, "type %s agno 0x%x agbno 0x%x error %d ret_ip %pS", + xchk_type_string(sc->sm->sm_type), agno, + XFS_INO_TO_AGBNO(mp, sc->sm->sm_ino), error, + __return_address); trace_xchk_op_error(sc, agno, XFS_INO_TO_AGBNO(mp, sc->sm->sm_ino), error, __return_address); return error; diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 94a733975879a..1a098a8913925 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -632,6 +632,45 @@ xchk_scrub_create_subord( return sub; } +static inline void +repair_outcomes(struct xfs_scrub *sc, int error) +{ + struct xfs_scrub_metadata *sm = sc->sm; + const char *wut = NULL; + + if (!xfs_globals.scrub_whine) + return; + + if (sc->flags & XREP_ALREADY_FIXED) { + wut = "*** REPAIR SUCCESS"; + error = 0; + } else if (error == -EBUSY) { + wut = "??? FILESYSTEM BUSY"; + } else if (error == -EAGAIN) { + wut = "??? REPAIR DEFERRED"; + } else if (error == -ECANCELED) { + wut = "??? REPAIR CANCELLED"; + } else if (error == -EINTR) { + wut = "??? REPAIR INTERRUPTED"; + } else if (error != -EOPNOTSUPP && error != -ENOENT) { + wut = "!!! REPAIR FAILED"; + xfs_info(sc->mp, +"%s ino 0x%llx type %s agno 0x%x inum 0x%llx gen 0x%x flags 0x%x error %d", + wut, XFS_I(file_inode(sc->file))->i_ino, + xchk_type_string(sm->sm_type), sm->sm_agno, + sm->sm_ino, sm->sm_gen, sm->sm_flags, error); + return; + } else { + return; + } + + xfs_info_ratelimited(sc->mp, +"%s ino 0x%llx type %s agno 0x%x inum 0x%llx gen 0x%x flags 0x%x error %d", + wut, XFS_I(file_inode(sc->file))->i_ino, + xchk_type_string(sm->sm_type), sm->sm_agno, sm->sm_ino, + sm->sm_gen, sm->sm_flags, error); +} + /* Dispatch metadata scrubbing. */ int xfs_scrub_metadata( @@ -729,6 +768,7 @@ xfs_scrub_metadata( * already tried to fix it, then attempt a repair. */ error = xrep_attempt(sc, &run); + repair_outcomes(sc, error); if (error == -EAGAIN) { /* * Either the repair function succeeded or it couldn't diff --git a/fs/xfs/scrub/trace.c b/fs/xfs/scrub/trace.c index 4d0a6dceaa6c6..24a75e9e1a821 100644 --- a/fs/xfs/scrub/trace.c +++ b/fs/xfs/scrub/trace.c @@ -69,3 +69,25 @@ xfbtree_ino( */ #define CREATE_TRACE_POINTS #include "scrub/trace.h" + +/* xchk_whine stuff */ +struct xchk_tstr { + unsigned int type; + const char *tag; +}; + +static const struct xchk_tstr xchk_tstr_tags[] = { XFS_SCRUB_TYPE_STRINGS }; + +const char * +xchk_type_string( + unsigned int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xchk_tstr_tags); i++) { + if (xchk_tstr_tags[i].type == type) + return xchk_tstr_tags[i].tag; + } + + return "???"; +} diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 2c6f7e3b7578d..cfd882edb2937 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -127,6 +127,8 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_RTREFCBT); { XFS_SCRUB_TYPE_RTRMAPBT, "rtrmapbt" }, \ { XFS_SCRUB_TYPE_RTREFCBT, "rtrefcountbt" } +const char *xchk_type_string(unsigned int type); + #define XFS_SCRUB_FLAG_STRINGS \ { XFS_SCRUB_IFLAG_REPAIR, "repair" }, \ { XFS_SCRUB_OFLAG_CORRUPT, "corrupt" }, \ diff --git a/fs/xfs/xfs_globals.c b/fs/xfs/xfs_globals.c index f18fec0adf666..f5fe896b9a8ec 100644 --- a/fs/xfs/xfs_globals.c +++ b/fs/xfs/xfs_globals.c @@ -44,6 +44,11 @@ struct xfs_globals xfs_globals = { .pwork_threads = -1, /* automatic thread detection */ .larp = false, /* log attribute replay */ #endif +#ifdef CONFIG_XFS_ONLINE_SCRUB_WHINE + .scrub_whine = true, +#else + .scrub_whine = false, +#endif /* * Leave this many record slots empty when bulk loading btrees. By diff --git a/fs/xfs/xfs_sysctl.h b/fs/xfs/xfs_sysctl.h index 276696a07040c..b0939ac370fba 100644 --- a/fs/xfs/xfs_sysctl.h +++ b/fs/xfs/xfs_sysctl.h @@ -91,6 +91,7 @@ struct xfs_globals { int mount_delay; /* mount setup delay (secs) */ bool bug_on_assert; /* BUG() the kernel on assert failure */ bool always_cow; /* use COW fork for all overwrites */ + bool scrub_whine; /* noisier output from scrub */ }; extern struct xfs_globals xfs_globals; diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c index 17485666b6723..b9ba47dcee8c1 100644 --- a/fs/xfs/xfs_sysfs.c +++ b/fs/xfs/xfs_sysfs.c @@ -262,6 +262,37 @@ larp_show( XFS_SYSFS_ATTR_RW(larp); #endif /* DEBUG */ +/* Logging of the outcomes of everything that scrub does */ +STATIC ssize_t +scrub_whine_store( + struct kobject *kobject, + const char *buf, + size_t count) +{ + int ret; + int val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val < -1 || val > num_possible_cpus()) + return -EINVAL; + + xfs_globals.scrub_whine = val; + + return count; +} + +STATIC ssize_t +scrub_whine_show( + struct kobject *kobject, + char *buf) +{ + return sysfs_emit(buf, "%d\n", xfs_globals.scrub_whine); +} +XFS_SYSFS_ATTR_RW(scrub_whine); + STATIC ssize_t bload_leaf_slack_store( struct kobject *kobject, @@ -323,6 +354,7 @@ static struct attribute *xfs_dbg_attrs[] = { ATTR_LIST(pwork_threads), ATTR_LIST(larp), #endif + ATTR_LIST(scrub_whine), ATTR_LIST(bload_leaf_slack), ATTR_LIST(bload_node_slack), NULL, ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 3/5] xfs: create a noalloc mode for allocation groups 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong 2023-12-31 22:01 ` [PATCH 2/5] xfs: whine to dmesg when we encounter errors Darrick J. Wong @ 2023-12-31 22:01 ` Darrick J. Wong 2023-12-31 22:01 ` [PATCH 4/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong 2023-12-31 22:02 ` [PATCH 5/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:01 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Create a new noalloc state for the per-AG structure that will disable block allocation in this AG. We accomplish this by subtracting from fdblocks all the free blocks in this AG, hiding those blocks from the allocator, and preventing freed blocks from updating fdblocks until we're ready to lift noalloc mode. Note that we reduce the free block count of the filesystem so that we can prevent transactions from entering the allocator looking for "free" space that we've turned off incore. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_ag.c | 60 +++++++++++++++++++++++++++++++++++++++++++ fs/xfs/libxfs/xfs_ag.h | 8 ++++++ fs/xfs/libxfs/xfs_ag_resv.c | 27 +++++++++++++++++-- fs/xfs/scrub/fscounters.c | 3 +- fs/xfs/xfs_fsops.c | 10 ++++++- fs/xfs/xfs_super.c | 1 + fs/xfs/xfs_trace.h | 46 +++++++++++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ag.c b/fs/xfs/libxfs/xfs_ag.c index 35deb474a7cf0..a855f943cfad9 100644 --- a/fs/xfs/libxfs/xfs_ag.c +++ b/fs/xfs/libxfs/xfs_ag.c @@ -1119,3 +1119,63 @@ xfs_ag_get_geometry( xfs_buf_relse(agi_bp); return error; } + +/* How many blocks does this AG contribute to fdblocks? */ +xfs_extlen_t +xfs_ag_fdblocks( + struct xfs_perag *pag) +{ + xfs_extlen_t ret; + + ASSERT(xfs_perag_initialised_agf(pag)); + + ret = pag->pagf_freeblks + pag->pagf_flcount + pag->pagf_btreeblks; + ret -= pag->pag_meta_resv.ar_reserved; + ret -= pag->pag_rmapbt_resv.ar_orig_reserved; + return ret; +} + +/* + * Hide all the free space in this AG. Caller must hold both the AGI and the + * AGF buffers or have otherwise prevented concurrent access. + */ +int +xfs_ag_set_noalloc( + struct xfs_perag *pag) +{ + struct xfs_mount *mp = pag->pag_mount; + int error; + + ASSERT(xfs_perag_initialised_agf(pag)); + ASSERT(xfs_perag_initialised_agi(pag)); + + if (xfs_perag_prohibits_alloc(pag)) + return 0; + + error = xfs_mod_fdblocks(mp, -(int64_t)xfs_ag_fdblocks(pag), false); + if (error) + return error; + + trace_xfs_ag_set_noalloc(pag); + set_bit(XFS_AGSTATE_NOALLOC, &pag->pag_opstate); + return 0; +} + +/* + * Unhide all the free space in this AG. Caller must hold both the AGI and + * the AGF buffers or have otherwise prevented concurrent access. + */ +void +xfs_ag_clear_noalloc( + struct xfs_perag *pag) +{ + struct xfs_mount *mp = pag->pag_mount; + + if (!xfs_perag_prohibits_alloc(pag)) + return; + + xfs_mod_fdblocks(mp, xfs_ag_fdblocks(pag), false); + + trace_xfs_ag_clear_noalloc(pag); + clear_bit(XFS_AGSTATE_NOALLOC, &pag->pag_opstate); +} diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h index 79017fcd3df58..c21cc30ebc73f 100644 --- a/fs/xfs/libxfs/xfs_ag.h +++ b/fs/xfs/libxfs/xfs_ag.h @@ -131,6 +131,7 @@ struct xfs_perag { #define XFS_AGSTATE_PREFERS_METADATA 2 #define XFS_AGSTATE_ALLOWS_INODES 3 #define XFS_AGSTATE_AGFL_NEEDS_RESET 4 +#define XFS_AGSTATE_NOALLOC 5 #define __XFS_AG_OPSTATE(name, NAME) \ static inline bool xfs_perag_ ## name (struct xfs_perag *pag) \ @@ -143,6 +144,7 @@ __XFS_AG_OPSTATE(initialised_agi, AGI_INIT) __XFS_AG_OPSTATE(prefers_metadata, PREFERS_METADATA) __XFS_AG_OPSTATE(allows_inodes, ALLOWS_INODES) __XFS_AG_OPSTATE(agfl_needs_reset, AGFL_NEEDS_RESET) +__XFS_AG_OPSTATE(prohibits_alloc, NOALLOC) int xfs_initialize_perag(struct xfs_mount *mp, xfs_agnumber_t agcount, xfs_rfsblock_t dcount, xfs_agnumber_t *maxagi); @@ -156,12 +158,18 @@ struct xfs_perag *xfs_perag_get_tag(struct xfs_mount *mp, xfs_agnumber_t agno, struct xfs_perag *xfs_perag_hold(struct xfs_perag *pag); void xfs_perag_put(struct xfs_perag *pag); + /* Active AG references */ struct xfs_perag *xfs_perag_grab(struct xfs_mount *, xfs_agnumber_t); struct xfs_perag *xfs_perag_grab_tag(struct xfs_mount *, xfs_agnumber_t, int tag); void xfs_perag_rele(struct xfs_perag *pag); +/* Enable or disable allocation from an AG */ +xfs_extlen_t xfs_ag_fdblocks(struct xfs_perag *pag); +int xfs_ag_set_noalloc(struct xfs_perag *pag); +void xfs_ag_clear_noalloc(struct xfs_perag *pag); + /* * Per-ag geometry infomation and validation */ diff --git a/fs/xfs/libxfs/xfs_ag_resv.c b/fs/xfs/libxfs/xfs_ag_resv.c index f775b92b4aacd..7142eda1c2501 100644 --- a/fs/xfs/libxfs/xfs_ag_resv.c +++ b/fs/xfs/libxfs/xfs_ag_resv.c @@ -73,6 +73,13 @@ xfs_ag_resv_critical( xfs_extlen_t avail; xfs_extlen_t orig; + /* + * Pretend we're critically low on reservations in this AG to scare + * everyone else away. + */ + if (xfs_perag_prohibits_alloc(pag)) + return true; + switch (type) { case XFS_AG_RESV_METADATA: avail = pag->pagf_freeblks - pag->pag_rmapbt_resv.ar_reserved; @@ -115,7 +122,12 @@ xfs_ag_resv_needed( break; case XFS_AG_RESV_IMETA: case XFS_AG_RESV_NONE: - /* empty */ + /* + * In noalloc mode, we pretend that all the free blocks in this + * AG have been allocated. Make this AG look full. + */ + if (xfs_perag_prohibits_alloc(pag)) + len += xfs_ag_fdblocks(pag); break; default: ASSERT(0); @@ -344,6 +356,8 @@ xfs_ag_resv_alloc_extent( xfs_extlen_t len; uint field; + ASSERT(type != XFS_AG_RESV_NONE || !xfs_perag_prohibits_alloc(pag)); + trace_xfs_ag_resv_alloc_extent(pag, type, args->len); switch (type) { @@ -401,7 +415,14 @@ xfs_ag_resv_free_extent( ASSERT(0); fallthrough; case XFS_AG_RESV_NONE: - xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, (int64_t)len); + /* + * Normally we put freed blocks back into fdblocks. In noalloc + * mode, however, we pretend that there are no fdblocks in the + * AG, so don't put them back. + */ + if (!xfs_perag_prohibits_alloc(pag)) + xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, + (int64_t)len); fallthrough; case XFS_AG_RESV_IGNORE: return; @@ -414,6 +435,6 @@ xfs_ag_resv_free_extent( /* Freeing into the reserved pool only requires on-disk update... */ xfs_trans_mod_sb(tp, XFS_TRANS_SB_RES_FDBLOCKS, len); /* ...but freeing beyond that requires in-core and on-disk update. */ - if (len > leftover) + if (len > leftover && !xfs_perag_prohibits_alloc(pag)) xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, len - leftover); } diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index 2b4bd2eb71b57..220b27e79497b 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -338,7 +338,8 @@ xchk_fscount_aggregate_agcounts( */ fsc->fdblocks -= pag->pag_meta_resv.ar_reserved; fsc->fdblocks -= pag->pag_rmapbt_resv.ar_orig_reserved; - + if (xfs_perag_prohibits_alloc(pag)) + fsc->fdblocks -= xfs_ag_fdblocks(pag); } if (pag) xfs_perag_rele(pag); diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c index 9584c08480f75..639b46c617be7 100644 --- a/fs/xfs/xfs_fsops.c +++ b/fs/xfs/xfs_fsops.c @@ -638,6 +638,14 @@ xfs_fs_unreserve_ag_blocks( if (xfs_has_realtime(mp)) xfs_rt_resv_free(mp); - for_each_perag(mp, agno, pag) + for_each_perag(mp, agno, pag) { + /* + * Bring the AG back online because our AG hiding only exists + * in-core and we need the superblock to be written out with + * the super fdblocks reflecting the AGF freeblks. Do this + * before adding the per-AG reservations back to fdblocks. + */ + xfs_ag_clear_noalloc(pag); xfs_ag_resv_free(pag); + } } diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 2aa91f9be05a6..8f06716dd0169 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -324,6 +324,7 @@ xfs_set_inode_alloc( pag = xfs_perag_get(mp, index); if (xfs_set_inode_alloc_perag(pag, ino, max_metadata)) maxagi++; + clear_bit(XFS_AGSTATE_NOALLOC, &pag->pag_opstate); xfs_perag_put(pag); } diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 6c99bf56184b0..531a522ac0f7a 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -4527,6 +4527,52 @@ DEFINE_INODE_CORRUPT_EVENT(xfs_inode_mark_sick); DEFINE_INODE_CORRUPT_EVENT(xfs_inode_mark_healthy); DEFINE_INODE_CORRUPT_EVENT(xfs_inode_unfixed_corruption); +DECLARE_EVENT_CLASS(xfs_ag_noalloc_class, + TP_PROTO(struct xfs_perag *pag), + TP_ARGS(pag), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_agnumber_t, agno) + __field(xfs_extlen_t, freeblks) + __field(xfs_extlen_t, flcount) + __field(xfs_extlen_t, btreeblks) + __field(xfs_extlen_t, meta_resv) + __field(xfs_extlen_t, rmap_resv) + + __field(unsigned long long, resblks) + __field(unsigned long long, resblks_avail) + ), + TP_fast_assign( + __entry->dev = pag->pag_mount->m_super->s_dev; + __entry->agno = pag->pag_agno; + __entry->freeblks = pag->pagf_freeblks; + __entry->flcount = pag->pagf_flcount; + __entry->btreeblks = pag->pagf_btreeblks; + __entry->meta_resv = pag->pag_meta_resv.ar_reserved; + __entry->rmap_resv = pag->pag_rmapbt_resv.ar_orig_reserved; + + __entry->resblks = pag->pag_mount->m_resblks; + __entry->resblks_avail = pag->pag_mount->m_resblks_avail; + ), + TP_printk("dev %d:%d agno 0x%x freeblks %u flcount %u btreeblks %u metaresv %u rmapresv %u resblks %llu resblks_avail %llu", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->agno, + __entry->freeblks, + __entry->flcount, + __entry->btreeblks, + __entry->meta_resv, + __entry->rmap_resv, + __entry->resblks, + __entry->resblks_avail) +); +#define DEFINE_AG_NOALLOC_EVENT(name) \ +DEFINE_EVENT(xfs_ag_noalloc_class, name, \ + TP_PROTO(struct xfs_perag *pag), \ + TP_ARGS(pag)) + +DEFINE_AG_NOALLOC_EVENT(xfs_ag_set_noalloc); +DEFINE_AG_NOALLOC_EVENT(xfs_ag_clear_noalloc); + TRACE_EVENT(xfs_iwalk_ag, TP_PROTO(struct xfs_mount *mp, xfs_agnumber_t agno, xfs_agino_t startino), ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 4/5] xfs: enable userspace to hide an AG from allocation 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong ` (2 preceding siblings ...) 2023-12-31 22:01 ` [PATCH 3/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong @ 2023-12-31 22:01 ` Darrick J. Wong 2023-12-31 22:02 ` [PATCH 5/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:01 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add an administrative interface so that userspace can hide an allocation group from block allocation. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_ag.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/libxfs/xfs_fs.h | 5 ++++ fs/xfs/xfs_ioctl.c | 4 +++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_ag.c b/fs/xfs/libxfs/xfs_ag.c index a855f943cfad9..183f46edb5d15 100644 --- a/fs/xfs/libxfs/xfs_ag.c +++ b/fs/xfs/libxfs/xfs_ag.c @@ -1075,6 +1075,54 @@ xfs_ag_extend_space( return 0; } +/* Compute the AG geometry flags. */ +static inline uint32_t +xfs_ag_calc_geoflags( + struct xfs_perag *pag) +{ + uint32_t ret = 0; + + if (xfs_perag_prohibits_alloc(pag)) + ret |= XFS_AG_FLAG_NOALLOC; + + return ret; +} + +/* + * Compare the current AG geometry flags against the flags in the AG geometry + * structure and update the AG state to reflect any changes, then update the + * struct to reflect the current status. + */ +static inline int +xfs_ag_update_geoflags( + struct xfs_perag *pag, + struct xfs_ag_geometry *ageo, + uint32_t new_flags) +{ + uint32_t old_flags = xfs_ag_calc_geoflags(pag); + int error; + + if (!(new_flags & XFS_AG_FLAG_UPDATE)) { + ageo->ag_flags = old_flags; + return 0; + } + + if ((old_flags & XFS_AG_FLAG_NOALLOC) && + !(new_flags & XFS_AG_FLAG_NOALLOC)) { + xfs_ag_clear_noalloc(pag); + } + + if (!(old_flags & XFS_AG_FLAG_NOALLOC) && + (new_flags & XFS_AG_FLAG_NOALLOC)) { + error = xfs_ag_set_noalloc(pag); + if (error) + return error; + } + + ageo->ag_flags = xfs_ag_calc_geoflags(pag); + return 0; +} + /* Retrieve AG geometry. */ int xfs_ag_get_geometry( @@ -1086,6 +1134,7 @@ xfs_ag_get_geometry( struct xfs_agi *agi; struct xfs_agf *agf; unsigned int freeblks; + uint32_t inflags = ageo->ag_flags; int error; /* Lock the AG headers. */ @@ -1096,6 +1145,10 @@ xfs_ag_get_geometry( if (error) goto out_agi; + error = xfs_ag_update_geoflags(pag, ageo, inflags); + if (error) + goto out; + /* Fill out form. */ memset(ageo, 0, sizeof(*ageo)); ageo->ag_number = pag->pag_agno; @@ -1113,6 +1166,7 @@ xfs_ag_get_geometry( ageo->ag_freeblks = freeblks; xfs_ag_geom_health(pag, ageo); +out: /* Release resources. */ xfs_buf_relse(agf_bp); out_agi: diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h index 4159e96d01ae6..96688f9301e08 100644 --- a/fs/xfs/libxfs/xfs_fs.h +++ b/fs/xfs/libxfs/xfs_fs.h @@ -305,6 +305,11 @@ struct xfs_ag_geometry { #define XFS_AG_GEOM_SICK_REFCNTBT (1 << 9) /* reference counts */ #define XFS_AG_GEOM_SICK_INODES (1 << 10) /* bad inodes were seen */ +#define XFS_AG_FLAG_UPDATE (1 << 0) /* update flags */ +#define XFS_AG_FLAG_NOALLOC (1 << 1) /* do not allocate from this AG */ +#define XFS_AG_FLAG_ALL (XFS_AG_FLAG_UPDATE | \ + XFS_AG_FLAG_NOALLOC) + /* * Structures for XFS_IOC_FSGROWFSDATA, XFS_IOC_FSGROWFSLOG & XFS_IOC_FSGROWFSRT */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index f85d5f142d180..d17b17e7eea1d 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -976,10 +976,12 @@ xfs_ioc_ag_geometry( if (copy_from_user(&ageo, arg, sizeof(ageo))) return -EFAULT; - if (ageo.ag_flags) + if (ageo.ag_flags & ~XFS_AG_FLAG_ALL) return -EINVAL; if (memchr_inv(&ageo.ag_reserved, 0, sizeof(ageo.ag_reserved))) return -EINVAL; + if ((ageo.ag_flags & XFS_AG_FLAG_UPDATE) && !capable(CAP_SYS_ADMIN)) + return -EPERM; pag = xfs_perag_get(mp, ageo.ag_number); if (!pag) ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 5/5] xfs: apply noalloc mode to inode allocations too 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong ` (3 preceding siblings ...) 2023-12-31 22:01 ` [PATCH 4/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong @ 2023-12-31 22:02 ` Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:02 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Don't allow inode allocations from this group if it's marked noalloc. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_ialloc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index 46c0bb67e4c47..e22f02722d19f 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -1064,6 +1064,7 @@ xfs_dialloc_ag_inobt( ASSERT(xfs_perag_initialised_agi(pag)); ASSERT(xfs_perag_allows_inodes(pag)); + ASSERT(!xfs_perag_prohibits_alloc(pag)); ASSERT(pag->pagi_freecount > 0); restart_pagno: @@ -1692,6 +1693,8 @@ xfs_dialloc_good_ag( return false; if (!xfs_perag_allows_inodes(pag)) return false; + if (xfs_perag_prohibits_alloc(pag)) + return false; if (!xfs_perag_initialised_agi(pag)) { error = xfs_ialloc_read_agi(pag, tp, NULL); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 3/5] xfs: report refcount information to userspace 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong @ 2023-12-31 19:39 ` Darrick J. Wong 2023-12-31 22:02 ` [PATCH 1/1] xfs: export reference count " Darrick J. Wong 2023-12-31 19:39 ` [PATCHSET 4/5] xfs: defragment free space Darrick J. Wong ` (6 subsequent siblings) 9 siblings, 1 reply; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:39 UTC (permalink / raw) To: djwong; +Cc: linux-xfs Hi all, Create a new ioctl to report the number of owners of each disk block so that reflink-aware defraggers can make better decisions about which extents to target. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=report-refcounts xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=report-refcounts fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=report-refcounts --- fs/xfs/Makefile | 1 fs/xfs/libxfs/xfs_fs_staging.h | 82 +++ fs/xfs/xfs_fsrefs.c | 970 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_fsrefs.h | 34 + fs/xfs/xfs_ioctl.c | 135 ++++++ fs/xfs/xfs_trace.c | 1 fs/xfs/xfs_trace.h | 99 ++++ 7 files changed, 1322 insertions(+) create mode 100644 fs/xfs/xfs_fsrefs.c create mode 100644 fs/xfs/xfs_fsrefs.h ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/1] xfs: export reference count information to userspace 2023-12-31 19:39 ` [PATCHSET 3/5] xfs: report refcount information to userspace Darrick J. Wong @ 2023-12-31 22:02 ` Darrick J. Wong 0 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:02 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Export refcount info to userspace so we can prototype a sharing-aware defrag/fs rearranging tool. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/Makefile | 1 fs/xfs/libxfs/xfs_fs_staging.h | 82 +++ fs/xfs/xfs_fsrefs.c | 970 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_fsrefs.h | 34 + fs/xfs/xfs_ioctl.c | 135 ++++++ fs/xfs/xfs_trace.c | 1 fs/xfs/xfs_trace.h | 99 ++++ 7 files changed, 1322 insertions(+) create mode 100644 fs/xfs/xfs_fsrefs.c create mode 100644 fs/xfs/xfs_fsrefs.h diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index a779030fd91db..f1b2814bbb183 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -81,6 +81,7 @@ xfs-y += xfs_aops.o \ xfs_filestream.o \ xfs_fsmap.o \ xfs_fsops.o \ + xfs_fsrefs.o \ xfs_globals.o \ xfs_health.o \ xfs_icache.o \ diff --git a/fs/xfs/libxfs/xfs_fs_staging.h b/fs/xfs/libxfs/xfs_fs_staging.h index 9f0c03103f05b..f7d872f8a8837 100644 --- a/fs/xfs/libxfs/xfs_fs_staging.h +++ b/fs/xfs/libxfs/xfs_fs_staging.h @@ -221,4 +221,86 @@ struct xfs_rtgroup_geometry { #define XFS_IOC_RTGROUP_GEOMETRY _IOWR('X', 63, struct xfs_rtgroup_geometry) +/* + * Structure for XFS_IOC_GETFSREFCOUNTS. + * + * The memory layout for this call are the scalar values defined in struct + * xfs_getfsrefs_head, followed by two struct xfs_getfsrefs that describe + * the lower and upper bound of mappings to return, followed by an array + * of struct xfs_getfsrefs mappings. + * + * fch_iflags control the output of the call, whereas fch_oflags report + * on the overall record output. fch_count should be set to the length + * of the fch_recs array, and fch_entries will be set to the number of + * entries filled out during each call. If fch_count is zero, the number + * of refcount mappings will be returned in fch_entries, though no + * mappings will be returned. fch_reserved must be set to zero. + * + * The two elements in the fch_keys array are used to constrain the + * output. The first element in the array should represent the lowest + * disk mapping ("low key") that the user wants to learn about. If this + * value is all zeroes, the filesystem will return the first entry it + * knows about. For a subsequent call, the contents of + * fsrefs_head.fch_recs[fsrefs_head.fch_count - 1] should be copied into + * fch_keys[0] to have the kernel start where it left off. + * + * The second element in the fch_keys array should represent the highest + * disk mapping ("high key") that the user wants to learn about. If this + * value is all ones, the filesystem will not stop until it runs out of + * mapping to return or runs out of space in fch_recs. + * + * fcr_device can be either a 32-bit cookie representing a device, or a + * 32-bit dev_t if the FCH_OF_DEV_T flag is set. fcr_physical and + * fcr_length are expressed in units of bytes. fcr_owners is the number + * of owners. + */ +struct xfs_getfsrefs { + __u32 fcr_device; /* device id */ + __u32 fcr_flags; /* mapping flags */ + __u64 fcr_physical; /* device offset of segment */ + __u64 fcr_owners; /* number of owners */ + __u64 fcr_length; /* length of segment */ + __u64 fcr_reserved[4]; /* must be zero */ +}; + +struct xfs_getfsrefs_head { + __u32 fch_iflags; /* control flags */ + __u32 fch_oflags; /* output flags */ + __u32 fch_count; /* # of entries in array incl. input */ + __u32 fch_entries; /* # of entries filled in (output). */ + __u64 fch_reserved[6]; /* must be zero */ + + struct xfs_getfsrefs fch_keys[2]; /* low and high keys for the mapping search */ + struct xfs_getfsrefs fch_recs[]; /* returned records */ +}; + +/* Size of an fsrefs_head with room for nr records. */ +static inline unsigned long long +xfs_getfsrefs_sizeof( + unsigned int nr) +{ + return sizeof(struct xfs_getfsrefs_head) + nr * sizeof(struct xfs_getfsrefs); +} + +/* Start the next fsrefs query at the end of the current query results. */ +static inline void +xfs_getfsrefs_advance( + struct xfs_getfsrefs_head *head) +{ + head->fch_keys[0] = head->fch_recs[head->fch_entries - 1]; +} + +/* fch_iflags values - set by XFS_IOC_GETFSREFCOUNTS caller in the header. */ +/* no flags defined yet */ +#define FCH_IF_VALID 0 + +/* fch_oflags values - returned in the header segment only. */ +#define FCH_OF_DEV_T (1U << 0) /* fcr_device values will be dev_t */ + +/* fcr_flags values - returned for each non-header segment */ +#define FCR_OF_LAST (1U << 0) /* segment is the last in the dataset */ + +/* XXX stealing XFS_IOC_GETBIOSIZE */ +#define XFS_IOC_GETFSREFCOUNTS _IOWR('X', 47, struct xfs_getfsrefs_head) + #endif /* __XFS_FS_STAGING_H__ */ diff --git a/fs/xfs/xfs_fsrefs.c b/fs/xfs/xfs_fsrefs.c new file mode 100644 index 0000000000000..cff1c7c4b7542 --- /dev/null +++ b/fs/xfs/xfs_fsrefs.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#include "xfs.h" +#include "xfs_fs.h" +#include "xfs_shared.h" +#include "xfs_format.h" +#include "xfs_log_format.h" +#include "xfs_trans_resv.h" +#include "xfs_mount.h" +#include "xfs_inode.h" +#include "xfs_trans.h" +#include "xfs_btree.h" +#include "xfs_trace.h" +#include "xfs_alloc.h" +#include "xfs_bit.h" +#include "xfs_fsrefs.h" +#include "xfs_refcount.h" +#include "xfs_refcount_btree.h" +#include "xfs_alloc_btree.h" +#include "xfs_rtalloc.h" +#include "xfs_rtrefcount_btree.h" +#include "xfs_ag.h" +#include "xfs_rtbitmap.h" +#include "xfs_rtgroup.h" + +/* getfsrefs query state */ +struct xfs_fsrefs_info { + struct xfs_fsrefs_head *head; + struct xfs_getfsrefs *fsrefs_recs; /* mapping records */ + + struct xfs_btree_cur *refc_cur; /* refcount btree cursor */ + struct xfs_btree_cur *bno_cur; /* bnobt btree cursor */ + + struct xfs_buf *agf_bp; /* AGF, for refcount queries */ + struct xfs_perag *pag; /* perag structure */ + struct xfs_rtgroup *rtg; + + xfs_daddr_t next_daddr; /* next daddr we expect */ + /* daddr of low fsmap key when we're using the rtbitmap */ + xfs_daddr_t low_daddr; + + /* + * Low refcount key for the query. If low.rc_blockcount is nonzero, + * this is the second (or later) call to retrieve the recordset in + * pieces. xfs_getfsrefs_rec_before_start will compare all records + * retrieved by the refcountbt query to filter out any records that + * start before the last record. + */ + struct xfs_refcount_irec low; + struct xfs_refcount_irec high; /* high refcount key */ + + u32 dev; /* device id */ + bool last; /* last extent? */ +}; + +/* Associate a device with a getfsrefs handler. */ +struct xfs_fsrefs_dev { + u32 dev; + int (*fn)(struct xfs_trans *tp, + const struct xfs_fsrefs *keys, + struct xfs_fsrefs_info *info); +}; + +/* Convert an xfs_fsrefs to an fsrefs. */ +static void +xfs_fsrefs_from_internal( + struct xfs_getfsrefs *dest, + struct xfs_fsrefs *src) +{ + dest->fcr_device = src->fcr_device; + dest->fcr_flags = src->fcr_flags; + dest->fcr_physical = BBTOB(src->fcr_physical); + dest->fcr_owners = src->fcr_owners; + dest->fcr_length = BBTOB(src->fcr_length); + dest->fcr_reserved[0] = 0; + dest->fcr_reserved[1] = 0; + dest->fcr_reserved[2] = 0; + dest->fcr_reserved[3] = 0; +} + +/* Convert an fsrefs to an xfs_fsrefs. */ +void +xfs_fsrefs_to_internal( + struct xfs_fsrefs *dest, + struct xfs_getfsrefs *src) +{ + dest->fcr_device = src->fcr_device; + dest->fcr_flags = src->fcr_flags; + dest->fcr_physical = BTOBBT(src->fcr_physical); + dest->fcr_owners = src->fcr_owners; + dest->fcr_length = BTOBBT(src->fcr_length); +} + +/* Compare two getfsrefs device handlers. */ +static int +xfs_fsrefs_dev_compare( + const void *p1, + const void *p2) +{ + const struct xfs_fsrefs_dev *d1 = p1; + const struct xfs_fsrefs_dev *d2 = p2; + + return d1->dev - d2->dev; +} + +static inline bool +xfs_fsrefs_rec_before_start( + struct xfs_fsrefs_info *info, + const struct xfs_refcount_irec *rec, + xfs_daddr_t rec_daddr) +{ + if (info->low_daddr != -1ULL) + return rec_daddr < info->low_daddr; + if (info->low.rc_blockcount) + return rec->rc_startblock < info->low.rc_startblock; + return false; +} + +/* + * Format a refcount record for fsrefs, having translated rc_startblock into + * the appropriate daddr units. + */ +STATIC int +xfs_fsrefs_helper( + struct xfs_trans *tp, + struct xfs_fsrefs_info *info, + const struct xfs_refcount_irec *rec, + xfs_daddr_t rec_daddr, + xfs_daddr_t len_daddr) +{ + struct xfs_fsrefs fcr; + struct xfs_getfsrefs *row; + struct xfs_mount *mp = tp->t_mountp; + + if (fatal_signal_pending(current)) + return -EINTR; + + if (len_daddr == 0) + len_daddr = XFS_FSB_TO_BB(mp, rec->rc_blockcount); + + /* + * Filter out records that start before our startpoint, if the + * caller requested that. + */ + if (xfs_fsrefs_rec_before_start(info, rec, rec_daddr)) + return 0; + + /* Are we just counting mappings? */ + if (info->head->fch_count == 0) { + if (info->head->fch_entries == UINT_MAX) + return -ECANCELED; + + info->head->fch_entries++; + return 0; + } + + /* Fill out the extent we found */ + if (info->head->fch_entries >= info->head->fch_count) + return -ECANCELED; + + if (info->pag) + trace_xfs_fsrefs_mapping(mp, info->dev, info->pag->pag_agno, + rec); + else if (info->rtg) + trace_xfs_fsrefs_mapping(mp, info->dev, info->rtg->rtg_rgno, + rec); + else + trace_xfs_fsrefs_mapping(mp, info->dev, NULLAGNUMBER, rec); + + fcr.fcr_device = info->dev; + fcr.fcr_flags = 0; + fcr.fcr_physical = rec_daddr; + fcr.fcr_owners = rec->rc_refcount; + fcr.fcr_length = len_daddr; + + trace_xfs_getfsrefs_mapping(mp, &fcr); + + row = &info->fsrefs_recs[info->head->fch_entries++]; + xfs_fsrefs_from_internal(row, &fcr); + return 0; +} + +/* Synthesize fsrefs records from free space data. */ +STATIC int +xfs_fsrefs_ddev_bnobt_helper( + struct xfs_btree_cur *cur, + const struct xfs_alloc_rec_incore *rec, + void *priv) +{ + struct xfs_refcount_irec irec; + struct xfs_mount *mp = cur->bc_mp; + struct xfs_fsrefs_info *info = priv; + xfs_agnumber_t next_agno; + xfs_agblock_t next_agbno; + xfs_daddr_t rec_daddr; + + /* + * Figure out if there's a gap between the last fsrefs record we + * emitted and this free extent. If there is, report the gap as a + * refcount==1 record. + */ + next_agno = xfs_daddr_to_agno(mp, info->next_daddr); + next_agbno = xfs_daddr_to_agbno(mp, info->next_daddr); + + ASSERT(next_agno >= cur->bc_ag.pag->pag_agno); + ASSERT(rec->ar_startblock >= next_agbno); + + /* + * If we've already moved on to the next AG, we don't have any fsrefs + * records to synthesize. + */ + if (next_agno > cur->bc_ag.pag->pag_agno) + return 0; + + info->next_daddr = XFS_AGB_TO_DADDR(mp, cur->bc_ag.pag->pag_agno, + rec->ar_startblock + rec->ar_blockcount); + + if (rec->ar_startblock == next_agbno) + return 0; + + /* Emit a record for the in-use space */ + irec.rc_startblock = next_agbno; + irec.rc_blockcount = rec->ar_startblock - next_agbno; + irec.rc_refcount = 1; + irec.rc_domain = XFS_REFC_DOMAIN_SHARED; + rec_daddr = XFS_AGB_TO_DADDR(mp, cur->bc_ag.pag->pag_agno, + irec.rc_startblock); + + return xfs_fsrefs_helper(cur->bc_tp, info, &irec, rec_daddr, 0); +} + +/* Emit records to fill a gap in the refcount btree with singly-owned blocks. */ +STATIC int +xfs_fsrefs_ddev_fill_refcount_gap( + struct xfs_trans *tp, + struct xfs_fsrefs_info *info, + xfs_agblock_t agbno) +{ + struct xfs_alloc_rec_incore low = {0}; + struct xfs_alloc_rec_incore high = {0}; + struct xfs_mount *mp = tp->t_mountp; + struct xfs_btree_cur *cur = info->bno_cur; + struct xfs_agf *agf; + int error; + + ASSERT(xfs_daddr_to_agno(mp, info->next_daddr) == + cur->bc_ag.pag->pag_agno); + + low.ar_startblock = xfs_daddr_to_agbno(mp, info->next_daddr); + if (low.ar_startblock >= agbno) + return 0; + + high.ar_startblock = agbno; + error = xfs_alloc_query_range(cur, &low, &high, + xfs_fsrefs_ddev_bnobt_helper, info); + if (error) + return error; + + /* + * Synthesize records for single-owner extents between the last + * fsrefcount record emitted and the end of the query range. + */ + agf = cur->bc_ag.agbp->b_addr; + low.ar_startblock = min_t(xfs_agblock_t, agbno, + be32_to_cpu(agf->agf_length)); + if (xfs_daddr_to_agbno(mp, info->next_daddr) > low.ar_startblock) + return 0; + + info->last = true; + return xfs_fsrefs_ddev_bnobt_helper(cur, &low, info); +} + +/* Transform a refcountbt irec into a fsrefs */ +STATIC int +xfs_fsrefs_ddev_refcountbt_helper( + struct xfs_btree_cur *cur, + const struct xfs_refcount_irec *rec, + void *priv) +{ + struct xfs_mount *mp = cur->bc_mp; + struct xfs_fsrefs_info *info = priv; + xfs_daddr_t rec_daddr; + int error; + + /* + * Stop once we get to the CoW staging extents; they're all shoved to + * the right side of the btree and were already covered by the bnobt + * scan. + */ + if (rec->rc_domain != XFS_REFC_DOMAIN_SHARED) + return -ECANCELED; + + /* Report on any gaps first */ + error = xfs_fsrefs_ddev_fill_refcount_gap(cur->bc_tp, info, + rec->rc_startblock); + if (error) + return error; + + rec_daddr = XFS_AGB_TO_DADDR(mp, cur->bc_ag.pag->pag_agno, + rec->rc_startblock); + info->next_daddr = XFS_AGB_TO_DADDR(mp, cur->bc_ag.pag->pag_agno, + rec->rc_startblock + rec->rc_blockcount); + + return xfs_fsrefs_helper(cur->bc_tp, info, rec, rec_daddr, 0); +} + +/* Execute a getfsrefs query against the regular data device. */ +STATIC int +xfs_fsrefs_ddev( + struct xfs_trans *tp, + const struct xfs_fsrefs *keys, + struct xfs_fsrefs_info *info) +{ + struct xfs_mount *mp = tp->t_mountp; + struct xfs_buf *agf_bp = NULL; + struct xfs_perag *pag = NULL; + xfs_fsblock_t start_fsb; + xfs_fsblock_t end_fsb; + xfs_agnumber_t start_ag; + xfs_agnumber_t end_ag; + xfs_agnumber_t agno; + uint64_t eofs; + int error = 0; + + eofs = XFS_FSB_TO_BB(mp, mp->m_sb.sb_dblocks); + if (keys[0].fcr_physical >= eofs) + return 0; + start_fsb = XFS_DADDR_TO_FSB(mp, keys[0].fcr_physical); + end_fsb = XFS_DADDR_TO_FSB(mp, min(eofs - 1, keys[1].fcr_physical)); + + info->refc_cur = info->bno_cur = NULL; + + /* + * Convert the fsrefs low/high keys to AG based keys. Initialize + * low to the fsrefs low key and max out the high key to the end + * of the AG. + */ + info->low.rc_startblock = XFS_FSB_TO_AGBNO(mp, start_fsb); + info->low.rc_blockcount = XFS_BB_TO_FSBT(mp, keys[0].fcr_length); + info->low.rc_refcount = 0; + info->low.rc_domain = XFS_REFC_DOMAIN_SHARED; + + /* Adjust the low key if we are continuing from where we left off. */ + if (info->low.rc_blockcount > 0) { + info->low.rc_startblock += info->low.rc_blockcount; + + start_fsb += info->low.rc_blockcount; + if (XFS_FSB_TO_DADDR(mp, start_fsb) >= eofs) + return 0; + } + + info->high.rc_startblock = -1U; + info->high.rc_blockcount = 0; + info->high.rc_refcount = 0; + info->high.rc_domain = XFS_REFC_DOMAIN_SHARED; + + start_ag = XFS_FSB_TO_AGNO(mp, start_fsb); + end_ag = XFS_FSB_TO_AGNO(mp, end_fsb); + + /* Query each AG */ + agno = start_ag; + for_each_perag_range(mp, agno, end_ag, pag) { + /* + * Set the AG high key from the fsrefs high key if this + * is the last AG that we're querying. + */ + info->pag = pag; + if (pag->pag_agno == end_ag) + info->high.rc_startblock = XFS_FSB_TO_AGBNO(mp, + end_fsb); + + if (info->refc_cur) { + xfs_btree_del_cursor(info->refc_cur, XFS_BTREE_NOERROR); + info->refc_cur = NULL; + } + if (info->bno_cur) { + xfs_btree_del_cursor(info->bno_cur, XFS_BTREE_NOERROR); + info->bno_cur = NULL; + } + if (agf_bp) { + xfs_trans_brelse(tp, agf_bp); + agf_bp = NULL; + } + + error = xfs_alloc_read_agf(pag, tp, 0, &agf_bp); + if (error) + break; + + trace_xfs_fsrefs_low_key(mp, info->dev, pag->pag_agno, + &info->low); + trace_xfs_fsrefs_high_key(mp, info->dev, pag->pag_agno, + &info->high); + + info->bno_cur = xfs_allocbt_init_cursor(mp, tp, agf_bp, pag, + XFS_BTNUM_BNO); + + if (xfs_has_reflink(mp)) { + info->refc_cur = xfs_refcountbt_init_cursor(mp, tp, + agf_bp, pag); + + /* + * Fill the query with refcount records and synthesize + * singly-owned block records from free space data. + */ + error = xfs_refcount_query_range(info->refc_cur, + &info->low, &info->high, + xfs_fsrefs_ddev_refcountbt_helper, + info); + if (error && error != -ECANCELED) + break; + } + + /* + * Synthesize refcount==1 records from the free space data + * between the end of the last fsrefs record reported and the + * end of the range. If we don't have refcount support, the + * starting point will be the start of the query range. + */ + error = xfs_fsrefs_ddev_fill_refcount_gap(tp, info, + info->high.rc_startblock); + if (error) + break; + + /* + * Set the AG low key to the start of the AG prior to + * moving on to the next AG. + */ + if (pag->pag_agno == start_ag) + memset(&info->low, 0, sizeof(info->low)); + + info->pag = NULL; + } + + if (info->refc_cur) { + xfs_btree_del_cursor(info->refc_cur, error); + info->refc_cur = NULL; + } + if (info->bno_cur) { + xfs_btree_del_cursor(info->bno_cur, error); + info->bno_cur = NULL; + } + if (agf_bp) + xfs_trans_brelse(tp, agf_bp); + if (info->pag) { + xfs_perag_rele(info->pag); + info->pag = NULL; + } else if (pag) { + /* loop termination case */ + xfs_perag_rele(pag); + } + + return error; +} + +/* Execute a getfsrefs query against the log device. */ +STATIC int +xfs_fsrefs_logdev( + struct xfs_trans *tp, + const struct xfs_fsrefs *keys, + struct xfs_fsrefs_info *info) +{ + struct xfs_mount *mp = tp->t_mountp; + struct xfs_refcount_irec refc; + xfs_daddr_t rec_daddr, len_daddr; + xfs_fsblock_t start_fsb, end_fsb; + uint64_t eofs; + + eofs = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks); + if (keys[0].fcr_physical >= eofs) + return 0; + start_fsb = XFS_BB_TO_FSBT(mp, + keys[0].fcr_physical + keys[0].fcr_length); + end_fsb = XFS_BB_TO_FSB(mp, min(eofs - 1, keys[1].fcr_physical)); + + /* Adjust the low key if we are continuing from where we left off. */ + if (keys[0].fcr_length > 0) + info->low_daddr = XFS_FSB_TO_BB(mp, start_fsb); + + trace_xfs_fsrefs_low_key_linear(mp, info->dev, start_fsb); + trace_xfs_fsrefs_high_key_linear(mp, info->dev, end_fsb); + + if (start_fsb > 0) + return 0; + + /* Fabricate an refc entry for the external log device. */ + refc.rc_startblock = 0; + refc.rc_blockcount = mp->m_sb.sb_logblocks; + refc.rc_refcount = 1; + refc.rc_domain = XFS_REFC_DOMAIN_SHARED; + + rec_daddr = XFS_FSB_TO_BB(mp, refc.rc_startblock); + len_daddr = XFS_FSB_TO_BB(mp, refc.rc_blockcount); + return xfs_fsrefs_helper(tp, info, &refc, rec_daddr, len_daddr); +} + +#ifdef CONFIG_XFS_RT +/* Synthesize fsrefs records from rtbitmap records. */ +STATIC int +xfs_fsrefs_rtdev_bitmap_helper( + struct xfs_mount *mp, + struct xfs_trans *tp, + const struct xfs_rtalloc_rec *rec, + void *priv) +{ + struct xfs_refcount_irec irec; + struct xfs_fsrefs_info *info = priv; + xfs_rtblock_t rt_startblock; + xfs_rtblock_t rec_rtlen; + xfs_rtblock_t next_rtbno; + xfs_daddr_t rec_daddr, len_daddr; + + /* + * Figure out if there's a gap between the last fsrefs record we + * emitted and this free extent. If there is, report the gap as a + * refcount==1 record. + */ + next_rtbno = XFS_BB_TO_FSBT(mp, info->next_daddr); + rec_daddr = XFS_FSB_TO_BB(mp, next_rtbno); + irec.rc_startblock = next_rtbno; + + rt_startblock = xfs_rtx_to_rtb(mp, rec->ar_startext); + rec_rtlen = xfs_rtx_to_rtb(mp, rec->ar_extcount); + + ASSERT(rt_startblock >= next_rtbno); + + info->next_daddr = XFS_FSB_TO_BB(mp, rt_startblock + rec_rtlen); + + if (rt_startblock == next_rtbno) + return 0; + + /* Emit a record for the in-use space */ + irec.rc_blockcount = rt_startblock - next_rtbno; + len_daddr = XFS_FSB_TO_BB(mp, rt_startblock - next_rtbno); + + irec.rc_refcount = 1; + irec.rc_domain = XFS_REFC_DOMAIN_SHARED; + + return xfs_fsrefs_helper(tp, info, &irec, rec_daddr, len_daddr); +} + +/* Emit records to fill a gap in the refcount btree with singly-owned blocks. */ +STATIC int +xfs_fsrefs_rtdev_fill_refcount_gap( + struct xfs_trans *tp, + struct xfs_fsrefs_info *info, + xfs_rtblock_t start_rtb, + xfs_rtblock_t end_rtb) +{ + struct xfs_rtalloc_rec low = { 0 }; + struct xfs_rtalloc_rec high = { 0 }; + struct xfs_mount *mp = tp->t_mountp; + xfs_daddr_t rec_daddr; + int error; + + /* + * Set up query parameters to return free extents covering the range we + * want. + */ + low.ar_startext = xfs_rtb_to_rtx(mp, start_rtb); + high.ar_startext = xfs_rtb_to_rtxup(mp, end_rtb); + error = xfs_rtalloc_query_range(mp, tp, &low, &high, + xfs_fsrefs_rtdev_bitmap_helper, info); + if (error) + return error; + + /* + * Synthesize records for single-owner extents between the last + * fsrefcount record emitted and the end of the query range. + */ + high.ar_startext = min(mp->m_sb.sb_rextents, high.ar_startext); + rec_daddr = XFS_FSB_TO_BB(mp, xfs_rtx_to_rtb(mp, high.ar_startext)); + if (info->next_daddr > rec_daddr) + return 0; + + info->last = true; + return xfs_fsrefs_rtdev_bitmap_helper(mp, tp, &high, info); +} + +/* Transform a absolute-startblock refcount (rtdev, logdev) into a fsrefs */ +STATIC int +xfs_fsrefs_rtdev_refcountbt_helper( + struct xfs_btree_cur *cur, + const struct xfs_refcount_irec *rec, + void *priv) +{ + struct xfs_mount *mp = cur->bc_mp; + struct xfs_fsrefs_info *info = priv; + xfs_rtblock_t rec_rtbno, next_rtbno; + int error; + + /* + * Stop once we get to the CoW staging extents; they're all shoved to + * the right side of the btree and were already covered by the rtbitmap + * scan. + */ + if (rec->rc_domain != XFS_REFC_DOMAIN_SHARED) + return -ECANCELED; + + /* Report on any gaps first */ + rec_rtbno = xfs_rgbno_to_rtb(mp, cur->bc_ino.rtg->rtg_rgno, + rec->rc_startblock); + next_rtbno = XFS_BB_TO_FSBT(mp, info->next_daddr); + error = xfs_fsrefs_rtdev_fill_refcount_gap(cur->bc_tp, info, + next_rtbno, rec_rtbno); + if (error) + return error; + + /* Report this refcount extent. */ + info->next_daddr = XFS_FSB_TO_BB(mp, rec_rtbno + rec->rc_blockcount); + return xfs_fsrefs_helper(cur->bc_tp, info, rec, + XFS_FSB_TO_BB(mp, rec_rtbno), 0); +} + +/* Execute a getfsrefs query against the realtime bitmap. */ +STATIC int +xfs_fsrefs_rtdev_rtbitmap( + struct xfs_trans *tp, + const struct xfs_fsrefs *keys, + struct xfs_fsrefs_info *info) +{ + struct xfs_mount *mp = tp->t_mountp; + xfs_rtblock_t start_rtb; + xfs_rtblock_t end_rtb; + uint64_t eofs; + int error; + + eofs = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rblocks); + if (keys[0].fcr_physical >= eofs) + return 0; + start_rtb = XFS_BB_TO_FSBT(mp, + keys[0].fcr_physical + keys[0].fcr_length); + end_rtb = XFS_BB_TO_FSB(mp, min(eofs - 1, keys[1].fcr_physical)); + + info->refc_cur = NULL; + + /* Adjust the low key if we are continuing from where we left off. */ + if (keys[0].fcr_length > 0) { + info->low_daddr = XFS_FSB_TO_BB(mp, start_rtb); + if (info->low_daddr >= eofs) + return 0; + } + + trace_xfs_fsrefs_low_key_linear(mp, info->dev, start_rtb); + trace_xfs_fsrefs_high_key_linear(mp, info->dev, end_rtb); + + /* Synthesize refcount==1 records from the free space data. */ + xfs_rtbitmap_lock_shared(mp, XFS_RBMLOCK_BITMAP); + error = xfs_fsrefs_rtdev_fill_refcount_gap(tp, info, start_rtb, + end_rtb); + xfs_rtbitmap_unlock_shared(mp, XFS_RBMLOCK_BITMAP); + return error; +} + +#define XFS_RTGLOCK_FSREFS (XFS_RTGLOCK_BITMAP_SHARED | \ + XFS_RTGLOCK_REFCOUNT) + +/* Execute a getfsrefs query against the realtime device. */ +STATIC int +xfs_fsrefs_rtdev( + struct xfs_trans *tp, + const struct xfs_fsrefs *keys, + struct xfs_fsrefs_info *info) +{ + struct xfs_mount *mp = tp->t_mountp; + struct xfs_rtgroup *rtg; + xfs_rtblock_t start_rtb; + xfs_rtblock_t end_rtb; + uint64_t eofs; + xfs_rgnumber_t start_rg, end_rg; + int error = 0; + + eofs = XFS_FSB_TO_BB(mp, xfs_rtx_to_rtb(mp, mp->m_sb.sb_rextents)); + if (keys[0].fcr_physical >= eofs) + return 0; + start_rtb = XFS_BB_TO_FSBT(mp, keys[0].fcr_physical); + end_rtb = XFS_BB_TO_FSB(mp, min(eofs - 1, keys[1].fcr_physical)); + + info->refc_cur = NULL; + + /* + * Convert the fsrefs low/high keys to rtgroup based keys. Initialize + * low to the fsrefs low key and max out the high key to the end of the + * rtgroup. + */ + info->low.rc_startblock = xfs_rtb_to_rgbno(mp, start_rtb, &start_rg); + info->low.rc_blockcount = XFS_BB_TO_FSBT(mp, keys[0].fcr_length); + info->low.rc_refcount = 0; + info->low.rc_domain = XFS_REFC_DOMAIN_SHARED; + + /* Adjust the low key if we are continuing from where we left off. */ + if (info->low.rc_blockcount > 0) { + info->low.rc_startblock += info->low.rc_blockcount; + + start_rtb += info->low.rc_blockcount; + if (xfs_rtb_to_daddr(mp, start_rtb) >= eofs) + return 0; + } + + info->high.rc_startblock = -1U; + info->high.rc_blockcount = 0; + info->high.rc_refcount = 0; + info->high.rc_domain = XFS_REFC_DOMAIN_SHARED; + + end_rg = xfs_rtb_to_rgno(mp, end_rtb); + + for_each_rtgroup_range(mp, start_rg, end_rg, rtg) { + /* + * Set the rtgroup high key from the fsrefs high key if this + * is the last rtgroup that we're querying. + */ + info->rtg = rtg; + if (rtg->rtg_rgno == end_rg) { + xfs_rgnumber_t junk; + + info->high.rc_startblock = xfs_rtb_to_rgbno(mp, + end_rtb, &junk); + } + + if (info->refc_cur) { + xfs_rtgroup_unlock(info->refc_cur->bc_ino.rtg, + XFS_RTGLOCK_FSREFS); + xfs_btree_del_cursor(info->refc_cur, XFS_BTREE_NOERROR); + info->refc_cur = NULL; + } + + trace_xfs_fsrefs_low_key(mp, info->dev, rtg->rtg_rgno, + &info->low); + trace_xfs_fsrefs_high_key(mp, info->dev, rtg->rtg_rgno, + &info->high); + + xfs_rtgroup_lock(NULL, rtg, XFS_RTGLOCK_FSREFS); + info->refc_cur = xfs_rtrefcountbt_init_cursor(mp, tp, rtg, + rtg->rtg_refcountip); + + /* + * Fill the query with refcount records and synthesize + * singly-owned block records from free space data. + */ + error = xfs_refcount_query_range(info->refc_cur, + &info->low, &info->high, + xfs_fsrefs_rtdev_refcountbt_helper, info); + if (error && error != -ECANCELED) + break; + + /* + * Set the rtgroup low key to the start of the rtgroup prior to + * moving on to the next rtgroup. + */ + if (rtg->rtg_rgno == start_rg) + memset(&info->low, 0, sizeof(info->low)); + + /* + * If this is the last rtgroup, report any gap at the end of it + * before we drop the reference to the perag when the loop + * terminates. + */ + if (rtg->rtg_rgno == end_rg) { + xfs_rtblock_t next_rtbno; + + info->last = true; + next_rtbno = XFS_BB_TO_FSBT(mp, info->next_daddr); + error = xfs_fsrefs_rtdev_fill_refcount_gap(tp, info, + next_rtbno, end_rtb); + if (error) + break; + } + info->rtg = NULL; + } + + if (info->refc_cur) { + xfs_rtgroup_unlock(info->refc_cur->bc_ino.rtg, + XFS_RTGLOCK_FSREFS); + xfs_btree_del_cursor(info->refc_cur, error); + info->refc_cur = NULL; + } + if (info->rtg) { + xfs_rtgroup_rele(info->rtg); + info->rtg = NULL; + } else if (rtg) { + /* loop termination case */ + xfs_rtgroup_rele(rtg); + } + + return error; +} +#endif + +/* Do we recognize the device? */ +STATIC bool +xfs_fsrefs_is_valid_device( + struct xfs_mount *mp, + struct xfs_fsrefs *fcr) +{ + if (fcr->fcr_device == 0 || fcr->fcr_device == UINT_MAX || + fcr->fcr_device == new_encode_dev(mp->m_ddev_targp->bt_dev)) + return true; + if (mp->m_logdev_targp && + fcr->fcr_device == new_encode_dev(mp->m_logdev_targp->bt_dev)) + return true; + if (mp->m_rtdev_targp && + fcr->fcr_device == new_encode_dev(mp->m_rtdev_targp->bt_dev)) + return true; + return false; +} + +/* Ensure that the low key is less than the high key. */ +STATIC bool +xfs_fsrefs_check_keys( + struct xfs_fsrefs *low_key, + struct xfs_fsrefs *high_key) +{ + if (low_key->fcr_device > high_key->fcr_device) + return false; + if (low_key->fcr_device < high_key->fcr_device) + return true; + + if (low_key->fcr_physical > high_key->fcr_physical) + return false; + if (low_key->fcr_physical < high_key->fcr_physical) + return true; + + return false; +} + +/* + * There are only two devices if we didn't configure RT devices at build time. + */ +#ifdef CONFIG_XFS_RT +#define XFS_GETFSREFS_DEVS 3 +#else +#define XFS_GETFSREFS_DEVS 2 +#endif /* CONFIG_XFS_RT */ + +/* + * Get filesystem's extent refcounts as described in head, and format for + * output. Fills in the supplied records array until there are no more reverse + * mappings to return or head.fch_entries == head.fch_count. In the second + * case, this function returns -ECANCELED to indicate that more records would + * have been returned. + * + * Key to Confusion + * ---------------- + * There are multiple levels of keys and counters at work here: + * xfs_fsrefs_head.fch_keys -- low and high fsrefs keys passed in; + * these reflect fs-wide sector addrs. + * dkeys -- fch_keys used to query each device; + * these are fch_keys but w/ the low key + * bumped up by fcr_length. + * xfs_fsrefs_info.next_daddr-- next disk addr we expect to see; this + * is how we detect gaps in the fsrefs + * records and report them. + * xfs_fsrefs_info.low/high -- per-AG low/high keys computed from + * dkeys; used to query the metadata. + */ +int +xfs_getfsrefs( + struct xfs_mount *mp, + struct xfs_fsrefs_head *head, + struct xfs_getfsrefs *fsrefs_recs) +{ + struct xfs_trans *tp = NULL; + struct xfs_fsrefs dkeys[2]; /* per-dev keys */ + struct xfs_fsrefs_dev handlers[XFS_GETFSREFS_DEVS]; + struct xfs_fsrefs_info info = { NULL }; + int i; + int error = 0; + + if (head->fch_iflags & ~FCH_IF_VALID) + return -EINVAL; + if (!xfs_fsrefs_is_valid_device(mp, &head->fch_keys[0]) || + !xfs_fsrefs_is_valid_device(mp, &head->fch_keys[1])) + return -EINVAL; + if (!xfs_fsrefs_check_keys(&head->fch_keys[0], &head->fch_keys[1])) + return -EINVAL; + + head->fch_entries = 0; + + /* Set up our device handlers. */ + memset(handlers, 0, sizeof(handlers)); + handlers[0].dev = new_encode_dev(mp->m_ddev_targp->bt_dev); + handlers[0].fn = xfs_fsrefs_ddev; + if (mp->m_logdev_targp != mp->m_ddev_targp) { + handlers[1].dev = new_encode_dev(mp->m_logdev_targp->bt_dev); + handlers[1].fn = xfs_fsrefs_logdev; + } +#ifdef CONFIG_XFS_RT + if (mp->m_rtdev_targp) { + handlers[2].dev = new_encode_dev(mp->m_rtdev_targp->bt_dev); + if (xfs_has_rtreflink(mp)) + handlers[2].fn = xfs_fsrefs_rtdev; + else + handlers[2].fn = xfs_fsrefs_rtdev_rtbitmap; + } +#endif /* CONFIG_XFS_RT */ + + xfs_sort(handlers, XFS_GETFSREFS_DEVS, sizeof(struct xfs_fsrefs_dev), + xfs_fsrefs_dev_compare); + + /* + * To continue where we left off, we allow userspace to use the last + * mapping from a previous call as the low key of the next. This is + * identified by a non-zero length in the low key. We have to increment + * the low key in this scenario to ensure we don't return the same + * mapping again, and instead return the very next mapping. Bump the + * physical offset as there can be no other mapping for the same + * physical block range. + * + * Each fsrefs backend is responsible for making this adjustment as + * appropriate for the backend. + */ + dkeys[0] = head->fch_keys[0]; + memset(&dkeys[1], 0xFF, sizeof(struct xfs_fsrefs)); + + info.next_daddr = head->fch_keys[0].fcr_physical + + head->fch_keys[0].fcr_length; + info.fsrefs_recs = fsrefs_recs; + info.head = head; + + /* For each device we support... */ + for (i = 0; i < XFS_GETFSREFS_DEVS; i++) { + /* Is this device within the range the user asked for? */ + if (!handlers[i].fn) + continue; + if (head->fch_keys[0].fcr_device > handlers[i].dev) + continue; + if (head->fch_keys[1].fcr_device < handlers[i].dev) + break; + + /* + * If this device number matches the high key, we have to pass + * the high key to the handler to limit the query results. If + * the device number exceeds the low key, zero out the low key + * so that we get everything from the beginning. + */ + if (handlers[i].dev == head->fch_keys[1].fcr_device) + dkeys[1] = head->fch_keys[1]; + if (handlers[i].dev > head->fch_keys[0].fcr_device) + memset(&dkeys[0], 0, sizeof(struct xfs_fsrefs)); + + /* + * Grab an empty transaction so that we can use its recursive + * buffer locking abilities to detect cycles in the refcountbt + * without deadlocking. + */ + error = xfs_trans_alloc_empty(mp, &tp); + if (error) + break; + + info.dev = handlers[i].dev; + info.last = false; + info.pag = NULL; + info.rtg = NULL; + info.low_daddr = -1ULL; + info.low.rc_blockcount = 0; + error = handlers[i].fn(tp, dkeys, &info); + if (error) + break; + xfs_trans_cancel(tp); + tp = NULL; + info.next_daddr = 0; + } + + if (tp) + xfs_trans_cancel(tp); + head->fch_oflags = FCH_OF_DEV_T; + return error; +} diff --git a/fs/xfs/xfs_fsrefs.h b/fs/xfs/xfs_fsrefs.h new file mode 100644 index 0000000000000..a27ae76cc2057 --- /dev/null +++ b/fs/xfs/xfs_fsrefs.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#ifndef __XFS_FSREFS_H__ +#define __XFS_FSREFS_H__ + +struct xfs_getfsrefs; + +/* internal fsrefs representation */ +struct xfs_fsrefs { + dev_t fcr_device; /* device id */ + uint32_t fcr_flags; /* mapping flags */ + uint64_t fcr_physical; /* device offset of segment */ + uint64_t fcr_owners; /* number of owners */ + xfs_filblks_t fcr_length; /* length of segment, blocks */ +}; + +struct xfs_fsrefs_head { + uint32_t fch_iflags; /* control flags */ + uint32_t fch_oflags; /* output flags */ + unsigned int fch_count; /* # of entries in array incl. input */ + unsigned int fch_entries; /* # of entries filled in (output). */ + + struct xfs_fsrefs fch_keys[2]; /* low and high keys */ +}; + +void xfs_fsrefs_to_internal(struct xfs_fsrefs *dest, struct xfs_getfsrefs *src); + +int xfs_getfsrefs(struct xfs_mount *mp, struct xfs_fsrefs_head *head, + struct xfs_getfsrefs *out_recs); + +#endif /* __XFS_FSREFS_H__ */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index d17b17e7eea1d..718cab5125883 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -31,6 +31,7 @@ #include "xfs_btree.h" #include <linux/fsmap.h> #include "xfs_fsmap.h" +#include "xfs_fsrefs.h" #include "scrub/xfs_scrub.h" #include "xfs_sb.h" #include "xfs_ag.h" @@ -1635,6 +1636,137 @@ xfs_ioc_getfsmap( return error; } +STATIC int +xfs_ioc_getfsrefcounts( + struct xfs_inode *ip, + struct xfs_getfsrefs_head __user *arg) +{ + struct xfs_fsrefs_head xhead = {0}; + struct xfs_getfsrefs_head head; + struct xfs_getfsrefs *recs; + unsigned int count; + __u32 last_flags = 0; + bool done = false; + int error; + + if (copy_from_user(&head, arg, sizeof(struct xfs_getfsrefs_head))) + return -EFAULT; + if (memchr_inv(head.fch_reserved, 0, sizeof(head.fch_reserved)) || + memchr_inv(head.fch_keys[0].fcr_reserved, 0, + sizeof(head.fch_keys[0].fcr_reserved)) || + memchr_inv(head.fch_keys[1].fcr_reserved, 0, + sizeof(head.fch_keys[1].fcr_reserved))) + return -EINVAL; + + /* + * Use an internal memory buffer so that we don't have to copy fsrefs + * data to userspace while holding locks. Start by trying to allocate + * up to 128k for the buffer, but fall back to a single page if needed. + */ + count = min_t(unsigned int, head.fch_count, + 131072 / sizeof(struct xfs_getfsrefs)); + recs = kvzalloc(count * sizeof(struct xfs_getfsrefs), GFP_KERNEL); + if (!recs) { + count = min_t(unsigned int, head.fch_count, + PAGE_SIZE / sizeof(struct xfs_getfsrefs)); + recs = kvzalloc(count * sizeof(struct xfs_getfsrefs), + GFP_KERNEL); + if (!recs) + return -ENOMEM; + } + + xhead.fch_iflags = head.fch_iflags; + xfs_fsrefs_to_internal(&xhead.fch_keys[0], &head.fch_keys[0]); + xfs_fsrefs_to_internal(&xhead.fch_keys[1], &head.fch_keys[1]); + + trace_xfs_getfsrefs_low_key(ip->i_mount, &xhead.fch_keys[0]); + trace_xfs_getfsrefs_high_key(ip->i_mount, &xhead.fch_keys[1]); + + head.fch_entries = 0; + do { + struct xfs_getfsrefs __user *user_recs; + struct xfs_getfsrefs *last_rec; + + user_recs = &arg->fch_recs[head.fch_entries]; + xhead.fch_entries = 0; + xhead.fch_count = min_t(unsigned int, count, + head.fch_count - head.fch_entries); + + /* Run query, record how many entries we got. */ + error = xfs_getfsrefs(ip->i_mount, &xhead, recs); + switch (error) { + case 0: + /* + * There are no more records in the result set. Copy + * whatever we got to userspace and break out. + */ + done = true; + break; + case -ECANCELED: + /* + * The internal memory buffer is full. Copy whatever + * records we got to userspace and go again if we have + * not yet filled the userspace buffer. + */ + error = 0; + break; + default: + goto out_free; + } + head.fch_entries += xhead.fch_entries; + head.fch_oflags = xhead.fch_oflags; + + /* + * If the caller wanted a record count or there aren't any + * new records to return, we're done. + */ + if (head.fch_count == 0 || xhead.fch_entries == 0) + break; + + /* Copy all the records we got out to userspace. */ + if (copy_to_user(user_recs, recs, + xhead.fch_entries * sizeof(struct xfs_getfsrefs))) { + error = -EFAULT; + goto out_free; + } + + /* Remember the last record flags we copied to userspace. */ + last_rec = &recs[xhead.fch_entries - 1]; + last_flags = last_rec->fcr_flags; + + /* Set up the low key for the next iteration. */ + xfs_fsrefs_to_internal(&xhead.fch_keys[0], last_rec); + trace_xfs_getfsrefs_low_key(ip->i_mount, &xhead.fch_keys[0]); + } while (!done && head.fch_entries < head.fch_count); + + /* + * If there are no more records in the query result set and we're not + * in counting mode, mark the last record returned with the LAST flag. + */ + if (done && head.fch_count > 0 && head.fch_entries > 0) { + struct xfs_getfsrefs __user *user_rec; + + last_flags |= FCR_OF_LAST; + user_rec = &arg->fch_recs[head.fch_entries - 1]; + + if (copy_to_user(&user_rec->fcr_flags, &last_flags, + sizeof(last_flags))) { + error = -EFAULT; + goto out_free; + } + } + + /* copy back header */ + if (copy_to_user(arg, &head, sizeof(struct xfs_getfsrefs_head))) { + error = -EFAULT; + goto out_free; + } + +out_free: + kmem_free(recs); + return error; +} + STATIC int xfs_ioc_scrub_metadata( struct file *file, @@ -2140,6 +2272,9 @@ xfs_file_ioctl( case FS_IOC_GETFSMAP: return xfs_ioc_getfsmap(ip, arg); + case XFS_IOC_GETFSREFCOUNTS: + return xfs_ioc_getfsrefcounts(ip, arg); + case XFS_IOC_SCRUBV_METADATA: return xfs_ioc_scrubv_metadata(filp, arg); case XFS_IOC_SCRUB_METADATA: diff --git a/fs/xfs/xfs_trace.c b/fs/xfs/xfs_trace.c index cb712873ce927..929d78b10ae70 100644 --- a/fs/xfs/xfs_trace.c +++ b/fs/xfs/xfs_trace.c @@ -47,6 +47,7 @@ #include "xfs_rtgroup.h" #include "xfs_rmap.h" #include "xfs_refcount.h" +#include "xfs_fsrefs.h" static inline void xfs_rmapbt_crack_agno_opdev( diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 531a522ac0f7a..9ea65cd65d4af 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -97,6 +97,7 @@ struct xfs_rtgroup; struct xfs_extent_free_item; struct xfs_rmap_intent; struct xfs_refcount_intent; +struct xfs_fsrefs; #define XFS_ATTR_FILTER_FLAGS \ { XFS_ATTR_ROOT, "ROOT" }, \ @@ -4212,6 +4213,104 @@ DEFINE_GETFSMAP_EVENT(xfs_getfsmap_low_key); DEFINE_GETFSMAP_EVENT(xfs_getfsmap_high_key); DEFINE_GETFSMAP_EVENT(xfs_getfsmap_mapping); +/* fsrefs traces */ +DECLARE_EVENT_CLASS(xfs_fsrefs_class, + TP_PROTO(struct xfs_mount *mp, u32 keydev, xfs_agnumber_t agno, + const struct xfs_refcount_irec *refc), + TP_ARGS(mp, keydev, agno, refc), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(dev_t, keydev) + __field(xfs_agnumber_t, agno) + __field(xfs_agblock_t, bno) + __field(xfs_extlen_t, len) + __field(uint64_t, owners) + ), + TP_fast_assign( + __entry->dev = mp->m_super->s_dev; + __entry->keydev = new_decode_dev(keydev); + __entry->agno = agno; + __entry->bno = refc->rc_startblock; + __entry->len = refc->rc_blockcount; + __entry->owners = refc->rc_refcount; + ), + TP_printk("dev %d:%d keydev %d:%d agno 0x%x refcbno 0x%x fsbcount 0x%x owners %llu", + MAJOR(__entry->dev), MINOR(__entry->dev), + MAJOR(__entry->keydev), MINOR(__entry->keydev), + __entry->agno, + __entry->bno, + __entry->len, + __entry->owners) +) +#define DEFINE_FSREFS_EVENT(name) \ +DEFINE_EVENT(xfs_fsrefs_class, name, \ + TP_PROTO(struct xfs_mount *mp, u32 keydev, xfs_agnumber_t agno, \ + const struct xfs_refcount_irec *refc), \ + TP_ARGS(mp, keydev, agno, refc)) +DEFINE_FSREFS_EVENT(xfs_fsrefs_low_key); +DEFINE_FSREFS_EVENT(xfs_fsrefs_high_key); +DEFINE_FSREFS_EVENT(xfs_fsrefs_mapping); + +DECLARE_EVENT_CLASS(xfs_fsrefs_linear_class, + TP_PROTO(struct xfs_mount *mp, u32 keydev, uint64_t bno), + TP_ARGS(mp, keydev, bno), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(dev_t, keydev) + __field(xfs_fsblock_t, bno) + ), + TP_fast_assign( + __entry->dev = mp->m_super->s_dev; + __entry->keydev = new_decode_dev(keydev); + __entry->bno = bno; + ), + TP_printk("dev %d:%d keydev %d:%d bno 0x%llx", + MAJOR(__entry->dev), MINOR(__entry->dev), + MAJOR(__entry->keydev), MINOR(__entry->keydev), + __entry->bno) +) +#define DEFINE_FSREFS_LINEAR_EVENT(name) \ +DEFINE_EVENT(xfs_fsrefs_linear_class, name, \ + TP_PROTO(struct xfs_mount *mp, u32 keydev, uint64_t bno), \ + TP_ARGS(mp, keydev, bno)) +DEFINE_FSREFS_LINEAR_EVENT(xfs_fsrefs_low_key_linear); +DEFINE_FSREFS_LINEAR_EVENT(xfs_fsrefs_high_key_linear); + +DECLARE_EVENT_CLASS(xfs_getfsrefs_class, + TP_PROTO(struct xfs_mount *mp, struct xfs_fsrefs *fsrefs), + TP_ARGS(mp, fsrefs), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(dev_t, keydev) + __field(xfs_daddr_t, block) + __field(xfs_daddr_t, len) + __field(uint64_t, owners) + __field(uint32_t, flags) + ), + TP_fast_assign( + __entry->dev = mp->m_super->s_dev; + __entry->keydev = new_decode_dev(fsrefs->fcr_device); + __entry->block = fsrefs->fcr_physical; + __entry->len = fsrefs->fcr_length; + __entry->owners = fsrefs->fcr_owners; + __entry->flags = fsrefs->fcr_flags; + ), + TP_printk("dev %d:%d keydev %d:%d daddr 0x%llx bbcount 0x%llx owners %llu flags 0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + MAJOR(__entry->keydev), MINOR(__entry->keydev), + __entry->block, + __entry->len, + __entry->owners, + __entry->flags) +) +#define DEFINE_GETFSREFS_EVENT(name) \ +DEFINE_EVENT(xfs_getfsrefs_class, name, \ + TP_PROTO(struct xfs_mount *mp, struct xfs_fsrefs *fsrefs), \ + TP_ARGS(mp, fsrefs)) +DEFINE_GETFSREFS_EVENT(xfs_getfsrefs_low_key); +DEFINE_GETFSREFS_EVENT(xfs_getfsrefs_high_key); +DEFINE_GETFSREFS_EVENT(xfs_getfsrefs_mapping); + DECLARE_EVENT_CLASS(xfs_trans_resv_class, TP_PROTO(struct xfs_mount *mp, unsigned int type, struct xfs_trans_res *res), ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 4/5] xfs: defragment free space 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (2 preceding siblings ...) 2023-12-31 19:39 ` [PATCHSET 3/5] xfs: report refcount information to userspace Darrick J. Wong @ 2023-12-31 19:39 ` Darrick J. Wong 2023-12-31 22:02 ` [PATCH 1/2] xfs: capture the offset and length in fallocate tracepoints Darrick J. Wong 2023-12-31 22:02 ` [PATCH 2/2] xfs: add an ioctl to map free space into a file Darrick J. Wong 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong ` (5 subsequent siblings) 9 siblings, 2 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:39 UTC (permalink / raw) To: djwong; +Cc: linux-xfs Hi all, These patches contain experimental code to enable userspace to defragment the free space in a filesystem. Two purposes are imagined for this functionality: clearing space at the end of a filesystem before shrinking it, and clearing free space in anticipation of making a large allocation. The first patch adds a new fallocate mode that allows userspace to allocate free space from the filesystem into a file. The goal here is to allow the filesystem shrink process to prevent allocation from a certain part of the filesystem while a free space defragmenter evacuates all the files from the doomed part of the filesystem. The second patch amends the online repair system to allow the sysadmin to forcibly rebuild metadata structures, even if they're not corrupt. Without adding an ioctl to move metadata btree blocks, this is the only way to dislodge metadata. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=defrag-freespace xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=defrag-freespace fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=defrag-freespace --- fs/xfs/libxfs/xfs_alloc.c | 88 +++++++++++ fs/xfs/libxfs/xfs_alloc.h | 3 fs/xfs/libxfs/xfs_bmap.c | 150 +++++++++++++++++++ fs/xfs/libxfs/xfs_bmap.h | 3 fs/xfs/libxfs/xfs_fs.h | 1 fs/xfs/libxfs/xfs_fs_staging.h | 15 ++ fs/xfs/xfs_bmap_util.c | 319 +++++++++++++++++++++++++++++++++++++++- fs/xfs/xfs_bmap_util.h | 3 fs/xfs/xfs_file.c | 79 ++++++++++ fs/xfs/xfs_file.h | 2 fs/xfs/xfs_ioctl.c | 68 +++++++++ fs/xfs/xfs_rtalloc.c | 52 +++++++ fs/xfs/xfs_rtalloc.h | 12 +- fs/xfs/xfs_trace.h | 72 ++++++++- 14 files changed, 856 insertions(+), 11 deletions(-) ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/2] xfs: capture the offset and length in fallocate tracepoints 2023-12-31 19:39 ` [PATCHSET 4/5] xfs: defragment free space Darrick J. Wong @ 2023-12-31 22:02 ` Darrick J. Wong 2023-12-31 22:02 ` [PATCH 2/2] xfs: add an ioctl to map free space into a file Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:02 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Change the class of the fallocate tracepoints to capture the offset and length of the requested operation. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/xfs_bmap_util.c | 8 ++++---- fs/xfs/xfs_file.c | 2 +- fs/xfs/xfs_trace.h | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index a3b987fafcce6..72abd62d40607 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -826,7 +826,7 @@ xfs_alloc_file_space( xfs_bmbt_irec_t imaps[1], *imapp; int error; - trace_xfs_alloc_file_space(ip); + trace_xfs_alloc_file_space(ip, offset, len); if (xfs_is_shutdown(mp)) return -EIO; @@ -1014,7 +1014,7 @@ xfs_free_file_space( xfs_fileoff_t endoffset_fsb; int done = 0, error; - trace_xfs_free_file_space(ip); + trace_xfs_free_file_space(ip, offset, len); error = xfs_qm_dqattach(ip); if (error) @@ -1152,7 +1152,7 @@ xfs_collapse_file_space( ASSERT(xfs_isilocked(ip, XFS_IOLOCK_EXCL)); ASSERT(xfs_isilocked(ip, XFS_MMAPLOCK_EXCL)); - trace_xfs_collapse_file_space(ip); + trace_xfs_collapse_file_space(ip, offset, len); error = xfs_free_file_space(ip, offset, len); if (error) @@ -1222,7 +1222,7 @@ xfs_insert_file_space( ASSERT(xfs_isilocked(ip, XFS_IOLOCK_EXCL)); ASSERT(xfs_isilocked(ip, XFS_MMAPLOCK_EXCL)); - trace_xfs_insert_file_space(ip); + trace_xfs_insert_file_space(ip, offset, len); error = xfs_bmap_can_insert_extents(ip, stop_fsb, shift_fsb); if (error) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index f2dd4daaa4e24..36ccec461e2a4 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1150,7 +1150,7 @@ xfs_file_fallocate( */ unsigned int blksize = i_blocksize(inode); - trace_xfs_zero_file_space(ip); + trace_xfs_zero_file_space(ip, offset, len); /* Unshare around the region to zero, if needed. */ if (xfs_file_write_needs_cow_around(ip, offset, len)) { diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 9ea65cd65d4af..7b3abcabbafb6 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -831,11 +831,6 @@ DEFINE_INODE_EVENT(xfs_getattr); DEFINE_INODE_EVENT(xfs_setattr); DEFINE_INODE_EVENT(xfs_readlink); DEFINE_INODE_EVENT(xfs_inactive_symlink); -DEFINE_INODE_EVENT(xfs_alloc_file_space); -DEFINE_INODE_EVENT(xfs_free_file_space); -DEFINE_INODE_EVENT(xfs_zero_file_space); -DEFINE_INODE_EVENT(xfs_collapse_file_space); -DEFINE_INODE_EVENT(xfs_insert_file_space); DEFINE_INODE_EVENT(xfs_readdir); #ifdef CONFIG_XFS_POSIX_ACL DEFINE_INODE_EVENT(xfs_get_acl); @@ -1629,6 +1624,11 @@ DEFINE_SIMPLE_IO_EVENT(xfs_end_io_direct_write); DEFINE_SIMPLE_IO_EVENT(xfs_end_io_direct_write_unwritten); DEFINE_SIMPLE_IO_EVENT(xfs_end_io_direct_write_append); DEFINE_SIMPLE_IO_EVENT(xfs_file_splice_read); +DEFINE_SIMPLE_IO_EVENT(xfs_alloc_file_space); +DEFINE_SIMPLE_IO_EVENT(xfs_free_file_space); +DEFINE_SIMPLE_IO_EVENT(xfs_zero_file_space); +DEFINE_SIMPLE_IO_EVENT(xfs_collapse_file_space); +DEFINE_SIMPLE_IO_EVENT(xfs_insert_file_space); DECLARE_EVENT_CLASS(xfs_itrunc_class, TP_PROTO(struct xfs_inode *ip, xfs_fsize_t new_size), ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/2] xfs: add an ioctl to map free space into a file 2023-12-31 19:39 ` [PATCHSET 4/5] xfs: defragment free space Darrick J. Wong 2023-12-31 22:02 ` [PATCH 1/2] xfs: capture the offset and length in fallocate tracepoints Darrick J. Wong @ 2023-12-31 22:02 ` Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:02 UTC (permalink / raw) To: djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add a new ioctl to map free physical space into a file, at the same file offset as if the file were a sparse image of the physical device backing the filesystem. The intent here is to use this to prototype a free space defragmentation tool. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_alloc.c | 88 +++++++++++ fs/xfs/libxfs/xfs_alloc.h | 3 fs/xfs/libxfs/xfs_bmap.c | 150 +++++++++++++++++++ fs/xfs/libxfs/xfs_bmap.h | 3 fs/xfs/libxfs/xfs_fs.h | 1 fs/xfs/libxfs/xfs_fs_staging.h | 15 ++ fs/xfs/xfs_bmap_util.c | 311 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_bmap_util.h | 3 fs/xfs/xfs_file.c | 77 ++++++++++ fs/xfs/xfs_file.h | 2 fs/xfs/xfs_ioctl.c | 68 +++++++++ fs/xfs/xfs_rtalloc.c | 52 +++++++ fs/xfs/xfs_rtalloc.h | 12 +- fs/xfs/xfs_trace.h | 62 ++++++++ 14 files changed, 846 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index cf56784aabbbc..dec07573b307e 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -4096,3 +4096,91 @@ xfs_extfree_intent_destroy_cache(void) kmem_cache_destroy(xfs_extfree_item_cache); xfs_extfree_item_cache = NULL; } + +/* + * Find the next chunk of free space in @pag starting at @agbno and going no + * higher than @end_agbno. Set @agbno and @len to whatever free space we find, + * or to @end_agbno if we find no space. + */ +int +xfs_alloc_find_freesp( + struct xfs_trans *tp, + struct xfs_perag *pag, + xfs_agblock_t *agbno, + xfs_agblock_t end_agbno, + xfs_extlen_t *len) +{ + struct xfs_mount *mp = pag->pag_mount; + struct xfs_btree_cur *cur; + struct xfs_buf *agf_bp = NULL; + xfs_agblock_t found_agbno; + xfs_extlen_t found_len; + int found; + int error; + + trace_xfs_alloc_find_freesp(mp, pag->pag_agno, *agbno, + end_agbno - *agbno); + + error = xfs_alloc_read_agf(pag, tp, 0, &agf_bp); + if (error) + return error; + + cur = xfs_allocbt_init_cursor(mp, tp, agf_bp, pag, XFS_BTNUM_BNO); + + /* Try to find a free extent that starts before here. */ + error = xfs_alloc_lookup_le(cur, *agbno, 0, &found); + if (error) + goto out_cur; + if (found) { + error = xfs_alloc_get_rec(cur, &found_agbno, &found_len, + &found); + if (error) + goto out_cur; + if (XFS_IS_CORRUPT(mp, !found)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto out_cur; + } + + if (found_agbno + found_len > *agbno) + goto found; + } + + /* Examine the next record if free extent not in range. */ + error = xfs_btree_increment(cur, 0, &found); + if (error) + goto out_cur; + if (!found) + goto next_ag; + + error = xfs_alloc_get_rec(cur, &found_agbno, &found_len, &found); + if (error) + goto out_cur; + if (XFS_IS_CORRUPT(mp, !found)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto out_cur; + } + + if (found_agbno >= end_agbno) + goto next_ag; + +found: + /* Found something, so update the mapping. */ + trace_xfs_alloc_find_freesp_done(mp, pag->pag_agno, found_agbno, + found_len); + if (found_agbno < *agbno) { + found_len -= *agbno - found_agbno; + found_agbno = *agbno; + } + *len = found_len; + *agbno = found_agbno; + goto out_cur; +next_ag: + /* Found nothing, so advance the cursor beyond the end of the range. */ + *agbno = end_agbno; + *len = 0; +out_cur: + xfs_btree_del_cursor(cur, error); + return error; +} diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h index 130026e981ea2..fedb6dc0443e5 100644 --- a/fs/xfs/libxfs/xfs_alloc.h +++ b/fs/xfs/libxfs/xfs_alloc.h @@ -290,5 +290,8 @@ void xfs_extfree_intent_destroy_cache(void); xfs_failaddr_t xfs_validate_ag_length(struct xfs_buf *bp, uint32_t seqno, uint32_t length); +int xfs_alloc_find_freesp(struct xfs_trans *tp, struct xfs_perag *pag, + xfs_agblock_t *agbno, xfs_agblock_t end_agbno, + xfs_extlen_t *len); #endif /* __XFS_ALLOC_H__ */ diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index c6ab6a38bc7cd..7d0d82353a745 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -40,6 +40,7 @@ #include "xfs_bmap_item.h" #include "xfs_symlink_remote.h" #include "xfs_inode_util.h" +#include "xfs_rtalloc.h" struct kmem_cache *xfs_bmap_intent_cache; @@ -6442,3 +6443,152 @@ xfs_get_cowextsz_hint( return XFS_DEFAULT_COWEXTSZ_HINT; return a; } + +static inline xfs_fileoff_t +xfs_fsblock_to_fileoff( + struct xfs_mount *mp, + xfs_fsblock_t fsbno) +{ + xfs_daddr_t daddr = XFS_FSB_TO_DADDR(mp, fsbno); + + return XFS_B_TO_FSB(mp, BBTOB(daddr)); +} + +/* + * Given a file and a free physical extent, map it into the file at the same + * offset if the file were a sparse image of the physical device. Set @mval to + * whatever mapping we added to the file. + */ +int +xfs_bmapi_freesp( + struct xfs_trans *tp, + struct xfs_inode *ip, + xfs_fsblock_t fsbno, + xfs_extlen_t len, + struct xfs_bmbt_irec *mval) +{ + struct xfs_bmbt_irec irec; + struct xfs_mount *mp = ip->i_mount; + xfs_fileoff_t startoff; + bool isrt = XFS_IS_REALTIME_INODE(ip); + int nimaps; + int error; + + trace_xfs_bmapi_freesp(ip, fsbno, len); + + error = xfs_iext_count_may_overflow(ip, XFS_DATA_FORK, + XFS_IEXT_ADD_NOSPLIT_CNT); + if (error) + return error; + + if (isrt) + startoff = fsbno; + else + startoff = xfs_fsblock_to_fileoff(mp, fsbno); + + /* Make sure the entire range is a hole. */ + nimaps = 1; + error = xfs_bmapi_read(ip, startoff, len, &irec, &nimaps, 0); + if (error) + return error; + + if (irec.br_startoff != startoff || + irec.br_startblock != HOLESTARTBLOCK || + irec.br_blockcount < len) + return -EINVAL; + + /* + * Allocate the physical extent. We should not have dropped the lock + * since the scan of the free space metadata, so this should work, + * though the length may be adjusted to play nicely with metadata space + * reservations. + */ + if (isrt) { + xfs_rtxnum_t rtx_in, rtx_out; + xfs_extlen_t rtxlen_in, rtxlen_out; + uint32_t mod; + + rtx_in = xfs_rtb_to_rtxrem(mp, fsbno, &mod); + if (mod) { + ASSERT(mod == 0); + return -EFSCORRUPTED; + } + + rtxlen_in = xfs_rtb_to_rtxrem(mp, len, &mod); + if (mod) { + ASSERT(mod == 0); + return -EFSCORRUPTED; + } + + error = xfs_rtallocate_extent(tp, rtx_in, 1, rtxlen_in, + &rtxlen_out, 0, 1, &rtx_out); + if (error) + return error; + if (rtx_out == NULLRTEXTNO) { + /* + * We were promised the space! In theory there aren't + * any reserve lists that would prevent us from getting + * the space. + */ + return -ENOSPC; + } + if (rtx_out != rtx_in) { + ASSERT(0); + xfs_bmap_mark_sick(ip, XFS_DATA_FORK); + return -EFSCORRUPTED; + } + mval->br_blockcount = rtxlen_out * mp->m_sb.sb_rextsize; + } else { + struct xfs_alloc_arg args = { + .mp = mp, + .tp = tp, + .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE, + .resv = XFS_AG_RESV_NONE, + .prod = 1, + .datatype = XFS_ALLOC_USERDATA, + .maxlen = len, + .minlen = 1, + }; + args.pag = xfs_perag_get(mp, XFS_FSB_TO_AGNO(mp, fsbno)); + error = xfs_alloc_vextent_exact_bno(&args, fsbno); + xfs_perag_put(args.pag); + if (error) + return error; + if (args.fsbno == NULLFSBLOCK) { + /* + * We were promised the space, but failed to get it. + * This could be because the space is reserved for + * metadata expansion, or it could be because the AGFL + * fixup grabbed the first block we wanted. Either + * way, if the transaction is dirty we must commit it + * and tell the caller to try again. + */ + if (tp->t_flags & XFS_TRANS_DIRTY) + return -EAGAIN; + return -ENOSPC; + } + if (args.fsbno != fsbno) { + ASSERT(0); + xfs_bmap_mark_sick(ip, XFS_DATA_FORK); + return -EFSCORRUPTED; + } + mval->br_blockcount = args.len; + } + + /* Map extent into file, update quota. */ + mval->br_startblock = fsbno; + mval->br_startoff = startoff; + mval->br_state = XFS_EXT_UNWRITTEN; + + trace_xfs_bmapi_freesp_done(ip, mval); + + xfs_bmap_map_extent(tp, ip, XFS_DATA_FORK, mval); + if (isrt) + xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_RTBCOUNT, + mval->br_blockcount); + else + xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_BCOUNT, + mval->br_blockcount); + + return 0; +} diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index 61c195198db43..afb54a517f1ad 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -197,6 +197,9 @@ int xfs_bmapi_read(struct xfs_inode *ip, xfs_fileoff_t bno, int xfs_bmapi_write(struct xfs_trans *tp, struct xfs_inode *ip, xfs_fileoff_t bno, xfs_filblks_t len, uint32_t flags, xfs_extlen_t total, struct xfs_bmbt_irec *mval, int *nmap); +int xfs_bmapi_freesp(struct xfs_trans *tp, struct xfs_inode *ip, + xfs_fsblock_t fsbno, xfs_extlen_t len, + struct xfs_bmbt_irec *mval); int xfs_bunmapi(struct xfs_trans *tp, struct xfs_inode *ip, xfs_fileoff_t bno, xfs_filblks_t len, uint32_t flags, xfs_extnum_t nexts, int *done); diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h index 96688f9301e08..922e9acfdc3c7 100644 --- a/fs/xfs/libxfs/xfs_fs.h +++ b/fs/xfs/libxfs/xfs_fs.h @@ -864,6 +864,7 @@ struct xfs_scrub_metadata { #define XFS_IOC_AG_GEOMETRY _IOWR('X', 61, struct xfs_ag_geometry) /* XFS_IOC_GETPARENTS ---- staging 62 */ /* XFS_IOC_RTGROUP_GEOMETRY - staging 63 */ +/* XFS_IOC_MAP_FREESP ---- staging 64 */ /* * ioctl commands that replace IRIX syssgi()'s diff --git a/fs/xfs/libxfs/xfs_fs_staging.h b/fs/xfs/libxfs/xfs_fs_staging.h index f7d872f8a8837..2a9effb7a846c 100644 --- a/fs/xfs/libxfs/xfs_fs_staging.h +++ b/fs/xfs/libxfs/xfs_fs_staging.h @@ -303,4 +303,19 @@ xfs_getfsrefs_advance( /* XXX stealing XFS_IOC_GETBIOSIZE */ #define XFS_IOC_GETFSREFCOUNTS _IOWR('X', 47, struct xfs_getfsrefs_head) +/* map free space to file */ + +struct xfs_map_freesp { + __s64 offset; /* disk address to map, in bytes */ + __s64 len; /* length in bytes */ + __u64 flags; /* must be zero */ + __u64 pad; /* must be zero */ +}; + +/* + * XFS_IOC_MAP_FREESP maps all the free physical space in the filesystem into + * the file at the same offsets. This ioctl requires CAP_SYS_ADMIN. + */ +#define XFS_IOC_MAP_FREESP _IOWR('X', 64, struct xfs_map_freesp) + #endif /* __XFS_FS_STAGING_H__ */ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 72abd62d40607..911838fbecbfb 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -30,6 +30,12 @@ #include "xfs_reflink.h" #include "xfs_rtbitmap.h" #include "xfs_swapext.h" +#include "xfs_rtrmap_btree.h" +#include "xfs_rtrefcount_btree.h" +#include "xfs_health.h" +#include "xfs_alloc_btree.h" +#include "xfs_rmap.h" +#include "xfs_ag.h" /* Kernel only BMAP related definitions and functions */ @@ -1457,3 +1463,308 @@ xfs_convert_bigalloc_file_space( return 0; } #endif /* CONFIG_XFS_RT */ + +/* + * Reserve space and quota to this transaction to map in as much free space + * as we can. Callers should set @len to the amount of space desired; this + * function will shorten that quantity if it can't get space. + */ +STATIC int +xfs_map_free_reserve_more( + struct xfs_trans *tp, + struct xfs_inode *ip, + xfs_extlen_t *len) +{ + struct xfs_mount *mp = ip->i_mount; + unsigned int dblocks; + unsigned int rblocks; + unsigned int min_len; + bool isrt = XFS_IS_REALTIME_INODE(ip); + int error; + + if (*len > XFS_MAX_BMBT_EXTLEN) + *len = XFS_MAX_BMBT_EXTLEN; + min_len = isrt ? mp->m_sb.sb_rextsize : 1; + +again: + if (isrt) { + dblocks = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK); + rblocks = *len; + } else { + dblocks = XFS_DIOSTRAT_SPACE_RES(mp, *len); + rblocks = 0; + } + error = xfs_trans_reserve_more_inode(tp, ip, dblocks, rblocks, false); + if (error == -ENOSPC && *len > min_len) { + *len >>= 1; + goto again; + } + if (error) { + trace_xfs_map_free_reserve_more_fail(ip, error, _RET_IP_); + return error; + } + + return 0; +} + +/* Find a free extent in this AG and map it into the file. */ +STATIC int +xfs_map_free_extent( + struct xfs_inode *ip, + struct xfs_perag *pag, + xfs_agblock_t *cursor, + xfs_agblock_t end_agbno, + xfs_agblock_t *last_enospc_agbno) +{ + struct xfs_bmbt_irec irec; + struct xfs_mount *mp = ip->i_mount; + struct xfs_trans *tp; + xfs_off_t endpos; + xfs_fsblock_t fsbno; + xfs_extlen_t free_len, map_len; + int error; + + if (fatal_signal_pending(current)) + return -EINTR; + + error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write, 0, 0, false, + &tp); + if (error) + return error; + + error = xfs_alloc_find_freesp(tp, pag, cursor, end_agbno, &free_len); + if (error) + goto out_cancel; + + /* Bail out if the cursor is beyond what we asked for. */ + if (*cursor >= end_agbno) + goto out_cancel; + + error = xfs_map_free_reserve_more(tp, ip, &free_len); + if (error) + goto out_cancel; + + fsbno = XFS_AGB_TO_FSB(mp, pag->pag_agno, *cursor); + map_len = free_len; + do { + error = xfs_bmapi_freesp(tp, ip, fsbno, map_len, &irec); + if (error == -EAGAIN) { + /* Failed to map space but were told to try again. */ + error = xfs_trans_commit(tp); + goto out; + } + if (error != -ENOSPC) + break; + /* + * If we can't get the space, try asking for successively less + * space in case we're bumping up against per-AG metadata + * reservation limits. + */ + map_len >>= 1; + } while (map_len > 0); + if (error == -ENOSPC) { + if (*last_enospc_agbno != *cursor) { + /* + * However, backing off on the size of the mapping + * request might not work if an AGFL fixup allocated + * the block at *cursor. The first time this happens, + * remember that we ran out of space here, and try + * again. + */ + *last_enospc_agbno = *cursor; + } else { + /* + * If we hit this a second time on the same extent, + * then it's likely that we're bumping up against + * per-AG space reservation limits. Skip to the next + * extent. + */ + *cursor += free_len; + } + error = 0; + goto out_cancel; + } + if (error) + goto out_cancel; + + /* Update isize if needed. */ + endpos = XFS_FSB_TO_B(mp, irec.br_startoff + irec.br_blockcount); + if (endpos > i_size_read(VFS_I(ip))) { + i_size_write(VFS_I(ip), endpos); + ip->i_disk_size = endpos; + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + } + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + if (error) + return error; + + *cursor += irec.br_blockcount; + return 0; +out_cancel: + xfs_trans_cancel(tp); +out: + xfs_iunlock(ip, XFS_ILOCK_EXCL); + return error; +} + +/* + * Allocate all free physical space between off and len and map it to this + * regular non-realtime file. + */ +int +xfs_map_free_space( + struct xfs_inode *ip, + xfs_off_t off, + xfs_off_t len) +{ + struct xfs_mount *mp = ip->i_mount; + struct xfs_perag *pag = NULL; + xfs_daddr_t off_daddr = BTOBB(off); + xfs_daddr_t end_daddr = BTOBBT(off + len); + xfs_fsblock_t off_fsb = XFS_DADDR_TO_FSB(mp, off_daddr); + xfs_fsblock_t end_fsb = XFS_DADDR_TO_FSB(mp, end_daddr); + xfs_agnumber_t off_agno = XFS_FSB_TO_AGNO(mp, off_fsb); + xfs_agnumber_t end_agno = XFS_FSB_TO_AGNO(mp, end_fsb); + xfs_agnumber_t agno; + int error = 0; + + trace_xfs_map_free_space(ip, off, len); + + agno = off_agno; + for_each_perag_range(mp, agno, end_agno, pag) { + xfs_agblock_t off_agbno = 0; + xfs_agblock_t end_agbno; + xfs_agblock_t last_enospc_agbno = NULLAGBLOCK; + + end_agbno = xfs_ag_block_count(mp, pag->pag_agno); + + if (pag->pag_agno == off_agno) + off_agbno = XFS_FSB_TO_AGBNO(mp, off_fsb); + if (pag->pag_agno == end_agno) + end_agbno = XFS_FSB_TO_AGBNO(mp, end_fsb); + + while (off_agbno < end_agbno) { + error = xfs_map_free_extent(ip, pag, &off_agbno, + end_agbno, &last_enospc_agbno); + if (error) + goto out; + } + } + +out: + if (pag) + xfs_perag_rele(pag); + if (error == -ENOSPC) + return 0; + return error; +} + +#ifdef CONFIG_XFS_RT +STATIC int +xfs_map_free_rt_extent( + struct xfs_inode *ip, + xfs_rtxnum_t *cursor, + xfs_rtxnum_t end_rtx) +{ + struct xfs_bmbt_irec irec; + struct xfs_mount *mp = ip->i_mount; + struct xfs_trans *tp; + xfs_off_t endpos; + xfs_rtblock_t rtbno; + xfs_rtxlen_t len_rtx; + xfs_extlen_t len; + int error; + + if (fatal_signal_pending(current)) + return -EINTR; + + error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write, 0, 0, false, + &tp); + if (error) + return error; + + xfs_rtbitmap_lock(tp, mp); + error = xfs_rtalloc_find_freesp(tp, cursor, end_rtx, &len_rtx); + if (error) + goto out_cancel; + + /* + * If off_rtx is beyond the end of the rt device or is past what the + * user asked for, bail out. + */ + if (*cursor >= end_rtx) + goto out_cancel; + + len = xfs_rtx_to_rtb(mp, len_rtx); + error = xfs_map_free_reserve_more(tp, ip, &len); + if (error) + goto out_cancel; + + rtbno = xfs_rtx_to_rtb(mp, *cursor); + error = xfs_bmapi_freesp(tp, ip, rtbno, len, &irec); + if (error) + goto out_cancel; + + /* Update isize if needed. */ + endpos = XFS_FSB_TO_B(mp, irec.br_startoff + irec.br_blockcount); + if (endpos > i_size_read(VFS_I(ip))) { + i_size_write(VFS_I(ip), endpos); + ip->i_disk_size = endpos; + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + } + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + if (error) + return error; + + ASSERT(xfs_rtb_to_rtxoff(mp, irec.br_blockcount) == 0); + *cursor += xfs_rtb_to_rtx(mp, irec.br_blockcount); + return 0; +out_cancel: + xfs_trans_cancel(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + return error; +} + +/* + * Allocate all free physical space between off and len and map it to this + * regular non-realtime file. + */ +int +xfs_map_free_rt_space( + struct xfs_inode *ip, + xfs_off_t off, + xfs_off_t len) +{ + struct xfs_mount *mp = ip->i_mount; + xfs_rtblock_t off_rtb = XFS_B_TO_FSB(mp, off); + xfs_rtblock_t end_rtb = XFS_B_TO_FSBT(mp, off + len); + xfs_rtxnum_t off_rtx; + xfs_rtxnum_t end_rtx; + int error = 0; + + /* Compute rt extents from the input parameters. */ + off_rtx = xfs_rtb_to_rtxup(mp, off_rtb); + end_rtx = xfs_rtb_to_rtx(mp, end_rtb); + + if (off_rtx >= mp->m_sb.sb_rextents) + return 0; + if (end_rtx >= mp->m_sb.sb_rextents) + end_rtx = mp->m_sb.sb_rextents - 1; + + trace_xfs_map_free_rt_space(ip, off, len); + + while (off_rtx < end_rtx) { + error = xfs_map_free_rt_extent(ip, &off_rtx, end_rtx); + if (error) + break; + } + + if (error == -ENOSPC) + return 0; + return error; +} +#endif diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index 231c4f1629c66..75d16202e2d34 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -61,6 +61,7 @@ int xfs_collapse_file_space(struct xfs_inode *, xfs_off_t offset, xfs_off_t len); int xfs_insert_file_space(struct xfs_inode *, xfs_off_t offset, xfs_off_t len); +int xfs_map_free_space(struct xfs_inode *ip, xfs_off_t off, xfs_off_t len); /* EOF block manipulation functions */ bool xfs_can_free_eofblocks(struct xfs_inode *ip, bool force); @@ -79,8 +80,10 @@ int xfs_flush_unmap_range(struct xfs_inode *ip, xfs_off_t offset, #ifdef CONFIG_XFS_RT int xfs_convert_bigalloc_file_space(struct xfs_inode *ip, loff_t pos, uint64_t len); +int xfs_map_free_rt_space(struct xfs_inode *ip, xfs_off_t off, xfs_off_t len); #else # define xfs_convert_bigalloc_file_space(ip, pos, len) (-EOPNOTSUPP) +# define xfs_map_free_rt_space(ip, off, len) (-EOPNOTSUPP) #endif #endif /* __XFS_BMAP_UTIL_H__ */ diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 36ccec461e2a4..78e53e4eafa58 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1240,6 +1240,83 @@ xfs_file_fallocate( return error; } +int +xfs_file_map_freesp( + struct file *file, + const struct xfs_map_freesp *mf) +{ + struct inode *inode = file_inode(file); + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + xfs_off_t device_size; + uint iolock = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL; + loff_t new_size = 0; + int error; + + xfs_ilock(ip, iolock); + error = xfs_break_layouts(inode, &iolock, BREAK_UNMAP); + if (error) + goto out_unlock; + + /* + * Must wait for all AIO to complete before we continue as AIO can + * change the file size on completion without holding any locks we + * currently hold. We must do this first because AIO can update both + * the on disk and in memory inode sizes, and the operations that follow + * require the in-memory size to be fully up-to-date. + */ + inode_dio_wait(inode); + + error = file_modified(file); + if (error) + goto out_unlock; + + if (XFS_IS_REALTIME_INODE(ip)) + device_size = XFS_FSB_TO_B(mp, mp->m_sb.sb_rblocks); + else + device_size = XFS_FSB_TO_B(mp, mp->m_sb.sb_dblocks); + + /* + * Bail out now if we aren't allowed to make the file size the + * same length as the device. + */ + if (device_size > i_size_read(inode)) { + new_size = device_size; + error = inode_newsize_ok(inode, new_size); + if (error) + goto out_unlock; + } + + if (XFS_IS_REALTIME_INODE(ip)) + error = xfs_map_free_rt_space(ip, mf->offset, mf->len); + else + error = xfs_map_free_space(ip, mf->offset, mf->len); + if (error) { + if (error == -ECANCELED) + error = 0; + goto out_unlock; + } + + /* Change file size if needed */ + if (new_size) { + struct iattr iattr; + + iattr.ia_valid = ATTR_SIZE; + iattr.ia_size = new_size; + error = xfs_vn_setattr_size(file_mnt_idmap(file), + file_dentry(file), &iattr); + if (error) + goto out_unlock; + } + + if (xfs_file_sync_writes(file)) + error = xfs_log_force_inode(ip); + +out_unlock: + xfs_iunlock(ip, iolock); + return error; +} + STATIC int xfs_file_fadvise( struct file *file, diff --git a/fs/xfs/xfs_file.h b/fs/xfs/xfs_file.h index 24490ea49e16c..41d8e17d29c18 100644 --- a/fs/xfs/xfs_file.h +++ b/fs/xfs/xfs_file.h @@ -15,4 +15,6 @@ bool xfs_is_falloc_aligned(struct xfs_inode *ip, loff_t pos, bool xfs_truncate_needs_cow_around(struct xfs_inode *ip, loff_t pos); int xfs_file_unshare_at(struct xfs_inode *ip, loff_t pos); +int xfs_file_map_freesp(struct file *file, const struct xfs_map_freesp *mf); + #endif /* __XFS_FILE_H__ */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 718cab5125883..ad289d37145e8 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -48,6 +48,8 @@ #include <linux/mount.h> #include <linux/namei.h> #include <linux/fileattr.h> +#include <linux/security.h> +#include <linux/fsnotify.h> /* * xfs_find_handle maps from userspace xfs_fsop_handlereq structure to @@ -2179,6 +2181,69 @@ xfs_ioc_exchange_range( return error; } +static int +xfs_ioc_map_freesp( + struct file *file, + struct xfs_map_freesp __user *argp) +{ + struct xfs_map_freesp args; + struct inode *inode = file_inode(file); + int error; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags || args.pad) + return -EINVAL; + + if (args.offset < 0 || args.len <= 0) + return -EINVAL; + + if (!(file->f_mode & FMODE_WRITE)) + return -EBADF; + + /* + * We can only allow pure fallocate on append only files + */ + if (IS_APPEND(inode)) + return -EPERM; + + if (IS_IMMUTABLE(inode)) + return -EPERM; + + /* + * We cannot allow any fallocate operation on an active swapfile + */ + if (IS_SWAPFILE(inode)) + return -ETXTBSY; + + if (S_ISFIFO(inode->i_mode)) + return -ESPIPE; + + if (S_ISDIR(inode->i_mode)) + return -EISDIR; + + if (!S_ISREG(inode->i_mode)) + return -ENODEV; + + /* Check for wrap through zero too */ + if (args.offset + args.len > inode->i_sb->s_maxbytes) + return -EFBIG; + if (args.offset + args.len < 0) + return -EFBIG; + + file_start_write(file); + error = xfs_file_map_freesp(file, &args); + if (!error) + fsnotify_modify(file); + + file_end_write(file); + return error; +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -2474,6 +2539,9 @@ xfs_file_ioctl( case XFS_IOC_EXCHANGE_RANGE: return xfs_ioc_exchange_range(filp, arg); + case XFS_IOC_MAP_FREESP: + return xfs_ioc_map_freesp(filp, arg); + default: return -ENOTTY; } diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 0af834897fad4..e59189e84943c 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -2134,3 +2134,55 @@ xfs_rtpick_extent( *pick = b; return 0; } + +/* + * Find the next free realtime extent starting at @rtx and going no higher than + * @end_rtx. Set @rtx and @len_rtx to whatever free extents we find, or to + * @end_rtx if we find no space. + */ +int +xfs_rtalloc_find_freesp( + struct xfs_trans *tp, + xfs_rtxnum_t *rtx, + xfs_rtxnum_t end_rtx, + xfs_rtxlen_t *len_rtx) +{ + struct xfs_mount *mp = tp->t_mountp; + struct xfs_rtalloc_args args = { + .mp = mp, + .tp = tp, + }; + unsigned int max_rt_extlen; + int error; + + trace_xfs_rtalloc_find_freesp(mp, *rtx, end_rtx - *rtx); + + max_rt_extlen = xfs_rtb_to_rtx(mp, XFS_MAX_BMBT_EXTLEN); + + while (*rtx < end_rtx) { + xfs_rtblock_t range_end_rtx; + int is_free = 0; + + /* Is the first block in the range free? */ + error = xfs_rtcheck_range(&args, *rtx, 1, 1, &range_end_rtx, + &is_free); + if (error) + return error; + + /* Free or not, how many more rtx have the same status? */ + error = xfs_rtfind_forw(&args, *rtx, end_rtx, &range_end_rtx); + if (error) + return error; + + if (is_free) { + trace_xfs_rtalloc_find_freesp_done(mp, *rtx, *len_rtx); + *len_rtx = min_t(xfs_rtblock_t, max_rt_extlen, + range_end_rtx - *rtx + 1); + return 0; + } + + *rtx = range_end_rtx + 1; + } + + return 0; +} diff --git a/fs/xfs/xfs_rtalloc.h b/fs/xfs/xfs_rtalloc.h index a03796ea90eaf..aea99b44a66ee 100644 --- a/fs/xfs/xfs_rtalloc.h +++ b/fs/xfs/xfs_rtalloc.h @@ -86,8 +86,17 @@ int xfs_growfs_check_rtgeom(const struct xfs_mount *mp, xfs_rfsblock_t dblocks, xfs_rfsblock_t rblocks, xfs_agblock_t rextsize, xfs_rtblock_t rextents, xfs_extlen_t rbmblocks, uint8_t rextslog); + +int xfs_rtalloc_find_freesp(struct xfs_trans *tp, xfs_rtxnum_t *rtx, + xfs_rtxnum_t end_rtx, xfs_rtxlen_t *len_rtx); #else -# define xfs_rtallocate_extent(t,b,min,max,l,f,p,rb) (-ENOSYS) +static inline int +xfs_rtallocate_extent(struct xfs_trans *tp, xfs_rtxnum_t start, + xfs_rtxlen_t minlen, xfs_rtxlen_t maxlen, xfs_rtxlen_t *len, + int wasdel, xfs_rtxlen_t prod, xfs_rtxnum_t *rtblock) +{ + return -ENOSYS; +} # define xfs_rtpick_extent(m,t,l,rb) (-ENOSYS) # define xfs_growfs_rt(mp,in) (-ENOSYS) # define xfs_rtalloc_reinit_frextents(m) (0) @@ -109,6 +118,7 @@ xfs_rtmount_init( # define xfs_rt_resv_init(mp) (0) # define xfs_growfs_check_rtgeom(mp, d, r, rs, rx, rb, rl) (0) # define xfs_rtmount_dqattach(mp) (0) +# define xfs_rtalloc_find_freesp(tp, rtx, end_rtx, len_rtx) (-ENOSYS) #endif /* CONFIG_XFS_RT */ #endif /* __XFS_RTALLOC_H__ */ diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 7b3abcabbafb6..db463d39649b9 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -1629,6 +1629,10 @@ DEFINE_SIMPLE_IO_EVENT(xfs_free_file_space); DEFINE_SIMPLE_IO_EVENT(xfs_zero_file_space); DEFINE_SIMPLE_IO_EVENT(xfs_collapse_file_space); DEFINE_SIMPLE_IO_EVENT(xfs_insert_file_space); +#ifdef CONFIG_XFS_RT +DEFINE_SIMPLE_IO_EVENT(xfs_map_free_rt_space); +#endif /* CONFIG_XFS_RT */ +DEFINE_SIMPLE_IO_EVENT(xfs_map_free_space); DECLARE_EVENT_CLASS(xfs_itrunc_class, TP_PROTO(struct xfs_inode *ip, xfs_fsize_t new_size), @@ -1718,6 +1722,31 @@ TRACE_EVENT(xfs_bunmap, ); +TRACE_EVENT(xfs_bmapi_freesp, + TP_PROTO(struct xfs_inode *ip, xfs_fileoff_t bno, xfs_extlen_t len), + TP_ARGS(ip, bno, len), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_ino_t, ino) + __field(xfs_fsize_t, size) + __field(xfs_fileoff_t, bno) + __field(xfs_extlen_t, len) + ), + TP_fast_assign( + __entry->dev = VFS_I(ip)->i_sb->s_dev; + __entry->ino = ip->i_ino; + __entry->size = ip->i_disk_size; + __entry->bno = bno; + __entry->len = len; + ), + TP_printk("dev %d:%d ino 0x%llx disize 0x%llx fileoff 0x%llx fsbcount 0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, + __entry->size, + __entry->bno, + __entry->len) +); + DECLARE_EVENT_CLASS(xfs_extent_busy_class, TP_PROTO(struct xfs_mount *mp, xfs_agnumber_t agno, xfs_agblock_t agbno, xfs_extlen_t len), @@ -1750,6 +1779,8 @@ DEFINE_BUSY_EVENT(xfs_extent_busy_enomem); DEFINE_BUSY_EVENT(xfs_extent_busy_force); DEFINE_BUSY_EVENT(xfs_extent_busy_reuse); DEFINE_BUSY_EVENT(xfs_extent_busy_clear); +DEFINE_BUSY_EVENT(xfs_alloc_find_freesp); +DEFINE_BUSY_EVENT(xfs_alloc_find_freesp_done); TRACE_EVENT(xfs_extent_busy_trim, TP_PROTO(struct xfs_mount *mp, xfs_agnumber_t agno, @@ -1781,6 +1812,35 @@ TRACE_EVENT(xfs_extent_busy_trim, __entry->tlen) ); +#ifdef CONFIG_XFS_RT +DECLARE_EVENT_CLASS(xfs_rtextent_class, + TP_PROTO(struct xfs_mount *mp, xfs_rtxnum_t off_rtx, + xfs_rtxnum_t len_rtx), + TP_ARGS(mp, off_rtx, len_rtx), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_rtxnum_t, off_rtx) + __field(xfs_rtxnum_t, len_rtx) + ), + TP_fast_assign( + __entry->dev = mp->m_super->s_dev; + __entry->off_rtx = off_rtx; + __entry->len_rtx = len_rtx; + ), + TP_printk("dev %d:%d rtx 0x%llx rtxcount 0x%llx", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->off_rtx, + __entry->len_rtx) +); +#define DEFINE_RTEXTENT_EVENT(name) \ +DEFINE_EVENT(xfs_rtextent_class, name, \ + TP_PROTO(struct xfs_mount *mp, xfs_rtxnum_t off_rtx, \ + xfs_rtxnum_t len_rtx), \ + TP_ARGS(mp, off_rtx, len_rtx)) +DEFINE_RTEXTENT_EVENT(xfs_rtalloc_find_freesp); +DEFINE_RTEXTENT_EVENT(xfs_rtalloc_find_freesp_done); +#endif /* CONFIG_XFS_RT */ + DECLARE_EVENT_CLASS(xfs_agf_class, TP_PROTO(struct xfs_mount *mp, struct xfs_agf *agf, int flags, unsigned long caller_ip), @@ -3783,6 +3843,7 @@ DECLARE_EVENT_CLASS(xfs_inode_irec_class, DEFINE_EVENT(xfs_inode_irec_class, name, \ TP_PROTO(struct xfs_inode *ip, struct xfs_bmbt_irec *irec), \ TP_ARGS(ip, irec)) +DEFINE_INODE_IREC_EVENT(xfs_bmapi_freesp_done); /* inode iomap invalidation events */ DECLARE_EVENT_CLASS(xfs_wb_invalid_class, @@ -3917,6 +3978,7 @@ DEFINE_INODE_ERROR_EVENT(xfs_reflink_remap_blocks_error); DEFINE_INODE_ERROR_EVENT(xfs_reflink_remap_extent_error); DEFINE_INODE_IREC_EVENT(xfs_reflink_remap_extent_src); DEFINE_INODE_IREC_EVENT(xfs_reflink_remap_extent_dest); +DEFINE_INODE_ERROR_EVENT(xfs_map_free_reserve_more_fail); /* dedupe tracepoints */ DEFINE_DOUBLE_IO_EVENT(xfs_reflink_compare_extents); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET v2 5/5] xfs: aligned file data extent mappings 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (3 preceding siblings ...) 2023-12-31 19:39 ` [PATCHSET 4/5] xfs: defragment free space Darrick J. Wong @ 2023-12-31 19:39 ` Darrick J. Wong 2023-12-31 22:03 ` [PATCH 1/4] xfs: create a new inode flag to require extsize alignment of file data space Darrick J. Wong ` (3 more replies) 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong ` (4 subsequent siblings) 9 siblings, 4 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:39 UTC (permalink / raw) To: djwong; +Cc: linux-xfs, john.g.garry Hi all, Years ago during the adaptation of XFS to persistent memory, a product development team expressed a desire to guarantee that mmap mappings of an S_DAX file on pmem would always get a PMD mapping to increase TLB hit rates. That means ensuring that all space allocations are aligned to 2MB (on x86) and ensuring that the file range of each file space mapping are aligned to 2MB. Then pmem faded. NVME/SCSI atomic write unit support has brought this discussion back to the forefront, because untorn writes to conventional storage only work if the space allocations are aligned to the max atomic write unit. To simplify the administrative model a bit, we want to ensure that the mappings are aligned to the same value. IOWs, for a storage device that supports up to 64k atomic write units, we want to ensure that the file range of all mappings are aligned to 64k. From a file metadata perspective, these are nearly the same use case. The realtime volume already has a realtime extent size field that forces the alignment of both space and mapping, but not everyone wants to adminster a realtime volume. This patchset adds a forcealign superblock feature and inode flag so that we can impose the same alignment restrictions on selected files on the data volume. IOWs, you can now set an inode flag that causes regular files on the data volume to have rextsize-aligned space mappings. This is an improvement over ext4 bigalloc style things that impose the larger allocation unit size even for things that don't need it. Note: Currently this patchset reuses sb_rextsize for the forcealign value because that enabled reuse of the COW around code that I wrote for realtime reflink that ensures that remapping operations follow forcealign. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=file-force-align xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=file-force-align --- fs/xfs/libxfs/xfs_bmap.c | 48 +++++++++++++++++++++++++++++++++++++--- fs/xfs/libxfs/xfs_format.h | 19 +++++++++++++--- fs/xfs/libxfs/xfs_inode_buf.c | 36 ++++++++++++++++++++++++++++++ fs/xfs/libxfs/xfs_inode_buf.h | 3 +++ fs/xfs/libxfs/xfs_inode_util.c | 14 ++++++++++++ fs/xfs/libxfs/xfs_sb.c | 30 +++++++++++++++++++++++++ fs/xfs/scrub/inode_repair.c | 41 ++++++++++++++++++++++++++++++++++ fs/xfs/scrub/trace.h | 1 + fs/xfs/xfs_bmap_util.c | 2 -- fs/xfs/xfs_bmap_util.h | 4 ++- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_inode.h | 8 ++++++- fs/xfs/xfs_ioctl.c | 14 ++++++++++++ fs/xfs/xfs_iomap.c | 4 +++ fs/xfs/xfs_mount.h | 2 ++ fs/xfs/xfs_reflink.c | 16 ++++++++++--- fs/xfs/xfs_rtalloc.c | 4 +++ fs/xfs/xfs_super.c | 4 +++ fs/xfs/xfs_trace.h | 4 --- include/uapi/linux/fs.h | 2 ++ 20 files changed, 236 insertions(+), 22 deletions(-) ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/4] xfs: create a new inode flag to require extsize alignment of file data space 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong @ 2023-12-31 22:03 ` Darrick J. Wong 2023-12-31 22:03 ` [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag Darrick J. Wong ` (2 subsequent siblings) 3 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:03 UTC (permalink / raw) To: djwong; +Cc: linux-xfs, john.g.garry From: Darrick J. Wong <djwong@kernel.org> Add a new inode flag to require that all file data extent mappings must be aligned (both the file offset range and the allocated space itself) to the extent size hint. Having a separate COW extent size hint is no longer allowed. The goal here is to enable sysadmins and users to mandate that all space mappings in a file must have a startoff/blockcount that are aligned to (say) a 2MB alignment and that the startblock/blockcount will follow the same alignment. Co-developed-by: John Garry <john.g.garry@oracle.com> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_format.h | 16 ++++++++++++++-- fs/xfs/libxfs/xfs_inode_buf.c | 36 +++++++++++++++++++++++++++++++++++ fs/xfs/libxfs/xfs_inode_buf.h | 3 +++ fs/xfs/libxfs/xfs_inode_util.c | 14 ++++++++++++++ fs/xfs/libxfs/xfs_sb.c | 30 +++++++++++++++++++++++++++++ fs/xfs/scrub/inode_repair.c | 41 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/trace.h | 1 + fs/xfs/xfs_inode.h | 5 +++++ fs/xfs/xfs_ioctl.c | 14 ++++++++++++++ fs/xfs/xfs_mount.h | 2 ++ fs/xfs/xfs_rtalloc.c | 4 ++++ fs/xfs/xfs_super.c | 4 ++++ include/uapi/linux/fs.h | 2 ++ 13 files changed, 170 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index ca964befb51cf..a0f5bae450135 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -103,7 +103,12 @@ typedef struct xfs_sb { xfs_ino_t sb_rootino; /* root inode number */ xfs_ino_t sb_rbmino; /* bitmap inode for realtime extents */ xfs_ino_t sb_rsumino; /* summary inode for rt bitmap */ - xfs_agblock_t sb_rextsize; /* realtime extent size, blocks */ + /* + * Realtime extent size, blocks. If the FORCEALIGN feature is set, + * the allocation group size must be a multiple of this value, and + * file data allocations will be aligned to this value. + */ + xfs_agblock_t sb_rextsize; xfs_agblock_t sb_agblocks; /* size of an allocation group */ xfs_agnumber_t sb_agcount; /* number of allocation groups */ xfs_extlen_t sb_rbmblocks; /* number of rt bitmap blocks */ @@ -387,6 +392,8 @@ xfs_sb_has_compat_feature( #define XFS_SB_FEAT_RO_COMPAT_RMAPBT (1 << 1) /* reverse map btree */ #define XFS_SB_FEAT_RO_COMPAT_REFLINK (1 << 2) /* reflinked files */ #define XFS_SB_FEAT_RO_COMPAT_INOBTCNT (1 << 3) /* inobt block counts */ +/* all AGs and data allocations must be aligned to rextsize, even for !rt files */ +#define XFS_SB_FEAT_RO_COMPAT_FORCEALIGN (1 << 30) #define XFS_SB_FEAT_RO_COMPAT_ALL \ (XFS_SB_FEAT_RO_COMPAT_FINOBT | \ XFS_SB_FEAT_RO_COMPAT_RMAPBT | \ @@ -1206,6 +1213,8 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev) #define XFS_DIFLAG2_COWEXTSIZE_BIT 2 /* copy on write extent size hint */ #define XFS_DIFLAG2_BIGTIME_BIT 3 /* big timestamps */ #define XFS_DIFLAG2_NREXT64_BIT 4 /* large extent counters */ +/* data extent mappings for regular files must be aligned to extent size hint */ +#define XFS_DIFLAG2_FORCEALIGN_BIT 5 #define XFS_DIFLAG2_METADIR_BIT 63 /* filesystem metadata */ #define XFS_DIFLAG2_DAX (1ULL << XFS_DIFLAG2_DAX_BIT) @@ -1239,9 +1248,12 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev) */ #define XFS_DIFLAG2_METADIR (1ULL << XFS_DIFLAG2_METADIR_BIT) +#define XFS_DIFLAG2_FORCEALIGN (1ULL << XFS_DIFLAG2_FORCEALIGN_BIT) + #define XFS_DIFLAG2_ANY \ (XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \ - XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADIR) + XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADIR | \ + XFS_DIFLAG2_FORCEALIGN) static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip) { diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index adc457da52ef0..b2ad88f7d63f3 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -726,6 +726,14 @@ xfs_dinode_verify( } else if (nextents + naextents == 0 && nblocks != 0) return __this_address; + if (flags2 & XFS_DIFLAG2_FORCEALIGN) { + fa = xfs_inode_validate_forcealign(mp, mode, flags, + be32_to_cpu(dip->di_extsize), + be32_to_cpu(dip->di_cowextsize)); + if (fa) + return fa; + } + return NULL; } @@ -900,3 +908,31 @@ xfs_inode_validate_cowextsize( return NULL; } + +/* Validate the forcealign inode flag */ +xfs_failaddr_t +xfs_inode_validate_forcealign( + struct xfs_mount *mp, + uint16_t mode, + uint16_t flags, + uint32_t extsize, + uint32_t cowextsize) +{ + /* superblock rocompat feature flag required */ + if (!xfs_has_forcealign(mp)) + return __this_address; + + /* Only regular files and directories */ + if (!S_ISDIR(mode) && !S_ISREG(mode)) + return __this_address; + + /* Requires no extent size hint */ + if (extsize != 0) + return __this_address; + + /* Requires no cow extent size hint */ + if (cowextsize != 0) + return __this_address; + + return NULL; +} diff --git a/fs/xfs/libxfs/xfs_inode_buf.h b/fs/xfs/libxfs/xfs_inode_buf.h index 8d43d2641c732..68526de991cc6 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.h +++ b/fs/xfs/libxfs/xfs_inode_buf.h @@ -36,6 +36,9 @@ xfs_failaddr_t xfs_inode_validate_extsize(struct xfs_mount *mp, xfs_failaddr_t xfs_inode_validate_cowextsize(struct xfs_mount *mp, uint32_t cowextsize, uint16_t mode, uint16_t flags, uint64_t flags2); +xfs_failaddr_t xfs_inode_validate_forcealign(struct xfs_mount *mp, + uint16_t mode, uint16_t flags, uint32_t extsize, + uint32_t cowextsize); static inline uint64_t xfs_inode_encode_bigtime(struct timespec64 tv) { diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c index 7833530102d1c..7e92c5fe35c78 100644 --- a/fs/xfs/libxfs/xfs_inode_util.c +++ b/fs/xfs/libxfs/xfs_inode_util.c @@ -81,6 +81,8 @@ xfs_flags2diflags2( di_flags2 |= XFS_DIFLAG2_DAX; if (xflags & FS_XFLAG_COWEXTSIZE) di_flags2 |= XFS_DIFLAG2_COWEXTSIZE; + if (xflags & FS_XFLAG_FORCEALIGN) + di_flags2 |= XFS_DIFLAG2_FORCEALIGN; return di_flags2; } @@ -127,6 +129,8 @@ xfs_ip2xflags( flags |= FS_XFLAG_DAX; if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) flags |= FS_XFLAG_COWEXTSIZE; + if (ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN) + flags |= FS_XFLAG_FORCEALIGN; } if (xfs_inode_has_attr_fork(ip)) @@ -228,6 +232,8 @@ xfs_inode_inherit_flags2( ip->i_diflags2 |= XFS_DIFLAG2_DAX; if (pip->i_diflags2 & XFS_DIFLAG2_METADIR) ip->i_diflags2 |= XFS_DIFLAG2_METADIR; + if (pip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN) + ip->i_diflags2 |= XFS_DIFLAG2_FORCEALIGN; /* Don't let invalid cowextsize hints propagate. */ failaddr = xfs_inode_validate_cowextsize(ip->i_mount, ip->i_cowextsize, @@ -236,6 +242,14 @@ xfs_inode_inherit_flags2( ip->i_diflags2 &= ~XFS_DIFLAG2_COWEXTSIZE; ip->i_cowextsize = 0; } + + if (ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN) { + failaddr = xfs_inode_validate_forcealign(ip->i_mount, + VFS_I(ip)->i_mode, ip->i_diflags, ip->i_extsize, + ip->i_cowextsize); + if (failaddr) + ip->i_diflags2 &= ~XFS_DIFLAG2_FORCEALIGN; + } } /* Initialise an inode's attributes. */ diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index b75a5bcbdf19e..8b7f8023d9bf7 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -167,6 +167,9 @@ xfs_sb_version_to_features( features |= XFS_FEAT_REFLINK; if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_INOBTCNT) features |= XFS_FEAT_INOBTCNT; + if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_FORCEALIGN) + features |= XFS_FEAT_FORCEALIGN; + if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_FTYPE) features |= XFS_FEAT_FTYPE; if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_SPINODES) @@ -370,6 +373,27 @@ xfs_validate_sb_rtgroups( return 0; } +static int +xfs_validate_sb_forcealign( + struct xfs_mount *mp, + struct xfs_sb *sbp) +{ + if (sbp->sb_rextsize == 0) { + xfs_warn(mp, + "Cannot have forced allocation alignment of zero."); + return -EINVAL; + } + + if (sbp->sb_agblocks % sbp->sb_rextsize != 0) { + xfs_warn(mp, + "Allocation group size %u not aligned to forcealign %u.", + sbp->sb_agblocks, sbp->sb_rextsize); + return -EINVAL; + } + + return 0; +} + /* Check the validity of the SB. */ STATIC int xfs_validate_sb_common( @@ -437,6 +461,12 @@ xfs_validate_sb_common( if (error) return error; } + + if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_FORCEALIGN) { + error = xfs_validate_sb_forcealign(mp, sbp); + if (error) + return error; + } } else if (sbp->sb_qflags & (XFS_PQUOTA_ENFD | XFS_GQUOTA_ENFD | XFS_PQUOTA_CHKD | XFS_GQUOTA_CHKD)) { xfs_notice(mp, diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index a182a9551c08c..5dd46565d82e8 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -666,6 +666,46 @@ xrep_dinode_extsize_hints( } } +/* Fix forcealign flag. */ +STATIC void +xrep_dinode_forcealign( + struct xfs_scrub *sc, + struct xfs_dinode *dip) +{ + uint64_t flags2; + uint16_t flags; + uint16_t mode; + + trace_xrep_dinode_forcealign(sc, dip); + + if (dip->di_version < 3) + return; + + mode = be16_to_cpu(dip->di_mode); + flags = be16_to_cpu(dip->di_flags); + flags2 = be64_to_cpu(dip->di_flags2); + + if (!(flags2 & XFS_DIFLAG2_FORCEALIGN)) + return; + + if (!xfs_has_forcealign(sc->mp)) + flags2 &= ~XFS_DIFLAG2_FORCEALIGN; + + if (!S_ISDIR(mode) && !S_ISREG(mode)) + flags2 &= ~XFS_DIFLAG2_FORCEALIGN; + + if (flags & XFS_DIFLAG_REALTIME) + flags2 &= ~XFS_DIFLAG2_FORCEALIGN; + + if (dip->di_extsize != 0) + flags2 &= ~XFS_DIFLAG2_FORCEALIGN; + + if (dip->di_cowextsize != 0) + flags2 &= ~XFS_DIFLAG2_FORCEALIGN; + + dip->di_flags2 = cpu_to_be64(flags2); +} + /* Count extents and blocks for an inode given an rmap. */ STATIC int xrep_dinode_walk_rmap( @@ -1506,6 +1546,7 @@ xrep_dinode_core( xrep_dinode_flags(sc, dip, ri->rt_extents > 0); xrep_dinode_size(ri, dip); xrep_dinode_extsize_hints(sc, dip); + xrep_dinode_forcealign(sc, dip); xrep_dinode_zap_forks(ri, dip); write: diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index cfd882edb2937..6e15de56be2b7 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2559,6 +2559,7 @@ DEFINE_REPAIR_DINODE_EVENT(xrep_dinode_zap_forks); DEFINE_REPAIR_DINODE_EVENT(xrep_dinode_zap_dfork); DEFINE_REPAIR_DINODE_EVENT(xrep_dinode_zap_afork); DEFINE_REPAIR_DINODE_EVENT(xrep_dinode_ensure_forkoff); +DEFINE_REPAIR_DINODE_EVENT(xrep_dinode_forcealign); DECLARE_EVENT_CLASS(xrep_inode_class, TP_PROTO(struct xfs_scrub *sc), diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 2779a353b4618..ea311b1fa616b 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -326,6 +326,11 @@ static inline bool xfs_inode_has_large_extent_counts(struct xfs_inode *ip) return ip->i_diflags2 & XFS_DIFLAG2_NREXT64; } +static inline bool xfs_inode_force_align(struct xfs_inode *ip) +{ + return ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN; +} + static inline bool xfs_inode_has_bigallocunit(struct xfs_inode *ip) { return XFS_IS_REALTIME_INODE(ip) && ip->i_mount->m_sb.sb_rextsize > 1; diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index ad289d37145e8..71f7503f75a7e 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1144,6 +1144,20 @@ xfs_ioctl_setattr_xflags( if (i_flags2 && !xfs_has_v3inodes(mp)) return -EINVAL; + /* + * Force-align requires a zero extent size hint and a zero cow extent + * size hint. + */ + if (fa->fsx_xflags & FS_XFLAG_FORCEALIGN) { + if (!xfs_has_forcealign(mp)) + return -EINVAL; + if (fa->fsx_xflags & FS_XFLAG_COWEXTSIZE) + return -EINVAL; + if (fa->fsx_xflags & (FS_XFLAG_EXTSIZE | + FS_XFLAG_EXTSZINHERIT)) + return -EINVAL; + } + ip->i_diflags = xfs_flags2diflags(ip, fa->fsx_xflags); ip->i_diflags2 = i_flags2; diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 1c99d0630364f..964560a471538 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -310,6 +310,7 @@ typedef struct xfs_mount { #define XFS_FEAT_NREXT64 (1ULL << 26) /* large extent counters */ #define XFS_FEAT_METADIR (1ULL << 27) /* metadata directory tree */ #define XFS_FEAT_RTGROUPS (1ULL << 28) /* realtime groups */ +#define XFS_FEAT_FORCEALIGN (1ULL << 29) /* aligned file data extents */ /* Mount features */ #define XFS_FEAT_NOATTR2 (1ULL << 48) /* disable attr2 creation */ @@ -375,6 +376,7 @@ __XFS_HAS_FEAT(needsrepair, NEEDSREPAIR) __XFS_HAS_FEAT(large_extent_counts, NREXT64) __XFS_HAS_FEAT(metadir, METADIR) __XFS_HAS_FEAT(rtgroups, RTGROUPS) +__XFS_HAS_FEAT(forcealign, FORCEALIGN) static inline bool xfs_has_rtrmapbt(struct xfs_mount *mp) { diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index e59189e84943c..3e4fcfe2776d3 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -1238,6 +1238,10 @@ xfs_growfs_rt( if (sbp->sb_rblocks > 0 && in->extsize != sbp->sb_rextsize) return -EINVAL; + /* Cannot change rt extent size when forcealign is set. */ + if (xfs_has_forcealign(mp) && in->extsize != sbp->sb_rextsize) + return -EINVAL; + /* Range check the extent size. */ if (XFS_FSB_TO_B(mp, in->extsize) > XFS_MAX_RTEXTSIZE || XFS_FSB_TO_B(mp, in->extsize) < XFS_MIN_RTEXTSIZE) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 8f06716dd0169..9b478e81f3d38 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -1727,6 +1727,10 @@ xfs_fs_fill_super( xfs_warn(mp, "EXPERIMENTAL realtime allocation group feature in use. Use at your own risk!"); + if (xfs_has_forcealign(mp)) + xfs_warn(mp, +"EXPERIMENTAL forced data extent alignment feature in use. Use at your own risk!"); + if (xfs_has_reflink(mp)) { /* * Reflink doesn't support pagecache pages that span multiple diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index da43810b74856..be458e69a140b 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -140,6 +140,8 @@ struct fsxattr { #define FS_XFLAG_FILESTREAM 0x00004000 /* use filestream allocator */ #define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */ #define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */ +/* data extent mappings for regular files must be aligned to extent size hint */ +#define FS_XFLAG_FORCEALIGN 0x00020000 #define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this */ /* the read-only stuff doesn't really belong here, but any other place is ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong 2023-12-31 22:03 ` [PATCH 1/4] xfs: create a new inode flag to require extsize alignment of file data space Darrick J. Wong @ 2023-12-31 22:03 ` Darrick J. Wong 2024-01-16 9:26 ` John Garry 2023-12-31 22:03 ` [PATCH 3/4] xfs: support reflink with force align enabled Darrick J. Wong 2023-12-31 22:03 ` [PATCH 4/4] xfs: enable file data force-align feature Darrick J. Wong 3 siblings, 1 reply; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:03 UTC (permalink / raw) To: djwong; +Cc: linux-xfs, john.g.garry From: Darrick J. Wong <djwong@kernel.org> The existing extsize hint code already did the work of expanding file range mapping requests so that the range is aligned to the hint value. Now add the code we need to guarantee that the space allocations are also always aligned. Co-developed-by: John Garry <john.g.garry@oracle.com> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_bmap.c | 48 ++++++++++++++++++++++++++++++++++++++++++---- fs/xfs/xfs_iomap.c | 4 +++- fs/xfs/xfs_reflink.c | 4 ++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index 7d0d82353a745..b14761ec96b87 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -3351,6 +3351,17 @@ xfs_bmap_btalloc_accounting( args->len); } +static inline bool +xfs_bmap_use_forcealign( + const struct xfs_bmalloca *ap) +{ + if (!xfs_inode_force_align(ap->ip)) + return false; + + return (ap->flags & XFS_BMAPI_COWFORK) || + (ap->datatype & XFS_ALLOC_USERDATA); +} + static int xfs_bmap_compute_alignments( struct xfs_bmalloca *ap, @@ -3366,6 +3377,17 @@ xfs_bmap_compute_alignments( else if (mp->m_dalign) stripe_align = mp->m_dalign; + /* + * File data mappings with forced alignment can use the stripe + * alignment if it's a multiple of the forcealign value. Otherwise, + * use the regular forcealign value. + */ + if (xfs_bmap_use_forcealign(ap)) { + if (!stripe_align || stripe_align % mp->m_sb.sb_rextsize) + stripe_align = mp->m_sb.sb_rextsize; + args->alignment = stripe_align; + } + if (ap->flags & XFS_BMAPI_COWFORK) align = xfs_get_cowextsz_hint(ap->ip); else if (ap->datatype & XFS_ALLOC_USERDATA) @@ -3438,6 +3460,9 @@ xfs_bmap_exact_minlen_extent_alloc( ASSERT(ap->length); + if (xfs_inode_force_align(ap->ip)) + return -ENOSPC; + if (ap->minlen != 1) { ap->blkno = NULLFSBLOCK; ap->length = 0; @@ -3511,6 +3536,7 @@ xfs_bmap_btalloc_at_eof( { struct xfs_mount *mp = args->mp; struct xfs_perag *caller_pag = args->pag; + int orig_alignment = args->alignment; int error; /* @@ -3585,10 +3611,10 @@ xfs_bmap_btalloc_at_eof( /* * Allocation failed, so turn return the allocation args to their - * original non-aligned state so the caller can proceed on allocation - * failure as if this function was never called. + * original state so the caller can proceed on allocation failure as + * if this function was never called. */ - args->alignment = 1; + args->alignment = orig_alignment; return 0; } @@ -3611,6 +3637,10 @@ xfs_bmap_btalloc_low_space( { int error; + /* Don't try unaligned last-chance allocations with forcealign */ + if (xfs_inode_force_align(ap->ip)) + return -ENOSPC; + if (args->minlen > ap->minlen) { args->minlen = ap->minlen; error = xfs_alloc_vextent_start_ag(args, ap->blkno); @@ -4115,7 +4145,9 @@ xfs_bmap_alloc_userdata( if (bma->offset == 0) bma->datatype |= XFS_ALLOC_INITIAL_USER_DATA; - if (mp->m_dalign && bma->length >= mp->m_dalign) { + /* forcealign mode reuses the stripe unit alignment code */ + if (xfs_inode_force_align(bma->ip) || + (mp->m_dalign && bma->length >= mp->m_dalign)) { error = xfs_bmap_isaeof(bma, whichfork); if (error) return error; @@ -6381,6 +6413,10 @@ xfs_extlen_t xfs_get_extsz_hint( struct xfs_inode *ip) { + /* forcealign means we align to rextsize */ + if (xfs_inode_force_align(ip)) + return ip->i_mount->m_sb.sb_rextsize; + /* * No point in aligning allocations if we need to COW to actually * write to them. @@ -6405,6 +6441,10 @@ xfs_get_cowextsz_hint( { xfs_extlen_t a, b; + /* forcealign means we align to rextsize */ + if (xfs_inode_force_align(ip)) + return ip->i_mount->m_sb.sb_rextsize; + a = 0; if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) a = ip->i_cowextsize; diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 559e8e7855952..3bfbcbed1bd68 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -185,7 +185,9 @@ xfs_eof_alignment( * If mounted with the "-o swalloc" option the alignment is * increased from the strip unit size to the stripe width. */ - if (mp->m_swidth && xfs_has_swalloc(mp)) + if (xfs_inode_force_align(ip)) + align = xfs_get_extsz_hint(ip); + else if (mp->m_swidth && xfs_has_swalloc(mp)) align = mp->m_swidth; else if (mp->m_dalign) align = mp->m_dalign; diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 0c54522404963..da39da13fcd7d 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1803,6 +1803,10 @@ xfs_reflink_remap_prep( if (IS_DAX(inode_in) != IS_DAX(inode_out)) goto out_unlock; + /* XXX Can't reflink forcealign files for now */ + if (xfs_inode_force_align(src) || xfs_inode_force_align(dest)) + goto out_unlock; + /* 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, ^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag 2023-12-31 22:03 ` [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag Darrick J. Wong @ 2024-01-16 9:26 ` John Garry 0 siblings, 0 replies; 48+ messages in thread From: John Garry @ 2024-01-16 9:26 UTC (permalink / raw) To: Darrick J. Wong; +Cc: linux-xfs, hch, david Hi Darrick, As mentioned internally, we have an issue for atomic writes [0] that we get an aligned and but not fully-written extent when we initially write a size less than the forcealign size, like: #/mkfs.xfs -f -d forcealign=16k /dev/sda ... # mount /dev/sda mnt # touch mnt/file # /test-pwritev2 -a -d -l 4096 -p 0 /root/mnt/file # direct IO, atomic write, 4096B at pos 0 # filefrag -v mnt/file Filesystem type is: 58465342 File size of mnt/file is 4096 (1 block of 4096 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 0: 24.. 24: 1: last,eof mnt/file: 1 extent found # /test-pwritev2 -a -d -l 16384 -p 0 /root/mnt/file wrote -1 bytes at pos 0 write_size=16384 # This causes an issue for atomic writes in that the 16K write means 2x mappings and then 2x BIOs, which we cannot tolerate. So how about this change on top: diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 731260a5af6d..6609f1058ae3 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -685,6 +685,12 @@ xfs_can_free_eofblocks( end_fsb = XFS_B_TO_FSB(mp, (xfs_ufsize_t)XFS_ISIZE(ip)); if (XFS_IS_REALTIME_INODE(ip) && mp->m_sb.sb_rextsize > 1) end_fsb = xfs_rtb_roundup_rtx(mp, end_fsb); + + /* Don't trim eof blocks */ + if (xfs_inode_force_align(ip)) { + end_fsb = roundup_64(end_fsb, xfs_get_extsz_hint(ip)); + } + last_fsb = XFS_B_TO_FSB(mp, mp->m_super->s_maxbytes); if (last_fsb <= end_fsb) return false; diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 0c7008322326..c906e3a424d1 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -291,6 +291,10 @@ xfs_iomap_write_direct( }david@fromorbit.com } + if (xfs_inode_force_align(ip)) { + bmapi_flags = XFS_BMAPI_ZERO; + } + error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write, dblocks, rblocks, force, &tp); if (error) lines 1-38/38 (END) Which gives: #/mkfs.xfs -d forcealign=16k /dev/sda ... # /test-pwritev2 -a -d -l 4096 -p 0 /root/mnt/file wrote 4096 bytes at pos 0 write_size=4096 # filefrag -v mnt/file Filesystem type is: 58465342 File size of mnt/file is 4096 (1 block of 4096 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 3: 24.. 27: 4: last,eof mnt/file: 1 extent found # # /test-pwritev2 -a -d -l 16384 -p 0 /root/mnt/file wrote 16384 bytes at pos 0 write_size=16384 # filefrag -v mnt/file Filesystem type is: 58465342 File size of mnt/file is 16384 (4 blocks of 4096 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 3: 24.. 27: 4: last,eof mnt/file: 1 extent found # Or maybe make that change under FS_XFLAG_ATOMICWRITES flag. Previously we were pre-zero'ing the complete file to get around this. Thanks, John [0] https://lore.kernel.org/linux-scsi/20240111161522.GB16626@lst.de/T/#mbc6824fbe9ce62c9506aa4c3f281173747695d77 (just referencing for others) ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 3/4] xfs: support reflink with force align enabled 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong 2023-12-31 22:03 ` [PATCH 1/4] xfs: create a new inode flag to require extsize alignment of file data space Darrick J. Wong 2023-12-31 22:03 ` [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag Darrick J. Wong @ 2023-12-31 22:03 ` Darrick J. Wong 2023-12-31 22:03 ` [PATCH 4/4] xfs: enable file data force-align feature Darrick J. Wong 3 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:03 UTC (permalink / raw) To: djwong; +Cc: linux-xfs, john.g.garry From: Darrick J. Wong <djwong@kernel.org> Reuse the "big rt extents" code to do the necessary "COW around" behaviors so that we always reflink entire forcealign units. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/xfs_bmap_util.c | 2 -- fs/xfs/xfs_bmap_util.h | 4 ++-- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_inode.h | 3 ++- fs/xfs/xfs_reflink.c | 18 +++++++++++------- fs/xfs/xfs_trace.h | 4 ---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 911838fbecbfb..a408c59c7852c 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1283,7 +1283,6 @@ xfs_insert_file_space( return error; } -#ifdef CONFIG_XFS_RT /* * Decide if this is an unwritten extent that isn't aligned to an allocation * unit boundary. @@ -1462,7 +1461,6 @@ xfs_convert_bigalloc_file_space( return 0; } -#endif /* CONFIG_XFS_RT */ /* * Reserve space and quota to this transaction to map in as much free space diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index 75d16202e2d34..887b9942123bb 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -77,12 +77,12 @@ int xfs_bmap_count_blocks(struct xfs_trans *tp, struct xfs_inode *ip, int xfs_flush_unmap_range(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t len); -#ifdef CONFIG_XFS_RT int xfs_convert_bigalloc_file_space(struct xfs_inode *ip, loff_t pos, uint64_t len); + +#ifdef CONFIG_XFS_RT int xfs_map_free_rt_space(struct xfs_inode *ip, xfs_off_t off, xfs_off_t len); #else -# define xfs_convert_bigalloc_file_space(ip, pos, len) (-EOPNOTSUPP) # define xfs_map_free_rt_space(ip, off, len) (-EOPNOTSUPP) #endif diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 3408804bee9b2..00d7c20725a85 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -3204,7 +3204,7 @@ xfs_inode_alloc_unitsize( { unsigned int blocks = 1; - if (XFS_IS_REALTIME_INODE(ip)) + if (XFS_IS_REALTIME_INODE(ip) || xfs_inode_force_align(ip)) blocks = ip->i_mount->m_sb.sb_rextsize; return XFS_FSB_TO_B(ip->i_mount, blocks); diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index ea311b1fa616b..defc1965a0216 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -333,7 +333,8 @@ static inline bool xfs_inode_force_align(struct xfs_inode *ip) static inline bool xfs_inode_has_bigallocunit(struct xfs_inode *ip) { - return XFS_IS_REALTIME_INODE(ip) && ip->i_mount->m_sb.sb_rextsize > 1; + return (xfs_inode_force_align(ip) || 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. */ diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index da39da13fcd7d..90cb3f7db8c35 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1614,7 +1614,6 @@ 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 an allocation unit (AU) * boundary. @@ -1661,9 +1660,6 @@ xfs_reflink_adjust_bigalloc_len( trace_xfs_reflink_adjust_bigalloc_len(src, pos_in, *len, dest, pos_out); return 0; } -#else -# define xfs_reflink_adjust_bigalloc_len(...) (0) -#endif /* CONFIG_XFS_RT */ /* * Check the alignment of a remap request when the allocation unit size isn't a @@ -1803,9 +1799,17 @@ xfs_reflink_remap_prep( if (IS_DAX(inode_in) != IS_DAX(inode_out)) goto out_unlock; - /* XXX Can't reflink forcealign files for now */ - if (xfs_inode_force_align(src) || xfs_inode_force_align(dest)) - goto out_unlock; + /* Check non-power of two alignment issues, if necessary. */ + if ((xfs_inode_force_align(src) || xfs_inode_force_align(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; + } /* Check non-power of two alignment issues, if necessary. */ if (XFS_IS_REALTIME_INODE(dest) && !is_power_of_2(alloc_unit)) { diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index db463d39649b9..75bb38cd08945 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -3968,9 +3968,7 @@ 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_adjust_bigalloc_len); -#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); @@ -4014,9 +4012,7 @@ TRACE_EVENT(xfs_ioctl_clone, 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_convert_bigalloc_file_space); -#endif /* CONFIG_XFS_RT */ /* copy on write */ DEFINE_INODE_IREC_EVENT(xfs_reflink_trim_around_shared); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 4/4] xfs: enable file data force-align feature 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong ` (2 preceding siblings ...) 2023-12-31 22:03 ` [PATCH 3/4] xfs: support reflink with force align enabled Darrick J. Wong @ 2023-12-31 22:03 ` Darrick J. Wong 3 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 22:03 UTC (permalink / raw) To: djwong; +Cc: linux-xfs, john.g.garry From: Darrick J. Wong <djwong@kernel.org> Enable this feature. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- fs/xfs/libxfs/xfs_format.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index a0f5bae450135..c9af0025c26a7 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -398,7 +398,8 @@ xfs_sb_has_compat_feature( (XFS_SB_FEAT_RO_COMPAT_FINOBT | \ XFS_SB_FEAT_RO_COMPAT_RMAPBT | \ XFS_SB_FEAT_RO_COMPAT_REFLINK| \ - XFS_SB_FEAT_RO_COMPAT_INOBTCNT) + XFS_SB_FEAT_RO_COMPAT_INOBTCNT | \ + XFS_SB_FEAT_RO_COMPAT_FORCEALIGN) #define XFS_SB_FEAT_RO_COMPAT_UNKNOWN ~XFS_SB_FEAT_RO_COMPAT_ALL static inline bool xfs_sb_has_ro_compat_feature( ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (4 preceding siblings ...) 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong @ 2023-12-31 19:56 ` Darrick J. Wong 2023-12-27 13:38 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong ` (4 more replies) 2023-12-31 19:56 ` [PATCHSET 2/3] xfsprogs: report refcount information to userspace Darrick J. Wong ` (3 subsequent siblings) 9 siblings, 5 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:56 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs Hi all, This series creates a new NOALLOC flag for allocation groups that causes the block and inode allocators to look elsewhere when trying to allocate resources. This is either the first part of a patchset to implement online shrinking (set noalloc on the last AGs, run fsr to move the files and directories) or freeze-free rmapbt rebuilding (set noalloc to prevent creation of new mappings, then hook deletion of old mappings). This is still totally a research project. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=noalloc-ags xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=noalloc-ags --- include/xfs_trace.h | 2 + include/xfs_trans.h | 4 ++ io/aginfo.c | 45 ++++++++++++++++++-- libxfs/xfs_ag.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++ libxfs/xfs_ag.h | 8 ++++ libxfs/xfs_ag_resv.c | 28 +++++++++++- libxfs/xfs_defer.c | 14 ++++++ libxfs/xfs_fs.h | 5 ++ libxfs/xfs_ialloc.c | 3 + man/man8/xfs_io.8 | 6 ++- 10 files changed, 220 insertions(+), 9 deletions(-) ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/5] xfs: track deferred ops statistics 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong @ 2023-12-27 13:38 ` Darrick J. Wong 2023-12-27 13:38 ` [PATCH 2/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong ` (3 subsequent siblings) 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:38 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Track some basic statistics on how hard we're pushing the defer ops. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- include/xfs_trans.h | 4 ++++ libxfs/xfs_defer.c | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/xfs_trans.h b/include/xfs_trans.h index 630bc85ce37..4b4c26b5561 100644 --- a/include/xfs_trans.h +++ b/include/xfs_trans.h @@ -82,6 +82,10 @@ typedef struct xfs_trans { long t_frextents_delta;/* superblock freextents chg*/ struct list_head t_items; /* log item descriptors */ struct list_head t_dfops; /* deferred operations */ + + unsigned int t_dfops_nr; + unsigned int t_dfops_nr_max; + unsigned int t_dfops_finished; } xfs_trans_t; void xfs_trans_init(struct xfs_mount *); diff --git a/libxfs/xfs_defer.c b/libxfs/xfs_defer.c index 4a1139913b9..2e32939024d 100644 --- a/libxfs/xfs_defer.c +++ b/libxfs/xfs_defer.c @@ -611,6 +611,8 @@ xfs_defer_finish_one( /* Done with the dfp, free it. */ list_del(&dfp->dfp_list); kmem_cache_free(xfs_defer_pending_cache, dfp); + tp->t_dfops_nr--; + tp->t_dfops_finished++; out: if (ops->finish_cleanup) ops->finish_cleanup(tp, state, error); @@ -673,6 +675,9 @@ xfs_defer_finish_noroll( list_splice_init(&(*tp)->t_dfops, &dop_pending); + (*tp)->t_dfops_nr_max = max((*tp)->t_dfops_nr, + (*tp)->t_dfops_nr_max); + if (has_intents < 0) { error = has_intents; goto out_shutdown; @@ -714,6 +719,7 @@ xfs_defer_finish_noroll( xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); trace_xfs_defer_finish_error(*tp, error); xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending); + (*tp)->t_dfops_nr = 0; xfs_defer_cancel(*tp); return error; } @@ -761,6 +767,7 @@ xfs_defer_cancel( trace_xfs_defer_cancel(tp, _RET_IP_); xfs_defer_trans_abort(tp, &tp->t_dfops); xfs_defer_cancel_list(mp, &tp->t_dfops); + tp->t_dfops_nr = 0; } /* @@ -824,6 +831,7 @@ xfs_defer_alloc( dfp->dfp_ops = ops; INIT_LIST_HEAD(&dfp->dfp_work); list_add_tail(&dfp->dfp_list, &tp->t_dfops); + tp->t_dfops_nr++; return dfp; } @@ -936,6 +944,12 @@ xfs_defer_move( struct xfs_trans *stp) { list_splice_init(&stp->t_dfops, &dtp->t_dfops); + dtp->t_dfops_nr += stp->t_dfops_nr; + dtp->t_dfops_nr_max = stp->t_dfops_nr_max; + dtp->t_dfops_finished = stp->t_dfops_finished; + stp->t_dfops_nr = 0; + stp->t_dfops_nr_max = 0; + stp->t_dfops_finished = 0; /* * Low free space mode was historically controlled by a dfops field. ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/5] xfs: create a noalloc mode for allocation groups 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong 2023-12-27 13:38 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong @ 2023-12-27 13:38 ` Darrick J. Wong 2023-12-27 13:38 ` [PATCH 3/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong ` (2 subsequent siblings) 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:38 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Create a new noalloc state for the per-AG structure that will disable block allocation in this AG. We accomplish this by subtracting from fdblocks all the free blocks in this AG, hiding those blocks from the allocator, and preventing freed blocks from updating fdblocks until we're ready to lift noalloc mode. Note that we reduce the free block count of the filesystem so that we can prevent transactions from entering the allocator looking for "free" space that we've turned off incore. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- include/xfs_trace.h | 2 ++ libxfs/xfs_ag.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ libxfs/xfs_ag.h | 8 +++++++ libxfs/xfs_ag_resv.c | 28 +++++++++++++++++++++-- 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/include/xfs_trace.h b/include/xfs_trace.h index a6ae0ca13b6..c8368227705 100644 --- a/include/xfs_trace.h +++ b/include/xfs_trace.h @@ -13,6 +13,8 @@ #define trace_xfbtree_trans_cancel_buf(...) ((void) 0) #define trace_xfbtree_trans_commit_buf(...) ((void) 0) +#define trace_xfs_ag_clear_noalloc(a) ((void) 0) +#define trace_xfs_ag_set_noalloc(a) ((void) 0) #define trace_xfs_agfl_free_defer(...) ((void) 0) #define trace_xfs_agfl_reset(a,b,c,d) ((void) 0) #define trace_xfs_alloc_cur_check(a,b,c,d,e,f) ((void) 0) diff --git a/libxfs/xfs_ag.c b/libxfs/xfs_ag.c index ac6b0090825..c639e8e07d7 100644 --- a/libxfs/xfs_ag.c +++ b/libxfs/xfs_ag.c @@ -1117,3 +1117,63 @@ xfs_ag_get_geometry( xfs_buf_relse(agi_bp); return error; } + +/* How many blocks does this AG contribute to fdblocks? */ +xfs_extlen_t +xfs_ag_fdblocks( + struct xfs_perag *pag) +{ + xfs_extlen_t ret; + + ASSERT(xfs_perag_initialised_agf(pag)); + + ret = pag->pagf_freeblks + pag->pagf_flcount + pag->pagf_btreeblks; + ret -= pag->pag_meta_resv.ar_reserved; + ret -= pag->pag_rmapbt_resv.ar_orig_reserved; + return ret; +} + +/* + * Hide all the free space in this AG. Caller must hold both the AGI and the + * AGF buffers or have otherwise prevented concurrent access. + */ +int +xfs_ag_set_noalloc( + struct xfs_perag *pag) +{ + struct xfs_mount *mp = pag->pag_mount; + int error; + + ASSERT(xfs_perag_initialised_agf(pag)); + ASSERT(xfs_perag_initialised_agi(pag)); + + if (xfs_perag_prohibits_alloc(pag)) + return 0; + + error = xfs_mod_fdblocks(mp, -(int64_t)xfs_ag_fdblocks(pag), false); + if (error) + return error; + + trace_xfs_ag_set_noalloc(pag); + set_bit(XFS_AGSTATE_NOALLOC, &pag->pag_opstate); + return 0; +} + +/* + * Unhide all the free space in this AG. Caller must hold both the AGI and + * the AGF buffers or have otherwise prevented concurrent access. + */ +void +xfs_ag_clear_noalloc( + struct xfs_perag *pag) +{ + struct xfs_mount *mp = pag->pag_mount; + + if (!xfs_perag_prohibits_alloc(pag)) + return; + + xfs_mod_fdblocks(mp, xfs_ag_fdblocks(pag), false); + + trace_xfs_ag_clear_noalloc(pag); + clear_bit(XFS_AGSTATE_NOALLOC, &pag->pag_opstate); +} diff --git a/libxfs/xfs_ag.h b/libxfs/xfs_ag.h index 79017fcd3df..c21cc30ebc7 100644 --- a/libxfs/xfs_ag.h +++ b/libxfs/xfs_ag.h @@ -131,6 +131,7 @@ struct xfs_perag { #define XFS_AGSTATE_PREFERS_METADATA 2 #define XFS_AGSTATE_ALLOWS_INODES 3 #define XFS_AGSTATE_AGFL_NEEDS_RESET 4 +#define XFS_AGSTATE_NOALLOC 5 #define __XFS_AG_OPSTATE(name, NAME) \ static inline bool xfs_perag_ ## name (struct xfs_perag *pag) \ @@ -143,6 +144,7 @@ __XFS_AG_OPSTATE(initialised_agi, AGI_INIT) __XFS_AG_OPSTATE(prefers_metadata, PREFERS_METADATA) __XFS_AG_OPSTATE(allows_inodes, ALLOWS_INODES) __XFS_AG_OPSTATE(agfl_needs_reset, AGFL_NEEDS_RESET) +__XFS_AG_OPSTATE(prohibits_alloc, NOALLOC) int xfs_initialize_perag(struct xfs_mount *mp, xfs_agnumber_t agcount, xfs_rfsblock_t dcount, xfs_agnumber_t *maxagi); @@ -156,12 +158,18 @@ struct xfs_perag *xfs_perag_get_tag(struct xfs_mount *mp, xfs_agnumber_t agno, struct xfs_perag *xfs_perag_hold(struct xfs_perag *pag); void xfs_perag_put(struct xfs_perag *pag); + /* Active AG references */ struct xfs_perag *xfs_perag_grab(struct xfs_mount *, xfs_agnumber_t); struct xfs_perag *xfs_perag_grab_tag(struct xfs_mount *, xfs_agnumber_t, int tag); void xfs_perag_rele(struct xfs_perag *pag); +/* Enable or disable allocation from an AG */ +xfs_extlen_t xfs_ag_fdblocks(struct xfs_perag *pag); +int xfs_ag_set_noalloc(struct xfs_perag *pag); +void xfs_ag_clear_noalloc(struct xfs_perag *pag); + /* * Per-ag geometry infomation and validation */ diff --git a/libxfs/xfs_ag_resv.c b/libxfs/xfs_ag_resv.c index 5963ff5602b..ddb679bc4ae 100644 --- a/libxfs/xfs_ag_resv.c +++ b/libxfs/xfs_ag_resv.c @@ -20,6 +20,7 @@ #include "xfs_ialloc_btree.h" #include "xfs_ag.h" #include "xfs_ag_resv.h" +#include "xfs_ag.h" /* * Per-AG Block Reservations @@ -72,6 +73,13 @@ xfs_ag_resv_critical( xfs_extlen_t avail; xfs_extlen_t orig; + /* + * Pretend we're critically low on reservations in this AG to scare + * everyone else away. + */ + if (xfs_perag_prohibits_alloc(pag)) + return true; + switch (type) { case XFS_AG_RESV_METADATA: avail = pag->pagf_freeblks - pag->pag_rmapbt_resv.ar_reserved; @@ -114,7 +122,12 @@ xfs_ag_resv_needed( break; case XFS_AG_RESV_IMETA: case XFS_AG_RESV_NONE: - /* empty */ + /* + * In noalloc mode, we pretend that all the free blocks in this + * AG have been allocated. Make this AG look full. + */ + if (xfs_perag_prohibits_alloc(pag)) + len += xfs_ag_fdblocks(pag); break; default: ASSERT(0); @@ -343,6 +356,8 @@ xfs_ag_resv_alloc_extent( xfs_extlen_t len; uint field; + ASSERT(type != XFS_AG_RESV_NONE || !xfs_perag_prohibits_alloc(pag)); + trace_xfs_ag_resv_alloc_extent(pag, type, args->len); switch (type) { @@ -400,7 +415,14 @@ xfs_ag_resv_free_extent( ASSERT(0); fallthrough; case XFS_AG_RESV_NONE: - xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, (int64_t)len); + /* + * Normally we put freed blocks back into fdblocks. In noalloc + * mode, however, we pretend that there are no fdblocks in the + * AG, so don't put them back. + */ + if (!xfs_perag_prohibits_alloc(pag)) + xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, + (int64_t)len); fallthrough; case XFS_AG_RESV_IGNORE: return; @@ -413,6 +435,6 @@ xfs_ag_resv_free_extent( /* Freeing into the reserved pool only requires on-disk update... */ xfs_trans_mod_sb(tp, XFS_TRANS_SB_RES_FDBLOCKS, len); /* ...but freeing beyond that requires in-core and on-disk update. */ - if (len > leftover) + if (len > leftover && !xfs_perag_prohibits_alloc(pag)) xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, len - leftover); } ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 3/5] xfs: enable userspace to hide an AG from allocation 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong 2023-12-27 13:38 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong 2023-12-27 13:38 ` [PATCH 2/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong @ 2023-12-27 13:38 ` Darrick J. Wong 2023-12-27 13:39 ` [PATCH 4/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong 2023-12-27 13:39 ` [PATCH 5/5] xfs_io: enhance the aginfo command to control the noalloc flag Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:38 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add an administrative interface so that userspace can hide an allocation group from block allocation. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- libxfs/xfs_ag.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ libxfs/xfs_fs.h | 5 +++++ 2 files changed, 59 insertions(+) diff --git a/libxfs/xfs_ag.c b/libxfs/xfs_ag.c index c639e8e07d7..abc0a9d89c5 100644 --- a/libxfs/xfs_ag.c +++ b/libxfs/xfs_ag.c @@ -1073,6 +1073,54 @@ xfs_ag_extend_space( return 0; } +/* Compute the AG geometry flags. */ +static inline uint32_t +xfs_ag_calc_geoflags( + struct xfs_perag *pag) +{ + uint32_t ret = 0; + + if (xfs_perag_prohibits_alloc(pag)) + ret |= XFS_AG_FLAG_NOALLOC; + + return ret; +} + +/* + * Compare the current AG geometry flags against the flags in the AG geometry + * structure and update the AG state to reflect any changes, then update the + * struct to reflect the current status. + */ +static inline int +xfs_ag_update_geoflags( + struct xfs_perag *pag, + struct xfs_ag_geometry *ageo, + uint32_t new_flags) +{ + uint32_t old_flags = xfs_ag_calc_geoflags(pag); + int error; + + if (!(new_flags & XFS_AG_FLAG_UPDATE)) { + ageo->ag_flags = old_flags; + return 0; + } + + if ((old_flags & XFS_AG_FLAG_NOALLOC) && + !(new_flags & XFS_AG_FLAG_NOALLOC)) { + xfs_ag_clear_noalloc(pag); + } + + if (!(old_flags & XFS_AG_FLAG_NOALLOC) && + (new_flags & XFS_AG_FLAG_NOALLOC)) { + error = xfs_ag_set_noalloc(pag); + if (error) + return error; + } + + ageo->ag_flags = xfs_ag_calc_geoflags(pag); + return 0; +} + /* Retrieve AG geometry. */ int xfs_ag_get_geometry( @@ -1084,6 +1132,7 @@ xfs_ag_get_geometry( struct xfs_agi *agi; struct xfs_agf *agf; unsigned int freeblks; + uint32_t inflags = ageo->ag_flags; int error; /* Lock the AG headers. */ @@ -1094,6 +1143,10 @@ xfs_ag_get_geometry( if (error) goto out_agi; + error = xfs_ag_update_geoflags(pag, ageo, inflags); + if (error) + goto out; + /* Fill out form. */ memset(ageo, 0, sizeof(*ageo)); ageo->ag_number = pag->pag_agno; @@ -1111,6 +1164,7 @@ xfs_ag_get_geometry( ageo->ag_freeblks = freeblks; xfs_ag_geom_health(pag, ageo); +out: /* Release resources. */ xfs_buf_relse(agf_bp); out_agi: diff --git a/libxfs/xfs_fs.h b/libxfs/xfs_fs.h index 4159e96d01a..96688f9301e 100644 --- a/libxfs/xfs_fs.h +++ b/libxfs/xfs_fs.h @@ -305,6 +305,11 @@ struct xfs_ag_geometry { #define XFS_AG_GEOM_SICK_REFCNTBT (1 << 9) /* reference counts */ #define XFS_AG_GEOM_SICK_INODES (1 << 10) /* bad inodes were seen */ +#define XFS_AG_FLAG_UPDATE (1 << 0) /* update flags */ +#define XFS_AG_FLAG_NOALLOC (1 << 1) /* do not allocate from this AG */ +#define XFS_AG_FLAG_ALL (XFS_AG_FLAG_UPDATE | \ + XFS_AG_FLAG_NOALLOC) + /* * Structures for XFS_IOC_FSGROWFSDATA, XFS_IOC_FSGROWFSLOG & XFS_IOC_FSGROWFSRT */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 4/5] xfs: apply noalloc mode to inode allocations too 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong ` (2 preceding siblings ...) 2023-12-27 13:38 ` [PATCH 3/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong @ 2023-12-27 13:39 ` Darrick J. Wong 2023-12-27 13:39 ` [PATCH 5/5] xfs_io: enhance the aginfo command to control the noalloc flag Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:39 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Don't allow inode allocations from this group if it's marked noalloc. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- libxfs/xfs_ialloc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libxfs/xfs_ialloc.c b/libxfs/xfs_ialloc.c index 935f8127c0e..09e494f4089 100644 --- a/libxfs/xfs_ialloc.c +++ b/libxfs/xfs_ialloc.c @@ -1059,6 +1059,7 @@ xfs_dialloc_ag_inobt( ASSERT(xfs_perag_initialised_agi(pag)); ASSERT(xfs_perag_allows_inodes(pag)); + ASSERT(!xfs_perag_prohibits_alloc(pag)); ASSERT(pag->pagi_freecount > 0); restart_pagno: @@ -1687,6 +1688,8 @@ xfs_dialloc_good_ag( return false; if (!xfs_perag_allows_inodes(pag)) return false; + if (xfs_perag_prohibits_alloc(pag)) + return false; if (!xfs_perag_initialised_agi(pag)) { error = xfs_ialloc_read_agi(pag, tp, NULL); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 5/5] xfs_io: enhance the aginfo command to control the noalloc flag 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong ` (3 preceding siblings ...) 2023-12-27 13:39 ` [PATCH 4/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong @ 2023-12-27 13:39 ` Darrick J. Wong 4 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:39 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Augment the aginfo command to be able to set and clear the noalloc state for an AG. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- io/aginfo.c | 45 ++++++++++++++++++++++++++++++++++++++++----- man/man8/xfs_io.8 | 6 +++++- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/io/aginfo.c b/io/aginfo.c index 43e0e9c21b8..8345d25c559 100644 --- a/io/aginfo.c +++ b/io/aginfo.c @@ -19,9 +19,11 @@ static cmdinfo_t rginfo_cmd; static int report_aginfo( struct xfs_fd *xfd, - xfs_agnumber_t agno) + xfs_agnumber_t agno, + int oflag) { struct xfs_ag_geometry ageo = { 0 }; + bool update = false; int ret; ret = -xfrog_ag_geometry(xfd->fd, agno, &ageo); @@ -30,6 +32,26 @@ report_aginfo( return 1; } + switch (oflag) { + case 0: + ageo.ag_flags |= XFS_AG_FLAG_UPDATE; + ageo.ag_flags &= ~XFS_AG_FLAG_NOALLOC; + update = true; + break; + case 1: + ageo.ag_flags |= (XFS_AG_FLAG_UPDATE | XFS_AG_FLAG_NOALLOC); + update = true; + break; + } + + if (update) { + ret = -xfrog_ag_geometry(xfd->fd, agno, &ageo); + if (ret) { + xfrog_perror(ret, "aginfo update"); + return 1; + } + } + printf(_("AG: %u\n"), ageo.ag_number); printf(_("Blocks: %u\n"), ageo.ag_length); printf(_("Free Blocks: %u\n"), ageo.ag_freeblks); @@ -51,6 +73,7 @@ aginfo_f( struct xfs_fd xfd = XFS_FD_INIT(file->fd); unsigned long long x; xfs_agnumber_t agno = NULLAGNUMBER; + int oflag = -1; int c; int ret = 0; @@ -61,7 +84,7 @@ aginfo_f( return 1; } - while ((c = getopt(argc, argv, "a:")) != EOF) { + while ((c = getopt(argc, argv, "a:o:")) != EOF) { switch (c) { case 'a': errno = 0; @@ -74,16 +97,27 @@ aginfo_f( } agno = x; break; + case 'o': + errno = 0; + x = strtoll(optarg, NULL, 10); + if (!errno && x != 0 && x != 1) + errno = ERANGE; + if (errno) { + perror("aginfo"); + return 1; + } + oflag = x; + break; default: return command_usage(&aginfo_cmd); } } if (agno != NULLAGNUMBER) { - ret = report_aginfo(&xfd, agno); + ret = report_aginfo(&xfd, agno, oflag); } else { for (agno = 0; !ret && agno < xfd.fsgeom.agcount; agno++) { - ret = report_aginfo(&xfd, agno); + ret = report_aginfo(&xfd, agno, oflag); } } @@ -98,6 +132,7 @@ aginfo_help(void) "Report allocation group geometry.\n" "\n" " -a agno -- Report on the given allocation group.\n" +" -o state -- Change the NOALLOC state for this allocation group.\n" "\n")); } @@ -107,7 +142,7 @@ static cmdinfo_t aginfo_cmd = { .cfunc = aginfo_f, .argmin = 0, .argmax = -1, - .args = "[-a agno]", + .args = "[-a agno] [-o state]", .flags = CMD_NOMAP_OK, .help = aginfo_help, }; diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8 index f94de0ce40f..4ae47555ae3 100644 --- a/man/man8/xfs_io.8 +++ b/man/man8/xfs_io.8 @@ -1240,7 +1240,7 @@ for the current memory mapping. .SH FILESYSTEM COMMANDS .TP -.BI "aginfo [ \-a " agno " ]" +.BI "aginfo [ \-a " agno " ] [ \-o " nr " ]" Show information about or update the state of allocation groups. .RE .RS 1.0i @@ -1248,6 +1248,10 @@ Show information about or update the state of allocation groups. .TP .BI \-a Act only on a specific allocation group. +.TP +.BI \-o +If 0, clear the NOALLOC flag. +If 1, set the NOALLOC flag. .PD .RE ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 2/3] xfsprogs: report refcount information to userspace 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (5 preceding siblings ...) 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong @ 2023-12-31 19:56 ` Darrick J. Wong 2023-12-27 13:39 ` [PATCH 1/2] xfs: export reference count " Darrick J. Wong 2023-12-27 13:39 ` [PATCH 2/2] xfs_io: dump reference count information Darrick J. Wong 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (2 subsequent siblings) 9 siblings, 2 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:56 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs Hi all, Create a new ioctl to report the number of owners of each disk block so that reflink-aware defraggers can make better decisions about which extents to target. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=report-refcounts xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=report-refcounts fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=report-refcounts --- io/Makefile | 2 io/fsrefcounts.c | 477 +++++++++++++++++++++++++++++++++++ io/init.c | 1 io/io.h | 1 libxfs/xfs_fs_staging.h | 82 ++++++ man/man2/ioctl_xfs_getfsrefcounts.2 | 239 ++++++++++++++++++ man/man8/xfs_io.8 | 88 ++++++ 7 files changed, 889 insertions(+), 1 deletion(-) create mode 100644 io/fsrefcounts.c create mode 100644 man/man2/ioctl_xfs_getfsrefcounts.2 ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/2] xfs: export reference count information to userspace 2023-12-31 19:56 ` [PATCHSET 2/3] xfsprogs: report refcount information to userspace Darrick J. Wong @ 2023-12-27 13:39 ` Darrick J. Wong 2023-12-27 13:39 ` [PATCH 2/2] xfs_io: dump reference count information Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:39 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Export refcount info to userspace so we can prototype a sharing-aware defrag/fs rearranging tool. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- libxfs/xfs_fs_staging.h | 82 ++++++++++++ man/man2/ioctl_xfs_getfsrefcounts.2 | 239 +++++++++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 man/man2/ioctl_xfs_getfsrefcounts.2 diff --git a/libxfs/xfs_fs_staging.h b/libxfs/xfs_fs_staging.h index 9f0c03103f0..f7d872f8a88 100644 --- a/libxfs/xfs_fs_staging.h +++ b/libxfs/xfs_fs_staging.h @@ -221,4 +221,86 @@ struct xfs_rtgroup_geometry { #define XFS_IOC_RTGROUP_GEOMETRY _IOWR('X', 63, struct xfs_rtgroup_geometry) +/* + * Structure for XFS_IOC_GETFSREFCOUNTS. + * + * The memory layout for this call are the scalar values defined in struct + * xfs_getfsrefs_head, followed by two struct xfs_getfsrefs that describe + * the lower and upper bound of mappings to return, followed by an array + * of struct xfs_getfsrefs mappings. + * + * fch_iflags control the output of the call, whereas fch_oflags report + * on the overall record output. fch_count should be set to the length + * of the fch_recs array, and fch_entries will be set to the number of + * entries filled out during each call. If fch_count is zero, the number + * of refcount mappings will be returned in fch_entries, though no + * mappings will be returned. fch_reserved must be set to zero. + * + * The two elements in the fch_keys array are used to constrain the + * output. The first element in the array should represent the lowest + * disk mapping ("low key") that the user wants to learn about. If this + * value is all zeroes, the filesystem will return the first entry it + * knows about. For a subsequent call, the contents of + * fsrefs_head.fch_recs[fsrefs_head.fch_count - 1] should be copied into + * fch_keys[0] to have the kernel start where it left off. + * + * The second element in the fch_keys array should represent the highest + * disk mapping ("high key") that the user wants to learn about. If this + * value is all ones, the filesystem will not stop until it runs out of + * mapping to return or runs out of space in fch_recs. + * + * fcr_device can be either a 32-bit cookie representing a device, or a + * 32-bit dev_t if the FCH_OF_DEV_T flag is set. fcr_physical and + * fcr_length are expressed in units of bytes. fcr_owners is the number + * of owners. + */ +struct xfs_getfsrefs { + __u32 fcr_device; /* device id */ + __u32 fcr_flags; /* mapping flags */ + __u64 fcr_physical; /* device offset of segment */ + __u64 fcr_owners; /* number of owners */ + __u64 fcr_length; /* length of segment */ + __u64 fcr_reserved[4]; /* must be zero */ +}; + +struct xfs_getfsrefs_head { + __u32 fch_iflags; /* control flags */ + __u32 fch_oflags; /* output flags */ + __u32 fch_count; /* # of entries in array incl. input */ + __u32 fch_entries; /* # of entries filled in (output). */ + __u64 fch_reserved[6]; /* must be zero */ + + struct xfs_getfsrefs fch_keys[2]; /* low and high keys for the mapping search */ + struct xfs_getfsrefs fch_recs[]; /* returned records */ +}; + +/* Size of an fsrefs_head with room for nr records. */ +static inline unsigned long long +xfs_getfsrefs_sizeof( + unsigned int nr) +{ + return sizeof(struct xfs_getfsrefs_head) + nr * sizeof(struct xfs_getfsrefs); +} + +/* Start the next fsrefs query at the end of the current query results. */ +static inline void +xfs_getfsrefs_advance( + struct xfs_getfsrefs_head *head) +{ + head->fch_keys[0] = head->fch_recs[head->fch_entries - 1]; +} + +/* fch_iflags values - set by XFS_IOC_GETFSREFCOUNTS caller in the header. */ +/* no flags defined yet */ +#define FCH_IF_VALID 0 + +/* fch_oflags values - returned in the header segment only. */ +#define FCH_OF_DEV_T (1U << 0) /* fcr_device values will be dev_t */ + +/* fcr_flags values - returned for each non-header segment */ +#define FCR_OF_LAST (1U << 0) /* segment is the last in the dataset */ + +/* XXX stealing XFS_IOC_GETBIOSIZE */ +#define XFS_IOC_GETFSREFCOUNTS _IOWR('X', 47, struct xfs_getfsrefs_head) + #endif /* __XFS_FS_STAGING_H__ */ diff --git a/man/man2/ioctl_xfs_getfsrefcounts.2 b/man/man2/ioctl_xfs_getfsrefcounts.2 new file mode 100644 index 00000000000..2c7cfada190 --- /dev/null +++ b/man/man2/ioctl_xfs_getfsrefcounts.2 @@ -0,0 +1,239 @@ +.\" Copyright (c) 2021-2024 Oracle. All rights reserved. +.\" +.\" %%%LICENSE_START(GPLv2+_DOC_FULL) +.\" This is free documentation; you can redistribute it and/or +.\" modify it under the terms of the GNU General Public License as +.\" published by the Free Software Foundation; either version 2 of +.\" the License, or (at your option) any later version. +.\" +.\" The GNU General Public License's references to "object code" +.\" and "executables" are to be interpreted as the output of any +.\" document formatting or typesetting system, including +.\" intermediate and printed output. +.\" +.\" This manual is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public +.\" License along with this manual; if not, see +.\" <http://www.gnu.org/licenses/>. +.\" %%%LICENSE_END +.TH IOCTL-XFS-GETFSREFCOUNTS 2 2023-05-08 "XFS" +.SH NAME +ioctl_xfs_getfsrefcounts \- retrieve the number of owners of space in the filesystem +.SH SYNOPSIS +.nf +.B #include <sys/ioctl.h> +.br +.B #include <xfs/xfs_fs_staging.h> +.PP +.BI "int ioctl(int " fd ", XFS_IOC_GETFSREFCOUNTS, struct xfs_fsrefs_head * " arg ); +.fi +.SH DESCRIPTION +This +.BR ioctl (2) +operation retrieves the number of owners for space extents in a filesystem. +This information can be used to discover the sharing factor of physical media, +among other things. +.PP +The sole argument to this operation should be a pointer to a single +.IR "struct xfs_getfsrefs_head" ":" +.PP +.in +4n +.EX +struct xfs_getfsrefs { + __u32 fcr_device; /* Device ID */ + __u32 fcr_flags; /* Mapping flags */ + __u64 fcr_physical; /* Device offset of segment */ + __u64 fcr_owners; /* Number of Owners */ + __u64 fcr_length; /* Length of segment */ + __u64 fcr_reserved[4]; /* Must be zero */ +}; + +struct xfs_getfsrefs_head { + __u32 fch_iflags; /* Control flags */ + __u32 fch_oflags; /* Output flags */ + __u32 fch_count; /* # of entries in array incl. input */ + __u32 fch_entries; /* # of entries filled in (output) */ + __u64 fch_reserved[6]; /* Must be zero */ + + struct xfs_getfsrefs fch_keys[2]; /* Low and high keys for + the mapping search */ + struct xfs_getfsrefs fch_recs[]; /* Returned records */ +}; +.EE +.in +.PP +The two +.I fch_keys +array elements specify the lowest and highest reverse-mapping +key for which the application would like physical mapping +information. +A reverse mapping key consists of the tuple (device, block, owner, offset). +The owner and offset fields are part of the key because some filesystems +support sharing physical blocks between multiple files and +therefore may return multiple mappings for a given physical block. +.PP +Filesystem mappings are copied into the +.I fch_recs +array, which immediately follows the header data. +.\" +.SS Fields of struct xfs_getfsrefs_head +The +.I fch_iflags +field is a bit mask passed to the kernel to alter the output. +No flags are currently defined, so the caller must set this value to zero. +.PP +The +.I fch_oflags +field is a bit mask of flags set by the kernel concerning the returned mappings. +If +.B FCH_OF_DEV_T +is set, then the +.I fcr_device +field represents a +.I dev_t +structure containing the major and minor numbers of the block device. +.PP +The +.I fch_count +field contains the number of elements in the array being passed to the +kernel. +If this value is 0, +.I fch_entries +will be set to the number of records that would have been returned had +the array been large enough; +no mapping information will be returned. +.PP +The +.I fch_entries +field contains the number of elements in the +.I fch_recs +array that contain useful information. +.PP +The +.I fch_reserved +fields must be set to zero. +.\" +.SS Keys +The two key records in +.I fsrefs_head.fch_keys +specify the lowest and highest extent records in the keyspace that the caller +wants returned. +The tuple +.RI "(" "device" ", " "physical" ", " "flags" ")" +can be used to index any filesystem space record. +The format of +.I fcr_device +in the keys must match the format of the same field in the output records, +as defined below. +By convention, the field +.I fsrefs_head.fch_keys[0] +must contain the low key and +.I fsrefs_head.fch_keys[1] +must contain the high key for the request. +.PP +For convenience, if +.I fcr_length +is set in the low key, it will be added to +.I fcr_block +as appropriate. +The caller can take advantage of this subtlety to set up subsequent calls +by copying +.I fsrefs_head.fch_recs[fsrefs_head.fch_entries \- 1] +into the low key. +The function +.I fsrefs_advance +(defined in +.IR linux/fsrefcounts.h ) +provides this functionality. +.\" +.SS Fields of struct xfs_getfsrefs +The +.I fcr_device +field uniquely identifies the underlying storage device. +If the +.B FCH_OF_DEV_T +flag is set in the header's +.I fch_oflags +field, this field contains a +.I dev_t +from which major and minor numbers can be extracted. +If the flag is not set, this field contains a value that must be unique +for each unique storage device. +.PP +The +.I fcr_physical +field contains the disk address of the extent in bytes. +.PP +The +.I fcr_owners +field contains the number of owners of this extent. +The actual owners can be queried with the +.BR FS_IOC_GETFSMAP (2) +ioctl. +.PP +The +.I fcr_length +field contains the length of the extent in bytes. +.PP +The +.I fcr_flags +field is a bit mask of extent state flags. +The bits are: +.RS 0.4i +.TP +.B FCR_OF_LAST +This is the last record in the data set. +.RE +.PP +The +.I fcr_reserved +field will be set to zero. +.\" +.RE +.SH RETURN VALUE +On error, \-1 is returned, and +.I errno +is set to indicate the error. +.SH ERRORS +The error placed in +.I errno +can be one of, but is not limited to, the following: +.TP +.B EBADF +.IR fd +is not open for reading. +.TP +.B EBADMSG +The filesystem has detected a checksum error in the metadata. +.TP +.B EFAULT +The pointer passed in was not mapped to a valid memory address. +.TP +.B EINVAL +The array is not long enough, the keys do not point to a valid part of +the filesystem, the low key points to a higher point in the filesystem's +physical storage address space than the high key, or a nonzero value +was passed in one of the fields that must be zero. +.TP +.B ENOMEM +Insufficient memory to process the request. +.TP +.B EOPNOTSUPP +The filesystem does not support this command. +.TP +.B EUCLEAN +The filesystem metadata is corrupt and needs repair. +.SH CONFORMING TO +This API is XFS-specific. +.SH EXAMPLES +See +.I io/fsrefs.c +in the +.I xfsprogs +distribution for a sample program. +.SH SEE ALSO +.BR ioctl (2) ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/2] xfs_io: dump reference count information 2023-12-31 19:56 ` [PATCHSET 2/3] xfsprogs: report refcount information to userspace Darrick J. Wong 2023-12-27 13:39 ` [PATCH 1/2] xfs: export reference count " Darrick J. Wong @ 2023-12-27 13:39 ` Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:39 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Dump refcount info from the kernel so we can prototype a sharing-aware defrag/fs rearranging tool. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- io/Makefile | 2 io/fsrefcounts.c | 477 +++++++++++++++++++++++++++++++++++++++++++++++++++++ io/init.c | 1 io/io.h | 1 man/man8/xfs_io.8 | 88 ++++++++++ 5 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 io/fsrefcounts.c diff --git a/io/Makefile b/io/Makefile index 32a40294358..a2faa22e1ec 100644 --- a/io/Makefile +++ b/io/Makefile @@ -13,7 +13,7 @@ CFILES = init.c aginfo.c \ file.c freeze.c fsuuid.c fsync.c getrusage.c imap.c inject.c label.c \ link.c mmap.c open.c parent.c pread.c prealloc.c pwrite.c reflink.c \ resblks.c scrub.c seek.c shutdown.c stat.c swapext.c sync.c \ - truncate.c utimes.c atomicupdate.c + truncate.c utimes.c atomicupdate.c fsrefcounts.c LLDLIBS = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG) $(LIBPTHREAD) $(LIBUUID) LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG) diff --git a/io/fsrefcounts.c b/io/fsrefcounts.c new file mode 100644 index 00000000000..2dac334b3ea --- /dev/null +++ b/io/fsrefcounts.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#include "platform_defs.h" +#include "command.h" +#include "init.h" +#include "libfrog/paths.h" +#include "io.h" +#include "input.h" +#include "libfrog/fsgeom.h" + +static cmdinfo_t fsrefcounts_cmd; +static dev_t xfs_data_dev; + +static void +fsrefcounts_help(void) +{ + printf(_( +"\n" +" Prints extent owner counts for the filesystem hosting the current file" +"\n" +" fsrefcounts prints the number of owners of disk blocks used by the whole\n" +" filesystem. When possible, owner and offset information will be included\n" +" in the space report.\n" +"\n" +" By default, each line of the listing takes the following form:\n" +" extent: major:minor [startblock..endblock]: owner startoffset..endoffset length\n" +" All the file offsets and disk blocks are in units of 512-byte blocks.\n" +" -d -- query only the data device (default).\n" +" -l -- query only the log device.\n" +" -r -- query only the realtime device.\n" +" -n -- query n extents at a time.\n" +" -o -- only print extents with at least this many owners (default 1).\n" +" -O -- only print extents with no more than this many owners (default 2^64-1).\n" +" -m -- output machine-readable format.\n" +" -v -- Verbose information, show AG and offsets. Show flags legend on 2nd -v\n" +"\n" +"The optional start and end arguments require one of -d, -l, or -r to be set.\n" +"\n")); +} + +static void +dump_refcounts( + unsigned long long *nr, + const unsigned long long min_owners, + const unsigned long long max_owners, + struct xfs_getfsrefs_head *head) +{ + unsigned long long i; + struct xfs_getfsrefs *p; + + for (i = 0, p = head->fch_recs; i < head->fch_entries; i++, p++) { + if (p->fcr_owners < min_owners || p->fcr_owners > max_owners) + continue; + printf("\t%llu: %u:%u [%lld..%lld]: ", i + (*nr), + major(p->fcr_device), minor(p->fcr_device), + (long long)BTOBBT(p->fcr_physical), + (long long)BTOBBT(p->fcr_physical + p->fcr_length - 1)); + printf(_("%llu %lld\n"), + (unsigned long long)p->fcr_owners, + (long long)BTOBBT(p->fcr_length)); + } + + (*nr) += head->fch_entries; +} + +static void +dump_refcounts_machine( + unsigned long long *nr, + const unsigned long long min_owners, + const unsigned long long max_owners, + struct xfs_getfsrefs_head *head) +{ + unsigned long long i; + struct xfs_getfsrefs *p; + + if (*nr == 0) + printf(_("EXT,MAJOR,MINOR,PSTART,PEND,OWNERS,LENGTH\n")); + for (i = 0, p = head->fch_recs; i < head->fch_entries; i++, p++) { + if (p->fcr_owners < min_owners || p->fcr_owners > max_owners) + continue; + printf("%llu,%u,%u,%lld,%lld,", i + (*nr), + major(p->fcr_device), minor(p->fcr_device), + (long long)BTOBBT(p->fcr_physical), + (long long)BTOBBT(p->fcr_physical + p->fcr_length - 1)); + printf("%llu,%lld\n", + (unsigned long long)p->fcr_owners, + (long long)BTOBBT(p->fcr_length)); + } + + (*nr) += head->fch_entries; +} + +/* + * Verbose mode displays: + * extent: major:minor [startblock..endblock]: owners \ + * ag# (agoffset..agendoffset) totalbbs flags + */ +#define MINRANGE_WIDTH 16 +#define MINAG_WIDTH 2 +#define MINTOT_WIDTH 5 +#define NFLG 4 /* count of flags */ +#define FLG_NULL 00000 /* Null flag */ +#define FLG_BSU 01000 /* Not on begin of stripe unit */ +#define FLG_ESU 00100 /* Not on end of stripe unit */ +#define FLG_BSW 00010 /* Not on begin of stripe width */ +#define FLG_ESW 00001 /* Not on end of stripe width */ +static void +dump_refcounts_verbose( + unsigned long long *nr, + const unsigned long long min_owners, + const unsigned long long max_owners, + struct xfs_getfsrefs_head *head, + bool *dumped_flags, + struct xfs_fsop_geom *fsgeo) +{ + unsigned long long i; + struct xfs_getfsrefs *p; + int agno; + off64_t agoff, bperag; + int boff_w, aoff_w, tot_w, agno_w, own_w; + int nr_w, dev_w; + char bbuf[40], abuf[40], obuf[40]; + char nbuf[40], dbuf[40], gbuf[40]; + int sunit, swidth; + int flg = 0; + + boff_w = aoff_w = own_w = MINRANGE_WIDTH; + dev_w = 3; + nr_w = 4; + tot_w = MINTOT_WIDTH; + bperag = (off64_t)fsgeo->agblocks * + (off64_t)fsgeo->blocksize; + sunit = (fsgeo->sunit * fsgeo->blocksize); + swidth = (fsgeo->swidth * fsgeo->blocksize); + + /* + * Go through the extents and figure out the width + * needed for all columns. + */ + for (i = 0, p = head->fch_recs; i < head->fch_entries; i++, p++) { + if (p->fcr_owners < min_owners || p->fcr_owners > max_owners) + continue; + if (sunit && + (p->fcr_physical % sunit != 0 || + ((p->fcr_physical + p->fcr_length) % sunit) != 0 || + p->fcr_physical % swidth != 0 || + ((p->fcr_physical + p->fcr_length) % swidth) != 0)) + flg = 1; + if (flg) + *dumped_flags = true; + snprintf(nbuf, sizeof(nbuf), "%llu", (*nr) + i); + nr_w = max(nr_w, strlen(nbuf)); + if (head->fch_oflags & FCH_OF_DEV_T) + snprintf(dbuf, sizeof(dbuf), "%u:%u", + major(p->fcr_device), + minor(p->fcr_device)); + else + snprintf(dbuf, sizeof(dbuf), "0x%x", p->fcr_device); + dev_w = max(dev_w, strlen(dbuf)); + snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:", + (long long)BTOBBT(p->fcr_physical), + (long long)BTOBBT(p->fcr_physical + p->fcr_length - 1)); + boff_w = max(boff_w, strlen(bbuf)); + snprintf(obuf, sizeof(obuf), "%llu", + (unsigned long long)p->fcr_owners); + own_w = max(own_w, strlen(obuf)); + if (p->fcr_device == xfs_data_dev) { + agno = p->fcr_physical / bperag; + agoff = p->fcr_physical - (agno * bperag); + snprintf(abuf, sizeof(abuf), + "(%lld..%lld)", + (long long)BTOBBT(agoff), + (long long)BTOBBT(agoff + p->fcr_length - 1)); + } else + abuf[0] = 0; + aoff_w = max(aoff_w, strlen(abuf)); + tot_w = max(tot_w, + numlen(BTOBBT(p->fcr_length), 10)); + } + agno_w = max(MINAG_WIDTH, numlen(fsgeo->agcount, 10)); + if (*nr == 0) + printf("%*s: %-*s %-*s %-*s %*s %-*s %*s%s\n", + nr_w, _("EXT"), + dev_w, _("DEV"), + boff_w, _("BLOCK-RANGE"), + own_w, _("OWNERS"), + agno_w, _("AG"), + aoff_w, _("AG-OFFSET"), + tot_w, _("TOTAL"), + flg ? _(" FLAGS") : ""); + for (i = 0, p = head->fch_recs; i < head->fch_entries; i++, p++) { + if (p->fcr_owners < min_owners || p->fcr_owners > max_owners) + continue; + flg = FLG_NULL; + /* + * If striping enabled, determine if extent starts/ends + * on a stripe unit boundary. + */ + if (sunit) { + if (p->fcr_physical % sunit != 0) + flg |= FLG_BSU; + if (((p->fcr_physical + + p->fcr_length ) % sunit ) != 0) + flg |= FLG_ESU; + if (p->fcr_physical % swidth != 0) + flg |= FLG_BSW; + if (((p->fcr_physical + + p->fcr_length ) % swidth ) != 0) + flg |= FLG_ESW; + } + if (head->fch_oflags & FCH_OF_DEV_T) + snprintf(dbuf, sizeof(dbuf), "%u:%u", + major(p->fcr_device), + minor(p->fcr_device)); + else + snprintf(dbuf, sizeof(dbuf), "0x%x", p->fcr_device); + snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:", + (long long)BTOBBT(p->fcr_physical), + (long long)BTOBBT(p->fcr_physical + p->fcr_length - 1)); + snprintf(obuf, sizeof(obuf), "%llu", + (unsigned long long)p->fcr_owners); + if (p->fcr_device == xfs_data_dev) { + agno = p->fcr_physical / bperag; + agoff = p->fcr_physical - (agno * bperag); + snprintf(abuf, sizeof(abuf), + "(%lld..%lld)", + (long long)BTOBBT(agoff), + (long long)BTOBBT(agoff + p->fcr_length - 1)); + snprintf(gbuf, sizeof(gbuf), + "%lld", + (long long)agno); + } else { + abuf[0] = 0; + gbuf[0] = 0; + } + printf("%*llu: %-*s %-*s %-*s %-*s %-*s %*lld", + nr_w, (*nr) + i, dev_w, dbuf, boff_w, bbuf, own_w, + obuf, agno_w, gbuf, aoff_w, abuf, tot_w, + (long long)BTOBBT(p->fcr_length)); + if (flg == FLG_NULL) + printf("\n"); + else + printf(" %-*.*o\n", NFLG, NFLG, flg); + } + + (*nr) += head->fch_entries; +} + +static void +dump_verbose_key(void) +{ + printf(_(" FLAG Values:\n")); + printf(_(" %*.*o Doesn't begin on stripe unit\n"), + NFLG+1, NFLG+1, FLG_BSU); + printf(_(" %*.*o Doesn't end on stripe unit\n"), + NFLG+1, NFLG+1, FLG_ESU); + printf(_(" %*.*o Doesn't begin on stripe width\n"), + NFLG+1, NFLG+1, FLG_BSW); + printf(_(" %*.*o Doesn't end on stripe width\n"), + NFLG+1, NFLG+1, FLG_ESW); +} + +static int +fsrefcounts_f( + int argc, + char **argv) +{ + struct xfs_getfsrefs *p; + struct xfs_getfsrefs_head *head; + struct xfs_getfsrefs *l, *h; + struct xfs_fsop_geom fsgeo; + long long start = 0; + long long end = -1; + unsigned long long min_owners = 1; + unsigned long long max_owners = ULLONG_MAX; + int map_size; + int nflag = 0; + int vflag = 0; + int mflag = 0; + int i = 0; + int c; + unsigned long long nr = 0; + size_t fsblocksize, fssectsize; + struct fs_path *fs; + static bool tab_init; + bool dumped_flags = false; + int dflag, lflag, rflag; + + init_cvtnum(&fsblocksize, &fssectsize); + + dflag = lflag = rflag = 0; + while ((c = getopt(argc, argv, "dlmn:o:O:rv")) != EOF) { + switch (c) { + case 'd': /* data device */ + dflag = 1; + break; + case 'l': /* log device */ + lflag = 1; + break; + case 'm': /* machine readable format */ + mflag++; + break; + case 'n': /* number of extents specified */ + nflag = cvt_u32(optarg, 10); + if (errno) + return command_usage(&fsrefcounts_cmd); + break; + case 'o': /* minimum owners */ + min_owners = cvt_u64(optarg, 10); + if (errno) + return command_usage(&fsrefcounts_cmd); + if (min_owners < 1) { + fprintf(stderr, + _("min_owners must be greater than zero.\n")); + exitcode = 1; + return 0; + } + break; + case 'O': /* maximum owners */ + max_owners = cvt_u64(optarg, 10); + if (errno) + return command_usage(&fsrefcounts_cmd); + if (max_owners < 1) { + fprintf(stderr, + _("max_owners must be greater than zero.\n")); + exitcode = 1; + return 0; + } + break; + case 'r': /* rt device */ + rflag = 1; + break; + case 'v': /* Verbose output */ + vflag++; + break; + default: + exitcode = 1; + return command_usage(&fsrefcounts_cmd); + } + } + + if ((dflag + lflag + rflag > 1) || (mflag > 0 && vflag > 0) || + (argc > optind && dflag + lflag + rflag == 0)) { + exitcode = 1; + return command_usage(&fsrefcounts_cmd); + } + + if (argc > optind) { + start = cvtnum(fsblocksize, fssectsize, argv[optind]); + if (start < 0) { + fprintf(stderr, + _("Bad refcount start_bblock %s.\n"), + argv[optind]); + exitcode = 1; + return 0; + } + start <<= BBSHIFT; + } + + if (argc > optind + 1) { + end = cvtnum(fsblocksize, fssectsize, argv[optind + 1]); + if (end < 0) { + fprintf(stderr, + _("Bad refcount end_bblock %s.\n"), + argv[optind + 1]); + exitcode = 1; + return 0; + } + end <<= BBSHIFT; + } + + if (vflag) { + c = -xfrog_geometry(file->fd, &fsgeo); + if (c) { + fprintf(stderr, + _("%s: can't get geometry [\"%s\"]: %s\n"), + progname, file->name, strerror(c)); + exitcode = 1; + return 0; + } + } + + map_size = nflag ? nflag : 131072 / sizeof(struct xfs_getfsrefs); + head = malloc(xfs_getfsrefs_sizeof(map_size)); + if (head == NULL) { + fprintf(stderr, _("%s: malloc of %llu bytes failed.\n"), + progname, + (unsigned long long)xfs_getfsrefs_sizeof(map_size)); + exitcode = 1; + return 0; + } + + memset(head, 0, sizeof(*head)); + l = head->fch_keys; + h = head->fch_keys + 1; + if (dflag) { + l->fcr_device = h->fcr_device = file->fs_path.fs_datadev; + } else if (lflag) { + l->fcr_device = h->fcr_device = file->fs_path.fs_logdev; + } else if (rflag) { + l->fcr_device = h->fcr_device = file->fs_path.fs_rtdev; + } else { + l->fcr_device = 0; + h->fcr_device = UINT_MAX; + } + l->fcr_physical = start; + h->fcr_physical = end; + h->fcr_owners = ULLONG_MAX; + h->fcr_flags = UINT_MAX; + + /* + * If this is an XFS filesystem, remember the data device. + * (We report AG number/block for data device extents on XFS). + */ + if (!tab_init) { + fs_table_initialise(0, NULL, 0, NULL); + tab_init = true; + } + fs = fs_table_lookup(file->name, FS_MOUNT_POINT); + xfs_data_dev = fs ? fs->fs_datadev : 0; + + head->fch_count = map_size; + do { + /* Get some extents */ + i = ioctl(file->fd, XFS_IOC_GETFSREFCOUNTS, head); + if (i < 0) { + fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETFSREFCOUNTS)" + " iflags=0x%x [\"%s\"]: %s\n"), + progname, head->fch_iflags, file->name, + strerror(errno)); + free(head); + exitcode = 1; + return 0; + } + + if (head->fch_entries == 0) + break; + + if (vflag) + dump_refcounts_verbose(&nr, min_owners, max_owners, + head, &dumped_flags, &fsgeo); + else if (mflag) + dump_refcounts_machine(&nr, min_owners, max_owners, + head); + else + dump_refcounts(&nr, min_owners, max_owners, head); + + p = &head->fch_recs[head->fch_entries - 1]; + if (p->fcr_flags & FCR_OF_LAST) + break; + xfs_getfsrefs_advance(head); + } while (true); + + if (dumped_flags) + dump_verbose_key(); + + free(head); + return 0; +} + +void +fsrefcounts_init(void) +{ + fsrefcounts_cmd.name = "fsrefcounts"; + fsrefcounts_cmd.cfunc = fsrefcounts_f; + fsrefcounts_cmd.argmin = 0; + fsrefcounts_cmd.argmax = -1; + fsrefcounts_cmd.flags = CMD_NOMAP_OK | CMD_FLAG_FOREIGN_OK; + fsrefcounts_cmd.args = _("[-d|-l|-r] [-m|-v] [-n nx] [start] [end]"); + fsrefcounts_cmd.oneline = _("print filesystem owner counts for a range of blocks"); + fsrefcounts_cmd.help = fsrefcounts_help; + + add_command(&fsrefcounts_cmd); +} diff --git a/io/init.c b/io/init.c index dee09b56333..b13847e8139 100644 --- a/io/init.c +++ b/io/init.c @@ -59,6 +59,7 @@ init_commands(void) freeze_init(); fsmap_init(); fsuuid_init(); + fsrefcounts_init(); fsync_init(); getrusage_init(); help_init(); diff --git a/io/io.h b/io/io.h index 61920fcee7f..9b2b8c4c4ab 100644 --- a/io/io.h +++ b/io/io.h @@ -191,3 +191,4 @@ extern void crc32cselftest_init(void); extern void bulkstat_init(void); extern void atomicupdate_init(void); extern void aginfo_init(void); +extern void fsrefcounts_init(void); diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8 index 4ae47555ae3..411144151a1 100644 --- a/man/man8/xfs_io.8 +++ b/man/man8/xfs_io.8 @@ -1322,6 +1322,94 @@ Only available in expert mode and requires privileges. .B thaw Undo the effects of a filesystem freeze operation. Only available in expert mode and requires privileges. + +.TP +.BI "fsrefcounts [ \-d | \-l | \-r ] [ \-m | \-v ] [ \-n " nx " ] [ \-o " min_owners " ] [ \-O " max_owners " ] [ " start " ] [ " end " ] +Prints the number of owners of disk extents used by the filesystem hosting the +current file. +The listing does not include free blocks. +Each line of the listings takes the following form: +.PP +.RS +.IR extent ": " major ":" minor " [" startblock .. endblock "]: " owners " " length +.PP +All blocks, offsets, and lengths are specified in units of 512-byte +blocks, no matter what the filesystem's block size is. +The optional +.I start +and +.I end +arguments can be used to constrain the output to a particular range of +disk blocks. +If these two options are specified, exactly one of +.BR "-d" ", " "-l" ", or " "-r" +must also be set. +.RE +.RS 1.0i +.PD 0 +.TP +.BI \-d +Display only extents from the data device. +This option only applies for XFS filesystems. +.TP +.BI \-l +Display only extents from the external log device. +This option only applies to XFS filesystems. +.TP +.BI \-r +Display only extents from the realtime device. +This option only applies to XFS filesystems. +.TP +.BI \-m +Display results in a machine readable format (CSV). +This option is not compatible with the +.B \-v +flag. +The columns of the output are: extent number, device major, device minor, +physical start, physical end, number of owners, length. +The start, end, and length numbers are provided in units of 512b. + +.TP +.BI \-n " num_extents" +If this option is given, +.B fsrefcounts +obtains the extent list of the file in groups of +.I num_extents +extents. +In the absence of +.BR "-n" ", " "fsrefcounts" +queries the system for extents in groups of 131,072 records. +.TP +.BI \-o " min_owners" +Only print extents having at least this many owners. +This argument must be in the range 1 to 2^64-1. +The default value is 1. +.TP +.BI \-O " max_owners" +Only print extents having this many or fewer owners. +This argument must be in the range 1 to 2^64-1. +There is no limit by default. +.TP +.B \-v +Shows verbose information. +When this flag is specified, additional AG specific information is +appended to each line in the following form: +.IP +.RS 1.2i +.IR agno " (" startagblock .. endagblock ") " nblocks " " flags +.RE +.IP +A second +.B \-v +option will print out the +.I flags +legend. +This option is not compatible with the +.B \-m +flag. +.RE +.PD + .TP .BI "inject [ " tag " ]" Inject errors into a filesystem to observe filesystem behavior at ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 3/3] xfsprogs: defragment free space 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (6 preceding siblings ...) 2023-12-31 19:56 ` [PATCHSET 2/3] xfsprogs: report refcount information to userspace Darrick J. Wong @ 2023-12-31 19:56 ` Darrick J. Wong 2023-12-27 13:40 ` [PATCH 01/10] xfs: add an ioctl to map free space into a file Darrick J. Wong ` (9 more replies) 2023-12-31 20:02 ` [PATCHSET 1/2] fstests: functional test for refcount reporting Darrick J. Wong 2023-12-31 20:02 ` [PATCHSET 2/2] fstests: defragment free space Darrick J. Wong 9 siblings, 10 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 19:56 UTC (permalink / raw) To: cem, djwong; +Cc: Dave Chinner, linux-xfs Hi all, These patches contain experimental code to enable userspace to defragment the free space in a filesystem. Two purposes are imagined for this functionality: clearing space at the end of a filesystem before shrinking it, and clearing free space in anticipation of making a large allocation. The first patch adds a new fallocate mode that allows userspace to allocate free space from the filesystem into a file. The goal here is to allow the filesystem shrink process to prevent allocation from a certain part of the filesystem while a free space defragmenter evacuates all the files from the doomed part of the filesystem. The second patch amends the online repair system to allow the sysadmin to forcibly rebuild metadata structures, even if they're not corrupt. Without adding an ioctl to move metadata btree blocks, this is the only way to dislodge metadata. This patchset also includes a separate inode migration tool as prototyped by Dave Chinner in 2020. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=defrag-freespace xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=defrag-freespace fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=defrag-freespace --- Makefile | 2 configure.ac | 1 db/agfl.c | 297 ++++ include/builddefs.in | 1 include/xfs_trace.h | 4 io/prealloc.c | 35 libfrog/Makefile | 5 libfrog/clearspace.c | 3103 +++++++++++++++++++++++++++++++++++++++ libfrog/clearspace.h | 72 + libfrog/fsgeom.h | 29 libfrog/radix-tree.c | 2 libfrog/radix-tree.h | 2 libxfs/libxfs_api_defs.h | 4 libxfs/libxfs_priv.h | 10 libxfs/xfs_alloc.c | 88 + libxfs/xfs_alloc.h | 3 libxfs/xfs_bmap.c | 149 ++ libxfs/xfs_bmap.h | 3 libxfs/xfs_fs.h | 1 libxfs/xfs_fs_staging.h | 15 m4/package_libcdev.m4 | 20 man/man2/ioctl_xfs_map_freesp.2 | 76 + man/man8/xfs_db.8 | 11 man/man8/xfs_io.8 | 8 man/man8/xfs_spaceman.8 | 40 + spaceman/Makefile | 10 spaceman/clearfree.c | 164 ++ spaceman/find_owner.c | 443 ++++++ spaceman/init.c | 7 spaceman/move_inode.c | 663 ++++++++ spaceman/relocation.c | 566 +++++++ spaceman/relocation.h | 53 + spaceman/space.h | 6 33 files changed, 5883 insertions(+), 10 deletions(-) create mode 100644 libfrog/clearspace.c create mode 100644 libfrog/clearspace.h create mode 100644 man/man2/ioctl_xfs_map_freesp.2 create mode 100644 spaceman/clearfree.c create mode 100644 spaceman/find_owner.c create mode 100644 spaceman/move_inode.c create mode 100644 spaceman/relocation.c create mode 100644 spaceman/relocation.h ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 01/10] xfs: add an ioctl to map free space into a file 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong @ 2023-12-27 13:40 ` Darrick J. Wong 2023-12-27 13:40 ` [PATCH 02/10] xfs_io: support using XFS_IOC_MAP_FREESP to map free space Darrick J. Wong ` (8 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:40 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add a new ioctl to map free physical space into a file, at the same file offset as if the file were a sparse image of the physical device backing the filesystem. The intent here is to use this to prototype a free space defragmentation tool. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- include/xfs_trace.h | 4 + libxfs/libxfs_priv.h | 10 +++ libxfs/xfs_alloc.c | 88 +++++++++++++++++++++++ libxfs/xfs_alloc.h | 3 + libxfs/xfs_bmap.c | 149 +++++++++++++++++++++++++++++++++++++++ libxfs/xfs_bmap.h | 3 + libxfs/xfs_fs.h | 1 libxfs/xfs_fs_staging.h | 15 ++++ man/man2/ioctl_xfs_map_freesp.2 | 76 ++++++++++++++++++++ 9 files changed, 349 insertions(+) create mode 100644 man/man2/ioctl_xfs_map_freesp.2 diff --git a/include/xfs_trace.h b/include/xfs_trace.h index c8368227705..328f276d498 100644 --- a/include/xfs_trace.h +++ b/include/xfs_trace.h @@ -26,6 +26,8 @@ #define trace_xfs_alloc_exact_done(a) ((void) 0) #define trace_xfs_alloc_exact_notfound(a) ((void) 0) #define trace_xfs_alloc_exact_error(a) ((void) 0) +#define trace_xfs_alloc_find_freesp(...) ((void) 0) +#define trace_xfs_alloc_find_freesp_done(...) ((void) 0) #define trace_xfs_alloc_near_first(a) ((void) 0) #define trace_xfs_alloc_near_greater(a) ((void) 0) #define trace_xfs_alloc_near_lesser(a) ((void) 0) @@ -196,6 +198,8 @@ #define trace_xfs_bmap_pre_update(a,b,c,d) ((void) 0) #define trace_xfs_bmap_post_update(a,b,c,d) ((void) 0) +#define trace_xfs_bmapi_freesp(...) ((void) 0) +#define trace_xfs_bmapi_freesp_done(...) ((void) 0) #define trace_xfs_bunmap(a,b,c,d,e) ((void) 0) #define trace_xfs_read_extent(a,b,c,d) ((void) 0) diff --git a/libxfs/libxfs_priv.h b/libxfs/libxfs_priv.h index bbe7dd63443..bc8f66aa986 100644 --- a/libxfs/libxfs_priv.h +++ b/libxfs/libxfs_priv.h @@ -501,6 +501,16 @@ void __xfs_buf_mark_corrupt(struct xfs_buf *bp, xfs_failaddr_t fa); #define xfs_filestream_new_ag(ip,ag) (0) #define xfs_filestream_select_ag(...) (-ENOSYS) +struct xfs_trans; + +static inline int +xfs_rtallocate_extent(struct xfs_trans *tp, xfs_rtblock_t bno, + xfs_extlen_t minlen, xfs_extlen_t maxlen, xfs_extlen_t *len, + int wasdel, xfs_extlen_t prod, xfs_rtblock_t *rtblock) +{ + return -EOPNOTSUPP; +} + #define xfs_trans_inode_buf(tp, bp) ((void) 0) /* quota bits */ diff --git a/libxfs/xfs_alloc.c b/libxfs/xfs_alloc.c index 589e9ef3003..50626d79c3f 100644 --- a/libxfs/xfs_alloc.c +++ b/libxfs/xfs_alloc.c @@ -4092,3 +4092,91 @@ xfs_extfree_intent_destroy_cache(void) kmem_cache_destroy(xfs_extfree_item_cache); xfs_extfree_item_cache = NULL; } + +/* + * Find the next chunk of free space in @pag starting at @agbno and going no + * higher than @end_agbno. Set @agbno and @len to whatever free space we find, + * or to @end_agbno if we find no space. + */ +int +xfs_alloc_find_freesp( + struct xfs_trans *tp, + struct xfs_perag *pag, + xfs_agblock_t *agbno, + xfs_agblock_t end_agbno, + xfs_extlen_t *len) +{ + struct xfs_mount *mp = pag->pag_mount; + struct xfs_btree_cur *cur; + struct xfs_buf *agf_bp = NULL; + xfs_agblock_t found_agbno; + xfs_extlen_t found_len; + int found; + int error; + + trace_xfs_alloc_find_freesp(mp, pag->pag_agno, *agbno, + end_agbno - *agbno); + + error = xfs_alloc_read_agf(pag, tp, 0, &agf_bp); + if (error) + return error; + + cur = xfs_allocbt_init_cursor(mp, tp, agf_bp, pag, XFS_BTNUM_BNO); + + /* Try to find a free extent that starts before here. */ + error = xfs_alloc_lookup_le(cur, *agbno, 0, &found); + if (error) + goto out_cur; + if (found) { + error = xfs_alloc_get_rec(cur, &found_agbno, &found_len, + &found); + if (error) + goto out_cur; + if (XFS_IS_CORRUPT(mp, !found)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto out_cur; + } + + if (found_agbno + found_len > *agbno) + goto found; + } + + /* Examine the next record if free extent not in range. */ + error = xfs_btree_increment(cur, 0, &found); + if (error) + goto out_cur; + if (!found) + goto next_ag; + + error = xfs_alloc_get_rec(cur, &found_agbno, &found_len, &found); + if (error) + goto out_cur; + if (XFS_IS_CORRUPT(mp, !found)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto out_cur; + } + + if (found_agbno >= end_agbno) + goto next_ag; + +found: + /* Found something, so update the mapping. */ + trace_xfs_alloc_find_freesp_done(mp, pag->pag_agno, found_agbno, + found_len); + if (found_agbno < *agbno) { + found_len -= *agbno - found_agbno; + found_agbno = *agbno; + } + *len = found_len; + *agbno = found_agbno; + goto out_cur; +next_ag: + /* Found nothing, so advance the cursor beyond the end of the range. */ + *agbno = end_agbno; + *len = 0; +out_cur: + xfs_btree_del_cursor(cur, error); + return error; +} diff --git a/libxfs/xfs_alloc.h b/libxfs/xfs_alloc.h index 130026e981e..fedb6dc0443 100644 --- a/libxfs/xfs_alloc.h +++ b/libxfs/xfs_alloc.h @@ -290,5 +290,8 @@ void xfs_extfree_intent_destroy_cache(void); xfs_failaddr_t xfs_validate_ag_length(struct xfs_buf *bp, uint32_t seqno, uint32_t length); +int xfs_alloc_find_freesp(struct xfs_trans *tp, struct xfs_perag *pag, + xfs_agblock_t *agbno, xfs_agblock_t end_agbno, + xfs_extlen_t *len); #endif /* __XFS_ALLOC_H__ */ diff --git a/libxfs/xfs_bmap.c b/libxfs/xfs_bmap.c index 13bcf146d08..94640a5077c 100644 --- a/libxfs/xfs_bmap.c +++ b/libxfs/xfs_bmap.c @@ -6436,3 +6436,152 @@ xfs_get_cowextsz_hint( return XFS_DEFAULT_COWEXTSZ_HINT; return a; } + +static inline xfs_fileoff_t +xfs_fsblock_to_fileoff( + struct xfs_mount *mp, + xfs_fsblock_t fsbno) +{ + xfs_daddr_t daddr = XFS_FSB_TO_DADDR(mp, fsbno); + + return XFS_B_TO_FSB(mp, BBTOB(daddr)); +} + +/* + * Given a file and a free physical extent, map it into the file at the same + * offset if the file were a sparse image of the physical device. Set @mval to + * whatever mapping we added to the file. + */ +int +xfs_bmapi_freesp( + struct xfs_trans *tp, + struct xfs_inode *ip, + xfs_fsblock_t fsbno, + xfs_extlen_t len, + struct xfs_bmbt_irec *mval) +{ + struct xfs_bmbt_irec irec; + struct xfs_mount *mp = ip->i_mount; + xfs_fileoff_t startoff; + bool isrt = XFS_IS_REALTIME_INODE(ip); + int nimaps; + int error; + + trace_xfs_bmapi_freesp(ip, fsbno, len); + + error = xfs_iext_count_may_overflow(ip, XFS_DATA_FORK, + XFS_IEXT_ADD_NOSPLIT_CNT); + if (error) + return error; + + if (isrt) + startoff = fsbno; + else + startoff = xfs_fsblock_to_fileoff(mp, fsbno); + + /* Make sure the entire range is a hole. */ + nimaps = 1; + error = xfs_bmapi_read(ip, startoff, len, &irec, &nimaps, 0); + if (error) + return error; + + if (irec.br_startoff != startoff || + irec.br_startblock != HOLESTARTBLOCK || + irec.br_blockcount < len) + return -EINVAL; + + /* + * Allocate the physical extent. We should not have dropped the lock + * since the scan of the free space metadata, so this should work, + * though the length may be adjusted to play nicely with metadata space + * reservations. + */ + if (isrt) { + xfs_rtxnum_t rtx_in, rtx_out; + xfs_extlen_t rtxlen_in, rtxlen_out; + uint32_t mod; + + rtx_in = xfs_rtb_to_rtxrem(mp, fsbno, &mod); + if (mod) { + ASSERT(mod == 0); + return -EFSCORRUPTED; + } + + rtxlen_in = xfs_rtb_to_rtxrem(mp, len, &mod); + if (mod) { + ASSERT(mod == 0); + return -EFSCORRUPTED; + } + + error = xfs_rtallocate_extent(tp, rtx_in, 1, rtxlen_in, + &rtxlen_out, 0, 1, &rtx_out); + if (error) + return error; + if (rtx_out == NULLRTEXTNO) { + /* + * We were promised the space! In theory there aren't + * any reserve lists that would prevent us from getting + * the space. + */ + return -ENOSPC; + } + if (rtx_out != rtx_in) { + ASSERT(0); + xfs_bmap_mark_sick(ip, XFS_DATA_FORK); + return -EFSCORRUPTED; + } + mval->br_blockcount = rtxlen_out * mp->m_sb.sb_rextsize; + } else { + struct xfs_alloc_arg args = { + .mp = mp, + .tp = tp, + .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE, + .resv = XFS_AG_RESV_NONE, + .prod = 1, + .datatype = XFS_ALLOC_USERDATA, + .maxlen = len, + .minlen = 1, + }; + args.pag = xfs_perag_get(mp, XFS_FSB_TO_AGNO(mp, fsbno)); + error = xfs_alloc_vextent_exact_bno(&args, fsbno); + xfs_perag_put(args.pag); + if (error) + return error; + if (args.fsbno == NULLFSBLOCK) { + /* + * We were promised the space, but failed to get it. + * This could be because the space is reserved for + * metadata expansion, or it could be because the AGFL + * fixup grabbed the first block we wanted. Either + * way, if the transaction is dirty we must commit it + * and tell the caller to try again. + */ + if (tp->t_flags & XFS_TRANS_DIRTY) + return -EAGAIN; + return -ENOSPC; + } + if (args.fsbno != fsbno) { + ASSERT(0); + xfs_bmap_mark_sick(ip, XFS_DATA_FORK); + return -EFSCORRUPTED; + } + mval->br_blockcount = args.len; + } + + /* Map extent into file, update quota. */ + mval->br_startblock = fsbno; + mval->br_startoff = startoff; + mval->br_state = XFS_EXT_UNWRITTEN; + + trace_xfs_bmapi_freesp_done(ip, mval); + + xfs_bmap_map_extent(tp, ip, XFS_DATA_FORK, mval); + if (isrt) + xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_RTBCOUNT, + mval->br_blockcount); + else + xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_BCOUNT, + mval->br_blockcount); + + return 0; +} diff --git a/libxfs/xfs_bmap.h b/libxfs/xfs_bmap.h index 61c195198db..afb54a517f1 100644 --- a/libxfs/xfs_bmap.h +++ b/libxfs/xfs_bmap.h @@ -197,6 +197,9 @@ int xfs_bmapi_read(struct xfs_inode *ip, xfs_fileoff_t bno, int xfs_bmapi_write(struct xfs_trans *tp, struct xfs_inode *ip, xfs_fileoff_t bno, xfs_filblks_t len, uint32_t flags, xfs_extlen_t total, struct xfs_bmbt_irec *mval, int *nmap); +int xfs_bmapi_freesp(struct xfs_trans *tp, struct xfs_inode *ip, + xfs_fsblock_t fsbno, xfs_extlen_t len, + struct xfs_bmbt_irec *mval); int xfs_bunmapi(struct xfs_trans *tp, struct xfs_inode *ip, xfs_fileoff_t bno, xfs_filblks_t len, uint32_t flags, xfs_extnum_t nexts, int *done); diff --git a/libxfs/xfs_fs.h b/libxfs/xfs_fs.h index 96688f9301e..922e9acfdc3 100644 --- a/libxfs/xfs_fs.h +++ b/libxfs/xfs_fs.h @@ -864,6 +864,7 @@ struct xfs_scrub_metadata { #define XFS_IOC_AG_GEOMETRY _IOWR('X', 61, struct xfs_ag_geometry) /* XFS_IOC_GETPARENTS ---- staging 62 */ /* XFS_IOC_RTGROUP_GEOMETRY - staging 63 */ +/* XFS_IOC_MAP_FREESP ---- staging 64 */ /* * ioctl commands that replace IRIX syssgi()'s diff --git a/libxfs/xfs_fs_staging.h b/libxfs/xfs_fs_staging.h index f7d872f8a88..2a9effb7a84 100644 --- a/libxfs/xfs_fs_staging.h +++ b/libxfs/xfs_fs_staging.h @@ -303,4 +303,19 @@ xfs_getfsrefs_advance( /* XXX stealing XFS_IOC_GETBIOSIZE */ #define XFS_IOC_GETFSREFCOUNTS _IOWR('X', 47, struct xfs_getfsrefs_head) +/* map free space to file */ + +struct xfs_map_freesp { + __s64 offset; /* disk address to map, in bytes */ + __s64 len; /* length in bytes */ + __u64 flags; /* must be zero */ + __u64 pad; /* must be zero */ +}; + +/* + * XFS_IOC_MAP_FREESP maps all the free physical space in the filesystem into + * the file at the same offsets. This ioctl requires CAP_SYS_ADMIN. + */ +#define XFS_IOC_MAP_FREESP _IOWR('X', 64, struct xfs_map_freesp) + #endif /* __XFS_FS_STAGING_H__ */ diff --git a/man/man2/ioctl_xfs_map_freesp.2 b/man/man2/ioctl_xfs_map_freesp.2 new file mode 100644 index 00000000000..ca1d9882437 --- /dev/null +++ b/man/man2/ioctl_xfs_map_freesp.2 @@ -0,0 +1,76 @@ +.\" Copyright (c) 2023-2024 Oracle. All rights reserved. +.\" +.\" %%%LICENSE_START(GPLv2+_DOC_FULL) +.\" SPDX-License-Identifier: GPL-2.0-or-later +.\" %%%LICENSE_END +.TH IOCTL-XFS-MAP-FREESP 2 2023-11-17 "XFS" +.SH NAME +ioctl_xfs_map_freesp \- map free space into a file +.SH SYNOPSIS +.br +.B #include <xfs/xfs_fs_staging.h> +.PP +.BI "int ioctl(int " fd ", XFS_IOC_MAP_FREESP, struct xfs_map_freesp *" arg ); +.SH DESCRIPTION +Maps free space into the sparse ranges of a regular file. +This ioctl uses +.B struct xfs_map_freesp +to specify the range of free space to be mapped: +.PP +.in +4n +.nf +struct xfs_map_freesp { + __s64 offset; + __s64 len; + __s64 flags; + __s64 pad; +}; +.fi +.in +.PP +.I offset +is the physical disk address, in bytes, of the start of the range to scan. +Each free space extent in this range will be mapped to the file if the +corresponding range of the file is sparse. +.PP +.I len +is the number of bytes in the range to scan. +.PP +.I flags +must be zero; there are no flags defined yet. +.PP +.I pad +must be zero. +.SH RETURN VALUE +On error, \-1 is returned, and +.I errno +is set to indicate the error. +.PP +.SH ERRORS +Error codes can be one of, but are not limited to, the following: +.TP +.B EFAULT +The kernel was not able to copy into the userspace buffer. +.TP +.B EFSBADCRC +Metadata checksum validation failed while performing the query. +.TP +.B EFSCORRUPTED +Metadata corruption was encountered while performing the query. +.TP +.B EINVAL +One of the arguments was not valid, +or the file was not sparse. +.TP +.B EIO +An I/O error was encountered while performing the query. +.TP +.B ENOMEM +There was insufficient memory to perform the query. +.TP +.B ENOSPC +There was insufficient disk space to commit the space mappings. +.SH CONFORMING TO +This API is specific to XFS filesystem on the Linux kernel. +.SH SEE ALSO +.BR ioctl (2) ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 02/10] xfs_io: support using XFS_IOC_MAP_FREESP to map free space 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong 2023-12-27 13:40 ` [PATCH 01/10] xfs: add an ioctl to map free space into a file Darrick J. Wong @ 2023-12-27 13:40 ` Darrick J. Wong 2023-12-27 13:40 ` [PATCH 03/10] xfs_db: get and put blocks on the AGFL Darrick J. Wong ` (7 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:40 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add a command to call XFS_IOC_MAP_FREESP. This is experimental code to see if we can build a free space defragmenter out of this. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- io/prealloc.c | 35 +++++++++++++++++++++++++++++++++++ man/man8/xfs_io.8 | 8 +++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/io/prealloc.c b/io/prealloc.c index 5805897a4a0..0de3e142de1 100644 --- a/io/prealloc.c +++ b/io/prealloc.c @@ -45,6 +45,7 @@ static cmdinfo_t finsert_cmd; static cmdinfo_t fzero_cmd; static cmdinfo_t funshare_cmd; #endif +static cmdinfo_t fmapfree_cmd; static int offset_length( @@ -383,6 +384,30 @@ funshare_f( } #endif /* HAVE_FALLOCATE */ +static int +fmapfree_f( + int argc, + char **argv) +{ + struct xfs_flock64 segment; + struct xfs_map_freesp args = { }; + + if (!offset_length(argv[1], argv[2], &segment)) { + exitcode = 1; + return 0; + } + + args.offset = segment.l_start; + args.len = segment.l_len; + + if (ioctl(file->fd, XFS_IOC_MAP_FREESP, &args)) { + perror("XFS_IOC_MAP_FREESP"); + exitcode = 1; + return 0; + } + return 0; +} + void prealloc_init(void) { @@ -497,4 +522,14 @@ prealloc_init(void) _("unshares shared blocks within the range"); add_command(&funshare_cmd); #endif /* HAVE_FALLOCATE */ + + fmapfree_cmd.name = "fmapfree"; + fmapfree_cmd.cfunc = fmapfree_f; + fmapfree_cmd.argmin = 2; + fmapfree_cmd.argmax = 2; + fmapfree_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK; + fmapfree_cmd.args = _("off len"); + fmapfree_cmd.oneline = + _("maps free space into a file"); + add_command(&fmapfree_cmd); } diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8 index 411144151a1..e360d22dc38 100644 --- a/man/man8/xfs_io.8 +++ b/man/man8/xfs_io.8 @@ -513,8 +513,14 @@ Call fallocate with FALLOC_FL_INSERT_RANGE flag as described in the .BR fallocate (2) manual page to create the hole by shifting data blocks. .TP +.BI fmapfree " offset length" +Maps free physical space into the file by calling XFS_IOC_MAP_FREESP as +described in the +.BR XFS_IOC_MAP_FREESP (2) +manual page. +.TP .BI fpunch " offset length" -Punches (de-allocates) blocks in the file by calling fallocate with +Punches (de-allocates) blocks in the file by calling fallocate with the FALLOC_FL_PUNCH_HOLE flag as described in the .BR fallocate (2) manual page. ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 03/10] xfs_db: get and put blocks on the AGFL 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong 2023-12-27 13:40 ` [PATCH 01/10] xfs: add an ioctl to map free space into a file Darrick J. Wong 2023-12-27 13:40 ` [PATCH 02/10] xfs_io: support using XFS_IOC_MAP_FREESP to map free space Darrick J. Wong @ 2023-12-27 13:40 ` Darrick J. Wong 2023-12-27 13:41 ` [PATCH 04/10] xfs_spaceman: implement clearing free space Darrick J. Wong ` (6 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:40 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Add a new xfs_db command to let people add and remove blocks from an AGFL. This isn't really related to rmap btree reconstruction, other than enabling debugging code to mess around with the AGFL to exercise various odd scenarios. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- db/agfl.c | 297 ++++++++++++++++++++++++++++++++++++++++++++++ libxfs/libxfs_api_defs.h | 4 + man/man8/xfs_db.8 | 11 ++ 3 files changed, 308 insertions(+), 4 deletions(-) diff --git a/db/agfl.c b/db/agfl.c index f0f3f21a64d..662b6403cb2 100644 --- a/db/agfl.c +++ b/db/agfl.c @@ -15,13 +15,14 @@ #include "output.h" #include "init.h" #include "agfl.h" +#include "libfrog/bitmap.h" static int agfl_bno_size(void *obj, int startoff); static int agfl_f(int argc, char **argv); static void agfl_help(void); static const cmdinfo_t agfl_cmd = - { "agfl", NULL, agfl_f, 0, 1, 1, N_("[agno]"), + { "agfl", NULL, agfl_f, 0, -1, 1, N_("[agno] [-g nr] [-p nr]"), N_("set address to agfl block"), agfl_help }; const field_t agfl_hfld[] = { { @@ -77,10 +78,280 @@ agfl_help(void) " for each allocation group. This acts as a reserved pool of space\n" " separate from the general filesystem freespace (not used for user data).\n" "\n" +" -g quantity\tRemove this many blocks from the AGFL.\n" +" -p quantity\tAdd this many blocks to the AGFL.\n" +"\n" )); } +struct dump_info { + struct xfs_perag *pag; + bool leak; +}; + +/* Return blocks freed from the AGFL to the free space btrees. */ +static int +free_grabbed( + uint64_t start, + uint64_t length, + void *data) +{ + struct dump_info *di = data; + struct xfs_perag *pag = di->pag; + struct xfs_mount *mp = pag->pag_mount; + struct xfs_trans *tp; + struct xfs_buf *agf_bp; + int error; + + error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, + &tp); + if (error) + return error; + + error = -libxfs_alloc_read_agf(pag, tp, 0, &agf_bp); + if (error) + goto out_cancel; + + error = -libxfs_free_extent(tp, pag, start, length, &XFS_RMAP_OINFO_AG, + XFS_AG_RESV_AGFL); + if (error) + goto out_cancel; + + return -libxfs_trans_commit(tp); + +out_cancel: + libxfs_trans_cancel(tp); + return error; +} + +/* Report blocks freed from the AGFL. */ +static int +dump_grabbed( + uint64_t start, + uint64_t length, + void *data) +{ + struct dump_info *di = data; + const char *fmt; + + if (length == 1) + fmt = di->leak ? _("agfl %u: leaked agbno %u\n") : + _("agfl %u: removed agbno %u\n"); + else + fmt = di->leak ? _("agfl %u: leaked agbno %u-%u\n") : + _("agfl %u: removed agbno %u-%u\n"); + + printf(fmt, di->pag->pag_agno, (unsigned int)start, + (unsigned int)(start + length - 1)); + return 0; +} + +/* Remove blocks from the AGFL. */ +static int +agfl_get( + struct xfs_perag *pag, + int quantity) +{ + struct dump_info di = { + .pag = pag, + .leak = quantity < 0, + }; + struct xfs_agf *agf; + struct xfs_buf *agf_bp; + struct xfs_trans *tp; + struct bitmap *grabbed; + const unsigned int agfl_size = libxfs_agfl_size(pag->pag_mount); + unsigned int i; + int error; + + if (!quantity) + return 0; + + if (di.leak) + quantity = -quantity; + quantity = min(quantity, agfl_size); + + error = bitmap_alloc(&grabbed); + if (error) + goto out; + + error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, quantity, 0, + 0, &tp); + if (error) + goto out_bitmap; + + error = -libxfs_alloc_read_agf(pag, tp, 0, &agf_bp); + if (error) + goto out_cancel; + + agf = agf_bp->b_addr; + quantity = min(quantity, be32_to_cpu(agf->agf_flcount)); + + for (i = 0; i < quantity; i++) { + xfs_agblock_t agbno; + + error = -libxfs_alloc_get_freelist(pag, tp, agf_bp, &agbno, 0); + if (error) + goto out_cancel; + + if (agbno == NULLAGBLOCK) { + error = ENOSPC; + goto out_cancel; + } + + error = bitmap_set(grabbed, agbno, 1); + if (error) + goto out_cancel; + } + + error = -libxfs_trans_commit(tp); + if (error) + goto out_bitmap; + + error = bitmap_iterate(grabbed, dump_grabbed, &di); + if (error) + goto out_bitmap; + + if (!di.leak) { + error = bitmap_iterate(grabbed, free_grabbed, &di); + if (error) + goto out_bitmap; + } + + bitmap_free(&grabbed); + return 0; + +out_cancel: + libxfs_trans_cancel(tp); +out_bitmap: + bitmap_free(&grabbed); +out: + if (error) + printf(_("agfl %u: %s\n"), pag->pag_agno, strerror(error)); + return error; +} + +/* Add blocks to the AGFL. */ +static int +agfl_put( + struct xfs_perag *pag, + int quantity) +{ + struct xfs_alloc_arg args = { + .mp = pag->pag_mount, + .alignment = 1, + .minlen = 1, + .prod = 1, + .resv = XFS_AG_RESV_AGFL, + .oinfo = XFS_RMAP_OINFO_AG, + }; + struct xfs_buf *agfl_bp; + struct xfs_agf *agf; + struct xfs_trans *tp; + xfs_fsblock_t target; + const unsigned int agfl_size = libxfs_agfl_size(pag->pag_mount); + unsigned int i; + bool eoag = quantity < 0; + int error; + + if (!quantity) + return 0; + + if (eoag) + quantity = -quantity; + quantity = min(quantity, agfl_size); + + error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, quantity, 0, + 0, &tp); + if (error) + return error; + args.tp = tp; + + error = -libxfs_alloc_read_agf(pag, tp, 0, &args.agbp); + if (error) + goto out_cancel; + + agf = args.agbp->b_addr; + args.maxlen = min(quantity, agfl_size - be32_to_cpu(agf->agf_flcount)); + + if (eoag) + target = XFS_AGB_TO_FSB(pag->pag_mount, pag->pag_agno, + be32_to_cpu(agf->agf_length) - 1); + else + target = XFS_AGB_TO_FSB(pag->pag_mount, pag->pag_agno, 0); + + error = -libxfs_alloc_read_agfl(pag, tp, &agfl_bp); + if (error) + goto out_cancel; + + error = -libxfs_alloc_vextent_near_bno(&args, target); + if (error) + goto out_cancel; + + if (args.agbno == NULLAGBLOCK) { + error = ENOSPC; + goto out_cancel; + } + + for (i = 0; i < args.len; i++) { + error = -libxfs_alloc_put_freelist(pag, tp, args.agbp, + agfl_bp, args.agbno + i, 0); + if (error) + goto out_cancel; + } + + if (i == 1) + printf(_("agfl %u: added agbno %u\n"), pag->pag_agno, + args.agbno); + else if (i > 1) + printf(_("agfl %u: added agbno %u-%u\n"), pag->pag_agno, + args.agbno, args.agbno + i - 1); + + error = -libxfs_trans_commit(tp); + if (error) + goto out; + + return 0; + +out_cancel: + libxfs_trans_cancel(tp); +out: + if (error) + printf(_("agfl %u: %s\n"), pag->pag_agno, strerror(error)); + return error; +} + +static void +agfl_adjust( + struct xfs_mount *mp, + xfs_agnumber_t agno, + int gblocks, + int pblocks) +{ + struct xfs_perag *pag; + int error; + + if (!expert_mode) { + printf(_("AGFL get/put only supported in expert mode.\n")); + exitcode = 1; + return; + } + + pag = libxfs_perag_get(mp, agno); + + error = agfl_get(pag, gblocks); + if (error) + goto out_pag; + + error = agfl_put(pag, pblocks); + +out_pag: + libxfs_perag_put(pag); + if (error) + exitcode = 1; +} + static int agfl_f( int argc, @@ -88,9 +359,25 @@ agfl_f( { xfs_agnumber_t agno; char *p; + int c; + int gblocks = 0, pblocks = 0; - if (argc > 1) { - agno = (xfs_agnumber_t)strtoul(argv[1], &p, 0); + while ((c = getopt(argc, argv, "g:p:")) != -1) { + switch (c) { + case 'g': + gblocks = atoi(optarg); + break; + case 'p': + pblocks = atoi(optarg); + break; + default: + agfl_help(); + return 1; + } + } + + if (argc > optind) { + agno = (xfs_agnumber_t)strtoul(argv[optind], &p, 0); if (*p != '\0' || agno >= mp->m_sb.sb_agcount) { dbprintf(_("bad allocation group number %s\n"), argv[1]); return 0; @@ -98,6 +385,10 @@ agfl_f( cur_agno = agno; } else if (cur_agno == NULLAGNUMBER) cur_agno = 0; + + if (gblocks || pblocks) + agfl_adjust(mp, cur_agno, gblocks, pblocks); + ASSERT(typtab[TYP_AGFL].typnm == TYP_AGFL); set_cur(&typtab[TYP_AGFL], XFS_AG_DADDR(mp, cur_agno, XFS_AGFL_DADDR(mp)), diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h index 4e7b3caba4b..52616086ef0 100644 --- a/libxfs/libxfs_api_defs.h +++ b/libxfs/libxfs_api_defs.h @@ -30,8 +30,12 @@ #define xfs_allocbt_maxrecs libxfs_allocbt_maxrecs #define xfs_allocbt_stage_cursor libxfs_allocbt_stage_cursor #define xfs_alloc_fix_freelist libxfs_alloc_fix_freelist +#define xfs_alloc_get_freelist libxfs_alloc_get_freelist #define xfs_alloc_min_freelist libxfs_alloc_min_freelist +#define xfs_alloc_put_freelist libxfs_alloc_put_freelist #define xfs_alloc_read_agf libxfs_alloc_read_agf +#define xfs_alloc_read_agfl libxfs_alloc_read_agfl +#define xfs_alloc_vextent_near_bno libxfs_alloc_vextent_near_bno #define xfs_alloc_vextent_start_ag libxfs_alloc_vextent_start_ag #define xfs_ascii_ci_hashname libxfs_ascii_ci_hashname diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8 index 3e80bcc57de..39461398c6a 100644 --- a/man/man8/xfs_db.8 +++ b/man/man8/xfs_db.8 @@ -182,10 +182,19 @@ Set current address to the AGF block for allocation group .IR agno . If no argument is given, use the current allocation group. .TP -.BI "agfl [" agno ] +.BI "agfl [" agno "] [\-g " " quantity" "] [\-p " quantity ] Set current address to the AGFL block for allocation group .IR agno . If no argument is given, use the current allocation group. +If the +.B -g +option is specified with a positive quantity, remove that many blocks from the +AGFL and put them in the free space btrees. +If the quantity is negative, remove the blocks and leak them. +If the +.B -p +option is specified, add that many blocks to the AGFL. +If the quantity is negative, the blocks are selected from the end of the AG. .TP .BI "agi [" agno ] Set current address to the AGI block for allocation group ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 04/10] xfs_spaceman: implement clearing free space 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (2 preceding siblings ...) 2023-12-27 13:40 ` [PATCH 03/10] xfs_db: get and put blocks on the AGFL Darrick J. Wong @ 2023-12-27 13:41 ` Darrick J. Wong 2023-12-27 13:41 ` [PATCH 05/10] spaceman: physically move a regular inode Darrick J. Wong ` (5 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:41 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> First attempt at evacuating all the used blocks from part of a filesystem. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- Makefile | 2 libfrog/Makefile | 5 libfrog/clearspace.c | 3103 +++++++++++++++++++++++++++++++++++++++++++++++ libfrog/clearspace.h | 72 + man/man8/xfs_spaceman.8 | 17 spaceman/Makefile | 2 spaceman/clearfree.c | 164 ++ spaceman/init.c | 1 spaceman/space.h | 2 9 files changed, 3366 insertions(+), 2 deletions(-) create mode 100644 libfrog/clearspace.c create mode 100644 libfrog/clearspace.h create mode 100644 spaceman/clearfree.c diff --git a/Makefile b/Makefile index c12df98dbef..f3a0249979c 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ quota: libxcmd repair: libxlog libxcmd copy: libxlog mkfs: libxcmd -spaceman: libxcmd +spaceman: libhandle libxcmd scrub: libhandle libxcmd rtcp: libfrog diff --git a/libfrog/Makefile b/libfrog/Makefile index 8c2d040bc29..9f2cf25ac9f 100644 --- a/libfrog/Makefile +++ b/libfrog/Makefile @@ -64,6 +64,11 @@ ifeq ($(HAVE_GETMNTENT),yes) LCFLAGS += -DHAVE_GETMNTENT endif +ifeq ($(HAVE_GETFSMAP),yes) +CFILES+=clearspace.c +HFILES+=clearspace.h +endif + LDIRT = gen_crc32table crc32table.h default: ltdepend $(LTLIBRARY) diff --git a/libfrog/clearspace.c b/libfrog/clearspace.c new file mode 100644 index 00000000000..2cbb260546b --- /dev/null +++ b/libfrog/clearspace.c @@ -0,0 +1,3103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#include "xfs.h" +#include <linux/fsmap.h> +#include "paths.h" +#include "fsgeom.h" +#include "logging.h" +#include "bulkstat.h" +#include "bitmap.h" +#include "file_exchange.h" +#include "clearspace.h" +#include "handle.h" + +/* + * Filesystem Space Balloons + * ========================= + * + * NOTE: Due to the evolving identity of this code, the "space_fd" or "space + * file" in the codebase are the same as the balloon file in this introduction. + * The introduction was written much later than the code. + * + * The goal of this code is to create a balloon file that is mapped to a range + * of the physical space that is managed by a filesystem. There are several + * uses envisioned for balloon files: + * + * 1. Defragmenting free space. Once the balloon is created, freeing it leaves + * a large chunk of contiguous free space ready for reallocation. + * + * 2. Shrinking the filesystem. If the balloon is inflated at the end of the + * filesystem, the file can be handed to the shrink code. The shrink code + * can then reduce the filesystem size by the size of the balloon. + * + * 3. Constraining usage of underlying thin provisioning pools. The space + * assigned to a balloon can be DISCARDed, which prevents the filesystem + * from using that space until the balloon is freed. This can be done more + * efficiently with the standard fallocate call, unless the balloon must + * target specific LBA ranges. + * + * Inflating a balloon is performed in five phases: claiming unused space; + * freezing used space; migrating file mappings away from frozen space; moving + * inodes; and rebuilding metadata elsewhere. + * + * Claiming Unused Space + * --------------------- + * + * The first step of inflating a file balloon is to define the range of + * physical space to be added to the balloon and claim as much of the free + * space inside that range as possible. Dirty data are flushed to disk and + * the block and inode garbage collectors are run to remove any speculative + * preallocations that might be occupying space in the target range. + * + * Second, the new XFS_IOC_MAP_FREESP ioctl is used to map free space in the + * target range to the balloon file. This step will be repeated after every + * space-clearing step below to capture that cleared space. Concurrent writer + * threads will (hopefully) be allocated space outside the target range. + * + * Freezing Used Space + * ------------------- + * + * The second phase of inflating the balloon is to freeze as much of the + * allocated space within the target range as possible. The purpose of this + * step is to grab a second reference to the used space, thereby preventing it + * from being reused elsewhere. + * + * Freezing of a physical space extent starts by using GETFSMAP to find the + * file owner of the space, and opening the file by handle. The fsmap record + * is used to create a FICLONERANGE request to link the file range into a work + * file. Once the reflink is made, any subsequent writes to any of the owners + * of that space are staged via copy on write. The balloon file prevents the + * copy on write from being staged within the target range. The frozen space + * mapping is moved from the work file to the balloon file, where it remains + * until the balloon file is freed. + * + * If reflink is not supported on the filesystem, used space cannot be frozen. + * This phase is skipped. + * + * Migrating File Mappings + * ----------------------- + * + * Once the balloon file has been populated with as much of the target range as + * possible, it is time to remap file ranges that point to the frozen space. + * + * It is advantageous to remap as many blocks as can be done with as few system + * calls as possible to avoid fragmenting files. Furthermore, it is preferable + * to remap heavily shared extents before lightly shared extents to preserve + * reflinks when possible. The new GETFSREFCOUNTS call is used to rank + * physical space extents by size and sharing factor so that the library always + * tries to relocate the highest ranking space extent. + * + * Once a space extent has been selected for relocation, it is reflinked from + * the balloon file into the work file. Next, fallocate is called with the + * FALLOC_FL_UNSHARE_RANGE mode to persist a new copy of the file data and + * update the mapping in the work file. The GETFSMAP call is used to find the + * remaining owners of the target space. For each owner, FIEDEDUPERANGE is + * used to change the owner file's mapping to the space in the work file if the + * owner has not been changed. + * + * If the filesystem does not support reflink, FIDEDUPERANGE will not be + * available. Fortunately, there will only be one owner of the frozen space. + * The file range contents are instead copied through the page cache to the + * work file, and EXCHANGE_RANGE is used to swap the mappings if the owner + * file has not been modified. + * + * When the only remaining owner of the space is the balloon file, return to + * the GETFSREFCOUNTS step to find a new target. This phase is complete when + * there are no more targets. + * + * Moving Inodes + * ------------- + * + * NOTE: This part is not written. + * + * When GETFSMAP tells us about an inode chunk, it is necessary to move the + * inodes allocated in that inode chunk to a new chunk. The first step is to + * create a new donor file whose inode record is not in the target range. This + * file must be created in a donor directory. Next, the file contents should + * be cloned, either via FICLONE for regular files or by copying the directory + * entries for directories. The caller must ensure that no programs write to + * the victim inode while this process is ongoing. + * + * Finally, the new inode must be mapped into the same points in the directory + * tree as the old inode. For each parent pointer accessible by the file, + * perform a RENAME_EXCHANGE operation to update the directory entry. One + * obvious flaw of this method is that we cannot specify (parent, name, child) + * pairs to renameat, which means that the rename does the wrong thing if + * either directory is updated concurrently. + * + * If parent pointers are not available, this phase could be performed slowly + * by iterating all directories looking for entries of interest and swapping + * them. + * + * It is required that the caller guarantee that other applications cannot + * update the filesystem concurrently. + * + * Rebuilding Metadata + * ------------------- + * + * The final phase identifies filesystem metadata occupying the target range + * and uses the online filesystem repair facility to rebuild the metadata + * structures. Assuming that the balloon file now maps most of the space in + * the target range, the new structures should be located outside of the target + * range. This phase runs in a loop until there is no more metadata to + * relocate or no progress can be made on relocating metadata. + * + * Limitations and Bugs + * -------------------- + * + * - This code must be able to find the owners of a range of physical space. + * If GETFSMAP does not return owner information, this code cannot succeed. + * In other words, reverse mapping must be enabled. + * + * - We cannot freeze EOF blocks because the FICLONERANGE code does not allow + * us to remap an EOF block into the middle of the balloon file. I think we + * actually succeed at reflinking the EOF block into the work file during the + * freeze step, but we need to dedupe/exchange the real owners' mappings + * without waiting for the freeze step. OTOH, we /also/ want to freeze as + * much space as quickly as we can. + * + * - Freeze cannot use FIECLONERANGE to reflink unwritten extents into the work + * file because FICLONERANGE ignores unwritten extents. We could create the + * work file as a sparse file and use EXCHANGE_RANGE to swap the unwritten + * extent with the hole, extend EOF to be allocunit aligned, and use + * EXCHANGE_RANGE to move it to the balloon file. That first exchange must + * be careful to sample the owner file's bulkstat data, re-measure the file + * range to confirm that the unwritten extent is still the one we want, and + * only exchange if the owner file has not changed. + * + * - csp_buffercopy seems to hang if pread returns zero bytes read. Do we dare + * use copy_file_range for this instead? + * + * - None of this code knows how to move inodes. Phase 4 is entirely + * speculative fiction rooted in Dave Chinner's earlier implementation. + * + * - Does this work for realtime files? Even for large rt extent sizes? + */ + +/* VFS helpers */ + +/* Remap the file range described by @fcr into fd, or return an errno. */ +static inline int +clonerange(int fd, struct file_clone_range *fcr) +{ + int ret; + + ret = ioctl(fd, FICLONERANGE, fcr); + if (ret) + return errno; + + return 0; +} + +/* Exchange the file ranges described by @xchg into fd, or return an errno. */ +static inline int +exchangerange(int fd, struct xfs_exch_range *xchg) +{ + int ret; + + ret = ioctl(fd, XFS_IOC_EXCHANGE_RANGE, xchg); + if (ret) + return errno; + + return 0; +} + +/* + * Deduplicate part of fd into the file range described by fdr. If the + * operation succeeded, we set @same to whether or not we deduped the data and + * return zero. If not, return an errno. + */ +static inline int +deduperange(int fd, struct file_dedupe_range *fdr, bool *same) +{ + struct file_dedupe_range_info *info = &fdr->info[0]; + int ret; + + assert(fdr->dest_count == 1); + *same = false; + + ret = ioctl(fd, FIDEDUPERANGE, fdr); + if (ret) + return errno; + + if (info->status < 0) + return -info->status; + + if (info->status == FILE_DEDUPE_RANGE_DIFFERS) + return 0; + + /* The kernel should never dedupe more than it was asked. */ + assert(fdr->src_length >= info->bytes_deduped); + + *same = true; + return 0; +} + +/* Space clearing operation control */ + +#define QUERY_BATCH_SIZE 1024 + +struct clearspace_tgt { + unsigned long long start; + unsigned long long length; + unsigned long long owners; + unsigned long long prio; + unsigned long long evacuated; + bool try_again; +}; + +struct clearspace_req { + struct xfs_fd *xfd; + + /* all the blocks that we've tried to clear */ + struct bitmap *visited; + + /* stat buffer of the open file */ + struct stat statbuf; + struct stat temp_statbuf; + struct stat space_statbuf; + + /* handle to this filesystem */ + void *fshandle; + size_t fshandle_sz; + + /* physical storage that we want to clear */ + unsigned long long start; + unsigned long long length; + dev_t dev; + + /* convenience variable */ + bool realtime:1; + bool use_reflink:1; + bool can_evac_metadata:1; + + /* + * The "space capture" file. Each extent in this file must be mapped + * to the same byte offset as the byte address of the physical space. + */ + int space_fd; + + /* work file for migrating file data */ + int work_fd; + + /* preallocated buffers for queries */ + struct getbmapx *bhead; + struct fsmap_head *mhead; + struct xfs_getfsrefs_head *rhead; + + /* buffer for copying data */ + char *buf; + + /* buffer for deduping data */ + struct file_dedupe_range *fdr; + + /* tracing mask and indent level */ + unsigned int trace_mask; + unsigned int trace_indent; +}; + +static inline bool +csp_is_internal_owner( + const struct clearspace_req *req, + unsigned long long owner) +{ + return owner == req->temp_statbuf.st_ino || + owner == req->space_statbuf.st_ino; +} + +/* Debugging stuff */ + +static const struct csp_errstr { + unsigned int mask; + const char *tag; +} errtags[] = { + { CSP_TRACE_FREEZE, "freeze" }, + { CSP_TRACE_GRAB, "grab" }, + { CSP_TRACE_PREP, "prep" }, + { CSP_TRACE_TARGET, "target" }, + { CSP_TRACE_DEDUPE, "dedupe" }, + { CSP_TRACE_EXCHANGE, "exchange_range" }, + { CSP_TRACE_XREBUILD, "rebuild" }, + { CSP_TRACE_EFFICACY, "efficacy" }, + { CSP_TRACE_SETUP, "setup" }, + { CSP_TRACE_DUMPFILE, "dumpfile" }, + { CSP_TRACE_BITMAP, "bitmap" }, + + /* prioritize high level functions over low level queries for tagging */ + { CSP_TRACE_FSMAP, "fsmap" }, + { CSP_TRACE_FSREFS, "fsrefs" }, + { CSP_TRACE_BMAPX, "bmapx" }, + { CSP_TRACE_FALLOC, "falloc" }, + { CSP_TRACE_STATUS, "status" }, + { 0, NULL }, +}; + +static void +csp_debug( + struct clearspace_req *req, + unsigned int mask, + const char *func, + int line, + const char *format, + ...) +{ + const struct csp_errstr *et = errtags; + bool debug = (req->trace_mask & ~CSP_TRACE_STATUS); + int indent = req->trace_indent; + va_list args; + + if ((req->trace_mask & mask) != mask) + return; + + if (debug) { + while (indent > 0) { + fprintf(stderr, " "); + indent--; + } + + for (; et->tag; et++) { + if (et->mask & mask) { + fprintf(stderr, "%s: ", et->tag); + break; + } + } + } + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + if (debug) + fprintf(stderr, " (line %d)\n", line); + else + fprintf(stderr, "\n"); + fflush(stderr); +} + +#define trace_freeze(req, format, ...) \ + csp_debug((req), CSP_TRACE_FREEZE, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_grabfree(req, format, ...) \ + csp_debug((req), CSP_TRACE_GRAB, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_fsmap(req, format, ...) \ + csp_debug((req), CSP_TRACE_FSMAP, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_fsmap_rec(req, mask, mrec) \ + while (!csp_is_internal_owner((req), (mrec)->fmr_owner)) { \ + csp_debug((req), (mask) | CSP_TRACE_FSMAP, __func__, __LINE__, \ +"fsmap phys 0x%llx owner 0x%llx offset 0x%llx bytecount 0x%llx flags 0x%x", \ + (unsigned long long)(mrec)->fmr_physical, \ + (unsigned long long)(mrec)->fmr_owner, \ + (unsigned long long)(mrec)->fmr_offset, \ + (unsigned long long)(mrec)->fmr_length, \ + (mrec)->fmr_flags); \ + break; \ + } + +#define trace_fsrefs(req, format, ...) \ + csp_debug((req), CSP_TRACE_FSREFS, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_fsrefs_rec(req, mask, rrec) \ + csp_debug((req), (mask) | CSP_TRACE_FSREFS, __func__, __LINE__, \ +"fsref phys 0x%llx bytecount 0x%llx owners %llu flags 0x%x", \ + (unsigned long long)(rrec)->fcr_physical, \ + (unsigned long long)(rrec)->fcr_length, \ + (unsigned long long)(rrec)->fcr_owners, \ + (rrec)->fcr_flags) + +#define trace_bmapx(req, format, ...) \ + csp_debug((req), CSP_TRACE_BMAPX, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_bmapx_rec(req, mask, brec) \ + csp_debug((req), (mask) | CSP_TRACE_BMAPX, __func__, __LINE__, \ +"bmapx pos 0x%llx bytecount 0x%llx phys 0x%llx flags 0x%x", \ + (unsigned long long)BBTOB((brec)->bmv_offset), \ + (unsigned long long)BBTOB((brec)->bmv_length), \ + (unsigned long long)BBTOB((brec)->bmv_block), \ + (brec)->bmv_oflags) + +#define trace_prep(req, format, ...) \ + csp_debug((req), CSP_TRACE_PREP, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_target(req, format, ...) \ + csp_debug((req), CSP_TRACE_TARGET, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_dedupe(req, format, ...) \ + csp_debug((req), CSP_TRACE_DEDUPE, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_falloc(req, format, ...) \ + csp_debug((req), CSP_TRACE_FALLOC, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_exchange(req, format, ...) \ + csp_debug((req), CSP_TRACE_EXCHANGE, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_xrebuild(req, format, ...) \ + csp_debug((req), CSP_TRACE_XREBUILD, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_setup(req, format, ...) \ + csp_debug((req), CSP_TRACE_SETUP, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_status(req, format, ...) \ + csp_debug((req), CSP_TRACE_STATUS, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_dumpfile(req, format, ...) \ + csp_debug((req), CSP_TRACE_DUMPFILE, __func__, __LINE__, format, __VA_ARGS__) + +#define trace_bitmap(req, format, ...) \ + csp_debug((req), CSP_TRACE_BITMAP, __func__, __LINE__, format, __VA_ARGS__) + +/* VFS Iteration helpers */ + +static inline void +start_spacefd_iter(struct clearspace_req *req) +{ + req->trace_indent++; +} + +static inline void +end_spacefd_iter(struct clearspace_req *req) +{ + req->trace_indent--; +} + +/* + * Iterate each hole in the space-capture file. Returns 1 if holepos/length + * has been set to a hole; 0 if there aren't any holes left, or -1 for error. + */ +static inline int +spacefd_hole_iter( + const struct clearspace_req *req, + loff_t *holepos, + loff_t *length) +{ + loff_t end = req->start + req->length; + loff_t h; + loff_t d; + + if (*length == 0) + d = req->start; + else + d = *holepos + *length; + if (d >= end) + return 0; + + h = lseek(req->space_fd, d, SEEK_HOLE); + if (h < 0) { + perror(_("finding start of hole in space capture file")); + return h; + } + if (h >= end) + return 0; + + d = lseek(req->space_fd, h, SEEK_DATA); + if (d < 0 && errno == ENXIO) + d = end; + if (d < 0) { + perror(_("finding end of hole in space capture file")); + return d; + } + if (d > end) + d = end; + + *holepos = h; + *length = d - h; + return 1; +} + +/* + * Iterate each written region in the space-capture file. Returns 1 if + * datapos/length have been set to a data area; 0 if there isn't any data left, + * or -1 for error. + */ +static int +spacefd_data_iter( + const struct clearspace_req *req, + loff_t *datapos, + loff_t *length) +{ + loff_t end = req->start + req->length; + loff_t d; + loff_t h; + + if (*length == 0) + h = req->start; + else + h = *datapos + *length; + if (h >= end) + return 0; + + d = lseek(req->space_fd, h, SEEK_DATA); + if (d < 0 && errno == ENXIO) + return 0; + if (d < 0) { + perror(_("finding start of data in space capture file")); + return d; + } + if (d >= end) + return 0; + + h = lseek(req->space_fd, d, SEEK_HOLE); + if (h < 0) { + perror(_("finding end of data in space capture file")); + return h; + } + if (h > end) + h = end; + + *datapos = d; + *length = h - d; + return 1; +} + +/* Filesystem space usage queries */ + +/* Allocate the structures needed for a fsmap query. */ +static void +start_fsmap_query( + struct clearspace_req *req, + dev_t dev, + unsigned long long physical, + unsigned long long length) +{ + struct fsmap_head *mhead = req->mhead; + + assert(req->mhead->fmh_count == 0); + memset(mhead, 0, sizeof(struct fsmap_head)); + mhead->fmh_count = QUERY_BATCH_SIZE; + mhead->fmh_keys[0].fmr_device = dev; + mhead->fmh_keys[0].fmr_physical = physical; + mhead->fmh_keys[1].fmr_device = dev; + mhead->fmh_keys[1].fmr_physical = physical + length; + mhead->fmh_keys[1].fmr_owner = ULLONG_MAX; + mhead->fmh_keys[1].fmr_flags = UINT_MAX; + mhead->fmh_keys[1].fmr_offset = ULLONG_MAX; + + trace_fsmap(req, "dev %u:%u physical 0x%llx bytecount 0x%llx highkey 0x%llx", + major(dev), minor(dev), + (unsigned long long)physical, + (unsigned long long)length, + (unsigned long long)mhead->fmh_keys[1].fmr_physical); + req->trace_indent++; +} + +static inline void +end_fsmap_query( + struct clearspace_req *req) +{ + req->trace_indent--; + req->mhead->fmh_count = 0; +} + +/* Set us up for the next run_fsmap_query, or return false. */ +static inline bool +advance_fsmap_cursor(struct fsmap_head *mhead) +{ + struct fsmap *mrec; + + mrec = &mhead->fmh_recs[mhead->fmh_entries - 1]; + if (mrec->fmr_flags & FMR_OF_LAST) + return false; + + fsmap_advance(mhead); + return true; +} + +/* + * Run a GETFSMAP query. Returns 1 if there are rows, 0 if there are no rows, + * or -1 for error. + */ +static inline int +run_fsmap_query( + struct clearspace_req *req) +{ + struct fsmap_head *mhead = req->mhead; + int ret; + + if (mhead->fmh_entries > 0 && !advance_fsmap_cursor(mhead)) + return 0; + + trace_fsmap(req, + "ioctl dev %u:%u physical 0x%llx length 0x%llx highkey 0x%llx", + major(mhead->fmh_keys[0].fmr_device), + minor(mhead->fmh_keys[0].fmr_device), + (unsigned long long)mhead->fmh_keys[0].fmr_physical, + (unsigned long long)mhead->fmh_keys[0].fmr_length, + (unsigned long long)mhead->fmh_keys[1].fmr_physical); + + ret = ioctl(req->xfd->fd, FS_IOC_GETFSMAP, mhead); + if (ret) { + perror(_("querying fsmap data")); + return -1; + } + + if (!(mhead->fmh_oflags & FMH_OF_DEV_T)) { + fprintf(stderr, _("fsmap does not return dev_t.\n")); + return -1; + } + + if (mhead->fmh_entries == 0) + return 0; + + return 1; +} + +#define for_each_fsmap_row(req, rec) \ + for ((rec) = (req)->mhead->fmh_recs; \ + (rec) < (req)->mhead->fmh_recs + (req)->mhead->fmh_entries; \ + (rec)++) + +/* Allocate the structures needed for a fsrefcounts query. */ +static void +start_fsrefs_query( + struct clearspace_req *req, + dev_t dev, + unsigned long long physical, + unsigned long long length) +{ + struct xfs_getfsrefs_head *rhead = req->rhead; + + assert(req->rhead->fch_count == 0); + memset(rhead, 0, sizeof(struct xfs_getfsrefs_head)); + rhead->fch_count = QUERY_BATCH_SIZE; + rhead->fch_keys[0].fcr_device = dev; + rhead->fch_keys[0].fcr_physical = physical; + rhead->fch_keys[1].fcr_device = dev; + rhead->fch_keys[1].fcr_physical = physical + length; + rhead->fch_keys[1].fcr_owners = ULLONG_MAX; + rhead->fch_keys[1].fcr_flags = UINT_MAX; + + trace_fsrefs(req, "dev %u:%u physical 0x%llx bytecount 0x%llx highkey 0x%llx", + major(dev), minor(dev), + (unsigned long long)physical, + (unsigned long long)length, + (unsigned long long)rhead->fch_keys[1].fcr_physical); + req->trace_indent++; +} + +static inline void +end_fsrefs_query( + struct clearspace_req *req) +{ + req->trace_indent--; + req->rhead->fch_count = 0; +} + +/* Set us up for the next run_fsrefs_query, or return false. */ +static inline bool +advance_fsrefs_query(struct xfs_getfsrefs_head *rhead) +{ + struct xfs_getfsrefs *rrec; + + rrec = &rhead->fch_recs[rhead->fch_entries - 1]; + if (rrec->fcr_flags & FCR_OF_LAST) + return false; + + xfs_getfsrefs_advance(rhead); + return true; +} + +/* + * Run a GETFSREFCOUNTS query. Returns 1 if there are rows, 0 if there are + * no rows, or -1 for error. + */ +static inline int +run_fsrefs_query( + struct clearspace_req *req) +{ + struct xfs_getfsrefs_head *rhead = req->rhead; + int ret; + + if (rhead->fch_entries > 0 && !advance_fsrefs_query(rhead)) + return 0; + + trace_fsrefs(req, + "ioctl dev %u:%u physical 0x%llx length 0x%llx highkey 0x%llx", + major(rhead->fch_keys[0].fcr_device), + minor(rhead->fch_keys[0].fcr_device), + (unsigned long long)rhead->fch_keys[0].fcr_physical, + (unsigned long long)rhead->fch_keys[0].fcr_length, + (unsigned long long)rhead->fch_keys[1].fcr_physical); + + ret = ioctl(req->xfd->fd, XFS_IOC_GETFSREFCOUNTS, rhead); + if (ret) { + perror(_("querying refcount data")); + return -1; + } + + if (!(rhead->fch_oflags & FCH_OF_DEV_T)) { + fprintf(stderr, _("fsrefcounts does not return dev_t.\n")); + return -1; + } + + if (rhead->fch_entries == 0) + return 0; + + return 1; +} + +#define for_each_fsref_row(req, rec) \ + for ((rec) = (req)->rhead->fch_recs; \ + (rec) < (req)->rhead->fch_recs + (req)->rhead->fch_entries; \ + (rec)++) + +/* Allocate the structures needed for a bmapx query. */ +static void +start_bmapx_query( + struct clearspace_req *req, + unsigned int fork, + unsigned long long pos, + unsigned long long length) +{ + struct getbmapx *bhead = req->bhead; + + assert(fork == BMV_IF_ATTRFORK || fork == BMV_IF_COWFORK || !fork); + assert(req->bhead->bmv_count == 0); + + memset(bhead, 0, sizeof(struct getbmapx)); + bhead[0].bmv_offset = BTOBB(pos); + bhead[0].bmv_length = BTOBB(length); + bhead[0].bmv_count = QUERY_BATCH_SIZE + 1; + bhead[0].bmv_iflags = fork | BMV_IF_PREALLOC | BMV_IF_DELALLOC; + + trace_bmapx(req, "%s pos 0x%llx bytecount 0x%llx", + fork == BMV_IF_COWFORK ? "cow" : fork == BMV_IF_ATTRFORK ? "attr" : "data", + (unsigned long long)BBTOB(bhead[0].bmv_offset), + (unsigned long long)BBTOB(bhead[0].bmv_length)); + req->trace_indent++; +} + +static inline void +end_bmapx_query( + struct clearspace_req *req) +{ + req->trace_indent--; + req->bhead->bmv_count = 0; +} + +/* Set us up for the next run_bmapx_query, or return false. */ +static inline bool +advance_bmapx_query(struct getbmapx *bhead) +{ + struct getbmapx *brec; + unsigned long long next_offset; + unsigned long long end = bhead->bmv_offset + bhead->bmv_length; + + brec = &bhead[bhead->bmv_entries]; + if (brec->bmv_oflags & BMV_OF_LAST) + return false; + + next_offset = brec->bmv_offset + brec->bmv_length; + if (next_offset > end) + return false; + + bhead->bmv_offset = next_offset; + bhead->bmv_length = end - next_offset; + return true; +} + +/* + * Run a GETBMAPX query. Returns 1 if there are rows, 0 if there are no rows, + * or -1 for error. + */ +static inline int +run_bmapx_query( + struct clearspace_req *req, + int fd) +{ + struct getbmapx *bhead = req->bhead; + unsigned int fork; + int ret; + + if (bhead->bmv_entries > 0 && !advance_bmapx_query(bhead)) + return 0; + + fork = bhead[0].bmv_iflags & (BMV_IF_COWFORK | BMV_IF_ATTRFORK); + trace_bmapx(req, "ioctl %s pos 0x%llx bytecount 0x%llx", + fork == BMV_IF_COWFORK ? "cow" : fork == BMV_IF_ATTRFORK ? "attr" : "data", + (unsigned long long)BBTOB(bhead[0].bmv_offset), + (unsigned long long)BBTOB(bhead[0].bmv_length)); + + ret = ioctl(fd, XFS_IOC_GETBMAPX, bhead); + if (ret) { + perror(_("querying bmapx data")); + return -1; + } + + if (bhead->bmv_entries == 0) + return 0; + + return 1; +} + +#define for_each_bmapx_row(req, rec) \ + for ((rec) = (req)->bhead + 1; \ + (rec) < (req)->bhead + 1 + (req)->bhead->bmv_entries; \ + (rec)++) + +static inline void +csp_dump_bmapx_row( + struct clearspace_req *req, + unsigned int nr, + const struct getbmapx *brec) +{ + if (brec->bmv_block == -1) { + trace_dumpfile(req, "[%u]: pos 0x%llx len 0x%llx hole", + nr, + (unsigned long long)BBTOB(brec->bmv_offset), + (unsigned long long)BBTOB(brec->bmv_length)); + return; + } + + if (brec->bmv_block == -2) { + trace_dumpfile(req, "[%u]: pos 0x%llx len 0x%llx delalloc", + nr, + (unsigned long long)BBTOB(brec->bmv_offset), + (unsigned long long)BBTOB(brec->bmv_length)); + return; + } + + trace_dumpfile(req, "[%u]: pos 0x%llx len 0x%llx phys 0x%llx flags 0x%x", + nr, + (unsigned long long)BBTOB(brec->bmv_offset), + (unsigned long long)BBTOB(brec->bmv_length), + (unsigned long long)BBTOB(brec->bmv_block), + brec->bmv_oflags); +} + +static inline void +csp_dump_bmapx( + struct clearspace_req *req, + int fd, + unsigned int indent, + const char *tag) +{ + unsigned int nr; + int ret; + + trace_dumpfile(req, "DUMP BMAP OF DATA FORK %s", tag); + start_bmapx_query(req, 0, req->start, req->length); + nr = 0; + while ((ret = run_bmapx_query(req, fd)) > 0) { + struct getbmapx *brec; + + for_each_bmapx_row(req, brec) { + csp_dump_bmapx_row(req, nr++, brec); + if (nr > 10) + goto dump_cow; + } + } + +dump_cow: + end_bmapx_query(req); + trace_dumpfile(req, "DUMP BMAP OF COW FORK %s", tag); + start_bmapx_query(req, BMV_IF_COWFORK, req->start, req->length); + nr = 0; + while ((ret = run_bmapx_query(req, fd)) > 0) { + struct getbmapx *brec; + + for_each_bmapx_row(req, brec) { + csp_dump_bmapx_row(req, nr++, brec); + if (nr > 10) + goto dump_attr; + } + } + +dump_attr: + end_bmapx_query(req); + trace_dumpfile(req, "DUMP BMAP OF ATTR FORK %s", tag); + start_bmapx_query(req, BMV_IF_ATTRFORK, req->start, req->length); + nr = 0; + while ((ret = run_bmapx_query(req, fd)) > 0) { + struct getbmapx *brec; + + for_each_bmapx_row(req, brec) { + csp_dump_bmapx_row(req, nr++, brec); + if (nr > 10) + goto stop; + } + } + +stop: + end_bmapx_query(req); + trace_dumpfile(req, "DONE DUMPING %s", tag); +} + +/* Return the first bmapx for the given file range. */ +static int +bmapx_one( + struct clearspace_req *req, + int fd, + unsigned long long pos, + unsigned long long length, + struct getbmapx *brec) +{ + struct getbmapx bhead[2]; + int ret; + + memset(bhead, 0, sizeof(struct getbmapx) * 2); + bhead[0].bmv_offset = BTOBB(pos); + bhead[0].bmv_length = BTOBB(length); + bhead[0].bmv_count = 2; + bhead[0].bmv_iflags = BMV_IF_PREALLOC | BMV_IF_DELALLOC; + + ret = ioctl(fd, XFS_IOC_GETBMAPX, bhead); + if (ret) { + perror(_("simple bmapx query")); + return -1; + } + + if (bhead->bmv_entries > 0) { + memcpy(brec, &bhead[1], sizeof(struct getbmapx)); + return 0; + } + + memset(brec, 0, sizeof(struct getbmapx)); + brec->bmv_offset = pos; + brec->bmv_block = -1; /* hole */ + brec->bmv_length = length; + return 0; +} + +/* Constrain space map records. */ +static void +__trim_fsmap( + uint64_t start, + uint64_t length, + struct fsmap *fsmap) +{ + unsigned long long delta, end; + bool need_off; + + need_off = (fsmap->fmr_flags & (FMR_OF_EXTENT_MAP | + FMR_OF_SPECIAL_OWNER)); + + if (fsmap->fmr_physical < start) { + delta = start - fsmap->fmr_physical; + fsmap->fmr_physical = start; + fsmap->fmr_length -= delta; + if (need_off) + fsmap->fmr_offset += delta; + } + + end = fsmap->fmr_physical + fsmap->fmr_length; + if (end > start + length) { + delta = end - (start + length); + fsmap->fmr_length -= delta; + } +} + +static inline void +trim_target_fsmap(const struct clearspace_tgt *tgt, struct fsmap *fsmap) +{ + return __trim_fsmap(tgt->start, tgt->length, fsmap); +} + +static inline void +trim_request_fsmap(const struct clearspace_req *req, struct fsmap *fsmap) +{ + return __trim_fsmap(req->start, req->length, fsmap); +} + +/* Actual space clearing code */ + +/* + * Map all the free space in the region that we're clearing to the space + * catcher file. + */ +static int +csp_grab_free_space( + struct clearspace_req *req) +{ + struct xfs_map_freesp args = { + .offset = req->start, + .len = req->length, + }; + int ret; + + trace_grabfree(req, "start 0x%llx length 0x%llx", + (unsigned long long)req->start, + (unsigned long long)req->length); + + ret = ioctl(req->space_fd, XFS_IOC_MAP_FREESP, &args); + if (ret) { + perror(_("map free space to space capture file")); + return -1; + } + + return 0; +} + +/* + * Rank a refcount record. We prefer to tackle highly shared and longer + * extents first. + */ +static inline unsigned long long +csp_space_prio( + const struct xfs_fsop_geom *g, + const struct xfs_getfsrefs *p) +{ + unsigned long long blocks = p->fcr_length / g->blocksize; + unsigned long long ret = blocks * p->fcr_owners; + + if (ret < blocks || ret < p->fcr_owners) + return UINT64_MAX; + return ret; +} + +/* Make the current refcount record the clearing target if desirable. */ +static void +csp_adjust_target( + struct clearspace_req *req, + struct clearspace_tgt *target, + const struct xfs_getfsrefs *rec, + unsigned long long prio) +{ + if (prio < target->prio) + return; + if (prio == target->prio && + rec->fcr_length <= target->length) + return; + + /* Ignore results that go beyond the end of what we wanted. */ + if (rec->fcr_physical >= req->start + req->length) + return; + + /* Ignore regions that we already tried to clear. */ + if (bitmap_test(req->visited, rec->fcr_physical, rec->fcr_length)) + return; + + trace_target(req, + "set target, prio 0x%llx -> 0x%llx phys 0x%llx bytecount 0x%llx", + target->prio, prio, + (unsigned long long)rec->fcr_physical, + (unsigned long long)rec->fcr_length); + + target->start = rec->fcr_physical; + target->length = rec->fcr_length; + target->owners = rec->fcr_owners; + target->prio = prio; +} + +/* + * Decide if this refcount record maps to extents that are sufficiently + * interesting to target. + */ +static int +csp_evaluate_refcount( + struct clearspace_req *req, + const struct xfs_getfsrefs *rrec, + struct clearspace_tgt *target) +{ + const struct xfs_fsop_geom *fsgeom = &req->xfd->fsgeom; + unsigned long long prio = csp_space_prio(fsgeom, rrec); + int ret; + + if (rrec->fcr_device != req->dev) + return 0; + + if (prio < target->prio) + return 0; + + /* + * XFS only supports sharing data blocks. If there's more than one + * owner, we know that we can easily move the blocks. + */ + if (rrec->fcr_owners > 1) { + csp_adjust_target(req, target, rrec, prio); + return 0; + } + + /* + * Otherwise, this extent has single owners. Walk the fsmap records to + * figure out if they're movable or not. + */ + start_fsmap_query(req, rrec->fcr_device, rrec->fcr_physical, + rrec->fcr_length); + while ((ret = run_fsmap_query(req)) > 0) { + struct fsmap *mrec; + uint64_t next_phys = 0; + + for_each_fsmap_row(req, mrec) { + struct xfs_getfsrefs fake_rec = { }; + + trace_fsmap_rec(req, CSP_TRACE_TARGET, mrec); + + if (mrec->fmr_device != rrec->fcr_device) + continue; + if (mrec->fmr_flags & FMR_OF_SPECIAL_OWNER) + continue; + if (csp_is_internal_owner(req, mrec->fmr_owner)) + continue; + + /* + * If the space has become shared since the fsrefs + * query, just skip this record. We might come back to + * it in a later iteration. + */ + if (mrec->fmr_physical < next_phys) + continue; + + /* Fake enough of a fsrefs to calculate the priority. */ + fake_rec.fcr_physical = mrec->fmr_physical; + fake_rec.fcr_length = mrec->fmr_length; + fake_rec.fcr_owners = 1; + prio = csp_space_prio(fsgeom, &fake_rec); + + /* Target unwritten extents first; they're cheap. */ + if (mrec->fmr_flags & FMR_OF_PREALLOC) + prio |= (1ULL << 63); + + csp_adjust_target(req, target, &fake_rec, prio); + + next_phys = mrec->fmr_physical + mrec->fmr_length; + } + } + end_fsmap_query(req); + + return ret; +} + +/* + * Given a range of storage to search, find the most appealing target for space + * clearing. If nothing suitable is found, the target will be zeroed. + */ +static int +csp_find_target( + struct clearspace_req *req, + struct clearspace_tgt *target) +{ + int ret; + + memset(target, 0, sizeof(struct clearspace_tgt)); + + start_fsrefs_query(req, req->dev, req->start, req->length); + while ((ret = run_fsrefs_query(req)) > 0) { + struct xfs_getfsrefs *rrec; + + for_each_fsref_row(req, rrec) { + trace_fsrefs_rec(req, CSP_TRACE_TARGET, rrec); + ret = csp_evaluate_refcount(req, rrec, target); + if (ret) { + end_fsrefs_query(req); + return ret; + } + } + } + end_fsrefs_query(req); + + if (target->length != 0) { + /* + * Mark this extent visited so that we won't try again this + * round. + */ + trace_bitmap(req, "set filedata start 0x%llx length 0x%llx", + target->start, target->length); + ret = bitmap_set(req->visited, target->start, target->length); + if (ret) { + perror(_("marking file extent visited")); + return ret; + } + } + + return 0; +} + +/* Try to evacuate blocks by using online repair. */ +static int +csp_evac_file_metadata( + struct clearspace_req *req, + struct clearspace_tgt *target, + const struct fsmap *mrec, + int fd, + const struct xfs_bulkstat *bulkstat) +{ + struct xfs_scrub_metadata scrub = { + .sm_type = XFS_SCRUB_TYPE_PROBE, + .sm_flags = XFS_SCRUB_IFLAG_REPAIR | + XFS_SCRUB_IFLAG_FORCE_REBUILD, + }; + struct xfs_fd *xfd = req->xfd; + int ret; + + trace_xrebuild(req, + "ino 0x%llx pos 0x%llx bytecount 0x%llx phys 0x%llx flags 0x%llx", + (unsigned long long)mrec->fmr_owner, + (unsigned long long)mrec->fmr_offset, + (unsigned long long)mrec->fmr_physical, + (unsigned long long)mrec->fmr_length, + (unsigned long long)mrec->fmr_flags); + + if (fd == -1) { + scrub.sm_ino = mrec->fmr_owner; + scrub.sm_gen = bulkstat->bs_gen; + fd = xfd->fd; + } + + if (mrec->fmr_flags & FMR_OF_ATTR_FORK) { + if (mrec->fmr_flags & FMR_OF_EXTENT_MAP) + scrub.sm_type = XFS_SCRUB_TYPE_BMBTA; + else + scrub.sm_type = XFS_SCRUB_TYPE_XATTR; + } else if (mrec->fmr_flags & FMR_OF_EXTENT_MAP) { + scrub.sm_type = XFS_SCRUB_TYPE_BMBTD; + } else if (S_ISLNK(bulkstat->bs_mode)) { + scrub.sm_type = XFS_SCRUB_TYPE_SYMLINK; + } else if (S_ISDIR(bulkstat->bs_mode)) { + scrub.sm_type = XFS_SCRUB_TYPE_DIR; + } + + if (scrub.sm_type == XFS_SCRUB_TYPE_PROBE) + return 0; + + trace_xrebuild(req, "ino 0x%llx gen 0x%x type %u", + (unsigned long long)mrec->fmr_owner, + (unsigned int)bulkstat->bs_gen, + (unsigned int)scrub.sm_type); + + ret = ioctl(fd, XFS_IOC_SCRUB_METADATA, &scrub); + if (ret) { + fprintf(stderr, + _("evacuating inode 0x%llx metadata type %u: %s\n"), + (unsigned long long)mrec->fmr_owner, + scrub.sm_type, strerror(errno)); + return -1; + } + + target->evacuated++; + return 0; +} + +/* + * Open an inode via handle. Returns a file descriptor, -2 if the file is + * gone, or -1 on error. + */ +static int +csp_open_by_handle( + struct clearspace_req *req, + int oflags, + uint64_t ino, + uint32_t gen) +{ + struct xfs_handle handle = { }; + struct xfs_fsop_handlereq hreq = { + .oflags = oflags | O_NOATIME | O_NOFOLLOW | + O_NOCTTY | O_LARGEFILE, + .ihandle = &handle, + .ihandlen = sizeof(handle), + }; + int ret; + + memcpy(&handle.ha_fsid, req->fshandle, sizeof(handle.ha_fsid)); + handle.ha_fid.fid_len = sizeof(xfs_fid_t) - + sizeof(handle.ha_fid.fid_len); + handle.ha_fid.fid_pad = 0; + handle.ha_fid.fid_ino = ino; + handle.ha_fid.fid_gen = gen; + + /* + * Since we extracted the fshandle from the open file instead of using + * path_to_fshandle, the fsid cache doesn't know about the fshandle. + * Construct the open by handle request manually. + */ + ret = ioctl(req->xfd->fd, XFS_IOC_OPEN_BY_HANDLE, &hreq); + if (ret < 0) { + if (errno == ENOENT || errno == EINVAL) + return -2; + + fprintf(stderr, _("open inode 0x%llx: %s\n"), + (unsigned long long)ino, + strerror(errno)); + return -1; + } + + return ret; +} + +/* + * Open a file for evacuation. Returns a positive errno on error; a fd in @fd + * if the caller is supposed to do something; or @fd == -1 if there's nothing + * further to do. + */ +static int +csp_evac_open( + struct clearspace_req *req, + struct clearspace_tgt *target, + const struct fsmap *mrec, + struct xfs_bulkstat *bulkstat, + int oflags, + int *fd) +{ + struct xfs_bulkstat __bs; + int target_fd; + int ret; + + *fd = -1; + + if (csp_is_internal_owner(req, mrec->fmr_owner) || + (mrec->fmr_flags & FMR_OF_SPECIAL_OWNER)) + goto nothing_to_do; + + if (bulkstat == NULL) + bulkstat = &__bs; + + /* + * Snapshot this file so that we can perform a fresh-only exchange. + * For other types of files we just skip to the evacuation step. + */ + ret = -xfrog_bulkstat_single(req->xfd, mrec->fmr_owner, 0, bulkstat); + if (ret) { + if (ret == ENOENT || ret == EINVAL) + goto nothing_to_do; + + fprintf(stderr, _("bulkstat inode 0x%llx: %s\n"), + (unsigned long long)mrec->fmr_owner, + strerror(ret)); + return ret; + } + + /* + * If we get stats for a different inode, the file may have been freed + * out from under us and there's nothing to do. + */ + if (bulkstat->bs_ino != mrec->fmr_owner) + goto nothing_to_do; + + /* + * We're only allowed to open regular files and directories via handle + * so jump to online rebuild for all other file types. + */ + if (!S_ISREG(bulkstat->bs_mode) && !S_ISDIR(bulkstat->bs_mode)) + return csp_evac_file_metadata(req, target, mrec, -1, + bulkstat); + + if (S_ISDIR(bulkstat->bs_mode)) + oflags = O_RDONLY; + + target_fd = csp_open_by_handle(req, oflags, mrec->fmr_owner, + bulkstat->bs_gen); + if (target_fd == -2) + goto nothing_to_do; + if (target_fd < 0) + return -target_fd; + + /* + * Exchange only works for regular file data blocks. If that isn't the + * case, our only recourse is online rebuild. + */ + if (S_ISDIR(bulkstat->bs_mode) || + (mrec->fmr_flags & (FMR_OF_ATTR_FORK | FMR_OF_EXTENT_MAP))) { + int ret2; + + ret = csp_evac_file_metadata(req, target, mrec, target_fd, + bulkstat); + ret2 = close(target_fd); + if (!ret && ret2) + ret = ret2; + return ret; + } + + *fd = target_fd; + return 0; + +nothing_to_do: + target->try_again = true; + return 0; +} + +/* Unshare the space in the work file that we're using for deduplication. */ +static int +csp_unshare_workfile( + struct clearspace_req *req, + unsigned long long start, + unsigned long long length) +{ + int ret; + + trace_falloc(req, "funshare workfd pos 0x%llx bytecount 0x%llx", + start, length); + + ret = fallocate(req->work_fd, FALLOC_FL_UNSHARE_RANGE, start, length); + if (ret) { + perror(_("unsharing work file")); + return ret; + } + + ret = fsync(req->work_fd); + if (ret) { + perror(_("syncing work file")); + return ret; + } + + /* Make sure we didn't get any space within the clearing range. */ + start_bmapx_query(req, 0, start, length); + while ((ret = run_bmapx_query(req, req->work_fd)) > 0) { + struct getbmapx *brec; + + for_each_bmapx_row(req, brec) { + unsigned long long p, l; + + trace_bmapx_rec(req, CSP_TRACE_FALLOC, brec); + p = BBTOB(brec->bmv_block); + l = BBTOB(brec->bmv_length); + + if (p + l < req->start || p >= req->start + req->length) + continue; + + trace_prep(req, + "workfd has extent inside clearing range, phys 0x%llx fsbcount 0x%llx", + p, l); + end_bmapx_query(req); + return -1; + } + } + end_bmapx_query(req); + + return 0; +} + +/* Try to deduplicate every block in the fdr request, if we can. */ +static int +csp_evac_dedupe_loop( + struct clearspace_req *req, + struct clearspace_tgt *target, + unsigned long long ino, + int max_reqlen) +{ + struct file_dedupe_range *fdr = req->fdr; + struct file_dedupe_range_info *info = &fdr->info[0]; + loff_t last_unshare_off = -1; + int ret; + + while (fdr->src_length > 0) { + struct getbmapx brec; + bool same; + unsigned int old_reqlen = fdr->src_length; + + if (max_reqlen && fdr->src_length > max_reqlen) + fdr->src_length = max_reqlen; + + trace_dedupe(req, "ino 0x%llx pos 0x%llx bytecount 0x%llx", + ino, + (unsigned long long)info->dest_offset, + (unsigned long long)fdr->src_length); + + ret = bmapx_one(req, req->work_fd, fdr->src_offset, + fdr->src_length, &brec); + if (ret) + return ret; + + trace_dedupe(req, "workfd pos 0x%llx phys 0x%llx", + (unsigned long long)fdr->src_offset, + (unsigned long long)BBTOB(brec.bmv_block)); + + ret = deduperange(req->work_fd, fdr, &same); + if (ret == ENOSPC && last_unshare_off < fdr->src_offset) { + req->trace_indent++; + trace_dedupe(req, "funshare workfd at phys 0x%llx", + (unsigned long long)fdr->src_offset); + /* + * If we ran out of space, it's possible that we have + * reached the maximum sharing factor of the blocks in + * the work file. Try unsharing the range of the work + * file to get a singly-owned range and loop again. + */ + ret = csp_unshare_workfile(req, fdr->src_offset, + fdr->src_length); + req->trace_indent--; + if (ret) + return ret; + + ret = fsync(req->work_fd); + if (ret) { + perror(_("sync after unshare work file")); + return ret; + } + + last_unshare_off = fdr->src_offset; + fdr->src_length = old_reqlen; + continue; + } + if (ret == EINVAL) { + /* + * If we can't dedupe get the block, it's possible that + * src_fd was punched or truncated out from under us. + * Treat this the same way we would if the contents + * didn't match. + */ + trace_dedupe(req, "cannot evac space, moving on", 0); + same = false; + ret = 0; + } + if (ret) { + fprintf(stderr, _("evacuating inode 0x%llx: %s\n"), + ino, strerror(ret)); + return ret; + } + + if (same) { + req->trace_indent++; + trace_dedupe(req, + "evacuated ino 0x%llx pos 0x%llx bytecount 0x%llx", + ino, + (unsigned long long)info->dest_offset, + (unsigned long long)info->bytes_deduped); + req->trace_indent--; + + target->evacuated++; + } else { + req->trace_indent++; + trace_dedupe(req, + "failed evac ino 0x%llx pos 0x%llx bytecount 0x%llx", + ino, + (unsigned long long)info->dest_offset, + (unsigned long long)fdr->src_length); + req->trace_indent--; + + target->try_again = true; + + /* + * If we aren't single-stepping the deduplication, + * stop early so that the caller goes into single-step + * mode. + */ + if (!max_reqlen) { + fdr->src_length = old_reqlen; + return 0; + } + + /* Contents changed, move on to the next block. */ + info->bytes_deduped = fdr->src_length; + } + fdr->src_length = old_reqlen; + + fdr->src_offset += info->bytes_deduped; + info->dest_offset += info->bytes_deduped; + fdr->src_length -= info->bytes_deduped; + } + + return 0; +} + +/* + * Evacuate one fsmapping by using dedupe to remap data stored in the target + * range to a copy stored in the work file. + */ +static int +csp_evac_dedupe_fsmap( + struct clearspace_req *req, + struct clearspace_tgt *target, + const struct fsmap *mrec) +{ + struct file_dedupe_range *fdr = req->fdr; + struct file_dedupe_range_info *info = &fdr->info[0]; + bool can_single_step; + int target_fd; + int ret, ret2; + + if (mrec->fmr_device != req->dev) { + fprintf(stderr, _("wrong fsmap device in results.\n")); + return -1; + } + + ret = csp_evac_open(req, target, mrec, NULL, O_RDONLY, &target_fd); + if (ret || target_fd < 0) + return ret; + + /* + * Use dedupe to try to shift the target file's mappings to use the + * copy of the data that's in the work file. + */ + fdr->src_offset = mrec->fmr_physical; + fdr->src_length = mrec->fmr_length; + fdr->dest_count = 1; + info->dest_fd = target_fd; + info->dest_offset = mrec->fmr_offset; + + can_single_step = mrec->fmr_length > req->xfd->fsgeom.blocksize; + + /* First we try to do the entire thing all at once. */ + ret = csp_evac_dedupe_loop(req, target, mrec->fmr_owner, 0); + if (ret) + goto out_fd; + + /* If there's any work left, try again one block at a time. */ + if (can_single_step && fdr->src_length > 0) { + ret = csp_evac_dedupe_loop(req, target, mrec->fmr_owner, + req->xfd->fsgeom.blocksize); + if (ret) + goto out_fd; + } + +out_fd: + ret2 = close(target_fd); + if (!ret && ret2) + ret = ret2; + return ret; +} + +/* Use deduplication to remap data extents away from where we're clearing. */ +static int +csp_evac_dedupe( + struct clearspace_req *req, + struct clearspace_tgt *target) +{ + int ret; + + start_fsmap_query(req, req->dev, target->start, target->length); + while ((ret = run_fsmap_query(req)) > 0) { + struct fsmap *mrec; + + for_each_fsmap_row(req, mrec) { + trace_fsmap_rec(req, CSP_TRACE_DEDUPE, mrec); + trim_target_fsmap(target, mrec); + + req->trace_indent++; + ret = csp_evac_dedupe_fsmap(req, target, mrec); + req->trace_indent--; + if (ret) + goto out; + + ret = csp_grab_free_space(req); + if (ret) + goto out; + } + } + +out: + end_fsmap_query(req); + if (ret) + trace_dedupe(req, "ret %d", ret); + return ret; +} + +#define BUFFERCOPY_BUFSZ 65536 + +/* + * Use a memory buffer to copy part of src_fd to dst_fd, or return an errno. */ +static int +csp_buffercopy( + struct clearspace_req *req, + int src_fd, + loff_t src_off, + int dst_fd, + loff_t dst_off, + loff_t len) +{ + int ret = 0; + + while (len > 0) { + size_t count = min(BUFFERCOPY_BUFSZ, len); + ssize_t bytes_read, bytes_written; + + bytes_read = pread(src_fd, req->buf, count, src_off); + if (bytes_read < 0) { + ret = errno; + break; + } + + bytes_written = pwrite(dst_fd, req->buf, bytes_read, dst_off); + if (bytes_written < 0) { + ret = errno; + break; + } + + src_off += bytes_written; + dst_off += bytes_written; + len -= bytes_written; + } + + return ret; +} + +/* + * Prepare the work file to assist in evacuating file data by copying the + * contents of the frozen space into the work file. + */ +static int +csp_prepare_for_dedupe( + struct clearspace_req *req) +{ + struct file_clone_range fcr; + struct stat statbuf; + loff_t datapos = 0; + loff_t length = 0; + int ret; + + ret = fstat(req->space_fd, &statbuf); + if (ret) { + perror(_("space capture file")); + return ret; + } + + ret = ftruncate(req->work_fd, 0); + if (ret) { + perror(_("truncate work file")); + return ret; + } + + ret = ftruncate(req->work_fd, statbuf.st_size); + if (ret) { + perror(_("reset work file")); + return ret; + } + + /* Make a working copy of the frozen file data. */ + start_spacefd_iter(req); + while ((ret = spacefd_data_iter(req, &datapos, &length)) > 0) { + trace_prep(req, "clone spacefd data 0x%llx length 0x%llx", + (long long)datapos, (long long)length); + + fcr.src_fd = req->space_fd; + fcr.src_offset = datapos; + fcr.src_length = length; + fcr.dest_offset = datapos; + + ret = clonerange(req->work_fd, &fcr); + if (ret == ENOSPC) { + req->trace_indent++; + trace_prep(req, + "falling back to buffered copy at 0x%llx", + (long long)datapos); + req->trace_indent--; + ret = csp_buffercopy(req, req->space_fd, datapos, + req->work_fd, datapos, length); + } + if (ret) { + perror( + _("copying space capture file contents to work file")); + return ret; + } + } + end_spacefd_iter(req); + if (ret < 0) + return ret; + + /* + * Unshare the work file so that it contains an identical copy of the + * contents of the space capture file but mapped to different blocks. + * This is key to using dedupe to migrate file space away from the + * requested region. + */ + req->trace_indent++; + ret = csp_unshare_workfile(req, req->start, req->length); + req->trace_indent--; + return ret; +} + +/* + * Evacuate one fsmapping by using dedupe to remap data stored in the target + * range to a copy stored in the work file. + */ +static int +csp_evac_exchange_fsmap( + struct clearspace_req *req, + struct clearspace_tgt *target, + const struct fsmap *mrec) +{ + struct xfs_bulkstat bulkstat; + struct xfs_exch_range xchg = { }; + struct getbmapx brec; + int target_fd; + int ret, ret2; + + if (mrec->fmr_device != req->dev) { + fprintf(stderr, _("wrong fsmap device in results.\n")); + return -1; + } + + ret = csp_evac_open(req, target, mrec, &bulkstat, O_RDWR, &target_fd); + if (ret || target_fd < 0) + return ret; + + ret = ftruncate(req->work_fd, 0); + if (ret) { + perror(_("truncating work file")); + goto out_fd; + } + + /* + * Copy the data from the original file to the work file. We assume + * that the work file will end up with different data blocks and that + * they're outside of the requested range. + */ + ret = csp_buffercopy(req, target_fd, mrec->fmr_offset, req->work_fd, + mrec->fmr_offset, mrec->fmr_length); + if (ret) { + fprintf(stderr, _("copying target file to work file: %s\n"), + strerror(ret)); + goto out_fd; + } + + ret = fsync(req->work_fd); + if (ret) { + perror(_("flush work file for fiexchange")); + goto out_fd; + } + + ret = bmapx_one(req, req->work_fd, mrec->fmr_physical, + mrec->fmr_length, &brec); + if (ret) + return ret; + + trace_exchange(req, "workfd pos 0x%llx phys 0x%llx", + (unsigned long long)mrec->fmr_physical, + (unsigned long long)BBTOB(brec.bmv_block)); + + /* + * Exchange the mappings, with the freshness check enabled. This + * should result in the target file being switched to new blocks unless + * it has changed, in which case we bounce out and find a new target. + */ + xfrog_file_exchange_prep(NULL, XFS_EXCH_RANGE_NONATOMIC, + mrec->fmr_offset, req->work_fd, mrec->fmr_offset, + mrec->fmr_length, &xchg); + xfrog_file_exchange_require_file2_fresh(&xchg, &bulkstat); + ret = exchangerange(target_fd, &xchg); + if (ret) { + if (ret == EBUSY) { + req->trace_indent++; + trace_exchange(req, + "failed evac ino 0x%llx pos 0x%llx bytecount 0x%llx", + bulkstat.bs_ino, + (unsigned long long)mrec->fmr_offset, + (unsigned long long)mrec->fmr_length); + req->trace_indent--; + target->try_again = true; + } else { + fprintf(stderr, + _("exchanging target and work file contents: %s\n"), + strerror(ret)); + } + goto out_fd; + } + + req->trace_indent++; + trace_exchange(req, + "evacuated ino 0x%llx pos 0x%llx bytecount 0x%llx", + bulkstat.bs_ino, + (unsigned long long)mrec->fmr_offset, + (unsigned long long)mrec->fmr_length); + req->trace_indent--; + target->evacuated++; + +out_fd: + ret2 = close(target_fd); + if (!ret && ret2) + ret = ret2; + return ret; +} + +/* + * Try to evacuate all data blocks in the target region by copying the contents + * to a new file and exchanging the extents. + */ +static int +csp_evac_exchange( + struct clearspace_req *req, + struct clearspace_tgt *target) +{ + int ret; + + start_fsmap_query(req, req->dev, target->start, target->length); + while ((ret = run_fsmap_query(req)) > 0) { + struct fsmap *mrec; + + for_each_fsmap_row(req, mrec) { + trace_fsmap_rec(req, CSP_TRACE_EXCHANGE, mrec); + trim_target_fsmap(target, mrec); + + req->trace_indent++; + ret = csp_evac_exchange_fsmap(req, target, mrec); + req->trace_indent--; + if (ret) + goto out; + + ret = csp_grab_free_space(req); + if (ret) + goto out; + } + } +out: + end_fsmap_query(req); + if (ret) + trace_exchange(req, "ret %d", ret); + return ret; +} + +/* Try to evacuate blocks by using online repair to rebuild AG metadata. */ +static int +csp_evac_ag_metadata( + struct clearspace_req *req, + struct clearspace_tgt *target, + uint32_t agno, + uint32_t mask) +{ + struct xfs_scrub_metadata scrub = { + .sm_flags = XFS_SCRUB_IFLAG_REPAIR | + XFS_SCRUB_IFLAG_FORCE_REBUILD, + }; + unsigned int i; + int ret; + + trace_xrebuild(req, "agno 0x%x mask 0x%x", + (unsigned int)agno, + (unsigned int)mask); + + for (i = XFS_SCRUB_TYPE_AGFL; i < XFS_SCRUB_TYPE_REFCNTBT; i++) { + + if (!(mask & (1U << i))) + continue; + + scrub.sm_type = i; + + req->trace_indent++; + trace_xrebuild(req, "agno %u type %u", + (unsigned int)agno, + (unsigned int)scrub.sm_type); + req->trace_indent--; + + ret = ioctl(req->xfd->fd, XFS_IOC_SCRUB_METADATA, &scrub); + if (ret) { + if (errno == ENOENT || errno == ENOSPC) + continue; + fprintf(stderr, _("rebuilding ag %u type %u: %s\n"), + (unsigned int)agno, scrub.sm_type, + strerror(errno)); + return -1; + } + + target->evacuated++; + + ret = csp_grab_free_space(req); + if (ret) + return ret; + } + + return 0; +} + +/* Compute a scrub mask for a fsmap special owner. */ +static uint32_t +fsmap_owner_to_scrub_mask(__u64 owner) +{ + switch (owner) { + case XFS_FMR_OWN_FREE: + case XFS_FMR_OWN_UNKNOWN: + case XFS_FMR_OWN_FS: + case XFS_FMR_OWN_LOG: + /* can't move these */ + return 0; + case XFS_FMR_OWN_AG: + return (1U << XFS_SCRUB_TYPE_BNOBT) | + (1U << XFS_SCRUB_TYPE_CNTBT) | + (1U << XFS_SCRUB_TYPE_AGFL) | + (1U << XFS_SCRUB_TYPE_RMAPBT); + case XFS_FMR_OWN_INOBT: + return (1U << XFS_SCRUB_TYPE_INOBT) | + (1U << XFS_SCRUB_TYPE_FINOBT); + case XFS_FMR_OWN_REFC: + return (1U << XFS_SCRUB_TYPE_REFCNTBT); + case XFS_FMR_OWN_INODES: + case XFS_FMR_OWN_COW: + /* don't know how to get rid of these */ + return 0; + case XFS_FMR_OWN_DEFECTIVE: + /* good, get rid of it */ + return 0; + default: + return 0; + } +} + +/* Try to clear all per-AG metadata from the requested range. */ +static int +csp_evac_fs_metadata( + struct clearspace_req *req, + struct clearspace_tgt *target, + bool *cleared_anything) +{ + uint32_t curr_agno = -1U; + uint32_t curr_mask = 0; + int ret = 0; + + if (req->realtime) + return 0; + + start_fsmap_query(req, req->dev, target->start, target->length); + while ((ret = run_fsmap_query(req)) > 0) { + struct fsmap *mrec; + + for_each_fsmap_row(req, mrec) { + uint64_t daddr; + uint32_t agno; + uint32_t mask; + + if (mrec->fmr_device != req->dev) + continue; + if (!(mrec->fmr_flags & FMR_OF_SPECIAL_OWNER)) + continue; + + /* Ignore regions that we already tried to clear. */ + if (bitmap_test(req->visited, mrec->fmr_physical, + mrec->fmr_length)) + continue; + + mask = fsmap_owner_to_scrub_mask(mrec->fmr_owner); + if (!mask) + continue; + + trace_fsmap_rec(req, CSP_TRACE_XREBUILD, mrec); + + daddr = BTOBB(mrec->fmr_physical); + agno = cvt_daddr_to_agno(req->xfd, daddr); + + trace_xrebuild(req, + "agno 0x%x -> 0x%x mask 0x%x owner %lld", + curr_agno, agno, curr_mask, + (unsigned long long)mrec->fmr_owner); + + if (curr_agno == -1U) { + curr_agno = agno; + } else if (curr_agno != agno) { + ret = csp_evac_ag_metadata(req, target, + curr_agno, curr_mask); + if (ret) + goto out; + + *cleared_anything = true; + curr_agno = agno; + curr_mask = 0; + } + + /* Put this on the list and try to clear it once. */ + curr_mask |= mask; + ret = bitmap_set(req->visited, mrec->fmr_physical, + mrec->fmr_length); + if (ret) { + perror(_("marking metadata extent visited")); + goto out; + } + } + } + + if (curr_agno != -1U && curr_mask != 0) { + ret = csp_evac_ag_metadata(req, target, curr_agno, curr_mask); + if (ret) + goto out; + *cleared_anything = true; + } + + if (*cleared_anything) + trace_bitmap(req, "set metadata start 0x%llx length 0x%llx", + target->start, target->length); + +out: + end_fsmap_query(req); + if (ret) + trace_xrebuild(req, "ret %d", ret); + return ret; +} + +/* + * Check that at least the start of the mapping was frozen into the work file + * at the correct offset. Set @len to the number of bytes that were frozen. + * Returns -1 for error, zero if written extents are waiting to be mapped into + * the space capture file, or 1 if there's nothing to transfer to the space + * capture file. + */ +enum freeze_outcome { + FREEZE_FAILED = -1, + FREEZE_DONE, + FREEZE_SKIP, +}; + +static enum freeze_outcome +csp_freeze_check_outcome( + struct clearspace_req *req, + const struct fsmap *mrec, + unsigned long long *len) +{ + struct getbmapx brec; + int ret; + + *len = 0; + + ret = bmapx_one(req, req->work_fd, 0, mrec->fmr_length, &brec); + if (ret) + return FREEZE_FAILED; + + trace_freeze(req, + "check if workfd pos 0x0 phys 0x%llx len 0x%llx maps to phys 0x%llx len 0x%llx", + (unsigned long long)mrec->fmr_physical, + (unsigned long long)mrec->fmr_length, + (unsigned long long)BBTOB(brec.bmv_block), + (unsigned long long)BBTOB(brec.bmv_length)); + + /* freeze of an unwritten extent punches a hole in the work file. */ + if ((mrec->fmr_flags & FMR_OF_PREALLOC) && brec.bmv_block == -1) { + *len = min(mrec->fmr_length, BBTOB(brec.bmv_length)); + return FREEZE_SKIP; + } + + /* + * freeze of a written extent must result in the same physical space + * being mapped into the work file. + */ + if (!(mrec->fmr_flags & FMR_OF_PREALLOC) && + BBTOB(brec.bmv_block) == mrec->fmr_physical) { + *len = min(mrec->fmr_length, BBTOB(brec.bmv_length)); + return FREEZE_DONE; + } + + /* + * We didn't find what we were looking for, which implies that the + * mapping changed out from under us. Punch out everything that could + * have been mapped into the work file. Set @len to zero and return so + * that we try again with the next mapping. + */ + trace_falloc(req, "reset workfd isize 0x0", 0); + + ret = ftruncate(req->work_fd, 0); + if (ret) { + perror(_("resetting work file after failed freeze")); + return FREEZE_FAILED; + } + + return FREEZE_SKIP; +} + +/* + * Open a file to try to freeze whatever data is in the requested range. + * + * Returns nonzero on error. Returns zero and a file descriptor in @fd if the + * caller is supposed to do something; or returns zero and @fd == -1 if there's + * nothing to freeze. + */ +static int +csp_freeze_open( + struct clearspace_req *req, + const struct fsmap *mrec, + int *fd) +{ + struct xfs_bulkstat bulkstat; + int oflags = O_RDWR; + int target_fd; + int ret; + + *fd = -1; + + ret = -xfrog_bulkstat_single(req->xfd, mrec->fmr_owner, 0, &bulkstat); + if (ret) { + if (ret == ENOENT || ret == EINVAL) + return 0; + + fprintf(stderr, _("bulkstat inode 0x%llx: %s\n"), + (unsigned long long)mrec->fmr_owner, + strerror(errno)); + return ret; + } + + /* + * If we get stats for a different inode, the file may have been freed + * out from under us and there's nothing to do. + */ + if (bulkstat.bs_ino != mrec->fmr_owner) + return 0; + + /* Skip anything we can't freeze. */ + if (!S_ISREG(bulkstat.bs_mode) && !S_ISDIR(bulkstat.bs_mode)) + return 0; + + if (S_ISDIR(bulkstat.bs_mode)) + oflags = O_RDONLY; + + target_fd = csp_open_by_handle(req, oflags, mrec->fmr_owner, + bulkstat.bs_gen); + if (target_fd == -2) + return 0; + if (target_fd < 0) + return target_fd; + + /* + * Skip mappings for directories, xattr data, and block mapping btree + * blocks. We still have to close the file though. + */ + if (S_ISDIR(bulkstat.bs_mode) || + (mrec->fmr_flags & (FMR_OF_ATTR_FORK | FMR_OF_EXTENT_MAP))) { + return close(target_fd); + } + + *fd = target_fd; + return 0; +} + +static inline uint64_t rounddown_64(uint64_t x, uint64_t y) +{ + return (x / y) * y; +} + +/* + * Deal with a frozen extent containing a partially written EOF block. Either + * we use funshare to get src_fd to release the block, or we reduce the length + * of the frozen extent by one block. + */ +static int +csp_freeze_unaligned_eofblock( + struct clearspace_req *req, + int src_fd, + const struct fsmap *mrec, + unsigned long long *frozen_len) +{ + struct getbmapx brec; + struct stat statbuf; + loff_t work_offset, length; + int ret; + + ret = fstat(req->work_fd, &statbuf); + if (ret) { + perror(_("statting work file")); + return ret; + } + + /* + * The frozen extent is less than the size of the work file, which + * means that we're already block aligned. + */ + if (*frozen_len <= statbuf.st_size) + return 0; + + /* The frozen extent does not contain a partially written EOF block. */ + if (statbuf.st_size % statbuf.st_blksize == 0) + return 0; + + /* + * Unshare what we think is a partially written EOF block of the + * original file, to try to force it to release that block. + */ + work_offset = rounddown_64(statbuf.st_size, statbuf.st_blksize); + length = statbuf.st_size - work_offset; + + trace_freeze(req, + "unaligned eofblock 0x%llx work_size 0x%llx blksize 0x%x work_offset 0x%llx work_length 0x%llx", + *frozen_len, statbuf.st_size, statbuf.st_blksize, + work_offset, length); + + ret = fallocate(src_fd, FALLOC_FL_UNSHARE_RANGE, + mrec->fmr_offset + work_offset, length); + if (ret) { + perror(_("unsharing original file")); + return ret; + } + + ret = fsync(src_fd); + if (ret) { + perror(_("flushing original file")); + return ret; + } + + ret = bmapx_one(req, req->work_fd, work_offset, length, &brec); + if (ret) + return ret; + + if (BBTOB(brec.bmv_block) != mrec->fmr_physical + work_offset) { + fprintf(stderr, + _("work file offset 0x%llx maps to phys 0x%llx, expected 0x%llx"), + (unsigned long long)work_offset, + (unsigned long long)BBTOB(brec.bmv_block), + (unsigned long long)mrec->fmr_physical); + return -1; + } + + /* + * If the block is still shared, there must be other owners of this + * block. Round down the frozen length and we'll come back to it + * eventually. + */ + if (brec.bmv_oflags & BMV_OF_SHARED) { + *frozen_len = work_offset; + return 0; + } + + /* + * Not shared anymore, so increase the size of the file to the next + * block boundary so that we can reflink it into the space capture + * file. + */ + ret = ftruncate(req->work_fd, + BBTOB(brec.bmv_length) + BBTOB(brec.bmv_offset)); + if (ret) { + perror(_("expanding work file")); + return ret; + } + + /* Double-check that we didn't lose the block. */ + ret = bmapx_one(req, req->work_fd, work_offset, length, &brec); + if (ret) + return ret; + + if (BBTOB(brec.bmv_block) != mrec->fmr_physical + work_offset) { + fprintf(stderr, + _("work file offset 0x%llx maps to phys 0x%llx, should be 0x%llx"), + (unsigned long long)work_offset, + (unsigned long long)BBTOB(brec.bmv_block), + (unsigned long long)mrec->fmr_physical); + return -1; + } + + return 0; +} + +/* + * Given a fsmap, try to reflink the physical space into the space capture + * file. + */ +static int +csp_freeze_req_fsmap( + struct clearspace_req *req, + unsigned long long *cursor, + const struct fsmap *mrec) +{ + struct fsmap short_mrec; + struct file_clone_range fcr = { }; + unsigned long long frozen_len; + enum freeze_outcome outcome; + int src_fd; + int ret, ret2; + + if (mrec->fmr_device != req->dev) { + fprintf(stderr, _("wrong fsmap device in results.\n")); + return -1; + } + + /* Ignore mappings for our secret files. */ + if (csp_is_internal_owner(req, mrec->fmr_owner)) + return 0; + + /* Ignore mappings before the cursor. */ + if (mrec->fmr_physical + mrec->fmr_length < *cursor) + return 0; + + /* Jump past mappings for metadata. */ + if (mrec->fmr_flags & FMR_OF_SPECIAL_OWNER) + goto skip; + + /* + * Open this file so that we can try to freeze its data blocks. + * For other types of files we just skip to the evacuation step. + */ + ret = csp_freeze_open(req, mrec, &src_fd); + if (ret) + return ret; + if (src_fd < 0) + goto skip; + + /* + * If the cursor is in the middle of this mapping, increase the start + * of the mapping to start at the cursor. + */ + if (mrec->fmr_physical < *cursor) { + unsigned long long delta = *cursor - mrec->fmr_physical; + + short_mrec = *mrec; + short_mrec.fmr_physical = *cursor; + short_mrec.fmr_offset += delta; + short_mrec.fmr_length -= delta; + + mrec = &short_mrec; + } + + req->trace_indent++; + if (mrec->fmr_length == 0) { + trace_freeze(req, "skipping zero-length freeze", 0); + goto out_fd; + } + + /* + * Reflink the mapping from the source file into the empty work file so + * that a write will be written elsewhere. The only way to reflink a + * partially written EOF block is if the kernel can reset the work file + * size so that the post-EOF part of the block remains post-EOF. If we + * can't do that, we're sunk. If the mapping is unwritten, we'll leave + * a hole in the work file. + */ + ret = ftruncate(req->work_fd, 0); + if (ret) { + perror(_("truncating work file for freeze")); + goto out_fd; + } + + fcr.src_fd = src_fd; + fcr.src_offset = mrec->fmr_offset; + fcr.src_length = mrec->fmr_length; + fcr.dest_offset = 0; + + trace_freeze(req, + "reflink ino 0x%llx offset 0x%llx bytecount 0x%llx into workfd", + (unsigned long long)mrec->fmr_owner, + (unsigned long long)fcr.src_offset, + (unsigned long long)fcr.src_length); + + ret = clonerange(req->work_fd, &fcr); + if (ret == EINVAL) { + /* + * If that didn't work, try reflinking to EOF and picking out + * whatever pieces we want. + */ + fcr.src_length = 0; + + trace_freeze(req, + "reflink ino 0x%llx offset 0x%llx to EOF into workfd", + (unsigned long long)mrec->fmr_owner, + (unsigned long long)fcr.src_offset); + + ret = clonerange(req->work_fd, &fcr); + } + if (ret == EINVAL) { + /* + * If we still can't get the block, it's possible that src_fd + * was punched or truncated out from under us, so we just move + * on to the next fsmap. + */ + trace_freeze(req, "cannot freeze space, moving on", 0); + ret = 0; + goto out_fd; + } + if (ret) { + fprintf(stderr, _("freezing space to work file: %s\n"), + strerror(ret)); + goto out_fd; + } + + req->trace_indent++; + outcome = csp_freeze_check_outcome(req, mrec, &frozen_len); + req->trace_indent--; + switch (outcome) { + case FREEZE_FAILED: + ret = -1; + goto out_fd; + case FREEZE_SKIP: + *cursor += frozen_len; + goto out_fd; + case FREEZE_DONE: + break; + } + + /* + * If we tried reflinking to EOF to capture a partially written EOF + * block in the work file, we need to unshare the end of the source + * file before we try to reflink the frozen space into the space + * capture file. + */ + if (fcr.src_length == 0) { + ret = csp_freeze_unaligned_eofblock(req, src_fd, mrec, + &frozen_len); + if (ret) + goto out_fd; + } + + /* + * We've frozen the mapping by reflinking it into the work file and + * confirmed that the work file has the space we wanted. Now we need + * to map the same extent into the space capture file. If reflink + * fails because we're out of space, fall back to EXCHANGE_RANGE. The + * end goal is to populate the space capture file; we don't care about + * the contents of the work file. + */ + fcr.src_fd = req->work_fd; + fcr.src_offset = 0; + fcr.dest_offset = mrec->fmr_physical; + fcr.src_length = frozen_len; + + trace_freeze(req, "reflink phys 0x%llx len 0x%llx to spacefd", + (unsigned long long)mrec->fmr_physical, + (unsigned long long)mrec->fmr_length); + + ret = clonerange(req->space_fd, &fcr); + if (ret == ENOSPC) { + struct xfs_exch_range xchg; + + xfrog_file_exchange_prep(NULL, XFS_EXCH_RANGE_NONATOMIC, + mrec->fmr_physical, req->work_fd, + mrec->fmr_physical, frozen_len, &xchg); + ret = exchangerange(req->space_fd, &xchg); + } + if (ret) { + fprintf(stderr, _("freezing space to space capture file: %s\n"), + strerror(ret)); + goto out_fd; + } + + *cursor += frozen_len; +out_fd: + ret2 = close(src_fd); + if (!ret && ret2) + ret = ret2; + req->trace_indent--; + if (ret) + trace_freeze(req, "ret %d", ret); + return ret; +skip: + *cursor += mrec->fmr_length; + return 0; +} + +/* + * Try to freeze all the space in the requested range against overwrites. + * + * For each file data fsmap within each hole in the part of the space capture + * file corresponding to the requested range, try to reflink the space into the + * space capture file so that any subsequent writes to the original owner are + * CoW and nobody else can allocate the space. If we cannot use reflink to + * freeze all the space, we cannot proceed with the clearing. + */ +static int +csp_freeze_req_range( + struct clearspace_req *req) +{ + unsigned long long cursor = req->start; + loff_t holepos = 0; + loff_t length = 0; + int ret; + + ret = ftruncate(req->space_fd, req->start + req->length); + if (ret) { + perror(_("setting up space capture file")); + return ret; + } + + if (!req->use_reflink) + return 0; + + start_spacefd_iter(req); + while ((ret = spacefd_hole_iter(req, &holepos, &length)) > 0) { + trace_freeze(req, "spacefd hole 0x%llx length 0x%llx", + (long long)holepos, (long long)length); + + start_fsmap_query(req, req->dev, holepos, length); + while ((ret = run_fsmap_query(req)) > 0) { + struct fsmap *mrec; + + for_each_fsmap_row(req, mrec) { + trace_fsmap_rec(req, CSP_TRACE_FREEZE, mrec); + trim_request_fsmap(req, mrec); + ret = csp_freeze_req_fsmap(req, &cursor, mrec); + if (ret) { + end_fsmap_query(req); + goto out; + } + } + } + end_fsmap_query(req); + } +out: + end_spacefd_iter(req); + return ret; +} + +/* + * Dump all speculative preallocations, COW staging blocks, and inactive inodes + * to try to free up as much space as we can. + */ +static int +csp_collect_garbage( + struct clearspace_req *req) +{ + struct xfs_fs_eofblocks eofb = { + .eof_version = XFS_EOFBLOCKS_VERSION, + .eof_flags = XFS_EOF_FLAGS_SYNC, + }; + int ret; + + ret = ioctl(req->xfd->fd, XFS_IOC_FREE_EOFBLOCKS, &eofb); + if (ret) { + perror(_("xfs garbage collector")); + return -1; + } + + return 0; +} + +static int +csp_prepare( + struct clearspace_req *req) +{ + blkcnt_t old_blocks = 0; + int ret; + + /* + * Empty out CoW forks and speculative post-EOF preallocations before + * starting the clearing process. This may be somewhat overkill. + */ + ret = syncfs(req->xfd->fd); + if (ret) { + perror(_("syncing filesystem")); + return ret; + } + + ret = csp_collect_garbage(req); + if (ret) + return ret; + + /* + * Set up the space capture file as a large sparse file mirroring the + * physical space that we want to defragment. + */ + ret = ftruncate(req->space_fd, req->start + req->length); + if (ret) { + perror(_("setting up space capture file")); + return ret; + } + + /* + * If we don't have reflink, just grab the free space and move on to + * copying and exchanging file contents. + */ + if (!req->use_reflink) + return csp_grab_free_space(req); + + /* + * Try to freeze as much of the requested range as we can, grab the + * free space in that range, and run freeze again to pick up anything + * that may have been allocated while all that was going on. + */ + do { + struct stat statbuf; + + ret = csp_freeze_req_range(req); + if (ret) + return ret; + + ret = csp_grab_free_space(req); + if (ret) + return ret; + + ret = fstat(req->space_fd, &statbuf); + if (ret) + return ret; + + if (old_blocks == statbuf.st_blocks) + break; + old_blocks = statbuf.st_blocks; + } while (1); + + /* + * If reflink is enabled, our strategy is to dedupe to free blocks in + * the area that we're clearing without making any user-visible changes + * to the file contents. For all the written file data blocks in area + * we're clearing, make an identical copy in the work file that is + * backed by blocks that are not in the clearing area. + */ + return csp_prepare_for_dedupe(req); +} + +/* Set up the target to clear all metadata from the given range. */ +static inline void +csp_target_metadata( + struct clearspace_req *req, + struct clearspace_tgt *target) +{ + target->start = req->start; + target->length = req->length; + target->prio = 0; + target->evacuated = 0; + target->owners = 0; + target->try_again = false; +} + +/* + * Loop through the space to find the most appealing part of the device to + * clear, then try to evacuate everything within. + */ +int +clearspace_run( + struct clearspace_req *req) +{ + struct clearspace_tgt target; + const struct csp_errstr *es; + bool cleared_anything; + int ret; + + if (req->trace_mask) { + fprintf(stderr, "debug flags 0x%x:", req->trace_mask); + for (es = errtags; es->tag; es++) { + if (req->trace_mask & es->mask) + fprintf(stderr, " %s", es->tag); + } + fprintf(stderr, "\n"); + } + + req->trace_indent = 0; + trace_status(req, + _("Clearing dev %u:%u physical 0x%llx bytecount 0x%llx."), + major(req->dev), minor(req->dev), + req->start, req->length); + + if (req->trace_mask & ~CSP_TRACE_STATUS) + trace_status(req, "reflink? %d evac_metadata? %d", + req->use_reflink, req->can_evac_metadata); + + ret = bitmap_alloc(&req->visited); + if (ret) { + perror(_("allocating visited bitmap")); + return ret; + } + + ret = csp_prepare(req); + if (ret) + goto out_bitmap; + + /* Evacuate as many file blocks as we can. */ + do { + ret = csp_find_target(req, &target); + if (ret) + goto out_bitmap; + + if (target.length == 0) + break; + + trace_target(req, + "phys 0x%llx len 0x%llx owners 0x%llx prio 0x%llx", + target.start, target.length, + target.owners, target.prio); + + if (req->use_reflink) + ret = csp_evac_dedupe(req, &target); + else + ret = csp_evac_exchange(req, &target); + if (ret) + goto out_bitmap; + + trace_status(req, _("Evacuated %llu file items."), + target.evacuated); + } while (target.evacuated > 0 || target.try_again); + + if (!req->can_evac_metadata) + goto out_bitmap; + + /* Evacuate as many AG metadata blocks as we can. */ + do { + csp_target_metadata(req, &target); + + ret = csp_evac_fs_metadata(req, &target, &cleared_anything); + if (ret) + goto out_bitmap; + + trace_status(req, "evacuated %llu metadata items", + target.evacuated); + } while (target.evacuated > 0 && cleared_anything); + +out_bitmap: + bitmap_free(&req->visited); + return ret; +} + +/* How much space did we actually clear? */ +int +clearspace_efficacy( + struct clearspace_req *req, + unsigned long long *cleared_bytes) +{ + unsigned long long cleared = 0; + int ret; + + start_bmapx_query(req, 0, req->start, req->length); + while ((ret = run_bmapx_query(req, req->space_fd)) > 0) { + struct getbmapx *brec; + + for_each_bmapx_row(req, brec) { + if (brec->bmv_block == -1) + continue; + + trace_bmapx_rec(req, CSP_TRACE_EFFICACY, brec); + + if (brec->bmv_offset != brec->bmv_block) { + fprintf(stderr, + _("space capture file mapped incorrectly\n")); + end_bmapx_query(req); + return -1; + } + cleared += BBTOB(brec->bmv_length); + } + } + end_bmapx_query(req); + if (ret) + return ret; + + *cleared_bytes = cleared; + return 0; +} + +/* + * Create a temporary file on the same volume (data/rt) that we're trying to + * clear free space on. + */ +static int +csp_open_tempfile( + struct clearspace_req *req, + struct stat *statbuf) +{ + struct fsxattr fsx; + int fd, ret; + + fd = openat(req->xfd->fd, ".", O_TMPFILE | O_RDWR | O_EXCL, 0600); + if (fd < 0) { + perror(_("opening temp file")); + return -1; + } + + /* Make sure we got the same filesystem as the open file. */ + ret = fstat(fd, statbuf); + if (ret) { + perror(_("stat temp file")); + goto fail; + } + if (statbuf->st_dev != req->statbuf.st_dev) { + fprintf(stderr, + _("Cannot create temp file on same fs as open file.\n")); + goto fail; + } + + /* Ensure this file targets the correct data/rt device. */ + ret = ioctl(fd, FS_IOC_FSGETXATTR, &fsx); + if (ret) { + perror(_("FSGETXATTR temp file")); + goto fail; + } + + if (!!(fsx.fsx_xflags & FS_XFLAG_REALTIME) != req->realtime) { + if (req->realtime) + fsx.fsx_xflags |= FS_XFLAG_REALTIME; + else + fsx.fsx_xflags &= ~FS_XFLAG_REALTIME; + + ret = ioctl(fd, FS_IOC_FSSETXATTR, &fsx); + if (ret) { + perror(_("FSSETXATTR temp file")); + goto fail; + } + } + + trace_setup(req, "opening temp inode 0x%llx as fd %d", + (unsigned long long)statbuf->st_ino, fd); + + return fd; +fail: + close(fd); + return -1; +} + +/* Extract fshandle from the open file. */ +static int +csp_install_file( + struct clearspace_req *req, + struct xfs_fd *xfd) +{ + void *handle; + size_t handle_sz; + int ret; + + ret = fstat(xfd->fd, &req->statbuf); + if (ret) + return ret; + + if (!S_ISDIR(req->statbuf.st_mode)) { + errno = -ENOTDIR; + return -1; + } + + ret = fd_to_handle(xfd->fd, &handle, &handle_sz); + if (ret) + return ret; + + ret = handle_to_fshandle(handle, handle_sz, &req->fshandle, + &req->fshandle_sz); + if (ret) + return ret; + + free_handle(handle, handle_sz); + req->xfd = xfd; + return 0; +} + +/* Decide if we can use online repair to evacuate metadata. */ +static void +csp_detect_evac_metadata( + struct clearspace_req *req) +{ + struct xfs_scrub_metadata scrub = { + .sm_type = XFS_SCRUB_TYPE_PROBE, + .sm_flags = XFS_SCRUB_IFLAG_REPAIR | + XFS_SCRUB_IFLAG_FORCE_REBUILD, + }; + int ret; + + ret = ioctl(req->xfd->fd, XFS_IOC_SCRUB_METADATA, &scrub); + if (ret) + return; + + /* + * We'll try to evacuate metadata if the probe works. This doesn't + * guarantee success; it merely means that the kernel call exists. + */ + req->can_evac_metadata = true; +} + +/* Detect XFS_IOC_MAP_FREESP; this is critical for grabbing free space! */ +static int +csp_detect_map_freesp( + struct clearspace_req *req) +{ + struct xfs_map_freesp args = { + .offset = 0, + .len = 1, + }; + int ret; + + /* + * A single-byte fallocate request will succeed without doing anything + * to the filesystem. + */ + ret = ioctl(req->work_fd, XFS_IOC_MAP_FREESP, &args); + if (!ret) + return 0; + + if (errno == EOPNOTSUPP) { + fprintf(stderr, + _("Filesystem does not support XFS_IOC_MAP_FREESP\n")); + return -1; + } + + perror(_("test XFS_IOC_MAP_FREESP on work file")); + return -1; +} + +/* + * Assemble operation information to clear the physical space in part of a + * filesystem. + */ +int +clearspace_init( + struct clearspace_req **reqp, + const struct clearspace_init *attrs) +{ + struct clearspace_req *req; + int ret; + + req = calloc(1, sizeof(struct clearspace_req)); + if (!req) { + perror(_("malloc clearspace")); + return -1; + } + + req->work_fd = -1; + req->space_fd = -1; + req->trace_mask = attrs->trace_mask; + + req->realtime = attrs->is_realtime; + req->dev = attrs->dev; + req->start = attrs->start; + req->length = attrs->length; + + ret = csp_install_file(req, attrs->xfd); + if (ret) { + perror(attrs->fname); + goto fail; + } + + csp_detect_evac_metadata(req); + + req->work_fd = csp_open_tempfile(req, &req->temp_statbuf); + if (req->work_fd < 0) + goto fail; + + req->space_fd = csp_open_tempfile(req, &req->space_statbuf); + if (req->space_fd < 0) + goto fail; + + ret = csp_detect_map_freesp(req); + if (ret) + goto fail; + + req->mhead = calloc(1, fsmap_sizeof(QUERY_BATCH_SIZE)); + if (!req->mhead) { + perror(_("opening fs mapping query")); + goto fail; + } + + req->rhead = calloc(1, xfs_getfsrefs_sizeof(QUERY_BATCH_SIZE)); + if (!req->rhead) { + perror(_("opening refcount query")); + goto fail; + } + + req->bhead = calloc(QUERY_BATCH_SIZE + 1, sizeof(struct getbmapx)); + if (!req->bhead) { + perror(_("opening file mapping query")); + goto fail; + } + + req->buf = malloc(BUFFERCOPY_BUFSZ); + if (!req->buf) { + perror(_("allocating file copy buffer")); + goto fail; + } + + req->fdr = calloc(1, sizeof(struct file_dedupe_range) + + sizeof(struct file_dedupe_range_info)); + if (!req->fdr) { + perror(_("allocating dedupe control buffer")); + goto fail; + } + + req->use_reflink = req->xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_REFLINK; + + *reqp = req; + return 0; +fail: + clearspace_free(&req); + return -1; +} + +/* Free all resources associated with a space clearing request. */ +int +clearspace_free( + struct clearspace_req **reqp) +{ + struct clearspace_req *req = *reqp; + int ret = 0; + + if (!req) + return 0; + + *reqp = NULL; + free(req->fdr); + free(req->buf); + free(req->bhead); + free(req->rhead); + free(req->mhead); + + if (req->space_fd >= 0) { + ret = close(req->space_fd); + if (ret) + perror(_("closing space capture file")); + } + + if (req->work_fd >= 0) { + int ret2 = close(req->work_fd); + + if (ret2) { + perror(_("closing work file")); + if (!ret && ret2) + ret = ret2; + } + } + + if (req->fshandle) + free_handle(req->fshandle, req->fshandle_sz); + free(req); + return ret; +} diff --git a/libfrog/clearspace.h b/libfrog/clearspace.h new file mode 100644 index 00000000000..07149eb818c --- /dev/null +++ b/libfrog/clearspace.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#ifndef __LIBFROG_CLEARSPACE_H__ +#define __LIBFROG_CLEARSPACE_H__ + +struct clearspace_req; + +struct clearspace_init { + /* Open file and its pathname */ + struct xfs_fd *xfd; + const char *fname; + + /* Which device do we want? */ + bool is_realtime; + dev_t dev; + + /* Range of device to clear. */ + unsigned long long start; + unsigned long long length; + + unsigned int trace_mask; +}; + +int clearspace_init(struct clearspace_req **reqp, + const struct clearspace_init *init); +int clearspace_free(struct clearspace_req **reqp); + +int clearspace_run(struct clearspace_req *req); + +int clearspace_efficacy(struct clearspace_req *req, + unsigned long long *cleared_bytes); + +/* Debugging levels */ + +#define CSP_TRACE_FREEZE (1U << 0) +#define CSP_TRACE_GRAB (1U << 1) +#define CSP_TRACE_FSMAP (1U << 2) +#define CSP_TRACE_FSREFS (1U << 3) +#define CSP_TRACE_BMAPX (1U << 4) +#define CSP_TRACE_PREP (1U << 5) +#define CSP_TRACE_TARGET (1U << 6) +#define CSP_TRACE_DEDUPE (1U << 7) +#define CSP_TRACE_FALLOC (1U << 8) +#define CSP_TRACE_EXCHANGE (1U << 9) +#define CSP_TRACE_XREBUILD (1U << 10) +#define CSP_TRACE_EFFICACY (1U << 11) +#define CSP_TRACE_SETUP (1U << 12) +#define CSP_TRACE_STATUS (1U << 13) +#define CSP_TRACE_DUMPFILE (1U << 14) +#define CSP_TRACE_BITMAP (1U << 15) + +#define CSP_TRACE_ALL (CSP_TRACE_FREEZE | \ + CSP_TRACE_GRAB | \ + CSP_TRACE_FSMAP | \ + CSP_TRACE_FSREFS | \ + CSP_TRACE_BMAPX | \ + CSP_TRACE_PREP | \ + CSP_TRACE_TARGET | \ + CSP_TRACE_DEDUPE | \ + CSP_TRACE_FALLOC | \ + CSP_TRACE_EXCHANGE | \ + CSP_TRACE_XREBUILD | \ + CSP_TRACE_EFFICACY | \ + CSP_TRACE_SETUP | \ + CSP_TRACE_STATUS | \ + CSP_TRACE_DUMPFILE | \ + CSP_TRACE_BITMAP) + +#endif /* __LIBFROG_CLEARSPACE_H__ */ diff --git a/man/man8/xfs_spaceman.8 b/man/man8/xfs_spaceman.8 index 7d2d1ff94ee..a326b9a6486 100644 --- a/man/man8/xfs_spaceman.8 +++ b/man/man8/xfs_spaceman.8 @@ -25,6 +25,23 @@ then the program exits. .SH COMMANDS .TP +.BI "clearfree [ \-n nr ] [ \-r ] [ \-v mask ] " start " " length +Try to clear the specified physical range in the filesystem. +The +.B start +and +.B length +arguments must be given in units of bytes. +If the +.B -n +option is given, run the clearing algorithm this many times. +If the +.B -r +option is given, clear the realtime device. +If the +.B -v +option is given, print what's happening every step of the way. +.TP .BI "freesp [ \-dgrs ] [-a agno]... [ \-b | \-e bsize | \-h bsize | \-m factor ]" With no arguments, .B freesp diff --git a/spaceman/Makefile b/spaceman/Makefile index d6fccc361cf..75df4ce86c2 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -19,7 +19,7 @@ LLDLIBS += $(LIBEDITLINE) $(LIBTERMCAP) endif ifeq ($(HAVE_GETFSMAP),yes) -CFILES += freesp.c +CFILES += freesp.c clearfree.c endif default: depend $(LTCOMMAND) diff --git a/spaceman/clearfree.c b/spaceman/clearfree.c new file mode 100644 index 00000000000..a5f984f0159 --- /dev/null +++ b/spaceman/clearfree.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021-2024 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#include "platform_defs.h" +#include "command.h" +#include "init.h" +#include "libfrog/paths.h" +#include "input.h" +#include "libfrog/fsgeom.h" +#include "libfrog/clearspace.h" +#include "handle.h" +#include "space.h" + +static void +clearfree_help(void) +{ + printf(_( +"Evacuate the contents of the given range of physical storage in the filesystem" +"\n" +" -n -- Run the space clearing algorithm this many times.\n" +" -r -- clear space on the realtime device.\n" +" -v -- verbosity level, or \"all\" to print everything.\n" +"\n" +"The start and length arguments are required, and must be specified in units\n" +"of bytes.\n" +"\n")); +} + +static int +clearfree_f( + int argc, + char **argv) +{ + struct clearspace_init attrs = { + .xfd = &file->xfd, + .fname = file->name, + }; + struct clearspace_req *req = NULL; + unsigned long long cleared; + unsigned long arg; + long long lnum; + unsigned int i, nr = 1; + int c, ret; + + while ((c = getopt(argc, argv, "n:rv:")) != EOF) { + switch (c) { + case 'n': + errno = 0; + arg = strtoul(optarg, NULL, 0); + if (errno) { + perror(optarg); + return 1; + } + if (arg > UINT_MAX) + arg = UINT_MAX; + nr = arg; + break; + case 'r': /* rt device */ + attrs.is_realtime = true; + break; + case 'v': /* Verbose output */ + if (!strcmp(optarg, "all")) { + attrs.trace_mask = CSP_TRACE_ALL; + } else { + errno = 0; + attrs.trace_mask = strtoul(optarg, NULL, 0); + if (errno) { + perror(optarg); + return 1; + } + } + break; + default: + exitcode = 1; + clearfree_help(); + return 0; + } + } + + if (attrs.trace_mask) + attrs.trace_mask |= CSP_TRACE_STATUS; + + if (argc != optind + 2) { + clearfree_help(); + goto fail; + } + + if (attrs.is_realtime) { + if (file->xfd.fsgeom.rtblocks == 0) { + fprintf(stderr, _("No realtime volume present.\n")); + goto fail; + } + attrs.dev = file->fs_path.fs_rtdev; + } else { + attrs.dev = file->fs_path.fs_datadev; + } + + lnum = cvtnum(file->xfd.fsgeom.blocksize, file->xfd.fsgeom.sectsize, + argv[optind]); + if (lnum < 0) { + fprintf(stderr, _("Bad clearfree start sector %s.\n"), + argv[optind]); + goto fail; + } + attrs.start = lnum; + + lnum = cvtnum(file->xfd.fsgeom.blocksize, file->xfd.fsgeom.sectsize, + argv[optind + 1]); + if (lnum < 0) { + fprintf(stderr, _("Bad clearfree length %s.\n"), + argv[optind + 1]); + goto fail; + } + attrs.length = lnum; + + ret = clearspace_init(&req, &attrs); + if (ret) + goto fail; + + for (i = 0; i < nr; i++) { + ret = clearspace_run(req); + if (ret) + goto fail; + } + + ret = clearspace_efficacy(req, &cleared); + if (ret) + goto fail; + + printf(_("Cleared 0x%llx bytes (%.1f%%) from 0x%llx to 0x%llx.\n"), + cleared, 100.0 * cleared / attrs.length, attrs.start, + attrs.start + attrs.length); + + ret = clearspace_free(&req); + if (ret) + goto fail; + + fshandle_destroy(); + return 0; +fail: + fshandle_destroy(); + exitcode = 1; + return 1; +} + +static struct cmdinfo clearfree_cmd = { + .name = "clearfree", + .cfunc = clearfree_f, + .argmin = 0, + .argmax = -1, + .flags = CMD_FLAG_ONESHOT, + .args = "[-n runs] [-r] [-v mask] start length", + .help = clearfree_help, +}; + +void +clearfree_init(void) +{ + clearfree_cmd.oneline = _("clear free space in the filesystem"); + + add_command(&clearfree_cmd); +} diff --git a/spaceman/init.c b/spaceman/init.c index cf1ff3cbb0e..bce62dec47f 100644 --- a/spaceman/init.c +++ b/spaceman/init.c @@ -35,6 +35,7 @@ init_commands(void) trim_init(); freesp_init(); health_init(); + clearfree_init(); } static int diff --git a/spaceman/space.h b/spaceman/space.h index 28fa35a3047..509e923375f 100644 --- a/spaceman/space.h +++ b/spaceman/space.h @@ -31,8 +31,10 @@ extern void quit_init(void); extern void trim_init(void); #ifdef HAVE_GETFSMAP extern void freesp_init(void); +extern void clearfree_init(void); #else # define freesp_init() do { } while (0) +# define clearfree_init() do { } while(0) #endif extern void info_init(void); extern void health_init(void); ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 05/10] spaceman: physically move a regular inode 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (3 preceding siblings ...) 2023-12-27 13:41 ` [PATCH 04/10] xfs_spaceman: implement clearing free space Darrick J. Wong @ 2023-12-27 13:41 ` Darrick J. Wong 2023-12-27 13:41 ` [PATCH 06/10] spaceman: find owners of space in an AG Darrick J. Wong ` (4 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:41 UTC (permalink / raw) To: cem, djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <dchinner@redhat.com> To be able to shrink a filesystem, we need to be able to physically move an inode and all it's data and metadata from it's current location to a new AG. Add a command to spaceman to allow an inode to be moved to a new AG. This new command is not intended to be a perfect solution. I am not trying to handle atomic movement of open files - this is intended to be run as a maintenance operation on idle filesystem. If root filesystems are the target, then this should be run via a rescue environment that is not executing directly on the root fs. With those caveats in place, we can do the entire inode move as a set of non-destructive operations finalised by an atomic inode swap without any needing special kernel support. To ensure we move metadata such as BMBT blocks even if we don't need to move data, we clone the data to a new inode that we've allocated in the destination AG. This will result in new bmbt blocks being allocated in the new location even though the data is not copied. Attributes need to be copied one at a time from the original inode. If data needs to be moved, then we use fallocate(UNSHARE) to create a private copy of the range of data that needs to be moved in the new inode. This will be allocated in the destination AG by normal allocation policy. Once the new inode has been finalised, use RENAME_EXCHANGE to swap it into place and unlink the original inode to free up all the resources it still pins. There are many optimisations still possible to speed this up, but the goal here is "functional" rather than "optimal". Performance can be optimised once all the parts for a "empty the tail of the filesystem before shrink" operation are implemented and solidly tested. This functionality has been smoke tested by creating a 32MB data file with 4k extents and several hundred attributes: $ cat test.sh fname=/mnt/scratch/foo xfs_io -f -c "pwrite 0 32m" -c sync $fname for (( i=0; i < 4096 ; i++ )); do xfs_io -c "fpunch $((i * 8))k 4k" $fname done for (( i=0; i < 100 ; i++ )); do setfattr -n user.blah.$i.$i.blah -v blah.$i.$i.blah $fname setfattr -n user.foo.$i.$i.foo -v $i.cantbele.$i.ve.$i.tsnotbutter $fname done for (( i=0; i < 100 ; i++ )); do setfattr -n security.baz.$i.$i.baz -v wotchul$i$iookinat $fname done xfs_io -c stat -c "bmap -vp" -c "bmap -avp" $fname xfs_spaceman -c "move_inode -a 22" /mnt/scratch/foo xfs_io -c stat -c "bmap -vp" -c "bmap -avp" $fname $ and the output looks something like: $ sudo ./test.sh .... fd.path = "/mnt/scratch/foo" fd.flags = non-sync,non-direct,read-write stat.ino = 133 /mnt/scratch/foo: EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL FLAGS 0: [0..7]: hole 8 1: [8..15]: 208..215 0 (208..215) 8 000000 2: [16..23]: hole 8 3: [24..31]: 224..231 0 (224..231) 8 000000 .... 8189: [65512..65519]: 65712..65719 0 (65712..65719) 8 000000 8190: [65520..65527]: hole 8 8191: [65528..65535]: 65728..65735 0 (65728..65735) 8 000000 mnt/scratch/foo: EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL FLAGS 0: [0..7]: 392..399 0 (392..399) 8 000000 1: [8..15]: 408..415 0 (408..415) 8 000000 2: [16..23]: 424..431 0 (424..431) 8 000000 3: [24..31]: 456..463 0 (456..463) 8 000000 move mnt /mnt/scratch, path /mnt/scratch/foo, agno 22 fd.path = "/mnt/scratch/foo" fd.flags = non-sync,non-direct,read-write stat.ino = 47244651475 .... /mnt/scratch/foo: EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL FLAGS 0: [0..7]: hole 8 1: [8..15]: 47244763192..47244763199 22 (123112..123119) 8 000000 2: [16..23]: hole 8 3: [24..31]: 47244763208..47244763215 22 (123128..123135) 8 000000 .... 8189: [65512..65519]: 47244828808..47244828815 22 (188728..188735) 8 000000 8190: [65520..65527]: hole 8 8191: [65528..65535]: 47244828824..47244828831 22 (188744..188751) 8 000000 /mnt/scratch/foo: EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL FLAGS 0: [0..7]: 47244763176..47244763183 22 (123096..123103) 8 000000 $ Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- man/man8/xfs_spaceman.8 | 4 spaceman/Makefile | 4 spaceman/init.c | 1 spaceman/move_inode.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++ spaceman/space.h | 1 5 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 spaceman/move_inode.c diff --git a/man/man8/xfs_spaceman.8 b/man/man8/xfs_spaceman.8 index a326b9a6486..f898a8bbe84 100644 --- a/man/man8/xfs_spaceman.8 +++ b/man/man8/xfs_spaceman.8 @@ -146,6 +146,10 @@ Report on the health of the files at the given path. .TP .BR "help [ " command " ]" Display a brief description of one or all commands. +.TP +.BI "move_inode \-a agno" +Move the currently open file into the specified allocation group. + .TP .BI "prealloc [ \-u id ] [ \-g id ] [ -p id ] [ \-m minlen ] [ \-s ]" Removes speculative preallocation. diff --git a/spaceman/Makefile b/spaceman/Makefile index 75df4ce86c2..41ab95a07b2 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -7,10 +7,10 @@ include $(TOPDIR)/include/builddefs LTCOMMAND = xfs_spaceman HFILES = init.h space.h -CFILES = info.c init.c file.c health.c prealloc.c trim.c +CFILES = info.c init.c file.c health.c move_inode.c prealloc.c trim.c LSRCFILES = xfs_info.sh -LLDLIBS = $(LIBHANDLE) $(LIBXCMD) $(LIBFROG) +LLDLIBS = $(LIBHANDLE) $(LIBXCMD) $(LIBFROG) $(LIBHANDLE) LTDEPENDENCIES = $(LIBHANDLE) $(LIBXCMD) $(LIBFROG) LLDFLAGS = -static diff --git a/spaceman/init.c b/spaceman/init.c index bce62dec47f..dbeebcf97b9 100644 --- a/spaceman/init.c +++ b/spaceman/init.c @@ -36,6 +36,7 @@ init_commands(void) freesp_init(); health_init(); clearfree_init(); + move_inode_init(); } static int diff --git a/spaceman/move_inode.c b/spaceman/move_inode.c new file mode 100644 index 00000000000..6238a48e948 --- /dev/null +++ b/spaceman/move_inode.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Red Hat, Inc. + * All Rights Reserved. + */ + +#include "libxfs.h" +#include "libfrog/fsgeom.h" +#include "command.h" +#include "init.h" +#include "libfrog/paths.h" +#include "space.h" +#include "input.h" +#include "handle.h" + +#include <linux/fiemap.h> +#include <linux/falloc.h> +#include <attr/attributes.h> + +static cmdinfo_t move_inode_cmd; + +/* + * We can't entirely use O_TMPFILE here because we want to use RENAME_EXCHANGE + * to swap the inode once rebuild is complete. Hence the new file has to be + * somewhere in the namespace for rename to act upon. Hence we use a normal + * open(O_CREATE) for now. + * + * This could potentially use O_TMPFILE to rebuild the entire inode, the use + * a linkat()/renameat2() pair to add it to the namespace then atomically + * replace the original. + */ +static int +create_tmpfile( + const char *mnt, + struct xfs_fd *xfd, + xfs_agnumber_t agno, + char **tmpfile, + int *tmpfd) +{ + char name[PATH_MAX + 1]; + mode_t mask; + int fd; + int i; + int ret; + + /* construct tmpdir */ + mask = umask(0); + + snprintf(name, PATH_MAX, "%s/.spaceman", mnt); + ret = mkdir(name, 0700); + if (ret) { + if (errno != EEXIST) { + fprintf(stderr, _("could not create tmpdir: %s: %s\n"), + name, strerror(errno)); + ret = -errno; + goto out_cleanup; + } + } + + /* loop creating directories until we get one in the right AG */ + for (i = 0; i < xfd->fsgeom.agcount; i++) { + struct stat st; + + snprintf(name, PATH_MAX, "%s/.spaceman/dir%d", mnt, i); + ret = mkdir(name, 0700); + if (ret) { + if (errno != EEXIST) { + fprintf(stderr, + _("cannot create tmpdir: %s: %s\n"), + name, strerror(errno)); + ret = -errno; + goto out_cleanup_dir; + } + } + ret = lstat(name, &st); + if (ret) { + fprintf(stderr, _("cannot stat tmpdir: %s: %s\n"), + name, strerror(errno)); + ret = -errno; + rmdir(name); + goto out_cleanup_dir; + } + if (cvt_ino_to_agno(xfd, st.st_ino) == agno) + break; + + /* remove directory in wrong AG */ + rmdir(name); + } + + if (i == xfd->fsgeom.agcount) { + /* + * Nothing landed in the selected AG! Must have been skipped + * because the AG is out of space. + */ + fprintf(stderr, _("Cannot create AG tmpdir.\n")); + ret = -ENOSPC; + goto out_cleanup_dir; + } + + /* create tmpfile */ + snprintf(name, PATH_MAX, "%s/.spaceman/dir%d/tmpfile.%d", mnt, i, getpid()); + fd = open(name, O_CREAT|O_EXCL|O_RDWR, 0700); + if (fd < 0) { + fprintf(stderr, _("cannot create tmpfile: %s: %s\n"), + name, strerror(errno)); + ret = -errno; + } + + /* return name and fd */ + (void)umask(mask); + *tmpfd = fd; + *tmpfile = strdup(name); + + return 0; +out_cleanup_dir: + snprintf(name, PATH_MAX, "%s/.spaceman", mnt); + rmdir(name); +out_cleanup: + (void)umask(mask); + return ret; +} + +static int +get_attr( + void *hdl, + size_t hlen, + char *name, + void *attrbuf, + int *attrlen, + int attr_ns) +{ + struct xfs_attr_multiop ops = { + .am_opcode = ATTR_OP_GET, + .am_attrname = name, + .am_attrvalue = attrbuf, + .am_length = *attrlen, + .am_flags = attr_ns, + }; + int ret; + + ret = attr_multi_by_handle(hdl, hlen, &ops, 1, 0); + if (ret < 0) { + fprintf(stderr, _("attr_multi_by_handle(GET): %s\n"), + strerror(errno)); + return -errno; + } + *attrlen = ops.am_length; + return 0; +} + +static int +set_attr( + void *hdl, + size_t hlen, + char *name, + void *attrbuf, + int attrlen, + int attr_ns) +{ + struct xfs_attr_multiop ops = { + .am_opcode = ATTR_OP_SET, + .am_attrname = name, + .am_attrvalue = attrbuf, + .am_length = attrlen, + .am_flags = ATTR_CREATE | attr_ns, + }; + int ret; + + ret = attr_multi_by_handle(hdl, hlen, &ops, 1, 0); + if (ret < 0) { + fprintf(stderr, _("attr_multi_by_handle(SET): %s\n"), + strerror(errno)); + return -errno; + } + return 0; +} + +/* + * Copy all the attributes from the original source file into the replacement + * destination. + * + * Oh the humanity of deprecated Irix compatible attr interfaces that are more + * functional and useful than their native Linux replacements! + */ +static int +copy_attrs( + int srcfd, + int dstfd, + int attr_ns) +{ + void *shdl; + void *dhdl; + size_t shlen; + size_t dhlen; + attrlist_cursor_t cursor; + attrlist_t *alist; + struct attrlist_ent *ent; + char alistbuf[XATTR_LIST_MAX]; + char attrbuf[XATTR_SIZE_MAX]; + int attrlen; + int error; + int i; + + memset(&cursor, 0, sizeof(cursor)); + + /* + * All this handle based stuff is hoop jumping to avoid: + * + * a) deprecated API warnings because attr_list, attr_get and attr_set + * have been deprecated hence through compiler warnings; and + * + * b) listxattr() failing hard if there are more than 64kB worth of attr + * names on the inode so is unusable. + * + * That leaves libhandle as the only usable interface for iterating all + * xattrs on an inode reliably. Lucky for us, libhandle is part of + * xfsprogs, so this hoop jump isn't going to get ripped out from under + * us any time soon. + */ + error = fd_to_handle(srcfd, (void **)&shdl, &shlen); + if (error) { + fprintf(stderr, _("fd_to_handle(shdl): %s\n"), + strerror(errno)); + return -errno; + } + error = fd_to_handle(dstfd, (void **)&dhdl, &dhlen); + if (error) { + fprintf(stderr, _("fd_to_handle(dhdl): %s\n"), + strerror(errno)); + goto out_free_shdl; + } + + /* loop to iterate all xattrs */ + error = attr_list_by_handle(shdl, shlen, alistbuf, + XATTR_LIST_MAX, attr_ns, &cursor); + if (error) { + fprintf(stderr, _("attr_list_by_handle(shdl): %s\n"), + strerror(errno)); + } + while (!error) { + alist = (attrlist_t *)alistbuf; + + /* + * We loop one attr at a time for initial implementation + * simplicity. attr_multi_by_handle() can retrieve and set + * multiple attrs in a single call, but that is more complex. + * Get it working first, then optimise. + */ + for (i = 0; i < alist->al_count; i++) { + ent = ATTR_ENTRY(alist, i); + + /* get xattr (val, len) from name */ + attrlen = XATTR_SIZE_MAX; + error = get_attr(shdl, shlen, ent->a_name, attrbuf, + &attrlen, attr_ns); + if (error) + break; + + /* set xattr (val, len) to name */ + error = set_attr(dhdl, dhlen, ent->a_name, attrbuf, + attrlen, ATTR_CREATE | attr_ns); + if (error) + break; + } + + if (!alist->al_more) + break; + error = attr_list_by_handle(shdl, shlen, alistbuf, + XATTR_LIST_MAX, attr_ns, &cursor); + } + + free_handle(dhdl, dhlen); +out_free_shdl: + free_handle(shdl, shlen); + return error ? -errno : 0; +} + +/* + * scan the range of the new file for data that isn't in the destination AG + * and unshare it to create a new copy of it in the current target location + * of the new file. + */ +#define EXTENT_BATCH 32 +static int +unshare_data( + struct xfs_fd *xfd, + int destfd, + xfs_agnumber_t agno) +{ + int ret; + struct fiemap *fiemap; + int done = 0; + int fiemap_flags = FIEMAP_FLAG_SYNC; + int i; + int map_size; + __u64 last_logical = 0; /* last extent offset handled */ + off64_t range_end = -1LL; /* mapping end*/ + + /* fiemap loop over extents */ + map_size = sizeof(struct fiemap) + + (EXTENT_BATCH * sizeof(struct fiemap_extent)); + fiemap = malloc(map_size); + if (!fiemap) { + fprintf(stderr, _("%s: malloc of %d bytes failed.\n"), + progname, map_size); + return -ENOMEM; + } + + while (!done) { + memset(fiemap, 0, map_size); + fiemap->fm_flags = fiemap_flags; + fiemap->fm_start = last_logical; + fiemap->fm_length = range_end - last_logical; + fiemap->fm_extent_count = EXTENT_BATCH; + + ret = ioctl(destfd, FS_IOC_FIEMAP, (unsigned long)fiemap); + if (ret < 0) { + fprintf(stderr, "%s: ioctl(FS_IOC_FIEMAP): %s\n", + progname, strerror(errno)); + free(fiemap); + return -errno; + } + + /* No more extents to map, exit */ + if (!fiemap->fm_mapped_extents) + break; + + for (i = 0; i < fiemap->fm_mapped_extents; i++) { + struct fiemap_extent *extent; + xfs_agnumber_t this_agno; + + extent = &fiemap->fm_extents[i]; + this_agno = cvt_daddr_to_agno(xfd, + cvt_btobbt(extent->fe_physical)); + + /* + * If extent not in dst AG, unshare whole extent to + * trigger reallocated of the extent to be local to + * the current inode. + */ + if (this_agno != agno) { + ret = fallocate(destfd, FALLOC_FL_UNSHARE_RANGE, + extent->fe_logical, extent->fe_length); + if (ret) { + fprintf(stderr, + "%s: fallocate(UNSHARE): %s\n", + progname, strerror(errno)); + return -errno; + } + } + + last_logical = extent->fe_logical + extent->fe_length; + + /* Kernel has told us there are no more extents */ + if (extent->fe_flags & FIEMAP_EXTENT_LAST) { + done = 1; + break; + } + } + } + return 0; +} + +/* + * Exchange the inodes at the two paths indicated after first ensuring that the + * owners, permissions and timestamps are set correctly in the tmpfile. + */ +static int +exchange_inodes( + struct xfs_fd *xfd, + int tmpfd, + const char *tmpfile, + const char *path) +{ + struct timespec ts[2]; + struct stat st; + int ret; + + ret = fstat(xfd->fd, &st); + if (ret) + return -errno; + + /* set user ids */ + ret = fchown(tmpfd, st.st_uid, st.st_gid); + if (ret) + return -errno; + + /* set permissions */ + ret = fchmod(tmpfd, st.st_mode); + if (ret) + return -errno; + + /* set timestamps */ + ts[0] = st.st_atim; + ts[1] = st.st_mtim; + ret = futimens(tmpfd, ts); + if (ret) + return -errno; + + /* exchange the two inodes */ + ret = renameat2(AT_FDCWD, tmpfile, AT_FDCWD, path, RENAME_EXCHANGE); + if (ret) + return -errno; + return 0; +} + +static int +move_file_to_ag( + const char *mnt, + const char *path, + struct xfs_fd *xfd, + xfs_agnumber_t agno) +{ + int ret; + int tmpfd = -1; + char *tmpfile = NULL; + + fprintf(stderr, "move mnt %s, path %s, agno %d\n", mnt, path, agno); + + /* create temporary file in agno */ + ret = create_tmpfile(mnt, xfd, agno, &tmpfile, &tmpfd); + if (ret) + return ret; + + /* clone data to tempfile */ + ret = ioctl(tmpfd, FICLONE, xfd->fd); + if (ret) + goto out_cleanup; + + /* copy system attributes to tempfile */ + ret = copy_attrs(xfd->fd, tmpfd, ATTR_ROOT); + if (ret) + goto out_cleanup; + + /* copy user attributes to tempfile */ + ret = copy_attrs(xfd->fd, tmpfd, 0); + if (ret) + goto out_cleanup; + + /* unshare data to move it */ + ret = unshare_data(xfd, tmpfd, agno); + if (ret) + goto out_cleanup; + + /* swap the inodes over */ + ret = exchange_inodes(xfd, tmpfd, tmpfile, path); + +out_cleanup: + if (ret == -1) + ret = -errno; + + close(tmpfd); + if (tmpfile) + unlink(tmpfile); + free(tmpfile); + + return ret; +} + +static int +move_inode_f( + int argc, + char **argv) +{ + void *fshandle; + size_t fshdlen; + xfs_agnumber_t agno = 0; + struct stat st; + int ret; + int c; + + while ((c = getopt(argc, argv, "a:")) != EOF) { + switch (c) { + case 'a': + agno = cvt_u32(optarg, 10); + if (errno) { + fprintf(stderr, _("bad agno value %s\n"), + optarg); + return command_usage(&move_inode_cmd); + } + break; + default: + return command_usage(&move_inode_cmd); + } + } + + if (optind != argc) + return command_usage(&move_inode_cmd); + + if (agno >= file->xfd.fsgeom.agcount) { + fprintf(stderr, +_("Destination AG %d does not exist. Filesystem only has %d AGs\n"), + agno, file->xfd.fsgeom.agcount); + exitcode = 1; + return 0; + } + + /* this is so we can use fd_to_handle() later on */ + ret = path_to_fshandle(file->fs_path.fs_dir, &fshandle, &fshdlen); + if (ret < 0) { + fprintf(stderr, _("Cannot get fshandle for mount %s: %s\n"), + file->fs_path.fs_dir, strerror(errno)); + goto exit_fail; + } + + ret = fstat(file->xfd.fd, &st); + if (ret) { + fprintf(stderr, _("stat(%s) failed: %s\n"), + file->name, strerror(errno)); + goto exit_fail; + } + + if (S_ISREG(st.st_mode)) { + ret = move_file_to_ag(file->fs_path.fs_dir, file->name, + &file->xfd, agno); + } else { + fprintf(stderr, _("Unsupported: %s is not a regular file.\n"), + file->name); + goto exit_fail; + } + + if (ret) { + fprintf(stderr, _("Failed to move inode to AG %d: %s\n"), + agno, strerror(-ret)); + goto exit_fail; + } + fshandle_destroy(); + return 0; + +exit_fail: + fshandle_destroy(); + exitcode = 1; + return 0; +} + +static void +move_inode_help(void) +{ + printf(_( +"\n" +"Physically move an inode into a new allocation group\n" +"\n" +" -a agno -- destination AG agno for the current open file\n" +"\n")); + +} + +void +move_inode_init(void) +{ + move_inode_cmd.name = "move_inode"; + move_inode_cmd.altname = "mvino"; + move_inode_cmd.cfunc = move_inode_f; + move_inode_cmd.argmin = 2; + move_inode_cmd.argmax = 2; + move_inode_cmd.args = "-a agno"; + move_inode_cmd.flags = CMD_FLAG_ONESHOT; + move_inode_cmd.oneline = _("Move an inode into a new AG."); + move_inode_cmd.help = move_inode_help; + + add_command(&move_inode_cmd); +} + diff --git a/spaceman/space.h b/spaceman/space.h index 509e923375f..96c3c356f13 100644 --- a/spaceman/space.h +++ b/spaceman/space.h @@ -38,5 +38,6 @@ extern void clearfree_init(void); #endif extern void info_init(void); extern void health_init(void); +void move_inode_init(void); #endif /* XFS_SPACEMAN_SPACE_H_ */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 06/10] spaceman: find owners of space in an AG 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (4 preceding siblings ...) 2023-12-27 13:41 ` [PATCH 05/10] spaceman: physically move a regular inode Darrick J. Wong @ 2023-12-27 13:41 ` Darrick J. Wong 2023-12-27 13:41 ` [PATCH 07/10] xfs_spaceman: wrap radix tree accesses in find_owner.c Darrick J. Wong ` (3 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:41 UTC (permalink / raw) To: cem, djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <dchinner@redhat.com> Before we can move inodes for a shrink operation, we have to find all the inodes that own space in the AG(s) we want to empty. This implementation uses FS_IOC_GETFSMAP on the assumption that filesystems to be shrunk have reverse mapping enabled as it is the only way to identify inode related metadata that userspace is unable to see or influence (e.g. BMBT blocks) that may be located in the specific AG. We can use GETFSMAP to identify both inodes to be moved (via XFS_FMR_OWN_INODES records) and inodes with just data and/or metadata to be moved. Once we have identified all the inodes to be moved, we have to map them to paths so that we can use renameat2() to exchange the directory entries pointing at the moved inode atomically. We also need to record inodes with hard links and all of the paths to the inode so that hard links can be recreated appropriately. This requires a directory tree walk to discover the paths (until parent pointers are a thing). Hence for filesystems that aren't reverse mapping enabled, we can eventually use this pass to discover inodes with visible data and metadata that need to be moved. As we resolve the paths to the inodes to be moved, output the information to stdout so that it can be acted upon by other utilities. This results in a command that acts similar to find but with a physical location filter rather than an inode metadata filter. Again, this is not meant to be an optimal implementation. It shouldn't suck, but there is plenty of scope for performance optimisation, especially with a multithreaded and/or async directory traversal/parent pointer path resolution process to hide access latencies. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- libfrog/fsgeom.h | 19 ++ libfrog/radix-tree.c | 2 libfrog/radix-tree.h | 2 man/man8/xfs_spaceman.8 | 11 + spaceman/Makefile | 2 spaceman/find_owner.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++ spaceman/init.c | 4 spaceman/space.h | 2 8 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 spaceman/find_owner.c diff --git a/libfrog/fsgeom.h b/libfrog/fsgeom.h index e5d43695901..073a03e8681 100644 --- a/libfrog/fsgeom.h +++ b/libfrog/fsgeom.h @@ -103,6 +103,25 @@ cvt_ino_to_agino( return ino & ((1ULL << xfd->aginolog) - 1); } +/* Convert an AG block to an AG inode number. */ +static inline uint32_t +cvt_agbno_to_agino( + const struct xfs_fd *xfd, + xfs_agblock_t agbno) +{ + return agbno << xfd->inopblog; +} + +/* Calculate the number of inodes in a byte range */ +static inline uint32_t +cvt_b_to_inode_count( + const struct xfs_fd *xfd, + uint64_t bytes) +{ + return (bytes >> xfd->blocklog) << xfd->inopblog; +} + + /* * Convert a linear fs block offset number into bytes. This is the runtime * equivalent of XFS_FSB_TO_B, which means that it is /not/ for segmented fsbno diff --git a/libfrog/radix-tree.c b/libfrog/radix-tree.c index 261fc2487de..788d11612e2 100644 --- a/libfrog/radix-tree.c +++ b/libfrog/radix-tree.c @@ -377,6 +377,8 @@ void *radix_tree_tag_set(struct radix_tree_root *root, unsigned int height, shift; struct radix_tree_node *slot; + ASSERT(tag < RADIX_TREE_MAX_TAGS); + height = root->height; if (index > radix_tree_maxindex(height)) return NULL; diff --git a/libfrog/radix-tree.h b/libfrog/radix-tree.h index dad5f5b7203..c10826a615b 100644 --- a/libfrog/radix-tree.h +++ b/libfrog/radix-tree.h @@ -28,7 +28,7 @@ do { \ } while (0) #ifdef RADIX_TREE_TAGS -#define RADIX_TREE_MAX_TAGS 2 +#define RADIX_TREE_MAX_TAGS 3 #endif int radix_tree_insert(struct radix_tree_root *, unsigned long, void *); diff --git a/man/man8/xfs_spaceman.8 b/man/man8/xfs_spaceman.8 index f898a8bbe84..6fef6949aa6 100644 --- a/man/man8/xfs_spaceman.8 +++ b/man/man8/xfs_spaceman.8 @@ -41,6 +41,14 @@ option is given, clear the realtime device. If the .B -v option is given, print what's happening every step of the way. +.TP +.BI "find_owner \-a agno" +Create an internal structure to map physical space in the given allocation +group to file paths. +This enables space reorganization on a mounted filesystem by enabling +us to find files. +Unclear why we can't just use FSMAP and BULKSTAT to open by handle. + .TP .BI "freesp [ \-dgrs ] [-a agno]... [ \-b | \-e bsize | \-h bsize | \-m factor ]" With no arguments, @@ -195,6 +203,9 @@ Wait for removal to complete. .B print Display a list of all open files. .TP +.B resolve_owner +Resolves space in the filesystem to file paths, maybe? +.TP .B quit Exit .BR xfs_spaceman . diff --git a/spaceman/Makefile b/spaceman/Makefile index 41ab95a07b2..19ce8862131 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/include/builddefs LTCOMMAND = xfs_spaceman HFILES = init.h space.h -CFILES = info.c init.c file.c health.c move_inode.c prealloc.c trim.c +CFILES = find_owner.c info.c init.c file.c health.c move_inode.c prealloc.c trim.c LSRCFILES = xfs_info.sh LLDLIBS = $(LIBHANDLE) $(LIBXCMD) $(LIBFROG) $(LIBHANDLE) diff --git a/spaceman/find_owner.c b/spaceman/find_owner.c new file mode 100644 index 00000000000..7667d9d3660 --- /dev/null +++ b/spaceman/find_owner.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Oracle. + * Copyright (c) 2020 Red Hat, Inc. + * All Rights Reserved. + */ + +#include "libxfs.h" +#include <linux/fiemap.h> +#include "libfrog/fsgeom.h" +#include "libfrog/radix-tree.h" +#include "command.h" +#include "init.h" +#include "libfrog/paths.h" +#include <linux/fsmap.h> +#include "space.h" +#include "input.h" + +static cmdinfo_t find_owner_cmd; +static cmdinfo_t resolve_owner_cmd; + +#define NR_EXTENTS 128 + +static RADIX_TREE(inode_tree, 0); +#define MOVE_INODE 0 +#define MOVE_BLOCKS 1 +#define INODE_PATH 2 +int inode_count; +int inode_paths; + +static void +track_inode_chunks( + struct xfs_fd *xfd, + xfs_agnumber_t agno, + uint64_t physaddr, + uint64_t length) +{ + xfs_agblock_t agbno = cvt_b_to_agbno(xfd, physaddr); + uint64_t first_ino = cvt_agino_to_ino(xfd, agno, + cvt_agbno_to_agino(xfd, agbno)); + uint64_t num_inodes = cvt_b_to_inode_count(xfd, length); + int i; + + printf(_("AG %d\tInode Range to move: 0x%llx - 0x%llx (length 0x%llx)\n"), + agno, + (unsigned long long)first_ino, + (unsigned long long)first_ino + num_inodes - 1, + (unsigned long long)length); + + for (i = 0; i < num_inodes; i++) { + if (!radix_tree_lookup(&inode_tree, first_ino + i)) { + radix_tree_insert(&inode_tree, first_ino + i, + (void *)first_ino + i); + inode_count++; + } + radix_tree_tag_set(&inode_tree, first_ino + i, MOVE_INODE); + } +} + +static void +track_inode( + struct xfs_fd *xfd, + xfs_agnumber_t agno, + uint64_t owner, + uint64_t physaddr, + uint64_t length) +{ + if (radix_tree_tag_get(&inode_tree, owner, MOVE_BLOCKS)) + return; + + printf(_("AG %d\tInode 0x%llx: blocks to move to move: 0x%llx - 0x%llx\n"), + agno, + (unsigned long long)owner, + (unsigned long long)physaddr, + (unsigned long long)physaddr + length - 1); + if (!radix_tree_lookup(&inode_tree, owner)) { + radix_tree_insert(&inode_tree, owner, (void *)owner); + inode_count++; + } + radix_tree_tag_set(&inode_tree, owner, MOVE_BLOCKS); +} + +static void +scan_ag( + xfs_agnumber_t agno) +{ + struct fsmap_head *fsmap; + struct fsmap *extent; + struct fsmap *l, *h; + struct fsmap *p; + struct xfs_fd *xfd = &file->xfd; + int ret; + int i; + + fsmap = malloc(fsmap_sizeof(NR_EXTENTS)); + if (!fsmap) { + fprintf(stderr, _("%s: fsmap malloc failed.\n"), progname); + exitcode = 1; + return; + } + + memset(fsmap, 0, sizeof(*fsmap)); + fsmap->fmh_count = NR_EXTENTS; + l = fsmap->fmh_keys; + h = fsmap->fmh_keys + 1; + l->fmr_physical = cvt_agbno_to_b(xfd, agno, 0); + h->fmr_physical = cvt_agbno_to_b(xfd, agno + 1, 0); + l->fmr_device = h->fmr_device = file->fs_path.fs_datadev; + h->fmr_owner = ULLONG_MAX; + h->fmr_flags = UINT_MAX; + h->fmr_offset = ULLONG_MAX; + + while (true) { + printf("Inode count %d\n", inode_count); + ret = ioctl(xfd->fd, FS_IOC_GETFSMAP, fsmap); + if (ret < 0) { + fprintf(stderr, _("%s: FS_IOC_GETFSMAP [\"%s\"]: %s\n"), + progname, file->name, strerror(errno)); + free(fsmap); + exitcode = 1; + return; + } + + /* No more extents to map, exit */ + if (!fsmap->fmh_entries) + break; + + /* + * Walk the extents, ignore everything except inode chunks + * and inode owned blocks. + */ + for (i = 0, extent = fsmap->fmh_recs; + i < fsmap->fmh_entries; + i++, extent++) { + if (extent->fmr_flags & FMR_OF_SPECIAL_OWNER) { + if (extent->fmr_owner != XFS_FMR_OWN_INODES) + continue; + /* + * This extent contains inodes that need to be + * moved into another AG. Convert the extent to + * a range of inode numbers and track them all. + */ + track_inode_chunks(xfd, agno, + extent->fmr_physical, + extent->fmr_length); + + continue; + } + + /* + * Extent is owned by an inode that may be located + * anywhere in the filesystem, not just this AG. + */ + track_inode(xfd, agno, extent->fmr_owner, + extent->fmr_physical, + extent->fmr_length); + } + + p = &fsmap->fmh_recs[fsmap->fmh_entries - 1]; + if (p->fmr_flags & FMR_OF_LAST) + break; + fsmap_advance(fsmap); + } + + free(fsmap); +} + +/* + * find inodes that own physical space in a given AG. + */ +static int +find_owner_f( + int argc, + char **argv) +{ + xfs_agnumber_t agno = -1; + int c; + + while ((c = getopt(argc, argv, "a:")) != EOF) { + switch (c) { + case 'a': + agno = cvt_u32(optarg, 10); + if (errno) { + fprintf(stderr, _("bad agno value %s\n"), + optarg); + return command_usage(&find_owner_cmd); + } + break; + default: + return command_usage(&find_owner_cmd); + } + } + + if (optind != argc) + return command_usage(&find_owner_cmd); + + if (agno == -1 || agno >= file->xfd.fsgeom.agcount) { + fprintf(stderr, +_("Destination AG %d does not exist. Filesystem only has %d AGs\n"), + agno, file->xfd.fsgeom.agcount); + exitcode = 1; + return 0; + } + + /* + * Check that rmap is enabled so that GETFSMAP is actually useful. + */ + if (!(file->xfd.fsgeom.flags & XFS_FSOP_GEOM_FLAGS_RMAPBT)) { + fprintf(stderr, +_("Filesystem at %s does not have reverse mapping enabled. Aborting.\n"), + file->fs_path.fs_dir); + exitcode = 1; + return 0; + } + + scan_ag(agno); + return 0; +} + +static void +find_owner_help(void) +{ + printf(_( +"\n" +"Find inodes owning physical blocks in a given AG.\n" +"\n" +" -a agno -- Scan the given AG agno.\n" +"\n")); + +} + +void +find_owner_init(void) +{ + find_owner_cmd.name = "find_owner"; + find_owner_cmd.altname = "fown"; + find_owner_cmd.cfunc = find_owner_f; + find_owner_cmd.argmin = 2; + find_owner_cmd.argmax = 2; + find_owner_cmd.args = "-a agno"; + find_owner_cmd.flags = CMD_FLAG_ONESHOT; + find_owner_cmd.oneline = _("Find inodes owning physical blocks in a given AG"); + find_owner_cmd.help = find_owner_help; + + add_command(&find_owner_cmd); +} + +/* + * for each dirent we get returned, look up the inode tree to see if it is an + * inode we need to process. If it is, then replace the entry in the tree with + * a structure containing the current path and mark the entry as resolved. + */ +struct inode_path { + uint64_t ino; + struct list_head path_list; + uint32_t link_count; + char path[1]; +}; + +static int +resolve_owner_cb( + const char *path, + const struct stat *stat, + int status, + struct FTW *data) +{ + struct inode_path *ipath, *slot_ipath; + int pathlen; + void **slot; + + /* + * Lookup the slot rather than the entry so we can replace the contents + * without another lookup later on. + */ + slot = radix_tree_lookup_slot(&inode_tree, stat->st_ino); + if (!slot || *slot == NULL) + return 0; + + /* Could not get stat data? Fail! */ + if (status == FTW_NS) { + fprintf(stderr, +_("Failed to obtain stat(2) information from path %s. Aborting\n"), + path); + return -EPERM; + } + + /* Allocate a new inode path and record the path in it. */ + pathlen = strlen(path); + ipath = calloc(1, sizeof(*ipath) + pathlen + 1); + if (!ipath) { + fprintf(stderr, +_("Aborting: Storing path %s for inode 0x%lx failed: %s\n"), + path, stat->st_ino, strerror(ENOMEM)); + return -ENOMEM; + } + INIT_LIST_HEAD(&ipath->path_list); + memcpy(&ipath->path[0], path, pathlen); + ipath->ino = stat->st_ino; + + /* + * If the slot contains the inode number we just looked up, then we + * haven't recorded a path for it yet. If that is the case, we just + * set the link count of the path to 1 and replace the slot contents + * with our new_ipath. + */ + if (stat->st_ino == (uint64_t)*slot) { + ipath->link_count = 1; + *slot = ipath; + radix_tree_tag_set(&inode_tree, stat->st_ino, INODE_PATH); + inode_paths++; + return 0; + } + + /* + * Multiple hard links to this inode. The slot already contains an + * ipath pointer, so we add the new ipath to the tail of the list held + * by the slot's ipath and bump the link count of the slot's ipath to + * keep track of how many hard links the inode has. + */ + slot_ipath = *slot; + slot_ipath->link_count++; + list_add_tail(&ipath->path_list, &slot_ipath->path_list); + return 0; +} + +/* + * This should be parallelised - pass subdirs off to a work queue, have the + * work queue processes subdirs, queueing more subdirs to work on. + */ +static int +walk_mount( + const char *mntpt) +{ + int ret; + + ret = nftw(mntpt, resolve_owner_cb, + 100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH); + if (ret) + return -errno; + return 0; +} + +static int +list_inode_paths(void) +{ + struct inode_path *ipath; + uint64_t idx = 0; + int ret; + + do { + bool move_blocks; + bool move_inode; + + ret = radix_tree_gang_lookup_tag(&inode_tree, (void **)&ipath, + idx, 1, INODE_PATH); + if (!ret) + break; + idx = ipath->ino + 1; + + /* Grab status tags and remove from tree. */ + move_blocks = radix_tree_tag_get(&inode_tree, ipath->ino, + MOVE_BLOCKS); + move_inode = radix_tree_tag_get(&inode_tree, ipath->ino, + MOVE_INODE); + radix_tree_delete(&inode_tree, ipath->ino); + + /* Print the initial path with inode number and state. */ + printf("0x%.16llx\t%s\t%s\t%8d\t%s\n", + (unsigned long long)ipath->ino, + move_blocks ? "BLOCK" : "---", + move_inode ? "INODE" : "---", + ipath->link_count, ipath->path); + ipath->link_count--; + + /* Walk all the hard link paths and emit them. */ + while (!list_empty(&ipath->path_list)) { + struct inode_path *hpath; + + hpath = list_first_entry(&ipath->path_list, + struct inode_path, path_list); + list_del(&hpath->path_list); + ipath->link_count--; + + printf("\t\t\t\t\t%s\n", hpath->path); + } + if (ipath->link_count) { + printf(_("Link count anomaly: %d paths left over\n"), + ipath->link_count); + } + free(ipath); + } while (true); + + /* + * Any inodes remaining in the tree at this point indicate inodes whose + * paths were not found. This will be unlinked but still open inodes or + * lost inodes due to corruptions. Either way, a shrink will not succeed + * until these inodes are removed from the filesystem. + */ + idx = 0; + do { + uint64_t ino; + + + ret = radix_tree_gang_lookup(&inode_tree, (void **)&ino, idx, 1); + if (!ret) { + if (idx != 0) + ret = -EBUSY; + break; + } + idx = ino + 1; + printf(_("No path found for inode 0x%llx!\n"), + (unsigned long long)ino); + radix_tree_delete(&inode_tree, ino); + } while (true); + + return ret; +} + +/* + * Resolve inode numbers to paths via a directory tree walk. + */ +static int +resolve_owner_f( + int argc, + char **argv) +{ + int ret; + + if (!inode_tree.rnode) { + fprintf(stderr, +_("Inode list has not been populated. No inodes to resolve.\n")); + return 0; + } + + ret = walk_mount(file->fs_path.fs_dir); + if (ret) { + fprintf(stderr, +_("Failed to resolve all paths from mount point %s: %s\n"), + file->fs_path.fs_dir, strerror(-ret)); + exitcode = 1; + return 0; + } + + ret = list_inode_paths(); + if (ret) { + fprintf(stderr, +_("Failed to list all resolved paths from mount point %s: %s\n"), + file->fs_path.fs_dir, strerror(-ret)); + exitcode = 1; + return 0; + } + return 0; +} + +static void +resolve_owner_help(void) +{ + printf(_( +"\n" +"Resolve inodes owning physical blocks in a given AG.\n" +"This requires the find_owner command to be run first to populate the table\n" +"of inodes that need to have their paths resolved.\n" +"\n")); + +} + +void +resolve_owner_init(void) +{ + resolve_owner_cmd.name = "resolve_owner"; + resolve_owner_cmd.altname = "rown"; + resolve_owner_cmd.cfunc = resolve_owner_f; + resolve_owner_cmd.argmin = 0; + resolve_owner_cmd.argmax = 0; + resolve_owner_cmd.args = ""; + resolve_owner_cmd.flags = CMD_FLAG_ONESHOT; + resolve_owner_cmd.oneline = _("Resolve patches to inodes owning physical blocks in a given AG"); + resolve_owner_cmd.help = resolve_owner_help; + + add_command(&resolve_owner_cmd); +} + diff --git a/spaceman/init.c b/spaceman/init.c index dbeebcf97b9..8b0af14e566 100644 --- a/spaceman/init.c +++ b/spaceman/init.c @@ -10,6 +10,7 @@ #include "input.h" #include "init.h" #include "libfrog/paths.h" +#include "libfrog/radix-tree.h" #include "space.h" char *progname; @@ -37,6 +38,8 @@ init_commands(void) health_init(); clearfree_init(); move_inode_init(); + find_owner_init(); + resolve_owner_init(); } static int @@ -71,6 +74,7 @@ init( setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); + radix_tree_init(); fs_table_initialise(0, NULL, 0, NULL); while ((c = getopt(argc, argv, "c:p:V")) != EOF) { diff --git a/spaceman/space.h b/spaceman/space.h index 96c3c356f13..cffb1882153 100644 --- a/spaceman/space.h +++ b/spaceman/space.h @@ -39,5 +39,7 @@ extern void clearfree_init(void); extern void info_init(void); extern void health_init(void); void move_inode_init(void); +void find_owner_init(void); +void resolve_owner_init(void); #endif /* XFS_SPACEMAN_SPACE_H_ */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 07/10] xfs_spaceman: wrap radix tree accesses in find_owner.c 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (5 preceding siblings ...) 2023-12-27 13:41 ` [PATCH 06/10] spaceman: find owners of space in an AG Darrick J. Wong @ 2023-12-27 13:41 ` Darrick J. Wong 2023-12-27 13:42 ` [PATCH 08/10] xfs_spaceman: port relocation structure to 32-bit systems Darrick J. Wong ` (2 subsequent siblings) 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:41 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> Wrap the raw radix tree accesses here so that we can provide an alternate implementation on platforms where radix tree indices cannot store a full 64-bit inode number. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- spaceman/Makefile | 2 - spaceman/find_owner.c | 76 +++++++++------------------------ spaceman/relocation.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ spaceman/relocation.h | 46 ++++++++++++++++++++ 4 files changed, 183 insertions(+), 55 deletions(-) create mode 100644 spaceman/relocation.c create mode 100644 spaceman/relocation.h diff --git a/spaceman/Makefile b/spaceman/Makefile index 19ce8862131..16a13e4bc19 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/include/builddefs LTCOMMAND = xfs_spaceman HFILES = init.h space.h -CFILES = find_owner.c info.c init.c file.c health.c move_inode.c prealloc.c trim.c +CFILES = find_owner.c info.c init.c file.c health.c move_inode.c prealloc.c relocation.c trim.c LSRCFILES = xfs_info.sh LLDLIBS = $(LIBHANDLE) $(LIBXCMD) $(LIBFROG) $(LIBHANDLE) diff --git a/spaceman/find_owner.c b/spaceman/find_owner.c index 7667d9d3660..4e03add75dc 100644 --- a/spaceman/find_owner.c +++ b/spaceman/find_owner.c @@ -15,19 +15,13 @@ #include <linux/fsmap.h> #include "space.h" #include "input.h" +#include "relocation.h" static cmdinfo_t find_owner_cmd; static cmdinfo_t resolve_owner_cmd; #define NR_EXTENTS 128 -static RADIX_TREE(inode_tree, 0); -#define MOVE_INODE 0 -#define MOVE_BLOCKS 1 -#define INODE_PATH 2 -int inode_count; -int inode_paths; - static void track_inode_chunks( struct xfs_fd *xfd, @@ -39,7 +33,7 @@ track_inode_chunks( uint64_t first_ino = cvt_agino_to_ino(xfd, agno, cvt_agbno_to_agino(xfd, agbno)); uint64_t num_inodes = cvt_b_to_inode_count(xfd, length); - int i; + uint64_t i; printf(_("AG %d\tInode Range to move: 0x%llx - 0x%llx (length 0x%llx)\n"), agno, @@ -47,14 +41,8 @@ track_inode_chunks( (unsigned long long)first_ino + num_inodes - 1, (unsigned long long)length); - for (i = 0; i < num_inodes; i++) { - if (!radix_tree_lookup(&inode_tree, first_ino + i)) { - radix_tree_insert(&inode_tree, first_ino + i, - (void *)first_ino + i); - inode_count++; - } - radix_tree_tag_set(&inode_tree, first_ino + i, MOVE_INODE); - } + for (i = 0; i < num_inodes; i++) + set_reloc_iflag(first_ino + i, MOVE_INODE); } static void @@ -65,7 +53,7 @@ track_inode( uint64_t physaddr, uint64_t length) { - if (radix_tree_tag_get(&inode_tree, owner, MOVE_BLOCKS)) + if (test_reloc_iflag(owner, MOVE_BLOCKS)) return; printf(_("AG %d\tInode 0x%llx: blocks to move to move: 0x%llx - 0x%llx\n"), @@ -73,11 +61,8 @@ track_inode( (unsigned long long)owner, (unsigned long long)physaddr, (unsigned long long)physaddr + length - 1); - if (!radix_tree_lookup(&inode_tree, owner)) { - radix_tree_insert(&inode_tree, owner, (void *)owner); - inode_count++; - } - radix_tree_tag_set(&inode_tree, owner, MOVE_BLOCKS); + + set_reloc_iflag(owner, MOVE_BLOCKS); } static void @@ -111,7 +96,7 @@ scan_ag( h->fmr_offset = ULLONG_MAX; while (true) { - printf("Inode count %d\n", inode_count); + printf("Inode count %llu\n", get_reloc_count()); ret = ioctl(xfd->fd, FS_IOC_GETFSMAP, fsmap); if (ret < 0) { fprintf(stderr, _("%s: FS_IOC_GETFSMAP [\"%s\"]: %s\n"), @@ -245,18 +230,6 @@ find_owner_init(void) add_command(&find_owner_cmd); } -/* - * for each dirent we get returned, look up the inode tree to see if it is an - * inode we need to process. If it is, then replace the entry in the tree with - * a structure containing the current path and mark the entry as resolved. - */ -struct inode_path { - uint64_t ino; - struct list_head path_list; - uint32_t link_count; - char path[1]; -}; - static int resolve_owner_cb( const char *path, @@ -266,14 +239,14 @@ resolve_owner_cb( { struct inode_path *ipath, *slot_ipath; int pathlen; - void **slot; + struct inode_path **slot; /* * Lookup the slot rather than the entry so we can replace the contents * without another lookup later on. */ - slot = radix_tree_lookup_slot(&inode_tree, stat->st_ino); - if (!slot || *slot == NULL) + slot = get_reloc_ipath_slot(stat->st_ino); + if (!slot) return 0; /* Could not get stat data? Fail! */ @@ -303,11 +276,10 @@ _("Aborting: Storing path %s for inode 0x%lx failed: %s\n"), * set the link count of the path to 1 and replace the slot contents * with our new_ipath. */ - if (stat->st_ino == (uint64_t)*slot) { + if (*slot == UNLINKED_IPATH) { ipath->link_count = 1; *slot = ipath; - radix_tree_tag_set(&inode_tree, stat->st_ino, INODE_PATH); - inode_paths++; + set_reloc_iflag(stat->st_ino, INODE_PATH); return 0; } @@ -351,18 +323,15 @@ list_inode_paths(void) bool move_blocks; bool move_inode; - ret = radix_tree_gang_lookup_tag(&inode_tree, (void **)&ipath, - idx, 1, INODE_PATH); - if (!ret) + ipath = get_next_reloc_ipath(idx); + if (!ipath) break; idx = ipath->ino + 1; /* Grab status tags and remove from tree. */ - move_blocks = radix_tree_tag_get(&inode_tree, ipath->ino, - MOVE_BLOCKS); - move_inode = radix_tree_tag_get(&inode_tree, ipath->ino, - MOVE_INODE); - radix_tree_delete(&inode_tree, ipath->ino); + move_blocks = test_reloc_iflag(ipath->ino, MOVE_BLOCKS); + move_inode = test_reloc_iflag(ipath->ino, MOVE_INODE); + forget_reloc_ino(ipath->ino); /* Print the initial path with inode number and state. */ printf("0x%.16llx\t%s\t%s\t%8d\t%s\n", @@ -400,9 +369,8 @@ list_inode_paths(void) do { uint64_t ino; - - ret = radix_tree_gang_lookup(&inode_tree, (void **)&ino, idx, 1); - if (!ret) { + ino = get_next_reloc_unlinked(idx); + if (!ino) { if (idx != 0) ret = -EBUSY; break; @@ -410,7 +378,7 @@ list_inode_paths(void) idx = ino + 1; printf(_("No path found for inode 0x%llx!\n"), (unsigned long long)ino); - radix_tree_delete(&inode_tree, ino); + forget_reloc_ino(ino); } while (true); return ret; @@ -426,7 +394,7 @@ resolve_owner_f( { int ret; - if (!inode_tree.rnode) { + if (!is_reloc_populated()) { fprintf(stderr, _("Inode list has not been populated. No inodes to resolve.\n")); return 0; diff --git a/spaceman/relocation.c b/spaceman/relocation.c new file mode 100644 index 00000000000..7c7d9a2b4b2 --- /dev/null +++ b/spaceman/relocation.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Red Hat, Inc. + * All Rights Reserved. + */ + +#include "libxfs.h" +#include "libfrog/fsgeom.h" +#include "libfrog/radix-tree.h" +#include "libfrog/paths.h" +#include "command.h" +#include "init.h" +#include "space.h" +#include "input.h" +#include "relocation.h" +#include "handle.h" + +static unsigned long long inode_count; +static unsigned long long inode_paths; + +unsigned long long +get_reloc_count(void) +{ + return inode_count; +} + +static RADIX_TREE(relocation_data, 0); + +bool +is_reloc_populated(void) +{ + return relocation_data.rnode != NULL; +} + +bool +test_reloc_iflag( + uint64_t ino, + unsigned int flag) +{ + return radix_tree_tag_get(&relocation_data, ino, flag); +} + +void +set_reloc_iflag( + uint64_t ino, + unsigned int flag) +{ + if (!radix_tree_lookup(&relocation_data, ino)) { + radix_tree_insert(&relocation_data, ino, UNLINKED_IPATH); + if (flag != INODE_PATH) + inode_count++; + } + if (flag == INODE_PATH) + inode_paths++; + + radix_tree_tag_set(&relocation_data, ino, flag); +} + +struct inode_path * +get_next_reloc_ipath( + uint64_t ino) +{ + struct inode_path *ipath; + int ret; + + ret = radix_tree_gang_lookup_tag(&relocation_data, (void **)&ipath, + ino, 1, INODE_PATH); + if (!ret) + return NULL; + return ipath; +} + +uint64_t +get_next_reloc_unlinked( + uint64_t ino) +{ + uint64_t next_ino; + int ret; + + ret = radix_tree_gang_lookup(&relocation_data, (void **)&next_ino, ino, + 1); + if (!ret) + return 0; + return next_ino; +} + +/* + * Return a pointer to a pointer where the caller can read or write a pointer + * to an inode path structure. + * + * The pointed-to pointer will be set to UNLINKED_IPATH if there is no ipath + * associated with this inode but the inode has been flagged for relocation. + * + * Returns NULL if the inode is not flagged for relocation. + */ +struct inode_path ** +get_reloc_ipath_slot( + uint64_t ino) +{ + struct inode_path **slot; + + slot = (struct inode_path **)radix_tree_lookup_slot(&relocation_data, + ino); + if (!slot || *slot == NULL) + return NULL; + return slot; +} + +void +forget_reloc_ino( + uint64_t ino) +{ + radix_tree_delete(&relocation_data, ino); +} diff --git a/spaceman/relocation.h b/spaceman/relocation.h new file mode 100644 index 00000000000..f05a871915d --- /dev/null +++ b/spaceman/relocation.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Red Hat, Inc. + * All Rights Reserved. + */ +#ifndef XFS_SPACEMAN_RELOCATION_H_ +#define XFS_SPACEMAN_RELOCATION_H_ + +bool is_reloc_populated(void); +unsigned long long get_reloc_count(void); + +/* + * Tags for the relocation_data tree that indicate what it contains and the + * discovery information that needed to be stored. + */ +#define MOVE_INODE 0 +#define MOVE_BLOCKS 1 +#define INODE_PATH 2 + +bool test_reloc_iflag(uint64_t ino, unsigned int flag); +void set_reloc_iflag(uint64_t ino, unsigned int flag); +struct inode_path *get_next_reloc_ipath(uint64_t ino); +uint64_t get_next_reloc_unlinked(uint64_t ino); +struct inode_path **get_reloc_ipath_slot(uint64_t ino); +void forget_reloc_ino(uint64_t ino); + +/* + * When the entry in the relocation_data tree is tagged with INODE_PATH, the + * entry contains a structure that tracks the discovered paths to the inode. If + * the inode has multiple hard links, then we chain each individual path found + * via the path_list and record the number of paths in the link_count entry. + */ +struct inode_path { + uint64_t ino; + struct list_head path_list; + uint32_t link_count; + char path[1]; +}; + +/* + * Sentinel value for inodes that we have to move but haven't yet found a path + * to. + */ +#define UNLINKED_IPATH ((struct inode_path *)1) + +#endif /* XFS_SPACEMAN_RELOCATION_H_ */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 08/10] xfs_spaceman: port relocation structure to 32-bit systems 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (6 preceding siblings ...) 2023-12-27 13:41 ` [PATCH 07/10] xfs_spaceman: wrap radix tree accesses in find_owner.c Darrick J. Wong @ 2023-12-27 13:42 ` Darrick J. Wong 2023-12-27 13:42 ` [PATCH 09/10] spaceman: relocate the contents of an AG Darrick J. Wong 2023-12-27 13:42 ` [PATCH 10/10] spaceman: move inodes with hardlinks Darrick J. Wong 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:42 UTC (permalink / raw) To: cem, djwong; +Cc: linux-xfs From: Darrick J. Wong <djwong@kernel.org> We can't use the radix tree to store relocation information on 32-bit systems because unsigned longs are not large enough to hold 64-bit inodes. Use an avl64 tree instead. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- configure.ac | 1 include/builddefs.in | 1 m4/package_libcdev.m4 | 20 +++++ spaceman/Makefile | 4 + spaceman/relocation.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+) diff --git a/configure.ac b/configure.ac index 3b36d769eac..db6366dcdab 100644 --- a/configure.ac +++ b/configure.ac @@ -258,6 +258,7 @@ AC_HAVE_MEMFD_CLOEXEC AC_HAVE_MEMFD_NOEXEC_SEAL AC_HAVE_O_TMPFILE AC_HAVE_MKOSTEMP_CLOEXEC +AC_USE_RADIX_TREE_FOR_INUMS AC_CONFIG_FILES([include/builddefs]) AC_OUTPUT diff --git a/include/builddefs.in b/include/builddefs.in index 6668e9bbe8b..30c8f301bca 100644 --- a/include/builddefs.in +++ b/include/builddefs.in @@ -138,6 +138,7 @@ HAVE_MEMFD_CLOEXEC = @have_memfd_cloexec@ HAVE_MEMFD_NOEXEC_SEAL = @have_memfd_noexec_seal@ HAVE_O_TMPFILE = @have_o_tmpfile@ HAVE_MKOSTEMP_CLOEXEC = @have_mkostemp_cloexec@ +USE_RADIX_TREE_FOR_INUMS = @use_radix_tree_for_inums@ GCCFLAGS = -funsigned-char -fno-strict-aliasing -Wall # -Wbitwise -Wno-transparent-union -Wno-old-initializer -Wno-decl diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4 index 2228697a7a3..003379ec2b8 100644 --- a/m4/package_libcdev.m4 +++ b/m4/package_libcdev.m4 @@ -612,3 +612,23 @@ AC_DEFUN([AC_HAVE_MKOSTEMP_CLOEXEC], AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) AC_SUBST(have_mkostemp_cloexec) ]) + +# +# Check if the radix tree index (unsigned long) is large enough to hold a +# 64-bit inode number +# +AC_DEFUN([AC_USE_RADIX_TREE_FOR_INUMS], + [ AC_MSG_CHECKING([if radix tree can store XFS inums]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/param.h> +#include <stdint.h> +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) + ]], [[ + typedef uint64_t xfs_ino_t; + + BUILD_BUG_ON(sizeof(unsigned long) < sizeof(xfs_ino_t)); + return 0; + ]])],[use_radix_tree_for_inums=yes + AC_MSG_RESULT(yes)],[AC_MSG_RESULT(no)]) + AC_SUBST(use_radix_tree_for_inums) + ]) diff --git a/spaceman/Makefile b/spaceman/Makefile index 16a13e4bc19..9c866339ac5 100644 --- a/spaceman/Makefile +++ b/spaceman/Makefile @@ -22,6 +22,10 @@ ifeq ($(HAVE_GETFSMAP),yes) CFILES += freesp.c clearfree.c endif +ifeq ($(USE_RADIX_TREE_FOR_INUMS),yes) +LCFLAGS += -DUSE_RADIX_TREE_FOR_INUMS +endif + default: depend $(LTCOMMAND) include $(BUILDRULES) diff --git a/spaceman/relocation.c b/spaceman/relocation.c index 7c7d9a2b4b2..1c0db6a1dab 100644 --- a/spaceman/relocation.c +++ b/spaceman/relocation.c @@ -6,7 +6,11 @@ #include "libxfs.h" #include "libfrog/fsgeom.h" +#ifdef USE_RADIX_TREE_FOR_INUMS #include "libfrog/radix-tree.h" +#else +#include "libfrog/avl64.h" +#endif /* USE_RADIX_TREE_FOR_INUMS */ #include "libfrog/paths.h" #include "command.h" #include "init.h" @@ -24,6 +28,7 @@ get_reloc_count(void) return inode_count; } +#ifdef USE_RADIX_TREE_FOR_INUMS static RADIX_TREE(relocation_data, 0); bool @@ -112,3 +117,201 @@ forget_reloc_ino( { radix_tree_delete(&relocation_data, ino); } +#else +struct reloc_node { + struct avl64node node; + uint64_t ino; + struct inode_path *ipath; + unsigned int flags; +}; + +static uint64_t +reloc_start( + struct avl64node *node) +{ + struct reloc_node *rln; + + rln = container_of(node, struct reloc_node, node); + return rln->ino; +} + +static uint64_t +reloc_end( + struct avl64node *node) +{ + struct reloc_node *rln; + + rln = container_of(node, struct reloc_node, node); + return rln->ino + 1; +} + +static struct avl64ops reloc_ops = { + reloc_start, + reloc_end, +}; + +static struct avl64tree_desc relocation_data = { + .avl_ops = &reloc_ops, +}; + +bool +is_reloc_populated(void) +{ + return relocation_data.avl_firstino != NULL; +} + +static inline struct reloc_node * +reloc_lookup( + uint64_t ino) +{ + avl64node_t *node; + + node = avl64_find(&relocation_data, ino); + if (!node) + return NULL; + + return container_of(node, struct reloc_node, node); +} + +static inline struct reloc_node * +reloc_insert( + uint64_t ino) +{ + struct reloc_node *rln; + avl64node_t *node; + + rln = malloc(sizeof(struct reloc_node)); + if (!rln) + return NULL; + + rln->node.avl_nextino = NULL; + rln->ino = ino; + rln->ipath = UNLINKED_IPATH; + rln->flags = 0; + + node = avl64_insert(&relocation_data, &rln->node); + if (node == NULL) { + free(rln); + return NULL; + } + + return rln; +} + +bool +test_reloc_iflag( + uint64_t ino, + unsigned int flag) +{ + struct reloc_node *rln; + + rln = reloc_lookup(ino); + if (!rln) + return false; + + return rln->flags & flag; +} + +void +set_reloc_iflag( + uint64_t ino, + unsigned int flag) +{ + struct reloc_node *rln; + + rln = reloc_lookup(ino); + if (!rln) { + rln = reloc_insert(ino); + if (!rln) + abort(); + if (flag != INODE_PATH) + inode_count++; + } + if (flag == INODE_PATH) + inode_paths++; + + rln->flags |= flag; +} + +#define avl_for_each_range_safe(pos, n, l, first, last) \ + for (pos = (first), n = pos->avl_nextino, l = (last)->avl_nextino; \ + pos != (l); \ + pos = n, n = pos ? pos->avl_nextino : NULL) + +struct inode_path * +get_next_reloc_ipath( + uint64_t ino) +{ + struct avl64node *firstn; + struct avl64node *lastn; + struct avl64node *pos; + struct avl64node *n; + struct avl64node *l; + struct reloc_node *rln; + + avl64_findranges(&relocation_data, ino - 1, -1ULL, &firstn, &lastn); + if (firstn == NULL && lastn == NULL) + return NULL; + + avl_for_each_range_safe(pos, n, l, firstn, lastn) { + rln = container_of(pos, struct reloc_node, node); + + if (rln->flags & INODE_PATH) + return rln->ipath; + } + + return NULL; +} + +uint64_t +get_next_reloc_unlinked( + uint64_t ino) +{ + struct avl64node *firstn; + struct avl64node *lastn; + struct avl64node *pos; + struct avl64node *n; + struct avl64node *l; + struct reloc_node *rln; + + avl64_findranges(&relocation_data, ino - 1, -1ULL, &firstn, &lastn); + if (firstn == NULL && lastn == NULL) + return 0; + + avl_for_each_range_safe(pos, n, l, firstn, lastn) { + rln = container_of(pos, struct reloc_node, node); + + if (!(rln->flags & INODE_PATH)) + return rln->ino; + } + + return 0; +} + +struct inode_path ** +get_reloc_ipath_slot( + uint64_t ino) +{ + struct reloc_node *rln; + + rln = reloc_lookup(ino); + if (!rln) + return NULL; + + return &rln->ipath; +} + +void +forget_reloc_ino( + uint64_t ino) +{ + struct reloc_node *rln; + + rln = reloc_lookup(ino); + if (!rln) + return; + + avl64_delete(&relocation_data, &rln->node); + free(rln); +} +#endif /* USE_RADIX_TREE_FOR_INUMS */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 09/10] spaceman: relocate the contents of an AG 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (7 preceding siblings ...) 2023-12-27 13:42 ` [PATCH 08/10] xfs_spaceman: port relocation structure to 32-bit systems Darrick J. Wong @ 2023-12-27 13:42 ` Darrick J. Wong 2023-12-27 13:42 ` [PATCH 10/10] spaceman: move inodes with hardlinks Darrick J. Wong 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:42 UTC (permalink / raw) To: cem, djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <dchinner@redhat.com> Shrinking a filesystem needs to first remove all the active user data and metadata from the AGs that are going to be lopped off the filesystem. Before we can do this, we have to relocate this information to a region of the filesystem that is going to be retained. We have a function to move an inode and all it's related information to a specific AG, we have functions to find the owners of all the information in an AG and we can find their paths. This gives us all the information we need to relocate all the objects in an AG we are going to remove via shrinking. Firstly we scan the AG to be emptied to find the inodes that need to be relocated, then we scan the directory structure to find all the paths to those inodes that need to be moved. Then we iterate over all the inodes to be moved attempting to move them to the lowest numbers AGs. When the destination AG fills up, we'll get ENOSPC from the moving code and this is a trigger to bump the destination AG and retry the move. If we haven't moved all the inodes and their data by the time the destination reaches the source AG, then the entire operation will fail with ENOSPC - there is not enough room in the filesystem to empty the selected AG in preparation for a shrink. This, once again, is not intended as an optimal or even guaranteed way of emptying an AG for shrink. It simply provides the basic algorithm and mechanisms we need to perform a shrink operation. Improvements and optimisations will come in time, but we can't get to an optimal solution without first having basic functionality in place. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- libfrog/fsgeom.h | 10 ++ man/man8/xfs_spaceman.8 | 8 ++ spaceman/find_owner.c | 32 +++--- spaceman/init.c | 1 spaceman/move_inode.c | 7 + spaceman/relocation.c | 234 +++++++++++++++++++++++++++++++++++++++++++++++ spaceman/relocation.h | 5 + spaceman/space.h | 1 8 files changed, 280 insertions(+), 18 deletions(-) diff --git a/libfrog/fsgeom.h b/libfrog/fsgeom.h index 073a03e8681..f23184a0d34 100644 --- a/libfrog/fsgeom.h +++ b/libfrog/fsgeom.h @@ -202,6 +202,16 @@ cvt_daddr_to_agno( return cvt_bb_to_off_fsbt(xfd, daddr) / xfd->fsgeom.agblocks; } +/* Convert sparse filesystem block to AG Number */ +static inline uint32_t +cvt_fsb_to_agno( + struct xfs_fd *xfd, + uint64_t fsbno) +{ + return fsbno >> xfd->agblklog; +} + + /* Convert sector number to AG block number. */ static inline uint32_t cvt_daddr_to_agbno( diff --git a/man/man8/xfs_spaceman.8 b/man/man8/xfs_spaceman.8 index 6fef6949aa6..b6488810cfa 100644 --- a/man/man8/xfs_spaceman.8 +++ b/man/man8/xfs_spaceman.8 @@ -202,9 +202,17 @@ Wait for removal to complete. .TP .B print Display a list of all open files. +.TP +.BI "relocate \-a agno [ \-h agno ]" +Empty out the given allocation group by moving file data elsewhere. +The +.B -h +option specifies the highest allocation group into which we can move data. + .TP .B resolve_owner Resolves space in the filesystem to file paths, maybe? + .TP .B quit Exit diff --git a/spaceman/find_owner.c b/spaceman/find_owner.c index 4e03add75dc..3236290e3fd 100644 --- a/spaceman/find_owner.c +++ b/spaceman/find_owner.c @@ -9,10 +9,10 @@ #include <linux/fiemap.h> #include "libfrog/fsgeom.h" #include "libfrog/radix-tree.h" -#include "command.h" -#include "init.h" #include "libfrog/paths.h" #include <linux/fsmap.h> +#include "command.h" +#include "init.h" #include "space.h" #include "input.h" #include "relocation.h" @@ -65,8 +65,8 @@ track_inode( set_reloc_iflag(owner, MOVE_BLOCKS); } -static void -scan_ag( +int +find_relocation_targets( xfs_agnumber_t agno) { struct fsmap_head *fsmap; @@ -80,8 +80,7 @@ scan_ag( fsmap = malloc(fsmap_sizeof(NR_EXTENTS)); if (!fsmap) { fprintf(stderr, _("%s: fsmap malloc failed.\n"), progname); - exitcode = 1; - return; + return -ENOMEM; } memset(fsmap, 0, sizeof(*fsmap)); @@ -102,8 +101,7 @@ scan_ag( fprintf(stderr, _("%s: FS_IOC_GETFSMAP [\"%s\"]: %s\n"), progname, file->name, strerror(errno)); free(fsmap); - exitcode = 1; - return; + return -errno; } /* No more extents to map, exit */ @@ -148,6 +146,7 @@ scan_ag( } free(fsmap); + return 0; } /* @@ -159,6 +158,7 @@ find_owner_f( char **argv) { xfs_agnumber_t agno = -1; + int ret; int c; while ((c = getopt(argc, argv, "a:")) != EOF) { @@ -198,7 +198,9 @@ _("Filesystem at %s does not have reverse mapping enabled. Aborting.\n"), return 0; } - scan_ag(agno); + ret = find_relocation_targets(agno); + if (ret) + exitcode = 1; return 0; } @@ -299,8 +301,8 @@ _("Aborting: Storing path %s for inode 0x%lx failed: %s\n"), * This should be parallelised - pass subdirs off to a work queue, have the * work queue processes subdirs, queueing more subdirs to work on. */ -static int -walk_mount( +int +resolve_target_paths( const char *mntpt) { int ret; @@ -361,9 +363,9 @@ list_inode_paths(void) /* * Any inodes remaining in the tree at this point indicate inodes whose - * paths were not found. This will be unlinked but still open inodes or - * lost inodes due to corruptions. Either way, a shrink will not succeed - * until these inodes are removed from the filesystem. + * paths were not found. This will be free inodes or unlinked but still + * open inodes. Either way, a shrink will not succeed until these inodes + * are removed from the filesystem. */ idx = 0; do { @@ -400,7 +402,7 @@ _("Inode list has not been populated. No inodes to resolve.\n")); return 0; } - ret = walk_mount(file->fs_path.fs_dir); + ret = resolve_target_paths(file->fs_path.fs_dir); if (ret) { fprintf(stderr, _("Failed to resolve all paths from mount point %s: %s\n"), diff --git a/spaceman/init.c b/spaceman/init.c index 8b0af14e566..cfe1b96fb66 100644 --- a/spaceman/init.c +++ b/spaceman/init.c @@ -40,6 +40,7 @@ init_commands(void) move_inode_init(); find_owner_init(); resolve_owner_init(); + relocate_init(); } static int diff --git a/spaceman/move_inode.c b/spaceman/move_inode.c index 6238a48e948..9190b1d3ab2 100644 --- a/spaceman/move_inode.c +++ b/spaceman/move_inode.c @@ -12,6 +12,7 @@ #include "space.h" #include "input.h" #include "handle.h" +#include "relocation.h" #include <linux/fiemap.h> #include <linux/falloc.h> @@ -404,8 +405,8 @@ exchange_inodes( return 0; } -static int -move_file_to_ag( +int +relocate_file_to_ag( const char *mnt, const char *path, struct xfs_fd *xfd, @@ -511,7 +512,7 @@ _("Destination AG %d does not exist. Filesystem only has %d AGs\n"), } if (S_ISREG(st.st_mode)) { - ret = move_file_to_ag(file->fs_path.fs_dir, file->name, + ret = relocate_file_to_ag(file->fs_path.fs_dir, file->name, &file->xfd, agno); } else { fprintf(stderr, _("Unsupported: %s is not a regular file.\n"), diff --git a/spaceman/relocation.c b/spaceman/relocation.c index 1c0db6a1dab..7b125cc0ae1 100644 --- a/spaceman/relocation.c +++ b/spaceman/relocation.c @@ -315,3 +315,237 @@ forget_reloc_ino( free(rln); } #endif /* USE_RADIX_TREE_FOR_INUMS */ + +static struct cmdinfo relocate_cmd; + +static int +relocate_targets_to_ag( + const char *mnt, + xfs_agnumber_t dst_agno) +{ + struct inode_path *ipath; + uint64_t idx = 0; + int ret = 0; + + do { + struct xfs_fd xfd = {0}; + struct stat st; + + /* lookup first relocation target */ + ipath = get_next_reloc_ipath(idx); + if (!ipath) + break; + + /* XXX: don't handle hard link cases yet */ + if (ipath->link_count > 1) { + fprintf(stderr, + "FIXME! Skipping hardlinked inode at path %s\n", + ipath->path); + goto next; + } + + + ret = stat(ipath->path, &st); + if (ret) { + fprintf(stderr, _("stat(%s) failed: %s\n"), + ipath->path, strerror(errno)); + goto next; + } + + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, + _("FIXME! Skipping %s: not a regular file.\n"), + ipath->path); + goto next; + } + + ret = xfd_open(&xfd, ipath->path, O_RDONLY); + if (ret) { + fprintf(stderr, _("xfd_open(%s) failed: %s\n"), + ipath->path, strerror(-ret)); + goto next; + } + + /* move to destination AG */ + ret = relocate_file_to_ag(mnt, ipath->path, &xfd, dst_agno); + xfd_close(&xfd); + + /* + * If the destination AG has run out of space, we do not remove + * this inode from relocation data so it will be immediately + * retried in the next AG. Other errors will be fatal. + */ + if (ret < 0) + return ret; +next: + /* remove from relocation data */ + idx = ipath->ino + 1; + forget_reloc_ino(ipath->ino); + } while (ret != -ENOSPC); + + return ret; +} + +static int +relocate_targets( + const char *mnt, + xfs_agnumber_t highest_agno) +{ + xfs_agnumber_t dst_agno = 0; + int ret; + + for (dst_agno = 0; dst_agno <= highest_agno; dst_agno++) { + ret = relocate_targets_to_ag(mnt, dst_agno); + if (ret == -ENOSPC) + continue; + break; + } + return ret; +} + +/* + * Relocate all the user objects in an AG to lower numbered AGs. + */ +static int +relocate_f( + int argc, + char **argv) +{ + xfs_agnumber_t target_agno = -1; + xfs_agnumber_t highest_agno = -1; + xfs_agnumber_t log_agno; + void *fshandle; + size_t fshdlen; + int c; + int ret; + + while ((c = getopt(argc, argv, "a:h:")) != EOF) { + switch (c) { + case 'a': + target_agno = cvt_u32(optarg, 10); + if (errno) { + fprintf(stderr, _("bad target agno value %s\n"), + optarg); + return command_usage(&relocate_cmd); + } + break; + case 'h': + highest_agno = cvt_u32(optarg, 10); + if (errno) { + fprintf(stderr, _("bad highest agno value %s\n"), + optarg); + return command_usage(&relocate_cmd); + } + break; + default: + return command_usage(&relocate_cmd); + } + } + + if (optind != argc) + return command_usage(&relocate_cmd); + + if (target_agno == -1) { + fprintf(stderr, _("Target AG must be specified!\n")); + return command_usage(&relocate_cmd); + } + + log_agno = cvt_fsb_to_agno(&file->xfd, file->xfd.fsgeom.logstart); + if (target_agno <= log_agno) { + fprintf(stderr, +_("Target AG %d must be higher than the journal AG (AG %d). Aborting.\n"), + target_agno, log_agno); + goto out_fail; + } + + if (target_agno >= file->xfd.fsgeom.agcount) { + fprintf(stderr, +_("Target AG %d does not exist. Filesystem only has %d AGs\n"), + target_agno, file->xfd.fsgeom.agcount); + goto out_fail; + } + + if (highest_agno == -1) + highest_agno = target_agno - 1; + + if (highest_agno >= target_agno) { + fprintf(stderr, +_("Highest destination AG %d must be less than target AG %d. Aborting.\n"), + highest_agno, target_agno); + goto out_fail; + } + + if (is_reloc_populated()) { + fprintf(stderr, +_("Relocation data populated from previous commands. Aborting.\n")); + goto out_fail; + } + + /* this is so we can use fd_to_handle() later on */ + ret = path_to_fshandle(file->fs_path.fs_dir, &fshandle, &fshdlen); + if (ret < 0) { + fprintf(stderr, _("Cannot get fshandle for mount %s: %s\n"), + file->fs_path.fs_dir, strerror(errno)); + goto out_fail; + } + + ret = find_relocation_targets(target_agno); + if (ret) { + fprintf(stderr, +_("Failure during target discovery. Aborting.\n")); + goto out_fail; + } + + ret = resolve_target_paths(file->fs_path.fs_dir); + if (ret) { + fprintf(stderr, +_("Failed to resolve all paths from mount point %s: %s\n"), + file->fs_path.fs_dir, strerror(-ret)); + goto out_fail; + } + + ret = relocate_targets(file->fs_path.fs_dir, highest_agno); + if (ret) { + fprintf(stderr, +_("Failed to relocate all targets out of AG %d: %s\n"), + target_agno, strerror(-ret)); + goto out_fail; + } + + return 0; +out_fail: + exitcode = 1; + return 0; +} + +static void +relocate_help(void) +{ + printf(_( +"\n" +"Relocate all the user data and metadata in an AG.\n" +"\n" +"This function will discover all the relocatable objects in a single AG and\n" +"move them to a lower AG as preparation for a shrink operation.\n" +"\n" +" -a <agno> Allocation group to empty\n" +" -h <agno> Highest target AG allowed to relocate into\n" +"\n")); + +} + +void +relocate_init(void) +{ + relocate_cmd.name = "relocate"; + relocate_cmd.altname = "relocate"; + relocate_cmd.cfunc = relocate_f; + relocate_cmd.argmin = 2; + relocate_cmd.argmax = 4; + relocate_cmd.args = "-a agno [-h agno]"; + relocate_cmd.flags = CMD_FLAG_ONESHOT; + relocate_cmd.oneline = _("Relocate data in an AG."); + relocate_cmd.help = relocate_help; + + add_command(&relocate_cmd); +} diff --git a/spaceman/relocation.h b/spaceman/relocation.h index f05a871915d..d4c71b7bb7f 100644 --- a/spaceman/relocation.h +++ b/spaceman/relocation.h @@ -43,4 +43,9 @@ struct inode_path { */ #define UNLINKED_IPATH ((struct inode_path *)1) +int find_relocation_targets(xfs_agnumber_t agno); +int relocate_file_to_ag(const char *mnt, const char *path, struct xfs_fd *xfd, + xfs_agnumber_t agno); +int resolve_target_paths(const char *mntpt); + #endif /* XFS_SPACEMAN_RELOCATION_H_ */ diff --git a/spaceman/space.h b/spaceman/space.h index cffb1882153..8c2b3e5464d 100644 --- a/spaceman/space.h +++ b/spaceman/space.h @@ -41,5 +41,6 @@ extern void health_init(void); void move_inode_init(void); void find_owner_init(void); void resolve_owner_init(void); +void relocate_init(void); #endif /* XFS_SPACEMAN_SPACE_H_ */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 10/10] spaceman: move inodes with hardlinks 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong ` (8 preceding siblings ...) 2023-12-27 13:42 ` [PATCH 09/10] spaceman: relocate the contents of an AG Darrick J. Wong @ 2023-12-27 13:42 ` Darrick J. Wong 9 siblings, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 13:42 UTC (permalink / raw) To: cem, djwong; +Cc: Dave Chinner, linux-xfs From: Dave Chinner <dchinner@redhat.com> When a inode to be moved to a different AG has multiple hard links, we need to "move" all the hard links, too. To do this, we need to create temporary hardlinks to the new file, and then use rename exchange to swap all the hardlinks that point to the old inode with new hardlinks that point to the new inode. We already know that an inode has hard links via the path discovery, and we can check it against the link count that is reported for the inode before we start building the link farm. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- spaceman/find_owner.c | 13 +---- spaceman/move_inode.c | 119 +++++++++++++++++++++++++++++++++++++++++++++---- spaceman/relocation.c | 35 ++++++++++---- spaceman/relocation.h | 6 ++ 4 files changed, 140 insertions(+), 33 deletions(-) diff --git a/spaceman/find_owner.c b/spaceman/find_owner.c index 3236290e3fd..2b3dc653271 100644 --- a/spaceman/find_owner.c +++ b/spaceman/find_owner.c @@ -240,7 +240,6 @@ resolve_owner_cb( struct FTW *data) { struct inode_path *ipath, *slot_ipath; - int pathlen; struct inode_path **slot; /* @@ -260,17 +259,9 @@ _("Failed to obtain stat(2) information from path %s. Aborting\n"), } /* Allocate a new inode path and record the path in it. */ - pathlen = strlen(path); - ipath = calloc(1, sizeof(*ipath) + pathlen + 1); - if (!ipath) { - fprintf(stderr, -_("Aborting: Storing path %s for inode 0x%lx failed: %s\n"), - path, stat->st_ino, strerror(ENOMEM)); + ipath = ipath_alloc(path, stat); + if (!ipath) return -ENOMEM; - } - INIT_LIST_HEAD(&ipath->path_list); - memcpy(&ipath->path[0], path, pathlen); - ipath->ino = stat->st_ino; /* * If the slot contains the inode number we just looked up, then we diff --git a/spaceman/move_inode.c b/spaceman/move_inode.c index 9190b1d3ab2..e8e3a64cb39 100644 --- a/spaceman/move_inode.c +++ b/spaceman/move_inode.c @@ -36,12 +36,14 @@ create_tmpfile( struct xfs_fd *xfd, xfs_agnumber_t agno, char **tmpfile, - int *tmpfd) + int *tmpfd, + int link_count) { char name[PATH_MAX + 1]; + char linkname[PATH_MAX + 1]; mode_t mask; int fd; - int i; + int i, j; int ret; /* construct tmpdir */ @@ -105,14 +107,36 @@ create_tmpfile( fprintf(stderr, _("cannot create tmpfile: %s: %s\n"), name, strerror(errno)); ret = -errno; + goto out_cleanup_dir; } + /* Create hard links to temporary file. */ + for (j = link_count; j > 1; i--) { + snprintf(linkname, PATH_MAX, "%s/.spaceman/dir%d/tmpfile.%d.hardlink.%d", mnt, i, getpid(), j); + ret = link(name, linkname); + if (ret < 0) { + fprintf(stderr, _("cannot create hardlink: %s: %s\n"), + linkname, strerror(errno)); + ret = -errno; + goto out_cleanup_links; + } + } + + /* return name and fd */ (void)umask(mask); *tmpfd = fd; *tmpfile = strdup(name); return 0; + +out_cleanup_links: + for (; j <= link_count; j++) { + snprintf(linkname, PATH_MAX, "%s/.spaceman/dir%d/tmpfile.%d.hardlink.%d", mnt, i, getpid(), j); + unlink(linkname); + } + close(fd); + unlink(name); out_cleanup_dir: snprintf(name, PATH_MAX, "%s/.spaceman", mnt); rmdir(name); @@ -405,21 +429,53 @@ exchange_inodes( return 0; } +static int +exchange_hardlinks( + struct inode_path *ipath, + const char *tmpfile) +{ + char linkname[PATH_MAX]; + struct inode_path *linkpath; + int i = 2; + int ret; + + list_for_each_entry(linkpath, &ipath->path_list, path_list) { + if (i++ > ipath->link_count) { + fprintf(stderr, "ipath link count mismatch!\n"); + return 0; + } + + snprintf(linkname, PATH_MAX, "%s.hardlink.%d", tmpfile, i); + ret = renameat2(AT_FDCWD, linkname, + AT_FDCWD, linkpath->path, RENAME_EXCHANGE); + if (ret) { + fprintf(stderr, + "failed to exchange hard link %s with %s: %s\n", + linkname, linkpath->path, strerror(errno)); + return -errno; + } + } + return 0; +} + int relocate_file_to_ag( const char *mnt, - const char *path, + struct inode_path *ipath, struct xfs_fd *xfd, xfs_agnumber_t agno) { int ret; int tmpfd = -1; char *tmpfile = NULL; + int i; - fprintf(stderr, "move mnt %s, path %s, agno %d\n", mnt, path, agno); + fprintf(stderr, "move mnt %s, path %s, agno %d\n", + mnt, ipath->path, agno); /* create temporary file in agno */ - ret = create_tmpfile(mnt, xfd, agno, &tmpfile, &tmpfd); + ret = create_tmpfile(mnt, xfd, agno, &tmpfile, &tmpfd, + ipath->link_count); if (ret) return ret; @@ -444,12 +500,28 @@ relocate_file_to_ag( goto out_cleanup; /* swap the inodes over */ - ret = exchange_inodes(xfd, tmpfd, tmpfile, path); + ret = exchange_inodes(xfd, tmpfd, tmpfile, ipath->path); + if (ret) + goto out_cleanup; + + /* swap the hard links over */ + ret = exchange_hardlinks(ipath, tmpfile); + if (ret) + goto out_cleanup; out_cleanup: if (ret == -1) ret = -errno; + /* remove old hard links */ + for (i = 2; i <= ipath->link_count; i++) { + char linkname[PATH_MAX + 256]; // anti-warning-crap + + snprintf(linkname, PATH_MAX + 256, "%s.hardlink.%d", tmpfile, i); + unlink(linkname); + } + + /* remove tmpfile */ close(tmpfd); if (tmpfile) unlink(tmpfile); @@ -458,11 +530,32 @@ relocate_file_to_ag( return ret; } +static int +build_ipath( + const char *path, + struct stat *st, + struct inode_path **ipathp) +{ + struct inode_path *ipath; + + *ipathp = NULL; + + ipath = ipath_alloc(path, st); + if (!ipath) + return -ENOMEM; + + /* we only move a single path with move_inode */ + ipath->link_count = 1; + *ipathp = ipath; + return 0; +} + static int move_inode_f( int argc, char **argv) { + struct inode_path *ipath = NULL; void *fshandle; size_t fshdlen; xfs_agnumber_t agno = 0; @@ -511,24 +604,30 @@ _("Destination AG %d does not exist. Filesystem only has %d AGs\n"), goto exit_fail; } - if (S_ISREG(st.st_mode)) { - ret = relocate_file_to_ag(file->fs_path.fs_dir, file->name, - &file->xfd, agno); - } else { + if (!S_ISREG(st.st_mode)) { fprintf(stderr, _("Unsupported: %s is not a regular file.\n"), file->name); goto exit_fail; } + ret = build_ipath(file->name, &st, &ipath); + if (ret) + goto exit_fail; + + ret = relocate_file_to_ag(file->fs_path.fs_dir, ipath, + &file->xfd, agno); if (ret) { fprintf(stderr, _("Failed to move inode to AG %d: %s\n"), agno, strerror(-ret)); goto exit_fail; } + free(ipath); fshandle_destroy(); return 0; exit_fail: + if (ipath) + free(ipath); fshandle_destroy(); exitcode = 1; return 0; diff --git a/spaceman/relocation.c b/spaceman/relocation.c index 7b125cc0ae1..b0960272168 100644 --- a/spaceman/relocation.c +++ b/spaceman/relocation.c @@ -318,6 +318,30 @@ forget_reloc_ino( static struct cmdinfo relocate_cmd; +struct inode_path * +ipath_alloc( + const char *path, + const struct stat *stat) +{ + struct inode_path *ipath; + int pathlen = strlen(path); + + /* Allocate a new inode path and record the path in it. */ + ipath = calloc(1, sizeof(*ipath) + pathlen + 1); + if (!ipath) { + fprintf(stderr, +_("Failed to allocate ipath %s for inode 0x%llx failed: %s\n"), + path, (unsigned long long)stat->st_ino, + strerror(-errno)); + return NULL; + } + INIT_LIST_HEAD(&ipath->path_list); + memcpy(&ipath->path[0], path, pathlen); + ipath->ino = stat->st_ino; + + return ipath; +} + static int relocate_targets_to_ag( const char *mnt, @@ -336,15 +360,6 @@ relocate_targets_to_ag( if (!ipath) break; - /* XXX: don't handle hard link cases yet */ - if (ipath->link_count > 1) { - fprintf(stderr, - "FIXME! Skipping hardlinked inode at path %s\n", - ipath->path); - goto next; - } - - ret = stat(ipath->path, &st); if (ret) { fprintf(stderr, _("stat(%s) failed: %s\n"), @@ -367,7 +382,7 @@ relocate_targets_to_ag( } /* move to destination AG */ - ret = relocate_file_to_ag(mnt, ipath->path, &xfd, dst_agno); + ret = relocate_file_to_ag(mnt, ipath, &xfd, dst_agno); xfd_close(&xfd); /* diff --git a/spaceman/relocation.h b/spaceman/relocation.h index d4c71b7bb7f..2c807aa678e 100644 --- a/spaceman/relocation.h +++ b/spaceman/relocation.h @@ -43,9 +43,11 @@ struct inode_path { */ #define UNLINKED_IPATH ((struct inode_path *)1) +struct inode_path *ipath_alloc(const char *path, const struct stat *st); + int find_relocation_targets(xfs_agnumber_t agno); -int relocate_file_to_ag(const char *mnt, const char *path, struct xfs_fd *xfd, - xfs_agnumber_t agno); +int relocate_file_to_ag(const char *mnt, struct inode_path *ipath, + struct xfs_fd *xfd, xfs_agnumber_t agno); int resolve_target_paths(const char *mntpt); #endif /* XFS_SPACEMAN_RELOCATION_H_ */ ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 1/2] fstests: functional test for refcount reporting 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (7 preceding siblings ...) 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong @ 2023-12-31 20:02 ` Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for the getfsrefs ioctl Darrick J. Wong 2023-12-27 14:06 ` [PATCH 2/2] xfs: test output of new FSREFCOUNTS ioctl Darrick J. Wong 2023-12-31 20:02 ` [PATCHSET 2/2] fstests: defragment free space Darrick J. Wong 9 siblings, 2 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 20:02 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests Hi all, Add a short functional test for the new GETFSREFCOUNTS ioctl that allows userspace to query reference count information for a given range of physical blocks. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=report-refcounts xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=report-refcounts fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=report-refcounts --- common/rc | 4 + doc/group-names.txt | 1 tests/xfs/122.out | 2 + tests/xfs/1921 | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1921.out | 4 + 5 files changed, 177 insertions(+), 2 deletions(-) create mode 100755 tests/xfs/1921 create mode 100644 tests/xfs/1921.out ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/2] xfs/122: update for the getfsrefs ioctl 2023-12-31 20:02 ` [PATCHSET 1/2] fstests: functional test for refcount reporting Darrick J. Wong @ 2023-12-27 14:06 ` Darrick J. Wong 2023-12-27 14:06 ` [PATCH 2/2] xfs: test output of new FSREFCOUNTS ioctl Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 14:06 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Add this new ioctl here too. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- tests/xfs/122.out | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/xfs/122.out b/tests/xfs/122.out index 9c2d2fffb4..4d7e6c1eb4 100644 --- a/tests/xfs/122.out +++ b/tests/xfs/122.out @@ -101,6 +101,8 @@ sizeof(struct xfs_fsop_ag_resblks) = 64 sizeof(struct xfs_fsop_geom) = 256 sizeof(struct xfs_fsop_geom_v1) = 112 sizeof(struct xfs_fsop_geom_v4) = 112 +sizeof(struct xfs_getfsrefs) = 64 +sizeof(struct xfs_getfsrefs_head) = 192 sizeof(struct xfs_getparents) = 96 sizeof(struct xfs_getparents_rec) = 24 sizeof(struct xfs_icreate_log) = 28 ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/2] xfs: test output of new FSREFCOUNTS ioctl 2023-12-31 20:02 ` [PATCHSET 1/2] fstests: functional test for refcount reporting Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for the getfsrefs ioctl Darrick J. Wong @ 2023-12-27 14:06 ` Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 14:06 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Make sure the cursors work properly and that refcounts are correct. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- common/rc | 4 + doc/group-names.txt | 1 tests/xfs/1921 | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1921.out | 4 + 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100755 tests/xfs/1921 create mode 100644 tests/xfs/1921.out diff --git a/common/rc b/common/rc index 2d67f7dff1..84e509e49b 100644 --- a/common/rc +++ b/common/rc @@ -2640,8 +2640,8 @@ _require_xfs_io_command() echo $testio | grep -q "Operation not supported" && \ _notrun "O_TMPFILE is not supported" ;; - "fsmap") - testio=`$XFS_IO_PROG -f -c "fsmap" $testfile 2>&1` + "fsmap"|"fsrefcounts") + testio=`$XFS_IO_PROG -f -c "$command" $testfile 2>&1` echo $testio | grep -q "Inappropriate ioctl" && \ _notrun "xfs_io $command support is missing" ;; diff --git a/doc/group-names.txt b/doc/group-names.txt index 4676825faf..9aec4a4ff8 100644 --- a/doc/group-names.txt +++ b/doc/group-names.txt @@ -57,6 +57,7 @@ freeze filesystem freeze tests fsck general fsck tests fsmap FS_IOC_GETFSMAP ioctl fsr XFS free space reorganizer +fsrefcounts FS_IOC_GETFSREFCOUNTS ioctl fuzzers filesystem fuzz tests growfs increasing the size of a filesystem hardlink hardlinks diff --git a/tests/xfs/1921 b/tests/xfs/1921 new file mode 100755 index 0000000000..28c05e16c7 --- /dev/null +++ b/tests/xfs/1921 @@ -0,0 +1,168 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2021-2024 Oracle. All Rights Reserved. +# +# FS QA Test No. 1921 +# +# Populate filesystem, check that fsrefcounts -n10000 matches fsrefcounts -n1, +# then verify that the refcount information is consistent with the fsmap info. +# +. ./common/preamble +_begin_fstest auto clone fsrefcounts fsmap + +# Override the default cleanup function. +_cleanup() +{ + cd / + rm -rf $tmp.* $TEST_DIR/a $TEST_DIR/b +} + +# Import common functions. +. ./common/filter + +# real QA test starts here +_supported_fs xfs +_require_scratch +_require_xfs_io_command "fsmap" +_require_xfs_io_command "fsrefcounts" + +echo "Format and mount" +_scratch_mkfs > $seqres.full 2>&1 +_scratch_mount >> $seqres.full 2>&1 + +cpus=$(( $(src/feature -o) * 4)) + +# Use fsstress to create a directory tree with some variability +FSSTRESS_ARGS=$(_scale_fsstress_args -p 4 -d $SCRATCH_MNT -n 4000 $FSSTRESS_AVOID) +$FSSTRESS_PROG $FSSTRESS_ARGS >> $seqres.full + +echo "Compare fsrefcounts" | tee -a $seqres.full +$XFS_IO_PROG -c 'fsrefcounts -m -n 65536' $SCRATCH_MNT | grep -v 'EXT:' > $TEST_DIR/a +$XFS_IO_PROG -c 'fsrefcounts -m -n 1' $SCRATCH_MNT | grep -v 'EXT:' > $TEST_DIR/b +cat $TEST_DIR/a $TEST_DIR/b >> $seqres.full + +diff -uw $TEST_DIR/a $TEST_DIR/b + +echo "Compare fsrefcounts to fsmap" | tee -a $seqres.full +$XFS_IO_PROG -c 'fsmap -m -n 65536' $SCRATCH_MNT | grep -v 'EXT:' > $TEST_DIR/b +cat $TEST_DIR/b >> $seqres.full + +while IFS=',' read ext major minor pstart pend owners length crap; do + test "$ext" = "EXT" && continue + + awk_args=(-'F' ',' '-v' "major=$major" '-v' "minor=$minor" \ + '-v' "pstart=$pstart" '-v' "pend=$pend" '-v' "owners=$owners") + + if [ "$owners" -eq 1 ]; then + $AWK_PROG "${awk_args[@]}" \ +' +BEGIN { + printf("Q:%s:%s:%s:%s:%s:\n", major, minor, pstart, pend, owners) > "/dev/stderr"; + next_map = -1; +} +{ + if ($2 != major || $3 != minor) { + next; + } + if ($5 <= pstart) { + next; + } + + printf(" A:%s:%s:%s:%s\n", $2, $3, $4, $5) > "/dev/stderr"; + if (next_map < 0) { + if ($4 > pstart) { + exit 1 + } + next_map = $5 + 1; + } else { + if ($4 != next_map) { + exit 1 + } + next_map = $5 + 1; + } + if (next_map >= pend) { + nextfile; + } +} +END { + exit 0; +} +' $TEST_DIR/b 2> $tmp.debug + res=$? + else + $AWK_PROG "${awk_args[@]}" \ +' +function max(a, b) { + return a > b ? a : b; +} +function min(a, b) { + return a < b ? a : b; +} +BEGIN { + printf("Q:%s:%s:%s:%s:%s:\n", major, minor, pstart, pend, owners) > "/dev/stderr"; + refcount_whole = 0; + aborted = 0; +} +{ + if ($2 != major || $3 != minor) { + next; + } + if ($4 >= pend) { + nextfile; + } + if ($5 <= pstart) { + next; + } + if ($6 == "special_0:2") { + /* unknown owner means we cannot distinguish separate owners */ + aborted = 1; + exit 0; + } + + printf(" A:%s:%s:%s:%s -> %d\n", $2, $3, $4, $5, refcount_whole) > "/dev/stderr"; + if ($4 <= pstart && $5 >= pend) { + /* Account for extents that span the whole range */ + refcount_whole++; + } else { + /* Otherwise track refcounts per-block as we find them */ + for (block = max($4, pstart); block <= min($5, pend); block++) { + refcounts[block]++; + } + } +} +END { + if (aborted) { + exit 0; + } + deficit = owners - refcount_whole; + printf(" W:%d:%d\n", owners, refcount_whole, deficit) > "/dev/stderr"; + if (deficit == 0) { + exit 0; + } + + refcount_slivers = 0; + for (block in refcounts) { + printf(" X:%s:%d\n", block, refcounts[block]) > "/dev/stderr"; + if (refcounts[block] == deficit) { + refcount_slivers = deficit; + } else { + exit 1; + } + } + + refcount_whole += refcount_slivers; + exit owners == refcount_whole ? 0 : 1; +} +' $TEST_DIR/b 2> $tmp.debug + res=$? + fi + if [ $res -ne 0 ]; then + echo "$major,$minor,$pstart,$pend,$owners not found in fsmap" + cat $tmp.debug >> $seqres.full + break + fi +done < $TEST_DIR/a + +# success, all done +status=0 +exit diff --git a/tests/xfs/1921.out b/tests/xfs/1921.out new file mode 100644 index 0000000000..f5ea660379 --- /dev/null +++ b/tests/xfs/1921.out @@ -0,0 +1,4 @@ +QA output created by 1921 +Format and mount +Compare fsrefcounts +Compare fsrefcounts to fsmap ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCHSET 2/2] fstests: defragment free space 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong ` (8 preceding siblings ...) 2023-12-31 20:02 ` [PATCHSET 1/2] fstests: functional test for refcount reporting Darrick J. Wong @ 2023-12-31 20:02 ` Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for XFS_IOC_MAP_FREESP Darrick J. Wong 2023-12-27 14:07 ` [PATCH 2/2] xfs: test clearing of free space Darrick J. Wong 9 siblings, 2 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-31 20:02 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests Hi all, These patches contain experimental code to enable userspace to defragment the free space in a filesystem. Two purposes are imagined for this functionality: clearing space at the end of a filesystem before shrinking it, and clearing free space in anticipation of making a large allocation. The first patch adds a new fallocate mode that allows userspace to allocate free space from the filesystem into a file. The goal here is to allow the filesystem shrink process to prevent allocation from a certain part of the filesystem while a free space defragmenter evacuates all the files from the doomed part of the filesystem. The second patch amends the online repair system to allow the sysadmin to forcibly rebuild metadata structures, even if they're not corrupt. Without adding an ioctl to move metadata btree blocks, this is the only way to dislodge metadata. If you're going to start using this code, I strongly recommend pulling from my git trees, which are linked below. This has been running on the djcloud for months with no problems. Enjoy! Comments and questions are, as always, welcome. --D kernel git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=defrag-freespace xfsprogs git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=defrag-freespace fstests git tree: https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=defrag-freespace --- common/rc | 5 +++ tests/xfs/122.out | 1 + tests/xfs/1400 | 57 ++++++++++++++++++++++++++++++++++++ tests/xfs/1400.out | 2 + tests/xfs/1401 | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1401.out | 2 + 6 files changed, 149 insertions(+) create mode 100755 tests/xfs/1400 create mode 100644 tests/xfs/1400.out create mode 100755 tests/xfs/1401 create mode 100644 tests/xfs/1401.out ^ permalink raw reply [flat|nested] 48+ messages in thread
* [PATCH 1/2] xfs/122: update for XFS_IOC_MAP_FREESP 2023-12-31 20:02 ` [PATCHSET 2/2] fstests: defragment free space Darrick J. Wong @ 2023-12-27 14:06 ` Darrick J. Wong 2023-12-27 14:07 ` [PATCH 2/2] xfs: test clearing of free space Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 14:06 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Update this test to include the new free space mapping ioctl. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- tests/xfs/122.out | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/xfs/122.out b/tests/xfs/122.out index 4d7e6c1eb4..3134cf1619 100644 --- a/tests/xfs/122.out +++ b/tests/xfs/122.out @@ -114,6 +114,7 @@ sizeof(struct xfs_legacy_timestamp) = 8 sizeof(struct xfs_log_dinode) = 176 sizeof(struct xfs_log_legacy_timestamp) = 8 sizeof(struct xfs_map_extent) = 32 +sizeof(struct xfs_map_freesp) = 32 sizeof(struct xfs_parent_name_rec) = 16 sizeof(struct xfs_phys_extent) = 16 sizeof(struct xfs_refcount_key) = 4 ^ permalink raw reply related [flat|nested] 48+ messages in thread
* [PATCH 2/2] xfs: test clearing of free space 2023-12-31 20:02 ` [PATCHSET 2/2] fstests: defragment free space Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for XFS_IOC_MAP_FREESP Darrick J. Wong @ 2023-12-27 14:07 ` Darrick J. Wong 1 sibling, 0 replies; 48+ messages in thread From: Darrick J. Wong @ 2023-12-27 14:07 UTC (permalink / raw) To: djwong, zlang; +Cc: guan, linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Simple regression test for the spaceman clearspace command, which tries to free all the used space in some part of the filesystem. Signed-off-by: Darrick J. Wong <djwong@kernel.org> --- common/rc | 5 +++ tests/xfs/1400 | 57 ++++++++++++++++++++++++++++++++++++ tests/xfs/1400.out | 2 + tests/xfs/1401 | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1401.out | 2 + 5 files changed, 148 insertions(+) create mode 100755 tests/xfs/1400 create mode 100644 tests/xfs/1400.out create mode 100755 tests/xfs/1401 create mode 100644 tests/xfs/1401.out diff --git a/common/rc b/common/rc index 84e509e49b..625494531d 100644 --- a/common/rc +++ b/common/rc @@ -2615,6 +2615,11 @@ _require_xfs_io_command() -c "fsync" -c "$command $blocksize $((2 * $blocksize))" \ $testfile 2>&1` ;; + "fmapfree") + local blocksize=$(_get_file_block_size $TEST_DIR) + testio=`$XFS_IO_PROG -F -f -c "$command $blocksize $((2 * $blocksize))" \ + $testfile 2>&1` + ;; "fiemap") # If 'ranged' is passed as argument then we check to see if fiemap supports # ranged query params diff --git a/tests/xfs/1400 b/tests/xfs/1400 new file mode 100755 index 0000000000..88a5e055d8 --- /dev/null +++ b/tests/xfs/1400 @@ -0,0 +1,57 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022-2024 Oracle. All Rights Reserved. +# +# FS QA Test 1400 +# +# Basic functionality testing for FALLOC_FL_MAP_FREE +# +. ./common/preamble +_begin_fstest auto prealloc + +# Import common functions. +. ./common/filter + +# real QA test starts here + +# Modify as appropriate. +_supported_fs generic +_require_scratch +_require_xfs_io_command "fmapfree" + +_scratch_mkfs | _filter_mkfs 2> $tmp.mkfs > /dev/null +_scratch_mount >> $seqres.full +. $tmp.mkfs + +testfile="$SCRATCH_MNT/$seq.txt" +touch $testfile +if $XFS_IO_PROG -c 'stat -v' $testfile | grep -q 'realtime'; then + # realtime + increment=$((dbsize * rtblocks / 100)) + length=$((dbsize * rtblocks)) +else + # data + increment=$((dbsize * dblocks / 100)) + length=$((dbsize * dblocks)) +fi + +free_bytes=$(stat -f -c '%f * %S' $testfile | bc) + +echo "free space: $free_bytes; increment: $increment; length: $length" >> $seqres.full + +# Map all the free space on that device, 10% at a time +for ((start = 0; start < length; start += increment)); do + $XFS_IO_PROG -f -c "fmapfree $start $increment" $testfile +done + +space_used=$(stat -c '%b * %B' $testfile | bc) + +echo "space captured: $space_used" >> $seqres.full +$FILEFRAG_PROG -v $testfile >> $seqres.full + +# Did we get within 10% of the free space? +_within_tolerance "mapfree space used" $space_used $free_bytes 10% -v + +# success, all done +status=0 +exit diff --git a/tests/xfs/1400.out b/tests/xfs/1400.out new file mode 100644 index 0000000000..601404d7a4 --- /dev/null +++ b/tests/xfs/1400.out @@ -0,0 +1,2 @@ +QA output created by 1400 +mapfree space used is in range diff --git a/tests/xfs/1401 b/tests/xfs/1401 new file mode 100755 index 0000000000..eabe7e18a9 --- /dev/null +++ b/tests/xfs/1401 @@ -0,0 +1,82 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022-2024 Oracle. All Rights Reserved. +# +# FS QA Test No. 1401 +# +# Basic functionality testing for the free space defragmenter. +# +. ./common/preamble +_begin_fstest auto defrag shrinkfs + +# Override the default cleanup function. +# _cleanup() +# { +# cd / +# rm -r -f $tmp.* +# } + +# Import common functions. +. ./common/filter + +# real QA test starts here + +_notrun "XXX test is not ready yet; you need to deal with eof blocks" +_notrun "XXX clearfree cannot move unwritten extents; does fiexchange work for this?" +_notrun "XXX csp_buffercopy never returns if we hit eof" + +# Modify as appropriate. +_supported_fs generic +_require_scratch +_require_xfs_spaceman_command "clearfree" + +_scratch_mkfs | _filter_mkfs 2> $tmp.mkfs > /dev/null +cat $tmp.mkfs >> $seqres.full +. $tmp.mkfs +_scratch_mount >> $seqres.full + +cpus=$(( $(src/feature -o) * 4)) + +# Use fsstress to create a directory tree with some variability +FSSTRESS_ARGS=$(_scale_fsstress_args -p 4 -d $SCRATCH_MNT -n 4000 $FSSTRESS_AVOID) +$FSSTRESS_PROG $FSSTRESS_ARGS >> $seqres.full + +$XFS_IO_PROG -c 'stat -v' $SCRATCH_MNT >> $seqres.full + +if $XFS_IO_PROG -c 'stat -v' $SCRATCH_MNT | grep -q 'rt-inherit'; then + # realtime + increment=$((dbsize * rtblocks / agcount)) + length=$((dbsize * rtblocks)) + fsmap_devarg="-r" +else + # data + increment=$((dbsize * agsize)) + length=$((dbsize * dblocks)) + fsmap_devarg="-d" +fi + +echo "start: $start; increment: $increment; length: $length" >> $seqres.full +$DF_PROG $SCRATCH_MNT >> $seqres.full + +TRACE_PROG="strace -s99 -e fallocate,ioctl,openat -o $tmp.strace" + +for ((start = 0; start < length; start += increment)); do + echo "---------------------------" >> $seqres.full + echo "start: $start end: $((start + increment))" >> $seqres.full + echo "---------------------------" >> $seqres.full + + fsmap_args="-vvvv $fsmap_devarg $((start / 512)) $((increment / 512))" + clearfree_args="-v all $start $increment" + + $XFS_IO_PROG -c "fsmap $fsmap_args" $SCRATCH_MNT > $tmp.before + $TRACE_PROG $XFS_SPACEMAN_PROG -c "clearfree $clearfree_args" $SCRATCH_MNT &>> $seqres.full || break + cat $tmp.strace >> $seqres.full + $XFS_IO_PROG -c "fsmap $fsmap_args" $SCRATCH_MNT > $tmp.after + cat $tmp.before >> $seqres.full + cat $tmp.after >> $seqres.full +done + +# success, all done +echo Silence is golden +status=0 +exit diff --git a/tests/xfs/1401.out b/tests/xfs/1401.out new file mode 100644 index 0000000000..504999381e --- /dev/null +++ b/tests/xfs/1401.out @@ -0,0 +1,2 @@ +QA output created by 1401 +Silence is golden ^ permalink raw reply related [flat|nested] 48+ messages in thread
end of thread, other threads:[~2024-01-16 9:27 UTC | newest] Thread overview: 48+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-12-31 18:25 [NYE PATCHRIVER 4/4] xfs: freespace defrag for online shrink Darrick J. Wong 2023-12-31 19:38 ` [PATCHSET 1/5] xfs: improve post-close eofblocks gc behavior Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/3] xfs: only free posteof blocks on first close Darrick J. Wong 2023-12-31 22:00 ` [PATCH 2/3] xfs: don't free EOF blocks on read close Darrick J. Wong 2023-12-31 22:00 ` [PATCH 3/3] xfs: Don't free EOF blocks on close when extent size hints are set Darrick J. Wong 2023-12-31 19:38 ` [PATCHSET RFC 2/5] xfs: noalloc allocation groups Darrick J. Wong 2023-12-31 22:00 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong 2023-12-31 22:01 ` [PATCH 2/5] xfs: whine to dmesg when we encounter errors Darrick J. Wong 2023-12-31 22:01 ` [PATCH 3/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong 2023-12-31 22:01 ` [PATCH 4/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong 2023-12-31 22:02 ` [PATCH 5/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong 2023-12-31 19:39 ` [PATCHSET 3/5] xfs: report refcount information to userspace Darrick J. Wong 2023-12-31 22:02 ` [PATCH 1/1] xfs: export reference count " Darrick J. Wong 2023-12-31 19:39 ` [PATCHSET 4/5] xfs: defragment free space Darrick J. Wong 2023-12-31 22:02 ` [PATCH 1/2] xfs: capture the offset and length in fallocate tracepoints Darrick J. Wong 2023-12-31 22:02 ` [PATCH 2/2] xfs: add an ioctl to map free space into a file Darrick J. Wong 2023-12-31 19:39 ` [PATCHSET v2 5/5] xfs: aligned file data extent mappings Darrick J. Wong 2023-12-31 22:03 ` [PATCH 1/4] xfs: create a new inode flag to require extsize alignment of file data space Darrick J. Wong 2023-12-31 22:03 ` [PATCH 2/4] xfs: make file data allocations observe the 'forcealign' flag Darrick J. Wong 2024-01-16 9:26 ` John Garry 2023-12-31 22:03 ` [PATCH 3/4] xfs: support reflink with force align enabled Darrick J. Wong 2023-12-31 22:03 ` [PATCH 4/4] xfs: enable file data force-align feature Darrick J. Wong 2023-12-31 19:56 ` [PATCHSET RFC 1/3] xfsprogs: noalloc allocation groups Darrick J. Wong 2023-12-27 13:38 ` [PATCH 1/5] xfs: track deferred ops statistics Darrick J. Wong 2023-12-27 13:38 ` [PATCH 2/5] xfs: create a noalloc mode for allocation groups Darrick J. Wong 2023-12-27 13:38 ` [PATCH 3/5] xfs: enable userspace to hide an AG from allocation Darrick J. Wong 2023-12-27 13:39 ` [PATCH 4/5] xfs: apply noalloc mode to inode allocations too Darrick J. Wong 2023-12-27 13:39 ` [PATCH 5/5] xfs_io: enhance the aginfo command to control the noalloc flag Darrick J. Wong 2023-12-31 19:56 ` [PATCHSET 2/3] xfsprogs: report refcount information to userspace Darrick J. Wong 2023-12-27 13:39 ` [PATCH 1/2] xfs: export reference count " Darrick J. Wong 2023-12-27 13:39 ` [PATCH 2/2] xfs_io: dump reference count information Darrick J. Wong 2023-12-31 19:56 ` [PATCHSET 3/3] xfsprogs: defragment free space Darrick J. Wong 2023-12-27 13:40 ` [PATCH 01/10] xfs: add an ioctl to map free space into a file Darrick J. Wong 2023-12-27 13:40 ` [PATCH 02/10] xfs_io: support using XFS_IOC_MAP_FREESP to map free space Darrick J. Wong 2023-12-27 13:40 ` [PATCH 03/10] xfs_db: get and put blocks on the AGFL Darrick J. Wong 2023-12-27 13:41 ` [PATCH 04/10] xfs_spaceman: implement clearing free space Darrick J. Wong 2023-12-27 13:41 ` [PATCH 05/10] spaceman: physically move a regular inode Darrick J. Wong 2023-12-27 13:41 ` [PATCH 06/10] spaceman: find owners of space in an AG Darrick J. Wong 2023-12-27 13:41 ` [PATCH 07/10] xfs_spaceman: wrap radix tree accesses in find_owner.c Darrick J. Wong 2023-12-27 13:42 ` [PATCH 08/10] xfs_spaceman: port relocation structure to 32-bit systems Darrick J. Wong 2023-12-27 13:42 ` [PATCH 09/10] spaceman: relocate the contents of an AG Darrick J. Wong 2023-12-27 13:42 ` [PATCH 10/10] spaceman: move inodes with hardlinks Darrick J. Wong 2023-12-31 20:02 ` [PATCHSET 1/2] fstests: functional test for refcount reporting Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for the getfsrefs ioctl Darrick J. Wong 2023-12-27 14:06 ` [PATCH 2/2] xfs: test output of new FSREFCOUNTS ioctl Darrick J. Wong 2023-12-31 20:02 ` [PATCHSET 2/2] fstests: defragment free space Darrick J. Wong 2023-12-27 14:06 ` [PATCH 1/2] xfs/122: update for XFS_IOC_MAP_FREESP Darrick J. Wong 2023-12-27 14:07 ` [PATCH 2/2] xfs: test clearing of free space Darrick J. Wong
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox