NFS: Add a dns resolver for use with NFSv4 referrals and migration
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Wed, 19 Aug 2009 22:12:27 +0000 (18:12 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Wed, 19 Aug 2009 22:22:15 +0000 (18:22 -0400)
The NFSv4 and NFSv4.1 protocols both allow for the redirection of a client
from one server to another in order to support filesystem migration and
replication. For full protocol support, we need to add the ability to
convert a DNS host name into an IP address that we can feed to the RPC
client.

We'll reuse the sunrpc cache, now that it has been converted to work with
rpc_pipefs.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Documentation/filesystems/nfs.txt [new file with mode: 0644]
Documentation/kernel-parameters.txt
fs/nfs/Makefile
fs/nfs/cache_lib.c [new file with mode: 0644]
fs/nfs/cache_lib.h [new file with mode: 0644]
fs/nfs/dns_resolve.c [new file with mode: 0644]
fs/nfs/dns_resolve.h [new file with mode: 0644]
fs/nfs/inode.c
net/sunrpc/rpc_pipe.c

diff --git a/Documentation/filesystems/nfs.txt b/Documentation/filesystems/nfs.txt
new file mode 100644 (file)
index 0000000..f50f26c
--- /dev/null
@@ -0,0 +1,98 @@
+
+The NFS client
+==============
+
+The NFS version 2 protocol was first documented in RFC1094 (March 1989).
+Since then two more major releases of NFS have been published, with NFSv3
+being documented in RFC1813 (June 1995), and NFSv4 in RFC3530 (April
+2003).
+
+The Linux NFS client currently supports all the above published versions,
+and work is in progress on adding support for minor version 1 of the NFSv4
+protocol.
+
+The purpose of this document is to provide information on some of the
+upcall interfaces that are used in order to provide the NFS client with
+some of the information that it requires in order to fully comply with
+the NFS spec.
+
+The DNS resolver
+================
+
+NFSv4 allows for one server to refer the NFS client to data that has been
+migrated onto another server by means of the special "fs_locations"
+attribute. See
+       http://tools.ietf.org/html/rfc3530#section-6
+and
+       http://tools.ietf.org/html/draft-ietf-nfsv4-referrals-00
+
+The fs_locations information can take the form of either an ip address and
+a path, or a DNS hostname and a path. The latter requires the NFS client to
+do a DNS lookup in order to mount the new volume, and hence the need for an
+upcall to allow userland to provide this service.
+
+Assuming that the user has the 'rpc_pipefs' filesystem mounted in the usual
+/var/lib/nfs/rpc_pipefs, the upcall consists of the following steps:
+
+   (1) The process checks the dns_resolve cache to see if it contains a
+       valid entry. If so, it returns that entry and exits.
+
+   (2) If no valid entry exists, the helper script '/sbin/nfs_cache_getent'
+       (may be changed using the 'nfs.cache_getent' kernel boot parameter)
+       is run, with two arguments:
+               - the cache name, "dns_resolve"
+               - the hostname to resolve
+
+   (3) After looking up the corresponding ip address, the helper script
+       writes the result into the rpc_pipefs pseudo-file
+       '/var/lib/nfs/rpc_pipefs/cache/dns_resolve/channel'
+       in the following (text) format:
+
+               "<ip address> <hostname> <ttl>\n"
+
+       Where <ip address> is in the usual IPv4 (123.456.78.90) or IPv6
+       (ffee:ddcc:bbaa:9988:7766:5544:3322:1100, ffee::1100, ...) format.
+       <hostname> is identical to the second argument of the helper
+       script, and <ttl> is the 'time to live' of this cache entry (in
+       units of seconds).
+
+       Note: If <ip address> is invalid, say the string "0", then a negative
+       entry is created, which will cause the kernel to treat the hostname
+       as having no valid DNS translation.
+
+
+
+
+A basic sample /sbin/nfs_cache_getent
+=====================================
+
+#!/bin/bash
+#
+ttl=600
+#
+cut=/usr/bin/cut
+getent=/usr/bin/getent
+rpc_pipefs=/var/lib/nfs/rpc_pipefs
+#
+die()
+{
+       echo "Usage: $0 cache_name entry_name"
+       exit 1
+}
+
+[ $# -lt 2 ] && die
+cachename="$1"
+cache_path=${rpc_pipefs}/cache/${cachename}/channel
+
+case "${cachename}" in
+       dns_resolve)
+               name="$2"
+               result="$(${getent} hosts ${name} | ${cut} -f1 -d\ )"
+               [ -z "${result}" ] && result="0"
+               ;;
+       *)
+               die
+               ;;
+esac
+echo "${result} ${name} ${ttl}" >${cache_path}
+
index c08813d..ce88537 100644 (file)
@@ -1503,6 +1503,14 @@ and is between 256 and 4096 characters. It is defined in the file
                        [NFS] set the TCP port on which the NFSv4 callback
                        channel should listen.
 
