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