/* * Copyright (C) 2021 Red Hat * * support/exportd/exportd.c * * Routines used to support NFSv4 exports * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #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); }