|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Copyright (c) 2018-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_trans_resv.h" | 
|  | #include "xfs_mount.h" | 
|  | #include "xfs_defer.h" | 
|  | #include "xfs_btree.h" | 
|  | #include "xfs_bit.h" | 
|  | #include "xfs_log_format.h" | 
|  | #include "xfs_trans.h" | 
|  | #include "xfs_sb.h" | 
|  | #include "xfs_inode.h" | 
|  | #include "xfs_inode_fork.h" | 
|  | #include "xfs_symlink.h" | 
|  | #include "xfs_bmap.h" | 
|  | #include "xfs_quota.h" | 
|  | #include "xfs_da_format.h" | 
|  | #include "xfs_da_btree.h" | 
|  | #include "xfs_bmap_btree.h" | 
|  | #include "xfs_trans_space.h" | 
|  | #include "xfs_symlink_remote.h" | 
|  | #include "xfs_exchmaps.h" | 
|  | #include "xfs_exchrange.h" | 
|  | #include "xfs_health.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" | 
|  | #include "scrub/tempexch.h" | 
|  | #include "scrub/reap.h" | 
|  |  | 
|  | /* | 
|  | * Symbolic Link Repair | 
|  | * ==================== | 
|  | * | 
|  | * We repair symbolic links by reading whatever target data we can find, up to | 
|  | * the first NULL byte.  If the recovered target strlen matches i_size, then | 
|  | * we rewrite the target.  In all other cases, we replace the target with an | 
|  | * overly long string that cannot possibly resolve.  The new target is written | 
|  | * into a private hidden temporary file, and then a file contents exchange | 
|  | * commits the new symlink target to the file being repaired. | 
|  | */ | 
|  |  | 
|  | /* Set us up to repair the symlink 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 exchanging | 
|  | * the extent mappings. | 
|  | * | 
|  | * We cannot use xfs_exchmaps_estimate because we have not yet | 
|  | * constructed the replacement symlink 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 | 
|  | * symlink 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 remote blocks.  Returns the number of bytes | 
|  | * salvaged or a negative errno. | 
|  | */ | 
|  | STATIC ssize_t | 
|  | xrep_symlink_salvage_remote( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | struct xfs_bmbt_irec	mval[XFS_SYMLINK_MAPS]; | 
|  | struct xfs_inode	*ip = sc->ip; | 
|  | struct xfs_buf		*bp; | 
|  | char			*target_buf = sc->buf; | 
|  | xfs_failaddr_t		fa; | 
|  | xfs_filblks_t		fsblocks; | 
|  | xfs_daddr_t		d; | 
|  | loff_t			len; | 
|  | loff_t			offset = 0; | 
|  | unsigned int		byte_cnt; | 
|  | bool			magic_ok; | 
|  | bool			hdr_ok; | 
|  | int			n; | 
|  | int			nmaps = XFS_SYMLINK_MAPS; | 
|  | int			error; | 
|  |  | 
|  | /* We'll only read until the buffer is full. */ | 
|  | 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) | 
|  | return error; | 
|  |  | 
|  | for (n = 0; n < nmaps; n++) { | 
|  | struct xfs_dsymlink_hdr	*dsl; | 
|  |  | 
|  | d = XFS_FSB_TO_DADDR(sc->mp, mval[n].br_startblock); | 
|  |  | 
|  | /* Read the rmt block.  We'll run the verifiers manually. */ | 
|  | error = xfs_trans_read_buf(sc->mp, sc->tp, sc->mp->m_ddev_targp, | 
|  | d, XFS_FSB_TO_BB(sc->mp, mval[n].br_blockcount), | 
|  | 0, &bp, NULL); | 
|  | if (error) | 
|  | return error; | 
|  | bp->b_ops = &xfs_symlink_buf_ops; | 
|  |  | 
|  | /* How many bytes do we expect to get out of this buffer? */ | 
|  | byte_cnt = XFS_FSB_TO_B(sc->mp, mval[n].br_blockcount); | 
|  | byte_cnt = XFS_SYMLINK_BUF_SPACE(sc->mp, byte_cnt); | 
|  | byte_cnt = min_t(unsigned int, byte_cnt, len); | 
|  |  | 
|  | /* | 
|  | * See if the verifiers accept this block.  We're willing to | 
|  | * salvage if the if the offset/byte/ino are ok and either the | 
|  | * verifier passed or the magic is ok.  Anything else and we | 
|  | * stop dead in our tracks. | 
|  | */ | 
|  | fa = bp->b_ops->verify_struct(bp); | 
|  | dsl = bp->b_addr; | 
|  | magic_ok = dsl->sl_magic == cpu_to_be32(XFS_SYMLINK_MAGIC); | 
|  | hdr_ok = xfs_symlink_hdr_ok(ip->i_ino, offset, byte_cnt, bp); | 
|  | if (!hdr_ok || (fa != NULL && !magic_ok)) | 
|  | break; | 
|  |  | 
|  | memcpy(target_buf + offset, dsl + 1, byte_cnt); | 
|  |  | 
|  | len -= byte_cnt; | 
|  | offset += byte_cnt; | 
|  | } | 
|  | return offset; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Try to salvage an inline symlink's contents.  Returns the number of bytes | 
|  | * salvaged or a negative errno. | 
|  | */ | 
|  | STATIC ssize_t | 
|  | xrep_symlink_salvage_inline( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | struct xfs_inode	*ip = sc->ip; | 
|  | char			*target_buf = sc->buf; | 
|  | char			*old_target; | 
|  | struct xfs_ifork	*ifp; | 
|  | unsigned int		nr; | 
|  |  | 
|  | ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK); | 
|  | if (!ifp->if_data) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * If inode repair zapped the link target, pretend that we didn't find | 
|  | * any bytes at all so that we can replace the (now totally lost) link | 
|  | * target with a warning message. | 
|  | */ | 
|  | old_target = ifp->if_data; | 
|  | if (xfs_inode_has_sickness(sc->ip, XFS_SICK_INO_SYMLINK_ZAPPED) && | 
|  | sc->ip->i_disk_size == 1 && old_target[0] == '?') | 
|  | return 0; | 
|  |  | 
|  | nr = min(XFS_SYMLINK_MAXLEN, xfs_inode_data_fork_size(ip)); | 
|  | strncpy(target_buf, ifp->if_data, nr); | 
|  | return nr; | 
|  | } | 
|  |  | 
|  | #define DUMMY_TARGET \ | 
|  | "The target of this symbolic link could not be recovered at all and " \ | 
|  | "has been replaced with this explanatory message.  To avoid " \ | 
|  | "accidentally pointing to an existing file path, this message is " \ | 
|  | "longer than the maximum supported file name length.  That is an " \ | 
|  | "acceptable length for a symlink target on XFS but will produce " \ | 
|  | "File Name Too Long errors if resolved." | 
|  |  | 
|  | /* Salvage whatever we can of the target. */ | 
|  | STATIC int | 
|  | xrep_symlink_salvage( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | char			*target_buf = sc->buf; | 
|  | ssize_t			buflen = 0; | 
|  |  | 
|  | BUILD_BUG_ON(sizeof(DUMMY_TARGET) - 1 <= NAME_MAX); | 
|  |  | 
|  | /* | 
|  | * Salvage the target if there weren't any corruption problems observed | 
|  | * while scanning it. | 
|  | */ | 
|  | if (!(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { | 
|  | if (sc->ip->i_df.if_format == XFS_DINODE_FMT_LOCAL) | 
|  | buflen = xrep_symlink_salvage_inline(sc); | 
|  | else | 
|  | buflen = xrep_symlink_salvage_remote(sc); | 
|  | if (buflen < 0) | 
|  | return buflen; | 
|  |  | 
|  | /* | 
|  | * NULL-terminate the buffer because the ondisk target does not | 
|  | * do that for us.  If salvage didn't find the exact amount of | 
|  | * data that we expected to find, don't salvage anything. | 
|  | */ | 
|  | target_buf[buflen] = 0; | 
|  | if (strlen(target_buf) != sc->ip->i_disk_size) | 
|  | buflen = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Change an empty target into a dummy target and clear the symlink | 
|  | * target zapped flag. | 
|  | */ | 
|  | if (buflen == 0) { | 
|  | sc->sick_mask |= XFS_SICK_INO_SYMLINK_ZAPPED; | 
|  | sprintf(target_buf, DUMMY_TARGET); | 
|  | } | 
|  |  | 
|  | trace_xrep_symlink_salvage_target(sc->ip, target_buf, | 
|  | strlen(target_buf)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | STATIC void | 
|  | xrep_symlink_local_to_remote( | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_buf		*bp, | 
|  | struct xfs_inode	*ip, | 
|  | struct xfs_ifork	*ifp, | 
|  | void			*priv) | 
|  | { | 
|  | struct xfs_scrub	*sc = priv; | 
|  | struct xfs_dsymlink_hdr	*dsl = bp->b_addr; | 
|  |  | 
|  | xfs_symlink_local_to_remote(tp, bp, ip, ifp, NULL); | 
|  |  | 
|  | if (!xfs_has_crc(sc->mp)) | 
|  | return; | 
|  |  | 
|  | dsl->sl_owner = cpu_to_be64(sc->ip->i_ino); | 
|  | xfs_trans_log_buf(tp, bp, 0, | 
|  | sizeof(struct xfs_dsymlink_hdr) + ifp->if_bytes - 1); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Prepare both links' data forks for an exchange.  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_swap_prep( | 
|  | struct xfs_scrub	*sc, | 
|  | bool			temp_local, | 
|  | bool			ip_local) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | /* | 
|  | * If the temp link is in shortform format, convert that to a remote | 
|  | * target so that we can use the atomic mapping exchange. | 
|  | */ | 
|  | if (temp_local) { | 
|  | int		logflags = XFS_ILOG_CORE; | 
|  |  | 
|  | error = xfs_bmap_local_to_extents(sc->tp, sc->tempip, 1, | 
|  | &logflags, XFS_DATA_FORK, | 
|  | xrep_symlink_local_to_remote, | 
|  | sc); | 
|  | 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 mapping | 
|  | * exchange. | 
|  | */ | 
|  | 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_data = NULL; | 
|  | ifp->if_height = 0; | 
|  |  | 
|  | xfs_trans_log_inode(sc->tp, sc->ip, | 
|  | XFS_ILOG_CORE | XFS_ILOG_DDATA); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Exchange the temporary symlink's data fork with the one being repaired. */ | 
|  | STATIC int | 
|  | xrep_symlink_swap( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | struct xrep_tempexch	*tx = sc->buf; | 
|  | bool			ip_local, temp_local; | 
|  | int			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_inode_data_fork_size(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; | 
|  |  | 
|  | return xrep_tempexch_contents(sc, tx); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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_ifork(sc, sc->tempip, XFS_DATA_FORK); | 
|  | if (error) | 
|  | return error; | 
|  | } | 
|  |  | 
|  | trace_xrep_symlink_reset_fork(sc->tempip); | 
|  |  | 
|  | /* Reset the temp symlink target to dummy content. */ | 
|  | xfs_idestroy_fork(ifp); | 
|  | return xfs_symlink_write_target(sc->tp, sc->tempip, sc->tempip->i_ino, | 
|  | "?", 1, 0, 0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Reinitialize a link target.  Caller must ensure the inode is joined to | 
|  | * the transaction. | 
|  | */ | 
|  | STATIC int | 
|  | xrep_symlink_rebuild( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | struct xrep_tempexch	*tx; | 
|  | char			*target_buf = sc->buf; | 
|  | xfs_fsblock_t		fs_blocks; | 
|  | unsigned int		target_len; | 
|  | unsigned int		resblks; | 
|  | int			error; | 
|  |  | 
|  | /* How many blocks do we need? */ | 
|  | target_len = strlen(target_buf); | 
|  | ASSERT(target_len != 0); | 
|  | if (target_len == 0 || target_len > XFS_SYMLINK_MAXLEN) | 
|  | return -EFSCORRUPTED; | 
|  |  | 
|  | trace_xrep_symlink_rebuild(sc->ip); | 
|  |  | 
|  | /* | 
|  | * In preparation to write the new symlink target to the temporary | 
|  | * file, drop the ILOCK of the file being repaired (it shouldn't be | 
|  | * joined) and take the ILOCK of the temporary file. | 
|  | * | 
|  | * The VFS does not take the IOLOCK while reading a symlink (and new | 
|  | * symlinks are hidden with INEW until they've been written) so it's | 
|  | * possible that a readlink() could see the old corrupted contents | 
|  | * while we're doing this. | 
|  | */ | 
|  | xchk_iunlock(sc, XFS_ILOCK_EXCL); | 
|  | xrep_tempfile_ilock(sc); | 
|  | xfs_trans_ijoin(sc->tp, sc->tempip, 0); | 
|  |  | 
|  | /* | 
|  | * Reserve resources to reinitialize the target.  We're allowed to | 
|  | * exceed file quota to repair inconsistent metadata, though this is | 
|  | * unlikely. | 
|  | */ | 
|  | 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->tempip, resblks, 0, | 
|  | true); | 
|  | 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, sc->ip->i_ino, | 
|  | target_buf, target_len, fs_blocks, resblks); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* | 
|  | * Commit the repair transaction so that we can use the atomic mapping | 
|  | * exchange functions to compute the correct block reservations and | 
|  | * re-lock the inodes. | 
|  | */ | 
|  | target_buf = NULL; | 
|  | error = xrep_trans_commit(sc); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* Last chance to abort before we start committing fixes. */ | 
|  | if (xchk_should_terminate(sc, &error)) | 
|  | return error; | 
|  |  | 
|  | xrep_tempfile_iunlock(sc); | 
|  |  | 
|  | /* | 
|  | * We're done with the temporary buffer, so we can reuse it for the | 
|  | * tempfile contents exchange information. | 
|  | */ | 
|  | tx = sc->buf; | 
|  | error = xrep_tempexch_trans_alloc(sc, XFS_DATA_FORK, tx); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* | 
|  | * Exchange the temp link's data fork with the file being repaired. | 
|  | * This recreates the transaction and takes the ILOCKs of the file | 
|  | * being repaired and the temporary file. | 
|  | */ | 
|  | error = xrep_symlink_swap(sc); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* | 
|  | * Release the old symlink blocks and reset the data fork of the temp | 
|  | * link to an empty shortform link.  This is the last repair action we | 
|  | * perform on the symlink, so we don't need to clean the transaction. | 
|  | */ | 
|  | return xrep_symlink_reset_fork(sc); | 
|  | } | 
|  |  | 
|  | /* Repair a symbolic link. */ | 
|  | int | 
|  | xrep_symlink( | 
|  | struct xfs_scrub	*sc) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | /* The rmapbt is required to reap the old data fork. */ | 
|  | if (!xfs_has_rmapbt(sc->mp)) | 
|  | return -EOPNOTSUPP; | 
|  | /* We require atomic file exchange range to rebuild anything. */ | 
|  | if (!xfs_has_exchange_range(sc->mp)) | 
|  | return -EOPNOTSUPP; | 
|  |  | 
|  | ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL); | 
|  |  | 
|  | error = xrep_symlink_salvage(sc); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* Now reset the target. */ | 
|  | error = xrep_symlink_rebuild(sc); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | return xrep_trans_commit(sc); | 
|  | } |