chiark / gitweb /
fishdescriptor: wip utility
[chiark-utils.git] / fishdescriptor / fishdescriptor
index b8c732a67270a5785b42e47bcf8d0915c6d696d0..074221c367f312b175858065719c70ea01fd1f22 100755 (executable)
@@ -2,21 +2,22 @@
 
 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
@@ -26,42 +27,122 @@ fishdescriptor <pid> <action>... [-p|--pid <pid> <action>...]
           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:]]