]> git.donarmstrong.com Git - infobot.git/blob - src/Shm.pl
ws
[infobot.git] / src / Shm.pl
1 #
2 #   Shm.pl: Shared Memory stuff.
3 #    Author: dms
4 #   Version: 20000201
5 #   Created: 20000124
6 #
7
8 # use strict;   # TODO
9
10 use POSIX qw(_exit);
11
12 sub openSHM {
13     my $IPC_PRIVATE = 0;
14     my $size = 2000;
15
16     if (&IsParam("noSHM")) {
17         &status("Shared memory: Disabled. WARNING: bot may become unreliable");
18         return 0;
19     }
20
21     if (defined( $_ = shmget($IPC_PRIVATE, $size, 0777) )) {
22         &status("Created shared memory (shm) key: [$_]");
23         return $_;
24     } else {
25         &ERROR("openSHM: failed.");
26         &ERROR("Please delete some shared memory with ipcs or ipcrm.");
27         exit 1;
28     }
29 }
30
31 sub closeSHM {
32     my ($key) = @_;
33     my $IPC_RMID = 0;
34
35     return '' if (!defined $key);
36
37     &shmFlush();
38     &status("Closed shared memory (shm) key: [$key]");
39     return shmctl($key, $IPC_RMID, 0);
40 }
41
42 sub shmRead {
43     my ($key) = @_;
44     my $position = 0;
45     my $size = 3*80;
46     my $retval = '';
47
48     return '' if (&IsParam("noSHM"));
49
50     if (shmread($key,$retval,$position,$size)) {
51         &DEBUG("shmRead($key): $retval");
52         return $retval;
53     } else {
54         &ERROR("shmRead: failed: $!");
55         ### TODO: if this fails, never try again.
56         &openSHM();
57         return '';
58     }
59 }
60
61 sub shmWrite {
62     my ($key, $str) = @_;
63     my $position = 0;
64     my $size = 80*3;
65
66     return if (&IsParam("noSHM"));
67
68     if (length($str) > $size) {
69         &status("ERROR: length(str) (..)>$size...");
70         return;
71     }
72
73     if (length($str) == 0) {
74         # does $size overwrite the whole lot?
75         # if not, set to 2000.
76         if (!shmwrite($key, '', $position, $size)) {
77             &ERROR("shmWrite: failed: $!");
78         }
79         return;
80     }
81
82     my $read = &shmRead($key);
83     $read =~ s/\0+//g;
84     if ($read eq "") {
85         $str = sprintf("%s:%d:%d: ", $param{ircNick}, $bot_pid, time());
86     } else {
87         $str = $read ."||". $str;
88     }
89
90     if (!shmwrite($key, $str, $position, $size)) {
91         &DEBUG("shmWrite($key, $str)");
92         &ERROR("shmWrite: failed: $!");
93     }
94 }
95
96 ##############
97 ### Helpers
98 ###
99
100 # Usage: &addForked($name);
101 # Return: 1 for success, 0 for failure.
102 sub addForked {
103     my ($name)          = @_;
104     my $forker_timeout  = 360;  # 6mins, in seconds.
105     $forker             = $name;
106
107     if (!defined $name) {
108         &WARN("addForked: name == NULL.");
109         return 0;
110     }
111
112     foreach (keys %forked) {
113         my $n = $_;
114         my $time = time() - $forked{$n}{Time};
115         next unless ($time > $forker_timeout);
116
117         ### TODO: use &time2string()?
118         &WARN("Fork: looks like we lost '$n', executed $time ago");
119
120         my $pid = $forked{$n}{PID};
121         if (!defined $pid) {
122             &WARN("Fork: no pid for $n.");
123             delete $forked{$n};
124             next;
125         }
126
127         if ($pid == $bot_pid) {
128             # don't kill parent, just warn.
129             &status("Fork: pid == \$bot_pid == \$\$ ($bot_pid)");
130
131         } elsif ( -d "/proc/$pid") {    # pid != bot_pid.
132             &status("Fork: killing $name ($pid)");
133             kill 9, $pid;
134         }
135
136         delete $forked{$n};
137     }
138
139     my $count = 0;
140     while (scalar keys %forked > 1) {   # 2 or more == fail.
141         sleep 1;
142
143         if ($count > 3) {       # 3 seconds.
144             my $list = join(', ', keys %forked);
145             if (defined $who) {
146                 &msg($who, "exceeded allowed forked count (shm $shm): $list");
147             } else {
148                 &status("Fork: I ran too many forked processes :) Giving up $name. Shm: $shm");
149             }
150
151             return 0;
152         }
153
154         $count++;
155     }
156
157     if (exists $forked{$name} and !scalar keys %{ $forked{$name} }) {
158         &WARN("addF: forked{$name} exists but is empty; deleting.");
159         undef $forked{$name};
160     }
161
162     if (exists $forked{$name} and scalar keys %{ $forked{$name} }) {
163         my $time        = $forked{$name}{Time};
164         my $continue    = 0;
165
166         $continue++ if ($forked{$name}{PID} == $$);
167
168         if ($continue) {
169             &WARN("hrm.. fork pid == mypid == $$; how did this happen?");
170
171         } elsif ( -d "/proc/$forked{$name}{PID}") {
172             &status("fork: still running; good. BAIL OUT.");
173             return 0;
174
175         } else {
176             &WARN("Found dead fork; removing and resetting.");
177             $continue = 1;
178         }
179
180         if ($continue) {
181             # NOTHING.
182
183         } elsif (time() - $time > 900) {        # stale fork > 15m.
184             &status("forked: forked{$name} presumably exited without notifying us.");
185
186         } else {                                # fresh fork.
187             &msg($who, "$name is already running ". &Time2String(time() - $time));
188             return 0;
189         }
190     }
191
192     $forked{$name}{Time}        = time();
193     $forked{$name}{PID}         = $$;
194     $forkedtime                 = time();
195     $count{'Fork'}++;
196     return 1;
197 }
198
199 sub delForked {
200     my ($name)  = @_;
201
202     return if ($$ == $bot_pid);
203
204     if (!defined $name) {
205         &WARN("delForked: name == NULL.");
206         POSIX::_exit(0);
207     }
208
209     if ($name =~ /\.pl/) {
210         &WARN("dF: name is name of source file ($name). FIX IT!");
211     }
212
213     &showProc();        # just for informational purposes.
214
215     if (exists $forked{$name}) {
216         my $timestr = &Time2String(time() - $forked{$name}{Time});
217         &status("fork: took $timestr for $name.");
218         &shmWrite($shm,"DELETE FORK $name");
219     } else {
220         &ERROR("delForked: forked{$name} does not exist. should not happen.");
221     }
222
223     &status("--- fork finished for '$name' ---");
224
225     POSIX::_exit(0);
226 }
227
228 sub shmFlush {
229     return if ($$ != $::bot_pid); # fork protection.
230
231     if (@_) {
232         &ScheduleThis(5, "shmFlush");
233         return if ($_[0] eq "2");
234     }
235
236     my $time;
237     my $shmmsg = &shmRead($shm);
238     # remove padded \0's.
239     $shmmsg =~ s/\0//g;
240     return if (length($shmmsg) == 0);
241     if ($shmmsg =~ s/^(\S+):(\d+):(\d+): //) {
242         my $n   = $1;
243         my $pid = $2;
244         $time   = $3;
245     } else {
246         &status("warn: shmmsg='$shmmsg'.");
247         return;
248     }
249
250     foreach (split '\|\|', $shmmsg) {
251         next if (/^$/);
252         &VERB("shm: Processing '$_'.",2);
253
254         if (/^DCC SEND (\S+) (\S+)$/) {
255             my ($nick,$file) = ($1,$2);
256             if (exists $dcc{'SEND'}{$who}) {
257                 &msg($nick, "DCC already active.");
258             } else {
259                 &DEBUG("shm: dcc sending $2 to $1.");
260                 $conn->new_send($1,$2);
261                 $dcc{'SEND'}{$who} = time();
262             }
263         } elsif (/^SET FORKPID (\S+) (\S+)/) {
264             $forked{$1}{PID} = $2;
265         } elsif (/^DELETE FORK (\S+)$/) {
266             delete $forked{$1};
267         } elsif (/^EVAL (.*)$/) {
268             &DEBUG("evaling '$1'.");
269             eval $1;
270         } else {
271             &DEBUG("shm: unknown msg. ($_)");
272         }
273     }
274
275     &shmWrite($shm,"") if ($shmmsg ne "");
276 }
277
278 1;