Subversion Repositories configs

Rev

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