mirror of
https://github.com/Rudd-O/ansible-qubes.git
synced 2025-03-01 14:22:33 +01:00
bombshell-client updated to work with Python 3 and to have better logging
This commit is contained in:
parent
a6def74206
commit
3e1674801a
@ -1,33 +1,110 @@
|
||||
#!/usr/bin/python -u
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
import cPickle
|
||||
# Security note:
|
||||
#
|
||||
# If you look at cPickle usage in bombshell, it's only used to package up
|
||||
# the command line at the initiator side, and then it is unpacked at the
|
||||
# receiver side. Given that the initiator has already been given all
|
||||
# permissions to run arbitrary programs on the receiver, there is no
|
||||
# additional security risk posed by the use of cPickle.
|
||||
#
|
||||
# End security note.
|
||||
import pickle
|
||||
import contextlib
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import pipes
|
||||
import Queue
|
||||
import queue
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import syslog
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
MAX_MUX_READ = 128*1024 # 64*1024*1024
|
||||
PACKLEN = 8
|
||||
PACKFORMAT = "!HbIx"
|
||||
|
||||
# From bits/fcntl.h
|
||||
# Values for 'flags', can be OR'ed together
|
||||
SPLICE_F_MOVE = 1
|
||||
SPLICE_F_NONBLOCK = 2
|
||||
SPLICE_F_MORE = 4
|
||||
SPLICE_F_GIFT = 8
|
||||
|
||||
def make_splice():
|
||||
'''Set up a splice(2) wrapper'''
|
||||
|
||||
# Load libc
|
||||
libc_name = ctypes.util.find_library('c')
|
||||
libc = ctypes.CDLL(libc_name, use_errno=True)
|
||||
|
||||
# Get a handle to the 'splice' call
|
||||
c_splice = libc.splice
|
||||
|
||||
# These should match for x86_64, might need some tweaking for other
|
||||
# platforms...
|
||||
c_loff_t = ctypes.c_uint64
|
||||
c_loff_t_p = ctypes.POINTER(c_loff_t)
|
||||
|
||||
# ssize_t splice(int fd_in, loff_t *off_in, int fd_out,
|
||||
# loff_t *off_out, size_t len, unsigned int flags)
|
||||
c_splice.argtypes = [
|
||||
ctypes.c_int, c_loff_t_p,
|
||||
ctypes.c_int, c_loff_t_p,
|
||||
ctypes.c_size_t,
|
||||
ctypes.c_uint
|
||||
]
|
||||
c_splice.restype = ctypes.c_ssize_t
|
||||
|
||||
# Clean-up closure names. Yup, useless nit-picking.
|
||||
del libc
|
||||
del libc_name
|
||||
del c_loff_t_p
|
||||
|
||||
# pylint: disable-msg=W0621,R0913
|
||||
def splice(fd_in, off_in, fd_out, off_out, len_, flags):
|
||||
'''Wrapper for splice(2)
|
||||
See the syscall documentation ('man 2 splice') for more information
|
||||
about the arguments and return value.
|
||||
`off_in` and `off_out` can be `None`, which is equivalent to `NULL`.
|
||||
If the call to `splice` fails (i.e. returns -1), an `OSError` is raised
|
||||
with the appropriate `errno`, unless the error is `EINTR`, which results
|
||||
in the call to be retried.
|
||||
'''
|
||||
|
||||
c_off_in = \
|
||||
ctypes.byref(c_loff_t(off_in)) if off_in is not None else None
|
||||
c_off_out = \
|
||||
ctypes.byref(c_loff_t(off_out)) if off_out is not None else None
|
||||
|
||||
# For handling EINTR...
|
||||
while True:
|
||||
res = c_splice(fd_in, c_off_in, fd_out, c_off_out, len_, flags)
|
||||
|
||||
if res == -1:
|
||||
errno_ = ctypes.get_errno()
|
||||
|
||||
# Try again on EINTR
|
||||
if errno_ == errno.EINTR:
|
||||
continue
|
||||
|
||||
raise IOError(errno_, os.strerror(errno_))
|
||||
|
||||
return res
|
||||
|
||||
return splice
|
||||
|
||||
|
||||
# Build and export wrapper
|
||||
splice = make_splice() #pylint: disable-msg=C0103
|
||||
del make_splice
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mutexfile(filepath):
|
||||
oldumask = os.umask(0077)
|
||||
oldumask = os.umask(0o077)
|
||||
try:
|
||||
f = file(filepath, "a")
|
||||
f = open(filepath, "a")
|
||||
finally:
|
||||
os.umask(oldumask)
|
||||
fcntl.lockf(f.fileno(), fcntl.LOCK_EX)
|
||||
@ -35,30 +112,50 @@ def mutexfile(filepath):
|
||||
f.close()
|
||||
|
||||
|
||||
def openfdforappend(fd):
|
||||
try:
|
||||
return os.fdopen(fd, "ab", 0, closefd=False)
|
||||
except IOError as e:
|
||||
if e.errno != errno.ESPIPE:
|
||||
raise
|
||||
return os.fdopen(fd, "wb", 0, closefd=False)
|
||||
|
||||
|
||||
def openfdforread(fd):
|
||||
return os.fdopen(fd, "rb", 0, closefd=False)
|
||||
|
||||
|
||||
debug_lock = threading.Lock()
|
||||
debug_enabled = False
|
||||
_startt = time.time()
|
||||
class LoggingEmu():
|
||||
def __init__(self, prefix):
|
||||
self.prefix = prefix
|
||||
syslog.openlog("bombshell-client.%s" % self.prefix)
|
||||
def debug(self, *a, **kw):
|
||||
if not debug_enabled:
|
||||
return
|
||||
self._print(*a, **kw)
|
||||
for p in ("copy", "write", "Unblocking"):
|
||||
if a[0].startswith(p):
|
||||
return
|
||||
self._print(syslog.LOG_DEBUG, *a, **kw)
|
||||
def info(self, *a, **kw):
|
||||
self._print(*a, **kw)
|
||||
self._print(syslog.LOG_INFO, *a, **kw)
|
||||
def error(self, *a, **kw):
|
||||
self._print(*a, **kw)
|
||||
def _print(self, *a, **kw):
|
||||
self._print(syslog.LOG_ERR, *a, **kw)
|
||||
def _print(self, prio, *a, **kw):
|
||||
debug_lock.acquire()
|
||||
global _startt
|
||||
deltat = time.time() - _startt
|
||||
try:
|
||||
if len(a) == 1:
|
||||
string = a[0]
|
||||
else:
|
||||
string = a[0] % a[1:]
|
||||
print >> sys.stderr, self.prefix, string
|
||||
syslog.syslog(prio, ("%.3f " % deltat) + string)
|
||||
finally:
|
||||
debug_lock.release()
|
||||
logging = LoggingEmu("master:" if "__file__" in globals() else "remote:")
|
||||
logging = None
|
||||
|
||||
|
||||
def send_command(chan, cmd):
|
||||
@ -68,7 +165,7 @@ def send_command(chan, cmd):
|
||||
chan: writable file-like object to send the command to
|
||||
cmd: command to send, iterable of strings
|
||||
"""
|
||||
pickled = cPickle.dumps(cmd)
|
||||
pickled = pickle.dumps(cmd)
|
||||
l = len(pickled)
|
||||
assert l < 1<<32, l
|
||||
chan.write(struct.pack("!I", l))
|
||||
@ -80,11 +177,26 @@ def send_command(chan, cmd):
|
||||
def recv_command(chan):
|
||||
l = struct.unpack("!I", sys.stdin.read(4))[0]
|
||||
pickled = chan.read(l)
|
||||
cmd = cPickle.loads(pickled)
|
||||
cmd = pickle.loads(pickled)
|
||||
logging.debug("Received command: %s", cmd)
|
||||
return cmd
|
||||
|
||||
|
||||
def send_beacon(chan):
|
||||
l = chan.write(b"1")
|
||||
if l != 1:
|
||||
raise Exception("Beacon could not be sent")
|
||||
chan.flush()
|
||||
logging.debug("Sent beacon")
|
||||
|
||||
|
||||
def recv_beacon(chan):
|
||||
m = chan.read(1)
|
||||
if not m or len(m) != 1:
|
||||
raise Exception("Beacon never received")
|
||||
logging.debug("Received beacon")
|
||||
|
||||
|
||||
def send_confirmation(chan, retval, errmsg):
|
||||
chan.write(struct.pack("!H", retval))
|
||||
l = len(errmsg)
|
||||
@ -92,11 +204,11 @@ def send_confirmation(chan, retval, errmsg):
|
||||
chan.write(struct.pack("!I", l))
|
||||
chan.write(errmsg)
|
||||
chan.flush()
|
||||
logging.debug("Sent confirmation: %s %s", retval, errmsg)
|
||||
logging.debug("Sent confirmation on channel %s: %s %s", chan, retval, errmsg)
|
||||
|
||||
|
||||
def recv_confirmation(chan):
|
||||
logging.debug("Waiting for confirmation")
|
||||
logging.debug("Waiting for confirmation on channel %s", chan)
|
||||
r = chan.read(2)
|
||||
if len(r) == 0:
|
||||
# This happens when the remote domain does not exist.
|
||||
@ -113,12 +225,26 @@ def recv_confirmation(chan):
|
||||
return r, errmsg
|
||||
|
||||
|
||||
class SignalSender(threading.Thread):
|
||||
class MyThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._run()
|
||||
except Exception as e:
|
||||
logging.error("%s: unexpected exception", threading.currentThread())
|
||||
tb = traceback.format_exc()
|
||||
logging.error("%s: traceback: %s", threading.currentThread(), tb)
|
||||
logging.error("%s: exiting program", threading.currentThread())
|
||||
os._exit(16)
|
||||
|
||||
|
||||
class SignalSender(MyThread):
|
||||
|
||||
def __init__(self, signals, sigqueue):
|
||||
"""Handles signals by pushing them into a file-like object."""
|
||||
threading.Thread.__init__(self)
|
||||
self.setDaemon(True)
|
||||
self.queue = Queue.Queue()
|
||||
self.queue = queue.Queue()
|
||||
self.sigqueue = sigqueue
|
||||
for sig in signals:
|
||||
signal.signal(sig, self.copy)
|
||||
@ -139,7 +265,7 @@ class SignalSender(threading.Thread):
|
||||
logging.debug("Wrote signal %s to remote end", signum)
|
||||
|
||||
|
||||
class Signaler(threading.Thread):
|
||||
class Signaler(MyThread):
|
||||
|
||||
def __init__(self, process, sigqueue):
|
||||
"""Reads integers from a file-like object and relays that as kill()."""
|
||||
@ -156,121 +282,153 @@ class Signaler(threading.Thread):
|
||||
break
|
||||
assert len(data) == 2
|
||||
signum = struct.unpack("!H", data)[0]
|
||||
logging.debug("Received relayed signal %s, sending to process", signum)
|
||||
logging.debug("Received relayed signal %s, sending to process %s", signum, self.process.pid)
|
||||
self.process.send_signal(signum)
|
||||
logging.debug("End of signaler")
|
||||
|
||||
|
||||
def unblock(fobj):
|
||||
fl = fcntl.fcntl(fobj, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fobj, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
logging.debug("Unblocking file object %s", fobj)
|
||||
os.set_blocking(fobj.fileno(), False)
|
||||
|
||||
|
||||
class DataMultiplexer(threading.Thread):
|
||||
def write(dst, buffer, l):
|
||||
alreadywritten = 0
|
||||
mv = memoryview(buffer)[:l]
|
||||
while len(mv):
|
||||
writtenthisloop = dst.write(mv)
|
||||
if writtenthisloop is None or writtenthisloop < 1:
|
||||
raise Exception("copy: Failed to write any bytes")
|
||||
mv = mv[writtenthisloop:]
|
||||
alreadywritten = alreadywritten + writtenthisloop
|
||||
logging.debug("write: Relayed %s bytes to sink %s", alreadywritten, dst)
|
||||
|
||||
|
||||
def copy(src, dst, buffer, l):
|
||||
alreadyread = 0
|
||||
mv = memoryview(buffer)[:l]
|
||||
assert len(mv) == l, "Buffer object is too small: %s %s" % (len(mv), l)
|
||||
while len(mv):
|
||||
_, _, _ = select.select([src], (), ())
|
||||
readthisloop = src.readinto(mv)
|
||||
if readthisloop is None or readthisloop < 1:
|
||||
raise Exception("copy: Failed to read any bytes")
|
||||
mv = mv[readthisloop:]
|
||||
alreadyread = alreadyread + readthisloop
|
||||
logging.debug("copy: Read %s bytes from %s", alreadyread, src)
|
||||
return write(dst, buffer, l)
|
||||
|
||||
|
||||
class DataMultiplexer(MyThread):
|
||||
|
||||
def __init__(self, sources, sink):
|
||||
threading.Thread.__init__(self)
|
||||
self.setDaemon(True)
|
||||
self.sources = dict((s,num) for num, s in enumerate(sources))
|
||||
self.sink = sink
|
||||
for s in sources: unblock(s)
|
||||
|
||||
def run(self):
|
||||
map(unblock, (s for s in self.sources))
|
||||
def _run(self):
|
||||
logging.debug("mux: Started with sources %s and sink %s", self.sources, self.sink)
|
||||
buffer = bytearray(MAX_MUX_READ)
|
||||
sources, _, x = select.select((s for s in self.sources), (), (s for s in self.sources))
|
||||
assert not x, x
|
||||
while sources:
|
||||
logging.debug("mux: Sources that alarmed: %s", [self.sources[s] for s in sources])
|
||||
for s in sources:
|
||||
n = self.sources[s]
|
||||
data = s.read()
|
||||
if data == "":
|
||||
logging.debug("Received no bytes from source %s", n)
|
||||
logging.debug("mux: Source %s (%s) is active", n, s)
|
||||
readthisloop = s.readinto(buffer)
|
||||
if readthisloop == 0:
|
||||
logging.debug("mux: Received no bytes from source %s", n)
|
||||
del self.sources[s]
|
||||
self.sink.write(struct.pack("!H", n))
|
||||
self.sink.write(struct.pack("b", False))
|
||||
self.sink.flush()
|
||||
logging.debug("Informed sink about death of source %s", n)
|
||||
header = struct.pack(PACKFORMAT, n, False, 0)
|
||||
logging.debug("mux: Sending packet: %s" % header)
|
||||
self.sink.write(header)
|
||||
logging.debug("mux: Informed sink about death of source %s", n)
|
||||
continue
|
||||
l = len(data)
|
||||
logging.debug("Received %s bytes from source %s", l, n)
|
||||
assert l < 1<<32
|
||||
self.sink.write(struct.pack("!H", n))
|
||||
self.sink.write(struct.pack("b", True))
|
||||
self.sink.write(struct.pack("!I", l))
|
||||
self.sink.write(data)
|
||||
self.sink.flush()
|
||||
logging.debug("Copied those %s bytes to sink", l)
|
||||
l = readthisloop
|
||||
logging.debug("mux: Received %s bytes from source %s", l, n)
|
||||
header = struct.pack(PACKFORMAT, n, True, l)
|
||||
logging.debug("mux: Sending packet: %s" % header)
|
||||
self.sink.write(header)
|
||||
write(self.sink, buffer, l)
|
||||
if not self.sources:
|
||||
break
|
||||
sources, _, _ = select.select((s for s in self.sources), (), (s for s in self.sources))
|
||||
sources, _, x = select.select((s for s in self.sources), (), (s for s in self.sources))
|
||||
assert not x, x
|
||||
logging.debug("End of data multiplexer")
|
||||
logging.debug("mux: End of data multiplexer")
|
||||
|
||||
|
||||
class DataDemultiplexer(threading.Thread):
|
||||
class DataDemultiplexer(MyThread):
|
||||
|
||||
def __init__(self, source, sinks):
|
||||
threading.Thread.__init__(self)
|
||||
self.setDaemon(True)
|
||||
self.sinks = dict(enumerate(sinks))
|
||||
self.source = source
|
||||
unblock(source)
|
||||
|
||||
def run(self):
|
||||
logging.debug("demux: Started with source %s and sinks %s", self.source, self.sinks)
|
||||
buffer = bytearray(MAX_MUX_READ)
|
||||
while self.sinks:
|
||||
r, _, x = select.select([self.source], (), [self.source])
|
||||
assert not x, x
|
||||
logging.debug("demux: Source alarmed")
|
||||
for s in r:
|
||||
n = s.read(2)
|
||||
if n == "":
|
||||
logging.debug("Received no bytes from source, closing all sinks")
|
||||
logging.debug("demux: Source %s is active", s)
|
||||
header = s.read(PACKLEN)
|
||||
logging.debug("demux: received packet: %s" % header)
|
||||
if header == "":
|
||||
logging.debug("demux: Received no bytes from source, closing all sinks")
|
||||
for sink in self.sinks.values():
|
||||
sink.close()
|
||||
self.sinks = []
|
||||
break
|
||||
assert len(n) == 2, data
|
||||
n = struct.unpack("!H", n)[0]
|
||||
|
||||
active = s.read(1)
|
||||
assert len(active) == 1, active
|
||||
active = struct.unpack("b", active)[0]
|
||||
|
||||
n, active, l = struct.unpack(PACKFORMAT, header)
|
||||
if not active:
|
||||
logging.debug("Source %s now inactive, closing corresponding sink", n)
|
||||
logging.debug("demux: Source %s now inactive, closing corresponding sink %s", s, self.sinks[n])
|
||||
self.sinks[n].close()
|
||||
del self.sinks[n]
|
||||
else:
|
||||
l = s.read(4)
|
||||
assert len(l) == 4, l
|
||||
l = struct.unpack("!I", l)[0]
|
||||
data = s.read(l)
|
||||
assert len(data) == l, len(data)
|
||||
logging.debug("Received %s bytes from source %s, relaying to corresponding sink", l, n)
|
||||
self.sinks[n].write(data)
|
||||
self.sinks[n].flush()
|
||||
logging.debug("Relayed %s bytes to sink %s", l, n)
|
||||
logging.debug("End of data demultiplexer")
|
||||
logging.debug("demux: Source %s is sending %s bytes, relaying to corresponding sink", s, l)
|
||||
copy(self.source, self.sinks[n], buffer, l)
|
||||
logging.debug("demux: End of data demultiplexer")
|
||||
|
||||
|
||||
def main_master():
|
||||
global logging
|
||||
logging = LoggingEmu("master")
|
||||
|
||||
global debug_enabled
|
||||
args = sys.argv[1:]
|
||||
if args[0] == "-d":
|
||||
args = args[1:]
|
||||
debug_enabled = True
|
||||
|
||||
logging.info("Started with arguments: %s", sys.argv[1:])
|
||||
|
||||
remote_vm = args[0]
|
||||
remote_command = args[1:]
|
||||
assert remote_command
|
||||
|
||||
remote_helper_text = "\n".join([
|
||||
"exec python -u -c '",
|
||||
open(__file__, "rb").read().replace("'", "'\\''"),
|
||||
"'" + (" -d" if debug_enabled else ""),
|
||||
"",
|
||||
# if debug_enabled:
|
||||
# remote_helper_text = b"""
|
||||
#f=$(mktemp)
|
||||
#echo '%s' > "$f"
|
||||
#chmod +x "$f"
|
||||
#exec "$f" -s -d
|
||||
#""" % (open(__file__, "rb").read().replace(b"'", b"'\\''"))
|
||||
# else:
|
||||
remote_helper_text = b"\n".join([
|
||||
b"exec %s -u -c '" % bytes(sys.executable, "utf-8"),
|
||||
open(__file__, "rb").read().replace(b"'", b"'\\''"),
|
||||
b"'" + (b" -d" if debug_enabled else b""),
|
||||
b"",
|
||||
])
|
||||
|
||||
saved_stderr = os.fdopen(os.dup(sys.stderr.fileno()), "a")
|
||||
saved_stderr = openfdforappend(os.dup(sys.stderr.fileno()))
|
||||
|
||||
with mutexfile(os.path.expanduser("~/.bombshell-lock")):
|
||||
try:
|
||||
@ -280,14 +438,23 @@ def main_master():
|
||||
stdout=subprocess.PIPE,
|
||||
close_fds=True,
|
||||
preexec_fn=os.setpgrp,
|
||||
bufsize=0,
|
||||
)
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
logging.error("cannot launch qrexec-client-vm: %s", e)
|
||||
return 127
|
||||
|
||||
logging.debug("Writing the helper text into the other side")
|
||||
p.stdin.write(remote_helper_text)
|
||||
p.stdin.flush()
|
||||
|
||||
try:
|
||||
recv_beacon(p.stdout)
|
||||
except Exception as e:
|
||||
logging.error("%s", e)
|
||||
p.kill()
|
||||
return 124
|
||||
|
||||
send_command(p.stdin, remote_command)
|
||||
confirmation, errmsg = recv_confirmation(p.stdout)
|
||||
if confirmation != 0:
|
||||
@ -306,29 +473,46 @@ def main_master():
|
||||
)
|
||||
read_signals, write_signals = pairofpipes()
|
||||
signaler = SignalSender(handled_signals, write_signals)
|
||||
signaler.setName("master signaler")
|
||||
signaler.start()
|
||||
|
||||
muxer = DataMultiplexer([sys.stdin, read_signals], p.stdin)
|
||||
muxer.setName("master multiplexer")
|
||||
muxer.start()
|
||||
|
||||
demuxer = DataDemultiplexer(p.stdout, [sys.stdout, saved_stderr])
|
||||
demuxer.setName("master demultiplexer")
|
||||
demuxer.start()
|
||||
|
||||
retval = p.wait()
|
||||
logging.info("Return code %s for qubes.VMShell proxy", retval)
|
||||
demuxer.join()
|
||||
logging.info("Ending bombshell")
|
||||
return retval
|
||||
|
||||
|
||||
def pairofpipes():
|
||||
read, write = os.pipe()
|
||||
return os.fdopen(read, "rb"), os.fdopen(write, "wb")
|
||||
return os.fdopen(read, "rb", 0), os.fdopen(write, "wb", 0)
|
||||
|
||||
|
||||
def main_remote():
|
||||
global logging
|
||||
logging = LoggingEmu("remote")
|
||||
|
||||
global debug_enabled
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "-d":
|
||||
if "-d" in sys.argv[1:]:
|
||||
debug_enabled = True
|
||||
|
||||
logging.info("Started with arguments: %s", sys.argv[1:])
|
||||
|
||||
try:
|
||||
send_beacon(sys.stdout)
|
||||
except Exception as e:
|
||||
logging.error("%s", e)
|
||||
p.kill()
|
||||
return 124
|
||||
|
||||
cmd = recv_command(sys.stdin)
|
||||
nicecmd = " ".join(pipes.quote(a) for a in cmd)
|
||||
try:
|
||||
@ -339,39 +523,46 @@ def main_remote():
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
close_fds=True,
|
||||
bufsize=0,
|
||||
)
|
||||
send_confirmation(sys.stdout, 0, "")
|
||||
except OSError, e:
|
||||
send_confirmation(sys.stdout, 0, b"")
|
||||
except OSError as e:
|
||||
msg = "cannot execute %s: %s" % (nicecmd, e)
|
||||
logging.error(msg)
|
||||
send_confirmation(sys.stdout, 127, msg)
|
||||
send_confirmation(sys.stdout, 127, bytes(msg, "utf-8"))
|
||||
sys.exit(0)
|
||||
except BaseException, e:
|
||||
except BaseException as e:
|
||||
msg = "cannot execute %s: %s" % (nicecmd, e)
|
||||
logging.error(msg)
|
||||
send_confirmation(sys.stdout, 126, msg)
|
||||
send_confirmation(sys.stdout, 126, bytes(msg, "utf-8"))
|
||||
sys.exit(0)
|
||||
|
||||
signals_read, signals_written = pairofpipes()
|
||||
|
||||
signaler = Signaler(p, signals_read)
|
||||
signaler.setName("remote signaler")
|
||||
signaler.start()
|
||||
|
||||
demuxer = DataDemultiplexer(sys.stdin, [p.stdin, signals_written])
|
||||
demuxer.setName("remote demultiplexer")
|
||||
demuxer.start()
|
||||
|
||||
muxer = DataMultiplexer([p.stdout, p.stderr], sys.stdout)
|
||||
muxer.setName("remote multiplexer")
|
||||
muxer.start()
|
||||
|
||||
logging.info("started %s", nicecmd)
|
||||
logging.info("Started %s", nicecmd)
|
||||
|
||||
retval = p.wait()
|
||||
logging.info("return code %s for %s", retval, nicecmd)
|
||||
logging.info("Return code %s for %s", retval, nicecmd)
|
||||
muxer.join()
|
||||
logging.info("Ending bombshell")
|
||||
return retval
|
||||
|
||||
|
||||
if "__file__" in locals():
|
||||
sys.stdin = openfdforread(sys.stdin.fileno())
|
||||
sys.stdout = openfdforappend(sys.stdout.fileno())
|
||||
if "__file__" in locals() and not ("-s" in sys.argv[1:2]):
|
||||
sys.exit(main_master())
|
||||
else:
|
||||
sys.exit(main_remote())
|
||||
|
Loading…
x
Reference in New Issue
Block a user