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};
#    }
#}

Reply via email to