HEX
Server: Apache
System: Linux sg241.singhost.net 2.6.32-896.16.1.lve1.4.51.el6.x86_64 #1 SMP Wed Jan 17 13:19:23 EST 2018 x86_64
User: honghock (909)
PHP: 8.0.30
Disabled: passthru,system,shell_exec,show_source,exec,popen,proc_open
Upload Files
File: //proc/self/root/usr/lib/python2.7/site-packages/salt/modules/mac_service.py
# -*- coding: utf-8 -*-
'''
The service module for macOS

.. versionadded:: 2016.3.0

This module has support for services in the following locations.

.. code-block:: bash

    /System/Library/LaunchDaemons/
    /System/Library/LaunchAgents/
    /Library/LaunchDaemons/
    /Library/LaunchAgents/

    # As of version "2019.2.0" support for user-specific services were added.
    /Users/foo/Library/LaunchAgents/

.. note::
    As of the 2019.2.0 release, if a service is located in a ``LaunchAgent``
    path and a ``runas`` user is NOT specified, the current console user will
    be used to properly interact with the service.

'''
from __future__ import absolute_import, unicode_literals, print_function

# Import python libs
import logging
import os
import re

# Import salt libs
import salt.utils.files
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils
from salt.exceptions import CommandExecutionError
from salt.utils.versions import LooseVersion as _LooseVersion

# Import 3rd party libs
from salt.ext import six

# Define the module's virtual name
__virtualname__ = 'service'

__func_alias__ = {
    'list_': 'list',
}

log = logging.getLogger(__name__)


def __virtual__():
    '''
    Only for macOS with launchctl
    '''
    if not salt.utils.platform.is_darwin():
        return (False, 'Failed to load the mac_service module:\n'
                       'Only available on macOS systems.')

    if not salt.utils.path.which('launchctl'):
        return (False, 'Failed to load the mac_service module:\n'
                       'Required binary not found: "launchctl"')

    if not salt.utils.path.which('plutil'):
        return (False, 'Failed to load the mac_service module:\n'
                       'Required binary not found: "plutil"')

    if _LooseVersion(__grains__['osrelease']) < _LooseVersion('10.11'):
        return (False, 'Failed to load the mac_service module:\n'
                       'Requires macOS 10.11 or newer')

    return __virtualname__


def _name_in_services(name, services):
    '''
    Checks to see if the given service is in the given services.

    :param str name: Service label, file name, or full path

    :param dict services: The currently available services.

    :return: The service information for the service, otherwise
    an empty dictionary

    :rtype: dict
    '''
    if name in services:
        # Match on label
        return services[name]

    for service in six.itervalues(services):
        if service['file_path'].lower() == name:
            # Match on full path
            return service
        basename, ext = os.path.splitext(service['file_name'])
        if basename.lower() == name:
            # Match on basename
            return service

    return dict()


def _get_service(name):
    '''
    Get information about a service.  If the service is not found, raise an
    error

    :param str name: Service label, file name, or full path

    :return: The service information for the service, otherwise an Error
    :rtype: dict
    '''
    services = __utils__['mac_utils.available_services']()
    name = name.lower()

    service = _name_in_services(name, services)

    # if we would the service we can return it
    if service:
        return service

    # if we got here our service is not available, now we can check to see if
    # we received a cached batch of services, if not we did a fresh check
    # so we need to raise that the service could not be found.
    try:
        if not __context__['using_cached_services']:
            raise CommandExecutionError('Service not found: {0}'.format(name))
    except KeyError:
        pass

    # we used a cached version to check, a service could have been made
    # between now and then, we should refresh our available services.
    services = __utils__['mac_utils.available_services'](refresh=True)

    # check to see if we found the service we are looking for.
    service = _name_in_services(name, services)

    if not service:
        # Could not find the service after refresh raise.
        raise CommandExecutionError('Service not found: {0}'.format(name))

    # found it :)
    return service


def _always_running_service(name):
    '''
    Check if the service should always be running based on the KeepAlive Key
    in the service plist.

    :param str name: Service label, file name, or full path

    :return: True if the KeepAlive key is set to True, False if set to False or
        not set in the plist at all.

    :rtype: bool

    .. versionadded:: 2019.2.0
    '''

    # get all the info from the launchctl service
    service_info = show(name)

    # get the value for the KeepAlive key in service plist
    try:
        keep_alive = service_info['plist']['KeepAlive']
    except KeyError:
        return False

    # check if KeepAlive is True and not just set.

    if isinstance(keep_alive, dict):
        # check for pathstate
        for _file, value in six.iteritems(keep_alive.get('PathState', {})):
            if value is True and os.path.exists(_file):
                return True
            elif value is False and not os.path.exists(_file):
                return True

    if keep_alive is True:
        return True

    return False


