#!/usr/bin/env perl # #$Id: serialmgrd,v 1.25 2009/06/27 18:24:33 staffcvs Exp $ # use Data::Dumper; use Error qw(:try); use POSIX; use Sys::Syslog; my $stuff = []; my $hosts = []; my $hup = 0; my $consolidate_exe = "/usr/local/bin/consolidate"; my $sympathy_exe = "/usr/local/bin/sympathy"; my @keys = qw(host portcode baud task user password name options); my $labeling = { shedu => { offsets => [ 0, 7, 3, 11, 15 ], }, phoenix => { offsets => [ 0, 3, 11, 15 ], }, leprechaun => { offsets => [ 0, 3 ], }, imp => { offsets => [ 0, 3 ], }, pixie => { offsets => [ 0, 3, 11 ], }, basilisk => { offsets => [ 0, 3, 11, 7, 15 ], }, wyvern => { offsets => [ 0, 3, 11 ], }, goblin => { offsets => [ 0, 3 ], }, naga => { offsets => [ 0, 5 ], }, dragon => { offsets => [ 0, 3, 11 ], }, woking => { offsets => [ 0, 3, 11 ], }, }; sub port_decode($$) { my ( $host, $code ) = @_; return "/dev/ttyS" . $code if ( $code =~ /^\d+$/ ); return "/dev/ttyUSB" . $1 if ( $code =~ /^u.(\d+)$/ ); return "/dev/ttyS" . ( $labeling->{$host}->{offsets}->[$1] + $2 ) if ( $code =~ /^(\d+)\.(\d+)$/ ); return $code; } sub quit() { sleep(10); exit(1); } sub read_file() { my $lines = []; my $c; open FILE, "<" . "/etc/serialmgr/config"; while () { next if ( $_ =~ /^#/ ); chomp; @_ = split(':'); $c = 0; push @$lines, { map { $keys[ $c++ ] => $_ } @_ }; } close FILE; return $lines; } sub hostname() { my ( $sysname, $nodename, $release, $version, $machine ) = POSIX::uname(); return $1 if ( $nodename =~ /([^.]+)\./ ); return $nodename; } sub check_users($) { my $c = shift; for my $l (@$c) { my $shell; if ( $l->{task} eq "sympathy" ) { $shell = "/usr/local/bin/run_sympathy"; } else { $shell = "/usr/local/bin/run_conclient"; } if ( not defined getpwnam( $l->{user} ) ) { syslog( LOG_ERR, "creating an account for user " . $l->{user} ); mkdir "/export/home"; mkdir "/export/home/colo"; my @cmd = (); push @cmd, "useradd"; push @cmd, "-d", "/export/home/colo/" . $l->{user}; if ( length( $l->{password} ) > 2 ) { push @cmd, "-p", $l->{password}; } push @cmd, "-s", $shell; push @cmd, "-m"; push @cmd, $l->{user}; #print join( ' ', @cmd ), "\n"; system @cmd; } if ( not defined getgrnam( $l->{user} ) ) { syslog( LOG_ERR, "need to create a group for " . $l->{user} ); quit(); } my $current_shell = ( getpwnam( $l->{user} ) )[8]; my $uid = getpwnam( $l->{user} ) ; if (( $uid > 0 ) and ( $current_shell ne $shell )) { syslog( LOG_ERR, "changing shell for user " . $l->{user} . " to " . $shell ); my @cmd = (); push @cmd, "usermod"; push @cmd, "-s", $shell; push @cmd, $l->{user}; system @cmd; } if ( length( $l->{password} ) > 2 ) { my $pwd = ( getpwnam( $l->{user} ) )[1]; if ( $l->{password} ne $pwd ) { syslog( LOG_ERR, "changing password for user " . $l->{user} ); my @cmd = (); push @cmd, "usermod"; push @cmd, "-p", $l->{password}; push @cmd, $l->{user}; system @cmd; } } if ( $l->{user} eq 'root' ) { } elsif ( ! -d "/export/home/colo/".$l->{user} ) { syslog( LOG_ERR, "need to create a home directory for " . $l->{user} ); quit(); } elsif ( ( getpwnam( $l->{user} ) )[2] != ( stat _ )[4] || ( getpwnam( $l->{user} ) )[3] != ( stat _ )[5] ) { syslog( LOG_ERR, "changing home directory ownership for user " . $l->{user} ); my @cmd = (); push @cmd, "chown", "-R"; push @cmd, $l->{user}.".".$l->{user}; push @cmd, "/export/home/colo/".$l->{user}; system @cmd; } } } sub resolve_users($) { my $c = shift; my $ret=[]; my $host = hostname(); for my $l (@$c) { next if ( $l->{host} ne $host ); my $uid = $l->{user}; my $gid = $l->{user}; if ( $uid =~ /[^\d]/ ) { $uid = getpwnam($uid); if ( !defined($uid) ) { syslog( LOG_ERR, "unknown user " . $l->{user} ); quit(); } } if ( $gid =~ /[^\d]/ ) { $gid = getgrnam($gid); if ( !defined($gid) ) { syslog( LOG_ERR, "unknown group " . $l->{user} ); quit(); } } $l->{uid} = $uid; $l->{gid} = $gid; $l->{backoff} = 0; $l->{badstarts} = 0; $l->{running} = 0; if ( $l->{uid} > 0 ) { $l->{socket} = "/export/home/colo/" . $l->{user} . "/console-socket"; $l->{socket_dir} = "/export/home/colo/" . $l->{user}; $l->{log} = "/export/home/colo/" . $l->{user} . "/logs/console"; $l->{log_dir} = "/export/home/colo/" . $l->{user} . "/logs"; } else { if ( $l->{task} eq "sympathy" ) { $l->{socket} = "/root/sympathy/" . $l->{name}; $l->{socket_dir} = "/root/sympathy"; $l->{log} = "/root/sympathy/" . $l->{name} . ".log"; $l->{log_dir} = "/root/sympathy"; } else { $l->{socket} = "/root/consoles/" . $l->{name}; $l->{socket_dir} = "/root/consoles"; $l->{log} = "/root/consoles/" . $l->{name} . ".log"; $l->{log_dir} = "/root/consoles"; } } push @$ret,$l; } return $ret; } sub get_config() { my $cf = read_file(); my $stuff = []; # my $host = hostname(); for $line (@$cf) { # if ( $line->{host} eq $host ) { $line->{device} = port_decode( $line->{host}, $line->{portcode} ); push @$stuff, $line; # } } return $stuff; } sub start($) { my $t = shift; my $exe; my @args; $t->{started} = time(); my $pid = fork(); if ( !defined($pid) ) { syslog( LOG_ERR, "fork failed" ); return undef; } elsif ( $pid != 0 ) { $t->{pid} = $pid; $t->{running} = 1; syslog( LOG_ERR, "Started " . $t->{task} . " for " . $t->{name} . " on " . $t->{device} . " (" . $t->{portcode} . ") pid " . $t->{pid} ); return $pid; } # use sympathy to clean up any old lock files whilst we're still root system( $sympathy_exe, "-S", "-C", "-d", $t->{device} ); chown $t->{gid}, $t->{uid}, $t->{device}; $( = $) = $t->{gid} if ( $t->{gid} != 0 ); $< = $> = $t->{uid} if ( $t->{uid} != 0 ); mkdir $t->{socket_dir}; mkdir $t->{log_dir}; if ( $t->{task} eq "sympathy" ) { push @args, "-S"; push @args, "-s"; push @args, "-F"; push @args, "-d"; push @args, $t->{device}; push @args, "-b"; push @args, $t->{baud}; push @args, "-K"; push @args, "-k"; push @args, $t->{socket}; push @args, "-L"; push @args, $t->{log}; push @args, "-R"; push @args, $t->{options} if $t->{options}; $exe = $sympathy_exe; } else { push @args, "-d"; push @args, "-e"; push @args, "-s"; push @args, $t->{baud} . ",n,8"; push @args, "-f"; push @args, "none"; push @args, "-l"; push @args, "1048576"; push @args, $t->{device}; push @args, $t->{log}; push @args, $t->{socket}; push @args, $t->{options} if $t->{options}; $exe = $consolidate_exe; } syslog(LOG_ERR, $exe. " ". join( ' ', @args )); exec( $exe, @args ); exit(1); } sub died($) { my $t = shift; my $ranfor = $t->{ended} - $t->{started}; syslog( LOG_ERR, "Finished " . $t->{task} . " for " . $t->{name} . " on " . $t->{device} . " (" . $t->{portcode} . ") pid " . $t->{pid} . " lasted " . $ranfor . "s" ); if ( $ranfor < 20 ) { $t->{badstarts}++; } if ( $t->{badstarts} > 5 ) { syslog( LOG_ERR, "Looping " . $t->{task} . " for " . $t->{name} . " on " . $t->{device} . " (" . $t->{portcode} . ") pid " . $t->{pid} . " suspended" ); $t->{backoff} = time() + 5 * 60; $t->{badstarts} = 0; } $t->{pid} = undef; } sub hup() { $hup++; } sub medea() { for $t (@$stuff) { kill POSIX::SIGTERM, $t->{pid} if ( defined( $t->{pid} ) ); } } sub terminate() { syslog( LOG_ERR, "shutting down on signal" ); medea(); exit(0); } sub sigchld() { for $t (@$stuff) { if ( $t->{running} and ( $t->{pid} == waitpid( $t->{pid}, WNOHANG ) ) ) { $t->{ended} = time(); $t->{running} = 0; } } } openlog( "serialmgrd", "pid,console", LOG_LOCAL0 ); syslog( LOG_ERR, "starts" ); $stuff = get_config(); check_users($stuff); $stuff=resolve_users($stuff); $SIG{HUP} = \&hup; $SIG{INT} = $SIG{TERM} = \&terminate; $SIG{CHLD} = \&sigchld; while (1) { my $now = time(); sigchld(); for $t (@$stuff) { if ( defined $t->{pid} ) { if ( ( $t->{running} ) and ( kill( 0, $t->{pid} ) == 0 ) ) { $t->{running} = 0; $t->{ended} = time(); } if ( not $t->{running} ) { died($t); } } if ( ( not defined( $t->{pid} ) ) and ( $t->{backoff} < $now ) ) { start($t); } } if ( scalar(@$stuff) < 1 ) { syslog( LOG_ERR, "Nothing to do - sleeping 3600s" ); sleep(3600); } if ($hup) { $hup = 0; syslog( LOG_ERR, "SIGHUP rereading config" ); medea(); $stuff = get_config(); check_users($stuff); $stuff=resolve_users($stuff); } sleep(10); }