| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * 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_trans_resv.h"
- #include "xfs_mount.h"
- #include "xfs_log_format.h"
- #include "xfs_trans.h"
- #include "xfs_inode.h"
- #include "xfs_ialloc.h"
- #include "xfs_quota.h"
- #include "xfs_bmap.h"
- #include "xfs_bmap_btree.h"
- #include "xfs_trans_space.h"
- #include "xfs_dir2.h"
- #include "xfs_exchrange.h"
- #include "xfs_exchmaps.h"
- #include "xfs_defer.h"
- #include "xfs_symlink_remote.h"
- #include "scrub/scrub.h"
- #include "scrub/common.h"
- #include "scrub/repair.h"
- #include "scrub/trace.h"
- #include "scrub/tempfile.h"
- #include "scrub/tempexch.h"
- #include "scrub/xfile.h"
- /*
- * Create a temporary file for reconstructing metadata, with the intention of
- * atomically exchanging the temporary file's contents with the file that's
- * being repaired.
- */
- int
- xrep_tempfile_create(
- struct xfs_scrub *sc,
- uint16_t mode)
- {
- struct xfs_icreate_args args = {
- .pip = sc->mp->m_rootip,
- .mode = mode,
- .flags = XFS_ICREATE_TMPFILE | XFS_ICREATE_UNLINKABLE,
- };
- struct xfs_mount *mp = sc->mp;
- struct xfs_trans *tp = NULL;
- struct xfs_dquot *udqp;
- struct xfs_dquot *gdqp;
- struct xfs_dquot *pdqp;
- struct xfs_trans_res *tres;
- struct xfs_inode *dp = mp->m_rootip;
- xfs_ino_t ino;
- unsigned int resblks;
- bool is_dir = S_ISDIR(mode);
- int error;
- if (xfs_is_shutdown(mp))
- return -EIO;
- if (xfs_is_readonly(mp))
- return -EROFS;
- ASSERT(sc->tp == NULL);
- ASSERT(sc->tempip == NULL);
- /*
- * Make sure that we have allocated dquot(s) on disk. The temporary
- * inode should be completely root owned so that we don't fail due to
- * quota limits.
- */
- error = xfs_icreate_dqalloc(&args, &udqp, &gdqp, &pdqp);
- if (error)
- return error;
- if (is_dir) {
- resblks = xfs_mkdir_space_res(mp, 0);
- tres = &M_RES(mp)->tr_mkdir;
- } else {
- resblks = XFS_IALLOC_SPACE_RES(mp);
- tres = &M_RES(mp)->tr_create_tmpfile;
- }
- error = xfs_trans_alloc_icreate(mp, tres, udqp, gdqp, pdqp, resblks,
- &tp);
- if (error)
- goto out_release_dquots;
- /* Allocate inode, set up directory. */
- error = xfs_dialloc(&tp, &args, &ino);
- if (error)
- goto out_trans_cancel;
- error = xfs_icreate(tp, ino, &args, &sc->tempip);
- if (error)
- goto out_trans_cancel;
- /* We don't touch file data, so drop the realtime flags. */
- sc->tempip->i_diflags &= ~(XFS_DIFLAG_REALTIME | XFS_DIFLAG_RTINHERIT);
- xfs_trans_log_inode(tp, sc->tempip, XFS_ILOG_CORE);
- /*
- * Mark our temporary file as private so that LSMs and the ACL code
- * don't try to add their own metadata or reason about these files.
- * The file should never be exposed to userspace.
- */
- VFS_I(sc->tempip)->i_flags |= S_PRIVATE;
- VFS_I(sc->tempip)->i_opflags &= ~IOP_XATTR;
- if (is_dir) {
- error = xfs_dir_init(tp, sc->tempip, dp);
- if (error)
- goto out_trans_cancel;
- } else if (S_ISLNK(VFS_I(sc->tempip)->i_mode)) {
- /*
- * Initialize the temporary symlink with a meaningless target
- * that won't trip the verifiers. Repair must rewrite the
- * target with meaningful content before swapping with the file
- * being repaired. A single-byte target will not write a
- * remote target block, so the owner is irrelevant.
- */
- error = xfs_symlink_write_target(tp, sc->tempip,
- sc->tempip->i_ino, ".", 1, 0, 0);
- if (error)
- goto out_trans_cancel;
- }
- /*
- * Attach the dquot(s) to the inodes and modify them incore.
- * These ids of the inode couldn't have changed since the new
- * inode has been locked ever since it was created.
- */
- xfs_qm_vop_create_dqattach(tp, sc->tempip, udqp, gdqp, pdqp);
- /*
- * Put our temp file on the unlinked list so it's purged automatically.
- * All file-based metadata being reconstructed using this file must be
- * atomically exchanged with the original file because the contents
- * here will be purged when the inode is dropped or log recovery cleans
- * out the unlinked list.
- */
- error = xfs_iunlink(tp, sc->tempip);
- if (error)
- goto out_trans_cancel;
- error = xfs_trans_commit(tp);
- if (error)
- goto out_release_inode;
- trace_xrep_tempfile_create(sc);
- xfs_qm_dqrele(udqp);
- xfs_qm_dqrele(gdqp);
- xfs_qm_dqrele(pdqp);
- /* Finish setting up the incore / vfs context. */
- xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
- xfs_setup_iops(sc->tempip);
- xfs_finish_inode_setup(sc->tempip);
- sc->temp_ilock_flags = 0;
- return error;
- out_trans_cancel:
- xfs_trans_cancel(tp);
- out_release_inode:
- /*
- * Wait until after the current transaction is aborted to finish the
- * setup of the inode and release the inode. This prevents recursive
- * transactions and deadlocks from xfs_inactive.
- */
- if (sc->tempip) {
- xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
- xfs_finish_inode_setup(sc->tempip);
- xchk_irele(sc, sc->tempip);
- }
- out_release_dquots:
- xfs_qm_dqrele(udqp);
- xfs_qm_dqrele(gdqp);
- xfs_qm_dqrele(pdqp);
- return error;
- }
- /* Take IOLOCK_EXCL on the temporary file, maybe. */
- bool
- xrep_tempfile_iolock_nowait(
- struct xfs_scrub *sc)
- {
- if (xfs_ilock_nowait(sc->tempip, XFS_IOLOCK_EXCL)) {
- sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
- return true;
- }
- return false;
- }
- /*
- * Take the temporary file's IOLOCK while holding a different inode's IOLOCK.
- * In theory nobody else should hold the tempfile's IOLOCK, but we use trylock
- * to avoid deadlocks and lockdep complaints.
- */
- int
- xrep_tempfile_iolock_polled(
- struct xfs_scrub *sc)
- {
- int error = 0;
- while (!xrep_tempfile_iolock_nowait(sc)) {
- if (xchk_should_terminate(sc, &error))
- return error;
- delay(1);
- }
- return 0;
- }
- /* Release IOLOCK_EXCL on the temporary file. */
- void
- xrep_tempfile_iounlock(
- struct xfs_scrub *sc)
- {
- xfs_iunlock(sc->tempip, XFS_IOLOCK_EXCL);
- sc->temp_ilock_flags &= ~XFS_IOLOCK_EXCL;
- }
- /* Prepare the temporary file for metadata updates by grabbing ILOCK_EXCL. */
- void
- xrep_tempfile_ilock(
- struct xfs_scrub *sc)
- {
- sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
- xfs_ilock(sc->tempip, XFS_ILOCK_EXCL);
- }
- /* Try to grab ILOCK_EXCL on the temporary file. */
- bool
- xrep_tempfile_ilock_nowait(
- struct xfs_scrub *sc)
- {
- if (xfs_ilock_nowait(sc->tempip, XFS_ILOCK_EXCL)) {
- sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
- return true;
- }
- return false;
- }
- /* Unlock ILOCK_EXCL on the temporary file after an update. */
- void
- xrep_tempfile_iunlock(
- struct xfs_scrub *sc)
- {
- xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
- sc->temp_ilock_flags &= ~XFS_ILOCK_EXCL;
- }
- /*
- * Begin the process of making changes to both the file being scrubbed and
- * the temporary file by taking ILOCK_EXCL on both.
- */
- void
- xrep_tempfile_ilock_both(
- struct xfs_scrub *sc)
- {
- xfs_lock_two_inodes(sc->ip, XFS_ILOCK_EXCL, sc->tempip, XFS_ILOCK_EXCL);
- sc->ilock_flags |= XFS_ILOCK_EXCL;
- sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
- }
- /* Unlock ILOCK_EXCL on both files. */
- void
- xrep_tempfile_iunlock_both(
- struct xfs_scrub *sc)
- {
- xrep_tempfile_iunlock(sc);
- xchk_iunlock(sc, XFS_ILOCK_EXCL);
- }
- /* Release the temporary file. */
- void
- xrep_tempfile_rele(
- struct xfs_scrub *sc)
- {
- if (!sc->tempip)
- return;
- if (sc->temp_ilock_flags) {
- xfs_iunlock(sc->tempip, sc->temp_ilock_flags);
- sc->temp_ilock_flags = 0;
- }
- xchk_irele(sc, sc->tempip);
- sc->tempip = NULL;
- }
- /*
- * Make sure that the given range of the data fork of the temporary file is
- * mapped to written blocks. The caller must ensure that both inodes are
- * joined to the transaction.
- */
- int
- xrep_tempfile_prealloc(
- struct xfs_scrub *sc,
- xfs_fileoff_t off,
- xfs_filblks_t len)
- {
- struct xfs_bmbt_irec map;
- xfs_fileoff_t end = off + len;
- int error;
- ASSERT(sc->tempip != NULL);
- ASSERT(!XFS_NOT_DQATTACHED(sc->mp, sc->tempip));
- for (; off < end; off = map.br_startoff + map.br_blockcount) {
- int nmaps = 1;
- /*
- * If we have a real extent mapping this block then we're
- * in ok shape.
- */
- error = xfs_bmapi_read(sc->tempip, off, end - off, &map, &nmaps,
- XFS_DATA_FORK);
- if (error)
- return error;
- if (nmaps == 0) {
- ASSERT(nmaps != 0);
- return -EFSCORRUPTED;
- }
- if (xfs_bmap_is_written_extent(&map))
- continue;
- /*
- * If we find a delalloc reservation then something is very
- * very wrong. Bail out.
- */
- if (map.br_startblock == DELAYSTARTBLOCK)
- return -EFSCORRUPTED;
- /*
- * Make sure this block has a real zeroed extent allocated to
- * it.
- */
- nmaps = 1;
- error = xfs_bmapi_write(sc->tp, sc->tempip, off, end - off,
- XFS_BMAPI_CONVERT | XFS_BMAPI_ZERO, 0, &map,
- &nmaps);
- if (error)
- return error;
- if (nmaps != 1)
- return -EFSCORRUPTED;
- trace_xrep_tempfile_prealloc(sc, XFS_DATA_FORK, &map);
- /* Commit new extent and all deferred work. */
- error = xfs_defer_finish(&sc->tp);
- if (error)
- return error;
- }
- return 0;
- }
- /*
- * Write data to each block of a file. The given range of the tempfile's data
- * fork must already be populated with written extents.
- */
- int
- xrep_tempfile_copyin(
- struct xfs_scrub *sc,
- xfs_fileoff_t off,
- xfs_filblks_t len,
- xrep_tempfile_copyin_fn prep_fn,
- void *data)
- {
- LIST_HEAD(buffers_list);
- struct xfs_mount *mp = sc->mp;
- struct xfs_buf *bp;
- xfs_fileoff_t flush_mask;
- xfs_fileoff_t end = off + len;
- loff_t pos = XFS_FSB_TO_B(mp, off);
- int error = 0;
- ASSERT(S_ISREG(VFS_I(sc->tempip)->i_mode));
- /* Flush buffers to disk every 512K */
- flush_mask = XFS_B_TO_FSBT(mp, (1U << 19)) - 1;
- for (; off < end; off++, pos += mp->m_sb.sb_blocksize) {
- struct xfs_bmbt_irec map;
- int nmaps = 1;
- /* Read block mapping for this file block. */
- error = xfs_bmapi_read(sc->tempip, off, 1, &map, &nmaps, 0);
- if (error)
- goto out_err;
- if (nmaps == 0 || !xfs_bmap_is_written_extent(&map)) {
- error = -EFSCORRUPTED;
- goto out_err;
- }
- /* Get the metadata buffer for this offset in the file. */
- error = xfs_trans_get_buf(sc->tp, mp->m_ddev_targp,
- XFS_FSB_TO_DADDR(mp, map.br_startblock),
- mp->m_bsize, 0, &bp);
- if (error)
- goto out_err;
- trace_xrep_tempfile_copyin(sc, XFS_DATA_FORK, &map);
- /* Read in a block's worth of data from the xfile. */
- error = prep_fn(sc, bp, data);
- if (error) {
- xfs_trans_brelse(sc->tp, bp);
- goto out_err;
- }
- /* Queue buffer, and flush if we have too much dirty data. */
- xfs_buf_delwri_queue_here(bp, &buffers_list);
- xfs_trans_brelse(sc->tp, bp);
- if (!(off & flush_mask)) {
- error = xfs_buf_delwri_submit(&buffers_list);
- if (error)
- goto out_err;
- }
- }
- /*
- * Write the new blocks to disk. If the ordered list isn't empty after
- * that, then something went wrong and we have to fail. This should
- * never happen, but we'll check anyway.
- */
- error = xfs_buf_delwri_submit(&buffers_list);
- if (error)
- goto out_err;
- if (!list_empty(&buffers_list)) {
- ASSERT(list_empty(&buffers_list));
- error = -EIO;
- goto out_err;
- }
- return 0;
- out_err:
- xfs_buf_delwri_cancel(&buffers_list);
- return error;
- }
- /*
- * Set the temporary file's size. Caller must join the tempfile to the scrub
- * transaction and is responsible for adjusting block mappings as needed.
- */
- int
- xrep_tempfile_set_isize(
- struct xfs_scrub *sc,
- unsigned long long isize)
- {
- if (sc->tempip->i_disk_size == isize)
- return 0;
- sc->tempip->i_disk_size = isize;
- i_size_write(VFS_I(sc->tempip), isize);
- return xrep_tempfile_roll_trans(sc);
- }
- /*
- * Roll a repair transaction involving the temporary file. Caller must join
- * both the temporary file and the file being scrubbed to the transaction.
- * This function return with both inodes joined to a new scrub transaction,
- * or the usual negative errno.
- */
- int
- xrep_tempfile_roll_trans(
- struct xfs_scrub *sc)
- {
- int error;
- xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE);
- error = xrep_roll_trans(sc);
- if (error)
- return error;
- xfs_trans_ijoin(sc->tp, sc->tempip, 0);
- return 0;
- }
- /*
- * Fill out the mapping exchange request in preparation for atomically
- * committing the contents of a metadata file that we've rebuilt in the temp
- * file.
- */
- STATIC int
- xrep_tempexch_prep_request(
- struct xfs_scrub *sc,
- int whichfork,
- struct xrep_tempexch *tx)
- {
- struct xfs_exchmaps_req *req = &tx->req;
- memset(tx, 0, sizeof(struct xrep_tempexch));
- /* COW forks don't exist on disk. */
- if (whichfork == XFS_COW_FORK) {
- ASSERT(0);
- return -EINVAL;
- }
- /* Both files should have the relevant forks. */
- if (!xfs_ifork_ptr(sc->ip, whichfork) ||
- !xfs_ifork_ptr(sc->tempip, whichfork)) {
- ASSERT(xfs_ifork_ptr(sc->ip, whichfork) != NULL);
- ASSERT(xfs_ifork_ptr(sc->tempip, whichfork) != NULL);
- return -EINVAL;
- }
- /* Exchange all mappings in both forks. */
- req->ip1 = sc->tempip;
- req->ip2 = sc->ip;
- req->startoff1 = 0;
- req->startoff2 = 0;
- switch (whichfork) {
- case XFS_ATTR_FORK:
- req->flags |= XFS_EXCHMAPS_ATTR_FORK;
- break;
- case XFS_DATA_FORK:
- /* Always exchange sizes when exchanging data fork mappings. */
- req->flags |= XFS_EXCHMAPS_SET_SIZES;
- break;
- }
- req->blockcount = XFS_MAX_FILEOFF;
- return 0;
- }
- /*
- * Fill out the mapping exchange resource estimation structures in preparation
- * for exchanging the contents of a metadata file that we've rebuilt in the
- * temp file. Caller must hold IOLOCK_EXCL but not ILOCK_EXCL on both files.
- */
- STATIC int
- xrep_tempexch_estimate(
- struct xfs_scrub *sc,
- struct xrep_tempexch *tx)
- {
- struct xfs_exchmaps_req *req = &tx->req;
- struct xfs_ifork *ifp;
- struct xfs_ifork *tifp;
- int whichfork = xfs_exchmaps_reqfork(req);
- int state = 0;
- /*
- * The exchmaps code only knows how to exchange file fork space
- * mappings. Any fork data in local format must be promoted to a
- * single block before the exchange can take place.
- */
- ifp = xfs_ifork_ptr(sc->ip, whichfork);
- if (ifp->if_format == XFS_DINODE_FMT_LOCAL)
- state |= 1;
- tifp = xfs_ifork_ptr(sc->tempip, whichfork);
- if (tifp->if_format == XFS_DINODE_FMT_LOCAL)
- state |= 2;
- switch (state) {
- case 0:
- /* Both files have mapped extents; use the regular estimate. */
- return xfs_exchrange_estimate(req);
- case 1:
- /*
- * The file being repaired is in local format, but the temp
- * file has mapped extents. To perform the exchange, the file
- * being repaired must have its shorform data converted to an
- * ondisk block so that the forks will be in extents format.
- * We need one resblk for the conversion; the number of
- * exchanges is (worst case) the temporary file's extent count
- * plus the block we converted.
- */
- req->ip1_bcount = sc->tempip->i_nblocks;
- req->ip2_bcount = 1;
- req->nr_exchanges = 1 + tifp->if_nextents;
- req->resblks = 1;
- break;
- case 2:
- /*
- * The temporary file is in local format, but the file being
- * repaired has mapped extents. To perform the exchange, the
- * temp file must have its shortform data converted to an
- * ondisk block, and the fork changed to extents format. We
- * need one resblk for the conversion; the number of exchanges
- * is (worst case) the extent count of the file being repaired
- * plus the block we converted.
- */
- req->ip1_bcount = 1;
- req->ip2_bcount = sc->ip->i_nblocks;
- req->nr_exchanges = 1 + ifp->if_nextents;
- req->resblks = 1;
- break;
- case 3:
- /*
- * Both forks are in local format. To perform the exchange,
- * both files must have their shortform data converted to
- * fsblocks, and both forks must be converted to extents
- * format. We need two resblks for the two conversions, and
- * the number of exchanges is 1 since there's only one block at
- * fileoff 0. Presumably, the caller could not exchange the
- * two inode fork areas directly.
- */
- req->ip1_bcount = 1;
- req->ip2_bcount = 1;
- req->nr_exchanges = 1;
- req->resblks = 2;
- break;
- }
- return xfs_exchmaps_estimate_overhead(req);
- }
- /*
- * Obtain a quota reservation to make sure we don't hit EDQUOT. We can skip
- * this if quota enforcement is disabled or if both inodes' dquots are the
- * same. The qretry structure must be initialized to zeroes before the first
- * call to this function.
- */
- STATIC int
- xrep_tempexch_reserve_quota(
- struct xfs_scrub *sc,
- const struct xrep_tempexch *tx)
- {
- struct xfs_trans *tp = sc->tp;
- const struct xfs_exchmaps_req *req = &tx->req;
- int64_t ddelta, rdelta;
- int error;
- /*
- * Don't bother with a quota reservation if we're not enforcing them
- * or the two inodes have the same dquots.
- */
- if (!XFS_IS_QUOTA_ON(tp->t_mountp) || req->ip1 == req->ip2 ||
- (req->ip1->i_udquot == req->ip2->i_udquot &&
- req->ip1->i_gdquot == req->ip2->i_gdquot &&
- req->ip1->i_pdquot == req->ip2->i_pdquot))
- return 0;
- /*
- * Quota reservation for each file comes from two sources. First, we
- * need to account for any net gain in mapped blocks during the
- * exchange. Second, we need reservation for the gross gain in mapped
- * blocks so that we don't trip over any quota block reservation
- * assertions. We must reserve the gross gain because the quota code
- * subtracts from bcount the number of blocks that we unmap; it does
- * not add that quantity back to the quota block reservation.
- */
- ddelta = max_t(int64_t, 0, req->ip2_bcount - req->ip1_bcount);
- rdelta = max_t(int64_t, 0, req->ip2_rtbcount - req->ip1_rtbcount);
- error = xfs_trans_reserve_quota_nblks(tp, req->ip1,
- ddelta + req->ip1_bcount, rdelta + req->ip1_rtbcount,
- true);
- if (error)
- return error;
- ddelta = max_t(int64_t, 0, req->ip1_bcount - req->ip2_bcount);
- rdelta = max_t(int64_t, 0, req->ip1_rtbcount - req->ip2_rtbcount);
- return xfs_trans_reserve_quota_nblks(tp, req->ip2,
- ddelta + req->ip2_bcount, rdelta + req->ip2_rtbcount,
- true);
- }
- /*
- * Prepare an existing transaction for an atomic file contents exchange.
- *
- * This function fills out the mapping exchange request and resource estimation
- * structures in preparation for exchanging the contents of a metadata file
- * that has been rebuilt in the temp file. Next, it reserves space and quota
- * for the transaction.
- *
- * The caller must hold ILOCK_EXCL of the scrub target file and the temporary
- * file. The caller must join both inodes to the transaction with no unlock
- * flags, and is responsible for dropping both ILOCKs when appropriate. Only
- * use this when those ILOCKs cannot be dropped.
- */
- int
- xrep_tempexch_trans_reserve(
- struct xfs_scrub *sc,
- int whichfork,
- struct xrep_tempexch *tx)
- {
- int error;
- ASSERT(sc->tp != NULL);
- xfs_assert_ilocked(sc->ip, XFS_ILOCK_EXCL);
- xfs_assert_ilocked(sc->tempip, XFS_ILOCK_EXCL);
- error = xrep_tempexch_prep_request(sc, whichfork, tx);
- if (error)
- return error;
- error = xfs_exchmaps_estimate(&tx->req);
- if (error)
- return error;
- error = xfs_trans_reserve_more(sc->tp, tx->req.resblks, 0);
- if (error)
- return error;
- return xrep_tempexch_reserve_quota(sc, tx);
- }
- /*
- * Create a new transaction for a file contents exchange.
- *
- * This function fills out the mapping excahange request and resource
- * estimation structures in preparation for exchanging the contents of a
- * metadata file that has been rebuilt in the temp file. Next, it reserves
- * space, takes ILOCK_EXCL of both inodes, joins them to the transaction and
- * reserves quota for the transaction.
- *
- * The caller is responsible for dropping both ILOCKs when appropriate.
- */
- int
- xrep_tempexch_trans_alloc(
- struct xfs_scrub *sc,
- int whichfork,
- struct xrep_tempexch *tx)
- {
- unsigned int flags = 0;
- int error;
- ASSERT(sc->tp == NULL);
- ASSERT(xfs_has_exchange_range(sc->mp));
- error = xrep_tempexch_prep_request(sc, whichfork, tx);
- if (error)
- return error;
- error = xrep_tempexch_estimate(sc, tx);
- if (error)
- return error;
- if (xfs_has_lazysbcount(sc->mp))
- flags |= XFS_TRANS_RES_FDBLKS;
- error = xfs_trans_alloc(sc->mp, &M_RES(sc->mp)->tr_itruncate,
- tx->req.resblks, 0, flags, &sc->tp);
- if (error)
- return error;
- sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
- sc->ilock_flags |= XFS_ILOCK_EXCL;
- xfs_exchrange_ilock(sc->tp, sc->ip, sc->tempip);
- return xrep_tempexch_reserve_quota(sc, tx);
- }
- /*
- * Exchange file mappings (and hence file contents) between the file being
- * repaired and the temporary file. Returns with both inodes locked and joined
- * to a clean scrub transaction.
- */
- int
- xrep_tempexch_contents(
- struct xfs_scrub *sc,
- struct xrep_tempexch *tx)
- {
- int error;
- ASSERT(xfs_has_exchange_range(sc->mp));
- xfs_exchange_mappings(sc->tp, &tx->req);
- error = xfs_defer_finish(&sc->tp);
- if (error)
- return error;
- /*
- * If we exchanged the ondisk sizes of two metadata files, we must
- * exchanged the incore sizes as well.
- */
- if (tx->req.flags & XFS_EXCHMAPS_SET_SIZES) {
- loff_t temp;
- temp = i_size_read(VFS_I(sc->ip));
- i_size_write(VFS_I(sc->ip), i_size_read(VFS_I(sc->tempip)));
- i_size_write(VFS_I(sc->tempip), temp);
- }
- return 0;
- }
- /*
- * Write local format data from one of the temporary file's forks into the same
- * fork of file being repaired, and exchange the file sizes, if appropriate.
- * Caller must ensure that the file being repaired has enough fork space to
- * hold all the bytes.
- */
- void
- xrep_tempfile_copyout_local(
- struct xfs_scrub *sc,
- int whichfork)
- {
- struct xfs_ifork *temp_ifp;
- struct xfs_ifork *ifp;
- unsigned int ilog_flags = XFS_ILOG_CORE;
- temp_ifp = xfs_ifork_ptr(sc->tempip, whichfork);
- ifp = xfs_ifork_ptr(sc->ip, whichfork);
- ASSERT(temp_ifp != NULL);
- ASSERT(ifp != NULL);
- ASSERT(temp_ifp->if_format == XFS_DINODE_FMT_LOCAL);
- ASSERT(ifp->if_format == XFS_DINODE_FMT_LOCAL);
- switch (whichfork) {
- case XFS_DATA_FORK:
- ASSERT(sc->tempip->i_disk_size <=
- xfs_inode_data_fork_size(sc->ip));
- break;
- case XFS_ATTR_FORK:
- ASSERT(sc->tempip->i_forkoff >= sc->ip->i_forkoff);
- break;
- default:
- ASSERT(0);
- return;
- }
- /* Recreate @sc->ip's incore fork (ifp) with data from temp_ifp. */
- xfs_idestroy_fork(ifp);
- xfs_init_local_fork(sc->ip, whichfork, temp_ifp->if_data,
- temp_ifp->if_bytes);
- if (whichfork == XFS_DATA_FORK) {
- i_size_write(VFS_I(sc->ip), i_size_read(VFS_I(sc->tempip)));
- sc->ip->i_disk_size = sc->tempip->i_disk_size;
- }
- ilog_flags |= xfs_ilog_fdata(whichfork);
- xfs_trans_log_inode(sc->tp, sc->ip, ilog_flags);
- }
- /* Decide if a given XFS inode is a temporary file for a repair. */
- bool
- xrep_is_tempfile(
- const struct xfs_inode *ip)
- {
- const struct inode *inode = &ip->i_vnode;
- if (IS_PRIVATE(inode) && !(inode->i_opflags & IOP_XATTR))
- return true;
- return false;
- }
|