diff options
author | Darrick J. Wong <djwong@kernel.org> | 2021-12-04 18:04:11 -0800 |
---|---|---|
committer | Darrick J. Wong <djwong@kernel.org> | 2021-12-15 17:29:32 -0800 |
commit | 851df07d9599471df909c4ff0e3cf33f4b9619f0 (patch) | |
tree | 390175cf6ee12df70340e9e1d23742882a01bce3 | |
parent | e659750c94e5a1c7b9398396e47ee00749d8973e (diff) |
xfs: convert symlink repair to use swapextrepair-symlink-swapext_2021-12-15
Convert the symlink repair code to use extent swapping. This means we
can eliminate the problem of symlinks with crosslinked blocks.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r-- | fs/xfs/libxfs/xfs_bmap.c | 2 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_bmap.h | 4 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_swapext.c | 48 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_symlink_remote.c | 46 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_symlink_remote.h | 1 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/symlink.c | 12 | ||||
-rw-r--r-- | fs/xfs/scrub/symlink_repair.c | 337 | ||||
-rw-r--r-- | fs/xfs/scrub/tempfile.c | 12 | ||||
-rw-r--r-- | fs/xfs/scrub/trace.h | 46 | ||||
-rw-r--r-- | fs/xfs/xfs_symlink.c | 51 |
11 files changed, 455 insertions, 106 deletions
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index 0ce5fee14c22..c0506e1153a6 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -763,7 +763,7 @@ xfs_bmap_local_to_extents_empty( } -STATIC int /* error */ +int /* error */ xfs_bmap_local_to_extents( xfs_trans_t *tp, /* transaction pointer */ xfs_inode_t *ip, /* incore inode pointer */ diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index 74458c71a6f7..2c5c33fc4a9a 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -174,6 +174,10 @@ unsigned int xfs_bmap_compute_attr_offset(struct xfs_mount *mp); int xfs_bmap_add_attrfork(struct xfs_inode *ip, int size, int rsvd); void xfs_bmap_local_to_extents_empty(struct xfs_trans *tp, struct xfs_inode *ip, int whichfork); +int xfs_bmap_local_to_extents(struct xfs_trans *tp, struct xfs_inode *ip, + xfs_extlen_t total, int *logflagsp, int whichfork, + void (*init_fn)(struct xfs_trans *tp, struct xfs_buf *bp, + struct xfs_inode *ip, struct xfs_ifork *ifp)); void xfs_bmap_compute_maxlevels(struct xfs_mount *mp, int whichfork); int xfs_bmap_first_unused(struct xfs_trans *tp, struct xfs_inode *ip, xfs_extlen_t len, xfs_fileoff_t *unused, int whichfork); diff --git a/fs/xfs/libxfs/xfs_swapext.c b/fs/xfs/libxfs/xfs_swapext.c index 7a1489e02d40..3dcf8f4ac817 100644 --- a/fs/xfs/libxfs/xfs_swapext.c +++ b/fs/xfs/libxfs/xfs_swapext.c @@ -28,6 +28,7 @@ #include "xfs_attr.h" #include "xfs_dir2_priv.h" #include "xfs_dir2.h" +#include "xfs_symlink_remote.h" struct kmem_cache *xfs_swapext_intent_cache; @@ -606,6 +607,48 @@ xfs_swapext_dir_to_sf( return xfs_dir2_block_to_sf(&args, bp, size, &sfh); } +/* Convert inode2's remote symlink target back to shortform, if possible. */ +STATIC int +xfs_swapext_link_to_sf( + struct xfs_trans *tp, + struct xfs_swapext_intent *sxi) +{ + struct xfs_inode *ip = sxi->sxi_ip2; + struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK); + char *buf; + int error; + + if (ifp->if_format == XFS_DINODE_FMT_LOCAL || + ip->i_disk_size > XFS_IFORK_DSIZE(ip)) + return 0; + + /* Read the current symlink target into a buffer. */ + buf = kmem_alloc(ip->i_disk_size + 1, KM_NOFS); + if (!buf) { + ASSERT(0); + return -ENOMEM; + } + + error = xfs_symlink_remote_read(ip, buf); + if (error) + goto free; + + /* Remove the blocks. */ + error = xfs_symlink_remote_truncate(tp, ip); + if (error) + goto free; + + /* Convert fork to local format and log our changes. */ + xfs_idestroy_fork(ifp); + ifp->if_bytes = 0; + ifp->if_format = XFS_DINODE_FMT_LOCAL; + xfs_init_local_fork(ip, XFS_DATA_FORK, buf, ip->i_disk_size); + xfs_trans_log_inode(tp, ip, XFS_ILOG_DDATA | XFS_ILOG_CORE); +free: + kmem_free(buf); + return error; +} + /* Finish whatever work might come after a swap operation. */ static int xfs_swapext_postop_work( @@ -619,6 +662,8 @@ xfs_swapext_postop_work( error = xfs_swapext_attr_to_sf(tp, sxi); else if (S_ISDIR(VFS_I(sxi->sxi_ip2)->i_mode)) error = xfs_swapext_dir_to_sf(tp, sxi); + else if (S_ISLNK(VFS_I(sxi->sxi_ip2)->i_mode)) + error = xfs_swapext_link_to_sf(tp, sxi); sxi->sxi_flags &= ~XFS_SWAP_EXT_FILE2_CVT_SF; if (error) return error; @@ -1107,7 +1152,8 @@ xfs_swapext( if (req->req_flags & XFS_SWAP_REQ_FILE2_CVT_SF) ASSERT(req->whichfork == XFS_ATTR_FORK || (req->whichfork == XFS_DATA_FORK && - S_ISDIR(VFS_I(req->ip2)->i_mode))); + (S_ISDIR(VFS_I(req->ip2)->i_mode) || + S_ISLNK(VFS_I(req->ip2)->i_mode)))); if (req->blockcount == 0) return 0; diff --git a/fs/xfs/libxfs/xfs_symlink_remote.c b/fs/xfs/libxfs/xfs_symlink_remote.c index 1fb71a52d37b..c357454242b5 100644 --- a/fs/xfs/libxfs/xfs_symlink_remote.c +++ b/fs/xfs/libxfs/xfs_symlink_remote.c @@ -381,3 +381,49 @@ xfs_symlink_write_target( ASSERT(pathlen == 0); return 0; } + +/* Remove all the blocks from a symlink and invalidate buffers. */ +int +xfs_symlink_remote_truncate( + struct xfs_trans *tp, + struct xfs_inode *ip) +{ + struct xfs_bmbt_irec mval[XFS_SYMLINK_MAPS]; + struct xfs_mount *mp = tp->t_mountp; + struct xfs_buf *bp; + int nmaps = XFS_SYMLINK_MAPS; + int done = 0; + int i; + int error; + + /* Read mappings and invalidate buffers. */ + error = xfs_bmapi_read(ip, 0, XFS_MAX_FILEOFF, mval, &nmaps, 0); + if (error) + return error; + + for (i = 0; i < nmaps; i++) { + if (!xfs_bmap_is_real_extent(&mval[i])) + break; + + error = xfs_trans_get_buf(tp, mp->m_ddev_targp, + XFS_FSB_TO_DADDR(mp, mval[i].br_startblock), + XFS_FSB_TO_BB(mp, mval[i].br_blockcount), 0, + &bp); + if (error) + return error; + + xfs_trans_binval(tp, bp); + } + + /* Unmap the remote blocks. */ + error = xfs_bunmapi(tp, ip, 0, XFS_MAX_FILEOFF, 0, nmaps, &done); + if (error) + return error; + if (!done) { + ASSERT(done); + return -EFSCORRUPTED; + } + + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + return 0; +} diff --git a/fs/xfs/libxfs/xfs_symlink_remote.h b/fs/xfs/libxfs/xfs_symlink_remote.h index d718c5dec0b7..ce578f9e1ab2 100644 --- a/fs/xfs/libxfs/xfs_symlink_remote.h +++ b/fs/xfs/libxfs/xfs_symlink_remote.h @@ -22,5 +22,6 @@ int xfs_symlink_remote_read(struct xfs_inode *ip, char *link); int xfs_symlink_write_target(struct xfs_trans *tp, struct xfs_inode *ip, const char *target_path, int pathlen, xfs_fsblock_t fs_blocks, uint resblks); +int xfs_symlink_remote_truncate(struct xfs_trans *tp, struct xfs_inode *ip); #endif /* __XFS_SYMLINK_REMOTE_H */ diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index 8a7d03396bb9..0c66cd02d76d 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -81,6 +81,7 @@ int xrep_setup_directory(struct xfs_scrub *sc); int xrep_setup_parent(struct xfs_scrub *sc); int xrep_setup_rtbitmap(struct xfs_scrub *sc, unsigned int *resblks); int xrep_setup_nlinks(struct xfs_scrub *sc, unsigned int *buf_bytes); +int xrep_setup_symlink(struct xfs_scrub *sc, unsigned int *resblks); int xrep_xattr_reset_fork(struct xfs_scrub *sc, struct xfs_inode *ip); @@ -283,6 +284,7 @@ xrep_setup_rtsummary(struct xfs_scrub *sc, unsigned int *whatever) } #define xrep_setup_rtbitmap xrep_setup_rtsummary #define xrep_setup_nlinks xrep_setup_rtsummary +#define xrep_setup_symlink xrep_setup_rtsummary #define xrep_revalidate_allocbt (NULL) #define xrep_revalidate_iallocbt (NULL) diff --git a/fs/xfs/scrub/symlink.c b/fs/xfs/scrub/symlink.c index 265bef07073b..86185c817acf 100644 --- a/fs/xfs/scrub/symlink.c +++ b/fs/xfs/scrub/symlink.c @@ -10,25 +10,33 @@ #include "xfs_trans_resv.h" #include "xfs_mount.h" #include "xfs_log_format.h" +#include "xfs_trans.h" #include "xfs_inode.h" #include "xfs_symlink.h" #include "xfs_symlink_remote.h" #include "scrub/scrub.h" #include "scrub/common.h" +#include "scrub/repair.h" /* Set us up to scrub a symbolic link. */ int xchk_setup_symlink( struct xfs_scrub *sc) { - uint resblks; + unsigned int resblks = 0; + int error; /* Allocate the buffer without the inode lock held. */ sc->buf = kvzalloc(XFS_SYMLINK_MAXLEN + 1, GFP_KERNEL); if (!sc->buf) return -ENOMEM; - resblks = xfs_symlink_blocks(sc->mp, XFS_SYMLINK_MAXLEN); + if (xchk_could_repair(sc)) { + error = xrep_setup_symlink(sc, &resblks); + if (error) + return error; + } + return xchk_setup_inode_contents(sc, resblks); } diff --git a/fs/xfs/scrub/symlink_repair.c b/fs/xfs/scrub/symlink_repair.c index e9ecf83216c2..73ffe3e80730 100644 --- a/fs/xfs/scrub/symlink_repair.c +++ b/fs/xfs/scrub/symlink_repair.c @@ -25,11 +25,14 @@ #include "xfs_bmap_btree.h" #include "xfs_trans_space.h" #include "xfs_symlink_remote.h" +#include "xfs_swapext.h" +#include "xfs_xchgrange.h" #include "scrub/xfs_scrub.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/trace.h" #include "scrub/repair.h" +#include "scrub/tempfile.h" /* * Symbolic Link Repair @@ -40,6 +43,42 @@ * turned into links to the current dir. */ +/* Set us up to repair the rtsummary file. */ +int +xrep_setup_symlink( + struct xfs_scrub *sc, + unsigned int *resblks) +{ + struct xfs_mount *mp = sc->mp; + unsigned long long blocks; + int error; + + error = xrep_tempfile_create(sc, S_IFLNK); + if (error) + return error; + + /* + * If we're doing a repair, we reserve enough blocks to write out a + * completely new symlink file, plus twice as many blocks as we would + * need if we can only allocate one block per data fork mapping. This + * should cover the preallocation of the temporary file and swapping + * the extent mappings. + * + * We cannot use xfs_swapext_estimate because we have not yet + * constructed the replacement rtsummary and therefore do not know how + * many extents it will use. By the time we do, we will have a dirty + * transaction (which we cannot drop because we cannot drop the + * rtsummary ILOCK) and cannot ask for more reservation. + */ + blocks = xfs_symlink_blocks(sc->mp, XFS_SYMLINK_MAXLEN); + blocks += xfs_bmbt_calc_size(mp, blocks) * 2; + if (blocks > UINT_MAX) + return -EOPNOTSUPP; + + *resblks += blocks; + return 0; +} + /* Try to salvage the pathname from rmt blocks. */ STATIC int xrep_symlink_salvage_remote( @@ -62,7 +101,7 @@ xrep_symlink_salvage_remote( int error; /* We'll only read until the buffer is full. */ - len = min_t(loff_t, i_size_read(VFS_I(ip)), XFS_SYMLINK_MAXLEN); + len = min_t(loff_t, ip->i_disk_size, XFS_SYMLINK_MAXLEN); fsblocks = xfs_symlink_blocks(sc->mp, len); error = xfs_bmapi_read(ip, 0, fsblocks, mval, &nmaps, 0); if (error) @@ -107,7 +146,7 @@ xrep_symlink_salvage_remote( } /* Ensure we have a zero at the end, and /some/ contents. */ - if (offset == 0) + if (offset == 0 || target_buf[0] == 0) sprintf(target_buf, "."); else target_buf[offset] = 0; @@ -123,64 +162,212 @@ xrep_symlink_salvage_inline( struct xfs_scrub *sc) { struct xfs_inode *ip = sc->ip; + char *target_buf = sc->buf; struct xfs_ifork *ifp; ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK); if (ifp->if_u1.if_data) - strncpy(sc->buf, ifp->if_u1.if_data, XFS_IFORK_DSIZE(ip)); - if (strlen(sc->buf) == 0) - sprintf(sc->buf, "."); + strncpy(target_buf, ifp->if_u1.if_data, XFS_IFORK_DSIZE(ip)); + if (target_buf[0] == 0) + sprintf(target_buf, "."); } -/* Reset an inline symlink to its fresh configuration. */ -STATIC void -xrep_symlink_truncate_inline( - struct xfs_inode *ip) +/* Salvage whatever we can of the target. */ +STATIC int +xrep_symlink_salvage( + struct xfs_scrub *sc) { - struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK); + if (sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL) { + xrep_symlink_salvage_inline(sc); + } else { + int error = xrep_symlink_salvage_remote(sc); - xfs_idestroy_fork(ifp); - memset(ifp, 0, sizeof(struct xfs_ifork)); - ifp->if_format = XFS_DINODE_FMT_EXTENTS; - ifp->if_nextents = 0; + if (error) + return error; + } + + trace_xrep_symlink_salvage_target(sc->ip, sc->buf, strlen(sc->buf)); + return 0; } /* - * Salvage an inline symlink's contents and reset data fork. - * Returns with the inode joined to the transaction. + * Prepare both links' data forks for extent swapping. Promote the tempfile + * from local format to extents format, and if the file being repaired has a + * short format data fork, turn it into an empty extent list. */ STATIC int -xrep_symlink_inline( - struct xfs_scrub *sc) +xrep_symlink_swap_prep( + struct xfs_scrub *sc, + bool temp_local, + bool ip_local) { - /* Salvage whatever link target information we can find. */ - xrep_symlink_salvage_inline(sc); + int error; + + /* + * If the temp link is in shortform format, convert that to a remote + * target so that we can use the atomic extent swap. + */ + if (temp_local) { + int logflags = XFS_ILOG_CORE; - /* Truncate the symlink. */ - xrep_symlink_truncate_inline(sc->ip); + error = xfs_bmap_local_to_extents(sc->tp, sc->tempip, 1, + &logflags, XFS_DATA_FORK, + xfs_symlink_local_to_remote); + if (error) + return error; + + xfs_trans_log_inode(sc->tp, sc->ip, 0); + + error = xfs_defer_finish(&sc->tp); + if (error) + return error; + } + + /* + * If the file being repaired had a shortform data fork, convert that + * to an empty extent list in preparation for the atomic extent swap. + */ + if (ip_local) { + struct xfs_ifork *ifp; + + ifp = XFS_IFORK_PTR(sc->ip, XFS_DATA_FORK); + xfs_idestroy_fork(ifp); + ifp->if_format = XFS_DINODE_FMT_EXTENTS; + ifp->if_nextents = 0; + ifp->if_bytes = 0; + ifp->if_u1.if_root = NULL; + ifp->if_height = 0; + + xfs_trans_log_inode(sc->tp, sc->ip, + XFS_ILOG_CORE | XFS_ILOG_DDATA); + } - xfs_trans_ijoin(sc->tp, sc->ip, 0); return 0; } /* - * Salvage an inline symlink's contents and reset data fork. - * Returns with the inode joined to the transaction. + * Change the owner field of every block in the data fork to match the link + * being repaired. */ STATIC int -xrep_symlink_remote( +xrep_symlink_swap_owner( + struct xfs_scrub *sc) +{ + struct xfs_bmbt_irec map; + struct xfs_mount *mp = sc->mp; + struct xfs_buf *bp; + struct xfs_dsymlink_hdr *dsl; + xfs_fileoff_t offset = 0; + xfs_fileoff_t end = XFS_MAX_FILEOFF; + int nmap; + int error; + + if (!xfs_has_crc(mp)) + return 0; + + for (offset = 0; + offset < end; + offset = map.br_startoff + map.br_blockcount) { + nmap = 1; + error = xfs_bmapi_read(sc->tempip, offset, end - offset, + &map, &nmap, 0); + if (error) + return error; + if (nmap != 1) + return -EFSCORRUPTED; + if (!xfs_bmap_is_written_extent(&map)) + continue; + + error = xfs_trans_read_buf(mp, sc->tp, mp->m_ddev_targp, + XFS_FSB_TO_DADDR(mp, map.br_startblock), + XFS_FSB_TO_BB(mp, map.br_blockcount), + 0, &bp, &xfs_symlink_buf_ops); + if (error) + return error; + + dsl = bp->b_addr; + dsl->sl_owner = cpu_to_be64(sc->ip->i_ino); + + xfs_trans_ordered_buf(sc->tp, bp); + xfs_trans_brelse(sc->tp, bp); + } + + return 0; +} + +/* Swap the temporary link's data fork with the one being repaired. */ +STATIC int +xrep_symlink_swap( struct xfs_scrub *sc) { + struct xfs_swapext_req req; + struct xfs_swapext_res res; + bool ip_local, temp_local; int error; - /* Salvage whatever link target information we can find. */ - error = xrep_symlink_salvage_remote(sc); + error = xrep_tempfile_swapext_prep(sc, XFS_DATA_FORK, &req, &res); if (error) return error; - /* Truncate the symlink. */ - xfs_trans_ijoin(sc->tp, sc->ip, 0); - return xfs_itruncate_extents(&sc->tp, sc->ip, XFS_DATA_FORK, 0); + error = xrep_tempfile_swapext_trans_alloc(sc, &res); + if (error) + return error; + + ip_local = sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL; + temp_local = sc->tempip->i_df.if_format == XFS_DINODE_FMT_LOCAL; + + /* + * If the both links have a local format data fork and the rebuilt + * remote data would fit in the repaired file's data fork, copy the + * contents from the tempfile and declare ourselves done. + */ + if (ip_local && temp_local && + sc->tempip->i_disk_size <= XFS_IFORK_DSIZE(sc->ip)) { + xrep_tempfile_copyout_local(sc, XFS_DATA_FORK); + return 0; + } + + /* Otherwise, make sure both data forks are in block-mapping mode. */ + error = xrep_symlink_swap_prep(sc, temp_local, ip_local); + if (error) + return error; + + /* Rewrite the owner field of all dir blocks in the temporary file. */ + error = xrep_symlink_swap_owner(sc); + if (error) + return error; + + return xfs_swapext(&sc->tp, &req); +} + +/* + * Free all the remote blocks and reset the data fork. The caller must join + * the inode to the transaction. This function returns with the inode joined + * to a clean scrub transaction. + */ +STATIC int +xrep_symlink_reset_fork( + struct xfs_scrub *sc) +{ + struct xfs_ifork *ifp = XFS_IFORK_PTR(sc->tempip, XFS_DATA_FORK); + int error; + + /* Unmap all the remote target buffers. */ + if (xfs_ifork_has_extents(ifp)) { + error = xrep_reap_fork(sc, sc->tempip, XFS_DATA_FORK); + if (error) + return error; + } + + trace_xrep_symlink_reset_fork(sc->tempip); + + /* Reset the temp link to have the same dummy content. */ + xfs_idestroy_fork(ifp); + error = xfs_symlink_write_target(sc->tp, sc->tempip, ".", 1, 0, 0); + if (error) + return error; + + return xrep_roll_trans(sc); } /* @@ -188,44 +375,77 @@ xrep_symlink_remote( * the transaction. */ STATIC int -xrep_symlink_reinitialize( +xrep_symlink_rebuild( struct xfs_scrub *sc) { + char *target_buf = sc->buf; xfs_fsblock_t fs_blocks; unsigned int target_len; unsigned int resblks; - unsigned int quota_flags = XFS_QMOPT_RES_REGBLKS; int error; /* How many blocks do we need? */ - target_len = strlen(sc->buf); + target_len = strlen(target_buf); ASSERT(target_len != 0); if (target_len == 0 || target_len > XFS_SYMLINK_MAXLEN) return -EFSCORRUPTED; - if (sc->flags & XCHK_TRY_HARDER) - quota_flags |= XFS_QMOPT_FORCE_RES; + trace_xrep_symlink_rebuild(sc->ip); - /* Set up to reinitialize the target. */ + xfs_trans_ijoin(sc->tp, sc->tempip, 0); + + /* Reserve resources to reinitialize the target. */ fs_blocks = xfs_symlink_blocks(sc->mp, target_len); resblks = XFS_SYMLINK_SPACE_RES(sc->mp, target_len, fs_blocks); - error = xfs_trans_reserve_quota_nblks(sc->tp, sc->ip, resblks, 0, - quota_flags); - if (error == -EDQUOT || error == -ENOSPC) { - /* Let xchk_teardown release everything, and try harder. */ - return -EDEADLOCK; - } + error = xfs_trans_reserve_quota_nblks(sc->tp, sc->tempip, resblks, 0, + false); + if (error) + return error; + + /* Erase the dummy target set up by the tempfile initialization. */ + xfs_idestroy_fork(&sc->tempip->i_df); + sc->tempip->i_df.if_bytes = 0; + sc->tempip->i_df.if_format = XFS_DINODE_FMT_EXTENTS; + + /* Write the salvaged target to the temporary link. */ + error = xfs_symlink_write_target(sc->tp, sc->tempip, target_buf, + target_len, fs_blocks, resblks); + if (error) + return error; + + /* + * Commit the repair transaction so that we can use the atomic extent + * swap helper functions to compute the correct block reservations and + * re-lock the inodes. + * + * We still hold IOLOCK_EXCL (aka i_rwsem) which will prevent symlink + * access until we're ready for the swap operation. + */ + error = xrep_trans_commit(sc); if (error) return error; - /* Try to write the new target back out. */ - error = xfs_symlink_write_target(sc->tp, sc->ip, sc->buf, target_len, - fs_blocks, resblks); + /* Last chance to abort before we start committing fixes. */ + if (xchk_should_terminate(sc, &error)) + return error; + + xchk_iunlock(sc, XFS_ILOCK_EXCL); + xrep_tempfile_iunlock(sc, XFS_ILOCK_EXCL); + + /* + * Swap the temp link's data fork with the file being repaired. This + * recreates the transaction and re-takes the ILOCK in the scrub + * context. + */ + error = xrep_symlink_swap(sc); if (error) return error; - /* Finish up any block mapping activities. */ - return xfs_defer_finish(&sc->tp); + /* + * Release the old symlink blocks and reset the data fork of the temp + * link to an empty shortform link. + */ + return xrep_symlink_reset_fork(sc); } /* Repair a symbolic link. */ @@ -235,19 +455,26 @@ xrep_symlink( { int error; + /* We require the rmapbt to rebuild anything. */ + if (!xfs_has_rmapbt(sc->mp)) + return -EOPNOTSUPP; + error = xfs_qm_dqattach_locked(sc->ip, false); if (error) return error; - /* Salvage whatever we can of the target. */ - *((char *)sc->buf) = 0; - if (sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL) - error = xrep_symlink_inline(sc); - else - error = xrep_symlink_remote(sc); + /* + * Cycle the ILOCK here so that we can lock both the file we're + * repairing as well as the tempfile we created earlier. + */ + if (sc->ilock_flags & XFS_ILOCK_EXCL) + xchk_iunlock(sc, XFS_ILOCK_EXCL); + xrep_tempfile_ilock_two(sc, XFS_ILOCK_EXCL); + + error = xrep_symlink_salvage(sc); if (error) return error; /* Now reset the target. */ - return xrep_symlink_reinitialize(sc); + return xrep_symlink_rebuild(sc); } diff --git a/fs/xfs/scrub/tempfile.c b/fs/xfs/scrub/tempfile.c index eaa5449bf945..540bbd03a59c 100644 --- a/fs/xfs/scrub/tempfile.c +++ b/fs/xfs/scrub/tempfile.c @@ -22,6 +22,7 @@ #include "xfs_swapext.h" #include "xfs_defer.h" #include "xfs_swapext.h" +#include "xfs_symlink_remote.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/repair.h" @@ -117,6 +118,10 @@ xrep_tempfile_create( error = xfs_dir_init(tp, sc->tempip, dp); if (error) goto out_trans_cancel; + } else if (S_ISLNK(VFS_I(sc->tempip)->i_mode)) { + error = xfs_symlink_write_target(tp, sc->tempip, ".", 1, 0, 0); + if (error) + goto out_trans_cancel; } /* @@ -433,10 +438,11 @@ xrep_tempfile_swapext_prep_request( req->req_flags |= XFS_SWAP_REQ_SET_SIZES; /* - * If we're repairing xattrs or directories, always try to convert ip2 - * to short format after swapping. + * If we're repairing symlinks, xattrs, or directories, always try to + * convert ip2 to short format after swapping. */ - if (whichfork == XFS_ATTR_FORK || S_ISDIR(VFS_I(sc->ip)->i_mode)) + if (whichfork == XFS_ATTR_FORK || S_ISDIR(VFS_I(sc->ip)->i_mode) || + S_ISLNK(VFS_I(sc->ip)->i_mode)) req->req_flags |= XFS_SWAP_REQ_FILE2_CVT_SF; return 0; diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 72ca66587d25..8f009ef52216 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2426,6 +2426,52 @@ TRACE_EVENT(xrep_rtrmap_live_update, __entry->flags) ); +TRACE_EVENT(xrep_symlink_salvage_target, + TP_PROTO(struct xfs_inode *ip, char *target, unsigned int targetlen), + TP_ARGS(ip, target, targetlen), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_ino_t, ino) + __field(unsigned int, targetlen) + __dynamic_array(char, target, targetlen + 1) + ), + TP_fast_assign( + __entry->dev = ip->i_mount->m_super->s_dev; + __entry->ino = ip->i_ino; + __entry->targetlen = targetlen; + memcpy(__get_str(target), target, targetlen); + __get_str(target)[targetlen] = 0; + ), + TP_printk("dev %d:%d ip 0x%llx target '%.*s'", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, + __entry->targetlen, + __get_str(target)) +); + +DECLARE_EVENT_CLASS(xrep_symlink_class, + TP_PROTO(struct xfs_inode *ip), + TP_ARGS(ip), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_ino_t, ino) + ), + TP_fast_assign( + __entry->dev = ip->i_mount->m_super->s_dev; + __entry->ino = ip->i_ino; + ), + TP_printk("dev %d:%d ip 0x%llx", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino) +); + +#define DEFINE_XREP_SYMLINK_EVENT(name) \ +DEFINE_EVENT(xrep_symlink_class, name, \ + TP_PROTO(struct xfs_inode *ip), \ + TP_ARGS(ip)) +DEFINE_XREP_SYMLINK_EVENT(xrep_symlink_rebuild); +DEFINE_XREP_SYMLINK_EVENT(xrep_symlink_reset_fork); + #endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */ diff --git a/fs/xfs/xfs_symlink.c b/fs/xfs/xfs_symlink.c index 4ad97295bdfc..c6de23bd92a7 100644 --- a/fs/xfs/xfs_symlink.c +++ b/fs/xfs/xfs_symlink.c @@ -251,19 +251,12 @@ out_release_dquots: */ STATIC int xfs_inactive_symlink_rmt( - struct xfs_inode *ip) + struct xfs_inode *ip) { - struct xfs_buf *bp; - int done; - int error; - int i; - xfs_mount_t *mp; - xfs_bmbt_irec_t mval[XFS_SYMLINK_MAPS]; - int nmaps; - int size; - xfs_trans_t *tp; - - mp = ip->i_mount; + struct xfs_mount *mp = ip->i_mount; + struct xfs_trans *tp; + int error; + ASSERT(!xfs_need_iread_extents(&ip->i_df)); /* * We're freeing a symlink that has some @@ -287,44 +280,14 @@ xfs_inactive_symlink_rmt( * locked for the second transaction. In the error paths we need it * held so the cancel won't rele it, see below. */ - size = (int)ip->i_disk_size; ip->i_disk_size = 0; VFS_I(ip)->i_mode = (VFS_I(ip)->i_mode & ~S_IFMT) | S_IFREG; xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); - /* - * Find the block(s) so we can inval and unmap them. - */ - done = 0; - nmaps = ARRAY_SIZE(mval); - error = xfs_bmapi_read(ip, 0, xfs_symlink_blocks(mp, size), - mval, &nmaps, 0); - if (error) - goto error_trans_cancel; - /* - * Invalidate the block(s). No validation is done. - */ - for (i = 0; i < nmaps; i++) { - error = xfs_trans_get_buf(tp, mp->m_ddev_targp, - XFS_FSB_TO_DADDR(mp, mval[i].br_startblock), - XFS_FSB_TO_BB(mp, mval[i].br_blockcount), 0, - &bp); - if (error) - goto error_trans_cancel; - xfs_trans_binval(tp, bp); - } - /* - * Unmap the dead block(s) to the dfops. - */ - error = xfs_bunmapi(tp, ip, 0, size, 0, nmaps, &done); + + error = xfs_symlink_remote_truncate(tp, ip); if (error) goto error_trans_cancel; - ASSERT(done); - /* - * Commit the transaction. This first logs the EFI and the inode, then - * rolls and commits the transaction that frees the extents. - */ - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); error = xfs_trans_commit(tp); if (error) { ASSERT(xfs_is_shutdown(mp)); |