blob: 4a2fab7dfabf326f268b31952275583a23004d05 [file] [log] [blame]
/*
* nfs4mount.c -- Linux NFS mount
* Copyright (C) 2002 Trond Myklebust <trond.myklebust@fys.uio.no>
*
* 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, 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.
*
* Note: this file based on the original nfsmount.c
*
* 2006-06-06 Amit Gud <agud@redhat.com>
* - Moved to nfs-utils/utils/mount from util-linux/mount.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rpc/auth.h>
#include <rpc/rpc.h>
#ifdef HAVE_RPCSVC_NFS_PROT_H
#include <rpcsvc/nfs_prot.h>
#else
#include <linux/nfs.h>
#define nfsstat nfs_stat
#endif
#include "pseudoflavors.h"
#include "nls.h"
#include "xcommon.h"
#include "mount.h"
#include "mount_constants.h"
#include "nfs4_mount.h"
#include "nfs_mount.h"
#include "error.h"
#include "network.h"
#if defined(VAR_LOCK_DIR)
#define DEFAULT_DIR VAR_LOCK_DIR
#else
#define DEFAULT_DIR "/var/lock/subsys"
#endif
extern char *progname;
extern int verbose;
extern int sloppy;
char *IDMAPLCK = DEFAULT_DIR "/rpcidmapd";
#define idmapd_check() do { \
if (access(IDMAPLCK, F_OK)) { \
printf(_("Warning: rpc.idmapd appears not to be running.\n" \
" All uids will be mapped to the nobody uid.\n")); \
} \
} while(0);
char *GSSDLCK = DEFAULT_DIR "/rpcgssd";
#define gssd_check() do { \
if (access(GSSDLCK, F_OK)) { \
printf(_("Warning: rpc.gssd appears not to be running.\n")); \
} \
} while(0);
#ifndef NFS_PORT
#define NFS_PORT 2049
#endif
#define MAX_USER_FLAVOUR 16
static int parse_sec(char *sec, int *pseudoflavour)
{
int i, num_flavour = 0;
for (sec = strtok(sec, ":"); sec; sec = strtok(NULL, ":")) {
if (num_flavour >= MAX_USER_FLAVOUR) {
nfs_error(_("%s: maximum number of security flavors "
"exceeded"), progname);
return 0;
}
for (i = 0; i < flav_map_size; i++) {
if (strcmp(sec, flav_map[i].flavour) == 0) {
pseudoflavour[num_flavour++] = flav_map[i].fnum;
break;
}
}
if (i == flav_map_size) {
nfs_error(_("%s: unknown security type %s\n"),
progname, sec);
return 0;
}
}
if (!num_flavour)
nfs_error(_("%s: no security flavors passed to sec= option"),
progname);
return num_flavour;
}
static int parse_devname(char *hostdir, char **hostname, char **dirname)
{
char *s;
if (!(s = strchr(hostdir, ':'))) {
nfs_error(_("%s: directory to mount not in host:dir format"),
progname);
return -1;
}
*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, ','))) {
*s = '\0';
nfs_error(_("%s: warning: multiple hostnames not supported"),
progname);
}
return 0;
}
static int fill_ipv4_sockaddr(const char *hostname, struct sockaddr_in *addr)
{
struct hostent *hp;
addr->sin_family = AF_INET;
if (inet_aton(hostname, &addr->sin_addr))
return 0;
if ((hp = gethostbyname(hostname)) == NULL) {
nfs_error(_("%s: can't get address for %s\n"),
progname, hostname);
return -1;
}
if (hp->h_length > sizeof(struct in_addr)) {
nfs_error(_("%s: got bad hp->h_length"), progname);
hp->h_length = sizeof(struct in_addr);
}
memcpy(&addr->sin_addr, hp->h_addr, hp->h_length);
return 0;
}
static int get_my_ipv4addr(char *ip_addr, int len)
{
char myname[1024];
struct sockaddr_in myaddr;
if (gethostname(myname, sizeof(myname))) {
nfs_error(_("%s: can't determine client address\n"),
progname);
return -1;
}
if (fill_ipv4_sockaddr(myname, &myaddr))
return -1;
snprintf(ip_addr, len, "%s", inet_ntoa(myaddr.sin_addr));
ip_addr[len-1] = '\0';
return 0;
}
int nfs4mount(const char *spec, const char *node, int flags,
char **extra_opts, int fake, int running_bg)
{
static struct nfs4_mount_data data;
static char hostdir[1024];
static char ip_addr[16] = "127.0.0.1";
static struct sockaddr_in server_addr, client_addr;
static int pseudoflavour[MAX_USER_FLAVOUR];
int num_flavour = 0;
int ip_addr_in_opts = 0;
char *hostname, *dirname, *old_opts;
char new_opts[1024];
char *opt, *opteq;
char *s;
int val;
int bg, soft, intr;
int nocto, noac, unshared;
int retry;
int retval = EX_FAIL;
time_t timeout, t;
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;
if (fill_ipv4_sockaddr(hostname, &server_addr))
goto fail;
if (get_my_ipv4addr(ip_addr, sizeof(ip_addr)))
goto fail;
/* add IP address to mtab options for use when unmounting */
s = inet_ntoa(server_addr.sin_addr);
old_opts = *extra_opts;
if (!old_opts)
old_opts = "";
if (strlen(old_opts) + strlen(s) + 10 >= sizeof(new_opts)) {
nfs_error(_("%s: excessively long option argument\n"),
progname);
goto fail;
}
if (running_bg)
strncpy(new_opts, old_opts, sizeof(new_opts));
else
snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s",
old_opts, *old_opts ? "," : "", s);
*extra_opts = xstrdup(new_opts);
/* Set default options.
* rsize/wsize and timeo are left 0 in order to
* let the kernel decide.
*/
memset(&data, 0, sizeof(data));
data.retrans = 3;
data.acregmin = 3;
data.acregmax = 60;
data.acdirmin = 30;
data.acdirmax = 60;
data.proto = IPPROTO_TCP;
bg = 0;
soft = 0;
intr = NFS4_MOUNT_INTR;
nocto = 0;
noac = 0;
unshared = 0;
retry = -1;
/*
* NFSv4 specifies that the default port should be 2049
*/
server_addr.sin_port = htons(NFS_PORT);
/* parse options */
for (opt = strtok(old_opts, ","); opt; opt = strtok(NULL, ",")) {
if ((opteq = strchr(opt, '='))) {
val = atoi(opteq + 1);
*opteq = '\0';
if (!strcmp(opt, "rsize"))
data.rsize = val;
else if (!strcmp(opt, "wsize"))
data.wsize = val;
else if (!strcmp(opt, "timeo"))
data.timeo = val;
else if (!strcmp(opt, "retrans"))
data.retrans = val;
else if (!strcmp(opt, "acregmin"))
data.acregmin = val;
else if (!strcmp(opt, "acregmax"))
data.acregmax = val;
else if (!strcmp(opt, "acdirmin"))
data.acdirmin = val;
else if (!strcmp(opt, "acdirmax"))
data.acdirmax = val;
else if (!strcmp(opt, "actimeo")) {
data.acregmin = val;
data.acregmax = val;
data.acdirmin = val;
data.acdirmax = val;
}
else if (!strcmp(opt, "retry"))
retry = val;
else if (!strcmp(opt, "port"))
server_addr.sin_port = htons(val);
else if (!strcmp(opt, "proto")) {
if (!strncmp(opteq+1, "tcp", 3))
data.proto = IPPROTO_TCP;
else if (!strncmp(opteq+1, "udp", 3))
data.proto = IPPROTO_UDP;
else
printf(_("Warning: Unrecognized proto= option.\n"));
} else if (!strcmp(opt, "clientaddr")) {
if (strlen(opteq+1) >= sizeof(ip_addr))
printf(_("Invalid client address %s"),
opteq+1);
strncpy(ip_addr,opteq+1, sizeof(ip_addr));
ip_addr[sizeof(ip_addr)-1] = '\0';
ip_addr_in_opts = 1;
} else if (!strcmp(opt, "sec")) {
num_flavour = parse_sec(opteq+1, pseudoflavour);
if (!num_flavour)
goto fail;
} else if (!strcmp(opt, "addr") || sloppy) {
/* ignore */;
} else {
printf(_("unknown nfs mount parameter: "
"%s=%d\n"), opt, val);
goto fail;
}
} else {
val = 1;
if (!strncmp(opt, "no", 2)) {
val = 0;
opt += 2;
}
if (!strcmp(opt, "bg"))
bg = val;
else if (!strcmp(opt, "fg"))
bg = !val;
else if (!strcmp(opt, "soft"))
soft = val;
else if (!strcmp(opt, "hard"))
soft = !val;
else if (!strcmp(opt, "intr"))
intr = val;
else if (!strcmp(opt, "cto"))
nocto = !val;
else if (!strcmp(opt, "ac"))
noac = !val;
else if (!strcmp(opt, "sharecache"))
unshared = !val;
else if (!sloppy) {
printf(_("unknown nfs mount option: %s%s\n"),
val ? "" : "no", opt);
goto fail;
}
}
}
/* if retry is still -1, then it wasn't set via an option */
if (retry == -1) {
if (bg)
retry = 10000; /* 10000 mins == ~1 week */
else
retry = 2; /* 2 min default on fg mounts */
}
data.flags = (soft ? NFS4_MOUNT_SOFT : 0)
| (intr ? NFS4_MOUNT_INTR : 0)
| (nocto ? NFS4_MOUNT_NOCTO : 0)
| (noac ? NFS4_MOUNT_NOAC : 0)
| (unshared ? NFS4_MOUNT_UNSHARED : 0);
/*
* Give a warning if the rpc.idmapd daemon is not running
*/
#if 0
/* We shouldn't have these checks as nothing in this package
* creates the files that are checked
*/
idmapd_check();
if (num_flavour == 0)
pseudoflavour[num_flavour++] = AUTH_UNIX;
else {
/*
* ditto with rpc.gssd daemon
*/
gssd_check();
}
#endif
data.auth_flavourlen = num_flavour;
data.auth_flavours = pseudoflavour;
data.client_addr.data = ip_addr;
data.client_addr.len = strlen(ip_addr);
data.mnt_path.data = dirname;
data.mnt_path.len = strlen(dirname);
data.hostname.data = hostname;
data.hostname.len = strlen(hostname);
data.host_addr = (struct sockaddr *)&server_addr;
data.host_addrlen = sizeof(server_addr);
#ifdef NFS_MOUNT_DEBUG
printf(_("rsize = %d, wsize = %d, timeo = %d, retrans = %d\n"),
data.rsize, data.wsize, data.timeo, data.retrans);
printf(_("acreg (min, max) = (%d, %d), acdir (min, max) = (%d, %d)\n"),
data.acregmin, data.acregmax, data.acdirmin, data.acdirmax);
printf(_("port = %d, bg = %d, retry = %d, flags = %.8x\n"),
ntohs(server_addr.sin_port), bg, retry, data.flags);
printf(_("soft = %d, intr = %d, nocto = %d, noac = %d, "
"nosharecache = %d\n"),
(data.flags & NFS4_MOUNT_SOFT) != 0,
(data.flags & NFS4_MOUNT_INTR) != 0,
(data.flags & NFS4_MOUNT_NOCTO) != 0,
(data.flags & NFS4_MOUNT_NOAC) != 0,
(data.flags & NFS4_MOUNT_UNSHARED) != 0);
if (num_flavour > 0) {
int pf_cnt, i;
printf(_("sec = "));
for (pf_cnt = 0; pf_cnt < num_flavour; pf_cnt++) {
for (i = 0; i < flav_map_size; i++) {
if (flav_map[i].fnum == pseudoflavour[pf_cnt]) {
printf("%s", flav_map[i].flavour);
break;
}
}
printf("%s", (pf_cnt < num_flavour-1) ? ":" : "\n");
}
}
printf(_("proto = %s\n"), (data.proto == IPPROTO_TCP) ? _("tcp") : _("udp"));
#endif
timeout = time(NULL) + 60 * retry;
data.version = NFS4_MOUNT_VERSION;
for (;;) {
if (verbose) {
printf(_("%s: pinging: prog %d vers %d prot %s port %d\n"),
progname, NFS_PROGRAM, 4,
data.proto == IPPROTO_UDP ? "udp" : "tcp",
ntohs(server_addr.sin_port));
}
client_addr.sin_family = 0;
client_addr.sin_addr.s_addr = 0;
clnt_ping(&server_addr, NFS_PROGRAM, 4, data.proto, &client_addr);
if (rpc_createerr.cf_stat == RPC_SUCCESS) {
if (!ip_addr_in_opts &&
client_addr.sin_family != 0 &&
client_addr.sin_addr.s_addr != 0) {
snprintf(ip_addr, sizeof(ip_addr), "%s",
inet_ntoa(client_addr.sin_addr));
data.client_addr.len = strlen(ip_addr);
}
break;
}
if (!bg) {
switch(rpc_createerr.cf_stat) {
case RPC_TIMEDOUT:
break;
case RPC_SYSTEMERROR:
if (errno == ETIMEDOUT)
break;
default:
rpc_mount_errors(hostname, 0, bg);
goto fail;
}
}
if (bg && !running_bg) {
if (retry > 0)
retval = EX_BG;
goto fail;
}
t = time(NULL);
if (t >= timeout) {
rpc_mount_errors(hostname, 0, bg);
goto fail;
}
rpc_mount_errors(hostname, 1, bg);
continue;
}
if (!fake) {
if (mount(spec, node, "nfs4",
flags & ~(MS_USER|MS_USERS), &data)) {
mount_error(spec, node, errno);
goto fail;
}
}
return EX_SUCCESS;
fail:
return retval;
}