Skip to contentSkip to conversation

Fix several issues with NFS mount option parsing

PATCH New Chuck Lever 1 participant 7 messages Apr 30, 2008 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover Fix several issues with NFS mount option parsing

Hi Jeff-

Here's the kind of changes I'm thinking about for addressing some recently noticed problems with text-based mounts.

These patches should address the following issues:

o The retrans= and timeo= settings don't have any effect if they are specified before proto=

o If UDP is blocked, specifying "proto=tcp" doesn't work because the kernel still uses mountproto=udp by default

o Sloppy mount options aren't ever allowed, so automounter maps that contain options that aren't supported on Linux cause failed mounts

I think there are still important corner cases that aren't working as well as they should, but this ought to be a good start.

Signature · 1 line
corporate:    <chuck dot lever at oracle dot com>

A message that is not in the archive (sent off-list or not yet received): 20080430154011.32402.42101.stgit-meopP2rzCrTwdl/1UfZZQIVfYA8g3rJ/@public.gmane.org

#2 lore raw

PATCH 1/6 NFS: Allow any value for the "retry" option

The kernel NFS mount option parser should ignore the retry= mount option since it is meaningful only in user space. Today it expects a number rather than arbitrary text, so it ignores the option if the value is numeric, but chokes if there are other characters in the value.

Change it to allow any text (except ",") as its value.

---

 fs/nfs/super.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
