Added "-k|--kill" option.
authorHeiko Schlittermann <hs@schlittermann.de>
Thu, 29 Jan 2009 15:06:18 +0100
changeset 25 4fb7b2a136d3
parent 24 1523d46da1d2
child 26 6c7fed815e4f
Added "-k|--kill" option.
.hgignore
Makefile
hlog
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Jan 29 15:06:18 2009 +0100
@@ -0,0 +1,1 @@
+hlog.1.gz
--- a/Makefile	Thu Jan 29 06:01:16 2009 +0100
+++ b/Makefile	Thu Jan 29 15:06:18 2009 +0100
@@ -1,8 +1,10 @@
 
 DESTDIR = 
 prefix  = /usr/local
-bindir = ${prefix}/bin
+bindir  = ${prefix}/bin
 man1dir = ${prefix}/share/man/man1
+logdir  = /var/log
+rundir  = /var/run
 
 .PHONY:	all clean install
 
@@ -14,11 +16,16 @@
 
 all:		$(MAN1)
 install:	all
-		# bin
+		# mandatory directories
 		install -m 0755 -d ${DESTDIR}${bindir}
+		install -m 0755 -d ${DESTDIR}${man1dir}
+
+		# optional directories
+		-install -m 0755 -d ${DESTDIR}${logdir}
+		-install -m 0755 -d ${DESTDIR}${rundir}
+
+		# script and manpage
 		install -m 0755 $(SCRIPT) ${DESTDIR}${bindir}/
-		# man
-		install -m 0755 -d ${DESTDIR}${man1dir}
 		install -m 0644 $(MAN1) ${DESTDIR}${man1dir}/
 
 clean:		; -rm -f $(CLEANFILES)
--- a/hlog	Thu Jan 29 06:01:16 2009 +0100
+++ b/hlog	Thu Jan 29 15:06:18 2009 +0100
@@ -33,27 +33,35 @@
 my $opt_daemon = 1;
 my $opt_kill   = 0;
 
-my $ME         = basename $0;
-my $rundir     = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ];
-my $logdir     = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ];
-my $access     = \"%s/access.log";
-my $errors     = \"%s/error.log";
-my $pidfile    = \"%s/%s.%s.pid";                         # %dir/%ip.%port
-my $maxlogsize = 1000_000_000;                            # ca 1 MByte
+my $ME = basename $0;
+
+# these vars will be filled with the real dirs later
+my $rundir = [ "/var/run/$ME", "$ENV{HOME}/.$ME" ];
+my $logdir = [ "/var/log/$ME", "$ENV{HOME}/.$ME" ];
+
+my $maxlogsize  = 1000_000_000;    # ca 1 MByte
+my $killtimeout = 3;
+
+# these are refs to detect if they're converted already
+my $access  = \"%s/access.log";
+my $errors  = \"%s/error.log";
+my $pidfile = \"%s/%s.%s.pid";     # %dir/%ip.%port
 
 END {
-    unlink $pidfile 
-    if defined $pidfile and not ref $pidfile;
+    unlink $pidfile
+      if defined $pidfile and not ref $pidfile;
 }
 
+sub find_writable_dir(@);
+
+sub log_open($);
+sub log_write($);
 
 sub handle_request($);
-sub date1123(;$);
 sub http($@);
+
 sub bad_request();
-sub log_open($);
-sub log_write($);
-sub find_writable_dir(@);
+sub date1123(;$);
 
 my %FILE;
 
@@ -64,7 +72,7 @@
         "port=i"  => \$opt_port,
         "lines=i" => \$opt_lines,
         "daemon!" => \$opt_daemon,
-	"kill"	  => \$opt_kill,
+        "kill"    => \$opt_kill,
         "help"    => sub { pod2usage(-verbose => 1, -exitval => 0) },
         "man"     => sub { pod2usage(-verbose => 2, -exitval => 0) },
     ) or pod2usage();
@@ -75,10 +83,40 @@
         log_open($access);
     }
 
-    $rundir = find_writable_dir(@$rundir);
+    if (defined($rundir = find_writable_dir(@$rundir))) {
+
+        # santize hostname
+        (my $host = $opt_addr) =~ s/([^\w.-])/sprintf "%%%02X", ord($1)/gie;
+        $pidfile = sprintf $$pidfile, $rundir, $host, $opt_port,;
+    }
+    else { $pidfile = undef }
+
+    if ($opt_kill) {
+        warn "Killing process on $opt_addr:$opt_port\n";
+        open(my $p, $pidfile) or die "Can't open $pidfile: $!\n";
+        defined($_ = <$p>) or die "no pid found in $pidfile\n";
+        chomp;
+        kill -15 => $_ or die "Can't kill pid $_: $!\n";
+
+        # we can't wait, it's not our process group, so we've to poll
+        eval {
+            $SIG{ALRM} = sub { die "TIMEOUT\n" };
+            alarm($killtimeout);
+            for (my $sleep = 1 ; kill -0 => $_ ; $sleep++) {
+                sleep($sleep > 10 ? 10 : $sleep);
+            }
+            alarm(0);
+        };
+        if ($@ eq "TIMEOUT\n") {
+            warn "Child $_ didn't respond. Using violence.\n";
+            kill -9 => $_;
+        }
+        exit 0;
+    }
 
     pod2usage() if not @ARGV;
 
