From 9003d99d16c46b5679da7fcf1f2a41adef495ff9 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Thu, 15 Apr 1999 18:09:05 +0000 Subject: zsh-3.1.5-pws-3 --- Config/version.mk | 4 +- Doc/Zsh/builtins.yo | 25 +- Doc/Zsh/expn.yo | 50 +- Doc/Zsh/guide.yo | 1 + Doc/Zsh/mod_stat.yo | 10 +- Doc/Zsh/mod_zle.yo | 2 +- Doc/Zsh/modules.yo | 5 + Functions/cdmatch2 | 2 +- Misc/globtests | 1 + Misc/globtests.ksh | 1 + Misc/lete2ctl | 49 +- Misc/zftp-functions | 1281 ++++++++++++++++++++++++ Src/Modules/example.c | 62 +- Src/Modules/stat.c | 77 +- Src/Modules/zftp.c | 2596 +++++++++++++++++++++++++++++++++++++++++++++++++ Src/Modules/zftp.mdd | 3 + Src/Zle/comp1.c | 2 +- Src/Zle/zle_main.c | 2 +- Src/Zle/zle_params.c | 3 + Src/Zle/zle_thingy.c | 4 +- Src/Zle/zle_tricky.c | 66 +- Src/Zle/zle_vi.c | 2 + Src/Zle/zle_word.c | 2 +- Src/builtin.c | 4 +- Src/cond.c | 20 + Src/exec.c | 52 +- Src/glob.c | 422 ++++---- Src/hashtable.c | 3 +- Src/init.c | 5 +- Src/mem.c | 8 +- Src/module.c | 328 ++++++- Src/params.c | 189 +++- Src/parse.c | 91 +- Src/signals.c | 2 +- Src/subst.c | 117 ++- Src/text.c | 21 + Src/utils.c | 47 +- Src/zsh.export | 17 + Src/zsh.h | 69 +- configure.in | 2 + patchlist.txt | 83 +- 41 files changed, 5309 insertions(+), 421 deletions(-) create mode 100644 Misc/zftp-functions create mode 100644 Src/Modules/zftp.c create mode 100644 Src/Modules/zftp.mdd diff --git a/Config/version.mk b/Config/version.mk index 5ed093b1b..5b8c88350 100644 --- a/Config/version.mk +++ b/Config/version.mk @@ -27,5 +27,5 @@ # This must also serve as a shell script, so do not add spaces around the # `=' signs. -VERSION=3.1.5 -VERSION_DATE='October 29, 1998' +VERSION=3.1.5-pws-3 +VERSION_DATE='December 12, 1998' diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 1bbc9c36a..4eae549e5 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -512,7 +512,7 @@ sitem([var(mm)tt(:)]var(ss))(minutes and seconds) endsitem() ) findex(local) -item(tt(local) [ {tt(PLUS())|tt(-)}tt(LRZilrtu) [var(n)]] [ var(name)[tt(=)var(value)] ] ...)( +item(tt(local) [ {tt(PLUS())|tt(-)}tt(ALRUZailrtu) [var(n)]] [ var(name)[tt(=)var(value)] ] ...)( Same as tt(typeset), except that the options tt(-x) and tt(-f) are not permitted. ) @@ -878,7 +878,7 @@ Equivalent to tt(whence -v). findex(typeset) cindex(parameters, setting) cindex(parameters, declaring) -item(tt(typeset) [ {tt(PLUS())|tt(-)}tt(LRUZfilrtuxm) [var(n)]] [ var(name)[tt(=)var(value)] ... ])( +item(tt(typeset) [ {tt(PLUS())|tt(-)}tt(ALRUZafilrtuxm) [var(n)]] [ var(name)[tt(=)var(value)] ... ])( Set attributes and values for shell parameters. When invoked inside a function a new parameter is created which will be unset when the function completes. The new parameter will not be @@ -887,6 +887,9 @@ exported provided no parameter of that name already exists. The following attributes are valid: startitem() +item(tt(-A))( +Declare var(name) to be an em(A)ssociation parameter (also known as a hash). +) item(tt(-L))( Left justify and remove leading blanks from var(value). If var(n) is nonzero, it defines the width of the field; @@ -916,6 +919,10 @@ If var(n) is nonzero it defines the width of the field; otherwise it is determined by the width of the value of the first assignment. ) +item(tt(-a))( +On its own, this option produces a list of all array parameters. +If any non-options are provided, the tt(typeset) command is silently ignored. +) item(tt(-f))( The names refer to functions rather than parameters. No assignments can be made, and the only other valid flags are tt(-t) @@ -1115,7 +1122,10 @@ xitem(tt(zmodload) tt(-u) [ tt(-i) ] var(name) ...) xitem(tt(zmodload) tt(-d) [ tt(-L) ] [ var(name) [ var(dep) ... ] ]) xitem(tt(zmodload) tt(-du) var(name) [ var(dep) ... ]) xitem(tt(zmodload) tt(-a) [ tt(-iL) ] [ var(name) [ var(builtin) ... ] ]) -item(tt(zmodload) tt(-au) [ tt(-i) ] var(builtin) ...)( +xitem(tt(zmodload) tt(-au) [ tt(-i) ] var(builtin) ...) +xitem(tt(zmodload) tt(-c) [ tt(-iI) ] [ var(name) [ var(cond) ... ] ]) +xitem(tt(zmodload) tt(-cu) [ tt(-iI) ] var(cond) ...) +item(tt(zmodload) tt(-c) [ tt(-IL) ])( tt(zmodload) performs operations relating to zsh's loadable modules. This feature is not available on all operating systems, or on all installations on a particular operating system. @@ -1182,5 +1192,14 @@ together with the tt(-u) option it removes builtins defined with tt(zmodload -a). This is only possible if the builtin is not yet loaded. tt(-i) suppresses the error if the builtin is already removed (or never existed). + +The tt(-c) option is used to define autoloaded condition codes. The +var(cond) strings give the names of the conditions defined by the +module. The optional tt(-I) option is used to define infix condition +names. Without this option prefix condition names are defined. + +Together with the tt(-u) option definitions for autoloaded conditions +are removed. If given no condition names all defined names are listed +(as a series of tt(zmodload) commands if the tt(-L) option is given). ) enditem() diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index ba8a1d239..5341ec61e 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -312,8 +312,10 @@ noderef(Parameters) for a description of parameters. In the expansions discussed below that require a pattern, the form of the pattern is the same as that used for filename generation; -see noderef(Filename Generation). In addition to the following -operations, the file modifiers described in +see noderef(Filename Generation). Note that this pattern, along with +the replacement text of a substitution, is itself subject to +parameter, command and arithmetic substitution. In addition to the +following operations, the file modifiers described in noderef(Modifiers) in noderef(History Expansion) can be applied: for example, tt(${i:s/foo/bar/}) performs string substitution on the value of parameter tt($i). @@ -398,6 +400,37 @@ is used, matching is performed on each array elements separately, and the matched array elements are removed (use the tt((M)) flag to remove the non-matched elements). ) +xitem(tt(${)var(name)tt(/)var(pattern)tt(/)var(repl)tt(})) +item(tt(${)var(name)tt(//)var(pattern)tt(/)var(repl)tt(}))( +Substitute the longest possible match of var(pattern) in the value of +variable var(name) with the string var(repl). The first form +substitutes just the first occurrence, the second all occurrences. +The var(pattern) may begin with a var(#), in which case the +var(pattern) must match at the start of the string, or var(%), in +which case it must match at the end of the string. The var(repl) may +be an empty string, in which case the final tt(/) may also be omitted. +To quote the final tt(/) in other cases it should be preceded by two +backslashes (i.e., a quoted backslash); this is not necessary if the +tt(/) occurs inside a substituted paramter. Substitution of an array +is as described for tt(#) and tt(%) above. + +The first tt(/) may be preceded by a tt(:), in which case the match +will only succeed if it matches the entire word. Note also the +effect of the tt(I) and tt(S) parameter expansion flags below: the +flags tt(M), tt(R), tt(B), tt(E) and tt(N) are not useful, however. + +For example, + +nofill(tt(foo="twinkle twinkle little star" sub="t*e" rep="spy")) +nofill(tt(print ${foo//${~sub}/$rep})) +nofill(tt(print ${(S)foo//${~sub}/$rep})) + +Here, the tt(~) ensures that the text of tt($sub) is treated as a +pattern rather than a plain string. In the first case, the longest +match for tt(t*e) is substituted and the result is `tt(spy star)', +while in the second case, the shortest matches are taken and the +result is `tt(spy spy lispy star)'. +) item(tt(${#)var(spec)tt(}))( If var(spec) is one of the above substitutions, substitute the length in characters of the result instead of @@ -549,10 +582,21 @@ for `tt(ps:\n:)'. item(tt(S))( (This and all remaining flags are used with the tt(${)...tt(#)...tt(}) or tt(${)...tt(%)...tt(}) forms.) -Search substrings as well as beginnings or ends. +Search substrings as well as beginnings or ends; with tt(#) start +from the beginning and with tt(%) start from the end of the string. +With substitution via tt(${)...tt(/)...tt(}) or +tt(${)...tt(//)...tt(}), specifies that the shortest instead of the +longest match should be replaced. ) item(tt(I:)var(expr)tt(:))( Search the var(expr)th match (where var(expr) evaluates to a number). +This only applies when searching for substrings, either with the tt(S) +flag, or with tt(${)...tt(/)...tt(}) (only the var(expr)th match is +substituted) or tt(${)...tt(//)...tt(}) (all matches from the +var(expr)th on are substituted). The var(expr)th match is counted +such that there is either one or zero matches from each starting +position in the string, although for global subsitution matches +overlapping previous replacements are ignored. ) item(tt(M))( Include the matched portion in the result. diff --git a/Doc/Zsh/guide.yo b/Doc/Zsh/guide.yo index d38a0d308..f54e6bfdb 100644 --- a/Doc/Zsh/guide.yo +++ b/Doc/Zsh/guide.yo @@ -114,6 +114,7 @@ menu(The example Module) menu(The files Module) menu(The sched Module) menu(The stat Module) +menu(The zftp Module) menu(The zle Module) endmenu() texinode(The Z Shell Guide)(Introduction)(Top)(Top) diff --git a/Doc/Zsh/mod_stat.yo b/Doc/Zsh/mod_stat.yo index 22fbe2a73..a8551d8e9 100644 --- a/Doc/Zsh/mod_stat.yo +++ b/Doc/Zsh/mod_stat.yo @@ -1,4 +1,4 @@ -texinode(The stat Module)(The zle Module)(The sched Module)(Zsh Modules) +texinode(The stat Module)(The zftp Module)(The sched Module)(Zsh Modules) sect(The stat Module) The tt(stat) module makes available one builtin command: @@ -6,7 +6,8 @@ startitem() findex(stat) cindex(files, listing) cindex(files, examining) -item(tt(stat) [ tt(-gnNlLtTrs) ] [ tt(-f) var(fd) ] [ tt(-A) var(array) ] \ +item(tt(stat) [ tt(-gnNlLtTrs) ] [ tt(-f) var(fd) ] \ + [ tt(-H) var(hash) ] [ tt(-A) var(array) ] \ [ tt(-F) var(fmt) ] [ tt(PLUS())var(element) ] [ var(file) ... ])( The command acts as a front end to the tt(stat) system call (see manref(stat)(2)). @@ -91,6 +92,11 @@ appropriate array element and in the latter case the file name appears as a separate array element preceding all the others. Other formatting options are respected. ) +item(tt(-H) var(hash))( +Similar to tt(-A), but instead assign the values to var(hash). The keys +are the elements listed above. If the tt(-n) option is provided then the +name of the file is included in the hash with key tt(name). +) item(tt(-f) var(fd))( Use the file on file descriptor var(fd) instead of named files; no list of file names is allowed in this case. diff --git a/Doc/Zsh/mod_zle.yo b/Doc/Zsh/mod_zle.yo index b7c6a934f..48dd1935f 100644 --- a/Doc/Zsh/mod_zle.yo +++ b/Doc/Zsh/mod_zle.yo @@ -1,4 +1,4 @@ -texinode(The zle Module)()(The stat Module)(Zsh Modules) +texinode(The zle Module)()(The zftp Module)(Zsh Modules) sect(The zle Module) The tt(zle) module contains the Zsh Line Editor. See ifzman(zmanref(zshzle))\ diff --git a/Doc/Zsh/modules.yo b/Doc/Zsh/modules.yo index b44b00e6b..300553845 100644 --- a/Doc/Zsh/modules.yo +++ b/Doc/Zsh/modules.yo @@ -36,6 +36,9 @@ A builtin that provides a timed execution facility within the shell. item(tt(stat))( A builtin command interface to the tt(stat) system call. ) +item(tt(zftp))( +A builtin FTP client. +) item(tt(zle))( The Zsh Line Editor, including the tt(bindkey) and tt(vared) builtins. ) @@ -50,6 +53,7 @@ menu(The example Module) menu(The files Module) menu(The sched Module) menu(The stat Module) +menu(The zftp Module) menu(The zle Module) endmenu() includefile(Zsh/mod_cap.yo) @@ -61,4 +65,5 @@ includefile(Zsh/mod_example.yo) includefile(Zsh/mod_files.yo) includefile(Zsh/mod_sched.yo) includefile(Zsh/mod_stat.yo) +includefile(Zsh/mod_zftp.yo) includefile(Zsh/mod_zle.yo) diff --git a/Functions/cdmatch2 b/Functions/cdmatch2 index c70357a34..8a3e9591f 100644 --- a/Functions/cdmatch2 +++ b/Functions/cdmatch2 @@ -2,7 +2,7 @@ # second argument of cd and pushd. emulate -R zsh # Requires zsh 3.0-pre4 or later -setopt localoptions +setopt localoptions extendedglob local from read -Ac from diff --git a/Misc/globtests b/Misc/globtests index b7d5fd0bd..9a12a11e1 100755 --- a/Misc/globtests +++ b/Misc/globtests @@ -112,5 +112,6 @@ t fooxx ((#i)FOOX)x f fooxx ((#i)FOOX)X f BAR (bar|(#i)foo) t FOO (bar|(#i)foo) +t Modules (#i)*m* EOT print "$failed tests failed." diff --git a/Misc/globtests.ksh b/Misc/globtests.ksh index da5145845..839637af4 100755 --- a/Misc/globtests.ksh +++ b/Misc/globtests.ksh @@ -96,5 +96,6 @@ t fooxx @((#i)FOOX)x f fooxx @((#i)FOOX)X f BAR @(bar|(#i)foo) t FOO @(bar|(#i)foo) +t Modules (#i)*m* EOT print "$failed tests failed." diff --git a/Misc/lete2ctl b/Misc/lete2ctl index 273ea4674..ca00b8aee 100755 --- a/Misc/lete2ctl +++ b/Misc/lete2ctl @@ -9,7 +9,6 @@ # Runs as a filter. Should ignore anything which isn't a "complete". # It expects each "complete" statement to be the first thing on a line. # All the examples in the tcsh manual give sensible results. -# Author: Peter Stephenson # # Option: # -x (exact): only applies in the case of command disambiguation (is @@ -39,11 +38,6 @@ # (5) Make sure all command names with wildcards are processed together -- # they need to be lumped into one "compctl -C" or "compctl -D" # statement for zsh. -# (6) Group completion (complete's g flag) is not built into zsh, so -# you need perl to be available to generate the groups. If this -# script is useful, I assume that's not a problem. -# (7) I don't know what `completing completions' means, so the X -# flag to complete is not handled. # Handle options if (@ARGV) { @@ -119,13 +113,6 @@ sub gettype { # Nothing (n) can be handled by returning nothing. (C.f. King Lear, I.i.) if ($c =~ /[abcjuv]/) { $ret = "-$c"; - } elsif ($c eq 'C') { - if (defined($glob)) { - $ret = "-W $glob -/g '*(.*)'"; - undef($glob); - } else { - $ret = '-c'; - } } elsif ($c eq 'S') { $ret = '-k signals'; } elsif ($c eq 'd') { @@ -134,42 +121,18 @@ sub gettype { } else { $ret = '-/'; } - } elsif ($c eq 'D') { - if (defined($glob)) { - $ret = "-W $glob -/"; - undef($glob); - } else { - $ret = '-/'; - } } elsif ($c eq 'e') { $ret = '-E'; } elsif ($c eq 'f' && !$glob) { $ret = '-f'; - } elsif ($c eq 'F') { - if (defined($glob)) { - $ret = "-W $glob -f"; - undef($glob); - } else { - $ret = '-f'; - } - } elsif ($c eq 'g') { - $ret = "-s '\$(perl -e '\\''while ((\$name) = getgrent)\n" . - "{ print \$name, \"\\n\"; }'\\'')'"; } elsif ($c eq 'l') { $ret = q!-k "(`limit | awk '{print $1}'`)"!; } elsif ($c eq 'p') { - $ret = "-W $glob -f", undef($glob) if defined($glob); + $ret = "-W $glob -f", undef($glob) if defined($glob); } elsif ($c eq 's') { - $ret = '-p'; + $ret = '-p'; } elsif ($c eq 't') { $qual = '.'; - } elsif ($c eq 'T') { - if (defined($glob)) { - $ret = "-W $glob -g '*(.)'"; - undef($glob); - } else { - $ret = "-g '*(.)'"; - } } elsif ($c eq 'x') { $glob =~ s/'/'\\''/g; $ret = "-X '$glob'"; @@ -227,7 +190,7 @@ $" = " - "; while (<>) { if (/^\s*complete\s/) { - undef(@stuff); + undef(@stuff); $default = ''; $_ = $'; while (/\\$/) { @@ -248,7 +211,7 @@ while (<>) { # Loop over remaining arguments to "complete". $sep = substr($word,1,1); $sep =~ s/(\W)/\\$1/g; - @split = split(/$sep/,$word,4); + @split = split(/$sep/,$word); for ($i = 0; $i < 3; $i++) { while ($split[$i] =~ /\\$/) { substr($split[$i],-1,1) = ""; @@ -262,9 +225,7 @@ while (<>) { # The "complete" catch-all: treat this as compctl\'s # default (requiring no pattern matching). $default .= &gettype($type) . ' '; - defined($suffix) && - (defined($defsuf) ? ($defsuf .= $suffix) - : ($defsuf = $suffix)); + defined($suffix) && ($defsuf .= $suffix); } else { $pat = &getpat($pat,$arg); $type = &gettype($type); diff --git a/Misc/zftp-functions b/Misc/zftp-functions new file mode 100644 index 000000000..a07e46d72 --- /dev/null +++ b/Misc/zftp-functions @@ -0,0 +1,1281 @@ +# zftp is a loadable module implementing an FTP client as a builtin +# command so that you can use the shell command language and line +# editing to make life easier. If your system has dynamically +# load libraries and zsh was compiled to use them, it is probably +# somewhere where it can be loaded at run time. Otherwise, it depends +# whether the shell was compiled with zftp already built into it. +# +# Here is a suite of functions, plus assorted other code, to make +# zftp work smoothly. +# +# Completion is implemented in a fairly natural way, except that +# very little support has been provided for non-UNIX remote hosts. +# On such machines, the safest thing to do is only try to complete +# files in the current directory; this should be OK. +# +# Remote globbing for commands which retrieve files is also +# implemented. This can be done in two different ways. The default +# is for zsh to do the globbing locally. The advantage is that full +# zsh pattern matching (respecting the setting of extendedglob) is +# possible, and no assumption (apart from the restrictions on +# directory handling noted above) is made about the behaviour of the +# server. The disadvantage is that the entire filename list for the +# current directory must be retrieved, and then zsh must laboriously +# do pattern matching against every file, so it is potentially slow +# for large directories. Only the non-directory part of file names is +# globbed. +# +# The alternative will be used if $zfrglob has non-zero length. +# Zsh then sends the pattern to the server for globbing. Best of +# luck. +# +# To support remote globbing, some functions have been aliased +# with 'noglob' in front. Currently, this has a dire effect on +# completion unless the completeinaliases option is set, so +# it is set below. This can conceivably cause you problems +# if you expect completion for aliases automatically to give you +# completion for the base command. I suspect that most people +# don't even know that happens. +# +# The following functions are provided. +# +# General status changing and displaying functions: +# zfparams +# Simple front end to `zftp params', except it will automatically +# query host, user and password. These are then stored to be +# used with a `zfopen' with no arguments. +# zfopen [ host [ user ... ] ] +# Open a connection and login. Unless the option -1 (once) +# is given, will store the parameters for the open (including +# a password which is prompted for and not echoed) so that +# if you call zfopen subsequently without arguments it will +# reopen the same connection. +# zfanon anonftphost +# Open a connection for anonymous FTP. Tries to guess an +# email address to use as the password, unless $EMAIL_ADDR is +# already set. The first time, will tell you what it has guessed. +# It's rude to set EMAIL_ADDR=mozilla. +# zfcd [ dir | old new ] +# Change directory on the server. This tries to mimic the behaviour +# of the shell's cd. In particular, +# zfcd change to '~' on server, if it interprets it +# zfcd - change to previous directory of current connection +# zfcd OLD NEW change directory from fooOLDbar to fooNEWbar +# One piece of magic is builtin: an initial part of the directory +# matching $HOME is translated back to `~'. Most UNIX servers +# recognise the usual shell convention. So things like `zfcd $PWD' +# is useful provide you are under your home directory and the +# structure on the remote machine mirrors that on the local. +# zfhere +# Synonym for `zfcd $PWD', see above. +# zfdir [args] +# Show a long diretory list of the remote connection. Any +# arguments are passed on to the server, apart from options. +# Currently this always uses a pager to show the directory +# list. Caching is implemented: zfdir on its own always shows +# the current diretory, which is cached; zfdir with some other +# directory arguments shows that, which is cached separately +# and can be reviewed with `zfdir -r'. Other options: +# -f force reget, overriding the cache, in case something's changed +# -d delete the cache, but don't show anything. +# To pass options to the server, use e.g. `zfdir -- -C'. +# This also has the zfcd ~ hack. +# zfls [args] +# Short list of the long directory, depending on what [args] +# do to the server. No options, no caching, no pager. +# zftype [ a[scii] | i[mage] | b[inary] ] +# Set or display the transfer type; currently only ASCII +# and image (same as binary) types are supported. +# zfclose +# Close the connection. +# zfstat +# Print the zftp status from local variables; doesn't do any network +# operations unless -v is supplied, in which case the server is +# asked for its views on the status, too. +# +# Functions for retrieving data: +# All accept the following options: +# -G Don't do remote globbing (see above); the default is to do it. +# -t Try to set local files to the same time as the remote ones. +# Unfortunately we only know the remote time in GMT, so it's +# a little tricky and you need perl 5 (installed as `perl') +# for this to work. Suggestions welcome. +# zfget file1 file2 ... +# Retrieve each file from the server. The remote file is the +# full name given, the local file is the non-directory part of that +# (assuming UNIX file paths). +# zfuget file1 file2 .. +# Get with update. Check remote and local sizes and times and +# retrieve files which are newer on the server. Will query +# hard cases, which are where the remote file is newer but a +# different size, or is older but the same size. With option -s +# (silent) assumes it's best to retrieve the files in both those +# cases. With -v (may be combined with -s), print the information +# about the files being considered. +# zfcget file1 ... +# Assuming file1 was incompletely retrieved, try to get the rest of +# it. This relies on a normal UNIX server behaviour which is not +# as specified in the FTP standard and hence is not universal. +# zfgcp file1 file2 +# zfgcp file1 file2 ... dir +# Get with the behaviour of cp, i.e. copy remote file1 to local +# file2, or get remote fileN into local diretory dir. +# +# Function for sending data: +# zfput file1 file2 ... +# Put the local files onto the server under the same name. The +# local files are exactly as given; the remote files are the +# non-diretory parts of that. +# zfuput file1 file2 .. +# Put the local files onto the server, with update. Works +# similarly to zfuget. +# +# Utility functions: +# zftp_chpwd +# Show the new directory when it changes; try to put it into +# an xterm on shelltool header. Works best alongside chpwd. +# zftp_progress +# Show the percentage of a file retrieved as it is coming; if the +# size is not available show the size transferred so far. The +# percentage may be wrong if sending data from a local pipe. +# If you transfer files in the background, you should undefine +# this before the transfer. It is smart enough not to print +# anything when stderr is not a terminal. +# zfcd_match +# Function for remote directory completion. +# zfget_match +# Function for remote filename completion. +# zfrglob varname +# This is used for the remote globbing. The pattern resides +# in $varname (note extra level of indirection), and on return +# $varname will contain the list of matching files. +# zfrtime locfile remfile [ time ] +# This sad thing does the setting of local file times to those +# of the remote, see horror story above. + +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 at the moment +setopt completealiases + +# +# zftp completions +# +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 zfopen zfparams + +function zfanon { + 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 +} + +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. + + # Autoopen: if not already open, hope there are parameters set up to + # do so. If not, we get the right error message, so no harm done. + [[ -z $ZFTP_HOST ]] && { zfopen || return 1; } + + 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 +} + +function zfcd_match { + # 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? + + 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. + reply=($(zftp ls -F $dir | + awk '/\/$/ { print substr($1, 0, length($1)-1) }')) + 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 + +} + +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. + + setopt localoptions + unsetopt ksharrays shwordsplit + + 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 +} + +function zfclose { + zftp close +} + +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. + + setopt localoptions unset + unsetopt shwordsplit ksharrays + + local file opt optlist redir i newargs force + + 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 + 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 + ;; + *) print option $opt not recognised >&2 + ;; + esac + done + shift + done + + # 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 + zftp dir $* | tee $file | eval ${PAGER-:more} + fi +} + +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. + # zfcp rfile lfile + # or + # zfcp 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. + + setopt localoptions + unsetopt shwordsplit + + local opt optlist nglob remlist rem loc stat=0 time + + 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 + + # 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 + return $stat +} + +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. + + local loc rem stat=0 optlist opt nglob remlist time + + 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 + + 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 + + return $stat +} + +function zfget_match { + # the zfcd hack: this may not be necessary here + if [[ $1 == $HOME || $1 == $HOME/* ]]; then + 1="~${1#$HOME}" + fi + + + if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then + # On the first argument to ls, we usually get away with a glob. + reply=($(zftp ls "$1*$2")) + else + if (( $#zftp_fcache == 0 )); then + # Always cache the current directory and use it + # even if the system is UNIX. + zftp_fcache=($(zftp ls)) + fi + reply=($zftp_fcache); + fi +} + +function zfhere { + # Change to the directory corresponding to $PWD on the server. + # See zfcd for how this works. + zfcd $PWD +} + +function zfls { + # directory hack, see zfcd + if [[ $1 = $HOME || $1 = $HOME/* ]]; then + 1="~${1#$HOME}" + fi + zftp ls $* +} + +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. + + 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 +} + +function zfparams { + # 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 $* +} + +function zfput { + # Simple put: dump every file under the same name, but stripping + # off any directory parts. + local loc rem stat=0 + for loc in $*; do + rem=${loc:t} + zftp put $rem <$loc + [[ $? == 0 ]] || stat=$? + done + return $stat +} + +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. + setopt localoptions unset + unsetopt ksharrays + + local pat dir nondir files i + + eval pat=\$$1 + + # Check if we really need to do anything. Look for standard + # globbing characters, and if extendedglob is set and we are + # using zsh for the actual pattern matching also look for + # extendedglob characters. + if [[ $remlist != *[][*?]* && + ( -n $zfrglob || ! -o extendedglob || $remlist != *[(|)~#^]* ) ]]; then + return 0 + fi + + if [[ $zfrglob != '' ]]; then + eval "$1=(\$(zftp ls \"$pat\" 2>/dev/null))" + 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##*/} + files=($(zftp ls "$dir" 2>/dev/null)) + else + # we just have to do an ls and hope that's right + nondir=$pat + if (( $#zftp_fcache == 0 )); then + zftp_fcache=($(zftp ls)) + fi + files=($zftp_fcache) + fi + # now we want to see which of the $files match $nondir + for (( i = 1; i <= $#files; i++)); do + # empty words are elided in array assignment + [[ $files[$i] = ${~nondir} ]] || files[$i]='' + done + eval "$1=(\$files)" + fi +} + +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. + + setopt localoptions unset + unsetopt ksharrays + + 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 + +} + +function zfstat { + # Give a zftp status report using local variables. + # With option -v, connect 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 + + # hack: in case the status from a subshell process hasn't been + # fixed yet + zftp type >&/dev/null + + 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." + 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 -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 + print "Status of remote server:" + # make sure we print the reply + local ZFTP_VERBOSE=045 + zftp quote STAT + fi + + return $stat +} + +function zftp_chpwd { + # You may want to alter chpwd to call this when $ZFTP_USER is set. + # If so, call it with a non-zero first argument so it doesn't + # print the new FTP directory. + + # 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 + + if [[ -z $ZFTP_USER ]]; then + # last call, after an FTP logout + + # delete the non-current cached directory + [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir + zfotherargs= + + # don't keep zflastdir between opens + zflastdir= + + # return the display to standard + # uncomment the following line if you have a chpwd which shows directories + # chpwd + else + [[ -z $zflastdir ]] && zflastdir=$ZFTP_PWD + local args + if [[ -t 1 && -t 2 ]]; then + local str="$ZFTP_HOST:$ZFTP_PWD" + [[ -z $1 ]] && print $str + [[ ${#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 +} + +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 +} + +function zftype { + local type + + if (( $# == 0 )); then + type=$(zftp type) + 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 == [aA]([sS][cC]([iI][iI]|)|) ]]; then + type=A + elif [[ $1 == [iI]([mM]([aA][gG][eE]|)|) || + $1 == [bB]([iI][nN]([aA][rR][yY]|)|) ]]; then + type=I + else + print "Type not recognised: $1" 2>&1 + return 1 + fi + zftp type $type + fi +} + +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. + + setopt localoptions + unsetopt ksharrays shwordsplit + + local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$ + local rstat remlist verbose optlist opt bad i silent nglob time + + 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 + + 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 + return $stat +} + +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. + + setopt localoptions + unsetopt ksharrays shwordsplit + + local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$ + local rstat verbose optlist opt bad i silent + + 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 + + if [[ $ZFTP_VERBOSE = *5* ]]; then + # should we turn it off locally? + print "Messages with code 550 are harmless." >&2 + fi + + for loc in $*; do + rem=${loc: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 + return $stat +} diff --git a/Src/Modules/example.c b/Src/Modules/example.c index 45ef3c28f..a71806c3a 100644 --- a/Src/Modules/example.c +++ b/Src/Modules/example.c @@ -49,6 +49,53 @@ bin_example(char *nam, char **args, char *ops, int func) return 0; } +/**/ +static int +cond_p_len(Conddef c, char **a) +{ + char *s1 = a[0], *s2 = a[1]; + + singsub(&s1); + untokenize(s1); + if (s2) { + singsub(&s2); + untokenize(s2); + return strlen(s1) == matheval(s2); + } else { + return !s1[0]; + } +} + +/**/ +static int +cond_i_ex(Conddef c, char **a) +{ + char *s1 = a[0], *s2 = a[1]; + + singsub(&s1); + untokenize(s1); + singsub(&s2); + untokenize(s2); + return !strcmp("example", dyncat(s1, s2)); +} + +/**/ +static int +ex_wrapper(List list, FuncWrap w, char *name) +{ + if (strncmp(name, "example", 7)) + return 1; + else { + int ogd = opts[GLOBDOTS]; + + opts[GLOBDOTS] = 1; + runshfunc(list, w, name); + opts[GLOBDOTS] = ogd; + + return 0; + } +} + /* * boot_example is executed when the module is loaded. */ @@ -57,11 +104,22 @@ static struct builtin bintab[] = { BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL), }; +static struct conddef cotab[] = { + CONDDEF("len", 0, 1, 2, cond_p_len), + CONDDEF("ex", CONDF_INFIX, 0, 0, cond_i_ex), +}; + +static struct funcwrap wrapper[] = { + WRAPDEF(ex_wrapper), +}; + /**/ int boot_example(Module m) { - return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return !(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) | + addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)) | + !addwrapper(m, wrapper)); } #ifdef MODULE @@ -71,6 +129,8 @@ int cleanup_example(Module m) { deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)); + deletewrapper(m, wrapper); return 0; } #endif diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c index 64664abaf..769b42b1a 100644 --- a/Src/Modules/stat.c +++ b/Src/Modules/stat.c @@ -34,11 +34,13 @@ enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID, ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM, ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT }; enum statflags { STF_NAME = 1, STF_FILE = 2, STF_STRING = 4, STF_RAW = 8, - STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64 }; + STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64, + STF_HASH = 128 }; static char *statelts[] = { "device", "inode", "mode", "nlink", "uid", "gid", "rdev", "size", "atime", "mtime", "ctime", "blksize", "blocks", "link", NULL }; +#define HNAMEKEY "name" /**/ static void @@ -287,6 +289,8 @@ statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags) * file names are returned as a separate array element, type names as * prefix to element. Note the formatting deliberately contains * fewer frills when -A is used. + * -H hash: as for -A array, but returns a hash with the keys being those + * from stat -l * -F fmt: specify a $TIME-like format for printing times; the default * is the (CTIME-like) "%a %b %e %k:%M:%S". This option implies * -s as it is not useful for numerical times. @@ -305,6 +309,7 @@ static int bin_stat(char *name, char **args, char *ops, int func) { char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL; + char *hashnam = NULL, **hash = NULL, **hashptr = NULL; int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0; struct stat statbuf; int found = 0, nargs; @@ -352,6 +357,16 @@ bin_stat(char *name, char **args, char *ops, int func) } flags |= STF_ARRAY; break; + } else if (*arg == 'H') { + if (arg[1]) { + hashnam = arg+1; + } else if (!(hashnam = *++args)) { + zerrnam(name, "missing parameter name\n", + NULL, 0); + return 1; + } + flags |= STF_HASH; + break; } else if (*arg == 'f') { char *sfd; ops['f'] = 1; @@ -385,6 +400,15 @@ bin_stat(char *name, char **args, char *ops, int func) } } + if ((flags & STF_ARRAY) && (flags & STF_HASH)) { + /* We don't implement setting multiple variables at once */ + zwarnnam(name, "both array and hash requested", NULL, 0); + return 1; + /* Alternate method would be to make -H blank arrnam etc etc * + * and so get 'silent loss' of earlier choice, which would * + * be similar to stat -A foo -A bar filename */ + } + if (ops['l']) { /* list types and return: can also list to array */ if (arrnam) { @@ -435,7 +459,7 @@ bin_stat(char *name, char **args, char *ops, int func) if (ops['g']) flags |= STF_GMT; - if (!arrnam) { + if (!(arrnam || hashnam)) { if (nargs > 1) flags |= STF_FILE; if (!(flags & STF_PICK)) @@ -444,9 +468,20 @@ bin_stat(char *name, char **args, char *ops, int func) if (ops['N'] || ops['f']) flags &= ~STF_FILE; - if (ops['T']) + if (ops['T'] || ops['H']) flags &= ~STF_NAME; + if (hashnam) { + if (nargs > 1) { + zwarnnam(name, "only one file allowed with -H", NULL, 0); + return 1; + } + arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; + if (flags & STF_FILE) + arrsize++; + hashptr = hash = (char **)zcalloc((arrsize+1)*2*sizeof(char *)); + } + if (arrnam) { arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; if (flags & STF_FILE) @@ -473,13 +508,20 @@ bin_stat(char *name, char **args, char *ops, int func) if (flags & STF_FILE) if (arrnam) *arrptr++ = ztrdup(*args); - else + else if (hashnam) { + *hashptr++ = ztrdup(HNAMEKEY); + *hashptr++ = ztrdup(*args); + } else printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n"); if (iwhich > -1) { statprint(&statbuf, outbuf, *args, iwhich, flags); if (arrnam) *arrptr++ = ztrdup(outbuf); - else + else if (hashnam) { + /* STF_NAME explicitly turned off for ops['H'] above */ + *hashptr++ = ztrdup(statelts[iwhich]); + *hashptr++ = ztrdup(outbuf); + } else printf("%s\n", outbuf); } else { int i; @@ -487,28 +529,39 @@ bin_stat(char *name, char **args, char *ops, int func) statprint(&statbuf, outbuf, *args, i, flags); if (arrnam) *arrptr++= ztrdup(outbuf); - else + else if (hashnam) { + /* STF_NAME explicitly turned off for ops['H'] above */ + *hashptr++ = ztrdup(statelts[i]); + *hashptr++ = ztrdup(outbuf); + } else printf("%s\n", outbuf); } } if (ops['f']) break; - if (!arrnam && args[1] && !(flags & STF_PICK)) + if (!arrnam && !hashnam && args[1] && !(flags & STF_PICK)) putchar('\n'); } if (arrnam) - if (ret) { - for (aptr = array; *aptr; aptr++) - zsfree(*aptr); - zfree(array, arrsize+1); - } else { + if (ret) + freearray(array); + else { setaparam(arrnam, array); if (errflag) return 1; } + if (hashnam) + if (ret) + freearray(hash); + else { + sethparam(hashnam, hash); + if (errflag) + return 1; + } + return ret; } diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c new file mode 100644 index 000000000..ca0843419 --- /dev/null +++ b/Src/Modules/zftp.c @@ -0,0 +1,2596 @@ +/* + * zftp.c - builtin FTP client + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1998 Peter Stephenson + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Peter Stephenson or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Peter Stephenson and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Peter Stephenson and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Peter Stephenson and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +/* + * TODO: + * 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. + */ +#include "zftp.mdh" +#include "zftp.pro" + +#include +#include +#include +#include +#include +#include +/* it's a TELNET based protocol, but don't think I like doing this */ +#include + +/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */ +#ifndef INADDR_NONE +#define INADDR_NONE (in_addr_t)-1 +#endif + +/* + * For FTP block mode + * + * The server on our AIX machine here happily accepts block mode, takes the + * first connection, then at the second complains that it's got nowhere + * to send data. The same problem happens with ncftp, it's not just + * me. And a lot of servers don't even support block mode. So I'm not sure + * how widespread the supposed ability to leave open the data fd between + * transfers. Therefore, I've closed all connections after the transfer. + * But then what's the point in block mode? I only implemented it because + * it says in RFC959 that you need it to be able to restart transfers + * later in the file. However, it turns out that's not true for + * most servers --- but our AIX machine happily accepts the REST + * command and then dumps the whole file onto you. Sigh. + * + * Note on block sizes: + * Not quite sure how to optimize this: in principle + * we should handle blocks up to 65535 bytes, which + * is pretty big, and should presumably send blocks + * which are smaller to be on the safe side. + * Currently we send 32768 and use that also as + * the maximum to receive. No-one's complained yet. Of course, + * no-one's *used* it yet apart from me, but even so. + */ + +struct zfheader { + char flags; + unsigned char bytes[2]; +}; + +enum { + ZFHD_MARK = 16, /* restart marker */ + ZFHD_ERRS = 32, /* suspected errors in block */ + ZFHD_EOFB = 64, /* block is end of record */ + ZFHD_EORB = 128 /* block is end of file */ +}; + +typedef int (*readwrite_t)(int, char *, size_t, int); + +struct zftpcmd { + const char *nam; + int (*fun) _((char *, char **, int)); + int min, max, flags; +}; + +enum { + ZFTP_CONN = 0x0001, /* must be connected */ + ZFTP_LOGI = 0x0002, /* must be logged in */ + ZFTP_TBIN = 0x0004, /* set transfer type image */ + ZFTP_TASC = 0x0008, /* set transfer type ASCII */ + ZFTP_NLST = 0x0010, /* use NLST rather than LIST */ + ZFTP_DELE = 0x0020, /* a delete rather than a make */ + ZFTP_SITE = 0x0040, /* a site rather than a quote */ + ZFTP_APPE = 0x0080, /* append rather than overwrite */ + ZFTP_HERE = 0x0100, /* here rather than over there */ + ZFTP_CDUP = 0x0200, /* CDUP rather than CWD */ + ZFTP_REST = 0x0400, /* restart: set point in remote file */ + ZFTP_RECV = 0x0800 /* receive rather than send */ +}; + +typedef struct zftpcmd *Zftpcmd; + +static struct zftpcmd zftpcmdtab[] = { + { "open", zftp_open, 0, 4, 0 }, + { "params", zftp_params, 0, 4, 0 }, + { "login", zftp_login, 0, 3, ZFTP_CONN }, + { "user", zftp_login, 0, 3, ZFTP_CONN }, + { "cd", zftp_cd, 1, 1, ZFTP_CONN|ZFTP_LOGI }, + { "cdup", zftp_cd, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_CDUP }, + { "dir", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI }, + { "ls", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_NLST }, + { "type", zftp_type, 0, 1, ZFTP_CONN|ZFTP_LOGI }, + { "ascii", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TASC }, + { "binary", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TBIN }, + { "mode", zftp_mode, 0, 1, ZFTP_CONN|ZFTP_LOGI }, + { "local", zftp_local, 0, -1, ZFTP_HERE }, + { "remote", zftp_local, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "get", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV }, + { "getat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV|ZFTP_REST }, + { "put", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "putat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_REST }, + { "append", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE }, + { "appendat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE|ZFTP_REST }, + { "delete", zftp_delete, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "mkdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI }, + { "rmdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI|ZFTP_DELE }, + { "rename", zftp_rename, 2, 2, ZFTP_CONN|ZFTP_LOGI }, + { "quote", zftp_quote, 1, -1, ZFTP_CONN }, + { "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} +}; + +static struct builtin bintab[] = { + BUILTIN("zftp", 0, bin_zftp, 1, -1, 0, NULL, NULL), +}; + +/* + * these are the non-special params to unset when a connection + * closes. any special params are handled, well, specially. + * currently there aren't any, which is the way I like it. + */ +static char *zfparams[] = { + "ZFTP_HOST", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER", + "ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL +}; + +/* flags for zfsetparam */ + +enum { + ZFPM_READONLY = 0x01, /* make parameter readonly */ + ZFPM_IFUNSET = 0x02, /* only set if not already set */ + ZFPM_INTEGER = 0x04 /* passed pointer to long */ +}; + +/* + * 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; + +/* + * zcfinish = 0 keep going + * 1 line finished, alles klar + * 2 EOF + */ +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 + * for convenience. + */ +static char *lastmsg, lastcodestr[4]; +static int lastcode; + +/* flag for remote system is UNIX --- useful to know as things are simpler */ +static int zfis_unix, zfpassive_conn; + +/* remote system has size, mdtm commands */ +enum { + ZFCP_UNKN = 0, /* dunno if it works on this server */ + ZFCP_YUPP = 1, /* it does */ + 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 + * if something happend in a subshell: mode changed, type changed, + * connection was closed. If something too substantial happened + * in a subshell --- connection opened, ZFTP_USER and ZFTP_PWD changed + * --- we don't try to track it because it's too complicated. + */ +enum { + ZFST_ASCI = 0x00, /* type for next transfer is ASCII */ + ZFST_IMAG = 0x01, /* type for next transfer is image */ + + ZFST_TMSK = 0x01, /* mask for type flags */ + ZFST_TBIT = 0x01, /* number of bits in type flags */ + + ZFST_CASC = 0x00, /* current type is ASCII - default */ + ZFST_CIMA = 0x02, /* current type is image */ + + ZFST_STRE = 0x00, /* stream mode - default */ + ZFST_BLOC = 0x04, /* block mode */ + + ZFST_MMSK = 0x04, /* mask for mode flags */ + + ZFST_LOGI = 0x08, /* user logged in */ + ZFST_NOPS = 0x10, /* server doesn't understand PASV */ + ZFST_NOSZ = 0x20, /* server doesn't send `(XXXX bytes)' reply */ + ZFST_TRSZ = 0x40, /* tried getting 'size' from reply */ + ZFST_CLOS = 0x80 /* connection closed */ +}; +#define ZFST_TYPE(x) (x & ZFST_TMSK) +/* + * shift current type flags to match type flags: should be by + * the number of bits in the type flags + */ +#define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK) +#define ZFST_MODE(x) (x & ZFST_MMSK) + +static int zfstatfd = -1, zfstatus; + +/* Preferences, read in from the `zftp_prefs' array variable */ +enum { + ZFPF_SNDP = 0x01, /* Use send port mode */ + ZFPF_PASV = 0x02, /* Try using passive mode */ + ZFPF_DUMB = 0x04 /* Don't do clever things with variables */ +}; + +/* The flags as stored internally. */ +int zfprefs; + + +/* zfuserparams is the storage area for zftp_params() */ +char **zfuserparams; + +/* + * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's + * easier). The complication is that SIGALRM may already be handled + * by the user setting TMOUT and possibly setting their own trap --- in + * fact, it's always handled by the shell when it's interactive. It's + * too difficult to use zsh's own signal handler --- either it would + * need rewriting to use a C function as a trap, or we would need a + * hack to make it callback via a hidden builtin from a function --- so + * just install our own, and use settrap() to restore the behaviour + * afterwards if necessary. However, the more that could be done by + * the main shell code, the better I would like it. + * + * Since we don't want to go through the palaver of changing between + * the main zsh signal handler and ours every time we start or stop the + * alarm, we keep the flag zfalarmed set to 1 while zftp is rigged to + * handle alarms. This is tested at the end of bin_zftp(), which is + * the entry point for all functions, and that restores the original + * handler for SIGALRM. To turn off the alarm temporarily in the zftp + * code we then just call alarm(0). + * + * If we could rely on having select() or some replacement, we would + * only need the alarm during zftp_open(). + */ + +/* flags for alarm set, alarm gone off */ +int zfalarmed, zfdrrrring; +/* remember old alarm status */ +time_t oaltime; +unsigned int oalremain; + +/* + * Where to jump to when the alarm goes off. This is much + * easier than fiddling with error flags at every turn. + * Since we don't expect too many alarm's, the simple setjmp() + * mechanism should be good enough. + * + * gcc -O gives apparently spurious `may be clobbered by longjmp' warnings. + */ +jmp_buf zfalrmbuf; + +/* The signal handler itself */ + +/**/ +static RETSIGTYPE +zfhandler(int sig) +{ + if (sig == SIGALRM) { + zfdrrrring = 1; +#ifdef ETIMEDOUT /* just in case */ + errno = ETIMEDOUT; +#else + errno = EIO; +#endif + longjmp(zfalrmbuf, 1); + } + DPUTS(1, "zfhandler caught incorrect signal"); +} + +/* Set up for an alarm call */ + +/**/ +static void +zfalarm(int tmout) +{ + zfdrrrring = 0; + /* + * We need to do this even if tmout is zero, since there may + * be a non-zero timeout set in the main shell which we don't + * want to go off. This could be argued the other way, since + * if we don't get that it's probably harmless. But this looks safer. + */ + if (zfalarmed) { + alarm(tmout); + return; + } + signal(SIGALRM, zfhandler); + oalremain = alarm(tmout); + if (oalremain) + oaltime = time(NULL); + /* + * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the + * shell's handler doesn't get the signal, they don't matter. + */ + zfalarmed = 1; +} + +/* Set up for a broken pipe */ + +/**/ +static void +zfpipe() +{ + /* Just ignore SIGPIPE and rely on getting EPIPE from the write. */ + signal(SIGPIPE, SIG_IGN); +} + +/* Unset the alarm, see above */ + +/**/ +static void +zfunalarm() +{ + if (oalremain) { + /* + * The alarm was previously set, so set it back, adjusting + * for the time used. Mostly the alarm was set to zero + * beforehand, so it would probably be best to reinstall + * the proper signal handler before resetting the alarm. + * + * I love the way alarm() uses unsigned int while time_t + * is probably something completely different. + */ + time_t tdiff = time(NULL) - oaltime; + alarm(oalremain < tdiff ? 1 : oalremain - tdiff); + } else + alarm(0); + if (sigtrapped[SIGALRM] || interact) { + if (sigfuncs[SIGALRM] || !sigtrapped[SIGALRM]) + install_handler(SIGALRM); + else + signal_ignore(SIGALRM); + } else + signal_default(SIGALRM); + zfalarmed = 0; +} + +/* Restore SIGPIPE handling to its usual status */ + +/**/ +static void +zfunpipe() +{ + if (sigtrapped[SIGPIPE]) { + if (sigfuncs[SIGPIPE]) + install_handler(SIGPIPE); + else + signal_ignore(SIGPIPE); + } else + signal_default(SIGPIPE); +} + +/* + * Same as movefd(), but don't mark the fd in the zsh tables, + * because we only want it closed by zftp. However, we still + * need to shift the fd's out of the way of the user-visible 0-9. + */ + +/**/ +static int +zfmovefd(int fd) +{ + if (fd != -1 && fd < 10) { +#ifdef F_DUPFD + int fe = fcntl(fd, F_DUPFD, 10); +#else + int fe = zfmovefd(dup(fd)); +#endif + close(fd); + fd = fe; + } + return fd; +} + +/* + * set a non-special parameter. + * if ZFPM_IFUNSET, don't set if it already exists. + * if ZFPM_READONLY, make it readonly, but only when creating it. + * if ZFPM_INTEGER, val pointer is to long (NB not int), don't free. + */ +/**/ +static void +zfsetparam(char *name, void *val, int flags) +{ + Param pm = NULL; + int type = (flags & ZFPM_INTEGER) ? PM_INTEGER : PM_SCALAR; + + if (!(pm = (Param) paramtab->getnode(paramtab, name)) + || (pm->flags & PM_UNSET)) { + /* + * just make it readonly when creating, in case user + * *really* knows what they're doing + */ + if ((pm = createparam(name, type)) && (flags & ZFPM_READONLY)) + pm->flags |= PM_READONLY; + } else if (flags & ZFPM_IFUNSET) { + pm = NULL; + } + if (!pm || PM_TYPE(pm->flags) != type) { + /* parameters are funny, you just never know */ + if (type == PM_SCALAR) + zsfree((char *)val); + return; + } + if (type == PM_INTEGER) + pm->sets.ifn(pm, *(long *)val); + else + pm->sets.cfn(pm, (char *)val); +} + +/* + * Unset a ZFTP parameter when the connection is closed. + * We only do this with connection-specific parameters. + */ + +/**/ +static void +zfunsetparam(char *name) +{ + Param pm; + + if ((pm = (Param) paramtab->getnode(paramtab, name))) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } +} + +/* + * Join command and arguments to make a proper TELNET command line. + * New line is in permanent storage. + */ + +/**/ +static char * +zfargstring(char *cmd, char **args) +{ + int clen = strlen(cmd) + 3; + char *line, **aptr; + + for (aptr = args; *aptr; aptr++) + clen += strlen(*aptr) + 1; + line = zalloc(clen); + strcpy(line, cmd); + for (aptr = args; *aptr; aptr++) { + strcat(line, " "); + strcat(line, *aptr); + } + strcat(line, "\r\n"); + + return line; +} + +/* + * get a line on the control connection according to TELNET rules + * Return status is first digit of FTP reply code + */ + +/**/ +static int +zfgetline(char *ln, int lnsize, int tmout) +{ + int ch, added = 0; + /* current line point */ + char *pcur = ln, cmdbuf[3]; + + zcfinish = 0; + /* leave room for null byte */ + lnsize--; + /* in case we return unexpectedly before getting anything */ + ln[0] = '\0'; + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout getting response", NULL, 0); + return 5; + } + zfalarm(tmout); + + /* + * We need to be more careful about errors here; we + * should do the stuff with errflag and so forth. + * We should probably holdintr() here, since if we don't + * get the message, the connection is going to be messed up. + * But then we get `frustrated user syndrome'. + */ + for (;;) { + ch = fgetc(zcin); + + switch(ch) { + case EOF: + if (ferror(zcin) && errno == EINTR) { + clearerr(zcin); + continue; + } + zcfinish = 2; + break; + + case '\r': + /* always precedes something else */ + ch = fgetc(zcin); + if (ch == EOF) { + zcfinish = 2; + break; + } + if (ch == '\n') { + zcfinish = 1; + break; + } + if (ch == '\0') { + ch = '\r'; + break; + } + /* not supposed to get here */ + ch = '\r'; + break; + + case '\n': + /* not supposed to get here */ + zcfinish = 1; + break; + + case IAC: + /* + * oh great, now it's sending TELNET commands. try + * to persuade it not to. + */ + ch = fgetc(zcin); + switch (ch) { + case WILL: + case WONT: + ch = fgetc(zcin); + /* whatever it wants to do, stop it. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)DONT; + cmdbuf[2] = ch; + write(zcfd, cmdbuf, 3); + continue; + + case DO: + case DONT: + ch = fgetc(zcin); + /* well, tough, we're not going to. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)WONT; + cmdbuf[2] = ch; + write(zcfd, cmdbuf, 3); + continue; + + case EOF: + /* strange machine. */ + zcfinish = 2; + break; + + default: + break; + } + break; + } + + if (zcfinish) + break; + if (added < lnsize) { + *pcur++ = ch; + added++; + } + /* junk it if we don't have room, but go on reading */ + } + + alarm(0); + + *pcur = '\0'; + /* if zcfinish == 2, at EOF, return that, else 0 */ + return (zcfinish & 2); +} + +/* + * Get a whole message from the server. A dash after + * the first line code means keep reading until we get + * a line with the same code followed by a space. + * + * Note that this returns an FTP status code, the first + * digit of the reply. There is also a pseudocode, 6, which + * means `there's no point trying anything, just yet'. + * We return it either if the connection is closed, or if + * we got a 530 (user not logged in), in which case whatever + * you're trying to do isn't going to work. + */ + +/**/ +static int +zfgetmsg() +{ + char line[256], *ptr, *verbose; + int stopit, printing = 0, tmout; + + if (zcfd == -1) + return 5; + if (!(verbose = getsparam("ZFTP_VERBOSE"))) + verbose = ""; + zsfree(lastmsg); + lastmsg = NULL; + + tmout = getiparam("ZFTP_TMOUT"); + + zfgetline(line, 256, tmout); + ptr = line; + if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) || + !isdigit((int)ptr[2])) { + /* timeout, or not talking FTP. not really interested. */ + zcfinish = 2; + if (!zfclosing) + zfclose(); + lastmsg = ztrdup(""); + strcpy(lastcodestr, "000"); + zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY); + return 6; + } + strncpy(lastcodestr, ptr, 3); + ptr += 3; + lastcodestr[3] = '\0'; + lastcode = atoi(lastcodestr); + zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY); + stopit = (*ptr++ != '-'); + + if (strchr(verbose, lastcodestr[0])) { + /* print the whole thing verbatim */ + printing = 1; + fputs(line, stderr); + } else if (strchr(verbose, '0') && !stopit) { + /* print multiline parts with the code stripped */ + printing = 2; + fputs(ptr, stderr); + } + if (printing) + fputc('\n', stderr); + + while (zcfinish != 2 && !stopit) { + zfgetline(line, 256, tmout); + ptr = line; + if (zfdrrrring) { + line[0] = '\0'; + break; + } + + if (!strncmp(lastcodestr, line, 3)) { + if (line[3] == ' ') { + stopit = 1; + ptr += 4; + } else if (line[3] == '-') + ptr += 4; + } else if (!strncmp(" ", line, 4)) + ptr += 4; + + if (printing == 2) { + if (!stopit) { + fputs(ptr, stderr); + fputc('\n', stderr); + } + } else if (printing) { + fputs(line, stderr); + fputc('\n', stderr); + } + } + + if (printing) + fflush(stderr); + + /* The internal message is just the text. */ + lastmsg = ztrdup(ptr); + /* + * The parameter is the whole thing, including the code. + */ + zfsetparam("ZFTP_REPLY", ztrdup(line), ZFPM_READONLY); + /* + * close the connection here if zcfinish == 2, i.e. EOF, + * or if we get a 421 (service not available, closing connection), + * but don't do it if it's expected (zfclosing set). + */ + if ((zcfinish == 2 || lastcode == 421) && !zfclosing) { + zcfinish = 2; /* don't need to tell server */ + zfclose(); + /* unexpected, so tell user */ + zwarnnam("zftp", "remote server has closed connection", NULL, 0); + return 6; /* pretend it failed, because it did */ + } + if (lastcode == 530) { + /* user not logged in */ + return 6; + } + /* + * May as well handle this here, though it's pretty uncommon. + * A 120 is something like "service ready in nnn minutes". + * It means we just hang around waiting for another reply. + */ + if (lastcode == 120) { + zwarnnam("zftp", "delay expected, waiting: %s", lastmsg, 0); + return zfgetmsg(); + } + + /* first digit of code determines success, failure, not in the mood... */ + return lastcodestr[0] - '0'; +} + + +/* + * Send a command and get the reply. + * The command is expected to have the \r\n already tacked on. + * Returns the status code for the reply. + */ + +/**/ +static int +zfsendcmd(char *cmd) +{ + /* + * We use the fd directly; there's no point even using + * stdio with line buffering, since we always send the + * complete line in one string anyway. + */ + int ret, tmout; + + if (zcfd == -1) + return 5; + tmout = getiparam("ZFTP_TMOUT"); + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout sending message", NULL, 0); + return 5; + } + zfalarm(tmout); + ret = write(zcfd, cmd, strlen(cmd)); + alarm(0); + + if (ret <= 0) { + zwarnnam("zftp send", "failed sending control message", NULL, 0); + return 5; /* FTP status code */ + } + + return zfgetmsg(); +} + + +/* Set up a data connection, return 1 for failure, 0 for success */ + +/**/ +static int +zfopendata(char *name) +{ + if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) { + zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0); + return 1; + } + zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0)); + if (zdfd < 0) { + zwarnnam(name, "can't get data socket: %e", NULL, errno); + return 1; + } + + zdsock = zsock; + zdsock.sin_family = AF_INET; + + if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { + char *ptr; + int i, nums[6], err; + unsigned char iaddr[4], iport[2]; + + if (zfsendcmd("PASV\r\n") == 6) + return 1; + else if (lastcode >= 500 && lastcode <= 504) { + /* + * Fall back to send port mode. That will + * test the preferences for whether that's OK. + */ + zfstatus |= ZFST_NOPS; + zfclosedata(); + return zfopendata(name); + } + /* + * OK, now we need to know what port we're looking at, + * which is cunningly concealed in the reply. + * lastmsg already has the reply code expunged. + */ + for (ptr = lastmsg; *ptr; ptr++) + if (isdigit(*ptr)) + break; + if (sscanf(ptr, "%d,%d,%d,%d,%d,%d", + nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) { + zwarnnam(name, "bad response to PASV: %s", lastmsg, 0); + zfclosedata(); + return 1; + } + for (i = 0; i < 4; i++) + iaddr[i] = STOUC(nums[i]); + iport[0] = STOUC(nums[4]); + iport[1] = STOUC(nums[5]); + + memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr)); + memcpy(&zdsock.sin_port, iport, sizeof(iport)); + + /* we should timeout this connect */ + do { + err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)); + } while (err && errno == EINTR && !errflag); + + if (err) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclosedata(); + return 1; + } + + zfpassive_conn = 1; + } else { + char portcmd[40]; + unsigned char *addr, *port; + int ret, len; + + if (!(zfprefs & ZFPF_SNDP)) { + zwarnnam(name, "only sendport mode available for data", NULL, 0); + return 1; + } + + zdsock.sin_port = 0; /* to be set by bind() */ + len = sizeof(zdsock); + /* need to do timeout stuff and probably handle EINTR here */ + if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0) + ret = 1; + else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0) + ret = 2; + else if (listen(zdfd, 1) < 0) + ret = 3; + else + ret = 0; + + if (ret) { + zwarnnam(name, "failure on data socket: %s: %e", + ret == 3 ? "listen" : ret == 2 ? "getsockname" : "bind", + errno); + zfclosedata(); + return 1; + } + + addr = (unsigned char *) &zdsock.sin_addr; + port = (unsigned char *) &zdsock.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) { + zwarnnam(name, "port command failed", NULL, 0); + zfclosedata(); + return 1; + } + zfpassive_conn = 0; + } + + return 0; +} + +/* Close the data connection. */ + +/**/ +static void +zfclosedata(void) +{ + if (zdfd == -1) + return; + close(zdfd); + zdfd = -1; +} + +/* + * Set up a data connection and use cmd to initiate a transfer. + * The actual data fd will be zdfd; 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. + * getsize is non-zero if we want to try to find the number + * of bytes in the reply to a RETR command. + * + * Return 0 on success, 1 on failure. + */ + +/**/ +static int +zfgetdata(char *name, char *rest, char *cmd, int getsize) +{ + int len, newfd; + + if (zfopendata(name)) + return 1; + + /* + * Set position in remote file for get/put. + * According to RFC959, the restart command needs something + * called a marker which has previously been put into the data. + * Luckily for the real world, UNIX machines just interpret this + * as an offset into the byte stream. + * + * This has to be sent immediately before the data transfer, i.e. + * after all mucking around with types and sizes and so on. + */ + if (rest && zfsendcmd(rest) > 3) { + zfclosedata(); + return 1; + } + + if (zfsendcmd(cmd) > 2) { + zfclosedata(); + return 1; + } + if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) { + /* + * See if we got something like: + * Opening data connection for nortypix.gif (1234567 bytes). + * On the first RETR, always see if this works, Then we + * can avoid sending a special SIZE command beforehand. + */ + char *ptr = strstr(lastmsg, "bytes"); + zfstatus |= ZFST_NOSZ|ZFST_TRSZ; + if (ptr) { + while (ptr > lastmsg && !isdigit(*ptr)) + ptr--; + while (ptr > lastmsg && isdigit(ptr[-1])) + ptr--; + if (isdigit(*ptr)) { + zfstatus &= ~ZFST_NOSZ; + if (getsize) { + long sz = zstrtol(ptr, NULL, 10); + zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); + } + } + } + } + + if (!zfpassive_conn) { + /* + * the current zdfd 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. + */ + + /* accept the connection */ + len = sizeof(zdsock); + newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len)); + zfclosedata(); + if (newfd < 0) { + zwarnnam(name, "unable to accept data.", NULL, 0); + return 1; + } + zdfd = newfd; /* this is now the actual data fd */ + } + + + /* more options, just to look professional */ +#ifdef SO_LINGER + /* + * Since data can take arbitrary amounts of time to arrive, + * the socket can be made to hang around until it doesn't think + * anything is arriving. + * + * In BSD 4.3, you could only linger for infinity. Don't + * know if this has changed. + */ + { + struct linger li; + + li.l_onoff = 1; + li.l_linger = 120; + setsockopt(zdfd, 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)); + } +#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); +#endif + + return 0; +} + +/* + * Find out about a local or remote file and pass back the information. + * + * We could jigger this to use ls like ncftp does as a backup. + * But if the server is non-standard enough not to have SIZE and MDTM, + * there's a very good chance ls -l isn't going to do great things. + * + * if fd is >= 0, it is used for an fstat when remote is zero: + * this is because on a put we are taking input from fd 0. + */ + +/**/ +static int +zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) +{ + long sz = -1; + char *mt = NULL; + int ret; + + if (retsize) + *retsize = -1; + if (retmdtm) + *retmdtm = NULL; + if (remote) { + char *cmd; + if ((zfhas_size == ZFCP_NOPE && retsize) || + (zfhas_mdtm == ZFCP_NOPE && retmdtm)) + return 2; + + /* + * File is coming from over there. + * Make sure we get the type right. + */ + zfsettype(ZFST_TYPE(zfstatus)); + if (retsize) { + cmd = tricat("SIZE ", fnam, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + if (ret == 6) + return 1; + else if (lastcode < 300) { + sz = zstrtol(lastmsg, 0, 10); + zfhas_size = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_size = ZFCP_NOPE; + return 2; + } else if (lastcode == 550) + return 1; + /* if we got a 550 from SIZE, the file doesn't exist */ + } + + if (retmdtm) { + cmd = tricat("MDTM ", fnam, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + if (ret == 6) + return 1; + else if (lastcode < 300) { + mt = ztrdup(lastmsg); + zfhas_mdtm = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_mdtm = ZFCP_NOPE; + return 2; + } else if (lastcode == 550) + return 1; + } + } else { + /* File is over here */ + struct stat statbuf; + struct tm *tm; + char tmbuf[20]; + + if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0) + return 1; + /* make sure it's long, since this has to be a pointer */ + sz = statbuf.st_size; + + if (retmdtm) { + /* use gmtime() rather than localtime() for consistency */ + tm = gmtime(&statbuf.st_mtime); + /* + * FTP format for data is YYYYMMDDHHMMSS + * Using tm directly is easier than worrying about + * incompatible strftime()'s. + */ + sprintf(tmbuf, "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + mt = ztrdup(tmbuf); + } + } + if (retsize) + *retsize = sz; + if (retmdtm) + *retmdtm = mt; + return 0; +} + +/* Set parameters to say what's coming */ + +/**/ +static void +zfstarttrans(char *nam, int recv, long sz) +{ + long cnt = 0; + /* + * sz = -1 signifies error getting size. don't set ZFTP_SIZE if sz is + * zero, either: it probably came from an fstat() on a pipe, so it + * means we don't know and shouldn't tell the user porkies. + */ + if (sz > 0) + zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); + zfsetparam("ZFTP_FILE", ztrdup(nam), ZFPM_READONLY); + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "G" : "P"), ZFPM_READONLY); + zfsetparam("ZFTP_COUNT", &cnt, ZFPM_READONLY|ZFPM_INTEGER); +} + +/* Tidy up afterwards */ + +/**/ +static void +zfendtrans() +{ + zfunsetparam("ZFTP_SIZE"); + zfunsetparam("ZFTP_FILE"); + zfunsetparam("ZFTP_TRANSFER"); + zfunsetparam("ZFTP_COUNT"); +} + +/* Read with timeout if recv is set. */ + +/**/ +static int +zfread(int fd, char *bf, size_t sz, int tmout) +{ + int ret; + + if (!tmout) + return read(fd, bf, sz); + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout on network read", NULL, 0); + return -1; + } + zfalarm(tmout); + + ret = read(fd, bf, sz); + + /* we don't bother turning off the whole alarm mechanism here */ + alarm(0); + return ret; +} + +/* Write with timeout if recv is not set. */ + +/**/ +static int +zfwrite(int fd, char *bf, size_t sz, int tmout) +{ + int ret; + + if (!tmout) + return write(fd, bf, sz); + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout on network write", NULL, 0); + return -1; + } + zfalarm(tmout); + + ret = write(fd, bf, sz); + + /* we don't bother turning off the whole alarm mechanism here */ + alarm(0); + return ret; +} + +static int zfread_eof; + +/* Version of zfread when we need to read in block mode. */ + +/**/ +static int +zfread_block(int fd, char *bf, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_t blksz, cnt; + char *bfptr; + do { + /* we need the header */ + do { + n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout); + } while (n < 0 && errno == EINTR); + if (n != 3 && !zfdrrrring) { + zwarnnam("zftp", "failed to read FTP block header", NULL, 0); + return n; + } + /* size is stored in network byte order */ + if (hdr.flags & ZFHD_EOFB) + zfread_eof = 1; + blksz = (hdr.bytes[0] << 8) | hdr.bytes[1]; + if (blksz > sz) { + /* + * See comments in file headers + */ + zwarnnam("zftp", "block too large to handle", NULL, 0); + errno = EIO; + return -1; + } + bfptr = bf; + cnt = blksz; + while (cnt) { + n = zfread(fd, bfptr, cnt, tmout); + if (n > 0) { + bfptr += n; + cnt -= n; + } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR)) + return n; + else + break; + } + if (cnt) { + zwarnnam("zftp", "short data block", NULL, 0); + errno = EIO; + return -1; + } + } while ((hdr.flags & ZFHD_MARK) && !zfread_eof); + return (hdr.flags & ZFHD_MARK) ? 0 : blksz; +} + +/* Version of zfwrite when we need to write in block mode. */ + +/**/ +static int +zfwrite_block(int fd, char *bf, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_t cnt; + char *bfptr; + /* we need the header */ + do { + hdr.bytes[0] = (sz & 0xff00) >> 8; + hdr.bytes[1] = sz & 0xff; + hdr.flags = sz ? 0 : ZFHD_EOFB; + n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout); + } while (n < 0 && errno == EINTR); + if (n != 3 && !zfdrrrring) { + zwarnnam("zftp", "failed to write FTP block header", NULL, 0); + return n; + } + bfptr = bf; + cnt = sz; + while (cnt) { + n = zfwrite(fd, bfptr, cnt, tmout); + if (n > 0) { + bfptr += n; + cnt -= n; + } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR)) + return n; + } + + return sz; +} + +/* + * Move stuff from fdin to fdout, tidying up the data connection + * when finished. The data connection could be either input or output: + * recv is 1 for receiving a file, 0 for sending. + * + * progress is 1 to use a progress meter. + * startat says how far in we're starting with a REST command. + * + * Since we're doing some buffering here anyway, we don't bother + * with a stdio layer. + */ + +/**/ +static int +zfsenddata(char *name, int recv, int progress, long startat) +{ +#define ZF_BUFSIZE 32768 +#define ZF_ASCSIZE (ZF_BUFSIZE/2) + /* ret = 2 signals the local read/write failed, so send abort */ + int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0; + int rtmout = 0, wtmout = 0; + char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr; + long sofar = 0, last_sofar = 0; + readwrite_t read_ptr = zfread, write_ptr = zfwrite; + List l; + + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* + * progress to set up: ZFTP_COUNT is zero. + * We do this here in case we needed to wait for a RETR + * command to tell us how many bytes are coming. + */ + doshfunc("zftp_progress", l, NULL, 0, 1); + /* Now add in the bit of the file we've got/sent already */ + sofar = last_sofar = startat; + } + if (recv) { + fdin = zdfd; + fdout = 1; + rtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + fromasc = 1; + if (ZFST_MODE(zfstatus) == ZFST_BLOC) + read_ptr = zfread_block; + } else { + fdin = 0; + fdout = zdfd; + wtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + toasc = 1; + if (ZFST_MODE(zfstatus) == ZFST_BLOC) + write_ptr = zfwrite_block; + } + + if (toasc) + ascbuf = zalloc(ZF_ASCSIZE); + zfpipe(); + zfread_eof = 0; + while (!ret && !zfread_eof) { + n = (toasc) ? read_ptr(fdin, ascbuf, ZF_ASCSIZE, rtmout) + : read_ptr(fdin, lsbuf, ZF_BUFSIZE, rtmout); + if (n > 0) { + char *iptr; + if (toasc) { + /* \n -> \r\n it shouldn't happen to a dog. */ + char *iptr = ascbuf, *optr = lsbuf; + int cnt = n; + while (cnt--) { + if (*iptr == '\n') { + *optr++ = '\r'; + n++; + } + *optr++ = *iptr++; + } + } + if (fromasc && (iptr = memchr(lsbuf, '\r', n))) { + /* \r\n -> \n */ + char *optr = iptr; + int cnt = n - (iptr - lsbuf); + while (cnt--) { + if (*iptr != '\r' || iptr[1] != '\n') { + *optr++ = *iptr; + } else + n--; + iptr++; + } + } + optr = lsbuf; + + sofar += n; + + for (;;) { + /* + * in principle, write can be interrupted after + * safely writing some bytes, and will return the + * number already written, which may not be the + * complete buffer. so make this robust. they call me + * `robustness stephenson'. in my dreams. + */ + int newn = write_ptr(fdout, optr, n, wtmout); + if (newn == n) + break; + if (newn < 0) { + /* + * The somewhat contorted test here (for write) + * and below (for read) means: + * real error if + * - errno is set and it's not just an interrupt, or + * - errflag is set, probably due to CTRL-c, or + * - zfdrrrring is set, due to the alarm going off. + * print an error message if + * - not a timeout, since that was reported, and + * either + * - a non-interactive shell, where we don't + * know what happened otherwise + * - or both of + * - not errflag, i.e. CTRL-c or what have you, + * since the user probably knows about that, and + * - not a SIGPIPE, since usually people are + * silent about those when going to pagers + * (if you quit less or more in the middle + * and see an error message you think `I + * shouldn't have done that'). + * + * If we didn't print an error message here, + * and were going to do an abort (ret == 2) + * because the error happened on the local end + * of the connection, set ret to 3 and don't print + * the 'aborting...' either. + * + * There must be a better way of doing this. + */ + if (errno != EINTR || errflag || zfdrrrring) { + if (!zfdrrrring && + (!interact || (!errflag && errno != EPIPE))) { + ret = recv ? 2 : 1; + zwarnnam(name, "write failed: %e", NULL, errno); + } else + ret = recv ? 3 : 1; + break; + } + continue; + } + optr += newn; + n -= newn; + } + } else if (n < 0) { + if (errno != EINTR || errflag || zfdrrrring) { + if (!zfdrrrring && + (!interact || (!errflag && errno != EPIPE))) { + ret = recv ? 1 : 2; + zwarnnam(name, "read failed: %e", NULL, errno); + } else + ret = recv ? 1 : 3; + break; + } + } else + break; + if (!ret && sofar != last_sofar && progress && + (l = getshfunc("zftp_progress")) != &dummy_list) { + zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER); + doshfunc("zftp_progress", l, NULL, 0, 1); + last_sofar = sofar; + } + } + zfunpipe(); + /* + * At this point any timeout was on the data connection, + * so we don't need to force the control connection to close. + */ + zfdrrrring = 0; + if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) { + /* send an end-of-file marker block */ + ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0); + } + if (errflag || ret > 1) { + /* + * some error occurred, maybe a keyboard interrupt, or + * a local file/pipe handling problem. + * send an abort. + * + * safest to block all signals here? can get frustrating if + * we're waiting for an abort. don't I know. let's start + * off just by blocking SIGINT's. + * + * maybe the timeout for the abort should be shorter than + * for normal commands. and what about aborting after + * we had a timeout on the data connection, is that + * really a good idea? + */ + /* RFC 959 says this is what to send */ + unsigned char msg[4] = { IAC, IP, IAC, SYNCH }; + + if (ret == 2) + zwarnnam(name, "aborting data transfer...", NULL, 0); + + holdintr(); + + /* 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); + + zfsendcmd("ABOR\r\n"); + if (lastcode == 226) { + /* + * 226 is supposed to mean the transfer got sent OK after + * all, and the abort got ignored, at least that's what + * rfc959 seems to be saying. but in fact what can happen + * is the transfer finishes (at least as far as the + * server's concerned) and it's response is waiting, then + * the abort gets sent, and we need to mop up a response to + * that. so actually in most cases we get two replies + * anyway. we could test if we had select() on all hosts. + */ + /* gotack = 1; */ + /* + * we'd better leave errflag, since we don't know + * where it came from. maybe the user wants to abort + * a whole script or function. + */ + } else + ret = 1; + + noholdintr(); + } + + if (toasc) + zfree(ascbuf, ZF_ASCSIZE); + zfclosedata(); + if (!gotack && zfgetmsg() > 2) + ret = 1; + return ret != 0; +} + +/* Open a new control connection, i.e. start a new FTP session */ + +/**/ +static int +zftp_open(char *name, char **args, int flags) +{ + struct in_addr ipaddr; + struct protoent *zprotop; + struct servent *zservp; + struct hostent *zhostp = NULL; + char **addrp, tbuf[2] = "X", *fname; + int err, len, tmout; + + if (!*args) { + if (zfuserparams) + args = zfuserparams; + else { + zwarnnam(name, "no host specified", NULL, 0); + return 1; + } + } + + /* + * Close the existing connection if any. + * Probably this is the safest thing to do. It's possible + * a `QUIT' will hang, though. + */ + if (zcfd != -1) + zfclose(); + + /* this is going to give 0. why bother? */ + zprotop = getprotobyname("tcp"); + zservp = getservbyname("ftp", "tcp"); + + if (!zprotop || !zservp) { + zwarnnam(name, "Somebody stole FTP!", NULL, 0); + return 1; + } + + /* don't try talking to server yet */ + zcfinish = 2; + + /* + * This sets an alarm for the whole process, getting the host name + * as well as connecting. Arguably you could time them out separately. + */ + tmout = getiparam("ZFTP_TMOUT"); + if (setjmp(zfalrmbuf)) { + char *hname; + alarm(0); + if ((hname = getsparam("ZFTP_HOST")) && *hname) + zwarnnam(name, "timeout connecting to %s", hname, 0); + else + zwarnnam(name, "timeout on host name lookup", NULL, 0); + zfclose(); + return 1; + } + zfalarm(tmout); + + /* + * Now this is what I like. A library which provides decent higher + * level functions to do things like converting address types. It saves + * so much trouble. Pity about the rest of the network interface, though. + */ + ipaddr.s_addr = inet_addr(args[0]); + if (ipaddr.s_addr != INADDR_NONE) { + /* + * hmmm, we don't necessarily want to do this... maybe the + * user is actively trying to avoid a bad nameserver. + * perhaps better just to set ZFTP_HOST to the dot address, too. + * that way shell functions know how it was opened. + * + * zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET); + * + * or, we could have a `no_lookup' flag. + */ + zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY); + zsock.sin_family = AF_INET; + } else { + zhostp = gethostbyname(args[0]); + if (!zhostp || errflag) { + /* should use herror() here if available, but maybe + * needs configure test. on AIX it's present but not + * in headers. + */ + zwarnnam(name, "host not found: %s", args[0], 0); + alarm(0); + return 1; + } + zsock.sin_family = zhostp->h_addrtype; + zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY); + } + + zsock.sin_port = ntohs(zservp->s_port); + zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0)); + if (zcfd < 0) { + zwarnnam(name, "socket failed: %e", NULL, errno); + zfunsetparam("ZFTP_HOST"); + alarm(0); + return 1; + } + +#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); +#endif + + /* + * now connect the socket. manual pages all say things like `this is all + * explained oh-so-wonderfully in some other manual page'. not. + */ + + err = 1; + + if (ipaddr.s_addr != INADDR_NONE) { + /* dot address */ + memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr)); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } 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); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } while (err && errno == EINTR && !errflag); + /* you can check whether it's worth retrying here */ + } + } + + alarm(0); + + if (err < 0) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclose(); + return 1; + } + zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY); + /* now we can talk to the control connection */ + zcfinish = 0; + + len = sizeof(zsock); + if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) { + zwarnnam(name, "getsockname failed: %e", NULL, errno); + zfclose(); + return 1; + } + /* nice to get some options right, ignore if they don't work */ +#ifdef SO_OOBINLINE + /* + * this says we want messages in line. maybe sophisticated people + * do clever things with SIGURG. + */ + len = 1; + setsockopt(zcfd, 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)); +#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"); + + if (!zcin) { + zwarnnam(name, "file handling error", NULL, 0); + zfclose(); + return 1; + } + +#ifdef _IONBF + setvbuf(zcin, NULL, _IONBF, 0); +#else + setlinebuf(zcin); +#endif + + /* + * now see what the remote server has to say about that. + */ + if (zfgetmsg() >= 4) { + zfclose(); + return 1; + } + + zfis_unix = 0; + zfhas_size = zfhas_mdtm = ZFCP_UNKN; + zdfd = -1; + /* initial status: open, ASCII data, stream mode 'n' stuff */ + zfstatus = 0; + + /* open file for saving the current status */ + 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); +#endif + unlink(fname); + + /* now find out what system we're connected to */ + if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) { + char *ptr = lastmsg, *eptr, *systype; + for (eptr = ptr; *eptr; eptr++) + ; + systype = ztrduppfx(ptr, eptr-ptr); + if (!strncmp(systype, "UNIX Type: L8", 13)) { + /* + * Use binary for transfers. This simple test saves much + * hassle for all concerned, particularly me. + */ + zfstatus |= ZFST_IMAG; + zfis_unix = 1; + } + /* + * we could set zfis_unix based just on the UNIX part, + * but I don't really know the consequences of that. + */ + zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); + } else if (zcfd == -1) { + /* final paranoid check */ + return 1; + } + + tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY); + /* if remaining arguments, use them to log in. */ + if (zcfd > -1 && *++args) + return zftp_login(name, args, flags); + /* if something wayward happened, connection was already closed */ + return zcfd == -1; +} + +/* + * Read a parameter string, with a prompt if reading from stdin. + * The returned string is on the heap. + * If noecho, turn off ECHO mode while reading. + */ + +/**/ +static char * +zfgetinfo(char *prompt, int noecho) +{ + int resettty = 0; + /* 256 characters should be enough, hardly worth allocating + * a password string byte by byte + */ + char instr[256], *strret; + int len; + + /* + * Only print the prompt if getting info from a tty. Of + * course, we don't know if stderr has been redirected, but + * that seems a minor point. + */ + if (isatty(0)) { + if (noecho) { + /* hmmm... all this great big shell and we have to read + * something with no echo by ourselves. + * bin_read() is far to complicated for our needs. + * we could use zread(), but that relies on static + * variables, so someone doesn't want that to happen. + * + * this is modified from setcbreak() in utils.c, + * except I don't see any point in using cbreak mode + */ + struct ttyinfo ti; + + ti = shttyinfo; +#ifdef HAS_TIO + ti.tio.c_lflag &= ~ECHO; +#else + ti.sgttyb.sg_flags &= ~ECHO; +#endif + settyinfo(&ti); + resettty = 1; + } + fflush(stdin); + fputs(prompt, stderr); + fflush(stderr); + } + + fgets(instr, 256, stdin); + if (instr[len = strlen(instr)-1] == '\n') + instr[len] = '\0'; + + strret = dupstring(instr); + + if (resettty) { + /* '\n' didn't get echoed */ + fputc('\n', stdout); + fflush(stdout); + settyinfo(&shttyinfo); + } + + return strret; +} + +/* + * set params for an open with no arguments. + * this allows easy re-opens. + */ + +/**/ +static int +zftp_params(char *name, char **args, int flags) +{ + char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " }; + char **aptr, **newarr; + int i, j, len; + + if (!*args) { + if (zfuserparams) { + for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) { + if (i == 2) { + len = strlen(*aptr); + for (j = 0; j < len; j++) + fputc('*', stdout); + fputc('\n', stdout); + } else + fprintf(stdout, "%s\n", *aptr); + } + } + return 0; + } + if (!strcmp(*args, "-")) { + if (zfuserparams) + freearray(zfuserparams); + zfuserparams = 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); + else + str = *aptr; + newarr[i] = ztrdup(str); + } + if (errflag) { + /* maybe user CTRL-c'd in the middle somewhere */ + for (aptr = newarr; *aptr; aptr++) + zsfree(*aptr); + zfree(newarr, len+1); + return 1; + } + if (zfuserparams) + freearray(zfuserparams); + zfuserparams = newarr; + return 0; +} + +/* login a user: often called as part of the open sequence */ + +/**/ +static int +zftp_login(char *name, char **args, int flags) +{ + char *ucmd, *passwd = NULL, *acct = NULL; + char *user; + int stopit; + + if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) + return 1; + + zfstatus &= ~ZFST_LOGI; + if (*args) { + user = *args++; + } else { + user = zfgetinfo("User: ", 0); + } + + ucmd = tricat("USER ", user, "\r\n"); + stopit = 0; + + if (zfsendcmd(ucmd) == 6) + stopit = 2; + + while (!stopit && !errflag) { + switch (lastcode) { + case 230: /* user logged in */ + case 202: /* command not implemented, don't care */ + stopit = 1; + break; + + case 331: /* need password */ + if (*args) + passwd = *args++; + else + passwd = zfgetinfo("Password: ", 1); + zsfree(ucmd); + ucmd = tricat("PASS ", passwd, "\r\n"); + if (zfsendcmd(ucmd) == 6) + stopit = 2; + break; + + case 332: /* need account */ + case 532: + if (*args) + acct = *args++; + else + acct = zfgetinfo("Account: ", 0); + zsfree(ucmd); + ucmd = tricat("ACCT ", passwd, "\r\n"); + if (zfsendcmd(ucmd) == 6) + stopit = 2; + break; + + case 421: /* service not available, so closed anyway */ + case 501: /* syntax error */ + case 503: /* bad commands */ + case 530: /* not logged in */ + case 550: /* random can't-do-that */ + default: /* whatever, should flag this as bad karma */ + /* need more diagnostics here */ + stopit = 2; + break; + } + } + + zsfree(ucmd); + if (zcfd == -1) + return 1; + if (stopit == 2 || (lastcode != 230 && lastcode != 202)) { + zwarnnam(name, "login failed", NULL, 0); + return 1; + } + + if (*args) { + int cnt; + for (cnt = 0; *args; args++) + cnt++; + zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt); + } + zfstatus |= ZFST_LOGI; + zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY); + if (acct) + zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY); + + /* + * Get the directory. This is possibly an unnecessary overhead, of + * course, but when you're being driven by shell functions there's + * just no way of telling. + */ + return zfgetcwd(); +} + +/* do ls or dir on the remote directory */ + +/**/ +static int +zftp_dir(char *name, char **args, int flags) +{ + /* maybe should be cleverer about handling arguments */ + char *cmd; + int ret; + + /* + * RFC959 says this must be ASCII or EBCDIC, not image format. + * I rather suspect on a UNIX server we get away handsomely + * with doing everything, including this, as image. + */ + zfsettype(ZFST_ASCI); + + cmd = zfargstring((flags & ZFTP_NLST) ? "NLST" : "LIST", args); + ret = zfgetdata(name, NULL, cmd, 0); + zsfree(cmd); + if (ret) + return 1; + + fflush(stdout); /* since we're now using fd 1 */ + return zfsenddata(name, 1, 0, 0); +} + +/* change the remote directory */ + +/**/ +static int +zftp_cd(char *name, char **args, int flags) +{ + /* change directory --- enhance to allow 'zftp cdup' */ + int ret; + + if ((flags & ZFTP_CDUP) || !strcmp(*args, "..") || + !strcmp(*args, "../")) { + ret = zfsendcmd("CDUP\r\n"); + } else { + char *cmd = tricat("CWD ", *args, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + } + if (ret > 2) + return 1; + /* sometimes the directory may be in the response. usually not. */ + if (zfgetcwd()) + return 1; + + return 0; +} + +/* get the remote directory */ + +/**/ +static int +zfgetcwd(void) +{ + char *ptr, *eptr; + int endc; + List l; + + if (zfprefs & ZFPF_DUMB) + return 1; + if (zfsendcmd("PWD\r\n") > 2) { + zfunsetparam("ZFTP_PWD"); + return 1; + } + ptr = lastmsg; + while (*ptr == ' ') + ptr++; + if (!*ptr) /* ultra safe */ + return 1; + if (*ptr == '"') { + ptr++; + endc = '"'; + } else + endc = ' '; + for (eptr = ptr; *eptr && *eptr != endc; eptr++) + ; + zfsetparam("ZFTP_PWD", ztrduppfx(ptr, eptr-ptr), ZFPM_READONLY); + + /* + * This isn't so necessary if we're going to have a shell function + * front end. By putting it here, and in close when ZFTP_PWD is unset, + * we at least cover the bases. + */ + if ((l = getshfunc("zftp_chpwd")) != &dummy_list) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + return 0; +} + +/* + * Set the type for the next transfer, usually image (binary) or ASCII. + */ + +/**/ +static int +zfsettype(int type) +{ + char buf[] = "TYPE X\r\n"; + if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus)) + return 0; + buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I'; + if (zfsendcmd(buf) > 2) + return 1; + zfstatus &= ~(ZFST_TMSK << ZFST_TBIT); + /* shift the type left to set the current type bits */; + zfstatus |= type << ZFST_TBIT; + return 0; +} + +/* + * Print or get a new type for the transfer. + * We don't actually set the type at this point. + */ + +/**/ +static int +zftp_type(char *name, char **args, int flags) +{ + char *str, nt, tbuf[2] = "A"; + if (flags & (ZFTP_TBIN|ZFTP_TASC)) { + nt = (flags & ZFTP_TBIN) ? 'I' : 'A'; + } else if (!(str = *args)) { + /* + * 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'); + fflush(stdout); + return 0; + } else { + nt = toupper(*str); + /* + * RFC959 specifies other types, but these are the only + * ones we know what to do with. + */ + if (str[1] || (nt != 'A' && nt != 'B' && nt != 'I')) { + zwarnnam(name, "transfer type %s not recognised", str, 0); + return 1; + } + + if (nt == 'B') /* binary = image */ + nt = 'I'; + } + + zfstatus &= ~ZFST_TMSK; + zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; + tbuf[0] = nt; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + return 0; +} + +/**/ +static int +zftp_mode(char *name, char **args, int flags) +{ + char *str, cmd[] = "MODE X\r\n"; + int nt; + + if (!(str = *args)) { + printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B'); + fflush(stdout); + return 0; + } + nt = str[0] = toupper(*str); + if (str[1] || (nt != 'S' && nt != 'B')) { + zwarnnam(name, "transfer mode %s not recognised", str, 0); + return 1; + } + cmd[5] = (char) nt; + if (zfsendcmd(cmd) > 2) + return 1; + zfstatus &= ZFST_MMSK; + zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; + zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY); + return 0; +} + +/**/ +static int +zftp_local(char *name, char **args, int flags) +{ + int more = !!args[1], ret = 0, dofd = !*args; + while (*args || dofd) { + long sz; + char *mt; + int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt, + dofd ? 0 : -1); + if (newret == 2) /* at least one is not implemented */ + return 2; + else if (newret) { + ret = 1; + if (mt) + zsfree(mt); + args++; + continue; + } + if (more) { + fputs(*args, stdout); + fputc(' ', stdout); + } + printf("%ld %s\n", sz, mt); + zsfree(mt); + if (dofd) + break; + args++; + } + fflush(stdout); + + return ret; +} + +/* + * Generic transfer for get, put and append. + * + * Get sends all files to stdout, i.e. this is basically cat. It's up to a + * shell function driver to turn this into standard FTP-like commands. + * + * Put/append sends everything from stdin down the drai^H^H^Hata connection. + * Slightly weird with multiple files in that it tries to read + * a separate complete file from stdin each time, which is + * only even potentially useful interactively. But the only + * real alternative is just to allow one file at a time. + */ + +/**/ +static int +zftp_getput(char *name, char **args, int flags) +{ + int ret = 0, recv = (flags & ZFTP_RECV), getsize = 0, progress = 1; + char *cmd = recv ? "RETR " : (flags & ZFTP_APPE) ? "APPE " : "STOR "; + List l; + + /* + * At this point I'd like to set progress to 0 if we're + * backgrounded, since it's hard for the user to find out. + * It turns out it's hard enough for us to find out. + * The problem is that zsh clears it's job table, so we + * just don't know if we're some forked shell in a pipeline + * somewhere or in the background. This seems to me a problem. + */ + + zfsettype(ZFST_TYPE(zfstatus)); + + if (recv) + fflush(stdout); /* since we may be using fd 1 */ + for (; *args; args++) { + char *ln, *rest = NULL; + long startat = 0; + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + long sz; + /* + * This calls the SIZE command to get the size for remote + * files. Some servers send the size with the reply to + * the transfer command (i.e. RETR), in which + * case we note the fact and don't call this + * next time. For that reason, the first call + * of zftp_progress is delayed until zfsenddata(). + */ + if ((!(zfprefs & ZFPF_DUMB) && + (zfstatus & (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); + /* even if it doesn't support SIZE, it may tell us */ + if (recv && sz == -1) + getsize = 1; + } else + getsize = 1; + zfstarttrans(*args, recv, sz); + } + + if (flags & ZFTP_REST) { + startat = zstrtol(args[1], NULL, 10); + rest = tricat("REST ", args[1], "\r\n"); + } + + 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)) + ret = 1; + zsfree(ln); + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* progress to finish: ZFTP_TRANSFER set to GF or PF */ + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"), + ZFPM_READONLY); + doshfunc("zftp_progress", l, NULL, 0, 1); + } + if (rest) { + zsfree(rest); + args++; + } + if (errflag) + break; + } + zfendtrans(); + return ret; +} + +/* + * Delete a list of files on the server. We allow a list by analogy with + * `rm'. + */ + +/**/ +static int +zftp_delete(char *name, char **args, int flags) +{ + int ret = 0; + char *cmd, **aptr; + for (aptr = args; *aptr; aptr++) { + cmd = tricat("DELE ", *aptr, "\r\n"); + if (zfsendcmd(cmd) > 2) + ret = 1; + zsfree(cmd); + } + return ret; +} + +/* Create or remove a directory on the server */ + +/**/ +static int +zftp_mkdir(char *name, char **args, int flags) +{ + int ret; + char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ", + *args, "\r\n"); + ret = (zfsendcmd(cmd) > 2); + zsfree(cmd); + return ret; +} + +/* Rename a file on the server */ + +/**/ +static int +zftp_rename(char *name, char **args, int flags) +{ + int ret; + char *cmd; + + cmd = tricat("RNFR ", args[0], "\r\n"); + ret = 1; + if (zfsendcmd(cmd) == 3) { + zsfree(cmd); + cmd = tricat("RNTO ", args[1], "\r\n"); + if (zfsendcmd(cmd) == 2) + ret = 0; + } + zsfree(cmd); + return ret; +} + +/* + * Send random commands, either with SITE or literal. + * In the second case, the user better know what they're doing. + */ + +/**/ +static int +zftp_quote(char *name, char **args, int flags) +{ + int ret = 0; + char *cmd; + + cmd = (flags & ZFTP_SITE) ? zfargstring("SITE", args) + : zfargstring(args[0], args+1); + ret = (zfsendcmd(cmd) > 2); + zsfree(cmd); + + return ret; +} + +/* Close the connection, ending the session */ + +/**/ +static int +zftp_close(char *name, char **args, int flags) +{ + char **aptr; + List l; + zfclosing = 1; + if (zcfinish != 2) { + /* + * haven't had EOF from server, so send a QUIT and get the response. + * maybe we should set a shorter timeout for this to avoid + * CTRL-c rage. + */ + zfsendcmd("QUIT\r\n"); + } + if (zcin) + fclose(zcin); + zcin = NULL; + close(zcfd); + zcfd = -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; + + /* 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) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + /* tidy up status variables, because mess is bad */ + zfclosing = zfdrrrring = 0; + + return 0; +} + +/* Safe front end to zftp_close() from within the package */ + +/**/ +static void +zfclose(void) +{ + if (zcfd != -1) + zftp_close("zftp close", NULL, 0); +} + +/* The builtin command frontend to the rest of the package */ + +/**/ +static int +bin_zftp(char *name, char **args, char *ops, int func) +{ + char fullname[11] = "zftp "; + char *cnam = *args++, *prefs, *ptr; + Zftpcmd zptr; + int n, ret; + + for (zptr = zftpcmdtab; zptr->nam; zptr++) + if (!strcmp(zptr->nam, cnam)) + break; + + if (!zptr->nam) { + zwarnnam(name, "no such subcommand: %s", cnam, 0); + return 1; + } + + /* check number of arguments */ + for (n = 0; args[n]; n++) + ; + if (n < zptr->min || (zptr->max != -1 && n > zptr->max)) { + zwarnnam(name, "wrong no. of arguments for %s", cnam, 0); + return 1; + } + + strcat(fullname, cnam); + if (zfstatfd != -1) { + /* Get the status in case it was set by a forked process */ + int oldstatus = zfstatus; + lseek(zfstatfd, 0, 0); + read(zfstatfd, &zfstatus, sizeof(zfstatus)); + if (zcfd != -1 && (zfstatus & ZFST_CLOS)) { + /* got closed in subshell without us knowing */ + zcfinish = 2; + zfclose(); + } 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)) + zfsetparam("ZFTP_TYPE", + ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ? + "A" : "I"), ZFPM_READONLY); + if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus)) + zfsetparam("ZFTP_MODE", + ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ? + "B" : "S"), ZFPM_READONLY); + } + } + if ((zptr->flags & ZFTP_CONN) && zcfd == -1) { + zwarnnam(fullname, "not connected.", NULL, 0); + return 1; + } + + if ((prefs = getsparam("ZFTP_PREFS"))) { + zfprefs = 0; + for (ptr = prefs; *ptr; ptr++) { + switch (toupper(*ptr)) { + case 'S': + /* sendport */ + zfprefs |= ZFPF_SNDP; + break; + + case 'P': + /* + * passive + * If we have already been told to use sendport mode, + * we're never going to use passive mode. + */ + if (!(zfprefs & ZFPF_SNDP)) + zfprefs |= ZFPF_PASV; + break; + + case 'D': + /* dumb */ + zfprefs |= ZFPF_DUMB; + break; + + default: + zwarnnam(name, "preference %c not recognized", NULL, *ptr); + break; + } + } + } + + ret = (*zptr->fun)(fullname, args, zptr->flags); + + if (zfalarmed) + zfunalarm(); + if (zfdrrrring) { + /* had a timeout, close the connection */ + zcfinish = 2; /* don't try sending QUIT */ + zfclose(); + } + if (zfstatfd != -1) { + /* Set the status in case another process needs to know */ + lseek(zfstatfd, 0, 0); + write(zfstatfd, &zfstatus, sizeof(zfstatus)); + } + return ret; +} + +/* The load/unload routines required by the zsh library interface */ + +/**/ +int +boot_zftp(Module m) +{ + int ret; + if ((ret = addbuiltins(m->nam, bintab, + sizeof(bintab)/sizeof(*bintab))) == 1) { + /* if successful, set some default parameters */ + long tmout_def = 60; + zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET); + zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER); + zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET); + /* default preferences if user deletes variable */ + zfprefs = ZFPF_SNDP|ZFPF_PASV; + } + return !ret; +} + +#ifdef MODULE + +/**/ +int +cleanup_zftp(Module m) +{ + /* + * There are various parameters hanging around, but they're + * all non-special so are entirely non-life-threatening. + */ + zfclosedata(); + zfclose(); + zsfree(lastmsg); + if (zfuserparams) + freearray(zfuserparams); + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Modules/zftp.mdd b/Src/Modules/zftp.mdd new file mode 100644 index 000000000..83051ae54 --- /dev/null +++ b/Src/Modules/zftp.mdd @@ -0,0 +1,3 @@ +autobins="zftp" + +objects="zftp.o" diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c index 68aa5ef52..5ffce0da2 100644 --- a/Src/Zle/comp1.c +++ b/Src/Zle/comp1.c @@ -44,7 +44,7 @@ Cmlist cmatcher; /* pointers to functions required by zle */ /**/ -void (*printcompctlptr) _((char *, Compctl, int)); +void (*printcompctlptr) _((char *, Compctl, int, int)); /**/ Compctl (*compctl_widgetptr) _((char *, char **)); diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 254021bed..57b75cd39 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -605,7 +605,7 @@ execzlefunc(Thingy func) } else { startparamscope(); makezleparams(); - doshfunc(l, NULL, 0, 1); + doshfunc(w->u.fnnam, l, NULL, 0, 1); endparamscope(); lastcmd = 0; } diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c index ed1420829..e0c4e94ec 100644 --- a/Src/Zle/zle_params.c +++ b/Src/Zle/zle_params.c @@ -72,6 +72,9 @@ makezleparams(void) for(zp = zleparams; zp->name; zp++) { Param pm = createparam(zp->name, zp->type | PM_SPECIAL); + if (!pm) + pm = (Param) paramtab->getnode(paramtab, zp->name); + DPUTS(!pm, "param not set in makezleparams"); pm->level = locallevel; pm->u.data = zp->data; diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c index f8de14f17..68329be65 100644 --- a/Src/Zle/zle_thingy.c +++ b/Src/Zle/zle_thingy.c @@ -394,7 +394,7 @@ scanlistwidgets(HashNode hn, int list) quotedzputs(t->nam, stdout); if (w->flags & WIDGET_COMP) { if (printcompctlptr && w->u.cc) - printcompctlptr(NULL, w->u.cc, PRINT_LIST); + printcompctlptr(NULL, w->u.cc, PRINT_LIST, 0); } else if(strcmp(t->nam, w->u.fnnam)) { fputc(' ', stdout); quotedzputs(w->u.fnnam, stdout); @@ -404,7 +404,7 @@ scanlistwidgets(HashNode hn, int list) if (w->flags & WIDGET_COMP) { fputs(" -C", stdout); if (printcompctlptr && w->u.cc) - printcompctlptr(NULL, w->u.cc, PRINT_TYPE); + printcompctlptr(NULL, w->u.cc, PRINT_TYPE, 0); } else if(strcmp(t->nam, w->u.fnnam)) { fputs(" (", stdout); nicezputs(w->u.fnnam, stdout); diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index a8998739c..cbc744601 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -2730,18 +2730,16 @@ maketildelist(void) /**/ static int -getcpat(char *wrd, int cpatindex, char *cpat, int class) +getcpat(char *str, int cpatindex, char *cpat, int class) { - char *str, *s, *t, *p; + char *s, *t, *p; int d = 0; - if (!wrd || !*wrd) + if (!str || !*str) return -1; cpat = rembslash(cpat); - str = ztrdup(wrd); - untokenize(str); if (!cpatindex) cpatindex++, d = 0; else if ((d = (cpatindex < 0))) @@ -2752,23 +2750,14 @@ getcpat(char *wrd, int cpatindex, char *cpat, int class) d ? s-- : s++) { for (t = s, p = cpat; *t && *p; p++) { if (class) { - if (*p == *s && !--cpatindex) { - zsfree(str); + if (*p == *s && !--cpatindex) return (int)(s - str + 1); - } } else if (*t++ != *p) break; } - if (!class && !*p && !--cpatindex) { - zsfree(str); - t += wrd - str; - for (d = 0; --t >= wrd;) - if (! INULL(*t)) - d++; - return d; - } + if (!class && !*p && !--cpatindex) + return t - str; } - zsfree(str); return -1; } @@ -3336,8 +3325,9 @@ makecomplistext(Compctl occ, char *os, int incmd) break; case CCT_CURSUF: case CCT_CURPRE: - s = ztrdup(clwpos < clwnum ? clwords[clwpos] : ""); + s = ztrdup(clwpos < clwnum ? os : ""); untokenize(s); + if (isset(COMPLETEINWORD)) s[offs] = '\0'; sc = rembslash(cc->u.s.s[i]); a = strlen(sc); if (!strncmp(s, sc, a)) { @@ -3347,10 +3337,13 @@ makecomplistext(Compctl occ, char *os, int incmd) break; case CCT_CURSUB: case CCT_CURSUBC: - if (clwpos < 0 || clwpos > clwnum) + if (clwpos < 0 || clwpos >= clwnum) t = 0; else { - a = getcpat(clwords[clwpos], + s = ztrdup(os); + untokenize(s); + if (isset(COMPLETEINWORD)) s[offs] = '\0'; + a = getcpat(s, cc->u.s.p[i], cc->u.s.s[i], cc->type == CCT_CURSUBC); @@ -4107,7 +4100,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) /* This flag allows us to use read -l and -c. */ incompctlfunc = 1; /* Call the function. */ - doshfunc(list, args, 0, 1); + doshfunc(cc->func, list, args, 0, 1); incompctlfunc = 0; /* And get the result from the reply parameter. */ if ((r = get_user_var("reply"))) @@ -4270,7 +4263,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) /* No harm in allowing read -l and -c here, too */ incompctlfunc = 1; - doshfunc(list, args, 0, 1); + doshfunc(cc->ylist, list, args, 0, 1); incompctlfunc = 0; uv = "reply"; } @@ -4912,7 +4905,7 @@ do_ambiguous(void) * if it is needed. */ if (isset(LISTBEEP)) feep(); - if (isset(AUTOLIST) && !amenu && !showinglist) + if (isset(AUTOLIST) && !amenu && !showinglist && smatches >= 2) showinglist = -2; if (am) lastambig = 1; @@ -5233,14 +5226,10 @@ listmatches(void) Cmgroup g; Cmatch *p, m; Cexpl *e; - int nlines = 0, ncols, colsz, ngr = 0, nlist = 0, longest = 1, pnl = 0; + int nlines = 0, ncols, nlist = 0, longest = 1, pnl = 0; int of = isset(LISTTYPES), opl = 0; int listmax = getiparam("LISTMAX"); - if (smatches < 2) { - showinglist = 0; - return; - } #ifdef DEBUG /* Sanity check */ if(!validlist) { @@ -5310,16 +5299,13 @@ listmatches(void) e++; } } - if (g->lcount) - ngr++; } longest += 2 + of; if ((ncols = (columns + 1) / longest)) { - colsz = (nlist + ncols - 1) / ncols; - nlines += ngr - 1 + colsz + (nlist == 0); + for (g = amatches; g; g = g->next) + nlines += (g->lcount + ncols - 1) / ncols; } else { ncols = 1; - colsz = 1; opl = 1; for (g = amatches; g; g = g->next) { char **pp = g->ylist; @@ -5396,12 +5382,11 @@ listmatches(void) } } else { - int n = g->lcount, nl = (n + ncols - 1) / ncols, i, a; - int nc = (opl ? 1 : (n + colsz - 1) / colsz); + int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, a; char **pq; while (n && nl--) { - i = nc; + i = ncols; pq = pp; while (n && i--) { if (pq - g->ylist >= g->lcount) @@ -5412,7 +5397,7 @@ listmatches(void) while (a--) putc(' ', shout); } - pq += colsz; + pq += nc; n--; } if (n) @@ -5422,8 +5407,7 @@ listmatches(void) } } else if (g->lcount) { - int n = g->lcount, nl = (n + ncols - 1) / ncols, i, j, a; - int nc = (opl ? 1 : (n + colsz - 1) / colsz); + int n = g->lcount, nl = (n + ncols - 1) / ncols, nc = nl, i, j, a; Cmatch *q; if (n && pnl) { @@ -5431,7 +5415,7 @@ listmatches(void) pnl = 0; } for (p = skipnolist(g->matches); n && nl--;) { - i = nc; + i = ncols; q = p; while (n && i--) { if (!(m = *q)) @@ -5460,7 +5444,7 @@ listmatches(void) while (a--) putc(' ', shout); if (--n) - for (j = colsz; j && *q; j--) + for (j = nc; j && *q; j--) q = skipnolist(q + 1); } if (n) { diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c index 46c6a705b..e699e438c 100644 --- a/Src/Zle/zle_vi.c +++ b/Src/Zle/zle_vi.c @@ -567,7 +567,9 @@ vioperswapcase(void) } /* go back to the first line of the range */ cs = oldcs; +#if 0 vifirstnonblank(); +#endif } vichgflag = 0; } diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c index 923216ef8..afd860066 100644 --- a/Src/Zle/zle_word.c +++ b/Src/Zle/zle_word.c @@ -73,7 +73,7 @@ viforwardword(void) cs++; if (wordflag && !n) return; - while (cs != ll && iblank(line[cs])) + while (cs != ll && (iblank(line[cs]) || line[cs] == '\n')) cs++; } } diff --git a/Src/builtin.c b/Src/builtin.c index dbe91a5b1..7e77bc190 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -120,7 +120,7 @@ static struct builtin builtins[] = BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"), #ifdef DYNAMIC - BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "Laudi", NULL), + BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "LaudicI", NULL), #endif }; @@ -994,7 +994,7 @@ cd_new_pwd(int func, LinkNode dir, int chaselinks) if ((l = getshfunc("chpwd")) != &dummy_list) { fflush(stdout); fflush(stderr); - doshfunc(l, NULL, 0, 1); + doshfunc("chpwd", l, NULL, 0, 1); } dirstacksize = getiparam("DIRSTACKSIZE"); diff --git a/Src/cond.c b/Src/cond.c index 79886a720..ed91f72f3 100644 --- a/Src/cond.c +++ b/Src/cond.c @@ -43,6 +43,26 @@ evalcond(Cond c) return evalcond(c->left) && evalcond(c->right); case COND_OR: return evalcond(c->left) || evalcond(c->right); + case COND_MOD: + case COND_MODI: + { + Conddef cd; + + if ((cd = getconddef((c->type == COND_MODI), (char *) c->left, 1))) { + if (c->type == COND_MOD) { + int l = arrlen((char **) c->right); + + if (l < cd->min || (cd->max >= 0 && l > cd->max)) { + zerr("unrecognized condition: `-%s'", (char *) c->left, 0); + return 0; + } + } + return cd->handler(cd, (char **) c->right); + } + else + zerr("unrecognized condition: `-%s'", (char *) c->left, 0); + return 0; + } } singsub((char **)&c->left); untokenize(c->left); diff --git a/Src/exec.c b/Src/exec.c index bb70e59c8..a2d74a9f4 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -2602,7 +2602,7 @@ execshfunc(Cmd cmd, Shfunc shf) deletejob(jobtab + thisjob); } - doshfunc(shf->funcdef, cmd->args, shf->flags, 0); + doshfunc(shf->nam, shf->funcdef, cmd->args, shf->flags, 0); if (!list_pipe) deletefilelist(last_file_list); @@ -2650,14 +2650,13 @@ execautofn(Cmd cmd) /**/ void -doshfunc(List list, LinkList doshargs, int flags, int noreturnval) +doshfunc(char *name, List list, LinkList doshargs, int flags, int noreturnval) /* If noreturnval is nonzero, then reset the current return * * value (lastval) to its value before the shell function * * was executed. */ { char **tab, **x, *oargv0 = NULL; int xexittr, newexittr, oldzoptind, oldlastval; - char *ou; void *xexitfn, *newexitfn; char saveopts[OPT_SIZE]; int obreaks = breaks; @@ -2705,14 +2704,7 @@ doshfunc(List list, LinkList doshargs, int flags, int noreturnval) argzero = ztrdup(argzero); } } - startparamscope(); - ou = underscore; - underscore = ztrdup(underscore); - execlist(dupstruct(list), 1, 0); - zsfree(underscore); - underscore = ou; - endparamscope(); - + runshfunc(list, wrappers, name); if (retflag) { retflag = 0; breaks = obreaks; @@ -2767,6 +2759,44 @@ doshfunc(List list, LinkList doshargs, int flags, int noreturnval) } LASTALLOC; } +/* This finally executes a shell function and any function wrappers * + * defined by modules. This works by calling the wrapper function which * + * in turn has to call back this function with the arguments it gets. */ + +/**/ +void +runshfunc(List list, FuncWrap wrap, char *name) +{ + int cont; + char *ou; + + while (wrap) { + wrap->module->flags |= MOD_WRAPPER; + wrap->count++; + cont = wrap->handler(list, wrap->next, name); + wrap->count--; + if (!wrap->count) { + wrap->module->flags &= ~MOD_WRAPPER; +#ifdef DYNAMIC + if (wrap->module->flags & MOD_UNLOAD) { + wrap->module->flags &= ~MOD_UNLOAD; + unload_module(wrap->module, NULL); + } +#endif + } + if (!cont) + return; + wrap = wrap->next; + } + startparamscope(); + ou = underscore; + underscore = ztrdup(underscore); + execlist(dupstruct(list), 1, 0); + zsfree(underscore); + underscore = ou; + endparamscope(); +} + /* Search fpath for an undefined function. Finds the file, and returns the * * list of its contents. */ diff --git a/Src/glob.c b/Src/glob.c index ea5d0133c..194d535a4 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -129,6 +129,8 @@ struct comp { static char *pptr; /* current place in string being matched */ static Comp tail; static int first; /* are leading dots special? */ +static int longest; /* always match longest piece of path. */ +static int inclosure; /* see comment in doesmatch() */ /* Add a component to pathbuf: This keeps track of how * * far we are into a file name, since each path component * @@ -1806,41 +1808,62 @@ matchpat(char *a, char *b) /* do the ${foo%%bar}, ${foo#bar} stuff */ /* please do not laugh at this code. */ +struct repldata { + int b, e; /* beginning and end of chunk to replace */ +}; +typedef struct repldata *Repldata; + +/* + * List of bits of matches to concatenate with replacement string. + * The data is a struct repldata. It is not used in cases like + * ${...//#foo/bar} even though SUB_GLOBAL is set, since the match + * is anchored. It goes on the heap. + */ + +static LinkList repllist; + /* Having found a match in getmatch, decide what part of string * to return. The matched part starts b characters into string s * and finishes e characters in: 0 <= b <= e <= strlen(s) * (yes, empty matches should work). - * Bits 3 and higher in fl are used: the flags are - * 8: Result is matched portion. - * 16: Result is unmatched portion. - * (N.B. this should be set for standard ${foo#bar} etc. matches.) - * 32: Result is numeric position of start of matched portion. - * 64: Result is numeric position of end of matched portion. - * 128: Result is length of matched portion. + * fl is a set of the SUB_* matches defined in zsh.h from SUB_MATCH onwards; + * the lower parts are ignored. + * replstr is the replacement string for a substitution */ /**/ static char * -get_match_ret(char *s, int b, int e, int fl) +get_match_ret(char *s, int b, int e, int fl, char *replstr) { char buf[80], *r, *p, *rr; int ll = 0, l = strlen(s), bl = 0, t = 0, i; - if (fl & 8) /* matched portion */ + if (replstr) { + if ((fl & SUB_GLOBAL) && repllist) { + /* We are replacing the chunk, just add this to the list */ + Repldata rd = (Repldata) halloc(sizeof(*rd)); + rd->b = b; + rd->e = e; + addlinknode(repllist, rd); + return s; + } + ll += strlen(replstr); + } + if (fl & SUB_MATCH) /* matched portion */ ll += 1 + (e - b); - if (fl & 16) /* unmatched portion */ + if (fl & SUB_REST) /* unmatched portion */ ll += 1 + (l - (e - b)); - if (fl & 32) { + if (fl & SUB_BIND) { /* position of start of matched portion */ sprintf(buf, "%d ", b + 1); ll += (bl = strlen(buf)); } - if (fl & 64) { + if (fl & SUB_EIND) { /* position of end of matched portion */ sprintf(buf + bl, "%d ", e + 1); ll += (bl = strlen(buf)); } - if (fl & 128) { + if (fl & SUB_LEN) { /* length of matched portion */ sprintf(buf + bl, "%d ", e - b); ll += (bl = strlen(buf)); @@ -1850,13 +1873,13 @@ get_match_ret(char *s, int b, int e, int fl) rr = r = (char *)ncalloc(ll); - if (fl & 8) { + if (fl & SUB_MATCH) { /* copy matched portion to new buffer */ for (i = b, p = s + b; i < e; i++) *rr++ = *p++; t = 1; } - if (fl & 16) { + if (fl & SUB_REST) { /* Copy unmatched portion to buffer. If both portions * * requested, put a space in between (why?) */ if (t) @@ -1864,6 +1887,9 @@ get_match_ret(char *s, int b, int e, int fl) /* there may be unmatched bits at both beginning and end of string */ for (i = 0, p = s; i < b; i++) *rr++ = *p++; + if (replstr) + for (p = replstr; *p; ) + *rr++ = *p++; for (i = e, p = s + e; i < l; i++) *rr++ = *p++; t = 1; @@ -1879,64 +1905,102 @@ get_match_ret(char *s, int b, int e, int fl) return r; } -/* It is called from paramsubst to get the match for ${foo#bar} etc. - * Bits of fl determines the required action: - * bit 0: match the end instead of the beginning (% or %%) - * bit 1: % or # was doubled so get the longest match - * bit 2: substring match - * bit 3: include the matched portion - * bit 4: include the unmatched portion - * bit 5: the index of the beginning - * bit 6: the index of the end - * bit 7: the length of the match - * bit 8: match the complete string +/* + * Run the pattern so that we always get the longest possible match. + * This eliminates a loop where we gradually shorten the target string + * to find same. We also need to check pptr (the point successfully + * reached along the target string) explicitly. + * + * For this to work, we need the full hairy closure code, so + * set inclosure. + */ + +/**/ +static int +dolongestmatch(char *str, Comp c, int fist) +{ + int ret; + longest = 1; + inclosure++; + ret = domatch(str, c, fist); + inclosure--; + longest = 0; + return ret; +} + +/* + * This is called from paramsubst to get the match for ${foo#bar} etc. + * fl is a set of the SUB_* flags defined in zsh.h * *sp points to the string we have to modify. The n'th match will be * returned in *sp. ncalloc is used to get memory for the result string. + * replstr is the replacement string from a ${.../orig/repl}, in + * which case pat is the original. + * + * n is now ignored unless we are looking for a substring, in + * which case the n'th match from the start is counted such that + * there is no more than one match from each position. */ /**/ int -getmatch(char **sp, char *pat, int fl, int n) +getmatch(char **sp, char *pat, int fl, int n, char *replstr) { Comp c; - char *s = *sp, *t, sav; - int i, j, l = strlen(*sp); + char *s = *sp, *t, *start, sav; + int i, j, l = strlen(*sp), matched; + MUSTUSEHEAP("getmatch"); /* presumably covered by prefork() test */ + repllist = NULL; c = parsereg(pat); if (!c) { zerr("bad pattern: %s", pat, 0); return 1; } - if (fl & 256) { + if (fl & SUB_ALL) { i = domatch(s, c, 0); - *sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl); - if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i))) + *sp = get_match_ret(*sp, 0, i ? l : 0, fl, i ? replstr : 0); + if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i))) return 0; return 1; } - switch (fl & 7) { + switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) { case 0: - /* Smallest possible match at head of string: * - * start adding characters until we get a match. */ - for (i = 0, t = s; i <= l; i++, t++) { - sav = *t; - *t = '\0'; - if (domatch(s, c, 0) && !--n) { + case SUB_LONG: + /* + * Largest/smallest possible match at head of string. + * First get the longest match. + */ + if (dolongestmatch(s, c, 0)) { + char *mpos = pptr; + while (!(fl & SUB_LONG) && pptr > s) { + /* + * If we want the shortest, keep backing up to the + * previous character and find the longest up to there. + * That way we can usually reach the shortest in only + * a few attempts. + */ + t = (pptr > s + 1 && pptr[-2] == Meta) ? pptr - 2 : pptr -1; + sav = *t; + *t = '\0'; + if (!dolongestmatch(s, c, 0)) { + *t = sav; + break; + } + mpos = pptr; *t = sav; - *sp = get_match_ret(*sp, 0, i, fl); - return 1; } - if ((*t = sav) == Meta) - i++, t++; + *sp = get_match_ret(*sp, 0, mpos-s, fl, replstr); + return 1; } break; - case 1: + case SUB_END: /* Smallest possible match at tail of string: * - * move back down string until we get a match. */ + * move back down string until we get a match. * + * There's no optimization here. */ for (t = s + l; t >= s; t--) { - if (domatch(t, c, 0) && !--n) { - *sp = get_match_ret(*sp, t - s, l, fl); + if (domatch(t, c, 0)) { + *sp = get_match_ret(*sp, t - s, l, fl, replstr); return 1; } if (t > s+1 && t[-2] == Meta) @@ -1944,29 +2008,13 @@ getmatch(char **sp, char *pat, int fl, int n) } break; - case 2: - /* Largest possible match at head of string: * - * delete characters from end until we get a match. */ - for (t = s + l; t > s; t--) { - sav = *t; - *t = '\0'; - if (domatch(s, c, 0) && !--n) { - *t = sav; - *sp = get_match_ret(*sp, 0, t - s, fl); - return 1; - } - *t = sav; - if (t >= s+2 && t[-2] == Meta) - t--; - } - break; - - case 3: + case (SUB_END|SUB_LONG): /* Largest possible match at tail of string: * - * move forward along string until we get a match. */ + * move forward along string until we get a match. * + * Again there's no optimisation. */ for (i = 0, t = s; i < l; i++, t++) { - if (domatch(t, c, 0) && !--n) { - *sp = get_match_ret(*sp, i, l, fl); + if (domatch(t, c, 0)) { + *sp = get_match_ret(*sp, i, l, fl, replstr); return 1; } if (*t == Meta) @@ -1974,110 +2022,147 @@ getmatch(char **sp, char *pat, int fl, int n) } break; - case 4: + case SUB_SUBSTR: /* Smallest at start, but matching substrings. */ - if (domatch(s + l, c, 0) && !--n) { - *sp = get_match_ret(*sp, 0, 0, fl); + if (!(fl & SUB_GLOBAL) && domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, 0, 0, fl, replstr); return 1; - } - for (i = 1; i <= l; i++) { - for (t = s, j = i; j <= l; j++, t++) { - sav = s[j]; - s[j] = '\0'; - if (domatch(t, c, 0) && !--n) { - s[j] = sav; - *sp = get_match_ret(*sp, t - s, j, fl); - return 1; + } /* fall through */ + case (SUB_SUBSTR|SUB_LONG): + /* longest or smallest at start with substrings */ + start = s; + if (fl & SUB_GLOBAL) + repllist = newlinklist(); + do { + /* loop over all matches for global substitution */ + matched = 0; + for (t = start; t < s + l; t++) { + /* Find the longest match from this position. */ + if (dolongestmatch(t, c, 0) && pptr > t) { + char *mpos = pptr; + while (!(fl & SUB_LONG) && pptr > t) { + /* Now reduce to find the smallest match */ + char *p = (pptr > t + 1 && pptr[-2] == Meta) ? + pptr - 2 : pptr - 1; + sav = *p; + *p = '\0'; + if (!dolongestmatch(t, c, 0)) { + *p = sav; + break; + } + mpos = pptr; + *p = sav; + } + if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) + *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr); + if (!(fl & SUB_GLOBAL)) { + if (n) { + /* + * Looking for a later match: in this case, + * we can continue looking for matches from + * the next character, even if it overlaps + * with what we just found. + */ + continue; + } else + return 1; + } + /* + * For a global match, we need to skip the stuff + * which is already marked for replacement. + */ + matched = 1; + start = mpos; + break; } - if ((s[j] = sav) == Meta) - j++; if (*t == Meta) t++; } - if (s[i] == Meta) - i++; + } while (matched); + /* + * check if we can match a blank string, if so do it + * at the start. Goodness knows if this is a good idea + * with global substitution, so it doesn't happen. + */ + if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG && + domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, 0, 0, fl, replstr); + return 1; } break; - case 5: - /* Smallest at end, matching substrings */ + case (SUB_END|SUB_SUBSTR): + /* Shortest at end with substrings */ if (domatch(s + l, c, 0) && !--n) { - *sp = get_match_ret(*sp, l, l, fl); + *sp = get_match_ret(*sp, l, l, fl, replstr); return 1; - } - for (i = l; i--;) { - if (i && s[i-1] == Meta) - i--; - for (t = s + l, j = i; j >= 0; j--, t--) { - sav = *t; - *t = '\0'; - if (domatch(s + j, c, 0) && !--n) { - *t = sav; - *sp = get_match_ret(*sp, j, t - s, fl); - return 1; - } - *t = sav; - if (t >= s+2 && t[-2] == Meta) - t--; - if (j >= 2 && s[j-2] == Meta) - j--; - } - } - break; - - case 6: - /* Largest at start, matching substrings. */ - for (i = l; i; i--) { - for (t = s, j = i; j <= l; j++, t++) { - sav = s[j]; - s[j] = '\0'; - if (domatch(t, c, 0) && !--n) { - s[j] = sav; - *sp = get_match_ret(*sp, t - s, j, fl); - return 1; + } /* fall through */ + case (SUB_END|SUB_LONG|SUB_SUBSTR): + /* Longest/shortest at end, matching substrings. */ + for (t = s + l - 1; t >= s; t--) { + if (t > s && t[-1] == Meta) + t--; + if (dolongestmatch(t, c, 0) && pptr > t && !--n) { + /* Found the longest match */ + char *mpos = pptr; + while (!(fl & SUB_LONG) && pptr > t) { + /* Look for the shortest match */ + char *p = (pptr > t+1 && pptr[-2] == Meta) ? + pptr-2 : pptr-1; + sav = *p; + *p = '\0'; + if (!dolongestmatch(t, c, 0) || pptr == t) { + *p = sav; + break; + } + *p = sav; + mpos = pptr; } - if ((s[j] = sav) == Meta) - j++; - if (*t == Meta) - t++; + *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr); + return 1; } - if (i >= 2 && s[i-2] == Meta) - i--; } - if (domatch(s + l, c, 0) && !--n) { - *sp = get_match_ret(*sp, 0, 0, fl); + if ((fl & SUB_LONG) && domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, l, l, fl, replstr); return 1; } break; + } - case 7: - /* Largest at end, matching substrings. */ - for (i = 0; i < l; i++) { - for (t = s + l, j = i; j >= 0; j--, t--) { - sav = *t; - *t = '\0'; - if (domatch(s + j, c, 0) && !--n) { - *t = sav; - *sp = get_match_ret(*sp, j, t - s, fl); - return 1; - } - *t = sav; - if (t >= s+2 && t[-2] == Meta) - t--; - if (j >= 2 && s[j-2] == Meta) - j--; - } - if (s[i] == Meta) - i++; + if (repllist && nonempty(repllist)) { + /* Put all the bits of a global search and replace together. */ + LinkNode nd; + Repldata rd; + int rlen; + int lleft = 0; /* size of returned string */ + + i = 0; /* start of last chunk we got from *sp */ + rlen = strlen(replstr); + for (nd = firstnode(repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + lleft += rd->b - i; /* previous chunk of *sp */ + lleft += rlen; /* the replaced bit */ + i = rd->e; /* start of next chunk of *sp */ } - if (domatch(s + l, c, 0) && !--n) { - *sp = get_match_ret(*sp, l, l, fl); - return 1; + lleft += l - i; /* final chunk from *sp */ + start = t = halloc(lleft+1); + i = 0; + for (nd = firstnode(repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + memcpy(t, s + i, rd->b - i); + t += rd->b - i; + memcpy(t, replstr, rlen); + t += rlen; + i = rd->e; } - break; + memcpy(t, s + i, l - i); + start[lleft] = '\0'; + *sp = start; + return 1; } - /* munge the whole string */ - *sp = get_match_ret(*sp, 0, 0, fl); + + /* munge the whole string: no match, so no replstr */ + *sp = get_match_ret(*sp, 0, 0, fl, 0); return 1; } @@ -2109,9 +2194,15 @@ static int excluded(Comp c, char *eptr) { char *saves = pptr; - int savei = first, ret; + int savei = first, savel = longest, ret; first = 0; + /* + * Even if we've been told always to match the longest string, + * i.e. not anchored to the end, we want to match the full string + * we've already matched when we're trying to exclude it. + */ + longest = 0; if (PATHADDP(c) && pathpos) { VARARR(char, buf, pathpos + strlen(eptr) + 1); @@ -2128,6 +2219,7 @@ excluded(Comp c, char *eptr) pptr = saves; first = savei; + longest = savel; return ret; } @@ -2138,8 +2230,6 @@ struct gclose { }; typedef struct gclose *Gclose; -static int inclosure; /* see comment in doesmatch() */ - /* Add a list of matches that fit the closure. trystring is a string of * the same length as the target string; a non-zero in that string * indicates that we have already tried to match the patterns following @@ -2182,6 +2272,15 @@ addclosures(Comp c, LinkList closlist, int *pdone, char *trystring) } } +/* + * Match characters with case-insensitivity. + * Note CHARMATCH(x,y) != CHARMATCH(y,x) + */ +#define CHARMATCH(x, y) \ +(x == y || (((c->stat & C_IGNCASE) ? (tulower(x) == tulower(y)) : \ + (c->stat & C_LCMATCHUC) ? (islower(y) && tuupper(y) == x) : 0))) + + /* see if current string in pptr matches c */ /**/ @@ -2219,7 +2318,7 @@ doesmatch(Comp c) for (; *pptr; pptr++) { if (*pptr == Meta) pptr++; - else if (*pptr == looka) + else if (CHARMATCH(*pptr, looka)) break; } if (!*(saves = pptr)) @@ -2233,7 +2332,7 @@ doesmatch(Comp c) for (done = 0; ; done++) { saves = pptr; if ((done || ONEHASHP(c) || OPTIONALP(c)) && - ((!c->next && (!LASTP(c) || !*pptr)) || + ((!c->next && (!LASTP(c) || !*pptr || longest)) || (c->next && doesmatch(c->next)))) return 1; if (done && OPTIONALP(c)) @@ -2267,7 +2366,7 @@ doesmatch(Comp c) break; saves = pptr; /* do we really want this LASTP here?? */ - if ((!c->next && (!LASTP(c) || !*pptr)) || + if ((!c->next && (!LASTP(c) || !*pptr || longest)) || (c->next && doesmatch(c->next))) { retflag = 1; break; @@ -2453,7 +2552,7 @@ matchonce(Comp c) if (!ret) break; if ((ret = ret2 && - ((!c->next && (!LASTP(c) || !*pptr)) + ((!c->next && (!LASTP(c) || !*pptr || longest)) || (c->next && doesmatch(c->next)))) || (!c->next && LASTP(c))) break; @@ -2485,7 +2584,7 @@ matchonce(Comp c) if (CLOSUREP(c)) return 1; if (!c->next) /* no more patterns left */ - return (!LASTP(c) || !*pptr); + return (!LASTP(c) || !*pptr || longest); /* optimisation when next pattern is not a closure */ if (!CLOSUREP(c->next)) { c = c->next; @@ -2589,10 +2688,7 @@ matchonce(Comp c) } continue; } - if (*pptr == *pat || - (((c->stat & C_IGNCASE) ? (tulower(*pat) == tulower(*pptr)) : - (c->stat & C_LCMATCHUC) ? - (islower(*pat) && tuupper(*pat) == *pptr) : 0))) { + if (CHARMATCH(*pptr, *pat)) { /* just plain old characters */ pptr++; pat++; diff --git a/Src/hashtable.c b/Src/hashtable.c index 5bcfec231..60fb3df80 100644 --- a/Src/hashtable.c +++ b/Src/hashtable.c @@ -414,9 +414,10 @@ scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc HashNode hn = st.u.u; st.u.u = st.u.u->next; if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2) && - domatch(hn->nam, com, 0)) + domatch(hn->nam, com, 0)) { scanfunc(hn, scanflags); match++; + } } ht->scan = NULL; diff --git a/Src/init.c b/Src/init.c index 33496adc6..decc7617e 100644 --- a/Src/init.c +++ b/Src/init.c @@ -117,7 +117,7 @@ loop(int toplevel, int justonce) if (he && he->text) addlinknode(args, he->text); } LASTALLOC; - doshfunc(prelist, args, 0, 1); + doshfunc("preexec", prelist, args, 0, 1); freelinklist(args, (FreeFunc) NULL); errflag = 0; } @@ -592,6 +592,9 @@ setupvals(void) createnameddirtable(); /* create hash table for named directories */ createparamtable(); /* create paramater hash table */ + condtab = NULL; + wrappers = NULL; + #ifdef TIOCGWINSZ adjustwinsize(); #else diff --git a/Src/mem.c b/Src/mem.c index 1145f8c5e..32822ab8c 100644 --- a/Src/mem.c +++ b/Src/mem.c @@ -244,7 +244,7 @@ halloc(size_t size) size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1); #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) - h_m[size < 1024 ? (size / H_ISIZE) : 1024]++; + h_m[size < (1024 * H_ISIZE) ? (size / H_ISIZE) : 1024]++; #endif /* find a heap with enough free space */ @@ -319,6 +319,9 @@ hrealloc(char *p, size_t old, size_t new) if (new > old) { char *ptr = (char *) halloc(new); memcpy(ptr, p, old); +#ifdef ZSH_MEM_DEBUG + memset(p, 0xff, old); +#endif return ptr; } else return new ? p : NULL; @@ -1004,8 +1007,9 @@ zfree(void *p, int sz) long n = (m_lfree->len - M_MIN) & ~(m_pgsz - 1); m_lfree->len -= n; - if (brk(m_high -= n) == -1) + if (brk(m_high -= n) == -1) { DPUTS(1, "MEM: allocation error at brk."); + } #ifdef ZSH_MEM_DEBUG m_b += n; diff --git a/Src/module.c b/Src/module.c index 91687a21d..8ed4f1d3b 100644 --- a/Src/module.c +++ b/Src/module.c @@ -90,6 +90,35 @@ addbuiltins(char const *nam, Builtin binl, int size) return hadf ? hads : 1; } +/* The list of function wrappers defined. */ + +/**/ +FuncWrap wrappers; + +/* This adds a definition for a wrapper. Return value is one in case of * + * error and zero if all went fine. */ + +/**/ +int +addwrapper(Module m, FuncWrap w) +{ + FuncWrap p, q; + + if (w->flags & WRAPF_ADDED) + return 1; + for (p = wrappers, q = NULL; p; q = p, p = p->next); + if (q) + q->next = w; + else + wrappers = w; + w->next = NULL; + w->flags |= WRAPF_ADDED; + w->module = m; + w->count = 0; + + return 0; +} + #ifdef DYNAMIC /* $module_path ($MODULE_PATH) */ @@ -161,6 +190,31 @@ deletebuiltins(char const *nam, Builtin binl, int size) return hadf ? hads : 1; } +/* This removes the given wrapper definition from the list. Returned is * + * one in case of error and zero otherwise. */ + +/**/ +int +deletewrapper(Module m, FuncWrap w) +{ + FuncWrap p, q; + + if (w->flags & WRAPF_ADDED) { + for (p = wrappers, q = NULL; p && p != w; q = p, p = p->next); + + if (p) { + if (q) + q->next = p->next; + else + wrappers = p->next; + p->flags &= ~WRAPF_ADDED; + + return 0; + } + } + return 1; +} + #ifdef AIXDYNAMIC #include @@ -498,6 +552,8 @@ bin_zmodload(char *nam, char **args, char *ops, int func) return bin_zmodload_dep(nam, args, ops); else if(ops['a']) return bin_zmodload_auto(nam, args, ops); + else if (ops['c'] || ops['C']) + return bin_zmodload_cond(nam, args, ops); else return bin_zmodload_load(nam, args, ops); } @@ -630,6 +686,98 @@ bin_zmodload_auto(char *nam, char **args, char *ops) } } +/**/ +static int +bin_zmodload_cond(char *nam, char **args, char *ops) +{ + int ret = 0; + + if (ops['u']) { + /* remove autoloaded conditions */ + for (; *args; args++) { + Conddef cd = getconddef(ops['I'], *args, 0); + + if (!cd) { + if (!ops['i']) { + zwarnnam(nam, "%s: no such condition", *args, 0); + ret = 1; + } + } else if (cd->flags & CONDF_ADDED) { + zwarnnam(nam, "%s: condition is already defined", *args, 0); + ret = 1; + } else + deleteconddef(cd); + } + return ret; + } else if (!*args) { + /* list autoloaded conditions */ + Conddef p; + + for (p = condtab; p; p = p->next) { + if (p->module) { + if (ops['L']) { + fputs("zmodload -c", stdout); + if (p->flags & CONDF_INFIX) + putchar('I'); + printf(" %s %s\n", p->module, p->name); + } else { + fputs("post ", stdout); + if (p->flags & CONDF_INFIX) + fputs("infix ", stdout); + printf("%s (%s)\n",p->name, p->module); + } + } + } + return 0; + } else { + /* add autoloaded conditions */ + char *modnam; + + modnam = *args++; + if(isset(RESTRICTED) && strchr(modnam, '/')) { + zwarnnam(nam, "%s: restricted", modnam, 0); + return 1; + } + do { + char *cnam = *args ? *args++ : modnam; + if (strchr(cnam, '/')) { + zwarnnam(nam, "%s: `/' is illegal in a condition", cnam, 0); + ret = 1; + } else if (add_autocond(cnam, ops['I'], modnam) && !ops['i']) { + zwarnnam(nam, "failed to add condition %s", cnam, 0); + ret = 1; + } + } while(*args); + return ret; + } +} + +/**/ +int +unload_module(Module m, LinkNode node) +{ + if (m->handle && cleanup_module(m)) + return 1; + else { + if (m->handle) + dlclose(m->handle); + m->handle = NULL; + if(!m->deps) { + if (!node) { + for (node = firstnode(modules); node; incnode(node)) + if (m == (Module) getdata(node)) + break; + if (!node) + return 1; + } + remnode(modules, node); + zsfree(m->nam); + zfree(m, sizeof(*m)); + } + } + return 0; +} + /**/ static int bin_zmodload_load(char *nam, char **args, char *ops) @@ -654,20 +802,13 @@ bin_zmodload_load(char *nam, char **args, char *ops) goto cont; } } - m = (Module) getdata(node); - if (m->handle && cleanup_module(m)) - ret = 1; - else { - if (m->handle) - dlclose(m->handle); - m->handle = NULL; - if(!m->deps) { - remnode(modules, node); - zsfree(m->nam); - zfree(m, sizeof(*m)); - } + if (!(m->flags & MOD_WRAPPER)) { + if (unload_module(m, node)) + ret = 1; } + else + m->flags |= MOD_UNLOAD; } else if (!ops['i']) { zwarnnam(nam, "no such module %s", *args, 0); ret = 1; @@ -711,3 +852,166 @@ bin_zmodload_load(char *nam, char **args, char *ops) } #endif /* DYNAMIC */ + +/* The list of module-defined conditions. */ + +/**/ +Conddef condtab; + +/* This gets a condition definition with the given name. The first * + * argument says if we have to look for an infix condition. The last * + * argument is non-zero if we should autoload modules if needed. */ + +/**/ +Conddef +getconddef(int inf, char *name, int autol) +{ + Conddef p; +#ifdef DYNAMIC + int f = 1; +#endif + + do { + for (p = condtab; p; p = p->next) { + if ((!!inf == !!(p->flags & CONDF_INFIX)) && + !strcmp(name, p->name)) + break; + } +#ifdef DYNAMIC + if (autol && p && p->module) { + /* This is a definition for an autoloaded condition, load the * + * module if we haven't tried that already. */ + if (f) { + load_module(p->module); + f = 0; + p = NULL; + } else + break; + } else +#endif + break; + } while (!p); + return p; +} + +#ifdef DYNAMIC + +/* This adds the given condition definition. The return value is zero on * + * success and 1 on failure. If there is a matching definition for an * + * autoloaded condition, it is removed. */ + +/**/ +int +addconddef(Conddef c) +{ + Conddef p = getconddef((c->flags & CONDF_INFIX), c->name, 0); + + if (p) { + if (!p->module || (p->flags & CONDF_ADDED)) + return 1; + + /* There is an autoload definition. */ + + deleteconddef(p); + } + c->next = condtab; + condtab = c; + return 0; +} + +/* This adds multiple condition definitions. This is like addbuiltins(). */ + +/**/ +int +addconddefs(char const *nam, Conddef c, int size) +{ + int hads = 0, hadf = 0; + + while (size--) { + if (c->flags & CONDF_ADDED) + continue; + if (addconddef(c)) { + zwarnnam(nam, "name clash when adding condition `%s'", c->name, 0); + hadf = 1; + } else { + c->flags |= CONDF_ADDED; + hads = 2; + } + c++; + } + return hadf ? hads : 1; +} + +/* This adds a definition for autoloading a module for a condition. */ + +/**/ +int +add_autocond(char *nam, int inf, char *module) +{ + Conddef c = zalloc(sizeof(*c)); + + c->name = ztrdup(nam); + c->flags = (inf ? CONDF_INFIX : 0); + c->module = ztrdup(module); + + if (addconddef(c)) { + zsfree(c->name); + zsfree(c->module); + zfree(c, sizeof(*c)); + + return 1; + } + return 0; +} + +/* This removes the given condition definition from the list(s). If this * + * is a definition for a autoloaded condition, the memory is freed. */ + +/**/ +int +deleteconddef(Conddef c) +{ + Conddef p, q; + + for (p = condtab, q = NULL; p && p != c; q = p, p = p->next); + + if (p) { + if (q) + q->next = p->next; + else + condtab = p->next; + + if (p->module) { + /* autoloaded, free it */ + zsfree(p->name); + zsfree(p->module); + zfree(p, sizeof(*p)); + } + return 0; + } + return -1; +} + +/* This removes multiple condition definitions (like deletebuiltins()). */ + +/**/ +int +deleteconddefs(char const *nam, Conddef c, int size) +{ + int hads = 0, hadf = 0; + + while (size--) { + if (!(c->flags & CONDF_ADDED)) + continue; + if (deleteconddef(c)) { + zwarnnam(nam, "condition `%s' already deleted", c->name, 0); + hadf = 1; + } else + hads = 2; + c->flags &= ~CONDF_ADDED; + c++; + } + return hadf ? hads : 1; +} + +#endif /* DYNAMIC */ diff --git a/Src/params.c b/Src/params.c index 69fd8a904..54699476c 100644 --- a/Src/params.c +++ b/Src/params.c @@ -303,7 +303,11 @@ copyparamtable(HashTable ht, char *name) #define SCANPM_WANTVALS (1<<0) #define SCANPM_WANTKEYS (1<<1) -#define SCANPM_WANTINDEX (1<<2) +#define SCANPM_WANTINDEX (1<<2) /* Useful only if nested arrays */ +#define SCANPM_MATCHKEY (1<<3) +#define SCANPM_MATCHVAL (1<<4) +#define SCANPM_MATCHMANY (1<<5) +#define SCANPM_ISVAR_AT ((-1)<<15) /* Only sign bit is significant */ static unsigned numparamvals; @@ -311,13 +315,12 @@ static unsigned numparamvals; static void scancountparams(HashNode hn, int flags) { - if (!(((Param)hn)->flags & PM_UNSET)) { + ++numparamvals; + if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS)) ++numparamvals; - if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS)) - ++numparamvals; - } } +static Comp scancomp; static char **paramvals; /**/ @@ -325,33 +328,45 @@ static void scanparamvals(HashNode hn, int flags) { struct value v; + if (numparamvals && (flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY)) && + !(flags & SCANPM_MATCHMANY)) + return; v.pm = (Param)hn; - if (!(v.pm->flags & PM_UNSET)) { - if (flags & SCANPM_WANTKEYS) { - paramvals[numparamvals++] = v.pm->nam; - if (!(flags & SCANPM_WANTVALS)) - return; - } - v.isarr = (PM_TYPE(v.pm->flags) & (PM_ARRAY|PM_HASHED)); - v.inv = (flags & SCANPM_WANTINDEX); - v.a = 0; - v.b = -1; - paramvals[numparamvals++] = getstrvalue(&v); + if ((flags & SCANPM_MATCHKEY) && !domatch(v.pm->nam, scancomp, 0)) { + return; + } + if (flags & SCANPM_WANTKEYS) { + paramvals[numparamvals++] = v.pm->nam; + if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL))) + return; } + v.isarr = (PM_TYPE(v.pm->flags) & (PM_ARRAY|PM_HASHED)); + v.inv = 0; + v.a = 0; + v.b = -1; + paramvals[numparamvals] = getstrvalue(&v); + if (flags & SCANPM_MATCHVAL) { + if (domatch(paramvals[numparamvals], scancomp, 0)) { + numparamvals += ((flags & SCANPM_WANTVALS) ? 1 : + !(flags & SCANPM_WANTKEYS)); + } else if (flags & SCANPM_WANTKEYS) + --numparamvals; /* Value didn't match, discard key */ + } else + ++numparamvals; } /**/ char ** -paramvalarr(HashTable ht, unsigned flags) +paramvalarr(HashTable ht, int flags) { MUSTUSEHEAP("paramvalarr"); numparamvals = 0; if (ht) - scanhashtable(ht, 0, 0, 0, scancountparams, flags); + scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags); paramvals = (char **) alloc((numparamvals + 1) * sizeof(char *)); if (ht) { numparamvals = 0; - scanhashtable(ht, 0, 0, 0, scanparamvals, flags); + scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags); } paramvals[numparamvals] = 0; return paramvals; @@ -369,15 +384,10 @@ getvaluearr(Value v) else if (PM_TYPE(v->pm->flags) == PM_ARRAY) return v->arr = v->pm->gets.afn(v->pm); else if (PM_TYPE(v->pm->flags) == PM_HASHED) { - unsigned flags = 0; - if (v->a) - flags |= SCANPM_WANTKEYS; - if (v->b > v->a) - flags |= SCANPM_WANTVALS; - v->arr = paramvalarr(v->pm->gets.hfn(v->pm), flags); + v->arr = paramvalarr(v->pm->gets.hfn(v->pm), v->isarr); /* Can't take numeric slices of associative arrays */ v->a = 0; - v->b = -1; + v->b = numparamvals; return v->arr; } else return NULL; @@ -737,7 +747,19 @@ getarg(char **str, int *inv, Value v, int a2, long *w) down = !down; num = -num; } - *inv = ind; + if (v->isarr & SCANPM_WANTKEYS) + *inv = (ind || !(v->isarr & SCANPM_WANTVALS)); + else if (v->isarr & SCANPM_WANTVALS) + *inv = 0; + else { + if (ind) { + v->isarr |= SCANPM_WANTKEYS; + v->isarr &= ~SCANPM_WANTVALS; + } + if (!down) + v->isarr &= ~SCANPM_MATCHMANY; + *inv = ind; + } for (t=s, i=0; *t && ((*t != ']' && *t != Outbrack && *t != ',') || i); t++) if (*t == '[' || *t == Inbrack) @@ -829,7 +851,21 @@ getarg(char **str, int *inv, Value v, int a2, long *w) if ((c = parsereg(s))) { if (v->isarr) { - ta = getarrvalue(v); + if (PM_TYPE(v->pm->flags) == PM_HASHED) { + scancomp = c; + if (ind) + v->isarr |= SCANPM_MATCHKEY; + else + v->isarr |= SCANPM_MATCHVAL; + if (down) + v->isarr |= SCANPM_MATCHMANY; + if ((ta = getvaluearr(v)) && *ta) { + *inv = v->inv; + *w = v->b; + return 1; + } + } else + ta = getarrvalue(v); if (!ta || !*ta) return 0; if (down) @@ -920,8 +956,8 @@ getindex(char **pptr, Value v) if (*tbrack == Outbrack) *tbrack = ']'; if ((s[0] == '*' || s[0] == '@') && s[1] == ']') { - if (v->isarr) - v->isarr = (s[0] == '*') ? 1 : -1; + if (v->isarr && s[0] == '@') + v->isarr |= SCANPM_ISVAR_AT; v->a = 0; v->b = -1; s += 2; @@ -941,7 +977,7 @@ getindex(char **pptr, Value v) } else a = -ztrlen(t + a + strlen(t)); } - if (a > 0 && isset(KSHARRAYS)) + if (a > 0 && (isset(KSHARRAYS) || (v->pm->flags & PM_HASHED))) a--; v->inv = 1; v->isarr = 0; @@ -984,6 +1020,13 @@ getindex(char **pptr, Value v) /**/ Value getvalue(char **pptr, int bracks) +{ + return fetchvalue(pptr, bracks, 0); +} + +/**/ +Value +fetchvalue(char **pptr, int bracks, int flags) { char *s, *t; char sav; @@ -1039,8 +1082,16 @@ getvalue(char **pptr, int bracks) if (!pm || (pm->flags & PM_UNSET)) return NULL; v = (Value) hcalloc(sizeof *v); - if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)) - v->isarr = isvarat ? -1 : 1; + if (PM_TYPE(pm->flags) & (PM_ARRAY|PM_HASHED)) { + /* Overload v->isarr as the flag bits for hashed arrays. */ + v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0); + /* If no flags were passed, we need something to represent * + * `true' yet differ from an explicit WANTVALS. This is a * + * bit of a hack, but makes some sense: When no subscript * + * is provided, all values are substituted. */ + if (!v->isarr) + v->isarr = SCANPM_MATCHMANY; + } v->pm = pm; v->inv = 0; v->a = 0; @@ -1079,7 +1130,7 @@ getstrvalue(Value v) if (!v) return hcalloc(1); HEAPALLOC { - if (v->inv) { + if (v->inv && !(v->pm->flags & PM_HASHED)) { sprintf(buf, "%d", v->a); s = dupstring(buf); LASTALLOC_RETURN s; @@ -1087,6 +1138,13 @@ getstrvalue(Value v) switch(PM_TYPE(v->pm->flags)) { case PM_HASHED: + /* (!v->isarr) should be impossible unless emulating ksh */ + if (!v->isarr && emulation == EMULATE_KSH) { + s = dupstring("[0]"); + if (getindex(&s, v) == 0) + s = getstrvalue(v); + LASTALLOC_RETURN s; + } /* else fall through */ case PM_ARRAY: ss = getvaluearr(v); if (v->isarr) @@ -1486,6 +1544,39 @@ setaparam(char *s, char **val) return v->pm; } +/**/ +Param +sethparam(char *s, char **kvarr) +{ + Value v; + Param pm; + char *t; + + if (!isident(s)) { + zerr("not an identifier: %s", s, 0); + freearray(kvarr); + errflag = 1; + return NULL; + } + t=ztrdup(s); /* Is this a memory leak? */ + /* Why does getvalue(s, 1) set s to empty string? */ + if ((v = getvalue(&t, 1))) + if (v->pm->flags & PM_SPECIAL) { + zerr("not overriding a special: %s", s, 0); + freearray(kvarr); + errflag = 1; + return NULL; + } else + unsetparam(s); + + pm = createparam(s, PM_HASHED); + DPUTS(!pm, "BUG: parameter not created"); + + arrhashsetfn(pm, kvarr); + + return pm; +} + /**/ Param setiparam(char *s, long val) @@ -2538,24 +2629,28 @@ printparamnode(HashNode hn, int printflags) return; } + quotedzputs(p->nam, stdout); + if (printflags & PRINT_KV_PAIR) + putchar(' '); + else + putchar('='); + /* How the value is displayed depends * * on the type of the parameter */ - quotedzputs(p->nam, stdout); - putchar('='); switch (PM_TYPE(p->flags)) { case PM_SCALAR: /* string: simple output */ if (p->gets.cfn && (t = p->gets.cfn(p))) quotedzputs(t, stdout); - putchar('\n'); break; case PM_INTEGER: /* integer */ - printf("%ld\n", p->gets.ifn(p)); + printf("%ld", p->gets.ifn(p)); break; case PM_ARRAY: /* array */ - putchar('('); + if (!(printflags & PRINT_KV_PAIR)) + putchar('('); u = p->gets.afn(p); if(*u) { quotedzputs(*u++, stdout); @@ -2564,17 +2659,25 @@ printparamnode(HashNode hn, int printflags) quotedzputs(*u++, stdout); } } - printf(")\n"); + if (!(printflags & PRINT_KV_PAIR)) + putchar(')'); break; case PM_HASHED: /* association */ - putchar('('); + if (!(printflags & PRINT_KV_PAIR)) + putchar('('); { HashTable ht = p->gets.hfn(p); if (ht) - scanhashtable(ht, 0, 0, 0, ht->printnode, 0); + scanhashtable(ht, 0, 0, PM_UNSET, + ht->printnode, PRINT_KV_PAIR); } - printf(")\n"); + if (!(printflags & PRINT_KV_PAIR)) + putchar(')'); break; } + if (printflags & PRINT_KV_PAIR) + putchar(' '); + else + putchar('\n'); } diff --git a/Src/parse.c b/Src/parse.c index d42be2f2f..9024a834e 100644 --- a/Src/parse.c +++ b/Src/parse.c @@ -114,7 +114,7 @@ par_event(void) } if (tok == ENDINPUT) return NULL; - if ((sl = par_sublist())) + if ((sl = par_sublist())) { if (tok == ENDINPUT) { l = (List) make_list(); l->type = Z_SYNC; @@ -137,6 +137,7 @@ par_event(void) yylex(); } else l = NULL; + } if (!l) { if (errflag) { yyerror(); @@ -181,7 +182,7 @@ par_list(void) while (tok == SEPER) yylex(); - if ((sl = par_sublist())) + if ((sl = par_sublist())) { if (tok == SEPER || tok == AMPER || tok == AMPERBANG) { l = (List) make_list(); l->left = sl; @@ -197,6 +198,7 @@ par_list(void) l->left = sl; l->type = Z_SYNC; } + } return l; } @@ -1139,13 +1141,14 @@ par_cond_2(void) condlex(); return c; } - if (tok != STRING) + if (tok != STRING) { if (tok && tok != LEXERR && condlex == testlex) { s1 = tokstr; condlex(); return par_cond_double("-n", s1); } else YYERROR; + } s1 = tokstr; if (condlex == testlex) dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1 @@ -1165,7 +1168,7 @@ par_cond_2(void) c->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); return c; } - if (tok != STRING) + if (tok != STRING) { if (tok != LEXERR && condlex == testlex) { if (!dble) return par_cond_double("-n", s1); @@ -1173,6 +1176,7 @@ par_cond_2(void) return par_cond_double(s1, "1"); } else YYERROR; + } s2 = tokstr; incond++; /* parentheses do globbing */ condlex(); @@ -1180,7 +1184,19 @@ par_cond_2(void) if (tok == STRING && !dble) { s3 = tokstr; condlex(); - return par_cond_triple(s1, s2, s3); + if (tok == STRING) { + LinkList l = newlinklist(); + + addlinknode(l, s2); + addlinknode(l, s3); + + while (tok == STRING) { + addlinknode(l, tokstr); + condlex(); + } + return par_cond_multi(s1, l); + } else + return par_cond_triple(s1, s2, s3); } else return par_cond_double(s1, s2); } @@ -1312,11 +1328,22 @@ par_cond_double(char *a, char *b) { Cond n = (Cond) make_cond(); - if (a[0] != '-' || !a[1] || a[2]) - COND_ERROR("parse error: condition expected: %s", a); - n->left = (void *) b; - n->type = a[1]; n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + n->left = (void *) b; + if (a[0] != '-' || !a[1]) + COND_ERROR("parse error: condition expected: %s", a); + else if (!a[2] && strspn(a+1, "abcdefgknoprstuwxzhLONGS") == 1) + n->type = a[1]; + else { + char *d[2]; + + n->ntype = NT_SET(N_COND, NT_STR, NT_STR | NT_ARR, 0, 0); + n->type = COND_MOD; + n->left = (void *) (a + 1); + d[0] = b; + d[1] = NULL; + n->right = (void *) arrdup(d); + } return n; } @@ -1343,6 +1370,9 @@ par_cond_triple(char *a, char *b, char *c) Cond n = (Cond) make_cond(); int t0; + n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + n->left = (void *) a; + n->right = (void *) c; if ((b[0] == Equals || b[0] == '=') && (!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2]))) n->type = COND_STREQ; @@ -1351,13 +1381,46 @@ par_cond_triple(char *a, char *b, char *c) else if (b[0] == '-') { if ((t0 = get_cond_num(b + 1)) > -1) n->type = t0 + COND_NT; - else - COND_ERROR("unrecognized condition: %s", b); + else { + char *d[3]; + + n->ntype = NT_SET(N_COND, NT_STR, NT_STR | NT_ARR, 0, 0); + n->type = COND_MODI; + n->left = (void *) (b + 1); + d[0] = a; + d[1] = c; + d[2] = NULL; + n->right = (void *) arrdup(d); + } + } else if (a[0] == '-' && a[1]) { + char *d[3]; + + n->ntype = NT_SET(N_COND, NT_STR, NT_STR | NT_ARR, 0, 0); + n->type = COND_MOD; + n->left = (void *) (a + 1); + d[0] = b; + d[1] = c; + d[2] = NULL; + n->right = (void *) arrdup(d); } else COND_ERROR("condition expected: %s", b); - n->left = (void *) a; - n->right = (void *) c; - n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + return n; +} + +/**/ +static Cond +par_cond_multi(char *a, LinkList l) +{ + Cond n = (Cond) make_cond(); + + n->ntype = NT_SET(N_COND, NT_STR, NT_STR | NT_ARR, 0, 0); + if (a[0] != '-' || !a[1]) + COND_ERROR("condition expected: %s", a); + else { + n->type = COND_MOD; + n->left = (void *) a; + n->right = (void *) listarr(l); + } return n; } diff --git a/Src/signals.c b/Src/signals.c index 5dc19dd22..e637a8ca9 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -712,7 +712,7 @@ dotrapargs(int sig, int *sigtr, void *sigfn) addlinknode(args, num); } LASTALLOC; trapreturn = -1; - doshfunc(sigfn, args, 0, 1); + doshfunc(name, sigfn, args, 0, 1); freelinklist(args, (FreeFunc) NULL); zsfree(name); } else HEAPALLOC { diff --git a/Src/subst.c b/Src/subst.c index cc1ae3027..77f0249e2 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -99,7 +99,7 @@ stringsubst(LinkList list, LinkNode node, int ssub) char *str = str3; while (!errflag && *str) { - if ((qt = *str == Qstring) || *str == String) + if ((qt = *str == Qstring) || *str == String) { if (str[1] == Inpar) { str++; goto comsub; @@ -125,7 +125,7 @@ stringsubst(LinkList list, LinkNode node, int ssub) str3 = (char *)getdata(node); continue; } - else if ((qt = *str == Qtick) || *str == Tick) + } else if ((qt = *str == Qtick) || *str == Tick) comsub: { LinkList pl; char *s, *str2 = str; @@ -135,8 +135,12 @@ stringsubst(LinkList list, LinkNode node, int ssub) if (*str == Inpar) { endchar = Outpar; str[-1] = '\0'; +#ifdef DEBUG if (skipparens(Inpar, Outpar, &str)) - DPUTS(1, "BUG: parse error in command substitution"); + dputs("BUG: parse error in command substitution"); +#else + skipparens(Inpar, Outpar, &str); +#endif str--; } else { endchar = *str; @@ -298,7 +302,7 @@ filesub(char **namptr, int assign) if (!assign) return; - if (assign < 3) + if (assign < 3) { if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) { if (assign == 1) for (ptr = *namptr; ptr != sub; ptr++) @@ -311,6 +315,7 @@ filesub(char **namptr, int assign) } } else return; + } ptr = *namptr; while ((sub = strchr(ptr, ':'))) { @@ -691,7 +696,6 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) char *aptr = *str; char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n); int colf; /* != 0 means we found a colon after the name */ - int doub = 0; /* != 0 means we have %%, not %, or ##, not # */ int isarr = 0; int plan9 = isset(RCEXPANDPARAM); int globsubst = isset(GLOBSUBST); @@ -705,11 +709,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) Value v; int flags = 0; int flnum = 0; - int substr = 0; int sortit = 0, casind = 0; int casmod = 0; char *sep = NULL, *spsep = NULL; char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL; + char *replstr = NULL; /* replacement string for /orig/repl */ long prenum = 0, postnum = 0; int copied = 0; int arrasg = 0; @@ -717,7 +721,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) int nojoin = 0; char inbrace = 0; /* != 0 means ${...}, otherwise $... */ char hkeys = 0; /* 1 means get keys from associative array */ - char hvals = 1; /* > hkeys get values of associative array */ + char hvals = 0; /* > hkeys get values of associative array */ *s++ = '\0'; if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' && @@ -764,22 +768,22 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) nojoin = 1; break; case 'M': - flags |= 8; + flags |= SUB_MATCH; break; case 'R': - flags |= 16; + flags |= SUB_REST; break; case 'B': - flags |= 32; + flags |= SUB_BIND; break; case 'E': - flags |= 64; + flags |= SUB_EIND; break; case 'N': - flags |= 128; + flags |= SUB_LEN; break; case 'S': - substr = 1; + flags |= SUB_SUBSTR; break; case 'I': flnum = get_intarg(&s); @@ -940,7 +944,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) s++; } else globsubst = 1; - } else if (*s == '+') + } else if (*s == '+') { if (iident(s[1])) chkset = 1, s++; else if (!inbrace) { @@ -951,7 +955,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) zerr("bad substitution", NULL, 0); return NULL; } - else + } else break; } globsubst = globsubst && !qt; @@ -974,8 +978,12 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) copied = 1; *s = sav; v = (Value) NULL; - } else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1))) - vunset = 1; + } else { + /* 2 == SCANPM_WANTKEYS, 1 == SCANPM_WANTVALS, see params.c */ + if (!(v = fetchvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1, + (hkeys ? 2 : 0) + ((hvals > hkeys) ? 1 : 0)))) + vunset = 1; + } while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) { if (!v) { Param pm; @@ -1000,13 +1008,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) break; } if ((isarr = v->isarr)) { - /* No way to reach here with v->inv != 0, so getvaluearr() * - * will definitely be called by getarrvalue(). Slicing of * - * associations isn't done, so use v->a and v->b for flags */ - if (PM_TYPE(v->pm->flags) == PM_HASHED) { - v->a = hkeys; - v->b = hvals; - } + /* No way to get here with v->inv != 0, so getvaluearr() * + * is called by getarrvalue(); needn't test PM_HASHED. */ aval = getarrvalue(v); } else { if (v->pm->flags & PM_ARRAY) { @@ -1124,23 +1127,72 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) *s == '=' || *s == Equals || *s == '%' || *s == '#' || *s == Pound || - *s == '?' || *s == Quest)) { + *s == '?' || *s == Quest || + *s == '/')) { if (!flnum) flnum++; if (*s == '%') - flags |= 1; + flags |= SUB_END; /* Check for ${..%%..} or ${..##..} */ if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) { s++; - doub = 1; + /* we have %%, not %, or ##, not # */ + flags |= SUB_LONG; } s++; + if (s[-1] == '/') { + char *ptr; + /* + * previous flags are irrelevant, except for (S) which + * indicates shortest substring; else look for longest. + */ + flags = (flags & SUB_SUBSTR) ? 0 : SUB_LONG; + if (*s == '/') { + /* doubled, so replace all occurrences */ + flags |= SUB_GLOBAL; + s++; + } + /* Check for anchored substitution */ + if (*s == '%') { + /* anchor at tail */ + flags |= SUB_END; + s++; + } else if (*s == '#' || *s == Pound) { + /* anchor at head: this is the `normal' case in getmatch */ + s++; + } else + flags |= SUB_SUBSTR; + /* + * Find the / marking the end of the search pattern. + * If there isn't one, we're just going to delete that, + * i.e. replace it with an empty string. + * + * This allows quotation of the slash with '\\/'. Why + * two? Well, for a non-quoted string we can check for + * Bnull+/, which is what you get from `\/', but inside + * double quotes the Bnull isn't there, so it's not + * consistent. + */ + for (ptr = s; *ptr && *ptr != '/'; ptr++) + if (*ptr == '\\' && ptr[1] == '/') + chuck(ptr); + replstr = (*ptr && ptr[1]) ? ptr+1 : ""; + singsub(&replstr); + untokenize(replstr); + *ptr = '\0'; + } - flags |= (doub << 1) | (substr << 2) | (colf << 8); - if (!(flags & 0xf8)) - flags |= 16; + if (colf) + flags |= SUB_ALL; + /* + * With no special flags, i.e. just a # or % or whatever, + * the matched portion is removed and we keep the rest. + * We also want the rest when we're doing a substitution. + */ + if (!(flags & (SUB_MATCH|SUB_REST|SUB_BIND|SUB_EIND|SUB_LEN))) + flags |= SUB_REST; if (colf && !vunset) vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]); @@ -1234,6 +1286,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) case '%': case '#': case Pound: + case '/': if (qt) if (parse_subst_string(s)) { zerr("parse error in ${...%c...} substitution", @@ -1247,14 +1300,14 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1)); while ((*pp = *ap++)) { - if (getmatch(pp, s, flags, flnum)) + if (getmatch(pp, s, flags, flnum, replstr)) pp++; } copied = 1; } else { if (vunset) val = dupstring(""); - getmatch(&val, s, flags, flnum); + getmatch(&val, s, flags, flnum, replstr); copied = 1; } break; diff --git a/Src/text.c b/Src/text.c index 836a6a0a8..ec724f27d 100644 --- a/Src/text.c +++ b/Src/text.c @@ -410,6 +410,27 @@ getcond(Cond nm, int addpar) taddstr(" || "); getcond(nm->right, _Cond(nm->right)->type == COND_AND); break; + case COND_MOD: + { + /* Module defined prefix condition. */ + char **p = (char **) nm->right; + + taddstr("-"); + taddstr(nm->left); + for (; *p; p++) { + taddstr(" "); + taddstr(*p); + } + } + break; + case COND_MODI: + /* Module defined infix condition. */ + taddstr(((char **) nm->right)[0]); + taddstr(" -"); + taddstr(nm->left); + taddstr(" "); + taddstr(((char **) nm->right)[1]); + break; default: if (nm->type <= COND_GE) { /* Binary test: `a = b' etc. */ diff --git a/Src/utils.c b/Src/utils.c index 44223867f..af0247ebf 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -634,7 +634,7 @@ preprompt(void) /* If a shell function named "precmd" exists, * * then execute it. */ if ((list = getshfunc("precmd")) != &dummy_list) - doshfunc(list, NULL, 0, 1); + doshfunc("precmd", list, NULL, 0, 1); if (errflag) return; @@ -643,7 +643,7 @@ preprompt(void) * executed "periodic", then execute it now. */ if (period && (time(NULL) > lastperiodic + period) && (list = getshfunc("periodic")) != &dummy_list) { - doshfunc(list, NULL, 0, 1); + doshfunc("periodic", list, NULL, 0, 1); lastperiodic = time(NULL); } if (errflag) @@ -732,7 +732,7 @@ checkmailpath(char **s) } } else { if (st.st_size && st.st_atime <= st.st_mtime && - st.st_mtime > lastmailcheck) + st.st_mtime > lastmailcheck) { if (!u) { fprintf(shout, "You have new mail.\n"); fflush(shout); @@ -751,6 +751,7 @@ checkmailpath(char **s) underscore = usav; } LASTALLOC; } + } if (isset(MAILWARNING) && st.st_atime > st.st_mtime && st.st_atime > lastmailcheck && st.st_size) { fprintf(shout, "The mail in %s has been read.\n", unmeta(*s)); @@ -1066,14 +1067,14 @@ zstrtol(const char *s, char **t, int base) else if (*s == '+') s++; - if (!base) + if (!base) { if (*s != '0') base = 10; else if (*++s == 'x' || *s == 'X') base = 16, s++; else base = 8; - + } if (base <= 10) for (; *s >= '0' && *s < ('0' + base); s++) ret = ret * base + *s - '0'; @@ -2137,22 +2138,24 @@ dupstruct2(void *a) n = dupstring(on); break; case NT_LIST | NT_NODE: - if (heap) + if (heap) { if (useheap) n = duplist(on, (VFunc) dupstruct2); else n = list2arr(on, (VFunc) dupstruct2); + } else if (useheap) n = arr2list(on, (VFunc) dupstruct2); else n = duparray(on, (VFunc) dupstruct2); break; case NT_LIST | NT_STR: - if (heap) + if (heap) { if (useheap) n = duplist(on, (VFunc) dupstring); else n = list2arr(on, (VFunc) ztrdup); + } else if (useheap) n = arr2list(on, (VFunc) dupstring); else @@ -2378,11 +2381,12 @@ inittyptab(void) for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Nularg); t0++) typtab[t0] |= ITOK | IMETA; for (s = ifs ? ifs : DEFAULT_IFS; *s; s++) { - if (inblank(*s)) + if (inblank(*s)) { if (s[1] == *s) s++; else typtab[STOUC(*s)] |= IWSEP; + } typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= ISEP; } for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) @@ -2405,6 +2409,21 @@ arrdup(char **s) return y; } +/**/ +char ** +listarr(LinkList l) +{ + char **x, **y; + LinkNode n; + + x = y = (char **) ncalloc((countlinknodes(l) + 1) * sizeof(char *)); + + for (n = firstnode(l); n; incnode(n)) + *x++ = dupstring((char *) getdata(n)); + *x = NULL; + return y; +} + /**/ static char * spname(char *oldname) @@ -3009,11 +3028,12 @@ niceztrdup(char const *s) char *p = buf, *n, *ret; while ((c = *s++)) { - if (itok(c)) + if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; + } if (c == Meta) c = *s++ ^ 32; n = nicechar(c); @@ -3034,11 +3054,12 @@ nicezputs(char const *s, FILE *stream) int c; while ((c = *s++)) { - if (itok(c)) + if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; + } if (c == Meta) c = *s++ ^ 32; if(fputs(nicechar(c), stream) < 0) @@ -3057,11 +3078,12 @@ niceztrlen(char const *s) int c; while ((c = *s++)) { - if (itok(c)) + if (itok(c)) { if (c <= Comma) c = ztokens[c - Pound]; else continue; + } if (c == Meta) c = *s++ ^ 32; l += strlen(nicechar(STOUC(c))); @@ -3328,13 +3350,14 @@ getkeystring(char *s, int *len, int fromwhere, int *misc) } default: if ((idigit(*s) && *s < '8') || *s == 'x') { - if (!fromwhere) + if (!fromwhere) { if (*s == '0') s++; else if (*s != 'x') { *t++ = '\\', s--; continue; } + } if (s[1] && s[2] && s[3]) { svchar = s[3]; s[3] = '\0'; diff --git a/Src/zsh.export b/Src/zsh.export index 701aeb990..c51699269 100644 --- a/Src/zsh.export +++ b/Src/zsh.export @@ -1,8 +1,10 @@ #! SHTTY addbuiltins +addconddefs addedx addhashnode +addwrapper aliastab alloc_stackp appstr @@ -30,7 +32,9 @@ ctxtlex curhist current_limits deletebuiltins +deleteconddefs deletehashtable +deletewrapper domatch doshfunc dputs @@ -65,6 +69,7 @@ getkeystring getlinknode getshfunc getsparam +gettempname glob_pre glob_suf global_heapalloc @@ -82,6 +87,7 @@ hgetc hgetline histentarr histentct +holdintr hptr hrealloc inbufct @@ -94,6 +100,7 @@ inpop inpush inredir insertlinknode +install_handler intr inwhat isfirstln @@ -108,6 +115,7 @@ limits line lines locallevel +matheval metadiffer metafy metalen @@ -124,6 +132,7 @@ niceztrdup niceztrlen noaliases noerrs +noholdintr noop_function noop_function_int optiontab @@ -157,8 +166,10 @@ resetneeded restoredir reswdtab retflag +runshfunc scanhashtable setaparam +sethparam setlimits setsparam settyinfo @@ -166,6 +177,8 @@ shfunctab shingetline shout shttyinfo +sigfuncs +sigtrapped singsub skipparens spaceinlineptr @@ -184,6 +197,7 @@ struncpy tclen tcstr termflags +thisjob tgoto tok tokenize @@ -201,6 +215,7 @@ ugetnode uinsertlinknode unmeta unmetafy +unsetparam_pm untokenize uremnode useheap @@ -210,6 +225,7 @@ zalloc zbeep zcalloc zchdir +zclose zerr zerrnam zexit @@ -222,6 +238,7 @@ zleparse zlereadptr zputs zreaddir +zrealloc zsetlimit zsfree zshcs diff --git a/Src/zsh.h b/Src/zsh.h index 837a76e88..6a962d8bf 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -233,6 +233,7 @@ typedef struct alias *Alias; typedef struct param *Param; typedef struct cmdnam *Cmdnam; typedef struct shfunc *Shfunc; +typedef struct funcwrap *FuncWrap; typedef struct builtin *Builtin; typedef struct nameddir *Nameddir; typedef struct module *Module; @@ -242,6 +243,7 @@ typedef struct job *Job; typedef struct value *Value; typedef struct varasg *Varasg; typedef struct cond *Cond; +typedef struct conddef *Conddef; typedef struct cmd *Cmd; typedef struct pline *Pline; typedef struct sublist *Sublist; @@ -455,6 +457,26 @@ struct cond { #define COND_GT 13 #define COND_LE 14 #define COND_GE 15 +#define COND_MOD 16 +#define COND_MODI 17 + +typedef int (*CondHandler) _((Conddef, char **)); + +struct conddef { + Conddef next; /* next in list */ + char *name; /* the condition name */ + int flags; /* see CONDF_* below */ + int min; /* minimum number of strings */ + int max; /* maximum number of strings */ + CondHandler handler; /* handler function */ + char *module; /* module to autoload */ +}; + +#define CONDF_INFIX 1 +#define CONDF_ADDED 2 + +#define CONDDEF(name, flags, min, max, handler) \ + { NULL, name, flags, min, max, handler, NULL } struct forcmd { /* for/select */ /* Cmd->args contains list of words to loop thru */ @@ -750,6 +772,23 @@ struct shfunc { List funcdef; /* function definition */ }; +/* node in list of function call wrappers */ + +typedef int (*WrapFunc) _((List, FuncWrap, char *)); + +struct funcwrap { + FuncWrap next; + int flags; + WrapFunc handler; + Module module; + int count; +}; + +#define WRAPF_ADDED 1 + +#define WRAPDEF(func) \ + { NULL, 0, func, NULL, 0 } + /* node in builtin command hash table (builtintab) */ typedef int (*HandlerFunc) _((char *, char **, char *, int)); @@ -800,6 +839,8 @@ struct module { }; #define MOD_BUSY (1<<0) +#define MOD_WRAPPER (1<<1) +#define MOD_UNLOAD (1<<2) /* node used in parameter hash table (paramtab) */ @@ -872,6 +913,23 @@ struct param { #define PM_RESTRICTED (1<<13) /* cannot be changed in restricted mode */ #define PM_UNSET (1<<14) /* has null value */ +/* + * Flags for doing matches inside parameter substitutions, i.e. + * ${...#...} and friends. This could be an enum, but so + * could a lot of other things. + */ + +#define SUB_END 0x0001 /* match end instead of begining, % or %% */ +#define SUB_LONG 0x0002 /* % or # doubled, get longest match */ +#define SUB_SUBSTR 0x0004 /* match a substring */ +#define SUB_MATCH 0x0008 /* include the matched portion */ +#define SUB_REST 0x0010 /* include the unmatched portion */ +#define SUB_BIND 0x0020 /* index of beginning of string */ +#define SUB_EIND 0x0040 /* index of end of string */ +#define SUB_LEN 0x0080 /* length of match */ +#define SUB_ALL 0x0100 /* match complete string */ +#define SUB_GLOBAL 0x0200 /* global substitution ${..//all/these} */ + /* node for named directory hash table (nameddirtab) */ struct nameddir { @@ -891,13 +949,14 @@ struct nameddir { #define PRINT_NAMEONLY (1<<0) #define PRINT_TYPE (1<<1) #define PRINT_LIST (1<<2) +#define PRINT_KV_PAIR (1<<3) /* flags for printing for the whence builtin */ -#define PRINT_WHENCE_CSH (1<<3) -#define PRINT_WHENCE_VERBOSE (1<<4) -#define PRINT_WHENCE_SIMPLE (1<<5) -#define PRINT_WHENCE_FUNCDEF (1<<6) -#define PRINT_WHENCE_WORD (1<<7) +#define PRINT_WHENCE_CSH (1<<4) +#define PRINT_WHENCE_VERBOSE (1<<5) +#define PRINT_WHENCE_SIMPLE (1<<6) +#define PRINT_WHENCE_FUNCDEF (1<<7) +#define PRINT_WHENCE_WORD (1<<8) /***********************************/ /* Definitions for history control */ diff --git a/configure.in b/configure.in index 822a7f477..6985f24e4 100644 --- a/configure.in +++ b/configure.in @@ -428,6 +428,8 @@ fi AC_CHECK_LIB(cap, cap_get_proc) +AC_CHECK_LIB(socket, socket) + dnl --------------------- dnl CHECK TERMCAP LIBRARY dnl --------------------- diff --git a/patchlist.txt b/patchlist.txt index 9c98bd6e3..923919098 100644 --- a/patchlist.txt +++ b/patchlist.txt @@ -47,7 +47,8 @@ Bart's chpwd() fix 4589 Second edition Added line in zle_tricky.c missed when patching by hand, spotted by -Bart. (Whitespace is still non-canonical.) +Bart. (Whitespace is still non-canonical in the completion code where +I have merged patches by hand.) Fixed up my compctl widgets patch for use with Sven's rewrite, which I hadn't done properly before. @@ -58,7 +59,9 @@ Bart's doc fixes, 4472 Bart's PWD and OLDPWD reshuffle, 4589 -My test-line-length patch for prompts, 4591 +My test-line-length patch for prompts, 4591 (`%(40l.yes.no)' outputs +`yes' if at least 40 characters have already appeared on the line, +`no' otherwise.) Configure patch from Wilfredo Sanchez in 4594, with some extra tabbification and without the setterm() hunk, since I've already renamed @@ -84,12 +87,16 @@ one two % print ${(kv)hash} # flag to get keys and values (**) one eins two zwei Comparison of (*) and (**) will reveal how to copy an associative -array, but you always need to declare it with typeset -A or an -ordinary array will appear. There is a predefined special associative -array $testhash, for testing purposes only, which will eventually -disappear. +array, `hash2=(${(kv}hash})', but you always need to `typeset -A +hash2' first or an ordinary array will appear. There is a predefined +special associative array $testhash, for testing purposes only, which +will eventually disappear. -My rewrite of prompt truncation, 4601 +My rewrite of prompt truncation, 4601 --- note this introduces a +slight incompatibility in that the string to be truncated now runs by +default to the end of the string, instead of only covering individual +%-substitutions. If necessary, stick in an extra '%>>' to turn +truncation off at the point you want. Bart's params error message fix, 4606 @@ -100,3 +107,65 @@ Bart's version of the *** fix, 4624 Bart's parameter substitution flag delimiter fix, 4644 My special parameter unset fix, 4662 + + Third edition + +I've taken the plunge and changed $ZSH_VERSION, the current one is now +3.1.5.pws-1 . It seemed rational to have something incremental at the +end for testing, so I abandoned using the date. + +4482 (cdmatch2)and 4641 (${assoc[0]}) now applied; 4641 was supposed +to be there before. + +nroff manual pages deleted, you now need yodl. + +deleted modules-bltin by hand, which `make distclean' somehow missed. +Caused problems when building a statically linked shell. + +Bart's scanmatchtable fix, 4674 + +Commented out vifirstnonblank() in vioperswapcase(), pending any +better patch for it. + +Bart's viforwardword fix, 4678 + +My case-independent globbing fix, 4693 + +Sven's zle_tricky.c, 4697 + +Sven's patch to ignore completions if the cursor is not in a part to +be completed, 4698, plus addition, 4707 + +I have not added Sven's zerr() patch, 4699, in case it has side +effects, but I haven't heard anything on the subject and I haven't +looked at it. + +Sven's pennockite heap memory patch, 4700 + +Sven's condition module patch, 4716, and addition, 4732, and the +function wrapper patch, 4734, and additions, 4742, 4769: the module.c +bits of these have been moved around a little to avoid clashes with +the AIXDYNAMIC stuff. The wrapper stuff is still not finished, but +doesn't currently impinge on use of the shell. + +Phil Pennock's patch to use associative arrays in stat, 4727 + +Unposted fix for use of printcompctlptr in completion widgets: +printcompctl() had acquired another argument. + +My bash-like ${foo/orig/new} patch, 4736, and the version to do +shortest matching together with optimizations of all pattern matching +in variable strings, 4754. + +Phil's patch for typeset -a docs, 4737 + +Nobody wanted my fix for `FOO=x eval external', so it's not there. + +zftp, 4761 + +Bart's fix for conddef without dynamical modules, 4762 + +Bart's associative array patches for implentation of subscripting flags, +4763, plus fix 4766; typeset output 4764 + +Sven's completion listing fix, 4767 -- cgit 1.4.1