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