[PATCH v13 04/12] famfs: Introduce inode_operations and super_operations

John Groves <[email protected]>
Newsgroups dev.linux.lists.nvdimm,dev.linux.lists.fuse-devel,org.kernel.vger.linux-cxl,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <0100019fed596b17-7d05bea5-52b4-4cf9-ac49-b687a0368a3e-000000@email.amazonses.com>
From: John Groves <[email protected]>

The famfs inode and super operations are generic other than
show_options, evict_inode and setattr (which prevents truncation..

This commit builds but is still too incomplete to run

Signed-off-by: John Groves <[email protected]>
---
v13:
 - famfs_rename(): gate a rename that replaces an existing target by the same
   FAMFS_OPT_UNLINK / FAMFS_OPT_RMDIR policy that unlink()/rmdir() enforce.
   Previously simple_rename() removed the target (a mapped file, or a
   directory) with only the FAMFS_OPT_RENAME check, bypassing those gates
   (Sashiko bot).
 - famfs_mknod(): bump the parent directory's mtime/ctime when an entry is
   created (create/mkdir/mknod) instead of re-setting the new child inode's
   timestamps; matches famfs_symlink() and POSIX (Sashiko bot).

 fs/famfs/famfs_inode.c    | 289 +++++++++++++++++++++++++++++++++++++-
 fs/famfs/famfs_internal.h |   6 +
 2 files changed, 292 insertions(+), 3 deletions(-)

diff --git a/fs/famfs/famfs_inode.c b/fs/famfs/famfs_inode.c
index 5735d8d1900b..b7a3d8d6ee8c 100644
--- a/fs/famfs/famfs_inode.c
+++ b/fs/famfs/famfs_inode.c
@@ -29,6 +29,9 @@
 
 #define FAMFS_DEFAULT_MODE	0755
 
+static const struct inode_operations famfs_file_inode_operations;
+static const struct inode_operations famfs_dir_inode_operations;
+
 static struct inode *
 famfs_get_inode(
 	struct super_block *sb,
@@ -55,11 +58,11 @@ famfs_get_inode(
 		init_special_inode(inode, mode, dev);
 		break;
 	case S_IFREG:
-		inode->i_op = NULL /* famfs_file_inode_operations */;
+		inode->i_op = &famfs_file_inode_operations;
 		inode->i_fop = NULL /* &famfs_file_operations */;
 		break;
 	case S_IFDIR:
-		inode->i_op = NULL /* famfs_dir_inode_operations */;
+		inode->i_op = &famfs_dir_inode_operations;
 		inode->i_fop = &simple_dir_operations;
 
 		/* Directory inodes start off with i_nlink == 2 (for ".") */
@@ -73,6 +76,286 @@ famfs_get_inode(
 	return inode;
 }
 
+/***************************************************************************
+ * famfs inode_operations
+ */
+
+static int
+famfs_setattr(
+	struct mnt_idmap *idmap,
+	struct dentry *dentry,
+	struct iattr *iattr)
+{
+	struct inode *inode = d_inode(dentry);
+	struct famfs_fs_info *fsi = inode->i_sb->s_fs_info;
+
+	/* Resizing a famfs file (its size is pinned to the fmap) */
+	if ((iattr->ia_valid & ATTR_SIZE) &&
+	    !famfs_opt_enabled(fsi, FAMFS_OPT_TRUNCATE) &&
+	    iattr->ia_size != i_size_read(inode))
+		return -EPERM;
+	if ((iattr->ia_valid & ATTR_MODE) &&
+	    !famfs_opt_enabled(fsi, FAMFS_OPT_CHMOD))
+		return -EPERM;
+	if ((iattr->ia_valid & (ATTR_UID | ATTR_GID)) &&
+	    !famfs_opt_enabled(fsi, FAMFS_OPT_CHOWN))
+		return -EPERM;
+	if ((iattr->ia_valid & (ATTR_ATIME | ATTR_MTIME)) &&
+	    !famfs_opt_enabled(fsi, FAMFS_OPT_UTIMES))
+		return -EPERM;
+
+	return simple_setattr(idmap, dentry, iattr);
+}
+
+static const struct inode_operations famfs_file_inode_operations = {
+	/* All generic */
+	.setattr	   = famfs_setattr,
+	.getattr	   = simple_getattr,
+};
+
+/*
+ * Internal inode creation helper, shared by ->create, ->mkdir, ->mknod and
+ * ->symlink. Each of those callers is responsible for its own FAMFS_OPT_*
+ * permission check before getting here.
+ */
+static int
+famfs_mknod(
+	struct mnt_idmap *idmap,
+	struct inode *dir,
+	struct dentry *dentry,
+	umode_t mode,
+	dev_t dev)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+	struct inode *inode;
+
+	if (fsi->deverror)
+		return -ENODEV;
+
+	inode = famfs_get_inode(dir->i_sb, dir, mode, dev);
+	if (!inode)
+		return -ENOSPC;
+
+	d_make_persistent(dentry, inode);
+	/* Adding an entry updates the parent directory's mtime/ctime */
+	inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+
+	return 0;
+}
+
+static struct dentry *
+famfs_mkdir(
+	struct mnt_idmap *idmap,
+	struct inode *dir,
+	struct dentry *dentry,
+	umode_t mode)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+	int rc;
+
+	if (fsi->deverror)
+		return ERR_PTR(-ENODEV);
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_MKDIR))
+		return ERR_PTR(-EPERM);
+
+	rc = famfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0);
+	if (rc)
+		return ERR_PTR(rc);
+
+	inc_nlink(dir);
+
+	return ERR_PTR(0);
+}
+
+static int
+famfs_create(
+	struct mnt_idmap *idmap,
+	struct inode *dir,
+	struct dentry *dentry,
+	umode_t mode,
+	bool excl)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+
+	if (fsi->deverror)
+		return -ENODEV;
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_CREATE))
+		return -EPERM;
+
+	return famfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0);
+}
+
+static int
+famfs_mknod_op(
+	struct mnt_idmap *idmap,
+	struct inode *dir,
+	struct dentry *dentry,
+	umode_t mode,
+	dev_t dev)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_MKNOD))
+		return -EPERM;
+
+	return famfs_mknod(idmap, dir, dentry, mode, dev);
+}
+
+static int
+famfs_symlink(
+	struct mnt_idmap *idmap,
+	struct inode *dir,
+	struct dentry *dentry,
+	const char *symname)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+	struct inode *inode;
+	int len, rc;
+
+	if (fsi->deverror)
+		return -ENODEV;
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_SYMLINK))
+		return -EPERM;
+
+	inode = famfs_get_inode(dir->i_sb, dir, S_IFLNK | 0777, 0);
+	if (!inode)
+		return -ENOSPC;
+
+	len = strlen(symname) + 1;
+	rc = page_symlink(inode, symname, len);
+	if (rc) {
+		iput(inode);
+		return rc;
+	}
+
+	d_make_persistent(dentry, inode);
+	inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+
+	return 0;
+}
+
+static int
+famfs_link(
+	struct dentry *old_dentry,
+	struct inode *dir,
+	struct dentry *dentry)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_LINK))
+		return -EPERM;
+
+	return simple_link(old_dentry, dir, dentry);
+}
+
+static int
+famfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct inode *inode = d_inode(dentry);
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+
+	/* A file with an fmap may only be unlinked when explicitly enabled */
+	if (inode->i_private && !famfs_opt_enabled(fsi, FAMFS_OPT_UNLINK))
+		return -EPERM;
+
+	return simple_unlink(dir, dentry);
+}
+
+static int famfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	struct famfs_fs_info *fsi = dir->i_sb->s_fs_info;
+
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_RMDIR))
+		return -EPERM;
+
+	return simple_rmdir(dir, dentry);
+}
+
+static int
+famfs_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 famfs_fs_info *fsi = old_dir->i_sb->s_fs_info;
+	struct inode *target = d_inode(new_dentry);
+
+	if (!famfs_opt_enabled(fsi, FAMFS_OPT_RENAME))
+		return -EPERM;
+
+	/*
+	 * Renaming over an existing target removes it, so require the same
+	 * policy gate that unlink()/rmdir() would. RENAME_EXCHANGE swaps the
+	 * two entries rather than removing the target, so it is exempt.
+	 */
+	if (target && !(flags & RENAME_EXCHANGE)) {
+		if (S_ISDIR(target->i_mode)) {
+			if (!famfs_opt_enabled(fsi, FAMFS_OPT_RMDIR))
+				return -EPERM;
+		} else if (target->i_private &&
+			   !famfs_opt_enabled(fsi, FAMFS_OPT_UNLINK)) {
+			return -EPERM;
+		}
+	}
+
+	return simple_rename(idmap, old_dir, old_dentry, new_dir, new_dentry,
+			     flags);
+}
+
+static const struct inode_operations famfs_dir_inode_operations = {
+	.create		= famfs_create,
+	.lookup		= simple_lookup,
+	.link		= famfs_link,
+	.unlink		= famfs_unlink,
+	.symlink	= famfs_symlink,
+	.mkdir		= famfs_mkdir,
+	.mknod		= famfs_mknod_op,
+	.rmdir		= famfs_rmdir,
+	.rename		= famfs_rename,
+};
+
+/*****************************************************************************
+ * famfs super_operations
+ *
+ * TODO: implement a famfs_statfs() that shows size, free and available space,
+ * etc.
+ */
+
+/*
+ * famfs_show_options() - Display the mount options in /proc/mounts.
+ */
+static int
+famfs_show_options(struct seq_file *m, struct dentry *root)
+{
+	struct famfs_fs_info *fsi = root->d_sb->s_fs_info;
+
+	if (fsi->mount_opts.mode != FAMFS_DEFAULT_MODE)
+		seq_printf(m, ",mode=%o", fsi->mount_opts.mode);
+
+	return 0;
+}
+
+static void
+famfs_evict_inode(struct inode *inode)
+{
+	inode->i_private = NULL;
+	dax_break_layout_final(inode);
+	truncate_inode_pages_final(&inode->i_data);
+	clear_inode(inode);
+}
+
+static const struct super_operations famfs_super_ops = {
+	.statfs		= simple_statfs,
+	.drop_inode	= inode_just_drop,
+	.show_options	= famfs_show_options,
+	.evict_inode    = famfs_evict_inode,
+};
+
+/*****************************************************************************/
+
 /*
  * famfs dax_operations (for famfs-mode dax)
  */
@@ -312,7 +595,7 @@ famfs_fill_super(struct super_block *sb, struct fs_context *fc)
 	sb->s_blocksize		= PAGE_SIZE;
 	sb->s_blocksize_bits	= PAGE_SHIFT;
 	sb->s_magic		= FAMFS_SUPER_MAGIC;
-	sb->s_op		= NULL /* famfs_super_ops */;
+	sb->s_op		= &famfs_super_ops;
 	sb->s_time_gran		= 1;
 }
 
diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h
index 37f667b2b79a..30f0b01010d5 100644
--- a/fs/famfs/famfs_internal.h
+++ b/fs/famfs/famfs_internal.h
@@ -70,6 +70,12 @@ struct famfs_fs_info {
 	struct rw_semaphore       devlist_sem;
 };
 
+/* This stub will be replaced in a later commit
+ * Note: the opt parameter is intentionally unused, and will be used by
+ * the replacement function when that commit lands
+ */
+#define famfs_opt_enabled(fsi, opt) (fsi != 0)
+
 int famfs_lookup_daxdev(const char *pathname, dev_t *devno);
 int famfs_devlist_alloc(struct famfs_fs_info *fsi);
 int famfs_install_daxdev(struct famfs_fs_info *fsi, struct super_block *sb,
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.