platform_utils.py 9.0 KB

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