#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $");
#endif
#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 <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/sm_inter.h>
#include "lockd.h"
#include <rpcsvc/nlm_prot.h>
int debug_level = 0;
int _rpcsvcdirty = 0;
int waitkern = 0;
const char *pid_file = NULL;
int grace_expired;
int nsm_state;
pid_t client_pid = -1;
struct mon mon_host;
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);
int claim_pid_file(const char *, int);
void cleanup_pid_file(void);
void handle_sig_cleanup(int);
void sigalarm_handler(void);
void my_svc_run(void);
const char *transports[] = { "udp", "tcp", "udp6", "tcp6" };
int
main(argc, argv)
int argc;
char **argv;
{
SVCXPRT *transp;
int ch;
struct sigaction sigalarm;
int grace_period = 30;
struct rlimit rlp;
int mib[6];
int oldstate;
int oldsize;
int newstate;
while ((ch = getopt(argc, argv, "d:g:wx:")) != (-1)) {
switch (ch) {
case 'd':
debug_level = atoi(optarg);
if (!debug_level) {
usage();
}
break;
case 'g':
grace_period = atoi(optarg);
if (!grace_period) {
usage();
}
break;
case 'w':
waitkern = 1;
break;
case 'x':
host_expire = atoi(optarg);
break;
default:
case '?':
usage();
}
}
if (geteuid()) {
fprintf(stderr, "Sorry. You are not superuser\n");
exit(1);
}
if (debug_level != 99 && daemon(0, debug_level > 0)) {
err(1, "cannot fork");
}
signal(SIGINT, handle_sig_cleanup);
signal(SIGTERM, handle_sig_cleanup);
signal(SIGHUP, handle_sig_cleanup);
signal(SIGQUIT, handle_sig_cleanup);
openlog("rpc.lockd", debug_level == 99 ? LOG_PERROR : 0, LOG_DAEMON);
mib[0] = CTL_KERN;
mib[1] = KERN_PROCDELAYTERM;
oldstate = 0;
oldsize = 4;
newstate = 1;
if (sysctl(mib, 2, &oldstate, &oldsize, &newstate, 4) < 0) {
syslog(LOG_INFO, "cannot mark pid for delayed termination");
}
if (claim_pid_file("/var/run/lockd.pid", 0) < 0) {
syslog(LOG_ERR, "cannot claim pid file");
exit(1);
}
if (waitkern) {
struct timespec ts;
client_kern_wait();
system("rpc.statd");
ts.tv_sec = 0;
ts.tv_nsec = 100*1000*1000;
nanosleep(&ts, NULL);
}
if (debug_level)
syslog(LOG_INFO, "Starting, debug level %d", debug_level);
else
syslog(LOG_INFO, "Starting");
(void)pmap_unset(NLM_PROG, NLM_SM);
(void)pmap_unset(NLM_PROG, NLM_VERS);
(void)pmap_unset(NLM_PROG, NLM_VERSX);
(void)pmap_unset(NLM_PROG, NLM_VERS4);
transp = svcudp_create(RPC_ANYSOCK);
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);
}
transp = svctcp_create(RPC_ANYSOCK, 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);
}
sigalarm.sa_handler = (sig_t) sigalarm_handler;
sigemptyset(&sigalarm.sa_mask);
sigalarm.sa_flags = SA_RESETHAND;
sigalarm.sa_flags |= SA_RESTART;
if (sigaction(SIGALRM, &sigalarm, NULL) != 0) {
syslog(LOG_WARNING, "sigaction(SIGALRM) failed: %s",
strerror(errno));
exit(1);
}
grace_expired = 0;
alarm(grace_period);
init_nsm();
client_pid = client_request();
if (getrlimit(RLIMIT_NOFILE, &rlp)) {
syslog(LOG_WARNING, "getrlimit(RLIMIT_NOFILE) failed: %s",
strerror(errno));
} else {
rlp.rlim_cur = rlp.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rlp)) {
syslog(LOG_WARNING, "setrlimit(RLIMIT_NOFILE) failed: %s",
strerror(errno));
}
}
my_svc_run();
exit(1);
}
void
sigalarm_handler(void)
{
grace_expired = 1;
}
void
usage()
{
errx(1, "usage: rpc.lockd [-d <debuglevel>] [-g <grace period>] "
" [-x <statd cache timeout>] [-w]");
}
void
init_nsm(void)
{
enum clnt_stat ret;
my_id id;
sm_stat stat;
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,
xdr_my_id, &id, xdr_sm_stat, &stat);
if (ret) {
syslog(LOG_WARNING, "%lu %s", SM_PROG, clnt_sperrno(ret));
if (++attempt < 20) {
sleep(attempt);
continue;
}
}
break;
} while (1);
if (ret != 0) {
syslog(LOG_ERR, "%lu %s", SM_PROG, clnt_sperrno(ret));
exit(1);
}
nsm_state = stat.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;
}
int
claim_pid_file(const char *name, int force)
{
int pidfd, rv, retried = 0;
FILE *pidfile;
try_again:
pidfd = open(name, O_EXCL|O_CREAT|O_WRONLY,
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if (pidfd < 0) {
char buf[16];
pid_t pid;
if (retried)
return -1;
bzero(buf, 16);
retried = 1;
pidfd = open(name, O_RDONLY);
if (pidfd < 0)
goto try_again;
rv = read(pidfd, buf, 15);
close(pidfd);
if (rv <= 0)
goto try_again;
pid = atoi(buf);
if (pid <= 0)
goto try_again;
rv = kill(pid, force ? SIGKILL : 0);
if ((rv < 0) || force)
unlink(name);
goto try_again;
}
pid_file = name;
atexit(cleanup_pid_file);
pidfile = fdopen(pidfd, "w");
if (pidfile) {
fchmod(pidfd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
fprintf(pidfile, "%d\n", getpid());
fclose(pidfile);
} else
perror("fdopen");
close(pidfd);
return 0;
}
void
cleanup_pid_file(void)
{
if (pid_file) {
unlink(pid_file);
pid_file = NULL;
}
}
void
handle_sig_cleanup(int sig __unused)
{
if (client_pid != -1)
kill(client_pid, SIGTERM);
cleanup_pid_file();
exit(1);
}
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 = host_expire + 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 (debug_level > 3 && error == 0)
fprintf(stderr, "my_svc_run: select timeout\n");
hashosts = expire_lock_hosts();
}
}