platform_utils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. # -*- coding:utf-8 -*-
  2. #
  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. import errno
  17. import os
  18. import platform
  19. import select
  20. import shutil
  21. import stat
  22. from pyversion import is_python3
  23. if is_python3():
  24. from queue import Queue
  25. else:
  26. from Queue import Queue
  27. from threading import Thread
  28. def isWindows():
  29. """ Returns True when running with the native port of Python for Windows,
  30. False when running on any other platform (including the Cygwin port of
  31. Python).
  32. """
  33. # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
  34. return platform.system() == "Windows"
  35. class FileDescriptorStreams(object):
  36. """ Platform agnostic abstraction enabling non-blocking I/O over a
  37. collection of file descriptors. This abstraction is required because
  38. fctnl(os.O_NONBLOCK) is not supported on Windows.
  39. """
  40. @classmethod
  41. def create(cls):
  42. """ Factory method: instantiates the concrete class according to the
  43. current platform.
  44. """
  45. if isWindows():
  46. return _FileDescriptorStreamsThreads()
  47. else:
  48. return _FileDescriptorStreamsNonBlocking()
  49. def __init__(self):
  50. self.streams = []
  51. def add(self, fd, dest, std_name):
  52. """ Wraps an existing file descriptor as a stream.
  53. """
  54. self.streams.append(self._create_stream(fd, dest, std_name))
  55. def remove(self, stream):
  56. """ Removes a stream, when done with it.
  57. """
  58. self.streams.remove(stream)
  59. @property
  60. def is_done(self):
  61. """ Returns True when all streams have been processed.
  62. """
  63. return len(self.streams) == 0
  64. def select(self):
  65. """ Returns the set of streams that have data available to read.
  66. The returned streams each expose a read() and a close() method.
  67. When done with a stream, call the remove(stream) method.
  68. """
  69. raise NotImplementedError
  70. def _create_stream(fd, dest, std_name):
  71. """ Creates a new stream wrapping an existing file descriptor.
  72. """
  73. raise NotImplementedError
  74. class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
  75. """ Implementation of FileDescriptorStreams for platforms that support
  76. non blocking I/O.
  77. """
  78. def __init__(self):
  79. super(_FileDescriptorStreamsNonBlocking, self).__init__()
  80. self._poll = select.poll()
  81. self._fd_to_stream = {}
  82. class Stream(object):
  83. """ Encapsulates a file descriptor """
  84. def __init__(self, fd, dest, std_name):
  85. self.fd = fd
  86. self.dest = dest
  87. self.std_name = std_name
  88. self.set_non_blocking()
  89. def set_non_blocking(self):
  90. import fcntl
  91. flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
  92. fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
  93. def fileno(self):
  94. return self.fd.fileno()
  95. def read(self):
  96. return self.fd.read(4096)
  97. def close(self):
  98. self.fd.close()
  99. def _create_stream(self, fd, dest, std_name):
  100. stream = self.Stream(fd, dest, std_name)
  101. self._fd_to_stream[stream.fileno()] = stream
  102. self._poll.register(stream, select.POLLIN)
  103. return stream
  104. def remove(self, stream):
  105. self._poll.unregister(stream)
  106. del self._fd_to_stream[stream.fileno()]
  107. super(_FileDescriptorStreamsNonBlocking, self).remove(stream)
  108. def select(self):
  109. return [self._fd_to_stream[fd] for fd, _ in self._poll.poll()]
  110. class _FileDescriptorStreamsThreads(FileDescriptorStreams):
  111. """ Implementation of FileDescriptorStreams for platforms that don't support
  112. non blocking I/O. This implementation requires creating threads issuing
  113. blocking read operations on file descriptors.
  114. """
  115. def __init__(self):
  116. super(_FileDescriptorStreamsThreads, self).__init__()
  117. # The queue is shared accross all threads so we can simulate the
  118. # behavior of the select() function
  119. self.queue = Queue(10) # Limit incoming data from streams
  120. def _create_stream(self, fd, dest, std_name):
  121. return self.Stream(fd, dest, std_name, self.queue)
  122. def select(self):
  123. # Return only one stream at a time, as it is the most straighforward
  124. # thing to do and it is compatible with the select() function.
  125. item = self.queue.get()
  126. stream = item.stream
  127. stream.data = item.data
  128. return [stream]
  129. class QueueItem(object):
  130. """ Item put in the shared queue """
  131. def __init__(self, stream, data):
  132. self.stream = stream
  133. self.data = data
  134. class Stream(object):
  135. """ Encapsulates a file descriptor """
  136. def __init__(self, fd, dest, std_name, queue):
  137. self.fd = fd
  138. self.dest = dest
  139. self.std_name = std_name
  140. self.queue = queue
  141. self.data = None
  142. self.thread = Thread(target=self.read_to_queue)
  143. self.thread.daemon = True
  144. self.thread.start()
  145. def close(self):
  146. self.fd.close()
  147. def read(self):
  148. data = self.data
  149. self.data = None
  150. return data
  151. def read_to_queue(self):
  152. """ The thread function: reads everything from the file descriptor into
  153. the shared queue and terminates when reaching EOF.
  154. """
  155. for line in iter(self.fd.readline, b''):
  156. self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
  157. self.fd.close()
  158. self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None))
  159. def symlink(source, link_name):
  160. """Creates a symbolic link pointing to source named link_name.
  161. Note: On Windows, source must exist on disk, as the implementation needs
  162. to know whether to create a "File" or a "Directory" symbolic link.
  163. """
  164. if isWindows():
  165. import platform_utils_win32
  166. source = _validate_winpath(source)
  167. link_name = _validate_winpath(link_name)
  168. target = os.path.join(os.path.dirname(link_name), source)
  169. if isdir(target):
  170. platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
  171. else:
  172. platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
  173. else:
  174. return os.symlink(source, link_name)
  175. def _validate_winpath(path):
  176. path = os.path.normpath(path)
  177. if _winpath_is_valid(path):
  178. return path
  179. raise ValueError("Path \"%s\" must be a relative path or an absolute "
  180. "path starting with a drive letter".format(path))
  181. def _winpath_is_valid(path):
  182. """Windows only: returns True if path is relative (e.g. ".\\foo") or is
  183. absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
  184. is ambiguous (e.g. "x:foo" or "\\foo").
  185. """
  186. assert isWindows()
  187. path = os.path.normpath(path)
  188. drive, tail = os.path.splitdrive(path)
  189. if tail:
  190. if not drive:
  191. return tail[0] != os.sep # "\\foo" is invalid
  192. else:
  193. return tail[0] == os.sep # "x:foo" is invalid
  194. else:
  195. return not drive # "x:" is invalid
  196. def _makelongpath(path):
  197. """Return the input path normalized to support the Windows long path syntax
  198. ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
  199. MAX_PATH limit.
  200. """
  201. if isWindows():
  202. # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
  203. if len(path) < 246:
  204. return path
  205. if path.startswith(u"\\\\?\\"):
  206. return path
  207. if not os.path.isabs(path):
  208. return path
  209. # Append prefix and ensure unicode so that the special longpath syntax
  210. # is supported by underlying Win32 API calls
  211. return u"\\\\?\\" + os.path.normpath(path)
  212. else:
  213. return path
  214. def rmtree(path, ignore_errors=False):
  215. """shutil.rmtree(path) wrapper with support for long paths on Windows.
  216. Availability: Unix, Windows."""
  217. onerror = None
  218. if isWindows():
  219. path = _makelongpath(path)
  220. onerror = handle_rmtree_error
  221. shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
  222. def handle_rmtree_error(function, path, excinfo):
  223. # Allow deleting read-only files
  224. os.chmod(path, stat.S_IWRITE)
  225. function(path)
  226. def rename(src, dst):
  227. """os.rename(src, dst) wrapper with support for long paths on Windows.
  228. Availability: Unix, Windows."""
  229. if isWindows():
  230. # On Windows, rename fails if destination exists, see
  231. # https://docs.python.org/2/library/os.html#os.rename
  232. try:
  233. os.rename(_makelongpath(src), _makelongpath(dst))
  234. except OSError as e:
  235. if e.errno == errno.EEXIST:
  236. os.remove(_makelongpath(dst))
  237. os.rename(_makelongpath(src), _makelongpath(dst))
  238. else:
  239. raise
  240. else:
  241. os.rename(src, dst)
  242. def remove(path):
  243. """Remove (delete) the file path. This is a replacement for os.remove that
  244. allows deleting read-only files on Windows, with support for long paths and
  245. for deleting directory symbolic links.
  246. Availability: Unix, Windows."""
  247. if isWindows():
  248. longpath = _makelongpath(path)
  249. try:
  250. os.remove(longpath)
  251. except OSError as e:
  252. if e.errno == errno.EACCES:
  253. os.chmod(longpath, stat.S_IWRITE)
  254. # Directory symbolic links must be deleted with 'rmdir'.
  255. if islink(longpath) and isdir(longpath):
  256. os.rmdir(longpath)
  257. else:
  258. os.remove(longpath)
  259. else:
  260. raise
  261. else:
  262. os.remove(path)
  263. def walk(top, topdown=True, onerror=None, followlinks=False):
  264. """os.walk(path) wrapper with support for long paths on Windows.
  265. Availability: Windows, Unix.
  266. """
  267. if isWindows():
  268. return _walk_windows_impl(top, topdown, onerror, followlinks)
  269. else:
  270. return os.walk(top, topdown, onerror, followlinks)
  271. def _walk_windows_impl(top, topdown, onerror, followlinks):
  272. try:
  273. names = listdir(top)
  274. except Exception as err:
  275. if onerror is not None:
  276. onerror(err)
  277. return
  278. dirs, nondirs = [], []
  279. for name in names:
  280. if isdir(os.path.join(top, name)):
  281. dirs.append(name)
  282. else:
  283. nondirs.append(name)
  284. if topdown:
  285. yield top, dirs, nondirs
  286. for name in dirs:
  287. new_path = os.path.join(top, name)
  288. if followlinks or not islink(new_path):
  289. for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
  290. yield x
  291. if not topdown:
  292. yield top, dirs, nondirs
  293. def listdir(path):
  294. """os.listdir(path) wrapper with support for long paths on Windows.
  295. Availability: Windows, Unix.
  296. """
  297. return os.listdir(_makelongpath(path))
  298. def rmdir(path):
  299. """os.rmdir(path) wrapper with support for long paths on Windows.
  300. Availability: Windows, Unix.
  301. """
  302. os.rmdir(_makelongpath(path))
  303. def isdir(path):
  304. """os.path.isdir(path) wrapper with support for long paths on Windows.
  305. Availability: Windows, Unix.
  306. """
  307. return os.path.isdir(_makelongpath(path))
  308. def islink(path):
  309. """os.path.islink(path) wrapper with support for long paths on Windows.
  310. Availability: Windows, Unix.
  311. """
  312. if isWindows():
  313. import platform_utils_win32
  314. return platform_utils_win32.islink(_makelongpath(path))
  315. else:
  316. return os.path.islink(path)
  317. def readlink(path):
  318. """Return a string representing the path to which the symbolic link
  319. points. The result may be either an absolute or relative pathname;
  320. if it is relative, it may be converted to an absolute pathname using
  321. os.path.join(os.path.dirname(path), result).
  322. Availability: Windows, Unix.
  323. """
  324. if isWindows():
  325. import platform_utils_win32
  326. return platform_utils_win32.readlink(_makelongpath(path))
  327. else:
  328. return os.readlink(path)
  329. def realpath(path):
  330. """Return the canonical path of the specified filename, eliminating
  331. any symbolic links encountered in the path.
  332. Availability: Windows, Unix.
  333. """
  334. if isWindows():
  335. current_path = os.path.abspath(path)
  336. path_tail = []
  337. for c in range(0, 100): # Avoid cycles
  338. if islink(current_path):
  339. target = readlink(current_path)
  340. current_path = os.path.join(os.path.dirname(current_path), target)
  341. else:
  342. basename = os.path.basename(current_path)
  343. if basename == '':
  344. path_tail.append(current_path)
  345. break
  346. path_tail.append(basename)
  347. current_path = os.path.dirname(current_path)
  348. path_tail.reverse()
  349. result = os.path.normpath(os.path.join(*path_tail))
  350. return result
  351. else:
  352. return os.path.realpath(path)