#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $");
#endif
#include <sys/syslimits.h>
#define FD_SETSIZE OPEN_MAX
#define _DARWIN_UNLIMITED_SELECT
#include <sys/types.h>
#include <sys/socket.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <syslog.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/select.h>
#include <libutil.h>
#include <launch.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/sm_inter.h>
#include "lockd.h"
#include <rpcsvc/nlm_prot.h>
int bindresvport_sa(int sd, struct sockaddr *sa);
int _rpcsvcdirty = 0;
struct pidfh *pfh = NULL;
const struct nfs_conf_lockd config_defaults =
{
45,
60,
0,
180,
180,
0,
};
struct nfs_conf_lockd config;
int udpport, tcpport;
int grace_expired;
int nsm_state;
pid_t server_pid = -1;
struct mon mon_host;
time_t currsec;
void init_nsm(void);
void nlm_prog_0(struct svc_req *, SVCXPRT *);
void nlm_prog_1(struct svc_req *, SVCXPRT *);
void nlm_prog_3(struct svc_req *, SVCXPRT *);
void nlm_prog_4(struct svc_req *, SVCXPRT *);
void usage(void);
static int config_read(struct nfs_conf_lockd *);
static void sigalarm_grace_period_handler(void);
static void handle_sigchld(int sig);
void my_svc_run(void);
static int statd_is_loaded(void);
static int statd_load(void);
static int statd_service_start(void);
const char *transports[] = { "udp", "tcp", "udp6", "tcp6" };
int
main(argc, argv)
int argc;
char **argv;
{
SVCXPRT *transp;
struct sockaddr_in inetaddr;
socklen_t socklen;
int ch, sockfd, rv;
struct sigaction sa;
struct rlimit rlp;
pid_t child, pid;
sigset_t waitset, osigset;
int server_sig;
config = config_defaults;
config_read(&config);
while ((ch = getopt(argc, argv, "d:g:x:")) != (-1)) {
switch (ch) {
case 'd':
config.verbose = atoi(optarg);
break;
case 'g':
config.grace_period = atoi(optarg);
break;
case 'x':
config.host_monitor_cache_timeout = atoi(optarg);
break;
default:
case '?':
usage();
}
}
if (geteuid()) {
fprintf(stderr, "Sorry. You are not superuser\n");
exit(1);
}
signal(SIGINT, handle_sig_cleanup);
signal(SIGTERM, handle_sig_cleanup);
signal(SIGHUP, handle_sig_cleanup);
signal(SIGQUIT, handle_sig_cleanup);
openlog("rpc.lockd", LOG_CONS | LOG_PID | ((config.verbose == 99) ? LOG_PERROR : 0), LOG_DAEMON);
pfh = pidfile_open(_PATH_LOCKD_PID, 0644, &pid);
if (pfh == NULL) {
syslog(LOG_ERR, "can't open lockd pidfile: %s (%d)", strerror(errno), errno);
if (errno == EEXIST) {
syslog(LOG_ERR, "lockd already running, pid: %d", pid);
exit(0);
}
exit(2);
}
if (pidfile_write(pfh) == -1)
syslog(LOG_WARNING, "can't write to lockd pidfile: %s (%d)", strerror(errno), errno);
if (config.verbose)
syslog(LOG_INFO, "lockd starting, debug level %d", config.verbose);
else
syslog(LOG_INFO, "lockd starting");
if ((rv = statd_start()))
syslog(LOG_WARNING, "unable to start statd %d", rv);
sa.sa_handler = handle_sigchld;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGCHLD);
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);
sigemptyset(&waitset);
sigaddset(&waitset, SIGUSR1);
sigprocmask(SIG_BLOCK, &waitset, &osigset);
switch (child = fork()) {
case -1:
syslog(LOG_ERR, "Could not fork server");
handle_sig_cleanup(0);
case 0:
sigprocmask(SIG_SETMASK, &osigset, NULL);
break;
default:
server_pid = child;
sigwait(&waitset, &server_sig);
if (server_sig != SIGUSR1) {
syslog(LOG_ERR, "Lockd got unexpected signal %d\n", server_sig);
handle_sig_cleanup(0);
}
sigprocmask(SIG_SETMASK, &osigset, NULL);
client_mach_request();
syslog(LOG_ERR, "Lockd: client_mach_request(), returned unexpectedly");
handle_sig_cleanup(0);
}
pmap_unset(NLM_PROG, NLM_SM);
pmap_unset(NLM_PROG, NLM_VERS);
pmap_unset(NLM_PROG, NLM_VERSX);
pmap_unset(NLM_PROG, NLM_VERS4);
pfh = NULL;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
syslog(LOG_ERR, "can't create UDP socket: %s (%d)", strerror(errno), errno);
exit(1);
}
inetaddr.sin_family = AF_INET;
inetaddr.sin_addr.s_addr = INADDR_ANY;
inetaddr.sin_port = htons(config.port);
inetaddr.sin_len = sizeof(inetaddr);
if (bindresvport_sa(sockfd, (struct sockaddr *) &inetaddr) < 0) {
syslog(LOG_ERR, "can't bind UDP addr: %s (%d)", strerror(errno), errno);
exit(1);
}
socklen = sizeof(inetaddr);
if (getsockname(sockfd, (struct sockaddr *) &inetaddr, &socklen))
syslog(LOG_ERR, "can't getsockname on UDP socket: %s (%d)", strerror(errno), errno);
else
udpport = ntohs(inetaddr.sin_port);
transp = svcudp_create(sockfd);
if (transp == NULL) {
syslog(LOG_ERR, "cannot create UDP service");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_SM, nlm_prog_0, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_SM, udp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERS, nlm_prog_1, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERS, udp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERSX, nlm_prog_3, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERSX, udp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERS4, nlm_prog_4, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERS4, udp)");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
syslog(LOG_ERR, "can't create TCP socket: %s (%d)", strerror(errno), errno);
exit(1);
}
inetaddr.sin_family = AF_INET;
inetaddr.sin_addr.s_addr = INADDR_ANY;
inetaddr.sin_port = htons(config.port);
inetaddr.sin_len = sizeof(inetaddr);
if (bindresvport_sa(sockfd, (struct sockaddr *) &inetaddr) < 0) {
syslog(LOG_ERR, "can't bind TCP addr: %s (%d)", strerror(errno), errno);
exit(1);
}
socklen = sizeof(inetaddr);
if (getsockname(sockfd, (struct sockaddr *) &inetaddr, &socklen))
syslog(LOG_ERR, "can't getsockname on TCP socket: %s (%d)", strerror(errno), errno);
else
tcpport = ntohs(inetaddr.sin_port);
transp = svctcp_create(sockfd, 0, 0);
if (transp == NULL) {
syslog(LOG_ERR, "cannot create TCP service");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_SM, nlm_prog_0, IPPROTO_TCP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_SM, tcp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERS, nlm_prog_1, IPPROTO_TCP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERS, tcp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERSX, nlm_prog_3, IPPROTO_TCP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERSX, tcp)");
exit(1);
}
if (!svc_register(transp, NLM_PROG, NLM_VERS4, nlm_prog_4, IPPROTO_TCP)) {
syslog(LOG_ERR, "unable to register (NLM_PROG, NLM_VERS4, tcp)");
exit(1);
}
sa.sa_handler = (sig_t) sigalarm_grace_period_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sa.sa_flags |= SA_RESTART;
if (sigaction(SIGALRM, &sa, NULL) != 0) {
syslog(LOG_WARNING, "sigaction(SIGALRM) failed: %s",
strerror(errno));
exit(1);
}
if (config.grace_period > 0) {
grace_expired = 0;
alarm(config.grace_period);
} else {
grace_expired = 1;
}
init_nsm();
if (getrlimit(RLIMIT_NOFILE, &rlp)) {
syslog(LOG_WARNING, "getrlimit(RLIMIT_NOFILE) failed: %s",
strerror(errno));
} else {
rlp.rlim_cur = MIN(rlp.rlim_max, OPEN_MAX);
if (setrlimit(RLIMIT_NOFILE, &rlp)) {
syslog(LOG_WARNING, "setrlimit(RLIMIT_NOFILE) failed: %s",
strerror(errno));
}
}
kill(getppid(), SIGUSR1);
my_svc_run();
exit(1);
}
void
sigalarm_grace_period_handler(void)
{
grace_expired = 1;
}
void
usage()
{
errx(1, "usage: rpc.lockd [-d <debuglevel>] [-g <grace period>] "
" [-x <host monitor cache timeout>] [-w]");
}
void
init_nsm(void)
{
enum clnt_stat ret;
my_id id;
sm_stat mstat;
char name[] = "NFS NLM";
char localhost[] = "localhost";
int attempt = 0;
memset(&id, 0, sizeof(id));
id.my_name = name;
do {
ret = callrpc("localhost", SM_PROG, SM_VERS, SM_UNMON_ALL,
(xdrproc_t)xdr_my_id, &id, (xdrproc_t)xdr_sm_stat, &mstat);
if (ret) {
syslog(LOG_WARNING, "can't contact statd, %lu %s", SM_PROG, clnt_sperrno(ret));
if (++attempt < 20) {
statd_start();
sleep(attempt);
continue;
}
}
break;
} while (1);
if (ret != 0) {
syslog(LOG_ERR, "server init_nsm failed! %lu %s", SM_PROG, clnt_sperrno(ret));
exit(1);
}
nsm_state = mstat.state;
mon_host.mon_id.my_id.my_name = localhost;
mon_host.mon_id.my_id.my_prog = NLM_PROG;
mon_host.mon_id.my_id.my_vers = NLM_SM;
mon_host.mon_id.my_id.my_proc = NLM_SM_NOTIFY;
}
void
handle_sig_cleanup(int sig)
{
if (server_pid != -1) {
kill(server_pid, SIGTERM);
} else {
alarm(1);
pmap_unset(NLM_PROG, NLM_SM);
pmap_unset(NLM_PROG, NLM_VERS);
pmap_unset(NLM_PROG, NLM_VERSX);
pmap_unset(NLM_PROG, NLM_VERS4);
}
if (pfh)
pidfile_remove(pfh);
exit((sig == SIGTERM) ? 0 : 1);
}
static void
handle_sigchld(int sig __unused)
{
pid_t pid;
int status;
pid = wait4(-1, &status, WNOHANG, NULL);
if ((server_pid != -1) && (pid == server_pid)) {
if (status)
syslog(LOG_ERR, "lockd server %d failed with status %d",
pid, WEXITSTATUS(status));
handle_sig_cleanup(0);
}
}
void
my_svc_run(void)
{
fd_set readfds;
struct timeval timeout;
struct timeval now;
int error;
int hashosts = 0;
int tsize = 0;
struct timeval *top;
for( ;; ) {
timeout.tv_sec = config.host_monitor_cache_timeout + 1;
timeout.tv_usec = 0;
tsize = getdtablesize();
bcopy(&svc_fdset, &readfds, sizeof(svc_fdset));
if (hashosts && (timeout.tv_sec >= 0))
top = &timeout;
else
top = NULL;
error = select(tsize, &readfds, NULL, NULL, top);
if (error == -1) {
if (errno == EINTR)
continue;
perror("rpc.lockd: my_svc_run: select failed");
return;
}
gettimeofday(&now, NULL);
currsec = now.tv_sec;
if (error > 0)
svc_getreqset(&readfds);
if ((config.verbose > 3) && (error == 0))
fprintf(stderr, "my_svc_run: select timeout\n");
hashosts = expire_lock_hosts();
}
}
static int
config_read(struct nfs_conf_lockd *conf)
{
FILE *f;
size_t len, linenum = 0;
char *line, *p, *key, *value;
long val;
if (!(f = fopen(_PATH_NFS_CONF, "r"))) {
if (errno != ENOENT)
syslog(LOG_WARNING, "%s", _PATH_NFS_CONF);
return (1);
}
for (; (line = fparseln(f, &len, &linenum, NULL, 0)); free(line)) {
if (len <= 0)
continue;
p = line + len - 1;
while ((p > line) && isspace(*p))
*p-- = '\0';
key = line;
while (isspace(*key))
key++;
value = p = strchr(line, '=');
if (p)
do { *p-- = '\0'; } while ((p > line) && isspace(*p));
if (value)
do { value++; } while (isspace(*value));
if (strncmp(key, "nfs.lockd.", 10)) {
if (config.verbose)
syslog(LOG_DEBUG, "%4ld %s=%s\n", linenum, key, value ? value : "");
continue;
}
val = !value ? 1 : strtol(value, NULL, 0);
if (config.verbose)
syslog(LOG_DEBUG, "%4ld %s=%s (%d)\n", linenum, key, value ? value : "", val);
if (!strcmp(key, "nfs.lockd.grace_period")) {
conf->grace_period = val;
} else if (!strcmp(key, "nfs.lockd.host_monitor_cache_timeout")) {
conf->host_monitor_cache_timeout = val;
} else if (!strcmp(key, "nfs.lockd.port")) {
conf->port = val;
} else if (!strcmp(key, "nfs.lockd.shutdown_delay_client")) {
conf->shutdown_delay_client = val;
} else if (!strcmp(key, "nfs.lockd.shutdown_delay_server")) {
conf->shutdown_delay_server = val;
} else if (!strcmp(key, "nfs.lockd.verbose")) {
conf->verbose = val;
} else {
if (config.verbose)
syslog(LOG_DEBUG, "ignoring unknown config value: %4ld %s=%s\n", linenum, key, value ? value : "");
}
}
fclose(f);
return (0);
}
static pid_t
get_statd_pid(void)
{
char pidbuf[128], *pidend;
int fd, len, rv;
pid_t pid;
struct flock lock;
if ((fd = open(_PATH_STATD_PID, O_RDONLY)) < 0) {
if (config.verbose)
syslog(LOG_DEBUG, "%s: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
}
len = sizeof(pidbuf) - 1;
if ((len = read(fd, pidbuf, len)) < 0) {
if (config.verbose)
syslog(LOG_DEBUG, "%s: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
}
pidbuf[len] = '\0';
pid = strtol(pidbuf, &pidend, 10);
if (!len || (pid < 1)) {
if (config.verbose)
syslog(LOG_DEBUG, "%s: bogus pid: %s", _PATH_STATD_PID, pidbuf);
return (0);
}
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
rv = fcntl(fd, F_GETLK, &lock);
close(fd);
if (rv != 0) {
if (config.verbose)
syslog(LOG_DEBUG, "%s: fcntl: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
} else if (lock.l_type == F_UNLCK) {
if (config.verbose)
syslog(LOG_DEBUG, "%s: not locked\n", _PATH_STATD_PID);
return (0);
}
return (pid);
}
static int
statd_is_running(void)
{
return (get_statd_pid() > 0);
}
static int
statd_is_loaded(void)
{
launch_data_t msg, resp;
int rv = 0;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (0);
launch_data_dict_insert(msg, launch_data_new_string(_STATD_SERVICE_LABEL), LAUNCH_KEY_GETJOB);
resp = launch_msg(msg);
if (resp) {
if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY)
rv = 1;
launch_data_free(resp);
} else {
syslog(LOG_ERR, "launch_msg(): %m");
}
launch_data_free(msg);
return (rv);
}
static int
statd_load(void)
{
launch_data_t msg, job, args, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
job = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
args = launch_data_alloc(LAUNCH_DATA_ARRAY);
if (!msg || !job || !args) {
if (msg) launch_data_free(msg);
if (job) launch_data_free(job);
if (args) launch_data_free(args);
return (1);
}
launch_data_array_set_index(args, launch_data_new_string(_PATH_STATD), 0);
launch_data_dict_insert(job, launch_data_new_string(_STATD_SERVICE_LABEL), LAUNCH_JOBKEY_LABEL);
launch_data_dict_insert(job, launch_data_new_bool(FALSE), LAUNCH_JOBKEY_ONDEMAND);
launch_data_dict_insert(job, launch_data_new_bool(TRUE), LAUNCH_JOBKEY_HOPEFULLYEXITSLAST);
launch_data_dict_insert(job, args, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
launch_data_dict_insert(msg, job, LAUNCH_KEY_SUBMITJOB);
resp = launch_msg(msg);
if (!resp)
rv = errno;
else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(msg);
return (rv);
}
static int
statd_service_start(void)
{
launch_data_t msg, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (1);
launch_data_dict_insert(msg, launch_data_new_string(_STATD_SERVICE_LABEL), LAUNCH_KEY_STARTJOB);
resp = launch_msg(msg);
if (!resp)
rv = errno;
else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(msg);
return (rv);
}
int
statd_start(void)
{
struct timespec ts;
int rv;
if (statd_is_running())
rv = 0;
else if (statd_is_loaded())
rv = statd_service_start();
else
rv = statd_load();
ts.tv_sec = 0;
ts.tv_nsec = 100*1000*1000;
nanosleep(&ts, NULL);
return (rv);
}
int
statd_stop(void)
{
launch_data_t msg, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (1);
launch_data_dict_insert(msg, launch_data_new_string(_STATD_SERVICE_LABEL), LAUNCH_KEY_REMOVEJOB);
resp = launch_msg(msg);
if (!resp)
rv = errno;
else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(msg);
return (rv);
}