xfs: xfs_swap_extents needs to handle dynamic fork offsets
PATCH 1/1 xfs: xfs_swap_extents needs to handle dynamic fork offsets
When swapping extents, we can corrupt inodes by swapping data forks that are in incompatible formats. This is caused by the two indoes having different fork offsets due to the presence of an attribute fork on an attr2 filesystem. xfs_fsr tries to be smart about setting the fork offset, but the trick it plays only works on attr1 (old fixed format attribute fork) filesystems.
Changing the way xfs_fsr sets up the attribute fork will prevent this situation from ever occurring, so in the kernel code we can get by with a preventative fix - check that the data fork in the defragmented inode is in a format valid for the inode it is being swapped into. This will lead to files that will silently and potentially repeatedly fail defragmentation, so issue a warning to the log when this particular failure occurs to let us know that xfs_fsr needs updating/fixing.
To help identify how to improve xfs_fsr to avoid this issue, add trace points for the inodes being swapped so that we can determine why the swap was rejected and to confirm that the code is making the right decisions and modifications when swapping forks.
A further complication is even when the swap is allowed to proceed when the fork offset is different between the two inodes then value for the maximum number of extents the data fork can hold can be wrong. Make sure these are also set correctly after the swap occurs.
--- fs/xfs/linux-2.6/xfs_trace.h | 53 ++++++++++++++++ fs/xfs/xfs_dfrag.c | 140 +++++++++++++++++++++++++++++++++--------- 2 files changed, 163 insertions(+), 30 deletions(-)
fs/xfs/linux-2.6/xfs_trace.h +53 -0
@@ -1441,6 +1441,59 @@ TRACE_EVENT(xfs_dir2_leafn_moveents, __entry->count) ); +#define XFS_SWAPEXT_INODES \ + { 0, "target" }, \ + { 1, "temp" } + +#define XFS_INODE_FORMAT_STR \ + { 0, "invalid" }, \ + { 1, "local" }, \ + { 2, "extent" }, \ + { 3, "btree" } + +DECLARE_EVENT_CLASS (xfs_swap_extent_class, + TP_PROTO(struct xfs_inode *ip, int which), +TP_ARGS (ip, which), +TP_STRUCT__entry ( + __field(dev_t, dev) + __field(int, which) + __field(xfs_ino_t, ino) + __field(int, format) + __field(int, nex) + __field(int, max_nex) + __field(int, broot_size) +__field (int, fork_off) + ), +TP_fast_assign ( + __entry->dev = VFS_I(ip)->i_sb->s_dev; + __entry->which = which; + __entry->ino = ip->i_ino; + __entry->format = ip->i_d.di_format; + __entry->nex = ip->i_d.di_nextents; + __entry->max_nex = ip->i_df.if_ext_max; + __entry->broot_size = ip->i_df.if_broot_bytes; + __entry->fork_off = XFS_IFORK_BOFF(ip); + ), +TP_printk ("dev %d:%d %s inode 0x%llx, %s format, num_extents %d, " +"Max in-fork extents %d, broot size %d, fork offset %d" , +MAJOR (__entry->dev),MINOR (__entry->dev), +__print_symbolic (__entry->which,XFS_SWAPEXT_INODES ), + __entry->ino, +__print_symbolic (__entry->format,XFS_INODE_FORMAT_STR ), + __entry->nex, + __entry->max_nex, + __entry->broot_size, + __entry->fork_off) +) + +#define DEFINE_SWAPEXT_EVENT (name) \ +DEFINE_EVENT(xfs_swap_extent_class, name, \ + TP_PROTO(struct xfs_inode *ip, int which), \ + TP_ARGS(ip, which)) + +DEFINE_SWAPEXT_EVENT (xfs_swap_extent_before); +DEFINE_SWAPEXT_EVENT (xfs_swap_extent_after); +#endif /* _TRACE_XFS_H */ #undef TRACE_INCLUDE_PATH
fs/xfs/xfs_dfrag.c +110 -30
211 lines not on this page · raw message
Signature · 6 lines
1.6.5 _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
fs/xfs/linux-2.6/xfs_trace.h TRACE_EVENT(xfs_dir2_leafn_moveents, · line 1478
Quoted 2 lines
fs/xfs/linux-2.6/xfs_trace.h +2
-0+ TP_printk("dev %d:%d %s inode 0x%llx, %s format, num_extents %d, " + "Max in-fork extents %d, broot size %d, fork offset %d",
It would be nice to keep the
"dev %d:%d ino 0x%llx"
prefix as a convention so that all trace records are similar at their beginning.
fs/xfs/xfs_dfrag.c xfs_swapext( · line 56
Quoted 8 lines
#undef
TRACE_INCLUDE_PATHfs/xfs/xfs_dfrag.c +1
-1@@ -53,7 +53,7 @@ xfs_swapext( xfs_swapext_t *sxp) {xfs_inode_t *ip, *tip; -struct file *file, *target_file; +struct file *file, *tmp_file;
I think these xfs_swapext belong into a separate patch. While they make the code quite a bit more redable they're purely cleanups and can wait for 2.6.34. And while you're at it you might also want to merge xfs_swap_extents into xfs_swapext - there's no need for that split at all.
fs/xfs/xfs_dfrag.c xfs_swapext( · line 140
Quoted 4 lines
fs/xfs/xfs_dfrag.c +4
-0+static int +xfs_swap_extents_check_format ( +xfs_inode_t *ip,/* target inode */ +xfs_inode_t *tip)/* tmp inode */
What about just using struct xfs_inode * for new code?
fs/xfs/xfs_dfrag.c xfs_swapext( · line 146
Quoted 4 lines
fs/xfs/xfs_dfrag.c +4
-0+/* Should never get a local format */ +if (ip->i_d.di_format ==XFS_DINODE_FMT_LOCAL || + tip->i_d.di_format ==XFS_DINODE_FMT_LOCAL ) +return EINVAL ;
Ok, same check as in the old code. Any reason we drop the XFS_ERROR here?
fs/xfs/xfs_dfrag.c xfs_swapext( · line 153
Quoted 6 lines
fs/xfs/xfs_dfrag.c +6
-0+/* +* if the target inode has less extents that then temporary inode then +* why did userspace call us? +*/ +if (ip->i_d.di_nextents < tip->i_d.di_nextents) +return EINVAL ;
Ok.
fs/xfs/xfs_dfrag.c xfs_swapext( · line 162
Quoted 8 lines
fs/xfs/xfs_dfrag.c +8
-0+/* +* if the target inode is in extent form and the temp inode is in btree +* form then we will end up with the target inode in the wrong format +* as we already know there are less extents in the temp inode. +*/ +if (ip->i_d.di_format ==XFS_DINODE_FMT_EXTENTS && + tip->i_d.di_format ==XFS_DINODE_FMT_BTREE ) +return EINVAL ;
Ok.
fs/xfs/xfs_dfrag.c xfs_swapext( · line 172
Quoted 9 lines
fs/xfs/xfs_dfrag.c +9
-0+/* Check temp in extent form to max in target */ +if (tip->i_d.di_format ==XFS_DINODE_FMT_EXTENTS && +XFS_IFORK_NEXTENTS (tip,XFS_DATA_FORK ) > ip->i_df.if_ext_max) +return EINVAL ; + +/* Check target in extent form to max in temp */ +if (ip->i_d.di_format ==XFS_DINODE_FMT_EXTENTS && +XFS_IFORK_NEXTENTS (ip,XFS_DATA_FORK ) > tip->i_df.if_ext_max) +return EINVAL ;
So we check this either way just to be sure, ok.
fs/xfs/xfs_dfrag.c xfs_swapext( · line 184
Quoted 11 lines
fs/xfs/xfs_dfrag.c +11
-0+/* Check root block of temp in btree form to max in target */ +if (tip->i_d.di_format ==XFS_DINODE_FMT_BTREE && +XFS_IFORK_BOFF (ip) && + tip->i_df.if_broot_bytes >XFS_IFORK_BOFF (ip)) +return EINVAL ; + +/* Check root block of target in btree form to max in temp */ +if (ip->i_d.di_format ==XFS_DINODE_FMT_BTREE && +XFS_IFORK_BOFF (tip) && + ip->i_df.if_broot_bytes >XFS_IFORK_BOFF (tip)) +return EINVAL ;
Same here.
Patch looks good,
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs
Quoted 9 lines · Christoph Hellwig
fs/xfs/linux-2.6/xfs_trace.h TRACE_EVENT(xfs_dir2_leafn_moveents, · line 1478
Quoted 2 lines
fs/xfs/linux-2.6/xfs_trace.h +2
-0+ TP_printk("dev %d:%d %s inode 0x%llx, %s format, num_extents %d, " + "Max in-fork extents %d, broot size %d, fork offset %d",It would be nice to keep the
"dev %d:%d ino 0x%llx"prefix as a convention so that all trace records are similar at their beginning.
Perhaps:
+ TP_printk("dev %d:%d inode 0x%llx (%s), %s format, num_extents %d, "
^
+--- symbolic entry->whichQuoted 6 lines
Quoted 6 lines
#undef
TRACE_INCLUDE_PATHfs/xfs/xfs_dfrag.c +0
-0@@ -53,7 +53,7 @@ xfs_swapext( xfs_swapext_t *sxp) {xfs_inode_t *ip, *tip;
Quoted 8 lines
fs/xfs/xfs_dfrag.c xfs_swapext( · line 56
Quoted 2 lines
fs/xfs/xfs_dfrag.c +1
-1-struct file *file, *target_file; +struct file *file, *tmp_file;I think these
xfs_swapextbelong into a separate patch. While they make the code quite a bit more redable they're purely cleanups and can wait for 2.6.34. And while you're at it you might also want to mergexfs_swap_extentsintoxfs_swapext- there's no need for that split at all.
I agree. The change here is good (it was confusing and wrong before). But Dave can you please re-submit this with only the critical changes so I can get them to Linus in this release cycle? I think the trace addition is probably fine, just get rid of the gratuitous variable name change in xfs_swapext() and put that in a separate patch.
The rest looks all good. Thanks.
-Alex
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs
Quoted 31 lines · Alex Elder
Quoted 9 lines · Christoph Hellwig
fs/xfs/linux-2.6/xfs_trace.h TRACE_EVENT(xfs_dir2_leafn_moveents, · line 1478
Quoted 2 lines
fs/xfs/linux-2.6/xfs_trace.h +2
-0+ TP_printk("dev %d:%d %s inode 0x%llx, %s format, num_extents %d, " + "Max in-fork extents %d, broot size %d, fork offset %d",It would be nice to keep the
"dev %d:%d ino 0x%llx"prefix as a convention so that all trace records are similar at their beginning.
Perhaps:
+ TP_printk("dev %d:%d inode 0x%llx (%s), %s format, num_extents %d, " ^ +--- symbolic entry->whichQuoted 6 lines
Quoted 6 lines
#undef
TRACE_INCLUDE_PATHfs/xfs/xfs_dfrag.c +0
-0@@ -53,7 +53,7 @@ xfs_swapext( xfs_swapext_t *sxp) {xfs_inode_t *ip, *tip;Quoted 8 lines
fs/xfs/xfs_dfrag.c xfs_swapext( · line 56
Quoted 2 lines
fs/xfs/xfs_dfrag.c +1
-1-struct file *file, *target_file; +struct file *file, *tmp_file;I think these
xfs_swapextbelong into a separate patch. While they make the code quite a bit more redable they're purely cleanups and can wait for 2.6.34. And while you're at it you might also want to mergexfs_swap_extentsintoxfs_swapext- there's no need for that split at all.
Quoted 1 line
I agree. The change here is good (it was confusing and wrong before).
I think this is all a bit crazy - I had to make this change before I could understand the code clearly enough to fix the bug. I had to go all the way back to xfs_fsr to work out what it was providing the kernel to determine WTF the kernel code was doing.
Clarifying the temp/target inodes is especially important because the bug fix results in enforcing the target/temp inode ordering by checking the temp inode has less extents than the target otherwise it will reject the swap. If you can't tell which inode is which, how can you tell the code is correct?
Quoted 4 lines
But Dave can you please re-submit this with only the critical changes so I can get them to Linus in this release cycle? I think the trace addition is probably fine, just get rid of the gratuitous variable name change in
xfs_swapext()and put that in a separate patch.
The tracing is there so if someone reports a problem with the new code we can easily determine if the correct action was taken by swap extents or whether there's some condition I haven't handled.
I'll split it out into three patches - the bug fix, the rename and the tracing code so you can pick and chose which ones you want to take first...
Cheers,
Dave.
Signature · 7 lines
Dave Chinner [email protected] _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
Quoted 8 lines · Alex Elder
Quoted 7 lines · Christoph Hellwig
fs/xfs/linux-2.6/xfs_trace.h TRACE_EVENT(xfs_dir2_leafn_moveents, · line 1478
Quoted 2 lines
fs/xfs/linux-2.6/xfs_trace.h +2
-0+ TP_printk("dev %d:%d %s inode 0x%llx, %s format, num_extents %d, " + "Max in-fork extents %d, broot size %d, fork offset %d",It would be nice to keep the
"dev %d:%d ino 0x%llx"
Quoted 7 lines
Quoted 2 lines
prefix as a convention so that all trace records are similar at their beginning.
Perhaps:
+ TP_printk("dev %d:%d inode 0x%llx (%s), %s format, num_extents %d, " ^ +--- symbolic entry->which
Sounds fine, except s/inode/ino/ above to stay consistant.
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs