This commit computes the maximum number of new extents that can be added to per-inode forks (data and xattr) at the beginning of a transaction. It then uses the helper function xfs_trans_resv_ext_cnt() to verify that the resulting sum does not overflow the corresponding on-disk inode extent counter. Signed-off-by: Chandan Babu R <chandanrlinux@xxxxxxxxx> --- fs/xfs/libxfs/xfs_attr.c | 33 ++++++++++++-- fs/xfs/libxfs/xfs_bmap.c | 7 +++ fs/xfs/xfs_bmap_item.c | 12 +++++ fs/xfs/xfs_bmap_util.c | 40 +++++++++++++++++ fs/xfs/xfs_dquot.c | 7 ++- fs/xfs/xfs_inode.c | 96 ++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_iomap.c | 19 ++++++++ fs/xfs/xfs_reflink.c | 35 +++++++++++++++ fs/xfs/xfs_rtalloc.c | 4 ++ fs/xfs/xfs_symlink.c | 18 ++++++++ 10 files changed, 267 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_attr.c b/fs/xfs/libxfs/xfs_attr.c index d4583a0d1b3f..745bf9293a17 100644 --- a/fs/xfs/libxfs/xfs_attr.c +++ b/fs/xfs/libxfs/xfs_attr.c @@ -142,7 +142,8 @@ xfs_attr_get( STATIC int xfs_attr_calc_size( struct xfs_da_args *args, - int *local) + int *local, + int *dsplit) { struct xfs_mount *mp = args->dp->i_mount; int size; @@ -157,7 +158,10 @@ xfs_attr_calc_size( if (*local) { if (size > (args->geo->blksize / 2)) { /* Double split possible */ + *dsplit = 1; nblks *= 2; + } else { + *dsplit = 0; } } else { /* @@ -395,7 +399,8 @@ xfs_attr_set( struct xfs_mount *mp = dp->i_mount; struct xfs_trans_res tres; bool rsvd = (args->attr_filter & XFS_ATTR_ROOT); - int error, local; + int error, local, dsplit; + int rsv_exts = 0; unsigned int total; if (XFS_FORCED_SHUTDOWN(dp->i_mount)) @@ -420,7 +425,7 @@ xfs_attr_set( XFS_STATS_INC(mp, xs_attr_set); args->op_flags |= XFS_DA_OP_ADDNAME; - args->total = xfs_attr_calc_size(args, &local); + args->total = xfs_attr_calc_size(args, &local, &dsplit); /* * If the inode doesn't have an attribute fork, add one. @@ -442,11 +447,19 @@ xfs_attr_set( tres.tr_logcount = XFS_ATTRSET_LOG_COUNT; tres.tr_logflags = XFS_TRANS_PERM_LOG_RES; total = args->total; + + if (local) { + if (dsplit) + ++rsv_exts; + } else { + rsv_exts += xfs_attr3_rmt_blocks(mp, args->valuelen); + } } else { XFS_STATS_INC(mp, xs_attr_remove); tres = M_RES(mp)->tr_attrrm; total = XFS_ATTRRM_SPACE_RES(mp); + rsv_exts += xfs_attr3_rmt_blocks(mp, XFS_XATTR_SIZE_MAX); } /* @@ -460,6 +473,20 @@ xfs_attr_set( xfs_ilock(dp, XFS_ILOCK_EXCL); xfs_trans_ijoin(args->trans, dp, 0); + + rsv_exts += XFS_DAENTER_BLOCKS(mp, XFS_ATTR_FORK); + + if (args->value || xfs_inode_hasattr(dp)) { + /* + * XFS_DA_NODE_MAXDEPTH blocks for dabtree. + * One extra block for dabtree in case of a double split. + * Extents for remote attributes. + */ + error = xfs_trans_resv_ext_cnt(dp, XFS_ATTR_FORK, rsv_exts); + if (error) + goto out_trans_cancel; + } + if (args->value) { unsigned int quota_flags = XFS_QMOPT_RES_REGBLKS; diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index 9c40d5971035..462869ab26b9 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -4527,6 +4527,13 @@ xfs_bmapi_convert_delalloc( return error; xfs_ilock(ip, XFS_ILOCK_EXCL); + + if (whichfork == XFS_DATA_FORK) { + error = xfs_trans_resv_ext_cnt(ip, whichfork, 1); + if (error) + goto out_trans_cancel; + } + xfs_trans_ijoin(tp, ip, 0); if (!xfs_iext_lookup_extent(ip, ifp, offset_fsb, &bma.icur, &bma.got) || diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index ec3691372e7c..de427444cc8a 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -519,6 +519,18 @@ xfs_bui_item_recover( } xfs_trans_ijoin(tp, ip, 0); + /* + * Removing an extent from the middle of an existing extent + * can cause the extent count to increase by 1. + * i.e. | Old extent | Hole | Old extent | + * + * Mapping a new extent into a file can cause the extent + * count to increase by 1. + */ + error = xfs_trans_resv_ext_cnt(ip, whichfork, 1); + if (error) + goto err_inode; + count = bmap->me_len; error = xfs_trans_log_finish_bmap_update(tp, budp, type, ip, whichfork, bmap->me_startoff, bmap->me_startblock, &count, state); diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index afdc7f8e0e70..a8cd0f7c6005 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -822,6 +822,10 @@ xfs_alloc_file_space( if (error) goto error1; + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto error0; + xfs_trans_ijoin(tp, ip, 0); error = xfs_bmapi_write(tp, ip, startoffset_fsb, @@ -886,6 +890,15 @@ xfs_unmap_extent( xfs_trans_ijoin(tp, ip, 0); + /* + * One extent encompasses the complete file offset range. + * Removing the file offset range causes extent count to + * increase by 1. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto out_trans_cancel; + error = xfs_bunmapi(tp, ip, startoffset_fsb, len_fsb, 0, 2, done); if (error) goto out_trans_cancel; @@ -1155,6 +1168,14 @@ xfs_insert_file_space( xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, 0); + /* + * Splitting the extent mapping containing stop_fsb will cause + * extent count to increase by 1. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto out_trans_cancel; + /* * The extent shifting code works on extent granularity. So, if stop_fsb * is not the starting block of extent, we need to split the extent at @@ -1356,6 +1377,25 @@ xfs_swap_extent_rmap( /* Unmap the old blocks in the source file. */ while (tirec.br_blockcount) { ASSERT(tp->t_firstblock == NULLFSBLOCK); + + /* + * Removing an initial part of source file's extent and + * adding a new extent (from donor file) in its place + * will cause extent count to increase by 1. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto out; + + /* + * Removing an initial part of donor file's extent and + * adding a new extent (from source file) in its place + * will cause extent count to increase by 1. + */ + error = xfs_trans_resv_ext_cnt(tip, XFS_DATA_FORK, 1); + if (error) + goto out; + trace_xfs_swap_extent_rmap_remap_piece(tip, &tirec); /* Read extent from the source file */ diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index 04dc2be19c3a..582f050595bc 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -290,8 +290,13 @@ xfs_dquot_disk_alloc( return -ESRCH; } - /* Create the block mapping. */ xfs_trans_ijoin(tp, quotip, XFS_ILOCK_EXCL); + + error = xfs_trans_resv_ext_cnt(quotip, XFS_DATA_FORK, 1); + if (error) + return error; + + /* Create the block mapping. */ error = xfs_bmapi_write(tp, quotip, dqp->q_fileoffset, XFS_DQUOT_CLUSTER_SIZE_FSB, XFS_BMAPI_METADATA, 0, &map, &nmaps); diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 407d6299606d..007a719c2cdf 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1175,6 +1175,24 @@ xfs_create( if (error) goto out_trans_cancel; + /* + * Directory entry addition can cause the following, + * 1. Data block can be added. + * A new extent can cause extent count to increase by 1. + * 2. Free disk block can be added. + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be added. Each of these + * can be new extents. Hence extent count can increase by + * XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + error = xfs_trans_resv_ext_cnt(dp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto out_trans_cancel; + /* * A newly created regular or special file just has one directory * entry pointing to them, but a directory also the "." entry @@ -1391,6 +1409,24 @@ xfs_link( xfs_trans_ijoin(tp, sip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, tdp, XFS_ILOCK_EXCL); + /* + * Creating a new link can cause the following, + * 1. Data block can be added. + * A new extent can cause extent count to increase by 1. + * 2. Free disk block can be added. + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be added. Each of these + * can be new extents. Hence extent count can increase by + * XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + error = xfs_trans_resv_ext_cnt(tdp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto error_return; + /* * If we are using project inheritance, we only allow hard link * creation in our tree when the project IDs are the same; else @@ -2861,6 +2897,26 @@ xfs_remove( xfs_trans_ijoin(tp, dp, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); + /* + * Directory entry removal can cause the following, + * 1. Data block can be freed. + * 3 data blocks can be contiguous. Deletion of a single + * data block can cause this single extent to be split into + * two. Hence extent count can increase by 1. + * 2. Free disk block + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be freed. Each of these + * blocks can cause a single extent to be split into two. + * Hence extent count can increase by XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + error = xfs_trans_resv_ext_cnt(dp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto out_trans_cancel; + /* * If we're removing a directory perform some additional validation. */ @@ -3221,6 +3277,46 @@ xfs_rename( if (wip) xfs_trans_ijoin(tp, wip, XFS_ILOCK_EXCL); + /* + * Directory entry removal can cause the following, + * 1. Data block can be freed. + * 3 data blocks can be contiguous. Deletion of a single + * data block can cause this single extent to be split into + * two. Hence extent count can increase by 1. + * 2. Free disk block + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be freed. Each of these + * blocks can cause a single extent to be split into two. + * Hence extent count can increase by XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + error = xfs_trans_resv_ext_cnt(src_dp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto out_trans_cancel; + + /* + * Directory entry addition can cause the following, + * 1. Data block can be added. + * A new extent can cause extent count to increase by 1. + * 2. Free disk block can be added. + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be added. Each of these + * can be new extents. Hence extent count can increase by + * XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + if (target_ip == NULL) { + error = xfs_trans_resv_ext_cnt(target_dp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto out_trans_cancel; + } + /* * If we are using project inheritance, we only allow renames * into our tree when the project IDs are the same; else the diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 0e3f62cde375..e7ea3b7bbb0d 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -250,6 +250,14 @@ xfs_iomap_write_direct( if (error) goto out_trans_cancel; + /* + * Writing to a hole or extending a file can cause + * the extent count to increase by 1. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto out_trans_cancel; + xfs_trans_ijoin(tp, ip, 0); /* @@ -561,6 +569,17 @@ xfs_iomap_write_unwritten( if (error) goto error_on_bmapi_transaction; + /* + * We might be writing to the middle region of an + * existing unwritten extent. This causes the original + * extent to be split into 3 extents + * i.e. | Unwritten | Real | Unwritten | + * Hence extent count can increase by 2. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 2); + if (error) + goto error_on_bmapi_transaction; + /* * Modify the unwritten extent state of the buffer. */ diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index aac83f9d6107..45fa89558cdb 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -29,6 +29,7 @@ #include "xfs_iomap.h" #include "xfs_sb.h" #include "xfs_ag_resv.h" +#include "xfs_trans_resv.h" /* * Copy on Write of Shared Blocks @@ -628,6 +629,17 @@ xfs_reflink_end_cow_extent( xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, 0); + /* + * Extents are unmapped starting from "end_fsb - 1" and moves + * towards offset_fsb. A data fork extent containing + * "end_fsb - 1" can be split into three parts i.e. + * | Old extent | New extent | Old extent | + * Hence number of extents increases by 2. + */ + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 2); + if (error) + goto out_cancel; + /* * In case of racing, overlapping AIO writes no COW extents might be * left by the time I/O completes for the loser of the race. In that @@ -1002,6 +1014,7 @@ xfs_reflink_remap_extent( bool smap_real; bool dmap_written = xfs_bmap_is_written_extent(dmap); int nimaps; + int resv_exts = 0; int error; /* Start a rolling transaction to switch the mappings */ @@ -1094,6 +1107,28 @@ xfs_reflink_remap_extent( goto out_cancel; } + /* + * When unmapping, an extent containing the entire unmap + * range can be split into two extents, + * i.e. | old extent | hole | old extent | + * Hence extent count increases by 1. + */ + if (smap_real) + ++resv_exts; + + /* + * Mapping in the new extent into the destination file can + * increase the extent count by 1. + */ + if (dmap_written) + ++resv_exts; + + if (resv_exts) { + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, resv_exts); + if (error) + goto out_cancel; + } + if (smap_real) { /* * If the extent we're unmapping is backed by storage (written diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 6209e7b6b895..a2e640f43f1e 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -787,6 +787,10 @@ xfs_growfs_rt_alloc( xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); + error = xfs_trans_resv_ext_cnt(ip, XFS_DATA_FORK, 1); + if (error) + goto out_trans_cancel; + /* * Allocate blocks to the bitmap file. */ diff --git a/fs/xfs/xfs_symlink.c b/fs/xfs/xfs_symlink.c index 8e88a7ca387e..1bc71576289d 100644 --- a/fs/xfs/xfs_symlink.c +++ b/fs/xfs/xfs_symlink.c @@ -220,6 +220,24 @@ xfs_symlink( if (error) goto out_trans_cancel; + /* + * Creating a new symlink can cause the following, + * 1. Data block can be added. + * A new extent can cause extent count to increase by 1. + * 2. Free disk block can be added. + * Same behaviour as described above for Data block. + * 3. Dabtree blocks. + * XFS_DA_NODE_MAXDEPTH blocks can be added. Each of these + * can be new extents. Hence extent count can increase by + * XFS_DA_NODE_MAXDEPTH. + * Total = XFS_DA_NODE_MAXDEPTH + 1 + 1; + */ + error = xfs_trans_resv_ext_cnt(dp, XFS_DATA_FORK, + (XFS_DA_NODE_MAXDEPTH + 1 + 1) * + mp->m_dir_geo->fsbcount); + if (error) + goto out_trans_cancel; + /* * Allocate an inode for the symlink. */ -- 2.20.1