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} {
99 global executing currentconn conns
102 set myconn $currentconn
103 if {[string length $currentconn]} {
104 trapping xmit-only-noreport $currentconn +$m
112 foreach conn [array names conns] {
113 if {[string compare $myconn $conn]} {
114 trapping xmit-only-noreport $conn $othersm
119 proc xmit-relevantly-savereplay {pri key rhs} {
121 upvar #0 replay($pk) rep
123 xmit-relevantly "$key $rhs"
126 proc savereplay-clear {pk} {
127 upvar #0 replay($pk) rep; catch { unset rep }
130 proc savereplay-clear-re {re} { # re is anchored at start
132 if {![info exists replay]} return
133 foreach pk [array names replay] {
134 if {[regexp -- ^$re "$pk "]} { unset replay($pk) }
138 proc save-dump-failed-warn {fpre howpre emsg} {
140 switch -glob $errorCode {
142 set k [lindex $errorCode 1]
143 set m [lindex $errorCode 2]
144 xmit-relevantly-savereplay 41 \
145 "warning save-dump-failed" "$fpre$k : $howpre$m"
148 xmit-relevantly-savereplay 41 \
149 "warning save-dump-failed" "$fpre: $emsg"
157 savereplay-clear "41 warning save-dump-failed"
158 savereplay-clear "42 info save-dump"
160 set now [clock seconds]
161 set now [clock format $now -format %Y-%m-%dT%H-%M-%S%z]
162 set dumpdir +dump.$now
167 save-dump-failed-warn "" "mkdir $dumpdir: " $emsg
168 error $emsg "" {TRAIN REPORTED}
173 +persist.data +persist.data.new +persist.data.old
174 +persist.conv +persist.conv.new +persist.conv.old
176 if {[catch { link $f $dumpdir/$f } emsg]} {
177 switch -glob $errorCode {
179 * { save-dump-failed-warn "$f " "link $f $dumpdir/: " $emsg }
183 xmit-relevantly-savereplay 42 "info save-dump" "$dumpdir"
186 #---------- multiplexer-implemented command ----------
188 proc local/select {conn args} {
190 compile-glob-patterns $args msel/$conn
193 proc global/!save-dump {conn args} {
195 if {[catch { save-dump } emsg]} {
196 cmderr HostSupportSystemsProblem "failed to save dump: $emsg"
200 proc do-replay {conn} {
202 foreach pk [lsort [array names replay]] {
203 set pri [string range $pk 0 2]
204 set lhs [string range $pk 3 end]
205 set r [string trimright $replay($pk) "\n"]
206 foreach m [split $r "\n"] {
207 puts "<$conn|$pri|$lhs $m"
208 xmit-only-noreport $conn "|$lhs $m"
213 proc local/replay {conn args} {
214 if {[llength $args]} {
215 rename msel/$conn mreplay/$conn ;# park it here for a moment
216 compile-glob-patterns $args msel/$conn
219 if {[llength $args]} {
221 rename mreplay/$conn msel/$conn
225 proc local/select-replay {conn args} {
227 compile-glob-patterns $args msel/$conn
231 #---------- automatic realtime restart ----------
233 proc global/!realtime {conn args} {
234 global realtime realtime_retry
236 set how [lindex $args 0]
238 # perhaps kill the running instance
239 # this switch also checks the argument
240 switch -exact -- $how {
241 kill - stop - restart - start - start-manual {
242 if {[info exists realtime]} {
243 realtime-failed killed "termination requested by command"
249 cmderr BadCmd "unknown !realtime subcommand"
253 # set the operating mode
254 switch -exact -- $how {
257 set realtime_retry {}
259 stop - start - start-manual {
261 catch { unset realtime_retry }
265 # (re)start if applicable
266 switch -exact -- $how {
274 after idle realtime-retry-check
279 proc realtime-retry-reset {} {
280 global realtime_retry serchan
281 if {![info exists realtime_retry]} return
282 if {![llength $realtime_retry]} return
283 manyset $realtime_retry timeoutid
284 fileevent $serchan readable {}
285 after cancel $timeoutid
286 set realtime_retry {}
289 proc realtime-retry-check {} {
290 global realtime_retry realtime
291 if {![info exists realtime_retry]} return
292 if {[llength $realtime_retry]} return
293 if {[info exists realtime]} return
297 proc realtime-retry-realtime-failed {} {
298 global realtime_retry serchan
299 if {![info exists realtime_retry]} return
300 if {[llength $realtime_retry]} { error "huh? $realtime_retry" }
301 fileevent $serchan readable realtime-retry-serchan-readable
302 set after [after 500 realtime-retry-send-ping]
303 set realtime_retry [list $after {} {}]
306 proc realtime-retry-send-ping {} {
307 global realtime_retry urandom serchan
308 manyset $realtime_retry after pong buf
309 set x [read $urandom 2]
311 if {[string length $x] != 4} { error "urandom short read `$x'" }
312 set x [expr "0x$x & 0x077f"]
313 set ping [format %04x [expr {0x8800 ^ $x}]]
314 set pong [format %04x [expr {0x885a ^ $x}]]
315 #puts "<! picioh out await-off $pong (await $pong)"
316 puts -nonewline $serchan [binary format H* $ping]
317 set after [after 700 realtime-retry-now-off]
318 set realtime_retry [list $after $pong {}]
321 proc realtime-retry-serchan-readable {} {
322 global realtime_retry serchan
323 manyset $realtime_retry after pong buf
324 set x [read $serchan]
326 #puts "<! picioh in await-off $x"
327 if {![string length $x] && [eof $serchan]} { error "eof on serial port" }
329 while {[regexp {^((?:[89a-f].)*[0-7].)(.*)$} $buf dummy msg buf]} {
330 if {![string compare 09 $msg]} {
331 realtime-retry-now-off
334 if {![string compare $pong $msg]} {
336 set after [after 200 realtime-retry-send-ping]
337 set realtime_retry [list $after {} {}]
341 set realtime_retry [list $after $pong $buf]
344 proc realtime-retry-now-off {} {
349 #---------- connection and commands ----------
351 proc client-inputline {conn l} {
355 client-disable-readable $conn
356 if {![llength $c(q)]} { lappend queueing $conn }
358 after idle process-queues
361 proc process-queues {} {
362 global queueing currentcmd
363 while {![info exists currentcmd] && [llength $queueing]} {
364 set conn [lindex $queueing 0]
365 set queueing [lrange $queueing 1 end]
368 if {![llength $c(q)]} continue
370 set l [lindex $c(q) 0]
371 set c(q) [lrange $c(q) 1 end]
372 if {[llength $c(q)]} {
373 lappend queueing $conn
375 client-enable-readable $conn
378 trapping process-command $conn $l
382 proc process-command {conn l} {
383 global currentcmd currentconn realtime executing
384 global errorInfo errorCode
388 if {[regexp {^#} $l]} return; # comments ?! ok then ...
389 set l [string trim $l]
390 if {![string length $l]} return
391 if {![regexp {^((!?)[-+a-z0-9]+)(?:\s.*)?$} $l dummy cmd priv]} {
392 error "improper command name" {} {TRAIN CMDNAK BadCmd}
394 if {[regexp {[^ \t!-~]} $l]} {
395 error "improper character" {} {TRAIN CMDNAK BadCmd}
397 if {[string length $priv] && !$c(super)} {
398 error "" {} {TRAIN CMDNAK PermissionDenied}
401 if {![catch { info args global/$cmd }]} {
403 set currentconn $conn
405 xmit-relevantly "executing $cmd"
406 eval [list global/$cmd $conn] [lrange [split $l] 1 end]
407 } elseif {![catch { info args local/$cmd }]} {
408 xmit-only $conn "+executing $cmd"
409 eval [list local/$cmd $conn] [lrange [split $l] 1 end]
410 } elseif {![info exists realtime]} {
411 error "" {} {TRAIN CMDNAK realtime-not-running}
414 set currentconn $conn
419 if {[string match {POSIX EPIPE *} $errorCode]} {
420 realtime-failed EPIPE ""
421 } elseif {[string match {POSIX *} $errorCode]} {
422 realtime-failed [lindex $errorCode 1] \
423 "write failed: [lindex $errorCode 2]"
425 realtime-failed ?write-[lindex $errorCode 0] \
426 "puts failed ($errorCode): $emsg"
445 switch -glob $errorCode {
449 set el [concat [list +nack] [lrange $errorCode 2 end]]
450 if {[string length $emsg]} { append el ": " $emsg }
451 xmit-only $conn "$el"
454 set el [concat [list +ack $cmd] [lrange $errorCode 2 end]]
455 xmit-only $conn "$el : $emsg"
457 catch { unset currentconn }
458 catch { unset currentcmd }
461 set ei $errorInfo; set ec $errorCode
468 proc client-eof {conn} {
473 proc client-enable-readable {conn} {
474 fileevent $conn readable [list trapping readable client $conn]
476 proc client-disable-readable {conn} {
477 fileevent $conn readable {}
480 #---------- general IO ----------
482 proc xmit-puts {conn msg} {
483 global conns errorInfo
484 if {![info exists conns($conn)]} return
485 if {[catch { puts -nonewline $conn $msg } emsg]} {
486 kill-conn $conn "=failed client-io : $emsg"
487 error $emsg $errorInfo {TRAIN REPORTED}
491 proc xmit-only-always {conn msg} {
493 xmit-puts $conn "$msg\n"
495 proc xmit-only-noreport {conn msg} {
497 if {[msel/$conn $msg]} { xmit-puts $conn $msg }
499 proc xmit-only {conn msg} {
500 xmit-only-noreport $conn $msg
503 #---------- error handling ----------
505 proc kill-conn {conn msg} {
506 global conns queueing currentconn
508 catch { unset conns($conn) } ;# do this first to stop any recursion
509 if {[info exists currentconn]} {
510 if {![string compare $currentconn $conn]} { set currentconn {} }
512 puts "<$conn\$ closing : $msg"
513 if {[string length $msg]} { catch { xmit-only-always $conn "$msg" } }
514 catch { close $conn }
515 if {[set ix [lsearch -exact $queueing $conn]] >= 0} {
516 set queueing [lreplace $queueing $ix $ix]
519 catch { rename msel/$conn {} }
520 catch { rename mreplay/$conn {} }
525 proc report-unexpected {headmsg emsg} {
527 global errorInfo errorCode
529 "========== $headmsg ==========\n
533 ========================================\n"
537 proc bgerror {emsg} {
538 global errorInfo errorCode
539 catch { report-unexpected {UNEXPECTED UNTRAPPED ERROR} $emsg }
543 proc kill-conn-ierr {conn} {
544 kill-conn $conn "=failed : Internal error"
547 proc trapping {proc conn args} {
548 global errorInfo errorCode
549 if {![catch { uplevel #0 [list $proc $conn] $args } r]} { return $r }
550 switch -glob $errorCode {
551 {TRAIN EXPECTED*} { kill-conn $conn "=failed : $r" }
552 {TRAIN REPORTED*} { kill-conn $conn "" }
554 report-unexpected {UNEXPECTED ERROR} $r
560 #---------- realtime subprocess ----------
562 proc realtime-failed {k m} {
563 global realtime currentcmd currentconn executing
564 global errorInfo errorCode replay
565 # if $m is "", use wait status
566 if {![string length $m]} {
568 fconfigure $realtime -blocking 1
572 set m "unexpectedly closed pipe ?!"
574 } elseif {[string match {CHILDSTATUS*} $errorCode]} {
575 set m "exited with status [lindex $errorCode 2]"
576 set k "exit[lindex $errorCode 2]"
577 } elseif {[string match {CHILDKILLED*} $errorCode]} {
578 set m "killed by signal [lindex $errorCode 3]"
579 set k [lindex $errorCode 2]
581 set m "failed confusingly ($errorCode): $emsg"
582 set k ?wait-[lindex $errorCode 0]
585 catch { close $realtime }
588 catch { unset realtime }
590 set sef [open +realtime.stderr r]
591 while {[gets $sef l] >= 0} {
592 xmit-relevantly-savereplay 40 \
593 "warning realtime-failed stderr" ": $l"
598 if {![string match {POSIX ENOENT *} $errorCode]} {
599 xmit-relevantly-savereplay 40 \
600 "warning realtime-failed stderr" "unreadable : $emsg"
604 xmit-relevantly-savereplay 40 \
605 "warning realtime-failed" "reason $k : $m"
608 # internal commands need to deal with it themselves
609 } elseif {$executing} {
610 xmit-relevantly "ack $currentcmd SystemFailed realtime : $m"
612 } elseif {[info exists currentcmd]} {
613 xmit-relevantly "nak SystemFailed realtime : $m"
618 realtime-retry-realtime-failed
621 proc realtime-notrunning {} {
622 xmit-relevantly-savereplay 10 stastate Crashed
625 proc realtime-start {xopts} {
626 global realtime records realtime_xopts dev_railway libdir
627 if {[info exists realtime]} { error "realtime already running" }
630 [list 2> +realtime.stderr $libdir/realtime -v2 -s$dev_railway] \
631 $realtime_xopts $xopts]
632 foreach f $records { lappend cl $libdir/$f }
634 savereplay-clear-re "40 warning realtime-failed"
636 set realtime [open |$cl r+]
637 fconfigure $realtime -translation binary -buffering line -blocking 0
638 fileevent $realtime readable [list readable realtime $realtime]
640 realtime-failed start $emsg
641 error "realtime failed : $emsg" "" {TRAIN REPORTED}
645 proc realtime-eof {dummy} { realtime-failed EOF "" }
647 proc detect0timeout {seg} {
649 upvar #0 detect0($seg) d0
651 set key "detect $seg"
654 xmit-relevantly "$key 0"
657 proc clear-replay-detects {} {
659 foreach k [array names replay] {
660 if {[regexp {^.. detect } $k]} { unset replay($k) }
664 proc realtime-inputline {dummy l} {
665 global detectlag replay testmode
666 global executing currentcmd currentconn
668 if {$testmode && [regexp {^%(.*)} $l dummy rhs]} {
669 set r [catch { uplevel #0 $rhs } emsg]
670 if {$r} { puts "** $emsg\n" } else { puts "=> $emsg\n" }
676 if {[regexp {^(detect (\w+)) ([01])$} $l dummy key seg value]} {
677 upvar #0 detect0($seg) d0
678 catch { after cancel $d0 }
680 if {[info exists d0]} { unset d0; return }
682 set d0 [after $detectlag detect0timeout $seg]
686 } elseif {[regexp {^(stastate) (.*)$} $l dummy key value]} {
687 switch -exact -- $value {
690 savereplay-clear-re {^60 train \S+ speed }
693 savereplay-clear-re {^50 resolution }
694 savereplay-clear-re {^60 train \S+ (?:at|has) }
698 } elseif {[regexp {^(resolution) (.*)$} $l dummy key addvalue]} {
700 } elseif {[regexp {^(picio out) (on|off)$} $l dummy key value]} {
702 } elseif {[regexp -expanded {
703 ^( movpos \s \S+ \s (?: feat \s \S+ | position) |
704 train \s \S+ \s (?: has | at | speed \s commanding ) |
705 picio \s out \s polarity
707 } $l dummy key value]} {
710 if {[info exists key]} {
712 upvar #0 replay($pk) rep
713 if {[info exists value]} {
716 append rep $addvalue "\n"
720 switch -regexp -- $l {
721 {^executing\s|^nak\s} {
728 switch -regexp -- $l {
733 after idle process-queues
738 #---------- new connections ----------
740 proc find-permission {ipaddr} {
742 set ipaddr [ipaddr2hex $ipaddr]
743 foreach {keyword paddr pmask} $permissions {
744 if {[expr {($ipaddr & $pmask) == $paddr}]} { return $keyword }
749 proc connected {conn} {
752 fconfig-trainproto $conn
755 set perm [find-permission $c(ipaddr)]
756 switch -exact $perm {
757 deny { finally $conn =denied; return }
758 allow { set c(super) 0 }
759 super { set c(super) 1 }
760 default { error "$perm ?" }
762 compile-glob-patterns {?info ?warning} msel/$conn
764 xmit-only-always $conn =connected
765 xmit-only-always $conn "=permission [lindex {normal super} $c(super)]"
766 client-enable-readable $conn
769 proc newconn {conn ipaddr port} {
772 puts "$conn new-client $conn $ipaddr,$port"
773 set c(ipaddr) $ipaddr
774 trapping connected $conn
777 proc try-bind {addr} {
778 global master errorInfo errorCode port
780 set master [socket -server newconn -myaddr $addr $port]
781 } emsg]} { return 1 }
782 if {[string match {POSIX EADDRNOTAVAIL *} $errorCode]} { return 0 }
783 error $emsg $errorInfo $errorCode
786 proc ipaddr2hex {addr} {
787 if {![regexp {^\d+\.\d+\.\d+\.\d+$} $addr]} {
788 error "invalid ip address $addr"
791 foreach octet [split $addr .] { append val [format %02x $octet] }
792 if {[string length $val] != 10} {
793 error "invalid numbers in ip address $addr (calculated $val ?!)"
798 proc binding {addr blist} {
799 global master permissions port
800 if {[info exists master]} return
801 if {![try-bind $addr]} return
802 puts "bound to $addr,$port"
804 foreach {keyword pattern} $blist {
805 switch -exact $keyword allow - super - deny { } \
806 default { error "unknown binding keyword $keyword" }
807 if {![regexp {^(.*)/(\d+)$} $pattern dummy host masklen]} {
808 set host $pattern; set masklen 32
810 set ipaddr [ipaddr2hex $host]
811 set mask [expr {$masklen==0 ? 0 : 0xffffffff << (32-$masklen)}]
812 set mask [format %#10x $mask]
813 if {$ipaddr & ~$mask} {
814 error "non-zero bits outside mask in $pattern ($ipaddr/$mask)"
816 lappend permissions $keyword $ipaddr $mask
821 global queueing executing testmode realtime port urandom serchan
822 global dev_railway libdir lputs
823 catch { close $master }; catch { unset master }
825 setting testmode 0 {[01]}
826 setting lputs 0 {[01]}
827 setting dev_railway {} {/.*}
830 uplevel #0 source $libdir/multiplex-config
834 set urandom [open /dev/urandom r]
835 fconfigure $urandom -buffering none -translation binary
837 set serchan [open $dev_railway {RDWR NOCTTY NONBLOCK}]
838 fconfigure $serchan -translation binary -buffering none -blocking 0
844 after idle realtime-retry-check
846 fconfig-trainproto stdin
847 fconfig-trainproto stdout
848 fileevent stdin readable [list readable realtime stdin]