You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.9 KiB

4 years ago
  1. import os
  2. import sys
  3. import stat
  4. import select
  5. import time
  6. import errno
  7. try:
  8. InterruptedError
  9. except NameError:
  10. # Alias Python2 exception to Python3
  11. InterruptedError = select.error
  12. if sys.version_info[0] >= 3:
  13. string_types = (str,)
  14. else:
  15. string_types = (unicode, str)
  16. def is_executable_file(path):
  17. """Checks that path is an executable regular file, or a symlink towards one.
  18. This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``.
  19. """
  20. # follow symlinks,
  21. fpath = os.path.realpath(path)
  22. if not os.path.isfile(fpath):
  23. # non-files (directories, fifo, etc.)
  24. return False
  25. mode = os.stat(fpath).st_mode
  26. if (sys.platform.startswith('sunos')
  27. and os.getuid() == 0):
  28. # When root on Solaris, os.X_OK is True for *all* files, irregardless
  29. # of their executability -- instead, any permission bit of any user,
  30. # group, or other is fine enough.
  31. #
  32. # (This may be true for other "Unix98" OS's such as HP-UX and AIX)
  33. return bool(mode & (stat.S_IXUSR |
  34. stat.S_IXGRP |
  35. stat.S_IXOTH))
  36. return os.access(fpath, os.X_OK)
  37. def which(filename, env=None):
  38. '''This takes a given filename; tries to find it in the environment path;
  39. then checks if it is executable. This returns the full path to the filename
  40. if found and executable. Otherwise this returns None.'''
  41. # Special case where filename contains an explicit path.
  42. if os.path.dirname(filename) != '' and is_executable_file(filename):
  43. return filename
  44. if env is None:
  45. env = os.environ
  46. p = env.get('PATH')
  47. if not p:
  48. p = os.defpath
  49. pathlist = p.split(os.pathsep)
  50. for path in pathlist:
  51. ff = os.path.join(path, filename)
  52. if is_executable_file(ff):
  53. return ff
  54. return None
  55. def split_command_line(command_line):
  56. '''This splits a command line into a list of arguments. It splits arguments
  57. on spaces, but handles embedded quotes, doublequotes, and escaped
  58. characters. It's impossible to do this with a regular expression, so I
  59. wrote a little state machine to parse the command line. '''
  60. arg_list = []
  61. arg = ''
  62. # Constants to name the states we can be in.
  63. state_basic = 0
  64. state_esc = 1
  65. state_singlequote = 2
  66. state_doublequote = 3
  67. # The state when consuming whitespace between commands.
  68. state_whitespace = 4
  69. state = state_basic
  70. for c in command_line:
  71. if state == state_basic or state == state_whitespace:
  72. if c == '\\':
  73. # Escape the next character
  74. state = state_esc
  75. elif c == r"'":
  76. # Handle single quote
  77. state = state_singlequote
  78. elif c == r'"':
  79. # Handle double quote
  80. state = state_doublequote
  81. elif c.isspace():
  82. # Add arg to arg_list if we aren't in the middle of whitespace.
  83. if state == state_whitespace:
  84. # Do nothing.
  85. None
  86. else:
  87. arg_list.append(arg)
  88. arg = ''
  89. state = state_whitespace
  90. else:
  91. arg = arg + c
  92. state = state_basic
  93. elif state == state_esc:
  94. arg = arg + c
  95. state = state_basic
  96. elif state == state_singlequote:
  97. if c == r"'":
  98. state = state_basic
  99. else:
  100. arg = arg + c
  101. elif state == state_doublequote:
  102. if c == r'"':
  103. state = state_basic
  104. else:
  105. arg = arg + c
  106. if arg != '':
  107. arg_list.append(arg)
  108. return arg_list
  109. def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
  110. '''This is a wrapper around select.select() that ignores signals. If
  111. select.select raises a select.error exception and errno is an EINTR
  112. error then it is ignored. Mainly this is used to ignore sigwinch
  113. (terminal resize). '''
  114. # if select() is interrupted by a signal (errno==EINTR) then
  115. # we loop back and enter the select() again.
  116. if timeout is not None:
  117. end_time = time.time() + timeout
  118. while True:
  119. try:
  120. return select.select(iwtd, owtd, ewtd, timeout)
  121. except InterruptedError:
  122. err = sys.exc_info()[1]
  123. if err.args[0] == errno.EINTR:
  124. # if we loop back we have to subtract the
  125. # amount of time we already waited.
  126. if timeout is not None:
  127. timeout = end_time - time.time()
  128. if timeout < 0:
  129. return([], [], [])
  130. else:
  131. # something else caused the select.error, so
  132. # this actually is an exception.
  133. raise
  134. def poll_ignore_interrupts(fds, timeout=None):
  135. '''Simple wrapper around poll to register file descriptors and
  136. ignore signals.'''
  137. if timeout is not None:
  138. end_time = time.time() + timeout
  139. poller = select.poll()
  140. for fd in fds:
  141. poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
  142. while True:
  143. try:
  144. timeout_ms = None if timeout is None else timeout * 1000
  145. results = poller.poll(timeout_ms)
  146. return [afd for afd, _ in results]
  147. except InterruptedError:
  148. err = sys.exc_info()[1]
  149. if err.args[0] == errno.EINTR:
  150. # if we loop back we have to subtract the
  151. # amount of time we already waited.
  152. if timeout is not None:
  153. timeout = end_time - time.time()
  154. if timeout < 0:
  155. return []
  156. else:
  157. # something else caused the select.error, so
  158. # this actually is an exception.
  159. raise