Initial revision
authorJulien Lerouge <julien.lerouge@m4x.org>
Fri, 22 Nov 2002 00:40:14 +0000 (00:40 +0000)
committerJulien Lerouge <julien.lerouge@m4x.org>
Fri, 22 Nov 2002 00:40:14 +0000 (00:40 +0000)
muxdaemon/muxdaemon.conf [new file with mode: 0644]
muxdaemon/muxdaemon.pl [new file with mode: 0755]

diff --git a/muxdaemon/muxdaemon.conf b/muxdaemon/muxdaemon.conf
new file mode 100644 (file)
index 0000000..e9d73e2
--- /dev/null
@@ -0,0 +1,65 @@
+#Cette commande doit renvoyer la charge
+ldcmd = "cat /proc/loadavg | sed 's/^\([^ ]* \).*$/\1/'"
+
+#error_log
+error_log = "/home/julien/toto"
+
+#Poll every 60 seconds
+#Active poll : Time to wait between 2 launches in sec
+active_poll = 0
+
+#Time to wait when load is too high for any client in sec
+sleep_poll = 60
+
+#Nombre max de client à ne pas dépasser simultanément
+max_client = 1
+
+<client erreurs>
+       #Charge au dessus de laquelle ce programme ne doit plus être lancé
+       max_load=0.3
+       #Mbox
+       #Si mbox existe, des lots de nb_mails y sont lus et pipés dans command
+       #si mbox termine par un "/", c'est une boite maildir
+       #Pas encore implémenté
+       mbox=/var/mail/xorg-errors/
+       #nombre de mails à traité ((1 command executée par mail)
+       nb_mails=50
+       #Command to launch (datas are piped in it if mbox defined)
+       command="cat"
+       #Peut-on lancer plusieurs instances de ce prog simultanément ? yes or no
+       allow_many=no
+       #Priority relative aux autres de 1 à 100
+       #Le client de priorité 100 est lancé 100 fois plus souvent que celui de
+       #priorité 1 (si possible)
+       priority=100
+       #Lancer le prog en tant que :
+       run_as_user=julien
+       run_as_group=julien
+       #Temps minimum entre deux lancements d'un client en sec
+       min_schedule=0
+</client>
+
+
+<client toto>
+       max_load=0.3
+       command="/bin/sleep 1"
+       allow_many=no
+       priority=99
+</client>
+
+<client titi>
+       max_load=0.3
+       command="/bin/sleep 3"
+       allow_many=no
+       priority=57
+       run_as_user=jb
+       run_as_group=jb
+</client>
+
+<client tutu>
+       max_load=0.3
+       command="/bin/sleep 2"
+       allow_many=no
+       priority=69
+</client>
+
diff --git a/muxdaemon/muxdaemon.pl b/muxdaemon/muxdaemon.pl
new file mode 100755 (executable)
index 0000000..875aff0
--- /dev/null
@@ -0,0 +1,417 @@
+#!/usr/bin/perl
+
+use Config::General;
+use Data::Dumper;
+use Getopt::Std;
+use IO::Handle;
+use POSIX qw(setsid setuid setgid);
+use POSIX ":sys_wait_h";
+
+
+#################
+## Global vars ## 
+#################
+
+my $confile;
+my %conf;
+my %running;
+my $reload_conf=0;
+my $debug=0;
+#For each process, uid and gid to set after fork
+my %gid;
+my %uid;
+
+#For sheduler
+#Number of launch, for each daemon
+my %launches;
+my %ratio_launch_prio;
+my %last_schedule;
+my $priority_ref = "";
+
+###############
+## Functions ##
+###############
+
+sub sigHUP_handler{
+   &debug_msg("got SIGHUP\n");
+   $reload_conf=1;
+}
+   
+
+#Read Configuration and init global vars
+sub read_conf {
+   my $file = shift;
+   $config = new Config::General( -file =>"$file", 
+                                 -AllowMultiOptions =>"yes",
+                                 -LowerCaseNames =>"yes",);
+   %conf = $config->getall;
+   &debug_msg(Dumper(\%conf));
+}
+
+
+sub debug_msg(#){
+   my $msg=shift;
+   if ($debug == 1){
+      print STDERR $msg;
+   }
+}
+   
+
+sub init {
+   #getopts('hf:', \%opts) or die "Illegal program option. ($0 -h for list)\n";
+   #&debug_msg(Dumper(\%opts));
+
+   if ($confile eq ""){
+      my $path = `pwd`;
+      chomp($path);
+      if ($path){
+        $confile="$path/muxdaemon.conf";
+      }else{
+        print STDERR "Error : unable to get working directory, $!\n";
+        exit(1);
+      }
+   }
+
+   if ( not getopts('dhf:', \%opts) or $opts{'h'}) {
+      print STDERR "\nThis is mail processing Daemon :\n",
+                   "-h          - this help message\n", 
+                  "-f <file>   - use <file> as config file\n",
+                  "-d          - debug mode\n";
+      exit(1);
+   }
+
+   if ($opts{'f'}){
+      if (-r "$opts{'f'}") {
+        print STDERR "Using $opts{'f'} as config file\n";
+        &read_conf("$opts{'f'}");
+      } else {
+        print STDERR "$opts{'f'} : not a valid config file\n";
+        exit(1);
+      }
+   } else {
+      print STDERR "Using $confile as config file\n";
+      if (-r "$confile") {
+        &read_conf("$confile");
+      } else {
+        print STDERR "No valid configuration file found, aborting\n";
+        exit(1);
+      }
+   }
+
+   if ($opts{'d'}){
+      $debug = 1;
+   }else{
+      $debug = 0;
+   }
+
+}
+
+sub init_launches(){
+   my $min_priority=100;
+   foreach my $arg (keys %{$conf{'client'}}){
+      $launches{"$arg"}=0;
+      $ratio_launch_prio{"$arg"}=0;
+   }
+}
+
+
+sub check_conf(){
+   print STDERR "Checking configuration file for wrong user/groups, schedules, ....\n";
+   foreach my $arg (keys %{$conf{'client'}}){
+      #La conf du client courant
+      my $hash=${$conf{'client'}}{"$arg"};
+      if (${$hash}{'run_as_user'}){
+        my $id = getpwnam("${$hash}{'run_as_user'}");
+        if ($id){
+           $uid{"$arg"} = $id;
+        }else{
+           print STDERR "  Error, user ${$hash}{'run_as_user'} does not exist, please check...\n";
+           exit(1);
+        }
+      }
+      if (${$hash}{'run_as_group'}){
+        my $id = getgrnam("${$hash}{'run_as_group'}");
+        if ($id){
+           $gid{"$arg"} = $id;
+        }else{
+           print STDERR "  Error, group ${$hash}{'run_as_group'} does not exist, please check...\n";
+           exit(1);
+        }
+      }
+      if (${$hash}{'allow_many'} eq "yes" and ${$hash}{'min_schedule'}){
+        print STDERR "  Error, allow_many and min_schedule defined for $arg\n";
+        exit(1);
+      }
+   }
+}
+
+   
+sub daemonize {
+   print STDERR "Forking ... \n";
+   chdir '/' or die "Can't chdir to /: $!";
+   open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
+   open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
+   if ($conf{'error_log'}){
+      open STDERR, ">>$conf{'error_log'}" or die "Can't write to $conf{'error_log'}: $!";
+   }else{
+      open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
+   }
+   defined(my $pid = fork) or die "Can't fork: $!";
+   exit if $pid;
+   setsid or die "Can't start a new session: $!";
+   umask 0;
+   &debug_msg("Daemon started\n");
+}
+
+sub get_load {
+   my $load = `$conf{"ldcmd"}`;
+   chop($load);
+   #&debug_msg("$load\n");
+   return $load;
+}
+
+#Return the list of client for which max_load is => ld
+sub get_possible_client(#){
+   my $ld=shift;
+   my @list=();
+   #&debug_msg("get_possible_client, load = $ld\n");
+   foreach my $arg (keys %{$conf{'client'}}){
+      #&debug_msg("get_possible_client : Trying client $arg :");
+      my $hash = ${$conf{'client'}}{$arg};
+      if (${$hash}{'max_load'} >= $ld){
+        #&debug_msg("max_load = ".${$hash}{'max_load'}." => in\n");
+        push(@list,$arg);
+      }else{
+        #&debug_msg("max_load = ".${$hash}{'max_load'}." => out\n");
+      }
+   }
+   return \@list;
+}
+   
+
+#Return the client that should be executed according to priorities
+#given in conf file
+# Refaire avec calcul du min de launches/prio !
+sub get_next(#){
+   my $ref=shift;
+   my @possible_client = @{$ref};
+   #List des proc executes assez souvent
+   my $client="";
+   my $min_ratio=-1;
+   
+   foreach my $arg (@possible_client){
+      if ($ratio_launch_prio{"$arg"} < $min_ratio or $min_ratio == -1){
+        $min_ratio=$ratio_launch_prio{"$arg"};
+        $client=$arg;
+      }
+   }
+
+   if ($client){
+      #Update ratio for him
+      $launches{"$client"}++;
+      $ratio_launch_prio{"$client"}=$launches{"$client"}/${${$conf{'client'}}{$client}}{'priority'};
+      return $client;
+   }else{
+      &debug_msg("No client runnable in get_next\n");
+      return;
+   }
+   return;
+}
+
+#Elimine les clients présent dans la liste qui sont en cours d'éxécution et
+#qui n'ont pas le allow_many (pour pas les éxécuter deux fois)
+#Elimine aussi les clients qui ont terminé depuis moins que min_schedule
+sub trim_possible_client(#){
+   my $ref=shift;
+   my @out_list;
+
+
+   foreach my $arg (@$ref){
+      if (${${$conf{'client'}}{$arg}}{'allow_many'} eq "no"){
+        #Exec only one
+        if ((time - $last_schedule{$arg}) >= ${${$conf{'client'}}{$arg}}{'min_schedule'}){
+           push(@out_list,$arg) unless grep($_ eq $arg, values %running);
+        }
+      }else{
+        push(@out_list,$arg);
+      }
+   }
+
+   return \@out_list;
+}
+
+
+sub change_uid_gid(#){
+   my $client=shift;
+   if ($gid{"$client"}){
+      &debug_msg("Changing gid to $gid{$client} for $client ...");
+      if (POSIX::setgid($gid{"$client"})){
+        &debug_msg("done\n");
+      }else{
+        &debug_msg("error, $!\n");
+        exit(1);
+      }
+   }
+   if ($uid{"$client"}){
+      &debug_msg("Changing uid to $uid{$client} for $client ...");
+      if (POSIX::setuid($uid{"$client"})){
+        &debug_msg("done\n");
+      }else{
+        &debug_msg("error, $!\n");
+        exit(1);
+      }
+   }
+}
+
+
+#Sends mail on the input of the command (pipe)
+sub send_data(#){
+   my $client=shift;
+   if (${${$conf{'client'}}{$client}}{'mbox'} =~ /\/$/){
+      &debug_msg("Sending datas for ${${$conf{'client'}}{$client}}{'mbox'}\n");
+      my @list=`ls -1 ${${$conf{'client'}}{$client}}{'mbox'}/new | head -n ${${$conf{'client'}}{$client}}{'nb_mails'}`;
+      if ($#list >= 0){
+        #Send files, one by one
+        foreach my $fich (@list){
+           &debug_msg("File : $fich\n");
+           if (my $pid = open(CHILD, "|-")) {
+              CHILD->autoflush(1);
+              &debug_msg("Parent Pid $$\n");
+              if (open(DATA,"<${${$conf{'client'}}{$client}}{'mbox'}/new/$fich")){
+                 while (my $line=<DATA>){
+                    print CHILD "$line";
+                    #&debug_msg("Sending : $line");
+                 }
+                 close(DATA);
+              }else{
+                 &debug_msg("Error opening ${${$conf{'client'}}{$client}}{'mbox'}/new/$fich : $!\n");
+                 exit(1);
+              }
+              close(CHILD);
+              #unlink("${${$conf{'client'}}{$client}}{'mbox'}/new/$fich");
+           } else {
+              die "cannot fork: $!" unless defined $pid;
+              &change_uid_gid("$client");
+              &debug_msg("Executing ${${$conf{'client'}}{$client}}{'command'} .... \n");
+              exec("${${$conf{'client'}}{$client}}{'command'}");
+           }
+        }
+      }else{
+        &debug_msg("No mail available\n");
+      }
+   }else{
+      &debug_msg("Mbox format not yet supported.\n");
+      exit(1);
+   }
+}
+      
+   
+
+sub launch(#){
+   my $client=shift;
+   #Launch the command of a simple client
+   &debug_msg("Son $client, launching ${${$conf{'client'}}{$client}}{'command'}\n");
+   #Change uid and gid if needed
+   &change_uid_gid("$client");
+   exec(${${$conf{'client'}}{$client}}{'command'});
+}
+
+
+sub main_loop {
+   
+   my $ld;
+   my $nchild;
+
+   while (1) {
+      
+      if ($reload_conf == 1){
+        #We have to load conf again
+        $reload_conf=0;
+        &debug_msg("Reloading configuration .... \n");
+        &debug_msg("  Waiting all child to terminate ...");
+        while ((my $kid=wait) != -1){
+           $nchild--;
+           delete($running{$kid});
+        }
+        &debug_msg("done\n");
+        &debug_msg("  Restarting                     ...");
+        &init;
+        &init_launches;
+        &debug_msg("done\n");
+      }
+        
+        
+      if ($nchild >= $conf{'max_client'}){
+        #We have to wait for one child (Blocking)"
+        &debug_msg("Waiting for childs to terminate\n");
+        my $kid=wait;
+        if ($kid>0){
+           &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n");
+           $nchild--;
+           delete($running{$kid});
+        }
+      }else{
+        #Rip child terminated
+        &debug_msg("Checking dead childs....\n");
+        while ( (my $kid = waitpid(-1, WNOHANG)) > 0 ){
+           &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n");
+           $nchild--;
+           delete($running{$kid});
+        }
+        
+        #Look for a client to execute
+        my $ld = &get_load; 
+        &debug_msg("Load = $ld, processing\n");
+        my $possible_client=&get_possible_client($ld);
+        my @list=@{$possible_client};
+        &debug_msg("Avant Trim :  \n");
+        &debug_msg(Dumper($possible_client));
+        $possible_client=&trim_possible_client($possible_client);
+        &debug_msg("Après Trim : \n");
+        &debug_msg(Dumper($possible_client));
+        my $client=&get_next($possible_client);
+        if ($client){
+           #sleep(1);
+           &debug_msg("$client was chosen, launching\n");
+           $last_schedule{$client} = time ;
+           if (my $pid = fork){
+              #Pere, on marque qu'on l'a lancé
+              $running{$pid}="$client";
+              $nchild++;
+              &debug_msg("$client launched as PID $pid\n");
+              &debug_msg(Dumper(\%running));
+              &debug_msg(Dumper(\%launches));
+           }else{
+              #Fils
+              if (${${$conf{'client'}}{$client}}{'mbox'}){
+                 #We have to send data ourselves
+                 &send_data("$client");
+              }else{
+                 #We only have to execute a command
+                 &launch("$client");
+              }
+              exit(0);
+           }
+        }else{
+           &debug_msg("No client runable\n");
+           sleep($conf{'sleep_poll'});
+        }
+        sleep($conf{'active_poll'});
+      }
+   }
+}
+
+
+
+##########
+## Main ##
+##########
+
+$SIG{HUP}=\&sigHUP_handler;
+&init();
+&init_launches();
+&check_conf();
+&daemonize();
+&main_loop();
+
+