blob: d10e888dcce6f21126cef4c4ef7b0ffe2c90d33e [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
Renaud Paquay2e702912016-11-01 11:23:38 -07002#
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Renaud Paquayad1abcb2016-11-01 11:34:55 -070017import errno
Renaud Paquay2e702912016-11-01 11:23:38 -070018import os
19import platform
20import select
Renaud Paquaya65adf72016-11-03 10:37:53 -070021import shutil
22import stat
Renaud Paquay2e702912016-11-01 11:23:38 -070023
Dylan Denge469a0c2018-06-23 15:02:26 +080024from pyversion import is_python3
25if is_python3():
26 from queue import Queue
27else:
28 from Queue import Queue
29
Renaud Paquay2e702912016-11-01 11:23:38 -070030from threading import Thread
31
32
33def isWindows():
34 """ Returns True when running with the native port of Python for Windows,
35 False when running on any other platform (including the Cygwin port of
36 Python).
37 """
38 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
39 return platform.system() == "Windows"
40
41
42class FileDescriptorStreams(object):
43 """ Platform agnostic abstraction enabling non-blocking I/O over a
44 collection of file descriptors. This abstraction is required because
45 fctnl(os.O_NONBLOCK) is not supported on Windows.
46 """
47 @classmethod
48 def create(cls):
49 """ Factory method: instantiates the concrete class according to the
50 current platform.
51 """
52 if isWindows():
53 return _FileDescriptorStreamsThreads()
54 else:
55 return _FileDescriptorStreamsNonBlocking()
56
57 def __init__(self):
58 self.streams = []
59
60 def add(self, fd, dest, std_name):
61 """ Wraps an existing file descriptor as a stream.
62 """
63 self.streams.append(self._create_stream(fd, dest, std_name))
64
65 def remove(self, stream):
66 """ Removes a stream, when done with it.
67 """
68 self.streams.remove(stream)
69
70 @property
71 def is_done(self):
72 """ Returns True when all streams have been processed.
73 """
74 return len(self.streams) == 0
75
76 def select(self):
77 """ Returns the set of streams that have data available to read.
78 The returned streams each expose a read() and a close() method.
79 When done with a stream, call the remove(stream) method.
80 """
81 raise NotImplementedError
82
Rostislav Krasnyec0ba272020-01-25 14:32:37 +020083 def _create_stream(self, fd, dest, std_name):
Renaud Paquay2e702912016-11-01 11:23:38 -070084 """ Creates a new stream wrapping an existing file descriptor.
85 """
86 raise NotImplementedError
87
88
89class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
90 """ Implementation of FileDescriptorStreams for platforms that support
91 non blocking I/O.
92 """
Theodore Dubois23d7daf2020-03-12 01:06:57 +000093 def __init__(self):
94 super(_FileDescriptorStreamsNonBlocking, self).__init__()
95 self._poll = select.poll()
96 self._fd_to_stream = {}
97
Renaud Paquay2e702912016-11-01 11:23:38 -070098 class Stream(object):
99 """ Encapsulates a file descriptor """
David Pursehouse819827a2020-02-12 15:20:19 +0900100
Renaud Paquay2e702912016-11-01 11:23:38 -0700101 def __init__(self, fd, dest, std_name):
102 self.fd = fd
103 self.dest = dest
104 self.std_name = std_name
105 self.set_non_blocking()
106
107 def set_non_blocking(self):
108 import fcntl
109 flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
110 fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
111
112 def fileno(self):
113 return self.fd.fileno()
114
115 def read(self):
116 return self.fd.read(4096)
117
118 def close(self):
119 self.fd.close()
120
121 def _create_stream(self, fd, dest, std_name):
Theodore Dubois23d7daf2020-03-12 01:06:57 +0000122 stream = self.Stream(fd, dest, std_name)
123 self._fd_to_stream[stream.fileno()] = stream
124 self._poll.register(stream, select.POLLIN)
125 return stream
126
127 def remove(self, stream):
128 self._poll.unregister(stream)
129 del self._fd_to_stream[stream.fileno()]
130 super(_FileDescriptorStreamsNonBlocking, self).remove(stream)
Renaud Paquay2e702912016-11-01 11:23:38 -0700131
132 def select(self):
Theodore Dubois23d7daf2020-03-12 01:06:57 +0000133 return [self._fd_to_stream[fd] for fd, _ in self._poll.poll()]
Renaud Paquay2e702912016-11-01 11:23:38 -0700134
135
136class _FileDescriptorStreamsThreads(FileDescriptorStreams):
137 """ Implementation of FileDescriptorStreams for platforms that don't support
138 non blocking I/O. This implementation requires creating threads issuing
139 blocking read operations on file descriptors.
140 """
David Pursehouse819827a2020-02-12 15:20:19 +0900141
Renaud Paquay2e702912016-11-01 11:23:38 -0700142 def __init__(self):
143 super(_FileDescriptorStreamsThreads, self).__init__()
144 # The queue is shared accross all threads so we can simulate the
145 # behavior of the select() function
146 self.queue = Queue(10) # Limit incoming data from streams
147
148 def _create_stream(self, fd, dest, std_name):
149 return self.Stream(fd, dest, std_name, self.queue)
150
151 def select(self):
152 # Return only one stream at a time, as it is the most straighforward
153 # thing to do and it is compatible with the select() function.
154 item = self.queue.get()
155 stream = item.stream
156 stream.data = item.data
157 return [stream]
158
159 class QueueItem(object):
160 """ Item put in the shared queue """
David Pursehouse819827a2020-02-12 15:20:19 +0900161
Renaud Paquay2e702912016-11-01 11:23:38 -0700162 def __init__(self, stream, data):
163 self.stream = stream
164 self.data = data
165
166 class Stream(object):
167 """ Encapsulates a file descriptor """
David Pursehouse819827a2020-02-12 15:20:19 +0900168
Renaud Paquay2e702912016-11-01 11:23:38 -0700169 def __init__(self, fd, dest, std_name, queue):
170 self.fd = fd
171 self.dest = dest
172 self.std_name = std_name
173 self.queue = queue
174 self.data = None
175 self.thread = Thread(target=self.read_to_queue)
176 self.thread.daemon = True
177 self.thread.start()
178
179 def close(self):
180 self.fd.close()
181
182 def read(self):
183 data = self.data
184 self.data = None
185 return data
186
187 def read_to_queue(self):
188 """ The thread function: reads everything from the file descriptor into
189 the shared queue and terminates when reaching EOF.
190 """
191 for line in iter(self.fd.readline, b''):
192 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
193 self.fd.close()
Mike Frysinger8f9bf482020-02-16 11:28:47 -0500194 self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, b''))
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700195
196
197def symlink(source, link_name):
198 """Creates a symbolic link pointing to source named link_name.
199 Note: On Windows, source must exist on disk, as the implementation needs
200 to know whether to create a "File" or a "Directory" symbolic link.
201 """
202 if isWindows():
203 import platform_utils_win32
204 source = _validate_winpath(source)
205 link_name = _validate_winpath(link_name)
206 target = os.path.join(os.path.dirname(link_name), source)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700207 if isdir(target):
208 platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700209 else:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700210 platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700211 else:
212 return os.symlink(source, link_name)
213
214
215def _validate_winpath(path):
216 path = os.path.normpath(path)
217 if _winpath_is_valid(path):
218 return path
219 raise ValueError("Path \"%s\" must be a relative path or an absolute "
220 "path starting with a drive letter".format(path))
221
222
223def _winpath_is_valid(path):
224 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
225 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
226 is ambiguous (e.g. "x:foo" or "\\foo").
227 """
228 assert isWindows()
229 path = os.path.normpath(path)
230 drive, tail = os.path.splitdrive(path)
231 if tail:
232 if not drive:
233 return tail[0] != os.sep # "\\foo" is invalid
234 else:
235 return tail[0] == os.sep # "x:foo" is invalid
236 else:
237 return not drive # "x:" is invalid
Renaud Paquaya65adf72016-11-03 10:37:53 -0700238
239
Renaud Paquaybed8b622018-09-27 10:46:58 -0700240def _makelongpath(path):
241 """Return the input path normalized to support the Windows long path syntax
242 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
243 MAX_PATH limit.
244 """
Renaud Paquaya65adf72016-11-03 10:37:53 -0700245 if isWindows():
Renaud Paquaybed8b622018-09-27 10:46:58 -0700246 # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
247 if len(path) < 246:
248 return path
249 if path.startswith(u"\\\\?\\"):
250 return path
251 if not os.path.isabs(path):
252 return path
253 # Append prefix and ensure unicode so that the special longpath syntax
254 # is supported by underlying Win32 API calls
255 return u"\\\\?\\" + os.path.normpath(path)
256 else:
257 return path
258
259
Mike Frysingerf4545122019-11-11 04:34:16 -0500260def rmtree(path, ignore_errors=False):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700261 """shutil.rmtree(path) wrapper with support for long paths on Windows.
262
263 Availability: Unix, Windows."""
Mike Frysingerf4545122019-11-11 04:34:16 -0500264 onerror = None
Renaud Paquaybed8b622018-09-27 10:46:58 -0700265 if isWindows():
Mike Frysingerf4545122019-11-11 04:34:16 -0500266 path = _makelongpath(path)
267 onerror = handle_rmtree_error
268 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
Renaud Paquaya65adf72016-11-03 10:37:53 -0700269
270
271def handle_rmtree_error(function, path, excinfo):
272 # Allow deleting read-only files
273 os.chmod(path, stat.S_IWRITE)
274 function(path)
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700275
276
277def rename(src, dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700278 """os.rename(src, dst) wrapper with support for long paths on Windows.
279
280 Availability: Unix, Windows."""
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700281 if isWindows():
282 # On Windows, rename fails if destination exists, see
283 # https://docs.python.org/2/library/os.html#os.rename
284 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700285 os.rename(_makelongpath(src), _makelongpath(dst))
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700286 except OSError as e:
287 if e.errno == errno.EEXIST:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700288 os.remove(_makelongpath(dst))
289 os.rename(_makelongpath(src), _makelongpath(dst))
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700290 else:
291 raise
292 else:
293 os.rename(src, dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700294
295
Renaud Paquay010fed72016-11-11 14:25:29 -0800296def remove(path):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700297 """Remove (delete) the file path. This is a replacement for os.remove that
298 allows deleting read-only files on Windows, with support for long paths and
299 for deleting directory symbolic links.
300
301 Availability: Unix, Windows."""
Renaud Paquay010fed72016-11-11 14:25:29 -0800302 if isWindows():
Renaud Paquaybed8b622018-09-27 10:46:58 -0700303 longpath = _makelongpath(path)
Renaud Paquay010fed72016-11-11 14:25:29 -0800304 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700305 os.remove(longpath)
Renaud Paquay010fed72016-11-11 14:25:29 -0800306 except OSError as e:
307 if e.errno == errno.EACCES:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700308 os.chmod(longpath, stat.S_IWRITE)
309 # Directory symbolic links must be deleted with 'rmdir'.
310 if islink(longpath) and isdir(longpath):
311 os.rmdir(longpath)
312 else:
313 os.remove(longpath)
Renaud Paquay010fed72016-11-11 14:25:29 -0800314 else:
315 raise
316 else:
317 os.remove(path)
318
319
Renaud Paquaybed8b622018-09-27 10:46:58 -0700320def walk(top, topdown=True, onerror=None, followlinks=False):
321 """os.walk(path) wrapper with support for long paths on Windows.
322
323 Availability: Windows, Unix.
324 """
325 if isWindows():
326 return _walk_windows_impl(top, topdown, onerror, followlinks)
327 else:
328 return os.walk(top, topdown, onerror, followlinks)
329
330
331def _walk_windows_impl(top, topdown, onerror, followlinks):
332 try:
333 names = listdir(top)
David Pursehoused26146d2018-11-01 11:54:10 +0900334 except Exception as err:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700335 if onerror is not None:
336 onerror(err)
337 return
338
339 dirs, nondirs = [], []
340 for name in names:
341 if isdir(os.path.join(top, name)):
342 dirs.append(name)
343 else:
344 nondirs.append(name)
345
346 if topdown:
347 yield top, dirs, nondirs
348 for name in dirs:
349 new_path = os.path.join(top, name)
350 if followlinks or not islink(new_path):
351 for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
352 yield x
353 if not topdown:
354 yield top, dirs, nondirs
355
356
357def listdir(path):
358 """os.listdir(path) wrapper with support for long paths on Windows.
359
360 Availability: Windows, Unix.
361 """
362 return os.listdir(_makelongpath(path))
363
364
365def rmdir(path):
366 """os.rmdir(path) wrapper with support for long paths on Windows.
367
368 Availability: Windows, Unix.
369 """
370 os.rmdir(_makelongpath(path))
371
372
373def isdir(path):
374 """os.path.isdir(path) wrapper with support for long paths on Windows.
375
376 Availability: Windows, Unix.
377 """
378 return os.path.isdir(_makelongpath(path))
379
380
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700381def islink(path):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700382 """os.path.islink(path) wrapper with support for long paths on Windows.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700383
384 Availability: Windows, Unix.
385 """
386 if isWindows():
387 import platform_utils_win32
Renaud Paquaybed8b622018-09-27 10:46:58 -0700388 return platform_utils_win32.islink(_makelongpath(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700389 else:
390 return os.path.islink(path)
391
392
393def readlink(path):
394 """Return a string representing the path to which the symbolic link
395 points. The result may be either an absolute or relative pathname;
396 if it is relative, it may be converted to an absolute pathname using
397 os.path.join(os.path.dirname(path), result).
398
399 Availability: Windows, Unix.
400 """
401 if isWindows():
402 import platform_utils_win32
Renaud Paquaybed8b622018-09-27 10:46:58 -0700403 return platform_utils_win32.readlink(_makelongpath(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700404 else:
405 return os.readlink(path)
406
407
408def realpath(path):
409 """Return the canonical path of the specified filename, eliminating
410 any symbolic links encountered in the path.
411
412 Availability: Windows, Unix.
413 """
414 if isWindows():
415 current_path = os.path.abspath(path)
416 path_tail = []
417 for c in range(0, 100): # Avoid cycles
418 if islink(current_path):
419 target = readlink(current_path)
420 current_path = os.path.join(os.path.dirname(current_path), target)
421 else:
422 basename = os.path.basename(current_path)
423 if basename == '':
424 path_tail.append(current_path)
425 break
426 path_tail.append(basename)
427 current_path = os.path.dirname(current_path)
428 path_tail.reverse()
429 result = os.path.normpath(os.path.join(*path_tail))
430 return result
431 else:
432 return os.path.realpath(path)