/*
 * parse_dev.c -- parse device name into hostname and export path
 *
 * Copyright (C) 2008 Oracle.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "xcommon.h"
#include "nls.h"
#include "parse_dev.h"

#ifndef NFS_MAXHOSTNAME
#define NFS_MAXHOSTNAME		(255)
#endif

#ifndef NFS_MAXPATHNAME
#define NFS_MAXPATHNAME		(1024)
#endif

extern char *progname;
extern int verbose;

static int nfs_pdn_no_devname_err(void)
{
	nfs_error(_("%s: no device name was provided"), progname);
	return 0;
}

static int nfs_pdn_hostname_too_long_err(void)
{
	nfs_error(_("%s: server hostname is too long"), progname);
	return 0;
}

static int nfs_pdn_pathname_too_long_err(void)
{
	nfs_error(_("%s: export pathname is too long"), progname);
	return 0;
}

static int nfs_pdn_bad_format_err(void)
{
	nfs_error(_("%s: remote share not in 'host:dir' format"), progname);
	return 0;
}

static int nfs_pdn_nomem_err(void)
{
	nfs_error(_("%s: no memory available to parse devname"), progname);
	return 0;
}

static int nfs_pdn_missing_brace_err(void)
{
	nfs_error(_("%s: closing bracket missing from server address"),
				progname);
	return 0;
}

/*
 * Standard hostname:path format
 */
static int nfs_parse_simple_hostname(const char *dev,
				     char **hostname, char **pathname)
{
	size_t host_len, path_len;
	char *colon, *comma;

	/* Must have a colon */
	colon = strchr(dev, ':');
	if (colon == NULL)
		return nfs_pdn_bad_format_err();
	*colon = '\0';
	host_len = colon - dev;

	if (host_len > NFS_MAXHOSTNAME)
		return nfs_pdn_hostname_too_long_err();

	/* If there's a comma before the colon, take only the
	 * first name in list */
	comma = strchr(dev, ',');
	if (comma != NULL) {
		*comma = '\0';
		host_len = comma - dev;
		nfs_error(_("%s: warning: multiple hostnames not supported"),
				progname);
	} else

	colon++;
	path_len = strlen(colon);
	if (path_len > NFS_MAXPATHNAME)
		return nfs_pdn_pathname_too_long_err();

	if (hostname) {
		*hostname = strndup(dev, host_len);
		if (*hostname == NULL)
			return nfs_pdn_nomem_err();
	}
	if (pathname) {
		*pathname = strndup(colon, path_len);
		if (*pathname == NULL) {
			if (hostname)
				free(*hostname);
			return nfs_pdn_nomem_err();
		}
	}
	return 1;
}

/*
 * To handle raw IPv6 addresses (which contain colons), the
 * server's address is enclosed in square brackets.  Return
 * what's between the brackets.
 *
 * There could be anything in between the brackets, but we'll
 * let DNS resolution sort it out later.
 */
static int nfs_parse_square_bracket(const char *dev,
				    char **hostname, char **pathname)
{
	size_t host_len, path_len;
	char *cbrace;

	dev++;

	/* Must have a closing square bracket */
	cbrace = strchr(dev, ']');
	if (cbrace == NULL)
		return nfs_pdn_missing_brace_err();
	*cbrace = '\0';
	host_len = cbrace - dev;

	/* Must have a colon just after the closing bracket */
	cbrace++;
	if (*cbrace != ':')
		return nfs_pdn_bad_format_err();

	if (host_len > NFS_MAXHOSTNAME)
		return nfs_pdn_hostname_too_long_err();

	cbrace++;
	path_len = strlen(cbrace);
	if (path_len > NFS_MAXPATHNAME)
		return nfs_pdn_pathname_too_long_err();

	if (hostname) {
		*hostname = strndup(dev, host_len);
		if (*hostname == NULL)
			return nfs_pdn_nomem_err();
	}
	if (pathname) {
		*pathname = strndup(cbrace, path_len);
		if (*pathname == NULL) {
			free(*hostname);
			return nfs_pdn_nomem_err();
		}
	}
	return 1;
}

/*
 * 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.
 */
static int nfs_parse_nfs_url(__attribute__((unused)) const char *dev,
			     __attribute__((unused)) char **hostname,
			     __attribute__((unused)) char **pathname)
{
	nfs_error(_("%s: NFS URLs are not supported"), progname);
	return 0;
}

/**
 * nfs_parse_devname - Determine the server's hostname by looking at "devname".
 * @devname: pointer to mounted device name (first argument of mount command)
 * @hostname: OUT: pointer to server's hostname
 * @pathname: OUT: pointer to export path on server
 *
 * Returns 1 if succesful, or zero if some error occurred.  On success,
 * @hostname and @pathname point to dynamically allocated buffers containing
 * the hostname of the server and the export pathname (both '\0'-terminated).
 *
 * @hostname or @pathname may be NULL if caller doesn't want a copy of those
 * parts of @devname.
 *
 * Note that this will not work if @devname is a wide-character string.
 */
int nfs_parse_devname(const char *devname,
		      char **hostname, char **pathname)
{
	char *dev;
	int result;

	if (devname == NULL)
		return nfs_pdn_no_devname_err();

	/* Parser is destructive, so operate on a copy of the device name. */
	dev = strdup(devname);
	if (dev == NULL)
		return nfs_pdn_nomem_err();
	if (*dev == '[')
		result = nfs_parse_square_bracket(dev, hostname, pathname);
	else if (strncmp(dev, "nfs://", 6) == 0)
		result = nfs_parse_nfs_url(dev, hostname, pathname);
	else
		result = nfs_parse_simple_hostname(dev, hostname, pathname);

	free(dev);
	return result;
}
