[RFC 04/18] pkernfs: support file truncation

From: James Gowans
Date: Mon Feb 05 2024 - 07:04:53 EST


In the previous commit a block allocator was added. Now use that block
allocator to allocate blocks for files when ftruncate is run on them.

To do that a inode_operations is added on the file inodes with a getattr
callback handling the ATTR_SIZE attribute. When this is invoked pages
are allocated, the indexes of which are put into a mappings block.
The mappings block is an array with the index being the file offset
block and the value at that index being the pkernfs block backign that
file offset.
---
fs/pkernfs/Makefile | 2 +-
fs/pkernfs/allocator.c | 24 +++++++++++++++++++
fs/pkernfs/file.c | 53 ++++++++++++++++++++++++++++++++++++++++++
fs/pkernfs/inode.c | 27 ++++++++++++++++++---
fs/pkernfs/pkernfs.h | 7 ++++++
5 files changed, 109 insertions(+), 4 deletions(-)
create mode 100644 fs/pkernfs/file.c

diff --git a/fs/pkernfs/Makefile b/fs/pkernfs/Makefile
index d8b92a74fbc6..e41f06cc490f 100644
--- a/fs/pkernfs/Makefile
+++ b/fs/pkernfs/Makefile
@@ -3,4 +3,4 @@
# Makefile for persistent kernel filesystem
#

-obj-$(CONFIG_PKERNFS_FS) += pkernfs.o inode.o allocator.o dir.o
+obj-$(CONFIG_PKERNFS_FS) += pkernfs.o inode.o allocator.o dir.o file.o
diff --git a/fs/pkernfs/allocator.c b/fs/pkernfs/allocator.c
index 1d4aac9c4545..3905ce92b4a9 100644
--- a/fs/pkernfs/allocator.c
+++ b/fs/pkernfs/allocator.c
@@ -25,3 +25,27 @@ void pkernfs_zero_allocations(struct super_block *sb)
/* Second page is inode store */
set_bit(1, pkernfs_allocations_bitmap(sb));
}
+
+/*
+ * Allocs one 2 MiB block, and returns the block index.
+ * Index is 2 MiB chunk index.
+ */
+unsigned long pkernfs_alloc_block(struct super_block *sb)
+{
+ unsigned long free_bit;
+
+ /* Allocations is 2nd half of first page */
+ void *allocations_mem = pkernfs_allocations_bitmap(sb);
+ free_bit = bitmap_find_next_zero_area(allocations_mem,
+ PMD_SIZE / 2, /* Size */
+ 0, /* Start */
+ 1, /* Number of zeroed bits to look for */
+ 0); /* Alignment mask - none required. */
+ bitmap_set(allocations_mem, free_bit, 1);
+ return free_bit;
+}
+
+void *pkernfs_addr_for_block(struct super_block *sb, int block_idx)
+{
+ return pkernfs_mem + (block_idx * PMD_SIZE);
+}
diff --git a/fs/pkernfs/file.c b/fs/pkernfs/file.c
new file mode 100644
index 000000000000..27a637423178
--- /dev/null
+++ b/fs/pkernfs/file.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "pkernfs.h"
+
+static int truncate(struct inode *inode, loff_t newsize)
+{
+ unsigned long free_block;
+ struct pkernfs_inode *pkernfs_inode;
+ unsigned long *mappings;
+
+ pkernfs_inode = pkernfs_get_persisted_inode(inode->i_sb, inode->i_ino);
+ mappings = (unsigned long *)pkernfs_addr_for_block(inode->i_sb,
+ pkernfs_inode->mappings_block);
+ i_size_write(inode, newsize);
+ for (int block_idx = 0; block_idx * PMD_SIZE < newsize; ++block_idx) {
+ free_block = pkernfs_alloc_block(inode->i_sb);
+ if (free_block <= 0)
+ /* TODO: roll back allocations. */
+ return -ENOMEM;
+ *(mappings + block_idx) = free_block;
+ ++pkernfs_inode->num_mappings;
+ }
+ return 0;
+}
+
+static int inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr)
+{
+ struct inode *inode = dentry->d_inode;
+ int error;
+
+ error = setattr_prepare(idmap, dentry, iattr);
+ if (error)
+ return error;
+
+ if (iattr->ia_valid & ATTR_SIZE) {
+ error = truncate(inode, iattr->ia_size);
+ if (error)
+ return error;
+ }
+ setattr_copy(idmap, inode, iattr);
+ mark_inode_dirty(inode);
+ return 0;
+}
+
+const struct inode_operations pkernfs_file_inode_operations = {
+ .setattr = inode_setattr,
+ .getattr = simple_getattr,
+};
+
+const struct file_operations pkernfs_file_fops = {
+ .owner = THIS_MODULE,
+ .iterate_shared = NULL,
+};
diff --git a/fs/pkernfs/inode.c b/fs/pkernfs/inode.c
index f6584c8b8804..7fe4e7b220cc 100644
--- a/fs/pkernfs/inode.c
+++ b/fs/pkernfs/inode.c
@@ -15,14 +15,28 @@ struct pkernfs_inode *pkernfs_get_persisted_inode(struct super_block *sb, int in

struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino)
{
+ struct pkernfs_inode *pkernfs_inode;
struct inode *inode = iget_locked(sb, ino);

/* If this inode is cached it is already populated; just return */
if (!(inode->i_state & I_NEW))
return inode;
- inode->i_op = &pkernfs_dir_inode_operations;
+ pkernfs_inode = pkernfs_get_persisted_inode(sb, ino);
inode->i_sb = sb;
- inode->i_mode = S_IFREG;
+ if (pkernfs_inode->flags & PKERNFS_INODE_FLAG_DIR) {
+ inode->i_op = &pkernfs_dir_inode_operations;
+ inode->i_mode = S_IFDIR;
+ } else {
+ inode->i_op = &pkernfs_file_inode_operations;
+ inode->i_mode = S_IFREG;
+ inode->i_fop = &pkernfs_file_fops;
+ }
+
+ inode->i_atime = inode->i_mtime = current_time(inode);
+ inode_set_ctime_current(inode);
+ set_nlink(inode, 1);
+
+ /* Switch based on file type */
unlock_new_inode(inode);
return inode;
}
@@ -79,6 +93,8 @@ static int pkernfs_create(struct mnt_idmap *id, struct inode *dir,
pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino)->child_ino = free_inode;
strscpy(pkernfs_inode->filename, dentry->d_name.name, PKERNFS_FILENAME_LEN);
pkernfs_inode->flags = PKERNFS_INODE_FLAG_FILE;
+ pkernfs_inode->mappings_block = pkernfs_alloc_block(dir->i_sb);
+ memset(pkernfs_addr_for_block(dir->i_sb, pkernfs_inode->mappings_block), 0, (2 << 20));

