platform_utils.py 8.9 KB

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