kern_cons.c revision 163858
1/*- 2 * Copyright (c) 1988 University of Utah. 3 * Copyright (c) 1991 The Regents of the University of California. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * the Systems Programming Group of the University of Utah Computer 8 * Science Department. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 4. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * from: @(#)cons.c 7.2 (Berkeley) 5/9/91 35 */ 36 37#include <sys/cdefs.h> 38__FBSDID("$FreeBSD: head/sys/kern/tty_cons.c 163858 2006-11-01 04:54:51Z jb $"); 39 40#include "opt_ddb.h" 41 42#include <sys/param.h> 43#include <sys/systm.h> 44#include <sys/lock.h> 45#include <sys/mutex.h> 46#include <sys/conf.h> 47#include <sys/cons.h> 48#include <sys/fcntl.h> 49#include <sys/kdb.h> 50#include <sys/kernel.h> 51#include <sys/malloc.h> 52#include <sys/msgbuf.h> 53#include <sys/namei.h> 54#include <sys/proc.h> 55#include <sys/queue.h> 56#include <sys/reboot.h> 57#include <sys/sysctl.h> 58#include <sys/tty.h> 59#include <sys/uio.h> 60#include <sys/vnode.h> 61 62#include <ddb/ddb.h> 63 64#include <machine/cpu.h> 65 66static d_open_t cnopen; 67static d_close_t cnclose; 68static d_read_t cnread; 69static d_write_t cnwrite; 70static d_ioctl_t cnioctl; 71static d_poll_t cnpoll; 72static d_kqfilter_t cnkqfilter; 73 74static struct cdevsw cn_cdevsw = { 75 .d_version = D_VERSION, 76 .d_open = cnopen, 77 .d_close = cnclose, 78 .d_read = cnread, 79 .d_write = cnwrite, 80 .d_ioctl = cnioctl, 81 .d_poll = cnpoll, 82 .d_name = "console", 83 .d_flags = D_TTY | D_NEEDGIANT, 84 .d_kqfilter = cnkqfilter, 85}; 86 87struct cn_device { 88 STAILQ_ENTRY(cn_device) cnd_next; 89 struct vnode *cnd_vp; 90 struct consdev *cnd_cn; 91}; 92 93#define CNDEVPATHMAX 32 94#define CNDEVTAB_SIZE 4 95static struct cn_device cn_devtab[CNDEVTAB_SIZE]; 96static STAILQ_HEAD(, cn_device) cn_devlist = 97 STAILQ_HEAD_INITIALIZER(cn_devlist); 98 99#define CND_INVALID(cnd, td) \ 100 (cnd == NULL || cnd->cnd_vp == NULL || \ 101 (cnd->cnd_vp->v_type == VBAD && !cn_devopen(cnd, td, 1))) 102 103static dev_t cn_udev_t; 104SYSCTL_OPAQUE(_machdep, OID_AUTO, consdev, CTLFLAG_RD, 105 &cn_udev_t, sizeof cn_udev_t, "T,struct cdev *", ""); 106 107int cons_avail_mask = 0; /* Bit mask. Each registered low level console 108 * which is currently unavailable for inpit 109 * (i.e., if it is in graphics mode) will have 110 * this bit cleared. 111 */ 112static int cn_mute; 113static int openflag; /* how /dev/console was opened */ 114static int cn_is_open; 115static char *consbuf; /* buffer used by `consmsgbuf' */ 116static struct callout conscallout; /* callout for outputting to constty */ 117struct msgbuf consmsgbuf; /* message buffer for console tty */ 118static u_char console_pausing; /* pause after each line during probe */ 119static char *console_pausestr= 120"<pause; press any key to proceed to next line or '.' to end pause mode>"; 121struct tty *constty; /* pointer to console "window" tty */ 122static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ 123static int use_cnputs_mtx = 0; /* != 0 if cnputs_mtx locking reqd. */ 124 125static void constty_timeout(void *arg); 126 127static struct consdev cons_consdev; 128DATA_SET(cons_set, cons_consdev); 129SET_DECLARE(cons_set, struct consdev); 130 131void 132cninit(void) 133{ 134 struct consdev *best_cn, *cn, **list; 135 136 /* 137 * Check if we should mute the console (for security reasons perhaps) 138 * It can be changes dynamically using sysctl kern.consmute 139 * once we are up and going. 140 * 141 */ 142 cn_mute = ((boothowto & (RB_MUTE 143 |RB_SINGLE 144 |RB_VERBOSE 145 |RB_ASKNAME)) == RB_MUTE); 146 147 /* 148 * Find the first console with the highest priority. 149 */ 150 best_cn = NULL; 151 SET_FOREACH(list, cons_set) { 152 cn = *list; 153 cnremove(cn); 154 if (cn->cn_probe == NULL) 155 continue; 156 cn->cn_probe(cn); 157 if (cn->cn_pri == CN_DEAD) 158 continue; 159 if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) 160 best_cn = cn; 161 if (boothowto & RB_MULTIPLE) { 162 /* 163 * Initialize console, and attach to it. 164 */ 165 cnadd(cn); 166 cn->cn_init(cn); 167 } 168 } 169 if (best_cn == NULL) 170 return; 171 if ((boothowto & RB_MULTIPLE) == 0) { 172 cnadd(best_cn); 173 best_cn->cn_init(best_cn); 174 } 175 if (boothowto & RB_PAUSE) 176 console_pausing = 1; 177 /* 178 * Make the best console the preferred console. 179 */ 180 cnselect(best_cn); 181} 182 183void 184cninit_finish() 185{ 186 console_pausing = 0; 187} 188 189/* add a new physical console to back the virtual console */ 190int 191cnadd(struct consdev *cn) 192{ 193 struct cn_device *cnd; 194 int i; 195 196 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 197 if (cnd->cnd_cn == cn) 198 return (0); 199 for (i = 0; i < CNDEVTAB_SIZE; i++) { 200 cnd = &cn_devtab[i]; 201 if (cnd->cnd_cn == NULL) 202 break; 203 } 204 if (cnd->cnd_cn != NULL) 205 return (ENOMEM); 206 cnd->cnd_cn = cn; 207 if (cn->cn_name[0] == '\0') { 208 /* XXX: it is unclear if/where this print might output */ 209 printf("WARNING: console at %p has no name\n", cn); 210 } 211 STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); 212 213 /* Add device to the active mask. */ 214 cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); 215 216 return (0); 217} 218 219void 220cnremove(struct consdev *cn) 221{ 222 struct cn_device *cnd; 223 int i; 224 225 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 226 if (cnd->cnd_cn != cn) 227 continue; 228 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); 229 if (cnd->cnd_vp != NULL) 230 vn_close(cnd->cnd_vp, openflag, NOCRED, NULL); 231 cnd->cnd_vp = NULL; 232 cnd->cnd_cn = NULL; 233 234 /* Remove this device from available mask. */ 235 for (i = 0; i < CNDEVTAB_SIZE; i++) 236 if (cnd == &cn_devtab[i]) { 237 cons_avail_mask &= ~(1 << i); 238 break; 239 } 240#if 0 241 /* 242 * XXX 243 * syscons gets really confused if console resources are 244 * freed after the system has initialized. 245 */ 246 if (cn->cn_term != NULL) 247 cn->cn_term(cn); 248#endif 249 return; 250 } 251} 252 253void 254cnselect(struct consdev *cn) 255{ 256 struct cn_device *cnd; 257 258 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 259 if (cnd->cnd_cn != cn) 260 continue; 261 if (cnd == STAILQ_FIRST(&cn_devlist)) 262 return; 263 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); 264 STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); 265 return; 266 } 267} 268 269void 270cnavailable(struct consdev *cn, int available) 271{ 272 int i; 273 274 for (i = 0; i < CNDEVTAB_SIZE; i++) { 275 if (cn_devtab[i].cnd_cn == cn) 276 break; 277 } 278 if (available) { 279 if (i < CNDEVTAB_SIZE) 280 cons_avail_mask |= (1 << i); 281 cn->cn_flags &= ~CN_FLAG_NOAVAIL; 282 } else { 283 if (i < CNDEVTAB_SIZE) 284 cons_avail_mask &= ~(1 << i); 285 cn->cn_flags |= CN_FLAG_NOAVAIL; 286 } 287} 288 289int 290cnunavailable(void) 291{ 292 293 return (cons_avail_mask == 0); 294} 295 296/* 297 * XXX: rewrite to use sbufs instead 298 */ 299 300static int 301sysctl_kern_console(SYSCTL_HANDLER_ARGS) 302{ 303 struct cn_device *cnd; 304 struct consdev *cp, **list; 305 char *name, *p; 306 int delete, len, error; 307 308 len = 2; 309 SET_FOREACH(list, cons_set) { 310 cp = *list; 311 if (cp->cn_name[0] != '\0') 312 len += strlen(cp->cn_name) + 1; 313 } 314 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 315 len += strlen(cnd->cnd_cn->cn_name) + 1; 316 len = len > CNDEVPATHMAX ? len : CNDEVPATHMAX; 317 MALLOC(name, char *, len, M_TEMP, M_WAITOK | M_ZERO); 318 p = name; 319 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 320 p += sprintf(p, "%s,", cnd->cnd_cn->cn_name); 321 *p++ = '/'; 322 SET_FOREACH(list, cons_set) { 323 cp = *list; 324 if (cp->cn_name[0] != '\0') 325 p += sprintf(p, "%s,", cp->cn_name); 326 } 327 error = sysctl_handle_string(oidp, name, len, req); 328 if (error == 0 && req->newptr != NULL) { 329 p = name; 330 error = ENXIO; 331 delete = 0; 332 if (*p == '-') { 333 delete = 1; 334 p++; 335 } 336 SET_FOREACH(list, cons_set) { 337 cp = *list; 338 if (strcmp(p, cp->cn_name) != 0) 339 continue; 340 if (delete) { 341 cnremove(cp); 342 error = 0; 343 } else { 344 error = cnadd(cp); 345 if (error == 0) 346 cnselect(cp); 347 } 348 break; 349 } 350 } 351 FREE(name, M_TEMP); 352 return (error); 353} 354 355SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW, 356 0, 0, sysctl_kern_console, "A", "Console device control"); 357 358/* 359 * User has changed the state of the console muting. 360 * This may require us to open or close the device in question. 361 */ 362static int 363sysctl_kern_consmute(SYSCTL_HANDLER_ARGS) 364{ 365 int error; 366 int ocn_mute; 367 368 ocn_mute = cn_mute; 369 error = sysctl_handle_int(oidp, &cn_mute, 0, req); 370 if (error != 0 || req->newptr == NULL) 371 return (error); 372 if (ocn_mute && !cn_mute && cn_is_open) 373 error = cnopen(NULL, openflag, 0, curthread); 374 else if (!ocn_mute && cn_mute && cn_is_open) { 375 error = cnclose(NULL, openflag, 0, curthread); 376 cn_is_open = 1; /* XXX hack */ 377 } 378 return (error); 379} 380 381SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW, 382 0, sizeof(cn_mute), sysctl_kern_consmute, "I", ""); 383 384static int 385cn_devopen(struct cn_device *cnd, struct thread *td, int forceopen) 386{ 387 char path[CNDEVPATHMAX]; 388 struct nameidata nd; 389 struct vnode *vp; 390 struct cdev *dev; 391 struct cdevsw *csw; 392 int error; 393 394 if ((vp = cnd->cnd_vp) != NULL) { 395 if (!forceopen && vp->v_type != VBAD) { 396 dev = vp->v_rdev; 397 csw = dev_refthread(dev); 398 if (csw == NULL) 399 return (ENXIO); 400 error = (*csw->d_open)(dev, openflag, 0, td); 401 dev_relthread(dev); 402 return (error); 403 } 404 cnd->cnd_vp = NULL; 405 vn_close(vp, openflag, td->td_ucred, td); 406 } 407 snprintf(path, sizeof(path), "/dev/%s", cnd->cnd_cn->cn_name); 408 NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, path, td); 409 error = vn_open(&nd, &openflag, 0, -1); 410 if (error == 0) { 411 NDFREE(&nd, NDF_ONLY_PNBUF); 412 VOP_UNLOCK(nd.ni_vp, 0, td); 413 if (nd.ni_vp->v_type == VCHR) 414 cnd->cnd_vp = nd.ni_vp; 415 else 416 vn_close(nd.ni_vp, openflag, td->td_ucred, td); 417 } 418 return (cnd->cnd_vp != NULL); 419} 420 421static int 422cnopen(struct cdev *dev, int flag, int mode, struct thread *td) 423{ 424 struct cn_device *cnd; 425 426 openflag = flag | FWRITE; /* XXX */ 427 cn_is_open = 1; /* console is logically open */ 428 if (cn_mute) 429 return (0); 430 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) 431 cn_devopen(cnd, td, 0); 432 return (0); 433} 434 435static int 436cnclose(struct cdev *dev, int flag, int mode, struct thread *td) 437{ 438 struct cn_device *cnd; 439 struct vnode *vp; 440 441 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 442 if ((vp = cnd->cnd_vp) == NULL) 443 continue; 444 cnd->cnd_vp = NULL; 445 vn_close(vp, openflag, td->td_ucred, td); 446 } 447 cn_is_open = 0; 448 return (0); 449} 450 451static int 452cnread(struct cdev *dev, struct uio *uio, int flag) 453{ 454 struct cn_device *cnd; 455 struct cdevsw *csw; 456 int error; 457 458 cnd = STAILQ_FIRST(&cn_devlist); 459 if (cn_mute || CND_INVALID(cnd, curthread)) 460 return (0); 461 dev = cnd->cnd_vp->v_rdev; 462 csw = dev_refthread(dev); 463 if (csw == NULL) 464 return (ENXIO); 465 error = (csw->d_read)(dev, uio, flag); 466 dev_relthread(dev); 467 return (error); 468} 469 470static int 471cnwrite(struct cdev *dev, struct uio *uio, int flag) 472{ 473 struct cn_device *cnd; 474 struct cdevsw *csw; 475 int error; 476 477 cnd = STAILQ_FIRST(&cn_devlist); 478 if (cn_mute || CND_INVALID(cnd, curthread)) 479 goto done; 480 if (constty) 481 dev = constty->t_dev; 482 else 483 dev = cnd->cnd_vp->v_rdev; 484 if (dev != NULL) { 485 log_console(uio); 486 csw = dev_refthread(dev); 487 if (csw == NULL) 488 return (ENXIO); 489 error = (csw->d_write)(dev, uio, flag); 490 dev_relthread(dev); 491 return (error); 492 } 493done: 494 uio->uio_resid = 0; /* dump the data */ 495 return (0); 496} 497 498static int 499cnioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) 500{ 501 struct cn_device *cnd; 502 struct cdevsw *csw; 503 int error; 504 505 cnd = STAILQ_FIRST(&cn_devlist); 506 if (cn_mute || CND_INVALID(cnd, td)) 507 return (0); 508 /* 509 * Superuser can always use this to wrest control of console 510 * output from the "virtual" console. 511 */ 512 if (cmd == TIOCCONS && constty) { 513 error = suser(td); 514 if (error) 515 return (error); 516 constty = NULL; 517 return (0); 518 } 519 dev = cnd->cnd_vp->v_rdev; 520 if (dev == NULL) 521 return (0); /* XXX : ENOTTY ? */ 522 csw = dev_refthread(dev); 523 if (csw == NULL) 524 return (ENXIO); 525 error = (csw->d_ioctl)(dev, cmd, data, flag, td); 526 dev_relthread(dev); 527 return (error); 528} 529 530/* 531 * XXX 532 * poll/kqfilter do not appear to be correct 533 */ 534static int 535cnpoll(struct cdev *dev, int events, struct thread *td) 536{ 537 struct cn_device *cnd; 538 struct cdevsw *csw; 539 int error; 540 541 cnd = STAILQ_FIRST(&cn_devlist); 542 if (cn_mute || CND_INVALID(cnd, td)) 543 return (0); 544 dev = cnd->cnd_vp->v_rdev; 545 if (dev == NULL) 546 return (0); 547 csw = dev_refthread(dev); 548 if (csw == NULL) 549 return (ENXIO); 550 error = (csw->d_poll)(dev, events, td); 551 dev_relthread(dev); 552 return (error); 553} 554 555static int 556cnkqfilter(struct cdev *dev, struct knote *kn) 557{ 558 struct cn_device *cnd; 559 struct cdevsw *csw; 560 int error; 561 562 cnd = STAILQ_FIRST(&cn_devlist); 563 if (cn_mute || CND_INVALID(cnd, curthread)) 564 return (EINVAL); 565 dev = cnd->cnd_vp->v_rdev; 566 if (dev == NULL) 567 return (ENXIO); 568 csw = dev_refthread(dev); 569 if (csw == NULL) 570 return (ENXIO); 571 error = (csw->d_kqfilter)(dev, kn); 572 dev_relthread(dev); 573 return (error); 574} 575 576/* 577 * Low level console routines. 578 */ 579int 580cngetc(void) 581{ 582 int c; 583 584 if (cn_mute) 585 return (-1); 586 while ((c = cncheckc()) == -1) 587 ; 588 if (c == '\r') 589 c = '\n'; /* console input is always ICRNL */ 590 return (c); 591} 592 593int 594cncheckc(void) 595{ 596 struct cn_device *cnd; 597 struct consdev *cn; 598 int c; 599 600 if (cn_mute) 601 return (-1); 602 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 603 cn = cnd->cnd_cn; 604 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { 605 if (cn->cn_checkc != NULL) 606 c = cn->cn_checkc(cn); 607 else 608 c = cn->cn_getc(cn); 609 if (c != -1) { 610 return (c); 611 } 612 } 613 } 614 return (-1); 615} 616 617void 618cnputc(int c) 619{ 620 struct cn_device *cnd; 621 struct consdev *cn; 622 char *cp; 623 624 if (cn_mute || c == '\0') 625 return; 626 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { 627 cn = cnd->cnd_cn; 628 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { 629 if (c == '\n') 630 cn->cn_putc(cn, '\r'); 631 cn->cn_putc(cn, c); 632 } 633 } 634 if (console_pausing && c == '\n' && !kdb_active) { 635 for (cp = console_pausestr; *cp != '\0'; cp++) 636 cnputc(*cp); 637 if (cngetc() == '.') 638 console_pausing = 0; 639 cnputc('\r'); 640 for (cp = console_pausestr; *cp != '\0'; cp++) 641 cnputc(' '); 642 cnputc('\r'); 643 } 644} 645 646void 647cnputs(char *p) 648{ 649 int c; 650 int unlock_reqd = 0; 651 652 if (use_cnputs_mtx) { 653 mtx_lock_spin(&cnputs_mtx); 654 unlock_reqd = 1; 655 } 656 657 while ((c = *p++) != '\0') 658 cnputc(c); 659 660 if (unlock_reqd) 661 mtx_unlock_spin(&cnputs_mtx); 662} 663 664static int consmsgbuf_size = 8192; 665SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0, 666 ""); 667 668/* 669 * Redirect console output to a tty. 670 */ 671void 672constty_set(struct tty *tp) 673{ 674 int size; 675 676 KASSERT(tp != NULL, ("constty_set: NULL tp")); 677 if (consbuf == NULL) { 678 size = consmsgbuf_size; 679 consbuf = malloc(size, M_TTYS, M_WAITOK); 680 msgbuf_init(&consmsgbuf, consbuf, size); 681 callout_init(&conscallout, 0); 682 } 683 constty = tp; 684 constty_timeout(NULL); 685} 686 687/* 688 * Disable console redirection to a tty. 689 */ 690void 691constty_clear(void) 692{ 693 int c; 694 695 constty = NULL; 696 if (consbuf == NULL) 697 return; 698 callout_stop(&conscallout); 699 while ((c = msgbuf_getchar(&consmsgbuf)) != -1) 700 cnputc(c); 701 free(consbuf, M_TTYS); 702 consbuf = NULL; 703} 704 705/* Times per second to check for pending console tty messages. */ 706static int constty_wakeups_per_second = 5; 707SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, 708 &constty_wakeups_per_second, 0, ""); 709 710static void 711constty_timeout(void *arg) 712{ 713 int c; 714 715 while (constty != NULL && (c = msgbuf_getchar(&consmsgbuf)) != -1) { 716 if (tputchar(c, constty) < 0) 717 constty = NULL; 718 } 719 if (constty != NULL) { 720 callout_reset(&conscallout, hz / constty_wakeups_per_second, 721 constty_timeout, NULL); 722 } else { 723 /* Deallocate the constty buffer memory. */ 724 constty_clear(); 725 } 726} 727 728static void 729cn_drvinit(void *unused) 730{ 731 732 make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "console"); 733 734 mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS); 735 use_cnputs_mtx = 1; 736} 737 738SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL) 739