4 # c/$conn(super) 0 or 1
6 # [msel/$conn "$msg "] 0 or 1
7 # [mreplay/$conn "$msg "] 0 or 1 during replay only
8 # c/$conn(q) [list $inputline ...]
11 # $queueing [list $conn ...]
13 # $permissions [list allow|super|deny $ipaddrhex $maskhex ...]
15 # $realtime_retry [list $timeoutid $awaitedpongmsg|{} $buf]
16 # $replay("$pri $key") $rhs $pri is \d\d; causes replay of "$key $rhs"
17 # $detect0($seg) unset -> 1 or irrelevant; [after ...]
20 # configs set directly in multiplex-config:
21 # $records [list filename.record ...]
24 # $conn is in $queueing iff c/$conn(q) is nonempty
26 # globals relating to realtime's command execution state:
27 # awaiting awaiting executing global
28 # idle executing/nak ack internal cmd dead
30 # $realtime set set set ? unset
31 # $currentcmd unset $cmd $cmd $cmd unset
32 # $currentconn unset $conn or {} $conn or {} $conn unset
33 # $executing 0 0 1 2 0
35 # globals relating to realtime start/stop:
36 # manual ready to running awaiting off
37 # $realtime any unset set unset
38 # $realtime_retry unset {} {} [list ...]
40 # replay priorities and messages:
42 # 40 warning realtime-failed
43 # 41 warning save-dump-failed
49 # picio out polarity ...
53 catch { set libdir $env(TRAINS_HOSTSIDE) }
54 source $libdir/lib.tcl
56 #---------- replay, general utilities, etc. ----------
58 proc compile-glob-patterns {pats procname} {
59 if {[llength $pats] > 20 || [string length $pats] > 200} {
60 cmderr LimitExceeded "too many, or too long, patterns"
63 append def " switch -regexp -- \$m {\n"
65 set neg [regsub {^~} $pat {} pat]
66 if {[regexp {[^-+./&|:=0-9a-zA-Z_*?]} $pat]} {
67 cmderr BadCmd "pattern contains invalid character"
69 regsub -all {[-+./&|:=]} $pat {\\&} pat
70 regsub -all {_} $pat {\s+} pat
71 regsub -all {\*} $pat {\S+} pat
72 regsub -all {\?} $pat {.} pat
74 append def " [list ^$pat " return [expr {!$neg}] "]\n"
76 append def { {^[-&]\S|^\+debug\s} { return 0 }} "\n"
78 append def " return 1\n"
79 proc $procname {m} $def
82 proc nargs {l {n 0}} {
83 if {[llength $l]!=$n} { cmderr BadCmd "wrong number of arguments" }
86 proc cmderr {ecode emsg} { error $emsg "" [list TRAIN CMDERR $ecode] }
92 {^\<[<&] picioh (?:in msg|out) 8[89a-f]|^\<[<&] picio (?:in pong|out ping)} \
98 proc xmit-relevantly {m {tlog 0}} {
99 global executing currentconn conns
101 puts "@[clock format [clock seconds] -format {%Y-%m-%d %T %Z}] $m"
105 set myconn $currentconn
106 if {[string length $currentconn]} {
107 trapping xmit-only-noreport $currentconn +$m
115 foreach conn [array names conns] {
116 if {[string compare $myconn $conn]} {
117 trapping xmit-only-noreport $conn $othersm
122 proc xmit-relevantly-savereplay {pri key rhs {tlog 0}} {
124 upvar #0 replay($pk) rep
126 xmit-relevantly "$key $rhs" $tlog
129 proc savereplay-clear {pk} {
130 upvar #0 replay($pk) rep; catch { unset rep }
133 proc savereplay-clear-re {re} { # re is anchored at start
135 if {![info exists replay]} return
136 foreach pk [array names replay] {
137 if {[regexp -- ^$re "$pk "]} { unset replay($pk) }
141 proc save-dump-failed-warn {fpre howpre emsg} {
143 switch -glob $errorCode {
145 set k [lindex $errorCode 1]
146 set m [lindex $errorCode 2]
147 xmit-relevantly-savereplay 41 \
148 "warning save-dump-failed" "$fpre$k : $howpre$m"
151 xmit-relevantly-savereplay 41 \
152 "warning save-dump-failed" "$fpre: $emsg"
160 savereplay-clear "41 warning save-dump-failed"
161 savereplay-clear "42 info save-dump"
163 set now [clock seconds]
164 set now [clock format $now -format %Y-%m-%dT%H-%M-%S%z]
165 set dumpdir +dump.$now
170 save-dump-failed-warn "" "mkdir $dumpdir: " $emsg
171 error $emsg "" {TRAIN REPORTED}
176 +persist.data +persist.data.new +persist.data.old
177 +persist.conv +persist.conv.new +persist.conv.old
179 if {[catch { link $f $dumpdir/$f } emsg]} {
180 switch -glob $errorCode {
182 * { save-dump-failed-warn "$f " "link $f $dumpdir/: " $emsg }
186 xmit-relevantly-savereplay 42 "info save-dump" "$dumpdir" 1
189 #---------- multiplexer-implemented command ----------
191 proc local/select {conn args} {
193 compile-glob-patterns $args msel/$conn
196 proc global/!save-dump {conn args} {
198 if {[catch { save-dump } emsg]} {
199 cmderr HostSupportSystemsProblem "failed to save dump: $emsg"
203 proc do-replay {conn} {
205 foreach pk [lsort [array names replay]] {
206 set pri [string range $pk 0 2]
207 set lhs [string range $pk 3 end]
208 set r [string trimright $replay($pk) "\n"]
209 foreach m [split $r "\n"] {
210 puts "<$conn|$pri|$lhs $m"
211 xmit-only-noreport $conn "|$lhs $m"
216 proc local/replay {conn args} {
217 if {[llength $args]} {
218 rename msel/$conn mreplay/$conn ;# park it here for a moment
219 compile-glob-patterns $args msel/$conn
222 if {[llength $args]} {
224 rename mreplay/$conn msel/$conn
228 proc local/select-replay {conn args} {
230 compile-glob-patterns $args msel/$conn
234 #---------- automatic realtime restart ----------
236 proc global/!realtime {conn args} {
237 global realtime realtime_retry
239 set how [lindex $args 0]
241 # perhaps kill the running instance
242 # this switch also checks the argument
243 switch -exact -- $how {
244 kill - stop - restart - start - start-manual {
245 if {[info exists realtime]} {
246 realtime-failed killed "termination requested by command"
252 cmderr BadCmd "unknown !realtime subcommand"
256 # set the operating mode
257 switch -exact -- $how {
260 set realtime_retry {}
262 stop - start - start-manual {
264 catch { unset realtime_retry }
268 # (re)start if applicable
269 switch -exact -- $how {
277 after idle realtime-retry-check
282 proc realtime-retry-reset {} {
283 global realtime_retry serchan
284 if {![info exists realtime_retry]} return
285 if {![llength $realtime_retry]} return
286 manyset $realtime_retry timeoutid
287 fileevent $serchan readable {}
288 after cancel $timeoutid
289 set realtime_retry {}
292 proc realtime-retry-check {} {
293 global realtime_retry realtime
294 if {![info exists realtime_retry]} return
295 if {[llength $realtime_retry]} return
296 if {[info exists realtime]} return
300 proc realtime-retry-realtime-failed {} {
301 global realtime_retry serchan
302 if {![info exists realtime_retry]} return
303 if {[llength $realtime_retry]} { error "huh? $realtime_retry" }
304 fileevent $serchan readable realtime-retry-serchan-readable
305 set after [after 500 realtime-retry-send-ping]
306 set realtime_retry [list $after {} {}]
309 proc realtime-retry-send-ping {} {
310 global realtime_retry urandom serchan
311 manyset $realtime_retry after pong buf
312 set x [read $urandom 2]
314 if {[string length $x] != 4} { error "urandom short read `$x'" }
315 set x [expr "0x$x & 0x077f"]
316 set ping [format %04x [expr {0x8800 ^ $x}]]
317 set pong [format %04x [expr {0x885a ^ $x}]]
318 #puts "<! picioh out await-off $pong (await $pong)"
319 puts -nonewline $serchan [binary format H* $ping]
320 set after [after 700 realtime-retry-now-off]
321 set realtime_retry [list $after $pong {}]
324 proc realtime-retry-serchan-readable {} {
325 global realtime_retry serchan
326 manyset $realtime_retry after pong buf
327 set x [read $serchan]
329 #puts "<! picioh in await-off $x"
330 if {![string length $x] && [eof $serchan]} { error "eof on serial port" }
332 while {[regexp {^((?:[89a-f].)*[0-7].)(.*)$} $buf dummy msg buf]} {
333 if {![string compare 09 $msg]} {
334 realtime-retry-now-off
337 if {![string compare $pong $msg]} {
339 set after [after 200 realtime-retry-send-ping]
340 set realtime_retry [list $after {} {}]
344 set realtime_retry [list $after $pong $buf]
347 proc realtime-retry-now-off {} {
352 #---------- connection and commands ----------
354 proc client-inputline {conn l} {
358 client-disable-readable $conn
359 if {![llength $c(q)]} { lappend queueing $conn }
361 after idle process-queues
364 proc process-queues {} {
365 global queueing currentcmd
366 while {![info exists currentcmd] && [llength $queueing]} {
367 set conn [lindex $queueing 0]
368 set queueing [lrange $queueing 1 end]
371 if {![llength $c(q)]} continue
373 set l [lindex $c(q) 0]
374 set c(q) [lrange $c(q) 1 end]
375 if {[llength $c(q)]} {
376 lappend queueing $conn
378 client-enable-readable $conn
381 trapping process-command $conn $l
385 proc process-command {conn l} {
386 global currentcmd currentconn realtime executing
387 global errorInfo errorCode
391 if {[regexp {^#} $l]} return; # comments ?! ok then ...
392 set l [string trim $l]
393 if {![string length $l]} return
394 if {![regexp {^((!?)[-+a-z0-9]+)(?:\s.*)?$} $l dummy cmd priv]} {
395 error "improper command name" {} {TRAIN CMDNAK BadCmd}
397 if {[regexp {[^ \t!-~]} $l]} {
398 error "improper character" {} {TRAIN CMDNAK BadCmd}
400 if {[string length $priv] && !$c(super)} {
401 error "" {} {TRAIN CMDNAK PermissionDenied}
404 if {![catch { info args global/$cmd }]} {
406 set currentconn $conn
408 xmit-relevantly "executing $cmd"
409 eval [list global/$cmd $conn] [lrange [split $l] 1 end]
410 } elseif {![catch { info args local/$cmd }]} {
411 xmit-only $conn "+executing $cmd"
412 eval [list local/$cmd $conn] [lrange [split $l] 1 end]
413 } elseif {![info exists realtime]} {
414 error "" {} {TRAIN CMDNAK realtime-not-running}
417 set currentconn $conn
422 if {[string match {POSIX EPIPE *} $errorCode]} {
423 realtime-failed EPIPE ""
424 } elseif {[string match {POSIX *} $errorCode]} {
425 realtime-failed [lindex $errorCode 1] \
426 "write failed: [lindex $errorCode 2]"
428 realtime-failed ?write-[lindex $errorCode 0] \
429 "puts failed ($errorCode): $emsg"
448 switch -glob $errorCode {
452 set el [concat [list +nack] [lrange $errorCode 2 end]]
453 if {[string length $emsg]} { append el ": " $emsg }
454 xmit-only $conn "$el"
457 set el [concat [list +ack $cmd] [lrange $errorCode 2 end]]
458 xmit-only $conn "$el : $emsg"
460 catch { unset currentconn }
461 catch { unset currentcmd }
464 set ei $errorInfo; set ec $errorCode
471 proc client-eof {conn} {
476 proc client-enable-readable {conn} {
477 fileevent $conn readable [list trapping readable client $conn]
479 proc client-disable-readable {conn} {
480 fileevent $conn readable {}
483 #---------- general IO ----------
485 proc xmit-puts {conn msg} {
486 global conns errorInfo
487 if {![info exists conns($conn)]} return
488 if {[catch { puts -nonewline $conn $msg } emsg]} {
489 kill-conn $conn "=failed client-io : $emsg"
490 error $emsg $errorInfo {TRAIN REPORTED}
494 proc xmit-only-always {conn msg} {
496 xmit-puts $conn "$msg\n"
498 proc xmit-only-noreport {conn msg} {
500 if {[msel/$conn $msg]} { xmit-puts $conn $msg }
502 proc xmit-only {conn msg} {
503 xmit-only-noreport $conn $msg
506 #---------- error handling ----------
508 proc kill-conn {conn msg} {
509 global conns queueing currentconn
511 catch { unset conns($conn) } ;# do this first to stop any recursion
512 if {[info exists currentconn]} {
513 if {![string compare $currentconn $conn]} { set currentconn {} }
515 puts "<$conn\$ closing : $msg"
516 if {[string length $msg]} { catch { xmit-only-always $conn "$msg" } }
517 catch { close $conn }
518 if {[set ix [lsearch -exact $queueing $conn]] >= 0} {
519 set queueing [lreplace $queueing $ix $ix]
522 catch { rename msel/$conn {} }
523 catch { rename mreplay/$conn {} }
528 proc report-unexpected {headmsg emsg} {
530 global errorInfo errorCode
532 "========== $headmsg ==========\n
536 ========================================\n"
540 proc bgerror {emsg} {
541 global errorInfo errorCode
542 catch { report-unexpected {UNEXPECTED UNTRAPPED ERROR} $emsg }
546 proc kill-conn-ierr {conn} {
547 kill-conn $conn "=failed : Internal error"
550 proc trapping {proc conn args} {
551 global errorInfo errorCode
552 if {![catch { uplevel #0 [list $proc $conn] $args } r]} { return $r }
553 switch -glob $errorCode {
554 {TRAIN EXPECTED*} { kill-conn $conn "=failed : $r" }
555 {TRAIN REPORTED*} { kill-conn $conn "" }
557 report-unexpected {UNEXPECTED ERROR} $r
563 #---------- realtime subprocess ----------
565 proc realtime-failed {k m} {
566 global realtime currentcmd currentconn executing
567 global errorInfo errorCode replay
568 # if $m is "", use wait status
569 if {![string length $m]} {
571 fconfigure $realtime -blocking 1
575 set m "unexpectedly closed pipe ?!"
577 } elseif {[string match {CHILDSTATUS*} $errorCode]} {
578 set m "exited with status [lindex $errorCode 2]"
579 set k "exit[lindex $errorCode 2]"
580 } elseif {[string match {CHILDKILLED*} $errorCode]} {
581 set m "killed by signal [lindex $errorCode 3]"
582 set k [lindex $errorCode 2]
584 set m "failed confusingly ($errorCode): $emsg"
585 set k ?wait-[lindex $errorCode 0]
588 catch { close $realtime }
591 catch { unset realtime }
593 set sef [open +realtime.stderr r]
594 while {[gets $sef l] >= 0} {
595 xmit-relevantly-savereplay 40 \
596 "warning realtime-failed stderr" ": $l"
601 if {![string match {POSIX ENOENT *} $errorCode]} {
602 xmit-relevantly-savereplay 40 \
603 "warning realtime-failed stderr" "unreadable : $emsg"
607 xmit-relevantly-savereplay 40 \
608 "warning realtime-failed" "reason $k : $m"
611 # internal commands need to deal with it themselves
612 } elseif {$executing} {
613 xmit-relevantly "ack $currentcmd SystemFailed realtime : $m"
615 } elseif {[info exists currentcmd]} {
616 xmit-relevantly "nak SystemFailed realtime : $m"
621 realtime-retry-realtime-failed
624 proc realtime-notrunning {} {
625 xmit-relevantly-savereplay 10 stastate Crashed
628 proc realtime-start {xopts} {
629 global realtime records realtime_xopts dev_railway libdir
630 if {[info exists realtime]} { error "realtime already running" }
633 [list 2> +realtime.stderr $libdir/realtime -v2 -s$dev_railway] \
634 $realtime_xopts $xopts]
635 foreach f $records { lappend cl $libdir/$f }
637 savereplay-clear-re "40 warning realtime-failed"
639 set realtime [open |$cl r+]
640 fconfigure $realtime -translation binary -buffering line -blocking 0
641 fileevent $realtime readable [list readable realtime $realtime]
643 realtime-failed start $emsg
644 error "realtime failed : $emsg" "" {TRAIN REPORTED}
648 proc realtime-eof {dummy} { realtime-failed EOF "" }
650 proc detect0timeout {seg} {
652 upvar #0 detect0($seg) d0
654 set key "detect $seg"
657 xmit-relevantly "$key 0"
660 proc clear-replay-detects {} {
662 foreach k [array names replay] {
663 if {[regexp {^.. detect } $k]} { unset replay($k) }
667 proc realtime-inputline {dummy l} {
668 global detectlag replay testmode
669 global executing currentcmd currentconn
671 if {$testmode && [regexp {^%(.*)} $l dummy rhs]} {
672 set r [catch { uplevel #0 $rhs } emsg]
673 if {$r} { puts "** $emsg\n" } else { puts "=> $emsg\n" }
680 if {[regexp {^(detect (\w+)) ([01])$} $l dummy key seg value]} {
681 upvar #0 detect0($seg) d0
682 catch { after cancel $d0 }
684 if {[info exists d0]} { unset d0; return }
686 set d0 [after $detectlag detect0timeout $seg]
690 } elseif {[regexp {^(stastate) (.*)$} $l dummy key value]} {
691 switch -exact -- $value {
694 savereplay-clear-re {^60 train \S+ speed }
697 savereplay-clear-re {^50 resolution }
698 savereplay-clear-re {^60 train \S+ (?:at|has) }
699 savereplay-clear-re {^60 movpos }
704 } elseif {[regexp {^(resolution) (.*)$} $l dummy key addvalue]} {
706 } elseif {[regexp {^(picio out) (on|off)$} $l dummy key value]} {
708 } elseif {[regexp -expanded {
709 ^( movpos \s \S+ \s (?: feat \s \S+ | position) |
710 train \s \S+ \s (?: has | at | speed \s commanding ) |
711 picio \s out \s polarity
713 } $l dummy key value]} {
716 if {[info exists key]} {
718 upvar #0 replay($pk) rep
719 if {[info exists value]} {
722 append rep $addvalue "\n"
726 switch -regexp -- $l {
727 {^executing\s|^nak\s} {
732 xmit-relevantly $l $tlog
734 switch -regexp -- $l {
739 after idle process-queues
744 #---------- new connections ----------
746 proc find-permission {ipaddr} {
748 set ipaddr [ipaddr2hex $ipaddr]
749 foreach {keyword paddr pmask} $permissions {
750 if {[expr {($ipaddr & $pmask) == $paddr}]} { return $keyword }
755 proc connected {conn} {
758 fconfig-trainproto $conn
761 set perm [find-permission $c(ipaddr)]
762 switch -exact $perm {
763 deny { kill-conn $conn =denied; return }
764 allow { set c(super) 0 }
765 super { set c(super) 1 }
766 default { error "$perm ?" }
768 compile-glob-patterns {?info ?warning} msel/$conn
770 xmit-only-always $conn =connected
771 xmit-only-always $conn "=permission [lindex {normal super} $c(super)]"
772 client-enable-readable $conn
775 proc newconn {conn ipaddr port} {
778 puts "$conn new-client $conn $ipaddr,$port"
779 set c(ipaddr) $ipaddr
780 trapping connected $conn
783 proc try-bind {addr} {
784 global master errorInfo errorCode port
786 set master [socket -server newconn -myaddr $addr $port]
787 } emsg]} { return 1 }
788 if {[string match {POSIX EADDRNOTAVAIL *} $errorCode]} { return 0 }
789 error $emsg $errorInfo $errorCode
792 proc ipaddr2hex {addr} {
793 if {![regexp {^\d+\.\d+\.\d+\.\d+$} $addr]} {
794 error "invalid ip address $addr"
797 foreach octet [split $addr .] { append val [format %02x $octet] }
798 if {[string length $val] != 10} {
799 error "invalid numbers in ip address $addr (calculated $val ?!)"
804 proc binding {addr blist} {
805 global master permissions port
806 if {[info exists master]} return
807 if {![try-bind $addr]} return
808 puts "bound to $addr,$port"
810 foreach {keyword pattern} $blist {
811 switch -exact $keyword allow - super - deny { } \
812 default { error "unknown binding keyword $keyword" }
813 if {![regexp {^(.*)/(\d+)$} $pattern dummy host masklen]} {
814 set host $pattern; set masklen 32
816 set ipaddr [ipaddr2hex $host]
817 set mask [expr {$masklen==0 ? 0 : 0xffffffff << (32-$masklen)}]
818 set mask [format %#10x $mask]
819 if {$ipaddr & ~$mask} {
820 error "non-zero bits outside mask in $pattern ($ipaddr/$mask)"
822 lappend permissions $keyword $ipaddr $mask
827 global queueing executing testmode realtime port urandom serchan
828 global dev_railway libdir lputs
829 catch { close $master }; catch { unset master }
831 setting testmode 0 {[01]}
832 setting lputs 0 {[01]}
833 setting dev_railway {} {/.*}
836 uplevel #0 source $libdir/multiplex-config
840 set urandom [open /dev/urandom r]
841 fconfigure $urandom -buffering none -translation binary
843 set serchan [open $dev_railway {RDWR NOCTTY NONBLOCK}]
844 fconfigure $serchan -translation binary -buffering none -blocking 0
850 after idle realtime-retry-check
852 fconfig-trainproto stdin
853 fconfig-trainproto stdout
854 fileevent stdin readable [list readable realtime stdin]