#!/usr/bin/perl

use strict;
use locale;
use Getopt::Std;
use IO::Socket;
use Sys::Hostname;
use POSIX qw(:sys_wait_h);
use POSIX qw(locale_h);
use Fcntl qw(:DEFAULT :flock);

my $installios_driver= "/usr/sbin/installios_driver";
my ($managed_sys, $netmask, $partition, $client_addr, $res_location, $mac_addr, $profile, $gateway);
my %options;
my $this_host= (gethostbyname(hostname))[0];
my ($this_addr)= inet_ntoa((gethostbyname($this_host))[4]);
my @args;
my $pid;
my $nimol_log= '/var/log/nimol.log';
my $license_root= '/media/cdrom/nimol/licenses';
my $cd_location= '/media/cdrom/nimol/ioserver_res';
my $restore_location= '/media/cdrom/nim_resources.tar';
my $from_cd= 0;
my $language;
my $accepted= 0;
my $no_net= 0;
my $speed= "100";
my $duplex= "full";
my $debug= "";
my $stripped_mac;
my $my_net;
my $client_net;
my @preinstalled_langs= ("C", "ca_ES", "en_GB", "en_US", "fr_BE", "fr_CA", "fr_CH", "fr_FR", "de_CH", "de_DE", "it_IT", "pt_BR", "pt_PT", "es_ES");
my $lang_support_root="/media/cdrom/installp/ppc";
my $is_preinstalled= 0;
my $lang_support_cd= 0;
my $do_licenses= 1;
my $preserve_label= 0;
my $pid_file= '/tmp/installios_pid';
my $installing_lockfile= '/tmp/installing.lock';
my $own_lock= 0;
my %xlated_fields= ('managed_system' => _("managed system"), 'vio_partition' => _("virtual I/O server partition"), 'profile' => _("profile"), 'mac_addr' => _("ethernet adapters"), 'language' => _("language"), 'source' => _("source"), 'client_addr' => _("IP address"), 'netmask' => _("subnet mask"), 'gateway' => _("gateway"), 'speed' => _("speed"), 'duplex' => _("duplex"), 'config_net' => _("configure network"));
my $label_name;
my $bosinst_data;
my $secondary_inst_images;
my $is_vioserver= 0;
my $res_root= '/info';
my $rm_labels= 0;
my $client_hostname;
my $from_script= 0;
my $global_rc= 0;

$debug= '-v -x' if ($ENV{INSTALLIOS_DEBUG});

# friendly usage statement that returns a nice error code
if ($ARGV[0] eq '-?' || $ARGV[0] eq '--help')
{
	print _(""."usage: installios [-s managed_sys -S netmask -p partition\n"."                         -r profile -i client_addr -d source_dir -m mac_addr\n"."                         -g gateway [-P speed] [-D duplex] [-n] [-l language]]\n"."                         | -u\n");
	exit 0;
}

#are you an hmcsuperadmin?
die _("ERROR installios: You must be an hmcsuperadmin to run this command.\n") unless (`lshmcusr --filter "names=\`whoami\`" -F"taskrole" 2>/dev/null` =~ /hmcsuperadmin/);

getopts("s:S:p:i:d:m:r:g:ul:nP:D:NCR:b:I:UZ", \%options) or die _(""."ERROR installios: usage: installios [-s managed_sys -S netmask -p partition\n"."                         -r profile -i client_addr -d source_dir -m mac_addr\n"."                         -g gateway [-P speed] [-D duplex] [-n] [-l language]]\n"."                         | -u\n");

die _(""."ERROR installios: usage: installios [-s managed_sys -S netmask -p partition\n"."                         -r profile -i client_addr -d source_dir -m mac_addr\n"."                         -g gateway [-P speed] [-D duplex] [-n] [-l language]]\n"."                         | -u\n") unless (((keys %options) == 0 && $0 =~ /installios/) || $options{u} || ($options{U} && ! $options{u}) || $options{s} && $options{S} && $options{p} && $options{i} && $options{r} && $options{g} && $options{d});

