import sys
import fishdescriptor.fish
+import optparse
import re
donor = None
usage =
'''
-fishdescriptor <pid> <action>... [-p|--pid <pid> <action>...]
+fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
<action>s
- <there-fd>[=<here>]
+ [<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> for exec
+ and, optionally, give it the number <here-fd> for exec
exec <program> [<arg>...]
- execute a process with each specified numeric <here>
+ execute a process with each specified <here>
as an actual fd
sockinfo
calls getsockname/getpeername on the most recent
now attach to <pid>, detaching from previous pid
'''
-def set_donor(pid_arg):
- pid = int(pid_arg)
+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
- arg_ix = 0
- def next_arg():
- sys.argv[arg_ix] =
+ op = optparse.OptionParser(usage=usage)
- while arg_ix < len(sys.argv):
- arg = next_arg()
- if arg_matches(r'^-p(\d+)') or
- arg_matches(r'^--pid=(\d+)$')
- set_donor(m.groups(1)[0])
-
- pid =
-(?:-p|^--pid(?:=|$))(\d*)$'):
-
- pid = int(m.groups(1)[0])
- elif arg_matches(r'^(?:-p|^--pid=)(\d+)$'):
-
- if expecting_pid:
- set_donor(arg)
- el
- elif arg_matches(r'\d+
-
- parser = 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
- parser.add_option("-p", "--pid", type='int', callback=set_donor)
- parser.add_option(
+ 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:]]