Blame | Last modification | View Log | RSS feed
"""Copyright 2008-2019 VMware, Inc. All rights reserved. -- VMware ConfidentialVMware VMX component installer."""GCONF_DEFAULTS = 'xml:readwrite:/etc/gconf/gconf.xml.defaults'DEST = LIBDIR/'vmware'CONFIG = DEST/'setup/vmware-config'CUPSLIBDIR = LIBDIR/'cups'SETTINGS = \{'libdir': DEST,'bindir': BINDIR,'initscriptdir': INITSCRIPTDIR,'gksu.rootMethod': 'sudo' if 'SUDO_USER' in ENV else 'su','NETWORKING': 'yes','VMBLOCK_CONFED': 'yes','VMCI_CONFED': 'yes','VSOCK_CONFED': 'yes','authd.fullpath': SBINDIR/'vmware-authd',}vmwareSentinel = '# Automatically generated by the VMware Installer - DO NOT REMOVE\n'MIN_NESTED_TOOLS_VERSION = '8.9.0'MIN_EQUIVALENT_KERNEL_VERSION = (3, 9)# Present requirement is a RHEL6-like environment.MIN_GLIBC_VERSION = (2, 12)MIN_KERNEL_VERSION = (2, 6, 32)# Player and Workstation both depend on some configuration living# in /etc/vmwareETCDIR = Destination('/etc/vmware')class VMX(Installer):def InitializeQuestions(self, old, new, upgrade):self.AddQuestion('ClosePrograms', key='ClosePrograms', text='',required=True, default='Yes', level='REQUIRED')# Check whether we're in a VM or on real hardware. If we're in a VM, verify that the# proper version of Tools is already installed.self.inVM = Falseself.checkVMTempFile = self.GetFilePath('extra/checkvm')# Get the directory above our installer and write checkvm to it so we can run ittmpdir = path(ENV['VMWARE_INSTALLER']).dirname().dirname()self.checkvmBin = tmpdir/'checkvm'path(self.checkVMTempFile).copy(self.checkvmBin)self.checkvmBin.chmod(0o755)path(self.checkVMTempFile).remove()# Check the environment for forceInstallInVM. This check is here in InitializeQuestions# because it is run before InitializeInstall, where this variable is also used.self.forceInstallInVM = Falsetry:txt = ENV['VMWARE_FORCE_INSTALL_IN_VM']if txt == 'yes':self.forceInstallInVM = Trueexcept KeyError:pass # Okay if it hasn't been setret = self.RunCommand(self.checkvmBin, ignoreErrors=True)if ret.retCode == 0:# We are running in a virtual machine, don't install VSock and VMCIlog.Info('Running inside a virtual machine!')self.inVM = Trueif self.forceInstallInVM:log.Info('But forcing install as if we are not.')self.inVM = Falseif self.inVM:# Now we need to check if Tools is installed. If they are, we need to see if# they are a recent enough version to make this work. If they aren't, then# notify the user that they are out of luck and must upgrade to a newer version# of Tools.minVersion = MIN_NESTED_TOOLS_VERSIONtoolsVersion = self.getToolsVersion()if not self.forceInstallInVM: # Only check if they haven't been overriddenif not toolsVersion:# vmci & vsock is upstreamed since kernel 3.9, and kernel 3.9 doesn't# need vmblock. So, in a distro with kernel later than 3.9, even if# there's no entire vmware tools, driver version is also acceptable.kernelVersion = self.getHostKernelVersion()if self.isVersionOkay(kernelVersion, MIN_EQUIVALENT_KERNEL_VERSION):log.Info('Kernel version is acceptable.')else:log.Info('Kernel version is too old. Related modules will be configured.')msg = 'You are installing inside a virtual machine. It is' \' recommended that you should install at least version' \' %s of VMware Tools before installing this product.' \' This product will work without VMware Tools, but if' \' you choose to install VMware Tools after installing' \' this product, the two products may not work together.' \% minVersionlog.Warn(msg)self.UserMessage ('Warning: %s' % msg)self.inVM = Falseelif not self.isToolsVersionOkay(toolsVersion, minVersion):self.UserMessage('You must have at least version %s of VMware' % minVersion + \' Tools installed to install this product inside a virtual machine.' + \' Your version is %s. Please update VMware Tools.' % toolsVersion)raise Exception('Tools version %s is too low to install inside a VM.')elif ret.retCode == 1:# We are on normal hardware.log.Info('Running on a real machine!')self.inVM = Falseelse:log.Warn('Something went wrong detecting whether we are in a VM or not. Assuming we are not.')self.inVM = Falsedef InitializeInstall(self, old, new, upgrade):if self._checkXenPresence():log.Warn('This system is running a Xen kernel. You cannot run VMs under the Xen kernel.')if path('/dev/kvm').exists():log.Warn('This system has KVM enabled. You cannot run VMs with KVM enabled.')self.AddTarget('File', 'bin/*', BINDIR)self.AddTarget('File', 'sbin/*', SBINDIR)self.AddTarget('File', 'lib/*', DEST)self.AddTarget('File', 'roms/*', DEST/'roms')self.AddTarget('File', 'etc/init.d/vmware', SYSCONFDIR/'init.d/vmware')# Install the proper modules.xml file depending on whether we're installing on a real# machine or inside a VMif not self.inVM or self.forceInstallInVM:self.AddTarget('File', 'extra/modules.xml', DEST/'modules/modules.xml')else:# Use the nested VM modules.xml file and mark modules that Tools provides as unconfigured# so Modconfig doesn't try to rebuild them and the init script doesn't try to manage themself.AddTarget('File', 'extra/modules.nestedVM.xml', DEST/'modules/modules.xml')SETTINGS['VMCI_CONFED'] = 'no'SETTINGS['VSOCK_CONFED'] = 'no'SETTINGS['VMBLOCK_CONFED'] = 'no'# Symlink all binaries to appLoader.for i in ('vmware-modconfig', 'vmware-modconfig-console','vmware-gksu', 'vmware-vmblock-fuse'):self.AddTarget('Link', DEST/'bin/appLoader', DEST/'bin'/i)self.SetPermission(DEST/'bin/*', BINARY)self.SetPermission(SYSCONFDIR/'init.d/vmware', BINARY)self.SetPermission(DEST/'bin/vmware-vmx*', SETUID)self.SetPermission(DEST/'lib/libvmware-gksu.so/gksu-run-helper', BINARY)self.SetPermission(SBINDIR/'vmware-authd', SETUID)# modprobe.d script. Only install this on systems where modprobe.d# exists.if path('/etc/modprobe.d').exists():self.AddTarget('File', 'etc/modprobe.d/modprobe-vmware-fuse.conf','/etc/modprobe.d/vmware-fuse.conf')# Add a link for internationalization directoryself.AddTarget('Link', DEST/'icu', SYSCONFDIR/'vmware/icu')def PreTransactionUninstall(self, old, new, upgrade):self.AddQuestion('ClosePrograms', key='ClosePrograms', text='',required=True, default='Yes', level='REQUIRED')def PreInstall(self, old, new, upgrade):# Remove all modules in case some were left behind somehow. For# example, the installation database could have been blown away.ret, kvers, _ = self.RunCommand('uname', '-r')kvers = kvers.strip()modules = ('vmmon', 'vmnet')if not self.inVM:modules = modules + ('vmblock', 'vmci', 'vsock')base = path('/lib/modules/%s/misc' % kvers)for module in modules:for ext in ('o', 'ko'):mod = '%s.%s' % (module, ext)(base/mod).remove(ignore_errors=True)def PreUninstall(self, old, new, upgrade):script = INITSCRIPTDIR/'vmware'# Stop and deconfigure servicesif ENV.get('VMWARE_SKIP_SERVICES'):log.Info('Skipping stopping services')elif INITSCRIPTDIR and self._scriptRunnable(script) and self.RunCommand(script, 'stop', ignoreErrors=True).retCode != 0:log.Error('Unable to stop VMware services')# Deconfigure servicesinits = self.LoadInclude('initscript')inits.DeconfigureService('vmware')# This must be the last thing done since the actions above may# depend on it.for key in list(SETTINGS.keys()):self.RunCommand(CONFIG, '-d', key)# Remove prelink appLoader exclusion.self._configurePrelink(False)def PostUninstall(self, old, new, upgrade):# Modules have been removed during uninstall. We need to be sure to# run depmod to pick up that change. Don't fail if for some reason# depmod can't be executed though.self.RunCommand('depmod', '-a', ignoreErrors=True)def PostInstall(self, old, new, upgrade):for key, val in list(SETTINGS.items()):self.RunCommand(CONFIG, '-s', key, val)bootstrap = ETCDIR/'bootstrap'# Create the bootstrap file.bootstrap.unlink(ignore_errors=True)# Fill it.for i in ('PREFIX', 'BINDIR', 'SBINDIR', 'LIBDIR', 'DATADIR','SYSCONFDIR', 'DOCDIR', 'MANDIR', 'INCLUDEDIR', 'INITDIR','INITSCRIPTDIR'):bootstrap.write_text('%s="%s"\n' % (i, globals()[i]), append=True)# Register it.# XXX: Skip registration for this change. Allow the installer to handle# deletion. A later change will correct this.# self.RegisterFile(bootstrap, fileType='ConfigFile')# Configure Gtk+.# @todo: make it its own componentlibconf = DEST/'libconf'replace = libconf/'etc/gtk-3.0/gdk-pixbuf.loaders'template = '@@LIBCONF_DIR@@'self.RunCommand('sed', '-e', 's,%s,%s,g' % (template, libconf),'-i', replace)# Add prelink appLoader exclusion.self._configurePrelink(True)# Set up virtual networkingself._configureNetworking(old, new)# Set up the vmware service scriptinits = self.LoadInclude('initscript')inits.ConfigureService('vmware','This service starts and stops VMware services','$network $syslog', # Start'$network $syslog', # Stop'','',19,8)# Make sure to start servicesscript = INITSCRIPTDIR/'vmware'if INITSCRIPTDIR and script.exists():self.RunCommand(script, 'stop', ignoreErrors=True)self.RunCommand(script, 'start')# If no INITDIR was given, notify the user that the vmware service must# be manually set upif not INITDIR:self.UserMessage('No rc*.d style init script directories were given to the installer. ''You must manually add the necessary links to ensure that the vmware ''service at %s is automatically started and stopped on startup and shutdown.' % str(INITSCRIPTDIR/'vmware'))def _scriptRunnable(self, script):""" Returns True if the script exists and is in a runnable state """return script.isexe() and script.isfile() and self.RunCommand(script, 'validate').retCode == 100def _AddLineToFile(self, fil, text, addToEnd=True):"""Add a line/consecutive lines to a file, surrounded by the VMware Sentinel.### This method only adds a single line to a file ###### You cannot make more than one modification per file ###@param fil: The file name. Either a string or path object@param text: The text to add. This function appends a \n@param addToEnd: Add to the end of the file? If false, to the beginning.@return: True on successful modification, False if the file did not exist."""fil = path(fil) # Make sure it's a path objectif fil.exists():txt = fil.text()# Modify the textif addToEnd:txt = ''.join([txt, vmwareSentinel, text, '\n', vmwareSentinel])else:txt = ''.join([vmwareSentinel, text, '\n', vmwareSentinel, txt])# Write it back to the filefil.write_text(txt)return Trueelse:log.Info('Attempted to modify file %s, does not exist.' % fil)return Falsedef _RemoveLineFromFile(self, fil):"""Remove a line bracketed by the VMware Sentinel@param fil: The file name. Either a string or path object@return: True on successful modification, False if the file did not exist."""fil = path(fil) # Make sure it's a path objectif fil.exists():txt = fil.text()m = re.sub(vmwareSentinel + '.*\n' + vmwareSentinel,'', txt, re.DOTALL)fil.write_text(m)return Trueelse:log.Info('Attempted to modify file %s, does not exist.' % fil)return Falsedef _configurePrelink(self, enable):"""Configures prelinking by adding appLoader exclusion.@param enable: True if to add exclusion, False if to remove it."""prelink = path('/etc/prelink.conf')if prelink.exists():# XXX: It would probably be good to refactor this into some# sort of helper function that can add and remove lines from# files.skipPrelink = [ '# appLoader will segfault if prelinked.','-b %s' % (DEST/'bin/appLoader') ]lines = prelink.lines(encoding='utf-8', retain=False)# Strip whitespace from lines so that trailing whitespace# won't impact matching lines.lines = [line.strip() for line in lines]if enable:if skipPrelink[-1] not in lines:lines += skipPrelinklog.Info('Added appLoader prelink exclusion to %s.', prelink)else:log.Warn('appLoader skip prelinking already present.')else:found = Falsefor line in skipPrelink:if line in lines:found = Truelines.remove(line)if found:log.Info('Removed appLoader prelink exclusion from %s.', prelink)# XXX: This can technically fail. Actually, there are a# whole host of things that can fail in this function. On# one hand we would like to catch errors and correct them# while being fairly resilient at installation time.## One option might be to have a @failok decoration for things# that can fail loudly for internal builds but do not cause# installations to fail in release builds.prelink.write_lines(lines, encoding='utf-8')else:log.Info('Prelink not present, skipping configuration.')def _validate_cpu_flags(self, reqFlags):"""Checks to ensure that every CPU on the given machine has all theflags passed in the reqFlags list.@param: A list of the flags that must be present.@returns: True on all necessary flags present, False othewise."""flagSec = FalsecpuInfoFile = '/proc/cpuinfo'try:cpuInfo = open(cpuInfoFile, mode='r')for line in cpuInfo:if line.startswith('flags'):flagSec = TrueflagsFound = line.split(' ')for flag in reqFlags:if flag not in flagsFound:return Falsereturn flagSecexcept IOError:raise Exception('Could not open %s' % (cpuInfoFile))finally:if cpuInfo:cpuInfo.close()def PreTransactionInstall(self, old, new, upgrade):if ENV.get('VMWARE_SKIP_VERSION_CHECKS'):return # skip all checks# CPU flags check# TODO: also check for vt / ept / (AMD equivalent)reqFlags = ['lm']cpuOk = self._validate_cpu_flags(reqFlags)kernelOk = self.isVersionOkay(self.getHostKernelVersion(),MIN_KERNEL_VERSION)glibcOk = self.isVersionOkay(self.getHostGlibcVersion(),MIN_GLIBC_VERSION)if not cpuOk:self.UserMessage('One or more of your processors does not have the ''necessary 64bit extensions to run VMware ''virtual machines.')if not kernelOk:self.UserMessage('The Linux kernel version on this system is ''insufficient for running VMware virtual machines. ''Linux kernel version %s or later is required.'% str(MIN_KERNEL_VERSION))if not glibcOk:self.UserMessage('The glibc library version on this system is ''insufficient for running VMware virtual machines. ''Glibc library version %s or later is required.'% str(MIN_GLIBC_VERSION))if not cpuOk or not glibcOk or not kernelOk:raise Exception('Host not supported by this product.')def PostTransactionInstall(self, old, new, upgrade):# Build modules with modconfig. This can't happen during the other install# phases because VMIS has locked the database and modconfig invokes another# version of VMIS to register the compiled modules.if ENV.get('VMWARE_SKIP_MODULES'):log.Info('Skipping kernel module installation')else:# run depmod -a before modconfig in case we are upgrading from a version# that did not properly do this after uninstalling modules. modconfig# uses modules.dep to determine upstream status of modules, therefore# it might get confused if modules.dep is not up-to-date.self.RunCommand('depmod', '-a', ignoreErrors=True)ret = self.RunCommand(BINDIR/'vmware-modconfig', '--console', '--install-all')if ret.retCode == 0:log.Info('Successfully installed kernel modules')else:log.Info('Unable to install kernel modules')log.Info('stdout: %s' % ret.stdout)log.Info('stderr: %s' % ret.stderr)def _checkXenPresence(self):"""Checks whether this install is within a Xen domain,that is running on a Xen kernel. Returns True if so."""xenTest = path('/proc/xen/capabilities')return xenTest.exists() and len(xenTest.text())def _escape(self, string):""" Escapes a string for use in a shell context """# XXX: Borrowed from util/shell.py: Escape. Break that into a component-side# include file and remove this method once that's done.return "'%s'" % string.replace("'", '"\'"')def _configureNetworking(self, old, new):if 'VMWARE_SKIP_NETWORKING' in ENV:returnvnetlib = BINDIR/'vmware-networks'backup = ENV.get('VMWARE_RESTORE_NETWORKING')backup = backup and path(backup)locations = ENV.get('VMWARE_MIGRATE_NETWORKING')locations = locations and path(locations)if backup and backup.owner == 'root':log.Info('Restoring previous network settings from %s', backup)backup.copy('/etc/vmware/networking')backup.remove()old = 1elif locations and locations.owner == 'root' and not old:log.Info('Migrating old network settings from %s', locations)if self.RunCommand(vnetlib, '--migrate-network-settings', locations, ignoreErrors=True).retCode == 0:locations.remove()return Trueelse:log.Info('Migrating network settings failed, forcing new network settings')old = 0new = 1# old might be None so make it 0 for the purposes of vnetlib.old = old or 0return self.RunCommand(vnetlib, '--postinstall', '%s,%s,%s' % ('vmware-player', old, new),ignoreErrors=True).retCode == 0def getToolsVersion(self):toolboxCmd = path('/usr/bin/vmware-toolbox-cmd')toolbox = path('/usr/bin/vmware-toolbox')ret = Noneif toolboxCmd:ret = self.RunCommand(toolboxCmd, '--version', ignoreErrors=True)elif toolbox:ret = self.RunCommand(toolbox, '--version', ignoreErrors=True)else:# Cannot find Tools version...return Noneif ret and ret.retCode == 0:toolsVersion = ret.stdout# Parse ittoolsVersion = re.findall('\d+\.\d+\.\d+', toolsVersion)if toolsVersion:toolsVersion = toolsVersion[0]log.Info('Found Tools version %s' % toolsVersion)return toolsVersionelse:# Something went wrong in the process... No Tools version found...return Noneelse:return Nonedef isToolsVersionOkay(self, version, minVersion):if not version:return Falseversions = self.LoadInclude('versions')if versions.CompareVersionString(version, minVersion) >= 0:log.Info('Tools version is compatible.')return Trueelse:log.Info('Tools version is too old.')return Falsedef getHostGlibcVersion(self):"""Return host's glibc version as a tuple."""try:import subprocessout = subprocess.check_output(["ldd", "--version"]).decode()brand = out.split('\n', 1)[0]except subprocess.CalledProcessError:log.Info('Unable to query glibc version.')return None# brand = full ldd brand string, e.g. 'ldd (Distro GLIBC N.NN) 2.17'log.Info('Glibc brand string ' + brand)try:# versionStr = glibc version, e.g. '2.17'versionStr = brand.split(' ')[-1]glibcVersion = tuple([int(x) for x in versionStr.split(".")])log.Info('Found glibc version ' + str(glibcVersion))return glibcVersionexcept Exception as e:log.Info('Unable to parse glibc version: ' + str(e))return Nonedef getHostKernelVersion(self):"""Return host's kernel version as a tuple."""import platform# releaseStr = full kernel version, e.g. '3.10.0-327.el7.x86_64'releaseStr = platform.release()log.Info('Kernel release string ' + releaseStr)# versionStr = kernel numeric version, e.g. '3.10.0'try:versionStr = releaseStr.split("-")[0]kernelVersion = tuple([int(x) for x in versionStr.split(".")])log.Info('Found Linux kernel version ' + str(kernelVersion))return kernelVersionexcept Exception as e:log.Info('Unable to parse kernel version: ' + str(e))return Nonedef isVersionOkay(self, version, minVersion):if not version:return True # Do not error out on parse failureelif version < minVersion:return Falseelse:return True