Import Jan 15 version of Andrew Gordon <andrew.gordon@net-tel.co.uk>'s

rpc.statd.

This is apparently fully functional and complete.
This commit is contained in:
Peter Wemm 1996-02-17 15:14:59 +00:00
parent 503d2aa8a2
commit 1494289f5b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/cvs2svn/branches/A_GORDON/; revision=14125
14 changed files with 2642 additions and 0 deletions

13
sbin/rpc.statd/Makefile Normal file
View File

@ -0,0 +1,13 @@
# $Id$
PROG = rpc.statd
SRCS = statd.c procs.c file.c
MAN8 = rpc.statd.8
DPADD= ${LIBRPCSVC}
LDADD= -lrpcsvc
.include <bsd.prog.mk>
test: test.c
cc -o test test.c -lrpcsvc

356
sbin/rpc.statd/file.c Normal file
View File

@ -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 <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h> /* For mmap() */
#include <rpc/rpc.h>
#include <syslog.h>
#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);
}

352
sbin/rpc.statd/procs.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <rpc/rpc.h>
#include <syslog.h>
#include <netdb.h> /* 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 */
}

107
sbin/rpc.statd/rpc.statd.8 Normal file
View File

@ -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

232
sbin/rpc.statd/statd.c Normal file
View File

@ -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 <stdio.h>
#include <rpc/rpc.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#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);
}
}

123
sbin/rpc.statd/statd.h Normal file
View File

@ -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 <rpcsvc/sm_inter.h>
/* 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);

138
sbin/rpc.statd/test.c Normal file
View File

@ -0,0 +1,138 @@
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpcsvc/sm_inter.h>
/* 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 <hostname> | 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;
}

View File

@ -0,0 +1,13 @@
# $Id$
PROG = rpc.statd
SRCS = statd.c procs.c file.c
MAN8 = rpc.statd.8
DPADD= ${LIBRPCSVC}
LDADD= -lrpcsvc
.include <bsd.prog.mk>
test: test.c
cc -o test test.c -lrpcsvc

356
usr.sbin/rpc.statd/file.c Normal file
View File

@ -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 <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h> /* For mmap() */
#include <rpc/rpc.h>
#include <syslog.h>
#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);
}

352
usr.sbin/rpc.statd/procs.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <rpc/rpc.h>
#include <syslog.h>
#include <netdb.h> /* 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 */
}

View File

@ -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

232
usr.sbin/rpc.statd/statd.c Normal file
View File

@ -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 <stdio.h>
#include <rpc/rpc.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#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);
}
}

123
usr.sbin/rpc.statd/statd.h Normal file
View File

@ -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 <rpcsvc/sm_inter.h>
/* 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);

138
usr.sbin/rpc.statd/test.c Normal file
View File

@ -0,0 +1,138 @@
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpcsvc/sm_inter.h>
/* 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 <hostname> | 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;
}