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