summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/xfs/libxfs/xfs_attr_leaf.c14
-rw-r--r--fs/xfs/libxfs/xfs_btree_staging.c7
-rw-r--r--fs/xfs/libxfs/xfs_da_btree.c3
-rw-r--r--fs/xfs/libxfs/xfs_dquot_buf.c47
-rw-r--r--fs/xfs/libxfs/xfs_ialloc.c2
-rw-r--r--fs/xfs/libxfs/xfs_inode_buf.c15
-rw-r--r--fs/xfs/libxfs/xfs_metadir.c64
-rw-r--r--fs/xfs/libxfs/xfs_metadir.h8
-rw-r--r--fs/xfs/libxfs/xfs_rtgroup.c58
-rw-r--r--fs/xfs/xfs_bmap_util.c42
-rw-r--r--fs/xfs/xfs_bmap_util.h7
-rw-r--r--fs/xfs/xfs_buf.c392
-rw-r--r--fs/xfs/xfs_buf.h8
-rw-r--r--fs/xfs/xfs_buf_item.c10
-rw-r--r--fs/xfs/xfs_buf_item_recover.c3
-rw-r--r--fs/xfs/xfs_dquot_item_recover.c1
-rw-r--r--fs/xfs/xfs_file.c118
-rw-r--r--fs/xfs/xfs_fsops.c2
-rw-r--r--fs/xfs/xfs_inode.c4
-rw-r--r--fs/xfs/xfs_inode_item_recover.c1
-rw-r--r--fs/xfs/xfs_log_recover.c5
-rw-r--r--fs/xfs/xfs_trace.h2
-rw-r--r--fs/xfs/xfs_trans.c16
-rw-r--r--fs/xfs/xfs_trans_buf.c7
-rw-r--r--fs/xfs/xfs_zone_gc.c2
25 files changed, 492 insertions, 346 deletions
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c
index 86c5c09a5db4..b6288395f853 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.c
+++ b/fs/xfs/libxfs/xfs_attr_leaf.c
@@ -325,6 +325,13 @@ xfs_attr3_leaf_verify_entry(
*/
if (ent->flags & XFS_ATTR_LOCAL) {
lentry = xfs_attr3_leaf_name_local(leaf, idx);
+
+ /* Validate lentry pointer is within bounds before field access */
+ if ((char *)lentry >= buf_end)
+ return __this_address;
+ if ((char *)lentry + offsetof(struct xfs_attr_leaf_name_local, nameval) > buf_end)
+ return __this_address;
+
namesize = xfs_attr_leaf_entsize_local(lentry->namelen,
be16_to_cpu(lentry->valuelen));
name_end = (char *)lentry + namesize;
@@ -332,6 +339,13 @@ xfs_attr3_leaf_verify_entry(
return __this_address;
} else {
rentry = xfs_attr3_leaf_name_remote(leaf, idx);
+
+ /* Validate rentry pointer is within bounds before field access */
+ if ((char *)rentry >= buf_end)
+ return __this_address;
+ if ((char *)rentry + offsetof(struct xfs_attr_leaf_name_remote, name) > buf_end)
+ return __this_address;
+
namesize = xfs_attr_leaf_entsize_remote(rentry->namelen);
name_end = (char *)rentry + namesize;
if (rentry->namelen == 0)
diff --git a/fs/xfs/libxfs/xfs_btree_staging.c b/fs/xfs/libxfs/xfs_btree_staging.c
index c3c7ea54895a..7314dab4bcfb 100644
--- a/fs/xfs/libxfs/xfs_btree_staging.c
+++ b/fs/xfs/libxfs/xfs_btree_staging.c
@@ -248,11 +248,10 @@ xfs_btree_bload_drop_buf(
return 0;
/*
- * Mark this buffer XBF_DONE (i.e. uptodate) so that a subsequent
- * xfs_buf_read will not pointlessly reread the contents from the disk.
+ * Mark this buffer uptodate so that a subsequent xfs_buf_read will
+ * not pointlessly reread the contents from the disk.
*/
- bp->b_flags |= XBF_DONE;
-
+ xfs_buf_set_uptodate(bp);
xfs_buf_delwri_queue_here(bp, buffers_list);
xfs_buf_relse(bp);
*bpp = NULL;
diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c
index 9debb95d86fa..f190c088591b 100644
--- a/fs/xfs/libxfs/xfs_da_btree.c
+++ b/fs/xfs/libxfs/xfs_da_btree.c
@@ -2354,8 +2354,7 @@ xfs_da_grow_inode_int(
* If we didn't get it and the block might work if fragmented,
* try without the CONTIG flag. Loop until we get it all.
*/
- mapp = kmalloc(sizeof(*mapp) * count,
- GFP_KERNEL | __GFP_NOFAIL);
+ mapp = kmalloc_objs(*mapp, count, GFP_KERNEL | __GFP_NOFAIL);
for (b = *bno, mapi = 0; b < *bno + count; ) {
c = (int)(*bno + count - b);
nmap = min(XFS_BMAP_MAX_NMAP, c);
diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c
index bbada0d3cc08..77954d1d924c 100644
--- a/fs/xfs/libxfs/xfs_dquot_buf.c
+++ b/fs/xfs/libxfs/xfs_dquot_buf.c
@@ -252,8 +252,8 @@ xfs_dquot_buf_read_verify(
/*
* readahead errors are silent and simply leave the buffer as !done so a real
* read will then be run with the xfs_dquot_buf_ops verifier. See
- * xfs_inode_buf_verify() for why we use EIO and ~XBF_DONE here rather than
- * reporting the failure.
+ * xfs_inode_buf_verify() for why we use EIO here rather than reporting the
+ * failure.
*/
static void
xfs_dquot_buf_readahead_verify(
@@ -262,10 +262,8 @@ xfs_dquot_buf_readahead_verify(
struct xfs_mount *mp = bp->b_mount;
if (!xfs_dquot_buf_verify_crc(mp, bp, true) ||
- xfs_dquot_buf_verify(mp, bp, true) != NULL) {
+ xfs_dquot_buf_verify(mp, bp, true) != NULL)
xfs_buf_ioerror(bp, -EIO);
- bp->b_flags &= ~XBF_DONE;
- }
}
/*
@@ -416,6 +414,15 @@ xfs_dqinode_load(
return 0;
}
+static int
+xfs_dqinode_init(
+ struct xfs_metadir_update *upd,
+ void *priv)
+{
+ xfs_trans_log_inode(upd->tp, upd->ip, XFS_ILOG_CORE);
+ return 0;
+}
+
/* Create a metadata directory quota inode. */
int
xfs_dqinode_metadir_create(
@@ -428,35 +435,9 @@ xfs_dqinode_metadir_create(
.metafile_type = xfs_dqinode_metafile_type(type),
.path = xfs_dqinode_path(type),
};
- int error;
-
- error = xfs_metadir_start_create(&upd);
- if (error)
- return error;
- error = xfs_metadir_create(&upd, S_IFREG);
- if (error)
- goto out_cancel;
-
- xfs_trans_log_inode(upd.tp, upd.ip, XFS_ILOG_CORE);
-
- error = xfs_metadir_commit(&upd);
- if (error)
- goto out_irele;
-
- xfs_finish_inode_setup(upd.ip);
- *ipp = upd.ip;
- return 0;
-
-out_cancel:
- xfs_metadir_cancel(&upd, error);
-out_irele:
- /* Have to finish setting up the inode to ensure it's deleted. */
- if (upd.ip) {
- xfs_finish_inode_setup(upd.ip);
- xfs_irele(upd.ip);
- }
- return error;
+ return xfs_metadir_create_file(&upd, S_IFREG, xfs_dqinode_init, NULL,
+ ipp);
}
#ifndef __KERNEL__
diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c
index ffcdd1f691fd..58dac4d505ba 100644
--- a/fs/xfs/libxfs/xfs_ialloc.c
+++ b/fs/xfs/libxfs/xfs_ialloc.c
@@ -413,7 +413,7 @@ xfs_ialloc_inode_init(
xfs_trans_ordered_buf(tp, fbuf);
}
} else {
- fbuf->b_flags |= XBF_DONE;
+ xfs_buf_set_uptodate(fbuf);
xfs_buf_delwri_queue(fbuf, buffer_list);
xfs_buf_relse(fbuf);
}
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
index 336ef843f2fe..e4c3f7b24e95 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -29,12 +29,14 @@
* has not had the inode cores stamped into it. Hence for readahead, the buffer
* may be potentially invalid.
*
- * If the readahead buffer is invalid, we need to mark it with an error and
- * clear the DONE status of the buffer so that a followup read will re-read it
- * from disk. We don't report the error otherwise to avoid warnings during log
- * recovery and we don't get unnecessary panics on debug kernels. We use EIO here
- * because all we want to do is say readahead failed; there is no-one to report
- * the error to, so this will distinguish it from a non-ra verifier failure.
+ * If the readahead buffer is invalid, we need to mark it with an error so that a
+ * followup read will re-read it from disk.
+ *
+ * We don't report the error otherwise to avoid warnings during log recovery and
+ * we don't get unnecessary panics on debug kernels. Use EIO here because all
+ * we want to do is say readahead failed; there is no-one to report the error
+ * to, so this will distinguish it from a non-ra verifier failure.
+ *
* Changes to this readahead error behaviour also need to be reflected in
* xfs_dquot_buf_readahead_verify().
*/
@@ -64,7 +66,6 @@ xfs_inode_buf_verify(
if (unlikely(!di_ok ||
XFS_TEST_ERROR(mp, XFS_ERRTAG_ITOBP_INOTOBP))) {
if (readahead) {
- bp->b_flags &= ~XBF_DONE;
xfs_buf_ioerror(bp, -EIO);
return;
}
diff --git a/fs/xfs/libxfs/xfs_metadir.c b/fs/xfs/libxfs/xfs_metadir.c
index 74c4596ee4cf..7c6b086b73db 100644
--- a/fs/xfs/libxfs/xfs_metadir.c
+++ b/fs/xfs/libxfs/xfs_metadir.c
@@ -182,7 +182,7 @@ xfs_metadir_teardown(
* Begin the process of creating a metadata file by allocating transactions
* and taking whatever resources we're going to need.
*/
-int
+static int
xfs_metadir_start_create(
struct xfs_metadir_update *upd)
{
@@ -236,7 +236,7 @@ out_teardown:
* a negative error code. If an inode is passed back, the caller must finish
* setting up the inode before releasing it.
*/
-int
+static int
xfs_metadir_create(
struct xfs_metadir_update *upd,
umode_t mode)
@@ -425,7 +425,7 @@ xfs_metadir_commit(
}
/* Cancel a metadir update and unlock/drop all resources. */
-void
+static void
xfs_metadir_cancel(
struct xfs_metadir_update *upd,
int error)
@@ -438,48 +438,64 @@ xfs_metadir_cancel(
xfs_metadir_teardown(upd, error);
}
-/* Create a metadata for the last component of the path. */
int
-xfs_metadir_mkdir(
- struct xfs_inode *dp,
- const char *path,
+xfs_metadir_create_file(
+ struct xfs_metadir_update *upd,
+ umode_t mode,
+ xfs_metadir_createfn create,
+ void *priv,
struct xfs_inode **ipp)
{
- struct xfs_metadir_update upd = {
- .dp = dp,
- .path = path,
- .metafile_type = XFS_METAFILE_DIR,
- };
int error;
- if (xfs_is_shutdown(dp->i_mount))
+ if (xfs_is_shutdown(upd->dp->i_mount))
return -EIO;
- /* Allocate a transaction to create the last directory. */
- error = xfs_metadir_start_create(&upd);
+ error = xfs_metadir_start_create(upd);
if (error)
return error;
- /* Create the subdirectory and take our reference. */
- error = xfs_metadir_create(&upd, S_IFDIR);
+ error = xfs_metadir_create(upd, mode);
if (error)
goto out_cancel;
- error = xfs_metadir_commit(&upd);
+ if (create) {
+ error = create(upd, priv);
+ if (error)
+ goto out_cancel;
+ }
+
+ error = xfs_metadir_commit(upd);
if (error)
goto out_irele;
- xfs_finish_inode_setup(upd.ip);
- *ipp = upd.ip;
+ xfs_finish_inode_setup(upd->ip);
+ *ipp = upd->ip;
return 0;
out_cancel:
- xfs_metadir_cancel(&upd, error);
+ xfs_metadir_cancel(upd, error);
out_irele:
/* Have to finish setting up the inode to ensure it's deleted. */
- if (upd.ip) {
- xfs_finish_inode_setup(upd.ip);
- xfs_irele(upd.ip);
+ if (upd->ip) {
+ xfs_finish_inode_setup(upd->ip);
+ xfs_irele(upd->ip);
}
return error;
}
+
+/* Create a metadata for the last component of the path. */
+int
+xfs_metadir_mkdir(
+ struct xfs_inode *dp,
+ const char *path,
+ struct xfs_inode **ipp)
+{
+ struct xfs_metadir_update upd = {
+ .dp = dp,
+ .path = path,
+ .metafile_type = XFS_METAFILE_DIR,
+ };
+
+ return xfs_metadir_create_file(&upd, S_IFDIR, NULL, NULL, ipp);
+}
diff --git a/fs/xfs/libxfs/xfs_metadir.h b/fs/xfs/libxfs/xfs_metadir.h
index bfecac7d3d14..e434b9d1c932 100644
--- a/fs/xfs/libxfs/xfs_metadir.h
+++ b/fs/xfs/libxfs/xfs_metadir.h
@@ -32,14 +32,16 @@ int xfs_metadir_load(struct xfs_trans *tp, struct xfs_inode *dp,
const char *path, enum xfs_metafile_type metafile_type,
struct xfs_inode **ipp);
-int xfs_metadir_start_create(struct xfs_metadir_update *upd);
-int xfs_metadir_create(struct xfs_metadir_update *upd, umode_t mode);
+typedef int (*xfs_metadir_createfn)(struct xfs_metadir_update *upd, void *priv);
+
+int xfs_metadir_create_file(struct xfs_metadir_update *upd, umode_t mode,
+ xfs_metadir_createfn create, void *priv,
+ struct xfs_inode **ipp);
int xfs_metadir_start_link(struct xfs_metadir_update *upd);
int xfs_metadir_link(struct xfs_metadir_update *upd);
int xfs_metadir_commit(struct xfs_metadir_update *upd);
-void xfs_metadir_cancel(struct xfs_metadir_update *upd, int error);
int xfs_metadir_mkdir(struct xfs_inode *dp, const char *path,
struct xfs_inode **ipp);
diff --git a/fs/xfs/libxfs/xfs_rtgroup.c b/fs/xfs/libxfs/xfs_rtgroup.c
index c85d50953218..fe7222bbe449 100644
--- a/fs/xfs/libxfs/xfs_rtgroup.c
+++ b/fs/xfs/libxfs/xfs_rtgroup.c
@@ -517,6 +517,25 @@ xfs_rtginode_irele(
*ipp = NULL;
}
+struct xfs_rtginode_create {
+ struct xfs_rtgroup *rtg;
+ enum xfs_rtg_inodes type;
+ bool init;
+};
+
+static int
+xfs_rtginode_init(
+ struct xfs_metadir_update *upd,
+ void *priv)
+{
+ struct xfs_rtginode_create *rc = priv;
+ const struct xfs_rtginode_ops *ops = &xfs_rtginode_ops[rc->type];
+
+ xfs_rtginode_lockdep_setup(upd->ip, rtg_rgno(rc->rtg), rc->type);
+ upd->ip->i_projid = rtg_rgno(rc->rtg);
+ return ops->create(rc->rtg, upd->ip, upd->tp, rc->init);
+}
+
/* Add a metadata inode for a realtime rmap btree. */
int
xfs_rtginode_create(
@@ -526,6 +545,11 @@ xfs_rtginode_create(
{
const struct xfs_rtginode_ops *ops = &xfs_rtginode_ops[type];
struct xfs_mount *mp = rtg_mount(rtg);
+ struct xfs_rtginode_create rc = {
+ .rtg = rtg,
+ .type = type,
+ .init = init,
+ };
struct xfs_metadir_update upd = {
.dp = mp->m_rtdirip,
.metafile_type = ops->metafile_type,
@@ -544,38 +568,8 @@ xfs_rtginode_create(
if (!upd.path)
return -ENOMEM;
- error = xfs_metadir_start_create(&upd);
- if (error)
- goto out_path;
-
- error = xfs_metadir_create(&upd, S_IFREG);
- if (error)
- goto out_cancel;
-
- xfs_rtginode_lockdep_setup(upd.ip, rtg_rgno(rtg), type);
-
- upd.ip->i_projid = rtg_rgno(rtg);
- error = ops->create(rtg, upd.ip, upd.tp, init);
- if (error)
- goto out_cancel;
-
- error = xfs_metadir_commit(&upd);
- if (error)
- goto out_path;
-
- kfree(upd.path);
- xfs_finish_inode_setup(upd.ip);
- rtg->rtg_inodes[type] = upd.ip;
- return 0;
-
-out_cancel:
- xfs_metadir_cancel(&upd, error);
- /* Have to finish setting up the inode to ensure it's deleted. */
- if (upd.ip) {
- xfs_finish_inode_setup(upd.ip);
- xfs_irele(upd.ip);
- }
-out_path:
+ error = xfs_metadir_create_file(&upd, S_IFREG, xfs_rtginode_init, &rc,
+ &rtg->rtg_inodes[type]);
kfree(upd.path);
return error;
}
diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
index c88b9ade7389..268d159339d0 100644
--- a/fs/xfs/xfs_bmap_util.c
+++ b/fs/xfs/xfs_bmap_util.c
@@ -642,11 +642,26 @@ out_unlock:
return error;
}
+/*
+ * Allocate space or convert extents for a file according to @mode:
+ *
+ * XFS_ALLOC_FILE_SPACE_PREALLOC:
+ * Preallocate unwritten extents over holes across the range and mark the inode
+ * as preallocated.
+ *
+ * XFS_ALLOC_FILE_SPACE_WRITE_ZEROES:
+ * Allocate written extents over holes and convert unwritten extents in the
+ * range to written extents, initialising both to contain zeroes.
+ *
+ * This function does not update the file size; callers that extend the file
+ * are responsible for updating it once the extents are allocated.
+ */
int
xfs_alloc_file_space(
struct xfs_inode *ip,
xfs_off_t offset,
- xfs_off_t len)
+ xfs_off_t len,
+ enum xfs_alloc_file_space_mode mode)
{
xfs_mount_t *mp = ip->i_mount;
xfs_off_t count;
@@ -657,6 +672,7 @@ xfs_alloc_file_space(
int rt;
xfs_trans_t *tp;
xfs_bmbt_irec_t imaps[1], *imapp;
+ uint32_t bmapi_flags, nr_exts;
int error;
if (xfs_is_always_cow_inode(ip))
@@ -674,6 +690,19 @@ xfs_alloc_file_space(
if (len <= 0)
return -EINVAL;
+ switch (mode) {
+ case XFS_ALLOC_FILE_SPACE_PREALLOC:
+ bmapi_flags = XFS_BMAPI_PREALLOC;
+ nr_exts = XFS_IEXT_ADD_NOSPLIT_CNT;
+ break;
+ case XFS_ALLOC_FILE_SPACE_WRITE_ZEROES:
+ bmapi_flags = XFS_BMAPI_CONVERT | XFS_BMAPI_ZERO;
+ nr_exts = XFS_IEXT_WRITE_UNWRITTEN_CNT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
rt = XFS_IS_REALTIME_INODE(ip);
extsz = xfs_get_extsz_hint(ip);
@@ -733,8 +762,7 @@ xfs_alloc_file_space(
if (error)
break;
- error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK,
- XFS_IEXT_ADD_NOSPLIT_CNT);
+ error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK, nr_exts);
if (error)
goto error;
@@ -748,7 +776,7 @@ xfs_alloc_file_space(
* will eventually reach the requested range.
*/
error = xfs_bmapi_write(tp, ip, startoffset_fsb,
- allocatesize_fsb, XFS_BMAPI_PREALLOC, 0, imapp,
+ allocatesize_fsb, bmapi_flags, 0, imapp,
&nimaps);
if (error) {
if (error != -ENOSR)
@@ -759,8 +787,10 @@ xfs_alloc_file_space(
allocatesize_fsb -= imapp->br_blockcount;
}
- ip->i_diflags |= XFS_DIFLAG_PREALLOC;
- xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ if (mode == XFS_ALLOC_FILE_SPACE_PREALLOC) {
+ ip->i_diflags |= XFS_DIFLAG_PREALLOC;
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ }
error = xfs_trans_commit(tp);
xfs_iunlock(ip, XFS_ILOCK_EXCL);
diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h
index eaaf094154b9..c7b48b2602f2 100644
--- a/fs/xfs/xfs_bmap_util.h
+++ b/fs/xfs/xfs_bmap_util.h
@@ -55,8 +55,13 @@ int xfs_bmap_last_extent(struct xfs_trans *tp, struct xfs_inode *ip,
int *is_empty);
/* preallocation and hole punch interface */
+enum xfs_alloc_file_space_mode {
+ XFS_ALLOC_FILE_SPACE_PREALLOC,
+ XFS_ALLOC_FILE_SPACE_WRITE_ZEROES,
+};
+
int xfs_alloc_file_space(struct xfs_inode *ip, xfs_off_t offset,
- xfs_off_t len);
+ xfs_off_t len, enum xfs_alloc_file_space_mode mode);
int xfs_free_file_space(struct xfs_inode *ip, xfs_off_t offset,
xfs_off_t len, struct xfs_zone_alloc_ctx *ac);
int xfs_collapse_file_space(struct xfs_inode *, xfs_off_t offset,
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 17b9d643e1a8..ee7c2e9c0340 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -55,6 +55,29 @@ static inline bool xfs_buf_is_uncached(struct xfs_buf *bp)
return bp->b_rhash_key == XFS_BUF_DADDR_NULL;
}
+static inline void
+xfs_buf_set_flags(
+ struct xfs_buf *bp,
+ unsigned int flags)
+{
+ WRITE_ONCE(bp->b_flags, bp->b_flags | flags);
+}
+
+static inline void
+xfs_buf_clear_flags(
+ struct xfs_buf *bp,
+ unsigned int flags)
+{
+ WRITE_ONCE(bp->b_flags, bp->b_flags & ~flags);
+}
+
+void
+xfs_buf_set_uptodate(
+ struct xfs_buf *bp)
+{
+ xfs_buf_set_flags(bp, XBF_DONE);
+}
+
/*
* When we mark a buffer stale, we remove the buffer from the LRU and clear the
* b_lru_ref count so that the buffer is freed immediately when the buffer
@@ -69,14 +92,14 @@ xfs_buf_stale(
{
ASSERT(xfs_buf_islocked(bp));
- bp->b_flags |= XBF_STALE;
+ xfs_buf_set_flags(bp, XBF_STALE);
/*
* Clear the delwri status so that a delwri queue walker will not
* flush this buffer to disk now that it is stale. The delwri queue has
* a reference to the buffer, so this is safe to do.
*/
- bp->b_flags &= ~_XBF_DELWRI_Q;
+ xfs_buf_clear_flags(bp, _XBF_DELWRI_Q);
spin_lock(&bp->b_lockref.lock);
atomic_set(&bp->b_lru_ref, 0);
@@ -85,6 +108,14 @@ xfs_buf_stale(
spin_unlock(&bp->b_lockref.lock);
}
+void
+xfs_buf_clear_stale(
+ struct xfs_buf *bp)
+{
+ ASSERT(bp->b_flags & XBF_STALE);
+ xfs_buf_clear_flags(bp, XBF_STALE);
+}
+
static void
xfs_buf_free_callback(
struct callback_head *cb)
@@ -159,7 +190,7 @@ xfs_buf_alloc_kmem(
bp->b_addr = NULL;
return -ENOMEM;
}
- bp->b_flags |= _XBF_KMEM;
+ xfs_buf_set_flags(bp, _XBF_KMEM);
trace_xfs_buf_backing_kmem(bp, _RET_IP_);
return 0;
}
@@ -282,15 +313,8 @@ xfs_buf_alloc(
* specifically set by later operations on the buffer.
*/
flags &= ~(XBF_TRYLOCK | XBF_ASYNC | XBF_READ_AHEAD);
-
- /*
- * A new buffer is held and locked by the owner. This ensures that the
- * buffer is owned by the caller and racing RCU lookups right after
- * inserting into the hash table are safe (and will have to wait for
- * the unlock to do anything non-trivial).
- */
lockref_init(&bp->b_lockref);
- sema_init(&bp->b_sema, 0); /* held, no waiters */
+ sema_init(&bp->b_sema, 1); /* unlocked */
atomic_set(&bp->b_lru_ref, 1);
init_completion(&bp->b_iowait);
INIT_LIST_HEAD(&bp->b_lru);
@@ -298,7 +322,7 @@ xfs_buf_alloc(
INIT_LIST_HEAD(&bp->b_li_list);
bp->b_target = target;
bp->b_mount = target->bt_mount;
- bp->b_flags = flags;
+ WRITE_ONCE(bp->b_flags, flags);
bp->b_rhash_key = map[0].bm_bn;
bp->b_length = 0;
bp->b_map_count = nmaps;
@@ -427,39 +451,31 @@ xfs_buf_find_lock(
return -ENOENT;
}
ASSERT((bp->b_flags & _XBF_DELWRI_Q) == 0);
- bp->b_flags &= _XBF_KMEM;
+ xfs_buf_clear_flags(bp, ~_XBF_KMEM);
bp->b_ops = NULL;
}
return 0;
}
-static inline int
+static inline struct xfs_buf *
xfs_buf_lookup(
struct xfs_buftarg *btp,
- struct xfs_buf_map *map,
- xfs_buf_flags_t flags,
- struct xfs_buf **bpp)
+ struct xfs_buf_map *map)
{
struct xfs_buf *bp;
- int error;
rcu_read_lock();
bp = rhashtable_lookup(&btp->bt_hash, map, xfs_buf_hash_params);
if (!bp || !lockref_get_not_dead(&bp->b_lockref)) {
rcu_read_unlock();
- return -ENOENT;
+ XFS_STATS_INC(btp->bt_mount, xb_miss_locked);
+ return NULL;
}
rcu_read_unlock();
- error = xfs_buf_find_lock(bp, flags);
- if (error) {
- xfs_buf_rele(bp);
- return error;
- }
-
- trace_xfs_buf_find(bp, flags, _RET_IP_);
- *bpp = bp;
- return 0;
+ trace_xfs_buf_find(bp, _RET_IP_);
+ XFS_STATS_INC(btp->bt_mount, xb_get_locked);
+ return bp;
}
/*
@@ -469,7 +485,6 @@ xfs_buf_lookup(
static int
xfs_buf_find_insert(
struct xfs_buftarg *btp,
- struct xfs_perag *pag,
struct xfs_buf_map *cmap,
struct xfs_buf_map *map,
int nmaps,
@@ -482,10 +497,13 @@ xfs_buf_find_insert(
error = xfs_buf_alloc(btp, map, nmaps, flags, &new_bp);
if (error)
- goto out_drop_pag;
+ return error;
/* The new buffer keeps the perag reference until it is freed. */
- new_bp->b_pag = pag;
+ if (!xfs_buftarg_is_mem(btp)) {
+ new_bp->b_pag = xfs_perag_get(btp->bt_mount,
+ xfs_daddr_to_agno(btp->bt_mount, cmap->bm_bn));
+ }
retry:
rcu_read_lock();
@@ -507,11 +525,7 @@ retry:
goto retry;
}
rcu_read_unlock();
- error = xfs_buf_find_lock(bp, flags);
- if (error)
- xfs_buf_rele(bp);
- else
- *bpp = bp;
+ *bpp = bp;
goto out_free_buf;
}
rcu_read_unlock();
@@ -520,39 +534,25 @@ retry:
return 0;
out_free_buf:
+ if (new_bp->b_pag)
+ xfs_perag_put(new_bp->b_pag);
xfs_buf_free(new_bp);
-out_drop_pag:
- if (pag)
- xfs_perag_put(pag);
return error;
}
-static inline struct xfs_perag *
-xfs_buftarg_get_pag(
- struct xfs_buftarg *btp,
- const struct xfs_buf_map *map)
-{
- struct xfs_mount *mp = btp->bt_mount;
-
- if (xfs_buftarg_is_mem(btp))
- return NULL;
- return xfs_perag_get(mp, xfs_daddr_to_agno(mp, map->bm_bn));
-}
-
/*
* Assembles a buffer covering the specified range. The code is optimised for
* cache hits, as metadata intensive workloads will see 3 orders of magnitude
* more hits than misses.
*/
-int
-xfs_buf_get_map(
+static int
+xfs_find_get_buf(
struct xfs_buftarg *btp,
struct xfs_buf_map *map,
int nmaps,
xfs_buf_flags_t flags,
struct xfs_buf **bpp)
{
- struct xfs_perag *pag;
struct xfs_buf *bp = NULL;
struct xfs_buf_map cmap = { .bm_bn = map[0].bm_bn };
int error;
@@ -567,46 +567,50 @@ xfs_buf_get_map(
if (error)
return error;
- pag = xfs_buftarg_get_pag(btp, &cmap);
-
- error = xfs_buf_lookup(btp, &cmap, flags, &bp);
- if (error && error != -ENOENT)
- goto out_put_perag;
-
/* cache hits always outnumber misses by at least 10:1 */
+ bp = xfs_buf_lookup(btp, &cmap);
if (unlikely(!bp)) {
- XFS_STATS_INC(btp->bt_mount, xb_miss_locked);
-
if (flags & XBF_INCORE)
- goto out_put_perag;
-
- /* xfs_buf_find_insert() consumes the perag reference. */
- error = xfs_buf_find_insert(btp, pag, &cmap, map, nmaps,
- flags, &bp);
+ return -ENOENT;
+ error = xfs_buf_find_insert(btp, &cmap, map, nmaps, flags, &bp);
if (error)
return error;
- } else {
- XFS_STATS_INC(btp->bt_mount, xb_get_locked);
- if (pag)
- xfs_perag_put(pag);
}
+ *bpp = bp;
+ return 0;
+}
+
+int
+xfs_buf_get_map(
+ struct xfs_buftarg *btp,
+ struct xfs_buf_map *map,
+ int nmaps,
+ xfs_buf_flags_t flags,
+ struct xfs_buf **bpp)
+{
+ int error;
+
+ ASSERT(!(flags & ~(XBF_TRYLOCK | XBF_INCORE | XBF_LIVESCAN)));
+ ASSERT(!(flags & XBF_LIVESCAN) || (flags & XBF_INCORE));
+
/*
- * Clear b_error if this is a lookup from a caller that doesn't expect
- * valid data to be found in the buffer.
+ * Zero the buffer and clear b_error as xfs_buf_get_map callers don't
+ * expect valid data to be found in the buffer.
*/
- if (!(flags & XBF_READ))
- xfs_buf_ioerror(bp, 0);
+ error = xfs_find_get_buf(btp, map, nmaps, flags, bpp);
+ if (error)
+ return error;
+ error = xfs_buf_find_lock(*bpp, flags);
+ if (error) {
+ xfs_buf_rele(*bpp);
+ return error;
+ }
XFS_STATS_INC(btp->bt_mount, xb_get);
- trace_xfs_buf_get(bp, flags, _RET_IP_);
- *bpp = bp;
+ trace_xfs_buf_get(*bpp, flags, _RET_IP_);
+ xfs_buf_ioerror(*bpp, 0);
return 0;
-
-out_put_perag:
- if (pag)
- xfs_perag_put(pag);
- return error;
}
int
@@ -615,47 +619,13 @@ _xfs_buf_read(
{
ASSERT(bp->b_maps[0].bm_bn != XFS_BUF_DADDR_NULL);
- bp->b_flags &= ~(XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD | XBF_DONE);
- bp->b_flags |= XBF_READ;
+ xfs_buf_clear_flags(bp, XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD |
+ XBF_DONE);
+ xfs_buf_set_flags(bp, XBF_READ);
xfs_buf_submit(bp);
return xfs_buf_iowait(bp);
}
-/*
- * Reverify a buffer found in cache without an attached ->b_ops.
- *
- * If the caller passed an ops structure and the buffer doesn't have ops
- * assigned, set the ops and use it to verify the contents. If verification
- * fails, clear XBF_DONE. We assume the buffer has no recorded errors and is
- * already in XBF_DONE state on entry.
- *
- * Under normal operations, every in-core buffer is verified on read I/O
- * completion. There are two scenarios that can lead to in-core buffers without
- * an assigned ->b_ops. The first is during log recovery of buffers on a V4
- * filesystem, though these buffers are purged at the end of recovery. The
- * other is online repair, which intentionally reads with a NULL buffer ops to
- * run several verifiers across an in-core buffer in order to establish buffer
- * type. If repair can't establish that, the buffer will be left in memory
- * with NULL buffer ops.
- */
-static int
-xfs_buf_reverify(
- struct xfs_buf *bp,
- const struct xfs_buf_ops *ops)
-{
- ASSERT(bp->b_flags & XBF_DONE);
- ASSERT(bp->b_error == 0);
-
- if (!ops || bp->b_ops)
- return 0;
-
- bp->b_ops = ops;
- bp->b_ops->verify_read(bp);
- if (bp->b_error)
- bp->b_flags &= ~XBF_DONE;
- return bp->b_error;
-}
-
int
xfs_buf_read_map(
struct xfs_buftarg *target,
@@ -669,31 +639,81 @@ xfs_buf_read_map(
struct xfs_buf *bp;
int error;
- ASSERT(!(flags & (XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD)));
+ ASSERT(!(flags & ~XBF_TRYLOCK));
flags |= XBF_READ;
*bpp = NULL;
- error = xfs_buf_get_map(target, map, nmaps, flags, &bp);
+ error = xfs_find_get_buf(target, map, nmaps, flags, &bp);
if (error)
return error;
+ error = xfs_buf_find_lock(bp, flags);
+ if (error) {
+ xfs_buf_rele(bp);
+ return error;
+ }
trace_xfs_buf_read(bp, flags, _RET_IP_);
- if (!(bp->b_flags & XBF_DONE)) {
+ if (bp->b_flags & XBF_DONE) {
+ ASSERT(bp->b_error == 0);
+
+ /*
+ * If the caller passed an ops structure and the buffer doesn't
+ * have ops assigned yet, set the ops and use them to verify the
+ * buffer contents.
+ *
+ * Under normal operations, every in-core buffer is verified on
+ * read I/O completion, but there are two scenarios that can
+ * lead to in-core buffers without an assigned ->b_ops:
+ *
+ * 1) During log recovery of buffers on a V4 filesystem.
+ * These buffers are purged at the end of recovery, though.
+ * 2) Oonline repair intentionally reads with a NULL buffer
+ * ops to run several verifiers across an in-core buffer in
+ * order to establish buffer type. If repair can't
+ * establish that, the buffer will be left in memory with
+ * NULL buffer ops.
+ */
+ if (ops && !bp->b_ops) {
+ bp->b_ops = ops;
+ bp->b_ops->verify_read(bp);
+ /*
+ * If verification failed, clear XBF_DONE as we assume
+ * that buffers have no recorded errors when in XBF_DONE
+ * state.
+ */
+ error = bp->b_error;
+ if (error)
+ xfs_buf_clear_flags(bp, XBF_DONE);
+ }
+
+ /* We do not want read in the flags */
+ xfs_buf_clear_flags(bp, XBF_READ);
+ } else {
/* Initiate the buffer read and wait. */
XFS_STATS_INC(target->bt_mount, xb_get_read);
bp->b_ops = ops;
error = _xfs_buf_read(bp);
- } else {
- /* Buffer already read; all we need to do is check it. */
- error = xfs_buf_reverify(bp, ops);
-
- /* We do not want read in the flags */
- bp->b_flags &= ~XBF_READ;
- ASSERT(bp->b_ops != NULL || ops == NULL);
}
+ if (error)
+ goto out_ioerror;
+
+ *bpp = bp;
+ return 0;
+
+out_ioerror:
+ /*
+ * Check against log shutdown for error reporting because metadata
+ * writeback may require a read first and we need to report errors in
+ * metadata writeback until the log is shut down. High level
+ * transaction read functions already check against mount shutdown, so
+ * we only need to be concerned about low level/ IO interactions here.
+ */
+ if (!xlog_is_shutdown(target->bt_mount->m_log))
+ xfs_buf_ioerror_alert(bp, fa);
+
/*
* If we've had a read error, then the contents of the buffer are
* invalid and should not be used. To ensure that a followup read tries
@@ -703,30 +723,14 @@ xfs_buf_read_map(
* future cache lookups will also treat it as an empty, uninitialised
* buffer.
*/
- if (error) {
- /*
- * Check against log shutdown for error reporting because
- * metadata writeback may require a read first and we need to
- * report errors in metadata writeback until the log is shut
- * down. High level transaction read functions already check
- * against mount shutdown, anyway, so we only need to be
- * concerned about low level IO interactions here.
- */
- if (!xlog_is_shutdown(target->bt_mount->m_log))
- xfs_buf_ioerror_alert(bp, fa);
-
- bp->b_flags &= ~XBF_DONE;
- xfs_buf_stale(bp);
- xfs_buf_relse(bp);
-
- /* bad CRC means corrupted metadata */
- if (error == -EFSBADCRC)
- error = -EFSCORRUPTED;
- return error;
- }
+ xfs_buf_clear_flags(bp, XBF_DONE);
+ xfs_buf_stale(bp);
+ xfs_buf_relse(bp);
- *bpp = bp;
- return 0;
+ /* bad CRC means corrupted metadata */
+ if (error == -EFSBADCRC)
+ return -EFSCORRUPTED;
+ return error;
}
/*
@@ -750,21 +754,36 @@ xfs_buf_readahead_map(
if (xfs_buftarg_is_mem(target))
return;
- if (xfs_buf_get_map(target, map, nmaps, flags | XBF_TRYLOCK, &bp))
+ if (xfs_find_get_buf(target, map, nmaps, flags, &bp))
return;
- trace_xfs_buf_readahead(bp, 0, _RET_IP_);
- if (bp->b_flags & XBF_DONE) {
- xfs_buf_reverify(bp, ops);
- xfs_buf_relse(bp);
- return;
- }
+ /*
+ * Do a lockless fast path check for a valid uptodate buffer and avoid
+ * locking entirely in this case.
+ */
+ if ((READ_ONCE(bp->b_flags) & (XBF_DONE | XBF_STALE)) == XBF_DONE)
+ goto out_rele;
+
+ /* Otherwise lock the buffer to stabilize the state */
+ if (!xfs_buf_trylock(bp))
+ goto out_rele;
+
+ /* Let the actual reader deal with stale buffers. */
+ if (bp->b_flags & (XBF_STALE | XBF_DONE))
+ goto out_unlock;
+
+ trace_xfs_buf_readahead(bp, 0, _RET_IP_);
XFS_STATS_INC(target->bt_mount, xb_get_read);
bp->b_ops = ops;
- bp->b_flags &= ~(XBF_WRITE | XBF_DONE);
- bp->b_flags |= flags;
+ xfs_buf_clear_flags(bp, XBF_WRITE | XBF_DONE);
+ xfs_buf_set_flags(bp, flags);
percpu_counter_inc(&target->bt_readahead_count);
xfs_buf_submit(bp);
+ return;
+out_unlock:
+ xfs_buf_unlock(bp);
+out_rele:
+ xfs_buf_rele(bp);
}
/*
@@ -794,7 +813,7 @@ xfs_buf_read_uncached(
ASSERT(bp->b_map_count == 1);
bp->b_rhash_key = XFS_BUF_DADDR_NULL;
bp->b_maps[0].bm_bn = daddr;
- bp->b_flags |= XBF_READ;
+ xfs_buf_set_flags(bp, XBF_READ);
bp->b_ops = ops;
xfs_buf_submit(bp);
@@ -818,9 +837,11 @@ xfs_buf_get_uncached(
DEFINE_SINGLE_BUF_MAP(map, XFS_BUF_DADDR_NULL, numblks);
error = xfs_buf_alloc(target, &map, 1, 0, bpp);
- if (!error)
- trace_xfs_buf_get_uncached(*bpp, _RET_IP_);
- return error;
+ if (error)
+ return error;
+ xfs_buf_lock(*bpp);
+ trace_xfs_buf_get_uncached(*bpp, _RET_IP_);
+ return 0;
}
/*
@@ -1042,7 +1063,7 @@ xfs_buf_ioend_handle_error(
* We're not going to bother about retrying this during recovery.
* One strike!
*/
- if (bp->b_flags & _XBF_LOGRECOVERY) {
+ if (mp->m_log && xlog_in_recovery(mp->m_log)) {
xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR);
return false;
}
@@ -1086,14 +1107,14 @@ xfs_buf_ioend_handle_error(
resubmit:
xfs_buf_ioerror(bp, 0);
- bp->b_flags |= (XBF_DONE | XBF_WRITE_FAIL);
+ xfs_buf_set_flags(bp, XBF_DONE | XBF_WRITE_FAIL);
reinit_completion(&bp->b_iowait);
xfs_buf_submit(bp);
return true;
out_stale:
xfs_buf_stale(bp);
- bp->b_flags |= XBF_DONE;
- bp->b_flags &= ~XBF_WRITE;
+ xfs_buf_set_flags(bp, XBF_DONE);
+ xfs_buf_clear_flags(bp, XBF_WRITE);
trace_xfs_buf_error_relse(bp, _RET_IP_);
return false;
}
@@ -1118,7 +1139,7 @@ xfs_buf_ioend(
if (!bp->b_error && bp->b_ops)
bp->b_ops->verify_read(bp);
if (!bp->b_error)
- bp->b_flags |= XBF_DONE;
+ xfs_buf_set_flags(bp, XBF_DONE);
if (bp->b_flags & XBF_READ_AHEAD)
percpu_counter_dec(&bp->b_target->bt_readahead_count);
} else {
@@ -1128,8 +1149,8 @@ xfs_buf_ioend(
return;
}
} else {
- bp->b_flags &= ~XBF_WRITE_FAIL;
- bp->b_flags |= XBF_DONE;
+ xfs_buf_clear_flags(bp, XBF_WRITE_FAIL);
+ xfs_buf_set_flags(bp, XBF_DONE);
}
/* clear the retry state */
@@ -1149,8 +1170,7 @@ xfs_buf_ioend(
bp->b_iodone(bp);
}
- bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD |
- _XBF_LOGRECOVERY);
+ xfs_buf_clear_flags(bp, XBF_READ | XBF_WRITE | XBF_READ_AHEAD);
if (async)
xfs_buf_relse(bp);
}
@@ -1196,8 +1216,8 @@ xfs_buf_fail(
{
ASSERT(xfs_buf_islocked(bp));
- bp->b_flags |= XBF_ASYNC;
- bp->b_flags &= ~XBF_DONE;
+ xfs_buf_set_flags(bp, XBF_ASYNC);
+ xfs_buf_clear_flags(bp, XBF_DONE);
xfs_buf_stale(bp);
xfs_buf_ioerror(bp, -EIO);
xfs_buf_ioend(bp);
@@ -1211,9 +1231,9 @@ xfs_bwrite(
ASSERT(xfs_buf_islocked(bp));
- bp->b_flags |= XBF_WRITE;
- bp->b_flags &= ~(XBF_ASYNC | XBF_READ | _XBF_DELWRI_Q |
- XBF_DONE);
+ xfs_buf_set_flags(bp, XBF_WRITE);
+ xfs_buf_clear_flags(bp, XBF_ASYNC | XBF_READ | _XBF_DELWRI_Q |
+ XBF_DONE);
xfs_buf_submit(bp);
error = xfs_buf_iowait(bp);
@@ -1404,7 +1424,7 @@ xfs_buf_submit(
return;
ioerror:
- bp->b_flags &= ~XBF_DONE;
+ xfs_buf_clear_flags(bp, XBF_DONE);
xfs_buf_stale(bp);
end_io:
if (bp->b_flags & XBF_ASYNC)
@@ -1815,7 +1835,7 @@ xfs_buf_delwri_cancel(
bp = list_first_entry(list, struct xfs_buf, b_list);
xfs_buf_lock(bp);
- bp->b_flags &= ~_XBF_DELWRI_Q;
+ xfs_buf_clear_flags(bp, _XBF_DELWRI_Q);
xfs_buf_list_del(bp);
xfs_buf_relse(bp);
}
@@ -1860,7 +1880,7 @@ xfs_buf_delwri_queue(
* might get readded to a delwri list after the synchronous writeout, in
* which case we need just need to re-add the flag here.
*/
- bp->b_flags |= _XBF_DELWRI_Q;
+ xfs_buf_set_flags(bp, _XBF_DELWRI_Q);
if (list_empty(&bp->b_list)) {
xfs_buf_hold(bp);
list_add_tail(&bp->b_list, list);
@@ -1937,8 +1957,8 @@ xfs_buf_delwri_submit_prep(
}
trace_xfs_buf_delwri_split(bp, _RET_IP_);
- bp->b_flags &= ~_XBF_DELWRI_Q;
- bp->b_flags |= XBF_WRITE;
+ xfs_buf_clear_flags(bp, _XBF_DELWRI_Q);
+ xfs_buf_set_flags(bp, XBF_WRITE);
return true;
}
@@ -1979,7 +1999,7 @@ xfs_buf_delwri_submit_nowait(
}
if (!xfs_buf_delwri_submit_prep(bp))
continue;
- bp->b_flags |= XBF_ASYNC;
+ xfs_buf_set_flags(bp, XBF_ASYNC);
xfs_buf_list_del(bp);
xfs_buf_submit(bp);
}
@@ -2012,7 +2032,7 @@ xfs_buf_delwri_submit(
xfs_buf_lock(bp);
if (!xfs_buf_delwri_submit_prep(bp))
continue;
- bp->b_flags &= ~XBF_ASYNC;
+ xfs_buf_clear_flags(bp, XBF_ASYNC);
list_move_tail(&bp->b_list, &wait_list);
xfs_buf_submit(bp);
}
diff --git a/fs/xfs/xfs_buf.h b/fs/xfs/xfs_buf.h
index 79cc9c3f0254..a4729253b56f 100644
--- a/fs/xfs/xfs_buf.h
+++ b/fs/xfs/xfs_buf.h
@@ -34,9 +34,6 @@ struct xfs_buf;
#define XBF_STALE (1u << 6) /* buffer has been staled, do not find it */
#define XBF_WRITE_FAIL (1u << 7) /* async writes have failed on this buffer */
-/* buffer type flags for write callbacks */
-#define _XBF_LOGRECOVERY (1u << 18)/* log recovery buffer */
-
/* flags used only internally */
#define _XBF_KMEM (1u << 21)/* backed by heap memory */
#define _XBF_DELWRI_Q (1u << 22)/* buffer on a delwri queue */
@@ -61,7 +58,6 @@ typedef unsigned int xfs_buf_flags_t;
{ XBF_DONE, "DONE" }, \
{ XBF_STALE, "STALE" }, \
{ XBF_WRITE_FAIL, "WRITE_FAIL" }, \
- { _XBF_LOGRECOVERY, "LOG_RECOVERY" }, \
{ _XBF_KMEM, "KMEM" }, \
{ _XBF_DELWRI_Q, "DELWRI_Q" }, \
/* The following interface flags should never be set */ \
@@ -305,7 +301,9 @@ static inline void xfs_buf_zero(struct xfs_buf *bp, size_t boff, size_t bsize)
memset(bp->b_addr + boff, 0, bsize);
}
-extern void xfs_buf_stale(struct xfs_buf *bp);
+void xfs_buf_set_uptodate(struct xfs_buf *bp);
+void xfs_buf_stale(struct xfs_buf *bp);
+void xfs_buf_clear_stale(struct xfs_buf *bp);
/* Delayed Write Buffer Routines */
extern void xfs_buf_delwri_cancel(struct list_head *);
diff --git a/fs/xfs/xfs_buf_item.c b/fs/xfs/xfs_buf_item.c
index 1f055cd6732e..1a4ef34af8d5 100644
--- a/fs/xfs/xfs_buf_item.c
+++ b/fs/xfs/xfs_buf_item.c
@@ -1066,6 +1066,8 @@ void
xfs_buf_item_done(
struct xfs_buf *bp)
{
+ struct xfs_buf_log_item *bip = bp->b_log_item;
+
/*
* If we are forcibly shutting down, this may well be off the AIL
* already. That's because we simulate the log-committed callbacks to
@@ -1078,8 +1080,8 @@ xfs_buf_item_done(
* Note that log recovery writes might have buffer items that are not on
* the AIL even when the file system is not shut down.
*/
- xfs_trans_ail_delete(&bp->b_log_item->bli_item,
- (bp->b_flags & _XBF_LOGRECOVERY) ? 0 :
- SHUTDOWN_CORRUPT_INCORE);
- xfs_buf_item_relse(bp->b_log_item);
+ xfs_trans_ail_delete(&bip->bli_item,
+ xlog_in_recovery(bip->bli_item.li_log) ?
+ 0 : SHUTDOWN_CORRUPT_INCORE);
+ xfs_buf_item_relse(bip);
}
diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c
index 240deb3f7827..57929f115055 100644
--- a/fs/xfs/xfs_buf_item_recover.c
+++ b/fs/xfs/xfs_buf_item_recover.c
@@ -448,7 +448,6 @@ xlog_recover_validate_buf_type(
if (bp->b_ops) {
struct xfs_buf_log_item *bip;
- bp->b_flags |= _XBF_LOGRECOVERY;
xfs_buf_item_init(bp, mp);
bip = bp->b_log_item;
bip->bli_item.li_lsn = current_lsn;
@@ -1122,7 +1121,6 @@ xlog_recover_buf_commit_pass2(
xfs_buf_lock(rtsb_bp);
xfs_buf_hold(rtsb_bp);
xfs_update_rtsb(rtsb_bp, bp);
- rtsb_bp->b_flags |= _XBF_LOGRECOVERY;
xfs_buf_delwri_queue(rtsb_bp, buffer_list);
xfs_buf_relse(rtsb_bp);
}
@@ -1164,7 +1162,6 @@ out_writebuf:
error = xfs_bwrite(bp);
} else {
ASSERT(bp->b_mount == mp);
- bp->b_flags |= _XBF_LOGRECOVERY;
xfs_buf_delwri_queue(bp, buffer_list);
}
diff --git a/fs/xfs/xfs_dquot_item_recover.c b/fs/xfs/xfs_dquot_item_recover.c
index 63bc9ab7d947..b8854316ac75 100644
--- a/fs/xfs/xfs_dquot_item_recover.c
+++ b/fs/xfs/xfs_dquot_item_recover.c
@@ -168,7 +168,6 @@ xlog_recover_dquot_commit_pass2(
ASSERT(dq_f->qlf_size == 2);
ASSERT(bp->b_mount == mp);
- bp->b_flags |= _XBF_LOGRECOVERY;
xfs_buf_delwri_queue(bp, buffer_list);
out_release:
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 768cabf6250b..7bff07e31cbd 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -166,17 +166,23 @@ xfs_file_fsync(
}
/*
- * If we only have a single device, and the log force about was
- * a no-op we might have to flush the data device cache here.
- * This can only happen for fdatasync/O_DSYNC if we were overwriting
- * an already allocated file and thus do not have any metadata to
- * commit.
+ * If the log force was a no-op, we may still need to flush the
+ * file data target cache here. This can happen for fdatasync/O_DSYNC
+ * when no metadata needed to be committed.
+ *
+ * Use the inode's actual file data target rather than assuming the
+ * main data device. Realtime inodes with a separate realtime device
+ * are flushed before the log force, so this fallback only applies
+ * when the file data target is the same as the log target.
*/
- if (!log_flushed && !XFS_IS_REALTIME_INODE(ip) &&
- mp->m_logdev_targp == mp->m_ddev_targp) {
- err2 = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev);
- if (err2 && !error)
- error = err2;
+ if (!log_flushed) {
+ struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip);
+
+ if (mp->m_logdev_targp == file_targp) {
+ err2 = blkdev_issue_flush(file_targp->bt_bdev);
+ if (err2 && !error)
+ error = err2;
+ }
}
return error;
@@ -1370,6 +1376,84 @@ xfs_falloc_force_zero(
return XFS_TEST_ERROR(ip->i_mount, XFS_ERRTAG_FORCE_ZERO_RANGE);
}
+static int
+xfs_falloc_write_zeroes(
+ struct file *file,
+ int mode,
+ loff_t offset,
+ loff_t len,
+ struct xfs_zone_alloc_ctx *ac)
+{
+ struct inode *inode = file_inode(file);
+ struct xfs_inode *ip = XFS_I(inode);
+ loff_t new_size = 0;
+ int error;
+
+ /*
+ * XXX: There is an issue with bigrtalloc inodes where there can be blocks
+ * that are written after the EOF block. This breaks the promise of no
+ * written blocks past EOF. Return EOPNOTSUPP until it is fixed.
+ */
+ if (xfs_is_always_cow_inode(ip) || xfs_inode_has_bigrtalloc(ip) ||
+ !bdev_write_zeroes_unmap_sectors(xfs_inode_buftarg(ip)->bt_bdev))
+ return -EOPNOTSUPP;
+
+ error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
+ if (error)
+ return error;
+
+ /*
+ *
+ * |----------|----------|----------|----------|----------|
+ * ^ ^ ^ ^ ^ ^
+ * | | | | | |
+ * | offset | | end |
+ * | | | |
+ * offset_rd offset_ru end_rd end_ru
+ *
+ * xfs_free_file_space() punches the aligned interior offset_ru -> end_rd
+ * to holes and byte-zeroes the in-range parts of the partial edge blocks,
+ * offset -> offset_ru and end_rd -> end. xfs_zero_range() only touches
+ * already-written blocks here; it skips holes and unwritten extents, so
+ * unallocated/unwritten edge blocks are left for the allocation below.
+ */
+ error = xfs_free_file_space(ip, offset, len, ac);
+ if (error)
+ return error;
+
+ /*
+ * Publish the new size while the punched range is still a hole, then
+ * fill it with written zeroes. Like the other fallocate modes we use
+ * xfs_falloc_setsize(), but it must run *before* we convert the range
+ * to written extents: xfs_setattr_size() zeroes [old EOF, new size) via
+ * xfs_zero_range(), which skips holes, so there is nothing to re-zero.
+ * It will also writeback partial EOF block before the on-disk size is
+ * logged.
+ * Note: extending the size before allocating means a failure below
+ * leaves the file larger with unallocated holes in the new range.
+ * That is safe as holes within i_size read back as zeroes and expose
+ * no stale data while the error is propagated to the caller.
+ */
+ error = xfs_falloc_setsize(file, new_size);
+ if (error)
+ return error;
+
+ /*
+ * Allocate written, zeroed extents across the range. xfs_alloc_file_space()
+ * rounds outward to block granularity:
+ * - holes (the punched interior and any unallocated edge block) are
+ * allocated and zeroed;
+ * - unwritten extents (including unwritten edge blocks) are converted to
+ * written and zeroed;
+ * - Already written edge blocks are skipped. The out-of-range bytes of
+ * a written edge block keep their data (offset_rd -> offset and
+ * end -> end_rd); their in-range bytes (offset -> offset_ru and
+ * end_ru -> end were already zeroed by xfs_free_file_space().
+ */
+ return xfs_alloc_file_space(ip, offset, len,
+ XFS_ALLOC_FILE_SPACE_WRITE_ZEROES);
+}
+
/*
* Punch a hole and prealloc the range. We use a hole punch rather than
* unwritten extent conversion for two reasons:
@@ -1408,7 +1492,8 @@ xfs_falloc_zero_range(
len = round_up(offset + len, blksize) -
round_down(offset, blksize);
offset = round_down(offset, blksize);
- error = xfs_alloc_file_space(ip, offset, len);
+ error = xfs_alloc_file_space(ip, offset, len,
+ XFS_ALLOC_FILE_SPACE_PREALLOC);
}
if (error)
return error;
@@ -1434,7 +1519,8 @@ xfs_falloc_unshare_range(
if (error)
return error;
- error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+ error = xfs_alloc_file_space(XFS_I(inode), offset, len,
+ XFS_ALLOC_FILE_SPACE_PREALLOC);
if (error)
return error;
return xfs_falloc_setsize(file, new_size);
@@ -1462,7 +1548,8 @@ xfs_falloc_allocate_range(
if (error)
return error;
- error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+ error = xfs_alloc_file_space(XFS_I(inode), offset, len,
+ XFS_ALLOC_FILE_SPACE_PREALLOC);
if (error)
return error;
return xfs_falloc_setsize(file, new_size);
@@ -1472,7 +1559,7 @@ xfs_falloc_allocate_range(
(FALLOC_FL_ALLOCATE_RANGE | FALLOC_FL_KEEP_SIZE | \
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | \
FALLOC_FL_ZERO_RANGE | FALLOC_FL_INSERT_RANGE | \
- FALLOC_FL_UNSHARE_RANGE)
+ FALLOC_FL_UNSHARE_RANGE | FALLOC_FL_WRITE_ZEROES)
STATIC long
__xfs_file_fallocate(
@@ -1524,6 +1611,9 @@ __xfs_file_fallocate(
case FALLOC_FL_ALLOCATE_RANGE:
error = xfs_falloc_allocate_range(file, mode, offset, len);
break;
+ case FALLOC_FL_WRITE_ZEROES:
+ error = xfs_falloc_write_zeroes(file, mode, offset, len, ac);
+ break;
default:
error = -EOPNOTSUPP;
break;
diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
index 67624a804a7f..21114bb6d4ff 100644
--- a/fs/xfs/xfs_fsops.c
+++ b/fs/xfs/xfs_fsops.c
@@ -501,7 +501,7 @@ xfs_do_force_shutdown(
return;
}
if (mp->m_sb_bp)
- mp->m_sb_bp->b_flags |= XBF_DONE;
+ xfs_buf_set_uptodate(mp->m_sb_bp);
if (flags & SHUTDOWN_FORCE_UMOUNT)
xfs_alert(mp, "User initiated shutdown received.");
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 15279d22a894..030a7c8f2c12 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -1753,7 +1753,7 @@ xfs_ifree_cluster(
* attachment may occur in xfs_inode_item_precommit() after we
* have marked this buffer stale. If this buffer was not in
* memory before xfs_ifree_cluster() started, it will not be
- * marked XBF_DONE and this will cause problems later in
+ * marked uptodate and this will cause problems later in
* xfs_inode_item_precommit() when we trip over a (stale, !done)
* buffer to attached to the transaction.
*
@@ -1766,7 +1766,7 @@ xfs_ifree_cluster(
* fail. We can acheive this by adding a write verifier to the
* buffer.
*/
- bp->b_flags |= XBF_DONE;
+ xfs_buf_set_uptodate(bp);
bp->b_ops = &xfs_inode_buf_ops;
/*
diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recover.c
index 169a8fe3bf0a..1d2319ad15c5 100644
--- a/fs/xfs/xfs_inode_item_recover.c
+++ b/fs/xfs/xfs_inode_item_recover.c
@@ -586,7 +586,6 @@ out_owner_change:
}
ASSERT(bp->b_mount == mp);
- bp->b_flags |= _XBF_LOGRECOVERY;
xfs_buf_delwri_queue(bp, buffer_list);
out_release:
diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
index fdb011e6ef60..e7e49529658b 100644
--- a/fs/xfs/xfs_log_recover.c
+++ b/fs/xfs/xfs_log_recover.c
@@ -3279,9 +3279,8 @@ xlog_do_recovery_pass(
* checkpoints at this start LSN.
*
* Note: Shutting down the filesystem will result in the
- * delwri submission marking all the buffers stale,
- * completing them and cleaning up _XBF_LOGRECOVERY
- * state without doing any IO.
+ * delwri submission marking all the buffers stale and
+ * completing them without doing any IO.
*/
xlog_force_shutdown(log, SHUTDOWN_LOG_IO_ERROR);
}
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index aeb89ac53bf1..f333c938fbd9 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -792,6 +792,7 @@ DEFINE_BUF_EVENT(xfs_buf_backing_folio);
DEFINE_BUF_EVENT(xfs_buf_backing_kmem);
DEFINE_BUF_EVENT(xfs_buf_backing_vmalloc);
DEFINE_BUF_EVENT(xfs_buf_backing_fallback);
+DEFINE_BUF_EVENT(xfs_buf_find);
/* not really buffer traces, but the buf provides useful information */
DEFINE_BUF_EVENT(xfs_btree_corrupt);
@@ -837,7 +838,6 @@ DECLARE_EVENT_CLASS(xfs_buf_flags_class,
DEFINE_EVENT(xfs_buf_flags_class, name, \
TP_PROTO(struct xfs_buf *bp, unsigned flags, unsigned long caller_ip), \
TP_ARGS(bp, flags, caller_ip))
-DEFINE_BUF_FLAGS_EVENT(xfs_buf_find);
DEFINE_BUF_FLAGS_EVENT(xfs_buf_get);
DEFINE_BUF_FLAGS_EVENT(xfs_buf_read);
DEFINE_BUF_FLAGS_EVENT(xfs_buf_readahead);
diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c
index 7bfbd9f6f0df..1b36cf12d4e3 100644
--- a/fs/xfs/xfs_trans.c
+++ b/fs/xfs/xfs_trans.c
@@ -1029,6 +1029,15 @@ xfs_trans_roll(
* duplicate transaction that gets returned.
*/
error = __xfs_trans_commit(tp, true);
+
+ tp = *tpp;
+ /*
+ * __xfs_trans_commit cleared the NOFS flag by calling into
+ * xfs_trans_free. Set it again here before doing memory
+ * allocations.
+ */
+ xfs_trans_set_context(tp);
+
if (error)
return error;
@@ -1040,13 +1049,6 @@ xfs_trans_roll(
* either nothing be locked across this call, or that anything that is
* locked be logged in the prior and the next transactions.
*/
- tp = *tpp;
- /*
- * __xfs_trans_commit cleared the NOFS flag by calling into
- * xfs_trans_free. Set it again here before doing memory
- * allocations.
- */
- xfs_trans_set_context(tp);
error = xfs_log_regrant(tp->t_mountp, tp->t_ticket);
if (error)
return error;
diff --git a/fs/xfs/xfs_trans_buf.c b/fs/xfs/xfs_trans_buf.c
index 7e17b93fe9ad..1e025848811a 100644
--- a/fs/xfs/xfs_trans_buf.c
+++ b/fs/xfs/xfs_trans_buf.c
@@ -140,7 +140,7 @@ xfs_trans_get_buf_map(
ASSERT(xfs_buf_islocked(bp));
if (xfs_is_shutdown(tp->t_mountp)) {
xfs_buf_stale(bp);
- bp->b_flags |= XBF_DONE;
+ xfs_buf_set_uptodate(bp);
}
ASSERT(bp->b_transp == tp);
@@ -482,7 +482,7 @@ xfs_trans_dirty_buf(
* item from the AIL and free it when the buffer is flushed
* to disk.
*/
- bp->b_flags |= XBF_DONE;
+ xfs_buf_set_uptodate(bp);
ASSERT(atomic_read(&bip->bli_refcount) > 0);
@@ -494,8 +494,7 @@ xfs_trans_dirty_buf(
*/
if (bip->bli_flags & XFS_BLI_STALE) {
bip->bli_flags &= ~XFS_BLI_STALE;
- ASSERT(bp->b_flags & XBF_STALE);
- bp->b_flags &= ~XBF_STALE;
+ xfs_buf_clear_stale(bp);
bip->__bli_format.blf_flags &= ~XFS_BLF_CANCEL;
}
bip->bli_flags |= XFS_BLI_DIRTY | XFS_BLI_LOGGED;
diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c
index 7ab8f2218c6a..d0b85179a3d2 100644
--- a/fs/xfs/xfs_zone_gc.c
+++ b/fs/xfs/xfs_zone_gc.c
@@ -812,7 +812,7 @@ xfs_zone_gc_split_write(
split_sectors = bio_split_rw_at(&chunk->bio, lim, &nsegs,
lim->max_zone_append_sectors << SECTOR_SHIFT);
- if (!split_sectors)
+ if (split_sectors <= 0)
return NULL;
/* ensure the split chunk is still block size aligned */