Subversion Repositories configs

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
209 - 1
"""
2
Copyright 2008-2020 VMware, Inc.  All rights reserved. -- VMware Confidential
3
 
4
VMware VMX component installer.
5
"""
6
 
7
GCONF_DEFAULTS = 'xml:readwrite:/etc/gconf/gconf.xml.defaults'
8
DEST = LIBDIR/'vmware'
9
CONFIG = DEST/'setup/vmware-config'
10
CUPSLIBDIR = LIBDIR/'cups'
11
SETTINGS = \
12
    {'libdir': DEST,
13
     'bindir': BINDIR,
14
     'initscriptdir': INITSCRIPTDIR,
15
     'gksu.rootMethod': 'sudo' if 'SUDO_USER' in ENV else 'su',
16
     'NETWORKING': 'yes',
17
     'authd.fullpath': SBINDIR/'vmware-authd',
18
    }
19
 
20
vmwareSentinel = '# Automatically generated by the VMware Installer - DO NOT REMOVE\n'
21
 
22
# Present requirement is a RHEL7-like environment.
23
MIN_GLIBC_VERSION = (2, 17)
24
MIN_KERNEL_VERSION = (3, 10)
25
 
26
# Player and Workstation both depend on some configuration living
27
# in /etc/vmware
28
ETCDIR = Destination('/etc/vmware')
29
 
30
class VMX(Installer):
31
   def InitializeQuestions(self, old, new, upgrade):
32
      self.AddQuestion('ClosePrograms', key='ClosePrograms', text='',
33
                       required=True, default='Yes', level='REQUIRED')
34
 
35
      # Check whether we're in a VM or on real hardware.
36
      # Nothing currently depends on this, but it might be useful to log.
37
      self.checkVMTempFile = self.GetFilePath('extra/checkvm')
38
      # Get the directory above our installer and write checkvm to it so we can run it
39
      tmpdir = path(ENV['VMWARE_INSTALLER']).dirname().dirname()
40
      self.checkvmBin = tmpdir/'checkvm'
41
      path(self.checkVMTempFile).copy(self.checkvmBin)
42
      self.checkvmBin.chmod(0o755)
43
      path(self.checkVMTempFile).remove()
44
 
45
      ret = self.RunCommand(self.checkvmBin, ignoreErrors=True)
46
      if ret.retCode == 0:
47
         # We are running in a virtual machine.
48
         log.Info('Running inside a virtual machine!')
49
      elif ret.retCode == 1:
50
         # We are on normal hardware.
51
         log.Info('Running on a real machine!')
52
      else:
53
         log.Warn('Something went wrong detecting whether we are in a VM or not.  Assuming we are not.')
54
 
55
 
56
   def InitializeInstall(self, old, new, upgrade):
57
      if self._checkXenPresence():
58
         log.Warn('This system is running a Xen kernel. You cannot run VMs under the Xen kernel.')
59
 
60
      if path('/dev/kvm').exists():
61
         log.Warn('This system has KVM enabled. You cannot run VMs with KVM enabled.')
62
 
63
      self.AddTarget('File', 'bin/*', BINDIR)
64
      self.AddTarget('File', 'sbin/*', SBINDIR)
65
      self.AddTarget('File', 'lib/*', DEST)
66
      self.AddTarget('File', 'roms/*', DEST/'roms')
67
      self.AddTarget('File', 'etc/init.d/vmware', SYSCONFDIR/'init.d/vmware')
68
 
69
      # Install the proper modules.xml file
70
      self.AddTarget('File', 'extra/modules.xml', DEST/'modules/modules.xml')
71
 
72
      # Symlink all binaries to appLoader.
73
      for i in ('vmware-modconfig', 'vmware-modconfig-console',
74
                'vmware-gksu', 'vmware-vmblock-fuse'):
75
        self.AddTarget('Link', DEST/'bin/appLoader', DEST/'bin'/i)
76
 
77
      self.SetPermission(DEST/'bin/*', BINARY)
78
      self.SetPermission(SYSCONFDIR/'init.d/vmware', BINARY)
79
      self.SetPermission(DEST/'bin/vmware-vmx*', SETUID)
