1"""Launch `dvc daemon` command in a separate detached process."""
2
3from __future__ import unicode_literals
4
5import os
6import sys
7import inspect
8from subprocess import Popen
9
10import dvc.logger as logger
11from dvc.utils import is_binary, fix_env
12from dvc.utils.compat import cast_bytes_py2
13
14
15CREATE_NEW_PROCESS_GROUP = 0x00000200
16DETACHED_PROCESS = 0x00000008
17
18
19def _spawn_windows(cmd, env):
20    from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
21
22    creationflags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
23
24    startupinfo = STARTUPINFO()
25    startupinfo.dwFlags |= STARTF_USESHOWWINDOW
26
27    Popen(
28        cmd,
29        env=env,
30        close_fds=True,
31        shell=False,
32        creationflags=creationflags,
33        startupinfo=startupinfo,
34    ).communicate()
35
36
37def _spawn_posix(cmd, env):
38    # NOTE: using os._exit instead of sys.exit, because dvc built
39    # with PyInstaller has trouble with SystemExit exeption and throws
40    # errors such as "[26338] Failed to execute script __main__"
41    try:
42        pid = os.fork()
43        if pid > 0:
44            return
45    except OSError:
46        logger.error("failed at first fork")
47        os._exit(1)  # pylint: disable=protected-access
48
49    os.setsid()
50    os.umask(0)
51
52    try:
53        pid = os.fork()
54        if pid > 0:
55            os._exit(0)  # pylint: disable=protected-access
56    except OSError:
57        logger.error("failed at second fork")
58        os._exit(1)  # pylint: disable=protected-access
59
60    sys.stdin.close()
61    sys.stdout.close()
62    sys.stderr.close()
63
64    Popen(cmd, env=env, close_fds=True, shell=False).communicate()
65
66    os._exit(0)  # pylint: disable=protected-access
67
68
69def daemon(args):
70    """Launch a `dvc daemon` command in a detached process.
71
72    Args:
73        args (list): list of arguments to append to `dvc daemon` command.
74    """
75    cmd = [sys.executable]
76    if not is_binary():
77        cmd += ["-m", "dvc"]
78    cmd += ["daemon", "-q"] + args
79
80    env = fix_env()
81    file_path = os.path.abspath(inspect.stack()[0][1])
82    env[cast_bytes_py2("PYTHONPATH")] = cast_bytes_py2(
83        os.path.dirname(os.path.dirname(file_path))
84    )
85
86    logger.debug("Trying to spawn '{}' with env '{}'".format(cmd, env))
87
88    if os.name == "nt":
89        _spawn_windows(cmd, env)
90    elif os.name == "posix":
91        _spawn_posix(cmd, env)
92    else:
93        raise NotImplementedError
94
95    logger.debug("Spawned '{}'".format(cmd))
96