1f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso# Copyright 2017 The Chromium OS Authors. All rights reserved.
2f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso# Use of this source code is governed by a BSD-style license that can be
3f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso# found in the LICENSE file.
4f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
5f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport logging
6f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport os
7f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport pyudev
8f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport re
9f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport select
10f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport struct
11f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport subprocess
12f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport threading
13f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoimport time
14f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusofrom autotest_lib.client.common_lib import error
15f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
16f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
17f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoJAIL_CONTROL_PATH = '/dev/jail-control'
18f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoJAIL_REQUEST_PATH = '/dev/jail-request'
19f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
20f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso# From linux/device_jail.h.
21f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoREQUEST_ALLOW = 0
22f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoREQUEST_ALLOW_WITH_LOCKDOWN = 1
23f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoREQUEST_ALLOW_WITH_DETACH = 2
24f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric CarusoREQUEST_DENY = 3
25f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
26f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
27f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoclass OSFile:
28f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    """Simple context manager for file descriptors."""
29f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __init__(self, path, flag):
30f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._fd = os.open(path, flag)
31f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
32f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def close(self):
33f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        os.close(self._fd)
34f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
35f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __enter__(self):
36f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        """Returns the fd so it can be used in with-blocks."""
37f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        return self._fd
38f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
39f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __exit__(self, exc_type, exc_val, traceback):
40f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self.close()
41f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
42f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
43f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoclass ConcurrentFunc:
44f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    """Simple context manager that starts and joins a thread."""
45f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __init__(self, target_func, timeout_func):
46f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._thread = threading.Thread(target=target_func)
47f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._timeout_func = timeout_func
48f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._target_name = target_func.__name__
49f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
50f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __enter__(self):
51f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._thread.start()
52f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
53f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __exit__(self, exc_type, exc_val, traceback):
54f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._thread.join(self._timeout_func())
55f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        if self._thread.is_alive() and not exc_val:
56f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            raise error.TestError('Function %s timed out' % self._target_name)
57f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
58f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
59f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusoclass JailDevice:
60f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    TIMEOUT_SEC = 3
61f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    PATH_MAX = 4096
62f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
63f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __init__(self, path_to_jail):
64f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        self._path_to_jail = path_to_jail
65f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
66f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
67f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __enter__(self):
68f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        """
69f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        Creates a jail device for the device located at self._path_to_jail.
70f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        If the jail already exists, don't take ownership of it.
71f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        """
72f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        try:
73f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            output = subprocess.check_output(
74f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                ['device_jail_utility',
75f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                 '--add={0}'.format(self._path_to_jail)],
76f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                stderr=subprocess.STDOUT)
77f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
78f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            match = re.search('created jail at (.*)', output)
79f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            if match:
80f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                self._path = match.group(1)
81f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                self._owns_device = True
82f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                return self
83f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
84f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            match = re.search('jail already exists at (.*)', output)
85f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            if match:
86f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                self._path = match.group(1)
87f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                self._owns_device = False
88f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                return self
89f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
90f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            raise error.TestError('Failed to create device jail')
91f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        except subprocess.CalledProcessError as e:
92f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            raise error.TestError('Failed to call device_jail_utility')
93f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
94f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
95f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def expect_open(self, verdict):
96f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        """
97f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        Tries to open the jail device. This method mocks out the
98f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        device_jail request server which is normally run by permission_broker.
99f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        This allows us to set the verdict we want to test. Since the open
100f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        call will block until we return the verdict, we have to use a
101f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        separate thread to perform the open call, as well.
102f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        """
103f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # Python 2 does not support "nonlocal" so this closure can't
104f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # set the values of identifiers it closes over unless they
105f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # are in global scope. Work around this by using a list and
106f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # value-mutation.
107f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        dev_file_wrapper = [None]
108f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        def open_device():
109f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            try:
110f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                dev_file_wrapper[0] = OSFile(self._path, os.O_RDWR)
111f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            except OSError as e:
112f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                # We don't throw an error because this might be intentional,
113f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                # such as when the verdict is REQUEST_DENY.
114f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                logging.info("Failed to open jail device: %s", e.strerror)
115f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
116f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # timeout_sec should be used for the timeouts below.
117f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # This ensures we don't spend much longer than TIMEOUT_SEC in
118f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # this method.
119f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        deadline = time.time() + self.TIMEOUT_SEC
120f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        def timeout_sec():
121f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            return max(deadline - time.time(), 0.01)
122f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
123f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # We have to use FDs because polling works with FDs and
124f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        # buffering is silly.
125f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        try:
126f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            req_f = OSFile(JAIL_REQUEST_PATH, os.O_RDWR)
127f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        except OSError as e:
128f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            raise error.TestError(
129f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                'Failed to open request device: %s' % e.strerror)
130f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
131f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        with req_f as req_fd:
132f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            poll_obj = select.poll()
133f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            poll_obj.register(req_fd, select.POLLIN)
134f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
135f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            # Starting open_device should ensure we have a request waiting
136f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            # on the request device.
137f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            with ConcurrentFunc(open_device, timeout_sec):
138f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                ready_fds = poll_obj.poll(timeout_sec() * 1000)
139f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                if not ready_fds:
140f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                    raise error.TestError('Timed out waiting for jail-request')
141f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
142f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                # Sanity check the request.
143f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                path = os.read(req_fd, self.PATH_MAX)
144f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                logging.info('Received jail-request for path %s', path)
145f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                if path != self._path_to_jail:
146f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                    raise error.TestError('Got request for the wrong path')
147f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
148f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                os.write(req_fd, struct.pack('I', verdict))
149f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                logging.info('Responded to jail-request')
150f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
151f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        return dev_file_wrapper[0]
152f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
153f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
154f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    def __exit__(self, exc_type, exc_val, traceback):
155f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        if self._owns_device:
156f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso            subprocess.call(['device_jail_utility',
157f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso                             '--remove={0}'.format(self._path)])
158f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
159f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso
160f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Carusodef get_usb_devices():
161f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    context = pyudev.Context()
162f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso    return [device for device in context.list_devices()
163f6ead933bd0819f78863988dfdb4dd10c3e92e5bEric Caruso        if device.device_node and device.device_node.startswith('/dev/bus/usb')]
164