fs/nfs/super.c +2 -1
index fa220dc..d199f7c 100644
@@ -101,6 +101,8 @@ enum {
 static match_table_t nfs_mount_option_tokens = {
 	{ Opt_userspace, "bg" },
 	{ Opt_userspace, "fg" },
+	{ Opt_userspace, "retry=%s" },
+
 	{ Opt_soft, "soft" },
 	{ Opt_hard, "hard" },
 	{ Opt_intr, "intr" },
@@ -136,7 +138,6 @@ static match_table_t nfs_mount_option_tokens = {
 	{ Opt_acdirmin, "acdirmin=%u" },
 	{ Opt_acdirmax, "acdirmax=%u" },
 	{ Opt_actimeo, "actimeo=%u" },
-	{ Opt_userspace, "retry=%u" },
 	{ Opt_namelen, "namlen=%u" },
 	{ Opt_mountport, "mountport=%u" },
 	{ Opt_mountvers, "mountvers=%u" },
#3 lore raw

PATCH 2/6 NFS: Treat "intr" and "nointr" options as deprecated

In reply to a message not in the archive

Clean up: the "intr" and "nointr" mount options were recently retired. Document this in the NFS mount option parser.

---

 fs/nfs/super.c |    9 +++------
 1 files changed, 3 insertions(+), 6 deletions(-)
fs/nfs/super.c +3 -6
index d199f7c..b69e3ec 100644
@@ -65,7 +65,6 @@
 enum {
 	/* Mount options that take no arguments */
 	Opt_soft, Opt_hard,
-	Opt_intr, Opt_nointr,
 	Opt_posix, Opt_noposix,
 	Opt_cto, Opt_nocto,
 	Opt_ac, Opt_noac,
@@ -105,8 +104,8 @@ static match_table_t nfs_mount_option_tokens = {
 
 	{ Opt_soft, "soft" },
 	{ Opt_hard, "hard" },
-	{ Opt_intr, "intr" },
-	{ Opt_nointr, "nointr" },
+	{ Opt_deprecated, "intr" },
+	{ Opt_deprecated, "nointr" },
 	{ Opt_posix, "posix" },
 	{ Opt_noposix, "noposix" },
 	{ Opt_cto, "cto" },
@@ -784,9 +783,6 @@ static int nfs_parse_mount_options(char *raw,
 		case Opt_hard:
 			mnt->flags &= ~NFS_MOUNT_SOFT;
 			break;
-		case Opt_intr:
-		case Opt_nointr:
-			break;
 		case Opt_posix:
 			mnt->flags |= NFS_MOUNT_POSIX;
 			break;
@@ -1102,6 +1098,7 @@ static int nfs_parse_mount_options(char *raw,
 
 		case Opt_userspace:
 		case Opt_deprecated:
+			dfprintk(MOUNT, "NFS:   ignoring nfs mount option '%s'\n", p);
 			break;
 
 		default:
#4 lore raw

PATCH 3/6 NFS: missing newline in NFS mount debugging message

In reply to a message not in the archive

Clean up.

---

 fs/nfs/super.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
fs/nfs/super.c +1 -1
index b69e3ec..cfe8d4c 100644
@@ -1186,7 +1186,7 @@ static int nfs_try_mount(struct nfs_parsed_mount_data *args,
 	if (status == 0)
 		return 0;
 
-	dfprintk(MOUNT, "NFS: unable to mount server %s, error %d",
+	dfprintk(MOUNT, "NFS: unable to mount server %s, error %d\n",
 			hostname, status);
 	return status;
 }
#5 lore raw

PATCH 4/6 NFS: Fix "retrans" and "timeo" mount option behavior

In reply to a message not in the archive

Jeff Layton noticed that specifying the "proto=" mount option after the "retrans" or "timeo" options will cause the retrans and timeo values to be overwritten with default settings.

Allow these options to be specified in any order without unexpectedly reverting retrans and timeo to their default.

---

 fs/nfs/super.c         |   64 +++++++++++++++++++++++++++++++++++-------------
 include/linux/nfs_fs.h |    5 ++++
 2 files changed, 51 insertions(+), 18 deletions(-)
fs/nfs/super.c +46 -18
index cfe8d4c..1f3fa91 100644
@@ -739,11 +739,16 @@ static void nfs_parse_server_address(char *value,
 /*
  * Error-check and convert a string of mount options from user space into
  * a data structure
+ *
+ * Note that callers can pre-initialize @mnt with default settings.  Take
+ * care not to overwrite fields (and thus wipe out the default setting)
+ * unless there is actually a specified mount option.
  */
 static int nfs_parse_mount_options(char *raw,
 				   struct nfs_parsed_mount_data *mnt)
 {
 	char *p, *string, *secdata;
+	int timeo = 0, retrans = 0;
 	int rc;
 
 	if (!raw) {
@@ -816,20 +821,26 @@ static int nfs_parse_mount_options(char *raw,
 		case Opt_udp:
 			mnt->flags &= ~NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-			mnt->timeo = 7;
-			mnt->retrans = 5;
+			if (timeo == 0)
+				timeo = NFS_DEF_UDP_TIMEO;
+			if (retrans == 0)
+				retrans = NFS_DEF_UDP_RETRANS;
 			break;
 		case Opt_tcp:
 			mnt->flags |= NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-			mnt->timeo = 600;
-			mnt->retrans = 2;
+			if (timeo == 0)
+				timeo = NFS_DEF_TCP_TIMEO;
+			if (retrans == 0)
+				retrans = NFS_DEF_TCP_RETRANS;
 			break;
 		case Opt_rdma:
 			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-			mnt->timeo = 600;
-			mnt->retrans = 2;
+			if (timeo == 0)
+				timeo = NFS_DEF_TCP_TIMEO;
+			if (retrans == 0)
+				retrans = NFS_DEF_TCP_RETRANS;
 			break;
 		case Opt_acl:
 			mnt->flags &= ~NFS_MOUNT_NOACL;
@@ -873,12 +884,18 @@ static int nfs_parse_mount_options(char *raw,
 			mnt->bsize = option;
 			break;
 		case Opt_timeo:
-			if (match_int(args, &mnt->timeo))
+			if (match_int(args, &option))
+				return 0;
+			if (option <= 0)
 				return 0;
+			timeo = option;
 			break;
 		case Opt_retrans:
-			if (match_int(args, &mnt->retrans))
+			if (match_int(args, &option))
+				return 0;
+			if (option <= 0)
 				return 0;
+			retrans = option;
 			break;
 		case Opt_acregmin:
 			if (match_int(args, &mnt->acregmin))
@@ -1023,21 +1040,27 @@ static int nfs_parse_mount_options(char *raw,
 			case Opt_xprt_udp:
 				mnt->flags &= ~NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-				mnt->timeo = 7;
-				mnt->retrans = 5;
+				if (timeo == 0)
+					timeo = NFS_DEF_UDP_TIMEO;
+				if (retrans == 0)
+					retrans = NFS_DEF_UDP_RETRANS;
 				break;
 			case Opt_xprt_tcp:
 				mnt->flags |= NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-				mnt->timeo = 600;
-				mnt->retrans = 2;
+				if (timeo == 0)
+					timeo = NFS_DEF_TCP_TIMEO;
+				if (retrans == 0)
+					retrans = NFS_DEF_TCP_RETRANS;
 				break;
 			case Opt_xprt_rdma:
 				/* vector side protocols to TCP */
 				mnt->flags |= NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-				mnt->timeo = 600;
-				mnt->retrans = 2;
+				if (timeo == 0)
+					timeo = NFS_DEF_TCP_TIMEO;
+				if (retrans == 0)
+					retrans = NFS_DEF_TCP_RETRANS;
 				break;
 			default:
 				goto out_unrec_xprt;
@@ -1109,6 +1132,11 @@ static int nfs_parse_mount_options(char *raw,
 	nfs_set_port((struct sockaddr *)&mnt->nfs_server.address,
 				mnt->nfs_server.port);
 
+	if (timeo != 0)
+		mnt->timeo = timeo;
+	if (retrans != 0)
+		mnt->retrans = retrans;
+
 	return 1;
 
 out_nomem:
@@ -1222,8 +1250,8 @@ static int nfs_validate_mount_data(void *options,
 	args->flags		= (NFS_MOUNT_VER3 | NFS_MOUNT_TCP);
 	args->rsize		= NFS_MAX_FILE_IO_SIZE;
 	args->wsize		= NFS_MAX_FILE_IO_SIZE;
-	args->timeo		= 600;
-	args->retrans		= 2;
+	args->timeo		= NFS_DEF_TCP_TIMEO;
+	args->retrans		= NFS_DEF_TCP_RETRANS;
 	args->acregmin		= 3;
 	args->acregmax		= 60;
 	args->acdirmin		= 30;
@@ -1791,8 +1819,8 @@ static int nfs4_validate_mount_data(void *options,
 
 	args->rsize		= NFS_MAX_FILE_IO_SIZE;
 	args->wsize		= NFS_MAX_FILE_IO_SIZE;
-	args->timeo		= 600;
-	args->retrans		= 2;
+	args->timeo		= NFS_DEF_TCP_TIMEO;
+	args->retrans		= NFS_DEF_TCP_RETRANS;
 	args->acregmin		= 3;
 	args->acregmax		= 60;
 	args->acdirmin		= 30;
include/linux/nfs_fs.h +5 -0
index 27d6a8d..afc3956 100644
@@ -12,6 +12,11 @@
 #include <linux/magic.h>
 
 /* Default timeout values */
+#define NFS_DEF_UDP_TIMEO	(7)
+#define NFS_DEF_UDP_RETRANS	(5)
+#define NFS_DEF_TCP_TIMEO	(600)
+#define NFS_DEF_TCP_RETRANS	(2)
+
 #define NFS_MAX_UDP_TIMEOUT	(60*HZ)
 #define NFS_MAX_TCP_TIMEOUT	(600*HZ)
 
#6 lore raw

PATCH 5/6 NFS: "proto=tcp" implies "mountproto=tcp"

In reply to a message not in the archive

If a user specifies "proto=tcp" via the legacy mount API, the mount command also uses TCP to contact the server's mount daemon. Ditto for "proto=udp". This makes it easier to mount through firewalls that block one or the other transport protocol.

Make the in-kernel NFS mount option parser emulate this behavior.

---

 fs/nfs/super.c |   16 ++++++++++++++--
 1 files changed, 14 insertions(+), 2 deletions(-)
fs/nfs/super.c +14 -2
index 1f3fa91..e2cadd9 100644
@@ -749,6 +749,7 @@ static int nfs_parse_mount_options(char *raw,
 {
 	char *p, *string, *secdata;
 	int timeo = 0, retrans = 0;
+	unsigned short mount_protocol = 0;
 	int rc;
 
 	if (!raw) {
@@ -821,6 +822,8 @@ static int nfs_parse_mount_options(char *raw,
 		case Opt_udp:
 			mnt->flags &= ~NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+			if (mount_protocol == 0)
+				mount_protocol = XPRT_TRANSPORT_UDP;
 			if (timeo == 0)
 				timeo = NFS_DEF_UDP_TIMEO;
 			if (retrans == 0)
@@ -829,6 +832,8 @@ static int nfs_parse_mount_options(char *raw,
 		case Opt_tcp:
 			mnt->flags |= NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+			if (mount_protocol == 0)
+				mount_protocol = XPRT_TRANSPORT_TCP;
 			if (timeo == 0)
 				timeo = NFS_DEF_TCP_TIMEO;
 			if (retrans == 0)
@@ -1040,6 +1045,8 @@ static int nfs_parse_mount_options(char *raw,
 			case Opt_xprt_udp:
 				mnt->flags &= ~NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+				if (mount_protocol == 0)
+					mount_protocol = XPRT_TRANSPORT_UDP;
 				if (timeo == 0)
 					timeo = NFS_DEF_UDP_TIMEO;
 				if (retrans == 0)
@@ -1048,6 +1055,8 @@ static int nfs_parse_mount_options(char *raw,
 			case Opt_xprt_tcp:
 				mnt->flags |= NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+				if (mount_protocol == 0)
+					mount_protocol = XPRT_TRANSPORT_TCP;
 				if (timeo == 0)
 					timeo = NFS_DEF_TCP_TIMEO;
 				if (retrans == 0)
@@ -1076,10 +1085,10 @@ static int nfs_parse_mount_options(char *raw,
 
 			switch (token) {
 			case Opt_xprt_udp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
+				mount_protocol = XPRT_TRANSPORT_UDP;
 				break;
 			case Opt_xprt_tcp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
+				mount_protocol = XPRT_TRANSPORT_TCP;
 				break;
 			case Opt_xprt_rdma: /* not used for side protocols */
 			default:
@@ -1137,6 +1146,9 @@ static int nfs_parse_mount_options(char *raw,
 	if (retrans != 0)
 		mnt->retrans = retrans;
 
+	if (mount_protocol)
+		mnt->mount_server.protocol = mount_protocol;
+
 	return 1;
 
 out_nomem:
#7 lore raw

PATCH 6/6 NFS: Allow sloppy mount option parsing

In reply to a message not in the archive

Currently the kernel's NFS mount option parser will fail a mount if it sees options it doesn't recognize. This is to prevent data corruption in cases where a required mount option such as "noac" is misspelled.

This is a problem in heterogeneous automounter environments, however. The same automounter maps can be used by several different operating systems, and NFS clients are expected to ignore mount options from other operating systems that they don't recognize.

To address this, add a new NFS mount option that forces the in-kernel NFS mount option parser to ignore unrecognized options and bad option values.

---

 fs/nfs/super.c |  236 ++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 150 insertions(+), 86 deletions(-)
fs/nfs/super.c +150 -86
index e2cadd9..81afbcb 100644
@@ -74,6 +74,7 @@ enum {
 	Opt_acl, Opt_noacl,
 	Opt_rdirplus, Opt_nordirplus,
 	Opt_sharecache, Opt_nosharecache,
+	Opt_sloppy,
 
 	/* Mount options that take integer arguments */
 	Opt_port,
@@ -125,6 +126,7 @@ static match_table_t nfs_mount_option_tokens = {
 	{ Opt_nordirplus, "nordirplus" },
 	{ Opt_sharecache, "sharecache" },
 	{ Opt_nosharecache, "nosharecache" },
+	{ Opt_sloppy, "sloppy" },
 
 	{ Opt_port, "port=%u" },
 	{ Opt_rsize, "rsize=%u" },
@@ -703,6 +705,40 @@ static int nfs_verify_server_address(struct sockaddr *addr)
 }
 
 /*
+ * Look for the "sloppy" option.  We must copy the mount option
+ * string because strsep() replaces each ',' with a '\0'.
+ *
+ * Returns 1 if "sloppy" option is present, zero if not, or a
+ * negative errno value if a failure occurred.
+ */
+static int nfs_parse_sloppy_option(char *raw)
+{
+	char *p, *options;
+	int result = 0;
+
+	options = kstrdup(raw, GFP_KERNEL);
+	if (options == NULL)
+		return -ENOMEM;
+
+	while ((p = strsep(&options, ",")) != NULL) {
+		substring_t args[MAX_OPT_ARGS];
+		int token;
+
+		if (!*p)
+			continue;
+
+		token = match_token(p, nfs_mount_option_tokens, args);
+		if (token == Opt_sloppy) {
+			result = 1;
+			break;
+		}
+	}
+
+	kfree(options);
+	return result;
+}
+
+/*
  * Parse string addresses passed in via a mount option,
  * and construct a sockaddr based on the result.
  *
@@ -750,12 +786,17 @@ static int nfs_parse_mount_options(char *raw,
 	char *p, *string, *secdata;
 	int timeo = 0, retrans = 0;
 	unsigned short mount_protocol = 0;
-	int rc;
+	int rc, sloppy;
 
 	if (!raw) {
 		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
 		return 1;
 	}
+
+	sloppy = nfs_parse_sloppy_option(raw);
+	if (sloppy == -ENOMEM)
+		goto out_nomem;
+
 	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
 
 	secdata = alloc_secdata();
@@ -785,40 +826,40 @@ static int nfs_parse_mount_options(char *raw,
 		switch (token) {
 		case Opt_soft:
 			mnt->flags |= NFS_MOUNT_SOFT;
-			break;
+			continue;
 		case Opt_hard:
 			mnt->flags &= ~NFS_MOUNT_SOFT;
-			break;
+			continue;
 		case Opt_posix:
 			mnt->flags |= NFS_MOUNT_POSIX;
-			break;
+			continue;
 		case Opt_noposix:
 			mnt->flags &= ~NFS_MOUNT_POSIX;
-			break;
+			continue;
 		case Opt_cto:
 			mnt->flags &= ~NFS_MOUNT_NOCTO;
-			break;
+			continue;
 		case Opt_nocto:
 			mnt->flags |= NFS_MOUNT_NOCTO;
-			break;
+			continue;
 		case Opt_ac:
 			mnt->flags &= ~NFS_MOUNT_NOAC;
-			break;
+			continue;
 		case Opt_noac:
 			mnt->flags |= NFS_MOUNT_NOAC;
-			break;
+			continue;
 		case Opt_lock:
 			mnt->flags &= ~NFS_MOUNT_NONLM;
-			break;
+			continue;
 		case Opt_nolock:
 			mnt->flags |= NFS_MOUNT_NONLM;
-			break;
+			continue;
 		case Opt_v2:
 			mnt->flags &= ~NFS_MOUNT_VER3;
-			break;
+			continue;
 		case Opt_v3:
 			mnt->flags |= NFS_MOUNT_VER3;
-			break;
+			continue;
 		case Opt_udp:
 			mnt->flags &= ~NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
@@ -828,7 +869,7 @@ static int nfs_parse_mount_options(char *raw,
 				timeo = NFS_DEF_UDP_TIMEO;
 			if (retrans == 0)
 				retrans = NFS_DEF_UDP_RETRANS;
-			break;
+			continue;
 		case Opt_tcp:
 			mnt->flags |= NFS_MOUNT_TCP;
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
@@ -838,7 +879,7 @@ static int nfs_parse_mount_options(char *raw,
 				timeo = NFS_DEF_TCP_TIMEO;
 			if (retrans == 0)
 				retrans = NFS_DEF_TCP_RETRANS;
-			break;
+			continue;
 		case Opt_rdma:
 			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */
 			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
@@ -846,118 +887,121 @@ static int nfs_parse_mount_options(char *raw,
 				timeo = NFS_DEF_TCP_TIMEO;
 			if (retrans == 0)
 				retrans = NFS_DEF_TCP_RETRANS;
-			break;
+			continue;
 		case Opt_acl:
 			mnt->flags &= ~NFS_MOUNT_NOACL;
-			break;
+			continue;
 		case Opt_noacl:
 			mnt->flags |= NFS_MOUNT_NOACL;
-			break;
+			continue;
 		case Opt_rdirplus:
 			mnt->flags &= ~NFS_MOUNT_NORDIRPLUS;
-			break;
+			continue;
 		case Opt_nordirplus:
 			mnt->flags |= NFS_MOUNT_NORDIRPLUS;
-			break;
+			continue;
 		case Opt_sharecache:
 			mnt->flags &= ~NFS_MOUNT_UNSHARED;
-			break;
+			continue;
 		case Opt_nosharecache:
 			mnt->flags |= NFS_MOUNT_UNSHARED;
-			break;
+			continue;
+		case Opt_sloppy:
+			continue;
 
 		case Opt_port:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option < 0 || option > 65535)
-				return 0;
+				break;
 			mnt->nfs_server.port = option;
-			break;
+			continue;
 		case Opt_rsize:
 			if (match_int(args, &mnt->rsize))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_wsize:
 			if (match_int(args, &mnt->wsize))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_bsize:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option < 0)
-				return 0;
+				break;
 			mnt->bsize = option;
-			break;
+			continue;
 		case Opt_timeo:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option <= 0)
-				return 0;
+				break;
 			timeo = option;
-			break;
+			continue;
 		case Opt_retrans:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option <= 0)
-				return 0;
+				break;
 			retrans = option;
-			break;
+			continue;
 		case Opt_acregmin:
 			if (match_int(args, &mnt->acregmin))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_acregmax:
 			if (match_int(args, &mnt->acregmax))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_acdirmin:
 			if (match_int(args, &mnt->acdirmin))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_acdirmax:
 			if (match_int(args, &mnt->acdirmax))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_actimeo:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option < 0)
-				return 0;
+				break;
 			mnt->acregmin =
 			mnt->acregmax =
 			mnt->acdirmin =
 			mnt->acdirmax = option;
-			break;
+			continue;
 		case Opt_namelen:
 			if (match_int(args, &mnt->namlen))
-				return 0;
-			break;
+				break;
+			continue;
 		case Opt_mountport:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option < 0 || option > 65535)
-				return 0;
+				break;
 			mnt->mount_server.port = option;
-			break;
+			continue;
 		case Opt_mountvers:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			if (option < 0)
-				return 0;
+				break;
 			mnt->mount_server.version = option;
-			break;
+			continue;
 		case Opt_nfsvers:
 			if (match_int(args, &option))
-				return 0;
+				break;
 			switch (option) {
 			case 2:
 				mnt->flags &= ~NFS_MOUNT_VER3;
-				break;
+				continue;
 			case 3:
 				mnt->flags |= NFS_MOUNT_VER3;
-				break;
+				continue;
 			default:
-				goto out_unrec_vers;
+				if (!sloppy)
+					goto out_unrec_vers;
 			}
 			break;
 
@@ -978,59 +1022,60 @@ static int nfs_parse_mount_options(char *raw,
 				mnt->flags &= ~NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 0;
 				mnt->auth_flavors[0] = RPC_AUTH_NULL;
-				break;
+				continue;
 			case Opt_sec_sys:
 				mnt->flags &= ~NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 0;
 				mnt->auth_flavors[0] = RPC_AUTH_UNIX;
-				break;
+				continue;
 			case Opt_sec_krb5:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5;
-				break;
+				continue;
 			case Opt_sec_krb5i:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5I;
-				break;
+				continue;
 			case Opt_sec_krb5p:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5P;
-				break;
+				continue;
 			case Opt_sec_lkey:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEY;
-				break;
+				continue;
 			case Opt_sec_lkeyi:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEYI;
-				break;
+				continue;
 			case Opt_sec_lkeyp:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEYP;
-				break;
+				continue;
 			case Opt_sec_spkm:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKM;
-				break;
+				continue;
 			case Opt_sec_spkmi:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKMI;
-				break;
+				continue;
 			case Opt_sec_spkmp:
 				mnt->flags |= NFS_MOUNT_SECFLAVOUR;
 				mnt->auth_flavor_len = 1;
 				mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKMP;
-				break;
+				continue;
 			default:
-				goto out_unrec_sec;
+				if (!sloppy)
+					goto out_unrec_sec;
 			}
 			break;
 		case Opt_proto:
@@ -1051,7 +1096,7 @@ static int nfs_parse_mount_options(char *raw,
 					timeo = NFS_DEF_UDP_TIMEO;
 				if (retrans == 0)
 					retrans = NFS_DEF_UDP_RETRANS;
-				break;
+				continue;
 			case Opt_xprt_tcp:
 				mnt->flags |= NFS_MOUNT_TCP;
 				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
@@ -1061,7 +1106,7 @@ static int nfs_parse_mount_options(char *raw,
 					timeo = NFS_DEF_TCP_TIMEO;
 				if (retrans == 0)
 					retrans = NFS_DEF_TCP_RETRANS;
-				break;
+				continue;
 			case Opt_xprt_rdma:
 				/* vector side protocols to TCP */
 				mnt->flags |= NFS_MOUNT_TCP;
@@ -1070,9 +1115,10 @@ static int nfs_parse_mount_options(char *raw,
 					timeo = NFS_DEF_TCP_TIMEO;
 				if (retrans == 0)
 					retrans = NFS_DEF_TCP_RETRANS;
-				break;
+				continue;
 			default:
-				goto out_unrec_xprt;
+				if (!sloppy)
+					goto out_unrec_xprt;
 			}
 			break;
 		case Opt_mountproto:
@@ -1086,13 +1132,14 @@ static int nfs_parse_mount_options(char *raw,
 			switch (token) {
 			case Opt_xprt_udp:
 				mount_protocol = XPRT_TRANSPORT_UDP;
-				break;
+				continue;
 			case Opt_xprt_tcp:
 				mount_protocol = XPRT_TRANSPORT_TCP;
-				break;
+				continue;
 			case Opt_xprt_rdma: /* not used for side protocols */
 			default:
-				goto out_unrec_xprt;
+				if (!sloppy)
+					goto out_unrec_xprt;
 			}
 			break;
 		case Opt_addr:
@@ -1103,21 +1150,21 @@ static int nfs_parse_mount_options(char *raw,
 						 &mnt->nfs_server.address,
 						 &mnt->nfs_server.addrlen);
 			kfree(string);
-			break;
+			continue;
 		case Opt_clientaddr:
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
 			kfree(mnt->client_address);
 			mnt->client_address = string;
-			break;
+			continue;
 		case Opt_mounthost:
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
 			kfree(mnt->mount_server.hostname);
 			mnt->mount_server.hostname = string;
-			break;
+			continue;
 		case Opt_mountaddr:
 			string = match_strdup(args);
 			if (string == NULL)
@@ -1126,16 +1173,27 @@ static int nfs_parse_mount_options(char *raw,
 						 &mnt->mount_server.address,
 						 &mnt->mount_server.addrlen);
 			kfree(string);
-			break;
+			continue;
 
 		case Opt_userspace:
 		case Opt_deprecated:
-			dfprintk(MOUNT, "NFS:   ignoring nfs mount option '%s'\n", p);
-			break;
+			dfprintk(MOUNT, "NFS:   ignoring mount option '%s'\n",
+					p);
+			continue;
 
 		default:
+			dfprintk(MOUNT, "NFS:   unrecognized "
+					"mount option '%s'\n", p);
+			if (sloppy)
+				continue;
 			goto out_unknown;
 		}
+
+		dfprintk(MOUNT, "NFS:   incorrect value in "
+				"keyword=value mount option\n");
+		if (sloppy)
+			continue;
+		goto out_bad_value;
 	}
 
 	nfs_set_port((struct sockaddr *)&mnt->nfs_server.address,
@@ -1154,10 +1212,12 @@ static int nfs_parse_mount_options(char *raw,
 out_nomem:
 	printk(KERN_INFO "NFS: not enough memory to parse option\n");
 	return 0;
+
 out_security_failure:
 	free_secdata(secdata);
 	printk(KERN_INFO "NFS: security options invalid: %d\n", rc);
 	return 0;
+
 out_unrec_vers:
 	printk(KERN_INFO "NFS: unrecognized NFS version number\n");
 	return 0;
@@ -1170,6 +1230,10 @@ out_unrec_sec:
 	printk(KERN_INFO "NFS: unrecognized security flavor\n");
 	return 0;
 
+out_bad_value:
+	printk(KERN_INFO "NFS: incorrect value in keyword=value option\n");
+	return 0;
+
 out_unknown:
 	printk(KERN_INFO "NFS: unknown mount option: %s\n", p);
 	return 0;