blob: 2ad56490033ba5295c5eae1e83b7606f9fff7ee9 [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
Renaud Paquayad1abcb2016-11-01 11:34:55 -070016import errno
Renaud Paquay2e702912016-11-01 11:23:38 -070017import os
18import platform
19import select
Renaud Paquaya65adf72016-11-03 10:37:53 -070020import shutil
21import stat
Renaud Paquay2e702912016-11-01 11:23:38 -070022
23from Queue import Queue
24from threading import Thread
25
26
27def isWindows():
28 """ Returns True when running with the native port of Python for Windows,
29 False when running on any other platform (including the Cygwin port of
30 Python).
31 """
32 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
33 return platform.system() == "Windows"
34
35
36class FileDescriptorStreams(object):
37 """ Platform agnostic abstraction enabling non-blocking I/O over a
38 collection of file descriptors. This abstraction is required because
39 fctnl(os.O_NONBLOCK) is not supported on Windows.
40 """
41 @classmethod
42 def create(cls):
43 """ Factory method: instantiates the concrete class according to the
44 current platform.
45 """
46 if isWindows():
47 return _FileDescriptorStreamsThreads()
48 else:
49 return _FileDescriptorStreamsNonBlocking()
50
51 def __init__(self):
52 self.streams = []
53
54 def add(self, fd, dest, std_name):
55 """ Wraps an existing file descriptor as a stream.
56 """
57 self.streams.append(self._create_stream(fd, dest, std_name))
58
59 def remove(self, stream):
60 """ Removes a stream, when done with it.
61 """
62 self.streams.remove(stream)
63
64 @property
65 def is_done(self):
66 """ Returns True when all streams have been processed.
67 """
68 return len(self.streams) == 0
69
70 def select(self):
71 """ Returns the set of streams that have data available to read.
72 The returned streams each expose a read() and a close() method.
73 When done with a stream, call the remove(stream) method.
74 """
75 raise NotImplementedError
76
77 def _create_stream(fd, dest, std_name):
78 """ Creates a new stream wrapping an existing file descriptor.
79 """
80 raise NotImplementedError
81
82
83class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
84 """ Implementation of FileDescriptorStreams for platforms that support
85 non blocking I/O.
86 """
87 class Stream(object):
88 """ Encapsulates a file descriptor """
89 def __init__(self, fd, dest, std_name):
90 self.fd = fd
91 self.dest = dest
92 self.std_name = std_name
93 self.set_non_blocking()
94
95 def set_non_blocking(self):
96 import fcntl
97 flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
98 fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
99
100 def fileno(self):
101 return self.fd.fileno()
102
103 def read(self):
104 return self.fd.read(4096)
105
106 def close(self):
107 self.fd.close()
108
109 def _create_stream(self, fd, dest, std_name):
110 return self.Stream(fd, dest, std_name)
111
112 def select(self):
113 ready_streams, _, _ = select.select(self.streams, [], [])
114 return ready_streams
115
116
117class _FileDescriptorStreamsThreads(FileDescriptorStreams):
118 """ Implementation of FileDescriptorStreams for platforms that don't support
119 non blocking I/O. This implementation requires creating threads issuing
120 blocking read operations on file descriptors.
121 """
122 def __init__(self):
123 super(_FileDescriptorStreamsThreads, self).__init__()
124 # The queue is shared accross all threads so we can simulate the
125 # behavior of the select() function
126 self.queue = Queue(10) # Limit incoming data from streams
127
128 def _create_stream(self, fd, dest, std_name):
129 return self.Stream(fd, dest, std_name, self.queue)
130
131 def select(self):
132 # Return only one stream at a time, as it is the most straighforward
133 # thing to do and it is compatible with the select() function.
134 item = self.queue.get()
135 stream = item.stream
136 stream.data = item.data
137 return [stream]
138
139 class QueueItem(object):
140 """ Item put in the shared queue """
141 def __init__(self, stream, data):
142 self.stream = stream
143 self.data = data
144
145 class Stream(object):
146 """ Encapsulates a file descriptor """
147 def __init__(self, fd, dest, std_name, queue):
148 self.fd = fd
149 self.dest = dest
150 self.std_name = std_name
151 self.queue = queue
152 self.data = None
153 self.thread = Thread(target=self.read_to_queue)
154 self.thread.daemon = True
155 self.thread.start()
156
157 def close(self):
158 self.fd.close()
159
160 def read(self):
161 data = self.data
162 self.data = None
163 return data
164
165 def read_to_queue(self):
166 """ The thread function: reads everything from the file descriptor into
167 the shared queue and terminates when reaching EOF.
168 """
169 for line in iter(self.fd.readline, b''):
170 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
171 self.fd.close()
172 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None))
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700173
174
175def symlink(source, link_name):
176 """Creates a symbolic link pointing to source named link_name.
177 Note: On Windows, source must exist on disk, as the implementation needs
178 to know whether to create a "File" or a "Directory" symbolic link.
179 """
180 if isWindows():
181 import platform_utils_win32
182 source = _validate_winpath(source)
183 link_name = _validate_winpath(link_name)
184 target = os.path.join(os.path.dirname(link_name), source)
185 if os.path.isdir(target):
186 platform_utils_win32.create_dirsymlink(source, link_name)
187 else:
188 platform_utils_win32.create_filesymlink(source, link_name)
189 else:
190 return os.symlink(source, link_name)
191
192
193def _validate_winpath(path):
194 path = os.path.normpath(path)
195 if _winpath_is_valid(path):
196 return path
197 raise ValueError("Path \"%s\" must be a relative path or an absolute "
198 "path starting with a drive letter".format(path))
199
200
201def _winpath_is_valid(path):
202 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
203 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
204 is ambiguous (e.g. "x:foo" or "\\foo").
205 """
206 assert isWindows()
207 path = os.path.normpath(path)
208 drive, tail = os.path.splitdrive(path)
209 if tail:
210 if not drive:
211 return tail[0] != os.sep # "\\foo" is invalid
212 else:
213 return tail[0] == os.sep # "x:foo" is invalid
214 else:
215 return not drive # "x:" is invalid
Renaud Paquaya65adf72016-11-03 10:37:53 -0700216
217
218def rmtree(path):
219 if isWindows():
220 shutil.rmtree(path, onerror=handle_rmtree_error)
221 else:
222 shutil.rmtree(path)
223
224
225def handle_rmtree_error(function, path, excinfo):
226 # Allow deleting read-only files
227 os.chmod(path, stat.S_IWRITE)
228 function(path)
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700229
230
231def rename(src, dst):
232 if isWindows():
233 # On Windows, rename fails if destination exists, see
234 # https://docs.python.org/2/library/os.html#os.rename
235 try:
236 os.rename(src, dst)
237 except OSError as e:
238 if e.errno == errno.EEXIST:
239 os.remove(dst)
240 os.rename(src, dst)
241 else:
242 raise
243 else:
244 os.rename(src, dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700245
246
247def islink(path):
248 """Test whether a path is a symbolic link.
249
250 Availability: Windows, Unix.
251 """
252 if isWindows():
253 import platform_utils_win32
254 return platform_utils_win32.islink(path)
255 else:
256 return os.path.islink(path)
257
258
259def readlink(path):
260 """Return a string representing the path to which the symbolic link
261 points. The result may be either an absolute or relative pathname;
262 if it is relative, it may be converted to an absolute pathname using
263 os.path.join(os.path.dirname(path), result).
264
265 Availability: Windows, Unix.
266 """
267 if isWindows():
268 import platform_utils_win32
269 return platform_utils_win32.readlink(path)
270 else:
271 return os.readlink(path)
272
273
274def realpath(path):
275 """Return the canonical path of the specified filename, eliminating
276 any symbolic links encountered in the path.
277
278 Availability: Windows, Unix.
279 """
280 if isWindows():
281 current_path = os.path.abspath(path)
282 path_tail = []
283 for c in range(0, 100): # Avoid cycles
284 if islink(current_path):
285 target = readlink(current_path)
286 current_path = os.path.join(os.path.dirname(current_path), target)
287 else:
288 basename = os.path.basename(current_path)
289 if basename == '':
290 path_tail.append(current_path)
291 break
292 path_tail.append(basename)
293 current_path = os.path.dirname(current_path)
294 path_tail.reverse()
295 result = os.path.normpath(os.path.join(*path_tail))
296 return result
297 else:
298 return os.path.realpath(path)