X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fmount%2Fnetwork.c;h=73d64a2b8782221b601f132cdb18aac450efc4f0;hp=d262e940091e195449d9d0aea05473a556c2b9fe;hb=5f7313a10b63da796d5cd79c01db1e097d4a6bf1;hpb=533e5a05a172306462ac6c11d222a18fe562ec6d diff --git a/utils/mount/network.c b/utils/mount/network.c index d262e94..73d64a2 100644 --- a/utils/mount/network.c +++ b/utils/mount/network.c @@ -48,6 +48,7 @@ #include "nfs_mount.h" #include "mount_constants.h" #include "nfsrpc.h" +#include "parse_opt.h" #include "network.h" #define PMAP_TIMEOUT (10) @@ -67,6 +68,33 @@ static const char *nfs_ns_pgmtbl[] = { 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", + "proto", + NULL, +}; + +static const char *nfs_version_opttbl[] = { + "v2", + "v3", + "vers", + "nfsvers", + NULL, +}; + static const unsigned long nfs_to_mnt[] = { 0, 0, @@ -157,39 +185,32 @@ static void nfs_set_port(struct sockaddr *sap, const unsigned short port) } } -#ifdef HAVE_DECL_AI_ADDRCONFIG -/** - * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address - * @hostname: pointer to C string containing DNS hostname to resolve - * @af_hint: hint to restrict resolution to one address family - * @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) +static 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, +#ifdef HAVE_DECL_AI_ADDRCONFIG .ai_flags = AI_ADDRCONFIG, +#endif /* HAVE_DECL_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; } @@ -212,61 +233,25 @@ int nfs_name_to_address(const char *hostname, freeaddrinfo(gai_results); return ret; } -#else /* HAVE_DECL_AI_ADDRCONFIG */ + /** - * nfs_name_to_address - resolve hostname to an IPv4 socket address + * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address * @hostname: pointer to C string containing DNS hostname to resolve - * @af_hint: hint to restrict resolution to one address family * @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. - * - * Some older getaddrinfo(3) implementations don't support - * AI_ADDRCONFIG or AI_V4MAPPED properly. For those cases, a DNS - * resolver based on the traditional gethostbyname(3) is provided. */ int nfs_name_to_address(const char *hostname, - const sa_family_t af_hint, struct sockaddr *sap, socklen_t *salen) { - struct sockaddr_in *sin = (struct sockaddr_in *)sap; - socklen_t len = *salen; - struct hostent *hp; - - *salen = 0; - - if (af_hint != AF_INET) { - nfs_error(_("%s: address family not supported by DNS resolver\n"), - progname, hostname); - return 0; - } - - sin->sin_family = AF_INET; - if (inet_aton(hostname, &sin->sin_addr)) { - *salen = sizeof(*sin); - return 1; - } - - hp = gethostbyname(hostname); - if (hp == NULL) { - nfs_error(_("%s: DNS resolution failed for %s: %s"), - progname, hostname, hstrerror(h_errno)); - return 0; - } - - if (hp->h_length > len) { - nfs_error(_("%s: DNS resolution results too long for buffer\n"), - progname); - return 0; - } - - memcpy(&sin->sin_addr, hp->h_addr, hp->h_length); - *salen = hp->h_length; - return 1; +#ifdef IPV6_SUPPORTED + return nfs_lookup(hostname, AF_UNSPEC, sap, salen); +#else /* !IPV6_SUPPORTED */ + return nfs_lookup(hostname, AF_INET, sap, salen); +#endif /* !IPV6_SUPPORTED */ } -#endif /* HAVE_DECL_AI_ADDRCONFIG */ /** * nfs_gethostbyname - resolve a hostname to an IPv4 address @@ -279,8 +264,7 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) { 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); } /** @@ -518,12 +502,28 @@ static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen, strcat(buf, "unknown host"); } - fprintf(stderr, _("%s: trying %s prog %ld vers %ld prot %s port %d\n"), - progname, buf, program, version, + 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 @@ -553,9 +553,11 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, memcpy(saddr, sap, salen); p_prot = prot ? &prot : protos; p_vers = vers ? &vers : versions; - rpc_createerr.cf_stat = 0; for (;;) { + 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) { @@ -565,28 +567,31 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, if (nfs_rpc_ping(saddr, salen, prog, *p_vers, *p_prot, NULL)) goto out_ok; - } + } 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) - goto out_bad; + break; if (vers || !*++p_vers) break; } -out_bad: + nfs_pp_debug2("failed"); return 0; out_ok: @@ -596,7 +601,7 @@ 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; } @@ -809,6 +814,68 @@ int start_statd(void) 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) +{ + struct sockaddr_storage address; + struct sockaddr *saddr = (struct sockaddr *)&address; + 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 = authunix_create_default(); + + res = CLNT_CALL(client, MOUNTPROC_UMNT, + (xdrproc_t)xdr_dirpath, (caddr_t)argp, + (xdrproc_t)xdr_void, NULL, + timeout); + if (verbose && res != RPC_SUCCESS) + 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 @@ -1111,3 +1178,188 @@ out_failed: return 0; } + +/* + * "nfsprog" is only supported by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns the value set by the nfsprog= option, the value of + * the RPC NFS program specified in /etc/rpc, or a baked-in + * default program number, if all fails. + */ +static rpcprog_t nfs_nfs_program(struct mount_options *options) +{ + long tmp; + + if (po_get_numeric(options, "nfsprog", &tmp) == PO_FOUND) + if (tmp >= 0) + return tmp; + return nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl); +} + + +/* + * Returns the RPC version number specified by the given mount + * options for the NFS service, or zero if all fails. + */ +static rpcvers_t nfs_nfs_version(struct mount_options *options) +{ + long tmp; + + switch (po_rightmost(options, nfs_version_opttbl)) { + case 0: /* v2 */ + return 2; + case 1: /* v3 */ + return 3; + case 2: /* vers */ + if (po_get_numeric(options, "vers", &tmp) == PO_FOUND) + if (tmp >= 2 && tmp <= 3) + return tmp; + break; + case 3: /* nfsvers */ + if (po_get_numeric(options, "nfsvers", &tmp) == PO_FOUND) + if (tmp >= 2 && tmp <= 3) + return tmp; + break; + } + + return 0; +} + +/* + * Returns the NFS transport protocol specified by the given mount options + * + * Returns the IPPROTO_ value specified by the given mount options, or + * IPPROTO_UDP if all fails. + */ +static unsigned short nfs_nfs_protocol(struct mount_options *options) +{ + char *option; + + switch (po_rightmost(options, nfs_transport_opttbl)) { + case 1: /* tcp */ + return IPPROTO_TCP; + case 2: /* proto */ + option = po_get(options, "proto"); + if (option) { + if (strcmp(option, "tcp") == 0) + return IPPROTO_TCP; + if (strcmp(option, "udp") == 0) + return IPPROTO_UDP; + } + } + + return IPPROTO_UDP; +} + +/* + * Returns the NFS server's port number specified by the given + * mount options, or zero if all fails. Zero results in a portmap + * query to discover the server's mountd service port. + * + * port=0 will guarantee an rpcbind request precedes the first + * NFS RPC so the client can determine the server's port number. + */ +static unsigned short nfs_nfs_port(struct mount_options *options) +{ + long tmp; + + if (po_get_numeric(options, "port", &tmp) == PO_FOUND) + if (tmp >= 0 && tmp <= 65535) + return tmp; + return 0; +} + +/* + * "mountprog" is only supported by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns the value set by the mountprog= option, the value of + * the RPC mount program specified in /etc/rpc, or a baked-in + * default program number, if all fails. + */ +static rpcprog_t nfs_mount_program(struct mount_options *options) +{ + long tmp; + + if (po_get_numeric(options, "mountprog", &tmp) == PO_FOUND) + if (tmp >= 0) + return tmp; + return nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl); +} + +/* + * Returns the RPC version number specified by the given mount options, + * or the version "3" if all fails. + */ +static rpcvers_t nfs_mount_version(struct mount_options *options) +{ + long tmp; + + if (po_get_numeric(options, "mountvers", &tmp) == PO_FOUND) + if (tmp >= 1 && tmp <= 4) + return tmp; + + return nfsvers_to_mnt(nfs_nfs_version(options)); +} + +/* + * Returns the transport protocol to use for the mount service + * + * Returns the IPPROTO_ value specified by the mountproto option, or + * if that doesn't exist, the IPPROTO_ value specified for NFS + * itself. + */ +static unsigned short nfs_mount_protocol(struct mount_options *options) +{ + char *option; + + option = po_get(options, "mountproto"); + if (option) { + if (strcmp(option, "tcp") == 0) + return IPPROTO_TCP; + if (strcmp(option, "udp") == 0) + return IPPROTO_UDP; + } + + return nfs_nfs_protocol(options); +} + +/* + * Returns the mountd server's port number specified by the given + * mount options, or zero if all fails. Zero results in a portmap + * query to discover the server's mountd service port. + * + * port=0 will guarantee an rpcbind request precedes the mount + * RPC so the client can determine the server's port number. + */ +static unsigned short nfs_mount_port(struct mount_options *options) +{ + long tmp; + + if (po_get_numeric(options, "mountport", &tmp) == PO_FOUND) + if (tmp >= 0 && tmp <= 65535) + return tmp; + 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 + * + */ +void nfs_options2pmap(struct mount_options *options, + struct pmap *nfs_pmap, struct pmap *mnt_pmap) +{ + nfs_pmap->pm_prog = nfs_nfs_program(options); + nfs_pmap->pm_vers = nfs_nfs_version(options); + nfs_pmap->pm_prot = nfs_nfs_protocol(options); + nfs_pmap->pm_port = nfs_nfs_port(options); + + mnt_pmap->pm_prog = nfs_mount_program(options); + mnt_pmap->pm_vers = nfs_mount_version(options); + mnt_pmap->pm_prot = nfs_mount_protocol(options); + mnt_pmap->pm_port = nfs_mount_port(options); +}