1"""
2Service support for RHEL-based systems, including support for both upstart and sysvinit
3
4.. important::
5    If you feel that Salt should be using this module to manage services on a
6    minion, and it is using a different module (or gives an error similar to
7    *'service.start' is not available*), see :ref:`here
8    <module-provider-override>`.
9"""
10
11import fnmatch
12import glob
13import logging
14import os
15import re
16import stat
17
18import salt.utils.path
19
20log = logging.getLogger(__name__)
21
22__func_alias__ = {"reload_": "reload"}
23
24# Define the module's virtual name
25__virtualname__ = "service"
26
27# Import upstart module if needed
28HAS_UPSTART = False
29if salt.utils.path.which("initctl"):
30    try:
31        # Don't re-invent the wheel, import the helper functions from the
32        # upstart module.
33        from salt.modules.upstart_service import (
34            _upstart_enable,
35            _upstart_disable,
36            _upstart_is_enabled,
37        )
38    except Exception as exc:  # pylint: disable=broad-except
39        log.error(
40            "Unable to import helper functions from salt.modules.upstart: %s", exc
41        )
42    else:
43        HAS_UPSTART = True
44
45
46def __virtual__():
47    """
48    Only work on select distros which still use Red Hat's /usr/bin/service for
49    management of either sysvinit or a hybrid sysvinit/upstart init system.
50    """
51    # Disable when booted with systemd
52    if __utils__["systemd.booted"](__context__):
53        return (
54            False,
55            "The rh_service execution module failed to load: this system was booted"
56            " with systemd.",
57        )
58
59    # Enable on these platforms only.
60    enable = {
61        "XenServer",
62        "XCP-ng",
63        "RedHat",
64        "CentOS",
65        "ScientificLinux",
66        "CloudLinux",
67        "Amazon",
68        "Fedora",
69        "ALT",
70        "OEL",
71        "SUSE  Enterprise Server",
72        "SUSE",
73        "McAfee  OS Server",
74        "VirtuozzoLinux",
75    }
76    if __grains__["os"] in enable:
77
78        if __grains__["os"] == "SUSE":
79            if str(__grains__["osrelease"]).startswith("11"):
80                return __virtualname__
81            else:
82                return (False, "Cannot load rh_service module on SUSE > 11")
83
84        osrelease_major = __grains__.get("osrelease_info", [0])[0]
85
86        if __grains__["os"] in ("XenServer", "XCP-ng"):
87            if osrelease_major >= 7:
88                return (
89                    False,
90                    "XenServer and XCP-ng >= 7 use systemd, will not load rh_service.py"
91                    " as virtual 'service'",
92                )
93            return __virtualname__
94
95        if __grains__["os"] == "Fedora":
96            if osrelease_major >= 15:
97                return (
98                    False,
99                    "Fedora >= 15 uses systemd, will not load rh_service.py "
100                    "as virtual 'service'",
101                )
102        if __grains__["os"] in (
103            "RedHat",
104            "CentOS",
105            "ScientificLinux",
106            "OEL",
107            "CloudLinux",
108        ):
109            if osrelease_major >= 7:
110                return (
111                    False,
112                    "RedHat-based distros >= version 7 use systemd, will not "
113                    "load rh_service.py as virtual 'service'",
114                )
115        return __virtualname__
116    return (False, "Cannot load rh_service module: OS not in {}".format(enable))
117
118
119def _runlevel():
120    """
121    Return the current runlevel
122    """
123    out = __salt__["cmd.run"]("/sbin/runlevel")
124    # unknown will be returned while inside a kickstart environment, since
125    # this is usually a server deployment it should be safe to assume runlevel
126    # 3.  If not all service related states will throw an out of range
127    # exception here which will cause other functions to fail.
128    if "unknown" in out:
129        return "3"
130    else:
131        return out.split()[1]
132
133
134def _chkconfig_add(name):
135    """
136    Run 'chkconfig --add' for a service whose script is installed in
137    /etc/init.d.  The service is initially configured to be disabled at all
138    run-levels.
139    """
140    cmd = "/sbin/chkconfig --add {}".format(name)
141    if __salt__["cmd.retcode"](cmd, python_shell=False) == 0:
142        log.info('Added initscript "%s" to chkconfig', name)
143        return True
144    else:
145        log.error('Unable to add initscript "%s" to chkconfig', name)
146        return False
147
148
149def _service_is_upstart(name):
150    """
151    Return True if the service is an upstart service, otherwise return False.
152    """
153    return HAS_UPSTART and os.path.exists("/etc/init/{}.conf".format(name))
154
155
156def _service_is_sysv(name):
157    """
158    Return True if the service is a System V service (includes those managed by
159    chkconfig); otherwise return False.
160    """
161    try:
162        # Look for user-execute bit in file mode.
163        return bool(os.stat(os.path.join("/etc/init.d", name)).st_mode & stat.S_IXUSR)
164    except OSError:
165        return False
166
167
168def _service_is_chkconfig(name):
169    """
170    Return True if the service is managed by chkconfig.
171    """
172    cmdline = "/sbin/chkconfig --list {}".format(name)
173    return (
174        __salt__["cmd.retcode"](cmdline, python_shell=False, ignore_retcode=True) == 0
175    )
176
177
178def _sysv_is_enabled(name, runlevel=None):
179    """
180    Return True if the sysv (or chkconfig) service is enabled for the specified
181    runlevel; otherwise return False.  If `runlevel` is None, then use the
182    current runlevel.
183    """
184    # Try chkconfig first.
185    result = _chkconfig_is_enabled(name, runlevel)
186    if result:
187        return True
188
189    if runlevel is None:
190        runlevel = _runlevel()
191    return len(glob.glob("/etc/rc.d/rc{}.d/S??{}".format(runlevel, name))) > 0
192
193
194def _chkconfig_is_enabled(name, runlevel=None):
195    """
196    Return ``True`` if the service is enabled according to chkconfig; otherwise
197    return ``False``.  If ``runlevel`` is ``None``, then use the current
198    runlevel.
199    """
200    cmdline = "/sbin/chkconfig --list {}".format(name)
201    result = __salt__["cmd.run_all"](cmdline, python_shell=False)
202
203    if runlevel is None:
204        runlevel = _runlevel()
205    if result["retcode"] == 0:
206        for row in result["stdout"].splitlines():
207            if "{}:on".format(runlevel) in row:
208                if row.split()[0] == name:
209                    return True
210            elif row.split() == [name, "on"]:
211                return True
212    return False
213
214
215def _sysv_enable(name):
216    """
217    Enable the named sysv service to start at boot.  The service will be enabled
218    using chkconfig with default run-levels if the service is chkconfig
219    compatible.  If chkconfig is not available, then this will fail.
220    """
221    if not _service_is_chkconfig(name) and not _chkconfig_add(name):
222        return False
223    cmd = "/sbin/chkconfig {} on".format(name)
224    return not __salt__["cmd.retcode"](cmd, python_shell=False)
225
226
227def _sysv_disable(name):
228    """
229    Disable the named sysv service from starting at boot.  The service will be
230    disabled using chkconfig with default run-levels if the service is chkconfig
231    compatible; otherwise, the service will be disabled for the current
232    run-level only.
233    """
234    if not _service_is_chkconfig(name) and not _chkconfig_add(name):
235        return False
236    cmd = "/sbin/chkconfig {} off".format(name)
237    return not __salt__["cmd.retcode"](cmd, python_shell=False)
238
239
240def _sysv_delete(name):
241    """
242    Delete the named sysv service from the system. The service will be
243    deleted using chkconfig.
244    """
245    if not _service_is_chkconfig(name):
246        return False
247    cmd = "/sbin/chkconfig --del {}".format(name)
248    return not __salt__["cmd.retcode"](cmd)
249
250
251def _upstart_delete(name):
252    """
253    Delete an upstart service. This will only rename the .conf file
254    """
255    if HAS_UPSTART:
256        if os.path.exists("/etc/init/{}.conf".format(name)):
257            os.rename(
258                "/etc/init/{}.conf".format(name),
259                "/etc/init/{}.conf.removed".format(name),
260            )
261    return True
262
263
264def _upstart_services():
265    """
266    Return list of upstart services.
267    """
268    if HAS_UPSTART:
269        return [os.path.basename(name)[:-5] for name in glob.glob("/etc/init/*.conf")]
270    else:
271        return []
272
273
274def _sysv_services():
275    """
276    Return list of sysv services.
277    """
278    _services = []
279    output = __salt__["cmd.run"](["chkconfig", "--list"], python_shell=False)
280    for line in output.splitlines():
281        comps = line.split()
282        try:
283            if comps[1].startswith("0:"):
284                _services.append(comps[0])
285        except IndexError:
286            continue
287    # Return only the services that have an initscript present
288    return [x for x in _services if _service_is_sysv(x)]
289
290
291def get_enabled(limit=""):
292    """
293    Return the enabled services. Use the ``limit`` param to restrict results
294    to services of that type.
295
296    CLI Examples:
297
298    .. code-block:: bash
299
300        salt '*' service.get_enabled
301        salt '*' service.get_enabled limit=upstart
302        salt '*' service.get_enabled limit=sysvinit
303    """
304    limit = limit.lower()
305    if limit == "upstart":
306        return sorted(name for name in _upstart_services() if _upstart_is_enabled(name))
307    elif limit == "sysvinit":
308        runlevel = _runlevel()
309        return sorted(
310            name for name in _sysv_services() if _sysv_is_enabled(name, runlevel)
311        )
312    else:
313        runlevel = _runlevel()
314        return sorted(
315            [name for name in _upstart_services() if _upstart_is_enabled(name)]
316            + [name for name in _sysv_services() if _sysv_is_enabled(name, runlevel)]
317        )
318
319
320def get_disabled(limit=""):
321    """
322    Return the disabled services. Use the ``limit`` param to restrict results
323    to services of that type.
324
325    CLI Example:
326
327    .. code-block:: bash
328
329        salt '*' service.get_disabled
330        salt '*' service.get_disabled limit=upstart
331        salt '*' service.get_disabled limit=sysvinit
332    """
333    limit = limit.lower()
334    if limit == "upstart":
335        return sorted(
336            name for name in _upstart_services() if not _upstart_is_enabled(name)
337        )
338    elif limit == "sysvinit":
339        runlevel = _runlevel()
340        return sorted(
341            name for name in _sysv_services() if not _sysv_is_enabled(name, runlevel)
342        )
343    else:
344        runlevel = _runlevel()
345        return sorted(
346            [name for name in _upstart_services() if not _upstart_is_enabled(name)]
347            + [
348                name
349                for name in _sysv_services()
350                if not _sysv_is_enabled(name, runlevel)
351            ]
352        )
353
354
355def get_all(limit=""):
356    """
357    Return all installed services. Use the ``limit`` param to restrict results
358    to services of that type.
359
360    CLI Example:
361
362    .. code-block:: bash
363
364        salt '*' service.get_all
365        salt '*' service.get_all limit=upstart
366        salt '*' service.get_all limit=sysvinit
367    """
368    limit = limit.lower()
369    if limit == "upstart":
370        return sorted(_upstart_services())
371    elif limit == "sysvinit":
372        return sorted(_sysv_services())
373    else:
374        return sorted(_sysv_services() + _upstart_services())
375
376
377def available(name, limit=""):
378    """
379    Return True if the named service is available.  Use the ``limit`` param to
380    restrict results to services of that type.
381
382    CLI Examples:
383
384    .. code-block:: bash
385
386        salt '*' service.available sshd
387        salt '*' service.available sshd limit=upstart
388        salt '*' service.available sshd limit=sysvinit
389    """
390    if limit == "upstart":
391        return _service_is_upstart(name)
392    elif limit == "sysvinit":
393        return _service_is_sysv(name)
394    else:
395        return (
396            _service_is_upstart(name)
397            or _service_is_sysv(name)
398            or _service_is_chkconfig(name)
399        )
400
401
402def missing(name, limit=""):
403    """
404    The inverse of service.available.
405    Return True if the named service is not available.  Use the ``limit`` param to
406    restrict results to services of that type.
407
408    CLI Examples:
409
410    .. code-block:: bash
411
412        salt '*' service.missing sshd
413        salt '*' service.missing sshd limit=upstart
414        salt '*' service.missing sshd limit=sysvinit
415    """
416    if limit == "upstart":
417        return not _service_is_upstart(name)
418    elif limit == "sysvinit":
419        return not _service_is_sysv(name)
420    else:
421        if _service_is_upstart(name) or _service_is_sysv(name):
422            return False
423        else:
424            return True
425
426
427def start(name):
428    """
429    Start the specified service
430
431    CLI Example:
432
433    .. code-block:: bash
434
435        salt '*' service.start <service name>
436    """
437    if _service_is_upstart(name):
438        cmd = "start {}".format(name)
439    else:
440        cmd = "/sbin/service {} start".format(name)
441    return not __salt__["cmd.retcode"](cmd, python_shell=False)
442
443
444def stop(name):
445    """
446    Stop the specified service
447
448    CLI Example:
449
450    .. code-block:: bash
451
452        salt '*' service.stop <service name>
453    """
454    if _service_is_upstart(name):
455        cmd = "stop {}".format(name)
456    else:
457        cmd = "/sbin/service {} stop".format(name)
458    return not __salt__["cmd.retcode"](cmd, python_shell=False)
459
460
461def restart(name):
462    """
463    Restart the named service
464
465    CLI Example:
466
467    .. code-block:: bash
468
469        salt '*' service.restart <service name>
470    """
471    if _service_is_upstart(name):
472        cmd = "restart {}".format(name)
473    else:
474        cmd = "/sbin/service {} restart".format(name)
475    return not __salt__["cmd.retcode"](cmd, python_shell=False)
476
477
478def reload_(name):
479    """
480    Reload the named service
481
482    CLI Example:
483
484    .. code-block:: bash
485
486        salt '*' service.reload <service name>
487    """
488    if _service_is_upstart(name):
489        cmd = "reload {}".format(name)
490    else:
491        cmd = "/sbin/service {} reload".format(name)
492    return not __salt__["cmd.retcode"](cmd, python_shell=False)
493
494
495def status(name, sig=None):
496    """
497    Return the status for a service.
498    If the name contains globbing, a dict mapping service name to True/False
499    values is returned.
500
501    .. versionchanged:: 2018.3.0
502        The service name can now be a glob (e.g. ``salt*``)
503
504    Args:
505        name (str): The name of the service to check
506        sig (str): Signature to use to find the service via ps
507
508    Returns:
509        bool: True if running, False otherwise
510        dict: Maps service name to True if running, False otherwise
511
512    CLI Example:
513
514    .. code-block:: bash
515
516        salt '*' service.status <service name> [service signature]
517    """
518    if sig:
519        return bool(__salt__["status.pid"](sig))
520
521    contains_globbing = bool(re.search(r"\*|\?|\[.+\]", name))
522    if contains_globbing:
523        services = fnmatch.filter(get_all(), name)
524    else:
525        services = [name]
526    results = {}
527    for service in services:
528        if _service_is_upstart(service):
529            cmd = "status {}".format(service)
530            results[service] = "start/running" in __salt__["cmd.run"](
531                cmd, python_shell=False
532            )
533        else:
534            cmd = "/sbin/service {} status".format(service)
535            results[service] = (
536                __salt__["cmd.retcode"](cmd, python_shell=False, ignore_retcode=True)
537                == 0
538            )
539    if contains_globbing:
540        return results
541    return results[name]
542
543
544def delete(name, **kwargs):
545    """
546    Delete the named service
547
548    .. versionadded:: 2016.3
549
550    CLI Example:
551
552    .. code-block:: bash
553
554        salt '*' service.delete <service name>
555    """
556    if _service_is_upstart(name):
557        return _upstart_delete(name)
558    else:
559        return _sysv_delete(name)
560
561
562def enable(name, **kwargs):
563    """
564    Enable the named service to start at boot
565
566    CLI Example:
567
568    .. code-block:: bash
569
570        salt '*' service.enable <service name>
571    """
572    if _service_is_upstart(name):
573        return _upstart_enable(name)
574    else:
575        return _sysv_enable(name)
576
577
578def disable(name, **kwargs):
579    """
580    Disable the named service to start at boot
581
582    CLI Example:
583
584    .. code-block:: bash
585
586        salt '*' service.disable <service name>
587    """
588    if _service_is_upstart(name):
589        return _upstart_disable(name)
590    else:
591        return _sysv_disable(name)
592
593
594def enabled(name, **kwargs):
595    """
596    Check to see if the named service is enabled to start on boot
597
598    CLI Example:
599
600    .. code-block:: bash
601
602        salt '*' service.enabled <service name>
603    """
604    if _service_is_upstart(name):
605        return _upstart_is_enabled(name)
606    else:
607        return _sysv_is_enabled(name)
608
609
610def disabled(name):
611    """
612    Check to see if the named service is disabled to start on boot
613
614    CLI Example:
615
616    .. code-block:: bash
617
618        salt '*' service.disabled <service name>
619    """
620    if _service_is_upstart(name):
621        return not _upstart_is_enabled(name)
622    else:
623        return not _sysv_is_enabled(name)
624