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 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; 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 *) usage ;; 487 esac 488 done 489 # Strip the leading " | " from the resulting pipeline. 490 pipeline="${pipeline#" | "}" 491 else 492 pipeline="$TBL" 493 fi 494 495 if [ -n "$tflag" ]; then 496 pipeline="$pipeline | $TROFF" 497 else 498 pipeline="$pipeline | $NROFF | $MANPAGER" 499 fi 500 501 if [ $debug -gt 0 ]; then 502 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" 503 ret=0 504 else 505 $cattool "$manpage" | eval "$pipeline" 506 ret=$? 507 fi 508} 509 510# Usage: man_find_and_display page 511# Search through the manpaths looking for the given page. 512man_find_and_display() { 513 local found_page has_slash locpath p path sect 514 515 # Check to see if it's a file. But only if it has a '/' in 516 # the filename or if -l was specified. 517 case "$1" in 518 */*) has_slash=yes 519 ;; 520 esac 521 if [ -n "$has_slash" -o -n "$lflag" ]; then 522 if [ -f "$1" -a -r "$1" ]; then 523 decho "Found a usable page, displaying that" 524 if [ -z "$lflag" ]; then 525 echo "Opening a file directly is deprecated," \ 526 "use -l instead." >&2 527 fi 528 unset use_cat 529 manpage="$1" 530 setup_cattool "$manpage" 531 p=$(cd "$(dirname "$manpage")" && pwd) 532 case "$(basename "$p")" in 533 man*|cat*) p=$p/.. ;; 534 *) p=$p/../.. ;; 535 esac 536 if man_check_for_so "$p"; then 537 found_page=yes 538 man_display_page 539 fi 540 return 541 elif [ -n "$lflag" ]; then 542 echo "Cannot read $1" >&2 543 ret=1 544 return 545 fi 546 fi 547 548 IFS=: 549 for sect in $MANSECT; do 550 decho "Searching section $sect" 2 551 for path in $MANPATH; do 552 for locpath in $locpaths; do 553 p=$path/$locpath 554 p=${p%/.} # Rid ourselves of the trailing /. 555 556 # Check if there is a MACHINE specific manpath. 557 if find_file $p $sect $MACHINE "$1"; then 558 if man_check_for_so $p; then 559 found_page=yes 560 man_display_page 561 if [ -n "$aflag" ]; then 562 continue 2 563 else 564 return 565 fi 566 fi 567 fi 568 569 # Check if there is a MACHINE_ARCH 570 # specific manpath. 571 if find_file $p $sect $MACHINE_ARCH "$1"; then 572 if man_check_for_so $p; then 573 found_page=yes 574 man_display_page 575 if [ -n "$aflag" ]; then 576 continue 2 577 else 578 return 579 fi 580 fi 581 fi 582 583 # Check plain old manpath. 584 if find_file $p $sect '' "$1"; then 585 if man_check_for_so $p; then 586 found_page=yes 587 man_display_page 588 if [ -n "$aflag" ]; then 589 continue 2 590 else 591 return 592 fi 593 fi 594 fi 595 done 596 done 597 done 598 unset IFS 599 600 # Nothing? Well, we are done then. 601 if [ -z "$found_page" ]; then 602 echo "No manual entry for \"$1\"" >&2 603 ret=1 604 return 605 fi 606} 607 608# Usage: man_parse_opts "$@" 609# Parses commandline options for man. 610man_parse_opts() { 611 local cmd_arg 612 613 OPTIND=1 614 while getopts 'K:M:P:S:adfhklm:op:tw' cmd_arg; do 615 case "${cmd_arg}" in 616 K) Kflag=Kflag 617 REGEXP=$OPTARG ;; 618 M) MANPATH=$OPTARG ;; 619 P) MANPAGER=$OPTARG ;; 620 S) MANSECT=$OPTARG ;; 621 a) aflag=aflag ;; 622 d) debug=$(( $debug + 1 )) ;; 623 f) fflag=fflag ;; 624 h) man_usage 0 ;; 625 k) kflag=kflag ;; 626 l) lflag=lflag ;; 627 m) mflag=$OPTARG ;; 628 o) oflag=oflag ;; 629 p) MANROFFSEQ=$OPTARG ;; 630 t) tflag=tflag ;; 631 w) wflag=wflag ;; 632 *) man_usage ;; 633 esac 634 done >&2 635 636 shift $(( $OPTIND - 1 )) 637 638 # Check the args for incompatible options. 639 case "${Kflag}${fflag}${kflag}${lflag}${tflag}${wflag}" in 640 Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;; 641 Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;; 642 Kflag*lflag*) echo "Incompatible options: -K and -l"; man_usage ;; 643 Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;; 644 fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;; 645 fflag*lflag*) echo "Incompatible options: -f and -l"; man_usage ;; 646 fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;; 647 fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;; 648 *kflaglflag*) echo "Incompatible options: -k and -l"; man_usage ;; 649 *kflag*tflag*) echo "Incompatible options: -k and -t"; man_usage ;; 650 *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;; 651 *lflag*wflag) echo "Incompatible options: -l and -w"; man_usage ;; 652 *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;; 653 esac 654 655 # Short circuit for whatis(1) and apropos(1) 656 if [ -n "$fflag" ]; then 657 do_whatis "$@" 658 exit 659 fi 660 661 if [ -n "$kflag" ]; then 662 do_apropos "$@" 663 exit 664 fi 665} 666 667# Usage: man_setup 668# Setup various trivial but essential variables. 669man_setup() { 670 # Setup machine and architecture variables. 671 if [ -n "$mflag" ]; then 672 MACHINE_ARCH=${mflag%%:*} 673 MACHINE=${mflag##*:} 674 fi 675 if [ -z "$MACHINE_ARCH" ]; then 676 MACHINE_ARCH=$($SYSCTL -n hw.machine_arch) 677 fi 678 if [ -z "$MACHINE" ]; then 679 MACHINE=$($SYSCTL -n hw.machine) 680 fi 681 decho "Using architecture: $MACHINE_ARCH:$MACHINE" 682 683 setup_pager 684 build_manpath 685 build_mansect 686 man_setup_locale 687 man_setup_width 688} 689 690# Usage: man_setup_width 691# Set up page width. 692man_setup_width() { 693 local sizes 694 695 unset use_width 696 case "$MANWIDTH" in 697 [0-9]*) 698 if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then 699 use_width=$MANWIDTH 700 fi 701 ;; 702 [Tt][Tt][Yy]) 703 if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then 704 set -- $sizes 705 if [ $2 -gt 80 ]; then 706 use_width=$(($2-2)) 707 fi 708 fi 709 ;; 710 esac 711 if [ -n "$use_width" ]; then 712 decho "Using non-standard page width: ${use_width}" 713 else 714 decho 'Using standard page width' 715 fi 716} 717 718# Usage: man_setup_locale 719# Setup necessary locale variables. 720man_setup_locale() { 721 local lang_cc 722 local locstr 723 724 locpaths='.' 725 man_charset='US-ASCII' 726 727 # Setup locale information. 728 if [ -n "$oflag" ]; then 729 decho 'Using non-localized manpages' 730 else 731 # Use the locale tool to give us proper locale information 732 eval $( $LOCALE ) 733 734 if [ -n "$LANG" ]; then 735 locstr=$LANG 736 else 737 locstr=$LC_CTYPE 738 fi 739 740 case "$locstr" in 741 C) ;; 742 C.UTF-8) ;; 743 POSIX) ;; 744 [a-z][a-z]_[A-Z][A-Z]\.*) 745 lang_cc="${locstr%.*}" 746 man_lang="${locstr%_*}" 747 man_country="${lang_cc#*_}" 748 man_charset="${locstr#*.}" 749 locpaths="$locstr" 750 locpaths="$locpaths:$man_lang.$man_charset" 751 if [ "$man_lang" != "en" ]; then 752 locpaths="$locpaths:en.$man_charset" 753 fi 754 locpaths="$locpaths:." 755 ;; 756 *) echo 'Unknown locale, assuming C' >&2 757 ;; 758 esac 759 fi 760 761 decho "Using locale paths: $locpaths" 762} 763 764# Usage: man_usage [exitcode] 765# Display usage for the man utility. 766man_usage() { 767 echo 'Usage:' 768 echo ' man [-adhlo] [-t | -w] [-M manpath] [-P pager] [-S mansect]' 769 echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]' 770 echo ' man -K | -f | -k expression [...] -- Search manual pages' 771 772 # When exit'ing with -h, it's not an error. 773 exit ${1:-1} 774} 775 776# Usage: parse_configs 777# Reads the end-user adjustable config files. 778parse_configs() { 779 local IFS file files 780 781 if [ -n "$parsed_configs" ]; then 782 return 783 fi 784 785 unset IFS 786 787 # Read the global config first in case the user wants 788 # to override config_local. 789 if [ -r "$config_global" ]; then 790 parse_file "$config_global" 791 fi 792 793 # Glob the list of files to parse. 794 set +f 795 files=$(echo $config_local) 796 set -f 797 798 for file in $files; do 799 if [ -r "$file" ]; then 800 parse_file "$file" 801 fi 802 done 803 804 parsed_configs='yes' 805} 806 807# Usage: parse_file file 808# Reads the specified config files. 809parse_file() { 810 local file line tstr var 811 812 file="$1" 813 decho "Parsing config file: $file" 814 while read line; do 815 decho " $line" 2 816 case "$line" in 817 \#*) decho " Comment" 3 818 ;; 819 MANPATH*) decho " MANPATH" 3 820 trim "${line#MANPATH}" 821 add_to_manpath "$tstr" 822 ;; 823 MANLOCALE*) decho " MANLOCALE" 3 824 trim "${line#MANLOCALE}" 825 manlocales="$manlocales:$tstr" 826 ;; 827 MANCONFIG*) decho " MANCONFIG" 3 828 trim "${line#MANCONFIG}" 829 config_local="$tstr" 830 ;; 831 MANSECT*) decho " MANSECT" 3 832 trim "${line#MANSECT}" 833 mansect="$mansect:$tstr" 834 ;; 835 # Set variables in the form of FOO_BAR 836 *_*[\ \ ]*) var="${line%%[\ \ ]*}" 837 trim "${line#$var}" 838 eval "$var=\"$tstr\"" 839 decho " Parsed $var" 3 840 ;; 841 esac 842 done < "$file" 843} 844 845# Usage: search_path 846# Traverse $PATH looking for manpaths. 847search_path() { 848 local IFS p path 849 850 decho "Searching PATH for man directories" 851 852 IFS=: 853 for path in $PATH; do 854 if add_to_manpath "$path/man"; then 855 : 856 elif add_to_manpath "$path/MAN"; then 857 : 858 else 859 case "$path" in 860 */bin) p="${path%/bin}/share/man" 861 add_to_manpath "$p" 862 p="${path%/bin}/man" 863 add_to_manpath "$p" 864 ;; 865 esac 866 fi 867 done 868 unset IFS 869 870 if [ -z "$manpath" ]; then 871 decho ' Unable to find any manpaths, using default' 872 manpath=$man_default_path 873 fi 874} 875 876# Usage: search_whatis cmd [arglist] 877# Do the heavy lifting for apropos/whatis 878search_whatis() { 879 local IFS bad cmd f good key keywords loc opt out path rval wlist 880 881 cmd="$1" 882 shift 883 884 whatis_parse_args "$@" 885 886 build_manpath 887 build_manlocales 888 setup_pager 889 890 if [ "$cmd" = "whatis" ]; then 891 opt="-w" 892 fi 893 894 f='whatis' 895 896 IFS=: 897 for path in $MANPATH; do 898 if [ \! -d "$path" ]; then 899 decho "Skipping non-existent path: $path" 2 900 continue 901 fi 902 903 if [ -f "$path/$f" -a -r "$path/$f" ]; then 904 decho "Found whatis: $path/$f" 905 wlist="$wlist $path/$f" 906 fi 907 908 for loc in $MANLOCALES; do 909 if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then 910 decho "Found whatis: $path/$loc/$f" 911 wlist="$wlist $path/$loc/$f" 912 fi 913 done 914 done 915 unset IFS 916 917 if [ -z "$wlist" ]; then 918 echo "$cmd: no whatis databases in $MANPATH" >&2 919 exit 1 920 fi 921 922 rval=0 923 for key in $keywords; do 924 out=$(grep -Ehi $opt -- "$key" $wlist) 925 if [ -n "$out" ]; then 926 good="$good\\n$out" 927 else 928 bad="$bad\\n$key: nothing appropriate" 929 rval=1 930 fi 931 done 932 933 # Strip leading carriage return. 934 good=${good#\\n} 935 bad=${bad#\\n} 936 937 if [ -n "$good" ]; then 938 printf '%b\n' "$good" | $MANPAGER 939 fi 940 941 if [ -n "$bad" ]; then 942 printf '%b\n' "$bad" >&2 943 fi 944 945 exit $rval 946} 947 948# Usage: setup_cattool page 949# Finds an appropriate decompressor based on extension 950setup_cattool() { 951 case "$1" in 952 *.bz) cattool='/usr/bin/bzcat' ;; 953 *.bz2) cattool='/usr/bin/bzcat' ;; 954 *.gz) cattool='/usr/bin/gzcat' ;; 955 *.lzma) cattool='/usr/bin/lzcat' ;; 956 *.xz) cattool='/usr/bin/xzcat' ;; 957 *.zst) cattool='/usr/bin/zstdcat' ;; 958 *) cattool='/usr/bin/zcat -f' ;; 959 esac 960} 961 962# Usage: setup_pager 963# Correctly sets $MANPAGER 964setup_pager() { 965 # Setup pager. 966 if [ -z "$MANPAGER" ]; then 967 if [ -n "$MANCOLOR" ]; then 968 MANPAGER="less -sR" 969 else 970 if [ -n "$PAGER" ]; then 971 MANPAGER="$PAGER" 972 else 973 MANPAGER="less -s" 974 fi 975 fi 976 fi 977 decho "Using pager: $MANPAGER" 978} 979 980# Usage: trim string 981# Trims whitespace from beginning and end of a variable 982trim() { 983 tstr=$1 984 while true; do 985 case "$tstr" in 986 [\ \ ]*) tstr="${tstr##[\ \ ]}" ;; 987 *[\ \ ]) tstr="${tstr%%[\ \ ]}" ;; 988 *) break ;; 989 esac 990 done 991} 992 993# Usage: whatis_parse_args "$@" 994# Parse commandline args for whatis and apropos. 995whatis_parse_args() { 996 local cmd_arg 997 OPTIND=1 998 while getopts 'd' cmd_arg; do 999 case "${cmd_arg}" in 1000 d) debug=$(( $debug + 1 )) ;; 1001 *) whatis_usage ;; 1002 esac 1003 done >&2 1004 1005 shift $(( $OPTIND - 1 )) 1006 1007 keywords="$*" 1008} 1009 1010# Usage: whatis_usage 1011# Display usage for the whatis/apropos utility. 1012whatis_usage() { 1013 echo "usage: $cmd [-d] keyword [...]" 1014 exit 1 1015} 1016 1017 1018 1019# Supported commands 1020do_apropos() { 1021 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \ 1022 exec apropos "$@" 1023 search_whatis apropos "$@" 1024} 1025 1026# Usage: do_full_search reg_exp 1027# Do a full search of the regular expression passed 1028# as parameter in all man pages 1029do_full_search() { 1030 local gflags re 1031 re=${1} 1032 1033 # Build grep(1) flags 1034 gflags="-H" 1035 1036 # wflag implies -l for grep(1) 1037 if [ -n "$wflag" ]; then 1038 gflags="${gflags} -l" 1039 fi 1040 1041 gflags="${gflags} --label" 1042 1043 set +f 1044 for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do 1045 for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do 1046 for manfile in ${mpath}/man${section}/*.${section}*; do 1047 mandoc "${manfile}" 2>/dev/null | 1048 grep -E ${gflags} "${manfile}" -e "${re}" 1049 done 1050 done 1051 done 1052 set -f 1053} 1054 1055do_man() { 1056 local IFS 1057 1058 man_parse_opts "$@" 1059 man_setup 1060 1061 shift $(( $OPTIND - 1 )) 1062 IFS=: 1063 for sect in $MANSECT; do 1064 if [ "$sect" = "$1" ]; then 1065 decho "Detected manual section as first arg: $1" 1066 MANSECT="$1" 1067 shift 1068 break 1069 fi 1070 done 1071 unset IFS 1072 pages="$*" 1073 1074 if [ -z "$pages" -a -z "${Kflag}" ]; then 1075 echo 'What manual page do you want?' >&2 1076 exit 1 1077 fi 1078 1079 if [ ! -z "${Kflag}" ]; then 1080 # Short circuit because -K flag does a sufficiently 1081 # different thing like not showing the man page at all 1082 do_full_search "${REGEXP}" 1083 fi 1084 1085 for page in "$@"; do 1086 decho "Searching for \"$page\"" 1087 man_find_and_display "$page" 1088 done 1089 1090 # The user will very commonly quit reading the page before 1091 # reaching the bottom. Depending on the length of the page 1092 # and the pager's buffer size, this may result in a SIGPIPE. 1093 # This is normal, so convert that exit code to zero. 1094 if [ ${ret:-0} -gt 128 ]; then 1095 if [ "$(kill -l "${ret}")" = "PIPE" ]; then 1096 ret=0 1097 fi 1098 fi 1099 1100 exit ${ret:-0} 1101} 1102 1103do_manpath() { 1104 manpath_parse_args "$@" 1105 if [ -z "$qflag" ]; then 1106 manpath_warnings 1107 fi 1108 if [ -n "$Lflag" ]; then 1109 build_manlocales 1110 echo $MANLOCALES 1111 else 1112 build_manpath 1113 echo $MANPATH 1114 fi 1115 exit 0 1116} 1117 1118do_whatis() { 1119 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \ 1120 exec whatis "$@" 1121 search_whatis whatis "$@" 1122} 1123 1124# User's PATH setting decides on the groff-suite to pick up. 1125EQN=eqn 1126NROFF='groff -S -P-h -Wall -mtty-char -mandoc' 1127PIC=pic 1128REFER=refer 1129TBL=tbl 1130TROFF='groff -S -mandoc' 1131 1132LOCALE=/usr/bin/locale 1133STTY=/bin/stty 1134SYSCTL=/sbin/sysctl 1135 1136debug=0 1137man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l' 1138man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man' 1139cattool='/usr/bin/zcat -f' 1140 1141config_global='/etc/man.conf' 1142 1143# This can be overridden via a setting in /etc/man.conf. 1144config_local='/usr/local/etc/man.d/*.conf' 1145 1146# Set noglobbing for now. I don't want spurious globbing. 1147set -f 1148 1149case "$0" in 1150*apropos) do_apropos "$@" ;; 1151*manpath) do_manpath "$@" ;; 1152*whatis) do_whatis "$@" ;; 1153*) do_man "$@" ;; 1154esac 1155