blob: a469a67a91dfd79c374766fdf4431051cb699085 [file] [log] [blame]
/*
* Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
* Modified by Olaf Kirch, Oct. 1996.
* Modified by H.J. Lu, 1998.
* Modified by L. Hohberger of Mission Critical Linux, 2000.
*
* NSM for Linux.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcmisc.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <grp.h>
#include "conffile.h"
#include "statd.h"
#include "nfslib.h"
#include "nfsrpc.h"
#include "nsm.h"
/* Socket operations */
#include <sys/types.h>
#include <sys/socket.h>
int run_mode = 0; /* foreground logging mode */
/* LH - I had these local to main, but it seemed silly to have
* two copies of each - one in main(), one static in log.c...
* It also eliminates the 256-char static in log.c */
static char *name_p = NULL;
/* PRC: a high-availability callout program can be specified with -H
* When this is done, the program will receive callouts whenever clients
* are added or deleted to the notify list */
char *ha_callout_prog = NULL;
static struct option longopts[] =
{
{ "foreground", 0, 0, 'F' },
{ "no-syslog", 0, 0, 'd' },
{ "help", 0, 0, 'h' },
{ "version", 0, 0, 'v' },
{ "outgoing-port", 1, 0, 'o' },
{ "port", 1, 0, 'p' },
{ "name", 1, 0, 'n' },
{ "state-directory-path", 1, 0, 'P' },
{ "notify-mode", 0, 0, 'N' },
{ "ha-callout", 1, 0, 'H' },
{ "no-notify", 0, 0, 'L' },
{ "nlm-port", 1, 0, 'T'},
{ "nlm-udp-port", 1, 0, 'U'},
{ NULL, 0, 0, 0 }
};
extern void sm_prog_1 (struct svc_req *, register SVCXPRT *);
stat_chge SM_stat_chge;
#ifdef SIMULATIONS
extern void simulator (int, char **);
#endif
#ifdef HAVE_TCP_WRAPPER
#include "tcpwrapper.h"
static void
sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp)
{
/* remote host authorization check */
if (!check_default("statd", nfs_getrpccaller(transp), SM_PROG)) {
svcerr_auth (transp, AUTH_FAILED);
return;
}
sm_prog_1 (rqstp, transp);
}
#define sm_prog_1 sm_prog_1_wrapper
#endif
static void
statd_unregister(void) {
nfs_svc_unregister(SM_PROG, SM_VERS);
}
/*
* Signal handler.
*/
static void
killer (int sig)
{
statd_unregister ();
xlog(D_GENERAL, "Caught signal %d, un-registering and exiting", sig);
exit(0);
}
static void
sigusr (int sig)
{
extern void my_svc_exit (void);
xlog(D_GENERAL, "Caught signal %d, re-notifying (state %d)", sig,
MY_STATE);
my_svc_exit();
}
/*
* Startup information.
*/
static void log_modes(void)
{
char buf[128]; /* watch stack size... */
/* No flags = no message */
if (!run_mode) return;
memset(buf,0,128);
sprintf(buf,"Flags: ");
if (run_mode & MODE_NODAEMON)
strcat(buf,"No-Daemon ");
if (run_mode & MODE_LOG_STDERR)
strcat(buf,"Log-STDERR ");
#ifdef HAVE_LIBTIRPC
strcat(buf, "TI-RPC ");
#endif
xlog_warn("%s", buf);
}
/*
* Since we do more than standard statd stuff, we might need to
* help the occasional admin.
*/
static void
usage(void)
{
fprintf(stderr,"usage: %s [options]\n", name_p);
fprintf(stderr," -h, -?, --help Print this help screen.\n");
fprintf(stderr," -F, --foreground Foreground (no-daemon mode)\n");
fprintf(stderr," -d, --no-syslog Verbose logging to stderr. Foreground mode only.\n");
fprintf(stderr," -p, --port Port to listen on\n");
fprintf(stderr," -o, --outgoing-port Port for outgoing connections\n");
fprintf(stderr," -V, -v, --version Display version information and exit.\n");
fprintf(stderr," -n, --name Specify a local hostname.\n");
fprintf(stderr," -P State directory path.\n");
fprintf(stderr," -N Run in notify only mode.\n");
fprintf(stderr," -L, --no-notify Do not perform any notification.\n");
fprintf(stderr," -H Specify a high-availability callout program.\n");
}
static const char *pidfile = "/run/rpc.statd.pid";
int pidfd = -1;
static void create_pidfile(void)
{
FILE *fp;
unlink(pidfile);
fp = fopen(pidfile, "w");
if (!fp)
xlog_err("Opening %s failed: %m\n", pidfile);
fprintf(fp, "%d\n", getpid());
pidfd = dup(fileno(fp));
if (fclose(fp) < 0) {
xlog_warn("Flushing pid file failed: errno %d (%m)\n",
errno);
}
}
static void truncate_pidfile(void)
{
if (pidfd >= 0) {
if (ftruncate(pidfd, 0) < 0) {
xlog_warn("truncating pid file failed: errno %d (%m)\n",
errno);
}
}
}
static void run_sm_notify(int outport)
{
char op[20];
char *av[6];
int ac = 0;
av[ac++] = "/usr/sbin/sm-notify";
if (run_mode & MODE_NODAEMON)
av[ac++] = "-d";
if (outport) {
sprintf(op, "-p%d", outport);
av[ac++] = op;
}
if (run_mode & STATIC_HOSTNAME) {
av[ac++] = "-v";
av[ac++] = MY_NAME;
}
av[ac] = NULL;
execv(av[0], av);
fprintf(stderr, "%s: failed to run %s\n", name_p, av[0]);
exit(2);
}
static void set_nlm_port(char *type, int port)
{
char nbuf[20];
char pathbuf[40];
int fd;
if (!port)
return;
snprintf(nbuf, sizeof(nbuf), "%d", port);
snprintf(pathbuf, sizeof(pathbuf), "/proc/sys/fs/nfs/nlm_%sport", type);
fd = open(pathbuf, O_WRONLY);
if (fd < 0 && errno == ENOENT) {
/* probably module not loaded */
if (system("modprobe lockd"))
{/* ignore return value */};
fd = open(pathbuf, O_WRONLY);
}
if (fd >= 0) {
if (write(fd, nbuf, strlen(nbuf)) != (ssize_t)strlen(nbuf))
fprintf(stderr, "%s: fail to set NLM %s port: %s\n",
name_p, type, strerror(errno));
close(fd);
} else
fprintf(stderr, "%s: failed to open %s: %s\n",
name_p, pathbuf, strerror(errno));
}
int port = 0, out_port = 0;
int nlm_udp = 0, nlm_tcp = 0;
inline static void
read_statd_conf(char **argv)
{
char *s;
conf_init_file(NFS_CONFFILE);
xlog_set_debug("statd");
out_port = conf_get_num("statd", "outgoing-port", out_port);
port = conf_get_num("statd", "port", port);
MY_NAME = conf_get_str("statd", "name");
if (MY_NAME)
run_mode |= STATIC_HOSTNAME;
s = conf_get_str("statd", "state-directory-path");
if (s && !nsm_setup_pathnames(argv[0], s))
exit(1);
s = conf_get_str("statd", "ha-callout");
if (s)
ha_callout_prog = s;
nlm_tcp = conf_get_num("lockd", "port", nlm_tcp);
/* udp defaults to the same as tcp ! */
nlm_udp = conf_get_num("lockd", "udp-port", nlm_tcp);
if (conf_get_bool("statd", "no-notify", false))
run_mode |= MODE_NO_NOTIFY;
}
/*
* Entry routine/main loop.
*/
int main (int argc, char **argv)
{
extern char *optarg;
int pid;
int arg;
struct rlimit rlim;
int notify_sockfd;
char *env;
/* Default: daemon mode, no other options */
run_mode = 0;
env = getenv("RPC_STATD_NO_NOTIFY");
if (env && atoi(env) > 0)
run_mode |= MODE_NO_NOTIFY;
/* Log to stderr if there's an error during startup */
xlog_stderr(1);
xlog_syslog(0);
/* Set the basename */
if ((name_p = strrchr(argv[0],'/')) != NULL) {
name_p ++;
} else {
name_p = argv[0];
}
/* Set hostname */
MY_NAME = NULL;
/* Read in config setting */
read_statd_conf(argv);
/* Process command line switches */
while ((arg = getopt_long(argc, argv, "h?vVFNH:dn:p:o:P:LT:U:", longopts, NULL)) != EOF) {
switch (arg) {
case 'V': /* Version */
case 'v':
printf("%s version " VERSION "\n",name_p);
exit(0);
case 'F': /* Foreground/nodaemon mode */
run_mode |= MODE_NODAEMON;
break;
case 'N':
run_mode |= MODE_NOTIFY_ONLY;
break;
case 'L': /* Listen only */
run_mode |= MODE_NO_NOTIFY;
break;
case 'd': /* No daemon only - log to stderr */
run_mode |= MODE_LOG_STDERR;
break;
case 'o':
out_port = atoi(optarg);
if (out_port < 1 || out_port > 65535) {
fprintf(stderr, "%s: bad port number: %s\n",
argv[0], optarg);
usage();
exit(1);
}
break;
case 'p':
port = atoi(optarg);
if (port < 1 || port > 65535) {
fprintf(stderr, "%s: bad port number: %s\n",
argv[0], optarg);
usage();
exit(1);
}
break;
case 'T': /* NLM TCP and UDP port */
nlm_tcp = atoi(optarg);
if (nlm_tcp < 1 || nlm_tcp > 65535) {
fprintf(stderr, "%s: bad nlm port number: %s\n",
argv[0], optarg);
usage();
exit(1);
}
if (nlm_udp == 0)
nlm_udp = nlm_tcp;
break;
case 'U': /* NLM UDP port */
nlm_udp = atoi(optarg);
if (nlm_udp < 1 || nlm_udp > 65535) {
fprintf(stderr, "%s: bad nlm UDP port number: %s\n",
argv[0], optarg);
usage();
exit(1);
}
break;
case 'n': /* Specify local hostname */
run_mode |= STATIC_HOSTNAME;
MY_NAME = xstrdup(optarg);
break;
case 'P':
if (!nsm_setup_pathnames(argv[0], optarg))
exit(1);
break;
case 'H': /* PRC: specify the ha-callout program */
if ((ha_callout_prog = xstrdup(optarg)) == NULL)
exit(1);
break;
case '?': /* heeeeeelllllllpppp? heh */
case 'h':
usage();
exit (0);
default: /* oh dear ... heh */
usage();
exit(-1);
}
}
/* Refuse to start if another statd is running */
if (nfs_probe_statd()) {
fprintf(stderr, "Statd service already running!\n");
exit(1);
}
if (port == out_port && port != 0) {
fprintf(stderr, "Listening and outgoing ports cannot be the same!\n");
exit(-1);
}
if (run_mode & MODE_NOTIFY_ONLY) {
fprintf(stderr, "%s: -N deprecated, consider using /usr/sbin/sm-notify directly\n",
name_p);
run_sm_notify(out_port);
}
if (!(run_mode & MODE_NODAEMON)) {
run_mode &= ~MODE_LOG_STDERR; /* Never log to console in
daemon mode. */
}
if (getrlimit (RLIMIT_NOFILE, &rlim) != 0)
fprintf(stderr, "%s: getrlimit (RLIMIT_NOFILE) failed: %s\n",
argv [0], strerror(errno));
else {
/* glibc sunrpc code dies if getdtablesize > FD_SETSIZE */
if (rlim.rlim_cur > FD_SETSIZE) {
rlim.rlim_cur = FD_SETSIZE;
if (setrlimit (RLIMIT_NOFILE, &rlim) != 0) {
fprintf(stderr, "%s: setrlimit (RLIMIT_NOFILE) failed: %s\n",
argv [0], strerror(errno));
}
}
}
set_nlm_port("tcp", nlm_tcp);
set_nlm_port("udp", nlm_udp);
#ifdef SIMULATIONS
if (argc > 1)
/* LH - I _really_ need to update simulator... */
simulator (--argc, ++argv); /* simulator() does exit() */
#endif
daemon_init((run_mode & MODE_NODAEMON));
if (run_mode & MODE_LOG_STDERR) {
xlog_syslog(0);
xlog_stderr(1);
xlog_config(D_ALL, 1);
} else {
xlog_syslog(1);
xlog_stderr(0);
}
xlog_open(name_p);
xlog(L_NOTICE, "Version " VERSION " starting");
log_modes();
signal (SIGHUP, killer);
signal (SIGINT, killer);
signal (SIGTERM, killer);
/* PRC: trap SIGUSR1 to re-read notify list from disk */
signal(SIGUSR1, sigusr);
/* WARNING: the following works on Linux and SysV, but not BSD! */
signal(SIGCHLD, SIG_IGN);
/*
* Ignore SIGPIPE to avoid statd dying when peers close their
* TCP connection while we're trying to reply to them.
*/
signal(SIGPIPE, SIG_IGN);
create_pidfile();
atexit(truncate_pidfile);
if (! (run_mode & MODE_NO_NOTIFY))
switch (pid = fork()) {
case 0:
run_sm_notify(out_port);
break;
case -1:
break;
default:
waitpid(pid, NULL, 0);
}
/* Make sure we have a privilege port for calling into the kernel */
if ((notify_sockfd = statd_get_socket()) < 0)
exit(1);
/* If sm-notify didn't take all the state files, load
* state information into our notify-list so we can
* pass on any SM_NOTIFY that arrives
*/
load_state();
MY_STATE = nsm_get_state(0);
if (MY_STATE == 0)
exit(1);
xlog(D_GENERAL, "Local NSM state number: %d", MY_STATE);
nsm_update_kernel_state(MY_STATE);
/*
* ORDER
* Clear old listeners while still root, to override any
* permission checking done by rpcbind.
*/
statd_unregister();
/*
* ORDER
*/
if (!nsm_drop_privileges(pidfd))
exit(1);
/*
* ORDER
* Create RPC listeners after dropping privileges. This permits
* statd to unregister its own listeners when it exits.
*/
if (nfs_svc_create("statd", SM_PROG, SM_VERS, sm_prog_1, port) == 0) {
xlog(L_ERROR, "failed to create RPC listeners, exiting");
exit(1);
}
atexit(statd_unregister);
/* If we got this far, we have successfully started */
daemon_ready();
for (;;) {
/*
* Handle incoming requests: SM_NOTIFY socket requests, as
* well as callbacks from lockd.
*/
my_svc_run(notify_sockfd); /* I rolled my own, Olaf made it better... */
/* Only get here when simulating a crash so we should probably
* start sm-notify running again. As we have already dropped
* privileges, this might not work, but I don't think
* responding to SM_SIMU_CRASH is an important use cases to
* get perfect.
*/
if (! (run_mode & MODE_NO_NOTIFY))
switch (pid = fork()) {
case 0:
run_sm_notify(out_port);
break;
case -1:
break;
default:
waitpid(pid, NULL, 0);
}
}
return 0;
}