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