utils.py 4.7 KB

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