if ((keys %options) == 0)
{
	my $fields= &wizard;

	$managed_sys= $fields->{managed_system};
	$netmask= $fields->{netmask};
	$partition= $fields->{vio_partition};
	$client_addr= $fields->{client_addr};
	$res_location= $fields->{source};
	$mac_addr= $fields->{mac_addr};
	$profile= $fields->{profile};
	$gateway= $fields->{gateway};
	$no_net= 1 if ($fields->{config_net} eq "n");
	$speed= $fields->{speed} if ($fields->{speed});
	$duplex= $fields->{duplex} if ($fields->{duplex});
	$language= $fields->{language} if ($fields->{language});
}
else
{
	$managed_sys= $options{s};
	$netmask= $options{S};
	$partition= $options{p};
	$client_addr= $options{i};
	$res_location= $options{d} if ($options{d});
	$mac_addr= $options{m};
	$profile= $options{r};
	$gateway= $options{g};
	$no_net= 1 if ($options{n});
	$speed= $options{P} if ($options{P});
	$duplex= $options{D} if ($options{D});
	$do_licenses= 0 if ($options{N});
	$preserve_label= 1 if ($options{C});
	$label_name= $options{R} if ($options{R});
	$bosinst_data= $options{b} if ($options{b});
	$secondary_inst_images= $options{I} if ($options{I});
	$rm_labels= $options{U} if $options{U};
	$from_script= 1 if ($options{Z});
}

# since getopts doesn't do a very good job of telling me when a flag requiring a parameter
# doesn't have a parameter, I have to check here
foreach my $each_flag (keys %options)
{
	# skip the boolean flags (see the getopts call above- the boolean flags are the ones 
	# that don't have a : after them)
	next if ($each_flag =~ /^[unNCUZ]$/);

	if (defined $options{$each_flag})
	{
		# if the flag was supplied to the cmd line, it better have an argument
		die _(""."ERROR installios: usage: installios [-s managed_sys -S netmask -p partition\n"."                         -r profile -i client_addr -d source_dir -m mac_addr\n"."                         -g gateway [-P speed] [-D duplex] [-n] [-l language]]\n"."                         | -u\n") unless ($options{$each_flag});
	}
}

&unconfigure(undef, $options{u}, 0) if ($options{u});

# Check that the managed, lpar name, and profile supplied are valid
if (system "lssyscfg -r lpar -m \"$managed_sys\" >/dev/null 2>&1")
{
	die _("ERROR: installios: \"%s\" is not a valid %s\n", $managed_sys, _("managed system"));
}
if (system "/usr/hmcrbin/lssyscfg -m \"$managed_sys\" -r lpar --filter \"lpar_names=$partition\" >/dev/null 2>&1")
{
	die _("ERROR: installios: \"%s\" is not a valid %s\n", $partition, _("logical partition"));
}
if (system "/usr/hmcrbin/lssyscfg -m \"$managed_sys\" -r prof --filter \"lpar_names=$partition\" | grep \"$profile\" >/dev/null 2>&1")
{
	die _("ERROR: installios: \"%s\" is not a valid %s\n", $profile, _("profile"));
}

#get subnet addresses
$my_net= &get_subnet_addr;
$client_net= &get_subnet_addr($client_addr, $netmask);
$client_hostname= (gethostbyaddr(pack("C4", split(/\./, $client_addr)), AF_INET))[0];

die _(""."ERROR installios: usage: installios [-s managed_sys -S netmask -p partition\n"."                         -r profile -i client_addr -d source_dir -m mac_addr\n"."                         -g gateway [-P speed] [-D duplex] [-n] [-l language]]\n"."                         | -u\n") unless ($mac_addr || ($my_net && $client_net && $my_net ne $client_net));

$is_vioserver= 1 if (`/usr/hmcrbin/lssyscfg -m \"$managed_sys\" -r lpar --filter \"lpar_names=$partition\" -F\"lpar_type\"` =~ /vioserver/);

#if being called as installios, don't allow other LPARs to be installed (that's for osinstall)
die _("ERROR installios: Only Virtual I/O Server type partitions may be installed using installios.\n") unless ($ENV{INSTALLIOS_INSTALL_ALL} || $is_vioserver || $0 =~ /osinstall/);

#add my pid to the pid file
&log_pid;

#first, try to create the lock file if it doesn't exist (use sysopen for atomic creation)
#regardless of existence, we now have the file handle for the lock file
sysopen(INSTL_LOCK, $installing_lockfile, O_WRONLY | O_CREAT) or die "$installing_lockfile: $!\n";