vfs_inode = pkernfs_inode_get(dir->i_sb, free_inode);
d_instantiate(dentry, vfs_inode);
@@ -90,6 +106,7 @@ static struct dentry *pkernfs_lookup(struct inode *dir,
unsigned int flags)
{
struct pkernfs_inode *pkernfs_inode;
+ struct inode *vfs_inode;
unsigned long ino;

pkernfs_inode = pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino);
@@ -97,7 +114,10 @@ static struct dentry *pkernfs_lookup(struct inode *dir,
while (ino) {
pkernfs_inode = pkernfs_get_persisted_inode(dir->i_sb, ino);
if (!strncmp(pkernfs_inode->filename, dentry->d_name.name, PKERNFS_FILENAME_LEN)) {
- d_add(dentry, pkernfs_inode_get(dir->i_sb, ino));
+ vfs_inode = pkernfs_inode_get(dir->i_sb, ino);
+ mark_inode_dirty(dir);
+ dir->i_atime = current_time(dir);
+ d_add(dentry, vfs_inode);
break;
}
ino = pkernfs_inode->sibling_ino;
@@ -146,3 +166,4 @@ const struct inode_operations pkernfs_dir_inode_operations = {
.lookup = pkernfs_lookup,
.unlink = pkernfs_unlink,
};
+
diff --git a/fs/pkernfs/pkernfs.h b/fs/pkernfs/pkernfs.h
index 4655780f31f2..8b4fee8c5b2e 100644
--- a/fs/pkernfs/pkernfs.h
+++ b/fs/pkernfs/pkernfs.h
@@ -34,8 +34,15 @@ struct pkernfs_inode {
};

void pkernfs_initialise_inode_store(struct super_block *sb);
+
void pkernfs_zero_allocations(struct super_block *sb);
+unsigned long pkernfs_alloc_block(struct super_block *sb);
struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino);
+void *pkernfs_addr_for_block(struct super_block *sb, int block_idx);
+
struct pkernfs_inode *pkernfs_get_persisted_inode(struct super_block *sb, int ino);

+
extern const struct file_operations pkernfs_dir_fops;
+extern const struct file_operations pkernfs_file_fops;
+extern const struct inode_operations pkernfs_file_inode_operations;
--
2.40.1