# Copyright (C) 1992 - 2002, 2003 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # Please email any bugs, comments, and/or additions to this file to: # bug-dejagnu@gnu.org # This file was written by Rob Savoye. (rob@welcomehome.org) # load various protocol support modules load_lib "mondfe.exp" load_lib "xsh.exp" load_lib "telnet.exp" load_lib "rlogin.exp" load_lib "kermit.exp" load_lib "tip.exp" load_lib "rsh.exp" load_lib "ftp.exp" # # Open a connection to a remote host or target. This requires the target_info # array be filled in with the proper info to work. # # type is either "build", "host", "target", or the name of a board loaded # into the board_info array. The default is target if no name is supplied. # It returns the spawn id of the process that is the connection. # proc remote_open { args } { global reboot if { [llength $args] == 0 } { set type "target" } else { set type $args } # Shudder... if { $reboot && $type == "target" } { reboot_target; } return [call_remote "" open $type]; } proc remote_raw_open { args } { return [eval call_remote raw open $args]; } # Run the specified COMMANDLINE on the local machine, redirecting input # to file INP (if non-empty), redirecting output to file OUTP (if non-empty), # and waiting TIMEOUT seconds for the command to complete before killing # it. A two-member list is returned; the first member is the exit status # of the command, the second is any output produced from the command # (if output is redirected, this may or may not be empty). If output is # redirected, both stdout and stderr will appear in the specified file. # # Caveats: A pipeline is used if input or output is redirected. There # will be problems with killing the program if a pipeline is used. Either # the "tee" command or the "cat" command is used in the pipeline if input # or output is redirected. If the program needs to be killed, /bin/sh and # the kill command will be invoked. # proc local_exec { commandline inp outp timeout } { # TCL's exec is a pile of crap. It does two very inappropriate things; # firstly, it has no business returning an error if the program being # executed happens to write to stderr. Secondly, it appends its own # error messages to the output of the command if the process exits with # non-zero status. # # So, ok, we do this funny stuff with using spawn sometimes and # open others because of spawn's inability to invoke commands with # redirected I/O. We also hope that nobody passes in a command that's # a pipeline, because spawn can't handle it. # # We want to use spawn in most cases, because tcl's pipe mechanism # doesn't assign process groups correctly and we can't reliably kill # programs that bear children. We can't use tcl's exec because it has # no way to timeout programs that hang. *sigh* # if { "$inp" == "" && "$outp" == "" } { set id -1; set result [catch "eval spawn \{${commandline}\}" pid]; if { $result == 0 } { set result2 0; } else { set pid 0; set result2 5; } } else { # Can you say "uuuuuugly"? I knew you could! # All in the name of non-infinite hangs. if { $inp != "" } { set inp "< $inp"; set mode "r"; } else { set mode "w"; } set use_tee 0; # We add |& cat so that TCL exec doesn't freak out if the # program writes to stderr. if { $outp == "" } { set outp "|& cat" } else { set outpf "$outp"; set outp "> $outp" if { $inp != "" } { set use_tee 1; } } # Why do we use tee? Because open can't redirect both input and output. if { $use_tee } { set result [catch {open "| ${commandline} $inp |& tee $outpf" RDONLY} id] ; } else { set result [catch {open "| ${commandline} $inp $outp" $mode} id] ; } if { $result != 0 } { global errorInfo return [list -1 "open of $commandline $inp $outp failed: $errorInfo"]; } set pid [pid $id]; set result [catch "spawn -leaveopen $id" result2]; } # Prepend "-" to each pid, to generate the "process group IDs" needed by # kill. set pgid "-[join $pid { -}]"; verbose "pid is $pid $pgid"; if { $result != 0 || $result2 != 0 } { # This shouldn't happen. global errorInfo; if [info exists errorInfo] { set foo $errorInfo; } else { set foo ""; } verbose "spawn -open $id failed, $result $result2, $foo"; catch "close $id"; return [list -1 "spawn failed"]; } set got_eof 0; set output ""; # Wait for either $timeout seconds to elapse, or for the program to # exit. expect { -i $spawn_id -timeout $timeout -re ".+" { append output $expect_out(buffer); if { [string length $output] < 512000 } { exp_continue -continue_timer; } } timeout { warning "program timed out."; } eof { set got_eof 1; } } # Uuuuuuugh. Now I'm getting really sick. # If we didn't get an EOF, we have to kill the poor defenseless program. # However, TCL has no kill primitive, so we have to execute an external # command in order to execute the execution. (English. Gotta love it.) if { ! $got_eof } { verbose "killing $pid $pgid"; # This is very, very nasty. SH, instead of EXPECT, is used to # run this in the background since, on older CYGWINs, a # strange file I/O error occures. exec sh -c "exec > /dev/null 2>&1 && (kill -2 $pgid || kill -2 $pid) && sleep 5 && (kill -15 $pgid || kill $pid) && sleep 5 && (kill -9 $pgid || kill -9 $pid) &"; } # This will hang if the kill doesn't work. Nothin' to do, and it's not ok. catch "close -i $spawn_id"; set r2 [catch "wait -i $spawn_id" wres]; if { $id > 0 } { set r2 [catch "close $id" res]; } else { verbose "waitres is $wres" 2; if { $r2 == 0 } { set r2 [lindex $wres 3]; if { [llength $wres] > 4 } { if { [lindex $wres 4] == "CHILDKILLED" } { set r2 1; } } if { $r2 != 0 } { set res "$wres"; } else { set res ""; } } else { set res "wait failed"; } } if { $r2 != 0 || $res != "" || ! $got_eof } { verbose "close result is $res"; set status 1; } else { set status 0; } verbose "output is $output"; if { $outp == "" } { return [list $status $output]; } else { return [list $status ""]; } } # # Execute the supplied program on HOSTNAME. There are four optional arguments; # the first is a set of arguments to pass to PROGRAM, the second is an # input file to feed to stdin of PROGRAM, the third is the name of an # output file where the output from PROGRAM should be written, and # the fourth is a timeout value (we give up after the specified # of seconds # has elapsed). # # A two-element list is returned. The first value is the exit status of the # program (-1 if the exec failed). The second is any output produced by # the program (which may or may not be empty if output from the program was # redirected). # proc remote_exec { hostname program args } { if { [llength $args] > 0 } { set pargs [lindex $args 0]; } else { set pargs "" } if { [llength $args] > 1 } { set inp "[lindex $args 1]"; } else { set inp "" } if { [llength $args] > 2 } { set outp "[lindex $args 2]"; } else { set outp "" } # 300 is probably a lame default. if { [llength $args] > 3 } { set timeout "[lindex $args 3]"; } else { set timeout 300 } verbose -log "Executing on $hostname: $program $pargs $inp $outp (timeout = $timeout)" 2; # Run it locally if appropriate. if { ![is_remote $hostname] } { return [local_exec "$program $pargs" $inp $outp $timeout]; } else { return [call_remote "" exec $hostname $program $pargs $inp $outp]; } } proc standard_exec { hostname args } { return [eval rsh_exec \"$hostname\" $args]; } # # Close the remote connection. # arg - This is the name of the machine whose connection we're closing, # or target, host or build. # proc remote_close { host } { while { 1 } { set result [call_remote "" close "$host"]; if { [remote_pop_conn $host] != "pass" } { break; } } return $result; } proc remote_raw_close { host } { return [call_remote raw close "$host"]; } proc standard_close { host } { global board_info if [board_info ${host} exists fileid] { set shell_id [board_info ${host} fileid]; set pid -1; verbose "Closing the remote shell $shell_id" 2 if [board_info ${host} exists fileid_origid] { set oid [board_info ${host} fileid_origid]; set pid [pid $oid]; unset board_info(${host},fileid_origid); } else { set result [catch "exp_pid -i $shell_id" pid]; if { $result != 0 || $pid <= 0 } { set result [catch "pid $shell_id" pid]; if { $result != 0 } { set pid -1; } } } if { $pid > 0 } { verbose "doing kill, pid is $pid"; # This is very, very nasty. SH, instead of EXPECT, is used # to run this in the background since, on older CYGWINs, a # strange file I/O error occures. set pgid "-[join $pid { -}]"; exec sh -c "exec > /dev/null 2>&1 && (kill -2 $pgid || kill -2 $pid) && sleep 5 && (kill $pgid || kill $pid) && sleep 5 && (kill -9 $pgid || kill -9 $pid) &"; } verbose "pid is $pid"; catch "close -i $shell_id"; if [info exists oid] { catch "close $oid"; } catch "wait -i $shell_id"; unset board_info(${host},fileid); verbose "Shell closed."; } return 0; } # # Set the connection into "binary" mode, a.k.a. no processing of input # characters. # proc remote_binary { host } { return [call_remote "" binary "$host"]; } proc remote_raw_binary { host } { return [call_remote raw binary "$host"]; } proc remote_reboot { host } { clone_output "\nRebooting ${host}\n"; # FIXME: don't close the host connection, or all the remote # procedures will fail. # remote_close $host; set status [call_remote "" reboot "$host"]; if [board_info $host exists name] { set host [board_info $host name]; } if { [info proc ${host}_init] != "" } { ${host}_init $host; } return $status; } proc standard_reboot { host } { return ""; } # # Download file FILE to DEST. If the optional DESTFILE is specified, # that file will be used on the destination board. It returns either # "" (indicating that the download failed), or the name of the file on # the destination machine. # proc remote_download { dest file args } { if { [llength $args] > 0 } { set destfile [lindex $args 0]; } else { set destfile [file tail $file]; } if { ![is_remote $dest] } { if { $destfile == "" || $destfile == $file } { return $file; } else { set result [catch "exec cp -p $file $destfile" output]; if [regexp "same file|are identical" $output] { set result 0 set output "" } else { # try to make sure we can read it # and write it (in case we copy onto it again) catch {exec chmod u+rw $destfile} } if { $result != 0 || $output != "" } { perror "remote_download to $dest of $file to $destfile: $output" return ""; } else { return $destfile; } } } return [call_remote "" download $dest $file $destfile]; } # # The default download procedure. Uses rcp to download to $dest. # proc standard_download {dest file destfile} { set orig_destfile $destfile if [board_info $dest exists nfsdir] { set destdir [board_info $dest nfsdir] if [board_info $dest exists nfsroot_server] { set dest [board_info $dest nfsroot_server]; } else { set dest ""; } set destfile "$destdir/$destfile"; } if { "$dest" != "" } { set result [rsh_download $dest $file $destfile]; if { $result == $destfile } { return $orig_destfile; } else { return $result; } } set result [catch "exec cp -p $file $destfile" output]; if [regexp "same file|are identical" $output] { set result 0 set output "" } else { # try to make sure we can read it # and write it (in case we copy onto it again) catch {exec chmod u+rw $destfile} } if { $result != 0 || $output != "" } { perror "remote_download to $dest of $file to $destfile: $output" return ""; } else { return $orig_destfile; } } proc remote_upload {dest srcfile args} { if { [llength $args] > 0 } { set destfile [lindex $args 0]; } else { set destfile [file tail $srcfile]; } if { ![is_remote $dest] } { if { $destfile == "" || $srcfile == $destfile } { return $srcfile; } set result [catch "exec cp -p $srcfile $destfile" output]; return $destfile; } return [call_remote "" upload $dest $srcfile $destfile]; } proc standard_upload { dest srcfile destfile } { set orig_srcfile $srcfile if [board_info $dest exists nfsdir] { set destdir [board_info $dest nfsdir] if [board_info $dest exists nfsroot_server] { set dest [board_info $dest nfsroot_server]; } else { set dest ""; } set srcfile "$destdir/$srcfile"; } if { "$dest" != "" } { return [rsh_upload $dest $srcfile $destfile]; } set result [catch "exec cp -p $srcfile $destfile" output]; if [regexp "same file|are identical" $output] { set result 0 set output "" } else { # try to make sure we can read it # and write it (in case we copy onto it again) catch {exec chmod u+rw $destfile} } if { $result != 0 || $output != "" } { perror "remote_upload to $dest of $file to $destfile: $output" return ""; } else { return $destfile; } return [rsh_upload $dest $srcfile $destfile]; } # # A standard procedure to call the appropriate function. It first looks # for a board-specific version, then a version specific to the protocol, # and then finally it will call standard_$proc. # proc call_remote { type proc dest args } { if [board_info $dest exists name] { set dest [board_info $dest name]; } if { $dest != "host" && $dest != "build" && $dest != "target" } { if { ![board_info $dest exists name] } { global board; if [info exists board] { blooie } load_board_description $dest; } } set high_prot "" if { $type != "raw" } { if [board_info $dest exists protocol] { set high_prot "${dest} [board_info $dest protocol]"; } else { set high_prot "${dest} [board_info $dest generic_name]"; } } verbose "call_remote $type $proc $dest $args " 3 # Close has to be handled specially. if { $proc == "close" || $proc == "open" } { foreach try "$high_prot [board_info $dest connect] telnet standard" { if { $try != "" } { if { [info proc "${try}_${proc}"] != "" } { verbose "call_remote calling ${try}_${proc}" 3 set result [eval ${try}_${proc} \"$dest\" $args]; break; } } } set ft "[board_info $dest file_transfer]" if { [info proc "${ft}_${proc}"] != "" } { verbose "calling ${ft}_${proc} $dest $args" 3 set result2 [eval ${ft}_${proc} \"$dest\" $args]; } if ![info exists result] { if [info exists result2] { set result $result2; } else { set result ""; } } return $result; } foreach try "${high_prot} [board_info $dest file_transfer] [board_info $dest connect] telnet standard" { verbose "looking for ${try}_${proc}" 4 if { $try != "" } { if { [info proc "${try}_${proc}"] != "" } { verbose "call_remote calling ${try}_${proc}" 3 return [eval ${try}_${proc} \"$dest\" $args]; } } } if { $proc == "close" } { return "" } error "No procedure for '$proc' in call_remote" return -1; } # # Send FILE through the existing session established to DEST. # proc remote_transmit { dest file } { return [call_remote "" transmit "$dest" "$file"]; } proc remote_raw_transmit { dest file } { return [call_remote raw transmit "$dest" "$file"]; } # # The default transmit procedure if no other exists. This feeds the # supplied file directly into the connection. # proc standard_transmit {dest file} { if [board_info ${dest} exists name] { set dest [board_info ${dest} name]; } if [board_info ${dest} exists baud] { set baud [board_info ${dest} baud]; } else { set baud 9600; } set shell_id [board_info ${dest} fileid]; set lines 0 set chars 0; set fd [open $file r] while { [gets $fd cur_line] >= 0 } { set errmess "" catch "send -i $shell_id \"$cur_line\r\"" errmess if [string match "write\(spawn_id=\[0-9\]+\):" $errmess] { perror "sent \"$cur_line\" got expect error \"$errmess\"" catch "close $fd" return -1 } set chars [expr $chars + ([string length $cur_line] * 10)] if { $chars > $baud } { sleep 1; set chars 0 } verbose "." 3 verbose "Sent $cur_line" 4 incr lines } verbose "$lines lines transmitted" 2 close $fd return 0 } proc remote_send { dest string } { return [call_remote "" send "$dest" "$string"]; } proc remote_raw_send { dest string } { return [call_remote raw send "$dest" "$string"]; } proc standard_send { dest string } { if ![board_info $dest exists fileid] { perror "no fileid for $dest" return "no fileid for $dest"; } else { set shell_id [board_info $dest fileid] verbose "shell_id in standard_send is $shell_id" 3 verbose "send -i [board_info $dest fileid] -- {$string}" 3 if [catch "send -i [board_info $dest fileid] -- {$string}" errorInfo] { return "$errorInfo"; } else { return ""; } } } proc file_on_host { op file args } { return [eval remote_file host \"$op\" \"$file\" $args]; } proc file_on_build { op file args } { return [eval remote_file build \"$op\" \"$file\" $args]; } proc remote_file { dest args } { return [eval call_remote \"\" file \"$dest\" $args]; } proc remote_raw_file { dest args } { return [eval call_remote raw file \"$dest\" $args]; } # # Perform the specified file op on a remote Unix board. # proc standard_file { dest op args } { set file [lindex $args 0]; verbose "dest in proc standard_file is $dest" 3; if { ![is_remote $dest] } { switch $op { cmp { set otherfile [lindex $args 1]; if { [file exists $file] && [file exists $otherfile] && [file size $file] == [file size $otherfile] } { set r [remote_exec build cmp "$file $otherfile"]; if { [lindex $r 0] == 0 } { return 0; } } return 1; } tail { return [file tail $file]; } dirname { if { [file pathtype $file] == "relative" } { set file [remote_file $dest absolute $file]; } set result [file dirname $file]; if { $result == "" } { return "/"; } return $result; } join { return [file join [lindex $args 0] [lindex $args 1]]; } absolute { return [unix_clean_filename $dest $file]; } exists { return [file exists $file]; } delete { foreach x $args { if { [file exists $x] && [file isfile $x] } { exec rm -f $x; } } return; } } } switch $op { exists { # mmmm, quotes. set status [remote_exec $dest "sh -c 'exit `\[ -f $file \]`'"]; return [lindex $status 0]; } delete { set file "" # Allow multiple files to be deleted at once. foreach x $args { append file " $x"; } verbose "remote_file deleting $file" set status [remote_exec $dest "rm -f $file"]; return [lindex $status 0]; } } } # # Return an absolute version of the filename in $file, with . and .. # removed. # proc unix_clean_filename { dest file } { if { [file pathtype $file] == "relative" } { set file [remote_file $dest join [pwd] $file]; } set result ""; foreach x [split $file "/"] { if { $x == "." || $x == "" } { continue; } if { $x == ".." } { set rlen [expr [llength $result] - 2]; if { $rlen >= 0 } { set result [lrange $result 0 $rlen]; } else { set result "" } continue; } lappend result $x; } return "/[join $result /]" } # # Start COMMANDLINE running on DEST. By default it is not possible to # redirect I/O. If the optional keyword "readonly" is specified, input # to the command may be redirected. If the optional keyword # "writeonly" is specified, output from the command may be redirected. # # If the command is successfully started, a positive "spawn id" is returned. # If the spawn fails, a negative value will be returned. # # Once the command is spawned, you can interact with it via the remote_expect # and remote_wait functions. # proc remote_spawn { dest commandline args } { global board_info if ![is_remote $dest] { if [info exists board_info($dest,fileid)] { unset board_info($dest,fileid); } verbose "remote_spawn is local" 3; if [board_info $dest exists name] { set dest [board_info $dest name]; } verbose "spawning command $commandline" if { [llength $args] > 0 } { if { [lindex $args 0] == "readonly" } { set result [catch { open "| ${commandline} |& cat" "r" } id]; if { $result != 0 } { return -1; } } else { set result [catch {open "| ${commandline}" "w"} id] ; if { $result != 0 } { return -1; } } set result [catch "spawn -leaveopen $id" result2]; if { $result == 0 && $result2 == 0} { verbose "setting board_info($dest,fileid) to $spawn_id" 3 set board_info($dest,fileid) $spawn_id; set board_info($dest,fileid_origid) $id; return $spawn_id; } else { # This shouldn't happen. global errorInfo; if [info exists errorInfo] { set foo $errorInfo; } else { set foo ""; } verbose "spawn -open $id failed, $result $result2, $foo"; catch "close $id"; return -1; } } else { set result [catch "spawn $commandline" pid]; if { $result == 0 } { verbose "setting board_info($dest,fileid) to $spawn_id" 3 set board_info($dest,fileid) $spawn_id; return $spawn_id; } else { verbose -log "spawn of $commandline failed"; return -1; } } } # Seems to me there should be a cleaner way to do this. if { "$args" == "" } { return [call_remote "" spawn "$dest" "$commandline"]; } else { return [call_remote "" spawn "$dest" "$commandline" $args]; } } proc remote_raw_spawn { dest commandline } { return [call_remote raw spawn "$dest" "$commandline"]; } # # The default spawn procedure. Uses rsh to connect to $dest. # proc standard_spawn { dest commandline } { global board_info if ![board_info $dest exists rsh_prog] { if { [which remsh] != 0 } { set RSH remsh } else { set RSH rsh } } else { set RSH [board_info $dest rsh_prog]; } if ![board_info $dest exists username] { set rsh_useropts "" } else { set rsh_useropts "-l $username" } if [board_info $dest exists hostname] { set remote [board_info $dest hostname]; } else { set remote $dest; } spawn $RSH $rsh_useropts $remote $commandline; set board_info($dest,fileid) $spawn_id; return $spawn_id; } # # Run PROG on DEST, with optional arguments, input and output files. # It returns a list of two items. The first is ether "pass" if the program # loaded, ran and exited with a zero exit status, or "fail" otherwise. # The second argument is any output produced by the program while it was # running. # proc remote_load { dest prog args } { global tool set dname [board_info $dest name]; set cache "[getenv REMOTELOAD_CACHE]/$tool/$dname/[file tail $prog]"; set empty [is_remote $dest]; if { [board_info $dest exists is_simulator] || [getenv REMOTELOAD_CACHE] == "" } { set empty 0; } else { for { set x 0; } {$x < [llength $args] } {incr x} { if { [lindex $args $x] != "" } { set empty 0; break; } } } if $empty { global sum_program; if [info exists sum_program] { if ![target_info exists objcopy] { set_currtarget_info objcopy [find_binutils_prog objcopy]; } if [is_remote host] { set dprog [remote_download host $prog "a.out"]; } else { set dprog $prog; } set status [remote_exec host "[target_info objcopy]" "-O srec $dprog ${dprog}.sum"]; if [is_remote host] { remote_file upload ${dprog}.sum ${prog}.sum; } if { [lindex $status 0] == 0 } { set sumout [remote_exec build "$sum_program" "${prog}.sum"]; set sum [lindex $sumout 1]; regsub "\[\r\n \t\]+$" "$sum" "" sum; } else { set sumout [remote_exec build "$sum_program" "${prog}"]; set sum [lindex $sumout 1]; regsub "\[\r\n \t\]+$" "$sum" "" sum; } remote_file build delete ${prog}.sum; } if [file exists $cache] { set same 0; if [info exists sum_program] { set id [open $cache "r"]; set oldsum [read $id]; close $id; if { $oldsum == $sum } { set same 1; } } else { if { [remote_file build cmp $prog $cache] == 0 } { set same 1; } } if { $same } { set fd [open "${cache}.res" "r"]; gets $fd l1; set result [list $l1 [read $fd]]; close $fd; } } } if ![info exists result] { set result [eval call_remote \"\" load \"$dname\" \"$prog\" $args]; # Not quite happy about the "pass" condition, but it makes sense if # you think about it for a while-- *why* did the test not pass? if { $empty && [lindex $result 0] == "pass" } { if { [getenv LOAD_REMOTECACHE] != "" } { set dir "[getenv REMOTELOAD_CACHE]/$tool/$dname" if ![file exists $dir] { file mkdir $dir } if [file exists $dir] { if [info exists sum_program] { set id [open $cache "w"]; puts -nonewline $id "$sum"; close $id; } else { remote_exec build cp "$prog $cache"; } set id [open "${cache}.res" "w"]; puts $id [lindex $result 0]; puts -nonewline $id [lindex $result 1]; close $id; } } } } return $result; } proc remote_raw_load { dest prog args } { return [eval call_remote raw load \"$dest\" \"$prog\" $args ]; } # # The default load procedure if no other exists for $dest. It uses # remote_download and remote_exec to load and execute the program. # proc standard_load { dest prog args } { if { [llength $args] > 0 } { set pargs [lindex $args 0]; } else { set pargs "" } if { [llength $args] > 1 } { set inp "[lindex $args 1]"; } else { set inp "" } if ![file exists $prog] then { # We call both here because this should never happen. perror "$prog does not exist in standard_load." verbose -log "$prog does not exist." 3 return "untested" } if [is_remote $dest] { set remotefile "/tmp/[file tail $prog].[pid]" set remotefile [remote_download $dest $prog $remotefile]; if { $remotefile == "" } { verbose -log "Download of $prog to [board_info $dest name] failed." 3 return "unresolved" } if [board_info $dest exists remote_link] { if [[board_info $dest remote_link] $remotefile] { verbose -log "Couldn't do remote link" remote_file target delete $remotefile return "unresolved" } } set status [remote_exec $dest $remotefile $pargs $inp]; remote_file $dest delete $remotefile; } else { set status [remote_exec $dest $prog $pargs $inp]; } if { [lindex $status 0] < 0 } { verbose -log "Couldn't execute $prog, [lindex $status 1]" 3 return "unresolved" } set output [lindex $status 1] set status [lindex $status 0] verbose -log "Executed $prog, status $status" 2 if ![string match "" $output] { verbose -log -- "$output" 2 } if { $status == 0 } { return [list "pass" $output]; } else { return [list "fail" $output]; } } # # Loads PROG into DEST. # proc remote_ld { dest prog } { return [eval call_remote \"\" ld \"$dest\" \"$prog\"]; } proc remote_raw_ld { dest prog } { return [eval call_remote raw ld \"$dest\" \"$prog\"]; } # Wait up to TIMEOUT seconds for the last spawned command on DEST to # complete. A list of two values is returned; the first is the exit # status (-1 if the program timed out), and the second is any output # produced by the command. proc remote_wait { dest timeout } { return [eval call_remote \"\" wait \"$dest\" $timeout]; } proc remote_raw_wait { dest timeout } { return [eval call_remote raw wait \"$dest\" $timeout]; } # The standard wait procedure, used for commands spawned on the local # machine. proc standard_wait { dest timeout } { set output ""; set status -1; if [info exists exp_close_result] { unset exp_close_result; } remote_expect $dest $timeout { -re ".+" { append output $expect_out(buffer); if { [string length $output] > 512000 } { remote_close $dest; set status 1; } else { exp_continue -continue_timer; } } timeout { warning "program timed out."; } eof { if [board_info $dest exists fileid_origid] { global board_info; set id [board_info $dest fileid]; set oid [board_info $dest fileid_origid]; verbose "$id $oid" unset board_info($dest,fileid); unset board_info($dest,fileid_origid); catch "close -i $id"; # I don't believe this. You HAVE to do a wait, even tho # it won't work! stupid ()*$%*)(% expect... catch "wait -i $id"; set r2 [catch "close $oid" res]; if { $r2 != 0 } { verbose "close result is $res"; set status 1; } else { set status 0; } } else { set s [wait -i [board_info $dest fileid]]; if { [lindex $s 0] != 0 && [lindex $s 2] == 0 } { set status [lindex $s 3]; if { [llength $s] > 4 } { if { [lindex $s 4] == "CHILDKILLED" } { set status 1; } } } } } } remote_close $dest; return [list $status $output]; } # This checks the value cotained in the variable named "variable" in # the calling procedure for output from the status wrapper and returns # a non-negative value if it exists; otherwise, it returns -1. The # output from the wrapper is removed from the variable. proc check_for_board_status { variable } { upvar $variable output; # If all programs of this board have a wrapper that always outputs a # status message, then the absence of it means that the program # crashed, regardless of status found elsewhere (e.g. simulator exit # code). if { [target_info needs_status_wrapper] != "" } then { set nomatch_return 2 } else { set nomatch_return -1 } if [regexp "(^|\[\r\n\])\\*\\*\\* EXIT code" $output] { regsub "^.*\\*\\*\\* EXIT code " $output "" result; regsub "\[\r\n\].*$" $result "" result; regsub -all "(^|\[\r\n\]|\r\n)\\*\\*\\* EXIT code \[^\r\n\]*(\[\r\n\]\[\r\n\]?|$)" $output "" output; regsub "^\[^0-9\]*" $result "" result regsub "\[^0-9\]*$" $result "" result verbose "got board status $result" 3 verbose "output is $output" 3 if { $result == "" } { return $nomatch_return } else { return [expr $result] } } else { return $nomatch_return; } } # # remote_expect works basically the same as standard expect, but it # also takes care of getting the file descriptor from the specified # host and also calling the timeout/eof/default section if there is an # error on the expect call. # proc remote_expect { board timeout args } { global errorInfo errorCode; global remote_suppress_flag; set spawn_id [board_info $board fileid]; if { [llength $args] == 1 } { set args "[lindex $args 0]"; } set res {} set got_re 0; set need_append 1; set orig "$args"; set error_sect ""; set save_next 0; if { $spawn_id == "" } { # This should be an invalid spawn id. set spawn_id 1000; } for { set i 0; } { $i < [llength $args] } { incr i ; } { if { $need_append } { append res "\n-i $spawn_id "; set need_append 0; } set x "[lrange $args $i $i]"; regsub "^\n*\[ \]*" "$x" "" x; if { $x == "-i" || $x == "-timeout" || $x == "-ex" } { append res "$x "; set next [expr ${i}+1]; append res "[lrange $args $next $next]"; incr i; continue; } if { $x == "-n" || $x == "-notransfer" || $x == "-nocase" || $x == "-indices" } { append res "${x} "; continue; } if { $x == "-re" } { append res "${x} "; set next [expr ${i}+1]; set y [lrange $args $next $next]; append res "${y} "; set got_re 1; incr i; continue; } if { $got_re } { set need_append 0; append res "$x "; set got_re 0; if { $save_next } { set save_next 0; set error_sect [lindex $args $i]; } } else { if { ${x} == "eof" } { set save_next 1; } elseif { ${x} == "default" || ${x} == "timeout" } { if { $error_sect == "" } { set save_next 1; } } append res "${x} "; set got_re 1; } } if [info exists remote_suppress_flag] { if { $remote_suppress_flag } { set code 1; } } if ![info exists code] { set res "\n-timeout $timeout $res"; set body "expect \{\n-i $spawn_id -timeout $timeout $orig\}"; set code [catch {uplevel $body} string]; } if {$code == 1} { if { $error_sect != "" } { set code [catch {uplevel $error_sect} string]; } else { warning "remote_expect statement without a default case?!"; return; } } if {$code == 1} { return -code error -errorinfo $errorInfo -errorcode $errorCode $string } elseif {$code == 2} { return -code return $string } elseif {$code == 3} { return } elseif {$code > 4} { return -code $code $string } } # Push the current connection to HOST onto a stack. proc remote_push_conn { host } { global board_info; set name [board_info $host name]; if { $name == "" } { return "fail"; } if ![board_info $host exists fileid] { return "fail"; } set fileid [board_info $host fileid]; set conninfo [board_info $host conninfo]; if ![info exists board_info($name,fileid_stack)] { set board_info($name,fileid_stack) {} } set board_info($name,fileid_stack) [list $fileid $conninfo $board_info($name,fileid_stack)]; unset board_info($name,fileid); if [info exists board_info($name,conninfo)] { unset board_info($name,conninfo); } return "pass"; } # Pop a previously-pushed connection from a stack. You should have closed the # current connection before doing this. proc remote_pop_conn { host } { global board_info; set name [board_info $host name]; if { $name == "" } { return "fail"; } if ![info exists board_info($name,fileid_stack)] { return "fail"; } set stack $board_info($name,fileid_stack); if { [llength $stack] < 3 } { return "fail"; } set board_info($name,fileid) [lindex $stack 0]; set board_info($name,conninfo) [lindex $stack 1]; set board_info($name,fileid_stack) [lindex $stack 2]; return "pass"; } # # Swap the current connection with the topmost one on the stack. # proc remote_swap_conn { host } { global board_info; set name [board_info $host name]; if ![info exists board_info($name,fileid)] { return "fail"; } set fileid $board_info($name,fileid); if [info exists board_info($name,conninfo)] { set conninfo $board_info($name,conninfo); } else { set conninfo {} } if { [remote_pop_conn $host] != "pass" } { set board_info($name,fileid) $fileid; set board_info($name,conninfo) $conninfo; return "fail"; } set newfileid $board_info($name,fileid); set newconninfo $board_info($name,conninfo); set board_info($name,fileid) $fileid; set board_info($name,conninfo) $conninfo; remote_push_conn $host; set board_info($name,fileid) $newfileid; set board_info($name,conninfo) $newconninfo; return "pass"; } set sum_program "testcsum";