From ae4803a1a6f8db179d9ceec06be3875813203d82 Mon Sep 17 00:00:00 2001 From: Joerg Sonnenberger Date: Thu, 25 Mar 2004 18:05:49 +0000 Subject: [PATCH] Replace the Perl scripts makewhatis(1), makewhatis.local(8) and catman(1) by C programs. Submitted by: Dheeraj Reddy Taken from: FreeBSD In contrast to FreeBSD, put makewhatis.local under src/libexec and put makewhatis into src/usr.sbin. Update man pages accordingly. --- gnu/usr.bin/man/Makefile | 4 +- gnu/usr.bin/man/catman/Makefile | 7 - gnu/usr.bin/man/catman/catman.1 | 133 --- gnu/usr.bin/man/catman/catman.perl | 440 ------- gnu/usr.bin/man/makewhatis/Makefile | 12 - gnu/usr.bin/man/makewhatis/makewhatis.1 | 149 --- gnu/usr.bin/man/makewhatis/makewhatis.perl | 575 --------- libexec/Makefile | 3 +- libexec/getNAME/getNAME.1 | 4 +- libexec/makewhatis.local/Makefile | 10 + .../makewhatis.local}/makewhatis.local.8 | 14 +- .../makewhatis.local}/makewhatis.local.sh | 10 +- share/man/man5/rc.conf.5 | 4 +- usr.bin/Makefile | 3 +- usr.bin/catman/Makefile | 6 + usr.bin/catman/catman.1 | 104 ++ usr.bin/catman/catman.c | 793 +++++++++++++ usr.sbin/Makefile | 3 +- usr.sbin/makewhatis/Makefile | 9 + usr.sbin/makewhatis/makewhatis.8 | 134 +++ usr.sbin/makewhatis/makewhatis.c | 1041 +++++++++++++++++ 21 files changed, 2123 insertions(+), 1335 deletions(-) delete mode 100644 gnu/usr.bin/man/catman/Makefile delete mode 100644 gnu/usr.bin/man/catman/catman.1 delete mode 100644 gnu/usr.bin/man/catman/catman.perl delete mode 100644 gnu/usr.bin/man/makewhatis/Makefile delete mode 100644 gnu/usr.bin/man/makewhatis/makewhatis.1 delete mode 100644 gnu/usr.bin/man/makewhatis/makewhatis.perl create mode 100644 libexec/makewhatis.local/Makefile rename {gnu/usr.bin/man/makewhatis => libexec/makewhatis.local}/makewhatis.local.8 (88%) rename {gnu/usr.bin/man/makewhatis => libexec/makewhatis.local}/makewhatis.local.sh (85%) create mode 100644 usr.bin/catman/Makefile create mode 100644 usr.bin/catman/catman.1 create mode 100644 usr.bin/catman/catman.c create mode 100644 usr.sbin/makewhatis/Makefile create mode 100644 usr.sbin/makewhatis/makewhatis.8 create mode 100644 usr.sbin/makewhatis/makewhatis.c diff --git a/gnu/usr.bin/man/Makefile b/gnu/usr.bin/man/Makefile index 2529d8dbd4..6d255e349a 100644 --- a/gnu/usr.bin/man/Makefile +++ b/gnu/usr.bin/man/Makefile @@ -4,7 +4,9 @@ # License as specified in the README file that comes with the man 1.0 # distribution. # +# $DragonFly: src/gnu/usr.bin/man/Makefile,v 1.2 2004/03/25 18:05:49 joerg Exp $ +# -SUBDIR = lib man manpath apropos makewhatis catman +SUBDIR = lib man manpath apropos .include diff --git a/gnu/usr.bin/man/catman/Makefile b/gnu/usr.bin/man/catman/Makefile deleted file mode 100644 index a350195e32..0000000000 --- a/gnu/usr.bin/man/catman/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# $FreeBSD: src/gnu/usr.bin/man/catman/Makefile,v 1.11.2.2 2001/04/25 14:04:13 ru Exp $ -# $DragonFly: src/gnu/usr.bin/man/catman/Attic/Makefile,v 1.3 2004/01/31 06:56:38 dillon Exp $ - -SCRIPTS= catman.perl -MAN= catman.1 - -.include diff --git a/gnu/usr.bin/man/catman/catman.1 b/gnu/usr.bin/man/catman/catman.1 deleted file mode 100644 index d4cdfe84e9..0000000000 --- a/gnu/usr.bin/man/catman/catman.1 +++ /dev/null @@ -1,133 +0,0 @@ -.\" Copyright (c) March 1996 Wolfram Schneider . Berlin. -.\" All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in the -.\" documentation and/or other materials provided with the distribution. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. -.\" -.\" /usr/bin/catman - preformat man pages -.\" -.\" $FreeBSD: src/gnu/usr.bin/man/catman/catman.1,v 1.17.2.6 2001/07/22 11:01:25 dd Exp $ -.\" $DragonFly: src/gnu/usr.bin/man/catman/Attic/catman.1,v 1.2 2003/06/17 04:25:46 dillon Exp $ -.Dd March 12, 1995 -.Dt CATMAN 1 -.Os -.Sh NAME -.Nm catman -.Nd preformat man pages -.Sh SYNOPSIS -.Nm -.Op Fl f | Fl force -.Op Fl h | Fl help -.Op Fl L | Fl locale -.Op Fl p | Fl print -.Op Fl r | Fl remove -.Op Fl v | Fl verbose -.Op Ar directories ... -.Sh DESCRIPTION -.Nm Catman -format man pages to ASCII. It's like typing -.Sq man program -for all man pages in -.Ar directories . -.Ar Directories -is a list of man directories or subdirectories separated -by spaces or colons. -Use -.Ar /usr/share/man -if no -.Ar directories -defined. -.Sh OPTIONS -.Bl -tag -width Ds -.It Fl f , Fl force -Force overwriting old cat pages. Normally only those pages will be formatted -which are not up to date. This option is a waste of time, CPU and RAM. -.It Fl h , Fl help -Print options and exit. -.It Fl L , Fl locale -Sense locale environment variables for possible localized man subdirectories -and process these entries only. -.It Fl p , Fl print -Don't actually format man pages. Show what would be done. -.It Fl r , Fl remove -Remove garbage, e.\& g. catpage without manpage, uncompressed catpage but -a compressed catpage exist, filenames with non-alphanumeric -characters, uncompressed manpage but a compressed manpage exist. -.It Fl v , Fl verbose -More warnings. -.El -.Sh EXAMPLES -.Dl $ catman -.Pp -Format man pages in -.Ar /usr/share/man -if necessary. -.Pp -.Dl $ catman $MANPATH -.Pp -Format all your man pages if necessary. -.Pp -.Dl $ catman -f /usr/local/man/man1 /usr/local/man/manl -.Pp -Force reformatting of all man pages in -.Pa /usr/local/man/man1 -and -.Pa /usr/local/man/manl . -.Pp -.Dl $ catman -p /usr/X11/man -.Pp -Show only. -.Sh FILES -.Bl -tag -width /etc/periodic/weekly/330.catman -.It Pa /etc/periodic/weekly/330.catman -Starts this program -.El -.Sh FEATURES -Very fast if all man pages already formatted. -Does not support the -.Fl w -option as some other systems do. Use -.Xr makewhatis 1 -to rebuild the -.Ql whatis -database. -.Sh BUGS -.Xr man 1 -is a setuid program. Be careful that user -.Sq man -has write permissions to the catman directories. -.Nm Catman -does not check for any -.Sq .so -in man page sources. Use hard or symlinks -to avoid redundant formatted man pages. -.Sh SEE ALSO -.Xr makewhatis 1 , -.Xr man 1 , -.Xr manpath 1 -.Sh HISTORY -This version of -.Nm -command appeared in -.Fx 2.1 . -.Sh AUTHORS -.An Wolfram Schneider Aq wosch@FreeBSD.org , -Berlin. diff --git a/gnu/usr.bin/man/catman/catman.perl b/gnu/usr.bin/man/catman/catman.perl deleted file mode 100644 index 9e669f7745..0000000000 --- a/gnu/usr.bin/man/catman/catman.perl +++ /dev/null @@ -1,440 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (c) March 1995 Wolfram Schneider . Berlin. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. -# -# /usr/bin/catman - preformat man pages -# -# $FreeBSD: src/gnu/usr.bin/man/catman/catman.perl,v 1.14.2.5 2002/01/05 16:20:52 phantom Exp $ -# $DragonFly: src/gnu/usr.bin/man/catman/Attic/catman.perl,v 1.2 2003/06/17 04:25:46 dillon Exp $ - - -sub usage { - -warn <= 0; - return @defaultmanpath if $#defaultmanpath >= 0; - - warn "Missing directories\n"; &usage; -} - -# make relative path to absolute path -sub absolute_path { - local(@dirlist) = @_; - local($pwd, $dir, @a); - - $pwd = $ENV{'PWD'}; - - foreach $dir (@dirlist) { - if ($dir !~ "^/") { - chop($pwd = `pwd`) if (!$pwd || $pwd !~ /^\//); - push(@a, "$pwd/$dir"); - } else { - push(@a, $dir); - } - } - return @a; -} - -# strip unused '/' -# e.g.: //usr///home// -> /usr/home -sub stripdir { - local($dir) = @_; - - $dir =~ s|/+|/|g; # delete double '/' - $dir =~ s|/$||; # delete '/' at end - $dir =~ s|/(\.\/)+|/|g; # delete ././././ - - $dir =~ s|/+|/|g; # delete double '/' - $dir =~ s|/$||; # delete '/' at end - $dir =~ s|/\.$||; # delete /. at end - return $dir if $dir ne ""; - return '/'; -} - -# read man directory -sub parse_dir { - local($dir) = @_; - local($subdir, $catdir); - local($dev,$ino) = (stat($dir))[01]; - - # already visit - if ($dir_visit{$dev,$ino}) { - warn "$dir already parsed: $dir_visit{$dev,$ino}\n"; - return 1; - } - $dir_visit{$dev,$ino} = $dir; - - # Manpath, /usr/local/man or - # localized manpath /usr/local/man/{$LC_CTYPE|$LANG} - if (($dir =~ /man$/) || - (($locale) && ($dir =~ /man/) && ($dir =~ $local_suffix))) { - warn "open manpath directory ``$dir''\n" if $verbose; - if (!opendir(DIR, $dir)) { - warn "opendir ``$dir'':$!\n"; $exit = 1; return 0; - } - - warn "chdir to: $dir\n" if $verbose; - chdir($dir) || do { warn "$dir: $!\n"; $exit = 1; return 0 }; - - foreach $subdir (sort(readdir(DIR))) { - if ($subdir =~ /^man\w+$/) { - $subdir = "$dir/$subdir"; - &catdir_create($subdir) && &parse_subdir($subdir); - } - } - closedir DIR - - # subdir, /usr/local/man/man1 - } elsif ($dir =~ /man\w+$/) { - local($parentdir) = $dir; - $parentdir =~ s|/[^/]+$||; - warn "chdir to: $parentdir\n" if $verbose; - chdir($parentdir) || do { - warn "$parentdir: $!\n"; $exit = 1; return 0 }; - - &catdir_create($dir) && &parse_subdir($dir); - } else { - warn "Assume ``$dir'' is not a man directory.\n"; - $exit = 1; - } -} - -# create cat subdirectory if neccessary -# e.g.: man9 exist, but cat9 not -sub catdir_create { - local($subdir) = @_; - local($catdir) = $subdir; - - $catdir = &man2cat($subdir); - if (-d $catdir) { - return 1 if -w _; - if (!chmod(0755, $catdir)) { - warn "Cannot write $catdir, chmod: $!\n"; - $exit = 1; - return 0; - } - return 1; - } - - warn "mkdir ``$catdir''\n" if $verbose || $print; - unless ($print) { - unlink($catdir); # be paranoid - if (!mkdir($catdir, 0755)) { - warn "Cannot make $catdir: $!\n"; - $exit = 1; - return 0; - } - return 1; - } -} - -# I: /usr/share/man/man9 -# O: /usr/share/man/cat9 -sub man2cat { - local($man) = @_; - - $man =~ s/man(\w+)$/cat$1/; - return $man; -} - -sub parse_subdir { - local($subdir) = @_; - local($file, $f, $catdir, $catdir_short, $mandir, $mandir_short); - local($mtime_man, $mtime_cat); - local(%read); - - $mandir = $subdir; - $catdir = &man2cat($mandir); - - ($mandir_short = $mandir) =~ s|.*/(.*)|$1|; - ($catdir_short = $catdir) =~ s|.*/(.*)|$1|; - - warn "open man directory: ``$mandir''\n" if $verbose; - if (!opendir(D, $mandir)) { - warn "opendir ``$mandir'': $!\n"; $exit = 1; return 0; - } - - foreach $file (readdir(D)) { - # skip current and parent directory - next if $file eq "." || $file eq ".."; - - # fo_09-o.bar0 - if ($file !~ /^[\w\-\+\[\.:]+\.\w+$/) { - &garbage("$mandir/$file", "Assume garbage") - unless -d "$mandir/$file"; - next; - } - - if ($file !~ /\.gz$/) { - if (-e "$mandir/$file.gz") { - &garbage("$mandir/$file", - "Manpage unused, see compressed version"); - next; - } - warn "$mandir/$file is uncompressed\n" if $verbose; - $cfile = "$file.gz"; - } else { - $cfile = "$file"; - } - - if (!(($mtime_man = ((stat("$mandir_short/$file"))[9])) && -r _ && -f _)) { - if (! -d _) { - warn "Cannot read file: ``$mandir/$file''\n"; - $exit = 1; - if ($remove && -l "$mandir/$file") { - &garbage("$mandir/$file", "Assume wrong symlink"); - } - next; - } - warn "Ignore subsubdirectory: ``$mandir/$file''\n" - if $verbose; - next; - } - - $read{$file} = 1; - - # Assume catpages always compressed - if (($mtime_cat = ((stat("$catdir_short/$cfile"))[9])) - && -r _ && -f _) { - if ($mtime_man > $mtime_cat || $force) { - &nroff("$mandir/$file", "$catdir/$cfile"); - } else { - warn "up to date: $mandir/$file\n" if $verbose; - #print STDERR "." if $verbose; - } - } else { - &nroff("$mandir/$file", "$catdir/$cfile"); - } - } - closedir D; - - if (!opendir(D, $catdir)) { - warn "opendir ``$catdir'': $!\n"; return 0; - } - - warn "open cat directory: ``$catdir''\n" if $verbose; - foreach $file (readdir(D)) { - next if $file =~ /^(\.|\.\.)$/; # skip current and parent directory - - if ($file !~ /^[\w\-\+\[\.:]+\.\w+$/) { - &garbage("$catdir/$file", "Assume garbage") - unless -d "$catdir/$file"; - next; - } - - if ($file !~ /\.gz$/ && $read{"$file.gz"}) { - &garbage("$catdir/$file", - "Catpage unused, see compressed version"); - } elsif (!$read{$file}) { - # maybe a bug in man(1) - # if both manpage and catpage are uncompressed, man reformats - # the manpage and puts a compressed catpage to the - # already existing uncompressed catpage - ($f = $file) =~ s/\.gz$//; - - # man page is uncompressed, catpage is compressed - next if $read{$f}; - &garbage("$catdir/$file", "Catpage without manpage"); - } - } - closedir D; -} - -sub garbage { - local($file, @text) = @_; - - warn "@text: ``$file''\n"; - if ($remove) { - warn "unlink $file\n"; - unless ($print) { - unlink($file) || warn "unlink $file: $!\n" ; - } - } -} - -sub nroff { - local($man,$cat) = @_; - local($nroff) = "nroff -T" . $dev_name . " -man | col"; - local($dev, $ino) = (stat($man))[01]; - - # It's a link - if ($link{"$dev.$ino"}) { - warn "Link: $link{\"$dev.$ino\"} -> $cat\n" if $verbose || $print; - - return if $print; # done - unlink($cat); # remove possible old link - - unless (link($link{"$dev.$ino"}, $cat)) { - warn "Link $cat: $!\n"; - $exit = 1; - } - return; - } else { - $cat = "$cat$ext" if $cat !~ /$ext$/; - warn "Format: $man -> $cat\n" if $verbose || $print; - - unless($print) { - # man page is compressed - if ($man =~ /$ext$/) { - $nroff = "zcat $man | tbl | $nroff"; - } else { - $nroff = "tbl $man | $nroff"; - } - - # start formatting - $tmp = "$cat.$tmp"; # for cleanup after signals - system("$nroff | gzip > $cat.tmp"); - if ($?) { - # assume a fatal signal to nroff - &Exit("INT to system() function") if ($? == 2); - } else { - rename("$cat.tmp", $cat); - } - } - } - - # dev/ino from manpage, path from catpage - $link{"$dev.$ino"} = $cat; -} - -# Set correct [gn]roff output device name ([ng]roff's "-T" option) -sub nroff_device { - # Choose default output device name. - $dev_name = "ascii"; - - if ($locale) { - # Use "nroff -Tkoi8-r -man" to format russian manpages (if catman "-L" - # option specified only). - if ($local_suffix =~ '\.KOI8-R$') { - $dev_name = "koi8-r"; - } - # Use "nroff -Tlatin1 -man" to format ISO 8859-1 manpages - elsif ($local_suffix =~ '\.ISO_?8859-15?$') { - $dev_name = "latin1"; - } - } - warn "nroff output device name is $dev_name\n" if $verbose; -} - -# process directory -sub process_dir { - local($dir) = @_; - - if (-e $dir && -d _ && -r _ && -x _) { - warn "``$dir'' is not writable for you,\n" . - "can only write to existing cat subdirs (if any)\n" - if ! -w _ && $verbose; - &parse_dir(&stripdir($dir)); - } else { - warn "``$dir'' is not a directory or not read-/searchable for you\n"; - $exit = 1; - } -} - -# convert locale name to short notation (ru_RU.KOI8-R -> ru.KOI8-R) -sub short_locale_name { - local($lname) = @_; - - $lname =~ s|_[A-Z][A-Z]||; - warn "short locale name is $lname\n" if $verbose && $locale; - - return $lname; -} - -############# -# main -warn "Don't start this program as root, use:\n" . - "echo $0 @ARGV | nice -5 su -m man\n" unless $>; - -&variables; -@argv = &parse(split(/[ :]/, join($", @ARGV))); -&nroff_device; -foreach $dir (@argv) { - if ($locale) { - if ($local_suffix ne "") { - &process_dir($dir.'/'.$local_suffix); - &process_dir($dir.'/'.&short_locale_name($local_suffix)); - } - } else { - &process_dir($dir); - } -} -exit($exit); diff --git a/gnu/usr.bin/man/makewhatis/Makefile b/gnu/usr.bin/man/makewhatis/Makefile deleted file mode 100644 index 18ca4e3b40..0000000000 --- a/gnu/usr.bin/man/makewhatis/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -# $FreeBSD: src/gnu/usr.bin/man/makewhatis/Makefile,v 1.16.2.2 2001/04/25 14:04:13 ru Exp $ -# $DragonFly: src/gnu/usr.bin/man/makewhatis/Attic/Makefile,v 1.3 2004/01/31 06:56:38 dillon Exp $ - -SCRIPTS=makewhatis.perl makewhatis.local.sh -MAN= makewhatis.1 makewhatis.local.8 - -libexecdir=/usr/libexec -LINKS=${libexecdir}/makewhatis.local ${libexecdir}/catman.local -SCRIPTSDIR_makewhatis.local.sh= ${libexecdir} -MLINKS= makewhatis.local.8 catman.local.8 - -.include diff --git a/gnu/usr.bin/man/makewhatis/makewhatis.1 b/gnu/usr.bin/man/makewhatis/makewhatis.1 deleted file mode 100644 index 72ef43174b..0000000000 --- a/gnu/usr.bin/man/makewhatis/makewhatis.1 +++ /dev/null @@ -1,149 +0,0 @@ -.\" Copyright (c) 1994-1996 Wolfram Schneider . Berlin. -.\" All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in the -.\" documentation and/or other materials provided with the distribution. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. -.\" -.\" $FreeBSD: src/gnu/usr.bin/man/makewhatis/makewhatis.1,v 1.14.2.6 2001/08/16 10:14:57 ru Exp $ -.\" $DragonFly: src/gnu/usr.bin/man/makewhatis/Attic/makewhatis.1,v 1.2 2003/06/17 04:25:46 dillon Exp $ -.Dd January 12, 1995 -.Dt MAKEWHATIS 1 -.Os -.Sh NAME -.Nm makewhatis -.Nd create whatis database -.Sh SYNOPSIS -.Nm -.Op Fl a | Fl append -.Op Fl h | Fl help -.Op Fl i | Fl indent Ar column -.Op Fl L | Fl locale -.Op Fl n | Fl name Ar name -.Op Fl o | Fl outfile Ar file -.Op Fl v | Fl verbose -.Op Ar directories ... -.Sh DESCRIPTION -.Nm -extracts the name and a short description from unformatted manpages -and creates the -.Xr whatis 1 -database. -.Nm -can read gzip'ed manpages. -.Ar Directory -names a directory containing manpage subdirectories -(named -.Pa man.+ ) . -Colons are treated as spaces, hence -.Ic makewhatis $MANPATH -or -.Ic makewhatis `manpath` -are allowed. -.Sh OPTIONS -.Bl -tag -width Ds -.It Fl a , Fl append -Append mode. Don't delete old entries in whatis database. Note: -the new database will be sorted without repeated lines and -.Nm -does not check if old entries are valid. -.It Fl h , Fl help -Print options and exit. -.It Fl i , Fl indent Ar column -Justify description strings to -.Ar column -(default 24). -.It Fl L , Fl locale -Sense locale environment variables for possible localized man subdirectories -and process this entries only. -.It Fl n , Fl name Ar name -Use -.Ar name -instead of -.Pa whatis Ns . -.It Fl o , Fl outfile Ar file -Write all output to -.Ar file -instead of -.Pa dirname/whatis Ns . -.It Fl v , Fl verbose -Issue more warnings -(to stderr). -For every parsed man page write a single char: -.Ql .\& -for an uncompressed page, -.Ql * -for a compressed page, and -.Ql + -for a link. -.El -.Sh EXAMPLES -.Ic makewhatis $MANPATH -.Pp -Create whatis database for all directories in your -.Pa $MANPATH Ns . -.Pp -.Ic makewhatis -outfile /tmp/mywhatis /usr/local/man $HOME/man -.Pp -Create whatis database -.Pa /tmp/mywhatis . -Traverse directories -.Pa /usr/local/man -and -.Pa $HOME/man Ns . -Don't create -.Pa /usr/local/man/whatis -or -.Pa $HOME/man/whatis Ns . -.Pp -.Ic makewhatis -name windex $HOME/man -.Pp -Create whatis database -.Pa windex -instead of -.Pa whatis Ns . -May be useful for Solaris. -.Sh FILES -.Bl -tag -width /etc/master.passwdxx -compact -.It Pa */man/whatis -whatis database -.It Pa /etc/periodic/weekly/320.whatis -run -.Nm makewhatis.local -every week -.El -.Sh SEE ALSO -.Xr apropos 1 , -.Xr catman 1 , -.Xr getNAME 1 , -.Xr man 1 , -.Xr manpath 1 , -.Xr sort 1 , -.Xr uniq 1 , -.Xr whatis 1 , -.Xr makewhatis.local 8 -.Sh HISTORY -This -.Nm -command appeared in -.Fx 2.1 . -.Sh AUTHORS -.An Wolfram Schneider , -Berlin. diff --git a/gnu/usr.bin/man/makewhatis/makewhatis.perl b/gnu/usr.bin/man/makewhatis/makewhatis.perl deleted file mode 100644 index 958c01852d..0000000000 --- a/gnu/usr.bin/man/makewhatis/makewhatis.perl +++ /dev/null @@ -1,575 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (c) 1994-1996 Wolfram Schneider . Berlin. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. -# -# makewhatis -- update the whatis database in the man directories. -# -# $FreeBSD: src/gnu/usr.bin/man/makewhatis/makewhatis.perl,v 1.21.2.4 2002/03/29 15:38:09 ru Exp $ -# $DragonFly: src/gnu/usr.bin/man/makewhatis/Attic/makewhatis.perl,v 1.2 2003/06/17 04:25:46 dillon Exp $ - - -sub usage { - - warn <) { - push(@a, $_); - } - close A; - } - - else { - warn "$whatisdb: $!\n" if lstat($file) && $verbose; # - } - undef $file; - } - - - warn "Open $whatisdb\n" if $verbose; - if (!open(A, "> $whatisdb")) { - die "$whatisdb: $!\n" if $outfile; - - warn "$whatisdb: $!\n"; $err++; return 0; - } - - select A; - return 1; -} - -sub close_output { - local($success) = @_; - local($w) = $whatisdb; - local($counter) = 0; - local($i, $last,@b); - - $w =~ s/\.tmp$//; - if ($success) { # success - # uniq - warn "\n" if $verbose && $pointflag; - warn "sort -u > $whatisdb\n" if $verbose; - foreach $i (sort @a) { - if ($i ne $last) { - push(@b, $i); - } - $last =$i; - } - - $counter = $#b + 1; - print @b; close A; select STDOUT; - - if (!$outfile) { - warn "Rename $whatisdb to $w\n" if $verbose; - rename($whatisdb, $w) || warn "rename $whatisdb $w\n"; - $counter_all += $counter; - warn "$counter entries in $w\n" if $verbose; - } else { - $counter_all = $counter; - } - } else { # building whatisdb failed - unlink($whatisdb); - warn "building whatisdb: $whatisdb failed\n" if $verbose; - } - return 1; -} - -sub parse_subdir { - local($dir) = @_; - local($file, $dev,$ino); - - warn "\n" if $pointflag; - warn "traverse $dir\n" if $verbose; - $pointflag = 0; - - if (!opendir(M, $dir)) { - warn "$dir: $!\n"; $err++; return 0; - } - - $| = 1 if $verbose; - foreach $file (readdir(M)) { - next if $file =~ /^(\.|\.\.)$/; - - ($dev, $ino) = ((stat("$dir/$file"))[01]); - if (-f _) { - if ($man_red{"$dev.$ino"}) { - # Link - print STDERR "+" if $verbose; - $pointflag++ if $verbose; - } else { - &manual("$dir/$file"); - } - $man_red{"$dev.$ino"} = 1; - } elsif (! -d _) { - warn "Cannot find file: $dir/$file\n"; $err++; - } - } - closedir M; - return 1; -} - -# read man directory -sub parse_dir { - local($dir) = @_; - local($subdir, $file); - - # clean up, in case mandir and subdirs are called simultaneously - # e. g.: ~/man/man1 ~/man/man2 ~/man - #~/man/ man1 and ~/man/man2 are a subset of ~/man - foreach $file (keys %man_red) { - delete $man_red{$file}; - } - - if ($dir =~ /man/) { - warn "\n" if $verbose && $pointflag; - warn "open manpath directory ``$dir''\n" if $verbose; - $pointflag = 0; - if (!opendir(DIR, $dir)) { - warn "opendir ``$dir'':$!\n"; $err = 1; return 0; - } - foreach $subdir (sort(readdir(DIR))) { - if ($subdir =~ /^man\w+$/) { - $subdir = "$dir/$subdir"; - &parse_subdir($subdir); - &parse_subdir($subdir) if -d ($subdir .= "/${machine}"); - } - } - closedir DIR - - } elsif ($dir =~ /man\w+$/) { - &parse_subdir($dir); - } else { - warn "Assume ``$dir'' is not a man directory.\n"; - $err = 1; return 0; - } - return 1; -} - -sub dir_redundant { - local($dir) = @_; - - local($dev,$ino) = (stat($dir))[0..1]; - - if ($dir_redundant{"$dev.$ino"}) { - warn "$dir is equal to: $dir_redundant{\"$dev.$ino\"}\n" if $verbose; - return 0; - } - $dir_redundant{"$dev.$ino"} = $dir; - return 1; -} - - -# ``/usr/man/man1/foo.l'' -> ``l'' -sub ext { - local($filename) = @_; - local($extension) = $filename; - - $extension =~ s/$ext$//g; # strip .gz - $extension =~ s/.*\///g; # basename - - if ($extension !~ m%[^/]+\.[^.]+$%) { # no dot - $extension = $filename; - #$extension =~ s|/[^/]+$||; - $extension =~ s%.*man([^/]+)/[^/]+%$1%; # last character - warn "\n" if $verbose && $pointflag; - warn "$filename has no extension, try section ``$extension''\n" - if $verbose; - $pointflag = 0; - } else { - $extension =~ s/.*\.//g; # foo.bla.1 -> 1 - } - return "$extension"; -} - -# ``/usr/man/man1/foo.1'' -> ``foo'' -sub name { - local($name) = @_; - - $name =~ s=.*/==; - $name =~ s=$ext$==o; - $name =~ s=\.[^\.]+$==; - - return "$name"; -} - -# output -sub out { - local($list) = @_; - local($delim) = " - "; - $_ = $list; - - # delete italic etc. - s/^\.[^ -]+[ -]+//; - s/\\\((em|mi)//; - s/\\f[IRBP]//g; - s/\\\*p//g; - s/\(OBSOLETED\)[ ]?//; - s/\\&//g; - s/^\@INDOT\@//; - s/[\"\\]//g; #" - s/[. \t-]+$//; - - s/ / - / unless / - /; - ($man,$desc) = split(/ - /); - - $man = $name unless $man; - $man =~ s/[,. ]+$//; - $man =~ s/,/($extension),/g; - $man .= "($extension)"; - - &manpagename; - - $desc =~ s/^[ \t]+//; - - for($i = length($man); $i < $indent && $desc; $i++) { - $man .= ' '; - } - if ($desc) { - push(@a, "$man$delim$desc\n"); - } else { - push(@a, "$man\n"); - } -} - -# The filename of manual page is not a keyword. -# This is bad, because you don't find the manpage -# whith: $ man
-# -# Add filename if a) filename is not a keyword and b) no keyword(s) -# exist as file in same mansection -# -sub manpagename { - foreach (split(/,\s+/, $man)) { - s/\(.+//; - # filename is keyword - return if $name eq $_; - } - - local($f) = $file; $f =~ s%/*[^/]+$%%; # dirname - local($e) = $file; $e =~ s/$ext$//; $e =~ s%.*(\.[^.]+)$%$1%; # .1 - - foreach (split(/,\s+/, $man)) { - s/\(.+//; - - # a keyword exist as file - return if -e "$f/$_$e" || -e "$f/$_$e$ext"; - } - - $man = "$name($extension), $man"; -} - -# looking for NAME -sub manual { - local($file) = @_; - local($list, $desc, $extension); - local($ofile) = $file; - - # Compressed man pages - if ($ofile =~ /$ext$/) { - $ofile = "gzcat $file |"; - print STDERR "*" if $verbose; - } else { - print STDERR "." if $verbose; - } - $pointflag++ if $verbose; - - if (!open(F, "$ofile")) { - warn "Cannot open file: $ofile\n"; $err++; - return 0; - } - # extension/section - $extension = &ext($file); - $name = &name($file); - - $section_name = "NAME|Name|NAMN|BEZEICHNUNG|̾¾Î|îáú÷áîéå"; - - local($source) = 0; - local($list); - while() { - # ``man'' style pages - # &&: it takes you only half the user time, regexp is slow!!! - if (/^\.SH/ && /^\.SH[ \t]+["]?($section_name)["]?/) { - #while() { last unless /^\./ } # Skip - #chop; $list = $_; - while() { - last if /^\.SH[ \t]/; - chop; - s/^\.IX\s.*//; # delete perlpod garbage - s/^\.[A-Z]+[ ]+[0-9]+$//; # delete commands - s/^\.[A-Za-z]*[ \t]*//; # delete commands - s/^\.\\".*$//; #" delete comments - s/^[ \t]+//; - if ($_) { - $list .= $_; - $list .= ' '; - } - } - while() { } # skip remaining input to avoid pipe errors - &out($list); close F; return 1; - } elsif (/^\.Sh/ && /^\.Sh[ \t]+["]?($section_name)["]?/) { - # ``doc'' style pages - local($flag) = 0; - while() { - last if /^\.Sh/; - chop; - s/^\.\\".*$//; #" delete comments - next if /^\.[ \t]*$/; # skip empty calls - if (/^\.Nm/) { - s/^\.Nm[ \t]*//; - s/ ,/,/g; - s/[ \t]+$//; - $list .= $_; - $list .= ' '; - } else { - $list .= '- ' if (!$flag && !/^- /); - $flag++; - if (/^\.Xr/) { - split; - $list .= @_[1]; - $list .= "(@_[2])" if @_[2]; - } else { - s/^\.([A-Z][a-z])?[ \t]*//; - s/[ \t]+$//; - $list .= $_; - } - $list .= ' '; - } - } - while() { } # skip remaining input to avoid pipe errors - &out($list); close F; return 1; - - } elsif(/^\.so/ && /^\.so[ \t]+man/) { - while() { } # skip remaining input to avoid pipe errors - close F; return 1; - } - } - if (!$source && $verbose) { - warn "\n" if $pointflag; - warn "Maybe $file is not a manpage\n" ; - $pointflag = 0; - } - return 0; -} - -# make relative path to absolute path -sub absolute_path { - local(@dirlist) = @_; - local($pwd, $dir, @a); - - $pwd = $ENV{'PWD'}; - foreach $dir (@dirlist) { - if ($dir !~ "^/") { - chop($pwd = `pwd`) if (!$pwd || $pwd !~ /^\//); - push(@a, "$pwd/$dir"); - } else { - push(@a, $dir); - } - } - return @a; -} - -# strip unused '/' -# e.g.: //usr///home// -> /usr/home -sub stripdir { - local($dir) = @_; - - $dir =~ s|/+|/|g; # delete double '/' - $dir =~ s|/$||; # delete '/' at end - $dir =~ s|/(\.\/)+|/|g; # delete ././././ - - $dir =~ s|/+|/|g; # delete double '/' - $dir =~ s|/$||; # delete '/' at end - $dir =~ s|/\.$||; # delete /. at end - return $dir if $dir ne ""; - return '/'; -} - -sub variables { - $verbose = 0; # Verbose - $indent = 24; # Indent for description - $outfile = 0; # Don't write to ./whatis - $whatis_name = "whatis"; # Default name for DB - $append = 0; # Don't delete old entries - $locale = 0; # Build DB only for localized man directories - chomp($machine = $ENV{'MACHINE'} || `uname -m`); - - # choose localized man directories suffix. - $local_suffix = $ENV{'LC_ALL'} || $ENV{'LC_CTYPE'} || $ENV{'LANG'}; - - # if no argument for directories given - @defaultmanpath = ( '/usr/share/man' ); - - $ext = '.gz'; # extension - umask(022); - - $err = 0; # exit code - $whatisdb = ''; - $counter_all = 0; - $dir_redundant = ''; # redundant directories - $man_red = ''; # redundant man pages - @a = (); # Array for output - - # Signals - $SIG{'INT'} = 'Exit'; - $SIG{'HUP'} = 'Exit'; - $SIG{'TRAP'} = 'Exit'; - $SIG{'QUIT'} = 'Exit'; - $SIG{'TERM'} = 'Exit'; - $tmp = ''; # tmp file - - $ENV{'PATH'} = "/bin:/usr/bin:$ENV{'PATH'}"; -} - -sub Exit { - unlink($tmp) if $tmp ne ""; # unlink if a filename - die "$0: die on signal SIG@_\n"; -} - -sub parse { - local(@argv) = @_; - local($i); - - while ($_ = $argv[0], /^-/) { - shift @argv; - last if /^--$/; - if (/^--?(v|verbose)$/) { $verbose = 1 } - elsif (/^--?(h|help|\?)$/) { &usage } - elsif (/^--?(o|outfile)$/) { $outfile = $argv[0]; shift @argv } - elsif (/^--?(f|format|i|indent)$/) { $i = $argv[0]; shift @argv } - elsif (/^--?(n|name)$/) { $whatis_name = $argv[0];shift @argv } - elsif (/^--?(a|append)$/) { $append = 1 } - elsif (/^--?(L|locale)$/) { $locale = 1 } - else { &usage } - } - warn "Localized man directory suffix is ``$local_suffix''\n" - if $verbose && $locale; - - if ($i ne "") { - if ($i =~ /^[0-9]+$/) { - $indent = $i; - } else { - warn "Ignoring wrong indent value: ``$i''\n"; - } - } - - return &absolute_path(@argv) if $#argv >= 0; - return @defaultmanpath if $#defaultmanpath >= 0; - - warn "Missing directories\n"; &usage; -} - -# Process man directory -sub process_dir { - local($dir) = @_; - - $dir = &stripdir($dir); - &dir_redundant($dir) && &parse_dir($dir); -} - -# Process man directory and store output to file -sub process_dir_to_file { - local($dir) = @_; - - $dir = &stripdir($dir); - &dir_redundant($dir) && - &close_output(&open_output($dir) && &parse_dir($dir)); -} - -# convert locale name to short notation (ru_RU.KOI8-R -> ru.KOI8-R) -sub short_locale_name { - local($lname) = @_; - - $lname =~ s|_[A-Z][A-Z]||; - warn "short locale name is $lname\n" if $verbose && $locale; - return $lname; -} - -## -## Main -## - -&variables; -# allow colons in dir: ``makewhatis dir1:dir2:dir3'' -@argv = &parse(split(/[: ]/, join($", @ARGV))); # " - -if ($outfile) { - if(&open_output($outfile)){ - foreach $dir (@argv) { - # "Local only" flag set ? Yes ... - if ($locale) { - if ($local_suffix ne "") { - &process_dir($dir.'/'.$local_suffix); - &process_dir($dir.'/'.&short_locale_name($local_suffix)); - } - } else { - &process_dir($dir); - } - } - } - &close_output(1); -} else { - foreach $dir (@argv) { - # "Local only" flag set ? Yes ... - if ($locale) { - if ($local_suffix ne "") { - &process_dir_to_file($dir.'/'.$local_suffix); - &process_dir_to_file($dir.'/'.&short_locale_name($local_suffix)); - } - } else { - &process_dir_to_file($dir); - } - } -} - -warn "Total entries: $counter_all\n" if $verbose && ($#argv > 0 || $outfile); -exit $err; diff --git a/libexec/Makefile b/libexec/Makefile index 21fd7c645c..2d1be399ee 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -1,6 +1,6 @@ # @(#)Makefile 8.1 (Berkeley) 6/4/93 # $FreeBSD: src/libexec/Makefile,v 1.42.2.5 2002/11/12 17:32:48 obrien Exp $ -# $DragonFly: src/libexec/Makefile,v 1.6 2004/03/20 16:27:40 drhodus Exp $ +# $DragonFly: src/libexec/Makefile,v 1.7 2004/03/25 18:05:48 joerg Exp $ # Present but disabled: kpasswdd SUBDIR= atrun \ @@ -11,6 +11,7 @@ SUBDIR= atrun \ getNAME \ getty \ makekey \ + makewhatis.local \ mknetid \ pppoed \ rbootd \ diff --git a/libexec/getNAME/getNAME.1 b/libexec/getNAME/getNAME.1 index e31d4e71d4..d8da3fff3a 100644 --- a/libexec/getNAME/getNAME.1 +++ b/libexec/getNAME/getNAME.1 @@ -23,7 +23,7 @@ .\" SUCH DAMAGE. .\" .\" $FreeBSD: src/libexec/getNAME/getNAME.1,v 1.8.2.3 2001/08/16 10:44:15 ru Exp $ -.\" $DragonFly: src/libexec/getNAME/Attic/getNAME.1,v 1.2 2003/06/17 04:27:07 dillon Exp $ +.\" $DragonFly: src/libexec/getNAME/Attic/getNAME.1,v 1.3 2004/03/25 18:05:49 joerg Exp $ .Dd July 8, 1996 .Dt GETNAME 1 .Os @@ -57,7 +57,7 @@ Print type of manual page (OLD, NEW, UNKNOWN). .\" .Sh BUGS .Sh SEE ALSO .Xr apropos 1 , -.Xr makewhatis 1 , +.Xr makewhatis 8 , .Xr man 1 .Sh HISTORY The manual page for diff --git a/libexec/makewhatis.local/Makefile b/libexec/makewhatis.local/Makefile new file mode 100644 index 0000000000..c27edf8943 --- /dev/null +++ b/libexec/makewhatis.local/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD: src/usr.bin/makewhatis/Makefile,v 1.3 2002/11/18 10:11:22 ru Exp $ +# $DragonFly: src/libexec/makewhatis.local/Makefile,v 1.1 2004/03/25 18:05:49 joerg Exp $ + +SCRIPTS= makewhatis.local.sh +SCRIPTSDIR= /usr/libexec +MAN= makewhatis.local.8 +LINKS= ${SCRIPTSDIR}/makewhatis.local ${SCRIPTSDIR}/catman.local +MLINKS= makewhatis.local.8 catman.local.8 + +.include diff --git a/gnu/usr.bin/man/makewhatis/makewhatis.local.8 b/libexec/makewhatis.local/makewhatis.local.8 similarity index 88% rename from gnu/usr.bin/man/makewhatis/makewhatis.local.8 rename to libexec/makewhatis.local/makewhatis.local.8 index 9c7b703952..067a567089 100644 --- a/gnu/usr.bin/man/makewhatis/makewhatis.local.8 +++ b/libexec/makewhatis.local/makewhatis.local.8 @@ -22,8 +22,8 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/gnu/usr.bin/man/makewhatis/makewhatis.local.8,v 1.8.2.3 2001/08/16 10:14:57 ru Exp $ -.\" $DragonFly: src/gnu/usr.bin/man/makewhatis/Attic/makewhatis.local.8,v 1.2 2003/06/17 04:25:46 dillon Exp $ +.\" $FreeBSD: src/usr.bin/makewhatis/makewhatis.local.8,v 1.13 2002/07/14 15:12:00 charnier Exp $ +.\" $DragonFly: src/libexec/makewhatis.local/makewhatis.local.8,v 1.1 2004/03/25 18:05:49 joerg Exp $ .Dd April 26, 1996 .Dt MAKEWHATIS.LOCAL 8 .Os @@ -38,9 +38,10 @@ .Op options .Ar directories ... .Sh DESCRIPTION +The .Nm -start -.Xr makewhatis 1 +utility starts +.Xr makewhatis 8 only for file systems physically mounted on the system where the .Nm @@ -52,8 +53,9 @@ your NFS server -- all NFS clients start makewhatis at the same time! So use this wrapper for .Xr cron 8 instead of calling makewhatis directly. +The .Nm catman.local -is using for same purposes as +utility is using for same purposes as .Nm but for .Xr catman 1 . @@ -77,5 +79,5 @@ every week .Sh HISTORY The .Nm -command appeared in +utility appeared in .Fx 2.2 . diff --git a/gnu/usr.bin/man/makewhatis/makewhatis.local.sh b/libexec/makewhatis.local/makewhatis.local.sh similarity index 85% rename from gnu/usr.bin/man/makewhatis/makewhatis.local.sh rename to libexec/makewhatis.local/makewhatis.local.sh index 3c41c36767..f80b3c2a8d 100644 --- a/gnu/usr.bin/man/makewhatis/makewhatis.local.sh +++ b/libexec/makewhatis.local/makewhatis.local.sh @@ -24,17 +24,17 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# makewhatis.local - start makewhatis(1) only for file systems +# makewhatis.local - start makewhatis(8) only for file systems +# physically mounted on the system +# catman.local - start catman(1) only for file systems # physically mounted on the system # # Running makewhatis from /etc/periodic/weekly/320.whatis for rw nfs-mounted # /usr may kill your NFS server -- all clients start makewhatis at the same # time! So use this wrapper instead calling makewhatis directly. # -# PS: this wrapper works also for catman(1) -# -# $FreeBSD: src/gnu/usr.bin/man/makewhatis/makewhatis.local.sh,v 1.7 1999/08/27 23:36:10 peter Exp $ -# $DragonFly: src/gnu/usr.bin/man/makewhatis/Attic/makewhatis.local.sh,v 1.2 2003/06/17 04:25:46 dillon Exp $ +# $FreeBSD: src/usr.bin/makewhatis/makewhatis.local.sh,v 1.7 1999/08/27 23:36:10 peter Exp $ +# $DragonFly: src/libexec/makewhatis.local/makewhatis.local.sh,v 1.1 2004/03/25 18:05:49 joerg Exp $ PATH=/bin:/usr/bin:$PATH; export PATH opt= dirs= localdirs= diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5 index 7136f9e135..0ff83c0ede 100644 --- a/share/man/man5/rc.conf.5 +++ b/share/man/man5/rc.conf.5 @@ -23,7 +23,7 @@ .\" SUCH DAMAGE. .\" .\" $FreeBSD: src/share/man/man5/rc.conf.5,v 1.197 2003/07/28 13:56:00 mbr Exp $ -.\" $DragonFly: src/share/man/man5/rc.conf.5,v 1.5 2004/03/11 12:28:56 hmp Exp $ +.\" $DragonFly: src/share/man/man5/rc.conf.5,v 1.6 2004/03/25 18:05:49 joerg Exp $ .Dd March 3, 2002 .Dt RC.CONF 5 .Os @@ -2716,7 +2716,7 @@ has not completed within the specified time (in seconds). .Xr gdb 1 , .Xr info 1 , .Xr kbdcontrol 1 , -.Xr makewhatis 1 , +.Xr makewhatis 8 , .Xr vidcontrol 1 , .Xr ip 4 , .Xr kld 4 , diff --git a/usr.bin/Makefile b/usr.bin/Makefile index c3cca1dc68..edc00cbc25 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -1,6 +1,6 @@ # From: @(#)Makefile 8.3 (Berkeley) 1/7/94 # $FreeBSD: src/usr.bin/Makefile,v 1.144.2.17 2003/01/04 17:17:07 obrien Exp $ -# $DragonFly: src/usr.bin/Makefile,v 1.10 2004/03/20 16:27:41 drhodus Exp $ +# $DragonFly: src/usr.bin/Makefile,v 1.11 2004/03/25 18:05:48 joerg Exp $ # XXX MISSING: deroff diction graph learn plot # spell spline struct xsend @@ -21,6 +21,7 @@ SUBDIR= alias \ c89 \ calendar \ cap_mkdb \ + catman \ chat \ checknr \ checkpt \ diff --git a/usr.bin/catman/Makefile b/usr.bin/catman/Makefile new file mode 100644 index 0000000000..8d6b69535b --- /dev/null +++ b/usr.bin/catman/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD: src/usr.bin/catman/Makefile,v 1.1 2002/05/18 09:19:07 markm Exp $ +# $DragonFly: src/usr.bin/catman/Makefile,v 1.1 2004/03/25 18:05:48 joerg Exp $ + +PROG= catman + +.include diff --git a/usr.bin/catman/catman.1 b/usr.bin/catman/catman.1 new file mode 100644 index 0000000000..41b3109633 --- /dev/null +++ b/usr.bin/catman/catman.1 @@ -0,0 +1,104 @@ +.\" Copyright (c) 2002 John Rochester +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/usr.bin/catman/catman.1,v 1.3 2002/12/13 16:53:51 ru Exp $ +.\" $DragonFly: src/usr.bin/catman/catman.1,v 1.1 2004/03/25 18:05:48 joerg Exp $ +.\" +.Dd May 11, 2002 +.Dt CATMAN 1 +.Os +.Sh NAME +.Nm catman +.Nd "preformat man pages" +.Sh SYNOPSIS +.Nm +.Op Fl fnrvL +.Op Ar directories ... +.Sh DESCRIPTION +The +.Nm +utility preformats all the man pages in +.Ar directories +using the +.Nm nroff Fl man +command. +Directories may be separated by colons instead of spaces. +If no +.Ar directories +are specified, the contents of the +.Ev MANPATH +environment variable is used, or if that is not set, the default directory +.Pa /usr/share/man +is processed. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl f +Force all man pages to be reformatted even if the corresponding cat page +is newer. +.It Fl L +Process only localized subdirectories corresponding to the locale specified +in the standard environment variables. +.It Fl n +Print out what would be done instead of performing any formatting. +.It Fl r +Scan for and remove +.Dq junk +files that are neither man pages nor their +corresponding formatted cat pages. +.It Fl v +Cause +.Nm +to be more verbose about what it is doing. +.El +.Sh ENVIRONMENT +.Bl -tag -width ".Ev MANPATH" -compact +.It Ev LC_ALL , LC_CTYPE , LANG +These variables control what subdirectories will be processed if the +.Fl L +option is used. +.It Ev MANPATH +Determines the set of directories to be processed if none are given on +the command line. +.El +.Sh FILES +.Bl -tag -width ".Pa /usr/share/man" -compact +.It Pa /usr/share/man +Default directory to process if the +.Ev MANPATH +environment variable is not set. +.El +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr makewhatis 8 , +.Xr man 1 , +.Xr nroff 1 +.Sh HISTORY +A previous version of the +.Nm +command appeared in +.Fx 2.1 . +.Sh AUTHORS +.An John Rochester . diff --git a/usr.bin/catman/catman.c b/usr.bin/catman/catman.c new file mode 100644 index 0000000000..d92acb6fdf --- /dev/null +++ b/usr.bin/catman/catman.c @@ -0,0 +1,793 @@ +/*- + * Copyright (c) 2002 John Rochester + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/catman/catman.c,v 1.9 2003/06/10 02:18:00 ache Exp $ + * $DragonFly: src/usr.bin/catman/catman.c,v 1.1 2004/03/25 18:05:48 joerg Exp $ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_MANPATH "/usr/share/man" + +#define TOP_LEVEL_DIR 0 /* signifies a top-level man directory */ +#define MAN_SECTION_DIR 1 /* signifies a man section directory */ +#define UNKNOWN 2 /* signifies an unclassifiable directory */ + +#define TEST_EXISTS 0x01 +#define TEST_DIR 0x02 +#define TEST_FILE 0x04 +#define TEST_READABLE 0x08 +#define TEST_WRITABLE 0x10 +#define TEST_EXECUTABLE 0x20 + +static int verbose; /* -v flag: be verbose with warnings */ +static int pretend; /* -n, -p flags: print out what would be done + instead of actually doing it */ +static int force; /* -f flag: force overwriting all cat pages */ +static int rm_junk; /* -r flag: remove garbage pages */ +static char *locale; /* user's locale if -L is used */ +static char *lang_locale; /* short form of locale */ +static int exit_code; /* exit code to use when finished */ + +/* + * -T argument for nroff + */ +static const char *nroff_device = "ascii"; + +/* + * Mapping from locale to nroff device + */ +static const char *locale_device[] = { + "KOI8-R", "koi8-r", + "ISO8859-1", "latin1", + "ISO8859-15", "latin1", + NULL +}; + +#define BZ2_CMD "bzip2" +#define BZ2_EXT ".bz2" +#define BZ2CAT_CMD "bz" +#define GZ_CMD "gzip" +#define GZ_EXT ".gz" +#define GZCAT_CMD "z" +enum Ziptype {NONE, BZIP, GZIP}; + +static uid_t uid; +static gid_t gids[NGROUPS_MAX]; +static int ngids; +static int starting_dir; +static char tmp_file[MAXPATHLEN]; +struct stat test_st; + +/* + * A hashtable is an array of chains composed of this entry structure. + */ +struct hash_entry { + ino_t inode_number; + dev_t device_number; + const char *data; + struct hash_entry *next; +}; + +#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ +#define HASH_MASK (HASHTABLE_ALLOC - 1) + +static struct hash_entry *visited[HASHTABLE_ALLOC]; +static struct hash_entry *links[HASHTABLE_ALLOC]; + +/* + * Inserts a string into a hashtable keyed by inode & device number. + */ +static void +insert_hashtable(struct hash_entry **table, ino_t inode_number, + dev_t device_number, const char *data) +{ + struct hash_entry *new_entry; + struct hash_entry **chain; + + new_entry = malloc(sizeof(struct hash_entry)); + if (new_entry == NULL) + err(1, "can't insert into hashtable"); + chain = &table[inode_number & HASH_MASK]; + new_entry->inode_number = inode_number; + new_entry->device_number = device_number; + new_entry->data = data; + new_entry->next = *chain; + *chain = new_entry; +} + +/* + * Finds a string in a hashtable keyed by inode & device number. + */ +static const char * +find_hashtable(struct hash_entry **table, ino_t inode_number, + dev_t device_number) +{ + struct hash_entry *chain; + + chain = table[inode_number & HASH_MASK]; + while (chain != NULL) { + if (chain->inode_number == inode_number && + chain->device_number == device_number) + return(chain->data); + chain = chain->next; + } + return(NULL); +} + +static void +trap_signal(int sig __unused) +{ + if (tmp_file[0] != '\0') + unlink(tmp_file); + exit(1); +} + +/* + * Deals with junk files in the man or cat section directories. + */ +static void +junk(const char *mandir, const char *name, const char *reason) +{ + if (verbose) + fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); + if (rm_junk) { + fprintf(stderr, "rm %s/%s\n", mandir, name); + if (!pretend && unlink(name) < 0) + warn("%s/%s", mandir, name); + } +} + +/* + * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, + * and UNKNOWN for everything else. + */ +static int +directory_type(char *dir) +{ + char *p; + + for (;;) { + p = strrchr(dir, '/'); + if (p == NULL || p[1] != '\0') + break; + *p = '\0'; + } + if (p == NULL) + p = dir; + else + p++; + if (strncmp(p, "man", 3) == 0) { + p += 3; + if (*p == '\0') + return TOP_LEVEL_DIR; + while (isalnum((unsigned char)*p) || *p == '_') { + if (*++p == '\0') + return(MAN_SECTION_DIR); + } + } + return(UNKNOWN); +} + +/* + * Tests whether the given file name (without a preceding path) + * is a proper man page name (like "mk-amd-map.8.gz"). + * Only alphanumerics and '_' are allowed after the last '.' and + * the last '.' can't be the first or last characters. + */ +static int +is_manpage_name(char *name) +{ + char *lastdot = NULL; + char *n; + + for (n = name; n != '\0'; n++) { + if (isalnum(*n)) + continue; + switch (*n) { + case '_': + break; + case '-': + case '+': + case '[': + case ':': + lastdot = NULL; + break; + case '.': + lastdot = n; + break; + default: + return(0); + } + } + return(lastdot > name && lastdot + 1 < n); +} + +static int +is_bzipped(char *name) +{ + int len = strlen(name); + return(len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0); +} + +static int +is_gzipped(char *name) +{ + int len = strlen(name); + return(len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0); +} + +/* + * Converts manXXX to catXXX. + */ +static char * +get_cat_section(char *section) +{ + char *cat_section; + + cat_section = strdup(section); + strncpy(cat_section, "cat", 3); + return(cat_section); +} + +/* + * Converts .../man/manXXX to .../man. + */ +static char * +get_mandir(char *section) +{ + char *slash; + char *mandir; + + slash = strrchr(section, '/'); + mandir = malloc(slash - section + 1); + strncpy(mandir, section, slash - section); + mandir[slash - section] = '\0'; + return(mandir); +} + +/* + * Tests to see if the given directory has already been visited. + */ +static int +already_visited(char *mandir, char *dir, int count_visit) +{ + struct stat st; + + if (stat(dir, &st) < 0) { + if (mandir != NULL) + warn("%s/%s", mandir, dir); + else + warn("%s", dir); + exit_code = 1; + return(1); + } + if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) { + if (mandir != NULL) + warnx("already visited %s/%s", mandir, dir); + else + warnx("already visited %s", dir); + return(1); + } + if (count_visit) + insert_hashtable(visited, st.st_ino, st.st_dev, ""); + return(0); +} + +/* + * Returns a set of TEST_* bits describing a file's type and permissions. + * If mod_time isn't NULL, it will contain the file's modification time. + */ +static int +test_path(char *name, time_t *mod_time) +{ + int result; + + if (stat(name, &test_st) < 0) + return(0); + result = TEST_EXISTS; + if (mod_time != NULL) + *mod_time = test_st.st_mtime; + if (S_ISDIR(test_st.st_mode)) + result |= TEST_DIR; + else if (S_ISREG(test_st.st_mode)) + result |= TEST_FILE; + if (test_st.st_uid == uid) { + test_st.st_mode >>= 6; + } else { + int i; + for (i = 0; i < ngids; i++) { + if (test_st.st_gid == gids[i]) { + test_st.st_mode >>= 3; + break; + } + } + } + if (test_st.st_mode & S_IROTH) + result |= TEST_READABLE; + if (test_st.st_mode & S_IWOTH) + result |= TEST_WRITABLE; + if (test_st.st_mode & S_IXOTH) + result |= TEST_EXECUTABLE; + return(result); +} + +/* + * Checks whether a file is a symbolic link. + */ +static int +is_symlink(char *path) +{ + struct stat st; + + return(lstat(path, &st) >= 0 && S_ISLNK(st.st_mode)); +} + +/* + * Tests to see if the given directory can be written to. + */ +static void +check_writable(char *mandir) +{ + if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) + fprintf(stderr, "%s: not writable - will only be able to write " + "to existing cat directories\n", mandir); +} + +/* + * If the directory exists, attempt to make it writable, otherwise + * attempt to create it. + */ +static int +make_writable_dir(char *mandir, char *dir) +{ + int test; + + if ((test = test_path(dir, NULL)) != 0) { + if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { + warn("%s/%s: chmod", mandir, dir); + exit_code = 1; + return(0); + } + } else { + if (verbose || pretend) + fprintf(stderr, "mkdir %s\n", dir); + if (!pretend) { + unlink(dir); + if (mkdir(dir, 0755) < 0) { + warn("%s/%s: mkdir", mandir, dir); + exit_code = 1; + return(0); + } + } + } + return(1); +} + +/* + * Processes a single man page source by using nroff to create + * the preformatted cat page. + */ +static void +process_page(char *mandir, char *src, char *cat, enum Ziptype zipped) +{ + int src_test, cat_test; + time_t src_mtime, cat_mtime; + char cmd[MAXPATHLEN]; + dev_t src_dev; + ino_t src_ino; + const char *link_name; + + src_test = test_path(src, &src_mtime); + if (!(src_test & (TEST_FILE|TEST_READABLE))) { + if (!(src_test & TEST_DIR)) { + warnx("%s/%s: unreadable", mandir, src); + exit_code = 1; + if (rm_junk && is_symlink(src)) + junk(mandir, src, "bogus symlink"); + } + return; + } + src_dev = test_st.st_dev; + src_ino = test_st.st_ino; + cat_test = test_path(cat, &cat_mtime); + if (cat_test & (TEST_FILE|TEST_READABLE)) { + if (!force && cat_mtime >= src_mtime) { + if (verbose) + fprintf(stderr, "\t%s/%s: up to date\n", + mandir, src); + return; + } + } + /* + * Is the man page a link to one we've already processed? + */ + if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { + if (verbose || pretend) + fprintf(stderr, "%slink %s -> %s\n", + verbose ? "\t" : "", cat, link_name); + if (!pretend) + link(link_name, cat); + return; + } + insert_hashtable(links, src_ino, src_dev, strdup(cat)); + if (verbose || pretend) { + fprintf(stderr, "%sformat %s -> %s\n", + verbose ? "\t" : "", src, cat); + if (pretend) + return; + } + snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); + snprintf(cmd, sizeof cmd, + "%scat %s | tbl | nroff -T%s -man | col | %s > %s.tmp", + zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", + src, nroff_device, + zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", + cat); + if (system(cmd) != 0) + err(1, "formatting pipeline"); + if (rename(tmp_file, cat) < 0) + warn("%s", cat); + tmp_file[0] = '\0'; +} + +/* + * Scan the man section directory for pages and process each one, + * then check for junk in the corresponding cat section. + */ +static void +scan_section(char *mandir, char *section, char *cat_section) +{ + struct dirent **entries; + char **expected = NULL; + int npages; + int nexpected = 0; + int i, e; + enum Ziptype zipped; + char *page_name; + char page_path[MAXPATHLEN]; + char cat_path[MAXPATHLEN]; + char zip_path[MAXPATHLEN]; + + /* + * scan the man section directory for pages + */ + npages = scandir(section, &entries, NULL, alphasort); + if (npages < 0) { + warn("%s/%s", mandir, section); + exit_code = 1; + return; + } + if (verbose || rm_junk) { + /* + * Maintain a list of all cat pages that should exist, + * corresponding to existing man pages. + */ + expected = (char **) calloc(npages, sizeof(char *)); + } + for (i = 0; i < npages; free(entries[i++])) { + page_name = entries[i]->d_name; + snprintf(page_path, sizeof page_path, "%s/%s", section, + page_name); + if (!is_manpage_name(page_name)) { + if (!(test_path(page_path, NULL) & TEST_DIR)) { + junk(mandir, page_path, + "invalid man page name"); + } + continue; + } + zipped = is_bzipped(page_name) ? BZIP : + is_gzipped(page_name) ? GZIP : NONE; + if (zipped != NONE) { + snprintf(cat_path, sizeof cat_path, "%s/%s", + cat_section, page_name); + if (expected != NULL) + expected[nexpected++] = strdup(page_name); + process_page(mandir, page_path, cat_path, zipped); + } else { + /* + * We've got an uncompressed man page, + * check to see if there's a (preferred) + * compressed one. + */ + snprintf(zip_path, sizeof zip_path, "%s%s", + page_path, GZ_EXT); + if (test_path(zip_path, NULL) != 0) { + junk(mandir, page_path, + "man page unused due to existing " GZ_EXT); + } else { + if (verbose) { + fprintf(stderr, + "warning, %s is uncompressed\n", + page_path); + } + snprintf(cat_path, sizeof cat_path, "%s/%s", + cat_section, page_name); + if (expected != NULL) { + asprintf(&expected[nexpected++], + "%s", page_name); + } + process_page(mandir, page_path, cat_path, NONE); + } + } + } + free(entries); + if (expected == NULL) + return; + /* + * scan cat sections for junk + */ + npages = scandir(cat_section, &entries, NULL, alphasort); + e = 0; + for (i = 0; i < npages; free(entries[i++])) { + const char *junk_reason; + int cmp = 1; + + page_name = entries[i]->d_name; + if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) + continue; + /* + * Keep the index into the expected cat page list + * ahead of the name we've found. + */ + while (e < nexpected && + (cmp = strcmp(page_name, expected[e])) > 0) + free(expected[e++]); + if (cmp == 0) + continue; + /* we have an unexpected page */ + if (!is_manpage_name(page_name)) { + junk_reason = "invalid cat page name"; + } else if (!is_gzipped(page_name) && e + 1 < nexpected && + strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && + strlen(expected[e + 1]) == strlen(page_name) + 3) { + junk_reason = "cat page unused due to existing " GZ_EXT; + } else + junk_reason = "cat page without man page"; + snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, + page_name); + junk(mandir, cat_path, junk_reason); + } + free(entries); + while (e < nexpected) + free(expected[e++]); + free(expected); +} + + +/* + * Processes a single man section. + */ +static void +process_section(char *mandir, char *section) +{ + char *cat_section; + + if (already_visited(mandir, section, 1)) + return; + if (verbose) + fprintf(stderr, " section %s\n", section); + cat_section = get_cat_section(section); + if (make_writable_dir(mandir, cat_section)) + scan_section(mandir, section, cat_section); +} + +static int +select_sections(struct dirent *entry) +{ + return(directory_type(entry->d_name) == MAN_SECTION_DIR); +} + +/* + * Processes a single top-level man directory. If section isn't NULL, + * it will only process that section sub-directory, otherwise it will + * process all of them. + */ +static void +process_mandir(char *dir_name, char *section) +{ + fchdir(starting_dir); + if (already_visited(NULL, dir_name, section == NULL)) + return; + check_writable(dir_name); + if (verbose) + fprintf(stderr, "man directory %s\n", dir_name); + if (pretend) + fprintf(stderr, "cd %s\n", dir_name); + if (chdir(dir_name) < 0) { + warn("%s: chdir", dir_name); + exit_code = 1; + return; + } + if (section != NULL) { + process_section(dir_name, section); + } else { + struct dirent **entries; + int nsections; + int i; + + nsections = scandir(".", &entries, select_sections, alphasort); + if (nsections < 0) { + warn("%s", dir_name); + exit_code = 1; + return; + } + for (i = 0; i < nsections; i++) { + process_section(dir_name, entries[i]->d_name); + free(entries[i]); + } + free(entries); + } +} + +/* + * Processes one argument, which may be a colon-separated list of + * directories. + */ +static void +process_argument(const char *arg) +{ + char *dir; + char *mandir; + char *parg; + + parg = strdup(arg); + if (parg == NULL) + err(1, "out of memory"); + while ((dir = strsep(&parg, ":")) != NULL) { + switch (directory_type(dir)) { + case TOP_LEVEL_DIR: + if (locale != NULL) { + asprintf(&mandir, "%s/%s", dir, locale); + process_mandir(mandir, NULL); + free(mandir); + if (lang_locale != NULL) { + asprintf(&mandir, "%s/%s", dir, + lang_locale); + process_mandir(mandir, NULL); + free(mandir); + } + } else { + process_mandir(dir, NULL); + } + break; + case MAN_SECTION_DIR: { + mandir = get_mandir(dir); + process_mandir(mandir, dir); + break; + } + default: + warnx("%s: directory name not in proper man form", dir); + exit_code = 1; + } + } + free(parg); +} + +static void +determine_locale(void) +{ + char *sep; + + if ((locale = setlocale(LC_CTYPE, "")) == NULL) { + warnx("-L option used, but no locale found\n"); + return; + } + sep = strchr(locale, '_'); + if (sep != NULL && isupper(sep[1]) && isupper(sep[2])) + asprintf(&lang_locale, "%.*s%s", sep - locale, locale, &sep[3]); + sep = nl_langinfo(CODESET); + if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) { + int i; + + for (i = 0; locale_device[i] != NULL; i += 2) { + if (strcmp(sep, locale_device[i]) == 0) { + nroff_device = locale_device[i + 1]; + break; + } + } + } + if (verbose) { + if (lang_locale != NULL) + fprintf(stderr, "short locale is %s\n", lang_locale); + fprintf(stderr, "nroff device is %s\n", nroff_device); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-fLnrv] [directories...]\n", getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + int opt; + + if ((uid = getuid()) == 0) { + fprintf(stderr, "don't run %s as root, use:\n echo", argv[0]); + for (optind = 0; optind < argc; optind++) + fprintf(stderr, " %s", argv[optind]); + fprintf(stderr, " | nice -5 su -m man\n"); + exit(1); + } + while ((opt = getopt(argc, argv, "vnfLrh")) != -1) { + switch (opt) { + case 'f': + force++; + break; + case 'L': + determine_locale(); + break; + case 'n': + pretend++; + break; + case 'r': + rm_junk++; + break; + case 'v': + verbose++; + break; + default: + usage(); + /* NOTREACHED */ + } + } + ngids = getgroups(NGROUPS_MAX, gids); + if ((starting_dir = open(".", 0)) < 0) + err(1, "."); + umask(022); + signal(SIGINT, trap_signal); + signal(SIGHUP, trap_signal); + signal(SIGQUIT, trap_signal); + signal(SIGTERM, trap_signal); + if (optind == argc) { + const char *manpath = getenv("MANPATH"); + if (manpath == NULL) + manpath = DEFAULT_MANPATH; + process_argument(manpath); + } else { + while (optind < argc) + process_argument(argv[optind++]); + } + exit(exit_code); +} diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index de04bfb752..934913b061 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -1,6 +1,6 @@ # From: @(#)Makefile 5.20 (Berkeley) 6/12/93 # $FreeBSD: src/usr.sbin/Makefile,v 1.183.2.14 2003/04/16 11:01:51 ru Exp $ -# $DragonFly: src/usr.sbin/Makefile,v 1.6 2004/01/20 21:32:34 dillon Exp $ +# $DragonFly: src/usr.sbin/Makefile,v 1.7 2004/03/25 18:05:48 joerg Exp $ # XXX MISSING: mkproto SUBDIR= IPXrouted \ @@ -41,6 +41,7 @@ SUBDIR= IPXrouted \ keyserv \ lastlogin \ mailwrapper \ + makewhatis \ manctl \ memcontrol \ mergemaster \ diff --git a/usr.sbin/makewhatis/Makefile b/usr.sbin/makewhatis/Makefile new file mode 100644 index 0000000000..ed263ea315 --- /dev/null +++ b/usr.sbin/makewhatis/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD: src/usr.bin/makewhatis/Makefile,v 1.3 2002/11/18 10:11:22 ru Exp $ +# $DragonFly: src/usr.sbin/makewhatis/Makefile,v 1.1 2004/03/25 18:05:48 joerg Exp $ + +PROG= makewhatis +DPADD= ${LIBZ} +LDADD= -lz +MAN= makewhatis.8 + +.include diff --git a/usr.sbin/makewhatis/makewhatis.8 b/usr.sbin/makewhatis/makewhatis.8 new file mode 100644 index 0000000000..977c4d9fb5 --- /dev/null +++ b/usr.sbin/makewhatis/makewhatis.8 @@ -0,0 +1,134 @@ +.\" Copyright (c) 2002 John Rochester +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/usr.bin/makewhatis/makewhatis.1,v 1.3 2002/05/18 15:39:56 ru Exp $ +.\" $DragonFly: src/usr.sbin/makewhatis/makewhatis.8,v 1.1 2004/03/25 18:05:48 joerg Exp $ +.\" +.Dd May 12, 2002 +.Dt MAKEWHATIS 8 +.Os +.Sh NAME +.Nm makewhatis +.Nd "create whatis database" +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl i Ar column +.Op Fl n Ar name +.Op Fl o Ar file +.Op Fl v +.Op Fl L +.Op Ar directories ... +.Sh DESCRIPTION +The +.Nm +utility collects the names and short descriptions from all the unformatted +man pages in the +.Ar directories +and puts them into a file used by the +.Xr whatis 1 +and +.Xr apropos 1 +commands. +Directories may be separated by colons instead of spaces. +If no +.Ar directories +are specified, the contents of the +.Ev MANPATH +environment variable will be used, or if that is not set, the default directory +.Pa /usr/share/man +will be processed. +.Pp +The options are as follows: +.Bl -tag -width ".Fl i Ar column" +.It Fl a +Appends to the output file(s) instead of replacing them. +The output +will be sorted with duplicate lines removed, but may have obsolete +entries. +.It Fl i Ar column +Indents the description by +.Ar column +characters. +The default value is 24. +.It Fl n Ar name +Uses +.Ar name +instead of +.Pa whatis . +.It Fl o Ar file +Outputs all lines to the +.Ar file +instead of +.Pa */man/whatis . +.It Fl v +Makes +.Nm +more verbose about what it is doing. +.It Fl L +Process only localized subdirectories corresponding to the locale specified +in the standard environment variables. +.El +.Sh ENVIRONMENT +.Bl -tag -width ".Ev MANPATH" +.It Ev LC_ALL , LC_CTYPE , LANG +These variables control what subdirectories will be processed if the +.Fl L +option is used. +.It Ev MACHINE +If set, its value is used to override the current +machine type when searching machine specific subdirectories. +.It Ev MANPATH +Determines the set of directories to be processed if none are given on +the command line. +.El +.Sh FILES +.Bl -tag -width ".Pa /usr/share/man" -compact +.It Pa /usr/share/man +Default directory to process if the +.Ev MANPATH +environment variable is not set. +.It Pa */man/whatis +The default output file. +.El +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr apropos 1 , +.Xr whatis 1 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 2.1 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was originally written in Perl and was contributed by +.An Wolfram Schneider . +The current version of +.Nm +was rewritten in C by +.An John Rochester . diff --git a/usr.sbin/makewhatis/makewhatis.c b/usr.sbin/makewhatis/makewhatis.c new file mode 100644 index 0000000000..7e0c9afddf --- /dev/null +++ b/usr.sbin/makewhatis/makewhatis.c @@ -0,0 +1,1041 @@ +/*- + * Copyright (c) 2002 John Rochester + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/makewhatis/makewhatis.c,v 1.9 2002/09/04 23:29:04 dwmalone Exp $ + * $DragonFly: src/usr.sbin/makewhatis/makewhatis.c,v 1.1 2004/03/25 18:05:48 joerg Exp $ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_MANPATH "/usr/share/man" +#define LINE_ALLOC 4096 + +static char blank[] = ""; + +/* + * Information collected about each man page in a section. + */ +struct page_info { + char * filename; + char * name; + char * suffix; + int gzipped; + ino_t inode; +}; + +/* + * An entry kept for each visited directory. + */ +struct visited_dir { + dev_t device; + ino_t inode; + SLIST_ENTRY(visited_dir) next; +}; + +/* + * an expanding string + */ +struct sbuf { + char * content; /* the start of the buffer */ + char * end; /* just past the end of the content */ + char * last; /* the last allocated character */ +}; + +/* + * Removes the last amount characters from the sbuf. + */ +#define sbuf_retract(sbuf, amount) \ + ((sbuf)->end -= (amount)) +/* + * Returns the length of the sbuf content. + */ +#define sbuf_length(sbuf) \ + ((sbuf)->end - (sbuf)->content) + +typedef char *edited_copy(char *from, char *to, int length); + +static int append; /* -a flag: append to existing whatis */ +static int verbose; /* -v flag: be verbose with warnings */ +static int indent = 24; /* -i option: description indentation */ +static const char *whatis_name="whatis";/* -n option: the name */ +static char *common_output; /* -o option: the single output file */ +static char *locale; /* user's locale if -L is used */ +static char *lang_locale; /* short form of locale */ +static const char *machine; + +static int exit_code; /* exit code to use when finished */ +static SLIST_HEAD(, visited_dir) visited_dirs = + SLIST_HEAD_INITIALIZER(visited_dirs); + +/* + * While the whatis line is being formed, it is stored in whatis_proto. + * When finished, it is reformatted into whatis_final and then appended + * to whatis_lines. + */ +static struct sbuf *whatis_proto; +static struct sbuf *whatis_final; +static StringList *whatis_lines; /* collected output lines */ + +static char tmp_file[MAXPATHLEN]; /* path of temporary file, if any */ + +/* A set of possible names for the NAME man page section */ +static const char *name_section_titles[] = { + "NAME", "Name", "NAMN", "BEZEICHNUNG", "\xcc\xbe\xbe\xce", + "\xee\xe1\xfa\xf7\xe1\xee\xe9\xe5", NULL +}; + +/* A subset of the mdoc(7) commands to ignore */ +static char mdoc_commands[] = "ArDvErEvFlLiNmPa"; + +/* + * Frees a struct page_info and its content. + */ +static void +free_page_info(struct page_info *info) +{ + free(info->filename); + free(info->name); + free(info->suffix); + free(info); +} + +/* + * Allocates and fills in a new struct page_info given the + * name of the man section directory and the dirent of the file. + * If the file is not a man page, returns NULL. + */ +static struct page_info * +new_page_info(char *dir, struct dirent *dirent) +{ + struct page_info *info; + int basename_length; + char *suffix; + struct stat st; + + info = malloc(sizeof(struct page_info)); + if (info == NULL) + err(1, "malloc"); + basename_length = strlen(dirent->d_name); + suffix = &dirent->d_name[basename_length]; + asprintf(&info->filename, "%s/%s", dir, dirent->d_name); + if ((info->gzipped = basename_length >= 4 && + strcmp(&dirent->d_name[basename_length - 3], ".gz") == 0)) { + suffix -= 3; + *suffix = '\0'; + } + for (;;) { + if (--suffix == dirent->d_name || !isalnum(*suffix)) { + if (*suffix == '.') + break; + if (verbose) + warnx("%s: invalid man page name", + info->filename); + free(info->filename); + free(info); + return(NULL); + } + } + *suffix++ = '\0'; + info->name = strdup(dirent->d_name); + info->suffix = strdup(suffix); + if (stat(info->filename, &st) < 0) { + warn("%s", info->filename); + free_page_info(info); + return(NULL); + } + if (!S_ISREG(st.st_mode)) { + if (verbose && !S_ISDIR(st.st_mode)) + warnx("%s: not a regular file", info->filename); + free_page_info(info); + return(NULL); + } + info->inode = st.st_ino; + return(info); +} + +/* + * Reset an sbuf's length to 0. + */ +static void +sbuf_clear(struct sbuf *sbuf) +{ + sbuf->end = sbuf->content; +} + +/* + * Allocate a new sbuf. + */ +static struct sbuf * +new_sbuf(void) +{ + struct sbuf *sbuf = (struct sbuf *) malloc(sizeof(struct sbuf)); + sbuf->content = malloc(LINE_ALLOC); + sbuf->last = sbuf->content + LINE_ALLOC - 1; + sbuf_clear(sbuf); + return(sbuf); +} + +/* + * Ensure that there is enough room in the sbuf for nchars more characters. + */ +static void +sbuf_need(struct sbuf *sbuf, int nchars) +{ + char *new_content; + size_t size, cntsize; + + /* double the size of the allocation until the buffer is big enough */ + while (sbuf->end + nchars > sbuf->last) { + size = sbuf->last + 1 - sbuf->content; + size *= 2; + cntsize = sbuf->end - sbuf->content; + + new_content = malloc(size); + memcpy(new_content, sbuf->content, cntsize); + free(sbuf->content); + sbuf->content = new_content; + sbuf->end = new_content + cntsize; + sbuf->last = new_content + size - 1; + } +} + +/* + * Appends a string of a given length to the sbuf. + */ +static void +sbuf_append(struct sbuf *sbuf, const char *text, int length) +{ + if (length > 0) { + sbuf_need(sbuf, length); + memcpy(sbuf->end, text, length); + sbuf->end += length; + } +} + +/* + * Appends a null-terminated string to the sbuf. + */ +static void +sbuf_append_str(struct sbuf *sbuf, char *text) +{ + sbuf_append(sbuf, text, strlen(text)); +} + +/* + * Appends an edited null-terminated string to the sbuf. + */ +static void +sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy) +{ + int length = strlen(text); + if (length > 0) { + sbuf_need(sbuf, length); + sbuf->end = copy(text, sbuf->end, length); + } +} + +/* + * Strips any of a set of chars from the end of the sbuf. + */ +static void +sbuf_strip(struct sbuf *sbuf, const char *set) +{ + while (sbuf->end > sbuf->content && strchr(set, sbuf->end[-1]) != NULL) + sbuf->end--; +} + +/* + * Returns the null-terminated string built by the sbuf. + */ +static char * +sbuf_content(struct sbuf *sbuf) +{ + *sbuf->end = '\0'; + return(sbuf->content); +} + +/* + * Returns true if no man page exists in the directory with + * any of the names in the StringList. + */ +static int +no_page_exists(char *dir, StringList *names, char *suffix) +{ + char path[MAXPATHLEN]; + size_t i; + + for (i = 0; i < names->sl_cur; i++) { + snprintf(path, sizeof path, "%s/%s.%s.gz", dir, names->sl_str[i], suffix); + if (access(path, F_OK) < 0) { + path[strlen(path) - 3] = '\0'; + if (access(path, F_OK) < 0) + continue; + } + return(0); + } + return(1); +} + +static void +trap_signal(int sig __unused) +{ + if (tmp_file[0] != '\0') + unlink(tmp_file); + exit(1); +} + +/* + * Attempts to open an output file. Returns NULL if unsuccessful. + */ +static FILE * +open_output(char *name) +{ + FILE *output; + + whatis_lines = sl_init(); + if (append) { + char line[LINE_ALLOC]; + + output = fopen(name, "r"); + if (output == NULL) { + warn("%s", name); + exit_code = 1; + return(NULL); + } + while (fgets(line, sizeof line, output) != NULL) { + line[strlen(line) - 1] = '\0'; + sl_add(whatis_lines, strdup(line)); + } + } + if (common_output == NULL) { + snprintf(tmp_file, sizeof tmp_file, "%s.tmp", name); + name = tmp_file; + } + output = fopen(name, "w"); + if (output == NULL) { + warn("%s", name); + exit_code = 1; + return(NULL); + } + return(output); +} + +static int +linesort(const void *a, const void *b) +{ + return(strcmp((*(const char * const *)a), (*(const char * const *)b))); +} + +/* + * Writes the unique sorted lines to the output file. + */ +static void +finish_output(FILE *output, char *name) +{ + size_t i; + char *prev = NULL; + + qsort(whatis_lines->sl_str, whatis_lines->sl_cur, sizeof(char *), + linesort); + for (i = 0; i < whatis_lines->sl_cur; i++) { + char *line = whatis_lines->sl_str[i]; + if (i > 0 && strcmp(line, prev) == 0) + continue; + prev = line; + fputs(line, output); + putc('\n', output); + } + fclose(output); + sl_free(whatis_lines, 1); + if (common_output == NULL) { + rename(tmp_file, name); + unlink(tmp_file); + } +} + +static FILE * +open_whatis(char *mandir) +{ + char filename[MAXPATHLEN]; + + snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); + return(open_output(filename)); +} + +static void +finish_whatis(FILE *output, char *mandir) +{ + char filename[MAXPATHLEN]; + + snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); + finish_output(output, filename); +} + +/* + * Tests to see if the given directory has already been visited. + */ +static int +already_visited(char *dir) +{ + struct stat st; + struct visited_dir *visit; + + if (stat(dir, &st) < 0) { + warn("%s", dir); + exit_code = 1; + return(1); + } + SLIST_FOREACH(visit, &visited_dirs, next) { + if (visit->inode == st.st_ino && + visit->device == st.st_dev) { + warnx("already visited %s", dir); + return(1); + } + } + visit = (struct visited_dir *) malloc(sizeof(struct visited_dir)); + visit->device = st.st_dev; + visit->inode = st.st_ino; + SLIST_INSERT_HEAD(&visited_dirs, visit, next); + return(0); +} + +/* + * Removes trailing spaces from a string, returning a pointer to just + * beyond the new last character. + */ +static char * +trim_rhs(char *str) +{ + char *rhs = &str[strlen(str)]; + while (--rhs > str && isspace(*rhs)) + ; + *++rhs = '\0'; + return(rhs); +} + +/* + * Returns a pointer to the next non-space character in the string. + */ +static char * +skip_spaces(char *s) +{ + while (*s != '\0' && isspace(*s)) + s++; + return(s); +} + +/* + * Returns whether the string contains only digits. + */ +static int +only_digits(char *line) +{ + if (!isdigit(*line++)) + return(0); + while (isdigit(*line)) + line++; + return(*line == '\0'); +} + +/* + * Returns whether the line is of one of the forms: + * .Sh NAME + * .Sh "NAME" + * etc. + * assuming that section_start is ".Sh". + */ +static int +name_section_line(char *line, const char *section_start) +{ + char *rhs; + const char **title; + + if (strncmp(line, section_start, 3) != 0) + return(0); + line = skip_spaces(line + 3); + rhs = trim_rhs(line); + if (*line == '"') { + line++; + if (*--rhs == '"') + *rhs = '\0'; + } + for (title = name_section_titles; *title != NULL; title++) + if (strcmp(*title, line) == 0) + return(1); + return(0); +} + +/* + * Copies characters while removing the most common nroff/troff + * markup: + * \(em, \(mi, \s[+-N], \& + * \fF, \f(fo, \f[font] + * \*s, \*(st, \*[stringvar] + */ +static char * +de_nroff_copy(char *from, char *to, int fromlen) +{ + char *from_end = &from[fromlen]; + while (from < from_end) { + switch (*from) { + case '\\': + switch (*++from) { + case '(': + if (strncmp(&from[1], "em", 2) == 0 || + strncmp(&from[1], "mi", 2) == 0) { + from += 3; + continue; + } + break; + case 's': + if (*++from == '-') + from++; + while (isdigit(*from)) + from++; + continue; + case 'f': + case '*': + if (*++from == '(') + from += 3; + else if (*from == '[') { + while (*++from != ']' && from < from_end) + ; + from++; + } else + from++; + continue; + case '&': + from++; + continue; + } + break; + } + *to++ = *from++; + } + return(to); +} + +/* + * Appends a string with the nroff formatting removed. + */ +static void +add_nroff(char *text) +{ + sbuf_append_edited(whatis_proto, text, de_nroff_copy); +} + +/* + * Appends "name(suffix), " to whatis_final. + */ +static void +add_whatis_name(char *name, char *suffix) +{ + if (*name != '\0') { + sbuf_append_str(whatis_final, name); + sbuf_append(whatis_final, "(", 1); + sbuf_append_str(whatis_final, suffix); + sbuf_append(whatis_final, "), ", 3); + } +} + +/* + * Processes an old-style man(7) line. This ignores commands with only + * a single number argument. + */ +static void +process_man_line(char *line) +{ + if (*line == '.') { + while (isalpha(*++line)) + ; + line = skip_spaces(line); + if (only_digits(line)) + return; + } else + line = skip_spaces(line); + if (*line != '\0') { + add_nroff(line); + sbuf_append(whatis_proto, " ", 1); + } +} + +/* + * Processes a new-style mdoc(7) line. + */ +static void +process_mdoc_line(char *line) +{ + int xref; + int arg = 0; + char *line_end = &line[strlen(line)]; + int orig_length = sbuf_length(whatis_proto); + char *next; + + if (*line == '\0') + return; + if (line[0] != '.' || !isupper(line[1]) || !islower(line[2])) { + add_nroff(skip_spaces(line)); + sbuf_append(whatis_proto, " ", 1); + return; + } + xref = strncmp(line, ".Xr", 3) == 0; + line += 3; + while ((line = skip_spaces(line)) < line_end) { + if (*line == '"') { + next = ++line; + for (;;) { + next = strchr(next, '"'); + if (next == NULL) + break; + memmove(next, next + 1, strlen(next)); + line_end--; + if (*next != '"') + break; + next++; + } + } else + next = strpbrk(line, " \t"); + if (next != NULL) + *next++ = '\0'; + else + next = line_end; + if (isupper(*line) && islower(line[1]) && line[2] == '\0') { + if (strcmp(line, "Ns") == 0) { + arg = 0; + line = next; + continue; + } + if (strstr(mdoc_commands, line) != NULL) { + line = next; + continue; + } + } + if (arg > 0 && strchr(",.:;?!)]", *line) == 0) { + if (xref) { + sbuf_append(whatis_proto, "(", 1); + add_nroff(line); + sbuf_append(whatis_proto, ")", 1); + xref = 0; + line = blank; + } else + sbuf_append(whatis_proto, " ", 1); + } + add_nroff(line); + arg++; + line = next; + } + if (sbuf_length(whatis_proto) > orig_length) + sbuf_append(whatis_proto, " ", 1); +} + +/* + * Collects a list of comma-separated names from the text. + */ +static void +collect_names(StringList *names, char *text) +{ + char *arg; + + for (;;) { + arg = text; + text = strchr(text, ','); + if (text != NULL) + *text++ = '\0'; + sl_add(names, arg); + if (text == NULL) + return; + if (*text == ' ') + text++; + } +} + +enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC }; + +/* + * Processes a man page source into a single whatis line and adds it + * to whatis_lines. + */ +static void +process_page(struct page_info *page, char *section_dir) +{ + gzFile *in; + char buffer[4096]; + char *line; + StringList *names; + char *descr; + int state = STATE_UNKNOWN; + size_t i; + + sbuf_clear(whatis_proto); + if ((in = gzopen(page->filename, "r")) == NULL) { + warn("%s", page->filename); + exit_code = 1; + return; + } + while (gzgets(in, buffer, sizeof buffer) != NULL) { + line = buffer; + if (strncmp(line, ".\\\"", 3) == 0) /* ignore comments */ + continue; + switch (state) { + /* + * haven't reached the NAME section yet. + */ + case STATE_UNKNOWN: + if (name_section_line(line, ".SH")) + state = STATE_MANSTYLE; + else if (name_section_line(line, ".Sh")) + state = STATE_MDOCNAME; + continue; + /* + * Inside an old-style .SH NAME section. + */ + case STATE_MANSTYLE: + if (strncmp(line, ".SH", 3) == 0) + break; + trim_rhs(line); + if (strcmp(line, ".") == 0) + continue; + if (strncmp(line, ".IX", 3) == 0) { + line += 3; + line = skip_spaces(line); + } + process_man_line(line); + continue; + /* + * Inside a new-style .Sh NAME section (the .Nm part). + */ + case STATE_MDOCNAME: + trim_rhs(line); + if (strncmp(line, ".Nm", 3) == 0) { + process_mdoc_line(line); + continue; + } else { + if (strcmp(line, ".") == 0) + continue; + sbuf_append(whatis_proto, "- ", 2); + state = STATE_MDOCDESC; + } + /* fall through */ + /* + * Inside a new-style .Sh NAME section (after the .Nm-s). + */ + case STATE_MDOCDESC: + if (strncmp(line, ".Sh", 3) == 0) + break; + trim_rhs(line); + if (strcmp(line, ".") == 0) + continue; + process_mdoc_line(line); + continue; + } + break; + } + gzclose(in); + sbuf_strip(whatis_proto, " \t.-"); + line = sbuf_content(whatis_proto); + /* + * line now contains the appropriate data, but without + * the proper indentation or the section appended to each name. + */ + descr = strstr(line, " - "); + if (descr == NULL) { + descr = strchr(line, ' '); + if (descr == NULL) { + if (verbose) + fprintf(stderr, + "\tignoring junk description \"%s\"\n", + line); + return; + } + *descr++ = '\0'; + } else { + *descr = '\0'; + descr += 3; + } + names = sl_init(); + collect_names(names, line); + sbuf_clear(whatis_final); + if (!sl_find(names, page->name) && + no_page_exists(section_dir, names, page->suffix)) { + /* + * Add the page name since that's the only thing that + * man(1) will find. + */ + add_whatis_name(page->name, page->suffix); + } + for (i = 0; i < names->sl_cur; i++) + add_whatis_name(names->sl_str[i], page->suffix); + sl_free(names, 0); + sbuf_retract(whatis_final, 2); /* remove last ", " */ + while (sbuf_length(whatis_final) < indent) + sbuf_append(whatis_final, " ", 1); + sbuf_append(whatis_final, " - ", 3); + sbuf_append_str(whatis_final, skip_spaces(descr)); + sl_add(whatis_lines, strdup(sbuf_content(whatis_final))); +} + +/* + * Sorts pages first by inode number, then by name. + */ +static int +pagesort(const void *a, const void *b) +{ + const struct page_info *p1 = *(struct page_info * const *) a; + const struct page_info *p2 = *(struct page_info * const *) b; + if (p1->inode == p2->inode) + return(strcmp(p1->name, p2->name)); + return(p1->inode - p2->inode); +} + +/* + * Processes a single man section. + */ +static void +process_section(char *section_dir) +{ + struct dirent **entries; + int nentries; + struct page_info **pages; + int npages = 0; + int i; + ino_t prev_inode = 0; + + if (verbose) + fprintf(stderr, " %s\n", section_dir); + + /* + * scan the man section directory for pages + */ + nentries = scandir(section_dir, &entries, NULL, alphasort); + if (nentries < 0) { + warn("%s", section_dir); + exit_code = 1; + return; + } + /* + * collect information about man pages + */ + pages = calloc(nentries, sizeof(struct page_info *)); + for (i = 0; i < nentries; i++) { + struct page_info *info = new_page_info(section_dir, entries[i]); + if (info != NULL) + pages[npages++] = info; + free(entries[i]); + } + free(entries); + qsort(pages, npages, sizeof(struct page_info *), pagesort); + /* + * process each unique page + */ + for (i = 0; i < npages; i++) { + struct page_info *page = pages[i]; + if (page->inode != prev_inode) { + prev_inode = page->inode; + if (verbose) + fprintf(stderr, "\treading %s\n", + page->filename); + process_page(page, section_dir); + } else if (verbose) + fprintf(stderr, "\tskipping %s, duplicate\n", + page->filename); + free_page_info(page); + } + free(pages); +} + +/* + * Returns whether the directory entry is a man page section. + */ +static int +select_sections(struct dirent *entry) +{ + char *p = &entry->d_name[3]; + + if (strncmp(entry->d_name, "man", 3) != 0) + return(0); + while (*p != '\0') { + if (!isalnum(*p++)) + return(0); + } + return(1); +} + +/* + * Processes a single top-level man directory by finding all the + * sub-directories named man* and processing each one in turn. + */ +static void +process_mandir(char *dir_name) +{ + struct dirent **entries; + int nsections; + FILE *fp = NULL; + int i; + struct stat st; + + if (already_visited(dir_name)) + return; + if (verbose) + fprintf(stderr, "man directory %s\n", dir_name); + nsections = scandir(dir_name, &entries, select_sections, alphasort); + if (nsections < 0) { + warn("%s", dir_name); + exit_code = 1; + return; + } + if (common_output == NULL && (fp = open_whatis(dir_name)) == NULL) + return; + for (i = 0; i < nsections; i++) { + char section_dir[MAXPATHLEN]; + snprintf(section_dir, sizeof section_dir, "%s/%s", dir_name, + entries[i]->d_name); + process_section(section_dir); + snprintf(section_dir, sizeof section_dir, "%s/%s/%s", dir_name, + entries[i]->d_name, machine); + if (stat(section_dir, &st) == 0 && S_ISDIR(st.st_mode)) + process_section(section_dir); + free(entries[i]); + } + free(entries); + if (common_output == NULL) + finish_whatis(fp, dir_name); +} + +/* + * Processes one argument, which may be a colon-separated list of + * directories. + */ +static void +process_argument(const char *arg) +{ + char *dir; + char *mandir; + char *parg; + + parg = strdup(arg); + if (parg == NULL) + err(1, "out of memory"); + while ((dir = strsep(&parg, ":")) != NULL) { + if (locale != NULL) { + asprintf(&mandir, "%s/%s", dir, locale); + process_mandir(mandir); + free(mandir); + if (lang_locale != NULL) { + asprintf(&mandir, "%s/%s", dir, lang_locale); + process_mandir(mandir); + free(mandir); + } + } else { + process_mandir(dir); + } + } + free(parg); +} + + +int +main(int argc, char **argv) +{ + int opt; + FILE *fp = NULL; + + while ((opt = getopt(argc, argv, "ai:n:o:vL")) != -1) { + switch (opt) { + case 'a': + append++; + break; + case 'i': + indent = atoi(optarg); + break; + case 'n': + whatis_name = optarg; + break; + case 'o': + common_output = optarg; + break; + case 'v': + verbose++; + break; + case 'L': + locale = getenv("LC_ALL"); + if (locale == NULL) + locale = getenv("LC_CTYPE"); + if (locale == NULL) + locale = getenv("LANG"); + if (locale != NULL) { + char *sep = strchr(locale, '_'); + if (sep != NULL && isupper(sep[1]) && + isupper(sep[2])) { + asprintf(&lang_locale, "%.*s%s", sep - locale, locale, &sep[3]); + } + } + break; + default: + fprintf(stderr, "usage: %s [-a] [-i indent] [-n name] [-o output_file] [-v] [-L] [directories...]\n", argv[0]); + exit(1); + } + } + + signal(SIGINT, trap_signal); + signal(SIGHUP, trap_signal); + signal(SIGQUIT, trap_signal); + signal(SIGTERM, trap_signal); + SLIST_INIT(&visited_dirs); + whatis_proto = new_sbuf(); + whatis_final = new_sbuf(); + + if ((machine = getenv("MACHINE")) == NULL) + machine = MACHINE; + + if (common_output != NULL && (fp = open_output(common_output)) == NULL) + err(1, "%s", common_output); + if (optind == argc) { + const char *manpath = getenv("MANPATH"); + if (manpath == NULL) + manpath = DEFAULT_MANPATH; + process_argument(manpath); + } else { + while (optind < argc) + process_argument(argv[optind++]); + } + if (common_output != NULL) + finish_output(fp, common_output); + exit(exit_code); +} -- 2.41.0