@@ -3,4 +3,4 @@
# Makefile for persistent kernel filesystem
#
-obj-$(CONFIG_PKERNFS_FS) += pkernfs.o
+obj-$(CONFIG_PKERNFS_FS) += pkernfs.o inode.o dir.o
new file mode 100644
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "pkernfs.h"
+
+static int pkernfs_dir_iterate(struct file *dir, struct dir_context *ctx)
+{
+ struct pkernfs_inode *pkernfs_inode;
+ struct super_block *sb = dir->f_inode->i_sb;
+
+ /* Indication from previous invoke that there's no more to iterate. */
+ if (ctx->pos == -1)
+ return 0;
+
+ if (!dir_emit_dots(dir, ctx))
+ return 0;
+
+ /*
+ * Just emitted this dir; go to dir contents. Use pos to smuggle
+ * the next inode number to emit across iterations.
+ * -1 indicates no valid inode. Can't use 0 because first loop has pos=0
+ */
+ if (ctx->pos == 2) {
+ ctx->pos = pkernfs_get_persisted_inode(sb, dir->f_inode->i_ino)->child_ino;
+ /* Empty dir case. */
+ if (ctx->pos == 0)
+ ctx->pos = -1;
+ }
+
+ while (ctx->pos > 1) {
+ pkernfs_inode = pkernfs_get_persisted_inode(sb, ctx->pos);
+ dir_emit(ctx, pkernfs_inode->filename, PKERNFS_FILENAME_LEN,
+ ctx->pos, DT_UNKNOWN);
+ ctx->pos = pkernfs_inode->sibling_ino;
+ if (!ctx->pos)
+ ctx->pos = -1;
+ }
+ return 0;
+}
+
+const struct file_operations pkernfs_dir_fops = {
+ .owner = THIS_MODULE,
+ .iterate_shared = pkernfs_dir_iterate,
+};
new file mode 100644
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "pkernfs.h"
+#include <linux/fs.h>
+
+const struct inode_operations pkernfs_dir_inode_operations;
+
+struct pkernfs_inode *pkernfs_get_persisted_inode(struct super_block *sb, int ino)
+{
+ /*
+ * Inode index starts at 1, so -1 to get memory index.
+ */
+ return ((struct pkernfs_inode *) (pkernfs_mem + PMD_SIZE)) + ino - 1;
+}
+
+struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino)
+{
+ 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;
+ inode->i_sb = sb;
+ inode->i_mode = S_IFREG;
+ unlock_new_inode(inode);
+ return inode;
+}
+
+static unsigned long pkernfs_allocate_inode(struct super_block *sb)
+{
+
+ unsigned long next_free_ino;
+ struct pkernfs_sb *psb = (struct pkernfs_sb *) pkernfs_mem;
+
+ next_free_ino = psb->next_free_ino;
+ if (!next_free_ino)
+ return -ENOMEM;
+ psb->next_free_ino =
+ pkernfs_get_persisted_inode(sb, next_free_ino)->sibling_ino;
+ return next_free_ino;
+}
+
+/*
+ * Zeroes the inode and makes it the head of the free list.
+ */
+static void pkernfs_free_inode(struct super_block *sb, unsigned long ino)
+{
+ struct pkernfs_sb *psb = (struct pkernfs_sb *) pkernfs_mem;
+ struct pkernfs_inode *inode = pkernfs_get_persisted_inode(sb, ino);
+
+ memset(inode, 0, sizeof(struct pkernfs_inode));
+ inode->sibling_ino = psb->next_free_ino;
+ psb->next_free_ino = ino;
+}
+
+void pkernfs_initialise_inode_store(struct super_block *sb)
+{
+ /* Inode store is a PMD sized (ie: 2 MiB) page */
+ memset(pkernfs_get_persisted_inode(sb, 1), 0, PMD_SIZE);
+ /* Point each inode for the next one; linked-list initialisation. */
+ for (unsigned long ino = 2; ino * sizeof(struct pkernfs_inode) < PMD_SIZE; ino++)
+ pkernfs_get_persisted_inode(sb, ino - 1)->sibling_ino = ino;
+}
+
+static int pkernfs_create(struct mnt_idmap *id, struct inode *dir,
+ struct dentry *dentry, umode_t mode, bool excl)
+{
+ unsigned long free_inode;
+ struct pkernfs_inode *pkernfs_inode;
+ struct inode *vfs_inode;
+
+ free_inode = pkernfs_allocate_inode(dir->i_sb);
+ if (free_inode <= 0)
+ return -ENOMEM;
+
+ pkernfs_inode = pkernfs_get_persisted_inode(dir->i_sb, free_inode);
+ pkernfs_inode->sibling_ino = pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino)->child_ino;
+ 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;
+
+ vfs_inode = pkernfs_inode_get(dir->i_sb, free_inode);
+ d_instantiate(dentry, vfs_inode);
+ return 0;
+}
+
+static struct dentry *pkernfs_lookup(struct inode *dir,
+ struct dentry *dentry,
+ unsigned int flags)
+{
+ struct pkernfs_inode *pkernfs_inode;
+ unsigned long ino;
+
+ pkernfs_inode = pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino);
+ ino = pkernfs_inode->child_ino;
+ 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));
+ break;
+ }
+ ino = pkernfs_inode->sibling_ino;
+ }
+ return NULL;
+}
+
+static int pkernfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ unsigned long ino;
+ struct pkernfs_inode *inode;
+
+ ino = pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino)->child_ino;
+
+ /* Special case for first file in dir */
+ if (ino == dentry->d_inode->i_ino) {
+ pkernfs_get_persisted_inode(dir->i_sb, dir->i_ino)->child_ino =
+ pkernfs_get_persisted_inode(dir->i_sb, dentry->d_inode->i_ino)->sibling_ino;
+ pkernfs_free_inode(dir->i_sb, ino);
+ return 0;
+ }
+
+ /*
+ * Although we know exactly the inode to free, because we maintain only
+ * a singly linked list we need to scan for it to find the previous
+ * element so it's "next" pointer can be updated.
+ */
+ while (ino) {
+ inode = pkernfs_get_persisted_inode(dir->i_sb, ino);
+ /* We've found the one pointing to the one we want to delete */
+ if (inode->sibling_ino == dentry->d_inode->i_ino) {
+ inode->sibling_ino =
+ pkernfs_get_persisted_inode(dir->i_sb,
+ dentry->d_inode->i_ino)->sibling_ino;
+ pkernfs_free_inode(dir->i_sb, dentry->d_inode->i_ino);
+ break;
+ }
+ ino = pkernfs_get_persisted_inode(dir->i_sb, ino)->sibling_ino;
+ }
+
+ return 0;
+}
+
+const struct inode_operations pkernfs_dir_inode_operations = {
+ .create = pkernfs_create,
+ .lookup = pkernfs_lookup,
+ .unlink = pkernfs_unlink,
+};
@@ -8,7 +8,7 @@
#include <linux/io.h>
static phys_addr_t pkernfs_base, pkernfs_size;
-static void *pkernfs_mem;
+void *pkernfs_mem;
static const struct super_operations pkernfs_super_ops = { };
static int pkernfs_fill_super(struct super_block *sb, struct fs_context *fc)
@@ -24,23 +24,26 @@ static int pkernfs_fill_super(struct super_block *sb, struct fs_context *fc)
pr_info("pkernfs: Restoring from super block\n");
} else {
pr_info("pkernfs: Clean super block; initialising\n");
+ pkernfs_initialise_inode_store(sb);
psb->magic_number = PKERNFS_MAGIC_NUMBER;
+ pkernfs_get_persisted_inode(sb, 1)->flags = PKERNFS_INODE_FLAG_DIR;
+ strscpy(pkernfs_get_persisted_inode(sb, 1)->filename, ".", PKERNFS_FILENAME_LEN);
+ psb->next_free_ino = 2;
}
sb->s_op = &pkernfs_super_ops;
- inode = new_inode(sb);
+ inode = pkernfs_inode_get(sb, 1);
if (!inode)
return -ENOMEM;
- inode->i_ino = 1;
inode->i_mode = S_IFDIR;
- inode->i_op = &simple_dir_inode_operations;
- inode->i_fop = &simple_dir_operations;
+ inode->i_fop = &pkernfs_dir_fops;
inode->i_atime = inode->i_mtime = current_time(inode);
inode_set_ctime_current(inode);
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
+ inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode);
dentry = d_make_root(inode);
if (!dentry)
@@ -1,6 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0-only */
+#include <linux/fs.h>
+
#define PKERNFS_MAGIC_NUMBER 0x706b65726e6673
+#define PKERNFS_FILENAME_LEN 255
+
+extern void *pkernfs_mem;
+
struct pkernfs_sb {
unsigned long magic_number;
+ /* Inode number */
+ unsigned long next_free_ino;
};
+
+// If neither of these are set the inode is not in use.
+#define PKERNFS_INODE_FLAG_FILE (1 << 0)
+#define PKERNFS_INODE_FLAG_DIR (1 << 1)
+struct pkernfs_inode {
+ int flags;
+ /*
+ * Points to next inode in the same directory, or
+ * 0 if last file in directory.
+ */
+ unsigned long sibling_ino;
+ /*
+ * If this inode is a directory, this points to the
+ * first inode *in* that directory.
+ */
+ unsigned long child_ino;
+ char filename[PKERNFS_FILENAME_LEN];
+ int mappings_block;
+ int num_mappings;
+};
+
+void pkernfs_initialise_inode_store(struct super_block *sb);
+struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino);
+struct pkernfs_inode *pkernfs_get_persisted_inode(struct super_block *sb, int ino);
+
+extern const struct file_operations pkernfs_dir_fops;