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