|
import os |
|
import sys |
|
import stat |
|
import select |
|
import time |
|
import errno |
|
|
|
try: |
|
InterruptedError |
|
except NameError: |
|
|
|
InterruptedError = select.error |
|
|
|
if sys.version_info[0] >= 3: |
|
string_types = (str,) |
|
else: |
|
string_types = (unicode, str) |
|
|
|
|
|
def is_executable_file(path): |
|
"""Checks that path is an executable regular file, or a symlink towards one. |
|
|
|
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``. |
|
""" |
|
|
|
fpath = os.path.realpath(path) |
|
|
|
if not os.path.isfile(fpath): |
|
|
|
return False |
|
|
|
mode = os.stat(fpath).st_mode |
|
|
|
if (sys.platform.startswith('sunos') |
|
and os.getuid() == 0): |
|
|
|
|
|
|
|
|
|
|
|
return bool(mode & (stat.S_IXUSR | |
|
stat.S_IXGRP | |
|
stat.S_IXOTH)) |
|
|
|
return os.access(fpath, os.X_OK) |
|
|
|
|
|
def which(filename, env=None): |
|
'''This takes a given filename; tries to find it in the environment path; |
|
then checks if it is executable. This returns the full path to the filename |
|
if found and executable. Otherwise this returns None.''' |
|
|
|
|
|
if os.path.dirname(filename) != '' and is_executable_file(filename): |
|
return filename |
|
if env is None: |
|
env = os.environ |
|
p = env.get('PATH') |
|
if not p: |
|
p = os.defpath |
|
pathlist = p.split(os.pathsep) |
|
for path in pathlist: |
|
ff = os.path.join(path, filename) |
|
if is_executable_file(ff): |
|
return ff |
|
return None |
|
|
|
|
|
def split_command_line(command_line): |
|
|
|
'''This splits a command line into a list of arguments. It splits arguments |
|
on spaces, but handles embedded quotes, doublequotes, and escaped |
|
characters. It's impossible to do this with a regular expression, so I |
|
wrote a little state machine to parse the command line. ''' |
|
|
|
arg_list = [] |
|
arg = '' |
|
|
|
|
|
state_basic = 0 |
|
state_esc = 1 |
|
state_singlequote = 2 |
|
state_doublequote = 3 |
|
|
|
state_whitespace = 4 |
|
state = state_basic |
|
|
|
for c in command_line: |
|
if state == state_basic or state == state_whitespace: |
|
if c == '\\': |
|
|
|
state = state_esc |
|
elif c == r"'": |
|
|
|
state = state_singlequote |
|
elif c == r'"': |
|
|
|
state = state_doublequote |
|
elif c.isspace(): |
|
|
|
if state == state_whitespace: |
|
|
|
None |
|
else: |
|
arg_list.append(arg) |
|
arg = '' |
|
state = state_whitespace |
|
else: |
|
arg = arg + c |
|
state = state_basic |
|
elif state == state_esc: |
|
arg = arg + c |
|
state = state_basic |
|
elif state == state_singlequote: |
|
if c == r"'": |
|
state = state_basic |
|
else: |
|
arg = arg + c |
|
elif state == state_doublequote: |
|
if c == r'"': |
|
state = state_basic |
|
else: |
|
arg = arg + c |
|
|
|
if arg != '': |
|
arg_list.append(arg) |
|
return arg_list |
|
|
|
|
|
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None): |
|
|
|
'''This is a wrapper around select.select() that ignores signals. If |
|
select.select raises a select.error exception and errno is an EINTR |
|
error then it is ignored. Mainly this is used to ignore sigwinch |
|
(terminal resize). ''' |
|
|
|
|
|
|
|
if timeout is not None: |
|
end_time = time.time() + timeout |
|
while True: |
|
try: |
|
return select.select(iwtd, owtd, ewtd, timeout) |
|
except InterruptedError: |
|
err = sys.exc_info()[1] |
|
if err.args[0] == errno.EINTR: |
|
|
|
|
|
if timeout is not None: |
|
timeout = end_time - time.time() |
|
if timeout < 0: |
|
return([], [], []) |
|
else: |
|
|
|
|
|
raise |
|
|
|
|
|
def poll_ignore_interrupts(fds, timeout=None): |
|
'''Simple wrapper around poll to register file descriptors and |
|
ignore signals.''' |
|
|
|
if timeout is not None: |
|
end_time = time.time() + timeout |
|
|
|
poller = select.poll() |
|
for fd in fds: |
|
poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR) |
|
|
|
while True: |
|
try: |
|
timeout_ms = None if timeout is None else timeout * 1000 |
|
results = poller.poll(timeout_ms) |
|
return [afd for afd, _ in results] |
|
except InterruptedError: |
|
err = sys.exc_info()[1] |
|
if err.args[0] == errno.EINTR: |
|
|
|
|
|
if timeout is not None: |
|
timeout = end_time - time.time() |
|
if timeout < 0: |
|
return [] |
|
else: |
|
|
|
|
|
raise |
|
|