chiark / gitweb /
fishdescriptor: wip utility, before rename various variables
[chiark-utils.git] / fishdescriptor / fishdescriptor
1 #!/usr/bin/python3
2
3 import sys
4 import fishdescriptor.fish
5 import optparse
6 import re
7
8 donor = None
9
10 usage =
11 '''
12 fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
13
14 <action>s
15   [<here-0fd>=]<there-fd>
16           fish the openfile referenced by descriptor <there-fd> in
17           (the most recent) <pid> and keep a descriptor onto it;
18           and, optionally, give it the number <here-fd> for exec
19   exec <program> [<arg>...]
20           execute a process with each specified <here>
21           as an actual fd
22   sockinfo
23           calls getsockname/getpeername on the most recent
24           <there-fd>
25
26   -p|-pid <pid>
27           now attach to <pid>, detaching from previous pid
28 '''
29
30 fdmap = { }
31 # fdmap[here] = (actual_fd_number_here, Donor, target_fd)
32
33 last_here = None
34
35 def implement_pending():
36     got_list = donor.fish([x[1] for x in pending])
37     assert(len(got_list) = len(pending))
38     for (here, there), got in zip(pending, got_list)
39         overwriting = fdmap.get(here)
40         if overwriting is not None: os.close(overwriting)
41         fdmap[here] = (got, Donor, there)
42         last_here = here
43
44 def implmement_sockinfo(here):
45     (fd, tdonor, there) = fdmap[here]
46     # socket.fromfd requires the AF.  But of course we don't know the AF.
47     # There isn't a sane way to get it in Python:
48     #  https://utcc.utoronto.ca/~cks/space/blog/python/SocketFromFdMistake
49     # Rejected options:
50     #  https://github.com/tiran/socketfromfd
51     #   adds a dependency, not portable due to reliance on SO_DOMAIN
52     #  call getsockname using ctypes
53     #   no sane way to discover how to unpack sa_family_t
54     perl_script = '''
55         use strict;
56         use Socket;
57         use POSIX;
58         my $sa = getsockname STDIN;
59         exit 0 if !defined $sa and $!==ENOTSOCK;
60         my $family = sockaddr_family $sa;
61         print $family, "\n" or die $!;
62     '''
63     famp = subprocess.Popen(
64         stdin = fd,
65         stdout = subprocess.PIPE,
66         args = ['perl','-we',perl_script]
67     )
68     (output, dummy) = famp.communicate()
69     family = int(output)
70
71     sock = socket.fromfd(fd, family, 0)
72
73     print("[%s] %d sockinfo" % (tdonor.pid, there), end='')
74     for f in (lambda: socket.AddressFamily(family).name,
75               lambda: repr(sock.getsockname()),
76               lambda: repr(sock.getpeername())):
77         try: info = f()
78         except Exception as e: info = repr(e)
79         print("\t", info, sep='', end='')
80     print("")
81
82     sock.close()
83
84 def permute_fds_for_exec():
85     actual_map = { info[0]: intent for intent, info in fdmap.items }
86     for intent, (actual, tdonor, intarget) in fdmap.items():
87         if intent is not None:
88             in_way = actual_map.get(intent)
89             if in_way is not None:
90                 moved = os.dup(intent)
91                 actual_map[moved] = 
92
93 def implement_exec(argl):
94     if donor is not None: donor.detach()
95     
96
97 def set_donor(pid):
98     global donor
99     if donor is not None: donor.detach()
100     donor = fishdescriptor.fish.Donor(pid)
101
102 def ocb_set_donor(option, opt, value, parser):
103     set_donor(value)
104
105 ov = optparse.Values()
106
107 def process_args():
108     def arg_matches(regexp):
109         nonlocal m
110         m = re.search(regexp, arg)
111         return m
112
113     op = optparse.OptionParser(usage=usage)
114
115     op.disable_interspersed_args()
116     op.add_option('-p','--pid', type='int', action='callback',
117                   callback='ocb_set_donor')
118
119     args = sys.argv
120     pending = []
121
122     while True:
123         (ov, args) = op.parse_args(args=args, values=ov)
124         if not len(args): break
125
126         arg = args.pop(0)
127
128         if donor is not None:
129             set_donor(int(arg))
130         elif arg_matches(r'^(?:(\d+)=)?(\d+)?$'):
131             (here, there) = m.groups()
132             here = None if here is None else int(here)
133             there = int(there)
134             pending.append = (here,there)
135         elif arg = 'exec':
136             if not len(args):
137                 op.error("exec needs command to run")
138             implement_pending()
139             implement_exec(args)
140         elif arg = 'sockinfo':
141             if last_here is None:
142                 op.error('sockinfo needs a prior fd spec')
143             implement_pending()
144             implement_sockinfo(last_here)
145         else:
146             op.error("unknown argument/option `%s'" % arg)
147
148     implement_pending()
149     
150
151             there = int(m.group[1])
152             here = None if m.group
153
154             ,here) = map(int, m.groups())
155             
156
157 pid = int(sys.argv[1])
158 fds = [int(x) for x in sys.argv[2:]]
159
160 d = fishdescriptor.fish.Donor(pid)
161 r = d.fish(fds)
162 print(repr(r))