Initial revision
[old-projects.git] / muxdaemon / muxdaemon.pl
CommitLineData
cdf5b41f
JL
1#!/usr/bin/perl
2
3use Config::General;
4use Data::Dumper;
5use Getopt::Std;
6use IO::Handle;
7use POSIX qw(setsid setuid setgid);
8use POSIX ":sys_wait_h";
9
10
11#################
12## Global vars ##
13#################
14
15my $confile;
16my %conf;
17my %running;
18my $reload_conf=0;
19my $debug=0;
20#For each process, uid and gid to set after fork
21my %gid;
22my %uid;
23
24#For sheduler
25#Number of launch, for each daemon
26my %launches;
27my %ratio_launch_prio;
28my %last_schedule;
29my $priority_ref = "";
30
31###############
32## Functions ##
33###############
34
35sub sigHUP_handler{
36 &debug_msg("got SIGHUP\n");
37 $reload_conf=1;
38}
39
40
41#Read Configuration and init global vars
42sub 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
52sub debug_msg(#){
53 my $msg=shift;
54 if ($debug == 1){
55 print STDERR $msg;
56 }
57}
58
59
60sub 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
109sub 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
118sub 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
149sub 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
166sub 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
174sub 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 !
195sub 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
224sub 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
244sub 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)
268sub 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
310sub 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
320sub 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