public inbox for linux-nfs@vger.kernel.org
 help / color / mirror / Atom feed
From: Steve Dickson <steved@redhat.com>
To: Roland Mainz <roland.mainz@nrubsig.org>,
	Linux NFS Mailing list <linux-nfs@vger.kernel.org>
Subject: Re: [PATCH V2] mount.nfs4: Add support for nfs://-URLs
Date: Tue, 10 Dec 2024 07:37:43 -0500	[thread overview]
Message-ID: <eb4a1148-93cb-437a-a2a9-1271e9a4badc@redhat.com> (raw)
In-Reply-To: <20241210122846.821199-1-roland.mainz@nrubsig.org>



On 12/10/24 7:28 AM, Roland Mainz wrote:
> Add support for RFC 2224-style nfs://-URLs as alternative to the
> traditional hostname:/path+-o port=<tcp-port> notation,
> providing standardised, extensible, single-string, crossplatform,
> portable, Character-Encoding independent (e.g. mount point with
> Japanese, Chinese, French etc. characters) and ASCII-compatible
> descriptions of NFSv4 server resources (exports).
> 
> Reviewed-by: Martin Wege <martin.l.wege@gmail.com>
> Signed-off-by: Marvin Wenzel <marvin.wenzel@rovema.de>
> Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
> ---
>   utils/mount/Makefile.am  |   3 +-
>   utils/mount/mount.c      |   3 +
>   utils/mount/nfs4mount.c  |  69 +++++++-
>   utils/mount/nfsmount.c   |  93 ++++++++--
>   utils/mount/parse_dev.c  |  67 ++++++--
>   utils/mount/stropts.c    |  96 ++++++++++-
>   utils/mount/urlparser1.c | 358 +++++++++++++++++++++++++++++++++++++++
>   utils/mount/urlparser1.h |  60 +++++++
>   utils/mount/utils.c      | 155 +++++++++++++++++
>   utils/mount/utils.h      |  23 +++
>   10 files changed, 890 insertions(+), 37 deletions(-)
>   create mode 100644 utils/mount/urlparser1.c
>   create mode 100644 utils/mount/urlparser1.h
I would like to see a man page update explaining how
to use the URLs.

steved.

