http://runtime.bordeaux.inria.fr/oaumage/oa/Teaching/ARSA_06/Umlnet/umlnet.pl#!/usr/bin/perl -w ################## # Strict Perl checking use strict; # Import usleep use Time::HiRes qw( usleep ); # Settings my $do_debug = 0; my $do_log = 1; my $do_trace = 1; my $do_exec = 1; my $do_use_konsole = 1; # Libs unshift @INC, "/lib/umlnet"; unshift @INC, "/usr/lib/umlnet"; unshift @INC, "/usr/local/lib/umlnet"; # Obtain user name my $user_name; if (exists $ENV{'USER'} and defined $ENV{'USER'}) { $user_name = ${ENV{'USER'}}; } # Obtain the home dir my $home_dir; # -I~/.umlnet # if (exists $ENV{'HOME'} and defined $ENV{'HOME'} and -d "$ENV{'HOME'}") { $home_dir = ${ENV{'HOME'}}; unshift @INC, "${home_dir}/.umlnet"; } # Import the umlnet library shared with UML nodes my $umlnet_lib_module = 'Umlnet_Lib.pm'; require $umlnet_lib_module; my $umlnet_lib_path = $INC{$umlnet_lib_module}; # UML tools control points my $uml_switch = '/usr/bin/uml_switch'; my $uml_switch_ctl = '/var/run/uml-utilities/uml_switch.ctl'; my $uml_socket_dir = '/tmp'; # Script usage information sub usage { print "usage: net.pl <net.cfg>\n"; exit 1; } usage if $#ARGV < 0; usage if $#ARGV > 1; # Get configuration filename from command line and try to open it my $cfg_filename = shift @ARGV; open (my $cfg_fd, "< $cfg_filename") or die "open $cfg_filename: $!\n"; # Parse the configuration file into a hash, and close it my $cfg_href = Umlnet_Lib::parse_config($cfg_fd); close $cfg_fd; # # Check if the config file has the required sections and fields, and cache the important values # 'main' section if (!exists ${$cfg_href}{"main"}) { die "no 'main' section found\n"; } my $main_section = ${$cfg_href}{"main"}; # 'kernel' field (path to kernel file) if (!exists ${$main_section}{"kernel"}) { die "no 'kernel' property found in main section\n"; } my $kernel = ${$main_section}{"kernel"}; # 'img' field (path to disk image) if (!exists ${$main_section}{"img"}) { die "no 'img' property found in main section\n"; } my $img = ${$main_section}{"img"}; # 'networks' field (list of networks) if (!exists ${$main_section}{"networks"}) { die "no 'networks' property found in main section\n"; } # # session variables # network names my @network_ids = split (',', ${$main_section}{"networks"}); # host names my @host_ids; # hosts entries my %hosts_h; # switch processes my %switch_ps; # host processes my %host_ps; # Konsole processes my %konsole_ps; # # Process the network entries, and launch the corresponding switches foreach my $net_id (@network_ids) { $do_log and print "network: $net_id\n"; # current network section if (!exists ${$cfg_href}{$net_id}) { die "no $net_id section found\n"; } my $section = ${$cfg_href}{$net_id}; # switch entry # - standalone: a new uml_switch process will be launched for this network # - default: the network will use the default system-wide uml_switch (possible connected # to the Internet if (!exists ${$section}{"switch"}) { die "no 'switch' property found in $net_id section\n"; } my $switch = ${$section}{"switch"}; # 'hosts' entry (list of hosts in this network) if (!exists ${$section}{"hosts"}) { die "no 'hosts' property found in $net_id section\n"; } my @net_host_ids = split (',',${$section}{"hosts"}); # Process the host entries for this network foreach my $host_id (@net_host_ids) { # Check if the host has not already been encountered on another host if (exists $hosts_h{$host_id}) { my $net_a = ${$hosts_h{$host_id}}{"network_ids"}; push @{$net_a}, $net_id; $do_log and print "host $host_id also found on net $net_id\n"; next; } # Check if the host section exists if (!exists ${$cfg_href}{$host_id}) { die "no $host_id section found\n"; } # Mark the host as already encountered $hosts_h{$host_id} = ${$cfg_href}{$host_id}; my $net_a = [ $net_id ]; ${$hosts_h{$host_id}}{"network_ids"} = $net_a; push @host_ids, $host_id; $do_log and print "new host $host_id found on net $net_id\n"; } # Launch the switches if ($switch eq "default") { # nothing to do } elsif ($switch eq "standalone") { my $cmdline; my @args; # Build and store the pathname of the UNIX socket connected to the switch my $uml_socket = "${uml_socket_dir}/${user_name}${net_id}"; ${$section}{"switch_socket"} = $uml_socket; # Select whether the uml_switch should emulate a hub or a regular switch my $type; if (exists ${$section}{"type"}) { $type = ${$section}{"type"}; } # Build the command line for launching the switch process push @args, "$uml_switch"; if (defined $type) { if ($type eq 'hub') { push @args, '-hub' } else { die "parse error: switch type\n" unless ($type eq 'switch'); } } push @args, '-unix'; push @args, "${uml_socket}"; # push @args, "-daemon"; $do_trace and print "Launching a standalone switch for network ${net_id}\n"; $cmdline = join(' ', @args); $do_log and print "$cmdline\n"; # Fork the process and exec the uml_switch my $child_pid = fork; if (!defined $child_pid) { print STDERR "WARNING: fork failed: $!\n"; next; } if ($child_pid) { # Father $do_log and print "father: switch child: ${child_pid}\n"; $switch_ps{$child_pid} = $net_id; } else { # Child $do_log and print "switch child\n"; if ($do_exec) { usleep(100); exec { $args[0] } @args or die "couldn't exec $args[0]: $!"; } exit 0; } } else { die "invalid switch type: $switch\n"; } } $do_log and print "\n"; # Launch the host processes foreach my $host_id (@host_ids) { my $section = $hosts_h{$host_id}; # cache a ref to the current host section my $host_network_ids = ${$section}{"network_ids"}; # Networks my $cow = "$img.$host_id"; # copy-on-write diff img my @args; my $cmdline; my $gdb; my $gdb_filename = "/tmp/gdb_cfg"; # Check if a gdb session should be attached to this host if (exists ${$section}{"gdb"}) { $gdb = ${$section}{"gdb"}; } # Start building the command line push @args, $kernel; # Kernel file push @args, "host_id=${host_id}"; # host identification # user name if (defined $user_name) { push @args, "user_name=${user_name}"; } # home directory if (defined $home_dir) { push @args, "home_dir=${home_dir}"; } # Fork/exec a Konsole process if we use KDE konsoles my $kpid; if ($do_use_konsole) { $kpid = fork(); if (!defined $kpid) { print STDERR "WARNING: fork failed: $!\n"; next; } if ($kpid) { # Father $do_log and print "father: konsole child: ${kpid}\n"; $konsole_ps{$kpid} = $kpid; } else { # Child exec "konsole --noclose --type umlnet --script" or die "couldn't exec konsole: $!"; } } push @args, 'con0=xterm'; # terminal type of console 0 # push @args, 'con0=fd:0,fd:1'; push @args, 'con=xterm'; # terminal type of other consoles # If we use KDE konsoles, we modify the 'xterm' terminal launching command to call # our helper instead # # Note: there must not be any whitespace between -H${host_id} and -T if ($do_use_konsole) { push @args, "xterm=umlnet_konsole_helper.pl,-H${host_id}-T"; } # Plug the image and copy-on-write private diff onto UML virtual disk device ubd0 push @args, "ubd0=${cow},${img}"; # Plug the umlnet library onto UML virtual disk ubd6 (read-only) push @args, "ubd6r=${umlnet_lib_path}"; # Plug the user supplied configuration file onto UML virtual disk ubd7 (read-only) push @args, "ubd7r=${cfg_filename}"; # Set the session name push @args, 'session=umlnet'; # Process the host configuration for the various networks it belongs to foreach my $net_id (@$host_network_ids) { # Check if the corresponding network section exists if (!exists ${$section}{$net_id}) { die "no $net_id property found in $host_id section\n"; } # Get the network config line for this host my $host_net = ${$section}{$net_id}; # Extract the various fields of the network config line # # eth_num: network interface name # eth_addr: hardware address of the interface (or suffix of the HW addr) # ip: either 'dhcp' or the IPv4 address (or suffix of the IP addr) # gw (optionnal): IPv4 address (or suffix) of the gateway # # suffixes may be specified instead of full addresses if (and only if) a # corresponding prefix is specified in the network section my ($eth_num, $eth_addr, $ip,) = split (',', $host_net); # Cache the network section my $net_section = ${$cfg_href}{$net_id}; # Get the switch type my $switch = ${$net_section}{'switch'}; # If a HW addr prefix is in use for the network, merge the HW net prefix # and the HW host suffix if (exists ${$net_section}{'hw_addr_prefix'}) { my $net_eth = ${$net_section}{'hw_addr_prefix'}; $eth_addr = Umlnet_Lib::eth_net($eth_addr, $net_eth); } # Connect the network interface to the uml_switch if ($switch eq 'default') { push @args, "${eth_num}=daemon,${eth_addr},unix,${uml_switch_ctl}"; } elsif ($switch eq "standalone") { my $uml_socket = ${$net_section}{"switch_socket"}; push @args, "${eth_num}=daemon,${eth_addr},unix,${uml_socket}"; } } # Clean-up any former private copy-on-write diff file $cmdline = "rm -fv ${cow}"; $do_log and print "$cmdline\n"; system($cmdline) == 0 or die "system $cmdline failed: $?"; # Fork and exec the host process $do_trace and print "Launching host ${host_id}\n"; $cmdline = join(' ', @args); $do_log and print "$cmdline\n"; my $child_pid = fork; if (!defined $child_pid) { print STDERR "WARNING: fork failed: $!\n"; next; } if ($child_pid) { # Father $do_log and print "father: host child: ${child_pid}\n"; $host_ps{$child_pid} = $host_id; } else { # Child $do_log and print "host child\n"; if ($do_use_konsole) { # If we use Konsole, we leave some time for dcop to connect to the Konsole process my $retries = 5; $ENV{'KPID'} = $kpid; while ($retries) { $retries --; system "dcop konsole-${kpid} session-1 renameSession umlnet"; if ($? == -1) { print "renameSession failed to execute: $!\n"; } elsif ($? & 127) { printf "renameSession child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without'; } else { my $v = $? >> 8; if ($v) { printf "renameSession child exited with value %d\n", $v; } else { last; } } sleep(1); print "retrying dcop call\n"; } } # NPTL incompatibility workaround $ENV{'LD_ASSUME_KERNEL'} = '2.4.1'; if ($do_exec) { usleep(100); # Actually exec the command, either inside a GDB/Emacs session, or simply alone if (defined $gdb and $gdb) { # GDB case my @gdb_args; # Build a config file for gdb open (my $gdb_fd, "> ${gdb_filename}") or die "open $gdb_filename: $!\n"; # Disable handling of SIGSEGV signals which are used by UML print $gdb_fd "handle SIGSEGV pass nostop noprint\n" or die "print to $gdb_filename: $!\n"; # Disable handling of SIGUSR1 signals which are used by UML print $gdb_fd "handle SIGUSR1 pass nostop noprint\n" or die "print to $gdb_filename: $!\n"; # Write the kernel command line args with proper double-quoting my $_kernel = shift @args; print $gdb_fd "set args \"" or die "print to $gdb_filename: $!\n"; print $gdb_fd (join '" "', @args) or die "print to $gdb_filename: $!\n"; print $gdb_fd "\"" or die "print to $gdb_filename: $!\n"; # Close the gdb config gile close $gdb_fd or die "close $gdb_filename: $!\n"; # Build the actual command line #push @gdb_args, 'xterm'; #push @gdb_args, '-e'; #push @gdb_args, 'echo'; #push @gdb_args, '--'; push @gdb_args, 'emacs'; push @gdb_args, '--execute'; # Note: we launch gdb inside emacs through a Emacs-Lisp command-line call my $wd = `pwd`; push @gdb_args, "(gdb \"gdb $_kernel -cd $wd -x $gdb_filename\")"; #push @gdb_args, "'("; #push @gdb_args, 'gdb'; #push @gdb_args, '"'; #push @gdb_args, "$_kernel"; #push @gdb_args, '-x'; #push @gdb_args, "$gdb_filename"; #push @gdb_args, '"'; #push @gdb_args, ")'"; print ((join ' ', @gdb_args), "\n"); exec { $gdb_args[0] } @gdb_args or die "couldn't exec $gdb_args[0]: $!"; } else { exec { $args[0] } @args or die "couldn't exec $args[0]: $!"; } } exit 0; } } # Wait for the host child processes to complete while (%host_ps) { # at least one active child process my $pid = wait; if ($pid == -1) { # all our childs have disappeared %host_ps = (); %switch_ps = (); } if (exists $host_ps{$pid}) { # the deceased child was a host child my $host_id = $host_ps{$pid}; $do_trace and print "host $host_id completed\n"; $do_log and print "deleting host process $pid\n"; delete $host_ps{$pid}; } elsif (exists $switch_ps{$pid}) { # the deceased child was a switch child my $net_id = $switch_ps{$pid}; $do_trace and print "standalone switch for network $net_id completed\n"; $do_log and print "deleting switch process $pid\n"; delete $switch_ps{$pid}; } elsif (exists $konsole_ps{$pid}) { delete $konsole_ps{$pid}; } else { print STDERR "WARNING: unknown deceased child: pid = $pid\n"; } } # Terminates remaining switch processes if any while (%switch_ps) { my @pid_list = keys %switch_ps; my $pid = shift @pid_list; my $net_id; kill 15, $pid or die "kill 15, $pid\n"; kill 9, $pid or die "kill 9, $pid\n"; while ( (my $_pid = wait) != $pid ) { if (exists $switch_ps{$_pid}) { $net_id = $switch_ps{$_pid}; $do_trace and print "standalone switch for network $net_id completed\n"; $do_log and print "deleting early deceased switch process $_pid\n"; delete $switch_ps{$_pid}; } else { print STDERR "WARNING: unknown deceased child: pid = $pid\n"; } } $net_id = $switch_ps{$pid}; $do_trace and print "standalone switch for network $net_id completed\n"; $do_log and print "deleting killed switch process $pid\n"; delete $switch_ps{$pid}; } # Terminates remaining konsole processes if any #if ($do_use_konsole) { # foreach my $kpid (keys %konsole_ps) { # system "dcop konsole-${kpid} MainApplication-Interface quit"; # delete $konsole_ps{$kpid}; # } #} |