blob: 4417c5a35c34cce99079360def72ed370c0e2d0d [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
Renaud Paquaya65adf72016-11-03 10:37:53 -070019import shutil
20import stat
Renaud Paquay2e702912016-11-01 11:23:38 -070021
22from Queue import Queue
23from threading import Thread
24
25
26def isWindows():
27 """ Returns True when running with the native port of Python for Windows,
28 False when running on any other platform (including the Cygwin port of
29 Python).
30 """
31 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
32 return platform.system() == "Windows"
33
34
35class FileDescriptorStreams(object):
36 """ Platform agnostic abstraction enabling non-blocking I/O over a
37 collection of file descriptors. This abstraction is required because
38 fctnl(os.O_NONBLOCK) is not supported on Windows.
39 """
40 @classmethod
41 def create(cls):
42 """ Factory method: instantiates the concrete class according to the
43 current platform.
44 """
45 if isWindows():
46 return _FileDescriptorStreamsThreads()
47 else:
48 return _FileDescriptorStreamsNonBlocking()
49
50 def __init__(self):
51 self.streams = []
52
53 def add(self, fd, dest, std_name):
54 """ Wraps an existing file descriptor as a stream.
55 """
56 self.streams.append(self._create_stream(fd, dest, std_name))
57
58 def remove(self, stream):
59 """ Removes a stream, when done with it.
60 """
61 self.streams.remove(stream)
62
63 @property
64 def is_done(self):
65 """ Returns True when all streams have been processed.
66 """
67 return len(self.streams) == 0
68
69 def select(self):
70 """ Returns the set of streams that have data available to read.
71 The returned streams each expose a read() and a close() method.
72 When done with a stream, call the remove(stream) method.
73 """
74 raise NotImplementedError
75
76 def _create_stream(fd, dest, std_name):
77 """ Creates a new stream wrapping an existing file descriptor.
78 """
79 raise NotImplementedError
80
81
82class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
83 """ Implementation of FileDescriptorStreams for platforms that support
84 non blocking I/O.
85 """
86 class Stream(object):
87 """ Encapsulates a file descriptor """
88 def __init__(self, fd, dest, std_name):
89 self.fd = fd
90 self.dest = dest
91 self.std_name = std_name
92 self.set_non_blocking()
93
94 def set_non_blocking(self):
95 import fcntl
96 flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
97 fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
98
99 def fileno(self):
100 return self.fd.fileno()
101
102 def read(self):
103 return self.fd.read(4096)
104
105 def close(self):
106 self.fd.close()
107
108 def _create_stream(self, fd, dest, std_name):
109 return self.Stream(fd, dest, std_name)
110
111 def select(self):
112 ready_streams, _, _ = select.select(self.streams, [], [])
113 return ready_streams
114
115
116class _FileDescriptorStreamsThreads(FileDescriptorStreams):
117 """ Implementation of FileDescriptorStreams for platforms that don't support
118 non blocking I/O. This implementation requires creating threads issuing
119 blocking read operations on file descriptors.
120 """
121 def __init__(self):
122 super(_FileDescriptorStreamsThreads, self).__init__()
123 # The queue is shared accross all threads so we can simulate the
124 # behavior of the select() function
125 self.queue = Queue(10) # Limit incoming data from streams
126
127 def _create_stream(self, fd, dest, std_name):
128 return self.Stream(fd, dest, std_name, self.queue)
129
130 def select(self):
131 # Return only one stream at a time, as it is the most straighforward
132 # thing to do and it is compatible with the select() function.
133 item = self.queue.get()
134 stream = item.stream
135 stream.data = item.data
136 return [stream]
137
138 class QueueItem(object):
139 """ Item put in the shared queue """
140 def __init__(self, stream, data):
141 self.stream = stream
142 self.data = data
143
144 class Stream(object):
145 """ Encapsulates a file descriptor """
146 def __init__(self, fd, dest, std_name, queue):
147 self.fd = fd
148 self.dest = dest
149 self.std_name = std_name
150 self.queue = queue
151 self.data = None
152 self.thread = Thread(target=self.read_to_queue)
153 self.thread.daemon = True
154 self.thread.start()
155
156 def close(self):
157 self.fd.close()
158
159 def read(self):
160 data = self.data
161 self.data = None
162 return data
163
164 def read_to_queue(self):
165 """ The thread function: reads everything from the file descriptor into
166 the shared queue and terminates when reaching EOF.
167 """
168 for line in iter(self.fd.readline, b''):
169 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
170 self.fd.close()
171 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None))
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700172
173
174def symlink(source, link_name):
175 """Creates a symbolic link pointing to source named link_name.
176 Note: On Windows, source must exist on disk, as the implementation needs
177 to know whether to create a "File" or a "Directory" symbolic link.
178 """
179 if isWindows():
180 import platform_utils_win32
181 source = _validate_winpath(source)
182 link_name = _validate_winpath(link_name)
183 target = os.path.join(os.path.dirname(link_name), source)
184 if os.path.isdir(target):
185 platform_utils_win32.create_dirsymlink(source, link_name)
186 else:
187 platform_utils_win32.create_filesymlink(source, link_name)
188 else:
189 return os.symlink(source, link_name)
190
191
192def _validate_winpath(path):
193 path = os.path.normpath(path)
194 if _winpath_is_valid(path):
195 return path
196 raise ValueError("Path \"%s\" must be a relative path or an absolute "
197 "path starting with a drive letter".format(path))
198
199
200def _winpath_is_valid(path):
201 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
202 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
203 is ambiguous (e.g. "x:foo" or "\\foo").
204 """
205 assert isWindows()
206 path = os.path.normpath(path)
207 drive, tail = os.path.splitdrive(path)
208 if tail:
209 if not drive:
210 return tail[0] != os.sep # "\\foo" is invalid
211 else:
212 return tail[0] == os.sep # "x:foo" is invalid
213 else:
214 return not drive # "x:" is invalid
Renaud Paquaya65adf72016-11-03 10:37:53 -0700215
216
217def rmtree(path):
218 if isWindows():
219 shutil.rmtree(path, onerror=handle_rmtree_error)
220 else:
221 shutil.rmtree(path)
222
223
224def handle_rmtree_error(function, path, excinfo):
225 # Allow deleting read-only files
226 os.chmod(path, stat.S_IWRITE)
227 function(path)