1/* 2 * tickadj - read, and possibly modify, the kernel `tick' and 3 * `tickadj' variables, as well as `dosynctodr'. Note that 4 * this operates on the running kernel only. I'd like to be 5 * able to read and write the binary as well, but haven't 6 * mastered this yet. 7 * 8 * HMS: The #includes here are different from those in xntpd/ntp_unixclock.c 9 * These seem "worse". 10 */ 11 12#ifdef HAVE_CONFIG_H 13# include <config.h> 14#endif 15 16#include "ntp_types.h" 17#include "l_stdlib.h" 18 19#include <stdio.h> 20#ifdef HAVE_UNISTD_H 21# include <unistd.h> 22#endif /* HAVE_UNISTD_H */ 23 24#ifdef HAVE___ADJTIMEX /* Linux */ 25 26#include <sys/timex.h> 27struct timex txc; 28 29#if 0 30int 31main( 32 int argc, 33 char *argv[] 34 ) 35{ 36 int c, i; 37 int quiet = 0; 38 int errflg = 0; 39 char *progname; 40 extern int ntp_optind; 41 extern char *ntp_optarg; 42 43 progname = argv[0]; 44 if (argc==2 && argv[1][0] != '-') { /* old Linux format, for compatability */ 45 if ((i = atoi(argv[1])) > 0) { 46 txc.time_tick = i; 47 txc.modes = ADJ_TIMETICK; 48 } else { 49 fprintf(stderr, "Silly value for tick: %s\n", argv[1]); 50 errflg++; 51 } 52 } else { 53 while ((c = ntp_getopt(argc, argv, "a:qt:")) != EOF) { 54 switch (c) { 55 case 'a': 56 if ((i=atoi(ntp_optarg)) > 0) { 57 txc.tickadj = i; 58 txc.modes |= ADJ_TICKADJ; 59 } else { 60 (void) fprintf(stderr, 61 "%s: unlikely value for tickadj: %s\n", 62 progname, ntp_optarg); 63 errflg++; 64 } 65 break; 66 67 case 'q': 68 quiet = 1; 69 break; 70 71 case 't': 72 if ((i=atoi(ntp_optarg)) > 0) { 73 txc.time_tick = i; 74 txc.modes |= ADJ_TIMETICK; 75 } else { 76 (void) fprintf(stderr, 77 "%s: unlikely value for tick: %s\n", 78 progname, ntp_optarg); 79 errflg++; 80 } 81 break; 82 83 default: 84 fprintf(stderr, 85 "Usage: %s [tick_value]\n-or- %s [ -q ] [ -t tick ] [ -a tickadj ]\n", 86 progname, progname); 87 errflg++; 88 break; 89 } 90 } 91 } 92 93 if (!errflg) { 94 if (__adjtimex(&txc) < 0) 95 perror("adjtimex"); 96 else if (!quiet) 97 printf("tick = %ld\ntick_adj = %d\n", 98 txc.time_tick, txc.tickadj); 99 } 100 101 exit(errflg ? 1 : 0); 102} 103#else 104int 105main( 106 int argc, 107 char *argv[] 108 ) 109{ 110 if (argc > 2) 111 { 112 fprintf(stderr, "Usage: %s [tick_value]\n", argv[0]); 113 exit(-1); 114 } 115 else if (argc == 2) 116 { 117#ifdef ADJ_TIMETICK 118 if ( (txc.time_tick = atoi(argv[1])) < 1 ) 119#else 120 if ( (txc.tick = atoi(argv[1])) < 1 ) 121#endif 122 { 123 fprintf(stderr, "Silly value for tick: %s\n", argv[1]); 124 exit(-1); 125 } 126#ifdef ADJ_TIMETICK 127 txc.modes = ADJ_TIMETICK; 128#else 129#ifdef MOD_OFFSET 130 txc.modes = ADJ_TICK; 131#else 132 txc.mode = ADJ_TICK; 133#endif 134#endif 135 } 136 else 137 { 138#ifdef ADJ_TIMETICK 139 txc.modes = 0; 140#else 141#ifdef MOD_OFFSET 142 txc.modes = 0; 143#else 144 txc.mode = 0; 145#endif 146#endif 147 } 148 149 if (__adjtimex(&txc) < 0) 150 { 151 perror("adjtimex"); 152 } 153 else 154 { 155#ifdef ADJ_TIMETICK 156 printf("tick = %ld\ntick_adj = %ld\n", txc.time_tick, txc.tickadj); 157#else 158 printf("tick = %ld\n", txc.tick); 159#endif 160 } 161 162 exit(0); 163} 164#endif 165 166#else /* not Linux... kmem tweaking: */ 167 168#ifdef HAVE_SYS_FILE_H 169# include <sys/file.h> 170#endif 171#include <sys/stat.h> 172 173#ifdef HAVE_SYS_PARAM_H 174# include <sys/param.h> 175#endif 176 177#ifdef NLIST_STRUCT 178# include <nlist.h> 179#else /* not NLIST_STRUCT */ /* was defined(SYS_AUX3) || defined(SYS_AUX2) */ 180# include <sys/resource.h> 181# include <sys/file.h> 182# include <a.out.h> 183# include <sys/var.h> 184#endif 185 186#include "ntp_stdlib.h" 187#include "ntp_io.h" 188 189#ifdef hz /* Was: RS6000 */ 190# undef hz 191#endif /* hz */ 192 193#ifdef HAVE_KVM_OPEN 194# include <kvm.h> 195#endif 196 197#ifdef SYS_VXWORKS 198/* vxWorks needs mode flag -casey*/ 199#define open(name, flags) open(name, flags, 0777) 200#endif 201 202#ifndef L_SET /* Was: defined(SYS_PTX) || defined(SYS_IX86OSF1) */ 203# define L_SET SEEK_SET 204#endif 205 206#ifndef HZ 207# define HZ DEFAULT_HZ 208#endif 209 210#define KMEM "/dev/kmem" 211#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) 212 213char *progname; 214volatile int debug; 215 216int dokmem = 1; 217int writetickadj = 0; 218int writeopttickadj = 0; 219int unsetdosync = 0; 220int writetick = 0; 221int quiet = 0; 222int setnoprintf = 0; 223 224const char *kmem = KMEM; 225const char *file = NULL; 226int fd = -1; 227 228static void getoffsets (off_t *, off_t *, off_t *, off_t *); 229static int openfile (const char *, int); 230static void writevar (int, off_t, int); 231static void readvar (int, off_t, int *); 232 233/* 234 * main - parse arguments and handle options 235 */ 236int 237main( 238 int argc, 239 char *argv[] 240 ) 241{ 242 int c; 243 int errflg = 0; 244 off_t tickadj_offset; 245 off_t tick_offset; 246 off_t dosync_offset; 247 off_t noprintf_offset; 248 int tickadj, ktickadj; /* HMS: Why isn't this u_long? */ 249 int tick, ktick; /* HMS: Why isn't this u_long? */ 250 int dosynctodr; 251 int noprintf; 252 int hz; 253 int hz_int, hz_hundredths; 254 int recommend_tickadj; 255 long tmp; 256 257 progname = argv[0]; 258 while ((c = ntp_getopt(argc, argv, "a:Adkpqst:")) != EOF) 259 { 260 switch (c) 261 { 262 case 'a': 263 writetickadj = atoi(ntp_optarg); 264 if (writetickadj <= 0) 265 { 266 (void) fprintf(stderr, 267 "%s: unlikely value for tickadj: %s\n", 268 progname, ntp_optarg); 269 errflg++; 270 } 271 272#if defined SCO5_CLOCK 273 if (writetickadj % HZ) 274 { 275 writetickadj = (writetickadj / HZ) * HZ; 276 (void) fprintf(stderr, 277 "tickadj truncated to: %d\n", writetickadj); 278 } 279#endif /* SCO5_CLOCK */ 280 281 break; 282 case 'A': 283 writeopttickadj = 1; 284 break; 285 case 'd': 286 ++debug; 287 break; 288 case 'k': 289 dokmem = 1; 290 break; 291 case 'p': 292 setnoprintf = 1; 293 break; 294 case 'q': 295 quiet = 1; 296 break; 297 case 's': 298 unsetdosync = 1; 299 break; 300 case 't': 301 writetick = atoi(ntp_optarg); 302 if (writetick <= 0) 303 { 304 (void) fprintf(stderr, 305 "%s: unlikely value for tick: %s\n", 306 progname, ntp_optarg); 307 errflg++; 308 } 309 break; 310 default: 311 errflg++; 312 break; 313 } 314 } 315 if (errflg || ntp_optind != argc) 316 { 317 (void) fprintf(stderr, 318 "usage: %s [-Adkpqs] [-a newadj] [-t newtick]\n", progname); 319 exit(2); 320 } 321 322 getoffsets(&tick_offset, &tickadj_offset, &dosync_offset, &noprintf_offset); 323 324 if (debug) 325 { 326 (void) printf("tick offset = %lu\n", (unsigned long)tick_offset); 327 (void) printf("tickadj offset = %lu\n", (unsigned long)tickadj_offset); 328 (void) printf("dosynctodr offset = %lu\n", (unsigned long)dosync_offset); 329 (void) printf("noprintf offset = %lu\n", (unsigned long)noprintf_offset); 330 } 331 332 if (writetick && (tick_offset == 0)) 333 { 334 (void) fprintf(stderr, 335 "No tick kernel variable\n"); 336 errflg++; 337 } 338 339 if (writeopttickadj && (tickadj_offset == 0)) 340 { 341 (void) fprintf(stderr, 342 "No tickadj kernel variable\n"); 343 errflg++; 344 } 345 346 if (unsetdosync && (dosync_offset == 0)) 347 { 348 (void) fprintf(stderr, 349 "No dosynctodr kernel variable\n"); 350 errflg++; 351 } 352 353 if (setnoprintf && (noprintf_offset == 0)) 354 { 355 (void) fprintf(stderr, 356 "No noprintf kernel variable\n"); 357 errflg++; 358 } 359 360 if (tick_offset != 0) 361 { 362 readvar(fd, tick_offset, &tick); 363#if defined(TICK_NANO) && defined(K_TICK_NAME) 364 if (!quiet) 365 (void) printf("KERNEL %s = %d nsec\n", K_TICK_NAME, tick); 366#endif /* TICK_NANO && K_TICK_NAME */ 367 368#ifdef TICK_NANO 369 tick /= 1000; 370#endif 371 } 372 else 373 { 374 tick = 0; 375 } 376 377 if (tickadj_offset != 0) 378 { 379 readvar(fd, tickadj_offset, &tickadj); 380 381#ifdef SCO5_CLOCK 382 /* scale from nsec/sec to usec/tick */ 383 tickadj /= (1000L * HZ); 384#endif /*SCO5_CLOCK */ 385 386#if defined(TICKADJ_NANO) && defined(K_TICKADJ_NAME) 387 if (!quiet) 388 (void) printf("KERNEL %s = %d nsec\n", K_TICKADJ_NAME, tickadj); 389#endif /* TICKADJ_NANO && K_TICKADJ_NAME */ 390 391#ifdef TICKADJ_NANO 392 tickadj += 999; 393 tickadj /= 1000; 394#endif 395 } 396 else 397 { 398 tickadj = 0; 399 } 400 401 if (dosync_offset != 0) 402 { 403 readvar(fd, dosync_offset, &dosynctodr); 404 } 405 406 if (noprintf_offset != 0) 407 { 408 readvar(fd, noprintf_offset, &noprintf); 409 } 410 411 (void) close(fd); 412 413 if (unsetdosync && dosync_offset == 0) 414 { 415 (void) fprintf(stderr, 416 "%s: can't find %s in namelist\n", 417 progname, 418#ifdef K_DOSYNCTODR_NAME 419 K_DOSYNCTODR_NAME 420#else /* not K_DOSYNCTODR_NAME */ 421 "dosynctodr" 422#endif /* not K_DOSYNCTODR_NAME */ 423 ); 424 exit(1); 425 } 426 427 hz = HZ; 428#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) 429 hz = (int) sysconf (_SC_CLK_TCK); 430#endif /* not HAVE_SYSCONF && _SC_CLK_TCK */ 431#ifdef OVERRIDE_HZ 432 hz = DEFAULT_HZ; 433#endif 434 ktick = tick; 435#ifdef PRESET_TICK 436 tick = PRESET_TICK; 437#endif /* PRESET_TICK */ 438#ifdef TICKADJ_NANO 439 tickadj /= 1000; 440 if (tickadj == 0) 441 tickadj = 1; 442#endif 443 ktickadj = tickadj; 444#ifdef PRESET_TICKADJ 445 tickadj = (PRESET_TICKADJ) ? PRESET_TICKADJ : 1; 446#endif /* PRESET_TICKADJ */ 447 448 if (!quiet) 449 { 450 if (tick_offset != 0) 451 { 452 (void) printf("KERNEL tick = %d usec (from %s kernel variable)\n", 453 ktick, 454#ifdef K_TICK_NAME 455 K_TICK_NAME 456#else 457 "<this can't happen>" 458#endif 459 ); 460 } 461#ifdef PRESET_TICK 462 (void) printf("PRESET tick = %d usec\n", tick); 463#endif /* PRESET_TICK */ 464 if (tickadj_offset != 0) 465 { 466 (void) printf("KERNEL tickadj = %d usec (from %s kernel variable)\n", 467 ktickadj, 468#ifdef K_TICKADJ_NAME 469 K_TICKADJ_NAME 470#else 471 "<this can't happen>" 472#endif 473 ); 474 } 475#ifdef PRESET_TICKADJ 476 (void) printf("PRESET tickadj = %d usec\n", tickadj); 477#endif /* PRESET_TICKADJ */ 478 if (dosync_offset != 0) 479 { 480 (void) printf("dosynctodr is %s\n", dosynctodr ? "on" : "off"); 481 } 482 if (noprintf_offset != 0) 483 { 484 (void) printf("kernel level printf's: %s\n", 485 noprintf ? "off" : "on"); 486 } 487 } 488 489 if (tick <= 0) 490 { 491 (void) fprintf(stderr, "%s: the value of tick is silly!\n", 492 progname); 493 exit(1); 494 } 495 496 hz_int = (int)(1000000L / (long)tick); 497 hz_hundredths = (int)((100000000L / (long)tick) - ((long)hz_int * 100L)); 498 if (!quiet) 499 { 500 (void) printf("KERNEL hz = %d\n", hz); 501 (void) printf("calculated hz = %d.%02d Hz\n", hz_int, 502 hz_hundredths); 503 } 504 505#if defined SCO5_CLOCK 506 recommend_tickadj = 100; 507#else /* SCO5_CLOCK */ 508 tmp = (long) tick * 500L; 509 recommend_tickadj = (int)(tmp / 1000000L); 510 if (tmp % 1000000L > 0) 511 { 512 recommend_tickadj++; 513 } 514 515#ifdef MIN_REC_TICKADJ 516 if (recommend_tickadj < MIN_REC_TICKADJ) 517 { 518 recommend_tickadj = MIN_REC_TICKADJ; 519 } 520#endif /* MIN_REC_TICKADJ */ 521#endif /* SCO5_CLOCK */ 522 523 524 if ((!quiet) && (tickadj_offset != 0)) 525 { 526 (void) printf("recommended value of tickadj = %d us\n", 527 recommend_tickadj); 528 } 529 530 if ( writetickadj == 0 531 && !writeopttickadj 532 && !unsetdosync 533 && writetick == 0 534 && !setnoprintf) 535 { 536 exit(errflg ? 1 : 0); 537 } 538 539 if (writetickadj == 0 && writeopttickadj) 540 { 541 writetickadj = recommend_tickadj; 542 } 543 544 fd = openfile(file, O_WRONLY); 545 546 if (setnoprintf && (noprintf_offset != 0)) 547 { 548 if (!quiet) 549 { 550 (void) fprintf(stderr, "setting noprintf: "); 551 (void) fflush(stderr); 552 } 553 writevar(fd, noprintf_offset, 1); 554 if (!quiet) 555 { 556 (void) fprintf(stderr, "done!\n"); 557 } 558 } 559 560 if ((writetick > 0) && (tick_offset != 0)) 561 { 562 if (!quiet) 563 { 564 (void) fprintf(stderr, "writing tick, value %d: ", 565 writetick); 566 (void) fflush(stderr); 567 } 568 writevar(fd, tick_offset, writetick); 569 if (!quiet) 570 { 571 (void) fprintf(stderr, "done!\n"); 572 } 573 } 574 575 if ((writetickadj > 0) && (tickadj_offset != 0)) 576 { 577 if (!quiet) 578 { 579 (void) fprintf(stderr, "writing tickadj, value %d: ", 580 writetickadj); 581 (void) fflush(stderr); 582 } 583 584#ifdef SCO5_CLOCK 585 /* scale from usec/tick to nsec/sec */ 586 writetickadj *= (1000L * HZ); 587#endif /* SCO5_CLOCK */ 588 589 writevar(fd, tickadj_offset, writetickadj); 590 if (!quiet) 591 { 592 (void) fprintf(stderr, "done!\n"); 593 } 594 } 595 596 if (unsetdosync && (dosync_offset != 0)) 597 { 598 if (!quiet) 599 { 600 (void) fprintf(stderr, "zeroing dosynctodr: "); 601 (void) fflush(stderr); 602 } 603 writevar(fd, dosync_offset, 0); 604 if (!quiet) 605 { 606 (void) fprintf(stderr, "done!\n"); 607 } 608 } 609 (void) close(fd); 610 return(errflg ? 1 : 0); 611} 612 613/* 614 * getoffsets - read the magic offsets from the specified file 615 */ 616static void 617getoffsets( 618 off_t *tick_off, 619 off_t *tickadj_off, 620 off_t *dosync_off, 621 off_t *noprintf_off 622 ) 623{ 624 625#ifndef NOKMEM 626# ifndef HAVE_KVM_OPEN 627 const char **kname; 628# endif 629#endif 630 631#ifndef NOKMEM 632# ifdef NLIST_NAME_UNION 633# define NL_B {{ 634# define NL_E }} 635# else 636# define NL_B { 637# define NL_E } 638# endif 639#endif 640 641#define K_FILLER_NAME "DavidLetterman" 642 643#ifdef NLIST_EXTRA_INDIRECTION 644 int i; 645#endif 646 647#ifndef NOKMEM 648 static struct nlist nl[] = 649 { 650 NL_B 651#ifdef K_TICKADJ_NAME 652#define N_TICKADJ 0 653 K_TICKADJ_NAME 654#else 655 K_FILLER_NAME 656#endif 657 NL_E, 658 NL_B 659#ifdef K_TICK_NAME 660#define N_TICK 1 661 K_TICK_NAME 662#else 663 K_FILLER_NAME 664#endif 665 NL_E, 666 NL_B 667#ifdef K_DOSYNCTODR_NAME 668#define N_DOSYNC 2 669 K_DOSYNCTODR_NAME 670#else 671 K_FILLER_NAME 672#endif 673 NL_E, 674 NL_B 675#ifdef K_NOPRINTF_NAME 676#define N_NOPRINTF 3 677 K_NOPRINTF_NAME 678#else 679 K_FILLER_NAME 680#endif 681 NL_E, 682 NL_B "" NL_E, 683 }; 684 685#ifndef HAVE_KVM_OPEN 686 static const char *kernels[] = 687 { 688#ifdef HAVE_GETBOOTFILE 689 NULL, /* *** SEE BELOW! *** */ 690#endif 691 "/kernel/unix", 692 "/kernel", 693 "/vmunix", 694 "/unix", 695 "/mach", 696 "/hp-ux", 697 "/386bsd", 698 "/netbsd", 699 "/stand/vmunix", 700 "/bsd", 701 NULL 702 }; 703#endif /* not HAVE_KVM_OPEN */ 704 705#ifdef HAVE_KVM_OPEN 706 /* 707 * Solaris > 2.5 doesn't have a kernel file. Use the kvm_* interface 708 * to read the kernel name list. -- stolcke 3/4/96 709 */ 710 kvm_t *kvm_handle = kvm_open(NULL, NULL, NULL, O_RDONLY, progname); 711 712 if (kvm_handle == NULL) 713 { 714 (void) fprintf(stderr, 715 "%s: kvm_open failed\n", 716 progname); 717 exit(1); 718 } 719 if (kvm_nlist(kvm_handle, nl) == -1) 720 { 721 (void) fprintf(stderr, 722 "%s: kvm_nlist failed\n", 723 progname); 724 exit(1); 725 } 726 kvm_close(kvm_handle); 727#else /* not HAVE_KVM_OPEN */ 728#ifdef HAVE_GETBOOTFILE /* *** SEE HERE! *** */ 729 if (kernels[0] == NULL) 730 { 731 char * cp = (char *)getbootfile(); 732 733 if (cp) 734 { 735 kernels[0] = cp; 736 } 737 else 738 { 739 kernels[0] = "/Placeholder"; 740 } 741 } 742#endif /* HAVE_GETBOOTFILE */ 743 for (kname = kernels; *kname != NULL; kname++) 744 { 745 struct stat stbuf; 746 747 if (stat(*kname, &stbuf) == -1) 748 { 749 continue; 750 } 751 if (nlist(*kname, nl) >= 0) 752 { 753 break; 754 } 755 else 756 { 757 (void) fprintf(stderr, 758 "%s: nlist didn't find needed symbols from <%s>: %s\n", 759 progname, *kname, strerror(errno)); 760 } 761 } 762 if (*kname == NULL) 763 { 764 (void) fprintf(stderr, 765 "%s: Couldn't find the kernel\n", 766 progname); 767 exit(1); 768 } 769#endif /* HAVE_KVM_OPEN */ 770 771 if (dokmem) 772 { 773 file = kmem; 774 775 fd = openfile(file, O_RDONLY); 776#ifdef NLIST_EXTRA_INDIRECTION 777 /* 778 * Go one more round of indirection. 779 */ 780 for (i = 0; i < (sizeof(nl) / sizeof(struct nlist)); i++) 781 { 782 if ((nl[i].n_value) && (nl[i].n_sclass == 0x6b)) 783 { 784 readvar(fd, nl[i].n_value, &nl[i].n_value); 785 } 786 } 787#endif /* NLIST_EXTRA_INDIRECTION */ 788 } 789#endif /* not NOKMEM */ 790 791 *tickadj_off = 0; 792 *tick_off = 0; 793 *dosync_off = 0; 794 *noprintf_off = 0; 795 796#if defined(N_TICKADJ) 797 *tickadj_off = nl[N_TICKADJ].n_value; 798#endif 799 800#if defined(N_TICK) 801 *tick_off = nl[N_TICK].n_value; 802#endif 803 804#if defined(N_DOSYNC) 805 *dosync_off = nl[N_DOSYNC].n_value; 806#endif 807 808#if defined(N_NOPRINTF) 809 *noprintf_off = nl[N_NOPRINTF].n_value; 810#endif 811 return; 812} 813 814#undef N_TICKADJ 815#undef N_TICK 816#undef N_DOSYNC 817#undef N_NOPRINTF 818 819 820/* 821 * openfile - open the file, check for errors 822 */ 823static int 824openfile( 825 const char *name, 826 int mode 827 ) 828{ 829 int ifd; 830 831 ifd = open(name, mode); 832 if (ifd < 0) 833 { 834 (void) fprintf(stderr, "%s: open %s: ", progname, name); 835 perror(""); 836 exit(1); 837 } 838 return ifd; 839} 840 841 842/* 843 * writevar - write a variable into the file 844 */ 845static void 846writevar( 847 int ofd, 848 off_t off, 849 int var 850 ) 851{ 852 853 if (lseek(ofd, off, L_SET) == -1) 854 { 855 (void) fprintf(stderr, "%s: lseek fails: ", progname); 856 perror(""); 857 exit(1); 858 } 859 if (write(ofd, (char *)&var, sizeof(int)) != sizeof(int)) 860 { 861 (void) fprintf(stderr, "%s: write fails: ", progname); 862 perror(""); 863 exit(1); 864 } 865 return; 866} 867 868 869/* 870 * readvar - read a variable from the file 871 */ 872static void 873readvar( 874 int ifd, 875 off_t off, 876 int *var 877 ) 878{ 879 int i; 880 881 if (lseek(ifd, off, L_SET) == -1) 882 { 883 (void) fprintf(stderr, "%s: lseek fails: ", progname); 884 perror(""); 885 exit(1); 886 } 887 i = read(ifd, (char *)var, sizeof(int)); 888 if (i < 0) 889 { 890 (void) fprintf(stderr, "%s: read fails: ", progname); 891 perror(""); 892 exit(1); 893 } 894 if (i != sizeof(int)) 895 { 896 (void) fprintf(stderr, "%s: read expected %d, got %d\n", 897 progname, (int)sizeof(int), i); 898 exit(1); 899 } 900 return; 901} 902#endif /* not Linux */ 903