#!/usr/bin/perl -w # $Id: linux-help,v 1.16 1999/11/08 21:49:54 root Exp root $ # Copyright (c) Mark Summerfield 1999. All Rights Reserved. # May be used/distributed under the GPL. # WARNING - linux-help is provided as an example of QuickForm use, (although # I now use it instead of dwww), and it may not be secure. # NOTE - linux-help has hard-coded paths for a Debian Linux system - your # paths may/will differ. See "CHANGE THESE FOR YOUR LOCAL SYSTEM" for the # paths that you will need to change. linux-help itself should be placed in # your cgi-bin directory and made executable. # linux-help has only ever been run as root on a local system - multi-user use # is not supported (since I do no record locking), in other words its only for # a single person to use. This program also uses the undocumented colour() # function from QuickForm. (This is one of CGI::QuickForm's example programs.) # TODO Configure option, e.g. to add/del paths to/from $Data{"$PREFIX PATH"} # so that hard-coding is not necessary # TODO Cache page searches, i.e. cache lists of links? # TODO Keyword searching use strict ; use CGI qw( :standard :html3 ) ; use CGI::QuickForm qw( show_form colour ) ; use DB_File ; use Fcntl ; # For DB_File constants. use File::Find ; use HTML::Entities ; use Storable qw( freeze thaw ) ; use URI::Escape ; use vars qw( $VERSION ) ; $VERSION = '1.01' ; use vars qw( $URL $PREDEFINED_PAGES ) ; ################### CHANGE THESE FOR YOUR LOCAL SYSTEM ############## # This is the db file where linux-help stores its configuration info - it must # be rw by linux-help running as a CGI script. my $DB_FILE = '/root/web/db/linux-help.db' ; # Change these to reflect your local situation; multiple paths are supported # separated by colons in the usual way. my $PATH_DOC = "/usr/doc" ; my $PATH_INFO = "/usr/info" ; my $PATH_MAN = "/usr/man:/usr/X11R6/man" ; my $PATH_POD = "/usr/doc/perl5:/usr/doc:/usr/lib/perl5:/root/lib" ; # This must be rw by this script. my $TMP = '/tmp' ; ################### END OF LOCAL CHANGES ############################ $| = 1 ; # Autoflush. my $TITLE = 'Linux Help' ; my $SEARCH = 'Search' ; my $NEW_SEARCH = 'New Search' ; my $NEW_TERM = 'New Term' ; my $PREV_TERM = 'Prev Term' ; my $PREV_PAGE = 'Prev Page' ; my $NEW_KEYWORD = 'New Keyword' ; my $SEARCH_PAGES = 'Search Pages' ; my $SEARCH_FOR = 'Search/Show' ; my $IGNORE_CASE = 'Ignore Case' ; my $DEL_PAGE = 'Del Page' ; my $DEL_PAGES = 'Del Pages' ; my $DEL_TERM = 'Del Term' ; my $DEL_TERMS = 'Del Terms' ; my $PREFIX = "\x01" ; my $SEARCH_TERMS = "$PREFIX SEARCH_TERMS" ; my $SHOWN = "$PREFIX SHOWN" ; my %Data ; my %Show ; my %SearchTerm ; # key is the term, value is the number of times used my @Term ; # Array of terms to be matched my $CaseSensitive = '(?i)' ; my %Found ; &initialise ; if( query_string() =~ /file=([^&]+)/o ) { &show_file( $1 ) ; } elsif( query_string() =~ /term=([^&]+)/o ) { my $term = $1 ; query_string() =~ /type=([^&]+)/o ; my $pagetype = $1 ; param( $SEARCH_PAGES, ( $pagetype ) ) ; param( $IGNORE_CASE, 'X' ) ; &show_matches( $term ) ; } else { my $footer = $PREDEFINED_PAGES ; #$footer .= hr . &show_config ; my @pages = sort @{ thaw( $Data{"$PREFIX PAGETYPE"} ) } ; show_form( -TITLE => $TITLE, -HEADER => header() . start_html( $TITLE ) . h3( $TITLE ), -FOOTER => $footer, -ACCEPT => \&on_valid_form, -FIELDS => [ { -LABEL => $PREV_PAGE, -TYPE => 'scrolling_list', '-values' => [ sort { lc $a cmp lc $b } keys %Show ], -size => 1, }, { -LABEL => $PREV_TERM, -TYPE => 'scrolling_list', '-values' => [ sort { $SearchTerm{$b} <=> $SearchTerm{$a} } keys %SearchTerm ], -size => 1, }, { -LABEL => $NEW_TERM, -size => 24, }, { -LABEL => $IGNORE_CASE, -TYPE => 'checkbox', -value => 'X', -checked => 'checked', -label => '', }, { -LABEL => $SEARCH_PAGES, -TYPE => 'checkbox_group', '-values' => \@pages, -default => \@pages, }, { -LABEL => $SEARCH_FOR, -TYPE => 'radio_group', '-values' => [ $PREV_PAGE, $PREV_TERM, $NEW_TERM, $NEW_KEYWORD ], }, ], -BUTTONS => [ { -name => $SEARCH_FOR }, { -name => $DEL_PAGE }, { -name => $DEL_PAGES }, { -name => $DEL_TERM }, { -name => $DEL_TERMS }, ], # If eventually we want to offer configuration we'll add the options # as fields and add a Configure button. ) ; } &clean_and_quit ; sub show_file { my $file = uri_unescape( shift ) ; my( $type, $compress ) = $file =~ /\.([^.]+)(?:\.(gz|z|zip|Z))?$/o ; ( $type, $compress ) = ( 'txt', $type ) if $type =~ /^(?:gz|z|zip|Z)$/o ; $type = 'man' if $type =~ /^\d[a-z]{0,2}$/o ; # Remember for next time. unless( $Show{ &file_to_name( $file ) } ) { $Show{ &file_to_name( $file ) } = $file ; $Data{$SHOWN} = freeze( \%Show ) ; } $compress ||= '' ; my $TIMEOUT = 30 ; my @lines ; local $_ ; if( $type !~ /man/o ) { if( $compress ) { @lines = `zcat $file` ; } else { @lines = `cat $file` ; } } print header ; CASE : { if( $type =~ /html?/o ) { # Should never get here! print @lines ; last CASE ; } if( $type =~ /te?xt/o ) { print start_html( $file ), h3( colour( 'BLUE', $file ) ), ; &new_search ; print "
" ;
foreach( @lines ) {
print encode_entities( $_ ) ;
}
print "" ;
&new_search ;
print end_html ;
last CASE ;
}
# if( $type =~ /pod|pm/o ) { #/
# # Doesn't work except from the command line.
# if( $compress ) {
# print `zcat $file | pod2html --norecurse` ;
# }
# else {
# print `pod2html --norecurse --infile $file` ;
# }
# last CASE ;
# }
if( $type =~ /man/o ) {
print
start_html( $file ),
h3( colour( 'BLUE', $file ) ),
;
&new_search ;
print "" ;
my $temp = $file ;
$temp =~ s,.+/,,o ;
$temp = "$TMP/$temp.cache" ;
&full_system( "man -l $file > $temp" ) ;
my $i = 0 ;
sleep 1 while not -e $temp and $i++ < $TIMEOUT ;
if( -e $temp ) {
@lines = `cat $temp` ;
foreach( @lines ) {
s/.\cH//g ;
print encode_entities( $_ ) ;
}
unlink $temp if $Data{"$PREFIX DEL_CACHE"} ;
}
else {
print "Timed out after $i seconds" ;
}
print "" ;
&new_search ;
print end_html ;
last CASE ;
}
if( $type =~ /info/o ) {
print
start_html( $file ),
h3( colour( 'BLUE', $file ) ),
;
&new_search ;
print "" ;
print `info --file $file` ;
print "" ;
&new_search ;
print end_html ;
last CASE ;
}
DEFAULT : {
print
start_html( $TITLE ),
h3( colour( 'BLUE', $TITLE ) ),
;
&new_search ;
print
p( colour( 'RED', "BUG: File ",
;
foreach( @lines ) {
print encode_entities( $_ ) ;
}
print "" ;
&new_search ;
print end_html ;
}
}
}
# Copied from Programming Perl 2nd Ed (Blue Camel).
sub full_system {
my $rc = 0xFFFF & system @_ ;
my $result = '' ;
if( $rc == 0 ) {
$result = "ran with normal exit\n" ;
}
elsif( $rc == 0xFF00 ) {
$result = "command failed: $!\n" ;
}
elsif( $rc > 0x80 ) {
$rc >>= 8 ;
$result = "ran with non-zero exit status $rc\n" ;
}
else {
$result = "ran with " ;
if( $rc & 0x80 ) {
$rc &= ~0x80 ;
$result .= "core dump from " ;
}
$result .= "signal $rc\n" ;
}
# print qq{$result} if $result ;
( $rc != 0 ) ;
}
sub on_valid_form {
if( param( $DEL_PAGE ) and param( $PREV_PAGE ) ) {
delete $Show{ param( $PREV_PAGE ) } ;
$Data{$SHOWN} = freeze( \%Show ) ;
&show_del( 'Page', param( $PREV_PAGE ) ) ;
}
elsif( param( $DEL_PAGES ) and param( $PREV_PAGE ) ) {
%Show = () ;
$Data{$SHOWN} = freeze( \%Show ) ;
&show_del( 'All Pages', '' ) ;
}
elsif( param( $DEL_TERM ) and param( $PREV_TERM ) ) {
delete $SearchTerm{ param( $PREV_TERM) } ;
$Data{$SEARCH_TERMS} = freeze( \%SearchTerm ) ;
&show_del( 'Term', param( $PREV_TERM ) ) ;
}
elsif( param( $DEL_TERMS ) and param( $PREV_TERM ) ) {
%SearchTerm = () ;
$Data{$SEARCH_TERMS} = freeze( \%SearchTerm ) ;
&show_del( 'All Terms', '' ) ;
}
elsif( param( $SEARCH_FOR ) eq $NEW_KEYWORD ) {
print
header,
start_html( $TITLE ),
h2( $TITLE ),
h3( colour( 'RED', "Find Keyword not implemented yet." ) ),
;
&new_search ;
print end_html ;
}
else {
my $term ;
my $find = param( $SEARCH_FOR ) ;
if( $term = param( $NEW_TERM ) ) {
# Look for a new term if given.
&show_matches( $term ) ;
}
elsif( $term = param( $PREV_TERM ) and $find ne $PREV_PAGE ) {
# Look for an existing term unless we're looking for a previous page.
&show_matches( $term ) ;
}
elsif( param( $PREV_PAGE ) ) {
&show_file( $Show{ param( $PREV_PAGE ) } ) ;
}
else {
print
header,
start_html( $TITLE ),
h2( $TITLE ),
h3( colour( 'RED', "Invalid search/show" ) ),
p( "Did you choose a $PREV_TERM without checking the $PREV_TERM " .
"check box?" ),
;
&new_search ;
print end_html ;
}
}
}
sub show_del {
my( $type, $value ) = @_ ;
print
header,
start_html( $TITLE ),
h2( $TITLE ),
h3( qq{Deleted $type $value} ),
;
&new_search ;
print end_html ;
}
sub show_matches {
my $term = shift ;
@Term = split ' ', $term ;
local $_ ;
&reduce_terms if scalar keys %SearchTerm > $Data{"$PREFIX MAX_TERMS"} ;
$SearchTerm{$term}++ if $term !~ /^\d[a-z]*\.?$/o ;
# Always freeze as early as possible in case the user interrupts.
$Data{$SEARCH_TERMS} = freeze( \%SearchTerm ) ;
print
header,
start_html( $TITLE ),
h2( $TITLE ),
h3( "Files matching ", join " or ",
map { qq{$_} } @Term ),
;
&new_search ;
my @path = () ;
my %path = %{ thaw( $Data{"$PREFIX PATH"} ) } ;
foreach my $pagetype ( param( $SEARCH_PAGES ) ) {
push @path, split /:/, $path{$pagetype} ;
}
$CaseSensitive = '' unless param( $IGNORE_CASE ) eq 'X' ;
%Found = () ;
find( \&wanted, @path ) ;
print "| Configuration | |
| Paths: | |
| $key | $path |
| database | $DB_FILE |
| Program: | $URL |
| Del cache: | $Data{"$PREFIX DEL_CACHE"}} . qq{ |
| Max Terms: } . qq{ | $Data{"$PREFIX MAX_TERMS"} |
| Page types: | $pagetype |
man 1\ \ \ Executable programs or shell commands
man 1db\ DB
man 1p\ \ Perl Functions
man 1x\ \ X Executable programs or shell commands
man 2\ \ \ System calls (functions provided by the kernel)
man 3\ \ \ Library calls (functions within system libraries)
man 3paper\ Paper related
man 3pm\ Perl Modules
man 4\ \ \ Special files (usually found in /dev)
man 5\ \ \ File formats and conventions eg /etc/passwd
man 5vga\ VGA File formats and conventions
man 5x\ \ X File formats and conventions
man 6\ \ \ Games
man 7\ \ \ Macro packages and conventions eg man(7), groff(7)
man 7vga\ VGA Macro packages and conventions
man 8\ \ \ System administration commands (usually only for root)
man 9\ \ \ Kernel routines