1#! /bin/sh 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2010 Gordon Tetlow 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions 10# are met: 11# 1. Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# 17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27# SUCH DAMAGE. 28# 29 30# Rendering a manual page is fast. Even a manual page several 100k in size 31# takes less than a CPU second. If it takes much longer, it is very likely 32# that a tool like mandoc(1) is running in an infinite loop. In this case 33# it is better to terminate it. 34ulimit -t 20 35 36# Do not ignore the exit codes of roff tools, as they may indicate a 37# problem with the page being rendered. Note that this also causes a 38# nonzero exit when the user quits reading before reaching the end, so 39# we need to look out for and deal with that specific case. 40set -o pipefail 41 42# Usage: add_to_manpath path 43# Adds a variable to manpath while ensuring we don't have duplicates. 44# Returns true if we were able to add something. False otherwise. 45add_to_manpath() { 46 case "$manpath" in 47 *:$1) decho " Skipping duplicate manpath entry $1" 2 ;; 48 $1:*) decho " Skipping duplicate manpath entry $1" 2 ;; 49 *:$1:*) decho " Skipping duplicate manpath entry $1" 2 ;; 50 *) if [ -d "$1" ]; then 51 decho " Adding $1 to manpath" 52 manpath="$manpath:$1" 53 return 0 54 fi 55 ;; 56 esac 57 58 return 1 59} 60 61# Usage: build_manlocales 62# Builds a correct MANLOCALES variable. 63build_manlocales() { 64 # If the user has set manlocales, who are we to argue. 65 if [ -n "$MANLOCALES" ]; then 66 return 67 fi 68 69 parse_configs 70 71 # Trim leading colon 72 MANLOCALES=${manlocales#:} 73 74 decho "Available manual locales: $MANLOCALES" 75} 76 77# Usage: build_mansect 78# Builds a correct MANSECT variable. 79build_mansect() { 80 # If the user has set mansect, who are we to argue. 81 if [ -n "$MANSECT" ]; then 82 return 83 fi 84 85 parse_configs 86 87 # Trim leading colon 88 MANSECT=${mansect#:} 89 90 if [ -z "$MANSECT" ]; then 91 MANSECT=$man_default_sections 92 fi 93 decho "Using manual sections: $MANSECT" 94} 95 96# Usage: build_manpath 97# Builds a correct MANPATH variable. 98build_manpath() { 99 local IFS 100 101 # If the user has set a manpath, who are we to argue. 102 if [ -n "$MANPATH" ]; then 103 case "$MANPATH" in 104 *:) PREPEND_MANPATH=${MANPATH} ;; 105 :*) APPEND_MANPATH=${MANPATH} ;; 106 *::*) 107 PREPEND_MANPATH=${MANPATH%%::*} 108 APPEND_MANPATH=${MANPATH#*::} 109 ;; 110 *) return ;; 111 esac 112 fi 113 114 if [ -n "$PREPEND_MANPATH" ]; then 115 IFS=: 116 for path in $PREPEND_MANPATH; do 117 add_to_manpath "$path" 118 done 119 unset IFS 120 fi 121 122 search_path 123 124 decho "Adding default manpath entries" 125 IFS=: 126 for path in $man_default_path; do 127 add_to_manpath "$path" 128 done 129 unset IFS 130 131 parse_configs 132 133 if [ -n "$APPEND_MANPATH" ]; then 134 IFS=: 135 for path in $APPEND_MANPATH; do 136 add_to_manpath "$path" 137 done 138 unset IFS 139 fi 140 # Trim leading colon 141 MANPATH=${manpath#:} 142 143 decho "Using manual path: $MANPATH" 144} 145 146# Usage: check_cat catglob 147# Checks to see if a cat glob is available. 148check_cat() { 149 if exists "$1"; then 150 use_cat=yes 151 catpage=$found 152 setup_cattool "$catpage" 153 decho " Found catpage \"$catpage\"" 154 return 0 155 else 156 return 1 157 fi 158} 159 160# Usage: check_man manglob catglob 161# Given 2 globs, figures out if the manglob is available, if so, check to 162# see if the catglob is also available and up to date. 163check_man() { 164 if exists "$1"; then 165 # We have a match, check for a cat page 166 manpage=$found 167 setup_cattool "$manpage" 168 decho " Found manpage \"$manpage\"" 169 170 if [ -n "${use_width}" ]; then 171 # non-standard width 172 unset use_cat 173 decho " Skipping catpage: non-standard page width" 174 elif exists "$2" && is_newer $found "$manpage"; then 175 # cat page found and is newer, use that 176 use_cat=yes 177 catpage=$found 178 setup_cattool "$catpage" 179 decho " Using catpage \"$catpage\"" 180 else 181 # no cat page or is older 182 unset use_cat 183 decho " Skipping catpage: not found or old" 184 fi 185 return 0 186 fi 187 188 return 1 189} 190 191# Usage: decho "string" [debuglevel] 192# Echoes to stderr string prefaced with -- if high enough debuglevel. 193decho() { 194 if [ $debug -ge ${2:-1} ]; then 195 echo "-- $1" >&2 196 fi 197} 198 199# Usage: exists glob 200# 201# Returns true if glob resolves to a real file and store the first 202# found filename in the variable $found 203exists() { 204 if [ -z "$1" ]; then 205 return 1 206 fi 207 208 local IFS 209 210 # Don't accidentally inherit callers IFS (breaks perl manpages) 211 unset IFS 212 213 # Use some globbing tricks in the shell to determine if a file 214 # exists or not. 215 set +f 216 for file in "$1"* 217 do 218 if [ -r "$file" ]; then 219 found="$file" 220 set -f 221 return 0 222 fi 223 done 224 set -f 225 226 return 1 227} 228 229# Usage: find_file path section subdir pagename 230# Returns: true if something is matched and found. 231# Search the given path/section combo for a given page. 232find_file() { 233 local manroot catroot mann man0 catn cat0 234 235 manroot="$1/man$2" 236 catroot="$1/cat$2" 237 if [ -n "$3" ]; then 238 manroot="$manroot/$3" 239 catroot="$catroot/$3" 240 fi 241 242 if [ ! -d "$manroot" -a ! -d "$catroot" ]; then 243 return 1 244 fi 245 decho " Searching directory $manroot" 2 246 247 mann="$manroot/$4.$2" 248 man0="$manroot/$4.0" 249 catn="$catroot/$4.$2" 250 cat0="$catroot/$4.0" 251 252 # This is the behavior as seen by the original man utility. 253 # Let's not change that which doesn't seem broken. 254 if check_man "$mann" "$catn"; then 255 return 0 256 elif check_man "$man0" "$cat0"; then 257 return 0 258 elif check_cat "$catn"; then 259 return 0 260 elif check_cat "$cat0"; then 261 return 0 262 fi 263 264 return 1 265} 266 267# Usage: is_newer file1 file2 268# Returns true if file1 is newer than file2 as calculated by mtime. 269is_newer() { 270 if ! [ "$1" -ot "$2" ]; then 271 decho " mtime: $1 not older than $2" 3 272 return 0 273 else 274 decho " mtime: $1 older than $2" 3 275 return 1 276 fi 277} 278 279# Usage: manpath_parse_args "$@" 280# Parses commandline options for manpath. 281manpath_parse_args() { 282 local cmd_arg 283 284 OPTIND=1 285 while getopts 'Ldq' cmd_arg; do 286 case "${cmd_arg}" in 287 L) Lflag=Lflag ;; 288 d) debug=$(( $debug + 1 )) ;; 289 q) qflag=qflag ;; 290 *) manpath_usage ;; 291 esac 292 done >&2 293} 294 295# Usage: manpath_usage 296# Display usage for the manpath(1) utility. 297manpath_usage() { 298 echo 'usage: manpath [-Ldq]' >&2 299 exit 1 300} 301 302# Usage: manpath_warnings 303# Display some warnings to stderr. 304manpath_warnings() { 305 if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then 306 echo "(Warning: MANLOCALES environment variable set)" >&2 307 fi 308} 309 310# Usage: man_check_for_so path 311# Returns: True if able to resolve the file, false if it ended in tears. 312# Detects the presence of the .so directive and causes the file to be 313# redirected to another source file. 314man_check_for_so() { 315 local IFS line tstr 316 local counter_so=0 317 local counter_so_limit=32 318 319 unset IFS 320 if [ -n "$catpage" ]; then 321 return 0 322 fi 323 324 # We need to loop to accommodate multiple .so directives. 325 while [ $counter_so -lt $counter_so_limit ] 326 do 327 counter_so=$((counter_so + 1)) 328 329 line=$($cattool "$manpage" 2>/dev/null | grep -E -m1 -v '^\.\\"[ ]*|^[ ]*$') 330 case "$line" in 331 '.so /'*) break ;; # ignore absolute path 332 '.so '*) trim "${line#.so}" 333 decho "$manpage includes $tstri level=$counter_so" 334 335 # Glob and check for the file. 336 if ! check_man "$1/$tstr" ""; then 337 decho " Unable to find $tstr" 338 return 1 339 fi 340 ;; 341 *) break ;; 342 esac 343 done 344 345 if [ $counter_so -ge $counter_so_limit ]; then 346 decho ".so include limit of $counter_so_limit reached, stop" 347 fi 348 349 return 0 350} 351 352# Usage: man_display_page 353# Display either the manpage or catpage depending on the use_cat variable 354man_display_page() { 355 local IFS pipeline testline 356 357 # We are called with IFS set to colon. This causes really weird 358 # things to happen for the variables that have spaces in them. 359 unset IFS 360 361 # If we are supposed to use a catpage and we aren't using troff(1) 362 # just zcat the catpage and we are done. 363 if [ -z "$tflag" -a -n "$use_cat" ]; then 364 if [ -n "$wflag" ]; then 365 echo "$catpage (source: \"$manpage\")" 366 ret=0 367 else 368 if [ $debug -gt 0 ]; then 369 decho "Command: $cattool \"$catpage\" | $MANPAGER" 370 ret=0 371 else 372 $cattool "$catpage" | $MANPAGER 373 ret=$? 374 fi 375 fi 376 return 377 fi 378 379 # Okay, we are using the manpage, do we just need to output the 380 # name of the manpage? 381 if [ -n "$wflag" ]; then 382 echo "$manpage" 383 ret=0 384 return 385 fi 386 387 if [ -n "$use_width" ]; then 388 mandoc_args="-O width=${use_width}" 389 fi 390 testline="mandoc -Tlint -Wunsupp >/dev/null 2>&1" 391 if [ -n "$tflag" ]; then 392 pipeline="mandoc -Tps $mandoc_args" 393 else 394 pipeline="mandoc $mandoc_args | $MANPAGER" 395 fi 396 397 if ! $cattool "$manpage" | eval "$testline"; then 398 if which -s groff; then 399 man_display_page_groff 400 else 401 echo "This manpage needs groff(1) to be rendered" >&2 402 echo "First install groff(1): " >&2 403 echo "pkg install groff " >&2 404 ret=1 405 fi 406 return 407 fi 408 409 if [ $debug -gt 0 ]; then 410 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" 411 ret=0 412 else 413 $cattool "$manpage" | eval "$pipeline" 414 ret=$? 415 fi 416} 417 418# Usage: man_display_page_groff 419# Display the manpage using groff 420man_display_page_groff() { 421 local EQN NROFF PIC TBL TROFF REFER VGRIND 422 local IFS l nroff_dev pipeline preproc_arg tool 423 424 # So, we really do need to parse the manpage. First, figure out the 425 # device flag (-T) we have to pass to eqn(1) and groff(1). Then, 426 # setup the pipeline of commands based on the user's request. 427 428 # If the manpage is from a particular charset, we need to setup nroff 429 # to properly output for the correct device. 430 case "${manpage}" in 431 *.${man_charset}/*) 432 # I don't pretend to know this; I'm just copying from the 433 # previous version of man(1). 434 case "$man_charset" in 435 KOI8-R) nroff_dev="koi8-r" ;; 436 ISO8859-1) nroff_dev="latin1" ;; 437 ISO8859-15) nroff_dev="latin1" ;; 438 UTF-8) nroff_dev="utf8" ;; 439 *) nroff_dev="ascii" ;; 440 esac 441 442 NROFF="$NROFF -T$nroff_dev" 443 EQN="$EQN -T$nroff_dev" 444 445 # Iff the manpage is from the locale and not just the charset, 446 # then we need to define the locale string. 447 case "${manpage}" in 448 */${man_lang}_${man_country}.${man_charset}/*) 449 NROFF="$NROFF -dlocale=$man_lang.$man_charset" 450 ;; 451 */${man_lang}.${man_charset}/*) 452 NROFF="$NROFF -dlocale=$man_lang.$man_charset" 453 ;; 454 esac 455 456 # Allow language specific calls to override the default 457 # set of utilities. 458 l=$(echo $man_lang | tr [:lower:] [:upper:]) 459 for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do 460 eval "$tool=\${${tool}_$l:-\$$tool}" 461 done 462 ;; 463 *) NROFF="$NROFF -Tascii" 464 EQN="$EQN -Tascii" 465 ;; 466 esac 467 468 if [ -z "$MANCOLOR" ]; then 469 NROFF="$NROFF -P-c" 470 fi 471 472 if [ -n "${use_width}" ]; then 473 NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n" 474 fi 475 476 if [ -n "$MANROFFSEQ" ]; then 477 set -- -$MANROFFSEQ 478 OPTIND=1 479 while getopts 'egprtv' preproc_arg; do 480 case "${preproc_arg}" in 481 e) pipeline="$pipeline | $EQN" ;; 482 g) ;; # Ignore for compatibility. 483 p) pipeline="$pipeline | $PIC" ;; 484 r) pipeline="$pipeline | $REFER" ;; 485 t) pipeline="$pipeline | $TBL" ;; 486 v) pipeline="$pipeline | $VGRIND" ;; 487 *) usage ;; 488 esac 489 done 490 # Strip the leading " | " from the resulting pipeline. 491 pipeline="${pipeline#" | "}" 492 else 493 pipeline="$TBL" 494 fi 495 496 if [ -n "$tflag" ]; then 497 pipeline="$pipeline | $TROFF" 498 else 499 pipeline="$pipeline | $NROFF | $MANPAGER" 500 fi 501 502 if [ $debug -gt 0 ]; then 503 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" 504 ret=0 505 else 506 $cattool "$manpage" | eval "$pipeline" 507 ret=$? 508 fi 509} 510 511# Usage: man_find_and_display page 512# Search through the manpaths looking for the given page. 513man_find_and_display() { 514 local found_page has_slash locpath p path sect 515 516 # Check to see if it's a file. But only if it has a '/' in 517 # the filename or if -l was specified. 518 case "$1" in 519 */*) has_slash=yes 520 ;; 521 esac 522 if [ -n "$has_slash" -o -n "$lflag" ]; then 523 if [ -f "$1" -a -r "$1" ]; then 524 decho "Found a usable page, displaying that" 525 if [ -z "$lflag" ]; then 526 echo "Opening a file directly is deprecated," \ 527 "use -l instead." >&2 528 fi 529 unset use_cat 530 manpage="$1" 531 setup_cattool "$manpage" 532 p=$(cd "$(dirname "$manpage")" && pwd) 533 case "$(basename "$p")" in 534 man*|cat*) p=$p/.. ;; 535 *) p=$p/../.. ;; 536 esac 537 if man_check_for_so "$p"; then 538 found_page=yes 539 man_display_page 540 fi 541 return 542 elif [ -n "$lflag" ]; then 543 echo "Cannot read $1" >&2 544 ret=1 545 return 546 fi 547 fi 548 549 IFS=: 550 for sect in $MANSECT; do 551 decho "Searching section $sect" 2 552 for path in $MANPATH; do 553 for locpath in $locpaths; do 554 p=$path/$locpath 555 p=${p%/.} # Rid ourselves of the trailing /. 556 557 # Check if there is a MACHINE specific manpath. 558 if find_file $p $sect $MACHINE "$1"; then 559 if man_check_for_so $p; then 560 found_page=yes 561 man_display_page 562 if [ -n "$aflag" ]; then 563 continue 2 564 else 565 return 566 fi 567 fi 568 fi 569 570 # Check if there is a MACHINE_ARCH 571 # specific manpath. 572 if find_file $p $sect $MACHINE_ARCH "$1"; then 573 if man_check_for_so $p; then 574 found_page=yes 575 man_display_page 576 if [ -n "$aflag" ]; then 577 continue 2 578 else 579 return 580 fi 581 fi 582 fi 583 584 # Check plain old manpath. 585 if find_file $p $sect '' "$1"; then 586 if man_check_for_so $p; then 587 found_page=yes 588 man_display_page 589 if [ -n "$aflag" ]; then 590 continue 2 591 else 592 return 593 fi 594 fi 595 fi 596 done 597 done 598 done 599 unset IFS 600 601 # Nothing? Well, we are done then. 602 if [ -z "$found_page" ]; then 603 echo "No manual entry for \"$1\"" >&2 604 ret=1 605 return 606 fi 607} 608 609# Usage: man_parse_opts "$@" 610# Parses commandline options for man. 611man_parse_opts() { 612 local cmd_arg 613 614 OPTIND=1 615 while getopts 'K:M:P:S:adfhklm:op:tw' cmd_arg; do 616 case "${cmd_arg}" in 617 K) Kflag=Kflag 618 REGEXP=$OPTARG ;; 619 M) MANPATH=$OPTARG ;; 620 P) MANPAGER=$OPTARG ;; 621 S) MANSECT=$OPTARG ;; 622 a) aflag=aflag ;; 623 d) debug=$(( $debug + 1 )) ;; 624 f) fflag=fflag ;; 625 h) man_usage 0 ;; 626 k) kflag=kflag ;; 627 l) lflag=lflag ;; 628 m) mflag=$OPTARG ;; 629 o) oflag=oflag ;; 630 p) MANROFFSEQ=$OPTARG ;; 631 t) tflag=tflag ;; 632 w) wflag=wflag ;; 633 *) man_usage ;; 634 esac 635 done >&2 636 637 shift $(( $OPTIND - 1 )) 638 639 # Check the args for incompatible options. 640 case "${Kflag}${fflag}${kflag}${lflag}${tflag}${wflag}" in 641 Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;; 642 Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;; 643 Kflag*lflag*) echo "Incompatible options: -K and -l"; man_usage ;; 644 Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;; 645 fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;; 646 fflag*lflag*) echo "Incompatible options: -f and -l"; man_usage ;; 647 fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;; 648 fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;; 649 *kflaglflag*) echo "Incompatible options: -k and -l"; man_usage ;; 650 *kflag*tflag*) echo "Incompatible options: -k and -t"; man_usage ;; 651 *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;; 652 *lflag*wflag) echo "Incompatible options: -l and -w"; man_usage ;; 653 *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;; 654 esac 655 656 # Short circuit for whatis(1) and apropos(1) 657 if [ -n "$fflag" ]; then 658 do_whatis "$@" 659 exit 660 fi 661 662 if [ -n "$kflag" ]; then 663 do_apropos "$@" 664 exit 665 fi 666} 667 668# Usage: man_setup 669# Setup various trivial but essential variables. 670man_setup() { 671 # Setup machine and architecture variables. 672 if [ -n "$mflag" ]; then 673 MACHINE_ARCH=${mflag%%:*} 674 MACHINE=${mflag##*:} 675 fi 676 if [ -z "$MACHINE_ARCH" ]; then 677 MACHINE_ARCH=$($SYSCTL -n hw.machine_arch) 678 fi 679 if [ -z "$MACHINE" ]; then 680 MACHINE=$($SYSCTL -n hw.machine) 681 fi 682 decho "Using architecture: $MACHINE_ARCH:$MACHINE" 683 684 setup_pager 685 build_manpath 686 build_mansect 687 man_setup_locale 688 man_setup_width 689} 690 691# Usage: man_setup_width 692# Set up page width. 693man_setup_width() { 694 local sizes 695 696 unset use_width 697 case "$MANWIDTH" in 698 [0-9]*) 699 if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then 700 use_width=$MANWIDTH 701 fi 702 ;; 703 [Tt][Tt][Yy]) 704 if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then 705 set -- $sizes 706 if [ $2 -gt 80 ]; then 707 use_width=$(($2-2)) 708 fi 709 fi 710 ;; 711 esac 712 if [ -n "$use_width" ]; then 713 decho "Using non-standard page width: ${use_width}" 714 else 715 decho 'Using standard page width' 716 fi 717} 718 719# Usage: man_setup_locale 720# Setup necessary locale variables. 721man_setup_locale() { 722 local lang_cc 723 local locstr 724 725 locpaths='.' 726 man_charset='US-ASCII' 727 728 # Setup locale information. 729 if [ -n "$oflag" ]; then 730 decho 'Using non-localized manpages' 731 else 732 # Use the locale tool to give us proper locale information 733 eval $( $LOCALE ) 734 735 if [ -n "$LANG" ]; then 736 locstr=$LANG 737 else 738 locstr=$LC_CTYPE 739 fi 740 741 case "$locstr" in 742 C) ;; 743 C.UTF-8) ;; 744 POSIX) ;; 745 [a-z][a-z]_[A-Z][A-Z]\.*) 746 lang_cc="${locstr%.*}" 747 man_lang="${locstr%_*}" 748 man_country="${lang_cc#*_}" 749 man_charset="${locstr#*.}" 750 locpaths="$locstr" 751 locpaths="$locpaths:$man_lang.$man_charset" 752 if [ "$man_lang" != "en" ]; then 753 locpaths="$locpaths:en.$man_charset" 754 fi 755 locpaths="$locpaths:." 756 ;; 757 *) echo 'Unknown locale, assuming C' >&2 758 ;; 759 esac 760 fi 761 762 decho "Using locale paths: $locpaths" 763} 764 765# Usage: man_usage [exitcode] 766# Display usage for the man utility. 767man_usage() { 768 echo 'Usage:' 769 echo ' man [-adhlo] [-t | -w] [-M manpath] [-P pager] [-S mansect]' 770 echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]' 771 echo ' man -K | -f | -k expression [...] -- Search manual pages' 772 773 # When exit'ing with -h, it's not an error. 774 exit ${1:-1} 775} 776 777# Usage: parse_configs 778# Reads the end-user adjustable config files. 779parse_configs() { 780 local IFS file files 781 782 if [ -n "$parsed_configs" ]; then 783 return 784 fi 785 786 unset IFS 787 788 # Read the global config first in case the user wants 789 # to override config_local. 790 if [ -r "$config_global" ]; then 791 parse_file "$config_global" 792 fi 793 794 # Glob the list of files to parse. 795 set +f 796 files=$(echo $config_local) 797 set -f 798 799 for file in $files; do 800 if [ -r "$file" ]; then 801 parse_file "$file" 802 fi 803 done 804 805 parsed_configs='yes' 806} 807 808# Usage: parse_file file 809# Reads the specified config files. 810parse_file() { 811 local file line tstr var 812 813 file="$1" 814 decho "Parsing config file: $file" 815 while read line; do 816 decho " $line" 2 817 case "$line" in 818 \#*) decho " Comment" 3 819 ;; 820 MANPATH*) decho " MANPATH" 3 821 trim "${line#MANPATH}" 822 add_to_manpath "$tstr" 823 ;; 824 MANLOCALE*) decho " MANLOCALE" 3 825 trim "${line#MANLOCALE}" 826 manlocales="$manlocales:$tstr" 827 ;; 828 MANCONFIG*) decho " MANCONFIG" 3 829 trim "${line#MANCONFIG}" 830 config_local="$tstr" 831 ;; 832 MANSECT*) decho " MANSECT" 3 833 trim "${line#MANSECT}" 834 mansect="$mansect:$tstr" 835 ;; 836 # Set variables in the form of FOO_BAR 837 *_*[\ \ ]*) var="${line%%[\ \ ]*}" 838 trim "${line#$var}" 839 eval "$var=\"$tstr\"" 840 decho " Parsed $var" 3 841 ;; 842 esac 843 done < "$file" 844} 845 846# Usage: search_path 847# Traverse $PATH looking for manpaths. 848search_path() { 849 local IFS p path 850 851 decho "Searching PATH for man directories" 852 853 IFS=: 854 for path in $PATH; do 855 if add_to_manpath "$path/man"; then 856 : 857 elif add_to_manpath "$path/MAN"; then 858 : 859 else 860 case "$path" in 861 */bin) p="${path%/bin}/share/man" 862 add_to_manpath "$p" 863 p="${path%/bin}/man" 864 add_to_manpath "$p" 865 ;; 866 esac 867 fi 868 done 869 unset IFS 870 871 if [ -z "$manpath" ]; then 872 decho ' Unable to find any manpaths, using default' 873 manpath=$man_default_path 874 fi 875} 876 877# Usage: search_whatis cmd [arglist] 878# Do the heavy lifting for apropos/whatis 879search_whatis() { 880 local IFS bad cmd f good key keywords loc opt out path rval wlist 881 882 cmd="$1" 883 shift 884 885 whatis_parse_args "$@" 886 887 build_manpath 888 build_manlocales 889 setup_pager 890 891 if [ "$cmd" = "whatis" ]; then 892 opt="-w" 893 fi 894 895 f='whatis' 896 897 IFS=: 898 for path in $MANPATH; do 899 if [ \! -d "$path" ]; then 900 decho "Skipping non-existent path: $path" 2 901 continue 902 fi 903 904 if [ -f "$path/$f" -a -r "$path/$f" ]; then 905 decho "Found whatis: $path/$f" 906 wlist="$wlist $path/$f" 907 fi 908 909 for loc in $MANLOCALES; do 910 if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then 911 decho "Found whatis: $path/$loc/$f" 912 wlist="$wlist $path/$loc/$f" 913 fi 914 done 915 done 916 unset IFS 917 918 if [ -z "$wlist" ]; then 919 echo "$cmd: no whatis databases in $MANPATH" >&2 920 exit 1 921 fi 922 923 rval=0 924 for key in $keywords; do 925 out=$(grep -Ehi $opt -- "$key" $wlist) 926 if [ -n "$out" ]; then 927 good="$good\\n$out" 928 else 929 bad="$bad\\n$key: nothing appropriate" 930 rval=1 931 fi 932 done 933 934 # Strip leading carriage return. 935 good=${good#\\n} 936 bad=${bad#\\n} 937 938 if [ -n "$good" ]; then 939 printf '%b\n' "$good" | $MANPAGER 940 fi 941 942 if [ -n "$bad" ]; then 943 printf '%b\n' "$bad" >&2 944 fi 945 946 exit $rval 947} 948 949# Usage: setup_cattool page 950# Finds an appropriate decompressor based on extension 951setup_cattool() { 952 case "$1" in 953 *.bz) cattool='/usr/bin/bzcat' ;; 954 *.bz2) cattool='/usr/bin/bzcat' ;; 955 *.gz) cattool='/usr/bin/gzcat' ;; 956 *.lzma) cattool='/usr/bin/lzcat' ;; 957 *.xz) cattool='/usr/bin/xzcat' ;; 958 *.zst) cattool='/usr/bin/zstdcat' ;; 959 *) cattool='/usr/bin/zcat -f' ;; 960 esac 961} 962 963# Usage: setup_pager 964# Correctly sets $MANPAGER 965setup_pager() { 966 # Setup pager. 967 if [ -z "$MANPAGER" ]; then 968 if [ -n "$MANCOLOR" ]; then 969 MANPAGER="less -sR" 970 else 971 if [ -n "$PAGER" ]; then 972 MANPAGER="$PAGER" 973 else 974 MANPAGER="less -s" 975 fi 976 fi 977 fi 978 decho "Using pager: $MANPAGER" 979} 980 981# Usage: trim string 982# Trims whitespace from beginning and end of a variable 983trim() { 984 tstr=$1 985 while true; do 986 case "$tstr" in 987 [\ \ ]*) tstr="${tstr##[\ \ ]}" ;; 988 *[\ \ ]) tstr="${tstr%%[\ \ ]}" ;; 989 *) break ;; 990 esac 991 done 992} 993 994# Usage: whatis_parse_args "$@" 995# Parse commandline args for whatis and apropos. 996whatis_parse_args() { 997 local cmd_arg 998 OPTIND=1 999 while getopts 'd' cmd_arg; do 1000 case "${cmd_arg}" in 1001 d) debug=$(( $debug + 1 )) ;; 1002 *) whatis_usage ;; 1003 esac 1004 done >&2 1005 1006 shift $(( $OPTIND - 1 )) 1007 1008 keywords="$*" 1009} 1010 1011# Usage: whatis_usage 1012# Display usage for the whatis/apropos utility. 1013whatis_usage() { 1014 echo "usage: $cmd [-d] keyword [...]" 1015 exit 1 1016} 1017 1018 1019 1020# Supported commands 1021do_apropos() { 1022 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \ 1023 exec apropos "$@" 1024 search_whatis apropos "$@" 1025} 1026 1027# Usage: do_full_search reg_exp 1028# Do a full search of the regular expression passed 1029# as parameter in all man pages 1030do_full_search() { 1031 local gflags re 1032 re=${1} 1033 1034 # Build grep(1) flags 1035 gflags="-H" 1036 1037 # wflag implies -l for grep(1) 1038 if [ -n "$wflag" ]; then 1039 gflags="${gflags} -l" 1040 fi 1041 1042 gflags="${gflags} --label" 1043 1044 set +f 1045 for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do 1046 for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do 1047 for manfile in ${mpath}/man${section}/*.${section}*; do 1048 mandoc "${manfile}" 2>/dev/null | 1049 grep -E ${gflags} "${manfile}" -e "${re}" 1050 done 1051 done 1052 done 1053 set -f 1054} 1055 1056do_man() { 1057 local IFS 1058 1059 man_parse_opts "$@" 1060 man_setup 1061 1062 shift $(( $OPTIND - 1 )) 1063 IFS=: 1064 for sect in $MANSECT; do 1065 if [ "$sect" = "$1" ]; then 1066 decho "Detected manual section as first arg: $1" 1067 MANSECT="$1" 1068 shift 1069 break 1070 fi 1071 done 1072 unset IFS 1073 pages="$*" 1074 1075 if [ -z "$pages" -a -z "${Kflag}" ]; then 1076 echo 'What manual page do you want?' >&2 1077 exit 1 1078 fi 1079 1080 if [ ! -z "${Kflag}" ]; then 1081 # Short circuit because -K flag does a sufficiently 1082 # different thing like not showing the man page at all 1083 do_full_search "${REGEXP}" 1084 fi 1085 1086 for page in "$@"; do 1087 decho "Searching for \"$page\"" 1088 man_find_and_display "$page" 1089 done 1090 1091 # The user will very commonly quit reading the page before 1092 # reaching the bottom. Depending on the length of the page 1093 # and the pager's buffer size, this may result in a SIGPIPE. 1094 # This is normal, so convert that exit code to zero. 1095 if [ ${ret:-0} -gt 128 ]; then 1096 if [ "$(kill -l "${ret}")" = "PIPE" ]; then 1097 ret=0 1098 fi 1099 fi 1100 1101 exit ${ret:-0} 1102} 1103 1104do_manpath() { 1105 manpath_parse_args "$@" 1106 if [ -z "$qflag" ]; then 1107 manpath_warnings 1108 fi 1109 if [ -n "$Lflag" ]; then 1110 build_manlocales 1111 echo $MANLOCALES 1112 else 1113 build_manpath 1114 echo $MANPATH 1115 fi 1116 exit 0 1117} 1118 1119do_whatis() { 1120 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \ 1121 exec whatis "$@" 1122 search_whatis whatis "$@" 1123} 1124 1125# User's PATH setting decides on the groff-suite to pick up. 1126EQN=eqn 1127NROFF='groff -S -P-h -Wall -mtty-char -mandoc' 1128PIC=pic 1129REFER=refer 1130TBL=tbl 1131TROFF='groff -S -mandoc' 1132VGRIND=vgrind 1133 1134LOCALE=/usr/bin/locale 1135STTY=/bin/stty 1136SYSCTL=/sbin/sysctl 1137 1138debug=0 1139man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l' 1140man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man' 1141cattool='/usr/bin/zcat -f' 1142 1143config_global='/etc/man.conf' 1144 1145# This can be overridden via a setting in /etc/man.conf. 1146config_local='/usr/local/etc/man.d/*.conf' 1147 1148# Set noglobbing for now. I don't want spurious globbing. 1149set -f 1150 1151case "$0" in 1152*apropos) do_apropos "$@" ;; 1153*manpath) do_manpath "$@" ;; 1154*whatis) do_whatis "$@" ;; 1155*) do_man "$@" ;; 1156esac 1157