summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/repair.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/scrub/repair.c')
-rw-r--r--fs/xfs/scrub/repair.c177
1 files changed, 176 insertions, 1 deletions
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index 222ab8746513..8a5d2ea26fdf 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -39,6 +39,7 @@
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/bitmap.h"
+#include "scrub/xfile.h"
/*
* Attempt to repair some metadata, if the metadata is corrupt and userspace
@@ -165,9 +166,21 @@ int
xrep_roll_trans(
struct xfs_scrub *sc)
{
+ int error;
+
if (!sc->ip)
return xrep_roll_ag_trans(sc);
- return xfs_trans_roll_inode(&sc->tp, sc->ip);
+
+ /*
+ * Roll the transaction with the inode we're fixing and the temp inode,
+ * so that neither can pin the log.
+ */
+ if (sc->tempip)
+ xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE);
+ error = xfs_trans_roll_inode(&sc->tp, sc->ip);
+ if (sc->tempip)
+ xfs_trans_ijoin(sc->tp, sc->tempip, 0);
+ return error;
}
/*
@@ -1618,3 +1631,165 @@ out_release_inode:
return error;
}
+
+/*
+ * 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_fallocate(
+ struct xfs_scrub *sc,
+ xfs_fileoff_t off,
+ xfs_filblks_t len)
+{
+ struct xfs_bmbt_irec map;
+ xfs_fileoff_t end = off + len;
+ int nmaps;
+ int error = 0;
+
+ ASSERT(sc->tempip != NULL);
+ ASSERT(!XFS_NOT_DQATTACHED(sc->mp, sc->tempip));
+
+ while (off < len) {
+ /*
+ * If we have a real extent mapping this block then we're
+ * in ok shape.
+ */
+ nmaps = 1;
+ error = xfs_bmapi_read(sc->tempip, off, end - off, &map, &nmaps,
+ XFS_DATA_FORK);
+ if (error)
+ break;
+
+ if (nmaps == 1 && xfs_bmap_is_written_extent(&map)) {
+ off += map.br_startblock;
+ 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 rtsum 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)
+ break;
+
+ error = xrep_roll_trans(sc);
+ if (error)
+ break;
+ off += map.br_startblock;
+ }
+
+ return error;
+}
+
+/*
+ * Write a number of bytes from the xfile into the temp file. The copybuf must
+ * be large enough to hold one filesystem block's worth of data. The caller
+ * must join both inodes to the transaction.
+ */
+int
+xrep_set_file_contents(
+ struct xfs_scrub *sc,
+ const struct xfs_buf_ops *ops,
+ enum xfs_blft type,
+ xfs_fileoff_t isize)
+{
+ LIST_HEAD(buffers_list);
+ struct xfs_bmbt_irec map;
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_buf *bp;
+ xfs_rtblock_t off = 0;
+ loff_t pos = 0;
+ unsigned int nr_buffers = 0;
+ int nmaps;
+ int error = 0;
+
+ ASSERT(S_ISREG(VFS_I(sc->tempip)->i_mode));
+
+ for (; pos < isize; pos += mp->m_sb.sb_blocksize, off++) {
+ size_t count;
+
+ /* Read block mapping for this file block. */
+ nmaps = 1;
+ error = xfs_bmapi_read(sc->tempip, off, 1, &map, &nmaps, 0);
+ if (error)
+ goto out;
+ if (nmaps == 0 || !xfs_bmap_is_written_extent(&map)) {
+ error = -EFSCORRUPTED;
+ goto out;
+ }
+
+ /* 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;
+ bp->b_ops = ops;
+ xfs_trans_buf_set_type(sc->tp, bp, type);
+
+ /* Read in a block's worth of data from the xfile. */
+ count = min_t(loff_t, isize - pos, mp->m_sb.sb_blocksize);
+ error = xfile_pread(sc->xfile, bp->b_addr, count, pos);
+ if (error) {
+ xfs_trans_brelse(sc->tp, bp);
+ goto out;
+ }
+
+ /*
+ * Put this buffer on the delwri list so we can write them all
+ * out in batches.
+ */
+ xfs_buf_delwri_queue(bp, &buffers_list);
+ xfs_trans_brelse(sc->tp, bp);
+ nr_buffers++;
+
+ /*
+ * If we have more than 256K of data to write out, flush it to
+ * disk so we don't use up too much memory.
+ */
+ if (XFS_FSB_TO_B(mp, nr_buffers) > 262144) {
+ error = xfs_buf_delwri_submit(&buffers_list);
+ if (error)
+ goto out;
+ nr_buffers = 0;
+ }
+ }
+
+ /*
+ * 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;
+ if (!list_empty(&buffers_list)) {
+ ASSERT(list_empty(&buffers_list));
+ return -EIO;
+ }
+
+ /* Set the new inode size, if needed. */
+ if (sc->tempip->i_d.di_size != isize) {
+ sc->tempip->i_d.di_size = isize;
+ xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE);
+ return xrep_roll_trans(sc);
+ }
+
+ return 0;
+out:
+ xfs_buf_delwri_cancel(&buffers_list);
+ return error;
+}