nfsd.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
/* LINTLIBRARY */
/* PROTOLIB1 */
#pragma ident "%Z%%M% %I% %E% SMI"
/* NFS server */
#include <sys/param.h>
#include <sys/types.h>
#include <syslog.h>
#include <tiuser.h>
#include <rpc/rpc.h>
#include <errno.h>
#include <thread.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/file.h>
#include <nfs/nfs.h>
#include <nfs/nfs_acl.h>
#include <nfs/nfssys.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <netconfig.h>
#include <netdir.h>
#include <string.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/tihdr.h>
#include <poll.h>
#include <priv_utils.h>
#include <sys/tiuser.h>
#include <netinet/tcp.h>
#include <deflt.h>
#include <rpcsvc/daemon_utils.h>
#include <rpcsvc/nfs4_prot.h>
#include "nfs_tbind.h"
#include "thrpool.h"
/* quiesce requests will be ignored if nfs_server_vers_max < QUIESCE_VERSMIN */
#define QUIESCE_VERSMIN 4
static int nfssvc(int, struct netbuf, struct netconfig *);
static int nfssvcpool(int maxservers);
static void usage(void);
extern int _nfssys(int, void *);
/* signal handlers */
static void sigflush(int);
static void quiesce(int);
static char *MyName;
static NETSELDECL(defaultproviders)[] = { "/dev/tcp6", "/dev/tcp", "/dev/udp",
"/dev/udp6", NULL };
/* static NETSELDECL(defaultprotos)[] = { NC_UDP, NC_TCP, NULL }; */
/*
* The following are all globals used by routines in nfs_tbind.c.
*/
size_t end_listen_fds; /* used by conn_close_oldest() */
size_t num_fds = 0; /* used by multiple routines */
int listen_backlog = 32; /* used by bind_to_{provider,proto}() */
int num_servers; /* used by cots_listen_event() */
int (*Mysvc)(int, struct netbuf, struct netconfig *) = nfssvc;
/* used by cots_listen_event() */
int max_conns_allowed = -1; /* used by cots_listen_event() */
/*
* Keep track of min/max versions of NFS protocol to be started.
* Start with the defaults (min == 2, max == 3). We have the
* capability of starting vers=4 but only if the user requests it.
*/
int nfs_server_vers_min = NFS_VERSMIN_DEFAULT;
int nfs_server_vers_max = NFS_VERSMAX_DEFAULT;
/*
* Set the default for server delegation enablement and set per
* /etc/default/nfs configuration (if present).
*/
int nfs_server_delegation = NFS_SERVER_DELEGATION_DEFAULT;
main(int ac, char **av)
{
char *dir = "/";
int allflag = 0;
int df_allflag = 0;
int opt_cnt = 0;
int maxservers = 1; /* zero allows inifinte number of threads */
int maxservers_set = 0;
int logmaxservers = 0;
int pid;
int i;
char *provider = (char *)NULL;
char *df_provider = (char *)NULL;
struct protob *protobp0, *protobp;
NETSELDECL(proto) = NULL;
NETSELDECL(df_proto) = NULL;
NETSELPDECL(providerp);
char *defval;
MyName = *av;
/*
* Initializations that require more privileges than we need to run.
*/
(void) _create_daemon_lock(NFSD, DAEMON_UID, DAEMON_GID);
svcsetprio();
if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET,
DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, (char *)NULL) == -1) {
(void) fprintf(stderr, "%s should be run with"
" sufficient privileges\n", av[0]);
exit(1);
}
/*
* Read in the values from config file first before we check
* commandline options so the options override the file.
*/
if ((defopen(NFSADMIN)) == 0) {
if ((defval = defread("NFSD_MAX_CONNECTIONS=")) != NULL) {
errno = 0;
max_conns_allowed = strtol(defval, (char **)NULL, 10);
if (errno != 0) {
max_conns_allowed = -1;
}
}
if ((defval = defread("NFSD_LISTEN_BACKLOG=")) != NULL) {
errno = 0;
listen_backlog = strtol(defval, (char **)NULL, 10);
if (errno != 0) {
listen_backlog = 32;
}
}
if ((defval = defread("NFSD_PROTOCOL=")) != NULL) {
df_proto = strdup(defval);
opt_cnt++;
if (strncasecmp("ALL", defval, 3) == 0) {
free(df_proto);
df_proto = NULL;
df_allflag = 1;
}
}
if ((defval = defread("NFSD_DEVICE=")) != NULL) {
df_provider = strdup(defval);
opt_cnt++;
}
if ((defval = defread("NFSD_SERVERS=")) != NULL) {
errno = 0;
maxservers = strtol(defval, (char **)NULL, 10);
if (errno != 0) {
maxservers = 1;
} else {
maxservers_set = 1;
}
}
if ((defval = defread("NFS_SERVER_VERSMIN=")) != NULL) {
errno = 0;
nfs_server_vers_min =
strtol(defval, (char **)NULL, 10);
if (errno != 0) {
nfs_server_vers_min = NFS_VERSMIN_DEFAULT;
}
}
if ((defval = defread("NFS_SERVER_VERSMAX=")) != NULL) {
errno = 0;
nfs_server_vers_max =
strtol(defval, (char **)NULL, 10);
if (errno != 0) {
nfs_server_vers_max = NFS_VERSMAX_DEFAULT;
}
}
if ((defval = defread("NFS_SERVER_DELEGATION=")) != NULL) {
if (strcmp(defval, "off") == 0) {
nfs_server_delegation = FALSE;
}
}
/* close defaults file */
defopen(NULL);
}
/*
* Conflict options error messages.
*/
if (opt_cnt > 1) {
(void) fprintf(stderr, "\nConflicting options, only one of "
"the following options can be specified\n"
"in " NFSADMIN ":\n"
"\tNFSD_PROTOCOL=ALL\n"
"\tNFSD_PROTOCOL=protocol\n"
"\tNFSD_DEVICE=device\n\n");
usage();
}
opt_cnt = 0;
while ((i = getopt(ac, av, "ac:p:t:l:")) != EOF) {
switch (i) {
case 'a':
free(df_proto);
df_proto = NULL;
free(df_provider);
df_provider = NULL;
allflag = 1;
opt_cnt++;
break;
case 'c':
max_conns_allowed = atoi(optarg);
break;
case 'p':
proto = optarg;
df_allflag = 0;
opt_cnt++;
break;
case 't':
provider = optarg;
df_allflag = 0;
opt_cnt++;
break;
case 'l':
listen_backlog = atoi(optarg);
break;
case '?':
usage();
/* NOTREACHED */
}
}
allflag = df_allflag;
if (proto == NULL)
proto = df_proto;
if (provider == NULL)
provider = df_provider;
/*
* Conflict options error messages.
*/
if (opt_cnt > 1) {
(void) fprintf(stderr, "\nConflicting options, only one of "
"the following options can be specified\n"
"on the command line:\n"
"\t-a\n"
"\t-p protocol\n"
"\t-t transport\n\n");
usage();
}
if (proto != NULL &&
strncasecmp(proto, NC_UDP, strlen(NC_UDP)) == 0) {
if (nfs_server_vers_max == NFS_V4) {
if (nfs_server_vers_min == NFS_V4) {
syslog(LOG_ERR,
"NFS version 4 is not supported "
"with the UDP protocol. Exiting\n");
fprintf(stderr,
"NFS version 4 is not supported "
"with the UDP protocol. Exiting\n");
exit(3);
} else {
fprintf(stderr,
"NFS version 4 is not supported "
"with the UDP protocol.\n");
}
}
}
/*
* If there is exactly one more argument, it is the number of
* servers.
*/
if (optind == ac - 1) {
maxservers = atoi(av[optind]);
maxservers_set = 1;
}
/*
* If there are two or more arguments, then this is a usage error.
*/
else if (optind < ac - 1)
usage();
/*
* Check the ranges for min/max version specified
*/
else if ((nfs_server_vers_min > nfs_server_vers_max) ||
(nfs_server_vers_min < NFS_VERSMIN) ||
(nfs_server_vers_max > NFS_VERSMAX))
usage();
/*
* There are no additional arguments, and we haven't set maxservers
* explicitly via the config file, we use a default number of
* servers. We will log this.
*/
else if (maxservers_set == 0)
logmaxservers = 1;
/*
* Basic Sanity checks on options
*
* max_conns_allowed must be positive, except for the special
* value of -1 which is used internally to mean unlimited, -1 isn't
* documented but we allow it anyway.
*
* maxservers must be positive
* listen_backlog must be positive or zero
*/
if (((max_conns_allowed != -1) && (max_conns_allowed <= 0)) ||
(listen_backlog < 0) || (maxservers <= 0)) {
usage();
}
/*
* Set current dir to server root
*/
if (chdir(dir) < 0) {
(void) fprintf(stderr, "%s: ", MyName);
perror(dir);
exit(1);
}
#ifndef DEBUG
/*
* Background
*/
pid = fork();
if (pid < 0) {
perror("nfsd: fork");
exit(1);
}
if (pid != 0)
exit(0);
/*
* Close existing file descriptors, open "/dev/null" as
* standard input, output, and error, and detach from
* controlling terminal.
*/
closefrom(0);
(void) open("/dev/null", O_RDONLY);
(void) open("/dev/null", O_WRONLY);
(void) dup(1);
(void) setsid();
#endif
openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON);
/*
* establish our lock on the lock file and write our pid to it.
* exit if some other process holds the lock, or if there's any
* error in writing/locking the file.
*/
pid = _enter_daemon_lock(NFSD);
switch (pid) {
case 0:
break;
case -1:
syslog(LOG_ERR, "error locking for %s: %s", NFSD,
strerror(errno));
exit(2);
default:
/* daemon was already running */
exit(0);
}
sigset(SIGTERM, sigflush);
sigset(SIGUSR1, quiesce);
if (logmaxservers) {
(void) syslog(LOG_INFO,
"Number of servers not specified. Using default of %d.",
maxservers);
}
/*
* Make sure to unregister any previous versions in case the
* user is reconfiguring the server in interesting ways.
*/
svc_unreg(NFS_PROGRAM, NFS_VERSION);
svc_unreg(NFS_PROGRAM, NFS_V3);
svc_unreg(NFS_PROGRAM, NFS_V4);
svc_unreg(NFS_ACL_PROGRAM, NFS_ACL_V2);
svc_unreg(NFS_ACL_PROGRAM, NFS_ACL_V3);
/*
* Set up kernel RPC thread pool for the NFS server.
*/
if (nfssvcpool(maxservers)) {
(void) syslog(LOG_ERR,
"Can't set up kernel NFS service: %m. Exiting");
exit(1);
}
/*
* Set up blocked thread to do LWP creation on behalf of the kernel.
*/
if (svcwait(NFS_SVCPOOL_ID)) {
(void) syslog(LOG_ERR,
"Can't set up NFS pool creator: %m, Exiting");
exit(1);
}
/*
* RDMA start and stop thread.
* Per pool RDMA listener creation and
* destructor thread.
*
* start rdma services and block in the kernel.
*/
if (svcrdma(NFS_SVCPOOL_ID, nfs_server_vers_min, nfs_server_vers_max,
nfs_server_delegation)) {
(void) syslog(LOG_ERR,
"Can't set up RDMA creator thread : %m.");
}
/*
* Build a protocol block list for registration.
*/
protobp0 = protobp = (struct protob *)malloc(sizeof (struct protob));
protobp->serv = "NFS";
protobp->versmin = nfs_server_vers_min;
protobp->versmax = nfs_server_vers_max;
protobp->program = NFS_PROGRAM;
protobp->next = (struct protob *)malloc(sizeof (struct protob));
protobp = protobp->next;
protobp->serv = "NFS_ACL"; /* not used */
protobp->versmin = nfs_server_vers_min;
/* XXX - this needs work to get the version just right */
protobp->versmax = (nfs_server_vers_max > NFS_ACL_V3) ?
NFS_ACL_V3 : nfs_server_vers_max;
protobp->program = NFS_ACL_PROGRAM;
protobp->next = (struct protob *)NULL;
if (allflag) {
if (do_all(protobp0, nfssvc) == -1)
exit(1);
} else if (proto) {
/* there's more than one match for the same protocol */
struct netconfig *nconf;
NCONF_HANDLE *nc;
bool_t protoFound = FALSE;
if ((nc = setnetconfig()) == (NCONF_HANDLE *) NULL) {
syslog(LOG_ERR, "setnetconfig failed: %m");
goto done;
}
while (nconf = getnetconfig(nc)) {
if (strcmp(nconf->nc_proto, proto) == 0) {
protoFound = TRUE;
do_one(nconf->nc_device, NULL,
protobp0, nfssvc);
}
}
(void) endnetconfig(nc);
if (protoFound == FALSE)
syslog(LOG_ERR, "couldn't find netconfig entry \
for protocol %s", proto);
} else if (provider)
do_one(provider, proto, protobp0, nfssvc);
else {
for (providerp = defaultproviders;
*providerp != NULL; providerp++) {
provider = *providerp;
do_one(provider, NULL, protobp0, nfssvc);
}
}
done:
free(protobp);
free(protobp0);
if (num_fds == 0) {
(void) syslog(LOG_ERR,
"Could not start NFS service for any protocol. Exiting.");
exit(1);
}
end_listen_fds = num_fds;
/*
* Get rid of unneeded privileges.
*/
__fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION,
PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, (char *)NULL);
/*
* Poll for non-data control events on the transport descriptors.
*/
poll_for_action();
/*
* If we get here, something failed in poll_for_action().
*/
return (1);
}
static int
nfssvcpool(int maxservers)
{
struct svcpool_args npa;
npa.id = NFS_SVCPOOL_ID;
npa.maxthreads = maxservers;
npa.redline = 0;
npa.qsize = 0;
npa.timeout = 0;
npa.stksize = 0;
npa.max_same_xprt = 0;
return (_nfssys(SVCPOOL_CREATE, &npa));
}
/*
* Establish NFS service thread.
*/
static int
nfssvc(int fd, struct netbuf addrmask, struct netconfig *nconf)
{
struct nfs_svc_args nsa;
nsa.fd = fd;
nsa.netid = nconf->nc_netid;
nsa.addrmask = addrmask;
if (strncasecmp(nconf->nc_proto, NC_UDP, strlen(NC_UDP)) == 0) {
nsa.versmax = (nfs_server_vers_max > NFS_V3) ?
NFS_V3 : nfs_server_vers_max;
nsa.versmin = nfs_server_vers_min;
/*
* If no version left, silently do nothing, previous
* checks will have assured at least TCP is available.
*/
if (nsa.versmin > nsa.versmax)
return (0);
} else {
nsa.versmax = nfs_server_vers_max;
nsa.versmin = nfs_server_vers_min;
}
nsa.delegation = nfs_server_delegation;
return (_nfssys(NFS_SVC, &nsa));
}
static void
usage(void)
{
(void) fprintf(stderr,
"usage: %s [ -a ] [ -c max_conns ] [ -p protocol ] [ -t transport ] ", MyName);
(void) fprintf(stderr, "\n[ -l listen_backlog ] [ nservers ]\n");
(void) fprintf(stderr,
"\twhere -a causes <nservers> to be started on each appropriate transport,\n");
(void) fprintf(stderr,
"\tmax_conns is the maximum number of concurrent connections allowed,\n");
(void) fprintf(stderr, "\t\tand max_conns must be a decimal number");
(void) fprintf(stderr, "> zero,\n");
(void) fprintf(stderr, "\tprotocol is a protocol identifier,\n");
(void) fprintf(stderr,
"\ttransport is a transport provider name (i.e. device),\n");
(void) fprintf(stderr,
"\tlisten_backlog is the TCP listen backlog,\n");
(void) fprintf(stderr,
"\tand <nservers> must be a decimal number > zero.\n");
exit(1);
}
/*
* Issue nfssys system call to flush all logging buffers asynchronously.
*
* NOTICE: It is extremely important to flush NFS logging buffers when
* nfsd exits. When the system is halted or rebooted nfslogd
* may not have an opportunity to flush the buffers.
*/
static void
nfsl_flush()
{
struct nfsl_flush_args nfa;
memset((void *)&nfa, 0, sizeof (nfa));
nfa.version = NFSL_FLUSH_ARGS_VERS;
nfa.directive = NFSL_ALL; /* flush all asynchronously */
if (_nfssys(LOG_FLUSH, &nfa) < 0)
syslog(LOG_ERR, "_nfssys(LOG_FLUSH) failed: %s\n",
strerror(errno));
}
/*
* SIGTERM handler.
* Flush logging buffers and exit.
*/
static void
sigflush(int sig)
{
nfsl_flush();
exit(0);
}
/*
* SIGUSR1 handler.
* Request server quiesce, then exit. For subsequent warm start.
* Equivalent to SIGTERM handler if nfs_server_vers_max < QUIESCE_VERSMIN.
*/
static void
quiesce(int sig)
{
int error;
int id = NFS_SVCPOOL_ID;
if (nfs_server_vers_max >= QUIESCE_VERSMIN) {
/* Request server quiesce at next shutdown */
error = _nfssys(NFS_SVC_REQUEST_QUIESCE, &id);
if (error) {
syslog(LOG_ERR,
"_nfssys(NFS_SVC_REQUEST_QUIESCE) failed: %s\n",
strerror(errno));
return;
}
}
/* Flush logging buffers */
nfsl_flush();
exit(0);
}