AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/17 16:06 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "XFS_FS"
  ],
  "FocusSymbols": [
    "xfs_ioc_write_stream_open",
    "xfs_ioc_write_stream_set",
    "xfs_ioc_write_stream_get",
    "xfs_ioc_write_stream_get_max",
    "xfs_inode_write_stream_open",
    "xfs_inode_set_write_stream",
    "xfs_inode_get_write_stream",
    "xfs_inode_max_write_streams",
    "xfs_write_stream_release",
    "xfs_inode_write_stream_ag_set",
    "xfs_ag_to_ag_set"
  ],
  "Reasoning": "The patch introduces write streams support to XFS and iomap, including new ioctls for managing write streams and modifications to block allocation logic to fan out allocations across AGs based on the stream ID. This is a functional change that modifies core filesystem logic and introduces new user-facing APIs, making it a prime candidate for fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/07/17 16:06 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 3f19f9cd1a4e5447ead1ecd78335f70bb33a33a4\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 17 16:06:02 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c\nindex e2cd5f92babedb..b7771a25c1c79f 100644\n--- a/fs/iomap/direct-io.c\n+++ b/fs/iomap/direct-io.c\n@@ -348,6 +348,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,\n \tfscrypt_set_bio_crypt_ctx(bio, iter-\u003einode, pos, GFP_KERNEL);\n \tbio-\u003ebi_iter.bi_sector = iomap_sector(\u0026iter-\u003eiomap, pos);\n \tbio-\u003ebi_write_hint = iter-\u003einode-\u003ei_write_hint;\n+\tbio-\u003ebi_write_stream = iter-\u003eiomap.write_stream;\n \tbio-\u003ebi_ioprio = dio-\u003eiocb-\u003eki_ioprio;\n \tbio-\u003ebi_private = dio;\n \tbio-\u003ebi_end_io = iomap_dio_bio_end_io;\ndiff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c\nindex 0565328764c175..5ea2c68993cf93 100644\n--- a/fs/iomap/ioend.c\n+++ b/fs/iomap/ioend.c\n@@ -165,6 +165,7 @@ static struct iomap_ioend *iomap_alloc_ioend(struct iomap_writepage_ctx *wpc,\n \t\t\t       GFP_NOFS, \u0026iomap_ioend_bioset);\n \tbio-\u003ebi_iter.bi_sector = iomap_sector(\u0026wpc-\u003eiomap, pos);\n \tbio-\u003ebi_write_hint = wpc-\u003einode-\u003ei_write_hint;\n+\tbio-\u003ebi_write_stream = wpc-\u003eiomap.write_stream;\n \twbc_init_bio(wpc-\u003ewbc, bio);\n \twpc-\u003enr_folios = 0;\n \treturn iomap_init_ioend(wpc-\u003einode, bio, pos, ioend_flags);\n@@ -188,6 +189,8 @@ static bool iomap_can_add_to_ioend(struct iomap_writepage_ctx *wpc, loff_t pos,\n \tif (!(wpc-\u003eiomap.flags \u0026 IOMAP_F_ANON_WRITE) \u0026\u0026\n \t    iomap_sector(\u0026wpc-\u003eiomap, pos) != bio_end_sector(\u0026ioend-\u003eio_bio))\n \t\treturn false;\n+\tif (wpc-\u003eiomap.write_stream != ioend-\u003eio_bio.bi_write_stream)\n+\t\treturn false;\n \t/*\n \t * Limit ioend bio chain lengths to minimise IO completion latency. This\n \t * also prevents long tight loops ending page writeback on all the\ndiff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c\nindex d64defeda645ed..872a4abd23157c 100644\n--- a/fs/xfs/libxfs/xfs_bmap.c\n+++ b/fs/xfs/libxfs/xfs_bmap.c\n@@ -3192,6 +3192,72 @@ xfs_bmap_select_minlen(\n \treturn args-\u003emaxlen;\n }\n \n+#define\tGENERIC_AG_SET_SZ\t(2)\n+\n+static inline xfs_agnumber_t\n+xfs_default_ag_set_size(\n+\tstruct xfs_inode\t*ip)\n+{\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\n+\treturn min_t(xfs_agnumber_t, GENERIC_AG_SET_SZ, mp-\u003em_sb.sb_agcount);\n+}\n+\n+static xfs_agnumber_t\n+xfs_inode_write_stream_ag_set(\n+\tstruct xfs_inode\t*ip,\n+\txfs_agnumber_t\t\t*target_agno)\n+{\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tuint32_t\t\tnr_streams = xfs_inode_max_write_streams(ip);\n+\tuint32_t\t\tstream_id = ip-\u003ei_write_stream;\n+\tuint32_t\t\tnr_ags = mp-\u003em_sb.sb_agcount;\n+\txfs_agnumber_t\t\tset_size;\n+\n+\n+\tif (!nr_streams)\n+\t\treturn xfs_default_ag_set_size(ip);\n+\n+\tstream_id -= 1; /* For 0-based math; stream-ids are 1-based */\n+\tset_size = nr_ags / nr_streams;\n+\n+\tif (set_size) {\n+\t\t*target_agno = stream_id * set_size;\n+\t\t/* uneven distribution, last stream will cover extra AGs */\n+\t\tif (stream_id == nr_streams - 1)\n+\t\t\tset_size = nr_ags - *target_agno;\n+\t} else {\n+\t\t/* for the case when we have fewer AGs than streams */\n+\t\t*target_agno = stream_id % nr_ags;\n+\t\tset_size = 1;\n+\t}\n+\n+\treturn set_size;\n+}\n+\n+static xfs_agnumber_t\n+xfs_ag_to_ag_set(\n+\tstruct xfs_bmalloca\t*ap,\n+\txfs_agnumber_t\t\tbase_agno)\n+{\n+\tstruct xfs_inode\t*ip = ap-\u003eip;\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\txfs_agnumber_t\t\tset_size;\n+\n+\t/* Apply fanning only for regular file data */\n+\tif (!(ap-\u003edatatype \u0026 XFS_ALLOC_USERDATA))\n+\t\treturn base_agno;\n+\n+\tif (ip-\u003ei_write_stream)\n+\t\tset_size = xfs_inode_write_stream_ag_set(ip, \u0026base_agno);\n+\telse\n+\t\tset_size = xfs_default_ag_set_size(ip);\n+\n+\t/* Fan out within the AG set using low bits of the inode */\n+\treturn (base_agno + (XFS_INO_TO_AGINO(mp, I_INO(ip)) % set_size)) %\n+\t\tmp-\u003em_sb.sb_agcount;\n+}\n+\n static int\n xfs_bmap_btalloc_select_lengths(\n \tstruct xfs_bmalloca\t*ap,\n@@ -3587,8 +3653,16 @@ xfs_bmap_btalloc_best_length(\n {\n \txfs_extlen_t\t\tblen = 0;\n \tint\t\t\terror;\n+\txfs_agnumber_t\t\ttarget_ag, start_ag;\n \n \tap-\u003eblkno = XFS_INODE_TO_FSB(ap-\u003eip);\n+\n+\t/* fan out initial AG across the generic AG set */\n+\tstart_ag = XFS_FSB_TO_AGNO(args-\u003emp, ap-\u003eblkno);\n+\ttarget_ag = xfs_ag_to_ag_set(ap, start_ag);\n+\tif (target_ag != start_ag)\n+\t\tap-\u003eblkno = XFS_AGB_TO_FSB(args-\u003emp, target_ag, 0);\n+\n \tif (!xfs_bmap_adjacent(ap))\n \t\tap-\u003eeof = false;\n \ndiff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c\nindex 9d8dd30bd927a2..7b9dda74122f0e 100644\n--- a/fs/xfs/xfs_icache.c\n+++ b/fs/xfs/xfs_icache.c\n@@ -129,6 +129,7 @@ xfs_inode_alloc(\n \tspin_lock_init(\u0026ip-\u003ei_ioend_lock);\n \tip-\u003ei_next_unlinked = NULLAGINO;\n \tip-\u003ei_prev_unlinked = 0;\n+\tip-\u003ei_write_stream = 0;\n \n \treturn ip;\n }\ndiff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c\nindex 15279d22a894fa..638bee3e4ac228 100644\n--- a/fs/xfs/xfs_inode.c\n+++ b/fs/xfs/xfs_inode.c\n@@ -4,6 +4,7 @@\n  * All Rights Reserved.\n  */\n #include \u003clinux/iversion.h\u003e\n+#include \u003clinux/anon_inodes.h\u003e\n \n #include \"xfs_platform.h\"\n #include \"xfs_fs.h\"\n@@ -47,6 +48,180 @@\n \n struct kmem_cache *xfs_inode_cache;\n \n+int\n+xfs_inode_max_write_streams(\n+\tstruct xfs_inode\t*ip)\n+{\n+\tstruct block_device\t*bdev;\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tint nr_streams;\n+\txfs_agnumber_t nr_ags, ag_set_size;\n+\tbool\t\t\tis_filestream, is_realtime;\n+\n+\txfs_ilock(ip, XFS_ILOCK_SHARED);\n+\tis_filestream = xfs_inode_is_filestream(ip);\n+\tis_realtime = XFS_IS_REALTIME_INODE(ip);\n+\tbdev = xfs_inode_buftarg(ip)-\u003ebt_bdev;\n+\txfs_iunlock(ip, XFS_ILOCK_SHARED);\n+\n+\tif (!bdev || is_filestream || is_realtime)\n+\t\treturn 0;\n+\n+\tnr_streams = bdev_max_write_streams(bdev);\n+\tif (nr_streams \u003e 0)\n+\t\treturn nr_streams;\n+\t/*\n+\t * Enable software-only streams if hardware streams are not available.\n+\t * This helps to\n+\t * - improve isolation; reduce allocation interleaving.\n+\t * - improve concurrency using AG-set based steering within and across streams.\n+\t */\n+\tnr_ags = mp-\u003em_sb.sb_agcount;\n+\tif (nr_ags \u003e= 16)\n+\t\tag_set_size = 4;\n+\telse if (nr_ags \u003e= 8)\n+\t\tag_set_size = 2;\n+\telse\n+\t\tag_set_size = 1;\n+\tnr_streams = nr_ags / ag_set_size;\n+\treturn min_t(uint16_t, nr_streams, XFS_SW_WRITE_STREAMS_MAX);\n+}\n+\n+uint16_t\n+xfs_inode_get_write_stream(\n+\tstruct xfs_inode\t*ip)\n+{\n+\tuint16_t\tstream_id;\n+\n+\txfs_ilock(ip, XFS_ILOCK_SHARED);\n+\tstream_id = ip-\u003ei_write_stream;\n+\txfs_iunlock(ip, XFS_ILOCK_SHARED);\n+\n+\treturn stream_id;\n+}\n+\n+struct xfs_write_stream {\n+\tstruct xfs_mount\t*mp;\n+\tuint16_t\t\tstream_id;\t/* 1-based */\n+};\n+\n+static int\n+xfs_write_stream_release(\n+\tstruct inode\t\t*inode,\n+\tstruct file\t\t*file)\n+{\n+\tstruct xfs_write_stream\t*ws = file-\u003eprivate_data;\n+\tstruct xfs_mount\t*mp = ws-\u003emp;\n+\n+\tspin_lock(\u0026mp-\u003em_streams_lock);\n+\tclear_bit(ws-\u003estream_id - 1, mp-\u003em_streams_in_use);\n+\tspin_unlock(\u0026mp-\u003em_streams_lock);\n+\tkfree(ws);\n+\treturn 0;\n+}\n+\n+static const struct file_operations xfs_write_stream_fops = {\n+\t.release\t= xfs_write_stream_release,\n+\t.llseek\t\t= noop_llseek,\n+};\n+\n+int\n+xfs_inode_write_stream_open(\n+\tstruct xfs_inode\t*ip,\n+\tu32\t\t\tflags,\n+\tu32\t\t\t*stream_idp)\n+{\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tstruct xfs_write_stream\t*ws;\n+\tint\t\t\tmax, slot, fd, ret;\n+\n+\tif (flags \u0026 ~FS_WRITE_STREAM_OPEN_EXACT)\n+\t\treturn -EINVAL;\n+\n+\tmax = xfs_inode_max_write_streams(ip);\n+\tif (!max)\n+\t\treturn -EOPNOTSUPP;\n+\tASSERT(mp-\u003em_streams_in_use);\n+\n+\tws = kmalloc(sizeof(*ws), GFP_KERNEL);\n+\tif (!ws)\n+\t\treturn -ENOMEM;\n+\n+\tspin_lock(\u0026mp-\u003em_streams_lock);\n+\tif (flags \u0026 FS_WRITE_STREAM_OPEN_EXACT) {\n+\t\tif (!*stream_idp || *stream_idp \u003e max) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t\tslot = *stream_idp - 1;\n+\t\tif (test_bit(slot, mp-\u003em_streams_in_use)) {\n+\t\t\tret = -EBUSY;\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t} else {\n+\t\tslot = find_first_zero_bit(mp-\u003em_streams_in_use, max);\n+\t\tif (slot \u003e= max) {\n+\t\t\tret = -EBUSY;\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\t}\n+\tset_bit(slot, mp-\u003em_streams_in_use);\n+\tspin_unlock(\u0026mp-\u003em_streams_lock);\n+\n+\tws-\u003emp = mp;\n+\tws-\u003estream_id = slot + 1;\t/* convert to 1-based */\n+\n+\tfd = anon_inode_getfd(\"[xfs_write_stream]\", \u0026xfs_write_stream_fops, ws,\n+\t\t\t      O_RDONLY | O_CLOEXEC);\n+\tif (fd \u003c 0) {\n+\t\tspin_lock(\u0026mp-\u003em_streams_lock);\n+\t\tclear_bit(slot, mp-\u003em_streams_in_use);\n+\t\tspin_unlock(\u0026mp-\u003em_streams_lock);\n+\t\tkfree(ws);\n+\t\treturn fd;\n+\t}\n+\n+\t*stream_idp = ws-\u003estream_id;\n+\treturn fd;\n+\n+out_unlock:\n+\tspin_unlock(\u0026mp-\u003em_streams_lock);\n+\tkfree(ws);\n+\treturn ret;\n+}\n+\n+int\n+xfs_inode_set_write_stream(\n+\tstruct xfs_inode\t*ip,\n+\tint\t\t\tstream_fd)\n+{\n+\tCLASS(fd, f)(stream_fd);\n+\tstruct xfs_write_stream\t*ws;\n+\tint\t\t\tret = 0;\n+\n+\tif (!fd_file(f))\n+\t\treturn -EBADF;\n+\tif (fd_file(f)-\u003ef_op != \u0026xfs_write_stream_fops)\n+\t\treturn -EINVAL;\n+\n+\tws = fd_file(f)-\u003eprivate_data;\n+\tif (ws-\u003emp != ip-\u003ei_mount)\n+\t\treturn -EINVAL;\n+\n+\txfs_ilock(ip, XFS_ILOCK_EXCL);\n+\n+\tif (XFS_IS_REALTIME_INODE(ip) || xfs_inode_is_filestream(ip) ||\n+\t    VFS_I(ip)-\u003ei_write_hint != WRITE_LIFE_NOT_SET) {\n+\t\tret = -EINVAL;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tip-\u003ei_write_stream = ws-\u003estream_id;\n+out_unlock:\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\treturn ret;\n+}\n+\n /*\n  * These two are wrapper routines around the xfs_ilock() routine used to\n  * centralize some grungy code.  They are used in places that wish to lock the\ndiff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h\nindex 34c1038ebfcde1..6abf82ffbf82ac 100644\n--- a/fs/xfs/xfs_inode.h\n+++ b/fs/xfs/xfs_inode.h\n@@ -37,6 +37,9 @@ typedef struct xfs_inode {\n \tstruct xfs_ifork\ti_df;\t\t/* data fork */\n \tstruct xfs_ifork\ti_af;\t\t/* attribute fork */\n \n+\t/* Write stream information */\n+\tuint16_t\t\ti_write_stream;\n+\n \t/* Transaction and locking information. */\n \tstruct xfs_inode_log_item *i_itemp;\t/* logging information */\n \tstruct rw_semaphore\ti_lock;\t\t/* inode lock */\n@@ -673,4 +676,9 @@ int xfs_icreate_dqalloc(const struct xfs_icreate_args *args,\n \t\tstruct xfs_dquot **udqpp, struct xfs_dquot **gdqpp,\n \t\tstruct xfs_dquot **pdqpp);\n \n+int xfs_inode_max_write_streams(struct xfs_inode *ip);\n+uint16_t xfs_inode_get_write_stream(struct xfs_inode *ip);\n+int xfs_inode_write_stream_open(struct xfs_inode *ip, u32 flags,\n+\t\tu32 *stream_idp);\n+int xfs_inode_set_write_stream(struct xfs_inode *ip, int stream_fd);\n #endif\t/* __XFS_INODE_H__ */\ndiff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c\nindex 1b53701bebea1a..8640e63897683f 100644\n--- a/fs/xfs/xfs_ioctl.c\n+++ b/fs/xfs/xfs_ioctl.c\n@@ -557,6 +557,12 @@ xfs_ioctl_setattr_xflags(\n \tbool\t\t\trtflag = (fa-\u003efsx_xflags \u0026 FS_XFLAG_REALTIME);\n \tuint64_t\t\ti_flags2;\n \n+\tif ((fa-\u003efsx_xflags \u0026 FS_XFLAG_FILESTREAM) \u0026\u0026 ip-\u003ei_write_stream)\n+\t\treturn -EINVAL;\n+\n+\tif (rtflag \u0026\u0026 ip-\u003ei_write_stream)\n+\t\treturn -EINVAL;\n+\n \tif (rtflag != XFS_IS_REALTIME_INODE(ip)) {\n \t\t/* Can't change realtime flag if any extents are allocated. */\n \t\tif (xfs_inode_has_filedata(ip))\n@@ -1200,6 +1206,59 @@ xfs_ioctl_fs_counts(\n \treturn 0;\n }\n \n+static int\n+xfs_ioc_write_stream_open(\n+\tstruct file\t\t*filp,\n+\tvoid __user\t\t*arg)\n+{\n+\tstruct xfs_inode\t*ip = XFS_I(file_inode(filp));\n+\tstruct fs_write_stream_open wso;\n+\tint\t\t\tfd;\n+\n+\tif (copy_from_user(\u0026wso, arg, sizeof(wso)))\n+\t\treturn -EFAULT;\n+\n+\tfd = xfs_inode_write_stream_open(ip, wso.flags, \u0026wso.stream_id);\n+\tif (fd \u003c 0)\n+\t\treturn fd;\n+\n+\tif (copy_to_user(arg, \u0026wso, sizeof(wso)))\n+\t\treturn -EFAULT;\n+\treturn fd;\n+}\n+\n+static int\n+xfs_ioc_write_stream_set(\n+\tstruct file\t\t*filp,\n+\tunsigned long\t\targ)\n+{\n+\tstruct xfs_inode\t*ip = XFS_I(file_inode(filp));\n+\n+\tif (!(filp-\u003ef_mode \u0026 FMODE_WRITE))\n+\t\treturn -EBADF;\n+\treturn xfs_inode_set_write_stream(ip, (int)arg);\n+}\n+\n+static int\n+xfs_ioc_write_stream_get(\n+\tstruct xfs_inode\t*ip,\n+\tvoid __user\t\t*arg)\n+{\n+\t__u32 stream_id = xfs_inode_get_write_stream(ip);\n+\n+\treturn put_user(stream_id, (__u32 __user *)arg);\n+}\n+\n+static int\n+xfs_ioc_write_stream_get_max(\n+\tstruct xfs_inode\t*ip,\n+\tvoid __user\t\t*arg)\n+{\n+\t__u32 nr_streams = xfs_inode_max_write_streams(ip);\n+\n+\treturn put_user(nr_streams, (__u32 __user *)arg);\n+}\n+\n /*\n  * These long-unused ioctls were removed from the official ioctl API in 5.17,\n  * but retain these definitions so that we can log warnings about them.\n@@ -1465,6 +1524,16 @@ xfs_file_ioctl(\n \t\treturn xfs_ioc_health_monitor(filp, arg);\n \tcase XFS_IOC_VERIFY_MEDIA:\n \t\treturn xfs_ioc_verify_media(filp, arg);\n+\tcase FS_IOC_WRITE_STREAM_OPEN:\n+\t\treturn xfs_ioc_write_stream_open(filp, (void __user *)arg);\n+\tcase FS_IOC_WRITE_STREAM_SET:\n+\t\treturn xfs_ioc_write_stream_set(filp, p);\n+\tcase FS_IOC_WRITE_STREAM_GET:\n+\t\treturn xfs_ioc_write_stream_get(XFS_I(file_inode(filp)),\n+\t\t\t\t\t\t(void __user *)arg);\n+\tcase FS_IOC_WRITE_STREAM_GET_MAX:\n+\t\treturn xfs_ioc_write_stream_get_max(XFS_I(file_inode(filp)),\n+\t\t\t\t\t\t    (void __user *)arg);\n \n \tdefault:\n \t\treturn -ENOTTY;\ndiff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c\nindex 225c3de88d03b1..bf423897d916df 100644\n--- a/fs/xfs/xfs_iomap.c\n+++ b/fs/xfs/xfs_iomap.c\n@@ -144,6 +144,7 @@ xfs_bmbt_to_iomap(\n \t}\n \tiomap-\u003eoffset = XFS_FSB_TO_B(mp, imap-\u003ebr_startoff);\n \tiomap-\u003elength = XFS_FSB_TO_B(mp, imap-\u003ebr_blockcount);\n+\tiomap-\u003ewrite_stream = ip-\u003ei_write_stream;\n \tif (mapping_flags \u0026 IOMAP_DAX) {\n \t\tiomap-\u003edax_dev = target-\u003ebt_daxdev;\n \t} else {\ndiff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h\nindex 66a02d1b9ad7a1..ad51115594d3c8 100644\n--- a/fs/xfs/xfs_mount.h\n+++ b/fs/xfs/xfs_mount.h\n@@ -45,6 +45,8 @@ enum {\n \n #define XFS_ERR_RETRY_FOREVER\t-1\n \n+#define XFS_SW_WRITE_STREAMS_MAX\t16\n+\n /*\n  * Although retry_timeout is in jiffies which is normally an unsigned long,\n  * we limit the retry timeout to 86400 seconds, or one day.  So even a\n@@ -349,6 +351,9 @@ typedef struct xfs_mount {\n \n \t/* Index of uuid record in the uuid xarray. */\n \tunsigned int\t\tm_uuid_table_index;\n+\n+\tunsigned long\t\t*m_streams_in_use;\n+\tspinlock_t\t\tm_streams_lock;\n } xfs_mount_t;\n \n #define M_IGEO(mp)\t\t(\u0026(mp)-\u003em_ino_geo)\ndiff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c\nindex 8531d526fc4409..d698be504c4548 100644\n--- a/fs/xfs/xfs_super.c\n+++ b/fs/xfs/xfs_super.c\n@@ -805,6 +805,7 @@ xfs_mount_free(\n #ifdef DEBUG\n \tkfree(mp-\u003em_errortag);\n #endif\n+\tbitmap_free(mp-\u003em_streams_in_use);\n \tkfree(mp);\n }\n \n@@ -1659,6 +1660,7 @@ xfs_fs_fill_super(\n \tstruct xfs_mount\t*mp = sb-\u003es_fs_info;\n \tstruct inode\t\t*root;\n \tint\t\t\tflags = 0, error;\n+\tint\t\t\tnr_streams;\n \n \tmp-\u003em_super = sb;\n \n@@ -1708,6 +1710,15 @@ xfs_fs_fill_super(\n \tif (error)\n \t\treturn error;\n \n+\tnr_streams = bdev_max_write_streams(mp-\u003em_ddev_targp-\u003ebt_bdev);\n+\tif (!nr_streams)\n+\t\tnr_streams = XFS_SW_WRITE_STREAMS_MAX;\n+\tmp-\u003em_streams_in_use = bitmap_zalloc(nr_streams, GFP_KERNEL);\n+\tif (!mp-\u003em_streams_in_use) {\n+\t\terror = -ENOMEM;\n+\t\tgoto out_shutdown_devices;\n+\t}\n+\n \tif (xfs_debugfs) {\n \t\tmp-\u003em_debugfs = xfs_debugfs_mkdir(mp-\u003em_super-\u003es_id,\n \t\t\t\t\t\t  xfs_debugfs);\n@@ -2249,6 +2260,7 @@ xfs_init_fs_context(\n #endif\n \n \tspin_lock_init(\u0026mp-\u003em_sb_lock);\n+\tspin_lock_init(\u0026mp-\u003em_streams_lock);\n \tfor (i = 0; i \u003c XG_TYPE_MAX; i++)\n \t\txa_init(\u0026mp-\u003em_groups[i].xa);\n \tmutex_init(\u0026mp-\u003em_growlock);\ndiff --git a/include/linux/iomap.h b/include/linux/iomap.h\nindex 56b43d594e6e29..4dc71da598f99d 100644\n--- a/include/linux/iomap.h\n+++ b/include/linux/iomap.h\n@@ -132,6 +132,8 @@ struct iomap {\n \tu64\t\t\tlength;\t/* length of mapping, bytes */\n \tu16\t\t\ttype;\t/* type of mapping */\n \tu16\t\t\tflags;\t/* flags for mapping */\n+\tu16\t\t\twrite_stream; /* write stream for I/O */\n+\t/* 2 bytes padding hole here */\n \tstruct block_device\t*bdev;\t/* block device for I/O */\n \tstruct dax_device\t*dax_dev; /* dax_dev for dax operations */\n \tvoid\t\t\t*inline_data;\ndiff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h\nindex bd87262f2e3494..a756d27bdf8417 100644\n--- a/include/uapi/linux/fs.h\n+++ b/include/uapi/linux/fs.h\n@@ -345,6 +345,22 @@ struct file_attr {\n /* Get logical block metadata capability details */\n #define FS_IOC_GETLBMD_CAP\t\t_IOWR(0x15, 2, struct logical_block_metadata_cap)\n \n+struct fs_write_stream_open {\n+\t__u32\t\tstream_id;\t/* IN: requested id if OPEN_EXACT set; OUT: assigned id */\n+\t__u32\t\tflags;\t\t/* IN: FS_WRITE_STREAM_OPEN_* */\n+};\n+\n+/*\n+ * Flag to ask for specific stream_id, otherwise kernel can return any.\n+ * Fails with -EBUSY if that stream_id is already held\n+ */\n+#define FS_WRITE_STREAM_OPEN_EXACT\t(1 \u003c\u003c 0)\n+\n+#define FS_IOC_WRITE_STREAM_GET_MAX\t_IOR('f', 135, __u32)\n+#define FS_IOC_WRITE_STREAM_OPEN\t_IOWR('f', 136, struct fs_write_stream_open)\n+#define FS_IOC_WRITE_STREAM_SET\t\t_IOW('f', 137, __s32)\n+#define FS_IOC_WRITE_STREAM_GET\t\t_IOR('f', 138, __u32)\n+\n /*\n  * Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)\n  *\n"
}

