blob: 2dd12cb6015b26831410c03fcabf68eb1dd40711 [file] [log] [blame]
/*
* Copyright (C) 2021 Red Hat <nfs@redhat.com>
*
* support/exportd/exportd.c
*
* Routines used to support NFSv4 exports
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stddef.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <wait.h>
#include "nfslib.h"
#include "conffile.h"
#include "exportfs.h"
#include "export.h"
extern void my_svc_run(void);
/* Number of mountd threads to start. Default is 1 and
* that's probably enough unless you need hundreds of
* clients to be able to mount at once. */
static int num_threads = 1;
/* Arbitrary limit on number of threads */
#define MAX_THREADS 64
int manage_gids;
int use_ipaddr = -1;
static struct option longopts[] =
{
{ "foreground", 0, 0, 'F' },
{ "debug", 1, 0, 'd' },
{ "help", 0, 0, 'h' },
{ "manage-gids", 0, 0, 'g' },
{ "num-threads", 1, 0, 't' },
{ "log-auth", 0, 0, 'l' },
{ "cache-use-ipaddr", 0, 0, 'i' },
{ "ttl", 0, 0, 'T' },
{ NULL, 0, 0, 0 }
};
static char shortopts[] = "d:fghs:t:liT:";
/*
* Signal handlers.
*/
inline static void set_signals(void);
/* Wait for all worker child processes to exit and reap them */
static void
wait_for_workers (void)
{
int status;
pid_t pid;
for (;;) {
pid = waitpid(0, &status, 0);
if (pid < 0) {
if (errno == ECHILD)
return; /* no more children */
xlog(L_FATAL, "mountd: can't wait: %s\n",
strerror(errno));
}
/* Note: because we SIG_IGN'd SIGCHLD earlier, this
* does not happen on 2.6 kernels, and waitpid() blocks
* until all the children are dead then returns with
* -ECHILD. But, we don't need to do anything on the
* death of individual workers, so we don't care. */
xlog(L_NOTICE, "mountd: reaped child %d, status %d\n",
(int)pid, status);
}
}
inline void
cleanup_lockfiles (void)
{
unlink(etab.lockfn);
}
/* Fork num_threads worker children and wait for them */
static void
fork_workers(void)
{
int i;
pid_t pid;
xlog(L_NOTICE, "mountd: starting %d threads\n", num_threads);
for (i = 0 ; i < num_threads ; i++) {
pid = fork();
if (pid < 0) {
xlog(L_FATAL, "mountd: cannot fork: %s\n",
strerror(errno));
}
if (pid == 0) {
/* worker child */
/* Re-enable the default action on SIGTERM et al
* so that workers die naturally when sent them.
* Only the parent unregisters with pmap and
* hence needs to do special SIGTERM handling. */
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
/* fall into my_svc_run in caller */
return;
}
}
/* in parent */
wait_for_workers();
cleanup_lockfiles();
free_state_path_names(&etab);
xlog(L_NOTICE, "exportd: no more workers, exiting\n");
exit(0);
}
static void
killer (int sig)
{
if (num_threads > 1) {
/* play Kronos and eat our children */
kill(0, SIGTERM);
wait_for_workers();
}
cleanup_lockfiles();
free_state_path_names(&etab);
xlog (L_NOTICE, "Caught signal %d, exiting.", sig);
exit(0);
}
static void
sig_hup (int UNUSED(sig))
{
/* don't exit on SIGHUP */
xlog (L_NOTICE, "Received SIGHUP... Ignoring.\n");
return;
}
inline static void
set_signals(void)
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGPIPE, &sa, NULL);
/* WARNING: the following works on Linux and SysV, but not BSD! */
sigaction(SIGCHLD, &sa, NULL);
sa.sa_handler = killer;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sa.sa_handler = sig_hup;
sigaction(SIGHUP, &sa, NULL);
}
static void
usage(const char *prog, int n)
{
fprintf(stderr,
"Usage: %s [-f|--foreground] [-h|--help] [-d kind|--debug kind]\n"
" [-g|--manage-gids] [-l|--log-auth] [-i|--cache-use-ipaddr] [-T|--ttl ttl]\n"
" [-s|--state-directory-path path]\n"
" [-t num|--num-threads=num]\n", prog);
exit(n);
}
inline static void
read_exportd_conf(char *progname, char **argv)
{
char *s;
int ttl;
conf_init_file(NFS_CONFFILE);
xlog_set_debug(progname);
manage_gids = conf_get_bool("exportd", "manage-gids", manage_gids);
num_threads = conf_get_num("exportd", "threads", num_threads);
if (conf_get_bool("mountd", "cache-use-ipaddr", 0))
use_ipaddr = 2;
s = conf_get_str("exportd", "state-directory-path");
if (s && !state_setup_basedir(argv[0], s))
exit(1);
ttl = conf_get_num("mountd", "ttl", default_ttl);
if (ttl > 0)
default_ttl = ttl;
}
int
main(int argc, char **argv)
{
char *progname;
int foreground = 0;
int c;
int ttl;
/* Set the basename */
if ((progname = strrchr(argv[0], '/')) != NULL)
progname++;
else
progname = argv[0];
/* Initialize logging. */
xlog_open(progname);
/* Read in config setting */
read_exportd_conf(progname, argv);
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) {
switch (c) {
case 'd':
xlog_sconfig(optarg, 1);
break;
case 'l':
xlog_sconfig("auth", 1);
break;
case 'f':
foreground++;
break;
case 'g':
manage_gids = 1;
break;
case 'h':
usage(progname, 0);
break;
case 'i':
use_ipaddr = 2;
break;
case 'T':
ttl = atoi(optarg);
if (ttl <= 0) {
fprintf(stderr, "%s: bad ttl number of seconds: %s\n",
argv[0], optarg);
usage(argv[0], 1);
}
default_ttl = ttl;
break;
case 's':
if (!state_setup_basedir(argv[0], optarg))
exit(1);
break;
case 't':
num_threads = atoi (optarg);
break;
case '?':
default:
usage(progname, 1);
}
}
if (!setup_state_path_names(progname, ETAB, ETABTMP, ETABLCK, &etab))
return 1;
if (!foreground)
xlog_stderr(0);
daemon_init(foreground);
set_signals();
daemon_ready();
/* silently bounds check num_threads */
if (foreground)
num_threads = 1;
else if (num_threads < 1)
num_threads = 1;
else if (num_threads > MAX_THREADS)
num_threads = MAX_THREADS;
if (num_threads > 1)
fork_workers();
/* Open files now to avoid sharing descriptors among forked processes */
cache_open();
v4clients_init();
/* Process incoming upcalls */
cache_process_loop();
xlog(L_ERROR, "%s: process loop terminated unexpectedly. Exiting...\n",
progname);
free_state_path_names(&etab);
exit(1);
}