1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
#!../expect --
# dislocate - allow disconnection and reconnection to a background program
# Author: Don Libes, NIST
exp_version -exit 5.1
# The following code attempts to intuit whether cat buffers by default.
# The -u flag is required on HPUX (8 and 9) and IBM AIX (3.2) systems.
if [file exists $exp_exec_library/cat-buffers] {
set catflags "-u"
} else {
set catflags ""
}
# If this fails, you can also force it by commenting in one of the following.
# Or, you can use the -catu flag to the script.
#set catflags ""
#set catflags "-u"
set escape \035 ;# control-right-bracket
set escape_printable "^\]"
set pidfile "~/.dislocate"
set prefix "disc"
set timeout -1
set debug_flag 0
while {$argc} {
set flag [lindex $argv 0]
switch -- $flag \
"-catu" {
set catflags "-u"
set argv [lrange $argv 1 end]
incr argc -1
} "-escape" {
set escape [lindex $argv 1]
set escape_printable $escape
set argv [lrange $argv 2 end]
incr argc -2
} "-debug" {
log_file [lindex $argv 1]
set debug_flag 1
set argv [lrange $argv 2 end]
incr argc -2
} default {
break
}
}
# These are correct from parent's point of view.
# In child, we will reset these so that they appear backwards
# thus allowing following two routines to be used by both parent and child
set infifosuffix ".i"
set outfifosuffix ".o"
proc infifoname {pid} {
global prefix infifosuffix
return "/tmp/$prefix$pid$infifosuffix"
}
proc outfifoname {pid} {
global prefix outfifosuffix
return "/tmp/$prefix$pid$outfifosuffix"
}
proc pid_remove {pid} {
global date proc
say "removing $pid $proc($pid)"
unset date($pid)
unset proc($pid)
}
# lines in data file looks like this:
# pid#date-started#argv
# allow element lookups on empty arrays
set date(dummy) dummy; unset date(dummy)
set proc(dummy) dummy; unset proc(dummy)
# load pidfile into memory
proc pidfile_read {} {
global date proc pidfile
if [catch {open $pidfile} fp] return
#
# read info out of file
#
say "reading pidfile"
set line 0
while {[gets $fp buf]!=-1} {
# while pid and date can't have # in it, proc can
if [regexp "(\[^#]*)#(\[^#]*)#(.*)" $buf junk pid xdate xproc] {
set date($pid) $xdate
set proc($pid) $xproc
} else {
puts "warning: inconsistency in $pidfile line $line"
}
incr line
}
close $fp
say "read $line entries"
#
# see if pids and fifos are still around
#
foreach pid [array names date] {
if {$pid && [catch {exec /bin/kill -0 $pid}]} {
say "$pid no longer exists, removing"
pid_remove $pid
continue
}
# pid still there, see if fifos are
if {![file exists [infifoname $pid]] || ![file exists [outfifoname $pid]]} {
say "$pid fifos no longer exists, removing"
pid_remove $pid
continue
}
}
}
proc pidfile_write {} {
global pidfile date proc
say "writing pidfile"
set fp [open $pidfile w]
foreach pid [array names date] {
puts $fp "$pid#$date($pid)#$proc($pid)"
say "wrote $pid#$date($pid)#$proc($pid)"
}
close $fp
}
proc fifo_pair_remove {pid} {
global date proc prefix
pidfile_read
pid_remove $pid
pidfile_write
catch {exec rm -f [infifoname $pid] [outfifoname $pid]}
}
proc fifo_pair_create {pid argdate argv} {
global prefix date proc
pidfile_read
set date($pid) $argdate
set proc($pid) $argv
pidfile_write
mkfifo [infifoname $pid]
mkfifo [outfifoname $pid]
}
proc mkfifo {f} {
if [file exists $f] {
say "uh, fifo already exists?"
return
}
if 0==[catch {exec mkfifo $f}] return ;# POSIX
if 0==[catch {exec mknod $f p}] return
# some systems put mknod in wierd places
if 0==[catch {exec /usr/etc/mknod $f p}] return ;# Sun
if 0==[catch {exec /etc/mknod $f p}] return ;# AIX, Cray
puts "Couldn't figure out how to make a fifo - where is mknod?"
exit
}
proc child {argdate argv} {
global catflags infifosuffix outfifosuffix
disconnect
# these are backwards from the child's point of view so that
# we can make everything else look "right"
set infifosuffix ".o"
set outfifosuffix ".i"
set pid 0
eval spawn $argv
set proc_spawn_id $spawn_id
while {1} {
say "opening [infifoname $pid] for read"
spawn -open [open "|cat $catflags < [infifoname $pid]" "r"]
set in $spawn_id
say "opening [outfifoname $pid] for write"
spawn -open [open [outfifoname $pid] w]
set out $spawn_id
fifo_pair_remove $pid
say "interacting"
interact {
-u $proc_spawn_id eof exit
-output $out
-input $in
}
# parent has closed connection
say "parent closed connection"
catch {close -i $in}
catch {wait -i $in}
catch {close -i $out}
catch {wait -i $out}
# switch to using real pid
set pid [pid]
# put entry back
fifo_pair_create $pid $argdate $argv
}
}
proc say {msg} {
global debug_flag
if !$debug_flag return
if [catch {puts "parent: $msg"}] {
send_log "child: $msg\n"
}
}
proc escape {} {
# export process handles so that user can get at them
global in out
puts "\nto disconnect, enter: exit (or ^D)"
puts "to suspend, press appropriate job control sequence"
puts "to return to process, enter: return"
interpreter
puts "returning ..."
}
# interactively query user to choose process, return pid
proc choose {} {
global index date
while 1 {
send_user "enter # or pid: "
expect_user -re "(.*)\n" {set buf $expect_out(1,string)}
if [info exists index($buf)] {
set pid $index($buf)
} elseif [info exists date($buf)] {
set pid $buf
} else {
puts "no such # or pid"
continue
}
return $pid
}
}
if {$argc} {
# initial creation occurs before fork because if we do it after
# then either the child or the parent may have to spin retrying
# the fifo open. Unfortunately, we cannot know the pid ahead of
# time so use "0". This will be set to the real pid when the
# parent does its initial disconnect. There is no collision
# problem because the fifos are deleted immediately anyway.
set datearg [exec date]
fifo_pair_create 0 $datearg $argv
set pid [fork]
say "after fork, pid = $pid"
if $pid==0 {
child $datearg $argv
}
# parent thinks of child as pid==0 for reason given earlier
set pid 0
}
say "examining pid"
if ![info exists pid] {
global fifos date proc
say "pid does not exist"
pidfile_read
set count 0
foreach pid [array names date] {
incr count
}
if $count==0 {
puts "no connectable processes"
exit
} elseif $count==1 {
puts "one connectable process: $proc($pid)"
puts "pid $pid, started $date($pid)"
send_user "connect? \[y] "
expect_user -re "(.*)\n" {set buf $expect_out(1,string)}
if {$buf!="y" && $buf!=""} exit
} else {
puts "connectable processes:"
set count 1
puts " # pid date started process"
foreach pid [array names date] {
puts [format "%2d %6d %.19s %s" \
$count $pid $date($pid) $proc($pid)]
set index($count) $pid
incr count
}
set pid [choose]
}
}
say "opening [outfifoname $pid] for write"
spawn -noecho -open [open [outfifoname $pid] w]
set out $spawn_id
say "opening [infifoname $pid] for read"
spawn -noecho -open [open "|cat $catflags < [infifoname $pid]" "r"]
set in $spawn_id
puts "Escape sequence is $escape_printable"
proc prompt1 {} {
global argv0
return "$argv0[history nextid]> "
}
interact {
-reset $escape escape
-output $out
-input $in
}
|