mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-19 11:31:26 +01:00
b3a88a53d3
The patch also downscopes a couple of local variables, with no functional change.
7339 lines
162 KiB
C
7339 lines
162 KiB
C
/*
|
|
* 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);
|
|
}
|
|
|
|
/* 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 (!iswprint(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 (!iswprint(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);
|
|
|
|
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;
|
|
}
|
|
sprintf(xbuf2, "%s/%s", xbuf, *pp);
|
|
t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
|
|
if (t0 == -1) {
|
|
zulong pplen = strlen(*pp) + 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 = ztrdup(t);
|
|
} else
|
|
nd->dir = ztrduppfx(t, eptr - t);
|
|
/* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */
|
|
if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD"))
|
|
nd->node.flags |= ND_NOABBREV;
|
|
nameddirtab->addnode(nameddirtab, ztrdup(s), nd);
|
|
}
|
|
|
|
/* Get a named directory: this function can cause a directory name *
|
|
* to be added to the hash table, if it isn't there already. */
|
|
|
|
/**/
|
|
char *
|
|
getnameddir(char *name)
|
|
{
|
|
Param pm;
|
|
char *str;
|
|
Nameddir nd;
|
|
|
|
/* Check if it is already in the named directory table */
|
|
if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)))
|
|
return dupstring(nd->dir);
|
|
|
|
/* Check if there is a scalar parameter with this name whose value *
|
|
* begins with a `/'. If there is, add it to the hash table and *
|
|
* return the new value. */
|
|
if ((pm = (Param) paramtab->getnode(paramtab, name)) &&
|
|
(PM_TYPE(pm->node.flags) == PM_SCALAR) &&
|
|
(str = getsparam(name)) && *str == '/') {
|
|
pm->node.flags |= PM_NAMEDDIR;
|
|
adduserdir(name, str, 0, 1);
|
|
return str;
|
|
}
|
|
|
|
#ifdef HAVE_GETPWNAM
|
|
{
|
|
/* Retrieve an entry from the password table/database for this user. */
|
|
struct passwd *pw;
|
|
if ((pw = getpwnam(name))) {
|
|
char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir, 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 ct = 1;
|
|
|
|
if (lock) {
|
|
char *fn;
|
|
|
|
pushheap();
|
|
l = newlinklist();
|
|
while ((fn = zreaddir(lock, 1)) && !errflag) {
|
|
if (u)
|
|
sprintf(buf, "%s/%s?%s", *s, fn, u);
|
|
else
|
|
sprintf(buf, "%s/%s", *s, fn);
|
|
addlinknode(l, dupstring(buf));
|
|
ct++;
|
|
}
|
|
closedir(lock);
|
|
ap = arr = (char **) zhalloc(ct * sizeof(char *));
|
|
|
|
while ((*ap++ = (char *)ugetnode(l)));
|
|
checkmailpath(arr);
|
|
popheap();
|
|
}
|
|
} else if (shout) {
|
|
if (st.st_size && st.st_atime <= st.st_mtime &&
|
|
st.st_mtime >= lastmailcheck) {
|
|
if (!u) {
|
|
fprintf(shout, "You have new mail.\n");
|
|
fflush(shout);
|
|
} else {
|
|
char *usav;
|
|
int uusav = underscoreused;
|
|
|
|
usav = zalloc(underscoreused);
|
|
|
|
if (usav)
|
|
memcpy(usav, zunderscore, underscoreused);
|
|
|
|
setunderscore(*s);
|
|
|
|
u = dupstring(u);
|
|
if (!parsestr(&u)) {
|
|
singsub(&u);
|
|
zputs(u, shout);
|
|
fputc('\n', shout);
|
|
fflush(shout);
|
|
}
|
|
if (usav) {
|
|
setunderscore(usav);
|
|
zfree(usav, uusav);
|
|
}
|
|
}
|
|
}
|
|
if (isset(MAILWARNING) && st.st_atime > st.st_mtime &&
|
|
st.st_atime > lastmailcheck && st.st_size) {
|
|
fprintf(shout, "The mail in %s has been read.\n", unmeta(*s));
|
|
fflush(shout);
|
|
}
|
|
}
|
|
*v = c;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
/* This prints the XTRACE prompt. */
|
|
|
|
/**/
|
|
FILE *xtrerr = 0;
|
|
|
|
/**/
|
|
void
|
|
printprompt4(void)
|
|
{
|
|
if (!xtrerr)
|
|
xtrerr = stderr;
|
|
if (prompt4) {
|
|
int l, t = opts[XTRACE];
|
|
char *s = dupstring(prompt4);
|
|
|
|
opts[XTRACE] = 0;
|
|
unmetafy(s, &l);
|
|
s = unmetafy(promptexpand(metafy(s, l, META_NOALLOC),
|
|
0, NULL, NULL, NULL), &l);
|
|
opts[XTRACE] = t;
|
|
|
|
fprintf(xtrerr, "%s", s);
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
freestr(void *a)
|
|
{
|
|
zsfree(a);
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
gettyinfo(struct ttyinfo *ti)
|
|
{
|
|
if (SHTTY != -1) {
|
|
#ifdef HAVE_TERMIOS_H
|
|
# ifdef HAVE_TCGETATTR
|
|
if (tcgetattr(SHTTY, &ti->tio) == -1)
|
|
# else
|
|
if (ioctl(SHTTY, TCGETS, &ti->tio) == -1)
|
|
# endif
|
|
zerr("bad tcgets: %e", errno);
|
|
#else
|
|
# ifdef HAVE_TERMIO_H
|
|
ioctl(SHTTY, TCGETA, &ti->tio);
|
|
# else
|
|
ioctl(SHTTY, TIOCGETP, &ti->sgttyb);
|
|
ioctl(SHTTY, TIOCLGET, &ti->lmodes);
|
|
ioctl(SHTTY, TIOCGETC, &ti->tchars);
|
|
ioctl(SHTTY, TIOCGLTC, &ti->ltchars);
|
|
# endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
settyinfo(struct ttyinfo *ti)
|
|
{
|
|
if (SHTTY != -1) {
|
|
#ifdef HAVE_TERMIOS_H
|
|
# ifdef HAVE_TCGETATTR
|
|
# ifndef TCSADRAIN
|
|
# define TCSADRAIN 1 /* XXX Princeton's include files are screwed up */
|
|
# endif
|
|
while (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1 && errno == EINTR)
|
|
;
|
|
# else
|
|
while (ioctl(SHTTY, TCSETS, &ti->tio) == -1 && errno == EINTR)
|
|
;
|
|
# endif
|
|
/* zerr("settyinfo: %e",errno);*/
|
|
#else
|
|
# ifdef HAVE_TERMIO_H
|
|
ioctl(SHTTY, TCSETA, &ti->tio);
|
|
# else
|
|
ioctl(SHTTY, TIOCSETN, &ti->sgttyb);
|
|
ioctl(SHTTY, TIOCLSET, &ti->lmodes);
|
|
ioctl(SHTTY, TIOCSETC, &ti->tchars);
|
|
ioctl(SHTTY, TIOCSLTC, &ti->ltchars);
|
|
# endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* the default tty state */
|
|
|
|
/**/
|
|
mod_export struct ttyinfo shttyinfo;
|
|
|
|
/* != 0 if we need to call resetvideo() */
|
|
|
|
/**/
|
|
mod_export int resetneeded;
|
|
|
|
#ifdef TIOCGWINSZ
|
|
/* window size changed */
|
|
|
|
/**/
|
|
mod_export int winchanged;
|
|
#endif
|
|
|
|
static int
|
|
adjustlines(int signalled)
|
|
{
|
|
int oldlines = zterm_lines;
|
|
|
|
#ifdef TIOCGWINSZ
|
|
if (signalled || zterm_lines <= 0)
|
|
zterm_lines = shttyinfo.winsize.ws_row;
|
|
else
|
|
shttyinfo.winsize.ws_row = zterm_lines;
|
|
#endif /* TIOCGWINSZ */
|
|
if (zterm_lines <= 0) {
|
|
DPUTS(signalled, "BUG: Impossible TIOCGWINSZ rows");
|
|
zterm_lines = tclines > 0 ? tclines : 24;
|
|
}
|
|
|
|
if (zterm_lines > 2)
|
|
termflags &= ~TERM_SHORT;
|
|
else
|
|
termflags |= TERM_SHORT;
|
|
|
|
return (zterm_lines != oldlines);
|
|
}
|
|
|
|
static int
|
|
adjustcolumns(int signalled)
|
|
{
|
|
int oldcolumns = zterm_columns;
|
|
|
|
#ifdef TIOCGWINSZ
|
|
if (signalled || zterm_columns <= 0)
|
|
zterm_columns = shttyinfo.winsize.ws_col;
|
|
else
|
|
shttyinfo.winsize.ws_col = zterm_columns;
|
|
#endif /* TIOCGWINSZ */
|
|
if (zterm_columns <= 0) {
|
|
DPUTS(signalled, "BUG: Impossible TIOCGWINSZ cols");
|
|
zterm_columns = tccolumns > 0 ? tccolumns : 80;
|
|
}
|
|
|
|
if (zterm_columns > 2)
|
|
termflags &= ~TERM_NARROW;
|
|
else
|
|
termflags |= TERM_NARROW;
|
|
|
|
return (zterm_columns != oldcolumns);
|
|
}
|
|
|
|
/* check the size of the window and adjust if necessary. *
|
|
* The value of from: *
|
|
* 0: called from update_job or setupvals *
|
|
* 1: called from the SIGWINCH handler *
|
|
* 2: called from the LINES parameter callback *
|
|
* 3: called from the COLUMNS parameter callback */
|
|
|
|
/**/
|
|
void
|
|
adjustwinsize(int from)
|
|
{
|
|
static int getwinsz = 1;
|
|
#ifdef TIOCGWINSZ
|
|
int ttyrows = shttyinfo.winsize.ws_row;
|
|
int ttycols = shttyinfo.winsize.ws_col;
|
|
#endif
|
|
int resetzle = 0;
|
|
|
|
if (getwinsz || from == 1) {
|
|
#ifdef TIOCGWINSZ
|
|
if (SHTTY == -1)
|
|
return;
|
|
if (ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize) == 0) {
|
|
resetzle = (ttyrows != shttyinfo.winsize.ws_row ||
|
|
ttycols != shttyinfo.winsize.ws_col);
|
|
if (from == 0 && resetzle && ttyrows && ttycols)
|
|
from = 1; /* Signal missed while a job owned the tty? */
|
|
ttyrows = shttyinfo.winsize.ws_row;
|
|
ttycols = shttyinfo.winsize.ws_col;
|
|
} else {
|
|
/* Set to value from environment on failure */
|
|
shttyinfo.winsize.ws_row = zterm_lines;
|
|
shttyinfo.winsize.ws_col = zterm_columns;
|
|
resetzle = (from == 1);
|
|
}
|
|
#else
|
|
resetzle = from == 1;
|
|
#endif /* TIOCGWINSZ */
|
|
} /* else
|
|
return; */
|
|
|
|
switch (from) {
|
|
case 0:
|
|
case 1:
|
|
getwinsz = 0;
|
|
/* Calling setiparam() here calls this function recursively, but *
|
|
* because we've already called adjustlines() and adjustcolumns() *
|
|
* here, recursive calls are no-ops unless a signal intervenes. *
|
|
* The commented "else return;" above might be a safe shortcut, *
|
|
* but I'm concerned about what happens on race conditions; e.g., *
|
|
* suppose the user resizes his xterm during `eval $(resize)'? */
|
|
if (adjustlines(from) && zgetenv("LINES"))
|
|
setiparam("LINES", zterm_lines);
|
|
if (adjustcolumns(from) && zgetenv("COLUMNS"))
|
|
setiparam("COLUMNS", zterm_columns);
|
|
getwinsz = 1;
|
|
break;
|
|
case 2:
|
|
resetzle = adjustlines(0);
|
|
break;
|
|
case 3:
|
|
resetzle = adjustcolumns(0);
|
|
break;
|
|
}
|
|
|
|
#ifdef TIOCGWINSZ
|
|
if (interact && from >= 2 &&
|
|
(shttyinfo.winsize.ws_row != ttyrows ||
|
|
shttyinfo.winsize.ws_col != ttycols)) {
|
|
/* shttyinfo.winsize is already set up correctly */
|
|
/* ioctl(SHTTY, TIOCSWINSZ, (char *)&shttyinfo.winsize); */
|
|
}
|
|
#endif /* TIOCGWINSZ */
|
|
|
|
if (zleactive && resetzle) {
|
|
#ifdef TIOCGWINSZ
|
|
winchanged =
|
|
#endif /* TIOCGWINSZ */
|
|
resetneeded = 1;
|
|
zleentry(ZLE_CMD_RESET_PROMPT);
|
|
zleentry(ZLE_CMD_REFRESH);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ensure the fdtable is large enough for fd, and that the
|
|
* maximum fd is set appropriately.
|
|
*/
|
|
static void
|
|
check_fd_table(int fd)
|
|
{
|
|
if (fd <= max_zsh_fd)
|
|
return;
|
|
|
|
if (fd >= fdtable_size) {
|
|
int old_size = fdtable_size;
|
|
while (fd >= fdtable_size)
|
|
fdtable = zrealloc(fdtable,
|
|
(fdtable_size *= 2)*sizeof(*fdtable));
|
|
memset(fdtable + old_size, 0,
|
|
(fdtable_size - old_size) * sizeof(*fdtable));
|
|
}
|
|
max_zsh_fd = fd;
|
|
}
|
|
|
|
/* Move a fd to a place >= 10 and mark the new fd in fdtable. If the fd *
|
|
* is already >= 10, it is not moved. If it is invalid, -1 is returned. */
|
|
|
|
/**/
|
|
mod_export int
|
|
movefd(int fd)
|
|
{
|
|
if(fd != -1 && fd < 10) {
|
|
#ifdef F_DUPFD
|
|
int fe = fcntl(fd, F_DUPFD, 10);
|
|
#else
|
|
int fe = movefd(dup(fd));
|
|
#endif
|
|
/*
|
|
* To close or not to close if fe is -1?
|
|
* If it is -1, we haven't moved the fd, so if we close
|
|
* it we lose it; but we're probably not going to be able
|
|
* to use it in situ anyway. So probably better to avoid a leak.
|
|
*/
|
|
zclose(fd);
|
|
fd = fe;
|
|
}
|
|
if(fd != -1) {
|
|
check_fd_table(fd);
|
|
fdtable[fd] = FDT_INTERNAL;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* Move fd x to y. If x == -1, fd y is closed.
|
|
* Returns y for success, -1 for failure.
|
|
*/
|
|
|
|
/**/
|
|
mod_export int
|
|
redup(int x, int y)
|
|
{
|
|
int ret = y;
|
|
|
|
if(x < 0)
|
|
zclose(y);
|
|
else if (x != y) {
|
|
if (dup2(x, y) == -1) {
|
|
ret = -1;
|
|
} else {
|
|
check_fd_table(y);
|
|
fdtable[y] = fdtable[x];
|
|
if (fdtable[y] == FDT_FLOCK || fdtable[y] == FDT_FLOCK_EXEC)
|
|
fdtable[y] = FDT_INTERNAL;
|
|
}
|
|
/*
|
|
* Closing any fd to the locked file releases the lock.
|
|
* This isn't expected to happen, it's here for completeness.
|
|
*/
|
|
if (fdtable[x] == FDT_FLOCK)
|
|
fdtable_flocks--;
|
|
zclose(x);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
#if HAVE_MKSTEMP
|
|
char *suffix = prefix ? ".XXXXXX" : "XXXXXX";
|
|
|
|
queue_signals();
|
|
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();
|
|
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;
|
|
|
|
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++;
|
|
*s = u;
|
|
*u = '\0';
|
|
}
|
|
|
|
/* Return the number of elements in an array of pointers. *
|
|
* It doesn't count the NULL pointer at the end. */
|
|
|
|
/**/
|
|
mod_export int
|
|
arrlen(char **s)
|
|
{
|
|
int count;
|
|
|
|
for (count = 0; *s; s++, count++);
|
|
return count;
|
|
}
|
|
|
|
/* 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 = (*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;
|
|
}
|
|
|
|
/**/
|
|
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);
|
|
while (zreaddir(rmd, ignoredots)) {
|
|
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
|
|
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 usec)
|
|
{
|
|
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 > 6)
|
|
digs = 6;
|
|
if (digs < 6) {
|
|
int trunc;
|
|
for (trunc = 5 - digs; trunc; trunc--)
|
|
usec /= 10;
|
|
usec = (usec + 5) / 10;
|
|
}
|
|
sprintf(buf, "%0*ld", digs, usec);
|
|
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 '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) {
|
|
strcpy(new, spnameguess);
|
|
strcat(new, old);
|
|
return newname;
|
|
} else
|
|
return NULL;
|
|
} else {
|
|
maxthresh = bestdist + thresh;
|
|
bestdist += thisdist;
|
|
}
|
|
for (p = spnamebest; (*new = *p++);)
|
|
new++;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
mindist(char *dir, char *mindistguess, char *mindistbest, int 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 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;
|
|
}
|
|
|
|
/**/
|
|
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 "'" 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
|
|
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))
|
|
return 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 == 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 == 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
|