diff options
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/utils.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/utils.c | 7520 |
1 files changed, 0 insertions, 7520 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/utils.c b/dotfiles/system/.zsh/modules/Src/utils.c deleted file mode 100644 index 075d272..0000000 --- a/dotfiles/system/.zsh/modules/Src/utils.c +++ /dev/null @@ -1,7520 +0,0 @@ -/* - * 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. - */ -static struct widechar_array wordchars_wide; - -/* - * The same for the separators (IFS) array. - */ -static 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_charinit(); - while (*mb_array) { - int mblen; - - if (STOUC(*mb_array) <= 0x7f) { - mb_array++; - *wcptr++ = (wchar_t)*mb_array; - continue; - } - - 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 - - The following functions use the following printf-like format codes - (implemented by zerrmsg()): - - Code Argument types Prints - %s const char * C string (null terminated) - %l const char *, int C string of given length (null not required) - %L long decimal value - %d int decimal value - %% (none) literal '%' - %c int character at that codepoint - %e int strerror() message (argument is typically 'errno') - */ - -static void -zwarning(const char *cmd, const char *fmt, va_list ap) -{ - if (isatty(2)) - zleentry(ZLE_CMD_TRASH); - - char *prefix = scriptname ? scriptname : (argzero ? argzero : ""); - - if (cmd) { - if (unset(SHINSTDIN) || locallevel) { - nicezputs(prefix, 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" : prefix, 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 |= ERRFLAG_ERROR; - return; - } - errflag |= ERRFLAG_ERROR; - - VA_START(ap, fmt); - VA_GET_ARG(ap, fmt, const char *); - zwarning(NULL, fmt, ap); - va_end(ap); -} - -/**/ -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; - errflag |= ERRFLAG_ERROR; - - 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); -} - -/**/ -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_u("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_charinit(); - 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 |= ERRFLAG_ERROR; - 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; - /* When adding format codes, update the comment above zwarning(). */ - } - } else { - putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, file); - fmt++; - } - putc('\n', file); - fflush(file); -} - -/* - * Wrapper for setupterm() and del_curterm(). - * These are called from terminfo.c and termcap.c. - */ -static int term_count; /* reference count of cur_term */ - -/**/ -mod_export void -zsetupterm(void) -{ -#ifdef HAVE_SETUPTERM - int errret; - - DPUTS(term_count < 0 || (term_count > 0 && !cur_term), - "inconsistent term_count and/or cur_term"); - /* - * Just because we can't set up the terminal doesn't - * mean the modules hasn't booted---TERM may change, - * and it should be handled dynamically---so ignore errors here. - */ - if (term_count++ == 0) - (void)setupterm((char *)0, 1, &errret); -#endif -} - -/**/ -mod_export void -zdeleteterm(void) -{ -#ifdef HAVE_SETUPTERM - DPUTS(term_count < 1 || !cur_term, - "inconsistent term_count and/or cur_term"); - if (--term_count == 0) - del_curterm(cur_term); -#endif -} - -/* 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; -} - -#ifdef MULTIBYTE_SUPPORT -/* - * 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_sel(int c, int quotable) -{ - static char buf[10]; - char *s = buf; - c &= 0xff; - if (ZISPRINT(c)) - goto done; - if (c & 0x80) { - if (isset(PRINTEIGHTBIT)) - goto done; - *s++ = '\\'; - *s++ = 'M'; - *s++ = '-'; - c &= 0x7f; - if(ZISPRINT(c)) - goto done; - } - if (c == 0x7f) { - if (quotable) { - *s++ = '\\'; - *s++ = 'C'; - *s++ = '-'; - } else - *s++ = '^'; - c = '?'; - } else if (c == '\n') { - *s++ = '\\'; - c = 'n'; - } else if (c == '\t') { - *s++ = '\\'; - c = 't'; - } else if (c < 0x20) { - if (quotable) { - *s++ = '\\'; - *s++ = 'C'; - *s++ = '-'; - } else - *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; -} - -/**/ -mod_export char * -nicechar(int c) -{ - return nicechar_sel(c, 0); -} - -#else /* MULTIBYTE_SUPPORT */ - -/**/ -mod_export char * -nicechar(int c) -{ - static char buf[10]; - char *s = buf; - c &= 0xff; - if (ZISPRINT(c)) - goto done; - if (c & 0x80) { - if (isset(PRINTEIGHTBIT)) - goto done; - *s++ = '\\'; - *s++ = 'M'; - *s++ = '-'; - c &= 0x7f; - if(ZISPRINT(c)) - goto done; - } - if (c == 0x7f) { - *s++ = '\\'; - *s++ = 'C'; - *s++ = '-'; - c = '?'; - } else if (c == '\n') { - *s++ = '\\'; - c = 'n'; - } else if (c == '\t') { - *s++ = '\\'; - c = 't'; - } else if (c < 0x20) { - *s++ = '\\'; - *s++ = 'C'; - *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; -} - -#endif /* MULTIBYTE_SUPPORT */ - -/* - * Return 1 if nicechar() would reformat this character. - */ - -/**/ -mod_export int -is_nicechar(int c) -{ - c &= 0xff; - if (ZISPRINT(c)) - return 0; - if (c & 0x80) - return !isset(PRINTEIGHTBIT); - return (c == 0x7f || c == '\n' || c == '\t' || c < 0x20); -} - -/**/ -#ifdef MULTIBYTE_SUPPORT -static mbstate_t mb_shiftstate; - -/* - * Initialise multibyte state: called before a sequence of - * wcs_nicechar(), mb_metacharlenconv(), or - * mb_charlenconv(). - */ - -/**/ -mod_export void -mb_charinit(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_charinit() before the first call, to - * set up the multibyte shift state for a range of characters. - */ - -/**/ -mod_export char * -wcs_nicechar_sel(wchar_t c, size_t *widthp, char **swidep, int quotable) -{ - 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 (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { - if (c == 0x7f) { - if (quotable) { - *s++ = '\\'; - *s++ = 'C'; - *s++ = '-'; - } else - *s++ = '^'; - c = '?'; - } else if (c == L'\n') { - *s++ = '\\'; - c = 'n'; - } else if (c == L'\t') { - *s++ = '\\'; - c = 't'; - } else if (c < 0x20) { - if (quotable) { - *s++ = '\\'; - *s++ = 'C'; - *s++ = '-'; - } else - *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 = widthp ? buf + *widthp : buf; - 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 char * -wcs_nicechar(wchar_t c, size_t *widthp, char **swidep) -{ - return wcs_nicechar_sel(c, widthp, swidep, 0); -} - -/* - * Return 1 if wcs_nicechar() would reformat this character for display. - */ - -/**/ -mod_export int is_wcs_nicechar(wchar_t c) -{ - if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) { - if (c == 0x7f || c == L'\n' || c == L'\t' || c < 0x20) - return 1; - if (c >= 0x80) { - return (c >= 0x100); - } - } - return 0; -} - -/**/ -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, 0); - s = tricat((pwd[1]) ? pwd : "", "/", s); - t = xsymlink(s, 0); - zsfree(s); - return t; -} - -/* Check whether a string contains the * - * name of the present directory. */ - -/**/ -int -ispwd(char *s) -{ - struct stat sbuf, tbuf; - - /* POSIX: environment PWD must be absolute */ - if (*s != '/') - return 0; - - if (stat((s = unmeta(s)), &sbuf) == 0 && stat(".", &tbuf) == 0) - if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino) { - /* POSIX: No element of $PWD may be "." or ".." */ - while (*s) { - if (s[0] == '.' && - (!s[1] || s[1] == '/' || - (s[1] == '.' && (!s[2] || s[2] == '/')))) - break; - while (*s++ != '/' && *s) - continue; - } - return !*s; - } - return 0; -} - -static char xbuf[PATH_MAX*2+1]; - -/**/ -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 */ - -/**/ -static int -xsymlinks(char *s, int full) -{ - char **pp, **opp; - char xbuf2[PATH_MAX*3+1], xbuf3[PATH_MAX*2+1]; - int t0, ret = 0; - zulong xbuflen = strlen(xbuf), pplen; - - opp = pp = slashsplit(s); - for (; xbuflen < sizeof(xbuf) && *pp && ret >= 0; pp++) { - if (!strcmp(*pp, ".")) - continue; - if (!strcmp(*pp, "..")) { - char *p; - - if (!strcmp(xbuf, "/")) - continue; - if (!*xbuf) - continue; - p = xbuf + xbuflen; - while (*--p != '/') - xbuflen--; - *p = '\0'; - /* The \0 isn't included in the length */ - xbuflen--; - continue; - } - /* Includes null byte. */ - pplen = strlen(*pp) + 1; - if (xbuflen + pplen + 1 > sizeof(xbuf2)) { - *xbuf = 0; - ret = -1; - break; - } - memcpy(xbuf2, xbuf, xbuflen); - xbuf2[xbuflen] = '/'; - memcpy(xbuf2 + xbuflen + 1, *pp, pplen); - t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX); - if (t0 == -1) { - if ((xbuflen += pplen) < sizeof(xbuf)) { - strcat(xbuf, "/"); - strcat(xbuf, *pp); - } else { - *xbuf = 0; - ret = -1; - break; - } - } else { - ret = 1; - metafy(xbuf3, t0, META_NOALLOC); - if (!full) { - /* - * If only one expansion requested, ensure the - * full path is in xbuf. - */ - zulong len = xbuflen; - if (*xbuf3 == '/') - strcpy(xbuf, xbuf3); - else if ((len += strlen(xbuf3) + 1) < sizeof(xbuf)) { - strcpy(xbuf + xbuflen, "/"); - strcpy(xbuf + xbuflen + 1, xbuf3); - } else { - *xbuf = 0; - ret = -1; - break; - } - - while (*++pp) { - zulong newlen = len + strlen(*pp) + 1; - if (newlen < sizeof(xbuf)) { - strcpy(xbuf + len, "/"); - strcpy(xbuf + len + 1, *pp); - len = newlen; - } else { - *xbuf = 01; - ret = -1; - break; - } - } - /* - * No need to update xbuflen, we're finished - * the expansion (for now). - */ - break; - } - if (*xbuf3 == '/') { - strcpy(xbuf, ""); - if (xsymlinks(xbuf3 + 1, 1) < 0) - ret = -1; - else - xbuflen = strlen(xbuf); - } else - if (xsymlinks(xbuf3, 1) < 0) - ret = -1; - else - xbuflen = strlen(xbuf); - } - } - freearray(opp); - return ret; -} - -/* - * expand symlinks in s, and remove other weird things: - * note that this always expands symlinks. - * - * 'heap' indicates whether to malloc() or allocate on the heap. - */ - -/**/ -char * -xsymlink(char *s, int heap) -{ - if (*s != '/') - return NULL; - *xbuf = '\0'; - if (xsymlinks(s + 1, 1) < 0) - zwarn("path expansion failed, using root directory"); - if (!*xbuf) - return heap ? dupstring("/") : ztrdup("/"); - return heap ? dupstring(xbuf) : ztrdup(xbuf); -} - -/**/ -void -print_if_link(char *s, int all) -{ - if (*s == '/') { - *xbuf = '\0'; - if (all) { - char *start = s + 1; - char xbuflink[PATH_MAX+1]; - for (;;) { - if (xsymlinks(start, 0) > 0) { - printf(" -> "); - zputs(*xbuf ? xbuf : "/", stdout); - if (!*xbuf) - break; - strcpy(xbuflink, xbuf); - start = xbuflink + 1; - *xbuf = '\0'; - } else { - break; - } - } - } else { - if (xsymlinks(s + 1, 1) > 0) - 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, QT_BACKSLASH); - return zhtricat("~", d->node.nam, quotestring(s + strlen(d->dir), - 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+1); - 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_ge(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 = metafy(t, -1, META_DUP); - } else - nd->dir = metafy(t, eptr - t, META_DUP); - /* 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, metafy(s, -1, META_DUP), 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, 0) - : ztrdup(pw->pw_dir); - if (dir) { - adduserdir(name, dir, ND_USERNAME, 1); - str = dupstring(dir); - zsfree(dir); - return str; - } else - return dupstring(pw->pw_dir); - } - } -#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. - * - * Returns 0 if at least one function was called (regardless of that function's - * exit status), and 1 otherwise. - */ - -/**/ -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; - time_t currentmailcheck; - LinkNode ln; - zlong period = getiparam("PERIOD"); - zlong mailcheck = getiparam("MAILCHECK"); - - /* - * Handle any pending window size changes before we compute prompts, - * then block them again to avoid interrupts during prompt display. - */ - winch_unblock(); - winch_block(); - - 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 && ((zlong)time(NULL) > (zlong)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 */ - currentmailcheck = time(NULL); - if (mailcheck && - (zlong) difftime(currentmailcheck, 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 = currentmailcheck; - } - - 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 + 1], **arr, **ap; - int buflen, ct = 1; - - if (lock) { - char *fn; - - pushheap(); - l = newlinklist(); - while ((fn = zreaddir(lock, 1)) && !errflag) { - if (u) - buflen = snprintf(buf, sizeof(buf), "%s/%s?%s", *s, fn, u); - else - buflen = snprintf(buf, sizeof(buf), "%s/%s", *s, fn); - if (buflen < 0 || buflen >= (int)sizeof(buf)) - continue; - 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 && zterm_lines < 0, - "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 && zterm_columns < 0, - "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; -} - -/* - * Add an fd opened ithin a module. - * - * fdt is the type of the fd; see the FDT_ definitions in zsh.h. - * The most likely falures are: - * - * FDT_EXTERNAL: the fd can be used within the shell for normal I/O but - * it will not be closed automatically or by normal shell syntax. - * - * FDT_MODULE: as FDT_EXTERNAL, but it can only be closed by the module - * (which should included zclose() as part of the sequence), not by - * the standard shell syntax for closing file descriptors. - * - * FDT_INTERNAL: fd is treated like others created by the shell for - * internal use; it can be closed and will be closed by the shell if it - * exec's or performs an exec with a fork optimised out. - * - * Safe if fd is -1 to indicate failure. - */ -/**/ -mod_export void -addmodulefd(int fd, int fdt) -{ - if (fd >= 0) { - check_fd_table(fd); - fdtable[fd] = fdt; - } -} - -/**/ - -/* - * 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. - * The string passed as "prefix" is expected to be metafied. */ - -/**/ -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; -} - -/* The gettempfile() "prefix" is expected to be metafied, see hist.c - * and gettempname(). */ - -/**/ -mod_export int -gettempfile(const char *prefix, int use_heap, char **tempname) -{ - char *fn; - int fd; - mode_t old_umask; -#if HAVE_MKSTEMP - char *suffix = prefix ? ".XXXXXX" : "XXXXXX"; - - queue_signals(); - old_umask = umask(0177); - 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; - - queue_signals(); - old_umask = umask(0177); - 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; - - umask(old_umask); - unqueue_signals(); - 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++)) - u++; - *s = u; - if (n > 0) /* just one null-byte will do, unlike strncpy(3) */ - *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; -} - -/* Return TRUE iff arrlen(s) >= lower_bound, but more efficiently. */ - -/**/ -mod_export char -arrlen_ge(char **s, unsigned lower_bound) -{ - while (lower_bound--) - if (!*s++) - return 0 /* FALSE */; - - return 1 /* TRUE */; -} - -/* Return TRUE iff arrlen(s) > lower_bound, but more efficiently. */ - -/**/ -mod_export char -arrlen_gt(char **s, unsigned lower_bound) -{ - return arrlen_ge(s, 1+lower_bound); -} - -/* Return TRUE iff arrlen(s) <= upper_bound, but more efficiently. */ - -/**/ -mod_export char -arrlen_le(char **s, unsigned upper_bound) -{ - return arrlen_lt(s, 1+upper_bound); -} - -/* Return TRUE iff arrlen(s) < upper_bound, but more efficiently. */ - -/**/ -mod_export char -arrlen_lt(char **s, unsigned upper_bound) -{ - return !arrlen_ge(s, upper_bound); -} - -/* 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; -} - -/**/ -mod_export zlong -zstrtol(const char *s, char **t, int base) -{ - return zstrtol_underscore(s, t, base, 0); -} - -/* 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_underscore(const char *s, char **t, int base, int underscore) -{ - const char *inp, *trunc = NULL; - zulong calc = 0, newcalc = 0; - int neg; - - while (inblank(*s)) - s++; - - if ((neg = IS_DASH(*s))) - s++; - else if (*s == '+') - s++; - - if (!base) { - if (*s != '0') - base = 10; - else if (*++s == 'x' || *s == 'X') - base = 16, s++; - else if (*s == 'b' || *s == 'B') - base = 2, 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)) || - (underscore && *s == '_'); s++) { - if (trunc || *s == '_') - 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)) - || (underscore && *s == '_'); s++) { - if (trunc || *s == '_') - 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; -} - -/* - * If s represents a complete unsigned integer (and nothing else) - * return 1 and set retval to the value. Otherwise return 0. - * - * Underscores are always allowed. - * - * Sensitive to OCTAL_ZEROES. - */ - -/**/ -mod_export int -zstrtoul_underscore(const char *s, zulong *retval) -{ - zulong calc = 0, newcalc = 0, base; - - if (*s == '+') - s++; - - if (*s != '0') - base = 10; - else if (*++s == 'x' || *s == 'X') - base = 16, s++; - else if (*s == 'b' || *s == 'B') - base = 2, s++; - else - base = isset(OCTALZEROES) ? 8 : 10; - if (base <= 10) { - for (; (*s >= '0' && *s < ('0' + base)) || - *s == '_'; s++) { - if (*s == '_') - continue; - newcalc = calc * base + *s - '0'; - if (newcalc < calc) - { - return 0; - } - calc = newcalc; - } - } else { - for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10)) - || (*s >= 'A' && *s < ('A' + base - 10)) - || *s == '_'; s++) { - if (*s == '_') - continue; - newcalc = calc*base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9); - if (newcalc < calc) - { - return 0; - } - calc = newcalc; - } - } - - if (*s) - return 0; - *retval = calc; - return 1; -} - -/**/ -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. - * - * fd may be -1 to sleep until the timeout in microseconds. This is a - * fallback for old systems that don't have nanosleep(). Some very old - * systems might not have select: get with it, daddy-o. - */ - -/**/ -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 (fd < 0 || (polltty && !isatty(fd))) - polltty = 0; /* no tty to poll */ - -#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 && fd >= 0) { - 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); - if (fd > -1) { - FD_SET(fd, &foofd); - ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv); - } else - ret = select(0, NULL, NULL, NULL, &expire_tv); -#else - if (fd < 0) { - /* OK, can't do that. Just quietly sleep for a second. */ - sleep(1); - return 1; - } -#ifdef FIONREAD - if (ioctl(fd, FIONREAD, (char *) &val) == 0) - ret = (val > 0); -#endif -#endif - - if (fd >= 0 && ret < 0 && !errflag) { - /* - * 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); -} - -/* - * Sleep for the given number of microseconds --- must be within - * range of a long at the moment, but this is only used for - * limited internal purposes. - */ - -/**/ -int -zsleep(long us) -{ -#ifdef HAVE_NANOSLEEP - struct timespec sleeptime; - - sleeptime.tv_sec = (time_t)us / (time_t)1000000; - sleeptime.tv_nsec = (us % 1000000L) * 1000L; - for (;;) { - struct timespec rem; - int ret = nanosleep(&sleeptime, &rem); - - if (ret == 0) - return 1; - else if (errno != EINTR) - return 0; - sleeptime = rem; - } -#else - int dummy; - return read_poll(-1, &dummy, 0, us); -#endif -} - -/** - * Sleep for time (fairly) randomly up to max_us microseconds. - * Don't let the wallclock time extend beyond end_time. - * Return 1 if that seemed to work, else 0. - * - * For best results max_us should be a multiple of 2**16 or large - * enough that it doesn't matter. - */ - -/**/ -int -zsleep_random(long max_us, time_t end_time) -{ - long r; - time_t now = time(NULL); - - /* - * Randomish backoff. Doesn't need to be fundamentally - * unpredictable, just probably unlike the value another - * exiting shell is using. On some systems the bottom 16 - * bits aren't that random but the use here doesn't - * really care. - */ - r = (long)(rand() & 0xFFFF); - /* - * Turn this into a fraction of sleep_us. Again, this - * doesn't need to be particularly accurate and the base time - * is sufficient that we can do the division first and not - * worry about the range. - */ - r = (max_us >> 16) * r; - /* - * Don't sleep beyond timeout. - * Not that important as timeout is ridiculously long, but - * if there's an interface, interface to it... - */ - while (r && now + (time_t)(r / 1000000) > end_time) - r >>= 1; - if (r) /* pedantry */ - return zsleep(r); - return 0; -} - -/**/ -int -checkrmall(char *s) -{ - DIR *rmd; - int count = 0; - if (!shout) - return 1; - if (*s != '/') { - if (pwd[1]) - s = zhtricat(pwd, "/", s); - else - s = dyncat("/", s); - } - const int max_count = 100; - if ((rmd = opendir(unmeta(s)))) { - int ignoredots = !isset(GLOBDOTS); - char *fname; - - while ((fname = zreaddir(rmd, 1))) { - if (ignoredots && *fname == '.') - continue; - count++; - if (count > max_count) - break; - } - closedir(rmd); - } - if (count > max_count) - fprintf(shout, "zsh: sure you want to delete more than %d files in ", - max_count); - else if (count == 1) - fprintf(shout, "zsh: sure you want to delete the only file in "); - else if (count > 0) - fprintf(shout, "zsh: sure you want to delete all %d files in ", - count); - else { - /* We don't know how many files the glob will expand to; see 41707. */ - fprintf(shout, "zsh: sure you want to delete all the files in "); - } - nicezputs(s, shout); - if(isset(RMSTARWAIT)) { - fputs("? (waiting ten seconds)", shout); - fflush(shout); - zbeep(); - sleep(10); - fputc('\n', shout); - } - if (errflag) - return 0; - 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; - int q = queue_signal_level(); - - dont_queue_signals(); - while (read(SHTTY, &c, 1) != 1) { - if (errno != EINTR || errflag || retflag || breaks || contflag) { - restore_queue_signals(q); - return -1; - } - } - restore_queue_signals(q); - 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, spnamepat; - -/**/ -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; - char ic = '\0'; - 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 ((correct_ignore = getsparam("CORRECT_IGNORE_FILE")) != NULL) { - tokenize(correct_ignore = dupstring(correct_ignore)); - remnulargs(correct_ignore); - spnamepat = patcompile(correct_ignore, 0, NULL); - } else - spnamepat = 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) { - int ne; - 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, 1)) < d) { - best = dupstring(bestcd); - d = thisdist; - } - } - } - } - } - if (errflag) - return; - if (best && (int)strlen(best) > 1 && strcmp(best, guess)) { - int x; - 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 *) zhalloc(t - *s + strlen(best + preflen) + 1); - strncpy(u, *s, t - *s); - strcpy(u + (t - *s), best + preflen); - } else { - u = (char *) zhalloc(strlen(best) + 2); - *u = '\0'; - 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", 0); - if (cmd && x == 'n') - pathchecked = path; - } else - x = 'n'; - } else - x = 'y'; - if (x == 'y') { - *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. - * - * fmt is metafied, but we need to unmetafy it on the fly to - * pass into strftime / combine with the output from strftime. - * The return value in buf is not metafied. - */ - -/**/ -mod_export int -ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm, long nsec) -{ - int hr12; -#ifdef HAVE_STRFTIME - int decr; - char *fmtstart; -#else - 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; - - - while (*fmt) { - if (*fmt == Meta) { - int chr = fmt[1] ^ 32; - if (ztrftimebuf(&bufsize, 1)) - return -1; - *buf++ = chr; - fmt += 2; - } else if (*fmt == '%') { - int strip; - int digs = 3; - -#ifdef HAVE_STRFTIME - fmtstart = -#endif - fmt++; - - if (*fmt == '-') { - strip = 1; - fmt++; - } else - strip = 0; - if (idigit(*fmt)) { - /* Digit --- only useful with . */ - char *dstart = fmt; - char *dend = fmt+1; - while (idigit(*dend)) - dend++; - if (*dend == '.') { - fmt = dend; - digs = atoi(dstart); - } - } - /* - * 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; -#ifdef HAVE_STRFTIME - /* Our internal handling doesn't handle padding and other gnu extensions, - * so here we detect them and pass over to strftime(). We don't want - * to do this unconditionally though, as we have some extensions that - * strftime() doesn't have (%., %f, %L and %K) */ -morefmt: - if (!((fmt - fmtstart == 1) || (fmt - fmtstart == 2 && strip) || *fmt == '.')) { - while (*fmt && strchr("OE^#_-0123456789", *fmt)) - fmt++; - if (*fmt) { - fmt++; - goto strftimehandling; - } - } -#endif - switch (*fmt++) { - case '.': - if (ztrftimebuf(&bufsize, digs)) - return -1; - if (digs > 9) - digs = 9; - if (digs < 9) { - int trunc; - for (trunc = 8 - digs; trunc; trunc--) - nsec /= 10; - nsec = (nsec + 8) / 10; - } - sprintf(buf, "%0*ld", digs, nsec); - buf += digs; - break; - case '\0': - /* Guard against premature end of string */ - *buf++ = '%'; - fmt--; - 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 'd': - if (tm->tm_mday > 9 || !strip) - *buf++ = '0' + tm->tm_mday / 10; - *buf++ = '0' + tm->tm_mday % 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 'N': - if (ztrftimebuf(&bufsize, 9)) - return -1; - sprintf(buf, "%09ld", nsec); - buf += 9; - 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; -#ifndef HAVE_STRFTIME - case 'Y': - { - 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 - case 'E': - case 'O': - case '^': - case '#': - case '_': - case '-': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - goto morefmt; -strftimehandling: - default: - /* - * Remember we've already allowed for two characters - * in the accounting in bufsize (but nowhere else). - */ - { - char origchar = fmt[-1]; - int size = fmt - fmtstart; - char *tmp, *last; - tmp = zhalloc(size + 1); - strncpy(tmp, fmtstart, size); - last = fmt-1; - if (*last == Meta) { - /* - * This is for consistency in counting: - * a metafiable character isn't actually - * a valid strftime descriptor. - * - * Previous characters were explicitly checked, - * so can't be metafied. - */ - *last = *++fmt ^ 32; - } - tmp[size] = '\0'; - *buf = '\1'; - if (!strftime(buf, bufsize + 2, tmp, tm)) - { - /* - * Some locales don't have strings for - * AM/PM, so empty output is valid. - */ - if (*buf || (origchar != 'p' && origchar != 'P')) { - 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 = (char *) (heap ? zhalloc(len) : zalloc(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. */ - -/**/ -mod_export 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); - - /* ### TODO: s/calloc/alloc/ */ - ptr = ret = (char **) (heap ? hcalloc(l) : 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 = (char *) (heap ? zhalloc((s - t) + 1) : - zalloc((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 || mul) && (sl || *(s + sl))) - 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 = (char *) (heap ? zhalloc(l) : zalloc(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 = (char **) (heap ? zhalloc((n + 1) * sizeof(char *)) : - zalloc((n + 1) * sizeof(char *))); - - for (t = s; n--;) { - tt = t; - (void)findsep(&t, sep, 0); - *p = (char *) (heap ? zhalloc(t - tt + 1) : - zalloc(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 char ** -hmkarray(char *s) -{ - char **t = (char **) zhalloc((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_u("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; -} - - -/* the ztypes table */ - -/**/ -mod_export short int typtab[256]; -static int typtab_flags = 0; - -/* initialize the ztypes table */ - -/**/ -void -inittyptab(void) -{ - int t0; - char *s; - - if (!(typtab_flags & ZTF_INIT)) { - typtab_flags = ZTF_INIT; - if (interact && isset(SHINSTDIN)) - typtab_flags |= ZTF_INTERACT; - } - - queue_signals(); - - memset(typtab, 0, sizeof(typtab)); - 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['.'] = typtab[STOUC(Dash)] = 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(LAST_NORMAL_TOK); 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) ? - DEFAULT_IFS_SH : 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) ? - DEFAULT_IFS_SH : DEFAULT_IFS, &ifs_wide); -#endif - for (s = SPECCHARS; *s; s++) - typtab[STOUC(*s)] |= ISPECIAL; - if (typtab_flags & ZTF_SP_COMMA) - typtab[STOUC(',')] |= ISPECIAL; - if (isset(BANGHIST) && bangchar && (typtab_flags & ZTF_INTERACT)) { - typtab_flags |= ZTF_BANGCHAR; - typtab[bangchar] |= ISPECIAL; - } else - typtab_flags &= ~ZTF_BANGCHAR; - for (s = PATCHARS; *s; s++) - typtab[STOUC(*s)] |= IPATTERN; - - unqueue_signals(); -} - -/**/ -mod_export void -makecommaspecial(int yesno) -{ - if (yesno != 0) { - typtab_flags |= ZTF_SP_COMMA; - typtab[STOUC(',')] |= ISPECIAL; - } else { - typtab_flags &= ~ZTF_SP_COMMA; - typtab[STOUC(',')] &= ~ISPECIAL; - } -} - -/**/ -mod_export void -makebangspecial(int yesno) -{ - /* Name and call signature for congruence with makecommaspecial(), - * but in this case when yesno is nonzero we defer to the state - * saved by inittyptab(). - */ - if (yesno == 0) { - typtab[bangchar] &= ~ISPECIAL; - } else if (typtab_flags & ZTF_BANGCHAR) { - typtab[bangchar] |= 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_charinit(); - while (*ptr) { - int len; - if (itok(*ptr)) { - /* Not untokenised yet --- can happen in raw command line */ - len = 1; - if (!zistype(*ptr,itype)) - break; - } else { - wint_t wc; - 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; -} - -/* Duplicate at most max elements of the array s with heap memory */ - -/**/ -mod_export char ** -arrdup_max(char **s, unsigned max) -{ - char **x, **y, **send; - int len = 0; - - if (max) - len = arrlen(s); - - /* Limit has sense only if not equal to len */ - if (max > len) - max = len; - - y = x = (char **) zhalloc(sizeof(char *) * (max + 1)); - - send = s + max; - while (s < send) - *x++ = dupstring(*s++); - *x = NULL; - - 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; - thisdist = mindist(newname, spnameguess, spnamebest, *old == '/'); - if (thisdist >= 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) { - struncpy(&new, spnameguess, sizeof(newname) - (new - newname)); - struncpy(&new, old, sizeof(newname) - (new - newname)); - return (new >= newname + sizeof(newname) -1) ? NULL : newname; - } else - return NULL; - } else { - maxthresh = bestdist + thresh; - bestdist += thisdist; - } - for (p = spnamebest; (*new = *p++);) { - if (new >= newname + sizeof(newname) - 1) - return NULL; - new++; - } - } -} - -/**/ -static int -mindist(char *dir, char *mindistguess, char *mindistbest, int wantdir) -{ - int mindistd, nd; - DIR *dd; - char *fn; - char *buf; - struct stat st; - size_t dirlen; - - if (dir[0] == '\0') - dir = "."; - mindistd = 100; - - if (!(buf = zalloc((dirlen = strlen(dir)) + strlen(mindistguess) + 2))) - return 0; - sprintf(buf, "%s/%s", dir, mindistguess); - - if (stat(unmeta(buf), &st) == 0 && (!wantdir || S_ISDIR(st.st_mode))) { - strcpy(mindistbest, mindistguess); - free(buf); - return 0; - } - - if ((dd = opendir(unmeta(dir)))) { - while ((fn = zreaddir(dd, 0))) { - if (spnamepat && pattry(spnamepat, fn)) - continue; - nd = spdist(fn, mindistguess, - (int)strlen(mindistguess) / 4 + 1); - if (nd <= mindistd) { - if (wantdir) { - if (!(buf = zrealloc(buf, dirlen + strlen(fn) + 2))) - continue; - sprintf(buf, "%s/%s", dir, fn); - if (stat(unmeta(buf), &st) != 0 || !S_ISDIR(st.st_mode)) - continue; - } - strcpy(mindistbest, fn); - mindistd = nd; - if (mindistd == 0) - break; - } - } - closedir(dd); - } - free(buf); - 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) { - 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 && *p) - 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; - - if (!file_name) - return NULL; - - 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 && *t) - *p = *t++ ^ 32; - *p = '\0'; - return fn; -} - -/* - * Unmetafy just one character and store the number of bytes it occupied. - */ -/**/ -mod_export convchar_t -unmeta_one(const char *in, int *sz) -{ - convchar_t wc; - int newsz; -#ifdef MULTIBYTE_SUPPORT - mbstate_t wstate; -#endif - - if (!sz) - sz = &newsz; - *sz = 0; - - if (!in || !*in) - return 0; - -#ifdef MULTIBYTE_SUPPORT - memset(&wstate, 0, sizeof(wstate)); - *sz = mb_metacharlenconv_r(in, &wc, &wstate); -#else - if (in[0] == Meta) { - *sz = 2; - wc = STOUC(in[1] ^ 32); - } else { - *sz = 1; - wc = STOUC(in[0]); - } -#endif - return wc; -} - -/* - * 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. - * - * As we don't use strcoll(), it seems overkill to convert multibyte - * characters to wide characters for comparison every time. In the case - * of UTF-8, Unicode ordering is preserved when sorted raw, and for - * other character sets we rely on an extension of ASCII so the result, - * while it may not be correct, is at least rational. - */ - -/**/ -int -ztrcmp(char const *s1, char const *s2) -{ - int c1, c2; - - while(*s1 && *s1 == *s2) { - s1++; - s2++; - } - - if(!(c1 = *s1)) - c1 = -1; - else if(c1 == STOUC(Meta)) - c1 = *++s1 ^ 32; - if(!(c2 = *s2)) - c2 = -1; - else if(c2 == STOUC(Meta)) - c2 = *++s2 ^ 32; - - if(c1 == c2) - return 0; - else if(c1 < c2) - return -1; - else - return 1; -} - -/* Return the unmetafied length of a metafied string. */ - -/**/ -mod_export int -ztrlen(char const *s) -{ - int l; - - for (l = 0; *s; l++) { - if (*s++ == Meta) { -#ifdef DEBUG - if (! *s) { - fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n"); - break; - } else -#endif - s++; - } - } - return l; -} - -#ifndef MULTIBYTE_SUPPORT -/* - * ztrlen() but with explicit end point for non-null-terminated - * segments. eptr may not be NULL. - */ - -/**/ -mod_export int -ztrlenend(char const *s, char const *eptr) -{ - int l; - - for (l = 0; s < eptr; l++) { - if (*s++ == Meta) { -#ifdef DEBUG - if (! *s) { - fprintf(stderr, - "BUG: unexpected end of string in ztrlenend()\n"); - break; - } else -#endif - s++; - } - } - return l; -} - -#endif /* MULTIBYTE_SUPPORT */ - -/* Subtract two pointers in a metafied string. */ - -/**/ -mod_export int -ztrsub(char const *t, char const *s) -{ - int l = t - s; - - while (s != t) { - if (*s++ == Meta) { -#ifdef DEBUG - if (! *s || s == t) - fprintf(stderr, "BUG: substring ends in the middle of a metachar in ztrsub()\n"); - else -#endif - s++; - l--; - } - } - return l; -} - -/* - * Wrapper for readdir(). - * - * If ignoredots is true, skip the "." and ".." entries. - * - * When __APPLE__ is defined, recode dirent names from UTF-8-MAC to UTF-8. - * - * Return the dirent's name, metafied. - */ - -/**/ -mod_export char * -zreaddir(DIR *dir, int ignoredots) -{ - struct dirent *de; -#if defined(HAVE_ICONV) && defined(__APPLE__) - static iconv_t conv_ds = (iconv_t)0; - static char *conv_name = 0; - char *conv_name_ptr, *orig_name_ptr; - size_t conv_name_len, orig_name_len; -#endif - - do { - de = readdir(dir); - if(!de) - return NULL; - } while(ignoredots && de->d_name[0] == '.' && - (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))); - -#if defined(HAVE_ICONV) && defined(__APPLE__) - if (!conv_ds) - conv_ds = iconv_open("UTF-8", "UTF-8-MAC"); - if (conv_ds != (iconv_t)(-1)) { - /* Force initial state in case re-using conv_ds */ - (void) iconv(conv_ds, 0, &orig_name_len, 0, &conv_name_len); - - orig_name_ptr = de->d_name; - orig_name_len = strlen(de->d_name); - conv_name = zrealloc(conv_name, orig_name_len+1); - conv_name_ptr = conv_name; - conv_name_len = orig_name_len; - if (iconv(conv_ds, - &orig_name_ptr, &orig_name_len, - &conv_name_ptr, &conv_name_len) != (size_t)(-1) && - orig_name_len == 0) { - /* Completely converted, metafy and return */ - *conv_name_ptr = '\0'; - return metafy(conv_name, -1, META_STATIC); - } - /* Error, or conversion incomplete, keep the original name */ - } -#endif - - return metafy(de->d_name, -1, META_STATIC); -} - -/* Unmetafy and output a string. Tokens are skipped. */ - -/**/ -mod_export int -zputs(char const *s, FILE *stream) -{ - int c; - - while (*s) { - if (*s == Meta) - c = *++s ^ 32; - else if(itok(*s)) { - s++; - continue; - } else - c = *s; - s++; - if (fputc(c, stream) < 0) - return EOF; - } - return 0; -} - -#ifndef MULTIBYTE_SUPPORT -/* Create a visibly-represented duplicate of a string. */ - -/**/ -mod_export char * -nicedup(char const *s, int heap) -{ - int c, len = strlen(s) * 5 + 1; - VARARR(char, buf, len); - char *p = buf, *n; - - while ((c = *s++)) { - if (itok(c)) { - if (c <= Comma) - c = ztokens[c - Pound]; - else - continue; - } - if (c == Meta) - c = *s++ ^ 32; - /* The result here is metafied */ - n = nicechar(c); - while(*n) - *p++ = *n++; - } - *p = '\0'; - return heap ? dupstring(buf) : ztrdup(buf); -} -#endif - -/**/ -mod_export char * -nicedupstring(char const *s) -{ - return nicedup(s, 1); -} - - -#ifndef MULTIBYTE_SUPPORT -/* Unmetafy and output a string, displaying special characters readably. */ - -/**/ -mod_export int -nicezputs(char const *s, FILE *stream) -{ - int c; - - while ((c = *s++)) { - if (itok(c)) { - if (c <= Comma) - c = ztokens[c - Pound]; - else - continue; - } - if (c == Meta) - c = *s++ ^ 32; - if(zputs(nicechar(c), stream) < 0) - return EOF; - } - return 0; -} - - -/* Return the length of the visible representation of a metafied string. */ - -/**/ -mod_export size_t -niceztrlen(char const *s) -{ - size_t l = 0; - int c; - - while ((c = *s++)) { - if (itok(c)) { - if (c <= Comma) - c = ztokens[c - Pound]; - else - continue; - } - if (c == Meta) - c = *s++ ^ 32; - l += strlen(nicechar(c)); - } - return l; -} -#endif - - -/**/ -#ifdef MULTIBYTE_SUPPORT -/* - * Version of both nicezputs() and niceztrlen() for use with multibyte - * characters. Input is a metafied string; output is the screen width of - * the string. - * - * If the FILE * is not NULL, output to that, too. - * - * If outstrp is not NULL, set *outstrp to a zalloc'd version of - * the output (still metafied). - * - * If flags contains NICEFLAG_HEAP, use the heap for *outstrp, else - * zalloc. - * If flags contsins NICEFLAG_QUOTE, the output is going to be within - * $'...', so quote "'" and "\" with a backslash. - */ - -/**/ -mod_export size_t -mb_niceformat(const char *s, FILE *stream, char **outstrp, int flags) -{ - size_t l = 0, newl; - int umlen, outalloc, outleft, eol = 0; - wchar_t c; - char *ums, *ptr, *fmt, *outstr, *outptr; - mbstate_t mbs; - - if (outstrp) { - outleft = outalloc = 5 * strlen(s); - outptr = outstr = zalloc(outalloc); - } else { - outleft = outalloc = 0; - outptr = outstr = NULL; - } - - ums = ztrdup(s); - /* - * is this necessary at this point? niceztrlen does this - * but it's used in lots of places. however, one day this may - * be, too. - */ - untokenize(ums); - ptr = unmetafy(ums, ¨en); - - memset(&mbs, 0, sizeof mbs); - while (umlen > 0) { - size_t cnt = eol ? MB_INVALID : mbrtowc(&c, ptr, umlen, &mbs); - - switch (cnt) { - case MB_INCOMPLETE: - eol = 1; - /* FALL THROUGH */ - case MB_INVALID: - /* The byte didn't convert, so output it as a \M-... sequence. */ - fmt = nicechar_sel(*ptr, flags & NICEFLAG_QUOTE); - newl = strlen(fmt); - cnt = 1; - /* Get mbs out of its undefined state. */ - memset(&mbs, 0, sizeof mbs); - break; - case 0: - /* Careful: converting '\0' returns 0, but a '\0' is a - * real character for us, so we should consume 1 byte. */ - cnt = 1; - /* FALL THROUGH */ - default: - if (c == L'\'' && (flags & NICEFLAG_QUOTE)) { - fmt = "\\'"; - newl = 2; - } - else if (c == L'\\' && (flags & NICEFLAG_QUOTE)) { - fmt = "\\\\"; - newl = 2; - } - else - fmt = wcs_nicechar_sel(c, &newl, NULL, flags & NICEFLAG_QUOTE); - break; - } - - umlen -= cnt; - ptr += cnt; - l += newl; - - if (stream) - zputs(fmt, stream); - if (outstr) { - /* Append to output string */ - int outlen = strlen(fmt); - if (outlen >= outleft) { - /* Reallocate to twice the length */ - int outoffset = outptr - outstr; - - outleft += outalloc; - outalloc *= 2; - outstr = zrealloc(outstr, outalloc); - outptr = outstr + outoffset; - } - memcpy(outptr, fmt, outlen); - /* Update start position */ - outptr += outlen; - /* Update available bytes */ - outleft -= outlen; - } - } - - free(ums); - if (outstrp) { - *outptr = '\0'; - /* Use more efficient storage for returned string */ - if (flags & NICEFLAG_NODUP) - *outstrp = outstr; - else { - *outstrp = (flags & NICEFLAG_HEAP) ? dupstring(outstr) : - ztrdup(outstr); - free(outstr); - } - } - - return l; -} - -/* - * Return 1 if mb_niceformat() would reformat this string, else 0. - */ - -/**/ -mod_export int -is_mb_niceformat(const char *s) -{ - int umlen, eol = 0, ret = 0; - wchar_t c; - char *ums, *ptr; - mbstate_t mbs; - - ums = ztrdup(s); - untokenize(ums); - ptr = unmetafy(ums, ¨en); - - memset(&mbs, 0, sizeof mbs); - while (umlen > 0) { - size_t cnt = eol ? MB_INVALID : mbrtowc(&c, ptr, umlen, &mbs); - - switch (cnt) { - case MB_INCOMPLETE: - eol = 1; - /* FALL THROUGH */ - case MB_INVALID: - /* The byte didn't convert, so output it as a \M-... sequence. */ - if (is_nicechar(*ptr)) { - ret = 1; - break; - } - cnt = 1; - /* Get mbs out of its undefined state. */ - memset(&mbs, 0, sizeof mbs); - break; - case 0: - /* Careful: converting '\0' returns 0, but a '\0' is a - * real character for us, so we should consume 1 byte. */ - cnt = 1; - /* FALL THROUGH */ - default: - if (is_wcs_nicechar(c)) - ret = 1; - break; - } - - if (ret) - break; - - umlen -= cnt; - ptr += cnt; - } - - free(ums); - - return ret; -} - -/* ztrdup multibyte string with nice formatting */ - -/**/ -mod_export char * -nicedup(const char *s, int heap) -{ - char *retstr; - - (void)mb_niceformat(s, NULL, &retstr, heap ? NICEFLAG_HEAP : 0); - - return retstr; -} - - -/* - * The guts of mb_metacharlenconv(). This version assumes we are - * processing a true multibyte character string without tokens, and - * takes the shift state as an argument. - */ - -/**/ -mod_export int -mb_metacharlenconv_r(const char *s, wint_t *wcp, mbstate_t *mbsp) -{ - size_t ret = MB_INVALID; - char inchar; - const char *ptr; - wchar_t wc; - - if (STOUC(*s) <= 0x7f) { - if (wcp) - *wcp = (wint_t)*s; - return 1; - } - - for (ptr = s; *ptr; ) { - if (*ptr == Meta) { - inchar = *++ptr ^ 32; - DPUTS(!*ptr, - "BUG: unexpected end of string in mb_metacharlen()\n"); - } else if (imeta(*ptr)) { - /* - * As this is metafied input, this is a token --- this - * can't be a part of the string. It might be - * something on the end of an unbracketed parameter - * reference, for example. - */ - break; - } else - inchar = *ptr; - ptr++; - ret = mbrtowc(&wc, &inchar, 1, mbsp); - - if (ret == MB_INVALID) - break; - if (ret == MB_INCOMPLETE) - continue; - if (wcp) - *wcp = wc; - return ptr - s; - } - - if (wcp) - *wcp = WEOF; - /* No valid multibyte sequence */ - memset(mbsp, 0, sizeof(*mbsp)); - if (ptr > s) { - return 1 + (*s == Meta); /* Treat as single byte character */ - } else - return 0; /* Probably shouldn't happen */ -} - -/* - * Length of metafied string s which contains the next multibyte - * character; single (possibly metafied) character if string is not null - * but character is not valid (e.g. possibly incomplete at end of string). - * Returned value is guaranteed not to reach beyond the end of the - * string (assuming correct metafication). - * - * If wcp is not NULL, the converted wide character is stored there. - * If no conversion could be done WEOF is used. - */ - -/**/ -mod_export int -mb_metacharlenconv(const char *s, wint_t *wcp) -{ - if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) { - /* treat as single byte, possibly metafied */ - if (wcp) - *wcp = (wint_t)(*s == Meta ? s[1] ^ 32 : *s); - return 1 + (*s == Meta); - } - /* - * We have to handle tokens here, since we may be looking - * through a tokenized input. Obviously this isn't - * a valid multibyte character, so just return WEOF - * and let the caller handle it as a single character. - * - * TODO: I've a sneaking suspicion we could do more here - * to prevent the caller always needing to handle invalid - * characters specially, but sometimes it may need to know. - */ - if (itok(*s)) { - if (wcp) - *wcp = WEOF; - return 1; - } - - return mb_metacharlenconv_r(s, wcp, &mb_shiftstate); -} - -/* - * Total number of multibyte characters in metafied string s. - * Same answer as iterating mb_metacharlen() and counting calls - * until end of string. - * - * If width is 1, return total character width rather than number. - * If width is greater than 1, return 1 if character has non-zero width, - * else 0. - * - * Ends if either *ptr is '\0', the normal case (eptr may be NULL for - * this), or ptr is eptr (i.e. *eptr is where the null would be if null - * terminated) for strings not delimited by nulls --- note these are - * still metafied. - */ - -/**/ -mod_export int -mb_metastrlenend(char *ptr, int width, char *eptr) -{ - char inchar, *laststart; - size_t ret; - wchar_t wc; - int num, num_in_char, complete; - - if (!isset(MULTIBYTE) || MB_CUR_MAX == 1) - return eptr ? (int)(eptr - ptr) : ztrlen(ptr); - - laststart = ptr; - ret = MB_INVALID; - num = num_in_char = 0; - complete = 1; - - memset(&mb_shiftstate, 0, sizeof(mb_shiftstate)); - while (*ptr && !(eptr && ptr >= eptr)) { - if (*ptr == Meta) - inchar = *++ptr ^ 32; - else - inchar = *ptr; - ptr++; - - if (complete && STOUC(inchar) <= STOUC(0x7f)) { - /* - * We rely on 7-bit US-ASCII as a subset, so skip - * multibyte handling if we have such a character. - */ - num++; - laststart = ptr; - num_in_char = 0; - continue; - } - - ret = mbrtowc(&wc, &inchar, 1, &mb_shiftstate); - - if (ret == MB_INCOMPLETE) { - /* - * "num_in_char" is only used for incomplete characters. - * The assumption is that we will output all trailing octets - * that form part of an incomplete character as a single - * character (of single width) if we don't get a complete - * character. This is purely pragmatic --- I'm not aware - * of a standard way of dealing with incomplete characters. - * - * If we do get a complete character, num_in_char - * becomes irrelevant and is set to zero - * - * This is in contrast to "num" which counts the characters - * or widths in complete characters. The two are summed, - * so we don't count characters twice. - */ - num_in_char++; - complete = 0; - } else { - if (ret == MB_INVALID) { - /* Reset, treat as single character */ - memset(&mb_shiftstate, 0, sizeof(mb_shiftstate)); - ptr = laststart + (*laststart == Meta) + 1; - num++; - } else if (width) { - /* - * Returns -1 if not a printable character. We - * turn this into 0. - */ - int wcw = WCWIDTH(wc); - if (wcw > 0) { - if (width == 1) - num += wcw; - else - num++; - } - } else - num++; - laststart = ptr; - num_in_char = 0; - complete = 1; - } - } - - /* If incomplete, treat remainder as trailing single character */ - return num + (num_in_char ? 1 : 0); -} - -/* - * The equivalent of mb_metacharlenconv_r() for - * strings that aren't metafied and hence have - * explicit lengths. - */ - -/**/ -mod_export int -mb_charlenconv_r(const char *s, int slen, wint_t *wcp, mbstate_t *mbsp) -{ - size_t ret = MB_INVALID; - char inchar; - const char *ptr; - wchar_t wc; - - if (slen && STOUC(*s) <= 0x7f) { - if (wcp) - *wcp = (wint_t)*s; - return 1; - } - - for (ptr = s; slen; ) { - inchar = *ptr; - ptr++; - slen--; - ret = mbrtowc(&wc, &inchar, 1, mbsp); - - if (ret == MB_INVALID) - break; - if (ret == MB_INCOMPLETE) - continue; - if (wcp) - *wcp = wc; - return ptr - s; - } - - if (wcp) - *wcp = WEOF; - /* No valid multibyte sequence */ - memset(mbsp, 0, sizeof(*mbsp)); - if (ptr > s) { - return 1; /* Treat as single byte character */ - } else - return 0; /* Probably shouldn't happen */ -} - -/* - * The equivalent of mb_metacharlenconv() for - * strings that aren't metafied and hence have - * explicit lengths; - */ - -/**/ -mod_export int -mb_charlenconv(const char *s, int slen, wint_t *wcp) -{ - if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) { - if (wcp) - *wcp = (wint_t)*s; - return 1; - } - - return mb_charlenconv_r(s, slen, wcp, &mb_shiftstate); -} - -/**/ -#else - -/* Simple replacement for mb_metacharlenconv */ - -/**/ -mod_export int -metacharlenconv(const char *x, int *c) -{ - /* - * Here we don't use STOUC() on the chars since they - * may be compared against other chars and this will fail - * if chars are signed and the high bit is set. - */ - if (*x == Meta) { - if (c) - *c = x[1] ^ 32; - return 2; - } - if (c) - *c = (char)*x; - return 1; -} - -/* Simple replacement for mb_charlenconv */ - -/**/ -mod_export int -charlenconv(const char *x, int len, int *c) -{ - if (!len) { - if (c) - *c = '\0'; - return 0; - } - - if (c) - *c = (char)*x; - return 1; -} - -/**/ -#endif /* MULTIBYTE_SUPPORT */ - -/* - * Expand tabs to given width, with given starting position on line. - * len is length of unmetafied string in bytes. - * Output to fout. - * Return the end position on the line, i.e. if this is 0 modulo width - * the next character is aligned with a tab stop. - * - * If all is set, all tabs are expanded, else only leading tabs. - */ - -/**/ -mod_export int -zexpandtabs(const char *s, int len, int width, int startpos, FILE *fout, - int all) -{ - int at_start = 1; - -#ifdef MULTIBYTE_SUPPORT - mbstate_t mbs; - size_t ret; - wchar_t wc; - - memset(&mbs, 0, sizeof(mbs)); -#endif - - while (len) { - if (*s == '\t') { - if (all || at_start) { - s++; - len--; - if (width <= 0 || !(startpos % width)) { - /* always output at least one space */ - fputc(' ', fout); - startpos++; - } - if (width <= 0) - continue; /* paranoia */ - while (startpos % width) { - fputc(' ', fout); - startpos++; - } - } else { - /* - * Leave tab alone. - * Guess width to apply... we might get this wrong. - * This is only needed if there's a following string - * that needs tabs expanding, which is unusual. - */ - startpos += width - startpos % width; - s++; - len--; - fputc('\t', fout); - } - continue; - } else if (*s == '\n' || *s == '\r') { - fputc(*s, fout); - s++; - len--; - startpos = 0; - at_start = 1; - continue; - } - - at_start = 0; -#ifdef MULTIBYTE_SUPPORT - if (isset(MULTIBYTE)) { - const char *sstart = s; - ret = mbrtowc(&wc, s, len, &mbs); - if (ret == MB_INVALID) { - /* Assume single character per character */ - memset(&mbs, 0, sizeof(mbs)); - s++; - len--; - } else if (ret == MB_INCOMPLETE) { - /* incomplete at end --- assume likewise, best we've got */ - s++; - len--; - } else { - s += ret; - len -= (int)ret; - } - if (ret == MB_INVALID || ret == MB_INCOMPLETE) { - startpos++; - } else { - int wcw = WCWIDTH(wc); - if (wcw > 0) /* paranoia */ - startpos += wcw; - } - fwrite(sstart, s - sstart, 1, fout); - - continue; - } -#endif /* MULTIBYTE_SUPPORT */ - fputc(*s, fout); - s++; - len--; - startpos++; - } - - return startpos; -} - -/* check for special characters in the string */ - -/**/ -mod_export int -hasspecial(char const *s) -{ - for (; *s; s++) { - if (ispecial(*s == Meta ? *++s ^ 32 : *s)) - return 1; - } - return 0; -} - - -static char * -addunprintable(char *v, const char *u, const char *uend) -{ - for (; u < uend; u++) { - /* - * Just do this byte by byte; there's no great - * advantage in being clever with multibyte - * characters if we don't think they're printable. - */ - int c; - if (*u == Meta) - c = STOUC(*++u ^ 32); - else - c = STOUC(*u); - switch (c) { - case '\0': - *v++ = '\\'; - *v++ = '0'; - if ('0' <= u[1] && u[1] <= '7') { - *v++ = '0'; - *v++ = '0'; - } - break; - - case '\007': *v++ = '\\'; *v++ = 'a'; break; - case '\b': *v++ = '\\'; *v++ = 'b'; break; - case '\f': *v++ = '\\'; *v++ = 'f'; break; - case '\n': *v++ = '\\'; *v++ = 'n'; break; - case '\r': *v++ = '\\'; *v++ = 'r'; break; - case '\t': *v++ = '\\'; *v++ = 't'; break; - case '\v': *v++ = '\\'; *v++ = 'v'; break; - - default: - *v++ = '\\'; - *v++ = '0' + ((c >> 6) & 7); - *v++ = '0' + ((c >> 3) & 7); - *v++ = '0' + (c & 7); - break; - } - } - - return v; -} - -/* - * Quote the string s and return the result as a string from the heap. - * - * The last argument is a QT_ value defined in zsh.h other than QT_NONE. - * - * Most quote styles other than backslash assume the quotes are to - * be added outside quotestring(). QT_SINGLE_OPTIONAL is different: - * the single quotes are only added where necessary, so the - * whole expression is handled here. - * - * The string may be metafied and contain tokens. - */ - -/**/ -mod_export char * -quotestring(const char *s, int instring) -{ - const char *u; - char *v; - int alloclen; - char *buf; - int shownull = 0; - /* - * quotesub is used with QT_SINGLE_OPTIONAL. - * quotesub = 0: mechanism not active - * quotesub = 1: mechanism pending, no "'" yet; - * needs adding at quotestart. - * quotesub = 2: mechanism active, added opening "'"; need - * closing "'". - */ - int quotesub = 0, slen; - char *quotestart; - convchar_t cc; - const char *uend; - - slen = strlen(s); - switch (instring) - { - case QT_BACKSLASH_SHOWNULL: - shownull = 1; - instring = QT_BACKSLASH; - /*FALLTHROUGH*/ - case QT_BACKSLASH: - /* - * With QT_BACKSLASH we may need to use $'\300' stuff. - * Keep memory usage within limits by allocating temporary - * storage and using heap for correct size at end. - */ - alloclen = slen * 7 + 1; - break; - - case QT_BACKSLASH_PATTERN: - alloclen = slen * 2 + 1; - break; - - case QT_SINGLE_OPTIONAL: - /* - * Here, we may need to add single quotes. - * Always show empty strings. - */ - alloclen = slen * 4 + 3; - quotesub = shownull = 1; - break; - - default: - alloclen = slen * 4 + 1; - break; - } - if (!*s && shownull) - alloclen += 2; /* for '' */ - - quotestart = v = buf = zshcalloc(alloclen); - - DPUTS(instring < QT_BACKSLASH || instring == QT_BACKTICK || - instring > QT_BACKSLASH_PATTERN, - "BUG: bad quote type in quotestring"); - u = s; - if (instring == QT_DOLLARS) { - /* - * The only way to get Nularg here is when - * it is placeholding for the empty string? - */ - if (inull(*u)) - u++; - /* - * As we test for printability here we need to be able - * to look for multibyte characters. - */ - MB_METACHARINIT(); - while (*u) { - uend = u + MB_METACHARLENCONV(u, &cc); - - if ( -#ifdef MULTIBYTE_SUPPORT - cc != WEOF && -#endif - WC_ISPRINT(cc)) { - switch (cc) { - case ZWC('\\'): - case ZWC('\''): - *v++ = '\\'; - break; - - default: - if (isset(BANGHIST) && cc == (wchar_t)bangchar) - *v++ = '\\'; - break; - } - while (u < uend) - *v++ = *u++; - } else { - /* Not printable */ - v = addunprintable(v, u, uend); - u = uend; - } - } - } else if (instring == QT_BACKSLASH_PATTERN) { - while (*u) { - if (ipattern(*u)) - *v++ = '\\'; - *v++ = *u++; - } - } else { - if (shownull) { - /* We can't show an empty string with just backslash quoting. */ - if (!*u) { - *v++ = '\''; - *v++ = '\''; - } - } - /* - * Here there are syntactic special characters, so - * we start by going through bytewise. - */ - while (*u) { - int dobackslash = 0; - if (*u == Tick || *u == Qtick) { - char c = *u++; - - *v++ = c; - while (*u && *u != c) - *v++ = *u++; - *v++ = c; - if (*u) - u++; - continue; - } else if ((*u == Qstring || *u == '$') && u[1] == '\'' && - instring == QT_DOUBLE) { - /* - * We don't need to quote $'...' inside a double-quoted - * string. This is largely cosmetic; it looks neater - * if we don't but it doesn't do any harm since the - * \ is stripped. - */ - *v++ = *u++; - } else if ((*u == String || *u == Qstring) && - (u[1] == Inpar || u[1] == Inbrack || u[1] == Inbrace)) { - char c = (u[1] == Inpar ? Outpar : (u[1] == Inbrace ? - Outbrace : Outbrack)); - char beg = *u; - int level = 0; - - *v++ = *u++; - *v++ = *u++; - while (*u && (*u != c || level)) { - if (*u == beg) - level++; - else if (*u == c) - level--; - *v++ = *u++; - } - if (*u) - *v++ = *u++; - continue; - } - else if (ispecial(*u) && - ((*u != '=' && *u != '~') || - u == s || - (isset(MAGICEQUALSUBST) && - (u[-1] == '=' || u[-1] == ':')) || - (*u == '~' && isset(EXTENDEDGLOB))) && - (instring == QT_BACKSLASH || - instring == QT_SINGLE_OPTIONAL || - (isset(BANGHIST) && *u == (char)bangchar && - instring != QT_SINGLE) || - (instring == QT_DOUBLE && - (*u == '$' || *u == '`' || *u == '\"' || *u == '\\')) || - (instring == QT_SINGLE && *u == '\''))) { - if (instring == QT_SINGLE_OPTIONAL) { - if (quotesub == 1) { - /* - * We haven't yet had to quote at the start. - */ - if (*u == '\'') { - /* - * We don't need to. - */ - *v++ = '\\'; - } else { - /* - * It's now time to add quotes. - */ - if (v > quotestart) - { - char *addq; - - for (addq = v; addq > quotestart; addq--) - *addq = addq[-1]; - } - *quotestart = '\''; - v++; - quotesub = 2; - } - *v++ = *u++; - /* - * Next place to start quotes is here. - */ - quotestart = v; - } else if (*u == '\'') { - if (unset(RCQUOTES)) { - *v++ = '\''; - *v++ = '\\'; - *v++ = '\''; - /* Don't restart quotes unless we need them */ - quotesub = 1; - quotestart = v; - } else { - /* simplest just to use '' always */ - *v++ = '\''; - *v++ = '\''; - } - /* dealt with */ - u++; - } else { - /* else already quoting, just add */ - *v++ = *u++; - } - continue; - } else if (*u == '\n' || - (instring == QT_SINGLE && *u == '\'')) { - if (*u == '\n') { - *v++ = '$'; - *v++ = '\''; - *v++ = '\\'; - *v++ = 'n'; - *v++ = '\''; - } else if (unset(RCQUOTES)) { - *v++ = '\''; - if (*u == '\'') - *v++ = '\\'; - *v++ = *u; - *v++ = '\''; - } else - *v++ = '\'', *v++ = '\''; - u++; - continue; - } else { - /* - * We'll need a backslash, but don't add it - * yet since if the character isn't printable - * we'll have to upgrade it to $'...'. - */ - dobackslash = 1; - } - } - - if (itok(*u) || instring != QT_BACKSLASH) { - /* Needs to be passed straight through. */ - if (dobackslash) - *v++ = '\\'; - if (*u == Inparmath) { - /* - * Already syntactically quoted: don't - * add more. - */ - int inmath = 1; - *v++ = *u++; - for (;;) { - char uc = *u; - *v++ = *u++; - if (uc == '\0') - break; - else if (uc == Outparmath && !--inmath) - break; - else if (uc == Inparmath) - ++inmath; - } - } else - *v++ = *u++; - continue; - } - - /* - * Now check if the output is unprintable in the - * current character set. - */ - uend = u + MB_METACHARLENCONV(u, &cc); - if ( -#ifdef MULTIBYTE_SUPPORT - cc != WEOF && -#endif - WC_ISPRINT(cc)) { - if (dobackslash) - *v++ = '\\'; - while (u < uend) { - if (*u == Meta) - *v++ = *u++; - *v++ = *u++; - } - } else { - /* Not printable */ - *v++ = '$'; - *v++ = '\''; - v = addunprintable(v, u, uend); - *v++ = '\''; - u = uend; - } - } - } - if (quotesub == 2) - *v++ = '\''; - *v = '\0'; - - v = dupstring(buf); - zfree(buf, alloclen); - return v; -} - -/* - * Unmetafy and output a string, quoted if it contains special - * characters. - * - * If stream is NULL, return the same output with any allocation on the - * heap. - */ - -/**/ -mod_export char * -quotedzputs(char const *s, FILE *stream) -{ - int inquote = 0, c; - char *outstr, *ptr; - - /* check for empty string */ - if(!*s) { - if (!stream) - return dupstring("''"); - fputs("''", stream); - return NULL; - } - -#ifdef MULTIBYTE_SUPPORT - if (is_mb_niceformat(s)) { - if (stream) { - fputs("$'", stream); - mb_niceformat(s, stream, NULL, NICEFLAG_QUOTE); - fputc('\'', stream); - return NULL; - } else { - char *substr; - mb_niceformat(s, NULL, &substr, NICEFLAG_QUOTE|NICEFLAG_NODUP); - outstr = (char *)zhalloc(4 + strlen(substr)); - sprintf(outstr, "$'%s'", substr); - free(substr); - return outstr; - } - } -#endif /* MULTIBYTE_SUPPORT */ - - if (!hasspecial(s)) { - if (stream) { - zputs(s, stream); - return NULL; - } else { - return dupstring(s); - } - } - - if (!stream) { - const char *cptr; - int l = strlen(s) + 2; - for (cptr = s; *cptr; cptr++) { - if (*cptr == Meta) - cptr++; - else if (*cptr == '\'') - l += isset(RCQUOTES) ? 1 : 3; - } - ptr = outstr = zhalloc(l + 1); - } else { - ptr = outstr = NULL; - } - if (isset(RCQUOTES)) { - /* use rc-style quotes-within-quotes for the whole string */ - if (stream) { - if (fputc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - while(*s) { - if (*s == Dash) - c = '-'; - else if (*s == Meta) - c = *++s ^ 32; - else - c = *s; - s++; - if (c == '\'') { - if (stream) { - if (fputc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - } else if (c == '\n' && isset(CSHJUNKIEQUOTES)) { - if (stream) { - if (fputc('\\', stream) < 0) - return NULL; - } else - *ptr++ = '\\'; - } - if (stream) { - if (fputc(c, stream) < 0) - return NULL; - } else { - if (imeta(c)) { - *ptr++ = Meta; - *ptr++ = c ^ 32; - } else - *ptr++ = c; - } - } - if (stream) { - if (fputc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - } else { - /* use Bourne-style quoting, avoiding empty quoted strings */ - while (*s) { - if (*s == Dash) - c = '-'; - else if (*s == Meta) - c = *++s ^ 32; - else - c = *s; - s++; - if (c == '\'') { - if (inquote) { - if (stream) { - if (putc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - inquote=0; - } - if (stream) { - if (fputs("\\'", stream) < 0) - return NULL; - } else { - *ptr++ = '\\'; - *ptr++ = '\''; - } - } else { - if (!inquote) { - if (stream) { - if (fputc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - inquote=1; - } - if (c == '\n' && isset(CSHJUNKIEQUOTES)) { - if (stream) { - if (fputc('\\', stream) < 0) - return NULL; - } else - *ptr++ = '\\'; - } - if (stream) { - if (fputc(c, stream) < 0) - return NULL; - } else { - if (imeta(c)) { - *ptr++ = Meta; - *ptr++ = c ^ 32; - } else - *ptr++ = c; - } - } - } - if (inquote) { - if (stream) { - if (fputc('\'', stream) < 0) - return NULL; - } else - *ptr++ = '\''; - } - } - if (!stream) - *ptr++ = '\0'; - - return outstr; -} - -/* Double-quote a metafied string. */ - -/**/ -mod_export char * -dquotedztrdup(char const *s) -{ - int len = strlen(s) * 4 + 2; - char *buf = zalloc(len); - char *p = buf, *ret; - - if(isset(CSHJUNKIEQUOTES)) { - int inquote = 0; - - while(*s) { - int c = *s++; - - if (c == Meta) - c = *s++ ^ 32; - switch(c) { - case '"': - case '$': - case '`': - if(inquote) { - *p++ = '"'; - inquote = 0; - } - *p++ = '\\'; - *p++ = c; - break; - default: - if(!inquote) { - *p++ = '"'; - inquote = 1; - } - if(c == '\n') - *p++ = '\\'; - *p++ = c; - break; - } - } - if (inquote) - *p++ = '"'; - } else { - int pending = 0; - - *p++ = '"'; - while(*s) { - int c = *s++; - - if (c == Meta) - c = *s++ ^ 32; - switch(c) { - case '\\': - if(pending) - *p++ = '\\'; - *p++ = '\\'; - pending = 1; - break; - case '"': - case '$': - case '`': - if(pending) - *p++ = '\\'; - *p++ = '\\'; - /* FALL THROUGH */ - default: - *p++ = c; - pending = 0; - break; - } - } - if(pending) - *p++ = '\\'; - *p++ = '"'; - } - ret = metafy(buf, p - buf, META_DUP); - zfree(buf, len); - return ret; -} - -/* Unmetafy and output a string, double quoting it in its entirety. */ - -#if 0 /**/ -int -dquotedzputs(char const *s, FILE *stream) -{ - char *d = dquotedztrdup(s); - int ret = zputs(d, stream); - - zsfree(d); - return ret; -} -#endif - -# if defined(HAVE_NL_LANGINFO) && defined(CODESET) && !defined(__STDC_ISO_10646__) -/* Convert a character from UCS4 encoding to UTF-8 */ - -static size_t -ucs4toutf8(char *dest, unsigned int wval) -{ - size_t len; - - if (wval < 0x80) - len = 1; - else if (wval < 0x800) - len = 2; - else if (wval < 0x10000) - len = 3; - else if (wval < 0x200000) - len = 4; - else if (wval < 0x4000000) - len = 5; - else - len = 6; - - switch (len) { /* falls through except to the last case */ - case 6: dest[5] = (wval & 0x3f) | 0x80; wval >>= 6; - case 5: dest[4] = (wval & 0x3f) | 0x80; wval >>= 6; - case 4: dest[3] = (wval & 0x3f) | 0x80; wval >>= 6; - case 3: dest[2] = (wval & 0x3f) | 0x80; wval >>= 6; - case 2: dest[1] = (wval & 0x3f) | 0x80; wval >>= 6; - *dest = wval | ((0xfc << (6 - len)) & 0xfc); - break; - case 1: *dest = wval; - } - - return len; -} -#endif - - -/* - * The following only occurs once or twice in the code, but in different - * places depending how character set conversion is implemented. - */ -#define CHARSET_FAILED() \ - if (how & GETKEY_DOLLAR_QUOTE) { \ - while ((*tdest++ = *++s)) { \ - if (how & GETKEY_UPDATE_OFFSET) { \ - if (s - sstart > *misc) \ - (*misc)++; \ - } \ - if (*s == Snull) { \ - *len = (s - sstart) + 1; \ - *tdest = '\0'; \ - return buf; \ - } \ - } \ - *len = tdest - buf; \ - return buf; \ - } \ - *t = '\0'; \ - *len = t - buf; \ - return buf - -/* - * Decode a key string, turning it into the literal characters. - * The value returned is a newly allocated string from the heap. - * - * The length is returned in *len. This is usually the length of - * the final unmetafied string. The exception is the case of - * a complete GETKEY_DOLLAR_QUOTE conversion where *len is the - * length of the input string which has been used (up to and including - * the terminating single quote); as the final string is metafied and - * NULL-terminated its length is not required. If both GETKEY_DOLLAR_QUOTE - * and GETKEY_UPDATE_OFFSET are present in "how", the string is not - * expected to be terminated (this is used in completion to parse - * a partial $'...'-quoted string) and the length passed back is - * that of the converted string. Note in both cases that this is a length - * in bytes (i.e. the same as given by a raw pointer difference), not - * characters, which may occupy multiple bytes. - * - * how is a set of bits from the GETKEY_ values defined in zsh.h; - * not all combinations of bits are useful. Callers will typically - * use one of the GETKEYS_ values which define sets of bits. - * Note, for example that: - * - GETKEY_SINGLE_CHAR must not be combined with GETKEY_DOLLAR_QUOTE. - * - GETKEY_UPDATE_OFFSET is only allowed if GETKEY_DOLLAR_QUOTE is - * also present. - * - * *misc is used for various purposes: - * - If GETKEY_BACKSLASH_MINUS is set, it indicates the presence - * of \- in the input. - * - If GETKEY_BACKSLASH_C is set, it indicates the presence - * of \c in the input. - * - If GETKEY_UPDATE_OFFSET is set, it is set on input to some - * mystical completion offset and is updated to a new offset based - * on the converted characters. All Hail the Completion System - * [makes the mystic completion system runic sign in the air]. - * - * The return value is unmetafied unless GETKEY_DOLLAR_QUOTE is - * in use. - */ - -/**/ -mod_export char * -getkeystring(char *s, int *len, int how, int *misc) -{ - char *buf, tmp[1]; - char *t, *tdest = NULL, *u = NULL, *sstart = s, *tbuf = NULL; - char svchar = '\0'; - int meta = 0, control = 0, ignoring = 0; - int i; -#if defined(HAVE_WCHAR_H) && defined(HAVE_WCTOMB) && defined(__STDC_ISO_10646__) - wint_t wval; - int count; -#else - unsigned int wval; -# if defined(HAVE_NL_LANGINFO) && defined(CODESET) -# if defined(HAVE_ICONV) - iconv_t cd; - char inbuf[4]; - size_t inbytes, outbytes; -# endif - size_t count; -# endif -#endif - - DPUTS((how & GETKEY_UPDATE_OFFSET) && - (how & ~(GETKEYS_DOLLARS_QUOTE|GETKEY_UPDATE_OFFSET)), - "BUG: offset updating in getkeystring only supported with $'."); - DPUTS((how & (GETKEY_DOLLAR_QUOTE|GETKEY_SINGLE_CHAR)) == - (GETKEY_DOLLAR_QUOTE|GETKEY_SINGLE_CHAR), - "BUG: incompatible options in getkeystring"); - - if (how & GETKEY_SINGLE_CHAR) - t = buf = tmp; - else { - /* Length including terminating NULL */ - int maxlen = 1; - /* - * We're not necessarily guaranteed the output string will - * be no longer than the input with \u and \U when output - * characters need to be metafied. As this is the only - * case where the string can get longer (?I think), - * include it in the allocation length here but don't - * bother taking account of other factors. - */ - for (t = s; *t; t++) { - if (*t == '\\') { - if (!t[1]) { - maxlen++; - break; - } - if (t[1] == 'u' || t[1] == 'U') - maxlen += MB_CUR_MAX * 2; - else - maxlen += 2; - /* skip the backslash and the following character */ - t++; - } else - maxlen++; - } - if (how & GETKEY_DOLLAR_QUOTE) { - /* - * We're going to unmetafy into a new string, but - * to get a proper metafied input we're going to metafy - * into an intermediate buffer. This is necessary if we have - * \u and \U's with multiple metafied bytes. We can't - * simply remetafy the entire string because there may - * be tokens (indeed, we know there are lexical nulls floating - * around), so we have to be aware character by character - * what we are converting. - * - * In this case, buf is the final buffer (as usual), - * but t points into a temporary buffer that just has - * to be long enough to hold the result of one escape - * code transformation. We count this is a full multibyte - * character (MB_CUR_MAX) with every character metafied - * (*2) plus a little bit of fuzz (for e.g. the odd backslash). - */ - buf = tdest = zhalloc(maxlen); - t = tbuf = zhalloc(MB_CUR_MAX * 3 + 1); - } else { - t = buf = zhalloc(maxlen); - } - } - for (; *s; s++) { - if (*s == '\\' && s[1]) { - int miscadded; - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) { - (*misc)--; - miscadded = 1; - } else - miscadded = 0; - switch (*++s) { - case 'a': -#ifdef __STDC__ - *t++ = '\a'; -#else - *t++ = '\07'; -#endif - break; - case 'n': - *t++ = '\n'; - break; - case 'b': - *t++ = '\b'; - break; - case 't': - *t++ = '\t'; - break; - case 'v': - *t++ = '\v'; - break; - case 'f': - *t++ = '\f'; - break; - case 'r': - *t++ = '\r'; - break; - case 'E': - if (!(how & GETKEY_EMACS)) { - *t++ = '\\', s--; - if (miscadded) - (*misc)++; - continue; - } - /* FALL THROUGH */ - case 'e': - *t++ = '\033'; - break; - case 'M': - /* HERE: GETKEY_UPDATE_OFFSET */ - if (how & GETKEY_EMACS) { - if (s[1] == '-') - s++; - meta = 1 + control; /* preserve the order of ^ and meta */ - } else { - if (miscadded) - (*misc)++; - *t++ = '\\', s--; - } - continue; - case 'C': - /* HERE: GETKEY_UPDATE_OFFSET */ - if (how & GETKEY_EMACS) { - if (s[1] == '-') - s++; - control = 1; - } else { - if (miscadded) - (*misc)++; - *t++ = '\\', s--; - } - continue; - case Meta: - if (miscadded) - (*misc)++; - *t++ = '\\', s--; - break; - case '-': - if (how & GETKEY_BACKSLASH_MINUS) { - *misc = 1; - break; - } - goto def; - case 'c': - if (how & GETKEY_BACKSLASH_C) { - *misc = 1; - *t = '\0'; - *len = t - buf; - return buf; - } - goto def; - case 'U': - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) - (*misc) -= 4; - /* FALLTHROUGH */ - case 'u': - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) { - (*misc) -= 6; /* HERE don't really believe this */ - /* - * We've now adjusted the offset for all the input - * characters, so we need to add for each - * byte of output below. - */ - } - wval = 0; - for (i=(*s == 'u' ? 4 : 8); i>0; i--) { - if (*++s && idigit(*s)) - wval = wval * 16 + (*s - '0'); - else if (*s && ((*s >= 'a' && *s <= 'f') || - (*s >= 'A' && *s <= 'F'))) - wval = wval * 16 + (*s & 0x1f) + 9; - else { - s--; - break; - } - } - if (how & GETKEY_SINGLE_CHAR) { - *misc = wval; - return s+1; - } -#if defined(HAVE_WCHAR_H) && defined(HAVE_WCTOMB) && defined(__STDC_ISO_10646__) - count = wctomb(t, (wchar_t)wval); - if (count == -1) { - zerr("character not in range"); - CHARSET_FAILED(); - } - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) - (*misc) += count; - t += count; -# else -# if defined(HAVE_NL_LANGINFO) && defined(CODESET) - if (!strcmp(nl_langinfo(CODESET), "UTF-8")) { - count = ucs4toutf8(t, wval); - t += count; - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) - (*misc) += count; - } else { -# ifdef HAVE_ICONV - ICONV_CONST char *inptr = inbuf; - const char *codesetstr = nl_langinfo(CODESET); - inbytes = 4; - outbytes = 6; - /* store value in big endian form */ - for (i=3;i>=0;i--) { - inbuf[i] = wval & 0xff; - wval >>= 8; - } - - /* - * If the code set isn't handled, we'd better - * assume it's US-ASCII rather than just failing - * hopelessly. Solaris has a weird habit of - * returning 646. This is handled by the - * native iconv(), but not by GNU iconv; what's - * more, some versions of the native iconv don't - * handle standard names like ASCII. - * - * This should only be a problem if there's a - * mismatch between the NLS and the iconv in use, - * which probably only means if libiconv is in use. - * We checked at configure time if our libraries - * pulled in _libiconv_version, which should be - * a good test. - * - * It shouldn't ever be NULL, but while we're - * being paranoid... - */ -#ifdef ICONV_FROM_LIBICONV - if (!codesetstr || !*codesetstr) - codesetstr = "US-ASCII"; -#endif - cd = iconv_open(codesetstr, "UCS-4BE"); -#ifdef ICONV_FROM_LIBICONV - if (cd == (iconv_t)-1 && !strcmp(codesetstr, "646")) { - codesetstr = "US-ASCII"; - cd = iconv_open(codesetstr, "UCS-4BE"); - } -#endif - if (cd == (iconv_t)-1) { - zerr("cannot do charset conversion (iconv failed)"); - CHARSET_FAILED(); - } - count = iconv(cd, &inptr, &inbytes, &t, &outbytes); - iconv_close(cd); - if (count == (size_t)-1) { - zerr("character not in range"); - CHARSET_FAILED(); - } - if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) - (*misc) += count; -# else - zerr("cannot do charset conversion (iconv not available)"); - CHARSET_FAILED(); -# endif - } -# else - zerr("cannot do charset conversion (NLS not supported)"); - CHARSET_FAILED(); -# endif -# endif - if (how & GETKEY_DOLLAR_QUOTE) { - char *t2; - for (t2 = tbuf; t2 < t; t2++) { - if (imeta(*t2)) { - *tdest++ = Meta; - *tdest++ = *t2 ^ 32; - } else - *tdest++ = *t2; - } - /* reset temporary buffer after handling */ - t = tbuf; - } - continue; - case '\'': - case '\\': - if (how & GETKEY_DOLLAR_QUOTE) { - /* - * Usually \' and \\ will have the initial - * \ turned into a Bnull, however that's not - * necessarily the case when called from - * completion. - */ - *t++ = *s; - break; - } - /* FALLTHROUGH */ - default: - def: - /* HERE: GETKEY_UPDATE_OFFSET? */ - if ((idigit(*s) && *s < '8') || *s == 'x') { - if (!(how & GETKEY_OCTAL_ESC)) { - if (*s == '0') - s++; - else if (*s != 'x') { - *t++ = '\\', s--; - continue; - } - } - if (s[1] && s[2] && s[3]) { - svchar = s[3]; - s[3] = '\0'; - u = s; - } - *t++ = zstrtol(s + (*s == 'x'), &s, - (*s == 'x') ? 16 : 8); - if ((how & GETKEY_PRINTF_PERCENT) && t[-1] == '%') - *t++ = '%'; - if (svchar) { - u[3] = svchar; - svchar = '\0'; - } - s--; - } else { - if (!(how & GETKEY_EMACS) && *s != '\\') { - if (miscadded) - (*misc)++; - *t++ = '\\'; - } - *t++ = *s; - } - break; - } - } else if ((how & GETKEY_DOLLAR_QUOTE) && *s == Snull) { - /* return length to following character */ - *len = (s - sstart) + 1; - *tdest = '\0'; - return buf; - } else if (*s == '^' && !control && (how & GETKEY_CTRL) && s[1]) { - control = 1; - continue; -#ifdef MULTIBYTE_SUPPORT - } else if ((how & GETKEY_SINGLE_CHAR) && - isset(MULTIBYTE) && STOUC(*s) > 127) { - wint_t wc; - int len; - len = mb_metacharlenconv(s, &wc); - if (wc != WEOF) { - *misc = (int)wc; - return s + len; - } -#endif - - } else if (*s == Meta) - *t++ = *++s ^ 32; - else { - if (itok(*s)) { - /* - * We need to be quite careful here. We haven't - * necessarily got an input stream with all tokens - * removed, so the majority of tokens need passing - * through untouched and without Meta handling. - * However, me may need to handle tokenized - * backslashes. - */ - if (meta || control) { - /* - * Presumably we should be using meta or control - * on the character representing the token. - * - * Special case: $'\M-\\' where the token is a Bnull. - * This time we dump the Bnull since we're - * replacing the whole thing. The lexer - * doesn't know about the meta or control modifiers. - */ - if ((how & GETKEY_DOLLAR_QUOTE) && *s == Bnull) - *t++ = *++s; - else - *t++ = ztokens[*s - Pound]; - } else if (how & GETKEY_DOLLAR_QUOTE) { - /* - * We don't want to metafy this, it's a real - * token. - */ - *tdest++ = *s; - if (*s == Bnull) { - /* - * Bnull is a backslash which quotes a couple - * of special characters that always appear - * literally next. See strquote handling - * in gettokstr() in lex.c. We need - * to retain the Bnull (as above) so that quote - * handling in completion can tell where the - * backslash was. - */ - *tdest++ = *++s; - } - /* reset temporary buffer, now handled */ - t = tbuf; - continue; - } else - *t++ = *s; - } else - *t++ = *s; - } - if (meta == 2) { - t[-1] |= 0x80; - meta = 0; - } - if (control) { - if (t[-1] == '?') - t[-1] = 0x7f; - else - t[-1] &= 0x9f; - control = 0; - } - if (meta) { - t[-1] |= 0x80; - meta = 0; - } - if (how & GETKEY_DOLLAR_QUOTE) { - char *t2; - for (t2 = tbuf; t2 < t; t2++) { - /* - * In POSIX mode, an embedded NULL is discarded and - * terminates processing. It just does, that's why. - */ - if (isset(POSIXSTRINGS)) { - if (*t2 == '\0') - ignoring = 1; - if (ignoring) - break; - } - if (imeta(*t2)) { - *tdest++ = Meta; - *tdest++ = *t2 ^ 32; - } else { - *tdest++ = *t2; - } - } - /* - * Reset use of temporary buffer. - */ - t = tbuf; - } - if ((how & GETKEY_SINGLE_CHAR) && t != tmp) { - *misc = STOUC(tmp[0]); - return s + 1; - } - } - /* - * When called from completion, where we use GETKEY_UPDATE_OFFSET to - * update the index into the metafied editor line, we don't necessarily - * have the end of a $'...' quotation, else we should do. - */ - DPUTS((how & (GETKEY_DOLLAR_QUOTE|GETKEY_UPDATE_OFFSET)) == - GETKEY_DOLLAR_QUOTE, "BUG: unterminated $' substitution"); - *t = '\0'; - if (how & GETKEY_DOLLAR_QUOTE) - *tdest = '\0'; - if (how & GETKEY_SINGLE_CHAR) - *misc = 0; - else - *len = ((how & GETKEY_DOLLAR_QUOTE) ? tdest : t) - buf; - return buf; -} - -/* Return non-zero if s is a prefix of t. */ - -/**/ -mod_export int -strpfx(const char *s, const char *t) -{ - while (*s && *s == *t) - s++, t++; - return !*s; -} - -/* Return non-zero if s is a suffix of t. */ - -/**/ -mod_export int -strsfx(char *s, char *t) -{ - int ls = strlen(s), lt = strlen(t); - - if (ls <= lt) - return !strcmp(t + lt - ls, s); - return 0; -} - -/**/ -static int -upchdir(int n) -{ - char buf[PATH_MAX+1]; - char *s; - int err = -1; - - while (n > 0) { - for (s = buf; s < buf + PATH_MAX - 4 && n--; ) - *s++ = '.', *s++ = '.', *s++ = '/'; - s[-1] = '\0'; - if (chdir(buf)) - return err; - err = -2; - } - return 0; -} - -/* - * Initialize a "struct dirsav". - * The structure will be set to the directory we want to save - * the first time we change to a different directory. - */ - -/**/ -mod_export void -init_dirsav(Dirsav d) -{ - d->ino = d->dev = 0; - d->dirname = NULL; - d->dirfd = d->level = -1; -} - -/* - * Change directory, without following symlinks. Returns 0 on success, -1 - * on failure. Sets errno to ENOTDIR if any symlinks are encountered. If - * fchdir() fails, or the current directory is unreadable, we might end up - * in an unwanted directory in case of failure. - * - * path is an unmetafied but null-terminated string, as needed by system - * calls. - */ - -/**/ -mod_export int -lchdir(char const *path, struct dirsav *d, int hard) -{ - char const *pptr; - int level; - struct stat st1; - struct dirsav ds; -#ifdef HAVE_LSTAT - char buf[PATH_MAX + 1], *ptr; - int err; - struct stat st2; -#endif -#ifdef HAVE_FCHDIR - int close_dir = 0; -#endif - - if (!d) { - init_dirsav(&ds); - d = &ds; - } -#ifdef HAVE_LSTAT - if ((*path == '/' || !hard) && - (d != &ds || hard)){ -#else - if (*path == '/') { -#endif - level = -1; -#ifndef HAVE_FCHDIR - if (!d->dirname) - zgetdir(d); -#endif - } else { - level = 0; - if (!d->dev && !d->ino) { - stat(".", &st1); - d->dev = st1.st_dev; - d->ino = st1.st_ino; - } - } - -#ifdef HAVE_LSTAT - if (!hard) -#endif - { - if (d != &ds) { - for (pptr = path; *pptr; level++) { - while (*pptr && *pptr++ != '/'); - while (*pptr == '/') - pptr++; - } - d->level = level; - } - return zchdir((char *) path); - } - -#ifdef HAVE_LSTAT -#ifdef HAVE_FCHDIR - if (d->dirfd < 0) { - close_dir = 1; - if ((d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 && - zgetdir(d) && *d->dirname != '/') - d->dirfd = open("..", O_RDONLY | O_NOCTTY); - } -#endif - if (*path == '/') - if (chdir("/") < 0) - zwarn("failed to chdir(/): %e", errno); - for(;;) { - while(*path == '/') - path++; - if(!*path) { - if (d == &ds) - zsfree(ds.dirname); - else - d->level = level; -#ifdef HAVE_FCHDIR - if (d->dirfd >=0 && close_dir) { - close(d->dirfd); - d->dirfd = -1; - } -#endif - return 0; - } - for(pptr = path; *++pptr && *pptr != '/'; ) ; - if(pptr - path > PATH_MAX) { - err = ENAMETOOLONG; - break; - } - for(ptr = buf; path != pptr; ) - *ptr++ = *path++; - *ptr = 0; - if(lstat(buf, &st1)) { - err = errno; - break; - } - if(!S_ISDIR(st1.st_mode)) { - err = ENOTDIR; - break; - } - if(chdir(buf)) { - err = errno; - break; - } - if (level >= 0) - level++; - if(lstat(".", &st2)) { - err = errno; - break; - } - if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { - err = ENOTDIR; - break; - } - } - if (restoredir(d)) { - int restoreerr = errno; - int i; - /* - * Failed to restore the directory. - * Just be definite, cd to root and report the result. - */ - for (i = 0; i < 2; i++) { - const char *cdest; - if (i) - cdest = "/"; - else { - if (!home) - continue; - cdest = home; - } - zsfree(pwd); - pwd = ztrdup(cdest); - if (chdir(pwd) == 0) - break; - } - if (i == 2) - zerr("lost current directory, failed to cd to /: %e", errno); - else - zerr("lost current directory: %e: changed to `%s'", restoreerr, - pwd); - if (d == &ds) - zsfree(ds.dirname); -#ifdef HAVE_FCHDIR - if (d->dirfd >=0 && close_dir) { - close(d->dirfd); - d->dirfd = -1; - } -#endif - errno = err; - return -2; - } - if (d == &ds) - zsfree(ds.dirname); -#ifdef HAVE_FCHDIR - if (d->dirfd >=0 && close_dir) { - close(d->dirfd); - d->dirfd = -1; - } -#endif - errno = err; - return -1; -#endif /* HAVE_LSTAT */ -} - -/**/ -mod_export int -restoredir(struct dirsav *d) -{ - int err = 0; - struct stat sbuf; - - if (d->dirname && *d->dirname == '/') - return chdir(d->dirname); -#ifdef HAVE_FCHDIR - if (d->dirfd >= 0) { - if (!fchdir(d->dirfd)) { - if (!d->dirname) { - return 0; - } else if (chdir(d->dirname)) { - close(d->dirfd); - d->dirfd = -1; - err = -2; - } - } else { - close(d->dirfd); - d->dirfd = err = -1; - } - } else -#endif - if (d->level > 0) - err = upchdir(d->level); - else if (d->level < 0) - err = -1; - if (d->dev || d->ino) { - stat(".", &sbuf); - if (sbuf.st_ino != d->ino || sbuf.st_dev != d->dev) - err = -2; - } - return err; -} - - -/* Check whether the shell is running with privileges in effect. * - * This is the case if EITHER the euid is zero, OR (if the system * - * supports POSIX.1e (POSIX.6) capability sets) the process' * - * Effective or Inheritable capability sets are non-empty. */ - -/**/ -int -privasserted(void) -{ - if(!geteuid()) - return 1; -#ifdef HAVE_CAP_GET_PROC - { - cap_t caps = cap_get_proc(); - if(caps) { - /* POSIX doesn't define a way to test whether a capability set * - * is empty or not. Typical. I hope this is conforming... */ - cap_flag_value_t val; - cap_value_t n; - for(n = 0; !cap_get_flag(caps, n, CAP_EFFECTIVE, &val); n++) - if(val) { - cap_free(caps); - return 1; - } - } - cap_free(caps); - } -#endif /* HAVE_CAP_GET_PROC */ - return 0; -} - -/**/ -mod_export int -mode_to_octal(mode_t mode) -{ - int m = 0; - - if(mode & S_ISUID) - m |= 04000; - if(mode & S_ISGID) - m |= 02000; - if(mode & S_ISVTX) - m |= 01000; - if(mode & S_IRUSR) - m |= 00400; - if(mode & S_IWUSR) - m |= 00200; - if(mode & S_IXUSR) - m |= 00100; - if(mode & S_IRGRP) - m |= 00040; - if(mode & S_IWGRP) - m |= 00020; - if(mode & S_IXGRP) - m |= 00010; - if(mode & S_IROTH) - m |= 00004; - if(mode & S_IWOTH) - m |= 00002; - if(mode & S_IXOTH) - m |= 00001; - return m; -} - -#ifdef MAILDIR_SUPPORT -/* - * Stat a file. If it's a maildir, check all messages - * in the maildir and present the grand total as a file. - * The fields in the 'struct stat' are from the mail directory. - * The following fields are emulated: - * - * st_nlink always 1 - * st_size total number of bytes in all files - * st_blocks total number of messages - * st_atime access time of newest file in maildir - * st_mtime modify time of newest file in maildir - * st_mode S_IFDIR changed to S_IFREG - * - * This is good enough for most mail-checking applications. - */ - -/**/ -int -mailstat(char *path, struct stat *st) -{ - DIR *dd; - struct dirent *fn; - struct stat st_ret, st_tmp; - static struct stat st_ret_last; - char *dir, *file = 0; - int i; - time_t atime = 0, mtime = 0; - size_t plen = strlen(path), dlen; - - /* First see if it's a directory. */ - if ((i = stat(path, st)) != 0 || !S_ISDIR(st->st_mode)) - return i; - - st_ret = *st; - st_ret.st_nlink = 1; - st_ret.st_size = 0; - st_ret.st_blocks = 0; - st_ret.st_mode &= ~S_IFDIR; - st_ret.st_mode |= S_IFREG; - - /* See if cur/ is present */ - dir = appstr(ztrdup(path), "/cur"); - if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; - st_ret.st_atime = st_tmp.st_atime; - - /* See if tmp/ is present */ - dir[plen] = 0; - dir = appstr(dir, "/tmp"); - if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; - st_ret.st_mtime = st_tmp.st_mtime; - - /* And new/ */ - dir[plen] = 0; - dir = appstr(dir, "/new"); - if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0; - st_ret.st_mtime = st_tmp.st_mtime; - -#if THERE_IS_EXACTLY_ONE_MAILDIR_IN_MAILPATH - { - static struct stat st_new_last; - /* Optimization - if new/ didn't change, nothing else did. */ - if (st_tmp.st_dev == st_new_last.st_dev && - st_tmp.st_ino == st_new_last.st_ino && - st_tmp.st_atime == st_new_last.st_atime && - st_tmp.st_mtime == st_new_last.st_mtime) { - *st = st_ret_last; - return 0; - } - st_new_last = st_tmp; - } -#endif - - /* Loop over new/ and cur/ */ - for (i = 0; i < 2; i++) { - dir[plen] = 0; - dir = appstr(dir, i ? "/cur" : "/new"); - if ((dd = opendir(dir)) == NULL) { - zsfree(file); - zsfree(dir); - return 0; - } - dlen = strlen(dir) + 1; /* include the "/" */ - while ((fn = readdir(dd)) != NULL) { - if (fn->d_name[0] == '.') - continue; - if (file) { - file[dlen] = 0; - file = appstr(file, fn->d_name); - } else { - file = tricat(dir, "/", fn->d_name); - } - if (stat(file, &st_tmp) != 0) - continue; - st_ret.st_size += st_tmp.st_size; - st_ret.st_blocks++; - if (st_tmp.st_atime != st_tmp.st_mtime && - st_tmp.st_atime > atime) - atime = st_tmp.st_atime; - if (st_tmp.st_mtime > mtime) - mtime = st_tmp.st_mtime; - } - closedir(dd); - } - zsfree(file); - zsfree(dir); - - if (atime) st_ret.st_atime = atime; - if (mtime) st_ret.st_mtime = mtime; - - *st = st_ret_last = st_ret; - return 0; -} -#endif |