80
      self.SetPermission(DEST/'lib/libvmware-gksu.so/gksu-run-helper', BINARY)
81
      self.SetPermission(SBINDIR/'vmware-authd', SETUID)
82
 
83
      # modprobe.d script.  Only install this on systems where modprobe.d
84
      # exists.
85
      if path('/etc/modprobe.d').exists():
86
         self.AddTarget('File', 'etc/modprobe.d/modprobe-vmware-fuse.conf',
87
                        '/etc/modprobe.d/vmware-fuse.conf')
88
 
89
      # Add a link for internationalization directory
90
      self.AddTarget('Link', DEST/'icu', SYSCONFDIR/'vmware/icu')
91
 
92
   def PreTransactionUninstall(self, old, new, upgrade):
93
      self.AddQuestion('ClosePrograms', key='ClosePrograms', text='',
94
                       required=True, default='Yes', level='REQUIRED')
95
 
96
   def PreInstall(self, old, new, upgrade):
97
      # Remove all modules in case some were left behind somehow.  For
98
      # example, the installation database could have been blown away.
99
      ret, kvers, _ = self.RunCommand('uname', '-r')
100
      kvers = kvers.strip()
101
      modules = ('vmmon', 'vmnet')
102
      base = path('/lib/modules/%s/misc' % kvers)
103
 
104
      for module in modules:
105
         for ext in ('o', 'ko'):
106
            mod = '%s.%s' % (module, ext)
107
            (base/mod).remove(ignore_errors=True)
108
 
109
   def PreUninstall(self, old, new, upgrade):
110
      script = INITSCRIPTDIR/'vmware'
111
 
112
      # Stop and deconfigure services
113
      if ENV.get('VMWARE_SKIP_SERVICES'):
114
         log.Info('Skipping stopping services')
115
      elif INITSCRIPTDIR and self._scriptRunnable(script) and self.RunCommand(script, 'stop', ignoreErrors=True).retCode != 0:
116
         log.Error('Unable to stop VMware services')
117
 
118
      # Deconfigure services
119
      inits = self.LoadInclude('initscript')
120
      inits.DeconfigureService('vmware')
121
 
122
      # This must be the last thing done since the actions above may
123
      # depend on it.
124
      for key in list(SETTINGS.keys()):
125
         self.RunCommand(CONFIG, '-d', key)
126
 
127
      # Remove prelink appLoader exclusion.
128
      self._configurePrelink(False)
129
 
130
   def PostUninstall(self, old, new, upgrade):
131
      # Modules have been removed during uninstall.  We need to be sure to
132
      # run depmod to pick up that change.  Don't fail if for some reason
133
      # depmod can't be executed though.
134
      self.RunCommand('depmod', '-a', ignoreErrors=True)
135
 
136
   def PostInstall(self, old, new, upgrade):
137
      for key, val in list(SETTINGS.items()):
138
         self.RunCommand(CONFIG, '-s', key, val)
139
 
140
      bootstrap = ETCDIR/'bootstrap'
141
      # Create the bootstrap file.
142
      bootstrap.unlink(ignore_errors=True)
143
      # Fill it.
144
      for i in ('PREFIX', 'BINDIR', 'SBINDIR', 'LIBDIR', 'DATADIR',
145
                'SYSCONFDIR', 'DOCDIR', 'MANDIR', 'INCLUDEDIR', 'INITDIR',
146
                'INITSCRIPTDIR'):
147
         bootstrap.write_text('%s="%s"\n' % (i, globals()[i]), append=True)
148
      # Register it.
149
      # XXX: Skip registration for this change.  Allow the installer to handle
150
      # deletion.  A later change will correct this.
151
      # self.RegisterFile(bootstrap, fileType='ConfigFile')
152
 
153
      # Configure Gtk+.
154
      # @todo: make it its own component
155
      libconf = DEST/'libconf'
156
      replace = libconf/'etc/gtk-3.0/gdk-pixbuf.loaders'
157
      template = '@@LIBCONF_DIR@@'
158
 
159
      self.RunCommand('sed', '-e', 's,%s,%s,g' % (template, libconf),
160
                      '-i', replace)
161
 
