import sys
import fishdescriptor.fish
+import optparse
+import re
+
+donor = None
+
+usage =
+'''
+fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
+
+<action>s
+ [<here-0fd>=]<there-fd>
+ fish the openfile referenced by descriptor <there-fd> in
+ (the most recent) <pid> and keep a descriptor onto it;
+ and, optionally, give it the number <here-fd> for exec
+ exec <program> [<arg>...]
+ execute a process with each specified <here>
+ as an actual fd
+ sockinfo
+ calls getsockname/getpeername on the most recent
+ <there-fd>
+
+ -p|-pid <pid>
+ now attach to <pid>, 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 = map(int, sys.argv[2:])
+fds = [int(x) for x in sys.argv[2:]]
d = fishdescriptor.fish.Donor(pid)
r = d.fish(fds)