platform_utils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. # Copyright (C) 2016 The Android Open Source Project
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import errno
  15. import os
  16. import platform
  17. from queue import Queue
  18. import select
  19. import shutil
  20. import stat
  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(self, 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. def __init__(self):
  73. super(_FileDescriptorStreamsNonBlocking, self).__init__()
  74. self._poll = select.poll()
  75. self._fd_to_stream = {}
  76. class Stream(object):
  77. """ Encapsulates a file descriptor """
  78. def __init__(self, fd, dest, std_name):
  79. self.fd = fd
  80. self.dest = dest
  81. self.std_name = std_name
  82. self.set_non_blocking()
  83. def set_non_blocking(self):
  84. import fcntl
  85. flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
  86. fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
  87. def fileno(self):
  88. return self.fd.fileno()
  89. def read(self):
  90. return self.fd.read(4096)
  91. def close(self):
  92. self.fd.close()
  93. def _create_stream(self, fd, dest, std_name):
  94. stream = self.Stream(fd, dest, std_name)
  95. self._fd_to_stream[stream.fileno()] = stream
  96. self._poll.register(stream, select.POLLIN)
  97. return stream
  98. def remove(self, stream):
  99. self._poll.unregister(stream)
  100. del self._fd_to_stream[stream.fileno()]
  101. super(_FileDescriptorStreamsNonBlocking, self).remove(stream)
  102. def select(self):
  103. return [self._fd_to_stream[fd] for fd, _ in self._poll.poll()]
  104. class _FileDescriptorStreamsThreads(FileDescriptorStreams):
  105. """ Implementation of FileDescriptorStreams for platforms that don't support
  106. non blocking I/O. This implementation requires creating threads issuing
  107. blocking read operations on file descriptors.
  108. """
  109. def __init__(self):
  110. super(_FileDescriptorStreamsThreads, self).__init__()
  111. # The queue is shared accross all threads so we can simulate the
  112. # behavior of the select() function
  113. self.queue = Queue(10) # Limit incoming data from streams
  114. def _create_stream(self, fd, dest, std_name):
  115. return self.Stream(fd, dest, std_name, self.queue)
  116. def select(self):
  117. # Return only one stream at a time, as it is the most straighforward
  118. # thing to do and it is compatible with the select() function.
  119. item = self.queue.get()
  120. stream = item.stream
  121. stream.data = item.data
  122. return [stream]
  123. class QueueItem(object):
  124. """ Item put in the shared queue """
  125. def __init__(self, stream, data):
  126. self.stream = stream
  127. self.data = data
  128. class Stream(object):
  129. """ Encapsulates a file descriptor """
  130. def __init__(self, fd, dest, std_name, queue):
  131. self.fd = fd
  132. self.dest = dest
  133. self.std_name = std_name
  134. self.queue = queue
  135. self.data = None
  136. self.thread = Thread(target=self.read_to_queue)
  137. self.thread.daemon = True
  138. self.thread.start()
  139. def close(self):
  140. self.fd.close()
  141. def read(self):
  142. data = self.data
  143. self.data = None
  144. return data
  145. def read_to_queue(self):
  146. """ The thread function: reads everything from the file descriptor into
  147. the shared queue and terminates when reaching EOF.
  148. """
  149. for line in iter(self.fd.readline, b''):
  150. self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
  151. self.fd.close()
  152. self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, b''))
  153. def symlink(source, link_name):
  154. """Creates a symbolic link pointing to source named link_name.
  155. Note: On Windows, source must exist on disk, as the implementation needs
  156. to know whether to create a "File" or a "Directory" symbolic link.
  157. """
  158. if isWindows():
  159. import platform_utils_win32
  160. source = _validate_winpath(source)
  161. link_name = _validate_winpath(link_name)
  162. target = os.path.join(os.path.dirname(link_name), source)
  163. if isdir(target):
  164. platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
  165. else:
  166. platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
  167. else:
  168. return os.symlink(source, link_name)
  169. def _validate_winpath(path):
  170. path = os.path.normpath(path)
  171. if _winpath_is_valid(path):
  172. return path
  173. raise ValueError("Path \"%s\" must be a relative path or an absolute "
  174. "path starting with a drive letter".format(path))
  175. def _winpath_is_valid(path):
  176. """Windows only: returns True if path is relative (e.g. ".\\foo") or is
  177. absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
  178. is ambiguous (e.g. "x:foo" or "\\foo").
  179. """
  180. assert isWindows()
  181. path = os.path.normpath(path)
  182. drive, tail = os.path.splitdrive(path)
  183. if tail:
  184. if not drive:
  185. return tail[0] != os.sep # "\\foo" is invalid
  186. else:
  187. return tail[0] == os.sep # "x:foo" is invalid
  188. else:
  189. return not drive # "x:" is invalid
  190. def _makelongpath(path):
  191. """Return the input path normalized to support the Windows long path syntax
  192. ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
  193. MAX_PATH limit.
  194. """
  195. if isWindows():
  196. # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
  197. if len(path) < 246:
  198. return path
  199. if path.startswith(u"\\\\?\\"):
  200. return path
  201. if not os.path.isabs(path):
  202. return path
  203. # Append prefix and ensure unicode so that the special longpath syntax
  204. # is supported by underlying Win32 API calls
  205. return u"\\\\?\\" + os.path.normpath(path)
  206. else:
  207. return path
  208. def rmtree(path, ignore_errors=False):
  209. """shutil.rmtree(path) wrapper with support for long paths on Windows.
  210. Availability: Unix, Windows."""
  211. onerror = None
  212. if isWindows():
  213. path = _makelongpath(path)
  214. onerror = handle_rmtree_error
  215. shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
  216. def handle_rmtree_error(function, path, excinfo):
  217. # Allow deleting read-only files
  218. os.chmod(path, stat.S_IWRITE)
  219. function(path)
  220. def rename(src, dst):
  221. """os.rename(src, dst) wrapper with support for long paths on Windows.
  222. Availability: Unix, Windows."""
  223. if isWindows():
  224. # On Windows, rename fails if destination exists, see
  225. # https://docs.python.org/2/library/os.html#os.rename
  226. try:
  227. os.rename(_makelongpath(src), _makelongpath(dst))
  228. except OSError as e:
  229. if e.errno == errno.EEXIST:
  230. os.remove(_makelongpath(dst))
  231. os.rename(_makelongpath(src), _makelongpath(dst))
  232. else:
  233. raise
  234. else:
  235. os.rename(src, dst)
  236. def remove(path):
  237. """Remove (delete) the file path. This is a replacement for os.remove that
  238. allows deleting read-only files on Windows, with support for long paths and
  239. for deleting directory symbolic links.
  240. Availability: Unix, Windows."""
  241. if isWindows():
  242. longpath = _makelongpath(path)
  243. try:
  244. os.remove(longpath)
  245. except OSError as e:
  246. if e.errno == errno.EACCES:
  247. os.chmod(longpath, stat.S_IWRITE)
  248. # Directory symbolic links must be deleted with 'rmdir'.
  249. if islink(longpath) and isdir(longpath):
  250. os.rmdir(longpath)
  251. else:
  252. os.remove(longpath)
  253. else:
  254. raise
  255. else:
  256. os.remove(path)
  257. def walk(top, topdown=True, onerror=None, followlinks=False):
  258. """os.walk(path) wrapper with support for long paths on Windows.
  259. Availability: Windows, Unix.
  260. """
  261. if isWindows():
  262. return _walk_windows_impl(top, topdown, onerror, followlinks)
  263. else:
  264. return os.walk(top, topdown, onerror, followlinks)
  265. def _walk_windows_impl(top, topdown, onerror, followlinks):
  266. try:
  267. names = listdir(top)
  268. except Exception as err:
  269. if onerror is not None:
  270. onerror(err)
  271. return
  272. dirs, nondirs = [], []
  273. for name in names:
  274. if isdir(os.path.join(top, name)):
  275. dirs.append(name)
  276. else:
  277. nondirs.append(name)
  278. if topdown:
  279. yield top, dirs, nondirs
  280. for name in dirs:
  281. new_path = os.path.join(top, name)
  282. if followlinks or not islink(new_path):
  283. for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
  284. yield x
  285. if not topdown:
  286. yield top, dirs, nondirs
  287. def listdir(path):
  288. """os.listdir(path) wrapper with support for long paths on Windows.
  289. Availability: Windows, Unix.
  290. """
  291. return os.listdir(_makelongpath(path))
  292. def rmdir(path):
  293. """os.rmdir(path) wrapper with support for long paths on Windows.
  294. Availability: Windows, Unix.
  295. """
  296. os.rmdir(_makelongpath(path))
  297. def isdir(path):
  298. """os.path.isdir(path) wrapper with support for long paths on Windows.
  299. Availability: Windows, Unix.
  300. """
  301. return os.path.isdir(_makelongpath(path))
  302. def islink(path):
  303. """os.path.islink(path) wrapper with support for long paths on Windows.
  304. Availability: Windows, Unix.
  305. """
  306. if isWindows():
  307. import platform_utils_win32
  308. return platform_utils_win32.islink(_makelongpath(path))
  309. else:
  310. return os.path.islink(path)
  311. def readlink(path):
  312. """Return a string representing the path to which the symbolic link
  313. points. The result may be either an absolute or relative pathname;
  314. if it is relative, it may be converted to an absolute pathname using
  315. os.path.join(os.path.dirname(path), result).
  316. Availability: Windows, Unix.
  317. """
  318. if isWindows():
  319. import platform_utils_win32
  320. return platform_utils_win32.readlink(_makelongpath(path))
  321. else:
  322. return os.readlink(path)
  323. def realpath(path):
  324. """Return the canonical path of the specified filename, eliminating
  325. any symbolic links encountered in the path.
  326. Availability: Windows, Unix.
  327. """
  328. if isWindows():
  329. current_path = os.path.abspath(path)
  330. path_tail = []
  331. for c in range(0, 100): # Avoid cycles
  332. if islink(current_path):
  333. target = readlink(current_path)
  334. current_path = os.path.join(os.path.dirname(current_path), target)
  335. else:
  336. basename = os.path.basename(current_path)
  337. if basename == '':
  338. path_tail.append(current_path)
  339. break
  340. path_tail.append(basename)
  341. current_path = os.path.dirname(current_path)
  342. path_tail.reverse()
  343. result = os.path.normpath(os.path.join(*path_tail))
  344. return result
  345. else:
  346. return os.path.realpath(path)