1# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import collections
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.common_lib import process_utils
9from autotest_lib.client.common_lib import utils
10
11# Use this with ProcessWatcher to start your process in a minijail.  This
12# is useful for instance if you would like to drop autotest's default root
13# priviledges.  Both fields must be set to valid users/groups.
14MinijailConfig = collections.namedtuple('MinijailConfig', ['user', 'group'])
15
16
17class ProcessWatcher(object):
18    """Start a process, and terminate it later."""
19
20    def __init__(self, command, args=tuple(), minijail_config=None, host=None):
21        """Construst a ProcessWatcher without starting the process.
22
23        @param command: string command to use to start the process.
24        @param args: list of strings to pass to the command.
25        @param minijail_config: MinijailConfig tuple defined above.
26        @param host: host object if the server should be started on a remote
27                host.
28
29        """
30        self._command = ' '.join([command] + list(args))
31        if '"' in self._command:
32            raise error.TestError('Please implement shell escaping in '
33                                  'ProcessWatcher.')
34        self._minijail_config = minijail_config
35        self._run = utils.run if host is None else host.run
36
37
38    def start(self):
39        """Start a (potentially remote) instance of the process."""
40        command = self._command
41        prefix = ''
42        if self._minijail_config is not None:
43            prefix = 'minijail0 -i -g %s -u %s ' % (self._minijail_config.group,
44                                                    self._minijail_config.user)
45        # Redirect output streams to avoid odd interactions between autotest's
46        # shell environment and the command's runtime environment.
47        self._run('%s%s >/dev/null 2>&1 &' % (prefix, self._command))
48
49
50    def close(self, timeout_seconds=40):
51        """Close the (potentially remote) instance of the process.
52
53        @param timeout_seconds: int number of seconds to wait for shutdown.
54
55        """
56        process_utils.pkill_process(self._command,
57                                    timeout_seconds=timeout_seconds)
58