Commit | Line | Data |
---|---|---|
cdf5b41f JL |
1 | #!/usr/bin/perl |
2 | ||
3 | use Config::General; | |
4 | use Data::Dumper; | |
5 | use Getopt::Std; | |
6 | use IO::Handle; | |
7 | use POSIX qw(setsid setuid setgid); | |
8 | use POSIX ":sys_wait_h"; | |
9 | ||
10 | ||
11 | ################# | |
12 | ## Global vars ## | |
13 | ################# | |
14 | ||
15 | my $confile; | |
16 | my %conf; | |
17 | my %running; | |
18 | my $reload_conf=0; | |
19 | my $debug=0; | |
20 | #For each process, uid and gid to set after fork | |
21 | my %gid; | |
22 | my %uid; | |
23 | ||
24 | #For sheduler | |
25 | #Number of launch, for each daemon | |
26 | my %launches; | |
27 | my %ratio_launch_prio; | |
28 | my %last_schedule; | |
29 | my $priority_ref = ""; | |
30 | ||
31 | ############### | |
32 | ## Functions ## | |
33 | ############### | |
34 | ||
35 | sub sigHUP_handler{ | |
36 | &debug_msg("got SIGHUP\n"); | |
37 | $reload_conf=1; | |
38 | } | |
39 | ||
40 | ||
41 | #Read Configuration and init global vars | |
42 | sub read_conf { | |
43 | my $file = shift; | |
44 | $config = new Config::General( -file =>"$file", | |
45 | -AllowMultiOptions =>"yes", | |
46 | -LowerCaseNames =>"yes",); | |
47 | %conf = $config->getall; | |
48 | &debug_msg(Dumper(\%conf)); | |
49 | } | |
50 | ||
51 | ||
52 | sub debug_msg(#){ | |
53 | my $msg=shift; | |
54 | if ($debug == 1){ | |
55 | print STDERR $msg; | |
56 | } | |
57 | } | |
58 | ||
59 | ||
60 | sub init { | |
61 | #getopts('hf:', \%opts) or die "Illegal program option. ($0 -h for list)\n"; | |
62 | #&debug_msg(Dumper(\%opts)); | |
63 | ||
64 | if ($confile eq ""){ | |
65 | my $path = `pwd`; | |
66 | chomp($path); | |
67 | if ($path){ | |
68 | $confile="$path/muxdaemon.conf"; | |
69 | }else{ | |
70 | print STDERR "Error : unable to get working directory, $!\n"; | |
71 | exit(1); | |
72 | } | |
73 | } | |
74 | ||
75 | if ( not getopts('dhf:', \%opts) or $opts{'h'}) { | |
76 | print STDERR "\nThis is mail processing Daemon :\n", | |
77 | "-h - this help message\n", | |
78 | "-f <file> - use <file> as config file\n", | |
79 | "-d - debug mode\n"; | |
80 | exit(1); | |
81 | } | |
82 | ||
83 | if ($opts{'f'}){ | |
84 | if (-r "$opts{'f'}") { | |
85 | print STDERR "Using $opts{'f'} as config file\n"; | |
86 | &read_conf("$opts{'f'}"); | |
87 | } else { | |
88 | print STDERR "$opts{'f'} : not a valid config file\n"; | |
89 | exit(1); | |
90 | } | |
91 | } else { | |
92 | print STDERR "Using $confile as config file\n"; | |
93 | if (-r "$confile") { | |
94 | &read_conf("$confile"); | |
95 | } else { | |
96 | print STDERR "No valid configuration file found, aborting\n"; | |
97 | exit(1); | |
98 | } | |
99 | } | |
100 | ||
101 | if ($opts{'d'}){ | |
102 | $debug = 1; | |
103 | }else{ | |
104 | $debug = 0; | |
105 | } | |
106 | ||
107 | } | |
108 | ||
109 | sub init_launches(){ | |
110 | my $min_priority=100; | |
111 | foreach my $arg (keys %{$conf{'client'}}){ | |
112 | $launches{"$arg"}=0; | |
113 | $ratio_launch_prio{"$arg"}=0; | |
114 | } | |
115 | } | |
116 | ||
117 | ||
118 | sub check_conf(){ | |
119 | print STDERR "Checking configuration file for wrong user/groups, schedules, ....\n"; | |
120 | foreach my $arg (keys %{$conf{'client'}}){ | |
121 | #La conf du client courant | |
122 | my $hash=${$conf{'client'}}{"$arg"}; | |
123 | if (${$hash}{'run_as_user'}){ | |
124 | my $id = getpwnam("${$hash}{'run_as_user'}"); | |
125 | if ($id){ | |
126 | $uid{"$arg"} = $id; | |
127 | }else{ | |
128 | print STDERR " Error, user ${$hash}{'run_as_user'} does not exist, please check...\n"; | |
129 | exit(1); | |
130 | } | |
131 | } | |
132 | if (${$hash}{'run_as_group'}){ | |
133 | my $id = getgrnam("${$hash}{'run_as_group'}"); | |
134 | if ($id){ | |
135 | $gid{"$arg"} = $id; | |
136 | }else{ | |
137 | print STDERR " Error, group ${$hash}{'run_as_group'} does not exist, please check...\n"; | |
138 | exit(1); | |
139 | } | |
140 | } | |
141 | if (${$hash}{'allow_many'} eq "yes" and ${$hash}{'min_schedule'}){ | |
142 | print STDERR " Error, allow_many and min_schedule defined for $arg\n"; | |
143 | exit(1); | |
144 | } | |
145 | } | |
146 | } | |
147 | ||
148 | ||
149 | sub daemonize { | |
150 | print STDERR "Forking ... \n"; | |
151 | chdir '/' or die "Can't chdir to /: $!"; | |
152 | open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; | |
153 | open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!"; | |
154 | if ($conf{'error_log'}){ | |
155 | open STDERR, ">>$conf{'error_log'}" or die "Can't write to $conf{'error_log'}: $!"; | |
156 | }else{ | |
157 | open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!"; | |
158 | } | |
159 | defined(my $pid = fork) or die "Can't fork: $!"; | |
160 | exit if $pid; | |
161 | setsid or die "Can't start a new session: $!"; | |
162 | umask 0; | |
163 | &debug_msg("Daemon started\n"); | |
164 | } | |
165 | ||
166 | sub get_load { | |
167 | my $load = `$conf{"ldcmd"}`; | |
168 | chop($load); | |
169 | #&debug_msg("$load\n"); | |
170 | return $load; | |
171 | } | |
172 | ||
173 | #Return the list of client for which max_load is => ld | |
174 | sub get_possible_client(#){ | |
175 | my $ld=shift; | |
176 | my @list=(); | |
177 | #&debug_msg("get_possible_client, load = $ld\n"); | |
178 | foreach my $arg (keys %{$conf{'client'}}){ | |
179 | #&debug_msg("get_possible_client : Trying client $arg :"); | |
180 | my $hash = ${$conf{'client'}}{$arg}; | |
181 | if (${$hash}{'max_load'} >= $ld){ | |
182 | #&debug_msg("max_load = ".${$hash}{'max_load'}." => in\n"); | |
183 | push(@list,$arg); | |
184 | }else{ | |
185 | #&debug_msg("max_load = ".${$hash}{'max_load'}." => out\n"); | |
186 | } | |
187 | } | |
188 | return \@list; | |
189 | } | |
190 | ||
191 | ||
192 | #Return the client that should be executed according to priorities | |
193 | #given in conf file | |
194 | # Refaire avec calcul du min de launches/prio ! | |
195 | sub get_next(#){ | |
196 | my $ref=shift; | |
197 | my @possible_client = @{$ref}; | |
198 | #List des proc executes assez souvent | |
199 | my $client=""; | |
200 | my $min_ratio=-1; | |
201 | ||
202 | foreach my $arg (@possible_client){ | |
203 | if ($ratio_launch_prio{"$arg"} < $min_ratio or $min_ratio == -1){ | |
204 | $min_ratio=$ratio_launch_prio{"$arg"}; | |
205 | $client=$arg; | |
206 | } | |
207 | } | |
208 | ||
209 | if ($client){ | |
210 | #Update ratio for him | |
211 | $launches{"$client"}++; | |
212 | $ratio_launch_prio{"$client"}=$launches{"$client"}/${${$conf{'client'}}{$client}}{'priority'}; | |
213 | return $client; | |
214 | }else{ | |
215 | &debug_msg("No client runnable in get_next\n"); | |
216 | return; | |
217 | } | |
218 | return; | |
219 | } | |
220 | ||
221 | #Elimine les clients présent dans la liste qui sont en cours d'éxécution et | |
222 | #qui n'ont pas le allow_many (pour pas les éxécuter deux fois) | |
223 | #Elimine aussi les clients qui ont terminé depuis moins que min_schedule | |
224 | sub trim_possible_client(#){ | |
225 | my $ref=shift; | |
226 | my @out_list; | |
227 | ||
228 | ||
229 | foreach my $arg (@$ref){ | |
230 | if (${${$conf{'client'}}{$arg}}{'allow_many'} eq "no"){ | |
231 | #Exec only one | |
232 | if ((time - $last_schedule{$arg}) >= ${${$conf{'client'}}{$arg}}{'min_schedule'}){ | |
233 | push(@out_list,$arg) unless grep($_ eq $arg, values %running); | |
234 | } | |
235 | }else{ | |
236 | push(@out_list,$arg); | |
237 | } | |
238 | } | |
239 | ||
240 | return \@out_list; | |
241 | } | |
242 | ||
243 | ||
244 | sub change_uid_gid(#){ | |
245 | my $client=shift; | |
246 | if ($gid{"$client"}){ | |
247 | &debug_msg("Changing gid to $gid{$client} for $client ..."); | |
248 | if (POSIX::setgid($gid{"$client"})){ | |
249 | &debug_msg("done\n"); | |
250 | }else{ | |
251 | &debug_msg("error, $!\n"); | |
252 | exit(1); | |
253 | } | |
254 | } | |
255 | if ($uid{"$client"}){ | |
256 | &debug_msg("Changing uid to $uid{$client} for $client ..."); | |
257 | if (POSIX::setuid($uid{"$client"})){ | |
258 | &debug_msg("done\n"); | |
259 | }else{ | |
260 | &debug_msg("error, $!\n"); | |
261 | exit(1); | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | ||
267 | #Sends mail on the input of the command (pipe) | |
268 | sub send_data(#){ | |
269 | my $client=shift; | |
270 | if (${${$conf{'client'}}{$client}}{'mbox'} =~ /\/$/){ | |
271 | &debug_msg("Sending datas for ${${$conf{'client'}}{$client}}{'mbox'}\n"); | |
272 | my @list=`ls -1 ${${$conf{'client'}}{$client}}{'mbox'}/new | head -n ${${$conf{'client'}}{$client}}{'nb_mails'}`; | |
273 | if ($#list >= 0){ | |
274 | #Send files, one by one | |
275 | foreach my $fich (@list){ | |
276 | &debug_msg("File : $fich\n"); | |
277 | if (my $pid = open(CHILD, "|-")) { | |
278 | CHILD->autoflush(1); | |
279 | &debug_msg("Parent Pid $$\n"); | |
280 | if (open(DATA,"<${${$conf{'client'}}{$client}}{'mbox'}/new/$fich")){ | |
281 | while (my $line=<DATA>){ | |
282 | print CHILD "$line"; | |
283 | #&debug_msg("Sending : $line"); | |
284 | } | |
285 | close(DATA); | |
286 | }else{ | |
287 | &debug_msg("Error opening ${${$conf{'client'}}{$client}}{'mbox'}/new/$fich : $!\n"); | |
288 | exit(1); | |
289 | } | |
290 | close(CHILD); | |
291 | #unlink("${${$conf{'client'}}{$client}}{'mbox'}/new/$fich"); | |
292 | } else { | |
293 | die "cannot fork: $!" unless defined $pid; | |
294 | &change_uid_gid("$client"); | |
295 | &debug_msg("Executing ${${$conf{'client'}}{$client}}{'command'} .... \n"); | |
296 | exec("${${$conf{'client'}}{$client}}{'command'}"); | |
297 | } | |
298 | } | |
299 | }else{ | |
300 | &debug_msg("No mail available\n"); | |
301 | } | |
302 | }else{ | |
303 | &debug_msg("Mbox format not yet supported.\n"); | |
304 | exit(1); | |
305 | } | |
306 | } | |
307 | ||
308 | ||
309 | ||
310 | sub launch(#){ | |
311 | my $client=shift; | |
312 | #Launch the command of a simple client | |
313 | &debug_msg("Son $client, launching ${${$conf{'client'}}{$client}}{'command'}\n"); | |
314 | #Change uid and gid if needed | |
315 | &change_uid_gid("$client"); | |
316 | exec(${${$conf{'client'}}{$client}}{'command'}); | |
317 | } | |
318 | ||
319 | ||
320 | sub main_loop { | |
321 | ||
322 | my $ld; | |
323 | my $nchild; | |
324 | ||
325 | while (1) { | |
326 | ||
327 | if ($reload_conf == 1){ | |
328 | #We have to load conf again | |
329 | $reload_conf=0; | |
330 | &debug_msg("Reloading configuration .... \n"); | |
331 | &debug_msg(" Waiting all child to terminate ..."); | |
332 | while ((my $kid=wait) != -1){ | |
333 | $nchild--; | |
334 | delete($running{$kid}); | |
335 | } | |
336 | &debug_msg("done\n"); | |
337 | &debug_msg(" Restarting ..."); | |
338 | &init; | |
339 | &init_launches; | |
340 | &debug_msg("done\n"); | |
341 | } | |
342 | ||
343 | ||
344 | if ($nchild >= $conf{'max_client'}){ | |
345 | #We have to wait for one child (Blocking)" | |
346 | &debug_msg("Waiting for childs to terminate\n"); | |
347 | my $kid=wait; | |
348 | if ($kid>0){ | |
349 | &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n"); | |
350 | $nchild--; | |
351 | delete($running{$kid}); | |
352 | } | |
353 | }else{ | |
354 | #Rip child terminated | |
355 | &debug_msg("Checking dead childs....\n"); | |
356 | while ( (my $kid = waitpid(-1, WNOHANG)) > 0 ){ | |
357 | &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n"); | |
358 | $nchild--; | |
359 | delete($running{$kid}); | |
360 | } | |
361 | ||
362 | #Look for a client to execute | |
363 | my $ld = &get_load; | |
364 | &debug_msg("Load = $ld, processing\n"); | |
365 | my $possible_client=&get_possible_client($ld); | |
366 | my @list=@{$possible_client}; | |
367 | &debug_msg("Avant Trim : \n"); | |
368 | &debug_msg(Dumper($possible_client)); | |
369 | $possible_client=&trim_possible_client($possible_client); | |
370 | &debug_msg("Après Trim : \n"); | |
371 | &debug_msg(Dumper($possible_client)); | |
372 | my $client=&get_next($possible_client); | |
373 | if ($client){ | |
374 | #sleep(1); | |
375 | &debug_msg("$client was chosen, launching\n"); | |
376 | $last_schedule{$client} = time ; | |
377 | if (my $pid = fork){ | |
378 | #Pere, on marque qu'on l'a lancé | |
379 | $running{$pid}="$client"; | |
380 | $nchild++; | |
381 | &debug_msg("$client launched as PID $pid\n"); | |
382 | &debug_msg(Dumper(\%running)); | |
383 | &debug_msg(Dumper(\%launches)); | |
384 | }else{ | |
385 | #Fils | |
386 | if (${${$conf{'client'}}{$client}}{'mbox'}){ | |
387 | #We have to send data ourselves | |
388 | &send_data("$client"); | |
389 | }else{ | |
390 | #We only have to execute a command | |
391 | &launch("$client"); | |
392 | } | |
393 | exit(0); | |
394 | } | |
395 | }else{ | |
396 | &debug_msg("No client runable\n"); | |
397 | sleep($conf{'sleep_poll'}); | |
398 | } | |
399 | sleep($conf{'active_poll'}); | |
400 | } | |
401 | } | |
402 | } | |
403 | ||
404 | ||
405 | ||
406 | ########## | |
407 | ## Main ## | |
408 | ########## | |
409 | ||
410 | $SIG{HUP}=\&sigHUP_handler; | |
411 | &init(); | |
412 | &init_launches(); | |
413 | &check_conf(); | |
414 | &daemonize(); | |
415 | &main_loop(); | |
416 | ||
417 |