diff options
author | Chet Ramey <chet.ramey@case.edu> | 2015-01-27 11:11:42 -0500 |
---|---|---|
committer | Chet Ramey <chet.ramey@case.edu> | 2015-01-27 11:11:42 -0500 |
commit | c4c90ef8308c6f87bf2c408933e1a03e9b5d2248 (patch) | |
tree | 4534c50c11a9f716e4d3879f84b86a7f8aa46244 | |
parent | 947f04912e4715e7a9df526cd99412bffa729368 (diff) | |
download | bash-c4c90ef8308c6f87bf2c408933e1a03e9b5d2248.tar.gz |
commit bash-20150123 snapshot
73 files changed, 16920 insertions, 251 deletions
diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 59157ae8..1028fd39 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -7902,3 +7902,38 @@ lib/readline/funmap.c lib/readline/readline.h - rl_vi_yank_pop: extern declaration + + 1/21 + ---- +lib/sh/shquote.c + - sh_backslash_quote: change to understand and handle multibyte + characters, using is_basic, COPY_CHAR_P. See + https://bugzilla.redhat.com/show_bug.cgi?id=1184320 for original + report + + 1/23 + ---- +include/posixjmp.h + - use setjmp_sigs instead of redefined setjmp, it fails on Cygwin. + Report from Eric Blake <eblake@redhat.com> + +builtins/wait.def,shell.c + - use setjmp_sigs instead of setjmp call, since setjmp no longer + redefined to something known + +include/chartypes.h, lib/readline/chardefs.h + - make sure all ctype.h macros are called with unsigned char args, + casting to make sure + +lib/sh/casemod.c + - sh_modcase: don't assume that the upper and lower case versions of a + character are the same width, so don't try to do the conversion in + place: convert and copy the converted characters one or more at a + time to the returned string + - sh_modcase: since upper and lower case versions of same character + may have different widths in some locales, don't shortcut and assume + that we can do single-byte case modification (toupper) with a + single-byte input character (e.g., `i'). Fix for problem reported + by Stephane Chazelas <stephane.chazelas@gmail.com> + + diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ index 9bafa046..817082ea 100644 --- a/CWRU/CWRU.chlog~ +++ b/CWRU/CWRU.chlog~ @@ -7899,3 +7899,28 @@ lib/readline/kill.c lib/readline/funmap.c - `vi-yank-pop': bindable name mapped to rl_vi_yank_pop + +lib/readline/readline.h + - rl_vi_yank_pop: extern declaration + + 1/21 + ---- +lib/sh/shquote.c + - sh_backslash_quote: change to understand and handle multibyte + characters, using is_basic, COPY_CHAR_P. See + https://bugzilla.redhat.com/show_bug.cgi?id=1184320 for original + report + + 1/23 + ---- +include/posixjmp.h + - use setjmp_sigs instead of redefined setjmp, it fails on Cygwin. + Report from Eric Blake <eblake@redhat.com> + +builtins/wait.def,shell.c + - use setjmp_sigs instead of setjmp call, since setjmp no longer + redefined to something known + +include/chartypes.h, lib/readline/chardefs.h + - make sure all ctype.h macros are called with unsigned char args, + casting to make sure @@ -1692,6 +1692,8 @@ AC_CHECK_HEADERS(wctype.h) AC_CHECK_HEADERS(wchar.h) AC_CHECK_HEADERS(langinfo.h) +AC_CHECK_HEADERS(mbstr.h) + AC_CHECK_FUNC(mbrlen, AC_DEFINE(HAVE_MBRLEN)) AC_CHECK_FUNC(mbscasecmp, AC_DEFINE(HAVE_MBSCMP)) AC_CHECK_FUNC(mbscmp, AC_DEFINE(HAVE_MBSCMP)) @@ -42,6 +42,10 @@ # include "pcomplete.h" #endif +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif + #define ALIAS_HASH_BUCKETS 16 /* must be power of two */ typedef int sh_alias_map_func_t __P((alias_t *)); diff --git a/arrayfunc.c b/arrayfunc.c index a7294f85..c2ea0e5f 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -33,6 +33,9 @@ #include "pathexp.h" #include "shmbutil.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif #include "builtins/common.h" diff --git a/autom4te.cache/output.0 b/autom4te.cache/output.0 index e72620ce..8c9e0c36 100644 --- a/autom4te.cache/output.0 +++ b/autom4te.cache/output.0 @@ -11079,6 +11079,19 @@ fi done +for ac_header in mbstr.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "mbstr.h" "ac_cv_header_mbstr_h" "$ac_includes_default" +if test "x$ac_cv_header_mbstr_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +@%:@define HAVE_MBSTR_H 1 +_ACEOF + +fi + +done + + ac_fn_c_check_func "$LINENO" "mbrlen" "ac_cv_func_mbrlen" if test "x$ac_cv_func_mbrlen" = xyes; then : $as_echo "@%:@define HAVE_MBRLEN 1" >>confdefs.h diff --git a/autom4te.cache/requests b/autom4te.cache/requests index 5cbb2090..02e81938 100644 --- a/autom4te.cache/requests +++ b/autom4te.cache/requests @@ -15,57 +15,57 @@ 'configure.ac' ], { - 'AM_PROG_F77_C_O' => 1, '_LT_AC_TAGCONFIG' => 1, - 'm4_pattern_forbid' => 1, + 'AM_PROG_F77_C_O' => 1, 'AC_INIT' => 1, - 'AC_CANONICAL_TARGET' => 1, + 'm4_pattern_forbid' => 1, '_AM_COND_IF' => 1, - 'AC_CONFIG_LIBOBJ_DIR' => 1, + 'AC_CANONICAL_TARGET' => 1, 'AC_SUBST' => 1, - 'AC_CANONICAL_HOST' => 1, + 'AC_CONFIG_LIBOBJ_DIR' => 1, 'AC_FC_SRCEXT' => 1, + 'AC_CANONICAL_HOST' => 1, 'AC_PROG_LIBTOOL' => 1, 'AM_INIT_AUTOMAKE' => 1, - 'AC_CONFIG_SUBDIRS' => 1, 'AM_PATH_GUILE' => 1, + 'AC_CONFIG_SUBDIRS' => 1, 'AM_AUTOMAKE_VERSION' => 1, 'LT_CONFIG_LTDL_DIR' => 1, - 'AC_CONFIG_LINKS' => 1, 'AC_REQUIRE_AUX_FILE' => 1, - 'LT_SUPPORTED_TAG' => 1, + 'AC_CONFIG_LINKS' => 1, 'm4_sinclude' => 1, + 'LT_SUPPORTED_TAG' => 1, 'AM_MAINTAINER_MODE' => 1, 'AM_NLS' => 1, 'AC_FC_PP_DEFINE' => 1, 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, - '_m4_warn' => 1, 'AM_MAKEFILE_INCLUDE' => 1, + '_m4_warn' => 1, 'AM_PROG_CXX_C_O' => 1, - '_AM_MAKEFILE_INCLUDE' => 1, '_AM_COND_ENDIF' => 1, + '_AM_MAKEFILE_INCLUDE' => 1, 'AM_ENABLE_MULTILIB' => 1, 'AM_SILENT_RULES' => 1, 'AM_PROG_MOC' => 1, 'AC_CONFIG_FILES' => 1, - 'LT_INIT' => 1, 'include' => 1, - 'AM_GNU_GETTEXT' => 1, + 'LT_INIT' => 1, 'AM_PROG_AR' => 1, + 'AM_GNU_GETTEXT' => 1, 'AC_LIBSOURCE' => 1, - 'AC_CANONICAL_BUILD' => 1, 'AM_PROG_FC_C_O' => 1, + 'AC_CANONICAL_BUILD' => 1, 'AC_FC_FREEFORM' => 1, - 'AC_FC_PP_SRCEXT' => 1, 'AH_OUTPUT' => 1, - 'AC_CONFIG_AUX_DIR' => 1, + 'AC_FC_PP_SRCEXT' => 1, '_AM_SUBST_NOTMAKE' => 1, - 'm4_pattern_allow' => 1, - 'AM_PROG_CC_C_O' => 1, + 'AC_CONFIG_AUX_DIR' => 1, 'sinclude' => 1, - 'AM_CONDITIONAL' => 1, - 'AC_CANONICAL_SYSTEM' => 1, + 'AM_PROG_CC_C_O' => 1, + 'm4_pattern_allow' => 1, 'AM_XGETTEXT_OPTION' => 1, + 'AC_CANONICAL_SYSTEM' => 1, + 'AM_CONDITIONAL' => 1, 'AC_CONFIG_HEADERS' => 1, 'AC_DEFINE_TRACE_LITERAL' => 1, 'AM_POT_TOOLS' => 1, diff --git a/autom4te.cache/traces.0 b/autom4te.cache/traces.0 index 6df9bc61..b92884da 100644 --- a/autom4te.cache/traces.0 +++ b/autom4te.cache/traces.0 @@ -620,7 +620,7 @@ m4trace:configure.ac:553: -1- _m4_warn([obsolete], [The macro `AC_TRY_RUN' is ob You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... -aclocal.m4:1806: RL_LIB_READLINE_VERSION is expanded from... +aclocal.m4:1808: RL_LIB_READLINE_VERSION is expanded from... configure.ac:553: the top level]) m4trace:configure.ac:553: -1- AC_DEFINE_TRACE_LITERAL([RL_READLINE_VERSION]) m4trace:configure.ac:553: -1- m4_pattern_allow([^RL_READLINE_VERSION$]) @@ -815,8 +815,8 @@ m4trace:configure.ac:707: -1- AC_SUBST_TRACE([MSGMERGE]) m4trace:configure.ac:707: -1- m4_pattern_allow([^MSGMERGE$]) m4trace:configure.ac:707: -1- _m4_warn([obsolete], [The macro `AC_OUTPUT_COMMANDS' is obsolete. You should run autoupdate.], [../../lib/autoconf/status.m4:1026: AC_OUTPUT_COMMANDS is expanded from... -aclocal.m4:3707: AM_PO_SUBDIRS is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:3709: AM_PO_SUBDIRS is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([off_t]) m4trace:configure.ac:707: -1- m4_pattern_allow([^off_t$]) @@ -880,9 +880,9 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2613: gt_INTDIV0 is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2615: gt_INTDIV0 is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([INTDIV0_RAISES_SIGFPE]) m4trace:configure.ac:707: -1- m4_pattern_allow([^INTDIV0_RAISES_SIGFPE$]) @@ -893,10 +893,10 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2614: AC_TRY_COMPILE ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2715: jm_AC_HEADER_INTTYPES_H is expanded from... -aclocal.m4:4016: jm_AC_TYPE_UINTMAX_T is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2717: jm_AC_HEADER_INTTYPES_H is expanded from... +aclocal.m4:4018: jm_AC_TYPE_UINTMAX_T is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_INTTYPES_H_WITH_UINTMAX]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_INTTYPES_H_WITH_UINTMAX$]) @@ -908,10 +908,10 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2614: AC_TRY_COMPILE ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:3986: jm_AC_HEADER_STDINT_H is expanded from... -aclocal.m4:4016: jm_AC_TYPE_UINTMAX_T is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:3988: jm_AC_HEADER_STDINT_H is expanded from... +aclocal.m4:4018: jm_AC_TYPE_UINTMAX_T is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_STDINT_H_WITH_UINTMAX]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_STDINT_H_WITH_UINTMAX$]) @@ -923,10 +923,10 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:4043: jm_AC_TYPE_UNSIGNED_LONG_LONG is expanded from... -aclocal.m4:4016: jm_AC_TYPE_UINTMAX_T is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:4045: jm_AC_TYPE_UNSIGNED_LONG_LONG is expanded from... +aclocal.m4:4018: jm_AC_TYPE_UINTMAX_T is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_UNSIGNED_LONG_LONG]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_UNSIGNED_LONG_LONG$]) @@ -946,9 +946,9 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2614: AC_TRY_COMPILE ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2688: gt_HEADER_INTTYPES_H is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2690: gt_HEADER_INTTYPES_H is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_INTTYPES_H]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_INTTYPES_H$]) @@ -959,9 +959,9 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2614: AC_TRY_COMPILE ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2743: gt_INTTYPES_PRI is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2745: gt_INTTYPES_PRI is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([PRI_MACROS_BROKEN]) m4trace:configure.ac:707: -1- m4_pattern_allow([^PRI_MACROS_BROKEN$]) @@ -1038,20 +1038,20 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2521: AM_ICONV_LINK is expanded from... -aclocal.m4:2576: AM_ICONV is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2523: AM_ICONV_LINK is expanded from... +aclocal.m4:2578: AM_ICONV is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- _m4_warn([obsolete], [The macro `AC_TRY_LINK' is obsolete. You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2521: AM_ICONV_LINK is expanded from... -aclocal.m4:2576: AM_ICONV is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2523: AM_ICONV_LINK is expanded from... +aclocal.m4:2578: AM_ICONV is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_ICONV]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_ICONV$]) @@ -1067,9 +1067,9 @@ m4trace:configure.ac:707: -1- _m4_warn([obsolete], [The macro `AC_TRY_COMPILE' i You should run autoupdate.], [../../lib/autoconf/general.m4:2614: AC_TRY_COMPILE is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... -aclocal.m4:2576: AM_ICONV is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2578: AM_ICONV is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([ICONV_CONST]) m4trace:configure.ac:707: -1- m4_pattern_allow([^ICONV_CONST$]) @@ -1080,9 +1080,9 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2040: AM_LANGINFO_CODESET is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2042: AM_LANGINFO_CODESET is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_LANGINFO_CODESET]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_LANGINFO_CODESET$]) @@ -1093,9 +1093,9 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2810: AM_LC_MESSAGES is expanded from... -aclocal.m4:2399: AM_INTL_SUBDIR is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2812: AM_LC_MESSAGES is expanded from... +aclocal.m4:2401: AM_INTL_SUBDIR is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([HAVE_LC_MESSAGES]) m4trace:configure.ac:707: -1- m4_pattern_allow([^HAVE_LC_MESSAGES$]) @@ -1113,21 +1113,21 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- _m4_warn([obsolete], [The macro `AC_TRY_LINK' is obsolete. You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- _m4_warn([obsolete], [The macro `AC_TRY_LINK' is obsolete. You should run autoupdate.], [../../lib/autoconf/general.m4:2687: AC_TRY_LINK is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:2111: AM_GNU_GETTEXT is expanded from... +aclocal.m4:2113: AM_GNU_GETTEXT is expanded from... configure.ac:707: the top level]) m4trace:configure.ac:707: -1- AC_DEFINE_TRACE_LITERAL([ENABLE_NLS]) m4trace:configure.ac:707: -1- m4_pattern_allow([^ENABLE_NLS$]) @@ -1810,6 +1810,10 @@ m4trace:configure.ac:862: -1- AH_OUTPUT([HAVE_LANGINFO_H], [/* Define to 1 if yo @%:@undef HAVE_LANGINFO_H]) m4trace:configure.ac:862: -1- AC_DEFINE_TRACE_LITERAL([HAVE_LANGINFO_H]) m4trace:configure.ac:862: -1- m4_pattern_allow([^HAVE_LANGINFO_H$]) +m4trace:configure.ac:862: -1- AH_OUTPUT([HAVE_MBSTR_H], [/* Define to 1 if you have the <mbstr.h> header file. */ +@%:@undef HAVE_MBSTR_H]) +m4trace:configure.ac:862: -1- AC_DEFINE_TRACE_LITERAL([HAVE_MBSTR_H]) +m4trace:configure.ac:862: -1- m4_pattern_allow([^HAVE_MBSTR_H$]) m4trace:configure.ac:862: -2- AC_DEFINE_TRACE_LITERAL([HAVE_MBRLEN]) m4trace:configure.ac:862: -2- m4_pattern_allow([^HAVE_MBRLEN$]) m4trace:configure.ac:862: -2- AC_DEFINE_TRACE_LITERAL([HAVE_MBSCMP]) @@ -2178,7 +2182,7 @@ m4trace:configure.ac:943: -1- _m4_warn([obsolete], [The macro `AC_TRY_RUN' is ob You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... -aclocal.m4:1920: BASH_FUNC_CTYPE_NONASCII is expanded from... +aclocal.m4:1922: BASH_FUNC_CTYPE_NONASCII is expanded from... configure.ac:943: the top level]) m4trace:configure.ac:943: -1- AC_DEFINE_TRACE_LITERAL([CTYPE_NON_ASCII]) m4trace:configure.ac:943: -1- m4_pattern_allow([^CTYPE_NON_ASCII$]) @@ -2400,7 +2404,7 @@ m4trace:configure.ac:981: -1- _m4_warn([obsolete], [The macro `AC_TRY_RUN' is ob You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... -aclocal.m4:4149: BASH_STRUCT_WEXITSTATUS_OFFSET is expanded from... +aclocal.m4:4151: BASH_STRUCT_WEXITSTATUS_OFFSET is expanded from... configure.ac:981: the top level]) m4trace:configure.ac:981: -1- AC_DEFINE_TRACE_LITERAL([WEXITSTATUS_OFFSET]) m4trace:configure.ac:981: -1- m4_pattern_allow([^WEXITSTATUS_OFFSET$]) @@ -2528,7 +2532,7 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:4065: BASH_FUNC_SNPRINTF is expanded from... +aclocal.m4:4067: BASH_FUNC_SNPRINTF is expanded from... configure.ac:997: the top level]) m4trace:configure.ac:997: -1- AC_DEFINE_TRACE_LITERAL([HAVE_SNPRINTF]) m4trace:configure.ac:997: -1- m4_pattern_allow([^HAVE_SNPRINTF$]) @@ -2541,7 +2545,7 @@ You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... ../../lib/autoconf/general.m4:2052: AC_CACHE_CHECK is expanded from... -aclocal.m4:4093: BASH_FUNC_VSNPRINTF is expanded from... +aclocal.m4:4095: BASH_FUNC_VSNPRINTF is expanded from... configure.ac:998: the top level]) m4trace:configure.ac:998: -1- AC_DEFINE_TRACE_LITERAL([HAVE_VSNPRINTF]) m4trace:configure.ac:998: -1- m4_pattern_allow([^HAVE_VSNPRINTF$]) @@ -2625,7 +2629,7 @@ m4trace:configure.ac:1026: -1- _m4_warn([obsolete], [The macro `AC_TRY_RUN' is o You should run autoupdate.], [../../lib/autoconf/general.m4:2764: AC_TRY_RUN is expanded from... ../../lib/m4sugar/m4sh.m4:639: AS_IF is expanded from... ../../lib/autoconf/general.m4:2031: AC_CACHE_VAL is expanded from... -aclocal.m4:1964: BASH_CHECK_WCONTINUED is expanded from... +aclocal.m4:1966: BASH_CHECK_WCONTINUED is expanded from... configure.ac:1026: the top level]) m4trace:configure.ac:1026: -1- AC_DEFINE_TRACE_LITERAL([WCONTINUED_BROKEN]) m4trace:configure.ac:1026: -1- m4_pattern_allow([^WCONTINUED_BROKEN$]) @@ -55,6 +55,10 @@ #include "shmbutil.h" #include "trap.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif + #include "builtins/common.h" #include <readline/rlconf.h> diff --git a/builtins/alias.def b/builtins/alias.def index d760cebe..d720ee4b 100644 --- a/builtins/alias.def +++ b/builtins/alias.def @@ -33,7 +33,7 @@ A trailing space in VALUE causes the next word to be checked for alias substitution when the alias is expanded. Options: - -p Print all defined aliases in a reusable format + -p print all defined aliases in a reusable format Exit Status: alias returns true unless a NAME is supplied for which no alias has been @@ -160,7 +160,7 @@ $SHORT_DOC unalias [-a] name [name ...] Remove each NAME from the list of defined aliases. Options: - -a remove all alias definitions. + -a remove all alias definitions Return success unless a NAME is not an existing alias. $END diff --git a/builtins/bind.def b/builtins/bind.def index 2e271515..22c29409 100644 --- a/builtins/bind.def +++ b/builtins/bind.def @@ -54,7 +54,7 @@ Options: -f filename Read key bindings from FILENAME. -x keyseq:shell-command Cause SHELL-COMMAND to be executed when KEYSEQ is entered. - -X List key sequences bound with -x and associated commands + -X List key sequences bound with -x and associated commands in a form that can be reused as input. Exit Status: diff --git a/builtins/cd.def b/builtins/cd.def index a440fca9..a2ffd333 100644 --- a/builtins/cd.def +++ b/builtins/cd.def @@ -93,16 +93,17 @@ the word is assumed to be a variable name. If that variable has a value, its value is used for DIR. Options: - -L force symbolic links to be followed: resolve symbolic links in - DIR after processing instances of `..' - -P use the physical directory structure without following symbolic - links: resolve symbolic links in DIR before processing instances - of `..' - -e if the -P option is supplied, and the current working directory - cannot be determined successfully, exit with a non-zero status + -L force symbolic links to be followed: resolve symbolic + links in DIR after processing instances of `..' + -P use the physical directory structure without following + symbolic links: resolve symbolic links in DIR before + processing instances of `..' + -e if the -P option is supplied, and the current working + directory cannot be determined successfully, exit with + a non-zero status #if defined (O_XATTR) - -@ on systems that support it, present a file with extended attributes - as a directory containing the file attributes + -@ on systems that support it, present a file with extended + attributes as a directory containing the file attributes #endif The default is to follow symbolic links, as if `-L' were specified. @@ -450,7 +451,7 @@ Print the name of the current working directory. Options: -L print the value of $PWD if it names the current working - directory + directory -P print the physical directory, without any symbolic links By default, `pwd' behaves as if `-L' were specified. diff --git a/builtins/command.def b/builtins/command.def index 310b3599..1f7e272e 100644 --- a/builtins/command.def +++ b/builtins/command.def @@ -30,10 +30,10 @@ information about the specified COMMANDs. Can be used to invoke commands on disk when a function with the same name exists. Options: - -p use a default value for PATH that is guaranteed to find all of - the standard utilities - -v print a description of COMMAND similar to the `type' builtin - -V print a more verbose description of each COMMAND + -p use a default value for PATH that is guaranteed to find all of + the standard utilities + -v print a description of COMMAND similar to the `type' builtin + -V print a more verbose description of each COMMAND Exit Status: Returns exit status of COMMAND, or failure if COMMAND is not found. diff --git a/builtins/common.c b/builtins/common.c index cfa14215..0cb809be 100644 --- a/builtins/common.c +++ b/builtins/common.c @@ -242,7 +242,7 @@ sh_invalidnum (s) { char *msg; - if (*s == '0' && isdigit (s[1])) + if (*s == '0' && isdigit ((unsigned char)s[1])) msg = _("invalid octal number"); else if (*s == '0' && s[1] == 'x') msg = _("invalid hex number"); diff --git a/builtins/common.c~ b/builtins/common.c~ new file mode 100644 index 00000000..cfa14215 --- /dev/null +++ b/builtins/common.c~ @@ -0,0 +1,918 @@ +/* common.c - utility functions for all builtins */ + +/* Copyright (C) 1987-2010 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include <stdio.h> +#include <chartypes.h> +#include "../bashtypes.h" +#include "posixstat.h" +#include <signal.h> + +#include <errno.h> + +#if defined (PREFER_STDARG) +# include <stdarg.h> +#else +# include <varargs.h> +#endif + +#include "../bashansi.h" +#include "../bashintl.h" + +#define NEED_FPURGE_DECL + +#include "../shell.h" +#include "maxpath.h" +#include "../flags.h" +#include "../jobs.h" +#include "../builtins.h" +#include "../input.h" +#include "../execute_cmd.h" +#include "../trap.h" +#include "bashgetopt.h" +#include "common.h" +#include "builtext.h" +#include <tilde/tilde.h> + +#if defined (HISTORY) +# include "../bashhist.h" +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int indirection_level, subshell_environment; +extern int line_number; +extern int last_command_exit_value; +extern int trap_saved_exit_value; +extern int running_trap; +extern int posixly_correct; +extern char *this_command_name, *shell_name; +extern const char * const bash_getcwd_errstr; + +/* Used by some builtins and the mainline code. */ +sh_builtin_func_t *last_shell_builtin = (sh_builtin_func_t *)NULL; +sh_builtin_func_t *this_shell_builtin = (sh_builtin_func_t *)NULL; + +/* **************************************************************** */ +/* */ +/* Error reporting, usage, and option processing */ +/* */ +/* **************************************************************** */ + +/* This is a lot like report_error (), but it is for shell builtins + instead of shell control structures, and it won't ever exit the + shell. */ + +static void +builtin_error_prolog () +{ + char *name; + + name = get_name_for_error (); + fprintf (stderr, "%s: ", name); + + if (interactive_shell == 0) + fprintf (stderr, _("line %d: "), executing_line_number ()); + + if (this_command_name && *this_command_name) + fprintf (stderr, "%s: ", this_command_name); +} + +void +#if defined (PREFER_STDARG) +builtin_error (const char *format, ...) +#else +builtin_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + builtin_error_prolog (); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + va_end (args); + fprintf (stderr, "\n"); +} + +void +#if defined (PREFER_STDARG) +builtin_warning (const char *format, ...) +#else +builtin_warning (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + builtin_error_prolog (); + fprintf (stderr, _("warning: ")); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + va_end (args); + fprintf (stderr, "\n"); +} + +/* Print a usage summary for the currently-executing builtin command. */ +void +builtin_usage () +{ + if (this_command_name && *this_command_name) + fprintf (stderr, _("%s: usage: "), this_command_name); + fprintf (stderr, "%s\n", _(current_builtin->short_doc)); + fflush (stderr); +} + +/* Return if LIST is NULL else barf and jump to top_level. Used by some + builtins that do not accept arguments. */ +void +no_args (list) + WORD_LIST *list; +{ + if (list) + { + builtin_error (_("too many arguments")); + top_level_cleanup (); + jump_to_top_level (DISCARD); + } +} + +/* Check that no options were given to the currently-executing builtin, + and return 0 if there were options. */ +int +no_options (list) + WORD_LIST *list; +{ + int opt; + + reset_internal_getopt (); + if ((opt = internal_getopt (list, "")) != -1) + { + if (opt == GETOPT_HELP) + { + builtin_help (); + return (2); + } + builtin_usage (); + return (1); + } + return (0); +} + +void +sh_needarg (s) + char *s; +{ + builtin_error (_("%s: option requires an argument"), s); +} + +void +sh_neednumarg (s) + char *s; +{ + builtin_error (_("%s: numeric argument required"), s); +} + +void +sh_notfound (s) + char *s; +{ + builtin_error (_("%s: not found"), s); +} + +/* Function called when one of the builtin commands detects an invalid + option. */ +void +sh_invalidopt (s) + char *s; +{ + builtin_error (_("%s: invalid option"), s); +} + +void +sh_invalidoptname (s) + char *s; +{ + builtin_error (_("%s: invalid option name"), s); +} + +void +sh_invalidid (s) + char *s; +{ + builtin_error (_("`%s': not a valid identifier"), s); +} + +void +sh_invalidnum (s) + char *s; +{ + char *msg; + + if (*s == '0' && isdigit (s[1])) + msg = _("invalid octal number"); + else if (*s == '0' && s[1] == 'x') + msg = _("invalid hex number"); + else + msg = _("invalid number"); + builtin_error ("%s: %s", s, msg); +} + +void +sh_invalidsig (s) + char *s; +{ + builtin_error (_("%s: invalid signal specification"), s); +} + +void +sh_badpid (s) + char *s; +{ + builtin_error (_("`%s': not a pid or valid job spec"), s); +} + +void +sh_readonly (s) + const char *s; +{ + builtin_error (_("%s: readonly variable"), s); +} + +void +sh_erange (s, desc) + char *s, *desc; +{ + if (s) + builtin_error (_("%s: %s out of range"), s, desc ? desc : _("argument")); + else + builtin_error (_("%s out of range"), desc ? desc : _("argument")); +} + +#if defined (JOB_CONTROL) +void +sh_badjob (s) + char *s; +{ + builtin_error (_("%s: no such job"), s); +} + +void +sh_nojobs (s) + char *s; +{ + if (s) + builtin_error (_("%s: no job control"), s); + else + builtin_error (_("no job control")); +} +#endif + +#if defined (RESTRICTED_SHELL) +void +sh_restricted (s) + char *s; +{ + if (s) + builtin_error (_("%s: restricted"), s); + else + builtin_error (_("restricted")); +} +#endif + +void +sh_notbuiltin (s) + char *s; +{ + builtin_error (_("%s: not a shell builtin"), s); +} + +void +sh_wrerror () +{ +#if defined (DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS) && defined (EPIPE) + if (errno != EPIPE) +#endif /* DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS && EPIPE */ + builtin_error (_("write error: %s"), strerror (errno)); +} + +void +sh_ttyerror (set) + int set; +{ + if (set) + builtin_error (_("error setting terminal attributes: %s"), strerror (errno)); + else + builtin_error (_("error getting terminal attributes: %s"), strerror (errno)); +} + +int +sh_chkwrite (s) + int s; +{ + fflush (stdout); + if (ferror (stdout)) + { + sh_wrerror (); + fpurge (stdout); + clearerr (stdout); + return (EXECUTION_FAILURE); + } + return (s); +} + +/* **************************************************************** */ +/* */ +/* Shell positional parameter manipulation */ +/* */ +/* **************************************************************** */ + +/* Convert a WORD_LIST into a C-style argv. Return the number of elements + in the list in *IP, if IP is non-null. A convenience function for + loadable builtins; also used by `test'. */ +char ** +make_builtin_argv (list, ip) + WORD_LIST *list; + int *ip; +{ + char **argv; + + argv = strvec_from_word_list (list, 0, 1, ip); + argv[0] = this_command_name; + return argv; +} + +/* Remember LIST in $1 ... $9, and REST_OF_ARGS. If DESTRUCTIVE is + non-zero, then discard whatever the existing arguments are, else + only discard the ones that are to be replaced. */ +void +remember_args (list, destructive) + WORD_LIST *list; + int destructive; +{ + register int i; + + for (i = 1; i < 10; i++) + { + if ((destructive || list) && dollar_vars[i]) + { + free (dollar_vars[i]); + dollar_vars[i] = (char *)NULL; + } + + if (list) + { + dollar_vars[i] = savestring (list->word->word); + list = list->next; + } + } + + /* If arguments remain, assign them to REST_OF_ARGS. + Note that copy_word_list (NULL) returns NULL, and + that dispose_words (NULL) does nothing. */ + if (destructive || list) + { + dispose_words (rest_of_args); + rest_of_args = copy_word_list (list); + } + + if (destructive) + set_dollar_vars_changed (); + + invalidate_cached_quoted_dollar_at (); +} + +static int changed_dollar_vars; + +/* Have the dollar variables been reset to new values since we last + checked? */ +int +dollar_vars_changed () +{ + return (changed_dollar_vars); +} + +void +set_dollar_vars_unchanged () +{ + changed_dollar_vars = 0; +} + +void +set_dollar_vars_changed () +{ + if (variable_context) + changed_dollar_vars |= ARGS_FUNC; + else if (this_shell_builtin == set_builtin) + changed_dollar_vars |= ARGS_SETBLTIN; + else + changed_dollar_vars |= ARGS_INVOC; +} + +/* **************************************************************** */ +/* */ +/* Validating numeric input and arguments */ +/* */ +/* **************************************************************** */ + +/* Read a numeric arg for this_command_name, the name of the shell builtin + that wants it. LIST is the word list that the arg is to come from. + Accept only the numeric argument; report an error if other arguments + follow. If FATAL is 1, call throw_to_top_level, which exits the + shell; if it's 2, call jump_to_top_level (DISCARD), which aborts the + current command; if FATAL is 0, return an indication of an invalid + number by setting *NUMOK == 0 and return -1. */ +int +get_numeric_arg (list, fatal, count) + WORD_LIST *list; + int fatal; + intmax_t *count; +{ + char *arg; + + if (count) + *count = 1; + + if (list && list->word && ISOPTION (list->word->word, '-')) + list = list->next; + + if (list) + { + arg = list->word->word; + if (arg == 0 || (legal_number (arg, count) == 0)) + { + sh_neednumarg (list->word->word ? list->word->word : "`'"); + if (fatal == 0) + return 0; + else if (fatal == 1) /* fatal == 1; abort */ + throw_to_top_level (); + else /* fatal == 2; discard current command */ + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + no_args (list->next); + } + + return (1); +} + +/* Get an eight-bit status value from LIST */ +int +get_exitstat (list) + WORD_LIST *list; +{ + int status; + intmax_t sval; + char *arg; + + if (list && list->word && ISOPTION (list->word->word, '-')) + list = list->next; + + if (list == 0) + { + /* If we're not running the DEBUG trap, the return builtin, when not + given any arguments, uses the value of $? before the trap ran. If + given an argument, return uses it. This means that the trap can't + change $?. The DEBUG trap gets to change $?, though, since that is + part of its reason for existing, and because the extended debug mode + does things with the return value. */ + if (this_shell_builtin == return_builtin && running_trap > 0 && running_trap != DEBUG_TRAP+1) + return (trap_saved_exit_value); + return (last_command_exit_value); + } + + arg = list->word->word; + if (arg == 0 || legal_number (arg, &sval) == 0) + { + sh_neednumarg (list->word->word ? list->word->word : "`'"); + return EX_BADUSAGE; + } + no_args (list->next); + + status = sval & 255; + return status; +} + +/* Return the octal number parsed from STRING, or -1 to indicate + that the string contained a bad number. */ +int +read_octal (string) + char *string; +{ + int result, digits; + + result = digits = 0; + while (*string && ISOCTAL (*string)) + { + digits++; + result = (result * 8) + (*string++ - '0'); + if (result > 0777) + return -1; + } + + if (digits == 0 || *string) + result = -1; + + return (result); +} + +/* **************************************************************** */ +/* */ +/* Manipulating the current working directory */ +/* */ +/* **************************************************************** */ + +/* Return a consed string which is the current working directory. + FOR_WHOM is the name of the caller for error printing. */ +char *the_current_working_directory = (char *)NULL; + +char * +get_working_directory (for_whom) + char *for_whom; +{ + if (no_symbolic_links) + { + FREE (the_current_working_directory); + the_current_working_directory = (char *)NULL; + } + + if (the_current_working_directory == 0) + { +#if defined (GETCWD_BROKEN) + the_current_working_directory = getcwd (0, PATH_MAX); +#else + the_current_working_directory = getcwd (0, 0); +#endif + if (the_current_working_directory == 0) + { + fprintf (stderr, _("%s: error retrieving current directory: %s: %s\n"), + (for_whom && *for_whom) ? for_whom : get_name_for_error (), + _(bash_getcwd_errstr), strerror (errno)); + return (char *)NULL; + } + } + + return (savestring (the_current_working_directory)); +} + +/* Make NAME our internal idea of the current working directory. */ +void +set_working_directory (name) + char *name; +{ + FREE (the_current_working_directory); + the_current_working_directory = savestring (name); +} + +/* **************************************************************** */ +/* */ +/* Job control support functions */ +/* */ +/* **************************************************************** */ + +#if defined (JOB_CONTROL) +int +get_job_by_name (name, flags) + const char *name; + int flags; +{ + register int i, wl, cl, match, job; + register PROCESS *p; + register JOB *j; + + job = NO_JOB; + wl = strlen (name); + for (i = js.j_jobslots - 1; i >= 0; i--) + { + j = get_job_by_jid (i); + if (j == 0 || ((flags & JM_STOPPED) && J_JOBSTATE(j) != JSTOPPED)) + continue; + + p = j->pipe; + do + { + if (flags & JM_EXACT) + { + cl = strlen (p->command); + match = STREQN (p->command, name, cl); + } + else if (flags & JM_SUBSTRING) + match = strcasestr (p->command, name) != (char *)0; + else + match = STREQN (p->command, name, wl); + + if (match == 0) + { + p = p->next; + continue; + } + else if (flags & JM_FIRSTMATCH) + return i; /* return first match */ + else if (job != NO_JOB) + { + if (this_shell_builtin) + builtin_error (_("%s: ambiguous job spec"), name); + else + internal_error (_("%s: ambiguous job spec"), name); + return (DUP_JOB); + } + else + job = i; + } + while (p != j->pipe); + } + + return (job); +} + +/* Return the job spec found in LIST. */ +int +get_job_spec (list) + WORD_LIST *list; +{ + register char *word; + int job, jflags; + + if (list == 0) + return (js.j_current); + + word = list->word->word; + + if (*word == '\0') + return (NO_JOB); + + if (*word == '%') + word++; + + if (DIGIT (*word) && all_digits (word)) + { + job = atoi (word); + return (job > js.j_jobslots ? NO_JOB : job - 1); + } + + jflags = 0; + switch (*word) + { + case 0: + case '%': + case '+': + return (js.j_current); + + case '-': + return (js.j_previous); + + case '?': /* Substring search requested. */ + jflags |= JM_SUBSTRING; + word++; + /* FALLTHROUGH */ + + default: + return get_job_by_name (word, jflags); + } +} +#endif /* JOB_CONTROL */ + +/* + * NOTE: `kill' calls this function with forcecols == 0 + */ +int +display_signal_list (list, forcecols) + WORD_LIST *list; + int forcecols; +{ + register int i, column; + char *name; + int result, signum, dflags; + intmax_t lsignum; + + result = EXECUTION_SUCCESS; + if (!list) + { + for (i = 1, column = 0; i < NSIG; i++) + { + name = signal_name (i); + if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) + continue; + + if (posixly_correct && !forcecols) + { + /* This is for the kill builtin. POSIX.2 says the signal names + are displayed without the `SIG' prefix. */ + if (STREQN (name, "SIG", 3)) + name += 3; + printf ("%s%s", name, (i == NSIG - 1) ? "" : " "); + } + else + { + printf ("%2d) %s", i, name); + + if (++column < 5) + printf ("\t"); + else + { + printf ("\n"); + column = 0; + } + } + } + + if ((posixly_correct && !forcecols) || column != 0) + printf ("\n"); + return result; + } + + /* List individual signal names or numbers. */ + while (list) + { + if (legal_number (list->word->word, &lsignum)) + { + /* This is specified by Posix.2 so that exit statuses can be + mapped into signal numbers. */ + if (lsignum > 128) + lsignum -= 128; + if (lsignum < 0 || lsignum >= NSIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + list = list->next; + continue; + } + + signum = lsignum; + name = signal_name (signum); + if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) + { + list = list->next; + continue; + } +#if defined (JOB_CONTROL) + /* POSIX.2 says that `kill -l signum' prints the signal name without + the `SIG' prefix. */ + printf ("%s\n", (this_shell_builtin == kill_builtin) ? name + 3 : name); +#else + printf ("%s\n", name); +#endif + } + else + { + dflags = DSIG_NOCASE; + if (posixly_correct == 0 || this_shell_builtin != kill_builtin) + dflags |= DSIG_SIGPREFIX; + signum = decode_signal (list->word->word, dflags); + if (signum == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + list = list->next; + continue; + } + printf ("%d\n", signum); + } + list = list->next; + } + return (result); +} + +/* **************************************************************** */ +/* */ +/* Finding builtin commands and their functions */ +/* */ +/* **************************************************************** */ + +/* Perform a binary search and return the address of the builtin function + whose name is NAME. If the function couldn't be found, or the builtin + is disabled or has no function associated with it, return NULL. + Return the address of the builtin. + DISABLED_OKAY means find it even if the builtin is disabled. */ +struct builtin * +builtin_address_internal (name, disabled_okay) + char *name; + int disabled_okay; +{ + int hi, lo, mid, j; + + hi = num_shell_builtins - 1; + lo = 0; + + while (lo <= hi) + { + mid = (lo + hi) / 2; + + j = shell_builtins[mid].name[0] - name[0]; + + if (j == 0) + j = strcmp (shell_builtins[mid].name, name); + + if (j == 0) + { + /* It must have a function pointer. It must be enabled, or we + must have explicitly allowed disabled functions to be found, + and it must not have been deleted. */ + if (shell_builtins[mid].function && + ((shell_builtins[mid].flags & BUILTIN_DELETED) == 0) && + ((shell_builtins[mid].flags & BUILTIN_ENABLED) || disabled_okay)) + return (&shell_builtins[mid]); + else + return ((struct builtin *)NULL); + } + if (j > 0) + hi = mid - 1; + else + lo = mid + 1; + } + return ((struct builtin *)NULL); +} + +/* Return the pointer to the function implementing builtin command NAME. */ +sh_builtin_func_t * +find_shell_builtin (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 0); + return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); +} + +/* Return the address of builtin with NAME, whether it is enabled or not. */ +sh_builtin_func_t * +builtin_address (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 1); + return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); +} + +/* Return the function implementing the builtin NAME, but only if it is a + POSIX.2 special builtin. */ +sh_builtin_func_t * +find_special_builtin (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 0); + return ((current_builtin && (current_builtin->flags & SPECIAL_BUILTIN)) ? + current_builtin->function : + (sh_builtin_func_t *)NULL); +} + +static int +shell_builtin_compare (sbp1, sbp2) + struct builtin *sbp1, *sbp2; +{ + int result; + + if ((result = sbp1->name[0] - sbp2->name[0]) == 0) + result = strcmp (sbp1->name, sbp2->name); + + return (result); +} + +/* Sort the table of shell builtins so that the binary search will work + in find_shell_builtin. */ +void +initialize_shell_builtins () +{ + qsort (shell_builtins, num_shell_builtins, sizeof (struct builtin), + (QSFUNC *)shell_builtin_compare); +} + +#if !defined (HELP_BUILTIN) +void +builtin_help () +{ + printf ("%s: %s\n", this_command_name, _("help not available in this version")); +} +#endif diff --git a/builtins/complete.def b/builtins/complete.def index 4d18c138..57d45f1b 100644 --- a/builtins/complete.def +++ b/builtins/complete.def @@ -33,11 +33,11 @@ allows them to be reused as input. Options: -p print existing completion specifications in a reusable format -r remove a completion specification for each NAME, or, if no - NAMEs are supplied, all completion specifications + NAMEs are supplied, all completion specifications -D apply the completions and actions as the default for commands - without any specific completion defined + without any specific completion defined -E apply the completions and actions to "empty" commands -- - completion attempted on a blank line + completion attempted on a blank line When completion is attempted, the actions are applied in the order the uppercase-letter options are listed above. The -D option takes diff --git a/builtins/declare.def b/builtins/declare.def index fbc03405..bdff891d 100644 --- a/builtins/declare.def +++ b/builtins/declare.def @@ -31,9 +31,9 @@ display the attributes and values of all variables. Options: -f restrict action or display to function names and definitions -F restrict display to function names only (plus line number and - source file when debugging) + source file when debugging) -g create global variables when used in a shell function; otherwise - ignored + ignored -p display the attributes and value of each NAME Options which set attributes: diff --git a/builtins/echo.def b/builtins/echo.def index f4738264..d001b607 100644 --- a/builtins/echo.def +++ b/builtins/echo.def @@ -59,9 +59,9 @@ Options: \v vertical tab \\ backslash \0nnn the character whose ASCII code is NNN (octal). NNN can be - 0 to 3 octal digits + 0 to 3 octal digits \xHH the eight-bit character whose value is HH (hexadecimal). HH - can be one or two hex digits + can be one or two hex digits Exit Status: Returns success unless a write error occurs. diff --git a/builtins/exec.def b/builtins/exec.def index 9cf76d53..40dbd3ac 100644 --- a/builtins/exec.def +++ b/builtins/exec.def @@ -31,8 +31,8 @@ any redirections take effect in the current shell. Options: -a name pass NAME as the zeroth argument to COMMAND - -c execute COMMAND with an empty environment - -l place a dash in the zeroth argument to COMMAND + -c execute COMMAND with an empty environment + -l place a dash in the zeroth argument to COMMAND If the command cannot be executed, a non-interactive shell exits, unless the shell option `execfail' is set. diff --git a/builtins/hash.def b/builtins/hash.def index a74bbfae..092208b2 100644 --- a/builtins/hash.def +++ b/builtins/hash.def @@ -29,15 +29,15 @@ Determine and remember the full pathname of each command NAME. If no arguments are given, information about remembered commands is displayed. Options: - -d forget the remembered location of each NAME - -l display in a format that may be reused as input + -d forget the remembered location of each NAME + -l display in a format that may be reused as input -p pathname use PATHNAME as the full pathname of NAME - -r forget all remembered locations - -t print the remembered location of each NAME, preceding + -r forget all remembered locations + -t print the remembered location of each NAME, preceding each location with the corresponding NAME if multiple NAMEs are given Arguments: - NAME Each NAME is searched for in $PATH and added to the list + NAME Each NAME is searched for in $PATH and added to the list of remembered commands. Exit Status: diff --git a/builtins/help.def b/builtins/help.def index ef20c7b4..4ced25cf 100644 --- a/builtins/help.def +++ b/builtins/help.def @@ -34,7 +34,7 @@ Options: -d output short description for each topic -m display usage in pseudo-manpage format -s output only a short usage synopsis for each topic matching - PATTERN + PATTERN Arguments: PATTERN Pattern specifiying a help topic diff --git a/builtins/history.def b/builtins/history.def index 1ac8d3ef..b16251f3 100644 --- a/builtins/history.def +++ b/builtins/history.def @@ -36,18 +36,18 @@ Options: -a append history lines from this session to the history file -n read all history lines not already read from the history file -r read the history file and append the contents to the history - list + list -w write the current history to the history file - and append them to the history list + and append them to the history list -p perform history expansion on each ARG and display the result - without storing it in the history list + without storing it in the history list -s append the ARGs to the history list as a single entry If FILENAME is given, it is used as the history file. Otherwise, -if $HISTFILE has a value, that is used, else ~/.bash_history. +if HISTFILE has a value, that is used, else ~/.bash_history. -If the $HISTTIMEFORMAT variable is set and not null, its value is used +If the HISTTIMEFORMAT variable is set and not null, its value is used as a format string for strftime(3) to print the time stamp associated with each displayed history entry. No time stamps are printed otherwise. diff --git a/builtins/jobs.def b/builtins/jobs.def index 47da58e4..e4749632 100644 --- a/builtins/jobs.def +++ b/builtins/jobs.def @@ -32,7 +32,7 @@ Without options, the status of all active jobs is displayed. Options: -l lists process IDs in addition to the normal information -n lists only processes that have changed status since the last - notification + notification -p lists process IDs only -r restrict output to running jobs -s restrict output to stopped jobs @@ -221,7 +221,7 @@ any JOBSPECs, the shell uses its notion of the current job. Options: -a remove all jobs if JOBSPEC is not supplied -h mark each JOBSPEC so that SIGHUP is not sent to the job if the - shell receives a SIGHUP + shell receives a SIGHUP -r remove only running jobs Exit Status: diff --git a/builtins/kill.def b/builtins/kill.def index fdcffc61..d56fed69 100644 --- a/builtins/kill.def +++ b/builtins/kill.def @@ -33,7 +33,7 @@ Options: -s sig SIG is a signal name -n sig SIG is a signal number -l list the signal names; if arguments follow `-l' they are - assumed to be signal numbers for which names should be listed + assumed to be signal numbers for which names should be listed Kill is a shell builtin for two reasons: it allows job IDs to be used instead of process IDs, and allows processes to be killed if the limit diff --git a/builtins/mapfile.def b/builtins/mapfile.def index 51e37c63..6eae23f8 100644 --- a/builtins/mapfile.def +++ b/builtins/mapfile.def @@ -31,16 +31,17 @@ from file descriptor FD if the -u option is supplied. The variable MAPFILE is the default ARRAY. Options: - -n count Copy at most COUNT lines. If COUNT is 0, all lines are copied. - -O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0. - -s count Discard the first COUNT lines read. - -t Remove a trailing newline from each line read. - -u fd Read lines from file descriptor FD instead of the standard input. - -C callback Evaluate CALLBACK each time QUANTUM lines are read. - -c quantum Specify the number of lines read between each call to CALLBACK. + -n count Copy at most COUNT lines. If COUNT is 0, all lines are copied + -O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0 + -s count Discard the first COUNT lines read + -t Remove a trailing newline from each line read + -u fd Read lines from file descriptor FD instead of the standard input + -C callback Evaluate CALLBACK each time QUANTUM lines are read + -c quantum Specify the number of lines read between each call to + CALLBACK Arguments: - ARRAY Array variable name to use for file data. + ARRAY Array variable name to use for file data If -C is supplied without -c, the default quantum is 5000. When CALLBACK is evaluated, it is supplied the index of the next array diff --git a/builtins/printf.def b/builtins/printf.def index a3899bef..b01958ec 100644 --- a/builtins/printf.def +++ b/builtins/printf.def @@ -40,8 +40,8 @@ printf interprets: %b expand backslash escape sequences in the corresponding argument %q quote the argument in a way that can be reused as shell input - %(fmt)T output the date-time string resulting from using FMT as a format - string for strftime(3) + %(fmt)T output the date-time string resulting from using FMT as a format + string for strftime(3) The format is re-used as necessary to consume all of the arguments. If there are fewer arguments than the format requires, extra format diff --git a/builtins/pushd.def b/builtins/pushd.def index 1d435fa5..da8e1820 100644 --- a/builtins/pushd.def +++ b/builtins/pushd.def @@ -32,19 +32,19 @@ directory. With no arguments, exchanges the top two directories. Options: -n Suppresses the normal change of directory when adding - directories to the stack, so only the stack is manipulated. + directories to the stack, so only the stack is manipulated. Arguments: +N Rotates the stack so that the Nth directory (counting - from the left of the list shown by `dirs', starting with - zero) is at the top. + from the left of the list shown by `dirs', starting with + zero) is at the top. -N Rotates the stack so that the Nth directory (counting - from the right of the list shown by `dirs', starting with - zero) is at the top. + from the right of the list shown by `dirs', starting with + zero) is at the top. dir Adds DIR to the directory stack at the top, making it the - new current working directory. + new current working directory. The `dirs' builtin displays the directory stack. @@ -64,16 +64,16 @@ the top directory from the stack, and changes to the new top directory. Options: -n Suppresses the normal change of directory when removing - directories from the stack, so only the stack is manipulated. + directories from the stack, so only the stack is manipulated. Arguments: +N Removes the Nth entry counting from the left of the list - shown by `dirs', starting with zero. For example: `popd +0' - removes the first directory, `popd +1' the second. + shown by `dirs', starting with zero. For example: `popd +0' + removes the first directory, `popd +1' the second. -N Removes the Nth entry counting from the right of the list - shown by `dirs', starting with zero. For example: `popd -0' - removes the last directory, `popd -1' the next to last. + shown by `dirs', starting with zero. For example: `popd -0' + removes the last directory, `popd -1' the next to last. The `dirs' builtin displays the directory stack. @@ -95,17 +95,19 @@ back up through the list with the `popd' command. Options: -c clear the directory stack by deleting all of the elements -l do not print tilde-prefixed versions of directories relative - to your home directory + to your home directory -p print the directory stack with one entry per line -v print the directory stack with one entry per line prefixed - with its position in the stack + with its position in the stack Arguments: - +N Displays the Nth entry counting from the left of the list shown by - dirs when invoked without options, starting with zero. + +N Displays the Nth entry counting from the left of the list + shown by dirs when invoked without options, starting with + zero. - -N Displays the Nth entry counting from the right of the list shown by - dirs when invoked without options, starting with zero. + -N Displays the Nth entry counting from the right of the list + shown by dirs when invoked without options, starting with + zero. Exit Status: Returns success unless an invalid option is supplied or an error occurs. diff --git a/builtins/read.def b/builtins/read.def index 83bcddf4..a878387c 100644 --- a/builtins/read.def +++ b/builtins/read.def @@ -39,25 +39,27 @@ Options: variable ARRAY, starting at zero -d delim continue until the first character of DELIM is read, rather than newline - -e use Readline to obtain the line in an interactive shell - -i text Use TEXT as the initial text for Readline + -e use Readline to obtain the line in an interactive shell + -i text use TEXT as the initial text for Readline -n nchars return after reading NCHARS characters rather than waiting - for a newline, but honor a delimiter if fewer than NCHARS - characters are read before the delimiter + for a newline, but honor a delimiter if fewer than + NCHARS characters are read before the delimiter -N nchars return only after reading exactly NCHARS characters, unless - EOF is encountered or read times out, ignoring any delimiter + EOF is encountered or read times out, ignoring any + delimiter -p prompt output the string PROMPT without a trailing newline before attempting to read - -r do not allow backslashes to escape any characters - -s do not echo input coming from a terminal - -t timeout time out and return failure if a complete line of input is - not read within TIMEOUT seconds. The value of the TMOUT - variable is the default timeout. TIMEOUT may be a - fractional number. If TIMEOUT is 0, read returns immediately, - without trying to read any data, returning success only if - input is available on the specified file descriptor. The - exit status is greater than 128 if the timeout is exceeded - -u fd read from file descriptor FD instead of the standard input + -r do not allow backslashes to escape any characters + -s do not echo input coming from a terminal + -t timeout time out and return failure if a complete line of + input is not read within TIMEOUT seconds. The value of the + TMOUT variable is the default timeout. TIMEOUT may be a + fractional number. If TIMEOUT is 0, read returns + immediately, without trying to read any data, returning + success only if input is available on the specified + file descriptor. The exit status is greater than 128 + if the timeout is exceeded + -u fd read from file descriptor FD instead of the standard input Exit Status: The return code is zero, unless end-of-file is encountered, read times out diff --git a/builtins/set.def b/builtins/set.def index aef82e1a..c2934f54 100644 --- a/builtins/set.def +++ b/builtins/set.def @@ -771,7 +771,7 @@ Options: -f treat each NAME as a shell function -v treat each NAME as a shell variable -n treat each NAME as a name reference and unset the variable itself - rather than the variable it references + rather than the variable it references Without options, unset first tries to unset a variable, and if that fails, tries to unset a function. diff --git a/builtins/setattr.def b/builtins/setattr.def index c5fe32ee..178a0b33 100644 --- a/builtins/setattr.def +++ b/builtins/setattr.def @@ -94,8 +94,8 @@ Options: -a refer to indexed array variables -A refer to associative array variables -f refer to shell functions - -p display a list of all readonly variables or functions, depending on - whether or not the -f option is given + -p display a list of all readonly variables or functions, + depending on whether or not the -f option is given An argument of `--' disables further option processing. diff --git a/builtins/test.def b/builtins/test.def index f1444000..249b2c2a 100644 --- a/builtins/test.def +++ b/builtins/test.def @@ -83,8 +83,9 @@ String operators: Other operators: -o OPTION True if the shell option OPTION is enabled. - -v VAR True if the shell variable VAR is set - -R VAR True if the shell variable VAR is set and is a name reference. + -v VAR True if the shell variable VAR is set. + -R VAR True if the shell variable VAR is set and is a name + reference. ! EXPR True if expr is false. EXPR1 -a EXPR2 True if both expr1 AND expr2 are true. EXPR1 -o EXPR2 True if either expr1 OR expr2 is true. diff --git a/builtins/type.def b/builtins/type.def index 6cfc72c4..67191b73 100644 --- a/builtins/type.def +++ b/builtins/type.def @@ -30,18 +30,18 @@ command name. Options: -a display all locations containing an executable named NAME; - includes aliases, builtins, and functions, if and only if - the `-p' option is not also used + includes aliases, builtins, and functions, if and only if + the `-p' option is not also used -f suppress shell function lookup -P force a PATH search for each NAME, even if it is an alias, - builtin, or function, and returns the name of the disk file - that would be executed + builtin, or function, and returns the name of the disk file + that would be executed -p returns either the name of the disk file that would be executed, - or nothing if `type -t NAME' would not return `file'. + or nothing if `type -t NAME' would not return `file' -t output a single word which is one of `alias', `keyword', - `function', `builtin', `file' or `', if NAME is an alias, shell - reserved word, shell function, shell builtin, disk file, or not - found, respectively + `function', `builtin', `file' or `', if NAME is an alias, + shell reserved word, shell function, shell builtin, disk file, + or not found, respectively Arguments: NAME Command name to be interpreted. diff --git a/builtins/ulimit.def b/builtins/ulimit.def index 08662ad4..263ddd09 100644 --- a/builtins/ulimit.def +++ b/builtins/ulimit.def @@ -52,7 +52,7 @@ Options: -v the size of virtual memory -x the maximum number of file locks -P the maximum number of pseudoterminals - -T the maximum number of threads + -T the maximum number of threads Not all options are available on all platforms. diff --git a/builtins/umask.def b/builtins/umask.def index d9aa0418..76f2bf22 100644 --- a/builtins/umask.def +++ b/builtins/umask.def @@ -145,8 +145,12 @@ umask_builtin (list) /* Print the umask in a symbolic form. In the output, a letter is printed if the corresponding bit is clear in the umask. */ static void +#if defined (__STDC__) +print_symbolic_umask (mode_t um) +#else print_symbolic_umask (um) mode_t um; +#endif { char ubits[4], gbits[4], obits[4]; /* u=rwx,g=rwx,o=rwx */ int i; diff --git a/builtins/umask.def~ b/builtins/umask.def~ new file mode 100644 index 00000000..d9aa0418 --- /dev/null +++ b/builtins/umask.def~ @@ -0,0 +1,312 @@ +This file is umask.def, from which is created umask.c. +It implements the builtin "umask" in Bash. + +Copyright (C) 1987-2009 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see <http://www.gnu.org/licenses/>. + +$PRODUCES umask.c + +$BUILTIN umask +$FUNCTION umask_builtin +$SHORT_DOC umask [-p] [-S] [mode] +Display or set file mode mask. + +Sets the user file-creation mask to MODE. If MODE is omitted, prints +the current value of the mask. + +If MODE begins with a digit, it is interpreted as an octal number; +otherwise it is a symbolic mode string like that accepted by chmod(1). + +Options: + -p if MODE is omitted, output in a form that may be reused as input + -S makes the output symbolic; otherwise an octal number is output + +Exit Status: +Returns success unless MODE is invalid or an invalid option is given. +$END + +#include <config.h> + +#include "../bashtypes.h" +#include "filecntl.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif + +#if defined (HAVE_UNISTD_H) +#include <unistd.h> +#endif + +#include <stdio.h> +#include <chartypes.h> + +#include "../bashintl.h" + +#include "../shell.h" +#include "posixstat.h" +#include "common.h" +#include "bashgetopt.h" + +/* **************************************************************** */ +/* */ +/* UMASK Builtin and Helpers */ +/* */ +/* **************************************************************** */ + +static void print_symbolic_umask __P((mode_t)); +static int symbolic_umask __P((WORD_LIST *)); + +/* Set or display the mask used by the system when creating files. Flag + of -S means display the umask in a symbolic mode. */ +int +umask_builtin (list) + WORD_LIST *list; +{ + int print_symbolically, opt, umask_value, pflag; + mode_t umask_arg; + + print_symbolically = pflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "Sp")) != -1) + { + switch (opt) + { + case 'S': + print_symbolically++; + break; + case 'p': + pflag++; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (list) + { + if (DIGIT (*list->word->word)) + { + umask_value = read_octal (list->word->word); + + /* Note that other shells just let you set the umask to zero + by specifying a number out of range. This is a problem + with those shells. We don't change the umask if the input + is lousy. */ + if (umask_value == -1) + { + sh_erange (list->word->word, _("octal number")); + return (EXECUTION_FAILURE); + } + } + else + { + umask_value = symbolic_umask (list); + if (umask_value == -1) + return (EXECUTION_FAILURE); + } + umask_arg = (mode_t)umask_value; + umask (umask_arg); + if (print_symbolically) + print_symbolic_umask (umask_arg); + } + else /* Display the UMASK for this user. */ + { + umask_arg = umask (022); + umask (umask_arg); + + if (pflag) + printf ("umask%s ", (print_symbolically ? " -S" : "")); + if (print_symbolically) + print_symbolic_umask (umask_arg); + else + printf ("%04lo\n", (unsigned long)umask_arg); + } + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +/* Print the umask in a symbolic form. In the output, a letter is + printed if the corresponding bit is clear in the umask. */ +static void +print_symbolic_umask (um) + mode_t um; +{ + char ubits[4], gbits[4], obits[4]; /* u=rwx,g=rwx,o=rwx */ + int i; + + i = 0; + if ((um & S_IRUSR) == 0) + ubits[i++] = 'r'; + if ((um & S_IWUSR) == 0) + ubits[i++] = 'w'; + if ((um & S_IXUSR) == 0) + ubits[i++] = 'x'; + ubits[i] = '\0'; + + i = 0; + if ((um & S_IRGRP) == 0) + gbits[i++] = 'r'; + if ((um & S_IWGRP) == 0) + gbits[i++] = 'w'; + if ((um & S_IXGRP) == 0) + gbits[i++] = 'x'; + gbits[i] = '\0'; + + i = 0; + if ((um & S_IROTH) == 0) + obits[i++] = 'r'; + if ((um & S_IWOTH) == 0) + obits[i++] = 'w'; + if ((um & S_IXOTH) == 0) + obits[i++] = 'x'; + obits[i] = '\0'; + + printf ("u=%s,g=%s,o=%s\n", ubits, gbits, obits); +} + +int +parse_symbolic_mode (mode, initial_bits) + char *mode; + int initial_bits; +{ + int who, op, perm, bits, c; + char *s; + + for (s = mode, bits = initial_bits;;) + { + who = op = perm = 0; + + /* Parse the `who' portion of the symbolic mode clause. */ + while (member (*s, "agou")) + { + switch (c = *s++) + { + case 'u': + who |= S_IRWXU; + continue; + case 'g': + who |= S_IRWXG; + continue; + case 'o': + who |= S_IRWXO; + continue; + case 'a': + who |= S_IRWXU | S_IRWXG | S_IRWXO; + continue; + default: + break; + } + } + + /* The operation is now sitting in *s. */ + op = *s++; + switch (op) + { + case '+': + case '-': + case '=': + break; + default: + builtin_error (_("`%c': invalid symbolic mode operator"), op); + return (-1); + } + + /* Parse out the `perm' section of the symbolic mode clause. */ + while (member (*s, "rwx")) + { + c = *s++; + + switch (c) + { + case 'r': + perm |= S_IRUGO; + break; + case 'w': + perm |= S_IWUGO; + break; + case 'x': + perm |= S_IXUGO; + break; + } + } + + /* Now perform the operation or return an error for a + bad permission string. */ + if (!*s || *s == ',') + { + if (who) + perm &= who; + + switch (op) + { + case '+': + bits |= perm; + break; + case '-': + bits &= ~perm; + break; + case '=': + if (who == 0) + who = S_IRWXU | S_IRWXG | S_IRWXO; + bits &= ~who; + bits |= perm; + break; + + /* No other values are possible. */ + } + + if (*s == '\0') + break; + else + s++; /* skip past ',' */ + } + else + { + builtin_error (_("`%c': invalid symbolic mode character"), *s); + return (-1); + } + } + + return (bits); +} + +/* Set the umask from a symbolic mode string similar to that accepted + by chmod. If the -S argument is given, then print the umask in a + symbolic form. */ +static int +symbolic_umask (list) + WORD_LIST *list; +{ + int um, bits; + + /* Get the initial umask. Don't change it yet. */ + um = umask (022); + umask (um); + + /* All work is done with the complement of the umask -- it's + more intuitive and easier to deal with. It is complemented + again before being returned. */ + bits = parse_symbolic_mode (list->word->word, ~um & 0777); + if (bits == -1) + return (-1); + + um = ~bits & 0777; + return (um); +} diff --git a/builtins/wait.def b/builtins/wait.def index 4f197144..77c8b784 100644 --- a/builtins/wait.def +++ b/builtins/wait.def @@ -1,7 +1,7 @@ This file is wait.def, from which is created wait.c. It implements the builtin "wait" in Bash. -Copyright (C) 1987-2013 Free Software Foundation, Inc. +Copyright (C) 1987-2015 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -131,7 +131,7 @@ wait_builtin (list) We handle SIGINT here; it's the only one that needs to be treated specially (I think), since it's handled specially in {no,}jobs.c. */ - code = setjmp (wait_intr_buf); + code = setjmp_sigs (wait_intr_buf); if (code) { last_command_exit_signal = wait_signal_received; diff --git a/builtins/wait.def~ b/builtins/wait.def~ new file mode 100644 index 00000000..2de7bfb6 --- /dev/null +++ b/builtins/wait.def~ @@ -0,0 +1,218 @@ +This file is wait.def, from which is created wait.c. +It implements the builtin "wait" in Bash. + +Copyright (C) 1987-2013 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see <http://www.gnu.org/licenses/>. + +$BUILTIN wait +$FUNCTION wait_builtin +$DEPENDS_ON JOB_CONTROL +$PRODUCES wait.c +$SHORT_DOC wait [-n] [id ...] +Wait for job completion and return exit status. + +Waits for each process identified by an ID, which may be a process ID or a +job specification, and reports its termination status. If ID is not +given, waits for all currently active child processes, and the return +status is zero. If ID is a a job specification, waits for all processes +in that job's pipeline. + +If the -n option is supplied, waits for the next job to terminate and +returns its exit status. + +Exit Status: +Returns the status of the last ID; fails if ID is invalid or an invalid +option is given. +$END + +$BUILTIN wait +$FUNCTION wait_builtin +$DEPENDS_ON !JOB_CONTROL +$SHORT_DOC wait [pid ...] +Wait for process completion and return exit status. + +Waits for each process specified by a PID and reports its termination status. +If PID is not given, waits for all currently active child processes, +and the return status is zero. PID must be a process ID. + +Exit Status: +Returns the status of the last PID; fails if PID is invalid or an invalid +option is given. +$END + +#include <config.h> + +#include "../bashtypes.h" +#include <signal.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include <chartypes.h> + +#include "../bashansi.h" + +#include "../shell.h" +#include "../jobs.h" +#include "common.h" +#include "bashgetopt.h" + +extern int wait_signal_received; +extern int last_command_exit_signal; + +procenv_t wait_intr_buf; + +/* Wait for the pid in LIST to stop or die. If no arguments are given, then + wait for all of the active background processes of the shell and return + 0. If a list of pids or job specs are given, return the exit status of + the last one waited for. */ + +#define WAIT_RETURN(s) \ + do \ + { \ + interrupt_immediately = old_interrupt_immediately;\ + wait_signal_received = 0; \ + return (s);\ + } \ + while (0) + +int +wait_builtin (list) + WORD_LIST *list; +{ + int status, code, opt, nflag; + volatile int old_interrupt_immediately; + + USE_VAR(list); + + nflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "n")) != -1) + { + switch (opt) + { +#if defined (JOB_CONTROL) + case 'n': + nflag = 1; + break; +#endif + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + old_interrupt_immediately = interrupt_immediately; +#if 0 + interrupt_immediately++; +#endif + + /* POSIX.2 says: When the shell is waiting (by means of the wait utility) + for asynchronous commands to complete, the reception of a signal for + which a trap has been set shall cause the wait utility to return + immediately with an exit status greater than 128, after which the trap + associated with the signal shall be taken. + + We handle SIGINT here; it's the only one that needs to be treated + specially (I think), since it's handled specially in {no,}jobs.c. */ + code = setjmp_sigs (wait_intr_buf); + if (code) + { + last_command_exit_signal = wait_signal_received; + status = 128 + wait_signal_received; + WAIT_RETURN (status); + } + + /* We support jobs or pids. + wait <pid-or-job> [pid-or-job ...] */ + +#if defined (JOB_CONTROL) + if (nflag) + { + status = wait_for_any_job (); + if (status < 0) + status = 127; + WAIT_RETURN (status); + } +#endif + + /* But wait without any arguments means to wait for all of the shell's + currently active background processes. */ + if (list == 0) + { + wait_for_background_pids (); + WAIT_RETURN (EXECUTION_SUCCESS); + } + + status = EXECUTION_SUCCESS; + while (list) + { + pid_t pid; + char *w; + intmax_t pid_value; + + w = list->word->word; + if (DIGIT (*w)) + { + if (legal_number (w, &pid_value) && pid_value == (pid_t)pid_value) + { + pid = (pid_t)pid_value; + status = wait_for_single_pid (pid); + } + else + { + sh_badpid (w); + WAIT_RETURN (EXECUTION_FAILURE); + } + } +#if defined (JOB_CONTROL) + else if (*w && *w == '%') + /* Must be a job spec. Check it out. */ + { + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + job = get_job_spec (list); + + if (INVALID_JOB (job)) + { + if (job != DUP_JOB) + sh_badjob (list->word->word); + UNBLOCK_CHILD (oset); + status = 127; /* As per Posix.2, section 4.70.2 */ + list = list->next; + continue; + } + + /* Job spec used. Wait for the last pid in the pipeline. */ + UNBLOCK_CHILD (oset); + status = wait_for_job (job); + } +#endif /* JOB_CONTROL */ + else + { + sh_badpid (w); + status = EXECUTION_FAILURE; + } + list = list->next; + } + + WAIT_RETURN (status); +} diff --git a/config.h.in b/config.h.in index 92ebcb3c..cb2c76bb 100644 --- a/config.h.in +++ b/config.h.in @@ -546,7 +546,6 @@ #undef UNUSABLE_RT_SIGNALS - /* Presence of system and C library functions. */ /* Define if you have the asprintf function. */ @@ -944,6 +943,9 @@ /* Define if you have the <locale.h> header file. */ #undef HAVE_LOCALE_H +/* Define if you have the <mbstr.h> header file. */ +#undef HAVE_MBSTR_H + /* Define if you have the <ndir.h> header file. */ #undef HAVE_NDIR_H @@ -11079,6 +11079,19 @@ fi done +for ac_header in mbstr.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "mbstr.h" "ac_cv_header_mbstr_h" "$ac_includes_default" +if test "x$ac_cv_header_mbstr_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_MBSTR_H 1 +_ACEOF + +fi + +done + + ac_fn_c_check_func "$LINENO" "mbrlen" "ac_cv_func_mbrlen" if test "x$ac_cv_func_mbrlen" = xyes; then : $as_echo "#define HAVE_MBRLEN 1" >>confdefs.h diff --git a/execute_cmd.c b/execute_cmd.c index 929e1437..a65c5c24 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -100,6 +100,10 @@ extern int errno; # include "bashhist.h" #endif +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif + #if defined (ARRAY_VARS) struct func_array_state { @@ -42,6 +42,10 @@ #include "test.h" #include "trap.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif + #include <tilde/tilde.h> #if !defined (errno) diff --git a/include/chartypes.h b/include/chartypes.h index c7080181..0333f944 100644 --- a/include/chartypes.h +++ b/include/chartypes.h @@ -46,11 +46,11 @@ #endif #if !defined (isprint) && !defined (HAVE_ISPRINT) -# define isprint(c) (isalpha(c) || isdigit(c) || ispunct(c)) +# define isprint(c) (isalpha((unsigned char)c) || isdigit((unsigned char)c) || ispunct((unsigned char)c)) #endif #if defined (isblank) || defined (HAVE_ISBLANK) -# define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank (c)) +# define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank ((unsigned char)c)) #else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') #endif @@ -58,7 +58,7 @@ #if defined (isgraph) || defined (HAVE_ISGRAPH) # define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isgraph (c)) #else -# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint (c) && !isspace (c)) +# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c) && !isspace ((unsigned char)c)) #endif #if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) @@ -67,16 +67,16 @@ #undef ISPRINT -#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) -#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) -#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) -#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) -#define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl (c)) -#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) -#define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct (c)) -#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) -#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) -#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit ((unsigned char)c)) +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum ((unsigned char)c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha ((unsigned char)c)) +#define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl ((unsigned char)c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower ((unsigned char)c)) +#define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct ((unsigned char)c)) +#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace ((unsigned char)c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper ((unsigned char)c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit ((unsigned char)c)) #define ISLETTER(c) (ISALPHA(c)) diff --git a/include/chartypes.h~ b/include/chartypes.h~ new file mode 100644 index 00000000..66553f64 --- /dev/null +++ b/include/chartypes.h~ @@ -0,0 +1,113 @@ +/* chartypes.h -- extend ctype.h */ + +/* Copyright (C) 2001 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SH_CHARTYPES_H +#define _SH_CHARTYPES_H + +#include <ctype.h> + +/* Jim Meyering writes: + + "... Some ctype macros are valid only for character codes that + isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when + using /bin/cc or gcc but without giving an ansi option). So, all + ctype uses should be through macros like ISPRINT... If + STDC_HEADERS is defined, then autoconf has verified that the ctype + macros don't need to be guarded with references to isascii. ... + Defining IN_CTYPE_DOMAIN to 1 should let any compiler worth its salt + eliminate the && through constant folding." + Solaris defines some of these symbols so we must undefine them first. */ + +#if STDC_HEADERS || (!defined (isascii) && !HAVE_ISASCII) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) isascii(c) +#endif + +#if !defined (isspace) && !defined (HAVE_ISSPACE) +# define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\f') +#endif + +#if !defined (isprint) && !defined (HAVE_ISPRINT) +# define isprint(c) (isalpha(c) || isdigit(c) || ispunct(c)) +#endif + +#if defined (isblank) || defined (HAVE_ISBLANK) +# define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank (c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#if defined (isgraph) || defined (HAVE_ISGRAPH) +# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isgraph (c)) +#else +# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint (c) && !isspace (c)) +#endif + +#if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) +# define isxdigit(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#endif + +#undef ISPRINT + +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit ((unsigned char)c)) +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum ((unsigned char)c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha ((unsigned char)c)) +#define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl ((unsigned char)c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower ((unsigned char)c)) +#define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct ((unsigned char)c)) +#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace ((unsigned char)c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper ((unsigned char)c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit ((unsigned char)c)) + +#define ISLETTER(c) (ISALPHA(c)) + +#define DIGIT(c) ((c) >= '0' && (c) <= '9') + +#define ISWORD(c) (ISLETTER(c) || DIGIT(c) || ((c) == '_')) + +#define HEXVALUE(c) \ + (((c) >= 'a' && (c) <= 'f') \ + ? (c)-'a'+10 \ + : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0') + +#ifndef ISOCTAL +# define ISOCTAL(c) ((c) >= '0' && (c) <= '7') +#endif +#define OCTVALUE(c) ((c) - '0') + +#define TODIGIT(c) ((c) - '0') +#define TOCHAR(c) ((c) + '0') + +#define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) +#define TOUPPER(c) (ISLOWER(c) ? toupper(c) : (c)) + +#ifndef TOCTRL + /* letter to control char -- ASCII. The TOUPPER is in there so \ce and + \cE will map to the same character in $'...' expansions. */ +# define TOCTRL(x) (TOUPPER(x) & 037) +#endif +#ifndef UNCTRL + /* control char to letter -- ASCII */ +# define UNCTRL(x) (TOUPPER(x) ^ 0x40) +#endif + +#endif /* _SH_CHARTYPES_H */ diff --git a/include/posixjmp.h b/include/posixjmp.h index 98cf7185..031c8fd8 100644 --- a/include/posixjmp.h +++ b/include/posixjmp.h @@ -1,6 +1,6 @@ /* posixjmp.h -- wrapper for setjmp.h with changes for POSIX systems. */ -/* Copyright (C) 1987,1991 Free Software Foundation, Inc. +/* Copyright (C) 1987,1991-2015 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -28,15 +28,15 @@ #if defined (HAVE_POSIX_SIGSETJMP) # define procenv_t sigjmp_buf # if !defined (__OPENNT) -# undef setjmp -# define setjmp(x) sigsetjmp((x), 1) # define setjmp_nosigs(x) sigsetjmp((x), 0) +# define setjmp_sigs(x) sigsetjmp((x), 1) # undef longjmp # define longjmp(x, n) siglongjmp((x), (n)) # endif /* !__OPENNT */ #else # define procenv_t jmp_buf # define setjmp_nosigs setjmp +# define setjmp_sigs setjmp #endif #endif /* _POSIXJMP_H_ */ diff --git a/include/posixjmp.h~ b/include/posixjmp.h~ new file mode 100644 index 00000000..8f8b45e7 --- /dev/null +++ b/include/posixjmp.h~ @@ -0,0 +1,42 @@ +/* posixjmp.h -- wrapper for setjmp.h with changes for POSIX systems. */ + +/* Copyright (C) 1987,1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _POSIXJMP_H_ +#define _POSIXJMP_H_ + +#include <setjmp.h> + +/* This *must* be included *after* config.h */ + +#if defined (HAVE_POSIX_SIGSETJMP) +# define procenv_t sigjmp_buf +# if !defined (__OPENNT) +# define setjmp_nosigs(x) sigsetjmp((x), 0) +# define setjmp_sigs(x) sigsetjmp((x), 1) +# undef longjmp +# define longjmp(x, n) siglongjmp((x), (n)) +# endif /* !__OPENNT */ +#else +# define procenv_t jmp_buf +# define setjmp_nosigs setjmp +# define setjmp_sigs setjmp +#endif + +#endif /* _POSIXJMP_H_ */ diff --git a/lib/readline/bind.c b/lib/readline/bind.c index 8de2067b..f52d9f36 100644 --- a/lib/readline/bind.c +++ b/lib/readline/bind.c @@ -542,7 +542,7 @@ rl_translate_keyseq (seq, array, len) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': i++; - for (temp = 2, c -= '0'; ISOCTAL (seq[i]) && temp--; i++) + for (temp = 2, c -= '0'; ISOCTAL ((unsigned char)seq[i]) && temp--; i++) c = (c * 8) + OCTVALUE (seq[i]); i--; /* auto-increment in for loop */ array[l++] = c & largest_char; diff --git a/lib/readline/bind.c~ b/lib/readline/bind.c~ new file mode 100644 index 00000000..8de2067b --- /dev/null +++ b/lib/readline/bind.c~ @@ -0,0 +1,2586 @@ +/* bind.c -- key binding and startup file support for the readline library. */ + +/* Copyright (C) 1987-2012 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "posixstat.h" + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +#if !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif /* !strchr && !__STDC__ */ + +/* Variables exported by this file. */ +Keymap rl_binding_keymap; + +static int _rl_skip_to_delim PARAMS((char *, int, int)); + +static char *_rl_read_file PARAMS((char *, size_t *)); +static void _rl_init_file_error PARAMS((const char *)); +static int _rl_read_init_file PARAMS((const char *, int)); +static int glean_key_from_name PARAMS((char *)); + +static int find_boolean_var PARAMS((const char *)); +static int find_string_var PARAMS((const char *)); + +static char *_rl_get_string_variable_value PARAMS((const char *)); +static int substring_member_of_array PARAMS((const char *, const char * const *)); + +static int currently_reading_init_file; + +/* used only in this file */ +static int _rl_prefer_visible_bell = 1; + +/* **************************************************************** */ +/* */ +/* Binding keys */ +/* */ +/* **************************************************************** */ + +/* rl_add_defun (char *name, rl_command_func_t *function, int key) + Add NAME to the list of named functions. Make FUNCTION be the function + that gets called. If KEY is not -1, then bind it. */ +int +rl_add_defun (name, function, key) + const char *name; + rl_command_func_t *function; + int key; +{ + if (key != -1) + rl_bind_key (key, function); + rl_add_funmap_entry (name, function); + return 0; +} + +/* Bind KEY to FUNCTION. Returns non-zero if KEY is out of range. */ +int +rl_bind_key (key, function) + int key; + rl_command_func_t *function; +{ + if (key < 0) + return (key); + + if (META_CHAR (key) && _rl_convert_meta_chars_to_ascii) + { + if (_rl_keymap[ESC].type == ISKMAP) + { + Keymap escmap; + + escmap = FUNCTION_TO_KEYMAP (_rl_keymap, ESC); + key = UNMETA (key); + escmap[key].type = ISFUNC; + escmap[key].function = function; + return (0); + } + return (key); + } + + _rl_keymap[key].type = ISFUNC; + _rl_keymap[key].function = function; + rl_binding_keymap = _rl_keymap; + return (0); +} + +/* Bind KEY to FUNCTION in MAP. Returns non-zero in case of invalid + KEY. */ +int +rl_bind_key_in_map (key, function, map) + int key; + rl_command_func_t *function; + Keymap map; +{ + int result; + Keymap oldmap; + + oldmap = _rl_keymap; + _rl_keymap = map; + result = rl_bind_key (key, function); + _rl_keymap = oldmap; + return (result); +} + +/* Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right + now, this is always used to attempt to bind the arrow keys, hence the + check for rl_vi_movement_mode. */ +int +rl_bind_key_if_unbound_in_map (key, default_func, kmap) + int key; + rl_command_func_t *default_func; + Keymap kmap; +{ + char keyseq[2]; + + keyseq[0] = (unsigned char)key; + keyseq[1] = '\0'; + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, kmap)); +} + +int +rl_bind_key_if_unbound (key, default_func) + int key; + rl_command_func_t *default_func; +{ + char keyseq[2]; + + keyseq[0] = (unsigned char)key; + keyseq[1] = '\0'; + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, _rl_keymap)); +} + +/* Make KEY do nothing in the currently selected keymap. + Returns non-zero in case of error. */ +int +rl_unbind_key (key) + int key; +{ + return (rl_bind_key (key, (rl_command_func_t *)NULL)); +} + +/* Make KEY do nothing in MAP. + Returns non-zero in case of error. */ +int +rl_unbind_key_in_map (key, map) + int key; + Keymap map; +{ + return (rl_bind_key_in_map (key, (rl_command_func_t *)NULL, map)); +} + +/* Unbind all keys bound to FUNCTION in MAP. */ +int +rl_unbind_function_in_map (func, map) + rl_command_func_t *func; + Keymap map; +{ + register int i, rval; + + for (i = rval = 0; i < KEYMAP_SIZE; i++) + { + if (map[i].type == ISFUNC && map[i].function == func) + { + map[i].function = (rl_command_func_t *)NULL; + rval = 1; + } + } + return rval; +} + +int +rl_unbind_command_in_map (command, map) + const char *command; + Keymap map; +{ + rl_command_func_t *func; + + func = rl_named_function (command); + if (func == 0) + return 0; + return (rl_unbind_function_in_map (func, map)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + FUNCTION, starting in the current keymap. This makes new + keymaps as necessary. */ +int +rl_bind_keyseq (keyseq, function) + const char *keyseq; + rl_command_func_t *function; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, _rl_keymap)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + FUNCTION. This makes new keymaps as necessary. The initial + place to do bindings is in MAP. */ +int +rl_bind_keyseq_in_map (keyseq, function, map) + const char *keyseq; + rl_command_func_t *function; + Keymap map; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, map)); +} + +/* Backwards compatibility; equivalent to rl_bind_keyseq_in_map() */ +int +rl_set_key (keyseq, function, map) + const char *keyseq; + rl_command_func_t *function; + Keymap map; +{ + return (rl_generic_bind (ISFUNC, keyseq, (char *)function, map)); +} + +/* Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right + now, this is always used to attempt to bind the arrow keys, hence the + check for rl_vi_movement_mode. */ +int +rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, kmap) + const char *keyseq; + rl_command_func_t *default_func; + Keymap kmap; +{ + rl_command_func_t *func; + + if (keyseq) + { + func = rl_function_of_keyseq (keyseq, kmap, (int *)NULL); +#if defined (VI_MODE) + if (!func || func == rl_do_lowercase_version || func == rl_vi_movement_mode) +#else + if (!func || func == rl_do_lowercase_version) +#endif + return (rl_bind_keyseq_in_map (keyseq, default_func, kmap)); + else + return 1; + } + return 0; +} + +int +rl_bind_keyseq_if_unbound (keyseq, default_func) + const char *keyseq; + rl_command_func_t *default_func; +{ + return (rl_bind_keyseq_if_unbound_in_map (keyseq, default_func, _rl_keymap)); +} + +/* Bind the key sequence represented by the string KEYSEQ to + the string of characters MACRO. This makes new keymaps as + necessary. The initial place to do bindings is in MAP. */ +int +rl_macro_bind (keyseq, macro, map) + const char *keyseq, *macro; + Keymap map; +{ + char *macro_keys; + int macro_keys_len; + + macro_keys = (char *)xmalloc ((2 * strlen (macro)) + 1); + + if (rl_translate_keyseq (macro, macro_keys, ¯o_keys_len)) + { + xfree (macro_keys); + return -1; + } + rl_generic_bind (ISMACR, keyseq, macro_keys, map); + return 0; +} + +/* Bind the key sequence represented by the string KEYSEQ to + the arbitrary pointer DATA. TYPE says what kind of data is + pointed to by DATA, right now this can be a function (ISFUNC), + a macro (ISMACR), or a keymap (ISKMAP). This makes new keymaps + as necessary. The initial place to do bindings is in MAP. */ +int +rl_generic_bind (type, keyseq, data, map) + int type; + const char *keyseq; + char *data; + Keymap map; +{ + char *keys; + int keys_len; + register int i; + KEYMAP_ENTRY k; + + k.function = 0; + + /* If no keys to bind to, exit right away. */ + if (keyseq == 0 || *keyseq == 0) + { + if (type == ISMACR) + xfree (data); + return -1; + } + + keys = (char *)xmalloc (1 + (2 * strlen (keyseq))); + + /* Translate the ASCII representation of KEYSEQ into an array of + characters. Stuff the characters into KEYS, and the length of + KEYS into KEYS_LEN. */ + if (rl_translate_keyseq (keyseq, keys, &keys_len)) + { + xfree (keys); + return -1; + } + + /* Bind keys, making new keymaps as necessary. */ + for (i = 0; i < keys_len; i++) + { + unsigned char uc = keys[i]; + int ic; + + ic = uc; + if (ic < 0 || ic >= KEYMAP_SIZE) + { + xfree (keys); + return -1; + } + + if (META_CHAR (ic) && _rl_convert_meta_chars_to_ascii) + { + ic = UNMETA (ic); + if (map[ESC].type == ISKMAP) + map = FUNCTION_TO_KEYMAP (map, ESC); + } + + if ((i + 1) < keys_len) + { + if (map[ic].type != ISKMAP) + { + /* We allow subsequences of keys. If a keymap is being + created that will `shadow' an existing function or macro + key binding, we save that keybinding into the ANYOTHERKEY + index in the new map. The dispatch code will look there + to find the function to execute if the subsequence is not + matched. ANYOTHERKEY was chosen to be greater than + UCHAR_MAX. */ + k = map[ic]; + + map[ic].type = ISKMAP; + map[ic].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap()); + } + map = FUNCTION_TO_KEYMAP (map, ic); + /* The dispatch code will return this function if no matching + key sequence is found in the keymap. This (with a little + help from the dispatch code in readline.c) allows `a' to be + mapped to something, `abc' to be mapped to something else, + and the function bound to `a' to be executed when the user + types `abx', leaving `bx' in the input queue. */ + if (k.function && ((k.type == ISFUNC && k.function != rl_do_lowercase_version) || k.type == ISMACR)) + { + map[ANYOTHERKEY] = k; + k.function = 0; + } + } + else + { + if (map[ic].type == ISMACR) + xfree ((char *)map[ic].function); + else if (map[ic].type == ISKMAP) + { + map = FUNCTION_TO_KEYMAP (map, ic); + ic = ANYOTHERKEY; + /* If we're trying to override a keymap with a null function + (e.g., trying to unbind it), we can't use a null pointer + here because that's indistinguishable from having not been + overridden. We use a special bindable function that does + nothing. */ + if (type == ISFUNC && data == 0) + data = (char *)_rl_null_function; + } + + map[ic].function = KEYMAP_TO_FUNCTION (data); + map[ic].type = type; + } + + rl_binding_keymap = map; + } + xfree (keys); + return 0; +} + +/* Translate the ASCII representation of SEQ, stuffing the values into ARRAY, + an array of characters. LEN gets the final length of ARRAY. Return + non-zero if there was an error parsing SEQ. */ +int +rl_translate_keyseq (seq, array, len) + const char *seq; + char *array; + int *len; +{ + register int i, c, l, temp; + + for (i = l = 0; c = seq[i]; i++) + { + if (c == '\\') + { + c = seq[++i]; + + if (c == 0) + break; + + /* Handle \C- and \M- prefixes. */ + if ((c == 'C' || c == 'M') && seq[i + 1] == '-') + { + /* Handle special case of backwards define. */ + if (strncmp (&seq[i], "C-\\M-", 5) == 0) + { + array[l++] = ESC; /* ESC is meta-prefix */ + i += 5; + array[l++] = CTRL (_rl_to_upper (seq[i])); + if (seq[i] == '\0') + i--; + } + else if (c == 'M') + { + i++; /* seq[i] == '-' */ + /* XXX - obey convert-meta setting */ + if (_rl_convert_meta_chars_to_ascii && _rl_keymap[ESC].type == ISKMAP) + array[l++] = ESC; /* ESC is meta-prefix */ + else if (seq[i+1] == '\\' && seq[i+2] == 'C' && seq[i+3] == '-') + { + i += 4; + temp = (seq[i] == '?') ? RUBOUT : CTRL (_rl_to_upper (seq[i])); + array[l++] = META (temp); + } + else + { + /* This doesn't yet handle things like \M-\a, which may + or may not have any reasonable meaning. You're + probably better off using straight octal or hex. */ + i++; + array[l++] = META (seq[i]); + } + } + else if (c == 'C') + { + i += 2; + /* Special hack for C-?... */ + array[l++] = (seq[i] == '?') ? RUBOUT : CTRL (_rl_to_upper (seq[i])); + } + continue; + } + + /* Translate other backslash-escaped characters. These are the + same escape sequences that bash's `echo' and `printf' builtins + handle, with the addition of \d -> RUBOUT. A backslash + preceding a character that is not special is stripped. */ + switch (c) + { + case 'a': + array[l++] = '\007'; + break; + case 'b': + array[l++] = '\b'; + break; + case 'd': + array[l++] = RUBOUT; /* readline-specific */ + break; + case 'e': + array[l++] = ESC; + break; + case 'f': + array[l++] = '\f'; + break; + case 'n': + array[l++] = NEWLINE; + break; + case 'r': + array[l++] = RETURN; + break; + case 't': + array[l++] = TAB; + break; + case 'v': + array[l++] = 0x0B; + break; + case '\\': + array[l++] = '\\'; + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + i++; + for (temp = 2, c -= '0'; ISOCTAL (seq[i]) && temp--; i++) + c = (c * 8) + OCTVALUE (seq[i]); + i--; /* auto-increment in for loop */ + array[l++] = c & largest_char; + break; + case 'x': + i++; + for (temp = 2, c = 0; ISXDIGIT ((unsigned char)seq[i]) && temp--; i++) + c = (c * 16) + HEXVALUE (seq[i]); + if (temp == 2) + c = 'x'; + i--; /* auto-increment in for loop */ + array[l++] = c & largest_char; + break; + default: /* backslashes before non-special chars just add the char */ + array[l++] = c; + break; /* the backslash is stripped */ + } + continue; + } + + array[l++] = c; + } + + *len = l; + array[l] = '\0'; + return (0); +} + +static int +_rl_isescape (c) + int c; +{ + switch (c) + { + case '\007': + case '\b': + case '\f': + case '\n': + case '\r': + case TAB: + case 0x0b: return (1); + default: return (0); + } +} + +static int +_rl_escchar (c) + int c; +{ + switch (c) + { + case '\007': return ('a'); + case '\b': return ('b'); + case '\f': return ('f'); + case '\n': return ('n'); + case '\r': return ('r'); + case TAB: return ('t'); + case 0x0b: return ('v'); + default: return (c); + } +} + +char * +rl_untranslate_keyseq (seq) + int seq; +{ + static char kseq[16]; + int i, c; + + i = 0; + c = seq; + if (META_CHAR (c)) + { + kseq[i++] = '\\'; + kseq[i++] = 'M'; + kseq[i++] = '-'; + c = UNMETA (c); + } + else if (c == ESC) + { + kseq[i++] = '\\'; + c = 'e'; + } + else if (CTRL_CHAR (c)) + { + kseq[i++] = '\\'; + kseq[i++] = 'C'; + kseq[i++] = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + else if (c == RUBOUT) + { + kseq[i++] = '\\'; + kseq[i++] = 'C'; + kseq[i++] = '-'; + c = '?'; + } + + if (c == ESC) + { + kseq[i++] = '\\'; + c = 'e'; + } + else if (c == '\\' || c == '"') + { + kseq[i++] = '\\'; + } + + kseq[i++] = (unsigned char) c; + kseq[i] = '\0'; + return kseq; +} + +char * +_rl_untranslate_macro_value (seq, use_escapes) + char *seq; + int use_escapes; +{ + char *ret, *r, *s; + int c; + + r = ret = (char *)xmalloc (7 * strlen (seq) + 1); + for (s = seq; *s; s++) + { + c = *s; + if (META_CHAR (c)) + { + *r++ = '\\'; + *r++ = 'M'; + *r++ = '-'; + c = UNMETA (c); + } + else if (c == ESC) + { + *r++ = '\\'; + c = 'e'; + } + else if (CTRL_CHAR (c)) + { + *r++ = '\\'; + if (use_escapes && _rl_isescape (c)) + c = _rl_escchar (c); + else + { + *r++ = 'C'; + *r++ = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + } + else if (c == RUBOUT) + { + *r++ = '\\'; + *r++ = 'C'; + *r++ = '-'; + c = '?'; + } + + if (c == ESC) + { + *r++ = '\\'; + c = 'e'; + } + else if (c == '\\' || c == '"') + *r++ = '\\'; + + *r++ = (unsigned char)c; + } + *r = '\0'; + return ret; +} + +/* Return a pointer to the function that STRING represents. + If STRING doesn't have a matching function, then a NULL pointer + is returned. */ +rl_command_func_t * +rl_named_function (string) + const char *string; +{ + register int i; + + rl_initialize_funmap (); + + for (i = 0; funmap[i]; i++) + if (_rl_stricmp (funmap[i]->name, string) == 0) + return (funmap[i]->function); + return ((rl_command_func_t *)NULL); +} + +/* Return the function (or macro) definition which would be invoked via + KEYSEQ if executed in MAP. If MAP is NULL, then the current keymap is + used. TYPE, if non-NULL, is a pointer to an int which will receive the + type of the object pointed to. One of ISFUNC (function), ISKMAP (keymap), + or ISMACR (macro). */ +rl_command_func_t * +rl_function_of_keyseq (keyseq, map, type) + const char *keyseq; + Keymap map; + int *type; +{ + register int i; + + if (map == 0) + map = _rl_keymap; + + for (i = 0; keyseq && keyseq[i]; i++) + { + unsigned char ic = keyseq[i]; + + if (META_CHAR (ic) && _rl_convert_meta_chars_to_ascii) + { + if (map[ESC].type == ISKMAP) + { + map = FUNCTION_TO_KEYMAP (map, ESC); + ic = UNMETA (ic); + } + /* XXX - should we just return NULL here, since this obviously + doesn't match? */ + else + { + if (type) + *type = map[ESC].type; + + return (map[ESC].function); + } + } + + if (map[ic].type == ISKMAP) + { + /* If this is the last key in the key sequence, return the + map. */ + if (keyseq[i + 1] == '\0') + { + if (type) + *type = ISKMAP; + + return (map[ic].function); + } + else + map = FUNCTION_TO_KEYMAP (map, ic); + } + /* If we're not at the end of the key sequence, and the current key + is bound to something other than a keymap, then the entire key + sequence is not bound. */ + else if (map[ic].type != ISKMAP && keyseq[i+1]) + return ((rl_command_func_t *)NULL); + else /* map[ic].type != ISKMAP && keyseq[i+1] == 0 */ + { + if (type) + *type = map[ic].type; + + return (map[ic].function); + } + } + return ((rl_command_func_t *) NULL); +} + +/* The last key bindings file read. */ +static char *last_readline_init_file = (char *)NULL; + +/* The file we're currently reading key bindings from. */ +static const char *current_readline_init_file; +static int current_readline_init_include_level; +static int current_readline_init_lineno; + +/* Read FILENAME into a locally-allocated buffer and return the buffer. + The size of the buffer is returned in *SIZEP. Returns NULL if any + errors were encountered. */ +static char * +_rl_read_file (filename, sizep) + char *filename; + size_t *sizep; +{ + struct stat finfo; + size_t file_size; + char *buffer; + int i, file; + + if ((stat (filename, &finfo) < 0) || (file = open (filename, O_RDONLY, 0666)) < 0) + return ((char *)NULL); + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if (file_size != finfo.st_size || file_size + 1 < file_size) + { + if (file >= 0) + close (file); +#if defined (EFBIG) + errno = EFBIG; +#endif + return ((char *)NULL); + } + + /* Read the file into BUFFER. */ + buffer = (char *)xmalloc (file_size + 1); + i = read (file, buffer, file_size); + close (file); + + if (i < 0) + { + xfree (buffer); + return ((char *)NULL); + } + + RL_CHECK_SIGNALS (); + + buffer[i] = '\0'; + if (sizep) + *sizep = i; + + return (buffer); +} + +/* Re-read the current keybindings file. */ +int +rl_re_read_init_file (count, ignore) + int count, ignore; +{ + int r; + r = rl_read_init_file ((const char *)NULL); + rl_set_keymap_from_edit_mode (); + return r; +} + +/* Do key bindings from a file. If FILENAME is NULL it defaults + to the first non-null filename from this list: + 1. the filename used for the previous call + 2. the value of the shell variable `INPUTRC' + 3. ~/.inputrc + 4. /etc/inputrc + If the file existed and could be opened and read, 0 is returned, + otherwise errno is returned. */ +int +rl_read_init_file (filename) + const char *filename; +{ + /* Default the filename. */ + if (filename == 0) + filename = last_readline_init_file; + if (filename == 0) + filename = sh_get_env_value ("INPUTRC"); + if (filename == 0 || *filename == 0) + { + filename = DEFAULT_INPUTRC; + /* Try to read DEFAULT_INPUTRC; fall back to SYS_INPUTRC on failure */ + if (_rl_read_init_file (filename, 0) == 0) + return 0; + filename = SYS_INPUTRC; + } + +#if defined (__MSDOS__) + if (_rl_read_init_file (filename, 0) == 0) + return 0; + filename = "~/_inputrc"; +#endif + return (_rl_read_init_file (filename, 0)); +} + +static int +_rl_read_init_file (filename, include_level) + const char *filename; + int include_level; +{ + register int i; + char *buffer, *openname, *line, *end; + size_t file_size; + + current_readline_init_file = filename; + current_readline_init_include_level = include_level; + + openname = tilde_expand (filename); + buffer = _rl_read_file (openname, &file_size); + xfree (openname); + + RL_CHECK_SIGNALS (); + if (buffer == 0) + return (errno); + + if (include_level == 0 && filename != last_readline_init_file) + { + FREE (last_readline_init_file); + last_readline_init_file = savestring (filename); + } + + currently_reading_init_file = 1; + + /* Loop over the lines in the file. Lines that start with `#' are + comments; all other lines are commands for readline initialization. */ + current_readline_init_lineno = 1; + line = buffer; + end = buffer + file_size; + while (line < end) + { + /* Find the end of this line. */ + for (i = 0; line + i != end && line[i] != '\n'; i++); + +#if defined (__CYGWIN__) + /* ``Be liberal in what you accept.'' */ + if (line[i] == '\n' && line[i-1] == '\r') + line[i - 1] = '\0'; +#endif + + /* Mark end of line. */ + line[i] = '\0'; + + /* Skip leading whitespace. */ + while (*line && whitespace (*line)) + { + line++; + i--; + } + + /* If the line is not a comment, then parse it. */ + if (*line && *line != '#') + rl_parse_and_bind (line); + + /* Move to the next line. */ + line += i + 1; + current_readline_init_lineno++; + } + + xfree (buffer); + currently_reading_init_file = 0; + return (0); +} + +static void +_rl_init_file_error (msg) + const char *msg; +{ + if (currently_reading_init_file) + _rl_errmsg ("%s: line %d: %s\n", current_readline_init_file, + current_readline_init_lineno, msg); + else + _rl_errmsg ("%s", msg); +} + +/* **************************************************************** */ +/* */ +/* Parser Directives */ +/* */ +/* **************************************************************** */ + +typedef int _rl_parser_func_t PARAMS((char *)); + +/* Things that mean `Control'. */ +const char * const _rl_possible_control_prefixes[] = { + "Control-", "C-", "CTRL-", (const char *)NULL +}; + +const char * const _rl_possible_meta_prefixes[] = { + "Meta", "M-", (const char *)NULL +}; + +/* Conditionals. */ + +/* Calling programs set this to have their argv[0]. */ +const char *rl_readline_name = "other"; + +/* Stack of previous values of parsing_conditionalized_out. */ +static unsigned char *if_stack = (unsigned char *)NULL; +static int if_stack_depth; +static int if_stack_size; + +/* Push _rl_parsing_conditionalized_out, and set parser state based + on ARGS. */ +static int +parser_if (args) + char *args; +{ + register int i; + + /* Push parser state. */ + if (if_stack_depth + 1 >= if_stack_size) + { + if (!if_stack) + if_stack = (unsigned char *)xmalloc (if_stack_size = 20); + else + if_stack = (unsigned char *)xrealloc (if_stack, if_stack_size += 20); + } + if_stack[if_stack_depth++] = _rl_parsing_conditionalized_out; + + /* If parsing is turned off, then nothing can turn it back on except + for finding the matching endif. In that case, return right now. */ + if (_rl_parsing_conditionalized_out) + return 0; + + /* Isolate first argument. */ + for (i = 0; args[i] && !whitespace (args[i]); i++); + + if (args[i]) + args[i++] = '\0'; + + /* Handle "$if term=foo" and "$if mode=emacs" constructs. If this + isn't term=foo, or mode=emacs, then check to see if the first + word in ARGS is the same as the value stored in rl_readline_name. */ + if (rl_terminal_name && _rl_strnicmp (args, "term=", 5) == 0) + { + char *tem, *tname; + + /* Terminals like "aaa-60" are equivalent to "aaa". */ + tname = savestring (rl_terminal_name); + tem = strchr (tname, '-'); + if (tem) + *tem = '\0'; + + /* Test the `long' and `short' forms of the terminal name so that + if someone has a `sun-cmd' and does not want to have bindings + that will be executed if the terminal is a `sun', they can put + `$if term=sun-cmd' into their .inputrc. */ + _rl_parsing_conditionalized_out = _rl_stricmp (args + 5, tname) && + _rl_stricmp (args + 5, rl_terminal_name); + xfree (tname); + } +#if defined (VI_MODE) + else if (_rl_strnicmp (args, "mode=", 5) == 0) + { + int mode; + + if (_rl_stricmp (args + 5, "emacs") == 0) + mode = emacs_mode; + else if (_rl_stricmp (args + 5, "vi") == 0) + mode = vi_mode; + else + mode = no_mode; + + _rl_parsing_conditionalized_out = mode != rl_editing_mode; + } +#endif /* VI_MODE */ + /* Check to see if the first word in ARGS is the same as the + value stored in rl_readline_name. */ + else if (_rl_stricmp (args, rl_readline_name) == 0) + _rl_parsing_conditionalized_out = 0; + else + _rl_parsing_conditionalized_out = 1; + return 0; +} + +/* Invert the current parser state if there is anything on the stack. */ +static int +parser_else (args) + char *args; +{ + register int i; + + if (if_stack_depth == 0) + { + _rl_init_file_error ("$else found without matching $if"); + return 0; + } + +#if 0 + /* Check the previous (n - 1) levels of the stack to make sure that + we haven't previously turned off parsing. */ + for (i = 0; i < if_stack_depth - 1; i++) +#else + /* Check the previous (n) levels of the stack to make sure that + we haven't previously turned off parsing. */ + for (i = 0; i < if_stack_depth; i++) +#endif + if (if_stack[i] == 1) + return 0; + + /* Invert the state of parsing if at top level. */ + _rl_parsing_conditionalized_out = !_rl_parsing_conditionalized_out; + return 0; +} + +/* Terminate a conditional, popping the value of + _rl_parsing_conditionalized_out from the stack. */ +static int +parser_endif (args) + char *args; +{ + if (if_stack_depth) + _rl_parsing_conditionalized_out = if_stack[--if_stack_depth]; + else + _rl_init_file_error ("$endif without matching $if"); + return 0; +} + +static int +parser_include (args) + char *args; +{ + const char *old_init_file; + char *e; + int old_line_number, old_include_level, r; + + if (_rl_parsing_conditionalized_out) + return (0); + + old_init_file = current_readline_init_file; + old_line_number = current_readline_init_lineno; + old_include_level = current_readline_init_include_level; + + e = strchr (args, '\n'); + if (e) + *e = '\0'; + r = _rl_read_init_file ((const char *)args, old_include_level + 1); + + current_readline_init_file = old_init_file; + current_readline_init_lineno = old_line_number; + current_readline_init_include_level = old_include_level; + + return r; +} + +/* Associate textual names with actual functions. */ +static const struct { + const char * const name; + _rl_parser_func_t *function; +} parser_directives [] = { + { "if", parser_if }, + { "endif", parser_endif }, + { "else", parser_else }, + { "include", parser_include }, + { (char *)0x0, (_rl_parser_func_t *)0x0 } +}; + +/* Handle a parser directive. STATEMENT is the line of the directive + without any leading `$'. */ +static int +handle_parser_directive (statement) + char *statement; +{ + register int i; + char *directive, *args; + + /* Isolate the actual directive. */ + + /* Skip whitespace. */ + for (i = 0; whitespace (statement[i]); i++); + + directive = &statement[i]; + + for (; statement[i] && !whitespace (statement[i]); i++); + + if (statement[i]) + statement[i++] = '\0'; + + for (; statement[i] && whitespace (statement[i]); i++); + + args = &statement[i]; + + /* Lookup the command, and act on it. */ + for (i = 0; parser_directives[i].name; i++) + if (_rl_stricmp (directive, parser_directives[i].name) == 0) + { + (*parser_directives[i].function) (args); + return (0); + } + + /* display an error message about the unknown parser directive */ + _rl_init_file_error ("unknown parser directive"); + return (1); +} + +/* Start at STRING[START] and look for DELIM. Return I where STRING[I] == + DELIM or STRING[I] == 0. DELIM is usually a double quote. */ +static int +_rl_skip_to_delim (string, start, delim) + char *string; + int start, delim; +{ + int i, c, passc; + + for (i = start,passc = 0; c = string[i]; i++) + { + if (passc) + { + passc = 0; + if (c == 0) + break; + continue; + } + + if (c == '\\') + { + passc = 1; + continue; + } + + if (c == delim) + break; + } + + return i; +} + +/* Read the binding command from STRING and perform it. + A key binding command looks like: Keyname: function-name\0, + a variable binding command looks like: set variable value. + A new-style keybinding looks like "\C-x\C-x": exchange-point-and-mark. */ +int +rl_parse_and_bind (string) + char *string; +{ + char *funname, *kname; + register int c, i; + int key, equivalency; + + while (string && whitespace (*string)) + string++; + + if (string == 0 || *string == 0 || *string == '#') + return 0; + + /* If this is a parser directive, act on it. */ + if (*string == '$') + { + handle_parser_directive (&string[1]); + return 0; + } + + /* If we aren't supposed to be parsing right now, then we're done. */ + if (_rl_parsing_conditionalized_out) + return 0; + + i = 0; + /* If this keyname is a complex key expression surrounded by quotes, + advance to after the matching close quote. This code allows the + backslash to quote characters in the key expression. */ + if (*string == '"') + { + i = _rl_skip_to_delim (string, 1, '"'); + + /* If we didn't find a closing quote, abort the line. */ + if (string[i] == '\0') + { + _rl_init_file_error ("no closing `\"' in key binding"); + return 1; + } + else + i++; /* skip past closing double quote */ + } + + /* Advance to the colon (:) or whitespace which separates the two objects. */ + for (; (c = string[i]) && c != ':' && c != ' ' && c != '\t'; i++ ); + + equivalency = (c == ':' && string[i + 1] == '='); + + /* Mark the end of the command (or keyname). */ + if (string[i]) + string[i++] = '\0'; + + /* If doing assignment, skip the '=' sign as well. */ + if (equivalency) + string[i++] = '\0'; + + /* If this is a command to set a variable, then do that. */ + if (_rl_stricmp (string, "set") == 0) + { + char *var, *value, *e; + int s; + + var = string + i; + /* Make VAR point to start of variable name. */ + while (*var && whitespace (*var)) var++; + + /* Make VALUE point to start of value string. */ + value = var; + while (*value && whitespace (*value) == 0) value++; + if (*value) + *value++ = '\0'; + while (*value && whitespace (*value)) value++; + + /* Strip trailing whitespace from values of boolean variables. */ + if (find_boolean_var (var) >= 0) + { + /* remove trailing whitespace */ +remove_trailing: + e = value + strlen (value) - 1; + while (e >= value && whitespace (*e)) + e--; + e++; /* skip back to whitespace or EOS */ + + if (*e && e >= value) + *e = '\0'; + } + else if ((i = find_string_var (var)) >= 0) + { + /* Allow quoted strings in variable values */ + if (*value == '"') + { + i = _rl_skip_to_delim (value, 1, *value); + value[i] = '\0'; + value++; /* skip past the quote */ + } + else + goto remove_trailing; + } + + rl_variable_bind (var, value); + return 0; + } + + /* Skip any whitespace between keyname and funname. */ + for (; string[i] && whitespace (string[i]); i++); + funname = &string[i]; + + /* Now isolate funname. + For straight function names just look for whitespace, since + that will signify the end of the string. But this could be a + macro definition. In that case, the string is quoted, so skip + to the matching delimiter. We allow the backslash to quote the + delimiter characters in the macro body. */ + /* This code exists to allow whitespace in macro expansions, which + would otherwise be gobbled up by the next `for' loop.*/ + /* XXX - it may be desirable to allow backslash quoting only if " is + the quoted string delimiter, like the shell. */ + if (*funname == '\'' || *funname == '"') + { + i = _rl_skip_to_delim (string, i+1, *funname); + if (string[i]) + i++; + } + + /* Advance to the end of the string. */ + for (; string[i] && whitespace (string[i]) == 0; i++); + + /* No extra whitespace at the end of the string. */ + string[i] = '\0'; + + /* Handle equivalency bindings here. Make the left-hand side be exactly + whatever the right-hand evaluates to, including keymaps. */ + if (equivalency) + { + return 0; + } + + /* If this is a new-style key-binding, then do the binding with + rl_bind_keyseq (). Otherwise, let the older code deal with it. */ + if (*string == '"') + { + char *seq; + register int j, k, passc; + + seq = (char *)xmalloc (1 + strlen (string)); + for (j = 1, k = passc = 0; string[j]; j++) + { + /* Allow backslash to quote characters, but leave them in place. + This allows a string to end with a backslash quoting another + backslash, or with a backslash quoting a double quote. The + backslashes are left in place for rl_translate_keyseq (). */ + if (passc || (string[j] == '\\')) + { + seq[k++] = string[j]; + passc = !passc; + continue; + } + + if (string[j] == '"') + break; + + seq[k++] = string[j]; + } + seq[k] = '\0'; + + /* Binding macro? */ + if (*funname == '\'' || *funname == '"') + { + j = strlen (funname); + + /* Remove the delimiting quotes from each end of FUNNAME. */ + if (j && funname[j - 1] == *funname) + funname[j - 1] = '\0'; + + rl_macro_bind (seq, &funname[1], _rl_keymap); + } + else + rl_bind_keyseq (seq, rl_named_function (funname)); + + xfree (seq); + return 0; + } + + /* Get the actual character we want to deal with. */ + kname = strrchr (string, '-'); + if (kname == 0) + kname = string; + else + kname++; + + key = glean_key_from_name (kname); + + /* Add in control and meta bits. */ + if (substring_member_of_array (string, _rl_possible_control_prefixes)) + key = CTRL (_rl_to_upper (key)); + + if (substring_member_of_array (string, _rl_possible_meta_prefixes)) + key = META (key); + + /* Temporary. Handle old-style keyname with macro-binding. */ + if (*funname == '\'' || *funname == '"') + { + char useq[2]; + int fl = strlen (funname); + + useq[0] = key; useq[1] = '\0'; + if (fl && funname[fl - 1] == *funname) + funname[fl - 1] = '\0'; + + rl_macro_bind (useq, &funname[1], _rl_keymap); + } +#if defined (PREFIX_META_HACK) + /* Ugly, but working hack to keep prefix-meta around. */ + else if (_rl_stricmp (funname, "prefix-meta") == 0) + { + char seq[2]; + + seq[0] = key; + seq[1] = '\0'; + rl_generic_bind (ISKMAP, seq, (char *)emacs_meta_keymap, _rl_keymap); + } +#endif /* PREFIX_META_HACK */ + else + rl_bind_key (key, rl_named_function (funname)); + return 0; +} + +/* Simple structure for boolean readline variables (i.e., those that can + have one of two values; either "On" or 1 for truth, or "Off" or 0 for + false. */ + +#define V_SPECIAL 0x1 + +static const struct { + const char * const name; + int *value; + int flags; +} boolean_varlist [] = { + { "bind-tty-special-chars", &_rl_bind_stty_chars, 0 }, + { "blink-matching-paren", &rl_blink_matching_paren, V_SPECIAL }, + { "byte-oriented", &rl_byte_oriented, 0 }, +#if defined (COLOR_SUPPORT) + { "colored-completion-prefix",&_rl_colored_completion_prefix, 0 }, + { "colored-stats", &_rl_colored_stats, 0 }, +#endif + { "completion-ignore-case", &_rl_completion_case_fold, 0 }, + { "completion-map-case", &_rl_completion_case_map, 0 }, + { "convert-meta", &_rl_convert_meta_chars_to_ascii, 0 }, + { "disable-completion", &rl_inhibit_completion, 0 }, + { "echo-control-characters", &_rl_echo_control_chars, 0 }, + { "enable-bracketed-paste", &_rl_enable_bracketed_paste, 0 }, + { "enable-keypad", &_rl_enable_keypad, 0 }, + { "enable-meta-key", &_rl_enable_meta, 0 }, + { "expand-tilde", &rl_complete_with_tilde_expansion, 0 }, + { "history-preserve-point", &_rl_history_preserve_point, 0 }, + { "horizontal-scroll-mode", &_rl_horizontal_scroll_mode, 0 }, + { "input-meta", &_rl_meta_flag, 0 }, + { "mark-directories", &_rl_complete_mark_directories, 0 }, + { "mark-modified-lines", &_rl_mark_modified_lines, 0 }, + { "mark-symlinked-directories", &_rl_complete_mark_symlink_dirs, 0 }, + { "match-hidden-files", &_rl_match_hidden_files, 0 }, + { "menu-complete-display-prefix", &_rl_menu_complete_prefix_first, 0 }, + { "meta-flag", &_rl_meta_flag, 0 }, + { "output-meta", &_rl_output_meta_chars, 0 }, + { "page-completions", &_rl_page_completions, 0 }, + { "prefer-visible-bell", &_rl_prefer_visible_bell, V_SPECIAL }, + { "print-completions-horizontally", &_rl_print_completions_horizontally, 0 }, + { "revert-all-at-newline", &_rl_revert_all_at_newline, 0 }, + { "show-all-if-ambiguous", &_rl_complete_show_all, 0 }, + { "show-all-if-unmodified", &_rl_complete_show_unmodified, 0 }, + { "show-mode-in-prompt", &_rl_show_mode_in_prompt, 0 }, + { "skip-completed-text", &_rl_skip_completed_text, 0 }, +#if defined (VISIBLE_STATS) + { "visible-stats", &rl_visible_stats, 0 }, +#endif /* VISIBLE_STATS */ + { (char *)NULL, (int *)NULL, 0 } +}; + +static int +find_boolean_var (name) + const char *name; +{ + register int i; + + for (i = 0; boolean_varlist[i].name; i++) + if (_rl_stricmp (name, boolean_varlist[i].name) == 0) + return i; + return -1; +} + +/* Hooks for handling special boolean variables, where a + function needs to be called or another variable needs + to be changed when they're changed. */ +static void +hack_special_boolean_var (i) + int i; +{ + const char *name; + + name = boolean_varlist[i].name; + + if (_rl_stricmp (name, "blink-matching-paren") == 0) + _rl_enable_paren_matching (rl_blink_matching_paren); + else if (_rl_stricmp (name, "prefer-visible-bell") == 0) + { + if (_rl_prefer_visible_bell) + _rl_bell_preference = VISIBLE_BELL; + else + _rl_bell_preference = AUDIBLE_BELL; + } + else if (_rl_stricmp (name, "show-mode-in-prompt") == 0) + _rl_reset_prompt (); +} + +typedef int _rl_sv_func_t PARAMS((const char *)); + +/* These *must* correspond to the array indices for the appropriate + string variable. (Though they're not used right now.) */ +#define V_BELLSTYLE 0 +#define V_COMBEGIN 1 +#define V_EDITMODE 2 +#define V_ISRCHTERM 3 +#define V_KEYMAP 4 + +#define V_STRING 1 +#define V_INT 2 + +/* Forward declarations */ +static int sv_bell_style PARAMS((const char *)); +static int sv_combegin PARAMS((const char *)); +static int sv_dispprefix PARAMS((const char *)); +static int sv_compquery PARAMS((const char *)); +static int sv_compwidth PARAMS((const char *)); +static int sv_editmode PARAMS((const char *)); +static int sv_emacs_modestr PARAMS((const char *)); +static int sv_histsize PARAMS((const char *)); +static int sv_isrchterm PARAMS((const char *)); +static int sv_keymap PARAMS((const char *)); +static int sv_seqtimeout PARAMS((const char *)); +static int sv_viins_modestr PARAMS((const char *)); +static int sv_vicmd_modestr PARAMS((const char *)); + +static const struct { + const char * const name; + int flags; + _rl_sv_func_t *set_func; +} string_varlist[] = { + { "bell-style", V_STRING, sv_bell_style }, + { "comment-begin", V_STRING, sv_combegin }, + { "completion-display-width", V_INT, sv_compwidth }, + { "completion-prefix-display-length", V_INT, sv_dispprefix }, + { "completion-query-items", V_INT, sv_compquery }, + { "editing-mode", V_STRING, sv_editmode }, + { "emacs-mode-string", V_STRING, sv_emacs_modestr }, + { "history-size", V_INT, sv_histsize }, + { "isearch-terminators", V_STRING, sv_isrchterm }, + { "keymap", V_STRING, sv_keymap }, + { "keyseq-timeout", V_INT, sv_seqtimeout }, + { "vi-cmd-mode-string", V_STRING, sv_vicmd_modestr }, + { "vi-ins-mode-string", V_STRING, sv_viins_modestr }, + { (char *)NULL, 0, (_rl_sv_func_t *)0 } +}; + +static int +find_string_var (name) + const char *name; +{ + register int i; + + for (i = 0; string_varlist[i].name; i++) + if (_rl_stricmp (name, string_varlist[i].name) == 0) + return i; + return -1; +} + +/* A boolean value that can appear in a `set variable' command is true if + the value is null or empty, `on' (case-insensitive), or "1". Any other + values result in 0 (false). */ +static int +bool_to_int (value) + const char *value; +{ + return (value == 0 || *value == '\0' || + (_rl_stricmp (value, "on") == 0) || + (value[0] == '1' && value[1] == '\0')); +} + +char * +rl_variable_value (name) + const char *name; +{ + register int i; + + /* Check for simple variables first. */ + i = find_boolean_var (name); + if (i >= 0) + return (*boolean_varlist[i].value ? "on" : "off"); + + i = find_string_var (name); + if (i >= 0) + return (_rl_get_string_variable_value (string_varlist[i].name)); + + /* Unknown variable names return NULL. */ + return 0; +} + +int +rl_variable_bind (name, value) + const char *name, *value; +{ + register int i; + int v; + + /* Check for simple variables first. */ + i = find_boolean_var (name); + if (i >= 0) + { + *boolean_varlist[i].value = bool_to_int (value); + if (boolean_varlist[i].flags & V_SPECIAL) + hack_special_boolean_var (i); + return 0; + } + + i = find_string_var (name); + + /* For the time being, unknown variable names or string names without a + handler function are simply ignored. */ + if (i < 0 || string_varlist[i].set_func == 0) + return 0; + + v = (*string_varlist[i].set_func) (value); + return v; +} + +static int +sv_editmode (value) + const char *value; +{ + if (_rl_strnicmp (value, "vi", 2) == 0) + { +#if defined (VI_MODE) + _rl_keymap = vi_insertion_keymap; + rl_editing_mode = vi_mode; +#endif /* VI_MODE */ + return 0; + } + else if (_rl_strnicmp (value, "emacs", 5) == 0) + { + _rl_keymap = emacs_standard_keymap; + rl_editing_mode = emacs_mode; + return 0; + } + return 1; +} + +static int +sv_combegin (value) + const char *value; +{ + if (value && *value) + { + FREE (_rl_comment_begin); + _rl_comment_begin = savestring (value); + return 0; + } + return 1; +} + +static int +sv_dispprefix (value) + const char *value; +{ + int nval = 0; + + if (value && *value) + { + nval = atoi (value); + if (nval < 0) + nval = 0; + } + _rl_completion_prefix_display_length = nval; + return 0; +} + +static int +sv_compquery (value) + const char *value; +{ + int nval = 100; + + if (value && *value) + { + nval = atoi (value); + if (nval < 0) + nval = 0; + } + rl_completion_query_items = nval; + return 0; +} + +static int +sv_compwidth (value) + const char *value; +{ + int nval = -1; + + if (value && *value) + nval = atoi (value); + + _rl_completion_columns = nval; + return 0; +} + +static int +sv_histsize (value) + const char *value; +{ + int nval; + + nval = 500; + if (value && *value) + { + nval = atoi (value); + if (nval < 0) + { + unstifle_history (); + return 0; + } + } + stifle_history (nval); + return 0; +} + +static int +sv_keymap (value) + const char *value; +{ + Keymap kmap; + + kmap = rl_get_keymap_by_name (value); + if (kmap) + { + rl_set_keymap (kmap); + return 0; + } + return 1; +} + +static int +sv_seqtimeout (value) + const char *value; +{ + int nval; + + nval = 0; + if (value && *value) + { + nval = atoi (value); + if (nval < 0) + nval = 0; + } + _rl_keyseq_timeout = nval; + return 0; +} + +static int +sv_bell_style (value) + const char *value; +{ + if (value == 0 || *value == '\0') + _rl_bell_preference = AUDIBLE_BELL; + else if (_rl_stricmp (value, "none") == 0 || _rl_stricmp (value, "off") == 0) + _rl_bell_preference = NO_BELL; + else if (_rl_stricmp (value, "audible") == 0 || _rl_stricmp (value, "on") == 0) + _rl_bell_preference = AUDIBLE_BELL; + else if (_rl_stricmp (value, "visible") == 0) + _rl_bell_preference = VISIBLE_BELL; + else + return 1; + return 0; +} + +static int +sv_isrchterm (value) + const char *value; +{ + int beg, end, delim; + char *v; + + if (value == 0) + return 1; + + /* Isolate the value and translate it into a character string. */ + v = savestring (value); + FREE (_rl_isearch_terminators); + if (v[0] == '"' || v[0] == '\'') + { + delim = v[0]; + for (beg = end = 1; v[end] && v[end] != delim; end++) + ; + } + else + { + for (beg = end = 0; whitespace (v[end]) == 0; end++) + ; + } + + v[end] = '\0'; + + /* The value starts at v + beg. Translate it into a character string. */ + _rl_isearch_terminators = (char *)xmalloc (2 * strlen (v) + 1); + rl_translate_keyseq (v + beg, _rl_isearch_terminators, &end); + _rl_isearch_terminators[end] = '\0'; + + xfree (v); + return 0; +} + +extern char *_rl_emacs_mode_str; + +static int +sv_emacs_modestr (value) + const char *value; +{ + if (value && *value) + { + FREE (_rl_emacs_mode_str); + _rl_emacs_mode_str = (char *)xmalloc (2 * strlen (value) + 1); + rl_translate_keyseq (value, _rl_emacs_mode_str, &_rl_emacs_modestr_len); + _rl_emacs_mode_str[_rl_emacs_modestr_len] = '\0'; + return 0; + } + else if (value) + { + FREE (_rl_emacs_mode_str); + _rl_emacs_mode_str = (char *)xmalloc (1); + _rl_emacs_mode_str[_rl_emacs_modestr_len = 0] = '\0'; + return 0; + } + else if (value == 0) + { + FREE (_rl_emacs_mode_str); + _rl_emacs_mode_str = 0; /* prompt_modestr does the right thing */ + _rl_emacs_modestr_len = 0; + return 0; + } + return 1; +} + +static int +sv_viins_modestr (value) + const char *value; +{ + if (value && *value) + { + FREE (_rl_vi_ins_mode_str); + _rl_vi_ins_mode_str = (char *)xmalloc (2 * strlen (value) + 1); + rl_translate_keyseq (value, _rl_vi_ins_mode_str, &_rl_vi_ins_modestr_len); + _rl_vi_ins_mode_str[_rl_vi_ins_modestr_len] = '\0'; + return 0; + } + else if (value) + { + FREE (_rl_vi_ins_mode_str); + _rl_vi_ins_mode_str = (char *)xmalloc (1); + _rl_vi_ins_mode_str[_rl_vi_ins_modestr_len = 0] = '\0'; + return 0; + } + else if (value == 0) + { + FREE (_rl_vi_ins_mode_str); + _rl_vi_ins_mode_str = 0; /* prompt_modestr does the right thing */ + _rl_vi_ins_modestr_len = 0; + return 0; + } + return 1; +} + +static int +sv_vicmd_modestr (value) + const char *value; +{ + if (value && *value) + { + FREE (_rl_vi_cmd_mode_str); + _rl_vi_cmd_mode_str = (char *)xmalloc (2 * strlen (value) + 1); + rl_translate_keyseq (value, _rl_vi_cmd_mode_str, &_rl_vi_cmd_modestr_len); + _rl_vi_cmd_mode_str[_rl_vi_cmd_modestr_len] = '\0'; + return 0; + } + else if (value) + { + FREE (_rl_vi_cmd_mode_str); + _rl_vi_cmd_mode_str = (char *)xmalloc (1); + _rl_vi_cmd_mode_str[_rl_vi_cmd_modestr_len = 0] = '\0'; + return 0; + } + else if (value == 0) + { + FREE (_rl_vi_cmd_mode_str); + _rl_vi_cmd_mode_str = 0; /* prompt_modestr does the right thing */ + _rl_vi_cmd_modestr_len = 0; + return 0; + } + return 1; +} + +/* Return the character which matches NAME. + For example, `Space' returns ' '. */ + +typedef struct { + const char * const name; + int value; +} assoc_list; + +static const assoc_list name_key_alist[] = { + { "DEL", 0x7f }, + { "ESC", '\033' }, + { "Escape", '\033' }, + { "LFD", '\n' }, + { "Newline", '\n' }, + { "RET", '\r' }, + { "Return", '\r' }, + { "Rubout", 0x7f }, + { "SPC", ' ' }, + { "Space", ' ' }, + { "Tab", 0x09 }, + { (char *)0x0, 0 } +}; + +static int +glean_key_from_name (name) + char *name; +{ + register int i; + + for (i = 0; name_key_alist[i].name; i++) + if (_rl_stricmp (name, name_key_alist[i].name) == 0) + return (name_key_alist[i].value); + + return (*(unsigned char *)name); /* XXX was return (*name) */ +} + +/* Auxiliary functions to manage keymaps. */ +static const struct { + const char * const name; + Keymap map; +} keymap_names[] = { + { "emacs", emacs_standard_keymap }, + { "emacs-standard", emacs_standard_keymap }, + { "emacs-meta", emacs_meta_keymap }, + { "emacs-ctlx", emacs_ctlx_keymap }, +#if defined (VI_MODE) + { "vi", vi_movement_keymap }, + { "vi-move", vi_movement_keymap }, + { "vi-command", vi_movement_keymap }, + { "vi-insert", vi_insertion_keymap }, +#endif /* VI_MODE */ + { (char *)0x0, (Keymap)0x0 } +}; + +Keymap +rl_get_keymap_by_name (name) + const char *name; +{ + register int i; + + for (i = 0; keymap_names[i].name; i++) + if (_rl_stricmp (name, keymap_names[i].name) == 0) + return (keymap_names[i].map); + return ((Keymap) NULL); +} + +char * +rl_get_keymap_name (map) + Keymap map; +{ + register int i; + for (i = 0; keymap_names[i].name; i++) + if (map == keymap_names[i].map) + return ((char *)keymap_names[i].name); + return ((char *)NULL); +} + +void +rl_set_keymap (map) + Keymap map; +{ + if (map) + _rl_keymap = map; +} + +Keymap +rl_get_keymap () +{ + return (_rl_keymap); +} + +void +rl_set_keymap_from_edit_mode () +{ + if (rl_editing_mode == emacs_mode) + _rl_keymap = emacs_standard_keymap; +#if defined (VI_MODE) + else if (rl_editing_mode == vi_mode) + _rl_keymap = vi_insertion_keymap; +#endif /* VI_MODE */ +} + +char * +rl_get_keymap_name_from_edit_mode () +{ + if (rl_editing_mode == emacs_mode) + return "emacs"; +#if defined (VI_MODE) + else if (rl_editing_mode == vi_mode) + return "vi"; +#endif /* VI_MODE */ + else + return "none"; +} + +/* **************************************************************** */ +/* */ +/* Key Binding and Function Information */ +/* */ +/* **************************************************************** */ + +/* Each of the following functions produces information about the + state of keybindings and functions known to Readline. The info + is always printed to rl_outstream, and in such a way that it can + be read back in (i.e., passed to rl_parse_and_bind ()). */ + +/* Print the names of functions known to Readline. */ +void +rl_list_funmap_names () +{ + register int i; + const char **funmap_names; + + funmap_names = rl_funmap_names (); + + if (!funmap_names) + return; + + for (i = 0; funmap_names[i]; i++) + fprintf (rl_outstream, "%s\n", funmap_names[i]); + + xfree (funmap_names); +} + +static char * +_rl_get_keyname (key) + int key; +{ + char *keyname; + int i, c; + + keyname = (char *)xmalloc (8); + + c = key; + /* Since this is going to be used to write out keysequence-function + pairs for possible inclusion in an inputrc file, we don't want to + do any special meta processing on KEY. */ + +#if 1 + /* XXX - Experimental */ + /* We might want to do this, but the old version of the code did not. */ + + /* If this is an escape character, we don't want to do any more processing. + Just add the special ESC key sequence and return. */ + if (c == ESC) + { + keyname[0] = '\\'; + keyname[1] = 'e'; + keyname[2] = '\0'; + return keyname; + } +#endif + + /* RUBOUT is translated directly into \C-? */ + if (key == RUBOUT) + { + keyname[0] = '\\'; + keyname[1] = 'C'; + keyname[2] = '-'; + keyname[3] = '?'; + keyname[4] = '\0'; + return keyname; + } + + i = 0; + /* Now add special prefixes needed for control characters. This can + potentially change C. */ + if (CTRL_CHAR (c)) + { + keyname[i++] = '\\'; + keyname[i++] = 'C'; + keyname[i++] = '-'; + c = _rl_to_lower (UNCTRL (c)); + } + + /* XXX experimental code. Turn the characters that are not ASCII or + ISO Latin 1 (128 - 159) into octal escape sequences (\200 - \237). + This changes C. */ + if (c >= 128 && c <= 159) + { + keyname[i++] = '\\'; + keyname[i++] = '2'; + c -= 128; + keyname[i++] = (c / 8) + '0'; + c = (c % 8) + '0'; + } + + /* Now, if the character needs to be quoted with a backslash, do that. */ + if (c == '\\' || c == '"') + keyname[i++] = '\\'; + + /* Now add the key, terminate the string, and return it. */ + keyname[i++] = (char) c; + keyname[i] = '\0'; + + return keyname; +} + +/* Return a NULL terminated array of strings which represent the key + sequences that are used to invoke FUNCTION in MAP. */ +char ** +rl_invoking_keyseqs_in_map (function, map) + rl_command_func_t *function; + Keymap map; +{ + register int key; + char **result; + int result_index, result_size; + + result = (char **)NULL; + result_index = result_size = 0; + + for (key = 0; key < KEYMAP_SIZE; key++) + { + switch (map[key].type) + { + case ISMACR: + /* Macros match, if, and only if, the pointers are identical. + Thus, they are treated exactly like functions in here. */ + case ISFUNC: + /* If the function in the keymap is the one we are looking for, + then add the current KEY to the list of invoking keys. */ + if (map[key].function == function) + { + char *keyname; + + keyname = _rl_get_keyname (key); + + if (result_index + 2 > result_size) + { + result_size += 10; + result = (char **)xrealloc (result, result_size * sizeof (char *)); + } + + result[result_index++] = keyname; + result[result_index] = (char *)NULL; + } + break; + + case ISKMAP: + { + char **seqs; + register int i; + + /* Find the list of keyseqs in this map which have FUNCTION as + their target. Add the key sequences found to RESULT. */ + if (map[key].function) + seqs = + rl_invoking_keyseqs_in_map (function, FUNCTION_TO_KEYMAP (map, key)); + else + break; + + if (seqs == 0) + break; + + for (i = 0; seqs[i]; i++) + { + char *keyname = (char *)xmalloc (6 + strlen (seqs[i])); + + if (key == ESC) + { + /* If ESC is the meta prefix and we're converting chars + with the eighth bit set to ESC-prefixed sequences, then + we can use \M-. Otherwise we need to use the sequence + for ESC. */ + if (_rl_convert_meta_chars_to_ascii && map[ESC].type == ISKMAP) + sprintf (keyname, "\\M-"); + else + sprintf (keyname, "\\e"); + } + else if (CTRL_CHAR (key)) + sprintf (keyname, "\\C-%c", _rl_to_lower (UNCTRL (key))); + else if (key == RUBOUT) + sprintf (keyname, "\\C-?"); + else if (key == '\\' || key == '"') + { + keyname[0] = '\\'; + keyname[1] = (char) key; + keyname[2] = '\0'; + } + else + { + keyname[0] = (char) key; + keyname[1] = '\0'; + } + + strcat (keyname, seqs[i]); + xfree (seqs[i]); + + if (result_index + 2 > result_size) + { + result_size += 10; + result = (char **)xrealloc (result, result_size * sizeof (char *)); + } + + result[result_index++] = keyname; + result[result_index] = (char *)NULL; + } + + xfree (seqs); + } + break; + } + } + return (result); +} + +/* Return a NULL terminated array of strings which represent the key + sequences that can be used to invoke FUNCTION using the current keymap. */ +char ** +rl_invoking_keyseqs (function) + rl_command_func_t *function; +{ + return (rl_invoking_keyseqs_in_map (function, _rl_keymap)); +} + +/* Print all of the functions and their bindings to rl_outstream. If + PRINT_READABLY is non-zero, then print the output in such a way + that it can be read back in. */ +void +rl_function_dumper (print_readably) + int print_readably; +{ + register int i; + const char **names; + const char *name; + + names = rl_funmap_names (); + + fprintf (rl_outstream, "\n"); + + for (i = 0; name = names[i]; i++) + { + rl_command_func_t *function; + char **invokers; + + function = rl_named_function (name); + invokers = rl_invoking_keyseqs_in_map (function, _rl_keymap); + + if (print_readably) + { + if (!invokers) + fprintf (rl_outstream, "# %s (not bound)\n", name); + else + { + register int j; + + for (j = 0; invokers[j]; j++) + { + fprintf (rl_outstream, "\"%s\": %s\n", + invokers[j], name); + xfree (invokers[j]); + } + + xfree (invokers); + } + } + else + { + if (!invokers) + fprintf (rl_outstream, "%s is not bound to any keys\n", + name); + else + { + register int j; + + fprintf (rl_outstream, "%s can be found on ", name); + + for (j = 0; invokers[j] && j < 5; j++) + { + fprintf (rl_outstream, "\"%s\"%s", invokers[j], + invokers[j + 1] ? ", " : ".\n"); + } + + if (j == 5 && invokers[j]) + fprintf (rl_outstream, "...\n"); + + for (j = 0; invokers[j]; j++) + xfree (invokers[j]); + + xfree (invokers); + } + } + } + + xfree (names); +} + +/* Print all of the current functions and their bindings to + rl_outstream. If an explicit argument is given, then print + the output in such a way that it can be read back in. */ +int +rl_dump_functions (count, key) + int count, key; +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_function_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +static void +_rl_macro_dumper_internal (print_readably, map, prefix) + int print_readably; + Keymap map; + char *prefix; +{ + register int key; + char *keyname, *out; + int prefix_len; + + for (key = 0; key < KEYMAP_SIZE; key++) + { + switch (map[key].type) + { + case ISMACR: + keyname = _rl_get_keyname (key); + out = _rl_untranslate_macro_value ((char *)map[key].function, 0); + + if (print_readably) + fprintf (rl_outstream, "\"%s%s\": \"%s\"\n", prefix ? prefix : "", + keyname, + out ? out : ""); + else + fprintf (rl_outstream, "%s%s outputs %s\n", prefix ? prefix : "", + keyname, + out ? out : ""); + xfree (keyname); + xfree (out); + break; + case ISFUNC: + break; + case ISKMAP: + prefix_len = prefix ? strlen (prefix) : 0; + if (key == ESC) + { + keyname = (char *)xmalloc (3 + prefix_len); + if (prefix) + strcpy (keyname, prefix); + keyname[prefix_len] = '\\'; + keyname[prefix_len + 1] = 'e'; + keyname[prefix_len + 2] = '\0'; + } + else + { + keyname = _rl_get_keyname (key); + if (prefix) + { + out = (char *)xmalloc (strlen (keyname) + prefix_len + 1); + strcpy (out, prefix); + strcpy (out + prefix_len, keyname); + xfree (keyname); + keyname = out; + } + } + + _rl_macro_dumper_internal (print_readably, FUNCTION_TO_KEYMAP (map, key), keyname); + xfree (keyname); + break; + } + } +} + +void +rl_macro_dumper (print_readably) + int print_readably; +{ + _rl_macro_dumper_internal (print_readably, _rl_keymap, (char *)NULL); +} + +int +rl_dump_macros (count, key) + int count, key; +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_macro_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +static char * +_rl_get_string_variable_value (name) + const char *name; +{ + static char numbuf[32]; + char *ret; + + if (_rl_stricmp (name, "bell-style") == 0) + { + switch (_rl_bell_preference) + { + case NO_BELL: + return "none"; + case VISIBLE_BELL: + return "visible"; + case AUDIBLE_BELL: + default: + return "audible"; + } + } + else if (_rl_stricmp (name, "comment-begin") == 0) + return (_rl_comment_begin ? _rl_comment_begin : RL_COMMENT_BEGIN_DEFAULT); + else if (_rl_stricmp (name, "completion-display-width") == 0) + { + sprintf (numbuf, "%d", _rl_completion_columns); + return (numbuf); + } + else if (_rl_stricmp (name, "completion-prefix-display-length") == 0) + { + sprintf (numbuf, "%d", _rl_completion_prefix_display_length); + return (numbuf); + } + else if (_rl_stricmp (name, "completion-query-items") == 0) + { + sprintf (numbuf, "%d", rl_completion_query_items); + return (numbuf); + } + else if (_rl_stricmp (name, "editing-mode") == 0) + return (rl_get_keymap_name_from_edit_mode ()); + else if (_rl_stricmp (name, "history-size") == 0) + { + sprintf (numbuf, "%d", history_is_stifled() ? history_max_entries : 0); + return (numbuf); + } + else if (_rl_stricmp (name, "isearch-terminators") == 0) + { + if (_rl_isearch_terminators == 0) + return 0; + ret = _rl_untranslate_macro_value (_rl_isearch_terminators, 0); + if (ret) + { + strncpy (numbuf, ret, sizeof (numbuf) - 1); + xfree (ret); + numbuf[sizeof(numbuf) - 1] = '\0'; + } + else + numbuf[0] = '\0'; + return numbuf; + } + else if (_rl_stricmp (name, "keymap") == 0) + { + ret = rl_get_keymap_name (_rl_keymap); + if (ret == 0) + ret = rl_get_keymap_name_from_edit_mode (); + return (ret ? ret : "none"); + } + else if (_rl_stricmp (name, "keyseq-timeout") == 0) + { + sprintf (numbuf, "%d", _rl_keyseq_timeout); + return (numbuf); + } + else if (_rl_stricmp (name, "emacs-mode-string") == 0) + return (_rl_emacs_mode_str ? _rl_emacs_mode_str : RL_EMACS_MODESTR_DEFAULT); + else if (_rl_stricmp (name, "vi-cmd-mode-string") == 0) + return (_rl_emacs_mode_str ? _rl_emacs_mode_str : RL_VI_CMD_MODESTR_DEFAULT); + else if (_rl_stricmp (name, "vi-ins-mode-string") == 0) + return (_rl_emacs_mode_str ? _rl_emacs_mode_str : RL_VI_INS_MODESTR_DEFAULT); + else + return (0); +} + +void +rl_variable_dumper (print_readably) + int print_readably; +{ + int i; + char *v; + + for (i = 0; boolean_varlist[i].name; i++) + { + if (print_readably) + fprintf (rl_outstream, "set %s %s\n", boolean_varlist[i].name, + *boolean_varlist[i].value ? "on" : "off"); + else + fprintf (rl_outstream, "%s is set to `%s'\n", boolean_varlist[i].name, + *boolean_varlist[i].value ? "on" : "off"); + } + + for (i = 0; string_varlist[i].name; i++) + { + v = _rl_get_string_variable_value (string_varlist[i].name); + if (v == 0) /* _rl_isearch_terminators can be NULL */ + continue; + if (print_readably) + fprintf (rl_outstream, "set %s %s\n", string_varlist[i].name, v); + else + fprintf (rl_outstream, "%s is set to `%s'\n", string_varlist[i].name, v); + } +} + +/* Print all of the current variables and their values to + rl_outstream. If an explicit argument is given, then print + the output in such a way that it can be read back in. */ +int +rl_dump_variables (count, key) + int count, key; +{ + if (rl_dispatching) + fprintf (rl_outstream, "\r\n"); + rl_variable_dumper (rl_explicit_arg); + rl_on_new_line (); + return (0); +} + +/* Return non-zero if any members of ARRAY are a substring in STRING. */ +static int +substring_member_of_array (string, array) + const char *string; + const char * const *array; +{ + while (*array) + { + if (_rl_strindex (string, *array)) + return (1); + array++; + } + return (0); +} diff --git a/lib/readline/chardefs.h b/lib/readline/chardefs.h index 1fa1b089..43aab7a6 100644 --- a/lib/readline/chardefs.h +++ b/lib/readline/chardefs.h @@ -73,7 +73,7 @@ #endif #if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) && !defined (__cplusplus) -# define isxdigit(c) (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +# define isxdigit(c) (isdigit((unsigned char)(c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) #endif #if defined (CTYPE_NON_ASCII) @@ -87,13 +87,13 @@ /* Beware: these only work with single-byte ASCII characters. */ -#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) -#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) -#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) -#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) -#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) -#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) -#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum ((unsigned char)c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha ((unsigned char)c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit ((unsigned char)c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower ((unsigned char)c)) +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper ((unsigned char)c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit ((unsigned char)c)) #define _rl_lowercase_p(c) (NON_NEGATIVE(c) && ISLOWER(c)) #define _rl_uppercase_p(c) (NON_NEGATIVE(c) && ISUPPER(c)) diff --git a/lib/readline/chardefs.h~ b/lib/readline/chardefs.h~ new file mode 100644 index 00000000..1fa1b089 --- /dev/null +++ b/lib/readline/chardefs.h~ @@ -0,0 +1,164 @@ +/* chardefs.h -- Character definitions for readline. */ + +/* Copyright (C) 1994-2009 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _CHARDEFS_H_ +#define _CHARDEFS_H_ + +#include <ctype.h> + +#if defined (HAVE_CONFIG_H) +# if defined (HAVE_STRING_H) +# if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) +# include <memory.h> +# endif +# include <string.h> +# endif /* HAVE_STRING_H */ +# if defined (HAVE_STRINGS_H) +# include <strings.h> +# endif /* HAVE_STRINGS_H */ +#else +# include <string.h> +#endif /* !HAVE_CONFIG_H */ + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifdef CTRL +# undef CTRL +#endif +#ifdef UNCTRL +# undef UNCTRL +#endif + +/* Some character stuff. */ +#define control_character_threshold 0x020 /* Smaller than this is control. */ +#define control_character_mask 0x1f /* 0x20 - 1 */ +#define meta_character_threshold 0x07f /* Larger than this is Meta. */ +#define control_character_bit 0x40 /* 0x000000, must be off. */ +#define meta_character_bit 0x080 /* x0000000, must be on. */ +#define largest_char 255 /* Largest character value. */ + +#define CTRL_CHAR(c) ((c) < control_character_threshold && (((c) & 0x80) == 0)) +#define META_CHAR(c) ((c) > meta_character_threshold && (c) <= largest_char) + +#define CTRL(c) ((c) & control_character_mask) +#define META(c) ((c) | meta_character_bit) + +#define UNMETA(c) ((c) & (~meta_character_bit)) +#define UNCTRL(c) _rl_to_upper(((c)|control_character_bit)) + +#if defined STDC_HEADERS || (!defined (isascii) && !defined (HAVE_ISASCII)) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) isascii(c) +#endif + +#if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) && !defined (__cplusplus) +# define isxdigit(c) (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#endif + +#if defined (CTYPE_NON_ASCII) +# define NON_NEGATIVE(c) 1 +#else +# define NON_NEGATIVE(c) ((unsigned char)(c) == (c)) +#endif + +/* Some systems define these; we want our definitions. */ +#undef ISPRINT + +/* Beware: these only work with single-byte ASCII characters. */ + +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) + +#define _rl_lowercase_p(c) (NON_NEGATIVE(c) && ISLOWER(c)) +#define _rl_uppercase_p(c) (NON_NEGATIVE(c) && ISUPPER(c)) +#define _rl_digit_p(c) ((c) >= '0' && (c) <= '9') + +#define _rl_pure_alphabetic(c) (NON_NEGATIVE(c) && ISALPHA(c)) +#define ALPHABETIC(c) (NON_NEGATIVE(c) && ISALNUM(c)) + +#ifndef _rl_to_upper +# define _rl_to_upper(c) (_rl_lowercase_p(c) ? toupper((unsigned char)c) : (c)) +# define _rl_to_lower(c) (_rl_uppercase_p(c) ? tolower((unsigned char)c) : (c)) +#endif + +#ifndef _rl_digit_value +# define _rl_digit_value(x) ((x) - '0') +#endif + +#ifndef _rl_isident +# define _rl_isident(c) (ISALNUM(c) || (c) == '_') +#endif + +#ifndef ISOCTAL +# define ISOCTAL(c) ((c) >= '0' && (c) <= '7') +#endif +#define OCTVALUE(c) ((c) - '0') + +#define HEXVALUE(c) \ + (((c) >= 'a' && (c) <= 'f') \ + ? (c)-'a'+10 \ + : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0') + +#ifndef NEWLINE +#define NEWLINE '\n' +#endif + +#ifndef RETURN +#define RETURN CTRL('M') +#endif + +#ifndef RUBOUT +#define RUBOUT 0x7f +#endif + +#ifndef TAB +#define TAB '\t' +#endif + +#ifdef ABORT_CHAR +#undef ABORT_CHAR +#endif +#define ABORT_CHAR CTRL('G') + +#ifdef PAGE +#undef PAGE +#endif +#define PAGE CTRL('L') + +#ifdef SPACE +#undef SPACE +#endif +#define SPACE ' ' /* XXX - was 0x20 */ + +#ifdef ESC +#undef ESC +#endif +#define ESC CTRL('[') + +#endif /* _CHARDEFS_H_ */ diff --git a/lib/readline/histfile.c b/lib/readline/histfile.c index 8341c208..aaab7525 100644 --- a/lib/readline/histfile.c +++ b/lib/readline/histfile.c @@ -114,7 +114,7 @@ int history_lines_written_to_file = 0; /* Does S look like the beginning of a history timestamp entry? Placeholder for more extensive tests. */ -#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((s)[1]) ) +#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((unsigned char)(s)[1]) ) /* Return the string that should be used in the place of this filename. This only matters when you don't specify the diff --git a/lib/readline/histfile.c~ b/lib/readline/histfile.c~ new file mode 100644 index 00000000..8341c208 --- /dev/null +++ b/lib/readline/histfile.c~ @@ -0,0 +1,650 @@ +/* histfile.c - functions to manipulate the history file. */ + +/* Copyright (C) 1989-2010 Free Software Foundation, Inc. + + This file contains the GNU History Library (History), a set of + routines for managing the text of previously typed lines. + + History is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + History is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with History. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* The goal is to make the implementation transparent, so that you + don't have to know what data types are used, just what functions + you can call. I think I have done that. */ + +#define READLINE_LIBRARY + +#if defined (__TANDEM) +# include <floss.h> +#endif + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#include <stdio.h> + +#include <sys/types.h> +#if ! defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif +#include "posixstat.h" +#include <fcntl.h> + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include <ctype.h> + +#if defined (__EMX__) +# undef HAVE_MMAP +#endif + +#ifdef HISTORY_USE_MMAP +# include <sys/mman.h> + +# ifdef MAP_FILE +# define MAP_RFLAGS (MAP_FILE|MAP_PRIVATE) +# define MAP_WFLAGS (MAP_FILE|MAP_SHARED) +# else +# define MAP_RFLAGS MAP_PRIVATE +# define MAP_WFLAGS MAP_SHARED +# endif + +# ifndef MAP_FAILED +# define MAP_FAILED ((void *)-1) +# endif + +#endif /* HISTORY_USE_MMAP */ + +/* If we're compiling for __EMX__ (OS/2) or __CYGWIN__ (cygwin32 environment + on win 95/98/nt), we want to open files with O_BINARY mode so that there + is no \n -> \r\n conversion performed. On other systems, we don't want to + mess around with O_BINARY at all, so we ensure that it's defined to 0. */ +#if defined (__EMX__) || defined (__CYGWIN__) +# ifndef O_BINARY +# define O_BINARY 0 +# endif +#else /* !__EMX__ && !__CYGWIN__ */ +# undef O_BINARY +# define O_BINARY 0 +#endif /* !__EMX__ && !__CYGWIN__ */ + +#include <errno.h> +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "history.h" +#include "histlib.h" + +#include "rlshell.h" +#include "xmalloc.h" + +/* If non-zero, we write timestamps to the history file in history_do_write() */ +int history_write_timestamps = 0; + +/* Immediately after a call to read_history() or read_history_range(), this + will return the number of lines just read from the history file in that + call. */ +int history_lines_read_from_file = 0; + +/* Immediately after a call to write_history() or history_do_write(), this + will return the number of lines just written to the history file in that + call. This also works with history_truncate_file. */ +int history_lines_written_to_file = 0; + +/* Does S look like the beginning of a history timestamp entry? Placeholder + for more extensive tests. */ +#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((s)[1]) ) + +/* Return the string that should be used in the place of this + filename. This only matters when you don't specify the + filename to read_history (), or write_history (). */ +static char * +history_filename (filename) + const char *filename; +{ + char *return_val; + const char *home; + int home_len; + + return_val = filename ? savestring (filename) : (char *)NULL; + + if (return_val) + return (return_val); + + home = sh_get_env_value ("HOME"); +#if defined (_WIN32) + if (home == 0) + home = sh_get_env_value ("APPDATA"); +#endif + + if (home == 0) + return (NULL); + else + home_len = strlen (home); + + return_val = (char *)xmalloc (2 + home_len + 8); /* strlen(".history") == 8 */ + strcpy (return_val, home); + return_val[home_len] = '/'; +#if defined (__MSDOS__) + strcpy (return_val + home_len + 1, "_history"); +#else + strcpy (return_val + home_len + 1, ".history"); +#endif + + return (return_val); +} + +static char * +history_backupfile (filename) + const char *filename; +{ + char *ret; + size_t len; + + len = strlen (filename); + ret = xmalloc (len + 2); + strcpy (ret, filename); + ret[len] = '-'; + ret[len+1] = '\0'; + return ret; +} + +/* Add the contents of FILENAME to the history list, a line at a time. + If FILENAME is NULL, then read from ~/.history. Returns 0 if + successful, or errno if not. */ +int +read_history (filename) + const char *filename; +{ + return (read_history_range (filename, 0, -1)); +} + +/* Read a range of lines from FILENAME, adding them to the history list. + Start reading at the FROM'th line and end at the TO'th. If FROM + is zero, start at the beginning. If TO is less than FROM, read + until the end of the file. If FILENAME is NULL, then read from + ~/.history. Returns 0 if successful, or errno if not. */ +int +read_history_range (filename, from, to) + const char *filename; + int from, to; +{ + register char *line_start, *line_end, *p; + char *input, *buffer, *bufend, *last_ts; + int file, current_line, chars_read; + struct stat finfo; + size_t file_size; +#if defined (EFBIG) + int overflow_errno = EFBIG; +#elif defined (EOVERFLOW) + int overflow_errno = EOVERFLOW; +#else + int overflow_errno = EIO; +#endif + + history_lines_read_from_file = 0; + + buffer = last_ts = (char *)NULL; + input = history_filename (filename); + file = input ? open (input, O_RDONLY|O_BINARY, 0666) : -1; + + if ((file < 0) || (fstat (file, &finfo) == -1)) + goto error_and_exit; + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if (file_size != finfo.st_size || file_size + 1 < file_size) + { + errno = overflow_errno; + goto error_and_exit; + } + +#ifdef HISTORY_USE_MMAP + /* We map read/write and private so we can change newlines to NULs without + affecting the underlying object. */ + buffer = (char *)mmap (0, file_size, PROT_READ|PROT_WRITE, MAP_RFLAGS, file, 0); + if ((void *)buffer == MAP_FAILED) + { + errno = overflow_errno; + goto error_and_exit; + } + chars_read = file_size; +#else + buffer = (char *)malloc (file_size + 1); + if (buffer == 0) + { + errno = overflow_errno; + goto error_and_exit; + } + + chars_read = read (file, buffer, file_size); +#endif + if (chars_read < 0) + { + error_and_exit: + if (errno != 0) + chars_read = errno; + else + chars_read = EIO; + if (file >= 0) + close (file); + + FREE (input); +#ifndef HISTORY_USE_MMAP + FREE (buffer); +#endif + + return (chars_read); + } + + close (file); + + /* Set TO to larger than end of file if negative. */ + if (to < 0) + to = chars_read; + + /* Start at beginning of file, work to end. */ + bufend = buffer + chars_read; + current_line = 0; + + /* Skip lines until we are at FROM. */ + for (line_start = line_end = buffer; line_end < bufend && current_line < from; line_end++) + if (*line_end == '\n') + { + p = line_end + 1; + /* If we see something we think is a timestamp, continue with this + line. We should check more extensively here... */ + if (HIST_TIMESTAMP_START(p) == 0) + current_line++; + line_start = p; + } + + /* If there are lines left to gobble, then gobble them now. */ + for (line_end = line_start; line_end < bufend; line_end++) + if (*line_end == '\n') + { + /* Change to allow Windows-like \r\n end of line delimiter. */ + if (line_end > line_start && line_end[-1] == '\r') + line_end[-1] = '\0'; + else + *line_end = '\0'; + + if (*line_start) + { + if (HIST_TIMESTAMP_START(line_start) == 0) + { + add_history (line_start); + if (last_ts) + { + add_history_time (last_ts); + last_ts = NULL; + } + } + else + { + last_ts = line_start; + current_line--; + } + } + + current_line++; + + if (current_line >= to) + break; + + line_start = line_end + 1; + } + + history_lines_read_from_file = current_line; + + FREE (input); +#ifndef HISTORY_USE_MMAP + FREE (buffer); +#else + munmap (buffer, file_size); +#endif + + return (0); +} + +/* Truncate the history file FNAME, leaving only LINES trailing lines. + If FNAME is NULL, then use ~/.history. Returns 0 on success, errno + on failure. */ +int +history_truncate_file (fname, lines) + const char *fname; + int lines; +{ + char *buffer, *filename, *bakname, *bp, *bp1; /* bp1 == bp+1 */ + int file, chars_read, rv, orig_lines, exists; + struct stat finfo; + size_t file_size; + + history_lines_written_to_file = 0; + + buffer = (char *)NULL; + filename = history_filename (fname); + bakname = 0; + file = filename ? open (filename, O_RDONLY|O_BINARY, 0666) : -1; + rv = exists = 0; + + /* Don't try to truncate non-regular files. */ + if (file == -1 || fstat (file, &finfo) == -1) + { + rv = errno; + if (file != -1) + close (file); + goto truncate_exit; + } + exists = 1; + + if (S_ISREG (finfo.st_mode) == 0) + { + close (file); +#ifdef EFTYPE + rv = EFTYPE; +#else + rv = EINVAL; +#endif + goto truncate_exit; + } + + file_size = (size_t)finfo.st_size; + + /* check for overflow on very large files */ + if (file_size != finfo.st_size || file_size + 1 < file_size) + { + close (file); +#if defined (EFBIG) + rv = errno = EFBIG; +#elif defined (EOVERFLOW) + rv = errno = EOVERFLOW; +#else + rv = errno = EINVAL; +#endif + goto truncate_exit; + } + + buffer = (char *)malloc (file_size + 1); + if (buffer == 0) + { + rv = errno; + close (file); + goto truncate_exit; + } + + chars_read = read (file, buffer, file_size); + close (file); + + if (chars_read <= 0) + { + rv = (chars_read < 0) ? errno : 0; + goto truncate_exit; + } + + orig_lines = lines; + /* Count backwards from the end of buffer until we have passed + LINES lines. bp1 is set funny initially. But since bp[1] can't + be a comment character (since it's off the end) and *bp can't be + both a newline and the history comment character, it should be OK. */ + for (bp1 = bp = buffer + chars_read - 1; lines && bp > buffer; bp--) + { + if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) + lines--; + bp1 = bp; + } + + /* If this is the first line, then the file contains exactly the + number of lines we want to truncate to, so we don't need to do + anything. It's the first line if we don't find a newline between + the current value of i and 0. Otherwise, write from the start of + this line until the end of the buffer. */ + for ( ; bp > buffer; bp--) + { + if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0) + { + bp++; + break; + } + bp1 = bp; + } + + /* Write only if there are more lines in the file than we want to + truncate to. */ + if (bp <= buffer) + { + rv = 0; + /* No-op if LINES == 0 at this point */ + history_lines_written_to_file = orig_lines - lines; + goto truncate_exit; + } + + bakname = history_backupfile (filename); + if (filename && bakname) + rename (filename, bakname); + + if ((file = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0600)) != -1) + { + if (write (file, bp, chars_read - (bp - buffer)) < 0) + rv = errno; + +#if defined (__BEOS__) + /* BeOS ignores O_TRUNC. */ + ftruncate (file, chars_read - (bp - buffer)); +#endif + + if (close (file) < 0 && rv == 0) + rv = errno; + } + else + rv = errno; + + truncate_exit: + FREE (buffer); + + history_lines_written_to_file = orig_lines - lines; + + if (rv != 0 && filename && bakname) + rename (bakname, filename); + else if (rv == 0 && bakname) + unlink (bakname); + + /* Make sure the new filename is owned by the same user as the old. If one + user is running this, it's a no-op. If the shell is running after sudo + with a shared history file, we don't want to leave the history file + owned by root. */ + if (rv == 0 && exists) + chown (filename, finfo.st_uid, finfo.st_gid); + + xfree (filename); + FREE (bakname); + + return rv; +} + +/* Workhorse function for writing history. Writes the last NELEMENT entries + from the history list to FILENAME. OVERWRITE is non-zero if you + wish to replace FILENAME with the entries. */ +static int +history_do_write (filename, nelements, overwrite) + const char *filename; + int nelements, overwrite; +{ + register int i; + char *output, *bakname; + int file, mode, rv, exists; + struct stat finfo; +#ifdef HISTORY_USE_MMAP + size_t cursize; + + history_lines_written_to_file = 0; + + mode = overwrite ? O_RDWR|O_CREAT|O_TRUNC|O_BINARY : O_RDWR|O_APPEND|O_BINARY; +#else + mode = overwrite ? O_WRONLY|O_CREAT|O_TRUNC|O_BINARY : O_WRONLY|O_APPEND|O_BINARY; +#endif + output = history_filename (filename); + bakname = (overwrite && output) ? history_backupfile (output) : 0; + exists = output ? (stat (output, &finfo) == 0) : 0; + + if (output && bakname) + rename (output, bakname); + + file = output ? open (output, mode, 0600) : -1; + rv = 0; + + if (file == -1) + { + rv = errno; + if (output && bakname) + rename (bakname, output); + FREE (output); + FREE (bakname); + return (rv); + } + +#ifdef HISTORY_USE_MMAP + cursize = overwrite ? 0 : lseek (file, 0, SEEK_END); +#endif + + if (nelements > history_length) + nelements = history_length; + + /* Build a buffer of all the lines to write, and write them in one syscall. + Suggested by Peter Ho (peter@robosts.oxford.ac.uk). */ + { + HIST_ENTRY **the_history; /* local */ + register int j; + int buffer_size; + char *buffer; + + the_history = history_list (); + /* Calculate the total number of bytes to write. */ + for (buffer_size = 0, i = history_length - nelements; i < history_length; i++) +#if 0 + buffer_size += 2 + HISTENT_BYTES (the_history[i]); +#else + { + if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) + buffer_size += strlen (the_history[i]->timestamp) + 1; + buffer_size += strlen (the_history[i]->line) + 1; + } +#endif + + /* Allocate the buffer, and fill it. */ +#ifdef HISTORY_USE_MMAP + if (ftruncate (file, buffer_size+cursize) == -1) + goto mmap_error; + buffer = (char *)mmap (0, buffer_size, PROT_READ|PROT_WRITE, MAP_WFLAGS, file, cursize); + if ((void *)buffer == MAP_FAILED) + { +mmap_error: + rv = errno; + close (file); + if (output && bakname) + rename (bakname, output); + FREE (output); + FREE (bakname); + return rv; + } +#else + buffer = (char *)malloc (buffer_size); + if (buffer == 0) + { + rv = errno; + close (file); + if (output && bakname) + rename (bakname, output); + FREE (output); + FREE (bakname); + return rv; + } +#endif + + for (j = 0, i = history_length - nelements; i < history_length; i++) + { + if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) + { + strcpy (buffer + j, the_history[i]->timestamp); + j += strlen (the_history[i]->timestamp); + buffer[j++] = '\n'; + } + strcpy (buffer + j, the_history[i]->line); + j += strlen (the_history[i]->line); + buffer[j++] = '\n'; + } + +#ifdef HISTORY_USE_MMAP + if (msync (buffer, buffer_size, MS_ASYNC) != 0 || munmap (buffer, buffer_size) != 0) + rv = errno; +#else + if (write (file, buffer, buffer_size) < 0) + rv = errno; + xfree (buffer); +#endif + } + + history_lines_written_to_file = nelements; + + if (close (file) < 0 && rv == 0) + rv = errno; + + if (rv != 0 && output && bakname) + rename (bakname, output); + else if (rv == 0 && bakname) + unlink (bakname); + + /* Make sure the new filename is owned by the same user as the old. If one + user is running this, it's a no-op. If the shell is running after sudo + with a shared history file, we don't want to leave the history file + owned by root. */ + if (rv == 0 && exists) + chown (output, finfo.st_uid, finfo.st_gid); + + FREE (output); + FREE (bakname); + + return (rv); +} + +/* Append NELEMENT entries to FILENAME. The entries appended are from + the end of the list minus NELEMENTs up to the end of the list. */ +int +append_history (nelements, filename) + int nelements; + const char *filename; +{ + return (history_do_write (filename, nelements, HISTORY_APPEND)); +} + +/* Overwrite FILENAME with the current history. If FILENAME is NULL, + then write the history list to ~/.history. Values returned + are as in read_history ().*/ +int +write_history (filename) + const char *filename; +{ + return (history_do_write (filename, history_length, HISTORY_OVERWRITE)); +} diff --git a/lib/readline/readline.c b/lib/readline/readline.c index 6d2e36b9..b0f2191a 100644 --- a/lib/readline/readline.c +++ b/lib/readline/readline.c @@ -533,9 +533,9 @@ readline_internal_charloop () int c, code, lk; lastc = EOF; - eof_found = 0; #if !defined (READLINE_CALLBACKS) + eof_found = 0; while (rl_done == 0) { #endif diff --git a/lib/readline/readline.c~ b/lib/readline/readline.c~ new file mode 100644 index 00000000..6d2e36b9 --- /dev/null +++ b/lib/readline/readline.c~ @@ -0,0 +1,1426 @@ +/* readline.c -- a general facility for reading lines of input + with emacs style editing and completion. */ + +/* Copyright (C) 1987-2013 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#include <sys/types.h> +#include "posixstat.h" +#include <fcntl.h> +#if defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> +#include "posixjmp.h" +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (__EMX__) +# define INCL_DOSPROCESS +# include <os2.h> +#endif /* __EMX__ */ + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +#ifndef RL_LIBRARY_VERSION +# define RL_LIBRARY_VERSION "5.1" +#endif + +#ifndef RL_READLINE_VERSION +# define RL_READLINE_VERSION 0x0501 +#endif + +extern void _rl_free_history_entry PARAMS((HIST_ENTRY *)); + +#if defined (COLOR_SUPPORT) +extern void _rl_parse_colors PARAMS((void)); /* XXX */ +#endif + + +/* Forward declarations used in this file. */ +static char *readline_internal PARAMS((void)); +static void readline_initialize_everything PARAMS((void)); + +static void bind_arrow_keys_internal PARAMS((Keymap)); +static void bind_arrow_keys PARAMS((void)); + +static void bind_bracketed_paste_prefix PARAMS((void)); + +static void readline_default_bindings PARAMS((void)); +static void reset_default_bindings PARAMS((void)); + +static int _rl_subseq_result PARAMS((int, Keymap, int, int)); +static int _rl_subseq_getchar PARAMS((int)); + +/* **************************************************************** */ +/* */ +/* Line editing input utility */ +/* */ +/* **************************************************************** */ + +const char *rl_library_version = RL_LIBRARY_VERSION; + +int rl_readline_version = RL_READLINE_VERSION; + +/* True if this is `real' readline as opposed to some stub substitute. */ +int rl_gnu_readline_p = 1; + +/* A pointer to the keymap that is currently in use. + By default, it is the standard emacs keymap. */ +Keymap _rl_keymap = emacs_standard_keymap; + +/* The current style of editing. */ +int rl_editing_mode = emacs_mode; + +/* The current insert mode: input (the default) or overwrite */ +int rl_insert_mode = RL_IM_DEFAULT; + +/* Non-zero if we called this function from _rl_dispatch(). It's present + so functions can find out whether they were called from a key binding + or directly from an application. */ +int rl_dispatching; + +/* Non-zero if the previous command was a kill command. */ +int _rl_last_command_was_kill = 0; + +/* The current value of the numeric argument specified by the user. */ +int rl_numeric_arg = 1; + +/* Non-zero if an argument was typed. */ +int rl_explicit_arg = 0; + +/* Temporary value used while generating the argument. */ +int rl_arg_sign = 1; + +/* Non-zero means we have been called at least once before. */ +static int rl_initialized; + +#if 0 +/* If non-zero, this program is running in an EMACS buffer. */ +static int running_in_emacs; +#endif + +/* Flags word encapsulating the current readline state. */ +int rl_readline_state = RL_STATE_NONE; + +/* The current offset in the current input line. */ +int rl_point; + +/* Mark in the current input line. */ +int rl_mark; + +/* Length of the current input line. */ +int rl_end; + +/* Make this non-zero to return the current input_line. */ +int rl_done; + +/* The last function executed by readline. */ +rl_command_func_t *rl_last_func = (rl_command_func_t *)NULL; + +/* Top level environment for readline_internal (). */ +procenv_t _rl_top_level; + +/* The streams we interact with. */ +FILE *_rl_in_stream, *_rl_out_stream; + +/* The names of the streams that we do input and output to. */ +FILE *rl_instream = (FILE *)NULL; +FILE *rl_outstream = (FILE *)NULL; + +/* Non-zero means echo characters as they are read. Defaults to no echo; + set to 1 if there is a controlling terminal, we can get its attributes, + and the attributes include `echo'. Look at rltty.c:prepare_terminal_settings + for the code that sets it. */ +int _rl_echoing_p = 0; + +/* Current prompt. */ +char *rl_prompt = (char *)NULL; +int rl_visible_prompt_length = 0; + +/* Set to non-zero by calling application if it has already printed rl_prompt + and does not want readline to do it the first time. */ +int rl_already_prompted = 0; + +/* The number of characters read in order to type this complete command. */ +int rl_key_sequence_length = 0; + +/* If non-zero, then this is the address of a function to call just + before readline_internal_setup () prints the first prompt. */ +rl_hook_func_t *rl_startup_hook = (rl_hook_func_t *)NULL; + +/* If non-zero, this is the address of a function to call just before + readline_internal_setup () returns and readline_internal starts + reading input characters. */ +rl_hook_func_t *rl_pre_input_hook = (rl_hook_func_t *)NULL; + +/* What we use internally. You should always refer to RL_LINE_BUFFER. */ +static char *the_line; + +/* The character that can generate an EOF. Really read from + the terminal driver... just defaulted here. */ +int _rl_eof_char = CTRL ('D'); + +/* Non-zero makes this the next keystroke to read. */ +int rl_pending_input = 0; + +/* Pointer to a useful terminal name. */ +const char *rl_terminal_name = (const char *)NULL; + +/* Non-zero means to always use horizontal scrolling in line display. */ +int _rl_horizontal_scroll_mode = 0; + +/* Non-zero means to display an asterisk at the starts of history lines + which have been modified. */ +int _rl_mark_modified_lines = 0; + +/* The style of `bell' notification preferred. This can be set to NO_BELL, + AUDIBLE_BELL, or VISIBLE_BELL. */ +int _rl_bell_preference = AUDIBLE_BELL; + +/* String inserted into the line by rl_insert_comment (). */ +char *_rl_comment_begin; + +/* Keymap holding the function currently being executed. */ +Keymap rl_executing_keymap; + +/* Keymap we're currently using to dispatch. */ +Keymap _rl_dispatching_keymap; + +/* Non-zero means to erase entire line, including prompt, on empty input lines. */ +int rl_erase_empty_line = 0; + +/* Non-zero means to read only this many characters rather than up to a + character bound to accept-line. */ +int rl_num_chars_to_read; + +/* Line buffer and maintenance. */ +char *rl_line_buffer = (char *)NULL; +int rl_line_buffer_len = 0; + +/* Key sequence `contexts' */ +_rl_keyseq_cxt *_rl_kscxt = 0; + +int rl_executing_key; +char *rl_executing_keyseq = 0; +int _rl_executing_keyseq_size = 0; + +/* Timeout (specified in milliseconds) when reading characters making up an + ambiguous multiple-key sequence */ +int _rl_keyseq_timeout = 500; + +#define RESIZE_KEYSEQ_BUFFER() \ + do \ + { \ + if (rl_key_sequence_length + 2 >= _rl_executing_keyseq_size) \ + { \ + _rl_executing_keyseq_size += 16; \ + rl_executing_keyseq = xrealloc (rl_executing_keyseq, _rl_executing_keyseq_size); \ + } \ + } \ + while (0); + +/* Forward declarations used by the display, termcap, and history code. */ + +/* **************************************************************** */ +/* */ +/* `Forward' declarations */ +/* */ +/* **************************************************************** */ + +/* Non-zero means do not parse any lines other than comments and + parser directives. */ +unsigned char _rl_parsing_conditionalized_out = 0; + +/* Non-zero means to convert characters with the meta bit set to + escape-prefixed characters so we can indirect through + emacs_meta_keymap or vi_escape_keymap. */ +int _rl_convert_meta_chars_to_ascii = 1; + +/* Non-zero means to output characters with the meta bit set directly + rather than as a meta-prefixed escape sequence. */ +int _rl_output_meta_chars = 0; + +/* Non-zero means to look at the termios special characters and bind + them to equivalent readline functions at startup. */ +int _rl_bind_stty_chars = 1; + +/* Non-zero means to go through the history list at every newline (or + whenever rl_done is set and readline returns) and revert each line to + its initial state. */ +int _rl_revert_all_at_newline = 0; + +/* Non-zero means to honor the termios ECHOCTL bit and echo control + characters corresponding to keyboard-generated signals. */ +int _rl_echo_control_chars = 1; + +/* Non-zero means to prefix the displayed prompt with a character indicating + the editing mode: @ for emacs, : for vi-command, + for vi-insert. */ +int _rl_show_mode_in_prompt = 0; + +/* Non-zero means to attempt to put the terminal in `bracketed paste mode', + where it will prefix pasted text with an escape sequence and send + another to mark the end of the paste. */ +int _rl_enable_bracketed_paste = 0; + +/* **************************************************************** */ +/* */ +/* Top Level Functions */ +/* */ +/* **************************************************************** */ + +/* Non-zero means treat 0200 bit in terminal input as Meta bit. */ +int _rl_meta_flag = 0; /* Forward declaration */ + +/* Set up the prompt and expand it. Called from readline() and + rl_callback_handler_install (). */ +int +rl_set_prompt (prompt) + const char *prompt; +{ + FREE (rl_prompt); + rl_prompt = prompt ? savestring (prompt) : (char *)NULL; + rl_display_prompt = rl_prompt ? rl_prompt : ""; + + rl_visible_prompt_length = rl_expand_prompt (rl_prompt); + return 0; +} + +/* Read a line of input. Prompt with PROMPT. An empty PROMPT means + none. A return value of NULL means that EOF was encountered. */ +char * +readline (prompt) + const char *prompt; +{ + char *value; +#if 0 + int in_callback; +#endif + + /* If we are at EOF return a NULL string. */ + if (rl_pending_input == EOF) + { + rl_clear_pending_input (); + return ((char *)NULL); + } + +#if 0 + /* If readline() is called after installing a callback handler, temporarily + turn off the callback state to avoid ensuing messiness. Patch supplied + by the gdb folks. XXX -- disabled. This can be fooled and readline + left in a strange state by a poorly-timed longjmp. */ + if (in_callback = RL_ISSTATE (RL_STATE_CALLBACK)) + RL_UNSETSTATE (RL_STATE_CALLBACK); +#endif + + rl_set_prompt (prompt); + + rl_initialize (); + if (rl_prep_term_function) + (*rl_prep_term_function) (_rl_meta_flag); + +#if defined (HANDLE_SIGNALS) + rl_set_signals (); +#endif + + value = readline_internal (); + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); + +#if defined (HANDLE_SIGNALS) + rl_clear_signals (); +#endif + +#if 0 + if (in_callback) + RL_SETSTATE (RL_STATE_CALLBACK); +#endif + +#if HAVE_DECL_AUDIT_TTY && defined (ENABLE_TTY_AUDIT_SUPPORT) + if (value) + _rl_audit_tty (value); +#endif + + return (value); +} + +#if defined (READLINE_CALLBACKS) +# define STATIC_CALLBACK +#else +# define STATIC_CALLBACK static +#endif + +STATIC_CALLBACK void +readline_internal_setup () +{ + char *nprompt; + + _rl_in_stream = rl_instream; + _rl_out_stream = rl_outstream; + + /* Enable the meta key only for the duration of readline(), if this + terminal has one and the terminal has been initialized */ + if (_rl_enable_meta & RL_ISSTATE (RL_STATE_TERMPREPPED)) + _rl_enable_meta_key (); + + if (rl_startup_hook) + (*rl_startup_hook) (); + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + rl_vi_insertion_mode (1, 'i'); /* don't want to reset last */ +#endif /* VI_MODE */ + + /* If we're not echoing, we still want to at least print a prompt, because + rl_redisplay will not do it for us. If the calling application has a + custom redisplay function, though, let that function handle it. */ + if (_rl_echoing_p == 0 && rl_redisplay_function == rl_redisplay) + { + if (rl_prompt && rl_already_prompted == 0) + { + nprompt = _rl_strip_prompt (rl_prompt); + fprintf (_rl_out_stream, "%s", nprompt); + fflush (_rl_out_stream); + xfree (nprompt); + } + } + else + { + if (rl_prompt && rl_already_prompted) + rl_on_new_line_with_prompt (); + else + rl_on_new_line (); + (*rl_redisplay_function) (); + } + + if (rl_pre_input_hook) + (*rl_pre_input_hook) (); + + RL_CHECK_SIGNALS (); +} + +STATIC_CALLBACK char * +readline_internal_teardown (eof) + int eof; +{ + char *temp; + HIST_ENTRY *entry; + + RL_CHECK_SIGNALS (); + + /* Restore the original of this history line, iff the line that we + are editing was originally in the history, AND the line has changed. */ + entry = current_history (); + + if (entry && rl_undo_list) + { + temp = savestring (the_line); + rl_revert_line (1, 0); + entry = replace_history_entry (where_history (), the_line, (histdata_t)NULL); + _rl_free_history_entry (entry); + + strcpy (the_line, temp); + xfree (temp); + } + + if (_rl_revert_all_at_newline) + _rl_revert_all_lines (); + + /* At any rate, it is highly likely that this line has an undo list. Get + rid of it now. */ + if (rl_undo_list) + rl_free_undo_list (); + + /* Disable the meta key, if this terminal has one and we were told to use it. + The check whether or not we sent the enable string is in + _rl_disable_meta_key(); the flag is set in _rl_enable_meta_key */ + _rl_disable_meta_key (); + + /* Restore normal cursor, if available. */ + _rl_set_insert_mode (RL_IM_INSERT, 0); + + return (eof ? (char *)NULL : savestring (the_line)); +} + +void +_rl_internal_char_cleanup () +{ +#if defined (VI_MODE) + /* In vi mode, when you exit insert mode, the cursor moves back + over the previous character. We explicitly check for that here. */ + if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap) + rl_vi_check (); +#endif /* VI_MODE */ + + if (rl_num_chars_to_read && rl_end >= rl_num_chars_to_read) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + rl_newline (1, '\n'); + } + + if (rl_done == 0) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + } + + /* If the application writer has told us to erase the entire line if + the only character typed was something bound to rl_newline, do so. */ + if (rl_erase_empty_line && rl_done && rl_last_func == rl_newline && + rl_point == 0 && rl_end == 0) + _rl_erase_entire_line (); +} + +STATIC_CALLBACK int +#if defined (READLINE_CALLBACKS) +readline_internal_char () +#else +readline_internal_charloop () +#endif +{ + static int lastc, eof_found; + int c, code, lk; + + lastc = EOF; + eof_found = 0; + +#if !defined (READLINE_CALLBACKS) + while (rl_done == 0) + { +#endif + lk = _rl_last_command_was_kill; + +#if defined (HAVE_POSIX_SIGSETJMP) + code = sigsetjmp (_rl_top_level, 0); +#else + code = setjmp (_rl_top_level); +#endif + + if (code) + { + (*rl_redisplay_function) (); + _rl_want_redisplay = 0; + /* If we get here, we're not being called from something dispatched + from _rl_callback_read_char(), which sets up its own value of + _rl_top_level (saving and restoring the old, of course), so + we can just return here. */ + if (RL_ISSTATE (RL_STATE_CALLBACK)) + return (0); + } + + if (rl_pending_input == 0) + { + /* Then initialize the argument and number of keys read. */ + _rl_reset_argument (); + rl_key_sequence_length = 0; + rl_executing_keyseq[0] = 0; + } + + RL_SETSTATE(RL_STATE_READCMD); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_READCMD); + + /* look at input.c:rl_getc() for the circumstances under which this will + be returned; punt immediately on read error without converting it to + a newline; assume that rl_read_key has already called the signal + handler. */ + if (c == READERR) + { +#if defined (READLINE_CALLBACKS) + RL_SETSTATE(RL_STATE_DONE); + return (rl_done = 1); +#else + eof_found = 1; + break; +#endif + } + + /* EOF typed to a non-blank line is ^D the first time, EOF the second + time in a row. This won't return any partial line read from the tty. + If we want to change this, to force any existing line to be returned + when read(2) reads EOF, for example, this is the place to change. */ + if (c == EOF && rl_end) + { + if (RL_SIG_RECEIVED ()) + { + RL_CHECK_SIGNALS (); + if (rl_signal_event_hook) + (*rl_signal_event_hook) (); /* XXX */ + } + + /* XXX - reading two consecutive EOFs returns EOF */ + if (RL_ISSTATE (RL_STATE_TERMPREPPED)) + { + if (lastc == _rl_eof_char || lastc == EOF) + rl_end = 0; + else + c = _rl_eof_char; + } + else + c = NEWLINE; + } + + /* The character _rl_eof_char typed to blank line, and not as the + previous character is interpreted as EOF. This doesn't work when + READLINE_CALLBACKS is defined, so hitting a series of ^Ds will + erase all the chars on the line and then return EOF. */ + if (((c == _rl_eof_char && lastc != c) || c == EOF) && rl_end == 0) + { +#if defined (READLINE_CALLBACKS) + RL_SETSTATE(RL_STATE_DONE); + return (rl_done = 1); +#else + eof_found = 1; + break; +#endif + } + + lastc = c; + _rl_dispatch ((unsigned char)c, _rl_keymap); + RL_CHECK_SIGNALS (); + + /* If there was no change in _rl_last_command_was_kill, then no kill + has taken place. Note that if input is pending we are reading + a prefix command, so nothing has changed yet. */ + if (rl_pending_input == 0 && lk == _rl_last_command_was_kill) + _rl_last_command_was_kill = 0; + + _rl_internal_char_cleanup (); + +#if defined (READLINE_CALLBACKS) + return 0; +#else + } + + return (eof_found); +#endif +} + +#if defined (READLINE_CALLBACKS) +static int +readline_internal_charloop () +{ + int eof = 1; + + while (rl_done == 0) + eof = readline_internal_char (); + return (eof); +} +#endif /* READLINE_CALLBACKS */ + +/* Read a line of input from the global rl_instream, doing output on + the global rl_outstream. + If rl_prompt is non-null, then that is our prompt. */ +static char * +readline_internal () +{ + int eof; + + readline_internal_setup (); + eof = readline_internal_charloop (); + return (readline_internal_teardown (eof)); +} + +void +_rl_init_line_state () +{ + rl_point = rl_end = rl_mark = 0; + the_line = rl_line_buffer; + the_line[0] = 0; +} + +void +_rl_set_the_line () +{ + the_line = rl_line_buffer; +} + +#if defined (READLINE_CALLBACKS) +_rl_keyseq_cxt * +_rl_keyseq_cxt_alloc () +{ + _rl_keyseq_cxt *cxt; + + cxt = (_rl_keyseq_cxt *)xmalloc (sizeof (_rl_keyseq_cxt)); + + cxt->flags = cxt->subseq_arg = cxt->subseq_retval = 0; + + cxt->okey = 0; + cxt->ocxt = _rl_kscxt; + cxt->childval = 42; /* sentinel value */ + + return cxt; +} + +void +_rl_keyseq_cxt_dispose (cxt) + _rl_keyseq_cxt *cxt; +{ + xfree (cxt); +} + +void +_rl_keyseq_chain_dispose () +{ + _rl_keyseq_cxt *cxt; + + while (_rl_kscxt) + { + cxt = _rl_kscxt; + _rl_kscxt = _rl_kscxt->ocxt; + _rl_keyseq_cxt_dispose (cxt); + } +} +#endif + +static int +_rl_subseq_getchar (key) + int key; +{ + int k; + + if (key == ESC) + RL_SETSTATE(RL_STATE_METANEXT); + RL_SETSTATE(RL_STATE_MOREINPUT); + k = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + if (key == ESC) + RL_UNSETSTATE(RL_STATE_METANEXT); + + return k; +} + +#if defined (READLINE_CALLBACKS) +int +_rl_dispatch_callback (cxt) + _rl_keyseq_cxt *cxt; +{ + int nkey, r; + + /* For now */ + /* The first time this context is used, we want to read input and dispatch + on it. When traversing the chain of contexts back `up', we want to use + the value from the next context down. We're simulating recursion using + a chain of contexts. */ + if ((cxt->flags & KSEQ_DISPATCHED) == 0) + { + nkey = _rl_subseq_getchar (cxt->okey); + if (nkey < 0) + { + _rl_abort_internal (); + return -1; + } + r = _rl_dispatch_subseq (nkey, cxt->dmap, cxt->subseq_arg); + cxt->flags |= KSEQ_DISPATCHED; + } + else + r = cxt->childval; + + /* For now */ + if (r != -3) /* don't do this if we indicate there will be other matches */ + r = _rl_subseq_result (r, cxt->oldmap, cxt->okey, (cxt->flags & KSEQ_SUBSEQ)); + + RL_CHECK_SIGNALS (); + /* We only treat values < 0 specially to simulate recursion. */ + if (r >= 0 || (r == -1 && (cxt->flags & KSEQ_SUBSEQ) == 0)) /* success! or failure! */ + { + _rl_keyseq_chain_dispose (); + RL_UNSETSTATE (RL_STATE_MULTIKEY); + return r; + } + + if (r != -3) /* magic value that says we added to the chain */ + _rl_kscxt = cxt->ocxt; + if (_rl_kscxt) + _rl_kscxt->childval = r; + if (r != -3) + _rl_keyseq_cxt_dispose (cxt); + + return r; +} +#endif /* READLINE_CALLBACKS */ + +/* Do the command associated with KEY in MAP. + If the associated command is really a keymap, then read + another key, and dispatch into that map. */ +int +_rl_dispatch (key, map) + register int key; + Keymap map; +{ + _rl_dispatching_keymap = map; + return _rl_dispatch_subseq (key, map, 0); +} + +int +_rl_dispatch_subseq (key, map, got_subseq) + register int key; + Keymap map; + int got_subseq; +{ + int r, newkey; + char *macro; + rl_command_func_t *func; +#if defined (READLINE_CALLBACKS) + _rl_keyseq_cxt *cxt; +#endif + + if (META_CHAR (key) && _rl_convert_meta_chars_to_ascii) + { + if (map[ESC].type == ISKMAP) + { + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (ESC); + RESIZE_KEYSEQ_BUFFER (); + rl_executing_keyseq[rl_key_sequence_length++] = ESC; + map = FUNCTION_TO_KEYMAP (map, ESC); + key = UNMETA (key); + return (_rl_dispatch (key, map)); + } + else + rl_ding (); + return 0; + } + + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (key); + + r = 0; + switch (map[key].type) + { + case ISFUNC: + func = map[key].function; + if (func) + { + /* Special case rl_do_lowercase_version (). */ + if (func == rl_do_lowercase_version) + /* Should we do anything special if key == ANYOTHERKEY? */ + return (_rl_dispatch (_rl_to_lower (key), map)); + + rl_executing_keymap = map; + rl_executing_key = key; + + RESIZE_KEYSEQ_BUFFER(); + rl_executing_keyseq[rl_key_sequence_length++] = key; + rl_executing_keyseq[rl_key_sequence_length] = '\0'; + + rl_dispatching = 1; + RL_SETSTATE(RL_STATE_DISPATCHING); + r = (*func) (rl_numeric_arg * rl_arg_sign, key); + RL_UNSETSTATE(RL_STATE_DISPATCHING); + rl_dispatching = 0; + + /* If we have input pending, then the last command was a prefix + command. Don't change the state of rl_last_func. Otherwise, + remember the last command executed in this variable. */ + if (rl_pending_input == 0 && map[key].function != rl_digit_argument) + rl_last_func = map[key].function; + + RL_CHECK_SIGNALS (); + } + else if (map[ANYOTHERKEY].function) + { + /* OK, there's no function bound in this map, but there is a + shadow function that was overridden when the current keymap + was created. Return -2 to note that. */ + if (RL_ISSTATE (RL_STATE_MACROINPUT)) + _rl_prev_macro_key (); + else + _rl_unget_char (key); + return -2; + } + else if (got_subseq) + { + /* Return -1 to note that we're in a subsequence, but we don't + have a matching key, nor was one overridden. This means + we need to back up the recursion chain and find the last + subsequence that is bound to a function. */ + if (RL_ISSTATE (RL_STATE_MACROINPUT)) + _rl_prev_macro_key (); + else + _rl_unget_char (key); + return -1; + } + else + { +#if defined (READLINE_CALLBACKS) + RL_UNSETSTATE (RL_STATE_MULTIKEY); + _rl_keyseq_chain_dispose (); +#endif + _rl_abort_internal (); + return -1; + } + break; + + case ISKMAP: + if (map[key].function != 0) + { +#if defined (VI_MODE) + /* The only way this test will be true is if a subsequence has been + bound starting with ESC, generally the arrow keys. What we do is + check whether there's input in the queue, which there generally + will be if an arrow key has been pressed, and, if there's not, + just dispatch to (what we assume is) rl_vi_movement_mode right + away. This is essentially an input test with a zero timeout (by + default) or a timeout determined by the value of `keyseq-timeout' */ + /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued + takes microseconds, so multiply by 1000 */ + if (rl_editing_mode == vi_mode && key == ESC && map == vi_insertion_keymap + && _rl_input_queued ((_rl_keyseq_timeout > 0) ? _rl_keyseq_timeout*1000 : 0) == 0) + return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key))); +#endif + + RESIZE_KEYSEQ_BUFFER (); + rl_executing_keyseq[rl_key_sequence_length++] = key; + _rl_dispatching_keymap = FUNCTION_TO_KEYMAP (map, key); + + /* Allocate new context here. Use linked contexts (linked through + cxt->ocxt) to simulate recursion */ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + /* Return 0 only the first time, to indicate success to + _rl_callback_read_char. The rest of the time, we're called + from _rl_dispatch_callback, so we return -3 to indicate + special handling is necessary. */ + r = RL_ISSTATE (RL_STATE_MULTIKEY) ? -3 : 0; + cxt = _rl_keyseq_cxt_alloc (); + + if (got_subseq) + cxt->flags |= KSEQ_SUBSEQ; + cxt->okey = key; + cxt->oldmap = map; + cxt->dmap = _rl_dispatching_keymap; + cxt->subseq_arg = got_subseq || cxt->dmap[ANYOTHERKEY].function; + + RL_SETSTATE (RL_STATE_MULTIKEY); + _rl_kscxt = cxt; + + return r; /* don't indicate immediate success */ + } +#endif + + /* Tentative inter-character timeout for potential multi-key + sequences? If no input within timeout, abort sequence and + act as if we got non-matching input. */ + /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued + takes microseconds, so multiply by 1000 */ + if (_rl_keyseq_timeout > 0 && + (RL_ISSTATE (RL_STATE_INPUTPENDING|RL_STATE_MACROINPUT) == 0) && + _rl_pushed_input_available () == 0 && + _rl_dispatching_keymap[ANYOTHERKEY].function && + _rl_input_queued (_rl_keyseq_timeout*1000) == 0) + return (_rl_subseq_result (-2, map, key, got_subseq)); + + newkey = _rl_subseq_getchar (key); + if (newkey < 0) + { + _rl_abort_internal (); + return -1; + } + + r = _rl_dispatch_subseq (newkey, _rl_dispatching_keymap, got_subseq || map[ANYOTHERKEY].function); + return _rl_subseq_result (r, map, key, got_subseq); + } + else + { + _rl_abort_internal (); /* XXX */ + return -1; + } + break; + + case ISMACR: + if (map[key].function != 0) + { + rl_executing_keyseq[rl_key_sequence_length] = '\0'; + macro = savestring ((char *)map[key].function); + _rl_with_macro_input (macro); + return 0; + } + break; + } +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap && + key != ANYOTHERKEY && + _rl_dispatching_keymap == vi_movement_keymap && + _rl_vi_textmod_command (key)) + _rl_vi_set_last (key, rl_numeric_arg, rl_arg_sign); +#endif + + return (r); +} + +static int +_rl_subseq_result (r, map, key, got_subseq) + int r; + Keymap map; + int key, got_subseq; +{ + Keymap m; + int type, nt; + rl_command_func_t *func, *nf; + + if (r == -2) + /* We didn't match anything, and the keymap we're indexed into + shadowed a function previously bound to that prefix. Call + the function. The recursive call to _rl_dispatch_subseq has + already taken care of pushing any necessary input back onto + the input queue with _rl_unget_char. */ + { + m = _rl_dispatching_keymap; + type = m[ANYOTHERKEY].type; + func = m[ANYOTHERKEY].function; + if (type == ISFUNC && func == rl_do_lowercase_version) + r = _rl_dispatch (_rl_to_lower (key), map); + else if (type == ISFUNC && func == rl_insert) + { + /* If the function that was shadowed was self-insert, we + somehow need a keymap with map[key].func == self-insert. + Let's use this one. */ + nt = m[key].type; + nf = m[key].function; + + m[key].type = type; + m[key].function = func; + r = _rl_dispatch (key, m); + m[key].type = nt; + m[key].function = nf; + } + else + r = _rl_dispatch (ANYOTHERKEY, m); + } + else if (r && map[ANYOTHERKEY].function) + { + /* We didn't match (r is probably -1), so return something to + tell the caller that it should try ANYOTHERKEY for an + overridden function. */ + if (RL_ISSTATE (RL_STATE_MACROINPUT)) + _rl_prev_macro_key (); + else + _rl_unget_char (key); + _rl_dispatching_keymap = map; + return -2; + } + else if (r && got_subseq) + { + /* OK, back up the chain. */ + if (RL_ISSTATE (RL_STATE_MACROINPUT)) + _rl_prev_macro_key (); + else + _rl_unget_char (key); + _rl_dispatching_keymap = map; + return -1; + } + + return r; +} + +/* **************************************************************** */ +/* */ +/* Initializations */ +/* */ +/* **************************************************************** */ + +/* Initialize readline (and terminal if not already). */ +int +rl_initialize () +{ + /* If we have never been called before, initialize the + terminal and data structures. */ + if (!rl_initialized) + { + RL_SETSTATE(RL_STATE_INITIALIZING); + readline_initialize_everything (); + RL_UNSETSTATE(RL_STATE_INITIALIZING); + rl_initialized++; + RL_SETSTATE(RL_STATE_INITIALIZED); + } + + /* Initialize the current line information. */ + _rl_init_line_state (); + + /* We aren't done yet. We haven't even gotten started yet! */ + rl_done = 0; + RL_UNSETSTATE(RL_STATE_DONE); + + /* Tell the history routines what is going on. */ + _rl_start_using_history (); + + /* Make the display buffer match the state of the line. */ + rl_reset_line_state (); + + /* No such function typed yet. */ + rl_last_func = (rl_command_func_t *)NULL; + + /* Parsing of key-bindings begins in an enabled state. */ + _rl_parsing_conditionalized_out = 0; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + _rl_vi_initialize_line (); +#endif + + /* Each line starts in insert mode (the default). */ + _rl_set_insert_mode (RL_IM_DEFAULT, 1); + + return 0; +} + +#if 0 +#if defined (__EMX__) +static void +_emx_build_environ () +{ + TIB *tibp; + PIB *pibp; + char *t, **tp; + int c; + + DosGetInfoBlocks (&tibp, &pibp); + t = pibp->pib_pchenv; + for (c = 1; *t; c++) + t += strlen (t) + 1; + tp = environ = (char **)xmalloc ((c + 1) * sizeof (char *)); + t = pibp->pib_pchenv; + while (*t) + { + *tp++ = t; + t += strlen (t) + 1; + } + *tp = 0; +} +#endif /* __EMX__ */ +#endif + +/* Initialize the entire state of the world. */ +static void +readline_initialize_everything () +{ +#if 0 +#if defined (__EMX__) + if (environ == 0) + _emx_build_environ (); +#endif +#endif + +#if 0 + /* Find out if we are running in Emacs -- UNUSED. */ + running_in_emacs = sh_get_env_value ("EMACS") != (char *)0; +#endif + + /* Set up input and output if they are not already set up. */ + if (!rl_instream) + rl_instream = stdin; + + if (!rl_outstream) + rl_outstream = stdout; + + /* Bind _rl_in_stream and _rl_out_stream immediately. These values + may change, but they may also be used before readline_internal () + is called. */ + _rl_in_stream = rl_instream; + _rl_out_stream = rl_outstream; + + /* Allocate data structures. */ + if (rl_line_buffer == 0) + rl_line_buffer = (char *)xmalloc (rl_line_buffer_len = DEFAULT_BUFFER_SIZE); + + /* Initialize the terminal interface. */ + if (rl_terminal_name == 0) + rl_terminal_name = sh_get_env_value ("TERM"); + _rl_init_terminal_io (rl_terminal_name); + + /* Bind tty characters to readline functions. */ + readline_default_bindings (); + + /* Initialize the function names. */ + rl_initialize_funmap (); + + /* Decide whether we should automatically go into eight-bit mode. */ + _rl_init_eightbit (); + + /* Read in the init file. */ + rl_read_init_file ((char *)NULL); + + /* XXX */ + if (_rl_horizontal_scroll_mode && _rl_term_autowrap) + { + _rl_screenwidth--; + _rl_screenchars -= _rl_screenheight; + } + + /* Override the effect of any `set keymap' assignments in the + inputrc file. */ + rl_set_keymap_from_edit_mode (); + + /* Try to bind a common arrow key prefix, if not already bound. */ + bind_arrow_keys (); + + /* Bind the bracketed paste prefix assuming that the user will enable + it on terminals that support it. */ + bind_bracketed_paste_prefix (); + + /* If the completion parser's default word break characters haven't + been set yet, then do so now. */ + if (rl_completer_word_break_characters == (char *)NULL) + rl_completer_word_break_characters = (char *)rl_basic_word_break_characters; + +#if defined (COLOR_SUPPORT) + if (_rl_colored_stats || _rl_colored_completion_prefix) + _rl_parse_colors (); +#endif + + rl_executing_keyseq = malloc (_rl_executing_keyseq_size = 16); + if (rl_executing_keyseq) + rl_executing_keyseq[0] = '\0'; +} + +/* If this system allows us to look at the values of the regular + input editing characters, then bind them to their readline + equivalents, iff the characters are not bound to keymaps. */ +static void +readline_default_bindings () +{ + if (_rl_bind_stty_chars) + rl_tty_set_default_bindings (_rl_keymap); +} + +/* Reset the default bindings for the terminal special characters we're + interested in back to rl_insert and read the new ones. */ +static void +reset_default_bindings () +{ + if (_rl_bind_stty_chars) + { + rl_tty_unset_default_bindings (_rl_keymap); + rl_tty_set_default_bindings (_rl_keymap); + } +} + +/* Bind some common arrow key sequences in MAP. */ +static void +bind_arrow_keys_internal (map) + Keymap map; +{ + Keymap xkeymap; + + xkeymap = _rl_keymap; + _rl_keymap = map; + +#if defined (__MSDOS__) + rl_bind_keyseq_if_unbound ("\033[0A", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033[0B", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033[0C", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033[0D", rl_get_next_history); +#endif + + rl_bind_keyseq_if_unbound ("\033[A", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033[B", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\033[C", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033[D", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033[H", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\033[F", rl_end_of_line); + + rl_bind_keyseq_if_unbound ("\033OA", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\033OB", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\033OC", rl_forward_char); + rl_bind_keyseq_if_unbound ("\033OD", rl_backward_char); + rl_bind_keyseq_if_unbound ("\033OH", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\033OF", rl_end_of_line); + +#if defined (__MINGW32__) + rl_bind_keyseq_if_unbound ("\340H", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\340P", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\340M", rl_forward_char); + rl_bind_keyseq_if_unbound ("\340K", rl_backward_char); + rl_bind_keyseq_if_unbound ("\340G", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\340O", rl_end_of_line); + rl_bind_keyseq_if_unbound ("\340S", rl_delete); + rl_bind_keyseq_if_unbound ("\340R", rl_overwrite_mode); + + /* These may or may not work because of the embedded NUL. */ + rl_bind_keyseq_if_unbound ("\\000H", rl_get_previous_history); + rl_bind_keyseq_if_unbound ("\\000P", rl_get_next_history); + rl_bind_keyseq_if_unbound ("\\000M", rl_forward_char); + rl_bind_keyseq_if_unbound ("\\000K", rl_backward_char); + rl_bind_keyseq_if_unbound ("\\000G", rl_beg_of_line); + rl_bind_keyseq_if_unbound ("\\000O", rl_end_of_line); + rl_bind_keyseq_if_unbound ("\\000S", rl_delete); + rl_bind_keyseq_if_unbound ("\\000R", rl_overwrite_mode); +#endif + + _rl_keymap = xkeymap; +} + +/* Try and bind the common arrow key prefixes after giving termcap and + the inputrc file a chance to bind them and create `real' keymaps + for the arrow key prefix. */ +static void +bind_arrow_keys () +{ + bind_arrow_keys_internal (emacs_standard_keymap); + +#if defined (VI_MODE) + bind_arrow_keys_internal (vi_movement_keymap); + /* Unbind vi_movement_keymap[ESC] to allow users to repeatedly hit ESC + in vi command mode while still allowing the arrow keys to work. */ + if (vi_movement_keymap[ESC].type == ISKMAP) + rl_bind_keyseq_in_map ("\033", (rl_command_func_t *)NULL, vi_movement_keymap); + bind_arrow_keys_internal (vi_insertion_keymap); +#endif +} + +static void +bind_bracketed_paste_prefix () +{ + Keymap xkeymap; + + xkeymap = _rl_keymap; + + _rl_keymap = emacs_standard_keymap; + rl_bind_keyseq_if_unbound (BRACK_PASTE_PREF, rl_bracketed_paste_begin); + + _rl_keymap = vi_insertion_keymap; + rl_bind_keyseq_if_unbound (BRACK_PASTE_PREF, rl_bracketed_paste_begin); + + _rl_keymap = xkeymap; +} + +/* **************************************************************** */ +/* */ +/* Saving and Restoring Readline's state */ +/* */ +/* **************************************************************** */ + +int +rl_save_state (sp) + struct readline_state *sp; +{ + if (sp == 0) + return -1; + + sp->point = rl_point; + sp->end = rl_end; + sp->mark = rl_mark; + sp->buffer = rl_line_buffer; + sp->buflen = rl_line_buffer_len; + sp->ul = rl_undo_list; + sp->prompt = rl_prompt; + + sp->rlstate = rl_readline_state; + sp->done = rl_done; + sp->kmap = _rl_keymap; + + sp->lastfunc = rl_last_func; + sp->insmode = rl_insert_mode; + sp->edmode = rl_editing_mode; + sp->kseq = rl_executing_keyseq; + sp->kseqlen = rl_key_sequence_length; + sp->inf = rl_instream; + sp->outf = rl_outstream; + sp->pendingin = rl_pending_input; + sp->macro = rl_executing_macro; + + sp->catchsigs = rl_catch_signals; + sp->catchsigwinch = rl_catch_sigwinch; + + sp->entryfunc = rl_completion_entry_function; + sp->menuentryfunc = rl_menu_completion_entry_function; + sp->ignorefunc = rl_ignore_some_completions_function; + sp->attemptfunc = rl_attempted_completion_function; + sp->wordbreakchars = rl_completer_word_break_characters; + + return (0); +} + +int +rl_restore_state (sp) + struct readline_state *sp; +{ + if (sp == 0) + return -1; + + rl_point = sp->point; + rl_end = sp->end; + rl_mark = sp->mark; + the_line = rl_line_buffer = sp->buffer; + rl_line_buffer_len = sp->buflen; + rl_undo_list = sp->ul; + rl_prompt = sp->prompt; + + rl_readline_state = sp->rlstate; + rl_done = sp->done; + _rl_keymap = sp->kmap; + + rl_last_func = sp->lastfunc; + rl_insert_mode = sp->insmode; + rl_editing_mode = sp->edmode; + rl_executing_keyseq = sp->kseq; + rl_key_sequence_length = sp->kseqlen; + rl_instream = sp->inf; + rl_outstream = sp->outf; + rl_pending_input = sp->pendingin; + rl_executing_macro = sp->macro; + + rl_catch_signals = sp->catchsigs; + rl_catch_sigwinch = sp->catchsigwinch; + + rl_completion_entry_function = sp->entryfunc; + rl_menu_completion_entry_function = sp->menuentryfunc; + rl_ignore_some_completions_function = sp->ignorefunc; + rl_attempted_completion_function = sp->attemptfunc; + rl_completer_word_break_characters = sp->wordbreakchars; + + return (0); +} diff --git a/lib/readline/rlmbutil.h b/lib/readline/rlmbutil.h index 06d85ab3..0b0a32b9 100644 --- a/lib/readline/rlmbutil.h +++ b/lib/readline/rlmbutil.h @@ -132,6 +132,12 @@ extern int _rl_walphabetic PARAMS((wchar_t)); # define WCWIDTH(wc) wcwidth(wc) #endif +#if defined (WCWIDTH_BROKEN) +# define IS_COMBINING_CHAR(x) (WCWIDTH(x) == 0 && iswcntrl(x) == 0) +#else +# define IS_COMBINING_CHAR(x) (WCWIDTH(x) == 0) +#endif + #else /* !HANDLE_MULTIBYTE */ #undef MB_LEN_MAX diff --git a/lib/readline/rlmbutil.h~ b/lib/readline/rlmbutil.h~ new file mode 100644 index 00000000..06d85ab3 --- /dev/null +++ b/lib/readline/rlmbutil.h~ @@ -0,0 +1,163 @@ +/* rlmbutil.h -- utility functions for multibyte characters. */ + +/* Copyright (C) 2001-2009 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see <http://www.gnu.org/licenses/>. +*/ + +#if !defined (_RL_MBUTIL_H_) +#define _RL_MBUTIL_H_ + +#include "rlstdc.h" + +/************************************************/ +/* check multibyte capability for I18N code */ +/************************************************/ + +/* For platforms which support the ISO C amendement 1 functionality we + support user defined character classes. */ + /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ +#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) && defined (HAVE_LOCALE_H) +# include <wchar.h> +# include <wctype.h> +# if defined (HAVE_ISWCTYPE) && \ + defined (HAVE_ISWLOWER) && \ + defined (HAVE_ISWUPPER) && \ + defined (HAVE_MBSRTOWCS) && \ + defined (HAVE_MBRTOWC) && \ + defined (HAVE_MBRLEN) && \ + defined (HAVE_TOWLOWER) && \ + defined (HAVE_TOWUPPER) && \ + defined (HAVE_WCHAR_T) && \ + defined (HAVE_WCWIDTH) + /* system is supposed to support XPG5 */ +# define HANDLE_MULTIBYTE 1 +# endif +#endif + +/* If we don't want multibyte chars even on a system that supports them, let + the configuring user turn multibyte support off. */ +#if defined (NO_MULTIBYTE_SUPPORT) +# undef HANDLE_MULTIBYTE +#endif + +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ +#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) +# define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) +# define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) +# define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) +# define mbrlen(s, n, ps) (mbrlen) (s, n, 0) +# define mbstate_t int +#endif + +/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to + handle multibyte chars (some systems define MB_LEN_MAX as 1) */ +#ifdef HANDLE_MULTIBYTE +# include <limits.h> +# if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) +# undef MB_LEN_MAX +# endif +# if !defined (MB_LEN_MAX) +# define MB_LEN_MAX 16 +# endif +#endif + +/************************************************/ +/* end of multibyte capability checks for I18N */ +/************************************************/ + +/* + * Flags for _rl_find_prev_mbchar and _rl_find_next_mbchar: + * + * MB_FIND_ANY find any multibyte character + * MB_FIND_NONZERO find a non-zero-width multibyte character + */ + +#define MB_FIND_ANY 0x00 +#define MB_FIND_NONZERO 0x01 + +extern int _rl_find_prev_mbchar PARAMS((char *, int, int)); +extern int _rl_find_next_mbchar PARAMS((char *, int, int, int)); + +#ifdef HANDLE_MULTIBYTE + +extern int _rl_compare_chars PARAMS((char *, int, mbstate_t *, char *, int, mbstate_t *)); +extern int _rl_get_char_len PARAMS((char *, mbstate_t *)); +extern int _rl_adjust_point PARAMS((char *, int, mbstate_t *)); + +extern int _rl_read_mbchar PARAMS((char *, int)); +extern int _rl_read_mbstring PARAMS((int, char *, int)); + +extern int _rl_is_mbchar_matched PARAMS((char *, int, int, char *, int)); + +extern wchar_t _rl_char_value PARAMS((char *, int)); +extern int _rl_walphabetic PARAMS((wchar_t)); + +#define _rl_to_wupper(wc) (iswlower (wc) ? towupper (wc) : (wc)) +#define _rl_to_wlower(wc) (iswupper (wc) ? towlower (wc) : (wc)) + +#define MB_NEXTCHAR(b,s,c,f) \ + ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \ + ? _rl_find_next_mbchar ((b), (s), (c), (f)) \ + : ((s) + (c))) +#define MB_PREVCHAR(b,s,f) \ + ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \ + ? _rl_find_prev_mbchar ((b), (s), (f)) \ + : ((s) - 1)) + +#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2) +#define MB_NULLWCH(x) ((x) == 0) + +/* Unicode combining characters range from U+0300 to U+036F */ +#define UNICODE_COMBINING_CHAR(x) ((x) >= 768 && (x) <= 879) + +#if defined (WCWIDTH_BROKEN) +# define WCWIDTH(wc) ((_rl_utf8locale && UNICODE_COMBINING_CHAR(wc)) ? 0 : wcwidth(wc)) +#else +# define WCWIDTH(wc) wcwidth(wc) +#endif + +#else /* !HANDLE_MULTIBYTE */ + +#undef MB_LEN_MAX +#undef MB_CUR_MAX + +#define MB_LEN_MAX 1 +#define MB_CUR_MAX 1 + +#define _rl_find_prev_mbchar(b, i, f) (((i) == 0) ? (i) : ((i) - 1)) +#define _rl_find_next_mbchar(b, i1, i2, f) ((i1) + (i2)) + +#define _rl_char_value(buf,ind) ((buf)[(ind)]) + +#define _rl_walphabetic(c) (rl_alphabetic (c)) + +#define _rl_to_wupper(c) (_rl_to_upper (c)) +#define _rl_to_wlower(c) (_rl_to_lower (c)) + +#define MB_NEXTCHAR(b,s,c,f) ((s) + (c)) +#define MB_PREVCHAR(b,s,f) ((s) - 1) + +#define MB_INVALIDCH(x) (0) +#define MB_NULLWCH(x) (0) + +#endif /* !HANDLE_MULTIBYTE */ + +extern int rl_byte_oriented; + +#endif /* _RL_MBUTIL_H_ */ diff --git a/lib/readline/text.c b/lib/readline/text.c index 0370029c..7f1f076d 100644 --- a/lib/readline/text.c +++ b/lib/readline/text.c @@ -1103,7 +1103,7 @@ _rl_rubout_char (count, key) c = rl_line_buffer[--rl_point]; rl_delete_text (rl_point, orig_point); /* The erase-at-end-of-line hack is of questionable merit now. */ - if (rl_point == rl_end && ISPRINT (c) && _rl_last_c_pos) + if (rl_point == rl_end && ISPRINT ((unsigned char)c) && _rl_last_c_pos) { int l; l = rl_character_len (c, rl_point); @@ -1337,7 +1337,7 @@ rl_change_case (count, op) } else nop = op; - if (MB_CUR_MAX == 1 || rl_byte_oriented || isascii (c)) + if (MB_CUR_MAX == 1 || rl_byte_oriented || isascii ((unsigned char)c)) { nc = (nop == UpCase) ? _rl_to_upper (c) : _rl_to_lower (c); rl_line_buffer[start] = nc; diff --git a/lib/readline/text.c~ b/lib/readline/text.c~ new file mode 100644 index 00000000..bb496d5f --- /dev/null +++ b/lib/readline/text.c~ @@ -0,0 +1,1705 @@ +/* text.c -- text handling commands for readline. */ + +/* Copyright (C) 1987-2010 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include <stdlib.h> +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include <locale.h> +#endif + +#include <stdio.h> + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +#if defined (__EMX__) +# define INCL_DOSPROCESS +# include <os2.h> +#endif /* __EMX__ */ + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +/* Forward declarations. */ +static int rl_change_case PARAMS((int, int)); +static int _rl_char_search PARAMS((int, int, int)); + +#if defined (READLINE_CALLBACKS) +static int _rl_insert_next_callback PARAMS((_rl_callback_generic_arg *)); +static int _rl_char_search_callback PARAMS((_rl_callback_generic_arg *)); +#endif + +/* The largest chunk of text that can be inserted in one call to + rl_insert_text. Text blocks larger than this are divided. */ +#define TEXT_COUNT_MAX 1024 + +/* **************************************************************** */ +/* */ +/* Insert and Delete */ +/* */ +/* **************************************************************** */ + +/* Insert a string of text into the line at point. This is the only + way that you should do insertion. _rl_insert_char () calls this + function. Returns the number of characters inserted. */ +int +rl_insert_text (string) + const char *string; +{ + register int i, l; + + l = (string && *string) ? strlen (string) : 0; + if (l == 0) + return 0; + + if (rl_end + l >= rl_line_buffer_len) + rl_extend_line_buffer (rl_end + l); + + for (i = rl_end; i >= rl_point; i--) + rl_line_buffer[i + l] = rl_line_buffer[i]; + strncpy (rl_line_buffer + rl_point, string, l); + + /* Remember how to undo this if we aren't undoing something. */ + if (_rl_doing_an_undo == 0) + { + /* If possible and desirable, concatenate the undos. */ + if ((l == 1) && + rl_undo_list && + (rl_undo_list->what == UNDO_INSERT) && + (rl_undo_list->end == rl_point) && + (rl_undo_list->end - rl_undo_list->start < 20)) + rl_undo_list->end++; + else + rl_add_undo (UNDO_INSERT, rl_point, rl_point + l, (char *)NULL); + } + rl_point += l; + rl_end += l; + rl_line_buffer[rl_end] = '\0'; + return l; +} + +/* Delete the string between FROM and TO. FROM is inclusive, TO is not. + Returns the number of characters deleted. */ +int +rl_delete_text (from, to) + int from, to; +{ + register char *text; + register int diff, i; + + /* Fix it if the caller is confused. */ + if (from > to) + SWAP (from, to); + + /* fix boundaries */ + if (to > rl_end) + { + to = rl_end; + if (from > to) + from = to; + } + if (from < 0) + from = 0; + + text = rl_copy_text (from, to); + + /* Some versions of strncpy() can't handle overlapping arguments. */ + diff = to - from; + for (i = from; i < rl_end - diff; i++) + rl_line_buffer[i] = rl_line_buffer[i + diff]; + + /* Remember how to undo this delete. */ + if (_rl_doing_an_undo == 0) + rl_add_undo (UNDO_DELETE, from, to, text); + else + xfree (text); + + rl_end -= diff; + rl_line_buffer[rl_end] = '\0'; + return (diff); +} + +/* Fix up point so that it is within the line boundaries after killing + text. If FIX_MARK_TOO is non-zero, the mark is forced within line + boundaries also. */ + +#define _RL_FIX_POINT(x) \ + do { \ + if (x > rl_end) \ + x = rl_end; \ + else if (x < 0) \ + x = 0; \ + } while (0) + +void +_rl_fix_point (fix_mark_too) + int fix_mark_too; +{ + _RL_FIX_POINT (rl_point); + if (fix_mark_too) + _RL_FIX_POINT (rl_mark); +} +#undef _RL_FIX_POINT + +/* Replace the contents of the line buffer between START and END with + TEXT. The operation is undoable. To replace the entire line in an + undoable mode, use _rl_replace_text(text, 0, rl_end); */ +int +_rl_replace_text (text, start, end) + const char *text; + int start, end; +{ + int n; + + n = 0; + rl_begin_undo_group (); + if (start <= end) + rl_delete_text (start, end + 1); + rl_point = start; + if (*text) + n = rl_insert_text (text); + rl_end_undo_group (); + + return n; +} + +/* Replace the current line buffer contents with TEXT. If CLEAR_UNDO is + non-zero, we free the current undo list. */ +void +rl_replace_line (text, clear_undo) + const char *text; + int clear_undo; +{ + int len; + + len = strlen (text); + if (len >= rl_line_buffer_len) + rl_extend_line_buffer (len); + strcpy (rl_line_buffer, text); + rl_end = len; + + if (clear_undo) + rl_free_undo_list (); + + _rl_fix_point (1); +} + +/* **************************************************************** */ +/* */ +/* Readline character functions */ +/* */ +/* **************************************************************** */ + +/* This is not a gap editor, just a stupid line input routine. No hair + is involved in writing any of the functions, and none should be. */ + +/* Note that: + + rl_end is the place in the string that we would place '\0'; + i.e., it is always safe to place '\0' there. + + rl_point is the place in the string where the cursor is. Sometimes + this is the same as rl_end. + + Any command that is called interactively receives two arguments. + The first is a count: the numeric arg passed to this command. + The second is the key which invoked this command. +*/ + +/* **************************************************************** */ +/* */ +/* Movement Commands */ +/* */ +/* **************************************************************** */ + +/* Note that if you `optimize' the display for these functions, you cannot + use said functions in other functions which do not do optimizing display. + I.e., you will have to update the data base for rl_redisplay, and you + might as well let rl_redisplay do that job. */ + +/* Move forward COUNT bytes. */ +int +rl_forward_byte (count, key) + int count, key; +{ + if (count < 0) + return (rl_backward_byte (-count, key)); + + if (count > 0) + { + int end, lend; + + end = rl_point + count; +#if defined (VI_MODE) + lend = rl_end > 0 ? rl_end - (VI_COMMAND_MODE()) : rl_end; +#else + lend = rl_end; +#endif + + if (end > lend) + { + rl_point = lend; + rl_ding (); + } + else + rl_point = end; + } + + if (rl_end < 0) + rl_end = 0; + + return 0; +} + +int +_rl_forward_char_internal (count) + int count; +{ + int point; + +#if defined (HANDLE_MULTIBYTE) + point = _rl_find_next_mbchar (rl_line_buffer, rl_point, count, MB_FIND_NONZERO); + +#if defined (VI_MODE) + if (point >= rl_end && VI_COMMAND_MODE()) + point = _rl_find_prev_mbchar (rl_line_buffer, rl_end, MB_FIND_NONZERO); +#endif + + if (rl_end < 0) + rl_end = 0; +#else + point = rl_point + count; + if (point > rl_end) + point = rl_end; +#endif + + return (point); +} + +#if defined (HANDLE_MULTIBYTE) +/* Move forward COUNT characters. */ +int +rl_forward_char (count, key) + int count, key; +{ + int point; + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + return (rl_forward_byte (count, key)); + + if (count < 0) + return (rl_backward_char (-count, key)); + + if (count > 0) + { + if (rl_point == rl_end && EMACS_MODE()) + { + rl_ding (); + return 0; + } + + point = _rl_forward_char_internal (count); + + if (rl_point == point) + rl_ding (); + + rl_point = point; + } + + return 0; +} +#else /* !HANDLE_MULTIBYTE */ +int +rl_forward_char (count, key) + int count, key; +{ + return (rl_forward_byte (count, key)); +} +#endif /* !HANDLE_MULTIBYTE */ + +/* Backwards compatibility. */ +int +rl_forward (count, key) + int count, key; +{ + return (rl_forward_char (count, key)); +} + +/* Move backward COUNT bytes. */ +int +rl_backward_byte (count, key) + int count, key; +{ + if (count < 0) + return (rl_forward_byte (-count, key)); + + if (count > 0) + { + if (rl_point < count) + { + rl_point = 0; + rl_ding (); + } + else + rl_point -= count; + } + + if (rl_point < 0) + rl_point = 0; + + return 0; +} + +#if defined (HANDLE_MULTIBYTE) +/* Move backward COUNT characters. */ +int +rl_backward_char (count, key) + int count, key; +{ + int point; + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + return (rl_backward_byte (count, key)); + + if (count < 0) + return (rl_forward_char (-count, key)); + + if (count > 0) + { + point = rl_point; + + while (count > 0 && point > 0) + { + point = _rl_find_prev_mbchar (rl_line_buffer, point, MB_FIND_NONZERO); + count--; + } + if (count > 0) + { + rl_point = 0; + rl_ding (); + } + else + rl_point = point; + } + + return 0; +} +#else +int +rl_backward_char (count, key) + int count, key; +{ + return (rl_backward_byte (count, key)); +} +#endif + +/* Backwards compatibility. */ +int +rl_backward (count, key) + int count, key; +{ + return (rl_backward_char (count, key)); +} + +/* Move to the beginning of the line. */ +int +rl_beg_of_line (count, key) + int count, key; +{ + rl_point = 0; + return 0; +} + +/* Move to the end of the line. */ +int +rl_end_of_line (count, key) + int count, key; +{ + rl_point = rl_end; + return 0; +} + +/* Move forward a word. We do what Emacs does. Handles multibyte chars. */ +int +rl_forward_word (count, key) + int count, key; +{ + int c; + + if (count < 0) + return (rl_backward_word (-count, key)); + + while (count) + { + if (rl_point == rl_end) + return 0; + + /* If we are not in a word, move forward until we are in one. + Then, move forward until we hit a non-alphabetic character. */ + c = _rl_char_value (rl_line_buffer, rl_point); + + if (_rl_walphabetic (c) == 0) + { + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + while (rl_point < rl_end) + { + c = _rl_char_value (rl_line_buffer, rl_point); + if (_rl_walphabetic (c)) + break; + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + } + } + + if (rl_point == rl_end) + return 0; + + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + while (rl_point < rl_end) + { + c = _rl_char_value (rl_line_buffer, rl_point); + if (_rl_walphabetic (c) == 0) + break; + rl_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + } + + --count; + } + + return 0; +} + +/* Move backward a word. We do what Emacs does. Handles multibyte chars. */ +int +rl_backward_word (count, key) + int count, key; +{ + int c, p; + + if (count < 0) + return (rl_forward_word (-count, key)); + + while (count) + { + if (rl_point == 0) + return 0; + + /* Like rl_forward_word (), except that we look at the characters + just before point. */ + + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + + if (_rl_walphabetic (c) == 0) + { + rl_point = p; + while (rl_point > 0) + { + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + if (_rl_walphabetic (c)) + break; + rl_point = p; + } + } + + while (rl_point) + { + p = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + c = _rl_char_value (rl_line_buffer, p); + if (_rl_walphabetic (c) == 0) + break; + else + rl_point = p; + } + + --count; + } + + return 0; +} + +/* Clear the current line. Numeric argument to C-l does this. */ +int +rl_refresh_line (ignore1, ignore2) + int ignore1, ignore2; +{ + int curr_line; + + curr_line = _rl_current_display_line (); + + _rl_move_vert (curr_line); + _rl_move_cursor_relative (0, rl_line_buffer); /* XXX is this right */ + + _rl_clear_to_eol (0); /* arg of 0 means to not use spaces */ + + rl_forced_update_display (); + rl_display_fixed = 1; + + return 0; +} + +/* C-l typed to a line without quoting clears the screen, and then reprints + the prompt and the current input line. Given a numeric arg, redraw only + the current line. */ +int +rl_clear_screen (count, key) + int count, key; +{ + if (rl_explicit_arg) + { + rl_refresh_line (count, key); + return 0; + } + + _rl_clear_screen (); /* calls termcap function to clear screen */ + rl_forced_update_display (); + rl_display_fixed = 1; + + return 0; +} + +int +rl_skip_csi_sequence (count, key) + int count, key; +{ + int ch; + + RL_SETSTATE (RL_STATE_MOREINPUT); + do + ch = rl_read_key (); + while (ch >= 0x20 && ch < 0x40); + RL_UNSETSTATE (RL_STATE_MOREINPUT); + + return 0; +} + +int +rl_arrow_keys (count, c) + int count, c; +{ + int ch; + + RL_SETSTATE(RL_STATE_MOREINPUT); + ch = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + switch (_rl_to_upper (ch)) + { + case 'A': + rl_get_previous_history (count, ch); + break; + + case 'B': + rl_get_next_history (count, ch); + break; + + case 'C': + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_forward_char (count, ch); + else + rl_forward_byte (count, ch); + break; + + case 'D': + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_backward_char (count, ch); + else + rl_backward_byte (count, ch); + break; + + default: + rl_ding (); + } + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Text commands */ +/* */ +/* **************************************************************** */ + +#ifdef HANDLE_MULTIBYTE +static char pending_bytes[MB_LEN_MAX]; +static int pending_bytes_length = 0; +static mbstate_t ps = {0}; +#endif + +/* Insert the character C at the current location, moving point forward. + If C introduces a multibyte sequence, we read the whole sequence and + then insert the multibyte char into the line buffer. */ +int +_rl_insert_char (count, c) + int count, c; +{ + register int i; + char *string; +#ifdef HANDLE_MULTIBYTE + int string_size; + char incoming[MB_LEN_MAX + 1]; + int incoming_length = 0; + mbstate_t ps_back; + static int stored_count = 0; +#endif + + if (count <= 0) + return 0; + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + incoming[0] = c; + incoming[1] = '\0'; + incoming_length = 1; + } + else + { + wchar_t wc; + size_t ret; + + if (stored_count <= 0) + stored_count = count; + else + count = stored_count; + + ps_back = ps; + pending_bytes[pending_bytes_length++] = c; + ret = mbrtowc (&wc, pending_bytes, pending_bytes_length, &ps); + + if (ret == (size_t)-2) + { + /* Bytes too short to compose character, try to wait for next byte. + Restore the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + ps = ps_back; + return 1; + } + else if (ret == (size_t)-1) + { + /* Invalid byte sequence for the current locale. Treat first byte + as a single character. */ + incoming[0] = pending_bytes[0]; + incoming[1] = '\0'; + incoming_length = 1; + pending_bytes_length--; + memmove (pending_bytes, pending_bytes + 1, pending_bytes_length); + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else if (ret == (size_t)0) + { + incoming[0] = '\0'; + incoming_length = 0; + pending_bytes_length--; + /* Clear the state of the byte sequence, because in this case the + effect of mbstate is undefined. */ + memset (&ps, 0, sizeof (mbstate_t)); + } + else + { + /* We successfully read a single multibyte character. */ + memcpy (incoming, pending_bytes, pending_bytes_length); + incoming[pending_bytes_length] = '\0'; + incoming_length = pending_bytes_length; + pending_bytes_length = 0; + } + } +#endif /* HANDLE_MULTIBYTE */ + + /* If we can optimize, then do it. But don't let people crash + readline because of extra large arguments. */ + if (count > 1 && count <= TEXT_COUNT_MAX) + { +#if defined (HANDLE_MULTIBYTE) + string_size = count * incoming_length; + string = (char *)xmalloc (1 + string_size); + + i = 0; + while (i < string_size) + { + strncpy (string + i, incoming, incoming_length); + i += incoming_length; + } + incoming_length = 0; + stored_count = 0; +#else /* !HANDLE_MULTIBYTE */ + string = (char *)xmalloc (1 + count); + + for (i = 0; i < count; i++) + string[i] = c; +#endif /* !HANDLE_MULTIBYTE */ + + string[i] = '\0'; + rl_insert_text (string); + xfree (string); + + return 0; + } + + if (count > TEXT_COUNT_MAX) + { + int decreaser; +#if defined (HANDLE_MULTIBYTE) + string_size = incoming_length * TEXT_COUNT_MAX; + string = (char *)xmalloc (1 + string_size); + + i = 0; + while (i < string_size) + { + strncpy (string + i, incoming, incoming_length); + i += incoming_length; + } + + while (count) + { + decreaser = (count > TEXT_COUNT_MAX) ? TEXT_COUNT_MAX : count; + string[decreaser*incoming_length] = '\0'; + rl_insert_text (string); + count -= decreaser; + } + + xfree (string); + incoming_length = 0; + stored_count = 0; +#else /* !HANDLE_MULTIBYTE */ + char str[TEXT_COUNT_MAX+1]; + + for (i = 0; i < TEXT_COUNT_MAX; i++) + str[i] = c; + + while (count) + { + decreaser = (count > TEXT_COUNT_MAX ? TEXT_COUNT_MAX : count); + str[decreaser] = '\0'; + rl_insert_text (str); + count -= decreaser; + } +#endif /* !HANDLE_MULTIBYTE */ + + return 0; + } + + if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + /* We are inserting a single character. + If there is pending input, then make a string of all of the + pending characters that are bound to rl_insert, and insert + them all. Don't do this if we're current reading input from + a macro. */ + if ((RL_ISSTATE (RL_STATE_MACROINPUT) == 0) && _rl_pushed_input_available ()) + _rl_insert_typein (c); + else + { + /* Inserting a single character. */ + char str[2]; + + str[1] = '\0'; + str[0] = c; + rl_insert_text (str); + } + } +#if defined (HANDLE_MULTIBYTE) + else + { + rl_insert_text (incoming); + stored_count = 0; + } +#endif + + return 0; +} + +/* Overwrite the character at point (or next COUNT characters) with C. + If C introduces a multibyte character sequence, read the entire sequence + before starting the overwrite loop. */ +int +_rl_overwrite_char (count, c) + int count, c; +{ + int i; +#if defined (HANDLE_MULTIBYTE) + char mbkey[MB_LEN_MAX]; + int k; + + /* Read an entire multibyte character sequence to insert COUNT times. */ + if (count > 0 && MB_CUR_MAX > 1 && rl_byte_oriented == 0) + k = _rl_read_mbstring (c, mbkey, MB_LEN_MAX); +#endif + + rl_begin_undo_group (); + + for (i = 0; i < count; i++) + { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_insert_text (mbkey); + else +#endif + _rl_insert_char (1, c); + + if (rl_point < rl_end) + rl_delete (1, c); + } + + rl_end_undo_group (); + + return 0; +} + +int +rl_insert (count, c) + int count, c; +{ + return (rl_insert_mode == RL_IM_INSERT ? _rl_insert_char (count, c) + : _rl_overwrite_char (count, c)); +} + +/* Insert the next typed character verbatim. */ +static int +_rl_insert_next (count) + int count; +{ + int c; + + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (c < 0) + return 1; + + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (c); + +#if defined (HANDLE_SIGNALS) + if (RL_ISSTATE (RL_STATE_CALLBACK) == 0) + _rl_restore_tty_signals (); +#endif + + return (_rl_insert_char (count, c)); +} + +#if defined (READLINE_CALLBACKS) +static int +_rl_insert_next_callback (data) + _rl_callback_generic_arg *data; +{ + int count; + + count = data->count; + + /* Deregister function, let rl_callback_read_char deallocate data */ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return _rl_insert_next (count); +} +#endif + +int +rl_quoted_insert (count, key) + int count, key; +{ + /* Let's see...should the callback interface futz with signal handling? */ +#if defined (HANDLE_SIGNALS) + if (RL_ISSTATE (RL_STATE_CALLBACK) == 0) + _rl_disable_tty_signals (); +#endif + +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_func = _rl_insert_next_callback; + return (0); + } +#endif + + return _rl_insert_next (count); +} + +/* Insert a tab character. */ +int +rl_tab_insert (count, key) + int count, key; +{ + return (_rl_insert_char (count, '\t')); +} + +/* What to do when a NEWLINE is pressed. We accept the whole line. + KEY is the key that invoked this command. I guess it could have + meaning in the future. */ +int +rl_newline (count, key) + int count, key; +{ + rl_done = 1; + + if (_rl_history_preserve_point) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + RL_SETSTATE(RL_STATE_DONE); + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + { + _rl_vi_done_inserting (); + if (_rl_vi_textmod_command (_rl_vi_last_command) == 0) /* XXX */ + _rl_vi_reset_last (); + } +#endif /* VI_MODE */ + + /* If we've been asked to erase empty lines, suppress the final update, + since _rl_update_final calls rl_crlf(). */ + if (rl_erase_empty_line && rl_point == 0 && rl_end == 0) + return 0; + + if (_rl_echoing_p) + _rl_update_final (); + return 0; +} + +/* What to do for some uppercase characters, like meta characters, + and some characters appearing in emacs_ctlx_keymap. This function + is just a stub, you bind keys to it and the code in _rl_dispatch () + is special cased. */ +int +rl_do_lowercase_version (ignore1, ignore2) + int ignore1, ignore2; +{ + return 0; +} + +/* This is different from what vi does, so the code's not shared. Emacs + rubout in overwrite mode has one oddity: it replaces a control + character that's displayed as two characters (^X) with two spaces. */ +int +_rl_overwrite_rubout (count, key) + int count, key; +{ + int opoint; + int i, l; + + if (rl_point == 0) + { + rl_ding (); + return 1; + } + + opoint = rl_point; + + /* L == number of spaces to insert */ + for (i = l = 0; i < count; i++) + { + rl_backward_char (1, key); + l += rl_character_len (rl_line_buffer[rl_point], rl_point); /* not exactly right */ + } + + rl_begin_undo_group (); + + if (count > 1 || rl_explicit_arg) + rl_kill_text (opoint, rl_point); + else + rl_delete_text (opoint, rl_point); + + /* Emacs puts point at the beginning of the sequence of spaces. */ + if (rl_point < rl_end) + { + opoint = rl_point; + _rl_insert_char (l, ' '); + rl_point = opoint; + } + + rl_end_undo_group (); + + return 0; +} + +/* Rubout the character behind point. */ +int +rl_rubout (count, key) + int count, key; +{ + if (count < 0) + return (rl_delete (-count, key)); + + if (!rl_point) + { + rl_ding (); + return 1; + } + + if (rl_insert_mode == RL_IM_OVERWRITE) + return (_rl_overwrite_rubout (count, key)); + + return (_rl_rubout_char (count, key)); +} + +int +_rl_rubout_char (count, key) + int count, key; +{ + int orig_point; + unsigned char c; + + /* Duplicated code because this is called from other parts of the library. */ + if (count < 0) + return (rl_delete (-count, key)); + + if (rl_point == 0) + { + rl_ding (); + return 1; + } + + orig_point = rl_point; + if (count > 1 || rl_explicit_arg) + { + rl_backward_char (count, key); + rl_kill_text (orig_point, rl_point); + } + else if (MB_CUR_MAX == 1 || rl_byte_oriented) + { + c = rl_line_buffer[--rl_point]; + rl_delete_text (rl_point, orig_point); + /* The erase-at-end-of-line hack is of questionable merit now. */ + if (rl_point == rl_end && ISPRINT (c) && _rl_last_c_pos) + { + int l; + l = rl_character_len (c, rl_point); + _rl_erase_at_end_of_line (l); + } + } + else + { + rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO); + rl_delete_text (rl_point, orig_point); + } + + return 0; +} + +/* Delete the character under the cursor. Given a numeric argument, + kill that many characters instead. */ +int +rl_delete (count, key) + int count, key; +{ + int xpoint; + + if (count < 0) + return (_rl_rubout_char (-count, key)); + + if (rl_point == rl_end) + { + rl_ding (); + return 1; + } + + if (count > 1 || rl_explicit_arg) + { + xpoint = rl_point; + if (MB_CUR_MAX > 1 && rl_byte_oriented == 0) + rl_forward_char (count, key); + else + rl_forward_byte (count, key); + + rl_kill_text (xpoint, rl_point); + rl_point = xpoint; + } + else + { + xpoint = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO); + rl_delete_text (rl_point, xpoint); + } + return 0; +} + +/* Delete the character under the cursor, unless the insertion + point is at the end of the line, in which case the character + behind the cursor is deleted. COUNT is obeyed and may be used + to delete forward or backward that many characters. */ +int +rl_rubout_or_delete (count, key) + int count, key; +{ + if (rl_end != 0 && rl_point == rl_end) + return (_rl_rubout_char (count, key)); + else + return (rl_delete (count, key)); +} + +/* Delete all spaces and tabs around point. */ +int +rl_delete_horizontal_space (count, ignore) + int count, ignore; +{ + int start; + + while (rl_point && whitespace (rl_line_buffer[rl_point - 1])) + rl_point--; + + start = rl_point; + + while (rl_point < rl_end && whitespace (rl_line_buffer[rl_point])) + rl_point++; + + if (start != rl_point) + { + rl_delete_text (start, rl_point); + rl_point = start; + } + + if (rl_point < 0) + rl_point = 0; + + return 0; +} + +/* Like the tcsh editing function delete-char-or-list. The eof character + is caught before this is invoked, so this really does the same thing as + delete-char-or-list-or-eof, as long as it's bound to the eof character. */ +int +rl_delete_or_show_completions (count, key) + int count, key; +{ + if (rl_end != 0 && rl_point == rl_end) + return (rl_possible_completions (count, key)); + else + return (rl_delete (count, key)); +} + +#ifndef RL_COMMENT_BEGIN_DEFAULT +#define RL_COMMENT_BEGIN_DEFAULT "#" +#endif + +/* Turn the current line into a comment in shell history. + A K*rn shell style function. */ +int +rl_insert_comment (count, key) + int count, key; +{ + char *rl_comment_text; + int rl_comment_len; + + rl_beg_of_line (1, key); + rl_comment_text = _rl_comment_begin ? _rl_comment_begin : RL_COMMENT_BEGIN_DEFAULT; + + if (rl_explicit_arg == 0) + rl_insert_text (rl_comment_text); + else + { + rl_comment_len = strlen (rl_comment_text); + if (STREQN (rl_comment_text, rl_line_buffer, rl_comment_len)) + rl_delete_text (rl_point, rl_point + rl_comment_len); + else + rl_insert_text (rl_comment_text); + } + + (*rl_redisplay_function) (); + rl_newline (1, '\n'); + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Changing Case */ +/* */ +/* **************************************************************** */ + +/* The three kinds of things that we know how to do. */ +#define UpCase 1 +#define DownCase 2 +#define CapCase 3 + +/* Uppercase the word at point. */ +int +rl_upcase_word (count, key) + int count, key; +{ + return (rl_change_case (count, UpCase)); +} + +/* Lowercase the word at point. */ +int +rl_downcase_word (count, key) + int count, key; +{ + return (rl_change_case (count, DownCase)); +} + +/* Upcase the first letter, downcase the rest. */ +int +rl_capitalize_word (count, key) + int count, key; +{ + return (rl_change_case (count, CapCase)); +} + +/* The meaty function. + Change the case of COUNT words, performing OP on them. + OP is one of UpCase, DownCase, or CapCase. + If a negative argument is given, leave point where it started, + otherwise, leave it where it moves to. */ +static int +rl_change_case (count, op) + int count, op; +{ + int start, next, end; + int inword, c, nc, nop; +#if defined (HANDLE_MULTIBYTE) + wchar_t wc, nwc; + char mb[MB_LEN_MAX+1]; + int mlen; + size_t m; + mbstate_t mps; +#endif + + start = rl_point; + rl_forward_word (count, 0); + end = rl_point; + + if (op != UpCase && op != DownCase && op != CapCase) + { + rl_ding (); + return 1; + } + + if (count < 0) + SWAP (start, end); + +#if defined (HANDLE_MULTIBYTE) + memset (&mps, 0, sizeof (mbstate_t)); +#endif + + /* We are going to modify some text, so let's prepare to undo it. */ + rl_modifying (start, end); + + inword = 0; + while (start < end) + { + c = _rl_char_value (rl_line_buffer, start); + /* This assumes that the upper and lower case versions are the same width. */ + next = MB_NEXTCHAR (rl_line_buffer, start, 1, MB_FIND_NONZERO); + + if (_rl_walphabetic (c) == 0) + { + inword = 0; + start = next; + continue; + } + + if (op == CapCase) + { + nop = inword ? DownCase : UpCase; + inword = 1; + } + else + nop = op; + if (MB_CUR_MAX == 1 || rl_byte_oriented || isascii ((unsigned char)c)) + { + nc = (nop == UpCase) ? _rl_to_upper (c) : _rl_to_lower (c); + rl_line_buffer[start] = nc; + } +#if defined (HANDLE_MULTIBYTE) + else + { + m = mbrtowc (&wc, rl_line_buffer + start, end - start, &mps); + if (MB_INVALIDCH (m)) + wc = (wchar_t)rl_line_buffer[start]; + else if (MB_NULLWCH (m)) + wc = L'\0'; + nwc = (nop == UpCase) ? _rl_to_wupper (wc) : _rl_to_wlower (wc); + if (nwc != wc) /* just skip unchanged characters */ + { + mlen = wcrtomb (mb, nwc, &mps); + if (mlen > 0) + mb[mlen] = '\0'; + /* Assume the same width */ + strncpy (rl_line_buffer + start, mb, mlen); + } + } +#endif + + start = next; + } + + rl_point = end; + return 0; +} + +/* **************************************************************** */ +/* */ +/* Transposition */ +/* */ +/* **************************************************************** */ + +/* Transpose the words at point. If point is at the end of the line, + transpose the two words before point. */ +int +rl_transpose_words (count, key) + int count, key; +{ + char *word1, *word2; + int w1_beg, w1_end, w2_beg, w2_end; + int orig_point = rl_point; + + if (!count) + return 0; + + /* Find the two words. */ + rl_forward_word (count, key); + w2_end = rl_point; + rl_backward_word (1, key); + w2_beg = rl_point; + rl_backward_word (count, key); + w1_beg = rl_point; + rl_forward_word (1, key); + w1_end = rl_point; + + /* Do some check to make sure that there really are two words. */ + if ((w1_beg == w2_beg) || (w2_beg < w1_end)) + { + rl_ding (); + rl_point = orig_point; + return 1; + } + + /* Get the text of the words. */ + word1 = rl_copy_text (w1_beg, w1_end); + word2 = rl_copy_text (w2_beg, w2_end); + + /* We are about to do many insertions and deletions. Remember them + as one operation. */ + rl_begin_undo_group (); + + /* Do the stuff at word2 first, so that we don't have to worry + about word1 moving. */ + rl_point = w2_beg; + rl_delete_text (w2_beg, w2_end); + rl_insert_text (word1); + + rl_point = w1_beg; + rl_delete_text (w1_beg, w1_end); + rl_insert_text (word2); + + /* This is exactly correct since the text before this point has not + changed in length. */ + rl_point = w2_end; + + /* I think that does it. */ + rl_end_undo_group (); + xfree (word1); + xfree (word2); + + return 0; +} + +/* Transpose the characters at point. If point is at the end of the line, + then transpose the characters before point. */ +int +rl_transpose_chars (count, key) + int count, key; +{ +#if defined (HANDLE_MULTIBYTE) + char *dummy; + int i; +#else + char dummy[2]; +#endif + int char_length, prev_point; + + if (count == 0) + return 0; + + if (!rl_point || rl_end < 2) + { + rl_ding (); + return 1; + } + + rl_begin_undo_group (); + + if (rl_point == rl_end) + { + rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + count = 1; + } + + prev_point = rl_point; + rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO); + +#if defined (HANDLE_MULTIBYTE) + char_length = prev_point - rl_point; + dummy = (char *)xmalloc (char_length + 1); + for (i = 0; i < char_length; i++) + dummy[i] = rl_line_buffer[rl_point + i]; + dummy[i] = '\0'; +#else + dummy[0] = rl_line_buffer[rl_point]; + dummy[char_length = 1] = '\0'; +#endif + + rl_delete_text (rl_point, rl_point + char_length); + + rl_point = _rl_find_next_mbchar (rl_line_buffer, rl_point, count, MB_FIND_NONZERO); + + _rl_fix_point (0); + rl_insert_text (dummy); + rl_end_undo_group (); + +#if defined (HANDLE_MULTIBYTE) + xfree (dummy); +#endif + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Character Searching */ +/* */ +/* **************************************************************** */ + +int +#if defined (HANDLE_MULTIBYTE) +_rl_char_search_internal (count, dir, smbchar, len) + int count, dir; + char *smbchar; + int len; +#else +_rl_char_search_internal (count, dir, schar) + int count, dir, schar; +#endif +{ + int pos, inc; +#if defined (HANDLE_MULTIBYTE) + int prepos; +#endif + + if (dir == 0) + return 1; + + pos = rl_point; + inc = (dir < 0) ? -1 : 1; + while (count) + { + if ((dir < 0 && pos <= 0) || (dir > 0 && pos >= rl_end)) + { + rl_ding (); + return 1; + } + +#if defined (HANDLE_MULTIBYTE) + pos = (inc > 0) ? _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY) + : _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY); +#else + pos += inc; +#endif + do + { +#if defined (HANDLE_MULTIBYTE) + if (_rl_is_mbchar_matched (rl_line_buffer, pos, rl_end, smbchar, len)) +#else + if (rl_line_buffer[pos] == schar) +#endif + { + count--; + if (dir < 0) + rl_point = (dir == BTO) ? _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY) + : pos; + else + rl_point = (dir == FTO) ? _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY) + : pos; + break; + } +#if defined (HANDLE_MULTIBYTE) + prepos = pos; +#endif + } +#if defined (HANDLE_MULTIBYTE) + while ((dir < 0) ? (pos = _rl_find_prev_mbchar (rl_line_buffer, pos, MB_FIND_ANY)) != prepos + : (pos = _rl_find_next_mbchar (rl_line_buffer, pos, 1, MB_FIND_ANY)) != prepos); +#else + while ((dir < 0) ? pos-- : ++pos < rl_end); +#endif + } + return (0); +} + +/* Search COUNT times for a character read from the current input stream. + FDIR is the direction to search if COUNT is non-negative; otherwise + the search goes in BDIR. So much is dependent on HANDLE_MULTIBYTE + that there are two separate versions of this function. */ +#if defined (HANDLE_MULTIBYTE) +static int +_rl_char_search (count, fdir, bdir) + int count, fdir, bdir; +{ + char mbchar[MB_LEN_MAX]; + int mb_len; + + mb_len = _rl_read_mbchar (mbchar, MB_LEN_MAX); + + if (mb_len <= 0) + return 1; + + if (count < 0) + return (_rl_char_search_internal (-count, bdir, mbchar, mb_len)); + else + return (_rl_char_search_internal (count, fdir, mbchar, mb_len)); +} +#else /* !HANDLE_MULTIBYTE */ +static int +_rl_char_search (count, fdir, bdir) + int count, fdir, bdir; +{ + int c; + + RL_SETSTATE(RL_STATE_MOREINPUT); + c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (c < 0) + return 1; + + if (count < 0) + return (_rl_char_search_internal (-count, bdir, c)); + else + return (_rl_char_search_internal (count, fdir, c)); +} +#endif /* !HANDLE_MULTIBYTE */ + +#if defined (READLINE_CALLBACKS) +static int +_rl_char_search_callback (data) + _rl_callback_generic_arg *data; +{ + _rl_callback_func = 0; + _rl_want_redisplay = 1; + + return (_rl_char_search (data->count, data->i1, data->i2)); +} +#endif + +int +rl_char_search (count, key) + int count, key; +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_data->i1 = FFIND; + _rl_callback_data->i2 = BFIND; + _rl_callback_func = _rl_char_search_callback; + return (0); + } +#endif + + return (_rl_char_search (count, FFIND, BFIND)); +} + +int +rl_backward_char_search (count, key) + int count, key; +{ +#if defined (READLINE_CALLBACKS) + if (RL_ISSTATE (RL_STATE_CALLBACK)) + { + _rl_callback_data = _rl_callback_data_alloc (count); + _rl_callback_data->i1 = BFIND; + _rl_callback_data->i2 = FFIND; + _rl_callback_func = _rl_char_search_callback; + return (0); + } +#endif + + return (_rl_char_search (count, BFIND, FFIND)); +} + +/* **************************************************************** */ +/* */ +/* The Mark and the Region. */ +/* */ +/* **************************************************************** */ + +/* Set the mark at POSITION. */ +int +_rl_set_mark_at_pos (position) + int position; +{ + if (position > rl_end) + return 1; + + rl_mark = position; + return 0; +} + +/* A bindable command to set the mark. */ +int +rl_set_mark (count, key) + int count, key; +{ + return (_rl_set_mark_at_pos (rl_explicit_arg ? count : rl_point)); +} + +/* Exchange the position of mark and point. */ +int +rl_exchange_point_and_mark (count, key) + int count, key; +{ + if (rl_mark > rl_end) + rl_mark = -1; + + if (rl_mark == -1) + { + rl_ding (); + return 1; + } + else + SWAP (rl_point, rl_mark); + + return 0; +} diff --git a/lib/sh/casemod.c b/lib/sh/casemod.c index bbcffa6f..48ce8514 100644 --- a/lib/sh/casemod.c +++ b/lib/sh/casemod.c @@ -1,6 +1,6 @@ /* casemod.c -- functions to change case of strings */ -/* Copyright (C) 2008,2009 Free Software Foundation, Inc. +/* Copyright (C) 2008,2009,2015 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -49,7 +49,7 @@ #if !defined (HANDLE_MULTIBYTE) # define cval(s, i) ((s)[(i)]) # define iswalnum(c) (isalnum(c)) -# define TOGGLE(x) (ISUPPER (x) ? tolower (x) : (TOUPPER (x))) +# define TOGGLE(x) (ISUPPER (x) ? tolower ((unsigned char)x) : (TOUPPER (x))) #else # define TOGGLE(x) (iswupper (x) ? towlower (x) : (_to_wupper(x))) #endif @@ -105,7 +105,7 @@ sh_modcase (string, pat, flags) char *pat; int flags; { - int start, next, end; + int start, next, end, retind; int inword, c, nc, nop, match, usewords; char *ret, *s; wchar_t wc; @@ -131,8 +131,8 @@ sh_modcase (string, pat, flags) start = 0; end = strlen (string); - ret = (char *)xmalloc (end + 1); - strcpy (ret, string); + ret = (char *)xmalloc (2*end + 1); + retind = 0; /* See if we are supposed to split on alphanumerics and operate on each word */ usewords = (flags & CASE_USEWORDS); @@ -141,26 +141,23 @@ sh_modcase (string, pat, flags) inword = 0; while (start < end) { - wc = cval (ret, start); + wc = cval (string, start); if (iswalnum (wc) == 0) - { - inword = 0; -#if 0 - ADVANCE_CHAR (ret, end, start); - continue; -#endif - } + inword = 0; if (pat) { next = start; - ADVANCE_CHAR (ret, end, next); - s = substring (ret, start, next); + ADVANCE_CHAR (string, end, next); + s = substring (string, start, next); match = strmatch (pat, s, FNM_EXTMATCH) != FNM_NOMATCH; free (s); if (match == 0) { + /* copy unmatched portion */ + memcpy (ret + retind, string + start, next - start); + retind += next - start; start = next; inword = 1; continue; @@ -210,27 +207,27 @@ sh_modcase (string, pat, flags) else nop = flags; - /* Need to check UCHAR_MAX since wc may have already been converted to a - wide character by cval() */ - if (MB_CUR_MAX == 1 || (wc <= UCHAR_MAX && is_basic ((int)wc))) + /* Can't short-circuit, some locales have multibyte upper and lower + case equivalents of single-byte ascii characters (e.g., Turkish) */ + if (MB_CUR_MAX == 1) { singlebyte: switch (nop) - { - default: - case CASE_NOOP: nc = wc; break; - case CASE_UPPER: nc = TOUPPER (wc); break; - case CASE_LOWER: nc = TOLOWER (wc); break; - case CASE_TOGGLEALL: - case CASE_TOGGLE: nc = TOGGLE (wc); break; - } - ret[start] = nc; + { + default: + case CASE_NOOP: nc = wc; break; + case CASE_UPPER: nc = TOUPPER (wc); break; + case CASE_LOWER: nc = TOLOWER (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nc = TOGGLE (wc); break; + } + ret[retind++] = nc; } #if defined (HANDLE_MULTIBYTE) else { m = mbrtowc (&wc, string + start, end - start, &state); - if (MB_INVALIDCH (m)) + if (MB_INVALIDCH (m) || m == 1) { wc = (unsigned char)string[start]; goto singlebyte; @@ -238,28 +235,34 @@ singlebyte: else if (MB_NULLWCH (m)) wc = L'\0'; switch (nop) - { - default: - case CASE_NOOP: nwc = wc; break; - case CASE_UPPER: nwc = _to_wupper (wc); break; - case CASE_LOWER: nwc = _to_wlower (wc); break; - case CASE_TOGGLEALL: - case CASE_TOGGLE: nwc = TOGGLE (wc); break; - } - if (nwc != wc) /* just skip unchanged characters */ + { + default: + case CASE_NOOP: nwc = wc; break; + case CASE_UPPER: nwc = _to_wupper (wc); break; + case CASE_LOWER: nwc = _to_wlower (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nwc = TOGGLE (wc); break; + } + + /* We don't have to convert `wide' characters that are in the + unsigned char range back to single-byte `multibyte' characters. */ + if ((int)nwc <= UCHAR_MAX && is_basic ((int)nwc)) + ret[retind++] = nwc; + else { mlen = wcrtomb (mb, nwc, &state); if (mlen > 0) mb[mlen] = '\0'; - /* Assume the same width */ - strncpy (ret + start, mb, mlen); + /* Don't assume the same width */ + strncpy (ret + retind, mb, mlen); + retind += mlen; } } #endif - /* This assumes that the upper and lower case versions are the same width. */ - ADVANCE_CHAR (ret, end, start); + ADVANCE_CHAR (string, end, start); } + ret[retind] = '\0'; return ret; } diff --git a/lib/sh/casemod.c~ b/lib/sh/casemod.c~ new file mode 100644 index 00000000..18386419 --- /dev/null +++ b/lib/sh/casemod.c~ @@ -0,0 +1,270 @@ +/* casemod.c -- functions to change case of strings */ + +/* Copyright (C) 2008,2009,2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#if defined (HAVE_CONFIG_H) +# include <config.h> +#endif + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ + +#include <stdc.h> + +#include <bashansi.h> +#include <bashintl.h> +#include <bashtypes.h> + +#include <stdio.h> +#include <ctype.h> +#include <xmalloc.h> + +#include <shmbchar.h> +#include <shmbutil.h> +#include <chartypes.h> +#include <typemax.h> + +#include <glob/strmatch.h> + +#define _to_wupper(wc) (iswlower (wc) ? towupper (wc) : (wc)) +#define _to_wlower(wc) (iswupper (wc) ? towlower (wc) : (wc)) + +#if !defined (HANDLE_MULTIBYTE) +# define cval(s, i) ((s)[(i)]) +# define iswalnum(c) (isalnum(c)) +# define TOGGLE(x) (ISUPPER (x) ? tolower ((unsigned char)x) : (TOUPPER (x))) +#else +# define TOGGLE(x) (iswupper (x) ? towlower (x) : (_to_wupper(x))) +#endif + +/* These must agree with the defines in externs.h */ +#define CASE_NOOP 0x0000 +#define CASE_LOWER 0x0001 +#define CASE_UPPER 0x0002 +#define CASE_CAPITALIZE 0x0004 +#define CASE_UNCAP 0x0008 +#define CASE_TOGGLE 0x0010 +#define CASE_TOGGLEALL 0x0020 +#define CASE_UPFIRST 0x0040 +#define CASE_LOWFIRST 0x0080 + +#define CASE_USEWORDS 0x1000 /* modify behavior to act on words in passed string */ + +extern char *substring __P((char *, int, int)); + +#ifndef UCHAR_MAX +# define UCHAR_MAX TYPE_MAXIMUM(unsigned char) +#endif + +#if defined (HANDLE_MULTIBYTE) +static wchar_t +cval (s, i) + char *s; + int i; +{ + size_t tmp; + wchar_t wc; + int l; + mbstate_t mps; + + if (MB_CUR_MAX == 1 || is_basic (s[i])) + return ((wchar_t)s[i]); + l = strlen (s); + if (i >= (l - 1)) + return ((wchar_t)s[i]); + memset (&mps, 0, sizeof (mbstate_t)); + tmp = mbrtowc (&wc, s + i, l - i, &mps); + if (MB_INVALIDCH (tmp) || MB_NULLWCH (tmp)) + return ((wchar_t)s[i]); + return wc; +} +#endif + +/* Modify the case of characters in STRING matching PAT based on the value of + FLAGS. If PAT is null, modify the case of each character */ +char * +sh_modcase (string, pat, flags) + const char *string; + char *pat; + int flags; +{ + int start, next, end, retind; + int inword, c, nc, nop, match, usewords; + char *ret, *s; + wchar_t wc; +#if defined (HANDLE_MULTIBYTE) + wchar_t nwc; + char mb[MB_LEN_MAX+1]; + int mlen; + size_t m; + mbstate_t state; +#endif + + if (string == 0 || *string == 0) + { + ret = (char *)xmalloc (1); + ret[0] = '\0'; + return ret; + } + +#if defined (HANDLE_MULTIBYTE) + memset (&state, 0, sizeof (mbstate_t)); +#endif + + start = 0; + end = strlen (string); + + ret = (char *)xmalloc (2*end + 1); + retind = 0; + + /* See if we are supposed to split on alphanumerics and operate on each word */ + usewords = (flags & CASE_USEWORDS); + flags &= ~CASE_USEWORDS; + + inword = 0; + while (start < end) + { + wc = cval (string, start); + + if (iswalnum (wc) == 0) + inword = 0; + + if (pat) + { + next = start; + ADVANCE_CHAR (string, end, next); + s = substring (string, start, next); + match = strmatch (pat, s, FNM_EXTMATCH) != FNM_NOMATCH; + free (s); + if (match == 0) + { + /* copy unmatched portion */ + memcpy (ret + retind, string + start, next - start); + retind += next - start; + start = next; + inword = 1; + continue; + } + } + + /* XXX - for now, the toggling operators work on the individual + words in the string, breaking on alphanumerics. Should I + leave the capitalization operators to do that also? */ + if (flags == CASE_CAPITALIZE) + { + if (usewords) + nop = inword ? CASE_LOWER : CASE_UPPER; + else + nop = (start > 0) ? CASE_LOWER : CASE_UPPER; + inword = 1; + } + else if (flags == CASE_UNCAP) + { + if (usewords) + nop = inword ? CASE_UPPER : CASE_LOWER; + else + nop = (start > 0) ? CASE_UPPER : CASE_LOWER; + inword = 1; + } + else if (flags == CASE_UPFIRST) + { + if (usewords) + nop = inword ? CASE_NOOP : CASE_UPPER; + else + nop = (start > 0) ? CASE_NOOP : CASE_UPPER; + inword = 1; + } + else if (flags == CASE_LOWFIRST) + { + if (usewords) + nop = inword ? CASE_NOOP : CASE_LOWER; + else + nop = (start > 0) ? CASE_NOOP : CASE_LOWER; + inword = 1; + } + else if (flags == CASE_TOGGLE) + { + nop = inword ? CASE_NOOP : CASE_TOGGLE; + inword = 1; + } + else + nop = flags; + + /* Can't short-circuit, some locales have multibyte upper and lower + case equivalents of single-byte ascii characters (e.g., Turkish) */ + if (MB_CUR_MAX == 1) + { +singlebyte: + switch (nop) + { + default: + case CASE_NOOP: nc = wc; break; + case CASE_UPPER: nc = TOUPPER (wc); break; + case CASE_LOWER: nc = TOLOWER (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nc = TOGGLE (wc); break; + } + ret[retind++] = nc; + } +#if defined (HANDLE_MULTIBYTE) + else + { + m = mbrtowc (&wc, string + start, end - start, &state); +if (m == 1) + itrace("sh_modcase: mbrtowc returns 1 for single byte char"); + if (MB_INVALIDCH (m) || m == 1) + { + wc = (unsigned char)string[start]; + goto singlebyte; + } + else if (MB_NULLWCH (m)) + wc = L'\0'; + switch (nop) + { + default: + case CASE_NOOP: nwc = wc; break; + case CASE_UPPER: nwc = _to_wupper (wc); break; + case CASE_LOWER: nwc = _to_wlower (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nwc = TOGGLE (wc); break; + } + + /* We don't have to convert `wide' characters that are in the + unsigned char range back to single-byte `multibyte' characters. */ + if ((int)nwc <= UCHAR_MAX && is_basic ((int)nwc)) + ret[retind++] = nwc; + else + { + mlen = wcrtomb (mb, nwc, &state); + if (mlen > 0) + mb[mlen] = '\0'; + /* Don't assume the same width */ + strncpy (ret + retind, mb, mlen); + retind += mlen; + } + } +#endif + + ADVANCE_CHAR (string, end, start); + } + + ret[retind] = '\0'; + return ret; +} diff --git a/lib/sh/shquote.c b/lib/sh/shquote.c index e678365b..40d09935 100644 --- a/lib/sh/shquote.c +++ b/lib/sh/shquote.c @@ -33,6 +33,9 @@ #include "syntax.h" #include <xmalloc.h> +#include "shmbchar.h" +#include "shmbutil.h" + extern char *ansic_quote __P((char *, int, int *)); extern int ansic_shouldquote __P((const char *)); @@ -231,13 +234,25 @@ sh_backslash_quote (string, table, flags) int flags; { int c; - char *result, *r, *s, *backslash_table; + size_t slen; + char *result, *r, *s, *backslash_table, *send; + DECLARE_MBSTATE; - result = (char *)xmalloc (2 * strlen (string) + 1); + slen = strlen (string); + send = string + slen; + result = (char *)xmalloc (2 * slen + 1); backslash_table = table ? table : (char *)bstab; for (r = result, s = string; s && (c = *s); s++) { +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1 && is_basic (c) == 0) + { + COPY_CHAR_P (r, s, send); + s--; /* compensate for auto-increment in loop above */ + continue; + } +#endif if (backslash_table[c] == 1) *r++ = '\\'; else if (c == '#' && s == string) /* comment char */ @@ -535,7 +535,7 @@ locale_setblanks () for (x = 0; x < sh_syntabsiz; x++) { - if (isblank (x)) + if (isblank ((unsigned char)x)) sh_syntaxtab[x] |= CSHBRK|CBLANK; else if (member (x, shell_break_chars)) { diff --git a/locale.c~ b/locale.c~ new file mode 100644 index 00000000..8dabc7d2 --- /dev/null +++ b/locale.c~ @@ -0,0 +1,563 @@ +/* locale.c - Miscellaneous internationalization functions. */ + +/* Copyright (C) 1996-2009,2012 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if HAVE_LANGINFO_CODESET +# include <langinfo.h> +#endif + +#include "bashintl.h" +#include "bashansi.h" +#include <stdio.h> +#include "chartypes.h" +#include <errno.h> + +#include "shell.h" +#include "input.h" /* For bash_input */ + +#ifndef errno +extern int errno; +#endif + +int locale_utf8locale; /* unused for now */ +int locale_mb_cur_max; /* value of MB_CUR_MAX for current locale (LC_CTYPE) */ + +extern int dump_translatable_strings, dump_po_strings; + +/* The current locale when the program begins */ +static char *default_locale; + +/* The current domain for textdomain(3). */ +static char *default_domain; +static char *default_dir; + +/* tracks the value of LC_ALL; used to override values for other locale + categories */ +static char *lc_all; + +/* tracks the value of LC_ALL; used to provide defaults for locale + categories */ +static char *lang; + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. */ +static int reset_locale_vars __P((void)); + +static void locale_setblanks __P((void)); +static int locale_isutf8 __P((char *)); + +/* Set the value of default_locale and make the current locale the + system default locale. This should be called very early in main(). */ +void +set_default_locale () +{ +#if defined (HAVE_SETLOCALE) + default_locale = setlocale (LC_ALL, ""); + if (default_locale) + default_locale = savestring (default_locale); +#endif /* HAVE_SETLOCALE */ + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + locale_mb_cur_max = MB_CUR_MAX; +} + +/* Set default values for LC_CTYPE, LC_COLLATE, LC_MESSAGES, LC_NUMERIC and + LC_TIME if they are not specified in the environment, but LC_ALL is. This + should be called from main() after parsing the environment. */ +void +set_default_locale_vars () +{ + char *val; + +#if defined (HAVE_SETLOCALE) + +# if defined (LC_CTYPE) + val = get_string_value ("LC_CTYPE"); + if (val == 0 && lc_all && *lc_all) + { + setlocale (LC_CTYPE, lc_all); + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + u32reset (); + } +# endif + +# if defined (LC_COLLATE) + val = get_string_value ("LC_COLLATE"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_COLLATE, lc_all); +# endif /* LC_COLLATE */ + +# if defined (LC_MESSAGES) + val = get_string_value ("LC_MESSAGES"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_MESSAGES, lc_all); +# endif /* LC_MESSAGES */ + +# if defined (LC_NUMERIC) + val = get_string_value ("LC_NUMERIC"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_NUMERIC, lc_all); +# endif /* LC_NUMERIC */ + +# if defined (LC_TIME) + val = get_string_value ("LC_TIME"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_TIME, lc_all); +# endif /* LC_TIME */ + +#endif /* HAVE_SETLOCALE */ + + val = get_string_value ("TEXTDOMAIN"); + if (val && *val) + { + FREE (default_domain); + default_domain = savestring (val); + if (default_dir && *default_dir) + bindtextdomain (default_domain, default_dir); + } + + val = get_string_value ("TEXTDOMAINDIR"); + if (val && *val) + { + FREE (default_dir); + default_dir = savestring (val); + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + } +} + +/* Set one of the locale categories (specified by VAR) to VALUE. Returns 1 + if successful, 0 otherwise. */ +int +set_locale_var (var, value) + char *var, *value; +{ + int r; + char *x; + + x = ""; + errno = 0; + if (var[0] == 'T' && var[10] == 0) /* TEXTDOMAIN */ + { + FREE (default_domain); + default_domain = value ? savestring (value) : (char *)NULL; + if (default_dir && *default_dir) + bindtextdomain (default_domain, default_dir); + return (1); + } + else if (var[0] == 'T') /* TEXTDOMAINDIR */ + { + FREE (default_dir); + default_dir = value ? savestring (value) : (char *)NULL; + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + return (1); + } + + /* var[0] == 'L' && var[1] == 'C' && var[2] == '_' */ + + else if (var[3] == 'A') /* LC_ALL */ + { + FREE (lc_all); + if (value) + lc_all = savestring (value); + else + { + lc_all = (char *)xmalloc (1); + lc_all[0] = '\0'; + } +#if defined (HAVE_SETLOCALE) + r = *lc_all ? ((x = setlocale (LC_ALL, lc_all)) != 0) : reset_locale_vars (); + if (x == 0) + { + if (errno == 0) + internal_warning(_("setlocale: LC_ALL: cannot change locale (%s)"), lc_all); + else + internal_warning(_("setlocale: LC_ALL: cannot change locale (%s): %s"), lc_all, strerror (errno)); + } + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + u32reset (); + return r; +#else + return (1); +#endif + } + +#if defined (HAVE_SETLOCALE) + else if (var[3] == 'C' && var[4] == 'T') /* LC_CTYPE */ + { +# if defined (LC_CTYPE) + if (lc_all == 0 || *lc_all == '\0') + { + x = setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")); + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + u32reset (); + } +# endif + } + else if (var[3] == 'C' && var[4] == 'O') /* LC_COLLATE */ + { +# if defined (LC_COLLATE) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")); +# endif /* LC_COLLATE */ + } + else if (var[3] == 'M' && var[4] == 'E') /* LC_MESSAGES */ + { +# if defined (LC_MESSAGES) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")); +# endif /* LC_MESSAGES */ + } + else if (var[3] == 'N' && var[4] == 'U') /* LC_NUMERIC */ + { +# if defined (LC_NUMERIC) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); +# endif /* LC_NUMERIC */ + } + else if (var[3] == 'T' && var[4] == 'I') /* LC_TIME */ + { +# if defined (LC_TIME) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_TIME, get_locale_var ("LC_TIME")); +# endif /* LC_TIME */ + } +#endif /* HAVE_SETLOCALE */ + + if (x == 0) + { + if (errno == 0) + internal_warning(_("setlocale: %s: cannot change locale (%s)"), var, get_locale_var (var)); + else + internal_warning(_("setlocale: %s: cannot change locale (%s): %s"), var, get_locale_var (var), strerror (errno)); + } + + return (x != 0); +} + +/* Called when LANG is assigned a value. Tracks value in `lang'. Calls + reset_locale_vars() to reset any default values if LC_ALL is unset or + null. */ +int +set_lang (var, value) + char *var, *value; +{ + FREE (lang); + if (value) + lang = savestring (value); + else + { + lang = (char *)xmalloc (1); + lang[0] = '\0'; + } + + return ((lc_all == 0 || *lc_all == 0) ? reset_locale_vars () : 0); +} + +/* Set default values for LANG and LC_ALL. Default values for all other + locale-related variables depend on these. */ +void +set_default_lang () +{ + char *v; + + v = get_string_value ("LC_ALL"); + set_locale_var ("LC_ALL", v); + + v = get_string_value ("LANG"); + set_lang ("LANG", v); +} + +/* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE). + The precedence is as POSIX.2 specifies: LC_ALL has precedence over + the specific locale variables, and LANG, if set, is used as the default. */ +char * +get_locale_var (var) + char *var; +{ + char *locale; + + locale = lc_all; + + if (locale == 0 || *locale == 0) + locale = get_string_value (var); /* XXX - mem leak? */ + if (locale == 0 || *locale == 0) + locale = lang; + if (locale == 0 || *locale == 0) +#if 0 + locale = default_locale; /* system-dependent; not really portable. should it be "C"? */ +#else + locale = ""; +#endif + return (locale); +} + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. DO NOT CALL THIS + IF LC_ALL HAS BEEN ASSIGNED A VALUE. */ +static int +reset_locale_vars () +{ + char *t; +#if defined (HAVE_SETLOCALE) + if (lang == 0 || *lang == '\0') + maybe_make_export_env (); /* trust that this will change environment for setlocale */ + if (setlocale (LC_ALL, lang ? lang : "") == 0) + return 0; + +# if defined (LC_CTYPE) + t = setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")); +# endif +# if defined (LC_COLLATE) + t = setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")); +# endif +# if defined (LC_MESSAGES) + t = setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")); +# endif +# if defined (LC_NUMERIC) + t = setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); +# endif +# if defined (LC_TIME) + t = setlocale (LC_TIME, get_locale_var ("LC_TIME")); +# endif + + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + u32reset (); + +#endif + return 1; +} + +/* Translate the contents of STRING, a $"..." quoted string, according + to the current locale. In the `C' or `POSIX' locale, or if gettext() + is not available, the passed string is returned unchanged. The + length of the translated string is returned in LENP, if non-null. */ +char * +localetrans (string, len, lenp) + char *string; + int len, *lenp; +{ + char *locale, *t; + char *translated; + int tlen; + + /* Don't try to translate null strings. */ + if (string == 0 || *string == 0) + { + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + locale = get_locale_var ("LC_MESSAGES"); + + /* If we don't have setlocale() or the current locale is `C' or `POSIX', + just return the string. If we don't have gettext(), there's no use + doing anything else. */ + if (locale == 0 || locale[0] == '\0' || + (locale[0] == 'C' && locale[1] == '\0') || STREQ (locale, "POSIX")) + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + return (t); + } + + /* Now try to translate it. */ + if (default_domain && *default_domain) + translated = dgettext (default_domain, string); + else + translated = string; + + if (translated == string) /* gettext returns its argument if untranslatable */ + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + } + else + { + tlen = strlen (translated); + t = (char *)xmalloc (tlen + 1); + strcpy (t, translated); + if (lenp) + *lenp = tlen; + } + return (t); +} + +/* Change a bash string into a string suitable for inclusion in a `po' file. + This backslash-escapes `"' and `\' and changes newlines into \\\n"\n". */ +char * +mk_msgstr (string, foundnlp) + char *string; + int *foundnlp; +{ + register int c, len; + char *result, *r, *s; + + for (len = 0, s = string; s && *s; s++) + { + len++; + if (*s == '"' || *s == '\\') + len++; + else if (*s == '\n') + len += 5; + } + + r = result = (char *)xmalloc (len + 3); + *r++ = '"'; + + for (s = string; s && (c = *s); s++) + { + if (c == '\n') /* <NL> -> \n"<NL>" */ + { + *r++ = '\\'; + *r++ = 'n'; + *r++ = '"'; + *r++ = '\n'; + *r++ = '"'; + if (foundnlp) + *foundnlp = 1; + continue; + } + if (c == '"' || c == '\\') + *r++ = '\\'; + *r++ = c; + } + + *r++ = '"'; + *r++ = '\0'; + + return result; +} + +/* $"..." -- Translate the portion of STRING between START and END + according to current locale using gettext (if available) and return + the result. The caller will take care of leaving the quotes intact. + The string will be left without the leading `$' by the caller. + If translation is performed, the translated string will be double-quoted + by the caller. The length of the translated string is returned in LENP, + if non-null. */ +char * +localeexpand (string, start, end, lineno, lenp) + char *string; + int start, end, lineno, *lenp; +{ + int len, tlen, foundnl; + char *temp, *t, *t2; + + temp = (char *)xmalloc (end - start + 1); + for (tlen = 0, len = start; len < end; ) + temp[tlen++] = string[len++]; + temp[tlen] = '\0'; + + /* If we're just dumping translatable strings, don't do anything with the + string itself, but if we're dumping in `po' file format, convert it into + a form more palatable to gettext(3) and friends by quoting `"' and `\' + with backslashes and converting <NL> into `\n"<NL>"'. If we find a + newline in TEMP, we first output a `msgid ""' line and then the + translated string; otherwise we output the `msgid' and translated + string all on one line. */ + if (dump_translatable_strings) + { + if (dump_po_strings) + { + foundnl = 0; + t = mk_msgstr (temp, &foundnl); + t2 = foundnl ? "\"\"\n" : ""; + + printf ("#: %s:%d\nmsgid %s%s\nmsgstr \"\"\n", + yy_input_name (), lineno, t2, t); + free (t); + } + else + printf ("\"%s\"\n", temp); + + if (lenp) + *lenp = tlen; + return (temp); + } + else if (*temp) + { + t = localetrans (temp, tlen, &len); + free (temp); + if (lenp) + *lenp = len; + return (t); + } + else + { + if (lenp) + *lenp = 0; + return (temp); + } +} + +/* Set every character in the <blank> character class to be a shell break + character for the lexical analyzer when the locale changes. */ +static void +locale_setblanks () +{ + int x; + + for (x = 0; x < sh_syntabsiz; x++) + { + if (isblank (x)) + sh_syntaxtab[x] |= CSHBRK|CBLANK; + else if (member (x, shell_break_chars)) + { + sh_syntaxtab[x] |= CSHBRK; + sh_syntaxtab[x] &= ~CBLANK; + } + else + sh_syntaxtab[x] &= ~(CSHBRK|CBLANK); + } +} + +static int +locale_isutf8 (lspec) + char *lspec; +{ + char *cp; + +#if HAVE_LANGINFO_CODESET + cp = nl_langinfo (CODESET); + return (STREQ (cp, "UTF-8") || STREQ (cp, "utf8")); +#else + /* Take a shot */ + return (strstr (lspec, "UTF-8") || strstr (lspec, "utf8")); +#endif +} @@ -3813,7 +3813,7 @@ eof_error: /* If we can read a reserved word, try to read one. */ if (tflags & LEX_RESWDOK) { - if MBTEST(islower (ch)) + if MBTEST(islower ((unsigned char)ch)) { /* Add this character. */ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); @@ -3841,7 +3841,7 @@ eof_error: RESWDOK flag, but reset the reserved word length counter so we can read another one. */ else if MBTEST(((tflags & LEX_INCASE) == 0) && - (isblank(ch) || ch == '\n') && + (isblank((unsigned char)ch) || ch == '\n') && lex_rwlen == 2 && STREQN (ret + retind - 2, "do", 2)) { @@ -3866,7 +3866,7 @@ eof_error: #if 0 /* If we find a space or tab but have read something and it's not `do', turn off the reserved-word-ok flag */ - else if MBTEST(isblank (ch) && lex_rwlen > 0) + else if MBTEST(isblank ((unsigned char)ch) && lex_rwlen > 0) { tflags &= ~LEX_RESWDOK; /*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/ Binary files differ@@ -1,6 +1,6 @@ /* shell.c -- GNU's idea of the POSIX shell specification. */ -/* Copyright (C) 1987-2012 Free Software Foundation, Inc. +/* Copyright (C) 1987-2015 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -415,7 +415,7 @@ main (argc, argv, env) mcheck (programming_error, (void (*) ())0); #endif /* USE_GNU_MALLOC_LIBRARY */ - if (setjmp (subshell_top_level)) + if (setjmp_sigs (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; @@ -598,7 +598,7 @@ main (argc, argv, env) /* Give this shell a place to longjmp to before executing the startup files. This allows users to press C-c to abort the lengthy startup. */ - code = setjmp (top_level); + code = setjmp_sigs (top_level); if (code) { if (code == EXITPROG || code == ERREXIT) diff --git a/shell.c~ b/shell.c~ new file mode 100644 index 00000000..4883dcf8 --- /dev/null +++ b/shell.c~ @@ -0,0 +1,1925 @@ +/* shell.c -- GNU's idea of the POSIX shell specification. */ + +/* Copyright (C) 1987-2012 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + Birthdate: + Sunday, January 10th, 1988. + Initial author: Brian Fox +*/ +#define INSTALL_DEBUG_MODE + +#include "config.h" + +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include <sys/file.h> +#endif +#include "posixstat.h" +#include "posixtime.h" +#include "bashansi.h" +#include <stdio.h> +#include <signal.h> +#include <errno.h> +#include "filecntl.h" +#include <pwd.h> + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include "bashintl.h" + +#define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ + +#include "shell.h" +#include "flags.h" +#include "trap.h" +#include "mailcheck.h" +#include "builtins.h" +#include "builtins/common.h" + +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ + +#include "input.h" +#include "execute_cmd.h" +#include "findcmd.h" + +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) +# include <malloc/shmalloc.h> +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include <readline/history.h> +#endif + +#if defined (READLINE) +# include <readline/readline.h> +# include "bashline.h" +#endif + +#include <tilde/tilde.h> +#include <glob/strmatch.h> + +#if defined (__OPENNT) +# include <opennt/opennt.h> +#endif + +#if !defined (HAVE_GETPW_DECLS) +extern struct passwd *getpwuid (); +#endif /* !HAVE_GETPW_DECLS */ + +#if !defined (errno) +extern int errno; +#endif + +#if defined (NO_MAIN_ENV_ARG) +extern char **environ; /* used if no third argument to main() */ +#endif + +extern char *dist_version, *release_status; +extern int patch_level, build_version; +extern int shell_level; +extern int subshell_environment; +extern int running_in_background; +extern int last_command_exit_value; +extern int line_number; +extern int expand_aliases; +extern int array_needs_making; +extern int gnu_error_format; +extern char *primary_prompt, *secondary_prompt; +extern char *this_command_name; + +/* Non-zero means that this shell has already been run; i.e. you should + call shell_reinitialize () if you need to start afresh. */ +int shell_initialized = 0; + +COMMAND *global_command = (COMMAND *)NULL; + +/* Information about the current user. */ +struct user_info current_user = +{ + (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, + (char *)NULL, (char *)NULL, (char *)NULL +}; + +/* The current host's name. */ +char *current_host_name = (char *)NULL; + +/* Non-zero means that this shell is a login shell. + Specifically: + 0 = not login shell. + 1 = login shell from getty (or equivalent fake out) + -1 = login shell from "--login" (or -l) flag. + -2 = both from getty, and from flag. + */ +int login_shell = 0; + +/* Non-zero means that at this moment, the shell is interactive. In + general, this means that the shell is at this moment reading input + from the keyboard. */ +int interactive = 0; + +/* Non-zero means that the shell was started as an interactive shell. */ +int interactive_shell = 0; + +/* Non-zero means to send a SIGHUP to all jobs when an interactive login + shell exits. */ +int hup_on_exit = 0; + +/* Non-zero means to list status of running and stopped jobs at shell exit */ +int check_jobs_at_exit = 0; + +/* Non-zero means to change to a directory name supplied as a command name */ +int autocd = 0; + +/* Tells what state the shell was in when it started: + 0 = non-interactive shell script + 1 = interactive + 2 = -c command + 3 = wordexp evaluation + This is a superset of the information provided by interactive_shell. +*/ +int startup_state = 0; + +/* Special debugging helper. */ +int debugging_login_shell = 0; + +/* The environment that the shell passes to other commands. */ +char **shell_environment; + +/* Non-zero when we are executing a top-level command. */ +int executing = 0; + +/* The number of commands executed so far. */ +int current_command_number = 1; + +/* Non-zero is the recursion depth for commands. */ +int indirection_level = 0; + +/* The name of this shell, as taken from argv[0]. */ +char *shell_name = (char *)NULL; + +/* time in seconds when the shell was started */ +time_t shell_start_time; + +/* Are we running in an emacs shell window? */ +int running_under_emacs; + +/* Do we have /dev/fd? */ +#ifdef HAVE_DEV_FD +int have_devfd = HAVE_DEV_FD; +#else +int have_devfd = 0; +#endif + +/* The name of the .(shell)rc file. */ +static char *bashrc_file = DEFAULT_BASHRC; + +/* Non-zero means to act more like the Bourne shell on startup. */ +static int act_like_sh; + +/* Non-zero if this shell is being run by `su'. */ +static int su_shell; + +/* Non-zero if we have already expanded and sourced $ENV. */ +static int sourced_env; + +/* Is this shell running setuid? */ +static int running_setuid; + +/* Values for the long-winded argument names. */ +static int debugging; /* Do debugging things. */ +static int no_rc; /* Don't execute ~/.bashrc */ +static int no_profile; /* Don't execute .profile */ +static int do_version; /* Display interesting version info. */ +static int make_login_shell; /* Make this shell be a `-bash' shell. */ +static int want_initial_help; /* --help option */ + +int debugging_mode = 0; /* In debugging mode with --debugger */ +#if defined (READLINE) +int no_line_editing = 0; /* non-zero -> don't do fancy line editing. */ +#else +int no_line_editing = 1; /* can't have line editing without readline */ +#endif +int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ +int dump_po_strings; /* Dump strings in $"..." in po format */ +int wordexp_only = 0; /* Do word expansion only */ +int protected_mode = 0; /* No command substitution with --wordexp */ + +#if defined (STRICT_POSIX) +int posixly_correct = 1; /* Non-zero means posix.2 superset. */ +#else +int posixly_correct = 0; /* Non-zero means posix.2 superset. */ +#endif + +/* Some long-winded argument names. These are obviously new. */ +#define Int 1 +#define Charp 2 +static const struct { + const char *name; + int type; + int *int_value; + char **char_value; +} long_args[] = { + { "debug", Int, &debugging, (char **)0x0 }, +#if defined (DEBUGGER) + { "debugger", Int, &debugging_mode, (char **)0x0 }, +#endif + { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, + { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, + { "help", Int, &want_initial_help, (char **)0x0 }, + { "init-file", Charp, (int *)0x0, &bashrc_file }, + { "login", Int, &make_login_shell, (char **)0x0 }, + { "noediting", Int, &no_line_editing, (char **)0x0 }, + { "noprofile", Int, &no_profile, (char **)0x0 }, + { "norc", Int, &no_rc, (char **)0x0 }, + { "posix", Int, &posixly_correct, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "protected", Int, &protected_mode, (char **)0x0 }, +#endif + { "rcfile", Charp, (int *)0x0, &bashrc_file }, +#if defined (RESTRICTED_SHELL) + { "restricted", Int, &restricted, (char **)0x0 }, +#endif + { "verbose", Int, &echo_input_at_read, (char **)0x0 }, + { "version", Int, &do_version, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "wordexp", Int, &wordexp_only, (char **)0x0 }, +#endif + { (char *)0x0, Int, (int *)0x0, (char **)0x0 } +}; + +/* These are extern so execute_simple_command can set them, and then + longjmp back to main to execute a shell script, instead of calling + main () again and resulting in indefinite, possibly fatal, stack + growth. */ +procenv_t subshell_top_level; +int subshell_argc; +char **subshell_argv; +char **subshell_envp; + +char *exec_argv0; + +#if defined (BUFFERED_INPUT) +/* The file descriptor from which the shell is reading input. */ +int default_buffered_input = -1; +#endif + +/* The following two variables are not static so they can show up in $-. */ +int read_from_stdin; /* -s flag supplied */ +int want_pending_command; /* -c flag supplied */ + +/* This variable is not static so it can be bound to $BASH_EXECUTION_STRING */ +char *command_execution_string; /* argument to -c option */ + +int malloc_trace_at_exit = 0; + +static int shell_reinitialized = 0; + +static FILE *default_input; + +static STRING_INT_ALIST *shopt_alist; +static int shopt_ind = 0, shopt_len = 0; + +static int parse_long_options __P((char **, int, int)); +static int parse_shell_options __P((char **, int, int)); +static int bind_args __P((char **, int, int, int)); + +static void start_debugger __P((void)); + +static void add_shopt_to_alist __P((char *, int)); +static void run_shopt_alist __P((void)); + +static void execute_env_file __P((char *)); +static void run_startup_files __P((void)); +static int open_shell_script __P((char *)); +static void set_bash_input __P((void)); +static int run_one_command __P((char *)); +#if defined (WORDEXP_OPTION) +static int run_wordexp __P((char *)); +#endif + +static int uidget __P((void)); + +static void init_interactive __P((void)); +static void init_noninteractive __P((void)); +static void init_interactive_script __P((void)); + +static void set_shell_name __P((char *)); +static void shell_initialize __P((void)); +static void shell_reinitialize __P((void)); + +static void show_shell_usage __P((FILE *, int)); + +#ifdef __CYGWIN__ +static void +_cygwin32_check_tmp () +{ + struct stat sb; + + if (stat ("/tmp", &sb) < 0) + internal_warning (_("could not find /tmp, please create!")); + else + { + if (S_ISDIR (sb.st_mode) == 0) + internal_warning (_("/tmp must be a valid directory name")); + } +} +#endif /* __CYGWIN__ */ + +#if defined (NO_MAIN_ENV_ARG) +/* systems without third argument to main() */ +int +main (argc, argv) + int argc; + char **argv; +#else /* !NO_MAIN_ENV_ARG */ +int +main (argc, argv, env) + int argc; + char **argv, **env; +#endif /* !NO_MAIN_ENV_ARG */ +{ + register int i; + int code, old_errexit_flag; +#if defined (RESTRICTED_SHELL) + int saverst; +#endif + volatile int locally_skip_execution; + volatile int arg_index, top_level_arg_index; +#ifdef __OPENNT + char **env; + + env = environ; +#endif /* __OPENNT */ + + USE_VAR(argc); + USE_VAR(argv); + USE_VAR(env); + USE_VAR(code); + USE_VAR(old_errexit_flag); +#if defined (RESTRICTED_SHELL) + USE_VAR(saverst); +#endif + + /* Catch early SIGINTs. */ + code = setjmp_nosigs (top_level); + if (code) + exit (2); + + xtrace_init (); + +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) +# if 1 + malloc_set_register (1); +# endif +#endif + + check_dev_tty (); + +#ifdef __CYGWIN__ + _cygwin32_check_tmp (); +#endif /* __CYGWIN__ */ + + /* Wait forever if we are debugging a login shell. */ + while (debugging_login_shell) sleep (3); + + set_default_locale (); + + running_setuid = uidget (); + + if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) + posixly_correct = 1; + +#if defined (USE_GNU_MALLOC_LIBRARY) + mcheck (programming_error, (void (*) ())0); +#endif /* USE_GNU_MALLOC_LIBRARY */ + + if (setjmp_sigs (subshell_top_level)) + { + argc = subshell_argc; + argv = subshell_argv; + env = subshell_envp; + sourced_env = 0; + } + + shell_reinitialized = 0; + + /* Initialize `local' variables for all `invocations' of main (). */ + arg_index = 1; + if (arg_index > argc) + arg_index = argc; + command_execution_string = (char *)NULL; + want_pending_command = locally_skip_execution = read_from_stdin = 0; + default_input = stdin; +#if defined (BUFFERED_INPUT) + default_buffered_input = -1; +#endif + + /* Fix for the `infinite process creation' bug when running shell scripts + from startup files on System V. */ + login_shell = make_login_shell = 0; + + /* If this shell has already been run, then reinitialize it to a + vanilla state. */ + if (shell_initialized || shell_name) + { + /* Make sure that we do not infinitely recurse as a login shell. */ + if (*shell_name == '-') + shell_name++; + + shell_reinitialize (); + if (setjmp_nosigs (top_level)) + exit (2); + } + + shell_environment = env; + set_shell_name (argv[0]); + shell_start_time = NOW; /* NOW now defined in general.h */ + + /* Parse argument flags from the input line. */ + + /* Find full word arguments first. */ + arg_index = parse_long_options (argv, arg_index, argc); + + if (want_initial_help) + { + show_shell_usage (stdout, 1); + exit (EXECUTION_SUCCESS); + } + + if (do_version) + { + show_shell_version (1); + exit (EXECUTION_SUCCESS); + } + + /* All done with full word options; do standard shell option parsing.*/ + this_command_name = shell_name; /* for error reporting */ + arg_index = parse_shell_options (argv, arg_index, argc); + + /* If user supplied the "--login" (or -l) flag, then set and invert + LOGIN_SHELL. */ + if (make_login_shell) + { + login_shell++; + login_shell = -login_shell; + } + + set_login_shell ("login_shell", login_shell != 0); + + if (dump_po_strings) + dump_translatable_strings = 1; + + if (dump_translatable_strings) + read_but_dont_execute = 1; + + if (running_setuid && privileged_mode == 0) + disable_priv_mode (); + + /* Need to get the argument to a -c option processed in the + above loop. The next arg is a command to execute, and the + following args are $0...$n respectively. */ + if (want_pending_command) + { + command_execution_string = argv[arg_index]; + if (command_execution_string == 0) + { + report_error (_("%s: option requires an argument"), "-c"); + exit (EX_BADUSAGE); + } + arg_index++; + } + this_command_name = (char *)NULL; + + cmd_init(); /* initialize the command object caches */ + + /* First, let the outside world know about our interactive status. + A shell is interactive if the `-i' flag was given, or if all of + the following conditions are met: + no -c command + no arguments remaining or the -s flag given + standard input is a terminal + standard error is a terminal + Refer to Posix.2, the description of the `sh' utility. */ + + if (forced_interactive || /* -i flag */ + (!command_execution_string && /* No -c command and ... */ + wordexp_only == 0 && /* No --wordexp and ... */ + ((arg_index == argc) || /* no remaining args or... */ + read_from_stdin) && /* -s flag with args, and */ + isatty (fileno (stdin)) && /* Input is a terminal and */ + isatty (fileno (stderr)))) /* error output is a terminal. */ + init_interactive (); + else + init_noninteractive (); + + /* + * Some systems have the bad habit of starting login shells with lots of open + * file descriptors. For instance, most systems that have picked up the + * pre-4.0 Sun YP code leave a file descriptor open each time you call one + * of the getpw* functions, and it's set to be open across execs. That + * means one for login, one for xterm, one for shelltool, etc. There are + * also systems that open persistent FDs to other agents or files as part + * of process startup; these need to be set to be close-on-exec. + */ + if (login_shell && interactive_shell) + { + for (i = 3; i < 20; i++) + SET_CLOSE_ON_EXEC (i); + } + + /* If we're in a strict Posix.2 mode, turn on interactive comments, + alias expansion in non-interactive shells, and other Posix.2 things. */ + if (posixly_correct) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + } + + /* Now we run the shopt_alist and process the options. */ + if (shopt_alist) + run_shopt_alist (); + + /* From here on in, the shell must be a normal functioning shell. + Variables from the environment are expected to be set, etc. */ + shell_initialize (); + + set_default_lang (); + set_default_locale_vars (); + + /* + * M-x term -> TERM=eterm EMACS=22.1 (term:0.96) (eterm) + * M-x shell -> TERM=dumb EMACS=t (no line editing) + * M-x terminal -> TERM=emacs-em7955 EMACS= (line editing) + */ + if (interactive_shell) + { + char *term, *emacs; + + term = get_string_value ("TERM"); + emacs = get_string_value ("EMACS"); + + /* Not sure any emacs terminal emulator sets TERM=emacs any more */ + no_line_editing |= term && (STREQ (term, "emacs")); + no_line_editing |= emacs && emacs[0] == 't' && emacs[1] == '\0' && STREQ (term, "dumb"); + + /* running_under_emacs == 2 for `eterm' */ + running_under_emacs = (emacs != 0) || (term && STREQN (term, "emacs", 5)); + running_under_emacs += term && STREQN (term, "eterm", 5) && emacs && strstr (emacs, "term"); + + if (running_under_emacs) + gnu_error_format = 1; + } + + top_level_arg_index = arg_index; + old_errexit_flag = exit_immediately_on_error; + + /* Give this shell a place to longjmp to before executing the + startup files. This allows users to press C-c to abort the + lengthy startup. */ + code = setjmp_sigs (top_level); + if (code) + { + if (code == EXITPROG || code == ERREXIT) + exit_shell (last_command_exit_value); + else + { +#if defined (JOB_CONTROL) + /* Reset job control, since run_startup_files turned it off. */ + set_job_control (interactive_shell); +#endif + /* Reset value of `set -e', since it's turned off before running + the startup files. */ + exit_immediately_on_error += old_errexit_flag; + locally_skip_execution++; + } + } + + arg_index = top_level_arg_index; + + /* Execute the start-up scripts. */ + + if (interactive_shell == 0) + { + unbind_variable ("PS1"); + unbind_variable ("PS2"); + interactive = 0; +#if 0 + /* This has already been done by init_noninteractive */ + expand_aliases = posixly_correct; +#endif + } + else + { + change_flag ('i', FLAG_ON); + interactive = 1; + } + +#if defined (RESTRICTED_SHELL) + /* Set restricted_shell based on whether the basename of $0 indicates that + the shell should be restricted or if the `-r' option was supplied at + startup. */ + restricted_shell = shell_is_restricted (shell_name); + + /* If the `-r' option is supplied at invocation, make sure that the shell + is not in restricted mode when running the startup files. */ + saverst = restricted; + restricted = 0; +#endif + + /* The startup files are run with `set -e' temporarily disabled. */ + if (locally_skip_execution == 0 && running_setuid == 0) + { + old_errexit_flag = exit_immediately_on_error; + exit_immediately_on_error = 0; + + run_startup_files (); + exit_immediately_on_error += old_errexit_flag; + } + + /* If we are invoked as `sh', turn on Posix mode. */ + if (act_like_sh) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + } + +#if defined (RESTRICTED_SHELL) + /* Turn on the restrictions after executing the startup files. This + means that `bash -r' or `set -r' invoked from a startup file will + turn on the restrictions after the startup files are executed. */ + restricted = saverst || restricted; + if (shell_reinitialized == 0) + maybe_make_restricted (shell_name); +#endif /* RESTRICTED_SHELL */ + +#if defined (WORDEXP_OPTION) + if (wordexp_only) + { + startup_state = 3; + last_command_exit_value = run_wordexp (argv[arg_index]); + exit_shell (last_command_exit_value); + } +#endif + + if (command_execution_string) + { + arg_index = bind_args (argv, arg_index, argc, 0); + startup_state = 2; + + if (debugging_mode) + start_debugger (); + +#if defined (ONESHOT) + executing = 1; + run_one_command (command_execution_string); + exit_shell (last_command_exit_value); +#else /* ONESHOT */ + with_input_from_string (command_execution_string, "-c"); + goto read_and_execute; +#endif /* !ONESHOT */ + } + + /* Get possible input filename and set up default_buffered_input or + default_input as appropriate. */ + if (arg_index != argc && read_from_stdin == 0) + { + open_shell_script (argv[arg_index]); + arg_index++; + } + else if (interactive == 0) + /* In this mode, bash is reading a script from stdin, which is a + pipe or redirected file. */ +#if defined (BUFFERED_INPUT) + default_buffered_input = fileno (stdin); /* == 0 */ +#else + setbuf (default_input, (char *)NULL); +#endif /* !BUFFERED_INPUT */ + + set_bash_input (); + + /* Bind remaining args to $1 ... $n */ + arg_index = bind_args (argv, arg_index, argc, 1); + + if (debugging_mode && locally_skip_execution == 0 && running_setuid == 0 && (dollar_vars[1] || interactive_shell == 0)) + start_debugger (); + + /* Do the things that should be done only for interactive shells. */ + if (interactive_shell) + { + /* Set up for checking for presence of mail. */ + reset_mail_timer (); + init_mail_dates (); + +#if defined (HISTORY) + /* Initialize the interactive history stuff. */ + bash_initialize_history (); + /* Don't load the history from the history file if we've already + saved some lines in this session (e.g., by putting `history -s xx' + into one of the startup files). */ + if (shell_initialized == 0 && history_lines_this_session == 0) + load_history (); +#endif /* HISTORY */ + + /* Initialize terminal state for interactive shells after the + .bash_profile and .bashrc are interpreted. */ + get_tty_state (); + } + +#if !defined (ONESHOT) + read_and_execute: +#endif /* !ONESHOT */ + + shell_initialized = 1; + + /* Read commands until exit condition. */ + reader_loop (); + exit_shell (last_command_exit_value); +} + +static int +parse_long_options (argv, arg_start, arg_end) + char **argv; + int arg_start, arg_end; +{ + int arg_index, longarg, i; + char *arg_string; + + arg_index = arg_start; + while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && + (*arg_string == '-')) + { + longarg = 0; + + /* Make --login equivalent to -login. */ + if (arg_string[1] == '-' && arg_string[2]) + { + longarg = 1; + arg_string++; + } + + for (i = 0; long_args[i].name; i++) + { + if (STREQ (arg_string + 1, long_args[i].name)) + { + if (long_args[i].type == Int) + *long_args[i].int_value = 1; + else if (argv[++arg_index] == 0) + { + report_error (_("%s: option requires an argument"), long_args[i].name); + exit (EX_BADUSAGE); + } + else + *long_args[i].char_value = argv[arg_index]; + + break; + } + } + if (long_args[i].name == 0) + { + if (longarg) + { + report_error (_("%s: invalid option"), argv[arg_index]); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + break; /* No such argument. Maybe flag arg. */ + } + + arg_index++; + } + + return (arg_index); +} + +static int +parse_shell_options (argv, arg_start, arg_end) + char **argv; + int arg_start, arg_end; +{ + int arg_index; + int arg_character, on_or_off, next_arg, i; + char *o_option, *arg_string; + + arg_index = arg_start; + while (arg_index != arg_end && (arg_string = argv[arg_index]) && + (*arg_string == '-' || *arg_string == '+')) + { + /* There are flag arguments, so parse them. */ + next_arg = arg_index + 1; + + /* A single `-' signals the end of options. From the 4.3 BSD sh. + An option `--' means the same thing; this is the standard + getopt(3) meaning. */ + if (arg_string[0] == '-' && + (arg_string[1] == '\0' || + (arg_string[1] == '-' && arg_string[2] == '\0'))) + return (next_arg); + + i = 1; + on_or_off = arg_string[0]; + while (arg_character = arg_string[i++]) + { + switch (arg_character) + { + case 'c': + want_pending_command = 1; + break; + + case 'l': + make_login_shell = 1; + break; + + case 's': + read_from_stdin = 1; + break; + + case 'o': + o_option = argv[next_arg]; + if (o_option == 0) + { + list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); + break; + } + if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + next_arg++; + break; + + case 'O': + /* Since some of these can be overridden by the normal + interactive/non-interactive shell initialization or + initializing posix mode, we save the options and process + them after initialization. */ + o_option = argv[next_arg]; + if (o_option == 0) + { + shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); + break; + } + add_shopt_to_alist (o_option, on_or_off); + next_arg++; + break; + + case 'D': + dump_translatable_strings = 1; + break; + + default: + if (change_flag (arg_character, on_or_off) == FLAG_ERROR) + { + report_error (_("%c%c: invalid option"), on_or_off, arg_character); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + } + } + /* Can't do just a simple increment anymore -- what about + "bash -abouo emacs ignoreeof -hP"? */ + arg_index = next_arg; + } + + return (arg_index); +} + +/* Exit the shell with status S. */ +void +exit_shell (s) + int s; +{ + fflush (stdout); /* XXX */ + fflush (stderr); + + /* Clean up the terminal if we are in a state where it's been modified. */ +#if defined (READLINE) + if (RL_ISSTATE (RL_STATE_TERMPREPPED) && rl_deprep_term_function) + (*rl_deprep_term_function) (); +#endif + if (read_tty_modified ()) + read_tty_cleanup (); + + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ + if (signal_is_trapped (0)) + s = run_exit_trap (); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (HISTORY) + if (remember_on_history) + maybe_save_shell_history (); +#endif /* HISTORY */ + +#if defined (COPROCESS_SUPPORT) + coproc_flush (); +#endif + +#if defined (JOB_CONTROL) + /* If the user has run `shopt -s huponexit', hangup all jobs when we exit + an interactive login shell. ksh does this unconditionally. */ + if (interactive_shell && login_shell && hup_on_exit) + hangup_all_jobs (); + + /* If this shell is interactive, or job control is active, terminate all + stopped jobs and restore the original terminal process group. Don't do + this if we're in a subshell and calling exit_shell after, for example, + a failed word expansion. We want to do this even if the shell is not + interactive because we set the terminal's process group when job control + is enabled regardless of the interactive status. */ + if (subshell_environment == 0) + end_job_control (); +#endif /* JOB_CONTROL */ + + /* Always return the exit status of the last command to our parent. */ + sh_exit (s); +} + +/* A wrapper for exit that (optionally) can do other things, like malloc + statistics tracing. */ +void +sh_exit (s) + int s; +{ +#if defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) + if (malloc_trace_at_exit) + trace_malloc_stats (get_name_for_error (), (char *)NULL); +#endif + + exit (s); +} + +/* Exit a subshell, which includes calling the exit trap. We don't want to + do any more cleanup, since a subshell is created as an exact copy of its + parent. */ +void +subshell_exit (s) + int s; +{ + fflush (stdout); + fflush (stderr); + + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ + if (signal_is_trapped (0)) + s = run_exit_trap (); + + sh_exit (s); +} + +/* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey + the Posix.2 startup file rules: $ENV is expanded, and if the file it + names exists, that file is sourced. The Posix.2 rules are in effect + for interactive shells only. (section 4.56.5.3) */ + +/* Execute ~/.bashrc for most shells. Never execute it if + ACT_LIKE_SH is set, or if NO_RC is set. + + If the executable file "/usr/gnu/src/bash/foo" contains: + + #!/usr/gnu/bin/bash + echo hello + + then: + + COMMAND EXECUTE BASHRC + -------------------------------- + bash -c foo NO + bash foo NO + foo NO + rsh machine ls YES (for rsh, which calls `bash -c') + rsh machine foo YES (for shell started by rsh) NO (for foo!) + echo ls | bash NO + login NO + bash YES +*/ + +static void +execute_env_file (env_file) + char *env_file; +{ + char *fn; + + if (env_file && *env_file) + { + fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); + if (fn && *fn) + maybe_execute_file (fn, 1); + FREE (fn); + } +} + +static void +run_startup_files () +{ +#if defined (JOB_CONTROL) + int old_job_control; +#endif + int sourced_login, run_by_ssh; + + /* get the rshd/sshd case out of the way first. */ + if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && + act_like_sh == 0 && command_execution_string) + { +#ifdef SSH_SOURCE_BASHRC + run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || + (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); +#else + run_by_ssh = 0; +#endif + + /* If we were run by sshd or we think we were run by rshd, execute + ~/.bashrc if we are a top-level shell. */ + if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) + { +#ifdef SYS_BASHRC +# if defined (__OPENNT) + maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); +# else + maybe_execute_file (SYS_BASHRC, 1); +# endif +#endif + maybe_execute_file (bashrc_file, 1); + return; + } + } + +#if defined (JOB_CONTROL) + /* Startup files should be run without job control enabled. */ + old_job_control = interactive_shell ? set_job_control (0) : 0; +#endif + + sourced_login = 0; + + /* A shell begun with the --login (or -l) flag that is not in posix mode + runs the login shell startup files, no matter whether or not it is + interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the + startup files if argv[0][0] == '-' as well. */ +#if defined (NON_INTERACTIVE_LOGIN_SHELLS) + if (login_shell && posixly_correct == 0) +#else + if (login_shell < 0 && posixly_correct == 0) +#endif + { + /* We don't execute .bashrc for login shells. */ + no_rc++; + + /* Execute /etc/profile and one of the personal login shell + initialization files. */ + if (no_profile == 0) + { + maybe_execute_file (SYS_PROFILE, 1); + + if (act_like_sh) /* sh */ + maybe_execute_file ("~/.profile", 1); + else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && + (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ + maybe_execute_file ("~/.profile", 1); + } + + sourced_login = 1; + } + + /* A non-interactive shell not named `sh' and not in posix mode reads and + executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' + and `-su' as the name of the shell, we want to read the startup files. + No other non-interactive shells read any startup files. */ + if (interactive_shell == 0 && !(su_shell && login_shell)) + { + if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && + sourced_env++ == 0) + execute_env_file (get_string_value ("BASH_ENV")); + return; + } + + /* Interactive shell or `-su' shell. */ + if (posixly_correct == 0) /* bash, sh */ + { + if (login_shell && sourced_login++ == 0) + { + /* We don't execute .bashrc for login shells. */ + no_rc++; + + /* Execute /etc/profile and one of the personal login shell + initialization files. */ + if (no_profile == 0) + { + maybe_execute_file (SYS_PROFILE, 1); + + if (act_like_sh) /* sh */ + maybe_execute_file ("~/.profile", 1); + else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && + (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ + maybe_execute_file ("~/.profile", 1); + } + } + + /* bash */ + if (act_like_sh == 0 && no_rc == 0) + { +#ifdef SYS_BASHRC +# if defined (__OPENNT) + maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); +# else + maybe_execute_file (SYS_BASHRC, 1); +# endif +#endif + maybe_execute_file (bashrc_file, 1); + } + /* sh */ + else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) + execute_env_file (get_string_value ("ENV")); + } + else /* bash --posix, sh --posix */ + { + /* bash and sh */ + if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) + execute_env_file (get_string_value ("ENV")); + } + +#if defined (JOB_CONTROL) + set_job_control (old_job_control); +#endif +} + +#if defined (RESTRICTED_SHELL) +/* Return 1 if the shell should be a restricted one based on NAME or the + value of `restricted'. Don't actually do anything, just return a + boolean value. */ +int +shell_is_restricted (name) + char *name; +{ + char *temp; + + if (restricted) + return 1; + temp = base_pathname (name); + if (*temp == '-') + temp++; + return (STREQ (temp, RESTRICTED_SHELL_NAME)); +} + +/* Perhaps make this shell a `restricted' one, based on NAME. If the + basename of NAME is "rbash", then this shell is restricted. The + name of the restricted shell is a configurable option, see config.h. + In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only + and non-unsettable. + Do this also if `restricted' is already set to 1; maybe the shell was + started with -r. */ +int +maybe_make_restricted (name) + char *name; +{ + char *temp; + + temp = base_pathname (name); + if (*temp == '-') + temp++; + if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) + { + set_var_read_only ("PATH"); + set_var_read_only ("SHELL"); + set_var_read_only ("ENV"); + set_var_read_only ("BASH_ENV"); + restricted = 1; + } + return (restricted); +} +#endif /* RESTRICTED_SHELL */ + +/* Fetch the current set of uids and gids and return 1 if we're running + setuid or setgid. */ +static int +uidget () +{ + uid_t u; + + u = getuid (); + if (current_user.uid != u) + { + FREE (current_user.user_name); + FREE (current_user.shell); + FREE (current_user.home_dir); + current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; + } + current_user.uid = u; + current_user.gid = getgid (); + current_user.euid = geteuid (); + current_user.egid = getegid (); + + /* See whether or not we are running setuid or setgid. */ + return (current_user.uid != current_user.euid) || + (current_user.gid != current_user.egid); +} + +void +disable_priv_mode () +{ + int e; + + if (setuid (current_user.uid) < 0) + { + e = errno; + sys_error (_("cannot set uid to %d: effective uid %d"), current_user.uid, current_user.euid); +#if defined (EXIT_ON_SETUID_FAILURE) + if (e == EAGAIN) + exit (e); +#endif + } + if (setgid (current_user.gid) < 0) + sys_error (_("cannot set gid to %d: effective gid %d"), current_user.gid, current_user.egid); + + current_user.euid = current_user.uid; + current_user.egid = current_user.gid; +} + +#if defined (WORDEXP_OPTION) +static int +run_wordexp (words) + char *words; +{ + int code, nw, nb; + WORD_LIST *wl, *tl, *result; + + code = setjmp_nosigs (top_level); + + if (code != NOT_JUMPED) + { + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case FORCE_EOF: + return last_command_exit_value = 127; + case ERREXIT: + case EXITPROG: + return last_command_exit_value; + case DISCARD: + return last_command_exit_value = 1; + default: + command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); + } + } + + /* Run it through the parser to get a list of words and expand them */ + if (words && *words) + { + with_input_from_string (words, "--wordexp"); + if (parse_command () != 0) + return (126); + if (global_command == 0) + { + printf ("0\n0\n"); + return (0); + } + if (global_command->type != cm_simple) + return (126); + wl = global_command->value.Simple->words; + if (protected_mode) + for (tl = wl; tl; tl = tl->next) + tl->word->flags |= W_NOCOMSUB|W_NOPROCSUB; + result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; + } + else + result = (WORD_LIST *)0; + + last_command_exit_value = 0; + + if (result == 0) + { + printf ("0\n0\n"); + return (0); + } + + /* Count up the number of words and bytes, and print them. Don't count + the trailing NUL byte. */ + for (nw = nb = 0, wl = result; wl; wl = wl->next) + { + nw++; + nb += strlen (wl->word->word); + } + printf ("%u\n%u\n", nw, nb); + /* Print each word on a separate line. This will have to be changed when + the interface to glibc is completed. */ + for (wl = result; wl; wl = wl->next) + printf ("%s\n", wl->word->word); + + return (0); +} +#endif + +#if defined (ONESHOT) +/* Run one command, given as the argument to the -c option. Tell + parse_and_execute not to fork for a simple command. */ +static int +run_one_command (command) + char *command; +{ + int code; + + code = setjmp_nosigs (top_level); + + if (code != NOT_JUMPED) + { +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case FORCE_EOF: + return last_command_exit_value = 127; + case ERREXIT: + case EXITPROG: + return last_command_exit_value; + case DISCARD: + return last_command_exit_value = 1; + default: + command_error ("run_one_command", CMDERR_BADJUMP, code, 0); + } + } + return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST)); +} +#endif /* ONESHOT */ + +static int +bind_args (argv, arg_start, arg_end, start_index) + char **argv; + int arg_start, arg_end, start_index; +{ + register int i; + WORD_LIST *args; + + for (i = arg_start, args = (WORD_LIST *)NULL; i < arg_end; i++) + args = make_word_list (make_word (argv[i]), args); + if (args) + { + args = REVERSE_LIST (args, WORD_LIST *); + if (start_index == 0) /* bind to $0...$n for sh -c command */ + { + /* Posix.2 4.56.3 says that the first argument after sh -c command + becomes $0, and the rest of the arguments become $1...$n */ + shell_name = savestring (args->word->word); + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (args->word->word); + remember_args (args->next, 1); + push_args (args->next); /* BASH_ARGV and BASH_ARGC */ + } + else /* bind to $1...$n for shell script */ + { + remember_args (args, 1); + push_args (args); /* BASH_ARGV and BASH_ARGC */ + } + + dispose_words (args); + } + + return (i); +} + +void +unbind_args () +{ + remember_args ((WORD_LIST *)NULL, 1); + pop_args (); /* Reset BASH_ARGV and BASH_ARGC */ +} + +static void +start_debugger () +{ +#if defined (DEBUGGER) && defined (DEBUGGER_START_FILE) + int old_errexit; + int r; + + old_errexit = exit_immediately_on_error; + exit_immediately_on_error = 0; + + r = force_execute_file (DEBUGGER_START_FILE, 1); + if (r < 0) + { + internal_warning ("cannot start debugger; debugging mode disabled"); + debugging_mode = function_trace_mode = 0; + } + else + function_trace_mode = 1; + + exit_immediately_on_error += old_errexit; +#endif +} + +static int +open_shell_script (script_name) + char *script_name; +{ + int fd, e, fd_is_tty; + char *filename, *path_filename, *t; + char sample[80]; + int sample_len; + struct stat sb; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; +#endif + + filename = savestring (script_name); + + fd = open (filename, O_RDONLY); + if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) + { + e = errno; + /* If it's not in the current directory, try looking through PATH + for it. */ + path_filename = find_path_file (script_name); + if (path_filename) + { + free (filename); + filename = path_filename; + fd = open (filename, O_RDONLY); + } + else + errno = e; + } + + if (fd < 0) + { + e = errno; + file_error (filename); + exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); + } + + free (dollar_vars[0]); + dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (script_name); + if (exec_argv0) + { + free (exec_argv0); + exec_argv0 = (char *)NULL; + } + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); + + array_push (bash_source_a, filename); + if (bash_lineno_a) + { + t = itos (executing_line_number ()); + array_push (bash_lineno_a, t); + free (t); + } + array_push (funcname_a, "main"); +#endif + +#ifdef HAVE_DEV_FD + fd_is_tty = isatty (fd); +#else + fd_is_tty = 0; +#endif + + /* Only do this with non-tty file descriptors we can seek on. */ + if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) + { + /* Check to see if the `file' in `bash file' is a binary file + according to the same tests done by execute_simple_command (), + and report an error and exit if it is. */ + sample_len = read (fd, sample, sizeof (sample)); + if (sample_len < 0) + { + e = errno; + if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) + internal_error (_("%s: is a directory"), filename); + else + { + errno = e; + file_error (filename); + } + exit (EX_NOEXEC); + } + else if (sample_len > 0 && (check_binary_file (sample, sample_len))) + { + internal_error (_("%s: cannot execute binary file"), filename); + exit (EX_BINARY_FILE); + } + /* Now rewind the file back to the beginning. */ + lseek (fd, 0L, 0); + } + + /* Open the script. But try to move the file descriptor to a randomly + large one, in the hopes that any descriptors used by the script will + not match with ours. */ + fd = move_to_high_fd (fd, 1, -1); + +#if defined (BUFFERED_INPUT) + default_buffered_input = fd; + SET_CLOSE_ON_EXEC (default_buffered_input); +#else /* !BUFFERED_INPUT */ + default_input = fdopen (fd, "r"); + + if (default_input == 0) + { + file_error (filename); + exit (EX_NOTFOUND); + } + + SET_CLOSE_ON_EXEC (fd); + if (fileno (default_input) != fd) + SET_CLOSE_ON_EXEC (fileno (default_input)); +#endif /* !BUFFERED_INPUT */ + + /* Just about the only way for this code to be executed is if something + like `bash -i /dev/stdin' is executed. */ + if (interactive_shell && fd_is_tty) + { + dup2 (fd, 0); + close (fd); + fd = 0; +#if defined (BUFFERED_INPUT) + default_buffered_input = 0; +#else + fclose (default_input); + default_input = stdin; +#endif + } + else if (forced_interactive && fd_is_tty == 0) + /* But if a script is called with something like `bash -i scriptname', + we need to do a non-interactive setup here, since we didn't do it + before. */ + init_interactive_script (); + + free (filename); + return (fd); +} + +/* Initialize the input routines for the parser. */ +static void +set_bash_input () +{ + /* Make sure the fd from which we are reading input is not in + no-delay mode. */ +#if defined (BUFFERED_INPUT) + if (interactive == 0) + sh_unset_nodelay_mode (default_buffered_input); + else +#endif /* !BUFFERED_INPUT */ + sh_unset_nodelay_mode (fileno (stdin)); + + /* with_input_from_stdin really means `with_input_from_readline' */ + if (interactive && no_line_editing == 0) + with_input_from_stdin (); +#if defined (BUFFERED_INPUT) + else if (interactive == 0) + with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); +#endif /* BUFFERED_INPUT */ + else + with_input_from_stream (default_input, dollar_vars[0]); +} + +/* Close the current shell script input source and forget about it. This is + extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO + is non-zero, we close default_buffered_input even if it's the standard + input (fd 0). */ +void +unset_bash_input (check_zero) + int check_zero; +{ +#if defined (BUFFERED_INPUT) + if ((check_zero && default_buffered_input >= 0) || + (check_zero == 0 && default_buffered_input > 0)) + { + close_buffered_fd (default_buffered_input); + default_buffered_input = bash_input.location.buffered_fd = -1; + bash_input.type = st_none; /* XXX */ + } +#else /* !BUFFERED_INPUT */ + if (default_input) + { + fclose (default_input); + default_input = (FILE *)NULL; + } +#endif /* !BUFFERED_INPUT */ +} + + +#if !defined (PROGRAM) +# define PROGRAM "bash" +#endif + +static void +set_shell_name (argv0) + char *argv0; +{ + /* Here's a hack. If the name of this shell is "sh", then don't do + any startup files; just try to be more like /bin/sh. */ + shell_name = argv0 ? base_pathname (argv0) : PROGRAM; + + if (argv0 && *argv0 == '-') + { + if (*shell_name == '-') + shell_name++; + login_shell = 1; + } + + if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') + act_like_sh++; + if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') + su_shell++; + + shell_name = argv0 ? argv0 : PROGRAM; + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (shell_name); + + /* A program may start an interactive shell with + "execl ("/bin/bash", "-", NULL)". + If so, default the name of this shell to our name. */ + if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) + shell_name = PROGRAM; +} + +static void +init_interactive () +{ + expand_aliases = interactive_shell = startup_state = 1; + interactive = 1; +} + +static void +init_noninteractive () +{ +#if defined (HISTORY) + bash_history_reinit (0); +#endif /* HISTORY */ + interactive_shell = startup_state = interactive = 0; + expand_aliases = posixly_correct; /* XXX - was 0 not posixly_correct */ + no_line_editing = 1; +#if defined (JOB_CONTROL) + /* Even if the shell is not interactive, enable job control if the -i or + -m option is supplied at startup. */ + set_job_control (forced_interactive||jobs_m_flag); +#endif /* JOB_CONTROL */ +} + +static void +init_interactive_script () +{ + init_noninteractive (); + expand_aliases = interactive_shell = startup_state = 1; +} + +void +get_current_user_info () +{ + struct passwd *entry; + + /* Don't fetch this more than once. */ + if (current_user.user_name == 0) + { +#if defined (__TANDEM) + entry = getpwnam (getlogin ()); +#else + entry = getpwuid (current_user.uid); +#endif + if (entry) + { + current_user.user_name = savestring (entry->pw_name); + current_user.shell = (entry->pw_shell && entry->pw_shell[0]) + ? savestring (entry->pw_shell) + : savestring ("/bin/sh"); + current_user.home_dir = savestring (entry->pw_dir); + } + else + { + current_user.user_name = _("I have no name!"); + current_user.user_name = savestring (current_user.user_name); + current_user.shell = savestring ("/bin/sh"); + current_user.home_dir = savestring ("/"); + } + endpwent (); + } +} + +/* Do whatever is necessary to initialize the shell. + Put new initializations in here. */ +static void +shell_initialize () +{ + char hostname[256]; + int should_be_restricted; + + /* Line buffer output for stderr and stdout. */ + if (shell_initialized == 0) + { + sh_setlinebuf (stderr); + sh_setlinebuf (stdout); + } + + /* Sort the array of shell builtins so that the binary search in + find_shell_builtin () works correctly. */ + initialize_shell_builtins (); + + /* Initialize the trap signal handlers before installing our own + signal handlers. traps.c:restore_original_signals () is responsible + for restoring the original default signal handlers. That function + is called when we make a new child. */ + initialize_traps (); + initialize_signals (0); + + /* It's highly unlikely that this will change. */ + if (current_host_name == 0) + { + /* Initialize current_host_name. */ + if (gethostname (hostname, 255) < 0) + current_host_name = "??host??"; + else + current_host_name = savestring (hostname); + } + + /* Initialize the stuff in current_user that comes from the password + file. We don't need to do this right away if the shell is not + interactive. */ + if (interactive_shell) + get_current_user_info (); + + /* Initialize our interface to the tilde expander. */ + tilde_initialize (); + +#if defined (RESTRICTED_SHELL) + should_be_restricted = shell_is_restricted (shell_name); +#endif + + /* Initialize internal and environment variables. Don't import shell + functions from the environment if we are running in privileged or + restricted mode or if the shell is running setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_variables (shell_environment, privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_variables (shell_environment, privileged_mode||running_setuid); +#endif + + /* Initialize the data structures for storing and running jobs. */ + initialize_job_control (jobs_m_flag); + + /* Initialize input streams to null. */ + initialize_bash_input (); + + initialize_flags (); + + /* Initialize the shell options. Don't import the shell options + from the environment variables $SHELLOPTS or $BASHOPTS if we are + running in privileged or restricted mode or if the shell is running + setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_options (privileged_mode||restricted||should_be_restricted||running_setuid); + initialize_bashopts (privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_options (privileged_mode||running_setuid); + initialize_bashopts (privileged_mode||running_setuid); +#endif +} + +/* Function called by main () when it appears that the shell has already + had some initialization performed. This is supposed to reset the world + back to a pristine state, as if we had been exec'ed. */ +static void +shell_reinitialize () +{ + /* The default shell prompts. */ + primary_prompt = PPROMPT; + secondary_prompt = SPROMPT; + + /* Things that get 1. */ + current_command_number = 1; + + /* We have decided that the ~/.bashrc file should not be executed + for the invocation of each shell script. If the variable $ENV + (or $BASH_ENV) is set, its value is used as the name of a file + to source. */ + no_rc = no_profile = 1; + + /* Things that get 0. */ + login_shell = make_login_shell = interactive = executing = 0; + debugging = do_version = line_number = last_command_exit_value = 0; + forced_interactive = interactive_shell = 0; + subshell_environment = running_in_background = 0; + expand_aliases = 0; + + /* XXX - should we set jobs_m_flag to 0 here? */ + +#if defined (HISTORY) + bash_history_reinit (0); +#endif /* HISTORY */ + +#if defined (RESTRICTED_SHELL) + restricted = 0; +#endif /* RESTRICTED_SHELL */ + + /* Ensure that the default startup file is used. (Except that we don't + execute this file for reinitialized shells). */ + bashrc_file = DEFAULT_BASHRC; + + /* Delete all variables and functions. They will be reinitialized when + the environment is parsed. */ + delete_all_contexts (shell_variables); + delete_all_variables (shell_functions); + + reinit_special_variables (); + +#if defined (READLINE) + bashline_reinitialize (); +#endif + + shell_reinitialized = 1; +} + +static void +show_shell_usage (fp, extra) + FILE *fp; + int extra; +{ + int i; + char *set_opts, *s, *t; + + if (extra) + fprintf (fp, _("GNU bash, version %s-(%s)\n"), shell_version_string (), MACHTYPE); + fprintf (fp, _("Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n"), + shell_name, shell_name); + fputs (_("GNU long options:\n"), fp); + for (i = 0; long_args[i].name; i++) + fprintf (fp, "\t--%s\n", long_args[i].name); + + fputs (_("Shell options:\n"), fp); + fputs (_("\t-ilrsD or -c command or -O shopt_option\t\t(invocation only)\n"), fp); + + for (i = 0, set_opts = 0; shell_builtins[i].name; i++) + if (STREQ (shell_builtins[i].name, "set")) + set_opts = savestring (shell_builtins[i].short_doc); + if (set_opts) + { + s = strchr (set_opts, '['); + if (s == 0) + s = set_opts; + while (*++s == '-') + ; + t = strchr (s, ']'); + if (t) + *t = '\0'; + fprintf (fp, _("\t-%s or -o option\n"), s); + free (set_opts); + } + + if (extra) + { + fprintf (fp, _("Type `%s -c \"help set\"' for more information about shell options.\n"), shell_name); + fprintf (fp, _("Type `%s -c help' for more information about shell builtin commands.\n"), shell_name); + fprintf (fp, _("Use the `bashbug' command to report bugs.\n")); + } +} + +static void +add_shopt_to_alist (opt, on_or_off) + char *opt; + int on_or_off; +{ + if (shopt_ind >= shopt_len) + { + shopt_len += 8; + shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); + } + shopt_alist[shopt_ind].word = opt; + shopt_alist[shopt_ind].token = on_or_off; + shopt_ind++; +} + +static void +run_shopt_alist () +{ + register int i; + + for (i = 0; i < shopt_ind; i++) + if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + free (shopt_alist); + shopt_alist = 0; + shopt_ind = shopt_len = 0; +} @@ -52,6 +52,9 @@ #include "mailcheck.h" #include "shmbutil.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include <mbstr.h> /* mbschr */ +#endif #include "typemax.h" #include "builtins/getopt.h" @@ -225,6 +225,7 @@ and () * '-'('G'|'L'|'O'|'S'|'N') filename * '-t' [int] * '-'('z'|'n') string + * '-'('v'|'R') varname * '-o' option * string * string ('!='|'='|'==') string diff --git a/tests/RUN-ONE-TEST b/tests/RUN-ONE-TEST index 3efcf32d..72ec06a2 100755 --- a/tests/RUN-ONE-TEST +++ b/tests/RUN-ONE-TEST @@ -1,4 +1,4 @@ -BUILD_DIR=/usr/local/build/chet/bash/bash-current +BUILD_DIR=/usr/local/build/bash/bash-current THIS_SH=$BUILD_DIR/bash PATH=$PATH:$BUILD_DIR diff --git a/tests/RUN-ONE-TEST~ b/tests/RUN-ONE-TEST~ new file mode 100755 index 00000000..3efcf32d --- /dev/null +++ b/tests/RUN-ONE-TEST~ @@ -0,0 +1,9 @@ +BUILD_DIR=/usr/local/build/chet/bash/bash-current +THIS_SH=$BUILD_DIR/bash +PATH=$PATH:$BUILD_DIR + +export THIS_SH PATH + +rm -f /tmp/xx + +/bin/sh "$@" diff --git a/tests/new-exp.right b/tests/new-exp.right index 0f9633d1..e0f64d53 100644 --- a/tests/new-exp.right +++ b/tests/new-exp.right @@ -593,7 +593,7 @@ c Sub = 0 2 4 8 <'ab cd'> <'4'> <'ab cd'> <> -argv[1] = <host(2)[4.3]$ > +argv[1] = <host(2)[4.4]$ > < > <' \t\n'> diff --git a/variables.c b/variables.c index 54b11671..2f07ebb9 100644 --- a/variables.c +++ b/variables.c @@ -5409,14 +5409,14 @@ sv_shcompat (name) return; } /* Handle decimal-like compatibility version specifications: 4.2 */ - if (isdigit (val[0]) && val[1] == '.' && isdigit (val[2]) && val[3] == 0) + if (ISDIGIT (val[0]) && val[1] == '.' && ISDIGIT (val[2]) && val[3] == 0) { tens = val[0] - '0'; ones = val[2] - '0'; compatval = tens*10 + ones; } /* Handle integer-like compatibility version specifications: 42 */ - else if (isdigit (val[0]) && isdigit (val[1]) && val[2] == 0) + else if (ISDIGIT (val[0]) && ISDIGIT (val[1]) && val[2] == 0) { tens = val[0] - '0'; ones = val[1] - '0'; diff --git a/variables.c~ b/variables.c~ new file mode 100644 index 00000000..54b11671 --- /dev/null +++ b/variables.c~ @@ -0,0 +1,5453 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* Copyright (C) 1987-2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (__QNX__) +# if defined (__QNXNTO__) +# include <sys/netmgr.h> +# else +# include <sys/vc.h> +# endif /* !__QNXNTO__ */ +#endif /* __QNX__ */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include <stdio.h> +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include <pwd.h> +#endif +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_XTRACE_SET_DECL + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" +#include "alias.h" +#include "jobs.h" + +#include "version.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" +#include "builtins/builtext.h" + +#if defined (READLINE) +# include "bashline.h" +# include <readline/readline.h> +#else +# include <tilde/tilde.h> +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include <readline/history.h> +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') + +#define BASHFUNC_PREFIX "BASH_FUNC_" +#define BASHFUNC_PREFLEN 10 /* == strlen(BASHFUNC_PREFIX */ +#define BASHFUNC_SUFFIX "%%" +#define BASHFUNC_SUFFLEN 2 /* == strlen(BASHFUNC_SUFFIX) */ + +/* flags for find_variable_internal */ + +#define FV_FORCETEMPENV 0x01 +#define FV_SKIPINVISIBLE 0x02 + +extern char **environ; + +/* Variables used here and defined in other files. */ +extern int posixly_correct; +extern int line_number, line_number_base; +extern int subshell_environment, indirection_level, subshell_level; +extern int build_version, patch_level; +extern int expanding_redir; +extern int last_command_exit_value; +extern char *dist_version, *release_status; +extern char *shell_name; +extern char *primary_prompt, *secondary_prompt; +extern char *current_host_name; +extern sh_builtin_func_t *this_shell_builtin; +extern SHELL_VAR *this_shell_function; +extern char *the_printed_command_except_trap; +extern char *this_command_name; +extern char *command_execution_string; +extern time_t shell_start_time; +extern int assigning_in_environment; +extern int executing_builtin; +extern int funcnest_max; + +#if defined (READLINE) +extern int no_line_editing; +extern int perform_hostname_completion; +#endif + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Set to non-zero if an assignment error occurs while putting variables + into the temporary environment. */ +int tempenv_assign_error; + +/* Some funky variables which are known about specially. Here is where + "$*", "$1", and all the cruft is kept. */ +char *dollar_vars[10]; +WORD_LIST *rest_of_args = (WORD_LIST *)NULL; + +/* The value of $$. */ +pid_t dollar_dollar_pid; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +#if defined (READLINE) +static int winsize_assignment; /* currently assigning to LINES or COLUMNS */ +#endif + +static HASH_TABLE *last_table_searched; /* hash_lookup sets this */ + +/* Some forward declarations. */ +static void create_variable_tables __P((void)); + +static void set_machine_vars __P((void)); +static void set_home_var __P((void)); +static void set_shell_var __P((void)); +static char *get_bash_name __P((void)); +static void initialize_shell_level __P((void)); +static void uidset __P((void)); +#if defined (ARRAY_VARS) +static void make_vers_array __P((void)); +#endif + +static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif +static SHELL_VAR *get_self __P((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_seconds __P((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var __P((void)); + +static int brand __P((void)); +static void sbrand __P((unsigned long)); /* set bash random number generator. */ +static void seedrand __P((void)); /* seed generator randomly */ +static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_random __P((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_lineno __P((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_subshell __P((SHELL_VAR *)); + +static SHELL_VAR *get_bashpid __P((SHELL_VAR *)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); +#endif + +#if defined (READLINE) +static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *)); +static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset __P((SHELL_VAR *)); + +static SHELL_VAR *build_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *get_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *, char *, arrayind_t, char *)); +# if defined (ALIAS) +static SHELL_VAR *build_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *get_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *, char *, arrayind_t, char *)); +# endif +#endif + +static SHELL_VAR *get_funcname __P((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var __P((void)); + +static void initialize_dynamic_variables __P((void)); + +static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable __P((const char *)); +static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int)); + +static void dispose_variable_value __P((SHELL_VAR *)); +static void free_variable_hash_data __P((PTR_T)); + +static VARLIST *vlist_alloc __P((int)); +static VARLIST *vlist_realloc __P((VARLIST *, int)); +static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); + +static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply __P((sh_var_map_func_t *)); +static SHELL_VAR **fapply __P((sh_var_map_func_t *)); + +static int visible_var __P((SHELL_VAR *)); +static int visible_and_exported __P((SHELL_VAR *)); +static int export_environment_candidate __P((SHELL_VAR *)); +static int local_and_exported __P((SHELL_VAR *)); +static int variable_in_context __P((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *find_nameref_at_context __P((SHELL_VAR *, VAR_CONTEXT *)); +static SHELL_VAR *find_variable_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); +static SHELL_VAR *find_variable_last_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); + +static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); +static void push_temp_var __P((PTR_T)); +static void propagate_temp_var __P((PTR_T)); +static void dispose_temporary_env __P((sh_free_func_t *)); + +static inline char *mk_env_string __P((const char *, const char *, int)); +static char **make_env_array_from_var_list __P((SHELL_VAR **)); +static char **make_var_export_array __P((VAR_CONTEXT *)); +static char **make_func_export_array __P((void)); +static void add_temp_array_to_env __P((char **, int, int)); + +static int n_shell_variables __P((void)); +static int set_context __P((SHELL_VAR *)); + +static void push_func_var __P((PTR_T)); +static void push_exported_var __P((PTR_T)); + +static inline int find_special_var __P((const char *)); + +static void +create_variable_tables () +{ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + if (shell_functions == 0) + shell_functions = hash_create (0); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (0); +#endif +} + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length, ro; + SHELL_VAR *temp_var; + + create_variable_tables (); + + for (string_index = 0; string = env[string_index++]; ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + temp_var = (SHELL_VAR *)NULL; + +#if defined (FUNCTION_IMPORT) + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && + STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) && + STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) && + STREQN ("() {", string, 4)) + { + size_t namelen; + char *tname; /* desired imported function name */ + + namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN; + + tname = name + BASHFUNC_PREFLEN; /* start of func name */ + tname[namelen] = '\0'; /* now tname == func name */ + + string_length = strlen (string); + temp_string = (char *)xmalloc (namelen + string_length + 2); + + memcpy (temp_string, tname, namelen); + temp_string[namelen] = ' '; + memcpy (temp_string + namelen + 1, string, string_length + 1); + + /* Don't import function names that are invalid identifiers from the + environment in posix mode, though we still allow them to be defined as + shell variables. */ + if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname))) + parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD); + else + free (temp_string); /* parse_and_execute does this */ + + if (temp_var = find_function (tname)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + { + if (temp_var = bind_variable (name, string, 0)) + { + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + array_needs_making = 1; + } + last_command_exit_value = 1; + report_error (_("error importing function definition for `%s'"), tname); + } + + /* Restore original suffix */ + tname[namelen] = BASHFUNC_SUFFIX[0]; + } +#endif /* FUNCTION_IMPORT */ +#if defined (ARRAY_VARS) +# if ARRAY_EXPORT + /* Array variables may not yet be exported. */ + else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')') + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (name, temp_string); + FREE (temp_string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } +# endif /* ARRAY_EXPORT */ +#endif + else + { + ro = 0; + if (posixly_correct && STREQ (name, "SHELLOPTS")) + { + temp_var = find_variable ("SHELLOPTS"); + ro = temp_var && readonly_p (temp_var); + if (temp_var) + VUNSETATTR (temp_var, att_readonly); + } + temp_var = bind_variable (name, string, 0); + if (temp_var) + { + if (legal_identifier (name)) + VSETATTR (temp_var, (att_exported | att_imported)); + else + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + if (ro) + VSETATTR (temp_var, att_readonly); + array_needs_making = 1; + } + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = set_if_not ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = getpid (); + + /* Now make our own defaults in case the vars that we think are + important are missing. */ + temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + + temp_var = set_if_not ("TERM", "dumb"); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + +#if defined (__QNX__) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; +# if defined (__QNXNTO__) + netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name)); +# else + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); +# endif + temp_var = bind_variable ("NODE", node_name, 0); + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n", 0); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + { + temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + VSETATTR (temp_var, att_integer); + } + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + /* Initialize the `getopts' stuff. */ + temp_var = bind_variable ("OPTIND", "1", 0); + VSETATTR (temp_var, att_integer); + getopts_reset (0); + bind_variable ("OPTERR", "1", 0); + sh_opterr = 1; + + if (login_shell == 1 && posixly_correct == 0) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name, 0); + free (name); + + /* Make the exported environment variable SHELL be the user's login + shell. Note that the `tset' command looks at this variable + to determine what style of commands to output; if it ends in "csh", + then C-shell commands are output, else Bourne shell commands. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string (), 0); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#if defined (HISTORY) + /* Set history variables to defaults, and then do whatever we would + do if the variable had just been set. Do this only in the case + that we are remembering commands on the history list. */ + if (remember_on_history) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + } +#endif /* HISTORY */ + + /* Seed the random number generator. */ + seedrand (); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + sv_histtimefmt ("HISTTIMEFORMAT"); + } +#endif /* HISTORY */ + +#if defined (READLINE) && defined (STRICT_POSIX) + /* POSIXLY_CORRECT will only be 1 here if the shell was compiled + -DSTRICT_POSIX */ + if (interactive_shell && posixly_correct && no_line_editing == 0) + rl_prefer_env_winsize = 1; +#endif /* READLINE && STRICT_POSIX */ + + /* + * 24 October 2001 + * + * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT + * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in + * isnetconn() to avoid running the startup files more often than wanted. + * That will, of course, only work if the user's login shell is bash, so + * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined + * in config-top.h. + */ +#if 0 + temp_var = find_variable ("SSH_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } + temp_var = find_variable ("SSH2_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } +#endif + + /* Get the user's real and effective user ids. */ + uidset (); + + temp_var = find_variable ("BASH_XTRACEFD"); + if (temp_var && imported_p (temp_var)) + sv_xtracefd (temp_var->name); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir (), 0); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell, 0); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level > 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level, 0); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (1); +} + +/* If we got PWD from the environment, update our idea of the current + working directory. In any case, make sure that PWD exists before + checking it. It is possible for getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + set_working_directory (temp_string); + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string, 0); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string, 0); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. + Make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = bind_variable ("OLDPWD", (char *)NULL, 0); + VSETATTR (temp_var, (att_exported | att_invisible)); +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name, 0); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = strchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + +#if defined (READLINE) + /* If we are currently assigning to LINES or COLUMNS, don't do anything. */ + if (winsize_assignment) + return; +#endif + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v, 0); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v, 0); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +print_var_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + if (invisible_p (var) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* Print the value of a single SHELL_VAR. No newline is + output, but the variable is printed in such a way that + it can be read back in. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); + else if (assoc_p (var)) + print_assoc_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + char *x; + + if (function_p (var) && var_isset (var)) + { + x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s", x); + } +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* DYNAMIC VARIABLES + + These are variables whose values are generated anew each time they are + referenced. These are implemented using a pair of function pointers + in the struct variable: assign_func, which is called from bind_variable + and, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal to return a `new' + value for the specified dynamic varible. If this function is NULL, + the variable is treated as a `normal' shell variable. If it is not, + however, then this function is called like this: + tempvar = (*(var->dynamic_value)) (var); + + Sometimes `tempvar' will replace the value of `var'. Other times, the + shell will simply use the string value. Pretty object-oriented, huh? + + Be warned, though: if you `unset' a special variable, it loses its + special meaning, even if you subsequently set it. + + The special assignment code would probably have been better put in + subst.c: do_assignment_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val), 0); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_assoc_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Initialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} + +static SHELL_VAR * +init_dynamic_assoc_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + +/* The value of $SECONDS. This is the number of seconds since shell + invocation, or, the number of seconds since the last assignment + the + value of the last assignment. */ +static intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (legal_number (value, &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + shell_start_time = NOW; + return (self); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + char *p; + + time_since_start = NOW - shell_start_time; + p = itos(seconds_value_assigned + time_since_start); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* The random number seed. You can change this by setting RANDOM. */ +static unsigned long rseed = 1; +static int last_random_value; +static int seeded_subshell = 0; + +/* A linear congruential random number generator based on the example + one in the ANSI C standard. This one isn't very good, but a more + complicated one is overkill. */ + +/* Returns a pseudo-random number between 0 and 32767. */ +static int +brand () +{ + /* From "Random number generators: good ones are hard to find", + Park and Miller, Communications of the ACM, vol. 31, no. 10, + October 1988, p. 1195. filtered through FreeBSD */ + long h, l; + + /* Can't seed with 0. */ + if (rseed == 0) + rseed = 123459876; + h = rseed / 127773; + l = rseed % 127773; + rseed = 16807 * l - 2836 * h; +#if 0 + if (rseed < 0) + rseed += 0x7fffffff; +#endif + return ((unsigned int)(rseed & 32767)); /* was % 32768 */ +} + +/* Set the random number generator seed to SEED. */ +static void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +static void +seedrand () +{ + struct timeval tv; + + gettimeofday (&tv, NULL); + sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ()); +} + +static SHELL_VAR * +assign_random (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + sbrand (strtoul (value, (char **)NULL, 10)); + if (subshell_environment) + seeded_subshell = getpid (); + return (self); +} + +int +get_random_number () +{ + int rv, pid; + + /* Reset for command and process substitution. */ + pid = getpid (); + if (subshell_environment && seeded_subshell != pid) + { + seedrand (); + seeded_subshell = pid; + } + + do + rv = brand (); + while (rv == last_random_value); + return rv; +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + char *p; + + rv = get_random_number (); + last_random_value = rv; + p = itos (rv); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_lineno (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = line_number_base = new_value; + return var; +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + char *p; + int ln; + + ln = executing_line_number (); + p = itos (ln); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_subshell (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (subshell_level); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bashpid (var) + SHELL_VAR *var; +{ + int pid; + char *p; + + pid = getpid (); + p = itos (pid); + + FREE (value_cell (var)); + VSETATTR (var, att_integer|att_readonly); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + if (the_printed_command_except_trap) + p = savestring (the_printed_command_except_trap); + else + { + p = (char *)xmalloc (1); + p[0] = '\0'; + } + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (history_number ()); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + + FREE (value_cell (var)); + var_setvalue (var, savestring (rl_completer_word_break_characters)); + + return (var); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free (rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (0); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} + +static SHELL_VAR * +build_hashcmd (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (hashed_filenames->nbuckets); + for (i = 0; i < hashed_filenames->nbuckets; i++) + { + for (item = hash_items (i, hashed_filenames); item; item = item->next) + { + k = savestring (item->key); + v = pathdata(item)->path; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_hashcmd (self) + SHELL_VAR *self; +{ + build_hashcmd (self); + return (self); +} + +static SHELL_VAR * +assign_hashcmd (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + phash_insert (key, value, 0, 0); + return (build_hashcmd (self)); +} + +#if defined (ALIAS) +static SHELL_VAR * +build_aliasvar (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (aliases->nbuckets); + for (i = 0; i < aliases->nbuckets; i++) + { + for (item = hash_items (i, aliases); item; item = item->next) + { + k = savestring (item->key); + v = ((alias_t *)(item->data))->value; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_aliasvar (self) + SHELL_VAR *self; +{ + build_aliasvar (self); + return (self); +} + +static SHELL_VAR * +assign_aliasvar (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + add_alias (key, value); + return (build_aliasvar (self)); +} +#endif /* ALIAS */ + +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + char *t; + if (variable_context && this_shell_function) + { + FREE (value_cell (self)); + t = savestring (this_shell_function->name); + var_setvalue (self, t); + } +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + VSETATTR (v, att_integer); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + VSETATTR (v, att_integer); + + INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign); + VSETATTR (v, att_integer|att_readonly); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); + VSETATTR (v, att_integer); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign|att_nounset); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset); + + v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree); +# if defined (ALIAS) + v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree); +# endif +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +/* How to get a pointer to the shell variable or function named NAME. + HASHED_VARS is a pointer to the hash table containing the list + of interest (either variables or functions). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + /* If we find the name in HASHED_VARS, set LAST_TABLE_SEARCHED to that + table. */ + if (bucket) + last_table_searched = hashed_vars; + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + int search_tempenv, force_tempenv; + VAR_CONTEXT *vc; + + var = (SHELL_VAR *)NULL; + + force_tempenv = (flags & FV_FORCETEMPENV); + + /* If explicitly requested, first look in the temporary environment for + the variable. This allows constructs such as "foo=x eval 'echo $foo'" + to get the `exported' value of $foo. This happens if we are executing + a function or builtin, or if we are looking up a variable in a + "subshell environment". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + { + if ((flags & FV_SKIPINVISIBLE) == 0) + var = var_lookup (name, shell_variables); + else + { + /* essentially var_lookup expanded inline so we can check for + att_invisible */ + for (vc = shell_variables; vc; vc = vc->down) + { + var = hash_lookup (name, vc->table); + if (var && invisible_p (var)) + var = 0; + if (var) + break; + } + } + } + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up and resolve the chain of nameref variables starting at V all the + way to NULL or non-nameref. */ +SHELL_VAR * +find_variable_nameref (v) + SHELL_VAR *v; +{ + int level, flags; + char *newname; + SHELL_VAR *orig, *oldv; + + level = 0; + orig = v; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)0); + oldv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (newname, flags); + if (v == orig || v == oldv) + { + internal_warning (_("%s: circular name reference"), orig->name); + return ((SHELL_VAR *)0); + } + } + return v; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_variable_last_nameref (name) + const char *name; +{ + SHELL_VAR *v, *nv; + char *newname; + int level, flags; + + nv = v = find_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)0); + nv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (newname, flags); + } + return nv; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_global_variable_last_nameref (name) + const char *name; +{ + SHELL_VAR *v, *nv; + char *newname; + int level; + + nv = v = find_global_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)0); + nv = v; + v = find_global_variable_noref (newname); + } + return nv; +} + +static SHELL_VAR * +find_nameref_at_context (v, vc) + SHELL_VAR *v; + VAR_CONTEXT *vc; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + char *newname; + int level; + + nv = v; + level = 1; + while (nv && nameref_p (nv)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)NULL); + newname = nameref_cell (nv); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)NULL); + nv2 = hash_lookup (newname, vc->table); + if (nv2 == 0) + break; + nv = nv2; + } + return nv; +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + if (nameref_p (nv) == 0) + break; + } + return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv); +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_last_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + } + return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL); +} + +/* Find a variable, forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_tempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, FV_FORCETEMPENV); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +/* Find a variable, not forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_notempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, 0); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +SHELL_VAR * +find_global_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_global_variable_noref (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_shell_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, shell_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found without att_invisible set; return 0 if no non-invisible instances + found. */ +SHELL_VAR * +find_variable_no_invisible (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = FV_SKIPINVISIBLE; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found even if att_invisible set. */ +SHELL_VAR * +find_variable_for_assignment (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +SHELL_VAR * +find_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + return v; +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ +#if defined (DEBUGGER) + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +#else + return ((FUNCTION_DEF *)0); +#endif +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); + else if (assoc_p (var)) + return (assoc_reference (assoc_cell (var), "0")); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment. Since + functions and variables have separate name spaces, returns NULL if + var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + if (shell_variables == 0) + create_variable_tables (); + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name) + const char *name; +{ + SHELL_VAR *new_var, *old_var; + VAR_CONTEXT *vc; + int was_tmpvar; + char *tmp_value; + + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_var && local_p (old_var) && old_var->context == variable_context) + return (old_var); + + was_tmpvar = old_var && tempvar_p (old_var); + /* If we're making a local variable in a shell function, the temporary env + has already been merged into the function's variable context stack. We + can assume that a temporary var in the same context appears in the same + VAR_CONTEXT and can safely be returned without creating a new variable + (which results in duplicate names in the same VAR_CONTEXT->table */ + /* We can't just test tmpvar_p because variables in the temporary env given + to a shell function appear in the function's local variable VAR_CONTEXT + but retain their tempvar attribute. We want temporary variables that are + found in temporary_env, hence the test for last_table_searched, which is + set in hash_lookup and only (so far) checked here. */ + if (was_tmpvar && old_var->context == variable_context && last_table_searched != temporary_env) + { + VUNSETATTR (old_var, att_invisible); /* XXX */ + return (old_var); + } + if (was_tmpvar) + tmp_value = value_cell (old_var); + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + else if (noassign_p (old_var)) + builtin_error (_("%s: variable may not be assigned value"), name); +#if 0 + /* Let noassign variables through with a warning */ + if (readonly_p (old_var)) +#endif + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = make_new_variable (name, vc->table); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. XXX - see above for temporary env + variables with the same context level as variable_context */ + /* XXX - we should only do this if the variable is not an array. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (tmp_value)); + + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + if (was_tmpvar == 0 && no_invisible_vars == 0) + VSETATTR (new_var, att_invisible); /* XXX */ + return (new_var); +} + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibility of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + create_variable_tables (); + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} + +SHELL_VAR * +make_local_array_variable (name, assoc_ok) + char *name; + int assoc_ok; +{ + SHELL_VAR *var; + ARRAY *array; + + var = make_local_variable (name); + if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var))) + return var; + + array = array_create (); + + dispose_variable_value (var); + var_setarray (var, array); + VSETATTR (var, att_array); + return var; +} + +SHELL_VAR * +make_new_assoc_variable (name) + char *name; +{ + SHELL_VAR *entry; + HASH_TABLE *hash; + + entry = make_new_variable (name, global_variables->table); + hash = assoc_create (0); + + var_setassoc (entry, hash); + VSETATTR (entry, att_assoc); + return entry; +} + +SHELL_VAR * +make_local_assoc_variable (name) + char *name; +{ + SHELL_VAR *var; + HASH_TABLE *hash; + + var = make_local_variable (name); + if (var == 0 || assoc_p (var)) + return var; + + dispose_variable_value (var); + hash = assoc_create (0); + + var_setassoc (var, hash); + VSETATTR (var, att_assoc); + return var; +} +#endif + +char * +make_variable_value (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + char *retval, *oval; + intmax_t lval, rval; + int expok, olen, op; + + /* If this variable has had its type set to integer (via `declare -i'), + then do expression evaluation on it and store the result. The + functions in expr.c (evalexp()) and bind_int_variable() are responsible + for turning off the integer flag if they don't want further + evaluation done. */ + if (integer_p (var)) + { + if (flags & ASS_APPEND) + { + oval = value_cell (var); + lval = evalexp (oval, &expok); /* ksh93 seems to do this */ + if (expok == 0) + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + rval = evalexp (value, &expok); + if (expok == 0) + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + /* This can be fooled if the variable's value changes while evaluating + `rval'. We can change it if we move the evaluation of lval to here. */ + if (flags & ASS_APPEND) + rval += lval; + retval = itos (rval); + } +#if defined (CASEMOD_ATTRS) + else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var)) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + op = capcase_p (var) ? CASE_CAPITALIZE + : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER); + oval = sh_modcase (retval, (char *)0, op); + free (retval); + retval = oval; + } +#endif /* CASEMOD_ATTRS */ + else if (value) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags, aflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags, aflags; +{ + char *newval; + SHELL_VAR *entry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + /* Follow the nameref chain here if this is the global variables table */ + if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table) + { + entry = find_global_variable (entry->name); + /* Let's see if we have a nameref referencing a variable that hasn't yet + been created. */ + if (entry == 0) + entry = find_variable_last_nameref (name); /* XXX */ + if (entry == 0) /* just in case */ + return (entry); + } + + /* The first clause handles `declare -n ref; ref=x;' */ + if (entry && invisible_p (entry) && nameref_p (entry)) + goto assign_value; + else if (entry && nameref_p (entry)) + { + newval = nameref_cell (entry); +#if defined (ARRAY_VARS) + /* declare -n foo=x[2] */ + if (valid_array_reference (newval)) + /* XXX - should it be aflags? */ + entry = assign_array_element (newval, make_variable_value (entry, value, 0), aflags); + else +#endif + { + entry = make_new_variable (newval, table); + var_setvalue (entry, make_variable_value (entry, value, 0)); + } + } + else if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */ + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + INVALIDATE_EXPORTSTR (entry); + newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value; + if (assoc_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0")); + else if (array_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, 0, 0); + else + entry = (*(entry->assign_func)) (entry, newval, -1, 0); + if (newval != value) + free (newval); + return (entry); + } + else + { +assign_value: + if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + +#if defined (ARRAY_VARS) + if (assoc_p (entry) || array_p (entry)) + newval = make_array_variable_value (entry, 0, "0", value, aflags); + else +#endif + + newval = make_variable_value (entry, value, aflags); /* XXX */ + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (assoc_p (entry)) + { + assoc_insert (assoc_cell (entry), savestring ("0"), newval); + free (newval); + } + else if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + SHELL_VAR *v, *nv; + VAR_CONTEXT *vc, *nvc; + int level; + + if (shell_variables == 0) + create_variable_tables (); + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env && value) /* XXX - can value be null here? */ + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + nvc = vc; + if (v && nameref_p (v)) + { + nv = find_variable_nameref_context (v, vc, &nvc); + if (nv == 0) + { + nv = find_variable_last_nameref_context (v, vc, &nvc); + if (nv && nameref_p (nv)) + { + /* If this nameref variable doesn't have a value yet, + set the value. Otherwise, assign using the value as + normal. */ + if (nameref_cell (nv) == 0) + return (bind_variable_internal (nv->name, value, nvc->table, 0, flags)); +#if defined (ARRAY_VARS) + else if (valid_array_reference (nameref_cell (nv))) + return (assign_array_element (nameref_cell (nv), value, flags)); + else +#endif + return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags)); + } + else + v = nv; + } + else + v = nv; + } + if (v) + return (bind_variable_internal (v->name, value, nvc->table, 0, flags)); + } + } + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +SHELL_VAR * +bind_global_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + SHELL_VAR *v, *nv; + VAR_CONTEXT *vc, *nvc; + int level; + + if (shell_variables == 0) + create_variable_tables (); + + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value, aflags) + SHELL_VAR *var; + char *value; + int aflags; +{ + char *t; + int invis; + + invis = invisible_p (var); + VUNSETATTR (var, att_invisible); + + if (var->assign_func) + { + /* If we're appending, we need the old value, so use + make_variable_value */ + t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value; + (*(var->assign_func)) (var, t, -1, 0); + if (t != value && t) + free (t); + } + else + { + t = make_variable_value (var, value, aflags); +#if defined (ARRAY_VARS) + if ((aflags & ASS_NAMEREF) && (t == 0 || *t == 0 || (legal_identifier (t) == 0 && valid_array_reference (t) == 0))) +#else + if ((aflags & ASS_NAMEREF) && (t == 0 || *t == 0 || legal_identifier (t) == 0)) +#endif + { + free (t); + if (invis) + VSETATTR (var, att_invisible); /* XXX */ + return ((SHELL_VAR *)NULL); + } + FREE (value_cell (var)); + var_setvalue (var, t); + } + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs) + char *lhs, *rhs; +{ + register SHELL_VAR *v; + int isint, isarr, implicitarray; + + isint = isarr = implicitarray = 0; +#if defined (ARRAY_VARS) + if (valid_array_reference (lhs)) + { + isarr = 1; + v = array_variable_part (lhs, (char **)0, (int *)0); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); +#if defined (ARRAY_VARS) + if (array_p (v) && isarr == 0) + implicitarray = 1; +#endif + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs, 0); + else if (implicitarray) + v = bind_array_variable (lhs, 0, rhs, 0); + else +#endif + v = bind_variable (lhs, rhs, 0); + + if (v && isint) + VSETATTR (v, att_integer); + + VUNSETATTR (v, att_invisible); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val) + char *var; + intmax_t val; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p)); +} + +/* Do a function binding to a variable. You pass the name and + the command to bind to. This conses the name and command. */ +SHELL_VAR * +bind_function (name, value) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +#if defined (DEBUGGER) +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table.*/ +void +bind_function_def (name, value) + const char *name; + FUNCTION_DEF *value; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} +#endif /* DEBUGGER */ + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (word, flags) + WORD_DESC *word; + int flags; +{ + int offset, aflags; + char *name, *temp, *value; + SHELL_VAR *var; + const char *string; + + string = word->word; + + aflags = 0; + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + /* don't ignore the `+' when assigning temporary environment */ + if (name[offset - 1] == '+') + { + name[offset - 1] = '\0'; + aflags |= ASS_APPEND; + } + + var = find_variable (name); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + + temp = name + offset + 1; + value = expand_assignment_string_to_string (temp, 0); + + if (var && (aflags & ASS_APPEND)) + { + temp = make_variable_value (var, value, aflags); + FREE (value); + value = temp; + } + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (name, temporary_env); + if (var == 0) + var = make_new_variable (name, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (name, value, 0); + + array_needs_making = 1; + + if (flags) + stupidly_hack_special_variables (name); + + if (echo_command_at_execute) + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* Copy VAR to a new data structure and return that structure. */ +SHELL_VAR * +copy_variable (var) + SHELL_VAR *var; +{ + SHELL_VAR *copy = (SHELL_VAR *)NULL; + + if (var) + { + copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + copy->attributes = var->attributes; + copy->name = savestring (var->name); + + if (function_p (var)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, array_copy (array_cell (var))); + else if (assoc_p (var)) + var_setassoc (copy, assoc_copy (assoc_cell (var))); +#endif + else if (nameref_cell (var)) /* XXX - nameref */ + var_setref (copy, savestring (nameref_cell (var))); + else if (value_cell (var)) /* XXX - nameref */ + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +static void +dispose_variable_value (var) + SHELL_VAR *var; +{ + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); + else if (assoc_p (var)) + assoc_dispose (assoc_cell (var)); +#endif + else if (nameref_p (var)) + FREE (nameref_cell (var)); + else + FREE (value_cell (var)); +} + +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (nofree_p (var) == 0) + dispose_variable_value (var); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. Unsetting a nameref variable + unsets the variable it resolves to but leaves the nameref alone. */ +int +unbind_variable (name) + const char *name; +{ + SHELL_VAR *v, *nv; + int r; + + v = var_lookup (name, shell_variables); + nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL; + + r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, shell_variables); + return r; +} + +/* Unbind NAME, where NAME is assumed to be a nameref variable */ +int +unbind_nameref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, shell_variables); + if (v && nameref_p (v)) + return makunbound (name, shell_variables); + return 0; +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +#if defined (DEBUGGER) +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} +#endif /* DEBUGGER */ + +int +delete_var (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + free (elt->key); + free (elt); + + dispose_variable (old_var); + return (0); +} + +/* Make the variable associated with NAME go away. HASH_LIST is the + hash table from which this variable should be deleted (either + shell_variables or shell_functions). + Returns non-zero if the variable couldn't be found. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && variable_context == old_var->context) + { + if (nofree_p (old_var)) + var_setvalue (old_var, (char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (old_var)) + array_dispose (array_cell (old_var)); + else if (assoc_p (old_var)) + assoc_dispose (assoc_cell (old_var)); +#endif + else if (nameref_p (old_var)) + FREE (nameref_cell (old_var)); + else + FREE (value_cell (old_var)); + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* Have to save a copy of name here, because it might refer to + old_var->name. If so, stupidly_hack_special_variables will + reference freed memory. */ + t = savestring (name); + + free (elt->key); + free (elt); + + dispose_variable (old_var); + stupidly_hack_special_variables (t); + free (t); + + return (0); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, "", 0); \ + if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static int +qsort_var_comp (var1, var2) + SHELL_VAR **var1, **var2; +{ + int result; + + if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) + result = strcmp ((*var1)->name, (*var2)->name); + + return (result); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Candidate variables for the export environment are either valid variables + with the export attribute or invalid variables inherited from the initial + environment and simply passed through. */ +static int +export_environment_candidate (var) + SHELL_VAR *var; +{ + return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var))); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables () +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && array_p (var)); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +char **tempvar_list; +int tvlist_ind; + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. */ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (0); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but might want to check. */ + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + v->attributes |= var->attributes; + + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + + dispose_variable (var); +} + +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + { + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + dispose_variable (var); + } +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. PUSHF stores names of variables + that require special handling (e.g., IFS) on tempvar_list, so this + function can call stupidly_hack_special_variables on all the + variables in the list when the temporary hash table is destroyed. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + int i; + + tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1); + tempvar_list[tvlist_ind = 0] = 0; + + hash_flush (temporary_env, pushf); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + + tempvar_list[tvlist_ind] = 0; + + array_needs_making = 1; + +#if 0 + sv_ifs ("IFS"); /* XXX here for now -- check setifs in assign_in_env */ +#endif + for (i = 0; i < tvlist_ind; i++) + stupidly_hack_special_variables (tempvar_list[i]); + + strvec_dispose (tempvar_list); + tempvar_list = 0; + tvlist_ind = 0; +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + { + dispose_temporary_env (propagate_temp_var); + maybe_make_export_env (); + } +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (push_temp_var); +} + +void +flush_temporary_env () +{ + if (temporary_env) + { + hash_flush (temporary_env, free_variable_hash_data); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + } +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value, isfunc) + const char *name, *value; + int isfunc; +{ + size_t name_len, value_len; + char *p, *q; + + name_len = strlen (name); + value_len = STRLEN (value); + + /* If we are exporting a shell function, construct the encoded function + name. */ + if (isfunc && value) + { + p = (char *)xmalloc (BASHFUNC_PREFLEN + name_len + BASHFUNC_SUFFLEN + value_len + 2); + q = p; + memcpy (q, BASHFUNC_PREFIX, BASHFUNC_PREFLEN); + q += BASHFUNC_PREFLEN; + memcpy (q, name, name_len); + q += name_len; + memcpy (q, BASHFUNC_SUFFIX, BASHFUNC_SUFFLEN); + q += BASHFUNC_SUFFLEN; + } + else + { + p = (char *)xmalloc (2 + name_len + value_len); + memcpy (p, name, name_len); + q = p + name_len; + } + + q[0] = '='; + if (value && *value) + memcpy (q + 1, value, value_len + 1); + else + q[1] = '\0'; + + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (s == 0) + { + internal_error (_("%s has null exportstr"), v->name); + return (0); + } + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + +#define USE_EXPORTSTR (value == var->exportstr) + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if ARRAY_EXPORT + value = array_to_assignment_string (array_cell (var)); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif /* ARRAY_EXPORT */ + else if (assoc_p (var)) +# if 0 + value = assoc_to_assignment_string (assoc_cell (var)); +# else + continue; /* XXX associative array vars cannot yet be exported */ +# endif +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value, function_p (var)); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if 0 /* not yet */ +#if defined (ARRAY_VARS) + if (array_p (var) || assoc_p (var)) + free (value); +#endif +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + +#if 0 + vars = map_over (visible_and_exported, vcxt); +#else + vars = map_over (export_environment_candidate, vcxt); +#endif + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + environ = export_env; \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_array); +} + +/* Make the environment array for the command about to be executed, if the + array needs making. Otherwise, do nothing. If a shell action could + change the array that commands receive for their environment, then the + code should `array_needs_making++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +int +chkexport (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && exported_p (v)) + { + array_needs_making = 1; + maybe_make_export_env (); + return 1; + } + return 0; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + environ = export_env; + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + temp_array = make_var_export_array (tcxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + + vc = new_var_context (name, flags); + vc->table = tempvars; + if (tempvars) + { + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + if (local_p (var) && STREQ (var->name, "-")) + set_current_options (value_cell (var)); + else if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate))) + { + /* Make sure we have a hash table to store the variable in while it is + being propagated down to the global variables table. Create one if + we have to */ + if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0) + shell_variables->table = hash_create (0); + /* XXX - should we set v->context here? */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } + + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + /* XXX - This isn't exactly right, because all tempenv variables have the + export attribute set. */ +#if 0 + if (exported_p (var) || (var->attributes & att_propagate)) +#else + if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate)) +#endif + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_func_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* XXX - should always be followed by remember_args () */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (WORD_LIST **) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (WORD_LIST *)); + } + dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1); + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; + set_dollar_vars_unchanged (); +} + +void +dispose_saved_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_shift (bash_argc_a, 1, 0); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ +extern int eof_encountered, eof_encountered_limit, ignoreeof; + +#if defined (READLINE) +extern int hostname_list_initialized; +#endif + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "BASH_COMPAT", sv_shcompat }, + { "BASH_XTRACEFD", sv_xtracefd }, + +#if defined (JOB_CONTROL) + { "CHILD_MAX", sv_childmax }, +#endif + +#if defined (READLINE) +# if defined (STRICT_POSIX) + { "COLUMNS", sv_winsize }, +# endif + { "COMP_WORDBREAKS", sv_comp_wordbreaks }, +#endif + + { "FUNCNEST", sv_funcnest }, + + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (__CYGWIN__) + { "HOME", sv_home }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + { "LC_TIME", sv_locale }, + +#if defined (READLINE) && defined (STRICT_POSIX) + { "LINES", sv_winsize }, +#endif + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +/* Special variables that need hooks to be run when they are unset as part + of shell reinitialization should have their sv_ functions run here. */ +void +reinit_special_variables () +{ +#if defined (READLINE) + sv_comp_wordbreaks ("COMP_WORDBREAKS"); +#endif + sv_globignore ("GLOBIGNORE"); + sv_opterr ("OPTERR"); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +void +sv_funcnest (name) + char *name; +{ + SHELL_VAR *v; + intmax_t num; + + v = find_variable (name); + if (v == 0) + funcnest_max = 0; + else if (legal_number (value_cell (v), &num) == 0) + funcnest_max = 0; + else + funcnest_max = num; +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + if (privileged_mode == 0) + setup_glob_ignore (name); +} + +#if defined (READLINE) +void +sv_comp_wordbreaks (name) + char *name; +{ + SHELL_VAR *sv; + + sv = find_variable (name); + if (sv == 0) + reset_completer_word_break_chars (); +} + +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} + +#if defined (STRICT_POSIX) +/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values + found in the initial environment) to override the terminal size reported by + the kernel. */ +void +sv_winsize (name) + char *name; +{ + SHELL_VAR *v; + intmax_t xd; + int d; + + if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing) + return; + + v = find_variable (name); + if (v == 0 || var_isnull (v)) + rl_reset_screen_size (); + else + { + if (legal_number (value_cell (v), &xd) == 0) + return; + winsize_assignment = 1; + d = xd; /* truncate */ + if (name[0] == 'L') /* LINES */ + rl_set_screen_size (d, -1); + else /* COLUMNS */ + rl_set_screen_size (-1, d); + winsize_assignment = 0; + } +} +#endif /* STRICT_POSIX */ +#endif /* READLINE */ + +/* Update the value of HOME in the export environment so tilde expansion will + work on cygwin. */ +#if defined (__CYGWIN__) +sv_home (name) + char *name; +{ + array_needs_making = 1; + maybe_make_export_env (); +} +#endif + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + int hmax; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + hmax = num; + if (hmax < 0 && name[4] == 'S') + unstifle_history (); /* unstifle history if HISTSIZE < 0 */ + else if (name[4] == 'S') + { + stifle_history (hmax); + hmax = where_history (); + if (history_lines_this_session > hmax) + history_lines_this_session = hmax; + } + else if (hmax >= 0) /* truncate HISTFILE if HISTFILESIZE >= 0 */ + { + history_truncate_file (get_string_value ("HISTFILE"), hmax); + /* If we just shrank the history file to fewer lines than we've + already read, make sure we adjust our idea of how many lines + we have read from the file. */ + if (hmax < history_lines_in_file) + history_lines_in_file = hmax; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + if (v = find_variable (name)) + { + if (history_comment_char == 0) + history_comment_char = '#'; + } + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) +void +sv_tz (name) + char *name; +{ + if (chkexport (name)) + tzset (); +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var != 0; + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + SHELL_VAR *var; + char *tt; + int s; + + var = find_variable ("OPTIND"); + tt = var ? get_variable_value (var) : (char *)NULL; + + /* Assume that if var->context < variable_context and variable_context > 0 + then we are restoring the variables's previous state while returning + from a function. */ + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SET_INT_VAR (name, posixly_correct); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + int r; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + r = set_lang (name, v); + else + r = set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ + +#if 1 + if (r == 0 && posixly_correct) + last_command_exit_value = 1; +#endif +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { + ae = element_forw (a->head); + free (element_value (ae)); + ae->value = itos (ps[0]); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + free (element_value (ae)); + ae->value = itos (ps[i]); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; ps[i] != -1; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } +} + +ARRAY * +save_pipestatus_array () +{ + SHELL_VAR *v; + ARRAY *a, *a2; + + v = find_variable ("PIPESTATUS"); + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return ((ARRAY *)NULL); + + a = array_cell (v); + a2 = array_copy (array_cell (v)); + + return a2; +} + +void +restore_pipestatus_array (a) + ARRAY *a; +{ + SHELL_VAR *v; + ARRAY *a2; + + v = find_variable ("PIPESTATUS"); + /* XXX - should we still assign even if existing value is NULL? */ + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return; + + a2 = array_cell (v); + var_setarray (v, a); + + array_dispose (a2); +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} + +void +sv_xtracefd (name) + char *name; +{ + SHELL_VAR *v; + char *t, *e; + int fd; + FILE *fp; + + v = find_variable (name); + if (v == 0) + { + xtrace_reset (); + return; + } + + t = value_cell (v); + if (t == 0 || *t == 0) + xtrace_reset (); + else + { + fd = (int)strtol (t, &e, 10); + if (e != t && *e == '\0' && sh_validfd (fd)) + { + fp = fdopen (fd, "w"); + if (fp == 0) + internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v)); + else + xtrace_set (fd, fp); + } + else + internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v)); + } +} + +#define MIN_COMPAT_LEVEL 31 + +void +sv_shcompat (name) + char *name; +{ + SHELL_VAR *v; + char *val; + int tens, ones, compatval; + + v = find_variable (name); + if (v == 0) + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + val = value_cell (v); + if (val == 0 || *val == '\0') + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + /* Handle decimal-like compatibility version specifications: 4.2 */ + if (isdigit (val[0]) && val[1] == '.' && isdigit (val[2]) && val[3] == 0) + { + tens = val[0] - '0'; + ones = val[2] - '0'; + compatval = tens*10 + ones; + } + /* Handle integer-like compatibility version specifications: 42 */ + else if (isdigit (val[0]) && isdigit (val[1]) && val[2] == 0) + { + tens = val[0] - '0'; + ones = val[1] - '0'; + compatval = tens*10 + ones; + } + else + { +compat_error: + internal_error (_("%s: %s: compatibility value out of range"), name, val); + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + + if (compatval < MIN_COMPAT_LEVEL || compatval > DEFAULT_COMPAT_LEVEL) + goto compat_error; + + shell_compatibility_level = compatval; + set_compatibility_opts (); +} + +#if defined (JOB_CONTROL) +void +sv_childmax (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value (name); + s = (tt && *tt) ? atoi (tt) : 0; + set_maxchild (s); +} +#endif |