Blame | Last modification | View Log | RSS feed
"""
Copyright 2008-2020 VMware, Inc. All rights reserved. -- VMware Confidential
VMware 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/vmware
ETCDIR = 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 it
tmpdir = 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 file
self.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 directory
self.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 services
if 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 services
inits = 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 component
libconf = 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 networking
self._configureNetworking(old, new)
# Set up the vmware service script
inits = 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 services
script = 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 up
if 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 == 100
def _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 object
if fil.exists():
txt = fil.text()
# Modify the text
if addToEnd:
txt = ''.join([txt, vmwareSentinel, text, '\n', vmwareSentinel])
else:
txt = ''.join([vmwareSentinel, text, '\n', vmwareSentinel, txt])
# Write it back to the file
fil.write_text(txt)
return True
else:
log.Info('Attempted to modify file %s, does not exist.' % fil)
return False
def _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 object
if fil.exists():
txt = fil.text()
m = re.sub(vmwareSentinel + '.*\n' + vmwareSentinel,
'', txt, re.DOTALL)
fil.write_text(m)
return True
else:
log.Info('Attempted to modify file %s, does not exist.' % fil)
return False
def _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 += skipPrelink
log.Info('Added appLoader prelink exclusion to %s.', prelink)
else:
log.Warn('appLoader skip prelinking already present.')
else:
found = False
for line in skipPrelink:
if line in lines:
found = True
lines.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 the
flags 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 = False
cpuInfoFile = '/proc/cpuinfo'
try:
cpuInfo = open(cpuInfoFile, mode='r')
for line in cpuInfo:
if line.startswith('flags'):
flagSec = True
flagsFound = line.split(' ')
for flag in reqFlags:
if flag not in flagsFound:
return False
return flagSec
except 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:
return
vnetlib = 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 = 1
elif 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 True
else:
log.Info('Migrating network settings failed, forcing new network settings')
old = 0
new = 1
# old might be None so make it 0 for the purposes of vnetlib.
old = old or 0
return self.RunCommand(vnetlib, '--postinstall', '%s,%s,%s' % ('vmware-player', old, new),
ignoreErrors=True).retCode == 0
def getHostGlibcVersion(self):
"""Return host's glibc version as a tuple."""
try:
import subprocess
out = 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 glibcVersion
except Exception as e:
log.Info('Unable to parse glibc version: ' + str(e))
return None
def 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 kernelVersion
except Exception as e:
log.Info('Unable to parse kernel version: ' + str(e))
return None
def isVersionOkay(self, version, minVersion):
if not version:
return True # Do not error out on parse failure
elif version < minVersion:
return False
else:
return True