blob: f4dfa0b142c6c48dcbcd3afd25a25fba1f4cadd9 [file] [log] [blame]
Renaud Paquay2e702912016-11-01 11:23:38 -07001#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import platform
18import select
19
20from Queue import Queue
21from threading import Thread
22
23
24def isWindows():
25 """ Returns True when running with the native port of Python for Windows,
26 False when running on any other platform (including the Cygwin port of
27 Python).
28 """
29 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
30 return platform.system() == "Windows"
31
32
33class FileDescriptorStreams(object):
34 """ Platform agnostic abstraction enabling non-blocking I/O over a
35 collection of file descriptors. This abstraction is required because
36 fctnl(os.O_NONBLOCK) is not supported on Windows.
37 """
38 @classmethod
39 def create(cls):
40 """ Factory method: instantiates the concrete class according to the
41 current platform.
42 """
43 if isWindows():
44 return _FileDescriptorStreamsThreads()
45 else:
46 return _FileDescriptorStreamsNonBlocking()
47
48 def __init__(self):
49 self.streams = []
50
51 def add(self, fd, dest, std_name):
52 """ Wraps an existing file descriptor as a stream.
53 """
54 self.streams.append(self._create_stream(fd, dest, std_name))
55
56 def remove(self, stream):
57 """ Removes a stream, when done with it.
58 """
59 self.streams.remove(stream)
60
61 @property
62 def is_done(self):
63 """ Returns True when all streams have been processed.
64 """
65 return len(self.streams) == 0
66
67 def select(self):
68 """ Returns the set of streams that have data available to read.
69 The returned streams each expose a read() and a close() method.
70 When done with a stream, call the remove(stream) method.
71 """
72 raise NotImplementedError
73
74 def _create_stream(fd, dest, std_name):
75 """ Creates a new stream wrapping an existing file descriptor.
76 """
77 raise NotImplementedError
78
79
80class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
81 """ Implementation of FileDescriptorStreams for platforms that support
82 non blocking I/O.
83 """
84 class Stream(object):
85 """ Encapsulates a file descriptor """
86 def __init__(self, fd, dest, std_name):
87 self.fd = fd
88 self.dest = dest
89 self.std_name = std_name
90 self.set_non_blocking()
91
92 def set_non_blocking(self):
93 import fcntl
94 flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
95 fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
96
97 def fileno(self):
98 return self.fd.fileno()
99
100 def read(self):
101 return self.fd.read(4096)
102
103 def close(self):
104 self.fd.close()
105
106 def _create_stream(self, fd, dest, std_name):
107 return self.Stream(fd, dest, std_name)
108
109 def select(self):
110 ready_streams, _, _ = select.select(self.streams, [], [])
111 return ready_streams
112
113
114class _FileDescriptorStreamsThreads(FileDescriptorStreams):
115 """ Implementation of FileDescriptorStreams for platforms that don't support
116 non blocking I/O. This implementation requires creating threads issuing
117 blocking read operations on file descriptors.
118 """
119 def __init__(self):
120 super(_FileDescriptorStreamsThreads, self).__init__()
121 # The queue is shared accross all threads so we can simulate the
122 # behavior of the select() function
123 self.queue = Queue(10) # Limit incoming data from streams
124
125 def _create_stream(self, fd, dest, std_name):
126 return self.Stream(fd, dest, std_name, self.queue)
127
128 def select(self):
129 # Return only one stream at a time, as it is the most straighforward
130 # thing to do and it is compatible with the select() function.
131 item = self.queue.get()
132 stream = item.stream
133 stream.data = item.data
134 return [stream]
135
136 class QueueItem(object):
137 """ Item put in the shared queue """
138 def __init__(self, stream, data):
139 self.stream = stream
140 self.data = data
141
142 class Stream(object):
143 """ Encapsulates a file descriptor """
144 def __init__(self, fd, dest, std_name, queue):
145 self.fd = fd
146 self.dest = dest
147 self.std_name = std_name
148 self.queue = queue
149 self.data = None
150 self.thread = Thread(target=self.read_to_queue)
151 self.thread.daemon = True
152 self.thread.start()
153
154 def close(self):
155 self.fd.close()
156
157 def read(self):
158 data = self.data
159 self.data = None
160 return data
161
162 def read_to_queue(self):
163 """ The thread function: reads everything from the file descriptor into
164 the shared queue and terminates when reaching EOF.
165 """
166 for line in iter(self.fd.readline, b''):
167 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
168 self.fd.close()
169 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None))
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700170
171
172def symlink(source, link_name):
173 """Creates a symbolic link pointing to source named link_name.
174 Note: On Windows, source must exist on disk, as the implementation needs
175 to know whether to create a "File" or a "Directory" symbolic link.
176 """
177 if isWindows():
178 import platform_utils_win32
179 source = _validate_winpath(source)
180 link_name = _validate_winpath(link_name)
181 target = os.path.join(os.path.dirname(link_name), source)
182 if os.path.isdir(target):
183 platform_utils_win32.create_dirsymlink(source, link_name)
184 else:
185 platform_utils_win32.create_filesymlink(source, link_name)
186 else:
187 return os.symlink(source, link_name)
188
189
190def _validate_winpath(path):
191 path = os.path.normpath(path)
192 if _winpath_is_valid(path):
193 return path
194 raise ValueError("Path \"%s\" must be a relative path or an absolute "
195 "path starting with a drive letter".format(path))
196
197
198def _winpath_is_valid(path):
199 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
200 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
201 is ambiguous (e.g. "x:foo" or "\\foo").
202 """
203 assert isWindows()
204 path = os.path.normpath(path)
205 drive, tail = os.path.splitdrive(path)
206 if tail:
207 if not drive:
208 return tail[0] != os.sep # "\\foo" is invalid
209 else:
210 return tail[0] == os.sep # "x:foo" is invalid
211 else:
212 return not drive # "x:" is invalid