1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-server
5 #
6 # USAGE: ./gosa-server
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: ---
12 # BUGS: ---
13 # NOTES:
14 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
15 # COMPANY:
16 # VERSION: 1.0
17 # CREATED: 12.09.2007 08:54:41 CEST
18 # REVISION: ---
19 #===============================================================================
21 use strict;
22 use warnings;
23 use Getopt::Long;
24 use Config::IniFiles;
25 use POSIX;
26 use Time::HiRes qw( gettimeofday );
28 use IO::Socket::INET;
29 use Crypt::Rijndael;
30 use XML::Simple;
31 use Data::Dumper;
32 use Sys::Syslog qw( :DEFAULT setlogsock);
34 my ($cfg_file, %cfg_defaults, $foreground);
35 my ($bus_passwd, $bus_ip, $bus_port, $bus);
36 my ($pid_file, $procid, $pid, $log_file);
37 my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout);
39 $foreground = 0 ;
40 %cfg_defaults =
41 ("general" =>
42 {"log_file" => [\$log_file, "/var/run/gosa-server-bus.log"],
43 "pid_file" => [\$pid_file, "/var/run/gosa-server-bus.pid"],
45 },
46 "bus" =>
47 {"bus_passwd" => [\$bus_passwd, "tester78901234567890123456789012"],
48 "bus_ip" => [\$bus_ip, "10.89.1.155"],
49 "bus_port" => [\$bus_port, "10001"],
50 "child_max" => [\$child_max, 10],
51 "child_min" => [\$child_min, 3],
52 "child_timeout" => [\$child_timeout, 180],
53 }
54 );
56 #=== FUNCTION ================================================================
57 # NAME: read_configfile
58 # PARAMETERS: cfg_file - string -
59 # RETURNS:
60 # DESCRIPTION:
61 #===============================================================================
62 sub read_configfile {
63 my $cfg;
64 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
65 if( -r $cfg_file ) {
66 $cfg = Config::IniFiles->new( -file => $cfg_file );
67 } else {
68 print STDERR "Couldn't read config file!";
69 }
70 } else {
71 $cfg = Config::IniFiles->new() ;
72 }
73 foreach my $section (keys %cfg_defaults) {
74 foreach my $param (keys %{$cfg_defaults{ $section }}) {
75 my $pinfo = $cfg_defaults{ $section }{ $param };
76 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
77 }
78 }
79 }
81 #=== FUNCTION ================================================================
82 # NAME: logging
83 # PARAMETERS: level - string - default 'info'
84 # msg - string -
85 # facility - string - default 'LOG_DAEMON'
86 # RETURNS:
87 # DESCRIPTION:
88 #===============================================================================
89 sub daemon_log {
90 my( $msg, $level ) = @_;
91 if(not defined $msg) { return }
92 if(not defined $level) { $level = 1 }
93 if(defined $log_file){
94 open(LOG_HANDLE, ">>$log_file");
95 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
96 print STDERR "cannot open $log_file: $!";
97 return }
98 chomp($msg);
99 print LOG_HANDLE $msg."\n";
100 if(defined $foreground) { print $msg."\n" }
101 }
102 close( LOG_HANDLE );
103 # my ($msg, $level, $facility) = @_;
104 # if(not defined $msg) {return}
105 # if(not defined $level) {$level = "info"}
106 # if(not defined $facility) {$facility = "LOG_DAEMON"}
107 # openlog($0, "pid,cons,", $facility);
108 # syslog($level, $msg);
109 # closelog;
110 # return;
111 }
113 #=== FUNCTION ================================================================
114 # NAME: check_cmdline_param
115 # PARAMETERS:
116 # RETURNS:
117 # DESCRIPTION:
118 #===============================================================================
119 sub check_cmdline_param () {
120 my $err_config;
121 my $err_counter = 0;
122 if( not defined( $cfg_file)) {
123 $err_config = "please specify a config file";
124 $err_counter += 1;
125 }
126 if( $err_counter > 0 ) {
127 &usage( "", 1 );
128 if( defined( $err_config)) { print STDERR "$err_config\n"}
129 print STDERR "\n";
130 exit( -1 );
131 }
132 }
134 #=== FUNCTION ================================================================
135 # NAME: check_pid
136 # PARAMETERS:
137 # RETURNS:
138 # DESCRIPTION:
139 #===============================================================================
140 sub check_pid {
141 $pid = -1;
142 # Check, if we are already running
143 if( open(LOCK_FILE, "<$pid_file") ) {
144 $pid = <LOCK_FILE>;
145 if( defined $pid ) {
146 chomp( $pid );
147 if( -f "/proc/$pid/stat" ) {
148 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
149 if( "faimond" eq $stat ) {
150 close( LOCK_FILE );
151 exit -1;
152 }
153 }
154 }
155 close( LOCK_FILE );
156 unlink( $pid_file );
157 }
158 # Try to open PID file
159 if (!sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
160 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
162 if (open(LOCK_FILE, '<', $pid_file)
163 && ($pid = <LOCK_FILE>))
164 {
165 chomp($pid);
166 $msg .= "(PID $pid)\n";
167 } else {
168 $msg .= "(unable to read PID)\n";
169 }
170 if( ! ($foreground) ) {
171 openlog( "gosa-server-bus", "cons,pid", "daemon" );
172 syslog( "warning", $msg );
173 closelog();
174 }
175 else {
176 print( STDERR " $msg " );
177 }
178 exit( -1 );
179 }
180 }
182 #=== FUNCTION ================================================================
183 # NAME: usage
184 # PARAMETERS:
185 # RETURNS:
186 # DESCRIPTION:
187 #===============================================================================
188 sub usage {
189 my( $text, $help ) = @_;
190 $text = undef if( "h" eq $text );
191 (defined $text) && print STDERR "\n$text\n";
192 if( (defined $help && $help) || (!defined $help && !defined $text) ) {
193 print STDERR << "EOF" ;
194 usage: $0 [-hvf] [-c config]
196 -h : this (help) message
197 -c <file> : config file
198 -v : be verbose (multiple to increase verbosity)
199 EOF
200 }
201 print "\n" ;
202 }
204 sub sig_int_handler {
205 my ($signal) = @_;
206 if($bus){
207 close($bus);
208 print "$bus closed\n";
209 }
210 print "$signal\n";
211 exit(1);
212 }
213 $SIG{INT} = \&sig_int_handler;
215 #=== FUNCTION ================================================================
216 # NAME: activating_child
217 # PARAMETERS:
218 # RETURNS:
219 # DESCRIPTION:
220 #===============================================================================
221 sub activating_child {
222 my ($msg) = @_;
223 my $child = &get_processing_child();
224 my $pipe_wr = $$child{'pipe_wr'};
225 print "activating: childpid:$$child{'pid'}\n";
226 #print "activating: pipe_wr:$pipe_wr\n";
227 print $pipe_wr $msg."\n";
228 #print "activating: done\n";
229 return;
231 }
234 #=== FUNCTION ================================================================
235 # NAME: get_processing_child
236 # PARAMETERS:
237 # RETURNS:
238 # DESCRIPTION:
239 #===============================================================================
240 sub get_processing_child {
241 my $child;
242 # schaue durch alle %busy_child{pipe_wr} ob irgendwo 'done' drinsteht, wenn ja, dann setze das kind von busy auf free um
243 while(my ($key, $val) = each(%busy_child)) {
244 # test ob prozess noch existiert
245 my $exitus_pid = waitpid($key, WNOHANG);
246 if($exitus_pid != 0) {
247 delete $busy_child{$key};
248 print "prozess:$key wurde aus busy_child entfernt\n";
249 next;
250 }
252 # test ob prozess noch arbeitet
253 my $fh = $$val{'pipe_rd'};
254 $fh->blocking(0);
255 my $child_answer;
256 if(not $child_answer = <$fh>) { next }
257 chomp($child_answer);
258 if($child_answer eq "done") {
259 delete $busy_child{$key};
260 $free_child{$key} = $val;
261 }
262 }
264 while(my ($key, $val) = each(%free_child)) {
265 my $exitus_pid = waitpid($key, WNOHANG);
266 if($exitus_pid != 0) {
267 delete $free_child{$key};
268 print "prozess:$key wurde aus free_child entfernt\n";
269 }
270 print "free child:$key\n";
271 }
274 # teste @free_child und @busy_child
275 my $free_len = scalar(keys(%free_child));
276 my $busy_len = scalar(keys(%busy_child));
277 print "free children $free_len, busy children $busy_len\n";
279 # gibt es bereits ein freies kind, dann lass es arbeiten
280 if($free_len > 0){
281 my @keys = keys(%free_child);
282 $child = $free_child{$keys[0]};
283 if(defined $child) {
284 $busy_child{$$child{'pid'}} = $child ;
285 delete $free_child{$$child{'pid'}};
286 }
287 return $child;
288 }
290 # no free child, try to fork another one
291 if($free_len + $busy_len < $child_max) {
293 print "not enough children, create a new one\n";
295 # New pipes for communication
296 my( $PARENT_wr, $PARENT_rd );
297 my( $CHILD_wr, $CHILD_rd );
298 pipe( $CHILD_rd, $PARENT_wr );
299 pipe( $PARENT_rd, $CHILD_wr );
300 $PARENT_wr->autoflush(1);
301 $CHILD_wr->autoflush(1);
303 ############
304 # fork child
305 ############
306 my $child_pid = fork();
308 #CHILD
309 if($child_pid == 0) {
310 # Close unused pipes
311 close( $CHILD_rd );
312 close( $CHILD_wr );
313 while( 1 ) {
314 my $rbits = "";
315 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
316 my $nf = select($rbits, undef, undef, $child_timeout);
317 if($nf < 0 ) {
318 # wenn $nf < 1, error handling
319 die "select(): $!\n";
320 } elsif (! $nf) {
321 # seit timeout ist nichts mehr passiert,
322 print "timeout!!\n";
324 # ist dieses kind nicht eines der letzenen $child_min, dann springe aus while schleife raus
325 $free_len = scalar(keys(%free_child));
326 $busy_len = scalar(keys(%busy_child));
327 if($free_len + $busy_len >= $child_min) {
328 last;
329 } else {
330 redo;
331 }
332 }
334 # ansonsten
335 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
336 my $msg = <$PARENT_rd>;
337 &process_incoming_msg($msg);
339 # schreibe an den parent_wr zurück 'done'
340 print $PARENT_wr "done"."\n";
342 redo;
343 }
344 }
345 # child die die while-schleife verlassen haben, "stirbt!"
346 exit(0);
348 #PARENT
349 } else {
350 # Close unused pipes
351 close( $PARENT_rd );
352 close( $PARENT_wr );
354 # add child to child alive hash
355 my %child_hash = (
356 'pid' => $child_pid,
357 'pipe_wr' => $CHILD_wr,
358 'pipe_rd' => $CHILD_rd,
359 );
361 $child = \%child_hash;
362 $busy_child{$$child{'pid'}} = $child;
363 return $child;
364 }
365 }
366 }
369 #=== FUNCTION ================================================================
370 # NAME: process_incoming_msg
371 # PARAMETERS:
372 # RETURNS:
373 # DESCRIPTION:
374 #===============================================================================
375 sub process_incoming_msg {
376 my ($msg) = @_;
377 print "msg to process:$msg\n";
378 sleep(10);
379 return;
380 }
382 #==== MAIN = main ==============================================================
384 # parse commandline options
385 Getopt::Long::Configure( "bundling" );
386 GetOptions("h|help" => \&usage,
387 "c|config=s" => \$cfg_file,
388 "f|foreground" => \$foreground,
389 );
391 # read and set config parameters
392 &read_configfile;
393 &check_cmdline_param ;
394 &check_pid;
396 $SIG{CHLD} = 'IGNORE';
398 # restart daemon log file
399 if(-e $log_file ) { unlink $log_file }
400 daemon_log("started!");
402 # Just fork, if we"re not in foreground mode
403 if( ! $foreground ) { $pid = fork(); }
404 else { $pid = $$; }
406 # Do something useful - put our PID into the pid_file
407 if( 0 != $pid ) {
408 open( LOCK_FILE, ">$pid_file" );
409 print LOCK_FILE "$pid\n";
410 close( LOCK_FILE );
411 if( !$foreground ) { exit( 0 ) };
412 }
414 # open the bus socket
415 $bus = IO::Socket::INET->new(LocalPort => $bus_port,
416 Type => SOCK_STREAM,
417 Reuse => 1,
418 Listen => 20,
419 ) or die "kann kein TCP-Server an Port $bus_port sein: $@\n";
421 # waiting for incoming msg
422 my $client;
423 while($client = $bus->accept()){
424 my $other_end = getpeername($client)
425 or die "Gegenstelle konnte nicht identifiziert werden: $!\n";
426 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
427 my $actual_ip = inet_ntoa($iaddr);
428 my $claimed_hostname = gethostbyaddr($iaddr, AF_INET);
429 daemon_log("accept client from $actual_ip\n");
431 my $in_msg;
432 chomp( $in_msg = <$client> );
434 &activating_child($in_msg);
435 close($client);
436 }
440 #Struktur Parent:
441 # syslog(Parent gestartet)
443 # forken von min_child kindern
444 # liste mit child refs
445 #
446 # aufbau des sockets
447 # warten auf eingang
448 # kinder die älter als child_timeout sind werden beendet, aber nicht mehr als child_min
449 # wenn eingang da, abfrage, ist ein kind frei?
450 # kind frei: kind nimmt eingang entgegen und arbeitet ihn ab
451 # kind nicht frei: existieren bereits max_child kinder?
452 # max ereicht: tue nichts, warte bis ein kind fertig ist
453 # max nicht erreicht: forke neues kind