"Fossies" - the Fresh Open Source Software Archive

Member "salt-2019.2.2/salt/grains/core.py" (2 Oct 2019, 110125 Bytes) of package /linux/misc/salt-2019.2.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "core.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2019.2.0_vs_2019.2.1.

    1 # -*- coding: utf-8 -*-
    2 '''
    3 The static grains, these are the core, or built in grains.
    4 
    5 When grains are loaded they are not loaded in the same way that modules are
    6 loaded, grain functions are detected and executed, the functions MUST
    7 return a dict which will be applied to the main grains dict. This module
    8 will always be executed first, so that any grains loaded here in the core
    9 module can be overwritten just by returning dict keys with the same value
   10 as those returned here
   11 '''
   12 
   13 # Import python libs
   14 from __future__ import absolute_import, print_function, unicode_literals
   15 import os
   16 import socket
   17 import sys
   18 import re
   19 import platform
   20 import logging
   21 import locale
   22 import uuid
   23 from errno import EACCES, EPERM
   24 import datetime
   25 import warnings
   26 import time
   27 
   28 # pylint: disable=import-error
   29 try:
   30     import dateutil.tz
   31     _DATEUTIL_TZ = True
   32 except ImportError:
   33     _DATEUTIL_TZ = False
   34 
   35 __proxyenabled__ = ['*']
   36 __FQDN__ = None
   37 
   38 # Extend the default list of supported distros. This will be used for the
   39 # /etc/DISTRO-release checking that is part of linux_distribution()
   40 from platform import _supported_dists
   41 _supported_dists += ('arch', 'mageia', 'meego', 'vmware', 'bluewhite64',
   42                      'slamd64', 'ovs', 'system', 'mint', 'oracle', 'void')
   43 
   44 # linux_distribution deprecated in py3.7
   45 try:
   46     from platform import linux_distribution as _deprecated_linux_distribution
   47 
   48     def linux_distribution(**kwargs):
   49         with warnings.catch_warnings():
   50             warnings.simplefilter("ignore")
   51             return _deprecated_linux_distribution(**kwargs)
   52 except ImportError:
   53     from distro import linux_distribution
   54 
   55 # Import salt libs
   56 import salt.exceptions
   57 import salt.log
   58 import salt.utils.dns
   59 import salt.utils.files
   60 import salt.utils.network
   61 import salt.utils.path
   62 import salt.utils.pkg.rpm
   63 import salt.utils.platform
   64 import salt.utils.stringutils
   65 from salt.ext import six
   66 from salt.ext.six.moves import range
   67 
   68 if salt.utils.platform.is_windows():
   69     import salt.utils.win_osinfo
   70 
   71 # Solve the Chicken and egg problem where grains need to run before any
   72 # of the modules are loaded and are generally available for any usage.
   73 import salt.modules.cmdmod
   74 import salt.modules.smbios
   75 
   76 __salt__ = {
   77     'cmd.run': salt.modules.cmdmod._run_quiet,
   78     'cmd.retcode': salt.modules.cmdmod._retcode_quiet,
   79     'cmd.run_all': salt.modules.cmdmod._run_all_quiet,
   80     'smbios.records': salt.modules.smbios.records,
   81     'smbios.get': salt.modules.smbios.get,
   82 }
   83 log = logging.getLogger(__name__)
   84 
   85 HAS_WMI = False
   86 if salt.utils.platform.is_windows():
   87     # attempt to import the python wmi module
   88     # the Windows minion uses WMI for some of its grains
   89     try:
   90         import wmi  # pylint: disable=import-error
   91         import salt.utils.winapi
   92         import win32api
   93         import salt.utils.win_reg
   94         HAS_WMI = True
   95     except ImportError:
   96         log.exception(
   97             'Unable to import Python wmi module, some core grains '
   98             'will be missing'
   99         )
  100 
  101 HAS_UNAME = True
  102 if not hasattr(os, 'uname'):
  103     HAS_UNAME = False
  104 
  105 _INTERFACES = {}
  106 
  107 
  108 def _windows_cpudata():
  109     '''
  110     Return some CPU information on Windows minions
  111     '''
  112     # Provides:
  113     #   num_cpus
  114     #   cpu_model
  115     grains = {}
  116     if 'NUMBER_OF_PROCESSORS' in os.environ:
  117         # Cast to int so that the logic isn't broken when used as a
  118         # conditional in templating. Also follows _linux_cpudata()
  119         try:
  120             grains['num_cpus'] = int(os.environ['NUMBER_OF_PROCESSORS'])
  121         except ValueError:
  122             grains['num_cpus'] = 1
  123     grains['cpu_model'] = salt.utils.win_reg.read_value(
  124         hive="HKEY_LOCAL_MACHINE",
  125         key="HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
  126         vname="ProcessorNameString").get('vdata')
  127     return grains
  128 
  129 
  130 def _linux_cpudata():
  131     '''
  132     Return some CPU information for Linux minions
  133     '''
  134     # Provides:
  135     #   num_cpus
  136     #   cpu_model
  137     #   cpu_flags
  138     grains = {}
  139     cpuinfo = '/proc/cpuinfo'
  140     # Parse over the cpuinfo file
  141     if os.path.isfile(cpuinfo):
  142         with salt.utils.files.fopen(cpuinfo, 'r') as _fp:
  143             for line in _fp:
  144                 comps = line.split(':')
  145                 if not len(comps) > 1:
  146                     continue
  147                 key = comps[0].strip()
  148                 val = comps[1].strip()
  149                 if key == 'processor':
  150                     grains['num_cpus'] = int(val) + 1
  151                 elif key == 'model name':
  152                     grains['cpu_model'] = val
  153                 elif key == 'flags':
  154                     grains['cpu_flags'] = val.split()
  155                 elif key == 'Features':
  156                     grains['cpu_flags'] = val.split()
  157                 # ARM support - /proc/cpuinfo
  158                 #
  159                 # Processor       : ARMv6-compatible processor rev 7 (v6l)
  160                 # BogoMIPS        : 697.95
  161                 # Features        : swp half thumb fastmult vfp edsp java tls
  162                 # CPU implementer : 0x41
  163                 # CPU architecture: 7
  164                 # CPU variant     : 0x0
  165                 # CPU part        : 0xb76
  166                 # CPU revision    : 7
  167                 #
  168                 # Hardware        : BCM2708
  169                 # Revision        : 0002
  170                 # Serial          : 00000000
  171                 elif key == 'Processor':
  172                     grains['cpu_model'] = val.split('-')[0]
  173                     grains['num_cpus'] = 1
  174     if 'num_cpus' not in grains:
  175         grains['num_cpus'] = 0
  176     if 'cpu_model' not in grains:
  177         grains['cpu_model'] = 'Unknown'
  178     if 'cpu_flags' not in grains:
  179         grains['cpu_flags'] = []
  180     return grains
  181 
  182 
  183 def _linux_gpu_data():
  184     '''
  185     num_gpus: int
  186     gpus:
  187       - vendor: nvidia|amd|ati|...
  188         model: string
  189     '''
  190     if __opts__.get('enable_lspci', True) is False:
  191         return {}
  192 
  193     if __opts__.get('enable_gpu_grains', True) is False:
  194         return {}
  195 
  196     lspci = salt.utils.path.which('lspci')
  197     if not lspci:
  198         log.debug(
  199             'The `lspci` binary is not available on the system. GPU grains '
  200             'will not be available.'
  201         )
  202         return {}
  203 
  204     # dominant gpu vendors to search for (MUST be lowercase for matching below)
  205     known_vendors = ['nvidia', 'amd', 'ati', 'intel', 'cirrus logic', 'vmware', 'matrox', 'aspeed']
  206     gpu_classes = ('vga compatible controller', '3d controller')
  207 
  208     devs = []
  209     try:
  210         lspci_out = __salt__['cmd.run']('{0} -vmm'.format(lspci))
  211 
  212         cur_dev = {}
  213         error = False
  214         # Add a blank element to the lspci_out.splitlines() list,
  215         # otherwise the last device is not evaluated as a cur_dev and ignored.
  216         lspci_list = lspci_out.splitlines()
  217         lspci_list.append('')
  218         for line in lspci_list:
  219             # check for record-separating empty lines
  220             if line == '':
  221                 if cur_dev.get('Class', '').lower() in gpu_classes:
  222                     devs.append(cur_dev)
  223                 cur_dev = {}
  224                 continue
  225             if re.match(r'^\w+:\s+.*', line):
  226                 key, val = line.split(':', 1)
  227                 cur_dev[key.strip()] = val.strip()
  228             else:
  229                 error = True
  230                 log.debug('Unexpected lspci output: \'%s\'', line)
  231 
  232         if error:
  233             log.warning(
  234                 'Error loading grains, unexpected linux_gpu_data output, '
  235                 'check that you have a valid shell configured and '
  236                 'permissions to run lspci command'
  237             )
  238     except OSError:
  239         pass
  240 
  241     gpus = []
  242     for gpu in devs:
  243         vendor_strings = gpu['Vendor'].lower().split()
  244         # default vendor to 'unknown', overwrite if we match a known one
  245         vendor = 'unknown'
  246         for name in known_vendors:
  247             # search for an 'expected' vendor name in the list of strings
  248             if name in vendor_strings:
  249                 vendor = name
  250                 break
  251         gpus.append({'vendor': vendor, 'model': gpu['Device']})
  252 
  253     grains = {}
  254     grains['num_gpus'] = len(gpus)
  255     grains['gpus'] = gpus
  256     return grains
  257 
  258 
  259 def _netbsd_gpu_data():
  260     '''
  261     num_gpus: int
  262     gpus:
  263       - vendor: nvidia|amd|ati|...
  264         model: string
  265     '''
  266     known_vendors = ['nvidia', 'amd', 'ati', 'intel', 'cirrus logic', 'vmware', 'matrox', 'aspeed']
  267 
  268     gpus = []
  269     try:
  270         pcictl_out = __salt__['cmd.run']('pcictl pci0 list')
  271 
  272         for line in pcictl_out.splitlines():
  273             for vendor in known_vendors:
  274                 vendor_match = re.match(
  275                     r'[0-9:]+ ({0}) (.+) \(VGA .+\)'.format(vendor),
  276                     line,
  277                     re.IGNORECASE
  278                 )
  279                 if vendor_match:
  280                     gpus.append({'vendor': vendor_match.group(1), 'model': vendor_match.group(2)})
  281     except OSError:
  282         pass
  283 
  284     grains = {}
  285     grains['num_gpus'] = len(gpus)
  286     grains['gpus'] = gpus
  287     return grains
  288 
  289 
  290 def _osx_gpudata():
  291     '''
  292     num_gpus: int
  293     gpus:
  294       - vendor: nvidia|amd|ati|...
  295         model: string
  296     '''
  297 
  298     gpus = []
  299     try:
  300         pcictl_out = __salt__['cmd.run']('system_profiler SPDisplaysDataType')
  301 
  302         for line in pcictl_out.splitlines():
  303             fieldname, _, fieldval = line.partition(': ')
  304             if fieldname.strip() == "Chipset Model":
  305                 vendor, _, model = fieldval.partition(' ')
  306                 vendor = vendor.lower()
  307                 gpus.append({'vendor': vendor, 'model': model})
  308 
  309     except OSError:
  310         pass
  311 
  312     grains = {}
  313     grains['num_gpus'] = len(gpus)
  314     grains['gpus'] = gpus
  315     return grains
  316 
  317 
  318 def _bsd_cpudata(osdata):
  319     '''
  320     Return CPU information for BSD-like systems
  321     '''
  322     # Provides:
  323     #   cpuarch
  324     #   num_cpus
  325     #   cpu_model
  326     #   cpu_flags
  327     sysctl = salt.utils.path.which('sysctl')
  328     arch = salt.utils.path.which('arch')
  329     cmds = {}
  330 
  331     if sysctl:
  332         cmds.update({
  333             'num_cpus': '{0} -n hw.ncpu'.format(sysctl),
  334             'cpuarch': '{0} -n hw.machine'.format(sysctl),
  335             'cpu_model': '{0} -n hw.model'.format(sysctl),
  336         })
  337 
  338     if arch and osdata['kernel'] == 'OpenBSD':
  339         cmds['cpuarch'] = '{0} -s'.format(arch)
  340 
  341     if osdata['kernel'] == 'Darwin':
  342         cmds['cpu_model'] = '{0} -n machdep.cpu.brand_string'.format(sysctl)
  343         cmds['cpu_flags'] = '{0} -n machdep.cpu.features'.format(sysctl)
  344 
  345     grains = dict([(k, __salt__['cmd.run'](v)) for k, v in six.iteritems(cmds)])
  346 
  347     if 'cpu_flags' in grains and isinstance(grains['cpu_flags'], six.string_types):
  348         grains['cpu_flags'] = grains['cpu_flags'].split(' ')
  349 
  350     if osdata['kernel'] == 'NetBSD':
  351         grains['cpu_flags'] = []
  352         for line in __salt__['cmd.run']('cpuctl identify 0').splitlines():
  353             cpu_match = re.match(r'cpu[0-9]:\ features[0-9]?\ .+<(.+)>', line)
  354             if cpu_match:
  355                 flag = cpu_match.group(1).split(',')
  356                 grains['cpu_flags'].extend(flag)
  357 
  358     if osdata['kernel'] == 'FreeBSD' and os.path.isfile('/var/run/dmesg.boot'):
  359         grains['cpu_flags'] = []
  360         # TODO: at least it needs to be tested for BSD other then FreeBSD
  361         with salt.utils.files.fopen('/var/run/dmesg.boot', 'r') as _fp:
  362             cpu_here = False
  363             for line in _fp:
  364                 if line.startswith('CPU: '):
  365                     cpu_here = True  # starts CPU descr
  366                     continue
  367                 if cpu_here:
  368                     if not line.startswith(' '):
  369                         break  # game over
  370                     if 'Features' in line:
  371                         start = line.find('<')
  372                         end = line.find('>')
  373                         if start > 0 and end > 0:
  374                             flag = line[start + 1:end].split(',')
  375                             grains['cpu_flags'].extend(flag)
  376     try:
  377         grains['num_cpus'] = int(grains['num_cpus'])
  378     except ValueError:
  379         grains['num_cpus'] = 1
  380 
  381     return grains
  382 
  383 
  384 def _sunos_cpudata():
  385     '''
  386     Return the CPU information for Solaris-like systems
  387     '''
  388     # Provides:
  389     #   cpuarch
  390     #   num_cpus
  391     #   cpu_model
  392     #   cpu_flags
  393     grains = {}
  394     grains['cpu_flags'] = []
  395 
  396     grains['cpuarch'] = __salt__['cmd.run']('isainfo -k')
  397     psrinfo = '/usr/sbin/psrinfo 2>/dev/null'
  398     grains['num_cpus'] = len(__salt__['cmd.run'](psrinfo, python_shell=True).splitlines())
  399     kstat_info = 'kstat -p cpu_info:*:*:brand'
  400     for line in __salt__['cmd.run'](kstat_info).splitlines():
  401         match = re.match(r'(\w+:\d+:\w+\d+:\w+)\s+(.+)', line)
  402         if match:
  403             grains['cpu_model'] = match.group(2)
  404     isainfo = 'isainfo -n -v'
  405     for line in __salt__['cmd.run'](isainfo).splitlines():
  406         match = re.match(r'^\s+(.+)', line)
  407         if match:
  408             cpu_flags = match.group(1).split()
  409             grains['cpu_flags'].extend(cpu_flags)
  410 
  411     return grains
  412 
  413 
  414 def _aix_cpudata():
  415     '''
  416     Return CPU information for AIX systems
  417     '''
  418     # Provides:
  419     #   cpuarch
  420     #   num_cpus
  421     #   cpu_model
  422     #   cpu_flags
  423     grains = {}
  424     cmd = salt.utils.path.which('prtconf')
  425     if cmd:
  426         data = __salt__['cmd.run']('{0}'.format(cmd)) + os.linesep
  427         for dest, regstring in (('cpuarch', r'(?im)^\s*Processor\s+Type:\s+(\S+)'),
  428                                 ('cpu_flags', r'(?im)^\s*Processor\s+Version:\s+(\S+)'),
  429                                 ('cpu_model', r'(?im)^\s*Processor\s+Implementation\s+Mode:\s+(.*)'),
  430                                 ('num_cpus', r'(?im)^\s*Number\s+Of\s+Processors:\s+(\S+)')):
  431             for regex in [re.compile(r) for r in [regstring]]:
  432                 res = regex.search(data)
  433                 if res and len(res.groups()) >= 1:
  434                     grains[dest] = res.group(1).strip().replace("'", '')
  435     else:
  436         log.error('The \'prtconf\' binary was not found in $PATH.')
  437     return grains
  438 
  439 
  440 def _linux_memdata():
  441     '''
  442     Return the memory information for Linux-like systems
  443     '''
  444     grains = {'mem_total': 0, 'swap_total': 0}
  445 
  446     meminfo = '/proc/meminfo'
  447     if os.path.isfile(meminfo):
  448         with salt.utils.files.fopen(meminfo, 'r') as ifile:
  449             for line in ifile:
  450                 comps = line.rstrip('\n').split(':')
  451                 if not len(comps) > 1:
  452                     continue
  453                 if comps[0].strip() == 'MemTotal':
  454                     # Use floor division to force output to be an integer
  455                     grains['mem_total'] = int(comps[1].split()[0]) // 1024
  456                 if comps[0].strip() == 'SwapTotal':
  457                     # Use floor division to force output to be an integer
  458                     grains['swap_total'] = int(comps[1].split()[0]) // 1024
  459     return grains
  460 
  461 
  462 def _osx_memdata():
  463     '''
  464     Return the memory information for BSD-like systems
  465     '''
  466     grains = {'mem_total': 0, 'swap_total': 0}
  467 
  468     sysctl = salt.utils.path.which('sysctl')
  469     if sysctl:
  470         mem = __salt__['cmd.run']('{0} -n hw.memsize'.format(sysctl))
  471         swap_total = __salt__['cmd.run']('{0} -n vm.swapusage'.format(sysctl)).split()[2].replace(',', '.')
  472         if swap_total.endswith('K'):
  473             _power = 2**10
  474         elif swap_total.endswith('M'):
  475             _power = 2**20
  476         elif swap_total.endswith('G'):
  477             _power = 2**30
  478         swap_total = float(swap_total[:-1]) * _power
  479 
  480         grains['mem_total'] = int(mem) // 1024 // 1024
  481         grains['swap_total'] = int(swap_total) // 1024 // 1024
  482     return grains
  483 
  484 
  485 def _bsd_memdata(osdata):
  486     '''
  487     Return the memory information for BSD-like systems
  488     '''
  489     grains = {'mem_total': 0, 'swap_total': 0}
  490 
  491     sysctl = salt.utils.path.which('sysctl')
  492     if sysctl:
  493         mem = __salt__['cmd.run']('{0} -n hw.physmem'.format(sysctl))
  494         if osdata['kernel'] == 'NetBSD' and mem.startswith('-'):
  495             mem = __salt__['cmd.run']('{0} -n hw.physmem64'.format(sysctl))
  496         grains['mem_total'] = int(mem) // 1024 // 1024
  497 
  498         if osdata['kernel'] in ['OpenBSD', 'NetBSD']:
  499             swapctl = salt.utils.path.which('swapctl')
  500             swap_data = __salt__['cmd.run']('{0} -sk'.format(swapctl))
  501             if swap_data == 'no swap devices configured':
  502                 swap_total = 0
  503             else:
  504                 swap_total = swap_data.split(' ')[1]
  505         else:
  506             swap_total = __salt__['cmd.run']('{0} -n vm.swap_total'.format(sysctl))
  507         grains['swap_total'] = int(swap_total) // 1024 // 1024
  508     return grains
  509 
  510 
  511 def _sunos_memdata():
  512     '''
  513     Return the memory information for SunOS-like systems
  514     '''
  515     grains = {'mem_total': 0, 'swap_total': 0}
  516 
  517     prtconf = '/usr/sbin/prtconf 2>/dev/null'
  518     for line in __salt__['cmd.run'](prtconf, python_shell=True).splitlines():
  519         comps = line.split(' ')
  520         if comps[0].strip() == 'Memory' and comps[1].strip() == 'size:':
  521             grains['mem_total'] = int(comps[2].strip())
  522 
  523     swap_cmd = salt.utils.path.which('swap')
  524     swap_data = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()
  525     try:
  526         swap_avail = int(swap_data[-2][:-1])
  527         swap_used = int(swap_data[-4][:-1])
  528         swap_total = (swap_avail + swap_used) // 1024
  529     except ValueError:
  530         swap_total = None
  531     grains['swap_total'] = swap_total
  532     return grains
  533 
  534 
  535 def _aix_memdata():
  536     '''
  537     Return the memory information for AIX systems
  538     '''
  539     grains = {'mem_total': 0, 'swap_total': 0}
  540     prtconf = salt.utils.path.which('prtconf')
  541     if prtconf:
  542         for line in __salt__['cmd.run'](prtconf, python_shell=True).splitlines():
  543             comps = [x for x in line.strip().split(' ') if x]
  544             if len(comps) > 2 and 'Memory' in comps[0] and 'Size' in comps[1]:
  545                 grains['mem_total'] = int(comps[2])
  546                 break
  547     else:
  548         log.error('The \'prtconf\' binary was not found in $PATH.')
  549 
  550     swap_cmd = salt.utils.path.which('swap')
  551     if swap_cmd:
  552         swap_data = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()
  553         try:
  554             swap_total = (int(swap_data[-2]) + int(swap_data[-6])) * 4
  555         except ValueError:
  556             swap_total = None
  557         grains['swap_total'] = swap_total
  558     else:
  559         log.error('The \'swap\' binary was not found in $PATH.')
  560     return grains
  561 
  562 
  563 def _windows_memdata():
  564     '''
  565     Return the memory information for Windows systems
  566     '''
  567     grains = {'mem_total': 0}
  568     # get the Total Physical memory as reported by msinfo32
  569     tot_bytes = win32api.GlobalMemoryStatusEx()['TotalPhys']
  570     # return memory info in gigabytes
  571     grains['mem_total'] = int(tot_bytes / (1024 ** 2))
  572     return grains
  573 
  574 
  575 def _memdata(osdata):
  576     '''
  577     Gather information about the system memory
  578     '''
  579     # Provides:
  580     #   mem_total
  581     #   swap_total, for supported systems.
  582     grains = {'mem_total': 0}
  583     if osdata['kernel'] == 'Linux':
  584         grains.update(_linux_memdata())
  585     elif osdata['kernel'] in ('FreeBSD', 'OpenBSD', 'NetBSD'):
  586         grains.update(_bsd_memdata(osdata))
  587     elif osdata['kernel'] == 'Darwin':
  588         grains.update(_osx_memdata())
  589     elif osdata['kernel'] == 'SunOS':
  590         grains.update(_sunos_memdata())
  591     elif osdata['kernel'] == 'AIX':
  592         grains.update(_aix_memdata())
  593     elif osdata['kernel'] == 'Windows' and HAS_WMI:
  594         grains.update(_windows_memdata())
  595     return grains
  596 
  597 
  598 def _aix_get_machine_id():
  599     '''
  600     Parse the output of lsattr -El sys0 for os_uuid
  601     '''
  602     grains = {}
  603     cmd = salt.utils.path.which('lsattr')
  604     if cmd:
  605         data = __salt__['cmd.run']('{0} -El sys0'.format(cmd)) + os.linesep
  606         uuid_regexes = [re.compile(r'(?im)^\s*os_uuid\s+(\S+)\s+(.*)')]
  607         for regex in uuid_regexes:
  608             res = regex.search(data)
  609             if res and len(res.groups()) >= 1:
  610                 grains['machine_id'] = res.group(1).strip()
  611                 break
  612     else:
  613         log.error('The \'lsattr\' binary was not found in $PATH.')
  614     return grains
  615 
  616 
  617 def _windows_virtual(osdata):
  618     '''
  619     Returns what type of virtual hardware is under the hood, kvm or physical
  620     '''
  621     # Provides:
  622     #   virtual
  623     #   virtual_subtype
  624     grains = dict()
  625     if osdata['kernel'] != 'Windows':
  626         return grains
  627 
  628     grains['virtual'] = 'physical'
  629 
  630     # It is possible that the 'manufacturer' and/or 'productname' grains
  631     # exist but have a value of None.
  632     manufacturer = osdata.get('manufacturer', '')
  633     if manufacturer is None:
  634         manufacturer = ''
  635     productname = osdata.get('productname', '')
  636     if productname is None:
  637         productname = ''
  638 
  639     if 'QEMU' in manufacturer:
  640         # FIXME: Make this detect between kvm or qemu
  641         grains['virtual'] = 'kvm'
  642     if 'Bochs' in manufacturer:
  643         grains['virtual'] = 'kvm'
  644     # Product Name: (oVirt) www.ovirt.org
  645     # Red Hat Community virtualization Project based on kvm
  646     elif 'oVirt' in productname:
  647         grains['virtual'] = 'kvm'
  648         grains['virtual_subtype'] = 'oVirt'
  649     # Red Hat Enterprise Virtualization
  650     elif 'RHEV Hypervisor' in productname:
  651         grains['virtual'] = 'kvm'
  652         grains['virtual_subtype'] = 'rhev'
  653     # Product Name: VirtualBox
  654     elif 'VirtualBox' in productname:
  655         grains['virtual'] = 'VirtualBox'
  656     # Product Name: VMware Virtual Platform
  657     elif 'VMware Virtual Platform' in productname:
  658         grains['virtual'] = 'VMware'
  659     # Manufacturer: Microsoft Corporation
  660     # Product Name: Virtual Machine
  661     elif 'Microsoft' in manufacturer and \
  662          'Virtual Machine' in productname:
  663         grains['virtual'] = 'VirtualPC'
  664     # Manufacturer: Parallels Software International Inc.
  665     elif 'Parallels Software' in manufacturer:
  666         grains['virtual'] = 'Parallels'
  667     # Apache CloudStack
  668     elif 'CloudStack KVM Hypervisor' in productname:
  669         grains['virtual'] = 'kvm'
  670         grains['virtual_subtype'] = 'cloudstack'
  671     return grains
  672 
  673 
  674 def _virtual(osdata):
  675     '''
  676     Returns what type of virtual hardware is under the hood, kvm or physical
  677     '''
  678     # This is going to be a monster, if you are running a vm you can test this
  679     # grain with please submit patches!
  680     # Provides:
  681     #   virtual
  682     #   virtual_subtype
  683     grains = {'virtual': 'physical'}
  684 
  685     # Skip the below loop on platforms which have none of the desired cmds
  686     # This is a temporary measure until we can write proper virtual hardware
  687     # detection.
  688     skip_cmds = ('AIX',)
  689 
  690     # list of commands to be executed to determine the 'virtual' grain
  691     _cmds = ['systemd-detect-virt', 'virt-what', 'dmidecode']
  692     # test first for virt-what, which covers most of the desired functionality
  693     # on most platforms
  694     if not salt.utils.platform.is_windows() and osdata['kernel'] not in skip_cmds:
  695         if salt.utils.path.which('virt-what'):
  696             _cmds = ['virt-what']
  697 
  698     # Check if enable_lspci is True or False
  699     if __opts__.get('enable_lspci', True) is True:
  700         # /proc/bus/pci does not exists, lspci will fail
  701         if os.path.exists('/proc/bus/pci'):
  702             _cmds += ['lspci']
  703 
  704     # Add additional last resort commands
  705     if osdata['kernel'] in skip_cmds:
  706         _cmds = ()
  707 
  708     # Quick backout for BrandZ (Solaris LX Branded zones)
  709     # Don't waste time trying other commands to detect the virtual grain
  710     if HAS_UNAME and osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname():
  711         grains['virtual'] = 'zone'
  712         return grains
  713 
  714     failed_commands = set()
  715     for command in _cmds:
  716         args = []
  717         if osdata['kernel'] == 'Darwin':
  718             command = 'system_profiler'
  719             args = ['SPDisplaysDataType']
  720         elif osdata['kernel'] == 'SunOS':
  721             virtinfo = salt.utils.path.which('virtinfo')
  722             if virtinfo:
  723                 try:
  724                     ret = __salt__['cmd.run_all']('{0} -a'.format(virtinfo))
  725                 except salt.exceptions.CommandExecutionError:
  726                     if salt.log.is_logging_configured():
  727                         failed_commands.add(virtinfo)
  728                 else:
  729                     if ret['stdout'].endswith('not supported'):
  730                         command = 'prtdiag'
  731                     else:
  732                         command = 'virtinfo'
  733             else:
  734                 command = 'prtdiag'
  735 
  736         cmd = salt.utils.path.which(command)
  737 
  738         if not cmd:
  739             continue
  740 
  741         cmd = '{0} {1}'.format(cmd, ' '.join(args))
  742 
  743         try:
  744             ret = __salt__['cmd.run_all'](cmd)
  745 
  746             if ret['retcode'] > 0:
  747                 if salt.log.is_logging_configured():
  748                     # systemd-detect-virt always returns > 0 on non-virtualized
  749                     # systems
  750                     # prtdiag only works in the global zone, skip if it fails
  751                     if salt.utils.platform.is_windows() or 'systemd-detect-virt' in cmd or 'prtdiag' in cmd:
  752                         continue
  753                     failed_commands.add(command)
  754                 continue
  755         except salt.exceptions.CommandExecutionError:
  756             if salt.log.is_logging_configured():
  757                 if salt.utils.platform.is_windows():
  758                     continue
  759                 failed_commands.add(command)
  760             continue
  761 
  762         output = ret['stdout']
  763         if command == "system_profiler":
  764             macoutput = output.lower()
  765             if '0x1ab8' in macoutput:
  766                 grains['virtual'] = 'Parallels'
  767             if 'parallels' in macoutput:
  768                 grains['virtual'] = 'Parallels'
  769             if 'vmware' in macoutput:
  770                 grains['virtual'] = 'VMware'
  771             if '0x15ad' in macoutput:
  772                 grains['virtual'] = 'VMware'
  773             if 'virtualbox' in macoutput:
  774                 grains['virtual'] = 'VirtualBox'
  775             # Break out of the loop so the next log message is not issued
  776             break
  777         elif command == 'systemd-detect-virt':
  778             if output in ('qemu', 'kvm', 'oracle', 'xen', 'bochs', 'chroot', 'uml', 'systemd-nspawn'):
  779                 grains['virtual'] = output
  780                 break
  781             elif 'vmware' in output:
  782                 grains['virtual'] = 'VMware'
  783                 break
  784             elif 'microsoft' in output:
  785                 grains['virtual'] = 'VirtualPC'
  786                 break
  787             elif 'lxc' in output:
  788                 grains['virtual'] = 'LXC'
  789                 break
  790             elif 'systemd-nspawn' in output:
  791                 grains['virtual'] = 'LXC'
  792                 break
  793         elif command == 'virt-what':
  794             try:
  795                 output = output.splitlines()[-1]
  796             except IndexError:
  797                 pass
  798             if output in ('kvm', 'qemu', 'uml', 'xen', 'lxc'):
  799                 grains['virtual'] = output
  800                 break
  801             elif 'vmware' in output:
  802                 grains['virtual'] = 'VMware'
  803                 break
  804             elif 'parallels' in output:
  805                 grains['virtual'] = 'Parallels'
  806                 break
  807             elif 'hyperv' in output:
  808                 grains['virtual'] = 'HyperV'
  809                 break
  810         elif command == 'dmidecode':
  811             # Product Name: VirtualBox
  812             if 'Vendor: QEMU' in output:
  813                 # FIXME: Make this detect between kvm or qemu
  814                 grains['virtual'] = 'kvm'
  815             if 'Manufacturer: QEMU' in output:
  816                 grains['virtual'] = 'kvm'
  817             if 'Vendor: Bochs' in output:
  818                 grains['virtual'] = 'kvm'
  819             if 'Manufacturer: Bochs' in output:
  820                 grains['virtual'] = 'kvm'
  821             if 'BHYVE' in output:
  822                 grains['virtual'] = 'bhyve'
  823             # Product Name: (oVirt) www.ovirt.org
  824             # Red Hat Community virtualization Project based on kvm
  825             elif 'Manufacturer: oVirt' in output:
  826                 grains['virtual'] = 'kvm'
  827                 grains['virtual_subtype'] = 'ovirt'
  828             # Red Hat Enterprise Virtualization
  829             elif 'Product Name: RHEV Hypervisor' in output:
  830                 grains['virtual'] = 'kvm'
  831                 grains['virtual_subtype'] = 'rhev'
  832             elif 'VirtualBox' in output:
  833                 grains['virtual'] = 'VirtualBox'
  834             # Product Name: VMware Virtual Platform
  835             elif 'VMware' in output:
  836                 grains['virtual'] = 'VMware'
  837             # Manufacturer: Microsoft Corporation
  838             # Product Name: Virtual Machine
  839             elif ': Microsoft' in output and 'Virtual Machine' in output:
  840                 grains['virtual'] = 'VirtualPC'
  841             # Manufacturer: Parallels Software International Inc.
  842             elif 'Parallels Software' in output:
  843                 grains['virtual'] = 'Parallels'
  844             elif 'Manufacturer: Google' in output:
  845                 grains['virtual'] = 'kvm'
  846             # Proxmox KVM
  847             elif 'Vendor: SeaBIOS' in output:
  848                 grains['virtual'] = 'kvm'
  849             # Break out of the loop, lspci parsing is not necessary
  850             break
  851         elif command == 'lspci':
  852             # dmidecode not available or the user does not have the necessary
  853             # permissions
  854             model = output.lower()
  855             if 'vmware' in model:
  856                 grains['virtual'] = 'VMware'
  857             # 00:04.0 System peripheral: InnoTek Systemberatung GmbH
  858             #         VirtualBox Guest Service
  859             elif 'virtualbox' in model:
  860                 grains['virtual'] = 'VirtualBox'
  861             elif 'qemu' in model:
  862                 grains['virtual'] = 'kvm'
  863             elif 'virtio' in model:
  864                 grains['virtual'] = 'kvm'
  865             # Break out of the loop so the next log message is not issued
  866             break
  867         elif command == 'prtdiag':
  868             model = output.lower().split("\n")[0]
  869             if 'vmware' in model:
  870                 grains['virtual'] = 'VMware'
  871             elif 'virtualbox' in model:
  872                 grains['virtual'] = 'VirtualBox'
  873             elif 'qemu' in model:
  874                 grains['virtual'] = 'kvm'
  875             elif 'joyent smartdc hvm' in model:
  876                 grains['virtual'] = 'kvm'
  877             break
  878         elif command == 'virtinfo':
  879             grains['virtual'] = 'LDOM'
  880             break
  881 
  882     choices = ('Linux', 'HP-UX')
  883     isdir = os.path.isdir
  884     sysctl = salt.utils.path.which('sysctl')
  885     if osdata['kernel'] in choices:
  886         if os.path.isdir('/proc'):
  887             try:
  888                 self_root = os.stat('/')
  889                 init_root = os.stat('/proc/1/root/.')
  890                 if self_root != init_root:
  891                     grains['virtual_subtype'] = 'chroot'
  892             except (IOError, OSError):
  893                 pass
  894         if isdir('/proc/vz'):
  895             if os.path.isfile('/proc/vz/version'):
  896                 grains['virtual'] = 'openvzhn'
  897             elif os.path.isfile('/proc/vz/veinfo'):
  898                 grains['virtual'] = 'openvzve'
  899                 # a posteriori, it's expected for these to have failed:
  900                 failed_commands.discard('lspci')
  901                 failed_commands.discard('dmidecode')
  902         # Provide additional detection for OpenVZ
  903         if os.path.isfile('/proc/self/status'):
  904             with salt.utils.files.fopen('/proc/self/status') as status_file:
  905                 vz_re = re.compile(r'^envID:\s+(\d+)$')
  906                 for line in status_file:
  907                     vz_match = vz_re.match(line.rstrip('\n'))
  908                     if vz_match and int(vz_match.groups()[0]) != 0:
  909                         grains['virtual'] = 'openvzve'
  910                     elif vz_match and int(vz_match.groups()[0]) == 0:
  911                         grains['virtual'] = 'openvzhn'
  912         if isdir('/proc/sys/xen') or \
  913                 isdir('/sys/bus/xen') or isdir('/proc/xen'):
  914             if os.path.isfile('/proc/xen/xsd_kva'):
  915                 # Tested on CentOS 5.3 / 2.6.18-194.26.1.el5xen
  916                 # Tested on CentOS 5.4 / 2.6.18-164.15.1.el5xen
  917                 grains['virtual_subtype'] = 'Xen Dom0'
  918             else:
  919                 if osdata.get('productname', '') == 'HVM domU':
  920                     # Requires dmidecode!
  921                     grains['virtual_subtype'] = 'Xen HVM DomU'
  922                 elif os.path.isfile('/proc/xen/capabilities') and \
  923                         os.access('/proc/xen/capabilities', os.R_OK):
  924                     with salt.utils.files.fopen('/proc/xen/capabilities') as fhr:
  925                         if 'control_d' not in fhr.read():
  926                             # Tested on CentOS 5.5 / 2.6.18-194.3.1.el5xen
  927                             grains['virtual_subtype'] = 'Xen PV DomU'
  928                         else:
  929                             # Shouldn't get to this, but just in case
  930                             grains['virtual_subtype'] = 'Xen Dom0'
  931                 # Tested on Fedora 10 / 2.6.27.30-170.2.82 with xen
  932                 # Tested on Fedora 15 / 2.6.41.4-1 without running xen
  933                 elif isdir('/sys/bus/xen'):
  934                     if 'xen:' in __salt__['cmd.run']('dmesg').lower():
  935                         grains['virtual_subtype'] = 'Xen PV DomU'
  936                     elif os.path.isfile('/sys/bus/xen/drivers/xenconsole'):
  937                         # An actual DomU will have the xenconsole driver
  938                         grains['virtual_subtype'] = 'Xen PV DomU'
  939             # If a Dom0 or DomU was detected, obviously this is xen
  940             if 'dom' in grains.get('virtual_subtype', '').lower():
  941                 grains['virtual'] = 'xen'
  942         # Check container type after hypervisors, to avoid variable overwrite on containers running in virtual environment.
  943         if os.path.isfile('/proc/1/cgroup'):
  944             try:
  945                 with salt.utils.files.fopen('/proc/1/cgroup', 'r') as fhr:
  946                     fhr_contents = fhr.read()
  947                 if ':/lxc/' in fhr_contents:
  948                     grains['virtual_subtype'] = 'LXC'
  949                 elif ':/kubepods/' in fhr_contents:
  950                     grains['virtual_subtype'] = 'kubernetes'
  951                 elif ':/libpod_parent/' in fhr_contents:
  952                     grains['virtual_subtype'] = 'libpod'
  953                 else:
  954                     if any(x in fhr_contents
  955                            for x in (':/system.slice/docker', ':/docker/',
  956                                      ':/docker-ce/')):
  957                         grains['virtual_subtype'] = 'Docker'
  958             except IOError:
  959                 pass
  960         if os.path.isfile('/proc/cpuinfo'):
  961             with salt.utils.files.fopen('/proc/cpuinfo', 'r') as fhr:
  962                 if 'QEMU Virtual CPU' in fhr.read():
  963                     grains['virtual'] = 'kvm'
  964         if os.path.isfile('/sys/devices/virtual/dmi/id/product_name'):
  965             try:
  966                 with salt.utils.files.fopen('/sys/devices/virtual/dmi/id/product_name', 'r') as fhr:
  967                     output = salt.utils.stringutils.to_unicode(fhr.read(), errors='replace')
  968                     if 'VirtualBox' in output:
  969                         grains['virtual'] = 'VirtualBox'
  970                     elif 'RHEV Hypervisor' in output:
  971                         grains['virtual'] = 'kvm'
  972                         grains['virtual_subtype'] = 'rhev'
  973                     elif 'oVirt Node' in output:
  974                         grains['virtual'] = 'kvm'
  975                         grains['virtual_subtype'] = 'ovirt'
  976                     elif 'Google' in output:
  977                         grains['virtual'] = 'gce'
  978                     elif 'BHYVE' in output:
  979                         grains['virtual'] = 'bhyve'
  980             except IOError:
  981                 pass
  982     elif osdata['kernel'] == 'FreeBSD':
  983         kenv = salt.utils.path.which('kenv')
  984         if kenv:
  985             product = __salt__['cmd.run'](
  986                 '{0} smbios.system.product'.format(kenv)
  987             )
  988             maker = __salt__['cmd.run'](
  989                 '{0} smbios.system.maker'.format(kenv)
  990             )
  991             if product.startswith('VMware'):
  992                 grains['virtual'] = 'VMware'
  993             if product.startswith('VirtualBox'):
  994                 grains['virtual'] = 'VirtualBox'
  995             if maker.startswith('Xen'):
  996                 grains['virtual_subtype'] = '{0} {1}'.format(maker, product)
  997                 grains['virtual'] = 'xen'
  998             if maker.startswith('Microsoft') and product.startswith('Virtual'):
  999                 grains['virtual'] = 'VirtualPC'
 1000             if maker.startswith('OpenStack'):
 1001                 grains['virtual'] = 'OpenStack'
 1002             if maker.startswith('Bochs'):
 1003                 grains['virtual'] = 'kvm'
 1004         if sysctl:
 1005             hv_vendor = __salt__['cmd.run']('{0} hw.hv_vendor'.format(sysctl))
 1006             model = __salt__['cmd.run']('{0} hw.model'.format(sysctl))
 1007             jail = __salt__['cmd.run'](
 1008                 '{0} -n security.jail.jailed'.format(sysctl)
 1009             )
 1010             if 'bhyve' in hv_vendor:
 1011                 grains['virtual'] = 'bhyve'
 1012             if jail == '1':
 1013                 grains['virtual_subtype'] = 'jail'
 1014             if 'QEMU Virtual CPU' in model:
 1015                 grains['virtual'] = 'kvm'
 1016     elif osdata['kernel'] == 'OpenBSD':
 1017         if 'manufacturer' in osdata:
 1018             if osdata['manufacturer'] in ['QEMU', 'Red Hat', 'Joyent']:
 1019                 grains['virtual'] = 'kvm'
 1020             if osdata['manufacturer'] == 'OpenBSD':
 1021                 grains['virtual'] = 'vmm'
 1022     elif osdata['kernel'] == 'SunOS':
 1023         if grains['virtual'] == 'LDOM':
 1024             roles = []
 1025             for role in ('control', 'io', 'root', 'service'):
 1026                 subtype_cmd = '{0} -c current get -H -o value {1}-role'.format(cmd, role)
 1027                 ret = __salt__['cmd.run_all']('{0}'.format(subtype_cmd))
 1028                 if ret['stdout'] == 'true':
 1029                     roles.append(role)
 1030             if roles:
 1031                 grains['virtual_subtype'] = roles
 1032         else:
 1033             # Check if it's a "regular" zone. (i.e. Solaris 10/11 zone)
 1034             zonename = salt.utils.path.which('zonename')
 1035             if zonename:
 1036                 zone = __salt__['cmd.run']('{0}'.format(zonename))
 1037                 if zone != 'global':
 1038                     grains['virtual'] = 'zone'
 1039             # Check if it's a branded zone (i.e. Solaris 8/9 zone)
 1040             if isdir('/.SUNWnative'):
 1041                 grains['virtual'] = 'zone'
 1042     elif osdata['kernel'] == 'NetBSD':
 1043         if sysctl:
 1044             if 'QEMU Virtual CPU' in __salt__['cmd.run'](
 1045                     '{0} -n machdep.cpu_brand'.format(sysctl)):
 1046                 grains['virtual'] = 'kvm'
 1047             elif 'invalid' not in __salt__['cmd.run'](
 1048                     '{0} -n machdep.xen.suspend'.format(sysctl)):
 1049                 grains['virtual'] = 'Xen PV DomU'
 1050             elif 'VMware' in __salt__['cmd.run'](
 1051                     '{0} -n machdep.dmi.system-vendor'.format(sysctl)):
 1052                 grains['virtual'] = 'VMware'
 1053             # NetBSD has Xen dom0 support
 1054             elif __salt__['cmd.run'](
 1055                     '{0} -n machdep.idle-mechanism'.format(sysctl)) == 'xen':
 1056                 if os.path.isfile('/var/run/xenconsoled.pid'):
 1057                     grains['virtual_subtype'] = 'Xen Dom0'
 1058 
 1059     for command in failed_commands:
 1060         log.info(
 1061             "Although '%s' was found in path, the current user "
 1062             'cannot execute it. Grains output might not be '
 1063             'accurate.', command
 1064         )
 1065     return grains
 1066 
 1067 
 1068 def _virtual_hv(osdata):
 1069     '''
 1070     Returns detailed hypervisor information from sysfs
 1071     Currently this seems to be used only by Xen
 1072     '''
 1073     grains = {}
 1074 
 1075     # Bail early if we're not running on Xen
 1076     try:
 1077         if 'xen' not in osdata['virtual']:
 1078             return grains
 1079     except KeyError:
 1080         return grains
 1081 
 1082     # Try to get the exact hypervisor version from sysfs
 1083     try:
 1084         version = {}
 1085         for fn in ('major', 'minor', 'extra'):
 1086             with salt.utils.files.fopen('/sys/hypervisor/version/{}'.format(fn), 'r') as fhr:
 1087                 version[fn] = salt.utils.stringutils.to_unicode(fhr.read().strip())
 1088         grains['virtual_hv_version'] = '{}.{}{}'.format(version['major'], version['minor'], version['extra'])
 1089         grains['virtual_hv_version_info'] = [version['major'], version['minor'], version['extra']]
 1090     except (IOError, OSError, KeyError):
 1091         pass
 1092 
 1093     # Try to read and decode the supported feature set of the hypervisor
 1094     # Based on https://github.com/brendangregg/Misc/blob/master/xen/xen-features.py
 1095     # Table data from include/xen/interface/features.h
 1096     xen_feature_table = {0: 'writable_page_tables',
 1097                          1: 'writable_descriptor_tables',
 1098                          2: 'auto_translated_physmap',
 1099                          3: 'supervisor_mode_kernel',
 1100                          4: 'pae_pgdir_above_4gb',
 1101                          5: 'mmu_pt_update_preserve_ad',
 1102                          7: 'gnttab_map_avail_bits',
 1103                          8: 'hvm_callback_vector',
 1104                          9: 'hvm_safe_pvclock',
 1105                         10: 'hvm_pirqs',
 1106                         11: 'dom0',
 1107                         12: 'grant_map_identity',
 1108                         13: 'memory_op_vnode_supported',
 1109                         14: 'ARM_SMCCC_supported'}
 1110     try:
 1111         with salt.utils.files.fopen('/sys/hypervisor/properties/features', 'r') as fhr:
 1112             features = salt.utils.stringutils.to_unicode(fhr.read().strip())
 1113         enabled_features = []
 1114         for bit, feat in six.iteritems(xen_feature_table):
 1115             if int(features, 16) & (1 << bit):
 1116                 enabled_features.append(feat)
 1117         grains['virtual_hv_features'] = features
 1118         grains['virtual_hv_features_list'] = enabled_features
 1119     except (IOError, OSError, KeyError):
 1120         pass
 1121 
 1122     return grains
 1123 
 1124 
 1125 def _ps(osdata):
 1126     '''
 1127     Return the ps grain
 1128     '''
 1129     grains = {}
 1130     bsd_choices = ('FreeBSD', 'NetBSD', 'OpenBSD', 'MacOS')
 1131     if osdata['os'] in bsd_choices:
 1132         grains['ps'] = 'ps auxwww'
 1133     elif osdata['os_family'] == 'Solaris':
 1134         grains['ps'] = '/usr/ucb/ps auxwww'
 1135     elif osdata['os'] == 'Windows':
 1136         grains['ps'] = 'tasklist.exe'
 1137     elif osdata.get('virtual', '') == 'openvzhn':
 1138         grains['ps'] = (
 1139             'ps -fH -p $(grep -l \"^envID:[[:space:]]*0\\$\" '
 1140             '/proc/[0-9]*/status | sed -e \"s=/proc/\\([0-9]*\\)/.*=\\1=\")  '
 1141             '| awk \'{ $7=\"\"; print }\''
 1142         )
 1143     elif osdata['os_family'] == 'AIX':
 1144         grains['ps'] = '/usr/bin/ps auxww'
 1145     elif osdata['os_family'] == 'NILinuxRT':
 1146         grains['ps'] = 'ps -o user,pid,ppid,tty,time,comm'
 1147     else:
 1148         grains['ps'] = 'ps -efHww'
 1149     return grains
 1150 
 1151 
 1152 def _clean_value(key, val):
 1153     '''
 1154     Clean out well-known bogus values.
 1155     If it isn't clean (for example has value 'None'), return None.
 1156     Otherwise, return the original value.
 1157 
 1158     NOTE: This logic also exists in the smbios module. This function is
 1159           for use when not using smbios to retrieve the value.
 1160     '''
 1161     if (val is None or not val or
 1162             re.match('none', val, flags=re.IGNORECASE)):
 1163         return None
 1164     elif 'uuid' in key:
 1165         # Try each version (1-5) of RFC4122 to check if it's actually a UUID
 1166         for uuidver in range(1, 5):
 1167             try:
 1168                 uuid.UUID(val, version=uuidver)
 1169                 return val
 1170             except ValueError:
 1171                 continue
 1172         log.trace('HW %s value %s is an invalid UUID', key, val.replace('\n', ' '))
 1173         return None
 1174     elif re.search('serial|part|version', key):
 1175         # 'To be filled by O.E.M.
 1176         # 'Not applicable' etc.
 1177         # 'Not specified' etc.
 1178         # 0000000, 1234567 etc.
 1179         # begone!
 1180         if (re.match(r'^[0]+$', val) or
 1181                 re.match(r'[0]?1234567[8]?[9]?[0]?', val) or
 1182                 re.search(r'sernum|part[_-]?number|specified|filled|applicable', val, flags=re.IGNORECASE)):
 1183             return None
 1184     elif re.search('asset|manufacturer', key):
 1185         # AssetTag0. Manufacturer04. Begone.
 1186         if re.search(r'manufacturer|to be filled|available|asset|^no(ne|t)', val, flags=re.IGNORECASE):
 1187             return None
 1188     else:
 1189         # map unspecified, undefined, unknown & whatever to None
 1190         if (re.search(r'to be filled', val, flags=re.IGNORECASE) or
 1191                 re.search(r'un(known|specified)|no(t|ne)? (asset|provided|defined|available|present|specified)',
 1192                     val, flags=re.IGNORECASE)):
 1193             return None
 1194     return val
 1195 
 1196 
 1197 def _windows_os_release_grain(caption, product_type):
 1198     '''
 1199     helper function for getting the osrelease grain
 1200     :return:
 1201     '''
 1202     # This creates the osrelease grain based on the Windows Operating
 1203     # System Product Name. As long as Microsoft maintains a similar format
 1204     # this should be future proof
 1205     version = 'Unknown'
 1206     release = ''
 1207     if 'Server' in caption:
 1208         for item in caption.split(' '):
 1209             # If it's all digits, then it's version
 1210             if re.match(r'\d+', item):
 1211                 version = item
 1212             # If it starts with R and then numbers, it's the release
 1213             # ie: R2
 1214             if re.match(r'^R\d+$', item):
 1215                 release = item
 1216         os_release = '{0}Server{1}'.format(version, release)
 1217     else:
 1218         for item in caption.split(' '):
 1219             # If it's a number, decimal number, Thin or Vista, then it's the
 1220             # version
 1221             if re.match(r'^(\d+(\.\d+)?)|Thin|Vista|XP$', item):
 1222                 version = item
 1223         os_release = version
 1224 
 1225     # If the version is still Unknown, revert back to the old way of getting
 1226     # the os_release
 1227     # https://github.com/saltstack/salt/issues/52339
 1228     if os_release in ['Unknown']:
 1229         os_release = platform.release()
 1230         server = {'Vista': '2008Server',
 1231                   '7': '2008ServerR2',
 1232                   '8': '2012Server',
 1233                   '8.1': '2012ServerR2',
 1234                   '10': '2016Server'}
 1235 
 1236         # Starting with Python 2.7.12 and 3.5.2 the `platform.uname()`
 1237         # function started reporting the Desktop version instead of the
 1238         # Server version on # Server versions of Windows, so we need to look
 1239         # those up. So, if you find a Server Platform that's a key in the
 1240         # server dictionary, then lookup the actual Server Release.
 1241         # (Product Type 1 is Desktop, Everything else is Server)
 1242         if product_type > 1 and os_release in server:
 1243             os_release = server[os_release]
 1244 
 1245     return os_release
 1246 
 1247 
 1248 def _windows_platform_data():
 1249     '''
 1250     Use the platform module for as much as we can.
 1251     '''
 1252     # Provides:
 1253     #    kernelrelease
 1254     #    kernelversion
 1255     #    osversion
 1256     #    osrelease
 1257     #    osservicepack
 1258     #    osmanufacturer
 1259     #    manufacturer
 1260     #    productname
 1261     #    biosversion
 1262     #    serialnumber
 1263     #    osfullname
 1264     #    timezone
 1265     #    windowsdomain
 1266     #    windowsdomaintype
 1267     #    motherboard.productname
 1268     #    motherboard.serialnumber
 1269     #    virtual
 1270 
 1271     if not HAS_WMI:
 1272         return {}
 1273 
 1274     with salt.utils.winapi.Com():
 1275         wmi_c = wmi.WMI()
 1276         # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx
 1277         systeminfo = wmi_c.Win32_ComputerSystem()[0]
 1278         # https://msdn.microsoft.com/en-us/library/aa394239(v=vs.85).aspx
 1279         osinfo = wmi_c.Win32_OperatingSystem()[0]
 1280         # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394077(v=vs.85).aspx
 1281         biosinfo = wmi_c.Win32_BIOS()[0]
 1282         # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394498(v=vs.85).aspx
 1283         timeinfo = wmi_c.Win32_TimeZone()[0]
 1284 
 1285         # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394072(v=vs.85).aspx
 1286         motherboard = {'product': None,
 1287                        'serial': None}
 1288         try:
 1289             motherboardinfo = wmi_c.Win32_BaseBoard()[0]
 1290             motherboard['product'] = motherboardinfo.Product
 1291             motherboard['serial'] = motherboardinfo.SerialNumber
 1292         except IndexError:
 1293             log.debug('Motherboard info not available on this system')
 1294 
 1295         kernel_version = platform.version()
 1296         info = salt.utils.win_osinfo.get_os_version_info()
 1297         net_info = salt.utils.win_osinfo.get_join_info()
 1298 
 1299         service_pack = None
 1300         if info['ServicePackMajor'] > 0:
 1301             service_pack = ''.join(['SP', six.text_type(info['ServicePackMajor'])])
 1302 
 1303         os_release = _windows_os_release_grain(caption=osinfo.Caption,
 1304                                                product_type=osinfo.ProductType)
 1305 
 1306         grains = {
 1307             'kernelrelease': _clean_value('kernelrelease', osinfo.Version),
 1308             'kernelversion': _clean_value('kernelversion', kernel_version),
 1309             'osversion': _clean_value('osversion', osinfo.Version),
 1310             'osrelease': _clean_value('osrelease', os_release),
 1311             'osservicepack': _clean_value('osservicepack', service_pack),
 1312             'osmanufacturer': _clean_value('osmanufacturer', osinfo.Manufacturer),
 1313             'manufacturer': _clean_value('manufacturer', systeminfo.Manufacturer),
 1314             'productname': _clean_value('productname', systeminfo.Model),
 1315             # bios name had a bunch of whitespace appended to it in my testing
 1316             # 'PhoenixBIOS 4.0 Release 6.0     '
 1317             'biosversion': _clean_value('biosversion', biosinfo.Name.strip()),
 1318             'serialnumber': _clean_value('serialnumber', biosinfo.SerialNumber),
 1319             'osfullname': _clean_value('osfullname', osinfo.Caption),
 1320             'timezone': _clean_value('timezone', timeinfo.Description),
 1321             'windowsdomain': _clean_value('windowsdomain', net_info['Domain']),
 1322             'windowsdomaintype': _clean_value('windowsdomaintype', net_info['DomainType']),
 1323             'motherboard': {
 1324                 'productname': _clean_value('motherboard.productname', motherboard['product']),
 1325                 'serialnumber': _clean_value('motherboard.serialnumber', motherboard['serial']),
 1326             }
 1327         }
 1328 
 1329         # test for virtualized environments
 1330         # I only had VMware available so the rest are unvalidated
 1331         if 'VRTUAL' in biosinfo.Version:  # (not a typo)
 1332             grains['virtual'] = 'HyperV'
 1333         elif 'A M I' in biosinfo.Version:
 1334             grains['virtual'] = 'VirtualPC'
 1335         elif 'VMware' in systeminfo.Model:
 1336             grains['virtual'] = 'VMware'
 1337         elif 'VirtualBox' in systeminfo.Model:
 1338             grains['virtual'] = 'VirtualBox'
 1339         elif 'Xen' in biosinfo.Version:
 1340             grains['virtual'] = 'Xen'
 1341             if 'HVM domU' in systeminfo.Model:
 1342                 grains['virtual_subtype'] = 'HVM domU'
 1343         elif 'OpenStack' in systeminfo.Model:
 1344             grains['virtual'] = 'OpenStack'
 1345         elif 'AMAZON' in biosinfo.Version:
 1346             grains['virtual'] = 'EC2'
 1347 
 1348     return grains
 1349 
 1350 
 1351 def _osx_platform_data():
 1352     '''
 1353     Additional data for macOS systems
 1354     Returns: A dictionary containing values for the following:
 1355         - model_name
 1356         - boot_rom_version
 1357         - smc_version
 1358         - system_serialnumber
 1359     '''
 1360     cmd = 'system_profiler SPHardwareDataType'
 1361     hardware = __salt__['cmd.run'](cmd)
 1362 
 1363     grains = {}
 1364     for line in hardware.splitlines():
 1365         field_name, _, field_val = line.partition(': ')
 1366         if field_name.strip() == "Model Name":
 1367             key = 'model_name'
 1368             grains[key] = _clean_value(key, field_val)
 1369         if field_name.strip() == "Boot ROM Version":
 1370             key = 'boot_rom_version'
 1371             grains[key] = _clean_value(key, field_val)
 1372         if field_name.strip() == "SMC Version (system)":
 1373             key = 'smc_version'
 1374             grains[key] = _clean_value(key, field_val)
 1375         if field_name.strip() == "Serial Number (system)":
 1376             key = 'system_serialnumber'
 1377             grains[key] = _clean_value(key, field_val)
 1378 
 1379     return grains
 1380 
 1381 
 1382 def id_():
 1383     '''
 1384     Return the id
 1385     '''
 1386     return {'id': __opts__.get('id', '')}
 1387 
 1388 
 1389 _REPLACE_LINUX_RE = re.compile(r'\W(?:gnu/)?linux', re.IGNORECASE)
 1390 
 1391 # This maps (at most) the first ten characters (no spaces, lowercased) of
 1392 # 'osfullname' to the 'os' grain that Salt traditionally uses.
 1393 # Please see os_data() and _supported_dists.
 1394 # If your system is not detecting properly it likely needs an entry here.
 1395 _OS_NAME_MAP = {
 1396     'redhatente': 'RedHat',
 1397     'gentoobase': 'Gentoo',
 1398     'archarm': 'Arch ARM',
 1399     'arch': 'Arch',
 1400     'debian': 'Debian',
 1401     'raspbian': 'Raspbian',
 1402     'fedoraremi': 'Fedora',
 1403     'chapeau': 'Chapeau',
 1404     'korora': 'Korora',
 1405     'amazonami': 'Amazon',
 1406     'alt': 'ALT',
 1407     'enterprise': 'OEL',
 1408     'oracleserv': 'OEL',
 1409     'cloudserve': 'CloudLinux',
 1410     'cloudlinux': 'CloudLinux',
 1411     'pidora': 'Fedora',
 1412     'scientific': 'ScientificLinux',
 1413     'synology': 'Synology',
 1414     'nilrt': 'NILinuxRT',
 1415     'poky': 'Poky',
 1416     'manjaro': 'Manjaro',
 1417     'manjarolin': 'Manjaro',
 1418     'univention': 'Univention',
 1419     'antergos': 'Antergos',
 1420     'sles': 'SUSE',
 1421     'void': 'Void',
 1422     'slesexpand': 'RES',
 1423     'linuxmint': 'Mint',
 1424     'neon': 'KDE neon',
 1425 }
 1426 
 1427 # Map the 'os' grain to the 'os_family' grain
 1428 # These should always be capitalized entries as the lookup comes
 1429 # post-_OS_NAME_MAP. If your system is having trouble with detection, please
 1430 # make sure that the 'os' grain is capitalized and working correctly first.
 1431 _OS_FAMILY_MAP = {
 1432     'Ubuntu': 'Debian',
 1433     'Fedora': 'RedHat',
 1434     'Chapeau': 'RedHat',
 1435     'Korora': 'RedHat',
 1436     'FedBerry': 'RedHat',
 1437     'CentOS': 'RedHat',
 1438     'GoOSe': 'RedHat',
 1439     'Scientific': 'RedHat',
 1440     'Amazon': 'RedHat',
 1441     'CloudLinux': 'RedHat',
 1442     'OVS': 'RedHat',
 1443     'OEL': 'RedHat',
 1444     'XCP': 'RedHat',
 1445     'XCP-ng': 'RedHat',
 1446     'XenServer': 'RedHat',
 1447     'RES': 'RedHat',
 1448     'Sangoma': 'RedHat',
 1449     'Mandrake': 'Mandriva',
 1450     'ESXi': 'VMware',
 1451     'Mint': 'Debian',
 1452     'VMwareESX': 'VMware',
 1453     'Bluewhite64': 'Bluewhite',
 1454     'Slamd64': 'Slackware',
 1455     'SLES': 'Suse',
 1456     'SUSE Enterprise Server': 'Suse',
 1457     'SUSE  Enterprise Server': 'Suse',
 1458     'SLED': 'Suse',
 1459     'openSUSE': 'Suse',
 1460     'SUSE': 'Suse',
 1461     'openSUSE Leap': 'Suse',
 1462     'openSUSE Tumbleweed': 'Suse',
 1463     'SLES_SAP': 'Suse',
 1464     'Solaris': 'Solaris',
 1465     'SmartOS': 'Solaris',
 1466     'OmniOS': 'Solaris',
 1467     'OpenIndiana Development': 'Solaris',
 1468     'OpenIndiana': 'Solaris',
 1469     'OpenSolaris Development': 'Solaris',
 1470     'OpenSolaris': 'Solaris',
 1471     'Oracle Solaris': 'Solaris',
 1472     'Arch ARM': 'Arch',
 1473     'Manjaro': 'Arch',
 1474     'Antergos': 'Arch',
 1475     'ALT': 'RedHat',
 1476     'Trisquel': 'Debian',
 1477     'GCEL': 'Debian',
 1478     'Linaro': 'Debian',
 1479     'elementary OS': 'Debian',
 1480     'elementary': 'Debian',
 1481     'Univention': 'Debian',
 1482     'ScientificLinux': 'RedHat',
 1483     'Raspbian': 'Debian',
 1484     'Devuan': 'Debian',
 1485     'antiX': 'Debian',
 1486     'Kali': 'Debian',
 1487     'neon': 'Debian',
 1488     'Cumulus': 'Debian',
 1489     'Deepin': 'Debian',
 1490     'NILinuxRT': 'NILinuxRT',
 1491     'KDE neon': 'Debian',
 1492     'Void': 'Void',
 1493     'IDMS': 'Debian',
 1494     'Funtoo': 'Gentoo',
 1495     'AIX': 'AIX',
 1496     'TurnKey': 'Debian',
 1497 }
 1498 
 1499 # Matches any possible format:
 1500 #     DISTRIB_ID="Ubuntu"
 1501 #     DISTRIB_ID='Mageia'
 1502 #     DISTRIB_ID=Fedora
 1503 #     DISTRIB_RELEASE='10.10'
 1504 #     DISTRIB_CODENAME='squeeze'
 1505 #     DISTRIB_DESCRIPTION='Ubuntu 10.10'
 1506 _LSB_REGEX = re.compile((
 1507     '^(DISTRIB_(?:ID|RELEASE|CODENAME|DESCRIPTION))=(?:\'|")?'
 1508     '([\\w\\s\\.\\-_]+)(?:\'|")?'
 1509 ))
 1510 
 1511 
 1512 def _linux_bin_exists(binary):
 1513     '''
 1514     Does a binary exist in linux (depends on which, type, or whereis)
 1515     '''
 1516     for search_cmd in ('which', 'type -ap'):
 1517         try:
 1518             return __salt__['cmd.retcode'](
 1519                 '{0} {1}'.format(search_cmd, binary)
 1520             ) == 0
 1521         except salt.exceptions.CommandExecutionError:
 1522             pass
 1523 
 1524     try:
 1525         return len(__salt__['cmd.run_all'](
 1526             'whereis -b {0}'.format(binary)
 1527         )['stdout'].split()) > 1
 1528     except salt.exceptions.CommandExecutionError:
 1529         return False
 1530 
 1531 
 1532 def _get_interfaces():
 1533     '''
 1534     Provide a dict of the connected interfaces and their ip addresses
 1535     '''
 1536 
 1537     global _INTERFACES
 1538     if not _INTERFACES:
 1539         _INTERFACES = salt.utils.network.interfaces()
 1540     return _INTERFACES
 1541 
 1542 
 1543 def _parse_lsb_release():
 1544     ret = {}
 1545     try:
 1546         log.trace('Attempting to parse /etc/lsb-release')
 1547         with salt.utils.files.fopen('/etc/lsb-release') as ifile:
 1548             for line in ifile:
 1549                 try:
 1550                     key, value = _LSB_REGEX.match(line.rstrip('\n')).groups()[:2]
 1551                 except AttributeError:
 1552                     pass
 1553                 else:
 1554                     # Adds lsb_distrib_{id,release,codename,description}
 1555                     ret['lsb_{0}'.format(key.lower())] = value.rstrip()
 1556     except (IOError, OSError) as exc:
 1557         log.trace('Failed to parse /etc/lsb-release: %s', exc)
 1558     return ret
 1559 
 1560 
 1561 def _parse_os_release(*os_release_files):
 1562     '''
 1563     Parse os-release and return a parameter dictionary
 1564 
 1565     See http://www.freedesktop.org/software/systemd/man/os-release.html
 1566     for specification of the file format.
 1567     '''
 1568     ret = {}
 1569     for filename in os_release_files:
 1570         try:
 1571             with salt.utils.files.fopen(filename) as ifile:
 1572                 regex = re.compile('^([\\w]+)=(?:\'|")?(.*?)(?:\'|")?$')
 1573                 for line in ifile:
 1574                     match = regex.match(line.strip())
 1575                     if match:
 1576                         # Shell special characters ("$", quotes, backslash,
 1577                         # backtick) are escaped with backslashes
 1578                         ret[match.group(1)] = re.sub(
 1579                             r'\\([$"\'\\`])', r'\1', match.group(2)
 1580                         )
 1581             break
 1582         except (IOError, OSError):
 1583             pass
 1584 
 1585     return ret
 1586 
 1587 
 1588 def _parse_cpe_name(cpe):
 1589     '''
 1590     Parse CPE_NAME data from the os-release
 1591 
 1592     Info: https://csrc.nist.gov/projects/security-content-automation-protocol/scap-specifications/cpe
 1593 
 1594     Note: cpe:2.3:part:vendor:product:version:update:edition:lang:sw_edition:target_sw:target_hw:other
 1595           however some OS's do not have the full 13 elements, for example:
 1596                 CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
 1597 
 1598     :param cpe:
 1599     :return:
 1600     '''
 1601     part = {
 1602         'o': 'operating system',
 1603         'h': 'hardware',
 1604         'a': 'application',
 1605     }
 1606     ret = {}
 1607     cpe = (cpe or '').split(':')
 1608     if len(cpe) > 4 and cpe[0] == 'cpe':
 1609         if cpe[1].startswith('/'):  # WFN to URI
 1610             ret['vendor'], ret['product'], ret['version'] = cpe[2:5]
 1611             ret['phase'] = cpe[5] if len(cpe) > 5 else None
 1612             ret['part'] = part.get(cpe[1][1:])
 1613         elif len(cpe) == 6 and cpe[1] == '2.3':  # WFN to a string
 1614             ret['vendor'], ret['product'], ret['version'] = [x if x != '*' else None for x in cpe[3:6]]
 1615             ret['phase'] = None
 1616             ret['part'] = part.get(cpe[2])
 1617         elif len(cpe) > 7 and len(cpe) <= 13 and cpe[1] == '2.3':  # WFN to a string
 1618             ret['vendor'], ret['product'], ret['version'], ret['phase'] = [x if x != '*' else None for x in cpe[3:7]]
 1619             ret['part'] = part.get(cpe[2])
 1620 
 1621     return ret
 1622 
 1623 
 1624 def os_data():
 1625     '''
 1626     Return grains pertaining to the operating system
 1627     '''
 1628     grains = {
 1629         'num_gpus': 0,
 1630         'gpus': [],
 1631         }
 1632 
 1633     # Windows Server 2008 64-bit
 1634     # ('Windows', 'MINIONNAME', '2008ServerR2', '6.1.7601', 'AMD64',
 1635     #  'Intel64 Fam ily 6 Model 23 Stepping 6, GenuineIntel')
 1636     # Ubuntu 10.04
 1637     # ('Linux', 'MINIONNAME', '2.6.32-38-server',
 1638     # '#83-Ubuntu SMP Wed Jan 4 11:26:59 UTC 2012', 'x86_64', '')
 1639 
 1640     # pylint: disable=unpacking-non-sequence
 1641     (grains['kernel'], grains['nodename'],
 1642      grains['kernelrelease'], grains['kernelversion'], grains['cpuarch'], _) = platform.uname()
 1643     # pylint: enable=unpacking-non-sequence
 1644 
 1645     if salt.utils.platform.is_proxy():
 1646         grains['kernel'] = 'proxy'
 1647         grains['kernelrelease'] = 'proxy'
 1648         grains['kernelversion'] = 'proxy'
 1649         grains['osrelease'] = 'proxy'
 1650         grains['os'] = 'proxy'
 1651         grains['os_family'] = 'proxy'
 1652         grains['osfullname'] = 'proxy'
 1653     elif salt.utils.platform.is_windows():
 1654         grains['os'] = 'Windows'
 1655         grains['os_family'] = 'Windows'
 1656         grains.update(_memdata(grains))
 1657         grains.update(_windows_platform_data())
 1658         grains.update(_windows_cpudata())
 1659         grains.update(_windows_virtual(grains))
 1660         grains.update(_ps(grains))
 1661 
 1662         if 'Server' in grains['osrelease']:
 1663             osrelease_info = grains['osrelease'].split('Server', 1)
 1664             osrelease_info[1] = osrelease_info[1].lstrip('R')
 1665         else:
 1666             osrelease_info = grains['osrelease'].split('.')
 1667 
 1668         for idx, value in enumerate(osrelease_info):
 1669             if not value.isdigit():
 1670                 continue
 1671             osrelease_info[idx] = int(value)
 1672         grains['osrelease_info'] = tuple(osrelease_info)
 1673 
 1674         grains['osfinger'] = '{os}-{ver}'.format(
 1675             os=grains['os'],
 1676             ver=grains['osrelease'])
 1677 
 1678         grains['init'] = 'Windows'
 1679 
 1680         return grains
 1681     elif salt.utils.platform.is_linux():
 1682         # Add SELinux grain, if you have it
 1683         if _linux_bin_exists('selinuxenabled'):
 1684             log.trace('Adding selinux grains')
 1685             grains['selinux'] = {}
 1686             grains['selinux']['enabled'] = __salt__['cmd.retcode'](
 1687                 'selinuxenabled'
 1688             ) == 0
 1689             if _linux_bin_exists('getenforce'):
 1690                 grains['selinux']['enforced'] = __salt__['cmd.run'](
 1691                     'getenforce'
 1692                 ).strip()
 1693 
 1694         # Add systemd grain, if you have it
 1695         if _linux_bin_exists('systemctl') and _linux_bin_exists('localectl'):
 1696             log.trace('Adding systemd grains')
 1697             grains['systemd'] = {}
 1698             systemd_info = __salt__['cmd.run'](
 1699                 'systemctl --version'
 1700             ).splitlines()
 1701             grains['systemd']['version'] = systemd_info[0].split()[1]
 1702             grains['systemd']['features'] = systemd_info[1]
 1703 
 1704         # Add init grain
 1705         grains['init'] = 'unknown'
 1706         log.trace('Adding init grain')
 1707         try:
 1708             os.stat('/run/systemd/system')
 1709             grains['init'] = 'systemd'
 1710         except (OSError, IOError):
 1711             try:
 1712                 with salt.utils.files.fopen('/proc/1/cmdline') as fhr:
 1713                     init_cmdline = fhr.read().replace('\x00', ' ').split()
 1714             except (IOError, OSError):
 1715                 pass
 1716             else:
 1717                 try:
 1718                     init_bin = salt.utils.path.which(init_cmdline[0])
 1719                 except IndexError:
 1720                     # Emtpy init_cmdline
 1721                     init_bin = None
 1722                     log.warning('Unable to fetch data from /proc/1/cmdline')
 1723                 if init_bin is not None and init_bin.endswith('bin/init'):
 1724                     supported_inits = (b'upstart', b'sysvinit', b'systemd')
 1725                     edge_len = max(len(x) for x in supported_inits) - 1
 1726                     try:
 1727                         buf_size = __opts__['file_buffer_size']
 1728                     except KeyError:
 1729                         # Default to the value of file_buffer_size for the minion
 1730                         buf_size = 262144
 1731                     try:
 1732                         with salt.utils.files.fopen(init_bin, 'rb') as fp_:
 1733                             edge = b''
 1734                             buf = fp_.read(buf_size).lower()
 1735                             while buf:
 1736                                 buf = edge + buf
 1737                                 for item in supported_inits:
 1738                                     if item in buf:
 1739                                         if six.PY3:
 1740                                             item = item.decode('utf-8')
 1741                                         grains['init'] = item
 1742                                         buf = b''
 1743                                         break
 1744                                 edge = buf[-edge_len:]
 1745                                 buf = fp_.read(buf_size).lower()
 1746                     except (IOError, OSError) as exc:
 1747                         log.error(
 1748                             'Unable to read from init_bin (%s): %s',
 1749                             init_bin, exc
 1750                         )
 1751                 elif salt.utils.path.which('supervisord') in init_cmdline:
 1752                     grains['init'] = 'supervisord'
 1753                 elif salt.utils.path.which('dumb-init') in init_cmdline:
 1754                     # https://github.com/Yelp/dumb-init
 1755                     grains['init'] = 'dumb-init'
 1756                 elif salt.utils.path.which('tini') in init_cmdline:
 1757                     # https://github.com/krallin/tini
 1758                     grains['init'] = 'tini'
 1759                 elif init_cmdline == ['runit']:
 1760                     grains['init'] = 'runit'
 1761                 elif '/sbin/my_init' in init_cmdline:
 1762                     # Phusion Base docker container use runit for srv mgmt, but
 1763                     # my_init as pid1
 1764                     grains['init'] = 'runit'
 1765                 else:
 1766                     log.debug(
 1767                         'Could not determine init system from command line: (%s)',
 1768                         ' '.join(init_cmdline)
 1769                     )
 1770 
 1771         # Add lsb grains on any distro with lsb-release. Note that this import
 1772         # can fail on systems with lsb-release installed if the system package
 1773         # does not install the python package for the python interpreter used by
 1774         # Salt (i.e. python2 or python3)
 1775         try:
 1776             log.trace('Getting lsb_release distro information')
 1777             import lsb_release  # pylint: disable=import-error
 1778             release = lsb_release.get_distro_information()
 1779             for key, value in six.iteritems(release):
 1780                 key = key.lower()
 1781                 lsb_param = 'lsb_{0}{1}'.format(
 1782                     '' if key.startswith('distrib_') else 'distrib_',
 1783                     key
 1784                 )
 1785                 grains[lsb_param] = value
 1786         # Catch a NameError to workaround possible breakage in lsb_release
 1787         # See https://github.com/saltstack/salt/issues/37867
 1788         except (ImportError, NameError):
 1789             # if the python library isn't available, try to parse
 1790             # /etc/lsb-release using regex
 1791             log.trace('lsb_release python bindings not available')
 1792             grains.update(_parse_lsb_release())
 1793 
 1794             if grains.get('lsb_distrib_description', '').lower().startswith('antergos'):
 1795                 # Antergos incorrectly configures their /etc/lsb-release,
 1796                 # setting the DISTRIB_ID to "Arch". This causes the "os" grain
 1797                 # to be incorrectly set to "Arch".
 1798                 grains['osfullname'] = 'Antergos Linux'
 1799             elif 'lsb_distrib_id' not in grains:
 1800                 log.trace(
 1801                     'Failed to get lsb_distrib_id, trying to parse os-release'
 1802                 )
 1803                 os_release = _parse_os_release('/etc/os-release', '/usr/lib/os-release')
 1804                 if os_release:
 1805                     if 'NAME' in os_release:
 1806                         grains['lsb_distrib_id'] = os_release['NAME'].strip()
 1807                     if 'VERSION_ID' in os_release:
 1808                         grains['lsb_distrib_release'] = os_release['VERSION_ID']
 1809                     if 'VERSION_CODENAME' in os_release:
 1810                         grains['lsb_distrib_codename'] = os_release['VERSION_CODENAME']
 1811                     elif 'PRETTY_NAME' in os_release:
 1812                         codename = os_release['PRETTY_NAME']
 1813                         # https://github.com/saltstack/salt/issues/44108
 1814                         if os_release['ID'] == 'debian':
 1815                             codename_match = re.search(r'\((\w+)\)$', codename)
 1816                             if codename_match:
 1817                                 codename = codename_match.group(1)
 1818                         grains['lsb_distrib_codename'] = codename
 1819                     if 'CPE_NAME' in os_release:
 1820                         cpe = _parse_cpe_name(os_release['CPE_NAME'])
 1821                         if not cpe:
 1822                             log.error('Broken CPE_NAME format in /etc/os-release!')
 1823                         elif cpe.get('vendor', '').lower() in ['suse', 'opensuse']:
 1824                             grains['os'] = "SUSE"
 1825                             # openSUSE `osfullname` grain normalization
 1826                             if os_release.get("NAME") == "openSUSE Leap":
 1827                                 grains['osfullname'] = "Leap"
 1828                             elif os_release.get("VERSION") == "Tumbleweed":
 1829                                 grains['osfullname'] = os_release["VERSION"]
 1830                             # Override VERSION_ID, if CPE_NAME around
 1831                             if cpe.get('version') and cpe.get('vendor') == 'opensuse':  # Keep VERSION_ID for SLES
 1832                                 grains['lsb_distrib_release'] = cpe['version']
 1833 
 1834                 elif os.path.isfile('/etc/SuSE-release'):
 1835                     log.trace('Parsing distrib info from /etc/SuSE-release')
 1836                     grains['lsb_distrib_id'] = 'SUSE'
 1837                     version = ''
 1838                     patch = ''
 1839                     with salt.utils.files.fopen('/etc/SuSE-release') as fhr:
 1840                         for line in fhr:
 1841                             if 'enterprise' in line.lower():
 1842                                 grains['lsb_distrib_id'] = 'SLES'
 1843                                 grains['lsb_distrib_codename'] = re.sub(r'\(.+\)', '', line).strip()
 1844                             elif 'version' in line.lower():
 1845                                 version = re.sub(r'[^0-9]', '', line)
 1846                             elif 'patchlevel' in line.lower():
 1847                                 patch = re.sub(r'[^0-9]', '', line)
 1848                     grains['lsb_distrib_release'] = version
 1849                     if patch:
 1850                         grains['lsb_distrib_release'] += '.' + patch
 1851                         patchstr = 'SP' + patch
 1852                         if grains['lsb_distrib_codename'] and patchstr not in grains['lsb_distrib_codename']:
 1853                             grains['lsb_distrib_codename'] += ' ' + patchstr
 1854                     if not grains.get('lsb_distrib_codename'):
 1855                         grains['lsb_distrib_codename'] = 'n.a'
 1856                 elif os.path.isfile('/etc/altlinux-release'):
 1857                     log.trace('Parsing distrib info from /etc/altlinux-release')
 1858                     # ALT Linux
 1859                     grains['lsb_distrib_id'] = 'altlinux'
 1860                     with salt.utils.files.fopen('/etc/altlinux-release') as ifile:
 1861                         # This file is symlinked to from:
 1862                         #     /etc/fedora-release
 1863                         #     /etc/redhat-release
 1864                         #     /etc/system-release
 1865                         for line in ifile:
 1866                             # ALT Linux Sisyphus (unstable)
 1867                             comps = line.split()
 1868                             if comps[0] == 'ALT':
 1869                                 grains['lsb_distrib_release'] = comps[2]
 1870                                 grains['lsb_distrib_codename'] = \
 1871                                     comps[3].replace('(', '').replace(')', '')
 1872                 elif os.path.isfile('/etc/centos-release'):
 1873                     log.trace('Parsing distrib info from /etc/centos-release')
 1874                     # CentOS Linux
 1875                     grains['lsb_distrib_id'] = 'CentOS'
 1876                     with salt.utils.files.fopen('/etc/centos-release') as ifile:
 1877                         for line in ifile:
 1878                             # Need to pull out the version and codename
 1879                             # in the case of custom content in /etc/centos-release
 1880                             find_release = re.compile(r'\d+\.\d+')
 1881                             find_codename = re.compile(r'(?<=\()(.*?)(?=\))')
 1882                             release = find_release.search(line)
 1883                             codename = find_codename.search(line)
 1884                             if release is not None:
 1885                                 grains['lsb_distrib_release'] = release.group()
 1886                             if codename is not None:
 1887                                 grains['lsb_distrib_codename'] = codename.group()
 1888                 elif os.path.isfile('/etc.defaults/VERSION') \
 1889                         and os.path.isfile('/etc.defaults/synoinfo.conf'):
 1890                     grains['osfullname'] = 'Synology'
 1891                     log.trace(
 1892                         'Parsing Synology distrib info from /etc/.defaults/VERSION'
 1893                     )
 1894                     with salt.utils.files.fopen('/etc.defaults/VERSION', 'r') as fp_:
 1895                         synoinfo = {}
 1896                         for line in fp_:
 1897                             try:
 1898                                 key, val = line.rstrip('\n').split('=')
 1899                             except ValueError:
 1900                                 continue
 1901                             if key in ('majorversion', 'minorversion',
 1902                                        'buildnumber'):
 1903                                 synoinfo[key] = val.strip('"')
 1904                         if len(synoinfo) != 3:
 1905                             log.warning(
 1906                                 'Unable to determine Synology version info. '
 1907                                 'Please report this, as it is likely a bug.'
 1908                             )
 1909                         else:
 1910                             grains['osrelease'] = (
 1911                                 '{majorversion}.{minorversion}-{buildnumber}'
 1912                                 .format(**synoinfo)
 1913                             )
 1914 
 1915         # Use the already intelligent platform module to get distro info
 1916         # (though apparently it's not intelligent enough to strip quotes)
 1917         log.trace(
 1918             'Getting OS name, release, and codename from '
 1919             'platform.linux_distribution()'
 1920         )
 1921         (osname, osrelease, oscodename) = \
 1922             [x.strip('"').strip("'") for x in
 1923              linux_distribution(supported_dists=_supported_dists)]
 1924         # Try to assign these three names based on the lsb info, they tend to
 1925         # be more accurate than what python gets from /etc/DISTRO-release.
 1926         # It's worth noting that Ubuntu has patched their Python distribution
 1927         # so that linux_distribution() does the /etc/lsb-release parsing, but
 1928         # we do it anyway here for the sake for full portability.
 1929         if 'osfullname' not in grains:
 1930             # If NI Linux RT distribution, set the grains['osfullname'] to 'nilrt'
 1931             if grains.get('lsb_distrib_id', '').lower().startswith('nilrt'):
 1932                 grains['osfullname'] = 'nilrt'
 1933             else:
 1934                 grains['osfullname'] = grains.get('lsb_distrib_id', osname).strip()
 1935         if 'osrelease' not in grains:
 1936             # NOTE: This is a workaround for CentOS 7 os-release bug
 1937             # https://bugs.centos.org/view.php?id=8359
 1938             # /etc/os-release contains no minor distro release number so we fall back to parse
 1939             # /etc/centos-release file instead.
 1940             # Commit introducing this comment should be reverted after the upstream bug is released.
 1941             if 'CentOS Linux 7' in grains.get('lsb_distrib_codename', ''):
 1942                 grains.pop('lsb_distrib_release', None)
 1943             grains['osrelease'] = grains.get('lsb_distrib_release', osrelease).strip()
 1944         grains['oscodename'] = grains.get('lsb_distrib_codename', '').strip() or oscodename
 1945         if 'Red Hat' in grains['oscodename']:
 1946             grains['oscodename'] = oscodename
 1947         distroname = _REPLACE_LINUX_RE.sub('', grains['osfullname']).strip()
 1948         # return the first ten characters with no spaces, lowercased
 1949         shortname = distroname.replace(' ', '').lower()[:10]
 1950         # this maps the long names from the /etc/DISTRO-release files to the
 1951         # traditional short names that Salt has used.
 1952         if 'os' not in grains:
 1953             grains['os'] = _OS_NAME_MAP.get(shortname, distroname)
 1954         grains.update(_linux_cpudata())
 1955         grains.update(_linux_gpu_data())
 1956     elif grains['kernel'] == 'SunOS':
 1957         if salt.utils.platform.is_smartos():
 1958             # See https://github.com/joyent/smartos-live/issues/224
 1959             if HAS_UNAME:
 1960                 uname_v = os.uname()[3]  # format: joyent_20161101T004406Z
 1961             else:
 1962                 uname_v = os.name
 1963             uname_v = uname_v[uname_v.index('_')+1:]
 1964             grains['os'] = grains['osfullname'] = 'SmartOS'
 1965             # store a parsed version of YYYY.MM.DD as osrelease
 1966             grains['osrelease'] = ".".join([
 1967                 uname_v.split('T')[0][0:4],
 1968                 uname_v.split('T')[0][4:6],
 1969                 uname_v.split('T')[0][6:8],
 1970             ])
 1971             # store a untouched copy of the timestamp in osrelease_stamp
 1972             grains['osrelease_stamp'] = uname_v
 1973         elif os.path.isfile('/etc/release'):
 1974             with salt.utils.files.fopen('/etc/release', 'r') as fp_:
 1975                 rel_data = fp_.read()
 1976                 try:
 1977                     release_re = re.compile(
 1978                         r'((?:Open|Oracle )?Solaris|OpenIndiana|OmniOS) (Development)?'
 1979                         r'\s*(\d+\.?\d*|v\d+)\s?[A-Z]*\s?(r\d+|\d+\/\d+|oi_\S+|snv_\S+)?'
 1980                     )
 1981                     osname, development, osmajorrelease, osminorrelease = release_re.search(rel_data).groups()
 1982                 except AttributeError:
 1983                     # Set a blank osrelease grain and fallback to 'Solaris'
 1984                     # as the 'os' grain.
 1985                     grains['os'] = grains['osfullname'] = 'Solaris'
 1986                     grains['osrelease'] = ''
 1987                 else:
 1988                     if development is not None:
 1989                         osname = ' '.join((osname, development))
 1990                     if HAS_UNAME:
 1991                         uname_v = os.uname()[3]
 1992                     else:
 1993                         uname_v = os.name
 1994                     grains['os'] = grains['osfullname'] = osname
 1995                     if osname in ['Oracle Solaris'] and uname_v.startswith(osmajorrelease):
 1996                         # Oracla Solars 11 and up have minor version in uname
 1997                         grains['osrelease'] = uname_v
 1998                     elif osname in ['OmniOS']:
 1999                         # OmniOS
 2000                         osrelease = []
 2001                         osrelease.append(osmajorrelease[1:])
 2002                         osrelease.append(osminorrelease[1:])
 2003                         grains['osrelease'] = ".".join(osrelease)
 2004                         grains['osrelease_stamp'] = uname_v
 2005                     else:
 2006                         # Sun Solaris 10 and earlier/comparable
 2007                         osrelease = []
 2008                         osrelease.append(osmajorrelease)
 2009                         if osminorrelease:
 2010                             osrelease.append(osminorrelease)
 2011                         grains['osrelease'] = ".".join(osrelease)
 2012                         grains['osrelease_stamp'] = uname_v
 2013 
 2014         grains.update(_sunos_cpudata())
 2015     elif grains['kernel'] == 'VMkernel':
 2016         grains['os'] = 'ESXi'
 2017     elif grains['kernel'] == 'Darwin':
 2018         osrelease = __salt__['cmd.run']('sw_vers -productVersion')
 2019         osname = __salt__['cmd.run']('sw_vers -productName')
 2020         osbuild = __salt__['cmd.run']('sw_vers -buildVersion')
 2021         grains['os'] = 'MacOS'
 2022         grains['os_family'] = 'MacOS'
 2023         grains['osfullname'] = "{0} {1}".format(osname, osrelease)
 2024         grains['osrelease'] = osrelease
 2025         grains['osbuild'] = osbuild
 2026         grains['init'] = 'launchd'
 2027         grains.update(_bsd_cpudata(grains))
 2028         grains.update(_osx_gpudata())
 2029         grains.update(_osx_platform_data())
 2030     elif grains['kernel'] == 'AIX':
 2031         osrelease = __salt__['cmd.run']('oslevel')
 2032         osrelease_techlevel = __salt__['cmd.run']('oslevel -r')
 2033         osname = __salt__['cmd.run']('uname')
 2034         grains['os'] = 'AIX'
 2035         grains['osfullname'] = osname
 2036         grains['osrelease'] = osrelease
 2037         grains['osrelease_techlevel'] = osrelease_techlevel
 2038         grains.update(_aix_cpudata())
 2039     else:
 2040         grains['os'] = grains['kernel']
 2041     if grains['kernel'] == 'FreeBSD':
 2042         try:
 2043             grains['osrelease'] = __salt__['cmd.run']('freebsd-version -u').split('-')[0]
 2044         except salt.exceptions.CommandExecutionError:
 2045             # freebsd-version was introduced in 10.0.
 2046             # derive osrelease from kernelversion prior to that
 2047             grains['osrelease'] = grains['kernelrelease'].split('-')[0]
 2048         grains.update(_bsd_cpudata(grains))
 2049     if grains['kernel'] in ('OpenBSD', 'NetBSD'):
 2050         grains.update(_bsd_cpudata(grains))
 2051         grains['osrelease'] = grains['kernelrelease'].split('-')[0]
 2052         if grains['kernel'] == 'NetBSD':
 2053             grains.update(_netbsd_gpu_data())
 2054     if not grains['os']:
 2055         grains['os'] = 'Unknown {0}'.format(grains['kernel'])
 2056         grains['os_family'] = 'Unknown'
 2057     else:
 2058         # this assigns family names based on the os name
 2059         # family defaults to the os name if not found
 2060         grains['os_family'] = _OS_FAMILY_MAP.get(grains['os'],
 2061                                                  grains['os'])
 2062 
 2063     # Build the osarch grain. This grain will be used for platform-specific
 2064     # considerations such as package management. Fall back to the CPU
 2065     # architecture.
 2066     if grains.get('os_family') == 'Debian':
 2067         osarch = __salt__['cmd.run']('dpkg --print-architecture').strip()
 2068     elif grains.get('os_family') in ['RedHat', 'Suse']:
 2069         osarch = salt.utils.pkg.rpm.get_osarch()
 2070     elif grains.get('os_family') in ('NILinuxRT', 'Poky'):
 2071         archinfo = {}
 2072         for line in __salt__['cmd.run']('opkg print-architecture').splitlines():
 2073             if line.startswith('arch'):
 2074                 _, arch, priority = line.split()
 2075                 archinfo[arch.strip()] = int(priority.strip())
 2076 
 2077         # Return osarch in priority order (higher to lower)
 2078         osarch = sorted(archinfo, key=archinfo.get, reverse=True)
 2079     else:
 2080         osarch = grains['cpuarch']
 2081     grains['osarch'] = osarch
 2082 
 2083     grains.update(_memdata(grains))
 2084 
 2085     # Get the hardware and bios data
 2086     grains.update(_hw_data(grains))
 2087 
 2088     # Load the virtual machine info
 2089     grains.update(_virtual(grains))
 2090     grains.update(_virtual_hv(grains))
 2091     grains.update(_ps(grains))
 2092 
 2093     if grains.get('osrelease', ''):
 2094         osrelease_info = grains['osrelease'].split('.')
 2095         for idx, value in enumerate(osrelease_info):
 2096             if not value.isdigit():
 2097                 continue
 2098             osrelease_info[idx] = int(value)
 2099         grains['osrelease_info'] = tuple(osrelease_info)
 2100         try:
 2101             grains['osmajorrelease'] = int(grains['osrelease_info'][0])
 2102         except (IndexError, TypeError, ValueError):
 2103             log.debug(
 2104                 'Unable to derive osmajorrelease from osrelease_info \'%s\'. '
 2105                 'The osmajorrelease grain will not be set.',
 2106                 grains['osrelease_info']
 2107             )
 2108         os_name = grains['os' if grains.get('os') in (
 2109             'Debian', 'FreeBSD', 'OpenBSD', 'NetBSD', 'Mac', 'Raspbian') else 'osfullname']
 2110         grains['osfinger'] = '{0}-{1}'.format(
 2111             os_name, grains['osrelease'] if os_name in ('Ubuntu',) else grains['osrelease_info'][0])
 2112 
 2113     return grains
 2114 
 2115 
 2116 def locale_info():
 2117     '''
 2118     Provides
 2119         defaultlanguage
 2120         defaultencoding
 2121     '''
 2122     grains = {}
 2123     grains['locale_info'] = {}
 2124 
 2125     if salt.utils.platform.is_proxy():
 2126         return grains
 2127 
 2128     try:
 2129         (
 2130             grains['locale_info']['defaultlanguage'],
 2131             grains['locale_info']['defaultencoding']
 2132         ) = locale.getdefaultlocale()
 2133     except Exception:
 2134         # locale.getdefaultlocale can ValueError!! Catch anything else it
 2135         # might do, per #2205
 2136         grains['locale_info']['defaultlanguage'] = 'unknown'
 2137         grains['locale_info']['defaultencoding'] = 'unknown'
 2138     grains['locale_info']['detectedencoding'] = __salt_system_encoding__
 2139 
 2140     grains['locale_info']['timezone'] = 'unknown'
 2141     if _DATEUTIL_TZ:
 2142         try:
 2143             grains['locale_info']['timezone'] = datetime.datetime.now(dateutil.tz.tzlocal()).tzname()
 2144         except UnicodeDecodeError:
 2145             # Because the method 'tzname' is not a part of salt the decoding error cant be fixed.
 2146             # The error is in datetime in the python2 lib
 2147             if salt.utils.platform.is_windows():
 2148                 grains['locale_info']['timezone'] = time.tzname[0].decode('mbcs')
 2149 
 2150     return grains
 2151 
 2152 
 2153 def hostname():
 2154     '''
 2155     Return fqdn, hostname, domainname
 2156 
 2157     .. note::
 2158         On Windows the ``domain`` grain may refer to the dns entry for the host
 2159         instead of the Windows domain to which the host is joined. It may also
 2160         be empty if not a part of any domain. Refer to the ``windowsdomain``
 2161         grain instead
 2162     '''
 2163     # This is going to need some work
 2164     # Provides:
 2165     #   fqdn
 2166     #   host
 2167     #   localhost
 2168     #   domain
 2169     global __FQDN__
 2170     grains = {}
 2171 
 2172     if salt.utils.platform.is_proxy():
 2173         return grains
 2174 
 2175     grains['localhost'] = socket.gethostname()
 2176     if __FQDN__ is None:
 2177         __FQDN__ = salt.utils.network.get_fqhostname()
 2178 
 2179     # On some distros (notably FreeBSD) if there is no hostname set
 2180     # salt.utils.network.get_fqhostname() will return None.
 2181     # In this case we punt and log a message at error level, but force the
 2182     # hostname and domain to be localhost.localdomain
 2183     # Otherwise we would stacktrace below
 2184     if __FQDN__ is None:   # still!
 2185         log.error('Having trouble getting a hostname.  Does this machine have its hostname and domain set properly?')
 2186         __FQDN__ = 'localhost.localdomain'
 2187 
 2188     grains['fqdn'] = __FQDN__
 2189     (grains['host'], grains['domain']) = grains['fqdn'].partition('.')[::2]
 2190     return grains
 2191 
 2192 
 2193 def append_domain():
 2194     '''
 2195     Return append_domain if set
 2196     '''
 2197 
 2198     grain = {}
 2199 
 2200     if salt.utils.platform.is_proxy():
 2201         return grain
 2202 
 2203     if 'append_domain' in __opts__:
 2204         grain['append_domain'] = __opts__['append_domain']
 2205     return grain
 2206 
 2207 
 2208 def fqdns():
 2209     '''
 2210     Return all known FQDNs for the system by enumerating all interfaces and
 2211     then trying to reverse resolve them (excluding 'lo' interface).
 2212     '''
 2213     # Provides:
 2214     # fqdns
 2215 
 2216     grains = {}
 2217     fqdns = set()
 2218 
 2219     addresses = salt.utils.network.ip_addrs(include_loopback=False,
 2220                                             interface_data=_INTERFACES)
 2221     addresses.extend(salt.utils.network.ip_addrs6(include_loopback=False,
 2222                                                   interface_data=_INTERFACES))
 2223     err_message = 'An exception occurred resolving address \'%s\': %s'
 2224     for ip in addresses:
 2225         try:
 2226             fqdns.add(socket.getfqdn(socket.gethostbyaddr(ip)[0]))
 2227         except socket.herror as err:
 2228             if err.errno == 0:
 2229                 # No FQDN for this IP address, so we don't need to know this all the time.
 2230                 log.debug("Unable to resolve address %s: %s", ip, err)
 2231             else:
 2232                 log.error(err_message, ip, err)
 2233         except (socket.error, socket.gaierror, socket.timeout) as err:
 2234             log.error(err_message, ip, err)
 2235 
 2236     grains['fqdns'] = sorted(list(fqdns))
 2237     return grains
 2238 
 2239 
 2240 def ip_fqdn():
 2241     '''
 2242     Return ip address and FQDN grains
 2243     '''
 2244     if salt.utils.platform.is_proxy():
 2245         return {}
 2246 
 2247     ret = {}
 2248     ret['ipv4'] = salt.utils.network.ip_addrs(include_loopback=True)
 2249     ret['ipv6'] = salt.utils.network.ip_addrs6(include_loopback=True)
 2250 
 2251     _fqdn = hostname()['fqdn']
 2252     for socket_type, ipv_num in ((socket.AF_INET, '4'), (socket.AF_INET6, '6')):
 2253         key = 'fqdn_ip' + ipv_num
 2254         if not ret['ipv' + ipv_num]:
 2255             ret[key] = []
 2256         else:
 2257             try:
 2258                 start_time = datetime.datetime.utcnow()
 2259                 info = socket.getaddrinfo(_fqdn, None, socket_type)
 2260                 ret[key] = list(set(item[4][0] for item in info))
 2261             except socket.error:
 2262                 timediff = datetime.datetime.utcnow() - start_time
 2263                 if timediff.seconds > 5 and __opts__['__role'] == 'master':
 2264                     log.warning(
 2265                         'Unable to find IPv%s record for "%s" causing a %s '
 2266                         'second timeout when rendering grains. Set the dns or '
 2267                         '/etc/hosts for IPv%s to clear this.',
 2268                         ipv_num, _fqdn, timediff, ipv_num
 2269                     )
 2270                 ret[key] = []
 2271 
 2272     return ret
 2273 
 2274 
 2275 def ip_interfaces():
 2276     '''
 2277     Provide a dict of the connected interfaces and their ip addresses
 2278     The addresses will be passed as a list for each interface
 2279     '''
 2280     # Provides:
 2281     #   ip_interfaces
 2282 
 2283     if salt.utils.platform.is_proxy():
 2284         return {}
 2285 
 2286     ret = {}
 2287     ifaces = _get_interfaces()
 2288     for face in ifaces:
 2289         iface_ips = []
 2290         for inet in ifaces[face].get('inet', []):
 2291             if 'address' in inet:
 2292                 iface_ips.append(inet['address'])
 2293         for inet in ifaces[face].get('inet6', []):
 2294             if 'address' in inet:
 2295                 iface_ips.append(inet['address'])
 2296         for secondary in ifaces[face].get('secondary', []):
 2297             if 'address' in secondary:
 2298                 iface_ips.append(secondary['address'])
 2299         ret[face] = iface_ips
 2300     return {'ip_interfaces': ret}
 2301 
 2302 
 2303 def ip4_interfaces():
 2304     '''
 2305     Provide a dict of the connected interfaces and their ip4 addresses
 2306     The addresses will be passed as a list for each interface
 2307     '''
 2308     # Provides:
 2309     #   ip_interfaces
 2310 
 2311     if salt.utils.platform.is_proxy():
 2312         return {}
 2313 
 2314     ret = {}
 2315     ifaces = _get_interfaces()
 2316     for face in ifaces:
 2317         iface_ips = []
 2318         for inet in ifaces[face].get('inet', []):
 2319             if 'address' in inet:
 2320                 iface_ips.append(inet['address'])
 2321         for secondary in ifaces[face].get('secondary', []):
 2322             if 'address' in secondary:
 2323                 iface_ips.append(secondary['address'])
 2324         ret[face] = iface_ips
 2325     return {'ip4_interfaces': ret}
 2326 
 2327 
 2328 def ip6_interfaces():
 2329     '''
 2330     Provide a dict of the connected interfaces and their ip6 addresses
 2331     The addresses will be passed as a list for each interface
 2332     '''
 2333     # Provides:
 2334     #   ip_interfaces
 2335 
 2336     if salt.utils.platform.is_proxy():
 2337         return {}
 2338 
 2339     ret = {}
 2340     ifaces = _get_interfaces()
 2341     for face in ifaces:
 2342         iface_ips = []
 2343         for inet in ifaces[face].get('inet6', []):
 2344             if 'address' in inet:
 2345                 iface_ips.append(inet['address'])
 2346         for secondary in ifaces[face].get('secondary', []):
 2347             if 'address' in secondary:
 2348                 iface_ips.append(secondary['address'])
 2349         ret[face] = iface_ips
 2350     return {'ip6_interfaces': ret}
 2351 
 2352 
 2353 def hwaddr_interfaces():
 2354     '''
 2355     Provide a dict of the connected interfaces and their
 2356     hw addresses (Mac Address)
 2357     '''
 2358     # Provides:
 2359     #   hwaddr_interfaces
 2360     ret = {}
 2361     ifaces = _get_interfaces()
 2362     for face in ifaces:
 2363         if 'hwaddr' in ifaces[face]:
 2364             ret[face] = ifaces[face]['hwaddr']
 2365     return {'hwaddr_interfaces': ret}
 2366 
 2367 
 2368 def dns():
 2369     '''
 2370     Parse the resolver configuration file
 2371 
 2372      .. versionadded:: 2016.3.0
 2373     '''
 2374     # Provides:
 2375     #   dns
 2376     if salt.utils.platform.is_windows() or 'proxyminion' in __opts__:
 2377         return {}
 2378 
 2379     resolv = salt.utils.dns.parse_resolv()
 2380     for key in ('nameservers', 'ip4_nameservers', 'ip6_nameservers',
 2381                 'sortlist'):
 2382         if key in resolv:
 2383             resolv[key] = [six.text_type(i) for i in resolv[key]]
 2384 
 2385     return {'dns': resolv} if resolv else {}
 2386 
 2387 
 2388 def get_machine_id():
 2389     '''
 2390     Provide the machine-id for machine/virtualization combination
 2391     '''
 2392     # Provides:
 2393     #   machine-id
 2394     if platform.system() == 'AIX':
 2395         return _aix_get_machine_id()
 2396 
 2397     locations = ['/etc/machine-id', '/var/lib/dbus/machine-id']
 2398     existing_locations = [loc for loc in locations if os.path.exists(loc)]
 2399     if not existing_locations:
 2400         return {}
 2401     else:
 2402         with salt.utils.files.fopen(existing_locations[0]) as machineid:
 2403             return {'machine_id': machineid.read().strip()}
 2404 
 2405 
 2406 def path():
 2407     '''
 2408     Return the path
 2409     '''
 2410     # Provides:
 2411     #   path
 2412     return {'path': os.environ.get('PATH', '').strip()}
 2413 
 2414 
 2415 def pythonversion():
 2416     '''
 2417     Return the Python version
 2418     '''
 2419     # Provides:
 2420     #   pythonversion
 2421     return {'pythonversion': list(sys.version_info)}
 2422 
 2423 
 2424 def pythonpath():
 2425     '''
 2426     Return the Python path
 2427     '''
 2428     # Provides:
 2429     #   pythonpath
 2430     return {'pythonpath': sys.path}
 2431 
 2432 
 2433 def pythonexecutable():
 2434     '''
 2435     Return the python executable in use
 2436     '''
 2437     # Provides:
 2438     #   pythonexecutable
 2439     return {'pythonexecutable': sys.executable}
 2440 
 2441 
 2442 def saltpath():
 2443     '''
 2444     Return the path of the salt module
 2445     '''
 2446     # Provides:
 2447     #   saltpath
 2448     salt_path = os.path.abspath(os.path.join(__file__, os.path.pardir))
 2449     return {'saltpath': os.path.dirname(salt_path)}
 2450 
 2451 
 2452 def saltversion():
 2453     '''
 2454     Return the version of salt
 2455     '''
 2456     # Provides:
 2457     #   saltversion
 2458     from salt.version import __version__
 2459     return {'saltversion': __version__}
 2460 
 2461 
 2462 def zmqversion():
 2463     '''
 2464     Return the zeromq version
 2465     '''
 2466     # Provides:
 2467     #   zmqversion
 2468     try:
 2469         import zmq
 2470         return {'zmqversion': zmq.zmq_version()}  # pylint: disable=no-member
 2471     except ImportError:
 2472         return {}
 2473 
 2474 
 2475 def saltversioninfo():
 2476     '''
 2477     Return the version_info of salt
 2478 
 2479      .. versionadded:: 0.17.0
 2480     '''
 2481     # Provides:
 2482     #   saltversioninfo
 2483     from salt.version import __version_info__
 2484     return {'saltversioninfo': list(__version_info__)}
 2485 
 2486 
 2487 def _hw_data(osdata):
 2488     '''
 2489     Get system specific hardware data from dmidecode
 2490 
 2491     Provides
 2492         biosversion
 2493         productname
 2494         manufacturer
 2495         serialnumber
 2496         biosreleasedate
 2497         uuid
 2498 
 2499     .. versionadded:: 0.9.5
 2500     '''
 2501 
 2502     if salt.utils.platform.is_proxy():
 2503         return {}
 2504 
 2505     grains = {}
 2506     if osdata['kernel'] == 'Linux' and os.path.exists('/sys/class/dmi/id'):
 2507         # On many Linux distributions basic firmware information is available via sysfs
 2508         # requires CONFIG_DMIID to be enabled in the Linux kernel configuration
 2509         sysfs_firmware_info = {
 2510             'biosversion': 'bios_version',
 2511             'productname': 'product_name',
 2512             'manufacturer': 'sys_vendor',
 2513             'biosreleasedate': 'bios_date',
 2514             'uuid': 'product_uuid',
 2515             'serialnumber': 'product_serial'
 2516         }
 2517         for key, fw_file in sysfs_firmware_info.items():
 2518             contents_file = os.path.join('/sys/class/dmi/id', fw_file)
 2519             if os.path.exists(contents_file):
 2520                 try:
 2521                     with salt.utils.files.fopen(contents_file, 'r') as ifile:
 2522                         grains[key] = salt.utils.stringutils.to_unicode(ifile.read().strip(), errors='replace')
 2523                         if key == 'uuid':
 2524                             grains['uuid'] = grains['uuid'].lower()
 2525                 except (IOError, OSError) as err:
 2526                     # PermissionError is new to Python 3, but corresponds to the EACESS and
 2527                     # EPERM error numbers. Use those instead here for PY2 compatibility.
 2528                     if err.errno == EACCES or err.errno == EPERM:
 2529                         # Skip the grain if non-root user has no access to the file.
 2530                         pass
 2531     elif salt.utils.path.which_bin(['dmidecode', 'smbios']) is not None and not (
 2532             salt.utils.platform.is_smartos() or
 2533             (  # SunOS on SPARC - 'smbios: failed to load SMBIOS: System does not export an SMBIOS table'
 2534                 osdata['kernel'] == 'SunOS' and
 2535                 osdata['cpuarch'].startswith('sparc')
 2536             )):
 2537         # On SmartOS (possibly SunOS also) smbios only works in the global zone
 2538         # smbios is also not compatible with linux's smbios (smbios -s = print summarized)
 2539         grains = {
 2540             'biosversion': __salt__['smbios.get']('bios-version'),
 2541             'productname': __salt__['smbios.get']('system-product-name'),
 2542             'manufacturer': __salt__['smbios.get']('system-manufacturer'),
 2543             'biosreleasedate': __salt__['smbios.get']('bios-release-date'),
 2544             'uuid': __salt__['smbios.get']('system-uuid')
 2545         }
 2546         grains = dict([(key, val) for key, val in grains.items() if val is not None])
 2547         uuid = __salt__['smbios.get']('system-uuid')
 2548         if uuid is not None:
 2549             grains['uuid'] = uuid.lower()
 2550         for serial in ('system-serial-number', 'chassis-serial-number', 'baseboard-serial-number'):
 2551             serial = __salt__['smbios.get'](serial)
 2552             if serial is not None:
 2553                 grains['serialnumber'] = serial
 2554                 break
 2555     elif salt.utils.path.which_bin(['fw_printenv']) is not None:
 2556         # ARM Linux devices expose UBOOT env variables via fw_printenv
 2557         hwdata = {
 2558             'manufacturer': 'manufacturer',
 2559             'serialnumber': 'serial#',
 2560             'productname': 'DeviceDesc',
 2561         }
 2562         for grain_name, cmd_key in six.iteritems(hwdata):
 2563             result = __salt__['cmd.run_all']('fw_printenv {0}'.format(cmd_key))
 2564             if result['retcode'] == 0:
 2565                 uboot_keyval = result['stdout'].split('=')
 2566                 grains[grain_name] = _clean_value(grain_name, uboot_keyval[1])
 2567     elif osdata['kernel'] == 'FreeBSD':
 2568         # On FreeBSD /bin/kenv (already in base system)
 2569         # can be used instead of dmidecode
 2570         kenv = salt.utils.path.which('kenv')
 2571         if kenv:
 2572             # In theory, it will be easier to add new fields to this later
 2573             fbsd_hwdata = {
 2574                 'biosversion': 'smbios.bios.version',
 2575                 'manufacturer': 'smbios.system.maker',
 2576                 'serialnumber': 'smbios.system.serial',
 2577                 'productname': 'smbios.system.product',
 2578                 'biosreleasedate': 'smbios.bios.reldate',
 2579                 'uuid': 'smbios.system.uuid',
 2580             }
 2581             for key, val in six.iteritems(fbsd_hwdata):
 2582                 value = __salt__['cmd.run']('{0} {1}'.format(kenv, val))
 2583                 grains[key] = _clean_value(key, value)
 2584     elif osdata['kernel'] == 'OpenBSD':
 2585         sysctl = salt.utils.path.which('sysctl')
 2586         hwdata = {'biosversion': 'hw.version',
 2587                   'manufacturer': 'hw.vendor',
 2588                   'productname': 'hw.product',
 2589                   'serialnumber': 'hw.serialno',
 2590                   'uuid': 'hw.uuid'}
 2591         for key, oid in six.iteritems(hwdata):
 2592             value = __salt__['cmd.run']('{0} -n {1}'.format(sysctl, oid))
 2593             if not value.endswith(' value is not available'):
 2594                 grains[key] = _clean_value(key, value)
 2595     elif osdata['kernel'] == 'NetBSD':
 2596         sysctl = salt.utils.path.which('sysctl')
 2597         nbsd_hwdata = {
 2598             'biosversion': 'machdep.dmi.board-version',
 2599             'manufacturer': 'machdep.dmi.system-vendor',
 2600             'serialnumber': 'machdep.dmi.system-serial',
 2601             'productname': 'machdep.dmi.system-product',
 2602             'biosreleasedate': 'machdep.dmi.bios-date',
 2603             'uuid': 'machdep.dmi.system-uuid',
 2604         }
 2605         for key, oid in six.iteritems(nbsd_hwdata):
 2606             result = __salt__['cmd.run_all']('{0} -n {1}'.format(sysctl, oid))
 2607             if result['retcode'] == 0:
 2608                 grains[key] = _clean_value(key, result['stdout'])
 2609     elif osdata['kernel'] == 'Darwin':
 2610         grains['manufacturer'] = 'Apple Inc.'
 2611         sysctl = salt.utils.path.which('sysctl')
 2612         hwdata = {'productname': 'hw.model'}
 2613         for key, oid in hwdata.items():
 2614             value = __salt__['cmd.run']('{0} -b {1}'.format(sysctl, oid))
 2615             if not value.endswith(' is invalid'):
 2616                 grains[key] = _clean_value(key, value)
 2617     elif osdata['kernel'] == 'SunOS' and osdata['cpuarch'].startswith('sparc'):
 2618         # Depending on the hardware model, commands can report different bits
 2619         # of information.  With that said, consolidate the output from various
 2620         # commands and attempt various lookups.
 2621         data = ""
 2622         for (cmd, args) in (('/usr/sbin/prtdiag', '-v'), ('/usr/sbin/prtconf', '-vp'), ('/usr/sbin/virtinfo', '-a')):
 2623             if salt.utils.path.which(cmd):  # Also verifies that cmd is executable
 2624                 data += __salt__['cmd.run']('{0} {1}'.format(cmd, args))
 2625                 data += '\n'
 2626 
 2627         sn_regexes = [
 2628             re.compile(r) for r in [
 2629                 r'(?im)^\s*Chassis\s+Serial\s+Number\n-+\n(\S+)',  # prtdiag
 2630                 r'(?im)^\s*chassis-sn:\s*(\S+)',  # prtconf
 2631                 r'(?im)^\s*Chassis\s+Serial#:\s*(\S+)',  # virtinfo
 2632             ]
 2633         ]
 2634 
 2635         obp_regexes = [
 2636             re.compile(r) for r in [
 2637                 r'(?im)^\s*System\s+PROM\s+revisions.*\nVersion\n-+\nOBP\s+(\S+)\s+(\S+)',  # prtdiag
 2638                 r'(?im)^\s*version:\s*\'OBP\s+(\S+)\s+(\S+)',  # prtconf
 2639             ]
 2640         ]
 2641 
 2642         fw_regexes = [
 2643             re.compile(r) for r in [
 2644                 r'(?im)^\s*Sun\s+System\s+Firmware\s+(\S+)\s+(\S+)',  # prtdiag
 2645             ]
 2646         ]
 2647 
 2648         uuid_regexes = [
 2649             re.compile(r) for r in [
 2650                 r'(?im)^\s*Domain\s+UUID:\s*(\S+)',  # virtinfo
 2651             ]
 2652         ]
 2653 
 2654         manufacture_regexes = [
 2655             re.compile(r) for r in [
 2656                 r'(?im)^\s*System\s+Configuration:\s*(.*)(?=sun)',  # prtdiag
 2657             ]
 2658         ]
 2659 
 2660         product_regexes = [
 2661             re.compile(r) for r in [
 2662                 r'(?im)^\s*System\s+Configuration:\s*.*?sun\d\S+[^\S\r\n]*(.*)',  # prtdiag
 2663                 r'(?im)^[^\S\r\n]*banner-name:[^\S\r\n]*(.*)',  # prtconf
 2664                 r'(?im)^[^\S\r\n]*product-name:[^\S\r\n]*(.*)',  # prtconf
 2665             ]
 2666         ]
 2667 
 2668         sn_regexes = [
 2669             re.compile(r) for r in [
 2670                 r'(?im)Chassis\s+Serial\s+Number\n-+\n(\S+)',  # prtdiag
 2671                 r'(?i)Chassis\s+Serial#:\s*(\S+)',  # virtinfo
 2672                 r'(?i)chassis-sn:\s*(\S+)',  # prtconf
 2673             ]
 2674         ]
 2675 
 2676         obp_regexes = [
 2677             re.compile(r) for r in [
 2678                 r'(?im)System\s+PROM\s+revisions.*\nVersion\n-+\nOBP\s+(\S+)\s+(\S+)',  # prtdiag
 2679                 r'(?im)version:\s*\'OBP\s+(\S+)\s+(\S+)',  # prtconf
 2680             ]
 2681         ]
 2682 
 2683         fw_regexes = [
 2684             re.compile(r) for r in [
 2685                 r'(?i)Sun\s+System\s+Firmware\s+(\S+)\s+(\S+)',  # prtdiag
 2686             ]
 2687         ]
 2688 
 2689         uuid_regexes = [
 2690             re.compile(r) for r in [
 2691                 r'(?i)Domain\s+UUID:\s+(\S+)',  # virtinfo
 2692             ]
 2693         ]
 2694 
 2695         for regex in sn_regexes:
 2696             res = regex.search(data)
 2697             if res and len(res.groups()) >= 1:
 2698                 grains['serialnumber'] = res.group(1).strip().replace("'", "")
 2699                 break
 2700 
 2701         for regex in obp_regexes:
 2702             res = regex.search(data)
 2703             if res and len(res.groups()) >= 1:
 2704                 obp_rev, obp_date = res.groups()[0:2]  # Limit the number in case we found the data in multiple places
 2705                 grains['biosversion'] = obp_rev.strip().replace("'", "")
 2706                 grains['biosreleasedate'] = obp_date.strip().replace("'", "")
 2707 
 2708         for regex in fw_regexes:
 2709             res = regex.search(data)
 2710             if res and len(res.groups()) >= 1:
 2711                 fw_rev, fw_date = res.groups()[0:2]
 2712                 grains['systemfirmware'] = fw_rev.strip().replace("'", "")
 2713                 grains['systemfirmwaredate'] = fw_date.strip().replace("'", "")
 2714                 break
 2715 
 2716         for regex in uuid_regexes:
 2717             res = regex.search(data)
 2718             if res and len(res.groups()) >= 1:
 2719                 grains['uuid'] = res.group(1).strip().replace("'", "")
 2720                 break
 2721 
 2722         for regex in manufacture_regexes:
 2723             res = regex.search(data)
 2724             if res and len(res.groups()) >= 1:
 2725                 grains['manufacture'] = res.group(1).strip().replace("'", "")
 2726                 break
 2727 
 2728         for regex in product_regexes:
 2729             res = regex.search(data)
 2730             if res and len(res.groups()) >= 1:
 2731                 t_productname = res.group(1).strip().replace("'", "")
 2732                 if t_productname:
 2733                     grains['product'] = t_productname
 2734                     grains['productname'] = t_productname
 2735                     break
 2736     elif osdata['kernel'] == 'AIX':
 2737         cmd = salt.utils.path.which('prtconf')
 2738         if cmd:
 2739             data = __salt__['cmd.run']('{0}'.format(cmd)) + os.linesep
 2740             for dest, regstring in (('serialnumber', r'(?im)^\s*Machine\s+Serial\s+Number:\s+(\S+)'),
 2741                                     ('systemfirmware', r'(?im)^\s*Firmware\s+Version:\s+(.*)')):
 2742                 for regex in [re.compile(r) for r in [regstring]]:
 2743                     res = regex.search(data)
 2744                     if res and len(res.groups()) >= 1:
 2745                         grains[dest] = res.group(1).strip().replace("'", '')
 2746 
 2747             product_regexes = [re.compile(r'(?im)^\s*System\s+Model:\s+(\S+)')]
 2748             for regex in product_regexes:
 2749                 res = regex.search(data)
 2750                 if res and len(res.groups()) >= 1:
 2751                     grains['manufacturer'], grains['productname'] = res.group(1).strip().replace("'", "").split(",")
 2752                     break
 2753         else:
 2754             log.error('The \'prtconf\' binary was not found in $PATH.')
 2755 
 2756     elif osdata['kernel'] == 'AIX':
 2757         cmd = salt.utils.path.which('prtconf')
 2758         if data:
 2759             data = __salt__['cmd.run']('{0}'.format(cmd)) + os.linesep
 2760             for dest, regstring in (('serialnumber', r'(?im)^\s*Machine\s+Serial\s+Number:\s+(\S+)'),
 2761                                     ('systemfirmware', r'(?im)^\s*Firmware\s+Version:\s+(.*)')):
 2762                 for regex in [re.compile(r) for r in [regstring]]:
 2763                     res = regex.search(data)
 2764                     if res and len(res.groups()) >= 1:
 2765                         grains[dest] = res.group(1).strip().replace("'", '')
 2766 
 2767             product_regexes = [re.compile(r'(?im)^\s*System\s+Model:\s+(\S+)')]
 2768             for regex in product_regexes:
 2769                 res = regex.search(data)
 2770                 if res and len(res.groups()) >= 1:
 2771                     grains['manufacturer'], grains['productname'] = res.group(1).strip().replace("'", "").split(",")
 2772                     break
 2773         else:
 2774             log.error('The \'prtconf\' binary was not found in $PATH.')
 2775 
 2776     return grains
 2777 
 2778 
 2779 def get_server_id():
 2780     '''
 2781     Provides an integer based on the FQDN of a machine.
 2782     Useful as server-id in MySQL replication or anywhere else you'll need an ID
 2783     like this.
 2784     '''
 2785     # Provides:
 2786     #   server_id
 2787 
 2788     if salt.utils.platform.is_proxy():
 2789         return {}
 2790     id_ = __opts__.get('id', '')
 2791     id_hash = None
 2792     py_ver = sys.version_info[:2]
 2793     if py_ver >= (3, 3):
 2794         # Python 3.3 enabled hash randomization, so we need to shell out to get
 2795         # a reliable hash.
 2796         id_hash = __salt__['cmd.run'](
 2797             [sys.executable, '-c', 'print(hash("{0}"))'.format(id_)],
 2798             env={'PYTHONHASHSEED': '0'}
 2799         )
 2800         try:
 2801             id_hash = int(id_hash)
 2802         except (TypeError, ValueError):
 2803             log.debug(
 2804                 'Failed to hash the ID to get the server_id grain. Result of '
 2805                 'hash command: %s', id_hash
 2806             )
 2807             id_hash = None
 2808     if id_hash is None:
 2809         # Python < 3.3 or error encountered above
 2810         id_hash = hash(id_)
 2811 
 2812     return {'server_id': abs(id_hash % (2 ** 31))}
 2813 
 2814 
 2815 def get_master():
 2816     '''
 2817     Provides the minion with the name of its master.
 2818     This is useful in states to target other services running on the master.
 2819     '''
 2820     # Provides:
 2821     #   master
 2822     return {'master': __opts__.get('master', '')}
 2823 
 2824 
 2825 def default_gateway():
 2826     '''
 2827     Populates grains which describe whether a server has a default gateway
 2828     configured or not. Uses `ip -4 route show` and `ip -6 route show` and greps
 2829     for a `default` at the beginning of any line. Assuming the standard
 2830     `default via <ip>` format for default gateways, it will also parse out the
 2831     ip address of the default gateway, and put it in ip4_gw or ip6_gw.
 2832 
 2833     If the `ip` command is unavailable, no grains will be populated.
 2834 
 2835     Currently does not support multiple default gateways. The grains will be
 2836     set to the first default gateway found.
 2837 
 2838     List of grains:
 2839 
 2840         ip4_gw: True  # ip/True/False if default ipv4 gateway
 2841         ip6_gw: True  # ip/True/False if default ipv6 gateway
 2842         ip_gw: True   # True if either of the above is True, False otherwise
 2843     '''
 2844     grains = {}
 2845     ip_bin = salt.utils.path.which('ip')
 2846     if not ip_bin:
 2847         return {}
 2848     grains['ip_gw'] = False
 2849     grains['ip4_gw'] = False
 2850     grains['ip6_gw'] = False
 2851     for ip_version in ('4', '6'):
 2852         try:
 2853             out = __salt__['cmd.run']([ip_bin, '-' + ip_version, 'route', 'show'])
 2854             for line in out.splitlines():
 2855                 if line.startswith('default'):
 2856                     grains['ip_gw'] = True
 2857                     grains['ip{0}_gw'.format(ip_version)] = True
 2858                     try:
 2859                         via, gw_ip = line.split()[1:3]
 2860                     except ValueError:
 2861                         pass
 2862                     else:
 2863                         if via == 'via':
 2864                             grains['ip{0}_gw'.format(ip_version)] = gw_ip
 2865                     break
 2866         except Exception:
 2867             continue
 2868     return grains