162
      # Add prelink appLoader exclusion.
163
      self._configurePrelink(True)
164
 
165
      # Set up virtual networking
166
      self._configureNetworking(old, new)
167
 
168
      # Set up the vmware service script
169
      inits = self.LoadInclude('initscript')
170
      inits.ConfigureService('vmware',
171
                             'This service starts and stops VMware services',
172
                             '$network $syslog', # Start
173
                             '$network $syslog', # Stop
174
                             '',
175
                             '',
176
                             19,
177
                             8)
178
 
179
      # Make sure to start services
180
      script = INITSCRIPTDIR/'vmware'
181
      if INITSCRIPTDIR and script.exists():
182
         self.RunCommand(script, 'stop', ignoreErrors=True)
183
         self.RunCommand(script, 'start')
184
 
185
      # If no INITDIR was given, notify the user that the vmware service must
186
      # be manually set up
187
      if not INITDIR:
188
         self.UserMessage('No rc*.d style init script directories were given to the installer. '
189
                          'You must manually add the necessary links to ensure that the vmware '
190
                          'service at %s is automatically started and stopped on startup and shutdown.' % str(INITSCRIPTDIR/'vmware'))
191
 
192
   def _scriptRunnable(self, script):
193
      """ Returns True if the script exists and is in a runnable state """
194
      return script.isexe() and script.isfile() and self.RunCommand(script, 'validate').retCode == 100
195
 
196
   def _AddLineToFile(self, fil, text, addToEnd=True):
197
      """
198
      Add a line/consecutive lines to a file, surrounded by the VMware Sentinel.
199
      ### This method only adds a single line to a file ###
200
      ### You cannot make more than one modification per file ###
201
 
202
      @param fil: The file name.  Either a string or path object
203
      @param text: The text to add.  This function appends a \n
204
      @param addToEnd: Add to the end of the file?  If false, to the beginning.
205
 
206
      @return: True on successful modification, False if the file did not exist.
207
      """
208
      fil = path(fil) # Make sure it's a path object
209
      if fil.exists():
210
         txt = fil.text()
211
         # Modify the text
212
         if addToEnd:
213
            txt = ''.join([txt, vmwareSentinel, text, '\n', vmwareSentinel])
214
         else:
215
            txt = ''.join([vmwareSentinel, text, '\n', vmwareSentinel, txt])
216
         # Write it back to the file
217
         fil.write_text(txt)
218
         return True
219
      else:
220
         log.Info('Attempted to modify file %s, does not exist.' % fil)
221
         return False
222
 
223
   def _RemoveLineFromFile(self, fil):
224
      """
225
      Remove a line bracketed by the VMware Sentinel
226
 
227
      @param fil: The file name.  Either a string or path object
228
 
229
      @return: True on successful modification, False if the file did not exist.
230
      """
231
      fil = path(fil) # Make sure it's a path object
232
      if fil.exists():
233
         txt = fil.text()
234
         m = re.sub(vmwareSentinel + '.*\n' + vmwareSentinel,
235
                    '', txt, re.DOTALL)
236
         fil.write_text(m)
237
         return True
238
      else:
239
         log.Info('Attempted to modify file %s, does not exist.' % fil)
240
         return False
241
 
242
   def _configurePrelink(self, enable):
243
      """
244
      Configures prelinking by adding appLoader exclusion.
245
 
246
      @param enable: True if to add exclusion, False if to remove it.
247
      """
248
      prelink = path('/etc/prelink.conf')
249
 
250
      if prelink.exists():
251
         # XXX: It would probably be good to refactor this into some
252
         # sort of helper function that can add and remove lines from
253
         # files.
254
         skipPrelink = [ '# appLoader will segfault if prelinked.',
255
                         '-b %s' % (DEST/'bin/appLoader') ]
256
 
257
         lines = prelink.lines(encoding='utf-8', retain=False)
258
 
259
         # Strip whitespace from lines so that trailing whitespace
260
         # won't impact matching lines.
261
         lines = [line.strip() for line in lines]
262
 
263
         if enable:
264
            if skipPrelink[-1] not in lines:
265
               lines += skipPrelink
