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