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