+       nfs.cache_getent=
+                       [NFS] sets the pathname to the program which is used
+                       to update the NFS client cache entries.
+
+       nfs.cache_getent_timeout=
+                       [NFS] sets the timeout after which an attempt to
+                       update a cache entry is deemed to have failed.
+
        nfs.idmap_cache_timeout=
                        [NFS] set the maximum lifetime for idmapper cache
                        entries.
index 8451598..da7fda6 100644 (file)
@@ -6,7 +6,8 @@ obj-$(CONFIG_NFS_FS) += nfs.o
 
 nfs-y                  := client.o dir.o file.o getroot.o inode.o super.o nfs2xdr.o \
                           direct.o pagelist.o proc.o read.o symlink.o unlink.o \
-                          write.o namespace.o mount_clnt.o
+                          write.o namespace.o mount_clnt.o \
+                          dns_resolve.o cache_lib.o
 nfs-$(CONFIG_ROOT_NFS) += nfsroot.o
 nfs-$(CONFIG_NFS_V3)   += nfs3proc.o nfs3xdr.o
 nfs-$(CONFIG_NFS_V3_ACL)       += nfs3acl.o
diff --git a/fs/nfs/cache_lib.c b/fs/nfs/cache_lib.c
new file mode 100644 (file)
index 0000000..b4ffd01
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * linux/fs/nfs/cache_lib.c
+ *
+ * Helper routines for the NFS client caches
+ *
+ * Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com>
+ */
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/sunrpc/cache.h>
+#include <linux/sunrpc/rpc_pipe_fs.h>
+
+#include "cache_lib.h"
+
+#define NFS_CACHE_UPCALL_PATHLEN 256
+#define NFS_CACHE_UPCALL_TIMEOUT 15
+
+static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] =
+                               "/sbin/nfs_cache_getent";
+static unsigned long nfs_cache_getent_timeout = NFS_CACHE_UPCALL_TIMEOUT;
+
+module_param_string(cache_getent, nfs_cache_getent_prog,
+               sizeof(nfs_cache_getent_prog), 0600);
+MODULE_PARM_DESC(cache_getent, "Path to the client cache upcall program");
+module_param_named(cache_getent_timeout, nfs_cache_getent_timeout, ulong, 0600);
+MODULE_PARM_DESC(cache_getent_timeout, "Timeout (in seconds) after which "
+               "the cache upcall is assumed to have failed");
+
+int nfs_cache_upcall(struct cache_detail *cd, char *entry_name)
+{
+       static char *envp[] = { "HOME=/",
+               "TERM=linux",
+               "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
+               NULL
+       };
+       char *argv[] = {
+               nfs_cache_getent_prog,
+               cd->name,
+               entry_name,
+               NULL
+       };
+       int ret = -EACCES;
+
+       if (nfs_cache_getent_prog[0] == '\0')
+               goto out;
+       ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
+       /*
+        * Disable the upcall mechanism if we're getting an ENOENT or
+        * EACCES error. The admin can re-enable it on the fly by using
+        * sysfs to set the 'cache_getent' parameter once the problem
+        * has been fixed.
+        */
+       if (ret == -ENOENT || ret == -EACCES)
+               nfs_cache_getent_prog[0] = '\0';
+out:
+       return ret > 0 ? 0 : ret;
+}
+
+/*
+ * Deferred request handling
+ */
+void nfs_cache_defer_req_put(struct nfs_cache_defer_req *dreq)
+{
+       if (atomic_dec_and_test(&dreq->count))
+               kfree(dreq);
+}
+
+static void nfs_dns_cache_revisit(struct cache_deferred_req *d, int toomany)
+{
+       struct nfs_cache_defer_req *dreq;
+
+       dreq = container_of(d, struct nfs_cache_defer_req, deferred_req);
+
+       complete_all(&dreq->completion);
+       nfs_cache_defer_req_put(dreq);
+}
+
+static struct cache_deferred_req *nfs_dns_cache_defer(struct cache_req *req)
+{
+       struct nfs_cache_defer_req *dreq;
+
+       dreq = container_of(req, struct nfs_cache_defer_req, req);
+       dreq->deferred_req.revisit = nfs_dns_cache_revisit;
+       atomic_inc(&dreq->count);
+
+       return &dreq->deferred_req;
+}
+
+struct nfs_cache_defer_req *nfs_cache_defer_req_alloc(void)
+{
+       struct nfs_cache_defer_req *dreq;
+
+       dreq = kzalloc(sizeof(*dreq), GFP_KERNEL);
+       if (dreq) {
+               init_completion(&dreq->completion);
+               atomic_set(&dreq->count, 1);
+               dreq->req.defer = nfs_dns_cache_defer;
+       }
+       return dreq;
+}
+
+int nfs_cache_wait_for_upcall(struct nfs_cache_defer_req *dreq)
+{
+       if (wait_for_completion_timeout(&dreq->completion,
+                       nfs_cache_getent_timeout * HZ) == 0)
+               return -ETIMEDOUT;
+       return 0;
+}
+
+int nfs_cache_register(struct cache_detail *cd)
+{
+       struct nameidata nd;
+       struct vfsmount *mnt;
+       int ret;
+
+       mnt = rpc_get_mount();
+       if (IS_ERR(mnt))
+               return PTR_ERR(mnt);
+       ret = vfs_path_lookup(mnt->mnt_root, mnt, "/cache", 0, &nd);
+       if (ret)
+               goto err;
+       ret = sunrpc_cache_register_pipefs(nd.path.dentry,
+                       cd->name, 0600, cd);
+       path_put(&nd.path);
+       if (!ret)
+               return ret;
+err:
+       rpc_put_mount();
+       return ret;
+}
+
+void nfs_cache_unregister(struct cache_detail *cd)
+{
+       sunrpc_cache_unregister_pipefs(cd);
+       rpc_put_mount();
+}
+
diff --git a/fs/nfs/cache_lib.h b/fs/nfs/cache_lib.h
new file mode 100644 (file)
index 0000000..76f856e
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Helper routines for the NFS client caches
+ *
+ * Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com>
+ */
+
+#include <linux/completion.h>
+#include <linux/sunrpc/cache.h>
+#include <asm/atomic.h>
+
+/*
+ * Deferred request handling
+ */
+struct nfs_cache_defer_req {
+       struct cache_req req;
+       struct cache_deferred_req deferred_req;
+       struct completion completion;
+       atomic_t count;
+};
+
+extern int nfs_cache_upcall(struct cache_detail *cd, char *entry_name);
+extern struct nfs_cache_defer_req *nfs_cache_defer_req_alloc(void);
+extern void nfs_cache_defer_req_put(struct nfs_cache_defer_req *dreq);
+extern int nfs_cache_wait_for_upcall(struct nfs_cache_defer_req *dreq);
+
+extern int nfs_cache_register(struct cache_detail *cd);
+extern void nfs_cache_unregister(struct cache_detail *cd);
diff --git a/fs/nfs/dns_resolve.c b/fs/nfs/dns_resolve.c
new file mode 100644 (file)
index 0000000..f4d54ba
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * linux/fs/nfs/dns_resolve.c
+ *
+ * Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com>
+ *
+ * Resolves DNS hostnames into valid ip addresses
+ */
+
+#include <linux/hash.h>
+#include <linux/string.h>
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/seq_file.h>
+#include <linux/inet.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/cache.h>
+#include <linux/sunrpc/svcauth.h>
+
+#include "dns_resolve.h"
+#include "cache_lib.h"
+
+#define NFS_DNS_HASHBITS 4
+#define NFS_DNS_HASHTBL_SIZE (1 << NFS_DNS_HASHBITS)
+
+static struct cache_head *nfs_dns_table[NFS_DNS_HASHTBL_SIZE];
+
+struct nfs_dns_ent {
+       struct cache_head h;
+
+       char *hostname;
+       size_t namelen;
+
+       struct sockaddr_storage addr;
+       size_t addrlen;
+};
+
+
+static void nfs_dns_ent_init(struct cache_head *cnew,
+               struct cache_head *ckey)
+{
+       struct nfs_dns_ent *new;
+       struct nfs_dns_ent *key;
+
+       new = container_of(cnew, struct nfs_dns_ent, h);
+       key = container_of(ckey, struct nfs_dns_ent, h);
+
+       kfree(new->hostname);
+       new->hostname = kstrndup(key->hostname, key->namelen, GFP_KERNEL);
+       if (new->hostname) {
+               new->namelen = key->namelen;
+               memcpy(&new->addr, &key->addr, key->addrlen);
+               new->addrlen = key->addrlen;
+       } else {
+               new->namelen = 0;
+               new->addrlen = 0;
+       }
+}
+
+static void nfs_dns_ent_put(struct kref *ref)
+{
+       struct nfs_dns_ent *item;
+
+       item = container_of(ref, struct nfs_dns_ent, h.ref);
+       kfree(item->hostname);
+       kfree(item);
+}
+
+static struct cache_head *nfs_dns_ent_alloc(void)
+{
+       struct nfs_dns_ent *item = kmalloc(sizeof(*item), GFP_KERNEL);
+
+       if (item != NULL) {
+               item->hostname = NULL;
+               item->namelen = 0;
+               item->addrlen = 0;
+               return &item->h;
+       }
+       return NULL;
+};
+
+static unsigned int nfs_dns_hash(const struct nfs_dns_ent *key)
+{
+       return hash_str(key->hostname, NFS_DNS_HASHBITS);
+}
+
+static void nfs_dns_request(struct cache_detail *cd,
+               struct cache_head *ch,
+               char **bpp, int *blen)
+{
+       struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h);
+
+       qword_add(bpp, blen, key->hostname);
+       (*bpp)[-1] = '\n';
+}
+
+static int nfs_dns_upcall(struct cache_detail *cd,
+               struct cache_head *ch)
+{
+       struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h);
+       int ret;
+
+       ret = nfs_cache_upcall(cd, key->hostname);
+       if (ret)
+               ret = sunrpc_cache_pipe_upcall(cd, ch, nfs_dns_request);
+       return ret;
+}
+
+static int nfs_dns_match(struct cache_head *ca,
+               struct cache_head *cb)
+{
+       struct nfs_dns_ent *a;
+       struct nfs_dns_ent *b;
+
+       a = container_of(ca, struct nfs_dns_ent, h);
+       b = container_of(cb, struct nfs_dns_ent, h);
+
+       if (a->namelen == 0 || a->namelen != b->namelen)
+               return 0;
+       return memcmp(a->hostname, b->hostname, a->namelen) == 0;
+}
+
+static int nfs_dns_show(struct seq_file *m, struct cache_detail *cd,
+               struct cache_head *h)
+{
+       struct nfs_dns_ent *item;
+       long ttl;
+
+       if (h == NULL) {
+               seq_puts(m, "# ip address      hostname        ttl\n");
+               return 0;
+       }
+       item = container_of(h, struct nfs_dns_ent, h);
+       ttl = (long)item->h.expiry_time - (long)get_seconds();
+       if (ttl < 0)
+               ttl = 0;
+
+       if (!test_bit(CACHE_NEGATIVE, &h->flags)) {
+               char buf[INET6_ADDRSTRLEN+IPV6_SCOPE_ID_LEN+1];
+
+               rpc_ntop((struct sockaddr *)&item->addr, buf, sizeof(buf));
+               seq_printf(m, "%15s ", buf);
+       } else
+               seq_puts(m, "<none>          ");
+       seq_printf(m, "%15s %ld\n", item->hostname, ttl);
+       return 0;
+}
+
+struct nfs_dns_ent *nfs_dns_lookup(struct cache_detail *cd,
+               struct nfs_dns_ent *key)
+{
+       struct cache_head *ch;
+
+       ch = sunrpc_cache_lookup(cd,
+                       &key->h,
+                       nfs_dns_hash(key));
+       if (!ch)
+               return NULL;
+       return container_of(ch, struct nfs_dns_ent, h);
+}
+
+struct nfs_dns_ent *nfs_dns_update(struct cache_detail *cd,
+               struct nfs_dns_ent *new,
+               struct nfs_dns_ent *key)
+{
+       struct cache_head *ch;
+
+       ch = sunrpc_cache_update(cd,
+                       &new->h, &key->h,
+                       nfs_dns_hash(key));
+       if (!ch)
+               return NULL;
+       return container_of(ch, struct nfs_dns_ent, h);
+}
+
+static int nfs_dns_parse(struct cache_detail *cd, char *buf, int buflen)
+{
+       char buf1[NFS_DNS_HOSTNAME_MAXLEN+1];
+       struct nfs_dns_ent key, *item;
+       unsigned long ttl;
+       ssize_t len;
+       int ret = -EINVAL;
+
+       if (buf[buflen-1] != '\n')
+               goto out;
+       buf[buflen-1] = '\0';
+
+       len = qword_get(&buf, buf1, sizeof(buf1));
+       if (len <= 0)
+               goto out;
+       key.addrlen = rpc_pton(buf1, len,
+                       (struct sockaddr *)&key.addr,
+                       sizeof(key.addr));
+
+       len = qword_get(&buf, buf1, sizeof(buf1));
+       if (len <= 0)
+               goto out;
+
+       key.hostname = buf1;
+       key.namelen = len;
+       memset(&key.h, 0, sizeof(key.h));
+
+       ttl = get_expiry(&buf);
+       if (ttl == 0)
+               goto out;
+       key.h.expiry_time = ttl + get_seconds();
+
+       ret = -ENOMEM;
+       item = nfs_dns_lookup(cd, &key);
+       if (item == NULL)
+               goto out;
+
+       if (key.addrlen == 0)
+               set_bit(CACHE_NEGATIVE, &key.h.flags);
+
+       item = nfs_dns_update(cd, &key, item);
+       if (item == NULL)
+               goto out;
+
+       ret = 0;
+       cache_put(&item->h, cd);
+out:
+       return ret;
+}
+
+static struct cache_detail nfs_dns_resolve = {
+       .owner = THIS_MODULE,
+       .hash_size = NFS_DNS_HASHTBL_SIZE,
+       .hash_table = nfs_dns_table,
+       .name = "dns_resolve",
+       .cache_put = nfs_dns_ent_put,
+       .cache_upcall = nfs_dns_upcall,
+       .cache_parse = nfs_dns_parse,
+       .cache_show = nfs_dns_show,
+       .match = nfs_dns_match,
+       .init = nfs_dns_ent_init,
+       .update = nfs_dns_ent_init,
+       .alloc = nfs_dns_ent_alloc,
+};
+
+static int do_cache_lookup(struct cache_detail *cd,
+               struct nfs_dns_ent *key,
+               struct nfs_dns_ent **item,
+               struct nfs_cache_defer_req *dreq)
+{
+       int ret = -ENOMEM;
+
+       *item = nfs_dns_lookup(cd, key);
+       if (*item) {
+               ret = cache_check(cd, &(*item)->h, &dreq->req);
+               if (ret)
+                       *item = NULL;
+       }
+       return ret;
+}
+
+static int do_cache_lookup_nowait(struct cache_detail *cd,
+               struct nfs_dns_ent *key,
+               struct nfs_dns_ent **item)
+{
+       int ret = -ENOMEM;
+
+       *item = nfs_dns_lookup(cd, key);
+       if (!*item)
+               goto out_err;
+       ret = -ETIMEDOUT;
+       if (!test_bit(CACHE_VALID, &(*item)->h.flags)
+                       || (*item)->h.expiry_time < get_seconds()
+                       || cd->flush_time > (*item)->h.last_refresh)
+               goto out_put;
+       ret = -ENOENT;
+       if (test_bit(CACHE_NEGATIVE, &(*item)->h.flags))
+               goto out_put;
+       return 0;
+out_put:
+       cache_put(&(*item)->h, cd);
+out_err:
+       *item = NULL;
+       return ret;
+}
+
+static int do_cache_lookup_wait(struct cache_detail *cd,
+               struct nfs_dns_ent *key,
+               struct nfs_dns_ent **item)
+{
+       struct nfs_cache_defer_req *dreq;
+       int ret = -ENOMEM;
+
+       dreq = nfs_cache_defer_req_alloc();
+       if (!dreq)
+               goto out;
+       ret = do_cache_lookup(cd, key, item, dreq);
+       if (ret == -EAGAIN) {
+               ret = nfs_cache_wait_for_upcall(dreq);
+               if (!ret)
+                       ret = do_cache_lookup_nowait(cd, key, item);
+       }
+       nfs_cache_defer_req_put(dreq);
+out:
+       return ret;
+}
+
+ssize_t nfs_dns_resolve_name(char *name, size_t namelen,
+               struct sockaddr *sa, size_t salen)
+{
+       struct nfs_dns_ent key = {
+               .hostname = name,
+               .namelen = namelen,
+       };
+       struct nfs_dns_ent *item = NULL;
+       ssize_t ret;
+
+       ret = do_cache_lookup_wait(&nfs_dns_resolve, &key, &item);
+       if (ret == 0) {
+               if (salen >= item->addrlen) {
+                       memcpy(sa, &item->addr, item->addrlen);
+                       ret = item->addrlen;
+               } else
+                       ret = -EOVERFLOW;
+               cache_put(&item->h, &nfs_dns_resolve);
+       } else if (ret == -ENOENT)
+               ret = -ESRCH;
+       return ret;
+}
+
+int nfs_dns_resolver_init(void)
+{
+       return nfs_cache_register(&nfs_dns_resolve);
+}
+
+void nfs_dns_resolver_destroy(void)
+{
+       nfs_cache_unregister(&nfs_dns_resolve);
+}
+
diff --git a/fs/nfs/dns_resolve.h b/fs/nfs/dns_resolve.h
new file mode 100644 (file)
index 0000000..a3f0938
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Resolve DNS hostnames into valid ip addresses
+ */
+#ifndef __LINUX_FS_NFS_DNS_RESOLVE_H
+#define __LINUX_FS_NFS_DNS_RESOLVE_H
+
+#define NFS_DNS_HOSTNAME_MAXLEN        (128)
+
+extern int nfs_dns_resolver_init(void);
+extern void nfs_dns_resolver_destroy(void);
+extern ssize_t nfs_dns_resolve_name(char *name, size_t namelen,
+               struct sockaddr *sa, size_t salen);
+
+#endif
index fe5a8b4..060022b 100644 (file)
@@ -46,6 +46,7 @@
 #include "iostat.h"
 #include "internal.h"
 #include "fscache.h"
