#!/usr/bin/python3 import sys import fishdescriptor.fish import optparse import re donor = None usage = ''' fishdescriptor [-p|--pid] ... [-p|--pid ...] s [=] fish the openfile referenced by descriptor in (the most recent) and keep a descriptor onto it; and, optionally, give it the number for exec exec [...] execute a process with each specified as an actual fd sockinfo calls getsockname/getpeername on the most recent -p|-pid now attach to , detaching from previous pid ''' fdmap = { } # fdmap[here] = (actual_fd_number_here, Donor, target_fd) last_here = None def implement_pending(): got_list = donor.fish([x[1] for x in pending]) assert(len(got_list) = len(pending)) for (here, there), got in zip(pending, got_list) overwriting = fdmap.get(here) if overwriting is not None: os.close(overwriting) fdmap[here] = (got, Donor, there) last_here = here def implmement_sockinfo(here): (fd, tdonor, there) = fdmap[here] # socket.fromfd requires the AF. But of course we don't know the AF. # There isn't a sane way to get it in Python: # https://utcc.utoronto.ca/~cks/space/blog/python/SocketFromFdMistake # Rejected options: # https://github.com/tiran/socketfromfd # adds a dependency, not portable due to reliance on SO_DOMAIN # call getsockname using ctypes # no sane way to discover how to unpack sa_family_t perl_script = ''' use strict; use Socket; use POSIX; my $sa = getsockname STDIN; exit 0 if !defined $sa and $!==ENOTSOCK; my $family = sockaddr_family $sa; print $family, "\n" or die $!; ''' famp = subprocess.Popen( stdin = fd, stdout = subprocess.PIPE, args = ['perl','-we',perl_script] ) (output, dummy) = famp.communicate() family = int(output) sock = socket.fromfd(fd, family, 0) print("[%s] %d sockinfo" % (tdonor.pid, there), end='') for f in (lambda: socket.AddressFamily(family).name, lambda: repr(sock.getsockname()), lambda: repr(sock.getpeername())): try: info = f() except Exception as e: info = repr(e) print("\t", info, sep='', end='') print("") sock.close() def implement_exec(argl): pass def set_donor(pid): global donor if donor is not None: donor.detach() donor = fishdescriptor.fish.Donor(pid) def ocb_set_donor(option, opt, value, parser): set_donor(value) ov = optparse.Values() def process_args(): def arg_matches(regexp): nonlocal m m = re.search(regexp, arg) return m op = optparse.OptionParser(usage=usage) op.disable_interspersed_args() op.add_option('-p','--pid', type='int', action='callback', callback='ocb_set_donor') args = sys.argv pending = [] while True: (ov, args) = op.parse_args(args=args, values=ov) if not len(args): break arg = args.pop(0) if donor is not None: set_donor(int(arg)) elif arg_matches(r'^(?:(\d+)=)?(\d+)?$'): (here, there) = m.groups() here = None if here is None else int(here) there = int(there) pending.append = (here,there) elif arg = 'exec': if not len(args): op.error("exec needs command to run") implement_pending() implement_exec(args) elif arg = 'sockinfo': if last_here is None: op.error('sockinfo needs a prior fd spec') implement_pending() implement_sockinfo(last_here) else: op.error("unknown argument/option `%s'" % arg) implement_pending() there = int(m.group[1]) here = None if m.group ,here) = map(int, m.groups()) pid = int(sys.argv[1]) fds = [int(x) for x in sys.argv[2:]] d = fishdescriptor.fish.Donor(pid) r = d.fish(fds) print(repr(r))