platform_utils.py 6.7 KB

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