Merge branch 'work.mount0' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-microblaze.git] / fs / nfsd / nfsctl.c
index 0f154b0..13c5487 100644 (file)
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
  * Syscall interface to knfsd.
  *
@@ -16,6 +17,7 @@
 #include <linux/sunrpc/gss_krb5_enctypes.h>
 #include <linux/sunrpc/rpc_pipe_fs.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
 
 #include "idmap.h"
 #include "nfsd.h"
@@ -53,6 +55,7 @@ enum {
        NFSD_RecoveryDir,
        NFSD_V4EndGrace,
 #endif
+       NFSD_MaxReserved
 };
 
 /*
@@ -1147,8 +1150,201 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size)
  *     populating the filesystem.
  */
 
+/* Basically copying rpc_get_inode. */
+static struct inode *nfsd_get_inode(struct super_block *sb, umode_t mode)
+{
+       struct inode *inode = new_inode(sb);
+       if (!inode)
+               return NULL;
+       /* Following advice from simple_fill_super documentation: */
+       inode->i_ino = iunique(sb, NFSD_MaxReserved);
+       inode->i_mode = mode;
+       inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
+       switch (mode & S_IFMT) {
+       case S_IFDIR:
+               inode->i_fop = &simple_dir_operations;
+               inode->i_op = &simple_dir_inode_operations;
+               inc_nlink(inode);
+       default:
+               break;
+       }
+       return inode;
+}
+
+static int __nfsd_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+       struct inode *inode;
+
+       inode = nfsd_get_inode(dir->i_sb, mode);
+       if (!inode)
+               return -ENOMEM;
+       d_add(dentry, inode);
+       inc_nlink(dir);
+       fsnotify_mkdir(dir, dentry);
+       return 0;
+}
+
+static struct dentry *nfsd_mkdir(struct dentry *parent, struct nfsdfs_client *ncl, char *name)
+{
+       struct inode *dir = parent->d_inode;
+       struct dentry *dentry;
+       int ret = -ENOMEM;
+
+       inode_lock(dir);
+       dentry = d_alloc_name(parent, name);
+       if (!dentry)
+               goto out_err;
+       ret = __nfsd_mkdir(d_inode(parent), dentry, S_IFDIR | 0600);
+       if (ret)
+               goto out_err;
+       if (ncl) {
+               d_inode(dentry)->i_private = ncl;
+               kref_get(&ncl->cl_ref);
+       }
+out:
+       inode_unlock(dir);
+       return dentry;
+out_err:
+       dentry = ERR_PTR(ret);
+       goto out;
+}
+
+static void clear_ncl(struct inode *inode)
+{
+       struct nfsdfs_client *ncl = inode->i_private;
+
+       inode->i_private = NULL;
+       synchronize_rcu();
+       kref_put(&ncl->cl_ref, ncl->cl_release);
+}
+
+
+static struct nfsdfs_client *__get_nfsdfs_client(struct inode *inode)
+{
+       struct nfsdfs_client *nc = inode->i_private;
+
+       if (nc)
+               kref_get(&nc->cl_ref);
+       return nc;
+}
+
+struct nfsdfs_client *get_nfsdfs_client(struct inode *inode)
+{
+       struct nfsdfs_client *nc;
+
+       rcu_read_lock();
+       nc = __get_nfsdfs_client(inode);
+       rcu_read_unlock();
+       return nc;
+}
+/* from __rpc_unlink */
+static void nfsdfs_remove_file(struct inode *dir, struct dentry *dentry)
+{
+       int ret;
+
+       clear_ncl(d_inode(dentry));
+       dget(dentry);
+       ret = simple_unlink(dir, dentry);
+       d_delete(dentry);
+       dput(dentry);
+       WARN_ON_ONCE(ret);
+}
+
+static void nfsdfs_remove_files(struct dentry *root)
+{
+       struct dentry *dentry, *tmp;
+
+       list_for_each_entry_safe(dentry, tmp, &root->d_subdirs, d_child) {
+               if (!simple_positive(dentry)) {
+                       WARN_ON_ONCE(1); /* I think this can't happen? */
+                       continue;
+               }
+               nfsdfs_remove_file(d_inode(root), dentry);
+       }
+}
+
+/* XXX: cut'n'paste from simple_fill_super; figure out if we could share
+ * code instead. */
+static  int nfsdfs_create_files(struct dentry *root,
+                                       const struct tree_descr *files)
+{
+       struct inode *dir = d_inode(root);
+       struct inode *inode;
+       struct dentry *dentry;
+       int i;
+
+       inode_lock(dir);
+       for (i = 0; files->name && files->name[0]; i++, files++) {
+               if (!files->name)
+                       continue;
+               dentry = d_alloc_name(root, files->name);
+               if (!dentry)
+                       goto out;
+               inode = nfsd_get_inode(d_inode(root)->i_sb,
+                                       S_IFREG | files->mode);
+               if (!inode) {
+                       dput(dentry);
+                       goto out;
+               }
+               inode->i_fop = files->ops;
+               inode->i_private = __get_nfsdfs_client(dir);
+               d_add(dentry, inode);
+               fsnotify_create(dir, dentry);
+       }
+       inode_unlock(dir);
+       return 0;
+out:
+       nfsdfs_remove_files(root);
+       inode_unlock(dir);
+       return -ENOMEM;
+}
+
+/* on success, returns positive number unique to that client. */
+struct dentry *nfsd_client_mkdir(struct nfsd_net *nn,
+               struct nfsdfs_client *ncl, u32 id,
+               const struct tree_descr *files)
+{
+       struct dentry *dentry;
+       char name[11];
+       int ret;
+
+       sprintf(name, "%u", id);
+
+       dentry = nfsd_mkdir(nn->nfsd_client_dir, ncl, name);
+       if (IS_ERR(dentry)) /* XXX: tossing errors? */
+               return NULL;
+       ret = nfsdfs_create_files(dentry, files);
+       if (ret) {
+               nfsd_client_rmdir(dentry);
+               return NULL;
+       }
+       return dentry;
+}
+
+/* Taken from __rpc_rmdir: */
+void nfsd_client_rmdir(struct dentry *dentry)
+{
+       struct inode *dir = d_inode(dentry->d_parent);
+       struct inode *inode = d_inode(dentry);
+       int ret;
+
+       inode_lock(dir);
+       nfsdfs_remove_files(dentry);
+       clear_ncl(inode);
+       dget(dentry);
+       ret = simple_rmdir(dir, dentry);
+       WARN_ON_ONCE(ret);
+       d_delete(dentry);
+       inode_unlock(dir);
+}
+
 static int nfsd_fill_super(struct super_block *sb, struct fs_context *fc)
 {
+       struct nfsd_net *nn = net_generic(current->nsproxy->net_ns,
+                                                       nfsd_net_id);
+       struct dentry *dentry;
+       int ret;
+
        static const struct tree_descr nfsd_files[] = {
                [NFSD_List] = {"exports", &exports_nfsd_operations, S_IRUGO},
                [NFSD_Export_features] = {"export_features",
@@ -1178,7 +1374,14 @@ static int nfsd_fill_super(struct super_block *sb, struct fs_context *fc)
                /* last one */ {""}
        };
 
-       return simple_fill_super(sb, 0x6e667364, nfsd_files);
+       ret = simple_fill_super(sb, 0x6e667364, nfsd_files);
+       if (ret)
+               return ret;
+       dentry = nfsd_mkdir(sb->s_root, NULL, "clients");
+       if (IS_ERR(dentry))
+               return PTR_ERR(dentry);
+       nn->nfsd_client_dir = dentry;
+       return 0;
 }
 
 static int nfsd_fs_get_tree(struct fs_context *fc)
@@ -1250,6 +1453,7 @@ unsigned int nfsd_net_id;
 static __net_init int nfsd_init_net(struct net *net)
 {
        int retval;
+       struct vfsmount *mnt;
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        retval = nfsd_export_init(net);
@@ -1260,18 +1464,33 @@ static __net_init int nfsd_init_net(struct net *net)
                goto out_idmap_error;
        nn->nfsd_versions = NULL;
        nn->nfsd4_minorversions = NULL;
+       retval = nfsd_reply_cache_init(nn);
+       if (retval)
+               goto out_drc_error;
        nn->nfsd4_lease = 90;   /* default lease time */
        nn->nfsd4_grace = 90;
        nn->somebody_reclaimed = false;
        nn->track_reclaim_completes = false;
        nn->clverifier_counter = prandom_u32();
-       nn->clientid_counter = prandom_u32();
+       nn->clientid_base = prandom_u32();
+       nn->clientid_counter = nn->clientid_base + 1;
        nn->s2s_cp_cl_id = nn->clientid_counter++;
 
        atomic_set(&nn->ntf_refcnt, 0);
        init_waitqueue_head(&nn->ntf_wq);
+
+       mnt =  vfs_kern_mount(&nfsd_fs_type, SB_KERNMOUNT, "nfsd", NULL);
+       if (IS_ERR(mnt)) {
+               retval = PTR_ERR(mnt);
+               goto out_mount_err;
+       }
+       nn->nfsd_mnt = mnt;
        return 0;
 
+out_mount_err:
+       nfsd_reply_cache_shutdown(nn);
+out_drc_error:
+       nfsd_idmap_shutdown(net);
 out_idmap_error:
        nfsd_export_shutdown(net);
 out_export_error:
@@ -1280,6 +1499,10 @@ out_export_error:
 
 static __net_exit void nfsd_exit_net(struct net *net)
 {
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+
+       mntput(nn->nfsd_mnt);
+       nfsd_reply_cache_shutdown(nn);
        nfsd_idmap_shutdown(net);
        nfsd_export_shutdown(net);
        nfsd_netns_free_versions(net_generic(net, nfsd_net_id));
@@ -1309,13 +1532,8 @@ static int __init init_nfsd(void)
        retval = nfsd4_init_pnfs();
        if (retval)
                goto out_free_slabs;
-       retval = nfsd_fault_inject_init(); /* nfsd fault injection controls */
-       if (retval)
-               goto out_exit_pnfs;
+       nfsd_fault_inject_init(); /* nfsd fault injection controls */
        nfsd_stat_init();       /* Statistics */
-       retval = nfsd_reply_cache_init();
-       if (retval)
-               goto out_free_stat;
        nfsd_lockd_init();      /* lockd->nfsd callbacks */
        retval = create_proc_exports_entry();
        if (retval)
@@ -1329,11 +1547,8 @@ out_free_all:
        remove_proc_entry("fs/nfs", NULL);
 out_free_lockd:
        nfsd_lockd_shutdown();
-       nfsd_reply_cache_shutdown();
-out_free_stat:
        nfsd_stat_shutdown();
        nfsd_fault_inject_cleanup();
-out_exit_pnfs:
        nfsd4_exit_pnfs();
 out_free_slabs:
        nfsd4_free_slabs();
@@ -1346,7 +1561,6 @@ out_unregister_pernet:
 
 static void __exit exit_nfsd(void)
 {
-       nfsd_reply_cache_shutdown();
        remove_proc_entry("fs/nfs/exports", NULL);
        remove_proc_entry("fs/nfs", NULL);
        nfsd_stat_shutdown();