+    # resolve tags and filenames
     foreach (@ARGV) {
         $_ = "default=$_" if not /=/ or /^\//;
         my ($tag, $file) = split /=/, $_, 2;
@@ -88,6 +126,7 @@
         $FILE{$tag} = $file;
     }
 
+    # start the listener
     my $listener = new IO::Socket::INET(
         LocalAddr => $opt_addr,
         LocalPort => $opt_port,
@@ -96,13 +135,6 @@
         ReuseAddr => 1,
     ) or die "Can't create listener socket: $!\n";
 
-    if ($rundir) {
-        $pidfile = sprintf $$pidfile, $rundir,
-          $listener->sockhost,
-          $listener->sockport;
-    }
-    else { $pidfile = undef }
-
     # go daemon
     chdir("/") or die "Can't chdir to /: $!\n";
 
@@ -115,7 +147,7 @@
             print "listener $pid "
               . $listener->sockhost . ":"
               . $listener->sockport . "\n";
-	    undef $pidfile;
+            undef $pidfile;
             exit 0;
         }
 
@@ -134,6 +166,7 @@
     }
 
     $SIG{INT} = $SIG{TERM} = sub { warn "Got signal $_[0]\n"; exit 0 };
+    $SIG{__WARN__} = sub { print STDERR localtime() . " ", @_ };
     $SIG{__DIE__} = sub { print STDERR @_; exit $? };
 
     if (defined $pidfile) {
@@ -193,7 +226,8 @@
     }
 
     sub log_write($) {
-        $fh->print(localtime() . " $_[0]\n");
+        $fh->print(localtime() . " $_[0]\n")
+          if defined $fh;
     }
 
 }
@@ -328,6 +362,7 @@
 =head1 SYNOPSIS
     
     hlog [--[no]daemon] 
+	 [-k|--kill]
          [-a|--address address] [-p|--port port]
 	 [--lines n] 
 	 {file|tag=file ...}
@@ -339,17 +374,7 @@
 This script should run as a server providing access to 
 the last lines of a logfile. It should understand basic HTTP/1.x.
 
-B<hlog> looks for a writable "log" directory (or tries to create on) as
-F</var/log/hlog> and F<$HOME/.hlog> and opens there an F<access.log> and
-an F<error.log>.  It is not fatal if it doesn't find a writable
-directory. Regularly (currently at each incoming request) the size of
-these logs is checked and the files are renamed to "*.1" whenever they
-are bigger than about 1MB.
-
-B<hlog> looks for a writable "run" directory (or tries to create on)
-as F</var/run/hlog> and F<$HOME/.hlog> and writes there the PID-file
-as I<addr>.I<port>.F<pid>. It's not fatal if it can't write the
-PID-file.
+See the L<FILES> section for more information on files.
 
 =head1 OPTIONS
 
@@ -367,6 +392,11 @@
 
 The number of lines to show. (default: 10)
 
+=item B<-k>|B<--kill>
+
+With this option the corresponding (address/port) process gets killed.
+(default: off)
+
 =item B<-p>|B<--port> I<port>
 
 The port to listen on. (default: 8080)
@@ -387,5 +417,25 @@
     http://<server>:8080/error
     http://<server>:8080/access?10
 
+=head1 FILES
+
+The B<hlog> tool tries to create several files
+
+=head2 F<access.log> and F<error.log>
+
+These files will be written to F</var/log/hlog/> or F<$HOME/.hlog/> if
+possible. The mentioned directories (the leave part) will be created, if
+possible. It is no fatal error if B<hlog> fails on this.
+
+=head2 PID file
+
+B<hlog> tries to create a pid file in F</var/run/hlog/> or
+F<$HOME/.hlog>. It even tries to create the leave part the directory.
+Failing on this it not fatal, but then the B<--kill> option will not
+work!
+
+The pid file will be named according to the hostname (see B<--address>)
+beeing used. For safety the hostname will be sanitized to avoid
+dangerous filenames.
 
 =cut