diff options
Diffstat (limited to 'platform_utils.py')
-rw-r--r-- | platform_utils.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/platform_utils.py b/platform_utils.py new file mode 100644 index 00000000..e0fa9dcc --- /dev/null +++ b/platform_utils.py | |||
@@ -0,0 +1,244 @@ | |||
1 | # | ||
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 | |||
16 | import errno | ||
17 | import os | ||
18 | import platform | ||
19 | import select | ||
20 | import shutil | ||
21 | import stat | ||
22 | |||
23 | from Queue import Queue | ||
24 | from threading import Thread | ||
25 | |||
26 | |||
27 | def 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 | |||
36 | class 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 | |||
83 | class _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 | |||
117 | class _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)) | ||
173 | |||
174 | |||
175 | def 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 | |||
193 | def _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 | |||
201 | def _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 | ||
216 | |||
217 | |||
218 | def rmtree(path): | ||
219 | if isWindows(): | ||
220 | shutil.rmtree(path, onerror=handle_rmtree_error) | ||
221 | else: | ||
222 | shutil.rmtree(path) | ||
223 | |||
224 | |||
225 | def handle_rmtree_error(function, path, excinfo): | ||
226 | # Allow deleting read-only files | ||
227 | os.chmod(path, stat.S_IWRITE) | ||
228 | function(path) | ||
229 | |||
230 | |||
231 | def 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) | ||