/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2006 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; int 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; boolean_t can_do_mlp; MyName = *av; /* * Initializations that require more privileges than we need to run. */ (void) _create_daemon_lock(NFSD, DAEMON_UID, DAEMON_GID); svcsetprio(); can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, can_do_mlp ? PRIV_NET_BINDMLP : NULL, 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 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 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); }