2/1 2026/07/17 16:06 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "XFS_FS"
  ],
  "FocusSymbols": [
    "xfs_ioc_write_stream_open",
    "xfs_ioc_write_stream_set",
    "xfs_ioc_write_stream_get",
    "xfs_ioc_write_stream_get_max",
    "xfs_inode_write_stream_open",
    "xfs_inode_set_write_stream",
    "xfs_inode_get_write_stream",
    "xfs_inode_max_write_streams",
    "xfs_write_stream_release",
    "xfs_inode_write_stream_ag_set",
    "xfs_ag_to_ag_set"
  ],
  "Reasoning": "The patch introduces write streams support to XFS and iomap, including new ioctls for managing write streams and modifications to block allocation logic to fan out allocations across AGs based on the stream ID. This is a functional change that modifies core filesystem logic and introduces new user-facing APIs, making it a prime candidate for fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3f19f9cd1a4e5447ead1ecd78335f70bb33a33a4
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 17 16:06:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index e2cd5f92babedb..b7771a25c1c79f 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -348,6 +348,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
 	fscrypt_set_bio_crypt_ctx(bio, iter->inode, pos, GFP_KERNEL);
 	bio->bi_iter.bi_sector = iomap_sector(&iter->iomap, pos);
 	bio->bi_write_hint = iter->inode->i_write_hint;
