1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * University Copyright- Copyright (c) 1982, 1986, 1988 32 * The Regents of the University of California 33 * All Rights Reserved 34 * 35 * University Acknowledgment- Portions of this document are derived from 36 * software developed by the University of California, Berkeley, and its 37 * contributors. 38 */ 39 40 #pragma ident "%Z%%M% %I% %E% SMI" 41 42 /* 43 * sm_statd.c consists of routines used for the intermediate 44 * statd implementation(3.2 rpc.statd); 45 * it creates an entry in "current" directory for each site that it monitors; 46 * after crash and recovery, it moves all entries in "current" 47 * to "backup" directory, and notifies the corresponding statd of its recovery. 48 */ 49 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <unistd.h> 53 #include <string.h> 54 #include <syslog.h> 55 #include <netdb.h> 56 #include <sys/types.h> 57 #include <sys/stat.h> 58 #include <sys/file.h> 59 #include <sys/param.h> 60 #include <arpa/inet.h> 61 #include <dirent.h> 62 #include <rpc/rpc.h> 63 #include <rpcsvc/sm_inter.h> 64 #include <rpcsvc/nsm_addr.h> 65 #include <errno.h> 66 #include <memory.h> 67 #include <signal.h> 68 #include <synch.h> 69 #include <thread.h> 70 #include <limits.h> 71 #include <strings.h> 72 #include "sm_statd.h" 73 74 75 int LOCAL_STATE; 76 77 sm_hash_t mon_table[MAX_HASHSIZE]; 78 static sm_hash_t record_table[MAX_HASHSIZE]; 79 static sm_hash_t recov_q; 80 81 static name_entry *find_name(name_entry **namepp, char *name); 82 static name_entry *insert_name(name_entry **namepp, char *name, 83 int need_alloc); 84 static void delete_name(name_entry **namepp, char *name); 85 static void remove_name(char *name, int op, int startup); 86 static int statd_call_statd(char *name); 87 static void pr_name(char *name, int flag); 88 static void *thr_statd_init(); 89 static void *sm_try(); 90 static void *thr_call_statd(void *); 91 static void remove_single_name(char *name, char *dir1, char *dir2); 92 static int move_file(char *fromdir, char *file, char *todir); 93 static int count_symlinks(char *dir, char *name, int *count); 94 static char *family2string(sa_family_t family); 95 96 /* 97 * called when statd first comes up; it searches /etc/sm to gather 98 * all entries to notify its own failure 99 */ 100 void 101 statd_init() 102 { 103 struct dirent *dirp; 104 DIR *dp; 105 FILE *fp, *fp_tmp; 106 int i, tmp_state; 107 char state_file[MAXPATHLEN+SM_MAXPATHLEN]; 108 109 if (debug) 110 (void) printf("enter statd_init\n"); 111 112 /* 113 * First try to open the file. If that fails, try to create it. 114 * If that fails, give up. 115 */ 116 if ((fp = fopen(STATE, "r+")) == (FILE *)NULL) 117 if ((fp = fopen(STATE, "w+")) == (FILE *)NULL) { 118 syslog(LOG_ERR, "can't open %s: %m", STATE); 119 exit(1); 120 } else 121 (void) chmod(STATE, 0644); 122 if ((fscanf(fp, "%d", &LOCAL_STATE)) == EOF) { 123 if (debug >= 2) 124 (void) printf("empty file\n"); 125 LOCAL_STATE = 0; 126 } 127 128 /* 129 * Scan alternate paths for largest "state" number 130 */ 131 for (i = 0; i < pathix; i++) { 132 (void) sprintf(state_file, "%s/statmon/state", path_name[i]); 133 if ((fp_tmp = fopen(state_file, "r+")) == (FILE *)NULL) { 134 if ((fp_tmp = fopen(state_file, "w+")) 135 == (FILE *)NULL) { 136 if (debug) 137 syslog(LOG_ERR, 138 "can't open %s: %m", 139 state_file); 140 continue; 141 } else 142 (void) chmod(state_file, 0644); 143 } 144 if ((fscanf(fp_tmp, "%d", &tmp_state)) == EOF) { 145 if (debug) 146 syslog(LOG_ERR, 147 "statd: %s: file empty\n", state_file); 148 (void) fclose(fp_tmp); 149 continue; 150 } 151 if (tmp_state > LOCAL_STATE) { 152 LOCAL_STATE = tmp_state; 153 if (debug) 154 (void) printf("Update LOCAL STATE: %d\n", 155 tmp_state); 156 } 157 (void) fclose(fp_tmp); 158 } 159 160 LOCAL_STATE = ((LOCAL_STATE%2) == 0) ? LOCAL_STATE+1 : LOCAL_STATE+2; 161 162 /* IF local state overflows, reset to value 1 */ 163 if (LOCAL_STATE < 0) { 164 LOCAL_STATE = 1; 165 } 166 167 /* Copy the LOCAL_STATE value back to all stat files */ 168 if (fseek(fp, 0, 0) == -1) { 169 syslog(LOG_ERR, "statd: fseek failed\n"); 170 exit(1); 171 } 172 173 (void) fprintf(fp, "%-10d", LOCAL_STATE); 174 (void) fflush(fp); 175 if (fsync(fileno(fp)) == -1) { 176 syslog(LOG_ERR, "statd: fsync failed\n"); 177 exit(1); 178 } 179 (void) fclose(fp); 180 181 for (i = 0; i < pathix; i++) { 182 (void) sprintf(state_file, "%s/statmon/state", path_name[i]); 183 if ((fp_tmp = fopen(state_file, "r+")) == (FILE *)NULL) { 184 if ((fp_tmp = fopen(state_file, "w+")) 185 == (FILE *)NULL) { 186 syslog(LOG_ERR, 187 "can't open %s: %m", state_file); 188 continue; 189 } else 190 (void) chmod(state_file, 0644); 191 } 192 (void) fprintf(fp_tmp, "%-10d", LOCAL_STATE); 193 (void) fflush(fp_tmp); 194 if (fsync(fileno(fp_tmp)) == -1) { 195 syslog(LOG_ERR, 196 "statd: %s: fsync failed\n", state_file); 197 (void) fclose(fp_tmp); 198 exit(1); 199 } 200 (void) fclose(fp_tmp); 201 } 202 203 if (debug) 204 (void) printf("local state = %d\n", LOCAL_STATE); 205 206 if ((mkdir(CURRENT, SM_DIRECTORY_MODE)) == -1) { 207 if (errno != EEXIST) { 208 syslog(LOG_ERR, "statd: mkdir current, error %m\n"); 209 exit(1); 210 } 211 } 212 if ((mkdir(BACKUP, SM_DIRECTORY_MODE)) == -1) { 213 if (errno != EEXIST) { 214 syslog(LOG_ERR, "statd: mkdir backup, error %m\n"); 215 exit(1); 216 } 217 } 218 219 /* get all entries in CURRENT into BACKUP */ 220 if ((dp = opendir(CURRENT)) == (DIR *)NULL) { 221 syslog(LOG_ERR, "statd: open current directory, error %m\n"); 222 exit(1); 223 } 224 225 while ((dirp = readdir(dp)) != NULL) { 226 if (strcmp(dirp->d_name, ".") != 0 && 227 strcmp(dirp->d_name, "..") != 0) { 228 /* rename all entries from CURRENT to BACKUP */ 229 (void) move_file(CURRENT, dirp->d_name, BACKUP); 230 } 231 } 232 233 (void) closedir(dp); 234 235 /* Contact hosts' statd */ 236 if (thr_create(NULL, NULL, thr_statd_init, NULL, THR_DETACHED, 0)) { 237 syslog(LOG_ERR, 238 "statd: unable to create thread for thr_statd_init\n"); 239 exit(1); 240 } 241 } 242 243 /* 244 * Work thread which contacts hosts' statd. 245 */ 246 void * 247 thr_statd_init() 248 { 249 struct dirent *dirp; 250 DIR *dp; 251 int num_threads; 252 int num_join; 253 int i; 254 char *name; 255 char buf[MAXPATHLEN+SM_MAXPATHLEN]; 256 257 /* Go thru backup directory and contact hosts */ 258 if ((dp = opendir(BACKUP)) == (DIR *)NULL) { 259 syslog(LOG_ERR, "statd: open backup directory, error %m\n"); 260 exit(1); 261 } 262 263 /* 264 * Create "UNDETACHED" threads for each symlink and (unlinked) 265 * regular file in backup directory to initiate statd_call_statd. 266 * NOTE: These threads are the only undetached threads in this 267 * program and thus, the thread id is not needed to join the threads. 268 */ 269 num_threads = 0; 270 while ((dirp = readdir(dp)) != NULL) { 271 /* 272 * If host file is not a symlink, don't bother to 273 * spawn a thread for it. If any link(s) refer to 274 * it, the host will be contacted using the link(s). 275 * If not, we'll deal with it during the legacy pass. 276 */ 277 (void) sprintf(buf, "%s/%s", BACKUP, dirp->d_name); 278 if (is_symlink(buf) == 0) { 279 continue; 280 } 281 282 /* 283 * If the num_threads has exceeded, wait until 284 * a certain amount of threads have finished. 285 * Currently, 10% of threads created should be joined. 286 */ 287 if (num_threads > MAX_THR) { 288 num_join = num_threads/PERCENT_MINJOIN; 289 for (i = 0; i < num_join; i++) 290 thr_join(0, 0, 0); 291 num_threads -= num_join; 292 } 293 294 /* 295 * If can't alloc name then print error msg and 296 * continue to next item on list. 297 */ 298 name = strdup(dirp->d_name); 299 if (name == (char *)NULL) { 300 syslog(LOG_ERR, 301 "statd: unable to allocate space for name %s\n", 302 dirp->d_name); 303 continue; 304 } 305 306 /* Create a thread to do a statd_call_statd for name */ 307 if (thr_create(NULL, NULL, thr_call_statd, 308 (void *) name, 0, 0)) { 309 syslog(LOG_ERR, 310 "statd: unable to create thr_call_statd() for name %s.\n", 311 dirp->d_name); 312 free(name); 313 continue; 314 } 315 num_threads++; 316 } 317 318 /* 319 * Join the other threads created above before processing the 320 * legacies. This allows all symlinks and the regular files 321 * to which they correspond to be processed and deleted. 322 */ 323 for (i = 0; i < num_threads; i++) { 324 thr_join(0, 0, 0); 325 } 326 327 /* 328 * The second pass checks for `legacies': regular files which 329 * never had symlinks pointing to them at all, just like in the 330 * good old (pre-1184192 fix) days. Once a machine has cleaned 331 * up its legacies they should only reoccur due to catastrophes 332 * (e.g., severed symlinks). 333 */ 334 rewinddir(dp); 335 num_threads = 0; 336 while ((dirp = readdir(dp)) != NULL) { 337 if (strcmp(dirp->d_name, ".") == 0 || 338 strcmp(dirp->d_name, "..") == 0) { 339 continue; 340 } 341 342 (void) sprintf(buf, "%s/%s", BACKUP, dirp->d_name); 343 if (is_symlink(buf)) { 344 /* 345 * We probably couldn't reach this host and it's 346 * been put on the recovery queue for retry. 347 * Skip it and keep looking for regular files. 348 */ 349 continue; 350 } 351 352 if (debug) { 353 (void) printf("thr_statd_init: legacy %s\n", 354 dirp->d_name); 355 } 356 357 /* 358 * If the number of threads exceeds the maximum, wait 359 * for some fraction of them to finish before 360 * continuing. 361 */ 362 if (num_threads > MAX_THR) { 363 num_join = num_threads/PERCENT_MINJOIN; 364 for (i = 0; i < num_join; i++) 365 thr_join(0, 0, 0); 366 num_threads -= num_join; 367 } 368 369 /* 370 * If can't alloc name then print error msg and 371 * continue to next item on list. 372 */ 373 name = strdup(dirp->d_name); 374 if (name == (char *)NULL) { 375 syslog(LOG_ERR, 376 "statd: unable to allocate space for name %s\n", 377 dirp->d_name); 378 continue; 379 } 380 381 /* Create a thread to do a statd_call_statd for name */ 382 if (thr_create(NULL, NULL, thr_call_statd, 383 (void *) name, 0, 0)) { 384 syslog(LOG_ERR, 385 "statd: unable to create thr_call_statd() for name %s.\n", 386 dirp->d_name); 387 free(name); 388 continue; 389 } 390 num_threads++; 391 } 392 393 (void) closedir(dp); 394 395 /* 396 * Join the other threads created above before creating thread 397 * to process items in recovery table. 398 */ 399 for (i = 0; i < num_threads; i++) { 400 thr_join(0, 0, 0); 401 } 402 403 /* 404 * Need to only copy /var/statmon/sm.bak to alternate paths, since 405 * the only hosts in /var/statmon/sm should be the ones currently 406 * being monitored and already should be in alternate paths as part 407 * of insert_mon(). 408 */ 409 for (i = 0; i < pathix; i++) { 410 (void) sprintf(buf, "%s/statmon/sm.bak", path_name[i]); 411 if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { 412 if (errno != EEXIST) 413 syslog(LOG_ERR, "statd: mkdir %s error %m\n", 414 buf); 415 else 416 copydir_from_to(BACKUP, buf); 417 } else 418 copydir_from_to(BACKUP, buf); 419 } 420 421 422 /* 423 * Reset the die and in_crash variable and signal other threads 424 * that have issued an sm_crash and are waiting. 425 */ 426 mutex_lock(&crash_lock); 427 die = 0; 428 in_crash = 0; 429 mutex_unlock(&crash_lock); 430 cond_broadcast(&crash_finish); 431 432 if (debug) 433 (void) printf("Creating thread for sm_try\n"); 434 435 /* Continue to notify statd on hosts that were unreachable. */ 436 if (thr_create(NULL, NULL, sm_try, NULL, THR_DETACHED, 0)) 437 syslog(LOG_ERR, 438 "statd: unable to create thread for sm_try().\n"); 439 thr_exit((void *) 0); 440 #ifdef lint 441 return (0); 442 #endif 443 } 444 445 /* 446 * Work thread to make call to statd_call_statd. 447 */ 448 void * 449 thr_call_statd(void *namep) 450 { 451 char *name = (char *)namep; 452 453 /* 454 * If statd of name is unreachable, add name to recovery table 455 * otherwise if statd_call_statd was successful, remove from backup. 456 */ 457 if (statd_call_statd(name) != 0) { 458 int n; 459 char *tail; 460 char path[MAXPATHLEN]; 461 /* 462 * since we are constructing this pathname below we add 463 * another space for the terminating NULL so we don't 464 * overflow our buffer when we do the readlink 465 */ 466 char rname[MAXNAMELEN + 1]; 467 468 if (debug) { 469 (void) printf( 470 "statd call failed, inserting %s in recov_q\n", name); 471 } 472 mutex_lock(&recov_q.lock); 473 (void) insert_name(&recov_q.sm_recovhdp, name, 0); 474 mutex_unlock(&recov_q.lock); 475 476 /* 477 * If we queued a symlink name in the recovery queue, 478 * we now clean up the regular file to which it referred. 479 * This may leave a severed symlink if multiple links 480 * referred to one regular file; this is unaesthetic but 481 * it works. The big benefit is that it prevents us 482 * from recovering the same host twice (as symlink and 483 * as regular file) needlessly, usually on separate reboots. 484 */ 485 (void) strcpy(path, BACKUP); 486 (void) strcat(path, "/"); 487 (void) strcat(path, name); 488 if (is_symlink(path)) { 489 n = readlink(path, rname, MAXNAMELEN); 490 if (n <= 0) { 491 if (debug >= 2) { 492 (void) printf( 493 "thr_call_statd: can't read link %s\n", 494 path); 495 } 496 } else { 497 rname[n] = '\0'; 498 499 tail = strrchr(path, '/') + 1; 500 501 if ((strlen(BACKUP) + strlen(rname) + 2) <= 502 MAXPATHLEN) { 503 (void) strcpy(tail, rname); 504 delete_file(path); 505 } else if (debug) { 506 printf("thr_call_statd: path over" 507 "maxpathlen!\n"); 508 } 509 } 510 511 } 512 513 if (debug) 514 pr_name(name, 0); 515 516 } else { 517 /* 518 * If `name' is an IP address symlink to a name file, 519 * remove it now. If it is the last such symlink, 520 * remove the name file as well. Regular files with 521 * no symlinks to them are assumed to be legacies and 522 * are removed as well. 523 */ 524 remove_name(name, 1, 1); 525 free(name); 526 } 527 thr_exit((void *) 0); 528 #ifdef lint 529 return (0); 530 #endif 531 } 532 533 /* 534 * Notifies the statd of host specified by name to indicate that 535 * state has changed for this server. 536 */ 537 static int 538 statd_call_statd(name) 539 char *name; 540 { 541 enum clnt_stat clnt_stat; 542 struct timeval tottimeout; 543 CLIENT *clnt; 544 char *name_or_addr; 545 stat_chge ntf; 546 int i; 547 int rc; 548 int dummy1, dummy2, dummy3, dummy4; 549 char ascii_addr[MAXNAMELEN]; 550 size_t unq_len; 551 552 ntf.mon_name = hostname; 553 ntf.state = LOCAL_STATE; 554 if (debug) 555 (void) printf("statd_call_statd at %s\n", name); 556 557 /* 558 * If it looks like an ASCII <address family>.<address> specifier, 559 * strip off the family - we just want the address when obtaining 560 * a client handle. 561 * If it's anything else, just pass it on to create_client(). 562 */ 563 unq_len = strcspn(name, "."); 564 565 if ((strncmp(name, SM_ADDR_IPV4, unq_len) == 0) || 566 (strncmp(name, SM_ADDR_IPV6, unq_len) == 0)) { 567 name_or_addr = strchr(name, '.') + 1; 568 } else { 569 name_or_addr = name; 570 } 571 572 /* 573 * NOTE: We depend here upon the fact that the RPC client code 574 * allows us to use ASCII dotted quad `names', i.e. "192.9.200.1". 575 * This may change in a future release. 576 */ 577 if (debug) { 578 (void) printf("statd_call_statd: calling create_client(%s)\n", 579 name_or_addr); 580 } 581 582 tottimeout.tv_sec = SM_RPC_TIMEOUT; 583 tottimeout.tv_usec = 0; 584 585 if ((clnt = create_client(name_or_addr, SM_PROG, SM_VERS, 586 &tottimeout)) == (CLIENT *) NULL) { 587 return (-1); 588 } 589 590 /* Perform notification to client */ 591 rc = 0; 592 clnt_stat = clnt_call(clnt, SM_NOTIFY, xdr_stat_chge, (char *)&ntf, 593 xdr_void, NULL, tottimeout); 594 if (debug) { 595 (void) printf("clnt_stat=%s(%d)\n", 596 clnt_sperrno(clnt_stat), clnt_stat); 597 } 598 if (clnt_stat != (int)RPC_SUCCESS) { 599 syslog(LOG_WARNING, 600 "statd: cannot talk to statd at %s, %s(%d)\n", 601 name_or_addr, clnt_sperrno(clnt_stat), clnt_stat); 602 rc = -1; 603 } 604 605 /* For HA systems and multi-homed hosts */ 606 ntf.state = LOCAL_STATE; 607 for (i = 0; i < addrix; i++) { 608 ntf.mon_name = host_name[i]; 609 if (debug) 610 (void) printf("statd_call_statd at %s\n", name_or_addr); 611 clnt_stat = clnt_call(clnt, SM_NOTIFY, xdr_stat_chge, 612 (char *)&ntf, xdr_void, NULL, 613 tottimeout); 614 if (clnt_stat != (int)RPC_SUCCESS) { 615 syslog(LOG_WARNING, 616 "statd: cannot talk to statd at %s, %s(%d)\n", 617 name_or_addr, clnt_sperrno(clnt_stat), clnt_stat); 618 rc = -1; 619 } 620 } 621 clnt_destroy(clnt); 622 return (rc); 623 } 624 625 /* 626 * Continues to contact hosts in recovery table that were unreachable. 627 * NOTE: There should only be one sm_try thread executing and 628 * thus locks are not needed for recovery table. Die is only cleared 629 * after all the hosts has at least been contacted once. The reader/writer 630 * lock ensures to finish this code before an sm_crash is started. Die 631 * variable will signal it. 632 */ 633 void * 634 sm_try() 635 { 636 name_entry *nl, *next; 637 timestruc_t wtime; 638 int delay = 0; 639 640 rw_rdlock(&thr_rwlock); 641 if (mutex_trylock(&sm_trylock)) 642 goto out; 643 mutex_lock(&crash_lock); 644 645 while (!die) { 646 wtime.tv_sec = delay; 647 wtime.tv_nsec = 0; 648 /* 649 * Wait until signalled to wakeup or time expired. 650 * If signalled to be awoken, then a crash has occurred 651 * or otherwise time expired. 652 */ 653 if (cond_reltimedwait(&retrywait, &crash_lock, &wtime) == 0) { 654 break; 655 } 656 657 /* Exit loop if queue is empty */ 658 if ((next = recov_q.sm_recovhdp) == NULL) 659 break; 660 661 mutex_unlock(&crash_lock); 662 663 while (((nl = next) != (name_entry *)NULL) && (!die)) { 664 next = next->nxt; 665 if (statd_call_statd(nl->name) == 0) { 666 /* remove name from BACKUP */ 667 remove_name(nl->name, 1, 0); 668 mutex_lock(&recov_q.lock); 669 /* remove entry from recovery_q */ 670 delete_name(&recov_q.sm_recovhdp, nl->name); 671 mutex_unlock(&recov_q.lock); 672 } else { 673 /* 674 * Print message only once since unreachable 675 * host can be contacted forever. 676 */ 677 if (delay == 0) 678 syslog(LOG_WARNING, 679 "statd: host %s is not responding\n", 680 nl->name); 681 } 682 } 683 /* 684 * Increment the amount of delay before restarting again. 685 * The amount of delay should not exceed the MAX_DELAYTIME. 686 */ 687 if (delay <= MAX_DELAYTIME) 688 delay += INC_DELAYTIME; 689 mutex_lock(&crash_lock); 690 } 691 692 mutex_unlock(&crash_lock); 693 mutex_unlock(&sm_trylock); 694 out: 695 rw_unlock(&thr_rwlock); 696 if (debug) 697 (void) printf("EXITING sm_try\n"); 698 thr_exit((void *) 0); 699 #ifdef lint 700 return (0); 701 #endif 702 } 703 704 /* 705 * Malloc's space and returns the ptr to malloc'ed space. NULL if unsuccessful. 706 */ 707 char * 708 xmalloc(len) 709 unsigned len; 710 { 711 char *new; 712 713 if ((new = malloc(len)) == 0) { 714 syslog(LOG_ERR, "statd: malloc, error %m\n"); 715 return ((char *)NULL); 716 } else { 717 (void) memset(new, 0, len); 718 return (new); 719 } 720 } 721 722 /* 723 * the following two routines are very similar to 724 * insert_mon and delete_mon in sm_proc.c, except the structture 725 * is different 726 */ 727 static name_entry * 728 insert_name(namepp, name, need_alloc) 729 name_entry **namepp; 730 char *name; 731 int need_alloc; 732 { 733 name_entry *new; 734 735 new = (name_entry *)xmalloc(sizeof (name_entry)); 736 if (new == (name_entry *) NULL) 737 return (NULL); 738 739 /* Allocate name when needed which is only when adding to record_t */ 740 if (need_alloc) { 741 if ((new->name = strdup(name)) == (char *)NULL) { 742 syslog(LOG_ERR, "statd: strdup, error %m\n"); 743 free(new); 744 return (NULL); 745 } 746 } else 747 new->name = name; 748 749 new->nxt = *namepp; 750 if (new->nxt != (name_entry *)NULL) 751 new->nxt->prev = new; 752 753 new->prev = (name_entry *) NULL; 754 755 *namepp = new; 756 if (debug) { 757 (void) printf("insert_name: inserted %s at %p\n", 758 name, (void *)namepp); 759 } 760 761 return (new); 762 } 763 764 /* 765 * Deletes name from specified list (namepp). 766 */ 767 static void 768 delete_name(namepp, name) 769 name_entry **namepp; 770 char *name; 771 { 772 name_entry *nl; 773 774 nl = *namepp; 775 while (nl != (name_entry *)NULL) { 776 if (str_cmp_address_specifier(nl->name, name) == 0 || 777 str_cmp_unqual_hostname(nl->name, name) == 0) { 778 if (nl->prev != (name_entry *)NULL) 779 nl->prev->nxt = nl->nxt; 780 else 781 *namepp = nl->nxt; 782 if (nl->nxt != (name_entry *)NULL) 783 nl->nxt->prev = nl->prev; 784 free(nl->name); 785 free(nl); 786 return; 787 } 788 nl = nl->nxt; 789 } 790 } 791 792 /* 793 * Finds name from specified list (namep). 794 */ 795 static name_entry * 796 find_name(namep, name) 797 name_entry **namep; 798 char *name; 799 { 800 name_entry *nl; 801 802 nl = *namep; 803 804 while (nl != (name_entry *)NULL) { 805 if (str_cmp_unqual_hostname(nl->name, name) == 0) { 806 return (nl); 807 } 808 nl = nl->nxt; 809 } 810 return ((name_entry *)NULL); 811 } 812 813 /* 814 * Creates a file. 815 */ 816 817 int 818 create_file(name) 819 char *name; 820 { 821 int fd; 822 823 /* 824 * The file might already exist. If it does, we ask for only write 825 * permission, since that's all the file was created with. 826 */ 827 if ((fd = open(name, O_CREAT | O_WRONLY, S_IWUSR)) == -1) { 828 if (errno != EEXIST) { 829 syslog(LOG_ERR, "can't open %s: %m", name); 830 return (1); 831 } 832 } 833 834 if (debug >= 2) 835 (void) printf("%s is created\n", name); 836 if (close(fd)) { 837 syslog(LOG_ERR, "statd: close, error %m\n"); 838 return (1); 839 } 840 841 return (0); 842 } 843 844 /* 845 * Deletes the file specified by name. 846 */ 847 void 848 delete_file(name) 849 char *name; 850 { 851 if (debug >= 2) 852 (void) printf("Remove monitor entry %s\n", name); 853 if (unlink(name) == -1) { 854 if (errno != ENOENT) 855 syslog(LOG_ERR, "statd: unlink of %s, error %m", name); 856 } 857 } 858 859 /* 860 * Return 1 if file is a symlink, else 0. 861 */ 862 int 863 is_symlink(file) 864 char *file; 865 { 866 int error; 867 struct stat lbuf; 868 869 do { 870 bzero((caddr_t)&lbuf, sizeof (lbuf)); 871 error = lstat(file, &lbuf); 872 } while (error == EINTR); 873 874 if (error == 0) { 875 return ((lbuf.st_mode & S_IFMT) == S_IFLNK); 876 } 877 878 return (0); 879 } 880 881 /* 882 * Moves the file specified by `from' to `to' only if the 883 * new file is guaranteed to be created (which is presumably 884 * why we don't just do a rename(2)). If `from' is a 885 * symlink, the destination file will be a similar symlink 886 * in the directory of `to'. 887 * 888 * Returns 0 for success, 1 for failure. 889 */ 890 static int 891 move_file(fromdir, file, todir) 892 char *fromdir; 893 char *file; 894 char *todir; 895 { 896 int n; 897 char rname[MAXNAMELEN + 1]; /* +1 for the terminating NULL */ 898 char from[MAXPATHLEN]; 899 char to[MAXPATHLEN]; 900 901 (void) strcpy(from, fromdir); 902 (void) strcat(from, "/"); 903 (void) strcat(from, file); 904 if (is_symlink(from)) { 905 /* 906 * Dig out the name of the regular file the link points to. 907 */ 908 n = readlink(from, rname, MAXNAMELEN); 909 if (n <= 0) { 910 if (debug >= 2) { 911 (void) printf("move_file: can't read link %s\n", 912 from); 913 } 914 return (1); 915 } 916 rname[n] = '\0'; 917 918 /* 919 * Create the link. 920 */ 921 if (create_symlink(todir, rname, file) != 0) { 922 return (1); 923 } 924 } else { 925 /* 926 * Do what we've always done to move regular files. 927 */ 928 (void) strcpy(to, todir); 929 (void) strcat(to, "/"); 930 (void) strcat(to, file); 931 if (create_file(to) != 0) { 932 return (1); 933 } 934 } 935 936 /* 937 * Remove the old file if we've created the new one. 938 */ 939 if (unlink(from) < 0) { 940 syslog(LOG_ERR, "move_file: unlink of %s, error %m", from); 941 return (1); 942 } 943 944 return (0); 945 } 946 947 /* 948 * Create a symbolic link named `lname' to regular file `rname'. 949 * Both files should be in directory `todir'. 950 */ 951 int 952 create_symlink(todir, rname, lname) 953 char *todir; 954 char *rname; 955 char *lname; 956 { 957 int error; 958 char lpath[MAXPATHLEN]; 959 960 /* 961 * Form the full pathname of the link. 962 */ 963 (void) strcpy(lpath, todir); 964 (void) strcat(lpath, "/"); 965 (void) strcat(lpath, lname); 966 967 /* 968 * Now make the new symlink ... 969 */ 970 if (symlink(rname, lpath) < 0) { 971 error = errno; 972 if (error != 0 && error != EEXIST) { 973 if (debug >= 2) { 974 (void) printf( 975 "create_symlink: can't link %s/%s -> %s\n", 976 todir, lname, rname); 977 } 978 return (1); 979 } 980 } 981 982 if (debug) { 983 if (error == EEXIST) { 984 (void) printf("link %s/%s -> %s already exists\n", 985 todir, lname, rname); 986 } else { 987 (void) printf("created link %s/%s -> %s\n", 988 todir, lname, rname); 989 } 990 } 991 992 return (0); 993 } 994 995 /* 996 * remove the name from the specified directory 997 * op = 0: CURRENT 998 * op = 1: BACKUP 999 */ 1000 static void 1001 remove_name(char *name, int op, int startup) 1002 { 1003 int i; 1004 char *alt_dir; 1005 char *queue; 1006 1007 if (op == 0) { 1008 alt_dir = "statmon/sm"; 1009 queue = CURRENT; 1010 } else { 1011 alt_dir = "statmon/sm.bak"; 1012 queue = BACKUP; 1013 } 1014 1015 remove_single_name(name, queue, NULL); 1016 /* 1017 * At startup, entries have not yet been copied to alternate 1018 * directories and thus do not need to be removed. 1019 */ 1020 if (startup == 0) { 1021 for (i = 0; i < pathix; i++) { 1022 remove_single_name(name, path_name[i], alt_dir); 1023 } 1024 } 1025 } 1026 1027 /* 1028 * Remove the name from the specified directory, which is dir1/dir2 or 1029 * dir1, depending on whether dir2 is NULL. 1030 */ 1031 static void 1032 remove_single_name(char *name, char *dir1, char *dir2) 1033 { 1034 int n, error; 1035 char path[MAXPATHLEN+MAXNAMELEN+SM_MAXPATHLEN]; /* why > MAXPATHLEN? */ 1036 char dirpath[MAXPATHLEN]; 1037 char rname[MAXNAMELEN + 1]; /* +1 for NULL term */ 1038 1039 if (strlen(name) + strlen(dir1) + (dir2 != NULL ? strlen(dir2) : 0) 1040 + 3 > MAXPATHLEN) { 1041 if (dir2 != NULL) 1042 syslog(LOG_ERR, 1043 "statd: pathname too long: %s/%s/%s\n", 1044 dir1, dir2, name); 1045 else 1046 syslog(LOG_ERR, 1047 "statd: pathname too long: %s/%s\n", 1048 dir1, name); 1049 1050 return; 1051 } 1052 1053 (void) strcpy(path, dir1); 1054 (void) strcat(path, "/"); 1055 if (dir2 != NULL) { 1056 (void) strcat(path, dir2); 1057 (void) strcat(path, "/"); 1058 } 1059 (void) strcpy(dirpath, path); /* save here - we may need it shortly */ 1060 (void) strcat(path, name); 1061 1062 /* 1063 * Despite the name of this routine :-@), `path' may be a symlink 1064 * to a regular file. If it is, and if that file has no other 1065 * links to it, we must remove it now as well. 1066 */ 1067 if (is_symlink(path)) { 1068 n = readlink(path, rname, MAXNAMELEN); 1069 if (n > 0) { 1070 rname[n] = '\0'; 1071 1072 if (count_symlinks(dirpath, rname, &n) < 0) { 1073 return; 1074 } 1075 1076 if (n == 1) { 1077 (void) strcat(dirpath, rname); 1078 error = unlink(dirpath); 1079 if (debug >= 2) { 1080 if (error < 0) { 1081 (void) printf( 1082 "remove_name: can't unlink %s\n", 1083 dirpath); 1084 } else { 1085 (void) printf( 1086 "remove_name: unlinked %s\n", 1087 dirpath); 1088 } 1089 } 1090 } 1091 } else { 1092 /* 1093 * Policy: if we can't read the symlink, leave it 1094 * here for analysis by the system administrator. 1095 */ 1096 syslog(LOG_ERR, 1097 "statd: can't read link %s: %m\n", path); 1098 } 1099 } 1100 1101 /* 1102 * If it's a regular file, we can assume all symlinks and the 1103 * files to which they refer have been processed already - just 1104 * fall through to here to remove it. 1105 */ 1106 delete_file(path); 1107 } 1108 1109 /* 1110 * Count the number of symlinks in `dir' which point to `name' (also in dir). 1111 * Passes back symlink count in `count'. 1112 * Returns 0 for success, < 0 for failure. 1113 */ 1114 static int 1115 count_symlinks(char *dir, char *name, int *count) 1116 { 1117 int cnt = 0; 1118 int n; 1119 DIR *dp; 1120 struct dirent *dirp; 1121 char lpath[MAXPATHLEN]; 1122 char rname[MAXNAMELEN + 1]; /* +1 for term NULL */ 1123 1124 if ((dp = opendir(dir)) == (DIR *)NULL) { 1125 syslog(LOG_ERR, "count_symlinks: open %s dir, error %m\n", 1126 dir); 1127 return (-1); 1128 } 1129 1130 while ((dirp = readdir(dp)) != NULL) { 1131 if (strcmp(dirp->d_name, ".") == 0 || 1132 strcmp(dirp->d_name, "..") == 0) { 1133 continue; 1134 } 1135 1136 (void) sprintf(lpath, "%s%s", dir, dirp->d_name); 1137 if (is_symlink(lpath)) { 1138 /* 1139 * Fetch the name of the file the symlink refers to. 1140 */ 1141 n = readlink(lpath, rname, MAXNAMELEN); 1142 if (n <= 0) { 1143 if (debug >= 2) { 1144 (void) printf( 1145 "count_symlinks: can't read link %s\n", 1146 lpath); 1147 } 1148 continue; 1149 } 1150 rname[n] = '\0'; 1151 1152 /* 1153 * If `rname' matches `name', bump the count. There 1154 * may well be multiple symlinks to the same name, so 1155 * we must continue to process the entire directory. 1156 */ 1157 if (strcmp(rname, name) == 0) { 1158 cnt++; 1159 } 1160 } 1161 } 1162 1163 (void) closedir(dp); 1164 1165 if (debug) { 1166 (void) printf("count_symlinks: found %d symlinks\n", cnt); 1167 } 1168 *count = cnt; 1169 return (0); 1170 } 1171 1172 /* 1173 * Manage the cache of hostnames. An entry for each host that has recently 1174 * locked a file is kept. There is an in-ram table (rec_table) and an empty 1175 * file in the file system name space (/var/statmon/sm/<name>). This 1176 * routine adds (deletes) the name to (from) the in-ram table and the entry 1177 * to (from) the file system name space. 1178 * 1179 * If op == 1 then the name is added to the queue otherwise the name is 1180 * deleted. 1181 */ 1182 void 1183 record_name(name, op) 1184 char *name; 1185 int op; 1186 { 1187 name_entry *nl; 1188 int i; 1189 char path[MAXPATHLEN+MAXNAMELEN+SM_MAXPATHLEN]; 1190 name_entry **record_q; 1191 unsigned int hash; 1192 1193 /* 1194 * These names are supposed to be just host names, not paths or 1195 * other arbitrary files. 1196 * manipulating the empty pathname unlinks CURRENT, 1197 * manipulating files with '/' would allow you to create and unlink 1198 * files all over the system; LOG_AUTH, it's a security thing. 1199 * Don't remove the directories . and .. 1200 */ 1201 if (name == NULL) 1202 return; 1203 1204 if (name[0] == '\0' || strchr(name, '/') != NULL || 1205 strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { 1206 syslog(LOG_ERR|LOG_AUTH, "statd: attempt to %s \"%s/%s\"", 1207 op == 1 ? "create" : "remove", CURRENT, name); 1208 return; 1209 } 1210 1211 SMHASH(name, hash); 1212 if (debug) { 1213 if (op == 1) 1214 (void) printf("inserting %s at hash %d,\n", 1215 name, hash); 1216 else 1217 (void) printf("deleting %s at hash %d\n", name, hash); 1218 pr_name(name, 1); 1219 } 1220 1221 1222 if (op == 1) { /* insert */ 1223 mutex_lock(&record_table[hash].lock); 1224 record_q = &record_table[hash].sm_rechdp; 1225 if ((nl = find_name(record_q, name)) == (name_entry *)NULL) { 1226 1227 int path_len; 1228 1229 if ((nl = insert_name(record_q, name, 1)) != 1230 (name_entry *) NULL) 1231 nl->count++; 1232 mutex_unlock(&record_table[hash].lock); 1233 /* make an entry in current directory */ 1234 1235 path_len = strlen(CURRENT) + strlen(name) + 2; 1236 if (path_len > MAXPATHLEN) { 1237 syslog(LOG_ERR, 1238 "statd: pathname too long: %s/%s\n", 1239 CURRENT, name); 1240 return; 1241 } 1242 (void) strcpy(path, CURRENT); 1243 (void) strcat(path, "/"); 1244 (void) strcat(path, name); 1245 (void) create_file(path); 1246 if (debug) { 1247 (void) printf("After insert_name\n"); 1248 pr_name(name, 1); 1249 } 1250 /* make an entry in alternate paths */ 1251 for (i = 0; i < pathix; i++) { 1252 path_len = strlen(path_name[i]) + 1253 strlen("/statmon/sm/") + 1254 strlen(name) + 1; 1255 1256 if (path_len > MAXPATHLEN) { 1257 syslog(LOG_ERR, 1258 "statd: pathname too long: %s/statmon/sm/%s\n", 1259 path_name[i], name); 1260 continue; 1261 } 1262 (void) strcpy(path, path_name[i]); 1263 (void) strcat(path, "/statmon/sm/"); 1264 (void) strcat(path, name); 1265 (void) create_file(path); 1266 } 1267 return; 1268 } 1269 nl->count++; 1270 mutex_unlock(&record_table[hash].lock); 1271 1272 } else { /* delete */ 1273 mutex_lock(&record_table[hash].lock); 1274 record_q = &record_table[hash].sm_rechdp; 1275 if ((nl = find_name(record_q, name)) == (name_entry *)NULL) { 1276 mutex_unlock(&record_table[hash].lock); 1277 return; 1278 } 1279 nl->count--; 1280 if (nl->count == 0) { 1281 delete_name(record_q, name); 1282 mutex_unlock(&record_table[hash].lock); 1283 /* remove this entry from current directory */ 1284 remove_name(name, 0, 0); 1285 } else 1286 mutex_unlock(&record_table[hash].lock); 1287 if (debug) { 1288 (void) printf("After delete_name \n"); 1289 pr_name(name, 1); 1290 } 1291 } 1292 } 1293 1294 /* 1295 * This routine adds a symlink in the form of an ASCII dotted quad 1296 * IP address that is linked to the name already recorded in the 1297 * filesystem name space by record_name(). Enough information is 1298 * (hopefully) provided to support other address types in the future. 1299 * The purpose of this is to cache enough information to contact 1300 * hosts in other domains during server crash recovery (see bugid 1301 * 1184192). 1302 * 1303 * The worst failure mode here is that the symlink is not made, and 1304 * statd falls back to the old buggy behavior. 1305 */ 1306 void 1307 record_addr(char *name, sa_family_t family, struct netobj *ah) 1308 { 1309 int i; 1310 int path_len; 1311 char *famstr; 1312 struct in_addr addr; 1313 char *addr6; 1314 char ascii_addr[MAXNAMELEN]; 1315 char path[MAXPATHLEN]; 1316 1317 if (family == AF_INET) { 1318 if (ah->n_len != sizeof (struct in_addr)) 1319 return; 1320 addr = *(struct in_addr *)ah->n_bytes; 1321 } else if (family == AF_INET6) { 1322 if (ah->n_len != sizeof (struct in6_addr)) 1323 return; 1324 addr6 = (char *)ah->n_bytes; 1325 } else 1326 return; 1327 1328 if (debug) { 1329 if (family == AF_INET) 1330 (void) printf("record_addr: addr= %x\n", addr.s_addr); 1331 else if (family == AF_INET6) 1332 (void) printf("record_addr: addr= %x\n", \ 1333 ((struct in6_addr *)addr6)->s6_addr); 1334 } 1335 1336 if (family == AF_INET) { 1337 if (addr.s_addr == INADDR_ANY || 1338 ((addr.s_addr && 0xff000000) == 0) || 1339 IN_BADCLASS(addr.s_addr)) { 1340 syslog(LOG_DEBUG, 1341 "record_addr: illegal IP address %x\n", 1342 addr.s_addr); 1343 return; 1344 } 1345 } 1346 1347 /* convert address to ASCII */ 1348 famstr = family2string(family); 1349 if (famstr == NULL) { 1350 syslog(LOG_DEBUG, 1351 "record_addr: unsupported address family %d\n", 1352 family); 1353 return; 1354 } 1355 1356 switch (family) { 1357 char abuf[INET6_ADDRSTRLEN]; 1358 case AF_INET: 1359 (void) sprintf(ascii_addr, "%s.%s", famstr, inet_ntoa(addr)); 1360 break; 1361 1362 case AF_INET6: 1363 (void) sprintf(ascii_addr, "%s.%s", famstr,\ 1364 inet_ntop(family, addr6, abuf, sizeof (abuf))); 1365 break; 1366 1367 default: 1368 if (debug) { 1369 (void) printf( 1370 "record_addr: family2string supports unknown family %d (%s)\n", 1371 family, 1372 famstr); 1373 } 1374 free(famstr); 1375 return; 1376 } 1377 1378 if (debug) { 1379 (void) printf("record_addr: ascii_addr= %s\n", ascii_addr); 1380 } 1381 free(famstr); 1382 1383 /* 1384 * Make the symlink in CURRENT. The `name' file should have 1385 * been created previously by record_name(). 1386 */ 1387 (void) create_symlink(CURRENT, name, ascii_addr); 1388 1389 /* 1390 * Similarly for alternate paths. 1391 */ 1392 for (i = 0; i < pathix; i++) { 1393 path_len = strlen(path_name[i]) + 1394 strlen("/statmon/sm/") + 1395 strlen(name) + 1; 1396 1397 if (path_len > MAXPATHLEN) { 1398 syslog(LOG_ERR, 1399 "statd: pathname too long: %s/statmon/sm/%s\n", 1400 path_name[i], name); 1401 continue; 1402 } 1403 (void) strcpy(path, path_name[i]); 1404 (void) strcat(path, "/statmon/sm"); 1405 (void) create_symlink(path, name, ascii_addr); 1406 } 1407 } 1408 1409 /* 1410 * SM_CRASH - simulate a crash of statd. 1411 */ 1412 void 1413 sm_crash() 1414 { 1415 name_entry *nl, *next; 1416 mon_entry *nl_monp, *mon_next; 1417 int k; 1418 my_id *nl_idp; 1419 1420 for (k = 0; k < MAX_HASHSIZE; k++) { 1421 mutex_lock(&mon_table[k].lock); 1422 if ((mon_next = mon_table[k].sm_monhdp) == 1423 (mon_entry *) NULL) { 1424 mutex_unlock(&mon_table[k].lock); 1425 continue; 1426 } else { 1427 while ((nl_monp = mon_next) != (mon_entry *)NULL) { 1428 mon_next = mon_next->nxt; 1429 nl_idp = &nl_monp->id.mon_id.my_id; 1430 free(nl_monp->id.mon_id.mon_name); 1431 free(nl_idp->my_name); 1432 free(nl_monp); 1433 } 1434 mon_table[k].sm_monhdp = (mon_entry *)NULL; 1435 } 1436 mutex_unlock(&mon_table[k].lock); 1437 } 1438 1439 /* Clean up entries in record table */ 1440 for (k = 0; k < MAX_HASHSIZE; k++) { 1441 mutex_lock(&record_table[k].lock); 1442 if ((next = record_table[k].sm_rechdp) == 1443 (name_entry *) NULL) { 1444 mutex_unlock(&record_table[k].lock); 1445 continue; 1446 } else { 1447 while ((nl = next) != (name_entry *)NULL) { 1448 next = next->nxt; 1449 free(nl->name); 1450 free(nl); 1451 } 1452 record_table[k].sm_rechdp = (name_entry *)NULL; 1453 } 1454 mutex_unlock(&record_table[k].lock); 1455 } 1456 1457 /* Clean up entries in recovery table */ 1458 mutex_lock(&recov_q.lock); 1459 if ((next = recov_q.sm_recovhdp) != (name_entry *)NULL) { 1460 while ((nl = next) != (name_entry *)NULL) { 1461 next = next->nxt; 1462 free(nl->name); 1463 free(nl); 1464 } 1465 recov_q.sm_recovhdp = (name_entry *)NULL; 1466 } 1467 mutex_unlock(&recov_q.lock); 1468 statd_init(); 1469 } 1470 1471 /* 1472 * Initialize the hash tables: mon_table, record_table, recov_q and 1473 * locks. 1474 */ 1475 void 1476 sm_inithash() 1477 { 1478 int k; 1479 1480 if (debug) 1481 (void) printf("Initializing hash tables\n"); 1482 for (k = 0; k < MAX_HASHSIZE; k++) { 1483 mon_table[k].sm_monhdp = (mon_entry *)NULL; 1484 record_table[k].sm_rechdp = (name_entry *)NULL; 1485 mutex_init(&mon_table[k].lock, USYNC_THREAD, NULL); 1486 mutex_init(&record_table[k].lock, USYNC_THREAD, NULL); 1487 } 1488 mutex_init(&recov_q.lock, USYNC_THREAD, NULL); 1489 recov_q.sm_recovhdp = (name_entry *)NULL; 1490 1491 } 1492 1493 /* 1494 * Maps a socket address family to a name string, or NULL if the family 1495 * is not supported by statd. 1496 * Caller is responsible for freeing storage used by result string, if any. 1497 */ 1498 static char * 1499 family2string(sa_family_t family) 1500 { 1501 char *rc; 1502 1503 switch (family) { 1504 case AF_INET: 1505 rc = strdup(SM_ADDR_IPV4); 1506 break; 1507 1508 case AF_INET6: 1509 rc = strdup(SM_ADDR_IPV6); 1510 break; 1511 1512 default: 1513 rc = NULL; 1514 break; 1515 } 1516 1517 return (rc); 1518 } 1519 1520 /* 1521 * Prints out list in record_table if flag is 1 otherwise 1522 * prints out each list in recov_q specified by name. 1523 */ 1524 static void 1525 pr_name(name, flag) 1526 char *name; 1527 int flag; 1528 { 1529 name_entry *nl; 1530 unsigned int hash; 1531 1532 if (!debug) 1533 return; 1534 if (flag) { 1535 SMHASH(name, hash); 1536 (void) printf("*****record_q: "); 1537 mutex_lock(&record_table[hash].lock); 1538 nl = record_table[hash].sm_rechdp; 1539 while (nl != (name_entry *)NULL) { 1540 (void) printf("(%x), ", (int)nl); 1541 nl = nl->nxt; 1542 } 1543 mutex_unlock(&record_table[hash].lock); 1544 } else { 1545 (void) printf("*****recovery_q: "); 1546 mutex_lock(&recov_q.lock); 1547 nl = recov_q.sm_recovhdp; 1548 while (nl != (name_entry *)NULL) { 1549 (void) printf("(%x), ", (int)nl); 1550 nl = nl->nxt; 1551 } 1552 mutex_unlock(&recov_q.lock); 1553 1554 } 1555 (void) printf("\n"); 1556 } 1557