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