+	bio->bi_write_stream = iter->iomap.write_stream;
 	bio->bi_ioprio = dio->iocb->ki_ioprio;
 	bio->bi_private = dio;
 	bio->bi_end_io = iomap_dio_bio_end_io;
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 0565328764c175..5ea2c68993cf93 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -165,6 +165,7 @@ static struct iomap_ioend *iomap_alloc_ioend(struct iomap_writepage_ctx *wpc,
 			       GFP_NOFS, &iomap_ioend_bioset);
 	bio->bi_iter.bi_sector = iomap_sector(&wpc->iomap, pos);
 	bio->bi_write_hint = wpc->inode->i_write_hint;
+	bio->bi_write_stream = wpc->iomap.write_stream;
 	wbc_init_bio(wpc->wbc, bio);
 	wpc->nr_folios = 0;
 	return iomap_init_ioend(wpc->inode, bio, pos, ioend_flags);
@@ -188,6 +189,8 @@ static bool iomap_can_add_to_ioend(struct iomap_writepage_ctx *wpc, loff_t pos,
 	if (!(wpc->iomap.flags & IOMAP_F_ANON_WRITE) &&
 	    iomap_sector(&wpc->iomap, pos) != bio_end_sector(&ioend->io_bio))
 		return false;
+	if (wpc->iomap.write_stream != ioend->io_bio.bi_write_stream)
+		return false;
 	/*
 	 * Limit ioend bio chain lengths to minimise IO completion latency. This
 	 * also prevents long tight loops ending page writeback on all the
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index d64defeda645ed..872a4abd23157c 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -3192,6 +3192,72 @@ xfs_bmap_select_minlen(
 	return args->maxlen;
 }
 
+#define	GENERIC_AG_SET_SZ	(2)
+
+static inline xfs_agnumber_t
+xfs_default_ag_set_size(
+	struct xfs_inode	*ip)
+{
+	struct xfs_mount	*mp = ip->i_mount;
+
+	return min_t(xfs_agnumber_t, GENERIC_AG_SET_SZ, mp->m_sb.sb_agcount);
+}
+
+static xfs_agnumber_t
+xfs_inode_write_stream_ag_set(
+	struct xfs_inode	*ip,
+	xfs_agnumber_t		*target_agno)
+{
+	struct xfs_mount	*mp = ip->i_mount;
+	uint32_t		nr_streams = xfs_inode_max_write_streams(ip);
+	uint32_t		stream_id = ip->i_write_stream;
+	uint32_t		nr_ags = mp->m_sb.sb_agcount;
+	xfs_agnumber_t		set_size;
+
+
+	if (!nr_streams)
+		return xfs_default_ag_set_size(ip);
+
+	stream_id -= 1; /* For 0-based math; stream-ids are 1-based */
+	set_size = nr_ags / nr_streams;
+
+	if (set_size) {
+		*target_agno = stream_id * set_size;
+		/* uneven distribution, last stream will cover extra AGs */
+		if (stream_id == nr_streams - 1)
+			set_size = nr_ags - *target_agno;
+	} else {
+		/* for the case when we have fewer AGs than streams */
+		*target_agno = stream_id % nr_ags;
+		set_size = 1;
+	}
+
+	return set_size;
+}
+
+static xfs_agnumber_t
+xfs_ag_to_ag_set(
+	struct xfs_bmalloca	*ap,
+	xfs_agnumber_t		base_agno)
+{
+	struct xfs_inode	*ip = ap->ip;
+	struct xfs_mount	*mp = ip->i_mount;
+	xfs_agnumber_t		set_size;
+
+	/* Apply fanning only for regular file data */
+	if (!(ap->datatype & XFS_ALLOC_USERDATA))
+		return base_agno;
+
+	if (ip->i_write_stream)
+		set_size = xfs_inode_write_stream_ag_set(ip, &base_agno);
+	else
+		set_size = xfs_default_ag_set_size(ip);
+
+	/* Fan out within the AG set using low bits of the inode */
+	return (base_agno + (XFS_INO_TO_AGINO(mp, I_INO(ip)) % set_size)) %
+		mp->m_sb.sb_agcount;
+}
+
 static int
 xfs_bmap_btalloc_select_lengths(
 	struct xfs_bmalloca	*ap,
@@ -3587,8 +3653,16 @@ xfs_bmap_btalloc_best_length(
 {
 	xfs_extlen_t		blen = 0;
 	int			error;
+	xfs_agnumber_t		target_ag, start_ag;
 
 	ap->blkno = XFS_INODE_TO_FSB(ap->ip);
+
+	/* fan out initial AG across the generic AG set */
+	start_ag = XFS_FSB_TO_AGNO(args->mp, ap->blkno);
+	target_ag = xfs_ag_to_ag_set(ap, start_ag);
+	if (target_ag != start_ag)
+		ap->blkno = XFS_AGB_TO_FSB(args->mp, target_ag, 0);
+
 	if (!xfs_bmap_adjacent(ap))
 		ap->eof = false;
 
diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c
index 9d8dd30bd927a2..7b9dda74122f0e 100644
--- a/fs/xfs/xfs_icache.c
+++ b/fs/xfs/xfs_icache.c
@@ -129,6 +129,7 @@ xfs_inode_alloc(
 	spin_lock_init(&ip->i_ioend_lock);
 	ip->i_next_unlinked = NULLAGINO;
 	ip->i_prev_unlinked = 0;
+	ip->i_write_stream = 0;
 
 	return ip;
 }
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 15279d22a894fa..638bee3e4ac228 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -4,6 +4,7 @@
  * All Rights Reserved.
  */
 #include <linux/iversion.h>
+#include <linux/anon_inodes.h>
 
 #include "xfs_platform.h"
 #include "xfs_fs.h"
@@ -47,6 +48,180 @@
 
 struct kmem_cache *xfs_inode_cache;
 
+int
+xfs_inode_max_write_streams(
+	struct xfs_inode	*ip)
+{
+	struct block_device	*bdev;
+	struct xfs_mount	*mp = ip->i_mount;
+	int nr_streams;
+	xfs_agnumber_t nr_ags, ag_set_size;
+	bool			is_filestream, is_realtime;
+
+	xfs_ilock(ip, XFS_ILOCK_SHARED);
+	is_filestream = xfs_inode_is_filestream(ip);
+	is_realtime = XFS_IS_REALTIME_INODE(ip);
+	bdev = xfs_inode_buftarg(ip)->bt_bdev;
+	xfs_iunlock(ip, XFS_ILOCK_SHARED);
+
+	if (!bdev || is_filestream || is_realtime)
+		return 0;
+
+	nr_streams = bdev_max_write_streams(bdev);
+	if (nr_streams > 0)
+		return nr_streams;
+	/*
+	 * Enable software-only streams if hardware streams are not available.
+	 * This helps to
+	 * - improve isolation; reduce allocation interleaving.
+	 * - improve concurrency using AG-set based steering within and across streams.
+	 */
+	nr_ags = mp->m_sb.sb_agcount;
+	if (nr_ags >= 16)
+		ag_set_size = 4;
+	else if (nr_ags >= 8)
+		ag_set_size = 2;
+	else
+		ag_set_size = 1;
+	nr_streams = nr_ags / ag_set_size;
+	return min_t(uint16_t, nr_streams, XFS_SW_WRITE_STREAMS_MAX);
+}
+
+uint16_t
+xfs_inode_get_write_stream(
+	struct xfs_inode	*ip)
+{
+	uint16_t	stream_id;
+
+	xfs_ilock(ip, XFS_ILOCK_SHARED);
+	stream_id = ip->i_write_stream;
+	xfs_iunlock(ip, XFS_ILOCK_SHARED);
+
+	return stream_id;
+}
+
+struct xfs_write_stream {
+	struct xfs_mount	*mp;
+	uint16_t		stream_id;	/* 1-based */
+};
+
+static int
+xfs_write_stream_release(
+	struct inode		*inode,
+	struct file		*file)
+{
+	struct xfs_write_stream	*ws = file->private_data;
+	struct xfs_mount	*mp = ws->mp;
+
+	spin_lock(&mp->m_streams_lock);
+	clear_bit(ws->stream_id - 1, mp->m_streams_in_use);
+	spin_unlock(&mp->m_streams_lock);
+	kfree(ws);
+	return 0;
+}
+
+static const struct file_operations xfs_write_stream_fops = {
+	.release	= xfs_write_stream_release,
+	.llseek		= noop_llseek,
+};
+
+int
+xfs_inode_write_stream_open(
+	struct xfs_inode	*ip,
+	u32			flags,
+	u32			*stream_idp)
+{
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_write_stream	*ws;
+	int			max, slot, fd, ret;
+
+	if (flags & ~FS_WRITE_STREAM_OPEN_EXACT)
+		return -EINVAL;
+
+	max = xfs_inode_max_write_streams(ip);
+	if (!max)
+		return -EOPNOTSUPP;
+	ASSERT(mp->m_streams_in_use);
+
+	ws = kmalloc(sizeof(*ws), GFP_KERNEL);
+	if (!ws)
+		return -ENOMEM;
+
+	spin_lock(&mp->m_streams_lock);
+	if (flags & FS_WRITE_STREAM_OPEN_EXACT) {
+		if (!*stream_idp || *stream_idp > max) {
+			ret = -EINVAL;
+			goto out_unlock;
+		}
+		slot = *stream_idp - 1;
+		if (test_bit(slot, mp->m_streams_in_use)) {
+			ret = -EBUSY;
+			goto out_unlock;
+		}
+	} else {
+		slot = find_first_zero_bit(mp->m_streams_in_use, max);
+		if (slot >= max) {
+			ret = -EBUSY;
+			goto out_unlock;
+		}
+	}
+	set_bit(slot, mp->m_streams_in_use);
+	spin_unlock(&mp->m_streams_lock);
+
+	ws->mp = mp;
+	ws->stream_id = slot + 1;	/* convert to 1-based */
+
+	fd = anon_inode_getfd("[xfs_write_stream]", &xfs_write_stream_fops, ws,
+			      O_RDONLY | O_CLOEXEC);
+	if (fd < 0) {
+		spin_lock(&mp->m_streams_lock);
+		clear_bit(slot, mp->m_streams_in_use);
+		spin_unlock(&mp->m_streams_lock);
+		kfree(ws);
+		return fd;
+	}
+
+	*stream_idp = ws->stream_id;
+	return fd;
+
+out_unlock:
+	spin_unlock(&mp->m_streams_lock);
+	kfree(ws);
+	return ret;
+}
+
+int
+xfs_inode_set_write_stream(
+	struct xfs_inode	*ip,
+	int			stream_fd)
+{
+	CLASS(fd, f)(stream_fd);
+	struct xfs_write_stream	*ws;
+	int			ret = 0;
+
+	if (!fd_file(f))
+		return -EBADF;
+	if (fd_file(f)->f_op != &xfs_write_stream_fops)
+		return -EINVAL;
+
+	ws = fd_file(f)->private_data;
+	if (ws->mp != ip->i_mount)
+		return -EINVAL;
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+
+	if (XFS_IS_REALTIME_INODE(ip) || xfs_inode_is_filestream(ip) ||
+	    VFS_I(ip)->i_write_hint != WRITE_LIFE_NOT_SET) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	ip->i_write_stream = ws->stream_id;
+out_unlock:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return ret;
+}
+
 /*
  * These two are wrapper routines around the xfs_ilock() routine used to
  * centralize some grungy code.  They are used in places that wish to lock the
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 34c1038ebfcde1..6abf82ffbf82ac 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -37,6 +37,9 @@ typedef struct xfs_inode {
 	struct xfs_ifork	i_df;		/* data fork */
 	struct xfs_ifork	i_af;		/* attribute fork */
 
+	/* Write stream information */
+	uint16_t		i_write_stream;
+
 	/* Transaction and locking information. */
 	struct xfs_inode_log_item *i_itemp;	/* logging information */
 	struct rw_semaphore	i_lock;		/* inode lock */
@@ -673,4 +676,9 @@ int xfs_icreate_dqalloc(const struct xfs_icreate_args *args,
 		struct xfs_dquot **udqpp, struct xfs_dquot **gdqpp,
 		struct xfs_dquot **pdqpp);
 
+int xfs_inode_max_write_streams(struct xfs_inode *ip);
+uint16_t xfs_inode_get_write_stream(struct xfs_inode *ip);
+int xfs_inode_write_stream_open(struct xfs_inode *ip, u32 flags,
+		u32 *stream_idp);
+int xfs_inode_set_write_stream(struct xfs_inode *ip, int stream_fd);
 #endif	/* __XFS_INODE_H__ */
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 1b53701bebea1a..8640e63897683f 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -557,6 +557,12 @@ xfs_ioctl_setattr_xflags(
 	bool			rtflag = (fa->fsx_xflags & FS_XFLAG_REALTIME);
 	uint64_t		i_flags2;
 
+	if ((fa->fsx_xflags & FS_XFLAG_FILESTREAM) && ip->i_write_stream)
+		return -EINVAL;
+
+	if (rtflag && ip->i_write_stream)
+		return -EINVAL;
+
 	if (rtflag != XFS_IS_REALTIME_INODE(ip)) {
 		/* Can't change realtime flag if any extents are allocated. */
 		if (xfs_inode_has_filedata(ip))
@@ -1200,6 +1206,59 @@ xfs_ioctl_fs_counts(
 	return 0;
 }
 
+static int
+xfs_ioc_write_stream_open(
+	struct file		*filp,
+	void __user		*arg)
+{
+	struct xfs_inode	*ip = XFS_I(file_inode(filp));
+	struct fs_write_stream_open wso;
+	int			fd;
+
+	if (copy_from_user(&wso, arg, sizeof(wso)))
+		return -EFAULT;
+
+	fd = xfs_inode_write_stream_open(ip, wso.flags, &wso.stream_id);
+	if (fd < 0)
+		return fd;
+
+	if (copy_to_user(arg, &wso, sizeof(wso)))
+		return -EFAULT;
+	return fd;
+}
+
+static int
+xfs_ioc_write_stream_set(
+	struct file		*filp,
+	unsigned long		arg)
+{
+	struct xfs_inode	*ip = XFS_I(file_inode(filp));
+
+	if (!(filp->f_mode & FMODE_WRITE))
+		return -EBADF;
+	return xfs_inode_set_write_stream(ip, (int)arg);
+}
+
+static int
+xfs_ioc_write_stream_get(
+	struct xfs_inode	*ip,
+	void __user		*arg)
+{
+	__u32 stream_id = xfs_inode_get_write_stream(ip);
+
+	return put_user(stream_id, (__u32 __user *)arg);
+}
+
+static int
+xfs_ioc_write_stream_get_max(
+	struct xfs_inode	*ip,
+	void __user		*arg)
+{
+	__u32 nr_streams = xfs_inode_max_write_streams(ip);
+
+	return put_user(nr_streams, (__u32 __user *)arg);
+}
+
 /*
  * These long-unused ioctls were removed from the official ioctl API in 5.17,
  * but retain these definitions so that we can log warnings about them.
@@ -1465,6 +1524,16 @@ xfs_file_ioctl(
 		return xfs_ioc_health_monitor(filp, arg);
 	case XFS_IOC_VERIFY_MEDIA:
 		return xfs_ioc_verify_media(filp, arg);
+	case FS_IOC_WRITE_STREAM_OPEN:
+		return xfs_ioc_write_stream_open(filp, (void __user *)arg);
+	case FS_IOC_WRITE_STREAM_SET:
+		return xfs_ioc_write_stream_set(filp, p);
+	case FS_IOC_WRITE_STREAM_GET:
+		return xfs_ioc_write_stream_get(XFS_I(file_inode(filp)),
+						(void __user *)arg);
+	case FS_IOC_WRITE_STREAM_GET_MAX:
+		return xfs_ioc_write_stream_get_max(XFS_I(file_inode(filp)),
+						    (void __user *)arg);
 
 	default:
 		return -ENOTTY;
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index 225c3de88d03b1..bf423897d916df 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -144,6 +144,7 @@ xfs_bmbt_to_iomap(
 	}
 	iomap->offset = XFS_FSB_TO_B(mp, imap->br_startoff);
 	iomap->length = XFS_FSB_TO_B(mp, imap->br_blockcount);
+	iomap->write_stream = ip->i_write_stream;
 	if (mapping_flags & IOMAP_DAX) {
 		iomap->dax_dev = target->bt_daxdev;
 	} else {
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 66a02d1b9ad7a1..ad51115594d3c8 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -45,6 +45,8 @@ enum {
 
 #define XFS_ERR_RETRY_FOREVER	-1
 
+#define XFS_SW_WRITE_STREAMS_MAX	16
+
 /*
  * Although retry_timeout is in jiffies which is normally an unsigned long,
  * we limit the retry timeout to 86400 seconds, or one day.  So even a
@@ -349,6 +351,9 @@ typedef struct xfs_mount {
 
 	/* Index of uuid record in the uuid xarray. */
 	unsigned int		m_uuid_table_index;
+
+	unsigned long		*m_streams_in_use;
+	spinlock_t		m_streams_lock;
 } xfs_mount_t;
 
 #define M_IGEO(mp)		(&(mp)->m_ino_geo)
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 8531d526fc4409..d698be504c4548 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -805,6 +805,7 @@ xfs_mount_free(
 #ifdef DEBUG
 	kfree(mp->m_errortag);
 #endif
+	bitmap_free(mp->m_streams_in_use);
 	kfree(mp);
 }
 
@@ -1659,6 +1660,7 @@ xfs_fs_fill_super(
 	struct xfs_mount	*mp = sb->s_fs_info;
 	struct inode		*root;
 	int			flags = 0, error;
+	int			nr_streams;
 
 	mp->m_super = sb;
 
@@ -1708,6 +1710,15 @@ xfs_fs_fill_super(
 	if (error)
 		return error;
 
+	nr_streams = bdev_max_write_streams(mp->m_ddev_targp->bt_bdev);
+	if (!nr_streams)
+		nr_streams = XFS_SW_WRITE_STREAMS_MAX;
+	mp->m_streams_in_use = bitmap_zalloc(nr_streams, GFP_KERNEL);
+	if (!mp->m_streams_in_use) {
+		error = -ENOMEM;
+		goto out_shutdown_devices;
+	}
+
 	if (xfs_debugfs) {
 		mp->m_debugfs = xfs_debugfs_mkdir(mp->m_super->s_id,
 						  xfs_debugfs);
@@ -2249,6 +2260,7 @@ xfs_init_fs_context(
 #endif
 
 	spin_lock_init(&mp->m_sb_lock);
+	spin_lock_init(&mp->m_streams_lock);
 	for (i = 0; i < XG_TYPE_MAX; i++)
 		xa_init(&mp->m_groups[i].xa);
 	mutex_init(&mp->m_growlock);
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 56b43d594e6e29..4dc71da598f99d 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -132,6 +132,8 @@ struct iomap {
 	u64			length;	/* length of mapping, bytes */
 	u16			type;	/* type of mapping */
 	u16			flags;	/* flags for mapping */
+	u16			write_stream; /* write stream for I/O */
+	/* 2 bytes padding hole here */
 	struct block_device	*bdev;	/* block device for I/O */
 	struct dax_device	*dax_dev; /* dax_dev for dax operations */
 	void			*inline_data;
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index bd87262f2e3494..a756d27bdf8417 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -345,6 +345,22 @@ struct file_attr {
 /* Get logical block metadata capability details */
 #define FS_IOC_GETLBMD_CAP		_IOWR(0x15, 2, struct logical_block_metadata_cap)
 
+struct fs_write_stream_open {
+	__u32		stream_id;	/* IN: requested id if OPEN_EXACT set; OUT: assigned id */
+	__u32		flags;		/* IN: FS_WRITE_STREAM_OPEN_* */
+};
+
+/*
+ * Flag to ask for specific stream_id, otherwise kernel can return any.
+ * Fails with -EBUSY if that stream_id is already held
+ */
+#define FS_WRITE_STREAM_OPEN_EXACT	(1 << 0)
+
+#define FS_IOC_WRITE_STREAM_GET_MAX	_IOR('f', 135, __u32)
+#define FS_IOC_WRITE_STREAM_OPEN	_IOWR('f', 136, struct fs_write_stream_open)
+#define FS_IOC_WRITE_STREAM_SET		_IOW('f', 137, __s32)
+#define FS_IOC_WRITE_STREAM_GET		_IOR('f', 138, __u32)
+
 /*
  * Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)
  *

3/2 2026/07/17 16:06 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 9269
							output: 30
							thoughts: 490
						

Thoughts:
**My Assessment of the Write Streams Patch Series**

Okay, so I'm looking at this patch series introducing "write streams" support for XFS and iomap. Immediately, I see it's a functional addition, not just a bug fix. That means I need to be more thorough in my analysis. It's adding new ioctls – `FS_IOC_WRITE_STREAM_GET_MAX`, `FS_IOC_WRITE_STREAM_OPEN`, `FS_IOC_WRITE_STREAM_SET`, and `FS_IOC_WRITE_STREAM_GET` – which is the first red flag. New ioctls always warrant extra scrutiny, as they are a significant interface change and can introduce subtle interactions with existing functionality.

The modifications to `fs/xfs/xfs_ioctl.c` and `fs/xfs/xfs_inode.c` are expected, that's where the core ioctl handling and inode-level stream operations would reside. I want to drill into the `xfs_inode_*` functions. Implementing functions such as `xfs_inode_write_stream_open`, `xfs_inode_set_write_stream`, and the others indicate this is where the core logic of managing these streams will be.

The modification to `xfs_bmap.c` is particularly interesting. The comment that this uses the stream ID to "fan out allocations across AGs" is important. This suggests a potential performance benefit (and perhaps a key design goal of the patch), but also adds complexity to the block allocation logic. This is definitely somewhere I'll want to focus on when I'm assessing it.

The changes in `fs/iomap/direct-io.c` and `fs/iomap/ioend.c` that now pass the write stream ID to the bio are a natural consequence of the stream concept. This change is crucial to ensure that the iomap layer understands which stream is associated with each block I/O operation.

Given that this introduces new ioctls and modifies block allocation, fuzzing is absolutely essential. I need to identify the key functions to subject to fuzzing. Let's see... I'd start with these ioctl handlers: `xfs_ioc_write_stream_open`, `xfs_ioc_write_stream_set`, `xfs_ioc_write_stream_get`, `xfs_ioc_write_stream_get_max`. I also need to fuzz the associated inode methods: `xfs_inode_write_stream_open`, `xfs_inode_set_write_stream`, `xfs_inode_get_write_stream`, and `xfs_inode_max_write_streams`. It looks like `xfs_write_stream_release`, `xfs_inode_write_stream_ag_set`, and `xfs_ag_to_ag_set` are also important to test, in case they are used in stream management.

Finally, I need to verify dependencies. Since this is XFS-specific, `XFS_FS` is a given. But I want to check for any more specific config options. A quick grep of the Kconfig files for "WRITE_STREAM" should give me a quick answer. Good, this is a solid start. Now, let's dive into the code...



4/2 2026/07/17 16:06 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "WRITE_STREAM",
  "PathPrefix": "fs/xfs/Kconfig"
}

Results:
{
  "error": "no matches"
}

5/2 2026/07/17 16:06 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10516
							output: 23
							thoughts: 16
						

Thoughts:
Here's my thought process, summarized as you requested:

**Kconfig Exploration for WRITE_STREAM**

Alright, time to dive into this Kconfig labyrinth. My initial instinct is to go straight to the source. I'm going to start with a broad sweep – a classic `grep`. I want to see *everywhere* `WRITE_STREAM` is mentioned across all the Kconfig files. This will be the foundational scan. The goal isn't necessarily to solve anything immediately, but to get a lay of the land, understand the scope of the variable's usage. We're talking about a quick and dirty search for occurrences, dependencies, and perhaps some initial context, I want to use `grep` as a primary investigative tool for this.



6/2 2026/07/17 16:06 tool grepper
0m Args:
{
  "Expression": "WRITE_STREAM",
  "PathPrefix": ""
}

Results:
{
  "Output": "drivers/ata/libata-eh.c=2487=const char *ata_get_cmd_name(u8 command)\n--\ndrivers/ata/libata-eh.c-2515-\t\t{ ATA_CMD_WRITE_QUEUED,\t\t\"WRITE DMA QUEUED EXT\" },\ndrivers/ata/libata-eh.c:2516:\t\t{ ATA_CMD_WRITE_STREAM_EXT,\t\"WRITE STREAM EXT\" },\ndrivers/ata/libata-eh.c:2517:\t\t{ ATA_CMD_WRITE_STREAM_DMA_EXT, \"WRITE STREAM DMA EXT\" },\ndrivers/ata/libata-eh.c-2518-\t\t{ ATA_CMD_WRITE_FUA_EXT,\t\"WRITE DMA FUA EXT\" },\n--\ndrivers/ata/libata-scsi.c=3172=static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)\n--\ndrivers/ata/libata-scsi.c-3330-\tcase ATA_CMD_PIO_WRITE_EXT:\ndrivers/ata/libata-scsi.c:3331:\tcase ATA_CMD_WRITE_STREAM_DMA_EXT:\ndrivers/ata/libata-scsi.c:3332:\tcase ATA_CMD_WRITE_STREAM_EXT:\ndrivers/ata/libata-scsi.c-3333-\t\tqc-\u003esect_size = scmd-\u003edevice-\u003esector_size;\n--\ndrivers/scsi/hisi_sas/hisi_sas_main.c=54=u8 hisi_sas_get_ata_protocol(struct sas_task *task)\n--\ndrivers/scsi/hisi_sas/hisi_sas_main.c-94-\tcase ATA_CMD_WRITE_LOG_DMA_EXT:\ndrivers/scsi/hisi_sas/hisi_sas_main.c:95:\tcase ATA_CMD_WRITE_STREAM_DMA_EXT:\ndrivers/scsi/hisi_sas/hisi_sas_main.c-96-\tcase ATA_CMD_ZAC_MGMT_IN:\n--\nfs/xfs/xfs_inode.c=52=xfs_inode_max_write_streams(\n--\nfs/xfs/xfs_inode.c-86-\tnr_streams = nr_ags / ag_set_size;\nfs/xfs/xfs_inode.c:87:\treturn min_t(uint16_t, nr_streams, XFS_SW_WRITE_STREAMS_MAX);\nfs/xfs/xfs_inode.c-88-}\n--\nfs/xfs/xfs_inode.c=129=xfs_inode_write_stream_open(\n--\nfs/xfs/xfs_inode.c-137-\nfs/xfs/xfs_inode.c:138:\tif (flags \u0026 ~FS_WRITE_STREAM_OPEN_EXACT)\nfs/xfs/xfs_inode.c-139-\t\treturn -EINVAL;\n--\nfs/xfs/xfs_inode.c-150-\tspin_lock(\u0026mp-\u003em_streams_lock);\nfs/xfs/xfs_inode.c:151:\tif (flags \u0026 FS_WRITE_STREAM_OPEN_EXACT) {\nfs/xfs/xfs_inode.c-152-\t\tif (!*stream_idp || *stream_idp \u003e max) {\n--\nfs/xfs/xfs_ioctl.c=1278=xfs_file_ioctl(\n--\nfs/xfs/xfs_ioctl.c-1526-\t\treturn xfs_ioc_verify_media(filp, arg);\nfs/xfs/xfs_ioctl.c:1527:\tcase FS_IOC_WRITE_STREAM_OPEN:\nfs/xfs/xfs_ioctl.c-1528-\t\treturn xfs_ioc_write_stream_open(filp, (void __user *)arg);\nfs/xfs/xfs_ioctl.c:1529:\tcase FS_IOC_WRITE_STREAM_SET:\nfs/xfs/xfs_ioctl.c-1530-\t\treturn xfs_ioc_write_stream_set(filp, p);\nfs/xfs/xfs_ioctl.c:1531:\tcase FS_IOC_WRITE_STREAM_GET:\nfs/xfs/xfs_ioctl.c-1532-\t\treturn xfs_ioc_write_stream_get(XFS_I(file_inode(filp)),\nfs/xfs/xfs_ioctl.c-1533-\t\t\t\t\t\t(void __user *)arg);\nfs/xfs/xfs_ioctl.c:1534:\tcase FS_IOC_WRITE_STREAM_GET_MAX:\nfs/xfs/xfs_ioctl.c-1535-\t\treturn xfs_ioc_write_stream_get_max(XFS_I(file_inode(filp)),\n--\nfs/xfs/xfs_mount.h=38=enum {\n--\nfs/xfs/xfs_mount.h-47-\nfs/xfs/xfs_mount.h:48:#define XFS_SW_WRITE_STREAMS_MAX\t16\nfs/xfs/xfs_mount.h-49-\n--\nfs/xfs/xfs_super.c=1656=xfs_fs_fill_super(\n--\nfs/xfs/xfs_super.c-1714-\tif (!nr_streams)\nfs/xfs/xfs_super.c:1715:\t\tnr_streams = XFS_SW_WRITE_STREAMS_MAX;\nfs/xfs/xfs_super.c-1716-\tmp-\u003em_streams_in_use = bitmap_zalloc(nr_streams, GFP_KERNEL);\n--\ninclude/linux/ata.h=24=enum {\n--\ninclude/linux/ata.h-223-\tATA_CMD_WRITE_QUEUED\t= 0x36,\ninclude/linux/ata.h:224:\tATA_CMD_WRITE_STREAM_EXT = 0x3B,\ninclude/linux/ata.h:225:\tATA_CMD_WRITE_STREAM_DMA_EXT = 0x3A,\ninclude/linux/ata.h-226-\tATA_CMD_WRITE_FUA_EXT\t= 0x3D,\n--\ninclude/trace/events/libata.h-36-\t\t ata_opcode_name(ATA_CMD_WRITE_QUEUED),\t\t\\\ninclude/trace/events/libata.h:37:\t\t ata_opcode_name(ATA_CMD_WRITE_STREAM_EXT),\t\\\ninclude/trace/events/libata.h:38:\t\t ata_opcode_name(ATA_CMD_WRITE_STREAM_DMA_EXT), \\\ninclude/trace/events/libata.h-39-\t\t ata_opcode_name(ATA_CMD_WRITE_FUA_EXT),\t\\\n--\ninclude/uapi/linux/fs.h=348=struct fs_write_stream_open {\ninclude/uapi/linux/fs.h-349-\t__u32\t\tstream_id;\t/* IN: requested id if OPEN_EXACT set; OUT: assigned id */\ninclude/uapi/linux/fs.h:350:\t__u32\t\tflags;\t\t/* IN: FS_WRITE_STREAM_OPEN_* */\ninclude/uapi/linux/fs.h-351-};\n--\ninclude/uapi/linux/fs.h-356- */\ninclude/uapi/linux/fs.h:357:#define FS_WRITE_STREAM_OPEN_EXACT\t(1 \u003c\u003c 0)\ninclude/uapi/linux/fs.h-358-\ninclude/uapi/linux/fs.h:359:#define FS_IOC_WRITE_STREAM_GET_MAX\t_IOR('f', 135, __u32)\ninclude/uapi/linux/fs.h:360:#define FS_IOC_WRITE_STREAM_OPEN\t_IOWR('f', 136, struct fs_write_stream_open)\ninclude/uapi/linux/fs.h:361:#define FS_IOC_WRITE_STREAM_SET\t\t_IOW('f', 137, __s32)\ninclude/uapi/linux/fs.h:362:#define FS_IOC_WRITE_STREAM_GET\t\t_IOR('f', 138, __u32)\ninclude/uapi/linux/fs.h-363-\n"
}

7/2 2026/07/17 16:06 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12828
							output: 35
							thoughts: 219
						

Thoughts:
**My Initial Assessment of the Write Stream Feature**

Okay, so I'm looking at this write stream feature within XFS, and it seems there aren't any *specific* configuration options dedicated to it. It appears to be an inherent part of XFS itself. The only relevant config required is the core `XFS_FS` setting. That's good to know—less to manage explicitly.

Now, let's take a look at the focus symbols. I've got a list here: `xfs_ioc_write_stream_open`, `xfs_ioc_write_stream_set`, `xfs_ioc_write_stream_get`, `xfs_ioc_write_stream_get_max`, `xfs_inode_write_stream_open`, `xfs_inode_set_write_stream`, `xfs_inode_get_write_stream`, `xfs_inode_max_write_streams`, `xfs_write_stream_release`, `xfs_inode_write_stream_ag_set`, and `xfs_ag_to_ag_set`. I need to verify that these symbols are actually defined within the context of the XFS code. This is the next logical step to ensure that the implementation is complete and the feature is ready.



8/2 2026/07/17 16:06 tool grepper
0m Args:
{
  "Expression": "xfs_ioc_write_stream_open",
  "PathPrefix": "fs/xfs/"
}

Results:
{
  "Output": "fs/xfs/xfs_ioctl.c=1209=static int\nfs/xfs/xfs_ioctl.c:1210:xfs_ioc_write_stream_open(\nfs/xfs/xfs_ioctl.c-1211-\tstruct file\t\t*filp,\n--\nfs/xfs/xfs_ioctl.c=1278=xfs_file_ioctl(\n--\nfs/xfs/xfs_ioctl.c-1527-\tcase FS_IOC_WRITE_STREAM_OPEN:\nfs/xfs/xfs_ioctl.c:1528:\t\treturn xfs_ioc_write_stream_open(filp, (void __user *)arg);\nfs/xfs/xfs_ioctl.c-1529-\tcase FS_IOC_WRITE_STREAM_SET:\n"
}

9/2 2026/07/17 16:06 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13579
							output: 250
							thoughts: 277
						

Thoughts:
**My Fuzzing Strategy: Write Streams in XFS**

Okay, so this patch is a definite yes for fuzzing. It's making functional changes, specifically to XFS's block allocation and introducing these brand new ioctls. That's a textbook definition of a potential attack surface. We need to get a fuzzing campaign set up for this immediately.

The heart of it lies in the write streams support they're adding to XFS and iomap. This includes those new ioctls, which immediately flags it as something new that needs scrutiny. The block allocation changes, where it's fanning out based on the stream ID, that’s another critical area. Any modification to the core logic, especially around allocation, is a red flag. We need to poke and prod that new allocation behavior thoroughly.

The focus should be on these symbols:

*   `xfs_ioc_write_stream_open`, `xfs_ioc_write_stream_set`, `xfs_ioc_write_stream_get`, `xfs_ioc_write_stream_get_max`, `xfs_inode_write_stream_open`, `xfs_inode_set_write_stream`, `xfs_inode_get_write_stream`, `xfs_inode_max_write_streams`, `xfs_write_stream_release`, `xfs_inode_write_stream_ag_set`, `xfs_ag_to_ag_set`.

These seem to be the core functions for managing the streams, setting them up, retrieving, etc, and the allocation itself. These are going to be the prime targets for fuzzing.

We need to make sure the fuzzer is configured with `XFS_FS` enabled, obviously. This is all about testing that write streams functionality. Let's see if we can find some good edge cases in this new logic.



10/2 2026/07/17 16:06 tool set-results
0m Args:
{
  "EnableConfigs": [
    "XFS_FS"
  ],
  "FocusSymbols": [
    "xfs_ioc_write_stream_open",
    "xfs_ioc_write_stream_set",
    "xfs_ioc_write_stream_get",
    "xfs_ioc_write_stream_get_max",
    "xfs_inode_write_stream_open",
    "xfs_inode_set_write_stream",
    "xfs_inode_get_write_stream",
    "xfs_inode_max_write_streams",
    "xfs_write_stream_release",
    "xfs_inode_write_stream_ag_set",
    "xfs_ag_to_ag_set"
  ],
  "Reasoning": "The patch introduces write streams support to XFS and iomap, including new ioctls for managing write streams and modifications to block allocation logic to fan out allocations across AGs based on the stream ID. This is a functional change that modifies core filesystem logic and introduces new user-facing APIs, making it a prime candidate for fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "XFS_FS"
  ],
  "FocusSymbols": [
    "xfs_ioc_write_stream_open",
    "xfs_ioc_write_stream_set",
    "xfs_ioc_write_stream_get",
    "xfs_ioc_write_stream_get_max",
    "xfs_inode_write_stream_open",
    "xfs_inode_set_write_stream",
    "xfs_inode_get_write_stream",
    "xfs_inode_max_write_streams",
    "xfs_write_stream_release",
    "xfs_inode_write_stream_ag_set",
    "xfs_ag_to_ag_set"
  ],
  "Reasoning": "The patch introduces write streams support to XFS and iomap, including new ioctls for managing write streams and modifications to block allocation logic to fan out allocations across AGs based on the stream ID. This is a functional change that modifies core filesystem logic and introduces new user-facing APIs, making it a prime candidate for fuzzing.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)