#try to lock installing semaphore here (non-blocking)- all the usage stuff is through by now and we're
#about to set the signal handlers, so this is a good place
&get_instl_lock;

$SIG{INT}= \&sig_handler;
$SIG{QUIT}= \&sig_handler;
$SIG{HUP}= 'IGNORE';
$SIG{USR1}= \&get_instl_lock;

# read the language and mount the media only if we're installing to a vioserver and we are not using
# a previously saved label
if ($res_location eq "/dev/cdrom" && $is_vioserver && ! ($label_name && -d $res_root.'/'.$label_name))
{
	die _("ERROR installios: Could not mount /dev/cdrom\n") if (system("/bin/mount /dev/cdrom"));
	$from_cd= 1;
	if (-d $cd_location)
	{
		$res_location= $cd_location;
	}
	else
	{
		$res_location= $restore_location;
	}
	$language= $options{l} if ($options{l});

	$lang_support_cd= 1 if (-f '/media/cdrom/nimol/language.mapfile');
}

$accepted= &accept_license($language) if ($language && $do_licenses);

# run the driver to start NIMOL
push @args, "/usr/sbin/installios_driver";
if ($res_location)
{
	push @args, "-d";
	push @args, "$res_location";
}
push @args, "-i";
push @args, "$client_addr";
push @args, "-S";
push @args, "$netmask";
push @args, "-m";
push @args, "$mac_addr";
push @args, "-g" ;
push @args, "$gateway";
push @args, "-n" if ($no_net);
push @args, "-C" if ($preserve_label);
push @args, "-O" if ($own_lock);

if ($language)
{
	push @args, "-l";
	push @args, "$language";
}

#accepted license?
push @args, "-a" if ($accepted);

#set vioserver locale to preinstalled language
foreach (@preinstalled_langs)
{
	if ($_ eq $language)
	{
		push @args, "-L";
		$is_preinstalled= 1;
		last;
	}
}

#install lang support if lang is not pre-installed
push @args, "-c" if ((! $is_preinstalled) && $lang_support_cd);

#if the install is through a private interface, send it to the driver
#and use DHCP (since the HMC's running server will be through the private
#interface)
if ($ENV{INSTALLIOS_PRIVATE_IF})
{
	push @args, "-I";
	push @args, $ENV{INSTALLIOS_PRIVATE_IF};
}
else
{
	# use nimol_bootreplyd instead of DHCP
	push @args, "-b";
}

#if bosinst.data was specified:
if ($bosinst_data)
{
        push @args, "-B";
	push @args, $bosinst_data;
}

#if label name was given:
if ($label_name)
{
        push @args, "-N";
	push @args, $label_name;
}

#if secondary inst.images was given:
if ($secondary_inst_images)
{
        push @args, "-M";
	push @args, $secondary_inst_images;
}

#done building arguments, fork and exec the driver program now
if ($pid= fork)
{
	#parent
	wait;
}
else
{
	die _("ERROR: installios: fork error\n") unless (defined $pid);

	exec(@args) or die _("ERROR: installios: Couldn't execute the driver program.\n");
}

# unconfigure with bad rc if installios_driver did not complete successfully
&unconfigure(undef, 0, 1) if ($?);

#read to the end of the nimol log file
open(NIMOL_LOG, "< $nimol_log") or die _("ERROR: installios: could not open the NIMOL log file.\n");
while (<NIMOL_LOG>){}

#now we can exec lpar_netboot
#strip out :

$mac_addr =~ s/://g if ($mac_addr);
my $m_arg= $mac_addr ? "-m $mac_addr" : "-A";

if (system("LC_ALL=en_US LANG=en_US /usr/hmcrbin/lpar_netboot -f $debug -t ent $m_arg -D -s $speed -d $duplex -S $this_addr -G $gateway -C $client_addr \"$partition\" \"$profile\" \"$managed_sys\""))
{
	print STDERR _("ERROR: installios: lpar_netboot encountered an error.\n");
	&unconfigure(undef, 0, 2);
}

#at this point monitor the progress of the installation until it has completed
&monitor or die _("ERROR: installios: An error occured while monitoring the installation.\n");

sleep 10;

&unconfigure(undef, 0, 0);

