#include <rpc/pmap_clnt.h>
#include <sys/socket.h>
-#include "conn.h"
#include "xcommon.h"
#include "mount.h"
#include "nls.h"
-#include "nfsumount.h"
#include "nfs_mount.h"
#include "mount_constants.h"
#include "network.h"
#define NFS_PORT 2049
#endif
+#define PMAP_TIMEOUT (10)
+#define CONNECT_TIMEOUT (20)
+#define MOUNT_TIMEOUT (30)
+
+#if SIZEOF_SOCKLEN_T - 0 == 0
+#define socklen_t unsigned int
+#endif
+
extern int nfs_mount_data_version;
extern char *progname;
extern int verbose;
+static const unsigned long nfs_to_mnt[] = {
+ 0,
+ 0,
+ 1,
+ 3,
+};
+
+static const unsigned long mnt_to_nfs[] = {
+ 0,
+ 2,
+ 2,
+ 3,
+};
+
+/*
+ * Map an NFS version into the corresponding Mountd version
+ */
+unsigned long nfsvers_to_mnt(const unsigned long vers)
+{
+ if (vers <= 3)
+ return nfs_to_mnt[vers];
+ return 0;
+}
+
+/*
+ * Map a Mountd version into the corresponding NFS version
+ */
+static unsigned long mntvers_to_nfs(const unsigned long vers)
+{
+ if (vers <= 3)
+ return mnt_to_nfs[vers];
+ return 0;
+}
+
static const unsigned int probe_udp_only[] = {
IPPROTO_UDP,
0,
}
/*
- * getport() is very similar to pmap_getport() with
- * the exception this version uses a non-reserve ports
- * instead of reserve ports since reserve ports
- * are not needed for pmap requests.
+ * Attempt to connect a socket, but time out after "timeout" seconds.
+ *
+ * On error return, caller closes the socket.
*/
-unsigned short getport(struct sockaddr_in *saddr, unsigned long prog,
- unsigned long vers, unsigned int prot)
+static int connect_to(int fd, struct sockaddr *addr,
+ socklen_t addrlen, int timeout)
+{
+ int ret, saved;
+ fd_set rset, wset;
+ struct timeval tv = {
+ .tv_sec = timeout,
+ };
+
+ saved = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, saved | O_NONBLOCK);
+
+ ret = connect(fd, addr, addrlen);
+ if (ret < 0 && errno != EINPROGRESS)
+ return -1;
+ if (ret == 0)
+ goto out;
+
+ FD_ZERO(&rset);
+ FD_SET(fd, &rset);
+ wset = rset;
+ ret = select(fd + 1, &rset, &wset, NULL, &tv);
+ if (ret == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) {
+ int error;
+ socklen_t len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+ return -1;
+ if (error) {
+ errno = error;
+ return -1;
+ }
+ } else
+ return -1;
+
+out:
+ fcntl(fd, F_SETFL, saved);
+ return 0;
+}
+
+/*
+ * Create a socket that is locally bound to a reserved or non-reserved port.
+ *
+ * The caller should check rpc_createerr to determine the cause of any error.
+ */
+static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot,
+ unsigned int timeout, int resvp, int conn)
+{
+ int so, cc, type;
+ struct sockaddr_in laddr;
+ socklen_t namelen = sizeof(laddr);
+
+ type = (p_prot == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM);
+ if ((so = socket (AF_INET, type, p_prot)) < 0)
+ goto err_socket;
+
+ laddr.sin_family = AF_INET;
+ laddr.sin_port = 0;
+ laddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (resvp) {
+ if (bindresvport(so, &laddr) < 0)
+ goto err_bindresvport;
+ } else {
+ cc = bind(so, (struct 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,
+ timeout);
+ if (cc < 0)
+ goto err_connect;
+ }
+ return so;
+
+err_socket:
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = errno;
+ if (verbose) {
+ nfs_error(_("%s: Unable to create %s socket: errno %d (%s)\n"),
+ progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+ errno, strerror(errno));
+ }
+ return RPC_ANYSOCK;
+
+err_bindresvport:
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = errno;
+ if (verbose) {
+ nfs_error(_("%s: Unable to bindresvport %s socket: errno %d"
+ " (%s)\n"),
+ progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+ errno, strerror(errno));
+ }
+ close(so);
+ return RPC_ANYSOCK;
+
+err_bind:
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = errno;
+ if (verbose) {
+ nfs_error(_("%s: Unable to bind to %s socket: errno %d (%s)\n"),
+ progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+ errno, strerror(errno));
+ }
+ close(so);
+ return RPC_ANYSOCK;
+
+err_connect:
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = errno;
+ if (verbose) {
+ nfs_error(_("%s: Unable to connect to %s:%d, errno %d (%s)\n"),
+ progname, inet_ntoa(saddr->sin_addr),
+ ntohs(saddr->sin_port), errno, strerror(errno));
+ }
+ close(so);
+ 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)
{
unsigned short port = 0;
int socket;
CLIENT *clnt = NULL;
- struct pmap parms;
enum clnt_stat stat;
- saddr->sin_port = htons (PMAPPORT);
- socket = get_socket(saddr, prot, FALSE, FALSE);
+ saddr->sin_port = htons(PMAPPORT);
+
+ socket = get_socket(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;
+ }
- switch (prot) {
+ switch (proto) {
case IPPROTO_UDP:
clnt = clntudp_bufcreate(saddr,
- PMAPPROG, PMAPVERS, TIMEOUT, &socket,
- UDPMSGSIZE, UDPMSGSIZE);
+ PMAPPROG, PMAPVERS,
+ RETRY_TIMEOUT, &socket,
+ RPCSMALLMSGSIZE,
+ RPCSMALLMSGSIZE);
break;
case IPPROTO_TCP:
- clnt = clnttcp_create(saddr,
- PMAPPROG, PMAPVERS, &socket, 50, 500);
+ clnt = clnttcp_create(saddr, PMAPPROG, PMAPVERS, &socket,
+ RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
break;
}
if (clnt != NULL) {
- parms.pm_prog = prog;
- parms.pm_vers = vers;
- parms.pm_prot = prot;
- parms.pm_port = 0; /* not needed or used */
-
- stat = clnt_call(clnt, PMAPPROC_GETPORT, (xdrproc_t)xdr_pmap,
- (caddr_t)&parms, (xdrproc_t)xdr_u_short, (caddr_t)&port, TIMEOUT);
+ 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;
else if (port == 0)
rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
}
- if (socket != 1)
- close(socket);
+ close(socket);
return port;
}
p_vers = vers ? &vers : versions;
rpc_createerr.cf_stat = 0;
for (;;) {
- saddr->sin_port = htons(PMAPPORT);
p_port = getport(saddr, prog, *p_vers, *p_prot);
if (p_port) {
if (!port || port == p_port) {
return 1;
}
-int probe_nfsport(clnt_addr_t *nfs_server)
+static int probe_nfsport(clnt_addr_t *nfs_server)
{
struct pmap *pmap = &nfs_server->pmap;
return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
}
-int probe_mntport(clnt_addr_t *mnt_server)
+static int probe_mntport(clnt_addr_t *mnt_server)
{
struct pmap *pmap = &mnt_server->pmap;
return 0;
}
+
+/*
+ * nfs_call_umount - ask the server to remove a share from it's rmtab
+ * @mnt_server: address of RPC MNT program server
+ * @argp: directory path of share to "unmount"
+ *
+ * Returns one if the unmount call succeeded; zero if the unmount
+ * failed for any reason.
+ *
+ * Note that a side effect of calling this function is that rpccreateerr
+ * is set.
+ */
+int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
+{
+ CLIENT *clnt;
+ enum clnt_stat res = 0;
+ int msock;
+
+ switch (mnt_server->pmap.pm_vers) {
+ case 3:
+ case 2:
+ case 1:
+ if (!probe_mntport(mnt_server))
+ return 0;
+ clnt = mnt_openclnt(mnt_server, &msock);
+ if (!clnt)
+ return 0;
+ res = clnt_call(clnt, MOUNTPROC_UMNT,
+ (xdrproc_t)xdr_dirpath, (caddr_t)argp,
+ (xdrproc_t)xdr_void, NULL,
+ TIMEOUT);
+ mnt_closeclnt(clnt, msock);
+ if (res == RPC_SUCCESS)
+ return 1;
+ break;
+ default:
+ res = RPC_SUCCESS;
+ break;
+ }
+
+ if (res == RPC_SUCCESS)
+ return 1;
+ return 0;
+}
+
+CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock)
+{
+ struct sockaddr_in *mnt_saddr = &mnt_server->saddr;
+ struct pmap *mnt_pmap = &mnt_server->pmap;
+ CLIENT *clnt = NULL;
+
+ mnt_saddr->sin_port = htons((u_short)mnt_pmap->pm_port);
+ *msock = get_socket(mnt_saddr, mnt_pmap->pm_prot, MOUNT_TIMEOUT,
+ TRUE, FALSE);
+ if (*msock == RPC_ANYSOCK) {
+ if (rpc_createerr.cf_error.re_errno == EADDRINUSE)
+ /*
+ * Probably in-use by a TIME_WAIT connection,
+ * It is worth waiting a while and trying again.
+ */
+ rpc_createerr.cf_stat = RPC_TIMEDOUT;
+ return NULL;
+ }
+
+ switch (mnt_pmap->pm_prot) {
+ case IPPROTO_UDP:
+ clnt = clntudp_bufcreate(mnt_saddr,
+ mnt_pmap->pm_prog, mnt_pmap->pm_vers,
+ RETRY_TIMEOUT, msock,
+ MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
+ break;
+ case IPPROTO_TCP:
+ clnt = clnttcp_create(mnt_saddr,
+ mnt_pmap->pm_prog, mnt_pmap->pm_vers,
+ msock,
+ MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
+ break;
+ }
+ if (clnt) {
+ /* try to mount hostname:dirname */
+ clnt->cl_auth = authunix_create_default();
+ return clnt;
+ }
+ return NULL;
+}
+
+void mnt_closeclnt(CLIENT *clnt, int msock)
+{
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ close(msock);
+}
+
+/*
+ * Sigh... getport() doesn'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
+ * RPC call.
+ */
+int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog,
+ const unsigned long vers, const unsigned int prot,
+ struct sockaddr_in *caddr)
+{
+ CLIENT *clnt = NULL;
+ int sock, stat;
+ static char clnt_res;
+ struct sockaddr dissolve;
+
+ rpc_createerr.cf_stat = stat = 0;
+ sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE);
+ if (sock == RPC_ANYSOCK) {
+ if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) {
+ /*
+ * TCP timeout. Bubble up the error to see
+ * how it should be handled.
+ */
+ rpc_createerr.cf_stat = RPC_TIMEDOUT;
+ }
+ return 0;
+ }
+
+ if (caddr) {
+ /* Get the address of our end of this connection */
+ socklen_t len = sizeof(*caddr);
+ if (getsockname(sock, caddr, &len) != 0)
+ caddr->sin_family = 0;
+ }
+
+ switch(prot) {
+ case IPPROTO_UDP:
+ /* The socket is connected (so we could getsockname successfully),
+ * but some servers on multi-homed hosts reply from
+ * the wrong address, so if we stay connected, we lose the reply.
+ */
+ dissolve.sa_family = AF_UNSPEC;
+ connect(sock, &dissolve, sizeof(dissolve));
+
+ clnt = clntudp_bufcreate(saddr, prog, vers,
+ RETRY_TIMEOUT, &sock,
+ RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+ break;
+ case IPPROTO_TCP:
+ clnt = clnttcp_create(saddr, prog, vers, &sock,
+ RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+ break;
+ }
+ if (!clnt) {
+ close(sock);
+ return 0;
+ }
+ memset(&clnt_res, 0, sizeof(clnt_res));
+ stat = clnt_call(clnt, NULLPROC,
+ (xdrproc_t)xdr_void, (caddr_t)NULL,
+ (xdrproc_t)xdr_void, (caddr_t)&clnt_res,
+ TIMEOUT);
+ if (stat) {
+ clnt_geterr(clnt, &rpc_createerr.cf_error);
+ rpc_createerr.cf_stat = stat;
+ }
+ clnt_destroy(clnt);
+ close(sock);
+
+ if (stat == RPC_SUCCESS)
+ return 1;
+ else
+ return 0;
+}