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