From e6ac6d91b7e1f994da1f3d7d6e3b1b285c37066e Mon Sep 17 00:00:00 2001 From: peter Date: Sat, 17 Feb 1996 15:14:59 +0000 Subject: Import Jan 15 version of Andrew Gordon 's rpc.statd. This is apparently fully functional and complete. --- sbin/rpc.statd/Makefile | 13 ++ sbin/rpc.statd/file.c | 356 +++++++++++++++++++++++++++++++++++++++++ sbin/rpc.statd/procs.c | 352 ++++++++++++++++++++++++++++++++++++++++ sbin/rpc.statd/rpc.statd.8 | 107 +++++++++++++ sbin/rpc.statd/statd.c | 232 +++++++++++++++++++++++++++ sbin/rpc.statd/statd.h | 123 ++++++++++++++ sbin/rpc.statd/test.c | 138 ++++++++++++++++ usr.sbin/rpc.statd/Makefile | 13 ++ usr.sbin/rpc.statd/file.c | 356 +++++++++++++++++++++++++++++++++++++++++ usr.sbin/rpc.statd/procs.c | 352 ++++++++++++++++++++++++++++++++++++++++ usr.sbin/rpc.statd/rpc.statd.8 | 107 +++++++++++++ usr.sbin/rpc.statd/statd.c | 232 +++++++++++++++++++++++++++ usr.sbin/rpc.statd/statd.h | 123 ++++++++++++++ usr.sbin/rpc.statd/test.c | 138 ++++++++++++++++ 14 files changed, 2642 insertions(+) create mode 100644 sbin/rpc.statd/Makefile create mode 100644 sbin/rpc.statd/file.c create mode 100644 sbin/rpc.statd/procs.c create mode 100644 sbin/rpc.statd/rpc.statd.8 create mode 100644 sbin/rpc.statd/statd.c create mode 100644 sbin/rpc.statd/statd.h create mode 100644 sbin/rpc.statd/test.c create mode 100644 usr.sbin/rpc.statd/Makefile create mode 100644 usr.sbin/rpc.statd/file.c create mode 100644 usr.sbin/rpc.statd/procs.c create mode 100644 usr.sbin/rpc.statd/rpc.statd.8 create mode 100644 usr.sbin/rpc.statd/statd.c create mode 100644 usr.sbin/rpc.statd/statd.h create mode 100644 usr.sbin/rpc.statd/test.c diff --git a/sbin/rpc.statd/Makefile b/sbin/rpc.statd/Makefile new file mode 100644 index 0000000..51bc93e --- /dev/null +++ b/sbin/rpc.statd/Makefile @@ -0,0 +1,13 @@ +# $Id$ + +PROG = rpc.statd +SRCS = statd.c procs.c file.c +MAN8 = rpc.statd.8 + +DPADD= ${LIBRPCSVC} +LDADD= -lrpcsvc + +.include + +test: test.c + cc -o test test.c -lrpcsvc diff --git a/sbin/rpc.statd/file.c b/sbin/rpc.statd/file.c new file mode 100644 index 0000000..aeb1fc0 --- /dev/null +++ b/sbin/rpc.statd/file.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include /* For mmap() */ +#include +#include + +#include "statd.h" + +FileLayout *status_info; /* Pointer to the mmap()ed status file */ +static int status_fd; /* File descriptor for the open file */ +static off_t status_file_len; /* Current on-disc length of file */ + +/* sync_file --------------------------------------------------------------- */ +/* + Purpose: Packaged call of msync() to flush changes to mmap()ed file + Returns: Nothing. Errors to syslog. +*/ + +void sync_file(void) +{ + if (msync((void *)status_info, 0, 0) < 0) + { + syslog(LOG_ERR, "msync() failed: %s", strerror(errno)); + } +} + +/* find_host -------------------------------------------------------------- */ +/* + Purpose: Find the entry in the status file for a given host + Returns: Pointer to that entry in the mmap() region, or NULL. + Notes: Also creates entries if requested. + Failure to create also returns NULL. +*/ + +HostInfo *find_host(char *hostname, int create) +{ + HostInfo *hp; + HostInfo *spare_slot = NULL; + HostInfo *result = NULL; + int i; + + for (i = 0, hp = status_info->hosts; i < status_info->noOfHosts; i++, hp++) + { + if (!strncasecmp(hostname, hp->hostname, SM_MAXSTRLEN)) + { + result = hp; + break; + } + if (!spare_slot && !hp->monList && !hp->notifyReqd) + spare_slot = hp; + } + + /* Return if entry found, or if not asked to create one. */ + if (result || !create) return (result); + + /* Now create an entry, using the spare slot if one was found or */ + /* adding to the end of the list otherwise, extending file if reqd */ + if (!spare_slot) + { + off_t desired_size; + spare_slot = &status_info->hosts[status_info->noOfHosts]; + desired_size = ((char*)spare_slot - (char*)status_info) + sizeof(HostInfo); + if (desired_size > status_file_len) + { + /* Extend file by writing 1 byte of junk at the desired end pos */ + lseek(status_fd, desired_size - 1, SEEK_SET); + i = write(status_fd, &i, 1); + if (i < 1) + { + syslog(LOG_ERR, "Unable to extend status file"); + return (NULL); + } + status_file_len = desired_size; + } + status_info->noOfHosts++; + } + + /* Initialise the spare slot that has been found/created */ + /* Note that we do not msync(), since the caller is presumed to be */ + /* about to modify the entry further */ + memset(spare_slot, 0, sizeof(HostInfo)); + strncpy(spare_slot->hostname, hostname, SM_MAXSTRLEN); + return (spare_slot); +} + +/* init_file -------------------------------------------------------------- */ +/* + Purpose: Open file, create if necessary, initialise it. + Returns: Nothing - exits on error + Notes: Called before process becomes daemon, hence logs to + stderr rather than syslog. + Opens the file, then mmap()s it for ease of access. + Also performs initial clean-up of the file, zeroing + monitor list pointers, setting the notifyReqd flag in + all hosts that had a monitor list, and incrementing + the state number to the next even value. +*/ + +void init_file(char *filename) +{ + int new_file = FALSE; + char buf[HEADER_LEN]; + int i; + + /* try to open existing file - if not present, create one */ + status_fd = open(filename, O_RDWR); + if ((status_fd < 0) && (errno == ENOENT)) + { + status_fd = open(filename, O_RDWR | O_CREAT, 0644); + new_file = TRUE; + } + if (status_fd < 0) + { + perror("rpc.statd"); + fprintf(stderr, "Unable to open status file %s\n", filename); + exit(1); + } + + /* File now open. mmap() it, with a generous size to allow for */ + /* later growth, where we will extend the file but not re-map it. */ + status_info = (FileLayout *) + mmap(NULL, 0x10000000, PROT_READ | PROT_WRITE, MAP_SHARED, status_fd, 0); + + if (status_info == (FileLayout *) -1) + { + perror("rpc.statd"); + fprintf(stderr, "Unable to mmap() status file\n"); + } + + status_file_len = lseek(status_fd, 0L, SEEK_END); + + /* If the file was not newly created, validate the contents, and if */ + /* defective, re-create from scratch. */ + if (!new_file) + { + if ((status_file_len < HEADER_LEN) || (status_file_len + < (HEADER_LEN + sizeof(HostInfo) * status_info->noOfHosts)) ) + { + fprintf(stderr, "rpc.statd: status file is corrupt\n"); + new_file = TRUE; + } + } + + /* Initialisation of a new, empty file. */ + if (new_file) + { + memset(buf, 0, sizeof(buf)); + lseek(status_fd, 0L, SEEK_SET); + write(status_fd, buf, HEADER_LEN); + status_file_len = HEADER_LEN; + } + else + { + /* Clean-up of existing file - monitored hosts will have a pointer */ + /* to a list of clients, which refers to memory in the previous */ + /* incarnation of the program and so are meaningless now. These */ + /* pointers are zeroed and the fact that the host was previously */ + /* monitored is recorded by setting the notifyReqd flag, which will */ + /* in due course cause a SM_NOTIFY to be sent. */ + /* Note that if we crash twice in quick succession, some hosts may */ + /* already have notifyReqd set, where we didn't manage to notify */ + /* them before the second crash occurred. */ + for (i = 0; i < status_info->noOfHosts; i++) + { + HostInfo *this_host = &status_info->hosts[i]; + + if (this_host->monList) + { + this_host->notifyReqd = TRUE; + this_host->monList = NULL; + } + } + /* Select the next higher even number for the state counter */ + status_info->ourState = (status_info->ourState + 2) & 0xfffffffe; +/*???????******/ status_info->ourState++; + } +} + +/* xdr_stat_chge ----------------------------------------------------------- */ +/* + Purpose: XDR-encode structure of type stat_chge + Returns: TRUE if successful + Notes: This function is missing from librpcsvc, because the + sm_inter.x distributed by Sun omits the SM_NOTIFY + procedure used between co-operating statd's +*/ + +bool_t xdr_stat_chge(XDR *xdrs, stat_chge *objp) +{ + if (!xdr_string(xdrs, &objp->mon_name, SM_MAXSTRLEN)) + { + return (FALSE); + } + if (!xdr_int(xdrs, &objp->state)) + { + return (FALSE); + } + return (TRUE); +} + + +/* notify_one_host --------------------------------------------------------- */ +/* + Purpose: Perform SM_NOTIFY procedure at specified host + Returns: TRUE if success, FALSE if failed. +*/ + +static int notify_one_host(char *hostname) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + char dummy; + stat_chge arg; + char our_hostname[SM_MAXSTRLEN+1]; + + gethostname(our_hostname, sizeof(our_hostname)); + our_hostname[SM_MAXSTRLEN] = '\0'; + arg.mon_name = our_hostname; + arg.state = status_info->ourState; + + if (debug) syslog (LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname); + + cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", hostname, + clnt_spcreateerror("")); + return (FALSE); + } + + if (clnt_call(cli, SM_NOTIFY, xdr_stat_chge, &arg, xdr_void, &dummy, timeout) + != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", hostname); + clnt_destroy(cli); + return (FALSE); + } + + clnt_destroy(cli); + return (TRUE); +} + +/* notify_hosts ------------------------------------------------------------ */ +/* + Purpose: Send SM_NOTIFY to all hosts marked as requiring it + Returns: Nothing, immediately - forks a process to do the work. + Notes: Does nothing if there are no monitored hosts. + Called after all the initialisation has been done - + logs to syslog. +*/ + +void notify_hosts(void) +{ + int i; + int attempts; + int work_to_do = FALSE; + HostInfo *hp; + pid_t pid; + + /* First check if there is in fact any work to do. */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + work_to_do = TRUE; + break; + } + } + + if (!work_to_do) return; /* No work found */ + + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return; + } + if (pid) return; + + /* Here in the child process. We continue until all the hosts marked */ + /* as requiring notification have been duly notified. */ + /* If one of the initial attempts fails, we sleep for a while and */ + /* have another go. This is necessary because when we have crashed, */ + /* (eg. a power outage) it is quite possible that we won't be able to */ + /* contact all monitored hosts immediately on restart, either because */ + /* they crashed too and take longer to come up (in which case the */ + /* notification isn't really required), or more importantly if some */ + /* router etc. needed to reach the monitored host has not come back */ + /* up yet. In this case, we will be a bit late in re-establishing */ + /* locks (after the grace period) but that is the best we can do. */ + /* We try 10 times at 5 sec intervals, 10 more times at 1 minute */ + /* intervals, then 24 more times at hourly intervals, finally */ + /* giving up altogether if the host hasn't come back to life after */ + /* 24 hours. */ + + for (attempts = 0; attempts < 44; attempts++) + { + work_to_do = FALSE; /* Unless anything fails */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + if (notify_one_host(hp->hostname)) + { + hp->notifyReqd = FALSE; + sync_file(); + } + else work_to_do = TRUE; + } + } + if (!work_to_do) break; + if (attempts < 10) sleep(5); + else if (attempts < 20) sleep(60); + else sleep(60*60); + } + exit(0); +} + + diff --git a/sbin/rpc.statd/procs.c b/sbin/rpc.statd/procs.c new file mode 100644 index 0000000..e708e94 --- /dev/null +++ b/sbin/rpc.statd/procs.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + +#include +#include +#include +#include +#include +#include +#include /* for gethostbyname() */ + +#include "statd.h" + +/* sm_stat_1 --------------------------------------------------------------- */ +/* + Purpose: RPC call to enquire if a host can be monitored + Returns: TRUE for any hostname that can be looked up to give + an address. +*/ + +struct sm_stat_res *sm_stat_1(sm_name *arg) +{ + static sm_stat_res res; + + if (debug) syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name); + + if (gethostbyname(arg->mon_name)) res.res_stat = stat_succ; + else + { + syslog(LOG_ERR, "invalid hostname to sm_stat: %s", arg->mon_name); + res.res_stat = stat_fail; + } + + res.state = status_info->ourState; + return (&res); +} + +/* sm_mon_1 ---------------------------------------------------------------- */ +/* + Purpose: RPC procedure to establish a monitor request + Returns: Success, unless lack of resources prevents + the necessary structures from being set up + to record the request, or if the hostname is not + valid (as judged by gethostbyname()) +*/ + +struct sm_stat_res *sm_mon_1(mon *arg) +{ + static sm_stat_res res; + HostInfo *hp; + MonList *lp; + + if (debug) + { + syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_id.mon_name, arg->mon_id.my_id.my_name, + arg->mon_id.my_id.my_prog, arg->mon_id.my_id.my_vers, + arg->mon_id.my_id.my_proc); + } + + res.res_stat = stat_fail; /* Assume fail until set otherwise */ + res.state = status_info->ourState; + + /* Find existing host entry, or create one if not found */ + /* If find_host() fails, it will have logged the error already. */ + if (!gethostbyname(arg->mon_id.mon_name)) + { + syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name); + } + else if (hp = find_host(arg->mon_id.mon_name, TRUE)) + { + lp = (MonList *)malloc(sizeof(MonList)); + if (!lp) + { + syslog(LOG_ERR, "Out of memory"); + } + else + { + strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN); + lp->notifyProg = arg->mon_id.my_id.my_prog; + lp->notifyVers = arg->mon_id.my_id.my_vers; + lp->notifyProc = arg->mon_id.my_id.my_proc; + memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData)); + + lp->next = hp->monList; + hp->monList = lp; + sync_file(); + + res.res_stat = stat_succ; /* Report success */ + } + } + + return (&res); +} + +/* do_unmon ---------------------------------------------------------------- */ +/* + Purpose: Remove a monitor request from a host + Returns: TRUE if found, FALSE if not found. + Notes: Common code from sm_unmon_1 and sm_unmon_all_1 + In the unlikely event of more than one identical monitor + request, all are removed. +*/ + +static int do_unmon(HostInfo *hp, my_id *idp) +{ + MonList *lp, *next; + MonList *last = NULL; + int result = FALSE; + + lp = hp->monList; + while (lp) + { + if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN) + && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc) + && (idp->my_vers == lp->notifyVers)) + { + /* found one. Unhook from chain and free. */ + next = lp->next; + if (last) last->next = next; + else hp->monList = next; + free(lp); + lp = next; + result = TRUE; + } + else + { + last = lp; + lp = lp->next; + } + } + return (result); +} + +/* sm_unmon_1 -------------------------------------------------------------- */ +/* + Purpose: RPC procedure to release a monitor request. + Returns: Local machine's status number + Notes: The supplied mon_id should match the value passed in an + earlier call to sm_mon_1 +*/ + +struct sm_stat *sm_unmon_1(mon_id *arg) +{ + static sm_stat res; + HostInfo *hp; + + if (debug) + { + syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_name, arg->my_id.my_name, arg->my_id.my_prog, + arg->my_id.my_vers, arg->my_id.my_proc); + } + + if (hp = find_host(arg->mon_name, FALSE)) + { + if (do_unmon(hp, &arg->my_id)) sync_file(); + else + { + syslog(LOG_ERR, "unmon request from %s, no matching monitor", + arg->my_id.my_name); + } + } + else syslog(LOG_ERR, "unmon request from %s for unknown host %s", + arg->my_id.my_name, arg->mon_name); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_unmon_all_1 ---------------------------------------------------------- */ +/* + Purpose: RPC procedure to release monitor requests. + Returns: Local machine's status number + Notes: Releases all monitor requests (if any) from the specified + host and program number. +*/ + +struct sm_stat *sm_unmon_all_1(my_id *arg) +{ + static sm_stat res; + HostInfo *hp; + MonList *lp; + int i; + + if (debug) + { + syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d", + arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc); + } + + for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++) + { + do_unmon(hp, arg); + } + sync_file(); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_simu_crash_1 --------------------------------------------------------- */ +/* + Purpose: RPC procedure to simulate a crash + Returns: Nothing + Notes: Standardised mechanism for debug purposes + The specification says that we should drop all of our + status information (apart from the list of monitored hosts + on disc). However, this would confuse the rpc.lockd + which would be unaware that all of its monitor requests + had been silently junked. Hence we in fact retain all + current requests and simply increment the status counter + and inform all hosts on the monitor list. +*/ + +void *sm_simu_crash_1(void) +{ + static char dummy; + int work_to_do; + HostInfo *hp; + int i; + + if (debug) syslog(LOG_DEBUG, "simu_crash called!!"); + + /* Simulate crash by setting notify-required flag on all monitored */ + /* hosts, and incrementing our status number. notify_hosts() is */ + /* then called to fork a process to do the notifications. */ + + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->monList) + { + work_to_do = TRUE; + hp->notifyReqd = TRUE; + } + } + status_info->ourState += 2; /* always even numbers if not crashed */ + + if (work_to_do) notify_hosts(); + + return (&dummy); +} + +/* sm_notify_1 ------------------------------------------------------------- */ +/* + Purpose: RPC procedure notifying local statd of the crash of another + Returns: Nothing + Notes: There is danger of deadlock, since it is quite likely that + the client procedure that we call will in turn call us + to remove or adjust the monitor request. + We therefore fork() a process to do the notifications. + Note that the main HostInfo structure is in a mmap() + region and so will be shared with the child, but the + monList pointed to by the HostInfo is in normal memory. + Hence if we read the monList before forking, we are + protected from the parent servicing other requests + that modify the list. +*/ + +void *sm_notify_1(stat_chge *arg) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + static char dummy; + status tx_arg; /* arg sent to callback procedure */ + MonList *lp; + HostInfo *hp; + pid_t pid; + + if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d", + arg->mon_name, arg->state); + + hp = find_host(arg->mon_name, FALSE); + if (!hp) + { + /* Never heard of this host - why is it notifying us? */ + syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name); + return; + } + lp = hp->monList; + if (!lp) return (FALSE); /* We know this host, but have no */ + /* outstanding requests. */ + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return; + } + if (pid) return (&dummy); /* Parent returns */ + + while (lp) + { + tx_arg.mon_name = arg->mon_name; + tx_arg.state = arg->state; + memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv)); + cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost, + clnt_spcreateerror("")); + } + else + { + if (clnt_call(cli, lp->notifyProc, xdr_status, &tx_arg, xdr_void, &dummy, + timeout) != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to call rpc.statd client at host %s", + lp->notifyHost); + } + clnt_destroy(cli); + } + lp = lp->next; + } + + exit (0); /* Child quits */ +} + diff --git a/sbin/rpc.statd/rpc.statd.8 b/sbin/rpc.statd/rpc.statd.8 new file mode 100644 index 0000000..34b095f --- /dev/null +++ b/sbin/rpc.statd/rpc.statd.8 @@ -0,0 +1,107 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd September 19, 1995 +.Dt RPC.STATD 8 +.Os +.Sh NAME +.Nm rpc.statd +.Nd host status monitoring daemon +.Sh SYNOPSIS +.Nm /sbin/rpc.statd +.Op Fl d +.Sh DESCRIPTION +.Nm rpc.rstatd +is a daemon which co-operates with rpc.statd daemons on other hosts to provide +a status monitoring service. The daemon accepts requests from +programs running on the local host (typically, +.Xr rpc.lockd 8 , +the NFS file locking daemon) to monitor the status of specified +hosts. If a monitored host crashes and restarts, the remote daemon will +notify the local daemon, which in turn will notify the local program(s) +which requested the monitoring service. Conversely, if this host crashes +and re-starts, when the +.Nm rpc.statd +re-starts, it will notify all of the hosts which were being monitored +at the time of the crash. +.Pp +Options and operands available for +.Nm rpc.statd : +.Bl -tag -width Ds +.It Fl d +The +.Fl d +option causes debugging information to be written to syslog, recording +all RPC transations to the daemon. These messages are logged with level +LOG_DEBUG and facility LOG_DAEMON. Error conditions are logged irrespective +of this option, using level LOG_ERR. +.El +.Pp +The +.Nm rpc.rstatd +daemon must NOT be invoked by +.Xr inetd 8 +because the protocol assumes that the daemon will run from system start time. +Instead, it should be run from +.Xr rc 8 +after the network has been started. +.Sh FILES +.Bl -tag -width /usr/include/rpcsvc/sm_inter.x -compact +.It Pa /var/db/statd.status +non-volatile record of currently monitored hosts. +.It Pa /usr/include/rpcsvc/sm_inter.x +RPC protocol specification used by local applications to register monitoring requests. +.El +.Sh SEE ALSO +.Xr rpc.lockd 8 , +.Xr rc 8 , +.Xr syslog 3 +.Sh BUGS +There is no means for the daemon to tell when a monitored host has +disappeared permanently (eg. catastrophic harware failure), as opposed +to transient failure of the host or an intermediate router. At present, +it will re-try notification attempts at frequent intervals for 10 minutes, +then hourly, and finally gives up after 24 hours. + +The protocol requires that symmetric monitor requests are made to both +the local and remote daemon in order to establish a monitored relationship. +This is convenient for the NFS locking protocol, but probably reduces the +usefulness of the monitoring system for other applications. + +The current implementation uses more than 1Kbyte per monitored host in +the status file (and also in VM). This may be inefficient for NFS servers +with large numbers of clients. +.Sh STANDARDS +The implementation is based on the specification in X/Open CAE Specification +C218, "Protocols for X/Open PC Interworking: XNFS, Issue 4", ISBN 1 872630 66 9 diff --git a/sbin/rpc.statd/statd.c b/sbin/rpc.statd/statd.c new file mode 100644 index 0000000..0aaa363 --- /dev/null +++ b/sbin/rpc.statd/statd.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + +/* main() function for status monitor daemon. Some of the code in this */ +/* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */ +/* The actual program logic is in the file procs.c */ + +#include +#include +#include +#include +#include +#include +#include "statd.h" + +#ifndef lint +static char rcsid[] = "$id: $"; +#endif /* not lint */ + +int debug = 0; /* Controls syslog() calls for debug messages */ + +static void sm_prog_1(); +static void handle_sigchld(); + +main(int argc, char **argv) +{ + SVCXPRT *transp; + struct sigaction sa; + + if (argc > 1) + { + if (strcmp(argv[1], "-d")) + { + fprintf(stderr, "Usage: rpc.statd [-d]\n"); + exit(1); + } + debug = 1; + } + + (void)pmap_unset(SM_PROG, SM_VERS); + + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL) + { + fprintf(stderr, "cannot create udp service.\n"); + exit(1); + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_UDP)) + { + fprintf(stderr, "unable to register (SM_PROG, SM_VERS, udp).\n"); + exit(1); + } + + transp = svctcp_create(RPC_ANYSOCK, 0, 0); + if (transp == NULL) + { + fprintf(stderr, "cannot create tcp service.\n"); + exit(1); + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_TCP)) { + fprintf(stderr, "unable to register (SM_PROG, SM_VERS, tcp).\n"); + exit(1); + } + init_file("/var/db/statd.status"); + + /* Note that it is NOT sensible to run this program from inetd - the */ + /* protocol assumes that it will run immediately at boot time. */ + daemon(0, 0); + openlog("rpc.statd", 0, LOG_DAEMON); + if (debug) syslog(LOG_INFO, "Starting - debug enabled"); + else syslog(LOG_INFO, "Starting"); + + /* Install signal handler to collect exit status of child processes */ + sa.sa_handler = handle_sigchld; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGCHLD); + sa.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa, NULL); + + /* Initialisation now complete - start operating */ + notify_hosts(); /* Forks a process (if necessary) to do the */ + /* SM_NOTIFY calls, which may be slow. */ + + svc_run(); /* Should never return */ + exit(1); +} + + +/* handle_sigchld ---------------------------------------------------------- */ +/* + Purpose: Catch SIGCHLD and collect process status + Retruns: Nothing. + Notes: No special action required, other than to collect the + process status and hence allow the child to die: + we only use child processes for asynchronous transmission + of SM_NOTIFY to other systems, so it is normal for the + children to exit when they have done their work. +*/ + +static void handle_sigchld(int sig, int code, struct sigcontext *scp) +{ + int pid, status; + pid = wait4(-1, &status, WNOHANG, (struct rusage*)0); + if (!pid) syslog(LOG_ERR, "Phantom SIGCHLD??"); + else if (status == 0) + { + if (debug) syslog(LOG_DEBUG, "Child %d exited OK", pid); + } + else syslog(LOG_ERR, "Child %d failed with status %d", pid, + WEXITSTATUS(status)); +} + + +/* sm_prog1 ---------------------------------------------------------------- */ +/* + Purpose: Handle one RPC request + Returns: Nothing + Notes: Called from RPC libraray on receipt of a request. + Code for this function was auto-generated by rpcgen. +*/ + +static void +sm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) +{ + union + { + struct sm_name sm_stat_1_arg; + struct mon sm_mon_1_arg; + struct mon_id sm_unmon_1_arg; + struct my_id sm_unmon_all_1_arg; + struct stat_chge sm_notify_1_arg; + } argument; + char *result; + bool_t (*xdr_argument)(), (*xdr_result)(); + char *(*local)(); + + switch (rqstp->rq_proc) + { + case NULLPROC: + (void)svc_sendreply(transp, xdr_void, (char *)NULL); + return; + + case SM_STAT: + xdr_argument = xdr_sm_name; + xdr_result = xdr_sm_stat_res; + local = (char *(*)()) sm_stat_1; + break; + + case SM_MON: + xdr_argument = xdr_mon; + xdr_result = xdr_sm_stat_res; + local = (char *(*)()) sm_mon_1; + break; + + case SM_UNMON: + xdr_argument = xdr_mon_id; + xdr_result = xdr_sm_stat; + local = (char *(*)()) sm_unmon_1; + break; + + case SM_UNMON_ALL: + xdr_argument = xdr_my_id; + xdr_result = xdr_sm_stat; + local = (char *(*)()) sm_unmon_all_1; + break; + + case SM_SIMU_CRASH: + xdr_argument = xdr_void; + xdr_result = xdr_void; + local = (char *(*)()) sm_simu_crash_1; + break; + + case SM_NOTIFY: + xdr_argument = xdr_stat_chge; + xdr_result = xdr_void; + local = (char *(*)()) sm_notify_1; + break; + + default: + svcerr_noproc(transp); + return; + } + + bzero((char *)&argument, sizeof(argument)); + if (!svc_getargs(transp, xdr_argument, (caddr_t)&argument)) + { + svcerr_decode(transp); + return; + } + result = (*local)(&argument, rqstp); + if (result != NULL && !svc_sendreply(transp, xdr_result, result)) + { + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, xdr_argument, (caddr_t)&argument)) + { + syslog(LOG_ERR, "unable to free arguments"); + exit(1); + } +} + diff --git a/sbin/rpc.statd/statd.h b/sbin/rpc.statd/statd.h new file mode 100644 index 0000000..63c4738 --- /dev/null +++ b/sbin/rpc.statd/statd.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + + +#include + +/* These pieces are missing from the distributed sm_inter.x, which */ +/* omits the SM_NOTIFY procedure used between cooperating rpc.statd's */ + +#define SM_NOTIFY ((u_long)6) +extern void *sm_notify_1(); + +struct stat_chge +{ + char *mon_name; + int state; +}; +typedef struct stat_chge stat_chge; +bool_t xdr_stat_chge(); + +/* ------------------------------------------------------------------------- */ +/* + Data structures for recording monitored hosts + + The information held by the status monitor comprises a list of hosts + that we have been asked to monitor, and, associated with each monitored + host, one or more clients to be called back if the monitored host crashes. + + The list of monitored hosts must be retained over a crash, so that upon + re-boot we can call the SM_NOTIFY procedure in all those hosts so as to + cause them to start recovery processing. On the other hand, the client + call-backs are not required to be preserved: they are assumed (in the + protocol design) to be local processes which will have crashed when + we did, and so are discarded on restart. + + We handle this by keeping the list of monitored hosts in a file + (/var/statd.state) which is mmap()ed and whose format is described + by the typedef FileLayout. The lists of client callbacks are chained + off this structure, but are held in normal memory and so will be + lost after a re-boot. Hence the actual values of MonList * pointers + in the copy on disc have no significance, but their NULL/non-NULL + status indicates whether this host is actually being monitored or if it + is an empty slot in the file. +*/ + +typedef struct MonList_s +{ + struct MonList_s *next; /* Next in list or NULL */ + char notifyHost[SM_MAXSTRLEN + 1]; /* Host to notify */ + int notifyProg; /* RPC program number to call */ + int notifyVers; /* version number */ + int notifyProc; /* procedure number */ + unsigned char notifyData[16]; /* Opaque data from caller */ +} MonList; + +typedef struct +{ + char hostname[SM_MAXSTRLEN + 1]; /* Name of monitored host */ + int notifyReqd; /* TRUE if we've crashed and not yet */ + /* informed the monitored host */ + MonList *monList; /* List of clients to inform if we */ + /* hear that the monitored host has */ + /* crashed, NULL if no longer monitored */ +} HostInfo; + + +/* Overall file layout. */ + +typedef struct +{ + int ourState; /* State number as defined in statd protocol */ + int noOfHosts; /* Number of elements in hosts[] */ + char reserved[248]; /* Reserved for future use */ + HostInfo hosts[1]; /* vector of monitored hosts */ +} FileLayout; + +#define HEADER_LEN (sizeof(FileLayout) - sizeof(HostInfo)) + +/* ------------------------------------------------------------------------- */ + +/* Global variables */ + +extern FileLayout *status_info; /* The mmap()ed status file */ + +extern int debug; /* =1 to enable diagnostics to syslog */ + +/* Function prototypes */ + +extern HostInfo *find_host(char * /*hostname*/, int /*create*/); +extern void init_file(char * /*filename*/); +extern void notify_hosts(void); +extern void sync_file(void); diff --git a/sbin/rpc.statd/test.c b/sbin/rpc.statd/test.c new file mode 100644 index 0000000..e864485 --- /dev/null +++ b/sbin/rpc.statd/test.c @@ -0,0 +1,138 @@ +#include +#include +#include + + +/* Default timeout can be changed using clnt_control() */ +static struct timeval TIMEOUT = { 25, 0 }; + +struct sm_stat_res * +sm_stat_1(argp, clnt) + struct sm_name *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_STAT, xdr_sm_name, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat_res * +sm_mon_1(argp, clnt) + struct mon *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_MON, xdr_mon, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_1(argp, clnt) + struct mon_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON, xdr_mon_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_all_1(argp, clnt) + struct my_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON_ALL, xdr_my_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +void * +sm_simu_crash_1(argp, clnt) + void *argp; + CLIENT *clnt; +{ + static char res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_SIMU_CRASH, xdr_void, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return ((void *)&res); +} + + +int main(int argc, char **argv) +{ + CLIENT *cli; + char dummy; + void *out; + struct mon mon; + + if (argc < 2) + { + fprintf(stderr, "usage: test | crash\n"); + fprintf(stderr, "Always talks to statd at localhost\n"); + exit(1); + } + + printf("Creating client for localhost\n" ); + cli = clnt_create("localhost", SM_PROG, SM_VERS, "udp"); + if (!cli) + { + printf("Failed to create client\n"); + exit(1); + } + + mon.mon_id.mon_name = argv[1]; + mon.mon_id.my_id.my_name = argv[1]; + mon.mon_id.my_id.my_prog = SM_PROG; + mon.mon_id.my_id.my_vers = SM_VERS; + mon.mon_id.my_id.my_proc = 1; /* have it call sm_stat() !!! */ + + if (strcmp(argv[1], "crash")) + { + /* Hostname given */ + struct sm_stat_res *res; + if (res = sm_mon_1(&mon, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + else + { + if (out = sm_simu_crash_1(&dummy, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + + return 0; +} diff --git a/usr.sbin/rpc.statd/Makefile b/usr.sbin/rpc.statd/Makefile new file mode 100644 index 0000000..51bc93e --- /dev/null +++ b/usr.sbin/rpc.statd/Makefile @@ -0,0 +1,13 @@ +# $Id$ + +PROG = rpc.statd +SRCS = statd.c procs.c file.c +MAN8 = rpc.statd.8 + +DPADD= ${LIBRPCSVC} +LDADD= -lrpcsvc + +.include + +test: test.c + cc -o test test.c -lrpcsvc diff --git a/usr.sbin/rpc.statd/file.c b/usr.sbin/rpc.statd/file.c new file mode 100644 index 0000000..aeb1fc0 --- /dev/null +++ b/usr.sbin/rpc.statd/file.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include /* For mmap() */ +#include +#include + +#include "statd.h" + +FileLayout *status_info; /* Pointer to the mmap()ed status file */ +static int status_fd; /* File descriptor for the open file */ +static off_t status_file_len; /* Current on-disc length of file */ + +/* sync_file --------------------------------------------------------------- */ +/* + Purpose: Packaged call of msync() to flush changes to mmap()ed file + Returns: Nothing. Errors to syslog. +*/ + +void sync_file(void) +{ + if (msync((void *)status_info, 0, 0) < 0) + { + syslog(LOG_ERR, "msync() failed: %s", strerror(errno)); + } +} + +/* find_host -------------------------------------------------------------- */ +/* + Purpose: Find the entry in the status file for a given host + Returns: Pointer to that entry in the mmap() region, or NULL. + Notes: Also creates entries if requested. + Failure to create also returns NULL. +*/ + +HostInfo *find_host(char *hostname, int create) +{ + HostInfo *hp; + HostInfo *spare_slot = NULL; + HostInfo *result = NULL; + int i; + + for (i = 0, hp = status_info->hosts; i < status_info->noOfHosts; i++, hp++) + { + if (!strncasecmp(hostname, hp->hostname, SM_MAXSTRLEN)) + { + result = hp; + break; + } + if (!spare_slot && !hp->monList && !hp->notifyReqd) + spare_slot = hp; + } + + /* Return if entry found, or if not asked to create one. */ + if (result || !create) return (result); + + /* Now create an entry, using the spare slot if one was found or */ + /* adding to the end of the list otherwise, extending file if reqd */ + if (!spare_slot) + { + off_t desired_size; + spare_slot = &status_info->hosts[status_info->noOfHosts]; + desired_size = ((char*)spare_slot - (char*)status_info) + sizeof(HostInfo); + if (desired_size > status_file_len) + { + /* Extend file by writing 1 byte of junk at the desired end pos */ + lseek(status_fd, desired_size - 1, SEEK_SET); + i = write(status_fd, &i, 1); + if (i < 1) + { + syslog(LOG_ERR, "Unable to extend status file"); + return (NULL); + } + status_file_len = desired_size; + } + status_info->noOfHosts++; + } + + /* Initialise the spare slot that has been found/created */ + /* Note that we do not msync(), since the caller is presumed to be */ + /* about to modify the entry further */ + memset(spare_slot, 0, sizeof(HostInfo)); + strncpy(spare_slot->hostname, hostname, SM_MAXSTRLEN); + return (spare_slot); +} + +/* init_file -------------------------------------------------------------- */ +/* + Purpose: Open file, create if necessary, initialise it. + Returns: Nothing - exits on error + Notes: Called before process becomes daemon, hence logs to + stderr rather than syslog. + Opens the file, then mmap()s it for ease of access. + Also performs initial clean-up of the file, zeroing + monitor list pointers, setting the notifyReqd flag in + all hosts that had a monitor list, and incrementing + the state number to the next even value. +*/ + +void init_file(char *filename) +{ + int new_file = FALSE; + char buf[HEADER_LEN]; + int i; + + /* try to open existing file - if not present, create one */ + status_fd = open(filename, O_RDWR); + if ((status_fd < 0) && (errno == ENOENT)) + { + status_fd = open(filename, O_RDWR | O_CREAT, 0644); + new_file = TRUE; + } + if (status_fd < 0) + { + perror("rpc.statd"); + fprintf(stderr, "Unable to open status file %s\n", filename); + exit(1); + } + + /* File now open. mmap() it, with a generous size to allow for */ + /* later growth, where we will extend the file but not re-map it. */ + status_info = (FileLayout *) + mmap(NULL, 0x10000000, PROT_READ | PROT_WRITE, MAP_SHARED, status_fd, 0); + + if (status_info == (FileLayout *) -1) + { + perror("rpc.statd"); + fprintf(stderr, "Unable to mmap() status file\n"); + } + + status_file_len = lseek(status_fd, 0L, SEEK_END); + + /* If the file was not newly created, validate the contents, and if */ + /* defective, re-create from scratch. */ + if (!new_file) + { + if ((status_file_len < HEADER_LEN) || (status_file_len + < (HEADER_LEN + sizeof(HostInfo) * status_info->noOfHosts)) ) + { + fprintf(stderr, "rpc.statd: status file is corrupt\n"); + new_file = TRUE; + } + } + + /* Initialisation of a new, empty file. */ + if (new_file) + { + memset(buf, 0, sizeof(buf)); + lseek(status_fd, 0L, SEEK_SET); + write(status_fd, buf, HEADER_LEN); + status_file_len = HEADER_LEN; + } + else + { + /* Clean-up of existing file - monitored hosts will have a pointer */ + /* to a list of clients, which refers to memory in the previous */ + /* incarnation of the program and so are meaningless now. These */ + /* pointers are zeroed and the fact that the host was previously */ + /* monitored is recorded by setting the notifyReqd flag, which will */ + /* in due course cause a SM_NOTIFY to be sent. */ + /* Note that if we crash twice in quick succession, some hosts may */ + /* already have notifyReqd set, where we didn't manage to notify */ + /* them before the second crash occurred. */ + for (i = 0; i < status_info->noOfHosts; i++) + { + HostInfo *this_host = &status_info->hosts[i]; + + if (this_host->monList) + { + this_host->notifyReqd = TRUE; + this_host->monList = NULL; + } + } + /* Select the next higher even number for the state counter */ + status_info->ourState = (status_info->ourState + 2) & 0xfffffffe; +/*???????******/ status_info->ourState++; + } +} + +/* xdr_stat_chge ----------------------------------------------------------- */ +/* + Purpose: XDR-encode structure of type stat_chge + Returns: TRUE if successful + Notes: This function is missing from librpcsvc, because the + sm_inter.x distributed by Sun omits the SM_NOTIFY + procedure used between co-operating statd's +*/ + +bool_t xdr_stat_chge(XDR *xdrs, stat_chge *objp) +{ + if (!xdr_string(xdrs, &objp->mon_name, SM_MAXSTRLEN)) + { + return (FALSE); + } + if (!xdr_int(xdrs, &objp->state)) + { + return (FALSE); + } + return (TRUE); +} + + +/* notify_one_host --------------------------------------------------------- */ +/* + Purpose: Perform SM_NOTIFY procedure at specified host + Returns: TRUE if success, FALSE if failed. +*/ + +static int notify_one_host(char *hostname) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + char dummy; + stat_chge arg; + char our_hostname[SM_MAXSTRLEN+1]; + + gethostname(our_hostname, sizeof(our_hostname)); + our_hostname[SM_MAXSTRLEN] = '\0'; + arg.mon_name = our_hostname; + arg.state = status_info->ourState; + + if (debug) syslog (LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname); + + cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", hostname, + clnt_spcreateerror("")); + return (FALSE); + } + + if (clnt_call(cli, SM_NOTIFY, xdr_stat_chge, &arg, xdr_void, &dummy, timeout) + != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", hostname); + clnt_destroy(cli); + return (FALSE); + } + + clnt_destroy(cli); + return (TRUE); +} + +/* notify_hosts ------------------------------------------------------------ */ +/* + Purpose: Send SM_NOTIFY to all hosts marked as requiring it + Returns: Nothing, immediately - forks a process to do the work. + Notes: Does nothing if there are no monitored hosts. + Called after all the initialisation has been done - + logs to syslog. +*/ + +void notify_hosts(void) +{ + int i; + int attempts; + int work_to_do = FALSE; + HostInfo *hp; + pid_t pid; + + /* First check if there is in fact any work to do. */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + work_to_do = TRUE; + break; + } + } + + if (!work_to_do) return; /* No work found */ + + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return; + } + if (pid) return; + + /* Here in the child process. We continue until all the hosts marked */ + /* as requiring notification have been duly notified. */ + /* If one of the initial attempts fails, we sleep for a while and */ + /* have another go. This is necessary because when we have crashed, */ + /* (eg. a power outage) it is quite possible that we won't be able to */ + /* contact all monitored hosts immediately on restart, either because */ + /* they crashed too and take longer to come up (in which case the */ + /* notification isn't really required), or more importantly if some */ + /* router etc. needed to reach the monitored host has not come back */ + /* up yet. In this case, we will be a bit late in re-establishing */ + /* locks (after the grace period) but that is the best we can do. */ + /* We try 10 times at 5 sec intervals, 10 more times at 1 minute */ + /* intervals, then 24 more times at hourly intervals, finally */ + /* giving up altogether if the host hasn't come back to life after */ + /* 24 hours. */ + + for (attempts = 0; attempts < 44; attempts++) + { + work_to_do = FALSE; /* Unless anything fails */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + if (notify_one_host(hp->hostname)) + { + hp->notifyReqd = FALSE; + sync_file(); + } + else work_to_do = TRUE; + } + } + if (!work_to_do) break; + if (attempts < 10) sleep(5); + else if (attempts < 20) sleep(60); + else sleep(60*60); + } + exit(0); +} + + diff --git a/usr.sbin/rpc.statd/procs.c b/usr.sbin/rpc.statd/procs.c new file mode 100644 index 0000000..e708e94 --- /dev/null +++ b/usr.sbin/rpc.statd/procs.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + +#include +#include +#include +#include +#include +#include +#include /* for gethostbyname() */ + +#include "statd.h" + +/* sm_stat_1 --------------------------------------------------------------- */ +/* + Purpose: RPC call to enquire if a host can be monitored + Returns: TRUE for any hostname that can be looked up to give + an address. +*/ + +struct sm_stat_res *sm_stat_1(sm_name *arg) +{ + static sm_stat_res res; + + if (debug) syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name); + + if (gethostbyname(arg->mon_name)) res.res_stat = stat_succ; + else + { + syslog(LOG_ERR, "invalid hostname to sm_stat: %s", arg->mon_name); + res.res_stat = stat_fail; + } + + res.state = status_info->ourState; + return (&res); +} + +/* sm_mon_1 ---------------------------------------------------------------- */ +/* + Purpose: RPC procedure to establish a monitor request + Returns: Success, unless lack of resources prevents + the necessary structures from being set up + to record the request, or if the hostname is not + valid (as judged by gethostbyname()) +*/ + +struct sm_stat_res *sm_mon_1(mon *arg) +{ + static sm_stat_res res; + HostInfo *hp; + MonList *lp; + + if (debug) + { + syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_id.mon_name, arg->mon_id.my_id.my_name, + arg->mon_id.my_id.my_prog, arg->mon_id.my_id.my_vers, + arg->mon_id.my_id.my_proc); + } + + res.res_stat = stat_fail; /* Assume fail until set otherwise */ + res.state = status_info->ourState; + + /* Find existing host entry, or create one if not found */ + /* If find_host() fails, it will have logged the error already. */ + if (!gethostbyname(arg->mon_id.mon_name)) + { + syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name); + } + else if (hp = find_host(arg->mon_id.mon_name, TRUE)) + { + lp = (MonList *)malloc(sizeof(MonList)); + if (!lp) + { + syslog(LOG_ERR, "Out of memory"); + } + else + { + strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN); + lp->notifyProg = arg->mon_id.my_id.my_prog; + lp->notifyVers = arg->mon_id.my_id.my_vers; + lp->notifyProc = arg->mon_id.my_id.my_proc; + memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData)); + + lp->next = hp->monList; + hp->monList = lp; + sync_file(); + + res.res_stat = stat_succ; /* Report success */ + } + } + + return (&res); +} + +/* do_unmon ---------------------------------------------------------------- */ +/* + Purpose: Remove a monitor request from a host + Returns: TRUE if found, FALSE if not found. + Notes: Common code from sm_unmon_1 and sm_unmon_all_1 + In the unlikely event of more than one identical monitor + request, all are removed. +*/ + +static int do_unmon(HostInfo *hp, my_id *idp) +{ + MonList *lp, *next; + MonList *last = NULL; + int result = FALSE; + + lp = hp->monList; + while (lp) + { + if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN) + && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc) + && (idp->my_vers == lp->notifyVers)) + { + /* found one. Unhook from chain and free. */ + next = lp->next; + if (last) last->next = next; + else hp->monList = next; + free(lp); + lp = next; + result = TRUE; + } + else + { + last = lp; + lp = lp->next; + } + } + return (result); +} + +/* sm_unmon_1 -------------------------------------------------------------- */ +/* + Purpose: RPC procedure to release a monitor request. + Returns: Local machine's status number + Notes: The supplied mon_id should match the value passed in an + earlier call to sm_mon_1 +*/ + +struct sm_stat *sm_unmon_1(mon_id *arg) +{ + static sm_stat res; + HostInfo *hp; + + if (debug) + { + syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_name, arg->my_id.my_name, arg->my_id.my_prog, + arg->my_id.my_vers, arg->my_id.my_proc); + } + + if (hp = find_host(arg->mon_name, FALSE)) + { + if (do_unmon(hp, &arg->my_id)) sync_file(); + else + { + syslog(LOG_ERR, "unmon request from %s, no matching monitor", + arg->my_id.my_name); + } + } + else syslog(LOG_ERR, "unmon request from %s for unknown host %s", + arg->my_id.my_name, arg->mon_name); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_unmon_all_1 ---------------------------------------------------------- */ +/* + Purpose: RPC procedure to release monitor requests. + Returns: Local machine's status number + Notes: Releases all monitor requests (if any) from the specified + host and program number. +*/ + +struct sm_stat *sm_unmon_all_1(my_id *arg) +{ + static sm_stat res; + HostInfo *hp; + MonList *lp; + int i; + + if (debug) + { + syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d", + arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc); + } + + for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++) + { + do_unmon(hp, arg); + } + sync_file(); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_simu_crash_1 --------------------------------------------------------- */ +/* + Purpose: RPC procedure to simulate a crash + Returns: Nothing + Notes: Standardised mechanism for debug purposes + The specification says that we should drop all of our + status information (apart from the list of monitored hosts + on disc). However, this would confuse the rpc.lockd + which would be unaware that all of its monitor requests + had been silently junked. Hence we in fact retain all + current requests and simply increment the status counter + and inform all hosts on the monitor list. +*/ + +void *sm_simu_crash_1(void) +{ + static char dummy; + int work_to_do; + HostInfo *hp; + int i; + + if (debug) syslog(LOG_DEBUG, "simu_crash called!!"); + + /* Simulate crash by setting notify-required flag on all monitored */ + /* hosts, and incrementing our status number. notify_hosts() is */ + /* then called to fork a process to do the notifications. */ + + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->monList) + { + work_to_do = TRUE; + hp->notifyReqd = TRUE; + } + } + status_info->ourState += 2; /* always even numbers if not crashed */ + + if (work_to_do) notify_hosts(); + + return (&dummy); +} + +/* sm_notify_1 ------------------------------------------------------------- */ +/* + Purpose: RPC procedure notifying local statd of the crash of another + Returns: Nothing + Notes: There is danger of deadlock, since it is quite likely that + the client procedure that we call will in turn call us + to remove or adjust the monitor request. + We therefore fork() a process to do the notifications. + Note that the main HostInfo structure is in a mmap() + region and so will be shared with the child, but the + monList pointed to by the HostInfo is in normal memory. + Hence if we read the monList before forking, we are + protected from the parent servicing other requests + that modify the list. +*/ + +void *sm_notify_1(stat_chge *arg) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + static char dummy; + status tx_arg; /* arg sent to callback procedure */ + MonList *lp; + HostInfo *hp; + pid_t pid; + + if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d", + arg->mon_name, arg->state); + + hp = find_host(arg->mon_name, FALSE); + if (!hp) + { + /* Never heard of this host - why is it notifying us? */ + syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name); + return; + } + lp = hp->monList; + if (!lp) return (FALSE); /* We know this host, but have no */ + /* outstanding requests. */ + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return; + } + if (pid) return (&dummy); /* Parent returns */ + + while (lp) + { + tx_arg.mon_name = arg->mon_name; + tx_arg.state = arg->state; + memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv)); + cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost, + clnt_spcreateerror("")); + } + else + { + if (clnt_call(cli, lp->notifyProc, xdr_status, &tx_arg, xdr_void, &dummy, + timeout) != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to call rpc.statd client at host %s", + lp->notifyHost); + } + clnt_destroy(cli); + } + lp = lp->next; + } + + exit (0); /* Child quits */ +} + diff --git a/usr.sbin/rpc.statd/rpc.statd.8 b/usr.sbin/rpc.statd/rpc.statd.8 new file mode 100644 index 0000000..34b095f --- /dev/null +++ b/usr.sbin/rpc.statd/rpc.statd.8 @@ -0,0 +1,107 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd September 19, 1995 +.Dt RPC.STATD 8 +.Os +.Sh NAME +.Nm rpc.statd +.Nd host status monitoring daemon +.Sh SYNOPSIS +.Nm /sbin/rpc.statd +.Op Fl d +.Sh DESCRIPTION +.Nm rpc.rstatd +is a daemon which co-operates with rpc.statd daemons on other hosts to provide +a status monitoring service. The daemon accepts requests from +programs running on the local host (typically, +.Xr rpc.lockd 8 , +the NFS file locking daemon) to monitor the status of specified +hosts. If a monitored host crashes and restarts, the remote daemon will +notify the local daemon, which in turn will notify the local program(s) +which requested the monitoring service. Conversely, if this host crashes +and re-starts, when the +.Nm rpc.statd +re-starts, it will notify all of the hosts which were being monitored +at the time of the crash. +.Pp +Options and operands available for +.Nm rpc.statd : +.Bl -tag -width Ds +.It Fl d +The +.Fl d +option causes debugging information to be written to syslog, recording +all RPC transations to the daemon. These messages are logged with level +LOG_DEBUG and facility LOG_DAEMON. Error conditions are logged irrespective +of this option, using level LOG_ERR. +.El +.Pp +The +.Nm rpc.rstatd +daemon must NOT be invoked by +.Xr inetd 8 +because the protocol assumes that the daemon will run from system start time. +Instead, it should be run from +.Xr rc 8 +after the network has been started. +.Sh FILES +.Bl -tag -width /usr/include/rpcsvc/sm_inter.x -compact +.It Pa /var/db/statd.status +non-volatile record of currently monitored hosts. +.It Pa /usr/include/rpcsvc/sm_inter.x +RPC protocol specification used by local applications to register monitoring requests. +.El +.Sh SEE ALSO +.Xr rpc.lockd 8 , +.Xr rc 8 , +.Xr syslog 3 +.Sh BUGS +There is no means for the daemon to tell when a monitored host has +disappeared permanently (eg. catastrophic harware failure), as opposed +to transient failure of the host or an intermediate router. At present, +it will re-try notification attempts at frequent intervals for 10 minutes, +then hourly, and finally gives up after 24 hours. + +The protocol requires that symmetric monitor requests are made to both +the local and remote daemon in order to establish a monitored relationship. +This is convenient for the NFS locking protocol, but probably reduces the +usefulness of the monitoring system for other applications. + +The current implementation uses more than 1Kbyte per monitored host in +the status file (and also in VM). This may be inefficient for NFS servers +with large numbers of clients. +.Sh STANDARDS +The implementation is based on the specification in X/Open CAE Specification +C218, "Protocols for X/Open PC Interworking: XNFS, Issue 4", ISBN 1 872630 66 9 diff --git a/usr.sbin/rpc.statd/statd.c b/usr.sbin/rpc.statd/statd.c new file mode 100644 index 0000000..0aaa363 --- /dev/null +++ b/usr.sbin/rpc.statd/statd.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + +/* main() function for status monitor daemon. Some of the code in this */ +/* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */ +/* The actual program logic is in the file procs.c */ + +#include +#include +#include +#include +#include +#include +#include "statd.h" + +#ifndef lint +static char rcsid[] = "$id: $"; +#endif /* not lint */ + +int debug = 0; /* Controls syslog() calls for debug messages */ + +static void sm_prog_1(); +static void handle_sigchld(); + +main(int argc, char **argv) +{ + SVCXPRT *transp; + struct sigaction sa; + + if (argc > 1) + { + if (strcmp(argv[1], "-d")) + { + fprintf(stderr, "Usage: rpc.statd [-d]\n"); + exit(1); + } + debug = 1; + } + + (void)pmap_unset(SM_PROG, SM_VERS); + + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL) + { + fprintf(stderr, "cannot create udp service.\n"); + exit(1); + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_UDP)) + { + fprintf(stderr, "unable to register (SM_PROG, SM_VERS, udp).\n"); + exit(1); + } + + transp = svctcp_create(RPC_ANYSOCK, 0, 0); + if (transp == NULL) + { + fprintf(stderr, "cannot create tcp service.\n"); + exit(1); + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_TCP)) { + fprintf(stderr, "unable to register (SM_PROG, SM_VERS, tcp).\n"); + exit(1); + } + init_file("/var/db/statd.status"); + + /* Note that it is NOT sensible to run this program from inetd - the */ + /* protocol assumes that it will run immediately at boot time. */ + daemon(0, 0); + openlog("rpc.statd", 0, LOG_DAEMON); + if (debug) syslog(LOG_INFO, "Starting - debug enabled"); + else syslog(LOG_INFO, "Starting"); + + /* Install signal handler to collect exit status of child processes */ + sa.sa_handler = handle_sigchld; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGCHLD); + sa.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa, NULL); + + /* Initialisation now complete - start operating */ + notify_hosts(); /* Forks a process (if necessary) to do the */ + /* SM_NOTIFY calls, which may be slow. */ + + svc_run(); /* Should never return */ + exit(1); +} + + +/* handle_sigchld ---------------------------------------------------------- */ +/* + Purpose: Catch SIGCHLD and collect process status + Retruns: Nothing. + Notes: No special action required, other than to collect the + process status and hence allow the child to die: + we only use child processes for asynchronous transmission + of SM_NOTIFY to other systems, so it is normal for the + children to exit when they have done their work. +*/ + +static void handle_sigchld(int sig, int code, struct sigcontext *scp) +{ + int pid, status; + pid = wait4(-1, &status, WNOHANG, (struct rusage*)0); + if (!pid) syslog(LOG_ERR, "Phantom SIGCHLD??"); + else if (status == 0) + { + if (debug) syslog(LOG_DEBUG, "Child %d exited OK", pid); + } + else syslog(LOG_ERR, "Child %d failed with status %d", pid, + WEXITSTATUS(status)); +} + + +/* sm_prog1 ---------------------------------------------------------------- */ +/* + Purpose: Handle one RPC request + Returns: Nothing + Notes: Called from RPC libraray on receipt of a request. + Code for this function was auto-generated by rpcgen. +*/ + +static void +sm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) +{ + union + { + struct sm_name sm_stat_1_arg; + struct mon sm_mon_1_arg; + struct mon_id sm_unmon_1_arg; + struct my_id sm_unmon_all_1_arg; + struct stat_chge sm_notify_1_arg; + } argument; + char *result; + bool_t (*xdr_argument)(), (*xdr_result)(); + char *(*local)(); + + switch (rqstp->rq_proc) + { + case NULLPROC: + (void)svc_sendreply(transp, xdr_void, (char *)NULL); + return; + + case SM_STAT: + xdr_argument = xdr_sm_name; + xdr_result = xdr_sm_stat_res; + local = (char *(*)()) sm_stat_1; + break; + + case SM_MON: + xdr_argument = xdr_mon; + xdr_result = xdr_sm_stat_res; + local = (char *(*)()) sm_mon_1; + break; + + case SM_UNMON: + xdr_argument = xdr_mon_id; + xdr_result = xdr_sm_stat; + local = (char *(*)()) sm_unmon_1; + break; + + case SM_UNMON_ALL: + xdr_argument = xdr_my_id; + xdr_result = xdr_sm_stat; + local = (char *(*)()) sm_unmon_all_1; + break; + + case SM_SIMU_CRASH: + xdr_argument = xdr_void; + xdr_result = xdr_void; + local = (char *(*)()) sm_simu_crash_1; + break; + + case SM_NOTIFY: + xdr_argument = xdr_stat_chge; + xdr_result = xdr_void; + local = (char *(*)()) sm_notify_1; + break; + + default: + svcerr_noproc(transp); + return; + } + + bzero((char *)&argument, sizeof(argument)); + if (!svc_getargs(transp, xdr_argument, (caddr_t)&argument)) + { + svcerr_decode(transp); + return; + } + result = (*local)(&argument, rqstp); + if (result != NULL && !svc_sendreply(transp, xdr_result, result)) + { + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, xdr_argument, (caddr_t)&argument)) + { + syslog(LOG_ERR, "unable to free arguments"); + exit(1); + } +} + diff --git a/usr.sbin/rpc.statd/statd.h b/usr.sbin/rpc.statd/statd.h new file mode 100644 index 0000000..63c4738 --- /dev/null +++ b/usr.sbin/rpc.statd/statd.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + + + +#include + +/* These pieces are missing from the distributed sm_inter.x, which */ +/* omits the SM_NOTIFY procedure used between cooperating rpc.statd's */ + +#define SM_NOTIFY ((u_long)6) +extern void *sm_notify_1(); + +struct stat_chge +{ + char *mon_name; + int state; +}; +typedef struct stat_chge stat_chge; +bool_t xdr_stat_chge(); + +/* ------------------------------------------------------------------------- */ +/* + Data structures for recording monitored hosts + + The information held by the status monitor comprises a list of hosts + that we have been asked to monitor, and, associated with each monitored + host, one or more clients to be called back if the monitored host crashes. + + The list of monitored hosts must be retained over a crash, so that upon + re-boot we can call the SM_NOTIFY procedure in all those hosts so as to + cause them to start recovery processing. On the other hand, the client + call-backs are not required to be preserved: they are assumed (in the + protocol design) to be local processes which will have crashed when + we did, and so are discarded on restart. + + We handle this by keeping the list of monitored hosts in a file + (/var/statd.state) which is mmap()ed and whose format is described + by the typedef FileLayout. The lists of client callbacks are chained + off this structure, but are held in normal memory and so will be + lost after a re-boot. Hence the actual values of MonList * pointers + in the copy on disc have no significance, but their NULL/non-NULL + status indicates whether this host is actually being monitored or if it + is an empty slot in the file. +*/ + +typedef struct MonList_s +{ + struct MonList_s *next; /* Next in list or NULL */ + char notifyHost[SM_MAXSTRLEN + 1]; /* Host to notify */ + int notifyProg; /* RPC program number to call */ + int notifyVers; /* version number */ + int notifyProc; /* procedure number */ + unsigned char notifyData[16]; /* Opaque data from caller */ +} MonList; + +typedef struct +{ + char hostname[SM_MAXSTRLEN + 1]; /* Name of monitored host */ + int notifyReqd; /* TRUE if we've crashed and not yet */ + /* informed the monitored host */ + MonList *monList; /* List of clients to inform if we */ + /* hear that the monitored host has */ + /* crashed, NULL if no longer monitored */ +} HostInfo; + + +/* Overall file layout. */ + +typedef struct +{ + int ourState; /* State number as defined in statd protocol */ + int noOfHosts; /* Number of elements in hosts[] */ + char reserved[248]; /* Reserved for future use */ + HostInfo hosts[1]; /* vector of monitored hosts */ +} FileLayout; + +#define HEADER_LEN (sizeof(FileLayout) - sizeof(HostInfo)) + +/* ------------------------------------------------------------------------- */ + +/* Global variables */ + +extern FileLayout *status_info; /* The mmap()ed status file */ + +extern int debug; /* =1 to enable diagnostics to syslog */ + +/* Function prototypes */ + +extern HostInfo *find_host(char * /*hostname*/, int /*create*/); +extern void init_file(char * /*filename*/); +extern void notify_hosts(void); +extern void sync_file(void); diff --git a/usr.sbin/rpc.statd/test.c b/usr.sbin/rpc.statd/test.c new file mode 100644 index 0000000..e864485 --- /dev/null +++ b/usr.sbin/rpc.statd/test.c @@ -0,0 +1,138 @@ +#include +#include +#include + + +/* Default timeout can be changed using clnt_control() */ +static struct timeval TIMEOUT = { 25, 0 }; + +struct sm_stat_res * +sm_stat_1(argp, clnt) + struct sm_name *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_STAT, xdr_sm_name, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat_res * +sm_mon_1(argp, clnt) + struct mon *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_MON, xdr_mon, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_1(argp, clnt) + struct mon_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON, xdr_mon_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_all_1(argp, clnt) + struct my_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON_ALL, xdr_my_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +void * +sm_simu_crash_1(argp, clnt) + void *argp; + CLIENT *clnt; +{ + static char res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_SIMU_CRASH, xdr_void, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return ((void *)&res); +} + + +int main(int argc, char **argv) +{ + CLIENT *cli; + char dummy; + void *out; + struct mon mon; + + if (argc < 2) + { + fprintf(stderr, "usage: test | crash\n"); + fprintf(stderr, "Always talks to statd at localhost\n"); + exit(1); + } + + printf("Creating client for localhost\n" ); + cli = clnt_create("localhost", SM_PROG, SM_VERS, "udp"); + if (!cli) + { + printf("Failed to create client\n"); + exit(1); + } + + mon.mon_id.mon_name = argv[1]; + mon.mon_id.my_id.my_name = argv[1]; + mon.mon_id.my_id.my_prog = SM_PROG; + mon.mon_id.my_id.my_vers = SM_VERS; + mon.mon_id.my_id.my_proc = 1; /* have it call sm_stat() !!! */ + + if (strcmp(argv[1], "crash")) + { + /* Hostname given */ + struct sm_stat_res *res; + if (res = sm_mon_1(&mon, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + else + { + if (out = sm_simu_crash_1(&dummy, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + + return 0; +} -- cgit v1.1