Tryag File Manager
Home
-
Turbo Force
Current Path :
/
proc
/
self
/
root
/
usr
/
bin
/
Upload File :
New :
File
Dir
//proc/self/root/usr/bin/vmware-config-tools.pl
#!/usr/bin/perl -w # If your copy of perl is not in /usr/bin, please adjust the line above. # # Copyright (c) 1998-2019 VMware, Inc. All rights reserved. # # Host configurator for VMware use strict; use IO::Handle qw( ); # For autoflush STDOUT->autoflush(1); # Use Config module to update VMware host-wide configuration file # BEGINNING_OF_CONFIG_DOT_PM #!/usr/bin/perl ### ### TODOs: ### config file hierarchies ### open/close/check file ### error handling ### config file checker ### pretty print should print not present devices not in misc ### use strict; package VMware::Config; my %PREF; #$PREF{'commentChanges'} = 1; sub new() { my $proto = shift; my $class = ref($proto) || $proto; my $self = $proto->create(); bless($self, $class); return($self); } sub create { my $self = {}; $self->{db} = {}; $self->{tr} = 1; return($self); } sub preserve_case($) { my $self = shift; my $preserve = shift; $self->{tr} = !$preserve; } sub clear() { my $self = {}; $self->{db} = {}; } sub readin($) { my $self = shift; my ($file) = @_; my $text = ""; my @stat = stat($file); $self->{timestamp} = $stat[9]; open(CFG, "< $file") || return undef; while (<CFG>) { $text = $text . $_; } close(CFG); my $ret = $self->parse($text); if (!defined($ret)) { return undef; } $self->{file} = $file; $self->{text} = $text; return 1; } sub writeout($) { my $self = shift; my ($file) = @_; if (!defined($file)) { $file = $self->{file}; } open(CFG, "> $file") || return undef; print CFG $self->update($self->{text}); close(CFG); return 1; } sub overwrite($$) { my $self = shift; my($orig, $file) = @_; if (!defined($file)) { $file = $orig->{file}; } open(CFG, "> $file") || return undef; print CFG $self->update($orig->{text}); close(CFG); return 1; } sub pretty_overwrite { my $self = shift; my($file) = @_; if (!defined($file)) { $file = $self->{file}; } open(CFG, "> $file") || return undef; print CFG $self->pretty_print(); close(CFG); return 1; } sub parse($) { my $self = shift; my ($text) = @_; my(@lines, $line, $num); @lines = split(/\n/, $text); $num = 1; foreach $line (@lines) { my($status, $name, $value, $start, $end) = $self->parse_line($line); if (!defined($status)) { $self->clear(); # syntax error on line $num return undef; } elsif ($status == 1) { if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } $self->{db}{$name}{value} = $value; $self->{db}{$name}{modified} = 0; $self->{db}{$name}{mark} = 0; } elsif ($status == 0) { # noop } else { $self->clear(); # internal error return undef; } $num++; } return 1; } sub timestamp() { my $self = shift; return $self->{timestamp}; } sub get() { my $self = shift; my($name, $default) = @_; if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } if (defined($self->{db}{$name})) { $self->{db}{$name}{mark} = 1; return $self->{db}{$name}{value}; } else { return $default; } } sub get_bool() { my $self = shift; my($name, $default) = @_; my $val = $self->get($name); if (!defined($val)) { $val = $default; } if ($val =~ /TRUE|1|Y|YES/i) { $val = 1; } else { $val = 0; } return $val; } sub set($$) { my $self = shift; my($name, $value) = @_; if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } $self->{db}{$name}{value} = $value; $self->{db}{$name}{modified} = 1; $self->{db}{$name}{mark} = 0; } sub remove($) { my $self = shift; my($name) = @_; if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } delete $self->{db}{$name}; } sub list($) { my $self = shift; my($pattern) = @_; return sort(grep(/$pattern/, keys(%{$self->{db}}))); } sub device_list { my $self = shift; my($name, $pattern, $show_all) = @_; my($dev, $val, %present); $show_all = 0 if (!defined($show_all)); foreach $_ (keys(%{$self->{db}})) { if (/$name($pattern)\.present/) { $dev = $name . $1; $val = $self->get_bool("$dev.present"); if ($show_all || !defined($val) || ($val)) { $present{$dev} = 1; } } } return sort(keys(%present)); } sub update($) { my $self = shift; my ($text) = @_; my $out = ""; my($line, $name); my @lines; if (defined($text)) { @lines = split(/\n/, $text); } my $num = 1; $self->unmark_all(); foreach $line (@lines) { my($status, $name, $value, $start, $end) = $self->parse_line($line); if (defined($name)) { if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } } ### ### five cases ### ### 1. deleted ### 2. modified ### 3. unmodified ### 4. comment or blank line ### 5. new (handled at the end) ### $line = $line . "\n"; if (!defined($status)) { # XXX syntax error on line $num return undef; } elsif ($status == 1) { if (!defined($self->{db}{$name})) { ### ### Case 1. removed ### if (defined($PREF{'commentChanges'})) { $line = "# " . $line; } else { $line = ""; } } else { $self->mark($name); if ($self->{db}{$name}{value} ne $value) { ### ### Case 2. modified ### my $newline = substr($line, 0, $start) . "\"" . $self->{db}{$name}{value} . "\"" . substr($line, $end); if (defined($PREF{'commentChanges'})) { $line = "# " . $line . $newline; } else { $line = $newline; } } else { ### ### Case 3. unmodified ### } } } elsif ($status == 0) { ### ### Case 4. comment or blank line ### } else { # XXX internal error: parse_line returned unknown status \"$status\" return undef; } $out = $out . "$line"; $num++; } ### ### Case 5. new entries ### $out = $out . $self->print_unmarked(); return $out; } sub dump_all() { my $self = shift; my $out = ""; my $name; foreach $name (keys(%{$self->{db}})) { $out = $out . "$name = \"$self->{db}{$name}{value}\"\n"; } return $out; } sub pretty_print($) { my $self = shift; my($templ) = @_; my $out = ""; my $sec; $self->unmark_all(); foreach $sec (@{$templ}) { $out = $out . $self->print_section($sec, ""); } $out = $out . "###\n### Misc.\n###\n\n"; $out = $out . $self->print_unmarked(); return $out; } sub print_section { my $self = shift; my($sec, $prefix) = @_; my $out = ""; my @list; my $dev; if (defined($sec->{header})) { $out = $out . "###\n### $sec->{header}\n###\n\n"; } ## name is here for compatibility, it should go away soon. my $name = defined($sec->{name}) ? $sec->{name} : ""; if (defined($sec->{pattern})) { @list = $self->device_list($prefix . $name, $sec->{pattern}, 1); foreach $dev (@list) { if (defined($sec->{title})) { $out = $out . sprintf("# $sec->{title}\n\n", $dev); } $out = $out . $self->print_values("$dev", $sec->{values}); if (defined($sec->{sublist})) { $out = $out . $self->print_section($sec->{sublist}, "$dev"); } } } else { if (defined($sec->{values})) { $out = $out . $self->print_values($prefix . $name, $sec->{values}); } else { $out = $out . $self->print_value($prefix . $name, "is not set"); $out = $out . "\n"; } } return $out; } sub print_values { my $self = shift; my($name, $vars) = @_; my $var; my $out = ""; foreach $var (@{$vars}) { my $v = ($name ne "") ? "$name.$var" : $var; $out = $out . $self->print_value($v); } $out = $out . "\n"; return $out; } sub print_value { my $self = shift; my($name, $notset) = @_; my $val = $self->get($name); if (defined($val)) { $self->mark($name); return "$name = \"$val\"\n"; } elsif (defined($notset)) { return "# $name $notset\n"; } } sub mark($) { my $self = shift; my($name) = @_; if ($self->{tr}) { $name =~ tr/A-Z/a-z/; } $self->{db}{$name}{mark} = 1; } sub unmark_all() { my $self = shift; my $name; foreach $name (keys %{$self->{db}}) { $self->{db}{$name}{mark} = 0; } } sub get_unmarked() { my $self = shift; my $name; my @list = (); foreach $name (keys %{$self->{db}}) { if (!$self->{db}{$name}{mark}) { push(@list, $name); } } return @list; } sub print_unmarked() { my $self = shift; my @unmarked = $self->get_unmarked(); my $out = ""; my $name; foreach $name (@unmarked) { $out = $out . "$name = \"$self->{db}{$name}{value}\"\n"; } return $out; } sub parse_line($) { my $self = shift; ($_) = @_; if (/^\s*(\#.*)?$/) { return (0); } elsif (/^((\s*(\S+)\s*=\s*)(([\"]([^\"]*)[\"])|(\S+)))\s*(\#.*)?$/) { my $prefix1 = $2; my $prefix2 = $1; my $name = $3; my $value; if (defined($6)) { $value = $6; } else { $value = $7; } return (1, $name, $value, length($prefix1), length($prefix2)); } return (undef); } 1; # END_OF_CONFIG_DOT_PM # BEGINNING_OF_UTIL_DOT_PL #!/usr/bin/perl use strict; no warnings 'once'; # Warns about use of Config::Config in config.pl my $have_thinprint='no'; my $have_vgauth='yes'; my $have_caf='yes'; my $have_grabbitmqproxy='yes'; my $need_glibc25='yes'; my $need_ubuntu1004='@@NEED_UBUNTU1004@@'; # A list of known open-vm-tools packages # my @cOpenVMToolsRPMPackages = ("vmware-kmp-debug", "vmware-kmp-default", "vmware-kmp-pae", "vmware-kmp-trace", "vmware-guest-kmp-debug", "vmware-guest-kmp-default", "vmware-guest-kmp-desktop", "vmware-guest-kmp-pae", "open-vm-tools-gui", "open-vm-tools"); my @cOpenVMToolsDEBPackages = ( "open-vm-dkms", "open-vm-source", "open-vm-toolbox", "open-vm-tools", "open-vm-tools-dbg", ); my @cOpenVMToolsRPMPackagesFail = ( "libvmtools-devel", "libvmtools0"); # A list of the DB keys to obtain the directory paths of VMware Tools on # a SELinux enabled system that must or should have their file context # attributes set immediately after installation and before attempting to # start any VMware Tools service. # my @cSELinuxDirKeys = ( # default Linux paths - typically "BINDIR", # /usr/bin "SBINDIR", # /usr/sbin "REGDIR", # /etc/vmware-tools "CAFETCDIR", # /etc/vmware-caf "LIBDIR", # /usr/lib/vmware-tools "VGAUTHLIBDIR", # /usr/lib/vmware-vgauth "CAFLIBDIRSELINUX" # /usr/lib/vmware-caf ); # Moved out of config.pl to support $gOption in spacechk_answer my %gOption; # Moved from various scripts that include util.pl my %gHelper; # # All the known modules that the config.pl script needs to # know about. Modules in this list are searched for when # we check for non-vmware modules on the system. # my @cKernelModules = ('vmblock', 'vmhgfs', 'vmmemctl', 'vmxnet', 'vmci', 'vsock', 'vmsync', 'pvscsi', 'vmxnet3', 'vmwsvga'); # # This list simply defined what modules need to be included # in the system ramdisk when we rebuild it. # my %cRamdiskKernelModules = (vmxnet3 => 'yes', pvscsi => 'yes', vmxnet => 'yes'); # # This defines module dependencies. It is a temporary solution # until we eventually move over to using the modules.xml file # to get our dependency information. # my %cKernelModuleDeps = (vsock => ('vmci'), vmhgfs => ('vmci')); # # Module PCI ID and alias definitions. # my %cKernelModuleAliases = ( # PCI IDs first 'pci:v000015ADd000007C0' => 'pvscsi', 'pci:v000015ADd00000740' => 'vmci', 'pci:v000015ADd000007B0' => 'vmxnet3', 'pci:v000015ADd00000720' => 'vmxnet', # Arbitrary aliases next 'vmware_vsock' => 'vsock', 'vmware_vmsync' => 'vmsync', 'vmware_vmmemctl' => 'vmmemctl', 'vmware_vmhgfs' => 'vmhgfs', 'vmware_vmblock' => 'vmblock', 'vmware_balloon' => 'vmmemctl', 'vmw_pvscsi' => 'pvscsi', ); # # Upstream module names and their corresponding internal module names. # my %cUpstrKernelModNames = ( 'vmw_balloon' => 'vmmemctl', 'vmw_pvscsi' => 'pvscsi', 'vmw_vmxnet3' => 'vmxnet3', 'vmware_balloon' => 'vmmemctl', 'vmxnet3' => 'vmxnet3', ); # # Table mapping vmware_product() strings to applicable services script or # Upstart job name. # my %cProductServiceTable = ( 'nvdk' => 'nvdk', 'player' => 'vmware', 'tools-for-freebsd' => 'vmware-tools.sh', 'tools-for-linux' => 'vmware-tools', 'tools-for-solaris' => 'vmware-tools', 'vix-disklib' => 'vmware-vix-disklib', 'ws' => 'vmware', '@@VCLI_PRODUCT@@' => '@@VCLI_PRODUCT_PATH_NAME@@', ); my %cToolsLinuxServices; if ($have_thinprint eq 'yes') { %cToolsLinuxServices = ( 'services' => 'vmware-tools', 'thinprint' => 'vmware-tools-thinprint', ); } else { %cToolsLinuxServices = ( 'services' => 'vmware-tools', ); } my %cToolsSolarisServices = ( 'services' => 'vmware-tools', ); my %cToolsFreeBSDServices = ( 'services' => 'vmware-tools.sh', ); # # Hashes to track vmware modules. # my %gNonVmwareModules = (); my %gVmwareInstalledModules = (); my %gVmwareRunningModules = (); my $cTerminalLineSize = 79; # Flags my $cFlagTimestamp = 0x1; my $cFlagConfig = 0x2; my $cFlagDirectoryMark = 0x4; my $cFlagUserModified = 0x8; my $cFlagFailureOK = 0x10; # See vmware_service_issue_command my $cServiceCommandDirect = 0; my $cServiceCommandSystem = 1; # Strings for Block Appends. my $cMarkerBegin = "# Beginning of the block added by the VMware software - DO NOT EDIT\n"; my $cMarkerEnd = "# End of the block added by the VMware software\n"; my $cDBAppendString = 'APPENDED_FILES'; # util.pl Globals my %gSystem; # Needed to access $Config{...}, the Perl system configuration information. require Config; # Tell if the user is the super user sub is_root { return $> == 0; } # Use the Perl system configuration information to make a good guess about # the bit-itude of our platform. If we're running on Solaris we don't have # to guess and can just ask isainfo(1) how many bits userland is directly. sub is64BitUserLand { if (vmware_product() eq 'tools-for-solaris') { if (direct_command(shell_string($gHelper{'isainfo'}) . ' -b') =~ /64/) { return 1; } else { return 0; } } if ($Config::Config{archname} =~ /^(x86_64|amd64)-/) { return 1; } else { return 0; } } # Return whether or not this is a hosted desktop product. sub isDesktopProduct { return vmware_product() eq "ws" || vmware_product() eq "player"; } sub isToolsProduct { return vmware_product() =~ /tools-for-/; } # Call to specify lib suffix, mainly for FreeBSD tools where multiple versions # of the tools are packaged up in 32bit and 64bit instances. So rather than # simply lib or bin, there is lib32-6 or bin64-53, where -6 refers to FreeBSD # version 6.0 and 53 to FreeBSD 5.3. sub getFreeBSDLibSuffix { return getFreeBSDSuffix(); } # Call to specify lib suffix, mainly for FreeBSD tools where multiple versions # of the tools are packaged up in 32bit and 64bit instances. So rather than # simply lib or bin, there is lib32-6 or bin64-53, where -6 refers to FreeBSD # version 6.0 and 53 to FreeBSD 5.3. sub getFreeBSDBinSuffix { return getFreeBSDSuffix(); } # Call to specify lib suffix, mainly for FreeBSD tools where multiple versions # of the tools are packaged up in 32bit and 64bit instances. In the case of # sbin, a lib compatiblity between 5.0 and older systems appeared. Rather # than sbin32, which exists normally for 5.0 and older systems, there needs # to be a specific sbin: sbin32-5. There is no 64bit set. sub getFreeBSDSbinSuffix { my $suffix = ''; my $release = `uname -r | cut -f1 -d-`; chomp($release); if (vmware_product() eq 'tools-for-freebsd' && $release == 5.0) { $suffix = '-5'; } else { $suffix = getFreeBSDSuffix(); } return $suffix; } sub getFreeBSDSuffix { my $suffix = ''; # On FreeBSD, we ship different builds of binaries for different releases. # # For FreeBSD 6.0 and higher (which shipped new versions of libc) we use the # binaries located in the -6 directories. # # For releases between 5.3 and 6.0 (which were the first to ship with 64-bit # userland support) we use binaries from the -53 directories. # # For FreeBSD 5.0, we use binaries from the sbin32-5 directory. # # Otherwise, we just use the normal bin and sbin directories, which will # contain binaries predominantly built against 3.2. if (vmware_product() eq 'tools-for-freebsd') { my $release = `uname -r | cut -f1 -d-`; # Tools lowest supported FreeBSD version is now 6.1. Since the lowest # modules we ship are for 6.3, we will just use these instead. They are # suppoed to be binary compatible (hopefully). if ($release >= 6.0) { $suffix = '-63'; } elsif ($release >= 5.3) { $suffix = '-53'; } elsif ($release >= 5.0) { # sbin dir is a special case here and is handled within getFreeBSDSbinSuffix(). $suffix = ''; } } return $suffix; } # Determine what version of FreeBSD we're on and convert that to # install package values. sub getFreeBSDVersion { my $system_version = direct_command("sysctl kern.osrelease"); if ($system_version =~ /: *([0-9]+\.[0-9]+)-/) { return "$1"; } # If we get here, we were unable to parse kern.osrelease return ''; } # Determine what version of Solaris we are on and convert that to # a two segment version value. This will handle the beta release number # schemes that have been identified. A "0.0" string is returned if the # "uname -v" returns a string that cannot be parsed as expected. sub getSolarisVersion { my $system_version = direct_command(shell_string($gHelper{'uname'}) . ' -v'); if ($system_version =~ /([0-9]+\.[0-9]+)/) { return "$1"; } # If we get here, we were unable to parse version number. chomp($system_version); print wrap("WARNING: Unable to parse OS release version from 'uname -v' " . "output: \"" . $system_version . "\"\n\n"); return "0.0"; } sub solaris_os_version { my $solVersion = direct_command(shell_string($gHelper{'uname'}) . ' -r'); chomp($solVersion); my ($major, $minor) = split /\./, $solVersion; return ($major, $minor); } sub solaris_os_name { my $solName = direct_command(shell_string($gHelper{'uname'}) . ' -v'); chomp($solName); return $solName; } # Determine whether SELinux is enabled. # Return: 1 - SELinux is enabled # 0 - SELinux is not enabled. sub is_selinux_enabled { my $cmd = internal_which('selinuxenabled'); if (defined $cmd && -x $cmd) { my $rv = system($cmd); return ($rv eq 0); } else { return 0; } } # Wordwrap system: append some content to the output sub append_output { my $output = shift; my $pos = shift; my $append = shift; $output .= $append; $pos += length($append); if ($pos >= $cTerminalLineSize) { $output .= "\n"; $pos = 0; } return ($output, $pos); } # Wordwrap system: deal with the next character sub wrap_one_char { my $output = shift; my $pos = shift; my $word = shift; my $char = shift; my $reserved = shift; my $length; if (not (($char eq "\n") || ($char eq ' ') || ($char eq ''))) { $word .= $char; return ($output, $pos, $word); } # We found a separator. Process the last word $length = length($word) + $reserved; if (($pos + $length) > $cTerminalLineSize) { # The last word doesn't fit in the end of the line. Break the line before # it $output .= "\n"; $pos = 0; } ($output, $pos) = append_output($output, $pos, $word); $word = ''; if ($char eq "\n") { $output .= "\n"; $pos = 0; } elsif ($char eq ' ') { if ($pos) { ($output, $pos) = append_output($output, $pos, ' '); } } return ($output, $pos, $word); } # Wordwrap system: word-wrap a string plus some reserved trailing space sub wrap { my $input = shift; my $reserved = shift; my $output; my $pos; my $word; my $i; if (!defined($reserved)) { $reserved = 0; } $output = ''; $pos = 0; $word = ''; for ($i = 0; $i < length($input); $i++) { ($output, $pos, $word) = wrap_one_char($output, $pos, $word, substr($input, $i, 1), 0); } # Use an artifical last '' separator to process the last word ($output, $pos, $word) = wrap_one_char($output, $pos, $word, '', $reserved); return $output; } # # send_rpc_failed_msgs # # A place that gets called when the configurator/installer bails out. # this ensures that the all necessary RPC end messages are sent. # sub send_rpc_failed_msgs { send_rpc("toolinstall.installerActive 0"); send_rpc('toolinstall.end 0'); } # Print an error message and exit sub error { my $msg = shift; # Ensure you send the terminating RPC message before you # unmount the CD. my $rpcresult = send_rpc('toolinstall.is_image_inserted'); chomp($rpcresult); # Send terminating RPC messages send_rpc_failed_msgs(); print STDERR wrap($msg . 'Execution aborted.' . "\n\n", 0); # Now unmount the CD. if ("$rpcresult" =~ /1/) { eject_tools_install_cd_if_mounted(); } exit 1; } # Convert a string to its equivalent shell representation sub shell_string { my $single_quoted = shift; $single_quoted =~ s/'/'"'"'/g; # This comment is a fix for emacs's broken syntax-highlighting code return '\'' . $single_quoted . '\''; } # Send an arbitrary RPC command to the VMX sub send_rpc { my $command = shift; my $rpctoolSuffix; my $rpctoolBinary = ''; my $libDir; my @rpcResultLines; if (vmware_product() eq 'tools-for-solaris') { $rpctoolSuffix = is64BitUserLand() ? '/sbin/amd64' : '/sbin/i86'; } else { $rpctoolSuffix = is64BitUserLand() ? '/sbin64' : '/sbin32'; } $rpctoolSuffix .= getFreeBSDSbinSuffix() . '/vmware-rpctool'; # We don't yet know if vmware-rpctool was copied into place. # Let's first try getting the location from the DB. $libDir = db_get_answer_if_exists('LIBDIR'); if (defined($libDir)) { $rpctoolBinary = $libDir . $rpctoolSuffix; } if (not (-x "$rpctoolBinary")) { # The DB didn't help. But no matter, we can # extract a path to the untarred tarball installer from our # current location. With that info, we can invoke the # rpc tool directly out of the staging area. Woot! $rpctoolBinary = "./lib" . $rpctoolSuffix; } # If we found the binary, send the RPC. if (-x "$rpctoolBinary") { open (RPCRESULT, shell_string($rpctoolBinary) . " " . shell_string($command) . ' 2> /dev/null |'); @rpcResultLines = <RPCRESULT>; close RPCRESULT; return (join("\n", @rpcResultLines)); } else { # Return something so we don't get any undef errors. return ''; } } # 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"); } } # Create a temporary directory # # They are a lot of small utility programs to create temporary files in a # secure way, but none of them is standard. So I wrote this sub make_tmp_dir { my $prefix = shift; my $tmp; my $serial; my $loop; my $tmpdir; $tmp = defined($ENV{'TMPDIR'}) ? $ENV{'TMPDIR'} : '/tmp'; # Don't overwrite existing user data # -> Create a directory with a name that didn't exist before # # This may never succeed (if we are racing with a malicious process), but at # least it is secure $serial = 0; for (;;) { # Check the validity of the temporary directory. We do this in the loop # because it can change over time if (not (-d $tmp)) { error('"' . $tmp . '" is not a directory.' . "\n\n"); } if (not ((-w $tmp) && (-x $tmp))) { error('"' . $tmp . '" should be writable and executable.' . "\n\n"); } # Be secure # -> Don't give write access to other users (so that they can not use this # directory to launch a symlink attack) $tmpdir = "$tmp/$prefix-$$.$serial"; if (mkdir($tmpdir, 0755)) { last; } $serial++; if ($serial % 200 == 0) { print STDERR 'Warning: The "' . $tmp . '" directory may be under attack.' . "\n\n"; } } return $tmpdir; } # Call restorecon on the supplied file if SELinux is enabled. Run with # the options: # '-F' to force reset of the context including the user, role and range # which are not typically changed. # '-i' to ignore a file that does not yet exist on the system instead of # reporting an error. Allows "semanage fcontext / restorecon" # combinations to be issued for files prior to existing on the system. # sub restorecon { my $file = shift; if (is_selinux_enabled()) { # we suppress warnings from restorecon. bug #1008386: system("/sbin/restorecon -F -i 2>/dev/null " . $file); # Return a 1, restorecon was called. return 1; } # If it is not enabled, return a -1, restorecon was NOT called. return -1; } # Call restorecon on the supplied directory and its contents if SELinux is # enabled. Add the # options: # '-F' to force reset of the context including the user, role and range # which are not typically changed. # '-i' to ignore (report no errors) for files that do not yet exist. # '-R' to change file and directory labels recursively. # sub restoreconDir { my $dir = shift; if (is_selinux_enabled()) { # we suppress warnings from restorecon. bug #1008386: system("/sbin/restorecon -R -F -i 2>/dev/null " . $dir); # Return a 1, restorecon was called. return 1; } # If it is not enabled, return a -1, restorecon was NOT called. return -1; } # Append a clearly delimited block to an unstructured text file # Result: # 1 on success # -1 on failure sub block_append { my $file = shift; my $begin = shift; my $block = shift; my $end = shift; if (not open(BLOCK, '>>' . $file)) { return -1; } print BLOCK $begin . $block . $end; if (not close(BLOCK)) { # Even if close fails, make sure to call restorecon. restorecon($file); return -1; } # Call restorecon to set SELinux policy for this file. restorecon($file); return 1; } # Append a clearly delimited block to an unstructured text file # and add this file to an "answer" entry in the locations db # # Result: # 1 on success # -1 on failure sub block_append_with_db_answer_entry { my $file = shift; my $block = shift; return -1 if (block_append($file, $cMarkerBegin, $block, $cMarkerEnd) < 0); # get the list of already-appended files my $list = db_get_answer_if_exists($cDBAppendString); # No need to check if there's anything in the list because # db_add_answer removes the existing answer with the same name if ($list) { $list = join(':', $list, $file); } else { $list = $file; } db_add_answer($cDBAppendString, $list); return 1; } # Insert a clearly delimited block to an unstructured text file # # Uses a regexp to find a particular spot in the file and adds # the block at the first regexp match. # # Result: # 1 on success # 0 on no regexp match (nothing added) # -1 on failure sub block_insert { my $file = shift; my $regexp = shift; my $begin = shift; my $block = shift; my $end = shift; my $line_added = 0; my $tmp_dir = make_tmp_dir('vmware-block-insert'); my $tmp_file = $tmp_dir . '/tmp_file'; if (not open(BLOCK_IN, '<' . $file) or not open(BLOCK_OUT, '>' . $tmp_file)) { return -1; } foreach my $line (<BLOCK_IN>) { if ($line =~ /($regexp)/ and not $line_added) { print BLOCK_OUT $begin . $block . $end; $line_added = 1; } print BLOCK_OUT $line; } if (not close(BLOCK_IN) or not close(BLOCK_OUT)) { return -1; } if (not system(shell_string($gHelper{'mv'}) . " $tmp_file $file")) { return -1; } remove_tmp_dir($tmp_dir); # Call restorecon to set SELinux policy for this file. restorecon($file); # Our return status is 1 if successful, 0 if nothing was added. return $line_added } # Test if specified file contains line matching regular expression # Result: # undef on failure # first matching line on success sub block_match { my $file = shift; my $block = shift; my $line = undef; if (open(BLOCK, '<' . $file)) { while (defined($line = <BLOCK>)) { chomp $line; last if ($line =~ /$block/); } close(BLOCK); } return defined($line); } # Remove all clearly delimited blocks from an unstructured text file # Result: # >= 0 number of blocks removed on success # -1 on failure sub block_remove { my $src = shift; my $dst = shift; my $begin = shift; my $end = shift; my $count; my $state; if (not open(SRC, '<' . $src)) { return -1; } if (not open(DST, '>' . $dst)) { close(SRC); return -1; } $count = 0; $state = 'outside'; while (<SRC>) { if ($state eq 'outside') { if ($_ eq $begin) { $state = 'inside'; $count++; } else { print DST $_; } } elsif ($state eq 'inside') { if ($_ eq $end) { $state = 'outside'; } } } if (not close(DST)) { close(SRC); # Even if close fails, make sure to call restorecon on $dst. restorecon($dst); return -1; } # $dst file has been modified, call restorecon to set the # SELinux policy for it. restorecon($dst); if (not close(SRC)) { return -1; } return $count; } # Similar to block_remove(). Find the delimited text, bracketed by $begin and $end, # and filter it out as the file is written out to a tmp file. Typicaly, block_remove() # is used in the pattern: create tmp dir, create tmp file, block_remove(), mv file, # remove tmp dir. This encapsulates the pattern. sub block_restore { my $src_file = shift; my $begin_marker = shift; my $end_marker = shift; my $tmp_dir = make_tmp_dir('vmware-block-restore'); my $tmp_file = $tmp_dir . '/tmp_file'; my $rv; my @sb; @sb = stat($src_file); $rv = block_remove($src_file, $tmp_file, $begin_marker, $end_marker); if ($rv >= 0) { system(shell_string($gHelper{'mv'}) . ' ' . $tmp_file . ' ' . $src_file); safe_chmod($sb[2], $src_file); } remove_tmp_dir($tmp_dir); # Call restorecon on the source file. restorecon($src_file); return $rv; } # Remove leading and trailing whitespaces sub remove_whitespaces { my $string = shift; $string =~ s/^\s*//; $string =~ s/\s*$//; return $string; } # Ask a question to the user and propose an optional default value # Use this when you don't care about the validity of the answer sub query { my $message = shift; my $defaultreply = shift; my $reserved = shift; my $reply; my $default_value = $defaultreply eq '' ? '' : ' [' . $defaultreply . ']'; my $terse = 'no'; my $default_selected = ''; # Allow the script to limit output in terse mode. Usually dictated by # vix in a nested install and the '--default' option. if (db_get_answer_if_exists('TERSE')) { $terse = db_get_answer('TERSE'); if ($terse eq 'yes') { $reply = remove_whitespaces($defaultreply); return $reply; } } # Reserve some room for the reply print wrap($message . $default_value, 1 + $reserved); # This is what the 1 is for print ' '; if ($gOption{'default'} == 1) { $reply = ''; } else { $reply = <STDIN>; $reply = '' unless defined($reply); chomp($reply); } # Simulate the enter key on "default" mode as well as insure that # "INPUT:" is on seperate line in installation log. print "\n"; $reply = remove_whitespaces($reply); if ($reply eq '') { $reply = $defaultreply; $default_selected = ' default'; } if (defined($gOption{'log-answers'}) && $gOption{'log-answers'} == 1) { # Log the answer. print wrap("INPUT: [" . $reply . "]" . $default_selected . "\n"); } print "\n"; return $reply; } # Execute the command passed as an argument # _without_ interpolating variables (Perl does it by default) sub direct_command { return `$_[0]`; } # If there is a pid for this process, consider it running. sub check_is_running { my $proc_name = shift; my $rv = system(shell_string($gHelper{'pidof'}) . " " . $proc_name . " > /dev/null"); return $rv eq 0; } # OS-independent method of unloading a kernel module by name # Returns true (non-zero) if the operation succeeded, false otherwise. sub kmod_unload { my $modname = shift; # IN: Module name my $doRecursive = shift; # IN: Whether to also try loading modules that # become unused as a result of unloading $modname if (defined($gHelper{'modprobe'}) && defined($doRecursive) && $doRecursive) { # Linux (with $doRecursive) return !system(shell_string($gHelper{'modprobe'}) . ' -r ' . shell_string($modname) . ' >/dev/null 2>&1'); } elsif (defined($gHelper{'rmmod'})) { # Linux (otherwise) return !system(shell_string($gHelper{'rmmod'}) . ' ' . shell_string($modname) . ' >/dev/null 2>&1'); } elsif (defined($gHelper{'kldunload'})) { # FreeBSD return !system(shell_string($gHelper{'kldunload'}) . ' ' . shell_string($modname) . ' >/dev/null 2>&1'); } elsif (defined($gHelper{'modunload'})) { # Solaris # Solaris won't let us unload by module name, so we have to find the ID from modinfo my $aline; my @lines = split('\n', direct_command(shell_string($gHelper{'modinfo'}))); foreach $aline (@lines) { chomp($aline); my($amodid, $dummy2, $dummy3, $dummy4, $dummy5, $amodname) = split(/\s+/, $aline); if ($modname eq $amodname) { return !system(shell_string($gHelper{'modunload'}) . ' -i ' . $amodid . ' >/dev/null 2>&1'); } } return 0; # Failure - module not found } return 0; # Failure } # Emulate a simplified ls program for directories sub internal_ls { my $dir = shift; my @fn; opendir(LS, $dir) or return (); @fn = grep(!/^\.\.?$/, readdir(LS)); closedir(LS); return @fn; } # Emulate a simplified dirname program sub internal_dirname { my $path = shift; my $pos; $path = dir_remove_trailing_slashes($path); $pos = rindex($path, '/'); if ($pos == -1) { # No slash return '.'; } if ($pos == 0) { # The only slash is at the beginning return '/'; } return substr($path, 0, $pos); } # # unconfigure_autostart_legacy -- # # Remove VMware-added blocks relating to vmware-user autostart from # pre-XDG resource files, scripts, etc. # # Results: # OpenSuSE: Revert xinitrc.common. # Debian/Ubuntu: Remove script from Xsession.d. # xdm: Revert xdm-config(s). # gdm: None. (gdm mechanism used install_symlink, so that will be # cleaned up separately.) # # Side effects: # None. # sub unconfigure_autostart_legacy { my $markerBegin = shift; # IN: block begin marker my $markerEnd = shift; # IN: block end marker if (!defined($markerBegin) || !defined($markerEnd)) { return; } my $chompedMarkerBegin = $markerBegin; # block_match requires chomped markers chomp($chompedMarkerBegin); # # OpenSuSE (xinitrc.common) # my $xinitrcCommon = '/etc/X11/xinit/xinitrc.common'; if (-f $xinitrcCommon && block_match($xinitrcCommon, $chompedMarkerBegin)) { block_restore($xinitrcCommon, $markerBegin, $markerEnd); } # # Debian (Xsession.d) - We forgot to simply call db_add_file() after # creating this one. # my $dotdScript = '/etc/X11/Xsession.d/99-vmware_vmware-user'; if (-f $dotdScript && !db_file_in($dotdScript)) { unlink($dotdScript); } # # xdm # my @xdmcfgs = ("/etc/X11/xdm/xdm-config"); my $x11Base = db_get_answer_if_exists('X11DIR'); if (defined($x11Base)) { push(@xdmcfgs, "$x11Base/lib/X11/xdm/xdm-config"); } foreach (@xdmcfgs) { if (-f $_ && block_match($_, "!$chompedMarkerBegin")) { block_restore($_, "!$markerBegin", "!$markerEnd"); } } } # Check a mountpoint to see if it hosts the guest tools install iso. sub check_mountpoint_for_tools { my $mountpoint = shift; my $foundit = 0; if (vmware_product() eq 'tools-for-solaris') { if ($mountpoint =~ /vmwaretools$/ || $mountpoint =~ /\/media\/VMware Tools$/) { $foundit = 1; } } elsif (opendir CDROMDIR, $mountpoint) { my @dircontents = readdir CDROMDIR; foreach my $entry ( @dircontents ) { if (vmware_product() eq 'tools-for-linux') { if ($entry =~ /VMwareTools-.*\.tar\.gz$/) { $foundit = 1; } } elsif (vmware_product() eq 'tools-for-freebsd') { if ($entry =~ /vmware-freebsd-tools\.tar\.gz$/) { $foundit = 1; } } } closedir(CDROMDIR); } return $foundit; } # Try to eject the guest tools install cd so the user doesn't have to manually. sub eject_tools_install_cd_if_mounted { # TODO: Add comments to the other code which generates the filenames # and volumeids which this code is now dependent upon. my @candidate_mounts; my $device; my $mountpoint; my $fstype; my $rest; my $eject_cmd = ''; my $eject_failed = 0; my $eject_really_failed = 0; # For each architecture, first collect a list of mounted cdroms. if (vmware_product() eq 'tools-for-linux') { $eject_cmd = internal_which('eject'); if (open(MOUNTS, '</proc/mounts')) { while (<MOUNTS>) { ($device, $mountpoint, $fstype, $rest) = split; # note: /proc/mounts replaces spaces with \040 $device =~ s/\\040/\ /g; $mountpoint =~ s/\\040/\ /g; if ($fstype eq "iso9660" && $device !~ /loop/ ) { push(@candidate_mounts, "${device}::::${mountpoint}"); } } close(MOUNTS); } } elsif (vmware_product() eq 'tools-for-freebsd' and -x internal_which('mount')) { $eject_cmd = internal_which('cdcontrol') . " eject"; my @mountlines = split('\n', direct_command(internal_which('mount'))); foreach my $mountline (@mountlines) { chomp($mountline); if ($mountline =~ /^(.+)\ on\ (.+)\ \(([0-9a-zA-Z]+),/) { $device = $1; $mountpoint = $2; $fstype = $3; # If the device begins with /dev/md it will most likely # be the equivalent of a loopback mount in linux. if ($fstype eq "cd9660" && $device !~ /^\/dev\/md/) { push(@candidate_mounts, "${device}::::${mountpoint}"); } } } } elsif (vmware_product() eq 'tools-for-solaris') { $eject_cmd = internal_which('eject'); # If this fails, don't bother trying to unmount, or error. if (open(MNTTAB, '</etc/mnttab')) { while (<MNTTAB>) { ($device, $rest) = split("\t", $_); # I don't think there are actually ever comments in /etc/mnttab. next if $device =~ /^#/; if ($device =~ /vmwaretools$/ || $rest =~ /\/media\/VMware Tools$/) { $mountpoint = $rest; $mountpoint =~ s/(.*)\s+hsfs.*/$1/; push(@candidate_mounts, "${device}::::${mountpoint}"); } } close(MNTTAB); } } # For each mounted cdrom, check if it's vmware guest tools installer, # and if so, try to eject it, then verify. foreach my $candidate_mount (@candidate_mounts) { ($device, $mountpoint) = split('::::',$candidate_mount); if (check_mountpoint_for_tools($mountpoint)) { print wrap("Found VMware Tools CDROM mounted at " . "${mountpoint}. Ejecting device $device ...\n"); # Freebsd doesn't auto unmount along with eject. if (vmware_product() eq 'tools-for-freebsd' and -x internal_which('umount')) { # If this fails, the eject will fail, and the user will see # the appropriate output. direct_command(internal_which('umount') . ' "' . $device . '"'); } my @output = (); if ($eject_cmd ne '') { open(CMDOUTPUT, "$eject_cmd $device 2>&1 |"); @output = <CMDOUTPUT>; close(CMDOUTPUT); $eject_failed = $?; } else { $eject_failed = 1; } # For unknown reasons, eject can succeed, but return error, so # double check that it really failed before showing the output to # the user. For more details see bug170327. if ($eject_failed && check_mountpoint_for_tools($mountpoint)) { foreach my $outputline (@output) { print wrap ($outputline, 0); } # $eject_really_failed ensures this message is not printed # multiple times. if (not $eject_really_failed) { if ($eject_cmd eq '') { print wrap ("No eject (or equivilant) command could be " . "located.\n"); } print wrap ("Eject Failed: If possible manually eject the " . "Tools installer from the guest cdrom mounted " . "at $mountpoint before canceling tools install " . "on the host.\n", 0); $eject_really_failed = 1; } } } } } # Compares variable length version strings against one another. # Returns 1 if the first version is greater, -1 if the second # version is greater, or 0 if they are equal. sub dot_version_compare { my $str1 = shift; my $str2 = shift; if ("$str1" eq '' or "$str2" eq '') { if ("$str1" eq '' and "$str2" eq '') { return 0; } else { return (("$str1" eq '') ? -1 : 1); } } if ("$str1" =~ /[^0-9\.]+/ or "$str2" =~ /[^0-9\.]+/) { error("Bad character detected in dot_version_compare.\n"); } my @arr1 = split(/\./, "$str1"); my @arr2 = split(/\./, "$str2"); my $indx = 0; while(1) { if (!defined $arr1[$indx] and !defined $arr2[$indx]) { return 0; } $arr1[$indx] = 0 if not defined $arr1[$indx]; $arr2[$indx] = 0 if not defined $arr2[$indx]; if ($arr1[$indx] != $arr2[$indx]) { return (($arr1[$indx] > $arr2[$indx]) ? 1 : -1); } $indx++; } error("NOT REACHED IN DOT_VERSION_COMPARE\n"); } # Returns the tuple ($halScript, $halName) if the system # has scripts to control HAL. # sub get_hal_script_name { my $initDir = shell_string(db_get_answer('INITSCRIPTSDIR')); $initDir =~ s/\'//g; # Remove quotes my @halguesses = ("haldaemon", "hal"); my $halScript = undef; my $halName = undef; # Attempt to find the init script for the HAL service. # It should be one of the names in our list of guesses. foreach my $hname (@halguesses) { if (-f "$initDir/$hname") { $halScript = "$initDir/$hname"; $halName = "$hname"; } } if (vmware_product() eq 'tools-for-solaris') { # In Solaris 11, use svcadm to handle HAL. # XXX: clean this up on main. my $svcadmBin = internal_which('svcadm'); if (system("$svcadmBin refresh hal >/dev/null 2>&1") eq 0) { $halScript = 'svcadm'; $halName = 'hal'; } } return ($halScript, $halName); } sub restart_hal { my $servicePath = internal_which("service"); my $halScript = undef; my $halName = undef; ($halScript, $halName) = get_hal_script_name(); # Hald does time stamp based cache obsolescence check, and it won't # reload new fdi if it has cache file with future timestamp. # Let's cleanup the cache file before restarting hald to get around # this problem. unlink('/var/cache/hald/fdi-cache'); if ($halScript eq 'svcadm') { # Solaris svcadm. my $svcadmBin = internal_which('svcadm'); system("$svcadmBin restart hal"); } elsif (-d '/etc/init' and $servicePath ne '' and defined($halName)) { # Upstart case. system("$servicePath $halName restart"); } elsif (defined($halScript)) { # Traditional init script restart case. system($halScript . ' restart'); } else { print "Could not locate hal daemon init script.\n"; } } ## # locate_upstart_jobinfo # # Determine whether Upstart is supported, and if so, return the path in which # Upstart jobs should be installed and any job file suffix. # # @retval ($path, $suffix) Path containing Upstart jobs, job suffix (ex: .conf). # @retval () Upstart unsupported or unable to determine job path. # sub locate_upstart_jobinfo() { my $initctl = internal_which('initctl'); my $retval; # bug #1423141 delete $ENV{'UPSTART_SESSION'}; if ($have_thinprint eq 'yes') { # we cannot use upstart unless cups also uses upstart, otherwise we # cannot make sure that tp starts after cups. if ( glob(db_get_answer('INITDIR') . '/rc2.d/' . 'S??cups*' ) and (not -e '/etc/init/cups.conf') ) { return (); } } # Don't bother checking directories unless initctl is available and # indicates that Upstart is active. if ($initctl ne '' and ( -x $initctl )) { my $initctl_version_string = direct_command(shell_string($initctl) . " version 2> /dev/null"); if (($initctl_version_string =~ /upstart ([\d\.]+)/) and # XXX Fix dot_version_compare to support a comparison like 0.6.5 to 0.6. (dot_version_compare($1, "0.6.0") >= 0)) { my $jobPath = "/etc/init"; if ( -d $jobPath ) { my $suffix = ""; foreach my $testSuffix (".conf") { if (glob ("$jobPath/*$testSuffix")) { $suffix = $testSuffix; last; } } return ($jobPath, $suffix); } } } return (); } ## # vmware_service_basename # # Simple product name -> service script map accessor. (See # $cProductServiceTable.) # # @return Service script basename on valid product, undef otherwise. # sub vmware_service_basename { return $cProductServiceTable{vmware_product()}; } ## # vmware_service_path # # @return Valid service script's path relative to INITSCRIPTSDIR unless # vmware_product() has no such script. # sub vmware_service_path { my $basename = vmware_service_basename(); return $basename ? join('/', db_get_answer('INITSCRIPTSDIR'), $basename) : undef; } ## # escaped_cmd # # Escape parameters, then join by a single space. # # @param[in] command and args # # @return escaped command # sub escaped_cmd { my @args = @_; my @escaped_args; foreach (@args) { push(@escaped_args, shell_string($_)); } return join(' ', @escaped_args); } ## # vmware_service_issue_command # # Executes a VMware services script, determined by locations database contents # and product type, with a single command parameter. # # @param[in] $useSystem If true, uses system(). Else uses direct_command(). # @param[in] $service the name of the service # @param[in] @commands List of commands passed to services script or initctl # (ex: start, stop, status vm). # # @returns Return value from system() or direct_command(). # sub vmware_service_issue_command { my $useSystem = shift; my $service = shift; # $what is 'start', 'stop' or 'status' my $what = shift; my @argv; my @escapedArgv; my $use_systemd = 0; # Upstart/initctl case. if (db_get_answer_if_exists('UPSTARTJOB')) { my $initctl = internal_which('initctl'); error("ASSERT: Failed to determine my service name.\n") unless defined $service; @argv = ($initctl, $what, $service); } elsif (my $systemctl = internal_which('systemctl')) { $use_systemd = 1; @argv = ($systemctl, $what, $service); # Legacy SYSV style. } else { @argv = (join('/', db_get_answer('INITSCRIPTSDIR'), $service), $what); } # bug #1423141 delete $ENV{'UPSTART_SESSION'}; my $cmd = escaped_cmd(@argv); my $result = $useSystem ? system($cmd) : direct_command($cmd); if ($what eq 'stop') { # stopping using systemctl may not always work # if it was started using init scripts. We try systemctl # first, then the init script. See bug #1821433 if ($use_systemd) { @argv = (join('/', db_get_answer('INITSCRIPTSDIR'), $service), $what); $cmd = escaped_cmd(@argv); $result = $useSystem ? system($cmd) : direct_command($cmd); } } return $result; } sub vmware_services_table() { my $product = vmware_product(); if ($product eq 'tools-for-linux') { return \%cToolsLinuxServices; } elsif ($product eq 'tools-for-freebsd') { return \%cToolsFreeBSDServices; } elsif ($product eq 'tools-for-solaris') { return \%cToolsSolarisServices; } error("$product not implemented in vmware_services_table()\n."); } ## # removeDuplicateEntries # # Removes duplicate entries from a given string and delimeter # @param - string to cleanse # @param - the delimeter # @returns - String without duplicate entries. # sub removeDuplicateEntries { my $string = shift; my $delim = shift; my $newStr = ''; if (not defined $string or not defined $delim) { error("Missing parameters in removeDuplicateEntries\n."); } foreach my $subStr (split($delim, $string)) { if ($newStr !~ /(^|$delim)$subStr($delim|$)/ and $subStr ne '') { if ($newStr ne '') { $newStr = join($delim, $newStr, $subStr); } else { $newStr = $subStr; } } } return $newStr; } ## # internalMv # # mv command for Perl that works across file system boundaries. The rename # function may not work across FS boundaries and I don't want to introduce # a dependency on File::Copy (at least not with this installer/configurator). # sub internalMv { my $src = shift; my $dst = shift; return system("mv $src $dst"); } ## # addTextToKVEntryInFile # # Despite the long and confusing function name, this function is very # useful. If you have a key value entry in a file, this function will # allow you to add an entry to it based on a special regular expression. # This regular expression must capture the pre-text, the values, and any # post text by using regex back references. # @param - Path to file # @param - The regular expression. See example below... # @param - The delimeter between values # @param - The new entry # @returns - 1 if the file was modified, 0 otherwise. # # For example, if I have # foo = 'bar,baz'; # I can add 'biz' to the values by calling this function with the proper # regex. A regex for this would look like '^(foo = ')(\.*)(;)$'. The # delimeter is ',' and the entry would be 'biz'. The result should look # like # foo = 'bar,baz,biz'; # # NOTE1: This function will only add to the first KV pair found. # sub addTextToKVEntryInFile { my $file = shift; my $regex = shift; my $delim = shift; my $entry = shift; my $modified = 0; my $firstPart; my $origValues; my $newValues; my $lastPart; $regex = qr/$regex/; if (not open(INFILE, "<$file")) { error("addTextToKVEntryInFile: File $file not found\n"); } my $tmpDir = make_tmp_dir('vmware-file-mod'); my $tmpFile = join('/', $tmpDir, 'new-file'); if (not open(OUTFILE, ">$tmpFile")) { error("addTextToKVEntryInFile: Failed to open output file\n"); } foreach my $line (<INFILE>) { if ($line =~ $regex and not $modified) { # We have a match. $1 and $2 have to be deifined; $3 is optional if (not defined $1 or not defined $2) { error("addTextToKVEntryInFile: Bad regex.\n"); } $firstPart = $1; $origValues = $2; $lastPart = ((defined $3) ? $3 : ''); chomp $firstPart; chomp $origValues; chomp $lastPart; # Modify the origValues and remove duplicates # Handle white space as well. if ($origValues =~ /^\s*$/) { $newValues = $entry; } else { $newValues = join($delim, $origValues, $entry); $newValues = removeDuplicateEntries($newValues, $delim); } print OUTFILE join('', $firstPart, $newValues, $lastPart, "\n"); $modified = 1; } else { print OUTFILE $line; } } close(INFILE); close(OUTFILE); return 0 unless (internalMv($tmpFile, $file) eq 0); remove_tmp_dir($tmpDir); # Our return status is 1 if successful, 0 if nothing was added. return $modified; } # work around "panic: end_shift" (bug #1027773) for old ( <= 5.008) perl versions sub safely_matches { my $line = shift; my $regex = shift; my $b; my @result; if ($] <= 5.008) { use bytes; $b = ($line =~ $regex); return ($b, $1, $2, $3); } else { $b = ($line =~ $regex); return ($b, $1, $2, $3); } } ## # removeTextInKVEntryInFile # # Does exactly the opposite of addTextToKVEntryFile. It will remove # all instances of the text entry in the first KV pair that it finds. # @param - Path to file # @param - The regular expression. See example above... # @param - The delimeter between values # @param - The entry to remove # @returns - 1 if the file was modified, 0 otherwise. # # NOTE1: This function will only remove from the first KV pair found. # sub removeTextInKVEntryInFile { my $file = shift; my $regex = shift; my $delim = shift; my $entry = shift; my $modified = 0; my $firstPart; my $origValues; my $newValues = ''; my $lastPart; $regex = qr/$regex/; if (not open(INFILE, "<$file")) { error("removeTextInKVEntryInFile: File $file not found\n"); } my $tmpDir = make_tmp_dir('vmware-file-mod'); my $tmpFile = join('/', $tmpDir, 'new-file'); if (not open(OUTFILE, ">$tmpFile")) { error("removeTextInKVEntryInFile: Failed to open output file $tmpFile\n"); } foreach my $line (<INFILE>) { my @res; @res = safely_matches($line, $regex); if ($res[0] and not $modified) { # We have a match. $res[1] and $res[2] have to be defined; $res[3] is optional if (not defined $res[1] or not defined $res[2]) { error("removeTextInKVEntryInFile: Bad regex.\n"); } $firstPart = $res[1]; $origValues = $res[2]; $lastPart = ((defined $res[3]) ? $res[3] : ''); chomp $firstPart; chomp $origValues; chomp $lastPart; # Modify the origValues and remove duplicates # If $origValues is just whitespace, no need to modify $newValues. if ($origValues !~ /^\s*$/) { foreach my $existingEntry (split($delim, $origValues)) { if ($existingEntry ne $entry) { if ($newValues eq '') { $newValues = $existingEntry; # avoid adding unnecessary whitespace } else { $newValues = join($delim, $newValues, $existingEntry); } } } } print OUTFILE join('', $firstPart, $newValues, $lastPart, "\n"); $modified = 1; } else { print OUTFILE $line; } } close(INFILE); close(OUTFILE); return 0 unless (internalMv($tmpFile, $file)); remove_tmp_dir($tmpDir); # Our return status is 1 if successful, 0 if nothing was added. return $modified; } # Parse and return key/value pairs in /etc/os-release, # which is only available in recent Linux distributions. # http://www.freedesktop.org/software/systemd/man/os-release.html sub identify_linux_variant { my %propRef; if (open(FH, '</etc/os-release')) { while (<FH>) { chomp; my @parts = split(/\s*=\s*/, $_, 2); if (@parts) { $parts[1] =~ s/^"?(.*?)"?$/$1/; $propRef{$parts[0]} = $parts[1]; } } } close(FH); return %propRef; } # Build a Linux kernel integer version sub kernel_version_integer { my $version = shift; my $patchLevel = shift; my $subLevel = shift; return $version * 65536 + $patchLevel * 256 + $subLevel; } # # getKernRel # # Returns the release of the kernel in question. Defaults to the # running kernel unless the user has set the --kernel-version option. # sub getKernRel { if (defined($gOption{'kernel_version'}) and $gOption{'kernel_version'} ne '') { return $gOption{'kernel_version'}; } else { if (not defined($gSystem{'uts_release'})) { $gSystem{'uts_release'} = direct_command(shell_string(internal_which('uname')) . ' -r'); } return $gSystem{'uts_release'}; } } # # returns the release of the kernel in question like getKernRel() # but as an integer (useful for comparisons) # sub getKernRelInteger { my ($version, $patchLevel, $subLevel) = split(/\./, getKernRel()); ($subLevel) = split(/[^0-9]/, $subLevel); return kernel_version_integer($version, $patchLevel, $subLevel); } # Determine glibc $major.$minor.$sub version sub get_glibc_version { my $ldd_out = direct_command(shell_string($gHelper{'ldd'}) . ' --version'); chomp($ldd_out); my ($major, $minor, $sub) = (0,0,0); # example $ldd_out: # ubuntu: # ldd (Ubuntu EGLIBC 2.12.1-0ubuntu10.2) 2.12.1 # other linux distributions: # ldd (GNU libc) 2.12 # # Parse through this to retrieve the version information. if ($ldd_out =~ /^ldd \(.*\) (\d+)\.(\d+)(\.(\d+))?/) { $major = $1; $minor = $2; $sub = $4 if $4; } return ($major, $minor, $sub); } # # Execute a "semanage fcontext" operation on the specified file followed by # a "restorecon -F" to make the context change effective immediately. # @param [in] - operation - "add" or "del" # @param [in] - file name to be managed # @param [in] - SELinux policy file type - mandatory for "add" operation # @param [in] - SELinux user context # @param [in] - SELinux range context # @returns - a zero (0) if there are no errors; return one (1) otherwise. # sub semanageFcontext { my $action = shift; my $path = shift; my $type = ""; my $user = ""; my $range = ""; my @cmd_sgmt = ("semanage fcontext"); my $cmd; my $error = 0; if (@_ >= 1) { $type = "-t " . shift; } if (@_ >= 1) { $user = "-s " . shift; } if (@_ >= 1) { $range = "-r " . shift; } # Test the action for "add" or "del"; may also need to handle a modify # action in the future. if ($action =~ "del") { push @cmd_sgmt, ("-d", $path); } else { if ($action =~ "add") { push @cmd_sgmt, ("-a"); } else { print wrap('Undefined "action" for "semanage fcontext" command: ' . $action . "\n"); return 1; } push @cmd_sgmt, ($type, $user, $range, $path); } $cmd = join(' ', @cmd_sgmt); if (system($cmd)) { print wrap("semanageFcontext: unable to set SELinux fcontext - " . 'command: "' . $cmd . '"' . "\n"); $error = 1; } else { # Direct restorecon command execution; subroutine (above) does not # report a failure. if (system('restorecon -F -i ' . $path)){ print wrap("semanageFcontext: unable to restore the SELinux " . "security context for " . $path . ".\n"); $error = 1; } } return $error; } # Generalized subroutine to manage configuration needed on a SELinux # enabled system. It will be called during installation and configuration, # re-configuration or deinstallation of VMware Tools. # # @param [in] - either "install" or "uninstall" # sub manageSELinux { my $action = shift; my $is64BitUserland = is64BitUserLand(); my $libdir = db_get_answer('LIBDIR'); my $libsbindir = $libdir . ($is64BitUserland ? '/sbin64' : '/sbin32'); my $libbindir = $libdir . ($is64BitUserland ? '/bin64' : '/bin32'); my $error = 0; my $srvTable = vmware_services_table(); my $service; my $initScript; my $dbKey; my $dir; my $subrTag = 'manageSELinux'; my $fcontextCmd; my $isInstall = 0; # see bug #1407966: if (is_selinux_enabled ()) { # Validate the action being requested and configure command options # and message phrases accordingly. if ($action eq "install") { $fcontextCmd = "add"; $isInstall = 1; } elsif ($action eq "uninstall") { $fcontextCmd = "del"; } else { print wrap($subrTag . ': action "' . $action . '" is invalid.' . "\n"); $error = 1; goto DONE; } $subrTag = $subrTag . ' ' . $action . ': '; if (! internal_which('semodule')) { if (! $isInstall) { # Exit quietly on uninstall. goto DONE; } print wrap($subrTag . "The 'semodule' utility was not found.\n"); $error = 1; goto DONE; } # We only can do something if the vmtools context exists. If the # configure script is run more than once, the "semodule -l" command # will report the permissive type "vmtools_t as well as the "vmtools" # module. Using 'grep -v permissive' to avoid any confusion. if (direct_command('semodule -l | cut -f1 | grep vmtools | ' . 'grep -v permissive') ne "vmtools\n") { goto DONE; } if (! internal_which('semanage')) { print wrap($subrTag . "The 'semanage' utility was not found.\n"); $error = 1; goto DONE; } if ($isInstall) { # # The tools files have been installed. Now reset all file labels # for the directories where binaries and libraries have been placed. # foreach $dbKey (@cSELinuxDirKeys) { $dir = db_get_answer_if_exists($dbKey); if (defined ($dir)) { restoreconDir($dir); } } # Configure vmtools_t context to permissive if not already there. if (system('semanage permissive -l | grep -q vmtools_t')) { if (system('semanage permissive -a vmtools_t') ) { print wrap($subrTag . 'Unable to set vmtools_t to ' . 'permissive.' . "\n"); $error = 1; } else { db_add_answer('VMT_PERMISSIVE_ADDED', "yes"); } } } else { # This is an "uninstall" action. # Remove vmtools_t"permissive" context if we added it during # configuration. if (defined(db_get_answer_if_exists('VMT_PERMISSIVE_ADDED'))) { if (system('semanage permissive -d vmtools_t > /dev/null 2>&1') ) { print wrap($subrTag . "Unable to remove vmtools_t " . "permissive mapping.\n"); $error = 1; } } } # For those VMware Tools files that require special context on # a SELinux system, set those file contexts now. # $error ||= semanageFcontext($fcontextCmd, db_get_answer('SBINDIR') . '/vmtoolsd', 'vmtools_exec_t', 'system_u'); $error ||= semanageFcontext($fcontextCmd, $libbindir . '/vmware-user-suid-wrapper', 'vmtools_helper_exec_t', 'system_u'); # # Now set the SELinux properties for any and all INITSCRPTSDIR # initialization scripts being installed and started. # foreach $service (keys %{$srvTable}){ $initScript = db_get_answer('INITSCRIPTSDIR') . '/' . $srvTable->{$service}; $error ||= semanageFcontext($fcontextCmd, $initScript, 'initrc_exec_t', 'system_u'); } } DONE: { if($error) { print wrap("There was an error configuring the SELinux security " . "context for " . vmware_product_name() . ". Please " . "make certain that SELinux is configured correctly.\n\n"); } } } # END_OF_UTIL_DOT_PL # Constants my $cKernelModuleDir = '/lib/modules'; my $cTmpDirPrefix = 'vmware-config'; my $cConnectSocketDir = '/var/run/vmware'; my $cVixProductName = ' VMware VIX API'; my $machine = 'host'; my $os = 'host'; if (vmware_product() eq 'server') { $machine = 'machine'; $os = "Console OS"; } my $cServices = '/etc/services'; my $cConfiguratorFileName = 'vmware-config.pl'; if (vmware_product() eq 'tools-for-linux' || vmware_product() eq 'tools-for-freebsd' || vmware_product() eq 'tools-for-solaris') { $cConfiguratorFileName = 'vmware-config-tools.pl'; } my $cModulesBuildEnv; if (vmware_product() eq 'tools-for-solaris') { $cModulesBuildEnv = ' please upgrade to a newer Solaris release.'; } else { $cModulesBuildEnv = ' you can install the driver by running ' . $cConfiguratorFileName . ' again after making sure that gcc, binutils, make ' . 'and the kernel sources for your running kernel are ' . 'installed on your machine. These packages are ' . 'available on your distribution\'s installation CD.'; } # kernels to avoid when rmmod'ing pcnet32 my %cPCnet32KernelBlacklist = ( '2.4.2' => 'yes', '2.4.9' => 'yes', '2.6.0' => '-test', '2.6.0' => '-test5_2', '2.6.16' => '.13-4-default', '2.6.8' => '-1', ); my $cDirExists = '1'; my $cCreateDirSuccess = '0'; my $cCreateDirFailure = '-1'; my @cGOSResolutionOptions = ("640x480", "800x600", "1024x768", "1280x800"); # # Global variables # my $gRegistryDir; my $gStateDir; my $gInstallerMainDB; my $gConfFlag; my $gLogDir; my $gGccPath; my $gKernelHeaders; my @gManifestNames; my @gManifestVersions; my @gManifestInstFlags; my @gRamdiskModules; # List of all ethernet adapters on the system my @gAllEthIf; # List of ethernet adapters that have not been bridged my @gAvailEthIf; # By convention, vmnet1 is the virtual ethernet interface connected to the # private virtual network that Samba uses. We are also reserving vmnet0 # for bridged networks. These are reserved vmnets. my $gDefBridged = '0'; my $gDefHostOnly = '1'; my $gDefNat = '8'; # Reserved vmnets my @gReservedVmnet = ($gDefBridged, $gDefHostOnly, $gDefNat); # Constant defined as the smallest vmnet that is allowed my $gMinVmnet = '0'; # Constant defined as the largest vmnet that is allowed # Although 256 are supported, #255 is reserved # Note: Max length of interface name is 8 my $gMaxVmnet = '254'; my $gFirstModuleBuild = 1; my $gCanCompileModules = 0; my $gDefaultAuthdPort = 902; my @gDefaultHttpProxy = (8222, 80); my @gDefaultHttpSProxy = (8333, 443); # BEGINNING OF THE SECOND LIBRARY FUNCTIONS # Global variables my %gDBAnswer; my %gDBFile; my %gDBDir; my %gDBUserFile; my $cBackupExtension = '.BeforeVMwareToolsInstall'; my $cRestorePrefix = 'RESTORE_'; my $cRestoreBackupSuffix = '_BAK'; my $cRestoreBackList = 'RESTORE_BACK_LIST'; my $cSwitchedToHost = 'SWITCHED_TO_HOST'; my $cXModulesDir = '/usr/X11R6/lib/modules'; my $cX64ModulesDir = '/usr/X11R6/lib64/modules'; my $gXMouseDriverFile = ''; my $gXVideoDriverFile = ''; my $gXVideoDriverLegacyFile = ''; my $gIs64BitX = 0; my $gSavedPath = $ENV{'PATH'}; my $gNoXDrivers = 0; my @gSuspectedFontLocations = ('/usr/share/fonts', '/usr/lib/X11/fonts', '/usr/lib64/X11/fonts'); my $useApploader = (vmware_product() eq 'tools-for-linux'); my %gInstallStatus; my $open_vm_compat = 0; # The validity of the following open-vm-tools settings are dependent on # $open_vm_compat != 0. my $gOpenVmCompatOverWrite = 0; my $gOpenVmCompatHGFSDefault = 'no'; my $reboot_recommended = 0; # Load the installer database sub db_load { undef %gDBAnswer; undef %gDBFile; undef %gDBDir; if (not open(INSTALLDB, '<' . $gInstallerMainDB)) { error('Unable to open the installer database ' . $gInstallerMainDB . ' in read-mode.' . "\n\n"); } while (<INSTALLDB>) { chomp; if (/^answer (\S+) (.+)$/) { $gDBAnswer{$1} = $2; } elsif (/^answer (\S+)/) { $gDBAnswer{$1} = ''; } elsif (/^remove_answer (\S+)/) { delete $gDBAnswer{$1}; } elsif (/^file (.+) (\d+)$/) { $gDBFile{$1} = $2; } elsif (/^file (.+)$/) { $gDBFile{$1} = 0; } elsif (/^modified (.+)$/) { if (defined $gDBFile{$1}) { $gDBUserFile{$1} = 0; } } elsif (/^remove_file (.+)$/) { delete $gDBFile{$1}; delete $gDBUserFile{$1}; # harmless if not in there } elsif (/^directory (.+)$/) { $gDBDir{$1} = ''; } elsif (/^remove_directory (.+)$/) { delete $gDBDir{$1}; } } close(INSTALLDB); } # Open the database on disk in append mode sub db_append { if (not open(INSTALLDB, '>>' . $gInstallerMainDB)) { error('Unable to open the installer database ' . $gInstallerMainDB . ' in append-mode.' . "\n\n"); } # Force a flush after every write operation. # See 'Programming Perl' 3rd edition, p. 781 (p. 110 in an older edition) select((select(INSTALLDB), $| = 1)[0]); } # Add a file to the tar installer database # flags: # 0x1 - write time stamp ($cFlagTimestamp) # 0x2 - is config file ($cFlagConfig) # 0x8 - is user-modified file ($cFlagUserModified) sub db_add_file { my $file = shift; my $flags = shift; if ($flags & $cFlagTimestamp) { my @statbuf; @statbuf = stat($file); if (not (defined($statbuf[9]))) { error('Unable to get the last modification timestamp of the destination ' . 'file ' . $file . '.' . "\n\n"); } $gDBFile{$file} = $statbuf[9]; print INSTALLDB 'file ' . $file . ' ' . $statbuf[9] . "\n"; } else { $gDBFile{$file} = 0; print INSTALLDB 'file ' . $file . "\n"; } if ($flags & $cFlagUserModified) { print INSTALLDB 'modified ' . $file . "\n"; $gDBUserFile{$file} = 0; } if ($flags & $cFlagConfig) { print INSTALLDB 'config ' . $file . "\n"; } } # Mark a file as modified without changing it. sub db_set_userfile { my $file = shift; if (!db_userfile_in($file)) { print INSTALLDB 'modified ' . $file . "\n"; $gDBUserFile{$file} = 0; } } # Remove a file from the tar installer database sub db_remove_file { my $file = shift; print INSTALLDB 'remove_file ' . $file . "\n"; delete $gDBFile{$file}; delete $gDBUserFile{$file}; } # Remove a directory from the tar installer database sub db_remove_dir { my $dir = shift; print INSTALLDB 'remove_directory ' . $dir . "\n"; delete $gDBDir{$dir}; } # Determine if a file belongs to the tar installer database sub db_file_in { my $file = shift; return defined($gDBFile{$file}); } # Determine if a directory belongs to the tar installer database sub db_dir_in { my $dir = shift; return defined($gDBDir{$dir}); } # Determine if a directory belongs to the tar installer database sub db_userfile_in { my $file = shift; return defined($gDBUserFile{$file}); } # Return the timestamp of an installed file sub db_file_ts { my $file = shift; return $gDBFile{$file}; } # Add a directory to the tar installer database sub db_add_dir { my $dir = shift; $gDBDir{$dir} = ''; print INSTALLDB 'directory ' . $dir . "\n"; } # Remove an answer from the tar installer database sub db_remove_answer { my $id = shift; if (defined($gDBAnswer{$id})) { print INSTALLDB 'remove_answer ' . $id . "\n"; delete $gDBAnswer{$id}; } } # Add an answer to the tar installer database sub db_add_answer { my $id = shift; my $value = shift; db_remove_answer($id); $gDBAnswer{$id} = $value; print INSTALLDB 'answer ' . $id . ' ' . $value . "\n"; } # Retrieve an answer that must be present in the database sub db_get_answer { my $id = shift; if (not defined($gDBAnswer{$id})) { error('Unable to find the answer ' . $id . ' in the installer database (' . $gInstallerMainDB . '). You may want to re-install ' . vmware_product_name() . ".\n\n"); } return $gDBAnswer{$id}; } # Retrieves an answer if it exists in the database, else returns undef; sub db_get_answer_if_exists { my $id = shift; if (not defined($gDBAnswer{$id})) { return undef; } if ($gDBAnswer{$id} eq '') { return undef; } return $gDBAnswer{$id}; } # Save the tar installer database sub db_save { close(INSTALLDB); } # END OF THE SECOND LIBRARY FUNCTIONS # BEGINNING OF THE LIBRARY FUNCTIONS # Global variables my %gAnswerSize; my %gCheckAnswerFct; # Contrary to a popular belief, 'which' is not always a shell builtin command. # So we cannot trust it to determine the location of other binaries. # Moreover, SuSE 6.1's 'which' is unable to handle program names beginning with # a '/'... # # Return value is the complete path if found, or '' if not found sub internal_which { my $bin = shift; my $useSavedPath = shift; # (optional, bool) Define this if you'd like to # look around using the user's original PATH. my $appendPaths = shift; # (optional, array ref) Define this if you'd like # to append custom directories to $gSavedPath or # $ENV{'PATH'}. if (substr($bin, 0, 1) eq '/') { # Absolute name if ((-f $bin) && (-x $bin)) { return $bin; } } else { # Relative name my @paths; my $path; if (index($bin, '/') == -1) { # There is no other '/' in the name @paths = split(':', $useSavedPath ? $gSavedPath : $ENV{'PATH'}); @paths = (@paths, @{$appendPaths}) if defined $appendPaths; foreach $path (@paths) { my $fullbin; $fullbin = $path . '/' . $bin; if ((-f $fullbin) && (-x $fullbin)) { return $fullbin; } } } } return ''; } # Check the validity of an answer whose type is yesno # Return a clean answer if valid, or '' sub check_answer_binpath { my $answer = shift; my $source = shift; my $fullpath = internal_which($answer); if (not ("$fullpath" eq '')) { return $fullpath; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. It must be the ' . 'complete name of a binary file.' . "\n\n", 0); } return ''; } $gAnswerSize{'binpath'} = 20; $gCheckAnswerFct{'binpath'} = \&check_answer_binpath; # Prompts the user if a binary is not found # Return value is: # '': the binary has not been found # the binary name if it has been found sub DoesBinaryExist_Prompt { my $bin = shift; my $answer; my $prefix = 'BIN_'; $answer = check_answer_binpath($bin, 'default'); if ($answer ne '') { return $answer; } else { if (defined db_get_answer_if_exists($prefix . $bin)) { return db_get_answer($prefix . $bin); } } 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 ''; } $answer = get_answer('What is the location of the "' . $bin . '" program on ' . 'your machine?', 'binpath', ''); if ($answer ne '' && not defined db_get_answer_if_exists($prefix . $bin)) { db_add_answer($prefix . $bin, $answer); } return $answer; } # 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 source file "' . $src . '".' . "\n\n"); } safe_chmod($statbuf[2] & 07777, $dst); } # Emulate a simplified sed program # Return 1 if success, 0 if failure # XXX as a side effect, if the string being replaced is '', remove # the entire line. Remove this, once we have better "block handling" of # our config data in config files. sub internal_sed { my $src = shift; my $dst = shift; my $append = shift; my $patchRef = shift; my @patchKeys; if (not open(SRC, '<' . $src)) { return 0; } if (not open(DST, (($append == 1) ? '>>' : '>') . $dst)) { return 0; } @patchKeys = keys(%$patchRef); if ($#patchKeys == -1) { while (defined($_ = <SRC>)) { print DST $_; } } else { while (defined($_ = <SRC>)) { my $patchKey; my $del = 0; foreach $patchKey (@patchKeys) { if (s/$patchKey/$$patchRef{$patchKey}/g) { if ($_ eq "\n") { $del = 1; } } } if ($del) { next; } print DST $_; } } close(SRC); close(DST); return 1; } # Check if a file name exists sub file_name_exist { my $file = shift; # Note: We must test for -l before, because if an existing symlink points to # a non-existing file, -e will be false return ((-l $file) || (-e $file)) } # Check if a file name already exists and prompt the user # Return 0 if the file can be written safely, 1 otherwise sub file_check_exist { my $file = shift; my $default_overwrite = 'yes'; if (@_ >= 1) { $default_overwrite = shift; } if (not file_name_exist($file)) { return 0; } # The default must make sure that the product will be correctly installed # We give the user the choice so that a sysadmin can perform a normal # install on a NFS server and then answer 'no' NFS clients return (get_answer('The file ' . $file . ' that this program was about to ' . 'install already exists. Overwrite?', 'yesno', $default_overwrite) eq 'yes') ? 0 : 1; } # # Set file contents # sub set_file_contents { my $file = shift; my $contents = shift; # Open the file w/ overwrite open (OUTFILE, ">" . $file); print OUTFILE $contents; close (OUTFILE); } # Returns 1 if a file has changed with respect to its timestamp in the database, # 0 otherwise sub file_changed_db_ts { my $file = shift; my @statbuf; # This doesn't matter if (a) file doesn't exist, (b) doesn't have a # timestamp anyway or (c) the timestamp is zero. Usually (b) and (c) # are equivalent but the use is undefined. if (!file_name_exist($file)) { return 0; } if (!defined(db_file_ts($file)) || db_file_ts($file) == 0) { return 0; } @statbuf = stat($file); if (defined($statbuf[9])) { return (db_file_ts($file) != $statbuf[9]); } else { error('Unable to get the last modification timestamp of the destination ' . 'file ' . $file . '.' . "\n\n"); return 0; } } # Install one file # flags are forwarded to db_add_file() sub install_file { my $src = shift; my $dst = shift; my $patchRef = shift; my $flags = shift; my $default_overwrite = 'yes'; if (@_ >= 1) { $default_overwrite = shift; } # If we are installing a config file and such a config file already exists # AND it has changed timestamp with regards to the DB, # OR # it's marked as a user-modified config file... if (($flags & $cFlagConfig) && file_name_exist($dst)) { if (db_userfile_in($dst)) { # Note the default choice. We should not require users to pass # a command line option to preserve userfiles. That should be the # default. my $default = ($gOption{'overwrite'} ? 'no' : 'yes'); my $rv = get_answer('You have previously modified the configuration ' . 'file ' . $dst . ' and chosen to keep your ' . 'version. Would you still like to keep it ' . 'instead of having this program create a new ' . 'version?', 'yesno', $default); if ($rv eq 'yes') { return 'no'; } } elsif (file_changed_db_ts($dst)) { # When this branch is reached, we default to clobbering unless # --preserve is used. my $default = ($gOption{'preserve'} ? 'yes' : 'no'); my $rv = get_answer('The configuration file ' . $dst . ' already ' . 'exists and has been modified (possibly by you) since the ' . 'last install. Would you like to keep your version of the ' . 'file instead of installing a new one?', 'yesno', $default); if ($rv eq 'yes') { db_remove_file($dst); db_set_userfile($dst); print wrap("Note that you may need to change this configuration " . "file yourself. For example, if you reconfigure your " . "networking settings using this script, and choose to keep " . "your version of a configuration file, you may need to " . "update it to reflect the new layout of the network." . "\n\n", 0); return 'no'; } } } # Well, if that's not true, just clobber it, whatever it is or was. # Doing this will also undo its userfile status. uninstall_file($dst); if (file_check_exist($dst, $default_overwrite)) { return 'no'; } # The file could be a symlink to another location. Remove it unlink($dst); if (not internal_sed($src, $dst, 0, $patchRef)) { error('Unable to copy the source file ' . $src . ' to the destination ' . 'file ' . $dst . '.' . "\n\n"); } db_add_file($dst, $flags); install_permission($src, $dst); return 'yes'; } # mkdir() that reports errors sub safe_mkdir { my $file = shift; if (-d $file) { return 1; } if (mkdir($file, 0777) == 0) { error('Unable to create the directory ' . $file . '.' . "\n\n"); } return 1; } # Remove trailing slashes in a dir path sub dir_remove_trailing_slashes { my $path = shift; for (;;) { my $len; my $pos; $len = length($path); if ($len < 2) { # Could be '/' or any other character. Ok. return $path; } $pos = rindex($path, '/'); if ($pos != $len - 1) { # No trailing slash return $path; } # Remove the trailing slash $path = substr($path, 0, $len - 1) } } # Emulate a simplified basename program sub internal_basename { return substr($_[0], rindex($_[0], '/') + 1); } # Create a hierarchy of directories with permission 0755 # flags: # 0x4 - write this directory creation in the installer database # ($cFlagDirectoryMark) # Return 1 if the directory existed before sub create_dir { my $dir = shift; my $parentDir = internal_dirname($dir); my $flags = shift; if (-d $dir) { return $cDirExists; } if (index($dir, '/') != -1) { create_dir($parentDir, $flags); } if ($flags & $cFlagFailureOK) { if (mkdir($dir, 0777) == 0) { return $cCreateDirFailure; } } else { safe_mkdir($dir, $flags); } if ($flags & $cFlagDirectoryMark) { db_add_dir($dir); } return $cCreateDirSuccess; } # Get a valid non-persistent answer to a question # Use this when the answer shouldn't be stored in the database sub get_answer { my $msg = shift; my $type = shift; my $default = shift; my $answer; if (not defined($gAnswerSize{$type})) { die 'get_answer(): type ' . $type . ' not implemented :(' . "\n\n"; } for (;;) { $answer = check_answer(query($msg, $default, $gAnswerSize{$type}), $type, 'user'); if ($answer ne '') { return $answer; } # Let the error propagate to callers if ($gOption{'default'} == 1) { return ''; } } } # Get a valid persistent answer to a question # Use this when you want an answer to be stored in the database sub get_persistent_answer { my $msg = shift; my $id = shift; my $type = shift; my $default = shift; my $answer; if (defined($gDBAnswer{$id})) { # There is a previous answer in the database $answer = check_answer($gDBAnswer{$id}, $type, 'db'); if ($answer ne '') { # 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 (not file_name_exist($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); } # Backup a file in the idea to restore it in the future. sub backup_file_to_restore { my $file = shift; my $restoreStr = shift; my $backupDir = shift; # (optional) Pass this in to backup $file to a different directory. my $dstFile; if (!defined($backupDir)) { $dstFile = $file . $cBackupExtension; $backupDir = ''; } else { $dstFile = $backupDir . '/' . internal_basename($file) . $cBackupExtension; } if (file_name_exist($file) && (not file_name_exist($dstFile))) { my %p; undef %p; rename $file, $dstFile; db_add_answer($cRestorePrefix . $restoreStr, $file); db_add_answer($cRestorePrefix . $restoreStr . $cRestoreBackupSuffix, $dstFile); if (defined db_get_answer_if_exists($cRestoreBackList)) { my $allRestoreStr; $allRestoreStr = db_get_answer($cRestoreBackList); db_add_answer($cRestoreBackList,$allRestoreStr . ':' . $restoreStr); } else { db_add_answer($cRestoreBackList, $restoreStr); } } } # XXX Duplicated in pkg_mgr.pl # format of the returned hash: # - key is the system file # - value is the backed up file. # This function should never know about filenames. Only database # operations. sub db_get_files_to_restore { my %fileToRestore; undef %fileToRestore; if (defined db_get_answer_if_exists($cRestoreBackList)) { my $restoreStr; foreach $restoreStr (split(/:/, db_get_answer($cRestoreBackList))) { if (defined db_get_answer_if_exists($cRestorePrefix . $restoreStr)) { $fileToRestore{db_get_answer($cRestorePrefix . $restoreStr)} = db_get_answer($cRestorePrefix . $restoreStr . $cRestoreBackupSuffix); } } } return %fileToRestore; } # Uninstall a file previously installed by us sub uninstall_file { my $file = shift; if (not db_file_in($file)) { # Not installed by this program return; } if (file_name_exist($file)) { if (file_changed_db_ts($file) || db_userfile_in($file)) { backup_file($file); db_remove_file($file); return; } if (not unlink($file)) { error('Unable to remove the file "' . $file . '".' . "\n"); } else { db_remove_file($file); } } else { print wrap('This program previously created the file ' . $file . ', and ' . 'was about to remove it. Somebody else apparently did it ' . 'already.' . "\n\n", 0); db_remove_file($file); } } # Uninstall a directory previously installed by us sub uninstall_dir { my $dir = shift; if (not db_dir_in($dir)) { # Not installed by this program return; } if (-d $dir) { if (not rmdir($dir)) { print wrap('This program previously created the directory ' . $dir . ', ' . 'and was about to remove it. Since there are files in that ' . 'directory that this program did not create, it will not be ' . 'removed.' . "\n\n", 0); if ( defined($ENV{'VMWARE_DEBUG'}) && ($ENV{'VMWARE_DEBUG'} eq 'yes')) { system('ls -AlR ' . shell_string($dir)); } } } else { print wrap('This program previously created the directory ' . $dir . ', and was about to remove it. Somebody else apparently did ' . 'it already.' . "\n\n", 0); } db_remove_dir($dir); } # Install one directory (recursively) sub install_dir { my $src_dir = shift; my $dst_dir = shift; my $patchRef = shift; my $file; if (create_dir($dst_dir, $cFlagDirectoryMark) == $cDirExists) { my @statbuf; @statbuf = stat($dst_dir); if (not (defined($statbuf[2]))) { error('Unable to get the access rights of destination directory "' . $dst_dir . '".' . "\n\n"); } # Was bug 15880 if ( ($statbuf[2] & 0555) != 0555 && get_answer('Current access permissions on directory "' . $dst_dir . '" will prevent some users from using ' . vmware_product_name() . '. Do you want to set those permissions properly?', 'yesno', 'yes') eq 'yes') { safe_chmod(($statbuf[2] & 07777) | 0555, $dst_dir); } } else { install_permission($src_dir, $dst_dir); } foreach $file (internal_ls($src_dir)) { if (-d $src_dir . '/' . $file) { install_dir($src_dir . '/' . $file, $dst_dir . '/' . $file, $patchRef); } else { install_file($src_dir . '/' . $file, $dst_dir . '/' . $file, $patchRef, $cFlagTimestamp); } } } # Uninstall files and directories beginning with a given prefix sub uninstall_prefix { my $prefix = shift; my $prefix_len; my $file; my $dir; $prefix_len = length($prefix); # Remove all files beginning with $prefix foreach $file (keys %gDBFile) { if (substr($file, 0, $prefix_len) eq $prefix) { uninstall_file($file); } } # Remove all directories beginning with $prefix # We sort them by decreasing order of their length, to ensure that we will # remove the inner ones before the outer ones foreach $dir (sort {length($b) <=> length($a)} keys %gDBDir) { if (substr($dir, 0, $prefix_len) eq $prefix) { uninstall_dir($dir); } } } # Return the version of VMware sub vmware_version { my $buildNr; $buildNr = '10.3.22 build-15902021'; return remove_whitespaces($buildNr); } # Return product name and version sub vmware_longname { my $name = vmware_product_name() . ' ' . vmware_version(); if (defined $gSystem{'system'} and not (vmware_product() eq 'server')) { $name .= ' for ' . $gSystem{'system'}; } return $name; } # 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"; } return &{$gCheckAnswerFct{$type}}($answer, $source); } # END OF THE LIBRARY FUNCTIONS # Set the name of the main /etc/vmware* directory. sub initialize_globals { if (vmware_product() eq 'tools-for-linux' || vmware_product() eq 'tools-for-freebsd' || vmware_product() eq 'tools-for-solaris') { $gRegistryDir = '/etc/vmware-tools'; } else { $gRegistryDir = '/etc/vmware'; } $gLogDir = '/var/log/vmware'; $gStateDir = $gRegistryDir . '/state'; $gInstallerMainDB = $gRegistryDir . '/locations'; $gConfFlag = $gRegistryDir . '/not_configured'; $gOption{'default'} = 0; $gOption{'compile'} = 0; $gOption{'prebuilt'} = 0; $gOption{'tools-switch'} = 0; $gOption{'clobber-xorg-modules'} = 0; $gOption{'regenerate-cert'} = 0; $gOption{'preserve'} = 0; $gOption{'overwrite'} = 0; $gOption{'skip-stop-start'} = vmware_product() eq 'server'; $gOption{'rpc-on-end'} = 1; $gOption{'create_shortcuts'} = 1; $gOption{'modules_only'} = 0; $gOption{'kernel_version'} = ''; $gOption{'log-answers'} = 0; $gOption{'installing'} = 0; } # Set up the location of external helpers sub initialize_external_helpers { my $program; my @programList; if (not defined($gHelper{'more'})) { $gHelper{'more'} = ''; if (defined($ENV{'PAGER'})) { my @tokens; # The environment variable sometimes contains the pager name _followed by # a few command line options_. # # Isolate the program name (we are certain it does not contain a # whitespace) before dealing with it. @tokens = split(' ', $ENV{'PAGER'}); $tokens[0] = DoesBinaryExist_Prompt($tokens[0]); if (not ($tokens[0] eq '')) { # This is _already_ a shell string $gHelper{'more'} = join(' ', @tokens); } } if ($gHelper{'more'} eq '') { $gHelper{'more'} = DoesBinaryExist_Prompt('more'); if ($gHelper{'more'} eq '') { error('Unable to continue.' . "\n\n"); } # Save it as a shell string $gHelper{'more'} = shell_string($gHelper{'more'}); } } if (vmware_product() eq 'tools-for-freebsd') { @programList = ('cp', 'uname', 'grep', 'ldd', 'mknod', 'kldload', 'kldunload', 'mv', 'rm', 'ldconfig'); } elsif (vmware_product() eq 'tools-for-solaris') { # Note that svcprop(1) is added for Solaris 10 and later after it is # guaranteed that uname(1) has been found @programList = ('cp', 'uname', 'grep', 'ldd', 'mknod', 'modload', 'modinfo', 'modunload', 'add_drv', 'rem_drv', 'update_drv', 'rm', 'isainfo', 'ifconfig', 'cat', 'mv', 'sed', 'cut','pkginfo'); } else { @programList = ('cp', 'uname', 'grep', 'ldd', 'mknod', 'depmod', 'insmod', 'lsmod', 'modprobe', 'mv', 'rmmod', 'rm', 'tar', 'modinfo'); } foreach $program (@programList) { if (not defined($gHelper{$program})) { $gHelper{$program} = DoesBinaryExist_Prompt($program); if ($gHelper{$program} eq '') { error('Unable to continue.' . "\n\n"); } } } if (vmware_product() eq 'tools-for-solaris' && solaris_10_or_greater() eq 'yes') { $gHelper{'svcprop'} = DoesBinaryExist_Prompt('svcprop'); if ($gHelper{'svcprop'} eq '') { error('Unable to continue.' . "\n\n"); } } $gHelper{'insserv'} = internal_which('insserv'); $gHelper{'chkconfig'} = internal_which('/sbin/chkconfig'); $gHelper{'update-rc.d'} = internal_which('update-rc.d'); if (vmware_product() eq 'server' && $gHelper{'chkconfig'} eq '') { error('No initscript installer found.' . "\n\n"); } } # Check the validity of an answer whose type is dirpath # Return a clean answer if valid, or '' sub check_answer_dirpath { my $answer = shift; my $source = shift; $answer = dir_remove_trailing_slashes($answer); if (substr($answer, 0, 1) ne '/') { print wrap('The path "' . $answer . '" is a relative path. Please enter ' . 'an absolute path.' . "\n\n", 0); return ''; } if (-d $answer) { # The path is an existing directory return $answer; } # The path is not a directory if (file_name_exist($answer)) { if ($source eq 'user') { print wrap('The path "' . $answer . '" exists, but is not a directory.' . "\n\n", 0); } return ''; } # The path does not exist if ($source eq 'user') { return (get_answer('The path "' . $answer . '" does not exist currently. ' . 'This program is going to create it, including needed ' . 'parent directories. Is this what you want?', 'yesno', 'yes') eq 'yes') ? $answer : ''; } else { return $answer; } } $gAnswerSize{'dirpath'} = 20; $gCheckAnswerFct{'dirpath'} = \&check_answer_dirpath; # Check the validity of an answer whose type is dirpath_existing # Return an existing directory if valid, or '' sub check_answer_dirpath_existing { my $answer = shift; my $source = shift; $answer = dir_remove_trailing_slashes($answer); if (substr($answer, 0, 1) ne '/') { print wrap('The path "' . $answer . '" is a relative path. Please enter ' . 'an absolute path.' . "\n\n", 0); return ''; } if (-d $answer) { # The path is an existing directory return $answer; } # The path is not a directory if (file_name_exist($answer) && ($source eq 'user')) { print wrap('The path "' . $answer . '" exists, but is not a directory.' . "\n\n", 0); } else { # The path does not exist print wrap('The path "' . $answer . '" does not exist.' . "\n\n", 0); } return ''; } $gAnswerSize{'dirpath_existing'} = 20; $gCheckAnswerFct{'dirpath_existing'} = \&check_answer_dirpath_existing; # 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 $uts_headers; $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 ($answer =~ m|^/usr/include(/.*)?$|) { #/# Broken colorizer. if ($source eq 'user') { if (get_answer('The header files in /usr/include are generally for C ' . 'libraries, not for the running kernel. If you do not ' . 'have kernel header files in your /usr/src directory, ' . 'you probably do not have the kernel-source package ' . 'installed. Are you sure that /usr/include contains ' . 'the header files associated with your running kernel?', 'yesno', 'no') eq 'no') { return ''; } } } if (not (-d $answer . '/linux')) { if ($source eq 'user') { print wrap('The path "' . $answer . '" is an existing directory, but it ' . 'does not contain a "linux" subdirectory 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); } return ''; } # # Kernels before 2.6.18 declare UTS_RELEASE in version.h. Newer kernels # use utsrelease.h. We include both just in case somebody moves UTS_RELEASE # back while leaving utsrelease.h file in place. # if ($gOption{'kernel_version'} eq '') { $uts_headers = "#include <linux/version.h>\n"; if (-e $answer . '/linux/utsrelease.h') { $uts_headers .= "#include <linux/utsrelease.h>\n"; } $header_version_uts = direct_command( shell_string($gHelper{'echo'}) . ' ' . shell_string($uts_headers . $pattern . ' UTS_RELEASE') . ' | ' . shell_string($gHelper{'gcc'}) . ' ' . shell_string('-I' . $answer) . ' -E - | ' . shell_string($gHelper{'grep'}) . ' ' . shell_string($pattern)); chomp($header_version_uts); $header_version_uts =~ s/^$pattern \"([^\"]*)\".*$/$1/; if (not ($header_version_uts eq $gSystem{'uts_release'})) { if ($source eq 'user') { print wrap('The directory of kernel headers (version ' . $header_version_uts . ') does not match your running ' . 'kernel (version ' . $gSystem{'uts_release'} . '). Even ' . 'if the module were to compile successfully, it 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); } return ''; } $header_smp = direct_command(shell_string($gHelper{'grep'}) . ' CONFIG_SMP ' . shell_string($answer . '/linux/autoconf.h')); if (not ($header_smp eq '')) { # linux/autoconf.h contains the up/smp information $header_smp = direct_command( shell_string($gHelper{'echo'}) . ' ' . shell_string('#include <linux/autoconf.h>' . "\n" . $pattern . ' CONFIG_SMP') . ' | ' . shell_string($gHelper{'gcc'}) . ' ' . shell_string('-I' . $answer) . ' -E - | ' . shell_string($gHelper{'grep'}) . ' ' . shell_string($pattern)); chomp($header_smp); $header_smp =~ s/^$pattern (\S+).*$/$1/; $header_smp = ($header_smp eq '1') ? 'yes' : 'no'; if (not (lc($header_smp) eq lc($gSystem{'smp'}))) { if ($source eq 'user') { print wrap('The kernel defined by this directory of header files is ' . (($header_smp eq 'yes') ? 'multiprocessor' : 'uniprocessor') . ', while ' . 'your running kernel is ' . (($gSystem{'smp'} eq 'yes') ? 'multiprocessor' : 'uniprocessor') . '.' . "\n\n", 0); } return ''; } } # # For kernels before 2.6.0 require asm and net subdirectories. And verify # that PAGE_OFFSET for running kernel matches one specified in kernel # headers. We use our Makefiles to build kernel modules on these kernels, # so we know that asm and net directories must be here for successful build, # and PAGE_OFFSET must match. # # For kernel 2.6.0 and above require ../Makefile and ../.config presence. # Although they could be theoretically missing, they are present on all # currently existing systems. And check for ../.config presence # rules out /usr/src/linux/include eliminates false positive we # currently hit on SuSE 9.x systems. And do not verify PAGE_OFFSET value # at all, asm/page.h needs special processing on 2.6.15+ kernels. # if ($header_version_uts =~ /^2\.[0-5]\./) { if ( (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 subdirectories "asm" and "net" as expected.' . "\n\n", 0); } return ''; } if (not (-r $answer . '/asm/page.h')) { if ($source eq 'user') { print wrap('The path "' . $answer . '" is a kernel header file ' . 'directory, but it does not contain the file "asm/page.h" ' . 'as expected.' . "\n\n", 0); } return ''; } my $header_page_offset = direct_command( shell_string($gHelper{'echo'}) . ' ' . shell_string('#define __KERNEL__' . "\n" . '#include <asm/page.h>' . "\n" . $pattern . ' __PAGE_OFFSET') . ' | ' . shell_string($gHelper{'gcc'}) . ' ' . shell_string('-I' . $answer) . ' -E - | ' . shell_string($gHelper{'grep'}) . ' ' . shell_string($pattern)); chomp($header_page_offset); # Ignore PAGE_OFFSET if we cannot parse it. if ($header_page_offset =~ /^$pattern \(?0x([0-9a-fA-F]{8,})/) { # We found a valid page offset $header_page_offset = $1; if (defined($gSystem{'page_offset'}) and not (lc($header_page_offset) eq lc($gSystem{'page_offset'}))) { if ($source eq 'user') { print wrap('The kernel defined by this directory of header files does ' . 'not have the same address space size as your running ' . 'kernel.' . "\n\n", 0); } return ''; } } } else { if (not (-r $answer . '/../Makefile')) { if ($source eq 'user') { print wrap('The path "' . $answer . '" is a kernel header file ' . 'directory, but it is not part of kernel source tree.' . "\n\n", 0); } return ''; } if (not (-r $answer . '/../.config')) { if ($source eq 'user') { print wrap('The path "' . $answer . '" is a kernel header file ' . 'directory, but it is not configured yet.' . "\n\n", 0); } return ''; } } return $answer; } $gAnswerSize{'headerdir'} = 20; $gCheckAnswerFct{'headerdir'} = \&check_answer_headerdir; # 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; my $re; $re = '^([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}$'; # This comment fixes emacs's broken syntax highlighting if ($answer =~ /$re/) { 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 serial number # Return a clean answer if valid, or '' sub check_answer_serialnum { my $answer = shift; my $source = shift; my $re; if ($answer eq '') { return ' '; } $re = '^(([0-9]|[A-Z]){5}-){3}(([0-9]|[A-Z]){5})$'; # This comment fixes emacs's broken syntax highlighting if ($answer =~ /$re/) { return $answer; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. It must be of the ' . 'form XXXXX-XXXXX-XXXXX-XXXXX where X is a digit 0-9 or a ' . 'capital letter A-Z' . "\n\n", 0); } return ''; } $gAnswerSize{'serialnum'} = 23; $gCheckAnswerFct{'serialnum'} = \&check_answer_serialnum; # Check the validity of an answer whose type is editorwizardhelp # Return a clean answer if valid, or '' sub check_answer_editorwizardhelp { my $answer = shift; my $source = shift; if (lc($answer) =~ /^e(ditor)?$/) { return 'editor'; } if (lc($answer) =~ /^w(izard)?$/) { return 'wizard'; } if (lc($answer) =~ /^h(elp)?$/) { return 'help'; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. It must be one of ' . '"w", "e" or "h".' . "\n\n", 0); } return ''; } $gAnswerSize{'editorwizardhelp'} = 6; $gCheckAnswerFct{'editorwizardhelp'} = \&check_answer_editorwizardhelp; # 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); } return ''; } $gAnswerSize{'yesnohelp'} = 4; $gCheckAnswerFct{'yesnohelp'} = \&check_answer_yesnohelp; # Check the validity of an answer whose type is vmnet # Return a clean answer if valid, or '' sub check_answer_vmnet { my $answer = shift; my $source = shift; if ($answer =~ /^\d+$/) { if ($answer >= $gMinVmnet && $answer <= $gMaxVmnet) { return $answer; } } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. It must be an ' . 'integer between ' . $gMinVmnet . ' and ' . $gMaxVmnet . '.' . "\n\n", 0); } return ''; } $gAnswerSize{'vmnet'} = length("$gMaxVmnet"); $gCheckAnswerFct{'vmnet'} = \&check_answer_vmnet; # Check the validity of an answer whose type is nettype # Return a clean answer if valid, or '' sub check_answer_nettype { my $answer = shift; my $source = shift; if (lc($answer) =~ /^h(ostonly)?$/) { return 'hostonly'; } if (lc($answer =~ /^b(ridged)?$/)) { return 'bridged'; } if (lc($answer =~ /^n(at)?$/)) { return 'nat'; } if (lc($answer =~ /^none$/)) { return 'none'; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. It must be either ' . '"b", "h", "n", or "none".' . "\n\n", 0); } return ''; } $gAnswerSize{'nettype'} = 8; $gCheckAnswerFct{'nettype'} = \&check_answer_nettype; # Check the validity of an answer whose type is availethif # Return a clean answer if valid, or '' sub check_answer_availethif { my $answer = shift; my $source = shift; if (grep($answer eq $_, @gAvailEthIf)) { return $answer; } if ($source eq 'user') { if (grep($answer eq $_, @gAllEthIf)) { print wrap('The ethernet device "' . $answer . '" is already configured ' . 'as a bridged device.' . "\n\n", 0); return ''; } if (get_answer('The ethernet device "' . $answer . '" was not detected on ' . 'your system. Available ethernet devices detected on ' . 'your system include ' . join(', ', @gAvailEthIf) . '. ' . 'Are you sure you want to use this device? (yes/no)', 'yesno', 'no') eq 'no') { return ''; } else { return $answer; } } return ''; } $gAnswerSize{'availethif'} = 4; $gCheckAnswerFct{'availethif'} = \&check_answer_availethif; # check the validity of a user or group name against the set of authenticatable users, # return the answer if valid or '' sub check_answer_usergrp { my $answer = shift; my $source = shift; if ($answer=~/^[^-][a-zA-Z0-9.\@\$_-]+$/) { my @id_params = getpwnam $answer; if ((scalar(@id_params) != 0) && length($answer) < 32) { return $answer; } } if ($source eq 'user') { my $answer_string = '""'; if (defined($answer)) { $answer_string = '"' . $answer . '"'; } print wrap('The answer ' . $answer_string .' is invalid. Please enter a valid ' . "name of length < 32 and containing any of letters of the alphabet, " . "numbers, '.\@_-', and and not beginning with a '-'. The name must " . "be a valid user on this system." . "\n\n", 0); } return ''; } $gAnswerSize{'usergrp'} = 32; $gCheckAnswerFct{'usergrp'} = \&check_answer_usergrp; # check the validity of a timeout value # return the answer if valid or '' sub check_answer_timeout { my $answer = shift; my $source = shift; if ($answer=~/^-?\d+$/ && $answer >= -1) { return $answer; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. Please enter a valid' . ' number of minutes in the range -1 to 99999' . "\n\n", 0); } return ''; } $gAnswerSize{'timeout'} = 5; $gCheckAnswerFct{'timeout'} = \&check_answer_timeout; # Check the validity of an answer whose type is nocheck # Always returns answer. sub check_answer_anyethif { my $answer = shift; my $source = shift; return $answer; } $gAnswerSize{'anyethif'} = 4; $gCheckAnswerFct{'anyethif'} = \&check_answer_anyethif; # Check the validity of an answer whose type is inetport # Return a clean answer if valid, or '' sub check_answer_inetport { my $answer = shift; my $source = shift; if ($source eq 'default' || $source eq 'db') { if (check_if_port_free($answer) != 1) { return ''; } } if (($answer !~ /^\d+$/) || ($answer < 0) || ($answer > 65536)) { my $filler = ''; if ($answer ne '') { $filler = ", $answer,"; } my $msg = "The port you selected" . $filler . " is invalid. A port value " . "must be between 0 - 65536 and contain only decimal digits." . "\n\n"; if ($source eq 'user') { print wrap($msg, 0); } return ''; } if (check_if_port_free($answer) != 1) { if ($source eq 'user') { print wrap("The port you chose is not available for use. Please select another " . "port value." . "\n", 0); } return ''; } return $answer; } $gAnswerSize{'inetport'} = 5; $gCheckAnswerFct{'inetport'} = \&check_answer_inetport; # Check the validity of an answer whose type is number # Return a clean number if valid, or '0' # Default value for the 'number' type of answer. # This $gMaxNumber as well as the $gAnswerSize{'number'} has to be updated # before calling get_*_answer functions so that wrap() leaves enough room # for the reply. my $gMaxNumber = 0; sub check_answer_number { my $answer = shift; my $source = shift; if (($answer =~ /^\d+$/) && ($answer > 0) && ($answer <= $gMaxNumber)) { return $answer; } if ($source eq 'user') { print wrap('The answer "' . $answer . '" is invalid. Please enter a valid ' . 'number in the range 1 to ' . $gMaxNumber . "\n\n", 0); } return ''; } $gAnswerSize{'number'} = length($gMaxNumber); $gCheckAnswerFct{'number'} = \&check_answer_number; # Check the validity of an answer whose type is netname # Always returns answer. sub check_answer_netname { my $answer = shift; my $source = shift; if (length($answer) > 255) { print wrap("That name is too long, please enter a name" . " shorter than 256 characters.\n"); $answer = ''; } return $answer; } $gAnswerSize{'netname'} = 32; $gCheckAnswerFct{'netname'} = \&check_answer_netname; my %gPortCache; # Check $cServices file for specified port # If port not in $cServices return 1 # If port is in $cServices return 0 sub check_port_not_registered { my $port = shift; if (defined($gPortCache{$port}) && $gPortCache{$port} == 2) { return 0; } return 1; } # Use /proc/net/tcp as a list of ports in use and fillout the # port cache with those entries. sub get_proc_tcp_entries { undef %gPortCache; foreach my $i (qw(tcp tcp6)) { if (not open(TCP, "</proc/net/" . $i)) { next; } while (<TCP>) { if (/^\s*\d+:\s*[0-9a-fA-F]+:([0-9a-fA-F]{4})\s*[0-9a-fA-F]+:[0-9a-fA-F]{4}\s*([0-9a-fA-F]{2}).*$/) { # We'll consider a socket free if it is in TIME_WAIT state if ($2 eq "06") { next; } # Ignore if port is already defined, unless its value is 2. That is, # the port is really a 'maybe' in use. if (!defined($gPortCache{$1}) || $gPortCache{$1} eq 2) { $gPortCache{hex($1)} = 1; } } } close TCP; } } sub check_if_port_active { my $port = shift; # In this case, only want ports that are active on the system, not just # place holders in /etc/services. if (defined($gPortCache{$port}) && $gPortCache{$port} == 1) { return 1; } return 0; } # if the port is already in use i.e. in the port cache. # If port is free, return 1; # If port is in use, return 0; sub check_if_port_free { my $port = shift; if (check_if_port_active($port)) { return 0; } return check_port_not_registered($port); } # Display the end-user license agreement sub show_EULA { if ( (not defined($gDBAnswer{'EULA_AGREED'})) || (db_get_answer('EULA_AGREED') eq 'no')) { if ($gOption{'default'} == 1) { print wrap('You must read and accept the End User License Agreement to ' . 'continue.' . "\n\n" . 'To display End User License ' . 'Agreement please restart ' . $0 . ' in the ' . 'interactive mode, without using `-d\' option.' . "\n\n", 0); exit 0; } query('You must read and accept the End User License Agreement to ' . 'continue.' . "\n" . 'Press enter to display it.', '', 0); open(EULA, db_get_answer('DOCDIR') . '/EULA') || error("$0: can't open EULA file: $!\n"); my $origRecordSeparator = $/; undef $/; my $eula = <EULA>; close(EULA); $/ = $origRecordSeparator; $eula =~ s/(.{50,76})\s/$1\n/g; # Trap the PIPE signal to avoid broken pipe errors on RHEL4 U4. local $SIG{PIPE} = sub {}; open(PAGER, '| ' . $gHelper{'more'}) || error("$0: can't open $gHelper{'more'}: $!\n"); print PAGER $eula . "\n"; close(PAGER); print "\n"; # Make sure there is no default answer here if (get_persistent_answer('Do you accept? (yes/no)', 'EULA_AGREED', 'yesno', '') eq 'no') { print wrap('Please try again when you are ready to accept.' . "\n\n", 0); exit 1; } print wrap('Thank you.' . "\n\n", 0); } } # Retrieve distribution information sub distribution_info { my $issue = '/etc/issue'; my $system; # First use the accurate method that are intended to work reliably on recent # distributions (if an FHS guy is listening, we really need a generic way to # do this) if (-e '/etc/debian_version') { return 'debian'; } if (-e '/etc/redhat-release') { return 'redhat'; } if (-e '/etc/SuSE-release') { return 'suse'; } if (-e '/etc/turbolinux-release') { return 'turbolinux'; } if (-e '/etc/mandrake-release') { return 'mandrake'; } # Then use less accurate methods that should work even on old distributions, # if people haven't customized their system too much if (-e $issue) { if (not (direct_command(shell_string($gHelper{'grep'}) . ' -i ' . shell_string('debian') . ' ' . shell_string($issue)) eq '')) { return 'debian'; } if (not (direct_command(shell_string($gHelper{'grep'}) . ' -i ' . shell_string('red *hat') . ' ' . shell_string($issue)) eq '')) { return 'redhat'; } if (not (direct_command(shell_string($gHelper{'grep'}) . ' -i ' . shell_string('suse\|s\.u\.s\.e') . ' ' . shell_string($issue)) eq '')) { return 'suse'; } if (not (direct_command(shell_string($gHelper{'grep'}) . ' -i ' . shell_string('caldera') . ' ' . shell_string($issue)) eq '')) { return 'caldera'; } } return 'unknown'; } sub vmware_check_vm_app_name { if (vmware_product() eq 'tools-for-solaris') { my $sbindir = db_get_answer('SBINDIR'); return $sbindir . '/vmware-checkvm'; } else { my $libdir = db_get_answer('LIBDIR'); return $libdir . '/sbin/vmware-checkvm'; } } sub vmware_vmx_app_name { return db_get_answer('LIBDIR') . '/bin/vmware-vmx'; } sub is64BitKernel { if (vmware_product() eq 'tools-for-solaris') { if (direct_command(shell_string($gHelper{'isainfo'}) . ' -k') =~ /amd64/) { return 1; } else { return 0; } } if (direct_command(shell_string($gHelper{'uname'}) . ' -m') =~ /(x86_64|amd64)/) { return 1; } else { return 0; } } # SIGINT handler (only gets used in tools configurations) sub sigint_handler { error("\n"); } # The installer packages up both 32 and 64 bit userlevel binaries, leaving # them all in LIBDIR. This function links the correct thing in BINDIR and # SBINDIR. This "installs" vmware-checkvm, vmware-guestd, # vmware-hgfsclient, vmware-hgfsmounter and vmware-vmblock-fuse. sub setup32or64Symlinks { my $is64BitUserland = is64BitUserLand(); my $libdir = db_get_answer('LIBDIR'); my $libbindir = $libdir . ($is64BitUserland ? '/bin64' : '/bin32'); my $libsbindir = $libdir . ($is64BitUserland ? '/sbin64' : '/sbin32'); my $liblibdir = $libdir . ($is64BitUserland ? '/lib64' : '/lib32'); my $pluginsdir = $libdir . ($is64BitUserland ? '/plugins64' : '/plugins32'); my $pamdfile = $libdir . '/configurator/pam.d/vmtoolsd'; my $bindir = db_get_answer('BINDIR'); my $sbindir = db_get_answer('SBINDIR'); $libbindir .= getFreeBSDBinSuffix(); $liblibdir .= getFreeBSDLibSuffix(); $libsbindir .= getFreeBSDSbinSuffix(); if ($useApploader) { install_hardlink($libbindir . '/appLoader', $libsbindir . '/vmtoolsd'); } if ($open_vm_compat == 0) { install_symlink($libsbindir . '/vmware-checkvm', $sbindir . '/vmware-checkvm'); install_symlink($libsbindir . '/vmware-rpctool', $sbindir . '/vmware-rpctool'); if ($useApploader) { install_symlink($libsbindir . '/vmtoolsd', $sbindir . "/vmtoolsd"); if ($is64BitUserland and $have_caf eq 'yes') { my $caflibdir = db_get_answer('CAFLIBDIR'); install_hardlink($libbindir . '/appLoader', $caflibdir . '/vmware-caf/pme/bin/CommAmqpListener'); install_hardlink($libbindir . '/appLoader', $caflibdir . '/vmware-caf/pme/bin/ManagementAgentHost'); } } elsif (vmware_product() eq 'tools-for-freebsd') { install_symlink($libsbindir . '/vmtoolsd-wrapper', $sbindir . '/vmtoolsd'); } else { install_symlink($libsbindir . '/vmware-guestd-wrapper', $sbindir . '/vmware-guestd'); } } # # Linux distros now use apploader for toolbox-cmd and # modconfig-console. # if ($useApploader) { install_symlink($libbindir . '/appLoader', $libsbindir . '/vmware-modconfig-console'); install_symlink($libbindir . '/appLoader', $sbindir . '/vmware-namespace-cmd'); } if ($open_vm_compat == 0) { if ($useApploader) { install_symlink($libbindir . '/appLoader', $bindir . '/vmware-toolbox-cmd'); } else { install_symlink($libbindir . '/vmware-toolbox-cmd-wrapper', $bindir . '/vmware-toolbox-cmd'); } } # On FreeBSD, Linux and Solaris we use a wrapper script for vmware-user. # vmware-user gets special attention as its dependency on shipped gtk libraries # require us to use special a wrapper script. Remember that the vmware-user wrapper # script is marked setuid. if (vmware_product() eq 'tools-for-linux') { if ($open_vm_compat == 0) { install_symlink($libbindir . '/vmware-user-suid-wrapper', $bindir . '/vmware-user'); set_manifest_component('vmwareuser', 'TRUE'); } } elsif (vmware_product() eq 'tools-for-freebsd') { install_symlink($libbindir . '/vmware-user-wrapper', $bindir . '/vmware-user-wrapper'); install_symlink($libbindir . '/vmware-user-suid-wrapper', $bindir . '/vmware-user'); } # Generic spots for the wrapper to access so it won't need to know lib32-6, etc. install_symlink($liblibdir, $libdir . "/lib"); install_symlink($libbindir, $libdir . "/bin"); install_symlink($libsbindir, $libdir . "/sbin"); install_symlink($liblibdir . "/libconf", $libdir . "/libconf"); install_symlink($pluginsdir, $libdir . "/plugins"); install_symlink($libdir . "/plugins", $gRegistryDir . "/plugins"); # Install a pam.d vmtoolsd file on all but Solaris. if (vmware_product() ne 'tools-for-solaris' && ($open_vm_compat == 0)) { my %patch; undef %patch; install_file($pamdfile, '/etc/pam.d/vmtoolsd', \%patch, $cFlagConfig | $cFlagTimestamp, 'no'); } if (vmware_product() eq 'tools-for-linux') { # If installing on top of an open-vm_tools package, warning the user # about possible missing commands should VMware Tools be permanently # removed from the system. if ($open_vm_compat) { print wrap("\nYou have chosen to install " . vmware_product_name() . ' on top of an open-vm-tools package. You will now be ' . 'given the option to replace some commands provided by ' . 'open-vm-tools. Please note that if you replace any ' . 'commands at this time and later remove ' . vmware_product_name() . ', it may be necessary to ' . "re-install the open-vm-tools.\n"); } # hgfsclient is installed by newer versions of ovt, so by default # we will not overwrite it: my $installed = install_symlink($libbindir . '/appLoader', $bindir . '/vmware-hgfsclient', $open_vm_compat ? 'no' : 'yes'); if ($installed eq 'yes') { set_manifest_component('hgfsclient', 'TRUE'); $gOpenVmCompatOverWrite = 1; } # The installation of vmhgfs-fuse has been moved until after # /sbin/mount.vmhgfs is processed. Newly installed vmhgfs-fuse will have # problems accessing runtime libraries at this point in the configuration. # - "Unable to lookup library directory" # Hardcoded because mount(8) expects mounting apps to be /sbin/mount.fs # Install the hgfsmounter app to /sbin/mount.vmhgfs to solve SELinux issues. # See bug 527827. set_manifest_component('hgfsmounter', 'TRUE'); # Handle vmware-hgfsmounter in a special manner so it will work with # SELinux. See bug 527827. if (-d '/sbin') { my %patch; my $vmwHgfsmntPath = "$libsbindir/vmware-hgfsmounter"; my $sbinHgfsmntPath = '/sbin/mount.vmhgfs'; # Copy vmware-hgfsmounter to /sbin and setup a link for # legacy purposes. Also call restorecon for good measure. my $dflt_overwrite = 'yes'; # If open_vm_compat mode, a version of the vmware-hgfsmounter may # not be installed by OVT packages, e.g. 10.2.5. This installer # will add a symbolic link for /sbin/mount.vmhgfs be default and # in this case will incorrectly flag that symbolic link as having # "replaced" an OVT command. To avoid this erroneous state, # check if the file pre-exists before any install logic. my $prevHgfsmnt = file_name_exist($sbinHgfsmntPath ); # If we are installing in ovt compat mode, check if we want vmhgfs. # Well, actually we cannot check on the configuration of the # open-vm-tools and the HGFS query comes later. At the moment, the # best that can be determined is whether vmhgfs-fuse indicates that # an updated mount.vmhgfs may be needed if HGFS is later enabled. # Note, VMHGFS_CONFED will probably never be defined when installing # on top of open-vm-tools so that check cannot be used here. if ($open_vm_compat and vmware_vmhgfs_can_use_fuse() == 0) { $dflt_overwrite = 'no'; $gOpenVmCompatHGFSDefault = 'yes'; } $installed = install_file($vmwHgfsmntPath, $sbinHgfsmntPath, \%patch, 0x1, $dflt_overwrite); if ($installed eq 'yes') { if ($prevHgfsmnt) { $gOpenVmCompatOverWrite = 1; } $gOpenVmCompatHGFSDefault = 'yes'; } restorecon($sbinHgfsmntPath); } $installed = install_symlink($libbindir . '/appLoader', $bindir . '/vmhgfs-fuse', $open_vm_compat ? 'no' : 'yes'); if ($installed eq 'yes') { $gOpenVmCompatOverWrite = 1; set_manifest_component('vmhgfs-fuse', 'TRUE'); } if($open_vm_compat == 0) { if ($have_grabbitmqproxy eq 'yes') { install_symlink($libbindir . '/appLoader', $bindir . '/vmware-guestproxycerttool'); set_manifest_component('guestproxycerttool', 'TRUE'); } install_symlink($libbindir . '/vmware-xferlogs', $bindir . '/vmware-xferlogs'); install_symlink($libbindir . '/appLoader', $sbindir . '/vmware-vmblock-fuse'); set_manifest_component('vmtoolsd', 'TRUE'); set_manifest_component('checkvm', 'TRUE'); set_manifest_component('toolbox-cmd', 'TRUE'); } } if (vmware_product() eq 'tools-for-freebsd') { my $hgfsmounterBinary = $libsbindir . '/vmware-hgfsmounter'; if (-f $hgfsmounterBinary) { safe_chmod(0555, $hgfsmounterBinary); # Hardcoded because FreeBSD's mount(8) expects mounting apps to be /sbin/mount_fs install_symlink($hgfsmounterBinary, '/sbin/mount_vmhgfs'); } safe_chmod(0555, $libsbindir . '/vmware-vmblockmounter'); install_symlink($libsbindir . '/vmware-vmblockmounter', '/sbin/mount_vmblock'); set_manifest_component('vmblockmounter', 'TRUE'); } if ($open_vm_compat == 0) { # # vmware-xdg-* scripts are packaged under ${libdir}/bin32 only. Explicitly # symlink those to $bindir. # if (vmware_product() eq 'tools-for-freebsd') { my $xdgSrc = sprintf("$libdir/bin32%s/vmware-xdg-detect-de", getFreeBSDBinSuffix()); install_symlink($xdgSrc, "$bindir/vmware-xdg-detect-de"); } # vgauth if ($have_vgauth eq 'yes') { my $vgauthdir = internal_dirname($libdir) . '/vmware-vgauth'; if($useApploader) { install_symlink($libbindir . '/appLoader', $vgauthdir . '/VGAuthService'); install_symlink($libbindir . '/appLoader', $vgauthdir . '/vmware-vgauth-cmd'); install_symlink($libbindir . '/appLoader', $vgauthdir . '/vmware-alias-import'); } } } } # Solaris can boot into either its 32-bit or 64-bit kernel and invokes the # appropriate binary through use of its isaexec(3C) program. This means that # we need to add symlinks for both the 32-bit and 64-bit versions and a hard # link to /usr/lib/isaexec. sub install_solaris_symlink { my $targetdir = shift; my $targetname = shift; my $linkdir = shift; my $linkname = shift; # Create i86 and amd64 directories if necessary create_dir($linkdir . '/i86', $cFlagDirectoryMark); create_dir($linkdir . '/amd64', $cFlagDirectoryMark); install_symlink($targetdir . '/i86/' . $targetname, $linkdir . '/i86/' . $linkname); install_symlink($targetdir . '/amd64/' . $targetname, $linkdir . '/amd64/' . $linkname); # Try to install a hard link to /usr/lib/isaexec. If that doesn't work, we # copy isaexec to $linkdir and create a hard link to that one. if (install_hardlink('/usr/lib/isaexec', $linkdir . '/' . $linkname) eq 'no') { my $isaexec = $linkdir . '/isaexec'; system(shell_string($gHelper{'cp'}) . ' /usr/lib/isaexec ' . $isaexec); db_add_file($isaexec, 0); install_hardlink($isaexec, $linkdir . '/' . $linkname); } } # See the comment above install_solaris_symlink(). sub setupSolarisSymlinks { my $libdir = db_get_answer('LIBDIR'); my $plugins32 = $libdir . '/plugins32'; my $plugins64 = $libdir . '/plugins64'; my $libbindir = $libdir . '/bin'; my $libsbindir = $libdir . '/sbin'; my $bindir = db_get_answer('BINDIR'); my $sbindir = db_get_answer('SBINDIR'); my %patch; install_solaris_symlink($libsbindir, 'vmware-checkvm', $sbindir, 'vmware-checkvm'); install_solaris_symlink($libsbindir, 'vmware-rpctool', $sbindir, 'vmware-rpctool'); # If the app requires a wrapper, let the wrapper handle selecting the arch. install_symlink("$libdir/wrapper-all.sh", "$sbindir/vmtoolsd"); install_symlink("$libdir/wrapper-all.sh", "$bindir/vmware-toolbox-cmd"); # VMware-user-suid-wrapper requires no wrapper and is only available in # a 32 bit flavor. Since the installer sets the suid bit, leave the binary # where it is and simply add a symlink to it from $bindir install_symlink("$libbindir/i86/vmware-user-suid-wrapper", "$bindir/vmware-user"); # Install vmware-hgfsmounter into /etc/fs/vmhgfs because that's # where Solaris's mount expects to find it. We only install 32 # bit version since it is going to work on 64 bit as well. create_dir('/etc/fs/vmhgfs', $cFlagDirectoryMark); install_symlink($libsbindir . '/i86/vmware-hgfsmounter', '/etc/fs/vmhgfs/mount'); # Do the same for vmware-vmblockmounter create_dir('/etc/fs/vmblock', $cFlagDirectoryMark); install_symlink($libsbindir . '/i86/vmware-vmblockmounter', '/etc/fs/vmblock/mount'); install_symlink($plugins32, "$gRegistryDir/plugins"); install_symlink($plugins64, "$plugins32/amd64"); # # vmware-xdg-* scripts are packaged under ${libdir}/bin/i86 only. Explicitly # symlink those to $bindir. # install_symlink("$libdir/bin/i86/vmware-xdg-detect-de", "$bindir/vmware-xdg-detect-de"); } # We must set up various symlinks for each of our Tools products. sub setupSymlinks { if (vmware_product() eq 'tools-for-linux') { setup32or64Symlinks(); } elsif (vmware_product() eq 'tools-for-freebsd') { setup32or64Symlinks(); } elsif (vmware_product() eq 'tools-for-solaris') { setupSolarisSymlinks(); } } # Open a file binary and read the ELF header. We really only care about the # fifth byte, EI_CLASS. I pulled the values from /usr/include/elf.h sub is64BitElf { my $file = shift; my ($buf, $buf2); my $cEI_CLASS = 4; my $cELFCLASS64 = 2; my $cEI_MAG0 = 0; my $cSELFMAG = 4; my $cELFMAG = "\x7FELF"; open(X_BIN, '<' . $file) || return 0; seek(X_BIN, $cEI_MAG0, 0) || return 0; read(X_BIN, $buf, $cSELFMAG) || return 0; ($buf2) = unpack("a4", $buf); if ($buf2 ne $cELFMAG) { return 0; } seek(X_BIN, $cEI_CLASS, 0) || return 0; read(X_BIN, $buf, 1) || return 0; ($buf2) = unpack("C", $buf); return ($buf2 eq $cELFCLASS64); } sub isAthlonKernel { my $version = shift; my $patchLevel = shift; my $answer = 'no'; # Right now this only applies to 2.4.x kernels as /proc/ksyms was # eliminated in the 2.6 kernel. # # Look for the mmx flag so we can tell if we are running on a kernel # built for the athlon family of processors. if ("$version.$patchLevel" eq '2.4') { if (not open (KSYMS, '</proc/ksyms')) { error ('Could not open /proc/ksyms to determine if kernel is compiled ' . "for Athlon processors.\n"); } while (<KSYMS>) { if (/mmx_clear_page/) { $answer = 'yes'; last; } } close (KSYMS); } return $answer; } # Retrieve and check system information sub system_info { my $fullVersion; my $version; my $patchLevel; my $subLevel; my $runSystem; # populate_non_vmware_modules can take a while to run. Add this here to # let users know our code is actually doing something. print wrap("Initializing...\n\n", 0); $gSystem{'system'} = direct_command(shell_string($gHelper{'uname'}) . ' -s'); chomp($gSystem{'system'}); if (vmware_product() eq 'tools-for-freebsd') { $runSystem = 'FreeBSD'; } elsif (vmware_product() eq 'tools-for-solaris') { $runSystem = 'SunOS'; } else { $runSystem = 'Linux'; } if (not ($gSystem{'system'} eq $runSystem)) { error('You are not running ' . $runSystem . '. This version of the product ' . 'only runs on ' . $runSystem . '.' . "\n\n"); } # Users will expect the output to be "Solaris", despite what uname -s says if (vmware_product() eq 'tools-for-solaris') { $gSystem{'system'} = 'Solaris'; } $gSystem{'uts_release'} = direct_command(shell_string($gHelper{'uname'}) . ' -r'); chomp($gSystem{'uts_release'}); $gSystem{'uts_version'} = direct_command(shell_string($gHelper{'uname'}) . ' -v'); chomp($gSystem{'uts_version'}); $gSystem{'distribution'} = distribution_info(); if ($runSystem eq 'Linux') { ($version, $patchLevel, $subLevel) = split(/\./, $gSystem{'uts_release'}); # Clean the subLevel in case there is an extraversion ($subLevel) = split(/[^0-9]/, $subLevel); $gSystem{'version_utsclean'} = $version . '.' . $patchLevel . '.' . $subLevel; $gSystem{'version_integer'} = kernel_version_integer($version, $patchLevel, $subLevel); if ($gSystem{'version_integer'} < kernel_version_integer(2, 0, 0)) { error('You are running Linux version ' . $gSystem{'version_utsclean'} . '. This product only runs on 2.0.0 and later kernels.' . "\n\n"); } if (vmware_product() eq 'server') { $gSystem{'smp'} = 'no'; $gSystem{'versioned'} = 'yes'; } else { $gSystem{'smp'} = (direct_command(shell_string($gHelper{'uname'}) . ' -v') =~ / SMP /) ? 'yes' : 'no'; $gSystem{'versioned'} = (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('^[0-9a-fA-F]\{8\} Using_Versions') . ' /proc/ksyms 2> /dev/null') eq '') ? 'no' : 'yes'; } if (is64BitKernel()) { $gSystem{'page_offset'} = '0000010000000000'; } else { $gSystem{'page_offset'} = 'C0000000'; } if ($gSystem{'version_integer'} >= kernel_version_integer(2, 1, 0)) { # 2.1.0+ kernels have hardware verify_area() support my @fields; @fields = split(' ', direct_command( shell_string($gHelper{'grep'}) . ' ' . shell_string('^[0-9a-fA-F]\{8\} printk') . ' /proc/ksyms 2> /dev/null')); if (not defined($fields[0])) { @fields = split(' ', direct_command( shell_string($gHelper{'grep'}) . ' ' . shell_string('^[0-9a-fA-F]\{8\} \w printk') . ' /proc/kallsyms 2> /dev/null')); } if (defined($fields[0])) { my $page_offset; # printk is always located in first 256KB of kernel - that is from # PAGE_OFFSET to PAGE_OFFSET + 256KB on normal kernel and # PAGE_OFFSET + 1MB to PAGE_OFFSET + 1.25MB for bzImage kernel. # Both ranges are well below 16MB granularity we are allowing. if ($fields[0] =~ /^([0-9a-fA-F]{2})/) { $page_offset = uc($1).'000000'; } else { $page_offset = undef; } $gSystem{'page_offset'} = $page_offset; } else { # Unable to find page_offset: accept anything $gSystem{'page_offset'} = undef; } } # Linux kernel build bug $gSystem{'build_bug'} = (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('^[0-9a-fA-F]\{8\} __global_cli_R__ver___global_cli') . ' /proc/ksyms 2> /dev/null') eq '') ? 'no' : 'yes'; } # Determine whether the kernel is complied for Athlon Processors if (vmware_product() eq 'tools-for-linux') { $gSystem{'athlonKernel'} = isAthlonKernel($version, $patchLevel); } else { $gSystem{'athlonKernel'} = 'no'; } # Warning, the return after the end of the if statement # will void everything after. if (vmware_product() eq 'tools-for-linux' || vmware_product() eq 'tools-for-freebsd' || vmware_product() eq 'tools-for-solaris') { $gSystem{'product'} = direct_command(shell_string(vmware_check_vm_app_name()) . ' -p'); if (direct_command(shell_string(vmware_check_vm_app_name())) =~ /good/) { $gSystem{'invm'} = 'yes'; } else { $gSystem{'invm'} = 'no'; } # get the resolution we'll use to determine the guest's resolution. $gSystem{'resolution'} = get_resolution(); return; } # These commands are Linux-specific if (vmware_product() ne 'tools-for-freebsd' && vmware_product() ne 'tools-for-solaris') { # C library # XXX This relies on the locale my @missing; my $ldd_out = direct_command(shell_string($gHelper{'ldd'}) . ' ' . vmware_vmx_app_name()); foreach my $lib (split(/\n/, $ldd_out)) { if ($lib =~ '(\S+) => not found') { push(@missing, $1); } } if (scalar(@missing) > 0) { print wrap("The following libraries could not be found on your system:\n", 0); print join("\n", @missing); print "\n\n"; query('You will need to install these manually before you can run ' . vmware_product_name() . ".\n\nPress enter to continue.", '', 0); } # Processor foreach my $instruction ('^cpuid', 'cmov') { if (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string($instruction) . ' /proc/cpuinfo') eq '') { # Read the current config file; open(CPUINFO, '/proc/cpuinfo') or error('Unable to open /proc/cpuinfo in read-mode' . "\n\n"); my @cpuinfo = <CPUINFO>; close(CPUINFO); error('Your ' . (($gSystem{'smp'} eq 'yes') ? 'processors do' : 'processor does') . ' not ' . 'support the ' . $instruction . ' instruction. ' . vmware_product_name() . ' will not run on this system.' . "\n\n" . 'Your /proc/cpuinfo is:' . "\n\n" . "@cpuinfo"); } } # The "flags" field became the "features" field in 2.4.0-test11-pre5 if (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('^\(flags\|features\).* tsc') . ' /proc/cpuinfo') eq '') { error('Your ' . (($gSystem{'smp'} eq 'yes') ? 'processors do' : 'processor does') . ' not ' . 'have a Time Stamp Counter. ' . vmware_product_name() . ' will not run on this system.' . "\n\n"); } } } # Point the user to a URL dealing with module-related problems and exits sub module_error { error('For more information on how to troubleshoot module-related problems, ' . 'please visit our Web site at "http://www.vmware.com/go/' . 'unsup-linux-products" and "http://www.vmware.com/go/' . 'unsup-linux-tools".' . "\n\n"); } # OS-independent method of loading a kernel module by object path # Returns true (non-zero) if the operation succeeded, false otherwise. sub kmod_load_by_path { my $modpath = shift; # IN: Path to module object file my $doSilent = shift; # IN: Flag to indicate whether loading should be done silently my $doForce = shift; # IN: Flag to indicate whether loading should be forced my $probe = shift; # IN: 1 if to probe only, 0 if to actually load my $silencer = ''; if (defined($doSilent) && $doSilent) { $silencer = ' >/dev/null 2>&1'; } if (defined($gHelper{'insmod'})) { # Linux return !system(shell_string($gHelper{'insmod'}) . ($probe ? ' -p ' : ' ') . ((defined($doForce) && $doForce) ? ' -f ' : ' ') . shell_string($modpath) . $silencer); } elsif (defined($gHelper{'kldload'})) { # FreeBSD return !system(shell_string($gHelper{'kldload'}) . ' ' . shell_string($modpath) . $silencer); } elsif (defined($gHelper{'modload'})) { # Solaris return !system(shell_string($gHelper{'modload'}) . ' ' . shell_string($modpath) . $silencer); } return 0; # Failure } # 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 $silent = shift; my $dst_dir; my %patch; if (not (-e $mod)) { # The module does not exist return 0; } # NOTE: See bug 347401. We don't want to unload the pvscsi kernel module # and try a new one, so we'll simply skip this step for pvscsi. # NOTE: See bug 349327. We no longer want to interrupt networking during # tools configuration. if (vmware_product() ne 'server' && $name ne 'pvscsi' && $name ne 'vmxnet' && $name ne 'vmxnet3') { # 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 if(!kmod_load_by_path($mod, $silent, $force, 1)) { return 0; } # If we are using new module-init-tools, they just ignore # '-p' option, and they just loaded module into the memory. # Just try rmmod-ing it. Silently. kmod_unload($name, 0); } if (-d $cKernelModuleDir . '/'. $gSystem{'uts_release'}) { $dst_dir = $cKernelModuleDir . '/' . $gSystem{'uts_release'}; } else { print wrap('This program does not know where to install the ' . $name . ' module because the "' . $cKernelModuleDir . '/' . $gSystem{'uts_release'} . '" directory (the usual ' . 'location where the running kernel would look for the ' . 'module) is missing. Please make sure that this ' . 'directory exists before re-running this program.' . "\n\n", 0); return 0; } create_dir($dst_dir . '/misc', $cFlagDirectoryMark); undef %patch; # Install the module with a .o extension, as the Linux kernel does my $modDest = $dst_dir . '/misc/' . $name; install_file($mod, $modDest . '.o', \%patch, $cFlagTimestamp); # install a .ko symlink for 2.6 kernels install_symlink($modDest . '.o', $modDest . '.ko'); # The old installer allowed people to manually build modules without .o # extension. Such modules were not removed by the old uninstaller, and # unfortunately, insmod tries them first. Let's move them. if (file_name_exist($dst_dir . '/misc/' . $name)) { backup_file($dst_dir . '/misc/' . $name); if (not unlink($dst_dir . '/misc/' . $name)) { print STDERR wrap('Unable to remove the file ' . $dst_dir . '/misc/' . $name . '.' . "\n\n", 0); } } return 1; } # Remove a temporary directory sub remove_tmp_dir { my $dir = shift; if (system(shell_string($gHelper{'rm'}) . ' -rf ' . shell_string($dir))) { print STDERR wrap('Unable to remove the temporary directory ' . $dir . '.' . "\n\n", 0); }; } sub get_cc { $gHelper{'gcc'} = ''; if (defined($ENV{'CC'}) && (not ($ENV{'CC'} eq ''))) { $gHelper{'gcc'} = internal_which($ENV{'CC'}); if ($gHelper{'gcc'} eq '') { print wrap('Unable to find the compiler specified in the CC environnment variable: "' . $ENV{'CC'} . '".' . "\n\n", 0); } } if ($gHelper{'gcc'} eq '') { $gHelper{'gcc'} = internal_which('gcc'); if ($gHelper{'gcc'} eq '') { $gHelper{'gcc'} = internal_which('egcs'); if ($gHelper{'gcc'} eq '') { $gHelper{'gcc'} = internal_which('kgcc'); if ($gHelper{'gcc'} eq '') { $gHelper{'gcc'} = DoesBinaryExist_Prompt('gcc'); } } } } print wrap('Using compiler "' . $gHelper{'gcc'} . '". Use environment variable CC to override.' . "\n\n", 0); return $gHelper{'gcc'}; } sub get_gcc_version { my ($gcc) = @_; # See bug 330893. Previously, we retrieved the gcc version from the output # of "gcc -dumpversion". Unfortunately, SuSE doesn't use this string like # any other distribution, and so we'll retrieve this from parsing the # output of "gcc -v" instead. my $gcc_version = direct_command(shell_string($gcc) . " -v 2>&1 | tail -1"); chomp($gcc_version); # Two examples of $gcc_version at this stage are: # # gcc version 4.1.2 20070115 (prerelease) (SUSE Linux) # gcc version 4.1.2 20071124 (Red Hat 4.1.2-42) # # Parse through this to retrieve the version information. if ($gcc_version =~ /^gcc version (egcs-)?(\d+\.\d+(\.\d+)*)/) { return $2; } else { print wrap('Your compiler "' . $gHelper{'gcc'} . '" version "' . $gcc_version . '" is not supported ' . 'by this version of ' . vmware_product_name() . '.' . "\n\n", 0); return 'no'; } } # Verify gcc version, finding a better match if needed. sub check_gcc_version { my ($kernel_gcc_version) = undef; # In kernels >= 2.6.19, we no longer have to worry about gcc version # differences between the kernel and the modules compiled for that kernel. # Hence, we can return yes if our kernel version is 2.6.19 or greater. # # See bug 350735 for details. -astiegmann if (defined ($gSystem{'version_integer'}) and $gSystem{'gcc_version'} ne 'no' and $gSystem{'version_integer'} >= kernel_version_integer (2, 6, 19)) { return 'yes'; } if (open(PROC_VERSION, '</proc/version')) { my $line; if (defined($line = <PROC_VERSION>)) { close PROC_VERSION; if ($line =~ /gcc version (egcs-)?(\d+(\.\d+)*)/) { $kernel_gcc_version = $2; if ($kernel_gcc_version eq $gSystem{'gcc_version'}) { return 'yes'; } } } else { close PROC_VERSION; } } my $msg; my $g_major = '0'; if ($gSystem{'gcc_version'} =~ /^(\d+)\./) { $g_major = $1; } if (defined($kernel_gcc_version)) { my $k_major = '0'; my $k_minor = '0'; if ($kernel_gcc_version =~ /^(\d+)\.(\d+)/) { $k_major = $1; $k_minor = $2; } if ($g_major ne $k_major) { # Try a to find a gcc-x.y binary my $newGcc = internal_which("gcc-$k_major.$k_minor"); if ($newGcc ne '') { # We found one, we need to update the global values. $gHelper{'gcc'} = $newGcc; $gSystem{'gcc_version'} = get_gcc_version($newGcc); if ($gSystem{'gcc_version'} eq 'no') { return 'no'; } else { $gSystem{'gcc_version'} =~ /^(\d+)\./; $g_major = $1; if ($kernel_gcc_version eq $gSystem{'gcc_version'}) { return 'yes'; } } } } $msg = 'Your kernel was built with "gcc" version "' . $kernel_gcc_version . '", while you are trying to use "' . $gHelper{'gcc'} . '" version "' . $gSystem{'gcc_version'} . '". '; if ($g_major ne $k_major) { $msg .= 'This configuration is not supported and ' . vmware_product_name() . ' cannot work in such configuration. ' . 'Please either recompile your kernel with "' . $gHelper{'gcc'} . '" version "'. $gSystem{'gcc_version'} . '", or restart ' . $0 . ' with CC environment variable pointing to the "gcc" version "' . $kernel_gcc_version . '".' . "\n\n"; print wrap($msg, 0); return 'no'; } $msg .= 'This configuration is not recommended and ' . vmware_product_name() . ' may crash if you\'ll continue. ' . 'Please try to use exactly same compiler as one used for ' . 'building your kernel. Do you want to go with compiler "' . $gHelper{'gcc'} .'" version "' . $gSystem{'gcc_version'} .'" anyway?'; } if (defined($msg) and get_answer($msg, 'yesno', 'no') eq 'no') { return 'no'; } return 'yes'; } # Determine whether it is remotely plausible to build a module from source sub can_build_module { my $name = shift; if (vmware_product() eq 'tools-for-freebsd' || vmware_product() eq 'tools-for-solaris') { return 'no'; # Right now we only build tools from source on Linux } return 'yes'; } # Build a module sub build_module { my $name = shift; my $dir = shift; my $ideal = shift; my $build_dir; my $gcc_version; # Lazy initialization if ($gFirstModuleBuild == 1) { my $program; my $headerdir; foreach $program ('make', 'echo', 'tar', 'rm') { if (not defined($gHelper{$program})) { $gHelper{$program} = DoesBinaryExist_Prompt($program); if ($gHelper{$program} eq '') { return 'no'; } } } if (get_cc() eq '') { return 'no'; } $gSystem{'gcc_version'} = get_gcc_version($gHelper{'gcc'}); if ($gSystem{'gcc_version'} eq 'no') { return 'no'; } if (check_gcc_version() eq 'no') { return 'no'; } # When installing the modules, kernels 2.4+ setup a symlink to the kernel # source directory $headerdir = $cKernelModuleDir . '/preferred/build/include'; if (($gOption{'kernel_version'} ne '') or (check_answer_headerdir($headerdir, 'default') eq '')) { $headerdir = $cKernelModuleDir . '/' . (($gOption{'kernel_version'} eq '')? $gSystem{'uts_release'}: $gOption{'kernel_version'}) . '/build/include'; if (check_answer_headerdir($headerdir, 'default') eq '') { # Use a default usual location $headerdir = '/usr/src/linux/include'; } } db_remove_answer("HEADER_DIR"); get_persistent_answer('What is the location of the directory of C header ' . 'files that match your running kernel?', 'HEADER_DIR', 'headerdir', $headerdir); $gFirstModuleBuild = 0; } print wrap('Extracting the sources of the ' . $name . ' module.' . "\n\n", 0); $build_dir = make_tmp_dir($cTmpDirPrefix); if (system(shell_string($gHelper{'tar'}) . ' -C ' . shell_string($build_dir) . ' -xopf ' . shell_string($dir . '/' . $name . '.tar'))) { print wrap('Unable to untar the "' . $dir . '/' . $name . '.tar' . '" file in the "' . $build_dir . '" directory.' . "\n\n", 0); return 'no'; } print wrap('Building the ' . $name . ' module.' . "\n\n", 0); if (system(shell_string($gHelper{'make'}) . ' -C ' . shell_string($build_dir . '/' . $name . '-only') . ' auto-build ' . (($gSystem{'smp'} eq 'yes') ? 'SUPPORT_SMP=1 ' : '') . (($gOption{'kernel_version'} ne '')? shell_string('VM_UNAME=' . $gOption{'kernel_version'}) . ' ':'') . shell_string('HEADER_DIR=' . db_get_answer('HEADER_DIR')) . ' ' . shell_string('CC=' . $gHelper{'gcc'}) . ' ' . shell_string('GREP=' . $gHelper{'grep'}) . ' ' . shell_string('IS_GCC_3=' . (($gSystem{'gcc_version'} =~ /^3\./) ? 'yes' : 'no')))) { print wrap('Unable to build the ' . $name . ' module.' . "\n\n", 0); return 'no'; } if ($gOption{'kernel_version'} eq '') { # Don't use the force flag: the module is supposed to perfectly load if (try_module($name, $build_dir . '/' . $name . '.o', 0, 1)) { print wrap('The ' . $name . ' module loads perfectly into the running kernel.' . "\n\n", 0); return 'yes'; } } else { print wrap('Not trying to load the module as it is for a different kernel version.' . "\n\n", 0); return 'yes'; } # Don't remove the build dir so that the user can investiguate print wrap('Unable to make a ' . $name . ' module that can be loaded in the ' . 'running kernel:' . "\n", 0); try_module($name, $build_dir . '/' . $name . '.o', 0, 0); # Try to analyze some usual suspects if ($gSystem{'build_bug'} eq 'yes') { print wrap('It appears that your running kernel has not been built from a ' . 'kernel source tree that was completely clean (i.e. the ' . 'person who built your running kernel did not use the "make ' . 'mrproper" command). You may want to ask the provider of ' . 'your Linux distribution to fix the problem. In the ' . 'meantime, you can do it yourself by rebuilding a kernel ' . 'from a kernel source tree that is completely clean.' . "\n\n", 0); } else { print wrap('There is probably a slight difference in the 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", 0); } return 'no'; } # Identify specific characteristics of the SuSE distro we're running on. # Takes a hash reference as a parameter. Fills hash with the following: # variant 'sle' or 'opensuse', if defined # version version string (e.g., '10' or '11.0'), if defined # patchlevel patchlevel string (e.g., '1'), if defined # The caller is responsible for determining that we're running on some # version of SuSE. sub identify_suse_variant { my %propRef; if (not open(FH, '</etc/SuSE-release')) { error("Unable to open /etc/SuSE-release in read-only mode.\n\n"); } while (<FH>) { chomp; if (/^SUSE Linux Enterprise/) { $propRef{'variant'} = 'sle'; } elsif (/^openSUSE/) { $propRef{'variant'} = 'opensuse'; } elsif (/^VERSION\s+=\s+(.+)$/) { $propRef{'version'} = $1; } elsif (/^PATCHLEVEL\s+=\s+(.+)$/) { $propRef{'patchlevel'} = $1; } } close(FH); return %propRef; } # Converts version to the opaque token - if tokens from two kernels # are identical, these two kernels are probably ABI compatible. # This is done for RHEL 3, 4, and 5, and for SLES 10 and 11. sub get_module_compatible_version { my $utsrel = shift; # RHEL3: 2.4.21-9.0.1.ELhugemem => 2.4.21-ELhugemem # RHEL4: 2.6.9-11.ELsmp => 2.6.9-ELsmp if ($utsrel =~ /^(\d+\.\d+\.\d+-)[0-9.]+\.(EL.*)$/) { return $1.$2; } # RHEL5: 2.6.18-8.1.1.el5 => 2.6.18-el5 if ($utsrel =~ /^(\d+\.\d+\.\d+-)[0-9.]+\.(el.*)$/) { return $1.$2; } # SLES 10/11: 2.6.16.46-0.12-default => 2.6.16.46-default if ($gSystem{'distribution'} eq 'suse') { my %prop = identify_suse_variant(); if (defined($prop{'variant'}) and $prop{'variant'} eq 'sle' and defined($prop{'version'}) and $prop{'version'} =~ /^1[01]/) { if ($utsrel =~ /^(\d\.\d\.\d+\.\d+)-[0-9.]+(-.*)$/) { return $1.$2; } } } return $utsrel; } # 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 sub get_suitable_modules { my $dir = shift; my @perfect = (); my @compatible = (); my @dangerous = (); my $candidate; my $uts_release = $gSystem{'uts_release'}; my $uts_compatible = get_module_compatible_version($uts_release); foreach $candidate (internal_ls($dir)) { my %prop; my $list; # Read the properties file if (not open(PROP, '<' . $dir . '/' . $candidate . '/properties')) { print STDERR wrap('Unable to open the property file "' . $dir . '/' . $candidate . '/properties". Skipping this kernel.' . "\n\n", 0); next; } undef %prop; while (<PROP>) { if (/^UtsVersion (.+)$/) { $prop{'UtsVersion'} = $1; } elsif (/^(\S+) (\S+)/) { $prop{$1} = $2; } } close(PROP); if (not (lc($gSystem{'smp'}) eq lc($prop{'SMP'}))) { # SMP does not match next; } if (defined($gSystem{'page_offset'}) and not (lc($gSystem{'page_offset'}) eq lc($prop{'PageOffset'}))) { # Page offset does not match next; } # Check if the kernel is from the Athlon family of kernels (athlon, k[78]). # If the kernel is compiled for Athlon processors, then we should only use # PBMs that are compiled for Athlon Kernels. Otherwise... don't use the # Athlon modules at all. See bug 360476 for more details. # # Note: Assume (for now) that if AthlonKernel is not defined, then # this PBM is forbidden from running on AthlonKernels if ($gSystem{'athlonKernel'} eq 'yes') { # Then we should only load modules that are not forbidden if (not defined ($prop{'AthlonKernel'}) or (lc ($prop{'AthlonKernel'}) eq 'forbidden')) { next; } } else { # Then we should skip all PBMs that require Athlon Kernels to run if (defined ($prop{'AthlonKernel'}) and (lc ($prop{'AthlonKernel'}) eq 'required')) { next; } } # Confirm that the target architecture of the prebuilt module matches # that of the running kernel. If the properties file specified # the target architecture, and if the specified target architecture does # not match the running kernel's architecture, this module will get # skipped. if (defined($prop{'UtsMachine'})) { if (is64BitKernel()) { if ($prop{'UtsMachine'} ne 'x86_64') { next; } } elsif ($prop{'UtsMachine'} ne 'i386') { next; } } # By default module is not good for anything $list = undef; # If module is versioned, try "compatible" match (ModVersion is requied # due to 2.4.19-4GB being delivered by both SuSE8.1 and SLES8) if (defined($prop{'ModVersion'}) and $prop{'ModVersion'} eq 'yes' and $uts_compatible eq get_module_compatible_version($prop{'UtsRelease'})) { $list = \@compatible; } # If version matches exactly, great. But only if UtsVersion matches, # otherwise it is second class match equivalent to the "compatible" match if ($uts_release eq $prop{'UtsRelease'} && (!defined($prop{'UtsVersion'}) || $gSystem{'uts_version'} eq $prop{'UtsVersion'})) { $list = \@perfect; } if (defined($list)) { push @$list, ($candidate, $prop{'ModVersion'}); } } return (@perfect, @compatible, @dangerous); } # Find the first file that exists from the list of files. # Returns undefined if none of them exists. sub find_first_exist { my $return_val; my $file = shift; while (defined $file) { if (-e $file) { $return_val = $file; last; } $file = shift; } return $return_val; } # # Will either return a valid path to the GCC bin or will return # nothing. # sub getValidGccPath { my $gcc_path = shift; my $modconfig = shift; my $appLoaderArgs = shift; my $answer; my $query; my $default; while (1) { if (system("$modconfig --validate-gcc \"$gcc_path\" $appLoaderArgs " . ">/dev/null 2>&1") == 0) { $query = "The path \"$gcc_path\" appears to be a valid path to the " . "gcc binary."; $default = 'no'; } else { $query = "The path \"$gcc_path\" is not valid path to the gcc binary."; $default = 'yes'; $gcc_path = ''; } $answer = get_answer($query . "\n Would you like to change it?", 'yesno', $default); if ($answer eq 'yes') { # Get new path. $gcc_path = query('What is the location of the gcc program ' . 'on your machine?', $gcc_path, 0); } else { last; } } return $gcc_path; } # # Will either return a valid path to the kernel headers or will return # nothing. # sub getValidKernelHeadersPath { my $kh_path = shift; my $modconfig = shift; my $appLoaderArgs = shift; my $answer; my $query; my $default; # Handle the --kernel_version flag my $kInQuestion = getKernRel(); my $mcKverOpt = "-k $kInQuestion"; while (1) { if (system("$modconfig --validate-kernel-headers $mcKverOpt \"$kh_path\" " . "$appLoaderArgs >/dev/null 2>&1") == 0) { $query = "The path \"$kh_path\" appears to be a valid path to the " . "$kInQuestion kernel headers."; $default = 'no'; } else { $query = "The path \"$kh_path\" is not a valid path to the " . "$kInQuestion kernel headers."; $default = 'yes'; $kh_path = ''; } $answer = get_answer($query . "\n Would you like to change it?", 'yesno', $default); if ($answer eq 'yes') { # Get new path. $kh_path = query('Enter the path to the kernel header files for the ' . "$kInQuestion kernel?", $kh_path, 0); } else { last; } } return $kh_path; } # # Asks the user if they want to compile modules for linux. # Display the requirements and check to see if they have a valid path # to both GCC and their kernel headers. # sub compile_module_linux { my $moduleName = shift; my $moduleDest = shift; my $destName = shift; my $libdir = db_get_answer('LIBDIR'); my $libsbindir = $libdir . (is64BitUserLand() ? '/sbin64' : '/sbin32'); my $modconfig; my $appLoaderArgs; my $makePath; my $msg; # Handle the --kernel_version flag my $mcKverOpt = "-k " . getKernRel(); if ($useApploader) { $modconfig = shell_string($libsbindir . '/vmware-modconfig-console'); $appLoaderArgs = "-- -l \"$libdir\""; } else { $modconfig = 'VMWARE_USE_SHIPPED_GTK=yes ' . shell_string($libsbindir . '/vmware-modconfig-console-wrapper'); } if ($gFirstModuleBuild == 1) { $gFirstModuleBuild = 0; $makePath = internal_which('make'); $gGccPath = `$modconfig --get-gcc $appLoaderArgs`; $gKernelHeaders = `$modconfig --get-kernel-headers $mcKverOpt $appLoaderArgs`; # XXX important... # Check to make sure the installation is interactive. # If it is not, DO NOT ask questions. if ($gOption{'default'} eq 0) { print wrap("\n" . "Before you can compile modules, you need to have the " . "following installed... \n" . "\n" . " make\n" . " gcc\n" . " kernel headers of the running kernel\n" . "\n" . "\n", 0); # Print out some helpful info so the users know if we were able # to detect gcc/kernel headers on our own. print wrap("Searching for GCC...\n", 0); if ("$gGccPath" ne '' and system("$modconfig --validate-gcc " . "\"$gGccPath\" $appLoaderArgs") == 0) { print wrap("Detected GCC binary at \"$gGccPath\".\n", 0); } $gGccPath = getValidGccPath($gGccPath, $modconfig, $appLoaderArgs); print wrap("Searching for a valid kernel header path...\n", 0); if ("$gKernelHeaders" ne '' and system("$modconfig --validate-kernel-headers $mcKverOpt " . "\"$gKernelHeaders\" $appLoaderArgs") == 0) { print wrap("Detected the kernel headers at " . "\"$gKernelHeaders\".\n", 0); } $gKernelHeaders = getValidKernelHeadersPath($gKernelHeaders, $modconfig, $appLoaderArgs); } # Now check everything and if any check fails, let the user know why. # # Currently modconfig will find make on its own. So if make is not # in the PATH, then the compile will fail. We check form make below so if # there is no make, our users will know exactly why we can't compile our # modules. if ("$makePath" ne '' and "$gGccPath" ne '' and "$gKernelHeaders" ne ''){ $gCanCompileModules = 1; } else { $msg = "\nWARNING: This program cannot compile any modules for " . "the following reason(s)...\n"; if ("$makePath" eq '') { $msg .= " - This program could not find a valid path to make. " . "Please ensure that the make binary is installed " . "in the system path.\n\n"; } if ("$gGccPath" eq '') { $msg .= " - This program could not find a valid path to the gcc " . "binary. Please ensure that the gcc binary is " . "installed on this sytem.\n\n"; } if ("$gKernelHeaders" eq '') { $msg .= " - This program could not find a valid path to the " . "kernel headers of the running kernel. Please " . "ensure that the header files for the running kernel " . "are installed on this sytem.\n\n"; } query($msg, ' Press Enter key to continue ', 0); } } # Now if we can compile the modules, make it happen. Otherwise just # skip past this part. if ($gCanCompileModules eq 1) { unless (system(sprintf("$modconfig --build-mod %s %s %s %s %s %s $appLoaderArgs", $mcKverOpt, $moduleName, shell_string($gGccPath), shell_string($gKernelHeaders), $moduleDest, $destName)) != 0) { set_module_status($moduleName, 'installed'); return 'yes'; } } return 'no'; } ## # getModDBKey # # Creates and returns the DB key for a module based on a little # system information # sub getModDBKey { my $modName = shift; my $tag = shift; my $kernel = getKernRel(); # Remove non alpha-numeric characters $kernel =~ s/[\.\-\+]//g; my $key = join('_', uc($modName), $kernel, $tag); return $key; } sub make_modconfig_command { my $arg = shift; my $libdir = db_get_answer('LIBDIR'); my $libsbindir = $libdir . (is64BitUserLand() ? '/sbin64' : '/sbin32'); my $modconfig = ''; my $appLoaderArgs = ''; if ($arg ne '') { $arg = ' ' . $arg; } if ($useApploader) { $modconfig = shell_string($libsbindir . '/vmware-modconfig-console') . $arg . " -- -l \"$libdir\""; } else { $modconfig = 'VMWARE_USE_SHIPPED_GTK=yes ' . shell_string($libsbindir . '/vmware-modconfig-console-wrapper') . $arg; } return $modconfig; } # Configure a module for Linux using vmware-modconfig-console sub configure_module_linux { my $name = shift; my $gcc_path; my $kernel_headers; my $result = 'no'; my $modDest = get_module_install_dest($name); my $destName = get_module_name($name); my $libdir = db_get_answer('LIBDIR'); # First check to see if a PBM is available. If so, try to install it. # # Note that there is a check earlier on to ensure that prebuilt and compile # are mutually exclusive options. if ($gOption{'compile'} == 0 and system(make_modconfig_command("--pbm-available $name")) == 0) { print wrap("Found a compatible pre-built module for $name. " . "Installing it...\n\n",0); if (system(make_modconfig_command("--install-pbm $name $modDest $destName")) != 0) { print wrap("Failed to install the $name pre-built module.\n\n",0); $result = 'no'; } else { set_module_status($name, 'installed'); $result = 'yes'; } } elsif ($gOption{'prebuilt'} == 0) { # Otherwise try to compile it. $result = compile_module_linux($name, $modDest, $destName); } # modconfig will create this dir, but we want it in the database: # correct would be to add it in modconfig, but modconfig cannot add # directories w/out additional changes. if ( -d $libdir . '/symvers') { db_add_dir($libdir . '/symvers') } # Because our modules can now change names, we need to maintain some # variables that tell us our modules names and locations so we can # use them in our startup scripts. if ($result eq 'yes') { my $ext = ($gSystem{'version_integer'} >= kernel_version_integer(2, 6, 0) ? '.ko' : '.o'); my $mod_path = join('/',"/lib/modules", getKernRel(), $modDest, $destName . $ext); db_add_answer(getModDBKey($name, 'NAME'), $destName); db_add_answer(getModDBKey($name, 'PATH'), $mod_path); $gVmwareInstalledModules{"$name"} = $mod_path; } # Add some space between the compile output and output text. print "\n"; return $result; } # Configure a module sub configure_module { my $name = shift; my $mod_dir; if (vmware_product() eq 'tools-for-linux') { return configure_module_linux($name); } if (defined($gDBAnswer{'ALT_MOD_DIR'}) && ($gDBAnswer{'ALT_MOD_DIR'} eq 'yes')) { $mod_dir = db_get_answer('LIBDIR') . '/modules.new'; } else { $mod_dir = db_get_answer('LIBDIR') . '/modules'; } if ($gOption{'compile'} == 1 && can_build_module($name) eq 'yes') { db_add_answer('BUILDR_' . $name, 'yes'); } else { my @mod_list; @mod_list = get_suitable_modules($mod_dir . '/binary'); while ($#mod_list > -1) { my $candidate = shift(@mod_list); my $modversion = shift(@mod_list); # Note: When using the force flag, # Non-versioned modules can load into a versioned kernel. # Versioned modules can load into a non-versioned kernel. # # Consequently, it is only safe to use the force flag if _both_ the # kernel and the module are versioned. # This is not always the case as demonstrated by bug 18371. # # I would stop using force flag immediately, it does nothing good. if (try_module($name, $mod_dir . '/binary/' . $candidate . '/objects/' . $name . '.o', ($gSystem{'versioned'} eq 'yes') && ($modversion eq 'yes'), 1)) { print wrap('The ' . $candidate . ' - ' . $name . ' module ' . 'loads perfectly into the ' . 'running kernel.' . "\n\n", 0); return 'yes'; } } if ($gOption{'prebuilt'} == 1) { db_add_answer('BUILDR_' . $name, 'no'); print wrap('None of the pre-built ' . $name . ' modules for ' . vmware_product_name() . ' is suitable for your ' . 'running kernel.' . "\n\n", 0); return 'no'; } # No more building modules for 'ws' unless forced to. if (vmware_product() eq 'ws' && !$gOption{'compile'}) { # don't restart services at the end, no modules are installed $gOption{'skip-stop-start'} = 1; return 'yes'; } if (can_build_module($name) ne "yes" || get_persistent_answer('None of the pre-built ' . $name . ' modules for ' . vmware_product_name() . ' is suitable ' . 'for your running kernel. Do you want this ' . 'program 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') { return 'no'; } } if (build_module($name, $mod_dir . '/source') eq 'no') { return 'no'; } return 'yes'; } # Determines whether a solaris driver is already configured using the provided # driver name and alias (alias may be '' if none is required for this driver). # Results: yes if configured, no if not sub solaris_driver_configured { my $driver = shift; my $alias = shift; if (system(shell_string($gHelper{'grep'}) . ' ' . shell_string($driver) . ' /etc/name_to_major > /dev/null 2>&1') == 0) { if ($alias eq '' || direct_command('grep ' . $driver . ' /etc/driver_aliases') =~ /$alias/) { return 'yes'; } } return 'no'; } sub solaris_10u1 { # typically first line in /etc/release looks like this: # Solaris 10 10/09 s10x_u8wos_08a X86 # This does not work when Solaris uses a different pattern, # so the result needs to be checked if it's defined. open(RELEASE, '</etc/release') || return ''; <RELEASE> =~ /Solaris\ +\d+\ +(\d+\/\d+)/; close(RELEASE); if (defined($1) && ($1 eq '1/06')) { return 'yes'; } return 'no'; } sub solaris_11_or_greater { my ($major, $minor) = solaris_os_version(); if ($major > 5 || ($major == 5 && $minor >= 11)) { return 'yes'; } return 'no'; } sub solaris_10_or_greater { my ($major, $minor) = solaris_os_version(); if ($major > 5 || ($major == 5 && $minor >= 10)) { return 'yes'; } return 'no'; } sub solaris_is_opensolaris { my ($major, $minor) = solaris_os_version(); my $name = solaris_os_name(); if ($minor == 11 && $name =~ m/^snv/) { return 'yes'; } return 'no'; } sub configure_module_solaris { my $module = shift; my %patch; my $dir = db_get_answer('LIBDIR') . '/modules/binary/'; my ($major, $minor) = solaris_os_version(); my $os_name = solaris_os_name(); my $osDir; my $osFlavorDir; my $currentMinor = 11; # The most recent version we build the drivers for if (solaris_10_or_greater() ne "yes") { print vmware_product_name() . " for Solaris is only available for Solaris 10 and later.\n"; return 'no'; } # set osDir to number no larger than currentMinor: if ($minor < $currentMinor) { $osDir = $minor; } else { $osDir = $currentMinor; } if ($os_name eq 'snv_111b') { $osFlavorDir = 2009.06; } else { $osFlavorDir = $osDir; } if ($module eq 'vmmemctl') { if ($minor == 11 && solaris_is_opensolaris() ne 'yes') { # On Solaris 11 kernel thread structure changed to we need to # use driver compiled for official Solaris 11 $osDir = $minor; } if (solaris_10u1() eq 'yes') { # no vmmemctl for Solaris 10u1 db_add_answer('VMMEMCTL_CONFED', 'no'); return 'yes'; } # Install the corresponding 32-bit driver undef %patch; install_file($dir . $osDir . '/vmmemctl', '/kernel/drv/vmmemctl', \%patch, $cFlagTimestamp); undef %patch; install_file($dir . $osDir . '_64/vmmemctl', '/kernel/drv/amd64/vmmemctl', \%patch, $cFlagTimestamp); db_add_answer('VMMEMCTL_CONFED', 'yes'); return 'yes'; } if ($module eq 'vmhgfs' || $module eq 'vmblock') { my $newMinor; # vmhgfs is supported on Solaris 11 if ($minor == 11) { $newMinor = $minor; } else { $newMinor = $osDir; } undef %patch; install_file($dir . $newMinor . '/' . $module, '/kernel/drv/' . $module, \%patch, $cFlagTimestamp); undef %patch; install_file($dir . $newMinor . '_64/' . $module, '/kernel/drv/amd64/' . $module, \%patch, $cFlagTimestamp); # configure_vmhgfs/block() are nice enough to add the VMHGFS_CONFED entry for us return 'yes'; } # if ($module eq 'vmblock') { # my $newMinor; # # if ($minor == 11) { # $newMinor = $minor; # } else { # $newMinor = $osDir; # } # # undef %patch; # install_file($dir . $newMinor . '/vmblock', # '/kernel/drv/vmblock', \%patch, $cFlagTimestamp); # # undef %patch; # install_file($dir . $newMinor . '_64/vmblock', # '/kernel/drv/amd64/vmblock', \%patch, $cFlagTimestamp); # # # configure_vmblock() is nice enough to add the VMBLOCK_CONFED entry for us # return 'yes'; # } if ($module eq 'vmxnet3s') { my $result = 'no'; my $options = ''; # First copy vmxnet3s.conf to /kernel/drv/ undef %patch; install_file($dir . $osFlavorDir . '/vmxnet3s.conf', '/kernel/drv/vmxnet3s.conf', \%patch, $cFlagTimestamp); # Then copy the module to /kernel/drv/ and /kernel/drv/amd64 undef %patch; install_file($dir . $osFlavorDir . '/vmxnet3s', '/kernel/drv/vmxnet3s', \%patch, $cFlagTimestamp); undef %patch; install_file($dir . $osFlavorDir . '_64/vmxnet3s', '/kernel/drv/amd64/vmxnet3s', \%patch, $cFlagTimestamp); unless(solaris_11_or_greater() eq 'yes') { # In Solaris 11, the -n option leaves the driver in a bad state, and # devfsadm will not rescue it. See bug #849803. $options = ' -n'; } # Check if the module is already configured, otherwise run add_drv if (solaris_driver_configured('vmxnet3s', 'pci15ad,7b0') eq 'no') { system(shell_string($gHelper{'add_drv'}) . $options . ' -m \'* 0600 root sys\'' . ' -i \'"pci15ad,7b0"\' vmxnet3s >/dev/null 2>&1'); } $result = 'yes'; db_add_answer('VMXNET3S_CONFED', $result); return $result; } return 'no'; } # # Look for all of the network nodes based on the paths passed in and # copy from the first to the second. In particular, when moving from # the pcnet driver on a 32bit machine to the vmxnet driver, the files # in etc, /etc/hostname.pcnet0, /etc/dhcp.pcn0, ..., need to reflect # the new vmxnet driver: /etc/hostname.vmxnet0, /etc/dhcp.vmxnet0. # sub migrate_network_files { my $index = 0; my $src_base = shift; my $trgt_base = shift; my $Id = shift; my $src = $src_base . $index; while (file_name_exist($src)) { my $trgt = $trgt_base . $index; if ( ! -e $trgt) { system(shell_string($gHelper{'cp'}) . ' ' . $src . ' ' . $trgt); db_add_file($trgt, 0); backup_file_to_restore($src, 'SOLARIS_NET_' . $index . '_' . $Id); } $index++; $src = $src_base . $index; } } sub configure_module_bsd { my $module = shift; my %patch; my $dir = db_get_answer('LIBDIR') . '/modules/binary/FreeBSD'; my $BSDModPath; my $moduleArch; my $moduleConfed = 'no'; my $freeBSDVersion = getFreeBSDVersion(); my $moduleVersion = '0.0'; if (dot_version_compare("$freeBSDVersion", '11.0') >= 0) { print wrap("Skipping " . $module . " because " . vmware_product_name() . " kernel modules are not available for FreeBSD 11 " . "and later.\n\n"); disable_module($module); return 'no'; } elsif (dot_version_compare("$freeBSDVersion", '10.0') >= 0) { $moduleVersion = '10.0'; } elsif (dot_version_compare("$freeBSDVersion", '9.0') >= 0) { $moduleVersion = '9.0'; } elsif (dot_version_compare("$freeBSDVersion", '8.1') >= 0) { $moduleVersion = '8.1'; } elsif (dot_version_compare("$freeBSDVersion", '8.0') >= 0) { $moduleVersion = '8.0'; } elsif (dot_version_compare("$freeBSDVersion", '7.3') >= 0) { $moduleVersion = '7.3'; } elsif (dot_version_compare("$freeBSDVersion", '7.1') >= 0) { $moduleVersion = '7.1'; } elsif (dot_version_compare("$freeBSDVersion", '7.0') >= 0) { $moduleVersion = '7.0'; } elsif (dot_version_compare("$freeBSDVersion", '6.3') >= 0) { $moduleVersion = '6.3'; } else { # If we get here, then tools is not supported. Error out. error ('Tools is not supported on FreeBSD < 6.3. ' . "Detected FreeBSD version $freeBSDVersion.\n"); } $BSDModPath = '/boot/modules'; if (is64BitKernel()) { $moduleArch = "amd64"; } else { $moduleArch = "i386"; } if ($module eq 'vmmemctl') { undef %patch; install_file($dir . $moduleVersion . '-' . $moduleArch . '/vmmemctl.ko', $BSDModPath . '/vmmemctl.ko', \%patch, $cFlagTimestamp); $moduleConfed = 'yes'; db_add_answer('VMMEMCTL_CONFED', $moduleConfed); } elsif ($module eq 'vmhgfs') { undef %patch; install_file($dir . $moduleVersion . '-' . $moduleArch . '/vmhgfs.ko', $BSDModPath . '/vmhgfs.ko', \%patch, $cFlagTimestamp); $moduleConfed = 'yes'; db_add_answer('VMHGFS_CONFED', 'yes'); db_add_answer('VMHGFS_CONFED_FUSE', 'no'); } elsif ($module eq 'vmblock') { undef %patch; install_file($dir . $moduleVersion . '-' . $moduleArch . '/vmblock.ko', $BSDModPath . '/vmblock.ko', \%patch, $cFlagTimestamp); $moduleConfed = 'yes'; db_add_answer('VMBLOCK_CONFED', 'yes'); db_add_answer('VMBLOCK_CONFED_FUSE', 'no'); } return $moduleConfed; } # Create a device name sub configure_dev { # Call the below function with 0 flags to ensure we don't timestamp file configure_dev_flags(shift, shift, shift, shift, 0); } sub configure_dev_flags { my $name = shift; my $major = shift; my $minor = shift; my $chr = shift; my $flags = shift; my $type; my $typename; if ($chr == 1) { $type = 'c'; $typename = 'character'; } else { $type = 'b'; $typename = 'block'; } 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) && ($chr == 1 && ($statbuf[2] & 0020000) != 0 || $chr == 0 && ($statbuf[2] & 0020000) == 0)) { # The device is already correctly configured return; } } if (get_answer('This program wanted to create the ' . $typename . ' 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"); } } if (system('rm -f ' . shell_string($name) . ' && ' . shell_string($gHelper{'mknod'}) . ' ' . shell_string($name) . ' ' . shell_string($type) . ' ' . shell_string($major) . ' ' . shell_string($minor))) { error('Unable to create the ' . $typename . ' device ' . $name . ' with ' . 'major number ' . $major . ' and minor number ' . $minor . '.' . "\n\n"); } safe_chmod(0600, $name); db_add_file($name, $flags); } # Determine whether /dev is populated dynamically sub is_dev_dynamic { if (-e '/dev/.devfs' || -e '/dev/.udev.tdb' || -e '/dev/.udevdb' || -e '/dev/.udev') { # Either the devfs" or "udev" filesystem is mounted on the "/dev" directory return 'yes'; } return 'no'; } # # change_scsi_timeout # # Changes the timeout value of all SCSI devices to the one specified # sub change_scsi_timeout { my $timeout = shift; my @files; my $file; # Now we need to adjust the timeout values here so the user doesn't need # to reboot their machine before this takes effect. @files = </sys/block/sd*>; foreach $file (@files) { # If the block device has a timeout file in its devices folder, then # set it to $timeout $file = $file . '/device/timeout'; if (-e $file) { set_file_contents($file, $timeout); } } } sub get_udev_tags { my $adm = internal_which('udevadm'); chomp($adm); if ($adm) { $adm = "$adm info"; } else { # Look for udevinfo $adm = internal_which('udevinfo'); chomp($adm); } if (! $adm) { return {}; } my $device=`$adm -q path -n /dev/sda`; my $tree = `$adm -a -p $device`; my @scsiNodes = (); my @nodeText = (); while ($tree =~ m/looking at.+?device (.*?)\n\n/sg) { my $txt = $1; if ($txt =~ m/{vendor}=="VMware/is) { # Get the udev device path from this node if ($txt =~ m/^'(.*?)':/s) { unshift(@scsiNodes, $1); unshift(@nodeText, $txt); } else { die "Could not find Parent Node...\n"; } } } my $scsi = ""; my $vendor = ""; my $model = ""; for (my $i=0; $i<=$#scsiNodes; $i++) { # Scan text for specific tags my $node = $scsiNodes[$i]; my $txt = $nodeText[$i]; # SCSI tag if ($txt =~ m/(BUS=="scsi")/) { $scsi = $1; } if ($txt =~ m/(SUBSYSTEMS=="scsi")/) { $scsi = $1;; } # Vendor tag if ($txt =~ m/\s+(\S+{vendor}==".*?")/) { $vendor = $1; } # Model tag if ($txt =~ m/\s+(\S+{model}==".*?")/) { $model = $1; } } return ("scsi" => $scsi, "vendor" => $vendor, "model" => $model); } # # configure_udev_scsi # # Adds a Udev rule for GOS SCSI devices to change the timeout # from 60 to 180, and then modifies the timeout so as not to # require a reboot. For more info, see Bug 271286 # sub configure_udev_scsi { # Check to make sure the Kernel Version is greater than 2.6.13 if ($gSystem{'version_integer'} < kernel_version_integer(2, 6, 13)) { return; } # Make sure we have SCSI disk before going any further if (! -e "/dev/sda") { return; } my %tags = get_udev_tags(); my $temp_dir = make_tmp_dir($cTmpDirPrefix); my $udev_file = "$temp_dir/99-vmware-scsi-udev.rules"; open FOUT, ">$udev_file"; print FOUT <<EOF; # # VMware SCSI devices Timeout adjustment # # Modify the timeout value for VMware SCSI devices so that # in the event of a failover, we don't time out. # See Bug 271286 for more information. EOF print FOUT 'ACTION=="add", ' . $tags{'scsi'} . ', ' . $tags{'vendor'} . ', ' . $tags{'model'} . ', RUN+="/bin/sh -c \'echo 180 >/sys$DEVPATH/timeout\'"' . "\n\n"; close FOUT; # Install the file installUdevRule($udev_file); # Now change the scsi timeout value to 180 change_scsi_timeout(180); } # configureDeviceKitVmmouse # # Installs the necessary files to make vmmouse work with # device kit. First it checks to make sure that no vmmouse rules file # exists. If one doesn't exist, then it installs the rule we provide. # Also install the Xorg mouse file if it doesn't exist. # sub configureDeviceKitVmmouse { my $dkRulesSrc = join('/', db_get_answer('LIBDIR'), 'configurator/udev/69-vmware-vmmouse.rules'); my $regex = qr/vmmouse\.rules/; my $ruleFound = searchForUdevRule($regex); installUdevRule($dkRulesSrc) if (not $ruleFound); # Now check for the X conf file for vmmouse and install it if we # don't find the file we are looking for. my $dkVmmouseConf = join('/', db_get_answer('LIBDIR'), 'configurator/xorg.conf.d/vmmouse.conf'); #xorg.conf.d path for distros like ubuntu10.04 my $dstDir = '/usr/lib/X11/xorg.conf.d/'; if (! -d $dstDir){ # xorg.conf.d path for distros like debian 6 $dstDir = '/usr/share/X11/xorg.conf.d'; } my $dst = join('/', $dstDir, '10-vmmouse.conf'); my %patch; undef %patch; # Currently this only applies to Ubuntu. Will need to adapt this # as more distros move to DeviceKit. $regex = qr/vmmouse\.conf/; if (-d $dstDir and not searchDirsForMatchingFile($regex, $dstDir)) { install_file($dkVmmouseConf, $dst, \%patch, $cFlagTimestamp); } } # searchForUdevRule # # Searches udev rules for file names that match the given regex # @param - Regex to match files against. # @returns - True if a match was found, false otherwise. # sub searchForUdevRule { my $regex = shift; my @searchDirs = ("/lib/udev/rules.d/", "/etc/udev/rules.d"); return searchDirsForMatchingFile($regex, @searchDirs); } # searchDirsForMatchingFile # # Searches a given list of directories for a file matching the # provided regex. Return a list of file names that match. # sub searchDirsForMatchingFile { my $regex = shift; my @searchDirs = @_; my @matches; foreach my $dir (@searchDirs) { foreach my $file (internal_ls($dir)) { unshift(@matches, "$dir/$file") if ($file =~ $regex); } } return @matches; } # installUdevRule # # Installs a udev rule to the proper location. # @param - The path to the rule to install # @returns - True if successful, false otherwise. # sub installUdevRule { my $src = shift; my $ruleName = internal_basename($src); my $dstDir = '/etc/udev/rules.d'; my $dst = join('/', $dstDir, $ruleName); my %patch; undef %patch; if (not -e $src or not -d $dstDir) { print STDERR "Warning: Could not find $src or $dstDir.\n\n"; return 0; } return install_file($src, $dst, \%patch, $cFlagTimestamp); } # Configuration related to the monitor sub configure_mon { if (configure_module('vmmon') eq 'no') { module_error(); } if (is_dev_dynamic() eq 'yes') { # Either the devfs" or "udev" filesystem is mounted on the "/dev" directory, # so the "/dev/vmmon" block device file is magically created/removed when the # "vmmon" module is loaded/unloaded (was bug 15571 and 72114) } else { configure_dev('/dev/vmmon', 10, 165, 1); } } # Configuration related to parallel ports sub configure_pp { my $i; # The parport numbering scheme in 2.2.X is confusing: # Because devices can be daisy-chained on a port, the first port # (/proc/parport/0) is /dev/parport0, but the second one (/proc/parport/1) # is /dev/parport16 (not /dev/parport1), and so on... # This message is wrong. I have found no evidence for this. # On all the linux machines that I've looked at /dev/parport1 is the 2nd port # That's my story and I'm sticking to it - DavidE if (is_dev_dynamic() eq 'no') { for ($i = 0; $i < 4; $i++) { configure_dev_flags('/dev/parport' . $i, 99, $i, 1, 0x1); } } } # Configuration of the vmmemctl tools device sub configure_vmmemctl { my $result; if (vmware_product() eq 'tools-for-freebsd') { $result = configure_module_bsd('vmmemctl'); } elsif (vmware_product() eq 'tools-for-solaris') { $result = configure_module_solaris('vmmemctl'); } else { # First check to make sure we should install this module. $result = mod_pre_install_check('vmmemctl'); if ($result eq 'yes') { $result = configure_module('vmmemctl'); if ($result eq 'no') { query('The memory manager driver (vmmemctl module) is used by ' . 'VMware host software to efficiently reclaim memory from a ' . 'virtual machine.' . "\n" . 'If the driver is not available, VMware host software may ' . 'instead need to swap guest memory to disk, which may reduce ' . 'performance.' . "\n" . 'The rest of the software provided by ' . vmware_product_name() . ' is designed to work independently of ' . 'this feature.' . "\n" . 'If you want the memory management feature,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } } module_post_configure('vmmemctl', $result); } } ## # is_esx_virt_env # # Returns true if the VM is runing in an ESX virtual environment, # false otherwise. # @returns - 1 (true) if in ESX, 0 (false) otherwise # sub is_esx_virt_env { my $ans = 0; my $sbinDir = db_get_answer('SBINDIR'); my $checkvm = vmware_check_vm_app_name(); if (-x $checkvm) { my $output = `$checkvm -p 2>&1`; $ans = 1 if ($output =~ m/ESX Server/); } return $ans; } ## # disable_module # # Sets the appropriate flags to disable the module. # @returns - Nothing useful. # sub disable_module { my $mod = shift; set_manifest_component("$mod", 'FALSE'); db_add_answer(uc("$mod") . '_CONFED', 'no'); return; } ## # # Configuration of the vmhgfs tools device # sub vmware_vmhgfs_use_fuse_app_name { my $bindir = db_get_answer('BINDIR'); return $bindir . '/vmhgfs-fuse'; } # Checks for FUSE availability on the system # @returns - 0 - enabled, 1 - OS not supported, 2 - fuse not installed. # sub vmware_vmhgfs_can_use_fuse { # Default is fuse is disabled my $useFuse = 1; my $checkFuse = vmware_vmhgfs_use_fuse_app_name(); if (-x $checkFuse) { $useFuse = system("$checkFuse -e > /dev/null 2>&1"); $useFuse >>= 8; } else { print wrap("WARNING: " . "It appears your system is missing the required " . $checkFuse . "\n\n", 0); } return $useFuse; } sub configure_vmhgfs { # vmhgfs is supported only since 2.4.0 Linux kernels and Solaris 10 and up if ((vmware_product() eq 'tools-for-linux' && $gSystem{'version_integer'} >= kernel_version_integer(2, 4, 0)) || (vmware_product() eq 'tools-for-solaris' && solaris_10_or_greater()) ) { # By default we don't want HGFS installed in guests runnning on ESX virtual environments # since its useless there. However we want HGFS to be installed by default on VMs # running in WS/Fusion virtual environments. Hence ask users and set the default answer based # on whether or not we are running in an ESX environment vs a WS/Fusion environment. my $defAns; if (is_esx_virt_env()) { $defAns = 'no'; } else { $defAns = $open_vm_compat ? $gOpenVmCompatHGFSDefault : 'yes'; } my $hgfsQ = 'The VMware Host-Guest Filesystem allows for shared folders between the ' . 'host OS and the guest OS in a Fusion or Workstation virtual environment. ' . 'Do you wish to enable this feature?'; if (get_persistent_answer($hgfsQ, 'ENABLE_HGFS', 'yesno', $defAns) eq 'no') { # Then disable HGFS. disable_module('vmhgfs'); db_add_answer('VMHGFS_CONFED_FUSE', 'no'); return; } my $result; my $dispInstallMsg = 1; if (vmware_product() eq 'tools-for-linux') { # We use the HGFS Fuse binary which contains the system compatibility logic. # Using the FUSE client itself with "-e" option centralizes the test for all use-cases. my $useFuse = vmware_vmhgfs_can_use_fuse(); if ($useFuse == 0) { db_add_answer('VMHGFS_CONFED_FUSE', 'yes'); db_add_answer('VMHGFS_CONFED', 'no'); return; } else { if ($useFuse == 2) { print wrap("WARNING: " . "It appears your system does not have the required FUSE " . "packages installed. The VMware Host-Guest filesystem " . "requires the fuse packages and its libraries to " . "function properly. Please install the FUSE or " . "fuse-utils package using your systems package " . "management utility and re-run this script in " . "order to enable the VMware Host-Guest filesystem. " . "\n\n", 0); return; } } if (mod_pre_install_check('vmhgfs') eq 'yes') { if (create_dir('/mnt/hgfs', $cFlagDirectoryMark | $cFlagFailureOK) != $cCreateDirFailure) { $result = configure_module('vmhgfs'); } else { $result = 'no'; my $msg = "Could not create the '/mnt/hgfs' directory. Please make sure " . "it is writeable and/or not currently in use.\n"; print wrap($msg, 0); } configure_updatedb() if ($result eq 'yes'); } else { # Failed the preinstall check. result has to equal 0, but don't display # the message about installing the driver as it is already installed. $result = 'no'; $dispInstallMsg = 0; } } elsif (vmware_product() eq 'tools-for-solaris') { # It's common to mount over /mnt in Solaris so we use /hgfs if (create_dir('/hgfs', $cFlagDirectoryMark | $cFlagFailureOK) != $cCreateDirFailure) { symlink_if_needed('/hgfs', '/mnt/hgfs'); $result = configure_module_solaris('vmhgfs'); } else { $result = 'no'; my $msg = "Could not create the '/hgfs' directory.\n"; print wrap($msg, 0); } } if ($result eq 'no' and $dispInstallMsg != 0) { my $msg = 'The filesystem driver (vmhgfs module) is used only for the ' . 'shared folder feature. The rest of the software provided by ' . vmware_product_name() . ' is designed to work independently of ' . 'this feature.' . "\n\n" . 'If you wish to have the shared folders ' . 'feature,' . $cModulesBuildEnv . "\n"; query ($msg, ' Press Enter key to continue ', 0); } module_post_configure('vmhgfs', $result); } } # Configuration of the vmxnet3 ethernet driver sub configure_vmxnet3 { my $result = 'no'; # vmxnet3 is supported for kernels 2.6.10 and higher (RHEL4 unsupported) if ($gSystem{'version_integer'} <= kernel_version_integer(2, 6, 9)) { query('You are running Linux version ' . $gSystem{'version_utsclean'} . '. The driver for the VMXNET 3 virtual network card is ' . 'only available for 2.6.10 and later kernels.' . "\n", ' Press Enter key to continue ', 0); } else { $result = mod_pre_install_check('vmxnet3'); if ($result eq 'yes') { $result = configure_module('vmxnet3'); if ($result eq 'no') { query('The driver for the VMXNET 3 virtual ' . 'network card is used only for ' . 'our advanced networking interface. ' . 'The rest of the software provided by ' . vmware_product_name() . ' is designed to work independently of ' . 'this feature.' . "\n" . 'If you wish to have the advanced network driver enabled,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } } } module_post_configure('vmxnet3', $result); } sub configure_vmci { my $result = 'no'; if (!(isDesktopProduct() || vmware_product() eq 'tools-for-linux' )) { return undef; } $result = mod_pre_install_check('vmci'); if ($result eq 'yes') { $result = configure_module('vmci'); if ($result eq 'no') { query('The communication service is used in addition to the ' . 'standard communication between the guest and the host. ' . 'The rest of the software provided by ' . vmware_product_name() . ' is designed to work independently of this feature.' . "\n" . 'If you wish to have the VMCI feature,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } } module_post_configure('vmci', $result); } sub configure_vsock { my $result = 'no'; $result = mod_pre_install_check('vsock'); if ($result eq 'yes') { # vsock needs the vmci module loaded first. # Note, now that we use modconfig to build the modules on tools-for-linux, # we no longer need to load the vmci modules as it is handled automatically # by modconfig. if (vmware_product() ne 'tools-for-linux') { if ( ($gInstallStatus{'vmci'} ne 'other') && ($gInstallStatus{'vmci'} ne 'builtin') && defined(db_get_answer_if_exists('VMCI_CONFED')) && db_get_answer('VMCI_CONFED') ne 'yes') { return 1; } } $result = configure_module('vsock'); if ($result eq 'no') { query("The VM communication interface socket family is used in conjunction " . "with the VM communication interface to provide a new communication " . "path among guests and host. The rest of this software " . "provided by " . vmware_product_name() . " is designed to work " . "independently of this feature. If you wish to have the VSOCK " . "feature " . $cModulesBuildEnv . "\n", " Press the Enter key to continue.", 0); } } module_post_configure('vsock', $result); } sub configure_pvscsi { my $result = 'no'; # We only install pvscsi on RHEL5 and other distributions with kernel versions >= 2.6.32 # bug 622041 if ($gSystem{'version_integer'} >= kernel_version_integer(2, 6, 32) || $gSystem{'uts_release'} =~ /\.el5/) { $result = mod_pre_install_check('pvscsi'); if ($result eq 'yes') { # NOTE: See bug 347401. We do not want to interrupt pvscsi services by # unloading the kernel module. # kmod_unload('pvscsi'); $result = configure_module('pvscsi'); if ($result eq 'no') { query('Unable to compile the pvscsi module. ' . 'If you wish to have the pvscsi feature,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } } } else { print wrap ("The VMware pvscsi module is only supported on kernel " . "version 2.6.32 and newer, or rhel5 distributions.\n", 0); } module_post_configure('pvscsi', $result); } sub configure_vmsync { my $result = 'no'; # vmsync is available on on kernels greater than or equal to 2.6.6 and # unnecessary from 2.6.29 and later thanks to FIFREEZE/FITHAW IOCTLs # (we will keep it for up thru 2.6.31 to be safe/consistent with some # of the upstreamed drivers) if (mod_pre_install_check('vmsync') eq 'yes' and $gSystem{'version_integer'} >= kernel_version_integer(2, 6, 6) and $gSystem{'version_integer'} < kernel_version_integer(2, 6, 32)) { if (get_persistent_answer( 'The VMware FileSystem Sync Driver ' . '(vmsync) allows external third-party backup software ' . 'that is integrated with vSphere to create backups ' . 'of the virtual machine. Do you wish to ' . 'enable this feature?', 'XPRMNTL_VMSYNC', 'yesno', 'no') eq 'yes') { $result = configure_module('vmsync'); if ($result eq 'no') { query('The file system sync driver (vmsync) is only used to create safe ' . 'backups of the virtual machine. The rest of the software ' . 'provided by ' . vmware_product_name() . ' is designed to work independently of this feature.' . "\n" . 'If you wish to have the vmsync feature,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } } } module_post_configure('vmsync', $result); } # # Configure dracut, Fedora (12+)'s and RHEL 6(+?)'s initrd creation # and management mechanism # sub configure_dracut { my $addedDrivers = shift; my $addDriversText = "add_drivers+=\"" . $addedDrivers . "\""; my $dracutConfFile = '/etc/dracut.conf.d/vmware-tools.conf'; # first check if the OS does things the 'dot d' way, the preferred # method. Fedora 12 does not, while the rest mentioned above do. if (-d '/etc/dracut.conf.d/') { if (not open(VMWARETOOLSCONF, ">$dracutConfFile")) { error('Unable to open ' . $dracutConfFile . ' for writing.'); } print VMWARETOOLSCONF $addDriversText; print VMWARETOOLSCONF "\n"; close (VMWARETOOLSCONF); # add file to the db so that it will be removed by our normal uninstall # process db_add_file($dracutConfFile, $cFlagTimestamp); } elsif (-e '/etc/dracut.conf') { # special case for Fedora 12 and those without the ".d" mechanism $dracutConfFile = '/etc/dracut.conf'; my $key = 'add_drivers'; my $regex = '^\s*(' . $key . '\s*=\s*")(.*)(")$'; # first, let's try to edit the file inline, as in configure_initrd_suse if(not addTextToKVEntryInFile($dracutConfFile, $regex, ' ', $addedDrivers)) { # otherwise, append to the file if (not open(DRACUTCONF, ">>$dracutConfFile")) { error("Unable to open " . $dracutConfFile . " to append."); } print DRACUTCONF "\n"; print DRACUTCONF $addDriversText; print DRACUTCONF "\n"; close (DRACUTCONF); } db_add_answer('INITRDMODS_CONF_VALS', $addedDrivers); db_add_answer('INITRDMODS_CONF_KEY', $key); } else { error("Unable to find /etc/dracut.conf or /etc/dracut.conf.d/ ."); } db_add_answer('INITRDMODS_CONF_FILE', $dracutConfFile); } # # Configure suse's initrd modules by appending them to # INITRD_MODULES in /etc/sysconfig/kernel # sub configure_initrd_suse { my $entry = shift; my $file = "/etc/sysconfig/kernel"; my $key = "INITRD_MODULES"; return 0 unless (-e $file); db_add_answer('INITRDMODS_CONF_FILE', $file); db_add_answer('INITRDMODS_CONF_KEY', $key); my $regex = '^\s*(' . $key . '\s*=\s*")(.*)(")$'; my $delim = ' '; # Append the list (string-ified) of necessary initrd modules to the # appropriate variable in the initrd file. if(addTextToKVEntryInFile($file, $regex, $delim, $entry)) { db_add_answer('INITRDMODS_CONF_VALS', $entry); } else { error('Unable to configure the initrd modules file at ' . $file . "."); } } # # Post configuration steps common to every module # sub module_post_configure { my $mod = shift; my $result = shift; if ($result eq 'yes' && vmware_product() eq 'tools-for-linux') { set_manifest_component("$mod", 'TRUE'); } db_add_answer(uc("$mod") . '_CONFED', $result); if (vmware_product() eq 'tools-for-linux') { module_ramdisk_check("$mod"); } } ## # addEntDBList # # Adds an entry to a list within the DB. This function also removes # duplicate entries from the list. # sub addEntDBList { my $dbKey = shift; my $ent = shift; if (not defined $dbKey or $dbKey eq '') { error("Bad dbKey value in addEntDBList.\n"); } if ($ent =~ m/,/) { error("New list entry cannot contain commas.\n"); } my $list = db_get_answer_if_exists($dbKey); my $newList = $list ? join(',', $list, $ent) : $ent; $newList = removeDuplicateEntries($newList, ','); db_add_answer($dbKey, $newList); } # # Update or replace the kernel's boot ramfs so that certain vmware drivers # are loaded at boot # # Create a helper app command to use to restore the initrd on uninstall. # sub configure_kernel_initrd { my $initmodfile; my ($syscmd, $restorecmd, $content, $binary, $style); $syscmd = $restorecmd = $content = $binary = $style = ''; my $kernRel = getKernRel(); # NOTE: In RESTORE_RAMDISK_CMD, use KREL as the template for the # kernel release. Then when tools are removed from the system, # the command in RESTORE_RAMDISK_CMD is run one time for every # kernel entry in RESTORE_RAMDISK_KERNEL. Set # RESTORE_RAMDISK_ONECALL if the RESTORE_RAMDISK_CMD only needs # to be run once. if (-f '/etc/initramfs-tools/modules') { $initmodfile = '/etc/initramfs-tools/modules'; $binary = internal_which('update-initramfs'); if (not defined($binary)) { my $msg = "Cannot find update-initramfs, necessary to update " . "the kernel initrd image.\n"; error($msg); } $syscmd = join(' ',$binary, '-u', '-k', $kernRel); $restorecmd = $binary . ' -u -k all'; db_add_answer('RESTORE_RAMDISK_CMD', "$restorecmd"); db_add_answer('RESTORE_RAMDISK_ONECALL', '1'); foreach my $key (@gRamdiskModules) { $content .= get_module_name($key) ."\n"; } # !!! It is important for the 'dracut' check to appear before the 'mkinitrd' check, # !!! since both exist on Fedora 13 and we want to use dracut in that case. } elsif (internal_which('dracut') ne '') { # Dracut is the replacement for mkinitrd first appearing in Fedora 12. $binary = internal_which('dracut'); $initmodfile = "/etc/dracut.conf"; $style = "dracut"; foreach my $key (@gRamdiskModules) { $content .= get_module_name($key) . ' '; } chop($content); #Redirect unimportant errors and warnings to /dev/null to suppress messages popping out #when generating initramfs image. See bug 1206893 and 1232621. my $image_file = "/boot/initramfs-" . $kernRel . ".img"; $syscmd = join(' ', $binary, '--force', '--add-drivers', "\"$content\"", $image_file, $kernRel, '>/dev/null 2>&1'); db_add_answer('RESTORE_RAMDISK_CMD', join(' ', $binary, '--force', '/boot/initramfs-KREL.img', 'KREL')); addEntDBList('RESTORE_RAMDISK_KERNELS', $kernRel); } elsif (internal_which('mkinitrd') ne '') { $binary = internal_which('mkinitrd'); $style = ''; # See if the version of mkinitrd is the Fedora/Redhat one or the SuSE one. Check # whether the help message mentions "--with=<module>" or not. If it does then # we're using a Redhat/Fedora style mkinitrd. Else SuSE. # # Also, mkinitrd prints out its help message through stderr, hence '2>&1.' if (not open(FILE, $binary. " --help 2>&1 |")) { error("Unable to run 'mkinitrd --help.'\n"); } while (<FILE>) { if (/--with=/) { $style = 'redhat'; last; } } close(FILE); foreach my $key (@gRamdiskModules) { if ($style eq 'redhat') { $content .= " --with=" . get_module_name($key) . " "; } else { $content .= get_module_name($key) . ' '; } } # Oracle UEK hackery # # Oracle is missing {e,o,u}hci-hcd in their uek kernel. If we don't tell # mkinitrd to ignor these modules, then our call will fail and hence the # script will fail. Bug 749933. if ($style eq 'redhat' and $gSystem{'uts_release'} =~ /uek/) { my @cOracleUnlovedModules = ( 'ehci-hcd', 'ohci-hcd', 'uhci-hcd', ); foreach my $mod (@cOracleUnlovedModules) { $content .= "--builtin=$mod "; } } if ($style eq 'redhat') { my $image_file = '/boot/initrd-' . $kernRel . ".img"; $syscmd = join(' ', $binary, '-f', $content, $image_file, $kernRel); db_add_answer('RESTORE_RAMDISK_CMD', join(' ', $binary, '-f', '/boot/initrd-KREL.img', 'KREL')); addEntDBList('RESTORE_RAMDISK_KERNELS', $kernRel); } else { # Assuming this is a SuSE system, you have to specify the kernel image and the # initrd image that you want to remake. If its not a SuSE system, then leave # it the way it was before. my $kernelList = "-k vmlinuz-$kernRel"; my $initrdList = "-i initrd-$kernRel"; $initmodfile = '/etc/sysconfig/kernel'; if ($gSystem{'distribution'} eq 'suse') { $syscmd = join(' ', $binary, $kernelList, $initrdList); } else { $syscmd = $binary; } # SuSE's version of mkinitrd will remake the initrd for all kernels # found in /boot if no -k or -i parameters are passed. db_add_answer('RESTORE_RAMDISK_CMD', $syscmd); db_add_answer('RESTORE_RAMDISK_ONECALL', '1'); } } else { # We can't rebuild the initrd if we get here. Not fatal, but we need # to let the users know about it. print wrap("\n Warning: This script could not find mkinitrd or " . "update-initramfs and cannot remake the initrd file!\n\n", 0); $syscmd = undef; } # Only need to modify the $initmodfile for Ubuntu, SuSE, and Fedora 12 (Dracut) style initrd. if ( defined($initmodfile) && file_name_exist($initmodfile) && defined($content)) { if ($style eq "dracut") { configure_dracut($content); } elsif ($gSystem{'distribution'} eq 'suse') { configure_initrd_suse($content); } else { block_restore($initmodfile, $cMarkerBegin, $cMarkerEnd); block_append_with_db_answer_entry($initmodfile, $content); } } system(shell_string($gHelper{'depmod'}) . ' -a'); # Make the initrd. if (defined $syscmd and $syscmd ne '') { print wrap("Creating a new initrd boot image for the kernel.\n", 0); if (system($syscmd) != 0) { # Check to ensure that the command succeded. If it didn't the system may # not boot. We need to error out if that is the case. error( wrap("ERROR: \"$syscmd\" exited with non-zero status.\n" . "\n" . 'Your system currently may not have a functioning init ' . 'image and may not boot properly. DO NOT REBOOT! ' . 'Please ensure that you have enough free space available ' . 'in your /boot directory and run this configuration ' . "script again.\n\n", 0)); } } } # # This is for module-init-tools (2.6 kernels) and hotplug # The first argument is a complete path to a file which will be read and # overwritten with the result. # The second argument will be only read and should be the system file present # before configuration. # sub configure_pci_dot_handmap { my ($newPciHandmap, $systemPciHandmap) = @_; my $inline; my $emittedVmnics = 0; my $emittedVmxnet = 0; if (not open(SYSHANDMAP, "<$systemPciHandmap")) { error('Unable to open the file "' . $systemPciHandmap . '".' . "\n\n"); } if (not open(NEWHANDMAP, ">$newPciHandmap")) { error('Unable to open the file "' . $newPciHandmap . '".' . "\n\n"); } # Look for matches and selectively replace drivers while (defined($inline = <SYSHANDMAP>)) { if ($inline =~ /^\s*(\w+)\s+(\w+)/) { my ($cmd, $val) = ($1, $2); if ($cmd eq 'vmxnet') { $inline = 'vmxnet\t\t0x000015ad 0x00000720 ' . '0xffffffff 0xffffffff 0x00000000 0x00000000 0x0' . "\n"; $emittedVmxnet = 1; } elsif ($cmd eq 'vmnics') { $inline = 'vmnics\t\t0x00001022 0x00002000 ' . '0xffffffff 0xffffffff 0x00000000 0x00000000 0x0' . "\n"; $emittedVmnics = 1; } } print NEWHANDMAP $inline; } my @output; if ($emittedVmxnet == 0 ) { push @output, "vmxnet\t\t0x000015ad 0x00000720 0xffffffff 0xffffffff 0x00000000 0x00000000 0x0\n"; } if ($emittedVmnics == 0) { push @output, "vmnics\t\t0x00001022 0x00002000 0xffffffff 0xffffffff 0x00000000 0x00000000 0x0\n"; } if (scalar @output) { print NEWHANDMAP "# Added by " . vmware_product_name() . "\n"; print NEWHANDMAP join('', @output); } close (SYSHANDMAP); close (NEWHANDMAP); } ## # configure_updatedb # # Configures updatedb.conf inline to prevent the scanning of file systems # mounted via hgfs # sub configure_updatedb { my @fkPairs = (['/etc/updatedb.conf', 'PRUNEFS'], ['/etc/sysconfig/locate', 'UPDATEDB_PRUNEFS']); my $file; my $key; foreach my $fkPair (@fkPairs) { ($file, $key) = @$fkPair; last if (-e $file); } return 0 unless (-e $file); db_add_answer('UPDATEDB_CONF_FILE', $file); db_add_answer('UPDATEDB_CONF_KEY', $key); my $regex = '^\s*(' . $key . '\s*=\s*")(.*)(")$'; my $delim = ' '; my $entry = 'vmhgfs'; return addTextToKVEntryInFile($file, $regex, $delim, $entry); } my %pci_hash = ( '10222000' => 'pcnet32', '15ad0720' => 'vmxnet', '15ad07b0' => 'vmxnet3', '15ad07c0' => 'pvscsi', '12741371' => 'es1371' ); sub get_devices_list { my $line; my $k; my %dev_counts = (); foreach $k (keys %pci_hash) { $dev_counts{$pci_hash{$k}} = 0; } open (PCI, '</proc/bus/pci/devices') or return undef; while (defined($line = <PCI>)) { $line = lc($line); if ($line =~ /^[0-9a-f]*\t([0-9a-f]*)\t/) { my $name = $pci_hash{$1}; if ($name and defined($dev_counts{$name})) { $dev_counts{$name}++; } } } close PCI; return \%dev_counts; } # Configuration of drivers for PCI devices sub write_module_config { my $modprobe_file = ''; my $result; if (vmware_product() ne 'tools-for-linux') { return; } # PR 848092 - # We special case the vmxnet driver - since we do not support this NIC on # Linux kernels > 3.2, we don't even want to allow the user to clobber. # Just return (with message) in all cases where kernel > 3.2 if ($gSystem{'version_integer'} >= kernel_version_integer(3,3,0)) { print wrap("The vmxnet driver is no longer supported on kernels " . "3.3 and greater. Please upgrade to a newer virtual NIC. " . "(e.g., vmxnet3 or e1000e)\n\n", 0); db_add_answer('VMXNET_CONFED', 'no'); return; } $result = mod_pre_install_check('vmxnet'); if ($result eq 'yes') { $result = configure_module('vmxnet'); if ($result eq 'no') { query('The fast network device driver (vmxnet module) is used only for ' . 'our fast networking interface. ' . 'The rest of the software provided by ' . vmware_product_name() . ' is designed to work independently of ' . 'this feature.' . "\n" . 'If you wish to have the fast network driver enabled,' . $cModulesBuildEnv . "\n", ' Press Enter key to continue ', 0); } else { my $initmodfile = '/etc/initramfs-tools/modules'; if ( -f $initmodfile ) { backup_file_to_restore($initmodfile, 'INITRAMFS_MODULES'); system(shell_string($gHelper{'cp'}). ' ' . $initmodfile . $cBackupExtension . ' ' . $initmodfile); if (not block_match($initmodfile, '^vmxnet$')) { block_append($initmodfile, $cMarkerBegin, "vmxnet\n", $cMarkerEnd); } } } # modprobe looks for module info first in modprobe.d<vmware-tools> # and then in modprobe.conf>. However, SLES9 includes a new # wrinkle: the file modprobe.conf.local. That gets included into # modprobe.conf and SLES9 wants user modified entries in that # local file. # It's very important that modprobe.d is checked *first*. We # want to use it if it exists and doing so makes this work # correctly on RHEL 5. # Need to special-case RHEL 4 because it doesn't actually pay attention to # files in the /etc/modprobe.d/ directory my $isRhel4Rel = system(shell_string($gHelper{'grep'}) . ' ' . "-q 'Red Hat Enterprise Linux .* release 4' /etc/redhat-release " . "> /dev/null 2>&1"); if (file_name_exist('/etc/modprobe.d') && not ($isRhel4Rel == 0)) { $modprobe_file = '/etc/modprobe.d/vmware-tools.conf'; } elsif (file_name_exist('/etc/modprobe.conf.local')) { $modprobe_file = '/etc/modprobe.conf.local'; } elsif (file_name_exist('/etc/modprobe.conf')) { $modprobe_file = '/etc/modprobe.conf'; } elsif (file_name_exist('/etc/modules.conf')) { $modprobe_file = '/etc/modules.conf'; } elsif (file_name_exist('/etc/conf.modules')) { $modprobe_file = '/etc/conf.modules'; } if (($modprobe_file eq '/etc/modprobe.conf.local') || ($modprobe_file eq '/etc/modprobe.conf')) { my $modprobe_command = ''; if ($gSystem{'version_integer'} < kernel_version_integer(2, 6, 22) ) { $modprobe_command .= "install pciehp /sbin/modprobe -q " . "--ignore-install acpiphp; /bin/true\n"; } $modprobe_command .= 'install pcnet32 (/sbin/modprobe -q ' . '--ignore-install vmxnet ; /sbin/modprobe -q ' . '--ignore-install pcnet32 $CMDLINE_OPTS); ' . "/bin/true\n"; # Append modprobe_command to the end of /etc/modprobe.conf(.local), # inside a block that can be removed later. block_append_with_db_answer_entry($modprobe_file, $modprobe_command); } elsif ($modprobe_file eq '/etc/modprobe.d/vmware-tools.conf') { my @netopt = ('install pcnet32 /sbin/modprobe -q --ignore-install vmxnet; ' . '/sbin/modprobe --ignore-install pcnet32 $CMDLINE_OPTS' . "\n"); if (vmware_product() eq 'tools-for-linux' && $gSystem{'version_integer'} < kernel_version_integer(2, 6, 22) ) { push(@netopt, 'install pciehp /sbin/modprobe -q --ignore-install acpiphp;' . "/bin/true\n"); } if (not open(NEWMODCONF, ">$modprobe_file")) { error('Unable to open the file "' . $modprobe_file . '".' . "\n\n"); } print NEWMODCONF "# Created by " . vmware_product_name() . "\n"; print NEWMODCONF join('', @netopt); close(NEWMODCONF); db_add_file($modprobe_file, 0x0); # Older kernels use conf.modules or modules.conf; the required command # is also a little different, which was taken from files in the new # configurator } elsif (file_name_exist('/etc/conf.modules') || file_name_exist('/etc/modules.conf')) { my $modules_file = file_name_exist('/etc/conf.modules') ? '/etc/conf.modules' : '/etc/modules.conf'; my $modconf_command = "pre-install pcnet32 " . "/sbin/modprobe -q vmxnet &> /dev/null || true\n"; # append the modconf_command to modules.conf or conf.modules, # inside a block that can be removed later block_append_with_db_answer_entry($modules_file, $modconf_command); } if (file_name_exist('/etc/hotplug/pci.handmap')) { my $handmap_file = '/etc/hotplug/pci.handmap'; backup_file_to_restore($handmap_file, 'PCI_HANDMAP'); configure_pci_dot_handmap($handmap_file, $handmap_file . $cBackupExtension); } } # The initramfs rebuilding process happens in the # configure_kernel_initrd function, which is called later. Defer # that configuration until then. module_post_configure('vmxnet', $result); } # There is no /usr/X11R6 directory for X window in some distribution like # Fedora 5. Instead binary files are put in /usr/bin. Please refer to bug # 86254. sub xserver_bin { my $path; if (vmware_product() eq 'tools-for-solaris' && -e '/usr/X11/bin') { return '/usr/X11/bin'; } # Search PATH for Xorg then X, in case it is somewhere else. Some OSs put # X in /usr/local/bin, so we use the original path rather than the cut down # one this script normally uses. $path = internal_which('Xorg', 1); if ($path eq '') { $path = internal_which('X', 1) } if ($path ne '') { # Only return path, so remove file name. return internal_dirname($path); } if (-e '/usr/X11R6/bin') { return '/usr/X11R6/bin'; } return ''; } sub xserver_xorg { return xserver_bin() . '/Xorg'; } sub xserver4 { return xserver_bin() . '/XFree86'; } sub xconfig_file_abs_path { my $xconfig_path = shift; my $xconfig_file_name = shift; return $xconfig_path . '/' . $xconfig_file_name; } # # path_compare(dir, path1, path2) # # Compare the two paths, and return true if they are identical # Evaluate the paths with respect to the passed in directory # sub path_compare { my ($dir, $path1, $path2) = @_; # Prepend directory for relative paths $path1 =~ s|^([^/])|$dir/$1|; $path2 =~ s|^([^/])|$dir/$1|; # Squash out ..'s in paths while ($path1 =~ /\/.*\/\.\.\//) { $path1 =~ s|/[^/]*/\.\./|/|; } while ($path2 =~ /\/.*\/\.\.\//) { $path2 =~ s|/[^/]*/\.\./|/|; } # Squash out .'s in paths while ($path1 =~ /\/\.\//) { $path1 =~ s|/\./|/|; } while ($path2 =~ /\/\.\//) { $path2 =~ s|/\./|/|; } # Squash out //'s in paths while ($path1 =~ /\/\//) { $path1 =~ s|//|/|; } while ($path2 =~ /\/\//) { $path2 =~ s|//|/|; } if ($path1 eq $path2) { return 'yes'; } else { return 'no'; } } # like readlink(), but return the filename # when it's not actually a link: # if file is a directory, make sure to pass it w/out trailing slash. sub linkdest { my $file = shift; my $dest = readlink($file); if(defined $dest) { return $dest; } return $file; } # check_link # Checks that a given link is pointing to the given file. sub check_link { my $file = shift; my $link = shift; my $linkDest; my $dirname; $linkDest = readlink($link); if (!defined $linkDest) { return 'no'; } $dirname = internal_dirname($link); return path_compare($dirname, $linkDest, $file); } # Install one link, symbolic or hard sub install_link { my $symbolic = shift; my $to = shift; my $name = shift; my $default_overwrite = 'yes'; if (@_ >= 1) { $default_overwrite = shift; } uninstall_file($name); if (file_check_exist($name, $default_overwrite)) { # must not be 'yes' or 'no' because we don't install # but it's not an error either: return 'skip'; } # The file could be a link to another location. Remove it unlink($name); if ($symbolic) { if (not symlink($to, $name)) { return 'no'; } } else { if (not link($to, $name)) { return 'no'; } } db_add_file($name, 0); return 'yes'; } sub install_symlink { my $to = shift; my $from = shift; my $default_overwrite = 'yes'; if (@_ >= 1) { $default_overwrite = shift; } my $result = install_link(1, $to, $from, $default_overwrite); if ($result eq 'no') { error('Unable to create symlink "' . $from . '" pointing to file "' . $to . '".' . "\n\n"); } return $result; } sub install_hardlink { my $to = shift; my $from = shift; return install_link(0, $to, $from); } my $gLinkCount = 0; sub symlink_if_needed { my $file = shift; my $link = shift; if (file_name_exist($file)) { if (-l $link && check_link($file, $link) eq 'yes') { return; } $gLinkCount = $gLinkCount + 1; backup_file_to_restore($link, 'LINK_' . $gLinkCount); install_symlink($file, $link); } } sub set_uid_X_server { my $x_server_file = shift; if (!-u $x_server_file) { safe_chmod(04711, $x_server_file); } } sub getXorgVersionAll { my $packedVersion = direct_command(shell_string(xserver_xorg()) . ' -version 2>&1'); my $xorgServerVersion; if ($packedVersion =~ /X Protocol Version 11.* Release (\d+\.\d+)/) { $packedVersion = $1 ? $1 : '0.0.0'; } elsif ($packedVersion =~ /X Server (\d+\.\d+\.?\d*)/) { $packedVersion = $1 ? $1 : '0.0.0'; $xorgServerVersion = $packedVersion; } my ($xorgMajorVer, $xorgMinorVer, $xorgSubVer) = split_X_version($packedVersion); if (!defined($xorgSubVer)) { $xorgSubVer = 0; } # The 1.3.0 release of the X-server had a little goof where it would say # X Window System, Release 1.3.0. so $major == 1 and $minor == 3. But # truly, it's a standalone X server release that came out after Xorg 7.2. # Similarly, Release 1.4.0 came out as part of Xorg 7.3. # # See bug#185281 for all the deets. if ($xorgMajorVer == 1) { $packedVersion = "7." . ($xorgMinorVer - 1) . "." . $xorgSubVer; } return ($packedVersion, $xorgServerVersion); } sub split_X_version { my $xversionAll = shift; my $major; my $minor; my $sub; if ($xversionAll =~ /(\d+)\.(\d+)\.?(\d*)/) { $major = $1; $minor = $2; $sub = $3 eq '' ? 0 : $3; } else { $major = 0; $minor = 0; $sub = 0; } return ($major, $minor, $sub); } sub fix_X_link { my $x_version = shift; my $x_server_link; my $x_server_link_bin = xserver_bin() . '/X'; my $x_wrapper_file_name = 'Xwrapper'; my $x_wrapper_file = xserver_bin() . '/' . $x_wrapper_file_name; my $x_server_file; my $x_server_file_name; if ($x_version == 4) { $x_server_file = xserver4(); } elsif ($x_version == 6) { $x_server_file = xserver_xorg(); } elsif ($x_version == 7) { $x_server_file = xserver_xorg(); } $x_server_file_name = internal_basename($x_server_file); # Case 1: # In this case, the Xwrapper is used if /etc/X11/X exists (could be broken) # _and_ /usr/X11R6/bin/X points to Xwrapper. # In this case, the Xwrapper will execute setuid anything /etc/X11/X # is pointing to. So /etc/X11/X has to be pointing to the correct X # server, this is XFree86 if XFree 4 is used. # WARNING: In this case, someone could very easily create a link /etc/X11/X # pointing to the Xwrapper, which, of course creates and infinite loop. # On SuSE, this mechanism is completely broken because Xwrapper tries to run # /usr/X11R6/bin/X ! # In general, The wrapper is stupid. $x_server_link = '/etc/X11/X'; if (-l $x_server_link && check_link($x_wrapper_file, $x_server_link_bin) eq 'yes') { symlink_if_needed($x_server_file, $x_server_link); set_uid_X_server($x_server_file); return; } # Case 2: # This case is often encountered on a SuSE system. # Where /var/X11R6/bin/X is a little like /etc/X11/X but the Xwrapper is # never used on a SuSE system, of course, there could be special cases. # We might be tempted to zap the use of this var place # but startx checks for X link and refuses to start if not present in var. # Of course, it doesn't check where it points to :-) $x_server_link = '/var/X11R6/bin/X'; if (-d internal_dirname($x_server_link)) { symlink_if_needed($x_server_file, $x_server_link); symlink_if_needed($x_server_link, $x_server_link_bin); set_uid_X_server($x_server_file); return; } # Case 3: # All the remaining cases, where the /usr/X11R6/bin/X bin link should be # pointing to a setuid root X server. $x_server_link = '/usr/X11R6/bin/X'; symlink_if_needed($x_server_file, $x_server_link_bin); set_uid_X_server($x_server_file); } # Checks for versioning information in both the system module # and the shipped module. If it finds information in the sytem # module, it compares it against the version information of the # shipped module and will use whatever module is newer. # Returns 1 if it uses the shipped module (and 0 otherwise). sub install_x_module { my $shippedMod = shift; my $systemMod = shift; my $modinfo = internal_which('modinfo'); my $shippedModVer = ''; my $systemModVer = ''; my $installShippedModule = 0; my $line; if (not -r $shippedMod) { error("Could not read $shippedMod\n"); } if ("$modinfo" ne '' and -r "$systemMod") { open (SHIPPED_MOD_VER, "$modinfo $shippedMod |"); open (SYSTEM_MOD_VER, "$modinfo $systemMod |"); foreach $line (<SHIPPED_MOD_VER>) { if ($line =~ /version: +([0-9\.]+)/) { $shippedModVer = "$1"; last; } } foreach $line (<SYSTEM_MOD_VER>) { if ($line =~ /version: +([0-9\.]+)/) { $systemModVer = "$1"; last; } } close (SHIPPED_MOD_VER); close (SYSTEM_MOD_VER); chomp ($shippedModVer); chomp ($systemModVer); if ("$systemModVer" eq '' or dot_version_compare ("$shippedModVer", "$systemModVer") > 0) { # Then the shipped module is newer than sytem module. $installShippedModule = 1; } } else { # If it has no version, assume the one we ship is newer. $installShippedModule = 1; } if ($gOption{'clobber-xorg-modules'} or $installShippedModule) { install_x_module_no_checks($shippedMod, $systemMod); return 1; } return 0; } sub install_x_module_no_checks { my $shippedMod = shift; my $systemMod = shift; my %patch; undef %patch; # Ensure we have a unique backup suffix for this file. # Also strip off anything sh wouldn't like. Bug 502544 my $bkupExt = internal_basename($systemMod); $bkupExt =~ s/^(\w+).*$/$1/; backup_file_to_restore($systemMod, $bkupExt); install_file ("$shippedMod", "$systemMod", \%patch, 1); } sub xorg { my $xconfig_path = '/etc/X11'; my $xconfig_file_name = 'xorg.conf'; my $xversion = 6; my $xversionAll = ''; my $xorgServerVersion = ''; my $xserver_link = ''; my $major; my $minor; my $disableHotPlug = 'no'; my $sub; my %p; undef %p; ($xversionAll, $xorgServerVersion) = getXorgVersionAll(); if (defined $ENV{'XORGCONFIG'} && file_name_exist('/etc/X11/' . $ENV{'XORGCONFIG'})) { $xconfig_path = '/etc/X11'; $xconfig_file_name = $ENV{'XORGCONFIG'}; } elsif (defined $ENV{'XORGCONFIG'} && file_name_exist('/usr/X11R6/etc/X11/' . $ENV{'XORGCONFIG'})) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = $ENV{'XORGCONFIG'}; } elsif (file_name_exist('/etc/X11/xorg.conf-4')) { $xconfig_path = '/etc/X11'; $xconfig_file_name = 'xorg.conf-4'; } elsif (file_name_exist('/etc/X11/xorg.conf')) { $xconfig_path = '/etc/X11'; $xconfig_file_name = 'xorg.conf'; } elsif (file_name_exist('/etc/xorg.conf')) { $xconfig_path = '/etc'; $xconfig_file_name = 'xorg.conf'; } elsif (file_name_exist('/usr/X11R6/etc/X11/xorg.conf-4')) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = 'xorg.conf-4'; } elsif (file_name_exist('/usr/X11R6/etc/X11/xorg.conf')) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = 'xorg.conf'; } elsif (file_name_exist('/usr/X11R6/lib/X11/xorg.conf-4')) { $xconfig_path = '/usr/X11R6/lib/X11'; $xconfig_file_name = 'xorg.conf-4'; } elsif (file_name_exist('/usr/X11R6/lib/X11/xorg.conf')) { $xconfig_path = '/usr/X11R6/lib/X11'; $xconfig_file_name = 'xorg.conf'; } elsif (file_name_exist('/etc/X11/.xorg.conf') && ! -e '/etc/X11/xorg.conf') { # For Solaris so that we patch the xorg file shipped install_file('/etc/X11/.xorg.conf', '/etc/X11/xorg.conf', \%p, 0); $xconfig_path = '/etc/X11'; $xconfig_file_name = 'xorg.conf'; } print wrap("\n\n Detected " . ($xorgServerVersion ? "X server version $xorgServerVersion" : "X version $xversionAll") . "\n\n", 0); ($major, $minor, $sub) = split_X_version($xversionAll); # vmmouse binary shipped with some distribution is buggy # Input hotplug needs to be turned off for X Server > 1.4.0. # The workaround is to add # Option "NoAutoAddDevices" # in ServerFlags section for build 1.4.0 and upwards. # See 291453 and # http://docs.fedoraproject.org/release-notes/f9/en_US/sn-Desktop.html#vmmouse-driver # Release 1.4.0 came out as part of Xorg 7.3 if ($major == 7 && $minor >= 3) { $disableHotPlug = 'yes'; } # If there is an existing driver, replace it by ours. if ($major == 6) { # If there is an existing driver replace it by ours, backing up the existing driver. # Install the drivers. if ($minor == 7) { install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.7.x' . ($gIs64BitX ? '_64' : '') . '/vmware_drv.o', $gXVideoDriverFile); install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.7.x' . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv.o', $gXMouseDriverFile); if (vmware_product() eq 'tools-for-linux') { if (!$gIs64BitX) { set_manifest_component('svga67', 'TRUE'); set_manifest_component('vmmouse67', 'TRUE'); } else { set_manifest_component('svga67_64', 'TRUE'); set_manifest_component('vmmouse67_64', 'TRUE'); } } } elsif ($minor == 8) { # Solaris is an early adopter and is using .so drivers on 6.8.x my $suffix = vmware_product() eq 'tools-for-solaris' ? '.so' : '.o'; install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.8.x' . ($gIs64BitX ? '_64' : '') . '/vmware_drv' . $suffix, $gXVideoDriverFile); install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.8.x' . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv' . $suffix, $gXMouseDriverFile); if (vmware_product() eq 'tools-for-linux') { if (!$gIs64BitX) { set_manifest_component('svga68', 'TRUE'); set_manifest_component('vmmouse68', 'TRUE'); } else { set_manifest_component('svga68_64', 'TRUE'); set_manifest_component('vmmouse68_64', 'TRUE'); } } } elsif ($minor == 9 && (vmware_product() eq 'tools-for-solaris' || vmware_product() eq 'tools-for-linux')) { # The 7.0 drivers work on 6.9.x as well (see bug 92501) # gxMouseDriverFile and gxVideoDriverFile have already been set for Solaris # by configure_X(). Use xorg paths for 6.9 instead of old XFree ones. if (vmware_product() ne 'tools-for-solaris') { my $xorg_modules_dir = xorg_find_modules_dir(); $gXMouseDriverFile = $xorg_modules_dir . '/input/vmmouse_drv.so'; $gXVideoDriverFile = $xorg_modules_dir . '/drivers/vmware_drv.so'; $gXVideoDriverLegacyFile = $xorg_modules_dir . '/drivers/vmwlegacy_drv.so'; install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/7.0' . ($gIs64BitX ? '_64' : '') . '/vmwlegacy_drv.so', $gXVideoDriverLegacyFile); } install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/7.0' . ($gIs64BitX ? '_64' : '') . '/vmware_drv.so', $gXVideoDriverFile); install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/7.0' . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv.so', $gXMouseDriverFile); } elsif ($minor == 9 && vmware_product() eq 'tools-for-freebsd') { install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.9' . ($gIs64BitX ? '_64' : '') . '/vmware_drv.so', $gXVideoDriverFile); install_x_module(db_get_answer('LIBDIR') . '/configurator/XOrg/6.9' . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv.so', $gXMouseDriverFile); } else { print wrap("\n\n No drivers for " . ($xorgServerVersion ? "X server version $xorgServerVersion" : "X version $xversionAll") . "\n\n", 0); $gNoXDrivers = 1; # Use this variable to alert about missing drivers } fix_X_link('6'); } elsif ($major == 7 && $minor >= 0 && $minor <= 6) { my $compat = $minor; # use 7.1 drivers for 7.2 if ($minor == 2 && vmware_product() ne 'tools-for-solaris') { $compat = 1; } # gxMouseDriverFile and gxVideoDriverFile have already been set for Solaris # by configure_X(). if (vmware_product() ne 'tools-for-solaris') { my $xorg_modules_dir = xorg_find_modules_dir(); $gXMouseDriverFile = $xorg_modules_dir . '/input/vmmouse_drv.so'; $gXVideoDriverFile = $xorg_modules_dir . '/drivers/vmware_drv.so'; } # If there is an existing driver replace it by ours, backing up # the existing driver. # Just in case the destination directories don't exist. safe_mkdir(internal_dirname($gXVideoDriverFile)); safe_mkdir(internal_dirname($gXMouseDriverFile)); # Install the drivers. my %p; undef %p; # 7.3.99 is a special case under Linux with a special driver if ((vmware_product() eq 'tools-for-linux') && ($major == 7) && ($minor == 3) && ($sub == 99)) { install_x_module(db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat.99" . ($gIs64BitX ? '_64' : '') . '/vmware_drv.so', $gXVideoDriverFile); install_x_module(db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat.99" . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv.so', $gXMouseDriverFile); } else { # For minor versions > 5, if the sub is == 99, assume that it's a pre-release for # the next version of xorg-server and treat it as the next version. # The minor version of 5 was chosen to prevent regressions from appearing # in code that is known to work with older versions of xorg-server if ((vmware_product() eq 'tools-for-linux') && ($minor > 5) && ($sub == 99)) { $compat ++; print wrap("Detected a pre-release version of Xorg X server.\n"); } my $xorg_modules_dir = xorg_find_modules_dir(); my $xorgModSrcDir32 = db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat"; my $xorgModSrcDir64 = $xorgModSrcDir32 . '_64'; my $xorgModSrcDir = $gIs64BitX ? $xorgModSrcDir64 : $xorgModSrcDir32; # Now check to make sure the drivers exist. if ( -d $xorgModSrcDir ) { if (vmware_product() eq 'tools-for-solaris') { # Need to attempt to install both 32 and 64 bit versions of the # xorg drivers on Solaris. # 32 bit my $xorgModsDriverDir32 = join('/', $xorg_modules_dir, 'drivers'); my $xorgModsInputDir32 = join('/', $xorg_modules_dir, 'input'); install_x_module($xorgModSrcDir32 . '/vmware_drv.so', $xorgModsDriverDir32 . '/vmware_drv.so'); install_x_module($xorgModSrcDir32 . '/vmmouse_drv.so', $xorgModsInputDir32 . '/vmmouse_drv.so'); # For 2 and above on Solaris, vmware_drv.so is just a shim which # loads vmwlegacy, so we need to lay those down. if ($compat >= 2) { install_x_module($xorgModSrcDir32 . '/vmwlegacy_drv.so', $xorgModsDriverDir32 . '/vmwlegacy_drv.so'); } # 64 bit. # The 64 bit dest path is the 32 bit dest path with # the amd64 directory appended to the end of the path. my $xorgModsDriverDir64 = join('/', $xorgModsDriverDir32, 'amd64'); my $xorgModsInputDir64 = join('/', $xorgModsInputDir32, 'amd64'); if (-d $xorgModsDriverDir64 and -d $xorgModsInputDir64) { install_x_module($xorgModSrcDir64 . '/vmware_drv.so', $xorgModsDriverDir64 .'/vmware_drv.so'); install_x_module($xorgModSrcDir64 . '/vmmouse_drv.so', $xorgModsInputDir64 .'/vmmouse_drv.so'); # For 2 and above on Solaris, vmware_drv.so is just a shim which # loads vmwlegacy, so we need to lay those down. if ($compat >= 2) { install_x_module($xorgModSrcDir64 . '/vmwlegacy_drv.so', $xorgModsDriverDir64 . '/vmwlegacy_drv.so'); } } } else { install_x_module($xorgModSrcDir . '/vmware_drv.so', $xorg_modules_dir . '/drivers/vmware_drv.so'); install_x_module($xorgModSrcDir . '/vmmouse_drv.so', $xorg_modules_dir . '/input/vmmouse_drv.so'); # For minor >= 6 on FreeBSD and minor >= 0 on Linux, install vmwlegacy. # vmware_drv.so is just a shim which loads vmwlegacy, # so we need to lay those down. if ((vmware_product() eq 'tools-for-freebsd' && $compat >= 6) || (vmware_product() eq 'tools-for-linux')) { install_x_module($xorgModSrcDir . '/vmwlegacy_drv.so', $xorg_modules_dir . '/drivers/vmwlegacy_drv.so'); } } } else { # No Xorg drivers. Stop configuring X. print wrap("\n\n No drivers for " . ($xorgServerVersion ? "X server version $xorgServerVersion" : "X version $xversionAll") . "\n\n", 0); $gNoXDrivers = 1; # Use this variable to alert about missing drivers return ($xversion, xconfig_file_abs_path($xconfig_path, $xconfig_file_name), $xversionAll, $disableHotPlug); } } # Now for all of the HAL configuration. Only attempt to configure HAL if # we are Linux or Solaris and minor version >= 4. if ($compat >= 4 && (vmware_product() eq 'tools-for-linux' || vmware_product() eq 'tools-for-solaris')) { # Install vmmouse_detect always for compat >= 4 backup_file_to_restore('/usr/bin/vmmouse_detect', 'VMMOUSE_DETECT'); install_file(db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat" . ($gIs64BitX ? '_64' : '') . '/vmmouse_detect', '/usr/bin/vmmouse_detect', \%p, 1); # Check if they use HAL. If HAL's dirs are present, install our bits. # Note: The order of directories in @halDirs is important! my $halScript = undef; my $halName = undef; ($halScript, $halName) = get_hal_script_name(); if (defined($halName)) { my @halDirs = ('/usr/lib/hal/scripts', '/usr/lib/hal', '/usr/libexec'); db_add_answer('HAL_RESTART_ON_UNINSTALL', 'no'); foreach my $dir (@halDirs) { if (-d $dir) { backup_file_to_restore("$dir/hal-probe-vmmouse", 'HAL_PROBE_VMMOUSE'); install_file(db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat" . ($gIs64BitX ? '_64' : '') . '/hal-probe-vmmouse', "$dir/hal-probe-vmmouse", \%p, 1); my $vmmouseFDIPath; if (vmware_product() eq 'tools-for-solaris') { $vmmouseFDIPath = '/etc/hal/fdi/policy/' . '20thirdparty/11-x11-vmmouse.fdi'; } else { $vmmouseFDIPath = '/usr/share/hal/fdi/policy/' . '20thirdparty/11-x11-vmmouse.fdi'; } backup_file_to_restore($vmmouseFDIPath, 'VMMOUSE_FDI'); install_file(db_get_answer('LIBDIR') . "/configurator/XOrg/7.$compat" . ($gIs64BitX ? '_64' : '') . '/11-x11-vmmouse.fdi', $vmmouseFDIPath, \%p, 1); restart_hal(); db_add_answer('HAL_RESTART_ON_UNINSTALL', 'yes'); # Don't search for any more HAL directories. last; } } } } if (vmware_product() eq 'tools-for-linux') { if ($compat == 0) { if (!$gIs64BitX) { set_manifest_component('svga70', 'TRUE'); set_manifest_component('vmmouse70', 'TRUE'); } else { set_manifest_component('svga70_64', 'TRUE'); set_manifest_component('vmmouse70_64', 'TRUE'); } } # Use 7.1 driver for 7.1 through 7.3.98 if ($compat == 1) { if (!$gIs64BitX) { set_manifest_component('svga71', 'TRUE'); set_manifest_component('vmmouse71', 'TRUE'); } else { set_manifest_component('svga71_64', 'TRUE'); set_manifest_component('vmmouse71_64', 'TRUE'); } } # Use 7.3 driver for most 7.3, # Use 7.3.99 driver for 7.3.99 only. if ($compat == 3) { if ($sub == 99) { if (!$gIs64BitX) { set_manifest_component('svga73_99', 'TRUE'); set_manifest_component('vmmouse73_99', 'TRUE'); } else { set_manifest_component('svga73_99_64', 'TRUE'); set_manifest_component('vmmouse73_99_64', 'TRUE'); } } else { if (!$gIs64BitX) { set_manifest_component('svga73', 'TRUE'); set_manifest_component('vmmouse73', 'TRUE'); } else { set_manifest_component('svga73_64', 'TRUE'); set_manifest_component('vmmouse73_64', 'TRUE'); } } } if ($compat >= 4) { # Ubuntu 10.04 (and eventually other distros) use device kit to load vmmouse # instad of HAL. Note both HAL and DeviceKit can be installed side by side. if ($minor >= 6) { configureDeviceKitVmmouse(); } # Update the Manifest. Entries look something like svga74_64. my $bitExt = ($gIs64BitX) ? '_64' : ''; my $manifestExt = join('', $major, $compat, $bitExt); my $svgaManifestTxt = join('', 'svga', $manifestExt); my $vmmouseManifestTxt = join('', 'vmmouse', $manifestExt); set_manifest_component($svgaManifestTxt, 'TRUE'); set_manifest_component($vmmouseManifestTxt, 'TRUE'); } } } elsif ($major == 7 && $minor > 6) { $gNoXDrivers = 1; # Drivers are upstreamed print wrap("\n\nDistribution provided drivers for Xorg X server are used.\n\n", 0); } else { $gNoXDrivers = 1; # Use this variable to alert about missing drivers print wrap("\n\n No drivers for " . ($xorgServerVersion ? "X server version $xorgServerVersion" : "X version $xversionAll") . "\n\n", 0); } return ($xversion, xconfig_file_abs_path($xconfig_path, $xconfig_file_name), $xversionAll, $disableHotPlug); } # Different xorg installations may store their modules in different places. sub xorg_find_modules_dir { # have to add /usr/X11R6/lib/modules to work with SLES 10 which has xorg # but uses this old path. lib64 must come before lib because both are # present on x64 machines with drivers being in lib64. # if the updates dir presents, assume it is using SuSE's "Xserver module # update mechanism". my @modDirs = qw(/usr/lib64/xorg/modules/updates /usr/lib64/xorg/modules /usr/lib/xorg/modules/updates /usr/lib/xorg/modules /usr/X11R6/lib64/modules /usr/local/lib/xorg/modules /usr/X11R6/lib/modules /usr/X11R6/lib/xorg/modules); foreach my $modDir (@modDirs) { if (-d $modDir) { return $modDir; } } return get_persistent_answer('What is the location of the directory which contains ' . 'your XOrg modules?', 'XORGMODULEDIR', 'dirpath_existing', ''); } sub xfree_4 { my $xconfig_path; my $xconfig_file_name; my $xversionAll = ''; my $xserver_link = ''; my $major; my $minor; my $sub; $xversionAll = direct_command(shell_string(xserver4()) . ' -version 2>&1') =~ /XFree86 Version (\d+\.\d+\.?\d*)/ ? $1: '0.0.0'; # This search order is issued from the XF86Config man page. if (defined $ENV{'XF86CONFIG'} && file_name_exist('/etc/X11/' . $ENV{'XF86CONFIG'})) { $xconfig_path = '/etc/X11'; $xconfig_file_name = $ENV{'XF86CONFIG'}; } elsif (defined $ENV{'XF86CONFIG'} && file_name_exist('/usr/X11R6/etc/X11/' . $ENV{'XF86CONFIG'})) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = $ENV{'XF86CONFIG'}; } elsif (file_name_exist('/etc/X11/XF86Config-4')) { $xconfig_path = '/etc/X11'; $xconfig_file_name = 'XF86Config-4'; } elsif (file_name_exist('/etc/X11/XF86Config')) { # In this case, we are in the situation of having a mix between # XFree 3 and XFree 4, which is usually the case on RH 7.x and # Mandrake 7.x systems. As far as the syntax is concerned, XF86Config # is the 3.x version and XF86Config-4 is the 4.x version. # fix_X_conf patches some of the fields of the old config file into the new # one. There are issues if 3.x syntax fields are patched in a 4.x config # file. By providing a non existing file fix_X_conf will generate a correct # one or if the XF86Config file has the XFree 4 syntax, we can use it. # See bug 23196. $xconfig_path = '/etc/X11'; if (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('.*') . ' ' . '/etc/X11/XF86Config') =~ /Section\s+\"ServerLayout\"/i) { $xconfig_file_name = 'XF86Config'; } else { $xconfig_file_name = 'XF86Config-4'; } } elsif (file_name_exist('/etc/XF86Config')) { $xconfig_path = '/etc'; $xconfig_file_name = 'XF86Config'; } elsif (file_name_exist('/usr/X11R6/etc/X11/XF86Config-4')) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = 'XF86Config-4'; } elsif (file_name_exist('/usr/X11R6/etc/X11/XF86Config')) { $xconfig_path = '/usr/X11R6/etc/X11'; $xconfig_file_name = 'XF86Config'; } elsif (file_name_exist('/usr/X11R6/lib/X11/XF86Config')) { # FreeBSD 5.2 after running xf86config in graphic mode $xconfig_path = '/usr/X11R6/lib/X11'; $xconfig_file_name = 'XF86Config'; } else { # X config file not found return (4, undef, $xversionAll); } if (defined $xconfig_file_name) { print wrap("\n\n" . 'Detected XFree86 version ' . $xversionAll . '.' . "\n\n", 0); } # If there is an existing driver, replace it by ours. backup_file_to_restore($gXVideoDriverFile, 'OLD_X4_DRV'); if (file_name_exist($gXVideoDriverFile)) { unlink $gXVideoDriverFile; } ($major, $minor, $sub) = split_X_version($xversionAll); if ($major == 4) { if ($minor == 2) { # For XFree 4.2.x, we need to replace xaa and shadowfb my $xaaDrv = '/usr/X11R6/lib/modules/libxaa.a'; my $shadowFbDrv = '/usr/X11R6/lib/modules/libshadowfb.a'; backup_file_to_restore($xaaDrv, 'OLD_X4_XAA_DRV'); backup_file_to_restore($shadowFbDrv, 'OLD_X4_SHADOW_FB_DRV'); unlink $xaaDrv; unlink $shadowFbDrv; my %p; undef %p; install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.2.x/libxaa.a', $xaaDrv, \%p, 1); install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.2.x/libshadowfb.a', $shadowFbDrv, \%p, 1); install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.2.x/vmware_drv.o', $gXVideoDriverFile, \%p, 1); if (vmware_product() eq 'tools-for-linux') { set_manifest_component('svga42', 'TRUE'); } } elsif ($minor > 2) { # In this case, all the XAA and ShadowFB changes are present # in the XFree Code and we only need to install the latest # driver. my %p; undef %p; install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.3.x' . ($gIs64BitX ? '_64' : '') . '/vmware_drv.o', $gXVideoDriverFile, \%p, 1); if ($minor == 3 && vmware_product() eq 'tools-for-linux') { if (!$gIs64BitX) { set_manifest_component('svga43', 'TRUE'); } else { set_manifest_component('svga43_64', 'TRUE'); } } } elsif ($minor < 2) { # The default, install the X free 4 driver which works with # the first versions of X. my %p; undef %p; install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.x/vmware_drv.o', $gXVideoDriverFile, \%p, 1); if (vmware_product() eq 'tools-for-linux') { set_manifest_component('svga4', 'TRUE'); } } # Absolute pointing device. if ($major == 4 && $minor == 2) { my %p; undef %p; install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.2.x/vmmouse_drv.o', $gXMouseDriverFile, \%p, 1); if (vmware_product() eq 'tools-for-linux') { set_manifest_component('vmmouse42', 'TRUE'); } } elsif ($major == 4 && $minor == 3) { my %p; undef %p; install_file(db_get_answer('LIBDIR') . '/configurator/XFree86-4/4.3.x' . ($gIs64BitX ? '_64' : '') . '/vmmouse_drv.o', $gXMouseDriverFile, \%p, 1); if (vmware_product() eq 'tools-for-linux') { if (!$gIs64BitX) { set_manifest_component('vmmouse43', 'TRUE'); } else { set_manifest_component('vmmouse43_64', 'TRUE'); } } } fix_X_link('4'); } else { error ('Problem extracting version of XFree 4' . "\n\n"); } return (4, xconfig_file_abs_path($xconfig_path, $xconfig_file_name), $xversionAll); } sub fix_mouse_file { my $mouse_file = '/etc/sysconfig/mouse'; # # If gpm supports imps2, use that as the gpm mouse driver # for both X & gpm. If gpm doesn't support imps2, or isn't set # in this mode, the mouse will be erratic when exiting X if # X was set to use imps2 # my $enableXImps2 = 'no'; my $GPMBinary = internal_which('gpm'); if (file_name_exist($GPMBinary) && file_name_exist($mouse_file)) { my $enableGpmImps2; if (vmware_product() eq 'tools-for-solaris') { $enableGpmImps2 = (system(shell_string($GPMBinary) . ' -t help | ' . $gHelper{'grep'} . ' imps2 > /dev/null 2>&1')) == 0 ? 'yes': 'no'; } else { $enableGpmImps2 = (system(shell_string($GPMBinary) . ' -t help | ' . $gHelper{'grep'} . ' -q imps2')) == 0 ? 'yes': 'no'; } $enableXImps2 = $enableGpmImps2; if ($enableGpmImps2 eq 'yes' ) { backup_file_to_restore($mouse_file, 'MOUSE_CONF'); unlink $mouse_file; my %p; undef %p; $p{'^MOUSETYPE=.*$'} = 'MOUSETYPE=imps2'; $p{'^XMOUSETYPE=.*$'} = 'XMOUSETYPE=IMPS/2'; internal_sed($mouse_file . $cBackupExtension, $mouse_file, 0, \%p); } } return $enableXImps2; } # Determine the name of the maximum available resolution that can fit in the # VMware virtual monitor sub get_best_resolution { my $width = shift; my $height = shift; my $best_name; my $best_res; my $resolution; $best_name = $cGOSResolutionOptions[0]; # ensure that we will return at least the lowest resolution $best_res = -1; foreach $resolution (@cGOSResolutionOptions) { my ($mode_width, $mode_height) = split(/x/, $resolution); if (($mode_width <= $width) && ($mode_height <= $height) && ($mode_width * $mode_height > $best_res)) { $best_res = $mode_width * $mode_height; $best_name = $resolution; } } return $best_name; } # # Try to determine the current screen size # sub get_screen_mode { my $xversion = shift; my $best_resolution = ''; my $chosen_resolution = ''; my $suggested_choice = 1; my $i = 0; my $mode; my $choice; my $width; my $height; my $cXPreviousResolution = 'X_PREVIOUS_RES'; # # Set mode according to what was previously chosen in case of an upgrade # or ask the user a valid range of resolutions. # my $prev_res; if (defined(db_get_answer_if_exists($cXPreviousResolution))) { $prev_res = db_get_answer($cXPreviousResolution); if (grep $_ eq $prev_res, @cGOSResolutionOptions) { if (get_answer("\n\n" . 'Do you want to change the starting screen display size? (yes/no)', 'yesno', 'no') eq 'no') { return $prev_res; } } } ($width, $height) = split('x', $gSystem{'resolution'}); if ($gSystem{'resolution'} eq "0x0" or $gSystem{'resolution'} eq "0 0") { print wrap( "\n" . "Unable to detect guest resolution.\n\n", 0); # unconfuse get_best_resolution() below by setting width and height to 0 $width = $height = 0; } else { print wrap( "\n" . "Resolution detected as \"$width x $height\".\n\n", 0); } # This is guaranteed to return at least the lowest resolution from the # cXConfigFile, or better if the host has a higher resolution, which # means we will get a suggested resolution in the loop below. $best_resolution = get_best_resolution($width, $height); print wrap("\n" . 'Please choose one of the following display sizes that X ' . 'will start with:' . "\n\n", 0); foreach $mode (@cGOSResolutionOptions) { my $header; $i = $i + 1; if ($best_resolution eq $mode) { $suggested_choice = $i; $header = '<'; print wrap('[' . $i . ']' . $header . ' ' . $mode . "\n", 0); } else { $header = ' '; print wrap('[' . $i . ']' . $header . ' ' . $mode . "\n", 0); } } $gMaxNumber = $i; $gAnswerSize{'number'} = length($gMaxNumber); $choice = get_answer('Please enter a number between 1 and ' . $i . ':' . "\n\n", 'number', $suggested_choice); $chosen_resolution = $cGOSResolutionOptions[$choice - 1]; db_add_answer($cXPreviousResolution, $chosen_resolution); return $chosen_resolution; # need to enclose resolution in quotes } # # The first argument is a complete path to a new xconfig file # The second argument will be only read and should be current xconfig file # The third argument is the version of XFree86 # The fourth is a boolean informing weather the Imwheel mouse is used # in gpm or not. # The fifth is a boolean informing weather an extra section must be added # to disable hotplug (see bug 291453). # sub fix_X_conf { my ($newXF86Config, $existingXF86Config, $xversion, $enableXImps2, $xversionAll, $disableHotPlug) = @_; my $inSection = 0; my $inDevice = 0; my $inMonitor = 0; my $gotMouseSection = 0; my $gotServerLayout = 0; my $gotServerFlagsSection = 0; my $gotKeyboardSection = 0; my $xorgScreenIdentifier = ''; my @currentSection; my $sectionLine; my $sectionName; my $mouseRegex = '^\s*driver\s+\"(?:mouse|vmmouse)\"'; my $isMouseSection = 0; my $XFree4_scanpci = xserver_bin() . '/scanpci'; my $major; my $minor; my $sub; my $line; my $screen_mode = get_screen_mode($xversion); my $needMonitor = 0; my %mouseOption = ('"ZAxisMapping"' => '"4 5"', '"Emulate3Buttons"' => '"true"'); ($major, $minor, $sub) = split_X_version($xversionAll); # # Check to see if the vmware svga driver is non-unified b/c we have to # specifiy the BusId in the XF86Config-4 file in that case # my $writeBusIDLine = 0; if ($xversion >= 4 && file_name_exist($XFree4_scanpci)) { my $found = 0; if (vmware_product() eq 'tools-for-solaris') { if ((system(shell_string($XFree4_scanpci) . ' | ' . shell_string($gHelper{'grep'}) . ' 0x0710 > /dev/null 2>&1')/256) == 0 ) { $found = 1; } } elsif ((system(shell_string($XFree4_scanpci) . ' | ' . shell_string($gHelper{'grep'}) . ' -q 0x0710')/256) == 0 ) { $found = 1; } if ($found == 1) { $writeBusIDLine = 1; # print wrap ('Found the device 0x0710' . "\n\n", 0); } } if (not open(EXISTINGXF86CONFIG, "<$existingXF86Config")) { error('Unable to open the file "' . $existingXF86Config . '".' . "\n\n"); } if (not open(NEWXF86CONFIG, ">$newXF86Config")) { error('Unable to open the file "' . $newXF86Config . '".' . "\n\n"); } while (defined($line = <EXISTINGXF86CONFIG>)) { if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) { # We only deal with lines within sections. For other lines, # just copy to new file. $sectionName = lc($1); $inSection = 1; push @currentSection, $line; } else { if ($inSection == 1) { if ($line =~ /^\s*EndSection/i) { # All lines within a section will first be read into # currentSection, then process those lines. push @currentSection, $line; if (($sectionName eq 'inputdevice') || ($sectionName eq 'pointer')) { # There are several different kinds of inputdevice section, such as # mouse, keyboard, and only mouse section will be re-processed, # others just copy to new file. 'pointer' is the mouse section name # for some x config file. $isMouseSection = 0; if ($sectionName eq 'pointer') { $isMouseSection = 1; } foreach $sectionLine (@currentSection) { if ($sectionLine =~ /$mouseRegex/i) { $isMouseSection = 1; last; } } if ($isMouseSection == 1) { $gotMouseSection = 1; my $seenDeviceSection = 0; foreach $sectionLine (@currentSection) { # Replace mouse driver if ($sectionLine =~ /^\s*Driver\s+\"(.+)\"/i) { # Install a mouse driver for all X versions >= 4.2 if (file_name_exist($gXMouseDriverFile) && ($major > 4 || ($major == 4 && $minor >= 2))) { $sectionLine =~ s/$1/vmmouse/g; } } # Replace mouse protocol. There are 2 different formats for mouse # protocol, so should be handled separately. if (($sectionLine =~ /^\s*Option\s+\"Protocol\"\s+\"(.+)\"/i) || ($sectionLine =~ /^\s*Protocol\s+\"(.+)\"/i)) { my $tmpmouse = $1; if (vmware_product() eq 'tools-for-freebsd') { if (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('moused_enable') . ' ' . shell_string('/etc/rc.conf')) =~ /yes/i) { $sectionLine =~ s/$tmpmouse/SysMouse/; } else { $sectionLine =~ s/$tmpmouse/ps\/2/; } } elsif ($enableXImps2 eq 'yes') { $sectionLine =~ s/$tmpmouse/IMPS\/2/g; } else { $sectionLine =~ s/$tmpmouse/ps\/2/g; } } if ($sectionLine =~ /^\s*Option\s+\"ZAxisMapping\"\s+\"(.+)\"/i) { $mouseOption{'"ZAxisMapping"'} = ""; } if ($sectionLine =~ /^\s*Option\s+\"Emulate3Buttons"\s+\"(.+)\"/i) { $mouseOption{'"Emulate3Buttons"'} = ""; } # Replace mouse device. There are 2 different formats for mouse # device, so should be handled separately. if (($sectionLine =~ /^\s*Option\s+\"Device\"\s+\"(.+)\"/i) || ($sectionLine =~ /^\s*Device\s+\"(.+)\"/i)) { $seenDeviceSection = 1; my $tmpdev = $1; if (vmware_product() eq 'tools-for-freebsd') { if (direct_command(shell_string($gHelper{'grep'}) . ' ' . shell_string('moused_enable') . ' ' . shell_string('/etc/rc.conf')) =~ /yes/i) { $sectionLine =~ s/$tmpdev/\/dev\/sysmouse/; } else { $sectionLine =~ s/$tmpdev/\/dev\/psm0/; } } elsif (vmware_product() eq 'tools-for-linux') { # If we have to create a xorg.conf file, the default # mouse device is /dev/mouse. Most machines don't have # /dev/mouse these days, so check if it's going to fail # and try the other alternatives. if (!file_name_exist("$tmpdev")) { # Most common case: /dev/input/mice if (file_name_exist("/dev/input/mice")) { $sectionLine =~ s/$tmpdev/\/dev\/input\/mice/; # 2.4 case: /dev/psaux } elsif (file_name_exist("/dev/psaux")) { $sectionLine =~ s/$tmpdev/\/dev\/psaux/; } } } } # Solaris guests should use the PS/2 device /dev/kdmouse if (vmware_product() eq 'tools-for-solaris') { if (($sectionLine =~ /^\s*Option\s+\"Device\"\s+\"(.+)\"/i) || ($sectionLine =~ /^\s*Device\s+\"(.+)\"/i)) { my $tmpdev = $1; $sectionLine =~ s/$tmpdev/\/dev\/kdmouse/; } } # Normalize the identifier name if ($sectionLine =~ /^\s*Identifier\s+\"/) { $sectionLine = "$&VMwareMouse[1]\"\n"; } # In the mouse section, if we end the section and we haven't # yet seen a Device line, add it, because vmmouse will not # work without one. Not sure what to do if there is no mouse # at all, but this is better than nothing for now. # # Ubuntu 8.04's mouse config section by default does not # specify a device (the driver probes for one and finds it.) # # To mitigate the risk of affecting behavior on other guests, # enable this behavior only for tools-for-linux. It hasn't # ever been a problem on other guests anyway AFAICT. if (vmware_product() eq 'tools-for-linux' && $sectionLine =~ /^\s*EndSection/i && $seenDeviceSection == 0) { if (file_name_exist("/dev/input/mice")) { print NEWXF86CONFIG " Option \"Device\" \"/dev/input/mice\"\n"; # 2.4 case: /dev/psaux } elsif (file_name_exist("/dev/psaux")) { print NEWXF86CONFIG " Option \"Device\" \"/dev/psaux\"\n"; } } # Insert any of the mouse options not already accounted for # before the end of the section if (vmware_product() eq 'tools-for-linux' && $sectionLine =~ /^\s*EndSection/i) { foreach my $option (keys %mouseOption) { if ($mouseOption{$option} eq '') { next; } print NEWXF86CONFIG "\tOption\t\t" . $option . "\t" . $mouseOption{$option} . "\n"; } } print NEWXF86CONFIG $sectionLine; } } else { # First check if it's keyboard and we can rename its identifier. my $isKeyboardSection = 0; foreach $sectionLine (@currentSection) { # SLES9-SP4 uses "Keyboard" instead "keyboard" as Driver name. # 'k' should be case-insensitive. if ($sectionLine =~ /^\s*Driver\s+\"((?i)keyboard|kbd)\"/) { $isKeyboardSection = 1; } } # In inputdevice section, if it is not mouse or keyboard, just copy # to new file directly. Otherwise, rename the keyboard identifier foreach $sectionLine (@currentSection) { if ($isKeyboardSection && $sectionLine =~ /^\s*Identifier\s+\"/) { $gotKeyboardSection = 1; print NEWXF86CONFIG "$&VMwareKeyboard[0]\"\n"; } else { print NEWXF86CONFIG $sectionLine; } } } } elsif ($sectionName eq 'device') { # Regenerate whole device section, and only one device section if ($inDevice == 0) { print NEWXF86CONFIG "Section \"Device\"\n"; print NEWXF86CONFIG " Identifier \"VMware SVGA\"\n"; if ($xversion >= 4) { # For config with newer format. print NEWXF86CONFIG " Driver \"vmware\"\n"; if ($writeBusIDLine) { print NEWXF86CONFIG " BusID \"PCI:0:15:0\"\n"; } } print NEWXF86CONFIG "EndSection\n"; $inDevice = 1; } } elsif ($sectionName eq 'screen') { # Regenerate whole screen section. This is done below. # Only the identifier will stay the same. Everything else # is regenreated. foreach $sectionLine (@currentSection) { if ($sectionLine =~ /^\s*Identifier\s+\"(.+)\"/i) { $xorgScreenIdentifier = "$1"; last; } } } elsif ($sectionName eq 'monitor') { # Regenerate whole monitor section, and only one monitor section if ($inMonitor == 0) { $inMonitor = 1; $needMonitor = 1; } } elsif ($sectionName eq 'serverlayout') { $gotServerLayout = 1; # Copy other sections directly to new file. foreach $sectionLine (@currentSection) { if ($sectionLine =~ 'EndSection') { # See matching 'XWorkAround' InputDevice section below. # # X 7.2(.X) thinks no default mouse is loaded even when # vmmouse has already loaded. So provide a workaround void # driver InputDevice section in xorg.conf to fool X. # # Bug 156988 has all the cory details. if (($major == 7 && ($minor == 2 || ($minor == 1 && ($sub == 99 || distribution_info() eq 'redhat')))) || ($major == 1 && $minor == 3)) { print NEWXF86CONFIG " InputDevice \"XWorkAround\"\n"; } # Since we can't know for sure that all InputDevice sections have # been read before getting here, we have to just force these to # standard names. print NEWXF86CONFIG " InputDevice \"VMwareKeyboard[0]\" \"CoreKeyboard\"\n"; print NEWXF86CONFIG " InputDevice \"VMwareMouse[1]\" \"CorePointer\"\n"; } if (!($sectionLine =~ /^\s*InputDevice\s+/) && !($sectionLine =~ /^\s*Pointer\s+/)) { print NEWXF86CONFIG $sectionLine; } } } elsif ($sectionName eq 'serverflags' && $disableHotPlug eq 'yes') { foreach $sectionLine (@currentSection) { # The option NoAutoAddDevices needs to be added. if ($sectionLine =~ 'EndSection' && $gotServerFlagsSection == 0) { print NEWXF86CONFIG " Option \"NoAutoAddDevices\"\n"; print NEWXF86CONFIG "EndSection\n"; $gotServerFlagsSection = 1; } elsif ($sectionLine =~ /^\s*Option\s+\"AutoAddDevices\"\s+\"(.+)\"/i && $gotServerFlagsSection == 0) { # The user specified AutoAddDevice something. # Using input hotplug isn't recommended inside a VM anyways. # We override his choice print NEWXF86CONFIG " Option \"NoAutoAddDevices\"\n"; $gotServerFlagsSection = 1; } else { print NEWXF86CONFIG $sectionLine; # If the option was already there. if ($sectionLine =~ /^\s*Option\s+\"NoAutoAddDevices\"/i) { $gotServerFlagsSection = 1; } } } } else { # Copy other sections directly to new file. foreach $sectionLine (@currentSection) { print NEWXF86CONFIG $sectionLine; } } # Reset for next section. $inSection = 0; @currentSection = (); } else { push @currentSection, $line; } } else { # Copy other lines outside sections directly to new file. print NEWXF86CONFIG $line; } } } # First bring up the new screen section, preserving the identifer. # If one does not exist, make one. if ("$xorgScreenIdentifier" eq '') { $xorgScreenIdentifier = 'VMware Screen'; } if ($xversion >= 4) { # For config with newer format. print NEWXF86CONFIG "Section \"Screen\"\n"; print NEWXF86CONFIG " Identifier \"$xorgScreenIdentifier\"\n"; print NEWXF86CONFIG <<EOF; Device "VMware SVGA" Monitor "vmware" # Don't specify DefaultColorDepth unless you know what you're # doing. It will override the driver's preferences which can # cause the X server not to run if the host doesn't support the # depth. Subsection "Display" # VGA mode: better left untouched Depth 4 Modes "640x480" ViewPort 0 0 EndSubsection Subsection "Display" Depth 8 Modes "$screen_mode" ViewPort 0 0 EndSubsection Subsection "Display" Depth 15 Modes "$screen_mode" ViewPort 0 0 EndSubsection Subsection "Display" Depth 16 Modes "$screen_mode" ViewPort 0 0 EndSubsection Subsection "Display" Depth 24 Modes "$screen_mode" ViewPort 0 0 EndSubsection EndSection EOF $needMonitor = 1; } if ($gotMouseSection == 0) { print NEWXF86CONFIG <<EOF; Section "InputDevice" Driver "vmmouse" Identifier "VMwareMouse[1]" Option "Buttons" "5" Option "Device" "/dev/input/mice" Option "Protocol" "IMPS/2" Option "ZAxisMapping" "4 5" Option "Emulate3Buttons" "true" EndSection EOF $gotMouseSection = 1; } if ($gotKeyboardSection == 0) { print NEWXF86CONFIG <<EOF; Section "InputDevice" Identifier "VMwareKeyboard[0]" Driver "keyboard" Option "AutoRepeat" "500 30" Option "XkbRules" "xfree86" Option "XkbModel" "pc104" Option "XkbLayout" "us" Option "XkbCompat" "" EndSection EOF $gotKeyboardSection = 1; } if ($needMonitor == 1) { print NEWXF86CONFIG <<EOF; Section "Monitor" Identifier "vmware" VendorName "VMware, Inc" HorizSync 1-10000 VertRefresh 1-10000 EndSection EOF $needMonitor = 0; } # See matching XWorkAround layout entry above. # if (($major == 7 && ($minor == 2 || ($minor == 1 && ($sub == 99 || distribution_info() eq 'redhat')))) || ($major == 1 && $minor == 3)) { print NEWXF86CONFIG <<EOF; Section "InputDevice" Identifier "XWorkAround" Driver "void" EndSection EOF } # See bug 291453 # if ($gotServerFlagsSection == 0 && $disableHotPlug eq 'yes') { print NEWXF86CONFIG <<EOF; Section "ServerFlags" Option "NoAutoAddDevices" EndSection EOF $gotServerFlagsSection = 1; } # # So some distros forget to create the ServerLayout section of the # Xorg.conf file (Debian 5.1 is a great example). If they don't have # it, it means we have to generate it for them... # if ($gotServerLayout == 0) { # Check to ensure the screen identifier was found. Otherwise we # have to bail out. if ($xorgScreenIdentifier eq '') { error("No Identifier for the screen section in xorg.conf found\n"); } print NEWXF86CONFIG <<EOF; Section "ServerLayout" Identifier "VMware ServerLayout" EOF print NEWXF86CONFIG " Screen \"$xorgScreenIdentifier\"\n"; print NEWXF86CONFIG <<EOF; InputDevice "VMwareMouse[1]" "CorePointer" InputDevice "VMwareKeyboard[0]" "CoreKeyboard" EndSection EOF $gotServerLayout = 1; } close (EXISTINGXF86CONFIG); close (NEWXF86CONFIG); } # Return next free X display number, or undef on error. sub find_free_X_display { for (0..99) { if ( ! -e "/tmp/.X${_}-lock" ) { return $_; } } return undef; } sub try_X_conf { my $xConfigFile = shift; my $xLogFile = shift; my $childPid; my $childStatus; my $vtNext; unless(defined(&VT_OPENQRY)) { # find available vt sub VT_OPENQRY () { 0x5600; } } my $TTY0; open($TTY0, '/dev/tty0') or die "open /dev/tty0 : $!\n"; my $data = pack("I", 0); if (ioctl($TTY0, VT_OPENQRY, $data)) { $vtNext = unpack("I", $data); } else { error("VT_OPENQRY ioctl() error: $!\n"); } close($TTY0); my $display_num = find_free_X_display(); if (!defined($display_num)) { error("Could not find unused X display while testing X11 config.\n"); } my @xargs = (xserver_bin() . '/X', ":$display_num", 'vt' . $vtNext, '-logfile', $xLogFile, '-once'); # Handle cases where there is no config file. push (@xargs, ('-xf86config', $xConfigFile)) if (-e $xConfigFile); # # Set Autoflush. Setting this avoids duplicate output and seems # to keep X from crashing/hanging when we are testing the new config # file. See bug 347610 for more details. -astiegmann # $| = 1; if ($childPid = fork()) { # Run parent code, reading from child eval { $SIG{ALRM} = sub { die "alarm\n" }; alarm 5; # Seconds do { $childStatus = waitpid($childPid,0); } until $childStatus == -1; alarm 0; }; if ($@) { # Propagate unexpected errors die unless $@ eq "alarm\n"; # Timed out print wrap("\n" . 'X is running fine with the new config file.' . "\n\n", 0); kill(15, $childPid); return 1; } else { print wrap ('Error: ' . "$childStatus" . '. X did not start.' . (-e "$xLogFile" ? "Details in $xLogFile" : '') . "\n", 0); return 0; } } else { error('Cannot fork: ' . "$!\n") unless defined $childPid; # Child code open STDERR, '>/dev/null'; exec @xargs; } $| = 0; } sub configure_X { my $xversion = ''; my $xconfig_file = ''; my $enableXImps2 = ''; my $disableHotPlug = 'no'; my $xversionAll = ''; my $xconfig_backup = ''; my $createNewXConf = 0; my $changeXConf = 1; my $addXconfToDb = 0; my $major; my $minor; my $sub; if (xserver_bin() eq '') { print wrap ('No X install found.' . "\n\n", 0); return 'no'; } if (vmware_product() eq 'tools-for-solaris' && solaris_10_or_greater() eq 'yes' && direct_command(shell_string($gHelper{'svcprop'}) . ' -p options/server ' . 'application/x11/x11-server') =~ /Xsun/) { if (get_answer("\n\n" . 'You are currently using the Solaris Xsun server. ' . vmware_product_name() . ' for Solaris only supports the Xorg ' . 'server (which can be switched to by running kdmconfig(1M) as ' . 'root). Would you like to configure the Xorg server now ' . 'so that you have the option of switching to it in the ' . 'future? (yes/no)', 'yesno', 'yes') eq 'no') { print wrap('Skipping X configuration.' . "\n\n", 0); return 'no'; } } if (file_name_exist(xserver_xorg())) { if (is64BitElf(xserver_xorg())) { $gIs64BitX = 1; # 64-bit FreeBSD puts it's 64-bit X modules in lib not lib64 if (vmware_product() ne 'tools-for-freebsd') { $gXMouseDriverFile = "$cX64ModulesDir/input/vmmouse_drv.o"; $gXVideoDriverFile = "$cX64ModulesDir/drivers/vmware_drv.o"; } elsif (vmware_product() eq 'tools-for-solaris') { $gXMouseDriverFile = "/usr/X11/lib/modules/input/amd64/vmmouse_drv.so"; $gXVideoDriverFile = "/usr/X11/lib/modules/drivers/amd64/vmware_drv.so"; } else { $gXMouseDriverFile = "$cXModulesDir/input/vmmouse_drv.o"; $gXVideoDriverFile = "$cXModulesDir/drivers/vmware_drv.o"; } } else { # Solaris' Xorg installation is in /usr/X11 (not /usr/X11R6) if (vmware_product() eq 'tools-for-solaris') { $gXMouseDriverFile = "/usr/X11/lib/modules/input/vmmouse_drv.so"; $gXVideoDriverFile = "/usr/X11/lib/modules/drivers/vmware_drv.so"; } else { $gXMouseDriverFile = "$cXModulesDir/input/vmmouse_drv.o"; $gXVideoDriverFile = "$cXModulesDir/drivers/vmware_drv.o"; } } ($xversion, $xconfig_file, $xversionAll, $disableHotPlug) = xorg(); } elsif (file_name_exist(xserver4())){ if (is64BitElf(xserver4())) { $gIs64BitX = 1; $gXMouseDriverFile = "$cX64ModulesDir/input/vmmouse_drv.o"; $gXVideoDriverFile = "$cX64ModulesDir/drivers/vmware_drv.o"; } else { $gXMouseDriverFile = "$cXModulesDir/input/vmmouse_drv.o"; $gXVideoDriverFile = "$cXModulesDir/drivers/vmware_drv.o"; } ($xversion, $xconfig_file, $xversionAll) = xfree_4(); } else { print wrap ('No supported X install found.' . "\n\n", 0); return 'no'; } ($major, $minor, $sub) = split_X_version($xversionAll); # $gNoXDrivers set to 1 means VMware tools didn't include # appropriate drivers for the detected X version. if ($gNoXDrivers == 1) { print wrap('Skipping X configuration because X drivers are not included.' . "\n\n", 0); return 'no'; } # 7.4 and 7.5 do not need a new xorg.conf file. # # See bug 360333 # If the OS is Fedora 9 or SLES11, then it needs to have # its xorg file modified. # # XXX The polarity of this boolean is inverted. 0 is true. my $isFedoraRel = system(shell_string($gHelper{'grep'}) . ' ' . "-q 'Fedora release 9' /etc/fedora-release " . ">/dev/null 2>&1"); # XXX The polarity of this boolean is inverted. 0 is true. my $isSuseRel11 = 1; if ($gSystem{'distribution'} eq 'suse') { my %prop = identify_suse_variant(); if (((defined($prop{'variant'}) and $prop{'variant'} eq 'sle') or (defined($prop{'variant'}) and $prop{'variant'} eq 'opensuse')) and defined($prop{'version'}) and $prop{'version'} =~ /^11(\.[012])*$/) { $isSuseRel11 = 0; } } if ($major == 7 and $minor >= 4 and $isFedoraRel != 0 and $isSuseRel11 != 0 and vmware_product() ne 'tools-for-freebsd') { $changeXConf = 0; # If there is a .conf file there, back it up so that we properly use # the Xorg auto-conf logic. if (defined $xconfig_file and -e $xconfig_file) { backup_file_to_restore($xconfig_file, 'XCONFIG_FILE'); } } if (not defined $xconfig_file) { if (get_answer("\n\n" . 'Could not locate X ' . $xversionAll . ' configuration file. Do you want to create a new' . ' one? (yes/no)', 'yesno', 'yes') eq 'no') { print wrap ("\n\n" . 'Could not locate X ' . $xversionAll . ' configuration file. X configuration skipped.' . "\n\n", 0); return 'no'; } $xconfig_file = "/etc/X11/XF86Config" . ($xversion >= 4 ? '-4' : ''); $createNewXConf = 1; } elsif (not file_name_exist($xconfig_file) and $changeXConf) { if (get_answer("\n\n" . 'The configuration file ' . $xconfig_file . ' cannot be found. Do you want to create a new' . ' one? (yes/no)', 'yesno', 'yes') eq 'no') { print wrap ("\n\n" . 'The configuration file ' . $xconfig_file . ' cannot be found. X configuration skipped.' . "\n\n", 0); return 'no'; } $createNewXConf = 1; } if (-l $xconfig_file && !-e $xconfig_file) { print wrap ("\n\n" . 'The configuration file ' . $xconfig_file . ' is a broken symlink. X configuration skipped.' . "\n\n", 0); return 'no'; } $enableXImps2 = fix_mouse_file(); $xconfig_backup = $xconfig_file . $cBackupExtension; # -e must be also tested because we don't want to unlink # a non-existed file if ((-e $xconfig_backup) && (!-s $xconfig_backup)) { unlink $xconfig_backup or die "Failed to cleanup empty $xconfig_backup file : $!\n"; } # If the X config file does not exist, we need to add it to our database. If # the X config file does exist, there are two cases: 1) we created it from # scratch during a previous Tools configuration, or 2) we are about to or # have already backed up the existing X config file. For case 1, we don't # want to backup the file since we created it; for case 2, we need to backup # the file for restoring (if the backup already exists, backup_file_to_restore # will do the right thing). if ($createNewXConf == 1) { $addXconfToDb = 1; } elsif ($changeXConf == 1) { # Only backup the file if we didn't previously add it to the database if (not db_file_in($xconfig_file)) { backup_file_to_restore($xconfig_file, 'XCONFIG_FILE'); } } my $tmp_dir = make_tmp_dir($cTmpDirPrefix); my $XF86tmp = $tmp_dir . '/XF86Config.' . $$; my $xLogFile = $tmp_dir . '/XF86ConfigLog.' . $$; if (-e $XF86tmp) { unlink $XF86tmp or die "Failed to cleanup old $XF86tmp file : $!\n"; } if (-e $xLogFile) { unlink $xLogFile or die "Failed to cleanup old $xLogFile file : $!\n"; } if (-e "$xLogFile.old" ) { unlink "$xLogFile.old" or die "Failed to cleanup old $xLogFile.old file : $!\n"; } # Change the test file if we're using the original xorg.conf my $xconfigTestFile = $XF86tmp; if ($major == 7 && $minor >= 4 && file_name_exist($xconfig_file)) { $xconfigTestFile = $xconfig_file; } if ($changeXConf == 1) { if ($createNewXConf == 0) { # Before installation, if there is no backup one, $xconfig_file will # be renamed to $xconfig_backup. So first $xconfig_file should be # checked if existed. if (-e $xconfig_file) { fix_X_conf($XF86tmp, $xconfig_file, $xversion, $enableXImps2, $xversionAll, $disableHotPlug); } else { fix_X_conf($XF86tmp, $xconfig_backup, $xversion, $enableXImps2, $xversionAll, $disableHotPlug); } } else { # If failed with existing $xconfig_file, try to generate a new x config # file with template one. The template file is also modified with fix_X_conf # to set right mouse protocol, driver, display, etc. fix_X_conf($XF86tmp, db_get_answer('LIBDIR') . '/configurator/XFree86-' . ($xversion >= 4 ? '4' : $xversion) . '/XF86Config' . ($xversion >= 4 ? '-4': ''), $xversion, $enableXImps2, $xversionAll, $disableHotPlug); } } my $isUbuntuHardy = system(shell_string($gHelper{'grep'}) . ' ' . "-q 'DISTRIB_CODENAME=hardy' /etc/lsb-release " . ">/dev/null 2>&1"); # try_X_conf has problem with old X window, please refer to bug 78985 # Ubuntu 8.04 has a very corner case issue where if you change the resolution # of Xorg via this script after a fresh install of tools, the try_X_conf method # will cause gdm to restart. So we have to sneak past this bug by checking if the # distry in question is Hardy and whether or not the .not_configured file exists. if (vmware_product() eq 'tools-for-linux' && ($major > 4 || ($major == 4 && $minor >= 2)) && not ($isUbuntuHardy == 0 && -e $gConfFlag) && !try_X_conf($xconfigTestFile, $xLogFile)) { if (get_answer("\n\n" . 'The updated X config file does not work well.' . ' See '. $xLogFile . ' for details. Do you want to create' . ' a new one from template? Warning: if you choose to' . ' create a new one, all old settings will be gone! (yes/no)', 'yesno', 'yes') eq 'no') { print wrap ('X configuration failed! The updated X config file does ' . 'not work well. File saved as ' . $XF86tmp . '. See ' . $xLogFile . ' for details.' . "\n\n"); # Here temp files are not removed because user may want to check the # files to see what is wrong. return 'no'; } unlink $XF86tmp; unlink $xLogFile; # If test failed with $XF86tmp, try to test a new $XF86tmp from template one. fix_X_conf($XF86tmp, db_get_answer('LIBDIR') . '/configurator/XFree86-' . ($xversion >= 4 ? '4' : $xversion) . '/XF86Config' . ($xversion >= 4 ? '-4': ''), $xversion, $enableXImps2, $xversionAll, $disableHotPlug); if (!try_X_conf($XF86tmp, $xLogFile)) { # Here temp files are not removed because user may want to check the # files to see what is wrong. return 'no'; } } if ($changeXConf == 1) { if (system(shell_string($gHelper{'cp'}) . ' -p ' . $XF86tmp . ' ' . $xconfig_file)) { print wrap ('Unable to copy the updated X config file to ' . $xconfig_file . "\n\n"); # Here temp files are not removed because user may manually copy files # to right place. return 'no'; } if ($addXconfToDb == 1) { db_add_file($xconfig_file, 0x0); } } unlink $XF86tmp; unlink $xLogFile; remove_tmp_dir($tmp_dir); return 'yes'; } # Set to CUPS in the guest to use thinprint sub configure_thinprint { my $lpadmin; my $cupsenable; my $cupsaccept; my $configText; my $printerName = 'VMware_Virtual_Printer'; my $printerURI = 'tpvmlp://VMware'; my $cupsDir; # Order is important! Check the lib64 directory first. my @cupsDirs = ( '/usr/lib64/cups/backend', '/usr/lib/cups/backend', ); my $cupsConfDir = '/etc/cups'; my $cupsPrinters = "$cupsConfDir/printers.conf"; my $cupsConf = "$cupsConfDir/cupsd.conf"; my $addDummyPrinter = 'false'; my $libdir = db_get_answer('LIBDIR'); # We don't support thinprint for systems that don't have glibc >= 2.5 installed. # BUG 736991 my ($major, $minor) = get_glibc_version(); if (! ($major > 2 || ($major == 2 && $minor >= 5))) { db_add_answer('THINPRINT_CONFED', 'no'); return 0; } # Disable TP for now on Fedora 15 since our init scripts have issues. # BUG 706586 if (open(FH, '</etc/fedora-release')) { my @res = grep(/Fedora release 15/, <FH>); close(FH); if (@res) { db_add_answer('THINPRINT_CONFED', 'no'); return 0; } } # To continue, CUPS must be where we expect it on the guest. foreach (@cupsDirs) { $cupsDir = $_ if file_name_exist($_); } if (!$cupsDir || !file_name_exist($cupsConf)) { return 0; } # make thinprint configurable (bug #866326) # On ESX it does not make sense, but we leave it up to the user my $defAns = is_esx_virt_env() ? 'no' : 'yes'; my $thinQ = 'Thinprint provides driver-free printing. ' . 'Do you wish to enable this feature?'; if (get_persistent_answer($thinQ, 'ENABLE_THINPRINT', 'yesno', $defAns) eq 'no') { db_add_answer('THINPRINT_CONFED', 'no'); return 0; } # # ThinPrint provide us 2 executables (well, appLoader libs) which handle a variety # of tasks, choosing which path to run based on argv[0]. # # libtpvmlp.so corresponds to the following # /usr/bin/tpvmlp # /usr/lib/cups/backend/tpvmlp # /usr/lib/cups/backend/tpvmgp # # libtpvmlpd.so corresponds to the following # /usr/bin/tpvmlpd # # libtpvmlpd.so is a transient wrapper around the real daemon in libtpvmlp.so # reached when argv[0] = tpvmlpd2. Yeah, this is a little confusing, but it is what # it is. # my $srcSO = "$libdir/lib/libtpvmlp.so/libtpvmlp.so"; foreach (qw(tpvmlpd2 tpvmgp)) { my $tgtF = 'lib' . $_ . '.so'; my $tgtD = linkdest($libdir . '/lib') . '/' . $tgtF; my $tgt = $tgtD . '/' . $tgtF; create_dir($tgtD, $cFlagDirectoryMark); install_link(0, $srcSO, $tgt); } my @backends = ("$cupsDir/tpvmlp", "$cupsDir/tpvmgp"); if (!file_name_exist($cupsPrinters)) { system("touch $cupsPrinters"); system("chmod --reference=$cupsConf $cupsPrinters"); system("chown --reference=$cupsConf $cupsPrinters"); } if (!file_name_exist($cupsPrinters)) { return 0; } $configText = "<Printer ${printerName}>\n" . " Info ${printerName}\n" . " DeviceURI ${printerURI}\n" . " State Idle\n" . " Accepting Yes\n" . "</Printer>\n"; install_symlink(db_get_answer('LIBDIR') . '/configurator/thinprint.ppd', $cupsConfDir . "/ppd/" . $printerName . ".ppd"); foreach (qw(tpvmlp tpvmlpd)) { install_symlink(db_get_answer('LIBDIR') . '/bin/appLoader', "/usr/bin/$_"); } # # From CUPS backend(7): # Backends without world execute permissions are run as the root user. Otherwise, the # backend is run using the unprivileged user account, typically "lp". # # Ideally, on SELinux systems we'd change the context to match /dev/ttyS0, but I don't # grok that bid'ness, and we were installing setuid root previously, anyway. # foreach(@backends) { my $backend = $_; my %patch; install_file("$libdir/bin/appLoader-av0", $backend, \%patch, 0); safe_chmod(0500, $backend); restorecon($backend); } if (is_selinux_enabled() && file_name_exist("/etc/redhat-release")) { # first make sure all the commands are present if (internal_which("checkmodule") eq '' || internal_which("semodule_package") eq '' || internal_which("semodule") eq '') { print wrap("One or more selinux tools missing: ". "can't configure cups backend.\n\n", 0); } else { # make a temp dir my $tmpDir = make_tmp_dir($cTmpDirPrefix); # the files we will use my $tmpFile = "$tmpDir/tpvmlpcupsd.te"; my $modFile = "$tmpDir/tpvmlpcupsd.mod"; my $ppFile = "$tmpDir/tpvmlpcupsd.pp"; # create tmp file if (open (TEFILE, ">", $tmpFile)) { print TEFILE <<EOF; module tpvmlpcupsd 1.0; require { type var_lock_t; type cupsd_t; type lib_t; class dir { getattr write read remove_name add_name search }; class file { getattr write read create unlink lock execute execute_no_trans }; } allow cupsd_t var_lock_t:dir { getattr write read remove_name add_name search }; allow cupsd_t var_lock_t:file { getattr write read create unlink lock }; allow cupsd_t lib_t:file { execute execute_no_trans }; EOF close TEFILE; # do the magic my $s = system("checkmodule -m -M -o $modFile $tmpFile >/dev/null 2>&1"); $s or $s = system("semodule_package -o $ppFile -m $modFile >/dev/null 2>&1"); $s or $s = system("semodule -i $ppFile >/dev/null 2>&1"); if ($s) { print wrap("Configuration of cups backend for selinux failed.\n\n"); } } # remove temp dir and it's contents remove_tmp_dir($tmpDir); } } # end of the selinux stuff install_symlink($gRegistryDir . '/tpvmlp.conf', '/etc/tpvmlp.conf'); if ($addDummyPrinter eq 'true') { block_remove($cupsPrinters, $cupsPrinters . '.bak', $cMarkerBegin, $cMarkerEnd); block_append($cupsPrinters . '.bak' , $cMarkerBegin, $configText, $cMarkerEnd); rename($cupsPrinters . '.bak', $cupsPrinters); } db_add_answer('THINPRINT_CONFED', 'yes'); } sub configure_caf{ if (is64BitUserLand() and db_get_answer('ENABLE_VGAUTH') eq 'yes') { my $enabled = db_get_answer_if_exists('ENABLE_CAF'); db_remove_answer('ENABLE_CAF'); my $answer = get_persistent_answer('Do you want to enable Common Agent (caf)?', 'ENABLE_CAF', 'yesno', 'no'); db_add_answer('ENABLE_CAF', $answer); if ($answer eq 'yes') { # lib is symlink to either lib64 or lib32 my $lib_lib_dir = db_get_answer('LIBDIR') . '/lib'; # If we had it already, we are upgrading, # otherwise this is a fresh install for caf. if ($enabled and $enabled eq 'yes') { # Below logic handles updating place holders. It is needed for downgrade scenario. my $caf_lib_dir = db_get_answer('CAFLIBDIR'); my $caf_var_dir = db_get_answer('CAFVARDIR'); my $caf_etc_dir = db_get_answer('CAFETCDIR'); my @files; my $file; my $pattern; foreach $file (glob($caf_etc_dir . '/pme/config/*')) { if (db_file_in($file)) { db_remove_file($file); push(@files, $file); } } # install.sh expects destination directories without 'vmware-caf': system($caf_etc_dir . "/pme/install/install.sh" . " -l $caf_lib_dir -i $caf_var_dir -o $caf_var_dir -t $lib_lib_dir"); foreach $file (@files) { db_add_file($file, 1); } system($caf_etc_dir . '/pme/install/upgrade.sh' . " -t $lib_lib_dir" ); } else { # install.sh modifies files in /etc/vmware/cafe/pme/config/ # this will change the timestamp, and therefore the file # will not be removed unless we re-add them to the database. my $caf_lib_dir = db_get_answer('CAFLIBDIR'); my $caf_var_dir = db_get_answer('CAFVARDIR'); my $caf_etc_dir = db_get_answer('CAFETCDIR'); my @files; my $file; my $pattern; foreach $file (glob($caf_etc_dir . '/pme/config/*')) { if (db_file_in($file)) { db_remove_file($file); push(@files, $file); } } # install.sh expects destination directories without 'vmware-caf': system($caf_etc_dir . "/pme/install/install.sh" . " -B $caf_lib_dir -l $caf_lib_dir -i $caf_var_dir -o $caf_var_dir -t $lib_lib_dir"); foreach $file (@files) { db_add_file($file, 1); } # install.sh adds symlinks, make sure those get added to the db # so they get removed on uninstall: foreach $file (glob($caf_lib_dir . '/vmware-caf/pme/lib/*.so*')) { if (-l $file) { db_add_file($file, 0); } } } } } else { db_add_answer('ENABLE_CAF', 'no'); } } sub configure_vgauth{ my $question; $question = 'Do you want to enable Guest Authentication (vgauth)?'; if ($have_caf eq 'yes') { $question = $question . ' Enabling vgauth is needed if you want to enable Common Agent (caf).'; } db_add_answer('ENABLE_VGAUTH', get_persistent_answer($question, 'ENABLE_VGAUTH', 'yesno', 'yes') ); } # # configure_guestproxy -- # # Generates the necessary certificate files for 'grabbitmqproxy' # plugin in 'VMware Tools'. # sub configure_guestproxy { if(vmware_product() ne 'tools-for-linux') { return; } my $bindir = db_get_answer('BINDIR'); my $certToolPath = $bindir . '/vmware-guestproxycerttool'; my $options = ' -g'; if ($gOption{'regenerate-cert'} == 1) { $options = $options . ' -f'; } if (system(shell_string($certToolPath) . $options)) { print wrap("Failed to create cert.pem and key.pem, error: " . $? . "\n"); } } # # configure_autostart_xdg -- # # Tests for the existence of well-known paths used to support the XDG # autostart mechanism. For each path encountered, a vmware-user.desktop # symlink is installed which will cause XDG autostart aware session managers # to launch vmware-user as part of the user's session. # # Results: # Returns the following triple: # ((int) number of symlinks installed, # (bool) 1 if a GNOME-specific directory was encountered, # (bool) 1 if a KDE-specific directory was encountered) # sub configure_autostart_xdg { # /path/to/vmware-user.desktop. my $dotDesktop = "$gRegistryDir/vmware-user.desktop"; my $numSymlinks = 0; my $foundGnome = 0; my $foundKde = 0; my %autodirs = ( "/etc/xdg/autostart" => undef, "/usr/share/autostart" => undef, "/usr/share/gnome/autostart" => undef, # FreeBSD, compiled from source, and maybe Gentoo? "/usr/local/share/autostart" => undef, "/usr/local/share/gnome/autostart" => undef, "/usr/local/kde4/share/autostart" => undef, # SuSE-style. "/opt/gnome/share/autostart" => undef, "/opt/kde/share/autostart" => undef, "/opt/kde3/share/autostart" => undef, "/opt/kde4/share/autostart" => undef, ); # If KDE is available, use kde{,4}-config to search for its install path, # and add that to the list of autostart directories. # # Since PATH is overridden in main(), test for other common locations # of kde-config (using the augmented argument to internal_which). # # NB: FreeBSD packages KDE 4 under /usr/local/kde4, and /usr/local/kde4/bin # typically isn't automatically added to users' search paths, so (for now) # we'll need to search there directly. my @kdeConfigs = ("kde-config", "kde4-config"); foreach (@kdeConfigs) { my $kdeConfig = internal_which($_, 1, ["/usr/local/kde4/bin"]); if ($kdeConfig ne '' && -x $kdeConfig) { # # Okay, we have a valid kde-config. Query it for its installation # prefix, then if an autostart path exists, add it to autodirs. # my $kdePrefix = direct_command(shell_string($kdeConfig) . " --prefix"); chomp($kdePrefix); my $kdeAutostart = "$kdePrefix/share/autostart"; if (-d $kdeAutostart) { $autodirs{$kdeAutostart} = undef; $foundKde = 1; } } }; foreach (keys(%autodirs)) { if (-d $_) { install_symlink($dotDesktop, "$_/vmware-user.desktop"); # At time of publishing, all versions of gnome-session supporting # XDG autostart do so via a GNOME-specific autostart directory. $foundGnome = 1 if $_ =~ /gnome/; $foundKde = 1 if $_ =~ /kde/; ++$numSymlinks; } } return ($numSymlinks, $foundGnome, $foundKde); } # # configure_autostart_legacy_xdm -- # # Jump through hoops to launch vmware-user as part of xdm's Xsession script. # # Results: # If applicable, xdm will now launch vmware-user before executing its usual # Xsession script. # # Returns the number of xdm-config files modified. # sub configure_autostart_legacy_xdm { my $x11Base = internal_dirname(xserver_bin()); db_add_answer('X11DIR', $x11Base); my $chompedMarkerBegin = $cMarkerBegin; chomp($chompedMarkerBegin); my $modCount = 0; # X.Org's XDM # - Determine X11BASE. # - Touch xdm-config to source our Xresources. my $xResources = "$gRegistryDir/vmware-user.Xresources"; my $xSessionXDM = "$gRegistryDir/xsession-xdm.sh"; my @xdmcfgs = ("$x11Base/lib/X11/xdm/xdm-config", "/etc/X11/xdm/xdm-config"); foreach my $xdmcfg (@xdmcfgs) { if (file_name_exist($xdmcfg)) { if (block_match($xdmcfg, "!$chompedMarkerBegin")) { block_restore($xdmcfg, $cMarkerBegin, $cMarkerEnd); } block_append($xdmcfg, "!$cMarkerBegin", "#include \"$xResources\"\n", "!$cMarkerEnd"); ++$modCount; } } return $modCount; } # # configure_autostart_legacy_gdm -- # # Attempt to launch vmware-user via gdm. # # Results: # If applicable, we place a script in /etc/X11/xinit/xinitrc.d, causing gdm # to launch vmware-user before its usual Xsession script. # # Returns 1 if the symlink was installed, and 0 otherwise. # sub configure_autostart_legacy_gdm { # GNOME's GDM (legacy) # - Symlink xsession-gdm to /etc/X11/xinit/xinitrc.d/vmware-user.sh. # (This is a hardcoded path in gdm sources.) my $xSessionGDM = "$gRegistryDir/xsession-gdm.sh"; my $xinitrcd = "/etc/X11/xinit/xinitrc.d"; if (-d $xinitrcd) { install_symlink($xSessionGDM, "$xinitrcd/vmware-xsession-gdm.sh"); return 1; } return 0; } # # configure_autostart_legacy_suse -- # # Hook into /etc/X11/xinit/xinitrc.common to launch vmware-user. # # - All packaged display managers point users to a consolidated # /etc/X11/xdm/Xsession script. (Even in the GDM case, SUSE's scheme # overrides what we'd use above.) # - This Xsession script sources /etc/X11/xinit/xinitrc.common for common # autostart tasks. Seems like a perfect fit for us. # # Results: # If applicable, we'll insert a few lines into xinitrc.common, and all # display managers on SuSE 10 will launch vmware-user during X11 session # startup. # # Returns 1 if xinitrc.common was modified, and 0 otherwise. # sub configure_autostart_legacy_suse { my $startCommand = shift; # Bourne-shell compatible string used to launch # vmware-user. my $xinitrcCommon = '/etc/X11/xinit/xinitrc.common'; my $chompedMarkerBegin = $cMarkerBegin; chomp($chompedMarkerBegin); if (file_name_exist($xinitrcCommon)) { if (block_match($xinitrcCommon, $chompedMarkerBegin)) { block_restore($xinitrcCommon, $cMarkerBegin, $cMarkerEnd); } block_append($xinitrcCommon, $cMarkerBegin, $startCommand . "\n", $cMarkerEnd); return 1; } return 0; } # # configure_autostart_legacy_xsessiond -- # # Hook into Xsession.d/*.sh to launch vmware-user. # # Results: # If applicable, we'll create a vmware-user launch script in the system's # Xsession.d directory, and vmware-user will launch during X11 session # startup. # # Returns 1 if we dropped in a script, and 0 otherwise. # sub configure_autostart_legacy_xsessiond($$) { my $startCommand = shift; # Bourne-shell compatible string used to launch # vmware-user. my $platform = shift; # Must be either "Debian" or "Solaris" # (case-insensitive). my $chompedMarkerBegin = $cMarkerBegin; chomp($chompedMarkerBegin); my $xSessionD; my $xSessionDst; my $prettyOSName; for ($platform) { /debian/i && do { $xSessionD = '/etc/X11/Xsession.d'; $xSessionDst = "$xSessionD/99-vmware_vmware-user"; $prettyOSName = "Debian and Ubuntu"; last; }; /solaris/i && do { $xSessionD = '/usr/dt/config/Xsession.d'; $xSessionDst = "$xSessionD/9999.autostart-vmware-user.sh"; $prettyOSName = "Solaris"; last; }; die sprintf("%s: platform '%s' unknown.\n", "configure_autostart_legacy_xsessiond", $platform); } my $tmpBlock = <<__EOF; # # This script is intended only as a last resort in order to launch the VMware # User Agent (vmware-user) in legacy $prettyOSName VMs whose shipped X11 # session managers may not support XDG/KDE-style autostart via .desktop files. # __EOF if (-d $xSessionD) { if (block_match($xSessionDst, $chompedMarkerBegin)) { block_restore($xSessionDst, $cMarkerBegin, $cMarkerEnd); } block_append($xSessionDst, $cMarkerBegin, $tmpBlock . "\n" . $startCommand . "\n", $cMarkerEnd); safe_chmod(0755, $xSessionDst); db_add_file($xSessionDst, 0); return 1; } return 0; } # # configure_autostart_legacy -- # # Use unconventional hooks to launch vmware-user at X session startup. # This is intended only for guests which do not support XDG-style # autostart. # # This routine will make use of hooks provided by the following: # - OpenSuSE 10's xinitrc.common # - Debian, Ubuntu via Xsession.d # - xdm (all known versions) # - gdm (2.2.3 and above) # # The vendor specific methods are preferred, so if either of those succeeds, # we'll avoid calling into the xdm & gdm routines. # # Results: # If applicable, we may insert vmware-user autostart hooks. # sub configure_autostart_legacy { # # This is the vmware-user launch command for pre-XDG autostart guests. The # delay is intended to prefer XDG-style launch for guests where we may # accidentally use both pre- and post-XDG autostart hooks. # my ($sleepingAgentDelay) = 15; # Give session managers a 15s head start. my ($sleepingAgentCommand) = sprintf("{ sleep %d && %s/%s &>/dev/null ; } &", $sleepingAgentDelay, db_get_answer('BINDIR'), 'vmware-user'); if ((configure_autostart_legacy_suse($sleepingAgentCommand) == 0) && (configure_autostart_legacy_xsessiond($sleepingAgentCommand, "debian") == 0) && (configure_autostart_legacy_xsessiond($sleepingAgentCommand, "solaris") == 0)) { configure_autostart_legacy_xdm(); configure_autostart_legacy_gdm(); } } # # configure_autostart -- # # Configures the system to launch vmware-user as part of users' graphical # sessions. # # This routine is heuristically inclined. We'll install XDG style .desktop # files if any paths exist, but we'll use legacy autostart hooks only if # we have reason to believe that the XDG solution didn't fully apply to # this guest. # # E.g., GNOME was a little late to the .desktop autostart party. So a # machine with both slightly older GNOME and KDE installed may have # an autostart directory present under $datadir/autostart, but it may # not be used by GNOME. If this is the case (indicated by $foundGnomeStart # being false), then we opt to continue and use some of the legacy # install hooks. # sub configure_autostart { my @sessionsDirs; my $hasGnome = 0; my $hasKde = 0; my $numSymlinks; my $foundGnomeStart; my $foundKdeStart; my $existingDirs = 0; # # We forgot to fully clean up after ourselves when uninstalling Tools. As a # result, users who upgrade Tools may find vmware-user launched via a # "legacy" autostart mechanism outside the context of their desktop (GNOME, # KDE, Xfce, etc.) session. (This breaks features like GHI.) The unfortunate # workaround is to simply take that step here. # # NB: This affects only users who installed Tools with beta versions of # Workstation and Fusion*, so we can likely pull this block out (while still # leaving the corresponding call in the uninstaller) in the next release. # # * This refers to any version of Tools that included the decoupled # vmware-{user,guestd}. # unconfigure_autostart_legacy($cMarkerBegin, $cMarkerEnd); @sessionsDirs = ('/usr/share/xsessions', '/usr/local/share/xsessions', '/usr/X11R6/share/xsessions'); # if gnome.desktop or kde*.desktop exists in any of the above sessionDirs, # then flag the appropriate variable saying so. foreach (@sessionsDirs) { next unless -d $_; my @tmpArray; $hasGnome = 1 if (-e "$_/gnome.desktop"); @tmpArray = glob("$_/kde*.desktop"); $hasKde = 1 if $#tmpArray != -1; ++$existingDirs; } if ($existingDirs == 0) { $hasGnome = defined internal_which('gnome', 1) || defined internal_which('gnome-session', 1); $hasKde = defined internal_which('startkde', 1) || defined internal_which('ksmserver', 1); } ($numSymlinks, $foundGnomeStart, $foundKdeStart) = configure_autostart_xdg(); # Fall back to legacy autostart if # a. no XDG symlinks were installed, or # b. user employs older GNOME but we were unable to find a GNOME- # supported XDG path, or # c. s/GNOME/KDE/g (less likely) if (($numSymlinks == 0) || ($hasGnome && !$foundGnomeStart) || ($hasKde && !$foundKdeStart)) { configure_autostart_legacy(); } } # Creates a bridged network. sub make_bridged_net { my $vHubNr = shift; my $vHostIf = shift; my $ethIf = shift; # Need to make sure the NAME key is present so that netmap.conf is created properly. my $net_name = db_get_answer_if_exists('VNET_' . $vHubNr . '_NAME'); if (not defined($net_name)) { db_add_answer('VNET_' . $vHubNr . '_NAME', 'Bridged-' . $vHubNr); } db_add_answer('VNET_' . $vHubNr . '_INTERFACE', $ethIf); db_remove_answer('VNET_' . $vHubNr . '_DHCP'); configure_dev('/dev/' . $vHostIf, 119, $vHubNr, 1); # Reload the list of available ethernet adapters load_ethif_info(); } # Converts an quad-dotted IPv4 address into a integer sub quaddot_to_int { my $quaddot = shift; my @quaddot_a; my $int; my $i; @quaddot_a = split(/\./, $quaddot); $int = 0; for ($i = 0; $i < 4; $i++) { $int <<= 8; $int |= $quaddot_a[$i]; } return $int; } # Converts an integer into a quad-dotted IPv4 address sub int_to_quaddot { my $int = shift; my @quaddot_a; my $i; for ($i = 3; $i >= 0; $i--) { $quaddot_a[$i] = $int & 0xFF; $int >>= 8; } return join('.', @quaddot_a); } # Compute the subnet address associated to a couple IP/netmask sub compute_subnet { my $ip = shift; my $netmask = shift; return int_to_quaddot(quaddot_to_int($ip) & quaddot_to_int($netmask)); } # Compute the broadcast address associated to a couple IP/netmask sub compute_broadcast { my $ip = shift; my $netmask = shift; return int_to_quaddot(quaddot_to_int($ip) | (0xFFFFFFFF - quaddot_to_int($netmask))); } # Makes the patch hash that is used to replace the options in the dhcpd config # file. # These DHCP options are needed for the hostonly network. sub make_dhcpd_patch { my $vHubNr = shift; my $vHostIf = shift; my %patch; undef %patch; $patch{'%vmnet%'} = $vHostIf; $patch{'%hostaddr%'} = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR'); $patch{'%netmask%'} = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK'); $patch{'%network%'} = db_get_answer_if_exists('VNET_' . $vHubNr . '_HOSTONLY_SUBNET'); if (not defined($patch{'%network%'})) { $patch{'%network%'} = compute_subnet($patch{'%hostaddr%'}, $patch{'%netmask%'}); } $patch{'%broadcast%'} = compute_broadcast($patch{'%hostaddr%'}, $patch{'%netmask%'}); # Median address in this subnet $patch{'%range_low%'} = int_to_quaddot( (quaddot_to_int($patch{'%network%'}) + quaddot_to_int($patch{'%broadcast%'}) + 1) / 2); # Last normal address in this subnet $patch{'%range_high%'} = int_to_quaddot( quaddot_to_int($patch{'%broadcast%'}) - 1); $patch{'%router_option%'} = ""; return %patch; } # Write VMware's DHCPd configuration files sub write_dhcpd_config { my $vHubNr = shift; my $vHostIf = shift; # Function that makes the patch needed for the DHCP config file my $make_patch_func = shift; my $dhcpd_dir; my %patch; %patch = &$make_patch_func($vHubNr, $vHostIf); # Create the dhcpd config directory (one per virtual interface) $dhcpd_dir = $gRegistryDir . '/' . $vHostIf . '/dhcpd'; create_dir($dhcpd_dir, $cFlagDirectoryMark); install_file(db_get_answer('LIBDIR') . '/configurator/vmnet-dhcpd.conf', $dhcpd_dir . '/dhcpd.conf', \%patch, $cFlagTimestamp | $cFlagConfig); # Create empty files that will be created by the daemon # They will be modified by the daemon, don't timestamp them undef %patch; install_file('/dev/null', $dhcpd_dir . '/dhcpd.leases', \%patch, 0); safe_chmod(0644, $dhcpd_dir . '/dhcpd.leases'); undef %patch; install_file('/dev/null', $dhcpd_dir . '/dhcpd.leases~', \%patch, 0); safe_chmod(0644, $dhcpd_dir . '/dhcpd.leases~'); } # Creates a hostonly network sub make_hostonly_net { my $vHubNr = shift; my $vHostIf = shift; my $subnet = shift; my $netmask = shift; my $run_dhcpd = shift; my $hostaddr = int_to_quaddot(quaddot_to_int($subnet) + 1); configure_dev('/dev/' . $vHostIf, 119, $vHubNr, 1); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR', $hostaddr); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK', $netmask); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_SUBNET', $subnet); db_add_answer('VNET_' . $vHubNr . '_DHCP', 'yes'); if ($run_dhcpd) { write_dhcpd_config($vHubNr, $vHostIf, \&make_dhcpd_patch); } else { # XXX NOT IMPLEMENTED } # Unmake Samba just in case they have it from a previous product version if (defined($gDBAnswer{'NETWORKING'}) && get_samba_net() != -1) { unmake_samba_net($vHubNr, $vHostIf); } } # Unconfigures Samba from the hostonly network sub unmake_samba_net { my $vHubNr = shift; my $vHostIf = shift; my $smb_dir = $gRegistryDir . '/' . $vHostIf . '/smb'; if (is_samba_running($vHubNr)) { db_remove_answer('VNET_' . $vHubNr . '_SAMBA'); db_remove_answer('VNET_' . $vHubNr . '_SAMBA_MACHINESID'); db_remove_answer('VNET_' . $vHubNr . '_SAMBA_SMBPASSWD'); uninstall_prefix($smb_dir); } db_add_answer('VNET_' . $vHubNr . '_SAMBA', 'no'); } # Gets the virtual network number where Samba is located. sub get_samba_net { my $vHubNr; for ($vHubNr = $gMinVmnet; $vHubNr <= $gMaxVmnet; $vHubNr++) { if (is_samba_running($vHubNr)) { return $vHubNr; } } return -1; } # Creates a NAT network sub make_nat_net { my $vHubNr = shift; my $vHostIf = shift; my $subnet = shift; my $netmask = shift; my $hostaddr = int_to_quaddot(quaddot_to_int($subnet) + 1); my $nataddr = int_to_quaddot(quaddot_to_int($subnet) + 2); configure_dev('/dev/' . $vHostIf, 119, $vHubNr, 1); db_add_answer('VNET_' . $vHubNr . '_NAT', 'yes'); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR', $hostaddr); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK', $netmask); db_add_answer('VNET_' . $vHubNr . '_HOSTONLY_SUBNET', $subnet); db_add_answer('VNET_' . $vHubNr . '_DHCP', 'yes'); write_dhcpd_config($vHubNr, $vHostIf, \&make_nat_patch); write_nat_config($vHubNr, $vHostIf); } # Write NAT configuration files sub write_nat_config { my $vHubNr = shift; my $vHostIf = shift; my $nat_dir; my %patch; # Create the nat config directory (one per virtual interface) $nat_dir = $gRegistryDir . '/' . $vHostIf . '/nat'; create_dir($nat_dir, $cFlagDirectoryMark); undef %patch; my $hostaddr = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR'); my $netmask = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK'); my $network = db_get_answer_if_exists('VNET_' . $vHubNr . '_HOSTONLY_SUBNET'); if (not defined($network)) { $network = compute_subnet($hostaddr, $netmask); } my $broadcast = compute_broadcast($hostaddr, $netmask); my $nataddr = int_to_quaddot(quaddot_to_int($network) + 2); $patch{'%nataddr%'} = $nataddr; $patch{'%netmask%'} = $netmask; $patch{'%sample%'} = int_to_quaddot( (quaddot_to_int($network) + quaddot_to_int($broadcast) + 1) / 2); $patch{'%vmnet%'} = "/dev/" . $vHostIf; install_file(db_get_answer('LIBDIR') . '/configurator/vmnet-nat.conf', $nat_dir . '/nat.conf', \%patch, $cFlagTimestamp | $cFlagConfig); } # Makes the patch hash that is used to replace the options in the dhcpd config # file. # These DHCP options are needed for the NAT network. sub make_nat_patch { my $vHubNr = shift; my $vHostIf = shift; my %patch; my $hostaddr = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR'); my $netmask = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK'); my $subnet = db_get_answer_if_exists('VNET_' . $vHubNr . '_HOSTONLY_SUBNET'); if (not defined($subnet)) { $subnet = compute_subnet($hostaddr, $netmask); } my $nataddr = int_to_quaddot(quaddot_to_int($subnet) + 2); undef %patch; $patch{'%vmnet%'} = $vHostIf; $patch{'%hostaddr%'} = $nataddr; $patch{'%netmask%'} = $netmask; $patch{'%network%'} = compute_subnet($nataddr, $netmask); $patch{'%broadcast%'} = compute_broadcast($nataddr, $netmask); # Median address in this subnet $patch{'%range_low%'} = int_to_quaddot( (quaddot_to_int($patch{'%network%'}) + quaddot_to_int($patch{'%broadcast%'}) + 1) / 2); # Last normal address in this subnet $patch{'%range_high%'} = int_to_quaddot(quaddot_to_int($patch{'%broadcast%'}) - 1); $patch{'%router_option%'} = "option routers $nataddr;"; return %patch; } # Return the specific VMware product sub vmware_product { return 'tools-for-linux'; } # This is a function in case a future product name contains language-specific # escape characters. sub vmware_product_name { return 'VMware Tools'; } # Returns the name of the main binary for this install. sub vmware_binary { return 'vmware-toolbox-cmd'; } sub vmware_tools_cmd_app_name { return db_get_answer('BINDIR') . '/vmware-toolbox-cmd'; } # Find binaries necessary for the server products (esx/gsx) sub configure_server { my $program; # Create the /var/log/vmware directory for event logs create_dir($gLogDir, $cFlagDirectoryMark); # Kill any running vmware-hostd process. system(shell_string($gHelper{'killall'}) . ' -TERM vmware-hostd ' . '>/dev/null 2>&1'); configure_authd(); configure_wgs_pam_d(); fix_vmlist_permissions(); } # Try to find a free port for authd use starting from default passed in # If none are available, return default passed in sub get_port_for_authd { my $base_port = shift; my $port = $base_port; my $max_range = 65536; while (check_answer_inetport($port, "default") ne $port) { $port = ($port + 1) % $max_range; if ($base_port == $port) { last; } } return $port; } # Find a suitable port for authd sub configure_authd { my $success = 0; my $port; # Initialize the port cache. Contains the set of ports # known to be active on the system: listed in /proc/net/tcp. get_proc_tcp_entries(); if (defined(db_get_answer_if_exists("AUTHDPORT"))) { $port = db_get_answer_if_exists("AUTHDPORT"); } else { # We'll try to find a good default port that is free $port = get_port_for_authd($gDefaultAuthdPort); if ($port != $gDefaultAuthdPort) { print wrap('The default port : '. $gDefaultAuthdPort. ' is not free.' . ' We have selected a suitable alternative port for ' . vmware_product_name() . ' use. You may override this value now.' . "\n", 0); print wrap(' Remember to use this port when installing' . ' remote clients on other machines.' . "\n", 0); } } $port = get_persistent_answer('Please specify a port for remote' . ' connections to use', 'AUTHDPORT', 'inetport', $port); if ($gDefaultAuthdPort != $port) { print wrap('WARNING: ' . vmware_product_name() . ' has been configured to ' . 'run on a port different from the default port. ' . 'Please make sure to use this port when installing remote' . ' clients on other machines.' . "\n\n", 0); } db_add_answer('VMAUTHD_USE_LAUNCHER', 'yes'); } # Move the /etc/vmware/pam.d information to its real home in /etc/pam.d sub configure_wgs_pam_d { my $dir = '/etc/pam.d'; my $o_file = $gRegistryDir . '/pam.d/vmware-authd'; if (system(shell_string($gHelper{'cp'}) . ' -p ' . $o_file . ' ' . $dir)) { error('Unable to copy the VMware vmware-authd PAM file to ' . $dir . "\n\n"); } } # Unconfigures the now obsolete Samba networking sub unconfigure_samba { print wrap('Removing obsolete VMware Samba config info. To access the ' . 'host filesystem please use the VMware shared folders.' . "\n\n", 0); unmake_samba_net($gDefHostOnly, 'vmnet' . $gDefHostOnly); return; } # Go through the /etc/vmware/vm-list file and set permissions correctly # also, upgrade vmkernel device names on ESX Server sub fix_vmlist_permissions { my $file = '/etc/vmware/vm-list'; my $cf; if (not -e $file) { return; } if (get_answer('Do you want this program to set up permissions for your ' . 'registered virtual machines? This will be done by ' . 'setting new permissions on all files found in the "' . $file . '" file.', 'yesno', 'no') eq 'no') { return; } if (not open(F, "$file")) { print wrap('Aborting attempt to change permissions on config files found ' . 'in "' . $file . '": Cannot read the file.' . "\n\n", 0); return; } while (<F>) { s/"//g; # This comment fixes emacs's broken syntax highlighting" ($cf) = m/^config (.*)$/; if (!defined($cf) || (not -e $cf) || (not -f $cf)) { next; } if (chmod(0754, $cf) != 1) { print wrap('Cannot change permissions on file "' . $cf . '".' . "\n\n", 0); } } close(F); } sub check_fuse_available { my $libModPath = join('/','/lib/modules', getKernRel()); my $available = 'no'; # See if FUSE has already been registered if (open(PROCFILESYSTEMS, "/proc/filesystems")) { if (grep(/fuse$/, <PROCFILESYSTEMS>)) { $available = 'yes'; } close(PROCFILESYSTEMS); } # The module might not be loaded yet if ($available ne 'yes' && open(MODULESDEP, "$libModPath/modules.dep")) { if (grep(/^(.*\/fuse\.k?o):.*$/, <MODULESDEP>)) { $available = 'yes'; } close(MODULESDEP); } # Finally check the system paths to see if the user has the # needed fusemount binary installed. if ($available eq 'yes') { $available = internal_which('fusermount') ? 'yes' : 'no'; } return $available; } sub configure_vmblock { if (vmware_product() eq 'tools-for-freebsd') { my $freeBSDVersion = getFreeBSDVersion(); if (dot_version_compare("$freeBSDVersion", '9.1') >= 0) { print wrap("vmblock is not supported for FreeBSD 9.1 and above.\n\n"); disable_module('vmblock'); db_add_answer('VMBLOCK_CONFED_FUSE', 'no'); return; } } # By default we don't want vmblock installed in guests runnning on ESX virtual environments # since its useless there. However we want vmblock to be installed by default on VMs # running in WS/Fusion virtual environments. Hence ask users and set the default answer based # on whether or not we are running in an ESX environment vs a WS/Fusion environment. my $defAns = is_esx_virt_env() ? 'no' : 'yes'; my $vmblockQ = 'The vmblock enables dragging or copying files between host and guest ' . 'in a Fusion or Workstation virtual environment. ' . 'Do you wish to enable this feature?'; if (get_persistent_answer($vmblockQ, 'ENABLE_VMBLOCK', 'yesno', $defAns) eq 'no') { disable_module('vmblock'); db_add_answer('VMBLOCK_CONFED_FUSE', 'no'); return; } my $result; my $canBuild = 'no'; my $explain; if (vmware_product() eq 'tools-for-solaris') { $result = configure_module_solaris('vmblock'); } elsif (vmware_product() eq 'tools-for-freebsd') { $result = configure_module_bsd('vmblock'); } else { # Use the Fuse version of vmblock exclusively for kernel versions >= 2.6.32. # I picked this version because it's the RHEL 6 release and when we started # upstreaming our various other kernel modules. if ($gSystem{'version_integer'} >= kernel_version_integer(2, 6, 32)) { if (check_fuse_available() eq 'yes') { db_add_answer('VMBLOCK_CONFED_FUSE', 'yes'); db_add_answer('VMBLOCK_CONFED', 'no'); } else { disable_module('vmblock'); print wrap("NOTICE: " . "It appears your system does not have the required fuse " . "packages installed. The VMware blocking filesystem " . "requires the fuse packages and its libraries to " . "function properly. Please install the fuse or " . "fuse-utils package using your systems package " . "management utility and re-run this script in " . "order to enable the VMware blocking filesystem. " . "\n\n", 0); } return; } # Check if FUSE is available if ($gSystem{'version_integer'} >= kernel_version_integer(2, 6, 27) && check_fuse_available() eq 'yes') { db_add_answer('VMBLOCK_CONFED_FUSE', 'yes'); db_add_answer('VMBLOCK_CONFED', 'no'); return; } $result = mod_pre_install_check('vmblock'); if ($result eq 'yes') { if ($gSystem{'version_integer'} < kernel_version_integer(2, 4, 0)) { print wrap("The vmblock module is not supported on kernels " . "older than 2.4.0\n\n", 0); $result = 'no'; } else { $result = configure_module('vmblock'); $canBuild = 'yes'; } if ($result eq 'no') { my $src; my $dest; if (vmware_product() =~ /^tools-for-/) { $src = "host"; $dest = "guest"; } else { $src = "guest"; $dest = "host"; } $explain = 'The vmblock module enables dragging or copying files from ' . 'within a ' . $src . ' and dropping or pasting them onto ' . 'your ' . $dest . ' (' . $src . ' to ' . $dest . ' drag and drop and file copy/paste). The rest of the ' . 'software provided by ' . vmware_product_name() . ' is designed to work independently of this feature (including ' . $dest . ' to ' . $src . ' drag and drop and file copy/paste).' . "\n\n"; if ($canBuild eq 'yes') { $explain .= 'If you would like the ' . $src . ' to ' . $dest . ' drag ' . 'and drop and file copy/paste features, ' . $cModulesBuildEnv . "\n"; } query($explain, ' Press Enter key to continue ', 0); } } } module_post_configure('vmblock', $result); } sub build_vmnet { if (db_get_answer('NETWORKING') ne 'no') { if (configure_module('vmnet') eq 'no') { module_error(); } } } # Indicates if samba is running on a virtual network sub is_samba_running { my $vHubNr = shift; my $hostonly = is_hostonly_network($vHubNr); my $samba = $gDBAnswer{'VNET_' . $vHubNr . '_SAMBA'}; return $hostonly && defined($samba) && $samba eq 'yes'; } # Create the links for VMware's services on a Solaris system sub link_services_solaris { my $service = shift; my $S_level = shift; my $K_level = shift; my @S_runlevels = ('2'); my @K_runlevels = ('0', '1', 'S'); my $runlevel; foreach $runlevel (@S_runlevels) { install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc' . $runlevel . '.d/S' . $S_level . $service); } foreach $runlevel (@K_runlevels) { install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc' . $runlevel . '.d/K' . $K_level . $service); } } # Write the VMware host-wide configuration file sub write_vmware_config { my $name; my $backupName; my $promoconfig; $name = $gRegistryDir . '/config'; $backupName = $gStateDir . '/config'; my $config = new VMware::Config; # First read in old config backed up from last uninstallation. if (file_name_exist($name)) { if (!$config->readin($name)) { error('Unable to read configuration file "' . $name . '".' . "\n\n"); } db_remove_file($name); } my $bindir = db_get_answer('BINDIR'); my $libdir = db_get_answer('LIBDIR'); my $sbindir = db_get_answer('SBINDIR'); $config->set('bindir', $bindir); # Here we set some defaults for guest.commands.* # The ->get with default is how we are sure to only change if it isn't # already set $config->set('guest.commands.enabledOnHost', $config->get('guest.commands.enabledOnHost','TRUE')); $config->set('guest.commands.allowAnonGuestCommandsOnHost', $config->get('guest.commands.allowAnonGuestCommandsOnHost', 'FALSE')); $config->set('guest.commands.allowAnonRootGuestCommandsOnHost', $config->get('guest.commands.allowAnonRootGuestCommandsOnHost', 'FALSE')); $config->set('guest.commands.anonGuestUserNameOnHost', $config->get('guest.commands.anonGuestUserNameOnHost','')); $config->set('guest.commands.anonGuestPasswordOnHost', $config->get('guest.commands.anonGuestPasswordOnHost','')); $config->set('vmware.fullpath', $bindir . '/vmware'); $config->set('dhcpd.fullpath', $bindir . '/vmnet-dhcpd'); $config->set('loop.fullpath', $bindir . '/vmware-loop'); $config->set('control.fullpath', $bindir . '/vmware-cmd'); $config->set('authd.fullpath', $sbindir . '/vmware-authd'); $config->set('libdir', $libdir); $config->set('product.name', vmware_product_name()); # Vix needs to know what version of workstation or server # it is installed with, even for dev builds. So add it # here as an extra variable and vmware_version() wil return # its usual values. Also, this allows other makefiles to # remain untouched. if (vmware_product() eq 'ws') { $config->set('product.version', '@@VERSIONNUMBER_FOR_VIX@@'); } else { $config->set('product.version', '10.3.22'); } $config->set('product.buildNumber', '15902021'); if (vmware_product() eq 'server') { $config->set('authd.client.port', db_get_answer('AUTHDPORT')); } my $vHubNr; for ($vHubNr = $gMinVmnet; $vHubNr <= $gMaxVmnet; $vHubNr++) { if (is_hostonly_network($vHubNr)) { my $hostaddr = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_HOSTADDR'); my $netmask = db_get_answer('VNET_' . $vHubNr . '_HOSTONLY_NETMASK'); # Used by the Linux wizard to determine if a hostonly network is # configured. $config->set('vmnet' . $vHubNr . '.HostOnlyAddress', $hostaddr); $config->set('vmnet' . $vHubNr . '.HostOnlyNetMask', $netmask); } else { $config->remove('vmnet' . $vHubNr . '.HostOnlyAddress'); $config->remove('vmnet' . $vHubNr . '.HostOnlyNetMask'); } } # Used by the Linux wizard to determine if Samba is configured on the # hostonly network. $config->remove('smbpasswd.fullpath'); if (vmware_product() eq 'server') { $config->set('authd.proxy.nfc', 'vmware-hostd:ha-nfc'); } $config->remove('authd.proxy.vim'); $config->remove('authd.soapServer'); $config->remove('serverd.fullpath'); $config->remove('serverd.init.fullpath'); if (!$config->writeout($name)) { error('Unable to write configuration file "' . $name . '".' . "\n\n"); } db_add_file($name, $cFlagTimestamp | $cFlagConfig); safe_chmod(0644, $name); # Append the promotional configuration if it exists $promoconfig = $libdir . '/configurator/PROMOCONFIG'; if (-e $promoconfig) { my %patch; undef %patch; internal_sed($promoconfig, $name, 1, \%patch); } if (!-d $gStateDir) { create_dir($gStateDir, 0x1); } system(shell_string($gHelper{'cp'}) . " " . $name . " " . $backupName); } # This is used for a VMware dictionary-compatible configuration file. # Newer tools use glib-style ini files which appLoader doesn't grok # through the dictionary functions. sub write_new_tools_config() { my $name = $gRegistryDir . '/config'; my $config = new VMware::Config; # First read in old config backed up from last uninstallation. if (file_name_exist($name)) { $config->readin($name); } $config->set('libdir', db_get_answer('LIBDIR')); if (!$config->writeout($name)) { error('Unable to write configuration file "' . $name . '".' . "\n\n"); } db_add_file($name, $cFlagTimestamp); safe_chmod(0644, $name); } # Display the PROMOCODE information sub show_PROMOCODE { my $promocode; $promocode = db_get_answer('DOCDIR') . '/PROMOCODE'; if (-e $promocode) { # $gHelper{'more'} is already a shell string system($gHelper{'more'} . ' ' . shell_string($promocode)); print "\n"; } } # # Determines the status of the given module in question. The returned status # is one of the following... # # not_installed - module is not installed. # installed_by_vmware - the module is not upstreamed and vmware has installed this module. # clobbered_by_vmware - the module is upstreamed but clobbered by vmware one. # installed_by_other - someone else has installed this module. # not_configured - We installed it, but its not marked as configured # compiled_in - Module has been compiled into the running kernel. # sub get_module_status { my $mod = shift; my $modstatus = $gInstallStatus{$mod}; if(!defined($modstatus)){ return 'unknown'; } if($modstatus eq 'other') { return 'installed_by_other'; } elsif ($modstatus eq 'builtin') { return 'compiled_in'; } # So its not in the list. If its configured, then we installed it. Otherwise # it isn't installed (it may be, but since it's not configured we will not # count it as being installed). my $modConfKey = uc("$mod") . '_CONFED'; if (defined $gVmwareInstalledModules{"$mod"}) { if (defined db_get_answer_if_exists($modConfKey) and db_get_answer($modConfKey) eq 'yes') { if ($modstatus eq 'clobbered') { return 'clobbered_by_vmware'; } elsif ($modstatus eq 'installed') { return 'installed_by_vmware'; } } else { return 'not_configured'; } } return 'not_installed'; } # # Returns the file name of the module on the system. # # Since upstreaming, our module names are not gauranteed to stay the same. # This function takes a module name and translates it to the name of the # module as modprobe would see it. # sub get_module_name { my $mod = shift; my $modName = "$mod"; my $modStatus = get_module_status($mod); if ($modStatus eq 'installed_by_other') { if ($gNonVmwareModules{"$mod"} =~ m,.*/([\w\.\-]+)\.k?o,) { $modName = $1; } } elsif ($modStatus eq 'installed_by_vmware' or $modStatus eq 'clobbered_by_vmware') { if ($gVmwareInstalledModules{"$mod"} =~ m,.*/([\w\.\-]+)\.k?o,) { $modName = $1; } } return $modName; } # # Sets the install destination for a module based on whether or not # the module is already installed on the system. # # If the module is not already on the system, put it in misc. # Otherwise it needs to go in the updates folder so depmod chooses # it over any other modules in the system. # sub get_module_install_dest { my $mod = shift; my $modStatus = get_module_status($mod); my $dest = "misc"; if ($modStatus eq 'installed_by_other' or $modStatus eq 'clobbered_by_vmware') { $dest = "updates/vmware" } elsif ($modStatus eq 'installed_by_vmware' and defined $gVmwareInstalledModules{"$mod"}) { # We need to check where we installed the module. if ($gVmwareInstalledModules{"$mod"} =~ m,/lib/modules/$gSystem{'uts_release'}/(.+)/[\w\.\-]+\.k?o,) { $dest = $1; } } return $dest; } # # Checks to see if we should install the given module. # # Returns yes if we should install the module, no otherwise. # sub mod_pre_install_check { my $mod = shift; my $modStatus = get_module_status($mod); # if we cannot determine the module status, we shouldn't install if ($modStatus eq 'unknown') { print wrap("The installation status of $mod could not be determined. Skipping" . "installation.\n\n"); return 'no'; } # we no longer support building these drivers for kernels >= 3.10, # see bug #1035688: my $deprecated_310 = (($mod eq 'vmmemctl' || $mod eq 'pvscsi' || $mod eq 'vmxnet3') && ($gSystem{'version_integer'} >= kernel_version_integer(3, 10, 0))); if ($modStatus eq 'compiled_in') { # Then we better not even try to install the module print wrap("The module $mod has been compiled into the kernel " . "and cannot be managed by " . vmware_product_name() . ".\n", 0); return 'no'; } if ($modStatus eq 'installed_by_other') { print wrap("The module $mod has already been installed on this " . "system by another installer or package " . "and will not be modified by this installer.\n\n", 0); return 'no'; } if ( $deprecated_310 ) { print wrap("The module $mod from this package is not supported for " . "kernels >= 3.10. Please enable this module in your kernel. " . "You may also have to enable HYPERVISOR_GUEST.\n\n", 0); return 'no'; } # If we get here, then the module is either not installed or was # installed by us. Hence we should install the module. return 'yes'; } # # Reinstalls the module after passing some basic sanity checks. # sub reinstall_module { my $mod = shift; my $modConfKey = uc("$mod") . '_CONFED'; my $result = db_get_answer_if_exists($modConfKey); if (defined $result and $result eq 'yes') { # Then the module was installed by us and can be reinstalled by us. configure_module($mod); } else { print wrap("$mod was not installed " . "and configured by VMware.\n\n\n", 0); } #even if module has been installed by other, we can add it to ramdisk module_ramdisk_check($mod); return; } # # Reinstalls vmblock after passing some basic sanity checks. # sub reinstall_module_vmblock { my $mod = 'vmblock'; my $modConfKey = uc("$mod") . '_CONFED'; my $result = db_get_answer_if_exists($modConfKey); if (defined $result and $result eq 'yes') { # Then the module was installed by us and can be reinstalled by us. # but for vmblock, VMBLOCK_CONFED is set also when using fuse: if (getKernRelInteger() < kernel_version_integer(2, 6, 32)) { configure_module($mod); module_ramdisk_check($mod); } else { print wrap("Skipping $mod because vmware-vmblock-fuse will " . "be used\n\n\n", 0); } } else { print wrap("Skipping $mod since it was not installed " . "and configured by VMware.\n\n\n", 0); } return; } # # Checks if the given module needs to be added to the ramdisk and # adds it if it does. # sub module_ramdisk_check { my $mod = shift; my $answer = $cRamdiskKernelModules{"$mod"}; my $modStatus = get_module_status("$mod"); if (defined $answer and "$answer" eq 'yes' and "$modStatus" ne 'not_installed' and "$modStatus" ne 'compiled_in') { push (@gRamdiskModules, "$mod"); } } # Display a usage error message for the configuration program and exit sub config_usage { my $long_name = vmware_longname(); my $prog_name = internal_basename($0); my $usage = <<EOF; $long_name configurator. Usage: $prog_name [OPTION]... Options -d, --default Automatically answer questions with the proposed answer. -c, --compile Force the compilation of kernel modules. -p, --prebuilt Force the use of pre-built kernel modules. --regenerate-cert Force to regenerate server key and certificate files even if they already exist. --preserve Always preserve user-modified configuration files. --overwrite Always overwrite user-modified configuration files. -m, --modules-only Only rebuild/install kernel modules and skip all other configuration steps (including system configuration for the kernel modules). NOTE: This flag will only work after the system has been configured to work with the VMware kernel modules at least once. -k, --kernel-version <version> Build/install modules for the specified kernel version. Implies --compile and --modules-only. --clobber-xorg-modules Skips the Xorg module version comparison tests and installs the VMware shipped Xorg modules. Command line arguments: The acceptable characters are: The letters A, B, C, ... The letters a, b, c, ... The numbers 0, 1, 2, ... and the special characters '_', '-', ',' and '='. EOF print STDERR $usage; exit 1; } # switch_to_guest # Sets links on configuration files we changed during configuration. # If switch_to_host was never called, do nothing. sub switch_to_guest { my %filesBackedUp; my $file; if (!defined(db_get_answer_if_exists($cSwitchedToHost))) { return; } %filesBackedUp = db_get_files_to_restore(); foreach $file (keys %filesBackedUp) { if (-l $file) { if (check_link($file, $file . db_get_answer($cSwitchedToHost)) eq 'yes') { return; } unlink $file; symlink $file . db_get_answer($cSwitchedToHost), $file; } } } # switch_to_host # Saves configuration files we changed during configuration. # Sets links on configuration files we backed up during configuration. sub switch_to_host { my $configuredExtension = '.AfterVMwareToolsInstall'; my %filesBackedUp; my $file; if (!defined(db_get_answer_if_exists($cSwitchedToHost))) { db_add_answer($cSwitchedToHost, $configuredExtension); } %filesBackedUp = db_get_files_to_restore(); foreach $file (keys %filesBackedUp) { if (-l $file) { if (check_link($file, $filesBackedUp{$file}) eq 'yes') { return; } unlink $file; } else { my %patch; undef %patch; install_file($file, $file . $configuredExtension, \%patch, $cFlagTimestamp); unlink $file; # The link might change, do not keep the timestamp. db_add_file($file, 0); } symlink $filesBackedUp{$file}, $file; } } # update LIBDIR/libconf/etc/fonts/fonts.conf with system font dirs. Take # just after its first <dir> entry. This does not yet handle commented out # <dir> elements and assumes that <dir> elements are grouped together in # the same heading. # # XXX Document return value(s). # sub configure_fonts_dot_conf { my $tmp_dir = make_tmp_dir("vmware-fonts"); my $sys_font_path = "/etc/fonts/fonts.conf"; if (! -f $sys_font_path) { # This means fontconfig was not installed/configured. In this case, # ensure that we look for the font directory/directories ourselves # and add their location to a temporary replacement for fonts.conf. $sys_font_path = $tmp_dir . '/system_fonts.conf'; open(SYSFONT, ">" . $sys_font_path) || error "Error opening " . $sys_font_path . "\n"; my $fonts_found = 0; foreach my $location (@gSuspectedFontLocations) { if (-d $location) { $fonts_found = 1; print SYSFONT "<dir>", $location, "</dir>\n"; } } close(SYSFONT); if ($fonts_found == 0) { # We were unable to find any fonts. Just quit. return; } } my ($font_line, $sys_line); my $font_path = linkdest(db_get_answer('LIBDIR') . '/libconf') . '/etc/fonts/fonts.conf'; my $tmp_file = $tmp_dir . '/fonts.conf'; open(MYFONT, "<" . $font_path) || error "Error opening " . $font_path . "\n"; open(SYSFONT, "<" . $sys_font_path) || error "Error opening " . $sys_font_path . "\n"; open(OUTFONT, ">" . $tmp_file) || error "Error opening " . $tmp_file . "\n"; # Read from our fonts.conf until reach a <dir> line. Skip the dir. # We'll dump our '<dir>' lines and use the system's. while ($font_line = <MYFONT>) { if ($font_line =~ /Font\s+directory\s+list/) { print OUTFONT $font_line; # for readability, add a line to separate the above line from the # following <dir> lines. print OUTFONT "\n"; last; } if ($font_line =~ /<dir>/) { # Use the first '<dir>' as a marker for inserting the new '<dir>' # lines. last; } print OUTFONT $font_line; } # Write out only <dir> lines. while ($sys_line = <SYSFONT>) { if ($sys_line !~ /<dir>/) { next; } print OUTFONT $sys_line; } # Finally finish up copying our fonts.conf into the tmp file. while ($font_line = <MYFONT>) { if ($font_line =~ /<dir>/) { next; } print OUTFONT $font_line; } close(SYSFONT); close(MYFONT); close(OUTFONT); system(shell_string($gHelper{'cp'}) . " " . $tmp_file . " " . $font_path); # re-add file to database so they it will not stay behind on uninstall # see bug #745860 db_remove_file($font_path); db_add_file($font_path, $cFlagTimestamp); remove_tmp_dir($tmp_dir); } sub configure_auto_kmods { my $ans; my $msg = <<EOF; VMware automatic kernel modules enables automatic building and installation of VMware kernel modules at boot that are not already present. This feature can be enabled/disabled by re-running vmware-config-tools.pl. Would you like to enable VMware automatic kernel modules? EOF $ans = get_persistent_answer($msg, 'AUTO_KMODS_ENABLED_ANSWER', 'yesno', 'yes'); db_add_answer('AUTO_KMODS_ENABLED', $ans); } # Install a pair of S/K startup scripts for a given runlevel sub link_runlevel { my $level = shift; my $service = shift; my $S_level = shift; my $K_level = shift; # # Create the S symlink # install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc' . $level . '.d/S' . $S_level . $service); # # Create the K symlink # install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc' . $level . '.d/K' . $K_level . $service); } # Create the links for VMware's services taking the service name and the # requested levels sub link_services { my @fields; my $service = shift; my $S_level = shift; my $K_level = shift; # Try using insserv if it is available. my $init_style = db_get_answer_if_exists('INIT_STYLE'); if ($gHelper{'insserv'} ne '') { if (0 == system(shell_string($gHelper{'insserv'}) . ' ' . shell_string(db_get_answer('INITSCRIPTSDIR') . '/' . $service) . ' >/dev/null 2>&1')) { return; } } if ("$init_style" eq 'lsb') { # Then we have gotten here, but gone past the insserv section, indicating # that insserv cannot be found. Warn the user... print wrap("WARNING: The installer initially used the " . "insserv application to setup the vmware-tools service. " . "That application did not run successfully. " . "Please re-install the insserv application or check your settings. " . "This script will now attempt to manually setup the " . "vmware-tools service.\n\n", 0); } # Now try using chkconfig if available. # Note: RedHat's chkconfig reads LSB INIT INFO if present. if ($gHelper{'chkconfig'} ne '') { if (0 == system(shell_string($gHelper{'chkconfig'}) . ' ' . $service . ' reset')) { return; } } if ("$init_style" eq 'chkconfig') { # Then we have gotten here, but gone past the chkconfig section, indicating # that chkconfig cannot be found. Warn the user.. print wrap("WARNING: The installer initially used the " . "chkconfig application to setup the vmware-tools service. " . "That application did not run successfully. " . "Please re-install the chkconfig application or check your settings. " . "This script will now attempt to manually setup the " . "vmware-tools service.\n\n", 0); } # Now try using update-rc.d if available. # This is Debian or Ubuntu if ($gHelper{'update-rc.d'} ne ' ') { if (0 == system(shell_string($gHelper{'update-rc.d'}) . " $service defaults")) { return; } } if ("$init_style" eq 'update-rc.d') { # Then we have gotten here, but gone past the update-rc.d section, indicating # that update-rc.d cannot be found. Warn the user.. print wrap("WARNING: The installer initially used the " . "'udpate-rc.d' to setup the vmware-tools service. " . "That command cannot be found. " . "Please re-install the 'sysv-rc' package. " . "This script will now attempt to manually setup the " . "vmware-tools service.", 0); } # Set up vmware to stop at run levels 0 and 6 # if this puzzles you, see Debian bug #351975, then read /etc/init.d/rc : if ((distribution_info() eq "debian") and (not $service eq 'vmware-tools-thinprint')) { # Set up vmware to start at run level S install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rcS.d/S' . $S_level . $service); } else { # Set up vmware to start/stop at run levels 2, 3 and 5 link_runlevel(2, $service, $S_level, $K_level); link_runlevel(3, $service, $S_level, $K_level); link_runlevel(5, $service, $S_level, $K_level); } # Set up vmware to stop at run levels 0 and 6 my $K_prefix = "K"; if ((distribution_info() eq "debian") and (not $service eq 'vmware-tools-thinprint')) { $K_prefix = "S"; } install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc0' . '.d/' . $K_prefix . $K_level . $service); install_symlink(db_get_answer('INITSCRIPTSDIR') . '/' . $service, db_get_answer('INITDIR') . '/rc6' . '.d/' . $K_prefix . $K_level . $service); } # Determines if the system at hand needs to have timer # based audio support disabled in pulse. Make this call # based on the version of pulseaudio installed on the system. # sub pulseNeedsTimerBasedAudioDisabled { my $pulseaudioBin = internal_which("pulseaudio"); my $cmd = "$pulseaudioBin --version"; my $verStr = '0'; if (-x $pulseaudioBin) { open(OUTPUT, "$cmd |"); foreach my $line (<OUTPUT>) { chomp $line; if ($line =~ /pulseaudio *([0-9\.]+)/) { $verStr = $1; last } } if (dot_version_compare($verStr, "0.9.19") ge 0) { # Then pulseaudio's version is >= 0.9.19 return 1; } } return 0 } # Disables timer based audio scheduling in the default config # file for PulseAudio # sub pulseDisableTimerBasedAudio { my $cfgFile = '/etc/pulse/default.pa'; my $regex = qr/^ *load-module +module-(udev|hal)-detect$/; my $tmpDir = make_tmp_dir('vmware-pulse'); my $tmpFile = $tmpDir . '/tmp_file'; my $fileModified = 0; if (not open(ORGPCF, "<$cfgFile") or not open(NEWPCF, ">$tmpFile")) { return 0; } foreach my $line (<ORGPCF>) { chomp $line; if ($line =~ $regex and $line !~ /tsched/) { # add the flag if its not already there. print NEWPCF "$line tsched=0\n"; $fileModified = 1; } else { # just print the line. print NEWPCF "$line\n"; } } close ORGPCF; close NEWPCF; if ($fileModified) { backup_file_to_restore($cfgFile, "orig"); system(join(' ', $gHelper{'cp'}, $tmpFile, $cfgFile)); restorecon($cfgFile); db_add_answer('PULSE_AUDIO_CONFED', $cfgFile); } remove_tmp_dir($tmpDir); } # Check if any modules are installed by VMware sub have_modules_installed { return (keys %gVmwareInstalledModules) > 0; } # Check if any modules which need to be included in system ramdisk # are installed by VMware # return 1 if yes, 0 otherwise sub have_ramdisk_modules_installed { foreach my $mod (@gRamdiskModules) { if (exists $gVmwareInstalledModules{$mod}) { return 1; } } return 0; } sub configure_tools_initscripts { my $srv_table = vmware_services_table(); my $service; # FIXME: need different start/stop levels for different services # for example, we want to start thinprint after cups foreach $service (keys %{$srv_table}){ if (vmware_product() eq 'tools-for-linux' and not db_get_answer_if_exists('UPSTARTJOB')) { # We want to be before networking (because we load network modules). # Being before syslog would be nice, but syslog sometimes starts # after networking, hence this is not possible. # Note: Ensure that these numbers are in sync with the LSB/chkconfig # entries at the top of bora/install/tar/pkg_mgr.pl if ($service eq 'thinprint') { link_services($srv_table->{$service}, '57', '43'); } else { if (distribution_info() eq "debian") { link_services($srv_table->{$service}, '38', '36'); } else { link_services($srv_table->{$service}, '03', '99'); } } } elsif (vmware_product() eq 'tools-for-solaris') { link_services_solaris($srv_table->{$service}, '05', '65'); } } } # Tools configurator sub configure_tools { if ($gSystem{'invm'} eq 'no') { error('This configuration program is to be executed in a ' . 'virtual machine.' . "\n\n"); } # # Stop VMware's services # Also hand-remove vmxnet/vmxnet3 since it is no longer done in services.sh # However, do not fail on failing to rmmod as there are plenty of # totally reasonable cases where this might happen. # print "\n"; # NOTE: See bug 349327. We no longer want to interrupt networking during # tools configuration. #if (!$gOption{'skip-stop-start'}) { # kmod_unload('vmxnet', 0); # if (vmware_product() eq 'tools-for-solaris') { # kmod_unload('vmxnet3s', 0); # } else { # kmod_unload('vmxnet3', 0); # } #} my $srv_table = vmware_services_table(); my $service; if (!$gOption{'skip-stop-start'}) { my $stopCode = 0; print wrap('Making sure services for ' . vmware_product_name() . ' are stopped.' . "\n\n", 0); foreach $service (keys %{$srv_table}){ if (db_get_answer_if_exists('UPSTARTJOB')) { my $str = vmware_service_issue_command($cServiceCommandDirect, $srv_table->{$service}, 'status'); if ($? == 0 and not $str =~ /stop\/waiting/) { vmware_service_issue_command($cServiceCommandSystem, $srv_table->{$service}, 'stop'); $stopCode = $?; } } else { vmware_service_issue_command($cServiceCommandSystem, $srv_table->{$service}, 'stop'); $stopCode = $?; } if ($stopCode != 0) { error('Unable to stop services for ' . vmware_product_name() . "\n\n"); } } } print "\n\n"; # Write the config file, but not the tools.conf file. That file # is for the tools people only and we shouldn't be messing with it. write_new_tools_config(); if (!$gOption{'modules_only'}) { configure_tools_initscripts(); if (vmware_product() eq 'tools-for-solaris') { configure_module_solaris('vmxnet3s'); } # configure the Linux-only drivers # Ensure that vmci gets configured before vsock and vmhgfs # as they both depend on vmci if ( vmware_product() eq 'tools-for-linux') { configure_vmsync(); configure_vmci(); configure_vsock(); configure_vmxnet3(); configure_pvscsi(); } configure_vmmemctl(); configure_vmhgfs(); write_module_config(); # open-vm tools has vmware-vmblock-fuse: if (!$open_vm_compat) { configure_vmblock(); } if (vmware_product() eq 'tools-for-linux') { if (have_modules_installed()) { configure_auto_kmods(); } else { print wrap("\nSkipping configuring automatic kernel modules as " . "no drivers were installed by this installer.\n\n"); } configure_ld_dot_so(); if ($have_thinprint eq 'yes') { configure_thinprint(); } if (!$open_vm_compat) { if (pulseNeedsTimerBasedAudioDisabled()) { pulseDisableTimerBasedAudio(); print "\nDisabling timer-based audio scheduling in pulseaudio.\n\n"; } if ($have_vgauth eq 'yes') { configure_vgauth(); } if ($have_caf eq 'yes') { configure_caf(); } } } if (!$open_vm_compat) { configure_X(); configure_autostart(); } if ( vmware_product() eq 'tools-for-linux') { configure_udev_scsi(); } } else { # Only re-installs modules. # Right now, this is linux-only, not sure it even makes sense for other OS. reinstall_module('vmmemctl'); reinstall_module('vmhgfs'); reinstall_module('vmxnet'); reinstall_module('vmxnet3'); reinstall_module_vmblock(); reinstall_module('vmci'); reinstall_module('vsock'); reinstall_module('pvscsi'); reinstall_module('vmsync'); } # Build dependency data for the new modules so that modprobe can find them. # Even though the Tools services script uses insmod and thus doesn't care for # module dependencies, it makes more sense for the dependencies to be rebuilt # prior to any module use. # # Note: You have to do this before rebuilding the ramdisk. Otherwise some # distros (SLES) will complain. if (vmware_product() eq 'tools-for-linux' and system(join(' ', $gHelper{'depmod'}, getKernRel())) != 0 ) { print wrap("Warning: depmod exited with a non-zero status.\n", 0); } # Rebuild the RamDisk here so new modules are included during the install # process and the module-only process. if (vmware_product() eq 'tools-for-linux') { if (have_ramdisk_modules_installed()) { configure_kernel_initrd(); } else { print wrap("\nSkipping rebuilding initrd boot image for kernel as " . "no drivers to be included in boot image were " . "installed by this installer.\n\n"); } } uninstall_file($gConfFlag); if (!$open_vm_compat) { # We don't ship libconf for Solaris, so we don't need to change the # fonts.conf being used. if (vmware_product() ne 'tools-for-solaris') { configure_fonts_dot_conf(); # This should always be after write_new_tools_config() # because configure_guestproxy() invokes appLoader to # run vmware-guestproxycerttool and appLoader needs # config. if (vmware_product() eq 'tools-for-linux' && $have_grabbitmqproxy eq 'yes') { configure_guestproxy(); } } } # Indicate that VMTools command(s) have replaced those installed by # open-vm-tools, if needed. if ($open_vm_compat && $gOpenVmCompatOverWrite) { db_add_answer('OV_COMPAT_CMD_OVERWRITE', 'yes'); } # # On SELinux enabled VMs, the SELinux context for VMware Tools must # be established before attempting to start any of VMware's services. # Otherwise, a service initializtion script or command used by that # script may be denied access to a resource and the service will # fail to function. if (vmware_product() eq 'tools-for-linux') { manageSELinux ("install"); } # # Then start VMware's services. if (!$gOption{'skip-stop-start'}) { my $srv_table = vmware_services_table(); my $service; foreach $service (keys %{$srv_table}){ vmware_service_issue_command($cServiceCommandSystem, $srv_table->{$service}, 'start'); if ($? != 0) { error('Unable to start services for ' . vmware_product_name() . "\n\n"); } } } if (vmware_product() eq 'tools-for-freebsd') { my $freeBSDVersion = getFreeBSDVersion(); if (dot_version_compare("$freeBSDVersion", '9.0') >= 0) { verify_bsd_xpkgs(); } } if (vmware_product() eq 'tools-for-solaris') { verify_solaris_packages(); } print wrap('The configuration of ' . vmware_longname() . ' for this running ' . 'kernel completed successfully.' . "\n\n", 0); # Remind Solaris users currently using the Xsun server to switch to Xorg if (vmware_product() eq 'tools-for-solaris' && solaris_10_or_greater() eq 'yes' && direct_command(shell_string($gHelper{'svcprop'}) . ' -p options/server ' . 'application/x11/x11-server') =~ /Xsun/) { print wrap('You must restart your X session under the Xorg X server before ' . 'any mouse or graphics changes take effect. Remember to run ' . 'kdmconfig(1M) as root to switch from the Xsun server to the ' . 'Xorg server.' . "\n\n", 0); } elsif (!$open_vm_compat) { print wrap('You must restart your X session before any mouse or graphics changes ' . 'take effect.' . "\n\n", 0); } if (!$open_vm_compat) { my $bindir = db_get_answer('BINDIR'); if (vmware_product() eq 'tools-for-linux') { print wrap('To enable advanced X features (e.g., guest resolution fit, ' . 'drag and drop, and file and text copy/paste), you will need ' . 'to do one (or more) of the following:' . "\n" . '1. Manually start ' . $bindir . '/vmware-user' . "\n" . '2. Log out and log back into your desktop session' . "\n" . '3. Restart your X session.' . "\n\n", 0); } } if (vmware_product() eq 'tools-for-linux') { my $devcounts = get_devices_list(); if (defined(db_get_answer_if_exists('VMXNET_CONFED')) && (db_get_answer('VMXNET_CONFED') eq 'yes')) { if (defined(isKernelBlacklisted())) { $reboot_recommended = 1; # because there are problems rmmod'ing the pcnet32 module on some older # kernels the safest way to pick up the vmxnet module is to reboot. # do not rmmod pcnet32! even by hand! you will terminally confuse the # kernel which will panic or hang very unpredictably. print wrap('to make use of the vmxnet driver you will need to ' . 'reboot.' . "\n",0); } else { if ($devcounts->{'vmxnet'} or $devcounts->{'pcnet32'}) { $reboot_recommended = 1; my $step = 1; print wrap("to use the vmxnet driver, either reboot or\n"); if ($devcounts->{'pcnet32'}) { print wrap("$step. stop networking or stop any interface using the vmxnet or pcnet32 driver\n"); $step++; print wrap("$step. remove the pcnet32 module with 'rmmod pcnet32'\n", 0); $step++; } else { print wrap("$step. stop networking or stop any interface using the vmxnet driver\n"); $step++; } print wrap("$step. remove the vmxnet module with 'rmmod vmxnet'\n"); $step++; print wrap("$step. load the vmxnet module with 'modprobe -v vmxnet'\n"); $step++; print wrap("$step. and restart networking or restart the stopped network interfaces\n\n"); } } } if (defined(db_get_answer_if_exists('VMXNET3_CONFED')) && (db_get_answer('VMXNET3_CONFED') eq 'yes')) { if ($devcounts->{'vmxnet3'}) { $reboot_recommended = 1; print wrap("to use the vmxnet3 driver, either reboot or\n" . "1. stop networking or stop any interface using the vmxnet driver\n" . "2. remove the vmxnet3 module with 'rmmod vmxnet3'\n" . "3. load the vmxnet3 module with 'modprobe -v vmxnet3'\n" . "4. and restart networking or restart the stopped network interfaces\n\n"); } } if (defined(db_get_answer_if_exists('PVSCSI_CONFED')) && (db_get_answer('PVSCSI_CONFED') eq 'yes')) { if ($devcounts->{'pvscsi'}) { $reboot_recommended = 1; print wrap("to use the pvscsi driver please reboot\n\n"); } } } # If open_vmt_compat mode, update the DB with kernel module status. if ($open_vm_compat == 1) { db_add_answer("OVT_VM_MODULES", have_modules_installed() ? "yes" : "no"); db_add_answer("OVT_VM_RAMDISK", have_ramdisk_modules_installed() ? "yes" : "no"); } # If configuration has been called from the installer, do not emit the # "Enjoy" message here. Let the install script emit the message. if (! $gOption{'installing'}) { print wrap('Enjoy,' . "\n\n" . ' --the VMware team' . "\n\n", 0); } } # # Patches and adds a config file for the linker so that certain libs # that we specify will appear in the system library path # sub configure_ld_dot_so { my $source = "/etc/vmware-tools/vmware-tools-libraries.conf"; my $destDir = "/etc/ld.so.conf.d/vmware-tools-libraries.conf"; my $destFile = "/etc/ld.so.conf"; my $blockStr = ''; my $libdir = db_get_answer('LIBDIR'); my $patchKey = '@@LIBDIR@@'; my %patch = ('@@LIBDIR@@' => $libdir); # Try and just lay down the file. If that is not an option, then # edit the ld.so.conf file if possible. Otherwise do nothing. if (internal_which('ldconfig') ne '') { if (-d internal_dirname($destDir)) { install_file($source, $destDir, \%patch, 1); db_add_answer('LD_DOT_SO_DOT_CONF_ADDED_FILE', 'yes'); } elsif (-f $destFile) { open(FD, $source); foreach my $line (<FD>) { chomp $line; $line =~ s/$patchKey/$libdir/; $blockStr .= $line . "\n"; } close(FD); block_append($destFile, $cMarkerBegin, $blockStr, $cMarkerEnd); db_add_answer('LD_DOT_SO_DOT_CONF_MODIFIED', $destFile); } system('ldconfig &> /dev/null'); } # Always set the manifest entries for vmGuestLib to be true # even if we don't install the libs in the system library path. # If we don't, tools might be marked out of date. set_manifest_component('vmguestlib', 'TRUE'); set_manifest_component('vmguestlibjava', 'TRUE'); } # switch_tools_config # Called by the services.sh startup script. # This allows a switch of configuration depending if the system is # booted in a VM or natively. sub switch_tools_config { if ($gSystem{'invm'} eq 'yes') { switch_to_guest(); } else { switch_to_host(); } db_save(); } sub get_httpd_status() { my $command = "/etc/init.d/httpd.vmware status"; local *FD; if (file_name_exist("/etc/init.d/httpd.vmware")) { if (!open(FD, "$command |")) { return 3; } while(<FD>) { if ( /\s*.*stopped.*/ ) { return 3; } else { return 0; } } } return 3; } sub configure_eclipse_plugin { my $eclipseDestDir; my $eclipseSrcDir = db_get_answer("LIBDIR") . '/eclipse-ivd'; # Some builds won't have the eclipse plugin packaged (e.g player). Only install it # if we have it. if (! -d $eclipseSrcDir) { return; } if (get_persistent_answer("Do you want to install the Eclipse Integrated Virtual " . "Debugger? You must have the Eclipse IDE installed.", "ECLIPSEINSTALL", "yesno", "no") eq 'no') { return; } $eclipseDestDir = get_persistent_answer('Which directory contains your eclipse plugins?', 'ECLIPSEDIR', 'dirpath_existing', ""); if ($eclipseDestDir eq "") { # don't install if the user (or --default) chose a bogus dir. return; } install_symlink($eclipseSrcDir . '/com.vmware.bfg_1.0.0', $eclipseDestDir . '/com.vmware.bfg_1.0.0'); } # Returns the console name of the product for use in a .desktop file sub getDesktopConsoleName { return vmware_product_name(); } # Returns the name of the .desktop file to produce sub getDesktopFileName { if (vmware_product() eq "ws") { return "vmware-workstation.desktop"; } return undef; } # Returns the name of the icon file to produce sub getIconFileName { if (vmware_product() eq "ws") { return "vmware-workstation.png"; } return undef; } # Creates a .desktop file sub createDesktopFile { my $use_desktop_utils = shift; my $mime_support = shift; my $desktopFilename = shift; my $productName = shift; my $iconShortFile = shift; my $execName = shift; my $comment = shift; my $mimetypes = shift; my $visible = shift; my $desktopConf; my $tmpdir; my $iconFile = db_get_answer("ICONDIR") . "/hicolor/48x48/apps/$iconShortFile"; my $pixmapFile = db_get_answer("PIXMAPDIR") . "/$iconShortFile"; my $iconName = $iconShortFile; $iconName =~ s/\.[^.]*$//; $tmpdir = make_tmp_dir($cTmpDirPrefix); $desktopConf = "$tmpdir/$desktopFilename"; if (!open(DESKTOP, ">$desktopConf")) { print STDERR wrap("Couldn't open \"$desktopConf\".\n" . "Unable to create the .desktop menu entry file. " . "You must add it to your menus by hand.\n", 0); remove_tmp_dir($tmpdir); return; } print DESKTOP <<EOF; [Desktop Entry] Encoding=UTF-8 Name=$productName Comment=$comment Exec=$execName Terminal=false Type=Application Icon=$iconName StartupNotify=true Categories=System; X-Desktop-File-Install-Version=0.9 MimeType=$mimetypes EOF if ($visible == 0) { print DESKTOP "NoDisplay=true\n"; } close(DESKTOP); safe_chmod(0644, $desktopConf); install_symlink($iconFile, $pixmapFile); my $desktopdir = db_get_answer("DESKTOPDIR"); # Make sure the executable exists. if (internal_which("desktop-file-install") eq "") { $use_desktop_utils = 0; } if ($use_desktop_utils == 1) { my $params = ""; if ($mime_support == 1) { $params = "--rebuild-mime-info-cache "; } if (system("desktop-file-install --vendor=vmware " . "--dir=" . shell_string($desktopdir) . " " . $params . shell_string($desktopConf))) { print STDERR wrap("Unable to install the .desktop menu entry file. " . "You must add it to your menus by hand.\n", 0); remove_tmp_dir($tmpdir); return; } db_add_file("$desktopdir/$desktopFilename", 1); } else { my %p; undef %p; install_file($desktopConf, "$desktopdir/$desktopFilename", \%p, 1); } remove_tmp_dir($tmpdir); } # Determine the directory for the icon and .desktop file, and install them sub configureDesktopFiles { my $use_desktop_utils = 1; my $mime_support = 0; my $pixmapdir; my $desktopdir; my $vmwareBinary; if (!isDesktopProduct() || !$gOption{'create_shortcuts'}) { return; } # NOTE: We don't uninstall the desktop file if we used # desktop-file-install, because there is no desktop-file-uninstall. $desktopdir = db_get_answer_if_exists("DESKTOPDIR"); if (defined($desktopdir)) { # Uninstall uninstall_prefix($desktopdir); } $pixmapdir = db_get_answer_if_exists("PIXMAPDIR"); if (defined($pixmapdir)) { # Uninstall uninstall_prefix($pixmapdir); } $desktopdir = get_persistent_answer( "What directory contains your desktop menu entry files? " . "These files have a .desktop file extension.", "DESKTOPDIR", "dirpath", "/usr/share/applications"); if (internal_which("desktop-file-install") eq "") { $use_desktop_utils = 0; create_dir($desktopdir, $cFlagDirectoryMark); } else { my $buf = `desktop-file-install --help 2>&1`; if ($buf =~ /--rebuild-mime-info-cache/) { $mime_support = 1; } } $pixmapdir = get_persistent_answer("In which directory do you want to " . "install the application's icon?", "PIXMAPDIR", "dirpath", "/usr/share/pixmaps"); create_dir($pixmapdir, $cFlagDirectoryMark); my $vmwareBinPath = db_get_answer('BINDIR'); if (vmware_binary() ne "vmplayer") { my $mimetypes = "application/x-vmware-vm;"; if (vmware_product() eq "ws") { $mimetypes .= "application/x-vmware-team;"; if (defined db_get_answer_if_exists('VNETLIB_CONFED')) { createDesktopFile($use_desktop_utils, $mime_support, "vmware-netcfg.desktop", "Virtual Network Editor", "vmware-netcfg.png", "$vmwareBinPath/vmware-netcfg", "Manage networking for your virtual machines", "", 1); } } $vmwareBinary = $vmwareBinPath . '/' . vmware_binary(); createDesktopFile($use_desktop_utils, $mime_support, getDesktopFileName(), getDesktopConsoleName(), getIconFileName(), $vmwareBinary, "Run and manage virtual machines", $mimetypes, 1); } if (isDesktopProduct()) { $vmwareBinary = $vmwareBinPath . '/vmplayer'; # Player is bundled with all desktop products. createDesktopFile($use_desktop_utils, $mime_support, "vmware-player.desktop", "VMware Player", "vmware-player.png", $vmwareBinary, "Run a virtual machine", "application/x-vmware-vm;", 1); } } # Creates a mimetype package description file sub createMimePackageFile { my $tmpdir; my $mimeConf; my $mimePath; my $mimePackagePath; my $desticondir; my %p; if (!isDesktopProduct()) { return; } $mimePath = "/usr/share/mime"; $mimePackagePath = $mimePath . "/packages"; # Uninstall uninstall_prefix($mimePackagePath); # Create the new mimetype package create_dir($mimePackagePath, $cFlagDirectoryMark); $tmpdir = make_tmp_dir($cTmpDirPrefix); $mimeConf = "$tmpdir/vmware.xml"; if (!open(MIMEPACKAGE, ">$mimeConf")) { print STDERR wrap("Couldn't open \"$mimeConf\".\n" . "Unable to create the MIME-Type package file.\n", 0); remove_tmp_dir($tmpdir); return; } print MIMEPACKAGE <<EOF; <?xml version="1.0" encoding="UTF-8"?> <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> <mime-type type="application/x-vmware-vm"> <comment xml:lang="en">VMware virtual machine</comment> <magic priority="50"> <match type="string" value='config.version = "' offset="0:4096"/> </magic> <glob pattern="*.vmx"/> </mime-type> <mime-type type="application/x-vmware-vmdisk"> <comment xml:lang="en">VMware virtual disk</comment> <magic priority="50"> <match type="string" value="# Disk DescriptorFile" offset="0"/> <match type="string" value="KDMV" offset="0"/> </magic> <glob pattern="*.vmdk"/> </mime-type> <mime-type type="application/x-vmware-team"> <comment xml:lang="en">VMware team</comment> <magic priority="50"> <match type="string" value='<Foundry version="1">' offset="0"> <match type="string" value="<VMTeam>" offset="23:24"/> </match> </magic> <glob pattern="*.vmtm"/> </mime-type> <mime-type type="application/x-vmware-snapshot"> <comment xml:lang="en">VMware virtual machine snapshot</comment> <magic priority="50"> <match type="string" value="\\0xD0\\0xBE\\0xD0\\0xBE" offset="0"/> </magic> <glob pattern="*.vmsn"/> </mime-type> <mime-type type="application/x-vmware-vmfoundry"> <comment xml:lang="en">VMware virtual machine foundry</comment> <magic priority="50"> <match type="string" value='<Foundry version="1">' offset="0"> <match type="string" value="<VM>" offset="23:24"/> </match> </magic> <glob pattern="*.vmxf"/> </mime-type> EOF print MIMEPACKAGE "</mime-info>\n"; close MIMEPACKAGE; safe_chmod(0644, $mimeConf); undef %p; install_file($mimeConf, $mimePackagePath . "/vmware.xml", \%p, 1); remove_tmp_dir($tmpdir); # Update the MIME database if (internal_which("update-mime-database") ne "") { if (system("update-mime-database " . shell_string($mimePath) . " >/dev/null 2>&1")) { print STDERR wrap("Unable to update the MIME-Type database.\n", 0); return; } } $desticondir = get_persistent_answer( "In which directory do you want to install the theme icons?", "ICONDIR", "dirpath", "/usr/share/icons"); undef %p; my $srcicondir = db_get_answer('LIBDIR') . '/share/icons/hicolor'; $desticondir = $desticondir . '/hicolor'; foreach my $sizedir (internal_ls($srcicondir)) { if (! -d $srcicondir . '/' . $sizedir) { next; } foreach my $category (qw(apps mimetypes)) { my $catdir = $sizedir . '/' . $category; if (! -d $srcicondir . '/' . $catdir) { next; } create_dir($desticondir . '/' . $catdir, $cFlagDirectoryMark); foreach my $icon (internal_ls($srcicondir . '/' . $catdir)) { my $iconpath = $catdir . '/' . $icon; install_symlink($srcicondir . '/' . $iconpath, $desticondir . '/' . $iconpath); if ($category eq 'mimetypes') { install_symlink($desticondir . '/' . $iconpath, $desticondir . '/' . $catdir . '/gnome-mime-' . $icon); } } } } # Refresh icon cache. Some systems (Ubuntu) don't do it automatically system(internal_which('touch') . ' -m ' . shell_string($desticondir) . '>/dev/null 2>&1'); system(internal_which('touch') . ' -m ' . shell_string($srcicondir) . '>/dev/null 2>&1'); system(shell_string(internal_which('gtk-update-icon-cache')) . ' >/dev/null 2>&1'); system(shell_string(internal_which('gtk-update-icon-cache')) . " -t $srcicondir >/dev/null 2>&1"); db_add_file($srcicondir . "/icon-theme.cache", 0) } # Given a bunch of db vars, organize them into a sequence of val=key pairs so the # resulting string can be used in a command line. sub assemble_command_line { my @Args = @_; my $string = " "; my $flag; foreach $flag (@Args) { if (db_get_answer_if_exists($flag)) { $string .= $flag . '=' . db_get_answer($flag) . ' '; } elsif (defined($gOption{$flag})) { $string .= '--' . $flag; if ($gOption{$flag} =~ /\S/) { $string .= '=' . $gOption{$flag} . ' '; } } } return $string; } sub install_vix { my $tmpDir = make_tmp_dir('vmware-vix-installer'); my $vixFileRoot = db_get_answer('LIBDIR') . '/vmware-vix/vmware-vix'; my $vixTarFile = $vixFileRoot . '.tar.gz'; my $cmd; # Since we're not on Solaris, whose tar doesn't support '.gz' and # therefore needs gunzip, we need only look for a file ending in # '.tar.gz' and not worry about the '.tar' case. if (!-f $vixTarFile) { return 1; } my $opts = ' -zxopf '; $opts = ' -C ' . $tmpDir . $opts; $cmd = shell_string($gHelper{'tar'}) . $opts . shell_string($vixTarFile); if (system($cmd)) { remove_tmp_dir($tmpDir); print wrap('Untarring ' . $vixTarFile . ' failed.' . ".\n", 0); return 1; } my $vixInstallFile = '/vmware-vix-distrib/vmware-install.pl'; my $defaultOpts = ($gOption{'default'} == 1) ? ' --default' : ''; $defaultOpts .= assemble_command_line(qw(EULA_AGREED NESTED UPGRADE prefix)); # Reset the EULA value so the next install asks the question again. if (db_get_answer_if_exists('EULA_AGREED')) { db_remove_answer('EULA_AGREED'); } if (system(shell_string($tmpDir . $vixInstallFile) . ' ' . $defaultOpts)) { remove_tmp_dir($tmpDir); return 1; } remove_tmp_dir($tmpDir); return 0; } # Check for kernels that won't tolerate removing pcnet32 from the # list of in use modules. If there is an entry in the blacklist # and it is a 'yes', then that kernel is blacklisted. If not a # 'yes', then treat the value is more of the blacklisted version # string. See if with the appended value, the blacklist string # matches a part of the uts_release value of the system's kernel. sub isKernelBlacklisted { my $result = $cPCnet32KernelBlacklist{$gSystem{'version_utsclean'}}; if (!defined($result)) { return undef; } if ($result eq 'yes') { return $result; } # append extra version bit and see if a regexp finds it in # the current systems uts_release value. my $extendedVersion = $gSystem{'version_utsclean'} . $result; if ($gSystem{'uts_release'} =~ "^$extendedVersion") { return $extendedVersion; } return undef; } # Set manifest component info sub set_manifest_component { my $name = shift; my $installed_flag = shift; my $i; for $i (0 .. $#gManifestNames) { if ($gManifestNames[$i] eq $name) { $gManifestInstFlags[$i] = $installed_flag; last; } } } # Write component version info to the manifest file sub write_manifest_file { my $manifest = $gRegistryDir . '/manifest.txt'; my $line1; my $line2; my $i; if (!open(MANIFESTFILE, ">$manifest")) { return; } for $i (0 .. $#gManifestNames) { $line1 = $gManifestNames[$i] . '.version = "' . $gManifestVersions[$i] . '"'; print MANIFESTFILE $line1 . "\n"; if ($gManifestNames[$i] ne 'monolithic') { $line2 = $gManifestNames[$i] . '.installed = "' . $gManifestInstFlags[$i] . '"'; print MANIFESTFILE $line2 . "\n"; } } close(MANIFESTFILE); db_add_file($manifest, 0x0); } # Initialize version manifest sub init_version_manifest { my $manifest_shipped = $gRegistryDir . '/manifest.txt.shipped'; my @data_lines; my $line; my $name; if (open(VERSIONDATA, "<$manifest_shipped")) { @data_lines = <VERSIONDATA>; foreach (@data_lines) { chomp($_); $line = $_; $name = substr($line, 0, index($line, '.')); if ($name ne '') { push(@gManifestNames, $name); $line =~ /(\d+\.\d+\.\d+(\.\d+)?)/; push(@gManifestVersions, $1); push(@gManifestInstFlags, 'FALSE'); } } close(VERSIONDATA); } } # Internationalization data file sub symlink_icudt44l { my $libdir = db_get_answer('LIBDIR'); install_symlink($libdir . '/icu', $gRegistryDir . '/icu'); } sub bsd_print_pkg_advice { my $util = ''; if (internal_which('pkg') ne '') { $util = 'pkg'; } elsif (internal_which('pkg_add') ne '') { $util = 'pkg_add'; } if ($util ne '') { print wrap ('The easiest way to install this package is by using ' . 'the ' . $util . ' utility. Refer to the man pages on how to ' . 'properly use this utility.' . "\n\n", 0); } else { print wrap ('Use the standard package management utility available for ' . 'this version of FreeBSD to install this package.' . "\n\n", 0); } } # The VMware Tools for FreeBSD 6 and beyond are shared. For FreeBSD 7+ users, # the Tools depend on the "misc/compat6x" package. (This package contains # libraries and other support files necessary to run FreeBSD 6 binaries.) # # This routine looks for the libraries, and if they aren't found, informs the # user and prompts him to determine whether or not we continue with installation. sub verify_bsd_libcompat { # Query ldconfig(1) for necessary FreeBSD 6 libraries. my ($ldconfigOutput); $ldconfigOutput = `ldconfig -r`; unless (($ldconfigOutput =~ /(^|\n)[ \t]*\d+:-lc\.6 => /) && ($ldconfigOutput =~ /(^|\n)[ \t]*\d+:-lm\.4 => /)) { my $pkg_name = 'compat6x-' . (is64BitUserLand() ? 'amd64' : 'i386'); my $version = getFreeBSDVersion(); print wrap (vmware_product_name() . " for FreeBSD $version depends on " . "libraries provided by the $pkg_name package. Unfortunately we were " . 'unable to locate these libraries on your system. Please install ' . "the $pkg_name package from the FreeBSD Ports Tree before " . 'you attempt to configure ' . vmware_product_name() . ".\n\n", 0); bsd_print_pkg_advice(); error("Please re-run this program after installing the $pkg_name " . 'package.' . "\n"); } } # Check if a package is installed # pkg_info prints the list of all installed packages including their # version number # We check that the package name is at the beginning of the line, and # is followed by a dash and numeric version number sub bsd_has_package { my $pkg = shift; my $arg = ''; my $pkginfo_path = internal_which('pkg_info'); if ($pkginfo_path eq ''){ # In FreeBSD 10, 'pkg info' is used instead of pkg_info: $pkginfo_path = internal_which('pkg'); if ($pkginfo_path eq ''){ error("This program could not find a valid path to pkg_info or pkg. " . "Please ensure that one of the pkg_info or pkg binaries is installed " . "in the system path.\n\n"); } $arg = ' info'; } my $pkg_info_output = `$pkginfo_path$arg`; return ($pkg_info_output =~ /^$pkg-\d/m); } # VMware toold for FreeBSD 9 depend on the installed packages # xf86-video-vmware and xf86-input-vmmouse (#544256). # This function checks for these packages and gives a notice # if they are not installed sub verify_bsd_xpkgs { my @xpkgs = ('xf86-video-vmware', 'xf86-input-vmmouse'); my @xpkgs_missing; my $version = getFreeBSDVersion(); my $pkg_name = ''; foreach $pkg_name (@xpkgs){ unless(bsd_has_package($pkg_name)){ push(@xpkgs_missing, $pkg_name); } } if(scalar(@xpkgs_missing) > 0){ print wrap ("For " . vmware_product_name() . " for FreeBSD $version it is " . "recommended that you install the following packages:\n\n"); foreach $pkg_name (@xpkgs_missing){ print " $pkg_name\n"; } print "\n"; bsd_print_pkg_advice(); } } # Packaging requirements have changed with Solaris 11.4. Determine # if installing on Solaris release 11.4 or later. # Returns 1 (true) if Solaris 11.4 or later; 0 (false) otherwise. sub is_solaris_114_or_later { my $release = direct_command(shell_string($gHelper{'uname'}) . ' -r'); my $version = getSolarisVersion(); chomp($release); if (dot_version_compare("$release", '5.11') >= 0 && dot_version_compare("$version", '11.4') >= 0) { return 1; } return 0; } # On Solaris, the SUNWuiu8 package has always been needed. As of # Solaris 11.4 (beta), that is no longer a requirement. If the SUNWuiu8 # package is needed and not currently installed, terminate this # configuration process. sub verify_solaris_SUNuiu8_need { if (is_solaris_114_or_later() == 1) { return; } if(does_solaris_package_exist('SUNWuiu8') == 0){ error("Package \"SUNWuiu8\" not found. " . "This package must be installed in order " . "for configuration to continue." . "\n\n"); } } sub verify_solaris_packages { my $pkg_name = "libpng"; my $pkg_cmd = internal_which('pkg'); if (is_solaris_114_or_later() == 1) { # Solaris 11.4 provides multiple versions of libpng. Check for a # specific package - libpng14 (version 1.4.x) $pkg_name = "libpng14"; } if ($pkg_cmd ne '') { my $ret = system("$pkg_cmd info $pkg_name > /dev/null 2>&1"); if ($ret != 0) { print wrap("For full functionality of " . vmware_product_name() . " for " . "Solaris, you need to install $pkg_name. You can use the command " . "'$pkg_cmd install $pkg_name'.\n\n"); } } else { print wrap("Unable to determine if $pkg_name is installed. Please " . "make sure that you have $pkg_name installed for full " . "functionality.\n\n"); } } # check_for_vmw_mods_in_kernel # # Checks /sys/module for our kernel modules. This only works on the # running kernel. # sub check_for_vmw_mods_in_kernel { my $k; my $v; return unless (getKernRel() eq $gSystem{'uts_release'}); while (($k, $v) = each %cUpstrKernelModNames) { my $path = join('/', '/sys/module', $k); if (-e $path) { $gVmwareRunningModules{$v} = $k; } } } # Extracts the alias and module name from a line, if it starts with "alias"; # Returns empty strings otherwise. Designed for use only when parsing # modules.alias. # sub extract_alias_and_modname { my $line = shift; my $alias = ""; my $modname = ""; if($line =~ m/alias (.*)\s(.*)\s/) { $alias = "$1"; $modname = "$2"; } return ($alias, $modname); } # Returns the full path to the first argument. Returns first argument # if it is already a full path, returns the join() of the second and # first arguments otherwise. # sub get_full_module_path { my $module = shift; my $path = shift; chomp($module); # get rid of colon and anything following (if it exists) if($module =~ m/(.*):/) { $module = "$1"; } my $fullPath; # if it starts with a slash, then it's already the full path if ($module =~ m/^\//) { $fullPath = $module; } else { # otherwise, get the path from the parameters and append $fullPath = join('/', $path, $module); } if(not -e $fullPath) { print wrap("WARNING: A module identified in modules.dep " . "could not be found. modules.dep may be out of date. " . "We recommend you run 'depmod -a' and then re-run this " . "configurator.\n\n", 0); } return $fullPath; } # Looks for a module (*.ko) in modules.dep and returns # the full path to it if it exists; returns an empty string otherwise # sub search_for_module_in_moddep { my $modName = shift; my $libModPath = shift; if(open(MODDEP, "$libModPath/modules.dep")) { my $modPath=''; while(<MODDEP>) { my $line = "$_"; if (($line =~ m/(.*$modName):.*/) || ($line =~ m/(.*$modName\.xz):.*/)){ $modPath = get_full_module_path("$1", "$libModPath"); last; } } close(MODDEP); return $modPath; } else { error("Unable to open kernel module dependency file\n."); } } # Reduce PCI id by removing trailing data (subvendor, etc) from PCI IDs. # Returns the reduced PCI id if "pci" start's the string, otherwise returns # the original string. # sub reduce_pciid { my $string = shift; if ($string=~ m/^(pci:v[0-9A-F]{8}d[0-9A-F]{8})/) { $string= "$1"; } return $string; } # Returns a list of VMware kernel modules that were # found on the system that were not placed there by the installer # by parsing modules.alias. # sub populate_vmw_modules_via_aliases_file { my $libModPath = shift; if(open(MODALIAS, "$libModPath/modules.alias")) { my @kernelModulesCopy = @cKernelModules; my ($alias, $actualMod, $modName, $modPath); while(<MODALIAS>) { ($alias, $actualMod) = extract_alias_and_modname("$_"); $alias = reduce_pciid($alias); $modName = $cKernelModuleAliases{"$alias"}; if (defined $modName) { # then a module alias matched one of our modules $modPath = search_for_module_in_moddep("$actualMod.ko", $libModPath); # remove $modName from @kernelModulesCopy @kernelModulesCopy = grep { $_ ne $modName } @kernelModulesCopy; check_if_vmw_installed_module($modName, $modPath); } } # search for any of the remaining modules for which # we did not find a module alias. Have to do this # second because it uses kernelModulesCopy, which is changed above foreach my $mod (@kernelModulesCopy) { $modPath = search_for_module_in_moddep("$mod.ko", $libModPath); if (not $modPath eq '') { check_if_vmw_installed_module($mod, $modPath); } } close(MODALIAS); } else { error("Unable to open modules.alias file\n."); } } # Returns a list of VMWare kernel modules that were # found on the system that were not placed there by the installer # by parsing modules.dep, modinfo-ing the module, and parsing # the output of modinfo. # sub populate_vmw_modules_via_modinfo { my $libModPath = shift; if (open(MODULESDEP, "$libModPath/modules.dep")) { my $modPath = ''; while (<MODULESDEP>) { if (/^(.*\.k?o):.*$/) { # # Then the module may not be there. In Ubuntu 9.04, modules.dep # no longer has a full path for the modules. Therefore we must # try out both a full path and one relative to the modules # directory of the currently running kernel. # $modPath = get_full_module_path("$1","$libModPath"); if (defined $modPath) { check_if_vmware_module($modPath); } } } close(MODULESDEP); } else { error("Unable to open kernel module dependency file\n."); } } # # Checks to see if the given module shares a name or PCI id with ours. # If there's a PCI or name match, send it to check_if_vmw_installed_module # to see if it's actually ours. # # This does the checks in the following order # 1. Check for PCI IDs # 2. Check for VMware module Aliases # 3. Check for module file names (legacy). # sub check_if_vmware_module { my $modPath = shift; my $modInfoCmd = shell_string($gHelper{'modinfo'}) . " -F alias $modPath 2>/dev/null"; my @modInfoOutput = map { chomp; $_ } (`$modInfoCmd`); my $line; my $modName; undef $modName; # First check for PCI IDs/Aliases foreach $line (@modInfoOutput) { $line = reduce_pciid($line); $modName = $cKernelModuleAliases{"$line"}; if (defined $modName) { check_if_vmw_installed_module($modName, $modPath); return; } } # Finally check the module name. if ($modPath =~ m,^.*/(\w+)\.k?o,) { foreach my $mod (@cKernelModules) { if ("$1" eq $mod) { check_if_vmw_installed_module($mod, $modPath); return; } } # If the module has been clobbered, the name is in the alias list. if (defined $cKernelModuleAliases{$1}) { return $cKernelModuleAliases{$1}; } } } # This function checks to see if the given module (modName) # is not in the db file; it adds the result # to gVmwareInstalledModules if it is in the db file # sub check_if_vmw_installed_module { my $modName = shift; my $modPath = shift; if (not -e $modPath) { return; } if (not db_file_in($modPath)) { # Add $modName module with path $modPath to bad list # Check to see if we have already found a module for this. If # so, there is not much we can do. Instead just warn the user. if (defined $gNonVmwareModules{$modName}) { print wrap("WARNING: A module identified as $modName has been found " . "at $gNonVmwareModules{$modName} and at $modPath. " . "Leaving both modules in there could potentially " . "cause a race condition when a device is added. " . "We recommend you remove one of them, run " . "'depmod -a', and then re-run this configurator.\n\n", 0); } $gNonVmwareModules{$modName} = "$modPath"; } else { # Its one of our modules. Lets keep track of where they are as # they might not be in the standard locations $gVmwareInstalledModules{$modName} = "$modPath"; } } sub set_module_status { my $mod = shift; my $status = shift; $gInstallStatus{$mod} = $status; } sub initialize_module_status { my $modconfig = make_modconfig_command("--install-status"); if(not open(STATIN, "$modconfig |")) { error("Unable to run $modconfig\n"); } while(<STATIN>) { if(/^(.*):\ (.*)$/){ $gInstallStatus{$1} = $2; } } close(STATIN); } # Returns a list of VMware kernel modules that were # found on the system that were not placed there by the installer. # Also checks the running kernel for modules that were built in when # the kernel was compiled. sub populate_vmware_modules { my $libModPath = join('/','/lib/modules', getKernRel()); # can't continue without the modules.dep file system(shell_string($gHelper{'depmod'}) . ' -a'); if (not -e "$libModPath/modules.dep") { error("Unable to find kernel module dependency file\n."); } if (-e "$libModPath/modules.alias") { populate_vmw_modules_via_aliases_file($libModPath); } else { populate_vmw_modules_via_modinfo($libModPath); } check_for_vmw_mods_in_kernel(); } ## # does_solaris_package_exist # # Executes a system call to check if the given package (passed in as # a parameter) exists. # # @param[in] $packageName # # @returns 1 (true) if package exists, 0 (false) otherwise # sub does_solaris_package_exist { my $packageName = shift; system("pkginfo $packageName > /dev/null 2>&1"); return $? == 0 ? 1 : 0; } ## # get_resolution # # This is a 'nice-to-have' function that will first attempt to get the guest's # original resolution, fall back to the host's resolution if xrandr fails, # doesn't exist, or is run over ssh. If the 'host' has no resolution (e.g., # a VM being run on ESX), then vmware-checkvm returns '0x0'. # # @returns a string - "0x0" if xrandr fails and on an ESX box, or a nonzero "#x#" # resolution otherwise sub get_resolution { my $currentRes; my @resSplit; my $width; my $height; my $xrandrPath; # xrandr may not be in standard locations, so we need to still attempt # to find it elsewhere. $xrandrPath = internal_which('xrandr'); if ($xrandrPath eq '') { $xrandrPath = internal_which('/usr/X11R6/bin/xrandr'); } # try to use the guest resolution first if ($xrandrPath ne '' and system("$xrandrPath > /dev/null 2>&1") == 0) { my $currentRes = direct_command("$xrandrPath 2>/dev/null | grep \\*"); # get the line with the resolution # in rare cases no line has an asterisk. Ignore in this case. if(defined($currentRes)) { # tragically, xrandr returns a different style of string for old xrandr (~RHEl 3.x, etc.) # so we need to handle both cases @resSplit = split(' ', $currentRes); if (@resSplit) { ($width, $height) = split('x', $resSplit[0]); if(defined($width) and defined($height)) { if ($width =~ /^\d+$/ and $height =~ /^\d+$/) { # traditional (newer) xrandr return "$width" . "x" . "$height"; } elsif ($resSplit[1] =~ /^\d+$/ and $resSplit[3] =~ /^\d+$/) { #RHEL3 style return "$resSplit[1]" . "x" . "$resSplit[3]"; } } } } } # if we couldn't get it thru xrandr, then default to vmware-checkvm # NOTE: this will return "0x0" on ESX systems $currentRes = direct_command(shell_string(vmware_check_vm_app_name()) . ' -r'); chomp($currentRes); return $currentRes; } # Program entry point sub main { my (@setOption, $opt); if (not is_root()) { error('Please re-run this program as the super user.' . "\n\n"); } # Force the path to reduce the risk of using "modified" external helpers # If the user has a special system setup, he will will prompted for the # proper location anyway $ENV{'PATH'} = '/bin:/usr/bin:/sbin:/usr/sbin'; initialize_globals(); if (not (-e $gInstallerMainDB)) { error('Unable to find the database file (' . $gInstallerMainDB . ')' . "\n\n"); } db_load(); db_append(); initialize_external_helpers(); # If we are configuring the tools, and the installer instructed us to # send the end RPC, specify a signal handler in case the user Ctrl-C's # early. The handler will send the RPC before exiting. if ($gOption{'rpc-on-end'} == 1) { $SIG{INT} = \&sigint_handler; $SIG{QUIT} = \&sigint_handler; } # List of questions answered with command-line arguments @setOption = (); # Command line analysis while ($#ARGV != -1) { my $arg; $arg = shift(@ARGV); if (lc($arg) =~ /^(-)?(-)?d(efault)?$/) { $gOption{'default'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?c(ompile)?$/) { $gOption{'compile'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?p(rebuilt)?$/) { $gOption{'prebuilt'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?s(witch)?$/) { $gOption{'tools-switch'} = 1; } elsif (lc($arg) =~ /^--clobber-xorg-modules$/) { $gOption{'clobber-xorg-modules'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?skip-stop-start$/) { $gOption{'skip-stop-start'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?make-all-net$/) { $gOption{'make-all-net'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?r(pc-on-end)?$/) { # Note: rpc-on-end has been defaulting to one for some time now. # Hence this is a moot argument. $gOption{'rpc-on-end'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?(no-create-shortcuts)$/) { $gOption{'create_shortcuts'} = 0; } elsif (lc($arg) =~ /^--regenerate-cert$/) { $gOption{'regenerate-cert'} = 1; } elsif (lc($arg) =~ /^--preserve$/) { $gOption{'preserve'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?log-answers$/) { $gOption{'log-answers'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?prefix=(.+)$/) { $gOption{'prefix'} = $3; } elsif (lc($arg) =~ /^(-)?(-)?m(odules-only)?$/) { if (vmware_product() ne 'tools-for-linux') { error("Cannot build modules for a non-linux OS.\n"); } $gOption{'modules_only'} = 1; } elsif (lc($arg) =~ /^(-)?(-)?k(ernel-version)?$/) { $gOption{'kernel_version'} = shift(@ARGV); if (vmware_product() ne 'tools-for-linux') { error("Cannot build for non-running kernel on non-linux OS.\n"); } if (!$gOption{'kernel_version'} or $gOption{'kernel_version'} eq '') { error("Must specify a parameter for --kernel-version.\n"); } # Argument validation is deferred till after system_info() is called. } elsif (lc($arg) =~ /^--overwrite$/) { $gOption{'overwrite'} = 1; } elsif (lc($arg) =~ /^--installing$/) { $gOption{'installing'} = 1; } elsif ($arg =~ /=yes/ || $arg =~ /=no/) { push(@setOption, $arg); } else { config_usage(); } } if (@setOption > 0) { $gOption{'default'} = 1; # User must specify 'EULA_AGREED=yes' on the command line db_add_answer('EULA_AGREED', 'no'); } # Install answers specified on the command line foreach $opt (@setOption) { my ($key, $val); ($key, $val) = ($opt =~ /^([^=]*)=([^=]*)/); db_add_answer($key, $val); } if (vmware_product() eq 'tools-for-linux' ) { if ((defined db_get_answer_if_exists('OPEN_VM_COMPAT')) && (db_get_answer('OPEN_VM_COMPAT') eq 'yes')) { $open_vm_compat = 1; } } # Be sure that this is called before anyone attempts to execute any of the # compiled binaries on FreeBSD 7. if (vmware_product() eq 'tools-for-freebsd') { my $freeBSDVersion = getFreeBSDVersion(); if (dot_version_compare("$freeBSDVersion", '7.0') >= 0) { verify_bsd_libcompat(); } } # Be sure that the SUNWuiu8 package, if needed, is installed before # trying to configure. if (vmware_product() eq 'tools-for-solaris') { verify_solaris_SUNuiu8_need(); } if (vmware_product() eq 'tools-for-linux' && $gOption{'tools-switch'} == 0) { init_version_manifest(); } if ($gOption{'tools-switch'} == 0) { setupSymlinks(); } # this call MUST come after setupSymlinks (if setupSymlinks is deemed necessary) system_info(); # system_info needs to be called before we can validate the --kernel-version argument if($gOption{'kernel_version'} ne '') { # First check that they have installed the kernel we are buildind modules for... my $modDepPath = "/lib/modules/$gOption{'kernel_version'}/modules.dep"; if (! -e $modDepPath) { error ("It appears that the $gOption{'kernel_version'} kernel " . "is not installed.\n"); } # --kernel-version always implies modules_only and compile. Only skip stop and start # if the kernel is not the one currently running. $gOption{'modules_only'} = 1; $gOption{'compile'} = 1; if($gOption{'kernel_version'} ne $gSystem{'uts_release'}) { $gOption{'skip-stop-start'} = 1; } } if (($gOption{'compile'} == 1) && ($gOption{'prebuilt'} == 1)) { print wrap('The "--compile" and "--prebuilt" command line options are ' . 'mutually exclusive. Also remember --kernel-version implies' . "--compile. \n\n", 0); config_usage(); } # Tools configurator entry point if ($gOption{'tools-switch'} == 1) { switch_tools_config(); } else { # Initialize the dictionary which tracks non-vmware modules # This only applies to linux currently. if (vmware_product() eq 'tools-for-linux') { initialize_module_status(); populate_vmware_modules(); } symlink_icudt44l(); configure_tools(); if (vmware_product() eq 'tools-for-linux') { write_manifest_file(); } # Try to detect if there is a vmware tools install cd in a drive, # due to the vmx 'install tools' feature, and if so eject it. # # NOTE: You have to check if the image is inserted BEFORE you # send the toolinstall.end RPC message, otherwise it won't # answer corredctly. # Only eject the tools cd AFTER the toolinstall.end RPC command # has been sent. Otherwise the VMX will think you are # trying to cancel the tools install. # See bug 409942 for more details. my $rpcresult = send_rpc('toolinstall.is_image_inserted'); # Send the end RPC along with the results of the configurator run. if ($gOption{'rpc-on-end'} == 1) { if ($reboot_recommended != 0) { # VUM uses this value to determine if a reboot is required. # 3010 is ERROR_SUCCESS_REBOOT_REQUIRED send_rpc('info-set guestinfo.toolsInstallErrCode 3010'); } else { # VUM expects a code different than ERROR_SUCCESS_REBOOT_REQUIRED # or it will reboot anyway. 0 is ERROR_SUCCESS. send_rpc('info-set guestinfo.toolsInstallErrCode 0'); } # The sleep allows time for the Tools service to send its # capabilities, which is needed so the manifest copy will # succeed. sleep(3); send_rpc('toolinstall.end 1'); } if ($rpcresult =~ /1/) { eject_tools_install_cd_if_mounted(); } } # record root access method for later use by module builder if (vmware_product() eq 'tools-for-linux') { if (defined $ENV{'SUDO_USER'}) { db_add_answer('ROOT_ACCESS_METHOD', 'sudo'); } else { db_add_answer('ROOT_ACCESS_METHOD', 'su'); } } db_save(); # make sure changes are flushed to disk before this scripts exits: # (bug #999703) system(internal_which('sync')); exit 0; } main();