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