summaryrefslogtreecommitdiff
path: root/fs/xfs/libxfs/xfs_imeta.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/libxfs/xfs_imeta.c')
-rw-r--r--fs/xfs/libxfs/xfs_imeta.c188
1 files changed, 188 insertions, 0 deletions
diff --git a/fs/xfs/libxfs/xfs_imeta.c b/fs/xfs/libxfs/xfs_imeta.c
index 4b383bc7d7cd..4b86280d6d37 100644
--- a/fs/xfs/libxfs/xfs_imeta.c
+++ b/fs/xfs/libxfs/xfs_imeta.c
@@ -25,6 +25,10 @@
#include "xfs_trans_space.h"
#include "xfs_dir2.h"
#include "xfs_ag.h"
+#include "xfs_errortag.h"
+#include "xfs_error.h"
+#include "xfs_btree.h"
+#include "xfs_alloc.h"
/*
* Metadata Inode Number Management
@@ -1034,3 +1038,187 @@ xfs_imeta_droplink(
xfs_is_metadata_inode(ip))
ip->i_diflags2 &= ~XFS_DIFLAG2_METADATA;
}
+
+/*
+ * Is the amount of space that could be allocated towards a given metadata
+ * file at or beneath a certain threshold?
+ */
+static inline bool
+xfs_imeta_resv_can_cover(
+ struct xfs_inode *ip,
+ int64_t rhs)
+{
+ /*
+ * The amount of space that can be allocated to this metadata file is
+ * the global free block count + all remaining reservation for the
+ * metadata file. First take care of the trivial case so that we don't
+ * have to touch the per-cpu counter.
+ */
+ if (ip->i_delayed_blks > rhs)
+ return false;
+
+ /*
+ * There aren't enough blocks left in the inode's reservation, but it
+ * isn't critical unless there also isn't enough free space.
+ */
+ return __percpu_counter_compare(&ip->i_mount->m_fdblocks,
+ rhs - ip->i_delayed_blks, 2048) <= 0;
+}
+
+/*
+ * Is this metadata file critically low on blocks? For now we'll define that
+ * as the number of blocks we can get our hands on being less than 10% of what
+ * we reserved or less than some arbitrary number (maximum btree height).
+ */
+bool
+xfs_imeta_resv_critical(
+ struct xfs_mount *mp,
+ struct xfs_inode *ip)
+{
+ uint64_t asked_low_water;
+ xfs_extlen_t btree_maxlevels;
+
+ if (!ip)
+ return false;
+
+ ASSERT(xfs_is_metadata_inode(ip));
+ trace_xfs_imeta_resv_critical(ip, 0);
+
+ /* Critically low if less than 10% or max btree height remains. */
+ asked_low_water = div_u64(ip->i_meta_resv_asked, 10);
+ btree_maxlevels = xfs_btree_maxlevels(mp, XFS_BTNUM_MAX);
+ return XFS_TEST_ERROR(xfs_imeta_resv_can_cover(ip, asked_low_water) ||
+ xfs_imeta_resv_can_cover(ip, btree_maxlevels),
+ mp, XFS_ERRTAG_IMETA_RESV_CRITICAL);
+}
+
+/* Allocate a block from the metadata file's reservation. */
+void
+xfs_imeta_resv_alloc_extent(
+ struct xfs_inode *ip,
+ struct xfs_alloc_arg *args)
+{
+ int64_t len = args->len;
+
+ ASSERT(xfs_is_metadata_inode(ip));
+ ASSERT(args->resv == XFS_AG_RESV_IMETA);
+
+ trace_xfs_imeta_resv_alloc_extent(ip, args->len);
+
+ /*
+ * Allocate the blocks from the metadata inode's block reservation
+ * and update the ondisk sb counter.
+ */
+ if (ip->i_delayed_blks > 0) {
+ int64_t from_resv;
+
+ from_resv = min_t(int64_t, len, ip->i_delayed_blks);
+ ip->i_delayed_blks -= from_resv;
+ xfs_mod_delalloc(ip->i_mount, -from_resv);
+ xfs_trans_mod_sb(args->tp, XFS_TRANS_SB_RES_FDBLOCKS,
+ -from_resv);
+ len -= from_resv;
+ }
+
+ /*
+ * Any allocation in excess of the reservation requires in-core and
+ * on-disk fdblocks updates.
+ */
+ if (len)
+ xfs_trans_mod_sb(args->tp, XFS_TRANS_SB_FDBLOCKS, -len);
+
+ ip->i_nblocks += args->len;
+}
+
+/* Free a block to the metadata file's reservation. */
+void
+xfs_imeta_resv_free_extent(
+ struct xfs_inode *ip,
+ struct xfs_trans *tp,
+ xfs_filblks_t len)
+{
+ int64_t to_resv;
+
+ ASSERT(xfs_is_metadata_inode(ip));
+ trace_xfs_imeta_resv_free_extent(ip, len);
+
+ ip->i_nblocks -= len;
+
+ /*
+ * Add the freed blocks back into the inode's delalloc reservation
+ * until it reaches the maximum size. Update the ondisk fdblocks only.
+ */
+ to_resv = ip->i_meta_resv_asked - (ip->i_nblocks + ip->i_delayed_blks);
+ if (to_resv > 0) {
+ to_resv = min_t(int64_t, to_resv, len);
+ ip->i_delayed_blks += to_resv;
+ xfs_mod_delalloc(ip->i_mount, to_resv);
+ xfs_trans_mod_sb(tp, XFS_TRANS_SB_RES_FDBLOCKS, to_resv);
+ len -= to_resv;
+ }
+
+ /*
+ * Everything else goes back to the filesystem, so update the in-core
+ * and on-disk counters.
+ */
+ if (len)
+ xfs_trans_mod_sb(tp, XFS_TRANS_SB_FDBLOCKS, len);
+}
+
+/* Release a metadata file's space reservation. */
+void
+xfs_imeta_resv_free_inode(
+ struct xfs_mount *mp,
+ struct xfs_inode *ip)
+{
+ if (!ip)
+ return;
+
+ ASSERT(xfs_is_metadata_inode(ip));
+ trace_xfs_imeta_resv_free(ip, 0);
+
+ xfs_mod_delalloc(ip->i_mount, -ip->i_delayed_blks);
+ xfs_mod_fdblocks(ip->i_mount, ip->i_delayed_blks, true);
+ ip->i_delayed_blks = 0;
+ ip->i_meta_resv_asked = 0;
+}
+
+/* Set up a metadata file's space reservation. */
+int
+xfs_imeta_resv_init_inode(
+ struct xfs_mount *mp,
+ struct xfs_inode *ip,
+ xfs_filblks_t ask)
+{
+ xfs_filblks_t hidden_space;
+ xfs_filblks_t used;
+ int error;
+
+ if (!ip || ip->i_meta_resv_asked > 0)
+ return 0;
+
+ ASSERT(xfs_is_metadata_inode(ip));
+
+ /*
+ * Space taken by all other metadata btrees are accounted on-disk as
+ * used space. We therefore only hide the space that is reserved but
+ * not used by the trees.
+ */
+ used = ip->i_nblocks;
+ if (used > ask)
+ ask = used;
+ hidden_space = ask - used;
+
+ error = xfs_mod_fdblocks(mp, -(int64_t)hidden_space, true);
+ if (error) {
+ trace_xfs_imeta_resv_init_error(ip, error, _RET_IP_);
+ return error;
+ }
+
+ xfs_mod_delalloc(mp, hidden_space);
+ ip->i_delayed_blks = hidden_space;
+ ip->i_meta_resv_asked = ask;
+
+ trace_xfs_imeta_resv_init(ip, ask);
+ return 0;
+}