+#include "dns_resolve.h"
 
 #define NFSDBG_FACILITY                NFSDBG_VFS
 
@@ -1506,6 +1507,10 @@ static int __init init_nfs_fs(void)
 {
        int err;
 
+       err = nfs_dns_resolver_init();
+       if (err < 0)
+               goto out8;
+
        err = nfs_fscache_register();
        if (err < 0)
                goto out7;
@@ -1564,6 +1569,8 @@ out5:
 out6:
        nfs_fscache_unregister();
 out7:
+       nfs_dns_resolver_destroy();
+out8:
        return err;
 }
 
@@ -1575,6 +1582,7 @@ static void __exit exit_nfs_fs(void)
        nfs_destroy_inodecache();
        nfs_destroy_nfspagecache();
        nfs_fscache_unregister();
+       nfs_dns_resolver_destroy();
 #ifdef CONFIG_PROC_FS
        rpc_proc_unregister("nfs");
 #endif
index 3fdacaf..7f676bd 100644 (file)
@@ -416,11 +416,13 @@ struct vfsmount *rpc_get_mount(void)
                return ERR_PTR(err);
        return rpc_mount;
 }
+EXPORT_SYMBOL_GPL(rpc_get_mount);
 
 void rpc_put_mount(void)
 {
        simple_release_fs(&rpc_mount, &rpc_mount_count);
 }
+EXPORT_SYMBOL_GPL(rpc_put_mount);
 
 static int rpc_delete_dentry(struct dentry *dentry)
 {
@@ -946,6 +948,7 @@ enum {
        RPCAUTH_portmap,
        RPCAUTH_statd,
        RPCAUTH_nfsd4_cb,
+       RPCAUTH_cache,
        RPCAUTH_RootEOF
 };
 
@@ -974,6 +977,10 @@ static const struct rpc_filelist files[] = {
                .name = "nfsd4_cb",
                .mode = S_IFDIR | S_IRUGO | S_IXUGO,
        },
+       [RPCAUTH_cache] = {
+               .name = "cache",
+               .mode = S_IFDIR | S_IRUGO | S_IXUGO,
+       },
 };
 
 static int