if (get_answer('Setup is unable to find the "' . $bin . '" program on your machine. Please make sure it is installed. Do you want to specify the location of this program by hand?', 'yesno', 'yes') eq 'no') {
return '';
}
return get_answer('What is the location of the "' . $bin . '" program on your machine?', 'binpath', '');
}
# Execute the command passed as an argument
# _without_ interpolating variables (Perl does it by default)
sub direct_command {
return `$_[0]`;
}
# chmod() that reports errors
sub safe_chmod {
my $mode = shift;
my $file = shift;
if (chmod($mode, $file) != 1) {
error('Unable to change the access rights of the file ' . $file . '.' . "\n\n");
}
}
# Emulate a simplified ls program for directories
sub internal_ls {
my $dir = shift;
my @fn;
opendir(LS, $dir);
@fn = grep(!/^\.\.?$/, readdir(LS));
closedir(LS);
return @fn;
}
# Install a file permission
sub install_permission {
my $src = shift;
my $dst = shift;
my @statbuf;
@statbuf = stat($src);
if (not (defined($statbuf[2]))) {
error('Unable to get the access rights of the source file ' . $src . '.' . "\n\n");
# The previous answer is valid. Make it the default value
$default = $answer;
}
}
$answer = get_answer($msg, $type, $default);
db_add_answer($id, $answer);
return $answer;
}
# Find a suitable backup name and backup a file
sub backup_file {
my $file = shift;
my $i;
for ($i = 0; $i < 100; $i++) {
if (! -e $file . '.old.' . $i) {
my %patch;
undef %patch;
if (internal_sed($file, $file . '.old.' . $i, 0, \%patch)) {
print wrap('File ' . $file . ' is backed up to ' . $file . '.old.' . $i . '.' . "\n\n", 0);
} else {
print STDERR wrap('Unable to backup the file ' . $file . ' to ' . $file . '.old.' . $i .'.' . "\n\n", 0);
}
return;
}
}
print STDERR wrap('Unable to backup the file ' . $file . '. You have too many backups files. They are files of the form ' . $file . '.old.N, where N is a number. Please delete some of them.' . "\n\n", 0);
}
# Uninstall a file previously installed by us
sub uninstall_file {
my $file = shift;
if (not db_file_in($file)) {
# Not installed by this script
return;
}
if (file_name_exist($file)) {
if (db_file_ts($file)) {
my @statbuf;
@statbuf = stat($file);
if (defined($statbuf[9])) {
if (db_file_ts($file) != $statbuf[9]) {
# Modified since this script installed it
backup_file($file);
}
} else {
print STDERR wrap('Unable to get the last modification timestamp of the file ' . $file . '.' . "\n\n", 0);
}
}
} else {
print wrap('This script previously created the file ' . $file . ', and was about to remove it. Somebody else apparently did it already.' . "\n\n", 0);
}
if (not unlink($file)) {
print STDERR wrap('Unable to remove the file ' . $file . '.' . "\n\n", 0);
}
db_remove_file($file);
}
# Return the version of VMware
sub vmware_version {
my $buildNr;
$buildNr = '1.1.2 ' . q$Name: build-364 $;
$buildNr =~ s/Name: //;
return remove_whitespaces($buildNr);
}
# Check the validity of an answer whose type is yesno
# Return a clean answer if valid, or ''
sub check_answer_yesno {
my $answer = shift;
my $source = shift;
if (lc($answer) =~ /^y(es)?$/) {
return 'yes';
}
if (lc($answer) =~ /^n(o)?$/) {
return 'no';
}
if ($source eq 'user') {
print wrap('The answer "' . $answer . '" is invalid. It must be one of "y" or "n".' . "\n\n", 0);
}
return '';
}
$gAnswerSize{'yesno'} = 3;
$gCheckAnswerFct{'yesno'} = \&check_answer_yesno;
# Check the validity of an answer based on its type
# Return a clean answer if valid, or ''
sub check_answer {
my $answer = shift;
my $type = shift;
my $source = shift;
if (not defined($gCheckAnswerFct{$type})) {
die 'check_answer(): type ' . $type . ' not implemented :(' . "\n\n";
# Check the validity of an answer whose type is headerdir
# Return a clean answer if valid, or ''
sub check_answer_headerdir {
my $answer = shift;
my $source = shift;
my $pattern = '@@VMWARE@@';
my $header_version_uts;
my $header_smp;
my $header_page_offset;
$answer = dir_remove_trailing_slashes($answer);
if (not (-d $answer)) {
if ($source eq 'user') {
print wrap('The path "' . $answer . '" is not an existing directory.' . "\n\n", 0);
}
return '';
}
if ( (not (-d $answer . '/linux'))
|| (not (-d $answer . '/asm'))
|| (not (-d $answer . '/net'))) {
if ($source eq 'user') {
print wrap('The path "' . $answer . '" is an existing directory, but it does not contain at least one of these directories "linux", "asm", "net" as expected.' . "\n\n", 0);
}
return '';
}
#
# Check that the running kernel matches the set of header files
#
if (not (-r $answer . '/linux/version.h')) {
if ($source eq 'user') {
print wrap('The path "' . $answer . '" is a kernel header file directory, but it does not contain the file "linux/version.h" as expected. This can happen if the kernel has never been built, or if you have invoked the "make mrproper" command in your kernel directory. In any case, you may want to rebuild your kernel.' . "\n\n", 0);
if (not ($header_version_uts eq $gSystem{'version_uts'})) {
if ($source eq 'user') {
print wrap('The directory of kernel headers (version ' . $header_version_uts . ') does not match your running kernel (version ' . $gSystem{'version_uts'} . '). Consequently, even if the compilation of the module was successful, the module would not load into the running kernel.' . "\n\n", 0);
}
return '';
}
if (not (-r $answer . '/linux/autoconf.h')) {
if ($source eq 'user') {
print wrap('The path "' . $answer . '" is a kernel header file directory, but it does not contain the file "linux/autoconf.h" as expected. This can happen if the kernel has never been built, or if you have invoked the "make mrproper" command in your kernel directory. In any case, you may want to rebuild your kernel.' . "\n\n", 0);
# Check the validity of an answer whose type is ip
# Return a clean answer if valid, or ''
sub check_answer_ip {
my $answer = shift;
my $source = shift;
# I'm in love with regular expressions --hpreg
if ($answer =~ /^([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))){3}$/) {
return $answer;
}
if ($source eq 'user') {
print wrap('The answer "' . $answer . '" is invalid. It must be of the form a.b.c.d where a, b, c and d are decimal numbers between 0 and 255.' . "\n\n", 0);
}
return '';
}
$gAnswerSize{'ip'} = 15;
$gCheckAnswerFct{'ip'} = \&check_answer_ip;
# Check the validity of an answer whose type is yesnohelp
# Return a clean answer if valid, or ''
sub check_answer_yesnohelp {
my $answer = shift;
my $source = shift;
if (lc($answer) =~ /^y(es)?$/) {
return 'yes';
}
if (lc($answer) =~ /^n(o)?$/) {
return 'no';
}
if (lc($answer) =~ /^h(elp)?$/) {
return 'help';
}
if ($source eq 'user') {
print wrap('The answer "' . $answer . '" is invalid. It must be one of "y", "n" or "h".' . "\n\n", 0);
error('You are running a Linux kernel version ' . $gSystem{'version_utsclean'} . ' that was not built with the CONFIG_UMISC configuration parameter set. VMware will not run on this system.' . "\n\n");
if (get_answer('You are running Linux version ' . $gSystem{'version_utsclean'} . ' possibly with a 3Com networking card. Linux kernel versions 2.0.34 and 2.0.35 have a bug in the 3Com driver that interacts badly with this product. Specifically, your physical machine will occasionally hang and will require a hard reset. This bug has been fixed in 2.0.36 and later kernels. Do you want to continue the configuration anyway?', 'yesno', 'no') eq 'no') {
print wrap('The correct version of one or more libraries needed to run vmware may be missing. This is the output of ' . $gHelper{'ldd'} . ' ' . db_get_answer('BINDIR') . '/vmware:' . "\n", 0);
query('This script cannot tell for sure, but you may need to upgrade libc5 to glibc before you can run vmware.' . "\n\n" . 'Hit enter to continue.', '', 0);
error('Your ' . (($gSystem{'smp'} eq 'yes') ? 'processors do' : 'processor does') . ' not support the cpuid instruction. VMware will not run on this system.' . "\n\n");
error('Your ' . (($gSystem{'smp'} eq 'yes') ? 'processors do' : 'processor does') . ' not have a Time Stamp Counter. VMware will not run on this system.' . "\n\n");
}
}
# Install a module if it suitable
# Return 1 if success, 0 if failure
sub try_module {
my $name = shift;
my $mod = shift;
my $force = shift;
my $dst_dir;
my %patch;
if (not (-e $mod)) {
# The module does not exist
return 0;
}
# Probe the module without loading it or executing its code. It is cool
# because it avoids problems like 'Device or resource busy'
# Note: -f bypasses only the kernel version check, not the symbol resolution
get_persistent_answer('What is the location of the directory of C header files that match your running kernel?', 'HEADER_DIR', 'headerdir', '/usr/src/linux/include');
}
print wrap('Extracting the sources of the ' . $name . ' module.' . "\n\n", 0);
# Do the work on a local filsystem to avoid NFS vs. root permission problems.
print wrap('The module has perfectly been loaded in the running kernel.' . "\n\n", 0);
remove_build_dir($build_dir);
return;
}
# Don't remove the build dir so that the user can investiguate
error('Unable to make a ' . $name . ' module that can be loaded in the running kernel. There is probably a light difference of kernel configuration between the set of C header files you specified and your running kernel. You may want to rebuild a kernel based on that directory, or specify another directory.' . "\n\n");
}
# Create a list of modules suitable for the running kernel
# The kernel module loader does quite a good job when modules are versioned.
# But in the other case, we must be _very_ careful
if (get_persistent_answer('None of VMware' . "'" . 's pre-built ' . $name . ' modules is suitable for your running kernel. Do you want this script to try to build the ' . $name . ' module for your system (you need to have a C compiler installed on your system)?', 'BUILDR_' . $name, 'yesno', 'yes') eq 'no') {
error('Unable to continue.' . "\n\n");
}
build_module($name, $mod_dir . '/source');
}
# Create a character device
sub configure_chrdev {
my $name = shift;
my $major = shift;
my $minor = shift;
uninstall_file($name);
if (-e $name) {
if (-c $name) {
my @statbuf;
@statbuf = stat($name);
if ( defined($statbuf[6])
&& (($statbuf[6] >> 8) == $major)
&& (($statbuf[6] & 0xFF) == $minor)) {
# The character device is already correctly configured
return;
}
}
if (get_answer('This script wanted to create the character device ' . $name . ' with major number ' . $major . ' and minor number ' . $minor . ', but there is already a different kind of file at this location. Overwrite?', 'yesno', 'yes') eq 'no') {
error('Unable to continue.' . "\n\n");
}
# mknod doesn't like when the file already exists
unlink($name);
}
if (system(shell_string($gHelper{'mknod'}) . ' ' . shell_string($name) . ' c ' . shell_string($major) . ' ' . shell_string($minor))) {
error('Unable to create the character device ' . $name . ' with major number ' . $major . ' and minor number ' . $minor . '.' . "\n\n");
}
safe_chmod(0600, $name);
# These file don't have a content, don't timestamp them
db_add_file($name, 0);
}
# Configuration related to the monitor
sub configure_mon {
configure_module('vmmon');
configure_chrdev('/dev/vmmon', 10, 165);
}
# Configuration related to parallel ports
sub configure_pp {
my $i;
if ($gSystem{'version_integer'} < kernel_version_integer(2, 1, 127)) {
query('You are running Linux version ' . $gSystem{'version_utsclean'} . ', and this kernel can not provide VMware with Bidirectional Parallel Port support. A fully-featured VMware requires Linux version 2.1.127 or higher.' . "\n\n" . 'Without this support, VMware will run flawlessly, but will lack the ability to use parallel ports in a bidirectional way. This means that it is possible that some parallel port devices (scanners, dongles, ...) will not work inside a Virtual Machine.' . "\n\n" . 'Hit enter to continue.', '', 0);
return;
}
if ($gSystem{'version_integer'} > kernel_version_integer(2, 3, 9)) {
query('You are running Linux version ' . $gSystem{'version_utsclean'} . ', and VMware does not provide support for Bidirectional Parallel Ports for Linux version 2.3.10 or higher yet.' . "\n\n" . 'Without this support, VMware will run flawlessly, but will lack the ability to use parallel ports in a bidirectional way. This means that it is possible that some parallel port devices (scanners, dongles, ...) will not work inside a Virtual Machine.' . "\n\n" . 'Hit enter to continue.', '', 0);
return;
}
# The vmppuser module relies on the parport modules. Let's
# make sure it is loaded before beginning our tests
# This comment fixes emacs's broken syntax highlighting
# parport support is not built in the kernel
if (system(shell_string($gHelper{'modprobe'}) . ' parport')) {
query('Unable to load the parport module that is required by the vmppuser module. You may want to load it manually before re-running this script.' . "\n\n" . 'Without this support, VMware will run flawlessly, but will lack the ability to use parallel ports in a bidirectional way. This means that it is possible that some parallel port devices (scanners, dongles, ...) will not work inside a Virtual Machine.' . "\n\n" . 'Hit enter to continue.', '', 0);
return;
}
}
# The vmppuser module relies on the parport_pc modules. Let's
# make sure it is loaded before beginning our tests
# This comment fixes emacs's broken syntax highlighting
# parport_pc support is not built in the kernel
if (system(shell_string($gHelper{'modprobe'}) . ' parport_pc')) {
query('Unable to load the parport_pc module that is required by the vmppuser module. You may want to load it manually before re-running this script.' . "\n\n" . 'Without this support, VMware will run flawlessly, but will lack the ability to use parallel ports in a bidirectional way. This means that it is possible that some parallel port devices (scanners, dongles, ...) will not work inside a Virtual Machine.' . "\n\n" . 'Hit enter to continue.', '', 0);
return;
}
}
configure_module('vmppuser');
# Try to unload the modules. Failure is allowed because some other
# The -a is important because it lists all interfaces (not only those
# which are up). The vmnet driver knows how to deal with down interfaces.
open(IFCONFIG, shell_string($gHelper{'ifconfig'}) . ' -a |');
@gAvailEthIf = ();
while (<IFCONFIG>) {
if (/^eth/) {
my @fields;
@fields = split(/[ ]+/);
push(@gAvailEthIf, $fields[0]);
}
}
if ($#gAvailEthIf == -1) {
# No interface. We provide a valid default so that everything works.
db_add_answer('VNET_INTERFACE', 'eth0');
return;
}
if ($#gAvailEthIf == 0) {
# Only one interface. Use it.
db_add_answer('VNET_INTERFACE', $gAvailEthIf[0]);
return;
}
# Several interfaces
get_persistent_answer('Your computer has multiple ethernet network interfaces: ' . join(', ', @gAvailEthIf) . '. Which one do you want the Virtual Machines to use?', 'VNET_INTERFACE', 'availethif', 'eth0');
}
# Probe for an unused private subnet
# Return value is 1 if success, 0 if failure
sub hostonly_probe {
my $i;
my @subnets;
my $tries;
my $maxTries = 100;
my $pings;
my $maxPings = 10;
# XXX We only consider class C subnets for the moment
my $netmask = '255.255.255.0';
# Generate the table of private class C subnets
@subnets = ();
for ($i = 0; $i < 255; $i++) {
$subnets[2 * $i ] = '192.168.' . $i;
$subnets[2 * $i + 1] = '172.16.' . $i;
}
print wrap('Probing for an unused private subnet (this can take some time).' . "\n\n", 0);
$tries = 0;
$pings = 0;
srand(time);
# Beware, 'last' doesn't seem to work in 'do'-'while' loops
for (;;) {
my $r;
my $subnet;
my $status;
$tries++;
$r = int(rand(2 * 256));
if ($subnets[$r] eq '') {
# Already tried
next;
}
$subnet = $subnets[$r];
$subnets[$r] = '';
# Our convention is that the host OS IP address is <subnet>.1
if (($pings == $maxPings) || ($tries == $maxTries)) {
last;
}
}
print STDERR wrap('We were unable to locate an unused Class C subnet in the range of private network numbers. For each subnet that we tried we received a response to our ICMP ping packets from a host at the network address intended for assignment to this host machine. Because no private subnet appears to be unused you will need to explicitly specify a network number.' . "\n\n", 0);
return 0;
}
# Display the DHCP copyright information
sub show_ISC {
if (not defined($gDBAnswer{'ISC_COPYRIGHT_SEEN'})) {
query('Press enter to display the DHCP server copyright information.', '', 0);
query('This system appears to have a DHCP server configured for normal use. Beware that you should teach it how not to interfere with VMware' . "'" . 's DHCP server. There are two ways to do this:' . "\n\n" . '1) Modify the file ' . $conf . ' to add something like:' . "\n\n" . 'subnet ' . $network . ' netmask ' . $netmask . ' {' . "\n" . ' # Note: No range is given, vmnet-dhcpd will deal with this subnet.' . "\n" . '}' . "\n\n" . '2) Start your DHCP server with an explicit list of network interfaces to deal with (leaving out ' . $gHostOnlyEthIf . '). e.g.:' . "\n\n" . 'dhcpd eth0' . "\n\n" . 'Consult the dhcpd(8) and dhcpd.conf(5) manual pages for details.' . "\n\n" . 'Hit enter to continue.', '', 0);
}
}
# Generate an interfaces specification for a samba configuration file
sub samba_make_interfaces {
my $if;
my $result;
my $sep;
# We assume that ifconfig without any command option only display interfaces
query('This system appears to have a CIFS/SMB server (Samba) configured for normal use. Note that if you want to offer service to Virtual Machines running on the host-only network, you must modify your ' . $conf . ' file to list the networks Samba should deal with. You can do this by adding a line looking like this one:' . "\n\n" . 'interfaces = ' . samba_make_interfaces() . ' ' . $hostaddr . '/' . $netmask . "\n\n" . 'You may also need to update any related security controls you might have setup such as the "hosts allow" specification.' . "\n\n" . 'Consult the smb.conf(5) manual page for more details.' . "\n\n" . 'Hit enter to continue.', '', 0);
}
}
# Configuration of hostonly networking
sub configure_hostonly_net {
my $keep_settings;
if (get_persistent_answer('Do you want to be able to use host-only networking in your Virtual Machines?', 'VNET_HOSTONLY', 'yesno', 'yes') eq 'no') {
return;
}
$keep_settings = 'no';
if ( defined($gDBAnswer{'VNET_HOSTONLY_HOSTADDR'})
$keep_settings = get_answer('Host-only networking is currently configured to use the private subnet ' . compute_subnet($gDBAnswer{'VNET_HOSTONLY_HOSTADDR'}, $gDBAnswer{'VNET_HOSTONLY_NETMASK'}) . '/' . $gDBAnswer{'VNET_HOSTONLY_NETMASK'} . '. Do you want to keep these settings?', 'yesno', 'yes');
}
if ($keep_settings eq 'no') {
for (;;) {
my $answer;
$answer = get_answer('Do you want this script to probe for an unused private subnet? (yes/no/help)', 'yesnohelp', 'yes');
if ($answer eq 'yes') {
if (hostonly_probe()) {
last;
}
# Fallback
$answer = 'no';
}
if ($answer eq 'no') {
get_persistent_answer('What will be the IP address of your host on the private network?', 'VNET_HOSTONLY_HOSTADDR', 'ip', '');
get_persistent_answer('What will be the netmask of your private network?', 'VNET_HOSTONLY_NETMASK', 'ip', '');
last;
}
print wrap('Virtual machines configured to use host-only networking are placed on a virtual network that is confined to this host. Virtual machines on this network can communicate with each other and the host, but no one else.' . "\n\n" . 'To setup this host-only networking you need to select a network number that is normally unreachable from the host. We can automatically select this number for you, or you can specify a network number that you want.' . "\n\n" . 'The automatic selection process works by testing a series of Class C subnet numbers to see if they are reachable from the host. The first one that is unreachable is used. The subnet numbers are chosen from the private network numbers specified by the Internet Engineering Task Force (IETF) in RFC 1918 (http://www.isi.edu/in-notes/rfc1918.txt).' . "\n\n" . 'Remember that the host-only network that virtual machines reside on will not be accessible outside the host. This means that it is ok to use the same number on different systems so long as you do not enable communication between these networks.' . "\n\n", 0);
}
}
show_ISC();
write_dhcpd_config();
dhcpd_consultant();
samba_consultant();
}
# Configuration related to networking
sub configure_net {
my $i;
if (get_persistent_answer('Do you want to be able to use the network in your Virtual Machines?', 'NETWORKING', 'yesno', 'yes') eq 'no') {
return;
}
configure_module('vmnet');
for ($i = 0; $i < 4; $i++) {
configure_chrdev('/dev/vmnet' . $i, 119, $i);
}
configure_bridged_net();
configure_hostonly_net();
}
# Install one symbolic link
sub install_symlink {
my $to = shift;
my $name = shift;
uninstall_file($name);
if (file_check_exist($name)) {
return;
}
# The file could be a symlink to another location. Remove it
unlink($name);
if (not symlink($to, $name)) {
error('Unable to create the symbolic link ' . $name . ' pointing to the file ' . $to . '.' . "\n\n");
}
db_add_file($name, 0);
}
# Install a pair of S/K startup scripts for a given runlevel
sub link_runlevel {
my $level = shift;
# Create the S symlink
# We use 90 because samba is at 91 and it didn't like it when we used 99.
print wrap('The configuration of VMware ' . vmware_version() . ' for Linux for this running kernel completed successfully.' . "\n\n" . 'You can now run VMware by invoking the following command: "' . db_get_answer('BINDIR') . '/vmware".' . "\n\n" . 'Enjoy,' . "\n\n" . ' --the VMware team' . "\n\n", 0);