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
|
#!/usr/lib/plan9/bin/rc
# zmxify - move a live harness session into a zmx session
#
# Lists the agents 9harness serves under /active, offers them to fzf, and
# writes the chosen zmx session name into that agent's `zmx` file. The
# daemon does the rest: it kills the harness and re-execs it under zmx
# with its session resumed.
#
# usage: zmxify [-n] [dir]
# dir only agents running there; all of them if omitted
# -n print the plan, write nothing
#
# This is the whole script now. Everything it used to work out for
# itself - which processes are harnesses, which stored session each one
# is writing, whether it already lives in zmx, and how to resume it -
# is a file under /active, resolved by the daemon and covered by its
# tests. Nothing here parses /proc, opens an fd table or queries a
# database, and nothing here kills anything: a name written into `zmx`
# is refused unless the session behind it was resolved first, so a move
# always has somewhere to come back to.
path=(/usr/bin /bin $path)
nl='
'
tab=' '
dry=()
here=()
if(~ $1 -n){
dry=y
shift
}
if(! ~ $#* 0){
here=`$nl {readlink -f $1}
if(~ $#here 0 || ! test -d $here){
echo 'zmxify: '^$1^': not a directory' >[1=2]
exit dir
}
shift
}
if(! ~ $#* 0){
echo 'usage: zmxify [-n] [dir]' >[1=2]
exit usage
}
mnt=$NINE_MOUNT
if(~ $#mnt 0)
mnt=/mnt/9p
act=$mnt/harness/active
if(! test -d $act){
echo 'zmxify: no harness fs at '^$act >[1=2]
echo ' start it with: zmx run harness -d 9harness --allow-move' >[1=2]
echo ' and run this inside a 9ns mount (an interactive fish already is)' >[1=2]
exit nofs
}
# A field that is absent (the harness does not publish it) and one that
# is present but empty (`zmx` on an agent outside zmx) both answer `-`:
# a null list would blow up the concatenations below.
fn field { # field dir name: its contents, or -
fv=()
if(test -e $1/$2)
fv=`$nl {cat $1/$2 >[2]/dev/null}
if(~ $#fv 0)
fv=-
echo $fv
}
# An agent already under zmx has nowhere to go: that is what this moves
# things into. Its own session is *not* excluded - moving the terminal
# you typed this into is the common case, and it is safe, because the
# daemon does the killing and the re-exec. This script only has to get
# the write out; it may die the instant after, and the move still lands.
# Ancestry is worked out only to know whether we will survive to attach.
fn ancestry { # ancestry pid: it and every parent of it
aup=$1
while(! ~ $aup 0 1){
echo $aup
aup=`{sed -n 's/^PPid:[ ]*//p' /proc/$aup/status >[2]/dev/null}
if(~ $#aup 0)
aup=0
}
}
mine=`$nl {ancestry $pid}
lines=()
for(h in `{ls $act >[2]/dev/null}){
for(d in `{ls $act/$h >[2]/dev/null}){
a=$act/$h/$d
acwd=`$nl {field $a cwd}
asess=`$nl {field $a session}
azmx=`{field $a zmx}
take=()
if(~ $#here 0 || ~ $acwd $here)
take=y
# Already under zmx: there is nothing to move it into.
if(! ~ $azmx -)
take=()
# Its session did not resolve, so the daemon would refuse
# the write; offering it would only produce an error.
if(~ $asess -)
take=()
if(! ~ $#take 0){
short=$acwd
if(! ~ $#home 0)
short=`$nl {echo -n $acwd | sed 's,^'^$home^',~,'}
avia=`{field $a via}
atitle=`$nl {field $a title}
lines=($lines $d^$tab^$h^$tab^$short^$tab^'via='^$avia^$tab^$asess^$tab^$"atitle)
}
}
}
scope=(all directories)
if(! ~ $#here 0)
scope=$here
if(~ $#lines 0){
echo 'zmxify: no movable harness session in '^$"scope >[1=2]
exit 0
}
fzfopts=(--no-multi --reverse --height=40% --delimiter=$tab --prompt='zmxify ' --header='move under zmx ['^$"scope^']'^$nl^'pid harness dir via session title')
sel=`$nl {{for(l in $lines) echo $l} | fzf $fzfopts}
if(~ $#sel 0){
echo 'zmxify: nothing picked' >[1=2]
exit 0
}
pick=`{echo $sel | awk '{print $1}'}
harness=`{echo $sel | awk -F$tab '{print $2}'}
a=$act/$harness/$pick
if(! test -d $a){
echo 'zmxify: '^$harness^' pid '^$pick^' is gone' >[1=2]
exit gone
}
pickcwd=`$nl {field $a cwd}
# A zmx session name nobody is using yet. zmx posts every session into
# the registry, so the registry is what says which names are taken.
zbase=`{basename $pickcwd | tr -c 'A-Za-z0-9'^$nl -}
if(~ $#zbase 0)
zbase=session
zname=$harness^-^$zbase
reg=$XDG_RUNTIME_DIR^/9p/zmx
if(~ $#XDG_RUNTIME_DIR 0)
reg=/run/user/^`{id -u}^/9p/zmx
n=()
while(test -e $reg/$zname){
n=($n x)
zname=$harness^-^$zbase^-^$#n
}
if(! ~ $#dry 0){
echo 'move ' $harness 'pid' $pick 'in' $pickcwd
echo 'session' `{field $a via} `{field $a session}
echo 'write ' $zname '>' $a/zmx
echo 'then ' zmx attach $zname
exit 0
}
echo 'zmxify: moving' $harness 'pid' $pick 'into zmx session' $zname
# Moving something we hang off means this shell goes with it. Say the
# attach line first, because there may be no `we` left to say it after.
if(~ $pick $mine)
echo 'zmxify: that is this terminal; it will drop. reattach with: zmx attach '^$zname
# The write returns when the new session is up, or fails having changed
# nothing it could avoid changing. The daemon owns the kill and the
# re-exec, so the move lands even if this process dies mid-write.
if(! echo $zname > $a/zmx){
echo 'zmxify: the move was refused' >[1=2]
exit move
}
if(~ $pick $mine)
exit 0
exec zmx attach $zname
|