Index: modules/ftp/ftp.tcl ================================================================== --- modules/ftp/ftp.tcl +++ modules/ftp/ftp.tcl @@ -116,10 +116,13 @@ # Arguments: # - # proc ::ftp::Timeout {s} { upvar ::ftp::ftp$s ftp + variable VERBOSE + + if {$VERBOSE} { DisplayMsg $s Waiting|Timeout! } after cancel $ftp(Wait) set ftp(state.control) 1 DisplayMsg "" "Timeout of control connection after $ftp(Timeout) sec.!" error @@ -142,27 +145,33 @@ # - # proc ::ftp::WaitOrTimeout {s} { upvar ::ftp::ftp$s ftp + variable VERBOSE set retvar 1 if { ![string length $ftp(Command)] && [info exists ftp(state.control)] } { + + if {$VERBOSE} { DisplayMsg $s Waiting|$ftp(Timeout)|\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# } set ftp(Wait) [after [expr {$ftp(Timeout) * 1000}] [list [namespace current]::Timeout $s]] vwait ::ftp::ftp${s}(state.control) set retvar $ftp(state.control) + + if {$VERBOSE} { DisplayMsg $s Waiting|Done|\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# } } if {$ftp(Error) != ""} { set errmsg $ftp(Error) set ftp(Error) "" DisplayMsg $s $errmsg error } + if {$VERBOSE} { DisplayMsg $s Waiting|OK|$retvar } return $retvar } ############################################################################# # @@ -233,10 +242,12 @@ proc ::ftp::StateHandler {s {sock ""}} { upvar ::ftp::ftp$s ftp variable DEBUG variable VERBOSE + if {$VERBOSE} { DisplayMsg $s StateHandler/$s/$sock/================================================ } + # disable fileevent on control socket, enable it at the and of the state machine # fileevent $ftp(CtrlSock) readable {} # there is no socket (and no channel to get) if called from a procedure @@ -306,32 +317,38 @@ } CloseDataConn $s WaitComplete $s 0 Command $ftp(Command) terminated catch {unset ftp(State)} + + if {$VERBOSE} { DisplayMsg $s EOF/Control } return } else { # Fix SF bug #466746: Incomplete line, do nothing. + if {$VERBOSE} { DisplayMsg $s Incomplete/Line } return } } if { $DEBUG } { - DisplayMsg $s "-> rc=\"$rc\"\n-> msgtext=\"$msgtext\"\n-> state=\"$ftp(State)\"" + DisplayMsg $s "-> rc=\"$rc\" -> msgtext=\"$msgtext\" -> state=\"$ftp(State)\"" } # In asynchronous mode, should we move on to the next state? set nextState 0 # system status replay if { [string equal $rc "211"] } { + if {$VERBOSE} { DisplayMsg $s Ignore/211 } return } # use only the first digit regexp -- "^\[0-9\]?" $rc rc - + + if {$VERBOSE} { DisplayMsg $s StateBegin////////($ftp(State)) } + switch -exact -- $ftp(State) { user { switch -exact -- $rc { 2 { PutsCtrlSock $s "USER $ftp(User)" @@ -536,10 +553,14 @@ } list_close { switch -exact -- $rc { 1 {} 2 { + # Sync control sequencer to active data connection + # before stepping out + WaitDataConn $s + set nextState 1 if {[info exists ftp(NextState)] && ![llength $ftp(NextState)]} { Command $ftp(Command) list [ListPostProcess $ftp(List)] } else { set complete_with 1 @@ -782,13 +803,18 @@ } put_close { switch -exact -- $rc { 1 { # Keep going + if {$VERBOSE} { DisplayMsg $s put_close/1--continue } return } 2 { + # Sync control sequencer to active data connection + # before stepping out + WaitDataConn $s + set complete_with 1 set nextState 1 Command $ftp(Command) put $ftp(RemoteFilename) } default { @@ -854,10 +880,14 @@ } } append_close { switch -exact -- $rc { 2 { + # Sync control sequencer to active data connection + # before stepping out + WaitDataConn $s + set complete_with 1 set nextState 1 Command $ftp(Command) append $ftp(RemoteFilename) } default { @@ -940,10 +970,14 @@ } } reget_close { switch -exact -- $rc { 2 { + # Sync control sequencer to active data connection + # before stepping out + WaitDataConn $s + set complete_with 1 set nextState 1 Command $ftp(Command) get $ftp(RemoteFilename):$ftp(From):$ftp(To) unset ftp(From) ftp(To) } @@ -1010,10 +1044,14 @@ } } get_close { switch -exact -- $rc { 2 { + # Sync control sequencer to active data connection + # before stepping out + WaitDataConn $s + set complete_with 1 set nextState 1 if {$ftp(inline)} { upvar #0 $ftp(get:varname) returnData set returnData $ftp(GetData) @@ -1032,13 +1070,19 @@ default { error "Unknown state \"$ftp(State)\"" } } + if {$VERBOSE} { DisplayMsg $s ////////StateDone } + # finish waiting if { [info exists complete_with] } { + if {$VERBOSE} { DisplayMsg $s WaitBegin////////($complete_with) } + WaitComplete $s $complete_with + + if {$VERBOSE} { DisplayMsg $s ////////WaitDone } } # display control channel message if { [info exists buffer] } { if { $VERBOSE } { @@ -1059,16 +1103,20 @@ # If operating asynchronously, commence next state if {$nextState && [info exists ftp(NextState)] && [llength $ftp(NextState)]} { # Pop the head of the NextState queue set ftp(State) [lindex $ftp(NextState) 0] set ftp(NextState) [lreplace $ftp(NextState) 0 0] + + if {$VERBOSE} { DisplayMsg $s Recurse/StateHandler } StateHandler $s } # enable fileevent on control socket again #fileevent $ftp(CtrlSock) readable [list ::ftp::StateHandler $ftp(CtrlSock)] + if {$VERBOSE} { DisplayMsg $s ======/HandlerDone } + return } ############################################################################# # # Type -- @@ -1135,11 +1183,14 @@ # # Returns: # sorted list of files or {} if listing fails proc ::ftp::NList {s { dir ""}} { + variable VERBOSE upvar ::ftp::ftp$s ftp + + if {$VERBOSE} { DisplayMsg $s NList($s)($dir)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } if { ![info exists ftp(State)] } { if { ![string is digit -strict $s] } { DisplayMsg $s "Bad connection name \"$s\"" error } else { @@ -1156,10 +1207,12 @@ } # save current type and force ascii mode set old_type $ftp(Type) if { $ftp(Type) != "ascii" } { + if {$VERBOSE} { DisplayMsg $s NList/ForceAscii } + if {[string length $ftp(Command)]} { set ftp(NextState) [list nlist_$ftp(Mode) type_change list_last] set ftp(type:changeto) $old_type Type $s ascii return {} @@ -1166,27 +1219,38 @@ } Type $s ascii } set ftp(State) nlist_$ftp(Mode) + + if {$VERBOSE} { DisplayMsg $s NList/Process~~~~~~~~~~~~~~~~~~~ } StateHandler $s + + if {$VERBOSE} { DisplayMsg $s NList/Processed~~~~~~~~~~~~~~~~~ } # wait for synchronization set rc [WaitOrTimeout $s] # restore old type + if {$VERBOSE} { DisplayMsg $s NList/RestoreType~~~~~~~~~~~~~~~~~~~~~ } if { [Type $s] != $old_type } { Type $s $old_type } unset ftp(Dir) if { $rc } { + if {$VERBOSE} { DisplayMsg $s NList/ReturnData~~~~~~~~~~~~~~~~~~~~~~~ } + return [lsort [split [string trim $ftp(List) \n] \n]] } else { + if {$VERBOSE} { DisplayMsg $s NList/CDC~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } + CloseDataConn $s return {} } + + if {$VERBOSE} { DisplayMsg $s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~NList/Done } } ############################################################################# # # List -- @@ -2755,10 +2819,11 @@ if {[info exists ftp(get:channel)]} { catch {unset ftp(get:channel)} catch {unset ftp(DestCI)} } + catch { unset ftp(AC) } catch {after cancel $ftp(Wait)} catch {fileevent $ftp(DataSock) readable {}} catch {close $ftp(DataSock); unset ftp(DataSock)} catch {close $ftp(DestCI); unset ftp(DestCI)} catch {close $ftp(SourceCI); unset ftp(SourceCI)} @@ -2781,10 +2846,14 @@ # port - the client's port number proc ::ftp::InitDataConn {s sock addr port} { upvar ::ftp::ftp$s ftp variable VERBOSE + + if { $VERBOSE } { + DisplayMsg $s "D: New Connection from $addr:$port" data + } # If the new channel is accepted, the dummy channel will be closed catch {close $ftp(DummySock); unset ftp(DummySock)} @@ -2837,12 +2906,61 @@ error "Unknown state \"$ftp(State)\"" } } if { $VERBOSE } { - DisplayMsg $s "D: Connection from $addr:$port" data + DisplayMsg $s "D: ... Connection from $addr:$port ... initialized" data + } + + # Marker for WaitDataConn + set ftp(AC) 1 + return +} + +############################################################################# +# +# WaitDataConn -- +# Arguments: The ftp connection handle +# Returns: None +# +# Synchronizes the control sequencer to the data connection (active +# mode). This must be placed at the end of all state sequences, +# i.e. the last state of each sequence, dealing with a data +# connection. Without the sync the control sequencer may step to the +# next command causing a very late-coming data connection to encounter +# an unknown state, and failing to establish what to do. +# +# Sync is achieved through the state field AC, in cooperation with the +# procedures OpenActiveConn and InitDataConn. +# +# Missing field => Not an active connection - Ignore +# AC == 0 => OAC has run, IDC not - Wait for IDC, then cleanup +# AC == 1 => OAC has run, IDC as well - No waiting, just cleanup. + +proc ::ftp::WaitDataConn {s} { + variable VERBOSE + upvar ::ftp::ftp$s ftp + + if {$VERBOSE} { DisplayMsg $s WDC|$s|Begin|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } + + # Passive connection, nothing to do + if {![info exists ftp(AC)]} { + if {$VERBOSE} { DisplayMsg $s WDC|$s|Passive|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } + return } + + # InitDataConn has not run yet. Wait! + if {!$ftp(AC)} { + if {$VERBOSE} { DisplayMsg $s WDC|$s|Sync|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } + vwait ::ftp::ftp${s}(AC) + # assert ftp(AC) == 1 + if {$VERBOSE} { DisplayMsg $s WDC|$s|Synced|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } + } ; # else: Was run already + + if {$VERBOSE} { DisplayMsg $s WDC|$s|Cleanup|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } + # InitDataConn has run, clean up and continue + unset ftp(AC) return } ############################################################################# # @@ -2883,10 +3001,12 @@ if { $VERBOSE } { DisplayMsg $s "D: Port is $p" data } set ftp(DataPort) "[expr {$p / 256}],[expr {$p % 256}]" + # Marker for WaitDataConn + set ftp(AC) 0 return 1 } ############################################################################# #