chiark / gitweb /
fishdescriptor: sort out debugging output
[chiark-utils.git] / fishdescriptor / fishdescriptor
index bdddff2..20eddfe 100755 (executable)
@@ -4,12 +4,13 @@ import sys
 import fishdescriptor.fish
 import optparse
 import re
+import subprocess
+import socket
+import os
 
 donor = None
 
-usage =
-'''
-fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
+usage = '''fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
 
 <action>s
   [<here-0fd>=]<there-fd>
@@ -27,22 +28,22 @@ fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
           now attach to <pid>, detaching from previous pid
 '''
 
-fdmap = { }
-# fdmap[here] = (actual_fd_number_here, Donor, target_fd)
+pending = []
+# list of (nominal, there) where nominal might be None
 
-last_here = None
+fdmap = { }
+# fdmap[nominal] = (actual, Donor, there)
 
 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]
+    actuals = donor.fish([pend[1] for pend in pending])
+    assert(len(actuals) == len(pending))
+    for (nominal, there), actual in zip(pending, actuals):
+        overwriting_info = fdmap.get(nominal)
+        if overwriting_info is not None: os.close(overwriting_info[0])
+        fdmap[nominal] = (actual, donor, there)
+
+def implement_sockinfo(nominal):
+    (actual, tdonor, there) = fdmap[nominal]
     # 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
@@ -61,14 +62,14 @@ def implmement_sockinfo(here):
         print $family, "\n" or die $!;
     '''
     famp = subprocess.Popen(
-        stdin = fd,
+        stdin = actual,
         stdout = subprocess.PIPE,
         args = ['perl','-we',perl_script]
     )
     (output, dummy) = famp.communicate()
     family = int(output)
 
-    sock = socket.fromfd(fd, family, 0)
+    sock = socket.fromfd(actual, family, 0)
 
     print("[%s] %d sockinfo" % (tdonor.pid, there), end='')
     for f in (lambda: socket.AddressFamily(family).name,
@@ -82,22 +83,41 @@ def implmement_sockinfo(here):
     sock.close()
 
 def permute_fds_for_exec():
-    actual_map = { info[0]: intent for intent, info in fdmap.items }
-    for intent, (actual, tdonor, intarget) in fdmap.items():
-        if intent is not None:
-            in_way = actual_map.get(intent)
-            if in_way is not None:
-                moved = os.dup(intent)
-                actual_map[moved] = 
+    actual2intended = { info[0]: nominal for nominal, info in fdmap.items() }
+    # invariant at the start of each loop iteration:
+    #     for each intended (aka `nominal') we have processed:
+    #         relevant open-file is only held in fd intended
+    #         (unless `nominal' is None in which case it is closed)
+    #     for each intended (aka `nominal') we have NOT processed:
+    #         relevant open-file is only held in actual
+    #         where  actual = fdmap[nominal][0]
+    #         and where  actual2intended[actual] = nominal
+    # we can rely on processing each intended only once,
+    #  since they're hash keys
+    # the post-condition is not really a valid state (fdmap
+    #  is nonsense) but we call this function just before exec
+    for intended, (actual, tdonor, there) in fdmap.items():
+        if intended == actual:
+            continue
+        if intended is not None:
+            inway_intended = actual2intended.get(intended)
+            if inway_intended is not None:
+                inway_moved = os.dup(intended)
+                actual2intended[inway_moved] = inway_intended
+                fdmap[inway_intented][0] = inway_moved
+            os.dup2(actual, intended)
+        os.close(actual)
 
 def implement_exec(argl):
     if donor is not None: donor.detach()
-    
+    sys.stdout.flush()
+    permute_fds_for_exec()
+    os.execvp(argl[0], argl)
 
 def set_donor(pid):
     global donor
     if donor is not None: donor.detach()
-    donor = fishdescriptor.fish.Donor(pid)
+    donor = fishdescriptor.fish.Donor(pid, debug=ov.debug)
 
 def ocb_set_donor(option, opt, value, parser):
     set_donor(value)
@@ -105,6 +125,10 @@ def ocb_set_donor(option, opt, value, parser):
 ov = optparse.Values()
 
 def process_args():
+    global ov
+
+    m = None
+    
     def arg_matches(regexp):
         nonlocal m
         m = re.search(regexp, arg)
@@ -114,10 +138,13 @@ def process_args():
 
     op.disable_interspersed_args()
     op.add_option('-p','--pid', type='int', action='callback',
-                  callback='ocb_set_donor')
+                  callback=ocb_set_donor)
+    op.add_option('-D','--debug', action='store_const',
+                  dest='debug', const=sys.stderr)
+    ov.debug = None
 
-    args = sys.argv
-    pending = []
+    args = sys.argv[1:]
+    last_nominal = None # None or (nominal,) ie None or (None,) or (int,)
 
     while True:
         (ov, args) = op.parse_args(args=args, values=ov)
@@ -125,38 +152,25 @@ def process_args():
 
         arg = args.pop(0)
 
-        if donor is not None:
+        if donor is None:
             set_donor(int(arg))
         elif arg_matches(r'^(?:(\d+)=)?(\d+)?$'):
-            (here, there) = m.groups()
-            here = None if here is None else int(here)
+            (nominal, there) = m.groups()
+            nominal = None if nominal is None else int(nominal)
             there = int(there)
-            pending.append = (here,there)
-        elif arg = 'exec':
+            pending.append((nominal,there))
+            last_nominal = (nominal,)
+        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:
+        elif arg == 'sockinfo':
+            if last_nominal is None:
                 op.error('sockinfo needs a prior fd spec')
             implement_pending()
-            implement_sockinfo(last_here)
+            implement_sockinfo(last_nominal[0])
         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))
+process_args()