root/usr.sbin/rpc.lockd/lockd_lock.c
/*
 * 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. Neither the name of the University nor the names of its contributors
 *    may 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.
 *
 * $NetBSD: lockd_lock.c,v 1.5 2000/11/21 03:47:41 enami Exp $
 * $FreeBSD: src/usr.sbin/rpc.lockd/lockd_lock.c,v 1.1 2001/03/19 12:50:09 alfred Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <rpc/rpc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/queue.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);

/* struct describing a lock */
struct file_lock {
        LIST_ENTRY(file_lock) lcklst;
        fhandle_t filehandle; /* NFS filehandle */
        struct sockaddr *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 */

void            lfree(struct file_lock *);
void            sigchild_handler(int);
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);

/* list of hosts we monitor */
LIST_HEAD(hostlst_head, host);
struct hostlst_head hostlst_head = LIST_HEAD_INITIALIZER(hostlst_head);

/* struct describing a lock */
struct host {
        LIST_ENTRY(host) hostlst;
        char name[SM_MAXSTRLEN];
        int refcnt;
};

void    do_mon(char *);

/*
 * 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 *
testlock(struct nlm4_lock *lock, int flags)
{
        struct file_lock *fl;
        fhandle_t filehandle;

        /* convert lock to a local filehandle */
        memcpy(&filehandle, lock->fh.n_bytes, sizeof(filehandle));

        siglock();
        /* search through the list for lock holder */
        for (fl = LIST_FIRST(&lcklst_head); fl != NULL;
            fl = LIST_NEXT(fl, lcklst)) {
                if (fl->status != LKST_LOCKED)
                        continue;
                if (memcmp(&fl->filehandle, &filehandle, sizeof(filehandle)))
                        continue;
                /* got it ! */
                syslog(LOG_DEBUG, "test for %s: found lock held by %s",
                    lock->caller_name, fl->client_name);
                sigunlock();
                return (&fl->client);
        }
        /* not found */
        sigunlock();
        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;

        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 = malloc(sizeof(struct file_lock));
        if (newfl == NULL) {
                syslog(LOG_NOTICE, "malloc failed: %s", strerror(errno));
                /* failed */
                return (flags & LOCK_V4) ?
                    nlm4_denied_nolock : nlm_denied_nolocks;
        }
        if (lckarg->alock.fh.n_len != sizeof(fhandle_t)) {
                syslog(LOG_DEBUG, "received fhandle size %d, local size %d",
                    lckarg->alock.fh.n_len, (int)sizeof(fhandle_t));
        }
        memcpy(&newfl->filehandle, lckarg->alock.fh.n_bytes, sizeof(fhandle_t));
        newfl->addr = (struct sockaddr *)svc_getrpccaller(rqstp->rq_xprt)->buf;
        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: %s", strerror(errno));
                free(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: %s", strerror(errno));
                free(newfl->client.oh.n_bytes);
                free(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);
        strncpy(newfl->client_name, lckarg->alock.caller_name, 128);
        newfl->nsm_status = lckarg->state;
        newfl->status = 0;
        newfl->flags = flags;
        siglock();
        /* look for a lock rq from this host for this fh */
        for (fl = LIST_FIRST(&lcklst_head); fl != NULL;
            fl = LIST_NEXT(fl, lcklst)) {
                if (memcmp(&newfl->filehandle, &fl->filehandle,
                    sizeof(fhandle_t)) == 0) {
                        if (strcmp(newfl->client_name, fl->client_name) == 0 &&
                            newfl->client.svid == fl->client.svid) {
                                /* already locked by this host ??? */
                                sigunlock();
                                syslog(LOG_NOTICE, "duplicate lock from %s",
                                    newfl->client_name);
                                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;
                                }
                        }
                        /*
                         * 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: already "
                                    "locked, waiting",
                                    lckarg->alock.caller_name);
                                newfl->status = LKST_WAITING;
                                LIST_INSERT_HEAD(&lcklst_head, newfl, lcklst);
                                do_mon(lckarg->alock.caller_name);
                                sigunlock();
                                return (flags & LOCK_V4) ?
                                    nlm4_blocked : nlm_blocked;
                        } else {
                                sigunlock();
                                syslog(LOG_DEBUG, "lock from %s: already "
                                    "locked, failed",
                                    lckarg->alock.caller_name);
                                lfree(newfl);
                                return (flags & LOCK_V4) ?
                                    nlm4_denied : nlm_denied;
                        }
                }
        }
        /* 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 */
                do_mon(lckarg->alock.caller_name);
                break;
        default:
                lfree(newfl);
                break;
        }
        sigunlock();
        return retval;
}