# this function will take a signal and figure out whether to call unconfig
sub sig_handler
{
	local $SIG{INT}= 'IGNORE';
	local $SIG{QUIT}= 'IGNORE';
	local $SIG{USR1}= 'IGNORE';
	my $signal= shift;

	# only call unconfigure if we have evidence it hasn't been called yet through a normal code path
	# we have to do this here because a normal exit call apparently generates a signal that we catch,
	# so you end up unconfiguring twice unless we do this
	&unconfigure($signal, 0, 255) if (-f '/etc/nimol.conf');
}

sub unconfigure
{
	local $SIG{INT}= 'IGNORE';
	local $SIG{QUIT}= 'IGNORE';
	local $SIG{USR1}= 'IGNORE';
	my $signal= shift;
	my $kill_all= shift;
	my $rc= shift;
	
	&unlog_pid;

	my @pids= &get_installios_pids;

	unless ($kill_all)
	{
		#close the installing semaphore to unlock it
		close INSTL_LOCK;

		#signal other installios's so they can lock the installing semaphore
		if ($own_lock)
		{
			if (@pids)
			{
				kill 'USR1', @pids; #whichever gets scheduled first will have the lock
			}
		}
	}

	if ($kill_all) # kill -INT everyone else so they stop what they're doing
	{
		if (@pids)
		{
			kill 'INT', @pids;
		}
	}

	system("/bin/umount /dev/cdrom") if ($from_cd);

	my @args;
	my $pid;

	push @args, "/usr/sbin/installios_driver";
	push @args, "-u";
	unless ($kill_all)
	{
		push @args, "-i";
		push @args, "$client_addr";
	}
	push @args, "-O" if ($own_lock);
	push @args, "-U" if ($rm_labels);

	if ($kill_all)
	{
		# wait for the other installios's to die without hogging up the cpu (perl does that sometimes)
		while (kill 0 => @pids){sleep 1}
		#close the installing semaphore to unlock it (I'll probably have it right now)
		close INSTL_LOCK;
	}

	if ($pid= fork)
	{
		#parent
		wait;
	}
	else
	{
		die _("ERROR: installios: fork error\n") unless (defined $pid);

		exec(@args) or die _("ERROR: installios: Couldn't exec the driver program to unconfigure NIMOL.\n");
	}

	exit $rc;
}

sub monitor
{
	my $line;
	my $success= 0;
	my $stop_str= '100%';
	my $start_date= scalar(localtime);
	my $reads= 0;
	my $short_client_host= (split /\./, $client_hostname)[0];
	
	while (1)
	{
		$reads++;

		if ($reads == 5)
		{
			$reads= 0;
			sleep 1;
		}

		#we only care about log entries for this host
		next unless ($line= <NIMOL_LOG>);
		next unless ($line =~ /$short_client_host/);

		# only clear the screen if we're not being called by a script
		unless ($from_script)
		{
			system("clear");
			print $start_date."\n-----------$nimol_log :---------------\n";
		}

		print "$line\n";

		if ($line =~ /$stop_str/)
		{
			$success= 1;
			last;
		}
	}
	close NIMOL_LOG;
	$success;
}

