Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
6213 lines (5635 sloc) 138 KB
/*
* utils.c - miscellaneous utilities
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1997 Paul Falstad
* 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 Paul Falstad 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 Paul Falstad and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Paul Falstad 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 Paul Falstad and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "zsh.mdh"
#include "utils.pro"
/* name of script being sourced */
/**/
mod_export char *scriptname; /* is sometimes a function name */
/* filename of script or other file containing code source e.g. autoload */
/**/
mod_export char *scriptfilename;
/* != 0 if we are in a new style completion function */
/**/
mod_export int incompfunc;
#ifdef MULTIBYTE_SUPPORT
struct widechar_array {
wchar_t *chars;
size_t len;
};
typedef struct widechar_array *Widechar_array;
/*
* The wordchars variable turned into a wide character array.
* This is much more convenient for testing.
*/
struct widechar_array wordchars_wide;
/*
* The same for the separators (IFS) array.
*/
struct widechar_array ifs_wide;
/* Function to set one of the above from the multibyte array */
static void
set_widearray(char *mb_array, Widechar_array wca)
{
if (wca->chars) {
free(wca->chars);
wca->chars = NULL;
}
wca->len = 0;
if (!isset(MULTIBYTE))
return;
if (mb_array) {
VARARR(wchar_t, tmpwcs, strlen(mb_array));
wchar_t *wcptr = tmpwcs;
wint_t wci;
mb_metacharinit();
while (*mb_array) {
int mblen = mb_metacharlenconv(mb_array, &wci);
if (!mblen)
break;
/* No good unless all characters are convertible */
if (wci == WEOF)
return;
*wcptr++ = (wchar_t)wci;
#ifdef DEBUG
/*
* This generates a warning from the compiler (and is
* indeed useless) if chars are unsigned. It's
* extreme paranoia anyway.
*/
if (wcptr[-1] < 0)
fprintf(stderr, "BUG: Bad cast to wchar_t\n");
#endif
mb_array += mblen;
}
wca->len = wcptr - tmpwcs;
wca->chars = (wchar_t *)zalloc(wca->len * sizeof(wchar_t));
wmemcpy(wca->chars, tmpwcs, wca->len);
}
}
#endif
/* Print an error */
static void
zwarning(const char *cmd, const char *fmt, va_list ap)
{
if (isatty(2))
zleentry(ZLE_CMD_TRASH);
if (cmd) {
if (unset(SHINSTDIN) || locallevel) {
nicezputs(scriptname ? scriptname : argzero, stderr);
fputc((unsigned char)':', stderr);
}
nicezputs(cmd, stderr);
fputc((unsigned char)':', stderr);
} else {
/*
* scriptname is set when sourcing scripts, so that we get the
* correct name instead of the generic name of whatever
* program/script is running. It's also set in shell functions,
* so test locallevel, too.
*/
nicezputs((isset(SHINSTDIN) && !locallevel) ? "zsh" :
scriptname ? scriptname : argzero, stderr);
fputc((unsigned char)':', stderr);
}
zerrmsg(stderr, fmt, ap);
}
/**/
mod_export void
zerr(VA_ALIST1(const char *fmt))
VA_DCL
{
va_list ap;
VA_DEF_ARG(const char *fmt);
if (errflag || noerrs) {
if (noerrs < 2)
errflag = 1;
return;
}
VA_START(ap, fmt);
VA_GET_ARG(ap, fmt, const char *);
zwarning(NULL, fmt, ap);
va_end(ap);
errflag = 1;
}
/**/
mod_export void
zerrnam(VA_ALIST2(const char *cmd, const char *fmt))
VA_DCL
{
va_list ap;
VA_DEF_ARG(const char *cmd);
VA_DEF_ARG(const char *fmt);
if (errflag || noerrs)
return;
VA_START(ap, fmt);
VA_GET_ARG(ap, cmd, const char *);
VA_GET_ARG(ap, fmt, const char *);
zwarning(cmd, fmt, ap);
va_end(ap);
errflag = 1;
}
/**/
mod_export void
zwarn(VA_ALIST1(const char *fmt))
VA_DCL
{
va_list ap;
VA_DEF_ARG(const char *fmt);
if (errflag || noerrs)
return;
VA_START(ap, fmt);
VA_GET_ARG(ap, fmt, const char *);
zwarning(NULL, fmt, ap);
va_end(ap);
}
/**/
mod_export void
zwarnnam(VA_ALIST2(const char *cmd, const char *fmt))
VA_DCL
{
va_list ap;
VA_DEF_ARG(const char *cmd);
VA_DEF_ARG(const char *fmt);
if (errflag || noerrs)
return;
VA_START(ap, fmt);
VA_GET_ARG(ap, cmd, const char *);
VA_GET_ARG(ap, fmt, const char *);
zwarning(cmd, fmt, ap);
va_end(ap);
}
#ifdef DEBUG
/**/
mod_export void
dputs(VA_ALIST1(const char *message))
VA_DCL
{
char *filename;
FILE *file;
va_list ap;
VA_DEF_ARG(const char *message);
VA_START(ap, message);
VA_GET_ARG(ap, message, const char *);
if ((filename = getsparam("ZSH_DEBUG_LOG")) != NULL &&
(file = fopen(filename, "a")) != NULL) {
zerrmsg(file, message, ap);
fclose(file);
} else
zerrmsg(stderr, message, ap);
va_end(ap);
}
#endif /* DEBUG */
#ifdef __CYGWIN__
/*
* This works around an occasional problem with dllwrap on Cygwin, seen
* on at least two installations. It fails to find the last symbol
* exported in alphabetical order (in our case zwarnnam). Until this is
* properly categorised and fixed we add a dummy symbol at the end.
*/
mod_export void
zz_plural_z_alpha(void)
{
}
#endif
/**/
void
zerrmsg(FILE *file, const char *fmt, va_list ap)
{
const char *str;
int num;
#ifdef DEBUG
long lnum;
#endif
#ifdef HAVE_STRERROR_R
#define ERRBUFSIZE (80)
int olderrno;
char errbuf[ERRBUFSIZE];
#endif
char *errmsg;
if ((unset(SHINSTDIN) || locallevel) && lineno) {
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
fprintf(file, "%lld: ", lineno);
#else
fprintf(file, "%ld: ", (long)lineno);
#endif
} else
fputc((unsigned char)' ', file);
while (*fmt)
if (*fmt == '%') {
fmt++;
switch (*fmt++) {
case 's':
str = va_arg(ap, const char *);
nicezputs(str, file);
break;
case 'l': {
char *s;
str = va_arg(ap, const char *);
num = va_arg(ap, int);
num = metalen(str, num);
s = zhalloc(num + 1);
memcpy(s, str, num);
s[num] = '\0';
nicezputs(s, file);
break;
}
#ifdef DEBUG
case 'L':
lnum = va_arg(ap, long);
fprintf(file, "%ld", lnum);
break;
#endif
case 'd':
num = va_arg(ap, int);
fprintf(file, "%d", num);
break;
case '%':
putc('%', file);
break;
case 'c':
num = va_arg(ap, int);
#ifdef MULTIBYTE_SUPPORT
mb_metacharinit();
zputs(wcs_nicechar(num, NULL, NULL), file);
#else
zputs(nicechar(num), file);
#endif
break;
case 'e':
/* print the corresponding message for this errno */
num = va_arg(ap, int);
if (num == EINTR) {
fputs("interrupt\n", file);
errflag = 1;
return;
}
errmsg = strerror(num);
/* If the message is not about I/O problems, it looks better *
* if we uncapitalize the first letter of the message */
if (num == EIO)
fputs(errmsg, file);
else {
fputc(tulower(errmsg[0]), file);
fputs(errmsg + 1, file);
}
break;
}
} else {
putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, file);
fmt++;
}
putc('\n', file);
fflush(file);
}
/* Output a single character, for the termcap routines. *
* This is used instead of putchar since it can be a macro. */
/**/
mod_export int
putraw(int c)
{
putc(c, stdout);
return 0;
}
/* Output a single character, for the termcap routines. */
/**/
mod_export int
putshout(int c)
{
putc(c, shout);
return 0;
}
/*
* Turn a character into a visible representation thereof. The visible
* string is put together in a static buffer, and this function returns
* a pointer to it. Printable characters stand for themselves, DEL is
* represented as "^?", newline and tab are represented as "\n" and
* "\t", and normal control characters are represented in "^C" form.
* Characters with bit 7 set, if unprintable, are represented as "\M-"
* followed by the visible representation of the character with bit 7
* stripped off. Tokens are interpreted, rather than being treated as
* literal characters.
*
* Note that the returned string is metafied, so that it must be
* treated like any other zsh internal string (and not, for example,
* output directly).
*
* This function is used even if MULTIBYTE_SUPPORT is defined: we
* use it as a fallback in case we couldn't identify a wide character
* in a multibyte string.
*/
/**/
mod_export char *
nicechar(int c)
{
static char buf[6];
char *s = buf;
c &= 0xff;
if (isprint(c))
goto done;
if (c & 0x80) {
if (isset(PRINTEIGHTBIT))
goto done;
*s++ = '\\';
*s++ = 'M';
*s++ = '-';
c &= 0x7f;
if(isprint(c))
goto done;
}
if (c == 0x7f) {
*s++ = '^';
c = '?';
} else if (c == '\n') {
*s++ = '\\';
c = 'n';
} else if (c == '\t') {
*s++ = '\\';
c = 't';
} else if (c < 0x20) {
*s++ = '^';
c += 0x40;
}
done:
/*
* The resulting string is still metafied, so check if
* we are returning a character in the range that needs metafication.
* This can't happen if the character is printed "nicely", so
* this results in a maximum of two bytes total (plus the null).
*/
if (imeta(c)) {
*s++ = Meta;
*s++ = c ^ 32;
} else
*s++ = c;
*s = 0;
return buf;
}
/**/
#ifdef MULTIBYTE_SUPPORT
static mbstate_t mb_shiftstate;
/*
* Initialise multibyte state: called before a sequence of
* wcs_nicechar() or mb_metacharlenconv().
*/
/**/
mod_export void
mb_metacharinit(void)
{
memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
}
/*
* The number of bytes we need to allocate for a "nice" representation
* of a multibyte character.
*
* We double MB_CUR_MAX to take account of the fact that
* we may need to metafy. In fact the representation probably
* doesn't allow every character to be in the meta range, but
* we don't need to be too pedantic.
*
* The 12 is for the output of a UCS-4 code; we don't actually
* need this at the same time as MB_CUR_MAX, but again it's
* not worth calculating more exactly.
*/
#define NICECHAR_MAX (12 + 2*MB_CUR_MAX)
/*
* Input a wide character. Output a printable representation,
* which is a metafied multibyte string. With widthp return
* the printing width.
*
* swide, if non-NULL, is used to help the completion code, which needs
* to know the printing width of the each part of the representation.
* *swide is set to the part of the returned string where the wide
* character starts. Any string up to that point is ASCII characters,
* so the width of it is (*swide - <return_value>). Anything left is
* a single wide character corresponding to the remaining width.
* Either the initial ASCII part or the wide character part may be empty
* (but not both). (Note the complication that the wide character
* part may contain metafied characters.)
*
* The caller needs to call mb_metacharinit() before the first call, to
* set up the multibyte shift state for a range of characters.
*/
/**/
mod_export char *
wcs_nicechar(wchar_t c, size_t *widthp, char **swidep)
{
static char *buf;
static int bufalloc = 0, newalloc;
char *s, *mbptr;
int ret = 0;
VARARR(char, mbstr, MB_CUR_MAX);
/*
* We want buf to persist beyond the return. MB_CUR_MAX and hence
* NICECHAR_MAX may not be constant, so we have to allocate this at
* run time. (We could probably get away with just allocating a
* large buffer, in practice.) For efficiency, only reallocate if
* we really need to, since this function will be called frequently.
*/
newalloc = NICECHAR_MAX;
if (bufalloc != newalloc)
{
bufalloc = newalloc;
buf = (char *)zrealloc(buf, bufalloc);
}
s = buf;
if (!iswprint(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
if (c == 0x7f) {
*s++ = '^';
c = '?';
} else if (c == L'\n') {
*s++ = '\\';
c = 'n';
} else if (c == L'\t') {
*s++ = '\\';
c = 't';
} else if (c < 0x20) {
*s++ = '^';
c += 0x40;
} else if (c >= 0x80) {
ret = -1;
}
}
if (ret != -1)
ret = wcrtomb(mbstr, c, &mb_shiftstate);
if (ret == -1) {
memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
/*
* Can't or don't want to convert character: use UCS-2 or
* UCS-4 code in print escape format.
*
* This comparison fails and generates a compiler warning
* if wchar_t is 16 bits, but the code is still correct.
*/
if (c >= 0x10000) {
sprintf(buf, "\\U%.8x", (unsigned int)c);
if (widthp)
*widthp = 10;
} else if (c >= 0x100) {
sprintf(buf, "\\u%.4x", (unsigned int)c);
if (widthp)
*widthp = 6;
} else {
strcpy(buf, nicechar((int)c));
/*
* There may be metafied characters from nicechar(),
* so compute width and end position independently.
*/
if (widthp)
*widthp = ztrlen(buf);
if (swidep)
*swidep = buf + strlen(buf);
return buf;
}
if (swidep)
*swidep = buf + *widthp;
return buf;
}
if (widthp) {
int wcw = WCWIDTH(c);
*widthp = (s - buf);
if (wcw >= 0)
*widthp += wcw;
else
(*widthp)++;
}
if (swidep)
*swidep = s;
for (mbptr = mbstr; ret; s++, mbptr++, ret--) {
DPUTS(s >= buf + NICECHAR_MAX,
"BUG: buffer too small in wcs_nicechar");
if (imeta(*mbptr)) {
*s++ = Meta;
DPUTS(s >= buf + NICECHAR_MAX,
"BUG: buffer too small for metafied char in wcs_nicechar");
*s = *mbptr ^ 32;
} else {
*s = *mbptr;
}
}
*s = 0;
return buf;
}
/**/
mod_export int
zwcwidth(wint_t wc)
{
int wcw;
/* assume a single-byte character if not valid */
if (wc == WEOF || unset(MULTIBYTE))
return 1;
wcw = WCWIDTH(wc);
/* if not printable, assume width 1 */
if (wcw < 0)
return 1;
return wcw;
}
/**/
#endif /* MULTIBYTE_SUPPORT */
/*
* Search the path for prog and return the file name.
* The returned value is unmetafied and in the unmeta storage
* area (N.B. should be duplicated if not used immediately and not
* equal to *namep).
*
* If namep is not NULL, *namep is set to the metafied programme
* name, which is in heap storage.
*/
/**/
char *
pathprog(char *prog, char **namep)
{
char **pp, ppmaxlen = 0, *buf, *funmeta;
struct stat st;
for (pp = path; *pp; pp++)
{
int len = strlen(*pp);
if (len > ppmaxlen)
ppmaxlen = len;
}
buf = zhalloc(ppmaxlen + strlen(prog) + 2);
for (pp = path; *pp; pp++) {
sprintf(buf, "%s/%s", *pp, prog);
funmeta = unmeta(buf);
if (access(funmeta, F_OK) == 0 &&
stat(funmeta, &st) >= 0 &&
!S_ISDIR(st.st_mode)) {
if (namep)
*namep = buf;
return funmeta;
}
}
return NULL;
}
/* get a symlink-free pathname for s relative to PWD */
/**/
char *
findpwd(char *s)
{
char *t;
if (*s == '/')
return xsymlink(s);
s = tricat((pwd[1]) ? pwd : "", "/", s);
t = xsymlink(s);
zsfree(s);
return t;
}
/* Check whether a string contains the *
* name of the present directory. */
/**/
int
ispwd(char *s)
{
struct stat sbuf, tbuf;
if (stat(unmeta(s), &sbuf) == 0 && stat(".", &tbuf) == 0)
if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino)
return 1;
return 0;
}
static char xbuf[PATH_MAX*2];
/**/
static char **
slashsplit(char *s)
{
char *t, **r, **q;
int t0;
if (!*s)
return (char **) zshcalloc(sizeof(char **));
for (t = s, t0 = 0; *t; t++)
if (*t == '/')
t0++;
q = r = (char **) zalloc(sizeof(char **) * (t0 + 2));
while ((t = strchr(s, '/'))) {
*q++ = ztrduppfx(s, t - s);
while (*t == '/')
t++;
if (!*t) {
*q = NULL;
return r;
}
s = t;
}
*q++ = ztrdup(s);
*q = NULL;
return r;
}
/* expands symlinks and .. or . expressions */
/* if flag = 0, only expand .. and . expressions */
/**/
static int
xsymlinks(char *s)
{
char **pp, **opp;
char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2];
int t0, ret = 0;
opp = pp = slashsplit(s);
for (; *pp; pp++) {
if (!strcmp(*pp, ".")) {
zsfree(*pp);
continue;
}
if (!strcmp(*pp, "..")) {
char *p;
zsfree(*pp);
if (!strcmp(xbuf, "/"))
continue;
if (!*xbuf)
continue;
p = xbuf + strlen(xbuf);
while (*--p != '/');
*p = '\0';
continue;
}
sprintf(xbuf2, "%s/%s", xbuf, *pp);
t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
if (t0 == -1) {
strcat(xbuf, "/");
strcat(xbuf, *pp);
zsfree(*pp);
} else {
ret = 1;
metafy(xbuf3, t0, META_NOALLOC);
if (*xbuf3 == '/') {
strcpy(xbuf, "");
xsymlinks(xbuf3 + 1);
} else
xsymlinks(xbuf3);
zsfree(*pp);
}
}
free(opp);
return ret;
}
/*
* expand symlinks in s, and remove other weird things:
* note that this always expands symlinks.
*/
/**/
char *
xsymlink(char *s)
{
if (*s != '/')
return NULL;
*xbuf = '\0';
xsymlinks(s + 1);
if (!*xbuf)
return ztrdup("/");
return ztrdup(xbuf);
}
/**/
void
print_if_link(char *s)
{
if (*s == '/') {
*xbuf = '\0';
if (xsymlinks(s + 1))
printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout);
}
}
/* print a directory */
/**/
void
fprintdir(char *s, FILE *f)
{
Nameddir d = finddir(s);
if (!d)
fputs(unmeta(s), f);
else {
putc('~', f);
fputs(unmeta(d->node.nam), f);
fputs(unmeta(s + strlen(d->dir)), f);
}
}
/*
* Substitute a directory using a name.
* If there is none, return the original argument.
*
* At this level all strings involved are metafied.
*/
/**/
char *
substnamedir(char *s)
{
Nameddir d = finddir(s);
if (!d)
return quotestring(s, NULL, QT_BACKSLASH);
return zhtricat("~", d->node.nam, quotestring(s + strlen(d->dir),
NULL, QT_BACKSLASH));
}
/* Returns the current username. It caches the username *
* and uid to try to avoid requerying the password files *
* or NIS/NIS+ database. */
/**/
uid_t cached_uid;
/**/
char *cached_username;
/**/
char *
get_username(void)
{
#ifdef HAVE_GETPWUID
struct passwd *pswd;
uid_t current_uid;
current_uid = getuid();
if (current_uid != cached_uid) {
cached_uid = current_uid;
zsfree(cached_username);
if ((pswd = getpwuid(current_uid)))
cached_username = ztrdup(pswd->pw_name);
else
cached_username = ztrdup("");
}
#else /* !HAVE_GETPWUID */
cached_uid = getuid();
#endif /* !HAVE_GETPWUID */
return cached_username;
}
/* static variables needed by finddir(). */
static char *finddir_full;
static Nameddir finddir_last;
static int finddir_best;
/* ScanFunc used by finddir(). */
/**/
static void
finddir_scan(HashNode hn, UNUSED(int flags))
{
Nameddir nd = (Nameddir) hn;
if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full)
&& !(nd->node.flags & ND_NOABBREV)) {
finddir_last=nd;
finddir_best=nd->diff;
}
}
/*
* See if a path has a named directory as its prefix.
* If passed a NULL argument, it will invalidate any
* cached information.
*
* s here is metafied.
*/
/**/
Nameddir
finddir(char *s)
{
static struct nameddir homenode = { {NULL, "", 0}, NULL, 0 };
static int ffsz;
char **ares;
int len;
/* Invalidate directory cache if argument is NULL. This is called *
* whenever a node is added to or removed from the hash table, and *
* whenever the value of $HOME changes. (On startup, too.) */
if (!s) {
homenode.dir = home ? home : "";
homenode.diff = home ? strlen(home) : 0;
if(homenode.diff==1)
homenode.diff = 0;
if(!finddir_full)
finddir_full = zalloc(ffsz = PATH_MAX);
finddir_full[0] = 0;
return finddir_last = NULL;
}
#if 0
/*
* It's not safe to use the cache while we have function
* transformations, and it's not clear it's worth the
* complexity of guessing here whether subst_string_by_hook
* is going to turn up the goods.
*/
if (!strcmp(s, finddir_full) && *finddir_full)
return finddir_last;
#endif
if ((int)strlen(s) >= ffsz) {
free(finddir_full);
finddir_full = zalloc(ffsz = strlen(s) * 2);
}
strcpy(finddir_full, s);
finddir_best=0;
finddir_last=NULL;
finddir_scan(&homenode.node, 0);
scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
ares = subst_string_by_hook("zsh_directory_name", "d", finddir_full);
if (ares && arrlen(ares) >= 2 &&
(len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
/* better duplicate this string since it's come from REPLY */
finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
finddir_last->node.nam = zhtricat("[", dupstring(ares[0]), "]");
finddir_last->dir = dupstrpfx(finddir_full, len);
finddir_last->diff = len - strlen(finddir_last->node.nam);
finddir_best = len;
}
return finddir_last;
}
/* add a named directory */
/**/
mod_export void
adduserdir(char *s, char *t, int flags, int always)
{
Nameddir nd;
char *eptr;
/* We don't maintain a hash table in non-interactive shells. */
if (!interact)
return;
/* The ND_USERNAME flag means that this possible hash table *
* entry is derived from a passwd entry. Such entries are *
* subordinate to explicitly generated entries. */
if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s))
return;
/* Normal parameter assignments generate calls to this function, *
* with always==0. Unless the AUTO_NAME_DIRS option is set, we *
* don't let such assignments actually create directory names. *
* Instead, a reference to the parameter as a directory name can *
* cause the actual creation of the hash table entry. */
if (!always && unset(AUTONAMEDIRS) &&
!nameddirtab->getnode2(nameddirtab, s))
return;
if (!t || *t != '/' || strlen(t) >= PATH_MAX) {
/* We can't use this value as a directory, so simply remove *
* the corresponding entry in the hash table, if any. */
HashNode hn = nameddirtab->removenode(nameddirtab, s);
if(hn)
nameddirtab->freenode(hn);
return;
}
/* add the name */
nd = (Nameddir) zshcalloc(sizeof *nd);
nd->node.flags = flags;
eptr = t + strlen(t);
while (eptr > t && eptr[-1] == '/')
eptr--;
if (eptr == t) {
/*
* Don't abbreviate multiple slashes at the start of a
* named directory, since these are sometimes used for
* special purposes.
*/
nd->dir = ztrdup(t);
} else
nd->dir = ztrduppfx(t, eptr - t);
/* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */
if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD"))
nd->node.flags |= ND_NOABBREV;
nameddirtab->addnode(nameddirtab, ztrdup(s), nd);
}
/* Get a named directory: this function can cause a directory name *
* to be added to the hash table, if it isn't there already. */
/**/
char *
getnameddir(char *name)
{
Param pm;
char *str;
Nameddir nd;
/* Check if it is already in the named directory table */
if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)))
return dupstring(nd->dir);
/* Check if there is a scalar parameter with this name whose value *
* begins with a `/'. If there is, add it to the hash table and *
* return the new value. */
if ((pm = (Param) paramtab->getnode(paramtab, name)) &&
(PM_TYPE(pm->node.flags) == PM_SCALAR) &&
(str = getsparam(name)) && *str == '/') {
pm->node.flags |= PM_NAMEDDIR;
adduserdir(name, str, 0, 1);
return str;
}
#ifdef HAVE_GETPWNAM
{
/* Retrieve an entry from the password table/database for this user. */
struct passwd *pw;
if ((pw = getpwnam(name))) {
char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir)
: ztrdup(pw->pw_dir);
adduserdir(name, dir, ND_USERNAME, 1);
str = dupstring(dir);
zsfree(dir);
return str;
}
}
#endif /* HAVE_GETPWNAM */
/* There are no more possible sources of directory names, so give up. */
return NULL;
}
/*
* Compare directories. Both are metafied.
*/
/**/
static int
dircmp(char *s, char *t)
{
if (s) {
for (; *s == *t; s++, t++)
if (!*s)
return 0;
if (!*s && *t == '/')
return 0;
}
return 1;
}
/*
* Extra functions to call before displaying the prompt.
* The data is a Prepromptfn.
*/
static LinkList prepromptfns;
/* Add a function to the list of pre-prompt functions. */
/**/
mod_export void
addprepromptfn(voidvoidfnptr_t func)
{
Prepromptfn ppdat = (Prepromptfn)zalloc(sizeof(struct prepromptfn));
ppdat->func = func;
if (!prepromptfns)
prepromptfns = znewlinklist();
zaddlinknode(prepromptfns, ppdat);
}
/* Remove a function from the list of pre-prompt functions. */
/**/
mod_export void
delprepromptfn(voidvoidfnptr_t func)
{
LinkNode ln;
for (ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
Prepromptfn ppdat = (Prepromptfn)getdata(ln);
if (ppdat->func == func) {
(void)remnode(prepromptfns, ln);
zfree(ppdat, sizeof(struct prepromptfn));
return;
}
}
#ifdef DEBUG
dputs("BUG: failed to delete node from prepromptfns");
#endif
}
/*
* Functions to call at a particular time even if not at
* the prompt. This is handled by zle. The data is a
* Timedfn. The functions must be in time order, but this
* is enforced by addtimedfn().
*
* Note on debugging: the code in sched.c currently assumes it's
* the only user of timedfns for the purposes of checking whether
* there's a function on the list. If this becomes no longer the case,
* the DPUTS() tests in sched.c need rewriting.
*/
/**/
mod_export LinkList timedfns;
/* Add a function to the list of timed functions. */
/**/
mod_export void
addtimedfn(voidvoidfnptr_t func, time_t when)
{
Timedfn tfdat = (Timedfn)zalloc(sizeof(struct timedfn));
tfdat->func = func;
tfdat->when = when;
if (!timedfns) {
timedfns = znewlinklist();
zaddlinknode(timedfns, tfdat);
} else {
LinkNode ln = firstnode(timedfns);
/*
* Insert the new element in the linked list. We do
* rather too much work here since the standard
* functions insert after a given node, whereas we
* want to insert the new data before the first element
* with a greater time.
*
* In practice, the only use of timed functions is
* sched, which only adds the one function; so this
* whole branch isn't used beyond the following block.
*/
if (!ln) {
zaddlinknode(timedfns, tfdat);
return;
}
for (;;) {
Timedfn tfdat2;
LinkNode next = nextnode(ln);
if (!next) {
zaddlinknode(timedfns, tfdat);
return;
}
tfdat2 = (Timedfn)getdata(next);
if (when < tfdat2->when) {
zinsertlinknode(timedfns, ln, tfdat);
return;
}
ln = next;
}
}
}
/*
* Delete a function from the list of timed functions.
* Note that if the function apperas multiple times only
* the first occurrence will be removed.
*
* Note also that when zle calls the function it does *not*
* automatically delete the entry from the list. That must
* be done by the function called. This is recommended as otherwise
* the function will keep being called immediately. (It just so
* happens this "feature" fits in well with the only current use
* of timed functions.)
*/
/**/
mod_export void
deltimedfn(voidvoidfnptr_t func)
{
LinkNode ln;
for (ln = firstnode(timedfns); ln; ln = nextnode(ln)) {
Timedfn ppdat = (Timedfn)getdata(ln);
if (ppdat->func == func) {
(void)remnode(timedfns, ln);
zfree(ppdat, sizeof(struct timedfn));
return;
}
}
#ifdef DEBUG
dputs("BUG: failed to delete node from timedfns");
#endif
}
/* the last time we checked mail */
/**/
time_t lastmailcheck;
/* the last time we checked the people in the WATCH variable */
/**/
time_t lastwatch;
/*
* Call a function given by "name" with optional arguments
* "lnklist". If these are present the first argument is the function name.
*
* If "arrayp" is not zero, we also look through
* the array "name"_functions and execute functions found there.
*
* If "retval" is not NULL, the return value of the first hook function to
* return non-zero is stored in *"retval". The return value is not otherwise
* available as the calling context is restored.
*/
/**/
mod_export int
callhookfunc(char *name, LinkList lnklst, int arrayp, int *retval)
{
Shfunc shfunc;
/*
* Save stopmsg, since user doesn't get a chance to respond
* to a list of jobs generated in a hook.
*/
int osc = sfcontext, osm = stopmsg, stat = 1, ret = 0;
int old_incompfunc = incompfunc;
sfcontext = SFC_HOOK;
incompfunc = 0;
if ((shfunc = getshfunc(name))) {
ret = doshfunc(shfunc, lnklst, 1);
stat = 0;
}
if (arrayp) {
char **arrptr;
int namlen = strlen(name);
VARARR(char, arrnam, namlen + HOOK_SUFFIX_LEN);
memcpy(arrnam, name, namlen);
memcpy(arrnam + namlen, HOOK_SUFFIX, HOOK_SUFFIX_LEN);
if ((arrptr = getaparam(arrnam))) {
arrptr = arrdup(arrptr);
for (; *arrptr; arrptr++) {
if ((shfunc = getshfunc(*arrptr))) {
int newret = doshfunc(shfunc, lnklst, 1);
if (!ret)
ret = newret;
stat = 0;
}
}
}
}
sfcontext = osc;
stopmsg = osm;
incompfunc = old_incompfunc;
if (retval)
*retval = ret;
return stat;
}
/* do pre-prompt stuff */
/**/
void
preprompt(void)
{
static time_t lastperiodic;
LinkNode ln;
int period = getiparam("PERIOD");
int mailcheck = getiparam("MAILCHECK");
if (isset(PROMPTSP) && isset(PROMPTCR) && !use_exit_printed && shout) {
/* The PROMPT_SP heuristic will move the prompt down to a new line
* if there was any dangling output on the line (assuming the terminal
* has automatic margins, but we try even if hasam isn't set).
* Unfortunately it interacts badly with ZLE displaying message
* when ^D has been pressed. So just disable PROMPT_SP logic in
* this case */
char *eolmark = getsparam("PROMPT_EOL_MARK");
char *str;
int percents = opts[PROMPTPERCENT], w = 0;
if (!eolmark)
eolmark = "%B%S%#%s%b";
opts[PROMPTPERCENT] = 1;
str = promptexpand(eolmark, 1, NULL, NULL, NULL);
countprompt(str, &w, 0, -1);
opts[PROMPTPERCENT] = percents;
zputs(str, shout);
fprintf(shout, "%*s\r%*s\r", (int)zterm_columns - w - !hasxn,
"", w, "");
fflush(shout);
free(str);
}
/* If NOTIFY is not set, then check for completed *
* jobs before we print the prompt. */
if (unset(NOTIFY))
scanjobs();
if (errflag)
return;
/* If a shell function named "precmd" exists, *
* then execute it. */
callhookfunc("precmd", NULL, 1, NULL);
if (errflag)
return;
/* If 1) the parameter PERIOD exists, 2) a hook function for *
* "periodic" exists, 3) it's been greater than PERIOD since we *
* executed any such hook, then execute it now. */
if (period && (time(NULL) > lastperiodic + period) &&
!callhookfunc("periodic", NULL, 1, NULL))
lastperiodic = time(NULL);
if (errflag)
return;
/* If WATCH is set, then check for the *
* specified login/logout events. */
if (watch) {
if ((int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK")) {
dowatch();
lastwatch = time(NULL);
}
}
if (errflag)
return;
/* Check mail */
if (mailcheck && (int) difftime(time(NULL), lastmailcheck) > mailcheck) {
char *mailfile;
if (mailpath && *mailpath && **mailpath)
checkmailpath(mailpath);
else {
queue_signals();
if ((mailfile = getsparam("MAIL")) && *mailfile) {
char *x[2];
x[0] = mailfile;
x[1] = NULL;
checkmailpath(x);
}
unqueue_signals();
}
lastmailcheck = time(NULL);
}
if (prepromptfns) {
for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
Prepromptfn ppnode = (Prepromptfn)getdata(ln);
ppnode->func();
}
}
}
/**/
static void
checkmailpath(char **s)
{
struct stat st;
char *v, *u, c;
while (*s) {
for (v = *s; *v && *v != '?'; v++);
c = *v;
*v = '\0';
if (c != '?')
u = NULL;
else
u = v + 1;
if (**s == 0) {
*v = c;
zerr("empty MAILPATH component: %s", *s);
} else if (mailstat(unmeta(*s), &st) == -1) {
if (errno != ENOENT)
zerr("%e: %s", errno, *s);
} else if (S_ISDIR(st.st_mode)) {
LinkList l;
DIR *lock = opendir(unmeta(*s));
char buf[PATH_MAX * 2], **arr, **ap;
int ct = 1;
if (lock) {
char *fn;
pushheap();
l = newlinklist();
while ((fn = zreaddir(lock, 1)) && !errflag) {
if (u)
sprintf(buf, "%s/%s?%s", *s, fn, u);
else
sprintf(buf, "%s/%s", *s, fn);
addlinknode(l, dupstring(buf));
ct++;
}
closedir(lock);
ap = arr = (char **) zhalloc(ct * sizeof(char *));
while ((*ap++ = (char *)ugetnode(l)));
checkmailpath(arr);
popheap();
}
} else if (shout) {
if (st.st_size && st.st_atime <= st.st_mtime &&
st.st_mtime > lastmailcheck) {
if (!u) {
fprintf(shout, "You have new mail.\n");
fflush(shout);
} else {
char *usav;
int uusav = underscoreused;
usav = zalloc(underscoreused);
if (usav)
memcpy(usav, zunderscore, underscoreused);
setunderscore(*s);
u = dupstring(u);
if (! parsestr(u)) {
singsub(&u);
zputs(u, shout);
fputc('\n', shout);
fflush(shout);
}
if (usav) {
setunderscore(usav);
zfree(usav, uusav);
}
}
}
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));
fflush(shout);
}
}
*v = c;
s++;
}
}
/* This prints the XTRACE prompt. */
/**/
FILE *xtrerr = 0;
/**/
void
printprompt4(void)
{
if (!xtrerr)
xtrerr = stderr;
if (prompt4) {
int l, t = opts[XTRACE];
char *s = dupstring(prompt4);
opts[XTRACE] = 0;
unmetafy(s, &l);
s = unmetafy(promptexpand(metafy(s, l, META_NOALLOC),
0, NULL, NULL, NULL), &l);
opts[XTRACE] = t;
fprintf(xtrerr, "%s", s);
free(s);
}
}
/**/
mod_export void
freestr(void *a)
{
zsfree(a);
}
/**/
mod_export void
gettyinfo(struct ttyinfo *ti)
{
if (SHTTY != -1) {
#ifdef HAVE_TERMIOS_H
# ifdef HAVE_TCGETATTR
if (tcgetattr(SHTTY, &ti->tio) == -1)
# else
if (ioctl(SHTTY, TCGETS, &ti->tio) == -1)
# endif
zerr("bad tcgets: %e", errno);
#else
# ifdef HAVE_TERMIO_H
ioctl(SHTTY, TCGETA, &ti->tio);
# else
ioctl(SHTTY, TIOCGETP, &ti->sgttyb);
ioctl(SHTTY, TIOCLGET, &ti->lmodes);
ioctl(SHTTY, TIOCGETC, &ti->tchars);
ioctl(SHTTY, TIOCGLTC, &ti->ltchars);
# endif
#endif
}
}
/**/
mod_export void
settyinfo(struct ttyinfo *ti)
{
if (SHTTY != -1) {
#ifdef HAVE_TERMIOS_H
# ifdef HAVE_TCGETATTR
# ifndef TCSADRAIN
# define TCSADRAIN 1 /* XXX Princeton's include files are screwed up */
# endif
while (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1 && errno == EINTR)
;
# else
while (ioctl(SHTTY, TCSETS, &ti->tio) == -1 && errno == EINTR)
;
# endif
/* zerr("settyinfo: %e",errno);*/
#else
# ifdef HAVE_TERMIO_H
ioctl(SHTTY, TCSETA, &ti->tio);
# else
ioctl(SHTTY, TIOCSETN, &ti->sgttyb);
ioctl(SHTTY, TIOCLSET, &ti->lmodes);
ioctl(SHTTY, TIOCSETC, &ti->tchars);
ioctl(SHTTY, TIOCSLTC, &ti->ltchars);
# endif
#endif
}
}
/* the default tty state */
/**/
mod_export struct ttyinfo shttyinfo;
/* != 0 if we need to call resetvideo() */
/**/
mod_export int resetneeded;
#ifdef TIOCGWINSZ
/* window size changed */
/**/
mod_export int winchanged;
#endif
static int
adjustlines(int signalled)
{
int oldlines = zterm_lines;
#ifdef TIOCGWINSZ
if (signalled || zterm_lines <= 0)
zterm_lines = shttyinfo.winsize.ws_row;
else
shttyinfo.winsize.ws_row = zterm_lines;
#endif /* TIOCGWINSZ */
if (zterm_lines <= 0) {
DPUTS(signalled, "BUG: Impossible TIOCGWINSZ rows");
zterm_lines = tclines > 0 ? tclines : 24;
}
if (zterm_lines > 2)
termflags &= ~TERM_SHORT;
else
termflags |= TERM_SHORT;
return (zterm_lines != oldlines);
}
static int
adjustcolumns(int signalled)
{
int oldcolumns = zterm_columns;
#ifdef TIOCGWINSZ
if (signalled || zterm_columns <= 0)
zterm_columns = shttyinfo.winsize.ws_col;
else
shttyinfo.winsize.ws_col = zterm_columns;
#endif /* TIOCGWINSZ */
if (zterm_columns <= 0) {
DPUTS(signalled, "BUG: Impossible TIOCGWINSZ cols");
zterm_columns = tccolumns > 0 ? tccolumns : 80;
}
if (zterm_columns > 2)
termflags &= ~TERM_NARROW;
else
termflags |= TERM_NARROW;
return (zterm_columns != oldcolumns);
}
/* check the size of the window and adjust if necessary. *
* The value of from: *
* 0: called from update_job or setupvals *
* 1: called from the SIGWINCH handler *
* 2: called from the LINES parameter callback *
* 3: called from the COLUMNS parameter callback */
/**/
void
adjustwinsize(int from)
{
static int getwinsz = 1;
#ifdef TIOCGWINSZ
int ttyrows = shttyinfo.winsize.ws_row;
int ttycols = shttyinfo.winsize.ws_col;
#endif
int resetzle = 0;
if (getwinsz || from == 1) {
#ifdef TIOCGWINSZ
if (SHTTY == -1)
return;
if (ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize) == 0) {
resetzle = (ttyrows != shttyinfo.winsize.ws_row ||
ttycols != shttyinfo.winsize.ws_col);
if (from == 0 && resetzle && ttyrows && ttycols)
from = 1; /* Signal missed while a job owned the tty? */
ttyrows = shttyinfo.winsize.ws_row;
ttycols = shttyinfo.winsize.ws_col;
} else {
/* Set to value from environment on failure */
shttyinfo.winsize.ws_row = zterm_lines;
shttyinfo.winsize.ws_col = zterm_columns;
resetzle = (from == 1);
}
#else
resetzle = from == 1;
#endif /* TIOCGWINSZ */
} /* else
return; */
switch (from) {
case 0:
case 1:
getwinsz = 0;
/* Calling setiparam() here calls this function recursively, but *
* because we've already called adjustlines() and adjustcolumns() *
* here, recursive calls are no-ops unless a signal intervenes. *
* The commented "else return;" above might be a safe shortcut, *
* but I'm concerned about what happens on race conditions; e.g., *
* suppose the user resizes his xterm during `eval $(resize)'? */
if (adjustlines(from) && zgetenv("LINES"))
setiparam("LINES", zterm_lines);
if (adjustcolumns(from) && zgetenv("COLUMNS"))
setiparam("COLUMNS", zterm_columns);
getwinsz = 1;
break;
case 2:
resetzle = adjustlines(0);
break;
case 3:
resetzle = adjustcolumns(0);
break;
}
#ifdef TIOCGWINSZ
if (interact && from >= 2 &&
(shttyinfo.winsize.ws_row != ttyrows ||
shttyinfo.winsize.ws_col != ttycols)) {
/* shttyinfo.winsize is already set up correctly */
ioctl(SHTTY, TIOCSWINSZ, (char *)&shttyinfo.winsize);
}
#endif /* TIOCGWINSZ */
if (zleactive && resetzle) {
#ifdef TIOCGWINSZ
winchanged =
#endif /* TIOCGWINSZ */
resetneeded = 1;
zleentry(ZLE_CMD_RESET_PROMPT);
zleentry(ZLE_CMD_REFRESH);
}
}
/*
* Ensure the fdtable is large enough for fd, and that the
* maximum fd is set appropriately.
*/
static void
check_fd_table(int fd)
{
if (fd <= max_zsh_fd)
return;
if (fd >= fdtable_size) {
int old_size = fdtable_size;
while (fd >= fdtable_size)
fdtable = zrealloc(fdtable,
(fdtable_size *= 2)*sizeof(*fdtable));
memset(fdtable + old_size, 0,
(fdtable_size - old_size) * sizeof(*fdtable));
}
max_zsh_fd = fd;
}
/* Move a fd to a place >= 10 and mark the new fd in fdtable. If the fd *
* is already >= 10, it is not moved. If it is invalid, -1 is returned. */
/**/
mod_export int
movefd(int fd)
{
if(fd != -1 && fd < 10) {
#ifdef F_DUPFD
int fe = fcntl(fd, F_DUPFD, 10);
#else
int fe = movefd(dup(fd));
#endif
/*
* To close or not to close if fe is -1?
* If it is -1, we haven't moved the fd, so if we close
* it we lose it; but we're probably not going to be able
* to use it in situ anyway. So probably better to avoid a leak.
*/
zclose(fd);
fd = fe;
}
if(fd != -1) {
check_fd_table(fd);
fdtable[fd] = FDT_INTERNAL;
}
return fd;
}
/*
* Move fd x to y. If x == -1, fd y is closed.
* Returns y for success, -1 for failure.
*/
/**/
mod_export int
redup(int x, int y)
{
int ret = y;
if(x < 0)
zclose(y);
else if (x != y) {
if (dup2(x, y) == -1) {
ret = -1;
} else {
check_fd_table(y);
fdtable[y] = fdtable[x];
if (fdtable[y] == FDT_FLOCK || fdtable[y] == FDT_FLOCK_EXEC)
fdtable[y] = FDT_INTERNAL;
}
/*
* Closing any fd to the locked file releases the lock.
* This isn't expected to happen, it's here for completeness.
*/
if (fdtable[x] == FDT_FLOCK)
fdtable_flocks--;
zclose(x);
}
return ret;
}
/*
* Indicate that an fd has a file lock; if cloexec is 1 it will be closed
* on exec.
* The fd should already be known to fdtable (e.g. by movefd).
* Note the fdtable code doesn't care what sort of lock
* is used; this simply prevents the main shell exiting prematurely
* when it holds a lock.
*/
/**/
mod_export void
addlockfd(int fd, int cloexec)
{
if (cloexec) {
if (fdtable[fd] != FDT_FLOCK)
fdtable_flocks++;
fdtable[fd] = FDT_FLOCK;
} else {
fdtable[fd] = FDT_FLOCK_EXEC;
}
}
/* Close the given fd, and clear it from fdtable. */
/**/
mod_export int
zclose(int fd)
{
if (fd >= 0) {
/*
* Careful: we allow closing of arbitrary fd's, beyond
* max_zsh_fd. In that case we don't try anything clever.
*/
if (fd <= max_zsh_fd) {
if (fdtable[fd] == FDT_FLOCK)
fdtable_flocks--;
fdtable[fd] = FDT_UNUSED;
while (max_zsh_fd > 0 && fdtable[max_zsh_fd] == FDT_UNUSED)
max_zsh_fd--;
if (fd == coprocin)
coprocin = -1;
if (fd == coprocout)
coprocout = -1;
}
return close(fd);
}
return -1;
}
/*
* Close an fd returning 0 if used for locking; return -1 if it isn't.
*/
/**/
mod_export int
zcloselockfd(int fd)
{
if (fd > max_zsh_fd)
return -1;
if (fdtable[fd] != FDT_FLOCK && fdtable[fd] != FDT_FLOCK_EXEC)
return -1;
zclose(fd);
return 0;
}
#ifdef HAVE__MKTEMP
extern char *_mktemp(char *);
#endif
/* Get a unique filename for use as a temporary file. If "prefix" is
* NULL, the name is relative to $TMPPREFIX; If it is non-NULL, the
* unique suffix includes a prefixed '.' for improved readability. If
* "use_heap" is true, we allocate the returned name on the heap. */
/**/
mod_export char *
gettempname(const char *prefix, int use_heap)
{
char *ret, *suffix = prefix ? ".XXXXXX" : "XXXXXX";
queue_signals();
if (!prefix && !(prefix = getsparam("TMPPREFIX")))
prefix = DEFAULT_TMPPREFIX;
if (use_heap)
ret = dyncat(unmeta(prefix), suffix);
else
ret = bicat(unmeta(prefix), suffix);
#ifdef HAVE__MKTEMP
/* Zsh uses mktemp() safely, so silence the warnings */
ret = (char *) _mktemp(ret);
#else
ret = (char *) mktemp(ret);
#endif
unqueue_signals();
return ret;
}
/**/
mod_export int
gettempfile(const char *prefix, int use_heap, char **tempname)
{
char *fn;
int fd;
#if HAVE_MKSTEMP
char *suffix = prefix ? ".XXXXXX" : "XXXXXX";
if (!prefix && !(prefix = getsparam("TMPPREFIX")))
prefix = DEFAULT_TMPPREFIX;
if (use_heap)
fn = dyncat(unmeta(prefix), suffix);
else
fn = bicat(unmeta(prefix), suffix);
fd = mkstemp(fn);
if (fd < 0) {
if (!use_heap)
free(fn);
fn = NULL;
}
#else
int failures = 0;
do {
if (!(fn = gettempname(prefix, use_heap))) {
fd = -1;
break;
}
if ((fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600)) >= 0)
break;
if (!use_heap)
free(fn);
fn = NULL;
} while (errno == EEXIST && ++failures < 16);
#endif
*tempname = fn;
return fd;
}
/* Check if a string contains a token */
/**/
mod_export int
has_token(const char *s)
{
while(*s)
if(itok(*s++))
return 1;
return 0;
}
/* Delete a character in a string */
/**/
mod_export void
chuck(char *str)
{
while ((str[0] = str[1]))
str++;
}
/**/
mod_export int
tulower(int c)
{
c &= 0xff;
return (isupper(c) ? tolower(c) : c);
}
/**/
mod_export int
tuupper(int c)
{
c &= 0xff;
return (islower(c) ? toupper(c) : c);
}
/* copy len chars from t into s, and null terminate */
/**/
void
ztrncpy(char *s, char *t, int len)
{
while (len--)
*s++ = *t++;
*s = '\0';
}
/* copy t into *s and update s */
/**/
mod_export void
strucpy(char **s, char *t)
{
char *u = *s;
while ((*u++ = *t++));
*s = u - 1;
}
/**/
mod_export void
struncpy(char **s, char *t, int n)
{
char *u = *s;
while (n--)
*u++ = *t++;
*s = u;
*u = '\0';
}
/* Return the number of elements in an array of pointers. *
* It doesn't count the NULL pointer at the end. */
/**/
mod_export int
arrlen(char **s)
{
int count;
for (count = 0; *s; s++, count++);
return count;
}
/* Skip over a balanced pair of parenthesis. */
/**/
mod_export int
skipparens(char inpar, char outpar, char **s)
{
int level;
if (**s != inpar)
return -1;
for (level = 1; *++*s && level;)
if (**s == inpar)
++level;
else if (**s == outpar)
--level;
return level;
}
/* Convert string to zlong (see zsh.h). This function (without the z) *
* is contained in the ANSI standard C library, but a lot of them seem *
* to be broken. */
/**/
mod_export zlong
zstrtol(const char *s, char **t, int base)
{
const char *inp, *trunc = NULL;
zulong calc = 0, newcalc = 0;
int neg;
while (inblank(*s))
s++;
if ((neg = (*s == '-')))
s++;
else if (*s == '+')
s++;
if (!base) {
if (*s != '0')
base = 10;
else if (*++s == 'x' || *s == 'X')
base = 16, s++;
else
base = 8;
}
inp = s;
if (base < 2 || base > 36) {
zerr("invalid base (must be 2 to 36 inclusive): %d", base);
return (zlong)0;
} else if (base <= 10)
for (; *s >= '0' && *s < ('0' + base); s++) {
if (trunc)
continue;
newcalc = calc * base + *s - '0';
if (newcalc < calc)
{
trunc = s;
continue;
}
calc = newcalc;
}
else
for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
|| (*s >= 'A' && *s < ('A' + base - 10)); s++) {
if (trunc)
continue;
newcalc = calc*base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
if (newcalc < calc)
{
trunc = s;
continue;
}
calc = newcalc;
}
/*
* Special case: check for a number that was just too long for
* signed notation.
* Extra special case: the lowest negative number would trigger
* the first test, but is actually representable correctly.
* This is a 1 in the top bit, all others zero, so test for
* that explicitly.
*/
if (!trunc && (zlong)calc < 0 &&
(!neg || calc & ~((zulong)1 << (8*sizeof(zulong)-1))))
{
trunc = s - 1;
calc /= base;
}
if (trunc)
zwarn("number truncated after %d digits: %s", (int)(trunc - inp), inp);
if (t)
*t = (char *)s;
return neg ? -(zlong)calc : (zlong)calc;
}
/**/
mod_export int
setblock_fd(int turnonblocking, int fd, long *modep)
{
#ifdef O_NDELAY
# ifdef O_NONBLOCK
# define NONBLOCK (O_NDELAY|O_NONBLOCK)
# else /* !O_NONBLOCK */
# define NONBLOCK O_NDELAY
# endif /* !O_NONBLOCK */
#else /* !O_NDELAY */
# ifdef O_NONBLOCK
# define NONBLOCK O_NONBLOCK
# else /* !O_NONBLOCK */
# define NONBLOCK 0
# endif /* !O_NONBLOCK */
#endif /* !O_NDELAY */
#if NONBLOCK
struct stat st;
if (!fstat(fd, &st) && !S_ISREG(st.st_mode)) {
*modep = fcntl(fd, F_GETFL, 0);
if (*modep != -1) {
if (!turnonblocking) {
/* We want to know if blocking was off */
if ((*modep & NONBLOCK) ||
!fcntl(fd, F_SETFL, *modep | NONBLOCK))
return 1;
} else if ((*modep & NONBLOCK) &&
!fcntl(fd, F_SETFL, *modep & ~NONBLOCK)) {
/* Here we want to know if the state changed */
return 1;
}
}
} else
#endif /* NONBLOCK */
*modep = -1;
return 0;
#undef NONBLOCK
}
/**/
int
setblock_stdin(void)
{
long mode;
return setblock_fd(1, 0, &mode);
}
/*
* Check for pending input on fd. If polltty is set, we may need to
* use termio to look for input. As a final resort, go to non-blocking
* input and try to read a character, which in this case will be
* returned in *readchar.
*
* Note that apart from setting (and restoring) non-blocking input,
* this function does not change the input mode. The calling function
* should have set cbreak mode if necessary.
*/
/**/
mod_export int
read_poll(int fd, int *readchar, int polltty, zlong microseconds)
{
int ret = -1;
long mode = -1;
char c;
#ifdef HAVE_SELECT
fd_set foofd;
struct timeval expire_tv;
#else
#ifdef FIONREAD
int val;
#endif
#endif
#ifdef HAS_TIO
struct ttyinfo ti;
#endif
#if defined(HAS_TIO) && !defined(__CYGWIN__)
/*
* Under Solaris, at least, reading from the terminal in non-canonical
* mode requires that we use the VMIN mechanism to poll. Any attempt
* to check any other way, or to set the terminal to non-blocking mode
* and poll that way, fails; it will just for canonical mode input.
* We should probably use this mechanism if the user has set non-canonical
* mode, in which case testing here for isatty() and ~ICANON would be
* better than testing whether bin_read() set it, but for now we've got
* enough problems.
*
* Under Cygwin, you won't be surprised to here, this mechanism,
* although present, doesn't work, and we *have* to use ordinary
* non-blocking reads to find out if there is a character present
* in non-canonical mode.
*
* I am assuming Solaris is nearer the UNIX norm. This is not necessarily
* as plausible as it sounds, but it seems the right way to guess.
* pws 2000/06/26
*/
if (polltty) {
gettyinfo(&ti);
if ((polltty = ti.tio.c_cc[VMIN])) {
ti.tio.c_cc[VMIN] = 0;
/* termios timeout is 10ths of a second */
ti.tio.c_cc[VTIME] = (int) (microseconds / (zlong)100000);
settyinfo(&ti);
}
}
#else
polltty = 0;
#endif
#ifdef HAVE_SELECT
expire_tv.tv_sec = (int) (microseconds / (zlong)1000000);
expire_tv.tv_usec = microseconds % (zlong)1000000;
FD_ZERO(&foofd);
FD_SET(fd, &foofd);
ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv);
#else
#ifdef FIONREAD
if (ioctl(fd, FIONREAD, (char *) &val) == 0)
ret = (val > 0);
#endif
#endif
if (ret < 0) {
/*
* Final attempt: set non-blocking read and try to read a character.
* Praise Bill, this works under Cygwin (nothing else seems to).
*/
if ((polltty || setblock_fd(0, fd, &mode)) && read(fd, &c, 1) > 0) {
*readchar = c;
ret = 1;
}
if (mode != -1)
fcntl(fd, F_SETFL, mode);
}
#ifdef HAS_TIO
if (polltty) {
ti.tio.c_cc[VMIN] = 1;
ti.tio.c_cc[VTIME] = 0;
settyinfo(&ti);
}
#endif
return (ret > 0);
}
/**/
int
checkrmall(char *s)
{
if (!shout)
return 1;
fprintf(shout, "zsh: sure you want to delete all the files in ");
if (*s != '/') {
nicezputs(pwd[1] ? unmeta(pwd) : "", shout);
fputc('/', shout);
}
nicezputs(s, shout);
if(isset(RMSTARWAIT)) {
fputs("? (waiting ten seconds)", shout);
fflush(shout);
zbeep();
sleep(10);
fputc('\n', shout);
}
fputs(" [yn]? ", shout);
fflush(shout);
zbeep();
return (getquery("ny", 1) == 'y');
}
/**/
mod_export ssize_t
read_loop(int fd, char *buf, size_t len)
{
ssize_t got = len;
while (1) {
ssize_t ret = read(fd, buf, len);
if (ret == len)
break;
if (ret <= 0) {
if (ret < 0) {
if (errno == EINTR)
continue;
if (fd != SHTTY)
zwarn("read failed: %e", errno);
}
return ret;
}
buf += ret;
len -= ret;
}
return got;
}
/**/
mod_export ssize_t
write_loop(int fd, const char *buf, size_t len)
{
ssize_t wrote = len;
while (1) {
ssize_t ret = write(fd, buf, len);
if (ret == len)
break;
if (ret < 0) {
if (errno == EINTR)
continue;
if (fd != SHTTY)
zwarn("write failed: %e", errno);
return -1;
}
buf += ret;
len -= ret;
}
return wrote;
}
static int
read1char(int echo)
{
char c;
while (read(SHTTY, &c, 1) != 1) {
if (errno != EINTR || errflag || retflag || breaks || contflag)
return -1;
}
if (echo)
write_loop(SHTTY, &c, 1);
return STOUC(c);
}
/**/
mod_export int
noquery(int purge)
{
int val = 0;
#ifdef FIONREAD
char c;
ioctl(SHTTY, FIONREAD, (char *)&val);
if (purge) {
for (; val; val--) {
if (read(SHTTY, &c, 1) != 1) {
/* Do nothing... */
}
}
}
#endif
return val;
}
/**/
int
getquery(char *valid_chars, int purge)
{
int c, d, nl = 0;
int isem = !strcmp(term, "emacs");
struct ttyinfo ti;
attachtty(mypgrp);
gettyinfo(&ti);
#ifdef HAS_TIO
ti.tio.c_lflag &= ~ECHO;
if (!isem) {
ti.tio.c_lflag &= ~ICANON;
ti.tio.c_cc[VMIN] = 1;
ti.tio.c_cc[VTIME] = 0;
}
#else
ti.sgttyb.sg_flags &= ~ECHO;
if (!isem)
ti.sgttyb.sg_flags |= CBREAK;
#endif
settyinfo(&ti);
if (noquery(purge)) {
if (!isem)
settyinfo(&shttyinfo);
write_loop(SHTTY, "n\n", 2);
return 'n';
}
while ((c = read1char(0)) >= 0) {
if (c == 'Y')
c = 'y';
else if (c == 'N')
c = 'n';
if (!valid_chars)
break;
if (c == '\n') {
c = *valid_chars;
nl = 1;
break;
}
if (strchr(valid_chars, c)) {
nl = 1;
break;
}
zbeep();
}
if (c >= 0) {
char buf = (char)c;
write_loop(SHTTY, &buf, 1);
}
if (nl)
write_loop(SHTTY, "\n", 1);
if (isem) {
if (c != '\n')
while ((d = read1char(1)) >= 0 && d != '\n');
} else {
if (c != '\n' && !valid_chars) {
#ifdef MULTIBYTE_SUPPORT
if (isset(MULTIBYTE) && c >= 0) {
/*
* No waiting for a valid character, and no draining;
* we should ensure we haven't stopped in the middle
* of a multibyte character.
*/
mbstate_t mbs;
char cc = (char)c;
memset(&mbs, 0, sizeof(mbs));
for (;;) {
size_t ret = mbrlen(&cc, 1, &mbs);
if (ret != MB_INCOMPLETE)
break;
c = read1char(1);
if (c < 0)
break;
cc = (char)c;
}
}
#endif
write_loop(SHTTY, "\n", 1);
}
}
settyinfo(&shttyinfo);
return c;
}
static int d;
static char *guess, *best;
static Patprog spckpat;
/**/
static void
spscan(HashNode hn, UNUSED(int scanflags))
{
int nd;
if (spckpat && pattry(spckpat, hn->nam))
return;
nd = spdist(hn->nam, guess, (int) strlen(guess) / 4 + 1);
if (nd <= d) {
best = hn->nam;
d = nd;
}
}
/* spellcheck a word */
/* fix s ; if hist is nonzero, fix the history list too */
/**/
mod_export void
spckword(char **s, int hist, int cmd, int ask)
{
char *t, *correct_ignore;
int x;
char ic = '\0';
int ne;
int preflen = 0;
int autocd = cmd && isset(AUTOCD) && strcmp(*s, ".") && strcmp(*s, "..");
if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%')
return;
if (!strcmp(*s, "in"))
return;
if (!(*s)[0] || !(*s)[1])
return;
if (cmd) {
if (shfunctab->getnode(shfunctab, *s) ||
builtintab->getnode(builtintab, *s) ||
cmdnamtab->getnode(cmdnamtab, *s) ||
aliastab->getnode(aliastab, *s) ||
reswdtab->getnode(reswdtab, *s))
return;
else if (isset(HASHLISTALL)) {
cmdnamtab->filltable(cmdnamtab);
if (cmdnamtab->getnode(cmdnamtab, *s))
return;
}
}
t = *s;
if (*t == Tilde || *t == Equals || *t == String)
t++;
for (; *t; t++)
if (itok(*t))
return;
best = NULL;
for (t = *s; *t; t++)
if (*t == '/')
break;
if (**s == Tilde && !*t)
return;
if ((correct_ignore = getsparam("CORRECT_IGNORE")) != NULL) {
tokenize(correct_ignore = dupstring(correct_ignore));
remnulargs(correct_ignore);
spckpat = patcompile(correct_ignore, 0, NULL);
} else
spckpat = NULL;
if (**s == String && !*t) {
guess = *s + 1;
if (itype_end(guess, IIDENT, 1) == guess)
return;
ic = String;
d = 100;
scanhashtable(paramtab, 1, 0, 0, spscan, 0);
} else if (**s == Equals) {
if (*t)
return;
if (hashcmd(guess = *s + 1, pathchecked))
return;
d = 100;
ic = Equals;
scanhashtable(aliastab, 1, 0, 0, spscan, 0);
scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
} else {
guess = *s;
if (*guess == Tilde || *guess == String) {
ic = *guess;
if (!*++t)
return;
guess = dupstring(guess);
ne = noerrs;
noerrs = 2;
singsub(&guess);
noerrs = ne;
if (!guess)
return;
preflen = strlen(guess) - strlen(t);
}
if (access(unmeta(guess), F_OK) == 0)
return;
best = spname(guess);
if (!*t && cmd) {
if (hashcmd(guess, pathchecked))
return;
d = 100;
scanhashtable(reswdtab, 1, 0, 0, spscan, 0);
scanhashtable(aliastab, 1, 0, 0, spscan, 0);
scanhashtable(shfunctab, 1, 0, 0, spscan, 0);
scanhashtable(builtintab, 1, 0, 0, spscan, 0);
scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
if (autocd) {
char **pp;
for (pp = cdpath; *pp; pp++) {
char bestcd[PATH_MAX + 1];
int thisdist;
/* Less than d here, instead of less than or equal *
* as used in spscan(), so that an autocd is chosen *
* only when it is better than anything so far, and *
* so we prefer directories earlier in the cdpath. */
if ((thisdist = mindist(*pp, *s, bestcd)) < d) {
best = dupstring(bestcd);
d = thisdist;
}
}
}
}
}
if (errflag)
return;
if (best && (int)strlen(best) > 1 && strcmp(best, guess)) {
if (ic) {
char *u;
if (preflen) {
/* do not correct the result of an expansion */
if (strncmp(guess, best, preflen))
return;
/* replace the temporarily expanded prefix with the original */
u = (char *) hcalloc(t - *s + strlen(best + preflen) + 1);
strncpy(u, *s, t - *s);
strcpy(u + (t - *s), best + preflen);
} else {
u = (char *) hcalloc(strlen(best) + 2);
strcpy(u + 1, best);
}
best = u;
guess = *s;
*guess = *best = ztokens[ic - Pound];
}
if (ask) {
if (noquery(0)) {
x = 'n';
} else if (shout) {
char *pptbuf;
pptbuf = promptexpand(sprompt, 0, best, guess, NULL);
zputs(pptbuf, shout);
free(pptbuf);
fflush(shout);
zbeep();
x = getquery("nyae \t", 0);
if (cmd && x == 'n')
pathchecked = path;
} else
x = 'n';
} else
x = 'y';
if (x == 'y' || x == ' ' || x == '\t') {
*s = dupstring(best);
if (hist)
hwrep(best);
} else if (x == 'a') {
histdone |= HISTFLAG_NOEXEC;
} else if (x == 'e') {
histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL;
}
if (ic)
**s = ic;
}
}
/*
* Helper for ztrftime. Called with a pointer to the length left
* in the buffer, and a new string length to decrement from that.
* Returns 0 if the new length fits, 1 otherwise. We assume a terminating
* NUL and return 1 if that doesn't fit.
*/
static int
ztrftimebuf(int *bufsizeptr, int decr)
{
if (*bufsizeptr <= decr)
return 1;
*bufsizeptr -= decr;
return 0;
}
/*
* Like the system function, this returns the number of characters
* copied, not including the terminating NUL. This may be zero
* if the string didn't fit.
*
* As an extension, try to detect an error in strftime --- typically
* not enough memory --- and return -1. Not guaranteed to be portable,
* since the strftime() interface doesn't make any guarantees about
* the state of the buffer if it returns zero.
*/
/**/
mod_export int
ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm)
{
int hr12, decr;
#ifndef HAVE_STRFTIME
static char *astr[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *estr[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
"Aug", "Sep", "Oct", "Nov", "Dec"};
#endif
char *origbuf = buf;
char tmp[4];
while (*fmt)
if (*fmt == '%') {
int strip;
fmt++;
if (*fmt == '-') {
strip = 1;
fmt++;
} else
strip = 0;
/*
* Assume this format will take up at least two
* characters. Not always true, but if that matters
* we are so close to the edge it's not a big deal.
* Fix up some longer cases specially when we get to them.
*/
if (ztrftimebuf(&bufsize, 2))
return -1;
switch (*fmt++) {
case 'd':
if (tm->tm_mday > 9 || !strip)
*buf++ = '0' + tm->tm_mday / 10;
*buf++ = '0' + tm->tm_mday % 10;
break;
case 'f':
strip = 1;
/* FALLTHROUGH */
case 'e':
if (tm->tm_mday > 9)
*buf++ = '0' + tm->tm_mday / 10;
else if (!strip)
*buf++ = ' ';
*buf++ = '0' + tm->tm_mday % 10;
break;
case 'K':
strip = 1;
/* FALLTHROUGH */
case 'H':
case 'k':
if (tm->tm_hour > 9)
*buf++ = '0' + tm->tm_hour / 10;
else if (!strip) {
if (fmt[-1] == 'H')
*buf++ = '0';
else
*buf++ = ' ';
}
*buf++ = '0' + tm->tm_hour % 10;
break;
case 'L':
strip = 1;
/* FALLTHROUGH */
case 'l':
hr12 = tm->tm_hour % 12;
if (hr12 == 0)
hr12 = 12;
if (hr12 > 9)
*buf++ = '1';
else if (!strip)
*buf++ = ' ';
*buf++ = '0' + (hr12 % 10);
break;
case 'm':
if (tm->tm_mon > 8 || !strip)
*buf++ = '0' + (tm->tm_mon + 1) / 10;
*buf++ = '0' + (tm->tm_mon + 1) % 10;
break;
case 'M':
if (tm->tm_min > 9 || !strip)
*buf++ = '0' + tm->tm_min / 10;
*buf++ = '0' + tm->tm_min % 10;
break;
case 'S':
if (tm->tm_sec > 9 || !strip)
*buf++ = '0' + tm->tm_sec / 10;
*buf++ = '0' + tm->tm_sec % 10;
break;
case 'y':
if (tm->tm_year > 9 || !strip)
*buf++ = '0' + (tm->tm_year / 10) % 10;
*buf++ = '0' + tm->tm_year % 10;
break;
case '\0':
/* Guard against premature end of string */
*buf++ = '%';
fmt--;
break;
#ifndef HAVE_STRFTIME
case 'Y':
{
/*
* Not worth handling this natively if
* strftime has it.
*/
int year, digits, testyear;
year = tm->tm_year + 1900;
digits = 1;
testyear = year;
while (testyear > 9) {
digits++;
testyear /= 10;
}
if (ztrftimebuf(&bufsize, digits))
return -1;
sprintf(buf, "%d", year);
buf += digits;
break;
}
case 'a':
if (ztrftimebuf(&bufsize, strlen(astr[tm->tm_wday]) - 2))
return -1;
strucpy(&buf, astr[tm->tm_wday]);
break;
case 'b':
if (ztrftimebuf(&bufsize, strlen(estr[tm->tm_mon]) - 2))
return -1;
strucpy(&buf, estr[tm->tm_mon]);
break;
case 'p':
*buf++ = (tm->tm_hour > 11) ? 'p' : 'a';
*buf++ = 'm';
break;
default:
*buf++ = '%';
if (fmt[-1] != '%')
*buf++ = fmt[-1];
#else
default:
/*
* Remember we've already allowed for two characters
* in the accounting in bufsize (but nowhere else).
*/
*buf = '\1';
sprintf(tmp, strip ? "%%-%c" : "%%%c", fmt[-1]);
if (!strftime(buf, bufsize + 2, tmp, tm))
{
if (*buf) {
buf[0] = '\0';
return -1;
}
return 0;
}
decr = strlen(buf);
buf += decr;
bufsize -= decr - 2;
#endif
break;
}
} else {
if (ztrftimebuf(&bufsize, 1))
return -1;
*buf++ = *fmt++;
}
*buf = '\0';
return buf - origbuf;
}
/**/
mod_export char *
zjoin(char **arr, int delim, int heap)
{
int len = 0;
char **s, *ret, *ptr;
for (s = arr; *s; s++)
len += strlen(*s) + 1 + (imeta(delim) ? 1 : 0);
if (!len)
return heap? "" : ztrdup("");
ptr = ret = (heap ? (char *) hcalloc(len) : (char *) zshcalloc(len));
for (s = arr; *s; s++) {
strucpy(&ptr, *s);
if (imeta(delim)) {
*ptr++ = Meta;
*ptr++ = delim ^ 32;
}
else
*ptr++ = delim;
}
ptr[-1 - (imeta(delim) ? 1 : 0)] = '\0';
return ret;
}
/* Split a string containing a colon separated list *
* of items into an array of strings. */
/**/
char **
colonsplit(char *s, int uniq)
{
int ct;
char *t, **ret, **ptr, **p;
for (t = s, ct = 0; *t; t++) /* count number of colons */
if (*t == ':')
ct++;
ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2));
t = s;
do {
s = t;
/* move t to point at next colon */
for (; *t && *t != ':'; t++);
if (uniq)
for (p = ret; p < ptr; p++)
if ((int)strlen(*p) == t - s && ! strncmp(*p, s, t - s))
goto cont;
*ptr = (char *) zalloc((t - s) + 1);
ztrncpy(*ptr++, s, t - s);
cont: ;
}
while (*t++);
*ptr = NULL;
return ret;
}
/**/
static int
skipwsep(char **s)
{
char *t = *s;
int i = 0;
/*
* Don't need to handle mutlibyte characters, they can't
* be IWSEP. Do need to check for metafication.
*/
while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) {
if (*t == Meta)
t++;
t++;
i++;
}
*s = t;
return i;
}
/*
* haven't worked out what allownull does; it's passed down from
* sepsplit but all the cases it's used are either 0 or 1 without
* a comment. it seems to be something to do with the `nulstring'
* which i think is some kind of a metafication thing, so probably
* allownull's value is associated with whether we are using
* metafied strings.
* see findsep() below for handling of `quote' argument
*/
/**/
mod_export char **
spacesplit(char *s, int allownull, int heap, int quote)
{
char *t, **ret, **ptr;
int l = sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1);
char *(*dup)(const char *) = (heap ? dupstring : ztrdup);
ptr = ret = (heap ? (char **) hcalloc(l) : (char **) zshcalloc(l));
if (quote) {
/*
* we will be stripping quoted separators by hacking string,
* so make sure it's hackable.
*/
s = dupstring(s);
}
t = s;
skipwsep(&s);
MB_METACHARINIT();
if (*s && itype_end(s, ISEP, 1) != s)
*ptr++ = dup(allownull ? "" : nulstring);
else if (!allownull && t != s)
*ptr++ = dup("");
while (*s) {
char *iend = itype_end(s, ISEP, 1);
if (iend != s) {
s = iend;
skipwsep(&s);
}
else if (quote && *s == '\\') {
s++;
skipwsep(&s);
}
t = s;
(void)findsep(&s, NULL, quote);
if (s > t || allownull) {
*ptr = (heap ? (char *) hcalloc((s - t) + 1) :
(char *) zshcalloc((s - t) + 1));
ztrncpy(*ptr++, t, s - t);
} else
*ptr++ = dup(nulstring);
t = s;
skipwsep(&s);
}
if (!allownull && t != s)
*ptr++ = dup("");
*ptr = NULL;
return ret;
}
/*
* Find a separator. Return 0 if already at separator, 1 if separator
* found later, else -1. (Historical note: used to return length into
* string but this is all that is necessary and is less ambiguous with
* multibyte characters around.)
*
* *s is the string we are looking along, which will be updated
* to the point we have got to.
*
* sep is a possibly multicharacter separator to look for. If NULL,
* use normal separator characters. If *sep is NULL, split on individual
* characters.
*
* quote is a flag that '\<sep>' should not be treated as a separator.
* in this case we need to be able to strip the backslash directly
* in the string, so the calling function must have sent us something
* modifiable. currently this only works for sep == NULL. also in
* in this case only, we need to turn \\ into \.
*/
/**/
static int
findsep(char **s, char *sep, int quote)
{
/*
*/
int i, ilen;
char *t, *tt;
convchar_t c;
MB_METACHARINIT();
if (!sep) {
for (t = *s; *t; t += ilen) {
if (quote && *t == '\\') {
if (t[1] == '\\') {
chuck(t);
ilen = 1;
continue;
} else {
ilen = MB_METACHARLENCONV(t+1, &c);
if (WC_ZISTYPE(c, ISEP)) {
chuck(t);
/* then advance over new character, length ilen */
} else {
/* treat *t (backslash) as normal byte */
if (isep(*t))
break;
ilen = 1;
}
}
} else {
ilen = MB_METACHARLENCONV(t, &c);
if (WC_ZISTYPE(c, ISEP))
break;
}
}
i = (t > *s);
*s = t;
return i;
}
if (!sep[0]) {
/*
* NULL separator just means advance past first character,
* if any.
*/
if (**s) {
*s += MB_METACHARLEN(*s);
return 1;
}
return -1;
}
for (i = 0; **s; i++) {
/*
* The following works for multibyte characters by virtue of
* the fact that sep may be a string (and we don't care how
* it divides up, we need to match all of it).
*/
for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++);
if (!*t)
return (i > 0);
*s += MB_METACHARLEN(*s);
}
return -1;
}
/**/
char *
findword(char **s, char *sep)
{
char *r, *t;
int sl;
if (!**s)
return NULL;
if (sep) {
sl = strlen(sep);
r = *s;
while (! findsep(s, sep, 0)) {
r = *s += sl;
}
return r;
}
MB_METACHARINIT();
for (t = *s; *t; t += sl) {
convchar_t c;
sl = MB_METACHARLENCONV(t, &c);
if (!WC_ZISTYPE(c, ISEP))
break;
}
*s = t;
(void)findsep(s, sep, 0);
return t;
}
/**/
int
wordcount(char *s, char *sep, int mul)
{
int r, sl, c;
if (sep) {
r = 1;
sl = strlen(sep);
for (; (c = findsep(&s, sep, 0)) >= 0; s += sl)
if ((c && *(s + sl)) || mul)
r++;
} else {
char *t = s;
r = 0;
if (mul <= 0)
skipwsep(&s);
if ((*s && itype_end(s, ISEP, 1) != s) ||
(mul < 0 && t != s))
r++;
for (; *s; r++) {
char *ie = itype_end(s, ISEP, 1);
if (ie != s) {
s = ie;
if (mul <= 0)
skipwsep(&s);
}
(void)findsep(&s, NULL, 0);
t = s;
if (mul <= 0)
skipwsep(&s);
}
if (mul < 0 && t != s)
r++;
}
return r;
}
/**/
mod_export char *
sepjoin(char **s, char *sep, int heap)
{
char *r, *p, **t;
int l, sl;
char sepbuf[2];
if (!*s)
return heap ? "" : ztrdup("");
if (!sep) {
/* optimise common case that ifs[0] is space */
if (ifs && *ifs != ' ') {
MB_METACHARINIT();
sep = dupstrpfx(ifs, MB_METACHARLEN(ifs));
} else {
p = sep = sepbuf;
*p++ = ' ';
*p = '\0';
}
}
sl = strlen(sep);
for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++);
r = p = (heap ? (char *) hcalloc(l) : (char *) zshcalloc(l));
t = s;
while (*t) {
strucpy(&p, *t);
if (*++t)
strucpy(&p, sep);
}
*p = '\0';
return r;
}
/**/
char **
sepsplit(char *s, char *sep, int allownull, int heap)
{
int n, sl;
char *t, *tt, **r, **p;
/* Null string? Treat as empty string. */
if (s[0] == Nularg && !s[1])
s++;
if (!sep)
return spacesplit(s, allownull, heap, 0);
sl = strlen(sep);
n = wordcount(s, sep, 1);
r = p = (heap ? (char **) hcalloc((n + 1) * sizeof(char *)) :
(char **) zshcalloc((n + 1) * sizeof(char *)));
for (t = s; n--;) {
tt = t;
(void)findsep(&t, sep, 0);
*p = (heap ? (char *) hcalloc(t - tt + 1) :
(char *) zshcalloc(t - tt + 1));
strncpy(*p, tt, t - tt);
(*p)[t - tt] = '\0';
p++;
t += sl;
}
*p = NULL;
return r;
}
/* Get the definition of a shell function */
/**/
mod_export Shfunc
getshfunc(char *nam)
{
return (Shfunc) shfunctab->getnode(shfunctab, nam);
}
/*
* Call the function func to substitute string orig by setting
* the parameter reply.
* Return the array from reply, or NULL if the function returned
* non-zero status.
* The returned value comes directly from the parameter and
* so should be used before there is any chance of that
* being changed or unset.
* If arg1 is not NULL, it is used as an initial argument to
* the function, with the original string as the second argument.
*/
/**/
char **
subst_string_by_func(Shfunc func, char *arg1, char *orig)
{
int osc = sfcontext, osm = stopmsg, old_incompfunc = incompfunc;
LinkList l = newlinklist();
char **ret;
addlinknode(l, func->node.nam);
if (arg1)
addlinknode(l, arg1);
addlinknode(l, orig);
sfcontext = SFC_SUBST;
incompfunc = 0;
if (doshfunc(func, l, 1))
ret = NULL;
else
ret = getaparam("reply");
sfcontext = osc;
stopmsg = osm;
incompfunc = old_incompfunc;
return ret;
}
/**
* Front end to subst_string_by_func to use hook-like logic.
* name can refer to a function, and name + "_hook" can refer
* to an array containing a list of functions. The functions
* are tried in order until one returns success.
*/
/**/
char **
subst_string_by_hook(char *name, char *arg1, char *orig)
{
Shfunc func;
char **ret = NULL;
if ((func = getshfunc(name))) {
ret = subst_string_by_func(func, arg1, orig);
}
if (!ret) {
char **arrptr;
int namlen = strlen(name);
VARARR(char, arrnam, namlen + HOOK_SUFFIX_LEN);
memcpy(arrnam, name, namlen);
memcpy(arrnam + namlen, HOOK_SUFFIX, HOOK_SUFFIX_LEN);
if ((arrptr = getaparam(arrnam))) {
/* Guard against internal modification of the array */
arrptr = arrdup(arrptr);
for (; *arrptr; arrptr++) {
if ((func = getshfunc(*arrptr))) {
ret = subst_string_by_func(func, arg1, orig);
if (ret)
break;
}
}
}
}
return ret;
}
/**/
mod_export char **
mkarray(char *s)
{
char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s));
if ((*t = s))
t[1] = NULL;
return t;
}
/**/
mod_export void
zbeep(void)
{
char *vb;
queue_signals();
if ((vb = getsparam("ZBEEP"))) {
int len;
vb = getkeystring(vb, &len, GETKEYS_BINDKEY, NULL);
write_loop(SHTTY, vb, len);
} else if (isset(BEEP))
write_loop(SHTTY, "\07", 1);
unqueue_signals();
}
/**/
mod_export void
freearray(char **s)
{
char **t = s;
DPUTS(!s, "freearray() with zero argument");
while (*s)
zsfree(*s++);
free(t);
}
/**/
int
equalsplit(char *s, char **t)
{
for (; *s && *s != '='; s++);
if (*s == '=') {
*s++ = '\0';
*t = s;
return 1;
}
return 0;
}
static int specialcomma;
/* the ztypes table */
/**/
mod_export short int typtab[256];
/* initialize the ztypes table */
/**/
void
inittyptab(void)
{
int t0;
char *s;
for (t0 = 0; t0 != 256; t0++)
typtab[t0] = 0;
for (t0 = 0; t0 != 32; t0++)
typtab[t0] = typtab[t0 + 128] = ICNTRL;
typtab[127] = ICNTRL;
for (t0 = '0'; t0 <= '9'; t0++)
typtab[t0] = IDIGIT | IALNUM | IWORD | IIDENT | IUSER;
for (t0 = 'a'; t0 <= 'z'; t0++)
typtab[t0] = typtab[t0 - 'a' + 'A'] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
#ifndef MULTIBYTE_SUPPORT
/*
* This really doesn't seem to me the right thing to do when
* we have multibyte character support... it was a hack to assume
* eight bit characters `worked' for some values of work before
* we could test for them properly. I'm not 100% convinced
* having IIDENT here is a good idea at all, but this code
* should disappear into history...
*/
for (t0 = 0240; t0 != 0400; t0++)
typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
#endif
/* typtab['.'] |= IIDENT; */ /* Allow '.' in variable names - broken */
typtab['_'] = IIDENT | IUSER;
typtab['-'] = typtab['.'] = IUSER;
typtab[' '] |= IBLANK | INBLANK;
typtab['\t'] |= IBLANK | INBLANK;
typtab['\n'] |= INBLANK;
typtab['\0'] |= IMETA;
typtab[STOUC(Meta) ] |= IMETA;
typtab[STOUC(Marker)] |= IMETA;
for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Comma); t0++)
typtab[t0] |= ITOK | IMETA;
for (t0 = (int)STOUC(Snull); t0 <= (int)STOUC(Nularg); t0++)
typtab[t0] |= ITOK | IMETA | INULL;
for (s = ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ?
ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS); *s; s++) {
int c = STOUC(*s == Meta ? *++s ^ 32 : *s);
#ifdef MULTIBYTE_SUPPORT
if (!isascii(c)) {
/* see comment for wordchars below */
continue;
}
#endif
if (inblank(c)) {
if (s[1] == c)
s++;
else
typtab[c] |= IWSEP;
}
typtab[c] |= ISEP;
}
for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) {
int c = STOUC(*s == Meta ? *++s ^ 32 : *s);
#ifdef MULTIBYTE_SUPPORT
if (!isascii(c)) {
/*
* If we have support for multibyte characters, we don't
* handle non-ASCII characters here; instead, we turn
* wordchars into a wide character array.
* (We may actually have a single-byte 8-bit character set,
* but it works the same way.)
*/
continue;
}
#endif
typtab[c] |= IWORD;
}
#ifdef MULTIBYTE_SUPPORT
set_widearray(wordchars, &wordchars_wide);
set_widearray(ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ?
ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS), &ifs_wide);
#endif
for (s = SPECCHARS; *s; s++)
typtab[STOUC(*s)] |= ISPECIAL;
if (specialcomma)
typtab[STOUC(',')] |= ISPECIAL;
if (isset(BANGHIST) && bangchar && interact && isset(SHINSTDIN))
typtab[bangchar] |= ISPECIAL;
}
/**/
mod_export void
makecommaspecial(int yesno)
{
if ((specialcomma = yesno) != 0)
typtab[STOUC(',')] |= ISPECIAL;
else
typtab[STOUC(',')] &= ~ISPECIAL;
}
/**/
#ifdef MULTIBYTE_SUPPORT
/* A wide-character version of the iblank() macro. */
/**/
mod_export int
wcsiblank(wint_t wc)
{
if (iswspace(wc) && wc != L'\n')
return 1;
return 0;
}
/*
* zistype macro extended to support wide characters.
* Works for IIDENT, IWORD, IALNUM, ISEP.
* We don't need this for IWSEP because that only applies to
* a fixed set of ASCII characters.
* Note here that use of multibyte mode is not tested:
* that's because for ZLE this is unconditional,
* not dependent on the option. The caller must decide.
*/
/**/
mod_export int
wcsitype(wchar_t c, int itype)
{
int len;
mbstate_t mbs;
VARARR(char, outstr, MB_CUR_MAX);
if (!isset(MULTIBYTE))
return zistype(c, itype);
/*
* Strategy: the shell requires that the multibyte representation
* be an extension of ASCII. So see if converting the character
* produces an ASCII character. If it does, use zistype on that.
* If it doesn't, use iswalnum on the original character.
* If that fails, resort to the appropriate wide character array.
*/
memset(&mbs, 0, sizeof(mbs));
len = wcrtomb(outstr, c, &mbs);
if (len == 0) {
/* NULL is special */
return zistype(0, itype);
} else if (len == 1 && isascii(outstr[0])) {
return zistype(outstr[0], itype);
} else {
switch (itype) {
case IIDENT:
if (!isset(POSIXIDENTIFIERS))
return 0;
return iswalnum(c);
case IWORD:
if (iswalnum(c))
return 1;
/*
* If we are handling combining characters, any punctuation
* characters with zero width needs to be considered part of
* a word. If we are not handling combining characters then
* logically they are still part of the word, even if they
* don't get displayed properly, so always do this.
*/
if (IS_COMBINING(c))
return 1;
return !!wmemchr(wordchars_wide.chars, c, wordchars_wide.len);
case ISEP:
return !!wmemchr(ifs_wide.chars, c, ifs_wide.len);
default:
return iswalnum(c);
}
}
}
/**/
#endif
/*
* Find the end of a set of characters in the set specified by itype;
* one of IALNUM, IIDENT, IWORD or IUSER. For non-ASCII characters, we assume
* alphanumerics are part of the set, with the exception that
* identifiers are not treated that way if POSIXIDENTIFIERS is set.
*
* See notes above for identifiers.
* Returns the same pointer as passed if not on an identifier character.
* If "once" is set, just test the first character, i.e. (outptr !=
* inptr) tests whether the first character is valid in an identifier.
*
* Currently this is only called with itype IIDENT, IUSER or ISEP.
*/
/**/
mod_export char *
itype_end(const char *ptr, int itype, int once)
{
#ifdef MULTIBYTE_SUPPORT
if (isset(MULTIBYTE) &&
(itype != IIDENT || !isset(POSIXIDENTIFIERS))) {
mb_metacharinit();
while (*ptr) {
wint_t wc;
int len = mb_metacharlenconv(ptr, &wc);
if (!len)
break;
if (wc == WEOF) {
/* invalid, treat as single character */
int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
/* in this case non-ASCII characters can't match */
if (chr > 127 || !zistype(chr,itype))
break;
} else if (len == 1 && isascii(*ptr)) {
/* ASCII: can't be metafied, use standard test */
if (!zistype(*ptr,itype))
break;
} else {
/*
* Valid non-ASCII character.
*/
switch (itype) {
case IWORD:
if (!iswalnum(wc) &&
!wmemchr(wordchars_wide.chars, wc,
wordchars_wide.len))
return (char *)ptr;
break;
case ISEP:
if (!wmemchr(ifs_wide.chars, wc, ifs_wide.len))
return (char *)ptr;
break;
default:
if (!iswalnum(wc))
return (char *)ptr;
}
}
ptr += len;
if (once)
break;
}
} else
#endif
for (;;) {
int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
if (!zistype(chr,itype))
break;
ptr += (*ptr == Meta) ? 2 : 1;
if (once)
break;
}
/*
* Nasty. The first argument is const char * because we
* don't modify it here. However, we really want to pass
* back the same type as was passed down, to allow idioms like
* p = itype_end(p, IIDENT, 0);
* So returning a const char * isn't really the right thing to do.
* Without having two different functions the following seems
* to be the best we can do.
*/
return (char *)ptr;
}
/**/
mod_export char **
arrdup(char **s)
{
char **x, **y;
y = x = (char **) zhalloc(sizeof(char *) * (arrlen(s) + 1));
while ((*x++ = dupstring(*s++)));
return y;
}
/**/
mod_export char **
zarrdup(char **s)
{
char **x, **y;
y = x = (char **) zalloc(sizeof(char *) * (arrlen(s) + 1));
while ((*x++ = ztrdup(*s++)));
return y;
}
/**/
#ifdef MULTIBYTE_SUPPORT
/**/
mod_export wchar_t **
wcs_zarrdup(wchar_t **s)
{
wchar_t **x, **y;
y = x = (wchar_t **) zalloc(sizeof(wchar_t *) * (arrlen((char **)s) + 1));
while ((*x++ = wcs_ztrdup(*s++)));
return y;
}
/**/
#endif /* MULTIBYTE_SUPPORT */
/**/
static char *
spname(char *oldname)
{
char *p, spnameguess[PATH_MAX + 1], spnamebest[PATH_MAX + 1];
static char newname[PATH_MAX + 1];
char *new = newname, *old = oldname;
int bestdist = 0, thisdist, thresh, maxthresh = 0;
/* This loop corrects each directory component of the path, stopping *
* when any correction distance would exceed the distance threshold. *
* NULL is returned only if the first component cannot be corrected; *
* otherwise a copy of oldname with a corrected prefix is returned. *
* Rationale for this, if there ever was any, has been forgotten. */
for (;;) {
while (*old == '/') {
if ((new - newname) >= (sizeof(newname)-1))
return NULL;
*new++ = *old++;
}
*new = '\0';
if (*old == '\0')
return newname;
p = spnameguess;
for (; *old != '/' && *old != '\0'; old++)
if (p < spnameguess + PATH_MAX)
*p++ = *old;
*p = '\0';
/* Every component is allowed a single distance 2 correction or two *
* distance 1 corrections. Longer ones get additional corrections. */
thresh = (int)(p - spnameguess) / 4 + 1;
if (thresh < 3)
thresh = 3;
else if (thresh > 100)
thresh = 100;
if ((thisdist = mindist(newname, spnameguess, spnamebest)) >= thresh) {
/* The next test is always true, except for the first path *
* component. We could initialize bestdist to some large *
* constant instead, and then compare to that constant here, *
* because an invariant is that we've never exceeded the *
* threshold for any component so far; but I think that looks *
* odd to the human reader, and we may make use of the total *
* distance for all corrections at some point in the future. */
if (bestdist < maxthresh) {
strcpy(new, spnameguess);
strcat(new, old);
return newname;
} else
return NULL;
} else {
maxthresh = bestdist + thresh;
bestdist += thisdist;
}
for (p = spnamebest; (*new = *p++);)
new++;
}
}
/**/
static int
mindist(char *dir, char *mindistguess, char *mindistbest)
{
int mindistd, nd;
DIR *dd;
char *fn;
char *buf;
if (dir[0] == '\0')
dir = ".";
mindistd = 100;
buf = zalloc(strlen(dir) + strlen(mindistguess) + 2);
sprintf(buf, "%s/%s", dir, mindistguess);
if (access(unmeta(buf), F_OK) == 0) {
strcpy(mindistbest, mindistguess);
free(buf);
return 0;
}
free(buf);
if (!(dd = opendir(unmeta(dir))))
return mindistd;
while ((fn = zreaddir(dd, 0))) {
nd = spdist(fn, mindistguess,
(int)strlen(mindistguess) / 4 + 1);
if (nd <= mindistd) {
strcpy(mindistbest, fn);
mindistd = nd;
if (mindistd == 0)
break;
}
}
closedir(dd);
return mindistd;
}
/**/
static int
spdist(char *s, char *t, int thresh)
{
/* TODO: Correction for non-ASCII and multibyte-input keyboards. */
char *p, *q;
const char qwertykeymap[] =
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\t1234567890-=\t\
\tqwertyuiop[]\t\
\tasdfghjkl;'\n\t\
\tzxcvbnm,./\t\t\t\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\t!@#$%^&*()_+\t\
\tQWERTYUIOP{}\t\
\tASDFGHJKL:\"\n\t\
\tZXCVBNM<>?\n\n\t\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
const char dvorakkeymap[] =
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\t1234567890[]\t\
\t',.pyfgcrl/=\t\
\taoeuidhtns-\n\t\
\t;qjkxbmwvz\t\t\t\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
\t!@#$%^&*(){}\t\
\t\"<>PYFGCRL?+\t\
\tAOEUIDHTNS_\n\t\
\t:QJKXBMWVZ\n\n\t\
\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
const char *keymap;
if ( isset( DVORAK ) )
keymap = dvorakkeymap;
else
keymap = qwertykeymap;
if (!strcmp(s, t))
return 0;
/* any number of upper/lower mistakes allowed (dist = 1) */
for (p = s, q = t; *p && tulower(*p) == tulower(*q); p++, q++);
if (!*p && !*q)
return 1;
if (!thresh)
return 200;
for (p = s, q = t; *p && *q; p++, q++)
if (*p == *q)
continue; /* don't consider "aa" transposed, ash */
else if (p[1] == q[0] && q[1] == p[0]) /* transpositions */
return spdist(p + 2, q + 2, thresh - 1) + 1;
else if (p[1] == q[0]) /* missing letter */
return spdist(p + 1, q + 0, thresh - 1) + 2;
else if (p[0] == q[1]) /* missing letter */
return spdist(p + 0, q + 1, thresh - 1) + 2;
else if (*p != *q)
break;
if ((!*p && strlen(q) == 1) || (!*q && strlen(p) == 1))
return 2;
for (p = s, q = t; *p && *q; p++, q++)
if (p[0] != q[0] && p[1] == q[1]) {
int t0;
char *z;
/* mistyped letter */
if (!(z = strchr(keymap, p[0])) || *z == '\n' || *z == '\t')
return spdist(p + 1, q + 1, thresh - 1) + 1;
t0 = z - keymap;
if (*q == keymap[t0 - 15] || *q == keymap[t0 - 14] ||
*q == keymap[t0 - 13] ||
*q == keymap[t0 - 1] || *q == keymap[t0 + 1] ||
*q == keymap[t0 + 13] || *q == keymap[t0 + 14] ||
*q == keymap[t0 + 15])
return spdist(p + 1, q + 1, thresh - 1) + 2;
return 200;
} else if (*p != *q)
break;
return 200;
}
/* set cbreak mode, or the equivalent */
/**/
void
setcbreak(void)
{
struct ttyinfo ti;
ti = shttyinfo;
#ifdef HAS_TIO
ti.tio.c_lflag &= ~ICANON;
ti.tio.c_cc[VMIN] = 1;
ti.tio.c_cc[VTIME] = 0;
#else
ti.sgttyb.sg_flags |= CBREAK;
#endif
settyinfo(&ti);
}
/* give the tty to some process */
/**/
mod_export void
attachtty(pid_t pgrp)
{
static int ep = 0;
if (jobbing && interact) {
#ifdef HAVE_TCSETPGRP
if (SHTTY != -1 && tcsetpgrp(SHTTY, pgrp) == -1 && !ep)
#else
# if ardent
if (SHTTY != -1 && setpgrp() == -1 && !ep)
# else
int arg = pgrp;
if (SHTTY != -1 && ioctl(SHTTY, TIOCSPGRP, &arg) == -1 && !ep)
# endif
#endif
{
if (pgrp != mypgrp && kill(-pgrp, 0) == -1)
attachtty(mypgrp);
else {
if (errno != ENOTTY)
{
zwarn("can't set tty pgrp: %e", errno);
fflush(stderr);
}
opts[MONITOR] = 0;
ep = 1;
}
}
}
}
/* get the process group associated with the tty */
/**/
pid_t
gettygrp(void)
{
pid_t arg;
if (SHTTY == -1)
return -1;
#ifdef HAVE_TCSETPGRP
arg = tcgetpgrp(SHTTY);
#else
ioctl(SHTTY, TIOCGPGRP, &arg);
#endif
return arg;
}
/* Escape tokens and null characters. Buf is the string which should be *
* escaped. len is the length of the string. If len is -1, buf should be *
* null terminated. If len is non-negative and the third parameter is not *
* META_DUP, buf should point to an at least len+1 long memory area. The *
* return value points to the quoted string. If the given string does not *
* contain any special character which should be quoted and the third *
* parameter is not META_(HEAP|)DUP, buf is returned unchanged (a *
* terminating null character is appended to buf if necessary). Otherwise *
* the third `heap' argument determines the method used to allocate space *
* for the result. It can have the following values: *
* META_REALLOC: use zrealloc on buf *
* META_HREALLOC: use hrealloc on buf *
* META_USEHEAP: get memory from the heap. This leaves buf unchanged. *
* META_NOALLOC: buf points to a memory area which is long enough to hold *
* the quoted form, just quote it and return buf. *
* META_STATIC: store the quoted string in a static area. The original *
* string should be at most PATH_MAX long. *
* META_ALLOC: allocate memory for the new string with zalloc(). *
* META_DUP: leave buf unchanged and allocate space for the return *
* value even if buf does not contains special characters *
* META_HEAPDUP: same as META_DUP, but uses the heap */
/**/
mod_export char *
metafy(char *buf, int len, int heap)
{
int meta = 0;
char *t, *p, *e;
static char mbuf[PATH_MAX*2+1];
if (len == -1) {
for (e = buf, len = 0; *e; len++)
if (imeta(*e++))
meta++;
} else
for (e = buf; e < buf + len;)
if (imeta(*e++))
meta++;
if (meta || heap == META_DUP || heap == META_HEAPDUP || *e != '\0') {
switch (heap) {
case META_REALLOC:
buf = zrealloc(buf, len + meta + 1);
break;
case META_HREALLOC:
buf = hrealloc(buf, len, len + meta + 1);
break;
case META_ALLOC:
case META_DUP:
buf = memcpy(zalloc(len + meta + 1), buf, len);
break;
case META_USEHEAP:
case META_HEAPDUP:
buf = memcpy(zhalloc(len + meta + 1), buf, len);
break;
case META_STATIC:
#ifdef DEBUG
if (len > PATH_MAX) {
fprintf(stderr, "BUG: len = %d > PATH_MAX in metafy\n", len);
fflush(stderr);
}
#endif
buf = memcpy(mbuf, buf, len);
break;
#ifdef DEBUG
case META_NOALLOC:
break;
default:
fprintf(stderr, "BUG: metafy called with invalid heap value\n");
fflush(stderr);
break;
#endif
}
p = buf + len;
e = t = buf + len + meta;
while (meta) {
if (imeta(*--t = *--p)) {
*t-- ^= 32;
*t = Meta;
meta--;
}
}
*e = '\0';
}
return buf;
}
/*
* Duplicate a string, metafying it as we go.
*
* Typically, this is used only for strings imported from outside
* zsh, as strings internally are either already metafied or passed
* around with an associated length.
*/
/**/
mod_export char *
ztrdup_metafy(const char *s)
{
/* To mimic ztrdup() behaviour */
if (!s)
return NULL;
/*
* metafy() does lots of different things, so the pointer
* isn't const. Using it with META_DUP should be safe.
*/
return metafy((char *)s, -1, META_DUP);
}
/*
* Take a null-terminated, metafied string in s into a literal
* representation by converting in place. The length is in *len
* len is non-NULL; if len is NULL, you don't know the length of
* the final string, but if it's to be supplied to some system
* routine that always uses NULL termination, such as a filename
* interpreter, that doesn't matter. Note the NULL termination
* is always copied for purposes of that kind.
*/
/**/
mod_export char *
unmetafy(char *s, int *len)
{
char *p, *t;
for (p = s; *p && *p != Meta; p++);
for (t = p; (*t = *p++);)
if (*t++ == Meta)
t[-1] = *p++ ^ 32;
if (len)
*len = t - s;
return s;
}
/* Return the character length of a metafied substring, given the *
* unmetafied substring length. */
/**/
mod_export int
metalen(const char *s, int len)
{
int mlen = len;
while (len--) {
if (*s++ == Meta) {
mlen++;
s++;
}
}
return mlen;
}
/*
* This function converts a zsh internal string to a form which can be
* passed to a system call as a filename. The result is stored in a
* single static area, sized to fit. If there is no Meta character
* the original string is returned.
*/
/**/
mod_export char *
unmeta(const char *file_name)
{
static char *fn;
static int sz;
char *p;
const char *t;
int newsz, meta;
meta = 0;
for (t = file_name; *t; t++) {
if (*t == Meta)
meta = 1;
}
if (!meta) {
/*
* don't need allocation... free if it's long, see below
*/
if (sz > 4 * PATH_MAX) {
zfree(fn, sz);
fn = NULL;
sz = 0;
}
return (char *) file_name;
}
newsz = (t - file_name) + 1;
/*
* Optimisation: don't resize if we don't have to.
* We need a new allocation if
* - nothing was allocated before
* - the new string is larger than the old one
* - the old string was larger than an arbitrary limit but the
* new string isn't so that we free up significant space by resizing.
*/
if (!fn || newsz > sz || (sz > 4 * PATH_MAX && newsz <= 4 * PATH_MAX))
{
if (fn)
zfree(fn, sz);
sz = newsz;
fn = (char *)zalloc(sz);
if (!fn) {
sz = 0;
/*
* will quite likely crash in the caller anyway...
*/
return NULL;
}
}
for (t = file_name, p = fn; *t; p++)
if ((*p = *t++) == Meta)
*p = *t++ ^ 32;
*p = '\0';
return fn;
}
/*
* Unmetafy and compare two strings, comparing unsigned character values.
* "a\0" sorts after "a".
*
* Currently this is only used in hash table sorting, where the
* keys are names of hash nodes and where we don't use strcoll();
* it's not clear if that's right but it does guarantee the ordering
* of shell structures on output.
*