/* unlock a filehandle */
enum nlm_stats
unlock(nlm4_lock *lck, int flags)
{
        struct file_lock *fl;
        fhandle_t filehandle;
        int err = (flags & LOCK_V4) ? nlm4_granted : nlm_granted;

        memcpy(&filehandle, lck->fh.n_bytes, sizeof(fhandle_t));
        siglock();
        for (fl = LIST_FIRST(&lcklst_head); fl != NULL;
            fl = LIST_NEXT(fl, lcklst)) {
                if (strcmp(fl->client_name, lck->caller_name) ||
                    memcmp(&filehandle, &fl->filehandle, sizeof(fhandle_t)) ||
                    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: found struct, status %d",
                    lck->caller_name, 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, "unknown status %d for %s",
                            fl->status, fl->client_name);
                }
                sigunlock();
                return err;
        }
        sigunlock();
        /* didn't find a matching entry; log anyway */
        syslog(LOG_NOTICE, "no matching entry for %s",
            lck->caller_name);
        return (flags & LOCK_V4) ? nlm4_granted : nlm_granted;
}

void
lfree(struct file_lock *fl)
{
        free(fl->client.oh.n_bytes);
        free(fl->client_cookie.n_bytes);
        free(fl);
}

void
sigchild_handler(int sig)
{
        int status;
        pid_t pid;
        struct file_lock *fl;

        while (1) {
                pid = wait4(-1, &status, WNOHANG, NULL);
                if (pid == -1) {
                        if (errno != ECHILD)
                                syslog(LOG_NOTICE, "wait failed: %s",
                                    strerror(errno));
                        else
                                syslog(LOG_DEBUG, "wait failed: %s",
                                    strerror(errno));
                        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.
                 */
                for (fl = LIST_FIRST(&lcklst_head); fl != NULL;
                    fl = LIST_NEXT(fl, lcklst)) {
                        if (pid == fl->locker)
                                break;
                }
                if (pid != fl->locker) {
                        syslog(LOG_NOTICE, "unknown child %d", pid);
                } else {
                        if (!WIFEXITED(status) || WEXITSTATUS(status) != 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(&fl->filehandle, 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): %s",
                    fl->client_name, strerror(errno));
                LIST_REMOVE(fl, lcklst);
                return error;
        }
        if (fstat(fl->fd, &st) < 0) {
                syslog(LOG_NOTICE, "fstat failed (from %s): %s",
                    fl->client_name, strerror(errno));
        }
        syslog(LOG_DEBUG, "lock from %s for file%s%s: dev %d ino %ju (uid %d), "
            "flags %d",
            fl->client_name, fl->client.exclusive ? " (exclusive)":"",
            block ? " (block)":"",
            st.st_dev, (uintmax_t)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: %s", strerror(errno));
                        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", fl->client_name);
                        lflags &= ~LOCK_NB;
                        if(flock(fl->fd, lflags) != 0) {
                                syslog(LOG_NOTICE, "flock failed: %s",
                                    strerror(errno));
                                exit(-1);
                        }
                        /* lock granted */
                        exit(0);
                default:
                        syslog(LOG_DEBUG, "lock request from %s: forked %d",
                            fl->client_name, 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: %s",
                            fl->client_name, strerror(errno));
                else syslog(LOG_DEBUG, "flock for %s failed: %s",
                            fl->client_name, strerror(errno));
                LIST_REMOVE(fl, lcklst);
                close(fl->fd);
                return error;
        }
        fl->status = LKST_LOCKED;
        return (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
}

void
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",
                    fl->client_name);
                /*
                 * 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 res;
                res.cookie = fl->client_cookie;
                res.exclusive = fl->client.exclusive;
                res.alock.caller_name = fl->client_name;
                res.alock.fh.n_len = sizeof(fhandle_t);
                res.alock.fh.n_bytes = (char*)&fl->filehandle;
                res.alock.oh = fl->client.oh;
                res.alock.svid = fl->client.svid;
                res.alock.l_offset = fl->client.l_offset;
                res.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,
                            (xdrproc_t)xdr_nlm4_testargs, &res,
                            (xdrproc_t)xdr_void, &dummy, timeo);
                } else {
                        success = clnt_call(cli, NLM4_GRANTED,
                            (xdrproc_t)xdr_nlm4_testargs, &res,
                            (xdrproc_t)xdr_nlm4_res, &retval4, timeo);
                }
        } else {
                static nlm_testargs res;

                res.cookie = fl->client_cookie;
                res.exclusive = fl->client.exclusive;
                res.alock.caller_name = fl->client_name;
                res.alock.fh.n_len = sizeof(fhandle_t);
                res.alock.fh.n_bytes = (char*)&fl->filehandle;
                res.alock.oh = fl->client.oh;
                res.alock.svid = fl->client.svid;
                res.alock.l_offset = fl->client.l_offset;
                res.alock.l_len = 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,
                            (xdrproc_t)xdr_nlm_testargs, &res,
                            (xdrproc_t)xdr_void, &dummy, timeo);
                } else {
                        success = clnt_call(cli, NLM_GRANTED,
                            (xdrproc_t)xdr_nlm_testargs, &res,
                            (xdrproc_t)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) < 0) {
                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): %s",
                    rfl->client_name, strerror(errno));
        } else {
                error = (rfl->flags & LOCK_V4) ?
                    nlm4_granted : nlm_granted;
        }
        LIST_REMOVE(rfl, lcklst);

        /* process the next LKST_WAITING lock request for this fh */
        for (fl = LIST_FIRST(&lcklst_head); fl != NULL;
             fl = LIST_NEXT(fl, lcklst)) {
                if (fl->status != LKST_WAITING ||
                    memcmp(&rfl->filehandle, &fl->filehandle,
                    sizeof(fhandle_t)) != 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;
        }
        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: %s", strerror(errno));
        }
}

void
sigunlock(void)
{
        sigset_t block;

        sigemptyset(&block);
        sigaddset(&block, SIGCHLD);

        if (sigprocmask(SIG_UNBLOCK, &block, NULL) < 0) {
                syslog(LOG_WARNING, "sigunlock failed: %s", strerror(errno));
        }
}

/* monitor a host through rpc.statd, and keep a ref count */
void
do_mon(char *hostname)
{
        struct host *hp;
        struct mon my_mon;
        struct sm_stat_res res;
        int retval;

        for (hp = LIST_FIRST(&hostlst_head); hp != NULL;
            hp = LIST_NEXT(hp, hostlst)) {
                if (strcmp(hostname, hp->name) == 0) {
                        /* already monitored, just bump refcnt */
                        hp->refcnt++;
                        return;
                }
        }
        /* not found, have to create an entry for it */
        hp = malloc(sizeof(struct host));
        strncpy(hp->name, hostname, SM_MAXSTRLEN);
        hp->refcnt = 1;
        syslog(LOG_DEBUG, "monitoring host %s",
            hostname);
        memset(&my_mon, 0, sizeof(my_mon));
        my_mon.mon_id.mon_name = hp->name;
        my_mon.mon_id.my_id.my_name = "localhost";
        my_mon.mon_id.my_id.my_prog = NLM_PROG;
        my_mon.mon_id.my_id.my_vers = NLM_SM;
        my_mon.mon_id.my_id.my_proc = NLM_SM_NOTIFY;
        if ((retval =
            callrpc("localhost", SM_PROG, SM_VERS, SM_MON, (xdrproc_t)xdr_mon,
            (char*)&my_mon, (xdrproc_t)xdr_sm_stat_res, (char*)&res)) != 0) {
                syslog(LOG_WARNING, "rpc to statd failed: %s",
                    clnt_sperrno((enum clnt_stat)retval));
                free(hp);
                return;
        }
        if (res.res_stat == stat_fail) {
                syslog(LOG_WARNING, "statd failed");
                free(hp);
                return;
        }
        LIST_INSERT_HEAD(&hostlst_head, hp, hostlst);
}

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, "unknown status %d for %s",
                                    fl->status, fl->client_name);
                        }
                }
        }
        sigunlock();
}