summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-12-04 18:04:11 -0800
committerDarrick J. Wong <djwong@kernel.org>2021-12-15 17:29:32 -0800
commit851df07d9599471df909c4ff0e3cf33f4b9619f0 (patch)
tree390175cf6ee12df70340e9e1d23742882a01bce3
parente659750c94e5a1c7b9398396e47ee00749d8973e (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.c2
-rw-r--r--fs/xfs/libxfs/xfs_bmap.h4
-rw-r--r--fs/xfs/libxfs/xfs_swapext.c48
-rw-r--r--fs/xfs/libxfs/xfs_symlink_remote.c46
-rw-r--r--fs/xfs/libxfs/xfs_symlink_remote.h1
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/symlink.c12
-rw-r--r--fs/xfs/scrub/symlink_repair.c337
-rw-r--r--fs/xfs/scrub/tempfile.c12
-rw-r--r--fs/xfs/scrub/trace.h46
-rw-r--r--fs/xfs/xfs_symlink.c51
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));