about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Doc/Zsh/zftpsys.yo435
-rw-r--r--Doc/zshzftpsys.yo0
-rw-r--r--Functions/Zftp/README4
-rw-r--r--Functions/Zftp/zfanon70
-rw-r--r--Functions/Zftp/zfautocheck33
-rw-r--r--Functions/Zftp/zfcd52
-rw-r--r--Functions/Zftp/zfcd_match42
-rw-r--r--Functions/Zftp/zfcget87
-rw-r--r--Functions/Zftp/zfclose3
-rw-r--r--Functions/Zftp/zfcput76
-rw-r--r--Functions/Zftp/zfdir99
-rw-r--r--Functions/Zftp/zfgcp83
-rw-r--r--Functions/Zftp/zfget64
-rw-r--r--Functions/Zftp/zfget_match27
-rw-r--r--Functions/Zftp/zfhere5
-rw-r--r--Functions/Zftp/zfinit28
-rw-r--r--Functions/Zftp/zfls13
-rw-r--r--Functions/Zftp/zfopen42
-rw-r--r--Functions/Zftp/zfparams12
-rw-r--r--Functions/Zftp/zfpcp47
-rw-r--r--Functions/Zftp/zfput23
-rw-r--r--Functions/Zftp/zfrglob70
-rw-r--r--Functions/Zftp/zfrtime45
-rw-r--r--Functions/Zftp/zfstat89
-rw-r--r--Functions/Zftp/zftp_chpwd39
-rw-r--r--Functions/Zftp/zftp_progress18
-rw-r--r--Functions/Zftp/zftype30
-rw-r--r--Functions/Zftp/zfuget147
-rw-r--r--Functions/Zftp/zfuput115
29 files changed, 1798 insertions, 0 deletions
diff --git a/Doc/Zsh/zftpsys.yo b/Doc/Zsh/zftpsys.yo
new file mode 100644
index 000000000..f20a0a6c3
--- /dev/null
+++ b/Doc/Zsh/zftpsys.yo
@@ -0,0 +1,435 @@
+texinode(Zftp Function System)()(Completion System)(Top)
+chapter(Zftp Function System)
+cindex(zftp, function system)
+sect(Description)
+
+This describes the set of shell functions supplied with the source
+distribution as an interface to the tt(zftp) builtin command, allowing you
+to perform FTP operations from the shell command line or within functions
+or scripts.  The interface is similar to a traditional FTP client (e.g. the
+manref(ftp)(1) command itself), but as it is entirely done within the shell
+all the familar completion, editing and globbing features, and so on, are
+present, and macros are particularly simple to write as they are just
+ordinary shell functions.
+
+The prerequisite is that the tt(zftp) command, as described in
+ifzman(\
+zmanref(zshmodules)
+)\
+ifnzman(\
+noderef(The zftp Module)
+), must be available in the
+version of tt(zsh) installed at your site.  If the shell is configured to
+load new commands at run time, it probably is: typing tt(zmodload zftp)
+will make sure (if that runs silently, it has worked).  If this is not the
+case, it is possible tt(zftp) was linked into the shell anyway: to test
+this, type tt(which zftp) and if tt(zftp) is available you will get the
+message tt(zftp: shell built-in command).
+
+Commands given directly with tt(zftp) builtin may be interspersed between
+the functions in this suite; in a few cases, using tt(zftp) directly may
+cause some of the status information stored in shell parameters to become
+invalid.  Note in particular the description of the variables
+tt($ZFTP_TMOUT), tt($ZFTP_PREFS) and tt($ZFTP_VERBOSE) for tt(zftp).
+
+startmenu()
+menu(Installation)
+menu(Zftp Functions)
+menu(Miscellaneous Features)
+endmenu()
+
+texinode(Installation)(Zftp Functions)()(Zftp Function System)
+sect(Installation)
+
+You should make sure all the functions from the tt(Functions/Zftp)
+directory of the source distribution are available; they all begin with the
+two letters tt(zf).  They may already have been installed on your system;
+otherwise, you will need to find them and copy them.  The directory should
+appear as one of the elements of the tt($fpath) array, and the functions
+should be autoloaded.  Finally, to initialise the use of the system you
+need to call the tt(zfinit) function.  The following code in your
+tt(.zshrc) will arrange for this; assume the functions are stored in the
+directory tt(~/myfns):
+
+tt(indent(
+nofill(fpath=(~/myfns $fpath))
+nofill(autoload ~/myfns/zf*(:t))
+nofill(zfinit)
+))
+
+Note that tt(zfinit) assumes you are using the tt(zmodload) method to
+load the tt(zftp) command.  If it is already built into the shell, change
+tt(zfinit) to tt(zfinit -n).
+
+texinode(Zftp Functions)(Miscellaneous Features)(Installation)(Zftp Function System)
+sect(Functions)
+
+The sequence of operations in performing a file transfer is essentially the
+same as that in a standard FTP client.
+
+subsect(Opening a connection)
+startitem()
+item(tt(zfparams [ var(host) [ var(user) [ var(password) ... ] ] ]))(
+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.
+)
+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
+given as `tt(?)' it will be prompted for.  If var(host) is not present, use
+a previously stored set of parameters.
+
+If the command was successful, and the terminal is an tt(xterm), a summary
+will appear in the title bar, giving the local tt(host:directory) and the
+remote tt(host:directory); this is handled by the function tt(zftp_chpwd),
+described below.
+
+Normally, the var(host), var(user) and var(password) are internally
+recorded for later re-opening, either by a tt(zfopen) with no arguments, or
+automatically (see below).  With the option tt(-1), no information is
+stored.
+)
+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
+generated from var(user)tt(@)tt(host); this is then stored in the shell
+parameter tt($EMAIL_ADDR) which can alternatively be set manually to a
+suitable string.
+)
+enditem()
+
+subsect(Directory management)
+startitem()
+xitem(tt(zfcd [ var(dir) ]))
+xitem(tt(zfcd -))
+item(tt(zfcd var(old) var(new)))(
+Change the current directory on the remote server:  this is implemented to
+have many of the features of the shell builtin tt(cd).
+
+In the first form with var(dir) present, change to the directory var(dir).
+The command tt(zfcd ..) is treated specially, so is guaranteed to work on
+non-UNIX servers (note this is handled internall by tt(zftp)).  If var(dir)
+is omitted, has the effect of tt(zfcd ~).
+
+The second form changes to the directory previously current.
+
+The third form attempts to change the current directory by replacing the
+first occurrence of the string var(old) with the string var(new) in the
+current directory.
+
+Note that in this command, and indeed anywhere a remote filename is
+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 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.
+)
+item(tt(zfhere))(
+Change directory on the remote server to the one corresponding to the
+current local directory, with special handling of tt(~) as in tt(zfcd).
+For example, if the current local directory is tt(~/foo/bar), then
+tt(zfhere) performs the effect of tt(zfcd ~/foo/bar).
+)
+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.
+
+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
+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.
+)
+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
+are passed directory to the server.  No pager and no caching is used.
+)
+enditem()
+
+subsect(Status commands)
+startitem()
+item(tt(zftype) [ var(type) ])(
+With no arguments, show the type of data to be transferred, usually ASCII
+or binary.  With an argument, change the type: the types tt(A) or
+tt(ASCII) for ASCII data and tt(B) or tt(BINARY), tt(I) or tt(IMAGE) for
+binary data are understood case-insensitively.
+)
+item(tt(zfstat) [ -v ])(
+Show the status of the current or last connection, as well as the status of
+some of tt(zftp)'s status variables.  With the tt(-v) option, a more
+verbose listing is produced by querying the server for its version of
+events, too.
+)
+enditem()
+
+subsect(Retrieving files)
+The commands for retrieving files all take at least two options. tt(-G)
+suppresses remote filename expansion which would otherwise be performed
+(see below for a more detailed description of that).  tt(-t) attempts
+to set the modification time of the local file to that of the remote file:
+this requires version 5 of tt(perl), see the description of the function
+tt(zfrtime) below for more information.
+
+startitem()
+item(tt(zfget [ -Gt ] var(file1) ...))(
+Retrieve all the listed files var(file1) ... one at a time from the remote
+server.  If a file contains a `tt(/)', the full name is passed to the
+remote server, but the file is stored locally under the name given by the
+part after the final `tt(/)'.
+)
+item(tt(zfuget [ -Gvst ] var(file1) ...))(
+As tt(zfget), but only retrieve files where the version on the remote
+server is newer (has a later modification time), or where the local file
+does not exist.  If the remote file is older but the files have different
+sizes, or if the sizes are the same but the remote file is newer, the user
+will usually be queried.  With the option tt(-s), the command runs silently
+and will always retrieve the file in either of those two cases.  With the
+option tt(-v), the command prints more information about the files while it
+is working out whether or not to transfer them.
+)
+item(tt(zfcget [ -Gt ] var(file1) ...))(
+As tt(zfget), but if any of the local files exists, and is shorter than
+the corresponding remote file, the command assumes that it is the result of
+a partially completed transfer and attempts to transfer the rest of the
+file.  This is useful on a poor connection which keeps failing.
+
+Note that this requires a commonly implemented, but non-standard, version
+of the FTP protocol, so is not guaranteed to work on all servers.
+)
+xitem(tt(zfgcp [ -Gt ] var(remote-file) var(local-file)))
+item(tt(zfgcp [ -Gt ] var(rfile1) ... var(ldir)))(
+This retrieves files from the remote server with arguments behaving
+similarly to the tt(cp) command.
+
+In the first form, copy var(remote-file) from the server to the local file
+var(local-file).
+
+In the second form, copy all the remote files var(rfile1) ... into the
+local directory var(ldir) retaining the same basenames.  This assumes UNIX
+directory semantics.
+)
+enditem()
+
+subsect(Sending files)
+startitem()
+item(tt(zfput var(file1) ...))(
+Send all the var(file1) ... given separately to the remote server.  If a
+filename contains a `tt(/)', the full filename is used locally to find the
+file, but only the basename is used for the remote file name.
+)
+item(tt(zfuput [ -vs ] var(file1) ...))(
+As tt(zfput), but only send files which are newer than their local
+equivalents, or if the remote file does not exist.  The logic is the same
+as for tt(zfuget), but reversed between local and remote files.
+)
+item(tt(zfcput var(file1) ...))(
+As tt(zfput), but if any remote file already exists and is shorter than the
+local equivalent, assume it is the result of an incomplete transfer and
+send the rest of the file to append to the existing part.  As the FTP
+append command is part of the standard set, this is in principle more
+likely to work than tt(zfcget).
+)
+xitem(tt(zfpcp var(local-file) var(remote-file)))
+item(tt(zfpcp var(lfile1) ... var(rdir)))(
+This sends files to the remote server with arguments behaving similarly to
+the tt(cp) command.
+
+With two arguments, copy var(local-file) to the server as
+var(remote-file).
+
+With more than two arguments, copy all the local files var(lfile1) ... into
+the existing remote directory var(rdir) retaining the same basenames.  This
+assumes UNIX directory semantics.
+
+A problem arises if you attempt to use tt(zfpcp) var(lfile1) var(rdir),
+i.e. the second form of copying but with two arguments, as the command has
+no simple way of knowing if var(rdir) corresponds to a directory or a
+filename.  It attempts to resolve this in various ways.  First, if the
+var(rdir) argument is tt(.) or tt(..) or ends in a slash, it is assumed to
+be a directory.  Secondly, if the operation of copying to a remote file in
+the first form failed, and the remote server sends back the expected
+failure code 553 and a reply including the string `tt(Is a directory)',
+then tt(zfpcp) will retry using the second form.
+)
+enditem()
+
+subsect(Closing the connectino)
+startitem()
+item(tt(zfclose))(
+Close the connection.
+)
+enditem()
+
+subsect(Other functions)
+Mostly, these functions will not be called directly (apart from
+tt(zfinit)), but are described here for completeness.  You may wish to
+alter tt(zftp_chpwd) and tt(zftp_progress), in particular.
+
+startitem()
+item(tt(zfinit [ -n ]))(
+As decribed above, this is used to initialise the zftp function system.
+The tt(-n) option should be used if the zftp command is already built into
+the shell.
+)
+item(tt(zfautocheck [ -dn ]))(
+This function is called to implement automatic reopening behaviour, as
+described in more detail below.  The options must appear in the first
+argument; tt(-n) prevents the command from changing to the old directory,
+while tt(-d) prevents it from setting the variable tt(do_close), which it
+otherwise does as a flag for automatically closing the connection after a
+transfer.  The host and directory for the last session are stored in the
+variable tt($zflastsession), but the internal host/user/password parameters
+must also be correctly set.
+)
+item(tt(zfcd_match var(prefix) var(suffix)))(
+This performs matching for completion of remote directory names.  If the
+remote server is UNIX, it will attempt to persuade the server to list the
+remote directory with subdirectories marked, which usually works but is not
+guaranteed.  On other hosts it simply calls tt(zfget_match) and hence
+completes all files, not just directories.  On some systems, directories
+may not even look like filenames.
+)
+item(tt(zfget_match var(prefix) var(suffix)))(
+This performs matching for completion of remote filenames.  It caches files
+for the current directory (only) in the shell parameter tt($zftp_fcache).
+It is in the form to be called by the tt(-K) option of tt(compctl), but
+also works when called from a widget-style completion function with
+var(prefix) and var(suffix) set appropriately.
+)
+item(tt(zfrglob var(varname)))(
+Perform remote globbing, as describes in more detail below.  var(varname)
+is the name of a variable containing the pattern to be expanded; if there
+were any matches, the same variable will be set to the exanded set of
+filenames on return.
+)
+item(tt(zfrtime var(lfile) var(rfile) [ var(time) ]))(
+Set the local file var(lfile) to have the same modification time as the
+remote file var(rfile), or the explicit time var(time) in FTP format
+tt(CCYYMMDDhhmmSS) for the GMT timezone.
+
+Currently this requires tt(perl) version 5 to perform the conversion from
+GMT to local time.  This is unfortunately difficult to do using shell code
+alone.
+)
+item(tt(zftp_chpwd))(
+This function is called every time a connection is opened, or closed, or
+the remote directory changes.  This version alters the title bar of an
+tt(xterm) or tt(sun-cmd) terminal emulator to reflect the local and remote
+hostnames and current directories.  It works best when combined with the
+function tt(chpwd).  In particular, a function of the form
+
+tt(indent(
+nofill(chpwd() {)
+nofill(  if [[ -n $ZFTP_USER ]]; then)
+nofill(    zftp_chpwd)
+nofill(  else)
+nofill(    # usual chpwd e.g put host:directory in title bar)
+nofill(  fi)
+nofill(})
+))
+
+fits in well.
+)
+item(tt(zftp_progress))(
+This function shows the status of the transfer as the percentage of the
+total so far transferred.  It will not write anything unless the output is
+going to a terminal; however, if you transfer files in the background, you
+should tt(unfunction) this first.  (Background file transfers don't work on
+all OSes.)  Note also that if you alter it, any output em(must) be to
+standard error, as standard output may be a file being received.
+)
+enditem()
+
+texinode(Miscellaneous Features)()(Zftp Functions)(Zftp Function System)
+sect(Miscellaneous Features)
+
+subsect(Remote globbing)
+
+The commands for retrieving files usually perform filename expansion
+(globbing) on their arguments; this can be turned off by passing the option
+tt(-G) to each of the commands.  Normally this operates by retrieving a
+complete list of files for the directory in question, then matching these
+locally against the pattern supplied.  This has the advantage that the full
+range of zsh patterns (respecting the setting of the option
+tt(EXTENDED_GLOB)) can be used.  However, it means that the directory part
+of a filename will not be expanded and must be given exactly.  If the
+remote server does not support the UNIX directory semantics, directory
+handling is problematic and it is recommended that globbing only be used
+within the current directory.  The list of files in the current directory,
+if retrieved, will be cached, so that subsequent globs in the same
+directory without an interventing tt(zfcd) are fast.
+
+If the variable tt($zfrglob) is set to a non-zero length, globbing is
+instead performed on the remote host:  the server is asked for a list of
+matching files.  This is highly dependent on how the server is implemented,
+though typically UNIX servers will provide support for basic glob
+patterns.  This may in some cases be faster, as it avoids retrieving the
+entire list of directory contents.
+
+subsect(Automatic and temporary reopening)
+
+As described for the tt(zfopen) command, a subsequent tt(zfopen) with no
+parameters will reopen the connection to the last host (this includes
+connections made with the tt(zfanon) command).  Opened in this fashion, the
+connection starts in the default remote directory and will remain open
+until explicitly closed.
+
+Automatic re-opening is also available.  If a connection is not currently
+open and a command requiring a connection is given, the last connection is
+implicitly reopened.  In this case the directory which was current when the
+connection was closed again becomes the current directory (unless, of
+course, the command given changes it).  Automatic reopening will also take
+place if the connection was close by the remote server for whatever reason
+(e.g. a timeout).  It is not available if the tt(-1) option to tt(zfopen)
+or tt(zfanon) was used.
+
+Furthermore, if the command issued is a file transfer, the connection will
+be closed after the transfer is finished, hence providing a one-shot mode
+for transfers.  This does not apply to directory changing or listing
+commands; for example a tt(zfdir) may reopen a connection but will leave it
+open.  Also, automatic closure will only ever happen in the same command as
+automatic opening, i.e a tt(zfdir) directly followed by a tt(zfget) will
+never close the connection automatically.
+
+Information about the previous connection is given by the tt(zfstat)
+function.  So, for example, if that reports:
+
+tt(indent(
+nofill(Not connected.)
+nofill(Last session:   ftp.bar.com:/pub/textfiles)
+))
+
+then the command tt(zfget file.txt) will attempt to reopen a connection to
+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.
+
+subsect(Completion)
+
+Completion of remote files and directories 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 should automatically be
+available.
diff --git a/Doc/zshzftpsys.yo b/Doc/zshzftpsys.yo
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Doc/zshzftpsys.yo
diff --git a/Functions/Zftp/README b/Functions/Zftp/README
new file mode 100644
index 000000000..794bff292
--- /dev/null
+++ b/Functions/Zftp/README
@@ -0,0 +1,4 @@
+This directory contains a set of functions acting as a front end to the
+zftp command, provided as an add-on module.  They allow you to perform FTP
+tasks from within the shell using as many of the shell's own facilities
+as possible.  For more information, see the zshzftpsys manual page.
diff --git a/Functions/Zftp/zfanon b/Functions/Zftp/zfanon
new file mode 100644
index 000000000..d8a9d06a3
--- /dev/null
+++ b/Functions/Zftp/zfanon
@@ -0,0 +1,70 @@
+# function zfanon {
+
+emulate -L zsh
+
+local opt optlist once
+
+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
+done
+
+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'.
+  local domain host
+  # First, maybe we've already got it.  Zen-like.
+  if [[ $HOST = *.* ]]; then
+    # assume this is the full host name
+    host=$HOST
+  elif [[ -f /etc/resolv.conf ]]; then
+    # Next, maybe we've got resolv.conf.
+    domain=$(awk '/domain/ { print $2 }' /etc/resolv.conf)
+    [[ -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 }')
+  if [[ -z $host ]]; then
+    # we're running out of ideas, but this should work.
+    # after all, i wrote it...
+    # don't want user to know about this, too embarrassed.
+    local oldvb=$ZFTP_VERBOSE oldtm=$ZFTP_TMOUT
+    ZFTP_VERBOSE=
+    ZFTP_TMOUT=5
+    if zftp open $host >& /dev/null; then
+      host=$ZFTP_HOST
+      zftp close $host
+    fi
+    ZFTP_VERBOSE=$oldvb
+    ZFTP_TMOUT=$oldtm
+  fi
+  if [[ -z $host ]]; then
+    print "Can't get your hostname.  Define \$EMAIL_ADDR by hand."
+    return 1;
+  fi
+  EMAIL_ADDR="$USER@$host"
+  print "Using $EMAIL_ADDR as anonymous FTP password."
+fi
+
+if [[ $once = 1 ]]; then
+  zftp open $1 anonymous $EMAIL_ADDR
+else
+  zftp params $1 anonymous $EMAIL_ADDR
+  zftp open
+fi
+# }
diff --git a/Functions/Zftp/zfautocheck b/Functions/Zftp/zfautocheck
new file mode 100644
index 000000000..abb994061
--- /dev/null
+++ b/Functions/Zftp/zfautocheck
@@ -0,0 +1,33 @@
+# function zfautocheck {
+# This function is used to implement auto-open behaviour.
+#
+# With first argument including n, don't change to the old directory; else do.
+#
+# Set do_close to 1 if the connection was not previously open, 0 otherwise
+# With first arguemnt including d, don't set do_close to 1.  Broadly
+# speaking, we use this mechanism to shut the connection after use
+# if the connection had been explicitly closed (i.e. didn't time out,
+# which zftp test investigates) and we are not using a directory
+# command, which implies we are looking for something so should stay open
+# for it.
+
+# Remember the old session:  zflastsession will be overwritten by
+# a successful open.
+local lastsession=$zflastsession
+
+if [[ -z $ZFTP_HOST ]]; then
+  zfopen || return 1
+  [[ $1 = *d* ]] || do_close=1
+elif zftp test 2>/dev/null; then
+  return 0
+else
+  zfopen || return 1
+fi
+
+if [[ $1 = *n* ]]; then
+  return 0
+elif [[ -n $lastsession && $ZFTP_HOST = ${lastsession%%:*} ]]; then
+  zfcd ${lastsession#*:}
+fi
+
+# }
diff --git a/Functions/Zftp/zfcd b/Functions/Zftp/zfcd
new file mode 100644
index 000000000..b726d9f55
--- /dev/null
+++ b/Functions/Zftp/zfcd
@@ -0,0 +1,52 @@
+# function zfcd {
+# zfcd:  change directory on the remote server.
+#
+#  Currently has the following features:
+# --- an initial string matching $HOME in the directory is turned back into ~
+#     to be re-interpreted by the remote server.
+# --- zfcd with no arguments changes directory to '~'
+# --- `zfcd old new' and `zfcd -' work analagously to cd
+# --- if the connection is not currently open, it will try to
+#     re-open it with the stored parameters as set by zfopen.
+#     If the connection timed out, however, it won't know until
+#     too late.  In that case, just try the same zfcd command again
+#     (but now `zfcd -' and `zfcd old new' won't work).
+
+# hack: if directory begins with $HOME, turn it back into ~
+# there are two reasons for this:
+#   first, a ~ on the command line gets expanded even with noglob.
+#     (I suppose this is correct, but I wouldn't like to swear to it.)
+#   second, we can no do 'zfcd $PWD' and the like, and that will
+#     work just as long as the directory structures under the home match.
+
+emulate -L zsh
+
+if [[ $1 = /* ]]; then
+  zfautocheck -dn
+else
+  zfautocheck -d
+fi
+
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+  1="~${1#$HOME}"
+fi
+
+if (( $# == 0 )); then
+  # Emulate `cd' behaviour
+  set -- '~'
+elif [[ $# -eq 1 && $1 = - ]]; then
+  # Emulate `cd -' behaviour.
+  set -- $zflastdir
+elif [[ $# -eq 2 ]]; then
+  # Emulate `cd old new' behaviour.
+  # We have to find a character not in $1 or $2; ! is a good bet.
+  eval set -- "\${ZFTP_PWD:s!$1!$2!}"
+fi
+
+# We have to remember the current directory before changing it
+# if we want to keep it.
+local lastdir=$ZFTP_PWD
+
+zftp cd "$@"  &&  zflastdir=$lastdir
+print $zflastsession
+# }
diff --git a/Functions/Zftp/zfcd_match b/Functions/Zftp/zfcd_match
new file mode 100644
index 000000000..67e719888
--- /dev/null
+++ b/Functions/Zftp/zfcd_match
@@ -0,0 +1,42 @@
+# function zfcd_match {
+
+emulate -L zsh
+
+# see zfcd for details of this hack
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+  1="~${1#$HOME}"
+fi
+
+# error messages only
+local ZFTP_VERBOSE=45
+# should we redirect 2>/dev/null or let the user see it?
+
+local tmpf=${TMPPREFIX}zfcm$$
+
+if [[ $ZFTP_SYSTEM = UNIX* ]]; then
+  # hoo, aren't we lucky: this makes things so much easier
+  setopt localoptions rcexpandparam
+  local dir
+  if [[ $1 = ?*/* ]]; then
+    dir=${1%/*}
+  elif [[ $1 = /* ]]; then
+    dir=/
+  fi
+  # 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
+  reply=($(awk '/\/$/ { print substr($1, 0, length($1)-1) }' $tmpf))
+  rm -f $tmpf
+  if [[ $dir = / ]]; then
+    reply=(${dir}$reply)
+  elif [[ -n $dir ]]; then
+    reply=($dir/$reply)
+  fi
+else
+  # I simply don't know what to do here.
+  # Just use the list of files for the current directory.
+  zfget_match $*
+fi
+
+# }
diff --git a/Functions/Zftp/zfcget b/Functions/Zftp/zfcget
new file mode 100644
index 000000000..fd6accfed
--- /dev/null
+++ b/Functions/Zftp/zfcget
@@ -0,0 +1,87 @@
+# function zfcget {
+# Continuation get of files from remote server.
+# For each file, if it's shorter here, try to get the remainder from
+# over there.  This requires the server to support the REST command
+# in the way many do but RFC959 doesn't specify.
+# Options:
+#   -G   don't to remote globbing, else do
+#   -t   update the local file times to the same time as the remote.
+#        Currently this only works if you have the `perl' command,
+#        and that perl is version 5 with the standard library.
+#        See the function zfrtime for more gory details.
+
+emulate -L zsh
+
+local loc rem stat=0 optlist opt nglob remlist locst remst
+local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time
+
+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
+done
+
+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
+    zfrglob remlist
+  fi
+  if (( $#remlist )); then
+    for rem in $remlist; do
+      loc=${rem:t}
+      if [[ ! -f $loc ]]; then
+	# File does not yet exist
+	zftp get $rem >$loc || stat=$?
+      else
+	# Compare the sizes.
+	locst=($(zftp local $loc))
+	zftp remote $rem >$tmpfile
+	rstat=$?
+	remst=($(<$tmpfile))
+	rm -f $tmpfile
+	if [[ $rstat = 2 ]]; then
+	  print "Server does not support SIZE command.\n" \
+	  "Assuming you know what you're doing..." 2>&1
+	  zftp getat $rem $locst[1] >>$loc || stat=$?
+	  continue
+	elif [[ $rstat = 1 ]]; then
+	  print "Remote file not found: $rem" 2>&1
+	  continue
+	fi
+	if [[ $locst[1] -gt $remst[1] ]]; then
+	  print "Local file is larger!" 2>&1
+	  continue;
+	elif [[ $locst[1] == $remst[1] ]]; then
+	  print "Files are already the same size." 2>&1
+	  continue
+	else
+	  if zftp getat $rem $locst[1] >>$loc; then
+	    [[ $time = 1 ]] && zfrtime $loc $rem $remst[2]
+	  else
+	    stat=1
+	  fi
+	fi
+      fi
+    done
+  fi
+done
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfclose b/Functions/Zftp/zfclose
new file mode 100644
index 000000000..fb49efd51
--- /dev/null
+++ b/Functions/Zftp/zfclose
@@ -0,0 +1,3 @@
+# function zfclose {
+zftp close
+# }
diff --git a/Functions/Zftp/zfcput b/Functions/Zftp/zfcput
new file mode 100644
index 000000000..fad5c3f86
--- /dev/null
+++ b/Functions/Zftp/zfcput
@@ -0,0 +1,76 @@
+# function zfcput {
+# Continuation put of files from remote server.
+# For each file, if it's shorter over there, put the remainder from
+# over here.  This uses append, which is standard, so unlike zfcget it's
+# expected to work on any reasonable server... err, as long as it
+# supports SIZE and MDTM.  (It could be enhanced so you can enter the
+# size so far by hand.)  You should probably be in binary transfer
+# mode, thought it's not enforced.
+#
+# To read from midway through a local file, `tail +<n>c' is used.
+# It would be nice to find a way of doing this which works on all OS's.
+
+emulate -L zsh
+
+local loc rem stat=0 locst remst offs tailtype
+local tmpfile=${TMPPREFIX}zfcget$$ rstat
+
+# find how tail works.  this is intensely annoying, since it's completely
+# standard in C.  od's no use, since we can only skip whole blocks.
+if [[ $(echo abcd | tail +2c) = bcd ]]; then
+  tailtype=c
+elif [[ $(echo abcd | tail --bytes=+2) = bcd ]]; then
+  tailtype=b
+else
+  print "I can't get your \`tail' to start from from arbitrary characters.\n" \
+  "If you know how to do this, let me know." 2>&1
+  return 1
+fi
+
+for loc in $*; do
+  # zfcd directory hack to put the front back to ~
+  rem=$loc
+  if [[ $rem = $HOME || $rem = $HOME/* ]]; then
+    rem="~${rem#$HOME}"
+  fi
+  if [[ ! -r $loc ]]; then
+    print "Can't read file $loc"
+    stat=1
+  else
+    # Compare the sizes.
+    locst=($(zftp local $loc))
+    zftp remote $rem >$tmpfile
+    rstat=$?
+    remst=($(<$tmpfile))
+    rm -f $tmpfile
+    if [[ $rstat = 2 ]]; then
+      print "Server does not support remote status commands.\n" \
+      "You will have to find out the size by hand and use zftp append." 2>&1
+      stat=1
+      continue
+    elif [[ $rstat = 1 ]]; then
+      # Not found, so just do a standard put.
+      zftp put $rem <$loc
+    elif [[ $remst[1] -gt $locst[1] ]]; then
+      print "Remote file is larger!" 2>&1
+      continue;
+    elif [[ $locst[1] == $remst[1] ]]; then
+      print "Files are already the same size." 2>&1
+      continue
+    else
+      # tail +<N>c takes the count of the character
+      # to start from, not the offset from zero. if we did
+      # this with years, then 2000 would be 1999.  no y2k bug!
+      # brilliant.
+      (( offs = $remst[1] + 1 ))
+      if [[ $tailtype = c ]]; then
+	tail +${offs}c $loc | zftp append $rem || stat=1
+      else
+	tail --bytes=+$offs $loc | zftp append $rem || stat=1
+      fi
+    fi
+  fi
+done
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfdir b/Functions/Zftp/zfdir
new file mode 100644
index 000000000..55befe000
--- /dev/null
+++ b/Functions/Zftp/zfdir
@@ -0,0 +1,99 @@
+# function zfdir {
+# Long directory of remote server.
+# The remote directory is cached.  In fact, two caches are kept:
+# one of the standard listing of the current directory, i.e. zfdir
+# with no arguments, and another for everything else.
+# To access the appropriate cache, just use zfdir with the same
+# arguments as previously.  zfdir -r will also re-use the `everything
+# else' cache; you can always reuse the current directory cache just
+# with zfdir on its own.
+#
+# The current directory cache is emptied when the directory changes;
+# the other is kept until a new zfdir with a non-empty argument list.
+# Both are removed when the connection is closed.
+#
+# zfdir -f will force the existing cache to be ignored, e.g. if you know
+#          or suspect the directory has changed.
+# zfdir -d will remove both caches without listing anything.
+# If you need to pass -r, -f or -d to the dir itself, use zfdir -- -d etc.;
+# unrecognised options are passed through to dir, but zfdir options must
+# appear first and unmixed with the others.
+
+emulate -L zsh
+setopt extendedglob
+
+local file opt optlist redir i newargs force
+
+while [[ $1 = -* ]]; do
+  if [[ $1 = - || $1 = -- ]]; then
+    shift;
+    break;
+  elif [[ $1 != -[rfd]## ]]; then
+    # pass options through to ls
+    break;
+  fi
+  optlist=${1#-}
+  for (( i = 1; i <= $#optlist; i++)); do
+    opt=$optlist[$i]
+    case $optlist[$i] in
+      r) redir=1
+	 ;;
+      f) force=1
+	 ;;
+      d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+	 [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+	 zftp_fcache=()
+	 return 0
+	 ;;
+    esac
+  done
+  shift
+done
+
+zfautocheck -d
+
+# directory hack, see zfcd
+for (( i = 1; i <= $#argv; i++ )); do
+  if [[ $argv[$i] = $HOME || $argv[$i] = $HOME/* ]]; then
+    argv[$i]="~${argv[$i]#$HOME}"
+  fi
+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
+else
+  # Last directly looked at was not the current one, or at least
+  # had non-standard arguments.
+  [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$
+  file=$zfotherdir
+  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
+  fi
+  zfotherargs=$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=()
+fi
+
+if [[ -n $file && -f $file ]]; then
+  eval ${PAGER:-more} \$file
+else
+  if (zftp test); then
+    # Works OK in subshells
+    zftp dir $* | tee $file | eval ${PAGER-:more}
+  else
+    # Doesn't work in subshells (IRIX 6.2 --- why?)
+    zftp dir $* >$file
+    eval ${PAGER-:more} $file
+  fi
+fi
+# }
diff --git a/Functions/Zftp/zfgcp b/Functions/Zftp/zfgcp
new file mode 100644
index 000000000..26a08697d
--- /dev/null
+++ b/Functions/Zftp/zfgcp
@@ -0,0 +1,83 @@
+# function zfgcp {
+# ZFTP get as copy:  i.e. first arguments are remote, last is local.
+# Supposed to work exactly like a normal copy otherwise, i.e.
+#  zfgcp rfile lfile
+# or
+#  zfgcp rfile1 rfile2 rfile3 ... ldir
+# Options:
+#   -G   don't to remote globbing, else do
+#   -t   update the local file times to the same time as the remote.
+#        Currently this only works if you have the `perl' command,
+#        and that perl is version 5 with the standard library.
+#        See the function zfrtime for more gory details.
+#
+# If there is no current connection, try to use the existing set of open
+# parameters to establish one and close it immediately afterwards.
+
+emulate -L zsh
+
+local opt optlist nglob remlist rem loc time
+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
+done
+
+zfautocheck
+
+# hmm, we should really check this after expanding the glob,
+# but we shouldn't expand the last argument remotely anyway.
+if [[ $# -gt 2 && ! -d $argv[-1] ]]; then
+  print "zfgcp:  last argument must be a directory." 2>&1
+  return 1
+elif [[ $# == 1 ]]; then
+  print "zfgcp:  not enough arguments." 2>&1
+  return 1
+fi
+
+if [[ -d $argv[-1] ]]; then
+  local dir=$argv[-1]
+  argv[-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
+      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
+	else
+	  stat=1
+	fi
+      done
+    fi
+  done
+else
+  zftp get $1 >$2 || stat=$?
+fi
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfget b/Functions/Zftp/zfget
new file mode 100644
index 000000000..878a36346
--- /dev/null
+++ b/Functions/Zftp/zfget
@@ -0,0 +1,64 @@
+# function zfget {
+# Get files from remote server.  Options:
+#   -G   don't to remote globbing, else do
+#   -t   update the local file times to the same time as the remote.
+#        Currently this only works if you have the `perl' command,
+#        and that perl is version 5 with the standard library.
+#        See the function zfrtime for more gory details.
+#
+# If the connection is not currently open, try to open it with the current
+# parameters (set by a previous zfopen or zfparams), then close it after
+# use.  The file is put in the current directory (i.e. using the basename
+# of the remote file only); for more control, use zfgcp.
+
+emulate -L zsh
+
+local loc rem optlist opt nglob remlist time
+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
+done
+
+zfautocheck
+
+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
+    zfrglob remlist
+  fi
+  if (( $#remlist )); then
+    for rem in $remlist; do
+      loc=${rem:t}
+      if zftp get $rem >$loc; then
+	[[ $time = 1 ]] && zfrtime $rem $loc
+      else
+	stat=1
+      fi
+    done
+  fi
+done
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfget_match b/Functions/Zftp/zfget_match
new file mode 100644
index 000000000..677108ede
--- /dev/null
+++ b/Functions/Zftp/zfget_match
@@ -0,0 +1,27 @@
+# function zfget_match {
+
+emulate -L zsh
+
+# the zfcd hack:  this may not be necessary here
+if [[ $1 == $HOME || $1 == $HOME/* ]]; then
+  1="~${1#$HOME}"
+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))
+    rm -f $tmpf
+  fi
+  reply=($zftp_fcache);
+fi
+# }
diff --git a/Functions/Zftp/zfhere b/Functions/Zftp/zfhere
new file mode 100644
index 000000000..43e599d3a
--- /dev/null
+++ b/Functions/Zftp/zfhere
@@ -0,0 +1,5 @@
+# function zfhere {
+# Change to the directory corresponding to $PWD on the server.
+# See zfcd for how this works.
+zfcd $PWD
+# }
diff --git a/Functions/Zftp/zfinit b/Functions/Zftp/zfinit
new file mode 100644
index 000000000..be827c6ac
--- /dev/null
+++ b/Functions/Zftp/zfinit
@@ -0,0 +1,28 @@
+[[ $1 = -n ]] || zmodload -ia zftp
+
+alias zfcd='noglob zfcd'
+alias zfget='noglob zfget'
+alias zfls='noglob zfls'
+alias zfdir='noglob zfdir'
+alias zfuget='noglob zfuget'
+
+# only way of getting that noglob out of the way: this is unnecessary with
+# widget-based completion and can be commented out.
+setopt completealiases
+
+#
+# zftp completions: only use these if new-style completion is not
+# active.
+#
+if [[ ${#patcomps} -eq 0 || ${patcomps[(i)zf*]} -gt ${#patcomps} ]]; then
+  compctl -f -x 'p[1]' \
+    -k '(open params user login type ascii binary mode put putat
+    get getat append appendat ls dir local remote mkdir rmdir delete
+    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
+  compctl -K zfcd_match -S/ -q zfcd zfdir zfls
+  compctl -K zfget_match zfget zfgcp zfuget zfcget
+  compctl -k hosts zfanon zfopen zfparams
+fi
diff --git a/Functions/Zftp/zfls b/Functions/Zftp/zfls
new file mode 100644
index 000000000..e8d3cfb28
--- /dev/null
+++ b/Functions/Zftp/zfls
@@ -0,0 +1,13 @@
+# function zfls {
+
+emulate -L zsh
+
+# directory hack, see zfcd
+if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+  1="~${1#$HOME}"
+fi
+
+zfautocheck -d
+
+zftp ls $*
+# }
diff --git a/Functions/Zftp/zfopen b/Functions/Zftp/zfopen
new file mode 100644
index 000000000..fa9b4f81d
--- /dev/null
+++ b/Functions/Zftp/zfopen
@@ -0,0 +1,42 @@
+# function zfopen {
+# Use zftp params to set parameters for open, rather than sending
+# them straight to open.  That way they are stored for a future open
+# command.
+#
+# With option -1 (just this 1ce), don't do that.
+
+emulate -L zsh
+
+local optlist opt once
+
+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
+done
+
+# This is where we should try and do same name-lookupage in
+# both .netrc and .ncftp/bookmarks .  We could even try saving
+# the info in their for new hosts, like ncftp does.
+
+if [[ $once = 1 ]]; then
+  zftp open $*
+else
+  # set parameters, but only if there was at least a host
+  (( $# > 0 )) && zfparams $*
+  # now call with no parameters
+  zftp open
+fi
+# }
diff --git a/Functions/Zftp/zfparams b/Functions/Zftp/zfparams
new file mode 100644
index 000000000..5c5262c52
--- /dev/null
+++ b/Functions/Zftp/zfparams
@@ -0,0 +1,12 @@
+# function zfparams {
+
+emulate -L zsh
+
+# Set to prompt for any user or password if not given.
+# Don't worry about accounts here.
+if (( $# > 0 )); then
+  (( $# < 2 )) && 2='?'
+  (( $# < 3 )) && 3='?'
+fi
+zftp params $*
+# }
diff --git a/Functions/Zftp/zfpcp b/Functions/Zftp/zfpcp
new file mode 100644
index 000000000..ddd570e59
--- /dev/null
+++ b/Functions/Zftp/zfpcp
@@ -0,0 +1,47 @@
+# function zfpcp {
+# ZFTP put as copy:  i.e. first arguments are remote, last is local.
+# Currently only supports
+#  zfcp lfile rfile
+# if there are two arguments, or the second one is . or .., or ends
+# with a slash
+# or
+#  zfcp lfile1 lfile2 lfile3 ... rdir
+# if there are more than two (because otherwise it doesn't
+# know if the last argument is a directory on the remote machine).
+# However, if the remote machine plays ball by telling us `Is a directory'
+# when we try to copy to a directory, zfpcp will then try to do the correct
+# thing.
+
+emulate -L zsh
+
+local rem loc
+integer stat do_close
+
+zfautocheck
+
+if [[ $# -gt 2 || $2 = (.|..) || $2 = */ ]]; then
+  local dir=$argv[-1]
+  argv[-1]=
+  # zfcd directory hack to put the front back to ~
+  if [[ $dir = $HOME || $dir = $HOME/* ]]; then
+    dir="~${dir#$HOME}"
+  fi
+  [[ -n $dir && $dir != */ ]] || dir="$dir/"
+  for loc in $*; do
+    rem=$dir${loc:t}
+    zftp put $rem <$loc || stat=1
+  done
+else
+  zftp put $2 <$1
+  stat=$?
+  if [[ stat -ne 0 && $ZFTP_CODE = 553 && $ZFTP_REPLY = *'Is a directory'* ]]
+  then
+       zftp put $2/$1:t <$1
+       stat=$?
+  fi
+fi
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfput b/Functions/Zftp/zfput
new file mode 100644
index 000000000..0687163f0
--- /dev/null
+++ b/Functions/Zftp/zfput
@@ -0,0 +1,23 @@
+# function zfput {
+# Simple put:  dump every file under the same name, but stripping
+# off any directory parts to get the remote filename (i.e. always
+# goes into current remote directory).  Use zfpcp to specify new
+# file name or new directory at remote end.
+
+emulate -L zsh
+
+local loc rem
+integer stat do_close
+
+zfautocheck
+
+for loc in $*; do
+  rem=${loc:t}
+  zftp put $rem <$loc
+  [[ $? == 0 ]] || stat=$?
+done
+
+(( $do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfrglob b/Functions/Zftp/zfrglob
new file mode 100644
index 000000000..f9d67b3f2
--- /dev/null
+++ b/Functions/Zftp/zfrglob
@@ -0,0 +1,70 @@
+# function zfrglob {
+# Do the remote globbing for zfput, etc.
+# We have two choices:
+#  (1) Get the entire file list and match it one by one
+#      locally against the pattern.
+#      Causes problems if we are globbing directories (rare, presumably).
+#      But: we can cache the current directory, which
+#      we need for completion anyway.  Works on any OS if you
+#      stick with a single directory.  This is the default.
+#  (2) Use remote globbing, i.e. pass it to ls at the site.
+#      Faster, but only works with UNIX, and only basic globbing.
+#      We do this if $zfrglob is non-null.
+
+# There is only one argument, the variable containing the
+# pattern to be globbed.  We set this back to an array containing
+# all the matches.
+
+emulate -L zsh
+setopt extendedglob
+
+local pat dir nondir files i
+
+eval pat=\$$1
+
+# Check if we really need to do anything.  Look for standard
+# globbing characters, and if we are
+# using zsh for the actual pattern matching also look for
+# extendedglob characters.
+if [[ $pat != *[][*?]* &&
+  ( -n $zfrglob || $pat != *[(|)#^]* ) ]]; then
+  return 0
+fi
+local tmpf=${TMPPREFIX}zfrglob$$
+
+if [[ $zfrglob != '' ]]; then
+  zftp ls "$pat" >$tmpf 2>/dev/null
+  eval "$1=(\$(<\$tmpf))"
+  rm -f $tmpf
+else
+  if [[ $ZFTP_SYSTEM = UNIX* && $pat = */* ]]; then
+    # not the current directory and we know how to handle paths
+    if [[ $pat = ?*/* ]]; then
+      # careful not to remove too many slashes
+      dir=${pat%/*}
+    else
+      dir=/
+    fi
+    nondir=${pat##*/}
+    zftp ls "$dir" 2>/dev/null >$tmpf
+    files=($(<$tmpf))
+    files=(${files:t})
+    rm -f $tmpf
+  else
+    # we just have to do an ls and hope that's right
+    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)
+  fi
+  # now we want to see which of the $files match $nondir:
+  # ${...:/foo} deletes occurrences of foo matching a complete word,
+  # while the ^ inverts the sense so that anything not matching the
+  # pattern in $nondir is excluded.
+  eval "$1=(\${files:/^\${~nondir}})"
+fi
+# }
diff --git a/Functions/Zftp/zfrtime b/Functions/Zftp/zfrtime
new file mode 100644
index 000000000..f63ffe04b
--- /dev/null
+++ b/Functions/Zftp/zfrtime
@@ -0,0 +1,45 @@
+# function zfrtime {
+# Set the modification time of file LOCAL to that of REMOTE.
+# If the optional TIME is passed, it should be in the FTP format
+# CCYYMMDDhhmmSS, i.e. no dot before the seconds, and in GMT.
+# This is what both `zftp remote' and `zftp local' return.
+#
+# Unfortunately, since the time returned from FTP is GMT and
+# your file needs to be set in local time, we need to do some
+# hacking around with time.  At the moment this requires perl 5
+# with the standard library.
+
+emulate -L zsh
+
+local time gmtime loctime
+
+if [[ -n $3 ]]; then
+  time=$3
+else
+  time=($(zftp remote $2 2>/dev/null))
+  [[ -n $time ]] && time=$time[2]
+fi
+[[ -z $time ]] && return 1
+
+# Now's the real *!@**!?!.  We have the date in GMT and want to turn
+# it into local time for touch to handle.  It's just too nasty
+# to handle in zsh; do it in perl.
+if perl -mTime::Local -e '($file, $t) = @ARGV;
+$yr = substr($t, 0, 4) - 1900;
+$mon = substr($t, 4, 2) - 1;
+$mday = substr($t, 6, 2) + 0;
+$hr = substr($t, 8, 2) + 0;
+$min = substr($t, 10, 2) + 0;
+$sec = substr($t, 12, 2) + 0;
+$time = Time::Local::timegm($sec, $min, $hr, $mday, $mon, $yr);
+utime $time, $time, $file and return 0;' $1 $time 2>/dev/null; then
+  print "Setting time for $1 failed.  Need perl 5." 2>1
+fi
+
+# If it wasn't for the GMT/local time thing, it would be this simple.
+#
+# time="${time[1,12]}.${time[13,14]}"
+#
+# touch -t $time $1
+
+# }
diff --git a/Functions/Zftp/zfstat b/Functions/Zftp/zfstat
new file mode 100644
index 000000000..0ca755d03
--- /dev/null
+++ b/Functions/Zftp/zfstat
@@ -0,0 +1,89 @@
+# function zfstat {
+# Give a zftp status report using local variables.
+# With option -v, connect to the remote host and ask it what it
+# thinks the status is.  
+
+setopt localoptions unset
+unsetopt ksharrays
+
+local i stat=0 opt optlist verbose
+
+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
+done
+
+if [[ -n $ZFTP_HOST ]]; then
+  print "Host:\t\t$ZFTP_HOST"
+  print "IP:\t\t$ZFTP_IP"
+  [[ -n $ZFTP_SYSTEM ]] && print "System type:\t$ZFTP_SYSTEM"
+  if [[ -n $ZFTP_USER ]]; then
+    print "User:\t\t$ZFTP_USER "
+    [[ -n $ZFTP_ACCOUNT ]] && print "Account:\t$AFTP_ACCOUNT"
+    print "Directory:\t$ZFTP_PWD"
+    print -n "Transfer type:\t"
+    if [[ $ZFTP_TYPE = "I" ]]; then
+      print Image
+    elif [[ $ZFTP_TYPE = "A" ]]; then
+      print Ascii
+    else
+      print Unknown
+    fi
+    print -n "Transfer mode:\t"
+    if [[ $ZFTP_MODE = "S" ]]; then
+      print Stream
+    elif [[ $ZFTP_MODE = "B" ]]; then
+      print Block
+    else
+      print Unknown
+    fi
+  else
+    print "No user logged in."
+  fi
+else
+  print "Not connected."
+  [[ -n $zflastsession ]] && print "Last session:\t$zflastsession"
+  stat=1
+fi
+
+# things which may be set even if not connected:
+[[ -n $ZFTP_REPLY ]] && print "Last reply:\t$ZFTP_REPLY"
+print "Verbosity:\t$ZFTP_VERBOSE"
+print "Timeout:\t$ZFTP_TMOUT"
+print -n "Preferences:\t"
+for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do
+  case $ZFTP_PREFS[$i] in
+    [pP]) print -n "Passive "
+	  ;;
+    [sS]) print -n "Sendport "
+	  ;;
+    [dD]) print -n "Dumb "
+	  ;;
+    *) print -n "$ZFTP_PREFS[$i]???"
+  esac
+done
+print
+
+if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then
+  zfautocheck -d
+  print "Status of remote server:"
+  # make sure we print the reply
+  local ZFTP_VERBOSE=045
+  zftp quote STAT
+fi
+
+return $stat
+# }
diff --git a/Functions/Zftp/zftp_chpwd b/Functions/Zftp/zftp_chpwd
new file mode 100644
index 000000000..0df199cfb
--- /dev/null
+++ b/Functions/Zftp/zftp_chpwd
@@ -0,0 +1,39 @@
+# function zftp_chpwd {
+# You may want to alter chpwd to call this when $ZFTP_USER is set.
+
+# Cancel the filename cache for the current directory.
+zftp_fcache=()
+# ...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=
+
+if [[ -z $ZFTP_USER ]]; then
+  # last call, after an FTP logout
+
+  # delete the non-current cached directory
+  [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+
+  # don't keep zflastdir between opens (do keep zflastsession)
+  zflastdir=
+
+  # return the display to standard
+  # uncomment the following line if you have a chpwd which shows directories
+  # chpwd
+else
+  [[ -n $ZFTP_PWD ]] && zflastdir=$ZFTP_PWD
+  zflastsession="$ZFTP_HOST:$ZFTP_PWD"
+  local args
+  if [[ -t 1 && -t 2 ]]; then
+    local str=$zflastsession
+    [[ ${#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"
+	     ;;
+    esac
+  fi
+fi
+# }
diff --git a/Functions/Zftp/zftp_progress b/Functions/Zftp/zftp_progress
new file mode 100644
index 000000000..e2b5084c4
--- /dev/null
+++ b/Functions/Zftp/zftp_progress
@@ -0,0 +1,18 @@
+# function zftp_progress {
+# Basic progress metre, showing the percent of the file transferred.
+# You want growing bars?  You gotta write growing bars.
+
+# Don't show progress unless stderr is a terminal
+[[ ! -t 2 ]] && return 0
+
+if [[ $ZFTP_TRANSFER = *F ]]; then
+  print 1>&2
+elif [[ -n $ZFTP_TRANSFER ]]; then
+  if [[ -n $ZFTP_SIZE ]]; then
+    local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%"
+    print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2
+  else
+    print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2
+  fi
+fi
+# }
diff --git a/Functions/Zftp/zftype b/Functions/Zftp/zftype
new file mode 100644
index 000000000..c3b93b7a0
--- /dev/null
+++ b/Functions/Zftp/zftype
@@ -0,0 +1,30 @@
+# function zftype {
+local type zftmp=${TMPPREFIX}zftype$$
+
+zfautocheck -d
+
+if (( $# == 0 )); then
+  zftp type >$zftmp
+  type=$(<$zftmp)
+  rm -f $zftmp
+  if [[ $type = I ]]; then
+    print "Current type is image (binary)"
+    return 0
+  elif [[ $type = A ]]; then
+    print "Current type is ASCII"
+    return 0
+  else
+    return 1
+  fi
+else
+  if [[ $1 == (#i)a(sc(ii|)|) ]]; then
+    type=A
+  elif [[ $1 == (#i)i(m(age|)|) || $1 == (#i)b(in(ary|)|) ]]; then
+    type=I
+  else
+    print "Type not recognised:  $1" 2>&1
+    return 1
+  fi
+  zftp type $type
+fi
+# }
diff --git a/Functions/Zftp/zfuget b/Functions/Zftp/zfuget
new file mode 100644
index 000000000..482da42e9
--- /dev/null
+++ b/Functions/Zftp/zfuget
@@ -0,0 +1,147 @@
+# function zfuget {
+# Get a list of files from the server with update.
+# In other words, only retrieve files which are newer than local
+# ones.  This depends on the clocks being adjusted correctly
+# (i.e. if one is fifteen minutes out, for the next fifteen minutes
+# updates may not be correctly calculated).  However, difficult
+# cases --- where the files are the same size, but the remote is newer,
+# or have different sizes, but the local is newer -- are prompted for.
+#
+# Files are globbed on the remote host --- assuming, of course, they
+# haven't already been globbed local, so use 'noglob' e.g. as
+# `alias zfuget="noglob zfuget"'.
+#
+# Options:
+#  -G    Glob:     turn off globbing
+#  -v    verbose:  print more about the files listed.
+#  -s    silent:   don't ask, just guess.  The guesses are:
+#                - if the files have different sizes but remote is older ) grab
+#                - if they have the same size but remote is newer        )
+#                  which is safe if the remote files are always the right ones.
+#   -t   time:     update the local file times to the same time as the remote.
+#                  Currently this only works if you have the `perl' command,
+#                  and that perl is version 5 with the standard library.
+#                  See the function zfrtime for more gory details.
+
+emulate -L zsh
+
+local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$
+local rstat remlist verbose optlist opt bad i silent nglob time
+integer stat do_close
+
+zfuget_print_time() {
+  local tim=$1
+  print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+  print -n GMT
+}
+
+zfuget_print () {
+  print -n "\nremote $rem ("
+  zfuget_print_time $remstats[2]
+  print -n ", $remstats[1] bytes)\nlocal $loc ("
+  zfuget_print_time $locstats[2]
+  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
+done
+
+[[ -n $bad ]] && return 1
+
+zfautocheck
+
+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
+    zfrglob remlist
+  fi
+  if (( $#remlist )); then
+    for rem in $remlist; do
+      loc=${rem:t}
+      doit=y
+      remstats=()
+      if [[ -f $loc ]]; then
+	zftp local $loc >$tmpfile
+	locstats=($(<$tmpfile))
+	zftp remote $rem >$tmpfile
+	rstat=$?
+	remstats=($(<$tmpfile))
+	rm -f $tmpfile
+	if [[ $rstat = 2 ]]; then
+	  print "Server does not implement full command set required." 1>&2
+	  return 1
+	elif [[ $rstat = 1 ]]; then
+	  print "File not found on server: $rem" 1>&2
+	  stat=1
+	  continue
+	fi
+	[[ $verbose = 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
+	    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
+	  fi
+	else
+	  # Files have same size
+	  if [[ $locstats[2] < $remstats[2] ]]; then
+	    if [[ $silent != 1 ]]; then
+	      [[ $verbose != 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
+	    doit=n
+	  fi
+	fi
+      else
+	[[ $verbose = 1 ]] && print New file $loc
+      fi
+      if [[ $doit = y ]]; then
+	if zftp get $rem >$loc; then
+	  if [[ $time = 1 ]]; then
+	    # if $remstats is set, it's second element is the remote time
+	    zfrtime $loc $rem $remstats[2]
+	  fi
+	else
+	  stat=$?
+	fi
+	
+      fi
+    done
+  fi
+done
+
+(( do_close )) && zfclose
+
+return $stat
+# }
diff --git a/Functions/Zftp/zfuput b/Functions/Zftp/zfuput
new file mode 100644
index 000000000..b54d0d0d4
--- /dev/null
+++ b/Functions/Zftp/zfuput
@@ -0,0 +1,115 @@
+# function zfuput {
+# Put a list of files from the server with update.
+# See zfuget for details.
+#
+# Options:
+#  -v    verbose:  print more about the files listed.
+#  -s    silent:   don't ask, just guess.  The guesses are:
+#                - if the files have different sizes but remote is older ) grab
+#                - if they have the same size but remote is newer        )
+#                  which is safe if the remote files are always the right ones.
+
+emulate -L zsh
+
+local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$
+local rstat verbose optlist opt bad i silent
+integer stat do_close
+
+zfuput_print_time() {
+  local tim=$1
+  print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+  print -n GMT
+}
+
+zfuput_print () {
+  print -n "\nremote $rem ("
+  zfuput_print_time $remstats[2]
+  print -n ", $remstats[1] bytes)\nlocal $loc ("
+  zfuput_print_time $locstats[2]
+  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
+done
+
+[[ -n $bad ]] && return 1
+
+zfautocheck
+
+if [[ $ZFTP_VERBOSE = *5* ]]; then
+  # should we turn it off locally?
+  print "Messages with code 550 are harmless." >&2
+fi
+
+for rem in $*; do
+  loc=${rem:t}
+  doit=y
+  remstats=()
+  if [[ ! -f $loc ]]; then
+    print "$loc: file not found" >&2
+    stat=1
+    continue
+  fi
+  zftp local $loc >$tmpfile
+  locstats=($(<$tmpfile))
+  zftp remote $rem >$tmpfile
+  rstat=$?
+  remstats=($(<$tmpfile))
+  rm -f $tmpfile
+  if [[ $rstat = 2 ]]; then
+    print "Server does not implement full command set required." 1>&2
+    return 1
+  elif [[ $rstat = 1 ]]; then
+    [[ $verbose = 1 ]] && print New file $loc
+  else
+    [[ $verbose = 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
+	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
+      fi
+    else
+      # Files have same size
+      if [[ $locstats[2] > $remstats[2] ]]; then
+	if [[ $silent != 1 ]]; then
+	  [[ $verbose != 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
+	doit=n
+      fi
+    fi
+  fi
+  if [[ $doit = y ]]; then
+    zftp put $rem <$loc || stat=$?
+  fi
+done
+
+(( do_close )) && zfclose
+
+return $stat
+# }