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 --- 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 +- 27 files changed, 3856 insertions(+), 358 deletions(-) create mode 100644 Src/Modules/zftp.c create mode 100644 Src/Modules/zftp.mdd (limited to 'Src') 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 */ -- cgit 1.4.1