266
               log.Info('Added appLoader prelink exclusion to %s.', prelink)
267
            else:
268
               log.Warn('appLoader skip prelinking already present.')
269
         else:
270
            found = False
271
 
272
            for line in skipPrelink:
273
               if line in lines:
274
                  found = True
275
                  lines.remove(line)
276
 
277
            if found:
278
               log.Info('Removed appLoader prelink exclusion from %s.', prelink)
279
 
280
         # XXX: This can technically fail.  Actually, there are a
281
         # whole host of things that can fail in this function.  On
282
         # one hand we would like to catch errors and correct them
283
         # while being fairly resilient at installation time.
284
         #
285
         # One option might be to have a @failok decoration for things
286
         # that can fail loudly for internal builds but do not cause
287
         # installations to fail in release builds.
288
         prelink.write_lines(lines, encoding='utf-8')
289
      else:
290
         log.Info('Prelink not present, skipping configuration.')
291
 
292
   def _validate_cpu_flags(self, reqFlags):
293
      """
294
      Checks to ensure that every CPU on the given machine has all the
295
      flags passed in the reqFlags list.
296
      @param: A list of the flags that must be present.
297
      @returns: True on all necessary flags present, False othewise.
298
      """
299
      flagSec = False
300
      cpuInfoFile = '/proc/cpuinfo'
301
      try:
302
         cpuInfo = open(cpuInfoFile, mode='r')
303
         for line in cpuInfo:
304
            if line.startswith('flags'):
305
               flagSec = True
306
               flagsFound = line.split(' ')
307
               for flag in reqFlags:
308
                  if flag not in flagsFound:
309
                     return False
310
         return flagSec
311
      except IOError:
312
         raise Exception('Could not open %s' % (cpuInfoFile))
313
      finally:
314
         if cpuInfo:
315
            cpuInfo.close()
316
 
317
   def PreTransactionInstall(self, old, new, upgrade):
318
      if ENV.get('VMWARE_SKIP_VERSION_CHECKS'):
319
         return  # skip all checks
320
 
321
      # CPU flags check
322
      # TODO: also check for vt / ept / (AMD equivalent)
323
      reqFlags = ['lm']
324
      cpuOk = self._validate_cpu_flags(reqFlags)
325
      kernelOk = self.isVersionOkay(self.getHostKernelVersion(),
326
                                    MIN_KERNEL_VERSION)
327
      glibcOk = self.isVersionOkay(self.getHostGlibcVersion(),
328
                                   MIN_GLIBC_VERSION)
329
      if not cpuOk:
330
         self.UserMessage('One or more of your processors does not have the '
331
                          'necessary 64bit extensions to run VMware '
332
                          'virtual machines.')
333
      if not kernelOk:
334
         self.UserMessage('The Linux kernel version on this system is '
335
                          'insufficient for running VMware virtual machines. '
336
                          'Linux kernel version %s or later is required.'
337
                          % str(MIN_KERNEL_VERSION))
338
      if not glibcOk:
339
         self.UserMessage('The glibc library version on this system is '
340
                          'insufficient for running VMware virtual machines. '
341
                          'Glibc library version %s or later is required.'
342
                          % str(MIN_GLIBC_VERSION))
343
      if not cpuOk or not glibcOk or not kernelOk:
344
         raise Exception('Host not supported by this product.')
345
 
346
   def PostTransactionInstall(self, old, new, upgrade):
347
      # Build modules with modconfig.  This can't happen during the other install
348
      # phases because VMIS has locked the database and modconfig invokes another
349
      # version of VMIS to register the compiled modules.
350
      if ENV.get('VMWARE_SKIP_MODULES'):
351
         log.Info('Skipping kernel module installation')
352
      else:
353
         # run depmod -a before modconfig in case we are upgrading from a version
354
         # that did not properly do this after uninstalling modules. modconfig
355
         # uses modules.dep to determine upstream status of modules, therefore
356
         # it might get confused if modules.dep is not up-to-date.
357
         self.RunCommand('depmod', '-a', ignoreErrors=True)
358
         ret = self.RunCommand(BINDIR/'vmware-modconfig', '--console', '--install-all')
359
         if ret.retCode == 0:
