diff options
Diffstat (limited to 'usr.sbin/rpc.lockd')
-rw-r--r-- | usr.sbin/rpc.lockd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/lockd.c | 109 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/lockd.h | 4 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/lockd_lock.c | 792 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/lockd_lock.h | 20 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/procs.c | 932 | ||||
-rw-r--r-- | usr.sbin/rpc.lockd/rpc.lockd.8 | 20 |
7 files changed, 1756 insertions, 125 deletions
diff --git a/usr.sbin/rpc.lockd/Makefile b/usr.sbin/rpc.lockd/Makefile index d0cf75c3ec8..ff4948a7288 100644 --- a/usr.sbin/rpc.lockd/Makefile +++ b/usr.sbin/rpc.lockd/Makefile @@ -1,7 +1,7 @@ -# $OpenBSD: Makefile,v 1.4 1997/08/19 08:05:16 niklas Exp $ +# $OpenBSD: Makefile,v 1.5 2008/06/13 21:32:26 sturm Exp $ PROG = rpc.lockd -SRCS = nlm_prot_svc.c lockd.c procs.c +SRCS = nlm_prot_svc.c lockd.c lockd_lock.c procs.c MAN = rpc.lockd.8 DPADD= ${LIBRPCSVC} diff --git a/usr.sbin/rpc.lockd/lockd.c b/usr.sbin/rpc.lockd/lockd.c index df64fa80528..effd860178e 100644 --- a/usr.sbin/rpc.lockd/lockd.c +++ b/usr.sbin/rpc.lockd/lockd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lockd.c,v 1.8 2008/05/17 23:31:52 sobrado Exp $ */ +/* $OpenBSD: lockd.c,v 1.9 2008/06/13 21:32:26 sturm Exp $ */ /* * Copyright (c) 1995 @@ -44,38 +44,56 @@ #include <stdio.h> #include <syslog.h> #include <stdlib.h> +#include <errno.h> #include <string.h> #include <netdb.h> #include "lockd.h" -extern void nlm_prog_1(struct svc_req *, SVCXPRT *); -extern void nlm_prog_3(struct svc_req *, SVCXPRT *); +int debug_level = 0; /* 0 = no debugging syslog() calls */ +int _rpcsvcdirty = 0; +int grace_expired; -int debug_level = 0; /* Zero means no debugging syslog() calls */ +void nlm_prog_1(struct svc_req *, SVCXPRT *); +void nlm_prog_3(struct svc_req *, SVCXPRT *); +void nlm_prog_4(struct svc_req *, SVCXPRT *); -int _rpcsvcdirty; +static void sigalarm_handler(int); +static void usage(void); int main(int argc, char *argv[]) { SVCXPRT *transp; + int ch; + struct sigaction sigchild, sigalarm; + int grace_period = 30; - if (argc > 1) { - if (strncmp(argv[1], "-d", 2)) { - fprintf(stderr, - "usage: rpc.lockd [-d [debug_level]]\n"); - exit(1); + while ((ch = getopt(argc, argv, "d:g:")) != (-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; + default: + case '?': + usage(); + /* NOTREACHED */ } - if (argc > 2) - debug_level = atoi(argv[2]); - else - debug_level = atoi(argv[1] + 2); - if (!debug_level) - debug_level = 1; } (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) { @@ -92,6 +110,11 @@ main(int argc, char *argv[]) fprintf(stderr, "unable to register (NLM_PROG, NLM_VERSX, udp).\n"); exit(1); } + if (!svc_register(transp, NLM_PROG, NLM_VERS4, + (void (*) (struct svc_req *, SVCXPRT *)) nlm_prog_4, IPPROTO_UDP)) { + fprintf(stderr, "unable to register (NLM_PROG, NLM_VERS4, udp).\n"); + exit(1); + } transp = svctcp_create(RPC_ANYSOCK, 0, 0); if (transp == NULL) { fprintf(stderr, "cannot create tcp service.\n"); @@ -107,17 +130,61 @@ main(int argc, char *argv[]) fprintf(stderr, "unable to register (NLM_PROG, NLM_VERSX, tcp).\n"); exit(1); } - - if (daemon(0, 0)) { - perror("cannot fork"); + if (!svc_register(transp, NLM_PROG, NLM_VERS4, + (void (*) (struct svc_req *, SVCXPRT *)) nlm_prog_4, IPPROTO_TCP)) { + fprintf(stderr, "unable to register (NLM_PROG, NLM_VERS4, tcp).\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 (daemon(0, 0) == -1) { + err(1, "cannot fork"); + /* NOTREACHED */ + } + openlog("rpc.lockd", 0, LOG_DAEMON); if (debug_level) syslog(LOG_INFO, "Starting, debug level %d", debug_level); else syslog(LOG_INFO, "Starting"); - svc_run(); - exit(1); + sigchild.sa_handler = sigchild_handler; + sigemptyset(&sigchild.sa_mask); + sigchild.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sigchild, NULL) != 0) { + syslog(LOG_WARNING, "sigaction(SIGCHLD) failed (%m)"); + exit(1); + } + sigalarm.sa_handler = 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 (%m)"); + exit(1); + } + grace_expired = 0; + if (alarm(10) == (unsigned int)-1) { + syslog(LOG_WARNING, "alarm failed (%m)"); + exit(1); + } + + svc_run(); /* Should never return */ + return 1; +} + +static void +/*ARGSUSED*/ +sigalarm_handler(int s) +{ + grace_expired = 1; +} + +static void +usage() +{ + errx(1, "usage: rpc.lockd [-d <debuglevel>] [-g <grace period>]"); } diff --git a/usr.sbin/rpc.lockd/lockd.h b/usr.sbin/rpc.lockd/lockd.h index 154089c9d04..8a795da1bfb 100644 --- a/usr.sbin/rpc.lockd/lockd.h +++ b/usr.sbin/rpc.lockd/lockd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: lockd.h,v 1.3 1997/09/17 23:09:37 deraadt Exp $ */ +/* $OpenBSD: lockd.h,v 1.4 2008/06/13 21:32:26 sturm Exp $ */ /* * Copyright (c) 1995 @@ -34,3 +34,5 @@ */ extern int debug_level; +extern int grace_expired; +void sigchild_handler(int); diff --git a/usr.sbin/rpc.lockd/lockd_lock.c b/usr.sbin/rpc.lockd/lockd_lock.c new file mode 100644 index 00000000000..52d8c96d5b6 --- /dev/null +++ b/usr.sbin/rpc.lockd/lockd_lock.c @@ -0,0 +1,792 @@ +/* $NetBSD: lockd_lock.c,v 1.2 2000/06/09 14:00:53 fvdl Exp $ */ + +/* + * Copyright (c) 2000 Manuel Bouyer. + * + * 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 by Manuel Bouyer. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <inttypes.h> +#include <syslog.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/wait.h> +#include <rpcsvc/sm_inter.h> +#include <rpcsvc/nlm_prot.h> +#include "lockd_lock.h" +#include "lockd.h" + +/* A set of utilities for managing file locking */ +LIST_HEAD(lcklst_head, file_lock); +struct lcklst_head lcklst_head = LIST_HEAD_INITIALIZER(lcklst_head); + +#define FHANDLE_SIZE_MAX 1024 /* arbitrary big enough value */ +typedef struct { + size_t fhsize; + char *fhdata; +} nfs_fhandle_t; + +static int +fhcmp(const nfs_fhandle_t *fh1, const nfs_fhandle_t *fh2) +{ + + if (fh1->fhsize != fh2->fhsize) { + return 1; + } + return memcmp(fh1->fhdata, fh2->fhdata, fh1->fhsize); +} + +static int +fhconv(nfs_fhandle_t *fh, const netobj *rfh) +{ + size_t sz; + + sz = rfh->n_len; + if (sz > FHANDLE_SIZE_MAX) { + syslog(LOG_DEBUG, + "received fhandle size %zd, max supported size %d", + sz, FHANDLE_SIZE_MAX); + errno = EINVAL; + return -1; + } + fh->fhdata = malloc(sz); + if (fh->fhdata == NULL) { + return -1; + } + fh->fhsize = sz; + memcpy(fh->fhdata, rfh->n_bytes, sz); + return 0; +} + +static void +fhfree(nfs_fhandle_t *fh) +{ + + free(fh->fhdata); +} + +/* struct describing a lock */ +struct file_lock { + LIST_ENTRY(file_lock) lcklst; + nfs_fhandle_t filehandle; /* NFS filehandle */ + struct sockaddr_in *addr; + struct nlm4_holder client; /* lock holder */ + netobj client_cookie; /* cookie sent by the client */ + char client_name[128]; + int nsm_status; /* status from the remote lock manager */ + int status; /* lock status, see below */ + int flags; /* lock flags, see lockd_lock.h */ + pid_t locker; /* pid of the child process trying to get the lock */ + int fd; /* file descriptor for this lock */ +}; + +/* lock status */ +#define LKST_LOCKED 1 /* lock is locked */ +#define LKST_WAITING 2 /* file is already locked by another host */ +#define LKST_PROCESSING 3 /* child is trying to acquire the lock */ +#define LKST_DYING 4 /* must dies when we get news from the child */ + +static struct file_lock *lalloc(void); +void lfree(struct file_lock *); +enum nlm_stats do_lock(struct file_lock *, int); +enum nlm_stats do_unlock(struct file_lock *); +void send_granted(struct file_lock *, int); +void siglock(void); +void sigunlock(void); + +#define LL_FH 0x01 +#define LL_NAME 0x02 +#define LL_SVID 0x04 + +static struct file_lock *lock_lookup(struct file_lock *, int); + +/* + * lock_lookup: lookup a matching lock. + * called with siglock held. + */ +static struct file_lock * +lock_lookup(struct file_lock *newfl, int flags) +{ + struct file_lock *fl; + + LIST_FOREACH(fl, &lcklst_head, lcklst) { + if ((flags & LL_SVID) != 0 && + newfl->client.svid != fl->client.svid) + continue; + if ((flags & LL_NAME) != 0 && + strcmp(newfl->client_name, fl->client_name) != 0) + continue; + if ((flags & LL_FH) != 0 && + fhcmp(&newfl->filehandle, &fl->filehandle) != 0) + continue; + /* found */ + break; + } + + return fl; +} + +/* + * testlock(): inform the caller if the requested lock would be granted or not + * returns NULL if lock would granted, or pointer to the current nlm4_holder + * otherwise. + */ + +struct nlm4_holder * +/*ARGSUSED*/ +testlock(struct nlm4_lock *lock, int flags) +{ + struct file_lock *fl; + nfs_fhandle_t filehandle; + + /* convert lock to a local filehandle */ + if (fhconv(&filehandle, &lock->fh)) { + syslog(LOG_NOTICE, "fhconv failed (%m)"); + return NULL; /* XXX */ + } + + siglock(); + /* search through the list for lock holder */ + LIST_FOREACH(fl, &lcklst_head, lcklst) { + if (fl->status != LKST_LOCKED) + continue; + if (fhcmp(&fl->filehandle, &filehandle) != 0) + continue; + /* got it ! */ + syslog(LOG_DEBUG, "test for %s: found lock held by %s", + lock->caller_name, fl->client_name); + sigunlock(); + fhfree(&filehandle); + return (&fl->client); + } + /* not found */ + sigunlock(); + fhfree(&filehandle); + syslog(LOG_DEBUG, "test for %s: no lock found", lock->caller_name); + return NULL; +} + +/* + * getlock: try to acquire the lock. + * If file is already locked and we can sleep, put the lock in the list with + * status LKST_WAITING; it'll be processed later. + * Otherwise try to lock. If we're allowed to block, fork a child which + * will do the blocking lock. + */ +enum nlm_stats +getlock(nlm4_lockargs * lckarg, struct svc_req *rqstp, int flags) +{ + struct file_lock *fl, *newfl; + enum nlm_stats retval; + struct sockaddr_in *addr; + + if (grace_expired == 0 && lckarg->reclaim == 0) + return (flags & LOCK_V4) ? + nlm4_denied_grace_period : nlm_denied_grace_period; + + /* allocate new file_lock for this request */ + newfl = lalloc(); + if (newfl == NULL) { + syslog(LOG_NOTICE, "malloc failed (%m)"); + /* failed */ + return (flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + } + if (fhconv(&newfl->filehandle, &lckarg->alock.fh)) { + syslog(LOG_NOTICE, "fhconv failed (%m)"); + lfree(newfl); + /* failed */ + return (flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + } + addr = svc_getcaller(rqstp->rq_xprt); + newfl->addr = malloc(addr->sin_len); + if (newfl->addr == NULL) { + syslog(LOG_NOTICE, "malloc failed (%m)"); + lfree(newfl); + /* failed */ + return (flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + } + memcpy(newfl->addr, addr, addr->sin_len); + newfl->client.exclusive = lckarg->exclusive; + newfl->client.svid = lckarg->alock.svid; + newfl->client.oh.n_bytes = malloc(lckarg->alock.oh.n_len); + if (newfl->client.oh.n_bytes == NULL) { + syslog(LOG_NOTICE, "malloc failed (%m)"); + lfree(newfl); + return (flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + } + newfl->client.oh.n_len = lckarg->alock.oh.n_len; + memcpy(newfl->client.oh.n_bytes, lckarg->alock.oh.n_bytes, + lckarg->alock.oh.n_len); + newfl->client.l_offset = lckarg->alock.l_offset; + newfl->client.l_len = lckarg->alock.l_len; + newfl->client_cookie.n_len = lckarg->cookie.n_len; + newfl->client_cookie.n_bytes = malloc(lckarg->cookie.n_len); + if (newfl->client_cookie.n_bytes == NULL) { + syslog(LOG_NOTICE, "malloc failed (%m)"); + lfree(newfl); + return (flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + } + memcpy(newfl->client_cookie.n_bytes, lckarg->cookie.n_bytes, + lckarg->cookie.n_len); + strlcpy(newfl->client_name, lckarg->alock.caller_name, + sizeof(newfl->client_name)); + newfl->nsm_status = lckarg->state; + newfl->status = 0; + newfl->flags = flags; + siglock(); + /* look for a lock rq from this host for this fh */ + fl = lock_lookup(newfl, LL_FH|LL_NAME|LL_SVID); + if (fl) { + /* already locked by this host ??? */ + sigunlock(); + syslog(LOG_NOTICE, "duplicate lock from %s.%" + PRIu32, + newfl->client_name, newfl->client.svid); + lfree(newfl); + switch(fl->status) { + case LKST_LOCKED: + return (flags & LOCK_V4) ? + nlm4_granted : nlm_granted; + case LKST_WAITING: + case LKST_PROCESSING: + return (flags & LOCK_V4) ? + nlm4_blocked : nlm_blocked; + case LKST_DYING: + return (flags & LOCK_V4) ? + nlm4_denied : nlm_denied; + default: + syslog(LOG_NOTICE, "bad status %d", + fl->status); + return (flags & LOCK_V4) ? + nlm4_failed : nlm_denied; + } + /* NOTREACHED */ + } + fl = lock_lookup(newfl, LL_FH); + if (fl) { + /* + * We already have a lock for this file. + * Put this one in waiting state if allowed to block + */ + if (lckarg->block) { + syslog(LOG_DEBUG, "lock from %s.%" PRIu32 ": " + "already locked, waiting", + lckarg->alock.caller_name, + lckarg->alock.svid); + newfl->status = LKST_WAITING; + LIST_INSERT_HEAD(&lcklst_head, newfl, lcklst); + sigunlock(); + return (flags & LOCK_V4) ? + nlm4_blocked : nlm_blocked; + } else { + sigunlock(); + syslog(LOG_DEBUG, "lock from %s.%" PRIu32 ": " + "already locked, failed", + lckarg->alock.caller_name, + lckarg->alock.svid); + lfree(newfl); + return (flags & LOCK_V4) ? + nlm4_denied : nlm_denied; + } + /* NOTREACHED */ + } + + /* no entry for this file yet; add to list */ + LIST_INSERT_HEAD(&lcklst_head, newfl, lcklst); + /* do the lock */ + retval = do_lock(newfl, lckarg->block); + switch (retval) { + case nlm4_granted: + /* case nlm_granted: is the same as nlm4_granted */ + case nlm4_blocked: + /* case nlm_blocked: is the same as nlm4_blocked */ + break; + default: + lfree(newfl); + break; + } + sigunlock(); + return retval; +} + +/* unlock a filehandle */ +enum nlm_stats +unlock(nlm4_lock *lck, int flags) +{ + struct file_lock *fl; + nfs_fhandle_t filehandle; + int err = (flags & LOCK_V4) ? nlm4_granted : nlm_granted; + + if (fhconv(&filehandle, &lck->fh)) { + syslog(LOG_NOTICE, "fhconv failed (%m)"); + return (flags & LOCK_V4) ? nlm4_denied : nlm_denied; + } + siglock(); + LIST_FOREACH(fl, &lcklst_head, lcklst) { + if (strcmp(fl->client_name, lck->caller_name) || + fhcmp(&filehandle, &fl->filehandle) != 0 || + fl->client.oh.n_len != lck->oh.n_len || + memcmp(fl->client.oh.n_bytes, lck->oh.n_bytes, + fl->client.oh.n_len) != 0 || + fl->client.svid != lck->svid) + continue; + /* Got it, unlock and remove from the queue */ + syslog(LOG_DEBUG, "unlock from %s.%" PRIu32 ": found struct, " + "status %d", lck->caller_name, lck->svid, fl->status); + switch (fl->status) { + case LKST_LOCKED: + err = do_unlock(fl); + break; + case LKST_WAITING: + /* remove from the list */ + LIST_REMOVE(fl, lcklst); + lfree(fl); + break; + case LKST_PROCESSING: + /* + * being handled by a child; will clean up + * when the child exits + */ + fl->status = LKST_DYING; + break; + case LKST_DYING: + /* nothing to do */ + break; + default: + syslog(LOG_NOTICE, "unknow status %d for %s", + fl->status, fl->client_name); + } + sigunlock(); + fhfree(&filehandle); + return err; + } + sigunlock(); + /* didn't find a matching entry; log anyway */ + syslog(LOG_NOTICE, "no matching entry for %s", + lck->caller_name); + fhfree(&filehandle); + return (flags & LOCK_V4) ? nlm4_granted : nlm_granted; +} + +static struct file_lock * +lalloc(void) +{ + return calloc(1, sizeof(struct file_lock)); +} + +void +lfree(struct file_lock *fl) +{ + free(fl->addr); + free(fl->client.oh.n_bytes); + free(fl->client_cookie.n_bytes); + fhfree(&fl->filehandle); + free(fl); +} + +void +/*ARGSUSED*/ +sigchild_handler(int sig) +{ + int sstatus; + pid_t pid; + struct file_lock *fl; + + for (;;) { + pid = wait4(-1, &sstatus, WNOHANG, NULL); + if (pid == -1) { + if (errno != ECHILD) + syslog(LOG_NOTICE, "wait failed (%m)"); + else + syslog(LOG_DEBUG, "wait failed (%m)"); + return; + } + if (pid == 0) { + /* no more child to handle yet */ + return; + } + /* + * if we're here we have a child that exited + * Find the associated file_lock. + */ + LIST_FOREACH(fl, &lcklst_head, lcklst) { + if (pid == fl->locker) + break; + } + if (fl == NULL) { + syslog(LOG_NOTICE, "unknown child %d", pid); + } else { + /* protect from pid reusing. */ + fl->locker = 0; + if (!WIFEXITED(sstatus) || WEXITSTATUS(sstatus) != 0) { + syslog(LOG_NOTICE, "child %d failed", pid); + /* + * can't do much here; we can't reply + * anything but OK for blocked locks + * Eventually the client will time out + * and retry. + */ + do_unlock(fl); + return; + } + + /* check lock status */ + syslog(LOG_DEBUG, "processing child %d, status %d", + pid, fl->status); + switch(fl->status) { + case LKST_PROCESSING: + fl->status = LKST_LOCKED; + send_granted(fl, (fl->flags & LOCK_V4) ? + nlm4_granted : nlm_granted); + break; + case LKST_DYING: + do_unlock(fl); + break; + default: + syslog(LOG_NOTICE, "bad lock status (%d) for" + " child %d", fl->status, pid); + } + } + } +} + +/* + * + * try to acquire the lock described by fl. Eventually fock a child to do a + * blocking lock if allowed and required. + */ + +enum nlm_stats +do_lock(struct file_lock *fl, int block) +{ + int lflags, error; + struct stat st; + + fl->fd = fhopen((fhandle_t *)fl->filehandle.fhdata, O_RDWR); + if (fl->fd < 0) { + switch (errno) { + case ESTALE: + error = nlm4_stale_fh; + break; + case EROFS: + error = nlm4_rofs; + break; + default: + error = nlm4_failed; + } + if ((fl->flags & LOCK_V4) == 0) + error = nlm_denied; + syslog(LOG_NOTICE, "fhopen failed (from %s) (%m)", + fl->client_name); + LIST_REMOVE(fl, lcklst); + return error; + } + if (fstat(fl->fd, &st) < 0) { + syslog(LOG_NOTICE, "fstat failed (from %s) (%m)", + fl->client_name); + } + syslog(LOG_DEBUG, "lock from %s.%" PRIu32 " for file%s%s: " + "dev %u ino %d (uid %d), flags %d", + fl->client_name, fl->client.svid, + fl->client.exclusive ? " (exclusive)":"", block ? " (block)":"", + st.st_dev, st.st_ino, st.st_uid, fl->flags); + lflags = LOCK_NB; + if (fl->client.exclusive == 0) + lflags |= LOCK_SH; + else + lflags |= LOCK_EX; + error = flock(fl->fd, lflags); + if (error != 0 && errno == EAGAIN && block) { + switch (fl->locker = fork()) { + case -1: /* fork failed */ + syslog(LOG_NOTICE, "fork failed (%m)"); + LIST_REMOVE(fl, lcklst); + close(fl->fd); + return (fl->flags & LOCK_V4) ? + nlm4_denied_nolock : nlm_denied_nolocks; + case 0: + /* + * Attempt a blocking lock. Will have to call + * NLM_GRANTED later. + */ + setproctitle("%s.%" PRIu32, + fl->client_name, fl->client.svid); + lflags &= ~LOCK_NB; + if(flock(fl->fd, lflags) != 0) { + syslog(LOG_NOTICE, "flock failed (%m)"); + _exit(1); + } + /* lock granted */ + _exit(0); + /*NOTREACHED*/ + default: + syslog(LOG_DEBUG, "lock request from %s.%" PRIu32 ": " + "forked %d", + fl->client_name, fl->client.svid, fl->locker); + fl->status = LKST_PROCESSING; + return (fl->flags & LOCK_V4) ? + nlm4_blocked : nlm_blocked; + } + } + /* non block case */ + if (error != 0) { + switch (errno) { + case EAGAIN: + error = nlm4_denied; + break; + case ESTALE: + error = nlm4_stale_fh; + break; + case EROFS: + error = nlm4_rofs; + break; + default: + error = nlm4_failed; + } + if ((fl->flags & LOCK_V4) == 0) + error = nlm_denied; + if (errno != EAGAIN) + syslog(LOG_NOTICE, "flock for %s failed (%m)", + fl->client_name); + else syslog(LOG_DEBUG, "flock for %s failed (%m)", + fl->client_name); + LIST_REMOVE(fl, lcklst); + close(fl->fd); + return error; + } + fl->status = LKST_LOCKED; + return (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted; +} + +void +/*ARGSUSED*/ +send_granted(struct file_lock *fl, int opcode) +{ + CLIENT *cli; + static char dummy; + struct timeval timeo; + int success; + static struct nlm_res retval; + static struct nlm4_res retval4; + + cli = get_client(fl->addr, + (fl->flags & LOCK_V4) ? NLM_VERS4 : NLM_VERS); + if (cli == NULL) { + syslog(LOG_NOTICE, "failed to get CLIENT for %s.%" PRIu32, + fl->client_name, fl->client.svid); + /* + * We fail to notify remote that the lock has been granted. + * The client will timeout and retry, the lock will be + * granted at this time. + */ + return; + } + timeo.tv_sec = 0; + timeo.tv_usec = (fl->flags & LOCK_ASYNC) ? 0 : 500000; /* 0.5s */ + + if (fl->flags & LOCK_V4) { + static nlm4_testargs result; + result.cookie = fl->client_cookie; + result.exclusive = fl->client.exclusive; + result.alock.caller_name = fl->client_name; + result.alock.fh.n_len = fl->filehandle.fhsize; + result.alock.fh.n_bytes = fl->filehandle.fhdata; + result.alock.oh = fl->client.oh; + result.alock.svid = fl->client.svid; + result.alock.l_offset = fl->client.l_offset; + result.alock.l_len = fl->client.l_len; + syslog(LOG_DEBUG, "sending v4 reply%s", + (fl->flags & LOCK_ASYNC) ? " (async)":""); + if (fl->flags & LOCK_ASYNC) { + success = clnt_call(cli, NLM4_GRANTED_MSG, + xdr_nlm4_testargs, &result, xdr_void, &dummy, timeo); + } else { + success = clnt_call(cli, NLM4_GRANTED, + xdr_nlm4_testargs, &result, xdr_nlm4_res, + &retval4, timeo); + } + } else { + static nlm_testargs result; + + result.cookie = fl->client_cookie; + result.exclusive = fl->client.exclusive; + result.alock.caller_name = fl->client_name; + result.alock.fh.n_len = fl->filehandle.fhsize; + result.alock.fh.n_bytes = fl->filehandle.fhdata; + result.alock.oh = fl->client.oh; + result.alock.svid = fl->client.svid; + result.alock.l_offset = + (unsigned int)fl->client.l_offset; + result.alock.l_len = + (unsigned int)fl->client.l_len; + syslog(LOG_DEBUG, "sending v1 reply%s", + (fl->flags & LOCK_ASYNC) ? " (async)":""); + if (fl->flags & LOCK_ASYNC) { + success = clnt_call(cli, NLM_GRANTED_MSG, + xdr_nlm_testargs, &result, xdr_void, &dummy, timeo); + } else { + success = clnt_call(cli, NLM_GRANTED, + xdr_nlm_testargs, &result, xdr_nlm_res, + &retval, timeo); + } + } + if (debug_level > 2) + syslog(LOG_DEBUG, "clnt_call returns %d(%s) for granted", + success, clnt_sperrno(success)); + +} + +enum nlm_stats +do_unlock(struct file_lock *rfl) +{ + struct file_lock *fl; + int error; + int lockst; + + /* unlock the file: closing is enough ! */ + if (close(rfl->fd) == -1) { + if (errno == ESTALE) + error = nlm4_stale_fh; + else + error = nlm4_failed; + if ((rfl->flags & LOCK_V4) == 0) + error = nlm_denied; + syslog(LOG_NOTICE, "close failed (from %s) (%m)", + rfl->client_name); + } else { + error = (rfl->flags & LOCK_V4) ? + nlm4_granted : nlm_granted; + } + LIST_REMOVE(rfl, lcklst); + + /* process the next LKST_WAITING lock request for this fh */ + LIST_FOREACH(fl, &lcklst_head, lcklst) { + if (fl->status != LKST_WAITING || + fhcmp(&rfl->filehandle, &fl->filehandle) != 0) + continue; + + lockst = do_lock(fl, 1); /* If it's LKST_WAITING we can block */ + switch (lockst) { + case nlm4_granted: + /* case nlm_granted: same as nlm4_granted */ + send_granted(fl, (fl->flags & LOCK_V4) ? + nlm4_granted : nlm_granted); + break; + case nlm4_blocked: + /* case nlm_blocked: same as nlm4_blocked */ + break; + default: + lfree(fl); + break; + } + break; + } + lfree(rfl); + return error; +} + +void +siglock(void) +{ + sigset_t block; + + sigemptyset(&block); + sigaddset(&block, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &block, NULL) < 0) { + syslog(LOG_WARNING, "siglock failed (%m)"); + } +} + +void +sigunlock(void) +{ + sigset_t block; + + sigemptyset(&block); + sigaddset(&block, SIGCHLD); + + if (sigprocmask(SIG_UNBLOCK, &block, NULL) < 0) { + syslog(LOG_WARNING, "sigunlock failed (%m)"); + } +} + +void +notify(const char *hostname, int state) +{ + struct file_lock *fl, *next_fl; + int err; + syslog(LOG_DEBUG, "notify from %s, new state %d", hostname, state); + /* search all lock for this host; if status changed, release the lock */ + siglock(); + for (fl = LIST_FIRST(&lcklst_head); fl != NULL; fl = next_fl) { + next_fl = LIST_NEXT(fl, lcklst); + if (strcmp(hostname, fl->client_name) == 0 && + fl->nsm_status != state) { + syslog(LOG_DEBUG, "state %d, nsm_state %d, unlocking", + fl->status, fl->nsm_status); + switch(fl->status) { + case LKST_LOCKED: + err = do_unlock(fl); + if (err != nlm_granted) + syslog(LOG_DEBUG, + "notify: unlock failed for %s (%d)", + hostname, err); + break; + case LKST_WAITING: + LIST_REMOVE(fl, lcklst); + lfree(fl); + break; + case LKST_PROCESSING: + fl->status = LKST_DYING; + break; + case LKST_DYING: + break; + default: + syslog(LOG_NOTICE, "unknow status %d for %s", + fl->status, fl->client_name); + } + } + } + sigunlock(); +} diff --git a/usr.sbin/rpc.lockd/lockd_lock.h b/usr.sbin/rpc.lockd/lockd_lock.h new file mode 100644 index 00000000000..dee5ae78ede --- /dev/null +++ b/usr.sbin/rpc.lockd/lockd_lock.h @@ -0,0 +1,20 @@ +/* $NetBSD: lockd_lock.h,v 1.2 2000/06/09 14:00:54 fvdl Exp $ */ + +/* Headers and function declarations for file-locking utilities */ + +struct nlm4_holder *testlock(struct nlm4_lock *, int); + +enum nlm_stats getlock(nlm4_lockargs *, struct svc_req *, int); +enum nlm_stats unlock(nlm4_lock *, int); +void notify(const char *, int); + +/* flags for testlock, getlock & unlock */ +#define LOCK_ASYNC 0x01 /* async version (getlock only) */ +#define LOCK_V4 0x02 /* v4 version */ +#define LOCK_MON 0x04 /* monitored lock (getlock only) */ +#define LOCK_CANCEL 0x08 /* cancel, not unlock request (unlock only) */ + +/* callbacks from lock_proc.c */ +void transmit_result(int, nlm_res *, struct sockaddr_in *); +void transmit4_result(int, nlm4_res *, struct sockaddr_in *); +CLIENT *get_client(struct sockaddr_in *, u_long); diff --git a/usr.sbin/rpc.lockd/procs.c b/usr.sbin/rpc.lockd/procs.c index 2c9f7a9470f..018b705c71e 100644 --- a/usr.sbin/rpc.lockd/procs.c +++ b/usr.sbin/rpc.lockd/procs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: procs.c,v 1.11 2003/07/06 21:26:14 deraadt Exp $ */ +/* $OpenBSD: procs.c,v 1.12 2008/06/13 21:32:26 sturm Exp $ */ /* * Copyright (c) 1995 @@ -48,10 +48,18 @@ #include <netdb.h> #include "lockd.h" +#include "lockd_lock.h" #define CLIENT_CACHE_SIZE 64 /* No. of client sockets cached */ #define CLIENT_CACHE_LIFETIME 120 /* In seconds */ +/* log_from_addr ----------------------------------------------------------- */ +/* + * Purpose: Log name of function called and source address + * Returns: Nothing + * Notes: Extracts the source address from the transport handle + * passed in as part of the called procedure specification + */ static void log_from_addr(char *fun_name, struct svc_req *req) { @@ -70,13 +78,42 @@ log_from_addr(char *fun_name, struct svc_req *req) } +/* get_client -------------------------------------------------------------- */ +/* + * Purpose: Get a CLIENT* for making RPC calls to lockd on given host + * Returns: CLIENT* pointer, from clnt_udp_create, or NULL if error + * Notes: Creating a CLIENT* is quite expensive, involving a + * conversation with the remote portmapper to get the + * port number. Since a given client is quite likely + * to make several locking requests in succession, it is + * desirable to cache the created CLIENT*. + * + * Since we are using UDP rather than TCP, there is no cost + * to the remote system in keeping these cached indefinitely. + * Unfortunately there is a snag: if the remote system + * reboots, the cached portmapper results will be invalid, + * and we will never detect this since all of the xxx_msg() + * calls return no result - we just fire off a udp packet + * and hope for the best. + * + * We solve this by discarding cached values after two + * minutes, regardless of whether they have been used + * in the meanwhile (since a bad one might have been used + * plenty of times, as the host keeps retrying the request + * and we keep sending the reply back to the wrong port). + * + * Given that the entries will always expire in the order + * that they were created, there is no point in a LRU + * algorithm for when the cache gets full - entries are + * always re-used in sequence. + */ static CLIENT *clnt_cache_ptr[CLIENT_CACHE_SIZE]; -static long clnt_cache_time[CLIENT_CACHE_SIZE]; /* time entry created */ +static long clnt_cache_time[CLIENT_CACHE_SIZE]; /* time entry created */ static struct in_addr clnt_cache_addr[CLIENT_CACHE_SIZE]; static int clnt_cache_next_to_use = 0; -static CLIENT * -get_client(struct sockaddr_in *host_addr) +CLIENT * +get_client(struct sockaddr_in *host_addr, u_long vers) { CLIENT *client; int sock_no, i; @@ -84,12 +121,14 @@ get_client(struct sockaddr_in *host_addr) gettimeofday(&time_now, NULL); - /* Search for the given client in the cache, zapping any expired */ - /* entries that we happen to notice in passing. */ + /* + * Search for the given client in the cache, zapping any expired + * entries that we happen to notice in passing. + */ for (i = 0; i < CLIENT_CACHE_SIZE; i++) { client = clnt_cache_ptr[i]; - if (client && - ((clnt_cache_time[i] + CLIENT_CACHE_LIFETIME) < time_now.tv_sec)) { + if (client && ((clnt_cache_time[i] + CLIENT_CACHE_LIFETIME) + < time_now.tv_sec)) { /* Cache entry has expired. */ if (debug_level > 3) syslog(LOG_DEBUG, "Expired CLIENT* in cache"); @@ -103,11 +142,11 @@ get_client(struct sockaddr_in *host_addr) /* Found it! */ if (debug_level > 3) syslog(LOG_DEBUG, "Found CLIENT* in cache"); - return (client); + return client; } } - /* Not found in cache. Free the next entry if it is in use */ + /* Not found in cache. Free the next entry if it is in use. */ if (clnt_cache_ptr[clnt_cache_next_to_use]) { clnt_destroy(clnt_cache_ptr[clnt_cache_next_to_use]); clnt_cache_ptr[clnt_cache_next_to_use] = NULL; @@ -117,276 +156,981 @@ get_client(struct sockaddr_in *host_addr) retry_time.tv_sec = 5; retry_time.tv_usec = 0; host_addr->sin_port = 0; - client = clntudp_create(host_addr, NLM_PROG, NLM_VERS, retry_time, &sock_no); + client = clntudp_create(host_addr, NLM_PROG, vers, retry_time, &sock_no); if (!client) { syslog(LOG_ERR, "%s", clnt_spcreateerror("clntudp_create")); syslog(LOG_ERR, "Unable to return result to %s", inet_ntoa(host_addr->sin_addr)); - return (NULL); + return NULL; } + + /* Success - update the cache entry */ clnt_cache_ptr[clnt_cache_next_to_use] = client; clnt_cache_addr[clnt_cache_next_to_use] = host_addr->sin_addr; clnt_cache_time[clnt_cache_next_to_use] = time_now.tv_sec; - if (++clnt_cache_next_to_use > CLIENT_CACHE_SIZE) + if (++clnt_cache_next_to_use >= CLIENT_CACHE_SIZE) clnt_cache_next_to_use = 0; + /* + * Disable the default timeout, so we can specify our own in calls + * to clnt_call(). (Note that the timeout is a different concept + * from the retry period set in clnt_udp_create() above.) + */ retry_time.tv_sec = -1; retry_time.tv_usec = -1; - clnt_control(client, CLSET_TIMEOUT, &retry_time); + clnt_control(client, CLSET_TIMEOUT, (char *)(void *)&retry_time); if (debug_level > 3) syslog(LOG_DEBUG, "Created CLIENT* for %s", inet_ntoa(host_addr->sin_addr)); - return (client); + return client; } -static void -transmit_result(int opcode, nlm_res *result, struct svc_req *req) +/* transmit_result --------------------------------------------------------- */ +/* + * Purpose: Transmit result for nlm_xxx_msg pseudo-RPCs + * Returns: Nothing - we have no idea if the datagram got there + * Notes: clnt_call() will always fail (with timeout) as we are + * calling it with timeout 0 as a hack to just issue a datagram + * without expecting a result + */ +void +transmit_result(int opcode, nlm_res *result, struct sockaddr_in *addr) { static char dummy; - struct sockaddr_in *addr; CLIENT *cli; - int success; struct timeval timeo; + int success; - addr = svc_getcaller(req->rq_xprt); - if ((cli = get_client(addr))) { - timeo.tv_sec = 0; + if ((cli = get_client(addr, NLM_VERS)) != NULL) { + timeo.tv_sec = 0; /* No timeout - not expecting response */ timeo.tv_usec = 0; - success = clnt_call(cli, opcode, xdr_nlm_res, result, xdr_void, - &dummy, timeo); + success = clnt_call(cli, opcode, xdr_nlm_res, + result, xdr_void, &dummy, timeo); + if (debug_level > 2) - syslog(LOG_DEBUG, "clnt_call returns %d", success); + syslog(LOG_DEBUG, "clnt_call returns %d(%s)", + success, clnt_sperrno(success)); } } +/* transmit4_result --------------------------------------------------------- */ +/* + * Purpose: Transmit result for nlm4_xxx_msg pseudo-RPCs + * Returns: Nothing - we have no idea if the datagram got there + * Notes: clnt_call() will always fail (with timeout) as we are + * calling it with timeout 0 as a hack to just issue a datagram + * without expecting a result + */ +void +transmit4_result(int opcode, nlm4_res *result, struct sockaddr_in *addr) +{ + static char dummy; + CLIENT *cli; + struct timeval timeo; + int success; + + if ((cli = get_client(addr, NLM_VERS4)) != NULL) { + timeo.tv_sec = 0; /* No timeout - not expecting response */ + timeo.tv_usec = 0; + + success = clnt_call(cli, opcode, xdr_nlm4_res, + result, xdr_void, &dummy, timeo); + + if (debug_level > 2) + syslog(LOG_DEBUG, "clnt_call returns %d(%s)", + success, clnt_sperrno(success)); + } +} + +/* + * converts a struct nlm_lock to struct nlm4_lock + */ +static void +nlmtonlm4(struct nlm_lock *arg, struct nlm4_lock *arg4) +{ + memcpy(arg4, arg, sizeof(nlm_lock)); + arg4->l_offset = arg->l_offset; + arg4->l_len = arg->l_len; +} + +/* ------------------------------------------------------------------------- */ +/* + * Functions for Unix<->Unix locking (ie. monitored locking, with rpc.statd + * involved to ensure reclaim of locks after a crash of the "stateless" + * server. + * + * These all come in two flavours - nlm_xxx() and nlm_xxx_msg(). + * The first are standard RPCs with argument and result. + * The nlm_xxx_msg() calls implement exactly the same functions, but + * use two pseudo-RPCs (one in each direction). These calls are NOT + * standard use of the RPC protocol in that they do not return a result + * at all (NB. this is quite different from returning a void result). + * The effect of this is to make the nlm_xxx_msg() calls simple unacknowledged + * datagrams, requiring higher-level code to perform retries. + * + * Despite the disadvantages of the nlm_xxx_msg() approach (some of which + * are documented in the comments to get_client() above), this is the + * interface used by all current commercial NFS implementations + * [Solaris, SCO, AIX etc.]. This is presumed to be because these allow + * implementations to continue using the standard RPC libraries, while + * avoiding the block-until-result nature of the library interface. + * + * No client implementations have been identified so far that make use + * of the true RPC version (early SunOS releases would be a likely candidate + * for testing). + */ +/* nlm_test ---------------------------------------------------------------- */ +/* + * Purpose: Test whether a specified lock would be granted if requested + * Returns: nlm_granted (or error code) + * Notes: + */ nlm_testres * nlm_test_1_svc(nlm_testargs *arg, struct svc_req *rqstp) { - static nlm_testres res; + static nlm_testres result; + struct nlm4_lock arg4; + struct nlm4_holder *holder; + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_test", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - return (&res); + + holder = testlock(&arg4, 0); + /* + * Copy the cookie from the argument into the result. Note that this + * is slightly hazardous, as the structure contains a pointer to a + * malloc()ed buffer that will get freed by the caller. However, the + * main function transmits the result before freeing the argument + * so it is in fact safe. + */ + result.cookie = arg->cookie; + if (holder == NULL) { + result.stat.stat = nlm_granted; + } else { + result.stat.stat = nlm_denied; + memcpy(&result.stat.nlm_testrply_u.holder, holder, + sizeof(struct nlm_holder)); + result.stat.nlm_testrply_u.holder.l_offset = + (unsigned int)holder->l_offset; + result.stat.nlm_testrply_u.holder.l_len = + (unsigned int)holder->l_len; + } + return &result; } void * nlm_test_msg_1_svc(nlm_testargs *arg, struct svc_req *rqstp) { - nlm_testres res; + nlm_testres result; static char dummy; struct sockaddr_in *addr; CLIENT *cli; - int success; + int success; struct timeval timeo; + struct nlm4_lock arg4; + struct nlm4_holder *holder; + + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_test_msg", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; + holder = testlock(&arg4, 0); + result.cookie = arg->cookie; + if (holder == NULL) { + result.stat.stat = nlm_granted; + } else { + result.stat.stat = nlm_denied; + memcpy(&result.stat.nlm_testrply_u.holder, holder, + sizeof(struct nlm_holder)); + result.stat.nlm_testrply_u.holder.l_offset = + (unsigned int)holder->l_offset; + result.stat.nlm_testrply_u.holder.l_len = + (unsigned int)holder->l_len; + } + + /* + * nlm_test has different result type to the other operations, so + * can't use transmit_result() in this case + */ addr = svc_getcaller(rqstp->rq_xprt); - if ((cli = get_client(addr))) { - timeo.tv_sec = 0; + if ((cli = get_client(addr, NLM_VERS)) != NULL) { + timeo.tv_sec = 0; /* No timeout - not expecting response */ timeo.tv_usec = 0; - success = clnt_call(cli, NLM_TEST_RES, xdr_nlm_testres, &res, xdr_void, - &dummy, timeo); + + success = clnt_call(cli, NLM_TEST_RES, xdr_nlm_testres, + &result, xdr_void, &dummy, timeo); + if (debug_level > 2) syslog(LOG_DEBUG, "clnt_call returns %d", success); } - return (NULL); + return NULL; } +/* nlm_lock ---------------------------------------------------------------- */ +/* + * Purposes: Establish a lock + * Returns: granted, denied or blocked + * Notes: *** grace period support missing + */ nlm_res * nlm_lock_1_svc(nlm_lockargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lockargs arg4; + nlmtonlm4(&arg->alock, &arg4.alock); + arg4.cookie = arg->cookie; + arg4.block = arg->block; + arg4.exclusive = arg->exclusive; + arg4.reclaim = arg->reclaim; + arg4.state = arg->state; if (debug_level) log_from_addr("nlm_lock", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - return (&res); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + + result.stat.stat = getlock(&arg4, rqstp, LOCK_MON); + return &result; } void * nlm_lock_msg_1_svc(nlm_lockargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lockargs arg4; + + nlmtonlm4(&arg->alock, &arg4.alock); + arg4.cookie = arg->cookie; + arg4.block = arg->block; + arg4.exclusive = arg->exclusive; + arg4.reclaim = arg->reclaim; + arg4.state = arg->state; if (debug_level) log_from_addr("nlm_lock_msg", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - transmit_result(NLM_LOCK_RES, &res, rqstp); - return (NULL); + + result.cookie = arg->cookie; + result.stat.stat = getlock(&arg4, rqstp, LOCK_ASYNC | LOCK_MON); + transmit_result(NLM_LOCK_RES, &result, svc_getcaller(rqstp->rq_xprt)); + + return NULL; } +/* nlm_cancel -------------------------------------------------------------- */ +/* + * Purpose: Cancel a blocked lock request + * Returns: granted or denied + * Notes: + */ nlm_res * nlm_cancel_1_svc(nlm_cancargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lock arg4; + + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_cancel", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_denied; - return (&res); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + + /* + * Since at present we never return 'nlm_blocked', there can never be + * a lock to cancel, so this call always fails. + */ + result.stat.stat = unlock(&arg4, LOCK_CANCEL); + return &result; } void * nlm_cancel_msg_1_svc(nlm_cancargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lock arg4; + + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_cancel_msg", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_denied; - transmit_result(NLM_CANCEL_RES, &res, rqstp); - return (NULL); + + result.cookie = arg->cookie; + /* + * Since at present we never return 'nlm_blocked', there can never be + * a lock to cancel, so this call always fails. + */ + result.stat.stat = unlock(&arg4, LOCK_CANCEL); + transmit_result(NLM_CANCEL_RES, &result, svc_getcaller(rqstp->rq_xprt)); + return NULL; } +/* nlm_unlock -------------------------------------------------------------- */ +/* + * Purpose: Release an existing lock + * Returns: Always granted, unless during grace period + * Notes: "no such lock" error condition is ignored, as the + * protocol uses unreliable UDP datagrams, and may well + * re-try an unlock that has already succeeded. + */ nlm_res * nlm_unlock_1_svc(nlm_unlockargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lock arg4; + + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_unlock", rqstp); - res.stat.stat = nlm_granted; - res.cookie = arg->cookie; - return (&res); + + result.stat.stat = unlock(&arg4, 0); + result.cookie = arg->cookie; + + return &result; } void * nlm_unlock_msg_1_svc(nlm_unlockargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; + struct nlm4_lock arg4; + + nlmtonlm4(&arg->alock, &arg4); if (debug_level) log_from_addr("nlm_unlock_msg", rqstp); - res.stat.stat = nlm_granted; - res.cookie = arg->cookie; - transmit_result(NLM_UNLOCK_RES, &res, rqstp); - return (NULL); + + result.stat.stat = unlock(&arg4, 0); + result.cookie = arg->cookie; + + transmit_result(NLM_UNLOCK_RES, &result, svc_getcaller(rqstp->rq_xprt)); + return NULL; } +/* ------------------------------------------------------------------------- */ +/* + * Client-side pseudo-RPCs for results. Note that for the client there + * are only nlm_xxx_msg() versions of each call, since the 'real RPC' + * version returns the results in the RPC result, and so the client + * does not normally receive incoming RPCs. + * + * The exception to this is nlm_granted(), which is genuinely an RPC + * call from the server to the client - a 'call-back' in normal procedure + * call terms. + */ + +/* nlm_granted ------------------------------------------------------------- */ +/* + * Purpose: Receive notification that formerly blocked lock now granted + * Returns: always success ('granted') + * Notes: + */ nlm_res * nlm_granted_1_svc(nlm_testargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; if (debug_level) log_from_addr("nlm_granted", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - return (&res); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + + result.stat.stat = nlm_granted; + return &result; } void * nlm_granted_msg_1_svc(nlm_testargs *arg, struct svc_req *rqstp) { - nlm_res res; + static nlm_res result; if (debug_level) log_from_addr("nlm_granted_msg", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - transmit_result(NLM_GRANTED_RES, &res, rqstp); - return (NULL); + + result.cookie = arg->cookie; + result.stat.stat = nlm_granted; + transmit_result(NLM_GRANTED_RES, &result, svc_getcaller(rqstp->rq_xprt)); + return NULL; } +/* nlm_test_res ------------------------------------------------------------ */ +/* + * Purpose: Accept result from earlier nlm_test_msg() call + * Returns: Nothing + */ void * +/*ARGSUSED*/ nlm_test_res_1_svc(nlm_testres *arg, struct svc_req *rqstp) { if (debug_level) log_from_addr("nlm_test_res", rqstp); - return (NULL); + return NULL; } +/* nlm_lock_res ------------------------------------------------------------ */ +/* + * Purpose: Accept result from earlier nlm_lock_msg() call + * Returns: Nothing + */ void * +/*ARGSUSED*/ nlm_lock_res_1_svc(nlm_res *arg, struct svc_req *rqstp) { if (debug_level) log_from_addr("nlm_lock_res", rqstp); - return (NULL); + return NULL; } +/* nlm_cancel_res ---------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_cancel_msg() call + * Returns: Nothing + */ void * +/*ARGSUSED*/ nlm_cancel_res_1_svc(nlm_res *arg, struct svc_req *rqstp) { if (debug_level) log_from_addr("nlm_cancel_res", rqstp); - return (NULL); + return NULL; } +/* nlm_unlock_res ---------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_unlock_msg() call + * Returns: Nothing + */ void * +/*ARGSUSED*/ nlm_unlock_res_1_svc(nlm_res *arg, struct svc_req *rqstp) { if (debug_level) log_from_addr("nlm_unlock_res", rqstp); - return (NULL); + return NULL; } +/* nlm_granted_res --------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_granted_msg() call + * Returns: Nothing + */ void * +/*ARGSUSED*/ nlm_granted_res_1_svc(nlm_res *arg, struct svc_req *rqstp) { if (debug_level) log_from_addr("nlm_granted_res", rqstp); - return (NULL); + return NULL; } +/* ------------------------------------------------------------------------- */ +/* + * Calls for PCNFS locking (aka non-monitored locking, no involvement + * of rpc.statd). + * + * These are all genuine RPCs - no nlm_xxx_msg() nonsense here. + */ + +/* nlm_share --------------------------------------------------------------- */ +/* + * Purpose: Establish a DOS-style lock + * Returns: success or failure + * Notes: Blocking locks are not supported - client is expected + * to retry if required. + */ nlm_shareres * nlm_share_3_svc(nlm_shareargs *arg, struct svc_req *rqstp) { - static nlm_shareres res; + static nlm_shareres result; if (debug_level) log_from_addr("nlm_share", rqstp); - res.cookie = arg->cookie; - res.stat = nlm_granted; - res.sequence = 1234356; /* X/Open says this field is ignored? */ - return (&res); + + result.cookie = arg->cookie; + result.stat = nlm_granted; + result.sequence = 1234356; /* X/Open says this field is ignored? */ + return &result; } +/* nlm_unshare ------------------------------------------------------------ */ +/* + * Purpose: Release a DOS-style lock + * Returns: nlm_granted, unless in grace period + * Notes: + */ nlm_shareres * nlm_unshare_3_svc(nlm_shareargs *arg, struct svc_req *rqstp) { - static nlm_shareres res; + static nlm_shareres result; if (debug_level) log_from_addr("nlm_unshare", rqstp); - res.cookie = arg->cookie; - res.stat = nlm_granted; - res.sequence = 1234356; /* X/Open says this field is ignored? */ - return (&res); + + result.cookie = arg->cookie; + result.stat = nlm_granted; + result.sequence = 1234356; /* X/Open says this field is ignored? */ + return &result; } +/* nlm_nm_lock ------------------------------------------------------------ */ +/* + * Purpose: non-monitored version of nlm_lock() + * Returns: as for nlm_lock() + * Notes: These locks are in the same style as the standard nlm_lock, + * but the rpc.statd should not be called to establish a + * monitor for the client machine, since that machine is + * declared not to be running a rpc.statd, and so would not + * respond to the statd protocol. + */ nlm_res * nlm_nm_lock_3_svc(nlm_lockargs *arg, struct svc_req *rqstp) { - static nlm_res res; + static nlm_res result; if (debug_level) log_from_addr("nlm_nm_lock", rqstp); - res.cookie = arg->cookie; - res.stat.stat = nlm_granted; - return (&res); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + result.stat.stat = nlm_granted; + return &result; } +/* nlm_free_all ------------------------------------------------------------ */ +/* + * Purpose: Release all locks held by a named client + * Returns: Nothing + * Notes: Potential denial of service security problem here - the + * locks to be released are specified by a host name, independent + * of the address from which the request has arrived. + * Should probably be rejected if the named host has been + * using monitored locks. + */ void * +/*ARGSUSED*/ nlm_free_all_3_svc(nlm_notify *arg, struct svc_req *rqstp) { static char dummy; if (debug_level) log_from_addr("nlm_free_all", rqstp); - return (&dummy); + return &dummy; +} + +/* calls for nlm version 4 (NFSv3) */ +/* nlm_test ---------------------------------------------------------------- */ +/* + * Purpose: Test whether a specified lock would be granted if requested + * Returns: nlm_granted (or error code) + * Notes: + */ +nlm4_testres * +nlm4_test_4_svc(nlm4_testargs *arg, struct svc_req *rqstp) +{ + static nlm4_testres result; + struct nlm4_holder *holder; + + if (debug_level) + log_from_addr("nlm4_test", rqstp); + + holder = testlock(&arg->alock, LOCK_V4); + + /* + * Copy the cookie from the argument into the result. Note that this + * is slightly hazardous, as the structure contains a pointer to a + * malloc()ed buffer that will get freed by the caller. However, the + * main function transmits the result before freeing the argument + * so it is in fact safe. + */ + result.cookie = arg->cookie; + if (holder == NULL) { + result.stat.stat = nlm4_granted; + } else { + result.stat.stat = nlm4_denied; + memcpy(&result.stat.nlm4_testrply_u.holder, holder, + sizeof(struct nlm4_holder)); + } + return &result; +} + +void * +nlm4_test_msg_4_svc(nlm4_testargs *arg, struct svc_req *rqstp) +{ + nlm4_testres result; + static char dummy; + struct sockaddr_in *addr; + CLIENT *cli; + int success; + struct timeval timeo; + struct nlm4_holder *holder; + + if (debug_level) + log_from_addr("nlm4_test_msg", rqstp); + + holder = testlock(&arg->alock, LOCK_V4); + + result.cookie = arg->cookie; + if (holder == NULL) { + result.stat.stat = nlm4_granted; + } else { + result.stat.stat = nlm4_denied; + memcpy(&result.stat.nlm4_testrply_u.holder, holder, + sizeof(struct nlm4_holder)); + } + + /* + * nlm_test has different result type to the other operations, so + * can't use transmit4_result() in this case + */ + addr = svc_getcaller(rqstp->rq_xprt); + if ((cli = get_client(addr, NLM_VERS4)) != NULL) { + timeo.tv_sec = 0; /* No timeout - not expecting response */ + timeo.tv_usec = 0; + + success = clnt_call(cli, NLM4_TEST_RES, xdr_nlm4_testres, + &result, xdr_void, &dummy, timeo); + + if (debug_level > 2) + syslog(LOG_DEBUG, "clnt_call returns %d", success); + } + return NULL; +} + +/* nlm_lock ---------------------------------------------------------------- */ +/* + * Purposes: Establish a lock + * Returns: granted, denied or blocked + * Notes: *** grace period support missing + */ +nlm4_res * +nlm4_lock_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_lock", rqstp); + + /* copy cookie from arg to result. See comment in nlm_test_4() */ + result.cookie = arg->cookie; + + result.stat.stat = getlock(arg, rqstp, LOCK_MON | LOCK_V4); + return &result; +} + +void * +nlm4_lock_msg_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_lock_msg", rqstp); + + result.cookie = arg->cookie; + result.stat.stat = getlock(arg, rqstp, LOCK_MON | LOCK_ASYNC | LOCK_V4); + transmit4_result(NLM4_LOCK_RES, &result, svc_getcaller(rqstp->rq_xprt)); + + return NULL; +} + +/* nlm_cancel -------------------------------------------------------------- */ +/* + * Purpose: Cancel a blocked lock request + * Returns: granted or denied + * Notes: + */ +nlm4_res * +nlm4_cancel_4_svc(nlm4_cancargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_cancel", rqstp); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + + /* + * Since at present we never return 'nlm_blocked', there can never be + * a lock to cancel, so this call always fails. + */ + result.stat.stat = unlock(&arg->alock, LOCK_CANCEL); + return &result; +} + +void * +nlm4_cancel_msg_4_svc(nlm4_cancargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_cancel_msg", rqstp); + + result.cookie = arg->cookie; + /* + * Since at present we never return 'nlm_blocked', there can never be + * a lock to cancel, so this call always fails. + */ + result.stat.stat = unlock(&arg->alock, LOCK_CANCEL | LOCK_V4); + transmit4_result(NLM4_CANCEL_RES, &result, svc_getcaller(rqstp->rq_xprt)); + + return NULL; +} + +/* nlm_unlock -------------------------------------------------------------- */ +/* + * Purpose: Release an existing lock + * Returns: Always granted, unless during grace period + * Notes: "no such lock" error condition is ignored, as the + * protocol uses unreliable UDP datagrams, and may well + * re-try an unlock that has already succeeded. + */ +nlm4_res * +nlm4_unlock_4_svc(nlm4_unlockargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_unlock", rqstp); + + result.stat.stat = unlock(&arg->alock, LOCK_V4); + result.cookie = arg->cookie; + + return &result; +} + +void * +nlm4_unlock_msg_4_svc(nlm4_unlockargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_unlock_msg", rqstp); + + result.stat.stat = unlock(&arg->alock, LOCK_V4); + result.cookie = arg->cookie; + + transmit4_result(NLM4_UNLOCK_RES, &result, svc_getcaller(rqstp->rq_xprt)); + return NULL; +} + +/* ------------------------------------------------------------------------- */ +/* + * Client-side pseudo-RPCs for results. Note that for the client there + * are only nlm_xxx_msg() versions of each call, since the 'real RPC' + * version returns the results in the RPC result, and so the client + * does not normally receive incoming RPCs. + * + * The exception to this is nlm_granted(), which is genuinely an RPC + * call from the server to the client - a 'call-back' in normal procedure + * call terms. + */ + +/* nlm_granted ------------------------------------------------------------- */ +/* + * Purpose: Receive notification that formerly blocked lock now granted + * Returns: always success ('granted') + * Notes: + */ +nlm4_res * +nlm4_granted_4_svc(nlm4_testargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_granted", rqstp); + + /* copy cookie from arg to result. See comment in nlm_test_1() */ + result.cookie = arg->cookie; + + result.stat.stat = nlm4_granted; + return &result; +} + +void * +nlm4_granted_msg_4_svc(nlm4_testargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_granted_msg", rqstp); + + result.cookie = arg->cookie; + result.stat.stat = nlm4_granted; + transmit4_result(NLM4_GRANTED_RES, &result, svc_getcaller(rqstp->rq_xprt)); + return NULL; +} + +/* nlm_test_res ------------------------------------------------------------ */ +/* + * Purpose: Accept result from earlier nlm_test_msg() call + * Returns: Nothing + */ +void * +/*ARGSUSED*/ +nlm4_test_res_4_svc(nlm4_testres *arg, struct svc_req *rqstp) +{ + if (debug_level) + log_from_addr("nlm4_test_res", rqstp); + return NULL; +} + +/* nlm_lock_res ------------------------------------------------------------ */ +/* + * Purpose: Accept result from earlier nlm_lock_msg() call + * Returns: Nothing + */ +void * +/*ARGSUSED*/ +nlm4_lock_res_4_svc(nlm4_res *arg, struct svc_req *rqstp) +{ + if (debug_level) + log_from_addr("nlm4_lock_res", rqstp); + + return NULL; +} + +/* nlm_cancel_res ---------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_cancel_msg() call + * Returns: Nothing + */ +void * +/*ARGSUSED*/ +nlm4_cancel_res_4_svc(nlm4_res *arg, struct svc_req *rqstp) +{ + if (debug_level) + log_from_addr("nlm4_cancel_res", rqstp); + return NULL; +} + +/* nlm_unlock_res ---------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_unlock_msg() call + * Returns: Nothing + */ +void * +/*ARGSUSED*/ +nlm4_unlock_res_4_svc(nlm4_res *arg, struct svc_req *rqstp) +{ + if (debug_level) + log_from_addr("nlm4_unlock_res", rqstp); + return NULL; +} + +/* nlm_granted_res --------------------------------------------------------- */ +/* + * Purpose: Accept result from earlier nlm_granted_msg() call + * Returns: Nothing + */ +void * +/*ARGSUSED*/ +nlm4_granted_res_4_svc(nlm4_res *arg, struct svc_req *rqstp) +{ + if (debug_level) + log_from_addr("nlm4_granted_res", rqstp); + return NULL; +} + +/* ------------------------------------------------------------------------- */ +/* + * Calls for PCNFS locking (aka non-monitored locking, no involvement + * of rpc.statd). + * + * These are all genuine RPCs - no nlm_xxx_msg() nonsense here. + */ + +/* nlm_share --------------------------------------------------------------- */ +/* + * Purpose: Establish a DOS-style lock + * Returns: success or failure + * Notes: Blocking locks are not supported - client is expected + * to retry if required. + */ +nlm4_shareres * +nlm4_share_4_svc(nlm4_shareargs *arg, struct svc_req *rqstp) +{ + static nlm4_shareres result; + + if (debug_level) + log_from_addr("nlm4_share", rqstp); + + result.cookie = arg->cookie; + result.stat = nlm4_granted; + result.sequence = 1234356; /* X/Open says this field is ignored? */ + return &result; +} + +/* nlm4_unshare ------------------------------------------------------------ */ +/* + * Purpose: Release a DOS-style lock + * Returns: nlm_granted, unless in grace period + * Notes: + */ +nlm4_shareres * +nlm4_unshare_4_svc(nlm4_shareargs *arg, struct svc_req *rqstp) +{ + static nlm4_shareres result; + + if (debug_level) + log_from_addr("nlm_unshare", rqstp); + + result.cookie = arg->cookie; + result.stat = nlm4_granted; + result.sequence = 1234356; /* X/Open says this field is ignored? */ + return &result; +} + +/* nlm4_nm_lock ------------------------------------------------------------ */ +/* + * Purpose: non-monitored version of nlm4_lock() + * Returns: as for nlm4_lock() + * Notes: These locks are in the same style as the standard nlm4_lock, + * but the rpc.statd should not be called to establish a + * monitor for the client machine, since that machine is + * declared not to be running a rpc.statd, and so would not + * respond to the statd protocol. + */ +nlm4_res * +nlm4_nm_lock_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp) +{ + static nlm4_res result; + + if (debug_level) + log_from_addr("nlm4_nm_lock", rqstp); + + /* copy cookie from arg to result. See comment in nlm4_test_1() */ + result.cookie = arg->cookie; + result.stat.stat = nlm4_granted; + return &result; +} + +/* nlm4_free_all ------------------------------------------------------------ */ +/* + * Purpose: Release all locks held by a named client + * Returns: Nothing + * Notes: Potential denial of service security problem here - the + * locks to be released are specified by a host name, independent + * of the address from which the request has arrived. + * Should probably be rejected if the named host has been + * using monitored locks. + */ +void * +/*ARGSUSED*/ +nlm4_free_all_4_svc(nlm_notify *arg, struct svc_req *rqstp) +{ + static char dummy; + + if (debug_level) + log_from_addr("nlm4_free_all", rqstp); + return &dummy; } diff --git a/usr.sbin/rpc.lockd/rpc.lockd.8 b/usr.sbin/rpc.lockd/rpc.lockd.8 index b750fb0f9c7..642c6c90cab 100644 --- a/usr.sbin/rpc.lockd/rpc.lockd.8 +++ b/usr.sbin/rpc.lockd/rpc.lockd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: rpc.lockd.8,v 1.11 2007/05/31 19:20:28 jmc Exp $ +.\" $OpenBSD: rpc.lockd.8,v 1.12 2008/06/13 21:32:26 sturm Exp $ .\" .\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk .\" All rights reserved. @@ -32,7 +32,7 @@ .\" SUCH DAMAGE. .\" .\" -.Dd $Mdocdate: May 31 2007 $ +.Dd $Mdocdate: June 13 2008 $ .Dt RPC.LOCKD 8 .Os .Sh NAME @@ -41,6 +41,7 @@ .Sh SYNOPSIS .Nm rpc.lockd .Op Fl d Op Ar debug_level +.Op Fl g Op Ar grace period .Sh DESCRIPTION .Nm rpc.lockd is a daemon which provides file- and record-locking services in an NFS @@ -61,6 +62,15 @@ is not specified, level 1 is assumed, giving one log line per protocol operation. Higher debug levels can be specified, causing display of operation arguments and internal operations of the daemon. +.It Fl g +The +.Fl g +option allows to specify the grace period, in seconds. +During the grace period +.Nm +only accepts requests from hosts which are reinitialising locks which +existed before the server restart. +Default is 30 seconds. .El .Pp Error conditions are logged to syslog, irrespective of the debug level, @@ -97,8 +107,4 @@ but there is currently no means for an .Ox client to establish locks). .Pp -Versions 1, 2 and 3 of the protocol are supported. -However, only versions -2 (Unix systems) and 3 (PC-NFS clients) seem to be in common use - the version -1 support has not been tested due to the lack of version 1 clients against -which to test. +The current implementation serialises lock requests that could be shared. |