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 status of roff tools 37set -o pipefail 38 39# ignore SIGPIPE exits because pagers may exit before reading all their input. 40trap '' SIGPIPE 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 local IFS 205 206 # Don't accidentally inherit callers IFS (breaks perl manpages) 207 unset IFS 208 209 # Use some globbing tricks in the shell to determine if a file 210 # exists or not. 211 set +f 212 for file in "$1"* 213 do 214 if [ -r "$file" ]; then 215 found="$file" 216 set -f 217 return 0 218 fi 219 done 220 set -f 221 222 return 1 223} 224 225# Usage: find_file path section subdir pagename 226# Returns: true if something is matched and found. 227# Search the given path/section combo for a given page. 228find_file() { 229 local manroot catroot mann man0 catn cat0 230 231 manroot="$1/man$2" 232 catroot="$1/cat$2" 233 if [ -n "$3" ]; then 234 manroot="$manroot/$3" 235 catroot="$catroot/$3" 236 fi 237 238 if [ ! -d "$manroot" -a ! -d "$catroot" ]; then 239 return 1 240 fi 241 decho " Searching directory $manroot" 2 242 243 mann="$manroot/$4.$2" 244 man0="$manroot/$4.0" 245 catn="$catroot/$4.$2" 246 cat0="$catroot/$4.0" 247 248 # This is the behavior as seen by the original man utility. 249 # Let's not change that which doesn't seem broken. 250 if check_man "$mann" "$catn"; then 251 return 0 252 elif check_man "$man0" "$cat0"; then 253 return 0 254 elif check_cat "$catn"; then 255 return 0 256 elif check_cat "$cat0"; then 257 return 0 258 fi 259 260 return 1 261} 262 263# Usage: is_newer file1 file2 264# Returns true if file1 is newer than file2 as calculated by mtime. 265is_newer() { 266 if ! [ "$1" -ot "$2" ]; then 267 decho " mtime: $1 not older than $2" 3 268 return 0 269 else 270 decho " mtime: $1 older than $2" 3 271 return 1 272 fi 273} 274 275# Usage: manpath_parse_args "$@" 276# Parses commandline options for manpath. 277manpath_parse_args() { 278 local cmd_arg 279 280 OPTIND=1 281 while getopts 'Ldq' cmd_arg; do 282 case "${cmd_arg}" in 283 L) Lflag=Lflag ;; 284 d) debug=$(( $debug + 1 )) ;; 285 q) qflag=qflag ;; 286 *) manpath_usage ;; 287 esac 288 done >&2 289} 290 291# Usage: manpath_usage 292# Display usage for the manpath(1) utility. 293manpath_usage() { 294 echo 'usage: manpath [-Ldq]' >&2 295 exit 1 296} 297 298# Usage: manpath_warnings 299# Display some warnings to stderr. 300manpath_warnings() { 301 if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then 302 echo "(Warning: MANLOCALES environment variable set)" >&2 303 fi 304} 305 306# Usage: man_check_for_so path 307# Returns: True if able to resolve the file, false if it ended in tears. 308# Detects the presence of the .so directive and causes the file to be 309# redirected to another source file. 310man_check_for_so() { 311 local IFS line tstr 312 313 unset IFS 314 if [ -n "$catpage" ]; then 315 return 0 316 fi 317 318 # We need to loop to accommodate multiple .so directives. 319 while true 320 do 321 line=$($cattool "$manpage" 2>/dev/null | head -n1) 322 case "$line" in 323 .so*) trim "${line#.so}" 324 decho "$manpage includes $tstr" 325 # Glob and check for the file. 326 if ! check_man "$1/$tstr" ""; then 327 decho " Unable to find $tstr" 328 return 1 329 fi 330 ;; 331 *) break ;; 332 esac 333 done 334 335 return 0 336} 337 338# Usage: man_display_page 339# Display either the manpage or catpage depending on the use_cat variable 340man_display_page() { 341 local IFS pipeline testline 342 343 # We are called with IFS set to colon. This causes really weird 344 # things to happen for the variables that have spaces in them. 345 unset IFS 346 347 # If we are supposed to use a catpage and we aren't using troff(1) 348 # just zcat the catpage and we are done. 349 if [ -z "$tflag" -a -n "$use_cat" ]; then 350 if [ -n "$wflag" ]; then 351 echo "$catpage (source: \"$manpage\")" 352 ret=0 353 else 354 if [ $debug -gt 0 ]; then 355 decho "Command: $cattool \"$catpage\" | $MANPAGER" 356 ret=0 357 else 358 $cattool "$catpage" | $MANPAGER 359 ret=$? 360 fi 361 fi 362 return 363 fi 364 365 # Okay, we are using the manpage, do we just need to output the 366 # name of the manpage? 367 if [ -n "$wflag" ]; then 368 echo "$manpage" 369 ret=0 370 return 371 fi 372 373 if [ -n "$use_width" ]; then 374 mandoc_args="-O width=${use_width}" 375 fi 376 testline="mandoc -Tlint -Wunsupp >/dev/null 2>&1" 377 if [ -n "$tflag" ]; then 378 pipeline="mandoc -Tps $mandoc_args" 379 else 380 pipeline="mandoc $mandoc_args | $MANPAGER" 381 fi 382 383 if ! $cattool "$manpage" | eval "$testline"; then 384 if which -s groff; then 385 man_display_page_groff 386 else 387 echo "This manpage needs groff(1) to be rendered" >&2 388 echo "First install groff(1): " >&2 389 echo "pkg install groff " >&2 390 ret=1 391 fi 392 return 393 fi 394 395 if [ $debug -gt 0 ]; then 396 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" 397 ret=0 398 else 399 $cattool "$manpage" | eval "$pipeline" 400 ret=$? 401 fi 402} 403 404# Usage: man_display_page_groff 405# Display the manpage using groff 406man_display_page_groff() { 407 local EQN NROFF PIC TBL TROFF REFER VGRIND 408 local IFS l nroff_dev pipeline preproc_arg tool 409 410 # So, we really do need to parse the manpage. First, figure out the 411 # device flag (-T) we have to pass to eqn(1) and groff(1). Then, 412 # setup the pipeline of commands based on the user's request. 413 414 # If the manpage is from a particular charset, we need to setup nroff 415 # to properly output for the correct device. 416 case "${manpage}" in 417 *.${man_charset}/*) 418 # I don't pretend to know this; I'm just copying from the 419 # previous version of man(1). 420 case "$man_charset" in 421 KOI8-R) nroff_dev="koi8-r" ;; 422 ISO8859-1) nroff_dev="latin1" ;; 423 ISO8859-15) nroff_dev="latin1" ;; 424 UTF-8) nroff_dev="utf8" ;; 425 *) nroff_dev="ascii" ;; 426 esac 427 428 NROFF="$NROFF -T$nroff_dev" 429 EQN="$EQN -T$nroff_dev" 430 431 # Iff the manpage is from the locale and not just the charset, 432 # then we need to define the locale string. 433 case "${manpage}" in 434 */${man_lang}_${man_country}.${man_charset}/*) 435 NROFF="$NROFF -dlocale=$man_lang.$man_charset" 436 ;; 437 */${man_lang}.${man_charset}/*) 438 NROFF="$NROFF -dlocale=$man_lang.$man_charset" 439 ;; 440 esac 441 442 # Allow language specific calls to override the default 443 # set of utilities. 444 l=$(echo $man_lang | tr [:lower:] [:upper:]) 445 for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do 446 eval "$tool=\${${tool}_$l:-\$$tool}" 447 done 448 ;; 449 *) NROFF="$NROFF -Tascii" 450 EQN="$EQN -Tascii" 451 ;; 452 esac 453 454 if [ -z "$MANCOLOR" ]; then 455 NROFF="$NROFF -P-c" 456 fi 457 458 if [ -n "${use_width}" ]; then 459 NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n" 460 fi 461 462 if [ -n "$MANROFFSEQ" ]; then 463 set -- -$MANROFFSEQ 464 OPTIND=1 465 while getopts 'egprtv' preproc_arg; do 466 case "${preproc_arg}" in 467 e) pipeline="$pipeline | $EQN" ;; 468 g) ;; # Ignore for compatibility. 469 p) pipeline="$pipeline | $PIC" ;; 470 r) pipeline="$pipeline | $REFER" ;; 471 t) pipeline="$pipeline | $TBL" ;; 472 v) pipeline="$pipeline | $VGRIND" ;; 473 *) usage ;; 474 esac 475 done 476 # Strip the leading " | " from the resulting pipeline. 477 pipeline="${pipeline#" | "}" 478 else 479 pipeline="$TBL" 480 fi 481 482 if [ -n "$tflag" ]; then 483 pipeline="$pipeline | $TROFF" 484 else 485 pipeline="$pipeline | $NROFF | $MANPAGER" 486 fi 487 488 if [ $debug -gt 0 ]; then 489 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" 490 ret=0 491 else 492 $cattool "$manpage" | eval "$pipeline" 493 ret=$? 494 fi 495} 496 497# Usage: man_find_and_display page 498# Search through the manpaths looking for the given page. 499man_find_and_display() { 500 local found_page locpath p path sect 501 502 # Check to see if it's a file. But only if it has a '/' in 503 # the filename. 504 case "$1" in 505 */*) if [ -f "$1" -a -r "$1" ]; then 506 decho "Found a usable page, displaying that" 507 unset use_cat 508 manpage="$1" 509 setup_cattool "$manpage" 510 p=$(cd "$(dirname "$manpage")" && pwd) 511 case "$(basename "$p")" in 512 man*|cat*) p=$p/.. ;; 513 *) p=$p/../.. ;; 514 esac 515 if man_check_for_so "$p"; then 516 found_page=yes 517 man_display_page 518 fi 519 return 520 fi 521 ;; 522 esac 523 524 IFS=: 525 for sect in $MANSECT; do 526 decho "Searching section $sect" 2 527 for path in $MANPATH; do 528 for locpath in $locpaths; do 529 p=$path/$locpath 530 p=${p%/.} # Rid ourselves of the trailing /. 531 532 # Check if there is a MACHINE specific manpath. 533 if find_file $p $sect $MACHINE "$1"; then 534 if man_check_for_so $p; then 535 found_page=yes 536 man_display_page 537 if [ -n "$aflag" ]; then 538 continue 2 539 else 540 return 541 fi 542 fi 543 fi 544 545 # Check if there is a MACHINE_ARCH 546 # specific manpath. 547 if find_file $p $sect $MACHINE_ARCH "$1"; then 548 if man_check_for_so $p; then 549 found_page=yes 550 man_display_page 551 if [ -n "$aflag" ]; then 552 continue 2 553 else 554 return 555 fi 556 fi 557 fi 558 559 # Check plain old manpath. 560 if find_file $p $sect '' "$1"; then 561 if man_check_for_so $p; then 562 found_page=yes 563 man_display_page 564 if [ -n "$aflag" ]; then 565 continue 2 566 else 567 return 568 fi 569 fi 570 fi 571 done 572 done 573 done 574 unset IFS 575 576 # Nothing? Well, we are done then. 577 if [ -z "$found_page" ]; then 578 echo "No manual entry for \"$1\"" >&2 579 ret=1 580 return 581 fi 582} 583 584# Usage: man_parse_opts "$@" 585# Parses commandline options for man. 586man_parse_opts() { 587 local cmd_arg 588 589 OPTIND=1 590 while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do 591 case "${cmd_arg}" in 592 K) Kflag=Kflag 593 REGEXP=$OPTARG ;; 594 M) MANPATH=$OPTARG ;; 595 P) MANPAGER=$OPTARG ;; 596 S) MANSECT=$OPTARG ;; 597 a) aflag=aflag ;; 598 d) debug=$(( $debug + 1 )) ;; 599 f) fflag=fflag ;; 600 h) man_usage 0 ;; 601 k) kflag=kflag ;; 602 m) mflag=$OPTARG ;; 603 o) oflag=oflag ;; 604 p) MANROFFSEQ=$OPTARG ;; 605 t) tflag=tflag ;; 606 w) wflag=wflag ;; 607 *) man_usage ;; 608 esac 609 done >&2 610 611 shift $(( $OPTIND - 1 )) 612 613 # Check the args for incompatible options. 614 615 case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in 616 Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;; 617 Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;; 618 Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;; 619 fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;; 620 fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;; 621 fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;; 622 *kflagtflag*) echo "Incompatible options: -k and -t"; man_usage ;; 623 *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;; 624 *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;; 625 esac 626 627 # Short circuit for whatis(1) and apropos(1) 628 if [ -n "$fflag" ]; then 629 do_whatis "$@" 630 exit 631 fi 632 633 if [ -n "$kflag" ]; then 634 do_apropos "$@" 635 exit 636 fi 637} 638 639# Usage: man_setup 640# Setup various trivial but essential variables. 641man_setup() { 642 # Setup machine and architecture variables. 643 if [ -n "$mflag" ]; then 644 MACHINE_ARCH=${mflag%%:*} 645 MACHINE=${mflag##*:} 646 fi 647 if [ -z "$MACHINE_ARCH" ]; then 648 MACHINE_ARCH=$($SYSCTL -n hw.machine_arch) 649 fi 650 if [ -z "$MACHINE" ]; then 651 MACHINE=$($SYSCTL -n hw.machine) 652 fi 653 decho "Using architecture: $MACHINE_ARCH:$MACHINE" 654 655 setup_pager 656 build_manpath 657 build_mansect 658 man_setup_locale 659 man_setup_width 660} 661 662# Usage: man_setup_width 663# Set up page width. 664man_setup_width() { 665 local sizes 666 667 unset use_width 668 case "$MANWIDTH" in 669 [0-9]*) 670 if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then 671 use_width=$MANWIDTH 672 fi 673 ;; 674 [Tt][Tt][Yy]) 675 if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then 676 set -- $sizes 677 if [ $2 -gt 80 ]; then 678 use_width=$(($2-2)) 679 fi 680 fi 681 ;; 682 esac 683 if [ -n "$use_width" ]; then 684 decho "Using non-standard page width: ${use_width}" 685 else 686 decho 'Using standard page width' 687 fi 688} 689 690# Usage: man_setup_locale 691# Setup necessary locale variables. 692man_setup_locale() { 693 local lang_cc 694 local locstr 695 696 locpaths='.' 697 man_charset='US-ASCII' 698 699 # Setup locale information. 700 if [ -n "$oflag" ]; then 701 decho 'Using non-localized manpages' 702 else 703 # Use the locale tool to give us proper locale information 704 eval $( $LOCALE ) 705 706 if [ -n "$LANG" ]; then 707 locstr=$LANG 708 else 709 locstr=$LC_CTYPE 710 fi 711 712 case "$locstr" in 713 C) ;; 714 C.UTF-8) ;; 715 POSIX) ;; 716 [a-z][a-z]_[A-Z][A-Z]\.*) 717 lang_cc="${locstr%.*}" 718 man_lang="${locstr%_*}" 719 man_country="${lang_cc#*_}" 720 man_charset="${locstr#*.}" 721 locpaths="$locstr" 722 locpaths="$locpaths:$man_lang.$man_charset" 723 if [ "$man_lang" != "en" ]; then 724 locpaths="$locpaths:en.$man_charset" 725 fi 726 locpaths="$locpaths:." 727 ;; 728 *) echo 'Unknown locale, assuming C' >&2 729 ;; 730 esac 731 fi 732 733 decho "Using locale paths: $locpaths" 734} 735 736# Usage: man_usage [exitcode] 737# Display usage for the man utility. 738man_usage() { 739 echo 'Usage:' 740 echo ' man [-adho] [-t | -w] [-K regexp] [-M manpath] [-P pager] [-S mansect]' 741 echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]' 742 echo ' man -f page [...] -- Emulates whatis(1)' 743 echo ' man -k page [...] -- Emulates apropos(1)' 744 745 # When exit'ing with -h, it's not an error. 746 exit ${1:-1} 747} 748 749# Usage: parse_configs 750# Reads the end-user adjustable config files. 751parse_configs() { 752 local IFS file files 753 754 if [ -n "$parsed_configs" ]; then 755 return 756 fi 757 758 unset IFS 759 760 # Read the global config first in case the user wants 761 # to override config_local. 762 if [ -r "$config_global" ]; then 763 parse_file "$config_global" 764 fi 765 766 # Glob the list of files to parse. 767 set +f 768 files=$(echo $config_local) 769 set -f 770 771 for file in $files; do 772 if [ -r "$file" ]; then 773 parse_file "$file" 774 fi 775 done 776 777 parsed_configs='yes' 778} 779 780# Usage: parse_file file 781# Reads the specified config files. 782parse_file() { 783 local file line tstr var 784 785 file="$1" 786 decho "Parsing config file: $file" 787 while read line; do 788 decho " $line" 2 789 case "$line" in 790 \#*) decho " Comment" 3 791 ;; 792 MANPATH*) decho " MANPATH" 3 793 trim "${line#MANPATH}" 794 add_to_manpath "$tstr" 795 ;; 796 MANLOCALE*) decho " MANLOCALE" 3 797 trim "${line#MANLOCALE}" 798 manlocales="$manlocales:$tstr" 799 ;; 800 MANCONFIG*) decho " MANCONFIG" 3 801 trim "${line#MANCONFIG}" 802 config_local="$tstr" 803 ;; 804 MANSECT*) decho " MANSECT" 3 805 trim "${line#MANSECT}" 806 mansect="$mansect:$tstr" 807 ;; 808 # Set variables in the form of FOO_BAR 809 *_*[\ \ ]*) var="${line%%[\ \ ]*}" 810 trim "${line#$var}" 811 eval "$var=\"$tstr\"" 812 decho " Parsed $var" 3 813 ;; 814 esac 815 done < "$file" 816} 817 818# Usage: search_path 819# Traverse $PATH looking for manpaths. 820search_path() { 821 local IFS p path 822 823 decho "Searching PATH for man directories" 824 825 IFS=: 826 for path in $PATH; do 827 if add_to_manpath "$path/man"; then 828 : 829 elif add_to_manpath "$path/MAN"; then 830 : 831 else 832 case "$path" in 833 */bin) p="${path%/bin}/share/man" 834 add_to_manpath "$p" 835 p="${path%/bin}/man" 836 add_to_manpath "$p" 837 ;; 838 esac 839 fi 840 done 841 unset IFS 842 843 if [ -z "$manpath" ]; then 844 decho ' Unable to find any manpaths, using default' 845 manpath=$man_default_path 846 fi 847} 848 849# Usage: search_whatis cmd [arglist] 850# Do the heavy lifting for apropos/whatis 851search_whatis() { 852 local IFS bad cmd f good key keywords loc opt out path rval wlist 853 854 cmd="$1" 855 shift 856 857 whatis_parse_args "$@" 858 859 build_manpath 860 build_manlocales 861 setup_pager 862 863 if [ "$cmd" = "whatis" ]; then 864 opt="-w" 865 fi 866 867 f='whatis' 868 869 IFS=: 870 for path in $MANPATH; do 871 if [ \! -d "$path" ]; then 872 decho "Skipping non-existent path: $path" 2 873 continue 874 fi 875 876 if [ -f "$path/$f" -a -r "$path/$f" ]; then 877 decho "Found whatis: $path/$f" 878 wlist="$wlist $path/$f" 879 fi 880 881 for loc in $MANLOCALES; do 882 if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then 883 decho "Found whatis: $path/$loc/$f" 884 wlist="$wlist $path/$loc/$f" 885 fi 886 done 887 done 888 unset IFS 889 890 if [ -z "$wlist" ]; then 891 echo "$cmd: no whatis databases in $MANPATH" >&2 892 exit 1 893 fi 894 895 rval=0 896 for key in $keywords; do 897 out=$(grep -Ehi $opt -- "$key" $wlist) 898 if [ -n "$out" ]; then 899 good="$good\\n$out" 900 else 901 bad="$bad\\n$key: nothing appropriate" 902 rval=1 903 fi 904 done 905 906 # Strip leading carriage return. 907 good=${good#\\n} 908 bad=${bad#\\n} 909 910 if [ -n "$good" ]; then 911 printf '%b\n' "$good" | $MANPAGER 912 fi 913 914 if [ -n "$bad" ]; then 915 printf '%b\n' "$bad" >&2 916 fi 917 918 exit $rval 919} 920 921# Usage: setup_cattool page 922# Finds an appropriate decompressor based on extension 923setup_cattool() { 924 case "$1" in 925 *.bz) cattool='/usr/bin/bzcat' ;; 926 *.bz2) cattool='/usr/bin/bzcat' ;; 927 *.gz) cattool='/usr/bin/gzcat' ;; 928 *.lzma) cattool='/usr/bin/lzcat' ;; 929 *.xz) cattool='/usr/bin/xzcat' ;; 930 *.zst) cattool='/usr/bin/zstdcat' ;; 931 *) cattool='/usr/bin/zcat -f' ;; 932 esac 933} 934 935# Usage: setup_pager 936# Correctly sets $MANPAGER 937setup_pager() { 938 # Setup pager. 939 if [ -z "$MANPAGER" ]; then 940 if [ -n "$MANCOLOR" ]; then 941 MANPAGER="less -sR" 942 else 943 if [ -n "$PAGER" ]; then 944 MANPAGER="$PAGER" 945 else 946 MANPAGER="less -s" 947 fi 948 fi 949 fi 950 decho "Using pager: $MANPAGER" 951} 952 953# Usage: trim string 954# Trims whitespace from beginning and end of a variable 955trim() { 956 tstr=$1 957 while true; do 958 case "$tstr" in 959 [\ \ ]*) tstr="${tstr##[\ \ ]}" ;; 960 *[\ \ ]) tstr="${tstr%%[\ \ ]}" ;; 961 *) break ;; 962 esac 963 done 964} 965 966# Usage: whatis_parse_args "$@" 967# Parse commandline args for whatis and apropos. 968whatis_parse_args() { 969 local cmd_arg 970 OPTIND=1 971 while getopts 'd' cmd_arg; do 972 case "${cmd_arg}" in 973 d) debug=$(( $debug + 1 )) ;; 974 *) whatis_usage ;; 975 esac 976 done >&2 977 978 shift $(( $OPTIND - 1 )) 979 980 keywords="$*" 981} 982 983# Usage: whatis_usage 984# Display usage for the whatis/apropos utility. 985whatis_usage() { 986 echo "usage: $cmd [-d] keyword [...]" 987 exit 1 988} 989 990 991 992# Supported commands 993do_apropos() { 994 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \ 995 exec apropos "$@" 996 search_whatis apropos "$@" 997} 998 999# Usage: do_full_search reg_exp 1000# Do a full search of the regular expression passed 1001# as parameter in all man pages 1002do_full_search() { 1003 local gflags re 1004 re=${1} 1005 1006 # Build grep(1) flags 1007 gflags="-H" 1008 1009 # wflag implies -l for grep(1) 1010 if [ -n "$wflag" ]; then 1011 gflags="${gflags} -l" 1012 fi 1013 1014 gflags="${gflags} --label" 1015 1016 set +f 1017 for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do 1018 for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do 1019 for manfile in ${mpath}/man${section}/*.${section}*; do 1020 mandoc "${manfile}" 2>/dev/null | 1021 grep -E ${gflags} "${manfile}" -e "${re}" 1022 done 1023 done 1024 done 1025 set -f 1026} 1027 1028do_man() { 1029 local IFS 1030 1031 man_parse_opts "$@" 1032 man_setup 1033 1034 shift $(( $OPTIND - 1 )) 1035 IFS=: 1036 for sect in $MANSECT; do 1037 if [ "$sect" = "$1" ]; then 1038 decho "Detected manual section as first arg: $1" 1039 MANSECT="$1" 1040 shift 1041 break 1042 fi 1043 done 1044 unset IFS 1045 pages="$*" 1046 1047 if [ -z "$pages" -a -z "${Kflag}" ]; then 1048 echo 'What manual page do you want?' >&2 1049 exit 1 1050 fi 1051 1052 if [ ! -z "${Kflag}" ]; then 1053 # Short circuit because -K flag does a sufficiently 1054 # different thing like not showing the man page at all 1055 do_full_search "${REGEXP}" 1056 fi 1057 1058 for page in "$@"; do 1059 decho "Searching for \"$page\"" 1060 man_find_and_display "$page" 1061 done 1062 1063 exit ${ret:-0} 1064} 1065 1066do_manpath() { 1067 manpath_parse_args "$@" 1068 if [ -z "$qflag" ]; then 1069 manpath_warnings 1070 fi 1071 if [ -n "$Lflag" ]; then 1072 build_manlocales 1073 echo $MANLOCALES 1074 else 1075 build_manpath 1076 echo $MANPATH 1077 fi 1078 exit 0 1079} 1080 1081do_whatis() { 1082 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \ 1083 exec whatis "$@" 1084 search_whatis whatis "$@" 1085} 1086 1087# User's PATH setting decides on the groff-suite to pick up. 1088EQN=eqn 1089NROFF='groff -S -P-h -Wall -mtty-char -mandoc' 1090PIC=pic 1091REFER=refer 1092TBL=tbl 1093TROFF='groff -S -mandoc' 1094VGRIND=vgrind 1095 1096LOCALE=/usr/bin/locale 1097STTY=/bin/stty 1098SYSCTL=/sbin/sysctl 1099 1100debug=0 1101man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l' 1102man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man' 1103cattool='/usr/bin/zcat -f' 1104 1105config_global='/etc/man.conf' 1106 1107# This can be overridden via a setting in /etc/man.conf. 1108config_local='/usr/local/etc/man.d/*.conf' 1109 1110# Set noglobbing for now. I don't want spurious globbing. 1111set -f 1112 1113case "$0" in 1114*apropos) do_apropos "$@" ;; 1115*manpath) do_manpath "$@" ;; 1116*whatis) do_whatis "$@" ;; 1117*) do_man "$@" ;; 1118esac 1119