def _get_domain_target(name, service_target=False):
    '''
    Returns the domain/service target and path for a service. This is used to
    determine whether or not a service should be loaded in a user space or
    system space.

    :param str name: Service label, file name, or full path

    :param bool service_target: Whether to return a full
    service target. This is needed for the enable and disable
    subcommands of /bin/launchctl. Defaults to False

    :return: Tuple of the domain/service target and the path to the service.

    :rtype: tuple

    .. versionadded:: 2019.2.0
    '''

    # Get service information
    service = _get_service(name)

    # get the path to the service
    path = service['file_path']

    # most of the time we'll be at the system level.
    domain_target = 'system'

    # check if a LaunchAgent as we should treat these differently.
    if 'LaunchAgents' in path:
        # Get the console user so we can service in the correct session
        uid = __utils__['mac_utils.console_user']()
        domain_target = 'gui/{}'.format(uid)

    # check to see if we need to make it a full service target.
    if service_target is True:
        domain_target = '{}/{}'.format(domain_target, service['plist']['Label'])

    return (domain_target, path)


def _launch_agent(name):
    '''
    Checks to see if the provided service is a LaunchAgent

    :param str name: Service label, file name, or full path

    :return: True if a LaunchAgent, False if not.

    :rtype: bool

    .. versionadded:: 2019.2.0
    '''

    # Get the path to the service.
    path = _get_service(name)['file_path']

    if 'LaunchAgents' not in path:
        return False
    return True


def show(name):
    '''
    Show properties of a launchctl service

    :param str name: Service label, file name, or full path

    :return: The service information if the service is found
    :rtype: dict

    CLI Example:

    .. code-block:: bash

        salt '*' service.show org.cups.cupsd  # service label
        salt '*' service.show org.cups.cupsd.plist  # file name
        salt '*' service.show /System/Library/LaunchDaemons/org.cups.cupsd.plist  # full path
    '''
    return _get_service(name)


def launchctl(sub_cmd, *args, **kwargs):
    '''
    Run a launchctl command and raise an error if it fails

    :param str sub_cmd: Sub command supplied to launchctl

    :param tuple args: Tuple containing additional arguments to pass to
        launchctl

    :param dict kwargs: Dictionary containing arguments to pass to
        ``cmd.run_all``

    :param bool return_stdout: A keyword argument.  If true return the stdout
        of the launchctl command

    :return: ``True`` if successful, raise ``CommandExecutionError`` if not, or
        the stdout of the launchctl command if requested
    :rtype: bool, str

    CLI Example:

    .. code-block:: bash

        salt '*' service.launchctl debug org.cups.cupsd
    '''
    return __utils__['mac_utils.launchctl'](sub_cmd, *args, **kwargs)


def list_(name=None, runas=None):
    '''
    Run launchctl list and return the output

    :param str name: The name of the service to list

    :param str runas: User to run launchctl commands

    :return: If a name is passed returns information about the named service,
        otherwise returns a list of all services and pids
    :rtype: str

    CLI Example:

    .. code-block:: bash

        salt '*' service.list
        salt '*' service.list org.cups.cupsd
    '''
    if name:
        # Get service information and label
        service = _get_service(name)
        label = service['plist']['Label']

        # we can assume if we are trying to list a LaunchAgent we need
        # to run as a user, if not provided, we'll use the console user.
        if not runas and _launch_agent(name):
            runas = __utils__['mac_utils.console_user'](username=True)

        # Collect information on service: will raise an error if it fails
        return launchctl('list',
                         label,
                         return_stdout=True,
                         runas=runas)

    # Collect information on all services: will raise an error if it fails
    return launchctl('list',
                     return_stdout=True,
                     runas=runas)


def enable(name, runas=None):
    '''
    Enable a launchd service. Raises an error if the service fails to be enabled

    :param str name: Service label, file name, or full path

    :param str runas: User to run launchctl commands

    :return: ``True`` if successful or if the service is already enabled
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.enable org.cups.cupsd
    '''
    # Get the domain target. enable requires a full <service-target>
    service_target = _get_domain_target(name, service_target=True)[0]

    # Enable the service: will raise an error if it fails
    return launchctl('enable', service_target, runas=runas)


def disable(name, runas=None):
    '''
    Disable a launchd service. Raises an error if the service fails to be
    disabled

    :param str name: Service label, file name, or full path

    :param str runas: User to run launchctl commands

    :return: ``True`` if successful or if the service is already disabled
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.disable org.cups.cupsd
    '''
    # Get the service target. enable requires a full <service-target>
    service_target = _get_domain_target(name, service_target=True)[0]

    # disable the service: will raise an error if it fails
    return launchctl('disable', service_target, runas=runas)


def start(name, runas=None):
    '''
    Start a launchd service.  Raises an error if the service fails to start

    .. note::
        To start a service in macOS the service must be enabled first. Use
        ``service.enable`` to enable the service.

    :param str name: Service label, file name, or full path

    :param str runas: User to run launchctl commands

    :return: ``True`` if successful or if the service is already running
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.start org.cups.cupsd
    '''
    # Get the domain target.
    domain_target, path = _get_domain_target(name)

    # Load (bootstrap) the service: will raise an error if it fails
    return launchctl('bootstrap', domain_target, path, runas=runas)


