From b67e4abb100f67ca05809baab37700eb5ee0a342 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Fri, 10 Sep 1999 13:57:31 +0000 Subject: manual/7767 --- Completion/Builtins/_zftp | 41 ++- Doc/Zsh/mod_zftp.yo | 118 ++++--- Doc/Zsh/zftpsys.yo | 123 +++++-- Functions/Zftp/zfanon | 29 +- Functions/Zftp/zfautocheck | 12 +- Functions/Zftp/zfcd | 12 +- Functions/Zftp/zfcd_match | 12 +- Functions/Zftp/zfcget | 30 +- Functions/Zftp/zfdir | 30 +- Functions/Zftp/zffcache | 24 ++ Functions/Zftp/zfgcp | 33 +- Functions/Zftp/zfget | 34 +- Functions/Zftp/zfget_match | 33 +- Functions/Zftp/zfgoto | 31 +- Functions/Zftp/zfinit | 22 +- Functions/Zftp/zfmark | 5 +- Functions/Zftp/zfopen | 30 +- Functions/Zftp/zfparams | 22 +- Functions/Zftp/zfpcp | 5 +- Functions/Zftp/zfrglob | 10 +- Functions/Zftp/zfsession | 71 ++++ Functions/Zftp/zfstat | 29 +- Functions/Zftp/zftp_chpwd | 26 +- Functions/Zftp/zftp_progress | 12 +- Functions/Zftp/zftransfer | 62 ++++ Functions/Zftp/zfuget | 50 +-- Functions/Zftp/zfuput | 42 +-- Src/Modules/zftp.c | 773 ++++++++++++++++++++++++++++++------------- 28 files changed, 1116 insertions(+), 605 deletions(-) create mode 100644 Functions/Zftp/zffcache create mode 100644 Functions/Zftp/zfsession create mode 100644 Functions/Zftp/zftransfer diff --git a/Completion/Builtins/_zftp b/Completion/Builtins/_zftp index 54cadc890..7aa1d94e8 100644 --- a/Completion/Builtins/_zftp +++ b/Completion/Builtins/_zftp @@ -1,19 +1,22 @@ #compdef -p zf* -# Don't try any more completion after this. -_compskip=all - # Completion for zftp builtin and zf* functions. The functions -# zfcd_match and zfget_match (used for old-style completion) +# zfcd_match and zfget_match (also used for old-style completion) # need to be installed for remote file and directory completion to work. +emulate -L zsh + +# Don't try any more completion after this. +_compskip=all + local subcom expl if [[ $words[1] = zftp ]]; then if [[ $CURRENT -eq 2 ]]; then _description expl sub-command compadd "$expl[@]" open params user login type ascii binary mode put \ - putat get getat append appendat ls dir local remote mkdir rmdir + putat get getat append appendat ls dir local remote mkdir rmdir \ + session rmsession return fi subcom=$words[2] @@ -23,17 +26,13 @@ fi case $subcom in *(cd|ls|dir)) - # complete remote directories; we could be smarter about hiding prefixes + # complete remote directories zfcd_match $PREFIX $SUFFIX - _description expl 'remote directory' - (( $#reply )) && compadd "$expl[@]" -S/ -q - $reply ;; *(get(|at)|gcp|delete|remote)) # complete remote files zfget_match $PREFIX $SUFFIX - _description expl 'remote file' - (( $#reply )) && compadd "$expl[@]" -F fignore - $reply ;; *(put(|at)|pcp)) @@ -60,6 +59,28 @@ case $subcom in fi ;; + *session) + # complete sessions, excluding the current one. + _description expl 'another FTP session' + compadd "$expl[@]" - ${$(zftp session):#$ZFTP_SESSION} + ;; + + *transfer) + # complete arguments like sess1:file1 sess2:file2 + if [[ $PREFIX = *:* ]]; then + # complete file in the given session + local sess=${PREFIX%%:*} oldsess=$ZFTP_SESSION + compset -p $(( $#sess + 1 )) + [[ -n $sess ]] && zftp session $sess + zfget_match $PREFIX $SUFFIX + [[ -n $sess && -n $oldsess ]] && zftp session $oldsess + else + # note here we can complete the current session + _description expl 'FTP session' + compadd "$expl[@]" -S : - $(zftp session) + fi + ;; + *) # dunno... try ordinary completion after all. _compskip='' diff --git a/Doc/Zsh/mod_zftp.yo b/Doc/Zsh/mod_zftp.yo index f5d165083..e5dc8ec2f 100644 --- a/Doc/Zsh/mod_zftp.yo +++ b/Doc/Zsh/mod_zftp.yo @@ -63,13 +63,20 @@ xitem(tt(params) [ var(host) [ var(user) [ var(password) \ item(tt(params) tt(-))( Store the given parameters for a later tt(open) command with no arguments. Only those given on the command line will be remembered. -Any of the parameters may, however, be specified as a `tt(?)', which -may need to be quoted to protect it from shell expansion: in this case, -the appropriate parameter will be read from stdin as with the -tt(login) subcommand, including special handling of var(password). - If no arguments are given, the parameters currently set are printed, -although the password will appear as a line of stars. +although the password will appear as a line of stars; the return value is +one if no parameters were set, zero otherwise. + +Any of the parameters may be specified as a `tt(?)', which +may need to be quoted to protect it from shell expansion. In this case, +the appropriate parameter will be read from stdin as with the +tt(login) subcommand, including special handling of var(password). If the +`tt(?)' is followed by a string, that is used as the prompt for reading the +parameter instead of the default message (any necessary punctuation and +whitespace should be included at the end of the prompt). The first letter +of the parameter (only) may be quoted with a `tt(\)'; hence an argument +tt("\\$word") guarantees that the string from the shell parameter tt($word) +will be treated literally, whether or not it begins with a `tt(?)'. If instead a single `tt(-)' is given, the existing parameters, if any, are deleted. In that case, calling tt(open) with no arguments will @@ -80,20 +87,10 @@ will be deleted if the tt(zftp) module is unloaded. For example, -example(zftp params ftp.elsewhere.xx juser '?') +example(zftp params ftp.elsewhere.xx juser '?Password for juser: ') will store the host tt(ftp.elsewhere.xx) and the user tt(juser) and -then prompt the user for the corresponding password. - -This command may also be used to set up a transfer which then takes -place completely in the background, freeing tt(zftp) for concurrent -foreground use. For example, - -example(zftp params ftp.soreeyes.ca bubble squeak -(zftp open; zftp get foo >bar; zftp close) &) - ---- here, the connection is restricted to a background subshell and -you are free to open a simultaneous connection in the foreground. +then prompt the user for the corresponding password with the given prompt. ) item(tt(test))( Test the connection; if the server has reported @@ -105,16 +102,11 @@ mechanism, or error messages if the connection closes. There is no network overhead for this test. The test is only supported on systems with either the tt(select(2)) or -tt(poll(2)) system calls; otherwise the message tt(not -supported on this system) is printed instead. +tt(poll(2)) system calls; otherwise the message `tt(not +supported on this system)' is printed instead. -It is useful to put the code - -example([[ -n $ZFTP_HOST ]] && zftp test) - -into the shell function tt(precmd) for testing the connection before -every prompt. However, tt(zftp) will call tt(test) at the start of any -other subcommand when a connection is open. +The tt(test) subcommand will automatically be called at the start of any +other subcommand for the current session when a connection is open. ) item(tt(cd) var(directory))( Change the remote directory to var(directory). Also alters the shell @@ -138,7 +130,7 @@ Otherwise, up to vagaries of the server implementation, behaves similar to tt(dir). ) item(tt(type) [ var(type) ])( -Change the type for transfer to var(type), or print the current type +Change the type for the transfer to var(type), or print the current type if var(type) is absent. The allowed values are `tt(A)' (ASCII), `tt(I)' (Image, i.e. binary), or `tt(B)' (a synonym for `tt(I)'). @@ -230,7 +222,32 @@ xitem(tt(close)) item(tt(quit))( Close the current data connection. This unsets the shell parameters tt(ZFTP_HOST), tt(ZFTP_IP), tt(ZFTP_SYSTEM), tt(ZFTP_USER), -tt(ZFTP_ACCOUNT) and tt(ZFTP_PWD). +tt(ZFTP_ACCOUNT), tt(ZFTP_PWD), tt(ZFTP_TYPE) and tt(ZFTP_MODE). +) +item(tt(session) [ var(sessname) ])( +Allows multiple FTP sessions to be used at once. The name of the session +is an arbitrary string of characters; the default session is called +`tt(default)'. If this command is called without an argument, it will list +all the current sessions; with an argument, it will either switch to the +existing session called var(sessname), or create a new session of that name. + +Each session remembers the status of the connection, the set of +connection-specific shell parameters (the same set as are unset when a +connection closes, as given in the description of tt(close)), and any user +parameters specified with the tt(params) subcommand. Changing to a +previous session restores those values; changing to a new session +initialises them in the same way as if tt(zftp) had just been loaded. The +name of the current session is given by the parameter tt(ZFTP_SESSION). +) +item(tt(rmsession) [ var(sessname) ])( +Delete a session; if a name is not given, the current session is deleted. +If the current session is deleted, the earliest existing session becomes +the new current session, otherwise the current session is not changed. +If the session being deleted is the only one, a new session called +`tt(default)' is created and becomes the current session; note that this is +a new session even if the session being deleted is also called +`tt(default)'. It is recommended that sessions not be deleted while +background commands which use tt(zftp) are still active. ) enditem() @@ -276,8 +293,8 @@ vindex(ZFTP_USER) item(tt(ZFTP_USER))( Readonly. The username currently logged in, if any. ) -vindex(ZFTP_ACCT) -item(tt(ZFTP_ACCT))( +vindex(ZFTP_ACCOUNT) +item(tt(ZFTP_ACCOUNT))( Readonly. The account name of the current user, if any. Most servers do not require an account name. ) @@ -288,12 +305,19 @@ Readonly. The current directory on the server. vindex(ZFTP_CODE) item(tt(ZFTP_CODE))( Readonly. The three digit code of the last FTP reply from the server -as a string. This can still be read after the connection is closed. +as a string. This can still be read after the connection is closed, and +is not changed when the current session changes. ) vindex(ZFTP_REPLY) item(tt(ZFTP_REPLY))( Readonly. The last line of the last reply sent by the server. This -can still be read after the connection is closed. +can still be read after the connection is closed, and is not changed when +the current session changes. +) +vindex(ZFTP_SESSION) +item(tt(ZFTP_SESSION))( +Readonly. The name of the current FTP session; see the description of the +tt(session) subcommand. ) vindex(ZFTP_PREFS) item(tt(ZFTP_PREFS))( @@ -430,21 +454,25 @@ Sometimes the progress meter may cause disruption. It is up to the user to decide whether the function should be defined and to use tt(unfunction) when necessary. ) +enditem() subsect(Problems) cindex(zftp, problems) -With the exception noted for the tt(params) subcommand, a connection -may not be opened in the left hand side of a pipe as this occurs in a -subshell and the file information is not updated in the main shell. -In the case of type or mode changes or closing the connection in a -subshell, the information is returned but variables are not updated -until the next call to tt(zftp). Other status changes in subshells -will not be reflected by changes to the variables (but should -be otherwise harmless). +A connection may not be opened in the left hand side of a pipe as this +occurs in a subshell and the file information is not updated in the main +shell. In the case of type or mode changes or closing the connection in a +subshell, the information is returned but variables are not updated until +the next call to tt(zftp). Other status changes in subshells will not be +reflected by changes to the variables (but should be otherwise harmless). -On some operating systems, the control connection is not valid after a -fork(), so that operations in subshells or on the left hand side of a -pipeline are not possible. +Deleting sessions while a tt(zftp) command is active in the background can +have unexpected effects, even if it does not use the session being deleted. +This is because all shell subprocesses share information on the state of +all connections, and deleting a session changes the ordering of that +information. -enditem() +On some operating systems, the control connection is not valid after a +fork(), so that operations in subshells, on the left hand side of a +pipeline, or in the background are not possible, as they should be. This +is presumably a bug in the operating system. diff --git a/Doc/Zsh/zftpsys.yo b/Doc/Zsh/zftpsys.yo index 3c8f90cdd..89d39b985 100644 --- a/Doc/Zsh/zftpsys.yo +++ b/Doc/Zsh/zftpsys.yo @@ -54,7 +54,7 @@ following code in your tt(.zshrc) will arrange for this; assume the functions are stored in the directory tt(~/myfns): example(fpath=(~/myfns $fpath) -autoload zfinit +autoload -U zfinit zfinit) Note that tt(zfinit) assumes you are using the tt(zmodload) method to @@ -67,7 +67,10 @@ texinode(Zftp Functions)(Miscellaneous Features)(Installation)(Zftp Function Sys sect(Functions) The sequence of operations in performing a file transfer is essentially the -same as that in a standard FTP client. +same as that in a standard FTP client. Note that, due to a quirk of the +shell's tt(getopts) builtin, for those functions that handle options you +must use `tt(-)tt(-)' rather than `tt(-)' to ensure the remaining arguments +are treated literally (a single `tt(-)' is treated as an argument). subsect(Opening a connection) startitem() @@ -77,16 +80,20 @@ Set or show the parameters for a future tt(zfopen) with no arguments. If no arguments are given, the current parameters are displayed (the password will be shown as a line of asterisks). If a host is given, and either the var(user) or var(password) is not, they will be prompted for; also, any -parameter given as `tt(?)' will be prompted for. - -As tt(zfopen) calls tt(zfparams) to store the parameters, this usually need -not be called directly. +parameter given as `tt(?)' will be prompted for, and if the `tt(?)' is +followed by a string, that will be used as the prompt. As tt(zfopen) calls +tt(zfparams) to store the parameters, this usually need not be called +directly. + +A single argument `tt(-)' will delete the stored parameters. This will +also cause the memory of the last directory (and so on) on the other host +to be deleted. ) findex(zfopen) item(tt(zfopen [ -1 ] [ var(host) [ var(user) [ var(password) [ var(account) ] ] ] ]))( If var(host) is present, open a connection to that host under username var(user) with password var(password) (and, on the rare occasions when it -is necessary account var(account)). If a necessary parameter is missing or +is necessary, account var(account)). If a necessary parameter is missing or given as `tt(?)' it will be prompted for. If var(host) is not present, use a previously stored set of parameters. @@ -106,12 +113,12 @@ var(host), then change directory to var(path) (which must be a directory, not a file). The `tt(ftp://)' can be omitted; the trailing `tt(/)' is enough to trigger recognition of the var(path). Note prefixes other than `tt(ftp:)' are not recognized, and that all characters after the first -slash beyond tt(host) are significant in var(path). +slash beyond var(host) are significant in var(path). ) findex(zfanon) item(tt(zfanon [ -1 ] var(host)))( Open a connection var(host) for anonymous FTP. The username used is -tt(anonymous). The password (which will be reported the first time) is +`tt(anonymous)'. The password (which will be reported the first time) is generated as var(user)tt(@)var(host); this is then stored in the shell parameter tt($EMAIL_ADDR) which can alternatively be set manually to a suitable string. @@ -143,12 +150,12 @@ expected, the string which on the local host corresponds to `tt(~)' is converted back to a `tt(~)' before being passed to the remote machine. This is convenient because of the way expansion is performed on the command line before tt(zfcd) receives a string. For example, suppose the command -is `tt(zfcd ~/foo)'. The shell will expand this to a full path as in +is `tt(zfcd ~/foo)'. The shell will expand this to a full path such as `tt(zfcd /home/user2/pws/foo)'. At this stage, tt(zfcd) recognises the -initial path as `tt(~)', and the directory sent to the remote host is -tt(~/foo), so that the `tt(~)' will be expanded by the server to the -correct remote host directory. Other named directories of the form -`tt(~name)' are not treated in this fashion. +initial path as corresponding to `tt(~)' and will send the directory to +the remote host as tt(~/foo), so that the `tt(~)' will be expanded by the +server to the correct remote host directory. Other named directories of +the form `tt(~name)' are not treated in this fashion. ) findex(zfhere) item(tt(zfhere))( @@ -162,21 +169,23 @@ item(tt(zfdir [ -rfd ] [ - ] [ var(dir-options) ] [ var(dir) ]))( Produce a long directory listing. The arguments var(dir-options) and var(dir) are passed directly to the server and their effect is implementation dependent, but specifying a particular remote directory -var(dir) is usually possible. The output is passed through pager. +var(dir) is usually possible. The output is passed through a pager +given by the environment variable tt($PAGER) or defaulting to `tt(more)'. The directory is usually cached for re-use. In fact, two caches are maintained. One is for use when there is no var(dir-options) or var(dir), i.e. a full listing of the current remote directory; it is flushed when the current remote directory changes. The other is kept for repeated use of tt(zfdir) with the same arguments; for example, -repeated use of tt(zfdir /pub/gnu) will only require the directory to be +repeated use of `tt(zfdir /pub/gnu)' will only require the directory to be retrieved on the first call. Alternatively, this cache can be re-viewed with the tt(-r) option. As relative directories will confuse -tt(zfdir), the tt(-f) option can be used to force the cache to be flushed. -Also, the option tt(-d) will delete both caches without showing a directory -listing. +tt(zfdir), the tt(-f) option can be used to force the cache to be flushed +before the directory is listed. The option tt(-d) will delete both +caches without showing a directory listing; it will also delete the cache +of file names in the current remote directory, if any. ) -findex(zfdir) +findex(zfls) item(tt(zfls) [ var(ls-options) ] [ var(dir) ])( List files on the remote server. With no arguments, this will produce a simple list of file names for the current remote directory. Any arguments @@ -311,6 +320,49 @@ Close the connection. ) enditem() +subsect(Session management) +startitem() +findex(zfsession) +item(tt(zfsession) [ tt(-lvod) ] [ var(sessname) ])( +Allows you to manage multiple FTP sessions at once. By default, +connections take place in a session called `tt(default)'; by giving the +command `tt(zfsession) var(sessname)' you can change to a new or existing +session with a name of your choice. The new session remembers its own +connection, as well as associated shell parameters, and also the host/user +parameters set by tt(zfparams). Hence you can have different sessions set +up to connect to different hosts, each remembering the appropriate host, +user and password. + +With no arguments, tt(zfsession) prints the name of the current session; +with the option tt(-l) it lists all sessions which currently exist, and +with the option tt(-v) it gives a verbose list showing the host and +directory for each session, where the current session is marked with an +asterisk. With tt(-o), it will switch to the most recent previous session. + +With tt(-d), the given session (or else the current one) is removed; +everything to do with it is completely forgotten. If it was the only +session, a new session called `tt(default)' is created and made current. +It is safest not to delete sessions while background commands using +tt(zftp) are active. +) +findex(zftransfer) +item(tt(zftransfer) var(sess1)tt(:)var(file1) var(sess2)tt(:)var(file2))( +Transfer files between two sessions; no local copy is made. The file +is read from the session var(sess1) as var(file1) and written to session +var(sess1) as file var(file2); var(file1) and var(file2) may be relative to +the current directories of the sesssion. Either var(sess1) or var(sess2) +may be omitted (though the colon should be retained if there is a +possibility of a colon appearing in the file name) and defaults to the +current session; var(file2) may be omitted or may end with a slash, in +which case the basename of var(file1) will be added. The sessions +var(sess1) and var(sess2) must be distinct. + +The operation is performed using pipes, so it is required that the +connections still be valid in a subshell, which is not the case under some +operating systems. +) +enditem() + subsect(Bookmarks) The two functions tt(zfmark) and tt(zfgoto) allow you to `bookmark' the present location (host, user and directory) of the current FTP connection @@ -329,16 +381,18 @@ closed; it is an error if there is none. Any existing bookmark under the same name will be silently replaced. If not given an argument, list the existing bookmarks and the points to -which they refer in the form var(user)tt(@)var(host)tt(:)var(directory). +which they refer in the form var(user)tt(@)var(host)tt(:)var(directory); +this is the format in which they are stored, and the file may be edited +directly. ) findex(zfgoto) item(tt(zfgoto [ -n ] )var(bookmark))( Return to the location given by var(bookmark), as previously set by tt(zfmark). If the location has user `tt(ftp)' or `tt(anonymous)', open the connection with tt(zfanon), so that no password is required. If the -user and host parameters match those currently stored, those will be used, -and again no password is required. Otherwise a password will be prompted -for. +user and host parameters match those stored for the current session, if +any, those will be used, and again no password is required. Otherwise a +password will be prompted for. With the option tt(-n), the bookmark is taken to be a nickname stored by the tt(ncftp) program in its bookmark file, which is assumed to be @@ -434,6 +488,11 @@ error, as standard output may be a file being received. The form of the progess meter, or whether it is used at all, can be configured without altering the function, as described in the next section. ) +findex(zffcache) +item(tt(zffcache))( +This is used to implement caching of files in the current directory for +each session separately. It is used by tt(zfget_match) and tt(zfrglob). +) enditem() texinode(Miscellaneous Features)()(Zftp Functions)(Zftp Function System) @@ -446,7 +505,10 @@ pindex(zfconfig) The tt(zfinit) function defines an associative array tt(zfconfig). Elements of this may subsequently be set to change the behaviour of the tt(zftp) functions using standard syntax (for example, -`tt(zfconfig[progress]=percent)'. The following keys are understood. +`tt(zfconfig[progress]=percent)'. tt(zfconfig) may also contain +various other values used by the function system, so it should not be used +as the target of an assignment for a complete array. The following keys +are understood. startitem() item(tt(progress))( @@ -525,7 +587,8 @@ never close the connection automatically. Information about the previous connection is given by the tt(zfstat) function. So, for example, if that reports: -example(Not connected. +example(Session: default +Not connected. Last session: ftp.bar.com:/pub/textfiles) then the command tt(zfget file.txt) will attempt to reopen a connection to @@ -533,10 +596,14 @@ tt(ftp.bar.com), retrieve the file tt(/pub/textfiles/file.txt), and immediately close the connection again. On the other hand, tt(zfcd ..) will open the connection in the directory tt(/pub) and leave it open. +Note that all the above is local to each session; if you return to a +previous session, the connection for that session is the one which will be +reopened. + subsect(Completion) -Completion of local and remote files, directories and bookmarks is -supported. The older, tt(compctl)-style completion is defined when +Completion of local and remote files, directories, sessions and bookmarks +is supported. The older, tt(compctl)-style completion is defined when tt(zfinit) is called; support for the new widget-based completion system is provided in the function tt(Completion/Builtins/_zftp), which should be installed with the other functions of the completion system and hence diff --git a/Functions/Zftp/zfanon b/Functions/Zftp/zfanon index 9624d48d9..f5754dda6 100644 --- a/Functions/Zftp/zfanon +++ b/Functions/Zftp/zfanon @@ -2,31 +2,19 @@ emulate -L zsh -local opt optlist once dir +local opt opt_1 dir -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $optlist[$i] in - 1) once=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :1 opt; do + [[ $opt = "?" ]] && print "zfanon: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) if [[ -z $EMAIL_ADDR ]]; then # Exercise in futility. There's a poem by Wallace Stevens # called something like `N ways of looking at a blackbird', # where N is somewhere around 0x14 to 0x18. Now zftp is - # ashamed to prsent `N ways of looking at a hostname'. + # ashamed to present `N ways of looking at a hostname'. local domain host # First, maybe we've already got it. Zen-like. if [[ $HOST = *.* ]]; then @@ -38,7 +26,8 @@ if [[ -z $EMAIL_ADDR ]]; then [[ -n $domain ]] && host=$HOST.$domain fi # Next, maybe we've got nlsookup. May not work on LINUX. - [[ -z $host ]] && host=$(nslookup $HOST | awk '/Name:/ { print $2 }') + [[ -z $host ]] && host=$(nslookup $HOST 2>/dev/null | + awk '/Name:/ { print $2 }') if [[ -z $host ]]; then # we're running out of ideas, but this should work. # after all, i wrote it... @@ -67,7 +56,7 @@ if [[ $1 = */* ]]; then 1=${1%%/*} fi -if [[ $once = 1 ]]; then +if [[ $opt_1 = 1 ]]; then zftp open $1 anonymous $EMAIL_ADDR || return 1 else zftp params $1 anonymous $EMAIL_ADDR diff --git a/Functions/Zftp/zfautocheck b/Functions/Zftp/zfautocheck index d06beca4f..993e84ebf 100644 --- a/Functions/Zftp/zfautocheck +++ b/Functions/Zftp/zfautocheck @@ -11,9 +11,12 @@ # command, which implies we are looking for something so should stay open # for it. -# Remember the old session: zflastsession will be overwritten by +# Remember the old location: will be overwritten by # a successful open. -local lastsession=$zflastsession +local lastloc=$zfconfig[lastloc_$ZFTP_SESSION] + +# Don't print out user messages when re-opening the connection. +local ZFTP_VERBOSE=${ZFTP_VERBOSE//0} # Unset the delay counter from the progress meter in case there was an # abnormal exit. @@ -30,8 +33,9 @@ fi if [[ $1 = *n* ]]; then return 0 -elif [[ -n $lastsession && $ZFTP_HOST = ${lastsession%%:*} ]]; then - zfcd ${lastsession#*:} +elif [[ -n $lastloc && $ZFTP_HOST = ${lastloc%%:*} ]]; then + # don't print directory since we're just going back where we were. + zfcd ${lastloc#*:} >& /dev/null fi # } diff --git a/Functions/Zftp/zfcd b/Functions/Zftp/zfcd index b726d9f55..2ecc8b0f6 100644 --- a/Functions/Zftp/zfcd +++ b/Functions/Zftp/zfcd @@ -22,9 +22,9 @@ emulate -L zsh if [[ $1 = /* ]]; then - zfautocheck -dn + zfautocheck -dn || return 1 else - zfautocheck -d + zfautocheck -d || return 1 fi if [[ $1 = $HOME || $1 = $HOME/* ]]; then @@ -36,7 +36,7 @@ if (( $# == 0 )); then set -- '~' elif [[ $# -eq 1 && $1 = - ]]; then # Emulate `cd -' behaviour. - set -- $zflastdir + set -- $zfconfig[lastdir_$ZFTP_SESSION] elif [[ $# -eq 2 ]]; then # Emulate `cd old new' behaviour. # We have to find a character not in $1 or $2; ! is a good bet. @@ -47,6 +47,8 @@ fi # if we want to keep it. local lastdir=$ZFTP_PWD -zftp cd "$@" && zflastdir=$lastdir -print $zflastsession +zftp cd "$@" && [[ $lastdir != $ZFTP_PWD ]] && +zfconfig[lastdir_$ZFTP_SESSION]=$lastdir + +print $zfconfig[lastloc_$ZFTP_SESSION] # } diff --git a/Functions/Zftp/zfcd_match b/Functions/Zftp/zfcd_match index 67e719888..e9d283c97 100644 --- a/Functions/Zftp/zfcd_match +++ b/Functions/Zftp/zfcd_match @@ -15,7 +15,7 @@ local tmpf=${TMPPREFIX}zfcm$$ if [[ $ZFTP_SYSTEM = UNIX* ]]; then # hoo, aren't we lucky: this makes things so much easier - setopt localoptions rcexpandparam + setopt rcexpandparam local dir if [[ $1 = ?*/* ]]; then dir=${1%/*} @@ -25,13 +25,15 @@ if [[ $ZFTP_SYSTEM = UNIX* ]]; then # If we're using -F, we get away with using a directory # to list, but not a glob. Don't ask me why. # I hate having to rely on awk here. - zftp ls -F $dir >$tmpf + zftp ls -LF $dir >$tmpf reply=($(awk '/\/$/ { print substr($1, 0, length($1)-1) }' $tmpf)) rm -f $tmpf - if [[ $dir = / ]]; then - reply=(${dir}$reply) + [[ -n $dir && $dir != */ ]] && dir="$dir/" + if [[ -n $WIDGET ]]; then + _description expl 'remote directory' + compadd -S/ -q -P "$dir" - $reply elif [[ -n $dir ]]; then - reply=($dir/$reply) + reply=(${dir}$reply) fi else # I simply don't know what to do here. diff --git a/Functions/Zftp/zfcget b/Functions/Zftp/zfcget index fd6accfed..f95f37704 100644 --- a/Functions/Zftp/zfcget +++ b/Functions/Zftp/zfcget @@ -12,35 +12,21 @@ emulate -L zsh -local loc rem stat=0 optlist opt nglob remlist locst remst -local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time +local loc rem stat=0 opt opt_G opt_t remlist locst remst +local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $optlist[$i] in - G) nglob=1 - ;; - t) time=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :Gt opt; do + [[ $opt = '?' ]] && print "zfcget: bad option: -$OPTARG" && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) for remlist in $*; do # zfcd directory hack to put the front back to ~ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then remlist="~${remlist#$HOME}" fi - if [[ $nglob != 1 ]]; then + if [[ $opt_G != 1 ]]; then zfrglob remlist fi if (( $#remlist )); then @@ -73,7 +59,7 @@ for remlist in $*; do continue else if zftp getat $rem $locst[1] >>$loc; then - [[ $time = 1 ]] && zfrtime $loc $rem $remst[2] + [[ $opt_t = 1 ]] && zfrtime $loc $rem $remst[2] else stat=1 fi diff --git a/Functions/Zftp/zfdir b/Functions/Zftp/zfdir index 55befe000..deb5b9762 100644 --- a/Functions/Zftp/zfdir +++ b/Functions/Zftp/zfdir @@ -23,6 +23,8 @@ emulate -L zsh setopt extendedglob local file opt optlist redir i newargs force +local curdir=$zfconfig[curdir_$ZFTP_SESSION] +local otherdir=$zfconfig[otherdir_$ZFTP_SESSION] while [[ $1 = -* ]]; do if [[ $1 = - || $1 = -- ]]; then @@ -40,9 +42,9 @@ while [[ $1 = -* ]]; do ;; f) force=1 ;; - d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir - [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir - zftp_fcache=() + d) [[ -n $curdir && -f $curdir ]] && rm -f $curdir + [[ -n $otherdir && -f $otherdir ]] && rm -f $otherdir + zffcache -d return 0 ;; esac @@ -50,7 +52,7 @@ while [[ $1 = -* ]]; do shift done -zfautocheck -d +zfautocheck -d || return 1 # directory hack, see zfcd for (( i = 1; i <= $#argv; i++ )); do @@ -62,26 +64,32 @@ done if [[ $# -eq 0 ]]; then # Cache it in the current directory file. This means that repeated # calls to zfdir with no arguments always use a cached file. - [[ -z $zfcurdir ]] && zfcurdir=${TMPPREFIX}zfcurdir$$ - file=$zfcurdir + if [[ -z $curdir ]]; then + curdir=${TMPPREFIX}zfcurdir_${ZFTP_SESSION}_$$ + zfconfig[curdir_$ZFTP_SESSION]=$curdir + fi + file=$curdir else # Last directly looked at was not the current one, or at least # had non-standard arguments. - [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$ - file=$zfotherdir + if [[ -z $otherdir ]]; then + otherdir=${TMPPREFIX}zfotherdir_${ZFTP_SESSION}_$$ + zfconfig[otherdir_$ZFTP_SESSION]=$otherdir + fi + file=$otherdir newargs="$*" if [[ -f $file && $redir != 1 && $force -ne 1 ]]; then # Don't use the cached file if the arguments changed. - [[ $newargs = $zfotherargs ]] || rm -f $file + [[ $newargs = $zfconfig[otherargs_$ZFTP_SESSION] ]] || rm -f $file fi - zfotherargs=$newargs + zfconfig[otherargs_$ZFTP_SESSION]=$newargs fi if [[ $force -eq 1 ]]; then rm -f $file # if it looks like current directory has changed, better invalidate # the filename cache, too. - (( $# == 0 )) && zftp_fcache=() + (( $# == 0 )) && zffcache -d fi if [[ -n $file && -f $file ]]; then diff --git a/Functions/Zftp/zffcache b/Functions/Zftp/zffcache new file mode 100644 index 000000000..0d9686660 --- /dev/null +++ b/Functions/Zftp/zffcache @@ -0,0 +1,24 @@ +# Generate an array name for storing the cache for the current session, +# storing it in fcache_name, then generate the cache for the current +# directory, or with argument -d clear the cache. + +fcache_name=$zfconfig[fcache_$ZFTP_SESSION] +if [[ -z $fcache_name ]]; then + local vals + vals=(${(v)zfconfig[(I)fcache_*]##zftp_fcache_}) + integer i + while [[ -n ${vals[(r)zftp_fcache_$i]} ]]; do + (( i++ )) + done + fcache_name=zftp_fcache_$i + zfconfig[fcache_$ZFTP_SESSION]=$fcache_name +fi + +if [[ $1 = -d ]]; then + unset $fcache_name +elif (( ${(P)#fcache_name} == 0 )); then + local tmpf=${TMPPREFIX}zffcache$$ + zftp ls >$tmpf + eval "$fcache_name=(\${(f)\"\$(<\$tmpf)\"})" + rm -f $tmpf +fi diff --git a/Functions/Zftp/zfgcp b/Functions/Zftp/zfgcp index 26a08697d..916a5f7b6 100644 --- a/Functions/Zftp/zfgcp +++ b/Functions/Zftp/zfgcp @@ -16,30 +16,16 @@ emulate -L zsh -local opt optlist nglob remlist rem loc time +local opt remlist rem loc opt_G opt_t integer stat do_close -while [[ $1 == -* ]]; do - if [[ $1 == - || $1 == -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $opt in - G) nglob=1 - ;; - t) time=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :Gt opt; do + [[ $opt = '?' ]] && print "zfgcp: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) -zfautocheck +zfautocheck || return 1 # hmm, we should really check this after expanding the glob, # but we shouldn't expand the last argument remotely anyway. @@ -59,14 +45,14 @@ if [[ -d $argv[-1] ]]; then if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then remlist="~${remlist#$HOME}" fi - if [[ $nglob != 1 ]]; then + if [[ $opt_G != 1 ]]; then zfrglob remlist fi if (( $#remlist )); then for rem in $remlist; do loc=$dir/${rem:t} if zftp get $rem >$loc; then - [[ $time = 1 ]] && zfrtime $rem $loc + [[ $opt_t = 1 ]] && zfrtime $rem $loc else stat=1 fi @@ -74,6 +60,9 @@ if [[ -d $argv[-1] ]]; then fi done else + if [[ $1 = $HOME || $1 = $HOME/* ]]; then + 1="~${1#$HOME}" + fi zftp get $1 >$2 || stat=$? fi diff --git a/Functions/Zftp/zfget b/Functions/Zftp/zfget index cee0290b3..cb058204d 100644 --- a/Functions/Zftp/zfget +++ b/Functions/Zftp/zfget @@ -19,50 +19,34 @@ emulate -L zsh -local loc rem optlist opt nglob remlist time cat +local loc rem opt remlist opt_G opt_t opt_c integer stat do_close -while [[ $1 == -* ]]; do - if [[ $1 == - || $1 == -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $opt in - G) nglob=1 - ;; - t) time=1 - ;; - c) cat=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :Gtc opt; do + [[ $opt = '?' ]] && print "zfget: bad option: -$OPTARG" && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) -zfautocheck +zfautocheck || return 1 for remlist in $*; do # zfcd directory hack to put the front back to ~ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then remlist="~${remlist#$HOME}" fi - if [[ $nglob != 1 ]]; then + if [[ $opt_G != 1 ]]; then zfrglob remlist fi if (( $#remlist )); then for rem in $remlist; do - if [[ -n $cat ]]; then + if [[ -n $opt_c ]]; then zftp get $rem stat=$? else loc=${rem:t} if zftp get $rem >$loc; then - [[ $time = 1 ]] && zfrtime $rem $loc + [[ $opt_t = 1 ]] && zfrtime $rem $loc else stat=1 fi diff --git a/Functions/Zftp/zfget_match b/Functions/Zftp/zfget_match index 677108ede..875fca5f1 100644 --- a/Functions/Zftp/zfget_match +++ b/Functions/Zftp/zfget_match @@ -10,18 +10,29 @@ fi local tmpf=${TMPPREFIX}zfgm$$ if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then - # On the first argument to ls, we usually get away with a glob. - zftp ls "$1*$2" >$tmpf - reply=($(<$tmpf)) - rm -f $tmpf -else - if (( $#zftp_fcache == 0 )); then - # Always cache the current directory and use it - # even if the system is UNIX. - zftp ls >$tmpf - zftp_fcache=($(<$tmpf)) + if [[ -n $WIDGET ]]; then + local dir=${1:h} + [[ $dir = */ ]] || dir="$dir/" + zftp ls -LF $dir >$tmpf + local reply + reply=(${${${(f)"$(<$tmpf)"}##$dir}%\*}) + rm -f $tmpf + _description expl 'remote file' + compadd "$expl[@]" -P $dir - $reply + else + # On the first argument to ls, we usually get away with a glob. + zftp ls "$1*$2" >$tmpf + reply=($(<$tmpf)) rm -f $tmpf fi - reply=($zftp_fcache); +else + local fcache_name + zffcache + if [[ -n $WIDGET ]]; then + _description expl 'remote file' + compadd "$expl[@]" -F fignore - ${(P)fcache_name} + else + reply=(${(P)fcache_name}); + fi fi # } diff --git a/Functions/Zftp/zfgoto b/Functions/Zftp/zfgoto index bd1cdbfe5..57651e383 100644 --- a/Functions/Zftp/zfgoto +++ b/Functions/Zftp/zfgoto @@ -2,6 +2,9 @@ # Go to bookmark bname, a location on a remote FTP host. Unless # this was the last session or is for anonymous FTP, prompt for # the user's password. +# +# Maybe this should try and look for an appropriate session to use +# for the transfer. emulate -L zsh setopt extendedglob @@ -11,33 +14,20 @@ setopt extendedglob : ${ZFTP_BMFILE:=${ZFDOTDIR:-$HOME}/.zfbkmarks} typeset -A bkmarks -local line ncftp opt optlist +local line opt_n opt -while [[ $1 == -* ]]; do - if [[ $1 == - || $1 == -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $opt in - n) ncftp=1 - ;; - *) print option $opt not recognised >&2 - return 1 - ;; - esac - done - shift +while getopts :n opt; do + [[ $opt = '?' ]] && print "zfgoto: bad option: -$OPTARG" && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) if (( $# != 1 )); then print "Usage: zfgoto bookmark" >&2 return 1 fi -if [[ -n $ncftp && -f ~/.ncftp/bookmarks ]]; then +if [[ -n $opt_n && -f ~/.ncftp/bookmarks ]]; then local oldifs=$IFS IFS=, while read -rA line; do @@ -73,7 +63,8 @@ if [[ $ZFTP_USER = $user && $ZFTP_HOST = $host ]]; then elif [[ $user = ftp || $user = anonymous ]]; then # Anonymous ftp, so we don't need password etc. zfanon $host && [[ -n $dir ]] && zfcd $dir -elif [[ $zflastsession = ${host}:* && $user = $zflastuser ]]; then +elif [[ $zfconfig[lastloc_$ZFTP_SESSION] = ${host}:* && + $user = $zfconfig[lastuser_$ZFTP_SESSION] ]]; then # This was the last session, so assume it's still setup in the # open parameters zfopen && [[ -n $dir ]] && zfcd $dir diff --git a/Functions/Zftp/zfinit b/Functions/Zftp/zfinit index 0bc619277..f650b7bbb 100644 --- a/Functions/Zftp/zfinit +++ b/Functions/Zftp/zfinit @@ -4,7 +4,7 @@ emulate -L zsh if [[ ${+zfconfig} = 0 ]]; then typeset -gA zfconfig - zfconfig=(progress bar update 1) + zfconfig=(progress bar update 1 lastsession default) fi alias zfcd='noglob zfcd' @@ -14,9 +14,9 @@ alias zfdir='noglob zfdir' alias zfuget='noglob zfuget' autoload -U zfanon zfautocheck zfcd zfcd_match zfcget zfclose zfcput -autoload -U zfdir zfgcp zfget zfget_match zfgoto zfhere zfinit zfls -autoload -U zfmark zfopen zfparams zfpcp zfput zfrglob zfrtime zfstat -autoload -U zftp_chpwd zftp_progress zftype zfuget zfuput +autoload -U zfdir zffcache zfgcp zfget zfget_match zfgoto zfhere zfinit zfls +autoload -U zfmark zfopen zfparams zfpcp zfput zfrglob zfrtime zfsession +autoload -U zfstat zftp_chpwd zftp_progress zftransfer zftype zfuget zfuput # # zftp completions: only use these if new-style completion is not @@ -33,7 +33,8 @@ if [[ ${#_patcomps} -eq 0 || ${_patcomps[(i)zf*]} -gt ${#_patcomps} ]]; then close quit)' - \ 'w[1,cd][1,ls][1,dir][1,rmdir]' -K zfcd_match -S/ -q - \ 'W[1,get*]' -K zfget_match - 'w[1,delete][1,remote]' -K zfget_match - \ - 'w[1,open][1,params]' -k hosts -- zftp + 'w[1,open][1,params]' -k hosts - \ + 'w[1,session]' -s '${$(zftp session):#$ZFTP_SESSION}' -- zftp compctl -K zfcd_match -S/ -q zfcd zfdir zfls compctl -K zfget_match zfget zfgcp zfuget zfcget compctl -k hosts zfanon zfopen zfparams @@ -42,4 +43,15 @@ if [[ ${#_patcomps} -eq 0 || ${_patcomps[(i)zf*]} -gt ${#_patcomps} ]]; then -x 'W[1,-*n*]' \ -s '$(awk -F, '\''NR > 2 { print $1 }'\'' ~/.ncftp/bookmarks)' -- \ zfgoto zfmark + compctl -s '${$(zftp session):#$ZFTP_SESSION}' -- zfsession + # in _zftp for new completion, but hard to inline into a compctl + zftransfer_match() { + local sess=${1%%:*} oldsess=$ZFTP_SESSION + [[ -n $sess ]] && zftp session $sess + zfget_match ${1#*:} $2 + [[ -n $sess && -n $oldsess ]] && zftp session $oldsess + reply=(${sess}:${^reply}) + } + compctl -s '$(zftp session)' -S : -x 'C[0,*:*]' \ + -K zftransfer_match -- zftransfer fi diff --git a/Functions/Zftp/zfmark b/Functions/Zftp/zfmark index 8d35ce45a..74cc702ac 100644 --- a/Functions/Zftp/zfmark +++ b/Functions/Zftp/zfmark @@ -37,8 +37,9 @@ fi if [[ -n $ZFTP_HOST ]]; then bkmarks[$1]="${ZFTP_USER}@${ZFTP_HOST}:${ZFTP_PWD}" -elif [[ -n $zflastsession ]]; then - bkmarks[$1]="${zflastuser}@${zflastsession}" +elif [[ -n $zfconfig[lastloc_$ZFTP_SESSION] ]]; then + bkmarks[$1]="${zfconig[lastuser_$ZFTP_SESSION]}@\ +${zfconfig[lastloc_$ZFTP_SESSION]}" else print "No current or recent ZFTP session to bookmark." >&2 return 1 diff --git a/Functions/Zftp/zfopen b/Functions/Zftp/zfopen index b264aeaba..32b450411 100644 --- a/Functions/Zftp/zfopen +++ b/Functions/Zftp/zfopen @@ -7,25 +7,13 @@ emulate -L zsh -local optlist opt once dir +local opt dir opt_1 -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $optlist[$i] in - 1) once=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :1 opt; do + [[ $opt = "?" ]] && print "zfopen: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) # This is where we should try and do same name-lookupage in # both .netrc and .ncftp/bookmarks . We could even try saving @@ -37,8 +25,14 @@ if [[ $1 = */* ]]; then 1=${1%%/*} fi -if [[ $once = 1 ]]; then +if [[ $opt_1 = 1 ]]; then zftp open $* || return 1 + if [[ $# = 1 ]]; then + if ! zftp login; then + zftp close + return 1 + fi + fi else # set parameters, but only if there was at least a host (( $# > 0 )) && zfparams $* diff --git a/Functions/Zftp/zfparams b/Functions/Zftp/zfparams index 5c5262c52..faae63251 100644 --- a/Functions/Zftp/zfparams +++ b/Functions/Zftp/zfparams @@ -2,11 +2,25 @@ emulate -L zsh -# Set to prompt for any user or password if not given. -# Don't worry about accounts here. -if (( $# > 0 )); then +if [[ $# -eq 1 && $1 = - ]]; then + # Delete existing parameter set. + local sess=$ZFTP_SESSION key + key=${zfconfig[fcache_$sess]} + [[ -n $key ]] && unset $key + for key in fcache lastloc lastdir curdir otherdir otherargs lastuser; do + unset "zfconfig[${key}_${sess}]" + done +elif (( $# > 0 )); then + # Set to prompt for any user or password if not given. + # Don't worry about accounts here. (( $# < 2 )) && 2='?' - (( $# < 3 )) && 3='?' + if (( $# < 3 )); then + if [[ $2 = '?'* ]]; then + 3="?Password on ${1}: " + else + 3="?Password for ${2##\\?} on ${1}: " + fi + fi fi zftp params $* # } diff --git a/Functions/Zftp/zfpcp b/Functions/Zftp/zfpcp index ddd570e59..9642688b7 100644 --- a/Functions/Zftp/zfpcp +++ b/Functions/Zftp/zfpcp @@ -17,7 +17,7 @@ emulate -L zsh local rem loc integer stat do_close -zfautocheck +zfautocheck || return 1 if [[ $# -gt 2 || $2 = (.|..) || $2 = */ ]]; then local dir=$argv[-1] @@ -32,6 +32,9 @@ if [[ $# -gt 2 || $2 = (.|..) || $2 = */ ]]; then zftp put $rem <$loc || stat=1 done else + if [[ $2 = $HOME || $2 = $HOME/* ]]; then + 2="~${2#$HOME}" + fi zftp put $2 <$1 stat=$? if [[ stat -ne 0 && $ZFTP_CODE = 553 && $ZFTP_REPLY = *'Is a directory'* ]] diff --git a/Functions/Zftp/zfrglob b/Functions/Zftp/zfrglob index fad0c3849..535cb8006 100644 --- a/Functions/Zftp/zfrglob +++ b/Functions/Zftp/zfrglob @@ -57,14 +57,10 @@ else rm -f $tmpf else # we just have to do an ls and hope that's right + local fcache_name + zffcache nondir=$pat - if (( $#zftp_fcache == 0 )); then - # Why does `zftp_fcache=($(zftp ls))' sometimes not work? - zftp ls >$tmpf - zftp_fcache=($(<$tmpf)) - rm -f $tmpf - fi - files=($zftp_fcache) + files=(${(P)fcache_name}) fi # now we want to see which of the $files match $nondir: # ${...:/foo} deletes occurrences of foo matching a complete word, diff --git a/Functions/Zftp/zfsession b/Functions/Zftp/zfsession new file mode 100644 index 000000000..9cd0d918f --- /dev/null +++ b/Functions/Zftp/zfsession @@ -0,0 +1,71 @@ +# function zfsession { +# Change or list the sessions for the current zftp connection. + +emulate -L zsh + +local opt opt_l opt_v opt_o opt_d hadopts + +while getopts ":lovd" opt; do + [[ $opt = "?" ]] && print "zfsession: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" + hadopts=1 +done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) + +if [[ $# -gt 1 || (( -n $hadopts && -z $opt_d ) && $# -gt 0 ) ]] +then + print "Usage: zfsession ( [ -lvod ] | session )" 1>&2 + return 1 +fi + +if [[ -n $opt_v ]]; then + local sess + for sess in $(zftp session); do + print -n "${(r.15.. ..:.)sess}\t${zfconfig[lastloc_$sess]:-not connected}" + if [[ $sess = $ZFTP_SESSION ]]; then + print " *" + else + print + fi + done +elif [[ -n $opt_l ]]; then + zftp session +fi + +if [[ -n $opt_o ]]; then + if [[ $zfconfig[lastsession] != $ZFTP_SESSION ]]; then + local cursession=$ZFTP_SESSION + zftp session $zfconfig[lastsession] + zfconfig[lastsession]=$cursession + print $ZFTP_SESSION + else + print "zfsession: no previous session." >&2 + return 1 + fi +fi + +if [[ -n $opt_d ]]; then + local del=${1:-$ZFTP_SESSION} key + key=${zfconfig[fcache_$del]} + [[ -n $key ]] && unset $key + for key in fcache lastloc lastdir curdir otherdir otherargs lastuser; do + unset "zfconfig[${key}_${del}]" + done + zftp rmsession $del + return +fi + +[[ -n $hadopts ]] && return $stat + +if [[ $# = 0 ]]; then + print $ZFTP_SESSION + return +fi + +local oldsession=${ZFTP_SESSION:-default} +zftp session $1 +if [[ $ZFTP_SESSION != $oldsession ]]; then + zfconfig[lastsession]=$oldsession + zftp_chpwd +fi +# } diff --git a/Functions/Zftp/zfstat b/Functions/Zftp/zfstat index 0ca755d03..6945da99b 100644 --- a/Functions/Zftp/zfstat +++ b/Functions/Zftp/zfstat @@ -6,25 +6,15 @@ setopt localoptions unset unsetopt ksharrays -local i stat=0 opt optlist verbose +local i stat=0 opt opt_v -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $opt in - v) verbose=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :v opt; do + [[ $opt = "?" ]] && print "zfstat: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) + +[[ -n $ZFTP_SESSION ]] && print "Session:\t$ZFTP_SESSION" if [[ -n $ZFTP_HOST ]]; then print "Host:\t\t$ZFTP_HOST" @@ -55,7 +45,8 @@ if [[ -n $ZFTP_HOST ]]; then fi else print "Not connected." - [[ -n $zflastsession ]] && print "Last session:\t$zflastsession" + [[ -n $zfconfig[lastloc_$ZFTP_SESSION] ]] && + print "Last location:\t$zfconfig[lastloc_$ZFTP_SESSION]" stat=1 fi @@ -77,7 +68,7 @@ for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do done print -if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then +if [[ -n $ZFTP_HOST && $opt_v = 1 ]]; then zfautocheck -d print "Status of remote server:" # make sure we print the reply diff --git a/Functions/Zftp/zftp_chpwd b/Functions/Zftp/zftp_chpwd index 0b5bbd7d5..53972cd65 100644 --- a/Functions/Zftp/zftp_chpwd +++ b/Functions/Zftp/zftp_chpwd @@ -2,14 +2,14 @@ # You may want to alter chpwd to call this when $ZFTP_USER is set. # If the directory really changed... -if [[ $ZFTP_PWD != $zflastdir ]]; then - # Cancel the filename cache for the current directory. - zftp_fcache=() +if [[ $ZFTP_PWD != $zfconfig[lastdir_$ZFTP_SESSION] ]]; then # ...and also empty the stored directory listing cache. # As this function is called when we close the connection, this # is the only place we need to do these two things. - [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir - zfotherargs= + local curdir=$zfconfig[curdir_$ZFTP_SESSION] + [[ -n $curdir && -f $curdir ]] && rm -f $curdir + zfconfig[otherargs_$ZFTP_SESSION]= + zffcache -d fi if [[ -z $ZFTP_USER ]]; then @@ -18,24 +18,24 @@ if [[ -z $ZFTP_USER ]]; then # delete the non-current cached directory [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir - # don't keep zflastdir between opens (do keep zflastsession) - zflastdir= + # don't keep lastdir between opens (do keep lastloc) + zfconfig[lastdir_$ZFTP_SESSION]= # return the display to standard # uncomment the following line if you have a chpwd which shows directories - # chpwd + chpwd else - [[ -n $ZFTP_PWD ]] && zflastdir=$ZFTP_PWD - zflastsession="$ZFTP_HOST:$ZFTP_PWD" - zflastuser="$ZFTP_USER" + [[ -n $ZFTP_PWD ]] && zfconfig[lastdir_$ZFTP_SESSION]=$ZFTP_PWD + zfconfig[lastloc_$ZFTP_SESSION]="$ZFTP_HOST:$ZFTP_PWD" + zfconfig[lastuser_$ZFTP_SESSION]="$ZFTP_USER" local args if [[ -t 1 && -t 2 ]]; then - local str=$zflastsession + local str=$zfconfig[lastloc_$ZFTP_SESSION] [[ ${#str} -lt 70 ]] && str="%m: %~ $str" case $TERM in sun-cmd) print -n -P "\033]l$str\033\\" ;; - xterm) print -n -P "\033]2;$str\a" + xterm|aixterm) print -n -P "\033]2;$str\a" ;; esac fi diff --git a/Functions/Zftp/zftp_progress b/Functions/Zftp/zftp_progress index 344d1c9c1..b4b639fce 100644 --- a/Functions/Zftp/zftp_progress +++ b/Functions/Zftp/zftp_progress @@ -26,17 +26,19 @@ if [[ -n $ZFTP_TRANSFER ]]; then # avoid a `parameter unset' message [[ $ZFTP_TRANSFER != *F ]] && (( ${+zftpseconds} )) && (( SECONDS - zftpseconds < update )) && return - if [[ -n $ZFTP_SIZE ]]; then - local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%" + # size is usually ZFTP_SIZE, but zftransfer may set ZFTP_TSIZE + local size=${ZFTP_TSIZE:-$ZFTP_SIZE} + if [[ -n $size ]]; then + local frac="$(( ZFTP_COUNT * 100 / size ))%" if [[ $style = bar && ${+COLUMNS} = 1 && $COLUMNS -gt 0 ]]; then if (( ! ${+zftpseconds} )); then - print "$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER" 1>&2 + print "$ZFTP_FILE ($size bytes): $ZFTP_TRANSFER" 1>&2 fi integer maxwidth=$(( COLUMNS - 7 )) - local width="$(( ZFTP_COUNT * maxwidth / ZFTP_SIZE ))" + local width="$(( ZFTP_COUNT * maxwidth / size ))" print -nP "\r%S${(l:width:):-}%s${(l:maxwidth-width:):-}: ${frac}%%" 1>&2 else - print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2 + print -n "\r$ZFTP_FILE ($size bytes): $ZFTP_TRANSFER $frac" 1>&2 fi else print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2 diff --git a/Functions/Zftp/zftransfer b/Functions/Zftp/zftransfer new file mode 100644 index 000000000..929f099d2 --- /dev/null +++ b/Functions/Zftp/zftransfer @@ -0,0 +1,62 @@ +# function zftransfer { +# Transfer files between two distinct sessions. No remote globbing +# is done, since only single pairs can be transferred. + +emulate -L zsh + +local sess1 sess2 file1 file2 oldsess=${ZFTP_SESSION} + +if [[ $# -ne 2 ]]; then + print "Usage: zftransfer sess1:file1 sess2:file2" 1>&2 + return 1 +fi + +if [[ $1 = *:* ]]; then + sess1=${1%%:*} + file1=${1#*:} +fi +: ${sess1:=$ZFTP_SESSION} + +if [[ $2 = *:* ]]; then + sess2=${2%%:*} + file2=${2#*:} +fi +: ${sess2:=$ZFTP_SESSION} +if [[ -z $file2 || $file2 = */ ]]; then + file2="${file2}${file1:t}" +fi + +if [[ $sess1 = $sess2 ]]; then + print "zftransfer: must use two distinct sessions." 1>&2 + return 1 +fi + +zftp session $sess1 +zfautocheck || return 1 + +# It's more useful to show the progress for the second part +# of the pipeline, but unfortunately that can't necessarily get +# the size from the pipe --- and if it does, it's probably wrong. +# To avoid that, try to get the size and set it for the progress to +# see. +if [[ $zfconfig[progress] != none ]]; then + local ZFTP_TSIZE array tmpfile=${TMPPREFIX}zft$$ + zftp remote $file1 >$tmpfile 2>/dev/null + array=($(<$tmpfile)) + rm -f $tmpfile + [[ $#array -eq 2 ]] && ZFTP_TSIZE=$array[1] +fi + +# We do the RHS of the pipeline in a subshell, too, so that +# the LHS can get SIGPIPE when it exits. +{ zfconfig[progress]=none + zftp get $file1 } | +( zftp session $sess2 + zfautocheck && zftp put $file2 ) + +local stat=$? + +zftp session $oldsess + +return $stat +# } diff --git a/Functions/Zftp/zfuget b/Functions/Zftp/zfuget index 482da42e9..955c48f4a 100644 --- a/Functions/Zftp/zfuget +++ b/Functions/Zftp/zfuget @@ -26,7 +26,7 @@ emulate -L zsh local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$ -local rstat remlist verbose optlist opt bad i silent nglob time +local rstat remlist opt opt_v opt_s opt_G opt_t integer stat do_close zfuget_print_time() { @@ -43,40 +43,20 @@ zfuget_print () { print ", $locstats[1] bytes)" } -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $optlist[$i] in - v) verbose=1 - ;; - s) silent=1 - ;; - G) nglob=1 - ;; - t) time=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :vsGt opt; do + [[ $opt = "?" ]] && print "zfuget: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) -[[ -n $bad ]] && return 1 - -zfautocheck +zfautocheck || return 1 for remlist in $*; do # zfcd directory hack to put the front back to ~ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then remlist="~${remlist#$HOME}" fi - if [[ $nglob != 1 ]]; then + if [[ $opt_n != 1 ]]; then zfrglob remlist fi if (( $#remlist )); then @@ -99,11 +79,11 @@ for remlist in $*; do stat=1 continue fi - [[ $verbose = 1 ]] && zfuget_print + [[ $opt_v = 1 ]] && zfuget_print if (( $locstats[1] != $remstats[1] )); then # Files have different sizes - if [[ $locstats[2] > $remstats[2] && $silent != 1 ]]; then - [[ $verbose != 1 ]] && zfuget_print + if [[ $locstats[2] > $remstats[2] && $opt_s != 1 ]]; then + [[ $opt_v != 1 ]] && zfuget_print print "Local file $loc more recent than remote," 1>&2 print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2 read -q doit @@ -111,24 +91,24 @@ for remlist in $*; do else # Files have same size if [[ $locstats[2] < $remstats[2] ]]; then - if [[ $silent != 1 ]]; then - [[ $verbose != 1 ]] && zfuget_print + if [[ $opt_s != 1 ]]; then + [[ $opt_v != 1 ]] && zfuget_print print "Local file $loc has same size as remote," 1>&2 print -n "but local file is older. Transfer anyway [y/n]? " 1>&2 read -q doit fi else # presumably same file, so don't get it. - [[ $verbose = 1 ]] && print Not transferring + [[ $opt_v = 1 ]] && print Not transferring doit=n fi fi else - [[ $verbose = 1 ]] && print New file $loc + [[ $opt_v = 1 ]] && print New file $loc fi if [[ $doit = y ]]; then if zftp get $rem >$loc; then - if [[ $time = 1 ]]; then + if [[ $opt_t = 1 ]]; then # if $remstats is set, it's second element is the remote time zfrtime $loc $rem $remstats[2] fi diff --git a/Functions/Zftp/zfuput b/Functions/Zftp/zfuput index b54d0d0d4..cb179052c 100644 --- a/Functions/Zftp/zfuput +++ b/Functions/Zftp/zfuput @@ -12,7 +12,7 @@ emulate -L zsh local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$ -local rstat verbose optlist opt bad i silent +local rstat opt opt_v opt_s integer stat do_close zfuput_print_time() { @@ -29,29 +29,13 @@ zfuput_print () { print ", $locstats[1] bytes)" } -while [[ $1 = -* ]]; do - if [[ $1 = - || $1 = -- ]]; then - shift; - break; - fi - optlist=${1#-} - for (( i = 1; i <= $#optlist; i++)); do - opt=$optlist[$i] - case $optlist[$i] in - v) verbose=1 - ;; - s) silent=1 - ;; - *) print option $opt not recognised >&2 - ;; - esac - done - shift +while getopts :vs opt; do + [[ $opt = "?" ]] && print "zfuget: bad option: -$OPTARG" >&2 && return 1 + eval "opt_$opt=1" done +(( OPTIND > 1 )) && shift $(( OPTIND - 1 )) -[[ -n $bad ]] && return 1 - -zfautocheck +zfautocheck || return 1 if [[ $ZFTP_VERBOSE = *5* ]]; then # should we turn it off locally? @@ -77,13 +61,13 @@ for rem in $*; do print "Server does not implement full command set required." 1>&2 return 1 elif [[ $rstat = 1 ]]; then - [[ $verbose = 1 ]] && print New file $loc + [[ $opt_v = 1 ]] && print New file $loc else - [[ $verbose = 1 ]] && zfuput_print + [[ $opt_v = 1 ]] && zfuput_print if (( $locstats[1] != $remstats[1] )); then # Files have different sizes - if [[ $locstats[2] < $remstats[2] && $silent != 1 ]]; then - [[ $verbose != 1 ]] && zfuput_print + if [[ $locstats[2] < $remstats[2] && $opt_s != 1 ]]; then + [[ $opt_v != 1 ]] && zfuput_print print "Remote file $rem more recent than local," 1>&2 print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2 read -q doit @@ -91,15 +75,15 @@ for rem in $*; do else # Files have same size if [[ $locstats[2] > $remstats[2] ]]; then - if [[ $silent != 1 ]]; then - [[ $verbose != 1 ]] && zfuput_print + if [[ $opt_s != 1 ]]; then + [[ $opt_v != 1 ]] && zfuput_print print "Remote file $rem has same size as local," 1>&2 print -n "but remote file is older. Transfer anyway [y/n]? " 1>&2 read -q doit fi else # presumably same file, so don't get it. - [[ $verbose = 1 ]] && print Not transferring + [[ $opt_v = 1 ]] && print Not transferring doit=n fi fi diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c index 738d596fa..37a549a63 100644 --- a/Src/Modules/zftp.c +++ b/Src/Modules/zftp.c @@ -29,27 +29,23 @@ /* * TODO: + * should be eight-bit clean, but isn't. + * tracking of logical rather than physical directories, like nochaselinks + * (usually PWD returns physical directory). * can signal handling be improved? - * error messages may need tidying up. * maybe we should block CTRL-c on some more operations, * otherwise you can get the connection closed prematurely. * some way of turning off progress reports when backgrounded * would be nice, but the shell doesn't make it easy to find that out. - * the progress reports 100% a bit prematurely: the data may still - * be in transit, and we are stuck waiting for a message from the - * server. but there's really nothing else to do. it's worst - * with small files. * proxy/gateway connections if i knew what to do * options to specify e.g. a non-standard port - * optimizing things out is hard in general when you don't know what - * the shell's going to want, but they may be places to second guess - * the user. Some of the variables could be made special and so - * only retrieve things like the current directory when necessary. - * But it's much neater to have ordinary variables, which the shell - * can manage without our interference, and getting the directory - * just every time it changes isn't so bad. The user can always - * toggle the `Dumb' preference if it's feeling clever. */ + +/* needed in prototypes for statics */ +struct sockaddr_in; +struct zftp_session; +typedef struct zftp_session *Zftp_session; + #include "zftp.mdh" #include "zftp.pro" @@ -138,7 +134,8 @@ enum { ZFTP_CDUP = 0x0200, /* CDUP rather than CWD */ ZFTP_REST = 0x0400, /* restart: set point in remote file */ ZFTP_RECV = 0x0800, /* receive rather than send */ - ZFTP_TEST = 0x1000 /* test command, don't test */ + ZFTP_TEST = 0x1000, /* test command, don't test */ + ZFTP_SESS = 0x2000 /* session command, don't need status */ }; typedef struct zftpcmd *Zftpcmd; @@ -173,7 +170,9 @@ static struct zftpcmd zftpcmdtab[] = { { "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE }, { "close", zftp_close, 0, 0, ZFTP_CONN }, { "quit", zftp_close, 0, 0, ZFTP_CONN }, - { 0, 0, 0, 0} + { "session", zftp_session, 0, 1, ZFTP_SESS }, + { "rmsession", zftp_rmsession, 0, 1, ZFTP_SESS }, + { 0, 0, 0, 0, 0 } }; static struct builtin bintab[] = { @@ -198,13 +197,8 @@ enum { ZFPM_INTEGER = 0x04 /* passed pointer to off_t */ }; -/* - * Basic I/O variables for control connection: - * zcfd != -1 is a flag that there is a connection open. - */ -static int zcfd = -1; -static FILE *zcin; -static struct sockaddr_in zsock; +/* Number of connections actually open */ +static int zfnopen; /* * zcfinish = 0 keep going @@ -215,12 +209,6 @@ static int zcfinish; /* zfclosing is set if zftp_close() is active */ static int zfclosing; -/* - * Now stuff for data connection - */ -static int zdfd = -1; -static struct sockaddr_in zdsock; - /* * Stuff about last message: last line of message and status code. * The reply is also stored in $ZFTP_REPLY; we keep these separate @@ -229,9 +217,6 @@ static struct sockaddr_in zdsock; static char *lastmsg, lastcodestr[4]; static int lastcode; -/* flag for remote system is UNIX --- useful to know as things are simpler */ -static int zfpassive_conn; - /* remote system has size, mdtm commands */ enum { ZFCP_UNKN = 0, /* dunno if it works on this server */ @@ -239,8 +224,6 @@ enum { ZFCP_NOPE = 2 /* it doesn't */ }; -static int zfhas_size, zfhas_mdtm; - /* * We keep an fd open for communication between the main shell * and forked off bits and pieces. This allows us to know @@ -279,7 +262,8 @@ enum { #define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK) #define ZFST_MODE(x) (x & ZFST_MMSK) -static int zfstatfd = -1, zfstatus; +/* fd containing status for all sessions and array for internal use */ +static int zfstatfd = -1, *zfstatusp; /* Preferences, read in from the `zftp_prefs' array variable */ enum { @@ -289,11 +273,42 @@ enum { }; /* The flags as stored internally. */ -int zfprefs; +static int zfprefs; +/* + * Data node for linked list of sessions. + * + * Memory management notes: + * name is permanently allocated and remains for the life of the node. + * userparams is set directly by zftp_params and also freed with the node. + * params and its data are allocated when we need + * to save an existing session, and are freed when we switch back + * to that session. + * The node itself is deleted when we remove it from the list. + */ +struct zftp_session { + char *name; /* name of session */ + char **params; /* parameters ordered as in zfparams */ + char **userparams; /* user parameters set by zftp_params */ + int cfd; /* control file descriptor */ + FILE *cin; /* control input file */ + struct sockaddr_in sock; /* the socket for the control connection */ + int dfd; /* data connection */ + int has_size; /* understands SIZE? */ + int has_mdtm; /* understands MDTM? */ +}; + +/* List of active sessions */ +static LinkList zfsessions; + +/* Current session */ +static Zftp_session zfsess; + +/* Number of current session, corresponding to position in list */ +static int zfsessno; -/* zfuserparams is the storage area for zftp_params() */ -char **zfuserparams; +/* Total number of sessions */ +static int zfsesscnt; /* * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's @@ -570,12 +585,12 @@ zfgetline(char *ln, int lnsize, int tmout) * But then we get `frustrated user syndrome'. */ for (;;) { - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); switch(ch) { case EOF: - if (ferror(zcin) && errno == EINTR) { - clearerr(zcin); + if (ferror(zfsess->cin) && errno == EINTR) { + clearerr(zfsess->cin); continue; } zcfinish = 2; @@ -583,7 +598,7 @@ zfgetline(char *ln, int lnsize, int tmout) case '\r': /* always precedes something else */ - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); if (ch == EOF) { zcfinish = 2; break; @@ -610,26 +625,26 @@ zfgetline(char *ln, int lnsize, int tmout) * oh great, now it's sending TELNET commands. try * to persuade it not to. */ - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); switch (ch) { case WILL: case WONT: - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); /* whatever it wants to do, stop it. */ cmdbuf[0] = (char)IAC; cmdbuf[1] = (char)DONT; cmdbuf[2] = ch; - write(zcfd, cmdbuf, 3); + write(zfsess->cfd, cmdbuf, 3); continue; case DO: case DONT: - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); /* well, tough, we're not going to. */ cmdbuf[0] = (char)IAC; cmdbuf[1] = (char)WONT; cmdbuf[2] = ch; - write(zcfd, cmdbuf, 3); + write(zfsess->cfd, cmdbuf, 3); continue; case EOF: @@ -679,7 +694,7 @@ zfgetmsg(void) char line[256], *ptr, *verbose; int stopit, printing = 0, tmout; - if (zcfd == -1) + if (zfsess->cfd == -1) return 6; if (!(verbose = getsparam("ZFTP_VERBOSE"))) verbose = ""; @@ -695,7 +710,7 @@ zfgetmsg(void) /* timeout, or not talking FTP. not really interested. */ zcfinish = 2; if (!zfclosing) - zfclose(); + zfclose(0); lastmsg = ztrdup(""); strcpy(lastcodestr, "000"); zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY); @@ -764,7 +779,7 @@ zfgetmsg(void) */ if ((zcfinish == 2 || lastcode == 421) && !zfclosing) { zcfinish = 2; /* don't need to tell server */ - zfclose(); + zfclose(0); /* unexpected, so tell user */ zwarnnam("zftp", "remote server has closed connection", NULL, 0); return 6; @@ -805,7 +820,7 @@ zfsendcmd(char *cmd) */ int ret, tmout; - if (zcfd == -1) + if (zfsess->cfd == -1) return 6; tmout = getiparam("ZFTP_TMOUT"); if (setjmp(zfalrmbuf)) { @@ -814,7 +829,7 @@ zfsendcmd(char *cmd) return 6; } zfalarm(tmout); - ret = write(zcfd, cmd, strlen(cmd)); + ret = write(zfsess->cfd, cmd, strlen(cmd)); alarm(0); if (ret <= 0) { @@ -831,22 +846,22 @@ zfsendcmd(char *cmd) /**/ static int -zfopendata(char *name) +zfopendata(char *name, struct sockaddr_in *zdsockp, int *is_passivep) { if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) { zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0); return 1; } - zdfd = socket(AF_INET, SOCK_STREAM, 0); - if (zdfd < 0) { + zfsess->dfd = socket(AF_INET, SOCK_STREAM, 0); + if (zfsess->dfd < 0) { zwarnnam(name, "can't get data socket: %e", NULL, errno); return 1; } - zdsock = zsock; - zdsock.sin_family = AF_INET; + *zdsockp = zfsess->sock; + zdsockp->sin_family = AF_INET; - if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { + if (!(zfstatusp[zfsessno] & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { char *ptr; int i, nums[6], err; unsigned char iaddr[4], iport[2]; @@ -858,9 +873,9 @@ zfopendata(char *name) * Fall back to send port mode. That will * test the preferences for whether that's OK. */ - zfstatus |= ZFST_NOPS; + zfstatusp[zfsessno] |= ZFST_NOPS; zfclosedata(); - return zfopendata(name); + return zfopendata(name, zdsockp, is_passivep); } /* * OK, now we need to know what port we're looking at, @@ -881,12 +896,13 @@ zfopendata(char *name) iport[0] = STOUC(nums[4]); iport[1] = STOUC(nums[5]); - memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr)); - memcpy(&zdsock.sin_port, iport, sizeof(iport)); + memcpy(&zdsockp->sin_addr, iaddr, sizeof(iaddr)); + memcpy(&zdsockp->sin_port, iport, sizeof(iport)); /* we should timeout this connect */ do { - err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)); + err = connect(zfsess->dfd, + (struct sockaddr *)zdsockp, sizeof(*zdsockp)); } while (err && errno == EINTR && !errflag); if (err) { @@ -895,7 +911,7 @@ zfopendata(char *name) return 1; } - zfpassive_conn = 1; + *is_passivep = 1; } else { char portcmd[40]; unsigned char *addr, *port; @@ -906,14 +922,16 @@ zfopendata(char *name) return 1; } - zdsock.sin_port = 0; /* to be set by bind() */ - len = sizeof(zdsock); + zdsockp->sin_port = 0; /* to be set by bind() */ + len = sizeof(*zdsockp); /* need to do timeout stuff and probably handle EINTR here */ - if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0) + if (bind(zfsess->dfd, (struct sockaddr *)zdsockp, + sizeof(*zdsockp)) < 0) ret = 1; - else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0) + else if (getsockname(zfsess->dfd, (struct sockaddr *)zdsockp, + &len) < 0) ret = 2; - else if (listen(zdfd, 1) < 0) + else if (listen(zfsess->dfd, 1) < 0) ret = 3; else ret = 0; @@ -926,8 +944,8 @@ zfopendata(char *name) return 1; } - addr = (unsigned char *) &zdsock.sin_addr; - port = (unsigned char *) &zdsock.sin_port; + addr = (unsigned char *) &zdsockp->sin_addr; + port = (unsigned char *) &zdsockp->sin_port; sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n", addr[0],addr[1],addr[2],addr[3],port[0],port[1]); if (zfsendcmd(portcmd) >= 5) { @@ -935,7 +953,7 @@ zfopendata(char *name) zfclosedata(); return 1; } - zfpassive_conn = 0; + *is_passivep = 0; } return 0; @@ -947,15 +965,15 @@ zfopendata(char *name) static void zfclosedata(void) { - if (zdfd == -1) + if (zfsess->dfd == -1) return; - close(zdfd); - zdfd = -1; + close(zfsess->dfd); + zfsess->dfd = -1; } /* * Set up a data connection and use cmd to initiate a transfer. - * The actual data fd will be zdfd; the calling routine + * The actual data fd will be zfsess->dfd; the calling routine * must handle the data itself. * rest is a REST command to specify starting somewhere other * then the start of the remote file. @@ -969,9 +987,10 @@ zfclosedata(void) static int zfgetdata(char *name, char *rest, char *cmd, int getsize) { - int len, newfd; + int len, newfd, is_passive; + struct sockaddr_in zdsock; - if (zfopendata(name)) + if (zfopendata(name, &zdsock, &is_passive)) return 1; /* @@ -993,7 +1012,8 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) zfclosedata(); return 1; } - if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) { + if (getsize || (!(zfstatusp[zfsessno] & ZFST_TRSZ) && + !strncmp(cmd, "RETR", 4))) { /* * See if we got something like: * Opening data connection for nortypix.gif (1234567 bytes). @@ -1001,14 +1021,14 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) * can avoid sending a special SIZE command beforehand. */ char *ptr = strstr(lastmsg, "bytes"); - zfstatus |= ZFST_NOSZ|ZFST_TRSZ; + zfstatusp[zfsessno] |= ZFST_NOSZ|ZFST_TRSZ; if (ptr) { while (ptr > lastmsg && !isdigit(STOUC(*ptr))) ptr--; while (ptr > lastmsg && isdigit(STOUC(ptr[-1]))) ptr--; if (isdigit(STOUC(*ptr))) { - zfstatus &= ~ZFST_NOSZ; + zfstatusp[zfsessno] &= ~ZFST_NOSZ; if (getsize) { off_t sz = zstrtol(ptr, NULL, 10); zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); @@ -1017,9 +1037,9 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) } } - if (!zfpassive_conn) { + if (!is_passive) { /* - * the current zdfd is the socket we opened, but we need + * the current zfsess->dfd is the socket we opened, but we need * to let the server set up a different fd for reading/writing. * then we can close the fd we were listening for a connection on. * don't expect me to understand this, i'm only the programmer. @@ -1027,20 +1047,21 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) /* accept the connection */ len = sizeof(zdsock); - newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len)); + newfd = zfmovefd(accept(zfsess->dfd, (struct sockaddr *)&zdsock, + &len)); if (newfd < 0) zwarnnam(name, "unable to accept data: %e", NULL, errno); zfclosedata(); if (newfd < 0) return 1; - zdfd = newfd; /* this is now the actual data fd */ + zfsess->dfd = newfd; /* this is now the actual data fd */ } else { /* - * We avoided dup'ing zdfd up to this point, to try to keep + * We avoided dup'ing zfsess->dfd up to this point, to try to keep * things simple, so we now need to move it out of the way * of the user-visible fd's. */ - zdfd = zfmovefd(zdfd); + zfsess->dfd = zfmovefd(zfsess->dfd); } @@ -1059,20 +1080,21 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) li.l_onoff = 1; li.l_linger = 120; - setsockopt(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li)); + setsockopt(zfsess->dfd, SOL_SOCKET, SO_LINGER, + (char *)&li, sizeof(li)); } #endif #if defined(IP_TOS) && defined(IPTOS_THROUGHPUT) /* try to get high throughput, snigger */ { int arg = IPTOS_THROUGHPUT; - setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); + setsockopt(zfsess->dfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); } #endif #if defined(F_SETFD) && defined(FD_CLOEXEC) /* If the shell execs a program, we don't want this fd left open. */ len = FD_CLOEXEC; - fcntl(zdfd, F_SETFD, &len); + fcntl(zfsess->dfd, F_SETFD, &len); #endif return 0; @@ -1103,15 +1125,15 @@ zfstats(char *fnam, int remote, off_t *retsize, char **retmdtm, int fd) *retmdtm = NULL; if (remote) { char *cmd; - if ((zfhas_size == ZFCP_NOPE && retsize) || - (zfhas_mdtm == ZFCP_NOPE && retmdtm)) + if ((zfsess->has_size == ZFCP_NOPE && retsize) || + (zfsess->has_mdtm == ZFCP_NOPE && retmdtm)) return 2; /* * File is coming from over there. * Make sure we get the type right. */ - zfsettype(ZFST_TYPE(zfstatus)); + zfsettype(ZFST_TYPE(zfstatusp[zfsessno])); if (retsize) { cmd = tricat("SIZE ", fnam, "\r\n"); ret = zfsendcmd(cmd); @@ -1120,9 +1142,9 @@ zfstats(char *fnam, int remote, off_t *retsize, char **retmdtm, int fd) return 1; else if (lastcode < 300) { sz = zstrtol(lastmsg, 0, 10); - zfhas_size = ZFCP_YUPP; + zfsess->has_size = ZFCP_YUPP; } else if (lastcode >= 500 && lastcode <= 504) { - zfhas_size = ZFCP_NOPE; + zfsess->has_size = ZFCP_NOPE; return 2; } else if (lastcode == 550) return 1; @@ -1137,9 +1159,9 @@ zfstats(char *fnam, int remote, off_t *retsize, char **retmdtm, int fd) return 1; else if (lastcode < 300) { mt = ztrdup(lastmsg); - zfhas_mdtm = ZFCP_YUPP; + zfsess->has_mdtm = ZFCP_YUPP; } else if (lastcode >= 500 && lastcode <= 504) { - zfhas_mdtm = ZFCP_NOPE; + zfsess->has_mdtm = ZFCP_NOPE; return 2; } else if (lastcode == 550) return 1; @@ -1387,20 +1409,20 @@ zfsenddata(char *name, int recv, int progress, off_t startat) sofar = last_sofar = startat; } if (recv) { - fdin = zdfd; + fdin = zfsess->dfd; fdout = 1; rtmout = getiparam("ZFTP_TMOUT"); - if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI) fromasc = 1; - if (ZFST_MODE(zfstatus) == ZFST_BLOC) + if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) read_ptr = zfread_block; } else { fdin = 0; - fdout = zdfd; + fdout = zfsess->dfd; wtmout = getiparam("ZFTP_TMOUT"); - if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI) toasc = 1; - if (ZFST_MODE(zfstatus) == ZFST_BLOC) + if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) write_ptr = zfwrite_block; } @@ -1525,7 +1547,8 @@ zfsenddata(char *name, int recv, int progress, off_t startat) * so we don't need to force the control connection to close. */ zfdrrrring = 0; - if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) { + if (!errflag && !ret && !recv && + ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) { /* send an end-of-file marker block */ ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0); } @@ -1554,8 +1577,8 @@ zfsenddata(char *name, int recv, int progress, off_t startat) /* the following is black magic, as far as I'm concerned. */ /* what are we going to do if it fails? not a lot, actually. */ - send(zcfd, (char *)msg, 3, 0); - send(zcfd, (char *)msg+3, 1, MSG_OOB); + send(zfsess->cfd, (char *)msg, 3, 0); + send(zfsess->cfd, (char *)msg+3, 1, MSG_OOB); zfsendcmd("ABOR\r\n"); if (lastcode == 226) { @@ -1603,8 +1626,8 @@ zftp_open(char *name, char **args, int flags) int err, len, tmout; if (!*args) { - if (zfuserparams) - args = zfuserparams; + if (zfsess->userparams) + args = zfsess->userparams; else { zwarnnam(name, "no host specified", NULL, 0); return 1; @@ -1616,8 +1639,8 @@ zftp_open(char *name, char **args, int flags) * Probably this is the safest thing to do. It's possible * a `QUIT' will hang, though. */ - if (zcfd != -1) - zfclose(); + if (zfsess->cfd != -1) + zfclose(0); /* this is going to give 0. why bother? */ zprotop = getprotobyname("tcp"); @@ -1643,7 +1666,7 @@ zftp_open(char *name, char **args, int flags) zwarnnam(name, "timeout connecting to %s", hname, 0); else zwarnnam(name, "timeout on host name lookup", NULL, 0); - zfclose(); + zfclose(0); return 1; } zfalarm(tmout); @@ -1666,7 +1689,7 @@ zftp_open(char *name, char **args, int flags) * or, we could have a `no_lookup' flag. */ zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY); - zsock.sin_family = AF_INET; + zfsess->sock.sin_family = AF_INET; } else { zhostp = gethostbyname(args[0]); if (!zhostp || errflag) { @@ -1678,18 +1701,20 @@ zftp_open(char *name, char **args, int flags) alarm(0); return 1; } - zsock.sin_family = zhostp->h_addrtype; + zfsess->sock.sin_family = zhostp->h_addrtype; zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY); } - zsock.sin_port = zservp->s_port; - zcfd = socket(zsock.sin_family, SOCK_STREAM, 0); - if (zcfd < 0) { + zfsess->sock.sin_port = zservp->s_port; + zfsess->cfd = socket(zfsess->sock.sin_family, SOCK_STREAM, 0); + if (zfsess->cfd < 0) { zwarnnam(name, "socket failed: %e", NULL, errno); zfunsetparam("ZFTP_HOST"); alarm(0); return 1; } + /* counts as `open' so long as it's not negative */ + zfnopen++; /* * now connect the socket. manual pages all say things like `this is all @@ -1700,16 +1725,18 @@ zftp_open(char *name, char **args, int flags) if (ipaddr.s_addr != INADDR_NONE) { /* dot address */ - memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr)); + memcpy(&zfsess->sock.sin_addr, &ipaddr, sizeof(ipaddr)); do { - err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + err = connect(zfsess->cfd, (struct sockaddr *)&zfsess->sock, + sizeof(zfsess->sock)); } while (err && errno == EINTR && !errflag); } else { /* host name: try all possible IP's */ for (addrp = zhostp->h_addr_list; *addrp; addrp++) { - memcpy(&zsock.sin_addr, *addrp, zhostp->h_length); + memcpy(&zfsess->sock.sin_addr, *addrp, zhostp->h_length); do { - err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + err = connect(zfsess->cfd, (struct sockaddr *)&zfsess->sock, + sizeof(zfsess->sock)); } while (err && errno == EINTR && !errflag); /* you can check whether it's worth retrying here */ } @@ -1719,10 +1746,11 @@ zftp_open(char *name, char **args, int flags) if (err < 0) { zwarnnam(name, "connect failed: %e", NULL, errno); - zfclose(); + zfclose(0); return 1; } - zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY); + zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zfsess->sock.sin_addr)), + ZFPM_READONLY); /* now we can talk to the control connection */ zcfinish = 0; @@ -1731,18 +1759,18 @@ zftp_open(char *name, char **args, int flags) * Move the fd out of the user-visible range. We need to do * this after the connect() on some systems. */ - zcfd = zfmovefd(zcfd); + zfsess->cfd = zfmovefd(zfsess->cfd); #if defined(F_SETFD) && defined(FD_CLOEXEC) /* If the shell execs a program, we don't want this fd left open. */ len = FD_CLOEXEC; - fcntl(zcfd, F_SETFD, &len); + fcntl(zfsess->cfd, F_SETFD, &len); #endif - len = sizeof(zsock); - if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) { + len = sizeof(zfsess->sock); + if (getsockname(zfsess->cfd, (struct sockaddr *)&zfsess->sock, &len) < 0) { zwarnnam(name, "getsockname failed: %e", NULL, errno); - zfclose(); + zfclose(0); return 1; } /* nice to get some options right, ignore if they don't work */ @@ -1752,67 +1780,75 @@ zftp_open(char *name, char **args, int flags) * do clever things with SIGURG. */ len = 1; - setsockopt(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); + setsockopt(zfsess->cfd, SOL_SOCKET, SO_OOBINLINE, + (char *)&len, sizeof(len)); #endif #if defined(IP_TOS) && defined(IPTOS_LOWDELAY) /* for control connection we want low delay. please don't laugh. */ len = IPTOS_LOWDELAY; - setsockopt(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len)); + setsockopt(zfsess->cfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len)); #endif /* * We use stdio with line buffering for convenience on input. * On output, we can just dump a complete message to the fd via write(). */ - zcin = fdopen(zcfd, "r"); + zfsess->cin = fdopen(zfsess->cfd, "r"); - if (!zcin) { + if (!zfsess->cin) { zwarnnam(name, "file handling error", NULL, 0); - zfclose(); + zfclose(0); return 1; } #ifdef _IONBF - setvbuf(zcin, NULL, _IONBF, 0); + setvbuf(zfsess->cin, NULL, _IONBF, 0); #else - setlinebuf(zcin); + setlinebuf(zfsess->cin); #endif /* * now see what the remote server has to say about that. */ if (zfgetmsg() >= 4) { - zfclose(); + zfclose(0); return 1; } - zfhas_size = zfhas_mdtm = ZFCP_UNKN; - zdfd = -1; + zfsess->has_size = zfsess->has_mdtm = ZFCP_UNKN; + zfsess->dfd = -1; /* initial status: open, ASCII data, stream mode 'n' stuff */ - zfstatus = 0; + zfstatusp[zfsessno] = 0; - /* open file for saving the current status */ - fname = gettempname(); - zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); - DPUTS(zfstatfd == -1, "zfstatfd not created"); + /* + * Open file for saving the current status. + * We keep this open at the end of the session because + * it is used to store the status for all sessions. + * However, it is closed whenever there are no connections open. + */ + if (zfstatfd == -1) { + fname = gettempname(); + zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); + DPUTS(zfstatfd == -1, "zfstatfd not created"); #if defined(F_SETFD) && defined(FD_CLOEXEC) - /* If the shell execs a program, we don't want this fd left open. */ - len = FD_CLOEXEC; - fcntl(zfstatfd, F_SETFD, &len); + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zfstatfd, F_SETFD, &len); #endif - unlink(fname); + unlink(fname); + } - if (zcfd == -1) { + if (zfsess->cfd == -1) { /* final paranoid check */ return 1; } zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY); /* if remaining arguments, use them to log in. */ - if (zcfd > -1 && *++args) + if (zfsess->cfd > -1 && *++args) return zftp_login(name, args, flags); /* if something wayward happened, connection was already closed */ - return zcfd == -1; + return zfsess->cfd == -1; } /* @@ -1894,8 +1930,8 @@ zftp_params(char *name, char **args, int flags) int i, j, len; if (!*args) { - if (zfuserparams) { - for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) { + if (zfsess->userparams) { + for (aptr = zfsess->userparams, i = 0; *aptr; aptr++, i++) { if (i == 2) { len = strlen(*aptr); for (j = 0; j < len; j++) @@ -1904,23 +1940,24 @@ zftp_params(char *name, char **args, int flags) } else fprintf(stdout, "%s\n", *aptr); } - } - return 0; + return 0; + } else + return 1; } if (!strcmp(*args, "-")) { - if (zfuserparams) - freearray(zfuserparams); - zfuserparams = 0; + if (zfsess->userparams) + freearray(zfsess->userparams); + zfsess->userparams = 0; return 0; } len = arrlen(args); newarr = (char **)zcalloc((len+1)*sizeof(char *)); for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) { char *str; - if (!strcmp(*aptr, "?")) - str = zfgetinfo(prompts[i], i == 2); + if (**aptr == '?') + str = zfgetinfo((*aptr)[1] ? (*aptr+1) : prompts[i], i == 2); else - str = *aptr; + str = (**aptr == '\\') ? *aptr+1 : *aptr; newarr[i] = ztrdup(str); } if (errflag) { @@ -1930,9 +1967,9 @@ zftp_params(char *name, char **args, int flags) zfree(newarr, len+1); return 1; } - if (zfuserparams) - freearray(zfuserparams); - zfuserparams = newarr; + if (zfsess->userparams) + freearray(zfsess->userparams); + zfsess->userparams = newarr; return 0; } @@ -1946,10 +1983,10 @@ zftp_login(char *name, char **args, int flags) char *user, tbuf[2] = "X"; int stopit; - if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) + if ((zfstatusp[zfsessno] & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) return 1; - zfstatus &= ~ZFST_LOGI; + zfstatusp[zfsessno] &= ~ZFST_LOGI; if (*args) { user = *args++; } else { @@ -2005,7 +2042,7 @@ zftp_login(char *name, char **args, int flags) } zsfree(ucmd); - if (zcfd == -1) + if (zfsess->cfd == -1) return 1; if (stopit == 2 || (lastcode != 230 && lastcode != 202)) { zwarnnam(name, "login failed", NULL, 0); @@ -2018,7 +2055,7 @@ zftp_login(char *name, char **args, int flags) cnt++; zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt); } - zfstatus |= ZFST_LOGI; + zfstatusp[zfsessno] |= ZFST_LOGI; zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY); if (acct) zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY); @@ -2028,7 +2065,7 @@ zftp_login(char *name, char **args, int flags) * won't let us do this until we're logged in; it's fairly safe * to delay it here for all systems. */ - if (!(zfprefs & ZFPF_DUMB) && !(zfstatus & ZFST_SYST)) { + if (!(zfprefs & ZFPF_DUMB) && !(zfstatusp[zfsessno] & ZFST_SYST)) { if (zfsendcmd("SYST\r\n") == 2) { char *ptr = lastmsg, *eptr, *systype; for (eptr = ptr; *eptr; eptr++) @@ -2042,13 +2079,13 @@ zftp_login(char *name, char **args, int flags) * We could set this based just on the UNIX part, * but I don't really know the consequences of that. */ - zfstatus |= ZFST_IMAG; + zfstatusp[zfsessno] |= ZFST_IMAG; } zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); } - zfstatus |= ZFST_SYST; + zfstatusp[zfsessno] |= ZFST_SYST; } - tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'; + tbuf[0] = (ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI) ? 'A' : 'I'; zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); /* @@ -2084,7 +2121,7 @@ zftp_test(char *name, char **args, int flags) struct timeval tv; # endif /* HAVE_POLL */ - if (zcfd == -1) + if (zfsess->cfd == -1) return 1; # ifdef HAVE_POLL @@ -2092,29 +2129,30 @@ zftp_test(char *name, char **args, int flags) /* safety first, though I think POLLIN is more common */ # define POLLIN POLLNORM # endif /* HAVE_POLL */ - pfd.fd = zcfd; + pfd.fd = zfsess->cfd; pfd.events = POLLIN; if ((ret = poll(&pfd, 1, 0)) < 0 && errno != EINTR && errno != EAGAIN) - zfclose(); + zfclose(0); else if (ret > 0 && pfd.revents) { /* handles 421 (maybe a bit noisily?) */ zfgetmsg(); } # else FD_ZERO(&f); - FD_SET(zcfd, &f); + FD_SET(zfsess->cfd, &f); tv.tv_sec = 0; tv.tv_usec = 0; - if ((ret = select(zcfd +1, (SELECT_ARG_2_T) &f, NULL, NULL, &tv)) < 0 + if ((ret = select(zfsess->cfd +1, (SELECT_ARG_2_T) &f, + NULL, NULL, &tv)) < 0 && errno != EINTR) - zfclose(); + zfclose(0); else if (ret > 0) { /* handles 421 */ zfgetmsg(); } # endif /* HAVE_POLL */ - /* if we now have zcfd == -1, then we've just been dumped out. */ - return (zcfd == -1) ? 2 : 0; + /* if we now have zfsess->cfd == -1, then we've just been dumped out. */ + return (zfsess->cfd == -1) ? 2 : 0; #else zfwarnnam(name, "not supported on this system.", NULL, 0); return 3; @@ -2229,14 +2267,14 @@ static int zfsettype(int type) { char buf[] = "TYPE X\r\n"; - if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus)) + if (ZFST_TYPE(type) == ZFST_CTYP(zfstatusp[zfsessno])) return 0; buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I'; if (zfsendcmd(buf) > 2) return 1; - zfstatus &= ~(ZFST_TMSK << ZFST_TBIT); + zfstatusp[zfsessno] &= ~(ZFST_TMSK << ZFST_TBIT); /* shift the type left to set the current type bits */; - zfstatus |= type << ZFST_TBIT; + zfstatusp[zfsessno] |= type << ZFST_TBIT; return 0; } @@ -2257,7 +2295,8 @@ zftp_type(char *name, char **args, int flags) * Since this is supposed to be a low-level basis for * an FTP system, just print the single code letter. */ - printf("%c\n", (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'); + printf("%c\n", (ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI) ? + 'A' : 'I'); fflush(stdout); return 0; } else { @@ -2275,8 +2314,8 @@ zftp_type(char *name, char **args, int flags) nt = 'I'; } - zfstatus &= ~ZFST_TMSK; - zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; + zfstatusp[zfsessno] &= ~ZFST_TMSK; + zfstatusp[zfsessno] |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; tbuf[0] = nt; zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); return 0; @@ -2290,7 +2329,8 @@ zftp_mode(char *name, char **args, int flags) int nt; if (!(str = *args)) { - printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B'); + printf("%c\n", (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_STRE) ? + 'S' : 'B'); fflush(stdout); return 0; } @@ -2302,8 +2342,8 @@ zftp_mode(char *name, char **args, int flags) cmd[5] = (char) nt; if (zfsendcmd(cmd) > 2) return 1; - zfstatus &= ZFST_MMSK; - zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; + zfstatusp[zfsessno] &= ZFST_MMSK; + zfstatusp[zfsessno] |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY); return 0; } @@ -2377,7 +2417,7 @@ zftp_getput(char *name, char **args, int flags) * somewhere or in the background. This seems to me a problem. */ - zfsettype(ZFST_TYPE(zfstatus)); + zfsettype(ZFST_TYPE(zfstatusp[zfsessno])); if (recv) fflush(stdout); /* since we may be using fd 1 */ @@ -2395,7 +2435,7 @@ zftp_getput(char *name, char **args, int flags) * of zftp_progress is delayed until zfsenddata(). */ if ((!(zfprefs & ZFPF_DUMB) && - (zfstatus & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ) + (zfstatusp[zfsessno] & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ) || !recv) { /* the final 0 is a local fd to fstat if recv is zero */ zfstats(*args, recv, &sz, NULL, 0); @@ -2413,12 +2453,19 @@ zftp_getput(char *name, char **args, int flags) } ln = tricat(cmd, *args, "\r\n"); - /* note zdfd doesn't exist till zfgetdata() creates it */ - if (zfgetdata(name, rest, ln, getsize) || - zfsenddata(name, recv, progress, startat)) + /* note zfsess->dfd doesn't exist till zfgetdata() creates it */ + if (zfgetdata(name, rest, ln, getsize)) + ret = 2; + else if (zfsenddata(name, recv, progress, startat)) ret = 1; zsfree(ln); - if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* + * The progress report isn't started till zfsenddata(), where + * it's the first item. Hence we send a final progress report + * if and only if we called zfsenddata(); + */ + if (progress && ret != 2 && + (l = getshfunc("zftp_progress")) != &dummy_list) { /* progress to finish: ZFTP_TRANSFER set to GF or PF */ int osc = sfcontext; @@ -2436,7 +2483,7 @@ zftp_getput(char *name, char **args, int flags) break; } zfendtrans(); - return ret; + return ret != 0; } /* @@ -2514,14 +2561,22 @@ zftp_quote(char *name, char **args, int flags) return ret; } -/* Close the connection, ending the session */ +/* + * Close the connection, ending the session. With leaveparams, + * don't do anything to the external status (parameters, zftp_chpwd), + * probably because this isn't the current session. + */ /**/ -static int -zftp_close(char *name, char **args, int flags) +static void +zfclose(int leaveparams) { char **aptr; List l; + + if (zfsess->cfd == -1) + return; + zfclosing = 1; if (zcfinish != 2) { /* @@ -2531,45 +2586,265 @@ zftp_close(char *name, char **args, int flags) */ zfsendcmd("QUIT\r\n"); } - if (zcin) - fclose(zcin); - zcin = NULL; - close(zcfd); - zcfd = -1; + if (zfsess->cin) { + fclose(zfsess->cin); + zfsess->cin = NULL; + } + if (zfsess->cfd != -1) { + zfnopen--; + close(zfsess->cfd); + zfsess->cfd = -1; + } - /* Write the final status in case this is a subshell */ - zfstatus |= ZFST_CLOS; - lseek(zfstatfd, 0, 0); - write(zfstatfd, &zfstatus, sizeof(zfstatus)); - close(zfstatfd); - zfstatfd = -1; + if (zfstatfd != -1) { + zfstatusp[zfsessno] |= ZFST_CLOS; + if (!zfnopen) { + /* Write the final status in case this is a subshell */ + lseek(zfstatfd, zfsessno*sizeof(int), 0); + write(zfstatfd, zfstatusp+zfsessno, sizeof(int)); + + close(zfstatfd); + zfstatfd = -1; + } + } - /* Unset the non-special parameters */ - for (aptr = zfparams; *aptr; aptr++) - zfunsetparam(*aptr); + if (!leaveparams) { + /* Unset the non-special parameters */ + for (aptr = zfparams; *aptr; aptr++) + zfunsetparam(*aptr); - /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */ - if ((l = getshfunc("zftp_chpwd")) != &dummy_list) { - int osc = sfcontext; + /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */ + if ((l = getshfunc("zftp_chpwd")) != &dummy_list) { + int osc = sfcontext; - sfcontext = SFC_HOOK; - doshfunc("zftp_chpwd", l, NULL, 0, 1); - sfcontext = osc; + sfcontext = SFC_HOOK; + doshfunc("zftp_chpwd", l, NULL, 0, 1); + sfcontext = osc; + } } + /* tidy up status variables, because mess is bad */ zfclosing = zfdrrrring = 0; +} + +/* Safe front end to zftp_close() from within the package */ +/**/ +static int +zftp_close(char *name, char **args, int flags) +{ + zfclose(0); return 0; } -/* Safe front end to zftp_close() from within the package */ + +/* + * Session management routines. A session consists of various + * internal variables describing the connection, the set of shell + * parameters --- the same set which is unset by closing a connection --- + * and the set of host/user parameters if set by zftp params. + */ + +/* + * Switch to a new session, creating it if necessary. + * Sets zfsessno, zfsess and $ZFTP_SESSION; updates zfsesscnt and zfstatusp. + */ /**/ static void -zfclose(void) +newsession(char *nm) { - if (zcfd != -1) - zftp_close("zftp close", NULL, 0); + LinkNode nptr; + + for (zfsessno = 0, nptr = firstnode(zfsessions); + nptr; zfsessno++, incnode(nptr)) { + zfsess = (Zftp_session) nptr->dat; + if (!strcmp(zfsess->name, nm)) + break; + } + + if (!nptr) { + PERMALLOC { + zfsess = (Zftp_session) zcalloc(sizeof(struct zftp_session)); + zfsess->name = ztrdup(nm); + zfsess->cfd = zfsess->dfd = -1; + zfsess->params = (char **) zcalloc(sizeof(zfparams)); + addlinknode(zfsessions, zfsess); + + zfsesscnt++; + zfstatusp = (int *)zrealloc(zfstatusp, sizeof(int)*zfsesscnt); + zfstatusp[zfsessno] = 0; + } LASTALLOC; + } + + zfsetparam("ZFTP_SESSION", ztrdup(zfsess->name), ZFPM_READONLY); +} + +/* Save the existing session: this just means saving the parameters. */ + +static void +savesession() +{ + char **ps, **pd, *val; + + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) { + if (*pd) + zsfree(*pd); + if ((val = getsparam(*ps))) + *pd = ztrdup(val); + else + *pd = NULL; + } + *pd = NULL; +} + +/* + * Switch to session nm, creating it if necessary. + * Just call newsession, then set up the session-specific parameters. + */ + +/**/ +static void +switchsession(char *nm) +{ + char **ps, **pd; + + newsession(nm); + + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) { + if (*pd) { + /* Use the permanently allocated string for the parameter */ + zfsetparam(*ps, *pd, ZFPM_READONLY); + *pd = NULL; + } else + zfunsetparam(*ps); + } +} + +/**/ +static void +freesession(Zftp_session sptr) +{ + char **ps, **pd; + zsfree(sptr->name); + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) + if (*pd) + zsfree(*pd); + zfree(zfsess->params, sizeof(zfparams)); + if (sptr->userparams) + freearray(sptr->userparams); + zfree(sptr, sizeof(struct zftp_session)); +} + +/**/ +static int +zftp_session(char *name, char **args, int flags) +{ + if (!*args) { + LinkNode nptr; + + for (nptr = firstnode(zfsessions); nptr; incnode(nptr)) + printf("%s\n", ((Zftp_session)nptr->dat)->name); + return 0; + } + + /* + * Check if we are already in the required session: if so, + * it's a no-op, not an error. + */ + if (!strcmp(*args, zfsess->name)) + return 0; + + savesession(); + switchsession(*args); + return 0; +} + +/* Remove a session and free it */ + +/**/ +static int +zftp_rmsession(char *name, char **args, int flags) +{ + int no; + LinkNode nptr; + Zftp_session sptr = NULL; + char *newsess = NULL; + + /* Find the session in the list: either the current one, or by name */ + for (no = 0, nptr = firstnode(zfsessions); nptr; no++, nptr++) { + sptr = (Zftp_session) nptr->dat; + if ((!*args && sptr == zfsess) || + (*args && !strcmp(sptr->name, *args))) + break; + } + if (!nptr) + return 1; + + if (sptr == zfsess) { + /* Freeing current session: make sure it's closed */ + zfclosedata(); + zfclose(0); + + /* + * Choose new session to switch to if any: first in list + * excluding the one just freed. + */ + if (zfsesscnt > 1) { + LinkNode newn = firstnode(zfsessions); + if (newn == nptr) + incnode(newn); + newsess = ((Zftp_session)nptr->dat)->name; + } + } else { + Zftp_session oldsess = zfsess; + zfsess = sptr; + /* + * Freeing another session: don't need to switch, just + * tell zfclose() not to delete parameters etc. + */ + zfclosedata(); + zfclose(1); + zfsess = oldsess; + } + remnode(zfsessions, nptr); + freesession(sptr); + + /* + * Fix up array of status pointers. + */ + if (--zfsesscnt) { + /* + * Some remaining, so just shift up + */ + int *newstatusp = (int *)zalloc(sizeof(int)*zfsesscnt); + int *src, *dst, i; + for (i = 0, src = zfstatusp, dst = newstatusp; i < zfsesscnt; + i++, src++, dst++) { + if (i == no) + src++; + *dst = *src; + } + zfree(zfstatusp, sizeof(int)*(zfsesscnt+1)); + zfstatusp = newstatusp; + + /* + * Maybe we need to switch to one of the remaining sessions. + */ + if (newsess) + switchsession(newsess); + } else { + zfree(zfstatusp, sizeof(int)); + zfstatusp = NULL; + + /* + * We've just deleted the last session, so we need to + * start again from scratch. + */ + newsession("default"); + } + + return 0; } /* The builtin command frontend to the rest of the package */ @@ -2601,33 +2876,33 @@ bin_zftp(char *name, char **args, char *ops, int func) } strcat(fullname, cnam); - if (zfstatfd != -1) { + if (zfstatfd != -1 && !(zptr->flags & ZFTP_SESS)) { /* Get the status in case it was set by a forked process */ - int oldstatus = zfstatus; + int oldstatus = zfstatusp[zfsessno]; lseek(zfstatfd, 0, 0); - read(zfstatfd, &zfstatus, sizeof(zfstatus)); - if (zcfd != -1 && (zfstatus & ZFST_CLOS)) { + read(zfstatfd, zfstatusp, sizeof(int)*zfsesscnt); + if (zfsess->cfd != -1 && (zfstatusp[zfsessno] & ZFST_CLOS)) { /* got closed in subshell without us knowing */ zcfinish = 2; - zfclose(); + zfclose(0); } else { /* * fix up status types: unfortunately they may already * have been looked at between being changed in the subshell * and now, but we can't help that. */ - if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus)) + if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatusp[zfsessno])) zfsetparam("ZFTP_TYPE", - ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ? + ztrdup(ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI ? "A" : "I"), ZFPM_READONLY); - if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus)) + if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatusp[zfsessno])) zfsetparam("ZFTP_MODE", - ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ? + ztrdup(ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC ? "B" : "S"), ZFPM_READONLY); } } #if defined(HAVE_SELECT) || defined (HAVE_POLL) - if (zcfd != -1 && !(zptr->flags & ZFTP_TEST)) { + if (zfsess->cfd != -1 && !(zptr->flags & (ZFTP_TEST|ZFTP_SESS))) { /* * Test the connection for a bad fd or incoming message, but * only if the connection was last heard of open, and @@ -2637,7 +2912,7 @@ bin_zftp(char *name, char **args, char *ops, int func) ret = zftp_test("zftp test", NULL, 0); } #endif - if ((zptr->flags & ZFTP_CONN) && zcfd == -1) { + if ((zptr->flags & ZFTP_CONN) && zfsess->cfd == -1) { if (ret != 2) { /* * with ret == 2, we just got dumped out in the test, @@ -2686,12 +2961,15 @@ bin_zftp(char *name, char **args, char *ops, int func) if (zfdrrrring) { /* had a timeout, close the connection */ zcfinish = 2; /* don't try sending QUIT */ - zfclose(); + zfclose(0); } if (zfstatfd != -1) { - /* Set the status in case another process needs to know */ - lseek(zfstatfd, 0, 0); - write(zfstatfd, &zfstatus, sizeof(zfstatus)); + /* + * Set the status in case another process needs to know, + * but only for the active session. + */ + lseek(zfstatfd, zfsessno*sizeof(int), 0); + write(zfstatfd, zfstatusp+zfsessno, sizeof(int)); } return ret; } @@ -2719,7 +2997,13 @@ boot_zftp(Module m) zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET); /* default preferences if user deletes variable */ zfprefs = ZFPF_SNDP|ZFPF_PASV; + + PERMALLOC { + zfsessions = newlinklist(); + } LASTALLOC; + newsession("default"); } + return !ret; } @@ -2733,11 +3017,22 @@ cleanup_zftp(Module m) * There are various parameters hanging around, but they're * all non-special so are entirely non-life-threatening. */ - zfclosedata(); - zfclose(); + LinkNode nptr; + Zftp_session cursess = zfsess; + for (zfsessno = 0, nptr = firstnode(zfsessions); nptr; + zfsessno++, incnode(nptr)) { + zfsess = (Zftp_session)nptr->dat; + zfclosedata(); + /* + * When closing the current session, do the usual unsetting, + * otherwise don't. + */ + zfclose(zfsess != cursess); + } zsfree(lastmsg); - if (zfuserparams) - freearray(zfuserparams); + zfunsetparam("ZFTP_SESSION"); + freelinklist(zfsessions, (FreeFunc) freesession); + zfree(zfstatusp, sizeof(int)*zfsesscnt); deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); return 0; } -- cgit 1.4.1