*
* 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., 59 Temple Place - Suite 330,
- * Boston, MA 021110-1307, USA.
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA
*
*/
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
#include <netinet/in.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <rpc/pmap_clnt.h>
+#include "sockaddr.h"
#include "xcommon.h"
#include "mount.h"
#include "nls.h"
#include "nfs_mount.h"
#include "mount_constants.h"
+#include "nfsrpc.h"
+#include "parse_opt.h"
#include "network.h"
-
-#ifdef HAVE_RPCSVC_NFS_PROT_H
-#include <rpcsvc/nfs_prot.h>
-#else
-#include <linux/nfs.h>
-#define nfsstat nfs_stat
-#endif
-
-#ifndef NFS_PORT
-#define NFS_PORT 2049
-#endif
+#include "conffile.h"
+#include "nfslib.h"
#define PMAP_TIMEOUT (10)
#define CONNECT_TIMEOUT (20)
#define MOUNT_TIMEOUT (30)
-#if SIZEOF_SOCKLEN_T - 0 == 0
-#define socklen_t unsigned int
-#endif
+#define SAFE_SOCKADDR(x) (struct sockaddr *)(char *)(x)
extern int nfs_mount_data_version;
extern char *progname;
extern int verbose;
+static const char *nfs_ns_pgmtbl[] = {
+ "status",
+ NULL,
+};
+
+static const char *nfs_mnt_pgmtbl[] = {
+ "mount",
+ "mountd",
+ NULL,
+};
+
+static const char *nfs_nfs_pgmtbl[] = {
+ "nfs",
+ "nfsprog",
+ NULL,
+};
+
+static const char *nfs_transport_opttbl[] = {
+ "udp",
+ "tcp",
+ "rdma",
+ "proto",
+ NULL,
+};
+
+static const char *nfs_version_opttbl[] = {
+ "v2",
+ "v3",
+ "v4",
+ "vers",
+ "nfsvers",
+ NULL,
+};
+
static const unsigned long nfs_to_mnt[] = {
0,
0,
0,
};
+static const unsigned int *nfs_default_proto(void);
+#ifdef MOUNT_CONFIG
+static const unsigned int *nfs_default_proto()
+{
+ extern unsigned long config_default_proto;
+ /*
+ * If the default proto has been set and
+ * its not TCP, start with UDP
+ */
+ if (config_default_proto && config_default_proto != IPPROTO_TCP)
+ return probe_udp_first;
+
+ return probe_tcp_first;
+}
+#else
+static const unsigned int *nfs_default_proto()
+{
+ return probe_tcp_first;
+}
+#endif /* MOUNT_CONFIG */
+
/**
- * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
+ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address
* @hostname: pointer to C string containing DNS hostname to resolve
+ * @family: address family hint
* @sap: pointer to buffer to fill with socket address
* @len: IN: size of buffer to fill; OUT: size of socket address
*
* Returns 1 and places a socket address at @sap if successful;
* otherwise zero.
*/
-int nfs_name_to_address(const char *hostname,
- const sa_family_t af_hint,
- struct sockaddr *sap, socklen_t *salen)
+int nfs_lookup(const char *hostname, const sa_family_t family,
+ struct sockaddr *sap, socklen_t *salen)
{
struct addrinfo *gai_results;
struct addrinfo gai_hint = {
- .ai_family = af_hint,
- .ai_flags = AI_ADDRCONFIG,
+ .ai_family = family,
};
socklen_t len = *salen;
int error, ret = 0;
- if (af_hint == AF_INET6)
- gai_hint.ai_flags |= AI_V4MAPPED|AI_ALL;
-
*salen = 0;
error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results);
- if (error) {
+ switch (error) {
+ case 0:
+ break;
+ case EAI_SYSTEM:
+ nfs_error(_("%s: DNS resolution failed for %s: %s"),
+ progname, hostname, strerror(errno));
+ return ret;
+ default:
nfs_error(_("%s: DNS resolution failed for %s: %s"),
- progname, hostname, (error == EAI_SYSTEM ?
- strerror(errno) : gai_strerror(error)));
+ progname, hostname, gai_strerror(error));
return ret;
}
/**
* nfs_gethostbyname - resolve a hostname to an IPv4 address
* @hostname: pointer to a C string containing a DNS hostname
- * @saddr: returns an IPv4 address
+ * @sin: returns an IPv4 address
*
* Returns 1 if successful, otherwise zero.
*/
{
socklen_t len = sizeof(*sin);
- return nfs_name_to_address(hostname, AF_INET,
- (struct sockaddr *)sin, &len);
+ return nfs_lookup(hostname, AF_INET, (struct sockaddr *)sin, &len);
}
/**
* nfs_string_to_sockaddr - convert string address to sockaddr
* @address: pointer to presentation format address to convert
- * @addrlen: length of presentation address
* @sap: pointer to socket address buffer to fill in
* @salen: IN: length of address buffer
* OUT: length of converted socket address
*
* Convert a presentation format address string to a socket address.
- * Similar to nfs_name_to_address(), but the DNS query is squelched,
- * and won't make any noise if the getaddrinfo() call fails.
+ * Similar to nfs_lookup(), but the DNS query is squelched, and it
+ * won't make any noise if the getaddrinfo() call fails.
*
* Returns 1 and fills in @sap and @salen if successful; otherwise zero.
*
* See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details
* on presenting IPv6 addresses as text strings.
*/
-int nfs_string_to_sockaddr(const char *address, const size_t addrlen,
- struct sockaddr *sap, socklen_t *salen)
+int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap,
+ socklen_t *salen)
{
struct addrinfo *gai_results;
struct addrinfo gai_hint = {
if (bindresvport(so, &laddr) < 0)
goto err_bindresvport;
} else {
- cc = bind(so, (struct sockaddr *)&laddr, namelen);
+ cc = bind(so, SAFE_SOCKADDR(&laddr), namelen);
if (cc < 0)
goto err_bind;
}
if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) {
- cc = connect_to(so, (struct sockaddr *)saddr, namelen,
+ cc = connect_to(so, SAFE_SOCKADDR(saddr), namelen,
timeout);
if (cc < 0)
goto err_connect;
return RPC_ANYSOCK;
}
-/*
- * getport() is very similar to pmap_getport() with the exception that
- * this version tries to use an ephemeral port, since reserved ports are
- * not needed for GETPORT queries. This conserves the very limited
- * reserved port space, which helps reduce failed socket binds
- * during mount storms.
- *
- * A side effect of calling this function is that rpccreateerr is set.
- */
-static unsigned short getport(struct sockaddr_in *saddr,
- unsigned long program,
- unsigned long version,
- unsigned int proto)
-{
- struct sockaddr_in bind_saddr;
- unsigned short port = 0;
- int socket;
- CLIENT *clnt = NULL;
- enum clnt_stat stat;
-
- bind_saddr = *saddr;
- bind_saddr.sin_port = htons(PMAPPORT);
-
- socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, FALSE);
- if (socket == RPC_ANYSOCK) {
- if (proto == IPPROTO_TCP &&
- rpc_createerr.cf_error.re_errno == ETIMEDOUT)
- rpc_createerr.cf_stat = RPC_TIMEDOUT;
- return 0;
- }
+static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen,
+ const rpcprog_t program, const rpcvers_t version,
+ const unsigned short protocol,
+ const unsigned short port)
+{
+ char buf[NI_MAXHOST];
- switch (proto) {
- case IPPROTO_UDP:
- clnt = clntudp_bufcreate(&bind_saddr,
- PMAPPROG, PMAPVERS,
- RETRY_TIMEOUT, &socket,
- RPCSMALLMSGSIZE,
- RPCSMALLMSGSIZE);
- break;
- case IPPROTO_TCP:
- clnt = clnttcp_create(&bind_saddr,
- PMAPPROG, PMAPVERS,
- &socket,
- RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
- break;
- }
- if (clnt != NULL) {
- struct pmap parms = {
- .pm_prog = program,
- .pm_vers = version,
- .pm_prot = proto,
- };
-
- stat = clnt_call(clnt, PMAPPROC_GETPORT,
- (xdrproc_t)xdr_pmap, (caddr_t)&parms,
- (xdrproc_t)xdr_u_short, (caddr_t)&port,
- TIMEOUT);
- if (stat) {
- clnt_geterr(clnt, &rpc_createerr.cf_error);
- rpc_createerr.cf_stat = stat;
- }
- clnt_destroy(clnt);
- if (stat != RPC_SUCCESS)
- port = 0;
- else if (port == 0)
- rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+ if (!verbose)
+ return;
+
+ if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) {
+ buf[0] = '\0';
+ strcat(buf, "unknown host");
}
- close(socket);
- return port;
+ fprintf(stderr, _("%s: trying %s prog %lu vers %lu prot %s port %d\n"),
+ progname, buf, (unsigned long)program,
+ (unsigned long)version,
+ (protocol == IPPROTO_UDP ? _("UDP") : _("TCP")),
+ port);
+}
+
+static void nfs_pp_debug2(const char *str)
+{
+ if (!verbose)
+ return;
+
+ if (rpc_createerr.cf_error.re_status == RPC_CANTRECV ||
+ rpc_createerr.cf_error.re_status == RPC_CANTSEND)
+ nfs_error(_("%s: portmap query %s%s - %s"),
+ progname, str, clnt_spcreateerror(""),
+ strerror(rpc_createerr.cf_error.re_errno));
+ else
+ nfs_error(_("%s: portmap query %s%s"),
+ progname, str, clnt_spcreateerror(""));
}
/*
* Use the portmapper to discover whether or not the service we want is
* available. The lists 'versions' and 'protos' define ordered sequences
* of service versions and udp/tcp protocols to probe for.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping. Note that if a port is already specified
+ * in @pmap and it matches the rpcbind query result, nfs_probe_port() does
+ * not perform an RPC ping.
+ *
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
*/
-static int probe_port(clnt_addr_t *server, const unsigned long *versions,
- const unsigned int *protos)
+static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
+ struct pmap *pmap, const unsigned long *versions,
+ const unsigned int *protos)
{
- struct sockaddr_in *saddr = &server->saddr;
- struct pmap *pmap = &server->pmap;
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
const unsigned long prog = pmap->pm_prog, *p_vers;
const unsigned int prot = (u_int)pmap->pm_prot, *p_prot;
const u_short port = (u_short) pmap->pm_port;
unsigned long vers = pmap->pm_vers;
unsigned short p_port;
+ memcpy(saddr, sap, salen);
p_prot = prot ? &prot : protos;
p_vers = vers ? &vers : versions;
- rpc_createerr.cf_stat = 0;
+
for (;;) {
- p_port = getport(saddr, prog, *p_vers, *p_prot);
+ if (verbose)
+ printf(_("%s: prog %lu, trying vers=%lu, prot=%u\n"),
+ progname, prog, *p_vers, *p_prot);
+ p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot);
if (p_port) {
if (!port || port == p_port) {
- saddr->sin_port = htons(p_port);
- if (verbose) {
- printf(_("%s: trying %s prog %ld vers "
- "%ld prot %s port %d\n"),
- progname,
- inet_ntoa(saddr->sin_addr),
- prog, *p_vers,
- *p_prot == IPPROTO_UDP ?
- _("UDP") : _("TCP"),
- p_port);
- }
- if (clnt_ping(saddr, prog, *p_vers, *p_prot, NULL))
+ nfs_set_port(saddr, p_port);
+ nfs_pp_debug(saddr, salen, prog, *p_vers,
+ *p_prot, p_port);
+ if (nfs_rpc_ping(saddr, salen, prog,
+ *p_vers, *p_prot, NULL))
goto out_ok;
- if (rpc_createerr.cf_stat == RPC_TIMEDOUT)
- goto out_bad;
- }
+ } else
+ rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
}
if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED &&
+ rpc_createerr.cf_stat != RPC_TIMEDOUT &&
+ rpc_createerr.cf_stat != RPC_CANTRECV &&
rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH)
- goto out_bad;
+ break;
if (!prot) {
- if (*++p_prot)
+ if (*++p_prot) {
+ nfs_pp_debug2("retrying");
continue;
+ }
p_prot = protos;
}
+ if (rpc_createerr.cf_stat == RPC_TIMEDOUT ||
+ rpc_createerr.cf_stat == RPC_CANTRECV)
+ break;
+
if (vers || !*++p_vers)
break;
}
-out_bad:
+ nfs_pp_debug2("failed");
return 0;
out_ok:
pmap->pm_prot = *p_prot;
if (!port)
pmap->pm_port = p_port;
- rpc_createerr.cf_stat = 0;
+ nfs_clear_rpc_createerr();
return 1;
}
-
-static int probe_nfsport(clnt_addr_t *nfs_server)
+/*
+ * Probe a server's NFS service to determine which versions and
+ * transport protocols are supported.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping. If all three are already specified, simply
+ * return success without an rpcbind query or RPC ping (we may be trying
+ * to mount an NFS service that is not advertised via rpcbind).
+ *
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen,
+ struct pmap *pmap)
{
- struct pmap *pmap = &nfs_server->pmap;
-
if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
return 1;
- if (nfs_mount_data_version >= 4)
- return probe_port(nfs_server, probe_nfs3_first, probe_tcp_first);
- else
- return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
+ if (nfs_mount_data_version >= 4) {
+ const unsigned int *probe_proto;
+
+ probe_proto = nfs_default_proto();
+
+ return nfs_probe_port(sap, salen, pmap,
+ probe_nfs3_first, probe_proto);
+ } else
+ return nfs_probe_port(sap, salen, pmap,
+ probe_nfs2_only, probe_udp_only);
}
-static int probe_mntport(clnt_addr_t *mnt_server)
+/*
+ * Probe a server's mountd service to determine which versions and
+ * transport protocols are supported.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping. If all three are already specified, simply
+ * return success without an rpcbind query or RPC ping (we may be trying
+ * to mount an NFS service that is not advertised via rpcbind).
+ *
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_mntport(const struct sockaddr *sap, const socklen_t salen,
+ struct pmap *pmap)
{
- struct pmap *pmap = &mnt_server->pmap;
-
if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
return 1;
if (nfs_mount_data_version >= 4)
- return probe_port(mnt_server, probe_mnt3_first, probe_udp_first);
+ return nfs_probe_port(sap, salen, pmap,
+ probe_mnt3_first, probe_udp_first);
else
- return probe_port(mnt_server, probe_mnt1_first, probe_udp_only);
+ return nfs_probe_port(sap, salen, pmap,
+ probe_mnt1_first, probe_udp_only);
}
-/**
- * probe_bothports - discover the RPC endpoints of mountd and NFS server
- * @mnt_server: pointer to address and pmap argument for mountd results
- * @nfs_server: pointer to address and pmap argument for NFS server
+/*
+ * Probe a server's mountd service to determine which versions and
+ * transport protocols are supported. Invoked when the protocol
+ * version is already known for both the NFS and mountd service.
*
- * Returns 1 if successful, otherwise zero if some error occurred.
- * Note that the arguments are both input and output arguments.
+ * Returns 1 and fills in both @pmap structs if the requested service
+ * ports are unambiguous and pingable. Otherwise zero is returned;
+ * rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_version_fixed(const struct sockaddr *mnt_saddr,
+ const socklen_t mnt_salen,
+ struct pmap *mnt_pmap,
+ const struct sockaddr *nfs_saddr,
+ const socklen_t nfs_salen,
+ struct pmap *nfs_pmap)
+{
+ if (!nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap))
+ return 0;
+ return nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap);
+}
+
+/**
+ * nfs_probe_bothports - discover the RPC endpoints of mountd and NFS server
+ * @mnt_saddr: pointer to socket address of mountd server
+ * @mnt_salen: length of mountd server's address
+ * @mnt_pmap: IN: partially filled-in mountd RPC service tuple;
+ * OUT: fully filled-in mountd RPC service tuple
+ * @nfs_saddr: pointer to socket address of NFS server
+ * @nfs_salen: length of NFS server's address
+ * @nfs_pmap: IN: partially filled-in NFS RPC service tuple;
+ * OUT: fully filled-in NFS RPC service tuple
*
- * A side effect of calling this function is that rpccreateerr is set.
+ * Returns 1 and fills in both @pmap structs if the requested service
+ * ports are unambiguous and pingable. Otherwise zero is returned;
+ * rpccreateerr.cf_stat is set to reflect the nature of the error.
*/
-int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
+int nfs_probe_bothports(const struct sockaddr *mnt_saddr,
+ const socklen_t mnt_salen,
+ struct pmap *mnt_pmap,
+ const struct sockaddr *nfs_saddr,
+ const socklen_t nfs_salen,
+ struct pmap *nfs_pmap)
{
- struct pmap *nfs_pmap = &nfs_server->pmap;
- struct pmap *mnt_pmap = &mnt_server->pmap;
struct pmap save_nfs, save_mnt;
- int res;
const unsigned long *probe_vers;
if (mnt_pmap->pm_vers && !nfs_pmap->pm_vers)
nfs_pmap->pm_vers = mntvers_to_nfs(mnt_pmap->pm_vers);
else if (nfs_pmap->pm_vers && !mnt_pmap->pm_vers)
mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers);
+
if (nfs_pmap->pm_vers)
- goto version_fixed;
+ return nfs_probe_version_fixed(mnt_saddr, mnt_salen, mnt_pmap,
+ nfs_saddr, nfs_salen, nfs_pmap);
memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs));
memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt));
for (; *probe_vers; probe_vers++) {
nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers);
- if ((res = probe_nfsport(nfs_server) != 0)) {
+ if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap) != 0) {
mnt_pmap->pm_vers = *probe_vers;
- if ((res = probe_mntport(mnt_server)) != 0)
+ if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0)
return 1;
memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap));
}
case RPC_PROGNOTREGISTERED:
break;
default:
- goto out_bad;
+ return 0;
}
memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap));
}
-out_bad:
return 0;
-
-version_fixed:
- if (!probe_nfsport(nfs_server))
- goto out_bad;
- return probe_mntport(mnt_server);
}
-static int probe_statd(void)
+/**
+ * probe_bothports - discover the RPC endpoints of mountd and NFS server
+ * @mnt_server: pointer to address and pmap argument for mountd results
+ * @nfs_server: pointer to address and pmap argument for NFS server
+ *
+ * This is the legacy API that takes "clnt_addr_t" for both servers,
+ * but supports only AF_INET addresses.
+ *
+ * Returns 1 and fills in the pmap field in both clnt_addr_t structs
+ * if the requested service ports are unambiguous and pingable.
+ * Otherwise zero is returned; rpccreateerr.cf_stat is set to reflect
+ * the nature of the error.
+ */
+int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
{
- struct sockaddr_in addr;
- unsigned short port;
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- port = getport(&addr, 100024, 1, IPPROTO_UDP);
+ struct sockaddr *mnt_addr = SAFE_SOCKADDR(&mnt_server->saddr);
+ struct sockaddr *nfs_addr = SAFE_SOCKADDR(&nfs_server->saddr);
- if (port == 0)
- return 0;
- addr.sin_port = htons(port);
+ return nfs_probe_bothports(mnt_addr, sizeof(mnt_server->saddr),
+ &mnt_server->pmap,
+ nfs_addr, sizeof(nfs_server->saddr),
+ &nfs_server->pmap);
+}
- if (clnt_ping(&addr, 100024, 1, IPPROTO_UDP, NULL) <= 0)
- return 0;
+static int nfs_probe_statd(void)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ rpcprog_t program = nfs_getrpcbyname(NSMPROG, nfs_ns_pgmtbl);
- return 1;
+ return nfs_getport_ping(SAFE_SOCKADDR(&addr), sizeof(addr),
+ program, (rpcvers_t)1, IPPROTO_UDP);
}
/**
struct stat stb;
#endif
- if (probe_statd())
+ if (nfs_probe_statd())
return 1;
#ifdef START_STATD
if (stat(START_STATD, &stb) == 0) {
if (S_ISREG(stb.st_mode) && (stb.st_mode & S_IXUSR)) {
- system(START_STATD);
- if (probe_statd())
+ pid_t pid = fork();
+ switch (pid) {
+ case 0: /* child */
+ execl(START_STATD, START_STATD, NULL);
+ exit(1);
+ case -1: /* error */
+ nfs_error(_("%s: fork failed: %s"),
+ progname, strerror(errno));
+ break;
+ default: /* parent */
+ waitpid(pid, NULL,0);
+ break;
+ }
+ if (nfs_probe_statd())
return 1;
}
}
return 0;
}
+/**
+ * nfs_advise_umount - ask the server to remove a share from it's rmtab
+ * @sap: pointer to IP address of server to call
+ * @salen: length of server address
+ * @pmap: partially filled-in mountd RPC service tuple
+ * @argp: directory path of share to "unmount"
+ *
+ * Returns one if the unmount call succeeded; zero if the unmount
+ * failed for any reason; rpccreateerr.cf_stat is set to reflect
+ * the nature of the error.
+ *
+ * We use a fast timeout since this call is advisory only.
+ */
+int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen,
+ const struct pmap *pmap, const dirpath *argp)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *saddr = &address.sa;
+ struct pmap mnt_pmap = *pmap;
+ struct timeval timeout = {
+ .tv_sec = MOUNT_TIMEOUT >> 3,
+ };
+ CLIENT *client;
+ enum clnt_stat res = 0;
+
+ memcpy(saddr, sap, salen);
+ if (nfs_probe_mntport(saddr, salen, &mnt_pmap) == 0) {
+ if (verbose)
+ nfs_error(_("%s: Failed to discover mountd port%s"),
+ progname, clnt_spcreateerror(""));
+ return 0;
+ }
+ nfs_set_port(saddr, mnt_pmap.pm_port);
+
+ client = nfs_get_priv_rpcclient(saddr, salen, mnt_pmap.pm_prot,
+ mnt_pmap.pm_prog, mnt_pmap.pm_vers,
+ &timeout);
+ if (client == NULL) {
+ if (verbose)
+ nfs_error(_("%s: Failed to create RPC client%s"),
+ progname, clnt_spcreateerror(""));
+ return 0;
+ }
+
+ client->cl_auth = nfs_authsys_create();
+ if (client->cl_auth == NULL) {
+ if (verbose)
+ nfs_error(_("%s: Failed to create RPC auth handle"),
+ progname);
+ CLNT_DESTROY(client);
+ return 0;
+ }
+
+ res = CLNT_CALL(client, MOUNTPROC_UMNT,
+ (xdrproc_t)xdr_dirpath, (caddr_t)argp,
+ (xdrproc_t)xdr_void, NULL,
+ timeout);
+ if (res != RPC_SUCCESS) {
+ rpc_createerr.cf_stat = res;
+ CLNT_GETERR(client, &rpc_createerr.cf_error);
+ if (verbose)
+ nfs_error(_("%s: UMNT call failed: %s"),
+ progname, clnt_sperrno(res));
+
+ }
+ auth_destroy(client->cl_auth);
+ CLNT_DESTROY(client);
+
+ if (res != RPC_SUCCESS)
+ return 0;
+ return 1;
+}
+
/**
* nfs_call_umount - ask the server to remove a share from it's rmtab
* @mnt_server: address of RPC MNT program server
*/
int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
{
+ struct sockaddr *sap = SAFE_SOCKADDR(&mnt_server->saddr);
+ socklen_t salen = sizeof(mnt_server->saddr);
+ struct pmap *pmap = &mnt_server->pmap;
CLIENT *clnt;
enum clnt_stat res = 0;
int msock;
- if (!probe_mntport(mnt_server))
+ if (!nfs_probe_mntport(sap, salen, pmap))
return 0;
clnt = mnt_openclnt(mnt_server, &msock);
if (!clnt)
}
if (clnt) {
/* try to mount hostname:dirname */
- clnt->cl_auth = authunix_create_default();
- return clnt;
+ clnt->cl_auth = nfs_authsys_create();
+ if (clnt->cl_auth)
+ return clnt;
+ CLNT_DESTROY(clnt);
}
return NULL;
}
* @prot: target RPC protocol
* @caddr: filled in with our network address
*
- * Sigh... getport() doesn't actually check the version number.
+ * Sigh... GETPORT queries don't actually check the version number.
* In order to make sure that the server actually supports the service
- * we're requesting, we open and RPC client, and fire off a NULL
+ * we're requesting, we open an RPC client, and fire off a NULL
* RPC call.
*
* caddr is the network address that the server will use to call us back.
struct sockaddr_in *caddr)
{
CLIENT *clnt = NULL;
- int sock, stat;
+ int sock, status;
static char clnt_res;
struct sockaddr dissolve;
- rpc_createerr.cf_stat = stat = 0;
+ rpc_createerr.cf_stat = status = 0;
sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE);
if (sock == RPC_ANYSOCK) {
if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) {
return 0;
}
memset(&clnt_res, 0, sizeof(clnt_res));
- stat = clnt_call(clnt, NULLPROC,
+ status = clnt_call(clnt, NULLPROC,
(xdrproc_t)xdr_void, (caddr_t)NULL,
(xdrproc_t)xdr_void, (caddr_t)&clnt_res,
TIMEOUT);
- if (stat) {
+ if (status) {
clnt_geterr(clnt, &rpc_createerr.cf_error);
- rpc_createerr.cf_stat = stat;
+ rpc_createerr.cf_stat = status;
}
clnt_destroy(clnt);
close(sock);
- if (stat == RPC_SUCCESS)
+ if (status == RPC_SUCCESS)
return 1;
else
return 0;
}
+/*
+ * Try a getsockname() on a connected datagram socket.
+ *
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
+ *
+ * A connected datagram socket prevents leaving a socket in TIME_WAIT.
+ * This conserves the ephemeral port number space, helping reduce failed
+ * socket binds during mount storms.
+ */
+static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen,
+ struct sockaddr *buf, socklen_t *buflen)
+{
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ };
+ struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ };
+ int sock, result = 0;
+
+ sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
+ return 0;
+
+ switch (sap->sa_family) {
+ case AF_INET:
+ if (bind(sock, SAFE_SOCKADDR(&sin), sizeof(sin)) < 0)
+ goto out;
+ break;
+ case AF_INET6:
+ if (bind(sock, SAFE_SOCKADDR(&sin6), sizeof(sin6)) < 0)
+ goto out;
+ break;
+ default:
+ errno = EAFNOSUPPORT;
+ goto out;
+ }
+
+ if (connect(sock, sap, salen) < 0)
+ goto out;
+
+ result = !getsockname(sock, buf, buflen);
+
+out:
+ close(sock);
+ return result;
+}
+
+/*
+ * Try to generate an address that prevents the server from calling us.
+ *
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
+ */
+static int nfs_ca_gai(const struct sockaddr *sap,
+ struct sockaddr *buf, socklen_t *buflen)
+{
+ struct addrinfo *gai_results;
+ struct addrinfo gai_hint = {
+ .ai_family = sap->sa_family,
+ .ai_flags = AI_PASSIVE, /* ANYADDR */
+ };
+
+ if (getaddrinfo(NULL, "", &gai_hint, &gai_results))
+ return 0;
+
+ *buflen = gai_results->ai_addrlen;
+ memcpy(buf, gai_results->ai_addr, *buflen);
+
+ freeaddrinfo(gai_results);
+
+ return 1;
+}
+
/**
- * get_client_address - acquire our local network address
- * @saddr: server's address
- * @caddr: filled in with our network address
+ * nfs_callback_address - acquire our local network address
+ * @sap: pointer to address of remote
+ * @sap_len: length of address
+ * @buf: pointer to buffer to be filled in with local network address
+ * @buflen: IN: length of buffer to fill in; OUT: length of filled-in address
*
- * Discover a network address that the server will use to call us back.
+ * Discover a network address that an NFSv4 server can use to call us back.
* On multi-homed clients, this address depends on which NIC we use to
* route requests to the server.
*
- * Use a connected datagram socket so as not to leave a socket in TIME_WAIT.
+ * Returns 1 and fills in @buf if an unambiguous local address is
+ * available; returns 1 and fills in an appropriate ANYADDR address
+ * if a local address isn't available; otherwise, returns zero.
+ */
+int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen,
+ struct sockaddr *buf, socklen_t *buflen)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf;
+
+ if (nfs_ca_sockname(sap, salen, buf, buflen) == 0)
+ if (nfs_ca_gai(sap, buf, buflen) == 0)
+ goto out_failed;
+
+ /*
+ * The server can't use an interface ID that was generated
+ * here on the client, so always clear sin6_scope_id.
+ */
+ if (sin6->sin6_family == AF_INET6)
+ sin6->sin6_scope_id = 0;
+
+ return 1;
+
+out_failed:
+ *buflen = 0;
+ if (verbose)
+ nfs_error(_("%s: failed to construct callback address"),
+ progname);
+ return 0;
+}
+
+/*
+ * "nfsprog" is supported only by the legacy mount command. The
+ * kernel mount client does not support this option.
*
- * Returns one if successful, otherwise zero.
+ * Returns TRUE if @program contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_nfs_program(struct mount_options *options, unsigned long *program)
+{
+ long tmp;
+
+ switch (po_get_numeric(options, "nfsprog", &tmp)) {
+ case PO_NOT_FOUND:
+ break;
+ case PO_FOUND:
+ if (tmp > 0) {
+ *program = tmp;
+ return 1;
+ }
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'nfsprog=' option"),
+ progname);
+ return 0;
+ }
+
+ /*
+ * NFS RPC program wasn't specified. The RPC program
+ * cannot be determined via an rpcbind query.
+ */
+ *program = nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl);
+ return 1;
+}
+
+/*
+ * Returns TRUE if @version contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+int
+nfs_nfs_version(struct mount_options *options, unsigned long *version)
+{
+ long tmp;
+
+ switch (po_rightmost(options, nfs_version_opttbl)) {
+ case 0: /* v2 */
+ *version = 2;
+ return 1;
+ case 1: /* v3 */
+ *version = 3;
+ return 1;
+ case 2: /* v4 */
+ *version = 4;
+ return 1;
+ case 3: /* vers */
+ switch (po_get_numeric(options, "vers", &tmp)) {
+ case PO_FOUND:
+ if (tmp >= 2 && tmp <= 4) {
+ *version = tmp;
+ return 1;
+ }
+ return 0;
+ case PO_NOT_FOUND:
+ nfs_error(_("%s: parsing error on 'vers=' option\n"),
+ progname);
+ return 0;
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'vers=' option"),
+ progname);
+ return 0;
+ }
+ case 4: /* nfsvers */
+ switch (po_get_numeric(options, "nfsvers", &tmp)) {
+ case PO_FOUND:
+ if (tmp >= 2 && tmp <= 4) {
+ *version = tmp;
+ return 1;
+ }
+ return 0;
+ case PO_NOT_FOUND:
+ nfs_error(_("%s: parsing error on 'nfsvers=' option\n"),
+ progname);
+ return 0;
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'nfsvers=' option"),
+ progname);
+ return 0;
+ }
+ }
+
+ /*
+ * NFS version wasn't specified. The pmap version value
+ * will be filled in later by an rpcbind query in this case.
+ */
+ *version = 0;
+ return 1;
+}
+
+/*
+ * Returns TRUE if @protocol contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value. On
+ * error, errno is set.
+ */
+int
+nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
+{
+ sa_family_t family;
+ char *option;
+
+ switch (po_rightmost(options, nfs_transport_opttbl)) {
+ case 0: /* udp */
+ *protocol = IPPROTO_UDP;
+ return 1;
+ case 1: /* tcp */
+ *protocol = IPPROTO_TCP;
+ return 1;
+ case 2: /* rdma */
+ *protocol = NFSPROTO_RDMA;
+ return 1;
+ case 3: /* proto */
+ option = po_get(options, "proto");
+ if (option != NULL) {
+ if (!nfs_get_proto(option, &family, protocol)) {
+ errno = EPROTONOSUPPORT;
+ nfs_error(_("%s: Failed to find '%s' protocol"),
+ progname, option);
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ /*
+ * NFS transport protocol wasn't specified. The pmap
+ * protocol value will be filled in later by an rpcbind
+ * query in this case.
+ */
+ *protocol = 0;
+ return 1;
+}
+
+/*
+ * Returns TRUE if @port contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_nfs_port(struct mount_options *options, unsigned long *port)
+{
+ long tmp;
+
+ switch (po_get_numeric(options, "port", &tmp)) {
+ case PO_NOT_FOUND:
+ break;
+ case PO_FOUND:
+ if (tmp >= 0 && tmp <= 65535) {
+ *port = tmp;
+ return 1;
+ }
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'port=' option"),
+ progname);
+ return 0;
+ }
+
+ /*
+ * NFS service port wasn't specified. The pmap port value
+ * will be filled in later by an rpcbind query in this case.
+ */
+ *port = 0;
+ return 1;
+}
+
+#ifdef IPV6_SUPPORTED
+sa_family_t config_default_family = AF_UNSPEC;
+
+static int
+nfs_verify_family(sa_family_t UNUSED(family))
+{
+ return 1;
+}
+#else /* IPV6_SUPPORTED */
+sa_family_t config_default_family = AF_INET;
+
+static int
+nfs_verify_family(sa_family_t family)
+{
+ if (family != AF_INET)
+ return 0;
+
+ return 1;
+}
+#endif /* IPV6_SUPPORTED */
+
+/*
+ * Returns TRUE and fills in @family if a valid NFS protocol option
+ * is found, or FALSE if the option was specified with an invalid value
+ * or if the protocol family isn't supported. On error, errno is set.
+ */
+int nfs_nfs_proto_family(struct mount_options *options,
+ sa_family_t *family)
+{
+ unsigned long protocol;
+ char *option;
+ sa_family_t tmp_family = config_default_family;
+
+ switch (po_rightmost(options, nfs_transport_opttbl)) {
+ case 0: /* udp */
+ case 1: /* tcp */
+ case 2: /* rdma */
+ /* for compatibility; these are always AF_INET */
+ *family = AF_INET;
+ return 1;
+ case 3: /* proto */
+ option = po_get(options, "proto");
+ if (option != NULL &&
+ !nfs_get_proto(option, &tmp_family, &protocol)) {
+
+ nfs_error(_("%s: Failed to find '%s' protocol"),
+ progname, option);
+ errno = EPROTONOSUPPORT;
+ return 0;
+ }
+ }
+
+ if (!nfs_verify_family(tmp_family))
+ goto out_err;
+ *family = tmp_family;
+ return 1;
+out_err:
+ errno = EAFNOSUPPORT;
+ return 0;
+}
+
+/*
+ * "mountprog" is supported only by the legacy mount command. The
+ * kernel mount client does not support this option.
+ *
+ * Returns TRUE if @program contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_program(struct mount_options *options, unsigned long *program)
+{
+ long tmp;
+
+ switch (po_get_numeric(options, "mountprog", &tmp)) {
+ case PO_NOT_FOUND:
+ break;
+ case PO_FOUND:
+ if (tmp > 0) {
+ *program = tmp;
+ return 1;
+ }
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'mountprog=' option"),
+ progname);
+ return 0;
+ }
+
+ /*
+ * MNT RPC program wasn't specified. The RPC program
+ * cannot be determined via an rpcbind query.
+ */
+ *program = nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl);
+ return 1;
+}
+
+/*
+ * Returns TRUE if @version contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_version(struct mount_options *options, unsigned long *version)
+{
+ long tmp;
+
+ switch (po_get_numeric(options, "mountvers", &tmp)) {
+ case PO_NOT_FOUND:
+ break;
+ case PO_FOUND:
+ if (tmp >= 1 && tmp <= 4) {
+ *version = tmp;
+ return 1;
+ }
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'mountvers=' option"),
+ progname);
+ return 0;
+ }
+
+ /*
+ * MNT version wasn't specified. The pmap version value
+ * will be filled in later by an rpcbind query in this case.
+ */
+ *version = 0;
+ return 1;
+}
+
+/*
+ * Returns TRUE if @protocol contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value. On
+ * error, errno is set.
*/
-int get_client_address(struct sockaddr_in *saddr, struct sockaddr_in *caddr)
+static int
+nfs_mount_protocol(struct mount_options *options, unsigned long *protocol)
{
- socklen_t len = sizeof(*caddr);
- int socket, err;
+ sa_family_t family;
+ char *option;
+
+ option = po_get(options, "mountproto");
+ if (option != NULL) {
+ if (!nfs_get_proto(option, &family, protocol)) {
+ errno = EPROTONOSUPPORT;
+ nfs_error(_("%s: Failed to find '%s' protocol"),
+ progname, option);
+ return 0;
+ }
+ return 1;
+ }
- socket = get_socket(saddr, IPPROTO_UDP, CONNECT_TIMEOUT, FALSE, TRUE);
- if (socket == RPC_ANYSOCK)
+ /*
+ * MNT transport protocol wasn't specified. If the NFS
+ * transport protocol was specified, use that; otherwise
+ * set @protocol to zero. The pmap protocol value will
+ * be filled in later by an rpcbind query in this case.
+ */
+ if (!nfs_nfs_protocol(options, protocol))
return 0;
+ if (*protocol == NFSPROTO_RDMA)
+ *protocol = IPPROTO_TCP;
+ return 1;
+}
- err = getsockname(socket, caddr, &len);
- close(socket);
+/*
+ * Returns TRUE if @port contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_port(struct mount_options *options, unsigned long *port)
+{
+ long tmp;
- if (err && verbose) {
- nfs_error(_("%s: getsockname failed: %s"),
- progname, strerror(errno));
+ switch (po_get_numeric(options, "mountport", &tmp)) {
+ case PO_NOT_FOUND:
+ break;
+ case PO_FOUND:
+ if (tmp >= 0 && tmp <= 65535) {
+ *port = tmp;
+ return 1;
+ }
+ case PO_BAD_VALUE:
+ nfs_error(_("%s: invalid value for 'mountport=' option"),
+ progname);
return 0;
}
+
+ /*
+ * MNT service port wasn't specified. The pmap port value
+ * will be filled in later by an rpcbind query in this case.
+ */
+ *port = 0;
+ return 1;
+}
+
+/*
+ * Returns TRUE and fills in @family if a valid MNT protocol option
+ * is found, or FALSE if the option was specified with an invalid value
+ * or if the protocol family isn't supported. On error, errno is set.
+ */
+int nfs_mount_proto_family(struct mount_options *options,
+ sa_family_t *family)
+{
+ unsigned long protocol;
+ char *option;
+ sa_family_t tmp_family = config_default_family;
+
+ option = po_get(options, "mountproto");
+ if (option != NULL) {
+ if (!nfs_get_proto(option, &tmp_family, &protocol)) {
+ nfs_error(_("%s: Failed to find '%s' protocol"),
+ progname, option);
+ errno = EPROTONOSUPPORT;
+ goto out_err;
+ }
+ if (!nfs_verify_family(tmp_family))
+ goto out_err;
+ *family = tmp_family;
+ return 1;
+ }
+
+ /*
+ * MNT transport protocol wasn't specified. If the NFS
+ * transport protocol was specified, derive the family
+ * from that; otherwise, return the default family for
+ * NFS.
+ */
+ return nfs_nfs_proto_family(options, family);
+out_err:
+ errno = EAFNOSUPPORT;
+ return 0;
+}
+
+/**
+ * nfs_options2pmap - set up pmap structs based on mount options
+ * @options: pointer to mount options
+ * @nfs_pmap: OUT: pointer to pmap arguments for NFS server
+ * @mnt_pmap: OUT: pointer to pmap arguments for mountd server
+ *
+ * Returns TRUE if the pmap options specified in @options have valid
+ * values; otherwise FALSE is returned.
+ */
+int nfs_options2pmap(struct mount_options *options,
+ struct pmap *nfs_pmap, struct pmap *mnt_pmap)
+{
+ if (!nfs_nfs_program(options, &nfs_pmap->pm_prog))
+ return 0;
+ if (!nfs_nfs_version(options, &nfs_pmap->pm_vers))
+ return 0;
+ if (!nfs_nfs_protocol(options, &nfs_pmap->pm_prot))
+ return 0;
+ if (!nfs_nfs_port(options, &nfs_pmap->pm_port))
+ return 0;
+
+ if (!nfs_mount_program(options, &mnt_pmap->pm_prog))
+ return 0;
+ if (!nfs_mount_version(options, &mnt_pmap->pm_vers))
+ return 0;
+ if (!nfs_mount_protocol(options, &mnt_pmap->pm_prot))
+ return 0;
+ if (!nfs_mount_port(options, &mnt_pmap->pm_port))
+ return 0;
+
return 1;
}
+
+/*
+ * Discover mount server's hostname/address by examining mount options
+ *
+ * Returns a pointer to a string that the caller must free, on
+ * success; otherwise NULL is returned.
+ */
+static char *nfs_umount_hostname(struct mount_options *options,
+ char *hostname)
+{
+ char *option;
+
+ option = po_get(options, "mountaddr");
+ if (option)
+ goto out;
+ option = po_get(options, "mounthost");
+ if (option)
+ goto out;
+ option = po_get(options, "addr");
+ if (option)
+ goto out;
+
+ return hostname;
+
+out:
+ free(hostname);
+ return strdup(option);
+}
+
+
+/*
+ * Returns EX_SUCCESS if mount options and device name have been
+ * parsed successfully; otherwise EX_FAIL.
+ */
+int nfs_umount_do_umnt(struct mount_options *options,
+ char **hostname, char **dirname)
+{
+ union nfs_sockaddr address;
+ struct sockaddr *sap = &address.sa;
+ socklen_t salen = sizeof(address);
+ struct pmap nfs_pmap, mnt_pmap;
+ sa_family_t family;
+
+ if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap))
+ return EX_FAIL;
+
+ /* Skip UMNT call for vers=4 mounts */
+ if (nfs_pmap.pm_vers == 4)
+ return EX_SUCCESS;
+
+ *hostname = nfs_umount_hostname(options, *hostname);
+ if (!*hostname) {
+ nfs_error(_("%s: out of memory"), progname);
+ return EX_FAIL;
+ }
+
+ if (!nfs_mount_proto_family(options, &family))
+ return 0;
+ if (!nfs_lookup(*hostname, family, sap, &salen))
+ /* nfs_lookup reports any errors */
+ return EX_FAIL;
+
+ if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0)
+ /* nfs_advise_umount reports any errors */
+ return EX_FAIL;
+
+ return EX_SUCCESS;
+}