Revise stty settings used in testsuite
[dejagnu.git] / lib / remote.exp
blob1c9971a076415adc2fcdc04ab8f78cc832ce1098
1 # Copyright (C) 1992-2019, 2020 Free Software Foundation, Inc.
3 # This file is part of DejaGnu.
5 # DejaGnu is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # DejaGnu is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with DejaGnu; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
19 # This file was written by Rob Savoye <rob@welcomehome.org>.
21 # Load various protocol support modules.
23 load_lib "telnet.exp"
24 load_lib "rlogin.exp"
25 load_lib "kermit.exp"
26 load_lib "tip.exp"
27 load_lib "rsh.exp"
28 load_lib "ssh.exp"
29 load_lib "ftp.exp"
31 # Open a connection to a remote host or target. This requires the target_info
32 # array be filled in with the proper info to work.
34 # type is either "build", "host", "target", or the name of a board loaded
35 # into the board_info array. The default is target if no name is supplied.
36 # It returns the spawn id of the process that is the connection.
38 proc remote_open { args } {
39     global reboot
41     if { [llength $args] == 0 } {
42         set type "target"
43     } else {
44         set type $args
45     }
47     # Shudder...
48     if { $reboot && $type eq "target" } {
49         reboot_target
50     }
52     return [call_remote "" open $type]
55 proc remote_raw_open { args } {
56     return [eval call_remote raw open $args]
59 # Close a spawn ID, and wait for the process to die.  If PID is not
60 # -1, then the process is killed if it doesn't exit gracefully.
62 proc close_wait_program { program_id pid {wres_varname ""} } {
63     if {$wres_varname ne "" } {
64         upvar 1 $wres_varname wres
65     }
67     set exec_pid -1
69     if { $pid > 0 } {
70         # Tcl has no kill primitive, so we have to execute an external
71         # command in order to kill the process.
72         verbose "doing kill, pid is $pid"
73         # Send SIGINT to give the program a better chance to interrupt
74         # whatever it might be doing and react to stdin closing.
75         # eg, in case of GDB, this should get it back to the prompt.
76         # Do so separately for each PID in the list to avoid differences
77         # in return value behavior for kill between shells
78         foreach spid $pid {
79             # Prepend "-" to generate the "process group ID" needed by
80             # kill.
81             exec sh -c "exec > /dev/null 2>&1 && (kill -2 -$spid || kill -2 $spid)"
82         }
84         # If the program doesn't exit gracefully when stdin closes,
85         # we'll need to kill it.  But only do this after 'wait'ing a
86         # bit, to avoid killing the wrong process in case of a
87         # PID-reuse race.  The extra sleep at the end is there to give
88         # time to kill $exec_pid without having _that_ be subject to a
89         # PID reuse race.
90         set secs 5
91         set sh_cmd "exec > /dev/null 2>&1"
92         append sh_cmd " && sleep $secs && ("
93         foreach spid $pid {
94             append sh_cmd "(kill -15 -$spid || kill -15 $spid);"
95         }
96         append sh_cmd ") && sleep $secs && ("
97         foreach spid $pid {
98             append sh_cmd "(kill -9 -$spid || kill -9 $spid);"
99         }
100         append sh_cmd ") && sleep $secs"
101         set exec_pid [exec sh -c $sh_cmd &]
102     }
103     verbose "pid is $pid"
105     # This closes the program's stdin.  This should cause well behaved
106     # interactive programs to exit.  This will hang if the kill
107     # doesn't work.  Nothin' to do, and it's not OK.
108     catch "close -i $program_id"
110     # Reap it.
111     set res [catch "wait -i $program_id" wres]
112     if { $exec_pid != -1 && [llength $pid] == 1 } {
113         # We reaped the process, so cancel the pending force-kills, as
114         # otherwise if the PID is reused for some other unrelated
115         # process, we'd kill the wrong process.
116         #
117         # Do this if the PID list only has a single entry however, as
118         # otherwise `wait' will have returned right away regardless of
119         # whether any process of the pipeline has exited.
120         #
121         # Use `catch' in case the force-kills have completed, so as not
122         # to cause TCL to choke if `kill' returns a failure.
123         catch {exec sh -c "kill -9 $exec_pid" >& /dev/null}
124     }
126     return $res
129 # Run the specified COMMANDLINE on the local machine, redirecting
130 # input from file INP (if non-empty), redirecting output to file OUTP
131 # (if non-empty), and waiting TIMEOUT seconds for the command to
132 # complete before killing it. A list of two elements is returned: the
133 # first member is the exit status of the command, the second is any
134 # output produced from the command (if output is redirected, this may
135 # or may not be empty). If output is redirected, both stdout and
136 # stderr will appear in the specified file.
138 # Caveats: A pipeline is used if input or output is redirected. There
139 # will be problems with killing the program if a pipeline is used. Either
140 # the "tee" command or the "cat" command is used in the pipeline if input
141 # or output is redirected. If the program needs to be killed, /bin/sh and
142 # the kill command will be invoked.
144 proc local_exec { commandline inp outp timeout } {
145     # Tcl's exec is a pile of crap. It does two very inappropriate things.
146     # Firstly, it has no business returning an error if the program being
147     # executed happens to write to stderr. Secondly, it appends its own
148     # error messages to the output of the command if the process exits with
149     # non-zero status.
150     #
151     # So, ok, we do this funny stuff with using spawn sometimes and
152     # open others because of spawn's inability to invoke commands with
153     # redirected I/O. We also hope that nobody passes in a command that's
154     # a pipeline, because spawn can't handle it.
155     #
156     # We want to use spawn in most cases, because Tcl's pipe mechanism
157     # doesn't assign process groups correctly and we can't reliably kill
158     # programs that bear children. We can't use Tcl's exec because it has
159     # no way to timeout programs that hang.
160     #
161     # The expect command will close the connection when it sees
162     # EOF. Closing the connection may send SIGHUP to the child and
163     # cause it to exit before it can exit normally.  The child should
164     # ignore SIGHUP.
165     global errorInfo
166     if { $inp eq "" && $outp eq "" } {
167         set id -1
168         set result [catch "eval spawn -ignore SIGHUP \{${commandline}\}" pid]
169         if { $result == 0 } {
170             set result2 0
171         } else {
172             set pid 0
173             set result2 5
174         }
175     } else {
176         # Use a command pipeline with open.
177         if { $inp ne "" } {
178             set inp "< $inp"
179             set mode "r"
180         } else {
181             set mode "w"
182         }
184         set use_tee 0
185         # We add |& cat so that Tcl exec doesn't freak out if the
186         # program writes to stderr.
187         if { $outp eq "" } {
188             set outp "|& cat"
189         } else {
190             set outpf $outp
191             set outp "> $outp"
192             if { $inp ne "" } {
193                 set use_tee 1
194             }
195         }
196         # Why do we use tee? Because open can't redirect both input and output.
197         if { $use_tee } {
198             set result [catch {open "| $commandline $inp |& tee $outpf" RDONLY} id]
199         } else {
200             set result [catch {open "| $commandline $inp $outp" $mode} id]
201         }
203         if { $result != 0 } {
204             return [list -1 "open of $commandline $inp $outp failed: $errorInfo"]
205         }
206         set pid [pid $id]
207         set result [catch "spawn -ignore SIGHUP -leaveopen $id" result2]
208     }
209     # Prepend "-" to each pid, to generate the "process group IDs" needed by
210     # kill.
211     set pgid "-[join $pid { -}]"
212     verbose "pid is $pid $pgid"
213     if { $result != 0 || $result2 != 0 } {
214         # This shouldn't happen.
215         if {[info exists errorInfo]} {
216             set foo $errorInfo
217         } else {
218             set foo ""
219         }
220         verbose "spawn -open $id failed, $result $result2, $foo"
221         catch "close $id"
222         return [list -1 "spawn failed"]
223     }
225     set got_eof 0
226     set output ""
228     # Wait for either $timeout seconds to elapse, or for the program to
229     # exit.
230     expect {
231         -i $spawn_id -timeout $timeout -re ".+" {
232             append output $expect_out(buffer)
233             exp_continue -continue_timer
234         }
235         timeout {
236             warning "program timed out"
237         }
238         eof {
239             set got_eof 1
240         }
241     }
243     # If we didn't get EOF, we have to kill the poor defenseless program.
244     if { $got_eof } {
245         set pid -1
246     }
247     set r2 [close_wait_program $spawn_id $pid wres]
248     if { $id > 0 } {
249         if { $pid > 0 } {
250             # If timed-out, don't wait for all the processes associated
251             # with the pipeline to terminate as a stuck one would cause
252             # us to hang.
253             catch {fconfigure $id -blocking false}
254         }
255         set r2 [catch "close $id" res]
256     } else {
257         verbose "waitres is $wres" 2
258         if { $r2 == 0 } {
259             set r2 [lindex $wres 3]
260             if { [llength $wres] > 4 } {
261                 if { [lindex $wres 4] eq "CHILDKILLED" } {
262                     set r2 1
263                 }
264             }
265             if { $r2 != 0 } {
266                 set res $wres
267             } else {
268                 set res ""
269             }
270         } else {
271             set res "wait failed"
272         }
273     }
274     if { $r2 != 0 || $res ne "" || ! $got_eof } {
275         verbose "close result is $res"
276         set status 1
277     } else {
278         set status 0
279     }
280     verbose "output is $output status $status"
281     if { $outp eq "" || $outp eq "|& cat" } {
282         return [list $status $output]
283     } else {
284         return [list $status ""]
285     }
289 # Execute the supplied program on HOSTNAME. There are four optional arguments
290 # the first is a set of arguments to pass to PROGRAM, the second is an
291 # input file to feed to stdin of PROGRAM, the third is the name of an
292 # output file where the output from PROGRAM should be written, and
293 # the fourth is a timeout value (we give up after the specified # of seconds
294 # has elapsed).
296 # A two-element list is returned. The first value is the exit status of the
297 # program (-1 if the exec failed). The second is any output produced by
298 # the program (which may or may not be empty if output from the program was
299 # redirected).
301 proc remote_exec { hostname program args } {
302     if { [llength $args] > 0 } {
303         set pargs [lindex $args 0]
304     } else {
305         set pargs ""
306     }
308     if { [llength $args] > 1 } {
309         set inp "[lindex $args 1]"
310     } else {
311         set inp ""
312     }
314     if { [llength $args] > 2 } {
315         set outp "[lindex $args 2]"
316     } else {
317         set outp ""
318     }
320     # call_remote below gets its timeout from global variable, so set
321     # it here.
322     global timeout
323     set old_timeout $timeout
324     # 300 is probably a lame default.
325     if { [llength $args] > 3 } {
326         set timeout "[lindex $args 3]"
327     } else {
328         set timeout 300
329     }
331     verbose -log "Executing on $hostname: $program $pargs $inp $outp (timeout = $timeout)" 2
333     # Run it locally if appropriate.
334     if { ![isremote $hostname] } {
335         set result [local_exec "$program $pargs" $inp $outp $timeout]
336     } else {
337         if { [board_info $hostname exists remotedir] } {
338             set remotedir [board_info $hostname remotedir]
339             # This is a bit too clever. Join cd $remotedir and
340             # $program on the command line with ';' and not '&&'. When
341             # called, $program may be mkdir to initially create the
342             # remote directory, in which case cd would fail.
343             set program "test -d $remotedir && cd $remotedir; $program"
344         }
345         set result [call_remote "" exec $hostname $program $pargs $inp $outp]
346     }
348     # Restore timeout.
349     set timeout $old_timeout
350     return $result
353 proc standard_exec { hostname args } {
354     return [eval rsh_exec \"$hostname\" $args]
357 # Close the remote connection.
358 #       arg - This is the name of the machine whose connection we're closing,
359 #             or target, host or build.
361 proc remote_close { host } {
362     while { 1 } {
363         set result [call_remote "" close $host]
364         if { [remote_pop_conn $host] ne "pass" } {
365             break
366         }
367     }
368     return $result
371 proc remote_raw_close { host } {
372     return [call_remote raw close $host]
375 proc standard_close { host } {
376     global board_info
378     if {[board_info $host exists fileid]} {
379         set shell_id [board_info $host fileid]
380         set pid -1
382         verbose "Closing the remote shell $shell_id" 2
383         if {[board_info $host exists fileid_origid]} {
384             set oid [board_info $host fileid_origid]
385             set pid [pid $oid]
386             unset board_info($host,fileid_origid)
387         } else {
388             set result [catch "exp_pid -i $shell_id" pid]
389             if { $result != 0 || $pid <= 0 } {
390                 set result [catch "pid $shell_id" pid]
391                 if { $result != 0 } {
392                     set pid -1
393                 }
394             }
395         }
397         close_wait_program $shell_id $pid
399         if {[info exists oid]} {
400             if { $pid > 0 } {
401                 # Don't wait for all the processes associated with the
402                 # pipeline to terminate as a stuck one would cause us
403                 # to hang.
404                 catch {fconfigure $oid -blocking false}
405             }
406             catch "close $oid"
407         }
409         unset board_info($host,fileid)
410         verbose "Shell closed."
411     }
412     return 0
415 # Set the connection into "binary" mode, a.k.a. no processing of input
416 # characters.
418 proc remote_binary { host } {
419     return [call_remote "" binary $host]
422 proc remote_raw_binary { host } {
423     return [call_remote raw binary $host]
427 # Return value of this function depends on actual implementation of reboot that
428 # will be used, in practice it is expected that remote_reboot returns 1 on
429 # success and 0 on failure.
431 proc remote_reboot { host } {
432     clone_output "\nRebooting $host\n"
433     # FIXME: don't close the host connection, or all the remote
434     # procedures will fail.
435     # remote_close $host
436     set status [call_remote "" reboot $host]
437     if {[board_info $host exists name]} {
438         set host [board_info $host name]
439     }
440     if { [info procs ${host}_init] ne "" } {
441         ${host}_init $host
442     }
443     return $status
446 # It looks like that this proc is never called, instead ${board}_reboot defined
447 # in base-config.exp will be used because it has higher priority and
448 # base-config.exp is always imported by runtest.
450 proc standard_reboot { host } {
451     return 1
454 # Download file FILE to DEST. If the optional DESTFILE is specified,
455 # that file will be used on the destination board. It returns either
456 # "" (indicating that the download failed), or the name of the file on
457 # the destination machine.
460 proc remote_download { dest file args } {
461     if { [llength $args] > 0 } {
462         set destfile [lindex $args 0]
463     } else {
464         set destfile [file tail $file]
465     }
467     if { ![isremote $dest] } {
468         if { $destfile eq "" || $destfile == $file } {
469             return $file
470         } else {
471             verbose -log "Downloading on $dest to $destfile: $file" 2
472             set result [catch "exec cp -p $file $destfile" output]
473             if {[regexp "same file|are identical" $output]} {
474                 set result 0
475                 set output ""
476             } else {
477                 # try to make sure we can read it
478                 # and write it (in case we copy onto it again)
479                 catch {exec chmod u+rw $destfile}
480             }
481             if { $result != 0 || $output ne "" } {
482                 perror "remote_download to $dest of $file to $destfile: $output"
483                 return ""
484             } else {
485                 return $destfile
486             }
487         }
488     }
489     if { [board_info $dest exists remotedir] } {
490         set remotedir [board_info $dest remotedir]
491         set status [remote_exec $dest mkdir "-p $remotedir"]
492         if { [lindex $status 0] != 0 } {
493             perror "Couldn't create remote directory $remotedir on $dest"
494             return ""
495         }
496         set destfile $remotedir/$destfile
497     }
499     return [call_remote "" download $dest $file $destfile]
502 # The default download procedure. Uses rcp to download to $dest.
504 proc standard_download {dest file destfile} {
505     set orig_destfile $destfile
507     if {[board_info $dest exists nfsdir]} {
508         set destdir [board_info $dest nfsdir]
509         if {[board_info $dest exists nfsroot_server]} {
510             set dest [board_info $dest nfsroot_server]
511         } else {
512             set dest ""
513         }
514         set destfile $destdir/$destfile
515     }
517     if { $dest ne "" } {
518         set result [rsh_download $dest $file $destfile]
519         if { $result eq $destfile } {
520             return $orig_destfile
521         } else {
522             return $result
523         }
524     }
526     set result [catch "exec cp -p $file $destfile" output]
527     if {[regexp "same file|are identical" $output]} {
528         set result 0
529         set output ""
530     } else {
531         # try to make sure we can read it
532         # and write it (in case we copy onto it again)
533         catch {exec chmod u+rw $destfile}
534     }
535     if { $result != 0 || $output ne "" } {
536         perror "remote_download to $dest of $file to $destfile: $output"
537         return ""
538     } else {
539         return $orig_destfile
540     }
543 proc remote_upload {dest srcfile args} {
544     if { [llength $args] > 0 } {
545         set destfile [lindex $args 0]
546     } else {
547         set destfile [file tail $srcfile]
548     }
550     if { ![isremote $dest] } {
551         if { $destfile eq "" || $srcfile eq $destfile } {
552             return $srcfile
553         }
554         set result [catch "exec cp -p $srcfile $destfile" output]
555         return $destfile
556     }
558     return [call_remote "" upload $dest $srcfile $destfile]
561 proc standard_upload { dest srcfile destfile } {
562     set orig_srcfile $srcfile
564     if {[board_info $dest exists nfsdir]} {
565         set destdir [board_info $dest nfsdir]
566         if {[board_info $dest exists nfsroot_server]} {
567             set dest [board_info $dest nfsroot_server]
568         } else {
569             set dest ""
570         }
571         set srcfile $destdir/$srcfile
572     }
574     if { $dest ne "" } {
575         return [rsh_upload $dest $srcfile $destfile]
576     }
578     set result [catch "exec cp -p $srcfile $destfile" output]
579     if {[regexp "same file|are identical" $output]} {
580         set result 0
581         set output ""
582     } else {
583         # try to make sure we can read it
584         # and write it (in case we copy onto it again)
585         catch {exec chmod u+rw $destfile}
586     }
587     if { $result != 0 || $output ne "" } {
588         perror "remote_upload to $dest of $srcfile to $destfile: $output"
589         return ""
590     } else {
591         return $destfile
592     }
595 # A standard procedure to call the appropriate function. It first looks
596 # for a board-specific version, then a version specific to the protocol,
597 # and then finally it will call standard_$proc.
599 proc call_remote { type proc dest args } {
600     if {[board_info $dest exists name]} {
601         set dest [board_info $dest name]
602     }
604     if { $proc eq "reboot" } {
605         regsub {/.*} $dest "" dest
606         verbose "Changed dest to $dest"
607     }
609     if { $dest ne "host" && $dest ne "build" && $dest ne "target" } {
610         if { ![board_info $dest exists name] } {
611             global board
613             if {[info exists board]} {
614                 error "board exists"
615             }
616             load_board_description $dest
617             if { $proc eq "reboot" } {
618                 regsub {/.*} $dest "" dest
619                 verbose "Changed dest to $dest"
620             }
621         }
622     }
624     set high_prot ""
625     if { $type ne "raw" } {
626         if {[board_info $dest exists protocol]} {
627             set high_prot "$dest [board_info $dest protocol]"
628         } else {
629             set high_prot "$dest [board_info $dest generic_name]"
630         }
631     }
633     verbose "call_remote $type $proc $dest $args " 3
634     # Close has to be handled specially.
635     if { $proc eq "close" || $proc eq "open" } {
636         foreach try "$high_prot [board_info $dest connect] telnet standard" {
637             if { $try ne "" } {
638                 if { [info procs "${try}_${proc}"] ne "" } {
639                     verbose "call_remote calling ${try}_${proc}" 3
640                     set result [eval ${try}_${proc} \"$dest\" $args]
641                     break
642                 }
643             }
644         }
645         set ft "[board_info $dest file_transfer]"
646         if { [info procs "${ft}_${proc}"] ne "" } {
647             verbose "calling ${ft}_${proc} $dest $args" 3
648             set result2 [eval ${ft}_${proc} \"$dest\" $args]
649         }
650         if {![info exists result]} {
651             if {[info exists result2]} {
652                 set result $result2
653             } else {
654                 set result ""
655             }
656         }
657         return $result
658     }
659     foreach try "$high_prot [board_info $dest file_transfer] [board_info $dest connect] telnet standard" {
660         verbose "looking for ${try}_${proc}" 4
661         if { $try ne "" } {
662             if { [info procs "${try}_${proc}"] ne "" } {
663                 verbose "call_remote calling ${try}_${proc}" 3
664                 return [eval ${try}_${proc} \"$dest\" $args]
665             }
666         }
667     }
668     if { $proc eq "close" } {
669         return ""
670     }
671     error "No procedure for '$proc' in call_remote"
674 # Send FILE through the existing session established to DEST.
676 proc remote_transmit { dest file } {
677     return [call_remote "" transmit $dest $file]
680 proc remote_raw_transmit { dest file } {
681     return [call_remote raw transmit $dest $file]
684 # The default transmit procedure if no other exists. This feeds the
685 # supplied file directly into the connection.
687 proc standard_transmit {dest file} {
688     if {[board_info $dest exists name]} {
689         set dest [board_info $dest name]
690     }
691     if {[board_info $dest exists baud]} {
692         set baud [board_info $dest baud]
693     } else {
694         set baud 9600
695     }
696     set shell_id [board_info $dest fileid]
698     set lines 0
699     set chars 0
700     set fd [open $file r]
701     while { [gets $fd cur_line] >= 0 } {
702         set errmess ""
703         catch "send -i $shell_id \"$cur_line\r\"" errmess
704         if {[string match "write\(spawn_id=\[0-9\]+\):" $errmess]} {
705             perror "sent \"$cur_line\" got expect error \"$errmess\""
706             catch "close $fd"
707             return -1
708         }
709         set chars [expr {$chars + ([string length $cur_line] * 10)}]
710         if { $chars > $baud } {
711             sleep 1
712             set chars 0
713         }
714         verbose "." 3
715         verbose "Sent $cur_line" 4
716         incr lines
717     }
718     verbose "$lines lines transmitted" 2
719     close $fd
720     return 0
723 proc remote_send { dest string } {
724     return [call_remote "" send $dest $string]
727 proc remote_raw_send { dest string } {
728     return [call_remote raw send $dest $string]
731 proc standard_send { dest string } {
732     if {![board_info $dest exists fileid]} {
733         perror "no fileid for $dest"
734         return "no fileid for $dest"
735     } else {
736         set shell_id [board_info $dest fileid]
737         verbose "shell_id in standard_send is $shell_id" 3
738         verbose "send -i [board_info $dest fileid] -- $string" 3
739         if {[catch "send -i [board_info $dest fileid] -- \$string" errorInfo]} {
740             return $errorInfo
741         } else {
742             return ""
743         }
744     }
747 proc file_on_host { op file args } {
748     return [eval remote_file host \"$op\" \"$file\" $args]
751 proc file_on_build { op file args } {
752     return [eval remote_file build \"$op\" \"$file\" $args]
755 proc remote_file { dest args } {
756     return [eval call_remote \"\" file \"$dest\" $args]
759 proc remote_raw_file { dest args } {
760     return [eval call_remote raw file \"$dest\" $args]
763 # Perform the specified file op on a remote Unix board.
765 proc standard_file { dest op args } {
766     set file [lindex $args 0]
767     verbose "dest in proc standard_file is $dest" 3
768     if { ![isremote $dest] } {
769         switch -- $op {
770             cmp {
771                 set otherfile [lindex $args 1]
772                 if { [file exists $file] && [file exists $otherfile]
773                      && [file size $file] == [file size $otherfile] } {
774                     set r [remote_exec build cmp "$file $otherfile"]
775                     if { [lindex $r 0] == 0 } {
776                         return 0
777                     }
778                 }
779                 return 1
780             }
781             tail {
782                 return [file tail $file]
783             }
784             dirname {
785                 if { [file pathtype $file] eq "relative" } {
786                     set file [remote_file $dest absolute $file]
787                 }
788                 set result [file dirname $file]
789                 if { $result eq "" } {
790                     return "/"
791                 }
792                 return $result
793             }
794             join {
795                 return [file join [lindex $args 0] [lindex $args 1]]
796             }
797             absolute {
798                 return [unix_clean_filename $dest $file]
799             }
800             exists {
801                 return [file exists $file]
802             }
803             delete {
804                 foreach x $args {
805                     if { [file exists $x] && [file isfile $x] } {
806                         file delete -force -- $x
807                     }
808                 }
809                 return {}
810             }
811         }
812     } else {
813         switch -- $op {
814             exists {
815                 set status [remote_exec $dest "test -f $file"]
816                 return [expr {[lindex $status 0] == 0}]
817             }
818             delete {
819                 set file ""
820                 # Allow multiple files to be deleted at once.
821                 foreach x $args {
822                     append file " $x"
823                 }
824                 verbose "remote_file deleting $file"
825                 set status [remote_exec $dest "rm -f $file"]
826                 return [lindex $status 0]
827             }
828         }
829     }
832 # Return an absolute version of the filename in $file, with . and ..
833 # removed.
835 proc unix_clean_filename { dest file } {
836     if { [file pathtype $file] eq "relative" } {
837         set file [remote_file $dest join [pwd] $file]
838     }
839     set result ""
840     foreach x [split $file "/"] {
841         if { $x eq "." || $x eq "" } {
842             continue
843         }
844         if { $x eq ".." } {
845             set rlen [expr {[llength $result] - 2}]
846             if { $rlen >= 0 } {
847                 set result [lrange $result 0 $rlen]
848             } else {
849                 set result ""
850             }
851             continue
852         }
853         lappend result $x
854     }
855     return "/[join $result /]"
859 # Start COMMANDLINE running on DEST. By default it is not possible to
860 # redirect I/O. If the optional keyword "readonly" is specified, input
861 # to the command may be redirected. If the optional keyword
862 # "writeonly" is specified, output from the command may be redirected.
864 # If the command is successfully started, a positive "spawn id" is returned.
865 # If the spawn fails, a negative value will be returned.
867 # Once the command is spawned, you can interact with it via the remote_expect
868 # and remote_wait functions.
870 proc remote_spawn { dest commandline args } {
871     global board_info
873     if {![isremote $dest]} {
874         if {[info exists board_info($dest,fileid)]} {
875             unset board_info($dest,fileid)
876         }
877         verbose "remote_spawn is local" 3
878         if {[board_info $dest exists name]} {
879             set dest [board_info $dest name]
880         }
882         verbose "spawning command $commandline"
884         if { [llength $args] > 0 } {
885             if { [lindex $args 0] eq "readonly" } {
886                 set result [catch { open "| $commandline |& cat" "r" } id]
887                 if { $result != 0 } {
888                     return -1
889                 }
890             } else {
891                 set result [catch {open "| $commandline" "w"} id]
892                 if { $result != 0 } {
893                     return -1
894                 }
895             }
896             set result [catch "spawn -leaveopen $id" result2]
897             if { $result == 0 && $result2 == 0} {
898                 verbose "setting board_info($dest,fileid) to $spawn_id" 3
899                 set board_info($dest,fileid) $spawn_id
900                 set board_info($dest,fileid_origid) $id
901                 return $spawn_id
902             } else {
903                 # This shouldn't happen.
904                 global errorInfo
905                 if {[info exists errorInfo]} {
906                     set foo $errorInfo
907                 } else {
908                     set foo ""
909                 }
910                 verbose "spawn -open $id failed, $result $result2, $foo"
911                 catch "close $id"
912                 return -1
913             }
914         } else {
915             set result [catch "spawn $commandline" pid]
916             if { $result == 0 } {
917                 verbose "setting board_info($dest,fileid) to $spawn_id" 3
918                 set board_info($dest,fileid) $spawn_id
919                 return $spawn_id
920             } else {
921                 verbose -log "spawn of $commandline failed"
922                 return -1
923             }
924         }
925     }
927     # Seems to me there should be a cleaner way to do this.
928     if { $args eq "" } {
929         return [call_remote "" spawn $dest $commandline]
930     } else {
931         return [call_remote "" spawn $dest $commandline $args]
932     }
935 proc remote_raw_spawn { dest commandline } {
936     return [call_remote raw spawn $dest $commandline]
939 # The default spawn procedure. Uses rsh to connect to $dest.
941 proc standard_spawn { dest commandline } {
942     global board_info
944     if {![board_info $dest exists rsh_prog]} {
945         if { [which remsh] != 0 } {
946             set RSH remsh
947         } else {
948             set RSH rsh
949         }
950     } else {
951         set RSH [board_info $dest rsh_prog]
952     }
954     if {[board_info $dest exists hostname]} {
955         set remote [board_info $dest hostname]
956     } else {
957         set remote $dest
958     }
960     if {![board_info $dest exists username]} {
961         spawn $RSH $remote $commandline
962     } else {
963         spawn $RSH -l [board_info $dest username] $remote $commandline
964     }
966     set board_info($dest,fileid) $spawn_id
967     return $spawn_id
970 # Run PROG on DEST, with optional arguments, input and output files.
971 # It returns a list of two items. The first is ether "pass" if the
972 # program loaded, ran and exited with a zero exit status, or "fail"
973 # otherwise.  The second argument is any output produced by the
974 # program while it was running.
976 proc remote_load { dest prog args } {
977     global tool
979     set dname [board_info $dest name]
980     set cache "[getenv REMOTELOAD_CACHE]/$tool/$dname/[file tail $prog]"
981     set empty [isremote $dest]
982     if { [board_info $dest exists is_simulator] || [getenv REMOTELOAD_CACHE] eq "" } {
983         set empty 0
984     } else {
985         for { set x 0 } {$x < [llength $args] } {incr x} {
986             if { [lindex $args $x] ne "" } {
987                 set empty 0
988                 break
989             }
990         }
991     }
992     if {$empty} {
993         global sum_program
995         if {[info exists sum_program]} {
996             if {![target_info exists objcopy]} {
997                 set_currtarget_info objcopy [find_binutils_prog objcopy]
998             }
999             if {[isremote host]} {
1000                 set dprog [remote_download host $prog "a.out"]
1001             } else {
1002                 set dprog $prog
1003             }
1004             set status [remote_exec host "[target_info objcopy]" "-O srec $dprog $dprog.sum"]
1005             if {[isremote host]} {
1006                 remote_file upload $dprog.sum $prog.sum
1007             }
1008             if { [lindex $status 0] == 0 } {
1009                 set sumout [remote_exec build $sum_program $prog.sum]
1010                 set sum [lindex $sumout 1]
1011                 regsub "\[\r\n \t\]+$" $sum "" sum
1012             } else {
1013                 set sumout [remote_exec build $sum_program $prog]
1014                 set sum [lindex $sumout 1]
1015                 regsub "\[\r\n \t\]+$" $sum "" sum
1016             }
1017             remote_file build delete $prog.sum
1018         }
1019         if {[file exists $cache]} {
1020             set same 0
1021             if {[info exists sum_program]} {
1022                 set id [open $cache "r"]
1023                 set oldsum [read $id]
1024                 close $id
1025                 if { $oldsum == $sum } {
1026                     set same 1
1027                 }
1028             } else {
1029                 if { [remote_file build cmp $prog $cache] == 0 } {
1030                     set same 1
1031                 }
1032             }
1033             if { $same } {
1034                 set fd [open $cache.res "r"]
1035                 gets $fd l1
1036                 set result [list $l1 [read $fd]]
1037                 close $fd
1038             }
1039         }
1040     }
1041     if {![info exists result]} {
1042         set result [eval call_remote \"\" load \"$dname\" \"$prog\" $args]
1043         # Not quite happy about the "pass" condition, but it makes sense if
1044         # you think about it for a while-- *why* did the test not pass?
1045         if { $empty && [lindex $result 0] eq "pass" } {
1046             if { [getenv LOAD_REMOTECACHE] ne "" } {
1047                 set dir "[getenv REMOTELOAD_CACHE]/$tool/$dname"
1048                 if {![file exists $dir]} {
1049                     file mkdir $dir
1050                 }
1051                 if {[file exists $dir]} {
1052                     if {[info exists sum_program]} {
1053                         set id [open $cache "w"]
1054                         puts -nonewline $id $sum
1055                         close $id
1056                     } else {
1057                         remote_exec build cp "$prog $cache"
1058                     }
1059                     set id [open $cache.res "w"]
1060                     puts $id [lindex $result 0]
1061                     puts -nonewline $id [lindex $result 1]
1062                     close $id
1063                 }
1064             }
1065         }
1066     }
1067     return $result
1070 proc remote_raw_load { dest prog args } {
1071     return [eval call_remote raw load \"$dest\" \"$prog\" $args ]
1074 # The default load procedure if no other exists for $dest. It uses
1075 # remote_download and remote_exec to load and execute the program.
1077 proc standard_load { dest prog args } {
1078     global board_info
1080     if { [llength $args] > 0 } {
1081         set pargs [lindex $args 0]
1082     } else {
1083         set pargs ""
1084     }
1086     if { [llength $args] > 1 } {
1087         set inp "[lindex $args 1]"
1088     } else {
1089         set inp ""
1090     }
1092     if {![file exists $prog]} then {
1093         # We call both here because this should never happen.
1094         perror "$prog does not exist in standard_load."
1095         verbose -log "$prog does not exist." 3
1096         return "untested"
1097     }
1099     if {[isremote $dest]} {
1100         if {![board_info $dest exists remotedir]} {
1101             set board_info($dest,remotedir) "/tmp/runtest.[pid]"
1102         }
1103         set remotefile [file tail $prog]
1104         set remotefile [remote_download $dest $prog $remotefile]
1105         if { $remotefile eq "" } {
1106             verbose -log "Download of $prog to [board_info $dest name] failed." 3
1107             return "unresolved"
1108         }
1109         if {[board_info $dest exists remote_link]} {
1110             if {[[board_info $dest remote_link] $remotefile]} {
1111                 verbose -log "Couldn't do remote link"
1112                 remote_file target delete $remotefile
1113                 return "unresolved"
1114             }
1115         }
1116         set status [remote_exec $dest $remotefile $pargs $inp]
1117         remote_file $dest delete $remotefile
1118     } else {
1119         set status [remote_exec $dest $prog $pargs $inp]
1120     }
1121     if { [lindex $status 0] < 0 } {
1122         verbose -log "Couldn't execute $prog, [lindex $status 1]" 3
1123         return "unresolved"
1124     }
1125     set output [lindex $status 1]
1126     set status [lindex $status 0]
1128     verbose -log "Executed $prog, status $status" 2
1129     if { $output ne "" } {
1130         verbose -log -- $output 2
1131     }
1132     if { $status == 0 } {
1133         return [list "pass" $output]
1134     } else {
1135         return [list "fail" $output]
1136     }
1139 # Loads PROG into DEST.
1141 proc remote_ld { dest prog } {
1142     return [eval call_remote \"\" ld \"$dest\" \"$prog\"]
1145 proc remote_raw_ld { dest prog } {
1146     return [eval call_remote raw ld \"$dest\" \"$prog\"]
1149 # Wait up to TIMEOUT seconds for the last spawned command on DEST to
1150 # complete. A list of two values is returned; the first is the exit
1151 # status (-1 if the program timed out), and the second is any output
1152 # produced by the command.
1154 proc remote_wait { dest timeout } {
1155     return [eval call_remote \"\" wait \"$dest\" $timeout]
1158 proc remote_raw_wait { dest timeout } {
1159     return [eval call_remote raw wait \"$dest\" $timeout]
1162 # The standard wait procedure, used for commands spawned on the local
1163 # machine.
1165 proc standard_wait { dest timeout } {
1166     set output ""
1167     set status -1
1169     if {[info exists exp_close_result]} {
1170         unset exp_close_result
1171     }
1172     remote_expect $dest $timeout {
1173         -re ".+" {
1174             append output $expect_out(buffer)
1175             exp_continue -continue_timer
1176         }
1177         timeout {
1178             warning "program timed out."
1179         }
1180         eof {
1181             # There may be trailing characters in the buffer.
1182             # Append them, too.
1183             append output $expect_out(buffer)
1184             if {[board_info $dest exists fileid_origid]} {
1185                 global board_info
1187                 set id [board_info $dest fileid]
1188                 set oid [board_info $dest fileid_origid]
1189                 verbose "$id $oid"
1190                 unset board_info($dest,fileid)
1191                 unset board_info($dest,fileid_origid)
1192                 catch "close -i $id"
1193                 # I don't believe this. You HAVE to do a wait, even tho
1194                 # it won't work! stupid ()*$%*)(% expect...
1195                 catch "wait -i $id"
1196                 set r2 [catch "close $oid" res]
1197                 if { $r2 != 0 } {
1198                     verbose "close result is $res"
1199                     set status 1
1200                 } else {
1201                     set status 0
1202                 }
1203             } else {
1204                 set s [wait -i [board_info $dest fileid]]
1205                 if { [lindex $s 0] != 0 && [lindex $s 2] == 0 } {
1206                     set status [lindex $s 3]
1207                     if { [llength $s] > 4 } {
1208                         if { [lindex $s 4] eq "CHILDKILLED" } {
1209                             set status 1
1210                         }
1211                     }
1212                 }
1213             }
1214         }
1215     }
1217     remote_close $dest
1218     return [list $status $output]
1221 # This checks the value contained in the variable named "variable" in
1222 # the calling procedure for output from the status wrapper and returns
1223 # a non-negative value if it exists; otherwise, it returns -1. The
1224 # output from the wrapper is removed from the variable.
1226 proc check_for_board_status  { variable } {
1227     upvar $variable output
1229     # If all programs of this board have a wrapper that always outputs a
1230     # status message, then the absence of it means that the program
1231     # crashed, regardless of status found elsewhere (e.g. simulator exit
1232     # code).
1233     if { [target_info needs_status_wrapper] ne "" } then {
1234         set nomatch_return 2
1235     } else {
1236         set nomatch_return -1
1237     }
1239     if {[regexp "(^|\[\r\n\])\\*\\*\\* EXIT code" $output]} {
1240         regsub "^.*\\*\\*\\* EXIT code " $output "" result
1241         regsub "\[\r\n\].*$" $result "" result
1242         regsub -all "(^|\[\r\n\]|\r\n)\\*\\*\\* EXIT code \[^\r\n\]*(\[\r\n\]\[\r\n\]?|$)" $output "" output
1243         regsub "^\[^0-9\]*" $result "" result
1244         regsub "\[^0-9\]*$" $result "" result
1245         verbose "got board status $result" 3
1246         verbose "output is $output" 3
1247         if { $result eq "" } {
1248             return $nomatch_return
1249         } else {
1250             return [expr {$result}]
1251         }
1252     } else {
1253         return $nomatch_return
1254     }
1257 # remote_expect works basically the same as standard expect, but it
1258 # also takes care of getting the file descriptor from the specified
1259 # host and also calling the timeout/eof/default section if there is an
1260 # error on the expect call.
1262 proc remote_expect { board timeout args } {
1263     global errorInfo errorCode
1264     global remote_suppress_flag
1266     set spawn_id [board_info $board fileid]
1268     if { [llength $args] == 1 } {
1269         set args "[lindex $args 0]"
1270     }
1272     set res {}
1273     set got_re 0
1274     set need_append 1
1276     set orig $args
1278     set error_sect ""
1279     set save_next 0
1281     if { $spawn_id eq "" } {
1282         # This should be an invalid spawn id.
1283         set spawn_id 1000
1284     }
1286     for { set i 0 } { $i < [llength $args] } { incr i }  {
1287         if { $need_append } {
1288             append res "\n-i $spawn_id "
1289             set need_append 0
1290         }
1292         set x "[lrange $args $i $i]"
1293         regsub "^\n*\[ \t\]*" $x "" x
1295         if { $x eq "-i" || $x eq "-timeout" || $x eq "-ex" } {
1296             append res "$x "
1297             set next [expr {$i + 1}]
1298             append res "[lrange $args $next $next]"
1299             incr i
1300             continue
1301         }
1302         if { $x eq "-n" || $x eq "-notransfer" || $x eq "-nocase" || $x eq "-indices" } {
1303             append res "$x "
1304             continue
1305         }
1306         if { $x eq "-re" } {
1307             append res "$x "
1308             set next [expr {$i + 1}]
1309             set y [lrange $args $next $next]
1310             append res "$y "
1311             set got_re 1
1312             incr i
1313             continue
1314         }
1315         if { $got_re } {
1316             set need_append 0
1317             append res "$x "
1318             set got_re 0
1319             if { $save_next } {
1320                 set save_next 0
1321                 set error_sect [lindex $args $i]
1322             }
1323         } else {
1324             if { $x eq "eof" } {
1325                 set save_next 1
1326             } elseif { $x eq "default" || $x eq "timeout" } {
1327                 if { $error_sect eq "" } {
1328                     set save_next 1
1329                 }
1330             }
1331             append res "$x "
1332             set got_re 1
1333         }
1334     }
1336     if {[info exists remote_suppress_flag]} {
1337         if { $remote_suppress_flag } {
1338             set code 1
1339         }
1340     }
1341     if {![info exists code]} {
1342         set res "\n-timeout $timeout $res"
1343         set body "expect \{\n-i $spawn_id -timeout $timeout $orig\}"
1344         set code [catch {uplevel $body} string]
1345     }
1347     if {$code == 1} {
1348         if {[info exists string]} {
1349             perror "$errorInfo $errorCode $string"
1350         }
1352         if { $error_sect ne "" } {
1353             set code [catch {uplevel $error_sect} string]
1354         } else {
1355             warning "remote_expect statement without a default case"
1356             return
1357         }
1358     }
1360     if {$code == 1} {
1361         return -code error -errorinfo $errorInfo -errorcode $errorCode $string
1362     } else {
1363         return -code $code $string
1364     }
1367 # Push the current connection to HOST onto a stack.
1369 proc remote_push_conn { host } {
1370     global board_info
1372     set name [board_info $host name]
1374     if { $name eq "" } {
1375         return "fail"
1376     }
1378     if {![board_info $host exists fileid]} {
1379         return "fail"
1380     }
1382     set fileid [board_info $host fileid]
1383     set conninfo [board_info $host conninfo]
1384     if {![info exists board_info($name,fileid_stack)]} {
1385         set board_info($name,fileid_stack) {}
1386     }
1387     set board_info($name,fileid_stack) [list $fileid $conninfo $board_info($name,fileid_stack)]
1388     unset board_info($name,fileid)
1389     if {[info exists board_info($name,conninfo)]} {
1390         unset board_info($name,conninfo)
1391     }
1392     return "pass"
1395 # Pop a previously-pushed connection from a stack. You should have closed the
1396 # current connection before doing this.
1398 proc remote_pop_conn { host } {
1399     global board_info
1401     set name [board_info $host name]
1403     if { $name eq "" } {
1404         return "fail"
1405     }
1406     if {![info exists board_info($name,fileid_stack)]} {
1407         return "fail"
1408     }
1409     set stack $board_info($name,fileid_stack)
1410     if { [llength $stack] < 3 } {
1411         return "fail"
1412     }
1413     set board_info($name,fileid) [lindex $stack 0]
1414     set board_info($name,conninfo) [lindex $stack 1]
1415     set board_info($name,fileid_stack) [lindex $stack 2]
1416     return "pass"
1419 # Swap the current connection with the topmost one on the stack.
1421 proc remote_swap_conn { host } {
1422     global board_info
1423     set name [board_info $host name]
1425     if {![info exists board_info($name,fileid)]} {
1426         return "fail"
1427     }
1429     set fileid $board_info($name,fileid)
1430     if {[info exists board_info($name,conninfo)]} {
1431         set conninfo $board_info($name,conninfo)
1432     } else {
1433         set conninfo {}
1434     }
1435     if { [remote_pop_conn $host] ne "pass" } {
1436         set board_info($name,fileid) $fileid
1437         set board_info($name,conninfo) $conninfo
1438         return "fail"
1439     }
1440     set newfileid $board_info($name,fileid)
1441     set newconninfo $board_info($name,conninfo)
1442     set board_info($name,fileid) $fileid
1443     set board_info($name,conninfo) $conninfo
1444     remote_push_conn $host
1445     set board_info($name,fileid) $newfileid
1446     set board_info($name,conninfo) $newconninfo
1447     return "pass"
1450 set sum_program "testcsum"