360
            log.Info('Successfully installed kernel modules')
361
         else:
362
            log.Info('Unable to install kernel modules')
363
            log.Info('stdout: %s' % ret.stdout)
364
            log.Info('stderr: %s' % ret.stderr)
365
 
366
   def _checkXenPresence(self):
367
      """
368
      Checks whether this install is within a Xen domain,
369
      that is running on a Xen kernel.  Returns True if so.
370
      """
371
      xenTest = path('/proc/xen/capabilities')
372
      return xenTest.exists() and len(xenTest.text())
373
 
374
   def _escape(self, string):
375
      """ Escapes a string for use in a shell context """
376
      # XXX: Borrowed from util/shell.py: Escape.  Break that into a component-side
377
      # include file and remove this method once that's done.
378
      return "'%s'" % string.replace("'", '"\'"')
379
 
380
   def _configureNetworking(self, old, new):
381
      if 'VMWARE_SKIP_NETWORKING' in ENV:
382
         return
383
 
384
      vnetlib = BINDIR/'vmware-networks'
385
 
386
      backup = ENV.get('VMWARE_RESTORE_NETWORKING')
387
      backup = backup and path(backup)
388
 
389
      locations = ENV.get('VMWARE_MIGRATE_NETWORKING')
390
      locations = locations and path(locations)
391
 
392
      if backup and backup.owner == 'root':
393
         log.Info('Restoring previous network settings from %s', backup)
394
         backup.copy('/etc/vmware/networking')
395
         backup.remove()
396
         old = 1
397
      elif locations and locations.owner == 'root' and not old:
398
         log.Info('Migrating old network settings from %s', locations)
399
         if self.RunCommand(vnetlib, '--migrate-network-settings', locations, ignoreErrors=True).retCode == 0:
400
            locations.remove()
401
            return True
402
         else:
403
            log.Info('Migrating network settings failed, forcing new network settings')
404
            old = 0
405
            new = 1
406
 
407
      # old might be None so make it 0 for the purposes of vnetlib.
408
      old = old or 0
409
 
410
      return self.RunCommand(vnetlib, '--postinstall', '%s,%s,%s' % ('vmware-player', old, new),
411
                             ignoreErrors=True).retCode == 0
412
 
413
   def getHostGlibcVersion(self):
414
      """Return host's glibc version as a tuple."""
415
      try:
416
         import subprocess
417
         out = subprocess.check_output(["ldd", "--version"]).decode()
418
         brand = out.split('\n', 1)[0]
419
      except subprocess.CalledProcessError:
420
         log.Info('Unable to query glibc version.')
421
         return None
422
      # brand = full ldd brand string, e.g. 'ldd (Distro GLIBC N.NN) 2.17'
423
      log.Info('Glibc brand string ' + brand)
424
      try:
425
          # versionStr = glibc version, e.g. '2.17'
426
          versionStr = brand.split(' ')[-1]
427
          glibcVersion = tuple([int(x) for x in versionStr.split(".")])
428
          log.Info('Found glibc version ' + str(glibcVersion))
429
          return glibcVersion
430
      except Exception as e:
431
          log.Info('Unable to parse glibc version: ' + str(e))
432
          return None
433
 
434
   def getHostKernelVersion(self):
435
      """Return host's kernel version as a tuple."""
436
      import platform
437
      # releaseStr = full kernel version, e.g. '3.10.0-327.el7.x86_64'
438
      releaseStr = platform.release()
439
      log.Info('Kernel release string ' + releaseStr)
440
      # versionStr = kernel numeric version, e.g. '3.10.0'
441
      try:
442
          versionStr = releaseStr.split("-")[0]
443
          kernelVersion = tuple([int(x) for x in versionStr.split(".")])
444
          log.Info('Found Linux kernel version ' + str(kernelVersion))
445
          return kernelVersion
446
      except Exception as e:
447
          log.Info('Unable to parse kernel version: ' + str(e))
448
          return None
449
 
450
   def isVersionOkay(self, version, minVersion):
451
      if not version:
452
         return True  # Do not error out on parse failure
453
      elif version < minVersion:
454
         return False
455
      else:
456
         return True