def stop(name, runas=None):
    '''
    Stop a launchd service.  Raises an error if the service fails to stop

    .. note::
        Though ``service.stop`` will unload a service in macOS, the service
        will start on next boot unless it is disabled. Use ``service.disable``
        to disable the service

    :param str name: Service label, file name, or full path

    :param str runas: User to run launchctl commands

    :return: ``True`` if successful or if the service is already stopped
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.stop org.cups.cupsd
    '''
    # Get the domain target.
    domain_target, path = _get_domain_target(name)

    # Stop (bootout) the service: will raise an error if it fails
    return launchctl('bootout', domain_target, path, runas=runas)


def restart(name, runas=None):
    '''
    Unloads and reloads a launchd service.  Raises an error if the service
    fails to reload

    :param str name: Service label, file name, or full path

    :param str runas: User to run launchctl commands

    :return: ``True`` if successful
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.restart org.cups.cupsd
    '''
    # Restart the service: will raise an error if it fails
    if enabled(name):
        stop(name, runas=runas)
    start(name, runas=runas)

    return True


def status(name, sig=None, runas=None):
    '''
    Return the status for a service.

    :param str name: Used to find the service from launchctl.  Can be any part
        of the service name or a regex expression.

    :param str sig: Find the service with status.pid instead.  Note that
        ``name`` must still be provided.

    :param str runas: User to run launchctl commands

    :return: The PID for the service if it is running, or 'loaded' if the
        service should not always have a PID, or otherwise an empty string

    :rtype: str

    CLI Example:

    .. code-block:: bash

        salt '*' service.status cups
    '''
    # Find service with ps
    if sig:
        return __salt__['status.pid'](sig)

    try:
        _get_service(name)
    except CommandExecutionError as msg:
        log.error(msg)
        return ''

    if not runas and _launch_agent(name):
        runas = __utils__['mac_utils.console_user'](username=True)

    output = list_(runas=runas)

    # Used a string here instead of a list because that's what the linux version
    # of this module does
    pids = ''
    for line in output.splitlines():
        if 'PID' in line:
            continue
        if re.search(name, line.split()[-1]):
            if line.split()[0].isdigit():
                if pids:
                    pids += '\n'
                pids += line.split()[0]

    # mac services are a little different than other platforms as they may be
    # set to run on intervals and may not always active with a PID. This will
    # return a string 'loaded' if it shouldn't always be running and is enabled.
    if not _always_running_service(name) and enabled(name) and not pids:
        return 'loaded'

    return pids


def available(name):
    '''
    Check that the given service is available.

    :param str name: The name of the service

    :return: True if the service is available, otherwise False
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.available com.openssh.sshd
    '''
    try:
        _get_service(name)
        return True
    except CommandExecutionError:
        return False


def missing(name):
    '''
    The inverse of service.available
    Check that the given service is not available.

    :param str name: The name of the service

    :return: True if the service is not available, otherwise False
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.missing com.openssh.sshd
    '''
    return not available(name)


def enabled(name, runas=None):
    '''
    Check if the specified service is enabled

    :param str name: The name of the service to look up

    :param str runas: User to run launchctl commands

    :return: True if the specified service enabled, otherwise False
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.enabled org.cups.cupsd
    '''
    # Try to list the service.  If it can't be listed, it's not enabled
    try:
        list_(name=name, runas=runas)
        return True
    except CommandExecutionError:
        return False


def disabled(name, runas=None, domain='system'):
    '''
    Check if the specified service is not enabled. This is the opposite of
    ``service.enabled``

    :param str name: The name to look up

    :param str runas: User to run launchctl commands

    :param str domain: domain to check for disabled services. Default is system.

    :return: True if the specified service is NOT enabled, otherwise False
    :rtype: bool

    CLI Example:

    .. code-block:: bash

        salt '*' service.disabled org.cups.cupsd
    '''

    disabled = launchctl('print-disabled',
                         domain,
                         return_stdout=True,
                         runas=runas)
    for service in disabled.split("\n"):
        if name in service:
            srv_name = service.split("=>")[0].split("\"")[1]
            status = service.split("=>")[1]
            if name != srv_name:
                pass
            else:
                return True if 'true' in status.lower() else False

    return False


def get_all(runas=None):
    '''
    Return a list of services that are enabled or available. Can be used to
    find the name of a service.

    :param str runas: User to run launchctl commands

    :return: A list of all the services available or enabled
    :rtype: list

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_all
    '''
    # Get list of enabled services
    enabled = get_enabled(runas=runas)

    # Get list of all services
    available = list(__utils__['mac_utils.available_services']().keys())

    # Return composite list
    return sorted(set(enabled + available))


def get_enabled(runas=None):
    '''
    Return a list of all services that are enabled. Can be used to find the
    name of a service.

    :param str runas: User to run launchctl commands

    :return: A list of all the services enabled on the system
    :rtype: list

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_enabled
    '''
    # Collect list of enabled services
    stdout = list_(runas=runas)
    service_lines = [line for line in stdout.splitlines()]

    # Construct list of enabled services
    enabled = []
    for line in service_lines:
        # Skip header line
        if line.startswith('PID'):
            continue

        pid, status, label = line.split('\t')
        enabled.append(label)

    return sorted(set(enabled))