summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 16:41:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 16:41:57 -0700
commitcf07e82984895a06a7cbfadee1b13d83805bb41b (patch)
tree3d8406033a3e3c9eee42cbc9093d753bcc56dde9
parentff68e5f557f69a08fdcfa4ce8b1b809d63bd4f45 (diff)
parentb7eea80be25f3334f131d52982b3131aba77b97d (diff)
Merge tag 'xfs-merge-7.3' of git://git.kernel.org:/pub/scm/fs/xfs/xfs-linux
Pull xfs updates from Carlos Maiolino: "There are no big standing out features on this window, so this mostly consists on bug fixes and code refactoring. The only user visible change that stands out is the support for FALLOC_FL_WRITE_ZEROES added to this" * tag 'xfs-merge-7.3' of git://git.kernel.org:/pub/scm/fs/xfs/xfs-linux: (23 commits) xfs: validate attr entry pointer before field access xfs: check split_sectors validity before bio_split call xfs: use file target for post-log fsync fallback flush xfs: restore nofs context unconditionally in xfs_trans_roll xfs: add lockless xfs_buf_readahead_map fast path xfs: move buffer locking out of xfs_find_get_buf xfs: merge xfs_buf_reverify into xfs_buf_read_map xfs: use goto based error unwinding in xfs_buf_read_map xfs: don't reverify buffers in xfs_buf_readahead_map xfs: use WRITE_ONCE to update b_flags xfs: hide b_flags manipulation from code outside of xfs_buf.c xfs: remove _XBF_LOGRECOVERY xfs: remove spurious XBF_DONE clearing on readahead validation failure xfs: split out a lower-level xfs_buf_get_map helper from xfs_find_get_buf xfs: consolidate buffer locking in xfs_buf_get_map xfs: don't get a pag reference in xfs_buf_get_map xfs: use kmalloc_objs() instead of kmalloc() in xfs_da_grow_inode_int xfs: mark internal metadir file creation helpers static xfs: create rtgroup metadir inodes using xfs_metadir_create_file xfs: create quota metadir inodes using xfs_metadir_create_file ...
-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 */