summaryrefslogtreecommitdiffstats
path: root/fs/nilfs2
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837 /fs/nilfs2
downloadlinux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz
linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
Diffstat (limited to 'fs/nilfs2')
-rw-r--r--fs/nilfs2/Kconfig27
-rw-r--r--fs/nilfs2/Makefile6
-rw-r--r--fs/nilfs2/alloc.c958
-rw-r--r--fs/nilfs2/alloc.h99
-rw-r--r--fs/nilfs2/bmap.c568
-rw-r--r--fs/nilfs2/bmap.h283
-rw-r--r--fs/nilfs2/btnode.c356
-rw-r--r--fs/nilfs2/btnode.h47
-rw-r--r--fs/nilfs2/btree.c2485
-rw-r--r--fs/nilfs2/btree.h65
-rw-r--r--fs/nilfs2/cpfile.c1184
-rw-r--r--fs/nilfs2/cpfile.h36
-rw-r--r--fs/nilfs2/dat.c541
-rw-r--r--fs/nilfs2/dat.h47
-rw-r--r--fs/nilfs2/dir.c665
-rw-r--r--fs/nilfs2/direct.c387
-rw-r--r--fs/nilfs2/direct.h28
-rw-r--r--fs/nilfs2/export.h26
-rw-r--r--fs/nilfs2/file.c165
-rw-r--r--fs/nilfs2/gcinode.c184
-rw-r--r--fs/nilfs2/ifile.c215
-rw-r--r--fs/nilfs2/ifile.h45
-rw-r--r--fs/nilfs2/inode.c1229
-rw-r--r--fs/nilfs2/ioctl.c1415
-rw-r--r--fs/nilfs2/mdt.c678
-rw-r--r--fs/nilfs2/mdt.h115
-rw-r--r--fs/nilfs2/namei.c594
-rw-r--r--fs/nilfs2/nilfs.h415
-rw-r--r--fs/nilfs2/page.c579
-rw-r--r--fs/nilfs2/page.h55
-rw-r--r--fs/nilfs2/recovery.c1016
-rw-r--r--fs/nilfs2/segbuf.c472
-rw-r--r--fs/nilfs2/segbuf.h172
-rw-r--r--fs/nilfs2/segment.c2929
-rw-r--r--fs/nilfs2/segment.h244
-rw-r--r--fs/nilfs2/sufile.c1273
-rw-r--r--fs/nilfs2/sufile.h162
-rw-r--r--fs/nilfs2/super.c1413
-rw-r--r--fs/nilfs2/sysfs.c1140
-rw-r--r--fs/nilfs2/sysfs.h167
-rw-r--r--fs/nilfs2/the_nilfs.c945
-rw-r--r--fs/nilfs2/the_nilfs.h376
42 files changed, 23806 insertions, 0 deletions
diff --git a/fs/nilfs2/Kconfig b/fs/nilfs2/Kconfig
new file mode 100644
index 000000000..7dae168e3
--- /dev/null
+++ b/fs/nilfs2/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NILFS2_FS
+ tristate "NILFS2 file system support"
+ select BUFFER_HEAD
+ select CRC32
+ select LEGACY_DIRECT_IO
+ help
+ NILFS2 is a log-structured file system (LFS) supporting continuous
+ snapshotting. In addition to versioning capability of the entire
+ file system, users can even restore files mistakenly overwritten or
+ destroyed just a few seconds ago. Since this file system can keep
+ consistency like conventional LFS, it achieves quick recovery after
+ system crashes.
+
+ NILFS2 creates a number of checkpoints every few seconds or per
+ synchronous write basis (unless there is no change). Users can
+ select significant versions among continuously created checkpoints,
+ and can change them into snapshots which will be preserved for long
+ periods until they are changed back to checkpoints. Each
+ snapshot is mountable as a read-only file system concurrently with
+ its writable mount, and this feature is convenient for online backup.
+
+ Some features including atime, extended attributes, and POSIX ACLs,
+ are not supported yet.
+
+ To compile this file system support as a module, choose M here: the
+ module will be called nilfs2. If unsure, say N.
diff --git a/fs/nilfs2/Makefile b/fs/nilfs2/Makefile
new file mode 100644
index 000000000..43b60b8a4
--- /dev/null
+++ b/fs/nilfs2/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_NILFS2_FS) += nilfs2.o
+nilfs2-y := inode.o file.o dir.o super.o namei.o page.o mdt.o \
+ btnode.o bmap.o btree.o direct.o dat.o recovery.o \
+ the_nilfs.o segbuf.o segment.o cpfile.o sufile.o \
+ ifile.o alloc.o gcinode.o ioctl.o sysfs.o
diff --git a/fs/nilfs2/alloc.c b/fs/nilfs2/alloc.c
new file mode 100644
index 000000000..7b1cd2bae
--- /dev/null
+++ b/fs/nilfs2/alloc.c
@@ -0,0 +1,958 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS dat/inode allocator
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Originally written by Koji Sato.
+ * Two allocators were unified by Ryusuke Konishi and Amagai Yoshiji.
+ */
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include "mdt.h"
+#include "alloc.h"
+
+
+/**
+ * nilfs_palloc_groups_per_desc_block - get the number of groups that a group
+ * descriptor block can maintain
+ * @inode: inode of metadata file using this allocator
+ *
+ * Return: Number of groups that a group descriptor block can maintain.
+ */
+static inline unsigned long
+nilfs_palloc_groups_per_desc_block(const struct inode *inode)
+{
+ return i_blocksize(inode) /
+ sizeof(struct nilfs_palloc_group_desc);
+}
+
+/**
+ * nilfs_palloc_groups_count - get maximum number of groups
+ * @inode: inode of metadata file using this allocator
+ *
+ * Return: Maximum number of groups.
+ */
+static inline unsigned long
+nilfs_palloc_groups_count(const struct inode *inode)
+{
+ return 1UL << (BITS_PER_LONG - (inode->i_blkbits + 3 /* log2(8) */));
+}
+
+/**
+ * nilfs_palloc_init_blockgroup - initialize private variables for allocator
+ * @inode: inode of metadata file using this allocator
+ * @entry_size: size of the persistent object
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_palloc_init_blockgroup(struct inode *inode, unsigned int entry_size)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+
+ mi->mi_bgl = kmalloc_obj(*mi->mi_bgl, GFP_NOFS);
+ if (!mi->mi_bgl)
+ return -ENOMEM;
+
+ bgl_lock_init(mi->mi_bgl);
+
+ nilfs_mdt_set_entry_size(inode, entry_size, 0);
+
+ mi->mi_blocks_per_group =
+ DIV_ROUND_UP(nilfs_palloc_entries_per_group(inode),
+ mi->mi_entries_per_block) + 1;
+ /*
+ * Number of blocks in a group including entry blocks
+ * and a bitmap block
+ */
+ mi->mi_blocks_per_desc_block =
+ nilfs_palloc_groups_per_desc_block(inode) *
+ mi->mi_blocks_per_group + 1;
+ /*
+ * Number of blocks per descriptor including the
+ * descriptor block
+ */
+ return 0;
+}
+
+/**
+ * nilfs_palloc_group - get group number and offset from an entry number
+ * @inode: inode of metadata file using this allocator
+ * @nr: serial number of the entry (e.g. inode number)
+ * @offset: pointer to store offset number in the group
+ *
+ * Return: Number of the group that contains the entry with the index
+ * specified by @nr.
+ */
+static unsigned long nilfs_palloc_group(const struct inode *inode, __u64 nr,
+ unsigned long *offset)
+{
+ __u64 group = nr;
+
+ *offset = do_div(group, nilfs_palloc_entries_per_group(inode));
+ return group;
+}
+
+/**
+ * nilfs_palloc_desc_blkoff - get block offset of a group descriptor block
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ *
+ * Return: Index number in the metadata file of the descriptor block of
+ * the group specified by @group.
+ */
+static unsigned long
+nilfs_palloc_desc_blkoff(const struct inode *inode, unsigned long group)
+{
+ unsigned long desc_block =
+ group / nilfs_palloc_groups_per_desc_block(inode);
+ return desc_block * NILFS_MDT(inode)->mi_blocks_per_desc_block;
+}
+
+/**
+ * nilfs_palloc_bitmap_blkoff - get block offset of a bitmap block
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ *
+ * nilfs_palloc_bitmap_blkoff() returns block offset of the bitmap
+ * block used to allocate/deallocate entries in the specified group.
+ *
+ * Return: Index number in the metadata file of the bitmap block of
+ * the group specified by @group.
+ */
+static unsigned long
+nilfs_palloc_bitmap_blkoff(const struct inode *inode, unsigned long group)
+{
+ unsigned long desc_offset =
+ group % nilfs_palloc_groups_per_desc_block(inode);
+ return nilfs_palloc_desc_blkoff(inode, group) + 1 +
+ desc_offset * NILFS_MDT(inode)->mi_blocks_per_group;
+}
+
+/**
+ * nilfs_palloc_group_desc_nfrees - get the number of free entries in a group
+ * @desc: pointer to descriptor structure for the group
+ * @lock: spin lock protecting @desc
+ *
+ * Return: Number of free entries written in the group descriptor @desc.
+ */
+static unsigned long
+nilfs_palloc_group_desc_nfrees(const struct nilfs_palloc_group_desc *desc,
+ spinlock_t *lock)
+{
+ unsigned long nfree;
+
+ spin_lock(lock);
+ nfree = le32_to_cpu(desc->pg_nfrees);
+ spin_unlock(lock);
+ return nfree;
+}
+
+/**
+ * nilfs_palloc_group_desc_add_entries - adjust count of free entries
+ * @desc: pointer to descriptor structure for the group
+ * @lock: spin lock protecting @desc
+ * @n: delta to be added
+ *
+ * Return: Number of free entries after adjusting the group descriptor
+ * @desc.
+ */
+static u32
+nilfs_palloc_group_desc_add_entries(struct nilfs_palloc_group_desc *desc,
+ spinlock_t *lock, u32 n)
+{
+ u32 nfree;
+
+ spin_lock(lock);
+ le32_add_cpu(&desc->pg_nfrees, n);
+ nfree = le32_to_cpu(desc->pg_nfrees);
+ spin_unlock(lock);
+ return nfree;
+}
+
+/**
+ * nilfs_palloc_entry_blkoff - get block offset of an entry block
+ * @inode: inode of metadata file using this allocator
+ * @nr: serial number of the entry (e.g. inode number)
+ *
+ * Return: Index number in the metadata file of the block containing
+ * the entry specified by @nr.
+ */
+static unsigned long
+nilfs_palloc_entry_blkoff(const struct inode *inode, __u64 nr)
+{
+ unsigned long group, group_offset;
+
+ group = nilfs_palloc_group(inode, nr, &group_offset);
+
+ return nilfs_palloc_bitmap_blkoff(inode, group) + 1 +
+ group_offset / NILFS_MDT(inode)->mi_entries_per_block;
+}
+
+/**
+ * nilfs_palloc_desc_block_init - initialize buffer of a group descriptor block
+ * @inode: inode of metadata file
+ * @bh: buffer head of the buffer to be initialized
+ * @from: kernel address mapped for a chunk of the block
+ *
+ * This function does not yet support the case where block size > PAGE_SIZE.
+ */
+static void nilfs_palloc_desc_block_init(struct inode *inode,
+ struct buffer_head *bh, void *from)
+{
+ struct nilfs_palloc_group_desc *desc = from;
+ unsigned long n = nilfs_palloc_groups_per_desc_block(inode);
+ __le32 nfrees;
+
+ nfrees = cpu_to_le32(nilfs_palloc_entries_per_group(inode));
+ while (n-- > 0) {
+ desc->pg_nfrees = nfrees;
+ desc++;
+ }
+}
+
+static int nilfs_palloc_get_block(struct inode *inode, unsigned long blkoff,
+ int create,
+ void (*init_block)(struct inode *,
+ struct buffer_head *,
+ void *),
+ struct buffer_head **bhp,
+ struct nilfs_bh_assoc *prev,
+ spinlock_t *lock)
+{
+ int ret;
+
+ spin_lock(lock);
+ if (prev->bh && blkoff == prev->blkoff &&
+ likely(buffer_uptodate(prev->bh))) {
+ get_bh(prev->bh);
+ *bhp = prev->bh;
+ spin_unlock(lock);
+ return 0;
+ }
+ spin_unlock(lock);
+
+ ret = nilfs_mdt_get_block(inode, blkoff, create, init_block, bhp);
+ if (!ret) {
+ spin_lock(lock);
+ /*
+ * The following code must be safe for change of the
+ * cache contents during the get block call.
+ */
+ brelse(prev->bh);
+ get_bh(*bhp);
+ prev->bh = *bhp;
+ prev->blkoff = blkoff;
+ spin_unlock(lock);
+ }
+ return ret;
+}
+
+/**
+ * nilfs_palloc_delete_block - delete a block on the persistent allocator file
+ * @inode: inode of metadata file using this allocator
+ * @blkoff: block offset
+ * @prev: nilfs_bh_assoc struct of the last used buffer
+ * @lock: spin lock protecting @prev
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Non-existent block.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_palloc_delete_block(struct inode *inode, unsigned long blkoff,
+ struct nilfs_bh_assoc *prev,
+ spinlock_t *lock)
+{
+ spin_lock(lock);
+ if (prev->bh && blkoff == prev->blkoff) {
+ brelse(prev->bh);
+ prev->bh = NULL;
+ }
+ spin_unlock(lock);
+ return nilfs_mdt_delete_block(inode, blkoff);
+}
+
+/**
+ * nilfs_palloc_get_desc_block - get buffer head of a group descriptor block
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ * @create: create flag
+ * @bhp: pointer to store the resultant buffer head
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_palloc_get_desc_block(struct inode *inode,
+ unsigned long group,
+ int create, struct buffer_head **bhp)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ return nilfs_palloc_get_block(inode,
+ nilfs_palloc_desc_blkoff(inode, group),
+ create, nilfs_palloc_desc_block_init,
+ bhp, &cache->prev_desc, &cache->lock);
+}
+
+/**
+ * nilfs_palloc_get_bitmap_block - get buffer head of a bitmap block
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ * @create: create flag
+ * @bhp: pointer to store the resultant buffer head
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_palloc_get_bitmap_block(struct inode *inode,
+ unsigned long group,
+ int create, struct buffer_head **bhp)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ return nilfs_palloc_get_block(inode,
+ nilfs_palloc_bitmap_blkoff(inode, group),
+ create, NULL, bhp,
+ &cache->prev_bitmap, &cache->lock);
+}
+
+/**
+ * nilfs_palloc_delete_bitmap_block - delete a bitmap block
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_palloc_delete_bitmap_block(struct inode *inode,
+ unsigned long group)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ return nilfs_palloc_delete_block(inode,
+ nilfs_palloc_bitmap_blkoff(inode,
+ group),
+ &cache->prev_bitmap, &cache->lock);
+}
+
+/**
+ * nilfs_palloc_get_entry_block - get buffer head of an entry block
+ * @inode: inode of metadata file using this allocator
+ * @nr: serial number of the entry (e.g. inode number)
+ * @create: create flag
+ * @bhp: pointer to store the resultant buffer head
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_palloc_get_entry_block(struct inode *inode, __u64 nr,
+ int create, struct buffer_head **bhp)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ return nilfs_palloc_get_block(inode,
+ nilfs_palloc_entry_blkoff(inode, nr),
+ create, NULL, bhp,
+ &cache->prev_entry, &cache->lock);
+}
+
+/**
+ * nilfs_palloc_delete_entry_block - delete an entry block
+ * @inode: inode of metadata file using this allocator
+ * @nr: serial number of the entry
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_palloc_delete_entry_block(struct inode *inode, __u64 nr)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ return nilfs_palloc_delete_block(inode,
+ nilfs_palloc_entry_blkoff(inode, nr),
+ &cache->prev_entry, &cache->lock);
+}
+
+/**
+ * nilfs_palloc_group_desc_offset - calculate the byte offset of a group
+ * descriptor in the folio containing it
+ * @inode: inode of metadata file using this allocator
+ * @group: group number
+ * @bh: buffer head of the group descriptor block
+ *
+ * Return: Byte offset in the folio of the group descriptor for @group.
+ */
+static size_t nilfs_palloc_group_desc_offset(const struct inode *inode,
+ unsigned long group,
+ const struct buffer_head *bh)
+{
+ return offset_in_folio(bh->b_folio, bh->b_data) +
+ sizeof(struct nilfs_palloc_group_desc) *
+ (group % nilfs_palloc_groups_per_desc_block(inode));
+}
+
+/**
+ * nilfs_palloc_bitmap_offset - calculate the byte offset of a bitmap block
+ * in the folio containing it
+ * @bh: buffer head of the bitmap block
+ *
+ * Return: Byte offset in the folio of the bitmap block for @bh.
+ */
+static size_t nilfs_palloc_bitmap_offset(const struct buffer_head *bh)
+{
+ return offset_in_folio(bh->b_folio, bh->b_data);
+}
+
+/**
+ * nilfs_palloc_entry_offset - calculate the byte offset of an entry in the
+ * folio containing it
+ * @inode: inode of metadata file using this allocator
+ * @nr: serial number of the entry (e.g. inode number)
+ * @bh: buffer head of the entry block
+ *
+ * Return: Byte offset in the folio of the entry @nr.
+ */
+size_t nilfs_palloc_entry_offset(const struct inode *inode, __u64 nr,
+ const struct buffer_head *bh)
+{
+ unsigned long entry_index_in_group, entry_index_in_block;
+
+ nilfs_palloc_group(inode, nr, &entry_index_in_group);
+ entry_index_in_block = entry_index_in_group %
+ NILFS_MDT(inode)->mi_entries_per_block;
+
+ return offset_in_folio(bh->b_folio, bh->b_data) +
+ entry_index_in_block * NILFS_MDT(inode)->mi_entry_size;
+}
+
+/**
+ * nilfs_palloc_find_available_slot - find available slot in a group
+ * @bitmap: bitmap of the group
+ * @target: offset number of an entry in the group (start point)
+ * @bsize: size in bits
+ * @lock: spin lock protecting @bitmap
+ * @wrap: whether to wrap around
+ *
+ * Return: Offset number within the group of the found free entry, or
+ * %-ENOSPC if not found.
+ */
+static int nilfs_palloc_find_available_slot(unsigned char *bitmap,
+ unsigned long target,
+ unsigned int bsize,
+ spinlock_t *lock, bool wrap)
+{
+ int pos, end = bsize;
+
+ if (likely(target < bsize)) {
+ pos = target;
+ do {
+ pos = nilfs_find_next_zero_bit(bitmap, end, pos);
+ if (pos >= end)
+ break;
+ if (!nilfs_set_bit_atomic(lock, pos, bitmap))
+ return pos;
+ } while (++pos < end);
+
+ end = target;
+ }
+ if (!wrap)
+ return -ENOSPC;
+
+ /* wrap around */
+ for (pos = 0; pos < end; pos++) {
+ pos = nilfs_find_next_zero_bit(bitmap, end, pos);
+ if (pos >= end)
+ break;
+ if (!nilfs_set_bit_atomic(lock, pos, bitmap))
+ return pos;
+ }
+
+ return -ENOSPC;
+}
+
+/**
+ * nilfs_palloc_rest_groups_in_desc_block - get the remaining number of groups
+ * in a group descriptor block
+ * @inode: inode of metadata file using this allocator
+ * @curr: current group number
+ * @max: maximum number of groups
+ *
+ * Return: Number of remaining descriptors (= groups) managed by the descriptor
+ * block.
+ */
+static unsigned long
+nilfs_palloc_rest_groups_in_desc_block(const struct inode *inode,
+ unsigned long curr, unsigned long max)
+{
+ return min_t(unsigned long,
+ nilfs_palloc_groups_per_desc_block(inode) -
+ curr % nilfs_palloc_groups_per_desc_block(inode),
+ max - curr + 1);
+}
+
+/**
+ * nilfs_palloc_count_desc_blocks - count descriptor blocks number
+ * @inode: inode of metadata file using this allocator
+ * @desc_blocks: descriptor blocks number [out]
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_palloc_count_desc_blocks(struct inode *inode,
+ unsigned long *desc_blocks)
+{
+ __u64 blknum;
+ int ret;
+
+ ret = nilfs_bmap_last_key(NILFS_I(inode)->i_bmap, &blknum);
+ if (likely(!ret))
+ *desc_blocks = DIV_ROUND_UP(
+ (unsigned long)blknum,
+ NILFS_MDT(inode)->mi_blocks_per_desc_block);
+ return ret;
+}
+
+/**
+ * nilfs_palloc_mdt_file_can_grow - check potential opportunity for
+ * MDT file growing
+ * @inode: inode of metadata file using this allocator
+ * @desc_blocks: known current descriptor blocks count
+ *
+ * Return: true if a group can be added in the metadata file, false if not.
+ */
+static inline bool nilfs_palloc_mdt_file_can_grow(struct inode *inode,
+ unsigned long desc_blocks)
+{
+ return (nilfs_palloc_groups_per_desc_block(inode) * desc_blocks) <
+ nilfs_palloc_groups_count(inode);
+}
+
+/**
+ * nilfs_palloc_count_max_entries - count max number of entries that can be
+ * described by descriptor blocks count
+ * @inode: inode of metadata file using this allocator
+ * @nused: current number of used entries
+ * @nmaxp: max number of entries [out]
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ERANGE - Number of entries in use is out of range.
+ */
+int nilfs_palloc_count_max_entries(struct inode *inode, u64 nused, u64 *nmaxp)
+{
+ unsigned long desc_blocks = 0;
+ u64 entries_per_desc_block, nmax;
+ int err;
+
+ err = nilfs_palloc_count_desc_blocks(inode, &desc_blocks);
+ if (unlikely(err))
+ return err;
+
+ entries_per_desc_block = (u64)nilfs_palloc_entries_per_group(inode) *
+ nilfs_palloc_groups_per_desc_block(inode);
+ nmax = entries_per_desc_block * desc_blocks;
+
+ if (nused == nmax &&
+ nilfs_palloc_mdt_file_can_grow(inode, desc_blocks))
+ nmax += entries_per_desc_block;
+
+ if (nused > nmax)
+ return -ERANGE;
+
+ *nmaxp = nmax;
+ return 0;
+}
+
+/**
+ * nilfs_palloc_prepare_alloc_entry - prepare to allocate a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the allocation
+ * @wrap: whether to wrap around
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - Entries exhausted (No entries available for allocation).
+ * * %-EROFS - Read only filesystem
+ */
+int nilfs_palloc_prepare_alloc_entry(struct inode *inode,
+ struct nilfs_palloc_req *req, bool wrap)
+{
+ struct buffer_head *desc_bh, *bitmap_bh;
+ struct nilfs_palloc_group_desc *desc;
+ unsigned char *bitmap;
+ size_t doff, boff;
+ unsigned long group, maxgroup, ngroups;
+ unsigned long group_offset, maxgroup_offset;
+ unsigned long n, entries_per_group;
+ unsigned long i, j;
+ spinlock_t *lock;
+ int pos, ret;
+
+ ngroups = nilfs_palloc_groups_count(inode);
+ maxgroup = ngroups - 1;
+ group = nilfs_palloc_group(inode, req->pr_entry_nr, &group_offset);
+ entries_per_group = nilfs_palloc_entries_per_group(inode);
+
+ for (i = 0; i < ngroups; i += n) {
+ if (group >= ngroups && wrap) {
+ /* wrap around */
+ group = 0;
+ maxgroup = nilfs_palloc_group(inode, req->pr_entry_nr,
+ &maxgroup_offset) - 1;
+ }
+ ret = nilfs_palloc_get_desc_block(inode, group, 1, &desc_bh);
+ if (ret < 0)
+ return ret;
+
+ doff = nilfs_palloc_group_desc_offset(inode, group, desc_bh);
+ desc = kmap_local_folio(desc_bh->b_folio, doff);
+ n = nilfs_palloc_rest_groups_in_desc_block(inode, group,
+ maxgroup);
+ for (j = 0; j < n; j++, group++, group_offset = 0) {
+ lock = nilfs_mdt_bgl_lock(inode, group);
+ if (nilfs_palloc_group_desc_nfrees(&desc[j], lock) == 0)
+ continue;
+
+ kunmap_local(desc);
+ ret = nilfs_palloc_get_bitmap_block(inode, group, 1,
+ &bitmap_bh);
+ if (unlikely(ret < 0)) {
+ brelse(desc_bh);
+ return ret;
+ }
+
+ /*
+ * Re-kmap the folio containing the first (and
+ * subsequent) group descriptors.
+ */
+ desc = kmap_local_folio(desc_bh->b_folio, doff);
+
+ boff = nilfs_palloc_bitmap_offset(bitmap_bh);
+ bitmap = kmap_local_folio(bitmap_bh->b_folio, boff);
+ pos = nilfs_palloc_find_available_slot(
+ bitmap, group_offset, entries_per_group, lock,
+ wrap);
+ /*
+ * Since the search for a free slot in the second and
+ * subsequent bitmap blocks always starts from the
+ * beginning, the wrap flag only has an effect on the
+ * first search.
+ */
+ kunmap_local(bitmap);
+ if (pos >= 0)
+ goto found;
+
+ brelse(bitmap_bh);
+ }
+
+ kunmap_local(desc);
+ brelse(desc_bh);
+ }
+
+ /* no entries left */
+ return -ENOSPC;
+
+found:
+ /* found a free entry */
+ nilfs_palloc_group_desc_add_entries(&desc[j], lock, -1);
+ req->pr_entry_nr = entries_per_group * group + pos;
+ kunmap_local(desc);
+
+ req->pr_desc_bh = desc_bh;
+ req->pr_bitmap_bh = bitmap_bh;
+ return 0;
+}
+
+/**
+ * nilfs_palloc_commit_alloc_entry - finish allocation of a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the allocation
+ */
+void nilfs_palloc_commit_alloc_entry(struct inode *inode,
+ struct nilfs_palloc_req *req)
+{
+ mark_buffer_dirty(req->pr_bitmap_bh);
+ mark_buffer_dirty(req->pr_desc_bh);
+ nilfs_mdt_mark_dirty(inode);
+
+ brelse(req->pr_bitmap_bh);
+ brelse(req->pr_desc_bh);
+}
+
+/**
+ * nilfs_palloc_commit_free_entry - finish deallocating a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the removal
+ */
+void nilfs_palloc_commit_free_entry(struct inode *inode,
+ struct nilfs_palloc_req *req)
+{
+ unsigned long group, group_offset;
+ size_t doff, boff;
+ struct nilfs_palloc_group_desc *desc;
+ unsigned char *bitmap;
+ spinlock_t *lock;
+
+ group = nilfs_palloc_group(inode, req->pr_entry_nr, &group_offset);
+ doff = nilfs_palloc_group_desc_offset(inode, group, req->pr_desc_bh);
+ desc = kmap_local_folio(req->pr_desc_bh->b_folio, doff);
+
+ boff = nilfs_palloc_bitmap_offset(req->pr_bitmap_bh);
+ bitmap = kmap_local_folio(req->pr_bitmap_bh->b_folio, boff);
+ lock = nilfs_mdt_bgl_lock(inode, group);
+
+ if (!nilfs_clear_bit_atomic(lock, group_offset, bitmap))
+ nilfs_warn(inode->i_sb,
+ "%s (ino=%llu): entry number %llu already freed",
+ __func__, inode->i_ino,
+ (unsigned long long)req->pr_entry_nr);
+ else
+ nilfs_palloc_group_desc_add_entries(desc, lock, 1);
+
+ kunmap_local(bitmap);
+ kunmap_local(desc);
+
+ mark_buffer_dirty(req->pr_desc_bh);
+ mark_buffer_dirty(req->pr_bitmap_bh);
+ nilfs_mdt_mark_dirty(inode);
+
+ brelse(req->pr_bitmap_bh);
+ brelse(req->pr_desc_bh);
+}
+
+/**
+ * nilfs_palloc_abort_alloc_entry - cancel allocation of a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the allocation
+ */
+void nilfs_palloc_abort_alloc_entry(struct inode *inode,
+ struct nilfs_palloc_req *req)
+{
+ struct nilfs_palloc_group_desc *desc;
+ size_t doff, boff;
+ unsigned char *bitmap;
+ unsigned long group, group_offset;
+ spinlock_t *lock;
+
+ group = nilfs_palloc_group(inode, req->pr_entry_nr, &group_offset);
+ doff = nilfs_palloc_group_desc_offset(inode, group, req->pr_desc_bh);
+ desc = kmap_local_folio(req->pr_desc_bh->b_folio, doff);
+
+ boff = nilfs_palloc_bitmap_offset(req->pr_bitmap_bh);
+ bitmap = kmap_local_folio(req->pr_bitmap_bh->b_folio, boff);
+ lock = nilfs_mdt_bgl_lock(inode, group);
+
+ if (!nilfs_clear_bit_atomic(lock, group_offset, bitmap))
+ nilfs_warn(inode->i_sb,
+ "%s (ino=%llu): entry number %llu already freed",
+ __func__, inode->i_ino,
+ (unsigned long long)req->pr_entry_nr);
+ else
+ nilfs_palloc_group_desc_add_entries(desc, lock, 1);
+
+ kunmap_local(bitmap);
+ kunmap_local(desc);
+
+ brelse(req->pr_bitmap_bh);
+ brelse(req->pr_desc_bh);
+
+ req->pr_entry_nr = 0;
+ req->pr_bitmap_bh = NULL;
+ req->pr_desc_bh = NULL;
+}
+
+/**
+ * nilfs_palloc_prepare_free_entry - prepare to deallocate a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the removal
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_palloc_prepare_free_entry(struct inode *inode,
+ struct nilfs_palloc_req *req)
+{
+ struct buffer_head *desc_bh, *bitmap_bh;
+ unsigned long group, group_offset;
+ int ret;
+
+ group = nilfs_palloc_group(inode, req->pr_entry_nr, &group_offset);
+ ret = nilfs_palloc_get_desc_block(inode, group, 1, &desc_bh);
+ if (ret < 0)
+ return ret;
+ ret = nilfs_palloc_get_bitmap_block(inode, group, 1, &bitmap_bh);
+ if (ret < 0) {
+ brelse(desc_bh);
+ return ret;
+ }
+
+ req->pr_desc_bh = desc_bh;
+ req->pr_bitmap_bh = bitmap_bh;
+ return 0;
+}
+
+/**
+ * nilfs_palloc_abort_free_entry - cancel deallocating a persistent object
+ * @inode: inode of metadata file using this allocator
+ * @req: nilfs_palloc_req structure exchanged for the removal
+ */
+void nilfs_palloc_abort_free_entry(struct inode *inode,
+ struct nilfs_palloc_req *req)
+{
+ brelse(req->pr_bitmap_bh);
+ brelse(req->pr_desc_bh);
+
+ req->pr_entry_nr = 0;
+ req->pr_bitmap_bh = NULL;
+ req->pr_desc_bh = NULL;
+}
+
+/**
+ * nilfs_palloc_freev - deallocate a set of persistent objects
+ * @inode: inode of metadata file using this allocator
+ * @entry_nrs: array of entry numbers to be deallocated
+ * @nitems: number of entries stored in @entry_nrs
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_palloc_freev(struct inode *inode, __u64 *entry_nrs, size_t nitems)
+{
+ struct buffer_head *desc_bh, *bitmap_bh;
+ struct nilfs_palloc_group_desc *desc;
+ unsigned char *bitmap;
+ size_t doff, boff;
+ unsigned long group, group_offset;
+ __u64 group_min_nr, last_nrs[8];
+ const unsigned long epg = nilfs_palloc_entries_per_group(inode);
+ const unsigned int epb = NILFS_MDT(inode)->mi_entries_per_block;
+ unsigned int entry_start, end, pos;
+ spinlock_t *lock;
+ int i, j, k, ret;
+ u32 nfree;
+
+ for (i = 0; i < nitems; i = j) {
+ int change_group = false;
+ int nempties = 0, n = 0;
+
+ group = nilfs_palloc_group(inode, entry_nrs[i], &group_offset);
+ ret = nilfs_palloc_get_desc_block(inode, group, 0, &desc_bh);
+ if (ret < 0)
+ return ret;
+ ret = nilfs_palloc_get_bitmap_block(inode, group, 0,
+ &bitmap_bh);
+ if (ret < 0) {
+ brelse(desc_bh);
+ return ret;
+ }
+
+ /* Get the first entry number of the group */
+ group_min_nr = (__u64)group * epg;
+
+ boff = nilfs_palloc_bitmap_offset(bitmap_bh);
+ bitmap = kmap_local_folio(bitmap_bh->b_folio, boff);
+ lock = nilfs_mdt_bgl_lock(inode, group);
+
+ j = i;
+ entry_start = rounddown(group_offset, epb);
+ do {
+ if (!nilfs_clear_bit_atomic(lock, group_offset,
+ bitmap)) {
+ nilfs_warn(inode->i_sb,
+ "%s (ino=%llu): entry number %llu already freed",
+ __func__, inode->i_ino,
+ (unsigned long long)entry_nrs[j]);
+ } else {
+ n++;
+ }
+
+ j++;
+ if (j >= nitems || entry_nrs[j] < group_min_nr ||
+ entry_nrs[j] >= group_min_nr + epg) {
+ change_group = true;
+ } else {
+ group_offset = entry_nrs[j] - group_min_nr;
+ if (group_offset >= entry_start &&
+ group_offset < entry_start + epb) {
+ /* This entry is in the same block */
+ continue;
+ }
+ }
+
+ /* Test if the entry block is empty or not */
+ end = entry_start + epb;
+ pos = nilfs_find_next_bit(bitmap, end, entry_start);
+ if (pos >= end) {
+ last_nrs[nempties++] = entry_nrs[j - 1];
+ if (nempties >= ARRAY_SIZE(last_nrs))
+ break;
+ }
+
+ if (change_group)
+ break;
+
+ /* Go on to the next entry block */
+ entry_start = rounddown(group_offset, epb);
+ } while (true);
+
+ kunmap_local(bitmap);
+ mark_buffer_dirty(bitmap_bh);
+ brelse(bitmap_bh);
+
+ for (k = 0; k < nempties; k++) {
+ ret = nilfs_palloc_delete_entry_block(inode,
+ last_nrs[k]);
+ if (ret && ret != -ENOENT)
+ nilfs_warn(inode->i_sb,
+ "error %d deleting block that object (entry=%llu, ino=%llu) belongs to",
+ ret, (unsigned long long)last_nrs[k],
+ inode->i_ino);
+ }
+
+ doff = nilfs_palloc_group_desc_offset(inode, group, desc_bh);
+ desc = kmap_local_folio(desc_bh->b_folio, doff);
+ nfree = nilfs_palloc_group_desc_add_entries(desc, lock, n);
+ kunmap_local(desc);
+ mark_buffer_dirty(desc_bh);
+ nilfs_mdt_mark_dirty(inode);
+ brelse(desc_bh);
+
+ if (nfree == nilfs_palloc_entries_per_group(inode)) {
+ ret = nilfs_palloc_delete_bitmap_block(inode, group);
+ if (ret && ret != -ENOENT)
+ nilfs_warn(inode->i_sb,
+ "error %d deleting bitmap block of group=%lu, ino=%llu",
+ ret, group, inode->i_ino);
+ }
+ }
+ return 0;
+}
+
+void nilfs_palloc_setup_cache(struct inode *inode,
+ struct nilfs_palloc_cache *cache)
+{
+ NILFS_MDT(inode)->mi_palloc_cache = cache;
+ spin_lock_init(&cache->lock);
+}
+
+void nilfs_palloc_clear_cache(struct inode *inode)
+{
+ struct nilfs_palloc_cache *cache = NILFS_MDT(inode)->mi_palloc_cache;
+
+ spin_lock(&cache->lock);
+ brelse(cache->prev_desc.bh);
+ brelse(cache->prev_bitmap.bh);
+ brelse(cache->prev_entry.bh);
+ cache->prev_desc.bh = NULL;
+ cache->prev_bitmap.bh = NULL;
+ cache->prev_entry.bh = NULL;
+ spin_unlock(&cache->lock);
+}
+
+void nilfs_palloc_destroy_cache(struct inode *inode)
+{
+ nilfs_palloc_clear_cache(inode);
+ NILFS_MDT(inode)->mi_palloc_cache = NULL;
+}
diff --git a/fs/nilfs2/alloc.h b/fs/nilfs2/alloc.h
new file mode 100644
index 000000000..046d876ea
--- /dev/null
+++ b/fs/nilfs2/alloc.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Persistent object (dat entry/disk inode) allocator/deallocator
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Originally written by Koji Sato.
+ * Two allocators were unified by Ryusuke Konishi and Amagai Yoshiji.
+ */
+
+#ifndef _NILFS_ALLOC_H
+#define _NILFS_ALLOC_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+
+/**
+ * nilfs_palloc_entries_per_group - get the number of entries per group
+ * @inode: inode of metadata file using this allocator
+ *
+ * The number of entries per group is defined by the number of bits
+ * that a bitmap block can maintain.
+ *
+ * Return: Number of entries per group.
+ */
+static inline unsigned long
+nilfs_palloc_entries_per_group(const struct inode *inode)
+{
+ return 1UL << (inode->i_blkbits + 3 /* log2(8 = CHAR_BITS) */);
+}
+
+int nilfs_palloc_init_blockgroup(struct inode *, unsigned int);
+int nilfs_palloc_get_entry_block(struct inode *, __u64, int,
+ struct buffer_head **);
+size_t nilfs_palloc_entry_offset(const struct inode *inode, __u64 nr,
+ const struct buffer_head *bh);
+
+int nilfs_palloc_count_max_entries(struct inode *, u64, u64 *);
+
+/**
+ * struct nilfs_palloc_req - persistent allocator request and reply
+ * @pr_entry_nr: entry number (vblocknr or inode number)
+ * @pr_desc_bh: buffer head of the buffer containing block group descriptors
+ * @pr_bitmap_bh: buffer head of the buffer containing a block group bitmap
+ * @pr_entry_bh: buffer head of the buffer containing translation entries
+ */
+struct nilfs_palloc_req {
+ __u64 pr_entry_nr;
+ struct buffer_head *pr_desc_bh;
+ struct buffer_head *pr_bitmap_bh;
+ struct buffer_head *pr_entry_bh;
+};
+
+int nilfs_palloc_prepare_alloc_entry(struct inode *inode,
+ struct nilfs_palloc_req *req, bool wrap);
+void nilfs_palloc_commit_alloc_entry(struct inode *,
+ struct nilfs_palloc_req *);
+void nilfs_palloc_abort_alloc_entry(struct inode *, struct nilfs_palloc_req *);
+void nilfs_palloc_commit_free_entry(struct inode *, struct nilfs_palloc_req *);
+int nilfs_palloc_prepare_free_entry(struct inode *, struct nilfs_palloc_req *);
+void nilfs_palloc_abort_free_entry(struct inode *, struct nilfs_palloc_req *);
+int nilfs_palloc_freev(struct inode *, __u64 *, size_t);
+
+#define nilfs_set_bit_atomic ext2_set_bit_atomic
+#define nilfs_clear_bit_atomic ext2_clear_bit_atomic
+#define nilfs_find_next_zero_bit find_next_zero_bit_le
+#define nilfs_find_next_bit find_next_bit_le
+
+/**
+ * struct nilfs_bh_assoc - block offset and buffer head association
+ * @blkoff: block offset
+ * @bh: buffer head
+ */
+struct nilfs_bh_assoc {
+ unsigned long blkoff;
+ struct buffer_head *bh;
+};
+
+/**
+ * struct nilfs_palloc_cache - persistent object allocator cache
+ * @lock: cache protecting lock
+ * @prev_desc: blockgroup descriptors cache
+ * @prev_bitmap: blockgroup bitmap cache
+ * @prev_entry: translation entries cache
+ */
+struct nilfs_palloc_cache {
+ spinlock_t lock;
+ struct nilfs_bh_assoc prev_desc;
+ struct nilfs_bh_assoc prev_bitmap;
+ struct nilfs_bh_assoc prev_entry;
+};
+
+void nilfs_palloc_setup_cache(struct inode *inode,
+ struct nilfs_palloc_cache *cache);
+void nilfs_palloc_clear_cache(struct inode *inode);
+void nilfs_palloc_destroy_cache(struct inode *inode);
+
+#endif /* _NILFS_ALLOC_H */
diff --git a/fs/nilfs2/bmap.c b/fs/nilfs2/bmap.c
new file mode 100644
index 000000000..83f6ea30c
--- /dev/null
+++ b/fs/nilfs2/bmap.c
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS block mapping.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include "nilfs.h"
+#include "bmap.h"
+#include "btree.h"
+#include "direct.h"
+#include "btnode.h"
+#include "mdt.h"
+#include "dat.h"
+#include "alloc.h"
+
+struct inode *nilfs_bmap_get_dat(const struct nilfs_bmap *bmap)
+{
+ struct the_nilfs *nilfs = bmap->b_inode->i_sb->s_fs_info;
+
+ return nilfs->ns_dat;
+}
+
+static int nilfs_bmap_convert_error(struct nilfs_bmap *bmap,
+ const char *fname, int err)
+{
+ struct inode *inode = bmap->b_inode;
+
+ if (err == -EINVAL) {
+ __nilfs_error(inode->i_sb, fname,
+ "broken bmap (inode number=%llu)", inode->i_ino);
+ err = -EIO;
+ }
+ return err;
+}
+
+/**
+ * nilfs_bmap_lookup_at_level - find a data block or node block
+ * @bmap: bmap
+ * @key: key
+ * @level: level
+ * @ptrp: place to store the value associated to @key
+ *
+ * Description: nilfs_bmap_lookup_at_level() finds a record whose key
+ * matches @key in the block at @level of the bmap. The record associated
+ * with @key is stored in the place pointed to by @ptrp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - A record associated with @key does not exist.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_lookup_at_level(struct nilfs_bmap *bmap, __u64 key, int level,
+ __u64 *ptrp)
+{
+ sector_t blocknr;
+ int ret;
+
+ down_read(&bmap->b_sem);
+ ret = bmap->b_ops->bop_lookup(bmap, key, level, ptrp);
+ if (ret < 0)
+ goto out;
+
+ if (NILFS_BMAP_USE_VBN(bmap)) {
+ ret = nilfs_dat_translate(nilfs_bmap_get_dat(bmap), *ptrp,
+ &blocknr);
+ if (!ret)
+ *ptrp = blocknr;
+ else if (ret == -ENOENT) {
+ /*
+ * If there was no valid entry in DAT for the block
+ * address obtained by b_ops->bop_lookup, then pass
+ * internal code -EINVAL to nilfs_bmap_convert_error
+ * to treat it as metadata corruption.
+ */
+ ret = -EINVAL;
+ }
+ }
+
+ out:
+ up_read(&bmap->b_sem);
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+int nilfs_bmap_lookup_contig(struct nilfs_bmap *bmap, __u64 key, __u64 *ptrp,
+ unsigned int maxblocks)
+{
+ int ret;
+
+ down_read(&bmap->b_sem);
+ ret = bmap->b_ops->bop_lookup_contig(bmap, key, ptrp, maxblocks);
+ up_read(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+static int nilfs_bmap_do_insert(struct nilfs_bmap *bmap, __u64 key, __u64 ptr)
+{
+ __u64 keys[NILFS_BMAP_SMALL_HIGH + 1];
+ __u64 ptrs[NILFS_BMAP_SMALL_HIGH + 1];
+ int ret, n;
+
+ if (bmap->b_ops->bop_check_insert != NULL) {
+ ret = bmap->b_ops->bop_check_insert(bmap, key);
+ if (ret > 0) {
+ n = bmap->b_ops->bop_gather_data(
+ bmap, keys, ptrs, NILFS_BMAP_SMALL_HIGH + 1);
+ if (n < 0)
+ return n;
+ ret = nilfs_btree_convert_and_insert(
+ bmap, key, ptr, keys, ptrs, n);
+ if (ret == 0)
+ bmap->b_u.u_flags |= NILFS_BMAP_LARGE;
+
+ return ret;
+ } else if (ret < 0)
+ return ret;
+ }
+
+ return bmap->b_ops->bop_insert(bmap, key, ptr);
+}
+
+/**
+ * nilfs_bmap_insert - insert a new key-record pair into a bmap
+ * @bmap: bmap
+ * @key: key
+ * @rec: record
+ *
+ * Description: nilfs_bmap_insert() inserts the new key-record pair specified
+ * by @key and @rec into @bmap.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EEXIST - A record associated with @key already exists.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_insert(struct nilfs_bmap *bmap, __u64 key, unsigned long rec)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = nilfs_bmap_do_insert(bmap, key, rec);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+static int nilfs_bmap_do_delete(struct nilfs_bmap *bmap, __u64 key)
+{
+ __u64 keys[NILFS_BMAP_LARGE_LOW + 1];
+ __u64 ptrs[NILFS_BMAP_LARGE_LOW + 1];
+ int ret, n;
+
+ if (bmap->b_ops->bop_check_delete != NULL) {
+ ret = bmap->b_ops->bop_check_delete(bmap, key);
+ if (ret > 0) {
+ n = bmap->b_ops->bop_gather_data(
+ bmap, keys, ptrs, NILFS_BMAP_LARGE_LOW + 1);
+ if (n < 0)
+ return n;
+ ret = nilfs_direct_delete_and_convert(
+ bmap, key, keys, ptrs, n);
+ if (ret == 0)
+ bmap->b_u.u_flags &= ~NILFS_BMAP_LARGE;
+
+ return ret;
+ } else if (ret < 0)
+ return ret;
+ }
+
+ return bmap->b_ops->bop_delete(bmap, key, false);
+}
+
+/**
+ * nilfs_bmap_seek_key - seek a valid entry and return its key
+ * @bmap: bmap struct
+ * @start: start key number
+ * @keyp: place to store valid key
+ *
+ * Description: nilfs_bmap_seek_key() seeks a valid key on @bmap
+ * starting from @start, and stores it to @keyp if found.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - No valid entry was found.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_seek_key(struct nilfs_bmap *bmap, __u64 start, __u64 *keyp)
+{
+ int ret;
+
+ down_read(&bmap->b_sem);
+ ret = bmap->b_ops->bop_seek_key(bmap, start, keyp);
+ up_read(&bmap->b_sem);
+
+ if (ret < 0)
+ ret = nilfs_bmap_convert_error(bmap, __func__, ret);
+ return ret;
+}
+
+int nilfs_bmap_last_key(struct nilfs_bmap *bmap, __u64 *keyp)
+{
+ int ret;
+
+ down_read(&bmap->b_sem);
+ ret = bmap->b_ops->bop_last_key(bmap, keyp);
+ up_read(&bmap->b_sem);
+
+ if (ret < 0)
+ ret = nilfs_bmap_convert_error(bmap, __func__, ret);
+ return ret;
+}
+
+/**
+ * nilfs_bmap_delete - delete a key-record pair from a bmap
+ * @bmap: bmap
+ * @key: key
+ *
+ * Description: nilfs_bmap_delete() deletes the key-record pair specified by
+ * @key from @bmap.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - A record associated with @key does not exist.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_delete(struct nilfs_bmap *bmap, __u64 key)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = nilfs_bmap_do_delete(bmap, key);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+static int nilfs_bmap_do_truncate(struct nilfs_bmap *bmap, __u64 key)
+{
+ __u64 lastkey;
+ int ret;
+
+ ret = bmap->b_ops->bop_last_key(bmap, &lastkey);
+ if (ret < 0) {
+ if (ret == -ENOENT)
+ ret = 0;
+ return ret;
+ }
+
+ while (key <= lastkey) {
+ ret = nilfs_bmap_do_delete(bmap, lastkey);
+ if (ret < 0)
+ return ret;
+ ret = bmap->b_ops->bop_last_key(bmap, &lastkey);
+ if (ret < 0) {
+ if (ret == -ENOENT)
+ ret = 0;
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/**
+ * nilfs_bmap_truncate - truncate a bmap to a specified key
+ * @bmap: bmap
+ * @key: key
+ *
+ * Description: nilfs_bmap_truncate() removes key-record pairs whose keys are
+ * greater than or equal to @key from @bmap.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_truncate(struct nilfs_bmap *bmap, __u64 key)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = nilfs_bmap_do_truncate(bmap, key);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+/**
+ * nilfs_bmap_clear - free resources a bmap holds
+ * @bmap: bmap
+ *
+ * Description: nilfs_bmap_clear() frees resources associated with @bmap.
+ */
+void nilfs_bmap_clear(struct nilfs_bmap *bmap)
+{
+ down_write(&bmap->b_sem);
+ if (bmap->b_ops->bop_clear != NULL)
+ bmap->b_ops->bop_clear(bmap);
+ up_write(&bmap->b_sem);
+}
+
+/**
+ * nilfs_bmap_propagate - propagate dirty state
+ * @bmap: bmap
+ * @bh: buffer head
+ *
+ * Description: nilfs_bmap_propagate() marks the buffers that directly or
+ * indirectly refer to the block specified by @bh dirty.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_propagate(struct nilfs_bmap *bmap, struct buffer_head *bh)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = bmap->b_ops->bop_propagate(bmap, bh);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+/**
+ * nilfs_bmap_lookup_dirty_buffers - collect dirty block buffers
+ * @bmap: bmap
+ * @listp: pointer to buffer head list
+ */
+void nilfs_bmap_lookup_dirty_buffers(struct nilfs_bmap *bmap,
+ struct list_head *listp)
+{
+ if (bmap->b_ops->bop_lookup_dirty_buffers != NULL)
+ bmap->b_ops->bop_lookup_dirty_buffers(bmap, listp);
+}
+
+/**
+ * nilfs_bmap_assign - assign a new block number to a block
+ * @bmap: bmap
+ * @bh: place to store a pointer to the buffer head to which a block
+ * address is assigned (in/out)
+ * @blocknr: block number
+ * @binfo: block information
+ *
+ * Description: nilfs_bmap_assign() assigns the block number @blocknr to the
+ * buffer specified by @bh. The block information is stored in the memory
+ * pointed to by @binfo, and the buffer head may be replaced as a block
+ * address is assigned, in which case a pointer to the new buffer head is
+ * stored in the memory pointed to by @bh.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_assign(struct nilfs_bmap *bmap,
+ struct buffer_head **bh,
+ unsigned long blocknr,
+ union nilfs_binfo *binfo)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = bmap->b_ops->bop_assign(bmap, bh, blocknr, binfo);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+/**
+ * nilfs_bmap_mark - mark block dirty
+ * @bmap: bmap
+ * @key: key
+ * @level: level
+ *
+ * Description: nilfs_bmap_mark() marks the block specified by @key and @level
+ * as dirty.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_mark(struct nilfs_bmap *bmap, __u64 key, int level)
+{
+ int ret;
+
+ if (bmap->b_ops->bop_mark == NULL)
+ return 0;
+
+ down_write(&bmap->b_sem);
+ ret = bmap->b_ops->bop_mark(bmap, key, level);
+ up_write(&bmap->b_sem);
+
+ return nilfs_bmap_convert_error(bmap, __func__, ret);
+}
+
+/**
+ * nilfs_bmap_test_and_clear_dirty - test and clear a bmap dirty state
+ * @bmap: bmap
+ *
+ * Description: nilfs_test_and_clear() is the atomic operation to test and
+ * clear the dirty state of @bmap.
+ *
+ * Return: 1 if @bmap is dirty, or 0 if clear.
+ */
+int nilfs_bmap_test_and_clear_dirty(struct nilfs_bmap *bmap)
+{
+ int ret;
+
+ down_write(&bmap->b_sem);
+ ret = nilfs_bmap_dirty(bmap);
+ nilfs_bmap_clear_dirty(bmap);
+ up_write(&bmap->b_sem);
+ return ret;
+}
+
+
+/*
+ * Internal use only
+ */
+__u64 nilfs_bmap_data_get_key(const struct nilfs_bmap *bmap,
+ const struct buffer_head *bh)
+{
+ loff_t pos = folio_pos(bh->b_folio) + bh_offset(bh);
+
+ return pos >> bmap->b_inode->i_blkbits;
+}
+
+__u64 nilfs_bmap_find_target_seq(const struct nilfs_bmap *bmap, __u64 key)
+{
+ __s64 diff;
+
+ diff = key - bmap->b_last_allocated_key;
+ if ((nilfs_bmap_keydiff_abs(diff) < NILFS_INODE_BMAP_SIZE) &&
+ (bmap->b_last_allocated_ptr != NILFS_BMAP_INVALID_PTR) &&
+ (bmap->b_last_allocated_ptr + diff > 0))
+ return bmap->b_last_allocated_ptr + diff;
+ else
+ return NILFS_BMAP_INVALID_PTR;
+}
+
+#define NILFS_BMAP_GROUP_DIV 8 /* must be power of 2 */
+
+__u64 nilfs_bmap_find_target_in_group(const struct nilfs_bmap *bmap)
+{
+ struct inode *dat = nilfs_bmap_get_dat(bmap);
+ unsigned long entries_per_group = nilfs_palloc_entries_per_group(dat);
+ unsigned long group;
+ u32 index;
+
+ BUILD_BUG_ON_NOT_POWER_OF_2(NILFS_BMAP_GROUP_DIV);
+
+ group = div_u64(bmap->b_inode->i_ino, entries_per_group);
+ index = bmap->b_inode->i_ino & (NILFS_BMAP_GROUP_DIV - 1);
+
+ return group * entries_per_group +
+ index * (entries_per_group / NILFS_BMAP_GROUP_DIV);
+}
+
+
+static struct lock_class_key nilfs_bmap_dat_lock_key;
+static struct lock_class_key nilfs_bmap_mdt_lock_key;
+
+/**
+ * nilfs_bmap_read - read a bmap from an inode
+ * @bmap: bmap
+ * @raw_inode: on-disk inode
+ *
+ * Description: nilfs_bmap_read() initializes the bmap @bmap.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (corrupted bmap).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_bmap_read(struct nilfs_bmap *bmap, struct nilfs_inode *raw_inode)
+{
+ if (raw_inode == NULL)
+ memset(bmap->b_u.u_data, 0, NILFS_BMAP_SIZE);
+ else
+ memcpy(bmap->b_u.u_data, raw_inode->i_bmap, NILFS_BMAP_SIZE);
+
+ init_rwsem(&bmap->b_sem);
+ bmap->b_state = 0;
+ bmap->b_inode = &NILFS_BMAP_I(bmap)->vfs_inode;
+ switch (bmap->b_inode->i_ino) {
+ case NILFS_DAT_INO:
+ bmap->b_ptr_type = NILFS_BMAP_PTR_P;
+ bmap->b_last_allocated_key = 0;
+ bmap->b_last_allocated_ptr = NILFS_BMAP_NEW_PTR_INIT;
+ lockdep_set_class(&bmap->b_sem, &nilfs_bmap_dat_lock_key);
+ break;
+ case NILFS_CPFILE_INO:
+ case NILFS_SUFILE_INO:
+ bmap->b_ptr_type = NILFS_BMAP_PTR_VS;
+ bmap->b_last_allocated_key = 0;
+ bmap->b_last_allocated_ptr = NILFS_BMAP_INVALID_PTR;
+ lockdep_set_class(&bmap->b_sem, &nilfs_bmap_mdt_lock_key);
+ break;
+ case NILFS_IFILE_INO:
+ lockdep_set_class(&bmap->b_sem, &nilfs_bmap_mdt_lock_key);
+ fallthrough;
+ default:
+ bmap->b_ptr_type = NILFS_BMAP_PTR_VM;
+ bmap->b_last_allocated_key = 0;
+ bmap->b_last_allocated_ptr = NILFS_BMAP_INVALID_PTR;
+ break;
+ }
+
+ return (bmap->b_u.u_flags & NILFS_BMAP_LARGE) ?
+ nilfs_btree_init(bmap) : nilfs_direct_init(bmap);
+}
+
+/**
+ * nilfs_bmap_write - write back a bmap to an inode
+ * @bmap: bmap
+ * @raw_inode: on-disk inode
+ *
+ * Description: nilfs_bmap_write() stores @bmap in @raw_inode.
+ */
+void nilfs_bmap_write(struct nilfs_bmap *bmap, struct nilfs_inode *raw_inode)
+{
+ memcpy(raw_inode->i_bmap, bmap->b_u.u_data,
+ NILFS_INODE_BMAP_SIZE * sizeof(__le64));
+ if (bmap->b_inode->i_ino == NILFS_DAT_INO)
+ bmap->b_last_allocated_ptr = NILFS_BMAP_NEW_PTR_INIT;
+}
+
+void nilfs_bmap_init_gc(struct nilfs_bmap *bmap)
+{
+ memset(&bmap->b_u, 0, NILFS_BMAP_SIZE);
+ init_rwsem(&bmap->b_sem);
+ bmap->b_inode = &NILFS_BMAP_I(bmap)->vfs_inode;
+ bmap->b_ptr_type = NILFS_BMAP_PTR_U;
+ bmap->b_last_allocated_key = 0;
+ bmap->b_last_allocated_ptr = NILFS_BMAP_INVALID_PTR;
+ bmap->b_state = 0;
+ nilfs_btree_init_gc(bmap);
+}
+
+void nilfs_bmap_save(const struct nilfs_bmap *bmap,
+ struct nilfs_bmap_store *store)
+{
+ memcpy(store->data, bmap->b_u.u_data, sizeof(store->data));
+ store->last_allocated_key = bmap->b_last_allocated_key;
+ store->last_allocated_ptr = bmap->b_last_allocated_ptr;
+ store->state = bmap->b_state;
+}
+
+void nilfs_bmap_restore(struct nilfs_bmap *bmap,
+ const struct nilfs_bmap_store *store)
+{
+ memcpy(bmap->b_u.u_data, store->data, sizeof(store->data));
+ bmap->b_last_allocated_key = store->last_allocated_key;
+ bmap->b_last_allocated_ptr = store->last_allocated_ptr;
+ bmap->b_state = store->state;
+}
diff --git a/fs/nilfs2/bmap.h b/fs/nilfs2/bmap.h
new file mode 100644
index 000000000..a72f3c308
--- /dev/null
+++ b/fs/nilfs2/bmap.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS block mapping.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_BMAP_H
+#define _NILFS_BMAP_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/nilfs2_ondisk.h> /* nilfs_binfo, nilfs_inode, etc */
+#include "alloc.h"
+#include "dat.h"
+
+#define NILFS_BMAP_INVALID_PTR 0
+
+#define nilfs_bmap_keydiff_abs(diff) ((diff) < 0 ? -(diff) : (diff))
+
+
+struct nilfs_bmap;
+
+/**
+ * union nilfs_bmap_ptr_req - request for bmap ptr
+ * @bpr_ptr: bmap pointer
+ * @bpr_req: request for persistent allocator
+ */
+union nilfs_bmap_ptr_req {
+ __u64 bpr_ptr;
+ struct nilfs_palloc_req bpr_req;
+};
+
+/**
+ * struct nilfs_bmap_stats - bmap statistics
+ * @bs_nblocks: number of blocks created or deleted
+ */
+struct nilfs_bmap_stats {
+ unsigned int bs_nblocks;
+};
+
+/**
+ * struct nilfs_bmap_operations - bmap operation table
+ * @bop_lookup: single block search operation
+ * @bop_lookup_contig: consecutive block search operation
+ * @bop_insert: block insertion operation
+ * @bop_delete: block delete operation
+ * @bop_clear: block mapping resource release operation
+ * @bop_propagate: operation to propagate dirty state towards the
+ * mapping root
+ * @bop_lookup_dirty_buffers: operation to collect dirty block buffers
+ * @bop_assign: disk block address assignment operation
+ * @bop_mark: operation to mark in-use blocks as dirty for
+ * relocation by GC
+ * @bop_seek_key: find valid block key operation
+ * @bop_last_key: find last valid block key operation
+ */
+struct nilfs_bmap_operations {
+ int (*bop_lookup)(const struct nilfs_bmap *, __u64, int, __u64 *);
+ int (*bop_lookup_contig)(const struct nilfs_bmap *, __u64, __u64 *,
+ unsigned int);
+ int (*bop_insert)(struct nilfs_bmap *, __u64, __u64);
+ int (*bop_delete)(struct nilfs_bmap *bmap, __u64 key, bool deform);
+ void (*bop_clear)(struct nilfs_bmap *);
+
+ int (*bop_propagate)(struct nilfs_bmap *, struct buffer_head *);
+ void (*bop_lookup_dirty_buffers)(struct nilfs_bmap *,
+ struct list_head *);
+
+ int (*bop_assign)(struct nilfs_bmap *,
+ struct buffer_head **,
+ sector_t,
+ union nilfs_binfo *);
+ int (*bop_mark)(struct nilfs_bmap *, __u64, int);
+
+ int (*bop_seek_key)(const struct nilfs_bmap *, __u64, __u64 *);
+ int (*bop_last_key)(const struct nilfs_bmap *, __u64 *);
+
+ /* private: internal use only */
+ int (*bop_check_insert)(const struct nilfs_bmap *, __u64);
+ int (*bop_check_delete)(struct nilfs_bmap *, __u64);
+ int (*bop_gather_data)(struct nilfs_bmap *, __u64 *, __u64 *, int);
+};
+
+
+#define NILFS_BMAP_SIZE (NILFS_INODE_BMAP_SIZE * sizeof(__le64))
+#define NILFS_BMAP_KEY_BIT BITS_PER_LONG
+#define NILFS_BMAP_NEW_PTR_INIT (1UL << (BITS_PER_LONG - 1))
+
+static inline int nilfs_bmap_is_new_ptr(unsigned long ptr)
+{
+ return !!(ptr & NILFS_BMAP_NEW_PTR_INIT);
+}
+
+
+/**
+ * struct nilfs_bmap - bmap structure
+ * @b_u: raw data
+ * @b_sem: semaphore
+ * @b_inode: owner of bmap
+ * @b_ops: bmap operation table
+ * @b_last_allocated_key: last allocated key for data block
+ * @b_last_allocated_ptr: last allocated ptr for data block
+ * @b_ptr_type: pointer type
+ * @b_state: state
+ * @b_nchildren_per_block: maximum number of child nodes for non-root nodes
+ */
+struct nilfs_bmap {
+ union {
+ __u8 u_flags;
+ __le64 u_data[NILFS_BMAP_SIZE / sizeof(__le64)];
+ } b_u;
+ struct rw_semaphore b_sem;
+ struct inode *b_inode;
+ const struct nilfs_bmap_operations *b_ops;
+ __u64 b_last_allocated_key;
+ __u64 b_last_allocated_ptr;
+ int b_ptr_type;
+ int b_state;
+ __u16 b_nchildren_per_block;
+};
+
+/* pointer type */
+#define NILFS_BMAP_PTR_P 0 /* physical block number (i.e. LBN) */
+#define NILFS_BMAP_PTR_VS 1 /*
+ * virtual block number (single
+ * version)
+ */
+#define NILFS_BMAP_PTR_VM 2 /*
+ * virtual block number (has multiple
+ * versions)
+ */
+#define NILFS_BMAP_PTR_U (-1) /* never perform pointer operations */
+
+#define NILFS_BMAP_USE_VBN(bmap) ((bmap)->b_ptr_type > 0)
+
+/* state */
+#define NILFS_BMAP_DIRTY 0x00000001
+
+/**
+ * struct nilfs_bmap_store - shadow copy of bmap state
+ * @data: cached raw block mapping of on-disk inode
+ * @last_allocated_key: cached value of last allocated key for data block
+ * @last_allocated_ptr: cached value of last allocated ptr for data block
+ * @state: cached value of state field of bmap structure
+ */
+struct nilfs_bmap_store {
+ __le64 data[NILFS_BMAP_SIZE / sizeof(__le64)];
+ __u64 last_allocated_key;
+ __u64 last_allocated_ptr;
+ int state;
+};
+
+int nilfs_bmap_test_and_clear_dirty(struct nilfs_bmap *);
+int nilfs_bmap_read(struct nilfs_bmap *, struct nilfs_inode *);
+void nilfs_bmap_write(struct nilfs_bmap *, struct nilfs_inode *);
+int nilfs_bmap_lookup_contig(struct nilfs_bmap *, __u64, __u64 *, unsigned int);
+int nilfs_bmap_insert(struct nilfs_bmap *bmap, __u64 key, unsigned long rec);
+int nilfs_bmap_delete(struct nilfs_bmap *bmap, __u64 key);
+int nilfs_bmap_seek_key(struct nilfs_bmap *bmap, __u64 start, __u64 *keyp);
+int nilfs_bmap_last_key(struct nilfs_bmap *bmap, __u64 *keyp);
+int nilfs_bmap_truncate(struct nilfs_bmap *bmap, __u64 key);
+void nilfs_bmap_clear(struct nilfs_bmap *);
+int nilfs_bmap_propagate(struct nilfs_bmap *, struct buffer_head *);
+void nilfs_bmap_lookup_dirty_buffers(struct nilfs_bmap *, struct list_head *);
+int nilfs_bmap_assign(struct nilfs_bmap *, struct buffer_head **,
+ unsigned long, union nilfs_binfo *);
+int nilfs_bmap_lookup_at_level(struct nilfs_bmap *, __u64, int, __u64 *);
+int nilfs_bmap_mark(struct nilfs_bmap *, __u64, int);
+
+void nilfs_bmap_init_gc(struct nilfs_bmap *);
+
+void nilfs_bmap_save(const struct nilfs_bmap *, struct nilfs_bmap_store *);
+void nilfs_bmap_restore(struct nilfs_bmap *, const struct nilfs_bmap_store *);
+
+static inline int nilfs_bmap_lookup(struct nilfs_bmap *bmap, __u64 key,
+ __u64 *ptr)
+{
+ return nilfs_bmap_lookup_at_level(bmap, key, 1, ptr);
+}
+
+/*
+ * Internal use only
+ */
+struct inode *nilfs_bmap_get_dat(const struct nilfs_bmap *);
+
+static inline int nilfs_bmap_prepare_alloc_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ if (dat)
+ return nilfs_dat_prepare_alloc(dat, &req->bpr_req);
+ /* ignore target ptr */
+ req->bpr_ptr = bmap->b_last_allocated_ptr++;
+ return 0;
+}
+
+static inline void nilfs_bmap_commit_alloc_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ if (dat)
+ nilfs_dat_commit_alloc(dat, &req->bpr_req);
+}
+
+static inline void nilfs_bmap_abort_alloc_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ if (dat)
+ nilfs_dat_abort_alloc(dat, &req->bpr_req);
+ else
+ bmap->b_last_allocated_ptr--;
+}
+
+static inline int nilfs_bmap_prepare_end_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ return dat ? nilfs_dat_prepare_end(dat, &req->bpr_req) : 0;
+}
+
+static inline void nilfs_bmap_commit_end_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ if (dat)
+ nilfs_dat_commit_end(dat, &req->bpr_req,
+ bmap->b_ptr_type == NILFS_BMAP_PTR_VS);
+}
+
+static inline void nilfs_bmap_abort_end_ptr(struct nilfs_bmap *bmap,
+ union nilfs_bmap_ptr_req *req,
+ struct inode *dat)
+{
+ if (dat)
+ nilfs_dat_abort_end(dat, &req->bpr_req);
+}
+
+static inline void nilfs_bmap_set_target_v(struct nilfs_bmap *bmap, __u64 key,
+ __u64 ptr)
+{
+ bmap->b_last_allocated_key = key;
+ bmap->b_last_allocated_ptr = ptr;
+}
+
+__u64 nilfs_bmap_data_get_key(const struct nilfs_bmap *,
+ const struct buffer_head *);
+
+__u64 nilfs_bmap_find_target_seq(const struct nilfs_bmap *, __u64);
+__u64 nilfs_bmap_find_target_in_group(const struct nilfs_bmap *);
+
+
+/* Assume that bmap semaphore is locked. */
+static inline int nilfs_bmap_dirty(const struct nilfs_bmap *bmap)
+{
+ return !!(bmap->b_state & NILFS_BMAP_DIRTY);
+}
+
+/* Assume that bmap semaphore is locked. */
+static inline void nilfs_bmap_set_dirty(struct nilfs_bmap *bmap)
+{
+ bmap->b_state |= NILFS_BMAP_DIRTY;
+}
+
+/* Assume that bmap semaphore is locked. */
+static inline void nilfs_bmap_clear_dirty(struct nilfs_bmap *bmap)
+{
+ bmap->b_state &= ~NILFS_BMAP_DIRTY;
+}
+
+
+#define NILFS_BMAP_LARGE 0x1
+
+#define NILFS_BMAP_SMALL_LOW NILFS_DIRECT_KEY_MIN
+#define NILFS_BMAP_SMALL_HIGH NILFS_DIRECT_KEY_MAX
+#define NILFS_BMAP_LARGE_LOW NILFS_BTREE_ROOT_NCHILDREN_MAX
+#define NILFS_BMAP_LARGE_HIGH NILFS_BTREE_KEY_MAX
+
+#endif /* _NILFS_BMAP_H */
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);
+ }
+}
diff --git a/fs/nilfs2/btnode.h b/fs/nilfs2/btnode.h
new file mode 100644
index 000000000..4bc5612df
--- /dev/null
+++ b/fs/nilfs2/btnode.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS B-tree node cache
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Seiji Kihara.
+ * Revised by Ryusuke Konishi.
+ */
+
+#ifndef _NILFS_BTNODE_H
+#define _NILFS_BTNODE_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/backing-dev.h>
+
+/**
+ * struct nilfs_btnode_chkey_ctxt - change key context
+ * @oldkey: old key of block's moving content
+ * @newkey: new key for block's content
+ * @bh: buffer head of old buffer
+ * @newbh: buffer head of new buffer
+ */
+struct nilfs_btnode_chkey_ctxt {
+ __u64 oldkey;
+ __u64 newkey;
+ struct buffer_head *bh;
+ struct buffer_head *newbh;
+};
+
+void nilfs_init_btnc_inode(struct inode *btnc_inode);
+void nilfs_btnode_cache_clear(struct address_space *);
+struct buffer_head *nilfs_btnode_create_block(struct address_space *btnc,
+ __u64 blocknr);
+int nilfs_btnode_submit_block(struct address_space *, __u64, sector_t,
+ blk_opf_t, struct buffer_head **, sector_t *);
+void nilfs_btnode_delete(struct buffer_head *);
+int nilfs_btnode_prepare_change_key(struct address_space *,
+ struct nilfs_btnode_chkey_ctxt *);
+void nilfs_btnode_commit_change_key(struct address_space *,
+ struct nilfs_btnode_chkey_ctxt *);
+void nilfs_btnode_abort_change_key(struct address_space *,
+ struct nilfs_btnode_chkey_ctxt *);
+
+#endif /* _NILFS_BTNODE_H */
diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c
new file mode 100644
index 000000000..6b8332e8c
--- /dev/null
+++ b/fs/nilfs2/btree.c
@@ -0,0 +1,2485 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS B-tree.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/folio_batch.h>
+#include "nilfs.h"
+#include "page.h"
+#include "btnode.h"
+#include "btree.h"
+#include "alloc.h"
+#include "dat.h"
+
+static void __nilfs_btree_init(struct nilfs_bmap *bmap);
+
+static struct nilfs_btree_path *nilfs_btree_alloc_path(void)
+{
+ struct nilfs_btree_path *path;
+ int level = NILFS_BTREE_LEVEL_DATA;
+
+ path = kmem_cache_alloc(nilfs_btree_path_cache, GFP_NOFS);
+ if (path == NULL)
+ goto out;
+
+ for (; level < NILFS_BTREE_LEVEL_MAX; level++) {
+ path[level].bp_bh = NULL;
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index = 0;
+ path[level].bp_oldreq.bpr_ptr = NILFS_BMAP_INVALID_PTR;
+ path[level].bp_newreq.bpr_ptr = NILFS_BMAP_INVALID_PTR;
+ path[level].bp_op = NULL;
+ }
+
+out:
+ return path;
+}
+
+static void nilfs_btree_free_path(struct nilfs_btree_path *path)
+{
+ int level = NILFS_BTREE_LEVEL_DATA;
+
+ for (; level < NILFS_BTREE_LEVEL_MAX; level++)
+ brelse(path[level].bp_bh);
+
+ kmem_cache_free(nilfs_btree_path_cache, path);
+}
+
+/*
+ * B-tree node operations
+ */
+static int nilfs_btree_get_new_block(const struct nilfs_bmap *btree,
+ __u64 ptr, struct buffer_head **bhp)
+{
+ struct inode *btnc_inode = NILFS_BMAP_I(btree)->i_assoc_inode;
+ struct address_space *btnc = btnc_inode->i_mapping;
+ struct buffer_head *bh;
+
+ bh = nilfs_btnode_create_block(btnc, ptr);
+ if (IS_ERR(bh))
+ return PTR_ERR(bh);
+
+ set_buffer_nilfs_volatile(bh);
+ *bhp = bh;
+ return 0;
+}
+
+static int nilfs_btree_node_get_flags(const struct nilfs_btree_node *node)
+{
+ return node->bn_flags;
+}
+
+static void
+nilfs_btree_node_set_flags(struct nilfs_btree_node *node, int flags)
+{
+ node->bn_flags = flags;
+}
+
+static int nilfs_btree_node_root(const struct nilfs_btree_node *node)
+{
+ return nilfs_btree_node_get_flags(node) & NILFS_BTREE_NODE_ROOT;
+}
+
+static int nilfs_btree_node_get_level(const struct nilfs_btree_node *node)
+{
+ return node->bn_level;
+}
+
+static void
+nilfs_btree_node_set_level(struct nilfs_btree_node *node, int level)
+{
+ node->bn_level = level;
+}
+
+static int nilfs_btree_node_get_nchildren(const struct nilfs_btree_node *node)
+{
+ return le16_to_cpu(node->bn_nchildren);
+}
+
+static void
+nilfs_btree_node_set_nchildren(struct nilfs_btree_node *node, int nchildren)
+{
+ node->bn_nchildren = cpu_to_le16(nchildren);
+}
+
+static int nilfs_btree_node_size(const struct nilfs_bmap *btree)
+{
+ return i_blocksize(btree->b_inode);
+}
+
+static int nilfs_btree_nchildren_per_block(const struct nilfs_bmap *btree)
+{
+ return btree->b_nchildren_per_block;
+}
+
+static __le64 *
+nilfs_btree_node_dkeys(const struct nilfs_btree_node *node)
+{
+ return (__le64 *)((char *)(node + 1) +
+ (nilfs_btree_node_root(node) ?
+ 0 : NILFS_BTREE_NODE_EXTRA_PAD_SIZE));
+}
+
+static __le64 *
+nilfs_btree_node_dptrs(const struct nilfs_btree_node *node, int ncmax)
+{
+ return (__le64 *)(nilfs_btree_node_dkeys(node) + ncmax);
+}
+
+static __u64
+nilfs_btree_node_get_key(const struct nilfs_btree_node *node, int index)
+{
+ return le64_to_cpu(*(nilfs_btree_node_dkeys(node) + index));
+}
+
+static void
+nilfs_btree_node_set_key(struct nilfs_btree_node *node, int index, __u64 key)
+{
+ *(nilfs_btree_node_dkeys(node) + index) = cpu_to_le64(key);
+}
+
+static __u64
+nilfs_btree_node_get_ptr(const struct nilfs_btree_node *node, int index,
+ int ncmax)
+{
+ return le64_to_cpu(*(nilfs_btree_node_dptrs(node, ncmax) + index));
+}
+
+static void
+nilfs_btree_node_set_ptr(struct nilfs_btree_node *node, int index, __u64 ptr,
+ int ncmax)
+{
+ *(nilfs_btree_node_dptrs(node, ncmax) + index) = cpu_to_le64(ptr);
+}
+
+static void nilfs_btree_node_init(struct nilfs_btree_node *node, int flags,
+ int level, int nchildren, int ncmax,
+ const __u64 *keys, const __u64 *ptrs)
+{
+ __le64 *dkeys;
+ __le64 *dptrs;
+ int i;
+
+ nilfs_btree_node_set_flags(node, flags);
+ nilfs_btree_node_set_level(node, level);
+ nilfs_btree_node_set_nchildren(node, nchildren);
+
+ dkeys = nilfs_btree_node_dkeys(node);
+ dptrs = nilfs_btree_node_dptrs(node, ncmax);
+ for (i = 0; i < nchildren; i++) {
+ dkeys[i] = cpu_to_le64(keys[i]);
+ dptrs[i] = cpu_to_le64(ptrs[i]);
+ }
+}
+
+/* Assume the buffer heads corresponding to left and right are locked. */
+static void nilfs_btree_node_move_left(struct nilfs_btree_node *left,
+ struct nilfs_btree_node *right,
+ int n, int lncmax, int rncmax)
+{
+ __le64 *ldkeys, *rdkeys;
+ __le64 *ldptrs, *rdptrs;
+ int lnchildren, rnchildren;
+
+ ldkeys = nilfs_btree_node_dkeys(left);
+ ldptrs = nilfs_btree_node_dptrs(left, lncmax);
+ lnchildren = nilfs_btree_node_get_nchildren(left);
+
+ rdkeys = nilfs_btree_node_dkeys(right);
+ rdptrs = nilfs_btree_node_dptrs(right, rncmax);
+ rnchildren = nilfs_btree_node_get_nchildren(right);
+
+ memcpy(ldkeys + lnchildren, rdkeys, n * sizeof(*rdkeys));
+ memcpy(ldptrs + lnchildren, rdptrs, n * sizeof(*rdptrs));
+ memmove(rdkeys, rdkeys + n, (rnchildren - n) * sizeof(*rdkeys));
+ memmove(rdptrs, rdptrs + n, (rnchildren - n) * sizeof(*rdptrs));
+
+ lnchildren += n;
+ rnchildren -= n;
+ nilfs_btree_node_set_nchildren(left, lnchildren);
+ nilfs_btree_node_set_nchildren(right, rnchildren);
+}
+
+/* Assume that the buffer heads corresponding to left and right are locked. */
+static void nilfs_btree_node_move_right(struct nilfs_btree_node *left,
+ struct nilfs_btree_node *right,
+ int n, int lncmax, int rncmax)
+{
+ __le64 *ldkeys, *rdkeys;
+ __le64 *ldptrs, *rdptrs;
+ int lnchildren, rnchildren;
+
+ ldkeys = nilfs_btree_node_dkeys(left);
+ ldptrs = nilfs_btree_node_dptrs(left, lncmax);
+ lnchildren = nilfs_btree_node_get_nchildren(left);
+
+ rdkeys = nilfs_btree_node_dkeys(right);
+ rdptrs = nilfs_btree_node_dptrs(right, rncmax);
+ rnchildren = nilfs_btree_node_get_nchildren(right);
+
+ memmove(rdkeys + n, rdkeys, rnchildren * sizeof(*rdkeys));
+ memmove(rdptrs + n, rdptrs, rnchildren * sizeof(*rdptrs));
+ memcpy(rdkeys, ldkeys + lnchildren - n, n * sizeof(*rdkeys));
+ memcpy(rdptrs, ldptrs + lnchildren - n, n * sizeof(*rdptrs));
+
+ lnchildren -= n;
+ rnchildren += n;
+ nilfs_btree_node_set_nchildren(left, lnchildren);
+ nilfs_btree_node_set_nchildren(right, rnchildren);
+}
+
+/* Assume that the buffer head corresponding to node is locked. */
+static void nilfs_btree_node_insert(struct nilfs_btree_node *node, int index,
+ __u64 key, __u64 ptr, int ncmax)
+{
+ __le64 *dkeys;
+ __le64 *dptrs;
+ int nchildren;
+
+ dkeys = nilfs_btree_node_dkeys(node);
+ dptrs = nilfs_btree_node_dptrs(node, ncmax);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ if (index < nchildren) {
+ memmove(dkeys + index + 1, dkeys + index,
+ (nchildren - index) * sizeof(*dkeys));
+ memmove(dptrs + index + 1, dptrs + index,
+ (nchildren - index) * sizeof(*dptrs));
+ }
+ dkeys[index] = cpu_to_le64(key);
+ dptrs[index] = cpu_to_le64(ptr);
+ nchildren++;
+ nilfs_btree_node_set_nchildren(node, nchildren);
+}
+
+/* Assume that the buffer head corresponding to node is locked. */
+static void nilfs_btree_node_delete(struct nilfs_btree_node *node, int index,
+ __u64 *keyp, __u64 *ptrp, int ncmax)
+{
+ __u64 key;
+ __u64 ptr;
+ __le64 *dkeys;
+ __le64 *dptrs;
+ int nchildren;
+
+ dkeys = nilfs_btree_node_dkeys(node);
+ dptrs = nilfs_btree_node_dptrs(node, ncmax);
+ key = le64_to_cpu(dkeys[index]);
+ ptr = le64_to_cpu(dptrs[index]);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ if (keyp != NULL)
+ *keyp = key;
+ if (ptrp != NULL)
+ *ptrp = ptr;
+
+ if (index < nchildren - 1) {
+ memmove(dkeys + index, dkeys + index + 1,
+ (nchildren - index - 1) * sizeof(*dkeys));
+ memmove(dptrs + index, dptrs + index + 1,
+ (nchildren - index - 1) * sizeof(*dptrs));
+ }
+ nchildren--;
+ nilfs_btree_node_set_nchildren(node, nchildren);
+}
+
+static int nilfs_btree_node_lookup(const struct nilfs_btree_node *node,
+ __u64 key, int *indexp)
+{
+ __u64 nkey;
+ int index, low, high, s;
+
+ /* binary search */
+ low = 0;
+ high = nilfs_btree_node_get_nchildren(node) - 1;
+ index = 0;
+ s = 0;
+ while (low <= high) {
+ index = (low + high) / 2;
+ nkey = nilfs_btree_node_get_key(node, index);
+ if (nkey == key) {
+ s = 0;
+ goto out;
+ } else if (nkey < key) {
+ low = index + 1;
+ s = -1;
+ } else {
+ high = index - 1;
+ s = 1;
+ }
+ }
+
+ /* adjust index */
+ if (nilfs_btree_node_get_level(node) > NILFS_BTREE_LEVEL_NODE_MIN) {
+ if (s > 0 && index > 0)
+ index--;
+ } else if (s < 0)
+ index++;
+
+ out:
+ *indexp = index;
+
+ return s == 0;
+}
+
+/**
+ * nilfs_btree_node_broken - verify consistency of btree node
+ * @node: btree node block to be examined
+ * @size: node size (in bytes)
+ * @inode: host inode of btree
+ * @blocknr: block number
+ *
+ * Return: 0 if normal, 1 if the node is broken.
+ */
+static int nilfs_btree_node_broken(const struct nilfs_btree_node *node,
+ size_t size, struct inode *inode,
+ sector_t blocknr)
+{
+ int level, flags, nchildren;
+ __u64 key, prev_key;
+ int i;
+
+ level = nilfs_btree_node_get_level(node);
+ flags = nilfs_btree_node_get_flags(node);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+
+ if (unlikely(level < NILFS_BTREE_LEVEL_NODE_MIN ||
+ level >= NILFS_BTREE_LEVEL_MAX ||
+ (flags & NILFS_BTREE_NODE_ROOT) ||
+ nchildren <= 0 ||
+ nchildren > NILFS_BTREE_NODE_NCHILDREN_MAX(size))) {
+ nilfs_crit(inode->i_sb,
+ "bad btree node (ino=%llu, blocknr=%llu): level = %d, flags = 0x%x, nchildren = %d",
+ inode->i_ino, (unsigned long long)blocknr, level,
+ flags, nchildren);
+ return 1;
+ }
+
+ for (i = 1, prev_key = nilfs_btree_node_get_key(node, 0);
+ i < nchildren; i++, prev_key = key) {
+ key = nilfs_btree_node_get_key(node, i);
+ if (unlikely(key <= prev_key)) {
+ nilfs_crit(inode->i_sb,
+ "bad btree node (ino=%llu, blocknr=%llu): unsorted keys at index %d (%llu) and %d (%llu)",
+ inode->i_ino, (unsigned long long)blocknr,
+ i - 1, prev_key, i, key);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * nilfs_btree_root_broken - verify consistency of btree root node
+ * @node: btree root node to be examined
+ * @inode: host inode of btree
+ *
+ * Return: 0 if normal, 1 if the root node is broken.
+ */
+static int nilfs_btree_root_broken(const struct nilfs_btree_node *node,
+ struct inode *inode)
+{
+ int level, flags, nchildren;
+ int ret = 0;
+
+ level = nilfs_btree_node_get_level(node);
+ flags = nilfs_btree_node_get_flags(node);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+
+ if (unlikely(level < NILFS_BTREE_LEVEL_NODE_MIN ||
+ level >= NILFS_BTREE_LEVEL_MAX ||
+ nchildren < 0 ||
+ nchildren > NILFS_BTREE_ROOT_NCHILDREN_MAX ||
+ (nchildren == 0 && level > NILFS_BTREE_LEVEL_NODE_MIN))) {
+ nilfs_crit(inode->i_sb,
+ "bad btree root (ino=%llu): level = %d, flags = 0x%x, nchildren = %d",
+ inode->i_ino, level, flags, nchildren);
+ ret = 1;
+ }
+ return ret;
+}
+
+int nilfs_btree_broken_node_block(struct buffer_head *bh)
+{
+ struct inode *inode;
+ int ret;
+
+ if (buffer_nilfs_checked(bh))
+ return 0;
+
+ inode = bh->b_folio->mapping->host;
+ ret = nilfs_btree_node_broken((struct nilfs_btree_node *)bh->b_data,
+ bh->b_size, inode, bh->b_blocknr);
+ if (likely(!ret))
+ set_buffer_nilfs_checked(bh);
+ return ret;
+}
+
+static struct nilfs_btree_node *
+nilfs_btree_get_root(const struct nilfs_bmap *btree)
+{
+ return (struct nilfs_btree_node *)btree->b_u.u_data;
+}
+
+static struct nilfs_btree_node *
+nilfs_btree_get_nonroot_node(const struct nilfs_btree_path *path, int level)
+{
+ return (struct nilfs_btree_node *)path[level].bp_bh->b_data;
+}
+
+static struct nilfs_btree_node *
+nilfs_btree_get_sib_node(const struct nilfs_btree_path *path, int level)
+{
+ return (struct nilfs_btree_node *)path[level].bp_sib_bh->b_data;
+}
+
+static int nilfs_btree_height(const struct nilfs_bmap *btree)
+{
+ return nilfs_btree_node_get_level(nilfs_btree_get_root(btree)) + 1;
+}
+
+static struct nilfs_btree_node *
+nilfs_btree_get_node(const struct nilfs_bmap *btree,
+ const struct nilfs_btree_path *path,
+ int level, int *ncmaxp)
+{
+ struct nilfs_btree_node *node;
+
+ if (level == nilfs_btree_height(btree) - 1) {
+ node = nilfs_btree_get_root(btree);
+ *ncmaxp = NILFS_BTREE_ROOT_NCHILDREN_MAX;
+ } else {
+ node = nilfs_btree_get_nonroot_node(path, level);
+ *ncmaxp = nilfs_btree_nchildren_per_block(btree);
+ }
+ return node;
+}
+
+static int nilfs_btree_bad_node(const struct nilfs_bmap *btree,
+ struct nilfs_btree_node *node, int level)
+{
+ if (unlikely(nilfs_btree_node_get_level(node) != level)) {
+ dump_stack();
+ nilfs_crit(btree->b_inode->i_sb,
+ "btree level mismatch (ino=%llu): %d != %d",
+ btree->b_inode->i_ino,
+ nilfs_btree_node_get_level(node), level);
+ return 1;
+ }
+ return 0;
+}
+
+struct nilfs_btree_readahead_info {
+ struct nilfs_btree_node *node; /* parent node */
+ int max_ra_blocks; /* max nof blocks to read ahead */
+ int index; /* current index on the parent node */
+ int ncmax; /* nof children in the parent node */
+};
+
+static int __nilfs_btree_get_block(const struct nilfs_bmap *btree, __u64 ptr,
+ struct buffer_head **bhp,
+ const struct nilfs_btree_readahead_info *ra)
+{
+ struct inode *btnc_inode = NILFS_BMAP_I(btree)->i_assoc_inode;
+ struct address_space *btnc = btnc_inode->i_mapping;
+ struct buffer_head *bh, *ra_bh;
+ sector_t submit_ptr = 0;
+ int ret;
+
+ ret = nilfs_btnode_submit_block(btnc, ptr, 0, REQ_OP_READ, &bh,
+ &submit_ptr);
+ if (ret) {
+ if (likely(ret == -EEXIST))
+ goto out_check;
+ if (ret == -ENOENT) {
+ /*
+ * Block address translation failed due to invalid
+ * value of 'ptr'. In this case, return internal code
+ * -EINVAL (broken bmap) to notify bmap layer of fatal
+ * metadata corruption.
+ */
+ ret = -EINVAL;
+ }
+ return ret;
+ }
+
+ if (ra) {
+ int i, n;
+ __u64 ptr2;
+
+ /* read ahead sibling nodes */
+ for (n = ra->max_ra_blocks, i = ra->index + 1;
+ n > 0 && i < ra->ncmax; n--, i++) {
+ ptr2 = nilfs_btree_node_get_ptr(ra->node, i, ra->ncmax);
+
+ ret = nilfs_btnode_submit_block(btnc, ptr2, 0,
+ REQ_OP_READ | REQ_RAHEAD,
+ &ra_bh, &submit_ptr);
+ if (likely(!ret || ret == -EEXIST))
+ brelse(ra_bh);
+ else if (ret != -EBUSY)
+ break;
+ if (!buffer_locked(bh))
+ goto out_no_wait;
+ }
+ }
+
+ wait_on_buffer(bh);
+
+ out_no_wait:
+ if (!buffer_uptodate(bh)) {
+ nilfs_err(btree->b_inode->i_sb,
+ "I/O error reading b-tree node block (ino=%llu, blocknr=%llu)",
+ btree->b_inode->i_ino, (unsigned long long)ptr);
+ brelse(bh);
+ return -EIO;
+ }
+
+ out_check:
+ if (nilfs_btree_broken_node_block(bh)) {
+ clear_buffer_uptodate(bh);
+ brelse(bh);
+ return -EINVAL;
+ }
+
+ *bhp = bh;
+ return 0;
+}
+
+static int nilfs_btree_get_block(const struct nilfs_bmap *btree, __u64 ptr,
+ struct buffer_head **bhp)
+{
+ return __nilfs_btree_get_block(btree, ptr, bhp, NULL);
+}
+
+static int nilfs_btree_do_lookup(const struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ __u64 key, __u64 *ptrp, int minlevel,
+ int readahead)
+{
+ struct nilfs_btree_node *node;
+ struct nilfs_btree_readahead_info p, *ra;
+ __u64 ptr;
+ int level, index, found, ncmax, ret;
+
+ node = nilfs_btree_get_root(btree);
+ level = nilfs_btree_node_get_level(node);
+ if (level < minlevel || nilfs_btree_node_get_nchildren(node) <= 0)
+ return -ENOENT;
+
+ found = nilfs_btree_node_lookup(node, key, &index);
+ ptr = nilfs_btree_node_get_ptr(node, index,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ path[level].bp_bh = NULL;
+ path[level].bp_index = index;
+
+ ncmax = nilfs_btree_nchildren_per_block(btree);
+
+ while (--level >= minlevel) {
+ ra = NULL;
+ if (level == NILFS_BTREE_LEVEL_NODE_MIN && readahead) {
+ p.node = nilfs_btree_get_node(btree, path, level + 1,
+ &p.ncmax);
+ p.index = index;
+ p.max_ra_blocks = 7;
+ ra = &p;
+ }
+ ret = __nilfs_btree_get_block(btree, ptr, &path[level].bp_bh,
+ ra);
+ if (ret < 0)
+ return ret;
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ if (nilfs_btree_bad_node(btree, node, level))
+ return -EINVAL;
+ if (!found)
+ found = nilfs_btree_node_lookup(node, key, &index);
+ else
+ index = 0;
+ if (index < ncmax) {
+ ptr = nilfs_btree_node_get_ptr(node, index, ncmax);
+ } else {
+ WARN_ON(found || level != NILFS_BTREE_LEVEL_NODE_MIN);
+ /* insert */
+ ptr = NILFS_BMAP_INVALID_PTR;
+ }
+ path[level].bp_index = index;
+ }
+ if (!found)
+ return -ENOENT;
+
+ if (ptrp != NULL)
+ *ptrp = ptr;
+
+ return 0;
+}
+
+static int nilfs_btree_do_lookup_last(const struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node;
+ __u64 ptr;
+ int index, level, ncmax, ret;
+
+ node = nilfs_btree_get_root(btree);
+ index = nilfs_btree_node_get_nchildren(node) - 1;
+ if (index < 0)
+ return -ENOENT;
+ level = nilfs_btree_node_get_level(node);
+ ptr = nilfs_btree_node_get_ptr(node, index,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ path[level].bp_bh = NULL;
+ path[level].bp_index = index;
+ ncmax = nilfs_btree_nchildren_per_block(btree);
+
+ for (level--; level > 0; level--) {
+ ret = nilfs_btree_get_block(btree, ptr, &path[level].bp_bh);
+ if (ret < 0)
+ return ret;
+ node = nilfs_btree_get_nonroot_node(path, level);
+ if (nilfs_btree_bad_node(btree, node, level))
+ return -EINVAL;
+ index = nilfs_btree_node_get_nchildren(node) - 1;
+ ptr = nilfs_btree_node_get_ptr(node, index, ncmax);
+ path[level].bp_index = index;
+ }
+
+ if (keyp != NULL)
+ *keyp = nilfs_btree_node_get_key(node, index);
+ if (ptrp != NULL)
+ *ptrp = ptr;
+
+ return 0;
+}
+
+/**
+ * nilfs_btree_get_next_key - get next valid key from btree path array
+ * @btree: bmap struct of btree
+ * @path: array of nilfs_btree_path struct
+ * @minlevel: start level
+ * @nextkey: place to store the next valid key
+ *
+ * Return: 0 if the next key was found, %-ENOENT if not found.
+ */
+static int nilfs_btree_get_next_key(const struct nilfs_bmap *btree,
+ const struct nilfs_btree_path *path,
+ int minlevel, __u64 *nextkey)
+{
+ struct nilfs_btree_node *node;
+ int maxlevel = nilfs_btree_height(btree) - 1;
+ int index, next_adj, level;
+
+ /* Next index is already set to bp_index for leaf nodes. */
+ next_adj = 0;
+ for (level = minlevel; level <= maxlevel; level++) {
+ if (level == maxlevel)
+ node = nilfs_btree_get_root(btree);
+ else
+ node = nilfs_btree_get_nonroot_node(path, level);
+
+ index = path[level].bp_index + next_adj;
+ if (index < nilfs_btree_node_get_nchildren(node)) {
+ /* Next key is in this node */
+ *nextkey = nilfs_btree_node_get_key(node, index);
+ return 0;
+ }
+ /* For non-leaf nodes, next index is stored at bp_index + 1. */
+ next_adj = 1;
+ }
+ return -ENOENT;
+}
+
+static int nilfs_btree_lookup(const struct nilfs_bmap *btree,
+ __u64 key, int level, __u64 *ptrp)
+{
+ struct nilfs_btree_path *path;
+ int ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, key, ptrp, level, 0);
+
+ nilfs_btree_free_path(path);
+
+ return ret;
+}
+
+static int nilfs_btree_lookup_contig(const struct nilfs_bmap *btree,
+ __u64 key, __u64 *ptrp,
+ unsigned int maxblocks)
+{
+ struct nilfs_btree_path *path;
+ struct nilfs_btree_node *node;
+ struct inode *dat = NULL;
+ __u64 ptr, ptr2;
+ sector_t blocknr;
+ int level = NILFS_BTREE_LEVEL_NODE_MIN;
+ int ret, cnt, index, maxlevel, ncmax;
+ struct nilfs_btree_readahead_info p;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, key, &ptr, level, 1);
+ if (ret < 0)
+ goto out;
+
+ if (NILFS_BMAP_USE_VBN(btree)) {
+ dat = nilfs_bmap_get_dat(btree);
+ ret = nilfs_dat_translate(dat, ptr, &blocknr);
+ if (ret < 0)
+ goto dat_error;
+ ptr = blocknr;
+ }
+ cnt = 1;
+ if (cnt == maxblocks)
+ goto end;
+
+ maxlevel = nilfs_btree_height(btree) - 1;
+ node = nilfs_btree_get_node(btree, path, level, &ncmax);
+ index = path[level].bp_index + 1;
+ for (;;) {
+ while (index < nilfs_btree_node_get_nchildren(node)) {
+ if (nilfs_btree_node_get_key(node, index) !=
+ key + cnt)
+ goto end;
+ ptr2 = nilfs_btree_node_get_ptr(node, index, ncmax);
+ if (dat) {
+ ret = nilfs_dat_translate(dat, ptr2, &blocknr);
+ if (ret < 0)
+ goto dat_error;
+ ptr2 = blocknr;
+ }
+ if (ptr2 != ptr + cnt || ++cnt == maxblocks)
+ goto end;
+ index++;
+ }
+ if (level == maxlevel)
+ break;
+
+ /* look-up right sibling node */
+ p.node = nilfs_btree_get_node(btree, path, level + 1, &p.ncmax);
+ p.index = path[level + 1].bp_index + 1;
+ p.max_ra_blocks = 7;
+ if (p.index >= nilfs_btree_node_get_nchildren(p.node) ||
+ nilfs_btree_node_get_key(p.node, p.index) != key + cnt)
+ break;
+ ptr2 = nilfs_btree_node_get_ptr(p.node, p.index, p.ncmax);
+ path[level + 1].bp_index = p.index;
+
+ brelse(path[level].bp_bh);
+ path[level].bp_bh = NULL;
+
+ ret = __nilfs_btree_get_block(btree, ptr2, &path[level].bp_bh,
+ &p);
+ if (ret < 0)
+ goto out;
+ node = nilfs_btree_get_nonroot_node(path, level);
+ ncmax = nilfs_btree_nchildren_per_block(btree);
+ index = 0;
+ path[level].bp_index = index;
+ }
+ end:
+ *ptrp = ptr;
+ ret = cnt;
+ out:
+ nilfs_btree_free_path(path);
+ return ret;
+
+ dat_error:
+ if (ret == -ENOENT)
+ ret = -EINVAL; /* Notify bmap layer of metadata corruption */
+ goto out;
+}
+
+static void nilfs_btree_promote_key(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 key)
+{
+ if (level < nilfs_btree_height(btree) - 1) {
+ do {
+ nilfs_btree_node_set_key(
+ nilfs_btree_get_nonroot_node(path, level),
+ path[level].bp_index, key);
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ } while ((path[level].bp_index == 0) &&
+ (++level < nilfs_btree_height(btree) - 1));
+ }
+
+ /* root */
+ if (level == nilfs_btree_height(btree) - 1) {
+ nilfs_btree_node_set_key(nilfs_btree_get_root(btree),
+ path[level].bp_index, key);
+ }
+}
+
+static void nilfs_btree_do_insert(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node;
+ int ncblk;
+
+ if (level < nilfs_btree_height(btree) - 1) {
+ node = nilfs_btree_get_nonroot_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ nilfs_btree_node_insert(node, path[level].bp_index,
+ *keyp, *ptrp, ncblk);
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+
+ if (path[level].bp_index == 0)
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(node,
+ 0));
+ } else {
+ node = nilfs_btree_get_root(btree);
+ nilfs_btree_node_insert(node, path[level].bp_index,
+ *keyp, *ptrp,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ }
+}
+
+static void nilfs_btree_carry_left(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *left;
+ int nchildren, lnchildren, n, move, ncblk;
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ left = nilfs_btree_get_sib_node(path, level);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ lnchildren = nilfs_btree_node_get_nchildren(left);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ move = 0;
+
+ n = (nchildren + lnchildren + 1) / 2 - lnchildren;
+ if (n > path[level].bp_index) {
+ /* move insert point */
+ n--;
+ move = 1;
+ }
+
+ nilfs_btree_node_move_left(left, node, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(node, 0));
+
+ if (move) {
+ brelse(path[level].bp_bh);
+ path[level].bp_bh = path[level].bp_sib_bh;
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index += lnchildren;
+ path[level + 1].bp_index--;
+ } else {
+ brelse(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index -= n;
+ }
+
+ nilfs_btree_do_insert(btree, path, level, keyp, ptrp);
+}
+
+static void nilfs_btree_carry_right(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *right;
+ int nchildren, rnchildren, n, move, ncblk;
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ right = nilfs_btree_get_sib_node(path, level);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ rnchildren = nilfs_btree_node_get_nchildren(right);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ move = 0;
+
+ n = (nchildren + rnchildren + 1) / 2 - rnchildren;
+ if (n > nchildren - path[level].bp_index) {
+ /* move insert point */
+ n--;
+ move = 1;
+ }
+
+ nilfs_btree_node_move_right(node, right, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ path[level + 1].bp_index++;
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(right, 0));
+ path[level + 1].bp_index--;
+
+ if (move) {
+ brelse(path[level].bp_bh);
+ path[level].bp_bh = path[level].bp_sib_bh;
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index -= nilfs_btree_node_get_nchildren(node);
+ path[level + 1].bp_index++;
+ } else {
+ brelse(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+ }
+
+ nilfs_btree_do_insert(btree, path, level, keyp, ptrp);
+}
+
+static void nilfs_btree_split(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *right;
+ int nchildren, n, move, ncblk;
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ right = nilfs_btree_get_sib_node(path, level);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ move = 0;
+
+ n = (nchildren + 1) / 2;
+ if (n > nchildren - path[level].bp_index) {
+ n--;
+ move = 1;
+ }
+
+ nilfs_btree_node_move_right(node, right, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ if (move) {
+ path[level].bp_index -= nilfs_btree_node_get_nchildren(node);
+ nilfs_btree_node_insert(right, path[level].bp_index,
+ *keyp, *ptrp, ncblk);
+
+ *keyp = nilfs_btree_node_get_key(right, 0);
+ *ptrp = path[level].bp_newreq.bpr_ptr;
+
+ brelse(path[level].bp_bh);
+ path[level].bp_bh = path[level].bp_sib_bh;
+ path[level].bp_sib_bh = NULL;
+ } else {
+ nilfs_btree_do_insert(btree, path, level, keyp, ptrp);
+
+ *keyp = nilfs_btree_node_get_key(right, 0);
+ *ptrp = path[level].bp_newreq.bpr_ptr;
+
+ brelse(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+ }
+
+ path[level + 1].bp_index++;
+}
+
+static void nilfs_btree_grow(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *root, *child;
+ int n, ncblk;
+
+ root = nilfs_btree_get_root(btree);
+ child = nilfs_btree_get_sib_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ n = nilfs_btree_node_get_nchildren(root);
+
+ nilfs_btree_node_move_right(root, child, n,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX, ncblk);
+ nilfs_btree_node_set_level(root, level + 1);
+
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ path[level].bp_bh = path[level].bp_sib_bh;
+ path[level].bp_sib_bh = NULL;
+
+ nilfs_btree_do_insert(btree, path, level, keyp, ptrp);
+
+ *keyp = nilfs_btree_node_get_key(child, 0);
+ *ptrp = path[level].bp_newreq.bpr_ptr;
+}
+
+static __u64 nilfs_btree_find_near(const struct nilfs_bmap *btree,
+ const struct nilfs_btree_path *path)
+{
+ struct nilfs_btree_node *node;
+ int level, ncmax;
+
+ if (path == NULL)
+ return NILFS_BMAP_INVALID_PTR;
+
+ /* left sibling */
+ level = NILFS_BTREE_LEVEL_NODE_MIN;
+ if (path[level].bp_index > 0) {
+ node = nilfs_btree_get_node(btree, path, level, &ncmax);
+ return nilfs_btree_node_get_ptr(node,
+ path[level].bp_index - 1,
+ ncmax);
+ }
+
+ /* parent */
+ level = NILFS_BTREE_LEVEL_NODE_MIN + 1;
+ if (level <= nilfs_btree_height(btree) - 1) {
+ node = nilfs_btree_get_node(btree, path, level, &ncmax);
+ return nilfs_btree_node_get_ptr(node, path[level].bp_index,
+ ncmax);
+ }
+
+ return NILFS_BMAP_INVALID_PTR;
+}
+
+static __u64 nilfs_btree_find_target_v(const struct nilfs_bmap *btree,
+ const struct nilfs_btree_path *path,
+ __u64 key)
+{
+ __u64 ptr;
+
+ ptr = nilfs_bmap_find_target_seq(btree, key);
+ if (ptr != NILFS_BMAP_INVALID_PTR)
+ /* sequential access */
+ return ptr;
+
+ ptr = nilfs_btree_find_near(btree, path);
+ if (ptr != NILFS_BMAP_INVALID_PTR)
+ /* near */
+ return ptr;
+
+ /* block group */
+ return nilfs_bmap_find_target_in_group(btree);
+}
+
+static int nilfs_btree_prepare_insert(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int *levelp, __u64 key, __u64 ptr,
+ struct nilfs_bmap_stats *stats)
+{
+ struct buffer_head *bh;
+ struct nilfs_btree_node *node, *parent, *sib;
+ __u64 sibptr;
+ int pindex, level, ncmax, ncblk, ret;
+ struct inode *dat = NULL;
+
+ stats->bs_nblocks = 0;
+ level = NILFS_BTREE_LEVEL_DATA;
+
+ /* allocate a new ptr for data block */
+ if (NILFS_BMAP_USE_VBN(btree)) {
+ path[level].bp_newreq.bpr_ptr =
+ nilfs_btree_find_target_v(btree, path, key);
+ dat = nilfs_bmap_get_dat(btree);
+ }
+
+ ret = nilfs_bmap_prepare_alloc_ptr(btree, &path[level].bp_newreq, dat);
+ if (ret < 0)
+ goto err_out_data;
+
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN;
+ level < nilfs_btree_height(btree) - 1;
+ level++) {
+ node = nilfs_btree_get_nonroot_node(path, level);
+ if (nilfs_btree_node_get_nchildren(node) < ncblk) {
+ path[level].bp_op = nilfs_btree_do_insert;
+ stats->bs_nblocks++;
+ goto out;
+ }
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ pindex = path[level + 1].bp_index;
+
+ /* left sibling */
+ if (pindex > 0) {
+ sibptr = nilfs_btree_node_get_ptr(parent, pindex - 1,
+ ncmax);
+ ret = nilfs_btree_get_block(btree, sibptr, &bh);
+ if (ret < 0)
+ goto err_out_child_node;
+ sib = (struct nilfs_btree_node *)bh->b_data;
+ if (nilfs_btree_node_get_nchildren(sib) < ncblk) {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_carry_left;
+ stats->bs_nblocks++;
+ goto out;
+ } else {
+ brelse(bh);
+ }
+ }
+
+ /* right sibling */
+ if (pindex < nilfs_btree_node_get_nchildren(parent) - 1) {
+ sibptr = nilfs_btree_node_get_ptr(parent, pindex + 1,
+ ncmax);
+ ret = nilfs_btree_get_block(btree, sibptr, &bh);
+ if (ret < 0)
+ goto err_out_child_node;
+ sib = (struct nilfs_btree_node *)bh->b_data;
+ if (nilfs_btree_node_get_nchildren(sib) < ncblk) {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_carry_right;
+ stats->bs_nblocks++;
+ goto out;
+ } else {
+ brelse(bh);
+ }
+ }
+
+ /* split */
+ path[level].bp_newreq.bpr_ptr =
+ path[level - 1].bp_newreq.bpr_ptr + 1;
+ ret = nilfs_bmap_prepare_alloc_ptr(btree,
+ &path[level].bp_newreq, dat);
+ if (ret < 0)
+ goto err_out_child_node;
+ ret = nilfs_btree_get_new_block(btree,
+ path[level].bp_newreq.bpr_ptr,
+ &bh);
+ if (ret < 0)
+ goto err_out_curr_node;
+
+ stats->bs_nblocks++;
+
+ sib = (struct nilfs_btree_node *)bh->b_data;
+ nilfs_btree_node_init(sib, 0, level, 0, ncblk, NULL, NULL);
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_split;
+ }
+
+ /* root */
+ node = nilfs_btree_get_root(btree);
+ if (nilfs_btree_node_get_nchildren(node) <
+ NILFS_BTREE_ROOT_NCHILDREN_MAX) {
+ path[level].bp_op = nilfs_btree_do_insert;
+ stats->bs_nblocks++;
+ goto out;
+ }
+
+ /* grow */
+ path[level].bp_newreq.bpr_ptr = path[level - 1].bp_newreq.bpr_ptr + 1;
+ ret = nilfs_bmap_prepare_alloc_ptr(btree, &path[level].bp_newreq, dat);
+ if (ret < 0)
+ goto err_out_child_node;
+ ret = nilfs_btree_get_new_block(btree, path[level].bp_newreq.bpr_ptr,
+ &bh);
+ if (ret < 0)
+ goto err_out_curr_node;
+
+ nilfs_btree_node_init((struct nilfs_btree_node *)bh->b_data,
+ 0, level, 0, ncblk, NULL, NULL);
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_grow;
+
+ level++;
+ path[level].bp_op = nilfs_btree_do_insert;
+
+ /* a newly-created node block and a data block are added */
+ stats->bs_nblocks += 2;
+
+ /* success */
+ out:
+ *levelp = level;
+ return ret;
+
+ /* error */
+ err_out_curr_node:
+ nilfs_bmap_abort_alloc_ptr(btree, &path[level].bp_newreq, dat);
+ err_out_child_node:
+ for (level--; level > NILFS_BTREE_LEVEL_DATA; level--) {
+ nilfs_btnode_delete(path[level].bp_sib_bh);
+ nilfs_bmap_abort_alloc_ptr(btree, &path[level].bp_newreq, dat);
+
+ }
+
+ nilfs_bmap_abort_alloc_ptr(btree, &path[level].bp_newreq, dat);
+ err_out_data:
+ *levelp = level;
+ stats->bs_nblocks = 0;
+ return ret;
+}
+
+static void nilfs_btree_commit_insert(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int maxlevel, __u64 key, __u64 ptr)
+{
+ struct inode *dat = NULL;
+ int level;
+
+ set_buffer_nilfs_volatile((struct buffer_head *)((unsigned long)ptr));
+ ptr = path[NILFS_BTREE_LEVEL_DATA].bp_newreq.bpr_ptr;
+ if (NILFS_BMAP_USE_VBN(btree)) {
+ nilfs_bmap_set_target_v(btree, key, ptr);
+ dat = nilfs_bmap_get_dat(btree);
+ }
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN; level <= maxlevel; level++) {
+ nilfs_bmap_commit_alloc_ptr(btree,
+ &path[level - 1].bp_newreq, dat);
+ path[level].bp_op(btree, path, level, &key, &ptr);
+ }
+
+ if (!nilfs_bmap_dirty(btree))
+ nilfs_bmap_set_dirty(btree);
+}
+
+static int nilfs_btree_insert(struct nilfs_bmap *btree, __u64 key, __u64 ptr)
+{
+ struct nilfs_btree_path *path;
+ struct nilfs_bmap_stats stats;
+ int level, ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, key, NULL,
+ NILFS_BTREE_LEVEL_NODE_MIN, 0);
+ if (ret != -ENOENT) {
+ if (ret == 0)
+ ret = -EEXIST;
+ goto out;
+ }
+
+ ret = nilfs_btree_prepare_insert(btree, path, &level, key, ptr, &stats);
+ if (ret < 0)
+ goto out;
+ nilfs_btree_commit_insert(btree, path, level, key, ptr);
+ nilfs_inode_add_blocks(btree->b_inode, stats.bs_nblocks);
+
+ out:
+ nilfs_btree_free_path(path);
+ return ret;
+}
+
+static void nilfs_btree_do_delete(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node;
+ int ncblk;
+
+ if (level < nilfs_btree_height(btree) - 1) {
+ node = nilfs_btree_get_nonroot_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ nilfs_btree_node_delete(node, path[level].bp_index,
+ keyp, ptrp, ncblk);
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (path[level].bp_index == 0)
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(node, 0));
+ } else {
+ node = nilfs_btree_get_root(btree);
+ nilfs_btree_node_delete(node, path[level].bp_index,
+ keyp, ptrp,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ }
+}
+
+static void nilfs_btree_borrow_left(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *left;
+ int nchildren, lnchildren, n, ncblk;
+
+ nilfs_btree_do_delete(btree, path, level, keyp, ptrp);
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ left = nilfs_btree_get_sib_node(path, level);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ lnchildren = nilfs_btree_node_get_nchildren(left);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ n = (nchildren + lnchildren) / 2 - nchildren;
+
+ nilfs_btree_node_move_right(left, node, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(node, 0));
+
+ brelse(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index += n;
+}
+
+static void nilfs_btree_borrow_right(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *right;
+ int nchildren, rnchildren, n, ncblk;
+
+ nilfs_btree_do_delete(btree, path, level, keyp, ptrp);
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ right = nilfs_btree_get_sib_node(path, level);
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ rnchildren = nilfs_btree_node_get_nchildren(right);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ n = (nchildren + rnchildren) / 2 - nchildren;
+
+ nilfs_btree_node_move_left(node, right, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ path[level + 1].bp_index++;
+ nilfs_btree_promote_key(btree, path, level + 1,
+ nilfs_btree_node_get_key(right, 0));
+ path[level + 1].bp_index--;
+
+ brelse(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+}
+
+static void nilfs_btree_concat_left(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *left;
+ int n, ncblk;
+
+ nilfs_btree_do_delete(btree, path, level, keyp, ptrp);
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ left = nilfs_btree_get_sib_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ n = nilfs_btree_node_get_nchildren(node);
+
+ nilfs_btree_node_move_left(left, node, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_sib_bh))
+ mark_buffer_dirty(path[level].bp_sib_bh);
+
+ nilfs_btnode_delete(path[level].bp_bh);
+ path[level].bp_bh = path[level].bp_sib_bh;
+ path[level].bp_sib_bh = NULL;
+ path[level].bp_index += nilfs_btree_node_get_nchildren(left);
+}
+
+static void nilfs_btree_concat_right(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *node, *right;
+ int n, ncblk;
+
+ nilfs_btree_do_delete(btree, path, level, keyp, ptrp);
+
+ node = nilfs_btree_get_nonroot_node(path, level);
+ right = nilfs_btree_get_sib_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ n = nilfs_btree_node_get_nchildren(right);
+
+ nilfs_btree_node_move_left(node, right, n, ncblk, ncblk);
+
+ if (!buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+
+ nilfs_btnode_delete(path[level].bp_sib_bh);
+ path[level].bp_sib_bh = NULL;
+ path[level + 1].bp_index++;
+}
+
+static void nilfs_btree_shrink(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *root, *child;
+ int n, ncblk;
+
+ nilfs_btree_do_delete(btree, path, level, keyp, ptrp);
+
+ root = nilfs_btree_get_root(btree);
+ child = nilfs_btree_get_nonroot_node(path, level);
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ nilfs_btree_node_delete(root, 0, NULL, NULL,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ nilfs_btree_node_set_level(root, level);
+ n = nilfs_btree_node_get_nchildren(child);
+ nilfs_btree_node_move_left(root, child, n,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX, ncblk);
+
+ nilfs_btnode_delete(path[level].bp_bh);
+ path[level].bp_bh = NULL;
+}
+
+/**
+ * nilfs_btree_discard - discard the last node for the mapping transformation
+ * @btree: bmap struct of btree
+ * @path: array of nilfs_btree_path struct
+ * @level: level of the B-tree node being operated on
+ * @keyp: argument for passing a key (unused)
+ * @ptrp: argument for passing a pointer (unused)
+ */
+static void nilfs_btree_discard(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path, int level,
+ __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *root = nilfs_btree_get_root(btree);
+
+ nilfs_btree_node_delete(root, 0, NULL, NULL,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ nilfs_btree_node_set_level(root, level);
+
+ nilfs_btnode_delete(path[level].bp_bh);
+ path[level].bp_bh = NULL;
+}
+
+static void nilfs_btree_nop(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, __u64 *keyp, __u64 *ptrp)
+{
+}
+
+static int nilfs_btree_prepare_delete(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int *levelp,
+ struct nilfs_bmap_stats *stats,
+ struct inode *dat, bool deform)
+{
+ struct buffer_head *bh;
+ struct nilfs_btree_node *node, *parent, *sib;
+ __u64 sibptr;
+ int pindex, dindex, level, ncmin, ncmax, ncblk, ret;
+
+ ret = 0;
+ stats->bs_nblocks = 0;
+ ncmin = NILFS_BTREE_NODE_NCHILDREN_MIN(nilfs_btree_node_size(btree));
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN, dindex = path[level].bp_index;
+ level < nilfs_btree_height(btree) - 1;
+ level++) {
+ node = nilfs_btree_get_nonroot_node(path, level);
+ path[level].bp_oldreq.bpr_ptr =
+ nilfs_btree_node_get_ptr(node, dindex, ncblk);
+ ret = nilfs_bmap_prepare_end_ptr(btree,
+ &path[level].bp_oldreq, dat);
+ if (ret < 0)
+ goto err_out_child_node;
+
+ if (nilfs_btree_node_get_nchildren(node) > ncmin) {
+ path[level].bp_op = nilfs_btree_do_delete;
+ stats->bs_nblocks++;
+ goto out;
+ }
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ pindex = path[level + 1].bp_index;
+ dindex = pindex;
+
+ if (pindex > 0) {
+ /* left sibling */
+ sibptr = nilfs_btree_node_get_ptr(parent, pindex - 1,
+ ncmax);
+ ret = nilfs_btree_get_block(btree, sibptr, &bh);
+ if (ret < 0)
+ goto err_out_curr_node;
+ sib = (struct nilfs_btree_node *)bh->b_data;
+ if (nilfs_btree_node_get_nchildren(sib) > ncmin) {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_borrow_left;
+ stats->bs_nblocks++;
+ goto out;
+ } else {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_concat_left;
+ stats->bs_nblocks++;
+ /* continue; */
+ }
+ } else if (pindex <
+ nilfs_btree_node_get_nchildren(parent) - 1) {
+ /* right sibling */
+ sibptr = nilfs_btree_node_get_ptr(parent, pindex + 1,
+ ncmax);
+ ret = nilfs_btree_get_block(btree, sibptr, &bh);
+ if (ret < 0)
+ goto err_out_curr_node;
+ sib = (struct nilfs_btree_node *)bh->b_data;
+ if (nilfs_btree_node_get_nchildren(sib) > ncmin) {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_borrow_right;
+ stats->bs_nblocks++;
+ goto out;
+ } else {
+ path[level].bp_sib_bh = bh;
+ path[level].bp_op = nilfs_btree_concat_right;
+ stats->bs_nblocks++;
+ /*
+ * When merging right sibling node
+ * into the current node, pointer to
+ * the right sibling node must be
+ * terminated instead. The adjustment
+ * below is required for that.
+ */
+ dindex = pindex + 1;
+ /* continue; */
+ }
+ } else {
+ /* no siblings */
+ /* the only child of the root node */
+ WARN_ON(level != nilfs_btree_height(btree) - 2);
+ if (nilfs_btree_node_get_nchildren(node) - 1 <=
+ NILFS_BTREE_ROOT_NCHILDREN_MAX) {
+ path[level].bp_op = nilfs_btree_shrink;
+ } else if (deform) {
+ path[level].bp_op = nilfs_btree_discard;
+ } else {
+ path[level].bp_op = nilfs_btree_do_delete;
+ stats->bs_nblocks++;
+ goto out;
+ }
+ stats->bs_nblocks += 2;
+ level++;
+ path[level].bp_op = nilfs_btree_nop;
+ goto shrink_root_child;
+ }
+ }
+
+ /* child of the root node is deleted */
+ path[level].bp_op = nilfs_btree_do_delete;
+ stats->bs_nblocks++;
+
+shrink_root_child:
+ node = nilfs_btree_get_root(btree);
+ path[level].bp_oldreq.bpr_ptr =
+ nilfs_btree_node_get_ptr(node, dindex,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+
+ ret = nilfs_bmap_prepare_end_ptr(btree, &path[level].bp_oldreq, dat);
+ if (ret < 0)
+ goto err_out_child_node;
+
+ /* success */
+ out:
+ *levelp = level;
+ return ret;
+
+ /* error */
+ err_out_curr_node:
+ nilfs_bmap_abort_end_ptr(btree, &path[level].bp_oldreq, dat);
+ err_out_child_node:
+ for (level--; level >= NILFS_BTREE_LEVEL_NODE_MIN; level--) {
+ brelse(path[level].bp_sib_bh);
+ nilfs_bmap_abort_end_ptr(btree, &path[level].bp_oldreq, dat);
+ }
+ *levelp = level;
+ stats->bs_nblocks = 0;
+ return ret;
+}
+
+static void nilfs_btree_commit_delete(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int maxlevel, struct inode *dat)
+{
+ int level;
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN; level <= maxlevel; level++) {
+ nilfs_bmap_commit_end_ptr(btree, &path[level].bp_oldreq, dat);
+ path[level].bp_op(btree, path, level, NULL, NULL);
+ }
+
+ if (!nilfs_bmap_dirty(btree))
+ nilfs_bmap_set_dirty(btree);
+}
+
+static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key, bool deform)
+
+{
+ struct nilfs_btree_path *path;
+ struct nilfs_bmap_stats stats;
+ struct inode *dat;
+ int level, ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, key, NULL,
+ NILFS_BTREE_LEVEL_NODE_MIN, 0);
+ if (ret < 0)
+ goto out;
+
+
+ dat = NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree) : NULL;
+
+ ret = nilfs_btree_prepare_delete(btree, path, &level, &stats, dat,
+ deform);
+ if (ret < 0)
+ goto out;
+ nilfs_btree_commit_delete(btree, path, level, dat);
+ nilfs_inode_sub_blocks(btree->b_inode, stats.bs_nblocks);
+
+out:
+ nilfs_btree_free_path(path);
+ return ret;
+}
+
+static int nilfs_btree_seek_key(const struct nilfs_bmap *btree, __u64 start,
+ __u64 *keyp)
+{
+ struct nilfs_btree_path *path;
+ const int minlevel = NILFS_BTREE_LEVEL_NODE_MIN;
+ int ret;
+
+ path = nilfs_btree_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, start, NULL, minlevel, 0);
+ if (!ret)
+ *keyp = start;
+ else if (ret == -ENOENT)
+ ret = nilfs_btree_get_next_key(btree, path, minlevel, keyp);
+
+ nilfs_btree_free_path(path);
+ return ret;
+}
+
+static int nilfs_btree_last_key(const struct nilfs_bmap *btree, __u64 *keyp)
+{
+ struct nilfs_btree_path *path;
+ int ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup_last(btree, path, keyp, NULL);
+
+ nilfs_btree_free_path(path);
+
+ return ret;
+}
+
+static int nilfs_btree_check_delete(struct nilfs_bmap *btree, __u64 key)
+{
+ struct buffer_head *bh;
+ struct nilfs_btree_node *root, *node;
+ __u64 maxkey, nextmaxkey;
+ __u64 ptr;
+ int nchildren, ret;
+
+ root = nilfs_btree_get_root(btree);
+ nchildren = nilfs_btree_node_get_nchildren(root);
+ if (unlikely(nchildren == 0))
+ return 0;
+
+ switch (nilfs_btree_height(btree)) {
+ case 2:
+ bh = NULL;
+ node = root;
+ break;
+ case 3:
+ if (nchildren > 1)
+ return 0;
+ ptr = nilfs_btree_node_get_ptr(root, nchildren - 1,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ ret = nilfs_btree_get_block(btree, ptr, &bh);
+ if (ret < 0)
+ return ret;
+ node = (struct nilfs_btree_node *)bh->b_data;
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ break;
+ default:
+ return 0;
+ }
+
+ maxkey = nilfs_btree_node_get_key(node, nchildren - 1);
+ nextmaxkey = (nchildren > 1) ?
+ nilfs_btree_node_get_key(node, nchildren - 2) : 0;
+ brelse(bh);
+
+ return (maxkey == key) && (nextmaxkey < NILFS_BMAP_LARGE_LOW);
+}
+
+static int nilfs_btree_gather_data(struct nilfs_bmap *btree,
+ __u64 *keys, __u64 *ptrs, int nitems)
+{
+ struct buffer_head *bh;
+ struct nilfs_btree_node *node, *root;
+ __le64 *dkeys;
+ __le64 *dptrs;
+ __u64 ptr;
+ int nchildren, ncmax, i, ret;
+
+ root = nilfs_btree_get_root(btree);
+ switch (nilfs_btree_height(btree)) {
+ case 2:
+ bh = NULL;
+ node = root;
+ ncmax = NILFS_BTREE_ROOT_NCHILDREN_MAX;
+ break;
+ case 3:
+ nchildren = nilfs_btree_node_get_nchildren(root);
+ WARN_ON(nchildren > 1);
+ ptr = nilfs_btree_node_get_ptr(root, nchildren - 1,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ ret = nilfs_btree_get_block(btree, ptr, &bh);
+ if (ret < 0)
+ return ret;
+ node = (struct nilfs_btree_node *)bh->b_data;
+ ncmax = nilfs_btree_nchildren_per_block(btree);
+ break;
+ default:
+ node = NULL;
+ return -EINVAL;
+ }
+
+ nchildren = nilfs_btree_node_get_nchildren(node);
+ if (nchildren < nitems)
+ nitems = nchildren;
+ dkeys = nilfs_btree_node_dkeys(node);
+ dptrs = nilfs_btree_node_dptrs(node, ncmax);
+ for (i = 0; i < nitems; i++) {
+ keys[i] = le64_to_cpu(dkeys[i]);
+ ptrs[i] = le64_to_cpu(dptrs[i]);
+ }
+
+ brelse(bh);
+
+ return nitems;
+}
+
+static int
+nilfs_btree_prepare_convert_and_insert(struct nilfs_bmap *btree, __u64 key,
+ union nilfs_bmap_ptr_req *dreq,
+ union nilfs_bmap_ptr_req *nreq,
+ struct buffer_head **bhp,
+ struct nilfs_bmap_stats *stats)
+{
+ struct buffer_head *bh;
+ struct inode *dat = NULL;
+ int ret;
+
+ stats->bs_nblocks = 0;
+
+ /* for data */
+ /* cannot find near ptr */
+ if (NILFS_BMAP_USE_VBN(btree)) {
+ dreq->bpr_ptr = nilfs_btree_find_target_v(btree, NULL, key);
+ dat = nilfs_bmap_get_dat(btree);
+ }
+
+ ret = nilfs_attach_btree_node_cache(&NILFS_BMAP_I(btree)->vfs_inode);
+ if (ret < 0)
+ return ret;
+
+ ret = nilfs_bmap_prepare_alloc_ptr(btree, dreq, dat);
+ if (ret < 0)
+ return ret;
+
+ *bhp = NULL;
+ stats->bs_nblocks++;
+ if (nreq != NULL) {
+ nreq->bpr_ptr = dreq->bpr_ptr + 1;
+ ret = nilfs_bmap_prepare_alloc_ptr(btree, nreq, dat);
+ if (ret < 0)
+ goto err_out_dreq;
+
+ ret = nilfs_btree_get_new_block(btree, nreq->bpr_ptr, &bh);
+ if (ret < 0)
+ goto err_out_nreq;
+
+ *bhp = bh;
+ stats->bs_nblocks++;
+ }
+
+ /* success */
+ return 0;
+
+ /* error */
+ err_out_nreq:
+ nilfs_bmap_abort_alloc_ptr(btree, nreq, dat);
+ err_out_dreq:
+ nilfs_bmap_abort_alloc_ptr(btree, dreq, dat);
+ stats->bs_nblocks = 0;
+ return ret;
+
+}
+
+static void
+nilfs_btree_commit_convert_and_insert(struct nilfs_bmap *btree,
+ __u64 key, __u64 ptr,
+ const __u64 *keys, const __u64 *ptrs,
+ int n,
+ union nilfs_bmap_ptr_req *dreq,
+ union nilfs_bmap_ptr_req *nreq,
+ struct buffer_head *bh)
+{
+ struct nilfs_btree_node *node;
+ struct inode *dat;
+ __u64 tmpptr;
+ int ncblk;
+
+ /* free resources */
+ if (btree->b_ops->bop_clear != NULL)
+ btree->b_ops->bop_clear(btree);
+
+ /* ptr must be a pointer to a buffer head. */
+ set_buffer_nilfs_volatile((struct buffer_head *)((unsigned long)ptr));
+
+ /* convert and insert */
+ dat = NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree) : NULL;
+ __nilfs_btree_init(btree);
+ if (nreq != NULL) {
+ nilfs_bmap_commit_alloc_ptr(btree, dreq, dat);
+ nilfs_bmap_commit_alloc_ptr(btree, nreq, dat);
+
+ /* create child node at level 1 */
+ node = (struct nilfs_btree_node *)bh->b_data;
+ ncblk = nilfs_btree_nchildren_per_block(btree);
+ nilfs_btree_node_init(node, 0, 1, n, ncblk, keys, ptrs);
+ nilfs_btree_node_insert(node, n, key, dreq->bpr_ptr, ncblk);
+ if (!buffer_dirty(bh))
+ mark_buffer_dirty(bh);
+ if (!nilfs_bmap_dirty(btree))
+ nilfs_bmap_set_dirty(btree);
+
+ brelse(bh);
+
+ /* create root node at level 2 */
+ node = nilfs_btree_get_root(btree);
+ tmpptr = nreq->bpr_ptr;
+ nilfs_btree_node_init(node, NILFS_BTREE_NODE_ROOT, 2, 1,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX,
+ &keys[0], &tmpptr);
+ } else {
+ nilfs_bmap_commit_alloc_ptr(btree, dreq, dat);
+
+ /* create root node at level 1 */
+ node = nilfs_btree_get_root(btree);
+ nilfs_btree_node_init(node, NILFS_BTREE_NODE_ROOT, 1, n,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX,
+ keys, ptrs);
+ nilfs_btree_node_insert(node, n, key, dreq->bpr_ptr,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ if (!nilfs_bmap_dirty(btree))
+ nilfs_bmap_set_dirty(btree);
+ }
+
+ if (NILFS_BMAP_USE_VBN(btree))
+ nilfs_bmap_set_target_v(btree, key, dreq->bpr_ptr);
+}
+
+/**
+ * nilfs_btree_convert_and_insert - Convert and insert entries into a B-tree
+ * @btree: NILFS B-tree structure
+ * @key: Key of the new entry to be inserted
+ * @ptr: Pointer (block number) associated with the key to be inserted
+ * @keys: Array of keys to be inserted in addition to @key
+ * @ptrs: Array of pointers associated with @keys
+ * @n: Number of keys and pointers in @keys and @ptrs
+ *
+ * This function is used to insert a new entry specified by @key and @ptr,
+ * along with additional entries specified by @keys and @ptrs arrays, into a
+ * NILFS B-tree.
+ * It prepares the necessary changes by allocating the required blocks and any
+ * necessary intermediate nodes. It converts configurations from other forms of
+ * block mapping (the one that currently exists is direct mapping) to a B-tree.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int nilfs_btree_convert_and_insert(struct nilfs_bmap *btree,
+ __u64 key, __u64 ptr,
+ const __u64 *keys, const __u64 *ptrs, int n)
+{
+ struct buffer_head *bh = NULL;
+ union nilfs_bmap_ptr_req dreq, nreq, *di, *ni;
+ struct nilfs_bmap_stats stats;
+ int ret;
+
+ if (n + 1 <= NILFS_BTREE_ROOT_NCHILDREN_MAX) {
+ di = &dreq;
+ ni = NULL;
+ } else if ((n + 1) <= NILFS_BTREE_NODE_NCHILDREN_MAX(
+ nilfs_btree_node_size(btree))) {
+ di = &dreq;
+ ni = &nreq;
+ } else {
+ di = NULL;
+ ni = NULL;
+ BUG();
+ }
+
+ ret = nilfs_btree_prepare_convert_and_insert(btree, key, di, ni, &bh,
+ &stats);
+ if (ret < 0)
+ return ret;
+ nilfs_btree_commit_convert_and_insert(btree, key, ptr, keys, ptrs, n,
+ di, ni, bh);
+ nilfs_inode_add_blocks(btree->b_inode, stats.bs_nblocks);
+ return 0;
+}
+
+static int nilfs_btree_propagate_p(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level,
+ struct buffer_head *bh)
+{
+ while ((++level < nilfs_btree_height(btree) - 1) &&
+ !buffer_dirty(path[level].bp_bh))
+ mark_buffer_dirty(path[level].bp_bh);
+
+ return 0;
+}
+
+static int nilfs_btree_prepare_update_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, struct inode *dat)
+{
+ struct nilfs_btree_node *parent;
+ int ncmax, ret;
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ path[level].bp_oldreq.bpr_ptr =
+ nilfs_btree_node_get_ptr(parent, path[level + 1].bp_index,
+ ncmax);
+ path[level].bp_newreq.bpr_ptr = path[level].bp_oldreq.bpr_ptr + 1;
+ ret = nilfs_dat_prepare_update(dat, &path[level].bp_oldreq.bpr_req,
+ &path[level].bp_newreq.bpr_req);
+ if (ret < 0)
+ return ret;
+
+ if (buffer_nilfs_node(path[level].bp_bh)) {
+ path[level].bp_ctxt.oldkey = path[level].bp_oldreq.bpr_ptr;
+ path[level].bp_ctxt.newkey = path[level].bp_newreq.bpr_ptr;
+ path[level].bp_ctxt.bh = path[level].bp_bh;
+ ret = nilfs_btnode_prepare_change_key(
+ NILFS_BMAP_I(btree)->i_assoc_inode->i_mapping,
+ &path[level].bp_ctxt);
+ if (ret < 0) {
+ nilfs_dat_abort_update(dat,
+ &path[level].bp_oldreq.bpr_req,
+ &path[level].bp_newreq.bpr_req);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void nilfs_btree_commit_update_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, struct inode *dat)
+{
+ struct nilfs_btree_node *parent;
+ int ncmax;
+
+ nilfs_dat_commit_update(dat, &path[level].bp_oldreq.bpr_req,
+ &path[level].bp_newreq.bpr_req,
+ btree->b_ptr_type == NILFS_BMAP_PTR_VS);
+
+ if (buffer_nilfs_node(path[level].bp_bh)) {
+ nilfs_btnode_commit_change_key(
+ NILFS_BMAP_I(btree)->i_assoc_inode->i_mapping,
+ &path[level].bp_ctxt);
+ path[level].bp_bh = path[level].bp_ctxt.bh;
+ }
+ set_buffer_nilfs_volatile(path[level].bp_bh);
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ nilfs_btree_node_set_ptr(parent, path[level + 1].bp_index,
+ path[level].bp_newreq.bpr_ptr, ncmax);
+}
+
+static void nilfs_btree_abort_update_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, struct inode *dat)
+{
+ nilfs_dat_abort_update(dat, &path[level].bp_oldreq.bpr_req,
+ &path[level].bp_newreq.bpr_req);
+ if (buffer_nilfs_node(path[level].bp_bh))
+ nilfs_btnode_abort_change_key(
+ NILFS_BMAP_I(btree)->i_assoc_inode->i_mapping,
+ &path[level].bp_ctxt);
+}
+
+static int nilfs_btree_prepare_propagate_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int minlevel, int *maxlevelp,
+ struct inode *dat)
+{
+ int level, ret;
+
+ level = minlevel;
+ if (!buffer_nilfs_volatile(path[level].bp_bh)) {
+ ret = nilfs_btree_prepare_update_v(btree, path, level, dat);
+ if (ret < 0)
+ return ret;
+ }
+ while ((++level < nilfs_btree_height(btree) - 1) &&
+ !buffer_dirty(path[level].bp_bh)) {
+
+ WARN_ON(buffer_nilfs_volatile(path[level].bp_bh));
+ ret = nilfs_btree_prepare_update_v(btree, path, level, dat);
+ if (ret < 0)
+ goto out;
+ }
+
+ /* success */
+ *maxlevelp = level - 1;
+ return 0;
+
+ /* error */
+ out:
+ while (--level > minlevel)
+ nilfs_btree_abort_update_v(btree, path, level, dat);
+ if (!buffer_nilfs_volatile(path[level].bp_bh))
+ nilfs_btree_abort_update_v(btree, path, level, dat);
+ return ret;
+}
+
+static void nilfs_btree_commit_propagate_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int minlevel, int maxlevel,
+ struct buffer_head *bh,
+ struct inode *dat)
+{
+ int level;
+
+ if (!buffer_nilfs_volatile(path[minlevel].bp_bh))
+ nilfs_btree_commit_update_v(btree, path, minlevel, dat);
+
+ for (level = minlevel + 1; level <= maxlevel; level++)
+ nilfs_btree_commit_update_v(btree, path, level, dat);
+}
+
+static int nilfs_btree_propagate_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level, struct buffer_head *bh)
+{
+ int maxlevel = 0, ret;
+ struct nilfs_btree_node *parent;
+ struct inode *dat = nilfs_bmap_get_dat(btree);
+ __u64 ptr;
+ int ncmax;
+
+ get_bh(bh);
+ path[level].bp_bh = bh;
+ ret = nilfs_btree_prepare_propagate_v(btree, path, level, &maxlevel,
+ dat);
+ if (ret < 0)
+ goto out;
+
+ if (buffer_nilfs_volatile(path[level].bp_bh)) {
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ ptr = nilfs_btree_node_get_ptr(parent,
+ path[level + 1].bp_index,
+ ncmax);
+ ret = nilfs_dat_mark_dirty(dat, ptr);
+ if (ret < 0)
+ goto out;
+ }
+
+ nilfs_btree_commit_propagate_v(btree, path, level, maxlevel, bh, dat);
+
+ out:
+ brelse(path[level].bp_bh);
+ path[level].bp_bh = NULL;
+ return ret;
+}
+
+static int nilfs_btree_propagate(struct nilfs_bmap *btree,
+ struct buffer_head *bh)
+{
+ struct nilfs_btree_path *path;
+ struct nilfs_btree_node *node;
+ __u64 key;
+ int level, ret;
+
+ WARN_ON(!buffer_dirty(bh));
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ if (buffer_nilfs_node(bh)) {
+ node = (struct nilfs_btree_node *)bh->b_data;
+ key = nilfs_btree_node_get_key(node, 0);
+ level = nilfs_btree_node_get_level(node);
+ } else {
+ key = nilfs_bmap_data_get_key(btree, bh);
+ level = NILFS_BTREE_LEVEL_DATA;
+ }
+
+ ret = nilfs_btree_do_lookup(btree, path, key, NULL, level + 1, 0);
+ if (ret < 0) {
+ if (unlikely(ret == -ENOENT)) {
+ nilfs_crit(btree->b_inode->i_sb,
+ "writing node/leaf block does not appear in b-tree (ino=%llu) at key=%llu, level=%d",
+ btree->b_inode->i_ino,
+ (unsigned long long)key, level);
+ ret = -EINVAL;
+ }
+ goto out;
+ }
+
+ ret = NILFS_BMAP_USE_VBN(btree) ?
+ nilfs_btree_propagate_v(btree, path, level, bh) :
+ nilfs_btree_propagate_p(btree, path, level, bh);
+
+ out:
+ nilfs_btree_free_path(path);
+
+ return ret;
+}
+
+static int nilfs_btree_propagate_gc(struct nilfs_bmap *btree,
+ struct buffer_head *bh)
+{
+ return nilfs_dat_mark_dirty(nilfs_bmap_get_dat(btree), bh->b_blocknr);
+}
+
+static void nilfs_btree_add_dirty_buffer(struct nilfs_bmap *btree,
+ struct list_head *lists,
+ struct buffer_head *bh)
+{
+ struct list_head *head;
+ struct buffer_head *cbh;
+ struct nilfs_btree_node *node, *cnode;
+ __u64 key, ckey;
+ int level;
+
+ get_bh(bh);
+ node = (struct nilfs_btree_node *)bh->b_data;
+ key = nilfs_btree_node_get_key(node, 0);
+ level = nilfs_btree_node_get_level(node);
+ if (level < NILFS_BTREE_LEVEL_NODE_MIN ||
+ level >= NILFS_BTREE_LEVEL_MAX) {
+ dump_stack();
+ nilfs_warn(btree->b_inode->i_sb,
+ "invalid btree level: %d (key=%llu, ino=%llu, blocknr=%llu)",
+ level, (unsigned long long)key,
+ btree->b_inode->i_ino,
+ (unsigned long long)bh->b_blocknr);
+ return;
+ }
+
+ list_for_each(head, &lists[level]) {
+ cbh = list_entry(head, struct buffer_head, b_assoc_buffers);
+ cnode = (struct nilfs_btree_node *)cbh->b_data;
+ ckey = nilfs_btree_node_get_key(cnode, 0);
+ if (key < ckey)
+ break;
+ }
+ list_add_tail(&bh->b_assoc_buffers, head);
+}
+
+static void nilfs_btree_lookup_dirty_buffers(struct nilfs_bmap *btree,
+ struct list_head *listp)
+{
+ struct inode *btnc_inode = NILFS_BMAP_I(btree)->i_assoc_inode;
+ struct address_space *btcache = btnc_inode->i_mapping;
+ struct list_head lists[NILFS_BTREE_LEVEL_MAX];
+ struct folio_batch fbatch;
+ struct buffer_head *bh, *head;
+ pgoff_t index = 0;
+ int level, i;
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN;
+ level < NILFS_BTREE_LEVEL_MAX;
+ level++)
+ INIT_LIST_HEAD(&lists[level]);
+
+ folio_batch_init(&fbatch);
+
+ while (filemap_get_folios_tag(btcache, &index, (pgoff_t)-1,
+ PAGECACHE_TAG_DIRTY, &fbatch)) {
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ bh = head = folio_buffers(fbatch.folios[i]);
+ do {
+ if (buffer_dirty(bh))
+ nilfs_btree_add_dirty_buffer(btree,
+ lists, bh);
+ } while ((bh = bh->b_this_page) != head);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+ }
+
+ for (level = NILFS_BTREE_LEVEL_NODE_MIN;
+ level < NILFS_BTREE_LEVEL_MAX;
+ level++)
+ list_splice_tail(&lists[level], listp);
+}
+
+static int nilfs_btree_assign_p(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_btree_node *parent;
+ __u64 key;
+ __u64 ptr;
+ int ncmax, ret;
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ ptr = nilfs_btree_node_get_ptr(parent, path[level + 1].bp_index,
+ ncmax);
+ if (buffer_nilfs_node(*bh)) {
+ path[level].bp_ctxt.oldkey = ptr;
+ path[level].bp_ctxt.newkey = blocknr;
+ path[level].bp_ctxt.bh = *bh;
+ ret = nilfs_btnode_prepare_change_key(
+ NILFS_BMAP_I(btree)->i_assoc_inode->i_mapping,
+ &path[level].bp_ctxt);
+ if (ret < 0)
+ return ret;
+ nilfs_btnode_commit_change_key(
+ NILFS_BMAP_I(btree)->i_assoc_inode->i_mapping,
+ &path[level].bp_ctxt);
+ *bh = path[level].bp_ctxt.bh;
+ }
+
+ nilfs_btree_node_set_ptr(parent, path[level + 1].bp_index, blocknr,
+ ncmax);
+
+ key = nilfs_btree_node_get_key(parent, path[level + 1].bp_index);
+ /* on-disk format */
+ binfo->bi_dat.bi_blkoff = cpu_to_le64(key);
+ binfo->bi_dat.bi_level = level;
+ memset(binfo->bi_dat.bi_pad, 0, sizeof(binfo->bi_dat.bi_pad));
+
+ return 0;
+}
+
+static int nilfs_btree_assign_v(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path,
+ int level,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_btree_node *parent;
+ struct inode *dat = nilfs_bmap_get_dat(btree);
+ __u64 key;
+ __u64 ptr;
+ union nilfs_bmap_ptr_req req;
+ int ncmax, ret;
+
+ parent = nilfs_btree_get_node(btree, path, level + 1, &ncmax);
+ ptr = nilfs_btree_node_get_ptr(parent, path[level + 1].bp_index,
+ ncmax);
+ req.bpr_ptr = ptr;
+ ret = nilfs_dat_prepare_start(dat, &req.bpr_req);
+ if (ret < 0)
+ return ret;
+ nilfs_dat_commit_start(dat, &req.bpr_req, blocknr);
+
+ key = nilfs_btree_node_get_key(parent, path[level + 1].bp_index);
+ /* on-disk format */
+ binfo->bi_v.bi_vblocknr = cpu_to_le64(ptr);
+ binfo->bi_v.bi_blkoff = cpu_to_le64(key);
+
+ return 0;
+}
+
+static int nilfs_btree_assign(struct nilfs_bmap *btree,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_btree_path *path;
+ struct nilfs_btree_node *node;
+ __u64 key;
+ int level, ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ if (buffer_nilfs_node(*bh)) {
+ node = (struct nilfs_btree_node *)(*bh)->b_data;
+ key = nilfs_btree_node_get_key(node, 0);
+ level = nilfs_btree_node_get_level(node);
+ } else {
+ key = nilfs_bmap_data_get_key(btree, *bh);
+ level = NILFS_BTREE_LEVEL_DATA;
+ }
+
+ ret = nilfs_btree_do_lookup(btree, path, key, NULL, level + 1, 0);
+ if (ret < 0) {
+ WARN_ON(ret == -ENOENT);
+ goto out;
+ }
+
+ ret = NILFS_BMAP_USE_VBN(btree) ?
+ nilfs_btree_assign_v(btree, path, level, bh, blocknr, binfo) :
+ nilfs_btree_assign_p(btree, path, level, bh, blocknr, binfo);
+
+ out:
+ nilfs_btree_free_path(path);
+
+ return ret;
+}
+
+static int nilfs_btree_assign_gc(struct nilfs_bmap *btree,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_btree_node *node;
+ __u64 key;
+ int ret;
+
+ ret = nilfs_dat_move(nilfs_bmap_get_dat(btree), (*bh)->b_blocknr,
+ blocknr);
+ if (ret < 0)
+ return ret;
+
+ if (buffer_nilfs_node(*bh)) {
+ node = (struct nilfs_btree_node *)(*bh)->b_data;
+ key = nilfs_btree_node_get_key(node, 0);
+ } else
+ key = nilfs_bmap_data_get_key(btree, *bh);
+
+ /* on-disk format */
+ binfo->bi_v.bi_vblocknr = cpu_to_le64((*bh)->b_blocknr);
+ binfo->bi_v.bi_blkoff = cpu_to_le64(key);
+
+ return 0;
+}
+
+static int nilfs_btree_mark(struct nilfs_bmap *btree, __u64 key, int level)
+{
+ struct buffer_head *bh;
+ struct nilfs_btree_path *path;
+ __u64 ptr;
+ int ret;
+
+ path = nilfs_btree_alloc_path();
+ if (path == NULL)
+ return -ENOMEM;
+
+ ret = nilfs_btree_do_lookup(btree, path, key, &ptr, level + 1, 0);
+ if (ret < 0) {
+ WARN_ON(ret == -ENOENT);
+ goto out;
+ }
+ ret = nilfs_btree_get_block(btree, ptr, &bh);
+ if (ret < 0) {
+ WARN_ON(ret == -ENOENT);
+ goto out;
+ }
+
+ if (!buffer_dirty(bh))
+ mark_buffer_dirty(bh);
+ brelse(bh);
+ if (!nilfs_bmap_dirty(btree))
+ nilfs_bmap_set_dirty(btree);
+
+ out:
+ nilfs_btree_free_path(path);
+ return ret;
+}
+
+static const struct nilfs_bmap_operations nilfs_btree_ops = {
+ .bop_lookup = nilfs_btree_lookup,
+ .bop_lookup_contig = nilfs_btree_lookup_contig,
+ .bop_insert = nilfs_btree_insert,
+ .bop_delete = nilfs_btree_delete,
+ .bop_clear = NULL,
+
+ .bop_propagate = nilfs_btree_propagate,
+
+ .bop_lookup_dirty_buffers = nilfs_btree_lookup_dirty_buffers,
+
+ .bop_assign = nilfs_btree_assign,
+ .bop_mark = nilfs_btree_mark,
+
+ .bop_seek_key = nilfs_btree_seek_key,
+ .bop_last_key = nilfs_btree_last_key,
+
+ .bop_check_insert = NULL,
+ .bop_check_delete = nilfs_btree_check_delete,
+ .bop_gather_data = nilfs_btree_gather_data,
+};
+
+static const struct nilfs_bmap_operations nilfs_btree_ops_gc = {
+ .bop_lookup = NULL,
+ .bop_lookup_contig = NULL,
+ .bop_insert = NULL,
+ .bop_delete = NULL,
+ .bop_clear = NULL,
+
+ .bop_propagate = nilfs_btree_propagate_gc,
+
+ .bop_lookup_dirty_buffers = nilfs_btree_lookup_dirty_buffers,
+
+ .bop_assign = nilfs_btree_assign_gc,
+ .bop_mark = NULL,
+
+ .bop_seek_key = NULL,
+ .bop_last_key = NULL,
+
+ .bop_check_insert = NULL,
+ .bop_check_delete = NULL,
+ .bop_gather_data = NULL,
+};
+
+static void __nilfs_btree_init(struct nilfs_bmap *bmap)
+{
+ bmap->b_ops = &nilfs_btree_ops;
+ bmap->b_nchildren_per_block =
+ NILFS_BTREE_NODE_NCHILDREN_MAX(nilfs_btree_node_size(bmap));
+}
+
+int nilfs_btree_init(struct nilfs_bmap *bmap)
+{
+ int ret = 0;
+
+ __nilfs_btree_init(bmap);
+
+ if (nilfs_btree_root_broken(nilfs_btree_get_root(bmap), bmap->b_inode))
+ ret = -EIO;
+ else
+ ret = nilfs_attach_btree_node_cache(
+ &NILFS_BMAP_I(bmap)->vfs_inode);
+
+ return ret;
+}
+
+void nilfs_btree_init_gc(struct nilfs_bmap *bmap)
+{
+ bmap->b_ops = &nilfs_btree_ops_gc;
+ bmap->b_nchildren_per_block =
+ NILFS_BTREE_NODE_NCHILDREN_MAX(nilfs_btree_node_size(bmap));
+}
diff --git a/fs/nilfs2/btree.h b/fs/nilfs2/btree.h
new file mode 100644
index 000000000..2a220f716
--- /dev/null
+++ b/fs/nilfs2/btree.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS B-tree.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_BTREE_H
+#define _NILFS_BTREE_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/list.h>
+#include <linux/nilfs2_ondisk.h> /* nilfs_btree_node */
+#include "btnode.h"
+#include "bmap.h"
+
+/**
+ * struct nilfs_btree_path - A path on which B-tree operations are executed
+ * @bp_bh: buffer head of node block
+ * @bp_sib_bh: buffer head of sibling node block
+ * @bp_index: index of child node
+ * @bp_oldreq: ptr end request for old ptr
+ * @bp_newreq: ptr alloc request for new ptr
+ * @bp_ctxt: context information for changing the key of a b-tree node block
+ * @bp_op: rebalance operation
+ */
+struct nilfs_btree_path {
+ struct buffer_head *bp_bh;
+ struct buffer_head *bp_sib_bh;
+ int bp_index;
+ union nilfs_bmap_ptr_req bp_oldreq;
+ union nilfs_bmap_ptr_req bp_newreq;
+ struct nilfs_btnode_chkey_ctxt bp_ctxt;
+ void (*bp_op)(struct nilfs_bmap *, struct nilfs_btree_path *,
+ int, __u64 *, __u64 *);
+};
+
+#define NILFS_BTREE_ROOT_SIZE NILFS_BMAP_SIZE
+#define NILFS_BTREE_ROOT_NCHILDREN_MAX \
+ ((NILFS_BTREE_ROOT_SIZE - sizeof(struct nilfs_btree_node)) / \
+ (sizeof(__le64 /* dkey */) + sizeof(__le64 /* dptr */)))
+#define NILFS_BTREE_ROOT_NCHILDREN_MIN 0
+#define NILFS_BTREE_NODE_EXTRA_PAD_SIZE (sizeof(__le64))
+#define NILFS_BTREE_NODE_NCHILDREN_MAX(nodesize) \
+ (((nodesize) - sizeof(struct nilfs_btree_node) - \
+ NILFS_BTREE_NODE_EXTRA_PAD_SIZE) / \
+ (sizeof(__le64 /* dkey */) + sizeof(__le64 /* dptr */)))
+#define NILFS_BTREE_NODE_NCHILDREN_MIN(nodesize) \
+ ((NILFS_BTREE_NODE_NCHILDREN_MAX(nodesize) - 1) / 2 + 1)
+#define NILFS_BTREE_KEY_MIN ((__u64)0)
+#define NILFS_BTREE_KEY_MAX (~(__u64)0)
+
+extern struct kmem_cache *nilfs_btree_path_cache;
+
+int nilfs_btree_init(struct nilfs_bmap *);
+int nilfs_btree_convert_and_insert(struct nilfs_bmap *, __u64, __u64,
+ const __u64 *, const __u64 *, int);
+void nilfs_btree_init_gc(struct nilfs_bmap *);
+
+int nilfs_btree_broken_node_block(struct buffer_head *bh);
+
+#endif /* _NILFS_BTREE_H */
diff --git a/fs/nilfs2/cpfile.c b/fs/nilfs2/cpfile.c
new file mode 100644
index 000000000..d3349fa58
--- /dev/null
+++ b/fs/nilfs2/cpfile.c
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS checkpoint file.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/buffer_head.h>
+#include <linux/errno.h>
+#include "mdt.h"
+#include "cpfile.h"
+
+
+static inline unsigned long
+nilfs_cpfile_checkpoints_per_block(const struct inode *cpfile)
+{
+ return NILFS_MDT(cpfile)->mi_entries_per_block;
+}
+
+/* block number from the beginning of the file */
+static unsigned long
+nilfs_cpfile_get_blkoff(const struct inode *cpfile, __u64 cno)
+{
+ __u64 tcno = cno + NILFS_MDT(cpfile)->mi_first_entry_offset - 1;
+
+ tcno = div64_ul(tcno, nilfs_cpfile_checkpoints_per_block(cpfile));
+ return (unsigned long)tcno;
+}
+
+/* offset in block */
+static unsigned long
+nilfs_cpfile_get_offset(const struct inode *cpfile, __u64 cno)
+{
+ __u64 tcno = cno + NILFS_MDT(cpfile)->mi_first_entry_offset - 1;
+
+ return do_div(tcno, nilfs_cpfile_checkpoints_per_block(cpfile));
+}
+
+static __u64 nilfs_cpfile_first_checkpoint_in_block(const struct inode *cpfile,
+ unsigned long blkoff)
+{
+ return (__u64)nilfs_cpfile_checkpoints_per_block(cpfile) * blkoff
+ + 1 - NILFS_MDT(cpfile)->mi_first_entry_offset;
+}
+
+static unsigned long
+nilfs_cpfile_checkpoints_in_block(const struct inode *cpfile,
+ __u64 curr,
+ __u64 max)
+{
+ return min_t(__u64,
+ nilfs_cpfile_checkpoints_per_block(cpfile) -
+ nilfs_cpfile_get_offset(cpfile, curr),
+ max - curr);
+}
+
+static inline int nilfs_cpfile_is_in_first(const struct inode *cpfile,
+ __u64 cno)
+{
+ return nilfs_cpfile_get_blkoff(cpfile, cno) == 0;
+}
+
+static unsigned int
+nilfs_cpfile_block_add_valid_checkpoints(const struct inode *cpfile,
+ struct buffer_head *bh,
+ unsigned int n)
+{
+ struct nilfs_checkpoint *cp;
+ unsigned int count;
+
+ cp = kmap_local_folio(bh->b_folio,
+ offset_in_folio(bh->b_folio, bh->b_data));
+ count = le32_to_cpu(cp->cp_checkpoints_count) + n;
+ cp->cp_checkpoints_count = cpu_to_le32(count);
+ kunmap_local(cp);
+ return count;
+}
+
+static int
+nilfs_cpfile_block_sub_valid_checkpoints(const struct inode *cpfile,
+ struct buffer_head *bh,
+ unsigned int n)
+{
+ struct nilfs_checkpoint *cp;
+ unsigned int checkpoints_count;
+ int count;
+
+ cp = kmap_local_folio(bh->b_folio,
+ offset_in_folio(bh->b_folio, bh->b_data));
+ checkpoints_count = le32_to_cpu(cp->cp_checkpoints_count);
+ if (unlikely(checkpoints_count < n)) {
+ nilfs_error(cpfile->i_sb,
+ "deleted checkpoints count %u exceeds block count %u",
+ n, checkpoints_count);
+ kunmap_local(cp);
+ return -EIO;
+ }
+ count = checkpoints_count - n;
+ cp->cp_checkpoints_count = cpu_to_le32(count);
+ kunmap_local(cp);
+ return count;
+}
+
+static void nilfs_cpfile_block_init(struct inode *cpfile,
+ struct buffer_head *bh,
+ void *from)
+{
+ struct nilfs_checkpoint *cp = from;
+ size_t cpsz = NILFS_MDT(cpfile)->mi_entry_size;
+ int n = nilfs_cpfile_checkpoints_per_block(cpfile);
+
+ while (n-- > 0) {
+ nilfs_checkpoint_set_invalid(cp);
+ cp = (void *)cp + cpsz;
+ }
+}
+
+/**
+ * nilfs_cpfile_checkpoint_offset - calculate the byte offset of a checkpoint
+ * entry in the folio containing it
+ * @cpfile: checkpoint file inode
+ * @cno: checkpoint number
+ * @bh: buffer head of block containing checkpoint indexed by @cno
+ *
+ * Return: Byte offset in the folio of the checkpoint specified by @cno.
+ */
+static size_t nilfs_cpfile_checkpoint_offset(const struct inode *cpfile,
+ __u64 cno,
+ struct buffer_head *bh)
+{
+ return offset_in_folio(bh->b_folio, bh->b_data) +
+ nilfs_cpfile_get_offset(cpfile, cno) *
+ NILFS_MDT(cpfile)->mi_entry_size;
+}
+
+/**
+ * nilfs_cpfile_cp_snapshot_list_offset - calculate the byte offset of a
+ * checkpoint snapshot list in the folio
+ * containing it
+ * @cpfile: checkpoint file inode
+ * @cno: checkpoint number
+ * @bh: buffer head of block containing checkpoint indexed by @cno
+ *
+ * Return: Byte offset in the folio of the checkpoint snapshot list specified
+ * by @cno.
+ */
+static size_t nilfs_cpfile_cp_snapshot_list_offset(const struct inode *cpfile,
+ __u64 cno,
+ struct buffer_head *bh)
+{
+ return nilfs_cpfile_checkpoint_offset(cpfile, cno, bh) +
+ offsetof(struct nilfs_checkpoint, cp_snapshot_list);
+}
+
+/**
+ * nilfs_cpfile_ch_snapshot_list_offset - calculate the byte offset of the
+ * snapshot list in the header
+ *
+ * Return: Byte offset in the folio of the checkpoint snapshot list
+ */
+static size_t nilfs_cpfile_ch_snapshot_list_offset(void)
+{
+ return offsetof(struct nilfs_cpfile_header, ch_snapshot_list);
+}
+
+static int nilfs_cpfile_get_header_block(struct inode *cpfile,
+ struct buffer_head **bhp)
+{
+ int err = nilfs_mdt_get_block(cpfile, 0, 0, NULL, bhp);
+
+ if (unlikely(err == -ENOENT)) {
+ nilfs_error(cpfile->i_sb,
+ "missing header block in checkpoint metadata");
+ err = -EIO;
+ }
+ return err;
+}
+
+static inline int nilfs_cpfile_get_checkpoint_block(struct inode *cpfile,
+ __u64 cno,
+ int create,
+ struct buffer_head **bhp)
+{
+ return nilfs_mdt_get_block(cpfile,
+ nilfs_cpfile_get_blkoff(cpfile, cno),
+ create, nilfs_cpfile_block_init, bhp);
+}
+
+/**
+ * nilfs_cpfile_find_checkpoint_block - find and get a buffer on cpfile
+ * @cpfile: inode of cpfile
+ * @start_cno: start checkpoint number (inclusive)
+ * @end_cno: end checkpoint number (inclusive)
+ * @cnop: place to store the next checkpoint number
+ * @bhp: place to store a pointer to buffer_head struct
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - no block exists in the range.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_cpfile_find_checkpoint_block(struct inode *cpfile,
+ __u64 start_cno, __u64 end_cno,
+ __u64 *cnop,
+ struct buffer_head **bhp)
+{
+ unsigned long start, end, blkoff;
+ int ret;
+
+ if (unlikely(start_cno > end_cno))
+ return -ENOENT;
+
+ start = nilfs_cpfile_get_blkoff(cpfile, start_cno);
+ end = nilfs_cpfile_get_blkoff(cpfile, end_cno);
+
+ ret = nilfs_mdt_find_block(cpfile, start, end, &blkoff, bhp);
+ if (!ret)
+ *cnop = (blkoff == start) ? start_cno :
+ nilfs_cpfile_first_checkpoint_in_block(cpfile, blkoff);
+ return ret;
+}
+
+static inline int nilfs_cpfile_delete_checkpoint_block(struct inode *cpfile,
+ __u64 cno)
+{
+ return nilfs_mdt_delete_block(cpfile,
+ nilfs_cpfile_get_blkoff(cpfile, cno));
+}
+
+/**
+ * nilfs_cpfile_read_checkpoint - read a checkpoint entry in cpfile
+ * @cpfile: checkpoint file inode
+ * @cno: number of checkpoint entry to read
+ * @root: nilfs root object
+ * @ifile: ifile's inode to read and attach to @root
+ *
+ * This function imports checkpoint information from the checkpoint file and
+ * stores it to the inode file given by @ifile and the nilfs root object
+ * given by @root.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid checkpoint.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EIO - I/O error (including metadata corruption).
+ */
+int nilfs_cpfile_read_checkpoint(struct inode *cpfile, __u64 cno,
+ struct nilfs_root *root, struct inode *ifile)
+{
+ struct buffer_head *cp_bh;
+ struct nilfs_checkpoint *cp;
+ size_t offset;
+ int ret;
+
+ if (cno < 1 || cno > nilfs_mdt_cno(cpfile))
+ return -EINVAL;
+
+ down_read(&NILFS_MDT(cpfile)->mi_sem);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &cp_bh);
+ if (unlikely(ret < 0)) {
+ if (ret == -ENOENT)
+ ret = -EINVAL;
+ goto out_sem;
+ }
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ if (nilfs_checkpoint_invalid(cp)) {
+ ret = -EINVAL;
+ goto put_cp;
+ }
+
+ ret = nilfs_read_inode_common(ifile, &cp->cp_ifile_inode);
+ if (unlikely(ret)) {
+ /*
+ * Since this inode is on a checkpoint entry, treat errors
+ * as metadata corruption.
+ */
+ nilfs_err(cpfile->i_sb,
+ "ifile inode (checkpoint number=%llu) corrupted",
+ (unsigned long long)cno);
+ ret = -EIO;
+ goto put_cp;
+ }
+
+ /* Configure the nilfs root object */
+ atomic64_set(&root->inodes_count, le64_to_cpu(cp->cp_inodes_count));
+ atomic64_set(&root->blocks_count, le64_to_cpu(cp->cp_blocks_count));
+ root->ifile = ifile;
+
+put_cp:
+ kunmap_local(cp);
+ brelse(cp_bh);
+out_sem:
+ up_read(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_create_checkpoint - create a checkpoint entry on cpfile
+ * @cpfile: checkpoint file inode
+ * @cno: number of checkpoint to set up
+ *
+ * This function creates a checkpoint with the number specified by @cno on
+ * cpfile. If the specified checkpoint entry already exists due to a past
+ * failure, it will be reused without returning an error.
+ * In either case, the buffer of the block containing the checkpoint entry
+ * and the cpfile inode are made dirty for inclusion in the write log.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-EROFS - Read only filesystem
+ */
+int nilfs_cpfile_create_checkpoint(struct inode *cpfile, __u64 cno)
+{
+ struct buffer_head *header_bh, *cp_bh;
+ struct nilfs_cpfile_header *header;
+ struct nilfs_checkpoint *cp;
+ size_t offset;
+ int ret;
+
+ if (WARN_ON_ONCE(cno < 1))
+ return -EIO;
+
+ down_write(&NILFS_MDT(cpfile)->mi_sem);
+ ret = nilfs_cpfile_get_header_block(cpfile, &header_bh);
+ if (unlikely(ret < 0))
+ goto out_sem;
+
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 1, &cp_bh);
+ if (unlikely(ret < 0))
+ goto out_header;
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ if (nilfs_checkpoint_invalid(cp)) {
+ /* a newly-created checkpoint */
+ nilfs_checkpoint_clear_invalid(cp);
+ kunmap_local(cp);
+ if (!nilfs_cpfile_is_in_first(cpfile, cno))
+ nilfs_cpfile_block_add_valid_checkpoints(cpfile, cp_bh,
+ 1);
+
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->ch_ncheckpoints, 1);
+ kunmap_local(header);
+ mark_buffer_dirty(header_bh);
+ } else {
+ kunmap_local(cp);
+ }
+
+ /* Force the buffer and the inode to become dirty */
+ mark_buffer_dirty(cp_bh);
+ brelse(cp_bh);
+ nilfs_mdt_mark_dirty(cpfile);
+
+out_header:
+ brelse(header_bh);
+
+out_sem:
+ up_write(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_finalize_checkpoint - fill in a checkpoint entry in cpfile
+ * @cpfile: checkpoint file inode
+ * @cno: checkpoint number
+ * @root: nilfs root object
+ * @blkinc: number of blocks added by this checkpoint
+ * @ctime: checkpoint creation time
+ * @minor: minor checkpoint flag
+ *
+ * This function completes the checkpoint entry numbered by @cno in the
+ * cpfile with the data given by the arguments @root, @blkinc, @ctime, and
+ * @minor.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EIO - I/O error (including metadata corruption).
+ */
+int nilfs_cpfile_finalize_checkpoint(struct inode *cpfile, __u64 cno,
+ struct nilfs_root *root, __u64 blkinc,
+ time64_t ctime, bool minor)
+{
+ struct buffer_head *cp_bh;
+ struct nilfs_checkpoint *cp;
+ size_t offset;
+ int ret;
+
+ if (WARN_ON_ONCE(cno < 1))
+ return -EIO;
+
+ down_write(&NILFS_MDT(cpfile)->mi_sem);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &cp_bh);
+ if (unlikely(ret < 0)) {
+ if (ret == -ENOENT)
+ goto error;
+ goto out_sem;
+ }
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ if (unlikely(nilfs_checkpoint_invalid(cp))) {
+ kunmap_local(cp);
+ brelse(cp_bh);
+ goto error;
+ }
+
+ cp->cp_snapshot_list.ssl_next = 0;
+ cp->cp_snapshot_list.ssl_prev = 0;
+ cp->cp_inodes_count = cpu_to_le64(atomic64_read(&root->inodes_count));
+ cp->cp_blocks_count = cpu_to_le64(atomic64_read(&root->blocks_count));
+ cp->cp_nblk_inc = cpu_to_le64(blkinc);
+ cp->cp_create = cpu_to_le64(ctime);
+ cp->cp_cno = cpu_to_le64(cno);
+
+ if (minor)
+ nilfs_checkpoint_set_minor(cp);
+ else
+ nilfs_checkpoint_clear_minor(cp);
+
+ nilfs_write_inode_common(root->ifile, &cp->cp_ifile_inode);
+ nilfs_bmap_write(NILFS_I(root->ifile)->i_bmap, &cp->cp_ifile_inode);
+
+ kunmap_local(cp);
+ brelse(cp_bh);
+out_sem:
+ up_write(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+
+error:
+ nilfs_error(cpfile->i_sb,
+ "checkpoint finalization failed due to metadata corruption.");
+ ret = -EIO;
+ goto out_sem;
+}
+
+/**
+ * nilfs_cpfile_delete_checkpoints - delete checkpoints
+ * @cpfile: inode of checkpoint file
+ * @start: start checkpoint number
+ * @end: end checkpoint number
+ *
+ * Description: nilfs_cpfile_delete_checkpoints() deletes the checkpoints in
+ * the period from @start to @end, excluding @end itself. The checkpoints
+ * which have been already deleted are ignored.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid checkpoints.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_cpfile_delete_checkpoints(struct inode *cpfile,
+ __u64 start,
+ __u64 end)
+{
+ struct buffer_head *header_bh, *cp_bh;
+ struct nilfs_cpfile_header *header;
+ struct nilfs_checkpoint *cp;
+ size_t cpsz = NILFS_MDT(cpfile)->mi_entry_size;
+ __u64 cno;
+ size_t offset;
+ void *kaddr;
+ unsigned long tnicps;
+ int ret, ncps, nicps, nss, count, i;
+
+ if (unlikely(start == 0 || start > end)) {
+ nilfs_err(cpfile->i_sb,
+ "cannot delete checkpoints: invalid range [%llu, %llu)",
+ (unsigned long long)start, (unsigned long long)end);
+ return -EINVAL;
+ }
+
+ down_write(&NILFS_MDT(cpfile)->mi_sem);
+
+ ret = nilfs_cpfile_get_header_block(cpfile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+ tnicps = 0;
+ nss = 0;
+
+ for (cno = start; cno < end; cno += ncps) {
+ ncps = nilfs_cpfile_checkpoints_in_block(cpfile, cno, end);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &cp_bh);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ break;
+ /* skip hole */
+ ret = 0;
+ continue;
+ }
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kaddr = kmap_local_folio(cp_bh->b_folio, offset);
+ nicps = 0;
+ for (i = 0; i < ncps; i++, cp = (void *)cp + cpsz) {
+ if (nilfs_checkpoint_snapshot(cp)) {
+ nss++;
+ } else if (!nilfs_checkpoint_invalid(cp)) {
+ nilfs_checkpoint_set_invalid(cp);
+ nicps++;
+ }
+ }
+ kunmap_local(kaddr);
+
+ if (nicps <= 0) {
+ brelse(cp_bh);
+ continue;
+ }
+
+ tnicps += nicps;
+ mark_buffer_dirty(cp_bh);
+ nilfs_mdt_mark_dirty(cpfile);
+ if (nilfs_cpfile_is_in_first(cpfile, cno)) {
+ brelse(cp_bh);
+ continue;
+ }
+
+ count = nilfs_cpfile_block_sub_valid_checkpoints(cpfile, cp_bh,
+ nicps);
+ brelse(cp_bh);
+ if (unlikely(count < 0)) {
+ ret = count;
+ break;
+ }
+ if (count)
+ continue;
+
+ /* Delete the block if there are no more valid checkpoints */
+ ret = nilfs_cpfile_delete_checkpoint_block(cpfile, cno);
+ if (unlikely(ret)) {
+ nilfs_err(cpfile->i_sb,
+ "error %d deleting checkpoint block", ret);
+ break;
+ }
+ }
+
+ if (tnicps > 0) {
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->ch_ncheckpoints, -(u64)tnicps);
+ mark_buffer_dirty(header_bh);
+ nilfs_mdt_mark_dirty(cpfile);
+ kunmap_local(header);
+ }
+
+ brelse(header_bh);
+ if (nss > 0)
+ ret = -EBUSY;
+
+ out_sem:
+ up_write(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+static void nilfs_cpfile_checkpoint_to_cpinfo(struct inode *cpfile,
+ struct nilfs_checkpoint *cp,
+ struct nilfs_cpinfo *ci)
+{
+ ci->ci_flags = le32_to_cpu(cp->cp_flags);
+ ci->ci_cno = le64_to_cpu(cp->cp_cno);
+ ci->ci_create = le64_to_cpu(cp->cp_create);
+ ci->ci_nblk_inc = le64_to_cpu(cp->cp_nblk_inc);
+ ci->ci_inodes_count = le64_to_cpu(cp->cp_inodes_count);
+ ci->ci_blocks_count = le64_to_cpu(cp->cp_blocks_count);
+ ci->ci_next = le64_to_cpu(cp->cp_snapshot_list.ssl_next);
+}
+
+static ssize_t nilfs_cpfile_do_get_cpinfo(struct inode *cpfile, __u64 *cnop,
+ void *buf, unsigned int cisz,
+ size_t nci)
+{
+ struct nilfs_checkpoint *cp;
+ struct nilfs_cpinfo *ci = buf;
+ struct buffer_head *bh;
+ size_t cpsz = NILFS_MDT(cpfile)->mi_entry_size;
+ __u64 cur_cno = nilfs_mdt_cno(cpfile), cno = *cnop;
+ size_t offset;
+ void *kaddr;
+ int n, ret;
+ int ncps, i;
+
+ if (cno == 0)
+ return -ENOENT; /* checkpoint number 0 is invalid */
+ down_read(&NILFS_MDT(cpfile)->mi_sem);
+
+ for (n = 0; n < nci; cno += ncps) {
+ ret = nilfs_cpfile_find_checkpoint_block(
+ cpfile, cno, cur_cno - 1, &cno, &bh);
+ if (ret < 0) {
+ if (likely(ret == -ENOENT))
+ break;
+ goto out;
+ }
+ ncps = nilfs_cpfile_checkpoints_in_block(cpfile, cno, cur_cno);
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, bh);
+ cp = kaddr = kmap_local_folio(bh->b_folio, offset);
+ for (i = 0; i < ncps && n < nci; i++, cp = (void *)cp + cpsz) {
+ if (!nilfs_checkpoint_invalid(cp)) {
+ nilfs_cpfile_checkpoint_to_cpinfo(cpfile, cp,
+ ci);
+ ci = (void *)ci + cisz;
+ n++;
+ }
+ }
+ kunmap_local(kaddr);
+ brelse(bh);
+ }
+
+ ret = n;
+ if (n > 0) {
+ ci = (void *)ci - cisz;
+ *cnop = ci->ci_cno + 1;
+ }
+
+ out:
+ up_read(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+static ssize_t nilfs_cpfile_do_get_ssinfo(struct inode *cpfile, __u64 *cnop,
+ void *buf, unsigned int cisz,
+ size_t nci)
+{
+ struct buffer_head *bh;
+ struct nilfs_cpfile_header *header;
+ struct nilfs_checkpoint *cp;
+ struct nilfs_cpinfo *ci = buf;
+ __u64 curr = *cnop, next;
+ unsigned long curr_blkoff, next_blkoff;
+ size_t offset;
+ int n = 0, ret;
+
+ down_read(&NILFS_MDT(cpfile)->mi_sem);
+
+ if (curr == 0) {
+ ret = nilfs_cpfile_get_header_block(cpfile, &bh);
+ if (ret < 0)
+ goto out;
+ header = kmap_local_folio(bh->b_folio, 0);
+ curr = le64_to_cpu(header->ch_snapshot_list.ssl_next);
+ kunmap_local(header);
+ brelse(bh);
+ if (curr == 0) {
+ ret = 0;
+ goto out;
+ }
+ } else if (unlikely(curr == ~(__u64)0)) {
+ ret = 0;
+ goto out;
+ }
+
+ curr_blkoff = nilfs_cpfile_get_blkoff(cpfile, curr);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, curr, 0, &bh);
+ if (unlikely(ret < 0)) {
+ if (ret == -ENOENT)
+ ret = 0; /* No snapshots (started from a hole block) */
+ goto out;
+ }
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, curr, bh);
+ cp = kmap_local_folio(bh->b_folio, offset);
+ while (n < nci) {
+ curr = ~(__u64)0; /* Terminator */
+ if (unlikely(nilfs_checkpoint_invalid(cp) ||
+ !nilfs_checkpoint_snapshot(cp)))
+ break;
+ nilfs_cpfile_checkpoint_to_cpinfo(cpfile, cp, ci);
+ ci = (void *)ci + cisz;
+ n++;
+ next = le64_to_cpu(cp->cp_snapshot_list.ssl_next);
+ if (next == 0)
+ break; /* reach end of the snapshot list */
+
+ kunmap_local(cp);
+ next_blkoff = nilfs_cpfile_get_blkoff(cpfile, next);
+ if (curr_blkoff != next_blkoff) {
+ brelse(bh);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, next,
+ 0, &bh);
+ if (unlikely(ret < 0)) {
+ WARN_ON(ret == -ENOENT);
+ goto out;
+ }
+ }
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, next, bh);
+ cp = kmap_local_folio(bh->b_folio, offset);
+ curr = next;
+ curr_blkoff = next_blkoff;
+ }
+ kunmap_local(cp);
+ brelse(bh);
+ *cnop = curr;
+ ret = n;
+
+ out:
+ up_read(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_get_cpinfo - get information on checkpoints
+ * @cpfile: checkpoint file inode
+ * @cnop: place to pass a starting checkpoint number and receive a
+ * checkpoint number to continue the search
+ * @mode: mode of checkpoints that the caller wants to retrieve
+ * @buf: buffer for storing checkpoints' information
+ * @cisz: byte size of one checkpoint info item in array
+ * @nci: number of checkpoint info items to retrieve
+ *
+ * nilfs_cpfile_get_cpinfo() searches for checkpoints in @mode state
+ * starting from the checkpoint number stored in @cnop, and stores
+ * information about found checkpoints in @buf.
+ * The buffer pointed to by @buf must be large enough to store information
+ * for @nci checkpoints. If at least one checkpoint information is
+ * successfully retrieved, @cnop is updated to point to the checkpoint
+ * number to continue searching.
+ *
+ * Return: Count of checkpoint info items stored in the output buffer on
+ * success, or one of the following negative error codes on failure:
+ * * %-EINVAL - Invalid checkpoint mode.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Invalid checkpoint number specified.
+ */
+
+ssize_t nilfs_cpfile_get_cpinfo(struct inode *cpfile, __u64 *cnop, int mode,
+ void *buf, unsigned int cisz, size_t nci)
+{
+ switch (mode) {
+ case NILFS_CHECKPOINT:
+ return nilfs_cpfile_do_get_cpinfo(cpfile, cnop, buf, cisz, nci);
+ case NILFS_SNAPSHOT:
+ return nilfs_cpfile_do_get_ssinfo(cpfile, cnop, buf, cisz, nci);
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * nilfs_cpfile_delete_checkpoint - delete a checkpoint
+ * @cpfile: checkpoint file inode
+ * @cno: checkpoint number to delete
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EBUSY - Checkpoint in use (snapshot specified).
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - No valid checkpoint found.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_cpfile_delete_checkpoint(struct inode *cpfile, __u64 cno)
+{
+ struct nilfs_cpinfo ci;
+ __u64 tcno = cno;
+ ssize_t nci;
+
+ nci = nilfs_cpfile_do_get_cpinfo(cpfile, &tcno, &ci, sizeof(ci), 1);
+ if (nci < 0)
+ return nci;
+ else if (nci == 0 || ci.ci_cno != cno)
+ return -ENOENT;
+ else if (nilfs_cpinfo_snapshot(&ci))
+ return -EBUSY;
+
+ return nilfs_cpfile_delete_checkpoints(cpfile, cno, cno + 1);
+}
+
+static int nilfs_cpfile_set_snapshot(struct inode *cpfile, __u64 cno)
+{
+ struct buffer_head *header_bh, *curr_bh, *prev_bh, *cp_bh;
+ struct nilfs_cpfile_header *header;
+ struct nilfs_checkpoint *cp;
+ struct nilfs_snapshot_list *list;
+ __u64 curr, prev;
+ unsigned long curr_blkoff, prev_blkoff;
+ size_t offset, curr_list_offset, prev_list_offset;
+ int ret;
+
+ if (cno == 0)
+ return -ENOENT; /* checkpoint number 0 is invalid */
+ down_write(&NILFS_MDT(cpfile)->mi_sem);
+
+ ret = nilfs_cpfile_get_header_block(cpfile, &header_bh);
+ if (unlikely(ret < 0))
+ goto out_sem;
+
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &cp_bh);
+ if (ret < 0)
+ goto out_header;
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ if (nilfs_checkpoint_invalid(cp)) {
+ ret = -ENOENT;
+ kunmap_local(cp);
+ goto out_cp;
+ }
+ if (nilfs_checkpoint_snapshot(cp)) {
+ ret = 0;
+ kunmap_local(cp);
+ goto out_cp;
+ }
+ kunmap_local(cp);
+
+ /*
+ * Find the last snapshot before the checkpoint being changed to
+ * snapshot mode by going backwards through the snapshot list.
+ * Set "prev" to its checkpoint number, or 0 if not found.
+ */
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ list = &header->ch_snapshot_list;
+ curr_bh = header_bh;
+ get_bh(curr_bh);
+ curr = 0;
+ curr_blkoff = 0;
+ curr_list_offset = nilfs_cpfile_ch_snapshot_list_offset();
+ prev = le64_to_cpu(list->ssl_prev);
+ while (prev > cno) {
+ prev_blkoff = nilfs_cpfile_get_blkoff(cpfile, prev);
+ curr = prev;
+ kunmap_local(list);
+ if (curr_blkoff != prev_blkoff) {
+ brelse(curr_bh);
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, curr,
+ 0, &curr_bh);
+ if (unlikely(ret < 0))
+ goto out_cp;
+ }
+ curr_list_offset = nilfs_cpfile_cp_snapshot_list_offset(
+ cpfile, curr, curr_bh);
+ list = kmap_local_folio(curr_bh->b_folio, curr_list_offset);
+ curr_blkoff = prev_blkoff;
+ prev = le64_to_cpu(list->ssl_prev);
+ }
+ kunmap_local(list);
+
+ if (prev != 0) {
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, prev, 0,
+ &prev_bh);
+ if (ret < 0)
+ goto out_curr;
+
+ prev_list_offset = nilfs_cpfile_cp_snapshot_list_offset(
+ cpfile, prev, prev_bh);
+ } else {
+ prev_bh = header_bh;
+ get_bh(prev_bh);
+ prev_list_offset = nilfs_cpfile_ch_snapshot_list_offset();
+ }
+
+ /* Update the list entry for the next snapshot */
+ list = kmap_local_folio(curr_bh->b_folio, curr_list_offset);
+ list->ssl_prev = cpu_to_le64(cno);
+ kunmap_local(list);
+
+ /* Update the checkpoint being changed to a snapshot */
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ cp->cp_snapshot_list.ssl_next = cpu_to_le64(curr);
+ cp->cp_snapshot_list.ssl_prev = cpu_to_le64(prev);
+ nilfs_checkpoint_set_snapshot(cp);
+ kunmap_local(cp);
+
+ /* Update the list entry for the previous snapshot */
+ list = kmap_local_folio(prev_bh->b_folio, prev_list_offset);
+ list->ssl_next = cpu_to_le64(cno);
+ kunmap_local(list);
+
+ /* Update the statistics in the header */
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->ch_nsnapshots, 1);
+ kunmap_local(header);
+
+ mark_buffer_dirty(prev_bh);
+ mark_buffer_dirty(curr_bh);
+ mark_buffer_dirty(cp_bh);
+ mark_buffer_dirty(header_bh);
+ nilfs_mdt_mark_dirty(cpfile);
+
+ brelse(prev_bh);
+
+ out_curr:
+ brelse(curr_bh);
+
+ out_cp:
+ brelse(cp_bh);
+
+ out_header:
+ brelse(header_bh);
+
+ out_sem:
+ up_write(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+static int nilfs_cpfile_clear_snapshot(struct inode *cpfile, __u64 cno)
+{
+ struct buffer_head *header_bh, *next_bh, *prev_bh, *cp_bh;
+ struct nilfs_cpfile_header *header;
+ struct nilfs_checkpoint *cp;
+ struct nilfs_snapshot_list *list;
+ __u64 next, prev;
+ size_t offset, next_list_offset, prev_list_offset;
+ int ret;
+
+ if (cno == 0)
+ return -ENOENT; /* checkpoint number 0 is invalid */
+ down_write(&NILFS_MDT(cpfile)->mi_sem);
+
+ ret = nilfs_cpfile_get_header_block(cpfile, &header_bh);
+ if (unlikely(ret < 0))
+ goto out_sem;
+
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &cp_bh);
+ if (ret < 0)
+ goto out_header;
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, cp_bh);
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ if (nilfs_checkpoint_invalid(cp)) {
+ ret = -ENOENT;
+ kunmap_local(cp);
+ goto out_cp;
+ }
+ if (!nilfs_checkpoint_snapshot(cp)) {
+ ret = 0;
+ kunmap_local(cp);
+ goto out_cp;
+ }
+
+ list = &cp->cp_snapshot_list;
+ next = le64_to_cpu(list->ssl_next);
+ prev = le64_to_cpu(list->ssl_prev);
+ kunmap_local(cp);
+
+ if (next != 0) {
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, next, 0,
+ &next_bh);
+ if (ret < 0)
+ goto out_cp;
+
+ next_list_offset = nilfs_cpfile_cp_snapshot_list_offset(
+ cpfile, next, next_bh);
+ } else {
+ next_bh = header_bh;
+ get_bh(next_bh);
+ next_list_offset = nilfs_cpfile_ch_snapshot_list_offset();
+ }
+ if (prev != 0) {
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, prev, 0,
+ &prev_bh);
+ if (ret < 0)
+ goto out_next;
+
+ prev_list_offset = nilfs_cpfile_cp_snapshot_list_offset(
+ cpfile, prev, prev_bh);
+ } else {
+ prev_bh = header_bh;
+ get_bh(prev_bh);
+ prev_list_offset = nilfs_cpfile_ch_snapshot_list_offset();
+ }
+
+ /* Update the list entry for the next snapshot */
+ list = kmap_local_folio(next_bh->b_folio, next_list_offset);
+ list->ssl_prev = cpu_to_le64(prev);
+ kunmap_local(list);
+
+ /* Update the list entry for the previous snapshot */
+ list = kmap_local_folio(prev_bh->b_folio, prev_list_offset);
+ list->ssl_next = cpu_to_le64(next);
+ kunmap_local(list);
+
+ /* Update the snapshot being changed back to a plain checkpoint */
+ cp = kmap_local_folio(cp_bh->b_folio, offset);
+ cp->cp_snapshot_list.ssl_next = cpu_to_le64(0);
+ cp->cp_snapshot_list.ssl_prev = cpu_to_le64(0);
+ nilfs_checkpoint_clear_snapshot(cp);
+ kunmap_local(cp);
+
+ /* Update the statistics in the header */
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->ch_nsnapshots, -1);
+ kunmap_local(header);
+
+ mark_buffer_dirty(next_bh);
+ mark_buffer_dirty(prev_bh);
+ mark_buffer_dirty(cp_bh);
+ mark_buffer_dirty(header_bh);
+ nilfs_mdt_mark_dirty(cpfile);
+
+ brelse(prev_bh);
+
+ out_next:
+ brelse(next_bh);
+
+ out_cp:
+ brelse(cp_bh);
+
+ out_header:
+ brelse(header_bh);
+
+ out_sem:
+ up_write(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_is_snapshot - determine if checkpoint is a snapshot
+ * @cpfile: inode of checkpoint file
+ * @cno: checkpoint number
+ *
+ * Return: 1 if the checkpoint specified by @cno is a snapshot, 0 if not, or
+ * one of the following negative error codes on failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - No such checkpoint.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_cpfile_is_snapshot(struct inode *cpfile, __u64 cno)
+{
+ struct buffer_head *bh;
+ struct nilfs_checkpoint *cp;
+ size_t offset;
+ int ret;
+
+ /*
+ * CP number is invalid if it's zero or larger than the
+ * largest existing one.
+ */
+ if (cno == 0 || cno >= nilfs_mdt_cno(cpfile))
+ return -ENOENT;
+ down_read(&NILFS_MDT(cpfile)->mi_sem);
+
+ ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 0, &bh);
+ if (ret < 0)
+ goto out;
+
+ offset = nilfs_cpfile_checkpoint_offset(cpfile, cno, bh);
+ cp = kmap_local_folio(bh->b_folio, offset);
+ if (nilfs_checkpoint_invalid(cp))
+ ret = -ENOENT;
+ else
+ ret = nilfs_checkpoint_snapshot(cp);
+ kunmap_local(cp);
+ brelse(bh);
+
+ out:
+ up_read(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_change_cpmode - change checkpoint mode
+ * @cpfile: inode of checkpoint file
+ * @cno: checkpoint number
+ * @mode: mode of checkpoint
+ *
+ * Description: nilfs_change_cpmode() changes the mode of the checkpoint
+ * specified by @cno. The mode @mode is NILFS_CHECKPOINT or NILFS_SNAPSHOT.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - No such checkpoint.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_cpfile_change_cpmode(struct inode *cpfile, __u64 cno, int mode)
+{
+ int ret;
+
+ switch (mode) {
+ case NILFS_CHECKPOINT:
+ if (nilfs_checkpoint_is_mounted(cpfile->i_sb, cno))
+ /*
+ * Current implementation does not have to protect
+ * plain read-only mounts since they are exclusive
+ * with a read/write mount and are protected from the
+ * cleaner.
+ */
+ ret = -EBUSY;
+ else
+ ret = nilfs_cpfile_clear_snapshot(cpfile, cno);
+ return ret;
+ case NILFS_SNAPSHOT:
+ return nilfs_cpfile_set_snapshot(cpfile, cno);
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * nilfs_cpfile_get_stat - get checkpoint statistics
+ * @cpfile: inode of checkpoint file
+ * @cpstat: pointer to a structure of checkpoint statistics
+ *
+ * Description: nilfs_cpfile_get_stat() returns information about checkpoints.
+ * The checkpoint statistics are stored in the location pointed to by @cpstat.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_cpfile_get_stat(struct inode *cpfile, struct nilfs_cpstat *cpstat)
+{
+ struct buffer_head *bh;
+ struct nilfs_cpfile_header *header;
+ int ret;
+
+ down_read(&NILFS_MDT(cpfile)->mi_sem);
+
+ ret = nilfs_cpfile_get_header_block(cpfile, &bh);
+ if (ret < 0)
+ goto out_sem;
+ header = kmap_local_folio(bh->b_folio, 0);
+ cpstat->cs_cno = nilfs_mdt_cno(cpfile);
+ cpstat->cs_ncps = le64_to_cpu(header->ch_ncheckpoints);
+ cpstat->cs_nsss = le64_to_cpu(header->ch_nsnapshots);
+ kunmap_local(header);
+ brelse(bh);
+
+ out_sem:
+ up_read(&NILFS_MDT(cpfile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_cpfile_read - read or get cpfile inode
+ * @sb: super block instance
+ * @cpsize: size of a checkpoint entry
+ * @raw_inode: on-disk cpfile inode
+ * @inodep: buffer to store the inode
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_cpfile_read(struct super_block *sb, size_t cpsize,
+ struct nilfs_inode *raw_inode, struct inode **inodep)
+{
+ struct inode *cpfile;
+ int err;
+
+ if (cpsize > sb->s_blocksize) {
+ nilfs_err(sb, "too large checkpoint size: %zu bytes", cpsize);
+ return -EINVAL;
+ } else if (cpsize < NILFS_MIN_CHECKPOINT_SIZE) {
+ nilfs_err(sb, "too small checkpoint size: %zu bytes", cpsize);
+ return -EINVAL;
+ }
+
+ cpfile = nilfs_iget_locked(sb, NULL, NILFS_CPFILE_INO);
+ if (unlikely(!cpfile))
+ return -ENOMEM;
+ if (!(inode_state_read_once(cpfile) & I_NEW))
+ goto out;
+
+ err = nilfs_mdt_init(cpfile, NILFS_MDT_GFP, 0);
+ if (err)
+ goto failed;
+
+ nilfs_mdt_set_entry_size(cpfile, cpsize,
+ sizeof(struct nilfs_cpfile_header));
+
+ err = nilfs_read_inode_common(cpfile, raw_inode);
+ if (err)
+ goto failed;
+
+ unlock_new_inode(cpfile);
+ out:
+ *inodep = cpfile;
+ return 0;
+ failed:
+ iget_failed(cpfile);
+ return err;
+}
diff --git a/fs/nilfs2/cpfile.h b/fs/nilfs2/cpfile.h
new file mode 100644
index 000000000..f5b1d5928
--- /dev/null
+++ b/fs/nilfs2/cpfile.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS checkpoint file.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_CPFILE_H
+#define _NILFS_CPFILE_H
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/nilfs2_api.h> /* nilfs_cpstat */
+#include <linux/nilfs2_ondisk.h> /* nilfs_inode, nilfs_checkpoint */
+
+
+int nilfs_cpfile_read_checkpoint(struct inode *cpfile, __u64 cno,
+ struct nilfs_root *root, struct inode *ifile);
+int nilfs_cpfile_create_checkpoint(struct inode *cpfile, __u64 cno);
+int nilfs_cpfile_finalize_checkpoint(struct inode *cpfile, __u64 cno,
+ struct nilfs_root *root, __u64 blkinc,
+ time64_t ctime, bool minor);
+int nilfs_cpfile_delete_checkpoints(struct inode *, __u64, __u64);
+int nilfs_cpfile_delete_checkpoint(struct inode *, __u64);
+int nilfs_cpfile_change_cpmode(struct inode *, __u64, int);
+int nilfs_cpfile_is_snapshot(struct inode *, __u64);
+int nilfs_cpfile_get_stat(struct inode *, struct nilfs_cpstat *);
+ssize_t nilfs_cpfile_get_cpinfo(struct inode *, __u64 *, int, void *,
+ unsigned int, size_t);
+
+int nilfs_cpfile_read(struct super_block *sb, size_t cpsize,
+ struct nilfs_inode *raw_inode, struct inode **inodep);
+
+#endif /* _NILFS_CPFILE_H */
diff --git a/fs/nilfs2/dat.c b/fs/nilfs2/dat.c
new file mode 100644
index 000000000..888dc1831
--- /dev/null
+++ b/fs/nilfs2/dat.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS disk address translation.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include "nilfs.h"
+#include "mdt.h"
+#include "alloc.h"
+#include "dat.h"
+
+
+#define NILFS_CNO_MIN ((__u64)1)
+#define NILFS_CNO_MAX (~(__u64)0)
+
+/**
+ * struct nilfs_dat_info - on-memory private data of DAT file
+ * @mi: on-memory private data of metadata file
+ * @palloc_cache: persistent object allocator cache of DAT file
+ * @shadow: shadow map of DAT file
+ */
+struct nilfs_dat_info {
+ struct nilfs_mdt_info mi;
+ struct nilfs_palloc_cache palloc_cache;
+ struct nilfs_shadow_map shadow;
+};
+
+static inline struct nilfs_dat_info *NILFS_DAT_I(struct inode *dat)
+{
+ return (struct nilfs_dat_info *)NILFS_MDT(dat);
+}
+
+static int nilfs_dat_prepare_entry(struct inode *dat,
+ struct nilfs_palloc_req *req, int create)
+{
+ int ret;
+
+ ret = nilfs_palloc_get_entry_block(dat, req->pr_entry_nr,
+ create, &req->pr_entry_bh);
+ if (unlikely(ret == -ENOENT)) {
+ nilfs_err(dat->i_sb,
+ "DAT doesn't have a block to manage vblocknr = %llu",
+ (unsigned long long)req->pr_entry_nr);
+ /*
+ * Return internal code -EINVAL to notify bmap layer of
+ * metadata corruption.
+ */
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static void nilfs_dat_commit_entry(struct inode *dat,
+ struct nilfs_palloc_req *req)
+{
+ mark_buffer_dirty(req->pr_entry_bh);
+ nilfs_mdt_mark_dirty(dat);
+ brelse(req->pr_entry_bh);
+}
+
+static void nilfs_dat_abort_entry(struct inode *dat,
+ struct nilfs_palloc_req *req)
+{
+ brelse(req->pr_entry_bh);
+}
+
+int nilfs_dat_prepare_alloc(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ int ret;
+
+ ret = nilfs_palloc_prepare_alloc_entry(dat, req, true);
+ if (ret < 0)
+ return ret;
+
+ ret = nilfs_dat_prepare_entry(dat, req, 1);
+ if (ret < 0)
+ nilfs_palloc_abort_alloc_entry(dat, req);
+
+ return ret;
+}
+
+void nilfs_dat_commit_alloc(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ struct nilfs_dat_entry *entry;
+ size_t offset;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ entry->de_start = cpu_to_le64(NILFS_CNO_MIN);
+ entry->de_end = cpu_to_le64(NILFS_CNO_MAX);
+ entry->de_blocknr = cpu_to_le64(0);
+ kunmap_local(entry);
+
+ nilfs_palloc_commit_alloc_entry(dat, req);
+ nilfs_dat_commit_entry(dat, req);
+}
+
+void nilfs_dat_abort_alloc(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ nilfs_dat_abort_entry(dat, req);
+ nilfs_palloc_abort_alloc_entry(dat, req);
+}
+
+static void nilfs_dat_commit_free(struct inode *dat,
+ struct nilfs_palloc_req *req)
+{
+ struct nilfs_dat_entry *entry;
+ size_t offset;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ entry->de_start = cpu_to_le64(NILFS_CNO_MIN);
+ entry->de_end = cpu_to_le64(NILFS_CNO_MIN);
+ entry->de_blocknr = cpu_to_le64(0);
+ kunmap_local(entry);
+
+ nilfs_dat_commit_entry(dat, req);
+
+ if (unlikely(req->pr_desc_bh == NULL || req->pr_bitmap_bh == NULL)) {
+ nilfs_error(dat->i_sb,
+ "state inconsistency probably due to duplicate use of vblocknr = %llu",
+ (unsigned long long)req->pr_entry_nr);
+ return;
+ }
+ nilfs_palloc_commit_free_entry(dat, req);
+}
+
+int nilfs_dat_prepare_start(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ return nilfs_dat_prepare_entry(dat, req, 0);
+}
+
+void nilfs_dat_commit_start(struct inode *dat, struct nilfs_palloc_req *req,
+ sector_t blocknr)
+{
+ struct nilfs_dat_entry *entry;
+ size_t offset;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ entry->de_start = cpu_to_le64(nilfs_mdt_cno(dat));
+ entry->de_blocknr = cpu_to_le64(blocknr);
+ kunmap_local(entry);
+
+ nilfs_dat_commit_entry(dat, req);
+}
+
+int nilfs_dat_prepare_end(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ struct nilfs_dat_entry *entry;
+ __u64 start;
+ sector_t blocknr;
+ size_t offset;
+ int ret;
+
+ ret = nilfs_dat_prepare_entry(dat, req, 0);
+ if (ret < 0)
+ return ret;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ start = le64_to_cpu(entry->de_start);
+ blocknr = le64_to_cpu(entry->de_blocknr);
+ kunmap_local(entry);
+
+ if (blocknr == 0) {
+ ret = nilfs_palloc_prepare_free_entry(dat, req);
+ if (ret < 0) {
+ nilfs_dat_abort_entry(dat, req);
+ return ret;
+ }
+ }
+ if (unlikely(start > nilfs_mdt_cno(dat))) {
+ nilfs_err(dat->i_sb,
+ "vblocknr = %llu has abnormal lifetime: start cno (= %llu) > current cno (= %llu)",
+ (unsigned long long)req->pr_entry_nr,
+ (unsigned long long)start,
+ (unsigned long long)nilfs_mdt_cno(dat));
+ nilfs_dat_abort_entry(dat, req);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void nilfs_dat_commit_end(struct inode *dat, struct nilfs_palloc_req *req,
+ int dead)
+{
+ struct nilfs_dat_entry *entry;
+ __u64 start, end;
+ sector_t blocknr;
+ size_t offset;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ end = start = le64_to_cpu(entry->de_start);
+ if (!dead) {
+ end = nilfs_mdt_cno(dat);
+ WARN_ON(start > end);
+ }
+ entry->de_end = cpu_to_le64(end);
+ blocknr = le64_to_cpu(entry->de_blocknr);
+ kunmap_local(entry);
+
+ if (blocknr == 0)
+ nilfs_dat_commit_free(dat, req);
+ else
+ nilfs_dat_commit_entry(dat, req);
+}
+
+void nilfs_dat_abort_end(struct inode *dat, struct nilfs_palloc_req *req)
+{
+ struct nilfs_dat_entry *entry;
+ __u64 start;
+ sector_t blocknr;
+ size_t offset;
+
+ offset = nilfs_palloc_entry_offset(dat, req->pr_entry_nr,
+ req->pr_entry_bh);
+ entry = kmap_local_folio(req->pr_entry_bh->b_folio, offset);
+ start = le64_to_cpu(entry->de_start);
+ blocknr = le64_to_cpu(entry->de_blocknr);
+ kunmap_local(entry);
+
+ if (start == nilfs_mdt_cno(dat) && blocknr == 0)
+ nilfs_palloc_abort_free_entry(dat, req);
+ nilfs_dat_abort_entry(dat, req);
+}
+
+int nilfs_dat_prepare_update(struct inode *dat,
+ struct nilfs_palloc_req *oldreq,
+ struct nilfs_palloc_req *newreq)
+{
+ int ret;
+
+ ret = nilfs_dat_prepare_end(dat, oldreq);
+ if (!ret) {
+ ret = nilfs_dat_prepare_alloc(dat, newreq);
+ if (ret < 0)
+ nilfs_dat_abort_end(dat, oldreq);
+ }
+ return ret;
+}
+
+void nilfs_dat_commit_update(struct inode *dat,
+ struct nilfs_palloc_req *oldreq,
+ struct nilfs_palloc_req *newreq, int dead)
+{
+ nilfs_dat_commit_end(dat, oldreq, dead);
+ nilfs_dat_commit_alloc(dat, newreq);
+}
+
+void nilfs_dat_abort_update(struct inode *dat,
+ struct nilfs_palloc_req *oldreq,
+ struct nilfs_palloc_req *newreq)
+{
+ nilfs_dat_abort_end(dat, oldreq);
+ nilfs_dat_abort_alloc(dat, newreq);
+}
+
+/**
+ * nilfs_dat_mark_dirty - mark the DAT block buffer containing the specified
+ * virtual block address entry as dirty
+ * @dat: DAT file inode
+ * @vblocknr: virtual block number
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid DAT entry (internal code).
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_dat_mark_dirty(struct inode *dat, __u64 vblocknr)
+{
+ struct nilfs_palloc_req req;
+ int ret;
+
+ req.pr_entry_nr = vblocknr;
+ ret = nilfs_dat_prepare_entry(dat, &req, 0);
+ if (ret == 0)
+ nilfs_dat_commit_entry(dat, &req);
+ return ret;
+}
+
+/**
+ * nilfs_dat_freev - free virtual block numbers
+ * @dat: DAT file inode
+ * @vblocknrs: array of virtual block numbers
+ * @nitems: number of virtual block numbers
+ *
+ * Description: nilfs_dat_freev() frees the virtual block numbers specified by
+ * @vblocknrs and @nitems.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - The virtual block number have not been allocated.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_dat_freev(struct inode *dat, __u64 *vblocknrs, size_t nitems)
+{
+ return nilfs_palloc_freev(dat, vblocknrs, nitems);
+}
+
+/**
+ * nilfs_dat_move - change a block number
+ * @dat: DAT file inode
+ * @vblocknr: virtual block number
+ * @blocknr: block number
+ *
+ * Description: nilfs_dat_move() changes the block number associated with
+ * @vblocknr to @blocknr.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_dat_move(struct inode *dat, __u64 vblocknr, sector_t blocknr)
+{
+ struct buffer_head *entry_bh;
+ struct nilfs_dat_entry *entry;
+ size_t offset;
+ int ret;
+
+ ret = nilfs_palloc_get_entry_block(dat, vblocknr, 0, &entry_bh);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The given disk block number (blocknr) is not yet written to
+ * the device at this point.
+ *
+ * To prevent nilfs_dat_translate() from returning the
+ * uncommitted block number, this makes a copy of the entry
+ * buffer and redirects nilfs_dat_translate() to the copy.
+ */
+ if (!buffer_nilfs_redirected(entry_bh)) {
+ ret = nilfs_mdt_freeze_buffer(dat, entry_bh);
+ if (ret) {
+ brelse(entry_bh);
+ return ret;
+ }
+ }
+
+ offset = nilfs_palloc_entry_offset(dat, vblocknr, entry_bh);
+ entry = kmap_local_folio(entry_bh->b_folio, offset);
+ if (unlikely(entry->de_blocknr == cpu_to_le64(0))) {
+ nilfs_crit(dat->i_sb,
+ "%s: invalid vblocknr = %llu, [%llu, %llu)",
+ __func__, (unsigned long long)vblocknr,
+ (unsigned long long)le64_to_cpu(entry->de_start),
+ (unsigned long long)le64_to_cpu(entry->de_end));
+ kunmap_local(entry);
+ brelse(entry_bh);
+ return -EINVAL;
+ }
+ WARN_ON(blocknr == 0);
+ entry->de_blocknr = cpu_to_le64(blocknr);
+ kunmap_local(entry);
+
+ mark_buffer_dirty(entry_bh);
+ nilfs_mdt_mark_dirty(dat);
+
+ brelse(entry_bh);
+
+ return 0;
+}
+
+/**
+ * nilfs_dat_translate - translate a virtual block number to a block number
+ * @dat: DAT file inode
+ * @vblocknr: virtual block number
+ * @blocknrp: pointer to a block number
+ *
+ * Description: nilfs_dat_translate() maps the virtual block number @vblocknr
+ * to the corresponding block number. The block number associated with
+ * @vblocknr is stored in the place pointed to by @blocknrp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - A block number associated with @vblocknr does not exist.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_dat_translate(struct inode *dat, __u64 vblocknr, sector_t *blocknrp)
+{
+ struct buffer_head *entry_bh, *bh;
+ struct nilfs_dat_entry *entry;
+ sector_t blocknr;
+ size_t offset;
+ int ret;
+
+ ret = nilfs_palloc_get_entry_block(dat, vblocknr, 0, &entry_bh);
+ if (ret < 0)
+ return ret;
+
+ if (!nilfs_doing_gc() && buffer_nilfs_redirected(entry_bh)) {
+ bh = nilfs_mdt_get_frozen_buffer(dat, entry_bh);
+ if (bh) {
+ WARN_ON(!buffer_uptodate(bh));
+ brelse(entry_bh);
+ entry_bh = bh;
+ }
+ }
+
+ offset = nilfs_palloc_entry_offset(dat, vblocknr, entry_bh);
+ entry = kmap_local_folio(entry_bh->b_folio, offset);
+ blocknr = le64_to_cpu(entry->de_blocknr);
+ if (blocknr == 0) {
+ ret = -ENOENT;
+ goto out;
+ }
+ *blocknrp = blocknr;
+
+ out:
+ kunmap_local(entry);
+ brelse(entry_bh);
+ return ret;
+}
+
+ssize_t nilfs_dat_get_vinfo(struct inode *dat, void *buf, unsigned int visz,
+ size_t nvi)
+{
+ struct buffer_head *entry_bh;
+ struct nilfs_dat_entry *entry, *first_entry;
+ struct nilfs_vinfo *vinfo = buf;
+ __u64 first, last;
+ size_t offset;
+ unsigned long entries_per_block = NILFS_MDT(dat)->mi_entries_per_block;
+ unsigned int entry_size = NILFS_MDT(dat)->mi_entry_size;
+ int i, j, n, ret;
+
+ for (i = 0; i < nvi; i += n) {
+ ret = nilfs_palloc_get_entry_block(dat, vinfo->vi_vblocknr,
+ 0, &entry_bh);
+ if (ret < 0)
+ return ret;
+
+ first = vinfo->vi_vblocknr;
+ first = div64_ul(first, entries_per_block);
+ first *= entries_per_block;
+ /* first virtual block number in this block */
+
+ last = first + entries_per_block - 1;
+ /* last virtual block number in this block */
+
+ offset = nilfs_palloc_entry_offset(dat, first, entry_bh);
+ first_entry = kmap_local_folio(entry_bh->b_folio, offset);
+ for (j = i, n = 0;
+ j < nvi && vinfo->vi_vblocknr >= first &&
+ vinfo->vi_vblocknr <= last;
+ j++, n++, vinfo = (void *)vinfo + visz) {
+ entry = (void *)first_entry +
+ (vinfo->vi_vblocknr - first) * entry_size;
+ vinfo->vi_start = le64_to_cpu(entry->de_start);
+ vinfo->vi_end = le64_to_cpu(entry->de_end);
+ vinfo->vi_blocknr = le64_to_cpu(entry->de_blocknr);
+ }
+ kunmap_local(first_entry);
+ brelse(entry_bh);
+ }
+
+ return nvi;
+}
+
+/**
+ * nilfs_dat_read - read or get dat inode
+ * @sb: super block instance
+ * @entry_size: size of a dat entry
+ * @raw_inode: on-disk dat inode
+ * @inodep: buffer to store the inode
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_dat_read(struct super_block *sb, size_t entry_size,
+ struct nilfs_inode *raw_inode, struct inode **inodep)
+{
+ static struct lock_class_key dat_lock_key;
+ struct inode *dat;
+ struct nilfs_dat_info *di;
+ int err;
+
+ if (entry_size > sb->s_blocksize) {
+ nilfs_err(sb, "too large DAT entry size: %zu bytes",
+ entry_size);
+ return -EINVAL;
+ } else if (entry_size < NILFS_MIN_DAT_ENTRY_SIZE) {
+ nilfs_err(sb, "too small DAT entry size: %zu bytes",
+ entry_size);
+ return -EINVAL;
+ }
+
+ dat = nilfs_iget_locked(sb, NULL, NILFS_DAT_INO);
+ if (unlikely(!dat))
+ return -ENOMEM;
+ if (!(inode_state_read_once(dat) & I_NEW))
+ goto out;
+
+ err = nilfs_mdt_init(dat, NILFS_MDT_GFP, sizeof(*di));
+ if (err)
+ goto failed;
+
+ err = nilfs_palloc_init_blockgroup(dat, entry_size);
+ if (err)
+ goto failed;
+
+ di = NILFS_DAT_I(dat);
+ lockdep_set_class(&di->mi.mi_sem, &dat_lock_key);
+ nilfs_palloc_setup_cache(dat, &di->palloc_cache);
+ err = nilfs_mdt_setup_shadow_map(dat, &di->shadow);
+ if (err)
+ goto failed;
+
+ err = nilfs_attach_btree_node_cache(dat);
+ if (err)
+ goto failed;
+ err = nilfs_read_inode_common(dat, raw_inode);
+ if (err)
+ goto failed;
+
+ unlock_new_inode(dat);
+ out:
+ *inodep = dat;
+ return 0;
+ failed:
+ iget_failed(dat);
+ return err;
+}
diff --git a/fs/nilfs2/dat.h b/fs/nilfs2/dat.h
new file mode 100644
index 000000000..468c82d26
--- /dev/null
+++ b/fs/nilfs2/dat.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS disk address translation.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_DAT_H
+#define _NILFS_DAT_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/nilfs2_ondisk.h> /* nilfs_inode, nilfs_checkpoint */
+
+
+struct nilfs_palloc_req;
+
+int nilfs_dat_translate(struct inode *, __u64, sector_t *);
+
+int nilfs_dat_prepare_alloc(struct inode *, struct nilfs_palloc_req *);
+void nilfs_dat_commit_alloc(struct inode *, struct nilfs_palloc_req *);
+void nilfs_dat_abort_alloc(struct inode *, struct nilfs_palloc_req *);
+int nilfs_dat_prepare_start(struct inode *, struct nilfs_palloc_req *);
+void nilfs_dat_commit_start(struct inode *, struct nilfs_palloc_req *,
+ sector_t);
+int nilfs_dat_prepare_end(struct inode *, struct nilfs_palloc_req *);
+void nilfs_dat_commit_end(struct inode *, struct nilfs_palloc_req *, int);
+void nilfs_dat_abort_end(struct inode *, struct nilfs_palloc_req *);
+int nilfs_dat_prepare_update(struct inode *, struct nilfs_palloc_req *,
+ struct nilfs_palloc_req *);
+void nilfs_dat_commit_update(struct inode *, struct nilfs_palloc_req *,
+ struct nilfs_palloc_req *, int);
+void nilfs_dat_abort_update(struct inode *, struct nilfs_palloc_req *,
+ struct nilfs_palloc_req *);
+
+int nilfs_dat_mark_dirty(struct inode *, __u64);
+int nilfs_dat_freev(struct inode *, __u64 *, size_t);
+int nilfs_dat_move(struct inode *, __u64, sector_t);
+ssize_t nilfs_dat_get_vinfo(struct inode *, void *, unsigned int, size_t);
+
+int nilfs_dat_read(struct super_block *sb, size_t entry_size,
+ struct nilfs_inode *raw_inode, struct inode **inodep);
+
+#endif /* _NILFS_DAT_H */
diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c
new file mode 100644
index 000000000..8b53802b6
--- /dev/null
+++ b/fs/nilfs2/dir.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS directory entry operations
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Modified for NILFS by Amagai Yoshiji.
+ */
+/*
+ * linux/fs/ext2/dir.c
+ *
+ * Copyright (C) 1992, 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * from
+ *
+ * linux/fs/minix/dir.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * ext2 directory handling functions
+ *
+ * Big-endian to little-endian byte-swapping/bitmaps by
+ * David S. Miller (davem@caip.rutgers.edu), 1995
+ *
+ * All code that works with directory layout had been switched to pagecache
+ * and moved here. AV
+ */
+
+#include <linux/pagemap.h>
+#include <linux/filelock.h>
+#include "nilfs.h"
+#include "page.h"
+
+static inline unsigned int nilfs_rec_len_from_disk(__le16 dlen)
+{
+ unsigned int len = le16_to_cpu(dlen);
+
+#if (PAGE_SIZE >= 65536)
+ if (len == NILFS_MAX_REC_LEN)
+ return 1 << 16;
+#endif
+ return len;
+}
+
+static inline __le16 nilfs_rec_len_to_disk(unsigned int len)
+{
+#if (PAGE_SIZE >= 65536)
+ if (len == (1 << 16))
+ return cpu_to_le16(NILFS_MAX_REC_LEN);
+
+ BUG_ON(len > (1 << 16));
+#endif
+ return cpu_to_le16(len);
+}
+
+/*
+ * nilfs uses block-sized chunks. Arguably, sector-sized ones would be
+ * more robust, but we have what we have
+ */
+static inline unsigned int nilfs_chunk_size(struct inode *inode)
+{
+ return inode->i_sb->s_blocksize;
+}
+
+/*
+ * Return the offset into page `page_nr' of the last valid
+ * byte in that page, plus one.
+ */
+static unsigned int nilfs_last_byte(struct inode *inode, unsigned long page_nr)
+{
+ u64 last_byte = inode->i_size;
+
+ last_byte -= page_nr << PAGE_SHIFT;
+ if (last_byte > PAGE_SIZE)
+ last_byte = PAGE_SIZE;
+ return last_byte;
+}
+
+static int nilfs_prepare_chunk(struct folio *folio, unsigned int from,
+ unsigned int to)
+{
+ loff_t pos = folio_pos(folio) + from;
+
+ return __block_write_begin(folio, pos, to - from, nilfs_get_block);
+}
+
+static void nilfs_commit_chunk(struct folio *folio,
+ struct address_space *mapping, size_t from, size_t to)
+{
+ struct inode *dir = mapping->host;
+ loff_t pos = folio_pos(folio) + from;
+ size_t copied, len = to - from;
+ unsigned int nr_dirty;
+ int err;
+
+ nr_dirty = nilfs_page_count_clean_buffers(folio, from, to);
+ copied = block_write_end(pos, len, len, folio);
+ if (pos + copied > dir->i_size)
+ i_size_write(dir, pos + copied);
+ if (IS_DIRSYNC(dir))
+ nilfs_set_transaction_flag(NILFS_TI_SYNC);
+ err = nilfs_set_file_dirty(dir, nr_dirty);
+ WARN_ON(err); /* do not happen */
+ folio_unlock(folio);
+}
+
+static bool nilfs_check_folio(struct folio *folio, char *kaddr)
+{
+ struct inode *dir = folio->mapping->host;
+ struct super_block *sb = dir->i_sb;
+ unsigned int chunk_size = nilfs_chunk_size(dir);
+ size_t offs, rec_len;
+ size_t limit = folio_size(folio);
+ struct nilfs_dir_entry *p;
+ char *error;
+
+ if (dir->i_size < folio_pos(folio) + limit) {
+ limit = dir->i_size - folio_pos(folio);
+ if (limit & (chunk_size - 1))
+ goto Ebadsize;
+ if (!limit)
+ goto out;
+ }
+ for (offs = 0; offs <= limit - NILFS_DIR_REC_LEN(1); offs += rec_len) {
+ p = (struct nilfs_dir_entry *)(kaddr + offs);
+ rec_len = nilfs_rec_len_from_disk(p->rec_len);
+
+ if (rec_len < NILFS_DIR_REC_LEN(1))
+ goto Eshort;
+ if (rec_len & 3)
+ goto Ealign;
+ if (rec_len < NILFS_DIR_REC_LEN(p->name_len))
+ goto Enamelen;
+ if (((offs + rec_len - 1) ^ offs) & ~(chunk_size-1))
+ goto Espan;
+ if (unlikely(p->inode &&
+ NILFS_PRIVATE_INODE(le64_to_cpu(p->inode))))
+ goto Einumber;
+ }
+ if (offs != limit)
+ goto Eend;
+out:
+ folio_set_checked(folio);
+ return true;
+
+ /* Too bad, we had an error */
+
+Ebadsize:
+ nilfs_error(sb,
+ "size of directory #%llu is not a multiple of chunk size",
+ dir->i_ino);
+ goto fail;
+Eshort:
+ error = "rec_len is smaller than minimal";
+ goto bad_entry;
+Ealign:
+ error = "unaligned directory entry";
+ goto bad_entry;
+Enamelen:
+ error = "rec_len is too small for name_len";
+ goto bad_entry;
+Espan:
+ error = "directory entry across blocks";
+ goto bad_entry;
+Einumber:
+ error = "disallowed inode number";
+bad_entry:
+ nilfs_error(sb,
+ "bad entry in directory #%llu: %s - offset=%lu, inode=%llu, rec_len=%zd, name_len=%d",
+ dir->i_ino, error, (folio->index << PAGE_SHIFT) + offs,
+ le64_to_cpu(p->inode), rec_len, p->name_len);
+ goto fail;
+Eend:
+ p = (struct nilfs_dir_entry *)(kaddr + offs);
+ nilfs_error(sb,
+ "entry in directory #%llu spans the page boundary offset=%lu, inode=%llu",
+ dir->i_ino, (folio->index << PAGE_SHIFT) + offs,
+ le64_to_cpu(p->inode));
+fail:
+ return false;
+}
+
+static void *nilfs_get_folio(struct inode *dir, unsigned long n,
+ struct folio **foliop)
+{
+ struct address_space *mapping = dir->i_mapping;
+ struct folio *folio = read_mapping_folio(mapping, n, NULL);
+ void *kaddr;
+
+ if (IS_ERR(folio))
+ return folio;
+
+ kaddr = kmap_local_folio(folio, 0);
+ if (unlikely(!folio_test_checked(folio))) {
+ if (!nilfs_check_folio(folio, kaddr))
+ goto fail;
+ }
+
+ *foliop = folio;
+ return kaddr;
+
+fail:
+ folio_release_kmap(folio, kaddr);
+ return ERR_PTR(-EIO);
+}
+
+/*
+ * NOTE! unlike strncmp, nilfs_match returns 1 for success, 0 for failure.
+ *
+ * len <= NILFS_NAME_LEN and de != NULL are guaranteed by caller.
+ */
+static int
+nilfs_match(int len, const unsigned char *name, struct nilfs_dir_entry *de)
+{
+ if (len != de->name_len)
+ return 0;
+ if (!de->inode)
+ return 0;
+ return !memcmp(name, de->name, len);
+}
+
+/*
+ * p is at least 6 bytes before the end of page
+ */
+static struct nilfs_dir_entry *nilfs_next_entry(struct nilfs_dir_entry *p)
+{
+ return (struct nilfs_dir_entry *)((char *)p +
+ nilfs_rec_len_from_disk(p->rec_len));
+}
+
+static int nilfs_readdir(struct file *file, struct dir_context *ctx)
+{
+ loff_t pos = ctx->pos;
+ struct inode *inode = file_inode(file);
+ struct super_block *sb = inode->i_sb;
+ unsigned int offset = pos & ~PAGE_MASK;
+ unsigned long n = pos >> PAGE_SHIFT;
+ unsigned long npages = dir_pages(inode);
+
+ if (pos > inode->i_size - NILFS_DIR_REC_LEN(1))
+ return 0;
+
+ for ( ; n < npages; n++, offset = 0) {
+ char *kaddr, *limit;
+ struct nilfs_dir_entry *de;
+ struct folio *folio;
+
+ kaddr = nilfs_get_folio(inode, n, &folio);
+ if (IS_ERR(kaddr)) {
+ nilfs_error(sb, "bad page in #%llu", inode->i_ino);
+ ctx->pos += PAGE_SIZE - offset;
+ return -EIO;
+ }
+ de = (struct nilfs_dir_entry *)(kaddr + offset);
+ limit = kaddr + nilfs_last_byte(inode, n) -
+ NILFS_DIR_REC_LEN(1);
+ for ( ; (char *)de <= limit; de = nilfs_next_entry(de)) {
+ if (de->rec_len == 0) {
+ nilfs_error(sb, "zero-length directory entry");
+ folio_release_kmap(folio, kaddr);
+ return -EIO;
+ }
+ if (de->inode) {
+ unsigned char t;
+
+ t = fs_ftype_to_dtype(de->file_type);
+
+ if (!dir_emit(ctx, de->name, de->name_len,
+ le64_to_cpu(de->inode), t)) {
+ folio_release_kmap(folio, kaddr);
+ return 0;
+ }
+ }
+ ctx->pos += nilfs_rec_len_from_disk(de->rec_len);
+ }
+ folio_release_kmap(folio, kaddr);
+ }
+ return 0;
+}
+
+/*
+ * nilfs_find_entry()
+ *
+ * Finds an entry in the specified directory with the wanted name. It
+ * returns the folio in which the entry was found, and the entry itself.
+ * The folio is mapped and unlocked. When the caller is finished with
+ * the entry, it should call folio_release_kmap().
+ *
+ * On failure, returns an error pointer and the caller should ignore foliop.
+ */
+struct nilfs_dir_entry *nilfs_find_entry(struct inode *dir,
+ const struct qstr *qstr, struct folio **foliop)
+{
+ const unsigned char *name = qstr->name;
+ int namelen = qstr->len;
+ unsigned int reclen = NILFS_DIR_REC_LEN(namelen);
+ unsigned long start, n;
+ unsigned long npages = dir_pages(dir);
+ struct nilfs_inode_info *ei = NILFS_I(dir);
+ struct nilfs_dir_entry *de;
+
+ if (npages == 0)
+ goto out;
+
+ start = ei->i_dir_start_lookup;
+ if (start >= npages)
+ start = 0;
+ n = start;
+ do {
+ char *kaddr = nilfs_get_folio(dir, n, foliop);
+
+ if (IS_ERR(kaddr))
+ return ERR_CAST(kaddr);
+
+ de = (struct nilfs_dir_entry *)kaddr;
+ kaddr += nilfs_last_byte(dir, n) - reclen;
+ while ((char *)de <= kaddr) {
+ if (de->rec_len == 0) {
+ nilfs_error(dir->i_sb,
+ "zero-length directory entry");
+ folio_release_kmap(*foliop, kaddr);
+ goto out;
+ }
+ if (nilfs_match(namelen, name, de))
+ goto found;
+ de = nilfs_next_entry(de);
+ }
+ folio_release_kmap(*foliop, kaddr);
+
+ if (++n >= npages)
+ n = 0;
+ /* next folio is past the blocks we've got */
+ if (unlikely(n > (dir->i_blocks >> (PAGE_SHIFT - 9)))) {
+ nilfs_error(dir->i_sb,
+ "dir %llu size %lld exceeds block count %llu",
+ dir->i_ino, dir->i_size,
+ (unsigned long long)dir->i_blocks);
+ goto out;
+ }
+ } while (n != start);
+out:
+ return ERR_PTR(-ENOENT);
+
+found:
+ ei->i_dir_start_lookup = n;
+ return de;
+}
+
+struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct folio **foliop)
+{
+ struct folio *folio;
+ struct nilfs_dir_entry *de, *next_de;
+ size_t limit;
+ char *msg;
+
+ de = nilfs_get_folio(dir, 0, &folio);
+ if (IS_ERR(de))
+ return NULL;
+
+ limit = nilfs_last_byte(dir, 0); /* is a multiple of chunk size */
+ if (unlikely(!limit || le64_to_cpu(de->inode) != dir->i_ino ||
+ !nilfs_match(1, ".", de))) {
+ msg = "missing '.'";
+ goto fail;
+ }
+
+ next_de = nilfs_next_entry(de);
+ /*
+ * If "next_de" has not reached the end of the chunk, there is
+ * at least one more record. Check whether it matches "..".
+ */
+ if (unlikely((char *)next_de == (char *)de + nilfs_chunk_size(dir) ||
+ !nilfs_match(2, "..", next_de))) {
+ msg = "missing '..'";
+ goto fail;
+ }
+ *foliop = folio;
+ return next_de;
+
+fail:
+ nilfs_error(dir->i_sb, "directory #%llu %s", dir->i_ino, msg);
+ folio_release_kmap(folio, de);
+ return NULL;
+}
+
+int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, u64 *ino)
+{
+ struct nilfs_dir_entry *de;
+ struct folio *folio;
+
+ de = nilfs_find_entry(dir, qstr, &folio);
+ if (IS_ERR(de))
+ return PTR_ERR(de);
+
+ *ino = le64_to_cpu(de->inode);
+ folio_release_kmap(folio, de);
+ return 0;
+}
+
+int nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
+ struct folio *folio, struct inode *inode)
+{
+ size_t from = offset_in_folio(folio, de);
+ size_t to = from + nilfs_rec_len_from_disk(de->rec_len);
+ struct address_space *mapping = folio->mapping;
+ int err;
+
+ folio_lock(folio);
+ err = nilfs_prepare_chunk(folio, from, to);
+ if (unlikely(err)) {
+ folio_unlock(folio);
+ return err;
+ }
+ de->inode = cpu_to_le64(inode->i_ino);
+ de->file_type = fs_umode_to_ftype(inode->i_mode);
+ nilfs_commit_chunk(folio, mapping, from, to);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ return 0;
+}
+
+/*
+ * Parent is locked.
+ */
+int nilfs_add_link(struct dentry *dentry, struct inode *inode)
+{
+ struct inode *dir = d_inode(dentry->d_parent);
+ const unsigned char *name = dentry->d_name.name;
+ int namelen = dentry->d_name.len;
+ unsigned int chunk_size = nilfs_chunk_size(dir);
+ unsigned int reclen = NILFS_DIR_REC_LEN(namelen);
+ unsigned short rec_len, name_len;
+ struct folio *folio = NULL;
+ struct nilfs_dir_entry *de;
+ unsigned long npages = dir_pages(dir);
+ unsigned long n;
+ size_t from, to;
+ int err;
+
+ /*
+ * We take care of directory expansion in the same loop.
+ * This code plays outside i_size, so it locks the folio
+ * to protect that region.
+ */
+ for (n = 0; n <= npages; n++) {
+ char *kaddr = nilfs_get_folio(dir, n, &folio);
+ char *dir_end;
+
+ if (IS_ERR(kaddr))
+ return PTR_ERR(kaddr);
+ folio_lock(folio);
+ dir_end = kaddr + nilfs_last_byte(dir, n);
+ de = (struct nilfs_dir_entry *)kaddr;
+ kaddr += folio_size(folio) - reclen;
+ while ((char *)de <= kaddr) {
+ if ((char *)de == dir_end) {
+ /* We hit i_size */
+ name_len = 0;
+ rec_len = chunk_size;
+ de->rec_len = nilfs_rec_len_to_disk(chunk_size);
+ de->inode = 0;
+ goto got_it;
+ }
+ if (de->rec_len == 0) {
+ nilfs_error(dir->i_sb,
+ "zero-length directory entry");
+ err = -EIO;
+ goto out_unlock;
+ }
+ err = -EEXIST;
+ if (nilfs_match(namelen, name, de))
+ goto out_unlock;
+ name_len = NILFS_DIR_REC_LEN(de->name_len);
+ rec_len = nilfs_rec_len_from_disk(de->rec_len);
+ if (!de->inode && rec_len >= reclen)
+ goto got_it;
+ if (rec_len >= name_len + reclen)
+ goto got_it;
+ de = (struct nilfs_dir_entry *)((char *)de + rec_len);
+ }
+ folio_unlock(folio);
+ folio_release_kmap(folio, kaddr);
+ }
+ BUG();
+ return -EINVAL;
+
+got_it:
+ from = offset_in_folio(folio, de);
+ to = from + rec_len;
+ err = nilfs_prepare_chunk(folio, from, to);
+ if (err)
+ goto out_unlock;
+ if (de->inode) {
+ struct nilfs_dir_entry *de1;
+
+ de1 = (struct nilfs_dir_entry *)((char *)de + name_len);
+ de1->rec_len = nilfs_rec_len_to_disk(rec_len - name_len);
+ de->rec_len = nilfs_rec_len_to_disk(name_len);
+ de = de1;
+ }
+ de->name_len = namelen;
+ memcpy(de->name, name, namelen);
+ de->inode = cpu_to_le64(inode->i_ino);
+ de->file_type = fs_umode_to_ftype(inode->i_mode);
+ nilfs_commit_chunk(folio, folio->mapping, from, to);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ nilfs_mark_inode_dirty(dir);
+ /* OFFSET_CACHE */
+out_put:
+ folio_release_kmap(folio, de);
+ return err;
+out_unlock:
+ folio_unlock(folio);
+ goto out_put;
+}
+
+/*
+ * nilfs_delete_entry deletes a directory entry by merging it with the
+ * previous entry. Folio is up-to-date.
+ */
+int nilfs_delete_entry(struct nilfs_dir_entry *dir, struct folio *folio)
+{
+ struct address_space *mapping = folio->mapping;
+ struct inode *inode = mapping->host;
+ char *kaddr = (char *)((unsigned long)dir & ~(folio_size(folio) - 1));
+ size_t from, to;
+ struct nilfs_dir_entry *de, *pde = NULL;
+ int err;
+
+ from = ((char *)dir - kaddr) & ~(nilfs_chunk_size(inode) - 1);
+ to = ((char *)dir - kaddr) + nilfs_rec_len_from_disk(dir->rec_len);
+ de = (struct nilfs_dir_entry *)(kaddr + from);
+
+ while ((char *)de < (char *)dir) {
+ if (de->rec_len == 0) {
+ nilfs_error(inode->i_sb,
+ "zero-length directory entry");
+ err = -EIO;
+ goto out;
+ }
+ pde = de;
+ de = nilfs_next_entry(de);
+ }
+ if (pde)
+ from = (char *)pde - kaddr;
+ folio_lock(folio);
+ err = nilfs_prepare_chunk(folio, from, to);
+ if (unlikely(err)) {
+ folio_unlock(folio);
+ goto out;
+ }
+ if (pde)
+ pde->rec_len = nilfs_rec_len_to_disk(to - from);
+ dir->inode = 0;
+ nilfs_commit_chunk(folio, mapping, from, to);
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+out:
+ return err;
+}
+
+/*
+ * Set the first fragment of directory.
+ */
+int nilfs_make_empty(struct inode *inode, struct inode *parent)
+{
+ struct address_space *mapping = inode->i_mapping;
+ struct folio *folio = filemap_grab_folio(mapping, 0);
+ unsigned int chunk_size = nilfs_chunk_size(inode);
+ struct nilfs_dir_entry *de;
+ int err;
+ void *kaddr;
+
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+
+ err = nilfs_prepare_chunk(folio, 0, chunk_size);
+ if (unlikely(err)) {
+ folio_unlock(folio);
+ goto fail;
+ }
+ kaddr = kmap_local_folio(folio, 0);
+ memset(kaddr, 0, chunk_size);
+ de = (struct nilfs_dir_entry *)kaddr;
+ de->name_len = 1;
+ de->rec_len = nilfs_rec_len_to_disk(NILFS_DIR_REC_LEN(1));
+ memcpy(de->name, ".\0\0", 4);
+ de->inode = cpu_to_le64(inode->i_ino);
+ de->file_type = fs_umode_to_ftype(inode->i_mode);
+
+ de = (struct nilfs_dir_entry *)(kaddr + NILFS_DIR_REC_LEN(1));
+ de->name_len = 2;
+ de->rec_len = nilfs_rec_len_to_disk(chunk_size - NILFS_DIR_REC_LEN(1));
+ de->inode = cpu_to_le64(parent->i_ino);
+ memcpy(de->name, "..\0", 4);
+ de->file_type = fs_umode_to_ftype(inode->i_mode);
+ kunmap_local(kaddr);
+ nilfs_commit_chunk(folio, mapping, 0, chunk_size);
+fail:
+ folio_put(folio);
+ return err;
+}
+
+/*
+ * routine to check that the specified directory is empty (for rmdir)
+ */
+int nilfs_empty_dir(struct inode *inode)
+{
+ struct folio *folio = NULL;
+ char *kaddr;
+ unsigned long i, npages = dir_pages(inode);
+
+ for (i = 0; i < npages; i++) {
+ struct nilfs_dir_entry *de;
+
+ kaddr = nilfs_get_folio(inode, i, &folio);
+ if (IS_ERR(kaddr))
+ return 0;
+
+ de = (struct nilfs_dir_entry *)kaddr;
+ kaddr += nilfs_last_byte(inode, i) - NILFS_DIR_REC_LEN(1);
+
+ while ((char *)de <= kaddr) {
+ if (de->rec_len == 0) {
+ nilfs_error(inode->i_sb,
+ "zero-length directory entry (kaddr=%p, de=%p)",
+ kaddr, de);
+ goto not_empty;
+ }
+ if (de->inode != 0) {
+ /* check for . and .. */
+ if (de->name[0] != '.')
+ goto not_empty;
+ if (de->name_len > 2)
+ goto not_empty;
+ if (de->name_len < 2) {
+ if (de->inode !=
+ cpu_to_le64(inode->i_ino))
+ goto not_empty;
+ } else if (de->name[1] != '.')
+ goto not_empty;
+ }
+ de = nilfs_next_entry(de);
+ }
+ folio_release_kmap(folio, kaddr);
+ }
+ return 1;
+
+not_empty:
+ folio_release_kmap(folio, kaddr);
+ return 0;
+}
+
+const struct file_operations nilfs_dir_operations = {
+ .llseek = generic_file_llseek,
+ .read = generic_read_dir,
+ .iterate_shared = nilfs_readdir,
+ .unlocked_ioctl = nilfs_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = nilfs_compat_ioctl,
+#endif /* CONFIG_COMPAT */
+ .fsync = nilfs_sync_file,
+ .setlease = generic_setlease,
+};
diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c
new file mode 100644
index 000000000..b8643d3aa
--- /dev/null
+++ b/fs/nilfs2/direct.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS direct block pointer.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/errno.h>
+#include "nilfs.h"
+#include "page.h"
+#include "direct.h"
+#include "alloc.h"
+#include "dat.h"
+
+static inline __le64 *nilfs_direct_dptrs(const struct nilfs_bmap *direct)
+{
+ return (__le64 *)
+ ((struct nilfs_direct_node *)direct->b_u.u_data + 1);
+}
+
+static inline __u64
+nilfs_direct_get_ptr(const struct nilfs_bmap *direct, __u64 key)
+{
+ return le64_to_cpu(*(nilfs_direct_dptrs(direct) + key));
+}
+
+static inline void nilfs_direct_set_ptr(struct nilfs_bmap *direct,
+ __u64 key, __u64 ptr)
+{
+ *(nilfs_direct_dptrs(direct) + key) = cpu_to_le64(ptr);
+}
+
+static int nilfs_direct_lookup(const struct nilfs_bmap *direct,
+ __u64 key, int level, __u64 *ptrp)
+{
+ __u64 ptr;
+
+ if (key > NILFS_DIRECT_KEY_MAX || level != 1)
+ return -ENOENT;
+ ptr = nilfs_direct_get_ptr(direct, key);
+ if (ptr == NILFS_BMAP_INVALID_PTR)
+ return -ENOENT;
+
+ *ptrp = ptr;
+ return 0;
+}
+
+static int nilfs_direct_lookup_contig(const struct nilfs_bmap *direct,
+ __u64 key, __u64 *ptrp,
+ unsigned int maxblocks)
+{
+ struct inode *dat = NULL;
+ __u64 ptr, ptr2;
+ sector_t blocknr;
+ int ret, cnt;
+
+ if (key > NILFS_DIRECT_KEY_MAX)
+ return -ENOENT;
+ ptr = nilfs_direct_get_ptr(direct, key);
+ if (ptr == NILFS_BMAP_INVALID_PTR)
+ return -ENOENT;
+
+ if (NILFS_BMAP_USE_VBN(direct)) {
+ dat = nilfs_bmap_get_dat(direct);
+ ret = nilfs_dat_translate(dat, ptr, &blocknr);
+ if (ret < 0)
+ goto dat_error;
+ ptr = blocknr;
+ }
+
+ maxblocks = min_t(unsigned int, maxblocks,
+ NILFS_DIRECT_KEY_MAX - key + 1);
+ for (cnt = 1; cnt < maxblocks &&
+ (ptr2 = nilfs_direct_get_ptr(direct, key + cnt)) !=
+ NILFS_BMAP_INVALID_PTR;
+ cnt++) {
+ if (dat) {
+ ret = nilfs_dat_translate(dat, ptr2, &blocknr);
+ if (ret < 0)
+ goto dat_error;
+ ptr2 = blocknr;
+ }
+ if (ptr2 != ptr + cnt)
+ break;
+ }
+ *ptrp = ptr;
+ return cnt;
+
+ dat_error:
+ if (ret == -ENOENT)
+ ret = -EINVAL; /* Notify bmap layer of metadata corruption */
+ return ret;
+}
+
+static __u64
+nilfs_direct_find_target_v(const struct nilfs_bmap *direct, __u64 key)
+{
+ __u64 ptr;
+
+ ptr = nilfs_bmap_find_target_seq(direct, key);
+ if (ptr != NILFS_BMAP_INVALID_PTR)
+ /* sequential access */
+ return ptr;
+
+ /* block group */
+ return nilfs_bmap_find_target_in_group(direct);
+}
+
+static int nilfs_direct_insert(struct nilfs_bmap *bmap, __u64 key, __u64 ptr)
+{
+ union nilfs_bmap_ptr_req req;
+ struct inode *dat = NULL;
+ struct buffer_head *bh;
+ int ret;
+
+ if (key > NILFS_DIRECT_KEY_MAX)
+ return -ENOENT;
+ if (nilfs_direct_get_ptr(bmap, key) != NILFS_BMAP_INVALID_PTR)
+ return -EEXIST;
+
+ if (NILFS_BMAP_USE_VBN(bmap)) {
+ req.bpr_ptr = nilfs_direct_find_target_v(bmap, key);
+ dat = nilfs_bmap_get_dat(bmap);
+ }
+ ret = nilfs_bmap_prepare_alloc_ptr(bmap, &req, dat);
+ if (!ret) {
+ /* ptr must be a pointer to a buffer head. */
+ bh = (struct buffer_head *)((unsigned long)ptr);
+ set_buffer_nilfs_volatile(bh);
+
+ nilfs_bmap_commit_alloc_ptr(bmap, &req, dat);
+ nilfs_direct_set_ptr(bmap, key, req.bpr_ptr);
+
+ if (!nilfs_bmap_dirty(bmap))
+ nilfs_bmap_set_dirty(bmap);
+
+ if (NILFS_BMAP_USE_VBN(bmap))
+ nilfs_bmap_set_target_v(bmap, key, req.bpr_ptr);
+
+ nilfs_inode_add_blocks(bmap->b_inode, 1);
+ }
+ return ret;
+}
+
+static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key, bool deform)
+{
+ union nilfs_bmap_ptr_req req;
+ struct inode *dat;
+ int ret;
+
+ if (key > NILFS_DIRECT_KEY_MAX ||
+ nilfs_direct_get_ptr(bmap, key) == NILFS_BMAP_INVALID_PTR)
+ return -ENOENT;
+
+ dat = NILFS_BMAP_USE_VBN(bmap) ? nilfs_bmap_get_dat(bmap) : NULL;
+ req.bpr_ptr = nilfs_direct_get_ptr(bmap, key);
+
+ ret = nilfs_bmap_prepare_end_ptr(bmap, &req, dat);
+ if (!ret) {
+ nilfs_bmap_commit_end_ptr(bmap, &req, dat);
+ nilfs_direct_set_ptr(bmap, key, NILFS_BMAP_INVALID_PTR);
+ nilfs_inode_sub_blocks(bmap->b_inode, 1);
+ }
+ return ret;
+}
+
+static int nilfs_direct_seek_key(const struct nilfs_bmap *direct, __u64 start,
+ __u64 *keyp)
+{
+ __u64 key;
+
+ for (key = start; key <= NILFS_DIRECT_KEY_MAX; key++) {
+ if (nilfs_direct_get_ptr(direct, key) !=
+ NILFS_BMAP_INVALID_PTR) {
+ *keyp = key;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static int nilfs_direct_last_key(const struct nilfs_bmap *direct, __u64 *keyp)
+{
+ __u64 key, lastkey;
+
+ lastkey = NILFS_DIRECT_KEY_MAX + 1;
+ for (key = NILFS_DIRECT_KEY_MIN; key <= NILFS_DIRECT_KEY_MAX; key++)
+ if (nilfs_direct_get_ptr(direct, key) !=
+ NILFS_BMAP_INVALID_PTR)
+ lastkey = key;
+
+ if (lastkey == NILFS_DIRECT_KEY_MAX + 1)
+ return -ENOENT;
+
+ *keyp = lastkey;
+
+ return 0;
+}
+
+static int nilfs_direct_check_insert(const struct nilfs_bmap *bmap, __u64 key)
+{
+ return key > NILFS_DIRECT_KEY_MAX;
+}
+
+static int nilfs_direct_gather_data(struct nilfs_bmap *direct,
+ __u64 *keys, __u64 *ptrs, int nitems)
+{
+ __u64 key;
+ __u64 ptr;
+ int n;
+
+ if (nitems > NILFS_DIRECT_NBLOCKS)
+ nitems = NILFS_DIRECT_NBLOCKS;
+ n = 0;
+ for (key = 0; key < nitems; key++) {
+ ptr = nilfs_direct_get_ptr(direct, key);
+ if (ptr != NILFS_BMAP_INVALID_PTR) {
+ keys[n] = key;
+ ptrs[n] = ptr;
+ n++;
+ }
+ }
+ return n;
+}
+
+int nilfs_direct_delete_and_convert(struct nilfs_bmap *bmap,
+ __u64 key, __u64 *keys, __u64 *ptrs, int n)
+{
+ __le64 *dptrs;
+ int ret, i, j;
+
+ /* no need to allocate any resource for conversion */
+
+ /* delete */
+ ret = bmap->b_ops->bop_delete(bmap, key, true);
+ if (ret < 0)
+ return ret;
+
+ /* free resources */
+ if (bmap->b_ops->bop_clear != NULL)
+ bmap->b_ops->bop_clear(bmap);
+
+ /* convert */
+ dptrs = nilfs_direct_dptrs(bmap);
+ for (i = 0, j = 0; i < NILFS_DIRECT_NBLOCKS; i++) {
+ if ((j < n) && (i == keys[j])) {
+ dptrs[i] = (i != key) ?
+ cpu_to_le64(ptrs[j]) :
+ NILFS_BMAP_INVALID_PTR;
+ j++;
+ } else
+ dptrs[i] = NILFS_BMAP_INVALID_PTR;
+ }
+
+ nilfs_direct_init(bmap);
+ return 0;
+}
+
+static int nilfs_direct_propagate(struct nilfs_bmap *bmap,
+ struct buffer_head *bh)
+{
+ struct nilfs_palloc_req oldreq, newreq;
+ struct inode *dat;
+ __u64 key;
+ __u64 ptr;
+ int ret;
+
+ if (!NILFS_BMAP_USE_VBN(bmap))
+ return 0;
+
+ dat = nilfs_bmap_get_dat(bmap);
+ key = nilfs_bmap_data_get_key(bmap, bh);
+ ptr = nilfs_direct_get_ptr(bmap, key);
+ if (ptr == NILFS_BMAP_INVALID_PTR)
+ return -EINVAL;
+
+ if (!buffer_nilfs_volatile(bh)) {
+ oldreq.pr_entry_nr = ptr;
+ newreq.pr_entry_nr = ptr;
+ ret = nilfs_dat_prepare_update(dat, &oldreq, &newreq);
+ if (ret < 0)
+ return ret;
+ nilfs_dat_commit_update(dat, &oldreq, &newreq,
+ bmap->b_ptr_type == NILFS_BMAP_PTR_VS);
+ set_buffer_nilfs_volatile(bh);
+ nilfs_direct_set_ptr(bmap, key, newreq.pr_entry_nr);
+ } else
+ ret = nilfs_dat_mark_dirty(dat, ptr);
+
+ return ret;
+}
+
+static int nilfs_direct_assign_v(struct nilfs_bmap *direct,
+ __u64 key, __u64 ptr,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ struct inode *dat = nilfs_bmap_get_dat(direct);
+ union nilfs_bmap_ptr_req req;
+ int ret;
+
+ req.bpr_ptr = ptr;
+ ret = nilfs_dat_prepare_start(dat, &req.bpr_req);
+ if (!ret) {
+ nilfs_dat_commit_start(dat, &req.bpr_req, blocknr);
+ binfo->bi_v.bi_vblocknr = cpu_to_le64(ptr);
+ binfo->bi_v.bi_blkoff = cpu_to_le64(key);
+ }
+ return ret;
+}
+
+static int nilfs_direct_assign_p(struct nilfs_bmap *direct,
+ __u64 key, __u64 ptr,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ nilfs_direct_set_ptr(direct, key, blocknr);
+
+ binfo->bi_dat.bi_blkoff = cpu_to_le64(key);
+ binfo->bi_dat.bi_level = 0;
+ memset(binfo->bi_dat.bi_pad, 0, sizeof(binfo->bi_dat.bi_pad));
+
+ return 0;
+}
+
+static int nilfs_direct_assign(struct nilfs_bmap *bmap,
+ struct buffer_head **bh,
+ sector_t blocknr,
+ union nilfs_binfo *binfo)
+{
+ __u64 key;
+ __u64 ptr;
+
+ key = nilfs_bmap_data_get_key(bmap, *bh);
+ if (unlikely(key > NILFS_DIRECT_KEY_MAX)) {
+ nilfs_crit(bmap->b_inode->i_sb,
+ "%s (ino=%llu): invalid key: %llu",
+ __func__,
+ bmap->b_inode->i_ino, (unsigned long long)key);
+ return -EINVAL;
+ }
+ ptr = nilfs_direct_get_ptr(bmap, key);
+ if (unlikely(ptr == NILFS_BMAP_INVALID_PTR)) {
+ nilfs_crit(bmap->b_inode->i_sb,
+ "%s (ino=%llu): invalid pointer: %llu",
+ __func__,
+ bmap->b_inode->i_ino, (unsigned long long)ptr);
+ return -EINVAL;
+ }
+
+ return NILFS_BMAP_USE_VBN(bmap) ?
+ nilfs_direct_assign_v(bmap, key, ptr, bh, blocknr, binfo) :
+ nilfs_direct_assign_p(bmap, key, ptr, bh, blocknr, binfo);
+}
+
+static const struct nilfs_bmap_operations nilfs_direct_ops = {
+ .bop_lookup = nilfs_direct_lookup,
+ .bop_lookup_contig = nilfs_direct_lookup_contig,
+ .bop_insert = nilfs_direct_insert,
+ .bop_delete = nilfs_direct_delete,
+ .bop_clear = NULL,
+
+ .bop_propagate = nilfs_direct_propagate,
+
+ .bop_lookup_dirty_buffers = NULL,
+
+ .bop_assign = nilfs_direct_assign,
+ .bop_mark = NULL,
+
+ .bop_seek_key = nilfs_direct_seek_key,
+ .bop_last_key = nilfs_direct_last_key,
+
+ .bop_check_insert = nilfs_direct_check_insert,
+ .bop_check_delete = NULL,
+ .bop_gather_data = nilfs_direct_gather_data,
+};
+
+
+int nilfs_direct_init(struct nilfs_bmap *bmap)
+{
+ bmap->b_ops = &nilfs_direct_ops;
+ return 0;
+}
diff --git a/fs/nilfs2/direct.h b/fs/nilfs2/direct.h
new file mode 100644
index 000000000..b7ca89626
--- /dev/null
+++ b/fs/nilfs2/direct.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS direct block pointer.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_DIRECT_H
+#define _NILFS_DIRECT_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include "bmap.h"
+
+
+#define NILFS_DIRECT_NBLOCKS (NILFS_BMAP_SIZE / sizeof(__le64) - 1)
+#define NILFS_DIRECT_KEY_MIN 0
+#define NILFS_DIRECT_KEY_MAX (NILFS_DIRECT_NBLOCKS - 1)
+
+
+int nilfs_direct_init(struct nilfs_bmap *);
+int nilfs_direct_delete_and_convert(struct nilfs_bmap *, __u64, __u64 *,
+ __u64 *, int);
+
+
+#endif /* _NILFS_DIRECT_H */
diff --git a/fs/nilfs2/export.h b/fs/nilfs2/export.h
new file mode 100644
index 000000000..d29fd837c
--- /dev/null
+++ b/fs/nilfs2/export.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef NILFS_EXPORT_H
+#define NILFS_EXPORT_H
+
+#include <linux/exportfs.h>
+
+extern const struct export_operations nilfs_export_ops;
+
+/**
+ * struct nilfs_fid - NILFS file id type
+ * @cno: checkpoint number
+ * @ino: inode number
+ * @gen: file generation (version) for NFS
+ * @parent_gen: parent generation (version) for NFS
+ * @parent_ino: parent inode number
+ */
+struct nilfs_fid {
+ u64 cno;
+ u64 ino;
+ u32 gen;
+
+ u32 parent_gen;
+ u64 parent_ino;
+} __packed;
+
+#endif
diff --git a/fs/nilfs2/file.c b/fs/nilfs2/file.c
new file mode 100644
index 000000000..f93b68c48
--- /dev/null
+++ b/fs/nilfs2/file.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS regular file handling primitives including fsync().
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Amagai Yoshiji and Ryusuke Konishi.
+ */
+
+#include <linux/fs.h>
+#include <linux/filelock.h>
+#include <linux/mm.h>
+#include <linux/writeback.h>
+#include "nilfs.h"
+#include "segment.h"
+
+int nilfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
+{
+ /*
+ * Called from fsync() system call
+ * This is the only entry point that can catch write and synch
+ * timing for both data blocks and intermediate blocks.
+ *
+ * This function should be implemented when the writeback function
+ * will be implemented.
+ */
+ struct the_nilfs *nilfs;
+ struct inode *inode = file->f_mapping->host;
+ int err = 0;
+
+ if (nilfs_inode_dirty(inode)) {
+ if (datasync)
+ err = nilfs_construct_dsync_segment(inode->i_sb, inode,
+ start, end);
+ else
+ err = nilfs_construct_segment(inode->i_sb);
+ }
+
+ nilfs = inode->i_sb->s_fs_info;
+ if (!err)
+ err = nilfs_flush_device(nilfs);
+
+ return err;
+}
+
+static vm_fault_t nilfs_page_mkwrite(struct vm_fault *vmf)
+{
+ struct vm_area_struct *vma = vmf->vma;
+ struct folio *folio = page_folio(vmf->page);
+ struct inode *inode = file_inode(vma->vm_file);
+ struct nilfs_transaction_info ti;
+ struct buffer_head *bh, *head;
+ int ret = 0;
+
+ if (unlikely(nilfs_near_disk_full(inode->i_sb->s_fs_info)))
+ return VM_FAULT_SIGBUS; /* -ENOSPC */
+
+ sb_start_pagefault(inode->i_sb);
+ folio_lock(folio);
+ if (folio->mapping != inode->i_mapping ||
+ folio_pos(folio) >= i_size_read(inode) ||
+ !folio_test_uptodate(folio)) {
+ folio_unlock(folio);
+ ret = -EFAULT; /* make the VM retry the fault */
+ goto out;
+ }
+
+ /*
+ * check to see if the folio is mapped already (no holes)
+ */
+ if (folio_test_mappedtodisk(folio))
+ goto mapped;
+
+ head = folio_buffers(folio);
+ if (head) {
+ int fully_mapped = 1;
+
+ bh = head;
+ do {
+ if (!buffer_mapped(bh)) {
+ fully_mapped = 0;
+ break;
+ }
+ } while (bh = bh->b_this_page, bh != head);
+
+ if (fully_mapped) {
+ folio_set_mappedtodisk(folio);
+ goto mapped;
+ }
+ }
+ folio_unlock(folio);
+
+ /*
+ * fill hole blocks
+ */
+ ret = nilfs_transaction_begin(inode->i_sb, &ti, 1);
+ /* never returns -ENOMEM, but may return -ENOSPC */
+ if (unlikely(ret))
+ goto out;
+
+ file_update_time(vma->vm_file);
+ ret = block_page_mkwrite(vma, vmf, nilfs_get_block);
+ if (ret) {
+ nilfs_transaction_abort(inode->i_sb);
+ goto out;
+ }
+ nilfs_set_file_dirty(inode, 1 << (PAGE_SHIFT - inode->i_blkbits));
+ nilfs_transaction_commit(inode->i_sb);
+
+ mapped:
+ /*
+ * Since checksumming including data blocks is performed to determine
+ * the validity of the log to be written and used for recovery, it is
+ * necessary to wait for writeback to finish here, regardless of the
+ * stable write requirement of the backing device.
+ */
+ folio_wait_writeback(folio);
+ out:
+ sb_end_pagefault(inode->i_sb);
+ return vmf_fs_error(ret);
+}
+
+static const struct vm_operations_struct nilfs_file_vm_ops = {
+ .fault = filemap_fault,
+ .map_pages = filemap_map_pages,
+ .page_mkwrite = nilfs_page_mkwrite,
+};
+
+static int nilfs_file_mmap_prepare(struct vm_area_desc *desc)
+{
+ file_accessed(desc->file);
+ desc->vm_ops = &nilfs_file_vm_ops;
+ return 0;
+}
+
+/*
+ * We have mostly NULL's here: the current defaults are ok for
+ * the nilfs filesystem.
+ */
+const struct file_operations nilfs_file_operations = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .write_iter = generic_file_write_iter,
+ .unlocked_ioctl = nilfs_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = nilfs_compat_ioctl,
+#endif /* CONFIG_COMPAT */
+ .mmap_prepare = nilfs_file_mmap_prepare,
+ .open = generic_file_open,
+ /* .release = nilfs_release_file, */
+ .fsync = nilfs_sync_file,
+ .splice_read = filemap_splice_read,
+ .splice_write = iter_file_splice_write,
+ .setlease = generic_setlease,
+};
+
+const struct inode_operations nilfs_file_inode_operations = {
+ .setattr = nilfs_setattr,
+ .permission = nilfs_permission,
+ .fiemap = nilfs_fiemap,
+ .fileattr_get = nilfs_fileattr_get,
+ .fileattr_set = nilfs_fileattr_set,
+};
+
+/* end of file */
diff --git a/fs/nilfs2/gcinode.c b/fs/nilfs2/gcinode.c
new file mode 100644
index 000000000..85379ac23
--- /dev/null
+++ b/fs/nilfs2/gcinode.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Dummy inodes to buffer blocks for garbage collection
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Seiji Kihara, Amagai Yoshiji, and Ryusuke Konishi.
+ * Revised by Ryusuke Konishi.
+ *
+ */
+/*
+ * This file adds the cache of on-disk blocks to be moved in garbage
+ * collection. The disk blocks are held with dummy inodes (called
+ * gcinodes), and this file provides lookup function of the dummy
+ * inodes and their buffer read function.
+ *
+ * Buffers and pages held by the dummy inodes will be released each
+ * time after they are copied to a new log. Dirty blocks made on the
+ * current generation and the blocks to be moved by GC never overlap
+ * because the dirty blocks make a new generation; they rather must be
+ * written individually.
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/mpage.h>
+#include <linux/hash.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+#include "nilfs.h"
+#include "btree.h"
+#include "btnode.h"
+#include "page.h"
+#include "mdt.h"
+#include "dat.h"
+#include "ifile.h"
+
+/*
+ * nilfs_gccache_submit_read_data() - add data buffer and submit read request
+ * @inode - gc inode
+ * @blkoff - dummy offset treated as the key for the page cache
+ * @pbn - physical block number of the block
+ * @vbn - virtual block number of the block, 0 for non-virtual block
+ * @out_bh - indirect pointer to a buffer_head struct to receive the results
+ *
+ * Description: nilfs_gccache_submit_read_data() registers the data buffer
+ * specified by @pbn to the GC pagecache with the key @blkoff.
+ * This function sets @vbn (@pbn if @vbn is zero) in b_blocknr of the buffer.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - The block specified with @pbn does not exist.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_gccache_submit_read_data(struct inode *inode, sector_t blkoff,
+ sector_t pbn, __u64 vbn,
+ struct buffer_head **out_bh)
+{
+ struct buffer_head *bh;
+ int err;
+
+ bh = nilfs_grab_buffer(inode, inode->i_mapping, blkoff, 0);
+ if (unlikely(!bh))
+ return -ENOMEM;
+
+ if (buffer_uptodate(bh))
+ goto out;
+
+ if (pbn == 0) {
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+
+ err = nilfs_dat_translate(nilfs->ns_dat, vbn, &pbn);
+ if (unlikely(err)) /* -EIO, -ENOMEM, -ENOENT */
+ goto failed;
+ }
+
+ lock_buffer(bh);
+ if (buffer_uptodate(bh)) {
+ unlock_buffer(bh);
+ goto out;
+ }
+
+ if (!buffer_mapped(bh))
+ set_buffer_mapped(bh);
+ bh->b_blocknr = pbn;
+ bh_submit(bh, REQ_OP_READ, bh_end_read);
+ if (vbn)
+ bh->b_blocknr = vbn;
+ out:
+ err = 0;
+ *out_bh = bh;
+
+ failed:
+ folio_unlock(bh->b_folio);
+ folio_put(bh->b_folio);
+ if (unlikely(err))
+ brelse(bh);
+ return err;
+}
+
+/*
+ * nilfs_gccache_submit_read_node() - add node buffer and submit read request
+ * @inode - gc inode
+ * @pbn - physical block number for the block
+ * @vbn - virtual block number for the block
+ * @out_bh - indirect pointer to a buffer_head struct to receive the results
+ *
+ * Description: nilfs_gccache_submit_read_node() registers the node buffer
+ * specified by @vbn to the GC pagecache. @pbn can be supplied by the
+ * caller to avoid translation of the disk block address.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Invalid virtual block address.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_gccache_submit_read_node(struct inode *inode, sector_t pbn,
+ __u64 vbn, struct buffer_head **out_bh)
+{
+ struct inode *btnc_inode = NILFS_I(inode)->i_assoc_inode;
+ int ret;
+
+ ret = nilfs_btnode_submit_block(btnc_inode->i_mapping, vbn ? : pbn, pbn,
+ REQ_OP_READ, out_bh, &pbn);
+ if (ret == -EEXIST) /* internal code (cache hit) */
+ ret = 0;
+ return ret;
+}
+
+int nilfs_gccache_wait_and_mark_dirty(struct buffer_head *bh)
+{
+ wait_on_buffer(bh);
+ if (!buffer_uptodate(bh)) {
+ struct inode *inode = bh->b_folio->mapping->host;
+
+ nilfs_err(inode->i_sb,
+ "I/O error reading %s block for GC (ino=%llu, vblocknr=%llu)",
+ buffer_nilfs_node(bh) ? "node" : "data",
+ inode->i_ino, (unsigned long long)bh->b_blocknr);
+ return -EIO;
+ }
+ if (buffer_dirty(bh))
+ return -EEXIST;
+
+ if (buffer_nilfs_node(bh) && nilfs_btree_broken_node_block(bh)) {
+ clear_buffer_uptodate(bh);
+ return -EIO;
+ }
+ mark_buffer_dirty(bh);
+ return 0;
+}
+
+int nilfs_init_gcinode(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+
+ inode->i_mode = S_IFREG;
+ mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS);
+ inode->i_mapping->a_ops = &nilfs_buffer_cache_aops;
+
+ ii->i_flags = 0;
+ nilfs_bmap_init_gc(ii->i_bmap);
+
+ return nilfs_attach_btree_node_cache(inode);
+}
+
+/**
+ * nilfs_remove_all_gcinodes() - remove all unprocessed gc inodes
+ * @nilfs: NILFS filesystem instance
+ */
+void nilfs_remove_all_gcinodes(struct the_nilfs *nilfs)
+{
+ struct list_head *head = &nilfs->ns_gc_inodes;
+ struct nilfs_inode_info *ii;
+
+ while (!list_empty(head)) {
+ ii = list_first_entry(head, struct nilfs_inode_info, i_dirty);
+ list_del_init(&ii->i_dirty);
+ truncate_inode_pages(&ii->vfs_inode.i_data, 0);
+ nilfs_btnode_cache_clear(ii->i_assoc_inode->i_mapping);
+ iput(&ii->vfs_inode);
+ }
+}
diff --git a/fs/nilfs2/ifile.c b/fs/nilfs2/ifile.c
new file mode 100644
index 000000000..c99e1e635
--- /dev/null
+++ b/fs/nilfs2/ifile.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS inode file
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Amagai Yoshiji.
+ * Revised by Ryusuke Konishi.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include "nilfs.h"
+#include "mdt.h"
+#include "alloc.h"
+#include "ifile.h"
+#include "cpfile.h"
+
+/**
+ * struct nilfs_ifile_info - on-memory private data of ifile
+ * @mi: on-memory private data of metadata file
+ * @palloc_cache: persistent object allocator cache of ifile
+ */
+struct nilfs_ifile_info {
+ struct nilfs_mdt_info mi;
+ struct nilfs_palloc_cache palloc_cache;
+};
+
+static inline struct nilfs_ifile_info *NILFS_IFILE_I(struct inode *ifile)
+{
+ return (struct nilfs_ifile_info *)NILFS_MDT(ifile);
+}
+
+/**
+ * nilfs_ifile_create_inode - create a new disk inode
+ * @ifile: ifile inode
+ * @out_ino: pointer to a variable to store inode number
+ * @out_bh: buffer_head contains newly allocated disk inode
+ *
+ * nilfs_ifile_create_inode() allocates a new inode in the ifile metadata
+ * file and stores the inode number in the variable pointed to by @out_ino,
+ * as well as storing the ifile's buffer with the disk inode in the location
+ * pointed to by @out_bh.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No inode left.
+ */
+int nilfs_ifile_create_inode(struct inode *ifile, u64 *out_ino,
+ struct buffer_head **out_bh)
+{
+ struct nilfs_palloc_req req;
+ int ret;
+
+ req.pr_entry_nr = NILFS_FIRST_INO(ifile->i_sb);
+ req.pr_entry_bh = NULL;
+
+ ret = nilfs_palloc_prepare_alloc_entry(ifile, &req, false);
+ if (!ret) {
+ ret = nilfs_palloc_get_entry_block(ifile, req.pr_entry_nr, 1,
+ &req.pr_entry_bh);
+ if (ret < 0)
+ nilfs_palloc_abort_alloc_entry(ifile, &req);
+ }
+ if (ret < 0) {
+ brelse(req.pr_entry_bh);
+ return ret;
+ }
+ nilfs_palloc_commit_alloc_entry(ifile, &req);
+ mark_buffer_dirty(req.pr_entry_bh);
+ nilfs_mdt_mark_dirty(ifile);
+ *out_ino = req.pr_entry_nr;
+ *out_bh = req.pr_entry_bh;
+ return 0;
+}
+
+/**
+ * nilfs_ifile_delete_inode - delete a disk inode
+ * @ifile: ifile inode
+ * @ino: inode number
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Inode number unallocated.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_ifile_delete_inode(struct inode *ifile, u64 ino)
+{
+ struct nilfs_palloc_req req = {
+ .pr_entry_nr = ino, .pr_entry_bh = NULL
+ };
+ struct nilfs_inode *raw_inode;
+ size_t offset;
+ int ret;
+
+ ret = nilfs_palloc_prepare_free_entry(ifile, &req);
+ if (!ret) {
+ ret = nilfs_palloc_get_entry_block(ifile, req.pr_entry_nr, 0,
+ &req.pr_entry_bh);
+ if (ret < 0)
+ nilfs_palloc_abort_free_entry(ifile, &req);
+ }
+ if (ret < 0) {
+ brelse(req.pr_entry_bh);
+ return ret;
+ }
+
+ offset = nilfs_palloc_entry_offset(ifile, req.pr_entry_nr,
+ req.pr_entry_bh);
+ raw_inode = kmap_local_folio(req.pr_entry_bh->b_folio, offset);
+ raw_inode->i_flags = 0;
+ kunmap_local(raw_inode);
+
+ mark_buffer_dirty(req.pr_entry_bh);
+ brelse(req.pr_entry_bh);
+
+ nilfs_palloc_commit_free_entry(ifile, &req);
+
+ return 0;
+}
+
+int nilfs_ifile_get_inode_block(struct inode *ifile, u64 ino,
+ struct buffer_head **out_bh)
+{
+ struct super_block *sb = ifile->i_sb;
+ int err;
+
+ if (unlikely(!NILFS_VALID_INODE(sb, ino))) {
+ nilfs_error(sb, "bad inode number: %llu", ino);
+ return -EINVAL;
+ }
+
+ err = nilfs_palloc_get_entry_block(ifile, ino, 0, out_bh);
+ if (unlikely(err))
+ nilfs_warn(sb, "error %d reading inode: ino=%llu", err, ino);
+ return err;
+}
+
+/**
+ * nilfs_ifile_count_free_inodes - calculate free inodes count
+ * @ifile: ifile inode
+ * @nmaxinodes: current maximum of available inodes count [out]
+ * @nfreeinodes: free inodes count [out]
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_ifile_count_free_inodes(struct inode *ifile,
+ u64 *nmaxinodes, u64 *nfreeinodes)
+{
+ u64 nused;
+ int err;
+
+ *nmaxinodes = 0;
+ *nfreeinodes = 0;
+
+ nused = atomic64_read(&NILFS_I(ifile)->i_root->inodes_count);
+ err = nilfs_palloc_count_max_entries(ifile, nused, nmaxinodes);
+ if (likely(!err))
+ *nfreeinodes = *nmaxinodes - nused;
+ return err;
+}
+
+/**
+ * nilfs_ifile_read - read or get ifile inode
+ * @sb: super block instance
+ * @root: root object
+ * @cno: number of checkpoint entry to read
+ * @inode_size: size of an inode
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid checkpoint.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EIO - I/O error (including metadata corruption).
+ */
+int nilfs_ifile_read(struct super_block *sb, struct nilfs_root *root,
+ __u64 cno, size_t inode_size)
+{
+ struct the_nilfs *nilfs;
+ struct inode *ifile;
+ int err;
+
+ ifile = nilfs_iget_locked(sb, root, NILFS_IFILE_INO);
+ if (unlikely(!ifile))
+ return -ENOMEM;
+ if (!(inode_state_read_once(ifile) & I_NEW))
+ goto out;
+
+ err = nilfs_mdt_init(ifile, NILFS_MDT_GFP,
+ sizeof(struct nilfs_ifile_info));
+ if (err)
+ goto failed;
+
+ err = nilfs_palloc_init_blockgroup(ifile, inode_size);
+ if (err)
+ goto failed;
+
+ nilfs_palloc_setup_cache(ifile, &NILFS_IFILE_I(ifile)->palloc_cache);
+
+ nilfs = sb->s_fs_info;
+ err = nilfs_cpfile_read_checkpoint(nilfs->ns_cpfile, cno, root, ifile);
+ if (err)
+ goto failed;
+
+ unlock_new_inode(ifile);
+ out:
+ return 0;
+ failed:
+ iget_failed(ifile);
+ return err;
+}
diff --git a/fs/nilfs2/ifile.h b/fs/nilfs2/ifile.h
new file mode 100644
index 000000000..d38a46f5a
--- /dev/null
+++ b/fs/nilfs2/ifile.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS inode file
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Amagai Yoshiji.
+ * Revised by Ryusuke Konishi.
+ *
+ */
+
+#ifndef _NILFS_IFILE_H
+#define _NILFS_IFILE_H
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include "mdt.h"
+#include "alloc.h"
+
+
+static inline struct nilfs_inode *
+nilfs_ifile_map_inode(struct inode *ifile, u64 ino, struct buffer_head *ibh)
+{
+ size_t __offset_in_folio = nilfs_palloc_entry_offset(ifile, ino, ibh);
+
+ return kmap_local_folio(ibh->b_folio, __offset_in_folio);
+}
+
+static inline void nilfs_ifile_unmap_inode(struct nilfs_inode *raw_inode)
+{
+ kunmap_local(raw_inode);
+}
+
+int nilfs_ifile_create_inode(struct inode *ifile, u64 *out_ino,
+ struct buffer_head **out_bh);
+int nilfs_ifile_delete_inode(struct inode *ifile, u64 ino);
+int nilfs_ifile_get_inode_block(struct inode *ifile, u64 ino,
+ struct buffer_head **out_bh);
+
+int nilfs_ifile_count_free_inodes(struct inode *, u64 *, u64 *);
+
+int nilfs_ifile_read(struct super_block *sb, struct nilfs_root *root,
+ __u64 cno, size_t inode_size);
+
+#endif /* _NILFS_IFILE_H */
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
new file mode 100644
index 000000000..34e609606
--- /dev/null
+++ b/fs/nilfs2/inode.c
@@ -0,0 +1,1229 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS inode operations.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/gfp.h>
+#include <linux/mpage.h>
+#include <linux/pagemap.h>
+#include <linux/writeback.h>
+#include <linux/uio.h>
+#include <linux/fiemap.h>
+#include <linux/random.h>
+#include "nilfs.h"
+#include "btnode.h"
+#include "segment.h"
+#include "page.h"
+#include "mdt.h"
+#include "cpfile.h"
+#include "ifile.h"
+
+/**
+ * struct nilfs_iget_args - arguments used during comparison between inodes
+ * @ino: inode number
+ * @cno: checkpoint number
+ * @root: pointer on NILFS root object (mounted checkpoint)
+ * @type: inode type
+ */
+struct nilfs_iget_args {
+ u64 ino;
+ __u64 cno;
+ struct nilfs_root *root;
+ unsigned int type;
+};
+
+static int nilfs_iget_test(struct inode *inode, void *opaque);
+
+void nilfs_inode_add_blocks(struct inode *inode, int n)
+{
+ struct nilfs_root *root = NILFS_I(inode)->i_root;
+
+ inode_add_bytes(inode, i_blocksize(inode) * n);
+ if (root)
+ atomic64_add(n, &root->blocks_count);
+}
+
+void nilfs_inode_sub_blocks(struct inode *inode, int n)
+{
+ struct nilfs_root *root = NILFS_I(inode)->i_root;
+
+ inode_sub_bytes(inode, i_blocksize(inode) * n);
+ if (root)
+ atomic64_sub(n, &root->blocks_count);
+}
+
+/**
+ * nilfs_get_block() - get a file block on the filesystem (callback function)
+ * @inode: inode struct of the target file
+ * @blkoff: file block number
+ * @bh_result: buffer head to be mapped on
+ * @create: indicate whether allocating the block or not when it has not
+ * been allocated yet.
+ *
+ * This function does not issue actual read request of the specified data
+ * block. It is done by VFS.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_get_block(struct inode *inode, sector_t blkoff,
+ struct buffer_head *bh_result, int create)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ __u64 blknum = 0;
+ int err = 0, ret;
+ unsigned int maxblocks = bh_result->b_size >> inode->i_blkbits;
+
+ down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ ret = nilfs_bmap_lookup_contig(ii->i_bmap, blkoff, &blknum, maxblocks);
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ if (ret >= 0) { /* found */
+ map_bh(bh_result, inode->i_sb, blknum);
+ if (ret > 0)
+ bh_result->b_size = (ret << inode->i_blkbits);
+ goto out;
+ }
+ /* data block was not found */
+ if (ret == -ENOENT && create) {
+ struct nilfs_transaction_info ti;
+
+ bh_result->b_blocknr = 0;
+ err = nilfs_transaction_begin(inode->i_sb, &ti, 1);
+ if (unlikely(err))
+ goto out;
+ err = nilfs_bmap_insert(ii->i_bmap, blkoff,
+ (unsigned long)bh_result);
+ if (unlikely(err != 0)) {
+ if (err == -EEXIST) {
+ /*
+ * The get_block() function could be called
+ * from multiple callers for an inode.
+ * However, the page having this block must
+ * be locked in this case.
+ */
+ nilfs_warn(inode->i_sb,
+ "%s (ino=%llu): a race condition while inserting a data block at offset=%llu",
+ __func__, inode->i_ino,
+ (unsigned long long)blkoff);
+ err = -EAGAIN;
+ }
+ nilfs_transaction_abort(inode->i_sb);
+ goto out;
+ }
+ nilfs_mark_inode_dirty_sync(inode);
+ nilfs_transaction_commit(inode->i_sb); /* never fails */
+ /* Error handling should be detailed */
+ set_buffer_new(bh_result);
+ set_buffer_delay(bh_result);
+ map_bh(bh_result, inode->i_sb, 0);
+ /* Disk block number must be changed to proper value */
+
+ } else if (ret == -ENOENT) {
+ /*
+ * not found is not error (e.g. hole); must return without
+ * the mapped state flag.
+ */
+ ;
+ } else {
+ err = ret;
+ }
+
+ out:
+ return err;
+}
+
+/**
+ * nilfs_read_folio() - implement read_folio() method of nilfs_aops {}
+ * address_space_operations.
+ * @file: file struct of the file to be read
+ * @folio: the folio to be read
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_read_folio(struct file *file, struct folio *folio)
+{
+ return mpage_read_folio(folio, nilfs_get_block);
+}
+
+static void nilfs_readahead(struct readahead_control *rac)
+{
+ mpage_readahead(rac, nilfs_get_block);
+}
+
+static int nilfs_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ struct inode *inode = mapping->host;
+ int err = 0;
+
+ if (sb_rdonly(inode->i_sb)) {
+ nilfs_clear_dirty_pages(mapping);
+ return -EROFS;
+ }
+
+ if (wbc->sync_mode == WB_SYNC_ALL)
+ err = nilfs_construct_dsync_segment(inode->i_sb, inode,
+ wbc->range_start,
+ wbc->range_end);
+ return err;
+}
+
+static bool nilfs_dirty_folio(struct address_space *mapping,
+ struct folio *folio)
+{
+ struct inode *inode = mapping->host;
+ struct buffer_head *head;
+ unsigned int nr_dirty = 0;
+ bool ret = filemap_dirty_folio(mapping, folio);
+
+ /*
+ * The page may not be locked, eg if called from try_to_unmap_one()
+ */
+ spin_lock(&mapping->i_private_lock);
+ head = folio_buffers(folio);
+ if (head) {
+ struct buffer_head *bh = head;
+
+ do {
+ /* Do not mark hole blocks dirty */
+ if (buffer_dirty(bh) || !buffer_mapped(bh))
+ continue;
+
+ set_buffer_dirty(bh);
+ nr_dirty++;
+ } while (bh = bh->b_this_page, bh != head);
+ } else if (ret) {
+ nr_dirty = 1 << (folio_shift(folio) - inode->i_blkbits);
+ }
+ spin_unlock(&mapping->i_private_lock);
+
+ if (nr_dirty)
+ nilfs_set_file_dirty(inode, nr_dirty);
+ return ret;
+}
+
+void nilfs_write_failed(struct address_space *mapping, loff_t to)
+{
+ struct inode *inode = mapping->host;
+
+ if (to > inode->i_size) {
+ truncate_pagecache(inode, inode->i_size);
+ nilfs_truncate(inode);
+ }
+}
+
+static int nilfs_write_begin(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len,
+ struct folio **foliop, void **fsdata)
+
+{
+ struct inode *inode = mapping->host;
+ int err = nilfs_transaction_begin(inode->i_sb, NULL, 1);
+
+ if (unlikely(err))
+ return err;
+
+ err = block_write_begin(mapping, pos, len, foliop, nilfs_get_block);
+ if (unlikely(err)) {
+ nilfs_write_failed(mapping, pos + len);
+ nilfs_transaction_abort(inode->i_sb);
+ }
+ return err;
+}
+
+static int nilfs_write_end(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct folio *folio, void *fsdata)
+{
+ struct inode *inode = mapping->host;
+ unsigned int start = pos & (PAGE_SIZE - 1);
+ unsigned int nr_dirty;
+ int err;
+
+ nr_dirty = nilfs_page_count_clean_buffers(folio, start,
+ start + copied);
+ copied = generic_write_end(iocb, mapping, pos, len, copied, folio,
+ fsdata);
+ nilfs_set_file_dirty(inode, nr_dirty);
+ err = nilfs_transaction_commit(inode->i_sb);
+ return err ? : copied;
+}
+
+static ssize_t
+nilfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+
+ if (iov_iter_rw(iter) == WRITE)
+ return 0;
+
+ /* Needs synchronization with the cleaner */
+ return blockdev_direct_IO(iocb, inode, iter, nilfs_get_block);
+}
+
+const struct address_space_operations nilfs_aops = {
+ .read_folio = nilfs_read_folio,
+ .writepages = nilfs_writepages,
+ .dirty_folio = nilfs_dirty_folio,
+ .readahead = nilfs_readahead,
+ .write_begin = nilfs_write_begin,
+ .write_end = nilfs_write_end,
+ .invalidate_folio = block_invalidate_folio,
+ .direct_IO = nilfs_direct_IO,
+ .migrate_folio = buffer_migrate_folio_norefs,
+ .is_partially_uptodate = block_is_partially_uptodate,
+};
+
+const struct address_space_operations nilfs_buffer_cache_aops = {
+ .invalidate_folio = block_invalidate_folio,
+};
+
+static int nilfs_insert_inode_locked(struct inode *inode,
+ struct nilfs_root *root, u64 ino)
+{
+ struct nilfs_iget_args args = {
+ .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL
+ };
+
+ return insert_inode_locked4(inode, ino, nilfs_iget_test, &args);
+}
+
+struct inode *nilfs_new_inode(struct inode *dir, umode_t mode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct inode *inode;
+ struct nilfs_inode_info *ii;
+ struct nilfs_root *root;
+ struct buffer_head *bh;
+ int err = -ENOMEM;
+ u64 ino;
+
+ inode = new_inode(sb);
+ if (unlikely(!inode))
+ goto failed;
+
+ mapping_set_gfp_mask(inode->i_mapping,
+ mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS));
+
+ root = NILFS_I(dir)->i_root;
+ ii = NILFS_I(inode);
+ ii->i_state = BIT(NILFS_I_NEW);
+ ii->i_type = NILFS_I_TYPE_NORMAL;
+ ii->i_root = root;
+
+ err = nilfs_ifile_create_inode(root->ifile, &ino, &bh);
+ if (unlikely(err))
+ goto failed_ifile_create_inode;
+ /* reference count of i_bh inherits from nilfs_mdt_read_block() */
+ ii->i_bh = bh;
+
+ atomic64_inc(&root->inodes_count);
+ inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
+ inode->i_ino = ino;
+ simple_inode_init_ts(inode);
+
+ if (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)) {
+ err = nilfs_bmap_read(ii->i_bmap, NULL);
+ if (err < 0)
+ goto failed_after_creation;
+
+ set_bit(NILFS_I_BMAP, &ii->i_state);
+ /* No lock is needed; iget() ensures it. */
+ }
+
+ ii->i_flags = nilfs_mask_flags(
+ mode, NILFS_I(dir)->i_flags & NILFS_FL_INHERITED);
+
+ /* ii->i_file_acl = 0; */
+ /* ii->i_dir_acl = 0; */
+ ii->i_dir_start_lookup = 0;
+ nilfs_set_inode_flags(inode);
+ inode->i_generation = get_random_u32();
+ if (nilfs_insert_inode_locked(inode, root, ino) < 0) {
+ err = -EIO;
+ goto failed_after_creation;
+ }
+
+ err = nilfs_init_acl(inode, dir);
+ if (unlikely(err))
+ /*
+ * Never occur. When supporting nilfs_init_acl(),
+ * proper cancellation of above jobs should be considered.
+ */
+ goto failed_after_creation;
+
+ return inode;
+
+ failed_after_creation:
+ clear_nlink(inode);
+ if (inode_state_read_once(inode) & I_NEW)
+ unlock_new_inode(inode);
+ iput(inode); /*
+ * raw_inode will be deleted through
+ * nilfs_evict_inode().
+ */
+ goto failed;
+
+ failed_ifile_create_inode:
+ make_bad_inode(inode);
+ iput(inode);
+ failed:
+ return ERR_PTR(err);
+}
+
+void nilfs_set_inode_flags(struct inode *inode)
+{
+ unsigned int flags = NILFS_I(inode)->i_flags;
+ unsigned int new_fl = 0;
+
+ if (flags & FS_SYNC_FL)
+ new_fl |= S_SYNC;
+ if (flags & FS_APPEND_FL)
+ new_fl |= S_APPEND;
+ if (flags & FS_IMMUTABLE_FL)
+ new_fl |= S_IMMUTABLE;
+ if (flags & FS_NOATIME_FL)
+ new_fl |= S_NOATIME;
+ if (flags & FS_DIRSYNC_FL)
+ new_fl |= S_DIRSYNC;
+ inode_set_flags(inode, new_fl, S_SYNC | S_APPEND | S_IMMUTABLE |
+ S_NOATIME | S_DIRSYNC);
+}
+
+int nilfs_read_inode_common(struct inode *inode,
+ struct nilfs_inode *raw_inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ int err;
+
+ inode->i_mode = le16_to_cpu(raw_inode->i_mode);
+ i_uid_write(inode, le32_to_cpu(raw_inode->i_uid));
+ i_gid_write(inode, le32_to_cpu(raw_inode->i_gid));
+ set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
+ inode->i_size = le64_to_cpu(raw_inode->i_size);
+ inode_set_atime(inode, le64_to_cpu(raw_inode->i_mtime),
+ le32_to_cpu(raw_inode->i_mtime_nsec));
+ inode_set_ctime(inode, le64_to_cpu(raw_inode->i_ctime),
+ le32_to_cpu(raw_inode->i_ctime_nsec));
+ inode_set_mtime(inode, le64_to_cpu(raw_inode->i_mtime),
+ le32_to_cpu(raw_inode->i_mtime_nsec));
+ if (nilfs_is_metadata_file_inode(inode) && !S_ISREG(inode->i_mode))
+ return -EIO; /* this inode is for metadata and corrupted */
+ if (inode->i_nlink == 0)
+ return -ESTALE; /* this inode is deleted */
+
+ inode->i_blocks = le64_to_cpu(raw_inode->i_blocks);
+ ii->i_flags = le32_to_cpu(raw_inode->i_flags);
+#if 0
+ ii->i_file_acl = le32_to_cpu(raw_inode->i_file_acl);
+ ii->i_dir_acl = S_ISREG(inode->i_mode) ?
+ 0 : le32_to_cpu(raw_inode->i_dir_acl);
+#endif
+ ii->i_dir_start_lookup = 0;
+ inode->i_generation = le32_to_cpu(raw_inode->i_generation);
+
+ if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
+ S_ISLNK(inode->i_mode)) {
+ err = nilfs_bmap_read(ii->i_bmap, raw_inode);
+ if (err < 0)
+ return err;
+ set_bit(NILFS_I_BMAP, &ii->i_state);
+ /* No lock is needed; iget() ensures it. */
+ }
+ return 0;
+}
+
+static int __nilfs_read_inode(struct super_block *sb,
+ struct nilfs_root *root, u64 ino,
+ struct inode *inode)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct buffer_head *bh;
+ struct nilfs_inode *raw_inode;
+ int err;
+
+ down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ err = nilfs_ifile_get_inode_block(root->ifile, ino, &bh);
+ if (unlikely(err))
+ goto bad_inode;
+
+ raw_inode = nilfs_ifile_map_inode(root->ifile, ino, bh);
+
+ err = nilfs_read_inode_common(inode, raw_inode);
+ if (err)
+ goto failed_unmap;
+
+ if (S_ISREG(inode->i_mode)) {
+ inode->i_op = &nilfs_file_inode_operations;
+ inode->i_fop = &nilfs_file_operations;
+ inode->i_mapping->a_ops = &nilfs_aops;
+ } else if (S_ISDIR(inode->i_mode)) {
+ inode->i_op = &nilfs_dir_inode_operations;
+ inode->i_fop = &nilfs_dir_operations;
+ inode->i_mapping->a_ops = &nilfs_aops;
+ } else if (S_ISLNK(inode->i_mode)) {
+ inode->i_op = &nilfs_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mapping->a_ops = &nilfs_aops;
+ } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
+ S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
+ inode->i_op = &nilfs_special_inode_operations;
+ init_special_inode(
+ inode, inode->i_mode,
+ huge_decode_dev(le64_to_cpu(raw_inode->i_device_code)));
+ } else {
+ nilfs_error(sb,
+ "invalid file type bits in mode 0%o for inode %llu",
+ inode->i_mode, ino);
+ err = -EIO;
+ goto failed_unmap;
+ }
+ nilfs_ifile_unmap_inode(raw_inode);
+ brelse(bh);
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ nilfs_set_inode_flags(inode);
+ mapping_set_gfp_mask(inode->i_mapping,
+ mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS));
+ return 0;
+
+ failed_unmap:
+ nilfs_ifile_unmap_inode(raw_inode);
+ brelse(bh);
+
+ bad_inode:
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ return err;
+}
+
+static int nilfs_iget_test(struct inode *inode, void *opaque)
+{
+ struct nilfs_iget_args *args = opaque;
+ struct nilfs_inode_info *ii;
+
+ if (args->ino != inode->i_ino || args->root != NILFS_I(inode)->i_root)
+ return 0;
+
+ ii = NILFS_I(inode);
+ if (ii->i_type != args->type)
+ return 0;
+
+ return !(args->type & NILFS_I_TYPE_GC) || args->cno == ii->i_cno;
+}
+
+static int nilfs_iget_set(struct inode *inode, void *opaque)
+{
+ struct nilfs_iget_args *args = opaque;
+
+ inode->i_ino = args->ino;
+ NILFS_I(inode)->i_cno = args->cno;
+ NILFS_I(inode)->i_root = args->root;
+ NILFS_I(inode)->i_type = args->type;
+ if (args->root && args->ino == NILFS_ROOT_INO)
+ nilfs_get_root(args->root);
+ return 0;
+}
+
+struct inode *nilfs_ilookup(struct super_block *sb, struct nilfs_root *root,
+ u64 ino)
+{
+ struct nilfs_iget_args args = {
+ .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL
+ };
+
+ return ilookup5(sb, ino, nilfs_iget_test, &args);
+}
+
+struct inode *nilfs_iget_locked(struct super_block *sb,
+ struct nilfs_root *root, u64 ino)
+{
+ struct nilfs_iget_args args = {
+ .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL
+ };
+
+ return iget5_locked(sb, ino, nilfs_iget_test, nilfs_iget_set, &args);
+}
+
+struct inode *nilfs_iget(struct super_block *sb, struct nilfs_root *root,
+ u64 ino)
+{
+ struct inode *inode;
+ int err;
+
+ inode = nilfs_iget_locked(sb, root, ino);
+ if (unlikely(!inode))
+ return ERR_PTR(-ENOMEM);
+
+ if (!(inode_state_read_once(inode) & I_NEW)) {
+ if (!inode->i_nlink) {
+ iput(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ return inode;
+ }
+
+ err = __nilfs_read_inode(sb, root, ino, inode);
+ if (unlikely(err)) {
+ iget_failed(inode);
+ return ERR_PTR(err);
+ }
+ unlock_new_inode(inode);
+ return inode;
+}
+
+struct inode *nilfs_iget_for_gc(struct super_block *sb, u64 ino, __u64 cno)
+{
+ struct nilfs_iget_args args = {
+ .ino = ino, .root = NULL, .cno = cno, .type = NILFS_I_TYPE_GC
+ };
+ struct inode *inode;
+ int err;
+
+ inode = iget5_locked(sb, ino, nilfs_iget_test, nilfs_iget_set, &args);
+ if (unlikely(!inode))
+ return ERR_PTR(-ENOMEM);
+ if (!(inode_state_read_once(inode) & I_NEW))
+ return inode;
+
+ err = nilfs_init_gcinode(inode);
+ if (unlikely(err)) {
+ iget_failed(inode);
+ return ERR_PTR(err);
+ }
+ unlock_new_inode(inode);
+ return inode;
+}
+
+/**
+ * nilfs_attach_btree_node_cache - attach a B-tree node cache to the inode
+ * @inode: inode object
+ *
+ * nilfs_attach_btree_node_cache() attaches a B-tree node cache to @inode,
+ * or does nothing if the inode already has it. This function allocates
+ * an additional inode to maintain page cache of B-tree nodes one-on-one.
+ *
+ * Return: 0 on success, or %-ENOMEM if memory is insufficient.
+ */
+int nilfs_attach_btree_node_cache(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct inode *btnc_inode;
+ struct nilfs_iget_args args;
+
+ if (ii->i_assoc_inode)
+ return 0;
+
+ args.ino = inode->i_ino;
+ args.root = ii->i_root;
+ args.cno = ii->i_cno;
+ args.type = ii->i_type | NILFS_I_TYPE_BTNC;
+
+ btnc_inode = iget5_locked(inode->i_sb, inode->i_ino, nilfs_iget_test,
+ nilfs_iget_set, &args);
+ if (unlikely(!btnc_inode))
+ return -ENOMEM;
+ if (inode_state_read_once(btnc_inode) & I_NEW) {
+ nilfs_init_btnc_inode(btnc_inode);
+ unlock_new_inode(btnc_inode);
+ }
+ NILFS_I(btnc_inode)->i_assoc_inode = inode;
+ NILFS_I(btnc_inode)->i_bmap = ii->i_bmap;
+ ii->i_assoc_inode = btnc_inode;
+
+ return 0;
+}
+
+/**
+ * nilfs_detach_btree_node_cache - detach the B-tree node cache from the inode
+ * @inode: inode object
+ *
+ * nilfs_detach_btree_node_cache() detaches the B-tree node cache and its
+ * holder inode bound to @inode, or does nothing if @inode doesn't have it.
+ */
+void nilfs_detach_btree_node_cache(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct inode *btnc_inode = ii->i_assoc_inode;
+
+ if (btnc_inode) {
+ NILFS_I(btnc_inode)->i_assoc_inode = NULL;
+ ii->i_assoc_inode = NULL;
+ iput(btnc_inode);
+ }
+}
+
+/**
+ * nilfs_iget_for_shadow - obtain inode for shadow mapping
+ * @inode: inode object that uses shadow mapping
+ *
+ * nilfs_iget_for_shadow() allocates a pair of inodes that holds page
+ * caches for shadow mapping. The page cache for data pages is set up
+ * in one inode and the one for b-tree node pages is set up in the
+ * other inode, which is attached to the former inode.
+ *
+ * Return: a pointer to the inode for data pages on success, or %-ENOMEM
+ * if memory is insufficient.
+ */
+struct inode *nilfs_iget_for_shadow(struct inode *inode)
+{
+ struct nilfs_iget_args args = {
+ .ino = inode->i_ino, .root = NULL, .cno = 0,
+ .type = NILFS_I_TYPE_SHADOW
+ };
+ struct inode *s_inode;
+ int err;
+
+ s_inode = iget5_locked(inode->i_sb, inode->i_ino, nilfs_iget_test,
+ nilfs_iget_set, &args);
+ if (unlikely(!s_inode))
+ return ERR_PTR(-ENOMEM);
+ if (!(inode_state_read_once(s_inode) & I_NEW))
+ return inode;
+
+ NILFS_I(s_inode)->i_flags = 0;
+ memset(NILFS_I(s_inode)->i_bmap, 0, sizeof(struct nilfs_bmap));
+ mapping_set_gfp_mask(s_inode->i_mapping, GFP_NOFS);
+ s_inode->i_mapping->a_ops = &nilfs_buffer_cache_aops;
+
+ err = nilfs_attach_btree_node_cache(s_inode);
+ if (unlikely(err)) {
+ iget_failed(s_inode);
+ return ERR_PTR(err);
+ }
+ unlock_new_inode(s_inode);
+ return s_inode;
+}
+
+/**
+ * nilfs_write_inode_common - export common inode information to on-disk inode
+ * @inode: inode object
+ * @raw_inode: on-disk inode
+ *
+ * This function writes standard information from the on-memory inode @inode
+ * to @raw_inode on ifile, cpfile or a super root block. Since inode bmap
+ * data is not exported, nilfs_bmap_write() must be called separately during
+ * log writing.
+ */
+void nilfs_write_inode_common(struct inode *inode,
+ struct nilfs_inode *raw_inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+
+ raw_inode->i_mode = cpu_to_le16(inode->i_mode);
+ raw_inode->i_uid = cpu_to_le32(i_uid_read(inode));
+ raw_inode->i_gid = cpu_to_le32(i_gid_read(inode));
+ raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
+ raw_inode->i_size = cpu_to_le64(inode->i_size);
+ raw_inode->i_ctime = cpu_to_le64(inode_get_ctime_sec(inode));
+ raw_inode->i_mtime = cpu_to_le64(inode_get_mtime_sec(inode));
+ raw_inode->i_ctime_nsec = cpu_to_le32(inode_get_ctime_nsec(inode));
+ raw_inode->i_mtime_nsec = cpu_to_le32(inode_get_mtime_nsec(inode));
+ raw_inode->i_blocks = cpu_to_le64(inode->i_blocks);
+
+ raw_inode->i_flags = cpu_to_le32(ii->i_flags);
+ raw_inode->i_generation = cpu_to_le32(inode->i_generation);
+
+ /*
+ * When extending inode, nilfs->ns_inode_size should be checked
+ * for substitutions of appended fields.
+ */
+}
+
+void nilfs_update_inode(struct inode *inode, struct buffer_head *ibh, int flags)
+{
+ u64 ino = inode->i_ino;
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct inode *ifile = ii->i_root->ifile;
+ struct nilfs_inode *raw_inode;
+
+ raw_inode = nilfs_ifile_map_inode(ifile, ino, ibh);
+
+ if (test_and_clear_bit(NILFS_I_NEW, &ii->i_state))
+ memset(raw_inode, 0, NILFS_MDT(ifile)->mi_entry_size);
+ if (flags & I_DIRTY_DATASYNC)
+ set_bit(NILFS_I_INODE_SYNC, &ii->i_state);
+
+ nilfs_write_inode_common(inode, raw_inode);
+
+ if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
+ raw_inode->i_device_code =
+ cpu_to_le64(huge_encode_dev(inode->i_rdev));
+
+ nilfs_ifile_unmap_inode(raw_inode);
+}
+
+#define NILFS_MAX_TRUNCATE_BLOCKS 16384 /* 64MB for 4KB block */
+
+static void nilfs_truncate_bmap(struct nilfs_inode_info *ii,
+ unsigned long from)
+{
+ __u64 b;
+ int ret;
+
+ if (!test_bit(NILFS_I_BMAP, &ii->i_state))
+ return;
+repeat:
+ ret = nilfs_bmap_last_key(ii->i_bmap, &b);
+ if (ret == -ENOENT)
+ return;
+ else if (ret < 0)
+ goto failed;
+
+ if (b < from)
+ return;
+
+ b -= min_t(__u64, NILFS_MAX_TRUNCATE_BLOCKS, b - from);
+ ret = nilfs_bmap_truncate(ii->i_bmap, b);
+ nilfs_relax_pressure_in_lock(ii->vfs_inode.i_sb);
+ if (!ret || (ret == -ENOMEM &&
+ nilfs_bmap_truncate(ii->i_bmap, b) == 0))
+ goto repeat;
+
+failed:
+ nilfs_warn(ii->vfs_inode.i_sb, "error %d truncating bmap (ino=%llu)",
+ ret, ii->vfs_inode.i_ino);
+}
+
+void nilfs_truncate(struct inode *inode)
+{
+ unsigned long blkoff;
+ unsigned int blocksize;
+ struct nilfs_transaction_info ti;
+ struct super_block *sb = inode->i_sb;
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+
+ if (!test_bit(NILFS_I_BMAP, &ii->i_state))
+ return;
+ if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
+ return;
+
+ blocksize = sb->s_blocksize;
+ blkoff = (inode->i_size + blocksize - 1) >> sb->s_blocksize_bits;
+ nilfs_transaction_begin(sb, &ti, 0); /* never fails */
+
+ block_truncate_page(inode->i_mapping, inode->i_size, nilfs_get_block);
+
+ nilfs_truncate_bmap(ii, blkoff);
+
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ if (IS_SYNC(inode))
+ nilfs_set_transaction_flag(NILFS_TI_SYNC);
+
+ nilfs_mark_inode_dirty(inode);
+ nilfs_set_file_dirty(inode, 0);
+ nilfs_transaction_commit(sb);
+ /*
+ * May construct a logical segment and may fail in sync mode.
+ * But truncate has no return value.
+ */
+}
+
+static void nilfs_clear_inode(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+
+ /*
+ * Free resources allocated in nilfs_read_inode(), here.
+ */
+ BUG_ON(!list_empty(&ii->i_dirty));
+ brelse(ii->i_bh);
+ ii->i_bh = NULL;
+
+ if (nilfs_is_metadata_file_inode(inode))
+ nilfs_mdt_clear(inode);
+
+ if (test_bit(NILFS_I_BMAP, &ii->i_state))
+ nilfs_bmap_clear(ii->i_bmap);
+
+ if (!(ii->i_type & NILFS_I_TYPE_BTNC))
+ nilfs_detach_btree_node_cache(inode);
+
+ if (ii->i_root && inode->i_ino == NILFS_ROOT_INO)
+ nilfs_put_root(ii->i_root);
+}
+
+void nilfs_evict_inode(struct inode *inode)
+{
+ struct nilfs_transaction_info ti;
+ struct super_block *sb = inode->i_sb;
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct the_nilfs *nilfs;
+ int ret;
+
+ if (inode->i_nlink || !ii->i_root || unlikely(is_bad_inode(inode))) {
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ nilfs_clear_inode(inode);
+ return;
+ }
+ nilfs_transaction_begin(sb, &ti, 0); /* never fails */
+
+ truncate_inode_pages_final(&inode->i_data);
+
+ nilfs = sb->s_fs_info;
+ if (unlikely(sb_rdonly(sb) || !nilfs->ns_writer)) {
+ /*
+ * If this inode is about to be disposed after the file system
+ * has been degraded to read-only due to file system corruption
+ * or after the writer has been detached, do not make any
+ * changes that cause writes, just clear it.
+ * Do this check after read-locking ns_segctor_sem by
+ * nilfs_transaction_begin() in order to avoid a race with
+ * the writer detach operation.
+ */
+ clear_inode(inode);
+ nilfs_clear_inode(inode);
+ nilfs_transaction_abort(sb);
+ return;
+ }
+
+ /* TODO: some of the following operations may fail. */
+ nilfs_truncate_bmap(ii, 0);
+ nilfs_mark_inode_dirty(inode);
+ clear_inode(inode);
+
+ ret = nilfs_ifile_delete_inode(ii->i_root->ifile, inode->i_ino);
+ if (!ret)
+ atomic64_dec(&ii->i_root->inodes_count);
+
+ nilfs_clear_inode(inode);
+
+ if (IS_SYNC(inode))
+ nilfs_set_transaction_flag(NILFS_TI_SYNC);
+ nilfs_transaction_commit(sb);
+ /*
+ * May construct a logical segment and may fail in sync mode.
+ * But delete_inode has no return value.
+ */
+}
+
+int nilfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct iattr *iattr)
+{
+ struct nilfs_transaction_info ti;
+ struct inode *inode = d_inode(dentry);
+ struct super_block *sb = inode->i_sb;
+ int err;
+
+ err = setattr_prepare(&nop_mnt_idmap, dentry, iattr);
+ if (err)
+ return err;
+
+ err = nilfs_transaction_begin(sb, &ti, 0);
+ if (unlikely(err))
+ return err;
+
+ if ((iattr->ia_valid & ATTR_SIZE) &&
+ iattr->ia_size != i_size_read(inode)) {
+ inode_dio_wait(inode);
+ truncate_setsize(inode, iattr->ia_size);
+ nilfs_truncate(inode);
+ }
+
+ setattr_copy(&nop_mnt_idmap, inode, iattr);
+ mark_inode_dirty(inode);
+
+ if (iattr->ia_valid & ATTR_MODE) {
+ err = nilfs_acl_chmod(inode);
+ if (unlikely(err))
+ goto out_err;
+ }
+
+ return nilfs_transaction_commit(sb);
+
+out_err:
+ nilfs_transaction_abort(sb);
+ return err;
+}
+
+int nilfs_permission(struct mnt_idmap *idmap, struct inode *inode,
+ int mask)
+{
+ struct nilfs_root *root = NILFS_I(inode)->i_root;
+
+ if ((mask & MAY_WRITE) && root &&
+ root->cno != NILFS_CPTREE_CURRENT_CNO)
+ return -EROFS; /* snapshot is not writable */
+
+ return generic_permission(&nop_mnt_idmap, inode, mask);
+}
+
+int nilfs_load_inode_block(struct inode *inode, struct buffer_head **pbh)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ int err;
+
+ spin_lock(&nilfs->ns_inode_lock);
+ if (ii->i_bh == NULL || unlikely(!buffer_uptodate(ii->i_bh))) {
+ spin_unlock(&nilfs->ns_inode_lock);
+ err = nilfs_ifile_get_inode_block(ii->i_root->ifile,
+ inode->i_ino, pbh);
+ if (unlikely(err))
+ return err;
+ spin_lock(&nilfs->ns_inode_lock);
+ if (ii->i_bh == NULL)
+ ii->i_bh = *pbh;
+ else if (unlikely(!buffer_uptodate(ii->i_bh))) {
+ __brelse(ii->i_bh);
+ ii->i_bh = *pbh;
+ } else {
+ brelse(*pbh);
+ *pbh = ii->i_bh;
+ }
+ } else
+ *pbh = ii->i_bh;
+
+ get_bh(*pbh);
+ spin_unlock(&nilfs->ns_inode_lock);
+ return 0;
+}
+
+int nilfs_inode_dirty(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ int ret = 0;
+
+ if (!list_empty(&ii->i_dirty)) {
+ spin_lock(&nilfs->ns_inode_lock);
+ ret = test_bit(NILFS_I_DIRTY, &ii->i_state) ||
+ test_bit(NILFS_I_BUSY, &ii->i_state);
+ spin_unlock(&nilfs->ns_inode_lock);
+ }
+ return ret;
+}
+
+int nilfs_set_file_dirty(struct inode *inode, unsigned int nr_dirty)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+
+ atomic_add(nr_dirty, &nilfs->ns_ndirtyblks);
+
+ if (test_and_set_bit(NILFS_I_DIRTY, &ii->i_state))
+ return 0;
+
+ spin_lock(&nilfs->ns_inode_lock);
+ if (!test_bit(NILFS_I_QUEUED, &ii->i_state) &&
+ !test_bit(NILFS_I_BUSY, &ii->i_state)) {
+ /*
+ * Because this routine may race with nilfs_dispose_list(),
+ * we have to check NILFS_I_QUEUED here, too.
+ */
+ if (list_empty(&ii->i_dirty) && igrab(inode) == NULL) {
+ /*
+ * This will happen when somebody is freeing
+ * this inode.
+ */
+ nilfs_warn(inode->i_sb,
+ "cannot set file dirty (ino=%llu): the file is being freed",
+ inode->i_ino);
+ spin_unlock(&nilfs->ns_inode_lock);
+ return -EINVAL; /*
+ * NILFS_I_DIRTY may remain for
+ * freeing inode.
+ */
+ }
+ list_move_tail(&ii->i_dirty, &nilfs->ns_dirty_files);
+ set_bit(NILFS_I_QUEUED, &ii->i_state);
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+ return 0;
+}
+
+int __nilfs_mark_inode_dirty(struct inode *inode, int flags)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct buffer_head *ibh;
+ int err;
+
+ /*
+ * Do not dirty inodes after the log writer has been detached
+ * and its nilfs_root struct has been freed.
+ */
+ if (unlikely(nilfs_purging(nilfs)))
+ return 0;
+
+ err = nilfs_load_inode_block(inode, &ibh);
+ if (unlikely(err)) {
+ nilfs_warn(inode->i_sb,
+ "cannot mark inode dirty (ino=%llu): error %d loading inode block",
+ inode->i_ino, err);
+ return err;
+ }
+ nilfs_update_inode(inode, ibh, flags);
+ mark_buffer_dirty(ibh);
+ nilfs_mdt_mark_dirty(NILFS_I(inode)->i_root->ifile);
+ brelse(ibh);
+ return 0;
+}
+
+/**
+ * nilfs_dirty_inode - reflect changes on given inode to an inode block.
+ * @inode: inode of the file to be registered.
+ * @flags: flags to determine the dirty state of the inode
+ *
+ * nilfs_dirty_inode() loads a inode block containing the specified
+ * @inode and copies data from a nilfs_inode to a corresponding inode
+ * entry in the inode block. This operation is excluded from the segment
+ * construction. This function can be called both as a single operation
+ * and as a part of indivisible file operations.
+ */
+void nilfs_dirty_inode(struct inode *inode, int flags)
+{
+ struct nilfs_transaction_info ti;
+ struct nilfs_mdt_info *mdi = NILFS_MDT(inode);
+
+ if (is_bad_inode(inode)) {
+ nilfs_warn(inode->i_sb,
+ "tried to mark bad_inode dirty. ignored.");
+ dump_stack();
+ return;
+ }
+ if (mdi) {
+ nilfs_mdt_mark_dirty(inode);
+ return;
+ }
+ nilfs_transaction_begin(inode->i_sb, &ti, 0);
+ __nilfs_mark_inode_dirty(inode, flags);
+ nilfs_transaction_commit(inode->i_sb); /* never fails */
+}
+
+int nilfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ __u64 start, __u64 len)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ __u64 logical = 0, phys = 0, size = 0;
+ __u32 flags = 0;
+ loff_t isize;
+ sector_t blkoff, end_blkoff;
+ sector_t delalloc_blkoff;
+ unsigned long delalloc_blklen;
+ unsigned int blkbits = inode->i_blkbits;
+ int ret, n;
+
+ ret = fiemap_prep(inode, fieinfo, start, &len, 0);
+ if (ret)
+ return ret;
+
+ inode_lock(inode);
+
+ isize = i_size_read(inode);
+
+ blkoff = start >> blkbits;
+ end_blkoff = (start + len - 1) >> blkbits;
+
+ delalloc_blklen = nilfs_find_uncommitted_extent(inode, blkoff,
+ &delalloc_blkoff);
+
+ do {
+ __u64 blkphy;
+ unsigned int maxblocks;
+
+ if (delalloc_blklen && blkoff == delalloc_blkoff) {
+ if (size) {
+ /* End of the current extent */
+ ret = fiemap_fill_next_extent(
+ fieinfo, logical, phys, size, flags);
+ if (ret)
+ break;
+ }
+ if (blkoff > end_blkoff)
+ break;
+
+ flags = FIEMAP_EXTENT_MERGED | FIEMAP_EXTENT_DELALLOC;
+ logical = blkoff << blkbits;
+ phys = 0;
+ size = delalloc_blklen << blkbits;
+
+ blkoff = delalloc_blkoff + delalloc_blklen;
+ delalloc_blklen = nilfs_find_uncommitted_extent(
+ inode, blkoff, &delalloc_blkoff);
+ continue;
+ }
+
+ /*
+ * Limit the number of blocks that we look up so as
+ * not to get into the next delayed allocation extent.
+ */
+ maxblocks = INT_MAX;
+ if (delalloc_blklen)
+ maxblocks = min_t(sector_t, delalloc_blkoff - blkoff,
+ maxblocks);
+ blkphy = 0;
+
+ down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ n = nilfs_bmap_lookup_contig(
+ NILFS_I(inode)->i_bmap, blkoff, &blkphy, maxblocks);
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+
+ if (n < 0) {
+ int past_eof;
+
+ if (unlikely(n != -ENOENT))
+ break; /* error */
+
+ /* HOLE */
+ blkoff++;
+ past_eof = ((blkoff << blkbits) >= isize);
+
+ if (size) {
+ /* End of the current extent */
+
+ if (past_eof)
+ flags |= FIEMAP_EXTENT_LAST;
+
+ ret = fiemap_fill_next_extent(
+ fieinfo, logical, phys, size, flags);
+ if (ret)
+ break;
+ size = 0;
+ }
+ if (blkoff > end_blkoff || past_eof)
+ break;
+ } else {
+ if (size) {
+ if (phys && blkphy << blkbits == phys + size) {
+ /* The current extent goes on */
+ size += (u64)n << blkbits;
+ } else {
+ /* Terminate the current extent */
+ ret = fiemap_fill_next_extent(
+ fieinfo, logical, phys, size,
+ flags);
+ if (ret || blkoff > end_blkoff)
+ break;
+
+ /* Start another extent */
+ flags = FIEMAP_EXTENT_MERGED;
+ logical = blkoff << blkbits;
+ phys = blkphy << blkbits;
+ size = (u64)n << blkbits;
+ }
+ } else {
+ /* Start a new extent */
+ flags = FIEMAP_EXTENT_MERGED;
+ logical = blkoff << blkbits;
+ phys = blkphy << blkbits;
+ size = (u64)n << blkbits;
+ }
+ blkoff += n;
+ }
+ cond_resched();
+ } while (true);
+
+ /* If ret is 1 then we just hit the end of the extent array */
+ if (ret == 1)
+ ret = 0;
+
+ inode_unlock(inode);
+ return ret;
+}
diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c
new file mode 100644
index 000000000..01a04080e
--- /dev/null
+++ b/fs/nilfs2/ioctl.c
@@ -0,0 +1,1415 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS ioctl operations.
+ *
+ * Copyright (C) 2007, 2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#include <linux/fs.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/capability.h> /* capable() */
+#include <linux/uaccess.h> /* copy_from_user(), copy_to_user() */
+#include <linux/vmalloc.h>
+#include <linux/compat.h> /* compat_ptr() */
+#include <linux/mount.h> /* mnt_want_write_file(), mnt_drop_write_file() */
+#include <linux/buffer_head.h>
+#include <linux/fileattr.h>
+#include <linux/string.h>
+#include "nilfs.h"
+#include "segment.h"
+#include "bmap.h"
+#include "cpfile.h"
+#include "sufile.h"
+#include "dat.h"
+
+/**
+ * nilfs_ioctl_wrap_copy - wrapping function of get/set metadata info
+ * @nilfs: nilfs object
+ * @argv: vector of arguments from userspace
+ * @dir: set of direction flags
+ * @dofunc: concrete function of get/set metadata info
+ *
+ * Description: nilfs_ioctl_wrap_copy() gets/sets metadata info by means of
+ * calling dofunc() function on the basis of @argv argument. If successful,
+ * the requested metadata information is copied to userspace memory.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during execution of requested operation.
+ * * %-EINVAL - Invalid arguments from userspace.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs,
+ struct nilfs_argv *argv, int dir,
+ ssize_t (*dofunc)(struct the_nilfs *,
+ __u64 *, int,
+ void *, size_t, size_t))
+{
+ void *buf;
+ void __user *base = u64_to_user_ptr(argv->v_base);
+ size_t maxmembs, total, n;
+ ssize_t nr;
+ int ret, i;
+ __u64 pos, ppos;
+
+ if (argv->v_nmembs == 0)
+ return 0;
+
+ if ((size_t)argv->v_size > PAGE_SIZE)
+ return -EINVAL;
+
+ /*
+ * Reject pairs of a start item position (argv->v_index) and a
+ * total count (argv->v_nmembs) which leads position 'pos' to
+ * overflow by the increment at the end of the loop.
+ */
+ if (argv->v_index > ~(__u64)0 - argv->v_nmembs)
+ return -EINVAL;
+
+ buf = kzalloc(PAGE_SIZE, GFP_NOFS);
+ if (unlikely(!buf))
+ return -ENOMEM;
+ maxmembs = PAGE_SIZE / argv->v_size;
+
+ ret = 0;
+ total = 0;
+ pos = argv->v_index;
+ for (i = 0; i < argv->v_nmembs; i += n) {
+ n = (argv->v_nmembs - i < maxmembs) ?
+ argv->v_nmembs - i : maxmembs;
+ if ((dir & _IOC_WRITE) &&
+ copy_from_user(buf, base + argv->v_size * i,
+ argv->v_size * n)) {
+ ret = -EFAULT;
+ break;
+ }
+ ppos = pos;
+ nr = dofunc(nilfs, &pos, argv->v_flags, buf, argv->v_size,
+ n);
+ if (nr < 0) {
+ ret = nr;
+ break;
+ }
+ if ((dir & _IOC_READ) &&
+ copy_to_user(base + argv->v_size * i, buf,
+ argv->v_size * nr)) {
+ ret = -EFAULT;
+ break;
+ }
+ total += nr;
+ if ((size_t)nr < n)
+ break;
+ if (pos == ppos)
+ pos += n;
+ }
+ argv->v_nmembs = total;
+
+ kfree(buf);
+ return ret;
+}
+
+/**
+ * nilfs_fileattr_get - retrieve miscellaneous file attributes
+ * @dentry: the object to retrieve from
+ * @fa: fileattr pointer
+ *
+ * Return: always 0 as success.
+ */
+int nilfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+
+ fileattr_fill_flags(fa, NILFS_I(inode)->i_flags & FS_FL_USER_VISIBLE);
+
+ return 0;
+}
+
+/**
+ * nilfs_fileattr_set - change miscellaneous file attributes
+ * @idmap: idmap of the mount
+ * @dentry: the object to change
+ * @fa: fileattr pointer
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_fileattr_set(struct mnt_idmap *idmap,
+ struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct nilfs_transaction_info ti;
+ unsigned int flags, oldflags;
+ int ret;
+
+ if (fileattr_has_fsx(fa))
+ return -EOPNOTSUPP;
+
+ flags = nilfs_mask_flags(inode->i_mode, fa->flags);
+
+ ret = nilfs_transaction_begin(inode->i_sb, &ti, 0);
+ if (ret)
+ return ret;
+
+ oldflags = NILFS_I(inode)->i_flags & ~FS_FL_USER_MODIFIABLE;
+ NILFS_I(inode)->i_flags = oldflags | (flags & FS_FL_USER_MODIFIABLE);
+
+ nilfs_set_inode_flags(inode);
+ inode_set_ctime_current(inode);
+ if (IS_SYNC(inode))
+ nilfs_set_transaction_flag(NILFS_TI_SYNC);
+
+ nilfs_mark_inode_dirty(inode);
+ return nilfs_transaction_commit(inode->i_sb);
+}
+
+/**
+ * nilfs_ioctl_getversion - get info about a file's version (generation number)
+ * @inode: inode object
+ * @argp: userspace memory where the generation number of @inode is stored
+ *
+ * Return: 0 on success, or %-EFAULT on error.
+ */
+static int nilfs_ioctl_getversion(struct inode *inode, void __user *argp)
+{
+ return put_user(inode->i_generation, (int __user *)argp);
+}
+
+/**
+ * nilfs_ioctl_change_cpmode - change checkpoint mode (checkpoint/snapshot)
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_change_cpmode() function changes mode of
+ * given checkpoint between checkpoint and snapshot state. This ioctl
+ * is used in chcp and mkcp utilities.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * %-EFAULT - Failure during checkpoint mode changing.
+ * %-EPERM - Operation not permitted.
+ */
+static int nilfs_ioctl_change_cpmode(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_transaction_info ti;
+ struct nilfs_cpmode cpmode;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ return ret;
+
+ ret = -EFAULT;
+ if (copy_from_user(&cpmode, argp, sizeof(cpmode)))
+ goto out;
+
+ mutex_lock(&nilfs->ns_snapshot_mount_mutex);
+
+ nilfs_transaction_begin(inode->i_sb, &ti, 0);
+ ret = nilfs_cpfile_change_cpmode(
+ nilfs->ns_cpfile, cpmode.cm_cno, cpmode.cm_mode);
+ if (unlikely(ret < 0))
+ nilfs_transaction_abort(inode->i_sb);
+ else
+ nilfs_transaction_commit(inode->i_sb); /* never fails */
+
+ mutex_unlock(&nilfs->ns_snapshot_mount_mutex);
+out:
+ mnt_drop_write_file(filp);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_delete_checkpoint - remove checkpoint
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_delete_checkpoint() function removes
+ * checkpoint from NILFS2 file system. This ioctl is used in rmcp
+ * utility.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * %-EFAULT - Failure during checkpoint removing.
+ * %-EPERM - Operation not permitted.
+ */
+static int
+nilfs_ioctl_delete_checkpoint(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_transaction_info ti;
+ __u64 cno;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ return ret;
+
+ ret = -EFAULT;
+ if (copy_from_user(&cno, argp, sizeof(cno)))
+ goto out;
+
+ nilfs_transaction_begin(inode->i_sb, &ti, 0);
+ ret = nilfs_cpfile_delete_checkpoint(nilfs->ns_cpfile, cno);
+ if (unlikely(ret < 0))
+ nilfs_transaction_abort(inode->i_sb);
+ else
+ nilfs_transaction_commit(inode->i_sb); /* never fails */
+out:
+ mnt_drop_write_file(filp);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_do_get_cpinfo - callback method getting info about checkpoints
+ * @nilfs: nilfs object
+ * @posp: pointer on array of checkpoint's numbers
+ * @flags: checkpoint mode (checkpoint or snapshot)
+ * @buf: buffer for storing checkponts' info
+ * @size: size in bytes of one checkpoint info item in array
+ * @nmembs: number of checkpoints in array (numbers and infos)
+ *
+ * Description: nilfs_ioctl_do_get_cpinfo() function returns info about
+ * requested checkpoints. The NILFS_IOCTL_GET_CPINFO ioctl is used in
+ * lscp utility and by nilfs_cleanerd daemon.
+ *
+ * Return: Count of nilfs_cpinfo structures in output buffer.
+ */
+static ssize_t
+nilfs_ioctl_do_get_cpinfo(struct the_nilfs *nilfs, __u64 *posp, int flags,
+ void *buf, size_t size, size_t nmembs)
+{
+ int ret;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_cpfile_get_cpinfo(nilfs->ns_cpfile, posp, flags, buf,
+ size, nmembs);
+ up_read(&nilfs->ns_segctor_sem);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_get_cpstat - get checkpoints statistics
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_get_cpstat() returns information about checkpoints.
+ * The NILFS_IOCTL_GET_CPSTAT ioctl is used by lscp, rmcp utilities
+ * and by nilfs_cleanerd daemon. The checkpoint statistics are copied to
+ * the userspace memory pointed to by @argp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during getting checkpoints statistics.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_get_cpstat(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_cpstat cpstat;
+ int ret;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_cpfile_get_stat(nilfs->ns_cpfile, &cpstat);
+ up_read(&nilfs->ns_segctor_sem);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &cpstat, sizeof(cpstat)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_do_get_suinfo - callback method getting segment usage info
+ * @nilfs: nilfs object
+ * @posp: pointer on array of segment numbers
+ * @flags: *not used*
+ * @buf: buffer for storing suinfo array
+ * @size: size in bytes of one suinfo item in array
+ * @nmembs: count of segment numbers and suinfos in array
+ *
+ * Description: nilfs_ioctl_do_get_suinfo() function returns segment usage
+ * info about requested segments. The NILFS_IOCTL_GET_SUINFO ioctl is used
+ * in lssu, nilfs_resize utilities and by nilfs_cleanerd daemon.
+ *
+ * Return: Count of nilfs_suinfo structures in output buffer on success,
+ * or a negative error code on failure.
+ */
+static ssize_t
+nilfs_ioctl_do_get_suinfo(struct the_nilfs *nilfs, __u64 *posp, int flags,
+ void *buf, size_t size, size_t nmembs)
+{
+ int ret;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_sufile_get_suinfo(nilfs->ns_sufile, *posp, buf, size,
+ nmembs);
+ up_read(&nilfs->ns_segctor_sem);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_get_sustat - get segment usage statistics
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_get_sustat() returns segment usage statistics.
+ * The NILFS_IOCTL_GET_SUSTAT ioctl is used in lssu, nilfs_resize utilities
+ * and by nilfs_cleanerd daemon. The requested segment usage information is
+ * copied to the userspace memory pointed to by @argp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during getting segment usage statistics.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_get_sustat(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_sustat sustat;
+ int ret;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_sufile_get_stat(nilfs->ns_sufile, &sustat);
+ up_read(&nilfs->ns_segctor_sem);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &sustat, sizeof(sustat)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_do_get_vinfo - callback method getting virtual blocks info
+ * @nilfs: nilfs object
+ * @posp: *not used*
+ * @flags: *not used*
+ * @buf: buffer for storing array of nilfs_vinfo structures
+ * @size: size in bytes of one vinfo item in array
+ * @nmembs: count of vinfos in array
+ *
+ * Description: nilfs_ioctl_do_get_vinfo() function returns information
+ * on virtual block addresses. The NILFS_IOCTL_GET_VINFO ioctl is used
+ * by nilfs_cleanerd daemon.
+ *
+ * Return: Count of nilfs_vinfo structures in output buffer on success, or
+ * a negative error code on failure.
+ */
+static ssize_t
+nilfs_ioctl_do_get_vinfo(struct the_nilfs *nilfs, __u64 *posp, int flags,
+ void *buf, size_t size, size_t nmembs)
+{
+ int ret;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_dat_get_vinfo(nilfs->ns_dat, buf, size, nmembs);
+ up_read(&nilfs->ns_segctor_sem);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_do_get_bdescs - callback method getting disk block descriptors
+ * @nilfs: nilfs object
+ * @posp: *not used*
+ * @flags: *not used*
+ * @buf: buffer for storing array of nilfs_bdesc structures
+ * @size: size in bytes of one bdesc item in array
+ * @nmembs: count of bdescs in array
+ *
+ * Description: nilfs_ioctl_do_get_bdescs() function returns information
+ * about descriptors of disk block numbers. The NILFS_IOCTL_GET_BDESCS ioctl
+ * is used by nilfs_cleanerd daemon.
+ *
+ * Return: Count of nilfs_bdescs structures in output buffer on success, or
+ * a negative error code on failure.
+ */
+static ssize_t
+nilfs_ioctl_do_get_bdescs(struct the_nilfs *nilfs, __u64 *posp, int flags,
+ void *buf, size_t size, size_t nmembs)
+{
+ struct nilfs_bmap *bmap = NILFS_I(nilfs->ns_dat)->i_bmap;
+ struct nilfs_bdesc *bdescs = buf;
+ int ret, i;
+
+ down_read(&nilfs->ns_segctor_sem);
+ for (i = 0; i < nmembs; i++) {
+ ret = nilfs_bmap_lookup_at_level(bmap,
+ bdescs[i].bd_offset,
+ bdescs[i].bd_level + 1,
+ &bdescs[i].bd_blocknr);
+ if (ret < 0) {
+ if (ret != -ENOENT) {
+ up_read(&nilfs->ns_segctor_sem);
+ return ret;
+ }
+ bdescs[i].bd_blocknr = 0;
+ }
+ }
+ up_read(&nilfs->ns_segctor_sem);
+ return nmembs;
+}
+
+/**
+ * nilfs_ioctl_get_bdescs - get disk block descriptors
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_do_get_bdescs() function returns information
+ * about descriptors of disk block numbers. The NILFS_IOCTL_GET_BDESCS ioctl
+ * is used by nilfs_cleanerd daemon. If successful, disk block descriptors
+ * are copied to userspace pointer @argp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during getting disk block descriptors.
+ * * %-EINVAL - Invalid arguments from userspace.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_get_bdescs(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_argv argv;
+ int ret;
+
+ if (copy_from_user(&argv, argp, sizeof(argv)))
+ return -EFAULT;
+
+ if (argv.v_size != sizeof(struct nilfs_bdesc))
+ return -EINVAL;
+
+ ret = nilfs_ioctl_wrap_copy(nilfs, &argv, _IOC_DIR(cmd),
+ nilfs_ioctl_do_get_bdescs);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &argv, sizeof(argv)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_move_inode_block - prepare data/node block for moving by GC
+ * @inode: inode object
+ * @vdesc: descriptor of virtual block number
+ * @buffers: list of moving buffers
+ *
+ * Description: nilfs_ioctl_move_inode_block() function registers data/node
+ * buffer in the GC pagecache and submit read request.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EEXIST - Block conflict detected.
+ * * %-EINVAL - Invalid virtual block descriptor.
+ * * %-EIO - I/O error.
+ * * %-ENOENT - Requested block doesn't exist.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_move_inode_block(struct inode *inode,
+ struct nilfs_vdesc *vdesc,
+ struct list_head *buffers)
+{
+ struct buffer_head *bh;
+ __u64 limit_blkidx = (__u64)inode->i_sb->s_maxbytes >> inode->i_blkbits;
+ int ret;
+
+ /*
+ * vblocknr 0 is reserved as an invalid pointer. Also, limit_blkidx
+ * ensures that the page index converted from vd_vblocknr never
+ * overflows the page cache limit and respects the architecture's bmap
+ * key width.
+ */
+ if (unlikely(vdesc->vd_vblocknr == 0 ||
+ vdesc->vd_vblocknr >= limit_blkidx))
+ return -EINVAL;
+
+ if (vdesc->vd_flags == 0) {
+ if (unlikely(vdesc->vd_offset >= limit_blkidx))
+ return -EINVAL;
+
+ ret = nilfs_gccache_submit_read_data(
+ inode, vdesc->vd_offset, vdesc->vd_blocknr,
+ vdesc->vd_vblocknr, &bh);
+ } else {
+ ret = nilfs_gccache_submit_read_node(
+ inode, vdesc->vd_blocknr, vdesc->vd_vblocknr, &bh);
+ }
+
+ if (unlikely(ret < 0)) {
+ if (ret == -ENOENT)
+ nilfs_crit(inode->i_sb,
+ "%s: invalid virtual block address (%s): ino=%llu, cno=%llu, offset=%llu, blocknr=%llu, vblocknr=%llu",
+ __func__, vdesc->vd_flags ? "node" : "data",
+ (unsigned long long)vdesc->vd_ino,
+ (unsigned long long)vdesc->vd_cno,
+ (unsigned long long)vdesc->vd_offset,
+ (unsigned long long)vdesc->vd_blocknr,
+ (unsigned long long)vdesc->vd_vblocknr);
+ return ret;
+ }
+ if (unlikely(!list_empty(&bh->b_assoc_buffers))) {
+ nilfs_crit(inode->i_sb,
+ "%s: conflicting %s buffer: ino=%llu, cno=%llu, offset=%llu, blocknr=%llu, vblocknr=%llu",
+ __func__, vdesc->vd_flags ? "node" : "data",
+ (unsigned long long)vdesc->vd_ino,
+ (unsigned long long)vdesc->vd_cno,
+ (unsigned long long)vdesc->vd_offset,
+ (unsigned long long)vdesc->vd_blocknr,
+ (unsigned long long)vdesc->vd_vblocknr);
+ brelse(bh);
+ return -EEXIST;
+ }
+ list_add_tail(&bh->b_assoc_buffers, buffers);
+ return 0;
+}
+
+/**
+ * nilfs_ioctl_move_blocks - move valid inode's blocks during garbage collection
+ * @sb: superblock object
+ * @argv: vector of arguments from userspace
+ * @buf: array of nilfs_vdesc structures
+ *
+ * Description: nilfs_ioctl_move_blocks() function reads valid data/node
+ * blocks that garbage collector specified with the array of nilfs_vdesc
+ * structures and stores them into page caches of GC inodes.
+ *
+ * Return: Number of processed nilfs_vdesc structures on success, or
+ * a negative error code on failure.
+ */
+static int nilfs_ioctl_move_blocks(struct super_block *sb,
+ struct nilfs_argv *argv, void *buf)
+{
+ size_t nmembs = argv->v_nmembs;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct inode *inode;
+ struct nilfs_vdesc *vdesc;
+ struct buffer_head *bh, *n;
+ LIST_HEAD(buffers);
+ u64 ino;
+ __u64 cno;
+ int i, ret;
+
+ for (i = 0, vdesc = buf; i < nmembs; ) {
+ ino = vdesc->vd_ino;
+ cno = vdesc->vd_cno;
+ inode = nilfs_iget_for_gc(sb, ino, cno);
+ if (IS_ERR(inode)) {
+ ret = PTR_ERR(inode);
+ goto failed;
+ }
+ if (list_empty(&NILFS_I(inode)->i_dirty)) {
+ /*
+ * Add the inode to GC inode list. Garbage Collection
+ * is serialized and no two processes manipulate the
+ * list simultaneously.
+ */
+ igrab(inode);
+ list_add(&NILFS_I(inode)->i_dirty,
+ &nilfs->ns_gc_inodes);
+ }
+
+ do {
+ ret = nilfs_ioctl_move_inode_block(inode, vdesc,
+ &buffers);
+ if (unlikely(ret < 0)) {
+ iput(inode);
+ goto failed;
+ }
+ vdesc++;
+ } while (++i < nmembs &&
+ vdesc->vd_ino == ino && vdesc->vd_cno == cno);
+
+ iput(inode); /* The inode still remains in GC inode list */
+ }
+
+ list_for_each_entry_safe(bh, n, &buffers, b_assoc_buffers) {
+ ret = nilfs_gccache_wait_and_mark_dirty(bh);
+ if (unlikely(ret < 0)) {
+ WARN_ON(ret == -EEXIST);
+ goto failed;
+ }
+ list_del_init(&bh->b_assoc_buffers);
+ brelse(bh);
+ }
+ return nmembs;
+
+ failed:
+ list_for_each_entry_safe(bh, n, &buffers, b_assoc_buffers) {
+ list_del_init(&bh->b_assoc_buffers);
+ brelse(bh);
+ }
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_delete_checkpoints - delete checkpoints
+ * @nilfs: nilfs object
+ * @argv: vector of arguments from userspace
+ * @buf: array of periods of checkpoints numbers
+ *
+ * Description: nilfs_ioctl_delete_checkpoints() function deletes checkpoints
+ * in the period from p_start to p_end, excluding p_end itself. The checkpoints
+ * which have been already deleted are ignored.
+ *
+ * Return: Number of processed nilfs_period structures on success, or one of
+ * the following negative error codes on failure:
+ * * %-EINVAL - invalid checkpoints.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_delete_checkpoints(struct the_nilfs *nilfs,
+ struct nilfs_argv *argv, void *buf)
+{
+ size_t nmembs = argv->v_nmembs;
+ struct inode *cpfile = nilfs->ns_cpfile;
+ struct nilfs_period *periods = buf;
+ int ret, i;
+
+ for (i = 0; i < nmembs; i++) {
+ ret = nilfs_cpfile_delete_checkpoints(
+ cpfile, periods[i].p_start, periods[i].p_end);
+ if (ret < 0)
+ return ret;
+ }
+ return nmembs;
+}
+
+/**
+ * nilfs_ioctl_free_vblocknrs - free virtual block numbers
+ * @nilfs: nilfs object
+ * @argv: vector of arguments from userspace
+ * @buf: array of virtual block numbers
+ *
+ * Description: nilfs_ioctl_free_vblocknrs() function frees
+ * the virtual block numbers specified by @buf and @argv->v_nmembs.
+ *
+ * Return: Number of processed virtual block numbers on success, or one of the
+ * following negative error codes on failure:
+ * * %-EIO - I/O error.
+ * * %-ENOENT - Unallocated virtual block number.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_free_vblocknrs(struct the_nilfs *nilfs,
+ struct nilfs_argv *argv, void *buf)
+{
+ size_t nmembs = argv->v_nmembs;
+ int ret;
+
+ ret = nilfs_dat_freev(nilfs->ns_dat, buf, nmembs);
+
+ return (ret < 0) ? ret : nmembs;
+}
+
+/**
+ * nilfs_ioctl_mark_blocks_dirty - mark blocks dirty
+ * @nilfs: nilfs object
+ * @argv: vector of arguments from userspace
+ * @buf: array of block descriptors
+ *
+ * Description: nilfs_ioctl_mark_blocks_dirty() function marks
+ * metadata file or data blocks as dirty.
+ *
+ * Return: Number of processed block descriptors on success, or one of the
+ * following negative error codes on failure:
+ * * %-EIO - I/O error.
+ * * %-ENOENT - Non-existent block (hole block).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_mark_blocks_dirty(struct the_nilfs *nilfs,
+ struct nilfs_argv *argv, void *buf)
+{
+ size_t nmembs = argv->v_nmembs;
+ struct nilfs_bmap *bmap = NILFS_I(nilfs->ns_dat)->i_bmap;
+ struct nilfs_bdesc *bdescs = buf;
+ struct buffer_head *bh;
+ int ret, i;
+
+ for (i = 0; i < nmembs; i++) {
+ /*
+ * bd_oblocknr must never be 0 as block 0
+ * is never a valid GC target block
+ */
+ if (unlikely(!bdescs[i].bd_oblocknr))
+ return -EINVAL;
+ /* XXX: use macro or inline func to check liveness */
+ ret = nilfs_bmap_lookup_at_level(bmap,
+ bdescs[i].bd_offset,
+ bdescs[i].bd_level + 1,
+ &bdescs[i].bd_blocknr);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ return ret;
+ bdescs[i].bd_blocknr = 0;
+ }
+ if (bdescs[i].bd_blocknr != bdescs[i].bd_oblocknr)
+ /* skip dead block */
+ continue;
+ if (bdescs[i].bd_level == 0) {
+ ret = nilfs_mdt_get_block(nilfs->ns_dat,
+ bdescs[i].bd_offset,
+ false, NULL, &bh);
+ if (unlikely(ret)) {
+ WARN_ON(ret == -ENOENT);
+ return ret;
+ }
+ mark_buffer_dirty(bh);
+ nilfs_mdt_mark_dirty(nilfs->ns_dat);
+ put_bh(bh);
+ } else {
+ ret = nilfs_bmap_mark(bmap, bdescs[i].bd_offset,
+ bdescs[i].bd_level);
+ if (ret < 0) {
+ WARN_ON(ret == -ENOENT);
+ return ret;
+ }
+ }
+ }
+ return nmembs;
+}
+
+int nilfs_ioctl_prepare_clean_segments(struct the_nilfs *nilfs,
+ struct nilfs_argv *argv, void **kbufs)
+{
+ const char *msg;
+ int ret;
+
+ ret = nilfs_ioctl_delete_checkpoints(nilfs, &argv[1], kbufs[1]);
+ if (ret < 0) {
+ /*
+ * can safely abort because checkpoints can be removed
+ * independently.
+ */
+ msg = "cannot delete checkpoints";
+ goto failed;
+ }
+ ret = nilfs_ioctl_free_vblocknrs(nilfs, &argv[2], kbufs[2]);
+ if (ret < 0) {
+ /*
+ * can safely abort because DAT file is updated atomically
+ * using a copy-on-write technique.
+ */
+ msg = "cannot delete virtual blocks from DAT file";
+ goto failed;
+ }
+ ret = nilfs_ioctl_mark_blocks_dirty(nilfs, &argv[3], kbufs[3]);
+ if (ret < 0) {
+ /*
+ * can safely abort because the operation is nondestructive.
+ */
+ msg = "cannot mark copying blocks dirty";
+ goto failed;
+ }
+ return 0;
+
+ failed:
+ nilfs_err(nilfs->ns_sb, "error %d preparing GC: %s", ret, msg);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_clean_segments - clean segments
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_clean_segments() function makes garbage
+ * collection operation in the environment of requested parameters
+ * from userspace. The NILFS_IOCTL_CLEAN_SEGMENTS ioctl is used by
+ * nilfs_cleanerd daemon.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_ioctl_clean_segments(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct nilfs_argv argv[5];
+ static const size_t argsz[5] = {
+ sizeof(struct nilfs_vdesc),
+ sizeof(struct nilfs_period),
+ sizeof(__u64),
+ sizeof(struct nilfs_bdesc),
+ sizeof(__u64),
+ };
+ void *kbufs[5];
+ struct the_nilfs *nilfs;
+ size_t len, nsegs;
+ int n, ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ return ret;
+
+ ret = -EFAULT;
+ if (copy_from_user(argv, argp, sizeof(argv)))
+ goto out;
+
+ ret = -EINVAL;
+ nsegs = argv[4].v_nmembs;
+ if (argv[4].v_size != argsz[4])
+ goto out;
+
+ /*
+ * argv[4] points to segment numbers this ioctl cleans. We
+ * use kmalloc() for its buffer because the memory used for the
+ * segment numbers is small enough.
+ */
+ kbufs[4] = memdup_array_user(u64_to_user_ptr(argv[4].v_base),
+ nsegs, sizeof(__u64));
+ if (IS_ERR(kbufs[4])) {
+ ret = PTR_ERR(kbufs[4]);
+ goto out;
+ }
+ nilfs = inode->i_sb->s_fs_info;
+
+ for (n = 0; n < 4; n++) {
+ ret = -EINVAL;
+ if (argv[n].v_size != argsz[n])
+ goto out_free;
+
+ if (argv[n].v_nmembs > nsegs * nilfs->ns_blocks_per_segment)
+ goto out_free;
+
+ if (argv[n].v_nmembs >= UINT_MAX / argv[n].v_size)
+ goto out_free;
+
+ len = argv[n].v_size * argv[n].v_nmembs;
+ if (len == 0) {
+ kbufs[n] = NULL;
+ continue;
+ }
+
+ kbufs[n] = vmemdup_user(u64_to_user_ptr(argv[n].v_base), len);
+ if (IS_ERR(kbufs[n])) {
+ ret = PTR_ERR(kbufs[n]);
+ goto out_free;
+ }
+ }
+
+ /*
+ * nilfs_ioctl_move_blocks() will call nilfs_iget_for_gc(),
+ * which will operates an inode list without blocking.
+ * To protect the list from concurrent operations,
+ * nilfs_ioctl_move_blocks should be atomic operation.
+ */
+ if (test_and_set_bit(THE_NILFS_GC_RUNNING, &nilfs->ns_flags)) {
+ ret = -EBUSY;
+ goto out_free;
+ }
+
+ ret = nilfs_ioctl_move_blocks(inode->i_sb, &argv[0], kbufs[0]);
+ if (ret < 0) {
+ nilfs_err(inode->i_sb,
+ "error %d preparing GC: cannot read source blocks",
+ ret);
+ } else {
+ if (nilfs_sb_need_update(nilfs))
+ set_nilfs_discontinued(nilfs);
+ ret = nilfs_clean_segments(inode->i_sb, argv, kbufs);
+ }
+
+ nilfs_remove_all_gcinodes(nilfs);
+ clear_nilfs_gc_running(nilfs);
+
+out_free:
+ while (--n >= 0)
+ kvfree(kbufs[n]);
+ kfree(kbufs[4]);
+out:
+ mnt_drop_write_file(filp);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_sync - make a checkpoint
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_sync() function constructs a logical segment
+ * for checkpointing. This function guarantees that all modified data
+ * and metadata are written out to the device when it successfully
+ * returned.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during execution of requested operation.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No space left on device (only in a panic state).
+ * * %-ERESTARTSYS - Interrupted.
+ * * %-EROFS - Read only filesystem.
+ */
+static int nilfs_ioctl_sync(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ __u64 cno;
+ int ret;
+ struct the_nilfs *nilfs;
+
+ ret = nilfs_construct_segment(inode->i_sb);
+ if (ret < 0)
+ return ret;
+
+ nilfs = inode->i_sb->s_fs_info;
+ ret = nilfs_flush_device(nilfs);
+ if (ret < 0)
+ return ret;
+
+ if (argp != NULL) {
+ down_read(&nilfs->ns_segctor_sem);
+ cno = nilfs->ns_cno - 1;
+ up_read(&nilfs->ns_segctor_sem);
+ if (copy_to_user(argp, &cno, sizeof(cno)))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/**
+ * nilfs_ioctl_resize - resize NILFS2 volume
+ * @inode: inode object
+ * @filp: file object
+ * @argp: pointer on argument from userspace
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_ioctl_resize(struct inode *inode, struct file *filp,
+ void __user *argp)
+{
+ __u64 newsize;
+ int ret = -EPERM;
+
+ if (!capable(CAP_SYS_ADMIN))
+ goto out;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ goto out;
+
+ ret = -EFAULT;
+ if (copy_from_user(&newsize, argp, sizeof(newsize)))
+ goto out_drop_write;
+
+ ret = nilfs_resize_fs(inode->i_sb, newsize);
+
+out_drop_write:
+ mnt_drop_write_file(filp);
+out:
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_trim_fs() - trim ioctl handle function
+ * @inode: inode object
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_trim_fs is the FITRIM ioctl handle function. It
+ * checks the arguments from userspace and calls nilfs_sufile_trim_fs, which
+ * performs the actual trim operation.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_ioctl_trim_fs(struct inode *inode, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct fstrim_range range;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!bdev_max_discard_sectors(nilfs->ns_bdev))
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&range, argp, sizeof(range)))
+ return -EFAULT;
+
+ range.minlen = max_t(u64, range.minlen,
+ bdev_discard_granularity(nilfs->ns_bdev));
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_sufile_trim_fs(nilfs->ns_sufile, &range);
+ up_read(&nilfs->ns_segctor_sem);
+
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &range, sizeof(range)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * nilfs_ioctl_set_alloc_range - limit range of segments to be allocated
+ * @inode: inode object
+ * @argp: pointer on argument from userspace
+ *
+ * Description: nilfs_ioctl_set_alloc_range() function defines lower limit
+ * of segments in bytes and upper limit of segments in bytes.
+ * The NILFS_IOCTL_SET_ALLOC_RANGE is used by nilfs_resize utility.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_ioctl_set_alloc_range(struct inode *inode, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ __u64 range[2];
+ __u64 minseg, maxseg;
+ unsigned long segbytes;
+ int ret = -EPERM;
+
+ if (!capable(CAP_SYS_ADMIN))
+ goto out;
+
+ ret = -EFAULT;
+ if (copy_from_user(range, argp, sizeof(__u64[2])))
+ goto out;
+
+ ret = -ERANGE;
+ if (range[1] > bdev_nr_bytes(inode->i_sb->s_bdev))
+ goto out;
+
+ segbytes = nilfs->ns_blocks_per_segment * nilfs->ns_blocksize;
+
+ minseg = range[0] + segbytes - 1;
+ minseg = div64_ul(minseg, segbytes);
+
+ if (range[1] < 4096)
+ goto out;
+
+ maxseg = NILFS_SB2_OFFSET_BYTES(range[1]);
+ if (maxseg < segbytes)
+ goto out;
+
+ maxseg = div64_ul(maxseg, segbytes);
+ maxseg--;
+
+ ret = nilfs_sufile_set_alloc_range(nilfs->ns_sufile, minseg, maxseg);
+out:
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_get_info - wrapping function of get metadata info
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ * @membsz: size of an item in bytes
+ * @dofunc: concrete function of getting metadata info
+ *
+ * Description: nilfs_ioctl_get_info() gets metadata info by means of
+ * calling dofunc() function. The requested metadata information is copied
+ * to userspace memory @argp.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Failure during execution of requested operation.
+ * * %-EINVAL - Invalid arguments from userspace.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_ioctl_get_info(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp,
+ size_t membsz,
+ ssize_t (*dofunc)(struct the_nilfs *,
+ __u64 *, int,
+ void *, size_t, size_t))
+
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_argv argv;
+ int ret;
+
+ if (copy_from_user(&argv, argp, sizeof(argv)))
+ return -EFAULT;
+
+ if (argv.v_size < membsz)
+ return -EINVAL;
+
+ ret = nilfs_ioctl_wrap_copy(nilfs, &argv, _IOC_DIR(cmd), dofunc);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &argv, sizeof(argv)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_set_suinfo - set segment usage info
+ * @inode: inode object
+ * @filp: file object
+ * @cmd: ioctl's request code
+ * @argp: pointer on argument from userspace
+ *
+ * Description: Expects an array of nilfs_suinfo_update structures
+ * encapsulated in nilfs_argv and updates the segment usage info
+ * according to the flags in nilfs_suinfo_update.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EEXIST - Block conflict detected.
+ * * %-EFAULT - Error copying input data.
+ * * %-EINVAL - Invalid values in input (segment number, flags or nblocks).
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EPERM - Not enough permissions.
+ */
+static int nilfs_ioctl_set_suinfo(struct inode *inode, struct file *filp,
+ unsigned int cmd, void __user *argp)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_transaction_info ti;
+ struct nilfs_argv argv;
+ size_t len;
+ void *kbuf;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ return ret;
+
+ ret = -EFAULT;
+ if (copy_from_user(&argv, argp, sizeof(argv)))
+ goto out;
+
+ ret = -EINVAL;
+ if (argv.v_size < sizeof(struct nilfs_suinfo_update))
+ goto out;
+
+ if (argv.v_nmembs > nilfs->ns_nsegments)
+ goto out;
+
+ if (argv.v_nmembs >= UINT_MAX / argv.v_size)
+ goto out;
+
+ len = argv.v_size * argv.v_nmembs;
+ if (!len) {
+ ret = 0;
+ goto out;
+ }
+
+ kbuf = vmemdup_user(u64_to_user_ptr(argv.v_base), len);
+ if (IS_ERR(kbuf)) {
+ ret = PTR_ERR(kbuf);
+ goto out;
+ }
+
+ nilfs_transaction_begin(inode->i_sb, &ti, 0);
+ ret = nilfs_sufile_set_suinfo(nilfs->ns_sufile, kbuf, argv.v_size,
+ argv.v_nmembs);
+ if (unlikely(ret < 0))
+ nilfs_transaction_abort(inode->i_sb);
+ else
+ nilfs_transaction_commit(inode->i_sb); /* never fails */
+
+ kvfree(kbuf);
+out:
+ mnt_drop_write_file(filp);
+ return ret;
+}
+
+/**
+ * nilfs_ioctl_get_fslabel - get the volume name of the file system
+ * @sb: super block instance
+ * @argp: pointer to userspace memory where the volume name should be stored
+ *
+ * Return: 0 on success, %-EFAULT if copying to userspace memory fails.
+ */
+static int nilfs_ioctl_get_fslabel(struct super_block *sb, void __user *argp)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ char label[NILFS_MAX_VOLUME_NAME + 1];
+
+ BUILD_BUG_ON(NILFS_MAX_VOLUME_NAME >= FSLABEL_MAX);
+
+ down_read(&nilfs->ns_sem);
+ memtostr_pad(label, nilfs->ns_sbp[0]->s_volume_name);
+ up_read(&nilfs->ns_sem);
+
+ if (copy_to_user(argp, label, sizeof(label)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * nilfs_ioctl_set_fslabel - set the volume name of the file system
+ * @sb: super block instance
+ * @filp: file object
+ * @argp: pointer to userspace memory that contains the volume name
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EFAULT - Error copying input data.
+ * * %-EINVAL - Label length exceeds record size in superblock.
+ * * %-EIO - I/O error.
+ * * %-EPERM - Operation not permitted (insufficient permissions).
+ * * %-EROFS - Read only file system.
+ */
+static int nilfs_ioctl_set_fslabel(struct super_block *sb, struct file *filp,
+ void __user *argp)
+{
+ char label[NILFS_MAX_VOLUME_NAME + 1];
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+ size_t len;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = mnt_want_write_file(filp);
+ if (ret)
+ return ret;
+
+ if (copy_from_user(label, argp, NILFS_MAX_VOLUME_NAME + 1)) {
+ ret = -EFAULT;
+ goto out_drop_write;
+ }
+
+ len = strnlen(label, NILFS_MAX_VOLUME_NAME + 1);
+ if (len > NILFS_MAX_VOLUME_NAME) {
+ nilfs_err(sb, "unable to set label with more than %zu bytes",
+ NILFS_MAX_VOLUME_NAME);
+ ret = -EINVAL;
+ goto out_drop_write;
+ }
+
+ down_write(&nilfs->ns_sem);
+ sbp = nilfs_prepare_super(sb, false);
+ if (unlikely(!sbp)) {
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ strtomem_pad(sbp[0]->s_volume_name, label, 0);
+ if (sbp[1])
+ strtomem_pad(sbp[1]->s_volume_name, label, 0);
+
+ ret = nilfs_commit_super(sb, NILFS_SB_COMMIT_ALL);
+
+out_unlock:
+ up_write(&nilfs->ns_sem);
+out_drop_write:
+ mnt_drop_write_file(filp);
+ return ret;
+}
+
+long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct inode *inode = file_inode(filp);
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case FS_IOC_GETVERSION:
+ return nilfs_ioctl_getversion(inode, argp);
+ case NILFS_IOCTL_CHANGE_CPMODE:
+ return nilfs_ioctl_change_cpmode(inode, filp, cmd, argp);
+ case NILFS_IOCTL_DELETE_CHECKPOINT:
+ return nilfs_ioctl_delete_checkpoint(inode, filp, cmd, argp);
+ case NILFS_IOCTL_GET_CPINFO:
+ return nilfs_ioctl_get_info(inode, filp, cmd, argp,
+ sizeof(struct nilfs_cpinfo),
+ nilfs_ioctl_do_get_cpinfo);
+ case NILFS_IOCTL_GET_CPSTAT:
+ return nilfs_ioctl_get_cpstat(inode, filp, cmd, argp);
+ case NILFS_IOCTL_GET_SUINFO:
+ return nilfs_ioctl_get_info(inode, filp, cmd, argp,
+ sizeof(struct nilfs_suinfo),
+ nilfs_ioctl_do_get_suinfo);
+ case NILFS_IOCTL_SET_SUINFO:
+ return nilfs_ioctl_set_suinfo(inode, filp, cmd, argp);
+ case NILFS_IOCTL_GET_SUSTAT:
+ return nilfs_ioctl_get_sustat(inode, filp, cmd, argp);
+ case NILFS_IOCTL_GET_VINFO:
+ return nilfs_ioctl_get_info(inode, filp, cmd, argp,
+ sizeof(struct nilfs_vinfo),
+ nilfs_ioctl_do_get_vinfo);
+ case NILFS_IOCTL_GET_BDESCS:
+ return nilfs_ioctl_get_bdescs(inode, filp, cmd, argp);
+ case NILFS_IOCTL_CLEAN_SEGMENTS:
+ return nilfs_ioctl_clean_segments(inode, filp, cmd, argp);
+ case NILFS_IOCTL_SYNC:
+ return nilfs_ioctl_sync(inode, filp, cmd, argp);
+ case NILFS_IOCTL_RESIZE:
+ return nilfs_ioctl_resize(inode, filp, argp);
+ case NILFS_IOCTL_SET_ALLOC_RANGE:
+ return nilfs_ioctl_set_alloc_range(inode, argp);
+ case FITRIM:
+ return nilfs_ioctl_trim_fs(inode, argp);
+ case FS_IOC_GETFSLABEL:
+ return nilfs_ioctl_get_fslabel(inode->i_sb, argp);
+ case FS_IOC_SETFSLABEL:
+ return nilfs_ioctl_set_fslabel(inode->i_sb, filp, argp);
+ default:
+ return -ENOTTY;
+ }
+}
+
+#ifdef CONFIG_COMPAT
+long nilfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case FS_IOC32_GETVERSION:
+ cmd = FS_IOC_GETVERSION;
+ break;
+ case NILFS_IOCTL_CHANGE_CPMODE:
+ case NILFS_IOCTL_DELETE_CHECKPOINT:
+ case NILFS_IOCTL_GET_CPINFO:
+ case NILFS_IOCTL_GET_CPSTAT:
+ case NILFS_IOCTL_GET_SUINFO:
+ case NILFS_IOCTL_SET_SUINFO:
+ case NILFS_IOCTL_GET_SUSTAT:
+ case NILFS_IOCTL_GET_VINFO:
+ case NILFS_IOCTL_GET_BDESCS:
+ case NILFS_IOCTL_CLEAN_SEGMENTS:
+ case NILFS_IOCTL_SYNC:
+ case NILFS_IOCTL_RESIZE:
+ case NILFS_IOCTL_SET_ALLOC_RANGE:
+ case FITRIM:
+ case FS_IOC_GETFSLABEL:
+ case FS_IOC_SETFSLABEL:
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return nilfs_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c
new file mode 100644
index 000000000..2a435349f
--- /dev/null
+++ b/fs/nilfs2/mdt.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Meta data file for NILFS
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/mpage.h>
+#include <linux/mm.h>
+#include <linux/writeback.h>
+#include <linux/backing-dev.h>
+#include <linux/swap.h>
+#include <linux/slab.h>
+#include "nilfs.h"
+#include "btnode.h"
+#include "segment.h"
+#include "page.h"
+#include "mdt.h"
+#include "alloc.h" /* nilfs_palloc_destroy_cache() */
+
+#include <trace/events/nilfs2.h>
+
+#define NILFS_MDT_MAX_RA_BLOCKS (16 - 1)
+
+
+static int
+nilfs_mdt_insert_new_block(struct inode *inode, unsigned long block,
+ struct buffer_head *bh,
+ void (*init_block)(struct inode *,
+ struct buffer_head *, void *))
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct folio *folio = bh->b_folio;
+ void *from;
+ int ret;
+
+ /* Caller exclude read accesses using page lock */
+
+ /* set_buffer_new(bh); */
+ bh->b_blocknr = 0;
+
+ ret = nilfs_bmap_insert(ii->i_bmap, block, (unsigned long)bh);
+ if (unlikely(ret))
+ return ret;
+
+ set_buffer_mapped(bh);
+
+ /* Initialize block (block size > PAGE_SIZE not yet supported) */
+ from = kmap_local_folio(folio, offset_in_folio(folio, bh->b_data));
+ memset(from, 0, bh->b_size);
+ if (init_block)
+ init_block(inode, bh, from);
+ kunmap_local(from);
+
+ flush_dcache_folio(folio);
+
+ set_buffer_uptodate(bh);
+ mark_buffer_dirty(bh);
+ nilfs_mdt_mark_dirty(inode);
+
+ trace_nilfs2_mdt_insert_new_block(inode, inode->i_ino, block);
+
+ return 0;
+}
+
+static int nilfs_mdt_create_block(struct inode *inode, unsigned long block,
+ struct buffer_head **out_bh,
+ void (*init_block)(struct inode *,
+ struct buffer_head *,
+ void *))
+{
+ struct super_block *sb = inode->i_sb;
+ struct nilfs_transaction_info ti;
+ struct buffer_head *bh;
+ int err;
+
+ nilfs_transaction_begin(sb, &ti, 0);
+
+ err = -ENOMEM;
+ bh = nilfs_grab_buffer(inode, inode->i_mapping, block, 0);
+ if (unlikely(!bh))
+ goto failed_unlock;
+
+ err = -EEXIST;
+ if (buffer_uptodate(bh))
+ goto failed_bh;
+
+ wait_on_buffer(bh);
+ if (buffer_uptodate(bh))
+ goto failed_bh;
+
+ err = nilfs_mdt_insert_new_block(inode, block, bh, init_block);
+ if (likely(!err)) {
+ get_bh(bh);
+ *out_bh = bh;
+ }
+
+ failed_bh:
+ folio_unlock(bh->b_folio);
+ folio_put(bh->b_folio);
+ brelse(bh);
+
+ failed_unlock:
+ if (likely(!err))
+ err = nilfs_transaction_commit(sb);
+ else
+ nilfs_transaction_abort(sb);
+
+ return err;
+}
+
+static int
+nilfs_mdt_submit_block(struct inode *inode, unsigned long blkoff, blk_opf_t opf,
+ struct buffer_head **out_bh)
+{
+ struct buffer_head *bh;
+ __u64 blknum = 0;
+ int ret = -ENOMEM;
+
+ bh = nilfs_grab_buffer(inode, inode->i_mapping, blkoff, 0);
+ if (unlikely(!bh))
+ goto failed;
+
+ ret = -EEXIST; /* internal code */
+ if (buffer_uptodate(bh))
+ goto out;
+
+ if (opf & REQ_RAHEAD) {
+ if (!trylock_buffer(bh)) {
+ ret = -EBUSY;
+ goto failed_bh;
+ }
+ } else /* opf == REQ_OP_READ */
+ lock_buffer(bh);
+
+ if (buffer_uptodate(bh)) {
+ unlock_buffer(bh);
+ goto out;
+ }
+
+ ret = nilfs_bmap_lookup(NILFS_I(inode)->i_bmap, blkoff, &blknum);
+ if (unlikely(ret)) {
+ unlock_buffer(bh);
+ goto failed_bh;
+ }
+ map_bh(bh, inode->i_sb, (sector_t)blknum);
+
+ bh_submit(bh, opf, bh_end_read);
+ ret = 0;
+
+ trace_nilfs2_mdt_submit_block(inode, inode->i_ino, blkoff,
+ opf & REQ_OP_MASK);
+ out:
+ get_bh(bh);
+ *out_bh = bh;
+
+ failed_bh:
+ folio_unlock(bh->b_folio);
+ folio_put(bh->b_folio);
+ brelse(bh);
+ failed:
+ return ret;
+}
+
+static int nilfs_mdt_read_block(struct inode *inode, unsigned long block,
+ int readahead, struct buffer_head **out_bh)
+{
+ struct buffer_head *first_bh, *bh;
+ unsigned long blkoff;
+ int i, nr_ra_blocks = NILFS_MDT_MAX_RA_BLOCKS;
+ int err;
+
+ err = nilfs_mdt_submit_block(inode, block, REQ_OP_READ, &first_bh);
+ if (err == -EEXIST) /* internal code */
+ goto out;
+
+ if (unlikely(err))
+ goto failed;
+
+ if (readahead) {
+ blkoff = block + 1;
+ for (i = 0; i < nr_ra_blocks; i++, blkoff++) {
+ err = nilfs_mdt_submit_block(inode, blkoff,
+ REQ_OP_READ | REQ_RAHEAD, &bh);
+ if (likely(!err || err == -EEXIST))
+ brelse(bh);
+ else if (err != -EBUSY)
+ break;
+ /* abort readahead if bmap lookup failed */
+ if (!buffer_locked(first_bh))
+ goto out_no_wait;
+ }
+ }
+
+ wait_on_buffer(first_bh);
+
+ out_no_wait:
+ err = -EIO;
+ if (!buffer_uptodate(first_bh)) {
+ nilfs_err(inode->i_sb,
+ "I/O error reading meta-data file (ino=%llu, block-offset=%lu)",
+ inode->i_ino, block);
+ goto failed_bh;
+ }
+ out:
+ *out_bh = first_bh;
+ return 0;
+
+ failed_bh:
+ brelse(first_bh);
+ failed:
+ return err;
+}
+
+/**
+ * nilfs_mdt_get_block - read or create a buffer on meta data file.
+ * @inode: inode of the meta data file
+ * @blkoff: block offset
+ * @create: create flag
+ * @init_block: initializer used for newly allocated block
+ * @out_bh: output of a pointer to the buffer_head
+ *
+ * nilfs_mdt_get_block() looks up the specified buffer and tries to create
+ * a new buffer if @create is not zero. If (and only if) this function
+ * succeeds, it stores a pointer to the retrieved buffer head in the location
+ * pointed to by @out_bh.
+ *
+ * The retrieved buffer may be either an existing one or a newly allocated one.
+ * For a newly created buffer, if the callback function argument @init_block
+ * is non-NULL, the callback will be called with the buffer locked to format
+ * the block.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - The specified block does not exist (hole block).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EROFS - Read only filesystem (for create mode).
+ */
+int nilfs_mdt_get_block(struct inode *inode, unsigned long blkoff, int create,
+ void (*init_block)(struct inode *,
+ struct buffer_head *, void *),
+ struct buffer_head **out_bh)
+{
+ int ret;
+
+ /* Should be rewritten with merging nilfs_mdt_read_block() */
+ retry:
+ ret = nilfs_mdt_read_block(inode, blkoff, !create, out_bh);
+ if (!create || ret != -ENOENT)
+ return ret;
+
+ ret = nilfs_mdt_create_block(inode, blkoff, out_bh, init_block);
+ if (unlikely(ret == -EEXIST)) {
+ /* create = 0; */ /* limit read-create loop retries */
+ goto retry;
+ }
+ return ret;
+}
+
+/**
+ * nilfs_mdt_find_block - find and get a buffer on meta data file.
+ * @inode: inode of the meta data file
+ * @start: start block offset (inclusive)
+ * @end: end block offset (inclusive)
+ * @blkoff: block offset
+ * @out_bh: place to store a pointer to buffer_head struct
+ *
+ * nilfs_mdt_find_block() looks up an existing block in range of
+ * [@start, @end] and stores pointer to a buffer head of the block to
+ * @out_bh, and block offset to @blkoff, respectively. @out_bh and
+ * @blkoff are substituted only when zero is returned.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - No block was found in the range.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_mdt_find_block(struct inode *inode, unsigned long start,
+ unsigned long end, unsigned long *blkoff,
+ struct buffer_head **out_bh)
+{
+ __u64 next;
+ int ret;
+
+ if (unlikely(start > end))
+ return -ENOENT;
+
+ ret = nilfs_mdt_read_block(inode, start, true, out_bh);
+ if (!ret) {
+ *blkoff = start;
+ goto out;
+ }
+ if (unlikely(ret != -ENOENT || start == ULONG_MAX))
+ goto out;
+
+ ret = nilfs_bmap_seek_key(NILFS_I(inode)->i_bmap, start + 1, &next);
+ if (!ret) {
+ if (next <= end) {
+ ret = nilfs_mdt_read_block(inode, next, true, out_bh);
+ if (!ret)
+ *blkoff = next;
+ } else {
+ ret = -ENOENT;
+ }
+ }
+out:
+ return ret;
+}
+
+/**
+ * nilfs_mdt_delete_block - make a hole on the meta data file.
+ * @inode: inode of the meta data file
+ * @block: block offset
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Non-existent block.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_mdt_delete_block(struct inode *inode, unsigned long block)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ int err;
+
+ err = nilfs_bmap_delete(ii->i_bmap, block);
+ if (!err || err == -ENOENT) {
+ nilfs_mdt_mark_dirty(inode);
+ nilfs_mdt_forget_block(inode, block);
+ }
+ return err;
+}
+
+/**
+ * nilfs_mdt_forget_block - discard dirty state and try to remove the page
+ * @inode: inode of the meta data file
+ * @block: block offset
+ *
+ * nilfs_mdt_forget_block() clears a dirty flag of the specified buffer, and
+ * tries to release the page including the buffer from a page cache.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EBUSY - Page has an active buffer.
+ * * %-ENOENT - Page cache has no page addressed by the offset.
+ */
+int nilfs_mdt_forget_block(struct inode *inode, unsigned long block)
+{
+ pgoff_t index = block >> (PAGE_SHIFT - inode->i_blkbits);
+ struct folio *folio;
+ struct buffer_head *bh;
+ int ret = 0;
+ int still_dirty;
+
+ folio = filemap_lock_folio(inode->i_mapping, index);
+ if (IS_ERR(folio))
+ return -ENOENT;
+
+ folio_wait_writeback(folio);
+
+ bh = folio_buffers(folio);
+ if (bh) {
+ unsigned long first_block = index <<
+ (PAGE_SHIFT - inode->i_blkbits);
+ bh = get_nth_bh(bh, block - first_block);
+ nilfs_forget_buffer(bh);
+ }
+ still_dirty = folio_test_dirty(folio);
+ folio_unlock(folio);
+ folio_put(folio);
+
+ if (still_dirty ||
+ invalidate_inode_pages2_range(inode->i_mapping, index, index) != 0)
+ ret = -EBUSY;
+ return ret;
+}
+
+int nilfs_mdt_fetch_dirty(struct inode *inode)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+
+ if (nilfs_bmap_test_and_clear_dirty(ii->i_bmap)) {
+ set_bit(NILFS_I_DIRTY, &ii->i_state);
+ return 1;
+ }
+ return test_bit(NILFS_I_DIRTY, &ii->i_state);
+}
+
+static int nilfs_mdt_write_folio(struct folio *folio,
+ struct writeback_control *wbc)
+{
+ struct inode *inode = folio->mapping->host;
+ struct super_block *sb;
+ int err = 0;
+
+ if (inode && sb_rdonly(inode->i_sb)) {
+ /*
+ * It means that filesystem was remounted in read-only
+ * mode because of error or metadata corruption. But we
+ * have dirty folios that try to be flushed in background.
+ * So, here we simply discard this dirty folio.
+ */
+ nilfs_clear_folio_dirty(folio);
+ folio_unlock(folio);
+ return -EROFS;
+ }
+
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
+
+ if (!inode)
+ return 0;
+
+ sb = inode->i_sb;
+
+ if (wbc->sync_mode == WB_SYNC_ALL)
+ err = nilfs_construct_segment(sb);
+
+ return err;
+}
+
+static int nilfs_mdt_writeback(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ struct folio *folio = NULL;
+ int error;
+
+ while ((folio = writeback_iter(mapping, wbc, folio, &error)))
+ error = nilfs_mdt_write_folio(folio, wbc);
+
+ return error;
+}
+
+static const struct address_space_operations def_mdt_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .writepages = nilfs_mdt_writeback,
+ .migrate_folio = buffer_migrate_folio_norefs,
+};
+
+static const struct inode_operations def_mdt_iops;
+static const struct file_operations def_mdt_fops;
+
+
+int nilfs_mdt_init(struct inode *inode, gfp_t gfp_mask, size_t objsz)
+{
+ struct nilfs_mdt_info *mi;
+
+ mi = kzalloc(max(sizeof(*mi), objsz), GFP_NOFS);
+ if (!mi)
+ return -ENOMEM;
+
+ init_rwsem(&mi->mi_sem);
+ inode->i_private = mi;
+
+ inode->i_mode = S_IFREG;
+ mapping_set_gfp_mask(inode->i_mapping, gfp_mask);
+
+ inode->i_op = &def_mdt_iops;
+ inode->i_fop = &def_mdt_fops;
+ inode->i_mapping->a_ops = &def_mdt_aops;
+
+ return 0;
+}
+
+/**
+ * nilfs_mdt_clear - do cleanup for the metadata file
+ * @inode: inode of the metadata file
+ */
+void nilfs_mdt_clear(struct inode *inode)
+{
+ struct nilfs_mdt_info *mdi = NILFS_MDT(inode);
+ struct nilfs_shadow_map *shadow = mdi->mi_shadow;
+
+ if (mdi->mi_palloc_cache)
+ nilfs_palloc_destroy_cache(inode);
+
+ if (shadow) {
+ struct inode *s_inode = shadow->inode;
+
+ shadow->inode = NULL;
+ iput(s_inode);
+ mdi->mi_shadow = NULL;
+ }
+}
+
+/**
+ * nilfs_mdt_destroy - release resources used by the metadata file
+ * @inode: inode of the metadata file
+ */
+void nilfs_mdt_destroy(struct inode *inode)
+{
+ struct nilfs_mdt_info *mdi = NILFS_MDT(inode);
+
+ kfree(mdi->mi_bgl); /* kfree(NULL) is safe */
+ kfree(mdi);
+}
+
+void nilfs_mdt_set_entry_size(struct inode *inode, unsigned int entry_size,
+ unsigned int header_size)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+
+ mi->mi_entry_size = entry_size;
+ mi->mi_entries_per_block = i_blocksize(inode) / entry_size;
+ mi->mi_first_entry_offset = DIV_ROUND_UP(header_size, entry_size);
+}
+
+/**
+ * nilfs_mdt_setup_shadow_map - setup shadow map and bind it to metadata file
+ * @inode: inode of the metadata file
+ * @shadow: shadow mapping
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_mdt_setup_shadow_map(struct inode *inode,
+ struct nilfs_shadow_map *shadow)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+ struct inode *s_inode;
+
+ INIT_LIST_HEAD(&shadow->frozen_buffers);
+
+ s_inode = nilfs_iget_for_shadow(inode);
+ if (IS_ERR(s_inode))
+ return PTR_ERR(s_inode);
+
+ shadow->inode = s_inode;
+ mi->mi_shadow = shadow;
+ return 0;
+}
+
+/**
+ * nilfs_mdt_save_to_shadow_map - copy bmap and dirty pages to shadow map
+ * @inode: inode of the metadata file
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_mdt_save_to_shadow_map(struct inode *inode)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct nilfs_shadow_map *shadow = mi->mi_shadow;
+ struct inode *s_inode = shadow->inode;
+ int ret;
+
+ ret = nilfs_copy_dirty_pages(s_inode->i_mapping, inode->i_mapping);
+ if (ret)
+ goto out;
+
+ ret = nilfs_copy_dirty_pages(NILFS_I(s_inode)->i_assoc_inode->i_mapping,
+ ii->i_assoc_inode->i_mapping);
+ if (ret)
+ goto out;
+
+ nilfs_bmap_save(ii->i_bmap, &shadow->bmap_store);
+ out:
+ return ret;
+}
+
+int nilfs_mdt_freeze_buffer(struct inode *inode, struct buffer_head *bh)
+{
+ struct nilfs_shadow_map *shadow = NILFS_MDT(inode)->mi_shadow;
+ struct buffer_head *bh_frozen;
+ struct folio *folio;
+ int blkbits = inode->i_blkbits;
+
+ folio = filemap_grab_folio(shadow->inode->i_mapping,
+ bh->b_folio->index);
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+
+ bh_frozen = folio_buffers(folio);
+ if (!bh_frozen)
+ bh_frozen = create_empty_buffers(folio, 1 << blkbits, 0);
+
+ bh_frozen = get_nth_bh(bh_frozen,
+ offset_in_folio(folio, bh->b_data) >> blkbits);
+
+ if (!buffer_uptodate(bh_frozen))
+ nilfs_copy_buffer(bh_frozen, bh);
+ if (list_empty(&bh_frozen->b_assoc_buffers)) {
+ list_add_tail(&bh_frozen->b_assoc_buffers,
+ &shadow->frozen_buffers);
+ set_buffer_nilfs_redirected(bh);
+ } else {
+ brelse(bh_frozen); /* already frozen */
+ }
+
+ folio_unlock(folio);
+ folio_put(folio);
+ return 0;
+}
+
+struct buffer_head *
+nilfs_mdt_get_frozen_buffer(struct inode *inode, struct buffer_head *bh)
+{
+ struct nilfs_shadow_map *shadow = NILFS_MDT(inode)->mi_shadow;
+ struct buffer_head *bh_frozen = NULL;
+ struct folio *folio;
+ int n;
+
+ folio = filemap_lock_folio(shadow->inode->i_mapping,
+ bh->b_folio->index);
+ if (!IS_ERR(folio)) {
+ bh_frozen = folio_buffers(folio);
+ if (bh_frozen) {
+ n = offset_in_folio(folio, bh->b_data) >>
+ inode->i_blkbits;
+ bh_frozen = get_nth_bh(bh_frozen, n);
+ }
+ folio_unlock(folio);
+ folio_put(folio);
+ }
+ return bh_frozen;
+}
+
+static void nilfs_release_frozen_buffers(struct nilfs_shadow_map *shadow)
+{
+ struct list_head *head = &shadow->frozen_buffers;
+ struct buffer_head *bh;
+
+ while (!list_empty(head)) {
+ bh = list_first_entry(head, struct buffer_head,
+ b_assoc_buffers);
+ list_del_init(&bh->b_assoc_buffers);
+ brelse(bh); /* drop ref-count to make it releasable */
+ }
+}
+
+/**
+ * nilfs_mdt_restore_from_shadow_map - restore dirty pages and bmap state
+ * @inode: inode of the metadata file
+ */
+void nilfs_mdt_restore_from_shadow_map(struct inode *inode)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct nilfs_shadow_map *shadow = mi->mi_shadow;
+
+ down_write(&mi->mi_sem);
+
+ if (mi->mi_palloc_cache)
+ nilfs_palloc_clear_cache(inode);
+
+ nilfs_clear_dirty_pages(inode->i_mapping);
+ nilfs_copy_back_pages(inode->i_mapping, shadow->inode->i_mapping);
+
+ nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping);
+ nilfs_copy_back_pages(ii->i_assoc_inode->i_mapping,
+ NILFS_I(shadow->inode)->i_assoc_inode->i_mapping);
+
+ nilfs_bmap_restore(ii->i_bmap, &shadow->bmap_store);
+
+ up_write(&mi->mi_sem);
+}
+
+/**
+ * nilfs_mdt_clear_shadow_map - truncate pages in shadow map caches
+ * @inode: inode of the metadata file
+ */
+void nilfs_mdt_clear_shadow_map(struct inode *inode)
+{
+ struct nilfs_mdt_info *mi = NILFS_MDT(inode);
+ struct nilfs_shadow_map *shadow = mi->mi_shadow;
+ struct inode *shadow_btnc_inode = NILFS_I(shadow->inode)->i_assoc_inode;
+
+ down_write(&mi->mi_sem);
+ nilfs_release_frozen_buffers(shadow);
+ truncate_inode_pages(shadow->inode->i_mapping, 0);
+ truncate_inode_pages(shadow_btnc_inode->i_mapping, 0);
+ up_write(&mi->mi_sem);
+}
diff --git a/fs/nilfs2/mdt.h b/fs/nilfs2/mdt.h
new file mode 100644
index 000000000..9e23bab3f
--- /dev/null
+++ b/fs/nilfs2/mdt.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS meta data file prototype and definitions
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ */
+
+#ifndef _NILFS_MDT_H
+#define _NILFS_MDT_H
+
+#include <linux/buffer_head.h>
+#include <linux/blockgroup_lock.h>
+#include "nilfs.h"
+#include "page.h"
+
+/**
+ * struct nilfs_shadow_map - shadow mapping of meta data file
+ * @bmap_store: shadow copy of bmap state
+ * @inode: holder of page caches used in shadow mapping
+ * @frozen_buffers: list of frozen buffers
+ */
+struct nilfs_shadow_map {
+ struct nilfs_bmap_store bmap_store;
+ struct inode *inode;
+ struct list_head frozen_buffers;
+};
+
+/**
+ * struct nilfs_mdt_info - on-memory private data of meta data files
+ * @mi_sem: reader/writer semaphore for meta data operations
+ * @mi_bgl: per-blockgroup locking
+ * @mi_entry_size: size of an entry
+ * @mi_first_entry_offset: offset to the first entry
+ * @mi_entries_per_block: number of entries in a block
+ * @mi_palloc_cache: persistent object allocator cache
+ * @mi_shadow: shadow of bmap and page caches
+ * @mi_blocks_per_group: number of blocks in a group
+ * @mi_blocks_per_desc_block: number of blocks per descriptor block
+ */
+struct nilfs_mdt_info {
+ struct rw_semaphore mi_sem;
+ struct blockgroup_lock *mi_bgl;
+ unsigned int mi_entry_size;
+ unsigned int mi_first_entry_offset;
+ unsigned long mi_entries_per_block;
+ struct nilfs_palloc_cache *mi_palloc_cache;
+ struct nilfs_shadow_map *mi_shadow;
+ unsigned long mi_blocks_per_group;
+ unsigned long mi_blocks_per_desc_block;
+};
+
+static inline struct nilfs_mdt_info *NILFS_MDT(const struct inode *inode)
+{
+ return inode->i_private;
+}
+
+static inline int nilfs_is_metadata_file_inode(const struct inode *inode)
+{
+ return inode->i_private != NULL;
+}
+
+/* Default GFP flags using highmem */
+#define NILFS_MDT_GFP (__GFP_RECLAIM | __GFP_IO | __GFP_HIGHMEM)
+
+int nilfs_mdt_get_block(struct inode *, unsigned long, int,
+ void (*init_block)(struct inode *,
+ struct buffer_head *, void *),
+ struct buffer_head **);
+int nilfs_mdt_find_block(struct inode *inode, unsigned long start,
+ unsigned long end, unsigned long *blkoff,
+ struct buffer_head **out_bh);
+int nilfs_mdt_delete_block(struct inode *, unsigned long);
+int nilfs_mdt_forget_block(struct inode *, unsigned long);
+int nilfs_mdt_fetch_dirty(struct inode *);
+
+int nilfs_mdt_init(struct inode *inode, gfp_t gfp_mask, size_t objsz);
+void nilfs_mdt_clear(struct inode *inode);
+void nilfs_mdt_destroy(struct inode *inode);
+
+void nilfs_mdt_set_entry_size(struct inode *, unsigned int, unsigned int);
+
+int nilfs_mdt_setup_shadow_map(struct inode *inode,
+ struct nilfs_shadow_map *shadow);
+int nilfs_mdt_save_to_shadow_map(struct inode *inode);
+void nilfs_mdt_restore_from_shadow_map(struct inode *inode);
+void nilfs_mdt_clear_shadow_map(struct inode *inode);
+int nilfs_mdt_freeze_buffer(struct inode *inode, struct buffer_head *bh);
+struct buffer_head *nilfs_mdt_get_frozen_buffer(struct inode *inode,
+ struct buffer_head *bh);
+
+static inline void nilfs_mdt_mark_dirty(struct inode *inode)
+{
+ if (!test_bit(NILFS_I_DIRTY, &NILFS_I(inode)->i_state))
+ set_bit(NILFS_I_DIRTY, &NILFS_I(inode)->i_state);
+}
+
+static inline void nilfs_mdt_clear_dirty(struct inode *inode)
+{
+ clear_bit(NILFS_I_DIRTY, &NILFS_I(inode)->i_state);
+}
+
+static inline __u64 nilfs_mdt_cno(struct inode *inode)
+{
+ return ((struct the_nilfs *)inode->i_sb->s_fs_info)->ns_cno;
+}
+
+static inline spinlock_t *
+nilfs_mdt_bgl_lock(struct inode *inode, unsigned int block_group)
+{
+ return bgl_lock_ptr(NILFS_MDT(inode)->mi_bgl, block_group);
+}
+
+#endif /* _NILFS_MDT_H */
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
new file mode 100644
index 000000000..e037e0c6e
--- /dev/null
+++ b/fs/nilfs2/namei.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS pathname lookup operations.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Modified for NILFS by Amagai Yoshiji and Ryusuke Konishi.
+ */
+/*
+ * linux/fs/ext2/namei.c
+ *
+ * Copyright (C) 1992, 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * from
+ *
+ * linux/fs/minix/namei.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Big-endian to little-endian byte-swapping/bitmaps by
+ * David S. Miller (davem@caip.rutgers.edu), 1995
+ */
+
+#include <linux/pagemap.h>
+#include "nilfs.h"
+#include "export.h"
+
+#define NILFS_FID_SIZE_NON_CONNECTABLE \
+ (offsetof(struct nilfs_fid, parent_gen) / 4)
+#define NILFS_FID_SIZE_CONNECTABLE (sizeof(struct nilfs_fid) / 4)
+
+static inline int nilfs_add_nondir(struct dentry *dentry, struct inode *inode)
+{
+ int err = nilfs_add_link(dentry, inode);
+
+ if (!err) {
+ d_instantiate_new(dentry, inode);
+ return 0;
+ }
+ inode_dec_link_count(inode);
+ unlock_new_inode(inode);
+ iput(inode);
+ return err;
+}
+
+/*
+ * Methods themselves.
+ */
+
+static struct dentry *
+nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
+{
+ struct inode *inode;
+ u64 ino;
+ int res;
+
+ if (dentry->d_name.len > NILFS_NAME_LEN)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ res = nilfs_inode_by_name(dir, &dentry->d_name, &ino);
+ if (res) {
+ if (res != -ENOENT)
+ return ERR_PTR(res);
+ inode = NULL;
+ } else {
+ inode = nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino);
+ if (inode == ERR_PTR(-ESTALE)) {
+ nilfs_error(dir->i_sb,
+ "deleted inode referenced: %llu", ino);
+ return ERR_PTR(-EIO);
+ }
+ }
+
+ return d_splice_alias(inode, dentry);
+}
+
+/*
+ * By the time this is called, we already have created
+ * the directory cache entry for the new file, but it
+ * is so far negative - it has no inode.
+ *
+ * If the create succeeds, we fill in the inode information
+ * with d_instantiate().
+ */
+static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode *inode;
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
+ if (err)
+ return err;
+ inode = nilfs_new_inode(dir, mode);
+ err = PTR_ERR(inode);
+ if (!IS_ERR(inode)) {
+ inode->i_op = &nilfs_file_inode_operations;
+ inode->i_fop = &nilfs_file_operations;
+ inode->i_mapping->a_ops = &nilfs_aops;
+ nilfs_mark_inode_dirty(inode);
+ err = nilfs_add_nondir(dentry, inode);
+ }
+ if (!err)
+ err = nilfs_transaction_commit(dir->i_sb);
+ else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err;
+}
+
+static int
+nilfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode, dev_t rdev)
+{
+ struct inode *inode;
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
+ if (err)
+ return err;
+ inode = nilfs_new_inode(dir, mode);
+ err = PTR_ERR(inode);
+ if (!IS_ERR(inode)) {
+ init_special_inode(inode, inode->i_mode, rdev);
+ nilfs_mark_inode_dirty(inode);
+ err = nilfs_add_nondir(dentry, inode);
+ }
+ if (!err)
+ err = nilfs_transaction_commit(dir->i_sb);
+ else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err;
+}
+
+static int nilfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, const char *symname)
+{
+ struct nilfs_transaction_info ti;
+ struct super_block *sb = dir->i_sb;
+ unsigned int l = strlen(symname) + 1;
+ struct inode *inode;
+ int err;
+
+ if (l > sb->s_blocksize)
+ return -ENAMETOOLONG;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
+ if (err)
+ return err;
+
+ inode = nilfs_new_inode(dir, S_IFLNK | 0777);
+ err = PTR_ERR(inode);
+ if (IS_ERR(inode))
+ goto out;
+
+ /* slow symlink */
+ inode->i_op = &nilfs_symlink_inode_operations;
+ inode_nohighmem(inode);
+ mapping_set_gfp_mask(inode->i_mapping,
+ mapping_gfp_constraint(inode->i_mapping,
+ ~__GFP_FS));
+ inode->i_mapping->a_ops = &nilfs_aops;
+ err = page_symlink(inode, symname, l);
+ if (err)
+ goto out_fail;
+
+ /* mark_inode_dirty(inode); */
+ /* page_symlink() do this */
+
+ err = nilfs_add_nondir(dentry, inode);
+out:
+ if (!err)
+ err = nilfs_transaction_commit(dir->i_sb);
+ else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err;
+
+out_fail:
+ drop_nlink(inode);
+ nilfs_mark_inode_dirty(inode);
+ unlock_new_inode(inode);
+ iput(inode);
+ goto out;
+}
+
+static int nilfs_link(struct dentry *old_dentry, struct inode *dir,
+ struct dentry *dentry)
+{
+ struct inode *inode = d_inode(old_dentry);
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
+ if (err)
+ return err;
+
+ inode_set_ctime_current(inode);
+ inode_inc_link_count(inode);
+ ihold(inode);
+
+ err = nilfs_add_link(dentry, inode);
+ if (!err) {
+ d_instantiate(dentry, inode);
+ err = nilfs_transaction_commit(dir->i_sb);
+ } else {
+ inode_dec_link_count(inode);
+ iput(inode);
+ nilfs_transaction_abort(dir->i_sb);
+ }
+
+ return err;
+}
+
+static struct dentry *nilfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode *inode;
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 1);
+ if (err)
+ return ERR_PTR(err);
+
+ inc_nlink(dir);
+
+ inode = nilfs_new_inode(dir, mode);
+ err = PTR_ERR(inode);
+ if (IS_ERR(inode))
+ goto out_dir;
+
+ inode->i_op = &nilfs_dir_inode_operations;
+ inode->i_fop = &nilfs_dir_operations;
+ inode->i_mapping->a_ops = &nilfs_aops;
+
+ inc_nlink(inode);
+
+ err = nilfs_make_empty(inode, dir);
+ if (err)
+ goto out_fail;
+
+ err = nilfs_add_link(dentry, inode);
+ if (err)
+ goto out_fail;
+
+ nilfs_mark_inode_dirty(inode);
+ d_instantiate_new(dentry, inode);
+out:
+ if (!err)
+ err = nilfs_transaction_commit(dir->i_sb);
+ else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err ? ERR_PTR(err) : NULL;
+
+out_fail:
+ drop_nlink(inode);
+ drop_nlink(inode);
+ nilfs_mark_inode_dirty(inode);
+ unlock_new_inode(inode);
+ iput(inode);
+out_dir:
+ drop_nlink(dir);
+ nilfs_mark_inode_dirty(dir);
+ goto out;
+}
+
+static int nilfs_do_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *inode;
+ struct nilfs_dir_entry *de;
+ struct folio *folio;
+ int err;
+
+ de = nilfs_find_entry(dir, &dentry->d_name, &folio);
+ if (IS_ERR(de)) {
+ err = PTR_ERR(de);
+ goto out;
+ }
+
+ inode = d_inode(dentry);
+ err = -EIO;
+ if (le64_to_cpu(de->inode) != inode->i_ino)
+ goto out;
+
+ if (!inode->i_nlink) {
+ nilfs_warn(inode->i_sb,
+ "deleting nonexistent file (ino=%llu), %d",
+ inode->i_ino, inode->i_nlink);
+ set_nlink(inode, 1);
+ }
+ err = nilfs_delete_entry(de, folio);
+ folio_release_kmap(folio, de);
+ if (err)
+ goto out;
+
+ inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
+ drop_nlink(inode);
+ err = 0;
+out:
+ return err;
+}
+
+static int nilfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 0);
+ if (err)
+ return err;
+
+ err = nilfs_do_unlink(dir, dentry);
+
+ if (!err) {
+ nilfs_mark_inode_dirty(dir);
+ nilfs_mark_inode_dirty(d_inode(dentry));
+ err = nilfs_transaction_commit(dir->i_sb);
+ } else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err;
+}
+
+static int nilfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *inode = d_inode(dentry);
+ struct nilfs_transaction_info ti;
+ int err;
+
+ err = nilfs_transaction_begin(dir->i_sb, &ti, 0);
+ if (err)
+ return err;
+
+ err = -ENOTEMPTY;
+ if (nilfs_empty_dir(inode)) {
+ err = nilfs_do_unlink(dir, dentry);
+ if (!err) {
+ inode->i_size = 0;
+ drop_nlink(inode);
+ nilfs_mark_inode_dirty(inode);
+ drop_nlink(dir);
+ nilfs_mark_inode_dirty(dir);
+ }
+ }
+ if (!err)
+ err = nilfs_transaction_commit(dir->i_sb);
+ else
+ nilfs_transaction_abort(dir->i_sb);
+
+ return err;
+}
+
+static int nilfs_rename(struct mnt_idmap *idmap,
+ struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
+{
+ struct inode *old_inode = d_inode(old_dentry);
+ struct inode *new_inode = d_inode(new_dentry);
+ struct folio *dir_folio = NULL;
+ struct nilfs_dir_entry *dir_de = NULL;
+ struct folio *old_folio;
+ struct nilfs_dir_entry *old_de;
+ struct nilfs_transaction_info ti;
+ bool old_is_dir = S_ISDIR(old_inode->i_mode);
+ int err;
+
+ if (flags & ~RENAME_NOREPLACE)
+ return -EINVAL;
+
+ err = nilfs_transaction_begin(old_dir->i_sb, &ti, 1);
+ if (unlikely(err))
+ return err;
+
+ old_de = nilfs_find_entry(old_dir, &old_dentry->d_name, &old_folio);
+ if (IS_ERR(old_de)) {
+ err = PTR_ERR(old_de);
+ goto out;
+ }
+
+ if (old_is_dir && old_dir != new_dir) {
+ err = -EIO;
+ dir_de = nilfs_dotdot(old_inode, &dir_folio);
+ if (!dir_de)
+ goto out_old;
+ }
+
+ if (new_inode) {
+ struct folio *new_folio;
+ struct nilfs_dir_entry *new_de;
+
+ err = -ENOTEMPTY;
+ if (old_is_dir && !nilfs_empty_dir(new_inode))
+ goto out_dir;
+
+ new_de = nilfs_find_entry(new_dir, &new_dentry->d_name,
+ &new_folio);
+ if (IS_ERR(new_de)) {
+ err = PTR_ERR(new_de);
+ goto out_dir;
+ }
+ err = nilfs_set_link(new_dir, new_de, new_folio, old_inode);
+ folio_release_kmap(new_folio, new_de);
+ if (unlikely(err))
+ goto out_dir;
+ nilfs_mark_inode_dirty(new_dir);
+ inode_set_ctime_current(new_inode);
+ if (old_is_dir)
+ drop_nlink(new_inode);
+ drop_nlink(new_inode);
+ nilfs_mark_inode_dirty(new_inode);
+ } else {
+ err = nilfs_add_link(new_dentry, old_inode);
+ if (err)
+ goto out_dir;
+ if (old_is_dir) {
+ inc_nlink(new_dir);
+ nilfs_mark_inode_dirty(new_dir);
+ }
+ }
+
+ /*
+ * Like most other Unix systems, set the ctime for inodes on a
+ * rename.
+ */
+ inode_set_ctime_current(old_inode);
+
+ err = nilfs_delete_entry(old_de, old_folio);
+ if (likely(!err)) {
+ if (old_is_dir) {
+ if (old_dir != new_dir)
+ err = nilfs_set_link(old_inode, dir_de,
+ dir_folio, new_dir);
+ drop_nlink(old_dir);
+ }
+ nilfs_mark_inode_dirty(old_dir);
+ }
+ nilfs_mark_inode_dirty(old_inode);
+
+out_dir:
+ if (dir_de)
+ folio_release_kmap(dir_folio, dir_de);
+out_old:
+ folio_release_kmap(old_folio, old_de);
+out:
+ if (likely(!err))
+ err = nilfs_transaction_commit(old_dir->i_sb);
+ else
+ nilfs_transaction_abort(old_dir->i_sb);
+ return err;
+}
+
+/*
+ * Export operations
+ */
+static struct dentry *nilfs_get_parent(struct dentry *child)
+{
+ u64 ino;
+ int res;
+ struct nilfs_root *root;
+
+ res = nilfs_inode_by_name(d_inode(child), &dotdot_name, &ino);
+ if (res)
+ return ERR_PTR(res);
+
+ root = NILFS_I(d_inode(child))->i_root;
+
+ return d_obtain_alias(nilfs_iget(child->d_sb, root, ino));
+}
+
+static struct dentry *nilfs_get_dentry(struct super_block *sb, u64 cno,
+ u64 ino, u32 gen)
+{
+ struct nilfs_root *root;
+ struct inode *inode;
+
+ if (ino < NILFS_FIRST_INO(sb) && ino != NILFS_ROOT_INO)
+ return ERR_PTR(-ESTALE);
+
+ root = nilfs_lookup_root(sb->s_fs_info, cno);
+ if (!root)
+ return ERR_PTR(-ESTALE);
+
+ inode = nilfs_iget(sb, root, ino);
+ nilfs_put_root(root);
+
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+ if (gen && inode->i_generation != gen) {
+ iput(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ return d_obtain_alias(inode);
+}
+
+static struct dentry *nilfs_fh_to_dentry(struct super_block *sb, struct fid *fh,
+ int fh_len, int fh_type)
+{
+ struct nilfs_fid *fid = (struct nilfs_fid *)fh;
+
+ if (fh_len < NILFS_FID_SIZE_NON_CONNECTABLE ||
+ (fh_type != FILEID_NILFS_WITH_PARENT &&
+ fh_type != FILEID_NILFS_WITHOUT_PARENT))
+ return NULL;
+
+ return nilfs_get_dentry(sb, fid->cno, fid->ino, fid->gen);
+}
+
+static struct dentry *nilfs_fh_to_parent(struct super_block *sb, struct fid *fh,
+ int fh_len, int fh_type)
+{
+ struct nilfs_fid *fid = (struct nilfs_fid *)fh;
+
+ if (fh_len < NILFS_FID_SIZE_CONNECTABLE ||
+ fh_type != FILEID_NILFS_WITH_PARENT)
+ return NULL;
+
+ return nilfs_get_dentry(sb, fid->cno, fid->parent_ino, fid->parent_gen);
+}
+
+static int nilfs_encode_fh(struct inode *inode, __u32 *fh, int *lenp,
+ struct inode *parent)
+{
+ struct nilfs_fid *fid = (struct nilfs_fid *)fh;
+ struct nilfs_root *root = NILFS_I(inode)->i_root;
+ int type;
+
+ if (parent && *lenp < NILFS_FID_SIZE_CONNECTABLE) {
+ *lenp = NILFS_FID_SIZE_CONNECTABLE;
+ return FILEID_INVALID;
+ }
+ if (*lenp < NILFS_FID_SIZE_NON_CONNECTABLE) {
+ *lenp = NILFS_FID_SIZE_NON_CONNECTABLE;
+ return FILEID_INVALID;
+ }
+
+ fid->cno = root->cno;
+ fid->ino = inode->i_ino;
+ fid->gen = inode->i_generation;
+
+ if (parent) {
+ fid->parent_ino = parent->i_ino;
+ fid->parent_gen = parent->i_generation;
+ type = FILEID_NILFS_WITH_PARENT;
+ *lenp = NILFS_FID_SIZE_CONNECTABLE;
+ } else {
+ type = FILEID_NILFS_WITHOUT_PARENT;
+ *lenp = NILFS_FID_SIZE_NON_CONNECTABLE;
+ }
+
+ return type;
+}
+
+const struct inode_operations nilfs_dir_inode_operations = {
+ .create = nilfs_create,
+ .lookup = nilfs_lookup,
+ .link = nilfs_link,
+ .unlink = nilfs_unlink,
+ .symlink = nilfs_symlink,
+ .mkdir = nilfs_mkdir,
+ .rmdir = nilfs_rmdir,
+ .mknod = nilfs_mknod,
+ .rename = nilfs_rename,
+ .setattr = nilfs_setattr,
+ .permission = nilfs_permission,
+ .fiemap = nilfs_fiemap,
+ .fileattr_get = nilfs_fileattr_get,
+ .fileattr_set = nilfs_fileattr_set,
+};
+
+const struct inode_operations nilfs_special_inode_operations = {
+ .setattr = nilfs_setattr,
+ .permission = nilfs_permission,
+};
+
+const struct inode_operations nilfs_symlink_inode_operations = {
+ .get_link = page_get_link,
+ .permission = nilfs_permission,
+};
+
+const struct export_operations nilfs_export_ops = {
+ .encode_fh = nilfs_encode_fh,
+ .fh_to_dentry = nilfs_fh_to_dentry,
+ .fh_to_parent = nilfs_fh_to_parent,
+ .get_parent = nilfs_get_parent,
+};
diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h
new file mode 100644
index 000000000..4fc42d378
--- /dev/null
+++ b/fs/nilfs2/nilfs.h
@@ -0,0 +1,415 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS local header file.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato and Ryusuke Konishi.
+ */
+
+#ifndef _NILFS_H
+#define _NILFS_H
+
+#include <linux/kernel.h>
+#include <linux/buffer_head.h>
+#include <linux/spinlock.h>
+#include <linux/blkdev.h>
+#include <linux/fs_struct.h>
+#include <linux/nilfs2_api.h>
+#include <linux/nilfs2_ondisk.h>
+#include "the_nilfs.h"
+#include "bmap.h"
+
+/**
+ * struct nilfs_inode_info - nilfs inode data in memory
+ * @i_flags: inode flags
+ * @i_type: inode type (combination of flags that inidicate usage)
+ * @i_state: dynamic state flags
+ * @i_bmap: pointer on i_bmap_data
+ * @i_bmap_data: raw block mapping
+ * @i_xattr: <TODO>
+ * @i_dir_start_lookup: page index of last successful search
+ * @i_cno: checkpoint number for GC inode
+ * @i_assoc_inode: associated inode (B-tree node cache holder or back pointer)
+ * @i_dirty: list for connecting dirty files
+ * @xattr_sem: semaphore for extended attributes processing
+ * @i_bh: buffer contains disk inode
+ * @i_root: root object of the current filesystem tree
+ * @vfs_inode: VFS inode object
+ */
+struct nilfs_inode_info {
+ __u32 i_flags;
+ unsigned int i_type;
+ unsigned long i_state; /* Dynamic state flags */
+ struct nilfs_bmap *i_bmap;
+ struct nilfs_bmap i_bmap_data;
+ __u64 i_xattr; /* sector_t ??? */
+ __u32 i_dir_start_lookup;
+ __u64 i_cno; /* check point number for GC inode */
+ struct inode *i_assoc_inode;
+ struct list_head i_dirty; /* List for connecting dirty files */
+
+#ifdef CONFIG_NILFS_XATTR
+ /*
+ * Extended attributes can be read independently of the main file
+ * data. Taking i_sem even when reading would cause contention
+ * between readers of EAs and writers of regular file data, so
+ * instead we synchronize on xattr_sem when reading or changing
+ * EAs.
+ */
+ struct rw_semaphore xattr_sem;
+#endif
+ struct buffer_head *i_bh; /*
+ * i_bh contains a new or dirty
+ * disk inode.
+ */
+ struct nilfs_root *i_root;
+ struct inode vfs_inode;
+};
+
+static inline struct nilfs_inode_info *NILFS_I(const struct inode *inode)
+{
+ return container_of(inode, struct nilfs_inode_info, vfs_inode);
+}
+
+static inline struct nilfs_inode_info *
+NILFS_BMAP_I(const struct nilfs_bmap *bmap)
+{
+ return container_of(bmap, struct nilfs_inode_info, i_bmap_data);
+}
+
+/*
+ * Dynamic state flags of NILFS on-memory inode (i_state)
+ */
+enum {
+ NILFS_I_NEW = 0, /* Inode is newly created */
+ NILFS_I_DIRTY, /* The file is dirty */
+ NILFS_I_QUEUED, /* inode is in dirty_files list */
+ NILFS_I_BUSY, /*
+ * Inode is grabbed by a segment
+ * constructor
+ */
+ NILFS_I_COLLECTED, /* All dirty blocks are collected */
+ NILFS_I_UPDATED, /* The file has been written back */
+ NILFS_I_INODE_SYNC, /* dsync is not allowed for inode */
+ NILFS_I_BMAP, /* has bmap and btnode_cache */
+};
+
+/*
+ * Flags to identify the usage of on-memory inodes (i_type)
+ */
+enum {
+ NILFS_I_TYPE_NORMAL = 0,
+ NILFS_I_TYPE_GC = 0x0001, /* For data caching during GC */
+ NILFS_I_TYPE_BTNC = 0x0002, /* For btree node cache */
+ NILFS_I_TYPE_SHADOW = 0x0004, /* For shadowed page cache */
+};
+
+/*
+ * commit flags for nilfs_commit_super and nilfs_sync_super
+ */
+enum {
+ NILFS_SB_COMMIT = 0, /* Commit a super block alternately */
+ NILFS_SB_COMMIT_ALL /* Commit both super blocks */
+};
+
+/**
+ * define NILFS_MAX_VOLUME_NAME - maximum number of characters (bytes) in a
+ * file system volume name
+ *
+ * Defined by the size of the volume name field in the on-disk superblocks.
+ * This volume name does not include the terminating NULL byte if the string
+ * length matches the field size, so use (NILFS_MAX_VOLUME_NAME + 1) for the
+ * size of the buffer that requires a NULL byte termination.
+ */
+#define NILFS_MAX_VOLUME_NAME \
+ sizeof_field(struct nilfs_super_block, s_volume_name)
+
+/*
+ * Macros to check inode numbers
+ */
+#define NILFS_MDT_INO_BITS \
+ (BIT(NILFS_DAT_INO) | BIT(NILFS_CPFILE_INO) | \
+ BIT(NILFS_SUFILE_INO) | BIT(NILFS_IFILE_INO) | \
+ BIT(NILFS_ATIME_INO) | BIT(NILFS_SKETCH_INO))
+
+#define NILFS_SYS_INO_BITS (BIT(NILFS_ROOT_INO) | NILFS_MDT_INO_BITS)
+
+#define NILFS_FIRST_INO(sb) (((struct the_nilfs *)sb->s_fs_info)->ns_first_ino)
+
+#define NILFS_MDT_INODE(sb, ino) \
+ ((ino) < NILFS_USER_INO && (NILFS_MDT_INO_BITS & BIT(ino)))
+#define NILFS_VALID_INODE(sb, ino) \
+ ((ino) >= NILFS_FIRST_INO(sb) || \
+ ((ino) < NILFS_USER_INO && (NILFS_SYS_INO_BITS & BIT(ino))))
+
+#define NILFS_PRIVATE_INODE(ino) ({ \
+ u64 __ino = (ino); \
+ ((__ino) < NILFS_USER_INO && (__ino) != NILFS_ROOT_INO && \
+ (__ino) != NILFS_SKETCH_INO); })
+
+/**
+ * struct nilfs_transaction_info: context information for synchronization
+ * @ti_magic: Magic number
+ * @ti_save: Backup of journal_info field of task_struct
+ * @ti_flags: Flags
+ * @ti_count: Nest level
+ */
+struct nilfs_transaction_info {
+ u32 ti_magic;
+ void *ti_save;
+ /*
+ * This should never be used. If it happens,
+ * one of other filesystems has a bug.
+ */
+ unsigned short ti_flags;
+ unsigned short ti_count;
+};
+
+/* ti_magic */
+#define NILFS_TI_MAGIC 0xd9e392fb
+
+/* ti_flags */
+#define NILFS_TI_DYNAMIC_ALLOC 0x0001 /* Allocated from slab */
+#define NILFS_TI_SYNC 0x0002 /*
+ * Force to construct segment at the
+ * end of transaction.
+ */
+#define NILFS_TI_GC 0x0004 /* GC context */
+#define NILFS_TI_COMMIT 0x0008 /* Change happened or not */
+#define NILFS_TI_WRITER 0x0010 /* Constructor context */
+
+
+int nilfs_transaction_begin(struct super_block *,
+ struct nilfs_transaction_info *, int);
+int nilfs_transaction_commit(struct super_block *);
+void nilfs_transaction_abort(struct super_block *);
+
+static inline void nilfs_set_transaction_flag(unsigned int flag)
+{
+ struct nilfs_transaction_info *ti = current->journal_info;
+
+ ti->ti_flags |= flag;
+}
+
+static inline int nilfs_test_transaction_flag(unsigned int flag)
+{
+ struct nilfs_transaction_info *ti = current->journal_info;
+
+ if (ti == NULL || ti->ti_magic != NILFS_TI_MAGIC)
+ return 0;
+ return !!(ti->ti_flags & flag);
+}
+
+static inline int nilfs_doing_gc(void)
+{
+ return nilfs_test_transaction_flag(NILFS_TI_GC);
+}
+
+static inline int nilfs_doing_construction(void)
+{
+ return nilfs_test_transaction_flag(NILFS_TI_WRITER);
+}
+
+/*
+ * function prototype
+ */
+#ifdef CONFIG_NILFS_POSIX_ACL
+#error "NILFS: not yet supported POSIX ACL"
+extern int nilfs_acl_chmod(struct inode *);
+extern int nilfs_init_acl(struct inode *, struct inode *);
+#else
+static inline int nilfs_acl_chmod(struct inode *inode)
+{
+ return 0;
+}
+
+static inline int nilfs_init_acl(struct inode *inode, struct inode *dir)
+{
+ if (S_ISLNK(inode->i_mode))
+ return 0;
+
+ inode->i_mode &= ~current_umask();
+ return 0;
+}
+#endif
+
+#define NILFS_ATIME_DISABLE
+
+/* Flags that should be inherited by new inodes from their parent. */
+#define NILFS_FL_INHERITED \
+ (FS_SECRM_FL | FS_UNRM_FL | FS_COMPR_FL | FS_SYNC_FL | \
+ FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL |\
+ FS_COMPRBLK_FL | FS_NOCOMP_FL | FS_NOTAIL_FL | FS_DIRSYNC_FL)
+
+/* Mask out flags that are inappropriate for the given type of inode. */
+static inline __u32 nilfs_mask_flags(umode_t mode, __u32 flags)
+{
+ if (S_ISDIR(mode))
+ return flags;
+ else if (S_ISREG(mode))
+ return flags & ~(FS_DIRSYNC_FL | FS_TOPDIR_FL);
+ else
+ return flags & (FS_NODUMP_FL | FS_NOATIME_FL);
+}
+
+/* dir.c */
+int nilfs_add_link(struct dentry *, struct inode *);
+int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, u64 *ino);
+int nilfs_make_empty(struct inode *, struct inode *);
+struct nilfs_dir_entry *nilfs_find_entry(struct inode *, const struct qstr *,
+ struct folio **);
+int nilfs_delete_entry(struct nilfs_dir_entry *, struct folio *);
+int nilfs_empty_dir(struct inode *);
+struct nilfs_dir_entry *nilfs_dotdot(struct inode *, struct folio **);
+int nilfs_set_link(struct inode *dir, struct nilfs_dir_entry *de,
+ struct folio *folio, struct inode *inode);
+
+/* file.c */
+extern int nilfs_sync_file(struct file *, loff_t, loff_t, int);
+
+/* ioctl.c */
+int nilfs_fileattr_get(struct dentry *dentry, struct file_kattr *m);
+int nilfs_fileattr_set(struct mnt_idmap *idmap,
+ struct dentry *dentry, struct file_kattr *fa);
+long nilfs_ioctl(struct file *, unsigned int, unsigned long);
+long nilfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+int nilfs_ioctl_prepare_clean_segments(struct the_nilfs *, struct nilfs_argv *,
+ void **);
+
+/* inode.c */
+void nilfs_inode_add_blocks(struct inode *inode, int n);
+void nilfs_inode_sub_blocks(struct inode *inode, int n);
+extern struct inode *nilfs_new_inode(struct inode *, umode_t);
+extern int nilfs_get_block(struct inode *, sector_t, struct buffer_head *, int);
+extern void nilfs_set_inode_flags(struct inode *);
+extern int nilfs_read_inode_common(struct inode *, struct nilfs_inode *);
+void nilfs_write_inode_common(struct inode *inode,
+ struct nilfs_inode *raw_inode);
+struct inode *nilfs_ilookup(struct super_block *sb, struct nilfs_root *root,
+ u64 ino);
+struct inode *nilfs_iget_locked(struct super_block *sb,
+ struct nilfs_root *root, u64 ino);
+struct inode *nilfs_iget(struct super_block *sb, struct nilfs_root *root,
+ u64 ino);
+struct inode *nilfs_iget_for_gc(struct super_block *sb, u64 ino, __u64 cno);
+int nilfs_attach_btree_node_cache(struct inode *inode);
+void nilfs_detach_btree_node_cache(struct inode *inode);
+struct inode *nilfs_iget_for_shadow(struct inode *inode);
+extern void nilfs_update_inode(struct inode *, struct buffer_head *, int);
+extern void nilfs_truncate(struct inode *);
+extern void nilfs_evict_inode(struct inode *);
+extern int nilfs_setattr(struct mnt_idmap *, struct dentry *,
+ struct iattr *);
+extern void nilfs_write_failed(struct address_space *mapping, loff_t to);
+int nilfs_permission(struct mnt_idmap *idmap, struct inode *inode,
+ int mask);
+int nilfs_load_inode_block(struct inode *inode, struct buffer_head **pbh);
+extern int nilfs_inode_dirty(struct inode *);
+int nilfs_set_file_dirty(struct inode *inode, unsigned int nr_dirty);
+extern int __nilfs_mark_inode_dirty(struct inode *, int);
+extern void nilfs_dirty_inode(struct inode *, int flags);
+int nilfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ __u64 start, __u64 len);
+static inline int nilfs_mark_inode_dirty(struct inode *inode)
+{
+ return __nilfs_mark_inode_dirty(inode, I_DIRTY);
+}
+static inline int nilfs_mark_inode_dirty_sync(struct inode *inode)
+{
+ return __nilfs_mark_inode_dirty(inode, I_DIRTY_SYNC);
+}
+
+/* super.c */
+extern struct inode *nilfs_alloc_inode(struct super_block *);
+
+__printf(2, 3)
+void __nilfs_msg(struct super_block *sb, const char *fmt, ...);
+extern __printf(3, 4)
+void __nilfs_error(struct super_block *sb, const char *function,
+ const char *fmt, ...);
+
+#ifdef CONFIG_PRINTK
+
+#define nilfs_msg(sb, level, fmt, ...) \
+ __nilfs_msg(sb, level fmt, ##__VA_ARGS__)
+#define nilfs_error(sb, fmt, ...) \
+ __nilfs_error(sb, __func__, fmt, ##__VA_ARGS__)
+
+#else
+
+#define nilfs_msg(sb, level, fmt, ...) \
+ do { \
+ no_printk(level fmt, ##__VA_ARGS__); \
+ (void)(sb); \
+ } while (0)
+#define nilfs_error(sb, fmt, ...) \
+ do { \
+ no_printk(fmt, ##__VA_ARGS__); \
+ __nilfs_error(sb, "", " "); \
+ } while (0)
+
+#endif /* CONFIG_PRINTK */
+
+#define nilfs_crit(sb, fmt, ...) \
+ nilfs_msg(sb, KERN_CRIT, fmt, ##__VA_ARGS__)
+#define nilfs_err(sb, fmt, ...) \
+ nilfs_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__)
+#define nilfs_warn(sb, fmt, ...) \
+ nilfs_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__)
+#define nilfs_info(sb, fmt, ...) \
+ nilfs_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__)
+
+extern struct nilfs_super_block *
+nilfs_read_super_block(struct super_block *, u64, int, struct buffer_head **);
+extern int nilfs_store_magic(struct super_block *sb,
+ struct nilfs_super_block *sbp);
+extern int nilfs_check_feature_compatibility(struct super_block *,
+ struct nilfs_super_block *);
+extern void nilfs_set_log_cursor(struct nilfs_super_block *,
+ struct the_nilfs *);
+struct nilfs_super_block **nilfs_prepare_super(struct super_block *sb,
+ int flip);
+int nilfs_commit_super(struct super_block *sb, int flag);
+int nilfs_cleanup_super(struct super_block *sb);
+int nilfs_resize_fs(struct super_block *sb, __u64 newsize);
+int nilfs_attach_checkpoint(struct super_block *sb, __u64 cno, int curr_mnt,
+ struct nilfs_root **root);
+int nilfs_checkpoint_is_mounted(struct super_block *sb, __u64 cno);
+
+/* gcinode.c */
+int nilfs_gccache_submit_read_data(struct inode *, sector_t, sector_t, __u64,
+ struct buffer_head **);
+int nilfs_gccache_submit_read_node(struct inode *, sector_t, __u64,
+ struct buffer_head **);
+int nilfs_gccache_wait_and_mark_dirty(struct buffer_head *);
+int nilfs_init_gcinode(struct inode *inode);
+void nilfs_remove_all_gcinodes(struct the_nilfs *nilfs);
+
+/* sysfs.c */
+int __init nilfs_sysfs_init(void);
+void nilfs_sysfs_exit(void);
+int nilfs_sysfs_create_device_group(struct super_block *);
+void nilfs_sysfs_delete_device_group(struct the_nilfs *);
+int nilfs_sysfs_create_snapshot_group(struct nilfs_root *);
+void nilfs_sysfs_delete_snapshot_group(struct nilfs_root *);
+
+/*
+ * Inodes and files operations
+ */
+extern const struct file_operations nilfs_dir_operations;
+extern const struct inode_operations nilfs_file_inode_operations;
+extern const struct file_operations nilfs_file_operations;
+extern const struct address_space_operations nilfs_aops;
+extern const struct address_space_operations nilfs_buffer_cache_aops;
+extern const struct inode_operations nilfs_dir_inode_operations;
+extern const struct inode_operations nilfs_special_inode_operations;
+extern const struct inode_operations nilfs_symlink_inode_operations;
+
+/*
+ * filesystem type
+ */
+extern struct file_system_type nilfs_fs_type;
+
+
+#endif /* _NILFS_H */
diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c
new file mode 100644
index 000000000..cf4f1c679
--- /dev/null
+++ b/fs/nilfs2/page.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Buffer/page management specific to NILFS
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi and Seiji Kihara.
+ */
+
+#include <linux/pagemap.h>
+#include <linux/writeback.h>
+#include <linux/swap.h>
+#include <linux/bitops.h>
+#include <linux/page-flags.h>
+#include <linux/list.h>
+#include <linux/highmem.h>
+#include <linux/folio_batch.h>
+#include <linux/gfp.h>
+#include "nilfs.h"
+#include "page.h"
+#include "mdt.h"
+
+
+#define NILFS_BUFFER_INHERENT_BITS \
+ (BIT(BH_Uptodate) | BIT(BH_Mapped) | BIT(BH_NILFS_Node) | \
+ BIT(BH_NILFS_Volatile) | BIT(BH_NILFS_Checked))
+
+static struct buffer_head *__nilfs_get_folio_block(struct folio *folio,
+ unsigned long block, pgoff_t index, int blkbits,
+ unsigned long b_state)
+
+{
+ unsigned long first_block;
+ struct buffer_head *bh = folio_buffers(folio);
+
+ if (!bh)
+ bh = create_empty_buffers(folio, 1 << blkbits, b_state);
+
+ first_block = (unsigned long)index << (PAGE_SHIFT - blkbits);
+ bh = get_nth_bh(bh, block - first_block);
+
+ wait_on_buffer(bh);
+ return bh;
+}
+
+struct buffer_head *nilfs_grab_buffer(struct inode *inode,
+ struct address_space *mapping,
+ unsigned long blkoff,
+ unsigned long b_state)
+{
+ int blkbits = inode->i_blkbits;
+ pgoff_t index = blkoff >> (PAGE_SHIFT - blkbits);
+ struct folio *folio;
+ struct buffer_head *bh;
+
+ folio = filemap_grab_folio(mapping, index);
+ if (IS_ERR(folio))
+ return NULL;
+
+ bh = __nilfs_get_folio_block(folio, blkoff, index, blkbits, b_state);
+ if (unlikely(!bh)) {
+ folio_unlock(folio);
+ folio_put(folio);
+ return NULL;
+ }
+ bh->b_bdev = inode->i_sb->s_bdev;
+ return bh;
+}
+
+/**
+ * nilfs_forget_buffer - discard dirty state
+ * @bh: buffer head of the buffer to be discarded
+ */
+void nilfs_forget_buffer(struct buffer_head *bh)
+{
+ struct folio *folio = bh->b_folio;
+ const unsigned long clear_bits =
+ (BIT(BH_Uptodate) | BIT(BH_Dirty) | BIT(BH_Mapped) |
+ BIT(BH_Async_Write) | BIT(BH_NILFS_Volatile) |
+ BIT(BH_NILFS_Checked) | BIT(BH_NILFS_Redirected) |
+ BIT(BH_Delay));
+
+ lock_buffer(bh);
+ set_mask_bits(&bh->b_state, clear_bits, 0);
+ if (nilfs_folio_buffers_clean(folio))
+ __nilfs_clear_folio_dirty(folio);
+
+ bh->b_blocknr = -1;
+ folio_clear_uptodate(folio);
+ folio_clear_mappedtodisk(folio);
+ unlock_buffer(bh);
+ brelse(bh);
+}
+
+/**
+ * nilfs_copy_buffer -- copy buffer data and flags
+ * @dbh: destination buffer
+ * @sbh: source buffer
+ */
+void nilfs_copy_buffer(struct buffer_head *dbh, struct buffer_head *sbh)
+{
+ void *saddr, *daddr;
+ unsigned long bits;
+ struct folio *sfolio = sbh->b_folio, *dfolio = dbh->b_folio;
+ struct buffer_head *bh;
+
+ saddr = kmap_local_folio(sfolio, bh_offset(sbh));
+ daddr = kmap_local_folio(dfolio, bh_offset(dbh));
+ memcpy(daddr, saddr, sbh->b_size);
+ kunmap_local(daddr);
+ kunmap_local(saddr);
+
+ dbh->b_state = sbh->b_state & NILFS_BUFFER_INHERENT_BITS;
+ dbh->b_blocknr = sbh->b_blocknr;
+ dbh->b_bdev = sbh->b_bdev;
+
+ bh = dbh;
+ bits = sbh->b_state & (BIT(BH_Uptodate) | BIT(BH_Mapped));
+ while ((bh = bh->b_this_page) != dbh) {
+ lock_buffer(bh);
+ bits &= bh->b_state;
+ unlock_buffer(bh);
+ }
+ if (bits & BIT(BH_Uptodate))
+ folio_mark_uptodate(dfolio);
+ else
+ folio_clear_uptodate(dfolio);
+ if (bits & BIT(BH_Mapped))
+ folio_set_mappedtodisk(dfolio);
+ else
+ folio_clear_mappedtodisk(dfolio);
+}
+
+/**
+ * nilfs_folio_buffers_clean - Check if a folio has dirty buffers or not.
+ * @folio: Folio to be checked.
+ *
+ * Return: false if the folio has dirty buffers, true otherwise.
+ */
+bool nilfs_folio_buffers_clean(struct folio *folio)
+{
+ struct buffer_head *bh, *head;
+
+ bh = head = folio_buffers(folio);
+ do {
+ if (buffer_dirty(bh))
+ return false;
+ bh = bh->b_this_page;
+ } while (bh != head);
+ return true;
+}
+
+void nilfs_folio_bug(struct folio *folio)
+{
+ struct buffer_head *bh, *head;
+ struct address_space *m;
+ u64 ino;
+
+ if (unlikely(!folio)) {
+ printk(KERN_CRIT "NILFS_FOLIO_BUG(NULL)\n");
+ return;
+ }
+
+ m = folio->mapping;
+ ino = m ? m->host->i_ino : 0;
+
+ printk(KERN_CRIT "NILFS_FOLIO_BUG(%p): cnt=%d index#=%lu flags=0x%lx mapping=%p ino=%llu\n",
+ folio, folio_ref_count(folio), folio->index, folio->flags.f,
+ m, ino);
+
+ head = folio_buffers(folio);
+ if (head) {
+ int i = 0;
+
+ bh = head;
+ do {
+ printk(KERN_CRIT
+ " BH[%d] %p: cnt=%d block#=%llu state=0x%lx\n",
+ i++, bh, atomic_read(&bh->b_count),
+ (unsigned long long)bh->b_blocknr, bh->b_state);
+ bh = bh->b_this_page;
+ } while (bh != head);
+ }
+}
+
+/**
+ * nilfs_copy_folio -- copy the folio with buffers
+ * @dst: destination folio
+ * @src: source folio
+ * @copy_dirty: flag whether to copy dirty states on the folio's buffer heads.
+ *
+ * This function is for both data folios and btnode folios. The dirty flag
+ * should be treated by caller. The folio must not be under i/o.
+ * Both src and dst folio must be locked
+ */
+static void nilfs_copy_folio(struct folio *dst, struct folio *src,
+ bool copy_dirty)
+{
+ struct buffer_head *dbh, *dbufs, *sbh;
+ unsigned long mask = NILFS_BUFFER_INHERENT_BITS;
+
+ BUG_ON(folio_test_writeback(dst));
+
+ sbh = folio_buffers(src);
+ dbh = folio_buffers(dst);
+ if (!dbh)
+ dbh = create_empty_buffers(dst, sbh->b_size, 0);
+
+ if (copy_dirty)
+ mask |= BIT(BH_Dirty);
+
+ dbufs = dbh;
+ do {
+ lock_buffer(sbh);
+ lock_buffer(dbh);
+ dbh->b_state = sbh->b_state & mask;
+ dbh->b_blocknr = sbh->b_blocknr;
+ dbh->b_bdev = sbh->b_bdev;
+ sbh = sbh->b_this_page;
+ dbh = dbh->b_this_page;
+ } while (dbh != dbufs);
+
+ folio_copy(dst, src);
+
+ if (folio_test_uptodate(src) && !folio_test_uptodate(dst))
+ folio_mark_uptodate(dst);
+ else if (!folio_test_uptodate(src) && folio_test_uptodate(dst))
+ folio_clear_uptodate(dst);
+ if (folio_test_mappedtodisk(src) && !folio_test_mappedtodisk(dst))
+ folio_set_mappedtodisk(dst);
+ else if (!folio_test_mappedtodisk(src) && folio_test_mappedtodisk(dst))
+ folio_clear_mappedtodisk(dst);
+
+ do {
+ unlock_buffer(sbh);
+ unlock_buffer(dbh);
+ sbh = sbh->b_this_page;
+ dbh = dbh->b_this_page;
+ } while (dbh != dbufs);
+}
+
+int nilfs_copy_dirty_pages(struct address_space *dmap,
+ struct address_space *smap)
+{
+ struct inode *smap_inode = smap->host;
+ struct folio_batch fbatch;
+ unsigned int i;
+ pgoff_t index = 0;
+ int err = 0;
+
+ folio_batch_init(&fbatch);
+repeat:
+ if (!filemap_get_folios_tag(smap, &index, (pgoff_t)-1,
+ PAGECACHE_TAG_DIRTY, &fbatch))
+ return 0;
+
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ struct folio *folio = fbatch.folios[i], *dfolio;
+
+ folio_lock(folio);
+ if (unlikely(!folio_test_dirty(folio))) {
+ if (WARN_ONCE(!sb_rdonly(smap_inode->i_sb),
+ "inconsistent dirty state\n"))
+ goto unlock_folio;
+
+ /*
+ * If the filesystem has been forced to read-only
+ * due to metadata corruption.
+ */
+ folio_unlock(folio);
+ err = -EROFS;
+ break;
+ }
+
+ dfolio = filemap_grab_folio(dmap, folio->index);
+ if (IS_ERR(dfolio)) {
+ /* No empty page is added to the page cache */
+ folio_unlock(folio);
+ err = PTR_ERR(dfolio);
+ break;
+ }
+ if (unlikely(!folio_buffers(folio)))
+ NILFS_FOLIO_BUG(folio,
+ "found empty page in dat page cache");
+
+ nilfs_copy_folio(dfolio, folio, true);
+ filemap_dirty_folio(folio_mapping(dfolio), dfolio);
+
+ folio_unlock(dfolio);
+ folio_put(dfolio);
+unlock_folio:
+ folio_unlock(folio);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+
+ if (likely(!err))
+ goto repeat;
+ return err;
+}
+
+/**
+ * nilfs_copy_back_pages -- copy back pages to original cache from shadow cache
+ * @dmap: destination page cache
+ * @smap: source page cache
+ *
+ * No pages must be added to the cache during this process.
+ * This must be ensured by the caller.
+ */
+void nilfs_copy_back_pages(struct address_space *dmap,
+ struct address_space *smap)
+{
+ struct folio_batch fbatch;
+ unsigned int i, n;
+ pgoff_t start = 0;
+
+ folio_batch_init(&fbatch);
+repeat:
+ n = filemap_get_folios(smap, &start, ~0UL, &fbatch);
+ if (!n)
+ return;
+
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ struct folio *folio = fbatch.folios[i], *dfolio;
+ pgoff_t index = folio->index;
+
+ folio_lock(folio);
+ dfolio = filemap_lock_folio(dmap, index);
+ if (!IS_ERR(dfolio)) {
+ /* overwrite existing folio in the destination cache */
+ WARN_ON(folio_test_dirty(dfolio));
+ nilfs_copy_folio(dfolio, folio, false);
+ folio_unlock(dfolio);
+ folio_put(dfolio);
+ /* Do we not need to remove folio from smap here? */
+ } else {
+ struct folio *f;
+
+ /* move the folio to the destination cache */
+ xa_lock_irq(&smap->i_pages);
+ f = __xa_erase(&smap->i_pages, index);
+ WARN_ON(folio != f);
+ smap->nrpages--;
+ xa_unlock_irq(&smap->i_pages);
+
+ xa_lock_irq(&dmap->i_pages);
+ f = __xa_store(&dmap->i_pages, index, folio, GFP_NOFS);
+ if (unlikely(f)) {
+ /* Probably -ENOMEM */
+ folio->mapping = NULL;
+ folio_put(folio);
+ } else {
+ folio->mapping = dmap;
+ dmap->nrpages++;
+ if (folio_test_dirty(folio))
+ __xa_set_mark(&dmap->i_pages, index,
+ PAGECACHE_TAG_DIRTY);
+ }
+ xa_unlock_irq(&dmap->i_pages);
+ }
+ folio_unlock(folio);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+
+ goto repeat;
+}
+
+/**
+ * nilfs_clear_dirty_pages - discard dirty pages in address space
+ * @mapping: address space with dirty pages for discarding
+ */
+void nilfs_clear_dirty_pages(struct address_space *mapping)
+{
+ struct folio_batch fbatch;
+ unsigned int i;
+ pgoff_t index = 0;
+
+ folio_batch_init(&fbatch);
+
+ while (filemap_get_folios_tag(mapping, &index, (pgoff_t)-1,
+ PAGECACHE_TAG_DIRTY, &fbatch)) {
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ struct folio *folio = fbatch.folios[i];
+
+ folio_lock(folio);
+
+ /*
+ * This folio may have been removed from the address
+ * space by truncation or invalidation when the lock
+ * was acquired. Skip processing in that case.
+ */
+ if (likely(folio->mapping == mapping))
+ nilfs_clear_folio_dirty(folio);
+
+ folio_unlock(folio);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+ }
+}
+
+/**
+ * nilfs_clear_folio_dirty - discard dirty folio
+ * @folio: dirty folio that will be discarded
+ *
+ * nilfs_clear_folio_dirty() clears working states including dirty state for
+ * the folio and its buffers. If the folio has buffers, clear only if it is
+ * confirmed that none of the buffer heads are busy (none have valid
+ * references and none are locked).
+ */
+void nilfs_clear_folio_dirty(struct folio *folio)
+{
+ struct buffer_head *bh, *head;
+
+ BUG_ON(!folio_test_locked(folio));
+
+ head = folio_buffers(folio);
+ if (head) {
+ const unsigned long clear_bits =
+ (BIT(BH_Uptodate) | BIT(BH_Dirty) | BIT(BH_Mapped) |
+ BIT(BH_Async_Write) | BIT(BH_NILFS_Volatile) |
+ BIT(BH_NILFS_Checked) | BIT(BH_NILFS_Redirected) |
+ BIT(BH_Delay));
+ bool busy, invalidated = false;
+
+recheck_buffers:
+ busy = false;
+ bh = head;
+ do {
+ if (atomic_read(&bh->b_count) | buffer_locked(bh)) {
+ busy = true;
+ break;
+ }
+ } while (bh = bh->b_this_page, bh != head);
+
+ if (busy) {
+ if (invalidated)
+ return;
+ invalidate_bh_lrus();
+ invalidated = true;
+ goto recheck_buffers;
+ }
+
+ bh = head;
+ do {
+ lock_buffer(bh);
+ set_mask_bits(&bh->b_state, clear_bits, 0);
+ unlock_buffer(bh);
+ } while (bh = bh->b_this_page, bh != head);
+ }
+
+ folio_clear_uptodate(folio);
+ folio_clear_mappedtodisk(folio);
+ folio_clear_checked(folio);
+ __nilfs_clear_folio_dirty(folio);
+}
+
+unsigned int nilfs_page_count_clean_buffers(struct folio *folio,
+ unsigned int from, unsigned int to)
+{
+ unsigned int block_start, block_end;
+ struct buffer_head *bh, *head;
+ unsigned int nc = 0;
+
+ for (bh = head = folio_buffers(folio), block_start = 0;
+ bh != head || !block_start;
+ block_start = block_end, bh = bh->b_this_page) {
+ block_end = block_start + bh->b_size;
+ if (block_end > from && block_start < to && !buffer_dirty(bh))
+ nc++;
+ }
+ return nc;
+}
+
+/*
+ * NILFS2 needs clear_page_dirty() in the following two cases:
+ *
+ * 1) For B-tree node pages and data pages of DAT file, NILFS2 clears dirty
+ * flag of pages when it copies back pages from shadow cache to the
+ * original cache.
+ *
+ * 2) Some B-tree operations like insertion or deletion may dispose buffers
+ * in dirty state, and this needs to cancel the dirty state of their pages.
+ */
+void __nilfs_clear_folio_dirty(struct folio *folio)
+{
+ struct address_space *mapping = folio->mapping;
+
+ if (mapping) {
+ xa_lock_irq(&mapping->i_pages);
+ if (folio_test_dirty(folio)) {
+ __xa_clear_mark(&mapping->i_pages, folio->index,
+ PAGECACHE_TAG_DIRTY);
+ xa_unlock_irq(&mapping->i_pages);
+ folio_clear_dirty_for_io(folio);
+ return;
+ }
+ xa_unlock_irq(&mapping->i_pages);
+ return;
+ }
+ folio_clear_dirty(folio);
+}
+
+/**
+ * nilfs_find_uncommitted_extent - find extent of uncommitted data
+ * @inode: inode
+ * @start_blk: start block offset (in)
+ * @blkoff: start offset of the found extent (out)
+ *
+ * This function searches an extent of buffers marked "delayed" which
+ * starts from a block offset equal to or larger than @start_blk. If
+ * such an extent was found, this will store the start offset in
+ * @blkoff and return its length in blocks.
+ *
+ * Return: Length in blocks of found extent, 0 otherwise.
+ */
+unsigned long nilfs_find_uncommitted_extent(struct inode *inode,
+ sector_t start_blk,
+ sector_t *blkoff)
+{
+ unsigned int i, nr_folios;
+ pgoff_t index;
+ unsigned long length = 0;
+ struct folio_batch fbatch;
+ struct folio *folio;
+
+ if (inode->i_mapping->nrpages == 0)
+ return 0;
+
+ index = start_blk >> (PAGE_SHIFT - inode->i_blkbits);
+
+ folio_batch_init(&fbatch);
+
+repeat:
+ nr_folios = filemap_get_folios_contig(inode->i_mapping, &index, ULONG_MAX,
+ &fbatch);
+ if (nr_folios == 0)
+ return length;
+
+ i = 0;
+ do {
+ folio = fbatch.folios[i];
+
+ folio_lock(folio);
+ if (folio_buffers(folio)) {
+ struct buffer_head *bh, *head;
+ sector_t b;
+
+ b = folio->index << (PAGE_SHIFT - inode->i_blkbits);
+ bh = head = folio_buffers(folio);
+ do {
+ if (b < start_blk)
+ continue;
+ if (buffer_delay(bh)) {
+ if (length == 0)
+ *blkoff = b;
+ length++;
+ } else if (length > 0) {
+ goto out_locked;
+ }
+ } while (++b, bh = bh->b_this_page, bh != head);
+ } else {
+ if (length > 0)
+ goto out_locked;
+ }
+ folio_unlock(folio);
+
+ } while (++i < nr_folios);
+
+ folio_batch_release(&fbatch);
+ cond_resched();
+ goto repeat;
+
+out_locked:
+ folio_unlock(folio);
+ folio_batch_release(&fbatch);
+ return length;
+}
diff --git a/fs/nilfs2/page.h b/fs/nilfs2/page.h
new file mode 100644
index 000000000..136cd1c14
--- /dev/null
+++ b/fs/nilfs2/page.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Buffer/page management specific to NILFS
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi and Seiji Kihara.
+ */
+
+#ifndef _NILFS_PAGE_H
+#define _NILFS_PAGE_H
+
+#include <linux/buffer_head.h>
+#include "nilfs.h"
+
+/*
+ * Extended buffer state bits
+ */
+enum {
+ BH_NILFS_Allocated = BH_PrivateStart,
+ BH_NILFS_Node,
+ BH_NILFS_Volatile,
+ BH_NILFS_Checked,
+ BH_NILFS_Redirected,
+};
+
+BUFFER_FNS(NILFS_Node, nilfs_node) /* nilfs node buffers */
+BUFFER_FNS(NILFS_Volatile, nilfs_volatile)
+BUFFER_FNS(NILFS_Checked, nilfs_checked) /* buffer is verified */
+BUFFER_FNS(NILFS_Redirected, nilfs_redirected) /* redirected to a copy */
+
+
+void __nilfs_clear_folio_dirty(struct folio *);
+
+struct buffer_head *nilfs_grab_buffer(struct inode *, struct address_space *,
+ unsigned long, unsigned long);
+void nilfs_forget_buffer(struct buffer_head *);
+void nilfs_copy_buffer(struct buffer_head *, struct buffer_head *);
+bool nilfs_folio_buffers_clean(struct folio *);
+void nilfs_folio_bug(struct folio *);
+
+int nilfs_copy_dirty_pages(struct address_space *, struct address_space *);
+void nilfs_copy_back_pages(struct address_space *, struct address_space *);
+void nilfs_clear_folio_dirty(struct folio *folio);
+void nilfs_clear_dirty_pages(struct address_space *mapping);
+unsigned int nilfs_page_count_clean_buffers(struct folio *folio,
+ unsigned int from, unsigned int to);
+unsigned long nilfs_find_uncommitted_extent(struct inode *inode,
+ sector_t start_blk,
+ sector_t *blkoff);
+
+#define NILFS_FOLIO_BUG(folio, m, a...) \
+ do { nilfs_folio_bug(folio); BUG(); } while (0)
+
+#endif /* _NILFS_PAGE_H */
diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c
new file mode 100644
index 000000000..45fb37215
--- /dev/null
+++ b/fs/nilfs2/recovery.c
@@ -0,0 +1,1016 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS recovery logic
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/blkdev.h>
+#include <linux/swap.h>
+#include <linux/slab.h>
+#include <linux/crc32.h>
+#include "nilfs.h"
+#include "segment.h"
+#include "sufile.h"
+#include "page.h"
+#include "segbuf.h"
+
+/*
+ * Segment check result
+ */
+enum {
+ NILFS_SEG_VALID,
+ NILFS_SEG_NO_SUPER_ROOT,
+ NILFS_SEG_FAIL_IO,
+ NILFS_SEG_FAIL_MAGIC,
+ NILFS_SEG_FAIL_SEQ,
+ NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT,
+ NILFS_SEG_FAIL_CHECKSUM_FULL,
+ NILFS_SEG_FAIL_CONSISTENCY,
+};
+
+/* work structure for recovery */
+struct nilfs_recovery_block {
+ u64 ino; /*
+ * Inode number of the file that this block
+ * belongs to
+ */
+ sector_t blocknr; /* block number */
+ __u64 vblocknr; /* virtual block number */
+ unsigned long blkoff; /* File offset of the data block (per block) */
+ struct list_head list;
+};
+
+
+static int nilfs_warn_segment_error(struct super_block *sb, int err)
+{
+ const char *msg = NULL;
+
+ switch (err) {
+ case NILFS_SEG_FAIL_IO:
+ nilfs_err(sb, "I/O error reading segment");
+ return -EIO;
+ case NILFS_SEG_FAIL_MAGIC:
+ msg = "Magic number mismatch";
+ break;
+ case NILFS_SEG_FAIL_SEQ:
+ msg = "Sequence number mismatch";
+ break;
+ case NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT:
+ msg = "Checksum error in super root";
+ break;
+ case NILFS_SEG_FAIL_CHECKSUM_FULL:
+ msg = "Checksum error in segment payload";
+ break;
+ case NILFS_SEG_FAIL_CONSISTENCY:
+ msg = "Inconsistency found";
+ break;
+ case NILFS_SEG_NO_SUPER_ROOT:
+ msg = "No super root in the last segment";
+ break;
+ default:
+ nilfs_err(sb, "unrecognized segment error %d", err);
+ return -EINVAL;
+ }
+ nilfs_warn(sb, "invalid segment: %s", msg);
+ return -EINVAL;
+}
+
+/**
+ * nilfs_compute_checksum - compute checksum of blocks continuously
+ * @nilfs: nilfs object
+ * @bhs: buffer head of start block
+ * @sum: place to store result
+ * @offset: offset bytes in the first block
+ * @check_bytes: number of bytes to be checked
+ * @start: DBN of start block
+ * @nblock: number of blocks to be checked
+ *
+ * Return: 0 on success, or %-EIO if an I/O error occurs.
+ */
+static int nilfs_compute_checksum(struct the_nilfs *nilfs,
+ struct buffer_head *bhs, u32 *sum,
+ unsigned long offset, u64 check_bytes,
+ sector_t start, unsigned long nblock)
+{
+ unsigned int blocksize = nilfs->ns_blocksize;
+ unsigned long size;
+ u32 crc;
+
+ BUG_ON(offset >= blocksize);
+ check_bytes -= offset;
+ size = min_t(u64, check_bytes, blocksize - offset);
+ crc = crc32_le(nilfs->ns_crc_seed,
+ (unsigned char *)bhs->b_data + offset, size);
+ if (--nblock > 0) {
+ do {
+ struct buffer_head *bh;
+
+ bh = __bread(nilfs->ns_bdev, ++start, blocksize);
+ if (!bh)
+ return -EIO;
+ check_bytes -= size;
+ size = min_t(u64, check_bytes, blocksize);
+ crc = crc32_le(crc, bh->b_data, size);
+ brelse(bh);
+ } while (--nblock > 0);
+ }
+ *sum = crc;
+ return 0;
+}
+
+/**
+ * nilfs_read_super_root_block - read super root block
+ * @nilfs: nilfs object
+ * @sr_block: disk block number of the super root block
+ * @pbh: address of a buffer_head pointer to return super root buffer
+ * @check: CRC check flag
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Super root block corrupted.
+ * * %-EIO - I/O error.
+ */
+int nilfs_read_super_root_block(struct the_nilfs *nilfs, sector_t sr_block,
+ struct buffer_head **pbh, int check)
+{
+ struct buffer_head *bh_sr;
+ struct nilfs_super_root *sr;
+ u32 crc;
+ int ret;
+
+ *pbh = NULL;
+ bh_sr = __bread(nilfs->ns_bdev, sr_block, nilfs->ns_blocksize);
+ if (unlikely(!bh_sr)) {
+ ret = NILFS_SEG_FAIL_IO;
+ goto failed;
+ }
+
+ sr = (struct nilfs_super_root *)bh_sr->b_data;
+ if (check) {
+ unsigned int bytes = le16_to_cpu(sr->sr_bytes);
+
+ if (bytes == 0 || bytes > nilfs->ns_blocksize) {
+ ret = NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT;
+ goto failed_bh;
+ }
+ if (nilfs_compute_checksum(
+ nilfs, bh_sr, &crc, sizeof(sr->sr_sum), bytes,
+ sr_block, 1)) {
+ ret = NILFS_SEG_FAIL_IO;
+ goto failed_bh;
+ }
+ if (crc != le32_to_cpu(sr->sr_sum)) {
+ ret = NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT;
+ goto failed_bh;
+ }
+ }
+ *pbh = bh_sr;
+ return 0;
+
+ failed_bh:
+ brelse(bh_sr);
+
+ failed:
+ return nilfs_warn_segment_error(nilfs->ns_sb, ret);
+}
+
+/**
+ * nilfs_read_log_header - read summary header of the specified log
+ * @nilfs: nilfs object
+ * @start_blocknr: start block number of the log
+ * @sum: pointer to return segment summary structure
+ *
+ * Return: Buffer head pointer, or NULL if an I/O error occurs.
+ */
+static struct buffer_head *
+nilfs_read_log_header(struct the_nilfs *nilfs, sector_t start_blocknr,
+ struct nilfs_segment_summary **sum)
+{
+ struct buffer_head *bh_sum;
+
+ bh_sum = __bread(nilfs->ns_bdev, start_blocknr, nilfs->ns_blocksize);
+ if (bh_sum)
+ *sum = (struct nilfs_segment_summary *)bh_sum->b_data;
+ return bh_sum;
+}
+
+/**
+ * nilfs_validate_log - verify consistency of log
+ * @nilfs: nilfs object
+ * @seg_seq: sequence number of segment
+ * @bh_sum: buffer head of summary block
+ * @sum: segment summary struct
+ *
+ * Return: 0 on success, or one of the following internal codes on failure:
+ * * %NILFS_SEG_FAIL_MAGIC - Magic number mismatch.
+ * * %NILFS_SEG_FAIL_SEQ - Sequence number mismatch.
+ * * %NIFLS_SEG_FAIL_CONSISTENCY - Block count out of range.
+ * * %NILFS_SEG_FAIL_IO - I/O error.
+ * * %NILFS_SEG_FAIL_CHECKSUM_FULL - Full log checksum verification failed.
+ */
+static int nilfs_validate_log(struct the_nilfs *nilfs, u64 seg_seq,
+ struct buffer_head *bh_sum,
+ struct nilfs_segment_summary *sum)
+{
+ unsigned long nblock;
+ u32 crc;
+ int ret;
+
+ ret = NILFS_SEG_FAIL_MAGIC;
+ if (le32_to_cpu(sum->ss_magic) != NILFS_SEGSUM_MAGIC)
+ goto out;
+
+ ret = NILFS_SEG_FAIL_SEQ;
+ if (le64_to_cpu(sum->ss_seq) != seg_seq)
+ goto out;
+
+ nblock = le32_to_cpu(sum->ss_nblocks);
+ ret = NILFS_SEG_FAIL_CONSISTENCY;
+ if (unlikely(nblock == 0 || nblock > nilfs->ns_blocks_per_segment))
+ /* This limits the number of blocks read in the CRC check */
+ goto out;
+
+ ret = NILFS_SEG_FAIL_IO;
+ if (nilfs_compute_checksum(nilfs, bh_sum, &crc, sizeof(sum->ss_datasum),
+ ((u64)nblock << nilfs->ns_blocksize_bits),
+ bh_sum->b_blocknr, nblock))
+ goto out;
+
+ ret = NILFS_SEG_FAIL_CHECKSUM_FULL;
+ if (crc != le32_to_cpu(sum->ss_datasum))
+ goto out;
+ ret = 0;
+out:
+ return ret;
+}
+
+/**
+ * nilfs_read_summary_info - read an item on summary blocks of a log
+ * @nilfs: nilfs object
+ * @pbh: the current buffer head on summary blocks [in, out]
+ * @offset: the current byte offset on summary blocks [in, out]
+ * @bytes: byte size of the item to be read
+ *
+ * Return: Kernel space address of current segment summary entry, or
+ * NULL if an I/O error occurs.
+ */
+static void *nilfs_read_summary_info(struct the_nilfs *nilfs,
+ struct buffer_head **pbh,
+ unsigned int *offset, unsigned int bytes)
+{
+ void *ptr;
+ sector_t blocknr;
+
+ BUG_ON((*pbh)->b_size < *offset);
+ if (bytes > (*pbh)->b_size - *offset) {
+ blocknr = (*pbh)->b_blocknr;
+ brelse(*pbh);
+ *pbh = __bread(nilfs->ns_bdev, blocknr + 1,
+ nilfs->ns_blocksize);
+ if (unlikely(!*pbh))
+ return NULL;
+ *offset = 0;
+ }
+ ptr = (*pbh)->b_data + *offset;
+ *offset += bytes;
+ return ptr;
+}
+
+/**
+ * nilfs_skip_summary_info - skip items on summary blocks of a log
+ * @nilfs: nilfs object
+ * @pbh: the current buffer head on summary blocks [in, out]
+ * @offset: the current byte offset on summary blocks [in, out]
+ * @bytes: byte size of the item to be skipped
+ * @count: number of items to be skipped
+ */
+static void nilfs_skip_summary_info(struct the_nilfs *nilfs,
+ struct buffer_head **pbh,
+ unsigned int *offset, unsigned int bytes,
+ unsigned long count)
+{
+ unsigned int rest_item_in_current_block
+ = ((*pbh)->b_size - *offset) / bytes;
+
+ if (count <= rest_item_in_current_block) {
+ *offset += bytes * count;
+ } else {
+ sector_t blocknr = (*pbh)->b_blocknr;
+ unsigned int nitem_per_block = (*pbh)->b_size / bytes;
+ unsigned int bcnt;
+
+ count -= rest_item_in_current_block;
+ bcnt = DIV_ROUND_UP(count, nitem_per_block);
+ *offset = bytes * (count - (bcnt - 1) * nitem_per_block);
+
+ brelse(*pbh);
+ *pbh = __bread(nilfs->ns_bdev, blocknr + bcnt,
+ nilfs->ns_blocksize);
+ }
+}
+
+/**
+ * nilfs_scan_dsync_log - get block information of a log written for data sync
+ * @nilfs: nilfs object
+ * @start_blocknr: start block number of the log
+ * @sum: log summary information
+ * @head: list head to add nilfs_recovery_block struct
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_scan_dsync_log(struct the_nilfs *nilfs, sector_t start_blocknr,
+ struct nilfs_segment_summary *sum,
+ struct list_head *head)
+{
+ struct buffer_head *bh;
+ unsigned int offset;
+ u32 nfinfo, sumbytes;
+ sector_t blocknr;
+ u64 ino;
+ int err = -EIO;
+
+ nfinfo = le32_to_cpu(sum->ss_nfinfo);
+ if (!nfinfo)
+ return 0;
+
+ sumbytes = le32_to_cpu(sum->ss_sumbytes);
+ blocknr = start_blocknr + DIV_ROUND_UP(sumbytes, nilfs->ns_blocksize);
+ bh = __bread(nilfs->ns_bdev, start_blocknr, nilfs->ns_blocksize);
+ if (unlikely(!bh))
+ goto out;
+
+ offset = le16_to_cpu(sum->ss_bytes);
+ for (;;) {
+ unsigned long nblocks, ndatablk, nnodeblk;
+ struct nilfs_finfo *finfo;
+
+ finfo = nilfs_read_summary_info(nilfs, &bh, &offset,
+ sizeof(*finfo));
+ if (unlikely(!finfo))
+ goto out;
+
+ ino = le64_to_cpu(finfo->fi_ino);
+ nblocks = le32_to_cpu(finfo->fi_nblocks);
+ ndatablk = le32_to_cpu(finfo->fi_ndatablk);
+ nnodeblk = nblocks - ndatablk;
+
+ while (ndatablk-- > 0) {
+ struct nilfs_recovery_block *rb;
+ struct nilfs_binfo_v *binfo;
+
+ binfo = nilfs_read_summary_info(nilfs, &bh, &offset,
+ sizeof(*binfo));
+ if (unlikely(!binfo))
+ goto out;
+
+ rb = kmalloc_obj(*rb, GFP_NOFS);
+ if (unlikely(!rb)) {
+ err = -ENOMEM;
+ goto out;
+ }
+ rb->ino = ino;
+ rb->blocknr = blocknr++;
+ rb->vblocknr = le64_to_cpu(binfo->bi_vblocknr);
+ rb->blkoff = le64_to_cpu(binfo->bi_blkoff);
+ /* INIT_LIST_HEAD(&rb->list); */
+ list_add_tail(&rb->list, head);
+ }
+ if (--nfinfo == 0)
+ break;
+ blocknr += nnodeblk; /* always 0 for data sync logs */
+ nilfs_skip_summary_info(nilfs, &bh, &offset, sizeof(__le64),
+ nnodeblk);
+ if (unlikely(!bh))
+ goto out;
+ }
+ err = 0;
+ out:
+ brelse(bh); /* brelse(NULL) is just ignored */
+ return err;
+}
+
+static void dispose_recovery_list(struct list_head *head)
+{
+ while (!list_empty(head)) {
+ struct nilfs_recovery_block *rb;
+
+ rb = list_first_entry(head, struct nilfs_recovery_block, list);
+ list_del(&rb->list);
+ kfree(rb);
+ }
+}
+
+struct nilfs_segment_entry {
+ struct list_head list;
+ __u64 segnum;
+};
+
+static int nilfs_segment_list_add(struct list_head *head, __u64 segnum)
+{
+ struct nilfs_segment_entry *ent = kmalloc_obj(*ent, GFP_NOFS);
+
+ if (unlikely(!ent))
+ return -ENOMEM;
+
+ ent->segnum = segnum;
+ INIT_LIST_HEAD(&ent->list);
+ list_add_tail(&ent->list, head);
+ return 0;
+}
+
+void nilfs_dispose_segment_list(struct list_head *head)
+{
+ while (!list_empty(head)) {
+ struct nilfs_segment_entry *ent;
+
+ ent = list_first_entry(head, struct nilfs_segment_entry, list);
+ list_del(&ent->list);
+ kfree(ent);
+ }
+}
+
+static int nilfs_prepare_segment_for_recovery(struct the_nilfs *nilfs,
+ struct super_block *sb,
+ struct nilfs_recovery_info *ri)
+{
+ struct list_head *head = &ri->ri_used_segments;
+ struct nilfs_segment_entry *ent, *n;
+ struct inode *sufile = nilfs->ns_sufile;
+ __u64 segnum[4];
+ int err;
+ int i;
+
+ segnum[0] = nilfs->ns_segnum;
+ segnum[1] = nilfs->ns_nextnum;
+ segnum[2] = ri->ri_segnum;
+ segnum[3] = ri->ri_nextnum;
+
+ /*
+ * Releasing the next segment of the latest super root.
+ * The next segment is invalidated by this recovery.
+ */
+ err = nilfs_sufile_free(sufile, segnum[1]);
+ if (unlikely(err)) {
+ if (err == -ENOENT) {
+ nilfs_err(sb,
+ "checkpoint log inconsistency at block %llu (segment %llu): next segment %llu is unallocated",
+ (unsigned long long)nilfs->ns_last_pseg,
+ (unsigned long long)nilfs->ns_segnum,
+ (unsigned long long)segnum[1]);
+ err = -EINVAL;
+ }
+ goto failed;
+ }
+
+ for (i = 1; i < 4; i++) {
+ err = nilfs_segment_list_add(head, segnum[i]);
+ if (unlikely(err))
+ goto failed;
+ }
+
+ /*
+ * Collecting segments written after the latest super root.
+ * These are marked dirty to avoid being reallocated in the next write.
+ */
+ list_for_each_entry_safe(ent, n, head, list) {
+ if (ent->segnum != segnum[0]) {
+ err = nilfs_sufile_scrap(sufile, ent->segnum);
+ if (unlikely(err))
+ goto failed;
+ }
+ list_del(&ent->list);
+ kfree(ent);
+ }
+
+ /* Allocate new segments for recovery */
+ err = nilfs_sufile_alloc(sufile, &segnum[0]);
+ if (unlikely(err))
+ goto failed;
+
+ nilfs->ns_pseg_offset = 0;
+ nilfs->ns_seg_seq = ri->ri_seq + 2;
+ nilfs->ns_nextnum = nilfs->ns_segnum = segnum[0];
+
+ failed:
+ /* No need to recover sufile because it will be destroyed on error */
+ return err;
+}
+
+static int nilfs_recovery_copy_block(struct the_nilfs *nilfs,
+ struct nilfs_recovery_block *rb,
+ loff_t pos, struct folio *folio)
+{
+ struct buffer_head *bh_org;
+ size_t from = offset_in_folio(folio, pos);
+
+ bh_org = __bread(nilfs->ns_bdev, rb->blocknr, nilfs->ns_blocksize);
+ if (unlikely(!bh_org))
+ return -EIO;
+
+ memcpy_to_folio(folio, from, bh_org->b_data, bh_org->b_size);
+ brelse(bh_org);
+ return 0;
+}
+
+static int nilfs_recover_dsync_blocks(struct the_nilfs *nilfs,
+ struct super_block *sb,
+ struct nilfs_root *root,
+ struct list_head *head,
+ unsigned long *nr_salvaged_blocks)
+{
+ struct inode *inode;
+ struct nilfs_recovery_block *rb, *n;
+ unsigned int blocksize = nilfs->ns_blocksize;
+ struct folio *folio;
+ loff_t pos;
+ int err = 0, err2 = 0;
+
+ list_for_each_entry_safe(rb, n, head, list) {
+ inode = nilfs_iget(sb, root, rb->ino);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ inode = NULL;
+ goto failed_inode;
+ }
+
+ pos = rb->blkoff << inode->i_blkbits;
+ err = block_write_begin(inode->i_mapping, pos, blocksize,
+ &folio, nilfs_get_block);
+ if (unlikely(err)) {
+ loff_t isize = inode->i_size;
+
+ if (pos + blocksize > isize)
+ nilfs_write_failed(inode->i_mapping,
+ pos + blocksize);
+ goto failed_inode;
+ }
+
+ err = nilfs_recovery_copy_block(nilfs, rb, pos, folio);
+ if (unlikely(err))
+ goto failed_folio;
+
+ err = nilfs_set_file_dirty(inode, 1);
+ if (unlikely(err))
+ goto failed_folio;
+
+ block_write_end(pos, blocksize, blocksize, folio);
+
+ folio_unlock(folio);
+ folio_put(folio);
+
+ (*nr_salvaged_blocks)++;
+ goto next;
+
+ failed_folio:
+ folio_unlock(folio);
+ folio_put(folio);
+
+ failed_inode:
+ nilfs_warn(sb,
+ "error %d recovering data block (ino=%lu, block-offset=%llu)",
+ err, (unsigned long)rb->ino,
+ (unsigned long long)rb->blkoff);
+ if (!err2)
+ err2 = err;
+ next:
+ iput(inode); /* iput(NULL) is just ignored */
+ list_del_init(&rb->list);
+ kfree(rb);
+ }
+ return err2;
+}
+
+/**
+ * nilfs_do_roll_forward - salvage logical segments newer than the latest
+ * checkpoint
+ * @nilfs: nilfs object
+ * @sb: super block instance
+ * @root: NILFS root instance
+ * @ri: pointer to a nilfs_recovery_info
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Log format error.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_do_roll_forward(struct the_nilfs *nilfs,
+ struct super_block *sb,
+ struct nilfs_root *root,
+ struct nilfs_recovery_info *ri)
+{
+ struct buffer_head *bh_sum = NULL;
+ struct nilfs_segment_summary *sum = NULL;
+ sector_t pseg_start;
+ sector_t seg_start, seg_end; /* Starting/ending DBN of full segment */
+ unsigned long nsalvaged_blocks = 0;
+ unsigned int flags;
+ u64 seg_seq;
+ __u64 segnum, nextnum = 0;
+ int empty_seg = 0;
+ int err = 0, ret;
+ LIST_HEAD(dsync_blocks); /* list of data blocks to be recovered */
+ enum {
+ RF_INIT_ST,
+ RF_DSYNC_ST, /* scanning data-sync segments */
+ };
+ int state = RF_INIT_ST;
+
+ pseg_start = ri->ri_lsegs_start;
+ seg_seq = ri->ri_lsegs_start_seq;
+ segnum = nilfs_get_segnum_of_block(nilfs, pseg_start);
+ nilfs_get_segment_range(nilfs, segnum, &seg_start, &seg_end);
+
+ while (segnum != ri->ri_segnum || pseg_start <= ri->ri_pseg_start) {
+ brelse(bh_sum);
+ bh_sum = nilfs_read_log_header(nilfs, pseg_start, &sum);
+ if (!bh_sum) {
+ err = -EIO;
+ goto failed;
+ }
+
+ ret = nilfs_validate_log(nilfs, seg_seq, bh_sum, sum);
+ if (ret) {
+ if (ret == NILFS_SEG_FAIL_IO) {
+ err = -EIO;
+ goto failed;
+ }
+ goto strayed;
+ }
+
+ flags = le16_to_cpu(sum->ss_flags);
+ if (flags & NILFS_SS_SR)
+ goto confused;
+
+ /* Found a valid partial segment; do recovery actions */
+ nextnum = nilfs_get_segnum_of_block(nilfs,
+ le64_to_cpu(sum->ss_next));
+ empty_seg = 0;
+ nilfs->ns_ctime = le64_to_cpu(sum->ss_create);
+ if (!(flags & NILFS_SS_GC))
+ nilfs->ns_nongc_ctime = nilfs->ns_ctime;
+
+ switch (state) {
+ case RF_INIT_ST:
+ if (!(flags & NILFS_SS_LOGBGN) ||
+ !(flags & NILFS_SS_SYNDT))
+ goto try_next_pseg;
+ state = RF_DSYNC_ST;
+ fallthrough;
+ case RF_DSYNC_ST:
+ if (!(flags & NILFS_SS_SYNDT))
+ goto confused;
+
+ err = nilfs_scan_dsync_log(nilfs, pseg_start, sum,
+ &dsync_blocks);
+ if (unlikely(err))
+ goto failed;
+ if (flags & NILFS_SS_LOGEND) {
+ err = nilfs_recover_dsync_blocks(
+ nilfs, sb, root, &dsync_blocks,
+ &nsalvaged_blocks);
+ if (unlikely(err))
+ goto failed;
+ state = RF_INIT_ST;
+ }
+ break; /* Fall through to try_next_pseg */
+ }
+
+ try_next_pseg:
+ if (pseg_start == ri->ri_lsegs_end)
+ break;
+ pseg_start += le32_to_cpu(sum->ss_nblocks);
+ if (pseg_start < seg_end)
+ continue;
+ goto feed_segment;
+
+ strayed:
+ if (pseg_start == ri->ri_lsegs_end)
+ break;
+
+ feed_segment:
+ /* Looking to the next full segment */
+ if (empty_seg++)
+ break;
+ seg_seq++;
+ segnum = nextnum;
+ nilfs_get_segment_range(nilfs, segnum, &seg_start, &seg_end);
+ pseg_start = seg_start;
+ }
+
+ if (nsalvaged_blocks) {
+ nilfs_info(sb, "salvaged %lu blocks", nsalvaged_blocks);
+ ri->ri_need_recovery = NILFS_RECOVERY_ROLLFORWARD_DONE;
+ }
+ out:
+ brelse(bh_sum);
+ dispose_recovery_list(&dsync_blocks);
+ return err;
+
+ confused:
+ err = -EINVAL;
+ failed:
+ nilfs_err(sb,
+ "error %d roll-forwarding partial segment at blocknr = %llu",
+ err, (unsigned long long)pseg_start);
+ goto out;
+}
+
+static void nilfs_finish_roll_forward(struct the_nilfs *nilfs,
+ struct nilfs_recovery_info *ri)
+{
+ struct buffer_head *bh;
+ int err;
+
+ if (nilfs_get_segnum_of_block(nilfs, ri->ri_lsegs_start) !=
+ nilfs_get_segnum_of_block(nilfs, ri->ri_super_root))
+ return;
+
+ bh = __getblk(nilfs->ns_bdev, ri->ri_lsegs_start, nilfs->ns_blocksize);
+ if (WARN_ON(!bh))
+ return; /* should never happen */
+
+ lock_buffer(bh);
+ memset(bh->b_data, 0, bh->b_size);
+ set_buffer_uptodate(bh);
+ set_buffer_dirty(bh);
+ unlock_buffer(bh);
+
+ err = sync_dirty_buffer(bh);
+ if (unlikely(err))
+ nilfs_warn(nilfs->ns_sb,
+ "buffer sync write failed during post-cleaning of recovery.");
+ brelse(bh);
+}
+
+/**
+ * nilfs_abort_roll_forward - cleaning up after a failed rollforward recovery
+ * @nilfs: nilfs object
+ */
+static void nilfs_abort_roll_forward(struct the_nilfs *nilfs)
+{
+ struct nilfs_inode_info *ii, *n;
+ LIST_HEAD(head);
+
+ /* Abandon inodes that have read recovery data */
+ spin_lock(&nilfs->ns_inode_lock);
+ list_splice_init(&nilfs->ns_dirty_files, &head);
+ spin_unlock(&nilfs->ns_inode_lock);
+ if (list_empty(&head))
+ return;
+
+ set_nilfs_purging(nilfs);
+ list_for_each_entry_safe(ii, n, &head, i_dirty) {
+ spin_lock(&nilfs->ns_inode_lock);
+ list_del_init(&ii->i_dirty);
+ spin_unlock(&nilfs->ns_inode_lock);
+
+ iput(&ii->vfs_inode);
+ }
+ clear_nilfs_purging(nilfs);
+}
+
+/**
+ * nilfs_salvage_orphan_logs - salvage logs written after the latest checkpoint
+ * @nilfs: nilfs object
+ * @sb: super block instance
+ * @ri: pointer to a nilfs_recovery_info struct to store search results.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Inconsistent filesystem state.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No space left on device (only in a panic state).
+ * * %-ERESTARTSYS - Interrupted.
+ */
+int nilfs_salvage_orphan_logs(struct the_nilfs *nilfs,
+ struct super_block *sb,
+ struct nilfs_recovery_info *ri)
+{
+ struct nilfs_root *root;
+ int err;
+
+ if (ri->ri_lsegs_start == 0 || ri->ri_lsegs_end == 0)
+ return 0;
+
+ err = nilfs_attach_checkpoint(sb, ri->ri_cno, true, &root);
+ if (unlikely(err)) {
+ nilfs_err(sb, "error %d loading the latest checkpoint", err);
+ return err;
+ }
+
+ err = nilfs_do_roll_forward(nilfs, sb, root, ri);
+ if (unlikely(err))
+ goto failed;
+
+ if (ri->ri_need_recovery == NILFS_RECOVERY_ROLLFORWARD_DONE) {
+ err = nilfs_prepare_segment_for_recovery(nilfs, sb, ri);
+ if (unlikely(err)) {
+ nilfs_err(sb, "error %d preparing segment for recovery",
+ err);
+ goto failed;
+ }
+
+ err = nilfs_attach_log_writer(sb, root);
+ if (unlikely(err))
+ goto failed;
+
+ set_nilfs_discontinued(nilfs);
+ err = nilfs_construct_segment(sb);
+ nilfs_detach_log_writer(sb);
+
+ if (unlikely(err)) {
+ nilfs_err(sb, "error %d writing segment for recovery",
+ err);
+ goto put_root;
+ }
+
+ nilfs_finish_roll_forward(nilfs, ri);
+ }
+
+put_root:
+ nilfs_put_root(root);
+ return err;
+
+failed:
+ nilfs_abort_roll_forward(nilfs);
+ goto put_root;
+}
+
+/**
+ * nilfs_search_super_root - search the latest valid super root
+ * @nilfs: the_nilfs
+ * @ri: pointer to a nilfs_recovery_info struct to store search results.
+ *
+ * nilfs_search_super_root() looks for the latest super-root from a partial
+ * segment pointed by the superblock. It sets up struct the_nilfs through
+ * this search. It fills nilfs_recovery_info (ri) required for recovery.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - No valid segment found.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_search_super_root(struct the_nilfs *nilfs,
+ struct nilfs_recovery_info *ri)
+{
+ struct buffer_head *bh_sum = NULL;
+ struct nilfs_segment_summary *sum = NULL;
+ sector_t pseg_start, pseg_end, sr_pseg_start = 0;
+ sector_t seg_start, seg_end; /* range of full segment (block number) */
+ sector_t b, end;
+ unsigned long nblocks;
+ unsigned int flags;
+ u64 seg_seq;
+ __u64 segnum, nextnum = 0;
+ __u64 cno;
+ LIST_HEAD(segments);
+ int empty_seg = 0, scan_newer = 0;
+ int ret;
+
+ pseg_start = nilfs->ns_last_pseg;
+ seg_seq = nilfs->ns_last_seq;
+ cno = nilfs->ns_last_cno;
+ segnum = nilfs_get_segnum_of_block(nilfs, pseg_start);
+
+ /* Calculate range of segment */
+ nilfs_get_segment_range(nilfs, segnum, &seg_start, &seg_end);
+
+ /* Read ahead segment */
+ b = seg_start;
+ while (b <= seg_end)
+ __breadahead(nilfs->ns_bdev, b++, nilfs->ns_blocksize);
+
+ for (;;) {
+ brelse(bh_sum);
+ ret = NILFS_SEG_FAIL_IO;
+ bh_sum = nilfs_read_log_header(nilfs, pseg_start, &sum);
+ if (!bh_sum)
+ goto failed;
+
+ ret = nilfs_validate_log(nilfs, seg_seq, bh_sum, sum);
+ if (ret) {
+ if (ret == NILFS_SEG_FAIL_IO)
+ goto failed;
+ goto strayed;
+ }
+
+ nblocks = le32_to_cpu(sum->ss_nblocks);
+ pseg_end = pseg_start + nblocks - 1;
+ if (unlikely(pseg_end > seg_end)) {
+ ret = NILFS_SEG_FAIL_CONSISTENCY;
+ goto strayed;
+ }
+
+ /* A valid partial segment */
+ ri->ri_pseg_start = pseg_start;
+ ri->ri_seq = seg_seq;
+ ri->ri_segnum = segnum;
+ nextnum = nilfs_get_segnum_of_block(nilfs,
+ le64_to_cpu(sum->ss_next));
+ ri->ri_nextnum = nextnum;
+ empty_seg = 0;
+
+ flags = le16_to_cpu(sum->ss_flags);
+ if (!(flags & NILFS_SS_SR) && !scan_newer) {
+ /*
+ * This will never happen because a superblock
+ * (last_segment) always points to a pseg with
+ * a super root.
+ */
+ ret = NILFS_SEG_FAIL_CONSISTENCY;
+ goto failed;
+ }
+
+ if (pseg_start == seg_start) {
+ nilfs_get_segment_range(nilfs, nextnum, &b, &end);
+ while (b <= end)
+ __breadahead(nilfs->ns_bdev, b++,
+ nilfs->ns_blocksize);
+ }
+ if (!(flags & NILFS_SS_SR)) {
+ if (!ri->ri_lsegs_start && (flags & NILFS_SS_LOGBGN)) {
+ ri->ri_lsegs_start = pseg_start;
+ ri->ri_lsegs_start_seq = seg_seq;
+ }
+ if (flags & NILFS_SS_LOGEND)
+ ri->ri_lsegs_end = pseg_start;
+ goto try_next_pseg;
+ }
+
+ /* A valid super root was found. */
+ ri->ri_cno = cno++;
+ ri->ri_super_root = pseg_end;
+ ri->ri_lsegs_start = ri->ri_lsegs_end = 0;
+
+ nilfs_dispose_segment_list(&segments);
+ sr_pseg_start = pseg_start;
+ nilfs->ns_pseg_offset = pseg_start + nblocks - seg_start;
+ nilfs->ns_seg_seq = seg_seq;
+ nilfs->ns_segnum = segnum;
+ nilfs->ns_cno = cno; /* nilfs->ns_cno = ri->ri_cno + 1 */
+ nilfs->ns_ctime = le64_to_cpu(sum->ss_create);
+ nilfs->ns_nextnum = nextnum;
+
+ if (scan_newer)
+ ri->ri_need_recovery = NILFS_RECOVERY_SR_UPDATED;
+ else {
+ if (nilfs->ns_mount_state & NILFS_VALID_FS)
+ goto super_root_found;
+ scan_newer = 1;
+ }
+
+ try_next_pseg:
+ /* Standing on a course, or met an inconsistent state */
+ pseg_start += nblocks;
+ if (pseg_start < seg_end)
+ continue;
+ goto feed_segment;
+
+ strayed:
+ /* Off the trail */
+ if (!scan_newer)
+ /*
+ * This can happen if a checkpoint was written without
+ * barriers, or as a result of an I/O failure.
+ */
+ goto failed;
+
+ feed_segment:
+ /* Looking to the next full segment */
+ if (empty_seg++)
+ goto super_root_found; /* found a valid super root */
+
+ ret = nilfs_segment_list_add(&segments, segnum);
+ if (unlikely(ret))
+ goto failed;
+
+ seg_seq++;
+ segnum = nextnum;
+ nilfs_get_segment_range(nilfs, segnum, &seg_start, &seg_end);
+ pseg_start = seg_start;
+ }
+
+ super_root_found:
+ /* Updating pointers relating to the latest checkpoint */
+ brelse(bh_sum);
+ list_splice_tail(&segments, &ri->ri_used_segments);
+ nilfs->ns_last_pseg = sr_pseg_start;
+ nilfs->ns_last_seq = nilfs->ns_seg_seq;
+ nilfs->ns_last_cno = ri->ri_cno;
+ return 0;
+
+ failed:
+ brelse(bh_sum);
+ nilfs_dispose_segment_list(&segments);
+ return ret < 0 ? ret : nilfs_warn_segment_error(nilfs->ns_sb, ret);
+}
diff --git a/fs/nilfs2/segbuf.c b/fs/nilfs2/segbuf.c
new file mode 100644
index 000000000..a8bdf3d31
--- /dev/null
+++ b/fs/nilfs2/segbuf.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS segment buffer
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/writeback.h>
+#include <linux/crc32.h>
+#include <linux/backing-dev.h>
+#include <linux/slab.h>
+#include "page.h"
+#include "segbuf.h"
+
+
+struct nilfs_write_info {
+ struct the_nilfs *nilfs;
+ struct bio *bio;
+ int start, end; /* The region to be submitted */
+ int rest_blocks;
+ int max_pages;
+ int nr_vecs;
+ sector_t blocknr;
+};
+
+static int nilfs_segbuf_write(struct nilfs_segment_buffer *segbuf,
+ struct the_nilfs *nilfs);
+static int nilfs_segbuf_wait(struct nilfs_segment_buffer *segbuf);
+
+struct nilfs_segment_buffer *nilfs_segbuf_new(struct super_block *sb)
+{
+ struct nilfs_segment_buffer *segbuf;
+
+ segbuf = kmem_cache_alloc(nilfs_segbuf_cachep, GFP_NOFS);
+ if (unlikely(!segbuf))
+ return NULL;
+
+ segbuf->sb_super = sb;
+ INIT_LIST_HEAD(&segbuf->sb_list);
+ INIT_LIST_HEAD(&segbuf->sb_segsum_buffers);
+ INIT_LIST_HEAD(&segbuf->sb_payload_buffers);
+ segbuf->sb_super_root = NULL;
+
+ init_completion(&segbuf->sb_bio_event);
+ atomic_set(&segbuf->sb_err, 0);
+ segbuf->sb_nbio = 0;
+
+ return segbuf;
+}
+
+void nilfs_segbuf_free(struct nilfs_segment_buffer *segbuf)
+{
+ kmem_cache_free(nilfs_segbuf_cachep, segbuf);
+}
+
+void nilfs_segbuf_map(struct nilfs_segment_buffer *segbuf, __u64 segnum,
+ unsigned long offset, struct the_nilfs *nilfs)
+{
+ segbuf->sb_segnum = segnum;
+ nilfs_get_segment_range(nilfs, segnum, &segbuf->sb_fseg_start,
+ &segbuf->sb_fseg_end);
+
+ segbuf->sb_pseg_start = segbuf->sb_fseg_start + offset;
+ segbuf->sb_rest_blocks =
+ segbuf->sb_fseg_end - segbuf->sb_pseg_start + 1;
+}
+
+/**
+ * nilfs_segbuf_map_cont - map a new log behind a given log
+ * @segbuf: new segment buffer
+ * @prev: segment buffer containing a log to be continued
+ */
+void nilfs_segbuf_map_cont(struct nilfs_segment_buffer *segbuf,
+ struct nilfs_segment_buffer *prev)
+{
+ segbuf->sb_segnum = prev->sb_segnum;
+ segbuf->sb_fseg_start = prev->sb_fseg_start;
+ segbuf->sb_fseg_end = prev->sb_fseg_end;
+ segbuf->sb_pseg_start = prev->sb_pseg_start + prev->sb_sum.nblocks;
+ segbuf->sb_rest_blocks =
+ segbuf->sb_fseg_end - segbuf->sb_pseg_start + 1;
+}
+
+void nilfs_segbuf_set_next_segnum(struct nilfs_segment_buffer *segbuf,
+ __u64 nextnum, struct the_nilfs *nilfs)
+{
+ segbuf->sb_nextnum = nextnum;
+ segbuf->sb_sum.next = nilfs_get_segment_start_blocknr(nilfs, nextnum);
+}
+
+int nilfs_segbuf_extend_segsum(struct nilfs_segment_buffer *segbuf)
+{
+ struct buffer_head *bh;
+
+ bh = sb_getblk(segbuf->sb_super,
+ segbuf->sb_pseg_start + segbuf->sb_sum.nsumblk);
+ if (unlikely(!bh))
+ return -ENOMEM;
+
+ lock_buffer(bh);
+ if (!buffer_uptodate(bh)) {
+ memset(bh->b_data, 0, bh->b_size);
+ set_buffer_uptodate(bh);
+ }
+ unlock_buffer(bh);
+ nilfs_segbuf_add_segsum_buffer(segbuf, bh);
+ return 0;
+}
+
+int nilfs_segbuf_extend_payload(struct nilfs_segment_buffer *segbuf,
+ struct buffer_head **bhp)
+{
+ struct buffer_head *bh;
+
+ bh = sb_getblk(segbuf->sb_super,
+ segbuf->sb_pseg_start + segbuf->sb_sum.nblocks);
+ if (unlikely(!bh))
+ return -ENOMEM;
+
+ nilfs_segbuf_add_payload_buffer(segbuf, bh);
+ *bhp = bh;
+ return 0;
+}
+
+int nilfs_segbuf_reset(struct nilfs_segment_buffer *segbuf, unsigned int flags,
+ time64_t ctime, __u64 cno)
+{
+ int err;
+
+ segbuf->sb_sum.nblocks = segbuf->sb_sum.nsumblk = 0;
+ err = nilfs_segbuf_extend_segsum(segbuf);
+ if (unlikely(err))
+ return err;
+
+ segbuf->sb_sum.flags = flags;
+ segbuf->sb_sum.sumbytes = sizeof(struct nilfs_segment_summary);
+ segbuf->sb_sum.nfinfo = segbuf->sb_sum.nfileblk = 0;
+ segbuf->sb_sum.ctime = ctime;
+ segbuf->sb_sum.cno = cno;
+ return 0;
+}
+
+/*
+ * Setup segment summary
+ */
+void nilfs_segbuf_fill_in_segsum(struct nilfs_segment_buffer *segbuf)
+{
+ struct nilfs_segment_summary *raw_sum;
+ struct buffer_head *bh_sum;
+
+ bh_sum = list_entry(segbuf->sb_segsum_buffers.next,
+ struct buffer_head, b_assoc_buffers);
+ raw_sum = (struct nilfs_segment_summary *)bh_sum->b_data;
+
+ raw_sum->ss_magic = cpu_to_le32(NILFS_SEGSUM_MAGIC);
+ raw_sum->ss_bytes = cpu_to_le16(sizeof(*raw_sum));
+ raw_sum->ss_flags = cpu_to_le16(segbuf->sb_sum.flags);
+ raw_sum->ss_seq = cpu_to_le64(segbuf->sb_sum.seg_seq);
+ raw_sum->ss_create = cpu_to_le64(segbuf->sb_sum.ctime);
+ raw_sum->ss_next = cpu_to_le64(segbuf->sb_sum.next);
+ raw_sum->ss_nblocks = cpu_to_le32(segbuf->sb_sum.nblocks);
+ raw_sum->ss_nfinfo = cpu_to_le32(segbuf->sb_sum.nfinfo);
+ raw_sum->ss_sumbytes = cpu_to_le32(segbuf->sb_sum.sumbytes);
+ raw_sum->ss_pad = 0;
+ raw_sum->ss_cno = cpu_to_le64(segbuf->sb_sum.cno);
+}
+
+/*
+ * CRC calculation routines
+ */
+static void
+nilfs_segbuf_fill_in_segsum_crc(struct nilfs_segment_buffer *segbuf, u32 seed)
+{
+ struct buffer_head *bh;
+ struct nilfs_segment_summary *raw_sum;
+ unsigned long size, bytes = segbuf->sb_sum.sumbytes;
+ u32 crc;
+
+ bh = list_entry(segbuf->sb_segsum_buffers.next, struct buffer_head,
+ b_assoc_buffers);
+
+ raw_sum = (struct nilfs_segment_summary *)bh->b_data;
+ size = min_t(unsigned long, bytes, bh->b_size);
+ crc = crc32_le(seed,
+ (unsigned char *)raw_sum +
+ sizeof(raw_sum->ss_datasum) + sizeof(raw_sum->ss_sumsum),
+ size - (sizeof(raw_sum->ss_datasum) +
+ sizeof(raw_sum->ss_sumsum)));
+
+ list_for_each_entry_continue(bh, &segbuf->sb_segsum_buffers,
+ b_assoc_buffers) {
+ bytes -= size;
+ size = min_t(unsigned long, bytes, bh->b_size);
+ crc = crc32_le(crc, bh->b_data, size);
+ }
+ raw_sum->ss_sumsum = cpu_to_le32(crc);
+}
+
+static void nilfs_segbuf_fill_in_data_crc(struct nilfs_segment_buffer *segbuf,
+ u32 seed)
+{
+ struct buffer_head *bh;
+ struct nilfs_segment_summary *raw_sum;
+ u32 crc;
+
+ bh = list_entry(segbuf->sb_segsum_buffers.next, struct buffer_head,
+ b_assoc_buffers);
+ raw_sum = (struct nilfs_segment_summary *)bh->b_data;
+ crc = crc32_le(seed,
+ (unsigned char *)raw_sum + sizeof(raw_sum->ss_datasum),
+ bh->b_size - sizeof(raw_sum->ss_datasum));
+
+ list_for_each_entry_continue(bh, &segbuf->sb_segsum_buffers,
+ b_assoc_buffers) {
+ crc = crc32_le(crc, bh->b_data, bh->b_size);
+ }
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers, b_assoc_buffers) {
+ size_t offset = offset_in_folio(bh->b_folio, bh->b_data);
+ unsigned char *from;
+
+ /* Do not support block sizes larger than PAGE_SIZE */
+ from = kmap_local_folio(bh->b_folio, offset);
+ crc = crc32_le(crc, from, bh->b_size);
+ kunmap_local(from);
+ }
+ raw_sum->ss_datasum = cpu_to_le32(crc);
+}
+
+static void
+nilfs_segbuf_fill_in_super_root_crc(struct nilfs_segment_buffer *segbuf,
+ u32 seed)
+{
+ struct nilfs_super_root *raw_sr;
+ struct the_nilfs *nilfs = segbuf->sb_super->s_fs_info;
+ unsigned int srsize;
+ u32 crc;
+
+ raw_sr = (struct nilfs_super_root *)segbuf->sb_super_root->b_data;
+ srsize = NILFS_SR_BYTES(nilfs->ns_inode_size);
+ crc = crc32_le(seed,
+ (unsigned char *)raw_sr + sizeof(raw_sr->sr_sum),
+ srsize - sizeof(raw_sr->sr_sum));
+ raw_sr->sr_sum = cpu_to_le32(crc);
+}
+
+static void nilfs_release_buffers(struct list_head *list)
+{
+ struct buffer_head *bh, *n;
+
+ list_for_each_entry_safe(bh, n, list, b_assoc_buffers) {
+ list_del_init(&bh->b_assoc_buffers);
+ brelse(bh);
+ }
+}
+
+static void nilfs_segbuf_clear(struct nilfs_segment_buffer *segbuf)
+{
+ nilfs_release_buffers(&segbuf->sb_segsum_buffers);
+ nilfs_release_buffers(&segbuf->sb_payload_buffers);
+ segbuf->sb_super_root = NULL;
+}
+
+/*
+ * Iterators for segment buffers
+ */
+void nilfs_clear_logs(struct list_head *logs)
+{
+ struct nilfs_segment_buffer *segbuf;
+
+ list_for_each_entry(segbuf, logs, sb_list)
+ nilfs_segbuf_clear(segbuf);
+}
+
+void nilfs_truncate_logs(struct list_head *logs,
+ struct nilfs_segment_buffer *last)
+{
+ struct nilfs_segment_buffer *n, *segbuf;
+
+ segbuf = list_prepare_entry(last, logs, sb_list);
+ list_for_each_entry_safe_continue(segbuf, n, logs, sb_list) {
+ list_del_init(&segbuf->sb_list);
+ nilfs_segbuf_clear(segbuf);
+ nilfs_segbuf_free(segbuf);
+ }
+}
+
+int nilfs_write_logs(struct list_head *logs, struct the_nilfs *nilfs)
+{
+ struct nilfs_segment_buffer *segbuf;
+ int ret = 0;
+
+ list_for_each_entry(segbuf, logs, sb_list) {
+ ret = nilfs_segbuf_write(segbuf, nilfs);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+int nilfs_wait_on_logs(struct list_head *logs)
+{
+ struct nilfs_segment_buffer *segbuf;
+ int err, ret = 0;
+
+ list_for_each_entry(segbuf, logs, sb_list) {
+ err = nilfs_segbuf_wait(segbuf);
+ if (err && !ret)
+ ret = err;
+ }
+ return ret;
+}
+
+/**
+ * nilfs_add_checksums_on_logs - add checksums on the logs
+ * @logs: list of segment buffers storing target logs
+ * @seed: checksum seed value
+ */
+void nilfs_add_checksums_on_logs(struct list_head *logs, u32 seed)
+{
+ struct nilfs_segment_buffer *segbuf;
+
+ list_for_each_entry(segbuf, logs, sb_list) {
+ if (segbuf->sb_super_root)
+ nilfs_segbuf_fill_in_super_root_crc(segbuf, seed);
+ nilfs_segbuf_fill_in_segsum_crc(segbuf, seed);
+ nilfs_segbuf_fill_in_data_crc(segbuf, seed);
+ }
+}
+
+/*
+ * BIO operations
+ */
+static void nilfs_end_bio_write(struct bio *bio)
+{
+ struct nilfs_segment_buffer *segbuf = bio->bi_private;
+
+ if (bio->bi_status)
+ atomic_inc(&segbuf->sb_err);
+
+ bio_put(bio);
+ complete(&segbuf->sb_bio_event);
+}
+
+static int nilfs_segbuf_submit_bio(struct nilfs_segment_buffer *segbuf,
+ struct nilfs_write_info *wi)
+{
+ struct bio *bio = wi->bio;
+
+ bio->bi_end_io = nilfs_end_bio_write;
+ bio->bi_private = segbuf;
+ submit_bio(bio);
+ segbuf->sb_nbio++;
+
+ wi->bio = NULL;
+ wi->rest_blocks -= wi->end - wi->start;
+ wi->nr_vecs = min(wi->max_pages, wi->rest_blocks);
+ wi->start = wi->end;
+ return 0;
+}
+
+static void nilfs_segbuf_prepare_write(struct nilfs_segment_buffer *segbuf,
+ struct nilfs_write_info *wi)
+{
+ wi->bio = NULL;
+ wi->rest_blocks = segbuf->sb_sum.nblocks;
+ wi->max_pages = BIO_MAX_VECS;
+ wi->nr_vecs = min(wi->max_pages, wi->rest_blocks);
+ wi->start = wi->end = 0;
+ wi->blocknr = segbuf->sb_pseg_start;
+}
+
+static int nilfs_segbuf_submit_bh(struct nilfs_segment_buffer *segbuf,
+ struct nilfs_write_info *wi,
+ struct buffer_head *bh)
+{
+ int err;
+
+ BUG_ON(wi->nr_vecs <= 0);
+ repeat:
+ if (!wi->bio) {
+ wi->bio = bio_alloc(wi->nilfs->ns_bdev, wi->nr_vecs,
+ REQ_OP_WRITE, GFP_NOIO);
+ wi->bio->bi_iter.bi_sector = (wi->blocknr + wi->end) <<
+ (wi->nilfs->ns_blocksize_bits - 9);
+ }
+
+ if (bio_add_folio(wi->bio, bh->b_folio, bh->b_size,
+ offset_in_folio(bh->b_folio, bh->b_data))) {
+ wi->end++;
+ return 0;
+ }
+ /* bio is FULL */
+ err = nilfs_segbuf_submit_bio(segbuf, wi);
+ /* never submit current bh */
+ if (likely(!err))
+ goto repeat;
+ return err;
+}
+
+/**
+ * nilfs_segbuf_write - submit write requests of a log
+ * @segbuf: buffer storing a log to be written
+ * @nilfs: nilfs object
+ *
+ * Return: Always 0.
+ */
+static int nilfs_segbuf_write(struct nilfs_segment_buffer *segbuf,
+ struct the_nilfs *nilfs)
+{
+ struct nilfs_write_info wi;
+ struct buffer_head *bh;
+ int res = 0;
+
+ wi.nilfs = nilfs;
+ nilfs_segbuf_prepare_write(segbuf, &wi);
+
+ list_for_each_entry(bh, &segbuf->sb_segsum_buffers, b_assoc_buffers) {
+ res = nilfs_segbuf_submit_bh(segbuf, &wi, bh);
+ if (unlikely(res))
+ goto failed_bio;
+ }
+
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers, b_assoc_buffers) {
+ res = nilfs_segbuf_submit_bh(segbuf, &wi, bh);
+ if (unlikely(res))
+ goto failed_bio;
+ }
+
+ if (wi.bio) {
+ /*
+ * Last BIO is always sent through the following
+ * submission.
+ */
+ wi.bio->bi_opf |= REQ_SYNC;
+ res = nilfs_segbuf_submit_bio(segbuf, &wi);
+ }
+
+ failed_bio:
+ return res;
+}
+
+/**
+ * nilfs_segbuf_wait - wait for completion of requested BIOs
+ * @segbuf: segment buffer
+ *
+ * Return: 0 on success, or %-EIO if I/O error is detected.
+ */
+static int nilfs_segbuf_wait(struct nilfs_segment_buffer *segbuf)
+{
+ int err = 0;
+
+ if (!segbuf->sb_nbio)
+ return 0;
+
+ do {
+ wait_for_completion(&segbuf->sb_bio_event);
+ } while (--segbuf->sb_nbio > 0);
+
+ if (unlikely(atomic_read(&segbuf->sb_err) > 0)) {
+ nilfs_err(segbuf->sb_super,
+ "I/O error writing log (start-blocknr=%llu, block-count=%lu) in segment %llu",
+ (unsigned long long)segbuf->sb_pseg_start,
+ segbuf->sb_sum.nblocks,
+ (unsigned long long)segbuf->sb_segnum);
+ err = -EIO;
+ }
+ return err;
+}
diff --git a/fs/nilfs2/segbuf.h b/fs/nilfs2/segbuf.h
new file mode 100644
index 000000000..e20091ede
--- /dev/null
+++ b/fs/nilfs2/segbuf.h
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS Segment buffer prototypes and definitions
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+#ifndef _NILFS_SEGBUF_H
+#define _NILFS_SEGBUF_H
+
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/bio.h>
+#include <linux/completion.h>
+
+/**
+ * struct nilfs_segsum_info - On-memory segment summary
+ * @flags: Flags
+ * @nfinfo: Number of file information structures
+ * @nblocks: Number of blocks included in the partial segment
+ * @nsumblk: Number of summary blocks
+ * @sumbytes: Byte count of segment summary
+ * @nfileblk: Total number of file blocks
+ * @seg_seq: Segment sequence number
+ * @cno: Checkpoint number
+ * @ctime: Creation time
+ * @next: Block number of the next full segment
+ */
+struct nilfs_segsum_info {
+ unsigned int flags;
+ unsigned long nfinfo;
+ unsigned long nblocks;
+ unsigned long nsumblk;
+ unsigned long sumbytes;
+ unsigned long nfileblk;
+ u64 seg_seq;
+ __u64 cno;
+ time64_t ctime;
+ sector_t next;
+};
+
+/**
+ * struct nilfs_segment_buffer - Segment buffer
+ * @sb_super: back pointer to a superblock struct
+ * @sb_list: List head to chain this structure
+ * @sb_sum: On-memory segment summary
+ * @sb_segnum: Index number of the full segment
+ * @sb_nextnum: Index number of the next full segment
+ * @sb_fseg_start: Start block number of the full segment
+ * @sb_fseg_end: End block number of the full segment
+ * @sb_pseg_start: Disk block number of partial segment
+ * @sb_rest_blocks: Number of residual blocks in the current segment
+ * @sb_segsum_buffers: List of buffers for segment summaries
+ * @sb_payload_buffers: List of buffers for segment payload
+ * @sb_super_root: Pointer to buffer storing a super root block (if exists)
+ * @sb_nbio: Number of flying bio requests
+ * @sb_err: I/O error status
+ * @sb_bio_event: Completion event of log writing
+ */
+struct nilfs_segment_buffer {
+ struct super_block *sb_super;
+ struct list_head sb_list;
+
+ /* Segment information */
+ struct nilfs_segsum_info sb_sum;
+ __u64 sb_segnum;
+ __u64 sb_nextnum;
+ sector_t sb_fseg_start, sb_fseg_end;
+ sector_t sb_pseg_start;
+ unsigned int sb_rest_blocks;
+
+ /* Buffers */
+ struct list_head sb_segsum_buffers;
+ struct list_head sb_payload_buffers; /* including super root */
+ struct buffer_head *sb_super_root;
+
+ /* io status */
+ int sb_nbio;
+ atomic_t sb_err;
+ struct completion sb_bio_event;
+};
+
+#define NILFS_LIST_SEGBUF(head) \
+ list_entry((head), struct nilfs_segment_buffer, sb_list)
+#define NILFS_NEXT_SEGBUF(segbuf) NILFS_LIST_SEGBUF((segbuf)->sb_list.next)
+#define NILFS_PREV_SEGBUF(segbuf) NILFS_LIST_SEGBUF((segbuf)->sb_list.prev)
+#define NILFS_LAST_SEGBUF(head) NILFS_LIST_SEGBUF((head)->prev)
+#define NILFS_FIRST_SEGBUF(head) NILFS_LIST_SEGBUF((head)->next)
+#define NILFS_SEGBUF_IS_LAST(segbuf, head) ((segbuf)->sb_list.next == (head))
+
+#define nilfs_for_each_segbuf_before(s, t, h) \
+ for ((s) = NILFS_FIRST_SEGBUF(h); (s) != (t); \
+ (s) = NILFS_NEXT_SEGBUF(s))
+
+#define NILFS_SEGBUF_FIRST_BH(head) \
+ (list_entry((head)->next, struct buffer_head, b_assoc_buffers))
+#define NILFS_SEGBUF_NEXT_BH(bh) \
+ (list_entry((bh)->b_assoc_buffers.next, struct buffer_head, \
+ b_assoc_buffers))
+#define NILFS_SEGBUF_BH_IS_LAST(bh, head) ((bh)->b_assoc_buffers.next == head)
+
+extern struct kmem_cache *nilfs_segbuf_cachep;
+
+struct nilfs_segment_buffer *nilfs_segbuf_new(struct super_block *);
+void nilfs_segbuf_free(struct nilfs_segment_buffer *);
+void nilfs_segbuf_map(struct nilfs_segment_buffer *, __u64, unsigned long,
+ struct the_nilfs *);
+void nilfs_segbuf_map_cont(struct nilfs_segment_buffer *segbuf,
+ struct nilfs_segment_buffer *prev);
+void nilfs_segbuf_set_next_segnum(struct nilfs_segment_buffer *, __u64,
+ struct the_nilfs *);
+int nilfs_segbuf_reset(struct nilfs_segment_buffer *, unsigned int, time64_t,
+ __u64);
+int nilfs_segbuf_extend_segsum(struct nilfs_segment_buffer *);
+int nilfs_segbuf_extend_payload(struct nilfs_segment_buffer *,
+ struct buffer_head **);
+void nilfs_segbuf_fill_in_segsum(struct nilfs_segment_buffer *);
+
+static inline int nilfs_segbuf_simplex(struct nilfs_segment_buffer *segbuf)
+{
+ unsigned int flags = segbuf->sb_sum.flags;
+
+ return (flags & (NILFS_SS_LOGBGN | NILFS_SS_LOGEND)) ==
+ (NILFS_SS_LOGBGN | NILFS_SS_LOGEND);
+}
+
+static inline int nilfs_segbuf_empty(struct nilfs_segment_buffer *segbuf)
+{
+ return segbuf->sb_sum.nblocks == segbuf->sb_sum.nsumblk;
+}
+
+static inline void
+nilfs_segbuf_add_segsum_buffer(struct nilfs_segment_buffer *segbuf,
+ struct buffer_head *bh)
+{
+ list_add_tail(&bh->b_assoc_buffers, &segbuf->sb_segsum_buffers);
+ segbuf->sb_sum.nblocks++;
+ segbuf->sb_sum.nsumblk++;
+}
+
+static inline void
+nilfs_segbuf_add_payload_buffer(struct nilfs_segment_buffer *segbuf,
+ struct buffer_head *bh)
+{
+ list_add_tail(&bh->b_assoc_buffers, &segbuf->sb_payload_buffers);
+ segbuf->sb_sum.nblocks++;
+}
+
+static inline void
+nilfs_segbuf_add_file_buffer(struct nilfs_segment_buffer *segbuf,
+ struct buffer_head *bh)
+{
+ get_bh(bh);
+ nilfs_segbuf_add_payload_buffer(segbuf, bh);
+ segbuf->sb_sum.nfileblk++;
+}
+
+void nilfs_clear_logs(struct list_head *logs);
+void nilfs_truncate_logs(struct list_head *logs,
+ struct nilfs_segment_buffer *last);
+int nilfs_write_logs(struct list_head *logs, struct the_nilfs *nilfs);
+int nilfs_wait_on_logs(struct list_head *logs);
+void nilfs_add_checksums_on_logs(struct list_head *logs, u32 seed);
+
+static inline void nilfs_destroy_logs(struct list_head *logs)
+{
+ nilfs_truncate_logs(logs, NULL);
+}
+
+#endif /* _NILFS_SEGBUF_H */
diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c
new file mode 100644
index 000000000..829573cb6
--- /dev/null
+++ b/fs/nilfs2/segment.c
@@ -0,0 +1,2929 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS segment constructor.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+
+#include <linux/pagemap.h>
+#include <linux/buffer_head.h>
+#include <linux/writeback.h>
+#include <linux/bitops.h>
+#include <linux/bio.h>
+#include <linux/completion.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/crc32.h>
+#include <linux/folio_batch.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+
+#include "nilfs.h"
+#include "btnode.h"
+#include "page.h"
+#include "segment.h"
+#include "sufile.h"
+#include "cpfile.h"
+#include "ifile.h"
+#include "segbuf.h"
+
+
+/*
+ * Segment constructor
+ */
+#define SC_N_INODEVEC 16 /* Size of locally allocated inode vector */
+
+#define SC_MAX_SEGDELTA 64 /*
+ * Upper limit of the number of segments
+ * appended in collection retry loop
+ */
+
+/* Construction mode */
+enum {
+ SC_LSEG_SR = 1, /* Make a logical segment having a super root */
+ SC_LSEG_DSYNC, /*
+ * Flush data blocks of a given file and make
+ * a logical segment without a super root.
+ */
+ SC_FLUSH_FILE, /*
+ * Flush data files, leads to segment writes without
+ * creating a checkpoint.
+ */
+ SC_FLUSH_DAT, /*
+ * Flush DAT file. This also creates segments
+ * without a checkpoint.
+ */
+};
+
+/* Stage numbers of dirty block collection */
+enum {
+ NILFS_ST_INIT = 0,
+ NILFS_ST_GC, /* Collecting dirty blocks for GC */
+ NILFS_ST_FILE,
+ NILFS_ST_IFILE,
+ NILFS_ST_CPFILE,
+ NILFS_ST_SUFILE,
+ NILFS_ST_DAT,
+ NILFS_ST_SR, /* Super root */
+ NILFS_ST_DSYNC, /* Data sync blocks */
+ NILFS_ST_DONE,
+};
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/nilfs2.h>
+
+/*
+ * nilfs_sc_cstage_inc(), nilfs_sc_cstage_set(), nilfs_sc_cstage_get() are
+ * wrapper functions of stage count (nilfs_sc_info->sc_stage.scnt). Users of
+ * the variable must use them because transition of stage count must involve
+ * trace events (trace_nilfs2_collection_stage_transition).
+ *
+ * nilfs_sc_cstage_get() isn't required for the above purpose because it doesn't
+ * produce tracepoint events. It is provided just for making the intention
+ * clear.
+ */
+static inline void nilfs_sc_cstage_inc(struct nilfs_sc_info *sci)
+{
+ sci->sc_stage.scnt++;
+ trace_nilfs2_collection_stage_transition(sci);
+}
+
+static inline void nilfs_sc_cstage_set(struct nilfs_sc_info *sci, int next_scnt)
+{
+ sci->sc_stage.scnt = next_scnt;
+ trace_nilfs2_collection_stage_transition(sci);
+}
+
+static inline int nilfs_sc_cstage_get(struct nilfs_sc_info *sci)
+{
+ return sci->sc_stage.scnt;
+}
+
+/* State flags of collection */
+#define NILFS_CF_NODE 0x0001 /* Collecting node blocks */
+#define NILFS_CF_IFILE_STARTED 0x0002 /* IFILE stage has started */
+#define NILFS_CF_SUFREED 0x0004 /* segment usages has been freed */
+#define NILFS_CF_HISTORY_MASK (NILFS_CF_IFILE_STARTED | NILFS_CF_SUFREED)
+
+/* Operations depending on the construction mode and file type */
+struct nilfs_sc_operations {
+ int (*collect_data)(struct nilfs_sc_info *, struct buffer_head *,
+ struct inode *);
+ int (*collect_node)(struct nilfs_sc_info *, struct buffer_head *,
+ struct inode *);
+ int (*collect_bmap)(struct nilfs_sc_info *, struct buffer_head *,
+ struct inode *);
+ void (*write_data_binfo)(struct nilfs_sc_info *,
+ struct nilfs_segsum_pointer *,
+ union nilfs_binfo *);
+ void (*write_node_binfo)(struct nilfs_sc_info *,
+ struct nilfs_segsum_pointer *,
+ union nilfs_binfo *);
+};
+
+/*
+ * Other definitions
+ */
+static void nilfs_segctor_start_timer(struct nilfs_sc_info *);
+static void nilfs_segctor_do_flush(struct nilfs_sc_info *, int);
+static void nilfs_segctor_do_immediate_flush(struct nilfs_sc_info *);
+static void nilfs_dispose_list(struct the_nilfs *, struct list_head *, int);
+
+#define nilfs_cnt32_ge(a, b) \
+ (typecheck(__u32, a) && typecheck(__u32, b) && \
+ ((__s32)((a) - (b)) >= 0))
+
+static int nilfs_prepare_segment_lock(struct super_block *sb,
+ struct nilfs_transaction_info *ti)
+{
+ struct nilfs_transaction_info *cur_ti = current->journal_info;
+ void *save = NULL;
+
+ if (cur_ti) {
+ if (cur_ti->ti_magic == NILFS_TI_MAGIC)
+ return ++cur_ti->ti_count;
+
+ /*
+ * If journal_info field is occupied by other FS,
+ * it is saved and will be restored on
+ * nilfs_transaction_commit().
+ */
+ nilfs_warn(sb, "journal info from a different FS");
+ save = current->journal_info;
+ }
+ if (!ti) {
+ ti = kmem_cache_alloc(nilfs_transaction_cachep, GFP_NOFS);
+ if (!ti)
+ return -ENOMEM;
+ ti->ti_flags = NILFS_TI_DYNAMIC_ALLOC;
+ } else {
+ ti->ti_flags = 0;
+ }
+ ti->ti_count = 0;
+ ti->ti_save = save;
+ ti->ti_magic = NILFS_TI_MAGIC;
+ current->journal_info = ti;
+ return 0;
+}
+
+/**
+ * nilfs_transaction_begin - start indivisible file operations.
+ * @sb: super block
+ * @ti: nilfs_transaction_info
+ * @vacancy_check: flags for vacancy rate checks
+ *
+ * nilfs_transaction_begin() acquires a reader/writer semaphore, called
+ * the segment semaphore, to make a segment construction and write tasks
+ * exclusive. The function is used with nilfs_transaction_commit() in pairs.
+ * The region enclosed by these two functions can be nested. To avoid a
+ * deadlock, the semaphore is only acquired or released in the outermost call.
+ *
+ * This function allocates a nilfs_transaction_info struct to keep context
+ * information on it. It is initialized and hooked onto the current task in
+ * the outermost call. If a pre-allocated struct is given to @ti, it is used
+ * instead; otherwise a new struct is assigned from a slab.
+ *
+ * When @vacancy_check flag is set, this function will check the amount of
+ * free space, and will wait for the GC to reclaim disk space if low capacity.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No space left on device (if checking free space).
+ */
+int nilfs_transaction_begin(struct super_block *sb,
+ struct nilfs_transaction_info *ti,
+ int vacancy_check)
+{
+ struct the_nilfs *nilfs;
+ int ret = nilfs_prepare_segment_lock(sb, ti);
+ struct nilfs_transaction_info *trace_ti;
+
+ if (unlikely(ret < 0))
+ return ret;
+ if (ret > 0) {
+ trace_ti = current->journal_info;
+
+ trace_nilfs2_transaction_transition(sb, trace_ti,
+ trace_ti->ti_count, trace_ti->ti_flags,
+ TRACE_NILFS2_TRANSACTION_BEGIN);
+ return 0;
+ }
+
+ sb_start_intwrite(sb);
+
+ nilfs = sb->s_fs_info;
+ down_read(&nilfs->ns_segctor_sem);
+ if (vacancy_check && nilfs_near_disk_full(nilfs)) {
+ up_read(&nilfs->ns_segctor_sem);
+ ret = -ENOSPC;
+ goto failed;
+ }
+
+ trace_ti = current->journal_info;
+ trace_nilfs2_transaction_transition(sb, trace_ti, trace_ti->ti_count,
+ trace_ti->ti_flags,
+ TRACE_NILFS2_TRANSACTION_BEGIN);
+ return 0;
+
+ failed:
+ ti = current->journal_info;
+ current->journal_info = ti->ti_save;
+ if (ti->ti_flags & NILFS_TI_DYNAMIC_ALLOC)
+ kmem_cache_free(nilfs_transaction_cachep, ti);
+ sb_end_intwrite(sb);
+ return ret;
+}
+
+/**
+ * nilfs_transaction_commit - commit indivisible file operations.
+ * @sb: super block
+ *
+ * nilfs_transaction_commit() releases the read semaphore which is
+ * acquired by nilfs_transaction_begin(). This is only performed
+ * in outermost call of this function. If a commit flag is set,
+ * nilfs_transaction_commit() sets a timer to start the segment
+ * constructor. If a sync flag is set, it starts construction
+ * directly.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_transaction_commit(struct super_block *sb)
+{
+ struct nilfs_transaction_info *ti = current->journal_info;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ int err = 0;
+
+ BUG_ON(ti == NULL || ti->ti_magic != NILFS_TI_MAGIC);
+ ti->ti_flags |= NILFS_TI_COMMIT;
+ if (ti->ti_count > 0) {
+ ti->ti_count--;
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_COMMIT);
+ return 0;
+ }
+ if (nilfs->ns_writer) {
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+
+ if (ti->ti_flags & NILFS_TI_COMMIT)
+ nilfs_segctor_start_timer(sci);
+ if (atomic_read(&nilfs->ns_ndirtyblks) > sci->sc_watermark)
+ nilfs_segctor_do_flush(sci, 0);
+ }
+ up_read(&nilfs->ns_segctor_sem);
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_COMMIT);
+
+ current->journal_info = ti->ti_save;
+
+ if (ti->ti_flags & NILFS_TI_SYNC)
+ err = nilfs_construct_segment(sb);
+ if (ti->ti_flags & NILFS_TI_DYNAMIC_ALLOC)
+ kmem_cache_free(nilfs_transaction_cachep, ti);
+ sb_end_intwrite(sb);
+ return err;
+}
+
+void nilfs_transaction_abort(struct super_block *sb)
+{
+ struct nilfs_transaction_info *ti = current->journal_info;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+
+ BUG_ON(ti == NULL || ti->ti_magic != NILFS_TI_MAGIC);
+ if (ti->ti_count > 0) {
+ ti->ti_count--;
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_ABORT);
+ return;
+ }
+ up_read(&nilfs->ns_segctor_sem);
+
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_ABORT);
+
+ current->journal_info = ti->ti_save;
+ if (ti->ti_flags & NILFS_TI_DYNAMIC_ALLOC)
+ kmem_cache_free(nilfs_transaction_cachep, ti);
+ sb_end_intwrite(sb);
+}
+
+void nilfs_relax_pressure_in_lock(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+
+ if (sb_rdonly(sb) || unlikely(!sci) || !sci->sc_flush_request)
+ return;
+
+ set_bit(NILFS_SC_PRIOR_FLUSH, &sci->sc_flags);
+ up_read(&nilfs->ns_segctor_sem);
+
+ down_write(&nilfs->ns_segctor_sem);
+ if (sci->sc_flush_request &&
+ test_bit(NILFS_SC_PRIOR_FLUSH, &sci->sc_flags)) {
+ struct nilfs_transaction_info *ti = current->journal_info;
+
+ ti->ti_flags |= NILFS_TI_WRITER;
+ nilfs_segctor_do_immediate_flush(sci);
+ ti->ti_flags &= ~NILFS_TI_WRITER;
+ }
+ downgrade_write(&nilfs->ns_segctor_sem);
+}
+
+static void nilfs_transaction_lock(struct super_block *sb,
+ struct nilfs_transaction_info *ti,
+ int gcflag)
+{
+ struct nilfs_transaction_info *cur_ti = current->journal_info;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+
+ WARN_ON(cur_ti);
+ ti->ti_flags = NILFS_TI_WRITER;
+ ti->ti_count = 0;
+ ti->ti_save = cur_ti;
+ ti->ti_magic = NILFS_TI_MAGIC;
+ current->journal_info = ti;
+
+ for (;;) {
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_TRYLOCK);
+
+ down_write(&nilfs->ns_segctor_sem);
+ if (!test_bit(NILFS_SC_PRIOR_FLUSH, &sci->sc_flags))
+ break;
+
+ nilfs_segctor_do_immediate_flush(sci);
+
+ up_write(&nilfs->ns_segctor_sem);
+ cond_resched();
+ }
+ if (gcflag)
+ ti->ti_flags |= NILFS_TI_GC;
+
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_LOCK);
+}
+
+static void nilfs_transaction_unlock(struct super_block *sb)
+{
+ struct nilfs_transaction_info *ti = current->journal_info;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+
+ BUG_ON(ti == NULL || ti->ti_magic != NILFS_TI_MAGIC);
+ BUG_ON(ti->ti_count > 0);
+
+ up_write(&nilfs->ns_segctor_sem);
+ current->journal_info = ti->ti_save;
+
+ trace_nilfs2_transaction_transition(sb, ti, ti->ti_count,
+ ti->ti_flags, TRACE_NILFS2_TRANSACTION_UNLOCK);
+}
+
+static void *nilfs_segctor_map_segsum_entry(struct nilfs_sc_info *sci,
+ struct nilfs_segsum_pointer *ssp,
+ unsigned int bytes)
+{
+ struct nilfs_segment_buffer *segbuf = sci->sc_curseg;
+ unsigned int blocksize = sci->sc_super->s_blocksize;
+ void *p;
+
+ if (unlikely(ssp->offset + bytes > blocksize)) {
+ ssp->offset = 0;
+ BUG_ON(NILFS_SEGBUF_BH_IS_LAST(ssp->bh,
+ &segbuf->sb_segsum_buffers));
+ ssp->bh = NILFS_SEGBUF_NEXT_BH(ssp->bh);
+ }
+ p = ssp->bh->b_data + ssp->offset;
+ ssp->offset += bytes;
+ return p;
+}
+
+/**
+ * nilfs_segctor_reset_segment_buffer - reset the current segment buffer
+ * @sci: nilfs_sc_info
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_segctor_reset_segment_buffer(struct nilfs_sc_info *sci)
+{
+ struct nilfs_segment_buffer *segbuf = sci->sc_curseg;
+ struct buffer_head *sumbh;
+ unsigned int sumbytes;
+ unsigned int flags = 0;
+ int err;
+
+ if (nilfs_doing_gc())
+ flags = NILFS_SS_GC;
+ err = nilfs_segbuf_reset(segbuf, flags, sci->sc_seg_ctime, sci->sc_cno);
+ if (unlikely(err))
+ return err;
+
+ sumbh = NILFS_SEGBUF_FIRST_BH(&segbuf->sb_segsum_buffers);
+ sumbytes = segbuf->sb_sum.sumbytes;
+ sci->sc_finfo_ptr.bh = sumbh; sci->sc_finfo_ptr.offset = sumbytes;
+ sci->sc_binfo_ptr.bh = sumbh; sci->sc_binfo_ptr.offset = sumbytes;
+ sci->sc_blk_cnt = sci->sc_datablk_cnt = 0;
+ return 0;
+}
+
+/**
+ * nilfs_segctor_zeropad_segsum - zero pad the rest of the segment summary area
+ * @sci: segment constructor object
+ *
+ * nilfs_segctor_zeropad_segsum() zero-fills unallocated space at the end of
+ * the current segment summary block.
+ */
+static void nilfs_segctor_zeropad_segsum(struct nilfs_sc_info *sci)
+{
+ struct nilfs_segsum_pointer *ssp;
+
+ ssp = sci->sc_blk_cnt > 0 ? &sci->sc_binfo_ptr : &sci->sc_finfo_ptr;
+ if (ssp->offset < ssp->bh->b_size)
+ memset(ssp->bh->b_data + ssp->offset, 0,
+ ssp->bh->b_size - ssp->offset);
+}
+
+static int nilfs_segctor_feed_segment(struct nilfs_sc_info *sci)
+{
+ sci->sc_nblk_this_inc += sci->sc_curseg->sb_sum.nblocks;
+ if (NILFS_SEGBUF_IS_LAST(sci->sc_curseg, &sci->sc_segbufs))
+ return -E2BIG; /*
+ * The current segment is filled up
+ * (internal code)
+ */
+ nilfs_segctor_zeropad_segsum(sci);
+ sci->sc_curseg = NILFS_NEXT_SEGBUF(sci->sc_curseg);
+ return nilfs_segctor_reset_segment_buffer(sci);
+}
+
+static int nilfs_segctor_add_super_root(struct nilfs_sc_info *sci)
+{
+ struct nilfs_segment_buffer *segbuf = sci->sc_curseg;
+ int err;
+
+ if (segbuf->sb_sum.nblocks >= segbuf->sb_rest_blocks) {
+ err = nilfs_segctor_feed_segment(sci);
+ if (err)
+ return err;
+ segbuf = sci->sc_curseg;
+ }
+ err = nilfs_segbuf_extend_payload(segbuf, &segbuf->sb_super_root);
+ if (likely(!err))
+ segbuf->sb_sum.flags |= NILFS_SS_SR;
+ return err;
+}
+
+/*
+ * Functions for making segment summary and payloads
+ */
+static int nilfs_segctor_segsum_block_required(
+ struct nilfs_sc_info *sci, const struct nilfs_segsum_pointer *ssp,
+ unsigned int binfo_size)
+{
+ unsigned int blocksize = sci->sc_super->s_blocksize;
+ /* Size of finfo and binfo is enough small against blocksize */
+
+ return ssp->offset + binfo_size +
+ (!sci->sc_blk_cnt ? sizeof(struct nilfs_finfo) : 0) >
+ blocksize;
+}
+
+static void nilfs_segctor_begin_finfo(struct nilfs_sc_info *sci,
+ struct inode *inode)
+{
+ sci->sc_curseg->sb_sum.nfinfo++;
+ sci->sc_binfo_ptr = sci->sc_finfo_ptr;
+ nilfs_segctor_map_segsum_entry(
+ sci, &sci->sc_binfo_ptr, sizeof(struct nilfs_finfo));
+
+ if (NILFS_I(inode)->i_root &&
+ !test_bit(NILFS_SC_HAVE_DELTA, &sci->sc_flags))
+ set_bit(NILFS_SC_HAVE_DELTA, &sci->sc_flags);
+ /* skip finfo */
+}
+
+static void nilfs_segctor_end_finfo(struct nilfs_sc_info *sci,
+ struct inode *inode)
+{
+ struct nilfs_finfo *finfo;
+ struct nilfs_inode_info *ii;
+ struct nilfs_segment_buffer *segbuf;
+ __u64 cno;
+
+ if (sci->sc_blk_cnt == 0)
+ return;
+
+ ii = NILFS_I(inode);
+
+ if (ii->i_type & NILFS_I_TYPE_GC)
+ cno = ii->i_cno;
+ else if (NILFS_ROOT_METADATA_FILE(inode->i_ino))
+ cno = 0;
+ else
+ cno = sci->sc_cno;
+
+ finfo = nilfs_segctor_map_segsum_entry(sci, &sci->sc_finfo_ptr,
+ sizeof(*finfo));
+ finfo->fi_ino = cpu_to_le64(inode->i_ino);
+ finfo->fi_nblocks = cpu_to_le32(sci->sc_blk_cnt);
+ finfo->fi_ndatablk = cpu_to_le32(sci->sc_datablk_cnt);
+ finfo->fi_cno = cpu_to_le64(cno);
+
+ segbuf = sci->sc_curseg;
+ segbuf->sb_sum.sumbytes = sci->sc_binfo_ptr.offset +
+ sci->sc_super->s_blocksize * (segbuf->sb_sum.nsumblk - 1);
+ sci->sc_finfo_ptr = sci->sc_binfo_ptr;
+ sci->sc_blk_cnt = sci->sc_datablk_cnt = 0;
+}
+
+static int nilfs_segctor_add_file_block(struct nilfs_sc_info *sci,
+ struct buffer_head *bh,
+ struct inode *inode,
+ unsigned int binfo_size)
+{
+ struct nilfs_segment_buffer *segbuf;
+ int required, err = 0;
+
+ retry:
+ segbuf = sci->sc_curseg;
+ required = nilfs_segctor_segsum_block_required(
+ sci, &sci->sc_binfo_ptr, binfo_size);
+ if (segbuf->sb_sum.nblocks + required + 1 > segbuf->sb_rest_blocks) {
+ nilfs_segctor_end_finfo(sci, inode);
+ err = nilfs_segctor_feed_segment(sci);
+ if (err)
+ return err;
+ goto retry;
+ }
+ if (unlikely(required)) {
+ nilfs_segctor_zeropad_segsum(sci);
+ err = nilfs_segbuf_extend_segsum(segbuf);
+ if (unlikely(err))
+ goto failed;
+ }
+ if (sci->sc_blk_cnt == 0)
+ nilfs_segctor_begin_finfo(sci, inode);
+
+ nilfs_segctor_map_segsum_entry(sci, &sci->sc_binfo_ptr, binfo_size);
+ /* Substitution to vblocknr is delayed until update_blocknr() */
+ nilfs_segbuf_add_file_buffer(segbuf, bh);
+ sci->sc_blk_cnt++;
+ failed:
+ return err;
+}
+
+/*
+ * Callback functions that enumerate, mark, and collect dirty blocks
+ */
+static int nilfs_collect_file_data(struct nilfs_sc_info *sci,
+ struct buffer_head *bh, struct inode *inode)
+{
+ int err;
+
+ err = nilfs_bmap_propagate(NILFS_I(inode)->i_bmap, bh);
+ if (err < 0)
+ return err;
+
+ err = nilfs_segctor_add_file_block(sci, bh, inode,
+ sizeof(struct nilfs_binfo_v));
+ if (!err)
+ sci->sc_datablk_cnt++;
+ return err;
+}
+
+static int nilfs_collect_file_node(struct nilfs_sc_info *sci,
+ struct buffer_head *bh,
+ struct inode *inode)
+{
+ return nilfs_bmap_propagate(NILFS_I(inode)->i_bmap, bh);
+}
+
+static int nilfs_collect_file_bmap(struct nilfs_sc_info *sci,
+ struct buffer_head *bh,
+ struct inode *inode)
+{
+ WARN_ON(!buffer_dirty(bh));
+ return nilfs_segctor_add_file_block(sci, bh, inode, sizeof(__le64));
+}
+
+static void nilfs_write_file_data_binfo(struct nilfs_sc_info *sci,
+ struct nilfs_segsum_pointer *ssp,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_binfo_v *binfo_v = nilfs_segctor_map_segsum_entry(
+ sci, ssp, sizeof(*binfo_v));
+ *binfo_v = binfo->bi_v;
+}
+
+static void nilfs_write_file_node_binfo(struct nilfs_sc_info *sci,
+ struct nilfs_segsum_pointer *ssp,
+ union nilfs_binfo *binfo)
+{
+ __le64 *vblocknr = nilfs_segctor_map_segsum_entry(
+ sci, ssp, sizeof(*vblocknr));
+ *vblocknr = binfo->bi_v.bi_vblocknr;
+}
+
+static const struct nilfs_sc_operations nilfs_sc_file_ops = {
+ .collect_data = nilfs_collect_file_data,
+ .collect_node = nilfs_collect_file_node,
+ .collect_bmap = nilfs_collect_file_bmap,
+ .write_data_binfo = nilfs_write_file_data_binfo,
+ .write_node_binfo = nilfs_write_file_node_binfo,
+};
+
+static int nilfs_collect_dat_data(struct nilfs_sc_info *sci,
+ struct buffer_head *bh, struct inode *inode)
+{
+ int err;
+
+ err = nilfs_bmap_propagate(NILFS_I(inode)->i_bmap, bh);
+ if (err < 0)
+ return err;
+
+ err = nilfs_segctor_add_file_block(sci, bh, inode, sizeof(__le64));
+ if (!err)
+ sci->sc_datablk_cnt++;
+ return err;
+}
+
+static int nilfs_collect_dat_bmap(struct nilfs_sc_info *sci,
+ struct buffer_head *bh, struct inode *inode)
+{
+ WARN_ON(!buffer_dirty(bh));
+ return nilfs_segctor_add_file_block(sci, bh, inode,
+ sizeof(struct nilfs_binfo_dat));
+}
+
+static void nilfs_write_dat_data_binfo(struct nilfs_sc_info *sci,
+ struct nilfs_segsum_pointer *ssp,
+ union nilfs_binfo *binfo)
+{
+ __le64 *blkoff = nilfs_segctor_map_segsum_entry(sci, ssp,
+ sizeof(*blkoff));
+ *blkoff = binfo->bi_dat.bi_blkoff;
+}
+
+static void nilfs_write_dat_node_binfo(struct nilfs_sc_info *sci,
+ struct nilfs_segsum_pointer *ssp,
+ union nilfs_binfo *binfo)
+{
+ struct nilfs_binfo_dat *binfo_dat =
+ nilfs_segctor_map_segsum_entry(sci, ssp, sizeof(*binfo_dat));
+ *binfo_dat = binfo->bi_dat;
+}
+
+static const struct nilfs_sc_operations nilfs_sc_dat_ops = {
+ .collect_data = nilfs_collect_dat_data,
+ .collect_node = nilfs_collect_file_node,
+ .collect_bmap = nilfs_collect_dat_bmap,
+ .write_data_binfo = nilfs_write_dat_data_binfo,
+ .write_node_binfo = nilfs_write_dat_node_binfo,
+};
+
+static const struct nilfs_sc_operations nilfs_sc_dsync_ops = {
+ .collect_data = nilfs_collect_file_data,
+ .collect_node = NULL,
+ .collect_bmap = NULL,
+ .write_data_binfo = nilfs_write_file_data_binfo,
+ .write_node_binfo = NULL,
+};
+
+static size_t nilfs_lookup_dirty_data_buffers(struct inode *inode,
+ struct list_head *listp,
+ size_t nlimit,
+ loff_t start, loff_t end)
+{
+ struct address_space *mapping = inode->i_mapping;
+ struct folio_batch fbatch;
+ pgoff_t index = 0, last = ULONG_MAX;
+ size_t ndirties = 0;
+ int i;
+
+ if (unlikely(start != 0 || end != LLONG_MAX)) {
+ /*
+ * A valid range is given for sync-ing data pages. The
+ * range is rounded to per-page; extra dirty buffers
+ * may be included if blocksize < pagesize.
+ */
+ index = start >> PAGE_SHIFT;
+ last = end >> PAGE_SHIFT;
+ }
+ folio_batch_init(&fbatch);
+ repeat:
+ if (unlikely(index > last) ||
+ !filemap_get_folios_tag(mapping, &index, last,
+ PAGECACHE_TAG_DIRTY, &fbatch))
+ return ndirties;
+
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ struct buffer_head *bh, *head;
+ struct folio *folio = fbatch.folios[i];
+
+ folio_lock(folio);
+ if (unlikely(folio->mapping != mapping)) {
+ /* Exclude folios removed from the address space */
+ folio_unlock(folio);
+ continue;
+ }
+ head = folio_buffers(folio);
+ if (!head)
+ head = create_empty_buffers(folio,
+ i_blocksize(inode), 0);
+
+ bh = head;
+ do {
+ if (!buffer_dirty(bh) || buffer_async_write(bh))
+ continue;
+ get_bh(bh);
+ list_add_tail(&bh->b_assoc_buffers, listp);
+ ndirties++;
+ if (unlikely(ndirties >= nlimit)) {
+ folio_unlock(folio);
+ folio_batch_release(&fbatch);
+ cond_resched();
+ return ndirties;
+ }
+ } while (bh = bh->b_this_page, bh != head);
+
+ folio_unlock(folio);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+ goto repeat;
+}
+
+static void nilfs_lookup_dirty_node_buffers(struct inode *inode,
+ struct list_head *listp)
+{
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ struct inode *btnc_inode = ii->i_assoc_inode;
+ struct folio_batch fbatch;
+ struct buffer_head *bh, *head;
+ unsigned int i;
+ pgoff_t index = 0;
+
+ if (!btnc_inode)
+ return;
+ folio_batch_init(&fbatch);
+
+ while (filemap_get_folios_tag(btnc_inode->i_mapping, &index,
+ (pgoff_t)-1, PAGECACHE_TAG_DIRTY, &fbatch)) {
+ for (i = 0; i < folio_batch_count(&fbatch); i++) {
+ bh = head = folio_buffers(fbatch.folios[i]);
+ do {
+ if (buffer_dirty(bh) &&
+ !buffer_async_write(bh)) {
+ get_bh(bh);
+ list_add_tail(&bh->b_assoc_buffers,
+ listp);
+ }
+ bh = bh->b_this_page;
+ } while (bh != head);
+ }
+ folio_batch_release(&fbatch);
+ cond_resched();
+ }
+}
+
+static void nilfs_dispose_list(struct the_nilfs *nilfs,
+ struct list_head *head, int force)
+{
+ struct nilfs_inode_info *ii, *n;
+ struct nilfs_inode_info *ivec[SC_N_INODEVEC], **pii;
+ unsigned int nv = 0;
+
+ while (!list_empty(head)) {
+ spin_lock(&nilfs->ns_inode_lock);
+ list_for_each_entry_safe(ii, n, head, i_dirty) {
+ list_del_init(&ii->i_dirty);
+ if (force) {
+ if (unlikely(ii->i_bh)) {
+ brelse(ii->i_bh);
+ ii->i_bh = NULL;
+ }
+ } else if (test_bit(NILFS_I_DIRTY, &ii->i_state)) {
+ set_bit(NILFS_I_QUEUED, &ii->i_state);
+ list_add_tail(&ii->i_dirty,
+ &nilfs->ns_dirty_files);
+ continue;
+ }
+ ivec[nv++] = ii;
+ if (nv == SC_N_INODEVEC)
+ break;
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+
+ for (pii = ivec; nv > 0; pii++, nv--)
+ iput(&(*pii)->vfs_inode);
+ }
+}
+
+static void nilfs_iput_work_func(struct work_struct *work)
+{
+ struct nilfs_sc_info *sci = container_of(work, struct nilfs_sc_info,
+ sc_iput_work);
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+
+ nilfs_dispose_list(nilfs, &sci->sc_iput_queue, 0);
+}
+
+static int nilfs_test_metadata_dirty(struct the_nilfs *nilfs,
+ struct nilfs_root *root)
+{
+ int ret = 0;
+
+ if (nilfs_mdt_fetch_dirty(root->ifile))
+ ret++;
+ if (nilfs_mdt_fetch_dirty(nilfs->ns_cpfile))
+ ret++;
+ if (nilfs_mdt_fetch_dirty(nilfs->ns_sufile))
+ ret++;
+ if ((ret || nilfs_doing_gc()) && nilfs_mdt_fetch_dirty(nilfs->ns_dat))
+ ret++;
+ return ret;
+}
+
+static int nilfs_segctor_clean(struct nilfs_sc_info *sci)
+{
+ return list_empty(&sci->sc_dirty_files) &&
+ !test_bit(NILFS_SC_DIRTY, &sci->sc_flags) &&
+ sci->sc_nfreesegs == 0 &&
+ (!nilfs_doing_gc() || list_empty(&sci->sc_gc_inodes));
+}
+
+static int nilfs_segctor_confirm(struct nilfs_sc_info *sci)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ int ret = 0;
+
+ if (nilfs_test_metadata_dirty(nilfs, sci->sc_root))
+ set_bit(NILFS_SC_DIRTY, &sci->sc_flags);
+
+ spin_lock(&nilfs->ns_inode_lock);
+ if (list_empty(&nilfs->ns_dirty_files) && nilfs_segctor_clean(sci))
+ ret++;
+
+ spin_unlock(&nilfs->ns_inode_lock);
+ return ret;
+}
+
+static void nilfs_segctor_clear_metadata_dirty(struct nilfs_sc_info *sci)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+
+ nilfs_mdt_clear_dirty(sci->sc_root->ifile);
+ nilfs_mdt_clear_dirty(nilfs->ns_cpfile);
+ nilfs_mdt_clear_dirty(nilfs->ns_sufile);
+ nilfs_mdt_clear_dirty(nilfs->ns_dat);
+}
+
+static void nilfs_fill_in_file_bmap(struct inode *ifile,
+ struct nilfs_inode_info *ii)
+
+{
+ struct buffer_head *ibh;
+ struct nilfs_inode *raw_inode;
+
+ if (test_bit(NILFS_I_BMAP, &ii->i_state)) {
+ ibh = ii->i_bh;
+ BUG_ON(!ibh);
+ raw_inode = nilfs_ifile_map_inode(ifile, ii->vfs_inode.i_ino,
+ ibh);
+ nilfs_bmap_write(ii->i_bmap, raw_inode);
+ nilfs_ifile_unmap_inode(raw_inode);
+ }
+}
+
+static void nilfs_segctor_fill_in_file_bmap(struct nilfs_sc_info *sci)
+{
+ struct nilfs_inode_info *ii;
+
+ list_for_each_entry(ii, &sci->sc_dirty_files, i_dirty) {
+ nilfs_fill_in_file_bmap(sci->sc_root->ifile, ii);
+ set_bit(NILFS_I_COLLECTED, &ii->i_state);
+ }
+}
+
+/**
+ * nilfs_write_root_mdt_inode - export root metadata inode information to
+ * the on-disk inode
+ * @inode: inode object of the root metadata file
+ * @raw_inode: on-disk inode
+ *
+ * nilfs_write_root_mdt_inode() writes inode information and bmap data of
+ * @inode to the inode area of the metadata file allocated on the super root
+ * block created to finalize the log. Since super root blocks are configured
+ * each time, this function zero-fills the unused area of @raw_inode.
+ */
+static void nilfs_write_root_mdt_inode(struct inode *inode,
+ struct nilfs_inode *raw_inode)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+
+ nilfs_write_inode_common(inode, raw_inode);
+
+ /* zero-fill unused portion of raw_inode */
+ raw_inode->i_xattr = 0;
+ raw_inode->i_pad = 0;
+ memset((void *)raw_inode + sizeof(*raw_inode), 0,
+ nilfs->ns_inode_size - sizeof(*raw_inode));
+
+ nilfs_bmap_write(NILFS_I(inode)->i_bmap, raw_inode);
+}
+
+static void nilfs_segctor_fill_in_super_root(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs)
+{
+ struct buffer_head *bh_sr;
+ struct nilfs_super_root *raw_sr;
+ unsigned int isz, srsz;
+
+ bh_sr = NILFS_LAST_SEGBUF(&sci->sc_segbufs)->sb_super_root;
+
+ lock_buffer(bh_sr);
+ raw_sr = (struct nilfs_super_root *)bh_sr->b_data;
+ isz = nilfs->ns_inode_size;
+ srsz = NILFS_SR_BYTES(isz);
+
+ raw_sr->sr_sum = 0; /* Ensure initialization within this update */
+ raw_sr->sr_bytes = cpu_to_le16(srsz);
+ raw_sr->sr_nongc_ctime
+ = cpu_to_le64(nilfs_doing_gc() ?
+ nilfs->ns_nongc_ctime : sci->sc_seg_ctime);
+ raw_sr->sr_flags = 0;
+
+ nilfs_write_root_mdt_inode(nilfs->ns_dat, (void *)raw_sr +
+ NILFS_SR_DAT_OFFSET(isz));
+ nilfs_write_root_mdt_inode(nilfs->ns_cpfile, (void *)raw_sr +
+ NILFS_SR_CPFILE_OFFSET(isz));
+ nilfs_write_root_mdt_inode(nilfs->ns_sufile, (void *)raw_sr +
+ NILFS_SR_SUFILE_OFFSET(isz));
+
+ memset((void *)raw_sr + srsz, 0, nilfs->ns_blocksize - srsz);
+ set_buffer_uptodate(bh_sr);
+ unlock_buffer(bh_sr);
+}
+
+static void nilfs_redirty_inodes(struct list_head *head)
+{
+ struct nilfs_inode_info *ii;
+
+ list_for_each_entry(ii, head, i_dirty) {
+ if (test_bit(NILFS_I_COLLECTED, &ii->i_state))
+ clear_bit(NILFS_I_COLLECTED, &ii->i_state);
+ }
+}
+
+static void nilfs_drop_collected_inodes(struct list_head *head)
+{
+ struct nilfs_inode_info *ii;
+
+ list_for_each_entry(ii, head, i_dirty) {
+ if (!test_and_clear_bit(NILFS_I_COLLECTED, &ii->i_state))
+ continue;
+
+ clear_bit(NILFS_I_INODE_SYNC, &ii->i_state);
+ set_bit(NILFS_I_UPDATED, &ii->i_state);
+ }
+}
+
+static int nilfs_segctor_apply_buffers(struct nilfs_sc_info *sci,
+ struct inode *inode,
+ struct list_head *listp,
+ int (*collect)(struct nilfs_sc_info *,
+ struct buffer_head *,
+ struct inode *))
+{
+ struct buffer_head *bh, *n;
+ int err = 0;
+
+ if (collect) {
+ list_for_each_entry_safe(bh, n, listp, b_assoc_buffers) {
+ list_del_init(&bh->b_assoc_buffers);
+ err = collect(sci, bh, inode);
+ brelse(bh);
+ if (unlikely(err))
+ goto dispose_buffers;
+ }
+ return 0;
+ }
+
+ dispose_buffers:
+ while (!list_empty(listp)) {
+ bh = list_first_entry(listp, struct buffer_head,
+ b_assoc_buffers);
+ list_del_init(&bh->b_assoc_buffers);
+ brelse(bh);
+ }
+ return err;
+}
+
+static size_t nilfs_segctor_buffer_rest(struct nilfs_sc_info *sci)
+{
+ /* Remaining number of blocks within segment buffer */
+ return sci->sc_segbuf_nblocks -
+ (sci->sc_nblk_this_inc + sci->sc_curseg->sb_sum.nblocks);
+}
+
+static int nilfs_segctor_scan_file(struct nilfs_sc_info *sci,
+ struct inode *inode,
+ const struct nilfs_sc_operations *sc_ops)
+{
+ LIST_HEAD(data_buffers);
+ LIST_HEAD(node_buffers);
+ int err;
+
+ if (!(sci->sc_stage.flags & NILFS_CF_NODE)) {
+ size_t n, rest = nilfs_segctor_buffer_rest(sci);
+
+ n = nilfs_lookup_dirty_data_buffers(
+ inode, &data_buffers, rest + 1, 0, LLONG_MAX);
+ if (n > rest) {
+ err = nilfs_segctor_apply_buffers(
+ sci, inode, &data_buffers,
+ sc_ops->collect_data);
+ BUG_ON(!err); /* always receive -E2BIG or true error */
+ goto break_or_fail;
+ }
+ }
+ nilfs_lookup_dirty_node_buffers(inode, &node_buffers);
+
+ if (!(sci->sc_stage.flags & NILFS_CF_NODE)) {
+ err = nilfs_segctor_apply_buffers(
+ sci, inode, &data_buffers, sc_ops->collect_data);
+ if (unlikely(err)) {
+ /* dispose node list */
+ nilfs_segctor_apply_buffers(
+ sci, inode, &node_buffers, NULL);
+ goto break_or_fail;
+ }
+ sci->sc_stage.flags |= NILFS_CF_NODE;
+ }
+ /* Collect node */
+ err = nilfs_segctor_apply_buffers(
+ sci, inode, &node_buffers, sc_ops->collect_node);
+ if (unlikely(err))
+ goto break_or_fail;
+
+ nilfs_bmap_lookup_dirty_buffers(NILFS_I(inode)->i_bmap, &node_buffers);
+ err = nilfs_segctor_apply_buffers(
+ sci, inode, &node_buffers, sc_ops->collect_bmap);
+ if (unlikely(err))
+ goto break_or_fail;
+
+ nilfs_segctor_end_finfo(sci, inode);
+ sci->sc_stage.flags &= ~NILFS_CF_NODE;
+
+ break_or_fail:
+ return err;
+}
+
+static int nilfs_segctor_scan_file_dsync(struct nilfs_sc_info *sci,
+ struct inode *inode)
+{
+ LIST_HEAD(data_buffers);
+ size_t n, rest = nilfs_segctor_buffer_rest(sci);
+ int err;
+
+ n = nilfs_lookup_dirty_data_buffers(inode, &data_buffers, rest + 1,
+ sci->sc_dsync_start,
+ sci->sc_dsync_end);
+
+ err = nilfs_segctor_apply_buffers(sci, inode, &data_buffers,
+ nilfs_collect_file_data);
+ if (!err) {
+ nilfs_segctor_end_finfo(sci, inode);
+ BUG_ON(n > rest);
+ /* always receive -E2BIG or true error if n > rest */
+ }
+ return err;
+}
+
+/**
+ * nilfs_free_segments - free the segments given by an array of segment numbers
+ * @nilfs: nilfs object
+ * @segnumv: array of segment numbers to be freed
+ * @nsegs: number of segments to be freed in @segnumv
+ *
+ * nilfs_free_segments() wraps nilfs_sufile_freev() and
+ * nilfs_sufile_cancel_freev(), and edits the segment usage metadata file
+ * (sufile) to free all segments given by @segnumv and @nsegs at once. If
+ * it fails midway, it cancels the changes so that none of the segments are
+ * freed. If @nsegs is 0, this function does nothing.
+ *
+ * The freeing of segments is not finalized until the writing of a log with
+ * a super root block containing this sufile change is complete, and it can
+ * be canceled with nilfs_sufile_cancel_freev() until then.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid segment number.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_free_segments(struct the_nilfs *nilfs, __u64 *segnumv,
+ size_t nsegs)
+{
+ size_t ndone;
+ int ret;
+
+ if (!nsegs)
+ return 0;
+
+ ret = nilfs_sufile_freev(nilfs->ns_sufile, segnumv, nsegs, &ndone);
+ if (unlikely(ret)) {
+ nilfs_sufile_cancel_freev(nilfs->ns_sufile, segnumv, ndone,
+ NULL);
+ /*
+ * If a segment usage of the segments to be freed is in a
+ * hole block, nilfs_sufile_freev() will return -ENOENT.
+ * In this case, -EINVAL should be returned to the caller
+ * since there is something wrong with the given segment
+ * number array. This error can only occur during GC, so
+ * there is no need to worry about it propagating to other
+ * callers (such as fsync).
+ */
+ if (ret == -ENOENT) {
+ nilfs_err(nilfs->ns_sb,
+ "The segment usage entry %llu to be freed is invalid (in a hole)",
+ (unsigned long long)segnumv[ndone]);
+ ret = -EINVAL;
+ }
+ }
+ return ret;
+}
+
+static int nilfs_segctor_collect_blocks(struct nilfs_sc_info *sci, int mode)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ struct list_head *head;
+ struct nilfs_inode_info *ii;
+ int err = 0;
+
+ switch (nilfs_sc_cstage_get(sci)) {
+ case NILFS_ST_INIT:
+ /* Pre-processes */
+ sci->sc_stage.flags = 0;
+
+ if (!test_bit(NILFS_SC_UNCLOSED, &sci->sc_flags)) {
+ sci->sc_nblk_inc = 0;
+ sci->sc_curseg->sb_sum.flags = NILFS_SS_LOGBGN;
+ if (mode == SC_LSEG_DSYNC) {
+ nilfs_sc_cstage_set(sci, NILFS_ST_DSYNC);
+ goto dsync_mode;
+ }
+ }
+
+ sci->sc_stage.dirty_file_ptr = NULL;
+ sci->sc_stage.gc_inode_ptr = NULL;
+ if (mode == SC_FLUSH_DAT) {
+ nilfs_sc_cstage_set(sci, NILFS_ST_DAT);
+ goto dat_stage;
+ }
+ nilfs_sc_cstage_inc(sci);
+ fallthrough;
+ case NILFS_ST_GC:
+ if (nilfs_doing_gc()) {
+ head = &sci->sc_gc_inodes;
+ ii = list_prepare_entry(sci->sc_stage.gc_inode_ptr,
+ head, i_dirty);
+ list_for_each_entry_continue(ii, head, i_dirty) {
+ err = nilfs_segctor_scan_file(
+ sci, &ii->vfs_inode,
+ &nilfs_sc_file_ops);
+ if (unlikely(err)) {
+ sci->sc_stage.gc_inode_ptr = list_entry(
+ ii->i_dirty.prev,
+ struct nilfs_inode_info,
+ i_dirty);
+ goto break_or_fail;
+ }
+ set_bit(NILFS_I_COLLECTED, &ii->i_state);
+ }
+ sci->sc_stage.gc_inode_ptr = NULL;
+ }
+ nilfs_sc_cstage_inc(sci);
+ fallthrough;
+ case NILFS_ST_FILE:
+ head = &sci->sc_dirty_files;
+ ii = list_prepare_entry(sci->sc_stage.dirty_file_ptr, head,
+ i_dirty);
+ list_for_each_entry_continue(ii, head, i_dirty) {
+ clear_bit(NILFS_I_DIRTY, &ii->i_state);
+
+ err = nilfs_segctor_scan_file(sci, &ii->vfs_inode,
+ &nilfs_sc_file_ops);
+ if (unlikely(err)) {
+ sci->sc_stage.dirty_file_ptr =
+ list_entry(ii->i_dirty.prev,
+ struct nilfs_inode_info,
+ i_dirty);
+ goto break_or_fail;
+ }
+ /* sci->sc_stage.dirty_file_ptr = NILFS_I(inode); */
+ /* XXX: required ? */
+ }
+ sci->sc_stage.dirty_file_ptr = NULL;
+ if (mode == SC_FLUSH_FILE) {
+ nilfs_sc_cstage_set(sci, NILFS_ST_DONE);
+ return 0;
+ }
+ nilfs_sc_cstage_inc(sci);
+ sci->sc_stage.flags |= NILFS_CF_IFILE_STARTED;
+ fallthrough;
+ case NILFS_ST_IFILE:
+ err = nilfs_segctor_scan_file(sci, sci->sc_root->ifile,
+ &nilfs_sc_file_ops);
+ if (unlikely(err))
+ break;
+ nilfs_sc_cstage_inc(sci);
+ /* Creating a checkpoint */
+ err = nilfs_cpfile_create_checkpoint(nilfs->ns_cpfile,
+ nilfs->ns_cno);
+ if (unlikely(err))
+ break;
+ fallthrough;
+ case NILFS_ST_CPFILE:
+ err = nilfs_segctor_scan_file(sci, nilfs->ns_cpfile,
+ &nilfs_sc_file_ops);
+ if (unlikely(err))
+ break;
+ nilfs_sc_cstage_inc(sci);
+ fallthrough;
+ case NILFS_ST_SUFILE:
+ err = nilfs_free_segments(nilfs, sci->sc_freesegs,
+ sci->sc_nfreesegs);
+ if (unlikely(err))
+ break;
+ sci->sc_stage.flags |= NILFS_CF_SUFREED;
+
+ err = nilfs_segctor_scan_file(sci, nilfs->ns_sufile,
+ &nilfs_sc_file_ops);
+ if (unlikely(err))
+ break;
+ nilfs_sc_cstage_inc(sci);
+ fallthrough;
+ case NILFS_ST_DAT:
+ dat_stage:
+ err = nilfs_segctor_scan_file(sci, nilfs->ns_dat,
+ &nilfs_sc_dat_ops);
+ if (unlikely(err))
+ break;
+ if (mode == SC_FLUSH_DAT) {
+ nilfs_sc_cstage_set(sci, NILFS_ST_DONE);
+ return 0;
+ }
+ nilfs_sc_cstage_inc(sci);
+ fallthrough;
+ case NILFS_ST_SR:
+ if (mode == SC_LSEG_SR) {
+ /* Appending a super root */
+ err = nilfs_segctor_add_super_root(sci);
+ if (unlikely(err))
+ break;
+ }
+ /* End of a logical segment */
+ sci->sc_curseg->sb_sum.flags |= NILFS_SS_LOGEND;
+ nilfs_sc_cstage_set(sci, NILFS_ST_DONE);
+ return 0;
+ case NILFS_ST_DSYNC:
+ dsync_mode:
+ sci->sc_curseg->sb_sum.flags |= NILFS_SS_SYNDT;
+ ii = sci->sc_dsync_inode;
+ if (!test_bit(NILFS_I_BUSY, &ii->i_state))
+ break;
+
+ err = nilfs_segctor_scan_file_dsync(sci, &ii->vfs_inode);
+ if (unlikely(err))
+ break;
+ sci->sc_curseg->sb_sum.flags |= NILFS_SS_LOGEND;
+ nilfs_sc_cstage_set(sci, NILFS_ST_DONE);
+ return 0;
+ case NILFS_ST_DONE:
+ return 0;
+ default:
+ BUG();
+ }
+
+ break_or_fail:
+ return err;
+}
+
+/**
+ * nilfs_segctor_begin_construction - setup segment buffer to make a new log
+ * @sci: nilfs_sc_info
+ * @nilfs: nilfs object
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_segctor_begin_construction(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs)
+{
+ struct nilfs_segment_buffer *segbuf, *prev;
+ __u64 nextnum;
+ int err, alloc = 0;
+
+ segbuf = nilfs_segbuf_new(sci->sc_super);
+ if (unlikely(!segbuf))
+ return -ENOMEM;
+
+ if (list_empty(&sci->sc_write_logs)) {
+ nilfs_segbuf_map(segbuf, nilfs->ns_segnum,
+ nilfs->ns_pseg_offset, nilfs);
+ if (segbuf->sb_rest_blocks < NILFS_PSEG_MIN_BLOCKS) {
+ nilfs_shift_to_next_segment(nilfs);
+ nilfs_segbuf_map(segbuf, nilfs->ns_segnum, 0, nilfs);
+ }
+
+ segbuf->sb_sum.seg_seq = nilfs->ns_seg_seq;
+ nextnum = nilfs->ns_nextnum;
+
+ if (nilfs->ns_segnum == nilfs->ns_nextnum)
+ /* Start from the head of a new full segment */
+ alloc++;
+ } else {
+ /* Continue logs */
+ prev = NILFS_LAST_SEGBUF(&sci->sc_write_logs);
+ nilfs_segbuf_map_cont(segbuf, prev);
+ segbuf->sb_sum.seg_seq = prev->sb_sum.seg_seq;
+ nextnum = prev->sb_nextnum;
+
+ if (segbuf->sb_rest_blocks < NILFS_PSEG_MIN_BLOCKS) {
+ nilfs_segbuf_map(segbuf, prev->sb_nextnum, 0, nilfs);
+ segbuf->sb_sum.seg_seq++;
+ alloc++;
+ }
+ }
+
+ err = nilfs_sufile_mark_dirty(nilfs->ns_sufile, segbuf->sb_segnum);
+ if (err)
+ goto failed;
+
+ if (alloc) {
+ err = nilfs_sufile_alloc(nilfs->ns_sufile, &nextnum);
+ if (err)
+ goto failed;
+ }
+ nilfs_segbuf_set_next_segnum(segbuf, nextnum, nilfs);
+
+ BUG_ON(!list_empty(&sci->sc_segbufs));
+ list_add_tail(&segbuf->sb_list, &sci->sc_segbufs);
+ sci->sc_segbuf_nblocks = segbuf->sb_rest_blocks;
+ return 0;
+
+ failed:
+ nilfs_segbuf_free(segbuf);
+ return err;
+}
+
+static int nilfs_segctor_extend_segments(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs, int nadd)
+{
+ struct nilfs_segment_buffer *segbuf, *prev;
+ struct inode *sufile = nilfs->ns_sufile;
+ __u64 nextnextnum;
+ LIST_HEAD(list);
+ int err, ret, i;
+
+ prev = NILFS_LAST_SEGBUF(&sci->sc_segbufs);
+ /*
+ * Since the segment specified with nextnum might be allocated during
+ * the previous construction, the buffer including its segusage may
+ * not be dirty. The following call ensures that the buffer is dirty
+ * and will pin the buffer on memory until the sufile is written.
+ */
+ err = nilfs_sufile_mark_dirty(sufile, prev->sb_nextnum);
+ if (unlikely(err))
+ return err;
+
+ for (i = 0; i < nadd; i++) {
+ /* extend segment info */
+ err = -ENOMEM;
+ segbuf = nilfs_segbuf_new(sci->sc_super);
+ if (unlikely(!segbuf))
+ goto failed;
+
+ /* map this buffer to region of segment on-disk */
+ nilfs_segbuf_map(segbuf, prev->sb_nextnum, 0, nilfs);
+ sci->sc_segbuf_nblocks += segbuf->sb_rest_blocks;
+
+ /* allocate the next next full segment */
+ err = nilfs_sufile_alloc(sufile, &nextnextnum);
+ if (unlikely(err))
+ goto failed_segbuf;
+
+ segbuf->sb_sum.seg_seq = prev->sb_sum.seg_seq + 1;
+ nilfs_segbuf_set_next_segnum(segbuf, nextnextnum, nilfs);
+
+ list_add_tail(&segbuf->sb_list, &list);
+ prev = segbuf;
+ }
+ list_splice_tail(&list, &sci->sc_segbufs);
+ return 0;
+
+ failed_segbuf:
+ nilfs_segbuf_free(segbuf);
+ failed:
+ list_for_each_entry(segbuf, &list, sb_list) {
+ ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum);
+ nilfs_sufile_warn_on_error(sufile, ret);
+ }
+ nilfs_destroy_logs(&list);
+ return err;
+}
+
+static void nilfs_free_incomplete_logs(struct list_head *logs,
+ struct the_nilfs *nilfs)
+{
+ struct nilfs_segment_buffer *segbuf, *prev;
+ struct inode *sufile = nilfs->ns_sufile;
+ int ret;
+
+ segbuf = NILFS_FIRST_SEGBUF(logs);
+ if (nilfs->ns_nextnum != segbuf->sb_nextnum) {
+ ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum);
+ nilfs_sufile_warn_on_error(sufile, ret);
+ }
+ if (atomic_read(&segbuf->sb_err)) {
+ /* Case 1: The first segment failed */
+ if (segbuf->sb_pseg_start != segbuf->sb_fseg_start)
+ /*
+ * Case 1a: Partial segment appended into an existing
+ * segment
+ */
+ nilfs_terminate_segment(nilfs, segbuf->sb_fseg_start,
+ segbuf->sb_fseg_end);
+ else /* Case 1b: New full segment */
+ set_nilfs_discontinued(nilfs);
+ }
+
+ prev = segbuf;
+ list_for_each_entry_continue(segbuf, logs, sb_list) {
+ if (prev->sb_nextnum != segbuf->sb_nextnum) {
+ ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum);
+ nilfs_sufile_warn_on_error(sufile, ret);
+ }
+ if (atomic_read(&segbuf->sb_err) &&
+ segbuf->sb_segnum != nilfs->ns_nextnum)
+ /* Case 2: extended segment (!= next) failed */
+ nilfs_sufile_set_error(sufile, segbuf->sb_segnum);
+ prev = segbuf;
+ }
+}
+
+static void nilfs_segctor_update_segusage(struct nilfs_sc_info *sci,
+ struct inode *sufile)
+{
+ struct nilfs_segment_buffer *segbuf;
+ unsigned long live_blocks;
+ int ret;
+
+ list_for_each_entry(segbuf, &sci->sc_segbufs, sb_list) {
+ live_blocks = segbuf->sb_sum.nblocks +
+ (segbuf->sb_pseg_start - segbuf->sb_fseg_start);
+ ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum,
+ live_blocks,
+ sci->sc_seg_ctime);
+ nilfs_sufile_warn_on_error(sufile, ret);
+ }
+}
+
+static void nilfs_cancel_segusage(struct list_head *logs, struct inode *sufile)
+{
+ struct nilfs_segment_buffer *segbuf;
+ int ret;
+
+ segbuf = NILFS_FIRST_SEGBUF(logs);
+ ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum,
+ segbuf->sb_pseg_start -
+ segbuf->sb_fseg_start, 0);
+ nilfs_sufile_warn_on_error(sufile, ret);
+
+ list_for_each_entry_continue(segbuf, logs, sb_list) {
+ ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum,
+ 0, 0);
+ nilfs_sufile_warn_on_error(sufile, ret);
+ }
+}
+
+static int nilfs_segctor_truncate_segments(struct nilfs_sc_info *sci,
+ struct nilfs_segment_buffer *last,
+ struct inode *sufile)
+{
+ struct nilfs_segment_buffer *segbuf = last;
+ int ret, err = 0;
+
+ list_for_each_entry_continue(segbuf, &sci->sc_segbufs, sb_list) {
+ sci->sc_segbuf_nblocks -= segbuf->sb_rest_blocks;
+
+ ret = nilfs_sufile_warn_on_error(
+ sufile, nilfs_sufile_free(sufile, segbuf->sb_nextnum));
+ if (unlikely(ret) && err != -EROFS)
+ err = ret;
+ }
+ nilfs_truncate_logs(&sci->sc_segbufs, last);
+ return err;
+}
+
+
+static int nilfs_segctor_collect(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs, int mode)
+{
+ struct nilfs_cstage prev_stage = sci->sc_stage;
+ int err, nadd = 1;
+
+ /* Collection retry loop */
+ for (;;) {
+ sci->sc_nblk_this_inc = 0;
+ sci->sc_curseg = NILFS_FIRST_SEGBUF(&sci->sc_segbufs);
+
+ err = nilfs_segctor_reset_segment_buffer(sci);
+ if (unlikely(err))
+ goto failed;
+
+ err = nilfs_segctor_collect_blocks(sci, mode);
+ sci->sc_nblk_this_inc += sci->sc_curseg->sb_sum.nblocks;
+ if (!err)
+ break;
+
+ if (unlikely(err != -E2BIG))
+ goto failed;
+
+ /* The current segment is filled up */
+ if (mode != SC_LSEG_SR ||
+ nilfs_sc_cstage_get(sci) < NILFS_ST_CPFILE)
+ break;
+
+ nilfs_clear_logs(&sci->sc_segbufs);
+
+ if (sci->sc_stage.flags & NILFS_CF_SUFREED) {
+ err = nilfs_sufile_cancel_freev(nilfs->ns_sufile,
+ sci->sc_freesegs,
+ sci->sc_nfreesegs,
+ NULL);
+ nilfs_sufile_warn_on_error(nilfs->ns_sufile, err);
+ sci->sc_stage.flags &= ~NILFS_CF_SUFREED;
+ }
+
+ err = nilfs_segctor_extend_segments(sci, nilfs, nadd);
+ if (unlikely(err))
+ return err;
+
+ nadd = min_t(int, nadd << 1, SC_MAX_SEGDELTA);
+ sci->sc_stage = prev_stage;
+ }
+ nilfs_segctor_zeropad_segsum(sci);
+ err = nilfs_segctor_truncate_segments(sci, sci->sc_curseg,
+ nilfs->ns_sufile);
+
+ failed:
+ return err;
+}
+
+static void nilfs_list_replace_buffer(struct buffer_head *old_bh,
+ struct buffer_head *new_bh)
+{
+ BUG_ON(!list_empty(&new_bh->b_assoc_buffers));
+
+ list_replace_init(&old_bh->b_assoc_buffers, &new_bh->b_assoc_buffers);
+ /* The caller must release old_bh */
+}
+
+static int
+nilfs_segctor_update_payload_blocknr(struct nilfs_sc_info *sci,
+ struct nilfs_segment_buffer *segbuf,
+ int mode)
+{
+ struct inode *inode = NULL;
+ sector_t blocknr;
+ unsigned long nfinfo = segbuf->sb_sum.nfinfo;
+ unsigned long nblocks = 0, ndatablk = 0;
+ const struct nilfs_sc_operations *sc_op = NULL;
+ struct nilfs_segsum_pointer ssp;
+ struct nilfs_finfo *finfo = NULL;
+ union nilfs_binfo binfo;
+ struct buffer_head *bh, *bh_org;
+ u64 ino = 0;
+ int err = 0;
+
+ if (!nfinfo)
+ goto out;
+
+ blocknr = segbuf->sb_pseg_start + segbuf->sb_sum.nsumblk;
+ ssp.bh = NILFS_SEGBUF_FIRST_BH(&segbuf->sb_segsum_buffers);
+ ssp.offset = sizeof(struct nilfs_segment_summary);
+
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers, b_assoc_buffers) {
+ if (bh == segbuf->sb_super_root)
+ break;
+ if (!finfo) {
+ finfo = nilfs_segctor_map_segsum_entry(
+ sci, &ssp, sizeof(*finfo));
+ ino = le64_to_cpu(finfo->fi_ino);
+ nblocks = le32_to_cpu(finfo->fi_nblocks);
+ ndatablk = le32_to_cpu(finfo->fi_ndatablk);
+
+ inode = bh->b_folio->mapping->host;
+
+ if (mode == SC_LSEG_DSYNC)
+ sc_op = &nilfs_sc_dsync_ops;
+ else if (ino == NILFS_DAT_INO)
+ sc_op = &nilfs_sc_dat_ops;
+ else /* file blocks */
+ sc_op = &nilfs_sc_file_ops;
+ }
+ bh_org = bh;
+ get_bh(bh_org);
+ err = nilfs_bmap_assign(NILFS_I(inode)->i_bmap, &bh, blocknr,
+ &binfo);
+ if (bh != bh_org)
+ nilfs_list_replace_buffer(bh_org, bh);
+ brelse(bh_org);
+ if (unlikely(err))
+ goto failed_bmap;
+
+ if (ndatablk > 0)
+ sc_op->write_data_binfo(sci, &ssp, &binfo);
+ else
+ sc_op->write_node_binfo(sci, &ssp, &binfo);
+
+ blocknr++;
+ if (--nblocks == 0) {
+ finfo = NULL;
+ if (--nfinfo == 0)
+ break;
+ } else if (ndatablk > 0)
+ ndatablk--;
+ }
+ out:
+ return 0;
+
+ failed_bmap:
+ return err;
+}
+
+static int nilfs_segctor_assign(struct nilfs_sc_info *sci, int mode)
+{
+ struct nilfs_segment_buffer *segbuf;
+ int err;
+
+ list_for_each_entry(segbuf, &sci->sc_segbufs, sb_list) {
+ err = nilfs_segctor_update_payload_blocknr(sci, segbuf, mode);
+ if (unlikely(err))
+ return err;
+ nilfs_segbuf_fill_in_segsum(segbuf);
+ }
+ return 0;
+}
+
+static void nilfs_begin_folio_io(struct folio *folio)
+{
+ if (!folio || folio_test_writeback(folio))
+ /*
+ * For split b-tree node pages, this function may be called
+ * twice. We ignore the 2nd or later calls by this check.
+ */
+ return;
+
+ folio_lock(folio);
+ folio_clear_dirty_for_io(folio);
+ folio_start_writeback(folio);
+ folio_unlock(folio);
+}
+
+/**
+ * nilfs_prepare_write_logs - prepare to write logs
+ * @logs: logs to prepare for writing
+ * @seed: checksum seed value
+ *
+ * nilfs_prepare_write_logs() adds checksums and prepares the block
+ * buffers/folios for writing logs. In order to stabilize folios of
+ * memory-mapped file blocks by putting them in writeback state before
+ * calculating the checksums, first prepare to write payload blocks other
+ * than segment summary and super root blocks in which the checksums will
+ * be embedded.
+ */
+static void nilfs_prepare_write_logs(struct list_head *logs, u32 seed)
+{
+ struct nilfs_segment_buffer *segbuf;
+ struct folio *bd_folio = NULL, *fs_folio = NULL;
+ struct buffer_head *bh;
+
+ /* Prepare to write payload blocks */
+ list_for_each_entry(segbuf, logs, sb_list) {
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers,
+ b_assoc_buffers) {
+ if (bh == segbuf->sb_super_root)
+ break;
+ set_buffer_async_write(bh);
+ if (bh->b_folio != fs_folio) {
+ nilfs_begin_folio_io(fs_folio);
+ fs_folio = bh->b_folio;
+ }
+ }
+ }
+ nilfs_begin_folio_io(fs_folio);
+
+ nilfs_add_checksums_on_logs(logs, seed);
+
+ /* Prepare to write segment summary blocks */
+ list_for_each_entry(segbuf, logs, sb_list) {
+ list_for_each_entry(bh, &segbuf->sb_segsum_buffers,
+ b_assoc_buffers) {
+ mark_buffer_dirty(bh);
+ if (bh->b_folio == bd_folio)
+ continue;
+ if (bd_folio) {
+ folio_lock(bd_folio);
+ folio_wait_writeback(bd_folio);
+ folio_clear_dirty_for_io(bd_folio);
+ folio_start_writeback(bd_folio);
+ folio_unlock(bd_folio);
+ }
+ bd_folio = bh->b_folio;
+ }
+ }
+
+ /* Prepare to write super root block */
+ bh = NILFS_LAST_SEGBUF(logs)->sb_super_root;
+ if (bh) {
+ mark_buffer_dirty(bh);
+ if (bh->b_folio != bd_folio) {
+ folio_lock(bd_folio);
+ folio_wait_writeback(bd_folio);
+ folio_clear_dirty_for_io(bd_folio);
+ folio_start_writeback(bd_folio);
+ folio_unlock(bd_folio);
+ bd_folio = bh->b_folio;
+ }
+ }
+
+ if (bd_folio) {
+ folio_lock(bd_folio);
+ folio_wait_writeback(bd_folio);
+ folio_clear_dirty_for_io(bd_folio);
+ folio_start_writeback(bd_folio);
+ folio_unlock(bd_folio);
+ }
+}
+
+static int nilfs_segctor_write(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs)
+{
+ int ret;
+
+ ret = nilfs_write_logs(&sci->sc_segbufs, nilfs);
+ list_splice_tail_init(&sci->sc_segbufs, &sci->sc_write_logs);
+ return ret;
+}
+
+static void nilfs_end_folio_io(struct folio *folio, int err)
+{
+ if (!folio)
+ return;
+
+ if (buffer_nilfs_node(folio_buffers(folio)) &&
+ !folio_test_writeback(folio)) {
+ /*
+ * For b-tree node pages, this function may be called twice
+ * or more because they might be split in a segment.
+ */
+ if (folio_test_dirty(folio)) {
+ /*
+ * For pages holding split b-tree node buffers, dirty
+ * flag on the buffers may be cleared discretely.
+ * In that case, the page is once redirtied for
+ * remaining buffers, and it must be cancelled if
+ * all the buffers get cleaned later.
+ */
+ folio_lock(folio);
+ if (nilfs_folio_buffers_clean(folio))
+ __nilfs_clear_folio_dirty(folio);
+ folio_unlock(folio);
+ }
+ return;
+ }
+
+ if (err || !nilfs_folio_buffers_clean(folio))
+ filemap_dirty_folio(folio->mapping, folio);
+
+ folio_end_writeback(folio);
+}
+
+static void nilfs_abort_logs(struct list_head *logs, int err)
+{
+ struct nilfs_segment_buffer *segbuf;
+ struct folio *bd_folio = NULL, *fs_folio = NULL;
+ struct buffer_head *bh;
+
+ if (list_empty(logs))
+ return;
+
+ list_for_each_entry(segbuf, logs, sb_list) {
+ list_for_each_entry(bh, &segbuf->sb_segsum_buffers,
+ b_assoc_buffers) {
+ clear_buffer_uptodate(bh);
+ if (bh->b_folio != bd_folio) {
+ if (bd_folio)
+ folio_end_writeback(bd_folio);
+ bd_folio = bh->b_folio;
+ }
+ }
+
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers,
+ b_assoc_buffers) {
+ if (bh == segbuf->sb_super_root) {
+ clear_buffer_uptodate(bh);
+ if (bh->b_folio != bd_folio) {
+ folio_end_writeback(bd_folio);
+ bd_folio = bh->b_folio;
+ }
+ break;
+ }
+ clear_buffer_async_write(bh);
+ if (bh->b_folio != fs_folio) {
+ nilfs_end_folio_io(fs_folio, err);
+ fs_folio = bh->b_folio;
+ }
+ }
+ }
+ if (bd_folio)
+ folio_end_writeback(bd_folio);
+
+ nilfs_end_folio_io(fs_folio, err);
+}
+
+static void nilfs_segctor_abort_construction(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs, int err)
+{
+ LIST_HEAD(logs);
+ int ret;
+
+ list_splice_tail_init(&sci->sc_write_logs, &logs);
+ ret = nilfs_wait_on_logs(&logs);
+ nilfs_abort_logs(&logs, ret ? : err);
+
+ list_splice_tail_init(&sci->sc_segbufs, &logs);
+ if (list_empty(&logs))
+ return; /* if the first segment buffer preparation failed */
+
+ nilfs_cancel_segusage(&logs, nilfs->ns_sufile);
+ nilfs_free_incomplete_logs(&logs, nilfs);
+
+ if (sci->sc_stage.flags & NILFS_CF_SUFREED) {
+ ret = nilfs_sufile_cancel_freev(nilfs->ns_sufile,
+ sci->sc_freesegs,
+ sci->sc_nfreesegs,
+ NULL);
+ nilfs_sufile_warn_on_error(nilfs->ns_sufile, ret);
+ }
+
+ nilfs_destroy_logs(&logs);
+}
+
+static void nilfs_set_next_segment(struct the_nilfs *nilfs,
+ struct nilfs_segment_buffer *segbuf)
+{
+ nilfs->ns_segnum = segbuf->sb_segnum;
+ nilfs->ns_nextnum = segbuf->sb_nextnum;
+ nilfs->ns_pseg_offset = segbuf->sb_pseg_start - segbuf->sb_fseg_start
+ + segbuf->sb_sum.nblocks;
+ nilfs->ns_seg_seq = segbuf->sb_sum.seg_seq;
+ nilfs->ns_ctime = segbuf->sb_sum.ctime;
+}
+
+static void nilfs_segctor_complete_write(struct nilfs_sc_info *sci)
+{
+ struct nilfs_segment_buffer *segbuf;
+ struct folio *bd_folio = NULL, *fs_folio = NULL;
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ int update_sr = false;
+
+ list_for_each_entry(segbuf, &sci->sc_write_logs, sb_list) {
+ struct buffer_head *bh;
+
+ list_for_each_entry(bh, &segbuf->sb_segsum_buffers,
+ b_assoc_buffers) {
+ set_buffer_uptodate(bh);
+ clear_buffer_dirty(bh);
+ if (bh->b_folio != bd_folio) {
+ if (bd_folio)
+ folio_end_writeback(bd_folio);
+ bd_folio = bh->b_folio;
+ }
+ }
+ /*
+ * We assume that the buffers which belong to the same folio
+ * continue over the buffer list.
+ * Under this assumption, the last BHs of folios is
+ * identifiable by the discontinuity of bh->b_folio
+ * (folio != fs_folio).
+ *
+ * For B-tree node blocks, however, this assumption is not
+ * guaranteed. The cleanup code of B-tree node folios needs
+ * special care.
+ */
+ list_for_each_entry(bh, &segbuf->sb_payload_buffers,
+ b_assoc_buffers) {
+ const unsigned long set_bits = BIT(BH_Uptodate);
+ const unsigned long clear_bits =
+ (BIT(BH_Dirty) | BIT(BH_Async_Write) |
+ BIT(BH_Delay) | BIT(BH_NILFS_Volatile) |
+ BIT(BH_NILFS_Redirected));
+
+ if (bh == segbuf->sb_super_root) {
+ set_buffer_uptodate(bh);
+ clear_buffer_dirty(bh);
+ if (bh->b_folio != bd_folio) {
+ folio_end_writeback(bd_folio);
+ bd_folio = bh->b_folio;
+ }
+ update_sr = true;
+ break;
+ }
+ set_mask_bits(&bh->b_state, clear_bits, set_bits);
+ if (bh->b_folio != fs_folio) {
+ nilfs_end_folio_io(fs_folio, 0);
+ fs_folio = bh->b_folio;
+ }
+ }
+
+ if (!nilfs_segbuf_simplex(segbuf)) {
+ if (segbuf->sb_sum.flags & NILFS_SS_LOGBGN) {
+ set_bit(NILFS_SC_UNCLOSED, &sci->sc_flags);
+ sci->sc_lseg_stime = jiffies;
+ }
+ if (segbuf->sb_sum.flags & NILFS_SS_LOGEND)
+ clear_bit(NILFS_SC_UNCLOSED, &sci->sc_flags);
+ }
+ }
+ /*
+ * Since folios may continue over multiple segment buffers,
+ * end of the last folio must be checked outside of the loop.
+ */
+ if (bd_folio)
+ folio_end_writeback(bd_folio);
+
+ nilfs_end_folio_io(fs_folio, 0);
+
+ nilfs_drop_collected_inodes(&sci->sc_dirty_files);
+
+ if (nilfs_doing_gc())
+ nilfs_drop_collected_inodes(&sci->sc_gc_inodes);
+ else
+ nilfs->ns_nongc_ctime = sci->sc_seg_ctime;
+
+ sci->sc_nblk_inc += sci->sc_nblk_this_inc;
+
+ segbuf = NILFS_LAST_SEGBUF(&sci->sc_write_logs);
+ nilfs_set_next_segment(nilfs, segbuf);
+
+ if (update_sr) {
+ nilfs->ns_flushed_device = 0;
+ nilfs_set_last_segment(nilfs, segbuf->sb_pseg_start,
+ segbuf->sb_sum.seg_seq, nilfs->ns_cno++);
+
+ clear_bit(NILFS_SC_HAVE_DELTA, &sci->sc_flags);
+ clear_bit(NILFS_SC_DIRTY, &sci->sc_flags);
+ set_bit(NILFS_SC_SUPER_ROOT, &sci->sc_flags);
+ nilfs_segctor_clear_metadata_dirty(sci);
+ } else
+ clear_bit(NILFS_SC_SUPER_ROOT, &sci->sc_flags);
+}
+
+static int nilfs_segctor_wait(struct nilfs_sc_info *sci)
+{
+ int ret;
+
+ ret = nilfs_wait_on_logs(&sci->sc_write_logs);
+ if (!ret) {
+ nilfs_segctor_complete_write(sci);
+ nilfs_destroy_logs(&sci->sc_write_logs);
+ }
+ return ret;
+}
+
+static int nilfs_segctor_collect_dirty_files(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs)
+{
+ struct nilfs_inode_info *ii, *n;
+ struct inode *ifile = sci->sc_root->ifile;
+
+ spin_lock(&nilfs->ns_inode_lock);
+ retry:
+ list_for_each_entry_safe(ii, n, &nilfs->ns_dirty_files, i_dirty) {
+ if (!ii->i_bh) {
+ struct buffer_head *ibh;
+ int err;
+
+ spin_unlock(&nilfs->ns_inode_lock);
+ err = nilfs_ifile_get_inode_block(
+ ifile, ii->vfs_inode.i_ino, &ibh);
+ if (unlikely(err)) {
+ nilfs_warn(sci->sc_super,
+ "log writer: error %d getting inode block (ino=%llu)",
+ err, ii->vfs_inode.i_ino);
+ return err;
+ }
+ spin_lock(&nilfs->ns_inode_lock);
+ if (likely(!ii->i_bh))
+ ii->i_bh = ibh;
+ else
+ brelse(ibh);
+ goto retry;
+ }
+
+ // Always redirty the buffer to avoid race condition
+ mark_buffer_dirty(ii->i_bh);
+ nilfs_mdt_mark_dirty(ifile);
+
+ clear_bit(NILFS_I_QUEUED, &ii->i_state);
+ set_bit(NILFS_I_BUSY, &ii->i_state);
+ list_move_tail(&ii->i_dirty, &sci->sc_dirty_files);
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+
+ return 0;
+}
+
+static void nilfs_segctor_drop_written_files(struct nilfs_sc_info *sci,
+ struct the_nilfs *nilfs)
+{
+ struct nilfs_inode_info *ii, *n;
+ int during_mount = !(sci->sc_super->s_flags & SB_ACTIVE);
+ int defer_iput = false;
+
+ spin_lock(&nilfs->ns_inode_lock);
+ list_for_each_entry_safe(ii, n, &sci->sc_dirty_files, i_dirty) {
+ if (!test_and_clear_bit(NILFS_I_UPDATED, &ii->i_state) ||
+ test_bit(NILFS_I_DIRTY, &ii->i_state))
+ continue;
+
+ clear_bit(NILFS_I_BUSY, &ii->i_state);
+ brelse(ii->i_bh);
+ ii->i_bh = NULL;
+ list_del_init(&ii->i_dirty);
+ if (!ii->vfs_inode.i_nlink || during_mount) {
+ /*
+ * Defer calling iput() to avoid deadlocks if
+ * i_nlink == 0 or mount is not yet finished.
+ */
+ list_add_tail(&ii->i_dirty, &sci->sc_iput_queue);
+ defer_iput = true;
+ } else {
+ spin_unlock(&nilfs->ns_inode_lock);
+ iput(&ii->vfs_inode);
+ spin_lock(&nilfs->ns_inode_lock);
+ }
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+
+ if (defer_iput)
+ schedule_work(&sci->sc_iput_work);
+}
+
+/*
+ * Main procedure of segment constructor
+ */
+static int nilfs_segctor_do_construct(struct nilfs_sc_info *sci, int mode)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ int err;
+
+ if (sb_rdonly(sci->sc_super))
+ return -EROFS;
+
+ nilfs_sc_cstage_set(sci, NILFS_ST_INIT);
+ sci->sc_cno = nilfs->ns_cno;
+
+ err = nilfs_segctor_collect_dirty_files(sci, nilfs);
+ if (unlikely(err))
+ goto out;
+
+ if (nilfs_test_metadata_dirty(nilfs, sci->sc_root))
+ set_bit(NILFS_SC_DIRTY, &sci->sc_flags);
+
+ if (nilfs_segctor_clean(sci))
+ goto out;
+
+ do {
+ sci->sc_stage.flags &= ~NILFS_CF_HISTORY_MASK;
+
+ err = nilfs_segctor_begin_construction(sci, nilfs);
+ if (unlikely(err))
+ goto failed;
+
+ /* Update time stamp */
+ sci->sc_seg_ctime = ktime_get_real_seconds();
+
+ err = nilfs_segctor_collect(sci, nilfs, mode);
+ if (unlikely(err))
+ goto failed;
+
+ /* Avoid empty segment */
+ if (nilfs_sc_cstage_get(sci) == NILFS_ST_DONE &&
+ nilfs_segbuf_empty(sci->sc_curseg)) {
+ nilfs_segctor_abort_construction(sci, nilfs, 1);
+ goto out;
+ }
+
+ err = nilfs_segctor_assign(sci, mode);
+ if (unlikely(err))
+ goto failed;
+
+ if (sci->sc_stage.flags & NILFS_CF_IFILE_STARTED)
+ nilfs_segctor_fill_in_file_bmap(sci);
+
+ if (mode == SC_LSEG_SR &&
+ nilfs_sc_cstage_get(sci) >= NILFS_ST_CPFILE) {
+ err = nilfs_cpfile_finalize_checkpoint(
+ nilfs->ns_cpfile, nilfs->ns_cno, sci->sc_root,
+ sci->sc_nblk_inc + sci->sc_nblk_this_inc,
+ sci->sc_seg_ctime,
+ !test_bit(NILFS_SC_HAVE_DELTA, &sci->sc_flags));
+ if (unlikely(err))
+ goto failed_to_write;
+
+ nilfs_segctor_fill_in_super_root(sci, nilfs);
+ }
+ nilfs_segctor_update_segusage(sci, nilfs->ns_sufile);
+
+ /* Write partial segments */
+ nilfs_prepare_write_logs(&sci->sc_segbufs, nilfs->ns_crc_seed);
+
+ err = nilfs_segctor_write(sci, nilfs);
+ if (unlikely(err))
+ goto failed_to_write;
+
+ if (nilfs_sc_cstage_get(sci) == NILFS_ST_DONE ||
+ nilfs->ns_blocksize_bits != PAGE_SHIFT) {
+ /*
+ * At this point, we avoid double buffering
+ * for blocksize < pagesize because page dirty
+ * flag is turned off during write and dirty
+ * buffers are not properly collected for
+ * pages crossing over segments.
+ */
+ err = nilfs_segctor_wait(sci);
+ if (err)
+ goto failed_to_write;
+ }
+ } while (nilfs_sc_cstage_get(sci) != NILFS_ST_DONE);
+
+ out:
+ nilfs_segctor_drop_written_files(sci, nilfs);
+ return err;
+
+ failed_to_write:
+ failed:
+ if (mode == SC_LSEG_SR && nilfs_sc_cstage_get(sci) >= NILFS_ST_IFILE)
+ nilfs_redirty_inodes(&sci->sc_dirty_files);
+ if (nilfs_doing_gc())
+ nilfs_redirty_inodes(&sci->sc_gc_inodes);
+ nilfs_segctor_abort_construction(sci, nilfs, err);
+ goto out;
+}
+
+/**
+ * nilfs_segctor_start_timer - set timer of background write
+ * @sci: nilfs_sc_info
+ *
+ * If the timer has already been set, it ignores the new request.
+ * This function MUST be called within a section locking the segment
+ * semaphore.
+ */
+static void nilfs_segctor_start_timer(struct nilfs_sc_info *sci)
+{
+ spin_lock(&sci->sc_state_lock);
+ if (!(sci->sc_state & NILFS_SEGCTOR_COMMIT)) {
+ if (sci->sc_task) {
+ sci->sc_timer.expires = jiffies + sci->sc_interval;
+ add_timer(&sci->sc_timer);
+ }
+ sci->sc_state |= NILFS_SEGCTOR_COMMIT;
+ }
+ spin_unlock(&sci->sc_state_lock);
+}
+
+static void nilfs_segctor_do_flush(struct nilfs_sc_info *sci, int bn)
+{
+ spin_lock(&sci->sc_state_lock);
+ if (!(sci->sc_flush_request & BIT(bn))) {
+ unsigned long prev_req = sci->sc_flush_request;
+
+ sci->sc_flush_request |= BIT(bn);
+ if (!prev_req)
+ wake_up(&sci->sc_wait_daemon);
+ }
+ spin_unlock(&sci->sc_state_lock);
+}
+
+struct nilfs_segctor_wait_request {
+ wait_queue_entry_t wq;
+ __u32 seq;
+ int err;
+ atomic_t done;
+};
+
+static int nilfs_segctor_sync(struct nilfs_sc_info *sci)
+{
+ struct nilfs_segctor_wait_request wait_req;
+ int err = 0;
+
+ init_wait(&wait_req.wq);
+ wait_req.err = 0;
+ atomic_set(&wait_req.done, 0);
+ init_waitqueue_entry(&wait_req.wq, current);
+
+ /*
+ * To prevent a race issue where completion notifications from the
+ * log writer thread are missed, increment the request sequence count
+ * "sc_seq_request" and insert a wait queue entry using the current
+ * sequence number into the "sc_wait_request" queue at the same time
+ * within the lock section of "sc_state_lock".
+ */
+ spin_lock(&sci->sc_state_lock);
+ wait_req.seq = ++sci->sc_seq_request;
+ add_wait_queue(&sci->sc_wait_request, &wait_req.wq);
+ spin_unlock(&sci->sc_state_lock);
+
+ wake_up(&sci->sc_wait_daemon);
+
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ /*
+ * Synchronize only while the log writer thread is alive.
+ * Leave flushing out after the log writer thread exits to
+ * the cleanup work in nilfs_segctor_destroy().
+ */
+ if (!sci->sc_task)
+ break;
+
+ if (atomic_read(&wait_req.done)) {
+ err = wait_req.err;
+ break;
+ }
+ if (!signal_pending(current)) {
+ schedule();
+ continue;
+ }
+ err = -ERESTARTSYS;
+ break;
+ }
+ finish_wait(&sci->sc_wait_request, &wait_req.wq);
+ return err;
+}
+
+static void nilfs_segctor_wakeup(struct nilfs_sc_info *sci, int err, bool force)
+{
+ struct nilfs_segctor_wait_request *wrq, *n;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sci->sc_wait_request.lock, flags);
+ list_for_each_entry_safe(wrq, n, &sci->sc_wait_request.head, wq.entry) {
+ if (!atomic_read(&wrq->done) &&
+ (force || nilfs_cnt32_ge(sci->sc_seq_done, wrq->seq))) {
+ wrq->err = err;
+ atomic_set(&wrq->done, 1);
+ }
+ if (atomic_read(&wrq->done)) {
+ wrq->wq.func(&wrq->wq,
+ TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,
+ 0, NULL);
+ }
+ }
+ spin_unlock_irqrestore(&sci->sc_wait_request.lock, flags);
+}
+
+/**
+ * nilfs_construct_segment - construct a logical segment
+ * @sb: super block
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No space left on device (only in a panic state).
+ * * %-ERESTARTSYS - Interrupted.
+ * * %-EROFS - Read only filesystem.
+ */
+int nilfs_construct_segment(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+ struct nilfs_transaction_info *ti;
+
+ if (sb_rdonly(sb) || unlikely(!sci))
+ return -EROFS;
+
+ /* A call inside transactions causes a deadlock. */
+ BUG_ON((ti = current->journal_info) && ti->ti_magic == NILFS_TI_MAGIC);
+
+ return nilfs_segctor_sync(sci);
+}
+
+/**
+ * nilfs_construct_dsync_segment - construct a data-only logical segment
+ * @sb: super block
+ * @inode: inode whose data blocks should be written out
+ * @start: start byte offset
+ * @end: end byte offset (inclusive)
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No space left on device (only in a panic state).
+ * * %-ERESTARTSYS - Interrupted.
+ * * %-EROFS - Read only filesystem.
+ */
+int nilfs_construct_dsync_segment(struct super_block *sb, struct inode *inode,
+ loff_t start, loff_t end)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+ struct nilfs_inode_info *ii;
+ struct nilfs_transaction_info ti;
+ int err = 0;
+
+ if (sb_rdonly(sb) || unlikely(!sci))
+ return -EROFS;
+
+ nilfs_transaction_lock(sb, &ti, 0);
+
+ ii = NILFS_I(inode);
+ if (test_bit(NILFS_I_INODE_SYNC, &ii->i_state) ||
+ nilfs_test_opt(nilfs, STRICT_ORDER) ||
+ test_bit(NILFS_SC_UNCLOSED, &sci->sc_flags) ||
+ nilfs_discontinued(nilfs)) {
+ nilfs_transaction_unlock(sb);
+ err = nilfs_segctor_sync(sci);
+ return err;
+ }
+
+ spin_lock(&nilfs->ns_inode_lock);
+ if (!test_bit(NILFS_I_QUEUED, &ii->i_state) &&
+ !test_bit(NILFS_I_BUSY, &ii->i_state)) {
+ spin_unlock(&nilfs->ns_inode_lock);
+ nilfs_transaction_unlock(sb);
+ return 0;
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+ sci->sc_dsync_inode = ii;
+ sci->sc_dsync_start = start;
+ sci->sc_dsync_end = end;
+
+ err = nilfs_segctor_do_construct(sci, SC_LSEG_DSYNC);
+ if (!err)
+ nilfs->ns_flushed_device = 0;
+
+ nilfs_transaction_unlock(sb);
+ return err;
+}
+
+#define FLUSH_FILE_BIT (0x1) /* data file only */
+#define FLUSH_DAT_BIT BIT(NILFS_DAT_INO) /* DAT only */
+
+/**
+ * nilfs_segctor_accept - record accepted sequence count of log-write requests
+ * @sci: segment constructor object
+ */
+static void nilfs_segctor_accept(struct nilfs_sc_info *sci)
+{
+ bool thread_is_alive;
+
+ spin_lock(&sci->sc_state_lock);
+ sci->sc_seq_accepted = sci->sc_seq_request;
+ thread_is_alive = (bool)sci->sc_task;
+ spin_unlock(&sci->sc_state_lock);
+
+ /*
+ * This function does not race with the log writer thread's
+ * termination. Therefore, deleting sc_timer, which should not be
+ * done after the log writer thread exits, can be done safely outside
+ * the area protected by sc_state_lock.
+ */
+ if (thread_is_alive)
+ timer_delete_sync(&sci->sc_timer);
+}
+
+/**
+ * nilfs_segctor_notify - notify the result of request to caller threads
+ * @sci: segment constructor object
+ * @mode: mode of log forming
+ * @err: error code to be notified
+ */
+static void nilfs_segctor_notify(struct nilfs_sc_info *sci, int mode, int err)
+{
+ /* Clear requests (even when the construction failed) */
+ spin_lock(&sci->sc_state_lock);
+
+ if (mode == SC_LSEG_SR) {
+ sci->sc_state &= ~NILFS_SEGCTOR_COMMIT;
+ sci->sc_seq_done = sci->sc_seq_accepted;
+ nilfs_segctor_wakeup(sci, err, false);
+ sci->sc_flush_request = 0;
+ } else {
+ if (mode == SC_FLUSH_FILE)
+ sci->sc_flush_request &= ~FLUSH_FILE_BIT;
+ else if (mode == SC_FLUSH_DAT)
+ sci->sc_flush_request &= ~FLUSH_DAT_BIT;
+
+ /* re-enable timer if checkpoint creation was not done */
+ if ((sci->sc_state & NILFS_SEGCTOR_COMMIT) && sci->sc_task &&
+ time_before(jiffies, sci->sc_timer.expires))
+ add_timer(&sci->sc_timer);
+ }
+ spin_unlock(&sci->sc_state_lock);
+}
+
+/**
+ * nilfs_segctor_construct - form logs and write them to disk
+ * @sci: segment constructor object
+ * @mode: mode of log forming
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_segctor_construct(struct nilfs_sc_info *sci, int mode)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ struct nilfs_super_block **sbp;
+ int err = 0;
+
+ nilfs_segctor_accept(sci);
+
+ if (nilfs_discontinued(nilfs))
+ mode = SC_LSEG_SR;
+ if (!nilfs_segctor_confirm(sci))
+ err = nilfs_segctor_do_construct(sci, mode);
+
+ if (likely(!err)) {
+ if (mode != SC_FLUSH_DAT)
+ atomic_set(&nilfs->ns_ndirtyblks, 0);
+ if (test_bit(NILFS_SC_SUPER_ROOT, &sci->sc_flags) &&
+ nilfs_discontinued(nilfs)) {
+ down_write(&nilfs->ns_sem);
+ err = -EIO;
+ sbp = nilfs_prepare_super(sci->sc_super,
+ nilfs_sb_will_flip(nilfs));
+ if (likely(sbp)) {
+ nilfs_set_log_cursor(sbp[0], nilfs);
+ err = nilfs_commit_super(sci->sc_super,
+ NILFS_SB_COMMIT);
+ }
+ up_write(&nilfs->ns_sem);
+ }
+ }
+
+ nilfs_segctor_notify(sci, mode, err);
+ return err;
+}
+
+static void nilfs_construction_timeout(struct timer_list *t)
+{
+ struct nilfs_sc_info *sci = timer_container_of(sci, t, sc_timer);
+
+ wake_up_process(sci->sc_task);
+}
+
+static void
+nilfs_remove_written_gcinodes(struct the_nilfs *nilfs, struct list_head *head)
+{
+ struct nilfs_inode_info *ii, *n;
+
+ list_for_each_entry_safe(ii, n, head, i_dirty) {
+ if (!test_bit(NILFS_I_UPDATED, &ii->i_state))
+ continue;
+ list_del_init(&ii->i_dirty);
+ truncate_inode_pages(&ii->vfs_inode.i_data, 0);
+ nilfs_btnode_cache_clear(ii->i_assoc_inode->i_mapping);
+ iput(&ii->vfs_inode);
+ }
+}
+
+int nilfs_clean_segments(struct super_block *sb, struct nilfs_argv *argv,
+ void **kbufs)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci = nilfs->ns_writer;
+ struct nilfs_transaction_info ti;
+ int err;
+ size_t i, nfreesegs = argv[4].v_nmembs;
+ __u64 *segnumv = kbufs[4];
+
+ if (unlikely(!sci))
+ return -EROFS;
+
+ nilfs_transaction_lock(sb, &ti, 1);
+
+ /*
+ * Validate segment numbers under ns_segctor_sem (held for write
+ * by nilfs_transaction_lock above) so the check is serialized
+ * against nilfs_ioctl_resize(), which can modify ns_nsegments.
+ * Rejecting bad input here, before any segment-cleaning work
+ * begins, avoids the per-element diagnostic path inside
+ * nilfs_sufile_updatev() that would otherwise run under this
+ * same lock and stall concurrent readers.
+ */
+ for (i = 0; i < nfreesegs; i++) {
+ if (segnumv[i] >= nilfs->ns_nsegments) {
+ nilfs_err(sb,
+ "Segment number %llu to be freed is out of range",
+ (unsigned long long)segnumv[i]);
+ err = -EINVAL;
+ goto bail_unlock;
+ }
+ }
+
+ err = nilfs_mdt_save_to_shadow_map(nilfs->ns_dat);
+ if (unlikely(err))
+ goto out_unlock;
+
+ err = nilfs_ioctl_prepare_clean_segments(nilfs, argv, kbufs);
+ if (unlikely(err)) {
+ nilfs_mdt_restore_from_shadow_map(nilfs->ns_dat);
+ goto out_unlock;
+ }
+
+ sci->sc_freesegs = kbufs[4];
+ sci->sc_nfreesegs = argv[4].v_nmembs;
+ list_splice_tail_init(&nilfs->ns_gc_inodes, &sci->sc_gc_inodes);
+
+ for (;;) {
+ err = nilfs_segctor_construct(sci, SC_LSEG_SR);
+ nilfs_remove_written_gcinodes(nilfs, &sci->sc_gc_inodes);
+
+ if (likely(!err))
+ break;
+
+ nilfs_warn(sb, "error %d cleaning segments", err);
+
+ if (unlikely(err == -EROFS))
+ goto out_unlock;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(sci->sc_interval);
+ }
+ if (nilfs_test_opt(nilfs, DISCARD)) {
+ int ret = nilfs_discard_segments(nilfs, sci->sc_freesegs,
+ sci->sc_nfreesegs);
+ if (ret) {
+ nilfs_warn(sb,
+ "error %d on discard request, turning discards off for the device",
+ ret);
+ nilfs_clear_opt(nilfs, DISCARD);
+ }
+ }
+
+ out_unlock:
+ sci->sc_freesegs = NULL;
+ sci->sc_nfreesegs = 0;
+ nilfs_mdt_clear_shadow_map(nilfs->ns_dat);
+ bail_unlock:
+ nilfs_transaction_unlock(sb);
+ return err;
+}
+
+static void nilfs_segctor_thread_construct(struct nilfs_sc_info *sci, int mode)
+{
+ struct nilfs_transaction_info ti;
+
+ nilfs_transaction_lock(sci->sc_super, &ti, 0);
+ nilfs_segctor_construct(sci, mode);
+
+ /*
+ * Unclosed segment should be retried. We do this using sc_timer.
+ * Timeout of sc_timer will invoke complete construction which leads
+ * to close the current logical segment.
+ */
+ if (test_bit(NILFS_SC_UNCLOSED, &sci->sc_flags))
+ nilfs_segctor_start_timer(sci);
+
+ nilfs_transaction_unlock(sci->sc_super);
+}
+
+static void nilfs_segctor_do_immediate_flush(struct nilfs_sc_info *sci)
+{
+ int mode = 0;
+
+ spin_lock(&sci->sc_state_lock);
+ mode = (sci->sc_flush_request & FLUSH_DAT_BIT) ?
+ SC_FLUSH_DAT : SC_FLUSH_FILE;
+ spin_unlock(&sci->sc_state_lock);
+
+ if (mode) {
+ nilfs_segctor_do_construct(sci, mode);
+
+ spin_lock(&sci->sc_state_lock);
+ sci->sc_flush_request &= (mode == SC_FLUSH_FILE) ?
+ ~FLUSH_FILE_BIT : ~FLUSH_DAT_BIT;
+ spin_unlock(&sci->sc_state_lock);
+ }
+ clear_bit(NILFS_SC_PRIOR_FLUSH, &sci->sc_flags);
+}
+
+static int nilfs_segctor_flush_mode(struct nilfs_sc_info *sci)
+{
+ if (!test_bit(NILFS_SC_UNCLOSED, &sci->sc_flags) ||
+ time_before(jiffies, sci->sc_lseg_stime + sci->sc_mjcp_freq)) {
+ if (!(sci->sc_flush_request & ~FLUSH_FILE_BIT))
+ return SC_FLUSH_FILE;
+ else if (!(sci->sc_flush_request & ~FLUSH_DAT_BIT))
+ return SC_FLUSH_DAT;
+ }
+ return SC_LSEG_SR;
+}
+
+/**
+ * nilfs_log_write_required - determine whether log writing is required
+ * @sci: nilfs_sc_info struct
+ * @modep: location for storing log writing mode
+ *
+ * Return: true if log writing is required, false otherwise. If log writing
+ * is required, the mode is stored in the location pointed to by @modep.
+ */
+static bool nilfs_log_write_required(struct nilfs_sc_info *sci, int *modep)
+{
+ bool timedout, ret = true;
+
+ spin_lock(&sci->sc_state_lock);
+ timedout = ((sci->sc_state & NILFS_SEGCTOR_COMMIT) &&
+ time_after_eq(jiffies, sci->sc_timer.expires));
+ if (timedout || sci->sc_seq_request != sci->sc_seq_done)
+ *modep = SC_LSEG_SR;
+ else if (sci->sc_flush_request)
+ *modep = nilfs_segctor_flush_mode(sci);
+ else
+ ret = false;
+
+ spin_unlock(&sci->sc_state_lock);
+ return ret;
+}
+
+/**
+ * nilfs_segctor_thread - main loop of the log writer thread
+ * @arg: pointer to a struct nilfs_sc_info.
+ *
+ * nilfs_segctor_thread() is the main loop function of the log writer kernel
+ * thread, which determines whether log writing is necessary, and if so,
+ * performs the log write in the background, or waits if not. It is also
+ * used to decide the background writeback of the superblock.
+ *
+ * Return: Always 0.
+ */
+static int nilfs_segctor_thread(void *arg)
+{
+ struct nilfs_sc_info *sci = (struct nilfs_sc_info *)arg;
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+
+ nilfs_info(sci->sc_super,
+ "segctord starting. Construction interval = %lu seconds, CP frequency < %lu seconds",
+ sci->sc_interval / HZ, sci->sc_mjcp_freq / HZ);
+
+ set_freezable();
+
+ while (!kthread_should_stop()) {
+ DEFINE_WAIT(wait);
+ bool should_write;
+ int mode;
+
+ if (freezing(current)) {
+ try_to_freeze();
+ continue;
+ }
+
+ prepare_to_wait(&sci->sc_wait_daemon, &wait,
+ TASK_INTERRUPTIBLE);
+ should_write = nilfs_log_write_required(sci, &mode);
+ if (!should_write)
+ schedule();
+ finish_wait(&sci->sc_wait_daemon, &wait);
+
+ if (nilfs_sb_dirty(nilfs) && nilfs_sb_need_update(nilfs))
+ set_nilfs_discontinued(nilfs);
+
+ if (should_write)
+ nilfs_segctor_thread_construct(sci, mode);
+ }
+
+ /* end sync. */
+ spin_lock(&sci->sc_state_lock);
+ sci->sc_task = NULL;
+ timer_shutdown_sync(&sci->sc_timer);
+ spin_unlock(&sci->sc_state_lock);
+ return 0;
+}
+
+/*
+ * Setup & clean-up functions
+ */
+static struct nilfs_sc_info *nilfs_segctor_new(struct super_block *sb,
+ struct nilfs_root *root)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci;
+
+ sci = kzalloc_obj(*sci);
+ if (!sci)
+ return NULL;
+
+ sci->sc_super = sb;
+
+ nilfs_get_root(root);
+ sci->sc_root = root;
+
+ init_waitqueue_head(&sci->sc_wait_request);
+ init_waitqueue_head(&sci->sc_wait_daemon);
+ spin_lock_init(&sci->sc_state_lock);
+ INIT_LIST_HEAD(&sci->sc_dirty_files);
+ INIT_LIST_HEAD(&sci->sc_segbufs);
+ INIT_LIST_HEAD(&sci->sc_write_logs);
+ INIT_LIST_HEAD(&sci->sc_gc_inodes);
+ INIT_LIST_HEAD(&sci->sc_iput_queue);
+ INIT_WORK(&sci->sc_iput_work, nilfs_iput_work_func);
+
+ sci->sc_interval = HZ * NILFS_SC_DEFAULT_TIMEOUT;
+ sci->sc_mjcp_freq = HZ * NILFS_SC_DEFAULT_SR_FREQ;
+ sci->sc_watermark = NILFS_SC_DEFAULT_WATERMARK;
+
+ if (nilfs->ns_interval)
+ sci->sc_interval = HZ * nilfs->ns_interval;
+ if (nilfs->ns_watermark)
+ sci->sc_watermark = nilfs->ns_watermark;
+ return sci;
+}
+
+static void nilfs_segctor_write_out(struct nilfs_sc_info *sci)
+{
+ int ret, retrycount = NILFS_SC_CLEANUP_RETRY;
+
+ /*
+ * The segctord thread was stopped and its timer was removed.
+ * But some tasks remain.
+ */
+ do {
+ struct nilfs_transaction_info ti;
+
+ nilfs_transaction_lock(sci->sc_super, &ti, 0);
+ ret = nilfs_segctor_construct(sci, SC_LSEG_SR);
+ nilfs_transaction_unlock(sci->sc_super);
+
+ flush_work(&sci->sc_iput_work);
+
+ } while (ret && ret != -EROFS && retrycount-- > 0);
+}
+
+/**
+ * nilfs_segctor_destroy - destroy the segment constructor.
+ * @sci: nilfs_sc_info
+ *
+ * nilfs_segctor_destroy() kills the segctord thread and frees
+ * the nilfs_sc_info struct.
+ * Caller must hold the segment semaphore.
+ */
+static void nilfs_segctor_destroy(struct nilfs_sc_info *sci)
+{
+ struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
+ int flag;
+
+ up_write(&nilfs->ns_segctor_sem);
+
+ if (sci->sc_task) {
+ wake_up(&sci->sc_wait_daemon);
+ if (kthread_stop(sci->sc_task)) {
+ spin_lock(&sci->sc_state_lock);
+ sci->sc_task = NULL;
+ timer_shutdown_sync(&sci->sc_timer);
+ spin_unlock(&sci->sc_state_lock);
+ }
+ }
+
+ spin_lock(&sci->sc_state_lock);
+ flag = ((sci->sc_state & NILFS_SEGCTOR_COMMIT) || sci->sc_flush_request
+ || sci->sc_seq_request != sci->sc_seq_done);
+ spin_unlock(&sci->sc_state_lock);
+
+ /*
+ * Forcibly wake up tasks waiting in nilfs_segctor_sync(), which can
+ * be called from delayed iput() via nilfs_evict_inode() and can race
+ * with the above log writer thread termination.
+ */
+ nilfs_segctor_wakeup(sci, 0, true);
+
+ if (flush_work(&sci->sc_iput_work))
+ flag = true;
+
+ if (flag || !nilfs_segctor_confirm(sci))
+ nilfs_segctor_write_out(sci);
+
+ if (!list_empty(&sci->sc_dirty_files)) {
+ nilfs_warn(sci->sc_super,
+ "disposed unprocessed dirty file(s) when stopping log writer");
+ nilfs_dispose_list(nilfs, &sci->sc_dirty_files, 1);
+ }
+
+ if (!list_empty(&sci->sc_iput_queue)) {
+ nilfs_warn(sci->sc_super,
+ "disposed unprocessed inode(s) in iput queue when stopping log writer");
+ nilfs_dispose_list(nilfs, &sci->sc_iput_queue, 1);
+ }
+
+ WARN_ON(!list_empty(&sci->sc_segbufs));
+ WARN_ON(!list_empty(&sci->sc_write_logs));
+
+ nilfs_put_root(sci->sc_root);
+
+ down_write(&nilfs->ns_segctor_sem);
+
+ kfree(sci);
+}
+
+/**
+ * nilfs_attach_log_writer - attach log writer
+ * @sb: super block instance
+ * @root: root object of the current filesystem tree
+ *
+ * This allocates a log writer object, initializes it, and starts the
+ * log writer.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINTR - Log writer thread creation failed due to interruption.
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_attach_log_writer(struct super_block *sb, struct nilfs_root *root)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_sc_info *sci;
+ struct task_struct *t;
+ int err;
+
+ if (nilfs->ns_writer) {
+ /*
+ * This happens if the filesystem is made read-only by
+ * __nilfs_error or nilfs_remount and then remounted
+ * read/write. In these cases, reuse the existing
+ * writer.
+ */
+ return 0;
+ }
+
+ sci = nilfs_segctor_new(sb, root);
+ if (unlikely(!sci))
+ return -ENOMEM;
+
+ nilfs->ns_writer = sci;
+ t = kthread_create(nilfs_segctor_thread, sci, "segctord");
+ if (IS_ERR(t)) {
+ err = PTR_ERR(t);
+ nilfs_err(sb, "error %d creating segctord thread", err);
+ nilfs_detach_log_writer(sb);
+ return err;
+ }
+ sci->sc_task = t;
+ timer_setup(&sci->sc_timer, nilfs_construction_timeout, 0);
+
+ wake_up_process(sci->sc_task);
+ return 0;
+}
+
+/**
+ * nilfs_detach_log_writer - destroy log writer
+ * @sb: super block instance
+ *
+ * This kills log writer daemon, frees the log writer object, and
+ * destroys list of dirty files.
+ */
+void nilfs_detach_log_writer(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ LIST_HEAD(garbage_list);
+
+ down_write(&nilfs->ns_segctor_sem);
+ if (nilfs->ns_writer) {
+ nilfs_segctor_destroy(nilfs->ns_writer);
+ nilfs->ns_writer = NULL;
+ }
+ set_nilfs_purging(nilfs);
+
+ /* Force to free the list of dirty files */
+ spin_lock(&nilfs->ns_inode_lock);
+ if (!list_empty(&nilfs->ns_dirty_files)) {
+ list_splice_init(&nilfs->ns_dirty_files, &garbage_list);
+ nilfs_warn(sb,
+ "disposed unprocessed dirty file(s) when detaching log writer");
+ }
+ spin_unlock(&nilfs->ns_inode_lock);
+ up_write(&nilfs->ns_segctor_sem);
+
+ nilfs_dispose_list(nilfs, &garbage_list, 1);
+ clear_nilfs_purging(nilfs);
+}
diff --git a/fs/nilfs2/segment.h b/fs/nilfs2/segment.h
new file mode 100644
index 000000000..4b39ed43a
--- /dev/null
+++ b/fs/nilfs2/segment.h
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS Segment constructor prototypes and definitions
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+#ifndef _NILFS_SEGMENT_H
+#define _NILFS_SEGMENT_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include <linux/workqueue.h>
+#include "nilfs.h"
+
+struct nilfs_root;
+
+/**
+ * struct nilfs_recovery_info - Recovery information
+ * @ri_need_recovery: Recovery status
+ * @ri_super_root: Block number of the last super root
+ * @ri_cno: Number of the last checkpoint
+ * @ri_lsegs_start: Region for roll-forwarding (start block number)
+ * @ri_lsegs_end: Region for roll-forwarding (end block number)
+ * @ri_lsegs_start_seq: Sequence value of the segment at ri_lsegs_start
+ * @ri_used_segments: List of segments to be mark active
+ * @ri_pseg_start: Block number of the last partial segment
+ * @ri_seq: Sequence number on the last partial segment
+ * @ri_segnum: Segment number on the last partial segment
+ * @ri_nextnum: Next segment number on the last partial segment
+ */
+struct nilfs_recovery_info {
+ int ri_need_recovery;
+ sector_t ri_super_root;
+ __u64 ri_cno;
+
+ sector_t ri_lsegs_start;
+ sector_t ri_lsegs_end;
+ u64 ri_lsegs_start_seq;
+ struct list_head ri_used_segments;
+ sector_t ri_pseg_start;
+ u64 ri_seq;
+ __u64 ri_segnum;
+ __u64 ri_nextnum;
+};
+
+/* ri_need_recovery */
+#define NILFS_RECOVERY_SR_UPDATED 1 /* The super root was updated */
+#define NILFS_RECOVERY_ROLLFORWARD_DONE 2 /* Rollforward was carried out */
+
+/**
+ * struct nilfs_cstage - Context of collection stage
+ * @scnt: Stage count, must be accessed via wrappers:
+ * nilfs_sc_cstage_inc(), nilfs_sc_cstage_set(), nilfs_sc_cstage_get()
+ * @flags: State flags
+ * @dirty_file_ptr: Pointer on dirty_files list, or inode of a target file
+ * @gc_inode_ptr: Pointer on the list of gc-inodes
+ */
+struct nilfs_cstage {
+ int scnt;
+ unsigned int flags;
+ struct nilfs_inode_info *dirty_file_ptr;
+ struct nilfs_inode_info *gc_inode_ptr;
+};
+
+struct nilfs_segment_buffer;
+
+struct nilfs_segsum_pointer {
+ struct buffer_head *bh;
+ unsigned int offset; /* offset in bytes */
+};
+
+/**
+ * struct nilfs_sc_info - Segment constructor information
+ * @sc_super: Back pointer to super_block struct
+ * @sc_root: root object of the current filesystem tree
+ * @sc_nblk_inc: Block count of current generation
+ * @sc_dirty_files: List of files to be written
+ * @sc_gc_inodes: List of GC inodes having blocks to be written
+ * @sc_iput_queue: list of inodes for which iput should be done
+ * @sc_iput_work: work struct to defer iput call
+ * @sc_freesegs: array of segment numbers to be freed
+ * @sc_nfreesegs: number of segments on @sc_freesegs
+ * @sc_dsync_inode: inode whose data pages are written for a sync operation
+ * @sc_dsync_start: start byte offset of data pages
+ * @sc_dsync_end: end byte offset of data pages (inclusive)
+ * @sc_segbufs: List of segment buffers
+ * @sc_write_logs: List of segment buffers to hold logs under writing
+ * @sc_segbuf_nblocks: Number of available blocks in segment buffers.
+ * @sc_curseg: Current segment buffer
+ * @sc_stage: Collection stage
+ * @sc_finfo_ptr: pointer to the current finfo struct in the segment summary
+ * @sc_binfo_ptr: pointer to the current binfo struct in the segment summary
+ * @sc_blk_cnt: Block count of a file
+ * @sc_datablk_cnt: Data block count of a file
+ * @sc_nblk_this_inc: Number of blocks included in the current logical segment
+ * @sc_seg_ctime: Creation time
+ * @sc_cno: checkpoint number of current log
+ * @sc_flags: Internal flags
+ * @sc_state_lock: spinlock for sc_state and so on
+ * @sc_state: Segctord state flags
+ * @sc_flush_request: inode bitmap of metadata files to be flushed
+ * @sc_wait_request: Client request queue
+ * @sc_wait_daemon: Daemon wait queue
+ * @sc_seq_request: Request counter
+ * @sc_seq_accepted: Accepted request count
+ * @sc_seq_done: Completion counter
+ * @sc_sync: Request of explicit sync operation
+ * @sc_interval: Timeout value of background construction
+ * @sc_mjcp_freq: Frequency of creating checkpoints
+ * @sc_lseg_stime: Start time of the latest logical segment
+ * @sc_watermark: Watermark for the number of dirty buffers
+ * @sc_timer: Timer for segctord
+ * @sc_task: current thread of segctord
+ */
+struct nilfs_sc_info {
+ struct super_block *sc_super;
+ struct nilfs_root *sc_root;
+
+ unsigned long sc_nblk_inc;
+
+ struct list_head sc_dirty_files;
+ struct list_head sc_gc_inodes;
+ struct list_head sc_iput_queue;
+ struct work_struct sc_iput_work;
+
+ __u64 *sc_freesegs;
+ size_t sc_nfreesegs;
+
+ struct nilfs_inode_info *sc_dsync_inode;
+ loff_t sc_dsync_start;
+ loff_t sc_dsync_end;
+
+ /* Segment buffers */
+ struct list_head sc_segbufs;
+ struct list_head sc_write_logs;
+ unsigned long sc_segbuf_nblocks;
+ struct nilfs_segment_buffer *sc_curseg;
+
+ struct nilfs_cstage sc_stage;
+
+ struct nilfs_segsum_pointer sc_finfo_ptr;
+ struct nilfs_segsum_pointer sc_binfo_ptr;
+ unsigned long sc_blk_cnt;
+ unsigned long sc_datablk_cnt;
+ unsigned long sc_nblk_this_inc;
+ time64_t sc_seg_ctime;
+ __u64 sc_cno;
+ unsigned long sc_flags;
+
+ spinlock_t sc_state_lock;
+ unsigned long sc_state;
+ unsigned long sc_flush_request;
+
+ wait_queue_head_t sc_wait_request;
+ wait_queue_head_t sc_wait_daemon;
+
+ __u32 sc_seq_request;
+ __u32 sc_seq_accepted;
+ __u32 sc_seq_done;
+
+ int sc_sync;
+ unsigned long sc_interval;
+ unsigned long sc_mjcp_freq;
+ unsigned long sc_lseg_stime; /* in 1/HZ seconds */
+ unsigned long sc_watermark;
+
+ struct timer_list sc_timer;
+ struct task_struct *sc_task;
+};
+
+/* sc_flags */
+enum {
+ NILFS_SC_DIRTY, /* One or more dirty meta-data blocks exist */
+ NILFS_SC_UNCLOSED, /* Logical segment is not closed */
+ NILFS_SC_SUPER_ROOT, /* The latest segment has a super root */
+ NILFS_SC_PRIOR_FLUSH, /*
+ * Requesting immediate flush without making a
+ * checkpoint
+ */
+ NILFS_SC_HAVE_DELTA, /*
+ * Next checkpoint will have update of files
+ * other than DAT, cpfile, sufile, or files
+ * moved by GC.
+ */
+};
+
+/* sc_state */
+#define NILFS_SEGCTOR_COMMIT 0x0004 /* committed transaction exists */
+
+/*
+ * Constant parameters
+ */
+#define NILFS_SC_CLEANUP_RETRY 3 /*
+ * Retry count of construction when
+ * destroying segctord
+ */
+
+/*
+ * Default values of timeout, in seconds.
+ */
+#define NILFS_SC_DEFAULT_TIMEOUT 5 /*
+ * Timeout value of dirty blocks.
+ * It triggers construction of a
+ * logical segment with a super root.
+ */
+#define NILFS_SC_DEFAULT_SR_FREQ 30 /*
+ * Maximum frequency of super root
+ * creation
+ */
+
+/*
+ * The default threshold amount of data, in block counts.
+ */
+#define NILFS_SC_DEFAULT_WATERMARK 3600
+
+/* super.c */
+extern struct kmem_cache *nilfs_transaction_cachep;
+
+/* segment.c */
+extern void nilfs_relax_pressure_in_lock(struct super_block *);
+
+extern int nilfs_construct_segment(struct super_block *);
+extern int nilfs_construct_dsync_segment(struct super_block *, struct inode *,
+ loff_t, loff_t);
+extern int nilfs_clean_segments(struct super_block *, struct nilfs_argv *,
+ void **);
+
+int nilfs_attach_log_writer(struct super_block *sb, struct nilfs_root *root);
+void nilfs_detach_log_writer(struct super_block *sb);
+
+/* recovery.c */
+extern int nilfs_read_super_root_block(struct the_nilfs *, sector_t,
+ struct buffer_head **, int);
+extern int nilfs_search_super_root(struct the_nilfs *,
+ struct nilfs_recovery_info *);
+int nilfs_salvage_orphan_logs(struct the_nilfs *nilfs, struct super_block *sb,
+ struct nilfs_recovery_info *ri);
+extern void nilfs_dispose_segment_list(struct list_head *);
+
+#endif /* _NILFS_SEGMENT_H */
diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c
new file mode 100644
index 000000000..eceedca02
--- /dev/null
+++ b/fs/nilfs2/sufile.c
@@ -0,0 +1,1273 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS segment usage file.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ * Revised by Ryusuke Konishi.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/buffer_head.h>
+#include <linux/errno.h>
+#include "mdt.h"
+#include "sufile.h"
+
+#include <trace/events/nilfs2.h>
+
+/**
+ * struct nilfs_sufile_info - on-memory private data of sufile
+ * @mi: on-memory private data of metadata file
+ * @ncleansegs: number of clean segments
+ * @allocmin: lower limit of allocatable segment range
+ * @allocmax: upper limit of allocatable segment range
+ */
+struct nilfs_sufile_info {
+ struct nilfs_mdt_info mi;
+ unsigned long ncleansegs;/* number of clean segments */
+ __u64 allocmin; /* lower limit of allocatable segment range */
+ __u64 allocmax; /* upper limit of allocatable segment range */
+};
+
+static inline struct nilfs_sufile_info *NILFS_SUI(struct inode *sufile)
+{
+ return (struct nilfs_sufile_info *)NILFS_MDT(sufile);
+}
+
+static inline unsigned long
+nilfs_sufile_segment_usages_per_block(const struct inode *sufile)
+{
+ return NILFS_MDT(sufile)->mi_entries_per_block;
+}
+
+static unsigned long
+nilfs_sufile_get_blkoff(const struct inode *sufile, __u64 segnum)
+{
+ __u64 t = segnum + NILFS_MDT(sufile)->mi_first_entry_offset;
+
+ t = div64_ul(t, nilfs_sufile_segment_usages_per_block(sufile));
+ return (unsigned long)t;
+}
+
+static unsigned long
+nilfs_sufile_get_offset(const struct inode *sufile, __u64 segnum)
+{
+ __u64 t = segnum + NILFS_MDT(sufile)->mi_first_entry_offset;
+
+ return do_div(t, nilfs_sufile_segment_usages_per_block(sufile));
+}
+
+static unsigned long
+nilfs_sufile_segment_usages_in_block(const struct inode *sufile, __u64 curr,
+ __u64 max)
+{
+ return min_t(unsigned long,
+ nilfs_sufile_segment_usages_per_block(sufile) -
+ nilfs_sufile_get_offset(sufile, curr),
+ max - curr + 1);
+}
+
+/**
+ * nilfs_sufile_segment_usage_offset - calculate the byte offset of a segment
+ * usage entry in the folio containing it
+ * @sufile: segment usage file inode
+ * @segnum: number of segment usage
+ * @bh: buffer head of block containing segment usage indexed by @segnum
+ *
+ * Return: Byte offset in the folio of the segment usage entry.
+ */
+static size_t nilfs_sufile_segment_usage_offset(const struct inode *sufile,
+ __u64 segnum,
+ struct buffer_head *bh)
+{
+ return offset_in_folio(bh->b_folio, bh->b_data) +
+ nilfs_sufile_get_offset(sufile, segnum) *
+ NILFS_MDT(sufile)->mi_entry_size;
+}
+
+static int nilfs_sufile_get_header_block(struct inode *sufile,
+ struct buffer_head **bhp)
+{
+ int err = nilfs_mdt_get_block(sufile, 0, 0, NULL, bhp);
+
+ if (unlikely(err == -ENOENT)) {
+ nilfs_error(sufile->i_sb,
+ "missing header block in segment usage metadata");
+ err = -EIO;
+ }
+ return err;
+}
+
+static inline int
+nilfs_sufile_get_segment_usage_block(struct inode *sufile, __u64 segnum,
+ int create, struct buffer_head **bhp)
+{
+ return nilfs_mdt_get_block(sufile,
+ nilfs_sufile_get_blkoff(sufile, segnum),
+ create, NULL, bhp);
+}
+
+static int nilfs_sufile_delete_segment_usage_block(struct inode *sufile,
+ __u64 segnum)
+{
+ return nilfs_mdt_delete_block(sufile,
+ nilfs_sufile_get_blkoff(sufile, segnum));
+}
+
+static void nilfs_sufile_mod_counter(struct buffer_head *header_bh,
+ u64 ncleanadd, u64 ndirtyadd)
+{
+ struct nilfs_sufile_header *header;
+
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->sh_ncleansegs, ncleanadd);
+ le64_add_cpu(&header->sh_ndirtysegs, ndirtyadd);
+ kunmap_local(header);
+
+ mark_buffer_dirty(header_bh);
+}
+
+/**
+ * nilfs_sufile_get_ncleansegs - return the number of clean segments
+ * @sufile: inode of segment usage file
+ *
+ * Return: Number of clean segments.
+ */
+unsigned long nilfs_sufile_get_ncleansegs(struct inode *sufile)
+{
+ return NILFS_SUI(sufile)->ncleansegs;
+}
+
+/**
+ * nilfs_sufile_updatev - modify multiple segment usages at a time
+ * @sufile: inode of segment usage file
+ * @segnumv: array of segment numbers
+ * @nsegs: size of @segnumv array
+ * @create: creation flag
+ * @ndone: place to store number of modified segments on @segnumv
+ * @dofunc: primitive operation for the update
+ *
+ * Description: nilfs_sufile_updatev() repeatedly calls @dofunc
+ * against the given array of segments. The @dofunc is called with
+ * buffers of a header block and the sufile block in which the target
+ * segment usage entry is contained. If @ndone is given, the number
+ * of successfully modified segments from the head is stored in the
+ * place @ndone points to.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid segment usage number
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOENT - Given segment usage is in hole block (may be returned if
+ * @create is zero)
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_sufile_updatev(struct inode *sufile, __u64 *segnumv, size_t nsegs,
+ int create, size_t *ndone,
+ void (*dofunc)(struct inode *, __u64,
+ struct buffer_head *,
+ struct buffer_head *))
+{
+ struct buffer_head *header_bh, *bh;
+ unsigned long blkoff, prev_blkoff;
+ __u64 *seg;
+ size_t nerr = 0, n = 0;
+ int ret = 0;
+
+ if (unlikely(nsegs == 0))
+ goto out;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+ for (seg = segnumv; seg < segnumv + nsegs; seg++) {
+ if (unlikely(*seg >= nilfs_sufile_get_nsegments(sufile))) {
+ nilfs_warn(sufile->i_sb,
+ "%s: invalid segment number: %llu",
+ __func__, (unsigned long long)*seg);
+ nerr++;
+ }
+ }
+ if (nerr > 0) {
+ ret = -EINVAL;
+ goto out_sem;
+ }
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+
+ seg = segnumv;
+ blkoff = nilfs_sufile_get_blkoff(sufile, *seg);
+ ret = nilfs_mdt_get_block(sufile, blkoff, create, NULL, &bh);
+ if (ret < 0)
+ goto out_header;
+
+ for (;;) {
+ dofunc(sufile, *seg, header_bh, bh);
+
+ if (++seg >= segnumv + nsegs)
+ break;
+ prev_blkoff = blkoff;
+ blkoff = nilfs_sufile_get_blkoff(sufile, *seg);
+ if (blkoff == prev_blkoff)
+ continue;
+
+ /* get different block */
+ brelse(bh);
+ ret = nilfs_mdt_get_block(sufile, blkoff, create, NULL, &bh);
+ if (unlikely(ret < 0))
+ goto out_header;
+ }
+ brelse(bh);
+
+ out_header:
+ n = seg - segnumv;
+ brelse(header_bh);
+ out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ out:
+ if (ndone)
+ *ndone = n;
+ return ret;
+}
+
+int nilfs_sufile_update(struct inode *sufile, __u64 segnum, int create,
+ void (*dofunc)(struct inode *, __u64,
+ struct buffer_head *,
+ struct buffer_head *))
+{
+ struct buffer_head *header_bh, *bh;
+ int ret;
+
+ if (unlikely(segnum >= nilfs_sufile_get_nsegments(sufile))) {
+ nilfs_warn(sufile->i_sb, "%s: invalid segment number: %llu",
+ __func__, (unsigned long long)segnum);
+ return -EINVAL;
+ }
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, create, &bh);
+ if (!ret) {
+ dofunc(sufile, segnum, header_bh, bh);
+ brelse(bh);
+ }
+ brelse(header_bh);
+
+ out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_set_alloc_range - limit range of segment to be allocated
+ * @sufile: inode of segment usage file
+ * @start: minimum segment number of allocatable region (inclusive)
+ * @end: maximum segment number of allocatable region (inclusive)
+ *
+ * Return: 0 on success, or %-ERANGE if segment range is invalid.
+ */
+int nilfs_sufile_set_alloc_range(struct inode *sufile, __u64 start, __u64 end)
+{
+ struct nilfs_sufile_info *sui = NILFS_SUI(sufile);
+ __u64 nsegs;
+ int ret = -ERANGE;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+ nsegs = nilfs_sufile_get_nsegments(sufile);
+
+ if (start <= end && end < nsegs) {
+ sui->allocmin = start;
+ sui->allocmax = end;
+ ret = 0;
+ }
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_alloc - allocate a segment
+ * @sufile: inode of segment usage file
+ * @segnump: pointer to segment number
+ *
+ * Description: nilfs_sufile_alloc() allocates a clean segment, and stores
+ * its segment number in the place pointed to by @segnump.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - No clean segment left.
+ */
+int nilfs_sufile_alloc(struct inode *sufile, __u64 *segnump)
+{
+ struct buffer_head *header_bh, *su_bh;
+ struct nilfs_sufile_header *header;
+ struct nilfs_segment_usage *su;
+ struct nilfs_sufile_info *sui = NILFS_SUI(sufile);
+ size_t susz = NILFS_MDT(sufile)->mi_entry_size;
+ __u64 segnum, maxsegnum, last_alloc;
+ size_t offset;
+ void *kaddr;
+ unsigned long nsegments, nsus, cnt;
+ int ret, j;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ last_alloc = le64_to_cpu(header->sh_last_alloc);
+ kunmap_local(header);
+
+ nsegments = nilfs_sufile_get_nsegments(sufile);
+ maxsegnum = sui->allocmax;
+ segnum = last_alloc + 1;
+ if (segnum < sui->allocmin || segnum > sui->allocmax)
+ segnum = sui->allocmin;
+
+ for (cnt = 0; cnt < nsegments; cnt += nsus) {
+ if (segnum > maxsegnum) {
+ if (cnt < sui->allocmax - sui->allocmin + 1) {
+ /*
+ * wrap around in the limited region.
+ * if allocation started from
+ * sui->allocmin, this never happens.
+ */
+ segnum = sui->allocmin;
+ maxsegnum = last_alloc;
+ } else if (segnum > sui->allocmin &&
+ sui->allocmax + 1 < nsegments) {
+ segnum = sui->allocmax + 1;
+ maxsegnum = nsegments - 1;
+ } else if (sui->allocmin > 0) {
+ segnum = 0;
+ maxsegnum = sui->allocmin - 1;
+ } else {
+ break; /* never happens */
+ }
+ }
+ trace_nilfs2_segment_usage_check(sufile, segnum, cnt);
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 1,
+ &su_bh);
+ if (ret < 0)
+ goto out_header;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum,
+ su_bh);
+ su = kaddr = kmap_local_folio(su_bh->b_folio, offset);
+
+ nsus = nilfs_sufile_segment_usages_in_block(
+ sufile, segnum, maxsegnum);
+ for (j = 0; j < nsus; j++, su = (void *)su + susz, segnum++) {
+ if (!nilfs_segment_usage_clean(su))
+ continue;
+ /* found a clean segment */
+ nilfs_segment_usage_set_dirty(su);
+ kunmap_local(kaddr);
+
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ le64_add_cpu(&header->sh_ncleansegs, -1);
+ le64_add_cpu(&header->sh_ndirtysegs, 1);
+ header->sh_last_alloc = cpu_to_le64(segnum);
+ kunmap_local(header);
+
+ sui->ncleansegs--;
+ mark_buffer_dirty(header_bh);
+ mark_buffer_dirty(su_bh);
+ nilfs_mdt_mark_dirty(sufile);
+ brelse(su_bh);
+ *segnump = segnum;
+
+ trace_nilfs2_segment_usage_allocated(sufile, segnum);
+
+ goto out_header;
+ }
+
+ kunmap_local(kaddr);
+ brelse(su_bh);
+ }
+
+ /* no segments left */
+ ret = -ENOSPC;
+
+ out_header:
+ brelse(header_bh);
+
+ out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+void nilfs_sufile_do_cancel_free(struct inode *sufile, __u64 segnum,
+ struct buffer_head *header_bh,
+ struct buffer_head *su_bh)
+{
+ struct nilfs_segment_usage *su;
+ size_t offset;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, su_bh);
+ su = kmap_local_folio(su_bh->b_folio, offset);
+ if (unlikely(!nilfs_segment_usage_clean(su))) {
+ nilfs_warn(sufile->i_sb, "%s: segment %llu must be clean",
+ __func__, (unsigned long long)segnum);
+ kunmap_local(su);
+ return;
+ }
+ nilfs_segment_usage_set_dirty(su);
+ kunmap_local(su);
+
+ nilfs_sufile_mod_counter(header_bh, -1, 1);
+ NILFS_SUI(sufile)->ncleansegs--;
+
+ mark_buffer_dirty(su_bh);
+ nilfs_mdt_mark_dirty(sufile);
+}
+
+void nilfs_sufile_do_scrap(struct inode *sufile, __u64 segnum,
+ struct buffer_head *header_bh,
+ struct buffer_head *su_bh)
+{
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ int clean, dirty;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, su_bh);
+ su = kmap_local_folio(su_bh->b_folio, offset);
+ if (su->su_flags == cpu_to_le32(BIT(NILFS_SEGMENT_USAGE_DIRTY)) &&
+ su->su_nblocks == cpu_to_le32(0)) {
+ kunmap_local(su);
+ return;
+ }
+ clean = nilfs_segment_usage_clean(su);
+ dirty = nilfs_segment_usage_dirty(su);
+
+ /* make the segment garbage */
+ su->su_lastmod = cpu_to_le64(0);
+ su->su_nblocks = cpu_to_le32(0);
+ su->su_flags = cpu_to_le32(BIT(NILFS_SEGMENT_USAGE_DIRTY));
+ kunmap_local(su);
+
+ nilfs_sufile_mod_counter(header_bh, clean ? (u64)-1 : 0, dirty ? 0 : 1);
+ NILFS_SUI(sufile)->ncleansegs -= clean;
+
+ mark_buffer_dirty(su_bh);
+ nilfs_mdt_mark_dirty(sufile);
+}
+
+void nilfs_sufile_do_free(struct inode *sufile, __u64 segnum,
+ struct buffer_head *header_bh,
+ struct buffer_head *su_bh)
+{
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ int sudirty;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, su_bh);
+ su = kmap_local_folio(su_bh->b_folio, offset);
+ if (nilfs_segment_usage_clean(su)) {
+ nilfs_warn(sufile->i_sb, "%s: segment %llu is already clean",
+ __func__, (unsigned long long)segnum);
+ kunmap_local(su);
+ return;
+ }
+ if (unlikely(nilfs_segment_usage_error(su)))
+ nilfs_warn(sufile->i_sb, "free segment %llu marked in error",
+ (unsigned long long)segnum);
+
+ sudirty = nilfs_segment_usage_dirty(su);
+ if (unlikely(!sudirty))
+ nilfs_warn(sufile->i_sb, "free unallocated segment %llu",
+ (unsigned long long)segnum);
+
+ nilfs_segment_usage_set_clean(su);
+ kunmap_local(su);
+ mark_buffer_dirty(su_bh);
+
+ nilfs_sufile_mod_counter(header_bh, 1, sudirty ? (u64)-1 : 0);
+ NILFS_SUI(sufile)->ncleansegs++;
+
+ nilfs_mdt_mark_dirty(sufile);
+
+ trace_nilfs2_segment_usage_freed(sufile, segnum);
+}
+
+/**
+ * nilfs_sufile_mark_dirty - mark the buffer having a segment usage dirty
+ * @sufile: inode of segment usage file
+ * @segnum: segment number
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_sufile_mark_dirty(struct inode *sufile, __u64 segnum)
+{
+ struct buffer_head *bh;
+ size_t offset;
+ struct nilfs_segment_usage *su;
+ int ret;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0, &bh);
+ if (unlikely(ret)) {
+ if (ret == -ENOENT) {
+ nilfs_error(sufile->i_sb,
+ "segment usage for segment %llu is unreadable due to a hole block",
+ (unsigned long long)segnum);
+ ret = -EIO;
+ }
+ goto out_sem;
+ }
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, bh);
+ su = kmap_local_folio(bh->b_folio, offset);
+ if (unlikely(nilfs_segment_usage_error(su))) {
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+
+ kunmap_local(su);
+ brelse(bh);
+ if (nilfs_segment_is_active(nilfs, segnum)) {
+ nilfs_error(sufile->i_sb,
+ "active segment %llu is erroneous",
+ (unsigned long long)segnum);
+ } else {
+ /*
+ * Segments marked erroneous are never allocated by
+ * nilfs_sufile_alloc(); only active segments, ie,
+ * the segments indexed by ns_segnum or ns_nextnum,
+ * can be erroneous here.
+ */
+ WARN_ON_ONCE(1);
+ }
+ ret = -EIO;
+ } else {
+ nilfs_segment_usage_set_dirty(su);
+ kunmap_local(su);
+ mark_buffer_dirty(bh);
+ nilfs_mdt_mark_dirty(sufile);
+ brelse(bh);
+ }
+out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_set_segment_usage - set usage of a segment
+ * @sufile: inode of segment usage file
+ * @segnum: segment number
+ * @nblocks: number of live blocks in the segment
+ * @modtime: modification time (option)
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_sufile_set_segment_usage(struct inode *sufile, __u64 segnum,
+ unsigned long nblocks, time64_t modtime)
+{
+ struct buffer_head *bh;
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ int ret;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0, &bh);
+ if (ret < 0)
+ goto out_sem;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, bh);
+ su = kmap_local_folio(bh->b_folio, offset);
+ if (modtime) {
+ /*
+ * Check segusage error and set su_lastmod only when updating
+ * this entry with a valid timestamp, not for cancellation.
+ */
+ WARN_ON_ONCE(nilfs_segment_usage_error(su));
+ su->su_lastmod = cpu_to_le64(modtime);
+ }
+ su->su_nblocks = cpu_to_le32(nblocks);
+ kunmap_local(su);
+
+ mark_buffer_dirty(bh);
+ nilfs_mdt_mark_dirty(sufile);
+ brelse(bh);
+
+ out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_get_stat - get segment usage statistics
+ * @sufile: inode of segment usage file
+ * @sustat: pointer to a structure of segment usage statistics
+ *
+ * Description: nilfs_sufile_get_stat() retrieves segment usage statistics
+ * and stores them in the location pointed to by @sustat.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+int nilfs_sufile_get_stat(struct inode *sufile, struct nilfs_sustat *sustat)
+{
+ struct buffer_head *header_bh;
+ struct nilfs_sufile_header *header;
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ int ret;
+
+ down_read(&NILFS_MDT(sufile)->mi_sem);
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ sustat->ss_nsegs = nilfs_sufile_get_nsegments(sufile);
+ sustat->ss_ncleansegs = le64_to_cpu(header->sh_ncleansegs);
+ sustat->ss_ndirtysegs = le64_to_cpu(header->sh_ndirtysegs);
+ sustat->ss_ctime = nilfs->ns_ctime;
+ sustat->ss_nongc_ctime = nilfs->ns_nongc_ctime;
+ spin_lock(&nilfs->ns_last_segment_lock);
+ sustat->ss_prot_seq = nilfs->ns_prot_seq;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+ kunmap_local(header);
+ brelse(header_bh);
+
+ out_sem:
+ up_read(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+void nilfs_sufile_do_set_error(struct inode *sufile, __u64 segnum,
+ struct buffer_head *header_bh,
+ struct buffer_head *su_bh)
+{
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ int suclean;
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum, su_bh);
+ su = kmap_local_folio(su_bh->b_folio, offset);
+ if (nilfs_segment_usage_error(su)) {
+ kunmap_local(su);
+ return;
+ }
+ suclean = nilfs_segment_usage_clean(su);
+ nilfs_segment_usage_set_error(su);
+ kunmap_local(su);
+
+ if (suclean) {
+ nilfs_sufile_mod_counter(header_bh, -1, 0);
+ NILFS_SUI(sufile)->ncleansegs--;
+ }
+ mark_buffer_dirty(su_bh);
+ nilfs_mdt_mark_dirty(sufile);
+}
+
+/**
+ * nilfs_sufile_truncate_range - truncate range of segment array
+ * @sufile: inode of segment usage file
+ * @start: start segment number (inclusive)
+ * @end: end segment number (inclusive)
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EBUSY - Dirty or active segments are present in the range.
+ * * %-EINVAL - Invalid number of segments specified.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_sufile_truncate_range(struct inode *sufile,
+ __u64 start, __u64 end)
+{
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ struct buffer_head *header_bh;
+ struct buffer_head *su_bh;
+ struct nilfs_segment_usage *su, *su2;
+ size_t susz = NILFS_MDT(sufile)->mi_entry_size;
+ unsigned long segusages_per_block;
+ unsigned long nsegs, ncleaned;
+ __u64 segnum;
+ size_t offset;
+ ssize_t n, nc;
+ int ret;
+ int j;
+
+ nsegs = nilfs_sufile_get_nsegments(sufile);
+
+ ret = -EINVAL;
+ if (start > end || start >= nsegs)
+ goto out;
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out;
+
+ segusages_per_block = nilfs_sufile_segment_usages_per_block(sufile);
+ ncleaned = 0;
+
+ for (segnum = start; segnum <= end; segnum += n) {
+ n = min_t(unsigned long,
+ segusages_per_block -
+ nilfs_sufile_get_offset(sufile, segnum),
+ end - segnum + 1);
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0,
+ &su_bh);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ goto out_header;
+ /* hole */
+ continue;
+ }
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum,
+ su_bh);
+ su = kmap_local_folio(su_bh->b_folio, offset);
+ su2 = su;
+ for (j = 0; j < n; j++, su = (void *)su + susz) {
+ if ((le32_to_cpu(su->su_flags) &
+ ~BIT(NILFS_SEGMENT_USAGE_ERROR)) ||
+ nilfs_segment_is_active(nilfs, segnum + j)) {
+ ret = -EBUSY;
+ kunmap_local(su2);
+ brelse(su_bh);
+ goto out_header;
+ }
+ }
+ nc = 0;
+ for (su = su2, j = 0; j < n; j++, su = (void *)su + susz) {
+ if (nilfs_segment_usage_error(su)) {
+ nilfs_segment_usage_set_clean(su);
+ nc++;
+ }
+ }
+ kunmap_local(su2);
+ if (nc > 0) {
+ mark_buffer_dirty(su_bh);
+ ncleaned += nc;
+ }
+ brelse(su_bh);
+
+ if (n == segusages_per_block) {
+ /* make hole */
+ nilfs_sufile_delete_segment_usage_block(sufile, segnum);
+ }
+ }
+ ret = 0;
+
+out_header:
+ if (ncleaned > 0) {
+ NILFS_SUI(sufile)->ncleansegs += ncleaned;
+ nilfs_sufile_mod_counter(header_bh, ncleaned, 0);
+ nilfs_mdt_mark_dirty(sufile);
+ }
+ brelse(header_bh);
+out:
+ return ret;
+}
+
+/**
+ * nilfs_sufile_resize - resize segment array
+ * @sufile: inode of segment usage file
+ * @newnsegs: new number of segments
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EBUSY - Dirty or active segments exist in the region to be truncated.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-ENOSPC - Enough free space is not left for shrinking.
+ */
+int nilfs_sufile_resize(struct inode *sufile, __u64 newnsegs)
+{
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ struct buffer_head *header_bh;
+ struct nilfs_sufile_header *header;
+ struct nilfs_sufile_info *sui = NILFS_SUI(sufile);
+ unsigned long nsegs, nrsvsegs;
+ int ret = 0;
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+
+ nsegs = nilfs_sufile_get_nsegments(sufile);
+ if (nsegs == newnsegs)
+ goto out;
+
+ ret = -ENOSPC;
+ nrsvsegs = nilfs_nrsvsegs(nilfs, newnsegs);
+ if (newnsegs < nsegs && nsegs - newnsegs + nrsvsegs > sui->ncleansegs)
+ goto out;
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out;
+
+ if (newnsegs > nsegs) {
+ sui->ncleansegs += newnsegs - nsegs;
+ } else /* newnsegs < nsegs */ {
+ ret = nilfs_sufile_truncate_range(sufile, newnsegs, nsegs - 1);
+ if (ret < 0)
+ goto out_header;
+
+ sui->ncleansegs -= nsegs - newnsegs;
+
+ /*
+ * If the sufile is successfully truncated, immediately adjust
+ * the segment allocation space while locking the semaphore
+ * "mi_sem" so that nilfs_sufile_alloc() never allocates
+ * segments in the truncated space.
+ */
+ sui->allocmax = newnsegs - 1;
+ sui->allocmin = 0;
+ }
+
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ header->sh_ncleansegs = cpu_to_le64(sui->ncleansegs);
+ kunmap_local(header);
+
+ mark_buffer_dirty(header_bh);
+ nilfs_mdt_mark_dirty(sufile);
+ nilfs_set_nsegments(nilfs, newnsegs);
+
+out_header:
+ brelse(header_bh);
+out:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_get_suinfo - get segment usage information
+ * @sufile: inode of segment usage file
+ * @segnum: segment number to start looking
+ * @buf: array of suinfo
+ * @sisz: byte size of suinfo
+ * @nsi: size of suinfo array
+ *
+ * Return: Count of segment usage info items stored in the output buffer on
+ * success, or one of the following negative error codes on failure:
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf,
+ unsigned int sisz, size_t nsi)
+{
+ struct buffer_head *su_bh;
+ struct nilfs_segment_usage *su;
+ struct nilfs_suinfo *si = buf;
+ size_t susz = NILFS_MDT(sufile)->mi_entry_size;
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ size_t offset;
+ void *kaddr;
+ unsigned long nsegs, segusages_per_block;
+ ssize_t n;
+ int ret, i, j;
+
+ down_read(&NILFS_MDT(sufile)->mi_sem);
+
+ segusages_per_block = nilfs_sufile_segment_usages_per_block(sufile);
+ nsegs = min_t(unsigned long,
+ nilfs_sufile_get_nsegments(sufile) - segnum,
+ nsi);
+ for (i = 0; i < nsegs; i += n, segnum += n) {
+ n = min_t(unsigned long,
+ segusages_per_block -
+ nilfs_sufile_get_offset(sufile, segnum),
+ nsegs - i);
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0,
+ &su_bh);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ goto out;
+ /* hole */
+ memset(si, 0, sisz * n);
+ si = (void *)si + sisz * n;
+ continue;
+ }
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum,
+ su_bh);
+ su = kaddr = kmap_local_folio(su_bh->b_folio, offset);
+ for (j = 0; j < n;
+ j++, su = (void *)su + susz, si = (void *)si + sisz) {
+ si->sui_lastmod = le64_to_cpu(su->su_lastmod);
+ si->sui_nblocks = le32_to_cpu(su->su_nblocks);
+ si->sui_flags = le32_to_cpu(su->su_flags) &
+ ~BIT(NILFS_SEGMENT_USAGE_ACTIVE);
+ if (nilfs_segment_is_active(nilfs, segnum + j))
+ si->sui_flags |=
+ BIT(NILFS_SEGMENT_USAGE_ACTIVE);
+ }
+ kunmap_local(kaddr);
+ brelse(su_bh);
+ }
+ ret = nsegs;
+
+ out:
+ up_read(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_set_suinfo - sets segment usage info
+ * @sufile: inode of segment usage file
+ * @buf: array of suinfo_update
+ * @supsz: byte size of suinfo_update
+ * @nsup: size of suinfo_update array
+ *
+ * Description: Takes an array of nilfs_suinfo_update structs and updates
+ * segment usage accordingly. Only the fields indicated by the sup_flags
+ * are updated.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid values in input (segment number, flags or nblocks).
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+ssize_t nilfs_sufile_set_suinfo(struct inode *sufile, void *buf,
+ unsigned int supsz, size_t nsup)
+{
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ struct buffer_head *header_bh, *bh;
+ struct nilfs_suinfo_update *sup, *supend = buf + supsz * nsup;
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ unsigned long blkoff, prev_blkoff;
+ int cleansi, cleansu, dirtysi, dirtysu;
+ long ncleaned = 0, ndirtied = 0;
+ int ret = 0;
+
+ if (unlikely(nsup == 0))
+ return ret;
+
+ for (sup = buf; sup < supend; sup = (void *)sup + supsz) {
+ if (sup->sup_segnum >= nilfs->ns_nsegments
+ || (sup->sup_flags &
+ (~0UL << __NR_NILFS_SUINFO_UPDATE_FIELDS))
+ || (nilfs_suinfo_update_nblocks(sup) &&
+ sup->sup_sui.sui_nblocks >
+ nilfs->ns_blocks_per_segment))
+ return -EINVAL;
+ }
+
+ down_write(&NILFS_MDT(sufile)->mi_sem);
+
+ ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+ if (ret < 0)
+ goto out_sem;
+
+ sup = buf;
+ blkoff = nilfs_sufile_get_blkoff(sufile, sup->sup_segnum);
+ ret = nilfs_mdt_get_block(sufile, blkoff, 1, NULL, &bh);
+ if (ret < 0)
+ goto out_header;
+
+ for (;;) {
+ offset = nilfs_sufile_segment_usage_offset(
+ sufile, sup->sup_segnum, bh);
+ su = kmap_local_folio(bh->b_folio, offset);
+
+ if (nilfs_suinfo_update_lastmod(sup))
+ su->su_lastmod = cpu_to_le64(sup->sup_sui.sui_lastmod);
+
+ if (nilfs_suinfo_update_nblocks(sup))
+ su->su_nblocks = cpu_to_le32(sup->sup_sui.sui_nblocks);
+
+ if (nilfs_suinfo_update_flags(sup)) {
+ /*
+ * Active flag is a virtual flag projected by running
+ * nilfs kernel code - drop it not to write it to
+ * disk.
+ */
+ sup->sup_sui.sui_flags &=
+ ~BIT(NILFS_SEGMENT_USAGE_ACTIVE);
+
+ cleansi = nilfs_suinfo_clean(&sup->sup_sui);
+ cleansu = nilfs_segment_usage_clean(su);
+ dirtysi = nilfs_suinfo_dirty(&sup->sup_sui);
+ dirtysu = nilfs_segment_usage_dirty(su);
+
+ if (cleansi && !cleansu)
+ ++ncleaned;
+ else if (!cleansi && cleansu)
+ --ncleaned;
+
+ if (dirtysi && !dirtysu)
+ ++ndirtied;
+ else if (!dirtysi && dirtysu)
+ --ndirtied;
+
+ su->su_flags = cpu_to_le32(sup->sup_sui.sui_flags);
+ }
+
+ kunmap_local(su);
+
+ sup = (void *)sup + supsz;
+ if (sup >= supend)
+ break;
+
+ prev_blkoff = blkoff;
+ blkoff = nilfs_sufile_get_blkoff(sufile, sup->sup_segnum);
+ if (blkoff == prev_blkoff)
+ continue;
+
+ /* get different block */
+ mark_buffer_dirty(bh);
+ put_bh(bh);
+ ret = nilfs_mdt_get_block(sufile, blkoff, 1, NULL, &bh);
+ if (unlikely(ret < 0))
+ goto out_mark;
+ }
+ mark_buffer_dirty(bh);
+ put_bh(bh);
+
+ out_mark:
+ if (ncleaned || ndirtied) {
+ nilfs_sufile_mod_counter(header_bh, (u64)ncleaned,
+ (u64)ndirtied);
+ NILFS_SUI(sufile)->ncleansegs += ncleaned;
+ }
+ nilfs_mdt_mark_dirty(sufile);
+ out_header:
+ put_bh(header_bh);
+ out_sem:
+ up_write(&NILFS_MDT(sufile)->mi_sem);
+ return ret;
+}
+
+/**
+ * nilfs_sufile_trim_fs() - trim ioctl handle function
+ * @sufile: inode of segment usage file
+ * @range: fstrim_range structure
+ *
+ * start: First Byte to trim
+ * len: number of Bytes to trim from start
+ * minlen: minimum extent length in Bytes
+ *
+ * Decription: nilfs_sufile_trim_fs goes through all segments containing bytes
+ * from start to start+len. start is rounded up to the next block boundary
+ * and start+len is rounded down. For each clean segment blkdev_issue_discard
+ * function is invoked.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range)
+{
+ struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+ struct buffer_head *su_bh;
+ struct nilfs_segment_usage *su;
+ size_t offset;
+ void *kaddr;
+ size_t n, i, susz = NILFS_MDT(sufile)->mi_entry_size;
+ sector_t seg_start, seg_end, start_block, end_block;
+ sector_t start = 0, nblocks = 0;
+ u64 segnum, segnum_end, minlen, len, max_blocks, ndiscarded = 0;
+ int ret = 0;
+ unsigned int sects_per_block;
+
+ sects_per_block = (1 << nilfs->ns_blocksize_bits) /
+ bdev_logical_block_size(nilfs->ns_bdev);
+ len = range->len >> nilfs->ns_blocksize_bits;
+ minlen = range->minlen >> nilfs->ns_blocksize_bits;
+ max_blocks = ((u64)nilfs->ns_nsegments * nilfs->ns_blocks_per_segment);
+
+ if (!len || range->start >= max_blocks << nilfs->ns_blocksize_bits)
+ return -EINVAL;
+
+ start_block = (range->start + nilfs->ns_blocksize - 1) >>
+ nilfs->ns_blocksize_bits;
+
+ /*
+ * range->len can be very large (actually, it is set to
+ * ULLONG_MAX by default) - truncate upper end of the range
+ * carefully so as not to overflow.
+ */
+ if (max_blocks - start_block < len)
+ end_block = max_blocks - 1;
+ else
+ end_block = start_block + len - 1;
+
+ if (end_block < nilfs->ns_first_data_block)
+ goto out;
+
+ segnum = nilfs_get_segnum_of_block(nilfs, start_block);
+ segnum_end = nilfs_get_segnum_of_block(nilfs, end_block);
+
+ down_read(&NILFS_MDT(sufile)->mi_sem);
+
+ while (segnum <= segnum_end) {
+ n = nilfs_sufile_segment_usages_in_block(sufile, segnum,
+ segnum_end);
+
+ ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0,
+ &su_bh);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ goto out_sem;
+ /* hole */
+ segnum += n;
+ continue;
+ }
+
+ offset = nilfs_sufile_segment_usage_offset(sufile, segnum,
+ su_bh);
+ su = kaddr = kmap_local_folio(su_bh->b_folio, offset);
+ for (i = 0; i < n; ++i, ++segnum, su = (void *)su + susz) {
+ if (!nilfs_segment_usage_clean(su))
+ continue;
+
+ nilfs_get_segment_range(nilfs, segnum, &seg_start,
+ &seg_end);
+
+ if (!nblocks) {
+ /* start new extent */
+ start = seg_start;
+ nblocks = seg_end - seg_start + 1;
+ continue;
+ }
+
+ if (start + nblocks == seg_start) {
+ /* add to previous extent */
+ nblocks += seg_end - seg_start + 1;
+ continue;
+ }
+
+ /* discard previous extent */
+ if (start < start_block) {
+ nblocks -= start_block - start;
+ start = start_block;
+ }
+
+ if (nblocks >= minlen) {
+ kunmap_local(kaddr);
+
+ ret = blkdev_issue_discard(nilfs->ns_bdev,
+ start * sects_per_block,
+ nblocks * sects_per_block,
+ GFP_NOFS);
+ if (ret < 0) {
+ put_bh(su_bh);
+ goto out_sem;
+ }
+
+ ndiscarded += nblocks;
+ offset = nilfs_sufile_segment_usage_offset(
+ sufile, segnum, su_bh);
+ su = kaddr = kmap_local_folio(su_bh->b_folio,
+ offset);
+ }
+
+ /* start new extent */
+ start = seg_start;
+ nblocks = seg_end - seg_start + 1;
+ }
+ kunmap_local(kaddr);
+ put_bh(su_bh);
+ }
+
+
+ if (nblocks) {
+ /* discard last extent */
+ if (start < start_block) {
+ nblocks -= start_block - start;
+ start = start_block;
+ }
+ if (start + nblocks > end_block + 1)
+ nblocks = end_block - start + 1;
+
+ if (nblocks >= minlen) {
+ ret = blkdev_issue_discard(nilfs->ns_bdev,
+ start * sects_per_block,
+ nblocks * sects_per_block,
+ GFP_NOFS);
+ if (!ret)
+ ndiscarded += nblocks;
+ }
+ }
+
+out_sem:
+ up_read(&NILFS_MDT(sufile)->mi_sem);
+
+out:
+ range->len = ndiscarded << nilfs->ns_blocksize_bits;
+ return ret;
+}
+
+/**
+ * nilfs_sufile_read - read or get sufile inode
+ * @sb: super block instance
+ * @susize: size of a segment usage entry
+ * @raw_inode: on-disk sufile inode
+ * @inodep: buffer to store the inode
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_sufile_read(struct super_block *sb, size_t susize,
+ struct nilfs_inode *raw_inode, struct inode **inodep)
+{
+ struct inode *sufile;
+ struct nilfs_sufile_info *sui;
+ struct buffer_head *header_bh;
+ struct nilfs_sufile_header *header;
+ int err;
+
+ if (susize > sb->s_blocksize) {
+ nilfs_err(sb, "too large segment usage size: %zu bytes",
+ susize);
+ return -EINVAL;
+ } else if (susize < NILFS_MIN_SEGMENT_USAGE_SIZE) {
+ nilfs_err(sb, "too small segment usage size: %zu bytes",
+ susize);
+ return -EINVAL;
+ }
+
+ sufile = nilfs_iget_locked(sb, NULL, NILFS_SUFILE_INO);
+ if (unlikely(!sufile))
+ return -ENOMEM;
+ if (!(inode_state_read_once(sufile) & I_NEW))
+ goto out;
+
+ err = nilfs_mdt_init(sufile, NILFS_MDT_GFP, sizeof(*sui));
+ if (err)
+ goto failed;
+
+ nilfs_mdt_set_entry_size(sufile, susize,
+ sizeof(struct nilfs_sufile_header));
+
+ err = nilfs_read_inode_common(sufile, raw_inode);
+ if (err)
+ goto failed;
+
+ err = nilfs_mdt_get_block(sufile, 0, 0, NULL, &header_bh);
+ if (unlikely(err)) {
+ if (err == -ENOENT) {
+ nilfs_err(sb,
+ "missing header block in segment usage metadata");
+ err = -EINVAL;
+ }
+ goto failed;
+ }
+
+ sui = NILFS_SUI(sufile);
+ header = kmap_local_folio(header_bh->b_folio, 0);
+ sui->ncleansegs = le64_to_cpu(header->sh_ncleansegs);
+ kunmap_local(header);
+ brelse(header_bh);
+
+ sui->allocmax = nilfs_sufile_get_nsegments(sufile) - 1;
+ sui->allocmin = 0;
+
+ unlock_new_inode(sufile);
+ out:
+ *inodep = sufile;
+ return 0;
+ failed:
+ iget_failed(sufile);
+ return err;
+}
diff --git a/fs/nilfs2/sufile.h b/fs/nilfs2/sufile.h
new file mode 100644
index 000000000..5888ed479
--- /dev/null
+++ b/fs/nilfs2/sufile.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS segment usage file.
+ *
+ * Copyright (C) 2006-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Koji Sato.
+ */
+
+#ifndef _NILFS_SUFILE_H
+#define _NILFS_SUFILE_H
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/buffer_head.h>
+#include "mdt.h"
+
+
+static inline unsigned long nilfs_sufile_get_nsegments(struct inode *sufile)
+{
+ return ((struct the_nilfs *)sufile->i_sb->s_fs_info)->ns_nsegments;
+}
+
+unsigned long nilfs_sufile_get_ncleansegs(struct inode *sufile);
+
+int nilfs_sufile_set_alloc_range(struct inode *sufile, __u64 start, __u64 end);
+int nilfs_sufile_alloc(struct inode *, __u64 *);
+int nilfs_sufile_mark_dirty(struct inode *sufile, __u64 segnum);
+int nilfs_sufile_set_segment_usage(struct inode *sufile, __u64 segnum,
+ unsigned long nblocks, time64_t modtime);
+int nilfs_sufile_get_stat(struct inode *, struct nilfs_sustat *);
+ssize_t nilfs_sufile_get_suinfo(struct inode *, __u64, void *, unsigned int,
+ size_t);
+ssize_t nilfs_sufile_set_suinfo(struct inode *, void *, unsigned int, size_t);
+
+int nilfs_sufile_updatev(struct inode *, __u64 *, size_t, int, size_t *,
+ void (*dofunc)(struct inode *, __u64,
+ struct buffer_head *,
+ struct buffer_head *));
+int nilfs_sufile_update(struct inode *, __u64, int,
+ void (*dofunc)(struct inode *, __u64,
+ struct buffer_head *,
+ struct buffer_head *));
+void nilfs_sufile_do_scrap(struct inode *, __u64, struct buffer_head *,
+ struct buffer_head *);
+void nilfs_sufile_do_free(struct inode *, __u64, struct buffer_head *,
+ struct buffer_head *);
+void nilfs_sufile_do_cancel_free(struct inode *, __u64, struct buffer_head *,
+ struct buffer_head *);
+void nilfs_sufile_do_set_error(struct inode *, __u64, struct buffer_head *,
+ struct buffer_head *);
+
+int nilfs_sufile_resize(struct inode *sufile, __u64 newnsegs);
+int nilfs_sufile_read(struct super_block *sb, size_t susize,
+ struct nilfs_inode *raw_inode, struct inode **inodep);
+int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range);
+
+/**
+ * nilfs_sufile_warn_on_error - warn on unexpected sufile error
+ * @sufile: inode of segment usage file
+ * @err: status code returned by a sufile function
+ *
+ * Even if buffer heads of blocks containing segment usage entries have
+ * been dirtied in advance by calling functions such as
+ * nilfs_sufile_mark_dirty() or nilfs_sufile_{alloc,free}(), those buffers
+ * can be discarded from memory after the file system detects corruption and
+ * degrades to read-only mode, which may cause sufile operations, including
+ * cancel operations, to return errors. nilfs_sufile_warn_on_error() is used
+ * to detect unexpected errors other than during read-only degradation.
+ *
+ * Return: 0 if @err is 0, %-EROFS if in read-only degraded mode, and %-EIO
+ * otherwise.
+ */
+#define nilfs_sufile_warn_on_error(sufile, err) \
+ ({ \
+ int _err = (err); \
+ \
+ if (unlikely(_err)) \
+ _err = WARN_ONCE(!sb_rdonly((sufile)->i_sb), \
+ "unexpected sufile error %d\n", _err) ? \
+ -EIO : -EROFS; \
+ _err; \
+ })
+
+/**
+ * nilfs_sufile_scrap - make a segment garbage
+ * @sufile: inode of segment usage file
+ * @segnum: segment number to be freed
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static inline int nilfs_sufile_scrap(struct inode *sufile, __u64 segnum)
+{
+ return nilfs_sufile_update(sufile, segnum, 1, nilfs_sufile_do_scrap);
+}
+
+/**
+ * nilfs_sufile_free - free segment
+ * @sufile: inode of segment usage file
+ * @segnum: segment number to be freed
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static inline int nilfs_sufile_free(struct inode *sufile, __u64 segnum)
+{
+ return nilfs_sufile_update(sufile, segnum, 0, nilfs_sufile_do_free);
+}
+
+/**
+ * nilfs_sufile_freev - free segments
+ * @sufile: inode of segment usage file
+ * @segnumv: array of segment numbers
+ * @nsegs: size of @segnumv array
+ * @ndone: place to store the number of freed segments
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static inline int nilfs_sufile_freev(struct inode *sufile, __u64 *segnumv,
+ size_t nsegs, size_t *ndone)
+{
+ return nilfs_sufile_updatev(sufile, segnumv, nsegs, 0, ndone,
+ nilfs_sufile_do_free);
+}
+
+/**
+ * nilfs_sufile_cancel_freev - reallocate freeing segments
+ * @sufile: inode of segment usage file
+ * @segnumv: array of segment numbers
+ * @nsegs: size of @segnumv array
+ * @ndone: place to store the number of cancelled segments
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static inline int nilfs_sufile_cancel_freev(struct inode *sufile,
+ __u64 *segnumv, size_t nsegs,
+ size_t *ndone)
+{
+ return nilfs_sufile_updatev(sufile, segnumv, nsegs, 0, ndone,
+ nilfs_sufile_do_cancel_free);
+}
+
+/**
+ * nilfs_sufile_set_error - mark a segment as erroneous
+ * @sufile: inode of segment usage file
+ * @segnum: segment number
+ *
+ * Description: nilfs_sufile_set_error() marks the segment specified by
+ * @segnum as erroneous. The error segment will never be used again.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - Invalid segment usage number.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static inline int nilfs_sufile_set_error(struct inode *sufile, __u64 segnum)
+{
+ return nilfs_sufile_update(sufile, segnum, 0,
+ nilfs_sufile_do_set_error);
+}
+
+#endif /* _NILFS_SUFILE_H */
diff --git a/fs/nilfs2/super.c b/fs/nilfs2/super.c
new file mode 100644
index 000000000..893a504cb
--- /dev/null
+++ b/fs/nilfs2/super.c
@@ -0,0 +1,1413 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS module and super block management.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ */
+/*
+ * linux/fs/ext2/super.c
+ *
+ * Copyright (C) 1992, 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * from
+ *
+ * linux/fs/minix/inode.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Big-endian to little-endian byte-swapping/bitmaps by
+ * David S. Miller (davem@caip.rutgers.edu), 1995
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/blkdev.h>
+#include <linux/crc32.h>
+#include <linux/vfs.h>
+#include <linux/writeback.h>
+#include <linux/seq_file.h>
+#include <linux/mount.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include "nilfs.h"
+#include "export.h"
+#include "mdt.h"
+#include "alloc.h"
+#include "btree.h"
+#include "btnode.h"
+#include "page.h"
+#include "cpfile.h"
+#include "sufile.h" /* nilfs_sufile_resize(), nilfs_sufile_set_alloc_range() */
+#include "ifile.h"
+#include "dat.h"
+#include "segment.h"
+#include "segbuf.h"
+
+MODULE_AUTHOR("NTT Corp.");
+MODULE_DESCRIPTION("A New Implementation of the Log-structured Filesystem "
+ "(NILFS)");
+MODULE_LICENSE("GPL");
+
+static struct kmem_cache *nilfs_inode_cachep;
+struct kmem_cache *nilfs_transaction_cachep;
+struct kmem_cache *nilfs_segbuf_cachep;
+struct kmem_cache *nilfs_btree_path_cache;
+
+static int nilfs_setup_super(struct super_block *sb, int is_mount);
+
+void __nilfs_msg(struct super_block *sb, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+ int level;
+
+ va_start(args, fmt);
+
+ level = printk_get_level(fmt);
+ vaf.fmt = printk_skip_level(fmt);
+ vaf.va = &args;
+
+ if (sb)
+ printk("%c%cNILFS (%s): %pV\n",
+ KERN_SOH_ASCII, level, sb->s_id, &vaf);
+ else
+ printk("%c%cNILFS: %pV\n",
+ KERN_SOH_ASCII, level, &vaf);
+
+ va_end(args);
+}
+
+static void nilfs_set_error(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+
+ down_write(&nilfs->ns_sem);
+ if (!(nilfs->ns_mount_state & NILFS_ERROR_FS)) {
+ nilfs->ns_mount_state |= NILFS_ERROR_FS;
+ sbp = nilfs_prepare_super(sb, 0);
+ if (likely(sbp)) {
+ sbp[0]->s_state |= cpu_to_le16(NILFS_ERROR_FS);
+ if (sbp[1])
+ sbp[1]->s_state |= cpu_to_le16(NILFS_ERROR_FS);
+ nilfs_commit_super(sb, NILFS_SB_COMMIT_ALL);
+ }
+ }
+ up_write(&nilfs->ns_sem);
+}
+
+/**
+ * __nilfs_error() - report failure condition on a filesystem
+ * @sb: super block instance
+ * @function: name of calling function
+ * @fmt: format string for message to be output
+ * @...: optional arguments to @fmt
+ *
+ * __nilfs_error() sets an ERROR_FS flag on the superblock as well as
+ * reporting an error message. This function should be called when
+ * NILFS detects incoherences or defects of meta data on disk.
+ *
+ * This implements the body of nilfs_error() macro. Normally,
+ * nilfs_error() should be used. As for sustainable errors such as a
+ * single-shot I/O error, nilfs_err() should be used instead.
+ *
+ * Callers should not add a trailing newline since this will do it.
+ */
+void __nilfs_error(struct super_block *sb, const char *function,
+ const char *fmt, ...)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ printk(KERN_CRIT "NILFS error (device %s): %s: %pV\n",
+ sb->s_id, function, &vaf);
+
+ va_end(args);
+
+ if (!sb_rdonly(sb)) {
+ nilfs_set_error(sb);
+
+ if (nilfs_test_opt(nilfs, ERRORS_RO)) {
+ printk(KERN_CRIT "Remounting filesystem read-only\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+ }
+
+ if (nilfs_test_opt(nilfs, ERRORS_PANIC))
+ panic("NILFS (device %s): panic forced after error\n",
+ sb->s_id);
+}
+
+struct inode *nilfs_alloc_inode(struct super_block *sb)
+{
+ struct nilfs_inode_info *ii;
+
+ ii = alloc_inode_sb(sb, nilfs_inode_cachep, GFP_NOFS);
+ if (!ii)
+ return NULL;
+ ii->i_bh = NULL;
+ ii->i_state = 0;
+ ii->i_type = 0;
+ ii->i_cno = 0;
+ ii->i_assoc_inode = NULL;
+ ii->i_bmap = &ii->i_bmap_data;
+ return &ii->vfs_inode;
+}
+
+static void nilfs_free_inode(struct inode *inode)
+{
+ if (nilfs_is_metadata_file_inode(inode))
+ nilfs_mdt_destroy(inode);
+
+ kmem_cache_free(nilfs_inode_cachep, NILFS_I(inode));
+}
+
+static int nilfs_sync_super(struct super_block *sb, int flag)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ int err;
+
+ retry:
+ set_buffer_dirty(nilfs->ns_sbh[0]);
+ if (nilfs_test_opt(nilfs, BARRIER)) {
+ err = __sync_dirty_buffer(nilfs->ns_sbh[0],
+ REQ_SYNC | REQ_PREFLUSH | REQ_FUA);
+ } else {
+ err = sync_dirty_buffer(nilfs->ns_sbh[0]);
+ }
+
+ if (unlikely(err)) {
+ nilfs_err(sb, "unable to write superblock: err=%d", err);
+ if (err == -EIO && nilfs->ns_sbh[1]) {
+ /*
+ * sbp[0] points to newer log than sbp[1],
+ * so copy sbp[0] to sbp[1] to take over sbp[0].
+ */
+ memcpy(nilfs->ns_sbp[1], nilfs->ns_sbp[0],
+ nilfs->ns_sbsize);
+ nilfs_fall_back_super_block(nilfs);
+ goto retry;
+ }
+ } else {
+ struct nilfs_super_block *sbp = nilfs->ns_sbp[0];
+
+ nilfs->ns_sbwcount++;
+
+ /*
+ * The latest segment becomes trailable from the position
+ * written in superblock.
+ */
+ clear_nilfs_discontinued(nilfs);
+
+ /* update GC protection for recent segments */
+ if (nilfs->ns_sbh[1]) {
+ if (flag == NILFS_SB_COMMIT_ALL) {
+ set_buffer_dirty(nilfs->ns_sbh[1]);
+ if (sync_dirty_buffer(nilfs->ns_sbh[1]) < 0)
+ goto out;
+ }
+ if (le64_to_cpu(nilfs->ns_sbp[1]->s_last_cno) <
+ le64_to_cpu(nilfs->ns_sbp[0]->s_last_cno))
+ sbp = nilfs->ns_sbp[1];
+ }
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ nilfs->ns_prot_seq = le64_to_cpu(sbp->s_last_seq);
+ spin_unlock(&nilfs->ns_last_segment_lock);
+ }
+ out:
+ return err;
+}
+
+void nilfs_set_log_cursor(struct nilfs_super_block *sbp,
+ struct the_nilfs *nilfs)
+{
+ sector_t nfreeblocks;
+
+ /* nilfs->ns_sem must be locked by the caller. */
+ nilfs_count_free_blocks(nilfs, &nfreeblocks);
+ sbp->s_free_blocks_count = cpu_to_le64(nfreeblocks);
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ sbp->s_last_seq = cpu_to_le64(nilfs->ns_last_seq);
+ sbp->s_last_pseg = cpu_to_le64(nilfs->ns_last_pseg);
+ sbp->s_last_cno = cpu_to_le64(nilfs->ns_last_cno);
+ spin_unlock(&nilfs->ns_last_segment_lock);
+}
+
+struct nilfs_super_block **nilfs_prepare_super(struct super_block *sb,
+ int flip)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp = nilfs->ns_sbp;
+
+ /* nilfs->ns_sem must be locked by the caller. */
+ if (sbp[0]->s_magic != cpu_to_le16(NILFS_SUPER_MAGIC)) {
+ if (sbp[1] &&
+ sbp[1]->s_magic == cpu_to_le16(NILFS_SUPER_MAGIC)) {
+ memcpy(sbp[0], sbp[1], nilfs->ns_sbsize);
+ } else {
+ nilfs_crit(sb, "superblock broke");
+ return NULL;
+ }
+ } else if (sbp[1] &&
+ sbp[1]->s_magic != cpu_to_le16(NILFS_SUPER_MAGIC)) {
+ memcpy(sbp[1], sbp[0], nilfs->ns_sbsize);
+ }
+
+ if (flip && sbp[1])
+ nilfs_swap_super_block(nilfs);
+
+ return sbp;
+}
+
+int nilfs_commit_super(struct super_block *sb, int flag)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp = nilfs->ns_sbp;
+ time64_t t;
+
+ /* nilfs->ns_sem must be locked by the caller. */
+ t = ktime_get_real_seconds();
+ nilfs->ns_sbwtime = t;
+ sbp[0]->s_wtime = cpu_to_le64(t);
+ sbp[0]->s_sum = 0;
+ sbp[0]->s_sum = cpu_to_le32(crc32_le(nilfs->ns_crc_seed,
+ (unsigned char *)sbp[0],
+ nilfs->ns_sbsize));
+ if (flag == NILFS_SB_COMMIT_ALL && sbp[1]) {
+ sbp[1]->s_wtime = sbp[0]->s_wtime;
+ sbp[1]->s_sum = 0;
+ sbp[1]->s_sum = cpu_to_le32(crc32_le(nilfs->ns_crc_seed,
+ (unsigned char *)sbp[1],
+ nilfs->ns_sbsize));
+ }
+ clear_nilfs_sb_dirty(nilfs);
+ nilfs->ns_flushed_device = 1;
+ /* make sure store to ns_flushed_device cannot be reordered */
+ smp_wmb();
+ return nilfs_sync_super(sb, flag);
+}
+
+/**
+ * nilfs_cleanup_super() - write filesystem state for cleanup
+ * @sb: super block instance to be unmounted or degraded to read-only
+ *
+ * This function restores state flags in the on-disk super block.
+ * This will set "clean" flag (i.e. NILFS_VALID_FS) unless the
+ * filesystem was not clean previously.
+ *
+ * Return: 0 on success, %-EIO if I/O error or superblock is corrupted.
+ */
+int nilfs_cleanup_super(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+ int flag = NILFS_SB_COMMIT;
+ int ret = -EIO;
+
+ sbp = nilfs_prepare_super(sb, 0);
+ if (sbp) {
+ sbp[0]->s_state = cpu_to_le16(nilfs->ns_mount_state);
+ nilfs_set_log_cursor(sbp[0], nilfs);
+ if (sbp[1] && sbp[0]->s_last_cno == sbp[1]->s_last_cno) {
+ /*
+ * make the "clean" flag also to the opposite
+ * super block if both super blocks point to
+ * the same checkpoint.
+ */
+ sbp[1]->s_state = sbp[0]->s_state;
+ flag = NILFS_SB_COMMIT_ALL;
+ }
+ ret = nilfs_commit_super(sb, flag);
+ }
+ return ret;
+}
+
+/**
+ * nilfs_move_2nd_super - relocate secondary super block
+ * @sb: super block instance
+ * @sb2off: new offset of the secondary super block (in bytes)
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int nilfs_move_2nd_super(struct super_block *sb, loff_t sb2off)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct buffer_head *nsbh;
+ struct nilfs_super_block *nsbp;
+ sector_t blocknr, newblocknr;
+ unsigned long offset;
+ int sb2i; /* array index of the secondary superblock */
+ int ret = 0;
+
+ /* nilfs->ns_sem must be locked by the caller. */
+ if (nilfs->ns_sbh[1] &&
+ nilfs->ns_sbh[1]->b_blocknr > nilfs->ns_first_data_block) {
+ sb2i = 1;
+ blocknr = nilfs->ns_sbh[1]->b_blocknr;
+ } else if (nilfs->ns_sbh[0]->b_blocknr > nilfs->ns_first_data_block) {
+ sb2i = 0;
+ blocknr = nilfs->ns_sbh[0]->b_blocknr;
+ } else {
+ sb2i = -1;
+ blocknr = 0;
+ }
+ if (sb2i >= 0 && (u64)blocknr << nilfs->ns_blocksize_bits == sb2off)
+ goto out; /* super block location is unchanged */
+
+ /* Get new super block buffer */
+ newblocknr = sb2off >> nilfs->ns_blocksize_bits;
+ offset = sb2off & (nilfs->ns_blocksize - 1);
+ nsbh = sb_getblk(sb, newblocknr);
+ if (!nsbh) {
+ nilfs_warn(sb,
+ "unable to move secondary superblock to block %llu",
+ (unsigned long long)newblocknr);
+ ret = -EIO;
+ goto out;
+ }
+ nsbp = (void *)nsbh->b_data + offset;
+
+ lock_buffer(nsbh);
+ if (sb2i >= 0) {
+ /*
+ * The position of the second superblock only changes by 4KiB,
+ * which is larger than the maximum superblock data size
+ * (= 1KiB), so there is no need to use memmove() to allow
+ * overlap between source and destination.
+ */
+ memcpy(nsbp, nilfs->ns_sbp[sb2i], nilfs->ns_sbsize);
+
+ /*
+ * Zero fill after copy to avoid overwriting in case of move
+ * within the same block.
+ */
+ memset(nsbh->b_data, 0, offset);
+ memset((void *)nsbp + nilfs->ns_sbsize, 0,
+ nsbh->b_size - offset - nilfs->ns_sbsize);
+ } else {
+ memset(nsbh->b_data, 0, nsbh->b_size);
+ }
+ set_buffer_uptodate(nsbh);
+ unlock_buffer(nsbh);
+
+ if (sb2i >= 0) {
+ brelse(nilfs->ns_sbh[sb2i]);
+ nilfs->ns_sbh[sb2i] = nsbh;
+ nilfs->ns_sbp[sb2i] = nsbp;
+ } else if (nilfs->ns_sbh[0]->b_blocknr < nilfs->ns_first_data_block) {
+ /* secondary super block will be restored to index 1 */
+ nilfs->ns_sbh[1] = nsbh;
+ nilfs->ns_sbp[1] = nsbp;
+ } else {
+ brelse(nsbh);
+ }
+out:
+ return ret;
+}
+
+/**
+ * nilfs_resize_fs - resize the filesystem
+ * @sb: super block instance
+ * @newsize: new size of the filesystem (in bytes)
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int nilfs_resize_fs(struct super_block *sb, __u64 newsize)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+ __u64 devsize, newnsegs;
+ loff_t sb2off;
+ int ret;
+
+ ret = -ERANGE;
+ devsize = bdev_nr_bytes(sb->s_bdev);
+ if (newsize > devsize)
+ goto out;
+
+ /*
+ * Prevent underflow in second superblock position calculation.
+ * The exact minimum size check is done in nilfs_sufile_resize().
+ */
+ if (newsize < 4096) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ /*
+ * Write lock is required to protect some functions depending
+ * on the number of segments, the number of reserved segments,
+ * and so forth.
+ */
+ down_write(&nilfs->ns_segctor_sem);
+
+ sb2off = NILFS_SB2_OFFSET_BYTES(newsize);
+ newnsegs = sb2off >> nilfs->ns_blocksize_bits;
+ newnsegs = div64_ul(newnsegs, nilfs->ns_blocks_per_segment);
+
+ ret = nilfs_sufile_resize(nilfs->ns_sufile, newnsegs);
+ up_write(&nilfs->ns_segctor_sem);
+ if (ret < 0)
+ goto out;
+
+ ret = nilfs_construct_segment(sb);
+ if (ret < 0)
+ goto out;
+
+ down_write(&nilfs->ns_sem);
+ nilfs_move_2nd_super(sb, sb2off);
+ ret = -EIO;
+ sbp = nilfs_prepare_super(sb, 0);
+ if (likely(sbp)) {
+ nilfs_set_log_cursor(sbp[0], nilfs);
+ /*
+ * Drop NILFS_RESIZE_FS flag for compatibility with
+ * mount-time resize which may be implemented in a
+ * future release.
+ */
+ sbp[0]->s_state = cpu_to_le16(le16_to_cpu(sbp[0]->s_state) &
+ ~NILFS_RESIZE_FS);
+ sbp[0]->s_dev_size = cpu_to_le64(newsize);
+ sbp[0]->s_nsegments = cpu_to_le64(nilfs->ns_nsegments);
+ if (sbp[1])
+ memcpy(sbp[1], sbp[0], nilfs->ns_sbsize);
+ ret = nilfs_commit_super(sb, NILFS_SB_COMMIT_ALL);
+ }
+ up_write(&nilfs->ns_sem);
+
+ /*
+ * Reset the range of allocatable segments last. This order
+ * is important in the case of expansion because the secondary
+ * superblock must be protected from log write until migration
+ * completes.
+ */
+ if (!ret)
+ nilfs_sufile_set_alloc_range(nilfs->ns_sufile, 0, newnsegs - 1);
+out:
+ return ret;
+}
+
+static void nilfs_put_super(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+
+ nilfs_detach_log_writer(sb);
+
+ if (!sb_rdonly(sb)) {
+ down_write(&nilfs->ns_sem);
+ nilfs_cleanup_super(sb);
+ up_write(&nilfs->ns_sem);
+ }
+
+ nilfs_sysfs_delete_device_group(nilfs);
+ iput(nilfs->ns_sufile);
+ iput(nilfs->ns_cpfile);
+ iput(nilfs->ns_dat);
+
+ destroy_nilfs(nilfs);
+ sb->s_fs_info = NULL;
+}
+
+static int nilfs_sync_fs(struct super_block *sb, int wait)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+ int err = 0;
+
+ /* This function is called when super block should be written back */
+ if (wait)
+ err = nilfs_construct_segment(sb);
+
+ down_write(&nilfs->ns_sem);
+ if (nilfs_sb_dirty(nilfs)) {
+ sbp = nilfs_prepare_super(sb, nilfs_sb_will_flip(nilfs));
+ if (likely(sbp)) {
+ nilfs_set_log_cursor(sbp[0], nilfs);
+ nilfs_commit_super(sb, NILFS_SB_COMMIT);
+ }
+ }
+ up_write(&nilfs->ns_sem);
+
+ if (!err)
+ err = nilfs_flush_device(nilfs);
+
+ return err;
+}
+
+int nilfs_attach_checkpoint(struct super_block *sb, __u64 cno, int curr_mnt,
+ struct nilfs_root **rootp)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_root *root;
+ int err = -ENOMEM;
+
+ root = nilfs_find_or_create_root(
+ nilfs, curr_mnt ? NILFS_CPTREE_CURRENT_CNO : cno);
+ if (!root)
+ return err;
+
+ if (root->ifile)
+ goto reuse; /* already attached checkpoint */
+
+ down_read(&nilfs->ns_segctor_sem);
+ err = nilfs_ifile_read(sb, root, cno, nilfs->ns_inode_size);
+ up_read(&nilfs->ns_segctor_sem);
+ if (unlikely(err))
+ goto failed;
+
+ reuse:
+ *rootp = root;
+ return 0;
+
+ failed:
+ if (err == -EINVAL)
+ nilfs_err(sb, "Invalid checkpoint (checkpoint number=%llu)",
+ (unsigned long long)cno);
+ nilfs_put_root(root);
+
+ return err;
+}
+
+static int nilfs_freeze(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ int err;
+
+ if (sb_rdonly(sb))
+ return 0;
+
+ /* Mark super block clean */
+ down_write(&nilfs->ns_sem);
+ err = nilfs_cleanup_super(sb);
+ up_write(&nilfs->ns_sem);
+ return err;
+}
+
+static int nilfs_unfreeze(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+
+ if (sb_rdonly(sb))
+ return 0;
+
+ down_write(&nilfs->ns_sem);
+ nilfs_setup_super(sb, false);
+ up_write(&nilfs->ns_sem);
+ return 0;
+}
+
+static int nilfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct nilfs_root *root = NILFS_I(d_inode(dentry))->i_root;
+ struct the_nilfs *nilfs = root->nilfs;
+ u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+ unsigned long long blocks;
+ unsigned long overhead;
+ unsigned long nrsvblocks;
+ sector_t nfreeblocks;
+ u64 nmaxinodes, nfreeinodes;
+ int err;
+
+ /*
+ * Compute all of the segment blocks
+ *
+ * The blocks before first segment and after last segment
+ * are excluded.
+ */
+ blocks = nilfs->ns_blocks_per_segment * nilfs->ns_nsegments
+ - nilfs->ns_first_data_block;
+ nrsvblocks = nilfs->ns_nrsvsegs * nilfs->ns_blocks_per_segment;
+
+ /*
+ * Compute the overhead
+ *
+ * When distributing meta data blocks outside segment structure,
+ * We must count them as the overhead.
+ */
+ overhead = 0;
+
+ err = nilfs_count_free_blocks(nilfs, &nfreeblocks);
+ if (unlikely(err))
+ return err;
+
+ err = nilfs_ifile_count_free_inodes(root->ifile,
+ &nmaxinodes, &nfreeinodes);
+ if (unlikely(err)) {
+ nilfs_warn(sb, "failed to count free inodes: err=%d", err);
+ if (err == -ERANGE) {
+ /*
+ * If nilfs_palloc_count_max_entries() returns
+ * -ERANGE error code then we simply treat
+ * curent inodes count as maximum possible and
+ * zero as free inodes value.
+ */
+ nmaxinodes = atomic64_read(&root->inodes_count);
+ nfreeinodes = 0;
+ err = 0;
+ } else
+ return err;
+ }
+
+ buf->f_type = NILFS_SUPER_MAGIC;
+ buf->f_bsize = sb->s_blocksize;
+ buf->f_blocks = blocks - overhead;
+ buf->f_bfree = nfreeblocks;
+ buf->f_bavail = (buf->f_bfree >= nrsvblocks) ?
+ (buf->f_bfree - nrsvblocks) : 0;
+ buf->f_files = nmaxinodes;
+ buf->f_ffree = nfreeinodes;
+ buf->f_namelen = NILFS_NAME_LEN;
+ buf->f_fsid = u64_to_fsid(id);
+
+ return 0;
+}
+
+static int nilfs_show_options(struct seq_file *seq, struct dentry *dentry)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_root *root = NILFS_I(d_inode(dentry))->i_root;
+
+ if (!nilfs_test_opt(nilfs, BARRIER))
+ seq_puts(seq, ",nobarrier");
+ if (root->cno != NILFS_CPTREE_CURRENT_CNO)
+ seq_printf(seq, ",cp=%llu", (unsigned long long)root->cno);
+ if (nilfs_test_opt(nilfs, ERRORS_PANIC))
+ seq_puts(seq, ",errors=panic");
+ if (nilfs_test_opt(nilfs, ERRORS_CONT))
+ seq_puts(seq, ",errors=continue");
+ if (nilfs_test_opt(nilfs, STRICT_ORDER))
+ seq_puts(seq, ",order=strict");
+ if (nilfs_test_opt(nilfs, NORECOVERY))
+ seq_puts(seq, ",norecovery");
+ if (nilfs_test_opt(nilfs, DISCARD))
+ seq_puts(seq, ",discard");
+
+ return 0;
+}
+
+static const struct super_operations nilfs_sops = {
+ .alloc_inode = nilfs_alloc_inode,
+ .free_inode = nilfs_free_inode,
+ .dirty_inode = nilfs_dirty_inode,
+ .evict_inode = nilfs_evict_inode,
+ .put_super = nilfs_put_super,
+ .sync_fs = nilfs_sync_fs,
+ .freeze_fs = nilfs_freeze,
+ .unfreeze_fs = nilfs_unfreeze,
+ .statfs = nilfs_statfs,
+ .show_options = nilfs_show_options
+};
+
+enum {
+ Opt_err, Opt_barrier, Opt_snapshot, Opt_order, Opt_norecovery,
+ Opt_discard,
+};
+
+static const struct constant_table nilfs_param_err[] = {
+ {"continue", NILFS_MOUNT_ERRORS_CONT},
+ {"panic", NILFS_MOUNT_ERRORS_PANIC},
+ {"remount-ro", NILFS_MOUNT_ERRORS_RO},
+ {}
+};
+
+static const struct fs_parameter_spec nilfs_param_spec[] = {
+ fsparam_enum ("errors", Opt_err, nilfs_param_err),
+ fsparam_flag_no ("barrier", Opt_barrier),
+ fsparam_u64 ("cp", Opt_snapshot),
+ fsparam_string ("order", Opt_order),
+ fsparam_flag ("norecovery", Opt_norecovery),
+ fsparam_flag_no ("discard", Opt_discard),
+ {}
+};
+
+struct nilfs_fs_context {
+ unsigned long ns_mount_opt;
+ __u64 cno;
+};
+
+static int nilfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct nilfs_fs_context *nilfs = fc->fs_private;
+ int is_remount = fc->purpose == FS_CONTEXT_FOR_RECONFIGURE;
+ struct fs_parse_result result;
+ int opt;
+
+ opt = fs_parse(fc, nilfs_param_spec, param, &result);
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case Opt_barrier:
+ if (result.negated)
+ nilfs_clear_opt(nilfs, BARRIER);
+ else
+ nilfs_set_opt(nilfs, BARRIER);
+ break;
+ case Opt_order:
+ if (strcmp(param->string, "relaxed") == 0)
+ /* Ordered data semantics */
+ nilfs_clear_opt(nilfs, STRICT_ORDER);
+ else if (strcmp(param->string, "strict") == 0)
+ /* Strict in-order semantics */
+ nilfs_set_opt(nilfs, STRICT_ORDER);
+ else
+ return -EINVAL;
+ break;
+ case Opt_err:
+ nilfs->ns_mount_opt &= ~NILFS_MOUNT_ERROR_MODE;
+ nilfs->ns_mount_opt |= result.uint_32;
+ break;
+ case Opt_snapshot:
+ if (is_remount) {
+ struct super_block *sb = fc->root->d_sb;
+
+ nilfs_err(sb,
+ "\"%s\" option is invalid for remount",
+ param->key);
+ return -EINVAL;
+ }
+ if (result.uint_64 == 0) {
+ nilfs_err(NULL,
+ "invalid option \"cp=0\": invalid checkpoint number 0");
+ return -EINVAL;
+ }
+ nilfs->cno = result.uint_64;
+ break;
+ case Opt_norecovery:
+ nilfs_set_opt(nilfs, NORECOVERY);
+ break;
+ case Opt_discard:
+ if (result.negated)
+ nilfs_clear_opt(nilfs, DISCARD);
+ else
+ nilfs_set_opt(nilfs, DISCARD);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nilfs_setup_super(struct super_block *sb, int is_mount)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_super_block **sbp;
+ int max_mnt_count;
+ int mnt_count;
+
+ /* nilfs->ns_sem must be locked by the caller. */
+ sbp = nilfs_prepare_super(sb, 0);
+ if (!sbp)
+ return -EIO;
+
+ if (!is_mount)
+ goto skip_mount_setup;
+
+ max_mnt_count = le16_to_cpu(sbp[0]->s_max_mnt_count);
+ mnt_count = le16_to_cpu(sbp[0]->s_mnt_count);
+
+ if (nilfs->ns_mount_state & NILFS_ERROR_FS) {
+ nilfs_warn(sb, "mounting fs with errors");
+#if 0
+ } else if (max_mnt_count >= 0 && mnt_count >= max_mnt_count) {
+ nilfs_warn(sb, "maximal mount count reached");
+#endif
+ }
+ if (!max_mnt_count)
+ sbp[0]->s_max_mnt_count = cpu_to_le16(NILFS_DFL_MAX_MNT_COUNT);
+
+ sbp[0]->s_mnt_count = cpu_to_le16(mnt_count + 1);
+ sbp[0]->s_mtime = cpu_to_le64(ktime_get_real_seconds());
+
+skip_mount_setup:
+ sbp[0]->s_state =
+ cpu_to_le16(le16_to_cpu(sbp[0]->s_state) & ~NILFS_VALID_FS);
+ /* synchronize sbp[1] with sbp[0] */
+ if (sbp[1])
+ memcpy(sbp[1], sbp[0], nilfs->ns_sbsize);
+ return nilfs_commit_super(sb, NILFS_SB_COMMIT_ALL);
+}
+
+struct nilfs_super_block *nilfs_read_super_block(struct super_block *sb,
+ u64 pos, int blocksize,
+ struct buffer_head **pbh)
+{
+ unsigned long long sb_index = pos;
+ unsigned long offset;
+
+ offset = do_div(sb_index, blocksize);
+ *pbh = sb_bread(sb, sb_index);
+ if (!*pbh)
+ return NULL;
+ return (struct nilfs_super_block *)((char *)(*pbh)->b_data + offset);
+}
+
+int nilfs_store_magic(struct super_block *sb,
+ struct nilfs_super_block *sbp)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+
+ sb->s_magic = le16_to_cpu(sbp->s_magic);
+
+ /* FS independent flags */
+#ifdef NILFS_ATIME_DISABLE
+ sb->s_flags |= SB_NOATIME;
+#endif
+
+ nilfs->ns_resuid = le16_to_cpu(sbp->s_def_resuid);
+ nilfs->ns_resgid = le16_to_cpu(sbp->s_def_resgid);
+ nilfs->ns_interval = le32_to_cpu(sbp->s_c_interval);
+ nilfs->ns_watermark = le32_to_cpu(sbp->s_c_block_max);
+
+ return 0;
+}
+
+int nilfs_check_feature_compatibility(struct super_block *sb,
+ struct nilfs_super_block *sbp)
+{
+ __u64 features;
+
+ features = le64_to_cpu(sbp->s_feature_incompat) &
+ ~NILFS_FEATURE_INCOMPAT_SUPP;
+ if (features) {
+ nilfs_err(sb,
+ "couldn't mount because of unsupported optional features (%llx)",
+ (unsigned long long)features);
+ return -EINVAL;
+ }
+ features = le64_to_cpu(sbp->s_feature_compat_ro) &
+ ~NILFS_FEATURE_COMPAT_RO_SUPP;
+ if (!sb_rdonly(sb) && features) {
+ nilfs_err(sb,
+ "couldn't mount RDWR because of unsupported optional features (%llx)",
+ (unsigned long long)features);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int nilfs_get_root_dentry(struct super_block *sb,
+ struct nilfs_root *root,
+ struct dentry **root_dentry)
+{
+ struct inode *inode;
+ struct dentry *dentry;
+ int ret = 0;
+
+ inode = nilfs_iget(sb, root, NILFS_ROOT_INO);
+ if (IS_ERR(inode)) {
+ ret = PTR_ERR(inode);
+ nilfs_err(sb, "error %d getting root inode", ret);
+ goto out;
+ }
+ if (!S_ISDIR(inode->i_mode) || !inode->i_blocks || !inode->i_size) {
+ iput(inode);
+ nilfs_err(sb, "corrupt root inode");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (root->cno == NILFS_CPTREE_CURRENT_CNO) {
+ dentry = d_find_alias(inode);
+ if (!dentry) {
+ dentry = d_make_root(inode);
+ if (!dentry) {
+ ret = -ENOMEM;
+ goto failed_dentry;
+ }
+ } else {
+ iput(inode);
+ }
+ } else {
+ dentry = d_obtain_root(inode);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto failed_dentry;
+ }
+ }
+ *root_dentry = dentry;
+ out:
+ return ret;
+
+ failed_dentry:
+ nilfs_err(sb, "error %d getting root dentry", ret);
+ goto out;
+}
+
+static int nilfs_attach_snapshot(struct super_block *s, __u64 cno,
+ struct dentry **root_dentry)
+{
+ struct the_nilfs *nilfs = s->s_fs_info;
+ struct nilfs_root *root;
+ int ret;
+
+ mutex_lock(&nilfs->ns_snapshot_mount_mutex);
+
+ down_read(&nilfs->ns_segctor_sem);
+ ret = nilfs_cpfile_is_snapshot(nilfs->ns_cpfile, cno);
+ up_read(&nilfs->ns_segctor_sem);
+ if (ret < 0) {
+ ret = (ret == -ENOENT) ? -EINVAL : ret;
+ goto out;
+ } else if (!ret) {
+ nilfs_err(s,
+ "The specified checkpoint is not a snapshot (checkpoint number=%llu)",
+ (unsigned long long)cno);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = nilfs_attach_checkpoint(s, cno, false, &root);
+ if (ret) {
+ nilfs_err(s,
+ "error %d while loading snapshot (checkpoint number=%llu)",
+ ret, (unsigned long long)cno);
+ goto out;
+ }
+ ret = nilfs_get_root_dentry(s, root, root_dentry);
+ nilfs_put_root(root);
+ out:
+ mutex_unlock(&nilfs->ns_snapshot_mount_mutex);
+ return ret;
+}
+
+/**
+ * nilfs_tree_is_busy() - try to shrink dentries of a checkpoint
+ * @root_dentry: root dentry of the tree to be shrunk
+ *
+ * Return: true if the tree was in-use, false otherwise.
+ */
+static bool nilfs_tree_is_busy(struct dentry *root_dentry)
+{
+ shrink_dcache_parent(root_dentry);
+ return d_count(root_dentry) > 1;
+}
+
+int nilfs_checkpoint_is_mounted(struct super_block *sb, __u64 cno)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ struct nilfs_root *root;
+ struct inode *inode;
+ struct dentry *dentry;
+ int ret;
+
+ if (cno > nilfs->ns_cno)
+ return false;
+
+ if (cno >= nilfs_last_cno(nilfs))
+ return true; /* protect recent checkpoints */
+
+ ret = false;
+ root = nilfs_lookup_root(nilfs, cno);
+ if (root) {
+ inode = nilfs_ilookup(sb, root, NILFS_ROOT_INO);
+ if (inode) {
+ dentry = d_find_alias(inode);
+ if (dentry) {
+ ret = nilfs_tree_is_busy(dentry);
+ dput(dentry);
+ }
+ iput(inode);
+ }
+ nilfs_put_root(root);
+ }
+ return ret;
+}
+
+/**
+ * nilfs_fill_super() - initialize a super block instance
+ * @sb: super_block
+ * @fc: filesystem context
+ *
+ * This function is called exclusively by nilfs->ns_mount_mutex.
+ * So, the recovery process is protected from other simultaneous mounts.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int
+nilfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+ struct the_nilfs *nilfs;
+ struct nilfs_root *fsroot;
+ struct nilfs_fs_context *ctx = fc->fs_private;
+ __u64 cno;
+ int err;
+
+ nilfs = alloc_nilfs(sb);
+ if (!nilfs)
+ return -ENOMEM;
+
+ sb->s_fs_info = nilfs;
+
+ err = init_nilfs(nilfs, sb);
+ if (err)
+ goto failed_nilfs;
+
+ /* Copy in parsed mount options */
+ nilfs->ns_mount_opt = ctx->ns_mount_opt;
+
+ sb->s_op = &nilfs_sops;
+ sb->s_export_op = &nilfs_export_ops;
+ sb->s_root = NULL;
+ sb->s_time_gran = 1;
+ sb->s_max_links = NILFS_LINK_MAX;
+
+ err = load_nilfs(nilfs, sb);
+ if (err)
+ goto failed_nilfs;
+
+ super_set_uuid(sb, nilfs->ns_sbp[0]->s_uuid,
+ sizeof(nilfs->ns_sbp[0]->s_uuid));
+ super_set_sysfs_name_bdev(sb);
+
+ cno = nilfs_last_cno(nilfs);
+ err = nilfs_attach_checkpoint(sb, cno, true, &fsroot);
+ if (err) {
+ nilfs_err(sb,
+ "error %d while loading last checkpoint (checkpoint number=%llu)",
+ err, (unsigned long long)cno);
+ goto failed_unload;
+ }
+
+ if (!sb_rdonly(sb)) {
+ err = nilfs_attach_log_writer(sb, fsroot);
+ if (err)
+ goto failed_checkpoint;
+ }
+
+ err = nilfs_get_root_dentry(sb, fsroot, &sb->s_root);
+ if (err)
+ goto failed_segctor;
+
+ nilfs_put_root(fsroot);
+
+ if (!sb_rdonly(sb)) {
+ down_write(&nilfs->ns_sem);
+ nilfs_setup_super(sb, true);
+ up_write(&nilfs->ns_sem);
+ }
+
+ return 0;
+
+ failed_segctor:
+ nilfs_detach_log_writer(sb);
+
+ failed_checkpoint:
+ nilfs_put_root(fsroot);
+
+ failed_unload:
+ nilfs_sysfs_delete_device_group(nilfs);
+ iput(nilfs->ns_sufile);
+ iput(nilfs->ns_cpfile);
+ iput(nilfs->ns_dat);
+
+ failed_nilfs:
+ destroy_nilfs(nilfs);
+ return err;
+}
+
+static int nilfs_reconfigure(struct fs_context *fc)
+{
+ struct nilfs_fs_context *ctx = fc->fs_private;
+ struct super_block *sb = fc->root->d_sb;
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ int err;
+
+ sync_filesystem(sb);
+
+ err = -EINVAL;
+
+ if (!nilfs_valid_fs(nilfs)) {
+ nilfs_warn(sb,
+ "couldn't remount because the filesystem is in an incomplete recovery state");
+ goto ignore_opts;
+ }
+ if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb))
+ goto out;
+ if (fc->sb_flags & SB_RDONLY) {
+ sb->s_flags |= SB_RDONLY;
+
+ /*
+ * Remounting a valid RW partition RDONLY, so set
+ * the RDONLY flag and then mark the partition as valid again.
+ */
+ down_write(&nilfs->ns_sem);
+ nilfs_cleanup_super(sb);
+ up_write(&nilfs->ns_sem);
+ } else {
+ __u64 features;
+ struct nilfs_root *root;
+
+ /*
+ * Mounting a RDONLY partition read-write, so reread and
+ * store the current valid flag. (It may have been changed
+ * by fsck since we originally mounted the partition.)
+ */
+ down_read(&nilfs->ns_sem);
+ features = le64_to_cpu(nilfs->ns_sbp[0]->s_feature_compat_ro) &
+ ~NILFS_FEATURE_COMPAT_RO_SUPP;
+ up_read(&nilfs->ns_sem);
+ if (features) {
+ nilfs_warn(sb,
+ "couldn't remount RDWR because of unsupported optional features (%llx)",
+ (unsigned long long)features);
+ err = -EROFS;
+ goto ignore_opts;
+ }
+
+ sb->s_flags &= ~SB_RDONLY;
+
+ root = NILFS_I(d_inode(sb->s_root))->i_root;
+ err = nilfs_attach_log_writer(sb, root);
+ if (err) {
+ sb->s_flags |= SB_RDONLY;
+ goto ignore_opts;
+ }
+
+ down_write(&nilfs->ns_sem);
+ nilfs_setup_super(sb, true);
+ up_write(&nilfs->ns_sem);
+ }
+ out:
+ sb->s_flags = (sb->s_flags & ~SB_POSIXACL);
+ /* Copy over parsed remount options */
+ nilfs->ns_mount_opt = ctx->ns_mount_opt;
+
+ return 0;
+
+ ignore_opts:
+ return err;
+}
+
+static int
+nilfs_get_tree(struct fs_context *fc)
+{
+ struct nilfs_fs_context *ctx = fc->fs_private;
+ struct super_block *s;
+ dev_t dev;
+ int err;
+
+ if (ctx->cno && !(fc->sb_flags & SB_RDONLY)) {
+ nilfs_err(NULL,
+ "invalid option \"cp=%llu\": read-only option is not specified",
+ ctx->cno);
+ return -EINVAL;
+ }
+
+ err = lookup_bdev(fc->source, &dev);
+ if (err)
+ return err;
+
+ s = sget_dev(fc, dev);
+ if (IS_ERR(s))
+ return PTR_ERR(s);
+
+ if (!s->s_root) {
+ err = setup_bdev_super(s, fc->sb_flags, fc);
+ if (!err)
+ err = nilfs_fill_super(s, fc);
+ if (err)
+ goto failed_super;
+
+ s->s_flags |= SB_ACTIVE;
+ } else if (!ctx->cno) {
+ if (nilfs_tree_is_busy(s->s_root)) {
+ if ((fc->sb_flags ^ s->s_flags) & SB_RDONLY) {
+ nilfs_err(s,
+ "the device already has a %s mount.",
+ sb_rdonly(s) ? "read-only" : "read/write");
+ err = -EBUSY;
+ goto failed_super;
+ }
+ } else {
+ /*
+ * Try reconfigure to setup mount states if the current
+ * tree is not mounted and only snapshots use this sb.
+ *
+ * Since nilfs_reconfigure() requires fc->root to be
+ * set, set it first and release it on failure.
+ */
+ fc->root = dget(s->s_root);
+ err = nilfs_reconfigure(fc);
+ if (err) {
+ dput(fc->root);
+ fc->root = NULL; /* prevent double release */
+ goto failed_super;
+ }
+ return 0;
+ }
+ }
+
+ if (ctx->cno) {
+ struct dentry *root_dentry;
+
+ err = nilfs_attach_snapshot(s, ctx->cno, &root_dentry);
+ if (err)
+ goto failed_super;
+ fc->root = root_dentry;
+ return 0;
+ }
+
+ fc->root = dget(s->s_root);
+ return 0;
+
+ failed_super:
+ deactivate_locked_super(s);
+ return err;
+}
+
+static void nilfs_free_fc(struct fs_context *fc)
+{
+ kfree(fc->fs_private);
+}
+
+static const struct fs_context_operations nilfs_context_ops = {
+ .parse_param = nilfs_parse_param,
+ .get_tree = nilfs_get_tree,
+ .reconfigure = nilfs_reconfigure,
+ .free = nilfs_free_fc,
+};
+
+static int nilfs_init_fs_context(struct fs_context *fc)
+{
+ struct nilfs_fs_context *ctx;
+
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->ns_mount_opt = NILFS_MOUNT_ERRORS_RO | NILFS_MOUNT_BARRIER;
+ fc->fs_private = ctx;
+ fc->ops = &nilfs_context_ops;
+
+ return 0;
+}
+
+struct file_system_type nilfs_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "nilfs2",
+ .kill_sb = kill_block_super,
+ .fs_flags = FS_REQUIRES_DEV,
+ .init_fs_context = nilfs_init_fs_context,
+ .parameters = nilfs_param_spec,
+};
+MODULE_ALIAS_FS("nilfs2");
+
+static void nilfs_inode_init_once(void *obj)
+{
+ struct nilfs_inode_info *ii = obj;
+
+ INIT_LIST_HEAD(&ii->i_dirty);
+#ifdef CONFIG_NILFS_XATTR
+ init_rwsem(&ii->xattr_sem);
+#endif
+ inode_init_once(&ii->vfs_inode);
+}
+
+static void nilfs_segbuf_init_once(void *obj)
+{
+ memset(obj, 0, sizeof(struct nilfs_segment_buffer));
+}
+
+static void nilfs_destroy_cachep(void)
+{
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+
+ kmem_cache_destroy(nilfs_inode_cachep);
+ kmem_cache_destroy(nilfs_transaction_cachep);
+ kmem_cache_destroy(nilfs_segbuf_cachep);
+ kmem_cache_destroy(nilfs_btree_path_cache);
+}
+
+static int __init nilfs_init_cachep(void)
+{
+ nilfs_inode_cachep = kmem_cache_create("nilfs2_inode_cache",
+ sizeof(struct nilfs_inode_info), 0,
+ SLAB_RECLAIM_ACCOUNT|SLAB_ACCOUNT,
+ nilfs_inode_init_once);
+ if (!nilfs_inode_cachep)
+ goto fail;
+
+ nilfs_transaction_cachep = kmem_cache_create("nilfs2_transaction_cache",
+ sizeof(struct nilfs_transaction_info), 0,
+ SLAB_RECLAIM_ACCOUNT, NULL);
+ if (!nilfs_transaction_cachep)
+ goto fail;
+
+ nilfs_segbuf_cachep = kmem_cache_create("nilfs2_segbuf_cache",
+ sizeof(struct nilfs_segment_buffer), 0,
+ SLAB_RECLAIM_ACCOUNT, nilfs_segbuf_init_once);
+ if (!nilfs_segbuf_cachep)
+ goto fail;
+
+ nilfs_btree_path_cache = kmem_cache_create("nilfs2_btree_path_cache",
+ sizeof(struct nilfs_btree_path) * NILFS_BTREE_LEVEL_MAX,
+ 0, 0, NULL);
+ if (!nilfs_btree_path_cache)
+ goto fail;
+
+ return 0;
+
+fail:
+ nilfs_destroy_cachep();
+ return -ENOMEM;
+}
+
+static int __init init_nilfs_fs(void)
+{
+ int err;
+
+ err = nilfs_init_cachep();
+ if (err)
+ goto fail;
+
+ err = nilfs_sysfs_init();
+ if (err)
+ goto free_cachep;
+
+ err = register_filesystem(&nilfs_fs_type);
+ if (err)
+ goto deinit_sysfs_entry;
+
+ printk(KERN_INFO "NILFS version 2 loaded\n");
+ return 0;
+
+deinit_sysfs_entry:
+ nilfs_sysfs_exit();
+free_cachep:
+ nilfs_destroy_cachep();
+fail:
+ return err;
+}
+
+static void __exit exit_nilfs_fs(void)
+{
+ nilfs_destroy_cachep();
+ nilfs_sysfs_exit();
+ unregister_filesystem(&nilfs_fs_type);
+}
+
+module_init(init_nilfs_fs)
+module_exit(exit_nilfs_fs)
diff --git a/fs/nilfs2/sysfs.c b/fs/nilfs2/sysfs.c
new file mode 100644
index 000000000..bc52afbfc
--- /dev/null
+++ b/fs/nilfs2/sysfs.c
@@ -0,0 +1,1140 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sysfs support implementation.
+ *
+ * Copyright (C) 2005-2014 Nippon Telegraph and Telephone Corporation.
+ * Copyright (C) 2014 HGST, Inc., a Western Digital Company.
+ *
+ * Written by Vyacheslav Dubeyko <Vyacheslav.Dubeyko@hgst.com>
+ */
+
+#include <linux/kobject.h>
+
+#include "nilfs.h"
+#include "mdt.h"
+#include "sufile.h"
+#include "cpfile.h"
+#include "sysfs.h"
+
+/* /sys/fs/<nilfs>/ */
+static struct kset *nilfs_kset;
+
+#define NILFS_DEV_INT_GROUP_OPS(name, parent_name) \
+static ssize_t nilfs_##name##_attr_show(struct kobject *kobj, \
+ struct attribute *attr, char *buf) \
+{ \
+ struct the_nilfs *nilfs = container_of(kobj->parent, \
+ struct the_nilfs, \
+ ns_##parent_name##_kobj); \
+ struct nilfs_##name##_attr *a = container_of(attr, \
+ struct nilfs_##name##_attr, \
+ attr); \
+ return a->show ? a->show(a, nilfs, buf) : 0; \
+} \
+static ssize_t nilfs_##name##_attr_store(struct kobject *kobj, \
+ struct attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ struct the_nilfs *nilfs = container_of(kobj->parent, \
+ struct the_nilfs, \
+ ns_##parent_name##_kobj); \
+ struct nilfs_##name##_attr *a = container_of(attr, \
+ struct nilfs_##name##_attr, \
+ attr); \
+ return a->store ? a->store(a, nilfs, buf, len) : 0; \
+} \
+static const struct sysfs_ops nilfs_##name##_attr_ops = { \
+ .show = nilfs_##name##_attr_show, \
+ .store = nilfs_##name##_attr_store, \
+}
+
+#define NILFS_DEV_INT_GROUP_TYPE(name, parent_name) \
+static void nilfs_##name##_attr_release(struct kobject *kobj) \
+{ \
+ struct nilfs_sysfs_##parent_name##_subgroups *subgroups = container_of(kobj, \
+ struct nilfs_sysfs_##parent_name##_subgroups, \
+ sg_##name##_kobj); \
+ complete(&subgroups->sg_##name##_kobj_unregister); \
+} \
+static const struct kobj_type nilfs_##name##_ktype = { \
+ .default_groups = nilfs_##name##_groups, \
+ .sysfs_ops = &nilfs_##name##_attr_ops, \
+ .release = nilfs_##name##_attr_release, \
+}
+
+#define NILFS_DEV_INT_GROUP_FNS(name, parent_name) \
+static int nilfs_sysfs_create_##name##_group(struct the_nilfs *nilfs) \
+{ \
+ struct kobject *parent; \
+ struct kobject *kobj; \
+ struct completion *kobj_unregister; \
+ struct nilfs_sysfs_##parent_name##_subgroups *subgroups; \
+ int err; \
+ subgroups = nilfs->ns_##parent_name##_subgroups; \
+ kobj = &subgroups->sg_##name##_kobj; \
+ kobj_unregister = &subgroups->sg_##name##_kobj_unregister; \
+ parent = &nilfs->ns_##parent_name##_kobj; \
+ kobj->kset = nilfs_kset; \
+ init_completion(kobj_unregister); \
+ err = kobject_init_and_add(kobj, &nilfs_##name##_ktype, parent, \
+ #name); \
+ if (err) \
+ kobject_put(kobj); \
+ return err; \
+} \
+static void nilfs_sysfs_delete_##name##_group(struct the_nilfs *nilfs) \
+{ \
+ kobject_put(&nilfs->ns_##parent_name##_subgroups->sg_##name##_kobj); \
+}
+
+/************************************************************************
+ * NILFS snapshot attrs *
+ ************************************************************************/
+
+static ssize_t
+nilfs_snapshot_inodes_count_show(struct nilfs_snapshot_attr *attr,
+ struct nilfs_root *root, char *buf)
+{
+ return sysfs_emit(buf, "%llu\n",
+ (unsigned long long)atomic64_read(&root->inodes_count));
+}
+
+static ssize_t
+nilfs_snapshot_blocks_count_show(struct nilfs_snapshot_attr *attr,
+ struct nilfs_root *root, char *buf)
+{
+ return sysfs_emit(buf, "%llu\n",
+ (unsigned long long)atomic64_read(&root->blocks_count));
+}
+
+static const char snapshot_readme_str[] =
+ "The group contains details about mounted snapshot.\n\n"
+ "(1) inodes_count\n\tshow number of inodes for snapshot.\n\n"
+ "(2) blocks_count\n\tshow number of blocks for snapshot.\n\n";
+
+static ssize_t
+nilfs_snapshot_README_show(struct nilfs_snapshot_attr *attr,
+ struct nilfs_root *root, char *buf)
+{
+ return sysfs_emit(buf, snapshot_readme_str);
+}
+
+NILFS_SNAPSHOT_RO_ATTR(inodes_count);
+NILFS_SNAPSHOT_RO_ATTR(blocks_count);
+NILFS_SNAPSHOT_RO_ATTR(README);
+
+static struct attribute *nilfs_snapshot_attrs[] = {
+ NILFS_SNAPSHOT_ATTR_LIST(inodes_count),
+ NILFS_SNAPSHOT_ATTR_LIST(blocks_count),
+ NILFS_SNAPSHOT_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_snapshot);
+
+static ssize_t nilfs_snapshot_attr_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct nilfs_root *root =
+ container_of(kobj, struct nilfs_root, snapshot_kobj);
+ struct nilfs_snapshot_attr *a =
+ container_of(attr, struct nilfs_snapshot_attr, attr);
+
+ return a->show ? a->show(a, root, buf) : 0;
+}
+
+static ssize_t nilfs_snapshot_attr_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, size_t len)
+{
+ struct nilfs_root *root =
+ container_of(kobj, struct nilfs_root, snapshot_kobj);
+ struct nilfs_snapshot_attr *a =
+ container_of(attr, struct nilfs_snapshot_attr, attr);
+
+ return a->store ? a->store(a, root, buf, len) : 0;
+}
+
+static void nilfs_snapshot_attr_release(struct kobject *kobj)
+{
+ struct nilfs_root *root = container_of(kobj, struct nilfs_root,
+ snapshot_kobj);
+ complete(&root->snapshot_kobj_unregister);
+}
+
+static const struct sysfs_ops nilfs_snapshot_attr_ops = {
+ .show = nilfs_snapshot_attr_show,
+ .store = nilfs_snapshot_attr_store,
+};
+
+static const struct kobj_type nilfs_snapshot_ktype = {
+ .default_groups = nilfs_snapshot_groups,
+ .sysfs_ops = &nilfs_snapshot_attr_ops,
+ .release = nilfs_snapshot_attr_release,
+};
+
+int nilfs_sysfs_create_snapshot_group(struct nilfs_root *root)
+{
+ struct the_nilfs *nilfs;
+ struct kobject *parent;
+ int err;
+
+ nilfs = root->nilfs;
+ parent = &nilfs->ns_dev_subgroups->sg_mounted_snapshots_kobj;
+ root->snapshot_kobj.kset = nilfs_kset;
+ init_completion(&root->snapshot_kobj_unregister);
+
+ if (root->cno == NILFS_CPTREE_CURRENT_CNO) {
+ err = kobject_init_and_add(&root->snapshot_kobj,
+ &nilfs_snapshot_ktype,
+ &nilfs->ns_dev_kobj,
+ "current_checkpoint");
+ } else {
+ err = kobject_init_and_add(&root->snapshot_kobj,
+ &nilfs_snapshot_ktype,
+ parent,
+ "%llu", root->cno);
+ }
+
+ if (err)
+ kobject_put(&root->snapshot_kobj);
+
+ return err;
+}
+
+void nilfs_sysfs_delete_snapshot_group(struct nilfs_root *root)
+{
+ kobject_put(&root->snapshot_kobj);
+}
+
+/************************************************************************
+ * NILFS mounted snapshots attrs *
+ ************************************************************************/
+
+static const char mounted_snapshots_readme_str[] =
+ "The mounted_snapshots group contains group for\n"
+ "every mounted snapshot.\n";
+
+static ssize_t
+nilfs_mounted_snapshots_README_show(struct nilfs_mounted_snapshots_attr *attr,
+ struct the_nilfs *nilfs, char *buf)
+{
+ return sysfs_emit(buf, mounted_snapshots_readme_str);
+}
+
+NILFS_MOUNTED_SNAPSHOTS_RO_ATTR(README);
+
+static struct attribute *nilfs_mounted_snapshots_attrs[] = {
+ NILFS_MOUNTED_SNAPSHOTS_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_mounted_snapshots);
+
+NILFS_DEV_INT_GROUP_OPS(mounted_snapshots, dev);
+NILFS_DEV_INT_GROUP_TYPE(mounted_snapshots, dev);
+NILFS_DEV_INT_GROUP_FNS(mounted_snapshots, dev);
+
+/************************************************************************
+ * NILFS checkpoints attrs *
+ ************************************************************************/
+
+static ssize_t
+nilfs_checkpoints_checkpoints_number_show(struct nilfs_checkpoints_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 ncheckpoints;
+ struct nilfs_cpstat cpstat;
+ int err;
+
+ down_read(&nilfs->ns_segctor_sem);
+ err = nilfs_cpfile_get_stat(nilfs->ns_cpfile, &cpstat);
+ up_read(&nilfs->ns_segctor_sem);
+ if (err < 0) {
+ nilfs_err(nilfs->ns_sb, "unable to get checkpoint stat: err=%d",
+ err);
+ return err;
+ }
+
+ ncheckpoints = cpstat.cs_ncps;
+
+ return sysfs_emit(buf, "%llu\n", ncheckpoints);
+}
+
+static ssize_t
+nilfs_checkpoints_snapshots_number_show(struct nilfs_checkpoints_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 nsnapshots;
+ struct nilfs_cpstat cpstat;
+ int err;
+
+ down_read(&nilfs->ns_segctor_sem);
+ err = nilfs_cpfile_get_stat(nilfs->ns_cpfile, &cpstat);
+ up_read(&nilfs->ns_segctor_sem);
+ if (err < 0) {
+ nilfs_err(nilfs->ns_sb, "unable to get checkpoint stat: err=%d",
+ err);
+ return err;
+ }
+
+ nsnapshots = cpstat.cs_nsss;
+
+ return sysfs_emit(buf, "%llu\n", nsnapshots);
+}
+
+static ssize_t
+nilfs_checkpoints_last_seg_checkpoint_show(struct nilfs_checkpoints_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 last_cno;
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ last_cno = nilfs->ns_last_cno;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+
+ return sysfs_emit(buf, "%llu\n", last_cno);
+}
+
+static ssize_t
+nilfs_checkpoints_next_checkpoint_show(struct nilfs_checkpoints_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 cno;
+
+ down_read(&nilfs->ns_segctor_sem);
+ cno = nilfs->ns_cno;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", cno);
+}
+
+static const char checkpoints_readme_str[] =
+ "The checkpoints group contains attributes that describe\n"
+ "details about volume's checkpoints.\n\n"
+ "(1) checkpoints_number\n\tshow number of checkpoints on volume.\n\n"
+ "(2) snapshots_number\n\tshow number of snapshots on volume.\n\n"
+ "(3) last_seg_checkpoint\n"
+ "\tshow checkpoint number of the latest segment.\n\n"
+ "(4) next_checkpoint\n\tshow next checkpoint number.\n\n";
+
+static ssize_t
+nilfs_checkpoints_README_show(struct nilfs_checkpoints_attr *attr,
+ struct the_nilfs *nilfs, char *buf)
+{
+ return sysfs_emit(buf, checkpoints_readme_str);
+}
+
+NILFS_CHECKPOINTS_RO_ATTR(checkpoints_number);
+NILFS_CHECKPOINTS_RO_ATTR(snapshots_number);
+NILFS_CHECKPOINTS_RO_ATTR(last_seg_checkpoint);
+NILFS_CHECKPOINTS_RO_ATTR(next_checkpoint);
+NILFS_CHECKPOINTS_RO_ATTR(README);
+
+static struct attribute *nilfs_checkpoints_attrs[] = {
+ NILFS_CHECKPOINTS_ATTR_LIST(checkpoints_number),
+ NILFS_CHECKPOINTS_ATTR_LIST(snapshots_number),
+ NILFS_CHECKPOINTS_ATTR_LIST(last_seg_checkpoint),
+ NILFS_CHECKPOINTS_ATTR_LIST(next_checkpoint),
+ NILFS_CHECKPOINTS_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_checkpoints);
+
+NILFS_DEV_INT_GROUP_OPS(checkpoints, dev);
+NILFS_DEV_INT_GROUP_TYPE(checkpoints, dev);
+NILFS_DEV_INT_GROUP_FNS(checkpoints, dev);
+
+/************************************************************************
+ * NILFS segments attrs *
+ ************************************************************************/
+
+static ssize_t
+nilfs_segments_segments_number_show(struct nilfs_segments_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ return sysfs_emit(buf, "%lu\n", nilfs->ns_nsegments);
+}
+
+static ssize_t
+nilfs_segments_blocks_per_segment_show(struct nilfs_segments_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ return sysfs_emit(buf, "%lu\n", nilfs->ns_blocks_per_segment);
+}
+
+static ssize_t
+nilfs_segments_clean_segments_show(struct nilfs_segments_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ unsigned long ncleansegs;
+
+ down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ ncleansegs = nilfs_sufile_get_ncleansegs(nilfs->ns_sufile);
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+
+ return sysfs_emit(buf, "%lu\n", ncleansegs);
+}
+
+static ssize_t
+nilfs_segments_dirty_segments_show(struct nilfs_segments_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ struct nilfs_sustat sustat;
+ int err;
+
+ down_read(&nilfs->ns_segctor_sem);
+ err = nilfs_sufile_get_stat(nilfs->ns_sufile, &sustat);
+ up_read(&nilfs->ns_segctor_sem);
+ if (err < 0) {
+ nilfs_err(nilfs->ns_sb, "unable to get segment stat: err=%d",
+ err);
+ return err;
+ }
+
+ return sysfs_emit(buf, "%llu\n", sustat.ss_ndirtysegs);
+}
+
+static const char segments_readme_str[] =
+ "The segments group contains attributes that describe\n"
+ "details about volume's segments.\n\n"
+ "(1) segments_number\n\tshow number of segments on volume.\n\n"
+ "(2) blocks_per_segment\n\tshow number of blocks in segment.\n\n"
+ "(3) clean_segments\n\tshow count of clean segments.\n\n"
+ "(4) dirty_segments\n\tshow count of dirty segments.\n\n";
+
+static ssize_t
+nilfs_segments_README_show(struct nilfs_segments_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ return sysfs_emit(buf, segments_readme_str);
+}
+
+NILFS_SEGMENTS_RO_ATTR(segments_number);
+NILFS_SEGMENTS_RO_ATTR(blocks_per_segment);
+NILFS_SEGMENTS_RO_ATTR(clean_segments);
+NILFS_SEGMENTS_RO_ATTR(dirty_segments);
+NILFS_SEGMENTS_RO_ATTR(README);
+
+static struct attribute *nilfs_segments_attrs[] = {
+ NILFS_SEGMENTS_ATTR_LIST(segments_number),
+ NILFS_SEGMENTS_ATTR_LIST(blocks_per_segment),
+ NILFS_SEGMENTS_ATTR_LIST(clean_segments),
+ NILFS_SEGMENTS_ATTR_LIST(dirty_segments),
+ NILFS_SEGMENTS_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_segments);
+
+NILFS_DEV_INT_GROUP_OPS(segments, dev);
+NILFS_DEV_INT_GROUP_TYPE(segments, dev);
+NILFS_DEV_INT_GROUP_FNS(segments, dev);
+
+/************************************************************************
+ * NILFS segctor attrs *
+ ************************************************************************/
+
+static ssize_t
+nilfs_segctor_last_pseg_block_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ sector_t last_pseg;
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ last_pseg = nilfs->ns_last_pseg;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+
+ return sysfs_emit(buf, "%llu\n",
+ (unsigned long long)last_pseg);
+}
+
+static ssize_t
+nilfs_segctor_last_seg_sequence_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ u64 last_seq;
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ last_seq = nilfs->ns_last_seq;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+
+ return sysfs_emit(buf, "%llu\n", last_seq);
+}
+
+static ssize_t
+nilfs_segctor_last_seg_checkpoint_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 last_cno;
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ last_cno = nilfs->ns_last_cno;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+
+ return sysfs_emit(buf, "%llu\n", last_cno);
+}
+
+static ssize_t
+nilfs_segctor_current_seg_sequence_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ u64 seg_seq;
+
+ down_read(&nilfs->ns_segctor_sem);
+ seg_seq = nilfs->ns_seg_seq;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", seg_seq);
+}
+
+static ssize_t
+nilfs_segctor_current_last_full_seg_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 segnum;
+
+ down_read(&nilfs->ns_segctor_sem);
+ segnum = nilfs->ns_segnum;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", segnum);
+}
+
+static ssize_t
+nilfs_segctor_next_full_seg_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 nextnum;
+
+ down_read(&nilfs->ns_segctor_sem);
+ nextnum = nilfs->ns_nextnum;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", nextnum);
+}
+
+static ssize_t
+nilfs_segctor_next_pseg_offset_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ unsigned long pseg_offset;
+
+ down_read(&nilfs->ns_segctor_sem);
+ pseg_offset = nilfs->ns_pseg_offset;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%lu\n", pseg_offset);
+}
+
+static ssize_t
+nilfs_segctor_next_checkpoint_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ __u64 cno;
+
+ down_read(&nilfs->ns_segctor_sem);
+ cno = nilfs->ns_cno;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", cno);
+}
+
+static ssize_t
+nilfs_segctor_last_seg_write_time_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t ctime;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ctime = nilfs->ns_ctime;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%ptTs\n", &ctime);
+}
+
+static ssize_t
+nilfs_segctor_last_seg_write_time_secs_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t ctime;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ctime = nilfs->ns_ctime;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", ctime);
+}
+
+static ssize_t
+nilfs_segctor_last_nongc_write_time_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t nongc_ctime;
+
+ down_read(&nilfs->ns_segctor_sem);
+ nongc_ctime = nilfs->ns_nongc_ctime;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%ptTs\n", &nongc_ctime);
+}
+
+static ssize_t
+nilfs_segctor_last_nongc_write_time_secs_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t nongc_ctime;
+
+ down_read(&nilfs->ns_segctor_sem);
+ nongc_ctime = nilfs->ns_nongc_ctime;
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%llu\n", nongc_ctime);
+}
+
+static ssize_t
+nilfs_segctor_dirty_data_blocks_count_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ u32 ndirtyblks;
+
+ down_read(&nilfs->ns_segctor_sem);
+ ndirtyblks = atomic_read(&nilfs->ns_ndirtyblks);
+ up_read(&nilfs->ns_segctor_sem);
+
+ return sysfs_emit(buf, "%u\n", ndirtyblks);
+}
+
+static const char segctor_readme_str[] =
+ "The segctor group contains attributes that describe\n"
+ "segctor thread activity details.\n\n"
+ "(1) last_pseg_block\n"
+ "\tshow start block number of the latest segment.\n\n"
+ "(2) last_seg_sequence\n"
+ "\tshow sequence value of the latest segment.\n\n"
+ "(3) last_seg_checkpoint\n"
+ "\tshow checkpoint number of the latest segment.\n\n"
+ "(4) current_seg_sequence\n\tshow segment sequence counter.\n\n"
+ "(5) current_last_full_seg\n"
+ "\tshow index number of the latest full segment.\n\n"
+ "(6) next_full_seg\n"
+ "\tshow index number of the full segment index to be used next.\n\n"
+ "(7) next_pseg_offset\n"
+ "\tshow offset of next partial segment in the current full segment.\n\n"
+ "(8) next_checkpoint\n\tshow next checkpoint number.\n\n"
+ "(9) last_seg_write_time\n"
+ "\tshow write time of the last segment in human-readable format.\n\n"
+ "(10) last_seg_write_time_secs\n"
+ "\tshow write time of the last segment in seconds.\n\n"
+ "(11) last_nongc_write_time\n"
+ "\tshow write time of the last segment not for cleaner operation "
+ "in human-readable format.\n\n"
+ "(12) last_nongc_write_time_secs\n"
+ "\tshow write time of the last segment not for cleaner operation "
+ "in seconds.\n\n"
+ "(13) dirty_data_blocks_count\n"
+ "\tshow number of dirty data blocks.\n\n";
+
+static ssize_t
+nilfs_segctor_README_show(struct nilfs_segctor_attr *attr,
+ struct the_nilfs *nilfs, char *buf)
+{
+ return sysfs_emit(buf, segctor_readme_str);
+}
+
+NILFS_SEGCTOR_RO_ATTR(last_pseg_block);
+NILFS_SEGCTOR_RO_ATTR(last_seg_sequence);
+NILFS_SEGCTOR_RO_ATTR(last_seg_checkpoint);
+NILFS_SEGCTOR_RO_ATTR(current_seg_sequence);
+NILFS_SEGCTOR_RO_ATTR(current_last_full_seg);
+NILFS_SEGCTOR_RO_ATTR(next_full_seg);
+NILFS_SEGCTOR_RO_ATTR(next_pseg_offset);
+NILFS_SEGCTOR_RO_ATTR(next_checkpoint);
+NILFS_SEGCTOR_RO_ATTR(last_seg_write_time);
+NILFS_SEGCTOR_RO_ATTR(last_seg_write_time_secs);
+NILFS_SEGCTOR_RO_ATTR(last_nongc_write_time);
+NILFS_SEGCTOR_RO_ATTR(last_nongc_write_time_secs);
+NILFS_SEGCTOR_RO_ATTR(dirty_data_blocks_count);
+NILFS_SEGCTOR_RO_ATTR(README);
+
+static struct attribute *nilfs_segctor_attrs[] = {
+ NILFS_SEGCTOR_ATTR_LIST(last_pseg_block),
+ NILFS_SEGCTOR_ATTR_LIST(last_seg_sequence),
+ NILFS_SEGCTOR_ATTR_LIST(last_seg_checkpoint),
+ NILFS_SEGCTOR_ATTR_LIST(current_seg_sequence),
+ NILFS_SEGCTOR_ATTR_LIST(current_last_full_seg),
+ NILFS_SEGCTOR_ATTR_LIST(next_full_seg),
+ NILFS_SEGCTOR_ATTR_LIST(next_pseg_offset),
+ NILFS_SEGCTOR_ATTR_LIST(next_checkpoint),
+ NILFS_SEGCTOR_ATTR_LIST(last_seg_write_time),
+ NILFS_SEGCTOR_ATTR_LIST(last_seg_write_time_secs),
+ NILFS_SEGCTOR_ATTR_LIST(last_nongc_write_time),
+ NILFS_SEGCTOR_ATTR_LIST(last_nongc_write_time_secs),
+ NILFS_SEGCTOR_ATTR_LIST(dirty_data_blocks_count),
+ NILFS_SEGCTOR_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_segctor);
+
+NILFS_DEV_INT_GROUP_OPS(segctor, dev);
+NILFS_DEV_INT_GROUP_TYPE(segctor, dev);
+NILFS_DEV_INT_GROUP_FNS(segctor, dev);
+
+/************************************************************************
+ * NILFS superblock attrs *
+ ************************************************************************/
+
+static ssize_t
+nilfs_superblock_sb_write_time_show(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t sbwtime;
+
+ down_read(&nilfs->ns_sem);
+ sbwtime = nilfs->ns_sbwtime;
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%ptTs\n", &sbwtime);
+}
+
+static ssize_t
+nilfs_superblock_sb_write_time_secs_show(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ time64_t sbwtime;
+
+ down_read(&nilfs->ns_sem);
+ sbwtime = nilfs->ns_sbwtime;
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%llu\n", sbwtime);
+}
+
+static ssize_t
+nilfs_superblock_sb_write_count_show(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ unsigned int sbwcount;
+
+ down_read(&nilfs->ns_sem);
+ sbwcount = nilfs->ns_sbwcount;
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%u\n", sbwcount);
+}
+
+static ssize_t
+nilfs_superblock_sb_update_frequency_show(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ unsigned int sb_update_freq;
+
+ down_read(&nilfs->ns_sem);
+ sb_update_freq = nilfs->ns_sb_update_freq;
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%u\n", sb_update_freq);
+}
+
+static ssize_t
+nilfs_superblock_sb_update_frequency_store(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs,
+ const char *buf, size_t count)
+{
+ unsigned int val;
+ int err;
+
+ err = kstrtouint(skip_spaces(buf), 0, &val);
+ if (err) {
+ nilfs_err(nilfs->ns_sb, "unable to convert string: err=%d",
+ err);
+ return err;
+ }
+
+ if (val < NILFS_SB_FREQ) {
+ val = NILFS_SB_FREQ;
+ nilfs_warn(nilfs->ns_sb,
+ "superblock update frequency cannot be lesser than 10 seconds");
+ }
+
+ down_write(&nilfs->ns_sem);
+ nilfs->ns_sb_update_freq = val;
+ up_write(&nilfs->ns_sem);
+
+ return count;
+}
+
+static const char sb_readme_str[] =
+ "The superblock group contains attributes that describe\n"
+ "superblock's details.\n\n"
+ "(1) sb_write_time\n\tshow previous write time of super block "
+ "in human-readable format.\n\n"
+ "(2) sb_write_time_secs\n\tshow previous write time of super block "
+ "in seconds.\n\n"
+ "(3) sb_write_count\n\tshow write count of super block.\n\n"
+ "(4) sb_update_frequency\n"
+ "\tshow/set interval of periodical update of superblock (in seconds).\n\n"
+ "\tYou can set preferable frequency of superblock update by command:\n\n"
+ "\t'echo <val> > /sys/fs/<nilfs>/<dev>/superblock/sb_update_frequency'\n";
+
+static ssize_t
+nilfs_superblock_README_show(struct nilfs_superblock_attr *attr,
+ struct the_nilfs *nilfs, char *buf)
+{
+ return sysfs_emit(buf, sb_readme_str);
+}
+
+NILFS_SUPERBLOCK_RO_ATTR(sb_write_time);
+NILFS_SUPERBLOCK_RO_ATTR(sb_write_time_secs);
+NILFS_SUPERBLOCK_RO_ATTR(sb_write_count);
+NILFS_SUPERBLOCK_RW_ATTR(sb_update_frequency);
+NILFS_SUPERBLOCK_RO_ATTR(README);
+
+static struct attribute *nilfs_superblock_attrs[] = {
+ NILFS_SUPERBLOCK_ATTR_LIST(sb_write_time),
+ NILFS_SUPERBLOCK_ATTR_LIST(sb_write_time_secs),
+ NILFS_SUPERBLOCK_ATTR_LIST(sb_write_count),
+ NILFS_SUPERBLOCK_ATTR_LIST(sb_update_frequency),
+ NILFS_SUPERBLOCK_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_superblock);
+
+NILFS_DEV_INT_GROUP_OPS(superblock, dev);
+NILFS_DEV_INT_GROUP_TYPE(superblock, dev);
+NILFS_DEV_INT_GROUP_FNS(superblock, dev);
+
+/************************************************************************
+ * NILFS device attrs *
+ ************************************************************************/
+
+static
+ssize_t nilfs_dev_revision_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ struct nilfs_super_block *raw_sb;
+ u32 major;
+ u16 minor;
+
+ down_read(&nilfs->ns_sem);
+ raw_sb = nilfs->ns_sbp[0];
+ major = le32_to_cpu(raw_sb->s_rev_level);
+ minor = le16_to_cpu(raw_sb->s_minor_rev_level);
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%d.%d\n", major, minor);
+}
+
+static
+ssize_t nilfs_dev_blocksize_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ return sysfs_emit(buf, "%u\n", nilfs->ns_blocksize);
+}
+
+static
+ssize_t nilfs_dev_device_size_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ struct nilfs_super_block *raw_sb;
+ u64 dev_size;
+
+ down_read(&nilfs->ns_sem);
+ raw_sb = nilfs->ns_sbp[0];
+ dev_size = le64_to_cpu(raw_sb->s_dev_size);
+ up_read(&nilfs->ns_sem);
+
+ return sysfs_emit(buf, "%llu\n", dev_size);
+}
+
+static
+ssize_t nilfs_dev_free_blocks_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ sector_t free_blocks = 0;
+
+ nilfs_count_free_blocks(nilfs, &free_blocks);
+ return sysfs_emit(buf, "%llu\n",
+ (unsigned long long)free_blocks);
+}
+
+static
+ssize_t nilfs_dev_uuid_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ struct nilfs_super_block *raw_sb;
+ ssize_t len;
+
+ down_read(&nilfs->ns_sem);
+ raw_sb = nilfs->ns_sbp[0];
+ len = sysfs_emit(buf, "%pUb\n", raw_sb->s_uuid);
+ up_read(&nilfs->ns_sem);
+
+ return len;
+}
+
+static
+ssize_t nilfs_dev_volume_name_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ struct nilfs_super_block *raw_sb;
+ ssize_t len;
+
+ down_read(&nilfs->ns_sem);
+ raw_sb = nilfs->ns_sbp[0];
+ len = scnprintf(buf, sizeof(raw_sb->s_volume_name), "%s\n",
+ raw_sb->s_volume_name);
+ up_read(&nilfs->ns_sem);
+
+ return len;
+}
+
+static const char dev_readme_str[] =
+ "The <device> group contains attributes that describe file system\n"
+ "partition's details.\n\n"
+ "(1) revision\n\tshow NILFS file system revision.\n\n"
+ "(2) blocksize\n\tshow volume block size in bytes.\n\n"
+ "(3) device_size\n\tshow volume size in bytes.\n\n"
+ "(4) free_blocks\n\tshow count of free blocks on volume.\n\n"
+ "(5) uuid\n\tshow volume's UUID.\n\n"
+ "(6) volume_name\n\tshow volume's name.\n\n";
+
+static ssize_t nilfs_dev_README_show(struct nilfs_dev_attr *attr,
+ struct the_nilfs *nilfs,
+ char *buf)
+{
+ return sysfs_emit(buf, dev_readme_str);
+}
+
+NILFS_DEV_RO_ATTR(revision);
+NILFS_DEV_RO_ATTR(blocksize);
+NILFS_DEV_RO_ATTR(device_size);
+NILFS_DEV_RO_ATTR(free_blocks);
+NILFS_DEV_RO_ATTR(uuid);
+NILFS_DEV_RO_ATTR(volume_name);
+NILFS_DEV_RO_ATTR(README);
+
+static struct attribute *nilfs_dev_attrs[] = {
+ NILFS_DEV_ATTR_LIST(revision),
+ NILFS_DEV_ATTR_LIST(blocksize),
+ NILFS_DEV_ATTR_LIST(device_size),
+ NILFS_DEV_ATTR_LIST(free_blocks),
+ NILFS_DEV_ATTR_LIST(uuid),
+ NILFS_DEV_ATTR_LIST(volume_name),
+ NILFS_DEV_ATTR_LIST(README),
+ NULL,
+};
+ATTRIBUTE_GROUPS(nilfs_dev);
+
+static ssize_t nilfs_dev_attr_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct the_nilfs *nilfs = container_of(kobj, struct the_nilfs,
+ ns_dev_kobj);
+ struct nilfs_dev_attr *a = container_of(attr, struct nilfs_dev_attr,
+ attr);
+
+ return a->show ? a->show(a, nilfs, buf) : 0;
+}
+
+static ssize_t nilfs_dev_attr_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, size_t len)
+{
+ struct the_nilfs *nilfs = container_of(kobj, struct the_nilfs,
+ ns_dev_kobj);
+ struct nilfs_dev_attr *a = container_of(attr, struct nilfs_dev_attr,
+ attr);
+
+ return a->store ? a->store(a, nilfs, buf, len) : 0;
+}
+
+static void nilfs_dev_attr_release(struct kobject *kobj)
+{
+ struct the_nilfs *nilfs = container_of(kobj, struct the_nilfs,
+ ns_dev_kobj);
+ complete(&nilfs->ns_dev_kobj_unregister);
+}
+
+static const struct sysfs_ops nilfs_dev_attr_ops = {
+ .show = nilfs_dev_attr_show,
+ .store = nilfs_dev_attr_store,
+};
+
+static const struct kobj_type nilfs_dev_ktype = {
+ .default_groups = nilfs_dev_groups,
+ .sysfs_ops = &nilfs_dev_attr_ops,
+ .release = nilfs_dev_attr_release,
+};
+
+int nilfs_sysfs_create_device_group(struct super_block *sb)
+{
+ struct the_nilfs *nilfs = sb->s_fs_info;
+ size_t devgrp_size = sizeof(struct nilfs_sysfs_dev_subgroups);
+ int err;
+
+ nilfs->ns_dev_subgroups = kzalloc(devgrp_size, GFP_KERNEL);
+ if (unlikely(!nilfs->ns_dev_subgroups)) {
+ err = -ENOMEM;
+ nilfs_err(sb, "unable to allocate memory for device group");
+ goto failed_create_device_group;
+ }
+
+ nilfs->ns_dev_kobj.kset = nilfs_kset;
+ init_completion(&nilfs->ns_dev_kobj_unregister);
+ err = kobject_init_and_add(&nilfs->ns_dev_kobj, &nilfs_dev_ktype, NULL,
+ "%s", sb->s_id);
+ if (err)
+ goto cleanup_dev_kobject;
+
+ err = nilfs_sysfs_create_mounted_snapshots_group(nilfs);
+ if (err)
+ goto cleanup_dev_kobject;
+
+ err = nilfs_sysfs_create_checkpoints_group(nilfs);
+ if (err)
+ goto delete_mounted_snapshots_group;
+
+ err = nilfs_sysfs_create_segments_group(nilfs);
+ if (err)
+ goto delete_checkpoints_group;
+
+ err = nilfs_sysfs_create_superblock_group(nilfs);
+ if (err)
+ goto delete_segments_group;
+
+ err = nilfs_sysfs_create_segctor_group(nilfs);
+ if (err)
+ goto delete_superblock_group;
+
+ return 0;
+
+delete_superblock_group:
+ nilfs_sysfs_delete_superblock_group(nilfs);
+
+delete_segments_group:
+ nilfs_sysfs_delete_segments_group(nilfs);
+
+delete_checkpoints_group:
+ nilfs_sysfs_delete_checkpoints_group(nilfs);
+
+delete_mounted_snapshots_group:
+ nilfs_sysfs_delete_mounted_snapshots_group(nilfs);
+
+cleanup_dev_kobject:
+ kobject_put(&nilfs->ns_dev_kobj);
+ kfree(nilfs->ns_dev_subgroups);
+
+failed_create_device_group:
+ return err;
+}
+
+void nilfs_sysfs_delete_device_group(struct the_nilfs *nilfs)
+{
+ nilfs_sysfs_delete_mounted_snapshots_group(nilfs);
+ nilfs_sysfs_delete_checkpoints_group(nilfs);
+ nilfs_sysfs_delete_segments_group(nilfs);
+ nilfs_sysfs_delete_superblock_group(nilfs);
+ nilfs_sysfs_delete_segctor_group(nilfs);
+ kobject_del(&nilfs->ns_dev_kobj);
+ kobject_put(&nilfs->ns_dev_kobj);
+ kfree(nilfs->ns_dev_subgroups);
+}
+
+/************************************************************************
+ * NILFS feature attrs *
+ ************************************************************************/
+
+static ssize_t nilfs_feature_revision_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%d.%d\n",
+ NILFS_CURRENT_REV, NILFS_MINOR_REV);
+}
+
+static const char features_readme_str[] =
+ "The features group contains attributes that describe NILFS file\n"
+ "system driver features.\n\n"
+ "(1) revision\n\tshow current revision of NILFS file system driver.\n";
+
+static ssize_t nilfs_feature_README_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, features_readme_str);
+}
+
+NILFS_FEATURE_RO_ATTR(revision);
+NILFS_FEATURE_RO_ATTR(README);
+
+static struct attribute *nilfs_feature_attrs[] = {
+ NILFS_FEATURE_ATTR_LIST(revision),
+ NILFS_FEATURE_ATTR_LIST(README),
+ NULL,
+};
+
+static const struct attribute_group nilfs_feature_attr_group = {
+ .name = "features",
+ .attrs = nilfs_feature_attrs,
+};
+
+int __init nilfs_sysfs_init(void)
+{
+ int err;
+
+ nilfs_kset = kset_create_and_add(NILFS_ROOT_GROUP_NAME, NULL, fs_kobj);
+ if (!nilfs_kset) {
+ err = -ENOMEM;
+ nilfs_err(NULL, "unable to create sysfs entry: err=%d", err);
+ goto failed_sysfs_init;
+ }
+
+ err = sysfs_create_group(&nilfs_kset->kobj, &nilfs_feature_attr_group);
+ if (unlikely(err)) {
+ nilfs_err(NULL, "unable to create feature group: err=%d", err);
+ goto cleanup_sysfs_init;
+ }
+
+ return 0;
+
+cleanup_sysfs_init:
+ kset_unregister(nilfs_kset);
+
+failed_sysfs_init:
+ return err;
+}
+
+void nilfs_sysfs_exit(void)
+{
+ sysfs_remove_group(&nilfs_kset->kobj, &nilfs_feature_attr_group);
+ kset_unregister(nilfs_kset);
+}
diff --git a/fs/nilfs2/sysfs.h b/fs/nilfs2/sysfs.h
new file mode 100644
index 000000000..d370cd5cc
--- /dev/null
+++ b/fs/nilfs2/sysfs.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Sysfs support declarations.
+ *
+ * Copyright (C) 2005-2014 Nippon Telegraph and Telephone Corporation.
+ * Copyright (C) 2014 HGST, Inc., a Western Digital Company.
+ *
+ * Written by Vyacheslav Dubeyko <Vyacheslav.Dubeyko@hgst.com>
+ */
+
+#ifndef _NILFS_SYSFS_H
+#define _NILFS_SYSFS_H
+
+#include <linux/sysfs.h>
+
+#define NILFS_ROOT_GROUP_NAME "nilfs2"
+
+/*
+ * struct nilfs_sysfs_dev_subgroups - device subgroup kernel objects
+ * @sg_superblock_kobj: /sys/fs/<nilfs>/<device>/superblock
+ * @sg_superblock_kobj_unregister: completion state
+ * @sg_segctor_kobj: /sys/fs/<nilfs>/<device>/segctor
+ * @sg_segctor_kobj_unregister: completion state
+ * @sg_mounted_snapshots_kobj: /sys/fs/<nilfs>/<device>/mounted_snapshots
+ * @sg_mounted_snapshots_kobj_unregister: completion state
+ * @sg_checkpoints_kobj: /sys/fs/<nilfs>/<device>/checkpoints
+ * @sg_checkpoints_kobj_unregister: completion state
+ * @sg_segments_kobj: /sys/fs/<nilfs>/<device>/segments
+ * @sg_segments_kobj_unregister: completion state
+ */
+struct nilfs_sysfs_dev_subgroups {
+ /* /sys/fs/<nilfs>/<device>/superblock */
+ struct kobject sg_superblock_kobj;
+ struct completion sg_superblock_kobj_unregister;
+
+ /* /sys/fs/<nilfs>/<device>/segctor */
+ struct kobject sg_segctor_kobj;
+ struct completion sg_segctor_kobj_unregister;
+
+ /* /sys/fs/<nilfs>/<device>/mounted_snapshots */
+ struct kobject sg_mounted_snapshots_kobj;
+ struct completion sg_mounted_snapshots_kobj_unregister;
+
+ /* /sys/fs/<nilfs>/<device>/checkpoints */
+ struct kobject sg_checkpoints_kobj;
+ struct completion sg_checkpoints_kobj_unregister;
+
+ /* /sys/fs/<nilfs>/<device>/segments */
+ struct kobject sg_segments_kobj;
+ struct completion sg_segments_kobj_unregister;
+};
+
+#define NILFS_KOBJ_ATTR_STRUCT(name) \
+struct nilfs_##name##_attr { \
+ struct attribute attr; \
+ ssize_t (*show)(struct kobject *, struct kobj_attribute *, \
+ char *); \
+ ssize_t (*store)(struct kobject *, struct kobj_attribute *, \
+ const char *, size_t); \
+}
+
+NILFS_KOBJ_ATTR_STRUCT(feature);
+
+#define NILFS_DEV_ATTR_STRUCT(name) \
+struct nilfs_##name##_attr { \
+ struct attribute attr; \
+ ssize_t (*show)(struct nilfs_##name##_attr *, struct the_nilfs *, \
+ char *); \
+ ssize_t (*store)(struct nilfs_##name##_attr *, struct the_nilfs *, \
+ const char *, size_t); \
+}
+
+NILFS_DEV_ATTR_STRUCT(dev);
+NILFS_DEV_ATTR_STRUCT(segments);
+NILFS_DEV_ATTR_STRUCT(mounted_snapshots);
+NILFS_DEV_ATTR_STRUCT(checkpoints);
+NILFS_DEV_ATTR_STRUCT(superblock);
+NILFS_DEV_ATTR_STRUCT(segctor);
+
+#define NILFS_CP_ATTR_STRUCT(name) \
+struct nilfs_##name##_attr { \
+ struct attribute attr; \
+ ssize_t (*show)(struct nilfs_##name##_attr *, struct nilfs_root *, \
+ char *); \
+ ssize_t (*store)(struct nilfs_##name##_attr *, struct nilfs_root *, \
+ const char *, size_t); \
+}
+
+NILFS_CP_ATTR_STRUCT(snapshot);
+
+#define NILFS_ATTR(type, name, mode, show, store) \
+ static struct nilfs_##type##_attr nilfs_##type##_attr_##name = \
+ __ATTR(name, mode, show, store)
+
+#define NILFS_INFO_ATTR(type, name) \
+ NILFS_ATTR(type, name, 0444, NULL, NULL)
+#define NILFS_RO_ATTR(type, name) \
+ NILFS_ATTR(type, name, 0444, nilfs_##type##_##name##_show, NULL)
+#define NILFS_RW_ATTR(type, name) \
+ NILFS_ATTR(type, name, 0644, \
+ nilfs_##type##_##name##_show, \
+ nilfs_##type##_##name##_store)
+
+#define NILFS_FEATURE_INFO_ATTR(name) \
+ NILFS_INFO_ATTR(feature, name)
+#define NILFS_FEATURE_RO_ATTR(name) \
+ NILFS_RO_ATTR(feature, name)
+#define NILFS_FEATURE_RW_ATTR(name) \
+ NILFS_RW_ATTR(feature, name)
+
+#define NILFS_DEV_INFO_ATTR(name) \
+ NILFS_INFO_ATTR(dev, name)
+#define NILFS_DEV_RO_ATTR(name) \
+ NILFS_RO_ATTR(dev, name)
+#define NILFS_DEV_RW_ATTR(name) \
+ NILFS_RW_ATTR(dev, name)
+
+#define NILFS_SEGMENTS_RO_ATTR(name) \
+ NILFS_RO_ATTR(segments, name)
+#define NILFS_SEGMENTS_RW_ATTR(name) \
+ NILFS_RW_ATTR(segs_info, name)
+
+#define NILFS_MOUNTED_SNAPSHOTS_RO_ATTR(name) \
+ NILFS_RO_ATTR(mounted_snapshots, name)
+
+#define NILFS_CHECKPOINTS_RO_ATTR(name) \
+ NILFS_RO_ATTR(checkpoints, name)
+#define NILFS_CHECKPOINTS_RW_ATTR(name) \
+ NILFS_RW_ATTR(checkpoints, name)
+
+#define NILFS_SNAPSHOT_INFO_ATTR(name) \
+ NILFS_INFO_ATTR(snapshot, name)
+#define NILFS_SNAPSHOT_RO_ATTR(name) \
+ NILFS_RO_ATTR(snapshot, name)
+#define NILFS_SNAPSHOT_RW_ATTR(name) \
+ NILFS_RW_ATTR(snapshot, name)
+
+#define NILFS_SUPERBLOCK_RO_ATTR(name) \
+ NILFS_RO_ATTR(superblock, name)
+#define NILFS_SUPERBLOCK_RW_ATTR(name) \
+ NILFS_RW_ATTR(superblock, name)
+
+#define NILFS_SEGCTOR_INFO_ATTR(name) \
+ NILFS_INFO_ATTR(segctor, name)
+#define NILFS_SEGCTOR_RO_ATTR(name) \
+ NILFS_RO_ATTR(segctor, name)
+#define NILFS_SEGCTOR_RW_ATTR(name) \
+ NILFS_RW_ATTR(segctor, name)
+
+#define NILFS_FEATURE_ATTR_LIST(name) \
+ (&nilfs_feature_attr_##name.attr)
+#define NILFS_DEV_ATTR_LIST(name) \
+ (&nilfs_dev_attr_##name.attr)
+#define NILFS_SEGMENTS_ATTR_LIST(name) \
+ (&nilfs_segments_attr_##name.attr)
+#define NILFS_MOUNTED_SNAPSHOTS_ATTR_LIST(name) \
+ (&nilfs_mounted_snapshots_attr_##name.attr)
+#define NILFS_CHECKPOINTS_ATTR_LIST(name) \
+ (&nilfs_checkpoints_attr_##name.attr)
+#define NILFS_SNAPSHOT_ATTR_LIST(name) \
+ (&nilfs_snapshot_attr_##name.attr)
+#define NILFS_SUPERBLOCK_ATTR_LIST(name) \
+ (&nilfs_superblock_attr_##name.attr)
+#define NILFS_SEGCTOR_ATTR_LIST(name) \
+ (&nilfs_segctor_attr_##name.attr)
+
+#endif /* _NILFS_SYSFS_H */
diff --git a/fs/nilfs2/the_nilfs.c b/fs/nilfs2/the_nilfs.c
new file mode 100644
index 000000000..f3805e7aa
--- /dev/null
+++ b/fs/nilfs2/the_nilfs.c
@@ -0,0 +1,945 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * the_nilfs shared structure.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+
+#include <linux/buffer_head.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/log2.h>
+#include <linux/crc32.h>
+#include "nilfs.h"
+#include "segment.h"
+#include "alloc.h"
+#include "cpfile.h"
+#include "sufile.h"
+#include "dat.h"
+#include "segbuf.h"
+
+
+static int nilfs_valid_sb(struct nilfs_super_block *sbp);
+
+void nilfs_set_last_segment(struct the_nilfs *nilfs,
+ sector_t start_blocknr, u64 seq, __u64 cno)
+{
+ spin_lock(&nilfs->ns_last_segment_lock);
+ nilfs->ns_last_pseg = start_blocknr;
+ nilfs->ns_last_seq = seq;
+ nilfs->ns_last_cno = cno;
+
+ if (!nilfs_sb_dirty(nilfs)) {
+ if (nilfs->ns_prev_seq == nilfs->ns_last_seq)
+ goto stay_cursor;
+
+ set_nilfs_sb_dirty(nilfs);
+ }
+ nilfs->ns_prev_seq = nilfs->ns_last_seq;
+
+ stay_cursor:
+ spin_unlock(&nilfs->ns_last_segment_lock);
+}
+
+/**
+ * alloc_nilfs - allocate a nilfs object
+ * @sb: super block instance
+ *
+ * Return: a pointer to the allocated nilfs object on success, or NULL on
+ * failure.
+ */
+struct the_nilfs *alloc_nilfs(struct super_block *sb)
+{
+ struct the_nilfs *nilfs;
+
+ nilfs = kzalloc_obj(*nilfs);
+ if (!nilfs)
+ return NULL;
+
+ nilfs->ns_sb = sb;
+ nilfs->ns_bdev = sb->s_bdev;
+ atomic_set(&nilfs->ns_ndirtyblks, 0);
+ init_rwsem(&nilfs->ns_sem);
+ mutex_init(&nilfs->ns_snapshot_mount_mutex);
+ INIT_LIST_HEAD(&nilfs->ns_dirty_files);
+ INIT_LIST_HEAD(&nilfs->ns_gc_inodes);
+ spin_lock_init(&nilfs->ns_inode_lock);
+ spin_lock_init(&nilfs->ns_last_segment_lock);
+ nilfs->ns_cptree = RB_ROOT;
+ spin_lock_init(&nilfs->ns_cptree_lock);
+ init_rwsem(&nilfs->ns_segctor_sem);
+ nilfs->ns_sb_update_freq = NILFS_SB_FREQ;
+
+ return nilfs;
+}
+
+/**
+ * destroy_nilfs - destroy nilfs object
+ * @nilfs: nilfs object to be released
+ */
+void destroy_nilfs(struct the_nilfs *nilfs)
+{
+ might_sleep();
+ if (nilfs_init(nilfs)) {
+ brelse(nilfs->ns_sbh[0]);
+ brelse(nilfs->ns_sbh[1]);
+ }
+ kfree(nilfs);
+}
+
+static int nilfs_load_super_root(struct the_nilfs *nilfs,
+ struct super_block *sb, sector_t sr_block)
+{
+ struct buffer_head *bh_sr;
+ struct nilfs_super_root *raw_sr;
+ struct nilfs_super_block **sbp = nilfs->ns_sbp;
+ struct nilfs_inode *rawi;
+ unsigned int dat_entry_size, segment_usage_size, checkpoint_size;
+ unsigned int inode_size;
+ int err;
+
+ err = nilfs_read_super_root_block(nilfs, sr_block, &bh_sr, 1);
+ if (unlikely(err))
+ return err;
+
+ down_read(&nilfs->ns_sem);
+ dat_entry_size = le16_to_cpu(sbp[0]->s_dat_entry_size);
+ checkpoint_size = le16_to_cpu(sbp[0]->s_checkpoint_size);
+ segment_usage_size = le16_to_cpu(sbp[0]->s_segment_usage_size);
+ up_read(&nilfs->ns_sem);
+
+ inode_size = nilfs->ns_inode_size;
+
+ rawi = (void *)bh_sr->b_data + NILFS_SR_DAT_OFFSET(inode_size);
+ err = nilfs_dat_read(sb, dat_entry_size, rawi, &nilfs->ns_dat);
+ if (err)
+ goto failed;
+
+ rawi = (void *)bh_sr->b_data + NILFS_SR_CPFILE_OFFSET(inode_size);
+ err = nilfs_cpfile_read(sb, checkpoint_size, rawi, &nilfs->ns_cpfile);
+ if (err)
+ goto failed_dat;
+
+ rawi = (void *)bh_sr->b_data + NILFS_SR_SUFILE_OFFSET(inode_size);
+ err = nilfs_sufile_read(sb, segment_usage_size, rawi,
+ &nilfs->ns_sufile);
+ if (err)
+ goto failed_cpfile;
+
+ raw_sr = (struct nilfs_super_root *)bh_sr->b_data;
+ nilfs->ns_nongc_ctime = le64_to_cpu(raw_sr->sr_nongc_ctime);
+
+ failed:
+ brelse(bh_sr);
+ return err;
+
+ failed_cpfile:
+ iput(nilfs->ns_cpfile);
+
+ failed_dat:
+ iput(nilfs->ns_dat);
+ goto failed;
+}
+
+static void nilfs_init_recovery_info(struct nilfs_recovery_info *ri)
+{
+ memset(ri, 0, sizeof(*ri));
+ INIT_LIST_HEAD(&ri->ri_used_segments);
+}
+
+static void nilfs_clear_recovery_info(struct nilfs_recovery_info *ri)
+{
+ nilfs_dispose_segment_list(&ri->ri_used_segments);
+}
+
+/**
+ * nilfs_store_log_cursor - load log cursor from a super block
+ * @nilfs: nilfs object
+ * @sbp: buffer storing super block to be read
+ *
+ * nilfs_store_log_cursor() reads the last position of the log
+ * containing a super root from a given super block, and initializes
+ * relevant information on the nilfs object preparatory for log
+ * scanning and recovery.
+ *
+ * Return: 0 on success, or %-EINVAL if current segment number is out
+ * of range.
+ */
+static int nilfs_store_log_cursor(struct the_nilfs *nilfs,
+ struct nilfs_super_block *sbp)
+{
+ int ret = 0;
+
+ nilfs->ns_last_pseg = le64_to_cpu(sbp->s_last_pseg);
+ nilfs->ns_last_cno = le64_to_cpu(sbp->s_last_cno);
+ nilfs->ns_last_seq = le64_to_cpu(sbp->s_last_seq);
+
+ nilfs->ns_prev_seq = nilfs->ns_last_seq;
+ nilfs->ns_seg_seq = nilfs->ns_last_seq;
+ nilfs->ns_segnum =
+ nilfs_get_segnum_of_block(nilfs, nilfs->ns_last_pseg);
+ nilfs->ns_cno = nilfs->ns_last_cno + 1;
+ if (nilfs->ns_segnum >= nilfs->ns_nsegments) {
+ nilfs_err(nilfs->ns_sb,
+ "pointed segment number is out of range: segnum=%llu, nsegments=%lu",
+ (unsigned long long)nilfs->ns_segnum,
+ nilfs->ns_nsegments);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/**
+ * nilfs_get_blocksize - get block size from raw superblock data
+ * @sb: super block instance
+ * @sbp: superblock raw data buffer
+ * @blocksize: place to store block size
+ *
+ * nilfs_get_blocksize() calculates the block size from the block size
+ * exponent information written in @sbp and stores it in @blocksize,
+ * or aborts with an error message if it's too large.
+ *
+ * Return: 0 on success, or %-EINVAL if the block size is too large.
+ */
+static int nilfs_get_blocksize(struct super_block *sb,
+ struct nilfs_super_block *sbp, int *blocksize)
+{
+ unsigned int shift_bits = le32_to_cpu(sbp->s_log_block_size);
+
+ if (unlikely(shift_bits >
+ ilog2(NILFS_MAX_BLOCK_SIZE) - BLOCK_SIZE_BITS)) {
+ nilfs_err(sb, "too large filesystem blocksize: 2 ^ %u KiB",
+ shift_bits);
+ return -EINVAL;
+ }
+ *blocksize = BLOCK_SIZE << shift_bits;
+ return 0;
+}
+
+/**
+ * load_nilfs - load and recover the nilfs
+ * @nilfs: the_nilfs structure to be released
+ * @sb: super block instance used to recover past segment
+ *
+ * load_nilfs() searches and load the latest super root,
+ * attaches the last segment, and does recovery if needed.
+ * The caller must call this exclusively for simultaneous mounts.
+ *
+ * Return: 0 on success, or one of the following negative error codes on
+ * failure:
+ * * %-EINVAL - No valid segment found.
+ * * %-EIO - I/O error.
+ * * %-ENOMEM - Insufficient memory available.
+ * * %-EROFS - Read only device or RO compat mode (if recovery is required)
+ */
+int load_nilfs(struct the_nilfs *nilfs, struct super_block *sb)
+{
+ struct nilfs_recovery_info ri;
+ unsigned int s_flags = sb->s_flags;
+ int really_read_only = bdev_read_only(nilfs->ns_bdev);
+ int valid_fs = nilfs_valid_fs(nilfs);
+ int err;
+
+ if (!valid_fs) {
+ nilfs_warn(sb, "mounting unchecked fs");
+ if (s_flags & SB_RDONLY) {
+ nilfs_info(sb,
+ "recovery required for readonly filesystem");
+ nilfs_info(sb,
+ "write access will be enabled during recovery");
+ }
+ }
+
+ nilfs_init_recovery_info(&ri);
+
+ err = nilfs_search_super_root(nilfs, &ri);
+ if (unlikely(err)) {
+ struct nilfs_super_block **sbp = nilfs->ns_sbp;
+ int blocksize;
+
+ if (err != -EINVAL)
+ goto scan_error;
+
+ if (!nilfs_valid_sb(sbp[1])) {
+ nilfs_warn(sb,
+ "unable to fall back to spare super block");
+ goto scan_error;
+ }
+ nilfs_info(sb, "trying rollback from an earlier position");
+
+ /*
+ * restore super block with its spare and reconfigure
+ * relevant states of the nilfs object.
+ */
+ memcpy(sbp[0], sbp[1], nilfs->ns_sbsize);
+ nilfs->ns_crc_seed = le32_to_cpu(sbp[0]->s_crc_seed);
+ nilfs->ns_sbwtime = le64_to_cpu(sbp[0]->s_wtime);
+
+ /* verify consistency between two super blocks */
+ err = nilfs_get_blocksize(sb, sbp[0], &blocksize);
+ if (err)
+ goto scan_error;
+
+ if (blocksize != nilfs->ns_blocksize) {
+ nilfs_warn(sb,
+ "blocksize differs between two super blocks (%d != %d)",
+ blocksize, nilfs->ns_blocksize);
+ err = -EINVAL;
+ goto scan_error;
+ }
+
+ err = nilfs_store_log_cursor(nilfs, sbp[0]);
+ if (err)
+ goto scan_error;
+
+ /* drop clean flag to allow roll-forward and recovery */
+ nilfs->ns_mount_state &= ~NILFS_VALID_FS;
+ valid_fs = 0;
+
+ err = nilfs_search_super_root(nilfs, &ri);
+ if (err)
+ goto scan_error;
+ }
+
+ err = nilfs_load_super_root(nilfs, sb, ri.ri_super_root);
+ if (unlikely(err)) {
+ nilfs_err(sb, "error %d while loading super root", err);
+ goto failed;
+ }
+
+ err = nilfs_sysfs_create_device_group(sb);
+ if (unlikely(err))
+ goto sysfs_error;
+
+ if (valid_fs)
+ goto skip_recovery;
+
+ if (s_flags & SB_RDONLY) {
+ __u64 features;
+
+ if (nilfs_test_opt(nilfs, NORECOVERY)) {
+ nilfs_info(sb,
+ "norecovery option specified, skipping roll-forward recovery");
+ goto skip_recovery;
+ }
+ features = le64_to_cpu(nilfs->ns_sbp[0]->s_feature_compat_ro) &
+ ~NILFS_FEATURE_COMPAT_RO_SUPP;
+ if (features) {
+ nilfs_err(sb,
+ "couldn't proceed with recovery because of unsupported optional features (%llx)",
+ (unsigned long long)features);
+ err = -EROFS;
+ goto failed_unload;
+ }
+ if (really_read_only) {
+ nilfs_err(sb,
+ "write access unavailable, cannot proceed");
+ err = -EROFS;
+ goto failed_unload;
+ }
+ sb->s_flags &= ~SB_RDONLY;
+ } else if (nilfs_test_opt(nilfs, NORECOVERY)) {
+ nilfs_err(sb,
+ "recovery cancelled because norecovery option was specified for a read/write mount");
+ err = -EINVAL;
+ goto failed_unload;
+ }
+
+ err = nilfs_salvage_orphan_logs(nilfs, sb, &ri);
+ if (err)
+ goto failed_unload;
+
+ down_write(&nilfs->ns_sem);
+ nilfs->ns_mount_state |= NILFS_VALID_FS; /* set "clean" flag */
+ err = nilfs_cleanup_super(sb);
+ up_write(&nilfs->ns_sem);
+
+ if (err) {
+ nilfs_err(sb,
+ "error %d updating super block. recovery unfinished.",
+ err);
+ goto failed_unload;
+ }
+ nilfs_info(sb, "recovery complete");
+
+ skip_recovery:
+ nilfs_clear_recovery_info(&ri);
+ sb->s_flags = s_flags;
+ return 0;
+
+ scan_error:
+ nilfs_err(sb, "error %d while searching super root", err);
+ goto failed;
+
+ failed_unload:
+ nilfs_sysfs_delete_device_group(nilfs);
+
+ sysfs_error:
+ iput(nilfs->ns_cpfile);
+ iput(nilfs->ns_sufile);
+ iput(nilfs->ns_dat);
+
+ failed:
+ nilfs_clear_recovery_info(&ri);
+ sb->s_flags = s_flags;
+ return err;
+}
+
+static unsigned long long nilfs_max_size(unsigned int blkbits)
+{
+ unsigned int max_bits;
+ unsigned long long res = MAX_LFS_FILESIZE; /* page cache limit */
+
+ max_bits = blkbits + NILFS_BMAP_KEY_BIT; /* bmap size limit */
+ if (max_bits < 64)
+ res = min_t(unsigned long long, res, (1ULL << max_bits) - 1);
+ return res;
+}
+
+/**
+ * nilfs_nrsvsegs - calculate the number of reserved segments
+ * @nilfs: nilfs object
+ * @nsegs: total number of segments
+ *
+ * Return: Number of reserved segments.
+ */
+unsigned long nilfs_nrsvsegs(struct the_nilfs *nilfs, unsigned long nsegs)
+{
+ return max_t(unsigned long, NILFS_MIN_NRSVSEGS,
+ DIV_ROUND_UP(nsegs * nilfs->ns_r_segments_percentage,
+ 100));
+}
+
+/**
+ * nilfs_max_segment_count - calculate the maximum number of segments
+ * @nilfs: nilfs object
+ *
+ * Return: Maximum number of segments
+ */
+static u64 nilfs_max_segment_count(struct the_nilfs *nilfs)
+{
+ u64 max_count = U64_MAX;
+
+ max_count = div64_ul(max_count, nilfs->ns_blocks_per_segment);
+ return min_t(u64, max_count, ULONG_MAX);
+}
+
+void nilfs_set_nsegments(struct the_nilfs *nilfs, unsigned long nsegs)
+{
+ nilfs->ns_nsegments = nsegs;
+ nilfs->ns_nrsvsegs = nilfs_nrsvsegs(nilfs, nsegs);
+}
+
+static int nilfs_store_disk_layout(struct the_nilfs *nilfs,
+ struct nilfs_super_block *sbp)
+{
+ u64 nsegments, nblocks;
+
+ if (le32_to_cpu(sbp->s_rev_level) < NILFS_MIN_SUPP_REV) {
+ nilfs_err(nilfs->ns_sb,
+ "unsupported revision (superblock rev.=%d.%d, current rev.=%d.%d). Please check the version of mkfs.nilfs(2).",
+ le32_to_cpu(sbp->s_rev_level),
+ le16_to_cpu(sbp->s_minor_rev_level),
+ NILFS_CURRENT_REV, NILFS_MINOR_REV);
+ return -EINVAL;
+ }
+ nilfs->ns_sbsize = le16_to_cpu(sbp->s_bytes);
+ if (nilfs->ns_sbsize > BLOCK_SIZE)
+ return -EINVAL;
+
+ nilfs->ns_inode_size = le16_to_cpu(sbp->s_inode_size);
+ if (nilfs->ns_inode_size > nilfs->ns_blocksize) {
+ nilfs_err(nilfs->ns_sb, "too large inode size: %d bytes",
+ nilfs->ns_inode_size);
+ return -EINVAL;
+ } else if (nilfs->ns_inode_size < NILFS_MIN_INODE_SIZE) {
+ nilfs_err(nilfs->ns_sb, "too small inode size: %d bytes",
+ nilfs->ns_inode_size);
+ return -EINVAL;
+ }
+ if (NILFS_SR_BYTES(nilfs->ns_inode_size) > nilfs->ns_blocksize) {
+ nilfs_err(nilfs->ns_sb,
+ "too large inode size for super root: %d bytes",
+ nilfs->ns_inode_size);
+ return -EINVAL;
+ }
+
+ nilfs->ns_first_ino = le32_to_cpu(sbp->s_first_ino);
+ if (nilfs->ns_first_ino < NILFS_USER_INO) {
+ nilfs_err(nilfs->ns_sb,
+ "too small lower limit for non-reserved inode numbers: %u",
+ nilfs->ns_first_ino);
+ return -EINVAL;
+ }
+
+ nilfs->ns_blocks_per_segment = le32_to_cpu(sbp->s_blocks_per_segment);
+ if (nilfs->ns_blocks_per_segment < NILFS_SEG_MIN_BLOCKS) {
+ nilfs_err(nilfs->ns_sb, "too short segment: %lu blocks",
+ nilfs->ns_blocks_per_segment);
+ return -EINVAL;
+ }
+
+ nilfs->ns_first_data_block = le64_to_cpu(sbp->s_first_data_block);
+ nilfs->ns_r_segments_percentage =
+ le32_to_cpu(sbp->s_r_segments_percentage);
+ if (nilfs->ns_r_segments_percentage < 1 ||
+ nilfs->ns_r_segments_percentage > 99) {
+ nilfs_err(nilfs->ns_sb,
+ "invalid reserved segments percentage: %lu",
+ nilfs->ns_r_segments_percentage);
+ return -EINVAL;
+ }
+
+ nsegments = le64_to_cpu(sbp->s_nsegments);
+ if (nsegments > nilfs_max_segment_count(nilfs)) {
+ nilfs_err(nilfs->ns_sb,
+ "segment count %llu exceeds upper limit (%llu segments)",
+ (unsigned long long)nsegments,
+ (unsigned long long)nilfs_max_segment_count(nilfs));
+ return -EINVAL;
+ }
+
+ nblocks = sb_bdev_nr_blocks(nilfs->ns_sb);
+ if (nblocks) {
+ u64 min_block_count = nsegments * nilfs->ns_blocks_per_segment;
+ /*
+ * To avoid failing to mount early device images without a
+ * second superblock, exclude that block count from the
+ * "min_block_count" calculation.
+ */
+
+ if (nblocks < min_block_count) {
+ nilfs_err(nilfs->ns_sb,
+ "total number of segment blocks %llu exceeds device size (%llu blocks)",
+ (unsigned long long)min_block_count,
+ (unsigned long long)nblocks);
+ return -EINVAL;
+ }
+ }
+
+ nilfs_set_nsegments(nilfs, nsegments);
+ nilfs->ns_crc_seed = le32_to_cpu(sbp->s_crc_seed);
+ return 0;
+}
+
+static int nilfs_valid_sb(struct nilfs_super_block *sbp)
+{
+ static unsigned char sum[4];
+ const int sumoff = offsetof(struct nilfs_super_block, s_sum);
+ size_t bytes;
+ u32 crc;
+
+ if (!sbp || le16_to_cpu(sbp->s_magic) != NILFS_SUPER_MAGIC)
+ return 0;
+ bytes = le16_to_cpu(sbp->s_bytes);
+ if (bytes < sumoff + 4 || bytes > BLOCK_SIZE)
+ return 0;
+ crc = crc32_le(le32_to_cpu(sbp->s_crc_seed), (unsigned char *)sbp,
+ sumoff);
+ crc = crc32_le(crc, sum, 4);
+ crc = crc32_le(crc, (unsigned char *)sbp + sumoff + 4,
+ bytes - sumoff - 4);
+ return crc == le32_to_cpu(sbp->s_sum);
+}
+
+/**
+ * nilfs_sb2_bad_offset - check the location of the second superblock
+ * @sbp: superblock raw data buffer
+ * @offset: byte offset of second superblock calculated from device size
+ *
+ * nilfs_sb2_bad_offset() checks if the position on the second
+ * superblock is valid or not based on the filesystem parameters
+ * stored in @sbp. If @offset points to a location within the segment
+ * area, or if the parameters themselves are not normal, it is
+ * determined to be invalid.
+ *
+ * Return: true if invalid, false if valid.
+ */
+static bool nilfs_sb2_bad_offset(struct nilfs_super_block *sbp, u64 offset)
+{
+ unsigned int shift_bits = le32_to_cpu(sbp->s_log_block_size);
+ u32 blocks_per_segment = le32_to_cpu(sbp->s_blocks_per_segment);
+ u64 nsegments = le64_to_cpu(sbp->s_nsegments);
+ u64 index;
+
+ if (blocks_per_segment < NILFS_SEG_MIN_BLOCKS ||
+ shift_bits > ilog2(NILFS_MAX_BLOCK_SIZE) - BLOCK_SIZE_BITS)
+ return true;
+
+ index = offset >> (shift_bits + BLOCK_SIZE_BITS);
+ do_div(index, blocks_per_segment);
+ return index < nsegments;
+}
+
+static void nilfs_release_super_block(struct the_nilfs *nilfs)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (nilfs->ns_sbp[i]) {
+ brelse(nilfs->ns_sbh[i]);
+ nilfs->ns_sbh[i] = NULL;
+ nilfs->ns_sbp[i] = NULL;
+ }
+ }
+}
+
+void nilfs_fall_back_super_block(struct the_nilfs *nilfs)
+{
+ brelse(nilfs->ns_sbh[0]);
+ nilfs->ns_sbh[0] = nilfs->ns_sbh[1];
+ nilfs->ns_sbp[0] = nilfs->ns_sbp[1];
+ nilfs->ns_sbh[1] = NULL;
+ nilfs->ns_sbp[1] = NULL;
+}
+
+void nilfs_swap_super_block(struct the_nilfs *nilfs)
+{
+ struct buffer_head *tsbh = nilfs->ns_sbh[0];
+ struct nilfs_super_block *tsbp = nilfs->ns_sbp[0];
+
+ nilfs->ns_sbh[0] = nilfs->ns_sbh[1];
+ nilfs->ns_sbp[0] = nilfs->ns_sbp[1];
+ nilfs->ns_sbh[1] = tsbh;
+ nilfs->ns_sbp[1] = tsbp;
+}
+
+static int nilfs_load_super_block(struct the_nilfs *nilfs,
+ struct super_block *sb, int blocksize,
+ struct nilfs_super_block **sbpp)
+{
+ struct nilfs_super_block **sbp = nilfs->ns_sbp;
+ struct buffer_head **sbh = nilfs->ns_sbh;
+ u64 sb2off, devsize = bdev_nr_bytes(nilfs->ns_bdev);
+ int valid[2], swp = 0, older;
+
+ if (devsize < NILFS_SEG_MIN_BLOCKS * NILFS_MIN_BLOCK_SIZE + 4096) {
+ nilfs_err(sb, "device size too small");
+ return -EINVAL;
+ }
+ sb2off = NILFS_SB2_OFFSET_BYTES(devsize);
+
+ sbp[0] = nilfs_read_super_block(sb, NILFS_SB_OFFSET_BYTES, blocksize,
+ &sbh[0]);
+ sbp[1] = nilfs_read_super_block(sb, sb2off, blocksize, &sbh[1]);
+
+ if (!sbp[0]) {
+ if (!sbp[1]) {
+ nilfs_err(sb, "unable to read superblock");
+ return -EIO;
+ }
+ nilfs_warn(sb,
+ "unable to read primary superblock (blocksize = %d)",
+ blocksize);
+ } else if (!sbp[1]) {
+ nilfs_warn(sb,
+ "unable to read secondary superblock (blocksize = %d)",
+ blocksize);
+ }
+
+ /*
+ * Compare two super blocks and set 1 in swp if the secondary
+ * super block is valid and newer. Otherwise, set 0 in swp.
+ */
+ valid[0] = nilfs_valid_sb(sbp[0]);
+ valid[1] = nilfs_valid_sb(sbp[1]);
+ swp = valid[1] && (!valid[0] ||
+ le64_to_cpu(sbp[1]->s_last_cno) >
+ le64_to_cpu(sbp[0]->s_last_cno));
+
+ if (valid[swp] && nilfs_sb2_bad_offset(sbp[swp], sb2off)) {
+ brelse(sbh[1]);
+ sbh[1] = NULL;
+ sbp[1] = NULL;
+ valid[1] = 0;
+ swp = 0;
+ }
+ if (!valid[swp]) {
+ nilfs_release_super_block(nilfs);
+ nilfs_err(sb, "couldn't find nilfs on the device");
+ return -EINVAL;
+ }
+
+ if (!valid[!swp])
+ nilfs_warn(sb,
+ "broken superblock, retrying with spare superblock (blocksize = %d)",
+ blocksize);
+ if (swp)
+ nilfs_swap_super_block(nilfs);
+
+ /*
+ * Calculate the array index of the older superblock data.
+ * If one has been dropped, set index 0 pointing to the remaining one,
+ * otherwise set index 1 pointing to the old one (including if both
+ * are the same).
+ *
+ * Divided case valid[0] valid[1] swp -> older
+ * -------------------------------------------------------------
+ * Both SBs are invalid 0 0 N/A (Error)
+ * SB1 is invalid 0 1 1 0
+ * SB2 is invalid 1 0 0 0
+ * SB2 is newer 1 1 1 0
+ * SB2 is older or the same 1 1 0 1
+ */
+ older = valid[1] ^ swp;
+
+ nilfs->ns_sbwcount = 0;
+ nilfs->ns_sbwtime = le64_to_cpu(sbp[0]->s_wtime);
+ nilfs->ns_prot_seq = le64_to_cpu(sbp[older]->s_last_seq);
+ *sbpp = sbp[0];
+ return 0;
+}
+
+/**
+ * init_nilfs - initialize a NILFS instance.
+ * @nilfs: the_nilfs structure
+ * @sb: super block
+ *
+ * init_nilfs() performs common initialization per block device (e.g.
+ * reading the super block, getting disk layout information, initializing
+ * shared fields in the_nilfs).
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int init_nilfs(struct the_nilfs *nilfs, struct super_block *sb)
+{
+ struct nilfs_super_block *sbp;
+ int blocksize;
+ int err;
+
+ blocksize = sb_min_blocksize(sb, NILFS_MIN_BLOCK_SIZE);
+ if (!blocksize) {
+ nilfs_err(sb, "unable to set blocksize");
+ err = -EINVAL;
+ goto out;
+ }
+ err = nilfs_load_super_block(nilfs, sb, blocksize, &sbp);
+ if (err)
+ goto out;
+
+ err = nilfs_store_magic(sb, sbp);
+ if (err)
+ goto failed_sbh;
+
+ err = nilfs_check_feature_compatibility(sb, sbp);
+ if (err)
+ goto failed_sbh;
+
+ err = nilfs_get_blocksize(sb, sbp, &blocksize);
+ if (err)
+ goto failed_sbh;
+
+ if (blocksize < NILFS_MIN_BLOCK_SIZE) {
+ nilfs_err(sb,
+ "couldn't mount because of unsupported filesystem blocksize %d",
+ blocksize);
+ err = -EINVAL;
+ goto failed_sbh;
+ }
+ if (sb->s_blocksize != blocksize) {
+ int hw_blocksize = bdev_logical_block_size(sb->s_bdev);
+
+ if (blocksize < hw_blocksize) {
+ nilfs_err(sb,
+ "blocksize %d too small for device (sector-size = %d)",
+ blocksize, hw_blocksize);
+ err = -EINVAL;
+ goto failed_sbh;
+ }
+ nilfs_release_super_block(nilfs);
+ if (!sb_set_blocksize(sb, blocksize)) {
+ nilfs_err(sb, "bad blocksize %d", blocksize);
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = nilfs_load_super_block(nilfs, sb, blocksize, &sbp);
+ if (err)
+ goto out;
+ /*
+ * Not to failed_sbh; sbh is released automatically
+ * when reloading fails.
+ */
+ }
+ nilfs->ns_blocksize_bits = sb->s_blocksize_bits;
+ nilfs->ns_blocksize = blocksize;
+
+ err = nilfs_store_disk_layout(nilfs, sbp);
+ if (err)
+ goto failed_sbh;
+
+ sb->s_maxbytes = nilfs_max_size(sb->s_blocksize_bits);
+
+ nilfs->ns_mount_state = le16_to_cpu(sbp->s_state);
+
+ err = nilfs_store_log_cursor(nilfs, sbp);
+ if (err)
+ goto failed_sbh;
+
+ set_nilfs_init(nilfs);
+ err = 0;
+ out:
+ return err;
+
+ failed_sbh:
+ nilfs_release_super_block(nilfs);
+ goto out;
+}
+
+int nilfs_discard_segments(struct the_nilfs *nilfs, __u64 *segnump,
+ size_t nsegs)
+{
+ sector_t seg_start, seg_end;
+ sector_t start = 0, nblocks = 0;
+ unsigned int sects_per_block;
+ __u64 *sn;
+ int ret = 0;
+
+ sects_per_block = (1 << nilfs->ns_blocksize_bits) /
+ bdev_logical_block_size(nilfs->ns_bdev);
+ for (sn = segnump; sn < segnump + nsegs; sn++) {
+ nilfs_get_segment_range(nilfs, *sn, &seg_start, &seg_end);
+
+ if (!nblocks) {
+ start = seg_start;
+ nblocks = seg_end - seg_start + 1;
+ } else if (start + nblocks == seg_start) {
+ nblocks += seg_end - seg_start + 1;
+ } else {
+ ret = blkdev_issue_discard(nilfs->ns_bdev,
+ start * sects_per_block,
+ nblocks * sects_per_block,
+ GFP_NOFS);
+ if (ret < 0)
+ return ret;
+ nblocks = 0;
+ }
+ }
+ if (nblocks)
+ ret = blkdev_issue_discard(nilfs->ns_bdev,
+ start * sects_per_block,
+ nblocks * sects_per_block,
+ GFP_NOFS);
+ return ret;
+}
+
+int nilfs_count_free_blocks(struct the_nilfs *nilfs, sector_t *nblocks)
+{
+ unsigned long ncleansegs;
+
+ ncleansegs = nilfs_sufile_get_ncleansegs(nilfs->ns_sufile);
+ *nblocks = (sector_t)ncleansegs * nilfs->ns_blocks_per_segment;
+ return 0;
+}
+
+int nilfs_near_disk_full(struct the_nilfs *nilfs)
+{
+ unsigned long ncleansegs, nincsegs;
+
+ ncleansegs = nilfs_sufile_get_ncleansegs(nilfs->ns_sufile);
+ nincsegs = atomic_read(&nilfs->ns_ndirtyblks) /
+ nilfs->ns_blocks_per_segment + 1;
+
+ return ncleansegs <= nilfs->ns_nrsvsegs + nincsegs;
+}
+
+struct nilfs_root *nilfs_lookup_root(struct the_nilfs *nilfs, __u64 cno)
+{
+ struct rb_node *n;
+ struct nilfs_root *root;
+
+ spin_lock(&nilfs->ns_cptree_lock);
+ n = nilfs->ns_cptree.rb_node;
+ while (n) {
+ root = rb_entry(n, struct nilfs_root, rb_node);
+
+ if (cno < root->cno) {
+ n = n->rb_left;
+ } else if (cno > root->cno) {
+ n = n->rb_right;
+ } else {
+ refcount_inc(&root->count);
+ spin_unlock(&nilfs->ns_cptree_lock);
+ return root;
+ }
+ }
+ spin_unlock(&nilfs->ns_cptree_lock);
+
+ return NULL;
+}
+
+struct nilfs_root *
+nilfs_find_or_create_root(struct the_nilfs *nilfs, __u64 cno)
+{
+ struct rb_node **p, *parent;
+ struct nilfs_root *root, *new;
+ int err;
+
+ root = nilfs_lookup_root(nilfs, cno);
+ if (root)
+ return root;
+
+ new = kzalloc_obj(*root);
+ if (!new)
+ return NULL;
+
+ spin_lock(&nilfs->ns_cptree_lock);
+
+ p = &nilfs->ns_cptree.rb_node;
+ parent = NULL;
+
+ while (*p) {
+ parent = *p;
+ root = rb_entry(parent, struct nilfs_root, rb_node);
+
+ if (cno < root->cno) {
+ p = &(*p)->rb_left;
+ } else if (cno > root->cno) {
+ p = &(*p)->rb_right;
+ } else {
+ refcount_inc(&root->count);
+ spin_unlock(&nilfs->ns_cptree_lock);
+ kfree(new);
+ return root;
+ }
+ }
+
+ new->cno = cno;
+ new->ifile = NULL;
+ new->nilfs = nilfs;
+ refcount_set(&new->count, 1);
+ atomic64_set(&new->inodes_count, 0);
+ atomic64_set(&new->blocks_count, 0);
+
+ rb_link_node(&new->rb_node, parent, p);
+ rb_insert_color(&new->rb_node, &nilfs->ns_cptree);
+
+ spin_unlock(&nilfs->ns_cptree_lock);
+
+ err = nilfs_sysfs_create_snapshot_group(new);
+ if (err) {
+ kfree(new);
+ new = NULL;
+ }
+
+ return new;
+}
+
+void nilfs_put_root(struct nilfs_root *root)
+{
+ struct the_nilfs *nilfs = root->nilfs;
+
+ if (refcount_dec_and_lock(&root->count, &nilfs->ns_cptree_lock)) {
+ rb_erase(&root->rb_node, &nilfs->ns_cptree);
+ spin_unlock(&nilfs->ns_cptree_lock);
+
+ nilfs_sysfs_delete_snapshot_group(root);
+ iput(root->ifile);
+
+ kfree(root);
+ }
+}
diff --git a/fs/nilfs2/the_nilfs.h b/fs/nilfs2/the_nilfs.h
new file mode 100644
index 000000000..4776a70f0
--- /dev/null
+++ b/fs/nilfs2/the_nilfs.h
@@ -0,0 +1,376 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * the_nilfs shared structure.
+ *
+ * Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
+ *
+ * Written by Ryusuke Konishi.
+ *
+ */
+
+#ifndef _THE_NILFS_H
+#define _THE_NILFS_H
+
+#include <linux/types.h>
+#include <linux/buffer_head.h>
+#include <linux/rbtree.h>
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/slab.h>
+#include <linux/refcount.h>
+
+struct nilfs_sc_info;
+struct nilfs_sysfs_dev_subgroups;
+
+/* the_nilfs struct */
+enum {
+ THE_NILFS_INIT = 0, /* Information from super_block is set */
+ THE_NILFS_DISCONTINUED, /* 'next' pointer chain has broken */
+ THE_NILFS_GC_RUNNING, /* gc process is running */
+ THE_NILFS_SB_DIRTY, /* super block is dirty */
+ THE_NILFS_PURGING, /* disposing dirty files for cleanup */
+};
+
+/**
+ * struct the_nilfs - struct to supervise multiple nilfs mount points
+ * @ns_flags: flags
+ * @ns_flushed_device: flag indicating if all volatile data was flushed
+ * @ns_sb: back pointer to super block instance
+ * @ns_bdev: block device
+ * @ns_sem: semaphore for shared states
+ * @ns_snapshot_mount_mutex: mutex to protect snapshot mounts
+ * @ns_sbh: buffer heads of on-disk super blocks
+ * @ns_sbp: pointers to super block data
+ * @ns_sbwtime: previous write time of super block
+ * @ns_sbwcount: write count of super block
+ * @ns_sbsize: size of valid data in super block
+ * @ns_mount_state: file system state
+ * @ns_sb_update_freq: interval of periodical update of superblocks (in seconds)
+ * @ns_seg_seq: segment sequence counter
+ * @ns_segnum: index number of the latest full segment.
+ * @ns_nextnum: index number of the full segment index to be used next
+ * @ns_pseg_offset: offset of next partial segment in the current full segment
+ * @ns_cno: next checkpoint number
+ * @ns_ctime: write time of the last segment
+ * @ns_nongc_ctime: write time of the last segment not for cleaner operation
+ * @ns_ndirtyblks: Number of dirty data blocks
+ * @ns_last_segment_lock: lock protecting fields for the latest segment
+ * @ns_last_pseg: start block number of the latest segment
+ * @ns_last_seq: sequence value of the latest segment
+ * @ns_last_cno: checkpoint number of the latest segment
+ * @ns_prot_seq: least sequence number of segments which must not be reclaimed
+ * @ns_prev_seq: base sequence number used to decide if advance log cursor
+ * @ns_writer: log writer
+ * @ns_segctor_sem: semaphore protecting log write
+ * @ns_dat: DAT file inode
+ * @ns_cpfile: checkpoint file inode
+ * @ns_sufile: segusage file inode
+ * @ns_cptree: rb-tree of all mounted checkpoints (nilfs_root)
+ * @ns_cptree_lock: lock protecting @ns_cptree
+ * @ns_dirty_files: list of dirty files
+ * @ns_inode_lock: lock protecting @ns_dirty_files
+ * @ns_gc_inodes: dummy inodes to keep live blocks
+ * @ns_mount_opt: mount options
+ * @ns_resuid: uid for reserved blocks
+ * @ns_resgid: gid for reserved blocks
+ * @ns_interval: checkpoint creation interval
+ * @ns_watermark: watermark for the number of dirty buffers
+ * @ns_blocksize_bits: bit length of block size
+ * @ns_blocksize: block size
+ * @ns_nsegments: number of segments in filesystem
+ * @ns_blocks_per_segment: number of blocks per segment
+ * @ns_r_segments_percentage: reserved segments percentage
+ * @ns_nrsvsegs: number of reserved segments
+ * @ns_first_data_block: block number of first data block
+ * @ns_inode_size: size of on-disk inode
+ * @ns_first_ino: first not-special inode number
+ * @ns_crc_seed: seed value of CRC32 calculation
+ * @ns_dev_kobj: /sys/fs/<nilfs>/<device>
+ * @ns_dev_kobj_unregister: completion state
+ * @ns_dev_subgroups: <device> subgroups pointer
+ */
+struct the_nilfs {
+ unsigned long ns_flags;
+ int ns_flushed_device;
+
+ struct super_block *ns_sb;
+ struct block_device *ns_bdev;
+ struct rw_semaphore ns_sem;
+ struct mutex ns_snapshot_mount_mutex;
+
+ /*
+ * used for
+ * - loading the latest checkpoint exclusively.
+ * - allocating a new full segment.
+ */
+ struct buffer_head *ns_sbh[2];
+ struct nilfs_super_block *ns_sbp[2];
+ time64_t ns_sbwtime;
+ unsigned int ns_sbwcount;
+ unsigned int ns_sbsize;
+ unsigned int ns_mount_state;
+ unsigned int ns_sb_update_freq;
+
+ /*
+ * The following fields are updated by a writable FS-instance.
+ * These fields are protected by ns_segctor_sem outside load_nilfs().
+ */
+ u64 ns_seg_seq;
+ __u64 ns_segnum;
+ __u64 ns_nextnum;
+ unsigned long ns_pseg_offset;
+ __u64 ns_cno;
+ time64_t ns_ctime;
+ time64_t ns_nongc_ctime;
+ atomic_t ns_ndirtyblks;
+
+ /*
+ * The following fields hold information on the latest partial segment
+ * written to disk with a super root. These fields are protected by
+ * ns_last_segment_lock.
+ */
+ spinlock_t ns_last_segment_lock;
+ sector_t ns_last_pseg;
+ u64 ns_last_seq;
+ __u64 ns_last_cno;
+ u64 ns_prot_seq;
+ u64 ns_prev_seq;
+
+ struct nilfs_sc_info *ns_writer;
+ struct rw_semaphore ns_segctor_sem;
+
+ /*
+ * Following fields are lock free except for the period before
+ * the_nilfs is initialized.
+ */
+ struct inode *ns_dat;
+ struct inode *ns_cpfile;
+ struct inode *ns_sufile;
+
+ /* Checkpoint tree */
+ struct rb_root ns_cptree;
+ spinlock_t ns_cptree_lock;
+
+ /* Dirty inode list */
+ struct list_head ns_dirty_files;
+ spinlock_t ns_inode_lock;
+
+ /* GC inode list */
+ struct list_head ns_gc_inodes;
+
+ /* Mount options */
+ unsigned long ns_mount_opt;
+
+ uid_t ns_resuid;
+ gid_t ns_resgid;
+ unsigned long ns_interval;
+ unsigned long ns_watermark;
+
+ /* Disk layout information (static) */
+ unsigned int ns_blocksize_bits;
+ unsigned int ns_blocksize;
+ unsigned long ns_nsegments;
+ unsigned long ns_blocks_per_segment;
+ unsigned long ns_r_segments_percentage;
+ unsigned long ns_nrsvsegs;
+ unsigned long ns_first_data_block;
+ int ns_inode_size;
+ unsigned int ns_first_ino;
+ u32 ns_crc_seed;
+
+ /* /sys/fs/<nilfs>/<device> */
+ struct kobject ns_dev_kobj;
+ struct completion ns_dev_kobj_unregister;
+ struct nilfs_sysfs_dev_subgroups *ns_dev_subgroups;
+};
+
+#define THE_NILFS_FNS(bit, name) \
+static inline void set_nilfs_##name(struct the_nilfs *nilfs) \
+{ \
+ set_bit(THE_NILFS_##bit, &(nilfs)->ns_flags); \
+} \
+static inline void clear_nilfs_##name(struct the_nilfs *nilfs) \
+{ \
+ clear_bit(THE_NILFS_##bit, &(nilfs)->ns_flags); \
+} \
+static inline int nilfs_##name(struct the_nilfs *nilfs) \
+{ \
+ return test_bit(THE_NILFS_##bit, &(nilfs)->ns_flags); \
+}
+
+THE_NILFS_FNS(INIT, init)
+THE_NILFS_FNS(DISCONTINUED, discontinued)
+THE_NILFS_FNS(GC_RUNNING, gc_running)
+THE_NILFS_FNS(SB_DIRTY, sb_dirty)
+THE_NILFS_FNS(PURGING, purging)
+
+/*
+ * Mount option operations
+ */
+#define nilfs_clear_opt(nilfs, opt) \
+ ((nilfs)->ns_mount_opt &= ~NILFS_MOUNT_##opt)
+#define nilfs_set_opt(nilfs, opt) \
+ ((nilfs)->ns_mount_opt |= NILFS_MOUNT_##opt)
+#define nilfs_test_opt(nilfs, opt) ((nilfs)->ns_mount_opt & NILFS_MOUNT_##opt)
+
+/**
+ * struct nilfs_root - nilfs root object
+ * @cno: checkpoint number
+ * @rb_node: red-black tree node
+ * @count: refcount of this structure
+ * @nilfs: nilfs object
+ * @ifile: inode file
+ * @inodes_count: number of inodes
+ * @blocks_count: number of blocks
+ * @snapshot_kobj: /sys/fs/<nilfs>/<device>/mounted_snapshots/<snapshot>
+ * @snapshot_kobj_unregister: completion state for kernel object
+ */
+struct nilfs_root {
+ __u64 cno;
+ struct rb_node rb_node;
+
+ refcount_t count;
+ struct the_nilfs *nilfs;
+ struct inode *ifile;
+
+ atomic64_t inodes_count;
+ atomic64_t blocks_count;
+
+ /* /sys/fs/<nilfs>/<device>/mounted_snapshots/<snapshot> */
+ struct kobject snapshot_kobj;
+ struct completion snapshot_kobj_unregister;
+};
+
+/* Special checkpoint number */
+#define NILFS_CPTREE_CURRENT_CNO 0
+
+/* Minimum interval of periodical update of superblocks (in seconds) */
+#define NILFS_SB_FREQ 10
+
+static inline int nilfs_sb_need_update(struct the_nilfs *nilfs)
+{
+ u64 t = ktime_get_real_seconds();
+
+ return t < nilfs->ns_sbwtime ||
+ t > nilfs->ns_sbwtime + nilfs->ns_sb_update_freq;
+}
+
+static inline int nilfs_sb_will_flip(struct the_nilfs *nilfs)
+{
+ int flip_bits = nilfs->ns_sbwcount & 0x0FL;
+
+ return (flip_bits != 0x08 && flip_bits != 0x0F);
+}
+
+void nilfs_set_last_segment(struct the_nilfs *, sector_t, u64, __u64);
+struct the_nilfs *alloc_nilfs(struct super_block *sb);
+void destroy_nilfs(struct the_nilfs *nilfs);
+int init_nilfs(struct the_nilfs *nilfs, struct super_block *sb);
+int load_nilfs(struct the_nilfs *nilfs, struct super_block *sb);
+unsigned long nilfs_nrsvsegs(struct the_nilfs *nilfs, unsigned long nsegs);
+void nilfs_set_nsegments(struct the_nilfs *nilfs, unsigned long nsegs);
+int nilfs_discard_segments(struct the_nilfs *, __u64 *, size_t);
+int nilfs_count_free_blocks(struct the_nilfs *, sector_t *);
+struct nilfs_root *nilfs_lookup_root(struct the_nilfs *nilfs, __u64 cno);
+struct nilfs_root *nilfs_find_or_create_root(struct the_nilfs *nilfs,
+ __u64 cno);
+void nilfs_put_root(struct nilfs_root *root);
+int nilfs_near_disk_full(struct the_nilfs *);
+void nilfs_fall_back_super_block(struct the_nilfs *);
+void nilfs_swap_super_block(struct the_nilfs *);
+
+
+static inline void nilfs_get_root(struct nilfs_root *root)
+{
+ refcount_inc(&root->count);
+}
+
+static inline int nilfs_valid_fs(struct the_nilfs *nilfs)
+{
+ unsigned int valid_fs;
+
+ down_read(&nilfs->ns_sem);
+ valid_fs = (nilfs->ns_mount_state & NILFS_VALID_FS);
+ up_read(&nilfs->ns_sem);
+ return valid_fs;
+}
+
+static inline void
+nilfs_get_segment_range(struct the_nilfs *nilfs, __u64 segnum,
+ sector_t *seg_start, sector_t *seg_end)
+{
+ *seg_start = (sector_t)nilfs->ns_blocks_per_segment * segnum;
+ *seg_end = *seg_start + nilfs->ns_blocks_per_segment - 1;
+ if (segnum == 0)
+ *seg_start = nilfs->ns_first_data_block;
+}
+
+static inline sector_t
+nilfs_get_segment_start_blocknr(struct the_nilfs *nilfs, __u64 segnum)
+{
+ return (segnum == 0) ? nilfs->ns_first_data_block :
+ (sector_t)nilfs->ns_blocks_per_segment * segnum;
+}
+
+static inline __u64
+nilfs_get_segnum_of_block(struct the_nilfs *nilfs, sector_t blocknr)
+{
+ sector_t segnum = blocknr;
+
+ sector_div(segnum, nilfs->ns_blocks_per_segment);
+ return segnum;
+}
+
+static inline void
+nilfs_terminate_segment(struct the_nilfs *nilfs, sector_t seg_start,
+ sector_t seg_end)
+{
+ /* terminate the current full segment (used in case of I/O-error) */
+ nilfs->ns_pseg_offset = seg_end - seg_start + 1;
+}
+
+static inline void nilfs_shift_to_next_segment(struct the_nilfs *nilfs)
+{
+ /* move forward with a full segment */
+ nilfs->ns_segnum = nilfs->ns_nextnum;
+ nilfs->ns_pseg_offset = 0;
+ nilfs->ns_seg_seq++;
+}
+
+static inline __u64 nilfs_last_cno(struct the_nilfs *nilfs)
+{
+ __u64 cno;
+
+ spin_lock(&nilfs->ns_last_segment_lock);
+ cno = nilfs->ns_last_cno;
+ spin_unlock(&nilfs->ns_last_segment_lock);
+ return cno;
+}
+
+static inline int nilfs_segment_is_active(struct the_nilfs *nilfs, __u64 n)
+{
+ return n == nilfs->ns_segnum || n == nilfs->ns_nextnum;
+}
+
+static inline int nilfs_flush_device(struct the_nilfs *nilfs)
+{
+ int err;
+
+ if (!nilfs_test_opt(nilfs, BARRIER) || nilfs->ns_flushed_device)
+ return 0;
+
+ nilfs->ns_flushed_device = 1;
+ /*
+ * the store to ns_flushed_device must not be reordered after
+ * blkdev_issue_flush().
+ */
+ smp_wmb();
+
+ err = blkdev_issue_flush(nilfs->ns_bdev);
+ if (err != -EIO)
+ err = 0;
+ return err;
+}
+
+#endif /* _THE_NILFS_H */