> 
> diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am
> index 83a8ee1c..0e4cab3e 100644
> --- a/utils/mount/Makefile.am
> +++ b/utils/mount/Makefile.am
> @@ -13,7 +13,8 @@ sbin_PROGRAMS	= mount.nfs
>   EXTRA_DIST = nfsmount.conf $(man8_MANS) $(man5_MANS)
>   mount_common = error.c network.c token.c \
>   		    parse_opt.c parse_dev.c \
> -		    nfsmount.c nfs4mount.c stropts.c\
> +		    nfsmount.c nfs4mount.c \
> +		    urlparser1.c urlparser1.h stropts.c \
>   		    mount_constants.h error.h network.h token.h \
>   		    parse_opt.h parse_dev.h \
>   		    nfs4_mount.h stropts.h version.h \
> diff --git a/utils/mount/mount.c b/utils/mount/mount.c
> index b98f9e00..2ce6209d 100644
> --- a/utils/mount/mount.c
> +++ b/utils/mount/mount.c
> @@ -29,6 +29,7 @@
>   #include <string.h>
>   #include <errno.h>
>   #include <fcntl.h>
> +#include <locale.h>
>   #include <sys/mount.h>
>   #include <getopt.h>
>   #include <mntent.h>
> @@ -386,6 +387,8 @@ int main(int argc, char *argv[])
>   	char *extra_opts = NULL, *mount_opts = NULL;
>   	uid_t uid = getuid();
>   
> +	(void)setlocale(LC_ALL, "");
> +
>   	progname = basename(argv[0]);
>   
>   	nfs_mount_data_version = discover_nfs_mount_data_version(&string);
> diff --git a/utils/mount/nfs4mount.c b/utils/mount/nfs4mount.c
> index 0fe142a7..8e4fbf30 100644
> --- a/utils/mount/nfs4mount.c
> +++ b/utils/mount/nfs4mount.c
> @@ -50,8 +50,10 @@
>   #include "mount_constants.h"
>   #include "nfs4_mount.h"
>   #include "nfs_mount.h"
> +#include "urlparser1.h"
>   #include "error.h"
>   #include "network.h"
> +#include "utils.h"
>   
>   #if defined(VAR_LOCK_DIR)
>   #define DEFAULT_DIR VAR_LOCK_DIR
> @@ -182,7 +184,7 @@ int nfs4mount(const char *spec, const char *node, int flags,
>   	int num_flavour = 0;
>   	int ip_addr_in_opts = 0;
>   
> -	char *hostname, *dirname, *old_opts;
> +	char *hostname, *dirname, *mb_dirname = NULL, *old_opts;
>   	char new_opts[1024];
>   	char *opt, *opteq;
>   	char *s;
> @@ -192,15 +194,66 @@ int nfs4mount(const char *spec, const char *node, int flags,
>   	int retry;
>   	int retval = EX_FAIL;
>   	time_t timeout, t;
> +	int nfs_port = NFS_PORT;
> +	parsed_nfs_url pnu;
> +
> +	(void)memset(&pnu, 0, sizeof(parsed_nfs_url));
>   
>   	if (strlen(spec) >= sizeof(hostdir)) {
>   		nfs_error(_("%s: excessively long host:dir argument\n"),
>   				progname);
>   		goto fail;
>   	}
> -	strcpy(hostdir, spec);
> -	if (parse_devname(hostdir, &hostname, &dirname))
> -		goto fail;
> +
> +	/*
> +	 * Support nfs://-URLS per RFC 2224 ("NFS URL
> +	 * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
> +	 * including custom port (nfs://hostname@port/path/...)
> +	 * and URL parameter (e.g. nfs://.../?param1=val1&param2=val2
> +	 * support
> +	 */
> +	if (is_spec_nfs_url(spec)) {
> +		if (!mount_parse_nfs_url(spec, &pnu)) {
> +			goto fail;
> +		}
> +
> +		/*
> +		 * |pnu.uctx->path| is in UTF-8, but we need the data
> +		 * in the current local locale's encoding, as mount(2)
> +		 * does not have something like a |MS_UTF8_SPEC| flag
> +		 * to indicate that the input path is in UTF-8,
> +		 * independently of the current locale
> +		 */
> +		hostname = pnu.uctx->hostport.hostname;
> +		dirname = mb_dirname = utf8str2mbstr(pnu.uctx->path);
> +
> +		(void)snprintf(hostdir, sizeof(hostdir), "%s:/%s",
> +			hostname, dirname);
> +		spec = hostdir;
> +
> +		if (pnu.uctx->hostport.port != -1) {
> +			nfs_port = pnu.uctx->hostport.port;
> +		}
> +
> +		/*
> +		 * Values added here based on URL parameters
> +		 * should be added the front of the list of options,
> +		 * so users can override the nfs://-URL given default.
> +		 *
> +		 * FIXME: We do not do that here for |MS_RDONLY|!
> +		 */
> +		if (pnu.mount_params.read_only != TRIS_BOOL_NOT_SET) {
> +			if (pnu.mount_params.read_only)
> +				flags |= MS_RDONLY;
> +			else
> +				flags &= ~MS_RDONLY;
> +		}
> +        } else {
> +		(void)strcpy(hostdir, spec);
> +
> +		if (parse_devname(hostdir, &hostname, &dirname))
> +			goto fail;
> +	}
>   
>   	if (fill_ipv4_sockaddr(hostname, &server_addr))
>   		goto fail;
> @@ -247,7 +300,7 @@ int nfs4mount(const char *spec, const char *node, int flags,
>   	/*
>   	 * NFSv4 specifies that the default port should be 2049
>   	 */
> -	server_addr.sin_port = htons(NFS_PORT);
> +	server_addr.sin_port = htons(nfs_port);
>   
>   	/* parse options */
>   
> @@ -474,8 +527,14 @@ int nfs4mount(const char *spec, const char *node, int flags,
>   		}
>   	}
>   
> +	mount_free_parse_nfs_url(&pnu);
> +	free(mb_dirname);
> +
>   	return EX_SUCCESS;
>   
>   fail:
> +	mount_free_parse_nfs_url(&pnu);
> +	free(mb_dirname);
> +
>   	return retval;
>   }
> diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c
> index a1c92fe8..e61d718a 100644
> --- a/utils/mount/nfsmount.c
> +++ b/utils/mount/nfsmount.c
> @@ -63,11 +63,13 @@
>   #include "xcommon.h"
>   #include "mount.h"
>   #include "nfs_mount.h"
> +#include "urlparser1.h"
>   #include "mount_constants.h"
>   #include "nls.h"
>   #include "error.h"
>   #include "network.h"
>   #include "version.h"
> +#include "utils.h"
>   
>   #ifdef HAVE_RPCSVC_NFS_PROT_H
>   #include <rpcsvc/nfs_prot.h>
> @@ -493,7 +495,7 @@ nfsmount(const char *spec, const char *node, int flags,
>   	 char **extra_opts, int fake, int running_bg)
>   {
>   	char hostdir[1024];
> -	char *hostname, *dirname, *old_opts, *mounthost = NULL;
> +	char *hostname, *dirname, *mb_dirname = NULL, *old_opts, *mounthost = NULL;
>   	char new_opts[1024], cbuf[1024];
>   	static struct nfs_mount_data data;
>   	int val;
> @@ -521,29 +523,79 @@ nfsmount(const char *spec, const char *node, int flags,
>   	time_t t;
>   	time_t prevt;
>   	time_t timeout;
> +	int nfsurl_port = -1;
> +	parsed_nfs_url pnu;
> +
> +	(void)memset(&pnu, 0, sizeof(parsed_nfs_url));
>   
>   	if (strlen(spec) >= sizeof(hostdir)) {
>   		nfs_error(_("%s: excessively long host:dir argument"),
>   				progname);
>   		goto fail;
>   	}
> -	strcpy(hostdir, spec);
> -	if ((s = strchr(hostdir, ':'))) {
> -		hostname = hostdir;
> -		dirname = s + 1;
> -		*s = '\0';
> -		/* Ignore all but first hostname in replicated mounts
> -		   until they can be fully supported. (mack@sgi.com) */
> -		if ((s = strchr(hostdir, ','))) {
> +
> +	/*
> +	 * Support nfs://-URLS per RFC 2224 ("NFS URL
> +	 * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
> +	 * including custom port (nfs://hostname@port/path/...)
> +	 * and URL parameter (e.g. nfs://.../?param1=val1&param2=val2
> +	 * support
> +	 */
> +	if (is_spec_nfs_url(spec)) {
> +		if (!mount_parse_nfs_url(spec, &pnu)) {
> +			goto fail;
> +		}
> +
> +		/*
> +		 * |pnu.uctx->path| is in UTF-8, but we need the data
> +		 * in the current local locale's encoding, as mount(2)
> +		 * does not have something like a |MS_UTF8_SPEC| flag
> +		 * to indicate that the input path is in UTF-8,
> +		 * independently of the current locale
> +		 */
> +		hostname = pnu.uctx->hostport.hostname;
> +		dirname = mb_dirname = utf8str2mbstr(pnu.uctx->path);
> +
> +		(void)snprintf(hostdir, sizeof(hostdir), "%s:/%s",
> +			hostname, dirname);
> +		spec = hostdir;
> +
> +		if (pnu.uctx->hostport.port != -1) {
> +			nfsurl_port = pnu.uctx->hostport.port;
> +		}
> +
> +		/*
> +		 * Values added here based on URL parameters
> +		 * should be added the front of the list of options,
> +		 * so users can override the nfs://-URL given default.
> +		 *
> +		 * FIXME: We do not do that here for |MS_RDONLY|!
> +		 */
> +		if (pnu.mount_params.read_only != TRIS_BOOL_NOT_SET) {
> +			if (pnu.mount_params.read_only)
> +				flags |= MS_RDONLY;
> +			else
> +				flags &= ~MS_RDONLY;
> +		}
> +        } else {
> +		(void)strcpy(hostdir, spec);
> +		if ((s = strchr(hostdir, ':'))) {
> +			hostname = hostdir;
> +			dirname = s + 1;
>   			*s = '\0';
> -			nfs_error(_("%s: warning: "
> -				  "multiple hostnames not supported"),
> +			/* Ignore all but first hostname in replicated mounts
> +			   until they can be fully supported. (mack@sgi.com) */
> +			if ((s = strchr(hostdir, ','))) {
> +				*s = '\0';
> +				nfs_error(_("%s: warning: "
> +					"multiple hostnames not supported"),
>   					progname);
> -		}
> -	} else {
> -		nfs_error(_("%s: directory to mount not in host:dir format"),
> +			}
> +		} else {
> +			nfs_error(_("%s: directory to mount not in host:dir format"),
>   				progname);
> -		goto fail;
> +			goto fail;
> +		}
>   	}
>   
>   	if (!nfs_gethostbyname(hostname, nfs_saddr))
> @@ -579,6 +631,14 @@ nfsmount(const char *spec, const char *node, int flags,
>   	memset(nfs_pmap, 0, sizeof(*nfs_pmap));
>   	nfs_pmap->pm_prog = NFS_PROGRAM;
>   
> +	if (nfsurl_port != -1) {
> +		/*
> +		 * Set custom TCP port defined by a nfs://-URL here,
> +		 * so $ mount -o port ... # can be used to override
> +		 */
> +		nfs_pmap->pm_port = nfsurl_port;
> +	}
> +
>   	/* parse options */
>   	new_opts[0] = 0;
>   	if (!parse_options(old_opts, &data, &bg, &retry, &mnt_server, &nfs_server,
> @@ -863,10 +923,13 @@ noauth_flavors:
>   		}
>   	}
>   
> +	mount_free_parse_nfs_url(&pnu);
> +
>   	return EX_SUCCESS;
>   
>   	/* abort */
>    fail:
> +	mount_free_parse_nfs_url(&pnu);
>   	if (fsock != -1)
>   		close(fsock);
>   	return retval;
> diff --git a/utils/mount/parse_dev.c b/utils/mount/parse_dev.c
> index 2ade5d5d..d9f8cf59 100644
> --- a/utils/mount/parse_dev.c
> +++ b/utils/mount/parse_dev.c
> @@ -27,6 +27,8 @@
>   #include "xcommon.h"
>   #include "nls.h"
>   #include "parse_dev.h"
> +#include "urlparser1.h"
> +#include "utils.h"
>   
>   #ifndef NFS_MAXHOSTNAME
>   #define NFS_MAXHOSTNAME		(255)
> @@ -179,17 +181,62 @@ static int nfs_parse_square_bracket(const char *dev,
>   }
>   
>   /*
> - * RFC 2224 says an NFS client must grok "public file handles" to
> - * support NFS URLs.  Linux doesn't do that yet.  Print a somewhat
> - * helpful error message in this case instead of pressing forward
> - * with the mount request and failing with a cryptic error message
> - * later.
> + * Support nfs://-URLS per RFC 2224 ("NFS URL
> + * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
> + * including port support (nfs://hostname@port/path/...)
>    */
> -static int nfs_parse_nfs_url(__attribute__((unused)) const char *dev,
> -			     __attribute__((unused)) char **hostname,
> -			     __attribute__((unused)) char **pathname)
> +static int nfs_parse_nfs_url(const char *dev,
> +			     char **out_hostname,
> +			     char **out_pathname)
>   {
> -	nfs_error(_("%s: NFS URLs are not supported"), progname);
> +	parsed_nfs_url pnu;
> +
> +	if (out_hostname)
> +		*out_hostname = NULL;
> +	if (out_pathname)
> +		*out_pathname = NULL;
> +
> +	/*
> +	 * Support nfs://-URLS per RFC 2224 ("NFS URL
> +	 * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
> +	 * including custom port (nfs://hostname@port/path/...)
> +	 * and URL parameter (e.g. nfs://.../?param1=val1&param2=val2
> +	 * support
> +	 */
> +	if (!mount_parse_nfs_url(dev, &pnu)) {
> +		goto fail;
> +	}
> +
> +	if (pnu.uctx->hostport.port != -1) {
> +		/* NOP here, unless we switch from hostname to hostport */
> +	}
> +
> +	if (out_hostname)
> +		*out_hostname = strdup(pnu.uctx->hostport.hostname);
> +	if (out_pathname)
> +		*out_pathname = utf8str2mbstr(pnu.uctx->path);
> +
> +	if (((out_hostname)?(*out_hostname == NULL):0) ||
> +		((out_pathname)?(*out_pathname == NULL):0)) {
> +		nfs_error(_("%s: out of memory"),
> +			progname);
> +		goto fail;
> +	}
> +
> +	mount_free_parse_nfs_url(&pnu);
> +
> +	return 1;
> +
> +fail:
> +	mount_free_parse_nfs_url(&pnu);
> +	if (out_hostname) {
> +		free(*out_hostname);
> +		*out_hostname = NULL;
> +	}
> +	if (out_pathname) {
> +		free(*out_pathname);
> +		*out_pathname = NULL;
> +	}
>   	return 0;
>   }
>   
> @@ -223,7 +270,7 @@ int nfs_parse_devname(const char *devname,
>   		return nfs_pdn_nomem_err();
>   	if (*dev == '[')
>   		result = nfs_parse_square_bracket(dev, hostname, pathname);
> -	else if (strncmp(dev, "nfs://", 6) == 0)
> +	else if (is_spec_nfs_url(dev))
>   		result = nfs_parse_nfs_url(dev, hostname, pathname);
>   	else
>   		result = nfs_parse_simple_hostname(dev, hostname, pathname);
> diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c
> index 23f0a8c0..ad92ab78 100644
> --- a/utils/mount/stropts.c
> +++ b/utils/mount/stropts.c
> @@ -42,6 +42,7 @@
>   #include "nls.h"
>   #include "nfsrpc.h"
>   #include "mount_constants.h"
> +#include "urlparser1.h"
>   #include "stropts.h"
>   #include "error.h"
>   #include "network.h"
> @@ -50,6 +51,7 @@
>   #include "parse_dev.h"
>   #include "conffile.h"
>   #include "misc.h"
> +#include "utils.h"
>   
>   #ifndef NFS_PROGRAM
>   #define NFS_PROGRAM	(100003)
> @@ -643,24 +645,106 @@ static int nfs_sys_mount(struct nfsmount_info *mi, struct mount_options *opts)
>   {
>   	char *options = NULL;
>   	int result;
> +	int nfs_port = 2049;
>   
>   	if (mi->fake)
>   		return 1;
>   
> -	if (po_join(opts, &options) == PO_FAILED) {
> -		errno = EIO;
> -		return 0;
> -	}
> +	/*
> +	 * Support nfs://-URLS per RFC 2224 ("NFS URL
> +	 * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
> +	 * including custom port (nfs://hostname@port/path/...)
> +	 * and URL parameter (e.g. nfs://.../?param1=val1&param2=val2
> +	 * support
> +	 */
> +	if (is_spec_nfs_url(mi->spec)) {
> +		parsed_nfs_url pnu;
> +		char *mb_path;
> +		char mount_source[1024];
> +
> +		if (!mount_parse_nfs_url(mi->spec, &pnu)) {
> +			result = 1;
> +			errno = EINVAL;
> +			goto done;
> +		}
> +
> +		/*
> +		 * |pnu.uctx->path| is in UTF-8, but we need the data
> +		 * in the current local locale's encoding, as mount(2)
> +		 * does not have something like a |MS_UTF8_SPEC| flag
> +		 * to indicate that the input path is in UTF-8,
> +		 * independently of the current locale
> +		 */
> +		mb_path = utf8str2mbstr(pnu.uctx->path);
> +		if (!mb_path) {
> +			nfs_error(_("%s: Could not convert path to local encoding."),
> +				progname);
> +			mount_free_parse_nfs_url(&pnu);
> +			result = 1;
> +			errno = EINVAL;
> +			goto done;
> +		}
> +
> +		(void)snprintf(mount_source, sizeof(mount_source),
> +			"%s:/%s",
> +			pnu.uctx->hostport.hostname,
> +			mb_path);
> +		free(mb_path);
> +
> +		if (pnu.uctx->hostport.port != -1) {
> +			nfs_port = pnu.uctx->hostport.port;
> +		}
>   
> -	result = mount(mi->spec, mi->node, mi->type,
> +		/*
> +		 * Insert "port=" option with the value from the nfs://
> +		 * URL at the beginning of the list of options, so
> +		 * users can override it with $ mount.nfs4 -o port= #,
> +		 * e.g.
> +		 * $ mount.nfs4 -o port=1234 nfs://10.49.202.230:400//bigdisk /mnt4 #
> +		 * should use port 1234, and not port 400 as specified
> +		 * in the URL.
> +		 */
> +		char portoptbuf[5+32+1];
> +		(void)snprintf(portoptbuf, sizeof(portoptbuf), "port=%d", nfs_port);
> +		(void)po_insert(opts, portoptbuf);
> +
> +		if (pnu.mount_params.read_only != TRIS_BOOL_NOT_SET) {
> +			if (pnu.mount_params.read_only)
> +				mi->flags |= MS_RDONLY;
> +			else
> +				mi->flags &= ~MS_RDONLY;
> +		}
> +
> +		mount_free_parse_nfs_url(&pnu);
> +
> +		if (po_join(opts, &options) == PO_FAILED) {
> +			errno = EIO;
> +			result = 1;
> +			goto done;
> +		}
> +
> +		result = mount(mount_source, mi->node, mi->type,
> +			mi->flags & ~(MS_USER|MS_USERS), options);
> +		free(options);
> +	} else {
> +		if (po_join(opts, &options) == PO_FAILED) {
> +			errno = EIO;
> +			result = 1;
> +			goto done;
> +		}
> +
> +		result = mount(mi->spec, mi->node, mi->type,
>   			mi->flags & ~(MS_USER|MS_USERS), options);
> -	free(options);
> +		free(options);
> +	}
>   
>   	if (verbose && result) {
>   		int save = errno;
>   		nfs_error(_("%s: mount(2): %s"), progname, strerror(save));
>   		errno = save;
>   	}
> +
> +done:
>   	return !result;
>   }
>   
> diff --git a/utils/mount/urlparser1.c b/utils/mount/urlparser1.c
> new file mode 100644
> index 00000000..d4c6f339
> --- /dev/null
> +++ b/utils/mount/urlparser1.c
> @@ -0,0 +1,358 @@
> +/*
> + * MIT License
> + *
> + * Copyright (c) 2024 Roland Mainz <roland.mainz@nrubsig.org>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in all
> + * copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> + * SOFTWARE.
> + */
> +
> +/* urlparser1.c - simple URL parser */
> +
> +#include <stdlib.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <ctype.h>
> +#include <stdio.h>
> +
> +#ifdef DBG_USE_WIDECHAR
> +#include <wchar.h>
> +#include <locale.h>
> +#include <io.h>
> +#include <fcntl.h>
> +#endif /* DBG_USE_WIDECHAR */
> +
> +#include "urlparser1.h"
> +
> +typedef struct _url_parser_context_private {
> +	url_parser_context c;
> +
> +	/* Private data */
> +	char *parameter_string_buff;
> +} url_parser_context_private;
> +
> +#define MAX_URL_PARAMETERS 256
> +
> +/*
> + * Original extended regular expression:
> + *
> + * "^"
> + * "(.+?)"				// scheme
> + * "://"				// '://'
> + * "("					// login
> + *	"(?:"
> + *	"(.+?)"				// user (optional)
> + *		"(?::(.+))?"		// password (optional)
> + *		"@"
> + *	")?"
> + *	"("				// hostport
> + *		"(.+?)"			// host
> + *		"(?::([[:digit:]]+))?"	// port (optional)
> + *	")"
> + * ")"
> + * "(?:/(.*?))?"			// path (optional)
> + * "(?:\?(.*?))?"			// URL parameters (optional)
> + * "$"
> + */
> +
> +#define DBGNULLSTR(s) (((s)!=NULL)?(s):"<NULL>")
> +#if 0 || defined(TEST_URLPARSER)
> +#define D(x) x
> +#else
> +#define D(x)
> +#endif
> +
> +#ifdef DBG_USE_WIDECHAR
> +/*
> + * Use wide-char APIs on WIN32, otherwise we cannot output
> + * Japanese/Chinese/etc correctly
> + */
> +#define DBG_PUTS(str, fp)		fputws(L"" str, (fp))
> +#define DBG_PUTC(c, fp)			fputwc(btowc(c), (fp))
> +#define DBG_PRINTF(fp, fmt, ...)	fwprintf((fp), L"" fmt, __VA_ARGS__)
> +#else
> +#define DBG_PUTS(str, fp)		fputs((str), (fp))
> +#define DBG_PUTC(c, fp)			fputc((c), (fp))
> +#define DBG_PRINTF(fp, fmt, ...)	fprintf((fp), fmt, __VA_ARGS__)
> +#endif /* DBG_USE_WIDECHAR */
> +
> +static
> +void urldecodestr(char *outbuff, const char *buffer, size_t len)
> +{
> +	size_t i, j;
> +
> +	for (i = j = 0 ; i < len ; ) {
> +		switch (buffer[i]) {
> +			case '%':
> +				if ((i + 2) < len) {
> +					if (isxdigit((int)buffer[i+1]) && isxdigit((int)buffer[i+2])) {
> +						const char hexstr[3] = {
> +							buffer[i+1],
> +							buffer[i+2],
> +							'\0'
> +						};
> +						outbuff[j++] = (unsigned char)strtol(hexstr, NULL, 16);
> +						i += 3;
> +					} else {
> +						/* invalid hex digit */
> +						outbuff[j++] = buffer[i];
> +						i++;
> +					}
> +				} else {
> +					/* incomplete hex digit */
> +					outbuff[j++] = buffer[i];
> +					i++;
> +				}
> +				break;
> +			case '+':
> +				outbuff[j++] = ' ';
> +				i++;
> +				break;
> +			default:
> +				outbuff[j++] = buffer[i++];
> +				break;
> +		}
> +	}
> +
> +	outbuff[j] = '\0';
> +}
> +
> +url_parser_context *url_parser_create_context(const char *in_url, unsigned int flags)
> +{
> +	url_parser_context_private *uctx;
> +	char *s;
> +	size_t in_url_len;
> +	size_t context_len;
> +
> +	/* |flags| is for future extensions */
> +	(void)flags;
> +
> +	if (!in_url)
> +		return NULL;
> +
> +	in_url_len = strlen(in_url);
> +
> +	context_len = sizeof(url_parser_context_private) +
> +		((in_url_len+1)*6) +
> +		(sizeof(url_parser_name_value)*MAX_URL_PARAMETERS)+sizeof(void*);
> +	uctx = malloc(context_len);
> +	if (!uctx)
> +		return NULL;
> +
> +	s = (void *)(uctx+1);
> +	uctx->c.in_url = s;		s+= in_url_len+1;
> +	(void)strcpy(uctx->c.in_url, in_url);
> +	uctx->c.scheme = s;		s+= in_url_len+1;
> +	uctx->c.login.username = s;	s+= in_url_len+1;
> +	uctx->c.hostport.hostname = s;	s+= in_url_len+1;
> +	uctx->c.path = s;		s+= in_url_len+1;
> +	uctx->c.hostport.port = -1;
> +	uctx->c.num_parameters = -1;
> +	uctx->c.parameters = (void *)s;		s+= (sizeof(url_parser_name_value)*MAX_URL_PARAMETERS)+sizeof(void*);
> +	uctx->parameter_string_buff = s;	s+= in_url_len+1;
> +
> +	return &uctx->c;
> +}
> +
> +int url_parser_parse(url_parser_context *ctx)
> +{
> +	url_parser_context_private *uctx = (url_parser_context_private *)ctx;
> +
> +	D((void)DBG_PRINTF(stderr, "## parser in_url='%s'\n", uctx->c.in_url));
> +
> +	char *s;
> +	const char *urlstr = uctx->c.in_url;
> +	size_t slen;
> +
> +	s = strstr(urlstr, "://");
> +	if (!s) {
> +		D((void)DBG_PUTS("url_parser: Not an URL\n", stderr));
> +		return -1;
> +	}
> +
> +	slen = s-urlstr;
> +	(void)memcpy(uctx->c.scheme, urlstr, slen);
> +	uctx->c.scheme[slen] = '\0';
> +	urlstr += slen + 3;
> +
> +	D((void)DBG_PRINTF(stdout, "scheme='%s', rest='%s'\n", uctx->c.scheme, urlstr));
> +
> +	s = strstr(urlstr, "@");
> +	if (s) {
> +		/* URL has user/password */
> +		slen = s-urlstr;
> +		urldecodestr(uctx->c.login.username, urlstr, slen);
> +		urlstr += slen + 1;
> +
> +		s = strstr(uctx->c.login.username, ":");
> +		if (s) {
> +			/* found passwd */
> +			uctx->c.login.passwd = s+1;
> +			*s = '\0';
> +		}
> +		else {
> +			uctx->c.login.passwd = NULL;
> +		}
> +
> +		/* catch password-only URLs */
> +		if (uctx->c.login.username[0] == '\0')
> +			uctx->c.login.username = NULL;
> +	}
> +	else {
> +		uctx->c.login.username = NULL;
> +		uctx->c.login.passwd = NULL;
> +	}
> +
> +	D((void)DBG_PRINTF(stdout, "login='%s', passwd='%s', rest='%s'\n",
> +		DBGNULLSTR(uctx->c.login.username),
> +		DBGNULLSTR(uctx->c.login.passwd),
> +		DBGNULLSTR(urlstr)));
> +
> +	char *raw_parameters;
> +
> +	uctx->c.num_parameters = 0;
> +	raw_parameters = strstr(urlstr, "?");
> +	/* Do we have a non-empty parameter string ? */
> +	if (raw_parameters && (raw_parameters[1] != '\0')) {
> +		*raw_parameters++ = '\0';
> +		D((void)DBG_PRINTF(stdout, "raw parameters = '%s'\n", raw_parameters));
> +
> +		char *ps = raw_parameters;
> +		char *pv; /* parameter value */
> +		char *na; /* next '&' */
> +		char *pb = uctx->parameter_string_buff;
> +		char *pname;
> +		char *pvalue;
> +		ssize_t pi;
> +
> +		for (pi = 0; pi < MAX_URL_PARAMETERS ; pi++) {
> +			pname = ps;
> +
> +			/*
> +			 * Handle parameters without value,
> +			 * e.g. "path?name1&name2=value2"
> +			 */
> +			na = strstr(ps, "&");
> +			pv = strstr(ps, "=");
> +			if (pv && (na?(na > pv):true)) {
> +				*pv++ = '\0';
> +				pvalue = pv;
> +				ps = pv;
> +			}
> +			else {
> +				pvalue = NULL;
> +			}
> +
> +			if (na) {
> +				*na++ = '\0';
> +			}
> +
> +			/* URLDecode parameter name */
> +			urldecodestr(pb, pname, strlen(pname));
> +			uctx->c.parameters[pi].name = pb;
> +			pb += strlen(uctx->c.parameters[pi].name)+1;
> +
> +			/* URLDecode parameter value */
> +			if (pvalue) {
> +				urldecodestr(pb, pvalue, strlen(pvalue));
> +				uctx->c.parameters[pi].value = pb;
> +				pb += strlen(uctx->c.parameters[pi].value)+1;
> +			}
> +			else {
> +				uctx->c.parameters[pi].value = NULL;
> +			}
> +
> +			/* Next '&' ? */
> +			if (!na)
> +				break;
> +
> +			ps = na;
> +		}
> +
> +		uctx->c.num_parameters = pi+1;
> +	}
> +
> +	s = strstr(urlstr, "/");
> +	if (s) {
> +		/* URL has hostport */
> +		slen = s-urlstr;
> +		urldecodestr(uctx->c.hostport.hostname, urlstr, slen);
> +		urlstr += slen + 1;
> +
> +		/*
> +		 * check for addresses within '[' and ']', like
> +		 * IPv6 addresses
> +		 */
> +		s = uctx->c.hostport.hostname;
> +		if (s[0] == '[')
> +			s = strstr(s, "]");
> +
> +		if (s == NULL) {
> +			D((void)DBG_PUTS("url_parser: Unmatched '[' in hostname\n", stderr));
> +			return -1;
> +		}
> +
> +		s = strstr(s, ":");
> +		if (s) {
> +			/* found port number */
> +			uctx->c.hostport.port = atoi(s+1);
> +			*s = '\0';
> +		}
> +	}
> +	else {
> +		(void)strcpy(uctx->c.hostport.hostname, urlstr);
> +		uctx->c.path = NULL;
> +		urlstr = NULL;
> +	}
> +
> +	D(
> +		(void)DBG_PRINTF(stdout,
> +			"hostport='%s', port=%d, rest='%s', num_parameters=%d\n",
> +			DBGNULLSTR(uctx->c.hostport.hostname),
> +			uctx->c.hostport.port,
> +			DBGNULLSTR(urlstr),
> +			(int)uctx->c.num_parameters);
> +	);
> +
> +
> +	D(
> +		ssize_t dpi;
> +		for (dpi = 0 ; dpi < uctx->c.num_parameters ; dpi++) {
> +			(void)DBG_PRINTF(stdout,
> +				"param[%d]: name='%s'/value='%s'\n",
> +				(int)dpi,
> +				uctx->c.parameters[dpi].name,
> +				DBGNULLSTR(uctx->c.parameters[dpi].value));
> +		}
> +	);
> +
> +	if (!urlstr) {
> +		goto done;
> +	}
> +
> +	urldecodestr(uctx->c.path, urlstr, strlen(urlstr));
> +	D((void)DBG_PRINTF(stdout, "path='%s'\n", uctx->c.path));
> +
> +done:
> +	return 0;
> +}
> +
> +void url_parser_free_context(url_parser_context *c)
> +{
> +	free(c);
> +}
> diff --git a/utils/mount/urlparser1.h b/utils/mount/urlparser1.h
> new file mode 100644
> index 00000000..515eea9d
> --- /dev/null
> +++ b/utils/mount/urlparser1.h
> @@ -0,0 +1,60 @@
> +/*
> + * MIT License
> + *
> + * Copyright (c) 2024 Roland Mainz <roland.mainz@nrubsig.org>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in all
> + * copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> + * SOFTWARE.
> + */
> +
> +/* urlparser1.h - header for simple URL parser */
> +
> +#ifndef __URLPARSER1_H__
> +#define __URLPARSER1_H__ 1
> +
> +#include <stdlib.h>
> +
> +typedef struct _url_parser_name_value {
> +	char *name;
> +	char *value;
> +} url_parser_name_value;
> +
> +typedef struct _url_parser_context {
> +	char *in_url;
> +
> +	char *scheme;
> +	struct {
> +		char *username;
> +		char *passwd;
> +	} login;
> +	struct {
> +		char *hostname;
> +		signed int port;
> +	} hostport;
> +	char *path;
> +
> +	ssize_t num_parameters;
> +	url_parser_name_value *parameters;
> +} url_parser_context;
> +
> +/* Prototypes */
> +url_parser_context *url_parser_create_context(const char *in_url, unsigned int flags);
> +int url_parser_parse(url_parser_context *uctx);
> +void url_parser_free_context(url_parser_context *c);
> +
> +#endif /* !__URLPARSER1_H__ */
> diff --git a/utils/mount/utils.c b/utils/mount/utils.c
> index b7562a47..3d55e997 100644
> --- a/utils/mount/utils.c
> +++ b/utils/mount/utils.c
> @@ -28,6 +28,7 @@
>   #include <unistd.h>
>   #include <sys/types.h>
>   #include <sys/stat.h>
> +#include <iconv.h>
>   
>   #include "sockaddr.h"
>   #include "nfs_mount.h"
> @@ -173,3 +174,157 @@ int nfs_umount23(const char *devname, char *string)
>   	free(dirname);
>   	return result;
>   }
> +
> +/* Convert UTF-8 string to multibyte string in the current locale */
> +char *utf8str2mbstr(const char *utf8_str)
> +{
> +	iconv_t cd;
> +
> +	cd = iconv_open("", "UTF-8");
> +	if (cd == (iconv_t)-1) {
> +		perror("utf8str2mbstr: iconv_open failed");
> +		return NULL;
> +	}
> +
> +	size_t inbytesleft = strlen(utf8_str);
> +	char *inbuf = (char *)utf8_str;
> +	size_t outbytesleft = inbytesleft*4+1;
> +	char *outbuf = malloc(outbytesleft);
> +	char *outbuf_orig = outbuf;
> +
> +	if (!outbuf) {
> +		perror("utf8str2mbstr: out of memory");
> +		(void)iconv_close(cd);
> +		return NULL;
> +	}
> +
> +	int ret = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
> +	if (ret == -1) {
> +		perror("utf8str2mbstr: iconv() failed");
> +		free(outbuf_orig);
> +		(void)iconv_close(cd);
> +		return NULL;
> +	}
> +
> +	*outbuf = '\0';
> +
> +	(void)iconv_close(cd);
> +	return outbuf_orig;
> +}
> +
> +/* fixme: We should use |bool|! */
> +int is_spec_nfs_url(const char *spec)
> +{
> +	return (!strncmp(spec, "nfs://", 6));
> +}
> +
> +int mount_parse_nfs_url(const char *spec, parsed_nfs_url *pnu)
> +{
> +	int result = 1;
> +	url_parser_context *uctx = NULL;
> +
> +	(void)memset(pnu, 0, sizeof(parsed_nfs_url));
> +	pnu->mount_params.read_only = TRIS_BOOL_NOT_SET;
> +
> +	uctx = url_parser_create_context(spec, 0);
> +	if (!uctx) {
> +		nfs_error(_("%s: out of memory"),
> +			progname);
> +		result = 1;
> +		goto done;
> +	}
> +
> +	if (url_parser_parse(uctx) < 0) {
> +		nfs_error(_("%s: Error parsing nfs://-URL."),
> +			progname);
> +		result = 1;
> +		goto done;
> +	}
> +	if (uctx->login.username || uctx->login.passwd) {
> +		nfs_error(_("%s: Username/Password are not defined for nfs://-URL."),
> +			progname);
> +		result = 1;
> +		goto done;
> +	}
> +	if (!uctx->path) {
> +		nfs_error(_("%s: Path missing in nfs://-URL."),
> +			progname);
> +		result = 1;
> +		goto done;
> +	}
> +	if (uctx->path[0] != '/') {
> +		nfs_error(_("%s: Relative nfs://-URLs are not supported."),
> +			progname);
> +		result = 1;
> +		goto done;
> +	}
> +
> +	if (uctx->num_parameters > 0) {
> +		int pi;
> +		const char *pname;
> +		const char *pvalue;
> +
> +		/*
> +		 * Values added here based on URL parameters
> +		 * should be added the front of the list of options,
> +		 * so users can override the nfs://-URL given default.
> +		 */
> +		for (pi = 0; pi < uctx->num_parameters ; pi++) {
> +			pname = uctx->parameters[pi].name;
> +			pvalue = uctx->parameters[pi].value;
> +
> +			if (!strcmp(pname, "rw")) {
> +				if ((pvalue == NULL) || (!strcmp(pvalue, "1"))) {
> +					pnu->mount_params.read_only = TRIS_BOOL_FALSE;
> +				}
> +				else if (!strcmp(pvalue, "0")) {
> +					pnu->mount_params.read_only = TRIS_BOOL_TRUE;
> +				}
> +				else {
> +					nfs_error(_("%s: Unsupported nfs://-URL "
> +						"parameter '%s' value '%s'."),
> +						progname, pname, pvalue);
> +					result = 1;
> +					goto done;
> +				}
> +			}
> +			else if (!strcmp(pname, "ro")) {
> +				if ((pvalue == NULL) || (!strcmp(pvalue, "1"))) {
> +					pnu->mount_params.read_only = TRIS_BOOL_TRUE;
> +				}
> +				else if (!strcmp(pvalue, "0")) {
> +					pnu->mount_params.read_only = TRIS_BOOL_FALSE;
> +				}
> +				else {
> +					nfs_error(_("%s: Unsupported nfs://-URL "
> +						"parameter '%s' value '%s'."),
> +						progname, pname, pvalue);
> +					result = 1;
> +					goto done;
> +				}
> +			}
> +			else {
> +				nfs_error(_("%s: Unsupported nfs://-URL "
> +						"parameter '%s'."),
> +					progname, pname);
> +				result = 1;
> +				goto done;
> +			}
> +		}
> +	}
> +
> +	result = 0;
> +done:
> +	if (result != 0) {
> +		url_parser_free_context(uctx);
> +		return 0;
> +	}
> +
> +	pnu->uctx = uctx;
> +	return 1;
> +}
> +
> +void mount_free_parse_nfs_url(parsed_nfs_url *pnu)
> +{
> +	url_parser_free_context(pnu->uctx);
> +}
> diff --git a/utils/mount/utils.h b/utils/mount/utils.h
> index 224918ae..465c0a5e 100644
> --- a/utils/mount/utils.h
> +++ b/utils/mount/utils.h
> @@ -24,13 +24,36 @@
>   #define _NFS_UTILS_MOUNT_UTILS_H
>   
>   #include "parse_opt.h"
> +#include "urlparser1.h"
>   
> +/* Boolean with three states: { not_set, false, true */
> +typedef signed char tristate_bool;
> +#define TRIS_BOOL_NOT_SET (-1)
> +#define TRIS_BOOL_TRUE (1)
> +#define TRIS_BOOL_FALSE (0)
> +
> +#define TRIS_BOOL_GET_VAL(tsb, tsb_default) \
> +	(((tsb)!=TRIS_BOOL_NOT_SET)?(tsb):(tsb_default))
> +
> +typedef struct _parsed_nfs_url {
> +	url_parser_context *uctx;
> +	struct {
> +		tristate_bool read_only;
> +	} mount_params;
> +} parsed_nfs_url;
> +
> +/* Prototypes */
>   int discover_nfs_mount_data_version(int *string_ver);
>   void print_one(char *spec, char *node, char *type, char *opts);
>   void mount_usage(void);
>   void umount_usage(void);
>   int chk_mountpoint(const char *mount_point);
> +char *utf8str2mbstr(const char *utf8_str);
> +int is_spec_nfs_url(const char *spec);
>   
>   int nfs_umount23(const char *devname, char *string);
>   
> +int mount_parse_nfs_url(const char *spec, parsed_nfs_url *pnu);
> +void mount_free_parse_nfs_url(parsed_nfs_url *pnu);
> +
>   #endif	/* !_NFS_UTILS_MOUNT_UTILS_H */


  reply	other threads:[~2024-12-10 12:37 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-12-10 12:28 [PATCH V2] mount.nfs4: Add support for nfs://-URLs Roland Mainz
2024-12-10 12:37 ` Steve Dickson [this message]
2024-12-10 17:54 ` Chuck Lever
2024-12-12 17:05 ` Jeff Layton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=eb4a1148-93cb-437a-a2a9-1271e9a4badc@redhat.com \
    --to=steved@redhat.com \
    --cc=linux-nfs@vger.kernel.org \
    --cc=roland.mainz@nrubsig.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox