Blame | Last modification | View Log | RSS feed
"""Copyright 2008-2020 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','authd.fullpath': SBINDIR/'vmware-authd',}vmwareSentinel = '# Automatically generated by the VMware Installer - DO NOT REMOVE\n'# Present requirement is a RHEL7-like environment.MIN_GLIBC_VERSION = (2, 17)MIN_KERNEL_VERSION = (3, 10)# 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.# Nothing currently depends on this, but it might be useful to log.self.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()ret = self.RunCommand(self.checkvmBin, ignoreErrors=True)if ret.retCode == 0:# We are running in a virtual machine.log.Info('Running inside a virtual machine!')elif ret.retCode == 1:# We are on normal hardware.log.Info('Running on a real machine!')else:log.Warn('Something went wrong detecting whether we are in a VM or not. Assuming we are not.')def 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 fileself.AddTarget('File', 'extra/modules.xml', DEST/'modules/modules.xml')# 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')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 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