fp@220: #!/usr/bin/perl -w fp@220: fp@220: #------------------------------------------------------------------------------ fp@220: # fp@220: # Copyright (C) 2006 Ingenieurgemeinschaft IgH fp@220: # fp@220: # This file is part of the IgH EtherCAT Master. fp@220: # fp@220: # The IgH EtherCAT Master is free software; you can redistribute it fp@220: # and/or modify it under the terms of the GNU General Public License fp@220: # as published by the Free Software Foundation; version 2 of the License. fp@220: # fp@220: # The IgH EtherCAT Master is distributed in the hope that it will be fp@220: # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of fp@220: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fp@220: # GNU General Public License for more details. fp@220: # fp@220: # You should have received a copy of the GNU General Public License fp@220: # along with the IgH EtherCAT Master; if not, write to the Free Software fp@220: # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA fp@220: # fp@220: #------------------------------------------------------------------------------ fp@220: # fp@220: # Multithreaded Server fp@220: # according to the example from "Programming Perl" fp@220: # this code is improved according to the example from fp@220: # perldoc perlipc, so now safely being usable under Perl 5.8 fp@220: # (see note (*)) fp@220: # fp@220: # works with read/write on a device-file fp@220: # fp@220: #------------------------------------------------------------------------------ fp@220: fp@220: require 5.002; fp@220: use strict; fp@220: BEGIN { $ENV{PATH} = '/opt/msr/bin:/usr/bin:/bin' } fp@220: use Socket; fp@220: use Carp; fp@220: use FileHandle; fp@220: use Getopt::Std; fp@220: fp@220: use Sys::Syslog qw(:DEFAULT setlogsock); fp@220: fp@220: use vars qw ( fp@220: $self $pid $dolog $port $dev %opts $selfbase fp@220: $len $offset $stream $written $read $log $blksize fp@220: $instdir fp@220: $authfile %authhosts fp@220: ); fp@220: fp@220: fp@220: # Do logging to local syslogd by unix-domain socket instead of inetd fp@220: setlogsock("unix"); fp@220: fp@220: # Prototypes and some little Tools fp@220: sub spawn; fp@220: sub logmsg { fp@220: my ($level, $debug, @text) = @_; fp@220: syslog("daemon|$level", @text) if $debug > $dolog; fp@220: # print STDERR "daemon|$level", @text, "\n" if $dolog; fp@220: } fp@220: sub out { fp@220: my $waitpid = wait; fp@220: logmsg("notice", 2, "$waitpid exited"); fp@220: unlink "$selfbase.pid"; fp@220: exit 0; fp@220: } fp@220: fp@220: sub help { fp@220: print "\n usage: $0 [-l og] [-h elp] [-p port] [-d device]\n"; fp@220: exit; fp@220: } fp@220: fp@220: # Process Options fp@220: %opts = ( fp@220: "l" => 1, fp@220: "h" => 0, fp@220: "p" => 2345, fp@220: "d" => "/dev/msr" fp@220: ); fp@220: fp@220: getopts("lhp:d:", \%opts); fp@220: fp@220: help if $opts{"h"}; fp@220: fp@220: ( $self = $0 ) =~ s+.*/++ ; fp@220: ( $selfbase = $self ) =~ s/\..*//; fp@220: $log = "$selfbase.log"; fp@220: $dolog = $opts{"l"}; fp@220: $port = $opts{"p"}; fp@220: $dev = $opts{"d"}; fp@220: $blksize = 1024; # try to write as much bytes fp@220: $instdir = "/opt/msr"; fp@220: $authfile = "$instdir/etc/hosts.auth"; fp@220: fp@220: # Start logging fp@220: openlog($self, 'pid'); fp@220: fp@220: # Flush Output, dont buffer fp@220: $| = 1; fp@220: fp@220: # first fork and run in background fp@220: if ($pid = fork) { fp@220: # open LOG, ">$log" if $dolog; fp@220: # close LOG; fp@220: logmsg("notice", 2, "forked process: $pid\n"); fp@220: exit 0; fp@220: } fp@220: fp@220: # Server tells about startup success fp@220: open (PID, ">/$instdir/var/run/$selfbase.pid"); fp@220: print PID "$$\n"; fp@220: close PID; fp@220: fp@220: # Cleanup on exit (due to kill -TERM signal) fp@220: $SIG{TERM} = \&out; fp@220: fp@220: # We use streams fp@220: my $proto = getprotobyname('tcp'); fp@220: fp@220: # Open Server socket fp@220: socket(Server, PF_INET, SOCK_STREAM, $proto) or die "socket: $!"; fp@220: setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) fp@220: or die "setsocketopt: $!"; fp@220: bind (Server, sockaddr_in($port, INADDR_ANY)) fp@220: or die "bind: $!"; fp@220: listen (Server, SOMAXCONN) fp@220: or die "listen: $!"; fp@220: fp@220: %authhosts = (); fp@220: # get authorized hosts fp@220: open (AUTH, $authfile) fp@220: or logmsg ("notice", 2, "Could not read allowed hosts file: $authfile"); fp@220: while () { fp@220: chomp; fp@220: my $host = lc $_; fp@220: if ($host =~ /^[\d\w]/) { fp@220: $authhosts{$_} = 1; fp@220: logmsg ("notice", 2, "Authorized host: >$host<"); fp@220: } fp@220: } fp@220: close (AUTH); fp@220: fp@220: # tell about open server socket fp@220: logmsg ("notice", 2, "Server started at port $port"); fp@220: fp@220: my $waitedpid = 0; fp@220: my $paddr; fp@220: fp@220: # wait for children to return, thus avoiding zombies fp@220: # improvement (*) fp@220: use POSIX ":sys_wait_h"; fp@220: sub REAPER { fp@220: my $child; fp@220: while (($waitedpid = waitpid(-1,WNOHANG)) > 0) { fp@220: logmsg ("notice", 2, "reaped $waitedpid", ($? ? " with exit $?" : "")); fp@220: } fp@220: $SIG{CHLD} = \&REAPER; # loathe sysV fp@220: } fp@220: fp@220: # also all sub-processes should wait for their children fp@220: $SIG{CHLD} = \&REAPER; fp@220: fp@220: # start a new server for every incoming request fp@220: # improvement (*) -- loop forever fp@220: fp@220: while ( 1 ) { fp@220: for ( $waitedpid = 0; fp@220: ($paddr = accept(Client,Server)) || $waitedpid; fp@220: $waitedpid = 0, close Client ) { fp@220: next if $waitedpid and not $paddr; fp@220: my ($port, $iaddr) = sockaddr_in($paddr); fp@220: my $name = lc gethostbyaddr($iaddr, AF_INET); fp@220: my $ipaddr = inet_ntoa($iaddr); fp@220: my $n = 0; fp@220: fp@220: # tell about the requesting client fp@220: logmsg ("info", 2, "Connection from >$ipaddr< ($name) at port $port"); fp@220: fp@220: spawn sub { fp@220: my ($head, $hlen, $pos, $pegel, $typ, $siz, $nch, $nrec, $dat, $i, $j, $n, $llen); fp@220: my ($watchpegel, $shmpegel); fp@220: my ($rin, $rout, $in, $line, $data_requested, $oversample); fp@220: my (@channels); fp@220: fp@220: # to use stdio on writing to Client fp@220: Client->autoflush(); fp@220: fp@220: # Open Device fp@220: sysopen (DEV, "$dev", O_RDWR | O_NONBLOCK, 0666) or die("can't open $dev"); fp@220: fp@220: # Bitmask to check for input on stdin fp@220: $rin = ""; fp@220: vec($rin, fileno(Client), 1) = 1; fp@220: fp@220: # check for authorized hosts fp@220: my $access = 'deny'; fp@220: $access = 'allow' if $authhosts{$ipaddr}; fp@220: $line = "\n"; fp@220: logmsg ("info", 2, $line); fp@220: $len = length $line; fp@220: $offset = 0; fp@220: while ($len) { fp@220: $written = syswrite (DEV, $line, $len, $offset); fp@220: $len -= $written; fp@220: $offset += $written; fp@220: } fp@220: fp@220: while ( 1 ) { fp@220: $in = select ($rout=$rin, undef, undef, 0.0); # poll client fp@220: # look for any Input from Client fp@220: if ($in) { fp@220: # exit on EOF fp@220: $len = sysread (Client, $line, $blksize) or exit; fp@220: logmsg("info", 0, "got $len bytes: \"$line\""); fp@220: $offset = 0; fp@220: # copy request to device fp@220: while ($len) { fp@220: $written = syswrite (DEV, $line, $len, $offset); fp@220: $len -= $written; fp@220: $offset += $written; fp@220: } fp@220: } fp@220: # look for some output from device fp@220: if ($len = sysread DEV, $stream, $blksize) { fp@220: print Client $stream; fp@220: } else { fp@220: select undef, undef, undef, 0.1; # calm down if nothing on device fp@220: } fp@220: } fp@220: }; fp@220: logmsg("info", 2, "spawned\n"); fp@220: } fp@220: logmsg("info", 2, "server loop\n"); fp@220: } fp@220: fp@220: sub spawn { fp@220: my $coderef = shift; fp@220: fp@220: unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') { fp@220: confess "usage: spawn CODEREF"; fp@220: } fp@220: my $pid; fp@220: if (!defined($pid = fork)) { fp@220: logmsg ("notice", 2, "fork failed: $!"); fp@220: return; fp@220: } elsif ($pid) { fp@220: logmsg ("notice", 2, "Request $pid"); fp@220: return; # Parent fp@220: } fp@220: fp@220: # do not use fdup as in the original example fp@220: # open (STDIN, "<&Client") or die "Can't dup client to stdin"; fp@220: # open (STDOUT, ">&Client") or die "Can't dup client to stdout"; fp@220: # STDOUT->autoflush(); fp@220: exit &$coderef(); fp@220: }