From ba1cf58a6cbe4953f6e012c10d5a66964d22a21f Mon Sep 17 00:00:00 2001 From: Brian May Date: Mon, 16 Nov 2015 08:41:25 +1100 Subject: [PATCH] Add Python 3.5 support. --- setup.py | 1 + sshuttle/__main__.py | 8 +- sshuttle/assembler.py | 2 +- sshuttle/client.py | 93 +-- sshuttle/compat/__init__.py | 0 sshuttle/compat/ssubprocess.py | 1296 -------------------------------- sshuttle/firewall.py | 30 +- sshuttle/helpers.py | 2 +- sshuttle/hostwatch.py | 14 +- sshuttle/options.py | 6 +- sshuttle/server.py | 14 +- sshuttle/ssh.py | 14 +- sshuttle/ssnet.py | 49 +- sshuttle/ssyslog.py | 2 +- sshuttle/stresstest.py | 20 +- sshuttle/ui-macos/main.py | 20 +- sshuttle/ui-macos/models.py | 2 +- sshuttle/ui-macos/my.py | 2 +- 18 files changed, 146 insertions(+), 1429 deletions(-) delete mode 100644 sshuttle/compat/__init__.py delete mode 100644 sshuttle/compat/ssubprocess.py diff --git a/setup.py b/setup.py index f16dc3c..ba080e6 100755 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ setup( "GNU General Public License v2 or later (GPLv2+)", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.5", "Topic :: System :: Networking", ], entry_points={ diff --git a/sshuttle/__main__.py b/sshuttle/__main__.py index 2c8aed6..1144c68 100644 --- a/sshuttle/__main__.py +++ b/sshuttle/__main__.py @@ -1,8 +1,8 @@ import sys import re import socket -import helpers -import options +import sshuttle.helpers as helpers +import sshuttle.options as options import sshuttle.client as client import sshuttle.server as server import sshuttle.firewall as firewall @@ -145,7 +145,7 @@ o = options.Options(optspec) if opt.daemon: opt.syslog = 1 if opt.wrap: - import ssnet + import sshuttle.ssnet as ssnet ssnet.MAX_CHANNEL = int(opt.wrap) helpers.verbose = opt.verbose @@ -230,7 +230,7 @@ try: log('Abnormal exit code detected, failing...' % return_code) sys.exit(return_code) -except Fatal, e: +except Fatal as e: log('fatal: %s\n' % e) sys.exit(99) except KeyboardInterrupt: diff --git a/sshuttle/assembler.py b/sshuttle/assembler.py index be5b957..ee2b12e 100644 --- a/sshuttle/assembler.py +++ b/sshuttle/assembler.py @@ -18,7 +18,7 @@ while 1: setattr(sys.modules[parent], parent_name, module) code = compile(content, name, "exec") - exec code in module.__dict__ + exec(code, module.__dict__) sys.modules[name] = module else: break diff --git a/sshuttle/client.py b/sshuttle/client.py index 55be9b3..cf4fc91 100644 --- a/sshuttle/client.py +++ b/sshuttle/client.py @@ -3,12 +3,12 @@ import errno import re import signal import time -import sshuttle.compat.ssubprocess as ssubprocess -import helpers +import subprocess as ssubprocess +import sshuttle.helpers as helpers import os import sshuttle.ssnet as ssnet import sshuttle.ssh as ssh -import ssyslog +import sshuttle.ssyslog as ssyslog import sys from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \ @@ -124,7 +124,7 @@ def check_daemon(pidfile): _pidname = os.path.abspath(pidfile) try: oldpid = open(_pidname).read(1024) - except IOError, e: + except IOError as e: if e.errno == errno.ENOENT: return # no pidfile, ok else: @@ -138,7 +138,7 @@ def check_daemon(pidfile): return # invalid pidfile, ok try: os.kill(oldpid, 0) - except OSError, e: + except OSError as e: if e.errno == errno.ESRCH: os.unlink(_pidname) return # outdated pidfile, ok @@ -157,7 +157,7 @@ def daemonize(): if os.fork(): os._exit(0) - outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0666) + outfd = os.open(_pidname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666) try: os.write(outfd, '%d\n' % os.getpid()) finally: @@ -179,7 +179,7 @@ def daemonize(): def daemon_cleanup(): try: os.unlink(_pidname) - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: pass else: @@ -215,7 +215,7 @@ def original_dst(sock): assert(socket.htons(proto) == socket.AF_INET) ip = '%d.%d.%d.%d' % (a, b, c, d) return (ip, port) - except socket.error, e: + except socket.error as e: if e.args[0] == errno.ENOPROTOOPT: return sock.getsockname() raise @@ -251,7 +251,7 @@ class MultiListener: if self.v4: try: self.v4.listen(backlog) - except socket.error, e: + except socket.error as e: # on some systems v4 bind will fail if the v6 suceeded, # in this case the v6 socket will receive v4 too. if e.errno == errno.EADDRINUSE and self.v6: @@ -321,17 +321,22 @@ class FirewallClient: self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup) e = None break - except OSError, e: + except OSError as e: pass self.argv = argv s1.close() - self.pfile = s2.makefile('wb+') + if sys.version_info < (3, 0): + # python 2.7 + self.pfile = s2.makefile('wb+') + else: + # python 3.5 + self.pfile = s2.makefile('rwb') if e: log('Spawning firewall manager: %r\n' % self.argv) raise Fatal(e) line = self.pfile.readline() self.check() - if line[0:5] != 'READY': + if line[0:5] != b'READY': raise Fatal('%r expected READY, got %r' % (self.argv, line)) self.method = line[6:-1] @@ -341,22 +346,26 @@ class FirewallClient: raise Fatal('%r returned %d' % (self.argv, rv)) def start(self): - self.pfile.write('ROUTES\n') - for (family, ip, width) in self.subnets_include + self.auto_nets: - self.pfile.write('%d,%d,0,%s\n' % (family, width, ip)) - for (family, ip, width) in self.subnets_exclude: - self.pfile.write('%d,%d,1,%s\n' % (family, width, ip)) - self.pfile.write('GO\n') + self.pfile.write(b'ROUTES\n') + try: + for (family, ip, width) in self.subnets_include + self.auto_nets: + self.pfile.write(b'%d,%d,0,%s\n' % (family, width, ip.encode("ASCII"))) + for (family, ip, width) in self.subnets_exclude: + self.pfile.write(b'%d,%d,1,%s\n' % (family, width, ip.encode("ASCII"))) + except Exception as e: + debug1("exception occured %r" % e) + raise + self.pfile.write(b'GO\n') self.pfile.flush() line = self.pfile.readline() self.check() - if line != 'STARTED\n': + if line != b'STARTED\n': raise Fatal('%r expected STARTED, got %r' % (self.argv, line)) def sethostip(self, hostname, ip): assert(not re.search(r'[^-\w]', hostname)) assert(not re.search(r'[^0-9.]', ip)) - self.pfile.write('HOST %s,%s\n' % (hostname, ip)) + self.pfile.write(b'HOST %s,%s\n' % (hostname, ip)) self.pfile.flush() def done(self): @@ -390,7 +399,7 @@ def onaccept_tcp(listener, method, mux, handlers): global _extra_fd try: sock, srcip = listener.accept() - except socket.error, e: + except socket.error as e: if e.args[0] in [errno.EMFILE, errno.ENFILE]: debug1('Rejected incoming connection: too many open files!\n') # free up an fd so we can eat the connection @@ -403,9 +412,9 @@ def onaccept_tcp(listener, method, mux, handlers): return else: raise - if method == "tproxy": + if method == b"tproxy": dstip = sock.getsockname() - elif method == "pf": + elif method == b"pf": dstip = pf_dst(sock) else: dstip = original_dst(sock) @@ -420,8 +429,8 @@ def onaccept_tcp(listener, method, mux, handlers): log('warning: too many open channels. Discarded connection.\n') sock.close() return - mux.send(chan, ssnet.CMD_TCP_CONNECT, '%d,%s,%s' % - (sock.family, dstip[0], dstip[1])) + mux.send(chan, ssnet.CMD_TCP_CONNECT, b'%d,%s,%d' % + (sock.family, dstip[0].encode("ASCII"), dstip[1])) outwrap = MuxWrapper(mux, chan) handlers.append(Proxy(SockWrapper(sock, sock), outwrap)) expire_connections(time.time(), mux) @@ -439,7 +448,7 @@ def udp_done(chan, data, method, family, dstip): sender.bind(srcip) sender.sendto(data, dstip) sender.close() - except socket.error, e: + except socket.error as e: debug1('-- ignored socket error sending UDP data: %r\n' % e) @@ -471,7 +480,7 @@ def dns_done(chan, data, method, sock, srcip, dstip, mux): debug3('dns_done: channel=%d src=%r dst=%r\n' % (chan, srcip, dstip)) del mux.channels[chan] del dnsreqs[chan] - if method == "tproxy": + if method == b"tproxy": debug3('doing send from %r to %r\n' % (srcip, dstip,)) sender = socket.socket(sock.family, socket.SOCK_DGRAM) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -487,7 +496,7 @@ def dns_done(chan, data, method, sock, srcip, dstip, mux): def ondns(listener, method, mux, handlers): now = time.time() srcip, dstip, data = recv_udp(listener, 4096) - if method == "tproxy" and not dstip: + if method == b"tproxy" and not dstip: debug1( "-- ignored UDP from %r: " "couldn't determine destination IP address\n" % (srcip,)) @@ -517,7 +526,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, ssh_cmd, remotename, python, stderr=ssyslog._p and ssyslog._p.stdin, options=dict(latency_control=latency_control, method=method)) - except socket.error, e: + except socket.error as e: if e.args[0] == errno.EPIPE: raise Fatal("failed to establish ssh session (1)") else: @@ -525,17 +534,17 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, mux = Mux(serversock, serversock) handlers.append(mux) - expected = 'SSHUTTLE0001' + expected = b'SSHUTTLE0001' try: v = 'x' - while v and v != '\0': + while v and v != b'\0': v = serversock.recv(1) v = 'x' - while v and v != '\0': + while v and v != b'\0': v = serversock.recv(1) initstring = serversock.recv(len(expected)) - except socket.error, e: + except socket.error as e: if e.args[0] == errno.ECONNRESET: raise Fatal("failed to establish ssh session (2)") else: @@ -549,7 +558,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, raise Fatal('expected server init string %r; got %r' % (expected, initstring)) debug1('connected.\n') - print 'Connected.' + print('Connected.') sys.stdout.flush() if daemon: daemonize() @@ -616,7 +625,7 @@ def main(listenip_v6, listenip_v4, if daemon: try: check_daemon(pidfile) - except Fatal, e: + except Fatal as e: log("%s\n" % e) return 5 debug1('Starting sshuttle proxy.\n') @@ -624,7 +633,7 @@ def main(listenip_v6, listenip_v4, if recvmsg is not None: debug1("recvmsg %s support enabled.\n" % recvmsg) - if method == "tproxy": + if method == b"tproxy": if recvmsg is not None: debug1("tproxy UDP support enabled.\n") udp = True @@ -643,7 +652,7 @@ def main(listenip_v6, listenip_v4, ports = [0, ] else: # if at least one port missing, we have to search - ports = xrange(12300, 9000, -1) + ports = range(12300, 9000, -1) # search for free ports and try to bind last_e = None @@ -688,7 +697,7 @@ def main(listenip_v6, listenip_v4, udp_listener.bind(lv6, lv4) bound = True break - except socket.error, e: + except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e else: @@ -708,7 +717,7 @@ def main(listenip_v6, listenip_v4, nslist += resolvconf_nameservers() # search for spare port for DNS debug2('Binding DNS:') - ports = xrange(12300, 9000, -1) + ports = range(12300, 9000, -1) for port in ports: debug2(' %d' % port) dns_listener = MultiListener(socket.SOCK_DGRAM) @@ -731,7 +740,7 @@ def main(listenip_v6, listenip_v4, dns_listener.bind(lv6, lv4) bound = True break - except socket.error, e: + except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e else: @@ -750,7 +759,7 @@ def main(listenip_v6, listenip_v4, subnets_exclude, dnsport_v6, dnsport_v4, nslist, method, udp) - if fw.method == "tproxy": + if fw.method == b"tproxy": tcp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1) if udp_listener: udp_listener.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1) @@ -767,7 +776,7 @@ def main(listenip_v6, listenip_v4, if dns_listener.v6 is not None: dns_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) - if fw.method == "pf": + if fw.method == b"pf": global pf_command_file pf_command_file = fw.pfile diff --git a/sshuttle/compat/__init__.py b/sshuttle/compat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sshuttle/compat/ssubprocess.py b/sshuttle/compat/ssubprocess.py deleted file mode 100644 index 87797e6..0000000 --- a/sshuttle/compat/ssubprocess.py +++ /dev/null @@ -1,1296 +0,0 @@ -# subprocess - Subprocesses with accessible I/O streams -# -# For more information about this module, see PEP 324. -# -# This module should remain compatible with Python 2.2, see PEP 291. -# -# Copyright (c) 2003-2005 by Peter Astrand -# -# Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. - -r"""subprocess - Subprocesses with accessible I/O streams - -This module allows you to spawn processes, connect to their -input/output/error pipes, and obtain their return codes. This module -intends to replace several other, older modules and functions, like: - -os.system -os.spawn* -os.popen* -popen2.* -commands.* - -Information about how the subprocess module can be used to replace these -modules and functions can be found below. - - - -Using the subprocess module -=========================== -This module defines one class called Popen: - -class Popen(args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - - -Arguments are: - -args should be a string, or a sequence of program arguments. The -program to execute is normally the first item in the args sequence or -string, but can be explicitly set by using the executable argument. - -On UNIX, with shell=False (default): In this case, the Popen class -uses os.execvp() to execute the child program. args should normally -be a sequence. A string will be treated as a sequence with the string -as the only item (the program to execute). - -On UNIX, with shell=True: If args is a string, it specifies the -command string to execute through the shell. If args is a sequence, -the first item specifies the command string, and any additional items -will be treated as additional shell arguments. - -On Windows: the Popen class uses CreateProcess() to execute the child -program, which operates on strings. If args is a sequence, it will be -converted to a string using the list2cmdline method. Please note that -not all MS Windows applications interpret the command line the same -way: The list2cmdline is designed for applications using the same -rules as the MS C runtime. - -bufsize, if given, has the same meaning as the corresponding argument -to the built-in open() function: 0 means unbuffered, 1 means line -buffered, any other positive value means use a buffer of -(approximately) that size. A negative bufsize means to use the system -default, which usually means fully buffered. The default value for -bufsize is 0 (unbuffered). - -stdin, stdout and stderr specify the executed programs' standard -input, standard output and standard error file handles, respectively. -Valid values are PIPE, an existing file descriptor (a positive -integer), an existing file object, and None. PIPE indicates that a -new pipe to the child should be created. With None, no redirection -will occur; the child's file handles will be inherited from the -parent. Additionally, stderr can be STDOUT, which indicates that the -stderr data from the applications should be captured into the same -file handle as for stdout. - -If preexec_fn is set to a callable object, this object will be called -in the child process just before the child is executed. - -If close_fds is true, all file descriptors except 0, 1 and 2 will be -closed before the child process is executed. - -if shell is true, the specified command will be executed through the -shell. - -If cwd is not None, the current directory will be changed to cwd -before the child is executed. - -If env is not None, it defines the environment variables for the new -process. - -If universal_newlines is true, the file objects stdout and stderr are -opened as a text files, but lines may be terminated by any of '\n', -the Unix end-of-line convention, '\r', the Macintosh convention or -'\r\n', the Windows convention. All of these external representations -are seen as '\n' by the Python program. Note: This feature is only -available if Python is built with universal newline support (the -default). Also, the newlines attribute of the file objects stdout, -stdin and stderr are not updated by the communicate() method. - -The startupinfo and creationflags, if given, will be passed to the -underlying CreateProcess() function. They can specify things such as -appearance of the main window and priority for the new process. -(Windows only) - - -This module also defines two shortcut functions: - -call(*popenargs, **kwargs): - Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - -check_call(*popenargs, **kwargs): - Run command with arguments. Wait for command to complete. If the - exit code was zero then return, otherwise raise - CalledProcessError. The CalledProcessError object will have the - return code in the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - check_call(["ls", "-l"]) - -Exceptions ----------- -Exceptions raised in the child process, before the new program has -started to execute, will be re-raised in the parent. Additionally, -the exception object will have one extra attribute called -'child_traceback', which is a string containing traceback information -from the childs point of view. - -The most common exception raised is OSError. This occurs, for -example, when trying to execute a non-existent file. Applications -should prepare for OSErrors. - -A ValueError will be raised if Popen is called with invalid arguments. - -check_call() will raise CalledProcessError, if the called process -returns a non-zero return code. - - -Security --------- -Unlike some other popen functions, this implementation will never call -/bin/sh implicitly. This means that all characters, including shell -metacharacters, can safely be passed to child processes. - - -Popen objects -============= -Instances of the Popen class have the following methods: - -poll() - Check if child process has terminated. Returns returncode - attribute. - -wait() - Wait for child process to terminate. Returns returncode attribute. - -communicate(input=None) - Interact with process: Send data to stdin. Read data from stdout - and stderr, until end-of-file is reached. Wait for process to - terminate. The optional input argument should be a string to be - sent to the child process, or None, if no data should be sent to - the child. - - communicate() returns a tuple (stdout, stderr). - - Note: The data read is buffered in memory, so do not use this - method if the data size is large or unlimited. - -The following attributes are also available: - -stdin - If the stdin argument is PIPE, this attribute is a file object - that provides input to the child process. Otherwise, it is None. - -stdout - If the stdout argument is PIPE, this attribute is a file object - that provides output from the child process. Otherwise, it is - None. - -stderr - If the stderr argument is PIPE, this attribute is file object that - provides error output from the child process. Otherwise, it is - None. - -pid - The process ID of the child process. - -returncode - The child return code. A None value indicates that the process - hasn't terminated yet. A negative value -N indicates that the - child was terminated by signal N (UNIX only). - - -Replacing older functions with the subprocess module -==================================================== -In this section, "a ==> b" means that b can be used as a replacement -for a. - -Note: All functions in this section fail (more or less) silently if -the executed program cannot be found; this module raises an OSError -exception. - -In the following examples, we assume that the subprocess module is -imported with "from subprocess import *". - - -Replacing /bin/sh shell backquote ---------------------------------- -output=`mycmd myarg` -==> -output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] - - -Replacing shell pipe line -------------------------- -output=`dmesg | grep hda` -==> -p1 = Popen(["dmesg"], stdout=PIPE) -p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) -output = p2.communicate()[0] - - -Replacing os.system() ---------------------- -sts = os.system("mycmd" + " myarg") -==> -p = Popen("mycmd" + " myarg", shell=True) -pid, sts = os.waitpid(p.pid, 0) - -Note: - -* Calling the program through the shell is usually not required. - -* It's easier to look at the returncode attribute than the - exitstatus. - -A more real-world example would look like this: - -try: - retcode = call("mycmd" + " myarg", shell=True) - if retcode < 0: - print >>sys.stderr, "Child was terminated by signal", -retcode - else: - print >>sys.stderr, "Child returned", retcode -except OSError, e: - print >>sys.stderr, "Execution failed:", e - - -Replacing os.spawn* -------------------- -P_NOWAIT example: - -pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") -==> -pid = Popen(["/bin/mycmd", "myarg"]).pid - - -P_WAIT example: - -retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") -==> -retcode = call(["/bin/mycmd", "myarg"]) - - -Vector example: - -os.spawnvp(os.P_NOWAIT, path, args) -==> -Popen([path] + args[1:]) - - -Environment example: - -os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) -==> -Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) - - -Replacing os.popen* -------------------- -pipe = os.popen(cmd, mode='r', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout - -pipe = os.popen(cmd, mode='w', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin - - -(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdin, child_stdout) = (p.stdin, p.stdout) - - -(child_stdin, - child_stdout, - child_stderr) = os.popen3(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) -(child_stdin, - child_stdout, - child_stderr) = (p.stdin, p.stdout, p.stderr) - - -(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) -(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) - - -Replacing popen2.* ------------------- -Note: If the cmd argument to popen2 functions is a string, the command -is executed through /bin/sh. If it is a list, the command is directly -executed. - -(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) -==> -p = Popen(["somestring"], shell=True, bufsize=bufsize - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - - -(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) -==> -p = Popen(["mycmd", "myarg"], bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - -The popen2.Popen3 and popen2.Popen4 basically works as subprocess.Popen, -except that: - -* subprocess.Popen raises an exception if the execution fails -* the capturestderr argument is replaced with the stderr argument. -* stdin=PIPE and stdout=PIPE must be specified. -* popen2 closes all filedescriptors by default, but you have to specify - close_fds=True with subprocess.Popen. -""" - -import sys -mswindows = (sys.platform == "win32") - -import os -import types -import traceback -import gc -import signal - -# Exception classes used by this module. - - -class CalledProcessError(Exception): - """This exception is raised when a process run by check_call() returns - a non-zero exit status. The exit status will be stored in the - returncode attribute.""" - - def __init__(self, returncode, cmd): - self.returncode = returncode - self.cmd = cmd - - def __str__(self): - return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) - - -if mswindows: - import threading - import msvcrt - if 0: # <-- change this to use pywin32 instead of the _subprocess driver - import pywintypes - from win32api import GetStdHandle, STD_INPUT_HANDLE, \ - STD_OUTPUT_HANDLE, STD_ERROR_HANDLE - from win32api import GetCurrentProcess, DuplicateHandle, \ - GetModuleFileName, GetVersion - from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE - from win32pipe import CreatePipe - from win32process import CreateProcess, STARTUPINFO, \ - GetExitCodeProcess, STARTF_USESTDHANDLES, \ - STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE - from win32process import TerminateProcess - from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 - else: - from _subprocess import * - - class STARTUPINFO: - dwFlags = 0 - hStdInput = None - hStdOutput = None - hStdError = None - wShowWindow = 0 - - class pywintypes: - error = IOError -else: - import select - import errno - import fcntl - import pickle - -__all__ = ["Popen", "PIPE", "STDOUT", "call", - "check_call", "CalledProcessError"] - -try: - MAXFD = os.sysconf("SC_OPEN_MAX") -except: - MAXFD = 256 - -# True/False does not exist on 2.2.0 -# try: -# False -# except NameError: -# False = 0 -# True = 1 - -_active = [] - - -def _cleanup(): - for inst in _active[:]: - if inst._internal_poll(_deadstate=sys.maxint) >= 0: - try: - _active.remove(inst) - except ValueError: - # This can happen if two threads create a new Popen instance. - # It's harmless that it was already removed, so ignore. - pass - -PIPE = -1 -STDOUT = -2 - - -def call(*popenargs, **kwargs): - """Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - """ - return Popen(*popenargs, **kwargs).wait() - - -def check_call(*popenargs, **kwargs): - """Run command with arguments. Wait for command to complete. If - the exit code was zero then return, otherwise raise - CalledProcessError. The CalledProcessError object will have the - return code in the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - check_call(["ls", "-l"]) - """ - retcode = call(*popenargs, **kwargs) - cmd = kwargs.get("args") - if cmd is None: - cmd = popenargs[0] - if retcode: - raise CalledProcessError(retcode, cmd) - return retcode - - -def list2cmdline(seq): - """ - Translate a sequence of arguments into a command line - string, using the same rules as the MS C runtime: - - 1) Arguments are delimited by white space, which is either a - space or a tab. - - 2) A string surrounded by double quotation marks is - interpreted as a single argument, regardless of white space - or pipe characters contained within. A quoted string can be - embedded in an argument. - - 3) A double quotation mark preceded by a backslash is - interpreted as a literal double quotation mark. - - 4) Backslashes are interpreted literally, unless they - immediately precede a double quotation mark. - - 5) If backslashes immediately precede a double quotation mark, - every pair of backslashes is interpreted as a literal - backslash. If the number of backslashes is odd, the last - backslash escapes the next double quotation mark as - described in rule 3. - """ - - # See - # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp - result = [] - needquote = False - for arg in seq: - bs_buf = [] - - # Add a space to separate this argument from the others - if result: - result.append(' ') - - needquote = (" " in arg) or ("\t" in arg) or ("|" in arg) or not arg - if needquote: - result.append('"') - - for c in arg: - if c == '\\': - # Don't know if we need to double yet. - bs_buf.append(c) - elif c == '"': - # Double backslashes. - result.append('\\' * len(bs_buf) * 2) - bs_buf = [] - result.append('\\"') - else: - # Normal char - if bs_buf: - result.extend(bs_buf) - bs_buf = [] - result.append(c) - - # Add remaining backslashes, if any. - if bs_buf: - result.extend(bs_buf) - - if needquote: - result.extend(bs_buf) - result.append('"') - - return ''.join(result) - - -def _closerange(start, max): - try: - os.closerange(start, max) - except AttributeError: - for i in xrange(start, max): - try: - os.close(i) - except: - pass - - -class Popen(object): - - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - """Create new Popen instance.""" - _cleanup() - - self._child_created = False - if not isinstance(bufsize, (int, long)): - raise TypeError("bufsize must be an integer") - - if mswindows: - if preexec_fn is not None: - raise ValueError("preexec_fn is not supported on Windows " - "platforms") - if close_fds and (stdin is not None or stdout is not None or - stderr is not None): - raise ValueError("close_fds is not supported on Windows " - "platforms if you redirect stdin/stdout/stderr") - else: - # POSIX - if startupinfo is not None: - raise ValueError("startupinfo is only supported on Windows " - "platforms") - if creationflags != 0: - raise ValueError("creationflags is only supported on Windows " - "platforms") - - self.stdin = None - self.stdout = None - self.stderr = None - self.pid = None - self.returncode = None - self.universal_newlines = universal_newlines - - # Input and output objects. The general principle is like - # this: - # - # Parent Child - # ------ ----- - # p2cwrite ---stdin---> p2cread - # c2pread <--stdout--- c2pwrite - # errread <--stderr--- errwrite - # - # On POSIX, the child objects are file descriptors. On - # Windows, these are Windows file handles. The parent objects - # are file descriptors on both platforms. The parent objects - # are None when not using PIPEs. The child objects are None - # when not redirecting. - - (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = self._get_handles(stdin, stdout, stderr) - - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - # On Windows, you cannot just redirect one or two handles: You - # either have to redirect all three or none. If the subprocess - # user has only redirected one or two handles, we are - # automatically creating PIPEs for the rest. We should close - # these after the process is started. See bug #1124861. - if mswindows: - if stdin is None and p2cwrite is not None: - os.close(p2cwrite) - p2cwrite = None - if stdout is None and c2pread is not None: - os.close(c2pread) - c2pread = None - if stderr is None and errread is not None: - os.close(errread) - errread = None - - if p2cwrite is not None: - self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) - if c2pread is not None: - if universal_newlines: - self.stdout = os.fdopen(c2pread, 'rU', bufsize) - else: - self.stdout = os.fdopen(c2pread, 'rb', bufsize) - if errread is not None: - if universal_newlines: - self.stderr = os.fdopen(errread, 'rU', bufsize) - else: - self.stderr = os.fdopen(errread, 'rb', bufsize) - - def _translate_newlines(self, data): - data = data.replace("\r\n", "\n") - data = data.replace("\r", "\n") - return data - - def __del__(self, sys=sys): - if not self._child_created: - # We didn't get to successfully create a child process. - return - # In case the child hasn't been waited on, check if it's done. - self._internal_poll(_deadstate=sys.maxint) - if self.returncode is None and _active is not None: - # Child is still running, keep us alive until we can wait on it. - _active.append(self) - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - - # Optimization: If we are only using one pipe, or no pipe at - # all, using select() or threads is unnecessary. - if [self.stdin, self.stdout, self.stderr].count(None) >= 2: - stdout = None - stderr = None - if self.stdin: - if input: - self.stdin.write(input) - self.stdin.close() - elif self.stdout: - stdout = self.stdout.read() - self.stdout.close() - elif self.stderr: - stderr = self.stderr.read() - self.stderr.close() - self.wait() - return (stdout, stderr) - - return self._communicate(input) - - def poll(self): - return self._internal_poll() - - if mswindows: - # - # Windows methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tupel with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - if stdin is None and stdout is None and stderr is None: - return (None, None, None, None, None, None) - - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin is None: - p2cread = GetStdHandle(STD_INPUT_HANDLE) - if p2cread is not None: - pass - elif stdin is None or stdin == PIPE: - p2cread, p2cwrite = CreatePipe(None, 0) - # Detach and turn into fd - p2cwrite = p2cwrite.Detach() - p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) - elif isinstance(stdin, int): - p2cread = msvcrt.get_osfhandle(stdin) - else: - # Assuming file-like object - p2cread = msvcrt.get_osfhandle(stdin.fileno()) - p2cread = self._make_inheritable(p2cread) - - if stdout is None: - c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) - if c2pwrite is not None: - pass - elif stdout is None or stdout == PIPE: - c2pread, c2pwrite = CreatePipe(None, 0) - # Detach and turn into fd - c2pread = c2pread.Detach() - c2pread = msvcrt.open_osfhandle(c2pread, 0) - elif isinstance(stdout, int): - c2pwrite = msvcrt.get_osfhandle(stdout) - else: - # Assuming file-like object - c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) - c2pwrite = self._make_inheritable(c2pwrite) - - if stderr is None: - errwrite = GetStdHandle(STD_ERROR_HANDLE) - if errwrite is not None: - pass - elif stderr is None or stderr == PIPE: - errread, errwrite = CreatePipe(None, 0) - # Detach and turn into fd - errread = errread.Detach() - errread = msvcrt.open_osfhandle(errread, 0) - elif stderr == STDOUT: - errwrite = c2pwrite - elif isinstance(stderr, int): - errwrite = msvcrt.get_osfhandle(stderr) - else: - # Assuming file-like object - errwrite = msvcrt.get_osfhandle(stderr.fileno()) - errwrite = self._make_inheritable(errwrite) - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - def _make_inheritable(self, handle): - """Return a duplicate of handle, which is inheritable""" - return DuplicateHandle(GetCurrentProcess(), handle, - GetCurrentProcess(), 0, 1, - DUPLICATE_SAME_ACCESS) - - def _find_w9xpopen(self): - """Find and return absolut path to w9xpopen.exe""" - w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - # Eeek - file-not-found - possibly an embedding - # situation - see if we can locate it in sys.exec_prefix - w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - raise RuntimeError("Cannot locate w9xpopen.exe, which is " - "needed for Popen to work with your " - "shell or platform.") - return w9xpopen - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (MS Windows version)""" - - if not isinstance(args, types.StringTypes): - args = list2cmdline(args) - - # Process startup details - if startupinfo is None: - startupinfo = STARTUPINFO() - if None not in (p2cread, c2pwrite, errwrite): - startupinfo.dwFlags |= STARTF_USESTDHANDLES - startupinfo.hStdInput = p2cread - startupinfo.hStdOutput = c2pwrite - startupinfo.hStdError = errwrite - - if shell: - startupinfo.dwFlags |= STARTF_USESHOWWINDOW - startupinfo.wShowWindow = SW_HIDE - comspec = os.environ.get("COMSPEC", "cmd.exe") - args = comspec + " /c " + args - if (GetVersion() >= 0x80000000L or - os.path.basename(comspec).lower() == "command.com"): - # Win9x, or using command.com on NT. We need to - # use the w9xpopen intermediate program. For more - # information, see KB Q150956 - # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) - w9xpopen = self._find_w9xpopen() - args = '"%s" %s' % (w9xpopen, args) - # Not passing CREATE_NEW_CONSOLE has been known to - # cause random failures on win9x. Specifically a - # dialog: "Your program accessed mem currently in - # use at xxx" and a hopeful warning about the - # stability of your system. Cost is Ctrl+C wont - # kill children. - creationflags |= CREATE_NEW_CONSOLE - - # Start the process - try: - hp, ht, pid, tid = CreateProcess(executable, args, - # no special security - None, None, - int(not close_fds), - creationflags, - env, - cwd, - startupinfo) - except pywintypes.error, e: - # Translate pywintypes.error to WindowsError, which is - # a subclass of OSError. FIXME: We should really - # translate errno using _sys_errlist (or simliar), but - # how can this be done from Python? - raise WindowsError(*e.args) - - # Retain the process handle, but close the thread handle - self._child_created = True - self._handle = hp - self.pid = pid - ht.Close() - - # Child is launched. Close the parent's copy of those pipe - # handles that only the child should have open. You need - # to make sure that no handles to the write end of the - # output pipe are maintained in this process or else the - # pipe will not close when the child process exits and the - # ReadFile will hang. - if p2cread is not None: - p2cread.Close() - if c2pwrite is not None: - c2pwrite.Close() - if errwrite is not None: - errwrite.Close() - - def _internal_poll(self, _deadstate=None): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode is None: - if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: - self.returncode = GetExitCodeProcess(self._handle) - return self.returncode - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode is None: - obj = WaitForSingleObject(self._handle, INFINITE) - self.returncode = GetExitCodeProcess(self._handle) - return self.returncode - - def _readerthread(self, fh, buffer): - buffer.append(fh.read()) - - def _communicate(self, input): - stdout = None # Return - stderr = None # Return - - if self.stdout: - stdout = [] - stdout_thread = threading.Thread(target=self._readerthread, - args=(self.stdout, stdout)) - stdout_thread.setDaemon(True) - stdout_thread.start() - if self.stderr: - stderr = [] - stderr_thread = threading.Thread(target=self._readerthread, - args=(self.stderr, stderr)) - stderr_thread.setDaemon(True) - stderr_thread.start() - - if self.stdin: - if input is not None: - self.stdin.write(input) - self.stdin.close() - - if self.stdout: - stdout_thread.join() - if self.stderr: - stderr_thread.join() - - # All data exchanged. Translate lists into strings. - if stdout is not None: - stdout = stdout[0] - if stderr is not None: - stderr = stderr[0] - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(file, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - def send_signal(self, sig): - """Send a signal to the process - """ - if sig == signal.SIGTERM: - self.terminate() - else: - raise ValueError("Only SIGTERM is supported on Windows") - - def terminate(self): - """Terminates the process - """ - TerminateProcess(self._handle, 1) - - kill = terminate - - else: - # - # POSIX methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tupel with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin is None: - pass - elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() - elif isinstance(stdin, int): - p2cread = stdin - else: - # Assuming file-like object - p2cread = stdin.fileno() - - if stdout is None: - pass - elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() - elif isinstance(stdout, int): - c2pwrite = stdout - else: - # Assuming file-like object - c2pwrite = stdout.fileno() - - if stderr is None: - pass - elif stderr == PIPE: - errread, errwrite = os.pipe() - elif stderr == STDOUT: - errwrite = c2pwrite - elif isinstance(stderr, int): - errwrite = stderr - else: - # Assuming file-like object - errwrite = stderr.fileno() - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - def _set_cloexec_flag(self, fd): - try: - cloexec_flag = fcntl.FD_CLOEXEC - except AttributeError: - cloexec_flag = 1 - - old = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) - - def _close_fds(self, but): - _closerange(3, but) - _closerange(but + 1, MAXFD) - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (POSIX version)""" - - if isinstance(args, types.StringTypes): - args = [args] - else: - args = list(args) - - if shell: - args = ["/bin/sh", "-c"] + args - - if executable is None: - executable = args[0] - - # For transferring possible exec failure from child to parent - # The first char specifies the exception type: 0 means - # OSError, 1 means some other error. - errpipe_read, errpipe_write = os.pipe() - self._set_cloexec_flag(errpipe_write) - - gc_was_enabled = gc.isenabled() - # Disable gc to avoid bug where gc -> file_dealloc -> - # write to stderr -> hang. http://bugs.python.org/issue1336 - gc.disable() - try: - self.pid = os.fork() - except: - if gc_was_enabled: - gc.enable() - raise - self._child_created = True - if self.pid == 0: - # Child - try: - # Close parent's pipe ends - if p2cwrite is not None: - os.close(p2cwrite) - if c2pread is not None: - os.close(c2pread) - if errread is not None: - os.close(errread) - os.close(errpipe_read) - - # Dup fds for child - if p2cread is not None: - os.dup2(p2cread, 0) - if c2pwrite is not None: - os.dup2(c2pwrite, 1) - if errwrite is not None: - os.dup2(errwrite, 2) - - # Close pipe fds. Make sure we don't close the same - # fd more than once, or standard fds. - if p2cread is not None and p2cread not in (0,): - os.close(p2cread) - if c2pwrite is not None and c2pwrite not in (p2cread, 1): - os.close(c2pwrite) - if errwrite is not None and errwrite not in (p2cread, c2pwrite, 2): - os.close(errwrite) - - # Close all other fds, if asked for - if close_fds: - self._close_fds(but=errpipe_write) - - if cwd is not None: - os.chdir(cwd) - - if preexec_fn: - preexec_fn() - - if env is None: - os.execvp(executable, args) - else: - os.execvpe(executable, args, env) - - except: - exc_type, exc_value, tb = sys.exc_info() - # Save the traceback and attach it to the exception object - exc_lines = traceback.format_exception(exc_type, - exc_value, - tb) - exc_value.child_traceback = ''.join(exc_lines) - os.write(errpipe_write, pickle.dumps(exc_value)) - - # This exitcode won't be reported to applications, so it - # really doesn't matter what we return. - os._exit(255) - - # Parent - if gc_was_enabled: - gc.enable() - os.close(errpipe_write) - if p2cread is not None and p2cwrite is not None: - os.close(p2cread) - if c2pwrite is not None and c2pread is not None: - os.close(c2pwrite) - if errwrite is not None and errread is not None: - os.close(errwrite) - - # Wait for exec to fail or succeed; possibly raising exception - data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB - os.close(errpipe_read) - if data != "": - os.waitpid(self.pid, 0) - child_exception = pickle.loads(data) - raise child_exception - - def _handle_exitstatus(self, sts): - if os.WIFSIGNALED(sts): - self.returncode = -os.WTERMSIG(sts) - elif os.WIFEXITED(sts): - self.returncode = os.WEXITSTATUS(sts) - else: - # Should never happen - raise RuntimeError("Unknown child exit status!") - - def _internal_poll(self, _deadstate=None): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode is None: - try: - pid, sts = os.waitpid(self.pid, os.WNOHANG) - if pid == self.pid: - self._handle_exitstatus(sts) - except os.error: - if _deadstate is not None: - self.returncode = _deadstate - return self.returncode - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode is None: - pid, sts = os.waitpid(self.pid, 0) - self._handle_exitstatus(sts) - return self.returncode - - def _communicate(self, input): - read_set = [] - write_set = [] - stdout = None # Return - stderr = None # Return - - if self.stdin: - # Flush stdio buffer. This might block, if the user has - # been writing to .stdin in an uncontrolled fashion. - self.stdin.flush() - if input: - write_set.append(self.stdin) - else: - self.stdin.close() - if self.stdout: - read_set.append(self.stdout) - stdout = [] - if self.stderr: - read_set.append(self.stderr) - stderr = [] - - input_offset = 0 - while read_set or write_set: - try: - rlist, wlist, xlist = select.select( - read_set, write_set, []) - except select.error, e: - if e.args[0] == errno.EINTR: - continue - raise - - if self.stdin in wlist: - # When select has indicated that the file is writable, - # we can write up to PIPE_BUF bytes without risk - # blocking. POSIX defines PIPE_BUF >= 512 - chunk = input[input_offset: input_offset + 512] - bytes_written = os.write(self.stdin.fileno(), chunk) - input_offset += bytes_written - if input_offset >= len(input): - self.stdin.close() - write_set.remove(self.stdin) - - if self.stdout in rlist: - data = os.read(self.stdout.fileno(), 1024) - if data == "": - self.stdout.close() - read_set.remove(self.stdout) - stdout.append(data) - - if self.stderr in rlist: - data = os.read(self.stderr.fileno(), 1024) - if data == "": - self.stderr.close() - read_set.remove(self.stderr) - stderr.append(data) - - # All data exchanged. Translate lists into strings. - if stdout is not None: - stdout = ''.join(stdout) - if stderr is not None: - stderr = ''.join(stderr) - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(file, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - def send_signal(self, sig): - """Send a signal to the process - """ - os.kill(self.pid, sig) - - def terminate(self): - """Terminate the process with SIGTERM - """ - self.send_signal(signal.SIGTERM) - - def kill(self): - """Kill the process with SIGKILL - """ - self.send_signal(signal.SIGKILL) - - -def _demo_posix(): - # - # Example 1: Simple redirection: Get process list - # - plist = Popen(["ps"], stdout=PIPE).communicate()[0] - print "Process list:" - print plist - - # - # Example 2: Change uid before executing child - # - if os.getuid() == 0: - p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) - p.wait() - - # - # Example 3: Connecting several subprocesses - # - print "Looking for 'hda'..." - p1 = Popen(["dmesg"], stdout=PIPE) - p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 4: Catch execution error - # - print - print "Trying a weird file..." - try: - print Popen(["/this/path/does/not/exist"]).communicate() - except OSError, e: - if e.errno == errno.ENOENT: - print "The file didn't exist. I thought so..." - print "Child traceback:" - print e.child_traceback - else: - print "Error", e.errno - else: - print >>sys.stderr, "Gosh. No error." - - -def _demo_windows(): - # - # Example 1: Connecting several subprocesses - # - print "Looking for 'PROMPT' in set output..." - p1 = Popen("set", stdout=PIPE, shell=True) - p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 2: Simple execution of program - # - print "Executing calc..." - p = Popen("calc") - p.wait() - - -if 0 and __name__ == "__main__": - if mswindows: - _demo_windows() - else: - _demo_posix() diff --git a/sshuttle/firewall.py b/sshuttle/firewall.py index 50621d0..31e870f 100644 --- a/sshuttle/firewall.py +++ b/sshuttle/firewall.py @@ -3,8 +3,8 @@ import socket import select import signal import struct -import compat.ssubprocess as ssubprocess -import ssyslog +import subprocess as ssubprocess +import sshuttle.ssyslog as ssyslog import sys import os import re @@ -22,7 +22,7 @@ IPPROTO_DIVERT = 254 def nonfatal(func, *args): try: func(*args) - except Fatal, e: + except Fatal as e: log('error: %s\n' % e) @@ -36,7 +36,7 @@ def ipt_chain_exists(family, table, name): argv = [cmd, '-t', table, '-nL'] p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE) for line in p.stdout: - if line.startswith('Chain %s ' % name): + if line.startswith(b'Chain %s ' % name.encode("ASCII")): return True rv = p.wait() if rv: @@ -134,7 +134,7 @@ def do_iptables_nat(port, dnsport, nslist, family, subnets, udp): '--to-ports', str(port)) if dnsport: - for f, ip in filter(lambda i: i[0] == family, nslist): + for f, ip in [i for i in nslist if i[0] == family]: ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', @@ -193,7 +193,7 @@ def do_iptables_tproxy(port, dnsport, nslist, family, subnets, udp): '-m', 'udp', '-p', 'udp') if dnsport: - for f, ip in filter(lambda i: i[0] == family, nslist): + for f, ip in [i for i in nslist if i[0] == family]: ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/32' % ip, '-m', 'udp', '-p', 'udp', '--dport', '53') @@ -440,7 +440,7 @@ def do_ipfw(port, dnsport, family, subnets, udp): IPPROTO_DIVERT) divertsock.bind(('0.0.0.0', port)) # IP field is ignored - for f, ip in filter(lambda i: i[0] == family, nslist): + for f, ip in [i for i in nslist if i[0] == family]: # relabel and then catch outgoing DNS requests ipfw('add', sport, 'divert', sport, 'udp', @@ -553,7 +553,7 @@ def rewrite_etc_hosts(port): try: old_content = open(HOSTSFILE).read() st = os.stat(HOSTSFILE) - except IOError, e: + except IOError as e: if e.errno == errno.ENOENT: pass else: @@ -575,7 +575,7 @@ def rewrite_etc_hosts(port): os.chmod(tmpname, st.st_mode) else: os.chown(tmpname, 0, 0) - os.chmod(tmpname, 0644) + os.chmod(tmpname, 0o644) os.rename(tmpname, HOSTSFILE) @@ -625,11 +625,11 @@ pfioc_pooladdr = c_char * 1136 # sizeof(struct pfioc_pooladdr) MAXPATHLEN = 1024 -DIOCNATLOOK = ((0x40000000L | 0x80000000L) | ( +DIOCNATLOOK = ((0x40000000 | 0x80000000) | ( (sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23)) -DIOCCHANGERULE = ((0x40000000L | 0x80000000L) | ( +DIOCCHANGERULE = ((0x40000000 | 0x80000000) | ( (sizeof(pfioc_rule) & 0x1fff) << 16) | ((ord('D')) << 8) | (26)) -DIOCBEGINADDRS = ((0x40000000L | 0x80000000L) | ( +DIOCBEGINADDRS = ((0x40000000 | 0x80000000) | ( (sizeof(pfioc_pooladdr) & 0x1fff) << 16) | ((ord('D')) << 8) | (51)) PF_CHANGE_ADD_TAIL = 2 @@ -794,14 +794,14 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, nslist, method, udp, syslog): if line: debug1('firewall manager: starting transproxy.\n') - subnets_v6 = filter(lambda i: i[0] == socket.AF_INET6, subnets) + subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6] if port_v6: do_wait = do_it( port_v6, dnsport_v6, nslist, socket.AF_INET6, subnets_v6, udp) elif len(subnets_v6) > 0: debug1("IPv6 subnets defined but IPv6 disabled\n") - subnets_v4 = filter(lambda i: i[0] == socket.AF_INET, subnets) + subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET] if port_v4: do_wait = do_it( port_v4, dnsport_v4, nslist, socket.AF_INET, subnets_v4, udp) @@ -832,7 +832,7 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, nslist, method, udp, syslog): try: dst = pf_query_nat(*(line[13:].split(','))) sys.stdout.write('QUERY_PF_NAT_SUCCESS %s,%r\n' % dst) - except IOError, e: + except IOError as e: sys.stdout.write('QUERY_PF_NAT_FAILURE %s\n' % e) sys.stdout.flush() diff --git a/sshuttle/helpers.py b/sshuttle/helpers.py index 6d8c9a3..db7083e 100644 --- a/sshuttle/helpers.py +++ b/sshuttle/helpers.py @@ -69,7 +69,7 @@ def islocal(ip, family): try: try: sock.bind((ip, 0)) - except socket.error, e: + except socket.error as e: if e.args[0] == errno.EADDRNOTAVAIL: return False # not a local IP else: diff --git a/sshuttle/hostwatch.py b/sshuttle/hostwatch.py index 1c49bf7..ad94efe 100644 --- a/sshuttle/hostwatch.py +++ b/sshuttle/hostwatch.py @@ -6,7 +6,7 @@ import errno import os import sys -import sshuttle.compat.ssubprocess as ssubprocess +import subprocess as ssubprocess import sshuttle.helpers as helpers from sshuttle.helpers import log, debug1, debug2, debug3 @@ -21,7 +21,7 @@ hostnames = {} queue = {} try: null = open('/dev/null', 'wb') -except IOError, e: +except IOError as e: log('warning: %s\n' % e) null = os.popen("sh -c 'while read x; do :; done'", 'wb', 4096) @@ -48,7 +48,7 @@ def write_host_cache(): def read_host_cache(): try: f = open(CACHEFILE) - except IOError, e: + except IOError as e: if e.errno == errno.ENOENT: return else: @@ -122,7 +122,7 @@ def _check_netstat(): p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) content = p.stdout.read() p.wait() - except OSError, e: + except OSError as e: log('%r failed: %r\n' % (argv, e)) return @@ -142,7 +142,7 @@ def _check_smb(hostname): p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) lines = p.stdout.readlines() p.wait() - except OSError, e: + except OSError as e: log('%r failed: %r\n' % (argv, e)) _smb_ok = False return @@ -199,7 +199,7 @@ def _check_nmb(hostname, is_workgroup, is_master): p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) lines = p.stdout.readlines() rv = p.wait() - except OSError, e: + except OSError as e: log('%r failed: %r\n' % (argv, e)) _nmb_ok = False return @@ -267,7 +267,7 @@ def hw_main(seed_hosts): while 1: now = time.time() - for t, last_polled in queue.items(): + for t, last_polled in list(queue.items()): (op, args) = t if not _stdin_still_ok(0): break diff --git a/sshuttle/options.py b/sshuttle/options.py index 1f58fbe..046ed5f 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -186,12 +186,12 @@ class Options: try: (flags, extra) = self.optfunc( args, self._shortopts, self._longopts) - except getopt.GetoptError, e: + except getopt.GetoptError as e: self.fatal(e) opt = OptDict() - for k, v in self._defaults.iteritems(): + for k, v in self._defaults.items(): k = self._aliases[k] opt[k] = v @@ -210,6 +210,6 @@ class Options: else: v = _intify(v) opt[k] = v - for (f1, f2) in self._aliases.iteritems(): + for (f1, f2) in self._aliases.items(): opt[f1] = opt._opts.get(f2) return (opt, flags, extra) diff --git a/sshuttle/server.py b/sshuttle/server.py index 7f7ad88..2759f89 100644 --- a/sshuttle/server.py +++ b/sshuttle/server.py @@ -9,7 +9,7 @@ import os import sshuttle.ssnet as ssnet import sshuttle.helpers as helpers import sshuttle.hostwatch as hostwatch -import sshuttle.compat.ssubprocess as ssubprocess +import subprocess as ssubprocess from sshuttle.ssnet import Handler, Proxy, Mux, MuxWrapper from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, \ resolvconf_random_nameserver @@ -148,7 +148,7 @@ class DnsProxy(Handler): debug2('DNS: sending to %r\n' % self.peer) try: self.sock.send(self.request) - except socket.error, e: + except socket.error as e: if e.args[0] in ssnet.NET_ERRS: # might have been spurious; try again. # Note: these errors sometimes are reported by recv(), @@ -163,7 +163,7 @@ class DnsProxy(Handler): def callback(self): try: data = self.sock.recv(4096) - except socket.error, e: + except socket.error as e: if e.args[0] in ssnet.NET_ERRS: # might have been spurious; try again. # Note: these errors sometimes are reported by recv(), @@ -195,14 +195,14 @@ class UdpProxy(Handler): debug2('UDP: sending to %r port %d\n' % dstip) try: self.sock.sendto(data, dstip) - except socket.error, e: + except socket.error as e: log('UDP send to %r port %d: %s\n' % (dstip[0], dstip[1], e)) return def callback(self): try: data, peer = self.sock.recvfrom(4096) - except socket.error, e: + except socket.error as e: log('UDP recv from %r port %d: %s\n' % (peer[0], peer[1], e)) return debug2('UDP response: %d bytes\n' % len(data)) @@ -322,13 +322,13 @@ def main(): if dnshandlers: now = time.time() - for channel, h in dnshandlers.items(): + for channel, h in list(dnshandlers.items()): if h.timeout < now or not h.ok: debug3('expiring dnsreqs channel=%d\n' % channel) del dnshandlers[channel] h.ok = False if udphandlers: - for channel, h in udphandlers.items(): + for channel, h in list(udphandlers.items()): if not h.ok: debug3('expiring UDP channel=%d\n' % channel) del udphandlers[channel] diff --git a/sshuttle/ssh.py b/sshuttle/ssh.py index aea22ae..5daefbc 100644 --- a/sshuttle/ssh.py +++ b/sshuttle/ssh.py @@ -4,7 +4,7 @@ import re import socket import zlib import imp -import sshuttle.compat.ssubprocess as ssubprocess +import subprocess as ssubprocess import sshuttle.helpers as helpers from sshuttle.helpers import debug2 @@ -40,7 +40,7 @@ def readfile(name): if f is not None: f.close() - return contents + return contents.encode("UTF8") def empackage(z, name, data=None): @@ -48,7 +48,8 @@ def empackage(z, name, data=None): data = readfile(name) content = z.compress(data) content += z.flush(zlib.Z_SYNC_FLUSH) - return '%s\n%d\n%s' % (name, len(content), content) + + return b'%s\n%d\n%s' % (name.encode("ASCII"), len(content), content) def connect(ssh_cmd, rhostport, python, stderr, options): @@ -77,16 +78,15 @@ def connect(ssh_cmd, rhostport, python, stderr, options): z = zlib.compressobj(1) content = readfile('sshuttle.assembler') - optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in options.items()) + optdata = ''.join("%s=%r\n" % (k, v) for (k, v) in list(options.items())) + optdata = optdata.encode("UTF8") content2 = (empackage(z, 'sshuttle') + empackage(z, 'sshuttle.cmdline_options', optdata) + empackage(z, 'sshuttle.helpers') + - empackage(z, 'sshuttle.compat') + - empackage(z, 'sshuttle.compat.ssubprocess') + empackage(z, 'sshuttle.ssnet') + empackage(z, 'sshuttle.hostwatch') + empackage(z, 'sshuttle.server') + - "\n") + b"\n") pyscript = r""" import sys; diff --git a/sshuttle/ssnet.py b/sshuttle/ssnet.py index 2781a24..9b9e566 100644 --- a/sshuttle/ssnet.py +++ b/sshuttle/ssnet.py @@ -75,7 +75,7 @@ def _fds(l): def _nb_clean(func, *args): try: return func(*args) - except OSError, e: + except OSError as e: if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN): raise else: @@ -88,7 +88,7 @@ def _try_peername(sock): pn = sock.getpeername() if pn: return '%s:%s' % (pn[0], pn[1]) - except socket.error, e: + except socket.error as e: if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK): raise return 'unknown' @@ -144,7 +144,7 @@ class SockWrapper: self.rsock.connect(self.connect_to) # connected successfully (Linux) self.connect_to = None - except socket.error, e: + except socket.error as e: debug3('%r: connect result: %s\n' % (self, e)) if e.args[0] == errno.EINVAL: # this is what happens when you call connect() on a socket @@ -191,7 +191,7 @@ class SockWrapper: self.shut_write = True try: self.wsock.shutdown(SHUT_WR) - except socket.error, e: + except socket.error as e: self.seterr('nowrite: %s' % e) def too_full(self): @@ -203,7 +203,7 @@ class SockWrapper: self.wsock.setblocking(False) try: return _nb_clean(os.write, self.wsock.fileno(), buf) - except OSError, e: + except OSError as e: if e.errno == errno.EPIPE: debug1('%r: uwrite: got EPIPE\n' % self) self.nowrite() @@ -225,9 +225,9 @@ class SockWrapper: self.rsock.setblocking(False) try: return _nb_clean(os.read, self.rsock.fileno(), 65536) - except OSError, e: + except OSError as e: self.seterr('uread: %s' % e) - return '' # unexpected error... we'll call it EOF + return b'' # unexpected error... we'll call it EOF def fill(self): if self.buf: @@ -235,7 +235,7 @@ class SockWrapper: rb = self.uread() if rb: self.buf.append(rb) - if rb == '': # empty string means EOF; None means temporarily empty + if rb == b'': # empty string means EOF; None means temporarily empty self.noread() def copy_to(self, outwrap): @@ -333,15 +333,15 @@ class Mux(Handler): self.channels = {} self.chani = 0 self.want = 0 - self.inbuf = '' + self.inbuf = b'' self.outbuf = [] self.fullness = 0 self.too_full = False - self.send(0, CMD_PING, 'chicken') + self.send(0, CMD_PING, b'chicken') def next_channel(self): # channel 0 is special, so we never allocate it - for timeout in xrange(1024): + for timeout in range(1024): self.chani += 1 if self.chani > MAX_CHANNEL: self.chani = 1 @@ -357,7 +357,7 @@ class Mux(Handler): def check_fullness(self): if self.fullness > 32768: if not self.too_full: - self.send(0, CMD_PING, 'rttest') + self.send(0, CMD_PING, b'rttest') self.too_full = True #ob = [] # for b in self.outbuf: @@ -366,9 +366,9 @@ class Mux(Handler): #log('outbuf: %d %r\n' % (self.amount_queued(), ob)) def send(self, channel, cmd, data): - data = str(data) - assert(len(data) <= 65535) - p = struct.pack('!ccHHH', 'S', 'S', channel, cmd, len(data)) + data + assert isinstance(data, bytes) + assert len(data) <= 65535 + p = struct.pack('!ccHHH', b'S', b'S', channel, cmd, len(data)) + data self.outbuf.append(p) debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n' % (channel, cmd_to_name.get(cmd, hex(cmd)), @@ -435,10 +435,10 @@ class Mux(Handler): self.rsock.setblocking(False) try: b = _nb_clean(os.read, self.rsock.fileno(), 32768) - except OSError, e: + except OSError as e: raise Fatal('other end: %r' % e) #log('<<< %r\n' % b) - if b == '': # EOF + if b == b'': # EOF self.ok = False if b: self.inbuf += b @@ -451,8 +451,8 @@ class Mux(Handler): if len(self.inbuf) >= (self.want or HDR_LEN): (s1, s2, channel, cmd, datalen) = \ struct.unpack('!ccHHH', self.inbuf[:HDR_LEN]) - assert(s1 == 'S') - assert(s2 == 'S') + assert(s1 == b'S') + assert(s2 == b'S') self.want = datalen + HDR_LEN if self.want and len(self.inbuf) >= self.want: data = self.inbuf[HDR_LEN:self.want] @@ -494,18 +494,21 @@ class MuxWrapper(SockWrapper): def noread(self): if not self.shut_read: + debug2('%r: done reading\n' % self) self.shut_read = True - self.mux.send(self.channel, CMD_TCP_STOP_SENDING, '') + self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b'') self.maybe_close() def nowrite(self): if not self.shut_write: + debug2('%r: done writing\n' % self) self.shut_write = True - self.mux.send(self.channel, CMD_TCP_EOF, '') + self.mux.send(self.channel, CMD_TCP_EOF, b'') self.maybe_close() def maybe_close(self): if self.shut_read and self.shut_write: + debug2('%r: closing connection\n' % self) # remove the mux's reference to us. The python garbage collector # will then be able to reap our object. self.mux.channels[self.channel] = None @@ -523,7 +526,7 @@ class MuxWrapper(SockWrapper): def uread(self): if self.shut_read: - return '' # EOF + return b'' # EOF else: return None # no data available right now @@ -552,7 +555,7 @@ def runonce(handlers, mux): r = [] w = [] x = [] - to_remove = filter(lambda s: not s.ok, handlers) + to_remove = [s for s in handlers if not s.ok] for h in to_remove: handlers.remove(h) diff --git a/sshuttle/ssyslog.py b/sshuttle/ssyslog.py index 9aa2678..023a4c8 100644 --- a/sshuttle/ssyslog.py +++ b/sshuttle/ssyslog.py @@ -1,6 +1,6 @@ import sys import os -from compat import ssubprocess +import subprocess as ssubprocess _p = None diff --git a/sshuttle/stresstest.py b/sshuttle/stresstest.py index 59abe80..931a9f5 100755 --- a/sshuttle/stresstest.py +++ b/sshuttle/stresstest.py @@ -25,10 +25,10 @@ while 1: count += 1 if count >= 16384: count = 1 - print 'cli CREATING %d' % count + print('cli CREATING %d' % count) b = struct.pack('I', count) + 'x' * count remain[c] = count - print 'cli >> %r' % len(b) + print('cli >> %r' % len(b)) c.send(b) c.shutdown(socket.SHUT_WR) clients.append(c) @@ -36,7 +36,7 @@ while 1: time.sleep(0.1) else: r = [listener] + servers + clients - print 'select(%d)' % len(r) + print('select(%d)' % len(r)) r, w, x = select.select(r, [], [], 5) assert(r) for i in r: @@ -45,7 +45,7 @@ while 1: servers.append(s) elif i in servers: b = i.recv(4096) - print 'srv << %r' % len(b) + print('srv << %r' % len(b)) if not i in remain: assert(len(b) >= 4) want = struct.unpack('I', b[:4])[0] @@ -54,34 +54,34 @@ while 1: else: want = remain[i] if want < len(b): - print 'weird wanted %d bytes, got %d: %r' % (want, len(b), b) + print('weird wanted %d bytes, got %d: %r' % (want, len(b), b)) assert(want >= len(b)) want -= len(b) remain[i] = want if not b: # EOF if want: - print 'weird: eof but wanted %d more' % want + print('weird: eof but wanted %d more' % want) assert(want == 0) i.close() servers.remove(i) del remain[i] else: - print 'srv >> %r' % len(b) + print('srv >> %r' % len(b)) i.send('y' * len(b)) if not want: i.shutdown(socket.SHUT_WR) elif i in clients: b = i.recv(4096) - print 'cli << %r' % len(b) + print('cli << %r' % len(b)) want = remain[i] if want < len(b): - print 'weird wanted %d bytes, got %d: %r' % (want, len(b), b) + print('weird wanted %d bytes, got %d: %r' % (want, len(b), b)) assert(want >= len(b)) want -= len(b) remain[i] = want if not b: # EOF if want: - print 'weird: eof but wanted %d more' % want + print('weird: eof but wanted %d more' % want) assert(want == 0) i.close() clients.remove(i) diff --git a/sshuttle/ui-macos/main.py b/sshuttle/ui-macos/main.py index 50d71ba..783498c 100644 --- a/sshuttle/ui-macos/main.py +++ b/sshuttle/ui-macos/main.py @@ -60,7 +60,7 @@ class Callback: class Runner: def __init__(self, argv, logfunc, promptfunc, serverobj): - print 'in __init__' + print('in __init__') self.id = argv self.rv = None self.pid = None @@ -70,14 +70,14 @@ class Runner: self.serverobj = serverobj self.buf = '' self.logfunc('\nConnecting to %s.\n' % self.serverobj.host()) - print 'will run: %r' % argv + print('will run: %r' % argv) self.serverobj.setConnected_(False) pid, fd = pty.fork() if pid == 0: # child try: os.execvp(argv[0], argv) - except Exception, e: + except Exception as e: sys.stderr.write('failed to start: %r\n' % e) raise finally: @@ -107,7 +107,7 @@ class Runner: self.serverobj.setConnected_(False) self.serverobj.setError_('VPN process died') self.logfunc('Disconnected.\n') - print 'wait_result: %r' % self.rv + print('wait_result: %r' % self.rv) return self.rv def wait(self): @@ -121,14 +121,14 @@ class Runner: def kill(self): assert(self.pid > 0) - print 'killing: pid=%r rv=%r' % (self.pid, self.rv) + print('killing: pid=%r rv=%r' % (self.pid, self.rv)) if self.rv is None: self.logfunc('Disconnecting from %s.\n' % self.serverobj.host()) os.kill(self.pid, 15) self.wait() def gotdata(self, notification): - print 'gotdata!' + print('gotdata!') d = str(self.file.availableData()) if d: self.logfunc(d) @@ -171,18 +171,18 @@ class SshuttleController(NSObject): def _connect(self, server): host = server.host() - print 'connecting %r' % host + print('connecting %r' % host) self.fill_menu() def logfunc(msg): - print 'log! (%d bytes)' % len(msg) + print('log! (%d bytes)' % len(msg)) self.logField.textStorage()\ .appendAttributedString_(NSAttributedString.alloc() .initWithString_(msg)) self.logField.didChangeText() def promptfunc(prompt): - print 'prompt! %r' % prompt + print('prompt! %r' % prompt) return askpass.askpass(prompt) nets_mode = server.autoNets() if nets_mode == models.NET_MANUAL: @@ -206,7 +206,7 @@ class SshuttleController(NSObject): def _disconnect(self, server): host = server.host() - print 'disconnecting %r' % host + print('disconnecting %r' % host) conn = self.conns.get(host) if conn: conn.kill() diff --git a/sshuttle/ui-macos/models.py b/sshuttle/ui-macos/models.py index 6d782c6..8be105b 100644 --- a/sshuttle/ui-macos/models.py +++ b/sshuttle/ui-macos/models.py @@ -89,7 +89,7 @@ class SshuttleServer(NSObject): return getattr(self, '_k_connected', False) def setConnected_(self, v): - print 'setConnected of %r to %r' % (self, v) + print('setConnected of %r to %r' % (self, v)) self._k_connected = v if v: self.setError_(None) # connected ok, so no error diff --git a/sshuttle/ui-macos/my.py b/sshuttle/ui-macos/my.py index e32bc46..e787c9c 100644 --- a/sshuttle/ui-macos/my.py +++ b/sshuttle/ui-macos/my.py @@ -53,7 +53,7 @@ def DelayedCallback(func, *args, **kwargs): def _go(): if flag[0]: - print 'running %r (flag=%r)' % (func, flag) + print('running %r (flag=%r)' % (func, flag)) flag[0] = 0 func(*args, **kwargs)