sub accept_license
{
	my $language= shift;
	my $license_path="$license_root/$language/IOS.la";
	my $return= 0;
	my $response;

	# can I even show the license?  return 0 if locale setings don't agree
	my $orig= $ENV{LANG} || 'C'; # lang = C if $LANG is empty
	my $new= $language;
	my $compatible= 0;

	my $orig_front= (split /_/, $orig)[0];
	my $new_front= (split /_/, $new)[0];

	my $orig_preinstalled= 0;
	my $new_preinstalled= 0;

	# if the locale's language is explicitly "POSIX" we need this to read "C"
	$orig= "C" if ($orig eq 'POSIX'); 

	foreach (@preinstalled_langs)
	{
	        $orig_preinstalled= 1 if ($_ eq $orig);
	        $new_preinstalled= 1 if ($_ eq $new);
	        last if ($orig_preinstalled && $new_preinstalled);
	}

	# so we can display the license if:
	#	lang of license == current locale
	#	curr locale AND lang of license are one of the preinstalled languages
	#	lang of license AND curr locale have utf charsets (first 2 letters of locale are all caps)

	# if license isn't displayable, they have to accept the license on the Virtual I/O Server

	$compatible= 1 if ($orig eq $new || (uc $orig_front eq $orig_front && uc $new_front eq $new_front) || ($orig_preinstalled && $new_preinstalled));

	if ($language && -f $license_path && $compatible)
	{
		if (system("/bin/more $license_path"))
		{
			#make sure more exits properly
			system("/bin/umount /dev/cdrom") if ($from_cd);
			die "\n";
		}

		while ($response ne (split //,_("yes"))[0] && $response ne (split //,_("no"))[0] && $response ne _("yes") && $response ne _("no") && $response ne "yes" && $response ne "no")
		{
			print _("\nDo you accept the license agreement? y/n: ");
			$response= <>;
			chomp $response;
		}

		unless ($response eq (split //,_("yes"))[0] || $response eq _("yes") || $response eq "yes")
		{
			system("/bin/umount /dev/cdrom") if ($from_cd);
			die _("installios: Exiting because license agreement was not accepted.\n");
		}
		$return= 1;
	}
	elsif ($compatible)
	{
		if ($lang_support_cd)
		{
			do
			{
				printf _("The language %s does not have a license agreement.\nDo you want to continue? [yes]/no? ", $language);
				$response= <>;
				chomp $response;
			}
			until ($response eq "yes" || $response eq "no" || $response eq _("yes") || $response eq _("no") || $response =~ /^\s*$/);
			if ($response eq 'no' || $response eq _("no"))
			{
				system("/bin/umount /dev/cdrom") if ($from_cd);
				exit 0;
			}
		}
		else
		{
			print STDERR _("WARNING: installios: The language \\\"%s\\\" is not supported.\n", $language);
		}
	}

	$return;
}

sub wizard
{
	my @types= ('managed_system', 'vio_partition', 'profile');
	my @special_fields_list= ('mac_addr', 'language');
	my @other_fields_list= ('source', 'client_addr', 'netmask', 'gateway', 'speed', 'duplex', 'config_net');
	my %special_fields= ('mac_addr' => \&get_mac_addr,
						 'language' => \&read_license_input);
	my %other_fields= ('client_addr' => [ _("Enter the client's intended IP address: "), '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' ],
					   'netmask' => [ _("Enter the client's intended subnet mask: "), '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' ],
					   'gateway' => [ _("Enter the client's gateway: "), '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' ],
					   'speed' => [ _("Enter the client's speed [100]: "), '^10|100|1000|(?:auto|'._("auto").')$', "100" ],
					   'duplex' => [ _("Enter the client's duplex [full]: "), '^(?:full|'._("full").')|(?:half|'._("half").')|(?:auto|'._("auto").')$', "full" ],
					   'config_net' => [ _("Would you like to configure the client's network after the\n\tinstallation [yes]/no? "), '^(?:yes|'._("yes").')|(?:no|'._("no").')$', "yes" ],
					   'source' => [ _("Enter the source of the installation images [/dev/cdrom]: "), ".+", "/dev/cdrom" ]);

	#lists determine the order in which the elements are prompted for
	#types list is the hmc-specific data
	#special_fields require special processing
	#other_fields are the regular parameters
	#special_fields require special processing and cannot be handled like the others, so they get their own function

	#other_fields SPEC: msg  ,   string to match   ,   default value

	# main order is types, other_fields, special_fields


	my @elements;
	my %input;
	my %parameters;
	my $i;

	#first go through hmc types needed
	foreach (@types)
	{
		my $cmd;

		if ($_ eq 'managed_system')
		{
			$cmd= '/usr/hmcrbin/lssyscfg -r sys -F "name"';
		}
		elsif ($_ eq 'vio_partition')
		{
			$cmd= "/usr/hmcrbin/lssyscfg -r lpar -m \"$parameters{managed_system}\"".q# -F"name,lpar_type" | /usr/bin/perl -e 'while (<>){chomp;@f= split /,/;print "$f[0]\n" if ($f[1] eq "vioserver");}'#;
		}
		elsif ($_ eq 'profile')
		{
			$cmd= "/usr/hmcrbin/lssyscfg -r prof -m \"$parameters{managed_system}\" --filter \"lpar_names=".$parameters{vio_partition}."\" -F\"name\"";
		}

		$input{$_}= &get_element($cmd, "\n");
		die _("ERROR: installios: No objects of type \\\"%s\\\" were found.\n", $xlated_fields{$_}) unless (@{ $input{$_} } > 0);
		$parameters{$_}= &get_input($xlated_fields{$_}, $input{$_});
	}

	#next get all other required fields
	foreach (@other_fields_list)
	{
		my $msg= $other_fields{$_}->[0];
		my $pattern= $other_fields{$_}->[1];
		my $default= $other_fields{$_}->[2];
		my $input_str= "";

		#take everything if no pattern is specified
		$pattern= '.+' unless ($pattern);

		while ($input_str !~ /$pattern/)
		{
			print $msg;
			$input_str= <>;
			chomp $input_str;

			if ((! $input_str) && $default)
			{
				$input_str= $default;
				last;
			}
		}

		$parameters{$_}= $input_str;
	}

	foreach (@special_fields_list)
	{
		$special_fields{$_}->(\%parameters) if ($special_fields{$_});
	}

	print _("Here are the values you entered:\n\n");;

	foreach (@types,@other_fields_list,@special_fields_list)
	{
		print "$xlated_fields{$_} = $parameters{$_}\n" if ($parameters{$_});
	}

	print _("\nPress enter to proceed or type Ctrl-C to cancel...\n");
	$_= <>;

	\%parameters;
}

#calls an hmc command and returns the output in a list reference
# Params:  CMD , DELIM
#          ^     ^
#		cmd to	 delimiter
#		execute
sub get_element
{
	my $cmd= shift;
	my $delim= shift;
	my $output;
	my @ret;

	print "\n";
	$output= `$cmd 2>/dev/null | /usr/bin/sed '/#.*/d'`;
	
	die _("ERROR installios: command '%s' failed to execute properly:\n\n%s\n", $cmd, $output) if ($?);
	@ret= split /$delim/, $output;

	\@ret;
}

sub get_input
{
	my $type= shift;
	my $i= shift;
	my @values= @$i;
	my $max= @values;
	$i= 0;

	#my %input= (managed_system => "",
	#			partition => "",
	#			profile => "");

	die _("ERROR: installios: No objects of type %s were found.\n", $type) unless ($max > 0);

	print _("The following objects of type \\\"%s\\\" were found.  Please select one:\n\n", $type);

	foreach (@values)
	{
		print ++$i.". $_\n";
	}
	$i= -1;
	print "\n";
	my $range;
	$range= " (1-$max)" if (@values > 1);

	while (!($i =~ /\d+/) || $i < 1 || $i > @values)
	{
		print _("Enter a number").$range.": ";
		$i= <>;
		chomp $i;
	}

	$values[$i - 1];
}

sub get_mac_addr
{
	my $fields= shift;
	my $line;
	my $type= 'mac_addr';
	my $elements;
	my @fields;

	#get subnet addresses
	$my_net= &get_subnet_addr;
	$client_net= &get_subnet_addr($fields->{client_addr}, $fields->{netmask});

	print _("\nRetrieving information for available network adapters\nThis will take several minutes...\n\n");
	$elements= &get_element("LC_ALL=en_US LANG=en_US /usr/hmcrbin/lpar_netboot -n -M -f -A -t ent \"$fields->{vio_partition}\" \"$fields->{profile}\" \"$fields->{managed_system}\"", "\n");

	foreach (@{$elements})
	{
		@fields= split /\s+/, $_;

		die _("ERROR installios: command '%s' failed to execute properly:\n\n%s\n", "/usr/hmcrbin/lpar_netboot -n -M -f -A -t ent \"$fields->{vio_partition}\" \"$fields->{profile}\" \"$fields->{managed_system}\"", join("\n", @{$elements})) unless ($fields[0] eq 'ent' && ($fields[2] =~ /^[0-9a-f]+$/ || $fields[2] eq "NA"));
	}

	$line= &get_input($xlated_fields{$type}, $elements);

	$fields->{$type}= (split /\s+/, $line)[2];
    $fields->{$type} =~ s/([0-9a-f]{2})/$1:/g;
    chop $fields->{$type};
}

sub read_license_input
{
	my $fields= shift;
	my $line;
	my $type= 'language';

	if ($fields->{source} eq '/dev/cdrom')
	{
		#change this message
		print _("Enter a language and locale [en_US]: ");
		$fields->{$type}= <>;
		chomp $fields->{$type};
		$fields->{$type}= 'en_US' unless ($fields->{$type});
	}
}

#if called with no parameters, returns the subnet address of this system
#otherwise, takes an ip address and a subnet mask
sub get_subnet_addr
{
	my $ip_addr= shift;
	my $subnet_mask= shift;
	my @ip_addr;
	my @subnet_mask;
	my @result;
	my $i= 0;

	unless ($ip_addr && $subnet_mask)
	{
		#get the system's ip address and subnet mask
		$ip_addr= $this_addr;
		$subnet_mask= `/sbin/ifconfig eth0 | /usr/bin/grep Mask: | /usr/bin/awk -F: '{print \$4}'`;
		return if ($?);
	}

	return unless ($ip_addr =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ && $subnet_mask =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);

	@ip_addr= split /\./, $ip_addr;
	@subnet_mask= split /\./, $subnet_mask;

	#turn each field into binary
	@ip_addr= map chr, @ip_addr;
	@subnet_mask= map chr, @subnet_mask;

	for (; $i < 4; $i++)
	{
		$result[$i]= $subnet_mask[$i] & $ip_addr[$i];
	}

	#turn each field back into Perl numbers
	@result= map ord, @result;

	return join ".", @result;
}

#this function will return an array of PIDs of other instances of installios.
#they will be sorted by date/time of initial execution
sub get_installios_pids
{
	my @pids= ();

	#return empty pid list if there's no pid files
	if (-f $pid_file)
	{
		sysopen(PIDFILE, $pid_file, O_RDWR | O_CREAT) or die "$pid_file : $!\n";

		#obtain a shared lock for reading
		flock(PIDFILE, LOCK_SH) or die "$pid_file : $!\n";

		#read in all the pids
		while (<PIDFILE>)
		{
			chomp;
			push @pids, $_ if ($_ =~ /^\d+$/ && kill 0 => $_);  # make sure this is a real process
		}

		#close the file, release the lock
		close PIDFILE or die "$pid_file : $!\n";
	}

	@pids;
}

#this function logs this process's pid to a file
sub log_pid
{
	my $orig_contents= "";

	sysopen(PIDFILE, $pid_file, O_RDWR | O_CREAT) or die "$pid_file : $!\n";

	# will block until we can obtain an exclusive lock on the file
	flock(PIDFILE, LOCK_EX) or die "$pid_file : $!\n";

	#read in existing contents of the file
	while (<PIDFILE>) {$orig_contents.= $_}

	#write back out the contents
	seek(PIDFILE, 0, 0) or die "$pid_file : $!\n";
	print PIDFILE $orig_contents or die "$pid_file : $!\n";

	#now append our pid to this file
	print PIDFILE $$."\n" or die "$pid_file : $!\n";

	#close it, thereby releasing the lock
	close PIDFILE or die "$pid_file : $!\n";
}

#this function, called upon termination, removes this instance's pid from the pid file
sub unlog_pid
{
	my $orig_contents= "";

	return unless (-f $pid_file);

	sysopen(PIDFILE, $pid_file, O_RDWR) or die "$pid_file : $!\n";

	# will block until we can obtain an exclusive lock on the file
	flock(PIDFILE, LOCK_EX) or die "$pid_file : $!\n"; 

	#read in existing contents of the file, omitting our pid
	while (<PIDFILE>)
	{
		chomp;
		$orig_contents.= $_."\n" unless ($_ eq $$);
	}

	#write back out the contents
	seek(PIDFILE, 0, 0) or die "$pid_file : $!\n";
	print PIDFILE $orig_contents or die "$pid_file : $!\n";

	#since we've reduced the length of this file, we must truncate it at its current position
	truncate(PIDFILE, tell(PIDFILE)) or die "$pid_file : $!\n";

	#close it, thereby releasing the lock
	close PIDFILE or die "$pid_file : $!\n";

	unlink $pid_file unless (-s $pid_file);
}

sub get_instl_lock
{
	local $SIG{USR1}= 'IGNORE';
	$own_lock= 1 if (flock(INSTL_LOCK, LOCK_EX | LOCK_NB));

	$own_lock;
}

sub _
{
	my $key= shift;
	my $str= `TEXTDOMAINDIR=/usr/share/locale TEXTDOMAIN=installios /usr/bin/gettext -se "$key"`;
	chomp $str;

	sprintf $str, @_;
}
