summaryrefslogtreecommitdiffstats
path: root/utils/statd/sm-notify.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/statd/sm-notify.c')
-rw-r--r--utils/statd/sm-notify.c928
1 files changed, 928 insertions, 0 deletions
diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c
new file mode 100644
index 0000000..ed82b8f
--- /dev/null
+++ b/utils/statd/sm-notify.c
@@ -0,0 +1,928 @@
+/*
+ * Send NSM notify calls to all hosts listed in /var/lib/sm
+ *
+ * Copyright (C) 2004-2006 Olaf Kirch <okir@suse.de>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <err.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <poll.h>
+#include <sys/param.h>
+#include <sys/syslog.h>
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <time.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <errno.h>
+#include <grp.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+#include "conffile.h"
+#include "sockaddr.h"
+#include "xlog.h"
+#include "nsm.h"
+#include "nfslib.h"
+#include "nfsrpc.h"
+
+/* glibc before 2.3.4 */
+#ifndef AI_NUMERICSERV
+#define AI_NUMERICSERV 0
+#endif
+
+#define NSM_TIMEOUT 2
+#define NSM_MAX_TIMEOUT 120 /* don't make this too big */
+
+#define NLM_END_GRACE_FILE "/proc/fs/lockd/nlm_end_grace"
+
+int lift_grace = 1;
+int force = 0;
+
+struct nsm_host {
+ struct nsm_host * next;
+ char * name;
+ const char * mon_name;
+ const char * my_name;
+ char * notify_arg;
+ struct addrinfo *ai;
+ time_t last_used;
+ time_t send_next;
+ unsigned int timeout;
+ unsigned int retries;
+ uint32_t xid;
+};
+
+static char nsm_hostname[SM_MAXSTRLEN + 1];
+static int nsm_state;
+static int nsm_family = AF_INET;
+static int opt_debug = 0;
+static _Bool opt_update_state = true;
+static unsigned int opt_max_retry = 15 * 60;
+static char * opt_srcaddr = NULL;
+static char * opt_srcport = NULL;
+
+static void notify(const int sock);
+static int notify_host(int, struct nsm_host *);
+static void recv_reply(int);
+static void insert_host(struct nsm_host *);
+static struct nsm_host *find_host(uint32_t);
+static int record_pid(void);
+
+static struct nsm_host * hosts = NULL;
+
+__attribute__((__malloc__))
+static struct addrinfo *
+smn_lookup(const char *name)
+{
+ struct addrinfo *ai = NULL;
+ struct addrinfo hint = {
+ .ai_family = (nsm_family == AF_INET ? AF_INET: AF_UNSPEC),
+ .ai_protocol = (int)IPPROTO_UDP,
+ };
+ int error;
+
+ res_init();
+ error = getaddrinfo(name, NULL, &hint, &ai);
+ if (error != 0) {
+ xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error));
+ return NULL;
+ }
+
+ return ai;
+}
+
+#ifdef HAVE_GETNAMEINFO
+static char *
+smn_get_hostname(const struct sockaddr *sap, const socklen_t salen,
+ const char *name)
+{
+ char buf[NI_MAXHOST];
+ int error;
+
+ error = getnameinfo(sap, salen, buf, sizeof(buf), NULL, 0, NI_NAMEREQD);
+ if (error != 0) {
+ xlog(L_ERROR, "my_name '%s' is unusable: %s",
+ name, gai_strerror(error));
+ return NULL;
+ }
+ return strdup(buf);
+}
+#else /* !HAVE_GETNAMEINFO */
+static char *
+smn_get_hostname(const struct sockaddr *sap,
+ __attribute__ ((unused)) const socklen_t salen,
+ const char *name)
+{
+ const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap;
+ const struct in_addr *addr = &sin->sin_addr;
+ struct hostent *hp;
+
+ if (sap->sa_family != AF_INET) {
+ xlog(L_ERROR, "my_name '%s' is unusable: Bad address family",
+ name);
+ return NULL;
+ }
+
+ hp = gethostbyaddr(addr, (socklen_t)sizeof(addr), AF_INET);
+ if (hp == NULL) {
+ xlog(L_ERROR, "my_name '%s' is unusable: %s",
+ name, hstrerror(h_errno));
+ return NULL;
+ }
+ return strdup(hp->h_name);
+}
+#endif /* !HAVE_GETNAMEINFO */
+
+/*
+ * Presentation addresses are converted to their canonical hostnames.
+ * If the IP address does not map to a hostname, it is an error:
+ * we never send a presentation address as the argument of SM_NOTIFY.
+ *
+ * If "name" is not a presentation address, it is left alone. This
+ * allows the administrator some flexibility if DNS isn't configured
+ * exactly how sm-notify prefers it.
+ *
+ * Returns NUL-terminated C string containing the result, or NULL
+ * if the canonical name doesn't exist or cannot be determined.
+ * The caller must free the result with free(3).
+ */
+__attribute__((__malloc__))
+static char *
+smn_verify_my_name(const char *name)
+{
+ struct addrinfo *ai = NULL;
+ struct addrinfo hint = {
+#ifdef IPV6_SUPPORTED
+ .ai_family = AF_UNSPEC,
+#else /* !IPV6_SUPPORTED */
+ .ai_family = AF_INET,
+#endif /* !IPV6_SUPPORTED */
+ .ai_flags = AI_NUMERICHOST,
+ };
+ char *retval;
+ int error;
+
+ error = getaddrinfo(name, NULL, &hint, &ai);
+ switch (error) {
+ case 0:
+ /* @name was a presentation address */
+ retval = smn_get_hostname(ai->ai_addr, ai->ai_addrlen, name);
+ nfs_freeaddrinfo(ai);
+ if (retval == NULL)
+ return NULL;
+ break;
+ case EAI_NONAME:
+ /* @name was not a presentation address */
+ retval = strdup(name);
+ break;
+ default:
+ xlog(L_ERROR, "my_name '%s' is unusable: %s",
+ name, gai_strerror(error));
+ return NULL;
+ }
+
+ xlog(D_GENERAL, "Canonical name for my_name '%s': %s",
+ name, retval);
+ return retval;
+}
+
+__attribute__((__malloc__))
+static struct nsm_host *
+smn_alloc_host(const char *hostname, const char *mon_name,
+ const char *my_name, const time_t timestamp)
+{
+ struct nsm_host *host;
+
+ host = calloc(1, sizeof(*host));
+ if (host == NULL)
+ goto out_nomem;
+
+ /*
+ * mon_name and my_name are preserved so sm-notify can
+ * find the right monitor record to remove when it is
+ * done processing this host.
+ */
+ host->name = strdup(hostname);
+ host->mon_name = (const char *)strdup(mon_name);
+ host->my_name = (const char *)strdup(my_name);
+ host->notify_arg = strdup(opt_srcaddr != NULL ?
+ nsm_hostname : my_name);
+ if (host->name == NULL ||
+ host->mon_name == NULL ||
+ host->my_name == NULL ||
+ host->notify_arg == NULL) {
+ free(host->notify_arg);
+ free((void *)host->my_name);
+ free((void *)host->mon_name);
+ free(host->name);
+ free(host);
+ goto out_nomem;
+ }
+
+ host->last_used = timestamp;
+ host->timeout = NSM_TIMEOUT;
+ host->retries = 100; /* force address retry */
+
+ return host;
+
+out_nomem:
+ xlog_warn("Unable to allocate memory");
+ return NULL;
+}
+
+static void smn_forget_host(struct nsm_host *host)
+{
+ xlog(D_CALL, "Removing %s (%s, %s) from notify list",
+ host->name, host->mon_name, host->my_name);
+
+ nsm_delete_notified_host(host->name, host->mon_name, host->my_name);
+
+ free(host->notify_arg);
+ free((void *)host->my_name);
+ free((void *)host->mon_name);
+ free(host->name);
+ nfs_freeaddrinfo(host->ai);
+
+ free(host);
+}
+
+static unsigned int
+smn_get_host(const char *hostname,
+ __attribute__ ((unused)) const struct sockaddr *sap,
+ const struct mon *m, const time_t timestamp)
+{
+ struct nsm_host *host;
+
+ host = smn_alloc_host(hostname,
+ m->mon_id.mon_name, m->mon_id.my_id.my_name, timestamp);
+ if (host == NULL)
+ return 0;
+
+ insert_host(host);
+ return 1;
+}
+
+#ifdef IPV6_SUPPORTED
+static int smn_socket(void)
+{
+ int sock;
+
+ /*
+ * Use an AF_INET socket if IPv6 is disabled on the
+ * local system.
+ */
+ sock = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ if (errno != EAFNOSUPPORT) {
+ xlog(L_ERROR, "Failed to create RPC socket: %m");
+ return -1;
+ }
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ xlog(L_ERROR, "Failed to create RPC socket: %m");
+ return -1;
+ }
+ } else
+ nsm_family = AF_INET6;
+
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
+ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
+ goto out_close;
+ }
+
+ /*
+ * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4. However,
+ * since sm-notify open-codes all of its RPC support, it can
+ * use a single socket and let the local network stack provide
+ * the correct mapping between address families automatically.
+ * This is the same thing that is done in the kernel.
+ */
+ if (nsm_family == AF_INET6) {
+ const int zero = 0;
+ socklen_t zerolen = (socklen_t)sizeof(zero);
+
+ if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY,
+ (char *)&zero, zerolen) == -1) {
+ xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m");
+ goto out_close;
+ }
+ }
+
+ return sock;
+
+out_close:
+ (void)close(sock);
+ return -1;
+}
+#else /* !IPV6_SUPPORTED */
+static int smn_socket(void)
+{
+ int sock;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ xlog(L_ERROR, "Failed to create RPC socket: %m");
+ return -1;
+ }
+
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
+ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
+ (void)close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+#endif /* !IPV6_SUPPORTED */
+
+/*
+ * If admin specified a source address or srcport, then convert those
+ * to a sockaddr and return it. Otherwise, return an ANYADDR address.
+ */
+__attribute__((__malloc__))
+static struct addrinfo *
+smn_bind_address(const char *srcaddr, const char *srcport)
+{
+ struct addrinfo *ai = NULL;
+ struct addrinfo hint = {
+ .ai_flags = AI_NUMERICSERV | AI_V4MAPPED,
+ .ai_family = nsm_family,
+ .ai_protocol = (int)IPPROTO_UDP,
+ };
+ int error;
+
+ if (srcaddr == NULL)
+ hint.ai_flags |= AI_PASSIVE;
+
+ /* Do not allow "node" and "service" parameters both to be NULL */
+ if (srcport == NULL)
+ error = getaddrinfo(srcaddr, "", &hint, &ai);
+ else
+ error = getaddrinfo(srcaddr, srcport, &hint, &ai);
+ if (error != 0) {
+ xlog(L_ERROR,
+ "Invalid bind address or port for RPC socket: %s",
+ gai_strerror(error));
+ return NULL;
+ }
+
+ return ai;
+}
+
+#ifdef HAVE_LIBTIRPC
+static int
+smn_bindresvport(int sock, struct sockaddr *sap)
+{
+ return bindresvport_sa(sock, sap);
+}
+
+#else /* !HAVE_LIBTIRPC */
+static int
+smn_bindresvport(int sock, struct sockaddr *sap)
+{
+ if (sap->sa_family != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ return bindresvport(sock, (struct sockaddr_in *)(char *)sap);
+}
+#endif /* !HAVE_LIBTIRPC */
+
+/*
+ * Prepare a socket for sending RPC requests
+ *
+ * Returns a bound datagram socket file descriptor, or -1 if
+ * an error occurs.
+ */
+static int
+smn_create_socket(const char *srcaddr, const char *srcport)
+{
+ int sock, retry_cnt = 0;
+ struct addrinfo *ai;
+
+retry:
+ sock = smn_socket();
+ if (sock == -1)
+ return -1;
+
+ ai = smn_bind_address(srcaddr, srcport);
+ if (ai == NULL) {
+ (void)close(sock);
+ return -1;
+ }
+
+ /* Use source port if provided on the command line,
+ * otherwise use bindresvport */
+ if (srcport) {
+ if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+ xlog(L_ERROR, "Failed to bind RPC socket: %m");
+ nfs_freeaddrinfo(ai);
+ (void)close(sock);
+ return -1;
+ }
+ } else {
+ struct servent *se;
+
+ if (smn_bindresvport(sock, ai->ai_addr) == -1) {
+ xlog(L_ERROR,
+ "bindresvport on RPC socket failed: %m");
+ nfs_freeaddrinfo(ai);
+ (void)close(sock);
+ return -1;
+ }
+
+ /* try to avoid known ports */
+ se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp");
+ if (se != NULL && retry_cnt < 100) {
+ retry_cnt++;
+ nfs_freeaddrinfo(ai);
+ (void)close(sock);
+ goto retry;
+ }
+ }
+
+ nfs_freeaddrinfo(ai);
+ return sock;
+}
+
+/* Inform the kernel that it's OK to lift lockd's grace period */
+static void
+nsm_lift_grace_period(void)
+{
+ int fd;
+
+ fd = open(NLM_END_GRACE_FILE, O_WRONLY);
+ if (fd < 0) {
+ /* Don't warn if file isn't present */
+ if (errno != ENOENT)
+ xlog(L_WARNING, "Unable to open %s: %m",
+ NLM_END_GRACE_FILE);
+ return;
+ }
+
+ if (write(fd, "Y", 1) < 0)
+ xlog(L_WARNING, "Unable to write to %s: %m", NLM_END_GRACE_FILE);
+
+ close(fd);
+ return;
+}
+inline static void
+read_smnotify_conf(char **argv)
+{
+ char *s;
+
+ conf_init_file(NFS_CONFFILE);
+ xlog_set_debug("sm-notify");
+ opt_max_retry = conf_get_num("sm-notify", "retry-time", opt_max_retry / 60) * 60;
+ opt_srcport = conf_get_str("sm-notify", "outgoing-port");
+ opt_srcaddr = conf_get_str("sm-notify", "outgoing-addr");
+ lift_grace = conf_get_bool("sm-notify", "lift-grace", lift_grace);
+
+ s = conf_get_str("statd", "state-directory-path");
+ if (s && !nsm_setup_pathnames(argv[0], s))
+ exit(1);
+ opt_update_state = conf_get_bool("sm-notify", "update-state", opt_update_state);
+ force = conf_get_bool("sm-notify", "force", force);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, sock;
+ char * progname;
+
+ progname = strrchr(argv[0], '/');
+ if (progname != NULL)
+ progname++;
+ else
+ progname = argv[0];
+
+ /* Read in config setting */
+ read_smnotify_conf(argv);
+
+ while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) {
+ switch (c) {
+ case 'f':
+ force = 1;
+ break;
+ case 'd':
+ opt_debug++;
+ break;
+ case 'm':
+ opt_max_retry = atoi(optarg) * 60;
+ break;
+ case 'n':
+ opt_update_state = false;
+ break;
+ case 'p':
+ opt_srcport = optarg;
+ break;
+ case 'v':
+ opt_srcaddr = optarg;
+ break;
+ case 'P':
+ if (!nsm_setup_pathnames(argv[0], optarg))
+ exit(1);
+ break;
+
+ default:
+ goto usage;
+ }
+ }
+
+ if (optind < argc) {
+usage: fprintf(stderr,
+ "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
+ " [-P /path/to/state/directory] [-v my_host_name]\n",
+ progname);
+ exit(1);
+ }
+
+ if (opt_debug) {
+ xlog_syslog(0);
+ xlog_stderr(1);
+ xlog_config(D_ALL, 1);
+ } else {
+ xlog_syslog(1);
+ xlog_stderr(0);
+ }
+
+ xlog_open(progname);
+ xlog(L_NOTICE, "Version " VERSION " starting");
+
+ if (nsm_is_default_parentdir()) {
+ if (record_pid() == 0 && force == 0 && opt_update_state) {
+ /* already run, don't try again */
+ xlog(L_NOTICE, "Already notifying clients; Exiting!");
+ exit(0);
+ }
+ }
+
+ if (opt_srcaddr != NULL) {
+ char *name;
+
+ name = smn_verify_my_name(opt_srcaddr);
+ if (name == NULL)
+ exit(1);
+
+ strncpy(nsm_hostname, name, sizeof(nsm_hostname)-1);
+ free(name);
+ }
+
+ (void)nsm_retire_monitored_hosts();
+ if (nsm_load_notify_list(smn_get_host) == 0) {
+ xlog(D_GENERAL, "No hosts to notify; exiting");
+ if (lift_grace)
+ nsm_lift_grace_period();
+ return 0;
+ }
+
+ nsm_state = nsm_get_state(opt_update_state);
+ if (nsm_state == 0)
+ exit(1);
+ nsm_update_kernel_state(nsm_state);
+
+ if (!opt_debug) {
+ xlog(L_NOTICE, "Backgrounding to notify hosts...\n");
+
+ if (daemon(0, 0) < 0) {
+ xlog(L_ERROR, "unable to background: %m");
+ exit(1);
+ }
+
+ close(0);
+ close(1);
+ close(2);
+ }
+
+ sock = smn_create_socket(opt_srcaddr, opt_srcport);
+ if (sock == -1)
+ exit(1);
+
+ if (!nsm_drop_privileges(-1))
+ exit(1);
+
+ notify(sock);
+
+ if (hosts) {
+ struct nsm_host *hp;
+
+ while ((hp = hosts) != 0) {
+ hosts = hp->next;
+ xlog(L_NOTICE, "Unable to notify %s, giving up",
+ hp->name);
+ }
+ exit(1);
+ }
+
+ exit(0);
+}
+
+/*
+ * Notify hosts
+ */
+static void
+notify(const int sock)
+{
+ time_t failtime = 0;
+
+ if (opt_max_retry)
+ failtime = time(NULL) + opt_max_retry;
+
+ while (hosts) {
+ struct pollfd pfd;
+ time_t now = time(NULL);
+ unsigned int sent = 0;
+ struct nsm_host *hp;
+ long wait;
+
+ if (failtime && now >= failtime)
+ break;
+
+ while (hosts && ((wait = hosts->send_next - now) <= 0)) {
+ /* Never send more than 10 packets at once */
+ if (sent++ >= 10)
+ break;
+
+ /* Remove queue head */
+ hp = hosts;
+ hosts = hp->next;
+
+ if (notify_host(sock, hp))
+ continue;
+
+ /* Set the timeout for this call, using an
+ exponential timeout strategy */
+ wait = hp->timeout;
+ if ((hp->timeout <<= 1) > NSM_MAX_TIMEOUT)
+ hp->timeout = NSM_MAX_TIMEOUT;
+ hp->send_next = now + wait;
+ hp->retries++;
+
+ insert_host(hp);
+ }
+ if (hosts == NULL)
+ return;
+
+ xlog(D_GENERAL, "Host %s due in %ld seconds",
+ hosts->name, wait);
+
+ pfd.fd = sock;
+ pfd.events = POLLIN;
+
+ wait *= 1000;
+ if (wait < 100)
+ wait = 100;
+ if (poll(&pfd, 1, wait) != 1)
+ continue;
+
+ recv_reply(sock);
+ }
+}
+
+/*
+ * Send notification to a single host
+ */
+static int
+notify_host(int sock, struct nsm_host *host)
+{
+ struct sockaddr *sap;
+ socklen_t salen;
+
+ if (host->ai == NULL) {
+ host->ai = smn_lookup(host->name);
+ if (host->ai == NULL) {
+ xlog_warn("DNS resolution of %s failed; "
+ "retrying later", host->name);
+ return 0;
+ }
+ }
+
+ /* If we retransmitted 4 times, reset the port to force
+ * a new portmap lookup (in case statd was restarted).
+ * We also rotate through multiple IP addresses at this
+ * point.
+ */
+ if (host->retries >= 4) {
+ /* don't rotate if there is only one addrinfo */
+ if (host->ai->ai_next != NULL) {
+ struct addrinfo *first = host->ai;
+ struct addrinfo **next = &host->ai;
+
+ /* remove the first entry from the list */
+ host->ai = first->ai_next;
+ first->ai_next = NULL;
+ /* find the end of the list */
+ next = &first->ai_next;
+ while ( *next )
+ next = & (*next)->ai_next;
+ /* put first entry at end */
+ *next = first;
+ }
+
+ nfs_set_port(host->ai->ai_addr, 0);
+ host->retries = 0;
+ }
+
+ sap = host->ai->ai_addr;
+ salen = host->ai->ai_addrlen;
+
+ if (nfs_get_port(sap) == 0)
+ host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS);
+ else
+ host->xid = nsm_xmit_notify(sock, sap, salen,
+ SM_PROG, host->notify_arg, nsm_state);
+
+ return 0;
+}
+
+static void
+smn_defer(struct nsm_host *host)
+{
+ host->xid = 0;
+ host->send_next = time(NULL) + NSM_MAX_TIMEOUT;
+ host->timeout = NSM_MAX_TIMEOUT;
+ insert_host(host);
+}
+
+static void
+smn_schedule(struct nsm_host *host)
+{
+ host->retries = 0;
+ host->xid = 0;
+ host->send_next = time(NULL);
+ host->timeout = NSM_TIMEOUT;
+ insert_host(host);
+}
+
+/*
+ * Extract the returned port number and set up the SM_NOTIFY call.
+ */
+static void
+recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr)
+{
+ uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr);
+
+ if (port == 0) {
+ /* No binding for statd... */
+ xlog(D_GENERAL, "No statd on host %s", host->name);
+ smn_defer(host);
+ } else {
+ xlog(D_GENERAL, "Processing rpcbind reply for %s (port %u)",
+ host->name, port);
+ nfs_set_port(sap, port);
+ smn_schedule(host);
+ }
+}
+
+/*
+ * Successful NOTIFY call. Server returns void.
+ *
+ * Try sending another SM_NOTIFY with an unqualified "my_name"
+ * argument. Reuse the port number. If "my_name" is already
+ * unqualified, we're done.
+ */
+static void
+recv_notify_reply(struct nsm_host *host)
+{
+ char *dot = strchr(host->notify_arg, '.');
+
+ if (dot != NULL) {
+ *dot = '\0';
+ smn_schedule(host);
+ } else {
+ xlog(D_GENERAL, "Host %s notified successfully", host->name);
+ smn_forget_host(host);
+ }
+}
+
+/*
+ * Receive reply from remote host
+ */
+static void
+recv_reply(int sock)
+{
+ struct nsm_host *hp;
+ struct sockaddr *sap;
+ char msgbuf[NSM_MAXMSGSIZE];
+ uint32_t xid;
+ ssize_t msglen;
+ XDR xdr;
+
+ memset(msgbuf, 0 , sizeof(msgbuf));
+ msglen = recv(sock, msgbuf, sizeof(msgbuf), 0);
+ if (msglen < 0)
+ return;
+
+ xlog(D_GENERAL, "Received packet...");
+
+ memset(&xdr, 0, sizeof(xdr));
+ xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE);
+ xid = nsm_parse_reply(&xdr);
+ if (xid == 0)
+ goto out;
+
+ /* Before we look at the data, find the host struct for
+ this reply */
+ if ((hp = find_host(xid)) == NULL)
+ goto out;
+
+ sap = hp->ai->ai_addr;
+ if (nfs_get_port(sap) == 0)
+ recv_rpcbind_reply(sap, hp, &xdr);
+ else
+ recv_notify_reply(hp);
+
+out:
+ xdr_destroy(&xdr);
+}
+
+/*
+ * Insert host into notification list, sorted by next send time
+ */
+static void
+insert_host(struct nsm_host *host)
+{
+ struct nsm_host **where, *p;
+
+ where = &hosts;
+ while ((p = *where) != 0) {
+ /* Sort in ascending order of timeout */
+ if (host->send_next < p->send_next)
+ break;
+ /* If we have the same timeout, put the
+ * most recently used host first.
+ * This makes sure that "recent" hosts
+ * get notified first.
+ */
+ if (host->send_next == p->send_next
+ && host->last_used > p->last_used)
+ break;
+ where = &p->next;
+ }
+
+ host->next = *where;
+ *where = host;
+ xlog(D_GENERAL, "Added host %s to notify list", host->name);
+}
+
+/*
+ * Find host given the XID
+ */
+static struct nsm_host *
+find_host(uint32_t xid)
+{
+ struct nsm_host **where, *p;
+
+ where = &hosts;
+ while ((p = *where) != 0) {
+ if (p->xid == xid) {
+ *where = p->next;
+ return p;
+ }
+ where = &p->next;
+ }
+ return NULL;
+}
+
+/*
+ * Record pid in /run/sm-notify.pid
+ * This file should remain until a reboot, even if the
+ * program exits.
+ * If file already exists, fail.
+ */
+static int record_pid(void)
+{
+ char pid[20];
+ ssize_t len;
+ int fd;
+
+ (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid());
+ fd = open("/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
+ if (fd < 0)
+ return 0;
+
+ len = write(fd, pid, strlen(pid));
+ if ((len < 0) || ((size_t)len != strlen(pid))) {
+ xlog_warn("Writing to pid file failed: errno %d (%m)",
+ errno);
+ }
+
+ (void)close(fd);
+ return 1;
+}