]> git.donarmstrong.com Git - infobot.git/blob - src/Shm.pl
helpful debug message
[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, '', $position, $size)) {
91         &ERROR("shmWrite: failed: $!");
92     }
93 }
94
95 ##############
96 ### Helpers
97 ###
98
99 # Usage: &addForked($name);
100 # Return: 1 for success, 0 for failure.
101 sub addForked {
102     my ($name)          = @_;
103     my $forker_timeout  = 360;  # 6mins, in seconds.
104     $forker             = $name;
105
106     if (!defined $name) {
107         &WARN("addForked: name == NULL.");
108         return 0;
109     }
110
111     foreach (keys %forked) {
112         my $n = $_;
113         my $time = time() - $forked{$n}{Time};
114         next unless ($time > $forker_timeout);
115
116         ### TODO: use &time2string()?
117         &WARN("Fork: looks like we lost '$n', executed $time ago");
118
119         my $pid = $forked{$n}{PID};
120         if (!defined $pid) {
121             &WARN("Fork: no pid for $n.");
122             delete $forked{$n};
123             next;
124         }
125
126         if ($pid == $bot_pid) {
127             # don't kill parent, just warn.
128             &status("Fork: pid == \$bot_pid == \$\$ ($bot_pid)");
129
130         } elsif ( -d "/proc/$pid") {    # pid != bot_pid.
131             &status("Fork: killing $name ($pid)");
132             kill 9, $pid;
133         }
134
135         delete $forked{$n};
136     }
137
138     my $count = 0;
139     while (scalar keys %forked > 1) {   # 2 or more == fail.
140         sleep 1;
141
142         if ($count > 3) {       # 3 seconds.
143             my $list = join(', ', keys %forked);
144             if (defined $who) {
145                 &msg($who, "exceeded allowed forked count: $list");
146             } else {
147                 &status("Fork: I ran too many forked processes :) Giving up $name.");
148             }
149
150             return 0;
151         }
152
153         $count++;
154     }
155
156     if (exists $forked{$name} and !scalar keys %{ $forked{$name} }) {
157         &WARN("addF: forked{$name} exists but is empty; deleting.");
158         undef $forked{$name};
159     }
160
161     if (exists $forked{$name} and scalar keys %{ $forked{$name} }) {
162         my $time        = $forked{$name}{Time};
163         my $continue    = 0;
164
165         $continue++ if ($forked{$name}{PID} == $$);
166
167         if ($continue) {
168             &WARN("hrm.. fork pid == mypid == $$; how did this happen?");
169
170         } elsif ( -d "/proc/$forked{$name}{PID}") {
171             &status("fork: still running; good. BAIL OUT.");
172             return 0;
173
174         } else {
175             &WARN("Found dead fork; removing and resetting.");
176             $continue = 1;
177         }
178
179         if ($continue) {
180             # NOTHING.
181
182         } elsif (time() - $time > 900) {        # stale fork > 15m.
183             &status("forked: forked{$name} presumably exited without notifying us.");
184
185         } else {                                # fresh fork.
186             &msg($who, "$name is already running ". &Time2String(time() - $time));
187             return 0;
188         }
189     }
190
191     $forked{$name}{Time}        = time();
192     $forked{$name}{PID}         = $$;
193     $forkedtime                 = time();
194     $count{'Fork'}++;
195     return 1;
196 }
197
198 sub delForked {
199     my ($name)  = @_;
200
201     return if ($$ == $bot_pid);
202
203     if (!defined $name) {
204         &WARN("delForked: name == NULL.");
205         POSIX::_exit(0);
206     }
207
208     if ($name =~ /\.pl/) {
209         &WARN("dF: name is name of source file ($name). FIX IT!");
210     }
211
212     &showProc();        # just for informational purposes.
213
214     if (exists $forked{$name}) {
215         my $timestr = &Time2String(time() - $forked{$name}{Time});
216         &status("fork: took $timestr for $name.");
217         &shmWrite($shm,"DELETE FORK $name");
218     } else {
219         &ERROR("delForked: forked{$name} does not exist. should not happen.");
220     }
221
222     &status("--- fork finished for '$name' ---");
223
224     POSIX::_exit(0);
225 }
226
227 sub shmFlush {
228     return if ($$ != $::bot_pid); # fork protection.
229
230     if (@_) {
231         &ScheduleThis(5, "shmFlush");
232         return if ($_[0] eq "2");
233     }
234
235     my $time;
236     my $shmmsg = &shmRead($shm);
237     $shmmsg =~ s/\0//g;         # remove padded \0's.
238     return if (length($shmmsg) == 0);
239     if ($shmmsg =~ s/^(\S+):(\d+):(\d+): //) {
240         my $n   = $1;
241         my $pid = $2;
242         $time   = $3;
243     } else {
244         &status("warn: shmmsg='$shmmsg'.");
245         return;
246     }
247
248     foreach (split '\|\|', $shmmsg) {
249         next if (/^$/);
250         &VERB("shm: Processing '$_'.",2);
251
252         if (/^DCC SEND (\S+) (\S+)$/) {
253             my ($nick,$file) = ($1,$2);
254             if (exists $dcc{'SEND'}{$who}) {
255                 &msg($nick, "DCC already active.");
256             } else {
257                 &DEBUG("shm: dcc sending $2 to $1.");
258                 $conn->new_send($1,$2);
259                 $dcc{'SEND'}{$who} = time();
260             }
261         } elsif (/^SET FORKPID (\S+) (\S+)/) {
262             $forked{$1}{PID} = $2;
263         } elsif (/^DELETE FORK (\S+)$/) {
264             delete $forked{$1};
265         } elsif (/^EVAL (.*)$/) {
266             &DEBUG("evaling '$1'.");
267             eval $1;
268         } else {
269             &DEBUG("shm: unknown msg. ($_)");
270         }
271     }
272
273     &shmWrite($shm,"") if ($shmmsg ne "");
274 }
275
276 1;