1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1988 University of Utah. 5 * Copyright (c) 1991 The Regents of the University of California. 6 * Copyright (c) 1999 Michael Smith 7 * Copyright (c) 2005 Pawel Jakub Dawidek <pjd@FreeBSD.org> 8 * 9 * All rights reserved. 10 * 11 * This code is derived from software contributed to Berkeley by 12 * the Systems Programming Group of the University of Utah Computer 13 * Science Department. 14 * 15 * Redistribution and use in source and binary forms, with or without 16 * modification, are permitted provided that the following conditions 17 * are met: 18 * 1. Redistributions of source code must retain the above copyright 19 * notice, this list of conditions and the following disclaimer. 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in the 22 * documentation and/or other materials provided with the distribution. 23 * 3. Neither the name of the University nor the names of its contributors 24 * may be used to endorse or promote products derived from this software 25 * without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 * SUCH DAMAGE. 38 */ 39 40 #include <sys/cdefs.h> 41 #include "opt_ddb.h" 42 #include "opt_syscons.h" 43 44 #include <sys/param.h> 45 #include <sys/systm.h> 46 #include <sys/lock.h> 47 #include <sys/mutex.h> 48 #include <sys/conf.h> 49 #include <sys/cons.h> 50 #include <sys/fcntl.h> 51 #include <sys/kbio.h> 52 #include <sys/kdb.h> 53 #include <sys/kernel.h> 54 #include <sys/malloc.h> 55 #include <sys/msgbuf.h> 56 #include <sys/namei.h> 57 #include <sys/priv.h> 58 #include <sys/proc.h> 59 #include <sys/queue.h> 60 #include <sys/reboot.h> 61 #include <sys/sysctl.h> 62 #include <sys/sbuf.h> 63 #include <sys/tslog.h> 64 #include <sys/tty.h> 65 #include <sys/uio.h> 66 #include <sys/vnode.h> 67 68 #include <ddb/ddb.h> 69 70 #include <dev/kbd/kbdreg.h> 71 72 #include <machine/cpu.h> 73 #include <machine/clock.h> 74 75 /* 76 * Check for 'options EARLY_PRINTF' that may have been used in old kernel 77 * config files. If you are hitting this error you should update your 78 * config to use 'options EARLY_PRINTF=<device name>', e.g. with the 79 * Arm pl011 use: 80 * 81 * options EARLY_PRINTF=pl011 82 */ 83 #if CHECK_EARLY_PRINTF(1) 84 #error Update your config to use 'options EARLY_PRINTF=<device name>' 85 #endif 86 87 static MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling"); 88 89 struct cn_device { 90 STAILQ_ENTRY(cn_device) cnd_next; 91 struct consdev *cnd_cn; 92 }; 93 94 #define CNDEVPATHMAX 32 95 #define CNDEVTAB_SIZE 4 96 static struct cn_device cn_devtab[CNDEVTAB_SIZE]; 97 static STAILQ_HEAD(, cn_device) cn_devlist = 98 STAILQ_HEAD_INITIALIZER(cn_devlist); 99 100 int cons_avail_mask = 0; /* Bit mask. Each registered low level console 101 * which is currently unavailable for inpit 102 * (i.e., if it is in graphics mode) will have 103 * this bit cleared. 104 */ 105 106 int cn_mute; 107 SYSCTL_INT(_kern, OID_AUTO, consmute, CTLFLAG_RW, &cn_mute, 0, 108 "State of the console muting"); 109 110 static char *consbuf; /* buffer used by `consmsgbuf' */ 111 static struct callout conscallout; /* callout for outputting to constty */ 112 struct msgbuf consmsgbuf; /* message buffer for console tty */ 113 static bool console_pausing; /* pause after each line during probe */ 114 static const char console_pausestr[] = 115 "<pause; press any key to proceed to next line or '.' to end pause mode>"; 116 struct tty *constty; /* pointer to console "window" tty */ 117 static struct mtx constty_mtx; /* Mutex for constty assignment. */ 118 MTX_SYSINIT(constty_mtx, &constty_mtx, "constty_mtx", MTX_DEF); 119 static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ 120 MTX_SYSINIT(cnputs_mtx, &cnputs_mtx, "cnputs_mtx", MTX_SPIN | MTX_NOWITNESS); 121 122 static void constty_timeout(void *arg); 123 124 static struct consdev cons_consdev; 125 DATA_SET(cons_set, cons_consdev); 126 SET_DECLARE(cons_set, struct consdev); 127 128 /* 129 * Stub for configurations that don't actually have a keyboard driver. Inclusion 130 * of kbd.c is contingent on any number of keyboard/console drivers being 131 * present in the kernel; rather than trying to catch them all, we'll just 132 * maintain this weak kbdinit that will be overridden by the strong version in 133 * kbd.c if it's present. 134 */ 135 __weak_symbol void 136 kbdinit(void) 137 { 138 139 } 140 141 static void 142 mute_console(void *data __unused) 143 { 144 145 if ((boothowto & (RB_MUTEMSGS | RB_VERBOSE)) == RB_MUTEMSGS) { 146 printf("-- Muting boot messages --\n"); 147 cn_mute = 1; 148 } 149 } 150 151 SYSINIT(mute_console, SI_SUB_COPYRIGHT, SI_ORDER_ANY, mute_console, NULL); 152 153 void 154 cninit(void) 155 { 156 struct consdev *best_cn, *cn, **list; 157 158 TSENTER(); 159 /* 160 * Check if we should mute the console (for security reasons perhaps) 161 * It can be changes dynamically using sysctl kern.consmute 162 * once we are up and going. 163 * 164 */ 165 cn_mute = ((boothowto & (RB_MUTE 166 |RB_SINGLE 167 |RB_VERBOSE 168 |RB_ASKNAME)) == RB_MUTE); 169 170 /* 171 * Bring up the kbd layer just in time for cnprobe. Console drivers 172 * have a dependency on kbd being ready, so this fits nicely between the 173 * machdep callers of cninit() and MI probing/initialization of consoles 174 * here. 175 */ 176 kbdinit(); 177 178 /* 179 * Find the first console with the highest priority. 180 */ 181 best_cn = NULL; 182 SET_FOREACH(list, cons_set) { 183 cn = *list; 184 cnremove(cn); 185 /* Skip cons_consdev. */ 186 if (cn->cn_ops == NULL) 187 continue; 188 cn->cn_ops->cn_probe(cn); 189 if (cn->cn_pri == CN_DEAD) 190 continue; 191 if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) 192 best_cn = cn; 193 if (boothowto & RB_MULTIPLE) { 194 /* 195 * Initialize console, and attach to it. 196 */ 197 cn->cn_ops->cn_init(cn); 198 cnadd(cn); 199 } 200 } 201 if (best_cn == NULL) 202 return; 203 if ((boothowto & RB_MULTIPLE) == 0) { 204 best_cn->cn_ops->cn_init(best_cn); 205 cnadd(best_cn); 206 } 207 if (boothowto & RB_PAUSE) 208 console_pausing = true; 209 /* 210 * Make the best console the preferred console. 211 */ 212 cnselect(best_cn); 213 214 #ifdef EARLY_PRINTF 215 /* 216 * Release early console. 217 */ 218 early_putc = NULL; 219 #endif 220 TSEXIT(); 221 } 222 223 void 224 cninit_finish(void) 225 { 226 console_pausing = false; 227 } 228 229 /* add a new physical console to back the virtual console */ 230 int 231 cnadd(struct consdev *cn) 232 { 233 struct cn_device *cnd; 234 int i; 235 236 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 237 if (cnd->cnd_cn == cn) 238 return (0); 239 for (i = 0; i < CNDEVTAB_SIZE; i++) { 240 cnd = &cn_devtab[i]; 241 if (cnd->cnd_cn == NULL) 242 break; 243 } 244 if (cnd->cnd_cn != NULL) 245 return (ENOMEM); 246 cnd->cnd_cn = cn; 247 if (cn->cn_name[0] == '\0') { 248 /* XXX: it is unclear if/where this print might output */ 249 printf("WARNING: console at %p has no name\n", cn); 250 } 251 STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); 252 if (STAILQ_FIRST(&cn_devlist) == cnd) 253 ttyconsdev_select(cnd->cnd_cn->cn_name); 254 255 /* Add device to the active mask. */ 256 cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); 257 258 return (0); 259 } 260 261 void 262 cnremove(struct consdev *cn) 263 { 264 struct cn_device *cnd; 265 int i; 266 267 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 268 if (cnd->cnd_cn != cn) 269 continue; 270 if (STAILQ_FIRST(&cn_devlist) == cnd) 271 ttyconsdev_select(NULL); 272 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); 273 cnd->cnd_cn = NULL; 274 275 /* Remove this device from available mask. */ 276 for (i = 0; i < CNDEVTAB_SIZE; i++) 277 if (cnd == &cn_devtab[i]) { 278 cons_avail_mask &= ~(1 << i); 279 break; 280 } 281 #if 0 282 /* 283 * XXX 284 * syscons gets really confused if console resources are 285 * freed after the system has initialized. 286 */ 287 if (cn->cn_term != NULL) 288 cn->cn_ops->cn_term(cn); 289 #endif 290 return; 291 } 292 } 293 294 void 295 cnselect(struct consdev *cn) 296 { 297 struct cn_device *cnd; 298 299 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 300 if (cnd->cnd_cn != cn) 301 continue; 302 if (cnd == STAILQ_FIRST(&cn_devlist)) 303 return; 304 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); 305 STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); 306 ttyconsdev_select(cnd->cnd_cn->cn_name); 307 return; 308 } 309 } 310 311 void 312 cnavailable(struct consdev *cn, int available) 313 { 314 int i; 315 316 for (i = 0; i < CNDEVTAB_SIZE; i++) { 317 if (cn_devtab[i].cnd_cn == cn) 318 break; 319 } 320 if (available) { 321 if (i < CNDEVTAB_SIZE) 322 cons_avail_mask |= (1 << i); 323 cn->cn_flags &= ~CN_FLAG_NOAVAIL; 324 } else { 325 if (i < CNDEVTAB_SIZE) 326 cons_avail_mask &= ~(1 << i); 327 cn->cn_flags |= CN_FLAG_NOAVAIL; 328 } 329 } 330 331 int 332 cnunavailable(void) 333 { 334 335 return (cons_avail_mask == 0); 336 } 337 338 /* 339 * sysctl_kern_console() provides output parseable in conscontrol(1). 340 */ 341 static int 342 sysctl_kern_console(SYSCTL_HANDLER_ARGS) 343 { 344 struct cn_device *cnd; 345 struct consdev *cp, **list; 346 char *p; 347 bool delete; 348 int error; 349 struct sbuf *sb; 350 351 sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND | 352 SBUF_INCLUDENUL); 353 if (sb == NULL) 354 return (ENOMEM); 355 sbuf_clear(sb); 356 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 357 sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name); 358 sbuf_putc(sb, '/'); 359 SET_FOREACH(list, cons_set) { 360 cp = *list; 361 if (cp->cn_name[0] != '\0') 362 sbuf_printf(sb, "%s,", cp->cn_name); 363 } 364 sbuf_finish(sb); 365 error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req); 366 if (error == 0 && req->newptr != NULL) { 367 p = sbuf_data(sb); 368 error = ENXIO; 369 delete = false; 370 if (*p == '-') { 371 delete = true; 372 p++; 373 } 374 SET_FOREACH(list, cons_set) { 375 cp = *list; 376 if (strcmp(p, cp->cn_name) != 0) 377 continue; 378 if (delete) { 379 cnremove(cp); 380 error = 0; 381 } else { 382 error = cnadd(cp); 383 if (error == 0) 384 cnselect(cp); 385 } 386 break; 387 } 388 } 389 sbuf_delete(sb); 390 return (error); 391 } 392 393 SYSCTL_PROC(_kern, OID_AUTO, console, 394 CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 0, 395 sysctl_kern_console, "A", 396 "Console device control"); 397 398 void 399 cngrab(void) 400 { 401 struct cn_device *cnd; 402 struct consdev *cn; 403 404 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 405 cn = cnd->cnd_cn; 406 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) 407 cn->cn_ops->cn_grab(cn); 408 } 409 } 410 411 void 412 cnungrab(void) 413 { 414 struct cn_device *cnd; 415 struct consdev *cn; 416 417 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 418 cn = cnd->cnd_cn; 419 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) 420 cn->cn_ops->cn_ungrab(cn); 421 } 422 } 423 424 void 425 cnresume(void) 426 { 427 struct cn_device *cnd; 428 struct consdev *cn; 429 430 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 431 cn = cnd->cnd_cn; 432 if (cn->cn_ops->cn_resume != NULL) 433 cn->cn_ops->cn_resume(cn); 434 } 435 } 436 437 /* 438 * Low level console routines. 439 */ 440 int 441 cngetc(void) 442 { 443 int c; 444 445 if (cn_mute) 446 return (-1); 447 while ((c = cncheckc()) == -1) 448 cpu_spinwait(); 449 if (c == '\r') 450 c = '\n'; /* console input is always ICRNL */ 451 return (c); 452 } 453 454 int 455 cncheckc(void) 456 { 457 struct cn_device *cnd; 458 struct consdev *cn; 459 int c; 460 461 if (cn_mute) 462 return (-1); 463 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 464 cn = cnd->cnd_cn; 465 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { 466 c = cn->cn_ops->cn_getc(cn); 467 if (c != -1) 468 return (c); 469 } 470 } 471 return (-1); 472 } 473 474 void 475 cngets(char *cp, size_t size, int visible) 476 { 477 char *lp, *end; 478 int c; 479 480 cngrab(); 481 482 lp = cp; 483 end = cp + size - 1; 484 for (;;) { 485 c = cngetc() & 0177; 486 switch (c) { 487 case '\n': 488 case '\r': 489 cnputc(c); 490 *lp = '\0'; 491 cnungrab(); 492 return; 493 case '\b': 494 case '\177': 495 if (lp > cp) { 496 if (visible) 497 cnputs("\b \b"); 498 lp--; 499 } 500 continue; 501 case '\0': 502 continue; 503 default: 504 if (lp < end) { 505 switch (visible) { 506 case GETS_NOECHO: 507 break; 508 case GETS_ECHOPASS: 509 cnputc('*'); 510 break; 511 default: 512 cnputc(c); 513 break; 514 } 515 *lp++ = c; 516 } 517 } 518 } 519 } 520 521 void 522 cnputc(int c) 523 { 524 struct cn_device *cnd; 525 struct consdev *cn; 526 const char *cp; 527 528 #ifdef EARLY_PRINTF 529 if (early_putc != NULL) { 530 if (c == '\n') 531 early_putc('\r'); 532 early_putc(c); 533 return; 534 } 535 #endif 536 537 if (cn_mute || c == '\0') 538 return; 539 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 540 cn = cnd->cnd_cn; 541 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { 542 if (c == '\n') 543 cn->cn_ops->cn_putc(cn, '\r'); 544 cn->cn_ops->cn_putc(cn, c); 545 } 546 } 547 if (console_pausing && c == '\n' && !kdb_active) { 548 for (cp = console_pausestr; *cp != '\0'; cp++) 549 cnputc(*cp); 550 cngrab(); 551 if (cngetc() == '.') 552 console_pausing = false; 553 cnungrab(); 554 cnputc('\r'); 555 for (cp = console_pausestr; *cp != '\0'; cp++) 556 cnputc(' '); 557 cnputc('\r'); 558 } 559 } 560 561 void 562 cnputsn(const char *p, size_t n) 563 { 564 size_t i; 565 bool unlock_reqd = false; 566 567 if (mtx_initialized(&cnputs_mtx)) { 568 /* 569 * NOTE: Debug prints and/or witness printouts in 570 * console driver clients can cause the "cnputs_mtx" 571 * mutex to recurse. Simply return if that happens. 572 */ 573 if (mtx_owned(&cnputs_mtx)) 574 return; 575 mtx_lock_spin(&cnputs_mtx); 576 unlock_reqd = true; 577 } 578 579 for (i = 0; i < n; i++) 580 cnputc(p[i]); 581 582 if (unlock_reqd) 583 mtx_unlock_spin(&cnputs_mtx); 584 } 585 586 void 587 cnputs(const char *p) 588 { 589 cnputsn(p, strlen(p)); 590 } 591 592 static unsigned int consmsgbuf_size = 65536; 593 SYSCTL_UINT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RWTUN, &consmsgbuf_size, 594 0, "Console tty buffer size"); 595 596 /* 597 * Redirect console output to a tty. 598 */ 599 int 600 constty_set(struct tty *tp) 601 { 602 int size = consmsgbuf_size; 603 void *buf = NULL; 604 605 tty_assert_locked(tp); 606 if (constty == tp) 607 return (0); 608 if (constty != NULL) 609 return (EBUSY); 610 611 if (consbuf == NULL) { 612 tty_unlock(tp); 613 buf = malloc(size, M_TTYCONS, M_WAITOK); 614 tty_lock(tp); 615 } 616 mtx_lock(&constty_mtx); 617 if (constty != NULL) { 618 mtx_unlock(&constty_mtx); 619 free(buf, M_TTYCONS); 620 return (EBUSY); 621 } 622 if (consbuf == NULL) { 623 consbuf = buf; 624 msgbuf_init(&consmsgbuf, buf, size); 625 } else 626 free(buf, M_TTYCONS); 627 constty = tp; 628 mtx_unlock(&constty_mtx); 629 630 callout_init_mtx(&conscallout, tty_getlock(tp), 0); 631 constty_timeout(tp); 632 return (0); 633 } 634 635 /* 636 * Disable console redirection to a tty. 637 */ 638 int 639 constty_clear(struct tty *tp) 640 { 641 int c; 642 643 tty_assert_locked(tp); 644 if (constty != tp) 645 return (ENXIO); 646 callout_stop(&conscallout); 647 mtx_lock(&constty_mtx); 648 constty = NULL; 649 mtx_unlock(&constty_mtx); 650 while ((c = msgbuf_getchar(&consmsgbuf)) != -1) 651 cnputc(c); 652 /* We never free consbuf because it can still be in use. */ 653 return (0); 654 } 655 656 /* Times per second to check for pending console tty messages. */ 657 static int constty_wakeups_per_second = 15; 658 SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, 659 &constty_wakeups_per_second, 0, 660 "Times per second to check for pending console tty messages"); 661 662 static void 663 constty_timeout(void *arg) 664 { 665 struct tty *tp = arg; 666 int c; 667 668 tty_assert_locked(tp); 669 while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { 670 if (tty_putchar(tp, c) < 0) { 671 constty_clear(tp); 672 return; 673 } 674 } 675 callout_reset_sbt(&conscallout, SBT_1S / constty_wakeups_per_second, 676 0, constty_timeout, tp, C_PREL(1)); 677 } 678 679 /* 680 * Sysbeep(), if we have hardware for it 681 */ 682 683 #ifdef HAS_TIMER_SPKR 684 685 static bool beeping; 686 static struct callout beeping_timer; 687 688 static void 689 sysbeepstop(void *chan) 690 { 691 692 timer_spkr_release(); 693 beeping = false; 694 } 695 696 int 697 sysbeep(int pitch, sbintime_t duration) 698 { 699 700 if (timer_spkr_acquire()) { 701 if (!beeping) { 702 /* Something else owns it. */ 703 return (EBUSY); 704 } 705 } 706 timer_spkr_setfreq(pitch); 707 if (!beeping) { 708 beeping = true; 709 callout_reset_sbt(&beeping_timer, duration, 0, sysbeepstop, 710 NULL, C_PREL(5)); 711 } 712 return (0); 713 } 714 715 static void 716 sysbeep_init(void *unused) 717 { 718 719 callout_init(&beeping_timer, 1); 720 } 721 SYSINIT(sysbeep, SI_SUB_SOFTINTR, SI_ORDER_ANY, sysbeep_init, NULL); 722 #else 723 724 /* 725 * No hardware, no sound 726 */ 727 728 int 729 sysbeep(int pitch __unused, sbintime_t duration __unused) 730 { 731 732 return (ENODEV); 733 } 734 735 #endif 736 737 /* 738 * Temporary support for sc(4) to vt(4) transition. 739 */ 740 static char vty_name[16]; 741 SYSCTL_STRING(_kern, OID_AUTO, vty, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, vty_name, 742 0, "Console vty driver"); 743 744 int 745 vty_enabled(unsigned vty) 746 { 747 static unsigned vty_selected = 0; 748 749 if (vty_selected == 0) { 750 TUNABLE_STR_FETCH("kern.vty", vty_name, sizeof(vty_name)); 751 do { 752 #if defined(DEV_SC) 753 if (strcmp(vty_name, "sc") == 0) { 754 vty_selected = VTY_SC; 755 break; 756 } 757 #endif 758 #if defined(DEV_VT) 759 if (strcmp(vty_name, "vt") == 0) { 760 vty_selected = VTY_VT; 761 break; 762 } 763 #endif 764 #if defined(DEV_VT) 765 vty_selected = VTY_VT; 766 #elif defined(DEV_SC) 767 vty_selected = VTY_SC; 768 #endif 769 } while (0); 770 771 if (vty_selected == VTY_VT) 772 strcpy(vty_name, "vt"); 773 else if (vty_selected == VTY_SC) 774 strcpy(vty_name, "sc"); 775 } 776 return ((vty_selected & vty) != 0); 777 } 778