1 /* $NetBSD: statd.c,v 1.28 2007/12/15 19:44:56 perry Exp $ */
4 * Copyright (c) 1997 Christos Zoulas. All rights reserved.
6 * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed for the FreeBSD project
19 * This product includes software developed by Christos Zoulas.
20 * 4. Neither the name of the author nor the names of any co-contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #include <sys/cdefs.h>
40 __RCSID("$NetBSD: statd.c,v 1.28 2007/12/15 19:44:56 perry Exp $");
43 /* main() function for status monitor daemon. Some of the code in this */
44 /* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */
45 /* The actual program logic is in the file procs.c */
47 #include <sys/param.h>
62 #include <netconfig.h>
69 int debug
= 0; /* Controls syslog() for debug msgs */
70 int _rpcsvcdirty
= 0; /* XXX ??? */
71 static DB
*db
; /* Database file */
75 static char undefdata
[] = "\0\1\2\3\4\5\6\7";
76 static DBT undefkey
= {
83 static int walk_one
__P((int (*fun
)__P ((DBT
*, HostInfo
*, void *)), DBT
*, DBT
*, void *));
84 static int walk_db
__P((int (*fun
)__P ((DBT
*, HostInfo
*, void *)), void *));
85 static int reset_host
__P((DBT
*, HostInfo
*, void *));
86 static int check_work
__P((DBT
*, HostInfo
*, void *));
87 static int unmon_host
__P((DBT
*, HostInfo
*, void *));
88 static int notify_one
__P((DBT
*, HostInfo
*, void *));
89 static void init_file
__P((const char *));
90 static int notify_one_host
__P((const char *));
91 static void die
__P((int)) __dead
;
93 int main
__P((int, char **));
101 struct sigaction nsa
;
102 int maxrec
= RPC_MAXDATASIZE
;
104 while ((ch
= getopt(argc
, argv
, "d")) != (-1)) {
111 (void)fprintf(stderr
, "usage: %s [-d]\n",
117 (void)rpcb_unset(SM_PROG
, SM_VERS
, NULL
);
119 rpc_control(RPC_SVC_CONNMAXREC_SET
, &maxrec
);
121 if (!svc_create(sm_prog_1
, SM_PROG
, SM_VERS
, "udp")) {
122 errx(1, "cannot create udp service.");
125 if (!svc_create(sm_prog_1
, SM_PROG
, SM_VERS
, "tcp")) {
126 errx(1, "cannot create udp service.");
130 init_file("/var/db/statd.status");
133 * Note that it is NOT sensible to run this program from inetd - the
134 * protocol assumes that it will run immediately at boot time.
139 sigemptyset(&nsa
.sa_mask
);
140 nsa
.sa_flags
= SA_NOCLDSTOP
|SA_NOCLDWAIT
;
141 nsa
.sa_handler
= SIG_IGN
;
142 (void)sigaction(SIGCHLD
, &nsa
, NULL
);
145 openlog("rpc.statd", 0, LOG_DAEMON
);
147 syslog(LOG_INFO
, "Starting - debug enabled");
149 syslog(LOG_INFO
, "Starting");
153 sigemptyset(&sa
.sa_mask
);
154 (void)sigaction(SIGTERM
, &sa
, NULL
);
155 (void)sigaction(SIGQUIT
, &sa
, NULL
);
156 (void)sigaction(SIGHUP
, &sa
, NULL
);
157 (void)sigaction(SIGINT
, &sa
, NULL
);
159 sa
.sa_handler
= SIG_IGN
;
160 sa
.sa_flags
= SA_RESTART
;
161 sigemptyset(&sa
.sa_mask
);
162 sigaddset(&sa
.sa_mask
, SIGALRM
);
164 /* Initialisation now complete - start operating */
166 /* Notify hosts that need it */
170 svc_run(); /* Should never return */
174 /* notify_handler ---------------------------------------------------------- */
176 * Purpose: Catch SIGALRM and collect process status
178 * Notes: No special action required, other than to collect the
179 * process status and hence allow the child to die:
180 * we only use child processes for asynchronous transmission
181 * of SM_NOTIFY to other systems, so it is normal for the
182 * children to exit when they have done their work.
191 sa
.sa_handler
= SIG_IGN
;
192 (void)sigaction(SIGALRM
, &sa
, NULL
);
196 (void) walk_db(notify_one
, &now
);
198 if (walk_db(check_work
, &now
) == 0) {
200 * No more work to be done.
210 /* sync_file --------------------------------------------------------------- */
212 * Purpose: Packaged call of msync() to flush changes to mmap()ed file
213 * Returns: Nothing. Errors to syslog.
220 data
.data
= &status_info
;
221 data
.size
= sizeof(status_info
);
222 switch ((*db
->put
)(db
, &undefkey
, &data
, 0)) {
230 if ((*db
->sync
)(db
, 0) == -1) {
232 syslog(LOG_ERR
, "database corrupted %m");
237 /* change_host -------------------------------------------------------------- */
239 * Purpose: Update/Create an entry for host
245 change_host(hostnamep
, hp
)
251 char hostname
[MAXHOSTNAMELEN
+ 1];
254 strncpy(hostname
, hostnamep
, MAXHOSTNAMELEN
+ 1);
257 for (ptr
= hostname
; *ptr
; ptr
++)
258 if (isupper((unsigned char) *ptr
))
259 *ptr
= tolower((unsigned char) *ptr
);
262 key
.size
= ptr
- hostname
+ 1;
264 data
.size
= sizeof(h
);
266 switch ((*db
->put
)(db
, &key
, &data
, 0)) {
268 syslog(LOG_ERR
, "database corrupted %m");
278 /* find_host -------------------------------------------------------------- */
280 * Purpose: Find the entry in the status file for a given host
281 * Returns: Copy of entry in hd, or NULL
286 find_host(hostname
, hp
)
293 for (ptr
= hostname
; *ptr
; ptr
++)
294 if (isupper((unsigned char) *ptr
))
295 *ptr
= tolower((unsigned char) *ptr
);
298 key
.size
= ptr
- hostname
+ 1;
299 switch ((*db
->get
)(db
, &key
, &data
, 0)) {
301 if (data
.size
!= sizeof(*hp
))
303 return memcpy(hp
, data
.data
, sizeof(*hp
));
313 syslog(LOG_ERR
, "Database corrupted %m");
317 /* walk_one ------------------------------------------------------------- */
319 * Purpose: Call the given function if the element is valid
320 * Returns: Nothing - exits on error
324 walk_one(fun
, key
, data
, ptr
)
325 int (*fun
) __P((DBT
*, HostInfo
*, void *));
330 if (key
->size
== undefkey
.size
&&
331 memcmp(key
->data
, undefkey
.data
, key
->size
) == 0)
333 if (data
->size
!= sizeof(HostInfo
)) {
334 syslog(LOG_ERR
, "Bad data in database");
337 memcpy(&h
, data
->data
, sizeof(h
));
338 return (*fun
)(key
, &h
, ptr
);
341 /* walk_db -------------------------------------------------------------- */
343 * Purpose: Iterate over all elements calling the given function
344 * Returns: -1 if function failed, 0 on success
349 int (*fun
) __P((DBT
*, HostInfo
*, void *));
354 switch ((*db
->seq
)(db
, &key
, &data
, R_FIRST
)) {
358 /* We should have at least the magic entry at this point */
361 if (walk_one(fun
, &key
, &data
, ptr
) == -1)
370 switch ((*db
->seq
)(db
, &key
, &data
, R_NEXT
)) {
374 if (walk_one(fun
, &key
, &data
, ptr
) == -1)
383 syslog(LOG_ERR
, "Corrupted database %m");
387 /* reset_host ------------------------------------------------------------ */
389 * Purpose: Clean up existing hosts in file.
390 * Returns: Always success 0.
391 * Notes: Clean-up of existing file - monitored hosts will have a
392 * pointer to a list of clients, which refers to memory in
393 * the previous incarnation of the program and so are
394 * meaningless now. These pointers are zeroed and the fact
395 * that the host was previously monitored is recorded by
396 * setting the notifyReqd flag, which will in due course
397 * cause a SM_NOTIFY to be sent.
399 * Note that if we crash twice in quick succession, some hosts
400 * may already have notifyReqd set, where we didn't manage to
401 * notify them before the second crash occurred.
404 reset_host(key
, hi
, ptr
)
411 hi
->notifyReqd
= *(time_t *) ptr
;
414 change_host((char *)key
->data
, hi
);
419 /* check_work ------------------------------------------------------------ */
421 * Purpose: Check if there is work to be done.
422 * Returns: 0 if there is no work to be done -1 if there is.
426 check_work(key
, hi
, ptr
)
431 return hi
->notifyReqd
? -1 : 0;
434 /* unmon_host ------------------------------------------------------------ */
436 * Purpose: Unmonitor a host
441 unmon_host(key
, hi
, ptr
)
446 char *name
= key
->data
;
448 if (do_unmon(name
, hi
, ptr
))
449 change_host(name
, hi
);
453 /* notify_one ------------------------------------------------------------ */
455 * Purpose: Notify one host.
456 * Returns: 0 if success -1 on failure
460 notify_one(key
, hi
, ptr
)
465 time_t now
= *(time_t *) ptr
;
466 char *name
= key
->data
;
469 if (hi
->notifyReqd
== 0 || hi
->notifyReqd
> now
)
473 * If one of the initial attempts fails, we wait
474 * for a while and have another go. This is necessary
475 * because when we have crashed, (eg. a power outage)
476 * it is quite possible that we won't be able to
477 * contact all monitored hosts immediately on restart,
478 * either because they crashed too and take longer
479 * to come up (in which case the notification isn't
480 * really required), or more importantly if some
481 * router etc. needed to reach the monitored host
482 * has not come back up yet. In this case, we will
483 * be a bit late in re-establishing locks (after the
484 * grace period) but that is the best we can do. We
485 * try 10 times at 5 sec intervals, 10 more times at
486 * 1 minute intervals, then 24 more times at hourly
487 * intervals, finally giving up altogether if the
488 * host hasn't come back to life after 24 hours.
490 if (notify_one_host(name
) || hi
->attempts
++ >= 44) {
496 if (hi
->attempts
< 10)
498 else if (hi
->attempts
< 20)
499 hi
->notifyReqd
+= 60;
501 hi
->notifyReqd
+= 60 * 60;
503 change_host(name
, hi
);
507 /* init_file -------------------------------------------------------------- */
509 * Purpose: Open file, create if necessary, initialise it.
510 * Returns: Nothing - exits on error
511 * Notes: Called before process becomes daemon, hence logs to
512 * stderr rather than syslog.
513 * Opens the file, then mmap()s it for ease of access.
514 * Also performs initial clean-up of the file, zeroing
515 * monitor list pointers, setting the notifyReqd flag in
516 * all hosts that had a monitor list, and incrementing
517 * the state number to the next even value.
521 const char *filename
;
525 db
= dbopen(filename
, O_RDWR
|O_CREAT
|O_NDELAY
|O_EXLOCK
, 0644, DB_HASH
,
528 err(1, "Cannot open `%s'", filename
);
530 switch ((*db
->get
)(db
, &undefkey
, &data
, 0)) {
533 (void)memset(&status_info
, 0, sizeof(status_info
));
538 err(1, "error accessing database (%m)");
540 /* Existing database */
541 if (data
.size
!= sizeof(status_info
))
542 errx(1, "database corrupted %lu != %lu",
543 (u_long
)data
.size
, (u_long
)sizeof(status_info
));
544 memcpy(&status_info
, data
.data
, data
.size
);
554 /* reset_database --------------------------------------------------------- */
556 * Purpose: Clears the statd database
558 * Notes: If this is not called on reset, it will leak memory.
563 time_t now
= time(NULL
);
564 walk_db(reset_host
, &now
);
566 /* Select the next higher even number for the state counter */
567 status_info
.ourState
=
568 (status_info
.ourState
+ 2) & 0xfffffffe;
569 status_info
.ourState
++; /* XXX - ??? */
573 /* unmon_hosts --------------------------------------------------------- */
575 * Purpose: Unmonitor all the hosts
582 time_t now
= time(NULL
);
583 walk_db(unmon_host
, &now
);
588 notify_one_host(hostname
)
589 const char *hostname
;
591 struct timeval timeout
= {20, 0}; /* 20 secs timeout */
595 char our_hostname
[MAXHOSTNAMELEN
+ 1];
597 gethostname(our_hostname
, sizeof(our_hostname
));
598 our_hostname
[sizeof(our_hostname
) - 1] = '\0';
599 arg
.mon_name
= our_hostname
;
600 arg
.state
= status_info
.ourState
;
603 syslog(LOG_DEBUG
, "Sending SM_NOTIFY to host %s from %s",
604 hostname
, our_hostname
);
606 cli
= clnt_create(hostname
, SM_PROG
, SM_VERS
, "udp");
608 syslog(LOG_ERR
, "Failed to contact host %s%s", hostname
,
609 clnt_spcreateerror(""));
612 if (clnt_call(cli
, SM_NOTIFY
, xdr_stat_chge
, &arg
, xdr_void
,
613 &dummy
, timeout
) != RPC_SUCCESS
) {
614 syslog(LOG_ERR
, "Failed to contact rpc.statd at host %s",