summaryrefslogtreecommitdiffstats
path: root/fs/nilfs2/btnode.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nilfs2/btnode.c')
-rw-r--r--fs/nilfs2/btnode.c356
1 files changed, 356 insertions, 0 deletions
diff --git a/fs/nilfs2/btnode.c b/fs/nilfs2/btnode.c
new file mode 100644
index 000000000..680e4009a
--- /dev/null
+++ b/fs/nilfs2/btnode.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS B-tree node cache
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Originally written by Seiji Kihara.
+ * Fully revised by Ryusuke Konishi for stabilization and simplification.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/mm.h>
+#include <linux/backing-dev.h>
+#include <linux/gfp.h>
+#include "nilfs.h"
+#include "mdt.h"
+#include "dat.h"
+#include "page.h"
+#include "btnode.h"
+
+
+/**
+ * nilfs_init_btnc_inode - initialize B-tree node cache inode
+ * @btnc_inode: inode to be initialized
+ *
+ * nilfs_init_btnc_inode() sets up an inode for B-tree node cache.
+ */
+void nilfs_init_btnc_inode(struct inode *btnc_inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(btnc_inode);
+
+ btnc_inode->i_mode = S_IFREG;
+ ii->i_flags = 0;
+ memset(&ii->i_bmap_data, 0, sizeof(struct nilfs_bmap));
+ mapping_set_gfp_mask(btnc_inode->i_mapping, GFP_NOFS);
+ btnc_inode->i_mapping->a_ops = &nilfs_buffer_cache_aops;
+}
+
+void nilfs_btnode_cache_clear(struct address_space *btnc)
+{
+ invalidate_mapping_pages(btnc, 0, -1);
+ truncate_inode_pages(btnc, 0);
+}
+
+struct buffer_head *
+nilfs_btnode_create_block(struct address_space *btnc, __u64 blocknr)
+{
+ struct inode *inode = btnc->host;
+ struct buffer_head *bh;
+
+ bh = nilfs_grab_buffer(inode, btnc, blocknr, BIT(BH_NILFS_Node));
+ if (unlikely(!bh))
+ return ERR_PTR(-ENOMEM);
+
+ if (unlikely(buffer_mapped(bh) || buffer_uptodate(bh) ||
+ buffer_dirty(bh))) {
+ /*
+ * The block buffer at the specified new address was already
+ * in use. This can happen if it is a virtual block number
+ * and has been reallocated due to corruption of the bitmap
+ * used to manage its allocation state (if not, the buffer
+ * clearing of an abandoned b-tree node is missing somewhere).
+ */
+ nilfs_error(inode->i_sb,
+ "state inconsistency probably due to duplicate use of b-tree node block address %llu (ino=%llu)",
+ (unsigned long long)blocknr, inode->i_ino);
+ goto failed;
+ }
+ memset(bh->b_data, 0, i_blocksize(inode));
+ bh->b_blocknr = blocknr;
+ set_buffer_mapped(bh);
+ set_buffer_uptodate(bh);
+
+ folio_unlock(bh->b_folio);
+ folio_put(bh->b_folio);
+ return bh;
+
+failed:
+ folio_unlock(bh->b_folio);
+ folio_put(bh->b_folio);
+ brelse(bh);
+ return ERR_PTR(-EIO);
+}
+
+int nilfs_btnode_submit_block(struct address_space *btnc, __u64 blocknr,
+ sector_t pblocknr, blk_opf_t opf,
+ struct buffer_head **pbh, sector_t *submit_ptr)
+{
+ struct buffer_head *bh;
+ struct inode *inode = btnc->host;
+ struct folio *folio;
+ int err;
+
+ bh = nilfs_grab_buffer(inode, btnc, blocknr, BIT(BH_NILFS_Node));
+ if (unlikely(!bh))
+ return -ENOMEM;
+
+ err = -EEXIST; /* internal code */
+ folio = bh->b_folio;
+
+ if (buffer_uptodate(bh) || buffer_dirty(bh))
+ goto found;
+
+ if (pblocknr == 0) {
+ pblocknr = blocknr;
+ if (inode->i_ino != NILFS_DAT_INO) {
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+
+ /* blocknr is a virtual block number */
+ err = nilfs_dat_translate(nilfs->ns_dat, blocknr,
+ &pblocknr);
+ if (unlikely(err)) {
+ brelse(bh);
+ goto out_locked;
+ }
+ }
+ }
+
+ if (opf & REQ_RAHEAD) {
+ if (pblocknr != *submit_ptr + 1 || !trylock_buffer(bh)) {
+ err = -EBUSY; /* internal code */
+ brelse(bh);
+ goto out_locked;
+ }
+ } else { /* opf == REQ_OP_READ */
+ lock_buffer(bh);
+ }
+ if (buffer_uptodate(bh)) {
+ unlock_buffer(bh);
+ err = -EEXIST; /* internal code */
+ goto found;
+ }
+ set_buffer_mapped(bh);
+ bh->b_blocknr = pblocknr; /* set block address for read */
+ bh_submit(bh, opf, bh_end_read);
+ bh->b_blocknr = blocknr; /* set back to the given block address */
+ *submit_ptr = pblocknr;
+ err = 0;
+found:
+ *pbh = bh;
+
+out_locked:
+ folio_unlock(folio);
+ folio_put(folio);
+ return err;
+}
+
+/**
+ * nilfs_btnode_delete - delete B-tree node buffer
+ * @bh: buffer to be deleted
+ *
+ * nilfs_btnode_delete() invalidates the specified buffer and delete the page
+ * including the buffer if the page gets unbusy.
+ */
+void nilfs_btnode_delete(struct buffer_head *bh)
+{
+ struct address_space *mapping;
+ struct folio *folio = bh->b_folio;
+ pgoff_t index = folio->index;
+ int still_dirty;
+
+ folio_get(folio);
+ folio_lock(folio);
+ folio_wait_writeback(folio);
+
+ nilfs_forget_buffer(bh);
+ still_dirty = folio_test_dirty(folio);
+ mapping = folio->mapping;
+ folio_unlock(folio);
+ folio_put(folio);
+
+ if (!still_dirty && mapping)
+ invalidate_inode_pages2_range(mapping, index, index);
+}
+
+/**
+ * nilfs_btnode_prepare_change_key - prepare to change the search key of a
+ * b-tree node block
+ * @btnc: page cache in which the b-tree node block is buffered
+ * @ctxt: structure for exchanging context information for key change
+ *
+ * nilfs_btnode_prepare_change_key() prepares to move the contents of the
+ * b-tree node block of the old key given in the "oldkey" member of @ctxt to
+ * the position of the new key given in the "newkey" member of @ctxt in the
+ * page cache @btnc. Here, the key of the block is an index in units of
+ * blocks, and if the page and block sizes match, it matches the page index
+ * in the page cache.
+ *
+ * If the page size and block size match, this function attempts to move the
+ * entire folio, and in preparation for this, inserts the original folio into
+ * the new index of the cache. If this insertion fails or if the page size
+ * and block size are different, it falls back to a copy preparation using
+ * nilfs_btnode_create_block(), inserts a new block at the position
+ * corresponding to "newkey", and stores the buffer head pointer in the
+ * "newbh" member of @ctxt.
+ *
+ * Note that the current implementation does not support folio sizes larger
+ * than the page size.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_btnode_prepare_change_key(struct address_space *btnc,
+ struct nilfs_btnode_chkey_ctxt *ctxt)
+{
+ struct buffer_head *obh, *nbh;
+ struct inode *inode = btnc->host;
+ __u64 oldkey = ctxt->oldkey, newkey = ctxt->newkey;
+ int err;
+
+ if (oldkey == newkey)
+ return 0;
+
+ obh = ctxt->bh;
+ ctxt->newbh = NULL;
+
+ if (inode->i_blkbits == PAGE_SHIFT) {
+ struct folio *ofolio = obh->b_folio;
+ folio_lock(ofolio);
+retry:
+ /* BUG_ON(oldkey != obh->b_folio->index); */
+ if (unlikely(oldkey != ofolio->index))
+ NILFS_FOLIO_BUG(ofolio,
+ "invalid oldkey %lld (newkey=%lld)",
+ (unsigned long long)oldkey,
+ (unsigned long long)newkey);
+
+ xa_lock_irq(&btnc->i_pages);
+ err = __xa_insert(&btnc->i_pages, newkey, ofolio, GFP_NOFS);
+ xa_unlock_irq(&btnc->i_pages);
+ /*
+ * Note: folio->index will not change to newkey until
+ * nilfs_btnode_commit_change_key() will be called.
+ * To protect the folio in intermediate state, the folio lock
+ * is held.
+ */
+ if (!err)
+ return 0;
+ else if (err != -EBUSY)
+ goto failed_unlock;
+
+ err = invalidate_inode_pages2_range(btnc, newkey, newkey);
+ if (!err)
+ goto retry;
+ /* fallback to copy mode */
+ folio_unlock(ofolio);
+ }
+
+ nbh = nilfs_btnode_create_block(btnc, newkey);
+ if (IS_ERR(nbh))
+ return PTR_ERR(nbh);
+
+ BUG_ON(nbh == obh);
+ ctxt->newbh = nbh;
+ return 0;
+
+ failed_unlock:
+ folio_unlock(obh->b_folio);
+ return err;
+}
+
+/**
+ * nilfs_btnode_commit_change_key - commit the change of the search key of
+ * a b-tree node block
+ * @btnc: page cache in which the b-tree node block is buffered
+ * @ctxt: structure for exchanging context information for key change
+ *
+ * nilfs_btnode_commit_change_key() executes the key change based on the
+ * context @ctxt prepared by nilfs_btnode_prepare_change_key(). If no valid
+ * block buffer is prepared in "newbh" of @ctxt (i.e., a full folio move),
+ * this function removes the folio from the old index and completes the move.
+ * Otherwise, it copies the block data and inherited flag states of "oldbh"
+ * to "newbh" and clears the "oldbh" from the cache. In either case, the
+ * relocated buffer is marked as dirty.
+ *
+ * As with nilfs_btnode_prepare_change_key(), the current implementation does
+ * not support folio sizes larger than the page size.
+ */
+void nilfs_btnode_commit_change_key(struct address_space *btnc,
+ struct nilfs_btnode_chkey_ctxt *ctxt)
+{
+ struct buffer_head *obh = ctxt->bh, *nbh = ctxt->newbh;
+ __u64 oldkey = ctxt->oldkey, newkey = ctxt->newkey;
+ struct folio *ofolio;
+
+ if (oldkey == newkey)
+ return;
+
+ if (nbh == NULL) { /* blocksize == pagesize */
+ ofolio = obh->b_folio;
+ if (unlikely(oldkey != ofolio->index))
+ NILFS_FOLIO_BUG(ofolio,
+ "invalid oldkey %lld (newkey=%lld)",
+ (unsigned long long)oldkey,
+ (unsigned long long)newkey);
+ mark_buffer_dirty(obh);
+
+ xa_lock_irq(&btnc->i_pages);
+ __xa_erase(&btnc->i_pages, oldkey);
+ __xa_set_mark(&btnc->i_pages, newkey, PAGECACHE_TAG_DIRTY);
+ xa_unlock_irq(&btnc->i_pages);
+
+ ofolio->index = obh->b_blocknr = newkey;
+ folio_unlock(ofolio);
+ } else {
+ nilfs_copy_buffer(nbh, obh);
+ mark_buffer_dirty(nbh);
+
+ nbh->b_blocknr = newkey;
+ ctxt->bh = nbh;
+ nilfs_btnode_delete(obh); /* will decrement bh->b_count */
+ }
+}
+
+/**
+ * nilfs_btnode_abort_change_key - abort the change of the search key of a
+ * b-tree node block
+ * @btnc: page cache in which the b-tree node block is buffered
+ * @ctxt: structure for exchanging context information for key change
+ *
+ * nilfs_btnode_abort_change_key() cancels the key change associated with the
+ * context @ctxt prepared via nilfs_btnode_prepare_change_key() and performs
+ * any necessary cleanup. If no valid block buffer is prepared in "newbh" of
+ * @ctxt, this function removes the folio from the destination index and aborts
+ * the move. Otherwise, it clears "newbh" from the cache.
+ *
+ * As with nilfs_btnode_prepare_change_key(), the current implementation does
+ * not support folio sizes larger than the page size.
+ */
+void nilfs_btnode_abort_change_key(struct address_space *btnc,
+ struct nilfs_btnode_chkey_ctxt *ctxt)
+{
+ struct buffer_head *nbh = ctxt->newbh;
+ __u64 oldkey = ctxt->oldkey, newkey = ctxt->newkey;
+
+ if (oldkey == newkey)
+ return;
+
+ if (nbh == NULL) { /* blocksize == pagesize */
+ xa_erase_irq(&btnc->i_pages, newkey);
+ folio_unlock(ctxt->bh->b_folio);
+ } else {
+ /*
+ * When canceling a buffer that a prepare operation has
+ * allocated to copy a node block to another location, use
+ * nilfs_btnode_delete() to initialize and release the buffer
+ * so that the buffer flags will not be in an inconsistent
+ * state when it is reallocated.
+ */
+ nilfs_btnode_delete(nbh);
+ }
+}