/* $NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $ */ /* $FreeBSD: src/usr.sbin/rpc.lockd/lockd.c,v 1.13 2002/04/11 07:19:30 alfred Exp $ */ /* * Copyright (c) 1995 * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the FreeBSD project * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #ifndef lint __RCSID("$NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $"); #endif /* * main() function for NFS lock daemon. Most of the code in this * file was generated by running rpcgen /usr/include/rpcsvc/nlm_prot.x. * * The actual program logic is in the file lock_proc.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lockd.h" #include int debug_level = 0; /* 0 = no debugging syslog() calls */ int _rpcsvcdirty = 0; int waitkern = 0; /* 1 = wait for kernel to say start */ 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(); /* NOTREACHED */ } break; case 'g': grace_period = atoi(optarg); if (!grace_period) { usage(); /* NOTREACHED */ } break; case 'w': waitkern = 1; break; case 'x': host_expire = atoi(optarg); break; default: case '?': usage(); /* NOTREACHED */ } } if (geteuid()) { /* This command allowed only to root */ fprintf(stderr, "Sorry. You are not superuser\n"); exit(1); } /* * Note that it is NOT sensible to run this program from inetd - the * protocol assumes that it will run immediately at boot time. */ if (debug_level != 99 && daemon(0, debug_level > 0)) { err(1, "cannot fork"); /* NOTREACHED */ } /* Install signal handler to remove any pid file */ 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; /* wait for kernel to get first lock request */ client_kern_wait(); /* start statd now, in case it isn't already */ system("rpc.statd"); /* sleep a little to give statd/portmap a chance to start */ /* (better to sleep 100ms than to timeout on portmap calls) */ 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; /* should only happen once */ 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(); /* raise our resource limits as far as they can go */ 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(); /* Should never return */ exit(1); } void sigalarm_handler(void) { grace_expired = 1; } void usage() { errx(1, "usage: rpc.lockd [-d ] [-g ] " " [-x ] [-w]"); } /* * init_nsm -- * Reset the NSM state-of-the-world and acquire its state. */ void init_nsm(void) { enum clnt_stat ret; my_id id; sm_stat stat; char name[] = "NFS NLM"; char localhost[] = "localhost"; int attempt = 0; /* * !!! * The my_id structure isn't used by the SM_UNMON_ALL call, as far * as I know. Leave it empty for now. */ memset(&id, 0, sizeof(id)); id.my_name = name; /* * !!! * The statd program must already be registered when lockd runs. * If we have a problem contacting statd, pause and try again a * number of times in case statd is just slow in coming up. */ 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; /* setup constant data for SM_MON calls */ 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; /* bsdi addition */ } /* * claim_pid_file * * Purpose: take ownership of and store pid in given pid_file * Returns: 0 on success or -1 on failure * Notes: force parameter requests that current owner (if any) of * pid file be terminated. */ int claim_pid_file(const char *name, int force) { int pidfd, rv, retried = 0; FILE *pidfile; try_again: /* attempt exclusive open of pid file */ 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; /* pid file busy, check validity */ 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 can't signal, assume stale pid file */ 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; } /* * cleanup_pid_file * * Purpose: delete any pid_file that has been claimed * Returns: Nothing */ void cleanup_pid_file(void) { if (pid_file) { unlink(pid_file); pid_file = NULL; } } /* * handle_sig_cleanup * * Purpose: on signal, kill client child and do pid file cleanup * Returns: Nothing */ 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 there are any expired hosts then sleep with a * timeout to expire them. */ 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(); } }