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 #pragma ident "%Z%%M% %I% %E% SMI" 26 27 /* 28 * Configuration and setup interface to vcc driver. 29 * At intialization time, vntsd opens vcc ctrl port and read initial 30 * configuratioa. It manages console groups, creates the listen thread, 31 * dynamically adds and removes virtual console within a group. 32 */ 33 34 35 #include <syslog.h> 36 #include <stdio.h> 37 #include <sys/types.h> 38 #include <sys/ipc.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <sys/socket.h> 43 #include <sys/ipc.h> 44 #include <sys/shm.h> 45 #include <sys/sem.h> 46 #include <wait.h> 47 #include <time.h> 48 #include <synch.h> 49 #include <netinet/in.h> 50 #include <thread.h> 51 #include <signal.h> 52 #include "vntsd.h" 53 54 /* signal all clients that console has been deleted */ 55 boolean_t 56 vntsd_notify_client_cons_del(vntsd_client_t *clientp) 57 { 58 (void) mutex_lock(&clientp->lock); 59 clientp->status |= VNTSD_CLIENT_CONS_DELETED; 60 (void) thr_kill(clientp->cons_tid, SIGUSR1); 61 (void) mutex_unlock(&clientp->lock); 62 return (B_FALSE); 63 } 64 65 /* free console structure */ 66 static void 67 free_cons(vntsd_cons_t *consp) 68 { 69 assert(consp); 70 (void) mutex_destroy(&consp->lock); 71 (void) cond_destroy(&consp->cvp); 72 if (consp->vcc_fd != -1) 73 (void) close(consp->vcc_fd); 74 free(consp); 75 } 76 77 /* free group structure */ 78 static void 79 free_group(vntsd_group_t *groupp) 80 { 81 assert(groupp); 82 (void) mutex_destroy(&groupp->lock); 83 (void) cond_destroy(&groupp->cvp); 84 if (groupp->sockfd != -1) 85 (void) close(groupp->sockfd); 86 free(groupp); 87 } 88 89 /* 90 * all clients connected to a console must disconnect before 91 * removing a console. 92 */ 93 static void 94 cleanup_cons(vntsd_cons_t *consp) 95 { 96 vntsd_group_t *groupp; 97 timestruc_t to; 98 99 assert(consp); 100 D1(stderr, "t@%d vntsd_disconn_clients@%d\n", thr_self(), 101 consp->cons_no); 102 103 groupp = consp->group; 104 assert(groupp); 105 106 107 (void) mutex_lock(&consp->lock); 108 109 /* wait for all clients disconnect from the console */ 110 while (consp->clientpq != NULL) { 111 consp->status |= VNTSD_CONS_SIG_WAIT; 112 113 /* signal client to disconnect the console */ 114 (void) vntsd_que_walk(consp->clientpq, 115 (el_func_t)vntsd_notify_client_cons_del); 116 117 (void) thr_kill(consp->wr_tid, SIGUSR1); 118 to.tv_sec = VNTSD_CV_WAIT_DELTIME; 119 to.tv_nsec = 0; 120 121 /* wait for clients to disconnect */ 122 (void) cond_reltimedwait(&consp->cvp, &consp->lock, &to); 123 } 124 125 /* reduce console count in the group */ 126 (void) mutex_lock(&groupp->lock); 127 assert(groupp->num_cons > 0); 128 groupp->num_cons--; 129 (void) mutex_unlock(&groupp->lock); 130 131 (void) mutex_unlock(&consp->lock); 132 133 free_cons(consp); 134 } 135 136 /* search for a group whose console is being deleted */ 137 static boolean_t 138 find_clean_cons_group(vntsd_group_t *groupp) 139 { 140 if (groupp->status & VNTSD_GROUP_CLEAN_CONS) { 141 return (B_TRUE); 142 } else { 143 return (B_FALSE); 144 } 145 } 146 147 /* search for a console that is being deleted */ 148 static boolean_t 149 find_clean_cons(vntsd_cons_t *consp) 150 { 151 if (consp->status & VNTSD_CONS_DELETED) { 152 return (B_TRUE); 153 } else { 154 return (B_FALSE); 155 } 156 } 157 158 /* delete a console */ 159 void 160 vntsd_delete_cons(vntsd_t *vntsdp) 161 { 162 vntsd_group_t *groupp; 163 vntsd_cons_t *consp; 164 165 for (; ; ) { 166 /* get the group contains deleted console */ 167 (void) mutex_lock(&vntsdp->lock); 168 groupp = vntsd_que_walk(vntsdp->grouppq, 169 (el_func_t)find_clean_cons_group); 170 if (groupp == NULL) { 171 /* no more group has console deleted */ 172 (void) mutex_unlock(&vntsdp->lock); 173 return; 174 } 175 (void) mutex_lock(&groupp->lock); 176 groupp->status &= ~VNTSD_GROUP_CLEAN_CONS; 177 (void) mutex_unlock(&groupp->lock); 178 (void) mutex_unlock(&vntsdp->lock); 179 180 for (; ; ) { 181 /* get the console to be deleted */ 182 (void) mutex_lock(&groupp->lock); 183 184 /* clean up any deleted console in the group */ 185 if (groupp->conspq != NULL) { 186 consp = vntsd_que_walk(groupp->conspq, 187 (el_func_t)find_clean_cons); 188 if (consp == NULL) { 189 /* no more cons to delete */ 190 (void) mutex_unlock(&groupp->lock); 191 break; 192 } 193 194 /* remove console from the group */ 195 (void) vntsd_que_rm(&groupp->conspq, consp); 196 (void) mutex_unlock(&groupp->lock); 197 198 /* clean up the console */ 199 cleanup_cons(consp); 200 } 201 202 /* delete group? */ 203 if (groupp->conspq == NULL) { 204 /* no more console in the group delete group */ 205 assert(groupp->vntsd); 206 207 (void) mutex_lock(&groupp->vntsd->lock); 208 (void) vntsd_que_rm(&groupp->vntsd->grouppq, 209 groupp); 210 (void) mutex_unlock(&groupp->vntsd->lock); 211 212 /* clean up the group */ 213 vntsd_clean_group(groupp); 214 break; 215 } 216 } 217 } 218 } 219 220 /* clean up a group */ 221 void 222 vntsd_clean_group(vntsd_group_t *groupp) 223 { 224 225 timestruc_t to; 226 227 D1(stderr, "t@%d clean_group() group=%s tcp=%lld\n", thr_self(), 228 groupp->group_name, groupp->tcp_port); 229 230 (void) mutex_lock(&groupp->lock); 231 232 /* prevent from reentry */ 233 if (groupp->status & VNTSD_GROUP_IN_CLEANUP) { 234 (void) mutex_unlock(&groupp->lock); 235 return; 236 } 237 groupp->status |= VNTSD_GROUP_IN_CLEANUP; 238 239 /* mark group waiting for listen thread to exits */ 240 groupp->status |= VNTSD_GROUP_SIG_WAIT; 241 (void) mutex_unlock(&groupp->lock); 242 243 vntsd_free_que(&groupp->conspq, (clean_func_t)cleanup_cons); 244 245 (void) mutex_lock(&groupp->lock); 246 /* walk through no cons client queue */ 247 while (groupp->no_cons_clientpq != NULL) { 248 (void) vntsd_que_walk(groupp->no_cons_clientpq, 249 (el_func_t)vntsd_notify_client_cons_del); 250 to.tv_sec = VNTSD_CV_WAIT_DELTIME; 251 to.tv_nsec = 0; 252 (void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to); 253 } 254 255 /* waiting for listen thread to exit */ 256 while (groupp->status & VNTSD_GROUP_SIG_WAIT) { 257 /* signal listen thread to exit */ 258 (void) thr_kill(groupp->listen_tid, SIGUSR1); 259 to.tv_sec = VNTSD_CV_WAIT_DELTIME; 260 to.tv_nsec = 0; 261 /* wait listen thread to exit */ 262 (void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to); 263 } 264 265 (void) mutex_unlock(&groupp->lock); 266 (void) thr_join(groupp->listen_tid, NULL, NULL); 267 /* free group */ 268 free_group(groupp); 269 } 270 271 /* allocate and initialize console structure */ 272 static vntsd_cons_t * 273 alloc_cons(vntsd_group_t *groupp, vcc_console_t *consolep) 274 { 275 vntsd_cons_t *consp; 276 int rv; 277 278 /* allocate console */ 279 consp = (vntsd_cons_t *)malloc(sizeof (vntsd_cons_t)); 280 if (consp == NULL) { 281 vntsd_log(VNTSD_ERR_NO_MEM, "alloc_cons"); 282 return (NULL); 283 } 284 285 /* intialize console */ 286 bzero(consp, sizeof (vntsd_cons_t)); 287 288 (void) mutex_init(&consp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL); 289 (void) cond_init(&consp->cvp, USYNC_THREAD, NULL); 290 291 consp->cons_no = consolep->cons_no; 292 (void) strlcpy(consp->domain_name, consolep->domain_name, MAXPATHLEN); 293 (void) strlcpy(consp->dev_name, consolep->dev_name, MAXPATHLEN); 294 consp->wr_tid = (thread_t)-1; 295 consp->vcc_fd = -1; 296 297 /* join the group */ 298 (void) mutex_lock(&groupp->lock); 299 300 if ((rv = vntsd_que_append(&groupp->conspq, consp)) != 301 VNTSD_SUCCESS) { 302 (void) mutex_unlock(&groupp->lock); 303 vntsd_log(rv, "alloc_cons"); 304 free_cons(consp); 305 return (NULL); 306 } 307 groupp->num_cons++; 308 consp->group = groupp; 309 310 (void) mutex_unlock(&groupp->lock); 311 312 D1(stderr, "t@%d alloc_cons@%d %s %s\n", thr_self(), 313 consp->cons_no, consp->domain_name, consp->dev_name); 314 315 return (consp); 316 } 317 318 /* compare tcp with group->tcp */ 319 static boolean_t 320 grp_by_tcp(vntsd_group_t *groupp, uint64_t *tcp_port) 321 { 322 assert(groupp); 323 assert(tcp_port); 324 return (groupp->tcp_port == *tcp_port); 325 } 326 327 /* allocate and initialize group */ 328 static vntsd_group_t * 329 alloc_group(vntsd_t *vntsdp, char *group_name, uint64_t tcp_port) 330 { 331 vntsd_group_t *groupp; 332 333 /* allocate group */ 334 groupp = (vntsd_group_t *)malloc(sizeof (vntsd_group_t)); 335 if (groupp == NULL) { 336 vntsd_log(VNTSD_ERR_NO_MEM, "alloc_group"); 337 return (NULL); 338 } 339 340 /* initialize group */ 341 bzero(groupp, sizeof (vntsd_group_t)); 342 343 (void) mutex_init(&groupp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL); 344 (void) cond_init(&groupp->cvp, USYNC_THREAD, NULL); 345 346 if (group_name != NULL) { 347 (void) memcpy(groupp->group_name, group_name, MAXPATHLEN); 348 } 349 350 groupp->tcp_port = tcp_port; 351 groupp->listen_tid = (thread_t)-1; 352 groupp->sockfd = -1; 353 groupp->vntsd = vntsdp; 354 355 D1(stderr, "t@%d alloc_group@%lld:%s\n", thr_self(), groupp->tcp_port, 356 groupp->group_name); 357 358 return (groupp); 359 } 360 361 /* mark a deleted console */ 362 boolean_t 363 vntsd_mark_deleted_cons(vntsd_cons_t *consp) 364 { 365 (void) mutex_lock(&consp->lock); 366 consp->status |= VNTSD_CONS_DELETED; 367 (void) mutex_unlock(&consp->lock); 368 return (B_FALSE); 369 } 370 371 /* 372 * Initialize a console, if console is associated with with a 373 * new group, intialize the group. 374 */ 375 static int 376 alloc_cons_with_group(vntsd_t *vntsdp, vcc_console_t *consp, 377 vntsd_group_t **new_groupp) 378 { 379 vntsd_group_t *groupp = NULL; 380 int rv; 381 382 *new_groupp = NULL; 383 384 /* match group by tcp port */ 385 386 387 (void) mutex_lock(&vntsdp->lock); 388 groupp = vntsd_que_find(vntsdp->grouppq, 389 (compare_func_t)grp_by_tcp, (void *)&(consp->tcp_port)); 390 if (groupp != NULL) 391 (void) mutex_lock(&groupp->lock); 392 393 (void) mutex_unlock(&vntsdp->lock); 394 395 if (groupp != NULL) { 396 /* 397 * group with same tcp port found. 398 * if there is no console in the group, the 399 * group should be removed and the tcp port can 400 * be used for tne new group. 401 * This is possible, when there is tight loop of 402 * creating/deleting domains. When a vcc port is 403 * removed, a read thread will have an I/O error because 404 * vcc has closed the port. The read thread then marks 405 * the console is removed and notify main thread to 406 * remove the console. 407 * Meanwhile, the same port and its group (with same 408 * tcp port and group name) is created. Vcc notify 409 * vntsd that new console is added. 410 * Main thread now have two events. If main thread polls 411 * out vcc notification first, it will find that there is 412 * a group has no console. 413 */ 414 415 if (vntsd_chk_group_total_cons(groupp) == 0) { 416 417 /* all consoles in the group have been removed */ 418 (void) vntsd_que_walk(groupp->conspq, 419 (el_func_t)vntsd_mark_deleted_cons); 420 groupp->status |= VNTSD_GROUP_CLEAN_CONS; 421 (void) mutex_unlock(&groupp->lock); 422 groupp = NULL; 423 424 } else if (strcmp(groupp->group_name, consp->group_name)) { 425 /* conflict group name */ 426 vntsd_log(VNTSD_ERR_VCC_GRP_NAME, 427 "group name is different from existing group"); 428 (void) mutex_unlock(&groupp->lock); 429 return (VNTSD_ERR_VCC_CTRL_DATA); 430 431 } else { 432 /* group already existed */ 433 (void) mutex_unlock(&groupp->lock); 434 } 435 436 } 437 438 if (groupp == NULL) { 439 /* new group */ 440 groupp = alloc_group(vntsdp, consp->group_name, 441 consp->tcp_port); 442 if (groupp == NULL) { 443 return (VNTSD_ERR_NO_MEM); 444 } 445 446 assert(groupp->conspq == NULL); 447 /* queue group to vntsdp */ 448 (void) mutex_lock(&vntsdp->lock); 449 rv = vntsd_que_append(&vntsdp->grouppq, groupp); 450 (void) mutex_unlock(&vntsdp->lock); 451 452 if (rv != VNTSD_SUCCESS) { 453 return (rv); 454 } 455 456 *new_groupp = groupp; 457 } 458 459 /* intialize console */ 460 if (alloc_cons(groupp, consp) == NULL) { 461 /* no memory */ 462 if (new_groupp != NULL) { 463 /* clean up new group */ 464 free_group(groupp); 465 } 466 467 return (VNTSD_ERR_NO_MEM); 468 } 469 470 return (VNTSD_SUCCESS); 471 472 } 473 474 475 /* create listen thread */ 476 static boolean_t 477 create_listen_thread(vntsd_group_t *groupp) 478 { 479 480 char err_msg[VNTSD_LINE_LEN]; 481 int rv; 482 483 assert(groupp); 484 485 (void) mutex_lock(&groupp->lock); 486 assert(groupp->num_cons); 487 488 D1(stderr, "t@%d create_listen:%lld\n", thr_self(), groupp->tcp_port); 489 490 if ((rv = thr_create(NULL, 0, (thr_func_t)vntsd_listen_thread, 491 (void *)groupp, THR_DETACHED, &groupp->listen_tid)) 492 != 0) { 493 (void) (void) snprintf(err_msg, sizeof (err_msg), 494 "Can not create listen thread for" 495 "group %s tcp %llx\n", groupp->group_name, 496 groupp->tcp_port); 497 vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg); 498 499 /* clean up group queue */ 500 vntsd_free_que(&groupp->conspq, (clean_func_t)free_cons); 501 groupp->listen_tid = (thread_t)-1; 502 } 503 504 (void) mutex_unlock(&groupp->lock); 505 506 return (rv != 0); 507 } 508 509 /* find deleted console by console no */ 510 static boolean_t 511 deleted_cons_by_consno(vntsd_cons_t *consp, int *cons_no) 512 { 513 vntsd_client_t *clientp; 514 515 assert(consp); 516 517 if (consp->cons_no != *cons_no) 518 return (B_FALSE); 519 520 /* has console marked as deleted? */ 521 if ((consp->status & VNTSD_CONS_DELETED) == 0) 522 return (B_TRUE); 523 524 if (consp->clientpq == NULL) 525 /* there is no client for this console */ 526 return (B_TRUE); 527 528 /* need to notify clients of console ? */ 529 clientp = (vntsd_client_t *)consp->clientpq->handle; 530 531 if (clientp->status & VNTSD_CLIENT_CONS_DELETED) 532 /* clients of console have notified */ 533 return (B_FALSE); 534 535 return (B_TRUE); 536 } 537 538 /* find group structure from console no */ 539 static boolean_t 540 find_cons_group_by_cons_no(vntsd_group_t *groupp, uint_t *cons_no) 541 { 542 vntsd_cons_t *consp; 543 544 consp = vntsd_que_find(groupp->conspq, 545 (compare_func_t)deleted_cons_by_consno, cons_no); 546 return (consp != NULL); 547 548 } 549 550 /* delete a console if the console exists in the vntsd */ 551 static void 552 delete_cons_before_add(vntsd_t *vntsdp, uint_t cons_no) 553 { 554 vntsd_group_t *groupp; 555 vntsd_cons_t *consp; 556 557 /* group exists? */ 558 (void) mutex_lock(&vntsdp->lock); 559 groupp = vntsd_que_find(vntsdp->grouppq, 560 (compare_func_t)find_cons_group_by_cons_no, 561 &cons_no); 562 (void) mutex_unlock(&vntsdp->lock); 563 564 if (groupp == NULL) { 565 /* no such group */ 566 return; 567 } 568 569 /* group exists, if console exists? */ 570 (void) mutex_lock(&groupp->lock); 571 consp = vntsd_que_find(groupp->conspq, 572 (compare_func_t)deleted_cons_by_consno, &cons_no); 573 574 if (consp == NULL) { 575 /* no such console */ 576 (void) mutex_unlock(&groupp->lock); 577 return; 578 } 579 580 /* console exists - mark console for main thread to delete it */ 581 (void) mutex_lock(&consp->lock); 582 583 if (consp->status & VNTSD_CONS_DELETED) { 584 /* already marked */ 585 (void) mutex_unlock(&consp->lock); 586 (void) mutex_unlock(&groupp->lock); 587 return; 588 } 589 590 consp->status |= VNTSD_CONS_DELETED; 591 groupp->status |= VNTSD_GROUP_CLEAN_CONS; 592 593 (void) mutex_unlock(&consp->lock); 594 (void) mutex_unlock(&groupp->lock); 595 596 } 597 598 /* add a console */ 599 static void 600 do_add_cons(vntsd_t *vntsdp, int cons_no) 601 { 602 vcc_console_t console; 603 vntsd_group_t *groupp; 604 int rv; 605 char err_msg[VNTSD_LINE_LEN]; 606 607 608 (void) snprintf(err_msg, sizeof (err_msg), 609 "do_add_cons():Can not add console=%d", cons_no); 610 611 /* get console configuration from vcc */ 612 613 if ((rv = vntsd_vcc_ioctl(VCC_CONS_INFO, cons_no, (void *)&console)) 614 != VNTSD_SUCCESS) { 615 vntsd_log(rv, err_msg); 616 return; 617 } 618 619 /* clean up the console if console was deleted and added again */ 620 delete_cons_before_add(vntsdp, console.cons_no); 621 622 /* initialize console */ 623 624 if ((rv = alloc_cons_with_group(vntsdp, &console, &groupp)) != 625 VNTSD_SUCCESS) { 626 /* no memory to add this new console */ 627 vntsd_log(rv, err_msg); 628 return; 629 } 630 631 if (groupp != NULL) { 632 /* new group */ 633 /* create listen thread for this console */ 634 if (create_listen_thread(groupp)) { 635 vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg); 636 free_group(groupp); 637 } 638 639 } 640 } 641 642 /* daemon wake up */ 643 void 644 vntsd_daemon_wakeup(vntsd_t *vntsdp) 645 { 646 647 vcc_response_t inq_data; 648 649 /* reason to wake up */ 650 if (vntsd_vcc_ioctl(VCC_INQUIRY, 0, (void *)&inq_data) != 651 VNTSD_SUCCESS) { 652 vntsd_log(VNTSD_ERR_VCC_IOCTL, "vntsd_daemon_wakeup()"); 653 return; 654 } 655 656 D1(stderr, "t@%d vntsd_daemon_wakup:msg %d port %x\n", thr_self(), 657 inq_data.reason, inq_data.cons_no); 658 659 switch (inq_data.reason) { 660 661 case VCC_CONS_ADDED: 662 do_add_cons(vntsdp, inq_data.cons_no); 663 break; 664 665 case VCC_CONS_MISS_ADDED: 666 /* an added port was deleted before vntsd can process it */ 667 return; 668 669 default: 670 DERR(stderr, "t@%d daemon_wakeup:ioctl_unknown %d\n", 671 thr_self(), inq_data.reason); 672 vntsd_log(VNTSD_ERR_UNKNOWN_CMD, "from vcc\n"); 673 break; 674 } 675 } 676 677 /* initial console configuration */ 678 void 679 vntsd_get_config(vntsd_t *vntsdp) 680 { 681 682 int i; 683 int num_cons; 684 vcc_console_t *consp; 685 vntsd_group_t *groupp; 686 687 /* num of consoles */ 688 num_cons = 0; 689 690 if (vntsd_vcc_ioctl(VCC_NUM_CONSOLE, 0, (void *)&num_cons) != 691 VNTSD_SUCCESS) { 692 vntsd_log(VNTSD_ERR_VCC_IOCTL, "VCC_NUM_CONSOLE failed\n"); 693 return; 694 } 695 696 D3(stderr, "get_config:num_cons=%d", num_cons); 697 698 if (num_cons == 0) { 699 return; 700 } 701 702 /* allocate memory for all consoles */ 703 consp = malloc(num_cons*sizeof (vcc_console_t)); 704 705 if (consp == NULL) { 706 vntsd_log(VNTSD_ERR_NO_MEM, "for console table."); 707 return; 708 } 709 710 /* get console table */ 711 if (vntsd_vcc_ioctl(VCC_CONS_TBL, 0, (void *)consp) != VNTSD_SUCCESS) { 712 vntsd_log(VNTSD_ERR_VCC_IOCTL, " VCC_CONS_TBL " 713 "for console table\n"); 714 return; 715 } 716 717 /* intialize groups and consoles */ 718 for (i = 0; i < num_cons; i++) { 719 if (alloc_cons_with_group(vntsdp, &consp[i], &groupp) 720 != VNTSD_SUCCESS) { 721 vntsd_log(VNTSD_ERR_ADD_CONS_FAILED, "get_config"); 722 } 723 } 724 725 /* create listen thread for each group */ 726 (void) mutex_lock(&vntsdp->lock); 727 728 for (; ; ) { 729 groupp = vntsd_que_walk(vntsdp->grouppq, 730 (el_func_t)create_listen_thread); 731 if (groupp == NULL) { 732 break; 733 } 734 vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, "get config()"); 735 } 736 737 (void) mutex_unlock(&vntsdp->lock); 738 } 739