chiark / gitweb /
fishdescriptor: wip utility
[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 implement_exec(argl):
85     pass
86
87 def set_donor(pid):
88     global donor
89     if donor is not None: donor.detach()
90     donor = fishdescriptor.fish.Donor(pid)
91
92 def ocb_set_donor(option, opt, value, parser):
93     set_donor(value)
94
95 ov = optparse.Values()
96
97 def process_args():
98     def arg_matches(regexp):
99         nonlocal m
100         m = re.search(regexp, arg)
101         return m
102
103     op = optparse.OptionParser(usage=usage)
104
105     op.disable_interspersed_args()
106     op.add_option('-p','--pid', type='int', action='callback',
107                   callback='ocb_set_donor')
108
109     args = sys.argv
110     pending = []
111
112     while True:
113         (ov, args) = op.parse_args(args=args, values=ov)
114         if not len(args): break
115
116         arg = args.pop(0)
117
118         if donor is not None:
119             set_donor(int(arg))
120         elif arg_matches(r'^(?:(\d+)=)?(\d+)?$'):
121             (here, there) = m.groups()
122             here = None if here is None else int(here)
123             there = int(there)
124             pending.append = (here,there)
125         elif arg = 'exec':
126             if not len(args):
127                 op.error("exec needs command to run")
128             implement_pending()
129             implement_exec(args)
130         elif arg = 'sockinfo':
131             if last_here is None:
132                 op.error('sockinfo needs a prior fd spec')
133             implement_pending()
134             implement_sockinfo(last_here)
135         else:
136             op.error("unknown argument/option `%s'" % arg)
137
138     implement_pending()
139     
140
141             there = int(m.group[1])
142             here = None if m.group
143
144             ,here) = map(int, m.groups())
145             
146
147 pid = int(sys.argv[1])
148 fds = [int(x) for x in sys.argv[2:]]
149
150 d = fishdescriptor.fish.Donor(pid)
151 r = d.fish(fds)
152 print(repr(r))