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