summaryrefslogtreecommitdiff
path: root/9harness/zmxify
diff options
context:
space:
mode:
Diffstat (limited to '9harness/zmxify')
-rwxr-xr-x9harness/zmxify176
1 files changed, 176 insertions, 0 deletions
diff --git a/9harness/zmxify b/9harness/zmxify
new file mode 100755
index 0000000..5512ffa
--- /dev/null
+++ b/9harness/zmxify
@@ -0,0 +1,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