1/*
2 * Copyright (c) 2002-2010 Apple Inc.  All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * Copyright (c) 1995
25 *	A.R. Gordon (andrew.gordon@net-tel.co.uk).  All rights reserved.
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 *    notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 *    notice, this list of conditions and the following disclaimer in the
34 *    documentation and/or other materials provided with the distribution.
35 * 3. All advertising materials mentioning features or use of this software
36 *    must display the following acknowledgement:
37 *	This product includes software developed for the FreeBSD project
38 * 4. Neither the name of the author nor the names of any co-contributors
39 *    may be used to endorse or promote products derived from this software
40 *    without specific prior written permission.
41 *
42 * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
43 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
45 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
46 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
48 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
50 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
51 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 *
54 * $FreeBSD$
55 *
56 */
57
58#include <err.h>
59#include <errno.h>
60#include <fcntl.h>
61#include <stdio.h>
62#include <string.h>
63#include <unistd.h>
64#include <search.h>
65#include <sys/types.h>
66#include <sys/stat.h>
67#include <sys/mman.h>		/* For mmap()				 */
68#include <oncrpc/rpc.h>
69#include <syslog.h>
70#include <stdlib.h>
71#include <sys/param.h>
72#include <sys/queue.h>
73#include <libutil.h>
74#include <libkern/OSByteOrder.h>
75
76#include "statd.h"
77
78FileHeader *status_info;	/* Pointer to the mmap()ed status file	 */
79static int status_fd;		/* File descriptor for the open file	 */
80static off_t status_file_len;	/* Current on-disc length of file	 */
81static off_t status_info_len;	/* current length of mmap region	 */
82void *mhroot = NULL;
83
84/* map_file --------------------------------------------------------------- */
85/*
86   Purpose:	Set up or update status file memory mapping.
87		Map with a generous size to allow file to grow without
88		needing to remap often.
89   Returns:	Nothing.  Errors to syslog.
90*/
91#define MAP_INCREMENT	(32*1024*1024)
92
93void
94map_file(void)
95{
96	off_t desired_map_len = (status_file_len + MAP_INCREMENT) & ~(MAP_INCREMENT - 1);
97	int prot;
98
99	if (status_info_len >= desired_map_len)
100		return;
101	if (status_info_len) {
102		sync_file();
103		munmap(status_info, status_info_len);
104	}
105	prot = PROT_READ;
106	if (!list_only)
107		prot |= PROT_WRITE;
108	status_info_len = desired_map_len;
109	status_info = mmap(NULL, status_info_len, prot, MAP_SHARED, status_fd, 0);
110	if (status_info == (FileHeader *) MAP_FAILED) {
111		log(LOG_ERR, "unable to mmap() status file");
112		exit(1);
113	}
114}
115
116/* sync_file --------------------------------------------------------------- */
117/*
118   Purpose:	Packaged call of msync() to flush changes to mmap()ed file
119   Returns:	Nothing.  Errors to syslog.
120*/
121
122void
123sync_file(void)
124{
125	if (msync((void *) status_info, status_file_len, MS_SYNC) < 0 &&
126	    msync((void *) status_info, status_file_len, MS_SYNC) < 0) {
127		log(LOG_ERR, "msync(%p,0x%llx) failed: %s", status_info, status_file_len, strerror(errno));
128	}
129}
130
131/* mhcmp ------------------------------------------------------------------- */
132/*
133   Purpose:	comparing nodes in the MonitoredHost tree
134   Returns:	-1, 0, 1
135*/
136
137int
138mhcmp(const void *key1, const void *key2)
139{
140	const MonitoredHost *mhp1 = key1;
141	const MonitoredHost *mhp2 = key2;
142	char *s1, *s2;
143
144	s1 = mhp1->mh_hostinfo_offset ?
145		HOSTINFO(mhp1->mh_hostinfo_offset)->hi_name : mhp1->mh_name;
146	s2 = mhp2->mh_hostinfo_offset ?
147		HOSTINFO(mhp2->mh_hostinfo_offset)->hi_name : mhp2->mh_name;
148	return strcmp(s1, s2);
149}
150
151#if 0				/* DEBUG */
152
153/* mhdump/mhprint ---------------------------------------------------------- */
154/*
155   Purpose:	dumping the monitored host tree - for debugging purposes
156   Returns:	Nothing
157*/
158
159void
160mhprint(const void *node, VISIT v, __unused int level)
161{
162	const MonitoredHost *const * mhp = node;
163	char *name;
164
165	if ((v != postorder) && (v != leaf))
166		return;
167
168	name = (*mhp)->mh_hostinfo_offset ?
169		HOSTINFO((*mhp)->mh_hostinfo_offset)->hi_name : (*mhp)->mh_name;
170	log(LOG_ERR, "%s", name);
171}
172
173void
174mhdump(void)
175{
176	log(LOG_ERR, "Monitored Host List:");
177	twalk(mhroot, mhprint);
178}
179
180#endif
181
182/* find_host -------------------------------------------------------------- */
183/*
184   Purpose:	Find the MonitoredHost and its entry in the status file for a given host
185   Returns:	MonitoredHost structure for this host, or NULL.
186   Notes:	Also creates structures/entries if requested.
187		Failure to create also returns NULL.
188*/
189
190MonitoredHost *
191find_host(char *hostname, int create)
192{
193	MonitoredHost mhtmp, **mhpp, *mhp;
194	HostInfo *hip = NULL;
195	HostInfo *spare_slot = NULL;
196	off_t off, spare_off = status_file_len;
197	int rv, namelen;
198	uint i;
199	uint16_t len, nlen;
200
201	namelen = strlen(hostname);
202	len = sizeof(HostInfo) - NAMEINCR + RNDUP_NAMELEN(namelen);
203
204	/* try to find the host in the current set of monitored hosts	 */
205	bzero(&mhtmp, sizeof(mhtmp));
206	mhtmp.mh_name = hostname;
207	mhpp = tfind(&mhtmp, &mhroot, mhcmp);
208
209	/* Return if entry found, or if not asked to create one.	 */
210	if (mhpp)
211		return (*mhpp);
212	else if (!create)
213		return (NULL);
214
215	/* not currently in the set of monitored hosts, allocate one	 */
216	mhp = malloc(sizeof(*mhp));
217	if (!mhp)
218		return (NULL);
219	bzero(mhp, sizeof(*mhp));
220
221	/* find the host's slot in the status file			 */
222	off = sizeof(FileHeader);
223	for (i = 0; i < ntohl(status_info->fh_reccnt); i++) {
224		hip = HOSTINFO(off);
225		if ((ntohs(hip->hi_namelen) == namelen) && !strncasecmp(hostname, hip->hi_name, namelen))
226			break;
227		if (!spare_slot && !hip->hi_monitored && !hip->hi_notify && (len <= ntohs(hip->hi_len))) {
228			spare_slot = hip;
229			spare_off = off;
230		}
231		off += ntohs(hip->hi_len);
232		hip = NULL;
233	}
234
235	/* Now create an entry, using the spare slot if one was found or	 */
236	/* adding to the end of the list otherwise, extending file if reqd	 */
237	if (!hip && !spare_slot) {
238		off = spare_off = status_file_len;
239		i = 0;
240		rv = pwrite(status_fd, &i, 1, off + len - 1);
241		if (rv < 1) {
242			free(mhp);
243			log(LOG_ERR, "Unable to extend status file");
244			return (NULL);
245		}
246		nlen = htons(len);
247		rv = pwrite(status_fd, &nlen, sizeof(nlen), off);
248		if ((rv < 2) || (rv = fsync(status_fd)))
249			log(LOG_ERR, "Unable to write extended status file record length: %d %s", rv, strerror(errno));
250		status_file_len = off + len;
251		map_file();
252		spare_slot = HOSTINFO(off);
253		status_info->fh_reccnt = htonl(ntohl(status_info->fh_reccnt) + 1);
254	}
255	if (!hip) {
256		/* Initialise the spare slot that has been found/created		 */
257		off = spare_off;
258		spare_slot->hi_len = htons(len);
259		spare_slot->hi_monitored = 0;
260		spare_slot->hi_notify = 0;
261		spare_slot->hi_namelen = htons(namelen);
262		strlcpy(spare_slot->hi_name, hostname, namelen+1);
263		bzero(&spare_slot->hi_name[namelen], RNDUP_NAMELEN(namelen) - namelen);
264		hip = spare_slot;
265	}
266	mhp->mh_hostinfo_offset = off;
267
268	/* insert new host into monitored host set */
269	mhpp = tsearch(mhp, &mhroot, mhcmp);
270	if (!mhpp) {
271		/* insert failed */
272		if (hip == spare_slot)
273			sync_file();
274		free(mhp);
275		return (NULL);
276	}
277	if (!hip->hi_monitored) {
278		/* update monitored status */
279		hip->hi_monitored = htons(1);
280		sync_file();
281	}
282	return (mhp);
283}
284
285/* convert_version0_file  -------------------------------------------------- */
286/*
287   Purpose:	converts old-style status file to new style
288   Returns:	Whether status file needs reinitialization (conversion failed)
289*/
290int
291convert_version0_file(const char *filename0)
292{
293	int fd0, fd = -1, len, namelen, rv, nhosts0, nhosts, i, newcreated = 0;
294	struct stat st;
295	FileHeader fh;
296	HostInfo0 *hi0p = NULL;
297	HostInfo *hip = NULL;
298	char *filename = NULL;
299	int filenamelen;
300
301	log(LOG_NOTICE, "converting old status file");
302
303	/* open old file */
304	fd0 = open(filename0, O_RDONLY);
305	if (fd0 < 0) {
306		log(LOG_ERR, "can't open status file: %s", strerror(errno));
307		goto fail;
308	}
309	rv = fstat(fd0, &st);
310	if (rv < 0) {
311		log(LOG_ERR, "can't fstat open status file: %s", strerror(errno));
312		goto fail;
313	}
314	/* read header0 */
315	rv = read(fd0, &fh, sizeof(fh));
316	if (rv != sizeof(fh)) {
317		log(LOG_ERR, "can't read status file header: %d, %s", rv, strerror(errno));
318		goto fail;
319	}
320	if (fh.fh_version != 0) {
321		log(LOG_ERR, "invalid status file header version: 0x%x", ntohl(fh.fh_version));
322		goto fail;
323	}
324	if (fh.fh_state > 2000000) {
325		/* assume endianness changed */
326		fh.fh_state = OSSwapInt32(fh.fh_state);
327		fh.fh_reccnt = OSSwapInt32(fh.fh_reccnt);
328	}
329	nhosts0 = fh.fh_reccnt;
330	/* verify nhosts0 vs file length */
331	if (st.st_size < ((off_t) sizeof(FileHeader) + (off_t) sizeof(HostInfo0) * nhosts0)) {
332		log(LOG_ERR, "status file header (state %d) host count %d exceeds file size: %lld (%lld)",
333		    fh.fh_state, nhosts0, st.st_size, ((off_t) sizeof(FileHeader) + (off_t) sizeof(HostInfo0) * nhosts0));
334		goto fail;
335	}
336	/* allocate maximally-sized hostinfo structs */
337	hi0p = malloc(sizeof(*hi0p));
338	hip = malloc(sizeof(*hip) + SM_MAXSTRLEN);
339
340	/* create new file */
341	len = strlen(filename0);
342	filenamelen = len + 4 + 1;
343	filename = malloc(filenamelen);
344	if (!hi0p || !hip || !filename) {
345		log(LOG_ERR, "can't allocate memory");
346		goto fail;
347	}
348	strlcpy(filename, filename0, filenamelen);
349	strlcat(filename, ".new", filenamelen);
350
351	fd = open(filename, O_RDWR | O_CREAT, 0644);
352	if (fd < 0) {
353		log(LOG_ERR, "can't open new status file: %s", strerror(errno));
354		goto fail;
355	}
356	newcreated = 1;
357
358	/* write header */
359	fh.fh_version = htonl(STATUS_DB_VERSION);
360	fh.fh_state = htonl(fh.fh_state);
361	fh.fh_reccnt = htonl(fh.fh_reccnt);
362	rv = write(fd, &fh, sizeof(fh));
363	if (rv != sizeof(fh)) {
364		log(LOG_ERR, "can't write new status file header: %d %s", rv, strerror(errno));
365		goto fail;
366	}
367	/* copy/convert all records in use */
368	nhosts = 0;
369	for (i = 0; i < nhosts0; i++) {
370		/* read each hostinfo0 */
371		rv = read(fd0, hi0p, sizeof(*hi0p));
372		if (rv != sizeof(*hi0p)) {
373			log(LOG_ERR, "can't read status file entry %d: %d %s", i, rv, strerror(errno));
374			goto fail;
375		}
376		/* write each hostinfo */
377		hip->hi_monitored = htons((hi0p->monList != 0) ? 1 : 0);
378		hip->hi_notify = htons((hi0p->notifyReqd != 0) ? 1 : 0);
379		if (!hip->hi_monitored && !hip->hi_notify)
380			continue;
381		namelen = strlen(hi0p->hostname);
382		if (namelen > SM_MAXSTRLEN) {
383			log(LOG_ERR, "status file entry %d, name too long: %d", i, namelen);
384			goto fail;
385		}
386		strlcpy(hip->hi_name, hi0p->hostname, namelen+1);
387		hip->hi_namelen = htons(namelen);
388		len = sizeof(*hip) - NAMEINCR + RNDUP_NAMELEN(namelen);
389		hip->hi_len = htons(len);
390		bzero(&hip->hi_name[namelen], RNDUP_NAMELEN(namelen) - namelen);
391		rv = write(fd, hip, len);
392		if (rv != len) {
393			log(LOG_ERR, "can't write new status file entry %d: %d %s", i, rv, strerror(errno));
394			goto fail;
395		}
396		nhosts++;
397	}
398
399	/* update header */
400	fh.fh_reccnt = htonl(nhosts);
401	rv = pwrite(fd, &fh, sizeof(fh), 0);
402	if ((rv != sizeof(fh)) || (rv = fsync(status_fd))) {
403		log(LOG_ERR, "can't update new status file header: %d %s", rv, strerror(errno));
404		goto fail;
405	}
406	free(hip);
407	free(hi0p);
408	close(fd);
409	close(fd0);
410	rv = rename(filename, filename0);
411	if (rv < 0)
412		log(LOG_ERR, "can't rename new status file into place: %d %s", rv, strerror(errno));
413	free(filename);
414	return (rv);
415fail:
416	if (hip)
417		free(hip);
418	if (hi0p)
419		free(hi0p);
420	if (fd >= 0)
421		close(fd);
422	if (newcreated)
423		unlink(filename);
424	if (filename)
425		free(filename);
426	if (fd0 >= 0)
427		close(fd0);
428	return (1);
429}
430
431/* init_file -------------------------------------------------------------- */
432/*
433   Purpose:	Open file, create if necessary, initialise it.
434   Returns:	Whether notifications are needed - exits on error
435   Notes:	Opens the status file, verifies its integrity, and
436   		sets up the mmap() access.
437		Also performs initial cleanup of the file, clearing
438		monitor status, setting the notify flag for hosts
439		that were previously monitored, and advancing the
440		state number.
441*/
442
443int
444init_file(const char *filename)
445{
446	int new_file = FALSE;
447	struct flock lock = {0};
448	int rv, len, err, update, need_notify = FALSE;
449	uint i;
450	FileHeader fh;
451	HostInfo *hip = NULL;
452	off_t off;
453
454	/*
455	 * only update if we're statd, or if we're statd.notify and statd
456	 * isn't running
457	 */
458	if (statd_server || (notify_only && !get_statd_pid()))
459		update = 1;
460	else
461		update = 0;
462
463reopen:
464	/* try to open existing file - if not present, create one		 */
465	status_fd = open(filename, list_only ? O_RDONLY : O_RDWR);
466	if ((status_fd < 0) && (errno == ENOENT) && statd_server) {
467		status_fd = open(filename, O_RDWR | O_CREAT, 0644);
468		new_file = TRUE;
469	}
470	if (status_fd < 0) {
471		if (notify_only)
472			return (need_notify);
473		log(LOG_ERR, "unable to open status file %s - error %d: %s", filename, errno, strerror(errno));
474		exit(1);
475	}
476	off = sizeof(fh);
477
478	if (!list_only) {
479		/* grab a lock on the file to protect initialization */
480		lock.l_type = F_WRLCK;
481		lock.l_whence = SEEK_SET;
482		lock.l_start = 0;
483		lock.l_len = sizeof(FileHeader);
484		err = fcntl(status_fd, F_SETLKW, &lock);
485		if (err == -1)
486			log(LOG_ERR, "fcntl lock error(%d): %s", errno, strerror(errno));
487	}
488	/* read header */
489	rv = read(status_fd, &fh, sizeof(fh));
490	if (rv != sizeof(fh)) {
491		if (rv < 0) {
492			log(LOG_ERR, "can't read status file header: %d, %s", rv, strerror(errno));
493			exit(1);
494		}
495		/* note: we may have just created an empty file above */
496		new_file = TRUE;
497		goto newfile;
498	}
499	if (fh.fh_version == ntohl(STATUS_DB_VERSION_CONVERTED)) {
500		/* Lost conversion race.  Just reopen. */
501		close(status_fd);
502		goto reopen;
503	}
504	if (fh.fh_version == 0) {
505		/* status file needs to be converted */
506		if (list_only) {
507			log(LOG_ERR, "status file needs conversion");
508			exit(1);
509		}
510		rv = convert_version0_file(filename);
511		if (rv) {
512			/* conversion failed */
513			log(LOG_ERR, "status file conversion failed, creating new file");
514			new_file = TRUE;
515			goto newfile;
516		}
517		/* update old file's version to alert anyone who may */
518		/* already have it opened (waiting for the lock) */
519		fh.fh_version = htonl(STATUS_DB_VERSION_CONVERTED);
520		rv = pwrite(status_fd, &fh, sizeof(fh), 0);
521		if ((rv != sizeof(fh)) || (rv = fsync(status_fd)))
522			log(LOG_ERR, "failed to update old status file header version: %d, %s", rv, strerror(errno));
523		close(status_fd);
524		goto reopen;
525	}
526	/* Scan the whole status file.  Along the way, we'll verify the	 */
527	/* integrity of the file and clean up monitored/notification fields.	 */
528	if (!update && notify_only)
529		DEBUG(1, "notify_only: skipping database initialization");
530
531	if (!hip)
532		hip = malloc(sizeof(*hip) + SM_MAXSTRLEN);
533	if (!hip) {
534		log(LOG_ERR, "can't allocate memory");
535		exit(1);
536	}
537	for (i = 0; i < ntohl(fh.fh_reccnt); i++) {
538		rv = pread(status_fd, hip, sizeof(*hip), off);
539		if (rv != sizeof(*hip)) {
540			log(LOG_ERR, "error reading status file host info entry # %d, %d %s", i, rv, strerror(errno));
541			break;
542		}
543		hip->hi_len = ntohs(hip->hi_len);
544		hip->hi_monitored = ntohs(hip->hi_monitored);
545		hip->hi_notify = ntohs(hip->hi_notify);
546		hip->hi_namelen = ntohs(hip->hi_namelen);
547		if (hip->hi_len > (sizeof(*hip) + SM_MAXSTRLEN)) {
548			log(LOG_ERR, "status file host info entry # %d, has bogus length %d", i, hip->hi_len);
549			break;
550		}
551		if (hip->hi_namelen > SM_MAXSTRLEN) {
552			log(LOG_ERR, "status file host info entry # %d, has bogus name length %d", i, hip->hi_namelen);
553			break;
554		}
555		if (hip->hi_namelen >= (hip->hi_len - sizeof(*hip) + NAMEINCR)) {
556			log(LOG_ERR, "status file host info entry # %d, name length %d exceeds record length %d", i, hip->hi_namelen, hip->hi_len);
557			break;
558		}
559		if (hip->hi_namelen >= NAMEINCR) {
560			len = RNDUP_NAMELEN(hip->hi_namelen) - NAMEINCR;
561			rv = pread(status_fd, &hip->hi_name[NAMEINCR], len, off + sizeof(*hip));
562			if (rv != len) {
563				log(LOG_ERR, "error reading status file host info entry # %d name, %d %s", i, rv, strerror(errno));
564				break;
565			}
566		}
567		if (hip->hi_name[hip->hi_namelen] != 0) {
568			log(LOG_ERR, "status file host info entry # %d, name not terminated", i);
569			break;
570		}
571		if (hip->hi_monitored && update) {
572			hip->hi_monitored = 0;
573			hip->hi_notify = htons(1);
574			hip->hi_namelen = htons(hip->hi_namelen);
575			hip->hi_len = htons(hip->hi_len);
576			rv = pwrite(status_fd, hip, sizeof(*hip), off);
577			if ((rv != sizeof(*hip)) || (rv = fsync(status_fd))) {
578				log(LOG_ERR, "error updating status file host info entry # %d, %d %s", i, rv, strerror(errno));
579				break;
580			}
581			hip->hi_len = ntohs(hip->hi_len);
582		}
583		if (hip->hi_notify && (statd_server || notify_only)) {
584			DEBUG(1, "need notify: %s", hip->hi_name);
585			need_notify = TRUE;
586		}
587		off += hip->hi_len;
588	}
589	if (i != ntohl(fh.fh_reccnt)) {
590		log(LOG_ERR, "status file failed verification, reinitializing file");
591		new_file = TRUE;
592	}
593newfile:
594	if (new_file)
595		need_notify = FALSE;
596	if (update) {
597		if (new_file) {
598			rv = ftruncate(status_fd, 0);
599			if (rv < 0) {
600				log(LOG_ERR, "unable to truncate status file, aborting");
601				exit(1);
602			}
603			bzero(&fh, sizeof(fh));
604			fh.fh_version = htonl(STATUS_DB_VERSION);
605			fh.fh_state = htonl(statd_server ? 1 : 2);
606			off = sizeof(fh);
607		} else {
608			/* advance to next even number (next odd if we're "up" again) */
609			fh.fh_state = ntohl(fh.fh_state);
610			if (statd_server)	/* advance to next odd */
611				fh.fh_state += (fh.fh_state & 1) ? 2 : 1;
612			else if (notify_only && (fh.fh_state & 1))	/* if odd, advance to even */
613				fh.fh_state++;
614			fh.fh_state = htonl(fh.fh_state);
615			/* make sure the file ends where we think it should */
616			status_file_len = lseek(status_fd, 0L, SEEK_END);
617			if (status_file_len != off) {
618				log(LOG_ERR, "status file is longer than expected, %lld vs %lld, truncating", status_file_len, off);
619				rv = ftruncate(status_fd, off);
620				if (rv < 0) {
621					log(LOG_ERR, "unable to truncate status file, aborting");
622					exit(1);
623				}
624			}
625		}
626		rv = pwrite(status_fd, &fh, sizeof(fh), 0);
627		if ((rv < 0) || (rv = fsync(status_fd))) {
628			log(LOG_ERR, "unable to initialize status file header (%d), aborting", rv);
629			exit(1);
630		}
631	}
632	status_file_len = off;
633	status_info_len = 0;
634	map_file();
635	sync_file();
636
637	if (!list_only) {
638		/* unlock header */
639		lock.l_type = F_UNLCK;
640		err = fcntl(status_fd, F_SETLKW, &lock);
641		if (err == -1)
642			log(LOG_ERR, "fcntl unlock error(%d): %s", errno, strerror(errno));
643	}
644	if (hip)
645		free(hip);
646	return (need_notify);
647}
648
649/* notify_one_host --------------------------------------------------------- */
650/*
651   Purpose:	Perform SM_NOTIFY procedure at specified host
652   Returns:	TRUE if success, FALSE if failed.
653*/
654
655static int
656notify_one_host(char *hostname, int warn)
657{
658	struct timeval timeout = {20, 0};	/* 20 secs timeout		 */
659	struct timeval try = {4, 0};		/* 4 secs per try		 */
660	struct addrinfo *ai, *ailist;
661	CLIENT *cli;
662	char dummy, empty[] = "", *spcerr = NULL;
663	stat_chge arg;
664	char our_hostname[SM_MAXSTRLEN + 1];
665	int result, error, sock;
666
667	gethostname(our_hostname, sizeof(our_hostname));
668	our_hostname[SM_MAXSTRLEN] = '\0';
669	arg.mon_name = our_hostname;
670	arg.state = ntohl(status_info->fh_state);
671	result = FALSE;
672
673	DEBUG(1, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname);
674
675	/* get the list of addresses for this host */
676	ailist = NULL;
677	if ((error = getaddresslist(hostname, &ailist))) {
678		if (warn)
679			log(LOG_WARNING, "Failed to contact host %s - error %d resolving", hostname, error);
680		return (result);
681	}
682	if (!ailist) {
683		if (warn)
684			log(LOG_WARNING, "Failed to contact host %s - no addresses", hostname);
685		return (result);
686	}
687
688	/* try each address until we get success */
689	for (ai=ailist; ai && (result == FALSE); ai = ai->ai_next) {
690		if (config.send_using_tcp) {
691			sock = RPC_ANYSOCK;
692			cli = clnttcp_create_sa(ai->ai_addr, SM_PROG, SM_VERS, &sock, 0, 0);
693		}
694		if (!config.send_using_tcp || !cli) {
695			sock = RPC_ANYSOCK;
696			cli = clntudp_create_sa(ai->ai_addr, SM_PROG, SM_VERS, try, &sock);
697		}
698		if (!cli) {
699			if (warn)
700				spcerr = clnt_spcreateerror(empty);
701			continue;
702		}
703		if (clnt_call(cli, SM_NOTIFY, (xdrproc_t)xdr_stat_chge, &arg, (xdrproc_t)xdr_void, &dummy, timeout) == RPC_SUCCESS)
704			result = TRUE;
705		clnt_destroy(cli);
706	}
707
708	if ((result == FALSE) && warn) {
709		if (spcerr)
710			log(LOG_WARNING, "Failed to contact host %s%s", hostname, spcerr);
711		log(LOG_WARNING, "Failed to contact rpc.statd at host %s", hostname);
712	}
713
714	freeaddrinfo(ailist);
715	return (result);
716}
717
718/* notify_hosts ------------------------------------------------------------ */
719/*
720   Purpose:	Send SM_NOTIFY to all hosts marked as requiring it
721   Returns:	Nothing.
722   Notes:	Does nothing if there are no monitored hosts.
723		Called after all the initialisation has been done -
724		logs to syslog.
725*/
726
727int
728notify_hosts(void)
729{
730	int i, reccnt;
731	int attempts, warn;
732	int work_to_do = FALSE;
733	HostInfo *hip;
734	off_t off;
735	pid_t pid;
736
737	/* claim PID file */
738	pfh = pidfile_open(_PATH_STATD_NOTIFY_PID, 0644, &pid);
739	if (pfh == NULL) {
740		log(LOG_ERR, "can't open statd.notify pidfile: %s (%d)", strerror(errno), errno);
741		if (errno == EEXIST) {
742			log(LOG_ERR, "statd.notify already running, pid: %d", pid);
743			return (0);
744		}
745		return (2);
746	}
747	if (pidfile_write(pfh) == -1)
748		log(LOG_WARNING, "can't write to statd.notify pidfile: %s (%d)", strerror(errno), errno);
749
750	work_to_do = init_file(_PATH_STATD_DATABASE);
751
752	if (!work_to_do) {
753		/* No work found */
754		log(LOG_NOTICE, "statd.notify - no notifications needed");
755		pidfile_remove(pfh);
756		return (0);
757	}
758	log(LOG_INFO, "statd.notify starting");
759
760	/* Here in the child process.  We continue until all the hosts marked	 */
761	/* as requiring notification have been duly notified.			 */
762	/* If one of the initial attempts fails, we sleep for a while and	 */
763	/* have another go.  This is necessary because when we have crashed,	 */
764	/* (eg. a power outage) it is quite possible that we won't be able to	 */
765	/* contact all monitored hosts immediately on restart, either because	 */
766	/* they crashed too and take longer to come up (in which case the	 */
767	/* notification isn't really required), or more importantly if some	 */
768	/* router etc. needed to reach the monitored host has not come back	 */
769	/* up yet.  In this case, we will be a bit late in re-establishing	 */
770	/* locks (after the grace period) but that is the best we can do.	 */
771	/* We try 10 times at 5 sec intervals, 10 more times at 1 minute	 */
772	/* intervals, then 24 more times at hourly intervals, finally		 */
773	/* giving up altogether if the host hasn't come back to life after	 */
774	/* 24 hours.								 */
775
776	for (attempts = 0; attempts < 44; attempts++) {
777		work_to_do = FALSE;	/* Unless anything fails		 */
778		warn = !(attempts % 10) || (attempts >= 20); /* limit warning frequency */
779
780		/* update status_file_len and mmap */
781		i = lseek(status_fd, 0L, SEEK_END);
782		if (i > 0) {
783			status_file_len = i;
784			map_file();
785		}
786		/* iterate through all entries */
787		off = sizeof(FileHeader);
788		reccnt = ntohl(status_info->fh_reccnt);
789		for (i = 0; i < reccnt; i++) {
790			hip = HOSTINFO(off);
791			if (hip->hi_notify) {
792				if (notify_one_host(hip->hi_name, warn)) {
793					hip->hi_notify = 0;
794					sync_file();
795					if (attempts)	/* log success if we've logged errors */
796						log(LOG_WARNING, "Contacted rpc.statd at host %s", hip->hi_name);
797				} else
798					work_to_do = TRUE;
799			}
800			off += ntohs(hip->hi_len);
801		}
802		if (!work_to_do)
803			break;
804		if (attempts < 10)
805			sleep(5);
806		else if (attempts < 20)
807			sleep(60);
808		else
809			sleep(60 * 60);
810	}
811	pidfile_remove(pfh);
812	return (0);
813}
814
815/* do_unnotify_host ------------------------------------------------------ */
816/*
817   Purpose:	Clear the notify flag for the given host.
818   Returns:	Nothing.
819*/
820
821int
822do_unnotify_host(const char *hostname)
823{
824	uint i;
825	int found = 0, dosync = 0;
826	off_t off;
827	HostInfo *hip;
828
829	init_file(_PATH_STATD_DATABASE);
830
831	/* iterate through all entries */
832	off = sizeof(FileHeader);
833	for (i = 0; i < status_info->fh_reccnt && off < status_file_len; i++, off += ntohs(hip->hi_len)) {
834		hip = HOSTINFO(off);
835		if (strncmp(hip->hi_name, hostname, SM_MAXSTRLEN))
836			continue;
837		found = 1;
838		if (!hip->hi_notify)
839			continue;
840		hip->hi_notify = 0;
841		dosync = 1;
842	}
843	if (dosync)
844		sync_file();
845	return (!found);
846}
847
848/* list_hosts ------------------------------------------------------------ */
849/*
850   Purpose:	Lists all hosts and their status.
851   Returns:	Nothing.
852*/
853
854struct hoststate {
855	TAILQ_ENTRY(hoststate) hs_list;
856	uint16_t hs_monitor;	/* host being monitored */
857	uint16_t hs_notify;	/* host needs notification */
858	char hs_flag;		/* temporary flag */
859#define HS_CHANGED	1
860#define HS_OLD		2
861#define HS_NEW		3
862	char hs_name[1];	/* host's name */
863};
864static
865TAILQ_HEAD(, hoststate) hosts;
866static uint32_t prev_state, prev_reccnt;
867static off_t prev_status_file_len;
868
869int
870list_hosts(int mode)
871{
872	uint i;
873	int watchmode = (mode == LIST_MODE_WATCH);
874	uint32_t state, reccnt;
875	HostInfo *hip;
876	struct hoststate *hsp, *nhsp;
877	off_t off;
878
879	init_file(_PATH_STATD_DATABASE);
880
881	if (watchmode) {
882		prev_state = 0xffffffff;
883		TAILQ_INIT(&hosts);
884	}
885	/* print header/legend */
886	printf("Status DB: %s\n", _PATH_STATD_DATABASE);
887	printf("Status DB Length: %lld\n", status_file_len);
888	printf("State: %d\n", ntohl(status_info->fh_state));
889	printf("#Hosts: %d\n", ntohl(status_info->fh_reccnt));
890	printf("  +----- Being Monitored\n");
891	printf("  |  +-- Notification Required\n");
892	printf("  |  |\n");
893	printf("  v  v\n");
894loop:
895	state = ntohl(status_info->fh_state);
896	reccnt = ntohl(status_info->fh_reccnt);
897	status_file_len = lseek(status_fd, 0L, SEEK_END);
898	map_file();
899
900	if (watchmode && (prev_state != 0xffffffff)) {
901		if (status_file_len != prev_status_file_len)
902			printf("Status DB Length: %lld -> %lld\n", prev_status_file_len, status_file_len);
903		if (state != prev_state)
904			printf("State: %d -> %d\n", prev_state, state);
905		if (reccnt != prev_reccnt)
906			printf("#Hosts: %d -> %d\n", prev_reccnt, reccnt);
907	}
908	/* iterate through all entries */
909	off = sizeof(FileHeader);
910	for (i = 0; i < reccnt && off < status_file_len; i++, off += ntohs(hip->hi_len)) {
911		hip = HOSTINFO(off);
912		if (!hip->hi_namelen || !hip->hi_name[0] ||
913		    (!hip->hi_monitored && !hip->hi_notify && (!config.verbose || watchmode)))
914			continue;
915		if (!watchmode) {
916			printf("  %c  %c   %s\n", (hip->hi_monitored ? 'M' : ' '), (hip->hi_notify ? 'N' : ' '), hip->hi_name);
917			continue;
918		}
919		TAILQ_FOREACH(hsp, &hosts, hs_list)
920			if (!strcmp(hsp->hs_name, hip->hi_name))
921			break;
922		if (!hsp) {
923			if (!(hsp = calloc(1, sizeof(*hsp) + ntohs(hip->hi_namelen))))
924				continue;
925			strncpy(hsp->hs_name, hip->hi_name, ntohs(hip->hi_namelen));
926			TAILQ_INSERT_TAIL(&hosts, hsp, hs_list);
927			hsp->hs_flag = HS_NEW;
928		} else {
929			hsp->hs_flag = ((hsp->hs_monitor == hip->hi_monitored) && (hsp->hs_notify == hip->hi_notify)) ? 0 : HS_CHANGED;
930		}
931		hsp->hs_monitor = hip->hi_monitored;
932		hsp->hs_notify = hip->hi_notify;
933	}
934
935	if (!watchmode)
936		return (0);
937
938	TAILQ_FOREACH_SAFE(hsp, &hosts, hs_list, nhsp) {
939		if (!hsp->hs_flag)
940			continue;
941		printf("%c %c  %c   %s\n", ((hsp->hs_flag == HS_OLD) ? '-' : ((hsp->hs_flag == HS_NEW) ? '+' : ' ')),
942		       (hsp->hs_monitor ? 'M' : ' '), (hsp->hs_notify ? 'N' : ' '), hsp->hs_name);
943		if (hsp->hs_flag == HS_OLD) {
944			TAILQ_REMOVE(&hosts, hsp, hs_list);
945			free(hsp);
946		}
947	}
948	prev_state = state;
949	prev_reccnt = reccnt;
950	prev_status_file_len = status_file_len;
951	TAILQ_FOREACH(hsp, &hosts, hs_list)
952		hsp->hs_flag = HS_OLD;
953	sleep(2);
954	goto loop;
955}
956