]> git.donarmstrong.com Git - infobot.git/blobdiff - src/Shm.pl
dunno
[infobot.git] / src / Shm.pl
index e4d037a9c5812e5a872d9bc99012eb937caedd82..9717b46978f2ae842fd2242f1e46461e2e08e9d2 100644 (file)
@@ -1,23 +1,38 @@
 #
 #   Shm.pl: Shared Memory stuff.
-#    Author: xk <xk@leguin.openprojects.net>
+#    Author: dms
 #   Version: 20000201
 #   Created: 20000124
 #
 
-if (&IsParam("useStrict")) { use strict; }
+# use strict;  # TODO
+
+use POSIX qw(_exit);
+
+my %shm_keys;
 
 sub openSHM {
     my $IPC_PRIVATE = 0;
-    my $size = 2000;
+    my $size        = 2000;
+
+    if ( &IsParam('noSHM') ) {
+        &status('Shared memory: Disabled. WARNING: bot may become unreliable');
+        return 0;
+    }
 
-    if (defined( $_ = shmget($IPC_PRIVATE, $size, 0777) )) {
-       &status("Created shared memory (shm) key: [$_]");
-       return $_;
-    } else {
-       &ERROR("openSHM: failed.");
-       &ERROR("Please delete some shared memory with ipcs or ipcrm.");
-       exit 1;
+    if ( defined( $_ = shmget( $IPC_PRIVATE, $size, 0777 ) ) ) {
+        &status("Created shared memory (shm) key: [$_]");
+        $shm_keys{$_} = {
+            time     => time,
+            accessed => 0,
+            key      => $_,
+        };
+        return $_;
+    }
+    else {
+        &ERROR('openSHM: failed.');
+        &ERROR('Please delete some shared memory with ipcs or ipcrm.');
+        exit 1;
     }
 }
 
@@ -25,107 +40,280 @@ sub closeSHM {
     my ($key) = @_;
     my $IPC_RMID = 0;
 
+    return '' if ( !defined $key );
+
+    &shmFlush();
     &status("Closed shared memory (shm) key: [$key]");
-    return shmctl($key, $IPC_RMID, 0);
+    return shmctl( $key, $IPC_RMID, 0 );
 }
 
 sub shmRead {
-    my ($key) = @_;
+    my ($key)    = @_;
     my $position = 0;
-    my $size = 3*80;
-    my $retval = '';
+    my $size     = 3 * 80;
+    my $retval   = '';
+
+    return '' if ( &IsParam('noSHM') );
 
-    if (shmread($key,$retval,$position,$size)) {
-       return $retval;
-    } else {
-       &ERROR("shmRead: failed: $!");
-       return '';
+    if ( shmread( $key, $retval, $position, $size ) ) {
+
+        #&DEBUG("shmRead($key): $retval");
+        return $retval;
+    }
+    else {
+        &ERROR("shmRead: failed: $!");
+        if ( exists $shm_keys{$_} ) {
+            closeSHM($key);
+        }
+        ### TODO: if this fails, never try again.
+        # What use is opening a SHM segment if we're not going to read it?
+        # &openSHM();
+        return '';
     }
 }
 
 sub shmWrite {
-    my ($key, $str) = @_;
+    my ( $key, $str ) = @_;
     my $position = 0;
-    my $size = 80*3;
+    my $size     = 80 * 3;
+
+    return if ( &IsParam('noSHM') );
+
+    $shm_keys{$keys}{accessed} = 1;
+
+    if ( length($str) > $size ) {
+        &status("ERROR: length(str) (..)>$size...");
+        return;
+    }
+
+    if ( length($str) == 0 ) {
+
+        # does $size overwrite the whole lot?
+        # if not, set to 2000.
+        if ( !shmwrite( $key, '', $position, $size ) ) {
+            &ERROR("shmWrite: failed: $!");
+        }
+        return;
+    }
 
-    # NULL hack.
-    ### TODO: create shmClear to deal with this.
-    if ($str !~ /^$/) {
-       my $read = &shmRead($key);
-       $read =~ s/\0+//g;
-       $str = $read ."||". $str if ($read ne "");
+    my $read = &shmRead($key);
+    $read =~ s/\0+//g;
+    if ( $read eq '' ) {
+        $str = sprintf( '%s:%d:%d: ', $param{ircUser}, $bot_pid, time() );
+    }
+    else {
+        $str = $read . '||' . $str;
     }
 
-    if (!shmwrite($key,$str,$position,$size)) {
-       &ERROR("shmWrite: failed: $!");
+    if ( !shmwrite( $key, $str, $position, $size ) ) {
+        &DEBUG("shmWrite($key, $str)");
+        &ERROR("shmWrite: failed: $!");
     }
 }
 
-#######
-# Helpers
-#
+##############
+### Helpers
+###
 
 # Usage: &addForked($name);
 # Return: 1 for success, 0 for failure.
 sub addForked {
     my ($name) = @_;
-    my $forker_timeout = 360;  # 6mins, in seconds.
+    my $forker_timeout = 360;    # 6mins, in seconds.
+    $forker = $name;
+
+    if ( !defined $name ) {
+        &WARN('addForked: name == NULL.');
+        return 0;
+    }
+
+    foreach ( keys %forked ) {
+        my $n    = $_;
+        my $time = time() - $forked{$n}{Time};
+        next unless ( $time > $forker_timeout );
+
+        ### TODO: use &time2string()?
+        &WARN("Fork: looks like we lost '$n', executed $time ago");
+
+        my $pid = $forked{$n}{PID};
+        if ( !defined $pid ) {
+            &WARN("Fork: no pid for $n.");
+            delete $forked{$n};
+            next;
+        }
 
-    foreach (keys %forked) {
-       my $time = time() - $forked{$_};
-       next unless ($time > $forker_timeout);
+        if ( $pid == $bot_pid ) {
 
-       ### TODO: use &time2string()?
-       &WARN("Fork: looks like we lost '$_', executed $time ago.");
-       delete $forked{$_};
+            # don't kill parent, just warn.
+            &status("Fork: pid == \$bot_pid == \$\$ ($bot_pid)");
+
+        }
+        elsif ( -d "/proc/$pid" ) {    # pid != bot_pid.
+            &status("Fork: killing $name ($pid)");
+            kill 9, $pid;
+        }
+
+        delete $forked{$n};
     }
 
     my $count = 0;
-    while (scalar keys %forked > 2) {  # 2 or more == fail.
-       sleep 1;
-
-       if ($count > 3) {       # 3 seconds.
-           my $list = join(', ', keys %forked);
-           if (defined $who) {
-               &msg($who, "already running ($list) => exceeded allowed forked processes count (1?).");
-           } else {
-               &status("Fork: I ran too many forked processes :) Giving up $name.");
-           }
-           return 0;
-       }
-
-       $count++;
-    }
-
-    if (exists $forked{$name}) {
-       my $time = $forked{$name};
-       if (time() - $forked{$name} > 900) {    # stale fork > 15m.
-           &status("forked: forked{$name} presumably exited without notifying us.");
-           $forked{$name} = time();
-           return 1;
-       } else {                                # fresh fork.
-           &msg($who, "$name is already running ". &Time2String(time() - $forked{$name}));
-           return 0;
-       }
-    } else {
-       $forked{$name} = time();
-       $count{'Fork'}++;
-       return 1;
+    while ( scalar keys %forked > 1 ) {    # 2 or more == fail.
+        sleep 1;
+
+        if ( $count > 3 ) {                # 3 seconds.
+            my $list = join( ', ', keys %forked );
+            if ( defined $who ) {
+                &msg( $who, "exceeded allowed forked count (shm $shm): $list" );
+            }
+            else {
+                &status(
+"Fork: I ran too many forked processes :) Giving up $name. Shm: $shm"
+                );
+            }
+
+            return 0;
+        }
+
+        $count++;
+    }
+
+    if ( exists $forked{$name} and !scalar keys %{ $forked{$name} } ) {
+        &WARN("addF: forked{$name} exists but is empty; deleting.");
+        undef $forked{$name};
+    }
+
+    if ( exists $forked{$name} and scalar keys %{ $forked{$name} } ) {
+        my $time     = $forked{$name}{Time};
+        my $continue = 0;
+
+        $continue++ if ( $forked{$name}{PID} == $$ );
+
+        if ($continue) {
+            &WARN("hrm.. fork pid == mypid == $$; how did this happen?");
+
+        }
+        elsif ( -d "/proc/$forked{$name}{PID}" ) {
+            &status('fork: still running; good. BAIL OUT.');
+            return 0;
+
+        }
+        else {
+            &WARN('Found dead fork; removing and resetting.');
+            $continue = 1;
+        }
+
+        if ($continue) {
+
+            # NOTHING.
+
+        }
+        elsif ( time() - $time > 900 ) {    # stale fork > 15m.
+            &status(
+                "forked: forked{$name} presumably exited without notifying us."
+            );
+
+        }
+        else {                              # fresh fork.
+            &msg( $who,
+                "$name is already running " . &Time2String( time() - $time ) );
+            return 0;
+        }
     }
+
+    $forked{$name}{Time} = time();
+    $forked{$name}{PID}  = $$;
+    $forkedtime          = time();
+    $count{'Fork'}++;
+    return 1;
 }
 
 sub delForked {
     my ($name) = @_;
 
-    if (exists $forked{$name}) {
-       my $timestr = &Time2String(time() - $forked{$name});
-       &status("fork: took $timestr for $name.");
-       &shmWrite($shm,"DELETE FORK $name");
-       return 1;
-    } else {
-       &ERROR("delForked: forked{$name} does not exist. should not happen.");
-       return 0;
+    return if ( $$ == $bot_pid );
+
+    if ( !defined $name ) {
+        &WARN('delForked: name == NULL.');
+        POSIX::_exit(0);
+    }
+
+    if ( $name =~ /\.pl/ ) {
+        &WARN("dF: name is name of source file ($name). FIX IT!");
+    }
+
+    &showProc();    # just for informational purposes.
+
+    if ( exists $forked{$name} ) {
+        my $timestr = &Time2String( time() - $forked{$name}{Time} );
+        &status("fork: took $timestr for $name.");
+        &shmWrite( $shm, "DELETE FORK $name" );
     }
+    else {
+        &ERROR("delForked: forked{$name} does not exist. should not happen.");
+    }
+
+    &status("--- fork finished for '$name' ---");
+
+    POSIX::_exit(0);
+}
+
+sub shmFlush {
+    return if ( $$ != $::bot_pid );    # fork protection.
+
+    if (@_) {
+        &ScheduleThis( 15 * 60, 'shmFlush' );    # 15 minutes
+        return if ( $_[0] eq '2' );
+    }
+
+    my $time;
+    my $shmmsg = &shmRead($shm);
+
+    # remove padded \0's.
+    $shmmsg =~ s/\0//g;
+    return if ( length($shmmsg) == 0 );
+    if ( $shmmsg =~ s/^(\S+):(\d+):(\d+): // ) {
+        my $n   = $1;
+        my $pid = $2;
+        $time = $3;
+    }
+    else {
+        &status("warn: shmmsg='$shmmsg'.");
+        return;
+    }
+
+    foreach ( split '\|\|', $shmmsg ) {
+        next if (/^$/);
+        &VERB( "shm: Processing '$_'.", 2 );
+
+        if (/^DCC SEND (\S+) (\S+)$/) {
+            my ( $nick, $file ) = ( $1, $2 );
+            if ( exists $dcc{'SEND'}{$who} ) {
+                &msg( $nick, 'DCC already active.' );
+            }
+            else {
+                &DEBUG("shm: dcc sending $2 to $1.");
+                $conn->new_send( $1, $2 );
+                $dcc{'SEND'}{$who} = time();
+            }
+        }
+        elsif (/^SET FORKPID (\S+) (\S+)/) {
+            $forked{$1}{PID} = $2;
+        }
+        elsif (/^DELETE FORK (\S+)$/) {
+            delete $forked{$1};
+        }
+        elsif (/^EVAL (.*)$/) {
+            &DEBUG("evaling '$1'.");
+            eval $1;
+        }
+        else {
+            &DEBUG("shm: unknown msg. ($_)");
+        }
+    }
+
+    &shmWrite( $shm, '' ) if ( $shmmsg ne '' );
 }
 
 1;
+
+# vim:ts=4:sw=4:expandtab:tw=80