4 import fishdescriptor.fish
10 usage = '''fishdescriptor [-p|--pid] <pid> <action>... [-p|--pid <pid> <action>...]
13 [<here-0fd>=]<there-fd>
14 fish the openfile referenced by descriptor <there-fd> in
15 (the most recent) <pid> and keep a descriptor onto it;
16 and, optionally, give it the number <here-fd> for exec
17 exec <program> [<arg>...]
18 execute a process with each specified <here>
21 calls getsockname/getpeername on the most recent
25 now attach to <pid>, detaching from previous pid
29 # list of (nominal, there) where nominal might be None
32 # fdmap[nominal] = (actual, Donor, there)
36 def implement_pending():
37 actuals = donor.fish([pend[1] for pend in pending])
38 assert(len(actuals) == len(pending))
39 for (nominal, there), actual in zip(pending, actuals)
40 overwriting_info = fdmap.get(nominal)
41 if overwriting_info is not None: os.close(overwriting_info[0])
42 fdmap[nominal] = (actual, Donor, there)
43 last_nominal = nominal
45 def implmement_sockinfo(nominal):
46 (actual, tdonor, there) = fdmap[nominal]
47 # socket.fromfd requires the AF. But of course we don't know the AF.
48 # There isn't a sane way to get it in Python:
49 # https://utcc.utoronto.ca/~cks/space/blog/python/SocketFromFdMistake
51 # https://github.com/tiran/socketfromfd
52 # adds a dependency, not portable due to reliance on SO_DOMAIN
53 # call getsockname using ctypes
54 # no sane way to discover how to unpack sa_family_t
59 my $sa = getsockname STDIN;
60 exit 0 if !defined $sa and $!==ENOTSOCK;
61 my $family = sockaddr_family $sa;
62 print $family, "\n" or die $!;
64 famp = subprocess.Popen(
66 stdout = subprocess.PIPE,
67 args = ['perl','-we',perl_script]
69 (output, dummy) = famp.communicate()
72 sock = socket.fromfd(fd, family, 0)
74 print("[%s] %d sockinfo" % (tdonor.pid, there), end='')
75 for f in (lambda: socket.AddressFamily(family).name,
76 lambda: repr(sock.getsockname()),
77 lambda: repr(sock.getpeername())):
79 except Exception as e: info = repr(e)
80 print("\t", info, sep='', end='')
85 def permute_fds_for_exec():
86 actual2intended = { info[0]: nominal for nominal, info in fdmap.items }
87 # invariant at the start of each loop iteration:
88 # for each intended (aka `nominal') we have processed:
89 # relevant open-file is only held in fd intended
90 # (unless `nominal' is None in which case it is closed)
91 # for each intended (aka `nominal') we have NOT processed:
92 # relevant open-file is only held in actual
93 # where actual = fdmap[nominal][0]
94 # and where actual2intended[actual] = nominal
95 # we can rely on processing each intended only once,
96 # since they're hash keys
97 # the post-condition is not really a valid state (fdmap
98 # is nonsense) but we call this function just before exec
99 for intended, (actual, tdonor, there) in fdmap.items():
100 if intended == actual:
102 if intended is not None:
103 inway_intended = actual2intended.get(intended)
104 if inway_intended is not None:
105 inway_moved = os.dup(intended)
106 actual2intended[inway_moved] = inway_intended
107 fdmap[inway_intented][0] = inway_moved
108 os.dup2(actual, intended)
111 def implement_exec(argl):
112 if donor is not None: donor.detach()
114 permut_fds_for_exec()
115 os.execvp(argl[0], argl)
119 if donor is not None: donor.detach()
120 donor = fishdescriptor.fish.Donor(pid)
122 def ocb_set_donor(option, opt, value, parser):
125 ov = optparse.Values()
128 def arg_matches(regexp):
130 m = re.search(regexp, arg)
133 op = optparse.OptionParser(usage=usage)
135 op.disable_interspersed_args()
136 op.add_option('-p','--pid', type='int', action='callback',
137 callback='ocb_set_donor')
142 (ov, args) = op.parse_args(args=args, values=ov)
143 if not len(args): break
147 if donor is not None:
149 elif arg_matches(r'^(?:(\d+)=)?(\d+)?$'):
150 (nominal, there) = m.groups()
151 nominal = None if nominal is None else int(nominal)
153 pending.append = (nominal,there)
156 op.error("exec needs command to run")
159 elif arg = 'sockinfo':
160 if last_nominal is None:
161 op.error('sockinfo needs a prior fd spec')
163 implement_sockinfo(last_nominal)
165 op.error("unknown argument/option `%s'" % arg)
170 there = int(m.group[1])
171 nominal = None if m.group
173 ,nominal) = map(int, m.groups())
176 pid = int(sys.argv[1])
177 fds = [int(x) for x in sys.argv[2:]]
179 d = fishdescriptor.fish.Donor(pid)