]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
add some missing files, update copyrights bash-5.3-alpha
authorChet Ramey <chet.ramey@case.edu>
Thu, 25 Apr 2024 19:50:39 +0000 (15:50 -0400)
committerChet Ramey <chet.ramey@case.edu>
Thu, 25 Apr 2024 19:50:39 +0000 (15:50 -0400)
116 files changed:
cross-build/msys32.cache [new file with mode: 0644]
examples/loadables/kv.c [new file with mode: 0644]
examples/loadables/strptime.c [new file with mode: 0644]
examples/scripts/secure-script [new file with mode: 0644]
include/intprops-internal.h [new file with mode: 0644]
include/stdckdint.in.h [new file with mode: 0644]
include/unlocked-io.h [new file with mode: 0644]
lib/intl/arg-nonnull.h [new file with mode: 0644]
lib/intl/attribute.h [new file with mode: 0644]
lib/intl/export.h [new file with mode: 0644]
lib/intl/filename.h [new file with mode: 0644]
lib/intl/flexmember.h [new file with mode: 0644]
lib/intl/hash-string.c [new file with mode: 0644]
lib/intl/intl-exports.c [new file with mode: 0644]
lib/intl/langprefs.c [new file with mode: 0644]
lib/intl/libgnuintl.in.h [new file with mode: 0644]
lib/intl/localename-table.c [new file with mode: 0644]
lib/intl/localename-table.in.h [new file with mode: 0644]
lib/intl/lock.c [new file with mode: 0644]
lib/intl/lock.h [new file with mode: 0644]
lib/intl/plural.h [new file with mode: 0644]
lib/intl/printf-args.c [new file with mode: 0644]
lib/intl/printf-args.h [new file with mode: 0644]
lib/intl/printf-parse.c [new file with mode: 0644]
lib/intl/printf-parse.h [new file with mode: 0644]
lib/intl/printf.c [new file with mode: 0644]
lib/intl/setlocale-lock.c [new file with mode: 0644]
lib/intl/setlocale.c [new file with mode: 0644]
lib/intl/setlocale_null.c [new file with mode: 0644]
lib/intl/setlocale_null.h [new file with mode: 0644]
lib/intl/thread-optim.h [new file with mode: 0644]
lib/intl/threadlib.c [new file with mode: 0644]
lib/intl/tsearch.c [new file with mode: 0644]
lib/intl/tsearch.h [new file with mode: 0644]
lib/intl/vasnprintf.c [new file with mode: 0644]
lib/intl/vasnprintf.h [new file with mode: 0644]
lib/intl/vasnwprintf.h [new file with mode: 0644]
lib/intl/verify.h [new file with mode: 0644]
lib/intl/version.c [new file with mode: 0644]
lib/intl/wprintf-parse.h [new file with mode: 0644]
lib/intl/xsize.c [new file with mode: 0644]
lib/intl/xsize.h [new file with mode: 0644]
lib/malloc/sbrk.c [new file with mode: 0644]
lib/sh/anonfile.c [new file with mode: 0644]
lib/sh/compat.c [new file with mode: 0644]
lib/sh/mbsncmp.c [new file with mode: 0644]
lib/sh/reallocarray.c [new file with mode: 0644]
lib/sh/strlcpy.c [new file with mode: 0644]
lib/sh/strscpy.c [new file with mode: 0644]
m4/bison.m4 [new file with mode: 0644]
m4/c-bool.m4 [new file with mode: 0644]
m4/flexmember.m4 [new file with mode: 0644]
m4/locale_h.m4 [new file with mode: 0644]
m4/unlocked-io.m4 [new file with mode: 0644]
patchlevel.h
tests/alias7.sub [new file with mode: 0644]
tests/arith9.sub [new file with mode: 0644]
tests/array31.sub [new file with mode: 0644]
tests/array32.sub [new file with mode: 0644]
tests/array33.sub [new file with mode: 0644]
tests/assoc19.sub [new file with mode: 0644]
tests/builtins10.sub [new file with mode: 0644]
tests/builtins11.sub [new file with mode: 0644]
tests/builtins12.sub [new file with mode: 0644]
tests/builtins8.sub [new file with mode: 0644]
tests/builtins9.sub [new file with mode: 0644]
tests/comsub2.right [new file with mode: 0644]
tests/comsub2.tests [new file with mode: 0644]
tests/comsub21.sub [new file with mode: 0644]
tests/comsub22.sub [new file with mode: 0644]
tests/comsub23.sub [new file with mode: 0644]
tests/comsub24.sub [new file with mode: 0644]
tests/comsub25.sub [new file with mode: 0644]
tests/comsub26.sub [new file with mode: 0644]
tests/comsub7.sub [new file with mode: 0644]
tests/cond-error1.sub [new file with mode: 0644]
tests/cond-xtrace1.sub [new file with mode: 0644]
tests/dollar-star11.sub [new file with mode: 0644]
tests/errors10.sub [new file with mode: 0644]
tests/errors11.sub [new file with mode: 0644]
tests/exec15.sub [new file with mode: 0644]
tests/extglob8.sub [new file with mode: 0644]
tests/func5.sub [new file with mode: 0644]
tests/glob-bracket.right [new file with mode: 0644]
tests/glob-bracket.tests [new file with mode: 0644]
tests/glob11.sub [new file with mode: 0644]
tests/heredoc10.sub [new file with mode: 0644]
tests/heredoc8.sub [new file with mode: 0644]
tests/heredoc9.sub [new file with mode: 0644]
tests/history7.sub [new file with mode: 0644]
tests/history8.sub [new file with mode: 0644]
tests/intl4.sub [new file with mode: 0644]
tests/invocation.right [new file with mode: 0644]
tests/invocation.tests [new file with mode: 0644]
tests/invocation1.sub [new file with mode: 0644]
tests/invocation2.sub [new file with mode: 0644]
tests/invocation3.sub [new file with mode: 0644]
tests/jobs8.sub [new file with mode: 0644]
tests/jobs9.sub [new file with mode: 0644]
tests/nameref24.sub [new file with mode: 0644]
tests/printf5.sub [new file with mode: 0644]
tests/printf6.sub [new file with mode: 0644]
tests/printf7.sub [new file with mode: 0644]
tests/read10.sub [new file with mode: 0644]
tests/read9.sub [new file with mode: 0644]
tests/redir13.in [new file with mode: 0644]
tests/run-comsub2 [new file with mode: 0644]
tests/run-glob-bracket [new file with mode: 0644]
tests/run-invocation [new file with mode: 0644]
tests/set-x2.sub [new file with mode: 0644]
tests/trap7.sub [new file with mode: 0644]
tests/trap8.sub [new file with mode: 0644]
tests/trap9.sub [new file with mode: 0644]
tests/type5.sub [new file with mode: 0644]
tests/varenv23.sub [new file with mode: 0644]
tests/varenv24.sub [new file with mode: 0644]

diff --git a/cross-build/msys32.cache b/cross-build/msys32.cache
new file mode 100644 (file)
index 0000000..7d9f257
--- /dev/null
@@ -0,0 +1,251 @@
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overriden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+ac_cv_build=${ac_cv_build='i686-pc-msys'}
+ac_cv_build_alias=${ac_cv_build_alias='i686-pc-msys'}
+ac_cv_c_bigendian=${ac_cv_c_bigendian='no'}
+ac_cv_c_char_unsigned=${ac_cv_c_char_unsigned='no'}
+ac_cv_c_compiler_gnu=${ac_cv_c_compiler_gnu='yes'}
+ac_cv_c_const=${ac_cv_c_const='yes'}
+ac_cv_c_inline=${ac_cv_c_inline='inline'}
+ac_cv_c_long_double=${ac_cv_c_long_double='yes'}
+ac_cv_c_stringize=${ac_cv_c_stringize='yes'}
+ac_cv_decl_sys_siglist=${ac_cv_decl_sys_siglist='no'}
+ac_cv_exeext=${ac_cv_exeext='.exe'}
+ac_cv_func___setostype=${ac_cv_func___setostype='no'}
+ac_cv_func__doprnt=${ac_cv_func__doprnt='no'}
+ac_cv_func_alloca_works=${ac_cv_func_alloca_works='yes'}
+ac_cv_func_asprintf=${ac_cv_func_asprintf='no'}
+ac_cv_func_bcopy=${ac_cv_func_bcopy='yes'}
+ac_cv_func_bindtextdomain=${ac_cv_func_bindtextdomain='no'}
+ac_cv_func_bzero=${ac_cv_func_bzero='yes'}
+ac_cv_func_confstr=${ac_cv_func_confstr='no'}
+ac_cv_func_dlclose=${ac_cv_func_dlclose='yes'}
+ac_cv_func_dlopen=${ac_cv_func_dlopen='yes'}
+ac_cv_func_dlsym=${ac_cv_func_dlsym='yes'}
+ac_cv_func_dup2=${ac_cv_func_dup2='yes'}
+ac_cv_func_fnmatch=${ac_cv_func_fnmatch='no'}
+ac_cv_func_getaddrinfo=${ac_cv_func_getaddrinfo='no'}
+ac_cv_func_getcwd=${ac_cv_func_getcwd='yes'}
+ac_cv_func_getdtablesize=${ac_cv_func_getdtablesize='yes'}
+ac_cv_func_getgroups=${ac_cv_func_getgroups='yes'}
+ac_cv_func_gethostbyname=${ac_cv_func_gethostbyname='yes'}
+ac_cv_func_gethostname=${ac_cv_func_gethostname='yes'}
+ac_cv_func_getpagesize=${ac_cv_func_getpagesize='yes'}
+ac_cv_func_getpeername=${ac_cv_func_getpeername='yes'}
+ac_cv_func_getpgrp_void=${ac_cv_func_getpgrp_void='yes'}
+ac_cv_func_getrlimit=${ac_cv_func_getrlimit='yes'}
+ac_cv_func_getrusage=${ac_cv_func_getrusage='yes'}
+ac_cv_func_getservbyname=${ac_cv_func_getservbyname='yes'}
+ac_cv_func_gettext=${ac_cv_func_gettext='no'}
+ac_cv_func_gettimeofday=${ac_cv_func_gettimeofday='yes'}
+ac_cv_func_inet_aton=${ac_cv_func_inet_aton='yes'}
+ac_cv_func_isascii=${ac_cv_func_isascii='yes'}
+ac_cv_func_isblank=${ac_cv_func_isblank='no'}
+ac_cv_func_isgraph=${ac_cv_func_isgraph='yes'}
+ac_cv_func_isprint=${ac_cv_func_isprint='yes'}
+ac_cv_func_isspace=${ac_cv_func_isspace='yes'}
+ac_cv_func_isxdigit=${ac_cv_func_isxdigit='yes'}
+ac_cv_func_killpg=${ac_cv_func_killpg='yes'}
+ac_cv_func_lstat=${ac_cv_func_lstat='yes'}
+ac_cv_func_memmove=${ac_cv_func_memmove='yes'}
+ac_cv_func_mkfifo=${ac_cv_func_mkfifo='yes'}
+ac_cv_func_pathconf=${ac_cv_func_pathconf='yes'}
+ac_cv_func_putenv=${ac_cv_func_putenv='yes'}
+ac_cv_func_readlink=${ac_cv_func_readlink='yes'}
+ac_cv_func_rename=${ac_cv_func_rename='yes'}
+ac_cv_func_sbrk=${ac_cv_func_sbrk='yes'}
+ac_cv_func_select=${ac_cv_func_select='yes'}
+ac_cv_func_setdtablesize=${ac_cv_func_setdtablesize='yes'}
+ac_cv_func_setenv=${ac_cv_func_setenv='yes'}
+ac_cv_func_setlinebuf=${ac_cv_func_setlinebuf='no'}
+ac_cv_func_setlocale=${ac_cv_func_setlocale='yes'}
+ac_cv_func_setvbuf=${ac_cv_func_setvbuf='yes'}
+ac_cv_func_setvbuf_reversed=${ac_cv_func_setvbuf_reversed='no'}
+ac_cv_func_siginterrupt=${ac_cv_func_siginterrupt='no'}
+ac_cv_func_snprintf=${ac_cv_func_snprintf='yes'}
+ac_cv_func_strcasecmp=${ac_cv_func_strcasecmp='yes'}
+ac_cv_func_strchr=${ac_cv_func_strchr='yes'}
+ac_cv_func_strcoll_works=${ac_cv_func_strcoll_works='yes'}
+ac_cv_func_strerror=${ac_cv_func_strerror='yes'}
+ac_cv_func_strpbrk=${ac_cv_func_strpbrk='yes'}
+ac_cv_func_strtod=${ac_cv_func_strtod='yes'}
+ac_cv_func_strtoimax=${ac_cv_func_strtoimax='no'}
+ac_cv_func_strtol=${ac_cv_func_strtol='yes'}
+ac_cv_func_strtoll=${ac_cv_func_strtoll='no'}
+ac_cv_func_strtoul=${ac_cv_func_strtoul='yes'}
+ac_cv_func_strtoull=${ac_cv_func_strtoull='no'}
+ac_cv_func_strtoumax=${ac_cv_func_strtoumax='no'}
+ac_cv_func_sysconf=${ac_cv_func_sysconf='yes'}
+ac_cv_func_tcgetattr=${ac_cv_func_tcgetattr='yes'}
+ac_cv_func_tcgetpgrp=${ac_cv_func_tcgetpgrp='yes'}
+ac_cv_func_textdomain=${ac_cv_func_textdomain='no'}
+ac_cv_func_times=${ac_cv_func_times='yes'}
+ac_cv_func_ttyname=${ac_cv_func_ttyname='yes'}
+ac_cv_func_tzset=${ac_cv_func_tzset='yes'}
+ac_cv_func_ulimit=${ac_cv_func_ulimit='no'}
+ac_cv_func_uname=${ac_cv_func_uname='yes'}
+ac_cv_func_vasprintf=${ac_cv_func_vasprintf='no'}
+ac_cv_func_vprintf=${ac_cv_func_vprintf='yes'}
+ac_cv_func_vsnprintf=${ac_cv_func_vsnprintf='yes'}
+ac_cv_func_wait3=${ac_cv_func_wait3='yes'}
+ac_cv_func_waitpid=${ac_cv_func_waitpid='yes'}
+ac_cv_have_decl_confstr=${ac_cv_have_decl_confstr='no'}
+ac_cv_have_decl_printf=${ac_cv_have_decl_printf='yes'}
+ac_cv_have_decl_sbrk=${ac_cv_have_decl_sbrk='yes'}
+ac_cv_have_decl_strsignal=${ac_cv_have_decl_strsignal='yes'}
+ac_cv_have_decl_strtold=${ac_cv_have_decl_strtold='no'}
+ac_cv_header_arpa_inet_h=${ac_cv_header_arpa_inet_h='yes'}
+ac_cv_header_dirent_dirent_h=${ac_cv_header_dirent_dirent_h='yes'}
+ac_cv_header_dlfcn_h=${ac_cv_header_dlfcn_h='yes'}
+ac_cv_header_grp_h=${ac_cv_header_grp_h='yes'}
+ac_cv_header_inttypes_h=${ac_cv_header_inttypes_h='no'}
+ac_cv_header_libintl_h=${ac_cv_header_libintl_h='yes'}
+ac_cv_header_limits_h=${ac_cv_header_limits_h='yes'}
+ac_cv_header_locale_h=${ac_cv_header_locale_h='yes'}
+ac_cv_header_memory_h=${ac_cv_header_memory_h='yes'}
+ac_cv_header_minix_config_h=${ac_cv_header_minix_config_h='no'}
+ac_cv_header_netdb_h=${ac_cv_header_netdb_h='yes'}
+ac_cv_header_netinet_in_h=${ac_cv_header_netinet_in_h='yes'}
+ac_cv_header_stat_broken=${ac_cv_header_stat_broken='no'}
+ac_cv_header_stdarg_h=${ac_cv_header_stdarg_h='yes'}
+ac_cv_header_stdc=${ac_cv_header_stdc='yes'}
+ac_cv_header_stddef_h=${ac_cv_header_stddef_h='yes'}
+ac_cv_header_stdint_h=${ac_cv_header_stdint_h='no'}
+ac_cv_header_stdlib_h=${ac_cv_header_stdlib_h='yes'}
+ac_cv_header_string_h=${ac_cv_header_string_h='yes'}
+ac_cv_header_strings_h=${ac_cv_header_strings_h='yes'}
+ac_cv_header_sys_file_h=${ac_cv_header_sys_file_h='yes'}
+ac_cv_header_sys_param_h=${ac_cv_header_sys_param_h='yes'}
+ac_cv_header_sys_pte_h=${ac_cv_header_sys_pte_h='no'}
+ac_cv_header_sys_ptem_h=${ac_cv_header_sys_ptem_h='no'}
+ac_cv_header_sys_resource_h=${ac_cv_header_sys_resource_h='yes'}
+ac_cv_header_sys_select_h=${ac_cv_header_sys_select_h='yes'}
+ac_cv_header_sys_socket_h=${ac_cv_header_sys_socket_h='yes'}
+ac_cv_header_sys_stat_h=${ac_cv_header_sys_stat_h='yes'}
+ac_cv_header_sys_stream_h=${ac_cv_header_sys_stream_h='no'}
+ac_cv_header_sys_time_h=${ac_cv_header_sys_time_h='yes'}
+ac_cv_header_sys_times_h=${ac_cv_header_sys_times_h='yes'}
+ac_cv_header_sys_types_h=${ac_cv_header_sys_types_h='yes'}
+ac_cv_header_sys_wait_h=${ac_cv_header_sys_wait_h='yes'}
+ac_cv_header_termcap_h=${ac_cv_header_termcap_h='yes'}
+ac_cv_header_termio_h=${ac_cv_header_termio_h='yes'}
+ac_cv_header_termios_h=${ac_cv_header_termios_h='yes'}
+ac_cv_header_time=${ac_cv_header_time='yes'}
+ac_cv_header_unistd_h=${ac_cv_header_unistd_h='yes'}
+ac_cv_header_varargs_h=${ac_cv_header_varargs_h='yes'}
+ac_cv_host=${ac_cv_host='i686-pc-msys'}
+ac_cv_host_alias=${ac_cv_host_alias='i686-pc-msys'}
+ac_cv_lib_dir_opendir=${ac_cv_lib_dir_opendir='no'}
+ac_cv_lib_dl_dlopen=${ac_cv_lib_dl_dlopen='no'}
+ac_cv_lib_intl_bindtextdomain=${ac_cv_lib_intl_bindtextdomain='yes'}
+ac_cv_lib_termcap_tgetent=${ac_cv_lib_termcap_tgetent='yes'}
+ac_cv_member_struct_stat_st_blocks=${ac_cv_member_struct_stat_st_blocks='yes'}
+ac_cv_member_struct_termio_c_line=${ac_cv_member_struct_termio_c_line='yes'}
+ac_cv_member_struct_termios_c_line=${ac_cv_member_struct_termios_c_line='yes'}
+ac_cv_objext=${ac_cv_objext='o'}
+ac_cv_path_install=${ac_cv_path_install='/usr/bin/install -c'}
+ac_cv_prog_AR=${ac_cv_prog_AR='ar'}
+ac_cv_prog_CPP=${ac_cv_prog_CPP='gcc -E'}
+ac_cv_prog_YACC=${ac_cv_prog_YACC='bison -y'}
+ac_cv_prog_ac_ct_CC=${ac_cv_prog_ac_ct_CC='gcc'}
+ac_cv_prog_ac_ct_RANLIB=${ac_cv_prog_ac_ct_RANLIB='ranlib'}
+ac_cv_prog_cc_g=${ac_cv_prog_cc_g='yes'}
+ac_cv_prog_cc_stdc=${ac_cv_prog_cc_stdc=''}
+ac_cv_prog_gcc_traditional=${ac_cv_prog_gcc_traditional='no'}
+ac_cv_prog_make_make_set=${ac_cv_prog_make_make_set='yes'}
+ac_cv_sizeof_char=${ac_cv_sizeof_char='1'}
+ac_cv_sizeof_char_p=${ac_cv_sizeof_char_p='4'}
+ac_cv_sizeof_double=${ac_cv_sizeof_double='8'}
+ac_cv_sizeof_int=${ac_cv_sizeof_int='4'}
+ac_cv_sizeof_long=${ac_cv_sizeof_long='4'}
+ac_cv_sizeof_long_long=${ac_cv_sizeof_long_long='8'}
+ac_cv_sizeof_short=${ac_cv_sizeof_short='2'}
+ac_cv_sys_file_offset_bits=${ac_cv_sys_file_offset_bits='no'}
+ac_cv_sys_interpreter=${ac_cv_sys_interpreter='yes'}
+ac_cv_sys_large_files=${ac_cv_sys_large_files='no'}
+ac_cv_sys_largefile_CC=${ac_cv_sys_largefile_CC='no'}
+ac_cv_sys_posix_termios=${ac_cv_sys_posix_termios='yes'}
+ac_cv_sys_tiocgwinsz_in_termios_h=${ac_cv_sys_tiocgwinsz_in_termios_h='yes'}
+ac_cv_type_bits16_t=${ac_cv_type_bits16_t='no'}
+ac_cv_type_bits32_t=${ac_cv_type_bits32_t='no'}
+ac_cv_type_bits64_t=${ac_cv_type_bits64_t='no'}
+ac_cv_type_char=${ac_cv_type_char='yes'}
+ac_cv_type_char_p=${ac_cv_type_char_p='yes'}
+ac_cv_type_double=${ac_cv_type_double='yes'}
+ac_cv_type_getgroups=${ac_cv_type_getgroups='gid_t'}
+ac_cv_type_int=${ac_cv_type_int='yes'}
+ac_cv_type_long=${ac_cv_type_long='yes'}
+ac_cv_type_long_long=${ac_cv_type_long_long='yes'}
+ac_cv_type_mode_t=${ac_cv_type_mode_t='yes'}
+ac_cv_type_off_t=${ac_cv_type_off_t='yes'}
+ac_cv_type_pid_t=${ac_cv_type_pid_t='yes'}
+ac_cv_type_ptrdiff_t=${ac_cv_type_ptrdiff_t='yes'}
+ac_cv_type_short=${ac_cv_type_short='yes'}
+ac_cv_type_signal=${ac_cv_type_signal='void'}
+ac_cv_type_size_t=${ac_cv_type_size_t='yes'}
+ac_cv_type_ssize_t=${ac_cv_type_ssize_t='yes'}
+ac_cv_type_time_t=${ac_cv_type_time_t='yes'}
+ac_cv_type_u_bits16_t=${ac_cv_type_u_bits16_t='no'}
+ac_cv_type_u_bits32_t=${ac_cv_type_u_bits32_t='no'}
+ac_cv_type_u_int=${ac_cv_type_u_int='yes'}
+ac_cv_type_u_long=${ac_cv_type_u_long='yes'}
+ac_cv_type_uid_t=${ac_cv_type_uid_t='yes'}
+ac_cv_working_alloca_h=${ac_cv_working_alloca_h='no'}
+
+bash_cv_decl_strtoimax=${bash_cv_decl_strtoimax='no'}
+bash_cv_decl_strtol=${bash_cv_decl_strtol='yes'}
+bash_cv_decl_strtoll=${bash_cv_decl_strtoll='no'}
+bash_cv_decl_strtoul=${bash_cv_decl_strtoul='yes'}
+bash_cv_decl_strtoull=${bash_cv_decl_strtoull='no'}
+bash_cv_decl_strtoumax=${bash_cv_decl_strtoumax='no'}
+bash_cv_decl_under_sys_siglist=${bash_cv_decl_under_sys_siglist='no'}
+bash_cv_dev_fd=${bash_cv_dev_fd='absent'}
+bash_cv_dev_stdin=${bash_cv_dev_stdin='absent'}
+bash_cv_dirent_has_d_fileno=${bash_cv_dirent_has_d_fileno='no'}
+bash_cv_dirent_has_dino=${bash_cv_dirent_has_dino='yes'}
+bash_cv_dup2_broken=${bash_cv_dup2_broken='no'}
+bash_cv_fionread_in_ioctl=${bash_cv_fionread_in_ioctl='no'}
+bash_cv_func_sigsetjmp=${bash_cv_func_sigsetjmp='present'}
+bash_cv_func_strcoll_broken=${bash_cv_func_strcoll_broken='no'}
+bash_cv_getenv_redef=${bash_cv_getenv_redef='yes'}
+bash_cv_getpw_declared=${bash_cv_getpw_declared='yes'}
+bash_cv_have_strsignal=${bash_cv_have_strsignal='yes'}
+bash_cv_job_control_missing=${bash_cv_job_control_missing='present'}
+bash_cv_mail_dir=${bash_cv_mail_dir='unknown'}
+bash_cv_must_reinstall_sighandlers=${bash_cv_must_reinstall_sighandlers='no'}
+bash_cv_opendir_not_robust=${bash_cv_opendir_not_robust='no'}
+bash_cv_pgrp_pipe=${bash_cv_pgrp_pipe='no'}
+bash_cv_printf_a_format=${bash_cv_printf_a_format='no'}
+bash_cv_signal_vintage=${bash_cv_signal_vintage='posix'}
+bash_cv_speed_t_in_sys_types=${bash_cv_speed_t_in_sys_types='no'}
+bash_cv_struct_timeval=${bash_cv_struct_timeval='yes'}
+bash_cv_struct_winsize_header=${bash_cv_struct_winsize_header='termios_h'}
+bash_cv_sys_errlist=${bash_cv_sys_errlist='no'}
+bash_cv_sys_named_pipes=${bash_cv_sys_named_pipes='present'}
+bash_cv_sys_siglist=${bash_cv_sys_siglist='no'}
+bash_cv_termcap_lib=${bash_cv_termcap_lib='libtermcap'}
+bash_cv_tiocstat_in_ioctl=${bash_cv_tiocstat_in_ioctl='no'}
+bash_cv_type_clock_t=${bash_cv_type_clock_t='yes'}
+bash_cv_type_intmax_t=${bash_cv_type_intmax_t='no'}
+bash_cv_type_long_long=${bash_cv_type_long_long='long long'}
+bash_cv_type_quad_t=${bash_cv_type_quad_t='no'}
+bash_cv_type_rlimit=${bash_cv_type_rlimit='rlim_t'}
+bash_cv_type_sigset_t=${bash_cv_type_sigset_t='yes'}
+bash_cv_type_socklen_t=${bash_cv_type_socklen_t='no'}
+bash_cv_type_uintmax_t=${bash_cv_type_uintmax_t='no'}
+bash_cv_type_unsigned_long_long=${bash_cv_type_unsigned_long_long='unsigned long long'}
+bash_cv_ulimit_maxfds=${bash_cv_ulimit_maxfds='no'}
+bash_cv_under_sys_siglist=${bash_cv_under_sys_siglist='no'}
+bash_cv_unusable_rtsigs=${bash_cv_unusable_rtsigs='no'}
+bash_cv_void_sighandler=${bash_cv_void_sighandler='yes'}
diff --git a/examples/loadables/kv.c b/examples/loadables/kv.c
new file mode 100644 (file)
index 0000000..ff912ef
--- /dev/null
@@ -0,0 +1,236 @@
+/* kv - process a series of lines containing key-value pairs and assign them
+        to an associative array. */
+
+/*
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   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/>.
+*/
+
+/* See Makefile for compilation details. */
+
+#include <config.h>
+
+#include <sys/types.h>
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+#include "bashansi.h"
+#include "posixstat.h"
+#include <stdio.h>
+#include <errno.h>
+
+#include "loadables.h"
+
+#ifndef errno
+extern int errno;
+#endif
+
+#define KV_ARRAY_DEFAULT       "KV"
+
+/* Split LINE into a key and value, with the delimiter between the key and
+   value being a member of DSTRING. A sequence of one or more delimiters
+   separates the key and value. Assign to associative array KV as if
+   KV[key]=value without expansion. This does not treat single or double
+   quotes specially, nor does it remove whitespace at the beginning or end
+   of LINE. */
+static int
+kvsplit (SHELL_VAR *v, char *line, char *dstring)
+{
+  char *key, *value;
+  size_t ind;
+
+  key = line;
+  value = 0;
+
+  ind = (line && *line) ? strcspn (key, dstring) : 0;
+
+  /* blank line or line starting with delimiter */
+  if (ind == 0 || *key == 0)
+    return 0;
+
+  if (key[ind])
+    {
+      key[ind++] = '\0';
+      value = key + ind;
+      /* skip until non-delim; this allows things like key1 = value1 where delims = " =" */
+      ind = strspn (value, dstring);
+      value += ind;
+    }
+  else
+    value = "";
+
+  return (bind_assoc_variable (v, name_cell (v), savestring (key), value, 0) != 0);
+}
+
+int
+kvfile (SHELL_VAR *v, int fd, char *delims, char *rs)
+{
+  ssize_t n;
+  char *line;
+  size_t llen;
+  int unbuffered_read, nr;
+  struct stat sb;
+
+  nr = 0;
+#ifndef __CYGWIN__
+  /* We probably don't need to worry about setting this at all; we're not
+     seeking back and forth yet. */
+  if (*rs == '\n')
+    unbuffered_read = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE);
+  else
+    unbuffered_read = (fstat (fd, &sb) != 0) || (S_ISREG (sb.st_mode) == 0);
+#else
+  unbuffered_read = 1;
+#endif
+
+  line = 0;
+  llen = 0;
+
+  zreset ();
+  while ((n = zgetline (fd, &line, &llen, *rs, unbuffered_read)) != -1)
+    {
+      QUIT;
+      if (line[n] == *rs)
+       line[n] = '\0';         /* value doesn't include the record separator */
+      nr += kvsplit (v, line, delims);
+      free (line);
+      line = 0;
+      llen = 0;
+    }
+
+  QUIT;
+  return nr;  
+}
+
+int
+kv_builtin (WORD_LIST *list)
+{
+  int opt, rval, free_delims;
+  char *array_name, *delims, *rs;
+  SHELL_VAR *v;
+
+  array_name = delims = rs = 0;
+  rval = EXECUTION_SUCCESS;
+  free_delims = 0;
+
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "A:s:d:")) != -1)
+    {
+      switch (opt)
+       {
+       case 'A':
+         array_name = list_optarg;
+         break;
+       case 's':
+         delims = list_optarg;
+         break;
+       case 'd':
+         rs = list_optarg;
+         break;
+       CASE_HELPOPT;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  if (list)
+    {
+      builtin_error ("too many arguments");
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  if (array_name == 0)
+    array_name = KV_ARRAY_DEFAULT;
+
+  if (valid_identifier (array_name) == 0)
+    {
+      sh_invalidid (array_name);
+      return (EXECUTION_FAILURE);
+    }
+
+  if (delims == 0)
+    {
+      delims = getifs ();
+      free_delims = 1;
+    }
+  if (rs == 0)
+    rs = "\n";
+
+  v = find_or_make_array_variable (array_name, 3);
+  if (v == 0 || readonly_p (v) || noassign_p (v))
+    {
+      if (v && readonly_p (v))
+       err_readonly (array_name);
+      return (EXECUTION_FAILURE);
+    }
+  else if (assoc_p (v) == 0)
+    {
+      builtin_error ("%s: not an associative array", array_name);
+      return (EXECUTION_FAILURE);
+    }
+  if (invisible_p (v))
+    VUNSETATTR (v, att_invisible);
+  assoc_flush (assoc_cell (v));
+
+  rval = kvfile (v, 0, delims, rs);
+
+  if (free_delims)
+    free (delims);     /* getifs returns allocated memory */  
+  return (rval > 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+}
+
+/* Called when builtin is enabled and loaded from the shared object.  If this
+   function returns 0, the load fails. */
+int
+kv_builtin_load (char *name)
+{
+  return (1);
+}
+
+/* Called when builtin is disabled. */
+void
+kv_builtin_unload (char *name)
+{
+}
+
+char *kv_doc[] = {
+       "Read key-value pairs into an associative array.",
+       "",
+       "Read delimiter-terminated records composed of a single key-value pair",
+       "from the standard input and add the key and corresponding value",
+       "to the associative array ARRAYNAME. The key and value are separated",
+       "by a sequence of one or more characters in SEPARATORS. Records are",
+       "terminated by the first character of RS, similar to the read and",
+       "mapfile builtins.",
+       "",
+       "If SEPARATORS is not supplied, $IFS is used to separate the keys",
+       "and values. If RS is not supplied, newlines terminate records.",
+       "If ARRAYNAME is not supplied, \"KV\" is the default array name.",
+       "",
+       "Returns success if at least one key-value pair is stored in ARRAYNAME.",
+       (char *)NULL
+};
+
+struct builtin kv_struct = {
+       "kv",                   /* builtin name */
+       kv_builtin,             /* function implementing the builtin */
+       BUILTIN_ENABLED,        /* initial flags for builtin */
+       kv_doc,         /* array of long documentation strings. */
+       "kv [-A ARRAYNAME] [-s SEPARATORS] [-d RS]",    /* usage synopsis; becomes short_doc */
+       0                       /* reserved for internal use */
+};
diff --git a/examples/loadables/strptime.c b/examples/loadables/strptime.c
new file mode 100644 (file)
index 0000000..194ca47
--- /dev/null
@@ -0,0 +1,241 @@
+/* strptime - take a date-time string and turn it into seconds since the epoch. */
+
+/* See Makefile for compilation details. */
+
+/*
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash.
+   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)
+#  include <unistd.h>
+#endif
+
+#include "bashtypes.h"
+#include "posixtime.h"
+
+#include <stdio.h>
+
+#include "builtins.h"
+#include "shell.h"
+#include "bashgetopt.h"
+#include "common.h"
+
+struct date_modifier
+{
+  char *shorthand;
+  int incr;
+};
+
+static struct date_modifier date_time_modifiers[] =
+{
+  { "now",     0 },
+  { "today",   0 },
+  { "tomorrow",        24*60*60 },
+  { "yesterday", -24*60*60 },
+  { "day after tomorrow", 48*60*60 },
+  { "two days ago", -48*60*60 },
+  { "next week", 7*24*60*60 },
+  { "last week", -7*24*60*60 },
+  { "the day after tomorrow", 48*60*60 },
+  { 0, 0 }
+};
+
+static char * const date_time_formats[] =
+{
+  "%a %b %d %T %Z %Y",         /* Unix date */
+  "%a %b %d %T %Y",            /* Wkd Mon DD HH:MM:SS YYYY */
+  "%FT%T%z",                   /* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */
+  "%FT%R%z",                   /* ISO8601 time YYYY-mm-ddTHH:MMzone */
+  "%G-%m-%dT%T%z",             /* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */
+  "%G-%m-%dT%R%z",             /* ISO8601 time YYYY-mm-ddTHH:MMzone */
+  "%G-%m-%d",                  /* ISO8601 time YYYY-mm-dd */
+  /* Can't do 8601 time zone offset with colon or fractions of a second */
+  "%a, %d %b %Y %T %Z",                /* RFC822/RFC2822 time */
+  "%a, %d %b %Y %T %z",                /* RFC822/RFC2822 time */
+  "%D %T",                     /* mm/dd/yy HH:MM:SS */
+  "%D %R",                     /* mm/dd/yy HH:MM */
+  "%D %r",                     /* mm/dd/yy HH:MM:SS a.m.  */
+  "%D %I:%M %p",               /* mm/dd/yy HH:MM p.m.  */
+  "%m/%d/%Y %T",               /* mm/dd/YYYY HH:MM:SS */
+  "%m/%d/%Y %R",               /* mm/dd/YYYY HH:MM */
+  "%m/%d/%Y %r",               /* mm/dd/YYYY HH:MM:SS a.m */
+  "%m/%d/%Y %I:%M %p",         /* mm/dd/YYYY HH:MM p.m. */
+  "%m-%d-%Y %T",               /* mm-dd-YYYY HH:MM:SS */
+  "%m-%d-%Y %R",               /* mm-dd-YYYY HH:MM */
+  "%m-%d-%Y %r",               /* mm-dd-YYYY HH:MM:SS a.m. */
+  "%m-%d-%Y %I:%M %p",         /* mm-dd-YYYY HH:MM p.m. */
+  "%Y/%m/%d %T",               /* YYYY/mm/dd HH:MM:SS */
+  "%Y/%m/%d %R",               /* YYYY/mm/dd HH:MM */
+  "%Y/%m/%d %r",               /* YYYY/mm/dd hh:MM:SS a.m. */
+  "%F %T",                     /* YYYY-mm-dd HH:MM:SS */
+  "%F %r",                     /* YYYY-mm-dd HH:MM:SS p.m. */
+  "%F %R",                     /* YYYY-mm-dd HH:MM */
+  "%F %I:%M %p",               /* YYYY-mm-dd HH:MM a.m. */
+  "%F",                                /* YYYY-mm-dd ISO8601 time */
+  "%T",                                /* HH:MM:SS */
+  "%H.%M.%S",                  /* HH.MM.SS */
+  /* From coreutils-9.2 date */
+  "%Y-%m-%dT%H:%M:%S%z",       /* ISO8601 time */
+  "%Y-%m-%dT%H%z",             /* ISO8601 time */
+  "%Y-%m-%dT%H:%M%z",          /* ISO8601 time */
+  /* RFC 3339 time */
+  "%Y-%m-%d %H:%M:%S%z",       /* RFC 3339 time */
+  "%Y-%m-%dT%H:%M:%S%z",       /* RFC 3339 time */
+  /* more oddball formats */
+  "%m.%d.%Y %T",               /* mm.dd.YYYY HH:MM:SS */
+  "%m.%d.%Y %R",               /* mm.dd.YYYY HH:MM */
+  "%m.%d.%Y %r",               /* mm.dd.YYYY HH:MM:SS a.m. */
+  "%m.%d.%Y %I:%M %p",         /* mm.dd.YYYY HH:MM p.m. */
+  "%m/%d/%Y",                  /* mm/dd/YYYY */
+  "%d %B %Y %T",               /* dd Month YYYY HH:MM:SS */
+  "%d %B %Y %R",               /* dd Month YYYY HH:MM */
+  "%d %B %Y %r",               /* dd Month YYYY HH:MM:SS a.m. */
+  "%d %B %Y %I:%M %p",         /* dd Month YYYY HH:MM p.m. */
+  "%d %b %Y %T",               /* dd Mon YYYY HH:MM:SS */
+  "%d %b %Y %R",               /* dd Mon YYYY HH:MM */
+  "%d %b %Y %r",               /* dd Mon YYYY HH:MM:SS a.m. */
+  "%d %b %Y %I:%M %p",         /* dd Mon YYYY HH:MM p.m. */
+  "%b %d, %Y %T",              /* Mon dd, YYYY HH:MM:SS */
+  "%b %d, %Y %R",              /* Mon dd, YYYY HH:MM */
+  "%b %d, %Y %r",              /* Mon dd, YYYY HH:MM:SS a.m. */
+  "%b %d, %Y %I:%M %p",                /* Mon dd, YYYY HH:MM p.m. */
+  "%m-%b-%Y",                  /* dd-Mon-YYYY */
+  "%m-%b-%Y %T",               /* dd-Mon-YYYY HH:MM:SS */
+  "%m-%b-%Y %R",               /* dd-Mon-YYYY HH:MM */
+  "%m-%b-%Y %r",               /* dd-Mon-YYYY HH:MM:SS a.m. */
+  "%m-%b-%Y %I:%M %p",         /* dd-Mon-YYYY HH:MM p.m. */
+  "%d/%b/%Y:%T %z",            /* NCSA log format dd/Mon/YYYY:HH:MM:SS zone */
+  "%d/%b/%Y:%T%z",             /* NCSA log format dd/Mon/YYYY:HH:MM:SSzone */
+  /* No delimiters */
+  "%Y%m%d %T",                 /* YYYYMMDD HH:MM:SS */
+  "%Y%m%d %R",                 /* YYYYMMDD HH:MM */
+  "%Y%m%d %r",                 /* YYYYMMDD HH:MM:SS a.m. */
+  "%Y%m%d %I:%M %p",           /* YYYYMMDD HH:MM p.m. */
+  "%Y%m%d %H:%M:%S%z",         /* YYYYMMDD HH:MM:SSzone */
+  "%Y%m%dT%H:%M:%S%z",         /* YYYYMMDDTHH:MM:SSzone */
+  "%Y%m%dT%T",                 /* YYYYMMDDTHH:MM:SS */
+  "%Y%m%dT%R",                 /* YYYYMMDDTHH:MM */
+  /* Non-US formats */
+  "%d-%m-%Y",                  /* dd-mm-YYYY */
+  "%d-%m-%Y %T",               /* dd-mm-YYYY HH:MM:SS */
+  "%d-%m-%Y %R",               /* dd-mm-YYYY HH:MM */
+  "%d-%m-%Y %r",               /* dd-mm-YYYY HH:MM:SS a.m. */    
+  "%d-%m-%Y %I:%M %p",         /* dd-mm-YYYY HH:MM p.m. */
+  "%d/%m/%Y %T",               /* dd/mm/YYYY HH:MM:SS */
+  "%d/%m/%Y %R",               /* dd/mm/YYYY HH:MM */
+  "%d/%m/%Y %r",               /* dd/mm/YYYY HH:MM:SS a.m. */
+  "%d/%m/%Y %I:%M %p",         /* dd/mm/YYYY HH:MM p.m. */
+  "%Y-%d-%m %T",               /* YYYY-dd-mm HH:MM:SS */
+  "%Y-%d-%m %R",               /* YYYY-dd-mm HH:MM */
+  "%d-%m-%Y %T",               /* dd-mm-YYYY HH:MM:SS */
+  "%d-%m-%Y %R",               /* dd-mm-YYYY HH:MM */
+  "%d-%m-%Y %r",               /* dd-mm-YYYY HH:MM:SS a.m. */
+  "%d-%m-%Y %I:%M %p",         /* dd-mm-YYYY HH:MM p.m. */
+  "%d.%m.%Y %T",               /* dd.mm.YYYY HH:MM:SS */
+  "%d.%m.%Y %R",               /* dd.mm.YYYY HH:MM */
+  "%d.%m.%Y %r",               /* dd.mm.YYYY HH:MM:SS a.m. */    
+  "%d.%m.%Y %I:%M %p",         /* dd.mm.YYYY HH:MM p.m. */
+  0
+};
+
+static void
+inittime (time_t *clock, struct tm *timeptr)
+{
+  timeptr = localtime (clock);         /* for now */
+
+  /* but default to midnight */
+  timeptr->tm_hour = timeptr->tm_min = timeptr->tm_sec = 0;
+  /* and let the system figure out the right DST offset */
+  timeptr->tm_isdst = -1;
+}
+
+int
+strptime_builtin (WORD_LIST *list)
+{
+  char *s;
+  struct tm t, *tm;
+  time_t now, secs;
+  char *datestr;
+  int i;
+
+  if (no_options (list))       /* for now */
+    return (EX_USAGE);
+
+  list = loptend;
+  if (list == 0)
+    {
+      builtin_usage ();
+     return (EX_USAGE);
+    }
+
+  datestr = string_list (list);
+  if (datestr == 0 || *datestr == 0)
+    return (EXECUTION_SUCCESS);
+
+  now = getnow ();
+  secs = -1;
+  for (i = 0; date_time_modifiers[i].shorthand; i++)
+    {
+      if (STREQ (datestr, date_time_modifiers[i].shorthand))
+       {
+         secs = now + date_time_modifiers[i].incr;
+         break;
+       }
+    }
+
+  if (secs == -1)
+    {
+      /* init struct tm */
+      inittime (&now, tm);
+      t = *tm;
+      for (i = 0; date_time_formats[i]; i++)
+        {
+         s = strptime (datestr, date_time_formats[i], &t);
+         if (s == 0)
+           continue;
+         /* skip extra characters at the end for now */
+         secs = mktime (&t);
+         break;
+        }
+    }
+
+  printf ("%ld\n", secs);    
+  return (EXECUTION_SUCCESS);
+}
+
+char *strptime_doc[] = {
+       "Convert a date-time string to seconds since the epoch.",
+       "",
+       "Take DATE-TIME, a date-time string, parse it against a set of common",
+       "date-time formats. If the string matches one of the formats, convert",
+       "it into seconds since the epoch and display the result.",
+       (char *)NULL
+};
+
+/* The standard structure describing a builtin command.  bash keeps an array
+   of these structures.  The flags must include BUILTIN_ENABLED so the
+   builtin can be used. */
+struct builtin strptime_struct = {
+       "strptime",             /* builtin name */
+       strptime_builtin,               /* function implementing the builtin */
+       BUILTIN_ENABLED,        /* initial flags for builtin */
+       strptime_doc,           /* array of long documentation strings. */
+       "strptime date-time",   /* usage synopsis; becomes short_doc */
+       0                       /* reserved for internal use */
+};
diff --git a/examples/scripts/secure-script b/examples/scripts/secure-script
new file mode 100644 (file)
index 0000000..e26802a
--- /dev/null
@@ -0,0 +1,31 @@
+# if we are worried somehow about inheriting a function for unset or exec,
+# set posix mode, then unset it later
+POSIXLY_CORRECT=1
+
+# make sure to run with bash -p to prevent inheriting functions. you can
+# do this (if the script does not need to run setuid) or use the
+# POSIXLY_CORRECT setting above (as long as you run set +o posix as done below)
+#case $SHELLOPTS in
+#*privileged*) ;;
+#*)    \exec /bin/bash -p $0 "$@" ;;
+#esac
+
+# unset is a special builtin and will be found before functions; quoting it
+# will prevent alias expansion
+# add any other shell builtins you're concerned about
+\unset -f command builtin unset shopt set unalias hash
+\unset -f read true exit echo printf
+
+# remove all aliases and disable alias expansion
+\unalias -a
+\shopt -u expand_aliases
+
+# and make sure we're no longer running in posix mode
+set +o posix
+
+# get rid of any hashed commands
+hash -r
+
+# if you're concerned about PATH spoofing, make sure to have a path that
+# will find the standard utilities
+#PATH=$(command getconf -p getconf PATH):$PATH
diff --git a/include/intprops-internal.h b/include/intprops-internal.h
new file mode 100644 (file)
index 0000000..c8a87d2
--- /dev/null
@@ -0,0 +1,397 @@
+/* intprops-internal.h -- properties of integer types not visible to users
+
+   Copyright (C) 2001-2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify it
+   under the terms of the GNU Lesser General Public License as published
+   by the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GL_INTPROPS_INTERNAL_H
+#define _GL_INTPROPS_INTERNAL_H
+
+#include <limits.h>
+
+/* Pacify GCC 13.2 in some calls to _GL_EXPR_SIGNED.  */
+#if defined __GNUC__ && 4 < __GNUC__ + (3 <= __GNUC_MINOR__)
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+   Do not evaluate E.  */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+   <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>.  */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* The extra casts in the following macros work around compiler bugs,
+   e.g., in Cray C 5.0.3.0.  */
+
+/* True if the real type T is signed.  */
+#define _GL_TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+
+/* Return 1 if the real expression E, after promotion, has a
+   signed or floating type.  Do not evaluate E.  */
+#define _GL_EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+
+/* Minimum and maximum values for integer types and expressions.  */
+
+/* The width in bits of the integer type or expression T.
+   Do not evaluate T.  T must not be a bit-field expression.
+   Padding bits are not supported; this is checked at compile-time below.  */
+#define _GL_TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* The maximum and minimum values for the type of the expression E,
+   after integer promotion.  E is not evaluated.  */
+#define _GL_INT_MINIMUM(e)                                              \
+  (_GL_EXPR_SIGNED (e)                                                  \
+   ? ~ _GL_SIGNED_INT_MAXIMUM (e)                                       \
+   : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e)                                              \
+  (_GL_EXPR_SIGNED (e)                                                  \
+   ? _GL_SIGNED_INT_MAXIMUM (e)                                         \
+   : _GL_INT_NEGATE_CONVERT (e, 1))
+#define _GL_SIGNED_INT_MAXIMUM(e)                                       \
+  (((_GL_INT_CONVERT (e, 1) << (_GL_TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* Work around OpenVMS incompatibility with C99.  */
+#if !defined LLONG_MAX && defined __INT64_MAX
+# define LLONG_MAX __INT64_MAX
+# define LLONG_MIN __INT64_MIN
+#endif
+
+/* This include file assumes that signed types are two's complement without
+   padding bits; the above macros have undefined behavior otherwise.
+   If this is a problem for you, please let us know how to fix it for your host.
+   This assumption is tested by the intprops-tests module.  */
+
+/* Does the __typeof__ keyword work?  This could be done by
+   'configure', but for now it's easier to do it by hand.  */
+#if (2 <= __GNUC__ \
+     || (4 <= __clang_major__) \
+     || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+     || (0x5110 <= __SUNPRO_C && !__STDC__))
+# define _GL_HAVE___TYPEOF__ 1
+#else
+# define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed.  Return 0
+   if it is definitely unsigned.  T must not be a bit-field expression.
+   This macro does not evaluate its argument, and expands to an
+   integer constant expression.  */
+#if _GL_HAVE___TYPEOF__
+# define _GL_SIGNED_TYPE_OR_EXPR(t) _GL_TYPE_SIGNED (__typeof__ (t))
+#else
+# define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return 1 if - A would overflow in [MIN,MAX] arithmetic.
+   A should not have side effects, and A's type should be an
+   integer with minimum value MIN and maximum MAX.  */
+#define _GL_INT_NEGATE_RANGE_OVERFLOW(a, min, max) \
+  ((min) < 0 ? (a) < - (max) : 0 < (a))
+
+/* True if __builtin_add_overflow (A, B, P) and __builtin_sub_overflow
+   (A, B, P) work when P is non-null.  */
+#ifdef __EDG__
+/* EDG-based compilers like nvc 22.1 cannot add 64-bit signed to unsigned
+   <https://bugs.gnu.org/53256>.  */
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 0
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW __has_builtin (__builtin_add_overflow)
+/* __builtin_{add,sub}_overflow exists but is not reliable in GCC 5.x and 6.x,
+   see <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98269>.  */
+#elif 7 <= __GNUC__
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 1
+#else
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 0
+#endif
+
+/* True if __builtin_mul_overflow (A, B, P) works when P is non-null.  */
+#if defined __clang_major__ && __clang_major__ < 14
+/* Work around Clang bug <https://bugs.llvm.org/show_bug.cgi?id=16404>.  */
+# define _GL_HAS_BUILTIN_MUL_OVERFLOW 0
+#else
+# define _GL_HAS_BUILTIN_MUL_OVERFLOW _GL_HAS_BUILTIN_ADD_OVERFLOW
+#endif
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+   __builtin_sub_overflow_p and __builtin_mul_overflow_p.  */
+#ifdef __EDG__
+/* In EDG-based compilers like ICC 2021.3 and earlier,
+   __builtin_add_overflow_p etc. are not treated as integral constant
+   expressions even when all arguments are.  */
+# define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+# define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+#if (!defined _GL_STDCKDINT_H && 202311 <= __STDC_VERSION__ \
+     && ! (_GL_HAS_BUILTIN_ADD_OVERFLOW && _GL_HAS_BUILTIN_MUL_OVERFLOW))
+# include <stdckdint.h>
+#endif
+
+/* Store the low-order bits of A + B, A - B, A * B, respectively, into *R.
+   Return 1 if the result overflows.  Arguments should not have side
+   effects and A, B and *R can be of any integer type other than char,
+   bool, a bit-precise integer type, or an enumeration type.  */
+#if _GL_HAS_BUILTIN_ADD_OVERFLOW
+# define _GL_INT_ADD_WRAPV(a, b, r) __builtin_add_overflow (a, b, r)
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) __builtin_sub_overflow (a, b, r)
+#elif defined ckd_add && defined ckd_sub && !defined _GL_STDCKDINT_H
+# define _GL_INT_ADD_WRAPV(a, b, r) ckd_add (r, + (a), + (b))
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) ckd_sub (r, + (a), + (b))
+#else
+# define _GL_INT_ADD_WRAPV(a, b, r) \
+   _GL_INT_OP_WRAPV (a, b, r, +, _GL_INT_ADD_RANGE_OVERFLOW)
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) \
+   _GL_INT_OP_WRAPV (a, b, r, -, _GL_INT_SUBTRACT_RANGE_OVERFLOW)
+#endif
+#if _GL_HAS_BUILTIN_MUL_OVERFLOW
+# if ((9 < __GNUC__ + (3 <= __GNUC_MINOR__) \
+       || (__GNUC__ == 8 && 4 <= __GNUC_MINOR__)) \
+      && !defined __EDG__)
+#  define _GL_INT_MULTIPLY_WRAPV(a, b, r) __builtin_mul_overflow (a, b, r)
+# else
+   /* Work around GCC bug 91450.  */
+#  define _GL_INT_MULTIPLY_WRAPV(a, b, r) \
+    ((!_GL_SIGNED_TYPE_OR_EXPR (*(r)) && _GL_EXPR_SIGNED (a) && _GL_EXPR_SIGNED (b) \
+      && _GL_INT_MULTIPLY_RANGE_OVERFLOW (a, b, 0, (__typeof__ (*(r))) -1)) \
+     ? ((void) __builtin_mul_overflow (a, b, r), 1) \
+     : __builtin_mul_overflow (a, b, r))
+# endif
+#elif defined ckd_mul && !defined _GL_STDCKDINT_H
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) ckd_mul (r, + (a), + (b))
+#else
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) \
+   _GL_INT_OP_WRAPV (a, b, r, *, _GL_INT_MULTIPLY_RANGE_OVERFLOW)
+#endif
+
+/* Nonzero if this compiler has GCC bug 68193 or Clang bug 25390.  See:
+   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68193
+   https://llvm.org/bugs/show_bug.cgi?id=25390
+   For now, assume all versions of GCC-like compilers generate bogus
+   warnings for _Generic.  This matters only for compilers that
+   lack relevant builtins.  */
+#if __GNUC__ || defined __clang__
+# define _GL__GENERIC_BOGUS 1
+#else
+# define _GL__GENERIC_BOGUS 0
+#endif
+
+/* Store the low-order bits of A <op> B into *R, where OP specifies
+   the operation and OVERFLOW the overflow predicate.  Return 1 if the
+   result overflows.  Arguments should not have side effects,
+   and A, B and *R can be of any integer type other than char, bool, a
+   bit-precise integer type, or an enumeration type.  */
+#if 201112 <= __STDC_VERSION__ && !_GL__GENERIC_BOGUS
+# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
+   (_Generic \
+    (*(r), \
+     signed char: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        signed char, SCHAR_MIN, SCHAR_MAX), \
+     unsigned char: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        unsigned char, 0, UCHAR_MAX), \
+     short int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        short int, SHRT_MIN, SHRT_MAX), \
+     unsigned short int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        unsigned short int, 0, USHRT_MAX), \
+     int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        int, INT_MIN, INT_MAX), \
+     unsigned int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                        unsigned int, 0, UINT_MAX), \
+     long int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                        long int, LONG_MIN, LONG_MAX), \
+     unsigned long int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                        unsigned long int, 0, ULONG_MAX), \
+     long long int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+                        long long int, LLONG_MIN, LLONG_MAX), \
+     unsigned long long int: \
+       _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+                        unsigned long long int, 0, ULLONG_MAX)))
+#else
+/* Store the low-order bits of A <op> B into *R, where OP specifies
+   the operation and OVERFLOW the overflow predicate.  If *R is
+   signed, its type is ST with bounds SMIN..SMAX; otherwise its type
+   is UT with bounds U..UMAX.  ST and UT are narrower than int.
+   Return 1 if the result overflows.  Arguments should not have side
+   effects, and A, B and *R can be of any integer type other than
+   char, bool, a bit-precise integer type, or an enumeration type.  */
+# if _GL_HAVE___TYPEOF__
+#  define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
+    (_GL_TYPE_SIGNED (__typeof__ (*(r))) \
+     ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, st, smin, smax) \
+     : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, ut, 0, umax))
+# else
+#  define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
+    (overflow (a, b, smin, smax) \
+     ? (overflow (a, b, 0, umax) \
+        ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 1) \
+        : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) < 0) \
+     : (overflow (a, b, 0, umax) \
+        ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) >= 0 \
+        : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 0)))
+# endif
+
+# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
+   (sizeof *(r) == sizeof (signed char) \
+    ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
+                                 signed char, SCHAR_MIN, SCHAR_MAX, \
+                                 unsigned char, UCHAR_MAX) \
+    : sizeof *(r) == sizeof (short int) \
+    ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
+                                 short int, SHRT_MIN, SHRT_MAX, \
+                                 unsigned short int, USHRT_MAX) \
+    : sizeof *(r) == sizeof (int) \
+    ? (_GL_EXPR_SIGNED (*(r)) \
+       ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                          int, INT_MIN, INT_MAX) \
+       : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+                          unsigned int, 0, UINT_MAX)) \
+    : _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow))
+# ifdef LLONG_MAX
+#  define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
+    (sizeof *(r) == sizeof (long int) \
+     ? (_GL_EXPR_SIGNED (*(r)) \
+        ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                           long int, LONG_MIN, LONG_MAX) \
+        : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                           unsigned long int, 0, ULONG_MAX)) \
+     : (_GL_EXPR_SIGNED (*(r)) \
+        ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+                           long long int, LLONG_MIN, LLONG_MAX) \
+        : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+                           unsigned long long int, 0, ULLONG_MAX)))
+# else
+#  define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
+    (_GL_EXPR_SIGNED (*(r)) \
+     ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                        long int, LONG_MIN, LONG_MAX) \
+     : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+                        unsigned long int, 0, ULONG_MAX))
+# endif
+#endif
+
+/* Store the low-order bits of A <op> B into *R, where the operation
+   is given by OP.  Use the unsigned type UT for calculation to avoid
+   overflow problems.  *R's type is T, with extrema TMIN and TMAX.
+   T can be any signed integer type other than char, bool, a
+   bit-precise integer type, or an enumeration type.
+   Return 1 if the result overflows.  */
+#define _GL_INT_OP_CALC(a, b, r, op, overflow, ut, t, tmin, tmax) \
+  (overflow (a, b, tmin, tmax) \
+   ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 1) \
+   : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 0))
+
+/* Return 1 if the integer expressions A - B and -A would overflow,
+   respectively.  Arguments should not have side effects,
+   and can be any signed integer type other than char, bool, a
+   bit-precise integer type, or an enumeration type.
+   These macros are tuned for their last input argument being a constant.  */
+
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+# define _GL_INT_NEGATE_OVERFLOW(a) \
+   __builtin_sub_overflow_p (0, a, (__typeof__ (- (a))) 0)
+#else
+# define _GL_INT_NEGATE_OVERFLOW(a) \
+   _GL_INT_NEGATE_RANGE_OVERFLOW (a, _GL_INT_MINIMUM (a), _GL_INT_MAXIMUM (a))
+#endif
+
+/* Return the low-order bits of A <op> B, where the operation is given
+   by OP.  Use the unsigned type UT for calculation to avoid undefined
+   behavior on signed integer overflow, and convert the result to type T.
+   UT is at least as wide as T and is no narrower than unsigned int,
+   T is two's complement, and there is no padding or trap representations.
+   Assume that converting UT to T yields the low-order bits, as is
+   done in all known two's-complement C compilers.  E.g., see:
+   https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html
+
+   According to the C standard, converting UT to T yields an
+   implementation-defined result or signal for values outside T's
+   range.  However, code that works around this theoretical problem
+   runs afoul of a compiler bug in Oracle Studio 12.3 x86.  See:
+   https://lists.gnu.org/r/bug-gnulib/2017-04/msg00049.html
+   As the compiler bug is real, don't try to work around the
+   theoretical problem.  */
+
+#define _GL_INT_OP_WRAPV_VIA_UNSIGNED(a, b, op, ut, t) \
+  ((t) ((ut) (a) op (ut) (b)))
+
+/* Return true if the numeric values A + B, A - B, A * B fall outside
+   the range TMIN..TMAX.  Arguments should not have side effects
+   and can be any integer type other than char, bool,
+   a bit-precise integer type, or an enumeration type.
+   TMIN should be signed and nonpositive.
+   TMAX should be positive, and should be signed unless TMIN is zero.  */
+#define _GL_INT_ADD_RANGE_OVERFLOW(a, b, tmin, tmax) \
+  ((b) < 0 \
+   ? (((tmin) \
+       ? ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (a, (tmin) - (b))) || (b) < (tmin)) \
+          && (a) < (tmin) - (b)) \
+       : (a) <= -1 - (b)) \
+      || ((_GL_EXPR_SIGNED (a) ? 0 <= (a) : (tmax) < (a)) && (tmax) < (a) + (b))) \
+   : (a) < 0 \
+   ? (((tmin) \
+       ? ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (b, (tmin) - (a))) || (a) < (tmin)) \
+          && (b) < (tmin) - (a)) \
+       : (b) <= -1 - (a)) \
+      || ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (a, b)) || (tmax) < (b)) \
+          && (tmax) < (a) + (b))) \
+   : (tmax) < (b) || (tmax) - (b) < (a))
+#define _GL_INT_SUBTRACT_RANGE_OVERFLOW(a, b, tmin, tmax) \
+  (((a) < 0) == ((b) < 0) \
+   ? ((a) < (b) \
+      ? !(tmin) || -1 - (tmin) < (b) - (a) - 1 \
+      : (tmax) < (a) - (b)) \
+   : (a) < 0 \
+   ? ((!_GL_EXPR_SIGNED (_GL_INT_CONVERT ((a) - (tmin), b)) && (a) - (tmin) < 0) \
+      || (a) - (tmin) < (b)) \
+   : ((! (_GL_EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
+          && _GL_EXPR_SIGNED (_GL_INT_CONVERT ((tmax) + (b), a))) \
+       && (tmax) <= -1 - (b)) \
+      || (tmax) + (b) < (a)))
+#define _GL_INT_MULTIPLY_RANGE_OVERFLOW(a, b, tmin, tmax) \
+  ((b) < 0 \
+   ? ((a) < 0 \
+      ? (_GL_EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
+         ? (a) < (tmax) / (b) \
+         : ((_GL_INT_NEGATE_OVERFLOW (b) \
+             ? _GL_INT_CONVERT (b, tmax) >> (_GL_TYPE_WIDTH (+ (b)) - 1) \
+             : (tmax) / -(b)) \
+            <= -1 - (a))) \
+      : _GL_INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (b, tmin)) && (b) == -1 \
+      ? (_GL_EXPR_SIGNED (a) \
+         ? 0 < (a) + (tmin) \
+         : 0 < (a) && -1 - (tmin) < (a) - 1) \
+      : (tmin) / (b) < (a)) \
+   : (b) == 0 \
+   ? 0 \
+   : ((a) < 0 \
+      ? (_GL_INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (a, tmin)) && (a) == -1 \
+         ? (_GL_EXPR_SIGNED (b) ? 0 < (b) + (tmin) : -1 - (tmin) < (b) - 1) \
+         : (tmin) / (a) < (b)) \
+      : (tmax) / (b) < (a)))
+
+#endif /* _GL_INTPROPS_INTERNAL_H */
diff --git a/include/stdckdint.in.h b/include/stdckdint.in.h
new file mode 100644 (file)
index 0000000..9184880
--- /dev/null
@@ -0,0 +1,35 @@
+/* stdckdint.h -- checked integer arithmetic
+
+   Copyright 2022-2024 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify it
+   under the terms of the GNU Lesser General Public License as published
+   by the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GL_STDCKDINT_H
+#define _GL_STDCKDINT_H
+
+#include "intprops-internal.h"
+
+/* Store into *R the low-order bits of A + B, A - B, A * B, respectively.
+   Return 1 if the result overflows, 0 otherwise.
+   A, B, and *R can have any integer type other than char, bool, a
+   bit-precise integer type, or an enumeration type.
+
+   These are like the standard macros introduced in C23, except that
+   arguments should not have side effects.  */
+
+#define ckd_add(r, a, b) ((bool) _GL_INT_ADD_WRAPV (a, b, r))
+#define ckd_sub(r, a, b) ((bool) _GL_INT_SUBTRACT_WRAPV (a, b, r))
+#define ckd_mul(r, a, b) ((bool) _GL_INT_MULTIPLY_WRAPV (a, b, r))
+
+#endif /* _GL_STDCKDINT_H */
diff --git a/include/unlocked-io.h b/include/unlocked-io.h
new file mode 100644 (file)
index 0000000..659e798
--- /dev/null
@@ -0,0 +1,138 @@
+/* Prefer faster, non-thread-safe stdio functions if available.
+
+   Copyright (C) 2001-2004, 2009-2024 Free Software Foundation, Inc.
+
+   This program 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.
+
+   This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Jim Meyering.  */
+
+/* Adapted from gnulib:lib/unlocked-io.h */
+
+#ifndef UNLOCKED_IO_H
+# define UNLOCKED_IO_H 1
+
+/* These are wrappers for functions/macros from the GNU C library, and
+   from other C libraries supporting POSIX's optional thread-safe functions.
+
+   The standard I/O functions are thread-safe.  These *_unlocked ones are
+   more efficient but not thread-safe.  That they're not thread-safe is
+   fine since all of the applications in this package are single threaded.
+
+   Also, some code that is shared with the GNU C library may invoke
+   the *_unlocked functions directly.  On hosts that lack those
+   functions, invoke the non-thread-safe versions instead.  */
+
+# include <stdio.h>
+
+# if HAVE_DECL_CLEARERR_UNLOCKED || defined clearerr_unlocked
+#  undef clearerr
+#  define clearerr(x) clearerr_unlocked (x)
+# else
+#  define clearerr_unlocked(x) clearerr (x)
+# endif
+
+# if HAVE_DECL_FEOF_UNLOCKED || defined feof_unlocked
+#  undef feof
+#  define feof(x) feof_unlocked (x)
+# else
+#  define feof_unlocked(x) feof (x)
+# endif
+
+# if HAVE_DECL_FERROR_UNLOCKED || defined ferror_unlocked
+#  undef ferror
+#  define ferror(x) ferror_unlocked (x)
+# else
+#  define ferror_unlocked(x) ferror (x)
+# endif
+
+# if HAVE_DECL_FFLUSH_UNLOCKED || defined fflush_unlocked
+#  undef fflush
+#  define fflush(x) fflush_unlocked (x)
+# else
+#  define fflush_unlocked(x) fflush (x)
+# endif
+
+# if HAVE_DECL_FGETS_UNLOCKED || defined fgets_unlocked
+#  undef fgets
+#  define fgets(x,y,z) fgets_unlocked (x,y,z)
+# else
+#  define fgets_unlocked(x,y,z) fgets (x,y,z)
+# endif
+
+# if HAVE_DECL_FPUTC_UNLOCKED || defined fputc_unlocked
+#  undef fputc
+#  define fputc(x,y) fputc_unlocked (x,y)
+# else
+#  define fputc_unlocked(x,y) fputc (x,y)
+# endif
+
+# if HAVE_DECL_FPUTS_UNLOCKED || defined fputs_unlocked
+#  undef fputs
+#  define fputs(x,y) fputs_unlocked (x,y)
+# else
+#  define fputs_unlocked(x,y) fputs (x,y)
+# endif
+
+# if HAVE_DECL_FREAD_UNLOCKED || defined fread_unlocked
+#  undef fread
+#  define fread(w,x,y,z) fread_unlocked (w,x,y,z)
+# else
+#  define fread_unlocked(w,x,y,z) fread (w,x,y,z)
+# endif
+
+# if HAVE_DECL_FWRITE_UNLOCKED || defined fwrite_unlocked
+#  undef fwrite
+#  define fwrite(w,x,y,z) fwrite_unlocked (w,x,y,z)
+# else
+#  define fwrite_unlocked(w,x,y,z) fwrite (w,x,y,z)
+# endif
+
+# if HAVE_DECL_GETC_UNLOCKED || defined getc_unlocked
+#  undef getc
+#  define getc(x) getc_unlocked (x)
+# else
+#  define getc_unlocked(x) getc (x)
+# endif
+
+# if HAVE_DECL_GETCHAR_UNLOCKED || defined getchar_unlocked
+#  undef getchar
+#  define getchar() getchar_unlocked ()
+# else
+#  define getchar_unlocked() getchar ()
+# endif
+
+# if HAVE_DECL_PUTC_UNLOCKED || defined putc_unlocked
+#  undef putc
+#  define putc(x,y) putc_unlocked (x,y)
+# else
+#  define putc_unlocked(x,y) putc (x,y)
+# endif
+
+# if HAVE_DECL_PUTCHAR_UNLOCKED || defined putchar_unlocked
+#  undef putchar
+#  define putchar(x) putchar_unlocked (x)
+# else
+#  define putchar_unlocked(x) putchar (x)
+# endif
+
+# undef flockfile
+# define flockfile(x) ((void) 0)
+
+# undef ftrylockfile
+# define ftrylockfile(x) 0
+
+# undef funlockfile
+# define funlockfile(x) ((void) 0)
+
+#endif /* UNLOCKED_IO_H */
diff --git a/lib/intl/arg-nonnull.h b/lib/intl/arg-nonnull.h
new file mode 100644 (file)
index 0000000..0fa737f
--- /dev/null
@@ -0,0 +1,26 @@
+/* A C macro for declaring that specific arguments must not be NULL.
+   Copyright (C) 2009-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* _GL_ARG_NONNULL((n,...,m)) tells the compiler and static analyzer tools
+   that the values passed as arguments n, ..., m must be non-NULL pointers.
+   n = 1 stands for the first argument, n = 2 for the second argument etc.  */
+#ifndef _GL_ARG_NONNULL
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || defined __clang__
+#  define _GL_ARG_NONNULL(params) __attribute__ ((__nonnull__ params))
+# else
+#  define _GL_ARG_NONNULL(params)
+# endif
+#endif
diff --git a/lib/intl/attribute.h b/lib/intl/attribute.h
new file mode 100644 (file)
index 0000000..45b953c
--- /dev/null
@@ -0,0 +1,226 @@
+/* ATTRIBUTE_* macros for using attributes in GCC and similar compilers
+
+   Copyright 2020-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert.  */
+
+/* Provide public ATTRIBUTE_* names for the private _GL_ATTRIBUTE_*
+   macros used within Gnulib.  */
+
+/* These attributes can be placed in two ways:
+     - At the start of a declaration (i.e. even before storage-class
+       specifiers!); then they apply to all entities that are declared
+       by the declaration.
+     - Immediately after the name of an entity being declared by the
+       declaration; then they apply to that entity only.  */
+
+#ifndef _GL_ATTRIBUTE_H
+#define _GL_ATTRIBUTE_H
+
+
+/* This file defines two types of attributes:
+   * C2x standard attributes.  These have macro names that do not begin with
+     'ATTRIBUTE_'.
+   * Selected GCC attributes; see:
+     https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
+     https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html
+     https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html
+     These names begin with 'ATTRIBUTE_' to avoid name clashes.  */
+
+
+/* =============== Attributes for specific kinds of functions =============== */
+
+/* Attributes for functions that should not be used.  */
+
+/* Warn if the entity is used.  */
+/* Applies to:
+     - function, variable,
+     - struct, union, struct/union member,
+     - enumeration, enumeration item,
+     - typedef,
+   in C++ also: namespace, class, template specialization.  */
+#define DEPRECATED _GL_ATTRIBUTE_DEPRECATED
+
+/* If a function call is not optimized way, warn with MSG.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_WARNING(msg) _GL_ATTRIBUTE_WARNING (msg)
+
+/* If a function call is not optimized way, report an error with MSG.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_ERROR(msg) _GL_ATTRIBUTE_ERROR (msg)
+
+
+/* Attributes for memory-allocating functions.  */
+
+/* The function returns a pointer to freshly allocated memory.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_MALLOC _GL_ATTRIBUTE_MALLOC
+
+/* ATTRIBUTE_ALLOC_SIZE ((N)) - The Nth argument of the function
+   is the size of the returned memory block.
+   ATTRIBUTE_ALLOC_SIZE ((M, N)) - Multiply the Mth and Nth arguments
+   to determine the size of the returned memory block.  */
+/* Applies to: function, pointer to function, function types.  */
+#define ATTRIBUTE_ALLOC_SIZE(args) _GL_ATTRIBUTE_ALLOC_SIZE (args)
+
+/* ATTRIBUTE_DEALLOC (F, I) declares that the function returns pointers
+   that can be freed by passing them as the Ith argument to the
+   function F.
+   ATTRIBUTE_DEALLOC_FREE declares that the function returns pointers that
+   can be freed via 'free'; it can be used only after declaring 'free'.  */
+/* Applies to: functions.  Cannot be used on inline functions.  */
+#define ATTRIBUTE_DEALLOC(f, i) _GL_ATTRIBUTE_DEALLOC(f, i)
+#define ATTRIBUTE_DEALLOC_FREE _GL_ATTRIBUTE_DEALLOC_FREE
+
+/* Attributes for variadic functions.  */
+
+/* The variadic function expects a trailing NULL argument.
+   ATTRIBUTE_SENTINEL () - The last argument is NULL (requires C99).
+   ATTRIBUTE_SENTINEL ((N)) - The (N+1)st argument from the end is NULL.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_SENTINEL(pos) _GL_ATTRIBUTE_SENTINEL (pos)
+
+
+/* ================== Attributes for compiler diagnostics ================== */
+
+/* Attributes that help the compiler diagnose programmer mistakes.
+   Some of them may also help for some compiler optimizations.  */
+
+/* ATTRIBUTE_FORMAT ((ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)) -
+   The STRING-INDEXth function argument is a format string of style
+   ARCHETYPE, which is one of:
+     printf, gnu_printf
+     scanf, gnu_scanf,
+     strftime, gnu_strftime,
+     strfmon,
+   or the same thing prefixed and suffixed with '__'.
+   If FIRST-TO-CHECK is not 0, arguments starting at FIRST-TO_CHECK
+   are suitable for the format string.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_FORMAT(spec) _GL_ATTRIBUTE_FORMAT (spec)
+
+/* ATTRIBUTE_NONNULL ((N1, N2,...)) - Arguments N1, N2,... must not be NULL.
+   ATTRIBUTE_NONNULL () - All pointer arguments must not be null.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_NONNULL(args) _GL_ATTRIBUTE_NONNULL (args)
+
+/* The function's return value is a non-NULL pointer.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_RETURNS_NONNULL _GL_ATTRIBUTE_RETURNS_NONNULL
+
+/* Warn if the caller does not use the return value,
+   unless the caller uses something like ignore_value.  */
+/* Applies to: function, enumeration, class.  */
+#define NODISCARD _GL_ATTRIBUTE_NODISCARD
+
+
+/* Attributes that disable false alarms when the compiler diagnoses
+   programmer "mistakes".  */
+
+/* Do not warn if the entity is not used.  */
+/* Applies to:
+     - function, variable,
+     - struct, union, struct/union member,
+     - enumeration, enumeration item,
+     - typedef,
+   in C++ also: class.  */
+#define MAYBE_UNUSED _GL_ATTRIBUTE_MAYBE_UNUSED
+
+/* The contents of a character array is not meant to be NUL-terminated.  */
+/* Applies to: struct/union members and variables that are arrays of element
+   type '[[un]signed] char'.  */
+#define ATTRIBUTE_NONSTRING _GL_ATTRIBUTE_NONSTRING
+
+/* Do not warn if control flow falls through to the immediately
+   following 'case' or 'default' label.  */
+/* Applies to: Empty statement (;), inside a 'switch' statement.  */
+#define FALLTHROUGH _GL_ATTRIBUTE_FALLTHROUGH
+
+
+/* ================== Attributes for debugging information ================== */
+
+/* Attributes regarding debugging information emitted by the compiler.  */
+
+/* Omit the function from stack traces when debugging.  */
+/* Applies to: function.  */
+#define ATTRIBUTE_ARTIFICIAL _GL_ATTRIBUTE_ARTIFICIAL
+
+/* Make the entity visible to debuggers etc., even with '-fwhole-program'.  */
+/* Applies to: functions, variables.  */
+#define ATTRIBUTE_EXTERNALLY_VISIBLE _GL_ATTRIBUTE_EXTERNALLY_VISIBLE
+
+
+/* ========== Attributes that mainly direct compiler optimizations ========== */
+
+/* The function does not throw exceptions.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_NOTHROW _GL_ATTRIBUTE_NOTHROW
+
+/* Do not inline the function.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_NOINLINE _GL_ATTRIBUTE_NOINLINE
+
+/* Always inline the function, and report an error if the compiler
+   cannot inline.  */
+/* Applies to: function.  */
+#define ATTRIBUTE_ALWAYS_INLINE _GL_ATTRIBUTE_ALWAYS_INLINE
+
+/* It is OK for a compiler to omit duplicate calls with the same arguments.
+   This attribute is safe for a function that neither depends on
+   nor affects observable state, and always returns exactly once -
+   e.g., does not loop forever, and does not call longjmp.
+   (This attribute is stricter than ATTRIBUTE_PURE.)  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_CONST _GL_ATTRIBUTE_CONST
+
+/* It is OK for a compiler to omit duplicate calls with the same
+   arguments if observable state is not changed between calls.
+   This attribute is safe for a function that does not affect
+   observable state, and always returns exactly once.
+   (This attribute is looser than ATTRIBUTE_CONST.)  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_PURE _GL_ATTRIBUTE_PURE
+
+/* The function is rarely executed.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_COLD _GL_ATTRIBUTE_COLD
+
+/* If called from some other compilation unit, the function executes
+   code from that unit only by return or by exception handling,
+   letting the compiler optimize that unit more aggressively.  */
+/* Applies to: functions.  */
+#define ATTRIBUTE_LEAF _GL_ATTRIBUTE_LEAF
+
+/* For struct members: The member has the smallest possible alignment.
+   For struct, union, class: All members have the smallest possible alignment,
+   minimizing the memory required.  */
+/* Applies to: struct members, struct, union,
+   in C++ also: class.  */
+#define ATTRIBUTE_PACKED _GL_ATTRIBUTE_PACKED
+
+
+/* ================ Attributes that make invalid code valid ================ */
+
+/* Attributes that prevent fatal compiler optimizations for code that is not
+   fully ISO C compliant.  */
+
+/* Pointers to the type may point to the same storage as pointers to
+   other types, thus disabling strict aliasing optimization.  */
+/* Applies to: types.  */
+#define ATTRIBUTE_MAY_ALIAS _GL_ATTRIBUTE_MAY_ALIAS
+
+
+#endif /* _GL_ATTRIBUTE_H */
diff --git a/lib/intl/export.h b/lib/intl/export.h
new file mode 100644 (file)
index 0000000..10253e3
--- /dev/null
@@ -0,0 +1,8 @@
+
+#if @HAVE_VISIBILITY@ && BUILDING_LIBINTL
+#define LIBINTL_DLL_EXPORTED __attribute__((__visibility__("default")))
+#elif defined _MSC_VER && BUILDING_LIBINTL
+#define LIBINTL_DLL_EXPORTED __declspec(dllexport)
+#else
+#define LIBINTL_DLL_EXPORTED
+#endif
diff --git a/lib/intl/filename.h b/lib/intl/filename.h
new file mode 100644 (file)
index 0000000..0135312
--- /dev/null
@@ -0,0 +1,110 @@
+/* Basic filename support macros.
+   Copyright (C) 2001-2004, 2007-2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* From Paul Eggert and Jim Meyering.  */
+
+#ifndef _FILENAME_H
+#define _FILENAME_H
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Filename support.
+   ISSLASH(C)                  tests whether C is a directory separator
+                               character.
+   HAS_DEVICE(Filename)        tests whether Filename contains a device
+                               specification.
+   FILE_SYSTEM_PREFIX_LEN(Filename)  length of the device specification
+                                     at the beginning of Filename,
+                                     index of the part consisting of
+                                     alternating components and slashes.
+   FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
+                               1 when a non-empty device specification
+                               can be followed by an empty or relative
+                               part,
+                               0 when a non-empty device specification
+                               must be followed by a slash,
+                               0 when device specification don't exist.
+   IS_ABSOLUTE_FILE_NAME(Filename)
+                               tests whether Filename is independent of
+                               any notion of "current directory".
+   IS_RELATIVE_FILE_NAME(Filename)
+                               tests whether Filename may be concatenated
+                               to a directory filename.
+   Note: On native Windows, OS/2, DOS, "c:" is neither an absolute nor a
+   relative file name!
+   IS_FILE_NAME_WITH_DIR(Filename)  tests whether Filename contains a device
+                                    or directory specification.
+ */
+#if defined _WIN32 || defined __CYGWIN__ \
+    || defined __EMX__ || defined __MSDOS__ || defined __DJGPP__
+  /* Native Windows, Cygwin, OS/2, DOS */
+# define ISSLASH(C) ((C) == '/' || (C) == '\\')
+  /* Internal macro: Tests whether a character is a drive letter.  */
+# define _IS_DRIVE_LETTER(C) \
+    (((C) >= 'A' && (C) <= 'Z') || ((C) >= 'a' && (C) <= 'z'))
+  /* Help the compiler optimizing it.  This assumes ASCII.  */
+# undef _IS_DRIVE_LETTER
+# define _IS_DRIVE_LETTER(C) \
+    (((unsigned int) (C) | ('a' - 'A')) - 'a' <= 'z' - 'a')
+# define HAS_DEVICE(Filename) \
+    (_IS_DRIVE_LETTER ((Filename)[0]) && (Filename)[1] == ':')
+# define FILE_SYSTEM_PREFIX_LEN(Filename) (HAS_DEVICE (Filename) ? 2 : 0)
+# ifdef __CYGWIN__
+#  define FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE 0
+# else
+   /* On native Windows, OS/2, DOS, the system has the notion of a
+      "current directory" on each drive.  */
+#  define FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE 1
+# endif
+# if FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
+#  define IS_ABSOLUTE_FILE_NAME(Filename) \
+     ISSLASH ((Filename)[FILE_SYSTEM_PREFIX_LEN (Filename)])
+# else
+#  define IS_ABSOLUTE_FILE_NAME(Filename) \
+     (ISSLASH ((Filename)[0]) || HAS_DEVICE (Filename))
+# endif
+# define IS_RELATIVE_FILE_NAME(Filename) \
+    (! (ISSLASH ((Filename)[0]) || HAS_DEVICE (Filename)))
+# define IS_FILE_NAME_WITH_DIR(Filename) \
+    (strchr ((Filename), '/') != NULL || strchr ((Filename), '\\') != NULL \
+     || HAS_DEVICE (Filename))
+#else
+  /* Unix */
+# define ISSLASH(C) ((C) == '/')
+# define HAS_DEVICE(Filename) ((void) (Filename), 0)
+# define FILE_SYSTEM_PREFIX_LEN(Filename) ((void) (Filename), 0)
+# define FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE 0
+# define IS_ABSOLUTE_FILE_NAME(Filename) ISSLASH ((Filename)[0])
+# define IS_RELATIVE_FILE_NAME(Filename) (! ISSLASH ((Filename)[0]))
+# define IS_FILE_NAME_WITH_DIR(Filename) (strchr ((Filename), '/') != NULL)
+#endif
+
+/* Deprecated macros.  For backward compatibility with old users of the
+   'filename' module.  */
+#define IS_ABSOLUTE_PATH IS_ABSOLUTE_FILE_NAME
+#define IS_PATH_WITH_DIR IS_FILE_NAME_WITH_DIR
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FILENAME_H */
diff --git a/lib/intl/flexmember.h b/lib/intl/flexmember.h
new file mode 100644 (file)
index 0000000..5a53172
--- /dev/null
@@ -0,0 +1,57 @@
+/* Sizes of structs with flexible array members.
+
+   Copyright 2016-2019 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+   Written by Paul Eggert.  */
+
+#include <stddef.h>
+
+/* Nonzero multiple of alignment of TYPE, suitable for FLEXSIZEOF below.
+   On older platforms without _Alignof, use a pessimistic bound that is
+   safe in practice even if FLEXIBLE_ARRAY_MEMBER is 1.
+   On newer platforms, use _Alignof to get a tighter bound.  */
+
+#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112
+# define FLEXALIGNOF(type) (sizeof (type) & ~ (sizeof (type) - 1))
+#else
+# define FLEXALIGNOF(type) _Alignof (type)
+#endif
+
+/* Yield a properly aligned upper bound on the size of a struct of
+   type TYPE with a flexible array member named MEMBER that is
+   followed by N bytes of other data.  The result is suitable as an
+   argument to malloc.  For example:
+
+     struct s { int n; char d[FLEXIBLE_ARRAY_MEMBER]; };
+     struct s *p = malloc (FLEXSIZEOF (struct s, d, n * sizeof (char)));
+
+   FLEXSIZEOF (TYPE, MEMBER, N) is not simply (sizeof (TYPE) + N),
+   since FLEXIBLE_ARRAY_MEMBER may be 1 on pre-C11 platforms.  Nor is
+   it simply (offsetof (TYPE, MEMBER) + N), as that might yield a size
+   that causes malloc to yield a pointer that is not properly aligned
+   for TYPE; for example, if sizeof (int) == alignof (int) == 4,
+   malloc (offsetof (struct s, d) + 3 * sizeof (char)) is equivalent
+   to malloc (7) and might yield a pointer that is not a multiple of 4
+   (which means the pointer is not properly aligned for struct s),
+   whereas malloc (FLEXSIZEOF (struct s, d, 3 * sizeof (char))) is
+   equivalent to malloc (8) and must yield a pointer that is a
+   multiple of 4.
+
+   Yield a value less than N if and only if arithmetic overflow occurs.  */
+
+#define FLEXSIZEOF(type, member, n) \
+   ((offsetof (type, member) + FLEXALIGNOF (type) - 1 + (n)) \
+    & ~ (FLEXALIGNOF (type) - 1))
diff --git a/lib/intl/hash-string.c b/lib/intl/hash-string.c
new file mode 100644 (file)
index 0000000..a81f114
--- /dev/null
@@ -0,0 +1,50 @@
+/* Implements a string hashing function.
+   Copyright (C) 1995-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Specification.  */
+#include "hash-string.h"
+
+
+/* Defines the so called `hashpjw' function by P.J. Weinberger
+   [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
+   1986, 1987 Bell Telephone Laboratories, Inc.]  */
+unsigned long int
+__hash_string (const char *str_param)
+{
+  unsigned long int hval, g;
+  const char *str = str_param;
+
+  /* Compute the hash value for the given string.  */
+  hval = 0;
+  while (*str != '\0')
+    {
+      hval <<= 4;
+      hval += (unsigned char) *str++;
+      g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
+      if (g != 0)
+       {
+         hval ^= g >> (HASHWORDBITS - 8);
+         hval ^= g;
+       }
+    }
+  return hval;
+}
diff --git a/lib/intl/intl-exports.c b/lib/intl/intl-exports.c
new file mode 100644 (file)
index 0000000..5654264
--- /dev/null
@@ -0,0 +1,38 @@
+/* List of exported symbols of libintl on Cygwin.
+   Copyright (C) 2006, 2012-2020 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 2006.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+ /* IMP(x) is a symbol that contains the address of x.  */
+#if defined _WIN64 || defined _LP64
+# define IMP(x) __imp_##x
+#else
+# define IMP(x) _imp__##x
+#endif
+
+ /* Ensure that the variable x is exported from the library, and that a
+    pseudo-variable IMP(x) is available.  */
+#define VARIABLE(x) \
+ /* Export x without redefining x.  This code was found by compiling a  \
+    snippet:                                                            \
+      extern __declspec(dllexport) int x; int x = 42;  */               \
+ asm (".section .drectve\n");                                           \
+ asm (".ascii \" -export:" #x ",data\"\n");                             \
+ asm (".data\n");                                                       \
+ /* Allocate a pseudo-variable IMP(x).  */                              \
+ extern int x;                                                          \
+ void * IMP(x) = &x;
+
+VARIABLE(libintl_version)
diff --git a/lib/intl/langprefs.c b/lib/intl/langprefs.c
new file mode 100644 (file)
index 0000000..f774ba2
--- /dev/null
@@ -0,0 +1,406 @@
+/* Determine the user's language preferences.
+   Copyright (C) 2004-2007, 2018-2019 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.
+   Win32 code originally by Michele Cicciotti <hackbunny@reactos.com>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
+# include <string.h>
+# if HAVE_CFLOCALECOPYPREFERREDLANGUAGES
+#  include <CoreFoundation/CFLocale.h>
+# elif HAVE_CFPREFERENCESCOPYAPPVALUE
+#  include <CoreFoundation/CFPreferences.h>
+# endif
+# include <CoreFoundation/CFPropertyList.h>
+# include <CoreFoundation/CFArray.h>
+# include <CoreFoundation/CFString.h>
+extern void _nl_locale_name_canonicalize (char *name);
+#endif
+
+#if defined _WIN32
+# define WIN32_NATIVE
+#endif
+
+#ifdef WIN32_NATIVE
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+# ifndef MUI_LANGUAGE_NAME
+# define MUI_LANGUAGE_NAME 8
+# endif
+# ifndef STATUS_BUFFER_OVERFLOW
+# define STATUS_BUFFER_OVERFLOW 0x80000005
+# endif
+
+extern void _nl_locale_name_canonicalize (char *name);
+extern const char *_nl_locale_name_from_win32_LANGID (LANGID langid);
+extern const char *_nl_locale_name_from_win32_LCID (LCID lcid);
+
+/* Get the preferences list through the MUI APIs. This works on Windows Vista
+   and newer.  */
+static const char *
+_nl_language_preferences_win32_mui (HMODULE kernel32)
+{
+  /* DWORD GetUserPreferredUILanguages (ULONG dwFlags,
+                                        PULONG pulNumLanguages,
+                                        PWSTR pwszLanguagesBuffer,
+                                        PULONG pcchLanguagesBuffer);  */
+  typedef DWORD (WINAPI *GetUserPreferredUILanguages_func) (ULONG, PULONG, PWSTR, PULONG);
+  GetUserPreferredUILanguages_func p_GetUserPreferredUILanguages;
+
+  p_GetUserPreferredUILanguages =
+   (GetUserPreferredUILanguages_func)
+   GetProcAddress (kernel32, "GetUserPreferredUILanguages");
+  if (p_GetUserPreferredUILanguages != NULL)
+    {
+      ULONG num_languages;
+      ULONG bufsize;
+      DWORD ret;
+
+      bufsize = 0;
+      ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
+                                           &num_languages,
+                                           NULL, &bufsize);
+      if (ret == 0
+          && GetLastError () == STATUS_BUFFER_OVERFLOW
+          && bufsize > 0)
+        {
+          WCHAR *buffer = (WCHAR *) malloc (bufsize * sizeof (WCHAR));
+          if (buffer != NULL)
+            {
+              ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
+                                                   &num_languages,
+                                                   buffer, &bufsize);
+              if (ret)
+                {
+                  /* Convert the list from NUL-delimited WCHAR[] Win32 locale
+                     names to colon-delimited char[] Unix locale names.
+                     We assume that all these locale names are in ASCII,
+                     nonempty and contain no colons.  */
+                  char *languages =
+                    (char *) malloc (bufsize + num_languages * 10 + 1);
+                  if (languages != NULL)
+                    {
+                      const WCHAR *p = buffer;
+                      char *q = languages;
+                      ULONG i;
+                      for (i = 0; i < num_languages; i++)
+                        {
+                          char *q1;
+                          char *q2;
+
+                          q1 = q;
+                          if (i > 0)
+                            *q++ = ':';
+                          q2 = q;
+                          for (; *p != (WCHAR)'\0'; p++)
+                            {
+                              if ((unsigned char) *p != *p || *p == ':')
+                                {
+                                  /* A non-ASCII character or a colon inside
+                                     the Win32 locale name! Punt.  */
+                                  q = q1;
+                                  break;
+                                }
+                              *q++ = (unsigned char) *p;
+                            }
+                          if (q == q1)
+                            /* An unexpected Win32 locale name occurred.  */
+                            break;
+                          *q = '\0';
+                          _nl_locale_name_canonicalize (q2);
+                          q = q2 + strlen (q2);
+                          p++;
+                        }
+                      *q = '\0';
+                      if (q > languages)
+                        {
+                          free (buffer);
+                          return languages;
+                        }
+                      free (languages);
+                    }
+                }
+              free (buffer);
+            }
+        }
+    }
+  return NULL;
+}
+
+/* Get a preference.  This works on Windows ME and newer.  */
+static const char *
+_nl_language_preferences_win32_ME (HMODULE kernel32)
+{
+  /* LANGID GetUserDefaultUILanguage (void);  */
+  typedef LANGID (WINAPI *GetUserDefaultUILanguage_func) (void);
+  GetUserDefaultUILanguage_func p_GetUserDefaultUILanguage;
+
+  p_GetUserDefaultUILanguage =
+   (GetUserDefaultUILanguage_func)
+   GetProcAddress (kernel32, "GetUserDefaultUILanguage");
+  if (p_GetUserDefaultUILanguage != NULL)
+    return _nl_locale_name_from_win32_LANGID (p_GetUserDefaultUILanguage ());
+  return NULL;
+}
+
+/* Get a preference.  This works on Windows 95 and newer.  */
+static const char *
+_nl_language_preferences_win32_95 ()
+{
+  HKEY desktop_resource_locale_key;
+
+  if (RegOpenKeyExA (HKEY_CURRENT_USER,
+                     "Control Panel\\Desktop\\ResourceLocale",
+                     0, KEY_QUERY_VALUE, &desktop_resource_locale_key)
+      == NO_ERROR)
+    {
+      DWORD type;
+      BYTE data[8 + 1];
+      DWORD data_size = sizeof (data);
+      DWORD ret;
+
+      ret = RegQueryValueExA (desktop_resource_locale_key, NULL, NULL,
+                              &type, data, &data_size);
+      RegCloseKey (desktop_resource_locale_key);
+
+      if (ret == NO_ERROR)
+        {
+          /* We expect a string, at most 8 bytes long, that parses as a
+             hexadecimal number.  */
+          if (type == REG_SZ
+              && data_size <= sizeof (data)
+              && (data_size < sizeof (data)
+                  || data[sizeof (data) - 1] == '\0'))
+            {
+              LCID lcid;
+              char *endp;
+              /* Ensure it's NUL terminated.  */
+              if (data_size < sizeof (data))
+                data[data_size] = '\0';
+              /* Parse it as a hexadecimal number.  */
+              lcid = strtoul ((char *) data, &endp, 16);
+              if (endp > (char *) data && *endp == '\0')
+                return _nl_locale_name_from_win32_LCID (lcid);
+            }
+        }
+    }
+  return NULL;
+}
+
+/* Get the system's preference.  This can be used as a fallback.  */
+static BOOL CALLBACK
+ret_first_language (HMODULE h, LPCSTR type, LPCSTR name, WORD lang, LONG_PTR param)
+{
+  *(const char **)param = _nl_locale_name_from_win32_LANGID (lang);
+  return FALSE;
+}
+static const char *
+_nl_language_preferences_win32_system (HMODULE kernel32)
+{
+  const char *languages = NULL;
+  /* Ignore the warning on mingw here. mingw has a wrong definition of the last
+     parameter type of ENUMRESLANGPROC.  */
+  EnumResourceLanguages (kernel32, RT_VERSION, MAKEINTRESOURCE (1),
+                         ret_first_language, (LONG_PTR)&languages);
+  return languages;
+}
+
+#endif
+
+/* Determine the user's language preferences, as a colon separated list of
+   locale names in XPG syntax
+     language[_territory][.codeset][@modifier]
+   The result must not be freed; it is statically allocated.
+   The LANGUAGE environment variable does not need to be considered; it is
+   already taken into account by the caller.  */
+
+const char *
+_nl_language_preferences_default (void)
+{
+#if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
+  /* MacOS X 10.4 or newer */
+  {
+    /* Cache the preferences list, since CoreFoundation calls are expensive.  */
+    static const char *cached_languages;
+    static int cache_initialized;
+
+    if (!cache_initialized)
+      {
+# if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
+        CFArrayRef prefArray = CFLocaleCopyPreferredLanguages ();
+# elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
+        CFTypeRef preferences =
+          CFPreferencesCopyAppValue (CFSTR ("AppleLanguages"),
+                                     kCFPreferencesCurrentApplication);
+        if (preferences != NULL
+            && CFGetTypeID (preferences) == CFArrayGetTypeID ())
+          {
+            CFArrayRef prefArray = (CFArrayRef)preferences;
+# endif
+
+            int n = CFArrayGetCount (prefArray);
+            char buf[256];
+            char buf2[256];
+            size_t size = 0;
+            int i;
+
+            for (i = 0; i < n; i++)
+              {
+                CFTypeRef element = CFArrayGetValueAtIndex (prefArray, i);
+                if (element != NULL
+                    && CFGetTypeID (element) == CFStringGetTypeID ()
+                    && CFStringGetCString ((CFStringRef)element,
+                                           buf, sizeof (buf),
+                                           kCFStringEncodingASCII))
+                  {
+                    strcpy (buf2, buf);
+                    _nl_locale_name_canonicalize (buf);
+                    size += strlen (buf) + 1;
+                    /* Mac OS X 10.12 or newer returns an array of elements of
+                       the form "ll-CC" or "ll-Scrp-CC" where ll is a language
+                       code, CC is a country code, and Scrp (optional) is a
+                       script code.
+                       _nl_locale_name_canonicalize converts this to "ll_CC" or
+                       "ll_Scrp_CC".
+                       Sometimes ll and CC are unrelated, i.e. there is no
+                       translation for "ll_CC" but one for "ll".
+                       Similarly, in the case with a script, sometimes there is
+                       no translation for "ll_Scrp_CC" but one for "ll_Scrp"
+                       (after proper canonicalization).
+                       Therefore, in the result, we return "ll_CC" followed
+                       by "ll", or similarly for the case with a script.  */
+                    {
+                      char *last_minus = strrchr (buf2, '-');
+                      if (last_minus != NULL)
+                        {
+                          *last_minus = '\0';
+                          _nl_locale_name_canonicalize (buf2);
+                          size += strlen (buf2) + 1;
+                        }
+                    }
+                    /* Most GNU programs use msgids in English and don't ship
+                       an en.mo message catalog.  Therefore when we see "en" or
+                       "en-CC" in the preferences list, arrange for gettext()
+                       to return the msgid, and ignore all further elements of
+                       the preferences list.  */
+                    if (buf[0] == 'e' && buf[1] == 'n'
+                        && (buf[2] == '\0' || buf[2] == '_'))
+                      break;
+                  }
+                else
+                  break;
+              }
+            if (size > 0)
+              {
+                char *languages = (char *) malloc (size);
+
+                if (languages != NULL)
+                  {
+                    char *p = languages;
+
+                    for (i = 0; i < n; i++)
+                      {
+                        CFTypeRef element =
+                          CFArrayGetValueAtIndex (prefArray, i);
+                        if (element != NULL
+                            && CFGetTypeID (element) == CFStringGetTypeID ()
+                            && CFStringGetCString ((CFStringRef)element,
+                                                   buf, sizeof (buf),
+                                                   kCFStringEncodingASCII))
+                          {
+                            strcpy (buf2, buf);
+                            _nl_locale_name_canonicalize (buf);
+                            strcpy (p, buf);
+                            p += strlen (buf);
+                            *p++ = ':';
+                            {
+                              char *last_minus = strrchr (buf2, '-');
+                              if (last_minus != NULL)
+                                {
+                                  *last_minus = '\0';
+                                  _nl_locale_name_canonicalize (buf2);
+                                  strcpy (p, buf2);
+                                  p += strlen (buf2);
+                                  *p++ = ':';
+                                }
+                            }
+                            if (buf[0] == 'e' && buf[1] == 'n'
+                                 && (buf[2] == '\0' || buf[2] == '_'))
+                              break;
+                          }
+                        else
+                          break;
+                      }
+                    *--p = '\0';
+
+                    cached_languages = languages;
+                  }
+              }
+
+# if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
+        CFRelease (prefArray);
+# elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
+          }
+# endif
+        cache_initialized = 1;
+      }
+    if (cached_languages != NULL)
+      return cached_languages;
+  }
+#endif
+
+#ifdef WIN32_NATIVE
+  {
+    /* Cache the preferences list, since computing it is expensive.  */
+    static const char *cached_languages;
+    static int cache_initialized;
+
+    /* Activate the new code only when the GETTEXT_MUI environment variable is
+       set, for the time being, since the new code is not well tested.  */
+    if (!cache_initialized && getenv ("GETTEXT_MUI") != NULL)
+      {
+        const char *languages = NULL;
+        HMODULE kernel32 = GetModuleHandle ("kernel32");
+
+        if (kernel32 != NULL)
+          languages = _nl_language_preferences_win32_mui (kernel32);
+
+        if (languages == NULL && kernel32 != NULL)
+          languages = _nl_language_preferences_win32_ME (kernel32);
+
+        if (languages == NULL)
+          languages = _nl_language_preferences_win32_95 ();
+
+        if (languages == NULL && kernel32 != NULL)
+          languages = _nl_language_preferences_win32_system (kernel32);
+
+        cached_languages = languages;
+        cache_initialized = 1;
+      }
+    if (cached_languages != NULL)
+      return cached_languages;
+  }
+#endif
+
+  return NULL;
+}
diff --git a/lib/intl/libgnuintl.in.h b/lib/intl/libgnuintl.in.h
new file mode 100644 (file)
index 0000000..9686feb
--- /dev/null
@@ -0,0 +1,599 @@
+/* Message catalogs for internationalization.
+   Copyright (C) 1995-1997, 2000-2016, 2018-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _LIBINTL_H
+#define _LIBINTL_H 1
+
+#include <locale.h>
+#if (defined __APPLE__ && defined __MACH__) && @HAVE_NEWLOCALE@
+# include <xlocale.h>
+#endif
+
+/* The LC_MESSAGES locale category is the category used by the functions
+   gettext() and dgettext().  It is specified in POSIX, but not in ANSI C.
+   On systems that don't define it, use an arbitrary value instead.
+   On Solaris, <locale.h> defines __LOCALE_H (or _LOCALE_H in Solaris 2.5)
+   then includes <libintl.h> (i.e. this file!) and then only defines
+   LC_MESSAGES.  To avoid a redefinition warning, don't define LC_MESSAGES
+   in this case.  */
+#if !defined LC_MESSAGES && !(defined __LOCALE_H || (defined _LOCALE_H && defined __sun))
+# define LC_MESSAGES 1729
+#endif
+
+/* We define an additional symbol to signal that we use the GNU
+   implementation of gettext.  */
+#define __USE_GNU_GETTEXT 1
+
+/* Provide information about the supported file formats.  Returns the
+   maximum minor revision number supported for a given major revision.  */
+#define __GNU_GETTEXT_SUPPORTED_REVISION(major) \
+  ((major) == 0 || (major) == 1 ? 1 : -1)
+
+/* Resolve a platform specific conflict on DJGPP.  GNU gettext takes
+   precedence over _conio_gettext.  */
+#ifdef __DJGPP__
+# undef gettext
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Version number: (major<<16) + (minor<<8) + subminor */
+#define LIBINTL_VERSION 0x001501
+extern int libintl_version;
+
+
+/* We redirect the functions to those prefixed with "libintl_".  This is
+   necessary, because some systems define gettext/textdomain/... in the C
+   library (namely, Solaris 2.4 and newer, and GNU libc 2.0 and newer).
+   If we used the unprefixed names, there would be cases where the
+   definition in the C library would override the one in the libintl.so
+   shared library.  Recall that on ELF systems, the symbols are looked
+   up in the following order:
+     1. in the executable,
+     2. in the shared libraries specified on the link command line, in order,
+     3. in the dependencies of the shared libraries specified on the link
+        command line,
+     4. in the dlopen()ed shared libraries, in the order in which they were
+        dlopen()ed.
+   The definition in the C library would override the one in libintl.so if
+   either
+     * -lc is given on the link command line and -lintl isn't, or
+     * -lc is given on the link command line before -lintl, or
+     * libintl.so is a dependency of a dlopen()ed shared library but not
+       linked to the executable at link time.
+   Since Solaris gettext() behaves differently than GNU gettext(), this
+   would be unacceptable.
+
+   The redirection happens by default through macros in C, so that &gettext
+   is independent of the compilation unit, but through inline functions in
+   C++, in order not to interfere with the name mangling of class fields or
+   class methods called 'gettext'.  */
+
+/* The user can define _INTL_REDIRECT_INLINE or _INTL_REDIRECT_MACROS.
+   If he doesn't, we choose the method.  A third possible method is
+   _INTL_REDIRECT_ASM, supported only by GCC.  */
+#if !(defined _INTL_REDIRECT_INLINE || defined _INTL_REDIRECT_MACROS)
+# if defined __GNUC__ && __GNUC__ >= 2 && !(defined __APPLE_CC__ && __APPLE_CC__ > 1) && !defined __MINGW32__ && !(__GNUC__ == 2 && defined _AIX) && (defined __STDC__ || defined __cplusplus)
+#  define _INTL_REDIRECT_ASM
+# else
+#  ifdef __cplusplus
+#   define _INTL_REDIRECT_INLINE
+#  else
+#   define _INTL_REDIRECT_MACROS
+#  endif
+# endif
+#endif
+/* Auxiliary macros.  */
+#ifdef _INTL_REDIRECT_ASM
+# define _INTL_ASM(cname) __asm__ (_INTL_ASMNAME (__USER_LABEL_PREFIX__, #cname))
+# define _INTL_ASMNAME(prefix,cnamestring) _INTL_STRINGIFY (prefix) cnamestring
+# define _INTL_STRINGIFY(prefix) #prefix
+#else
+# define _INTL_ASM(cname)
+#endif
+
+/* _INTL_MAY_RETURN_STRING_ARG(n) declares that the given function may return
+   its n-th argument literally.  This enables GCC to warn for example about
+   printf (gettext ("foo %y")).  */
+#if defined __GNUC__ && __GNUC__ >= 3 && !(defined __APPLE_CC__ && __APPLE_CC__ > 1 && !(defined __clang__ && __clang__ && __clang_major__ >= 3) && defined __cplusplus)
+# define _INTL_MAY_RETURN_STRING_ARG(n) __attribute__ ((__format_arg__ (n)))
+#else
+# define _INTL_MAY_RETURN_STRING_ARG(n)
+#endif
+
+/* Look up MSGID in the current default message catalog for the current
+   LC_MESSAGES locale.  If not found, returns MSGID itself (the default
+   text).  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_gettext (const char *__msgid)
+       _INTL_MAY_RETURN_STRING_ARG (1);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (1)
+char *gettext (const char *__msgid)
+{
+  return libintl_gettext (__msgid);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define gettext libintl_gettext
+# endif
+extern char *gettext (const char *__msgid)
+       _INTL_ASM (libintl_gettext)
+       _INTL_MAY_RETURN_STRING_ARG (1);
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current
+   LC_MESSAGES locale.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_dgettext (const char *__domainname, const char *__msgid)
+       _INTL_MAY_RETURN_STRING_ARG (2);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (2)
+char *dgettext (const char *__domainname, const char *__msgid)
+{
+  return libintl_dgettext (__domainname, __msgid);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define dgettext libintl_dgettext
+# endif
+extern char *dgettext (const char *__domainname, const char *__msgid)
+       _INTL_ASM (libintl_dgettext)
+       _INTL_MAY_RETURN_STRING_ARG (2);
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
+   locale.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_dcgettext (const char *__domainname, const char *__msgid,
+                                int __category)
+       _INTL_MAY_RETURN_STRING_ARG (2);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (2)
+char *dcgettext (const char *__domainname, const char *__msgid, int __category)
+{
+  return libintl_dcgettext (__domainname, __msgid, __category);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define dcgettext libintl_dcgettext
+# endif
+extern char *dcgettext (const char *__domainname, const char *__msgid,
+                        int __category)
+       _INTL_ASM (libintl_dcgettext)
+       _INTL_MAY_RETURN_STRING_ARG (2);
+#endif
+
+
+/* Similar to 'gettext' but select the plural form corresponding to the
+   number N.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_ngettext (const char *__msgid1, const char *__msgid2,
+                               unsigned long int __n)
+       _INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2)
+char *ngettext (const char *__msgid1, const char *__msgid2,
+                unsigned long int __n)
+{
+  return libintl_ngettext (__msgid1, __msgid2, __n);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define ngettext libintl_ngettext
+# endif
+extern char *ngettext (const char *__msgid1, const char *__msgid2,
+                       unsigned long int __n)
+       _INTL_ASM (libintl_ngettext)
+       _INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2);
+#endif
+
+/* Similar to 'dgettext' but select the plural form corresponding to the
+   number N.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_dngettext (const char *__domainname, const char *__msgid1,
+                                const char *__msgid2, unsigned long int __n)
+       _INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3)
+char *dngettext (const char *__domainname, const char *__msgid1,
+                 const char *__msgid2, unsigned long int __n)
+{
+  return libintl_dngettext (__domainname, __msgid1, __msgid2, __n);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define dngettext libintl_dngettext
+# endif
+extern char *dngettext (const char *__domainname,
+                        const char *__msgid1, const char *__msgid2,
+                        unsigned long int __n)
+       _INTL_ASM (libintl_dngettext)
+       _INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
+#endif
+
+/* Similar to 'dcgettext' but select the plural form corresponding to the
+   number N.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_dcngettext (const char *__domainname,
+                                 const char *__msgid1, const char *__msgid2,
+                                 unsigned long int __n, int __category)
+       _INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
+static inline
+_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3)
+char *dcngettext (const char *__domainname,
+                  const char *__msgid1, const char *__msgid2,
+                  unsigned long int __n, int __category)
+{
+  return libintl_dcngettext (__domainname, __msgid1, __msgid2, __n, __category);
+}
+#else
+# ifdef _INTL_REDIRECT_MACROS
+#  define dcngettext libintl_dcngettext
+# endif
+extern char *dcngettext (const char *__domainname,
+                         const char *__msgid1, const char *__msgid2,
+                         unsigned long int __n, int __category)
+       _INTL_ASM (libintl_dcngettext)
+       _INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
+#endif
+
+
+#ifndef IN_LIBGLOCALE
+
+/* Set the current default message catalog to DOMAINNAME.
+   If DOMAINNAME is null, return the current default.
+   If DOMAINNAME is "", reset to the default of "messages".  */
+# ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_textdomain (const char *__domainname);
+static inline char *textdomain (const char *__domainname)
+{
+  return libintl_textdomain (__domainname);
+}
+# else
+#  ifdef _INTL_REDIRECT_MACROS
+#   define textdomain libintl_textdomain
+#  endif
+extern char *textdomain (const char *__domainname)
+       _INTL_ASM (libintl_textdomain);
+# endif
+
+/* Specify that the DOMAINNAME message catalog will be found
+   in DIRNAME rather than in the system locale data base.  */
+# ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_bindtextdomain (const char *__domainname,
+                                     const char *__dirname);
+static inline char *bindtextdomain (const char *__domainname,
+                                    const char *__dirname)
+{
+  return libintl_bindtextdomain (__domainname, __dirname);
+}
+# else
+#  ifdef _INTL_REDIRECT_MACROS
+#   define bindtextdomain libintl_bindtextdomain
+#  endif
+extern char *bindtextdomain (const char *__domainname, const char *__dirname)
+       _INTL_ASM (libintl_bindtextdomain);
+# endif
+
+# if defined _WIN32 && !defined __CYGWIN__
+/* Specify that the DOMAINNAME message catalog will be found
+   in WDIRNAME rather than in the system locale data base.  */
+#  ifdef _INTL_REDIRECT_INLINE
+extern wchar_t *libintl_wbindtextdomain (const char *__domainname,
+                                         const wchar_t *__wdirname);
+static inline wchar_t *wbindtextdomain (const char *__domainname,
+                                        const wchar_t *__wdirname)
+{
+  return libintl_wbindtextdomain (__domainname, __wdirname);
+}
+#  else
+#   ifdef _INTL_REDIRECT_MACROS
+#    define wbindtextdomain libintl_wbindtextdomain
+#   endif
+extern wchar_t *wbindtextdomain (const char *__domainname,
+                                 const wchar_t *__wdirname)
+       _INTL_ASM (libintl_wbindtextdomain);
+#  endif
+# endif
+
+/* Specify the character encoding in which the messages from the
+   DOMAINNAME message catalog will be returned.  */
+# ifdef _INTL_REDIRECT_INLINE
+extern char *libintl_bind_textdomain_codeset (const char *__domainname,
+                                              const char *__codeset);
+static inline char *bind_textdomain_codeset (const char *__domainname,
+                                             const char *__codeset)
+{
+  return libintl_bind_textdomain_codeset (__domainname, __codeset);
+}
+# else
+#  ifdef _INTL_REDIRECT_MACROS
+#   define bind_textdomain_codeset libintl_bind_textdomain_codeset
+#  endif
+extern char *bind_textdomain_codeset (const char *__domainname,
+                                      const char *__codeset)
+       _INTL_ASM (libintl_bind_textdomain_codeset);
+# endif
+
+#endif /* IN_LIBGLOCALE */
+
+
+/* Support for format strings with positions in *printf(), following the
+   POSIX/XSI specification.
+   Note: These replacements for the *printf() functions are visible only
+   in source files that #include <libintl.h> or #include "gettext.h".
+   Packages that use *printf() in source files that don't refer to _()
+   or gettext() but for which the format string could be the return value
+   of _() or gettext() need to add this #include.  Oh well.  */
+
+/* Note: In C++ mode, it is not sufficient to redefine a symbol at the
+   preprocessor macro level, such as
+     #define sprintf libintl_sprintf
+   Some programs may reference std::sprintf after including <libintl.h>.
+   Therefore we must make sure that std::libintl_sprintf is defined and
+   identical to ::libintl_sprintf.
+   The user can define _INTL_CXX_NO_CLOBBER_STD_NAMESPACE to avoid this.
+   In such cases, they will not benefit from the overrides when using
+   the 'std' namespace, and they will need to do the references to the
+   'std' namespace *before* including <libintl.h> or "gettext.h".  */
+
+#if !@HAVE_POSIX_PRINTF@
+
+# include <stdio.h>
+# include <stddef.h>
+
+/* Get va_list.  */
+# if (defined __STDC__ && __STDC__) || defined __cplusplus || defined _MSC_VER
+#  include <stdarg.h>
+# else
+#  include <varargs.h>
+# endif
+
+# if !((defined fprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_fprintf) /* don't override gnulib */
+#  undef fprintf
+#  define fprintf libintl_fprintf
+extern int fprintf (FILE *, const char *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_fprintf; }
+#  endif
+# endif
+# if !((defined vfprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vfprintf) /* don't override gnulib */
+#  undef vfprintf
+#  define vfprintf libintl_vfprintf
+extern int vfprintf (FILE *, const char *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vfprintf; }
+#  endif
+# endif
+
+# if !((defined printf && defined _GL_STDIO_H) || defined GNULIB_overrides_printf) /* don't override gnulib */
+#  undef printf
+#  if defined __NetBSD__ || defined __BEOS__ || defined __CYGWIN__ || defined __MINGW32__
+/* Don't break __attribute__((format(printf,M,N))).
+   This redefinition is only possible because the libc in NetBSD, Cygwin,
+   mingw does not have a function __printf__.
+   Alternatively, we could have done this redirection only when compiling with
+   __GNUC__, together with a symbol redirection:
+       extern int printf (const char *, ...)
+              __asm__ (#__USER_LABEL_PREFIX__ "libintl_printf");
+   But doing it now would introduce a binary incompatibility with already
+   distributed versions of libintl on these systems.  */
+#   define libintl_printf __printf__
+#  endif
+#  define printf libintl_printf
+extern int printf (const char *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_printf; }
+#  endif
+# endif
+# if !((defined vprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vprintf) /* don't override gnulib */
+#  undef vprintf
+#  define vprintf libintl_vprintf
+extern int vprintf (const char *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vprintf; }
+#  endif
+# endif
+
+# if !((defined sprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_sprintf) /* don't override gnulib */
+#  undef sprintf
+#  define sprintf libintl_sprintf
+extern int sprintf (char *, const char *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_sprintf; }
+#  endif
+# endif
+# if !((defined vsprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vsprintf) /* don't override gnulib */
+#  undef vsprintf
+#  define vsprintf libintl_vsprintf
+extern int vsprintf (char *, const char *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vsprintf; }
+#  endif
+# endif
+
+# if @HAVE_SNPRINTF@
+
+#  if !((defined snprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_snprintf) /* don't override gnulib */
+#   undef snprintf
+#   define snprintf libintl_snprintf
+extern int snprintf (char *, size_t, const char *, ...);
+#   if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_snprintf; }
+#   endif
+#  endif
+#  if !((defined vsnprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vsnprintf) /* don't override gnulib */
+#   undef vsnprintf
+#   define vsnprintf libintl_vsnprintf
+extern int vsnprintf (char *, size_t, const char *, va_list);
+#   if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vsnprintf; }
+#   endif
+#  endif
+
+# endif
+
+# if @HAVE_ASPRINTF@
+
+#  if !((defined asprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_asprintf) /* don't override gnulib */
+#   undef asprintf
+#   define asprintf libintl_asprintf
+extern int asprintf (char **, const char *, ...);
+#   if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_asprintf; }
+#   endif
+#  endif
+#  if !((defined vasprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vasprintf) /* don't override gnulib */
+#   undef vasprintf
+#   define vasprintf libintl_vasprintf
+extern int vasprintf (char **, const char *, va_list);
+#   if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vasprintf; }
+#   endif
+#  endif
+
+# endif
+
+# if @HAVE_WPRINTF@
+
+#  undef fwprintf
+#  define fwprintf libintl_fwprintf
+extern int fwprintf (FILE *, const wchar_t *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_fwprintf; }
+#  endif
+#  undef vfwprintf
+#  define vfwprintf libintl_vfwprintf
+extern int vfwprintf (FILE *, const wchar_t *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vfwprintf; }
+#  endif
+
+#  undef wprintf
+#  define wprintf libintl_wprintf
+extern int wprintf (const wchar_t *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_wprintf; }
+#  endif
+#  undef vwprintf
+#  define vwprintf libintl_vwprintf
+extern int vwprintf (const wchar_t *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vwprintf; }
+#  endif
+
+#  undef swprintf
+#  define swprintf libintl_swprintf
+extern int swprintf (wchar_t *, size_t, const wchar_t *, ...);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_swprintf; }
+#  endif
+#  undef vswprintf
+#  define vswprintf libintl_vswprintf
+extern int vswprintf (wchar_t *, size_t, const wchar_t *, va_list);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_vswprintf; }
+#  endif
+
+# endif
+
+#endif
+
+
+/* Support for retrieving the name of a locale_t object.  */
+#if @ENHANCE_LOCALE_FUNCS@
+
+# ifndef GNULIB_defined_newlocale /* don't override gnulib */
+#  undef newlocale
+#  define newlocale libintl_newlocale
+extern locale_t newlocale (int, const char *, locale_t);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_newlocale; }
+#  endif
+# endif
+
+# ifndef GNULIB_defined_duplocale /* don't override gnulib */
+#  undef duplocale
+#  define duplocale libintl_duplocale
+extern locale_t duplocale (locale_t);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_duplocale; }
+#  endif
+# endif
+
+# ifndef GNULIB_defined_freelocale /* don't override gnulib */
+#  undef freelocale
+#  define freelocale libintl_freelocale
+extern void freelocale (locale_t);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_freelocale; }
+#  endif
+# endif
+
+#endif
+
+
+/* Support for the locale chosen by the user.  */
+#if (defined __APPLE__ && defined __MACH__) || defined _WIN32 || defined __CYGWIN__
+
+# ifndef GNULIB_defined_setlocale /* don't override gnulib */
+#  undef setlocale
+#  define setlocale libintl_setlocale
+extern char *setlocale (int, const char *);
+#  if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_setlocale; }
+#  endif
+# endif
+
+# if @HAVE_NEWLOCALE@
+
+#  undef newlocale
+#  define newlocale libintl_newlocale
+/* Declare newlocale() only if the system headers define the 'locale_t' type. */
+#  if !(defined __CYGWIN__ && !defined LC_ALL_MASK)
+extern locale_t newlocale (int, const char *, locale_t);
+#   if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
+namespace std { using ::libintl_newlocale; }
+#   endif
+#  endif
+
+# endif
+
+#endif
+
+
+/* Support for relocatable packages.  */
+
+/* Sets the original and the current installation prefix of the package.
+   Relocation simply replaces a pathname starting with the original prefix
+   by the corresponding pathname with the current prefix instead.  Both
+   prefixes should be directory names without trailing slash (i.e. use ""
+   instead of "/").  */
+#define libintl_set_relocation_prefix libintl_set_relocation_prefix
+extern void
+       libintl_set_relocation_prefix (const char *orig_prefix,
+                                      const char *curr_prefix);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* libintl.h */
diff --git a/lib/intl/localename-table.c b/lib/intl/localename-table.c
new file mode 100644 (file)
index 0000000..10be7be
--- /dev/null
@@ -0,0 +1,48 @@
+/* Table that maps a locale object to the names of the locale categories.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2018.  */
+
+#include <config.h>
+
+#if HAVE_WORKING_USELOCALE && HAVE_NAMELESS_LOCALES
+
+/* Specification.  */
+#include "localename-table.h"
+
+#include <stdint.h>
+
+/* A hash function for pointers.  */
+size_t _GL_ATTRIBUTE_CONST
+locale_hash_function (locale_t x)
+{
+  uintptr_t p = (uintptr_t) x;
+  size_t h = ((p % 4177) << 12) + ((p % 79) << 6) + (p % 61);
+  return h;
+}
+
+struct locale_hash_node * locale_hash_table[LOCALE_HASH_TABLE_SIZE]
+  /* = { NULL, ..., NULL } */;
+
+gl_rwlock_define_initialized(, locale_lock)
+
+#else
+
+/* This declaration is solely to ensure that after preprocessing
+   this file is never empty.  */
+typedef int dummy;
+
+#endif
diff --git a/lib/intl/localename-table.in.h b/lib/intl/localename-table.in.h
new file mode 100644 (file)
index 0000000..6b2a807
--- /dev/null
@@ -0,0 +1,73 @@
+/* Table that maps a locale object to the names of the locale categories.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2018.  */
+
+#if HAVE_WORKING_USELOCALE && HAVE_NAMELESS_LOCALES
+
+# include <stddef.h>
+# include <locale.h>
+
+# ifdef IN_LIBINTL
+#  include "lock.h"
+# else
+#  include "glthread/lock.h"
+# endif
+
+struct locale_categories_names
+  {
+    /* Locale category -> name (allocated with indefinite extent).  */
+    const char *category_name[6];
+  };
+
+/* A hash table of fixed size.  Multiple threads can access it read-only
+   simultaneously, but only one thread can insert into it or remove from it
+   at the same time.
+   This hash table has global scope, so that when an application uses both
+   GNU libintl and gnulib, the application sees only one hash table.  (When
+   linking statically with libintl, the fact that localename-table.c is a
+   separate compilation unit resolves the duplicate symbol conflict.  When
+   linking with libintl as a shared library, we rely on ELF and the symbol
+   conflict resolution implemented in the ELF dynamic loader here.)
+   Both the libintl overrides and the gnulib overrides of the functions
+   newlocale, duplocale, freelocale see the same hash table (and the same lock).
+   For this reason, the internal layout of the hash table and the hash function
+   MUST NEVER CHANGE.  If you need to change the internal layout or the hash
+   function, introduce versioning by appending a version suffix to the symbols
+   at the linker level.  */
+# define locale_hash_function libintl_locale_hash_function
+# define locale_hash_table libintl_locale_hash_table
+# define locale_lock libintl_locale_lock
+
+extern size_t _GL_ATTRIBUTE_CONST locale_hash_function (locale_t x);
+
+/* A node in a hash bucket collision list.  */
+struct locale_hash_node
+  {
+    struct locale_hash_node *next;
+    locale_t locale;
+    struct locale_categories_names names;
+  };
+
+# define LOCALE_HASH_TABLE_SIZE 101
+extern struct locale_hash_node * locale_hash_table[LOCALE_HASH_TABLE_SIZE];
+
+/* This lock protects the locale_hash_table against multiple simultaneous
+   accesses (except that multiple simultaneous read accesses are allowed).  */
+
+gl_rwlock_define(extern, locale_lock)
+
+#endif
diff --git a/lib/intl/lock.c b/lib/intl/lock.c
new file mode 100644 (file)
index 0000000..f509e21
--- /dev/null
@@ -0,0 +1,749 @@
+/* Locking in multithreaded situations.
+   Copyright (C) 2005-2008, 2012, 2017, 2019-2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2005.
+   Based on GCC's gthr-posix.h, gthr-posix95.h.  */
+
+#include <config.h>
+
+#include "lock.h"
+
+/* ========================================================================= */
+
+#if USE_ISOC_THREADS || USE_ISOC_AND_POSIX_THREADS
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+int
+glthread_lock_init (gl_lock_t *lock)
+{
+  if (mtx_init (&lock->mutex, mtx_plain) != thrd_success)
+    return ENOMEM;
+  lock->init_needed = 0;
+  return 0;
+}
+
+int
+glthread_lock_lock (gl_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_lock (&lock->mutex) != thrd_success)
+    return EAGAIN;
+  return 0;
+}
+
+int
+glthread_lock_unlock (gl_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_unlock (&lock->mutex) != thrd_success)
+    return EINVAL;
+  return 0;
+}
+
+int
+glthread_lock_destroy (gl_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  mtx_destroy (&lock->mutex);
+  return 0;
+}
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+int
+glthread_rwlock_init (gl_rwlock_t *lock)
+{
+  if (mtx_init (&lock->lock, mtx_plain) != thrd_success
+      || cnd_init (&lock->waiting_readers) != thrd_success
+      || cnd_init (&lock->waiting_writers) != thrd_success)
+    return ENOMEM;
+  lock->waiting_writers_count = 0;
+  lock->runcount = 0;
+  lock->init_needed = 0;
+  return 0;
+}
+
+int
+glthread_rwlock_rdlock (gl_rwlock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_lock (&lock->lock) != thrd_success)
+    return EAGAIN;
+  /* Test whether only readers are currently running, and whether the runcount
+     field will not overflow, and whether no writer is waiting.  The latter
+     condition is because POSIX recommends that "write locks shall take
+     precedence over read locks", to avoid "writer starvation".  */
+  while (!(lock->runcount + 1 > 0 && lock->waiting_writers_count == 0))
+    {
+      /* This thread has to wait for a while.  Enqueue it among the
+         waiting_readers.  */
+      if (cnd_wait (&lock->waiting_readers, &lock->lock) != thrd_success)
+        {
+          mtx_unlock (&lock->lock);
+          return EINVAL;
+        }
+    }
+  lock->runcount++;
+  if (mtx_unlock (&lock->lock) != thrd_success)
+    return EINVAL;
+  return 0;
+}
+
+int
+glthread_rwlock_wrlock (gl_rwlock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_lock (&lock->lock) != thrd_success)
+    return EAGAIN;
+  /* Test whether no readers or writers are currently running.  */
+  while (!(lock->runcount == 0))
+    {
+      /* This thread has to wait for a while.  Enqueue it among the
+         waiting_writers.  */
+      lock->waiting_writers_count++;
+      if (cnd_wait (&lock->waiting_writers, &lock->lock) != thrd_success)
+        {
+          lock->waiting_writers_count--;
+          mtx_unlock (&lock->lock);
+          return EINVAL;
+        }
+      lock->waiting_writers_count--;
+    }
+  lock->runcount--; /* runcount becomes -1 */
+  if (mtx_unlock (&lock->lock) != thrd_success)
+    return EINVAL;
+  return 0;
+}
+
+int
+glthread_rwlock_unlock (gl_rwlock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_lock (&lock->lock) != thrd_success)
+    return EAGAIN;
+  if (lock->runcount < 0)
+    {
+      /* Drop a writer lock.  */
+      if (!(lock->runcount == -1))
+        {
+          mtx_unlock (&lock->lock);
+          return EINVAL;
+        }
+      lock->runcount = 0;
+    }
+  else
+    {
+      /* Drop a reader lock.  */
+      if (!(lock->runcount > 0))
+        {
+          mtx_unlock (&lock->lock);
+          return EINVAL;
+        }
+      lock->runcount--;
+    }
+  if (lock->runcount == 0)
+    {
+      /* POSIX recommends that "write locks shall take precedence over read
+         locks", to avoid "writer starvation".  */
+      if (lock->waiting_writers_count > 0)
+        {
+          /* Wake up one of the waiting writers.  */
+          if (cnd_signal (&lock->waiting_writers) != thrd_success)
+            {
+              mtx_unlock (&lock->lock);
+              return EINVAL;
+            }
+        }
+      else
+        {
+          /* Wake up all waiting readers.  */
+          if (cnd_broadcast (&lock->waiting_readers) != thrd_success)
+            {
+              mtx_unlock (&lock->lock);
+              return EINVAL;
+            }
+        }
+    }
+  if (mtx_unlock (&lock->lock) != thrd_success)
+    return EINVAL;
+  return 0;
+}
+
+int
+glthread_rwlock_destroy (gl_rwlock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  mtx_destroy (&lock->lock);
+  cnd_destroy (&lock->waiting_readers);
+  cnd_destroy (&lock->waiting_writers);
+  return 0;
+}
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+int
+glthread_recursive_lock_init (gl_recursive_lock_t *lock)
+{
+  if (mtx_init (&lock->mutex, mtx_plain | mtx_recursive) != thrd_success)
+    return ENOMEM;
+  lock->init_needed = 0;
+  return 0;
+}
+
+int
+glthread_recursive_lock_lock (gl_recursive_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_lock (&lock->mutex) != thrd_success)
+    return EAGAIN;
+  return 0;
+}
+
+int
+glthread_recursive_lock_unlock (gl_recursive_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  if (mtx_unlock (&lock->mutex) != thrd_success)
+    return EINVAL;
+  return 0;
+}
+
+int
+glthread_recursive_lock_destroy (gl_recursive_lock_t *lock)
+{
+  if (lock->init_needed)
+    call_once (&lock->init_once, lock->init_func);
+  mtx_destroy (&lock->mutex);
+  return 0;
+}
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+#endif
+
+/* ========================================================================= */
+
+#if USE_POSIX_THREADS
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+# if HAVE_PTHREAD_RWLOCK && (HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER || (defined PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP && (__GNU_LIBRARY__ > 1)))
+
+#  if defined PTHREAD_RWLOCK_INITIALIZER || defined PTHREAD_RWLOCK_INITIALIZER_NP
+
+#   if !HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER
+     /* glibc with bug https://sourceware.org/bugzilla/show_bug.cgi?id=13701 */
+
+int
+glthread_rwlock_init_for_glibc (pthread_rwlock_t *lock)
+{
+  pthread_rwlockattr_t attributes;
+  int err;
+
+  err = pthread_rwlockattr_init (&attributes);
+  if (err != 0)
+    return err;
+  /* Note: PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP is the only value that
+     causes the writer to be preferred. PTHREAD_RWLOCK_PREFER_WRITER_NP does not
+     do this; see
+     http://man7.org/linux/man-pages/man3/pthread_rwlockattr_setkind_np.3.html */
+  err = pthread_rwlockattr_setkind_np (&attributes,
+                                       PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
+  if (err == 0)
+    err = pthread_rwlock_init(lock, &attributes);
+  /* pthread_rwlockattr_destroy always returns 0.  It cannot influence the
+     return value.  */
+  pthread_rwlockattr_destroy (&attributes);
+  return err;
+}
+
+#   endif
+#  else
+
+int
+glthread_rwlock_init_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_rwlock_init (&lock->rwlock, NULL);
+  if (err != 0)
+    return err;
+  lock->initialized = 1;
+  return 0;
+}
+
+int
+glthread_rwlock_rdlock_multithreaded (gl_rwlock_t *lock)
+{
+  if (!lock->initialized)
+    {
+      int err;
+
+      err = pthread_mutex_lock (&lock->guard);
+      if (err != 0)
+        return err;
+      if (!lock->initialized)
+        {
+          err = glthread_rwlock_init_multithreaded (lock);
+          if (err != 0)
+            {
+              pthread_mutex_unlock (&lock->guard);
+              return err;
+            }
+        }
+      err = pthread_mutex_unlock (&lock->guard);
+      if (err != 0)
+        return err;
+    }
+  return pthread_rwlock_rdlock (&lock->rwlock);
+}
+
+int
+glthread_rwlock_wrlock_multithreaded (gl_rwlock_t *lock)
+{
+  if (!lock->initialized)
+    {
+      int err;
+
+      err = pthread_mutex_lock (&lock->guard);
+      if (err != 0)
+        return err;
+      if (!lock->initialized)
+        {
+          err = glthread_rwlock_init_multithreaded (lock);
+          if (err != 0)
+            {
+              pthread_mutex_unlock (&lock->guard);
+              return err;
+            }
+        }
+      err = pthread_mutex_unlock (&lock->guard);
+      if (err != 0)
+        return err;
+    }
+  return pthread_rwlock_wrlock (&lock->rwlock);
+}
+
+int
+glthread_rwlock_unlock_multithreaded (gl_rwlock_t *lock)
+{
+  if (!lock->initialized)
+    return EINVAL;
+  return pthread_rwlock_unlock (&lock->rwlock);
+}
+
+int
+glthread_rwlock_destroy_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  if (!lock->initialized)
+    return EINVAL;
+  err = pthread_rwlock_destroy (&lock->rwlock);
+  if (err != 0)
+    return err;
+  lock->initialized = 0;
+  return 0;
+}
+
+#  endif
+
+# else
+
+int
+glthread_rwlock_init_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_init (&lock->lock, NULL);
+  if (err != 0)
+    return err;
+  err = pthread_cond_init (&lock->waiting_readers, NULL);
+  if (err != 0)
+    return err;
+  err = pthread_cond_init (&lock->waiting_writers, NULL);
+  if (err != 0)
+    return err;
+  lock->waiting_writers_count = 0;
+  lock->runcount = 0;
+  return 0;
+}
+
+int
+glthread_rwlock_rdlock_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_lock (&lock->lock);
+  if (err != 0)
+    return err;
+  /* Test whether only readers are currently running, and whether the runcount
+     field will not overflow, and whether no writer is waiting.  The latter
+     condition is because POSIX recommends that "write locks shall take
+     precedence over read locks", to avoid "writer starvation".  */
+  while (!(lock->runcount + 1 > 0 && lock->waiting_writers_count == 0))
+    {
+      /* This thread has to wait for a while.  Enqueue it among the
+         waiting_readers.  */
+      err = pthread_cond_wait (&lock->waiting_readers, &lock->lock);
+      if (err != 0)
+        {
+          pthread_mutex_unlock (&lock->lock);
+          return err;
+        }
+    }
+  lock->runcount++;
+  return pthread_mutex_unlock (&lock->lock);
+}
+
+int
+glthread_rwlock_wrlock_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_lock (&lock->lock);
+  if (err != 0)
+    return err;
+  /* Test whether no readers or writers are currently running.  */
+  while (!(lock->runcount == 0))
+    {
+      /* This thread has to wait for a while.  Enqueue it among the
+         waiting_writers.  */
+      lock->waiting_writers_count++;
+      err = pthread_cond_wait (&lock->waiting_writers, &lock->lock);
+      if (err != 0)
+        {
+          lock->waiting_writers_count--;
+          pthread_mutex_unlock (&lock->lock);
+          return err;
+        }
+      lock->waiting_writers_count--;
+    }
+  lock->runcount--; /* runcount becomes -1 */
+  return pthread_mutex_unlock (&lock->lock);
+}
+
+int
+glthread_rwlock_unlock_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_lock (&lock->lock);
+  if (err != 0)
+    return err;
+  if (lock->runcount < 0)
+    {
+      /* Drop a writer lock.  */
+      if (!(lock->runcount == -1))
+        {
+          pthread_mutex_unlock (&lock->lock);
+          return EINVAL;
+        }
+      lock->runcount = 0;
+    }
+  else
+    {
+      /* Drop a reader lock.  */
+      if (!(lock->runcount > 0))
+        {
+          pthread_mutex_unlock (&lock->lock);
+          return EINVAL;
+        }
+      lock->runcount--;
+    }
+  if (lock->runcount == 0)
+    {
+      /* POSIX recommends that "write locks shall take precedence over read
+         locks", to avoid "writer starvation".  */
+      if (lock->waiting_writers_count > 0)
+        {
+          /* Wake up one of the waiting writers.  */
+          err = pthread_cond_signal (&lock->waiting_writers);
+          if (err != 0)
+            {
+              pthread_mutex_unlock (&lock->lock);
+              return err;
+            }
+        }
+      else
+        {
+          /* Wake up all waiting readers.  */
+          err = pthread_cond_broadcast (&lock->waiting_readers);
+          if (err != 0)
+            {
+              pthread_mutex_unlock (&lock->lock);
+              return err;
+            }
+        }
+    }
+  return pthread_mutex_unlock (&lock->lock);
+}
+
+int
+glthread_rwlock_destroy_multithreaded (gl_rwlock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_destroy (&lock->lock);
+  if (err != 0)
+    return err;
+  err = pthread_cond_destroy (&lock->waiting_readers);
+  if (err != 0)
+    return err;
+  err = pthread_cond_destroy (&lock->waiting_writers);
+  if (err != 0)
+    return err;
+  return 0;
+}
+
+# endif
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+# if HAVE_PTHREAD_MUTEX_RECURSIVE
+
+#  if defined PTHREAD_RECURSIVE_MUTEX_INITIALIZER || defined PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+
+int
+glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock)
+{
+  pthread_mutexattr_t attributes;
+  int err;
+
+  err = pthread_mutexattr_init (&attributes);
+  if (err != 0)
+    return err;
+  err = pthread_mutexattr_settype (&attributes, PTHREAD_MUTEX_RECURSIVE);
+  if (err != 0)
+    {
+      pthread_mutexattr_destroy (&attributes);
+      return err;
+    }
+  err = pthread_mutex_init (lock, &attributes);
+  if (err != 0)
+    {
+      pthread_mutexattr_destroy (&attributes);
+      return err;
+    }
+  err = pthread_mutexattr_destroy (&attributes);
+  if (err != 0)
+    return err;
+  return 0;
+}
+
+#  else
+
+int
+glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock)
+{
+  pthread_mutexattr_t attributes;
+  int err;
+
+  err = pthread_mutexattr_init (&attributes);
+  if (err != 0)
+    return err;
+  err = pthread_mutexattr_settype (&attributes, PTHREAD_MUTEX_RECURSIVE);
+  if (err != 0)
+    {
+      pthread_mutexattr_destroy (&attributes);
+      return err;
+    }
+  err = pthread_mutex_init (&lock->recmutex, &attributes);
+  if (err != 0)
+    {
+      pthread_mutexattr_destroy (&attributes);
+      return err;
+    }
+  err = pthread_mutexattr_destroy (&attributes);
+  if (err != 0)
+    return err;
+  lock->initialized = 1;
+  return 0;
+}
+
+int
+glthread_recursive_lock_lock_multithreaded (gl_recursive_lock_t *lock)
+{
+  if (!lock->initialized)
+    {
+      int err;
+
+      err = pthread_mutex_lock (&lock->guard);
+      if (err != 0)
+        return err;
+      if (!lock->initialized)
+        {
+          err = glthread_recursive_lock_init_multithreaded (lock);
+          if (err != 0)
+            {
+              pthread_mutex_unlock (&lock->guard);
+              return err;
+            }
+        }
+      err = pthread_mutex_unlock (&lock->guard);
+      if (err != 0)
+        return err;
+    }
+  return pthread_mutex_lock (&lock->recmutex);
+}
+
+int
+glthread_recursive_lock_unlock_multithreaded (gl_recursive_lock_t *lock)
+{
+  if (!lock->initialized)
+    return EINVAL;
+  return pthread_mutex_unlock (&lock->recmutex);
+}
+
+int
+glthread_recursive_lock_destroy_multithreaded (gl_recursive_lock_t *lock)
+{
+  int err;
+
+  if (!lock->initialized)
+    return EINVAL;
+  err = pthread_mutex_destroy (&lock->recmutex);
+  if (err != 0)
+    return err;
+  lock->initialized = 0;
+  return 0;
+}
+
+#  endif
+
+# else
+
+int
+glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock)
+{
+  int err;
+
+  err = pthread_mutex_init (&lock->mutex, NULL);
+  if (err != 0)
+    return err;
+  lock->owner = (pthread_t) 0;
+  lock->depth = 0;
+  return 0;
+}
+
+int
+glthread_recursive_lock_lock_multithreaded (gl_recursive_lock_t *lock)
+{
+  pthread_t self = pthread_self ();
+  if (lock->owner != self)
+    {
+      int err;
+
+      err = pthread_mutex_lock (&lock->mutex);
+      if (err != 0)
+        return err;
+      lock->owner = self;
+    }
+  if (++(lock->depth) == 0) /* wraparound? */
+    {
+      lock->depth--;
+      return EAGAIN;
+    }
+  return 0;
+}
+
+int
+glthread_recursive_lock_unlock_multithreaded (gl_recursive_lock_t *lock)
+{
+  if (lock->owner != pthread_self ())
+    return EPERM;
+  if (lock->depth == 0)
+    return EINVAL;
+  if (--(lock->depth) == 0)
+    {
+      lock->owner = (pthread_t) 0;
+      return pthread_mutex_unlock (&lock->mutex);
+    }
+  else
+    return 0;
+}
+
+int
+glthread_recursive_lock_destroy_multithreaded (gl_recursive_lock_t *lock)
+{
+  if (lock->owner != (pthread_t) 0)
+    return EBUSY;
+  return pthread_mutex_destroy (&lock->mutex);
+}
+
+# endif
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+static const pthread_once_t fresh_once = PTHREAD_ONCE_INIT;
+
+int
+glthread_once_singlethreaded (pthread_once_t *once_control)
+{
+  /* We don't know whether pthread_once_t is an integer type, a floating-point
+     type, a pointer type, or a structure type.  */
+  char *firstbyte = (char *)once_control;
+  if (*firstbyte == *(const char *)&fresh_once)
+    {
+      /* First time use of once_control.  Invert the first byte.  */
+      *firstbyte = ~ *(const char *)&fresh_once;
+      return 1;
+    }
+  else
+    return 0;
+}
+
+# if !(PTHREAD_IN_USE_DETECTION_HARD || USE_POSIX_THREADS_WEAK)
+
+int
+glthread_once_multithreaded (pthread_once_t *once_control,
+                             void (*init_function) (void))
+{
+  int err = pthread_once (once_control, init_function);
+  if (err == ENOSYS)
+    {
+      /* This happens on FreeBSD 11: The pthread_once function in libc returns
+         ENOSYS.  */
+      if (glthread_once_singlethreaded (once_control))
+        init_function ();
+      return 0;
+    }
+  return err;
+}
+
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+#if USE_WINDOWS_THREADS
+
+#endif
+
+/* ========================================================================= */
diff --git a/lib/intl/lock.h b/lib/intl/lock.h
new file mode 100644 (file)
index 0000000..86dcd01
--- /dev/null
@@ -0,0 +1,791 @@
+/* Locking in multithreaded situations.
+   Copyright (C) 2005-2008, 2017-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2005.
+   Based on GCC's gthr-posix.h, gthr-posix95.h, gthr-win32.h.  */
+
+/* This file contains locking primitives for use with a given thread library.
+   It does not contain primitives for creating threads or for other
+   synchronization primitives.
+
+   Normal (non-recursive) locks:
+     Type:                gl_lock_t
+     Declaration:         gl_lock_define(extern, name)
+     Initializer:         gl_lock_define_initialized(, name)
+     Initialization:      gl_lock_init (name);
+     Taking the lock:     gl_lock_lock (name);
+     Releasing the lock:  gl_lock_unlock (name);
+     De-initialization:   gl_lock_destroy (name);
+   Equivalent functions with control of error handling:
+     Initialization:      err = glthread_lock_init (&name);
+     Taking the lock:     err = glthread_lock_lock (&name);
+     Releasing the lock:  err = glthread_lock_unlock (&name);
+     De-initialization:   err = glthread_lock_destroy (&name);
+
+   Read-Write (non-recursive) locks:
+     Type:                gl_rwlock_t
+     Declaration:         gl_rwlock_define(extern, name)
+     Initializer:         gl_rwlock_define_initialized(, name)
+     Initialization:      gl_rwlock_init (name);
+     Taking the lock:     gl_rwlock_rdlock (name);
+                          gl_rwlock_wrlock (name);
+     Releasing the lock:  gl_rwlock_unlock (name);
+     De-initialization:   gl_rwlock_destroy (name);
+   Equivalent functions with control of error handling:
+     Initialization:      err = glthread_rwlock_init (&name);
+     Taking the lock:     err = glthread_rwlock_rdlock (&name);
+                          err = glthread_rwlock_wrlock (&name);
+     Releasing the lock:  err = glthread_rwlock_unlock (&name);
+     De-initialization:   err = glthread_rwlock_destroy (&name);
+
+   Recursive locks:
+     Type:                gl_recursive_lock_t
+     Declaration:         gl_recursive_lock_define(extern, name)
+     Initializer:         gl_recursive_lock_define_initialized(, name)
+     Initialization:      gl_recursive_lock_init (name);
+     Taking the lock:     gl_recursive_lock_lock (name);
+     Releasing the lock:  gl_recursive_lock_unlock (name);
+     De-initialization:   gl_recursive_lock_destroy (name);
+   Equivalent functions with control of error handling:
+     Initialization:      err = glthread_recursive_lock_init (&name);
+     Taking the lock:     err = glthread_recursive_lock_lock (&name);
+     Releasing the lock:  err = glthread_recursive_lock_unlock (&name);
+     De-initialization:   err = glthread_recursive_lock_destroy (&name);
+
+  Once-only execution:
+     Type:                gl_once_t
+     Initializer:         gl_once_define(extern, name)
+     Execution:           gl_once (name, initfunction);
+   Equivalent functions with control of error handling:
+     Execution:           err = glthread_once (&name, initfunction);
+*/
+
+
+#ifndef _LOCK_H
+#define _LOCK_H
+
+#include <errno.h>
+#include <stdlib.h>
+
+#if !defined c11_threads_in_use
+# if HAVE_THREADS_H && USE_POSIX_THREADS_FROM_LIBC
+#  define c11_threads_in_use() 1
+# elif HAVE_THREADS_H && USE_POSIX_THREADS_WEAK
+#  include <threads.h>
+#  pragma weak thrd_exit
+#  define c11_threads_in_use() (thrd_exit != NULL)
+# else
+#  define c11_threads_in_use() 0
+# endif
+#endif
+
+/* ========================================================================= */
+
+#if USE_ISOC_THREADS || USE_ISOC_AND_POSIX_THREADS
+
+/* Use the ISO C threads library.  */
+
+# include <threads.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+typedef struct
+        {
+          int volatile init_needed;
+          once_flag init_once;
+          void (*init_func) (void);
+          mtx_t mutex;
+        }
+        gl_lock_t;
+# define gl_lock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_lock_t NAME;
+# define gl_lock_define_initialized(STORAGECLASS, NAME) \
+    static void _atomic_init_##NAME (void);       \
+    STORAGECLASS gl_lock_t NAME =                 \
+      { 1, ONCE_FLAG_INIT, _atomic_init_##NAME }; \
+    static void _atomic_init_##NAME (void)        \
+    {                                             \
+      if (glthread_lock_init (&(NAME)))           \
+        abort ();                                 \
+    }
+extern int glthread_lock_init (gl_lock_t *lock);
+extern int glthread_lock_lock (gl_lock_t *lock);
+extern int glthread_lock_unlock (gl_lock_t *lock);
+extern int glthread_lock_destroy (gl_lock_t *lock);
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+typedef struct
+        {
+          int volatile init_needed;
+          once_flag init_once;
+          void (*init_func) (void);
+          mtx_t lock; /* protects the remaining fields */
+          cnd_t waiting_readers; /* waiting readers */
+          cnd_t waiting_writers; /* waiting writers */
+          unsigned int waiting_writers_count; /* number of waiting writers */
+          int runcount; /* number of readers running, or -1 when a writer runs */
+        }
+        gl_rwlock_t;
+# define gl_rwlock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_rwlock_t NAME;
+# define gl_rwlock_define_initialized(STORAGECLASS, NAME) \
+    static void _atomic_init_##NAME (void);       \
+    STORAGECLASS gl_rwlock_t NAME =               \
+      { 1, ONCE_FLAG_INIT, _atomic_init_##NAME }; \
+    static void _atomic_init_##NAME (void)        \
+    {                                             \
+      if (glthread_rwlock_init (&(NAME)))         \
+        abort ();                                 \
+    }
+extern int glthread_rwlock_init (gl_rwlock_t *lock);
+extern int glthread_rwlock_rdlock (gl_rwlock_t *lock);
+extern int glthread_rwlock_wrlock (gl_rwlock_t *lock);
+extern int glthread_rwlock_unlock (gl_rwlock_t *lock);
+extern int glthread_rwlock_destroy (gl_rwlock_t *lock);
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+typedef struct
+        {
+          int volatile init_needed;
+          once_flag init_once;
+          void (*init_func) (void);
+          mtx_t mutex;
+        }
+        gl_recursive_lock_t;
+# define gl_recursive_lock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_recursive_lock_t NAME;
+# define gl_recursive_lock_define_initialized(STORAGECLASS, NAME) \
+    static void _atomic_init_##NAME (void);       \
+    STORAGECLASS gl_recursive_lock_t NAME =       \
+      { 1, ONCE_FLAG_INIT, _atomic_init_##NAME }; \
+    static void _atomic_init_##NAME (void)        \
+    {                                             \
+      if (glthread_recursive_lock_init (&(NAME))) \
+        abort ();                                 \
+    }
+extern int glthread_recursive_lock_init (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_lock (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_unlock (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_destroy (gl_recursive_lock_t *lock);
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+typedef once_flag gl_once_t;
+# define gl_once_define(STORAGECLASS, NAME) \
+    STORAGECLASS once_flag NAME = ONCE_FLAG_INIT;
+# define glthread_once(ONCE_CONTROL, INITFUNCTION) \
+    (call_once (ONCE_CONTROL, INITFUNCTION), 0)
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+#if USE_POSIX_THREADS
+
+/* Use the POSIX threads library.  */
+
+# include <pthread.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+# if PTHREAD_IN_USE_DETECTION_HARD
+
+/* The pthread_in_use() detection needs to be done at runtime.  */
+#  define pthread_in_use() \
+     glthread_in_use ()
+extern int glthread_in_use (void);
+
+# endif
+
+# if USE_POSIX_THREADS_WEAK
+
+/* Use weak references to the POSIX threads library.  */
+
+/* Weak references avoid dragging in external libraries if the other parts
+   of the program don't use them.  Here we use them, because we don't want
+   every program that uses libintl to depend on libpthread.  This assumes
+   that libpthread would not be loaded after libintl; i.e. if libintl is
+   loaded first, by an executable that does not depend on libpthread, and
+   then a module is dynamically loaded that depends on libpthread, libintl
+   will not be multithread-safe.  */
+
+/* The way to test at runtime whether libpthread is present is to test
+   whether a function pointer's value, such as &pthread_mutex_init, is
+   non-NULL.  However, some versions of GCC have a bug through which, in
+   PIC mode, &foo != NULL always evaluates to true if there is a direct
+   call to foo(...) in the same function.  To avoid this, we test the
+   address of a function in libpthread that we don't use.  */
+
+#  pragma weak pthread_mutex_init
+#  pragma weak pthread_mutex_lock
+#  pragma weak pthread_mutex_unlock
+#  pragma weak pthread_mutex_destroy
+#  pragma weak pthread_rwlock_init
+#  pragma weak pthread_rwlock_rdlock
+#  pragma weak pthread_rwlock_wrlock
+#  pragma weak pthread_rwlock_unlock
+#  pragma weak pthread_rwlock_destroy
+#  pragma weak pthread_once
+#  pragma weak pthread_cond_init
+#  pragma weak pthread_cond_wait
+#  pragma weak pthread_cond_signal
+#  pragma weak pthread_cond_broadcast
+#  pragma weak pthread_cond_destroy
+#  pragma weak pthread_mutexattr_init
+#  pragma weak pthread_mutexattr_settype
+#  pragma weak pthread_mutexattr_destroy
+#  pragma weak pthread_rwlockattr_init
+#  if __GNU_LIBRARY__ > 1
+#   pragma weak pthread_rwlockattr_setkind_np
+#  endif
+#  pragma weak pthread_rwlockattr_destroy
+#  ifndef pthread_self
+#   pragma weak pthread_self
+#  endif
+
+#  if !PTHREAD_IN_USE_DETECTION_HARD
+    /* Considering all platforms with USE_POSIX_THREADS_WEAK, only few symbols
+       can be used to determine whether libpthread is in use.  These are:
+         pthread_mutexattr_gettype
+         pthread_rwlockattr_destroy
+         pthread_rwlockattr_init
+     */
+#   pragma weak pthread_mutexattr_gettype
+#   define pthread_in_use() \
+      (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
+#  endif
+
+# else
+
+#  if !PTHREAD_IN_USE_DETECTION_HARD
+#   define pthread_in_use() 1
+#  endif
+
+# endif
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+typedef pthread_mutex_t gl_lock_t;
+# define gl_lock_define(STORAGECLASS, NAME) \
+    STORAGECLASS pthread_mutex_t NAME;
+# define gl_lock_define_initialized(STORAGECLASS, NAME) \
+    STORAGECLASS pthread_mutex_t NAME = gl_lock_initializer;
+# define gl_lock_initializer \
+    PTHREAD_MUTEX_INITIALIZER
+# define glthread_lock_init(LOCK) \
+    (pthread_in_use () ? pthread_mutex_init (LOCK, NULL) : 0)
+# define glthread_lock_lock(LOCK) \
+    (pthread_in_use () ? pthread_mutex_lock (LOCK) : 0)
+# define glthread_lock_unlock(LOCK) \
+    (pthread_in_use () ? pthread_mutex_unlock (LOCK) : 0)
+# define glthread_lock_destroy(LOCK) \
+    (pthread_in_use () ? pthread_mutex_destroy (LOCK) : 0)
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+# if HAVE_PTHREAD_RWLOCK && (HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER || (defined PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP && (__GNU_LIBRARY__ > 1)))
+
+#  if defined PTHREAD_RWLOCK_INITIALIZER || defined PTHREAD_RWLOCK_INITIALIZER_NP
+
+typedef pthread_rwlock_t gl_rwlock_t;
+#   define gl_rwlock_define(STORAGECLASS, NAME) \
+      STORAGECLASS pthread_rwlock_t NAME;
+#   define gl_rwlock_define_initialized(STORAGECLASS, NAME) \
+      STORAGECLASS pthread_rwlock_t NAME = gl_rwlock_initializer;
+#   if HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER
+#    if defined PTHREAD_RWLOCK_INITIALIZER
+#     define gl_rwlock_initializer \
+        PTHREAD_RWLOCK_INITIALIZER
+#    else
+#     define gl_rwlock_initializer \
+        PTHREAD_RWLOCK_INITIALIZER_NP
+#    endif
+#    define glthread_rwlock_init(LOCK) \
+       (pthread_in_use () ? pthread_rwlock_init (LOCK, NULL) : 0)
+#   else /* glibc with bug https://sourceware.org/bugzilla/show_bug.cgi?id=13701 */
+#    define gl_rwlock_initializer \
+       PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP
+#    define glthread_rwlock_init(LOCK) \
+       (pthread_in_use () ? glthread_rwlock_init_for_glibc (LOCK) : 0)
+extern int glthread_rwlock_init_for_glibc (pthread_rwlock_t *lock);
+#   endif
+#   define glthread_rwlock_rdlock(LOCK) \
+      (pthread_in_use () ? pthread_rwlock_rdlock (LOCK) : 0)
+#   define glthread_rwlock_wrlock(LOCK) \
+      (pthread_in_use () ? pthread_rwlock_wrlock (LOCK) : 0)
+#   define glthread_rwlock_unlock(LOCK) \
+      (pthread_in_use () ? pthread_rwlock_unlock (LOCK) : 0)
+#   define glthread_rwlock_destroy(LOCK) \
+      (pthread_in_use () ? pthread_rwlock_destroy (LOCK) : 0)
+
+#  else
+
+typedef struct
+        {
+          int initialized;
+          pthread_mutex_t guard;   /* protects the initialization */
+          pthread_rwlock_t rwlock; /* read-write lock */
+        }
+        gl_rwlock_t;
+#   define gl_rwlock_define(STORAGECLASS, NAME) \
+      STORAGECLASS gl_rwlock_t NAME;
+#   define gl_rwlock_define_initialized(STORAGECLASS, NAME) \
+      STORAGECLASS gl_rwlock_t NAME = gl_rwlock_initializer;
+#   define gl_rwlock_initializer \
+      { 0, PTHREAD_MUTEX_INITIALIZER }
+#   define glthread_rwlock_init(LOCK) \
+      (pthread_in_use () ? glthread_rwlock_init_multithreaded (LOCK) : 0)
+#   define glthread_rwlock_rdlock(LOCK) \
+      (pthread_in_use () ? glthread_rwlock_rdlock_multithreaded (LOCK) : 0)
+#   define glthread_rwlock_wrlock(LOCK) \
+      (pthread_in_use () ? glthread_rwlock_wrlock_multithreaded (LOCK) : 0)
+#   define glthread_rwlock_unlock(LOCK) \
+      (pthread_in_use () ? glthread_rwlock_unlock_multithreaded (LOCK) : 0)
+#   define glthread_rwlock_destroy(LOCK) \
+      (pthread_in_use () ? glthread_rwlock_destroy_multithreaded (LOCK) : 0)
+extern int glthread_rwlock_init_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_rdlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_wrlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_unlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_destroy_multithreaded (gl_rwlock_t *lock);
+
+#  endif
+
+# else
+
+typedef struct
+        {
+          pthread_mutex_t lock; /* protects the remaining fields */
+          pthread_cond_t waiting_readers; /* waiting readers */
+          pthread_cond_t waiting_writers; /* waiting writers */
+          unsigned int waiting_writers_count; /* number of waiting writers */
+          int runcount; /* number of readers running, or -1 when a writer runs */
+        }
+        gl_rwlock_t;
+# define gl_rwlock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_rwlock_t NAME;
+# define gl_rwlock_define_initialized(STORAGECLASS, NAME) \
+    STORAGECLASS gl_rwlock_t NAME = gl_rwlock_initializer;
+# define gl_rwlock_initializer \
+    { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, 0 }
+# define glthread_rwlock_init(LOCK) \
+    (pthread_in_use () ? glthread_rwlock_init_multithreaded (LOCK) : 0)
+# define glthread_rwlock_rdlock(LOCK) \
+    (pthread_in_use () ? glthread_rwlock_rdlock_multithreaded (LOCK) : 0)
+# define glthread_rwlock_wrlock(LOCK) \
+    (pthread_in_use () ? glthread_rwlock_wrlock_multithreaded (LOCK) : 0)
+# define glthread_rwlock_unlock(LOCK) \
+    (pthread_in_use () ? glthread_rwlock_unlock_multithreaded (LOCK) : 0)
+# define glthread_rwlock_destroy(LOCK) \
+    (pthread_in_use () ? glthread_rwlock_destroy_multithreaded (LOCK) : 0)
+extern int glthread_rwlock_init_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_rdlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_wrlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_unlock_multithreaded (gl_rwlock_t *lock);
+extern int glthread_rwlock_destroy_multithreaded (gl_rwlock_t *lock);
+
+# endif
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+# if HAVE_PTHREAD_MUTEX_RECURSIVE
+
+#  if defined PTHREAD_RECURSIVE_MUTEX_INITIALIZER || defined PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+
+typedef pthread_mutex_t gl_recursive_lock_t;
+#   define gl_recursive_lock_define(STORAGECLASS, NAME) \
+      STORAGECLASS pthread_mutex_t NAME;
+#   define gl_recursive_lock_define_initialized(STORAGECLASS, NAME) \
+      STORAGECLASS pthread_mutex_t NAME = gl_recursive_lock_initializer;
+#   ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER
+#    define gl_recursive_lock_initializer \
+       PTHREAD_RECURSIVE_MUTEX_INITIALIZER
+#   else
+#    define gl_recursive_lock_initializer \
+       PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+#   endif
+#   define glthread_recursive_lock_init(LOCK) \
+      (pthread_in_use () ? glthread_recursive_lock_init_multithreaded (LOCK) : 0)
+#   define glthread_recursive_lock_lock(LOCK) \
+      (pthread_in_use () ? pthread_mutex_lock (LOCK) : 0)
+#   define glthread_recursive_lock_unlock(LOCK) \
+      (pthread_in_use () ? pthread_mutex_unlock (LOCK) : 0)
+#   define glthread_recursive_lock_destroy(LOCK) \
+      (pthread_in_use () ? pthread_mutex_destroy (LOCK) : 0)
+extern int glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock);
+
+#  else
+
+typedef struct
+        {
+          pthread_mutex_t recmutex; /* recursive mutex */
+          pthread_mutex_t guard;    /* protects the initialization */
+          int initialized;
+        }
+        gl_recursive_lock_t;
+#   define gl_recursive_lock_define(STORAGECLASS, NAME) \
+      STORAGECLASS gl_recursive_lock_t NAME;
+#   define gl_recursive_lock_define_initialized(STORAGECLASS, NAME) \
+      STORAGECLASS gl_recursive_lock_t NAME = gl_recursive_lock_initializer;
+#   define gl_recursive_lock_initializer \
+      { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, 0 }
+#   define glthread_recursive_lock_init(LOCK) \
+      (pthread_in_use () ? glthread_recursive_lock_init_multithreaded (LOCK) : 0)
+#   define glthread_recursive_lock_lock(LOCK) \
+      (pthread_in_use () ? glthread_recursive_lock_lock_multithreaded (LOCK) : 0)
+#   define glthread_recursive_lock_unlock(LOCK) \
+      (pthread_in_use () ? glthread_recursive_lock_unlock_multithreaded (LOCK) : 0)
+#   define glthread_recursive_lock_destroy(LOCK) \
+      (pthread_in_use () ? glthread_recursive_lock_destroy_multithreaded (LOCK) : 0)
+extern int glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_lock_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_unlock_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_destroy_multithreaded (gl_recursive_lock_t *lock);
+
+#  endif
+
+# else
+
+/* Old versions of POSIX threads on Solaris did not have recursive locks.
+   We have to implement them ourselves.  */
+
+typedef struct
+        {
+          pthread_mutex_t mutex;
+          pthread_t owner;
+          unsigned long depth;
+        }
+        gl_recursive_lock_t;
+#  define gl_recursive_lock_define(STORAGECLASS, NAME) \
+     STORAGECLASS gl_recursive_lock_t NAME;
+#  define gl_recursive_lock_define_initialized(STORAGECLASS, NAME) \
+     STORAGECLASS gl_recursive_lock_t NAME = gl_recursive_lock_initializer;
+#  define gl_recursive_lock_initializer \
+     { PTHREAD_MUTEX_INITIALIZER, (pthread_t) 0, 0 }
+#  define glthread_recursive_lock_init(LOCK) \
+     (pthread_in_use () ? glthread_recursive_lock_init_multithreaded (LOCK) : 0)
+#  define glthread_recursive_lock_lock(LOCK) \
+     (pthread_in_use () ? glthread_recursive_lock_lock_multithreaded (LOCK) : 0)
+#  define glthread_recursive_lock_unlock(LOCK) \
+     (pthread_in_use () ? glthread_recursive_lock_unlock_multithreaded (LOCK) : 0)
+#  define glthread_recursive_lock_destroy(LOCK) \
+     (pthread_in_use () ? glthread_recursive_lock_destroy_multithreaded (LOCK) : 0)
+extern int glthread_recursive_lock_init_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_lock_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_unlock_multithreaded (gl_recursive_lock_t *lock);
+extern int glthread_recursive_lock_destroy_multithreaded (gl_recursive_lock_t *lock);
+
+# endif
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+typedef pthread_once_t gl_once_t;
+# define gl_once_define(STORAGECLASS, NAME) \
+    STORAGECLASS pthread_once_t NAME = PTHREAD_ONCE_INIT;
+# if PTHREAD_IN_USE_DETECTION_HARD || USE_POSIX_THREADS_WEAK
+#  define glthread_once(ONCE_CONTROL, INITFUNCTION) \
+     (pthread_in_use ()                                                        \
+      ? pthread_once (ONCE_CONTROL, INITFUNCTION)                              \
+      : (glthread_once_singlethreaded (ONCE_CONTROL) ? (INITFUNCTION (), 0) : 0))
+# else
+#  define glthread_once(ONCE_CONTROL, INITFUNCTION) \
+     (pthread_in_use ()                                                        \
+      ? glthread_once_multithreaded (ONCE_CONTROL, INITFUNCTION)               \
+      : (glthread_once_singlethreaded (ONCE_CONTROL) ? (INITFUNCTION (), 0) : 0))
+extern int glthread_once_multithreaded (pthread_once_t *once_control,
+                                        void (*init_function) (void));
+# endif
+extern int glthread_once_singlethreaded (pthread_once_t *once_control);
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+#if USE_WINDOWS_THREADS
+
+# define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+# include <windows.h>
+
+# include "windows-mutex.h"
+# include "windows-rwlock.h"
+# include "windows-recmutex.h"
+# include "windows-once.h"
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/* We can use CRITICAL_SECTION directly, rather than the native Windows Event,
+   Mutex, Semaphore types, because
+     - we need only to synchronize inside a single process (address space),
+       not inter-process locking,
+     - we don't need to support trylock operations.  (TryEnterCriticalSection
+       does not work on Windows 95/98/ME.  Packages that need trylock usually
+       define their own mutex type.)  */
+
+/* There is no way to statically initialize a CRITICAL_SECTION.  It needs
+   to be done lazily, once only.  For this we need spinlocks.  */
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+typedef glwthread_mutex_t gl_lock_t;
+# define gl_lock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_lock_t NAME;
+# define gl_lock_define_initialized(STORAGECLASS, NAME) \
+    STORAGECLASS gl_lock_t NAME = gl_lock_initializer;
+# define gl_lock_initializer \
+    GLWTHREAD_MUTEX_INIT
+# define glthread_lock_init(LOCK) \
+    (glwthread_mutex_init (LOCK), 0)
+# define glthread_lock_lock(LOCK) \
+    glwthread_mutex_lock (LOCK)
+# define glthread_lock_unlock(LOCK) \
+    glwthread_mutex_unlock (LOCK)
+# define glthread_lock_destroy(LOCK) \
+    glwthread_mutex_destroy (LOCK)
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+typedef glwthread_rwlock_t gl_rwlock_t;
+# define gl_rwlock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_rwlock_t NAME;
+# define gl_rwlock_define_initialized(STORAGECLASS, NAME) \
+    STORAGECLASS gl_rwlock_t NAME = gl_rwlock_initializer;
+# define gl_rwlock_initializer \
+    GLWTHREAD_RWLOCK_INIT
+# define glthread_rwlock_init(LOCK) \
+    (glwthread_rwlock_init (LOCK), 0)
+# define glthread_rwlock_rdlock(LOCK) \
+    glwthread_rwlock_rdlock (LOCK)
+# define glthread_rwlock_wrlock(LOCK) \
+    glwthread_rwlock_wrlock (LOCK)
+# define glthread_rwlock_unlock(LOCK) \
+    glwthread_rwlock_unlock (LOCK)
+# define glthread_rwlock_destroy(LOCK) \
+    glwthread_rwlock_destroy (LOCK)
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+typedef glwthread_recmutex_t gl_recursive_lock_t;
+# define gl_recursive_lock_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_recursive_lock_t NAME;
+# define gl_recursive_lock_define_initialized(STORAGECLASS, NAME) \
+    STORAGECLASS gl_recursive_lock_t NAME = gl_recursive_lock_initializer;
+# define gl_recursive_lock_initializer \
+    GLWTHREAD_RECMUTEX_INIT
+# define glthread_recursive_lock_init(LOCK) \
+    (glwthread_recmutex_init (LOCK), 0)
+# define glthread_recursive_lock_lock(LOCK) \
+    glwthread_recmutex_lock (LOCK)
+# define glthread_recursive_lock_unlock(LOCK) \
+    glwthread_recmutex_unlock (LOCK)
+# define glthread_recursive_lock_destroy(LOCK) \
+    glwthread_recmutex_destroy (LOCK)
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+typedef glwthread_once_t gl_once_t;
+# define gl_once_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_once_t NAME = GLWTHREAD_ONCE_INIT;
+# define glthread_once(ONCE_CONTROL, INITFUNCTION) \
+    (glwthread_once (ONCE_CONTROL, INITFUNCTION), 0)
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+#if !(USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS)
+
+/* Provide dummy implementation if threads are not supported.  */
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+typedef int gl_lock_t;
+# define gl_lock_define(STORAGECLASS, NAME)
+# define gl_lock_define_initialized(STORAGECLASS, NAME)
+# define glthread_lock_init(NAME) 0
+# define glthread_lock_lock(NAME) 0
+# define glthread_lock_unlock(NAME) 0
+# define glthread_lock_destroy(NAME) 0
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+typedef int gl_rwlock_t;
+# define gl_rwlock_define(STORAGECLASS, NAME)
+# define gl_rwlock_define_initialized(STORAGECLASS, NAME)
+# define glthread_rwlock_init(NAME) 0
+# define glthread_rwlock_rdlock(NAME) 0
+# define glthread_rwlock_wrlock(NAME) 0
+# define glthread_rwlock_unlock(NAME) 0
+# define glthread_rwlock_destroy(NAME) 0
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+typedef int gl_recursive_lock_t;
+# define gl_recursive_lock_define(STORAGECLASS, NAME)
+# define gl_recursive_lock_define_initialized(STORAGECLASS, NAME)
+# define glthread_recursive_lock_init(NAME) 0
+# define glthread_recursive_lock_lock(NAME) 0
+# define glthread_recursive_lock_unlock(NAME) 0
+# define glthread_recursive_lock_destroy(NAME) 0
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+typedef int gl_once_t;
+# define gl_once_define(STORAGECLASS, NAME) \
+    STORAGECLASS gl_once_t NAME = 0;
+# define glthread_once(ONCE_CONTROL, INITFUNCTION) \
+    (*(ONCE_CONTROL) == 0 ? (*(ONCE_CONTROL) = ~ 0, INITFUNCTION (), 0) : 0)
+
+#endif
+
+/* ========================================================================= */
+
+/* Macros with built-in error handling.  */
+
+/* -------------------------- gl_lock_t datatype -------------------------- */
+
+#define gl_lock_init(NAME) \
+   do                                  \
+     {                                 \
+       if (glthread_lock_init (&NAME)) \
+         abort ();                     \
+     }                                 \
+   while (0)
+#define gl_lock_lock(NAME) \
+   do                                  \
+     {                                 \
+       if (glthread_lock_lock (&NAME)) \
+         abort ();                     \
+     }                                 \
+   while (0)
+#define gl_lock_unlock(NAME) \
+   do                                    \
+     {                                   \
+       if (glthread_lock_unlock (&NAME)) \
+         abort ();                       \
+     }                                   \
+   while (0)
+#define gl_lock_destroy(NAME) \
+   do                                     \
+     {                                    \
+       if (glthread_lock_destroy (&NAME)) \
+         abort ();                        \
+     }                                    \
+   while (0)
+
+/* ------------------------- gl_rwlock_t datatype ------------------------- */
+
+#define gl_rwlock_init(NAME) \
+   do                                    \
+     {                                   \
+       if (glthread_rwlock_init (&NAME)) \
+         abort ();                       \
+     }                                   \
+   while (0)
+#define gl_rwlock_rdlock(NAME) \
+   do                                      \
+     {                                     \
+       if (glthread_rwlock_rdlock (&NAME)) \
+         abort ();                         \
+     }                                     \
+   while (0)
+#define gl_rwlock_wrlock(NAME) \
+   do                                      \
+     {                                     \
+       if (glthread_rwlock_wrlock (&NAME)) \
+         abort ();                         \
+     }                                     \
+   while (0)
+#define gl_rwlock_unlock(NAME) \
+   do                                      \
+     {                                     \
+       if (glthread_rwlock_unlock (&NAME)) \
+         abort ();                         \
+     }                                     \
+   while (0)
+#define gl_rwlock_destroy(NAME) \
+   do                                       \
+     {                                      \
+       if (glthread_rwlock_destroy (&NAME)) \
+         abort ();                          \
+     }                                      \
+   while (0)
+
+/* --------------------- gl_recursive_lock_t datatype --------------------- */
+
+#define gl_recursive_lock_init(NAME) \
+   do                                            \
+     {                                           \
+       if (glthread_recursive_lock_init (&NAME)) \
+         abort ();                               \
+     }                                           \
+   while (0)
+#define gl_recursive_lock_lock(NAME) \
+   do                                            \
+     {                                           \
+       if (glthread_recursive_lock_lock (&NAME)) \
+         abort ();                               \
+     }                                           \
+   while (0)
+#define gl_recursive_lock_unlock(NAME) \
+   do                                              \
+     {                                             \
+       if (glthread_recursive_lock_unlock (&NAME)) \
+         abort ();                                 \
+     }                                             \
+   while (0)
+#define gl_recursive_lock_destroy(NAME) \
+   do                                               \
+     {                                              \
+       if (glthread_recursive_lock_destroy (&NAME)) \
+         abort ();                                  \
+     }                                              \
+   while (0)
+
+/* -------------------------- gl_once_t datatype -------------------------- */
+
+#define gl_once(NAME, INITFUNCTION) \
+   do                                           \
+     {                                          \
+       if (glthread_once (&NAME, INITFUNCTION)) \
+         abort ();                              \
+     }                                          \
+   while (0)
+
+/* ========================================================================= */
+
+#endif /* _LOCK_H */
diff --git a/lib/intl/plural.h b/lib/intl/plural.h
new file mode 100644 (file)
index 0000000..f894a94
--- /dev/null
@@ -0,0 +1,90 @@
+/* A Bison parser, made by GNU Bison 3.8.2.  */
+
+/* Bison interface for Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+   Inc.
+
+   This program 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.
+
+   This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+   especially those whose name start with YY_ or yy_.  They are
+   private implementation details that can be changed or removed.  */
+
+#ifndef YY__GETTEXT_PLURAL_TAB_H_INCLUDED
+# define YY__GETTEXT_PLURAL_TAB_H_INCLUDED
+/* Debug traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int __gettextdebug;
+#endif
+
+/* Token kinds.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+  enum yytokentype
+  {
+    YYEMPTY = -2,
+    YYEOF = 0,                     /* "end of file"  */
+    YYerror = 256,                 /* error  */
+    YYUNDEF = 257,                 /* "invalid token"  */
+    EQUOP2 = 258,                  /* EQUOP2  */
+    CMPOP2 = 259,                  /* CMPOP2  */
+    ADDOP2 = 260,                  /* ADDOP2  */
+    MULOP2 = 261,                  /* MULOP2  */
+    NUMBER = 262                   /* NUMBER  */
+  };
+  typedef enum yytokentype yytoken_kind_t;
+#endif
+
+/* Value type.  */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 42 "plural.y"
+
+  unsigned long int num;
+  enum expression_operator op;
+  struct expression *exp;
+
+#line 77 "plural.h"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+
+
+int __gettextparse (struct parse_args *arg);
+
+
+#endif /* !YY__GETTEXT_PLURAL_TAB_H_INCLUDED  */
diff --git a/lib/intl/printf-args.c b/lib/intl/printf-args.c
new file mode 100644 (file)
index 0000000..af120ba
--- /dev/null
@@ -0,0 +1,186 @@
+/* Decomposed printf argument list.
+   Copyright (C) 1999, 2002-2003, 2005-2007 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This file can be parametrized with the following macros:
+     ENABLE_UNISTDIO    Set to 1 to enable the unistdio extensions.
+     PRINTF_FETCHARGS   Name of the function to be defined.
+     STATIC             Set to 'static' to declare the function static.  */
+
+#ifndef PRINTF_FETCHARGS
+# include <config.h>
+#endif
+
+/* Specification.  */
+#ifndef PRINTF_FETCHARGS
+# include "printf-args.h"
+#endif
+
+#ifdef STATIC
+STATIC
+#endif
+int
+PRINTF_FETCHARGS (va_list args, arguments *a)
+{
+  size_t i;
+  argument *ap;
+
+  for (i = 0, ap = &a->arg[0]; i < a->count; i++, ap++)
+    switch (ap->type)
+      {
+      case TYPE_SCHAR:
+        ap->a.a_schar = va_arg (args, /*signed char*/ int);
+        break;
+      case TYPE_UCHAR:
+        ap->a.a_uchar = va_arg (args, /*unsigned char*/ int);
+        break;
+      case TYPE_SHORT:
+        ap->a.a_short = va_arg (args, /*short*/ int);
+        break;
+      case TYPE_USHORT:
+        ap->a.a_ushort = va_arg (args, /*unsigned short*/ int);
+        break;
+      case TYPE_INT:
+        ap->a.a_int = va_arg (args, int);
+        break;
+      case TYPE_UINT:
+        ap->a.a_uint = va_arg (args, unsigned int);
+        break;
+      case TYPE_LONGINT:
+        ap->a.a_longint = va_arg (args, long int);
+        break;
+      case TYPE_ULONGINT:
+        ap->a.a_ulongint = va_arg (args, unsigned long int);
+        break;
+#if HAVE_LONG_LONG_INT
+      case TYPE_LONGLONGINT:
+        ap->a.a_longlongint = va_arg (args, long long int);
+        break;
+      case TYPE_ULONGLONGINT:
+        ap->a.a_ulonglongint = va_arg (args, unsigned long long int);
+        break;
+#endif
+      case TYPE_DOUBLE:
+        ap->a.a_double = va_arg (args, double);
+        break;
+      case TYPE_LONGDOUBLE:
+        ap->a.a_longdouble = va_arg (args, long double);
+        break;
+      case TYPE_CHAR:
+        ap->a.a_char = va_arg (args, int);
+        break;
+#if HAVE_WINT_T
+      case TYPE_WIDE_CHAR:
+        /* Although ISO C 99 7.24.1.(2) says that wint_t is "unchanged by
+           default argument promotions", this is not the case in mingw32,
+           where wint_t is 'unsigned short'.  */
+        ap->a.a_wide_char =
+          (sizeof (wint_t) < sizeof (int)
+           ? va_arg (args, int)
+           : va_arg (args, wint_t));
+        break;
+#endif
+      case TYPE_STRING:
+        ap->a.a_string = va_arg (args, const char *);
+        /* A null pointer is an invalid argument for "%s", but in practice
+           it occurs quite frequently in printf statements that produce
+           debug output.  Use a fallback in this case.  */
+        if (ap->a.a_string == NULL)
+          ap->a.a_string = "(NULL)";
+        break;
+#if HAVE_WCHAR_T
+      case TYPE_WIDE_STRING:
+        ap->a.a_wide_string = va_arg (args, const wchar_t *);
+        /* A null pointer is an invalid argument for "%ls", but in practice
+           it occurs quite frequently in printf statements that produce
+           debug output.  Use a fallback in this case.  */
+        if (ap->a.a_wide_string == NULL)
+          {
+            static const wchar_t wide_null_string[] =
+              {
+                (wchar_t)'(',
+                (wchar_t)'N', (wchar_t)'U', (wchar_t)'L', (wchar_t)'L',
+                (wchar_t)')',
+                (wchar_t)0
+              };
+            ap->a.a_wide_string = wide_null_string;
+          }
+        break;
+#endif
+      case TYPE_POINTER:
+        ap->a.a_pointer = va_arg (args, void *);
+        break;
+      case TYPE_COUNT_SCHAR_POINTER:
+        ap->a.a_count_schar_pointer = va_arg (args, signed char *);
+        break;
+      case TYPE_COUNT_SHORT_POINTER:
+        ap->a.a_count_short_pointer = va_arg (args, short *);
+        break;
+      case TYPE_COUNT_INT_POINTER:
+        ap->a.a_count_int_pointer = va_arg (args, int *);
+        break;
+      case TYPE_COUNT_LONGINT_POINTER:
+        ap->a.a_count_longint_pointer = va_arg (args, long int *);
+        break;
+#if HAVE_LONG_LONG_INT
+      case TYPE_COUNT_LONGLONGINT_POINTER:
+        ap->a.a_count_longlongint_pointer = va_arg (args, long long int *);
+        break;
+#endif
+#if ENABLE_UNISTDIO
+      /* The unistdio extensions.  */
+      case TYPE_U8_STRING:
+        ap->a.a_u8_string = va_arg (args, const uint8_t *);
+        /* A null pointer is an invalid argument for "%U", but in practice
+           it occurs quite frequently in printf statements that produce
+           debug output.  Use a fallback in this case.  */
+        if (ap->a.a_u8_string == NULL)
+          {
+            static const uint8_t u8_null_string[] =
+              { '(', 'N', 'U', 'L', 'L', ')', 0 };
+            ap->a.a_u8_string = u8_null_string;
+          }
+        break;
+      case TYPE_U16_STRING:
+        ap->a.a_u16_string = va_arg (args, const uint16_t *);
+        /* A null pointer is an invalid argument for "%lU", but in practice
+           it occurs quite frequently in printf statements that produce
+           debug output.  Use a fallback in this case.  */
+        if (ap->a.a_u16_string == NULL)
+          {
+            static const uint16_t u16_null_string[] =
+              { '(', 'N', 'U', 'L', 'L', ')', 0 };
+            ap->a.a_u16_string = u16_null_string;
+          }
+        break;
+      case TYPE_U32_STRING:
+        ap->a.a_u32_string = va_arg (args, const uint32_t *);
+        /* A null pointer is an invalid argument for "%llU", but in practice
+           it occurs quite frequently in printf statements that produce
+           debug output.  Use a fallback in this case.  */
+        if (ap->a.a_u32_string == NULL)
+          {
+            static const uint32_t u32_null_string[] =
+              { '(', 'N', 'U', 'L', 'L', ')', 0 };
+            ap->a.a_u32_string = u32_null_string;
+          }
+        break;
+#endif
+      default:
+        /* Unknown type.  */
+        return -1;
+      }
+  return 0;
+}
diff --git a/lib/intl/printf-args.h b/lib/intl/printf-args.h
new file mode 100644 (file)
index 0000000..a0f7159
--- /dev/null
@@ -0,0 +1,157 @@
+/* Decomposed printf argument list.
+   Copyright (C) 1999, 2002-2003, 2006-2007, 2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _PRINTF_ARGS_H
+#define _PRINTF_ARGS_H
+
+/* This file can be parametrized with the following macros:
+     ENABLE_UNISTDIO    Set to 1 to enable the unistdio extensions.
+     PRINTF_FETCHARGS   Name of the function to be declared.
+     STATIC             Set to 'static' to declare the function static.  */
+
+/* Default parameters.  */
+#ifndef PRINTF_FETCHARGS
+# define PRINTF_FETCHARGS printf_fetchargs
+#endif
+
+/* Get size_t.  */
+#include <stddef.h>
+
+/* Get wchar_t.  */
+#if HAVE_WCHAR_T
+# include <stddef.h>
+#endif
+
+/* Get wint_t.  */
+#if HAVE_WINT_T
+# include <wchar.h>
+#endif
+
+/* Get va_list.  */
+#include <stdarg.h>
+
+
+/* Argument types */
+typedef enum
+{
+  TYPE_NONE,
+  TYPE_SCHAR,
+  TYPE_UCHAR,
+  TYPE_SHORT,
+  TYPE_USHORT,
+  TYPE_INT,
+  TYPE_UINT,
+  TYPE_LONGINT,
+  TYPE_ULONGINT,
+#if HAVE_LONG_LONG_INT
+  TYPE_LONGLONGINT,
+  TYPE_ULONGLONGINT,
+#endif
+  TYPE_DOUBLE,
+  TYPE_LONGDOUBLE,
+  TYPE_CHAR,
+#if HAVE_WINT_T
+  TYPE_WIDE_CHAR,
+#endif
+  TYPE_STRING,
+#if HAVE_WCHAR_T
+  TYPE_WIDE_STRING,
+#endif
+  TYPE_POINTER,
+  TYPE_COUNT_SCHAR_POINTER,
+  TYPE_COUNT_SHORT_POINTER,
+  TYPE_COUNT_INT_POINTER,
+  TYPE_COUNT_LONGINT_POINTER
+#if HAVE_LONG_LONG_INT
+, TYPE_COUNT_LONGLONGINT_POINTER
+#endif
+#if ENABLE_UNISTDIO
+  /* The unistdio extensions.  */
+, TYPE_U8_STRING
+, TYPE_U16_STRING
+, TYPE_U32_STRING
+#endif
+} arg_type;
+
+/* Polymorphic argument */
+typedef struct
+{
+  arg_type type;
+  union
+  {
+    signed char                 a_schar;
+    unsigned char               a_uchar;
+    short                       a_short;
+    unsigned short              a_ushort;
+    int                         a_int;
+    unsigned int                a_uint;
+    long int                    a_longint;
+    unsigned long int           a_ulongint;
+#if HAVE_LONG_LONG_INT
+    long long int               a_longlongint;
+    unsigned long long int      a_ulonglongint;
+#endif
+    float                       a_float;
+    double                      a_double;
+    long double                 a_longdouble;
+    int                         a_char;
+#if HAVE_WINT_T
+    wint_t                      a_wide_char;
+#endif
+    const char*                 a_string;
+#if HAVE_WCHAR_T
+    const wchar_t*              a_wide_string;
+#endif
+    void*                       a_pointer;
+    signed char *               a_count_schar_pointer;
+    short *                     a_count_short_pointer;
+    int *                       a_count_int_pointer;
+    long int *                  a_count_longint_pointer;
+#if HAVE_LONG_LONG_INT
+    long long int *             a_count_longlongint_pointer;
+#endif
+#if ENABLE_UNISTDIO
+    /* The unistdio extensions.  */
+    const uint8_t *             a_u8_string;
+    const uint16_t *            a_u16_string;
+    const uint32_t *            a_u32_string;
+#endif
+  }
+  a;
+}
+argument;
+
+/* Number of directly allocated arguments (no malloc() needed).  */
+#define N_DIRECT_ALLOC_ARGUMENTS 7
+
+typedef struct
+{
+  size_t count;
+  argument *arg;
+  argument direct_alloc_arg[N_DIRECT_ALLOC_ARGUMENTS];
+}
+arguments;
+
+
+/* Fetch the arguments, putting them into a. */
+#ifdef STATIC
+STATIC
+#else
+extern
+#endif
+int PRINTF_FETCHARGS (va_list args, arguments *a);
+
+#endif /* _PRINTF_ARGS_H */
diff --git a/lib/intl/printf-parse.c b/lib/intl/printf-parse.c
new file mode 100644 (file)
index 0000000..7de467a
--- /dev/null
@@ -0,0 +1,639 @@
+/* Formatted output to strings.
+   Copyright (C) 1999-2000, 2002-2003, 2006-2008, 2010-2011, 2018 Free Software
+   Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This file can be parametrized with the following macros:
+     CHAR_T             The element type of the format string.
+     CHAR_T_ONLY_ASCII  Set to 1 to enable verification that all characters
+                        in the format string are ASCII.
+     DIRECTIVE          Structure denoting a format directive.
+                        Depends on CHAR_T.
+     DIRECTIVES         Structure denoting the set of format directives of a
+                        format string.  Depends on CHAR_T.
+     PRINTF_PARSE       Function that parses a format string.
+                        Depends on CHAR_T.
+     STATIC             Set to 'static' to declare the function static.
+     ENABLE_UNISTDIO    Set to 1 to enable the unistdio extensions.  */
+
+#ifndef PRINTF_PARSE
+# include <config.h>
+#endif
+
+/* Specification.  */
+#ifndef PRINTF_PARSE
+# include "printf-parse.h"
+#endif
+
+/* Default parameters.  */
+#ifndef PRINTF_PARSE
+# define PRINTF_PARSE printf_parse
+# define CHAR_T char
+# define DIRECTIVE char_directive
+# define DIRECTIVES char_directives
+#endif
+
+/* Get size_t, NULL.  */
+#include <stddef.h>
+
+/* Get intmax_t.  */
+#if defined IN_LIBINTL || defined IN_LIBASPRINTF
+# if HAVE_STDINT_H_WITH_UINTMAX
+#  include <stdint.h>
+# endif
+# if HAVE_INTTYPES_H_WITH_UINTMAX
+#  include <inttypes.h>
+# endif
+#else
+# include <stdint.h>
+#endif
+
+/* malloc(), realloc(), free().  */
+#include <stdlib.h>
+
+/* memcpy().  */
+#include <string.h>
+
+/* errno.  */
+#include <errno.h>
+
+/* Checked size_t computations.  */
+#include "xsize.h"
+
+#if CHAR_T_ONLY_ASCII
+/* c_isascii().  */
+# include "c-ctype.h"
+#endif
+
+#ifdef STATIC
+STATIC
+#endif
+int
+PRINTF_PARSE (const CHAR_T *format, DIRECTIVES *d, arguments *a)
+{
+  const CHAR_T *cp = format;    /* pointer into format */
+  size_t arg_posn = 0;          /* number of regular arguments consumed */
+  size_t d_allocated;           /* allocated elements of d->dir */
+  size_t a_allocated;           /* allocated elements of a->arg */
+  size_t max_width_length = 0;
+  size_t max_precision_length = 0;
+
+  d->count = 0;
+  d_allocated = N_DIRECT_ALLOC_DIRECTIVES;
+  d->dir = d->direct_alloc_dir;
+
+  a->count = 0;
+  a_allocated = N_DIRECT_ALLOC_ARGUMENTS;
+  a->arg = a->direct_alloc_arg;
+
+#define REGISTER_ARG(_index_,_type_) \
+  {                                                                     \
+    size_t n = (_index_);                                               \
+    if (n >= a_allocated)                                               \
+      {                                                                 \
+        size_t memory_size;                                             \
+        argument *memory;                                               \
+                                                                        \
+        a_allocated = xtimes (a_allocated, 2);                          \
+        if (a_allocated <= n)                                           \
+          a_allocated = xsum (n, 1);                                    \
+        memory_size = xtimes (a_allocated, sizeof (argument));          \
+        if (size_overflow_p (memory_size))                              \
+          /* Overflow, would lead to out of memory.  */                 \
+          goto out_of_memory;                                           \
+        memory = (argument *) (a->arg != a->direct_alloc_arg            \
+                               ? realloc (a->arg, memory_size)          \
+                               : malloc (memory_size));                 \
+        if (memory == NULL)                                             \
+          /* Out of memory.  */                                         \
+          goto out_of_memory;                                           \
+        if (a->arg == a->direct_alloc_arg)                              \
+          memcpy (memory, a->arg, a->count * sizeof (argument));        \
+        a->arg = memory;                                                \
+      }                                                                 \
+    while (a->count <= n)                                               \
+      a->arg[a->count++].type = TYPE_NONE;                              \
+    if (a->arg[n].type == TYPE_NONE)                                    \
+      a->arg[n].type = (_type_);                                        \
+    else if (a->arg[n].type != (_type_))                                \
+      /* Ambiguous type for positional argument.  */                    \
+      goto error;                                                       \
+  }
+
+  while (*cp != '\0')
+    {
+      CHAR_T c = *cp++;
+      if (c == '%')
+        {
+          size_t arg_index = ARG_NONE;
+          DIRECTIVE *dp = &d->dir[d->count]; /* pointer to next directive */
+
+          /* Initialize the next directive.  */
+          dp->dir_start = cp - 1;
+          dp->flags = 0;
+          dp->width_start = NULL;
+          dp->width_end = NULL;
+          dp->width_arg_index = ARG_NONE;
+          dp->precision_start = NULL;
+          dp->precision_end = NULL;
+          dp->precision_arg_index = ARG_NONE;
+          dp->arg_index = ARG_NONE;
+
+          /* Test for positional argument.  */
+          if (*cp >= '0' && *cp <= '9')
+            {
+              const CHAR_T *np;
+
+              for (np = cp; *np >= '0' && *np <= '9'; np++)
+                ;
+              if (*np == '$')
+                {
+                  size_t n = 0;
+
+                  for (np = cp; *np >= '0' && *np <= '9'; np++)
+                    n = xsum (xtimes (n, 10), *np - '0');
+                  if (n == 0)
+                    /* Positional argument 0.  */
+                    goto error;
+                  if (size_overflow_p (n))
+                    /* n too large, would lead to out of memory later.  */
+                    goto error;
+                  arg_index = n - 1;
+                  cp = np + 1;
+                }
+            }
+
+          /* Read the flags.  */
+          for (;;)
+            {
+              if (*cp == '\'')
+                {
+                  dp->flags |= FLAG_GROUP;
+                  cp++;
+                }
+              else if (*cp == '-')
+                {
+                  dp->flags |= FLAG_LEFT;
+                  cp++;
+                }
+              else if (*cp == '+')
+                {
+                  dp->flags |= FLAG_SHOWSIGN;
+                  cp++;
+                }
+              else if (*cp == ' ')
+                {
+                  dp->flags |= FLAG_SPACE;
+                  cp++;
+                }
+              else if (*cp == '#')
+                {
+                  dp->flags |= FLAG_ALT;
+                  cp++;
+                }
+              else if (*cp == '0')
+                {
+                  dp->flags |= FLAG_ZERO;
+                  cp++;
+                }
+#if __GLIBC__ >= 2 && !defined __UCLIBC__
+              else if (*cp == 'I')
+                {
+                  dp->flags |= FLAG_LOCALIZED;
+                  cp++;
+                }
+#endif
+              else
+                break;
+            }
+
+          /* Parse the field width.  */
+          if (*cp == '*')
+            {
+              dp->width_start = cp;
+              cp++;
+              dp->width_end = cp;
+              if (max_width_length < 1)
+                max_width_length = 1;
+
+              /* Test for positional argument.  */
+              if (*cp >= '0' && *cp <= '9')
+                {
+                  const CHAR_T *np;
+
+                  for (np = cp; *np >= '0' && *np <= '9'; np++)
+                    ;
+                  if (*np == '$')
+                    {
+                      size_t n = 0;
+
+                      for (np = cp; *np >= '0' && *np <= '9'; np++)
+                        n = xsum (xtimes (n, 10), *np - '0');
+                      if (n == 0)
+                        /* Positional argument 0.  */
+                        goto error;
+                      if (size_overflow_p (n))
+                        /* n too large, would lead to out of memory later.  */
+                        goto error;
+                      dp->width_arg_index = n - 1;
+                      cp = np + 1;
+                    }
+                }
+              if (dp->width_arg_index == ARG_NONE)
+                {
+                  dp->width_arg_index = arg_posn++;
+                  if (dp->width_arg_index == ARG_NONE)
+                    /* arg_posn wrapped around.  */
+                    goto error;
+                }
+              REGISTER_ARG (dp->width_arg_index, TYPE_INT);
+            }
+          else if (*cp >= '0' && *cp <= '9')
+            {
+              size_t width_length;
+
+              dp->width_start = cp;
+              for (; *cp >= '0' && *cp <= '9'; cp++)
+                ;
+              dp->width_end = cp;
+              width_length = dp->width_end - dp->width_start;
+              if (max_width_length < width_length)
+                max_width_length = width_length;
+            }
+
+          /* Parse the precision.  */
+          if (*cp == '.')
+            {
+              cp++;
+              if (*cp == '*')
+                {
+                  dp->precision_start = cp - 1;
+                  cp++;
+                  dp->precision_end = cp;
+                  if (max_precision_length < 2)
+                    max_precision_length = 2;
+
+                  /* Test for positional argument.  */
+                  if (*cp >= '0' && *cp <= '9')
+                    {
+                      const CHAR_T *np;
+
+                      for (np = cp; *np >= '0' && *np <= '9'; np++)
+                        ;
+                      if (*np == '$')
+                        {
+                          size_t n = 0;
+
+                          for (np = cp; *np >= '0' && *np <= '9'; np++)
+                            n = xsum (xtimes (n, 10), *np - '0');
+                          if (n == 0)
+                            /* Positional argument 0.  */
+                            goto error;
+                          if (size_overflow_p (n))
+                            /* n too large, would lead to out of memory
+                               later.  */
+                            goto error;
+                          dp->precision_arg_index = n - 1;
+                          cp = np + 1;
+                        }
+                    }
+                  if (dp->precision_arg_index == ARG_NONE)
+                    {
+                      dp->precision_arg_index = arg_posn++;
+                      if (dp->precision_arg_index == ARG_NONE)
+                        /* arg_posn wrapped around.  */
+                        goto error;
+                    }
+                  REGISTER_ARG (dp->precision_arg_index, TYPE_INT);
+                }
+              else
+                {
+                  size_t precision_length;
+
+                  dp->precision_start = cp - 1;
+                  for (; *cp >= '0' && *cp <= '9'; cp++)
+                    ;
+                  dp->precision_end = cp;
+                  precision_length = dp->precision_end - dp->precision_start;
+                  if (max_precision_length < precision_length)
+                    max_precision_length = precision_length;
+                }
+            }
+
+          {
+            arg_type type;
+
+            /* Parse argument type/size specifiers.  */
+            {
+              int flags = 0;
+
+              for (;;)
+                {
+                  if (*cp == 'h')
+                    {
+                      flags |= (1 << (flags & 1));
+                      cp++;
+                    }
+                  else if (*cp == 'L')
+                    {
+                      flags |= 4;
+                      cp++;
+                    }
+                  else if (*cp == 'l')
+                    {
+                      flags += 8;
+                      cp++;
+                    }
+                  else if (*cp == 'j')
+                    {
+                      if (sizeof (intmax_t) > sizeof (long))
+                        {
+                          /* intmax_t = long long */
+                          flags += 16;
+                        }
+                      else if (sizeof (intmax_t) > sizeof (int))
+                        {
+                          /* intmax_t = long */
+                          flags += 8;
+                        }
+                      cp++;
+                    }
+                  else if (*cp == 'z' || *cp == 'Z')
+                    {
+                      /* 'z' is standardized in ISO C 99, but glibc uses 'Z'
+                         because the warning facility in gcc-2.95.2 understands
+                         only 'Z' (see gcc-2.95.2/gcc/c-common.c:1784).  */
+                      if (sizeof (size_t) > sizeof (long))
+                        {
+                          /* size_t = long long */
+                          flags += 16;
+                        }
+                      else if (sizeof (size_t) > sizeof (int))
+                        {
+                          /* size_t = long */
+                          flags += 8;
+                        }
+                      cp++;
+                    }
+                  else if (*cp == 't')
+                    {
+                      if (sizeof (ptrdiff_t) > sizeof (long))
+                        {
+                          /* ptrdiff_t = long long */
+                          flags += 16;
+                        }
+                      else if (sizeof (ptrdiff_t) > sizeof (int))
+                        {
+                          /* ptrdiff_t = long */
+                          flags += 8;
+                        }
+                      cp++;
+                    }
+#if defined __APPLE__ && defined __MACH__
+                  /* On Mac OS X 10.3, PRIdMAX is defined as "qd".
+                     We cannot change it to "lld" because PRIdMAX must also
+                     be understood by the system's printf routines.  */
+                  else if (*cp == 'q')
+                    {
+                      if (64 / 8 > sizeof (long))
+                        {
+                          /* int64_t = long long */
+                          flags += 16;
+                        }
+                      else
+                        {
+                          /* int64_t = long */
+                          flags += 8;
+                        }
+                      cp++;
+                    }
+#endif
+#if defined _WIN32 && ! defined __CYGWIN__
+                  /* On native Windows, PRIdMAX is defined as "I64d".
+                     We cannot change it to "lld" because PRIdMAX must also
+                     be understood by the system's printf routines.  */
+                  else if (*cp == 'I' && cp[1] == '6' && cp[2] == '4')
+                    {
+                      if (64 / 8 > sizeof (long))
+                        {
+                          /* __int64 = long long */
+                          flags += 16;
+                        }
+                      else
+                        {
+                          /* __int64 = long */
+                          flags += 8;
+                        }
+                      cp += 3;
+                    }
+#endif
+                  else
+                    break;
+                }
+
+              /* Read the conversion character.  */
+              c = *cp++;
+              switch (c)
+                {
+                case 'd': case 'i':
+#if HAVE_LONG_LONG_INT
+                  /* If 'long long' exists and is larger than 'long':  */
+                  if (flags >= 16 || (flags & 4))
+                    type = TYPE_LONGLONGINT;
+                  else
+#endif
+                  /* If 'long long' exists and is the same as 'long', we parse
+                     "lld" into TYPE_LONGINT.  */
+                  if (flags >= 8)
+                    type = TYPE_LONGINT;
+                  else if (flags & 2)
+                    type = TYPE_SCHAR;
+                  else if (flags & 1)
+                    type = TYPE_SHORT;
+                  else
+                    type = TYPE_INT;
+                  break;
+                case 'o': case 'u': case 'x': case 'X':
+#if HAVE_LONG_LONG_INT
+                  /* If 'long long' exists and is larger than 'long':  */
+                  if (flags >= 16 || (flags & 4))
+                    type = TYPE_ULONGLONGINT;
+                  else
+#endif
+                  /* If 'unsigned long long' exists and is the same as
+                     'unsigned long', we parse "llu" into TYPE_ULONGINT.  */
+                  if (flags >= 8)
+                    type = TYPE_ULONGINT;
+                  else if (flags & 2)
+                    type = TYPE_UCHAR;
+                  else if (flags & 1)
+                    type = TYPE_USHORT;
+                  else
+                    type = TYPE_UINT;
+                  break;
+                case 'f': case 'F': case 'e': case 'E': case 'g': case 'G':
+                case 'a': case 'A':
+                  if (flags >= 16 || (flags & 4))
+                    type = TYPE_LONGDOUBLE;
+                  else
+                    type = TYPE_DOUBLE;
+                  break;
+                case 'c':
+                  if (flags >= 8)
+#if HAVE_WINT_T
+                    type = TYPE_WIDE_CHAR;
+#else
+                    goto error;
+#endif
+                  else
+                    type = TYPE_CHAR;
+                  break;
+#if HAVE_WINT_T
+                case 'C':
+                  type = TYPE_WIDE_CHAR;
+                  c = 'c';
+                  break;
+#endif
+                case 's':
+                  if (flags >= 8)
+#if HAVE_WCHAR_T
+                    type = TYPE_WIDE_STRING;
+#else
+                    goto error;
+#endif
+                  else
+                    type = TYPE_STRING;
+                  break;
+#if HAVE_WCHAR_T
+                case 'S':
+                  type = TYPE_WIDE_STRING;
+                  c = 's';
+                  break;
+#endif
+                case 'p':
+                  type = TYPE_POINTER;
+                  break;
+                case 'n':
+#if HAVE_LONG_LONG_INT
+                  /* If 'long long' exists and is larger than 'long':  */
+                  if (flags >= 16 || (flags & 4))
+                    type = TYPE_COUNT_LONGLONGINT_POINTER;
+                  else
+#endif
+                  /* If 'long long' exists and is the same as 'long', we parse
+                     "lln" into TYPE_COUNT_LONGINT_POINTER.  */
+                  if (flags >= 8)
+                    type = TYPE_COUNT_LONGINT_POINTER;
+                  else if (flags & 2)
+                    type = TYPE_COUNT_SCHAR_POINTER;
+                  else if (flags & 1)
+                    type = TYPE_COUNT_SHORT_POINTER;
+                  else
+                    type = TYPE_COUNT_INT_POINTER;
+                  break;
+#if ENABLE_UNISTDIO
+                /* The unistdio extensions.  */
+                case 'U':
+                  if (flags >= 16)
+                    type = TYPE_U32_STRING;
+                  else if (flags >= 8)
+                    type = TYPE_U16_STRING;
+                  else
+                    type = TYPE_U8_STRING;
+                  break;
+#endif
+                case '%':
+                  type = TYPE_NONE;
+                  break;
+                default:
+                  /* Unknown conversion character.  */
+                  goto error;
+                }
+            }
+
+            if (type != TYPE_NONE)
+              {
+                dp->arg_index = arg_index;
+                if (dp->arg_index == ARG_NONE)
+                  {
+                    dp->arg_index = arg_posn++;
+                    if (dp->arg_index == ARG_NONE)
+                      /* arg_posn wrapped around.  */
+                      goto error;
+                  }
+                REGISTER_ARG (dp->arg_index, type);
+              }
+            dp->conversion = c;
+            dp->dir_end = cp;
+          }
+
+          d->count++;
+          if (d->count >= d_allocated)
+            {
+              size_t memory_size;
+              DIRECTIVE *memory;
+
+              d_allocated = xtimes (d_allocated, 2);
+              memory_size = xtimes (d_allocated, sizeof (DIRECTIVE));
+              if (size_overflow_p (memory_size))
+                /* Overflow, would lead to out of memory.  */
+                goto out_of_memory;
+              memory = (DIRECTIVE *) (d->dir != d->direct_alloc_dir
+                                      ? realloc (d->dir, memory_size)
+                                      : malloc (memory_size));
+              if (memory == NULL)
+                /* Out of memory.  */
+                goto out_of_memory;
+              if (d->dir == d->direct_alloc_dir)
+                memcpy (memory, d->dir, d->count * sizeof (DIRECTIVE));
+              d->dir = memory;
+            }
+        }
+#if CHAR_T_ONLY_ASCII
+      else if (!c_isascii (c))
+        {
+          /* Non-ASCII character.  Not supported.  */
+          goto error;
+        }
+#endif
+    }
+  d->dir[d->count].dir_start = cp;
+
+  d->max_width_length = max_width_length;
+  d->max_precision_length = max_precision_length;
+  return 0;
+
+error:
+  if (a->arg != a->direct_alloc_arg)
+    free (a->arg);
+  if (d->dir != d->direct_alloc_dir)
+    free (d->dir);
+  errno = EINVAL;
+  return -1;
+
+out_of_memory:
+  if (a->arg != a->direct_alloc_arg)
+    free (a->arg);
+  if (d->dir != d->direct_alloc_dir)
+    free (d->dir);
+  errno = ENOMEM;
+  return -1;
+}
+
+#undef PRINTF_PARSE
+#undef DIRECTIVES
+#undef DIRECTIVE
+#undef CHAR_T_ONLY_ASCII
+#undef CHAR_T
diff --git a/lib/intl/printf-parse.h b/lib/intl/printf-parse.h
new file mode 100644 (file)
index 0000000..c7c5504
--- /dev/null
@@ -0,0 +1,84 @@
+/* Parse printf format string.
+   Copyright (C) 1999, 2002-2003, 2005, 2007, 2010-2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _PRINTF_PARSE_H
+#define _PRINTF_PARSE_H
+
+#if HAVE_FEATURES_H
+# include <features.h> /* for __GLIBC__, __UCLIBC__ */
+#endif
+
+#include "printf-args.h"
+
+
+/* Flags */
+#define FLAG_GROUP       1      /* ' flag */
+#define FLAG_LEFT        2      /* - flag */
+#define FLAG_SHOWSIGN    4      /* + flag */
+#define FLAG_SPACE       8      /* space flag */
+#define FLAG_ALT        16      /* # flag */
+#define FLAG_ZERO       32
+#if __GLIBC__ >= 2 && !defined __UCLIBC__
+# define FLAG_LOCALIZED 64      /* I flag, uses localized digits */
+#endif
+
+/* arg_index value indicating that no argument is consumed.  */
+#define ARG_NONE        (~(size_t)0)
+
+/* Number of directly allocated directives (no malloc() needed).  */
+#define N_DIRECT_ALLOC_DIRECTIVES 7
+
+/* A parsed directive.  */
+typedef struct
+{
+  const char* dir_start;
+  const char* dir_end;
+  int flags;
+  const char* width_start;
+  const char* width_end;
+  size_t width_arg_index;
+  const char* precision_start;
+  const char* precision_end;
+  size_t precision_arg_index;
+  char conversion; /* d i o u x X f e E g G c s p n U % but not C S */
+  size_t arg_index;
+}
+char_directive;
+
+/* A parsed format string.  */
+typedef struct
+{
+  size_t count;
+  char_directive *dir;
+  size_t max_width_length;
+  size_t max_precision_length;
+  char_directive direct_alloc_dir[N_DIRECT_ALLOC_DIRECTIVES];
+}
+char_directives;
+
+
+/* Parses the format string.  Fills in the number N of directives, and fills
+   in directives[0], ..., directives[N-1], and sets directives[N].dir_start
+   to the end of the format string.  Also fills in the arg_type fields of the
+   arguments and the needed count of arguments.  */
+#ifdef STATIC
+STATIC
+#else
+extern
+#endif
+int printf_parse (const char *format, char_directives *d, arguments *a);
+
+#endif /* _PRINTF_PARSE_H */
diff --git a/lib/intl/printf.c b/lib/intl/printf.c
new file mode 100644 (file)
index 0000000..b84daf0
--- /dev/null
@@ -0,0 +1,458 @@
+/* Formatted output to strings, using POSIX/XSI format strings with positions.
+   Copyright (C) 2003, 2006-2007, 2009-2011, 2018, 2020-2022 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 2003.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef __GNUC__
+# define alloca __builtin_alloca
+# define HAVE_ALLOCA 1
+#else
+# ifdef _MSC_VER
+#  include <malloc.h>
+#  define alloca _alloca
+# else
+#  if defined HAVE_ALLOCA_H || defined _LIBC
+#   include <alloca.h>
+#  else
+#   ifdef _AIX
+ #pragma alloca
+#   else
+#    ifndef alloca
+char *alloca ();
+#    endif
+#   endif
+#  endif
+# endif
+#endif
+
+#include <stdio.h>
+
+#if !HAVE_POSIX_PRINTF
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Some systems, like OSF/1 4.0 and Woe32, don't have EOVERFLOW.  */
+#ifndef EOVERFLOW
+# define EOVERFLOW E2BIG
+#endif
+
+/* When building a DLL, we must export some functions.  Note that because
+   the functions are only defined for binary backward compatibility, we
+   don't need to use __declspec(dllimport) in any case.  */
+#if HAVE_VISIBILITY && BUILDING_DLL
+# define DLL_EXPORTED __attribute__((__visibility__("default")))
+#elif defined _MSC_VER && BUILDING_DLL
+# define DLL_EXPORTED __declspec(dllexport)
+#else
+# define DLL_EXPORTED
+#endif
+
+#define STATIC static
+
+/* You can enable this for debugging on Windows.  But not in a release!  */
+#if 0
+# define ENABLE_WCHAR_FALLBACK 1
+#endif
+
+/* This needs to be consistent with libgnuintl.in.h.  */
+#if defined __NetBSD__ || defined __BEOS__ || defined __CYGWIN__ || defined __MINGW32__
+/* Don't break __attribute__((format(printf,M,N))).
+   This redefinition is only possible because the libc in NetBSD, Cygwin,
+   mingw does not have a function __printf__.  */
+# define libintl_printf __printf__
+#endif
+
+/* Define auxiliary functions declared in "printf-args.h".  */
+#include "printf-args.c"
+
+/* Define auxiliary functions declared in "printf-parse.h".  */
+#include "printf-parse.c"
+
+/* Define functions declared in "vasnprintf.h".  */
+#define vasnprintf libintl_vasnprintf
+#include "vasnprintf.c"
+#if 0 /* not needed */
+#define asnprintf libintl_asnprintf
+#include "asnprintf.c"
+#endif
+
+/* Users don't expect libintl_fprintf to be less POSIX compliant
+   than the fprintf implementation provided by gnulib or - on mingw -
+   the one provided by mingw libs when __USE_MINGW_ANSI_STDIO is in
+   effect.  */
+#define USE_REPLACEMENT_CODE_ALWAYS 1
+
+DLL_EXPORTED
+int
+libintl_vfprintf (FILE *stream, const char *format, va_list args)
+{
+#if !USE_REPLACEMENT_CODE_ALWAYS
+  if (strchr (format, '$') == NULL)
+    return vfprintf (stream, format, args);
+  else
+#endif
+    {
+      size_t length;
+      char *result = libintl_vasnprintf (NULL, &length, format, args);
+      int retval = -1;
+      if (result != NULL)
+        {
+          size_t written = fwrite (result, 1, length, stream);
+          free (result);
+          if (written == length)
+            {
+              if (length > INT_MAX)
+                errno = EOVERFLOW;
+              else
+                retval = length;
+            }
+        }
+      return retval;
+    }
+}
+
+DLL_EXPORTED
+int
+libintl_fprintf (FILE *stream, const char *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vfprintf (stream, format, args);
+  va_end (args);
+  return retval;
+}
+
+DLL_EXPORTED
+int
+libintl_vprintf (const char *format, va_list args)
+{
+  return libintl_vfprintf (stdout, format, args);
+}
+
+DLL_EXPORTED
+int
+libintl_printf (const char *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vprintf (format, args);
+  va_end (args);
+  return retval;
+}
+
+DLL_EXPORTED
+int
+libintl_vsprintf (char *resultbuf, const char *format, va_list args)
+{
+#if !USE_REPLACEMENT_CODE_ALWAYS
+  if (strchr (format, '$') == NULL)
+    return vsprintf (resultbuf, format, args);
+  else
+#endif
+    {
+      size_t length = (size_t) ~0 / (4 * sizeof (char));
+      char *result = libintl_vasnprintf (resultbuf, &length, format, args);
+      if (result != resultbuf)
+        {
+          free (result);
+          return -1;
+        }
+      if (length > INT_MAX)
+        {
+          errno = EOVERFLOW;
+          return -1;
+        }
+      else
+        return length;
+    }
+}
+
+DLL_EXPORTED
+int
+libintl_sprintf (char *resultbuf, const char *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vsprintf (resultbuf, format, args);
+  va_end (args);
+  return retval;
+}
+
+#if HAVE_SNPRINTF
+
+# if HAVE_DECL__SNPRINTF
+   /* Windows.  The mingw function vsnprintf() has fewer bugs than the MSVCRT
+      function _vsnprintf(), so prefer that.  */
+#  if defined __MINGW32__
+#   define system_vsnprintf vsnprintf
+#  else
+#   define system_vsnprintf _vsnprintf
+#  endif
+# else
+   /* Unix.  */
+#  define system_vsnprintf vsnprintf
+# endif
+
+DLL_EXPORTED
+int
+libintl_vsnprintf (char *resultbuf, size_t length, const char *format, va_list args)
+{
+# if !USE_REPLACEMENT_CODE_ALWAYS
+  if (strchr (format, '$') == NULL)
+    return system_vsnprintf (resultbuf, length, format, args);
+  else
+# endif
+    {
+      size_t maxlength = length;
+      char *result = libintl_vasnprintf (resultbuf, &length, format, args);
+      if (result == NULL)
+        return -1;
+      if (result != resultbuf)
+        {
+          if (maxlength > 0)
+            {
+              size_t pruned_length =
+                (length < maxlength ? length : maxlength - 1);
+              memcpy (resultbuf, result, pruned_length);
+              resultbuf[pruned_length] = '\0';
+            }
+          free (result);
+        }
+      if (length > INT_MAX)
+        {
+          errno = EOVERFLOW;
+          return -1;
+        }
+      else
+        return length;
+    }
+}
+
+DLL_EXPORTED
+int
+libintl_snprintf (char *resultbuf, size_t length, const char *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vsnprintf (resultbuf, length, format, args);
+  va_end (args);
+  return retval;
+}
+
+#endif
+
+#if HAVE_ASPRINTF
+
+DLL_EXPORTED
+int
+libintl_vasprintf (char **resultp, const char *format, va_list args)
+{
+  size_t length;
+  char *result = libintl_vasnprintf (NULL, &length, format, args);
+  if (result == NULL)
+    return -1;
+  if (length > INT_MAX)
+    {
+      free (result);
+      errno = EOVERFLOW;
+      return -1;
+    }
+  *resultp = result;
+  return length;
+}
+
+DLL_EXPORTED
+int
+libintl_asprintf (char **resultp, const char *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vasprintf (resultp, format, args);
+  va_end (args);
+  return retval;
+}
+
+#endif
+
+#if HAVE_WPRINTF
+
+#include <wchar.h>
+
+#define WIDE_CHAR_VERSION 1
+
+#include "wprintf-parse.h"
+/* Define auxiliary functions declared in "wprintf-parse.h".  */
+#define CHAR_T wchar_t
+#define DIRECTIVE wchar_t_directive
+#define DIRECTIVES wchar_t_directives
+#define PRINTF_PARSE wprintf_parse
+#include "printf-parse.c"
+
+/* Define functions declared in "vasnprintf.h".  */
+#define vasnwprintf libintl_vasnwprintf
+#include "vasnprintf.c"
+#if 0 /* not needed */
+#define asnwprintf libintl_asnwprintf
+#include "asnprintf.c"
+#endif
+
+# if HAVE_DECL__SNWPRINTF
+   /* Windows.  The function vswprintf() has a different signature than
+      on Unix; we use the function _vsnwprintf() instead.  */
+#  define system_vswprintf _vsnwprintf
+# else
+   /* Unix.  */
+#  define system_vswprintf vswprintf
+# endif
+
+DLL_EXPORTED
+int
+libintl_vfwprintf (FILE *stream, const wchar_t *format, va_list args)
+{
+# if !USE_REPLACEMENT_CODE_ALWAYS
+  if (wcschr (format, '$') == NULL)
+    return vfwprintf (stream, format, args);
+  else
+# endif
+    {
+      size_t length;
+      wchar_t *result = libintl_vasnwprintf (NULL, &length, format, args);
+      int retval = -1;
+      if (result != NULL)
+        {
+          size_t i;
+          for (i = 0; i < length; i++)
+            if (fputwc (result[i], stream) == WEOF)
+              break;
+          free (result);
+          if (i == length)
+            {
+              if (length > INT_MAX)
+                errno = EOVERFLOW;
+              else
+                retval = length;
+            }
+        }
+      return retval;
+    }
+}
+
+DLL_EXPORTED
+int
+libintl_fwprintf (FILE *stream, const wchar_t *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vfwprintf (stream, format, args);
+  va_end (args);
+  return retval;
+}
+
+DLL_EXPORTED
+int
+libintl_vwprintf (const wchar_t *format, va_list args)
+{
+  return libintl_vfwprintf (stdout, format, args);
+}
+
+DLL_EXPORTED
+int
+libintl_wprintf (const wchar_t *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vwprintf (format, args);
+  va_end (args);
+  return retval;
+}
+
+DLL_EXPORTED
+int
+libintl_vswprintf (wchar_t *resultbuf, size_t length, const wchar_t *format, va_list args)
+{
+# if !USE_REPLACEMENT_CODE_ALWAYS
+  if (wcschr (format, '$') == NULL)
+    return system_vswprintf (resultbuf, length, format, args);
+  else
+# endif
+    {
+      size_t maxlength = length;
+      wchar_t *result = libintl_vasnwprintf (resultbuf, &length, format, args);
+      if (result == NULL)
+        return -1;
+      if (result != resultbuf)
+        {
+          if (maxlength > 0)
+            {
+              size_t pruned_length =
+                (length < maxlength ? length : maxlength - 1);
+              memcpy (resultbuf, result, pruned_length * sizeof (wchar_t));
+              resultbuf[pruned_length] = 0;
+            }
+          free (result);
+          /* Unlike vsnprintf, which has to return the number of character that
+             would have been produced if the resultbuf had been sufficiently
+             large, the vswprintf function has to return a negative value if
+             the resultbuf was not sufficiently large.  */
+          if (length >= maxlength)
+            return -1;
+        }
+      if (length > INT_MAX)
+        {
+          errno = EOVERFLOW;
+          return -1;
+        }
+      else
+        return length;
+    }
+}
+
+DLL_EXPORTED
+int
+libintl_swprintf (wchar_t *resultbuf, size_t length, const wchar_t *format, ...)
+{
+  va_list args;
+  int retval;
+
+  va_start (args, format);
+  retval = libintl_vswprintf (resultbuf, length, format, args);
+  va_end (args);
+  return retval;
+}
+
+#endif
+
+#endif
diff --git a/lib/intl/setlocale-lock.c b/lib/intl/setlocale-lock.c
new file mode 100644 (file)
index 0000000..b824e38
--- /dev/null
@@ -0,0 +1,150 @@
+/* Return the internal lock used by setlocale_null_r.
+   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2019.  */
+
+#include <config.h>
+
+/* When it is known that the gl_get_setlocale_null_lock function is defined
+   by a dependency library, it should not be defined here.  */
+#if OMIT_SETLOCALE_LOCK
+
+/* This declaration is solely to ensure that after preprocessing
+   this file is never empty.  */
+typedef int dummy;
+
+#else
+
+/* This file defines the internal lock used by setlocale_null_r.
+   It is a separate compilation unit, so that only one copy of it is
+   present when linking statically.  */
+
+/* Prohibit renaming this symbol.  */
+# undef gl_get_setlocale_null_lock
+
+/* Macro for exporting a symbol (function, not variable) defined in this file,
+   when compiled into a shared library.  */
+# ifndef DLL_EXPORTED
+#  if HAVE_VISIBILITY
+  /* Override the effect of the compiler option '-fvisibility=hidden'.  */
+#   define DLL_EXPORTED __attribute__((__visibility__("default")))
+#  elif defined _WIN32 || defined __CYGWIN__
+#   define DLL_EXPORTED __declspec(dllexport)
+#  else
+#   define DLL_EXPORTED
+#  endif
+# endif
+
+# if defined _WIN32 && !defined __CYGWIN__
+
+#  define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#  include <windows.h>
+
+#  include "windows-initguard.h"
+
+/* The return type is a 'CRITICAL_SECTION *', not a 'glwthread_mutex_t *',
+   because the latter is not guaranteed to be a stable ABI in the future.  */
+
+/* Make sure the function gets exported from DLLs.  */
+DLL_EXPORTED CRITICAL_SECTION *gl_get_setlocale_null_lock (void);
+
+static glwthread_initguard_t guard = GLWTHREAD_INITGUARD_INIT;
+static CRITICAL_SECTION lock;
+
+/* Returns the internal lock used by setlocale_null_r.  */
+CRITICAL_SECTION *
+gl_get_setlocale_null_lock (void)
+{
+  if (!guard.done)
+    {
+      if (InterlockedIncrement (&guard.started) == 0)
+        {
+          /* This thread is the first one to need the lock.  Initialize it.  */
+          InitializeCriticalSection (&lock);
+          guard.done = 1;
+        }
+      else
+        {
+          /* Don't let guard.started grow and wrap around.  */
+          InterlockedDecrement (&guard.started);
+          /* Yield the CPU while waiting for another thread to finish
+             initializing this mutex.  */
+          while (!guard.done)
+            Sleep (0);
+        }
+    }
+  return &lock;
+}
+
+# elif HAVE_PTHREAD_API
+
+#  include <pthread.h>
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Make sure the function gets exported from shared libraries.  */
+DLL_EXPORTED pthread_mutex_t *gl_get_setlocale_null_lock (void);
+
+/* Returns the internal lock used by setlocale_null_r.  */
+pthread_mutex_t *
+gl_get_setlocale_null_lock (void)
+{
+  return &mutex;
+}
+
+# elif HAVE_THREADS_H
+
+#  include <threads.h>
+#  include <stdlib.h>
+
+static int volatile init_needed = 1;
+static once_flag init_once = ONCE_FLAG_INIT;
+static mtx_t mutex;
+
+static void
+atomic_init (void)
+{
+  if (mtx_init (&mutex, mtx_plain) != thrd_success)
+    abort ();
+  init_needed = 0;
+}
+
+/* Make sure the function gets exported from shared libraries.  */
+DLL_EXPORTED mtx_t *gl_get_setlocale_null_lock (void);
+
+/* Returns the internal lock used by setlocale_null_r.  */
+mtx_t *
+gl_get_setlocale_null_lock (void)
+{
+  if (init_needed)
+    call_once (&init_once, atomic_init);
+  return &mutex;
+}
+
+# endif
+
+# if (defined _WIN32 || defined __CYGWIN__) && !defined _MSC_VER
+/* Make sure the '__declspec(dllimport)' in setlocale_null.c does not cause
+   a link failure when no DLLs are involved.  */
+#  if defined _WIN64 || defined _LP64
+#   define IMP(x) __imp_##x
+#  else
+#   define IMP(x) _imp__##x
+#  endif
+void * IMP(gl_get_setlocale_null_lock) = &gl_get_setlocale_null_lock;
+# endif
+
+#endif
diff --git a/lib/intl/setlocale.c b/lib/intl/setlocale.c
new file mode 100644 (file)
index 0000000..2995db1
--- /dev/null
@@ -0,0 +1,1771 @@
+/* setlocale() function that respects the locale chosen by the user.
+   Copyright (C) 2009, 2011, 2013, 2018-2019 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 2009.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Override setlocale() and newlocale() so that when the default locale is
+   requested (locale = "") and no relevant environment variable is set, the
+   locale chosen by the user is used.
+   This matters on MacOS X 10 and Windows.
+   See the comments in localename.c, function gl_locale_name_default.  */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* When building a DLL, we must export some functions.  Note that because
+   the functions are only defined for binary backward compatibility, we
+   don't need to use __declspec(dllimport) in any case.  */
+#if HAVE_VISIBILITY && BUILDING_DLL
+# define DLL_EXPORTED __attribute__((__visibility__("default")))
+#elif defined _MSC_VER && BUILDING_DLL
+# define DLL_EXPORTED __declspec(dllexport)
+#else
+# define DLL_EXPORTED
+#endif
+
+#include "gettextP.h"
+
+#if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
+# if HAVE_CFLOCALECOPYPREFERREDLANGUAGES
+#  include <CoreFoundation/CFLocale.h>
+# elif HAVE_CFPREFERENCESCOPYAPPVALUE
+#  include <CoreFoundation/CFPreferences.h>
+# endif
+# include <CoreFoundation/CFPropertyList.h>
+# include <CoreFoundation/CFArray.h>
+# include <CoreFoundation/CFString.h>
+#endif
+
+#if (defined __APPLE__ && defined __MACH__) || defined _WIN32 || defined __CYGWIN__
+
+# undef setlocale
+# undef newlocale
+
+/* Return string representation of locale category CATEGORY.  */
+static const char *
+category_to_name (int category)
+{
+  const char *retval;
+
+  switch (category)
+  {
+  case LC_COLLATE:
+    retval = "LC_COLLATE";
+    break;
+  case LC_CTYPE:
+    retval = "LC_CTYPE";
+    break;
+  case LC_MONETARY:
+    retval = "LC_MONETARY";
+    break;
+  case LC_NUMERIC:
+    retval = "LC_NUMERIC";
+    break;
+  case LC_TIME:
+    retval = "LC_TIME";
+    break;
+  case LC_MESSAGES:
+    retval = "LC_MESSAGES";
+    break;
+  default:
+    /* If you have a better idea for a default value let me know.  */
+    retval = "LC_XXX";
+  }
+
+  return retval;
+}
+
+# if defined _WIN32 && ! defined __CYGWIN__
+
+/* The native Windows setlocale() function expects locale names of the form
+   "German" or "German_Germany" or "DEU", but not "de" or "de_DE".  We need
+   to convert the names from the form with ISO 639 language code and ISO 3166
+   country code to the form with English names or with three-letter identifier.
+   The three-letter identifiers known by a Windows XP SP2 or SP3 are:
+     AFK  Afrikaans_South Africa.1252
+     ARA  Arabic_Saudi Arabia.1256
+     ARB  Arabic_Lebanon.1256
+     ARE  Arabic_Egypt.1256
+     ARG  Arabic_Algeria.1256
+     ARH  Arabic_Bahrain.1256
+     ARI  Arabic_Iraq.1256
+     ARJ  Arabic_Jordan.1256
+     ARK  Arabic_Kuwait.1256
+     ARL  Arabic_Libya.1256
+     ARM  Arabic_Morocco.1256
+     ARO  Arabic_Oman.1256
+     ARQ  Arabic_Qatar.1256
+     ARS  Arabic_Syria.1256
+     ART  Arabic_Tunisia.1256
+     ARU  Arabic_U.A.E..1256
+     ARY  Arabic_Yemen.1256
+     AZE  Azeri (Latin)_Azerbaijan.1254
+     BEL  Belarusian_Belarus.1251
+     BGR  Bulgarian_Bulgaria.1251
+     BSB  Bosnian_Bosnia and Herzegovina.1250
+     BSC  Bosnian (Cyrillic)_Bosnia and Herzegovina.1250  (wrong encoding!)
+     CAT  Catalan_Spain.1252
+     CHH  Chinese_Hong Kong S.A.R..950
+     CHI  Chinese_Singapore.936
+     CHS  Chinese_People's Republic of China.936
+     CHT  Chinese_Taiwan.950
+     CSY  Czech_Czech Republic.1250
+     CYM  Welsh_United Kingdom.1252
+     DAN  Danish_Denmark.1252
+     DEA  German_Austria.1252
+     DEC  German_Liechtenstein.1252
+     DEL  German_Luxembourg.1252
+     DES  German_Switzerland.1252
+     DEU  German_Germany.1252
+     ELL  Greek_Greece.1253
+     ENA  English_Australia.1252
+     ENB  English_Caribbean.1252
+     ENC  English_Canada.1252
+     ENG  English_United Kingdom.1252
+     ENI  English_Ireland.1252
+     ENJ  English_Jamaica.1252
+     ENL  English_Belize.1252
+     ENP  English_Republic of the Philippines.1252
+     ENS  English_South Africa.1252
+     ENT  English_Trinidad and Tobago.1252
+     ENU  English_United States.1252
+     ENW  English_Zimbabwe.1252
+     ENZ  English_New Zealand.1252
+     ESA  Spanish_Panama.1252
+     ESB  Spanish_Bolivia.1252
+     ESC  Spanish_Costa Rica.1252
+     ESD  Spanish_Dominican Republic.1252
+     ESE  Spanish_El Salvador.1252
+     ESF  Spanish_Ecuador.1252
+     ESG  Spanish_Guatemala.1252
+     ESH  Spanish_Honduras.1252
+     ESI  Spanish_Nicaragua.1252
+     ESL  Spanish_Chile.1252
+     ESM  Spanish_Mexico.1252
+     ESN  Spanish_Spain.1252
+     ESO  Spanish_Colombia.1252
+     ESP  Spanish_Spain.1252
+     ESR  Spanish_Peru.1252
+     ESS  Spanish_Argentina.1252
+     ESU  Spanish_Puerto Rico.1252
+     ESV  Spanish_Venezuela.1252
+     ESY  Spanish_Uruguay.1252
+     ESZ  Spanish_Paraguay.1252
+     ETI  Estonian_Estonia.1257
+     EUQ  Basque_Spain.1252
+     FAR  Farsi_Iran.1256
+     FIN  Finnish_Finland.1252
+     FOS  Faroese_Faroe Islands.1252
+     FPO  Filipino_Philippines.1252
+     FRA  French_France.1252
+     FRB  French_Belgium.1252
+     FRC  French_Canada.1252
+     FRL  French_Luxembourg.1252
+     FRM  French_Principality of Monaco.1252
+     FRS  French_Switzerland.1252
+     FYN  Frisian_Netherlands.1252
+     GLC  Galician_Spain.1252
+     HEB  Hebrew_Israel.1255
+     HRB  Croatian_Bosnia and Herzegovina.1250
+     HRV  Croatian_Croatia.1250
+     HUN  Hungarian_Hungary.1250
+     IND  Indonesian_Indonesia.1252
+     IRE  Irish_Ireland.1252
+     ISL  Icelandic_Iceland.1252
+     ITA  Italian_Italy.1252
+     ITS  Italian_Switzerland.1252
+     IUK  Inuktitut (Latin)_Canada.1252
+     JPN  Japanese_Japan.932
+     KKZ  Kazakh_Kazakhstan.1251
+     KOR  Korean_Korea.949
+     KYR  Kyrgyz_Kyrgyzstan.1251
+     LBX  Luxembourgish_Luxembourg.1252
+     LTH  Lithuanian_Lithuania.1257
+     LVI  Latvian_Latvia.1257
+     MKI  FYRO Macedonian_Former Yugoslav Republic of Macedonia.1251
+     MON  Mongolian_Mongolia.1251
+     MPD  Mapudungun_Chile.1252
+     MSB  Malay_Brunei Darussalam.1252
+     MSL  Malay_Malaysia.1252
+     MWK  Mohawk_Canada.1252
+     NLB  Dutch_Belgium.1252
+     NLD  Dutch_Netherlands.1252
+     NON  Norwegian-Nynorsk_Norway.1252
+     NOR  Norwegian (Bokmål)_Norway.1252
+     NSO  Northern Sotho_South Africa.1252
+     PLK  Polish_Poland.1250
+     PTB  Portuguese_Brazil.1252
+     PTG  Portuguese_Portugal.1252
+     QUB  Quechua_Bolivia.1252
+     QUE  Quechua_Ecuador.1252
+     QUP  Quechua_Peru.1252
+     RMC  Romansh_Switzerland.1252
+     ROM  Romanian_Romania.1250
+     RUS  Russian_Russia.1251
+     SKY  Slovak_Slovakia.1250
+     SLV  Slovenian_Slovenia.1250
+     SMA  Sami (Southern)_Norway.1252
+     SMB  Sami (Southern)_Sweden.1252
+     SME  Sami (Northern)_Norway.1252
+     SMF  Sami (Northern)_Sweden.1252
+     SMG  Sami (Northern)_Finland.1252
+     SMJ  Sami (Lule)_Norway.1252
+     SMK  Sami (Lule)_Sweden.1252
+     SMN  Sami (Inari)_Finland.1252
+     SMS  Sami (Skolt)_Finland.1252
+     SQI  Albanian_Albania.1250
+     SRB  Serbian (Cyrillic)_Serbia and Montenegro.1251
+     SRL  Serbian (Latin)_Serbia and Montenegro.1250
+     SRN  Serbian (Cyrillic)_Bosnia and Herzegovina.1251
+     SRS  Serbian (Latin)_Bosnia and Herzegovina.1250
+     SVE  Swedish_Sweden.1252
+     SVF  Swedish_Finland.1252
+     SWK  Swahili_Kenya.1252
+     THA  Thai_Thailand.874
+     TRK  Turkish_Turkey.1254
+     TSN  Tswana_South Africa.1252
+     TTT  Tatar_Russia.1251
+     UKR  Ukrainian_Ukraine.1251
+     URD  Urdu_Islamic Republic of Pakistan.1256
+     USA  English_United States.1252
+     UZB  Uzbek (Latin)_Uzbekistan.1254
+     VIT  Vietnamese_Viet Nam.1258
+     XHO  Xhosa_South Africa.1252
+     ZHH  Chinese_Hong Kong S.A.R..950
+     ZHI  Chinese_Singapore.936
+     ZHM  Chinese_Macau S.A.R..950
+     ZUL  Zulu_South Africa.1252
+ */
+
+/* Table from ISO 639 language code, optionally with country or script suffix,
+   to English name.
+   Keep in sync with the gl_locale_name_from_win32_LANGID function in
+   localename.c!  */
+struct table_entry
+{
+  const char *code;
+  const char *english;
+};
+static const struct table_entry language_table[] =
+  {
+    { "af", "Afrikaans" },
+    { "am", "Amharic" },
+    { "ar", "Arabic" },
+    { "arn", "Mapudungun" },
+    { "as", "Assamese" },
+    { "az@cyrillic", "Azeri (Cyrillic)" },
+    { "az@latin", "Azeri (Latin)" },
+    { "ba", "Bashkir" },
+    { "be", "Belarusian" },
+    { "ber", "Tamazight" },
+    { "ber@arabic", "Tamazight (Arabic)" },
+    { "ber@latin", "Tamazight (Latin)" },
+    { "bg", "Bulgarian" },
+    { "bin", "Edo" },
+    { "bn", "Bengali" },
+    { "bn_BD", "Bengali (Bangladesh)" },
+    { "bn_IN", "Bengali (India)" },
+    { "bnt", "Sutu" },
+    { "bo", "Tibetan" },
+    { "br", "Breton" },
+    { "bs", "BSB" }, /* "Bosnian (Latin)" */
+    { "bs@cyrillic", "BSC" }, /* Bosnian (Cyrillic) */
+    { "ca", "Catalan" },
+    { "chr", "Cherokee" },
+    { "co", "Corsican" },
+    { "cpe", "Hawaiian" },
+    { "cs", "Czech" },
+    { "cy", "Welsh" },
+    { "da", "Danish" },
+    { "de", "German" },
+    { "dsb", "Lower Sorbian" },
+    { "dv", "Divehi" },
+    { "el", "Greek" },
+    { "en", "English" },
+    { "es", "Spanish" },
+    { "et", "Estonian" },
+    { "eu", "Basque" },
+    { "fa", "Farsi" },
+    { "ff", "Fulfulde" },
+    { "fi", "Finnish" },
+    { "fo", "Faroese" }, /* "Faeroese" does not work */
+    { "fr", "French" },
+    { "fy", "Frisian" },
+    { "ga", "IRE" }, /* Gaelic (Ireland) */
+    { "gd", "Gaelic (Scotland)" },
+    { "gd", "Scottish Gaelic" },
+    { "gl", "Galician" },
+    { "gn", "Guarani" },
+    { "gsw", "Alsatian" },
+    { "gu", "Gujarati" },
+    { "ha", "Hausa" },
+    { "he", "Hebrew" },
+    { "hi", "Hindi" },
+    { "hr", "Croatian" },
+    { "hsb", "Upper Sorbian" },
+    { "hu", "Hungarian" },
+    { "hy", "Armenian" },
+    { "id", "Indonesian" },
+    { "ig", "Igbo" },
+    { "ii", "Yi" },
+    { "is", "Icelandic" },
+    { "it", "Italian" },
+    { "iu", "IUK" }, /* Inuktitut */
+    { "ja", "Japanese" },
+    { "ka", "Georgian" },
+    { "kk", "Kazakh" },
+    { "kl", "Greenlandic" },
+    { "km", "Cambodian" },
+    { "km", "Khmer" },
+    { "kn", "Kannada" },
+    { "ko", "Korean" },
+    { "kok", "Konkani" },
+    { "kr", "Kanuri" },
+    { "ks", "Kashmiri" },
+    { "ks_IN", "Kashmiri_India" },
+    { "ks_PK", "Kashmiri (Arabic)_Pakistan" },
+    { "ky", "Kyrgyz" },
+    { "la", "Latin" },
+    { "lb", "Luxembourgish" },
+    { "lo", "Lao" },
+    { "lt", "Lithuanian" },
+    { "lv", "Latvian" },
+    { "mi", "Maori" },
+    { "mk", "FYRO Macedonian" },
+    { "mk", "Macedonian" },
+    { "ml", "Malayalam" },
+    { "mn", "Mongolian" },
+    { "mni", "Manipuri" },
+    { "moh", "Mohawk" },
+    { "mr", "Marathi" },
+    { "ms", "Malay" },
+    { "mt", "Maltese" },
+    { "my", "Burmese" },
+    { "nb", "NOR" }, /* Norwegian Bokmål */
+    { "ne", "Nepali" },
+    { "nic", "Ibibio" },
+    { "nl", "Dutch" },
+    { "nn", "NON" }, /* Norwegian Nynorsk */
+    { "no", "Norwegian" },
+    { "nso", "Northern Sotho" },
+    { "nso", "Sepedi" },
+    { "oc", "Occitan" },
+    { "om", "Oromo" },
+    { "or", "Oriya" },
+    { "pa", "Punjabi" },
+    { "pap", "Papiamentu" },
+    { "pl", "Polish" },
+    { "prs", "Dari" },
+    { "ps", "Pashto" },
+    { "pt", "Portuguese" },
+    { "qu", "Quechua" },
+    { "qut", "K'iche'" },
+    { "rm", "Romansh" },
+    { "ro", "Romanian" },
+    { "ru", "Russian" },
+    { "rw", "Kinyarwanda" },
+    { "sa", "Sanskrit" },
+    { "sah", "Yakut" },
+    { "sd", "Sindhi" },
+    { "se", "Sami (Northern)" },
+    { "se", "Northern Sami" },
+    { "si", "Sinhalese" },
+    { "sk", "Slovak" },
+    { "sl", "Slovenian" },
+    { "sma", "Sami (Southern)" },
+    { "sma", "Southern Sami" },
+    { "smj", "Sami (Lule)" },
+    { "smj", "Lule Sami" },
+    { "smn", "Sami (Inari)" },
+    { "smn", "Inari Sami" },
+    { "sms", "Sami (Skolt)" },
+    { "sms", "Skolt Sami" },
+    { "so", "Somali" },
+    { "sq", "Albanian" },
+    { "sr", "Serbian (Latin)" },
+    { "sr@cyrillic", "SRB" }, /* Serbian (Cyrillic) */
+    { "sv", "Swedish" },
+    { "sw", "Swahili" },
+    { "syr", "Syriac" },
+    { "ta", "Tamil" },
+    { "te", "Telugu" },
+    { "tg", "Tajik" },
+    { "th", "Thai" },
+    { "ti", "Tigrinya" },
+    { "tk", "Turkmen" },
+    { "tl", "Filipino" },
+    { "tn", "Tswana" },
+    { "tr", "Turkish" },
+    { "ts", "Tsonga" },
+    { "tt", "Tatar" },
+    { "ug", "Uighur" },
+    { "uk", "Ukrainian" },
+    { "ur", "Urdu" },
+    { "uz", "Uzbek" },
+    { "uz", "Uzbek (Latin)" },
+    { "uz@cyrillic", "Uzbek (Cyrillic)" },
+    { "ve", "Venda" },
+    { "vi", "Vietnamese" },
+    { "wen", "Sorbian" },
+    { "wo", "Wolof" },
+    { "xh", "Xhosa" },
+    { "yi", "Yiddish" },
+    { "yo", "Yoruba" },
+    { "zh", "Chinese" },
+    { "zu", "Zulu" }
+  };
+
+/* Table from ISO 3166 country code to English name.
+   Keep in sync with the gl_locale_name_from_win32_LANGID function in
+   localename.c!  */
+static const struct table_entry country_table[] =
+  {
+    { "AE", "U.A.E." },
+    { "AF", "Afghanistan" },
+    { "AL", "Albania" },
+    { "AM", "Armenia" },
+    { "AN", "Netherlands Antilles" },
+    { "AR", "Argentina" },
+    { "AT", "Austria" },
+    { "AU", "Australia" },
+    { "AZ", "Azerbaijan" },
+    { "BA", "Bosnia and Herzegovina" },
+    { "BD", "Bangladesh" },
+    { "BE", "Belgium" },
+    { "BG", "Bulgaria" },
+    { "BH", "Bahrain" },
+    { "BN", "Brunei Darussalam" },
+    { "BO", "Bolivia" },
+    { "BR", "Brazil" },
+    { "BT", "Bhutan" },
+    { "BY", "Belarus" },
+    { "BZ", "Belize" },
+    { "CA", "Canada" },
+    { "CG", "Congo" },
+    { "CH", "Switzerland" },
+    { "CI", "Cote d'Ivoire" },
+    { "CL", "Chile" },
+    { "CM", "Cameroon" },
+    { "CN", "People's Republic of China" },
+    { "CO", "Colombia" },
+    { "CR", "Costa Rica" },
+    { "CS", "Serbia and Montenegro" },
+    { "CZ", "Czech Republic" },
+    { "DE", "Germany" },
+    { "DK", "Denmark" },
+    { "DO", "Dominican Republic" },
+    { "DZ", "Algeria" },
+    { "EC", "Ecuador" },
+    { "EE", "Estonia" },
+    { "EG", "Egypt" },
+    { "ER", "Eritrea" },
+    { "ES", "Spain" },
+    { "ET", "Ethiopia" },
+    { "FI", "Finland" },
+    { "FO", "Faroe Islands" },
+    { "FR", "France" },
+    { "GB", "United Kingdom" },
+    { "GD", "Caribbean" },
+    { "GE", "Georgia" },
+    { "GL", "Greenland" },
+    { "GR", "Greece" },
+    { "GT", "Guatemala" },
+    { "HK", "Hong Kong" },
+    { "HK", "Hong Kong S.A.R." },
+    { "HN", "Honduras" },
+    { "HR", "Croatia" },
+    { "HT", "Haiti" },
+    { "HU", "Hungary" },
+    { "ID", "Indonesia" },
+    { "IE", "Ireland" },
+    { "IL", "Israel" },
+    { "IN", "India" },
+    { "IQ", "Iraq" },
+    { "IR", "Iran" },
+    { "IS", "Iceland" },
+    { "IT", "Italy" },
+    { "JM", "Jamaica" },
+    { "JO", "Jordan" },
+    { "JP", "Japan" },
+    { "KE", "Kenya" },
+    { "KG", "Kyrgyzstan" },
+    { "KH", "Cambodia" },
+    { "KR", "South Korea" },
+    { "KW", "Kuwait" },
+    { "KZ", "Kazakhstan" },
+    { "LA", "Laos" },
+    { "LB", "Lebanon" },
+    { "LI", "Liechtenstein" },
+    { "LK", "Sri Lanka" },
+    { "LT", "Lithuania" },
+    { "LU", "Luxembourg" },
+    { "LV", "Latvia" },
+    { "LY", "Libya" },
+    { "MA", "Morocco" },
+    { "MC", "Principality of Monaco" },
+    { "MD", "Moldava" },
+    { "MD", "Moldova" },
+    { "ME", "Montenegro" },
+    { "MK", "Former Yugoslav Republic of Macedonia" },
+    { "ML", "Mali" },
+    { "MM", "Myanmar" },
+    { "MN", "Mongolia" },
+    { "MO", "Macau S.A.R." },
+    { "MT", "Malta" },
+    { "MV", "Maldives" },
+    { "MX", "Mexico" },
+    { "MY", "Malaysia" },
+    { "NG", "Nigeria" },
+    { "NI", "Nicaragua" },
+    { "NL", "Netherlands" },
+    { "NO", "Norway" },
+    { "NP", "Nepal" },
+    { "NZ", "New Zealand" },
+    { "OM", "Oman" },
+    { "PA", "Panama" },
+    { "PE", "Peru" },
+    { "PH", "Philippines" },
+    { "PK", "Islamic Republic of Pakistan" },
+    { "PL", "Poland" },
+    { "PR", "Puerto Rico" },
+    { "PT", "Portugal" },
+    { "PY", "Paraguay" },
+    { "QA", "Qatar" },
+    { "RE", "Reunion" },
+    { "RO", "Romania" },
+    { "RS", "Serbia" },
+    { "RU", "Russia" },
+    { "RW", "Rwanda" },
+    { "SA", "Saudi Arabia" },
+    { "SE", "Sweden" },
+    { "SG", "Singapore" },
+    { "SI", "Slovenia" },
+    { "SK", "Slovak" },
+    { "SN", "Senegal" },
+    { "SO", "Somalia" },
+    { "SR", "Suriname" },
+    { "SV", "El Salvador" },
+    { "SY", "Syria" },
+    { "TH", "Thailand" },
+    { "TJ", "Tajikistan" },
+    { "TM", "Turkmenistan" },
+    { "TN", "Tunisia" },
+    { "TR", "Turkey" },
+    { "TT", "Trinidad and Tobago" },
+    { "TW", "Taiwan" },
+    { "TZ", "Tanzania" },
+    { "UA", "Ukraine" },
+    { "US", "United States" },
+    { "UY", "Uruguay" },
+    { "VA", "Vatican" },
+    { "VE", "Venezuela" },
+    { "VN", "Viet Nam" },
+    { "YE", "Yemen" },
+    { "ZA", "South Africa" },
+    { "ZW", "Zimbabwe" }
+  };
+
+/* Given a string STRING, find the set of indices i such that TABLE[i].code is
+   the given STRING.  It is a range [lo,hi-1].  */
+typedef struct { size_t lo; size_t hi; } range_t;
+static void
+search (const struct table_entry *table, size_t table_size, const char *string,
+        range_t *result)
+{
+  /* The table is sorted.  Perform a binary search.  */
+  size_t hi = table_size;
+  size_t lo = 0;
+  while (lo < hi)
+    {
+      /* Invariant:
+         for i < lo, strcmp (table[i].code, string) < 0,
+         for i >= hi, strcmp (table[i].code, string) > 0.  */
+      size_t mid = (hi + lo) >> 1; /* >= lo, < hi */
+      int cmp = strcmp (table[mid].code, string);
+      if (cmp < 0)
+        lo = mid + 1;
+      else if (cmp > 0)
+        hi = mid;
+      else
+        {
+          /* Found an i with
+               strcmp (language_table[i].code, string) == 0.
+             Find the entire interval of such i.  */
+          {
+            size_t i;
+
+            for (i = mid; i > lo; )
+              {
+                i--;
+                if (strcmp (table[i].code, string) < 0)
+                  {
+                    lo = i + 1;
+                    break;
+                  }
+              }
+          }
+          {
+            size_t i;
+
+            for (i = mid + 1; i < hi; i++)
+              {
+                if (strcmp (table[i].code, string) > 0)
+                  {
+                    hi = i;
+                    break;
+                  }
+              }
+          }
+          /* The set of i with
+               strcmp (language_table[i].code, string) == 0
+             is the interval [lo, hi-1].  */
+          break;
+        }
+    }
+  result->lo = lo;
+  result->hi = hi;
+}
+
+/* Like setlocale, but accept also locale names in the form ll or ll_CC,
+   where ll is an ISO 639 language code and CC is an ISO 3166 country code.  */
+static char *
+setlocale_unixlike (int category, const char *locale)
+{
+  char *result;
+  char llCC_buf[64];
+  char ll_buf[64];
+  char CC_buf[64];
+
+  /* The native Windows implementation of setlocale understands the special
+     locale name "C", but not "POSIX".  Therefore map "POSIX" to "C".  */
+  if (locale != NULL && strcmp (locale, "POSIX") == 0)
+    locale = "C";
+
+  /* First, try setlocale with the original argument unchanged.  */
+  result = setlocale (category, locale);
+  if (result != NULL)
+    return result;
+
+  /* Otherwise, assume the argument is in the form
+       language[_territory][.codeset][@modifier]
+     and try to map it using the tables.  */
+  if (strlen (locale) < sizeof (llCC_buf))
+    {
+      /* Second try: Remove the codeset part.  */
+      {
+        const char *p = locale;
+        char *q = llCC_buf;
+
+        /* Copy the part before the dot.  */
+        for (; *p != '\0' && *p != '.'; p++, q++)
+          *q = *p;
+        if (*p == '.')
+          /* Skip the part up to the '@', if any.  */
+          for (; *p != '\0' && *p != '@'; p++)
+            ;
+        /* Copy the part starting with '@', if any.  */
+        for (; *p != '\0'; p++, q++)
+          *q = *p;
+        *q = '\0';
+      }
+      /* llCC_buf now contains
+           language[_territory][@modifier]
+       */
+      if (strcmp (llCC_buf, locale) != 0)
+        {
+          result = setlocale (category, llCC_buf);
+          if (result != NULL)
+            return result;
+        }
+      /* Look it up in language_table.  */
+      {
+        range_t range;
+        size_t i;
+
+        search (language_table,
+                sizeof (language_table) / sizeof (language_table[0]),
+                llCC_buf,
+                &range);
+
+        for (i = range.lo; i < range.hi; i++)
+          {
+            /* Try the replacement in language_table[i].  */
+            result = setlocale (category, language_table[i].english);
+            if (result != NULL)
+              return result;
+          }
+      }
+      /* Split language[_territory][@modifier]
+         into  ll_buf = language[@modifier]
+         and   CC_buf = territory
+       */
+      {
+        const char *underscore = strchr (llCC_buf, '_');
+        if (underscore != NULL)
+          {
+            const char *territory_start = underscore + 1;
+            const char *territory_end = strchr (territory_start, '@');
+            if (territory_end == NULL)
+              territory_end = territory_start + strlen (territory_start);
+
+            memcpy (ll_buf, llCC_buf, underscore - llCC_buf);
+            strcpy (ll_buf + (underscore - llCC_buf), territory_end);
+
+            memcpy (CC_buf, territory_start, territory_end - territory_start);
+            CC_buf[territory_end - territory_start] = '\0';
+
+            {
+              /* Look up ll_buf in language_table
+                 and CC_buf in country_table.  */
+              range_t language_range;
+
+              search (language_table,
+                      sizeof (language_table) / sizeof (language_table[0]),
+                      ll_buf,
+                      &language_range);
+              if (language_range.lo < language_range.hi)
+                {
+                  range_t country_range;
+
+                  search (country_table,
+                          sizeof (country_table) / sizeof (country_table[0]),
+                          CC_buf,
+                          &country_range);
+                  if (country_range.lo < country_range.hi)
+                    {
+                      size_t i;
+                      size_t j;
+
+                      for (i = language_range.lo; i < language_range.hi; i++)
+                        for (j = country_range.lo; j < country_range.hi; j++)
+                          {
+                            /* Concatenate the replacements.  */
+                            const char *part1 = language_table[i].english;
+                            size_t part1_len = strlen (part1);
+                            const char *part2 = country_table[j].english;
+                            size_t part2_len = strlen (part2) + 1;
+                            char buf[64+64];
+
+                            if (!(part1_len + 1 + part2_len <= sizeof (buf)))
+                              abort ();
+                            memcpy (buf, part1, part1_len);
+                            buf[part1_len] = '_';
+                            memcpy (buf + part1_len + 1, part2, part2_len);
+
+                            /* Try the concatenated replacements.  */
+                            result = setlocale (category, buf);
+                            if (result != NULL)
+                              return result;
+                          }
+                    }
+
+                  /* Try omitting the country entirely.  This may set a locale
+                     corresponding to the wrong country, but is better than
+                     failing entirely.  */
+                  {
+                    size_t i;
+
+                    for (i = language_range.lo; i < language_range.hi; i++)
+                      {
+                        /* Try only the language replacement.  */
+                        result =
+                          setlocale (category, language_table[i].english);
+                        if (result != NULL)
+                          return result;
+                      }
+                  }
+                }
+            }
+          }
+      }
+    }
+
+  /* Failed.  */
+  return NULL;
+}
+
+# elif defined __ANDROID__
+
+/* Like setlocale, but accept also the locale names "C" and "POSIX".  */
+static char *
+setlocale_unixlike (int category, const char *locale)
+{
+  char *result = setlocale (category, locale);
+  if (result == NULL)
+    switch (category)
+      {
+      case LC_CTYPE:
+      case LC_NUMERIC:
+      case LC_TIME:
+      case LC_COLLATE:
+      case LC_MONETARY:
+      case LC_MESSAGES:
+      case LC_ALL:
+      case LC_PAPER:
+      case LC_NAME:
+      case LC_ADDRESS:
+      case LC_TELEPHONE:
+      case LC_MEASUREMENT:
+        if (locale == NULL
+            || strcmp (locale, "C") == 0 || strcmp (locale, "POSIX") == 0)
+          result = (char *) "C";
+        break;
+      default:
+        break;
+      }
+  return result;
+}
+#  define setlocale setlocale_unixlike
+
+# else
+#  define setlocale_unixlike setlocale
+# endif
+
+# if LC_MESSAGES == 1729
+
+/* The system does not store an LC_MESSAGES locale category.  Do it here.  */
+static char lc_messages_name[64] = "C";
+
+/* Like setlocale, but support also LC_MESSAGES.  */
+static char *
+setlocale_single (int category, const char *locale)
+{
+  if (category == LC_MESSAGES)
+    {
+      if (locale != NULL)
+        {
+          lc_messages_name[sizeof (lc_messages_name) - 1] = '\0';
+          strncpy (lc_messages_name, locale, sizeof (lc_messages_name) - 1);
+        }
+      return lc_messages_name;
+    }
+  else
+    return setlocale_unixlike (category, locale);
+}
+
+# else
+#  define setlocale_single setlocale_unixlike
+# endif
+
+# if defined __APPLE__ && defined __MACH__
+
+/* Mapping from language to main territory where that language is spoken.  */
+static char const locales_with_principal_territory[][6 + 1] =
+  {
+                /* Language     Main territory */
+    "ace_ID",   /* Achinese     Indonesia */
+    "af_ZA",    /* Afrikaans    South Africa */
+    "ak_GH",    /* Akan         Ghana */
+    "am_ET",    /* Amharic      Ethiopia */
+    "an_ES",    /* Aragonese    Spain */
+    "ang_GB",   /* Old English  Britain */
+    "arn_CL",   /* Mapudungun   Chile */
+    "as_IN",    /* Assamese     India */
+    "ast_ES",   /* Asturian     Spain */
+    "av_RU",    /* Avaric       Russia */
+    "awa_IN",   /* Awadhi       India */
+    "az_AZ",    /* Azerbaijani  Azerbaijan */
+    "ban_ID",   /* Balinese     Indonesia */
+    "be_BY",    /* Belarusian   Belarus */
+    "bej_SD",   /* Beja         Sudan */
+    "bem_ZM",   /* Bemba        Zambia */
+    "bg_BG",    /* Bulgarian    Bulgaria */
+    "bho_IN",   /* Bhojpuri     India */
+    "bi_VU",    /* Bislama      Vanuatu */
+    "bik_PH",   /* Bikol        Philippines */
+    "bin_NG",   /* Bini         Nigeria */
+    "bm_ML",    /* Bambara      Mali */
+    "bn_IN",    /* Bengali      India */
+    "bo_CN",    /* Tibetan      China */
+    "br_FR",    /* Breton       France */
+    "bs_BA",    /* Bosnian      Bosnia */
+    "bug_ID",   /* Buginese     Indonesia */
+    "ca_ES",    /* Catalan      Spain */
+    "ce_RU",    /* Chechen      Russia */
+    "ceb_PH",   /* Cebuano      Philippines */
+    "co_FR",    /* Corsican     France */
+    "cr_CA",    /* Cree         Canada */
+    /* Don't put "crh_UZ" or "crh_UA" here.  That would be asking for fruitless
+       political discussion.  */
+    "cs_CZ",    /* Czech        Czech Republic */
+    "csb_PL",   /* Kashubian    Poland */
+    "cy_GB",    /* Welsh        Britain */
+    "da_DK",    /* Danish       Denmark */
+    "de_DE",    /* German       Germany */
+    "din_SD",   /* Dinka        Sudan */
+    "doi_IN",   /* Dogri        India */
+    "dsb_DE",   /* Lower Sorbian        Germany */
+    "dv_MV",    /* Divehi       Maldives */
+    "dz_BT",    /* Dzongkha     Bhutan */
+    "ee_GH",    /* Éwé          Ghana */
+    "el_GR",    /* Greek        Greece */
+    /* Don't put "en_GB" or "en_US" here.  That would be asking for fruitless
+       political discussion.  */
+    "es_ES",    /* Spanish      Spain */
+    "et_EE",    /* Estonian     Estonia */
+    "fa_IR",    /* Persian      Iran */
+    "fi_FI",    /* Finnish      Finland */
+    "fil_PH",   /* Filipino     Philippines */
+    "fj_FJ",    /* Fijian       Fiji */
+    "fo_FO",    /* Faroese      Faeroe Islands */
+    "fon_BJ",   /* Fon          Benin */
+    "fr_FR",    /* French       France */
+    "fur_IT",   /* Friulian     Italy */
+    "fy_NL",    /* Western Frisian      Netherlands */
+    "ga_IE",    /* Irish        Ireland */
+    "gd_GB",    /* Scottish Gaelic      Britain */
+    "gon_IN",   /* Gondi        India */
+    "gsw_CH",   /* Swiss German Switzerland */
+    "gu_IN",    /* Gujarati     India */
+    "he_IL",    /* Hebrew       Israel */
+    "hi_IN",    /* Hindi        India */
+    "hil_PH",   /* Hiligaynon   Philippines */
+    "hr_HR",    /* Croatian     Croatia */
+    "hsb_DE",   /* Upper Sorbian        Germany */
+    "ht_HT",    /* Haitian      Haiti */
+    "hu_HU",    /* Hungarian    Hungary */
+    "hy_AM",    /* Armenian     Armenia */
+    "id_ID",    /* Indonesian   Indonesia */
+    "ig_NG",    /* Igbo         Nigeria */
+    "ii_CN",    /* Sichuan Yi   China */
+    "ilo_PH",   /* Iloko        Philippines */
+    "is_IS",    /* Icelandic    Iceland */
+    "it_IT",    /* Italian      Italy */
+    "ja_JP",    /* Japanese     Japan */
+    "jab_NG",   /* Hyam         Nigeria */
+    "jv_ID",    /* Javanese     Indonesia */
+    "ka_GE",    /* Georgian     Georgia */
+    "kab_DZ",   /* Kabyle       Algeria */
+    "kaj_NG",   /* Jju          Nigeria */
+    "kam_KE",   /* Kamba        Kenya */
+    "kmb_AO",   /* Kimbundu     Angola */
+    "kcg_NG",   /* Tyap         Nigeria */
+    "kdm_NG",   /* Kagoma       Nigeria */
+    "kg_CD",    /* Kongo        Democratic Republic of Congo */
+    "kk_KZ",    /* Kazakh       Kazakhstan */
+    "kl_GL",    /* Kalaallisut  Greenland */
+    "km_KH",    /* Central Khmer        Cambodia */
+    "kn_IN",    /* Kannada      India */
+    "ko_KR",    /* Korean       Korea (South) */
+    "kok_IN",   /* Konkani      India */
+    "kr_NG",    /* Kanuri       Nigeria */
+    "kru_IN",   /* Kurukh       India */
+    "ky_KG",    /* Kyrgyz       Kyrgyzstan */
+    "lg_UG",    /* Ganda        Uganda */
+    "li_BE",    /* Limburgish   Belgium */
+    "lo_LA",    /* Laotian      Laos */
+    "lt_LT",    /* Lithuanian   Lithuania */
+    "lu_CD",    /* Luba-Katanga Democratic Republic of Congo */
+    "lua_CD",   /* Luba-Lulua   Democratic Republic of Congo */
+    "luo_KE",   /* Luo          Kenya */
+    "lv_LV",    /* Latvian      Latvia */
+    "mad_ID",   /* Madurese     Indonesia */
+    "mag_IN",   /* Magahi       India */
+    "mai_IN",   /* Maithili     India */
+    "mak_ID",   /* Makasar      Indonesia */
+    "man_ML",   /* Mandingo     Mali */
+    "men_SL",   /* Mende        Sierra Leone */
+    "mfe_MU",   /* Mauritian Creole     Mauritius */
+    "mg_MG",    /* Malagasy     Madagascar */
+    "mi_NZ",    /* Maori        New Zealand */
+    "min_ID",   /* Minangkabau  Indonesia */
+    "mk_MK",    /* Macedonian   North Macedonia */
+    "ml_IN",    /* Malayalam    India */
+    "mn_MN",    /* Mongolian    Mongolia */
+    "mni_IN",   /* Manipuri     India */
+    "mos_BF",   /* Mossi        Burkina Faso */
+    "mr_IN",    /* Marathi      India */
+    "ms_MY",    /* Malay        Malaysia */
+    "mt_MT",    /* Maltese      Malta */
+    "mwr_IN",   /* Marwari      India */
+    "my_MM",    /* Burmese      Myanmar */
+    "na_NR",    /* Nauru        Nauru */
+    "nah_MX",   /* Nahuatl      Mexico */
+    "nap_IT",   /* Neapolitan   Italy */
+    "nb_NO",    /* Norwegian Bokmål    Norway */
+    "nds_DE",   /* Low Saxon    Germany */
+    "ne_NP",    /* Nepali       Nepal */
+    "nl_NL",    /* Dutch        Netherlands */
+    "nn_NO",    /* Norwegian Nynorsk    Norway */
+    "no_NO",    /* Norwegian    Norway */
+    "nr_ZA",    /* South Ndebele        South Africa */
+    "nso_ZA",   /* Northern Sotho       South Africa */
+    "ny_MW",    /* Chichewa     Malawi */
+    "nym_TZ",   /* Nyamwezi     Tanzania */
+    "nyn_UG",   /* Nyankole     Uganda */
+    "oc_FR",    /* Occitan      France */
+    "oj_CA",    /* Ojibwa       Canada */
+    "or_IN",    /* Oriya        India */
+    "pa_IN",    /* Punjabi      India */
+    "pag_PH",   /* Pangasinan   Philippines */
+    "pam_PH",   /* Pampanga     Philippines */
+    "pap_AN",   /* Papiamento   Netherlands Antilles - this line can be removed in 2018 */
+    "pbb_CO",   /* Páez         Colombia */
+    "pl_PL",    /* Polish       Poland */
+    "ps_AF",    /* Pashto       Afghanistan */
+    "pt_PT",    /* Portuguese   Portugal */
+    "raj_IN",   /* Rajasthani   India */
+    "rm_CH",    /* Romansh      Switzerland */
+    "rn_BI",    /* Kirundi      Burundi */
+    "ro_RO",    /* Romanian     Romania */
+    "ru_RU",    /* Russian      Russia */
+    "rw_RW",    /* Kinyarwanda  Rwanda */
+    "sa_IN",    /* Sanskrit     India */
+    "sah_RU",   /* Yakut        Russia */
+    "sas_ID",   /* Sasak        Indonesia */
+    "sat_IN",   /* Santali      India */
+    "sc_IT",    /* Sardinian    Italy */
+    "scn_IT",   /* Sicilian     Italy */
+    "sg_CF",    /* Sango        Central African Republic */
+    "shn_MM",   /* Shan         Myanmar */
+    "si_LK",    /* Sinhala      Sri Lanka */
+    "sid_ET",   /* Sidamo       Ethiopia */
+    "sk_SK",    /* Slovak       Slovakia */
+    "sl_SI",    /* Slovenian    Slovenia */
+    "sm_WS",    /* Samoan       Samoa */
+    "smn_FI",   /* Inari Sami   Finland */
+    "sms_FI",   /* Skolt Sami   Finland */
+    "so_SO",    /* Somali       Somalia */
+    "sq_AL",    /* Albanian     Albania */
+    "sr_RS",    /* Serbian      Serbia */
+    "srr_SN",   /* Serer        Senegal */
+    "suk_TZ",   /* Sukuma       Tanzania */
+    "sus_GN",   /* Susu         Guinea */
+    "sv_SE",    /* Swedish      Sweden */
+    "te_IN",    /* Telugu       India */
+    "tem_SL",   /* Timne        Sierra Leone */
+    "tet_ID",   /* Tetum        Indonesia */
+    "tg_TJ",    /* Tajik        Tajikistan */
+    "th_TH",    /* Thai         Thailand */
+    "ti_ER",    /* Tigrinya     Eritrea */
+    "tiv_NG",   /* Tiv          Nigeria */
+    "tk_TM",    /* Turkmen      Turkmenistan */
+    "tl_PH",    /* Tagalog      Philippines */
+    "to_TO",    /* Tonga        Tonga */
+    "tpi_PG",   /* Tok Pisin    Papua New Guinea */
+    "tr_TR",    /* Turkish      Turkey */
+    "tum_MW",   /* Tumbuka      Malawi */
+    "ug_CN",    /* Uighur       China */
+    "uk_UA",    /* Ukrainian    Ukraine */
+    "umb_AO",   /* Umbundu      Angola */
+    "ur_PK",    /* Urdu         Pakistan */
+    "uz_UZ",    /* Uzbek        Uzbekistan */
+    "ve_ZA",    /* Venda        South Africa */
+    "vi_VN",    /* Vietnamese   Vietnam */
+    "wa_BE",    /* Walloon      Belgium */
+    "wal_ET",   /* Walamo       Ethiopia */
+    "war_PH",   /* Waray        Philippines */
+    "wen_DE",   /* Sorbian      Germany */
+    "yao_MW",   /* Yao          Malawi */
+    "zap_MX"    /* Zapotec      Mexico */
+  };
+
+/* Compare just the language part of two locale names.  */
+static int
+langcmp (const char *locale1, const char *locale2)
+{
+  size_t locale1_len;
+  size_t locale2_len;
+  int cmp;
+
+  {
+    const char *locale1_end = strchr (locale1, '_');
+    if (locale1_end != NULL)
+      locale1_len = locale1_end - locale1;
+    else
+      locale1_len = strlen (locale1);
+  }
+  {
+    const char *locale2_end = strchr (locale2, '_');
+    if (locale2_end != NULL)
+      locale2_len = locale2_end - locale2;
+    else
+      locale2_len = strlen (locale2);
+  }
+
+  if (locale1_len < locale2_len)
+    {
+      cmp = memcmp (locale1, locale2, locale1_len);
+      if (cmp == 0)
+        cmp = -1;
+    }
+  else
+    {
+      cmp = memcmp (locale1, locale2, locale2_len);
+      if (locale1_len > locale2_len && cmp == 0)
+        cmp = 1;
+    }
+
+  return cmp;
+}
+
+/* Given a locale name, return the main locale with the same language,
+   or NULL if not found.
+   For example: "fr_DE" -> "fr_FR".  */
+static const char *
+get_main_locale_with_same_language (const char *locale)
+{
+#  define table locales_with_principal_territory
+  /* The table is sorted.  Perform a binary search.  */
+  size_t hi = sizeof (table) / sizeof (table[0]);
+  size_t lo = 0;
+  while (lo < hi)
+    {
+      /* Invariant:
+         for i < lo, langcmp (table[i], locale) < 0,
+         for i >= hi, langcmp (table[i], locale) > 0.  */
+      size_t mid = (hi + lo) >> 1; /* >= lo, < hi */
+      int cmp = langcmp (table[mid], locale);
+      if (cmp < 0)
+        lo = mid + 1;
+      else if (cmp > 0)
+        hi = mid;
+      else
+        {
+          /* Found an i with
+               langcmp (language_table[i], locale) == 0.
+             Verify that it is the only such i.  */
+          if (mid > lo && langcmp (table[mid - 1], locale) >= 0)
+            abort ();
+          if (mid + 1 < hi && langcmp (table[mid + 1], locale) <= 0)
+            abort ();
+          return table[mid];
+        }
+    }
+#  undef table
+  return NULL;
+}
+
+/* Mapping from territory to main language that is spoken in that territory.  */
+static char const locales_with_principal_language[][6 + 1] =
+  {
+    /* This is based on the set of existing locales in glibc, with duplicates
+       removed, and on the Wikipedia pages named "Languages of <territory>".
+       If in doubt, use the locale that exists in macOS.  For example, the only
+       "*_IN" locale in macOS 10.13 is "hi_IN", so use that.  */
+    /* A useful shell function for producing a line of this table is:
+         func_line ()
+         {
+           # Usage: func_line ll_CC
+           ll=`echo "$1" | sed -e 's|_.*||'`
+           cc=`echo "$1" | sed -e 's|^.*_||'`
+           llx=`sed -n -e "s|^${ll} ||p" < gettext-tools/doc/ISO_639`
+           ccx=`expand gettext-tools/doc/ISO_3166 | sed -n -e "s|^${cc}  *||p"`
+           echo "    \"$1\",    /$X* ${llx} ${ccx} *$X/"
+         }
+     */
+              /* Main language  Territory */
+    "ca_AD",    /* Catalan      Andorra */
+    "ar_AE",    /* Arabic       United Arab Emirates */
+    "ps_AF",    /* Pashto       Afghanistan */
+    "en_AG",    /* English      Antigua and Barbuda */
+    "sq_AL",    /* Albanian     Albania */
+    "hy_AM",    /* Armenian     Armenia */
+    "pap_AN",   /* Papiamento   Netherlands Antilles - this line can be removed in 2018 */
+    "pt_AO",    /* Portuguese   Angola */
+    "es_AR",    /* Spanish      Argentina */
+    "de_AT",    /* German       Austria */
+    "en_AU",    /* English      Australia */
+    /* Aruba has two official languages: "nl_AW", "pap_AW".  */
+    "az_AZ",    /* Azerbaijani  Azerbaijan */
+    "bs_BA",    /* Bosnian      Bosnia */
+    "bn_BD",    /* Bengali      Bangladesh */
+    "nl_BE",    /* Dutch        Belgium */
+    "fr_BF",    /* French       Burkina Faso */
+    "bg_BG",    /* Bulgarian    Bulgaria */
+    "ar_BH",    /* Arabic       Bahrain */
+    "rn_BI",    /* Kirundi      Burundi */
+    "fr_BJ",    /* French       Benin */
+    "es_BO",    /* Spanish      Bolivia */
+    "pt_BR",    /* Portuguese   Brazil */
+    "dz_BT",    /* Dzongkha     Bhutan */
+    "en_BW",    /* English      Botswana */
+    "be_BY",    /* Belarusian   Belarus */
+    "en_CA",    /* English      Canada */
+    "fr_CD",    /* French       Democratic Republic of Congo */
+    "sg_CF",    /* Sango        Central African Republic */
+    "de_CH",    /* German       Switzerland */
+    "es_CL",    /* Spanish      Chile */
+    "zh_CN",    /* Chinese      China */
+    "es_CO",    /* Spanish      Colombia */
+    "es_CR",    /* Spanish      Costa Rica */
+    "es_CU",    /* Spanish      Cuba */
+    /* Curaçao has three official languages: "nl_CW", "pap_CW", "en_CW".  */
+    "el_CY",    /* Greek        Cyprus */
+    "cs_CZ",    /* Czech        Czech Republic */
+    "de_DE",    /* German       Germany */
+    /* Djibouti has two official languages: "ar_DJ" and "fr_DJ".  */
+    "da_DK",    /* Danish       Denmark */
+    "es_DO",    /* Spanish      Dominican Republic */
+    "ar_DZ",    /* Arabic       Algeria */
+    "es_EC",    /* Spanish      Ecuador */
+    "et_EE",    /* Estonian     Estonia */
+    "ar_EG",    /* Arabic       Egypt */
+    "ti_ER",    /* Tigrinya     Eritrea */
+    "es_ES",    /* Spanish      Spain */
+    "am_ET",    /* Amharic      Ethiopia */
+    "fi_FI",    /* Finnish      Finland */
+    /* Fiji has three official languages: "en_FJ", "fj_FJ", "hif_FJ".  */
+    "fo_FO",    /* Faroese      Faeroe Islands */
+    "fr_FR",    /* French       France */
+    "en_GB",    /* English      Britain */
+    "ka_GE",    /* Georgian     Georgia */
+    "en_GH",    /* English      Ghana */
+    "kl_GL",    /* Kalaallisut  Greenland */
+    "fr_GN",    /* French       Guinea */
+    "el_GR",    /* Greek        Greece */
+    "es_GT",    /* Spanish      Guatemala */
+    "zh_HK",    /* Chinese      Hong Kong */
+    "es_HN",    /* Spanish      Honduras */
+    "hr_HR",    /* Croatian     Croatia */
+    "ht_HT",    /* Haitian      Haiti */
+    "hu_HU",    /* Hungarian    Hungary */
+    "id_ID",    /* Indonesian   Indonesia */
+    "en_IE",    /* English      Ireland */
+    "he_IL",    /* Hebrew       Israel */
+    "hi_IN",    /* Hindi        India */
+    "ar_IQ",    /* Arabic       Iraq */
+    "fa_IR",    /* Persian      Iran */
+    "is_IS",    /* Icelandic    Iceland */
+    "it_IT",    /* Italian      Italy */
+    "ar_JO",    /* Arabic       Jordan */
+    "ja_JP",    /* Japanese     Japan */
+    "sw_KE",    /* Swahili      Kenya */
+    "ky_KG",    /* Kyrgyz       Kyrgyzstan */
+    "km_KH",    /* Central Khmer        Cambodia */
+    "ko_KR",    /* Korean       Korea (South) */
+    "ar_KW",    /* Arabic       Kuwait */
+    "kk_KZ",    /* Kazakh       Kazakhstan */
+    "lo_LA",    /* Laotian      Laos */
+    "ar_LB",    /* Arabic       Lebanon */
+    "de_LI",    /* German       Liechtenstein */
+    "si_LK",    /* Sinhala      Sri Lanka */
+    "lt_LT",    /* Lithuanian   Lithuania */
+    /* Luxembourg has three official languages: "lb_LU", "fr_LU", "de_LU".  */
+    "lv_LV",    /* Latvian      Latvia */
+    "ar_LY",    /* Arabic       Libya */
+    "ar_MA",    /* Arabic       Morocco */
+    "sr_ME",    /* Serbian      Montenegro */
+    "mg_MG",    /* Malagasy     Madagascar */
+    "mk_MK",    /* Macedonian   North Macedonia */
+    "fr_ML",    /* French       Mali */
+    "my_MM",    /* Burmese      Myanmar */
+    "mn_MN",    /* Mongolian    Mongolia */
+    "mt_MT",    /* Maltese      Malta */
+    "mfe_MU",   /* Mauritian Creole     Mauritius */
+    "dv_MV",    /* Divehi       Maldives */
+    "ny_MW",    /* Chichewa     Malawi */
+    "es_MX",    /* Spanish      Mexico */
+    "ms_MY",    /* Malay        Malaysia */
+    "en_NG",    /* English      Nigeria */
+    "es_NI",    /* Spanish      Nicaragua */
+    "nl_NL",    /* Dutch        Netherlands */
+    "no_NO",    /* Norwegian    Norway */
+    "ne_NP",    /* Nepali       Nepal */
+    "na_NR",    /* Nauru        Nauru */
+    "niu_NU",   /* Niuean       Niue */
+    "en_NZ",    /* English      New Zealand */
+    "ar_OM",    /* Arabic       Oman */
+    "es_PA",    /* Spanish      Panama */
+    "es_PE",    /* Spanish      Peru */
+    "tpi_PG",   /* Tok Pisin    Papua New Guinea */
+    "fil_PH",   /* Filipino     Philippines */
+    "pa_PK",    /* Punjabi      Pakistan */
+    "pl_PL",    /* Polish       Poland */
+    "es_PR",    /* Spanish      Puerto Rico */
+    "pt_PT",    /* Portuguese   Portugal */
+    "es_PY",    /* Spanish      Paraguay */
+    "ar_QA",    /* Arabic       Qatar */
+    "ro_RO",    /* Romanian     Romania */
+    "sr_RS",    /* Serbian      Serbia */
+    "ru_RU",    /* Russian      Russia */
+    "rw_RW",    /* Kinyarwanda  Rwanda */
+    "ar_SA",    /* Arabic       Saudi Arabia */
+    "en_SC",    /* English      Seychelles */
+    "ar_SD",    /* Arabic       Sudan */
+    "sv_SE",    /* Swedish      Sweden */
+    "en_SG",    /* English      Singapore */
+    "sl_SI",    /* Slovenian    Slovenia */
+    "sk_SK",    /* Slovak       Slovakia */
+    "en_SL",    /* English      Sierra Leone */
+    "fr_SN",    /* French       Senegal */
+    "so_SO",    /* Somali       Somalia */
+    "ar_SS",    /* Arabic       South Sudan */
+    "es_SV",    /* Spanish      El Salvador */
+    "ar_SY",    /* Arabic       Syria */
+    "th_TH",    /* Thai         Thailand */
+    "tg_TJ",    /* Tajik        Tajikistan */
+    "tk_TM",    /* Turkmen      Turkmenistan */
+    "ar_TN",    /* Arabic       Tunisia */
+    "to_TO",    /* Tonga        Tonga */
+    "tr_TR",    /* Turkish      Turkey */
+    "zh_TW",    /* Chinese      Taiwan */
+    "sw_TZ",    /* Swahili      Tanzania */
+    "uk_UA",    /* Ukrainian    Ukraine */
+    "lg_UG",    /* Ganda        Uganda */
+    "en_US",    /* English      United States of America */
+    "es_UY",    /* Spanish      Uruguay */
+    "uz_UZ",    /* Uzbek        Uzbekistan */
+    "es_VE",    /* Spanish      Venezuela */
+    "vi_VN",    /* Vietnamese   Vietnam */
+    "bi_VU",    /* Bislama      Vanuatu */
+    "sm_WS",    /* Samoan       Samoa */
+    "ar_YE",    /* Arabic       Yemen */
+    "en_ZA",    /* English      South Africa */
+    "en_ZM",    /* English      Zambia */
+    "en_ZW"     /* English      Zimbabwe */
+  };
+
+/* Compare just the territory part of two locale names.  */
+static int
+terrcmp (const char *locale1, const char *locale2)
+{
+  const char *territory1 = strrchr (locale1, '_') + 1;
+  const char *territory2 = strrchr (locale2, '_') + 1;
+
+  return strcmp (territory1, territory2);
+}
+
+/* Given a locale name, return the locale corresponding to the main language
+   with the same territory, or NULL if not found.
+   For example: "fr_DE" -> "de_DE".  */
+static const char *
+get_main_locale_with_same_territory (const char *locale)
+{
+  if (strrchr (locale, '_') != NULL)
+    {
+#  define table locales_with_principal_language
+      /* The table is sorted.  Perform a binary search.  */
+      size_t hi = sizeof (table) / sizeof (table[0]);
+      size_t lo = 0;
+      while (lo < hi)
+        {
+          /* Invariant:
+             for i < lo, terrcmp (table[i], locale) < 0,
+             for i >= hi, terrcmp (table[i], locale) > 0.  */
+          size_t mid = (hi + lo) >> 1; /* >= lo, < hi */
+          int cmp = terrcmp (table[mid], locale);
+          if (cmp < 0)
+            lo = mid + 1;
+          else if (cmp > 0)
+            hi = mid;
+          else
+            {
+              /* Found an i with
+                   terrcmp (language_table[i], locale) == 0.
+                 Verify that it is the only such i.  */
+              if (mid > lo && terrcmp (table[mid - 1], locale) >= 0)
+                abort ();
+              if (mid + 1 < hi && terrcmp (table[mid + 1], locale) <= 0)
+                abort ();
+              return table[mid];
+            }
+        }
+#  undef table
+    }
+  return NULL;
+}
+
+# endif
+
+DLL_EXPORTED
+char *
+libintl_setlocale (int category, const char *locale)
+{
+  if (locale != NULL && locale[0] == '\0')
+    {
+      /* A request to the set the current locale to the default locale.  */
+      if (category == LC_ALL)
+        {
+          /* Set LC_CTYPE first.  Then the other categories.  */
+          static int const categories[] =
+            {
+              LC_CTYPE,
+              LC_NUMERIC,
+              LC_TIME,
+              LC_COLLATE,
+              LC_MONETARY,
+              LC_MESSAGES
+            };
+          char *saved_locale;
+          const char *base_name;
+          unsigned int i;
+
+          /* Back up the old locale, in case one of the steps fails.  */
+          saved_locale = setlocale (LC_ALL, NULL);
+          if (saved_locale == NULL)
+            return NULL;
+          saved_locale = strdup (saved_locale);
+          if (saved_locale == NULL)
+            return NULL;
+
+          /* Set LC_CTYPE category.  Set all other categories (except possibly
+             LC_MESSAGES) to the same value in the same call; this is likely to
+             save calls.  */
+          base_name =
+            gl_locale_name_environ (LC_CTYPE, category_to_name (LC_CTYPE));
+          if (base_name == NULL)
+            base_name = gl_locale_name_default ();
+
+          if (setlocale_unixlike (LC_ALL, base_name) != NULL)
+            {
+              /* LC_CTYPE category already set.  */
+              i = 1;
+            }
+          else
+            {
+              /* On Mac OS X, "UTF-8" is a valid locale name for LC_CTYPE but
+                 not for LC_ALL.  Therefore this call may fail.  So, try
+                 another base_name.  */
+              base_name = "C";
+              if (setlocale_unixlike (LC_ALL, base_name) == NULL)
+                goto fail;
+              i = 0;
+            }
+# if defined _WIN32 && ! defined __CYGWIN__
+          /* On native Windows, setlocale(LC_ALL,...) may succeed but set the
+             LC_CTYPE category to an invalid value ("C") when it does not
+             support the specified encoding.  Report a failure instead.  */
+          if (strchr (base_name, '.') != NULL
+              && strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+            goto fail;
+# endif
+
+          for (; i < sizeof (categories) / sizeof (categories[0]); i++)
+            {
+              int cat = categories[i];
+              const char *name;
+
+              name = gl_locale_name_environ (cat, category_to_name (cat));
+              if (name == NULL)
+                name = gl_locale_name_default ();
+
+              /* If name is the same as base_name, it has already been set
+                 through the setlocale call before the loop.  */
+              if (strcmp (name, base_name) != 0
+# if LC_MESSAGES == 1729
+                  || cat == LC_MESSAGES
+# endif
+                 )
+                if (setlocale_single (cat, name) == NULL)
+# if defined __APPLE__ && defined __MACH__
+                  {
+                    /* On Mac OS X 10.13, some locales can be set through
+                       System Preferences > Language & Region, that are not
+                       supported by libc.  The system's setlocale() falls
+                       back to "C" for these locale categories.  We can do
+                       better, by trying an existing locale with the same
+                       language or an existing locale with the same territory.
+                       If we can't, print a warning, to limit user
+                       expectations.  */
+                    int warn = 0;
+
+                    if (cat == LC_CTYPE)
+                      warn = (setlocale_single (cat, "UTF-8") == NULL);
+                    else if (cat == LC_MESSAGES)
+                      {
+#  if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
+                        /* Take the primary language preference.  */
+#   if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
+                        CFArrayRef prefArray = CFLocaleCopyPreferredLanguages ();
+#   elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
+                        CFTypeRef preferences =
+                          CFPreferencesCopyAppValue (CFSTR ("AppleLanguages"),
+                                                     kCFPreferencesCurrentApplication);
+                        if (preferences != NULL
+                            && CFGetTypeID (preferences) == CFArrayGetTypeID ())
+                          {
+                            CFArrayRef prefArray = (CFArrayRef)preferences;
+#   endif
+                            int n = CFArrayGetCount (prefArray);
+                            if (n > 0)
+                              {
+                                char buf[256];
+                                CFTypeRef element = CFArrayGetValueAtIndex (prefArray, 0);
+                                if (element != NULL
+                                    && CFGetTypeID (element) == CFStringGetTypeID ()
+                                    && CFStringGetCString ((CFStringRef)element,
+                                                           buf, sizeof (buf),
+                                                           kCFStringEncodingASCII))
+                                  {
+                                    /* Remove the country.
+                                       E.g. "zh-Hans-DE" -> "zh-Hans".  */
+                                    char *last_minus = strrchr (buf, '-');
+                                    if (last_minus != NULL)
+                                      *last_minus = '\0';
+
+                                    /* Convert to Unix locale name.
+                                       E.g. "zh-Hans" -> "zh_CN".  */
+                                    gl_locale_name_canonicalize (buf);
+
+                                    /* Try setlocale with this value.  */
+                                    if (setlocale_single (cat, buf) == NULL)
+                                      {
+                                        const char *last_try =
+                                          get_main_locale_with_same_language (buf);
+
+                                        if (last_try == NULL
+                                            || setlocale_single (cat, last_try) == NULL)
+                                          warn = 1;
+                                      }
+                                  }
+                              }
+#   if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
+                        CFRelease (prefArray);
+#   elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
+                          }
+#   endif
+#  else
+                        const char *last_try =
+                          get_main_locale_with_same_language (name);
+
+                        if (last_try == NULL
+                            || setlocale_single (cat, last_try) == NULL)
+                          warn = 1;
+#  endif
+                      }
+                    else
+                      {
+                        /* For LC_NUMERIC, the application should use the locale
+                           properties kCFLocaleDecimalSeparator,
+                           kCFLocaleGroupingSeparator.
+                           For LC_TIME, the application should use the locale
+                           property kCFLocaleCalendarIdentifier.
+                           For LC_COLLATE, the application should use the locale
+                           properties kCFLocaleCollationIdentifier,
+                           kCFLocaleCollatorIdentifier.
+                           For LC_MONETARY, the applicationshould use the locale
+                           properties kCFLocaleCurrencySymbol,
+                           kCFLocaleCurrencyCode.
+                           But since most applications don't have macOS specific
+                           code like this, try an existing locale with the same
+                           territory.  */
+                        const char *last_try =
+                          get_main_locale_with_same_territory (name);
+
+                        if (last_try == NULL
+                            || setlocale_single (cat, last_try) == NULL)
+                          warn = 1;
+                      }
+
+                    if (warn)
+                      {
+                        /* Warn only if the environment variable
+                           SETLOCALE_VERBOSE is set.  Otherwise these warnings
+                           are just annoyances, since normal users won't invoke
+                           'localedef'.  */
+                        const char *verbose = getenv ("SETLOCALE_VERBOSE");
+                        if (verbose != NULL && verbose[0] != '\0')
+                          fprintf (stderr,
+                                   "Warning: Failed to set locale category %s to %s.\n",
+                                   category_to_name (cat), name);
+                      }
+                  }
+# else
+                  goto fail;
+# endif
+            }
+
+          /* All steps were successful.  */
+          ++_nl_msg_cat_cntr;
+          free (saved_locale);
+          return setlocale (LC_ALL, NULL);
+
+        fail:
+          if (saved_locale[0] != '\0') /* don't risk an endless recursion */
+            setlocale (LC_ALL, saved_locale);
+          free (saved_locale);
+          return NULL;
+        }
+      else
+        {
+          char *result;
+          const char *name =
+            gl_locale_name_environ (category, category_to_name (category));
+          if (name == NULL)
+            name = gl_locale_name_default ();
+
+          result = setlocale_single (category, name);
+          if (result != NULL)
+            ++_nl_msg_cat_cntr;
+          return result;
+        }
+    }
+  else
+    {
+# if defined _WIN32 && ! defined __CYGWIN__
+      if (category == LC_ALL && locale != NULL && strchr (locale, '.') != NULL)
+        {
+          char *saved_locale;
+
+          /* Back up the old locale.  */
+          saved_locale = setlocale (LC_ALL, NULL);
+          if (saved_locale == NULL)
+            return NULL;
+          saved_locale = strdup (saved_locale);
+          if (saved_locale == NULL)
+            return NULL;
+
+          if (setlocale_unixlike (LC_ALL, locale) == NULL)
+            {
+              free (saved_locale);
+              return NULL;
+            }
+
+          /* On native Windows, setlocale(LC_ALL,...) may succeed but set the
+             LC_CTYPE category to an invalid value ("C") when it does not
+             support the specified encoding.  Report a failure instead.  */
+          if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+            {
+              if (saved_locale[0] != '\0') /* don't risk an endless recursion */
+                setlocale (LC_ALL, saved_locale);
+              free (saved_locale);
+              return NULL;
+            }
+
+          /* It was really successful.  */
+          ++_nl_msg_cat_cntr;
+          free (saved_locale);
+          return setlocale (LC_ALL, NULL);
+        }
+      else
+# endif
+        {
+          char *result = setlocale_single (category, locale);
+          if (result != NULL)
+            ++_nl_msg_cat_cntr;
+          return result;
+        }
+    }
+}
+
+# if HAVE_NEWLOCALE
+
+DLL_EXPORTED
+locale_t
+libintl_newlocale (int category_mask, const char *locale, locale_t base)
+{
+  if (category_mask != 0 && locale != NULL && locale[0] == '\0')
+    {
+      /* A request to construct a locale_t object that refers to the default
+         locale.  */
+
+      /* Set LC_CTYPE first.  Then the other categories.  */
+      static struct { int cat; int mask; } const categories[] =
+        {
+          { LC_CTYPE,    LC_CTYPE_MASK },
+          { LC_NUMERIC,  LC_NUMERIC_MASK },
+          { LC_TIME,     LC_TIME_MASK },
+          { LC_COLLATE,  LC_COLLATE_MASK },
+          { LC_MONETARY, LC_MONETARY_MASK },
+          { LC_MESSAGES, LC_MESSAGES_MASK }
+        };
+
+      locale_t orig_base = base;
+
+      if ((LC_ALL_MASK & ~category_mask) == 0)
+        {
+          const char *base_name;
+          unsigned int i;
+
+          /* Set LC_CTYPE category.  Set all other categories (except possibly
+             LC_MESSAGES) to the same value in the same call; this is likely to
+             save calls.  */
+          base_name =
+            gl_locale_name_environ (LC_CTYPE, category_to_name (LC_CTYPE));
+          if (base_name == NULL)
+            base_name = gl_locale_name_default ();
+
+          base = newlocale (LC_ALL_MASK, base_name, base);
+          if (base == NULL)
+            return NULL;
+
+          for (i = 1; i < sizeof (categories) / sizeof (categories[0]); i++)
+            {
+              int category = categories[i].cat;
+              int category_mask = categories[i].mask;
+              const char *name;
+
+              name =
+                gl_locale_name_environ (category, category_to_name (category));
+              if (name == NULL)
+                name = gl_locale_name_default ();
+
+              /* If name is the same as base_name, it has already been set
+                 through the setlocale call before the loop.  */
+              if (strcmp (name, base_name) != 0)
+                {
+                  locale_t copy = newlocale (category_mask, name, base);
+                  if (copy == NULL)
+                    goto fail;
+                  /* No need to call freelocale (base) if copy != base; the
+                     newlocale function already takes care of doing it.  */
+                  base = copy;
+                }
+            }
+        }
+      else
+        {
+          unsigned int i;
+
+          for (i = 0; i < sizeof (categories) / sizeof (categories[0]); i++)
+            {
+              int cat_mask = categories[i].mask;
+
+              if ((category_mask & cat_mask) != 0)
+                {
+                  int cat = categories[i].cat;
+                  const char *name;
+                  locale_t copy;
+
+                  name = gl_locale_name_environ (cat, category_to_name (cat));
+                  if (name == NULL)
+                    name = gl_locale_name_default ();
+
+                  copy = newlocale (cat_mask, name, base);
+                  if (copy == NULL)
+                    goto fail;
+                  /* No need to call freelocale (base) if copy != base; the
+                     newlocale function already takes care of doing it.  */
+                  base = copy;
+                }
+            }
+        }
+
+      /* All steps were successful.  */
+      return base;
+
+    fail:
+      if (base != NULL && orig_base == NULL)
+        {
+          int saved_errno = errno;
+          freelocale (base);
+          errno = saved_errno;
+        }
+      return NULL;
+    }
+  else
+    return newlocale (category_mask, locale, base);
+}
+
+# endif
+
+#endif
diff --git a/lib/intl/setlocale_null.c b/lib/intl/setlocale_null.c
new file mode 100644 (file)
index 0000000..e62b3a2
--- /dev/null
@@ -0,0 +1,414 @@
+/* Query the name of the current global locale.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2019.  */
+
+#include <config.h>
+
+#define SETLOCALE_NULL_ALL_MTSAFE 1
+#define SETLOCALE_NULL_ONE_MTSAFE 1
+
+/* Specification.  */
+#include "setlocale_null.h"
+
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined _WIN32 && !defined __CYGWIN__
+# include <wchar.h>
+#endif
+
+#if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE)
+# if defined _WIN32 && !defined __CYGWIN__
+
+#  define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#  include <windows.h>
+
+# elif HAVE_PTHREAD_API
+
+#  include <pthread.h>
+#  if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
+#   include <threads.h>
+#   pragma weak thrd_exit
+#   define c11_threads_in_use() (thrd_exit != NULL)
+#  else
+#   define c11_threads_in_use() 0
+#  endif
+
+# elif HAVE_THREADS_H
+
+#  include <threads.h>
+
+# endif
+#endif
+
+/* Use the system's setlocale() function, not the gnulib override, here.  */
+#undef setlocale
+
+static const char *
+setlocale_null_androidfix (int category)
+{
+  const char *result = setlocale (category, NULL);
+
+#ifdef __ANDROID__
+  if (result == NULL)
+    switch (category)
+      {
+      case LC_CTYPE:
+      case LC_NUMERIC:
+      case LC_TIME:
+      case LC_COLLATE:
+      case LC_MONETARY:
+      case LC_MESSAGES:
+      case LC_ALL:
+      case LC_PAPER:
+      case LC_NAME:
+      case LC_ADDRESS:
+      case LC_TELEPHONE:
+      case LC_MEASUREMENT:
+        result = "C";
+        break;
+      default:
+        break;
+      }
+#endif
+
+  return result;
+}
+
+static int
+setlocale_null_unlocked (int category, char *buf, size_t bufsize)
+{
+#if defined _WIN32 && !defined __CYGWIN__ && defined _MSC_VER
+  /* On native Windows, nowadays, the setlocale() implementation is based
+     on _wsetlocale() and uses malloc() for the result.  We are better off
+     using _wsetlocale() directly.  */
+  const wchar_t *result = _wsetlocale (category, NULL);
+
+  if (result == NULL)
+    {
+      /* CATEGORY is invalid.  */
+      if (bufsize > 0)
+        /* Return an empty string in BUF.
+           This is a convenience for callers that don't want to write explicit
+           code for handling EINVAL.  */
+        buf[0] = '\0';
+      return EINVAL;
+    }
+  else
+    {
+      size_t length = wcslen (result);
+      if (length < bufsize)
+        {
+          size_t i;
+
+          /* Convert wchar_t[] -> char[], assuming plain ASCII.  */
+          for (i = 0; i <= length; i++)
+            buf[i] = result[i];
+
+          return 0;
+        }
+      else
+        {
+          if (bufsize > 0)
+            {
+              /* Return a truncated result in BUF.
+                 This is a convenience for callers that don't want to write
+                 explicit code for handling ERANGE.  */
+              size_t i;
+
+              /* Convert wchar_t[] -> char[], assuming plain ASCII.  */
+              for (i = 0; i < bufsize; i++)
+                buf[i] = result[i];
+              buf[bufsize - 1] = '\0';
+            }
+          return ERANGE;
+        }
+    }
+#else
+  const char *result = setlocale_null_androidfix (category);
+
+  if (result == NULL)
+    {
+      /* CATEGORY is invalid.  */
+      if (bufsize > 0)
+        /* Return an empty string in BUF.
+           This is a convenience for callers that don't want to write explicit
+           code for handling EINVAL.  */
+        buf[0] = '\0';
+      return EINVAL;
+    }
+  else
+    {
+      size_t length = strlen (result);
+      if (length < bufsize)
+        {
+          memcpy (buf, result, length + 1);
+          return 0;
+        }
+      else
+        {
+          if (bufsize > 0)
+            {
+              /* Return a truncated result in BUF.
+                 This is a convenience for callers that don't want to write
+                 explicit code for handling ERANGE.  */
+              memcpy (buf, result, bufsize - 1);
+              buf[bufsize - 1] = '\0';
+            }
+          return ERANGE;
+        }
+    }
+#endif
+}
+
+#if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin */
+
+/* Use a lock, so that no two threads can invoke setlocale_null_unlocked
+   at the same time.  */
+
+/* Prohibit renaming this symbol.  */
+# undef gl_get_setlocale_null_lock
+
+# if defined _WIN32 && !defined __CYGWIN__
+
+extern __declspec(dllimport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void);
+
+static int
+setlocale_null_with_lock (int category, char *buf, size_t bufsize)
+{
+  CRITICAL_SECTION *lock = gl_get_setlocale_null_lock ();
+  int ret;
+
+  EnterCriticalSection (lock);
+  ret = setlocale_null_unlocked (category, buf, bufsize);
+  LeaveCriticalSection (lock);
+
+  return ret;
+}
+
+# elif HAVE_PTHREAD_API /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin */
+
+extern
+#  if defined _WIN32 || defined __CYGWIN__
+  __declspec(dllimport)
+#  endif
+  pthread_mutex_t *gl_get_setlocale_null_lock (void);
+
+#  if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
+
+    /* Avoid the need to link with '-lpthread'.  */
+#   pragma weak pthread_mutex_lock
+#   pragma weak pthread_mutex_unlock
+
+    /* Determine whether libpthread is in use.  */
+#   pragma weak pthread_mutexattr_gettype
+    /* See the comments in lock.h.  */
+#   define pthread_in_use() \
+      (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
+
+#  else
+#   define pthread_in_use() 1
+#  endif
+
+static int
+setlocale_null_with_lock (int category, char *buf, size_t bufsize)
+{
+  if (pthread_in_use())
+    {
+      pthread_mutex_t *lock = gl_get_setlocale_null_lock ();
+      int ret;
+
+      if (pthread_mutex_lock (lock))
+        abort ();
+      ret = setlocale_null_unlocked (category, buf, bufsize);
+      if (pthread_mutex_unlock (lock))
+        abort ();
+
+      return ret;
+    }
+  else
+    return setlocale_null_unlocked (category, buf, bufsize);
+}
+
+# elif HAVE_THREADS_H
+
+extern mtx_t *gl_get_setlocale_null_lock (void);
+
+static int
+setlocale_null_with_lock (int category, char *buf, size_t bufsize)
+{
+  mtx_t *lock = gl_get_setlocale_null_lock ();
+  int ret;
+
+  if (mtx_lock (lock) != thrd_success)
+    abort ();
+  ret = setlocale_null_unlocked (category, buf, bufsize);
+  if (mtx_unlock (lock) != thrd_success)
+    abort ();
+
+  return ret;
+}
+
+# endif
+
+#endif
+
+int
+setlocale_null_r (int category, char *buf, size_t bufsize)
+{
+#if SETLOCALE_NULL_ALL_MTSAFE
+# if SETLOCALE_NULL_ONE_MTSAFE
+
+  return setlocale_null_unlocked (category, buf, bufsize);
+
+# else
+
+  if (category == LC_ALL)
+    return setlocale_null_unlocked (category, buf, bufsize);
+  else
+    return setlocale_null_with_lock (category, buf, bufsize);
+
+# endif
+#else
+# if SETLOCALE_NULL_ONE_MTSAFE
+
+  if (category == LC_ALL)
+    return setlocale_null_with_lock (category, buf, bufsize);
+  else
+    return setlocale_null_unlocked (category, buf, bufsize);
+
+# else
+
+  return setlocale_null_with_lock (category, buf, bufsize);
+
+# endif
+#endif
+}
+
+const char *
+setlocale_null (int category)
+{
+#if SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE
+  return setlocale_null_androidfix (category);
+#else
+
+  /* This call must be multithread-safe.  To achieve this without using
+     thread-local storage:
+       1. We use a specific static buffer for each possible CATEGORY
+          argument.  So that different threads can call setlocale_mtsafe
+          with different CATEGORY arguments, without interfering.
+       2. We use a simple strcpy or memcpy to fill this static buffer.
+          Filling it through, for example, strcpy + strcat would not be
+          guaranteed to leave the buffer's contents intact if another thread
+          is currently accessing it.  If necessary, the contents is first
+          assembled in a stack-allocated buffer.  */
+  if (category == LC_ALL)
+    {
+# if SETLOCALE_NULL_ALL_MTSAFE
+      return setlocale_null_androidfix (LC_ALL);
+# else
+      char buf[SETLOCALE_NULL_ALL_MAX];
+      static char resultbuf[SETLOCALE_NULL_ALL_MAX];
+
+      if (setlocale_null_r (LC_ALL, buf, sizeof (buf)))
+        return "C";
+      strcpy (resultbuf, buf);
+      return resultbuf;
+# endif
+    }
+  else
+    {
+# if SETLOCALE_NULL_ONE_MTSAFE
+      return setlocale_null_androidfix (category);
+# else
+      enum
+        {
+          LC_CTYPE_INDEX,
+          LC_NUMERIC_INDEX,
+          LC_TIME_INDEX,
+          LC_COLLATE_INDEX,
+          LC_MONETARY_INDEX,
+          LC_MESSAGES_INDEX,
+#  ifdef LC_PAPER
+          LC_PAPER_INDEX,
+#  endif
+#  ifdef LC_NAME
+          LC_NAME_INDEX,
+#  endif
+#  ifdef LC_ADDRESS
+          LC_ADDRESS_INDEX,
+#  endif
+#  ifdef LC_TELEPHONE
+          LC_TELEPHONE_INDEX,
+#  endif
+#  ifdef LC_MEASUREMENT
+          LC_MEASUREMENT_INDEX,
+#  endif
+#  ifdef LC_IDENTIFICATION
+          LC_IDENTIFICATION_INDEX,
+#  endif
+          LC_INDICES_COUNT
+        }
+        i;
+      char buf[SETLOCALE_NULL_MAX];
+      static char resultbuf[LC_INDICES_COUNT][SETLOCALE_NULL_MAX];
+      int err;
+
+      err = setlocale_null_r (category, buf, sizeof (buf));
+      if (err == EINVAL)
+        return NULL;
+      if (err)
+        return "C";
+
+      switch (category)
+        {
+        case LC_CTYPE:          i = LC_CTYPE_INDEX;          break;
+        case LC_NUMERIC:        i = LC_NUMERIC_INDEX;        break;
+        case LC_TIME:           i = LC_TIME_INDEX;           break;
+        case LC_COLLATE:        i = LC_COLLATE_INDEX;        break;
+        case LC_MONETARY:       i = LC_MONETARY_INDEX;       break;
+        case LC_MESSAGES:       i = LC_MESSAGES_INDEX;       break;
+#  ifdef LC_PAPER
+        case LC_PAPER:          i = LC_PAPER_INDEX;          break;
+#  endif
+#  ifdef LC_NAME
+        case LC_NAME:           i = LC_NAME_INDEX;           break;
+#  endif
+#  ifdef LC_ADDRESS
+        case LC_ADDRESS:        i = LC_ADDRESS_INDEX;        break;
+#  endif
+#  ifdef LC_TELEPHONE
+        case LC_TELEPHONE:      i = LC_TELEPHONE_INDEX;      break;
+#  endif
+#  ifdef LC_MEASUREMENT
+        case LC_MEASUREMENT:    i = LC_MEASUREMENT_INDEX;    break;
+#  endif
+#  ifdef LC_IDENTIFICATION
+        case LC_IDENTIFICATION: i = LC_IDENTIFICATION_INDEX; break;
+#  endif
+        default:
+          /* If you get here, a #ifdef LC_xxx is missing.  */
+          abort ();
+        }
+
+      strcpy (resultbuf[i], buf);
+      return resultbuf[i];
+# endif
+    }
+#endif
+}
diff --git a/lib/intl/setlocale_null.h b/lib/intl/setlocale_null.h
new file mode 100644 (file)
index 0000000..b6c5c31
--- /dev/null
@@ -0,0 +1,82 @@
+/* Query the name of the current global locale.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2019.  */
+
+#ifndef _SETLOCALE_NULL_H
+#define _SETLOCALE_NULL_H
+
+#include <stddef.h>
+
+#include "arg-nonnull.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Recommended size of a buffer for a locale name for a single category.
+   On glibc systems, you can have locale names that are relative file names;
+   assume a maximum length 256.
+   In native Windows, in 2018 the longest locale name was of length 58
+   ("FYRO Macedonian_Former Yugoslav Republic of Macedonia.1251").  */
+#define SETLOCALE_NULL_MAX (256+1)
+
+/* Recommended size of a buffer for a locale name with all categories.
+   On glibc systems, you can have locale names that are relative file names;
+   assume maximum length 256 for each.  There are 12 categories; so, the
+   maximum total length is 148+12*256.
+   In native Windows, there are 5 categories, and the maximum total length is
+   55+5*58.  */
+#define SETLOCALE_NULL_ALL_MAX (148+12*256+1)
+
+/* setlocale_null_r (CATEGORY, BUF, BUFSIZE) is like setlocale (CATEGORY, NULL),
+   except that
+     - it is guaranteed to be multithread-safe,
+     - it returns the resulting locale category name or locale name in the
+       user-supplied buffer BUF, which must be BUFSIZE bytes long.
+   The recommended minimum buffer size is
+     - SETLOCALE_NULL_MAX for CATEGORY != LC_ALL, and
+     - SETLOCALE_NULL_ALL_MAX for CATEGORY == LC_ALL.
+   The return value is an error code: 0 if the call is successful, EINVAL if
+   CATEGORY is invalid, or ERANGE if BUFSIZE is smaller than the length needed
+   size (including the trailing NUL byte).  In the latter case, a truncated
+   result is returned in BUF, but still NUL-terminated if BUFSIZE > 0.
+   For this call to be multithread-safe, *all* calls to
+   setlocale (CATEGORY, NULL) in all other threads must have been converted
+   to use setlocale_null_r or setlocale_null as well, and the other threads
+   must not make other setlocale invocations (since changing the global locale
+   has side effects on all threads).  */
+extern int setlocale_null_r (int category, char *buf, size_t bufsize)
+  _GL_ARG_NONNULL ((2));
+
+/* setlocale_null (CATEGORY) is like setlocale (CATEGORY, NULL), except that
+   it is guaranteed to be multithread-safe.
+   The return value is NULL if CATEGORY is invalid.
+   For this call to be multithread-safe, *all* calls to
+   setlocale (CATEGORY, NULL) in all other threads must have been converted
+   to use setlocale_null_r or setlocale_null as well, and the other threads
+   must not make other setlocale invocations (since changing the global locale
+   has side effects on all threads).  */
+extern const char *setlocale_null (int category);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SETLOCALE_NULL_H */
diff --git a/lib/intl/thread-optim.h b/lib/intl/thread-optim.h
new file mode 100644 (file)
index 0000000..ef38ceb
--- /dev/null
@@ -0,0 +1,60 @@
+/* Optimization of multithreaded code.
+
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2020.  */
+
+#ifndef _THREAD_OPTIM_H
+#define _THREAD_OPTIM_H
+
+/* This file defines a way to optimize multithreaded code for the single-thread
+   case, based on the variable '__libc_single_threaded', defined in
+   glibc >= 2.32.  */
+
+/* Typical use: In a block or function, use
+
+     bool mt = gl_multithreaded ();
+     ...
+     if (mt)
+       if (pthread_mutex_lock (&lock)) abort ();
+     ...
+     if (mt)
+       if (pthread_mutex_unlock (&lock)) abort ();
+
+   The gl_multithreaded () invocation determines whether the program currently
+   is multithreaded.
+
+   if (mt) STATEMENT executes STATEMENT in the multithreaded case, and skips
+   it in the single-threaded case.
+
+   The code between the gl_multithreaded () invocation and any use of the
+   variable 'mt' must not create threads or invoke functions that may
+   indirectly create threads (e.g. 'dlopen' may, indirectly through C++
+   initializers of global variables in the shared library being opened,
+   create threads).
+
+   The lock here is meant to synchronize threads in the same process.  The
+   same optimization cannot be applied to locks that synchronize different
+   processes (e.g. through shared memory mappings).  */
+
+#if HAVE_SYS_SINGLE_THREADED_H /* glibc >= 2.32 */
+# include <sys/single_threaded.h>
+# define gl_multithreaded()  !__libc_single_threaded
+#else
+# define gl_multithreaded()  1
+#endif
+
+#endif /* _THREAD_OPTIM_H */
diff --git a/lib/intl/threadlib.c b/lib/intl/threadlib.c
new file mode 100644 (file)
index 0000000..9840ab4
--- /dev/null
@@ -0,0 +1,108 @@
+/* Multithreading primitives.
+   Copyright (C) 2005-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2005.  */
+
+#include <config.h>
+
+/* ========================================================================= */
+
+#if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS
+
+/* Use the POSIX threads library.  */
+
+# include <errno.h>
+# include <pthread.h>
+# include <stdlib.h>
+
+# if PTHREAD_IN_USE_DETECTION_HARD
+
+#  if defined __FreeBSD__ || defined __DragonFly__                 /* FreeBSD */
+
+/* Test using pthread_key_create.  */
+
+int
+glthread_in_use (void)
+{
+  static int tested;
+  static int result; /* 1: linked with -lpthread, 0: only with libc */
+
+  if (!tested)
+    {
+      pthread_key_t key;
+      int err = pthread_key_create (&key, NULL);
+
+      if (err == ENOSYS)
+        result = 0;
+      else
+        {
+          result = 1;
+          if (err == 0)
+            pthread_key_delete (key);
+        }
+      tested = 1;
+    }
+  return result;
+}
+
+#  else                                                     /* Solaris, HP-UX */
+
+/* Test using pthread_create.  */
+
+/* The function to be executed by a dummy thread.  */
+static void *
+dummy_thread_func (void *arg)
+{
+  return arg;
+}
+
+int
+glthread_in_use (void)
+{
+  static int tested;
+  static int result; /* 1: linked with -lpthread, 0: only with libc */
+
+  if (!tested)
+    {
+      pthread_t thread;
+
+      if (pthread_create (&thread, NULL, dummy_thread_func, NULL) != 0)
+        /* Thread creation failed.  */
+        result = 0;
+      else
+        {
+          /* Thread creation works.  */
+          void *retval;
+          if (pthread_join (thread, &retval) != 0)
+            abort ();
+          result = 1;
+        }
+      tested = 1;
+    }
+  return result;
+}
+
+#  endif
+
+# endif
+
+#endif
+
+/* ========================================================================= */
+
+/* This declaration is solely to ensure that after preprocessing
+   this file is never empty.  */
+typedef int dummy;
diff --git a/lib/intl/tsearch.c b/lib/intl/tsearch.c
new file mode 100644 (file)
index 0000000..9e191ef
--- /dev/null
@@ -0,0 +1,682 @@
+/* Copyright (C) 1995-1997, 2000, 2006 Free Software Foundation, Inc.
+   Contributed by Bernd Schmidt <crux@Pool.Informatik.RWTH-Aachen.DE>, 1997.
+
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@gnu.org.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Tree search for red/black trees.
+   The algorithm for adding nodes is taken from one of the many "Algorithms"
+   books by Robert Sedgewick, although the implementation differs.
+   The algorithm for deleting nodes can probably be found in a book named
+   "Introduction to Algorithms" by Cormen/Leiserson/Rivest.  At least that's
+   the book that my professor took most algorithms from during the "Data
+   Structures" course...
+
+   Totally public domain.  */
+
+/* Red/black trees are binary trees in which the edges are colored either red
+   or black.  They have the following properties:
+   1. The number of black edges on every path from the root to a leaf is
+      constant.
+   2. No two red edges are adjacent.
+   Therefore there is an upper bound on the length of every path, it's
+   O(log n) where n is the number of nodes in the tree.  No path can be longer
+   than 1+2*P where P is the length of the shortest path in the tree.
+   Useful for the implementation:
+   3. If one of the children of a node is NULL, then the other one is red
+      (if it exists).
+
+   In the implementation, not the edges are colored, but the nodes.  The color
+   interpreted as the color of the edge leading to this node.  The color is
+   meaningless for the root node, but we color the root node black for
+   convenience.  All added nodes are red initially.
+
+   Adding to a red/black tree is rather easy.  The right place is searched
+   with a usual binary tree search.  Additionally, whenever a node N is
+   reached that has two red successors, the successors are colored black and
+   the node itself colored red.  This moves red edges up the tree where they
+   pose less of a problem once we get to really insert the new node.  Changing
+   N's color to red may violate rule 2, however, so rotations may become
+   necessary to restore the invariants.  Adding a new red leaf may violate
+   the same rule, so afterwards an additional check is run and the tree
+   possibly rotated.
+
+   Deleting is hairy.  There are mainly two nodes involved: the node to be
+   deleted (n1), and another node that is to be unchained from the tree (n2).
+   If n1 has a successor (the node with a smallest key that is larger than
+   n1), then the successor becomes n2 and its contents are copied into n1,
+   otherwise n1 becomes n2.
+   Unchaining a node may violate rule 1: if n2 is black, one subtree is
+   missing one black edge afterwards.  The algorithm must try to move this
+   error upwards towards the root, so that the subtree that does not have
+   enough black edges becomes the whole tree.  Once that happens, the error
+   has disappeared.  It may not be necessary to go all the way up, since it
+   is possible that rotations and recoloring can fix the error before that.
+
+   Although the deletion algorithm must walk upwards through the tree, we
+   do not store parent pointers in the nodes.  Instead, delete allocates a
+   small array of parent pointers and fills it while descending the tree.
+   Since we know that the length of a path is O(log n), where n is the number
+   of nodes, this is likely to use less memory.  */
+
+/* Tree rotations look like this:
+      A                C
+     / \              / \
+    B   C            A   G
+   / \ / \  -->     / \
+   D E F G         B   F
+                  / \
+                 D   E
+
+   In this case, A has been rotated left.  This preserves the ordering of the
+   binary tree.  */
+
+#include <config.h>
+
+/* Specification.  */
+#ifdef IN_LIBINTL
+# include "tsearch.h"
+#else
+# include <search.h>
+#endif
+
+#include <stdlib.h>
+
+typedef int (*__compar_fn_t) (const void *, const void *);
+typedef void (*__action_fn_t) (const void *, VISIT, int);
+
+#ifndef weak_alias
+# define __tsearch tsearch
+# define __tfind tfind
+# define __tdelete tdelete
+# define __twalk twalk
+#endif
+
+#ifndef internal_function
+/* Inside GNU libc we mark some function in a special way.  In other
+   environments simply ignore the marking.  */
+# define internal_function
+#endif
+
+typedef struct node_t
+{
+  /* Callers expect this to be the first element in the structure - do not
+     move!  */
+  const void *key;
+  struct node_t *left;
+  struct node_t *right;
+  unsigned int red:1;
+} *node;
+typedef const struct node_t *const_node;
+
+#undef DEBUGGING
+
+#ifdef DEBUGGING
+
+/* Routines to check tree invariants.  */
+
+#include <assert.h>
+
+#define CHECK_TREE(a) check_tree(a)
+
+static void
+check_tree_recurse (node p, int d_sofar, int d_total)
+{
+  if (p == NULL)
+    {
+      assert (d_sofar == d_total);
+      return;
+    }
+
+  check_tree_recurse (p->left, d_sofar + (p->left && !p->left->red), d_total);
+  check_tree_recurse (p->right, d_sofar + (p->right && !p->right->red), d_total);
+  if (p->left)
+    assert (!(p->left->red && p->red));
+  if (p->right)
+    assert (!(p->right->red && p->red));
+}
+
+static void
+check_tree (node root)
+{
+  int cnt = 0;
+  node p;
+  if (root == NULL)
+    return;
+  root->red = 0;
+  for(p = root->left; p; p = p->left)
+    cnt += !p->red;
+  check_tree_recurse (root, 0, cnt);
+}
+
+
+#else
+
+#define CHECK_TREE(a)
+
+#endif
+
+/* Possibly "split" a node with two red successors, and/or fix up two red
+   edges in a row.  ROOTP is a pointer to the lowest node we visited, PARENTP
+   and GPARENTP pointers to its parent/grandparent.  P_R and GP_R contain the
+   comparison values that determined which way was taken in the tree to reach
+   ROOTP.  MODE is 1 if we need not do the split, but must check for two red
+   edges between GPARENTP and ROOTP.  */
+static void
+maybe_split_for_insert (node *rootp, node *parentp, node *gparentp,
+                        int p_r, int gp_r, int mode)
+{
+  node root = *rootp;
+  node *rp, *lp;
+  rp = &(*rootp)->right;
+  lp = &(*rootp)->left;
+
+  /* See if we have to split this node (both successors red).  */
+  if (mode == 1
+      || ((*rp) != NULL && (*lp) != NULL && (*rp)->red && (*lp)->red))
+    {
+      /* This node becomes red, its successors black.  */
+      root->red = 1;
+      if (*rp)
+        (*rp)->red = 0;
+      if (*lp)
+        (*lp)->red = 0;
+
+      /* If the parent of this node is also red, we have to do
+         rotations.  */
+      if (parentp != NULL && (*parentp)->red)
+        {
+          node gp = *gparentp;
+          node p = *parentp;
+          /* There are two main cases:
+             1. The edge types (left or right) of the two red edges differ.
+             2. Both red edges are of the same type.
+             There exist two symmetries of each case, so there is a total of
+             4 cases.  */
+          if ((p_r > 0) != (gp_r > 0))
+            {
+              /* Put the child at the top of the tree, with its parent
+                 and grandparent as successors.  */
+              p->red = 1;
+              gp->red = 1;
+              root->red = 0;
+              if (p_r < 0)
+                {
+                  /* Child is left of parent.  */
+                  p->left = *rp;
+                  *rp = p;
+                  gp->right = *lp;
+                  *lp = gp;
+                }
+              else
+                {
+                  /* Child is right of parent.  */
+                  p->right = *lp;
+                  *lp = p;
+                  gp->left = *rp;
+                  *rp = gp;
+                }
+              *gparentp = root;
+            }
+          else
+            {
+              *gparentp = *parentp;
+              /* Parent becomes the top of the tree, grandparent and
+                 child are its successors.  */
+              p->red = 0;
+              gp->red = 1;
+              if (p_r < 0)
+                {
+                  /* Left edges.  */
+                  gp->left = p->right;
+                  p->right = gp;
+                }
+              else
+                {
+                  /* Right edges.  */
+                  gp->right = p->left;
+                  p->left = gp;
+                }
+            }
+        }
+    }
+}
+
+/* Find or insert datum into search tree.
+   KEY is the key to be located, ROOTP is the address of tree root,
+   COMPAR the ordering function.  */
+void *
+__tsearch (const void *key, void **vrootp, __compar_fn_t compar)
+{
+  node q;
+  node *parentp = NULL, *gparentp = NULL;
+  node *rootp = (node *) vrootp;
+  node *nextp;
+  int r = 0, p_r = 0, gp_r = 0; /* No they might not, Mr Compiler.  */
+
+  if (rootp == NULL)
+    return NULL;
+
+  /* This saves some additional tests below.  */
+  if (*rootp != NULL)
+    (*rootp)->red = 0;
+
+  CHECK_TREE (*rootp);
+
+  nextp = rootp;
+  while (*nextp != NULL)
+    {
+      node root = *rootp;
+      r = (*compar) (key, root->key);
+      if (r == 0)
+        return root;
+
+      maybe_split_for_insert (rootp, parentp, gparentp, p_r, gp_r, 0);
+      /* If that did any rotations, parentp and gparentp are now garbage.
+         That doesn't matter, because the values they contain are never
+         used again in that case.  */
+
+      nextp = r < 0 ? &root->left : &root->right;
+      if (*nextp == NULL)
+        break;
+
+      gparentp = parentp;
+      parentp = rootp;
+      rootp = nextp;
+
+      gp_r = p_r;
+      p_r = r;
+    }
+
+  q = (struct node_t *) malloc (sizeof (struct node_t));
+  if (q != NULL)
+    {
+      *nextp = q;                       /* link new node to old */
+      q->key = key;                     /* initialize new node */
+      q->red = 1;
+      q->left = q->right = NULL;
+
+      if (nextp != rootp)
+        /* There may be two red edges in a row now, which we must avoid by
+           rotating the tree.  */
+        maybe_split_for_insert (nextp, rootp, parentp, r, p_r, 1);
+    }
+
+  return q;
+}
+#ifdef weak_alias
+weak_alias (__tsearch, tsearch)
+#endif
+
+
+/* Find datum in search tree.
+   KEY is the key to be located, ROOTP is the address of tree root,
+   COMPAR the ordering function.  */
+void *
+__tfind (key, vrootp, compar)
+     const void *key;
+     void *const *vrootp;
+     __compar_fn_t compar;
+{
+  node *rootp = (node *) vrootp;
+
+  if (rootp == NULL)
+    return NULL;
+
+  CHECK_TREE (*rootp);
+
+  while (*rootp != NULL)
+    {
+      node root = *rootp;
+      int r;
+
+      r = (*compar) (key, root->key);
+      if (r == 0)
+        return root;
+
+      rootp = r < 0 ? &root->left : &root->right;
+    }
+  return NULL;
+}
+#ifdef weak_alias
+weak_alias (__tfind, tfind)
+#endif
+
+
+/* Delete node with given key.
+   KEY is the key to be deleted, ROOTP is the address of the root of tree,
+   COMPAR the comparison function.  */
+void *
+__tdelete (const void *key, void **vrootp, __compar_fn_t compar)
+{
+  node p, q, r, retval;
+  int cmp;
+  node *rootp = (node *) vrootp;
+  node root, unchained;
+  /* Stack of nodes so we remember the parents without recursion.  It's
+     _very_ unlikely that there are paths longer than 40 nodes.  The tree
+     would need to have around 250.000 nodes.  */
+  int stacksize = 100;
+  int sp = 0;
+  node *nodestack[100];
+
+  if (rootp == NULL)
+    return NULL;
+  p = *rootp;
+  if (p == NULL)
+    return NULL;
+
+  CHECK_TREE (p);
+
+  while ((cmp = (*compar) (key, (*rootp)->key)) != 0)
+    {
+      if (sp == stacksize)
+        abort ();
+
+      nodestack[sp++] = rootp;
+      p = *rootp;
+      rootp = ((cmp < 0)
+               ? &(*rootp)->left
+               : &(*rootp)->right);
+      if (*rootp == NULL)
+        return NULL;
+    }
+
+  /* This is bogus if the node to be deleted is the root... this routine
+     really should return an integer with 0 for success, -1 for failure
+     and errno = ESRCH or something.  */
+  retval = p;
+
+  /* We don't unchain the node we want to delete. Instead, we overwrite
+     it with its successor and unchain the successor.  If there is no
+     successor, we really unchain the node to be deleted.  */
+
+  root = *rootp;
+
+  r = root->right;
+  q = root->left;
+
+  if (q == NULL || r == NULL)
+    unchained = root;
+  else
+    {
+      node *parent = rootp, *up = &root->right;
+      for (;;)
+        {
+          if (sp == stacksize)
+            abort ();
+          nodestack[sp++] = parent;
+          parent = up;
+          if ((*up)->left == NULL)
+            break;
+          up = &(*up)->left;
+        }
+      unchained = *up;
+    }
+
+  /* We know that either the left or right successor of UNCHAINED is NULL.
+     R becomes the other one, it is chained into the parent of UNCHAINED.  */
+  r = unchained->left;
+  if (r == NULL)
+    r = unchained->right;
+  if (sp == 0)
+    *rootp = r;
+  else
+    {
+      q = *nodestack[sp-1];
+      if (unchained == q->right)
+        q->right = r;
+      else
+        q->left = r;
+    }
+
+  if (unchained != root)
+    root->key = unchained->key;
+  if (!unchained->red)
+    {
+      /* Now we lost a black edge, which means that the number of black
+         edges on every path is no longer constant.  We must balance the
+         tree.  */
+      /* NODESTACK now contains all parents of R.  R is likely to be NULL
+         in the first iteration.  */
+      /* NULL nodes are considered black throughout - this is necessary for
+         correctness.  */
+      while (sp > 0 && (r == NULL || !r->red))
+        {
+          node *pp = nodestack[sp - 1];
+          p = *pp;
+          /* Two symmetric cases.  */
+          if (r == p->left)
+            {
+              /* Q is R's brother, P is R's parent.  The subtree with root
+                 R has one black edge less than the subtree with root Q.  */
+              q = p->right;
+              if (q->red)
+                {
+                  /* If Q is red, we know that P is black. We rotate P left
+                     so that Q becomes the top node in the tree, with P below
+                     it.  P is colored red, Q is colored black.
+                     This action does not change the black edge count for any
+                     leaf in the tree, but we will be able to recognize one
+                     of the following situations, which all require that Q
+                     is black.  */
+                  q->red = 0;
+                  p->red = 1;
+                  /* Left rotate p.  */
+                  p->right = q->left;
+                  q->left = p;
+                  *pp = q;
+                  /* Make sure pp is right if the case below tries to use
+                     it.  */
+                  nodestack[sp++] = pp = &q->left;
+                  q = p->right;
+                }
+              /* We know that Q can't be NULL here.  We also know that Q is
+                 black.  */
+              if ((q->left == NULL || !q->left->red)
+                  && (q->right == NULL || !q->right->red))
+                {
+                  /* Q has two black successors.  We can simply color Q red.
+                     The whole subtree with root P is now missing one black
+                     edge.  Note that this action can temporarily make the
+                     tree invalid (if P is red).  But we will exit the loop
+                     in that case and set P black, which both makes the tree
+                     valid and also makes the black edge count come out
+                     right.  If P is black, we are at least one step closer
+                     to the root and we'll try again the next iteration.  */
+                  q->red = 1;
+                  r = p;
+                }
+              else
+                {
+                  /* Q is black, one of Q's successors is red.  We can
+                     repair the tree with one operation and will exit the
+                     loop afterwards.  */
+                  if (q->right == NULL || !q->right->red)
+                    {
+                      /* The left one is red.  We perform the same action as
+                         in maybe_split_for_insert where two red edges are
+                         adjacent but point in different directions:
+                         Q's left successor (let's call it Q2) becomes the
+                         top of the subtree we are looking at, its parent (Q)
+                         and grandparent (P) become its successors. The former
+                         successors of Q2 are placed below P and Q.
+                         P becomes black, and Q2 gets the color that P had.
+                         This changes the black edge count only for node R and
+                         its successors.  */
+                      node q2 = q->left;
+                      q2->red = p->red;
+                      p->right = q2->left;
+                      q->left = q2->right;
+                      q2->right = q;
+                      q2->left = p;
+                      *pp = q2;
+                      p->red = 0;
+                    }
+                  else
+                    {
+                      /* It's the right one.  Rotate P left. P becomes black,
+                         and Q gets the color that P had.  Q's right successor
+                         also becomes black.  This changes the black edge
+                         count only for node R and its successors.  */
+                      q->red = p->red;
+                      p->red = 0;
+
+                      q->right->red = 0;
+
+                      /* left rotate p */
+                      p->right = q->left;
+                      q->left = p;
+                      *pp = q;
+                    }
+
+                  /* We're done.  */
+                  sp = 1;
+                  r = NULL;
+                }
+            }
+          else
+            {
+              /* Comments: see above.  */
+              q = p->left;
+              if (q->red)
+                {
+                  q->red = 0;
+                  p->red = 1;
+                  p->left = q->right;
+                  q->right = p;
+                  *pp = q;
+                  nodestack[sp++] = pp = &q->right;
+                  q = p->left;
+                }
+              if ((q->right == NULL || !q->right->red)
+                       && (q->left == NULL || !q->left->red))
+                {
+                  q->red = 1;
+                  r = p;
+                }
+              else
+                {
+                  if (q->left == NULL || !q->left->red)
+                    {
+                      node q2 = q->right;
+                      q2->red = p->red;
+                      p->left = q2->right;
+                      q->right = q2->left;
+                      q2->left = q;
+                      q2->right = p;
+                      *pp = q2;
+                      p->red = 0;
+                    }
+                  else
+                    {
+                      q->red = p->red;
+                      p->red = 0;
+                      q->left->red = 0;
+                      p->left = q->right;
+                      q->right = p;
+                      *pp = q;
+                    }
+                  sp = 1;
+                  r = NULL;
+                }
+            }
+          --sp;
+        }
+      if (r != NULL)
+        r->red = 0;
+    }
+
+  free (unchained);
+  return retval;
+}
+#ifdef weak_alias
+weak_alias (__tdelete, tdelete)
+#endif
+
+
+/* Walk the nodes of a tree.
+   ROOT is the root of the tree to be walked, ACTION the function to be
+   called at each node.  LEVEL is the level of ROOT in the whole tree.  */
+static void
+internal_function
+trecurse (const void *vroot, __action_fn_t action, int level)
+{
+  const_node root = (const_node) vroot;
+
+  if (root->left == NULL && root->right == NULL)
+    (*action) (root, leaf, level);
+  else
+    {
+      (*action) (root, preorder, level);
+      if (root->left != NULL)
+        trecurse (root->left, action, level + 1);
+      (*action) (root, postorder, level);
+      if (root->right != NULL)
+        trecurse (root->right, action, level + 1);
+      (*action) (root, endorder, level);
+    }
+}
+
+
+/* Walk the nodes of a tree.
+   ROOT is the root of the tree to be walked, ACTION the function to be
+   called at each node.  */
+void
+__twalk (const void *vroot, __action_fn_t action)
+{
+  const_node root = (const_node) vroot;
+
+  CHECK_TREE (root);
+
+  if (root != NULL && action != NULL)
+    trecurse (root, action, 0);
+}
+#ifdef weak_alias
+weak_alias (__twalk, twalk)
+#endif
+
+
+#ifdef _LIBC
+
+/* The standardized functions miss an important functionality: the
+   tree cannot be removed easily.  We provide a function to do this.  */
+static void
+internal_function
+tdestroy_recurse (node root, __free_fn_t freefct)
+{
+  if (root->left != NULL)
+    tdestroy_recurse (root->left, freefct);
+  if (root->right != NULL)
+    tdestroy_recurse (root->right, freefct);
+  (*freefct) ((void *) root->key);
+  /* Free the node itself.  */
+  free (root);
+}
+
+void
+__tdestroy (void *vroot, __free_fn_t freefct)
+{
+  node root = (node) vroot;
+
+  CHECK_TREE (root);
+
+  if (root != NULL)
+    tdestroy_recurse (root, freefct);
+}
+weak_alias (__tdestroy, tdestroy)
+
+#endif /* _LIBC */
diff --git a/lib/intl/tsearch.h b/lib/intl/tsearch.h
new file mode 100644 (file)
index 0000000..e3a5ca4
--- /dev/null
@@ -0,0 +1,81 @@
+/* Binary tree data structure.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _TSEARCH_H
+#define _TSEARCH_H
+
+#if HAVE_TSEARCH
+
+/* Get tseach(), tfind(), tdelete(), twalk() declarations.  */
+#include <search.h>
+
+#else
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* See <http://www.opengroup.org/susv3xbd/search.h.html>,
+       <http://www.opengroup.org/susv3xsh/tsearch.html>
+   for details.  */
+
+typedef enum
+{ 
+  preorder,
+  postorder, 
+  endorder,
+  leaf
+}
+VISIT;
+
+/* Searches an element in the tree *VROOTP that compares equal to KEY.
+   If one is found, it is returned.  Otherwise, a new element equal to KEY
+   is inserted in the tree and is returned.  */
+extern void * tsearch (const void *key, void **vrootp,
+                       int (*compar) (const void *, const void *));
+
+/* Searches an element in the tree *VROOTP that compares equal to KEY.
+   If one is found, it is returned.  Otherwise, NULL is returned.  */
+extern void * tfind (const void *key, void *const *vrootp,
+                     int (*compar) (const void *, const void *));
+
+/* Searches an element in the tree *VROOTP that compares equal to KEY.
+   If one is found, it is removed from the tree, and its parent node is
+   returned.  Otherwise, NULL is returned.  */
+extern void * tdelete (const void *key, void **vrootp,
+                       int (*compar) (const void *, const void *));
+
+/* Perform a depth-first, left-to-right traversal of the tree VROOT.
+   The ACTION function is called:
+     - for non-leaf nodes: 3 times, before the left subtree traversal,
+       after the left subtree traversal but before the right subtree traversal,
+       and after the right subtree traversal,
+     - for leaf nodes: once.
+   The arguments passed to ACTION are:
+     1. the node; it can be casted to a 'const void * const *', i.e. into a
+        pointer to the key,
+     2. an indicator which visit of the node this is,
+     3. the level of the node in the tree (0 for the root).  */
+extern void twalk (const void *vroot,
+                   void (*action) (const void *, VISIT, int));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif /* _TSEARCH_H */
diff --git a/lib/intl/vasnprintf.c b/lib/intl/vasnprintf.c
new file mode 100644 (file)
index 0000000..5a7bf81
--- /dev/null
@@ -0,0 +1,5874 @@
+/* vsprintf with automatic memory allocation.
+   Copyright (C) 1999, 2002-2022 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This file can be parametrized with the following macros:
+     VASNPRINTF         The name of the function being defined.
+     FCHAR_T            The element type of the format string.
+     DCHAR_T            The element type of the destination (result) string.
+     FCHAR_T_ONLY_ASCII Set to 1 to enable verification that all characters
+                        in the format string are ASCII. MUST be set if
+                        FCHAR_T and DCHAR_T are not the same type.
+     DIRECTIVE          Structure denoting a format directive.
+                        Depends on FCHAR_T.
+     DIRECTIVES         Structure denoting the set of format directives of a
+                        format string.  Depends on FCHAR_T.
+     PRINTF_PARSE       Function that parses a format string.
+                        Depends on FCHAR_T.
+     DCHAR_CPY          memcpy like function for DCHAR_T[] arrays.
+     DCHAR_SET          memset like function for DCHAR_T[] arrays.
+     DCHAR_MBSNLEN      mbsnlen like function for DCHAR_T[] arrays.
+     SNPRINTF           The system's snprintf (or similar) function.
+                        This may be either snprintf or swprintf.
+     TCHAR_T            The element type of the argument and result string
+                        of the said SNPRINTF function.  This may be either
+                        char or wchar_t.  The code exploits that
+                        sizeof (TCHAR_T) | sizeof (DCHAR_T) and
+                        alignof (TCHAR_T) <= alignof (DCHAR_T).
+     DCHAR_IS_TCHAR     Set to 1 if DCHAR_T and TCHAR_T are the same type.
+     DCHAR_CONV_FROM_ENCODING A function to convert from char[] to DCHAR[].
+     DCHAR_IS_UINT8_T   Set to 1 if DCHAR_T is uint8_t.
+     DCHAR_IS_UINT16_T  Set to 1 if DCHAR_T is uint16_t.
+     DCHAR_IS_UINT32_T  Set to 1 if DCHAR_T is uint32_t.
+     ENABLE_UNISTDIO    Set to 1 to enable the unistdio extensions.
+     ENABLE_WCHAR_FALLBACK  Set to 1 to avoid EILSEQ during conversion of wide
+                        characters (wchar_t) and wide character strings
+                        (wchar_t[]) to multibyte sequences.  The fallback is the
+                        hexadecimal escape syntax (\unnnn or \Unnnnnnnn) or,
+                        if wchar_t is not Unicode encoded, \wnnnn or \Wnnnnnnnn.
+ */
+
+/* Tell glibc's <stdio.h> to provide a prototype for snprintf().
+   This must come before <config.h> because <config.h> may include
+   <features.h>, and once <features.h> has been included, it's too late.  */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE    1
+#endif
+
+#ifndef VASNPRINTF
+# include <config.h>
+#endif
+
+/* As of GCC 11.2.1, gcc -Wanalyzer-too-complex reports that main's
+   use of CHECK macros expands to code that is too complicated for gcc
+   -fanalyzer.  Suppress the resulting bogus warnings.  */
+#if 10 <= __GNUC__
+# pragma GCC diagnostic ignored "-Wanalyzer-null-argument"
+#endif
+
+#ifndef IN_LIBINTL
+# include <alloca.h>
+#endif
+
+/* Specification.  */
+#ifndef VASNPRINTF
+# if WIDE_CHAR_VERSION
+#  include "vasnwprintf.h"
+# else
+#  include "vasnprintf.h"
+# endif
+#endif
+
+#include <locale.h>     /* localeconv() */
+#include <stdio.h>      /* snprintf(), sprintf() */
+#include <stdlib.h>     /* abort(), malloc(), realloc(), free() */
+#include <string.h>     /* memcpy(), strlen() */
+#include <errno.h>      /* errno */
+#include <limits.h>     /* CHAR_BIT */
+#include <float.h>      /* DBL_MAX_EXP, LDBL_MAX_EXP */
+#if HAVE_NL_LANGINFO
+# include <langinfo.h>
+#endif
+#ifndef VASNPRINTF
+# if WIDE_CHAR_VERSION
+#  include "wprintf-parse.h"
+# else
+#  include "printf-parse.h"
+# endif
+#endif
+
+/* Checked size_t computations.  */
+#include "xsize.h"
+
+#include "attribute.h"
+#include "verify.h"
+
+#if (NEED_PRINTF_DOUBLE || NEED_PRINTF_LONG_DOUBLE) && !defined IN_LIBINTL
+# include <math.h>
+# include "float+.h"
+#endif
+
+#if (NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE) && !defined IN_LIBINTL
+# include <math.h>
+# include "isnand-nolibm.h"
+#endif
+
+#if (NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE) && !defined IN_LIBINTL
+# include <math.h>
+# include "isnanl-nolibm.h"
+# include "fpucw.h"
+#endif
+
+#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_DOUBLE) && !defined IN_LIBINTL
+# include <math.h>
+# include "isnand-nolibm.h"
+# include "printf-frexp.h"
+#endif
+
+#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE) && !defined IN_LIBINTL
+# include <math.h>
+# include "isnanl-nolibm.h"
+# include "printf-frexpl.h"
+# include "fpucw.h"
+#endif
+
+/* Default parameters.  */
+#ifndef VASNPRINTF
+# if WIDE_CHAR_VERSION
+#  define VASNPRINTF vasnwprintf
+#  define FCHAR_T wchar_t
+#  define DCHAR_T wchar_t
+#  define TCHAR_T wchar_t
+#  define DCHAR_IS_TCHAR 1
+#  define DIRECTIVE wchar_t_directive
+#  define DIRECTIVES wchar_t_directives
+#  define PRINTF_PARSE wprintf_parse
+#  define DCHAR_CPY wmemcpy
+#  define DCHAR_SET wmemset
+# else
+#  define VASNPRINTF vasnprintf
+#  define FCHAR_T char
+#  define DCHAR_T char
+#  define TCHAR_T char
+#  define DCHAR_IS_TCHAR 1
+#  define DIRECTIVE char_directive
+#  define DIRECTIVES char_directives
+#  define PRINTF_PARSE printf_parse
+#  define DCHAR_CPY memcpy
+#  define DCHAR_SET memset
+# endif
+#endif
+#if WIDE_CHAR_VERSION
+  /* TCHAR_T is wchar_t.  */
+# define USE_SNPRINTF 1
+# if HAVE_DECL__SNWPRINTF
+   /* On Windows, the function swprintf() has a different signature than
+      on Unix; we use the function _snwprintf() or - on mingw - snwprintf()
+      instead.  The mingw function snwprintf() has fewer bugs than the
+      MSVCRT function _snwprintf(), so prefer that.  */
+#  if defined __MINGW32__
+#   define SNPRINTF snwprintf
+#  else
+#   define SNPRINTF _snwprintf
+#   define USE_MSVC__SNPRINTF 1
+#  endif
+# else
+   /* Unix.  */
+#  define SNPRINTF swprintf
+# endif
+#else
+  /* TCHAR_T is char.  */
+  /* Use snprintf if it exists under the name 'snprintf' or '_snprintf'.
+     But don't use it on BeOS, since BeOS snprintf produces no output if the
+     size argument is >= 0x3000000.
+     Also don't use it on Linux libc5, since there snprintf with size = 1
+     writes any output without bounds, like sprintf.  */
+# if (HAVE_DECL__SNPRINTF || HAVE_SNPRINTF) && !defined __BEOS__ && !(__GNU_LIBRARY__ == 1)
+#  define USE_SNPRINTF 1
+# else
+#  define USE_SNPRINTF 0
+# endif
+# if HAVE_DECL__SNPRINTF
+   /* Windows.  The mingw function snprintf() has fewer bugs than the MSVCRT
+      function _snprintf(), so prefer that.  */
+#  if defined __MINGW32__
+#   define SNPRINTF snprintf
+    /* Here we need to call the native snprintf, not rpl_snprintf.  */
+#   undef snprintf
+#  else
+    /* MSVC versions < 14 did not have snprintf, only _snprintf.  */
+#   define SNPRINTF _snprintf
+#   define USE_MSVC__SNPRINTF 1
+#  endif
+# else
+   /* Unix.  */
+#  define SNPRINTF snprintf
+   /* Here we need to call the native snprintf, not rpl_snprintf.  */
+#  undef snprintf
+# endif
+#endif
+/* Here we need to call the native sprintf, not rpl_sprintf.  */
+#undef sprintf
+
+/* GCC >= 4.0 with -Wall emits unjustified "... may be used uninitialized"
+   warnings in this file.  Use -Dlint to suppress them.  */
+#if defined GCC_LINT || defined lint
+# define IF_LINT(Code) Code
+#else
+# define IF_LINT(Code) /* empty */
+#endif
+
+/* Avoid some warnings from "gcc -Wshadow".
+   This file doesn't use the exp() and remainder() functions.  */
+#undef exp
+#define exp expo
+#undef remainder
+#define remainder rem
+
+#if (!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF) && !WIDE_CHAR_VERSION
+# if (HAVE_STRNLEN && !defined _AIX)
+#  define local_strnlen strnlen
+# else
+#  ifndef local_strnlen_defined
+#   define local_strnlen_defined 1
+static size_t
+local_strnlen (const char *string, size_t maxlen)
+{
+  const char *end = memchr (string, '\0', maxlen);
+  return end ? (size_t) (end - string) : maxlen;
+}
+#  endif
+# endif
+#endif
+
+#if (((!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF) && WIDE_CHAR_VERSION) || ((!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (NEED_PRINTF_DIRECTIVE_LS && !defined IN_LIBINTL)) && !WIDE_CHAR_VERSION && DCHAR_IS_TCHAR)) && HAVE_WCHAR_T
+# if HAVE_WCSLEN
+#  define local_wcslen wcslen
+# else
+   /* Solaris 2.5.1 has wcslen() in a separate library libw.so. To avoid
+      a dependency towards this library, here is a local substitute.
+      Define this substitute only once, even if this file is included
+      twice in the same compilation unit.  */
+#  ifndef local_wcslen_defined
+#   define local_wcslen_defined 1
+static size_t
+local_wcslen (const wchar_t *s)
+{
+  const wchar_t *ptr;
+
+  for (ptr = s; *ptr != (wchar_t) 0; ptr++)
+    ;
+  return ptr - s;
+}
+#  endif
+# endif
+#endif
+
+#if (!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF) && HAVE_WCHAR_T && WIDE_CHAR_VERSION
+# if HAVE_WCSNLEN
+#  define local_wcsnlen wcsnlen
+# else
+#  ifndef local_wcsnlen_defined
+#   define local_wcsnlen_defined 1
+static size_t
+local_wcsnlen (const wchar_t *s, size_t maxlen)
+{
+  const wchar_t *ptr;
+
+  for (ptr = s; maxlen > 0 && *ptr != (wchar_t) 0; ptr++, maxlen--)
+    ;
+  return ptr - s;
+}
+#  endif
+# endif
+#endif
+
+#if (((!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (NEED_PRINTF_DIRECTIVE_LS && !defined IN_LIBINTL) || ENABLE_WCHAR_FALLBACK) && HAVE_WCHAR_T) || (ENABLE_WCHAR_FALLBACK && HAVE_WINT_T)) && !WIDE_CHAR_VERSION
+# if ENABLE_WCHAR_FALLBACK
+static size_t
+wctomb_fallback (char *s, wchar_t wc)
+{
+  static char const hex[16] = "0123456789ABCDEF";
+
+  s[0] = '\\';
+  if (sizeof (wchar_t) > 2 && wc > 0xffff)
+    {
+#  if __STDC_ISO_10646__ || (__GLIBC__ >= 2) || (defined _WIN32 || defined __CYGWIN__)
+      s[1] = 'U';
+#  else
+      s[1] = 'W';
+#  endif
+      s[2] = hex[(wc & 0xf0000000U) >> 28];
+      s[3] = hex[(wc & 0xf000000U) >> 24];
+      s[4] = hex[(wc & 0xf00000U) >> 20];
+      s[5] = hex[(wc & 0xf0000U) >> 16];
+      s[6] = hex[(wc & 0xf000U) >> 12];
+      s[7] = hex[(wc & 0xf00U) >> 8];
+      s[8] = hex[(wc & 0xf0U) >> 4];
+      s[9] = hex[wc & 0xfU];
+      return 10;
+    }
+  else
+    {
+#  if __STDC_ISO_10646__ || (__GLIBC__ >= 2) || (defined _WIN32 || defined __CYGWIN__)
+      s[1] = 'u';
+#  else
+      s[1] = 'w';
+#  endif
+      s[2] = hex[(wc & 0xf000U) >> 12];
+      s[3] = hex[(wc & 0xf00U) >> 8];
+      s[4] = hex[(wc & 0xf0U) >> 4];
+      s[5] = hex[wc & 0xfU];
+      return 6;
+    }
+}
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+static size_t
+local_wcrtomb (char *s, wchar_t wc, mbstate_t *ps)
+{
+  size_t count = wcrtomb (s, wc, ps);
+  if (count == (size_t)(-1))
+    count = wctomb_fallback (s, wc);
+  return count;
+}
+#  else
+static int
+local_wctomb (char *s, wchar_t wc)
+{
+  int count = wctomb (s, wc);
+  if (count < 0)
+    count = wctomb_fallback (s, wc);
+  return count;
+}
+#   define local_wcrtomb(S, WC, PS)  local_wctomb ((S), (WC))
+#  endif
+# else
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+#   define local_wcrtomb(S, WC, PS)  wcrtomb ((S), (WC), (PS))
+#  else
+#   define local_wcrtomb(S, WC, PS)  wctomb ((S), (WC))
+#  endif
+# endif
+#endif
+
+#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE || NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE) && !defined IN_LIBINTL
+/* Determine the decimal-point character according to the current locale.  */
+# ifndef decimal_point_char_defined
+#  define decimal_point_char_defined 1
+static char
+decimal_point_char (void)
+{
+  const char *point;
+  /* Determine it in a multithread-safe way.  We know nl_langinfo is
+     multithread-safe on glibc systems and Mac OS X systems, but is not required
+     to be multithread-safe by POSIX.  sprintf(), however, is multithread-safe.
+     localeconv() is rarely multithread-safe.  */
+#  if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
+  point = nl_langinfo (RADIXCHAR);
+#  elif 1
+  char pointbuf[5];
+  sprintf (pointbuf, "%#.0f", 1.0);
+  point = &pointbuf[1];
+#  else
+  point = localeconv () -> decimal_point;
+#  endif
+  /* The decimal point is always a single byte: either '.' or ','.  */
+  return (point[0] != '\0' ? point[0] : '.');
+}
+# endif
+#endif
+
+#if NEED_PRINTF_INFINITE_DOUBLE && !NEED_PRINTF_DOUBLE && !defined IN_LIBINTL
+
+/* Equivalent to !isfinite(x) || x == 0, but does not require libm.  */
+static int
+is_infinite_or_zero (double x)
+{
+  return isnand (x) || x + x == x;
+}
+
+#endif
+
+#if NEED_PRINTF_INFINITE_LONG_DOUBLE && !NEED_PRINTF_LONG_DOUBLE && !defined IN_LIBINTL
+
+/* Equivalent to !isfinite(x) || x == 0, but does not require libm.  */
+static int
+is_infinite_or_zerol (long double x)
+{
+  return isnanl (x) || x + x == x;
+}
+
+#endif
+
+#if (NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_DOUBLE) && !defined IN_LIBINTL
+
+/* Converting 'long double' to decimal without rare rounding bugs requires
+   real bignums.  We use the naming conventions of GNU gmp, but vastly simpler
+   (and slower) algorithms.  */
+
+typedef unsigned int mp_limb_t;
+# define GMP_LIMB_BITS 32
+verify (sizeof (mp_limb_t) * CHAR_BIT == GMP_LIMB_BITS);
+
+typedef unsigned long long mp_twolimb_t;
+# define GMP_TWOLIMB_BITS 64
+verify (sizeof (mp_twolimb_t) * CHAR_BIT == GMP_TWOLIMB_BITS);
+
+/* Representation of a bignum >= 0.  */
+typedef struct
+{
+  size_t nlimbs;
+  mp_limb_t *limbs; /* Bits in little-endian order, allocated with malloc().  */
+} mpn_t;
+
+/* Compute the product of two bignums >= 0.
+   Return the allocated memory in case of success, NULL in case of memory
+   allocation failure.  */
+static void *
+multiply (mpn_t src1, mpn_t src2, mpn_t *dest)
+{
+  const mp_limb_t *p1;
+  const mp_limb_t *p2;
+  size_t len1;
+  size_t len2;
+
+  if (src1.nlimbs <= src2.nlimbs)
+    {
+      len1 = src1.nlimbs;
+      p1 = src1.limbs;
+      len2 = src2.nlimbs;
+      p2 = src2.limbs;
+    }
+  else
+    {
+      len1 = src2.nlimbs;
+      p1 = src2.limbs;
+      len2 = src1.nlimbs;
+      p2 = src1.limbs;
+    }
+  /* Now 0 <= len1 <= len2.  */
+  if (len1 == 0)
+    {
+      /* src1 or src2 is zero.  */
+      dest->nlimbs = 0;
+      dest->limbs = (mp_limb_t *) malloc (1);
+    }
+  else
+    {
+      /* Here 1 <= len1 <= len2.  */
+      size_t dlen;
+      mp_limb_t *dp;
+      size_t k, i, j;
+
+      dlen = len1 + len2;
+      dp = (mp_limb_t *) malloc (dlen * sizeof (mp_limb_t));
+      if (dp == NULL)
+        return NULL;
+      for (k = len2; k > 0; )
+        dp[--k] = 0;
+      for (i = 0; i < len1; i++)
+        {
+          mp_limb_t digit1 = p1[i];
+          mp_twolimb_t carry = 0;
+          for (j = 0; j < len2; j++)
+            {
+              mp_limb_t digit2 = p2[j];
+              carry += (mp_twolimb_t) digit1 * (mp_twolimb_t) digit2;
+              carry += dp[i + j];
+              dp[i + j] = (mp_limb_t) carry;
+              carry = carry >> GMP_LIMB_BITS;
+            }
+          dp[i + len2] = (mp_limb_t) carry;
+        }
+      /* Normalise.  */
+      while (dlen > 0 && dp[dlen - 1] == 0)
+        dlen--;
+      dest->nlimbs = dlen;
+      dest->limbs = dp;
+    }
+  return dest->limbs;
+}
+
+/* Compute the quotient of a bignum a >= 0 and a bignum b > 0.
+   a is written as  a = q * b + r  with 0 <= r < b.  q is the quotient, r
+   the remainder.
+   Finally, round-to-even is performed: If r > b/2 or if r = b/2 and q is odd,
+   q is incremented.
+   Return the allocated memory in case of success, NULL in case of memory
+   allocation failure.  */
+static void *
+divide (mpn_t a, mpn_t b, mpn_t *q)
+{
+  /* Algorithm:
+     First normalise a and b: a=[a[m-1],...,a[0]], b=[b[n-1],...,b[0]]
+     with m>=0 and n>0 (in base beta = 2^GMP_LIMB_BITS).
+     If m<n, then q:=0 and r:=a.
+     If m>=n=1, perform a single-precision division:
+       r:=0, j:=m,
+       while j>0 do
+         {Here (q[m-1]*beta^(m-1)+...+q[j]*beta^j) * b[0] + r*beta^j =
+               = a[m-1]*beta^(m-1)+...+a[j]*beta^j und 0<=r<b[0]<beta}
+         j:=j-1, r:=r*beta+a[j], q[j]:=floor(r/b[0]), r:=r-b[0]*q[j].
+       Normalise [q[m-1],...,q[0]], yields q.
+     If m>=n>1, perform a multiple-precision division:
+       We have a/b < beta^(m-n+1).
+       s:=intDsize-1-(highest bit in b[n-1]), 0<=s<intDsize.
+       Shift a and b left by s bits, copying them. r:=a.
+       r=[r[m],...,r[0]], b=[b[n-1],...,b[0]] with b[n-1]>=beta/2.
+       For j=m-n,...,0: {Here 0 <= r < b*beta^(j+1).}
+         Compute q* :
+           q* := floor((r[j+n]*beta+r[j+n-1])/b[n-1]).
+           In case of overflow (q* >= beta) set q* := beta-1.
+           Compute c2 := ((r[j+n]*beta+r[j+n-1]) - q* * b[n-1])*beta + r[j+n-2]
+           and c3 := b[n-2] * q*.
+           {We have 0 <= c2 < 2*beta^2, even 0 <= c2 < beta^2 if no overflow
+            occurred.  Furthermore 0 <= c3 < beta^2.
+            If there was overflow and
+            r[j+n]*beta+r[j+n-1] - q* * b[n-1] >= beta, i.e. c2 >= beta^2,
+            the next test can be skipped.}
+           While c3 > c2, {Here 0 <= c2 < c3 < beta^2}
+             Put q* := q* - 1, c2 := c2 + b[n-1]*beta, c3 := c3 - b[n-2].
+           If q* > 0:
+             Put r := r - b * q* * beta^j. In detail:
+               [r[n+j],...,r[j]] := [r[n+j],...,r[j]] - q* * [b[n-1],...,b[0]].
+               hence: u:=0, for i:=0 to n-1 do
+                              u := u + q* * b[i],
+                              r[j+i]:=r[j+i]-(u mod beta) (+ beta, if carry),
+                              u:=u div beta (+ 1, if carry in subtraction)
+                      r[n+j]:=r[n+j]-u.
+               {Since always u = (q* * [b[i-1],...,b[0]] div beta^i) + 1
+                               < q* + 1 <= beta,
+                the carry u does not overflow.}
+             If a negative carry occurs, put q* := q* - 1
+               and [r[n+j],...,r[j]] := [r[n+j],...,r[j]] + [0,b[n-1],...,b[0]].
+         Set q[j] := q*.
+       Normalise [q[m-n],..,q[0]]; this yields the quotient q.
+       Shift [r[n-1],...,r[0]] right by s bits and normalise; this yields the
+       rest r.
+       The room for q[j] can be allocated at the memory location of r[n+j].
+     Finally, round-to-even:
+       Shift r left by 1 bit.
+       If r > b or if r = b and q[0] is odd, q := q+1.
+   */
+  const mp_limb_t *a_ptr = a.limbs;
+  size_t a_len = a.nlimbs;
+  const mp_limb_t *b_ptr = b.limbs;
+  size_t b_len = b.nlimbs;
+  mp_limb_t *roomptr;
+  mp_limb_t *tmp_roomptr = NULL;
+  mp_limb_t *q_ptr;
+  size_t q_len;
+  mp_limb_t *r_ptr;
+  size_t r_len;
+
+  /* Allocate room for a_len+2 digits.
+     (Need a_len+1 digits for the real division and 1 more digit for the
+     final rounding of q.)  */
+  roomptr = (mp_limb_t *) malloc ((a_len + 2) * sizeof (mp_limb_t));
+  if (roomptr == NULL)
+    return NULL;
+
+  /* Normalise a.  */
+  while (a_len > 0 && a_ptr[a_len - 1] == 0)
+    a_len--;
+
+  /* Normalise b.  */
+  for (;;)
+    {
+      if (b_len == 0)
+        /* Division by zero.  */
+        abort ();
+      if (b_ptr[b_len - 1] == 0)
+        b_len--;
+      else
+        break;
+    }
+
+  /* Here m = a_len >= 0 and n = b_len > 0.  */
+
+  if (a_len < b_len)
+    {
+      /* m<n: trivial case.  q=0, r := copy of a.  */
+      r_ptr = roomptr;
+      r_len = a_len;
+      memcpy (r_ptr, a_ptr, a_len * sizeof (mp_limb_t));
+      q_ptr = roomptr + a_len;
+      q_len = 0;
+    }
+  else if (b_len == 1)
+    {
+      /* n=1: single precision division.
+         beta^(m-1) <= a < beta^m  ==>  beta^(m-2) <= a/b < beta^m  */
+      r_ptr = roomptr;
+      q_ptr = roomptr + 1;
+      {
+        mp_limb_t den = b_ptr[0];
+        mp_limb_t remainder = 0;
+        const mp_limb_t *sourceptr = a_ptr + a_len;
+        mp_limb_t *destptr = q_ptr + a_len;
+        size_t count;
+        for (count = a_len; count > 0; count--)
+          {
+            mp_twolimb_t num =
+              ((mp_twolimb_t) remainder << GMP_LIMB_BITS) | *--sourceptr;
+            *--destptr = num / den;
+            remainder = num % den;
+          }
+        /* Normalise and store r.  */
+        if (remainder > 0)
+          {
+            r_ptr[0] = remainder;
+            r_len = 1;
+          }
+        else
+          r_len = 0;
+        /* Normalise q.  */
+        q_len = a_len;
+        if (q_ptr[q_len - 1] == 0)
+          q_len--;
+      }
+    }
+  else
+    {
+      /* n>1: multiple precision division.
+         beta^(m-1) <= a < beta^m, beta^(n-1) <= b < beta^n  ==>
+         beta^(m-n-1) <= a/b < beta^(m-n+1).  */
+      /* Determine s.  */
+      size_t s;
+      {
+        mp_limb_t msd = b_ptr[b_len - 1]; /* = b[n-1], > 0 */
+        /* Determine s = GMP_LIMB_BITS - integer_length (msd).
+           Code copied from gnulib's integer_length.c.  */
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) \
+     || (__clang_major__ >= 4)
+        s = __builtin_clz (msd);
+# else
+#  if defined DBL_EXPBIT0_WORD && defined DBL_EXPBIT0_BIT
+        if (GMP_LIMB_BITS <= DBL_MANT_BIT)
+          {
+            /* Use 'double' operations.
+               Assumes an IEEE 754 'double' implementation.  */
+#   define DBL_EXP_MASK ((DBL_MAX_EXP - DBL_MIN_EXP) | 7)
+#   define DBL_EXP_BIAS (DBL_EXP_MASK / 2 - 1)
+#   define NWORDS \
+     ((sizeof (double) + sizeof (unsigned int) - 1) / sizeof (unsigned int))
+            union { double value; unsigned int word[NWORDS]; } m;
+
+            /* Use a single integer to floating-point conversion.  */
+            m.value = msd;
+
+            s = GMP_LIMB_BITS
+                - (((m.word[DBL_EXPBIT0_WORD] >> DBL_EXPBIT0_BIT) & DBL_EXP_MASK)
+                   - DBL_EXP_BIAS);
+          }
+        else
+#   undef NWORDS
+#  endif
+          {
+            s = 31;
+            if (msd >= 0x10000)
+              {
+                msd = msd >> 16;
+                s -= 16;
+              }
+            if (msd >= 0x100)
+              {
+                msd = msd >> 8;
+                s -= 8;
+              }
+            if (msd >= 0x10)
+              {
+                msd = msd >> 4;
+                s -= 4;
+              }
+            if (msd >= 0x4)
+              {
+                msd = msd >> 2;
+                s -= 2;
+              }
+            if (msd >= 0x2)
+              {
+                msd = msd >> 1;
+                s -= 1;
+              }
+          }
+# endif
+      }
+      /* 0 <= s < GMP_LIMB_BITS.
+         Copy b, shifting it left by s bits.  */
+      if (s > 0)
+        {
+          tmp_roomptr = (mp_limb_t *) malloc (b_len * sizeof (mp_limb_t));
+          if (tmp_roomptr == NULL)
+            {
+              free (roomptr);
+              return NULL;
+            }
+          {
+            const mp_limb_t *sourceptr = b_ptr;
+            mp_limb_t *destptr = tmp_roomptr;
+            mp_twolimb_t accu = 0;
+            size_t count;
+            for (count = b_len; count > 0; count--)
+              {
+                accu += (mp_twolimb_t) *sourceptr++ << s;
+                *destptr++ = (mp_limb_t) accu;
+                accu = accu >> GMP_LIMB_BITS;
+              }
+            /* accu must be zero, since that was how s was determined.  */
+            if (accu != 0)
+              abort ();
+          }
+          b_ptr = tmp_roomptr;
+        }
+      /* Copy a, shifting it left by s bits, yields r.
+         Memory layout:
+         At the beginning: r = roomptr[0..a_len],
+         at the end: r = roomptr[0..b_len-1], q = roomptr[b_len..a_len]  */
+      r_ptr = roomptr;
+      if (s == 0)
+        {
+          memcpy (r_ptr, a_ptr, a_len * sizeof (mp_limb_t));
+          r_ptr[a_len] = 0;
+        }
+      else
+        {
+          const mp_limb_t *sourceptr = a_ptr;
+          mp_limb_t *destptr = r_ptr;
+          mp_twolimb_t accu = 0;
+          size_t count;
+          for (count = a_len; count > 0; count--)
+            {
+              accu += (mp_twolimb_t) *sourceptr++ << s;
+              *destptr++ = (mp_limb_t) accu;
+              accu = accu >> GMP_LIMB_BITS;
+            }
+          *destptr++ = (mp_limb_t) accu;
+        }
+      q_ptr = roomptr + b_len;
+      q_len = a_len - b_len + 1; /* q will have m-n+1 limbs */
+      {
+        size_t j = a_len - b_len; /* m-n */
+        mp_limb_t b_msd = b_ptr[b_len - 1]; /* b[n-1] */
+        mp_limb_t b_2msd = b_ptr[b_len - 2]; /* b[n-2] */
+        mp_twolimb_t b_msdd = /* b[n-1]*beta+b[n-2] */
+          ((mp_twolimb_t) b_msd << GMP_LIMB_BITS) | b_2msd;
+        /* Division loop, traversed m-n+1 times.
+           j counts down, b is unchanged, beta/2 <= b[n-1] < beta.  */
+        for (;;)
+          {
+            mp_limb_t q_star;
+            mp_limb_t c1;
+            if (r_ptr[j + b_len] < b_msd) /* r[j+n] < b[n-1] ? */
+              {
+                /* Divide r[j+n]*beta+r[j+n-1] by b[n-1], no overflow.  */
+                mp_twolimb_t num =
+                  ((mp_twolimb_t) r_ptr[j + b_len] << GMP_LIMB_BITS)
+                  | r_ptr[j + b_len - 1];
+                q_star = num / b_msd;
+                c1 = num % b_msd;
+              }
+            else
+              {
+                /* Overflow, hence r[j+n]*beta+r[j+n-1] >= beta*b[n-1].  */
+                q_star = (mp_limb_t)~(mp_limb_t)0; /* q* = beta-1 */
+                /* Test whether r[j+n]*beta+r[j+n-1] - (beta-1)*b[n-1] >= beta
+                   <==> r[j+n]*beta+r[j+n-1] + b[n-1] >= beta*b[n-1]+beta
+                   <==> b[n-1] < floor((r[j+n]*beta+r[j+n-1]+b[n-1])/beta)
+                        {<= beta !}.
+                   If yes, jump directly to the subtraction loop.
+                   (Otherwise, r[j+n]*beta+r[j+n-1] - (beta-1)*b[n-1] < beta
+                    <==> floor((r[j+n]*beta+r[j+n-1]+b[n-1])/beta) = b[n-1] ) */
+                if (r_ptr[j + b_len] > b_msd
+                    || (c1 = r_ptr[j + b_len - 1] + b_msd) < b_msd)
+                  /* r[j+n] >= b[n-1]+1 or
+                     r[j+n] = b[n-1] and the addition r[j+n-1]+b[n-1] gives a
+                     carry.  */
+                  goto subtract;
+              }
+            /* q_star = q*,
+               c1 = (r[j+n]*beta+r[j+n-1]) - q* * b[n-1] (>=0, <beta).  */
+            {
+              mp_twolimb_t c2 = /* c1*beta+r[j+n-2] */
+                ((mp_twolimb_t) c1 << GMP_LIMB_BITS) | r_ptr[j + b_len - 2];
+              mp_twolimb_t c3 = /* b[n-2] * q* */
+                (mp_twolimb_t) b_2msd * (mp_twolimb_t) q_star;
+              /* While c2 < c3, increase c2 and decrease c3.
+                 Consider c3-c2.  While it is > 0, decrease it by
+                 b[n-1]*beta+b[n-2].  Because of b[n-1]*beta+b[n-2] >= beta^2/2
+                 this can happen only twice.  */
+              if (c3 > c2)
+                {
+                  q_star = q_star - 1; /* q* := q* - 1 */
+                  if (c3 - c2 > b_msdd)
+                    q_star = q_star - 1; /* q* := q* - 1 */
+                }
+            }
+            if (q_star > 0)
+              subtract:
+              {
+                /* Subtract r := r - b * q* * beta^j.  */
+                mp_limb_t cr;
+                {
+                  const mp_limb_t *sourceptr = b_ptr;
+                  mp_limb_t *destptr = r_ptr + j;
+                  mp_twolimb_t carry = 0;
+                  size_t count;
+                  for (count = b_len; count > 0; count--)
+                    {
+                      /* Here 0 <= carry <= q*.  */
+                      carry =
+                        carry
+                        + (mp_twolimb_t) q_star * (mp_twolimb_t) *sourceptr++
+                        + (mp_limb_t) ~(*destptr);
+                      /* Here 0 <= carry <= beta*q* + beta-1.  */
+                      *destptr++ = ~(mp_limb_t) carry;
+                      carry = carry >> GMP_LIMB_BITS; /* <= q* */
+                    }
+                  cr = (mp_limb_t) carry;
+                }
+                /* Subtract cr from r_ptr[j + b_len], then forget about
+                   r_ptr[j + b_len].  */
+                if (cr > r_ptr[j + b_len])
+                  {
+                    /* Subtraction gave a carry.  */
+                    q_star = q_star - 1; /* q* := q* - 1 */
+                    /* Add b back.  */
+                    {
+                      const mp_limb_t *sourceptr = b_ptr;
+                      mp_limb_t *destptr = r_ptr + j;
+                      mp_limb_t carry = 0;
+                      size_t count;
+                      for (count = b_len; count > 0; count--)
+                        {
+                          mp_limb_t source1 = *sourceptr++;
+                          mp_limb_t source2 = *destptr;
+                          *destptr++ = source1 + source2 + carry;
+                          carry =
+                            (carry
+                             ? source1 >= (mp_limb_t) ~source2
+                             : source1 > (mp_limb_t) ~source2);
+                        }
+                    }
+                    /* Forget about the carry and about r[j+n].  */
+                  }
+              }
+            /* q* is determined.  Store it as q[j].  */
+            q_ptr[j] = q_star;
+            if (j == 0)
+              break;
+            j--;
+          }
+      }
+      r_len = b_len;
+      /* Normalise q.  */
+      if (q_ptr[q_len - 1] == 0)
+        q_len--;
+# if 0 /* Not needed here, since we need r only to compare it with b/2, and
+          b is shifted left by s bits.  */
+      /* Shift r right by s bits.  */
+      if (s > 0)
+        {
+          mp_limb_t ptr = r_ptr + r_len;
+          mp_twolimb_t accu = 0;
+          size_t count;
+          for (count = r_len; count > 0; count--)
+            {
+              accu = (mp_twolimb_t) (mp_limb_t) accu << GMP_LIMB_BITS;
+              accu += (mp_twolimb_t) *--ptr << (GMP_LIMB_BITS - s);
+              *ptr = (mp_limb_t) (accu >> GMP_LIMB_BITS);
+            }
+        }
+# endif
+      /* Normalise r.  */
+      while (r_len > 0 && r_ptr[r_len - 1] == 0)
+        r_len--;
+    }
+  /* Compare r << 1 with b.  */
+  if (r_len > b_len)
+    goto increment_q;
+  {
+    size_t i;
+    for (i = b_len;;)
+      {
+        mp_limb_t r_i =
+          (i <= r_len && i > 0 ? r_ptr[i - 1] >> (GMP_LIMB_BITS - 1) : 0)
+          | (i < r_len ? r_ptr[i] << 1 : 0);
+        mp_limb_t b_i = (i < b_len ? b_ptr[i] : 0);
+        if (r_i > b_i)
+          goto increment_q;
+        if (r_i < b_i)
+          goto keep_q;
+        if (i == 0)
+          break;
+        i--;
+      }
+  }
+  if (q_len > 0 && ((q_ptr[0] & 1) != 0))
+    /* q is odd.  */
+    increment_q:
+    {
+      size_t i;
+      for (i = 0; i < q_len; i++)
+        if (++(q_ptr[i]) != 0)
+          goto keep_q;
+      q_ptr[q_len++] = 1;
+    }
+  keep_q:
+  if (tmp_roomptr != NULL)
+    free (tmp_roomptr);
+  q->limbs = q_ptr;
+  q->nlimbs = q_len;
+  return roomptr;
+}
+
+/* Convert a bignum a >= 0, multiplied with 10^extra_zeroes, to decimal
+   representation.
+   Destroys the contents of a.
+   Return the allocated memory - containing the decimal digits in low-to-high
+   order, terminated with a NUL character - in case of success, NULL in case
+   of memory allocation failure.  */
+static char *
+convert_to_decimal (mpn_t a, size_t extra_zeroes)
+{
+  mp_limb_t *a_ptr = a.limbs;
+  size_t a_len = a.nlimbs;
+  /* 0.03345 is slightly larger than log(2)/(9*log(10)).  */
+  size_t c_len = 9 * ((size_t)(a_len * (GMP_LIMB_BITS * 0.03345f)) + 1);
+  /* We need extra_zeroes bytes for zeroes, followed by c_len bytes for the
+     digits of a, followed by 1 byte for the terminating NUL.  */
+  char *c_ptr = (char *) malloc (xsum (xsum (extra_zeroes, c_len), 1));
+  if (c_ptr != NULL)
+    {
+      char *d_ptr = c_ptr;
+      for (; extra_zeroes > 0; extra_zeroes--)
+        *d_ptr++ = '0';
+      while (a_len > 0)
+        {
+          /* Divide a by 10^9, in-place.  */
+          mp_limb_t remainder = 0;
+          mp_limb_t *ptr = a_ptr + a_len;
+          size_t count;
+          for (count = a_len; count > 0; count--)
+            {
+              mp_twolimb_t num =
+                ((mp_twolimb_t) remainder << GMP_LIMB_BITS) | *--ptr;
+              *ptr = num / 1000000000;
+              remainder = num % 1000000000;
+            }
+          /* Store the remainder as 9 decimal digits.  */
+          for (count = 9; count > 0; count--)
+            {
+              *d_ptr++ = '0' + (remainder % 10);
+              remainder = remainder / 10;
+            }
+          /* Normalize a.  */
+          if (a_ptr[a_len - 1] == 0)
+            a_len--;
+        }
+      /* Remove leading zeroes.  */
+      while (d_ptr > c_ptr && d_ptr[-1] == '0')
+        d_ptr--;
+      /* But keep at least one zero.  */
+      if (d_ptr == c_ptr)
+        *d_ptr++ = '0';
+      /* Terminate the string.  */
+      *d_ptr = '\0';
+    }
+  return c_ptr;
+}
+
+# if NEED_PRINTF_LONG_DOUBLE
+
+/* Assuming x is finite and >= 0:
+   write x as x = 2^e * m, where m is a bignum.
+   Return the allocated memory in case of success, NULL in case of memory
+   allocation failure.  */
+static void *
+decode_long_double (long double x, int *ep, mpn_t *mp)
+{
+  mpn_t m;
+  int exp;
+  long double y;
+  size_t i;
+
+  /* Allocate memory for result.  */
+  m.nlimbs = (LDBL_MANT_BIT + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS;
+  m.limbs = (mp_limb_t *) malloc (m.nlimbs * sizeof (mp_limb_t));
+  if (m.limbs == NULL)
+    return NULL;
+  /* Split into exponential part and mantissa.  */
+  y = frexpl (x, &exp);
+  if (!(y >= 0.0L && y < 1.0L))
+    abort ();
+  /* x = 2^exp * y = 2^(exp - LDBL_MANT_BIT) * (y * 2^LDBL_MANT_BIT), and the
+     latter is an integer.  */
+  /* Convert the mantissa (y * 2^LDBL_MANT_BIT) to a sequence of limbs.
+     I'm not sure whether it's safe to cast a 'long double' value between
+     2^31 and 2^32 to 'unsigned int', therefore play safe and cast only
+     'long double' values between 0 and 2^16 (to 'unsigned int' or 'int',
+     doesn't matter).  */
+#  if (LDBL_MANT_BIT % GMP_LIMB_BITS) != 0
+#   if (LDBL_MANT_BIT % GMP_LIMB_BITS) > GMP_LIMB_BITS / 2
+    {
+      mp_limb_t hi, lo;
+      y *= (mp_limb_t) 1 << (LDBL_MANT_BIT % (GMP_LIMB_BITS / 2));
+      hi = (int) y;
+      y -= hi;
+      if (!(y >= 0.0L && y < 1.0L))
+        abort ();
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      lo = (int) y;
+      y -= lo;
+      if (!(y >= 0.0L && y < 1.0L))
+        abort ();
+      m.limbs[LDBL_MANT_BIT / GMP_LIMB_BITS] = (hi << (GMP_LIMB_BITS / 2)) | lo;
+    }
+#   else
+    {
+      mp_limb_t d;
+      y *= (mp_limb_t) 1 << (LDBL_MANT_BIT % GMP_LIMB_BITS);
+      d = (int) y;
+      y -= d;
+      if (!(y >= 0.0L && y < 1.0L))
+        abort ();
+      m.limbs[LDBL_MANT_BIT / GMP_LIMB_BITS] = d;
+    }
+#   endif
+#  endif
+  for (i = LDBL_MANT_BIT / GMP_LIMB_BITS; i > 0; )
+    {
+      mp_limb_t hi, lo;
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      hi = (int) y;
+      y -= hi;
+      if (!(y >= 0.0L && y < 1.0L))
+        abort ();
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      lo = (int) y;
+      y -= lo;
+      if (!(y >= 0.0L && y < 1.0L))
+        abort ();
+      m.limbs[--i] = (hi << (GMP_LIMB_BITS / 2)) | lo;
+    }
+#  if 0 /* On FreeBSD 6.1/x86, 'long double' numbers sometimes have excess
+           precision.  */
+  if (!(y == 0.0L))
+    abort ();
+#  endif
+  /* Normalise.  */
+  while (m.nlimbs > 0 && m.limbs[m.nlimbs - 1] == 0)
+    m.nlimbs--;
+  *mp = m;
+  *ep = exp - LDBL_MANT_BIT;
+  return m.limbs;
+}
+
+# endif
+
+# if NEED_PRINTF_DOUBLE
+
+/* Assuming x is finite and >= 0:
+   write x as x = 2^e * m, where m is a bignum.
+   Return the allocated memory in case of success, NULL in case of memory
+   allocation failure.  */
+static void *
+decode_double (double x, int *ep, mpn_t *mp)
+{
+  mpn_t m;
+  int exp;
+  double y;
+  size_t i;
+
+  /* Allocate memory for result.  */
+  m.nlimbs = (DBL_MANT_BIT + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS;
+  m.limbs = (mp_limb_t *) malloc (m.nlimbs * sizeof (mp_limb_t));
+  if (m.limbs == NULL)
+    return NULL;
+  /* Split into exponential part and mantissa.  */
+  y = frexp (x, &exp);
+  if (!(y >= 0.0 && y < 1.0))
+    abort ();
+  /* x = 2^exp * y = 2^(exp - DBL_MANT_BIT) * (y * 2^DBL_MANT_BIT), and the
+     latter is an integer.  */
+  /* Convert the mantissa (y * 2^DBL_MANT_BIT) to a sequence of limbs.
+     I'm not sure whether it's safe to cast a 'double' value between
+     2^31 and 2^32 to 'unsigned int', therefore play safe and cast only
+     'double' values between 0 and 2^16 (to 'unsigned int' or 'int',
+     doesn't matter).  */
+#  if (DBL_MANT_BIT % GMP_LIMB_BITS) != 0
+#   if (DBL_MANT_BIT % GMP_LIMB_BITS) > GMP_LIMB_BITS / 2
+    {
+      mp_limb_t hi, lo;
+      y *= (mp_limb_t) 1 << (DBL_MANT_BIT % (GMP_LIMB_BITS / 2));
+      hi = (int) y;
+      y -= hi;
+      if (!(y >= 0.0 && y < 1.0))
+        abort ();
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      lo = (int) y;
+      y -= lo;
+      if (!(y >= 0.0 && y < 1.0))
+        abort ();
+      m.limbs[DBL_MANT_BIT / GMP_LIMB_BITS] = (hi << (GMP_LIMB_BITS / 2)) | lo;
+    }
+#   else
+    {
+      mp_limb_t d;
+      y *= (mp_limb_t) 1 << (DBL_MANT_BIT % GMP_LIMB_BITS);
+      d = (int) y;
+      y -= d;
+      if (!(y >= 0.0 && y < 1.0))
+        abort ();
+      m.limbs[DBL_MANT_BIT / GMP_LIMB_BITS] = d;
+    }
+#   endif
+#  endif
+  for (i = DBL_MANT_BIT / GMP_LIMB_BITS; i > 0; )
+    {
+      mp_limb_t hi, lo;
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      hi = (int) y;
+      y -= hi;
+      if (!(y >= 0.0 && y < 1.0))
+        abort ();
+      y *= (mp_limb_t) 1 << (GMP_LIMB_BITS / 2);
+      lo = (int) y;
+      y -= lo;
+      if (!(y >= 0.0 && y < 1.0))
+        abort ();
+      m.limbs[--i] = (hi << (GMP_LIMB_BITS / 2)) | lo;
+    }
+  if (!(y == 0.0))
+    abort ();
+  /* Normalise.  */
+  while (m.nlimbs > 0 && m.limbs[m.nlimbs - 1] == 0)
+    m.nlimbs--;
+  *mp = m;
+  *ep = exp - DBL_MANT_BIT;
+  return m.limbs;
+}
+
+# endif
+
+/* Assuming x = 2^e * m is finite and >= 0, and n is an integer:
+   Returns the decimal representation of round (x * 10^n).
+   Return the allocated memory - containing the decimal digits in low-to-high
+   order, terminated with a NUL character - in case of success, NULL in case
+   of memory allocation failure.  */
+static char *
+scale10_round_decimal_decoded (int e, mpn_t m, void *memory, int n)
+{
+  int s;
+  size_t extra_zeroes;
+  unsigned int abs_n;
+  unsigned int abs_s;
+  mp_limb_t *pow5_ptr;
+  size_t pow5_len;
+  unsigned int s_limbs;
+  unsigned int s_bits;
+  mpn_t pow5;
+  mpn_t z;
+  void *z_memory;
+  char *digits;
+
+  if (memory == NULL)
+    return NULL;
+  /* x = 2^e * m, hence
+     y = round (2^e * 10^n * m) = round (2^(e+n) * 5^n * m)
+       = round (2^s * 5^n * m).  */
+  s = e + n;
+  extra_zeroes = 0;
+  /* Factor out a common power of 10 if possible.  */
+  if (s > 0 && n > 0)
+    {
+      extra_zeroes = (s < n ? s : n);
+      s -= extra_zeroes;
+      n -= extra_zeroes;
+    }
+  /* Here y = round (2^s * 5^n * m) * 10^extra_zeroes.
+     Before converting to decimal, we need to compute
+     z = round (2^s * 5^n * m).  */
+  /* Compute 5^|n|, possibly shifted by |s| bits if n and s have the same
+     sign.  2.322 is slightly larger than log(5)/log(2).  */
+  abs_n = (n >= 0 ? n : -n);
+  abs_s = (s >= 0 ? s : -s);
+  pow5_ptr = (mp_limb_t *) malloc (((int)(abs_n * (2.322f / GMP_LIMB_BITS)) + 1
+                                    + abs_s / GMP_LIMB_BITS + 1)
+                                   * sizeof (mp_limb_t));
+  if (pow5_ptr == NULL)
+    {
+      free (memory);
+      return NULL;
+    }
+  /* Initialize with 1.  */
+  pow5_ptr[0] = 1;
+  pow5_len = 1;
+  /* Multiply with 5^|n|.  */
+  if (abs_n > 0)
+    {
+      static mp_limb_t const small_pow5[13 + 1] =
+        {
+          1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, 1953125, 9765625,
+          48828125, 244140625, 1220703125
+        };
+      unsigned int n13;
+      for (n13 = 0; n13 <= abs_n; n13 += 13)
+        {
+          mp_limb_t digit1 = small_pow5[n13 + 13 <= abs_n ? 13 : abs_n - n13];
+          size_t j;
+          mp_twolimb_t carry = 0;
+          for (j = 0; j < pow5_len; j++)
+            {
+              mp_limb_t digit2 = pow5_ptr[j];
+              carry += (mp_twolimb_t) digit1 * (mp_twolimb_t) digit2;
+              pow5_ptr[j] = (mp_limb_t) carry;
+              carry = carry >> GMP_LIMB_BITS;
+            }
+          if (carry > 0)
+            pow5_ptr[pow5_len++] = (mp_limb_t) carry;
+        }
+    }
+  s_limbs = abs_s / GMP_LIMB_BITS;
+  s_bits = abs_s % GMP_LIMB_BITS;
+  if (n >= 0 ? s >= 0 : s <= 0)
+    {
+      /* Multiply with 2^|s|.  */
+      if (s_bits > 0)
+        {
+          mp_limb_t *ptr = pow5_ptr;
+          mp_twolimb_t accu = 0;
+          size_t count;
+          for (count = pow5_len; count > 0; count--)
+            {
+              accu += (mp_twolimb_t) *ptr << s_bits;
+              *ptr++ = (mp_limb_t) accu;
+              accu = accu >> GMP_LIMB_BITS;
+            }
+          if (accu > 0)
+            {
+              *ptr = (mp_limb_t) accu;
+              pow5_len++;
+            }
+        }
+      if (s_limbs > 0)
+        {
+          size_t count;
+          for (count = pow5_len; count > 0;)
+            {
+              count--;
+              pow5_ptr[s_limbs + count] = pow5_ptr[count];
+            }
+          for (count = s_limbs; count > 0;)
+            {
+              count--;
+              pow5_ptr[count] = 0;
+            }
+          pow5_len += s_limbs;
+        }
+      pow5.limbs = pow5_ptr;
+      pow5.nlimbs = pow5_len;
+      if (n >= 0)
+        {
+          /* Multiply m with pow5.  No division needed.  */
+          z_memory = multiply (m, pow5, &z);
+        }
+      else
+        {
+          /* Divide m by pow5 and round.  */
+          z_memory = divide (m, pow5, &z);
+        }
+    }
+  else
+    {
+      pow5.limbs = pow5_ptr;
+      pow5.nlimbs = pow5_len;
+      if (n >= 0)
+        {
+          /* n >= 0, s < 0.
+             Multiply m with pow5, then divide by 2^|s|.  */
+          mpn_t numerator;
+          mpn_t denominator;
+          void *tmp_memory;
+          tmp_memory = multiply (m, pow5, &numerator);
+          if (tmp_memory == NULL)
+            {
+              free (pow5_ptr);
+              free (memory);
+              return NULL;
+            }
+          /* Construct 2^|s|.  */
+          {
+            mp_limb_t *ptr = pow5_ptr + pow5_len;
+            size_t i;
+            for (i = 0; i < s_limbs; i++)
+              ptr[i] = 0;
+            ptr[s_limbs] = (mp_limb_t) 1 << s_bits;
+            denominator.limbs = ptr;
+            denominator.nlimbs = s_limbs + 1;
+          }
+          z_memory = divide (numerator, denominator, &z);
+          free (tmp_memory);
+        }
+      else
+        {
+          /* n < 0, s > 0.
+             Multiply m with 2^s, then divide by pow5.  */
+          mpn_t numerator;
+          mp_limb_t *num_ptr;
+          num_ptr = (mp_limb_t *) malloc ((m.nlimbs + s_limbs + 1)
+                                          * sizeof (mp_limb_t));
+          if (num_ptr == NULL)
+            {
+              free (pow5_ptr);
+              free (memory);
+              return NULL;
+            }
+          {
+            mp_limb_t *destptr = num_ptr;
+            {
+              size_t i;
+              for (i = 0; i < s_limbs; i++)
+                *destptr++ = 0;
+            }
+            if (s_bits > 0)
+              {
+                const mp_limb_t *sourceptr = m.limbs;
+                mp_twolimb_t accu = 0;
+                size_t count;
+                for (count = m.nlimbs; count > 0; count--)
+                  {
+                    accu += (mp_twolimb_t) *sourceptr++ << s_bits;
+                    *destptr++ = (mp_limb_t) accu;
+                    accu = accu >> GMP_LIMB_BITS;
+                  }
+                if (accu > 0)
+                  *destptr++ = (mp_limb_t) accu;
+              }
+            else
+              {
+                const mp_limb_t *sourceptr = m.limbs;
+                size_t count;
+                for (count = m.nlimbs; count > 0; count--)
+                  *destptr++ = *sourceptr++;
+              }
+            numerator.limbs = num_ptr;
+            numerator.nlimbs = destptr - num_ptr;
+          }
+          z_memory = divide (numerator, pow5, &z);
+          free (num_ptr);
+        }
+    }
+  free (pow5_ptr);
+  free (memory);
+
+  /* Here y = round (x * 10^n) = z * 10^extra_zeroes.  */
+
+  if (z_memory == NULL)
+    return NULL;
+  digits = convert_to_decimal (z, extra_zeroes);
+  free (z_memory);
+  return digits;
+}
+
+# if NEED_PRINTF_LONG_DOUBLE
+
+/* Assuming x is finite and >= 0, and n is an integer:
+   Returns the decimal representation of round (x * 10^n).
+   Return the allocated memory - containing the decimal digits in low-to-high
+   order, terminated with a NUL character - in case of success, NULL in case
+   of memory allocation failure.  */
+static char *
+scale10_round_decimal_long_double (long double x, int n)
+{
+  int e IF_LINT(= 0);
+  mpn_t m;
+  void *memory = decode_long_double (x, &e, &m);
+  return scale10_round_decimal_decoded (e, m, memory, n);
+}
+
+# endif
+
+# if NEED_PRINTF_DOUBLE
+
+/* Assuming x is finite and >= 0, and n is an integer:
+   Returns the decimal representation of round (x * 10^n).
+   Return the allocated memory - containing the decimal digits in low-to-high
+   order, terminated with a NUL character - in case of success, NULL in case
+   of memory allocation failure.  */
+static char *
+scale10_round_decimal_double (double x, int n)
+{
+  int e IF_LINT(= 0);
+  mpn_t m;
+  void *memory = decode_double (x, &e, &m);
+  return scale10_round_decimal_decoded (e, m, memory, n);
+}
+
+# endif
+
+# if NEED_PRINTF_LONG_DOUBLE
+
+/* Assuming x is finite and > 0:
+   Return an approximation for n with 10^n <= x < 10^(n+1).
+   The approximation is usually the right n, but may be off by 1 sometimes.  */
+static int
+floorlog10l (long double x)
+{
+  int exp;
+  long double y;
+  double z;
+  double l;
+
+  /* Split into exponential part and mantissa.  */
+  y = frexpl (x, &exp);
+  if (!(y >= 0.0L && y < 1.0L))
+    abort ();
+  if (y == 0.0L)
+    return INT_MIN;
+  if (y < 0.5L)
+    {
+      while (y < (1.0L / (1 << (GMP_LIMB_BITS / 2)) / (1 << (GMP_LIMB_BITS / 2))))
+        {
+          y *= 1.0L * (1 << (GMP_LIMB_BITS / 2)) * (1 << (GMP_LIMB_BITS / 2));
+          exp -= GMP_LIMB_BITS;
+        }
+      if (y < (1.0L / (1 << 16)))
+        {
+          y *= 1.0L * (1 << 16);
+          exp -= 16;
+        }
+      if (y < (1.0L / (1 << 8)))
+        {
+          y *= 1.0L * (1 << 8);
+          exp -= 8;
+        }
+      if (y < (1.0L / (1 << 4)))
+        {
+          y *= 1.0L * (1 << 4);
+          exp -= 4;
+        }
+      if (y < (1.0L / (1 << 2)))
+        {
+          y *= 1.0L * (1 << 2);
+          exp -= 2;
+        }
+      if (y < (1.0L / (1 << 1)))
+        {
+          y *= 1.0L * (1 << 1);
+          exp -= 1;
+        }
+    }
+  if (!(y >= 0.5L && y < 1.0L))
+    abort ();
+  /* Compute an approximation for l = log2(x) = exp + log2(y).  */
+  l = exp;
+  z = y;
+  if (z < 0.70710678118654752444)
+    {
+      z *= 1.4142135623730950488;
+      l -= 0.5;
+    }
+  if (z < 0.8408964152537145431)
+    {
+      z *= 1.1892071150027210667;
+      l -= 0.25;
+    }
+  if (z < 0.91700404320467123175)
+    {
+      z *= 1.0905077326652576592;
+      l -= 0.125;
+    }
+  if (z < 0.9576032806985736469)
+    {
+      z *= 1.0442737824274138403;
+      l -= 0.0625;
+    }
+  /* Now 0.95 <= z <= 1.01.  */
+  z = 1 - z;
+  /* log2(1-z) = 1/log(2) * (- z - z^2/2 - z^3/3 - z^4/4 - ...)
+     Four terms are enough to get an approximation with error < 10^-7.  */
+  l -= 1.4426950408889634074 * z * (1.0 + z * (0.5 + z * ((1.0 / 3) + z * 0.25)));
+  /* Finally multiply with log(2)/log(10), yields an approximation for
+     log10(x).  */
+  l *= 0.30102999566398119523;
+  /* Round down to the next integer.  */
+  return (int) l + (l < 0 ? -1 : 0);
+}
+
+# endif
+
+# if NEED_PRINTF_DOUBLE
+
+/* Assuming x is finite and > 0:
+   Return an approximation for n with 10^n <= x < 10^(n+1).
+   The approximation is usually the right n, but may be off by 1 sometimes.  */
+static int
+floorlog10 (double x)
+{
+  int exp;
+  double y;
+  double z;
+  double l;
+
+  /* Split into exponential part and mantissa.  */
+  y = frexp (x, &exp);
+  if (!(y >= 0.0 && y < 1.0))
+    abort ();
+  if (y == 0.0)
+    return INT_MIN;
+  if (y < 0.5)
+    {
+      while (y < (1.0 / (1 << (GMP_LIMB_BITS / 2)) / (1 << (GMP_LIMB_BITS / 2))))
+        {
+          y *= 1.0 * (1 << (GMP_LIMB_BITS / 2)) * (1 << (GMP_LIMB_BITS / 2));
+          exp -= GMP_LIMB_BITS;
+        }
+      if (y < (1.0 / (1 << 16)))
+        {
+          y *= 1.0 * (1 << 16);
+          exp -= 16;
+        }
+      if (y < (1.0 / (1 << 8)))
+        {
+          y *= 1.0 * (1 << 8);
+          exp -= 8;
+        }
+      if (y < (1.0 / (1 << 4)))
+        {
+          y *= 1.0 * (1 << 4);
+          exp -= 4;
+        }
+      if (y < (1.0 / (1 << 2)))
+        {
+          y *= 1.0 * (1 << 2);
+          exp -= 2;
+        }
+      if (y < (1.0 / (1 << 1)))
+        {
+          y *= 1.0 * (1 << 1);
+          exp -= 1;
+        }
+    }
+  if (!(y >= 0.5 && y < 1.0))
+    abort ();
+  /* Compute an approximation for l = log2(x) = exp + log2(y).  */
+  l = exp;
+  z = y;
+  if (z < 0.70710678118654752444)
+    {
+      z *= 1.4142135623730950488;
+      l -= 0.5;
+    }
+  if (z < 0.8408964152537145431)
+    {
+      z *= 1.1892071150027210667;
+      l -= 0.25;
+    }
+  if (z < 0.91700404320467123175)
+    {
+      z *= 1.0905077326652576592;
+      l -= 0.125;
+    }
+  if (z < 0.9576032806985736469)
+    {
+      z *= 1.0442737824274138403;
+      l -= 0.0625;
+    }
+  /* Now 0.95 <= z <= 1.01.  */
+  z = 1 - z;
+  /* log2(1-z) = 1/log(2) * (- z - z^2/2 - z^3/3 - z^4/4 - ...)
+     Four terms are enough to get an approximation with error < 10^-7.  */
+  l -= 1.4426950408889634074 * z * (1.0 + z * (0.5 + z * ((1.0 / 3) + z * 0.25)));
+  /* Finally multiply with log(2)/log(10), yields an approximation for
+     log10(x).  */
+  l *= 0.30102999566398119523;
+  /* Round down to the next integer.  */
+  return (int) l + (l < 0 ? -1 : 0);
+}
+
+# endif
+
+/* Tests whether a string of digits consists of exactly PRECISION zeroes and
+   a single '1' digit.  */
+static int
+is_borderline (const char *digits, size_t precision)
+{
+  for (; precision > 0; precision--, digits++)
+    if (*digits != '0')
+      return 0;
+  if (*digits != '1')
+    return 0;
+  digits++;
+  return *digits == '\0';
+}
+
+#endif
+
+#if !USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF
+
+/* Use a different function name, to make it possible that the 'wchar_t'
+   parametrization and the 'char' parametrization get compiled in the same
+   translation unit.  */
+# if WIDE_CHAR_VERSION
+#  define MAX_ROOM_NEEDED wmax_room_needed
+# else
+#  define MAX_ROOM_NEEDED max_room_needed
+# endif
+
+/* Returns the number of TCHAR_T units needed as temporary space for the result
+   of sprintf or SNPRINTF of a single conversion directive.  */
+static size_t
+MAX_ROOM_NEEDED (const arguments *ap, size_t arg_index, FCHAR_T conversion,
+                 arg_type type, int flags, size_t width, int has_precision,
+                 size_t precision, int pad_ourselves)
+{
+  size_t tmp_length;
+
+  switch (conversion)
+    {
+    case 'd': case 'i': case 'u':
+      if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long long) * CHAR_BIT
+                          * 0.30103 /* binary -> decimal */
+                         )
+          + 1; /* turn floor into ceil */
+      else if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long) * CHAR_BIT
+                          * 0.30103 /* binary -> decimal */
+                         )
+          + 1; /* turn floor into ceil */
+      else
+        tmp_length =
+          (unsigned int) (sizeof (unsigned int) * CHAR_BIT
+                          * 0.30103 /* binary -> decimal */
+                         )
+          + 1; /* turn floor into ceil */
+      if (tmp_length < precision)
+        tmp_length = precision;
+      /* Multiply by 2, as an estimate for FLAG_GROUP.  */
+      tmp_length = xsum (tmp_length, tmp_length);
+      /* Add 1, to account for a leading sign.  */
+      tmp_length = xsum (tmp_length, 1);
+      break;
+
+    case 'o':
+      if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long long) * CHAR_BIT
+                          * 0.333334 /* binary -> octal */
+                         )
+          + 1; /* turn floor into ceil */
+      else if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long) * CHAR_BIT
+                          * 0.333334 /* binary -> octal */
+                         )
+          + 1; /* turn floor into ceil */
+      else
+        tmp_length =
+          (unsigned int) (sizeof (unsigned int) * CHAR_BIT
+                          * 0.333334 /* binary -> octal */
+                         )
+          + 1; /* turn floor into ceil */
+      if (tmp_length < precision)
+        tmp_length = precision;
+      /* Add 1, to account for a leading sign.  */
+      tmp_length = xsum (tmp_length, 1);
+      break;
+
+    case 'x': case 'X':
+      if (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long long) * CHAR_BIT
+                          * 0.25 /* binary -> hexadecimal */
+                         )
+          + 1; /* turn floor into ceil */
+      else if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
+        tmp_length =
+          (unsigned int) (sizeof (unsigned long) * CHAR_BIT
+                          * 0.25 /* binary -> hexadecimal */
+                         )
+          + 1; /* turn floor into ceil */
+      else
+        tmp_length =
+          (unsigned int) (sizeof (unsigned int) * CHAR_BIT
+                          * 0.25 /* binary -> hexadecimal */
+                         )
+          + 1; /* turn floor into ceil */
+      if (tmp_length < precision)
+        tmp_length = precision;
+      /* Add 2, to account for a leading sign or alternate form.  */
+      tmp_length = xsum (tmp_length, 2);
+      break;
+
+    case 'f': case 'F':
+      if (type == TYPE_LONGDOUBLE)
+        tmp_length =
+          (unsigned int) (LDBL_MAX_EXP
+                          * 0.30103 /* binary -> decimal */
+                          * 2 /* estimate for FLAG_GROUP */
+                         )
+          + 1 /* turn floor into ceil */
+          + 10; /* sign, decimal point etc. */
+      else
+        tmp_length =
+          (unsigned int) (DBL_MAX_EXP
+                          * 0.30103 /* binary -> decimal */
+                          * 2 /* estimate for FLAG_GROUP */
+                         )
+          + 1 /* turn floor into ceil */
+          + 10; /* sign, decimal point etc. */
+      tmp_length = xsum (tmp_length, precision);
+      break;
+
+    case 'e': case 'E': case 'g': case 'G':
+      tmp_length =
+        12; /* sign, decimal point, exponent etc. */
+      tmp_length = xsum (tmp_length, precision);
+      break;
+
+    case 'a': case 'A':
+      if (type == TYPE_LONGDOUBLE)
+        tmp_length =
+          (unsigned int) (LDBL_DIG
+                          * 0.831 /* decimal -> hexadecimal */
+                         )
+          + 1; /* turn floor into ceil */
+      else
+        tmp_length =
+          (unsigned int) (DBL_DIG
+                          * 0.831 /* decimal -> hexadecimal */
+                         )
+          + 1; /* turn floor into ceil */
+      if (tmp_length < precision)
+        tmp_length = precision;
+      /* Account for sign, decimal point etc. */
+      tmp_length = xsum (tmp_length, 12);
+      break;
+
+    case 'c':
+# if HAVE_WINT_T && !WIDE_CHAR_VERSION
+      if (type == TYPE_WIDE_CHAR)
+        {
+          tmp_length = MB_CUR_MAX;
+#  if ENABLE_WCHAR_FALLBACK
+          if (tmp_length < (sizeof (wchar_t) > 2 ? 10 : 6))
+            tmp_length = (sizeof (wchar_t) > 2 ? 10 : 6);
+#  endif
+        }
+      else
+# endif
+        tmp_length = 1;
+      break;
+
+    case 's':
+# if HAVE_WCHAR_T
+      if (type == TYPE_WIDE_STRING)
+        {
+#  if WIDE_CHAR_VERSION
+          /* ISO C says about %ls in fwprintf:
+               "If the precision is not specified or is greater than the size
+                of the array, the array shall contain a null wide character."
+             So if there is a precision, we must not use wcslen.  */
+          const wchar_t *arg = ap->arg[arg_index].a.a_wide_string;
+
+          if (has_precision)
+            tmp_length = local_wcsnlen (arg, precision);
+          else
+            tmp_length = local_wcslen (arg);
+#  else
+          /* ISO C says about %ls in fprintf:
+               "If a precision is specified, no more than that many bytes are
+                written (including shift sequences, if any), and the array
+                shall contain a null wide character if, to equal the multibyte
+                character sequence length given by the precision, the function
+                would need to access a wide character one past the end of the
+                array."
+             So if there is a precision, we must not use wcslen.  */
+          /* This case has already been handled separately in VASNPRINTF.  */
+          abort ();
+#  endif
+        }
+      else
+# endif
+        {
+# if WIDE_CHAR_VERSION
+          /* ISO C says about %s in fwprintf:
+               "If the precision is not specified or is greater than the size
+                of the converted array, the converted array shall contain a
+                null wide character."
+             So if there is a precision, we must not use strlen.  */
+          /* This case has already been handled separately in VASNPRINTF.  */
+          abort ();
+# else
+          /* ISO C says about %s in fprintf:
+               "If the precision is not specified or greater than the size of
+                the array, the array shall contain a null character."
+             So if there is a precision, we must not use strlen.  */
+          const char *arg = ap->arg[arg_index].a.a_string;
+
+          if (has_precision)
+            tmp_length = local_strnlen (arg, precision);
+          else
+            tmp_length = strlen (arg);
+# endif
+        }
+      break;
+
+    case 'p':
+      tmp_length =
+        (unsigned int) (sizeof (void *) * CHAR_BIT
+                        * 0.25 /* binary -> hexadecimal */
+                       )
+          + 1 /* turn floor into ceil */
+          + 2; /* account for leading 0x */
+      break;
+
+    default:
+      abort ();
+    }
+
+  if (!pad_ourselves)
+    {
+# if ENABLE_UNISTDIO
+      /* Padding considers the number of characters, therefore the number of
+         elements after padding may be
+           > max (tmp_length, width)
+         but is certainly
+           <= tmp_length + width.  */
+      tmp_length = xsum (tmp_length, width);
+# else
+      /* Padding considers the number of elements, says POSIX.  */
+      if (tmp_length < width)
+        tmp_length = width;
+# endif
+    }
+
+  tmp_length = xsum (tmp_length, 1); /* account for trailing NUL */
+
+  return tmp_length;
+}
+
+#endif
+
+DCHAR_T *
+VASNPRINTF (DCHAR_T *resultbuf, size_t *lengthp,
+            const FCHAR_T *format, va_list args)
+{
+  DIRECTIVES d;
+  arguments a;
+
+  if (PRINTF_PARSE (format, &d, &a) < 0)
+    /* errno is already set.  */
+    return NULL;
+
+  /* Frees the memory allocated by this function.  Preserves errno.  */
+#define CLEANUP() \
+  if (d.dir != d.direct_alloc_dir)                                      \
+    free (d.dir);                                                       \
+  if (a.arg != a.direct_alloc_arg)                                      \
+    free (a.arg);
+
+  if (PRINTF_FETCHARGS (args, &a) < 0)
+    {
+      CLEANUP ();
+      errno = EINVAL;
+      return NULL;
+    }
+
+  {
+    size_t buf_neededlength;
+    TCHAR_T *buf;
+    TCHAR_T *buf_malloced;
+    const FCHAR_T *cp;
+    size_t i;
+    DIRECTIVE *dp;
+    /* Output string accumulator.  */
+    DCHAR_T *result;
+    size_t allocated;
+    size_t length;
+
+    /* Allocate a small buffer that will hold a directive passed to
+       sprintf or snprintf.  */
+    buf_neededlength =
+      xsum4 (7, d.max_width_length, d.max_precision_length, 6);
+#if HAVE_ALLOCA
+    if (buf_neededlength < 4000 / sizeof (TCHAR_T))
+      {
+        buf = (TCHAR_T *) alloca (buf_neededlength * sizeof (TCHAR_T));
+        buf_malloced = NULL;
+      }
+    else
+#endif
+      {
+        size_t buf_memsize = xtimes (buf_neededlength, sizeof (TCHAR_T));
+        if (size_overflow_p (buf_memsize))
+          goto out_of_memory_1;
+        buf = (TCHAR_T *) malloc (buf_memsize);
+        if (buf == NULL)
+          goto out_of_memory_1;
+        buf_malloced = buf;
+      }
+
+    if (resultbuf != NULL)
+      {
+        result = resultbuf;
+        allocated = *lengthp;
+      }
+    else
+      {
+        result = NULL;
+        allocated = 0;
+      }
+    length = 0;
+    /* Invariants:
+       result is either == resultbuf or == NULL or malloc-allocated.
+       If length > 0, then result != NULL.  */
+
+    /* Ensures that allocated >= needed.  Aborts through a jump to
+       out_of_memory if needed is SIZE_MAX or otherwise too big.  */
+#define ENSURE_ALLOCATION_ELSE(needed, oom_statement) \
+    if ((needed) > allocated)                                                \
+      {                                                                      \
+        size_t memory_size;                                                  \
+        DCHAR_T *memory;                                                     \
+                                                                             \
+        allocated = (allocated > 0 ? xtimes (allocated, 2) : 12);            \
+        if ((needed) > allocated)                                            \
+          allocated = (needed);                                              \
+        memory_size = xtimes (allocated, sizeof (DCHAR_T));                  \
+        if (size_overflow_p (memory_size))                                   \
+          oom_statement                                                      \
+        if (result == resultbuf || result == NULL)                           \
+          memory = (DCHAR_T *) malloc (memory_size);                         \
+        else                                                                 \
+          memory = (DCHAR_T *) realloc (result, memory_size);                \
+        if (memory == NULL)                                                  \
+          oom_statement                                                      \
+        if (result == resultbuf && length > 0)                               \
+          DCHAR_CPY (memory, result, length);                                \
+        result = memory;                                                     \
+      }
+#define ENSURE_ALLOCATION(needed) \
+  ENSURE_ALLOCATION_ELSE((needed), goto out_of_memory; )
+
+    for (cp = format, i = 0, dp = &d.dir[0]; ; cp = dp->dir_end, i++, dp++)
+      {
+        if (cp != dp->dir_start)
+          {
+            size_t n = dp->dir_start - cp;
+            size_t augmented_length = xsum (length, n);
+
+            ENSURE_ALLOCATION (augmented_length);
+            /* This copies a piece of FCHAR_T[] into a DCHAR_T[].  Here we
+               need that the format string contains only ASCII characters
+               if FCHAR_T and DCHAR_T are not the same type.  */
+            if (sizeof (FCHAR_T) == sizeof (DCHAR_T))
+              {
+                DCHAR_CPY (result + length, (const DCHAR_T *) cp, n);
+                length = augmented_length;
+              }
+            else
+              {
+                do
+                  result[length++] = *cp++;
+                while (--n > 0);
+              }
+          }
+        if (i == d.count)
+          break;
+
+        /* Execute a single directive.  */
+        if (dp->conversion == '%')
+          {
+            size_t augmented_length;
+
+            if (!(dp->arg_index == ARG_NONE))
+              abort ();
+            augmented_length = xsum (length, 1);
+            ENSURE_ALLOCATION (augmented_length);
+            result[length] = '%';
+            length = augmented_length;
+          }
+        else
+          {
+            if (!(dp->arg_index != ARG_NONE))
+              abort ();
+
+            if (dp->conversion == 'n')
+              {
+                switch (a.arg[dp->arg_index].type)
+                  {
+                  case TYPE_COUNT_SCHAR_POINTER:
+                    *a.arg[dp->arg_index].a.a_count_schar_pointer = length;
+                    break;
+                  case TYPE_COUNT_SHORT_POINTER:
+                    *a.arg[dp->arg_index].a.a_count_short_pointer = length;
+                    break;
+                  case TYPE_COUNT_INT_POINTER:
+                    *a.arg[dp->arg_index].a.a_count_int_pointer = length;
+                    break;
+                  case TYPE_COUNT_LONGINT_POINTER:
+                    *a.arg[dp->arg_index].a.a_count_longint_pointer = length;
+                    break;
+                  case TYPE_COUNT_LONGLONGINT_POINTER:
+                    *a.arg[dp->arg_index].a.a_count_longlongint_pointer = length;
+                    break;
+                  default:
+                    abort ();
+                  }
+              }
+#if ENABLE_UNISTDIO
+            /* The unistdio extensions.  */
+            else if (dp->conversion == 'U')
+              {
+                arg_type type = a.arg[dp->arg_index].type;
+                int flags = dp->flags;
+                int has_width;
+                size_t width;
+                int has_precision;
+                size_t precision;
+
+                has_width = 0;
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                    has_width = 1;
+                  }
+
+                has_precision = 0;
+                precision = 0;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+
+                switch (type)
+                  {
+                  case TYPE_U8_STRING:
+                    {
+                      const uint8_t *arg = a.arg[dp->arg_index].a.a_u8_string;
+                      const uint8_t *arg_end;
+                      size_t characters;
+
+                      if (has_precision)
+                        {
+                          /* Use only PRECISION characters, from the left.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (; precision > 0; precision--)
+                            {
+                              int count = u8_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else if (has_width)
+                        {
+                          /* Use the entire string, and count the number of
+                             characters.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (;;)
+                            {
+                              int count = u8_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else
+                        {
+                          /* Use the entire string.  */
+                          arg_end = arg + u8_strlen (arg);
+                          /* The number of characters doesn't matter.  */
+                          characters = 0;
+                        }
+
+                      if (characters < width && !(dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+
+# if DCHAR_IS_UINT8_T
+                      {
+                        size_t n = arg_end - arg;
+                        ENSURE_ALLOCATION (xsum (length, n));
+                        DCHAR_CPY (result + length, arg, n);
+                        length += n;
+                      }
+# else
+                      { /* Convert.  */
+                        DCHAR_T *converted = result + length;
+                        size_t converted_len = allocated - length;
+#  if DCHAR_IS_TCHAR
+                        /* Convert from UTF-8 to locale encoding.  */
+                        converted =
+                          u8_conv_to_encoding (locale_charset (),
+                                               iconveh_question_mark,
+                                               arg, arg_end - arg, NULL,
+                                               converted, &converted_len);
+#  else
+                        /* Convert from UTF-8 to UTF-16/UTF-32.  */
+                        converted =
+                          U8_TO_DCHAR (arg, arg_end - arg,
+                                       converted, &converted_len);
+#  endif
+                        if (converted == NULL)
+                          {
+                            if (!(result == resultbuf || result == NULL))
+                              free (result);
+                            if (buf_malloced != NULL)
+                              free (buf_malloced);
+                            CLEANUP ();
+                            return NULL;
+                          }
+                        if (converted != result + length)
+                          {
+                            ENSURE_ALLOCATION_ELSE (xsum (length, converted_len),
+                                                    { free (converted); goto out_of_memory; });
+                            DCHAR_CPY (result + length, converted, converted_len);
+                            free (converted);
+                          }
+                        length += converted_len;
+                      }
+# endif
+
+                      if (characters < width && (dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+                    }
+                    break;
+
+                  case TYPE_U16_STRING:
+                    {
+                      const uint16_t *arg = a.arg[dp->arg_index].a.a_u16_string;
+                      const uint16_t *arg_end;
+                      size_t characters;
+
+                      if (has_precision)
+                        {
+                          /* Use only PRECISION characters, from the left.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (; precision > 0; precision--)
+                            {
+                              int count = u16_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else if (has_width)
+                        {
+                          /* Use the entire string, and count the number of
+                             characters.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (;;)
+                            {
+                              int count = u16_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else
+                        {
+                          /* Use the entire string.  */
+                          arg_end = arg + u16_strlen (arg);
+                          /* The number of characters doesn't matter.  */
+                          characters = 0;
+                        }
+
+                      if (characters < width && !(dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+
+# if DCHAR_IS_UINT16_T
+                      {
+                        size_t n = arg_end - arg;
+                        ENSURE_ALLOCATION (xsum (length, n));
+                        DCHAR_CPY (result + length, arg, n);
+                        length += n;
+                      }
+# else
+                      { /* Convert.  */
+                        DCHAR_T *converted = result + length;
+                        size_t converted_len = allocated - length;
+#  if DCHAR_IS_TCHAR
+                        /* Convert from UTF-16 to locale encoding.  */
+                        converted =
+                          u16_conv_to_encoding (locale_charset (),
+                                                iconveh_question_mark,
+                                                arg, arg_end - arg, NULL,
+                                                converted, &converted_len);
+#  else
+                        /* Convert from UTF-16 to UTF-8/UTF-32.  */
+                        converted =
+                          U16_TO_DCHAR (arg, arg_end - arg,
+                                        converted, &converted_len);
+#  endif
+                        if (converted == NULL)
+                          {
+                            if (!(result == resultbuf || result == NULL))
+                              free (result);
+                            if (buf_malloced != NULL)
+                              free (buf_malloced);
+                            CLEANUP ();
+                            return NULL;
+                          }
+                        if (converted != result + length)
+                          {
+                            ENSURE_ALLOCATION_ELSE (xsum (length, converted_len),
+                                                    { free (converted); goto out_of_memory; });
+                            DCHAR_CPY (result + length, converted, converted_len);
+                            free (converted);
+                          }
+                        length += converted_len;
+                      }
+# endif
+
+                      if (characters < width && (dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+                    }
+                    break;
+
+                  case TYPE_U32_STRING:
+                    {
+                      const uint32_t *arg = a.arg[dp->arg_index].a.a_u32_string;
+                      const uint32_t *arg_end;
+                      size_t characters;
+
+                      if (has_precision)
+                        {
+                          /* Use only PRECISION characters, from the left.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (; precision > 0; precision--)
+                            {
+                              int count = u32_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else if (has_width)
+                        {
+                          /* Use the entire string, and count the number of
+                             characters.  */
+                          arg_end = arg;
+                          characters = 0;
+                          for (;;)
+                            {
+                              int count = u32_strmblen (arg_end);
+                              if (count == 0)
+                                break;
+                              if (count < 0)
+                                {
+                                  if (!(result == resultbuf || result == NULL))
+                                    free (result);
+                                  if (buf_malloced != NULL)
+                                    free (buf_malloced);
+                                  CLEANUP ();
+                                  errno = EILSEQ;
+                                  return NULL;
+                                }
+                              arg_end += count;
+                              characters++;
+                            }
+                        }
+                      else
+                        {
+                          /* Use the entire string.  */
+                          arg_end = arg + u32_strlen (arg);
+                          /* The number of characters doesn't matter.  */
+                          characters = 0;
+                        }
+
+                      if (characters < width && !(dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+
+# if DCHAR_IS_UINT32_T
+                      {
+                        size_t n = arg_end - arg;
+                        ENSURE_ALLOCATION (xsum (length, n));
+                        DCHAR_CPY (result + length, arg, n);
+                        length += n;
+                      }
+# else
+                      { /* Convert.  */
+                        DCHAR_T *converted = result + length;
+                        size_t converted_len = allocated - length;
+#  if DCHAR_IS_TCHAR
+                        /* Convert from UTF-32 to locale encoding.  */
+                        converted =
+                          u32_conv_to_encoding (locale_charset (),
+                                                iconveh_question_mark,
+                                                arg, arg_end - arg, NULL,
+                                                converted, &converted_len);
+#  else
+                        /* Convert from UTF-32 to UTF-8/UTF-16.  */
+                        converted =
+                          U32_TO_DCHAR (arg, arg_end - arg,
+                                        converted, &converted_len);
+#  endif
+                        if (converted == NULL)
+                          {
+                            if (!(result == resultbuf || result == NULL))
+                              free (result);
+                            if (buf_malloced != NULL)
+                              free (buf_malloced);
+                            CLEANUP ();
+                            return NULL;
+                          }
+                        if (converted != result + length)
+                          {
+                            ENSURE_ALLOCATION_ELSE (xsum (length, converted_len),
+                                                    { free (converted); goto out_of_memory; });
+                            DCHAR_CPY (result + length, converted, converted_len);
+                            free (converted);
+                          }
+                        length += converted_len;
+                      }
+# endif
+
+                      if (characters < width && (dp->flags & FLAG_LEFT))
+                        {
+                          size_t n = width - characters;
+                          ENSURE_ALLOCATION (xsum (length, n));
+                          DCHAR_SET (result + length, ' ', n);
+                          length += n;
+                        }
+                    }
+                    break;
+
+                  default:
+                    abort ();
+                  }
+              }
+#endif
+#if (!USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || (NEED_PRINTF_DIRECTIVE_LS && !defined IN_LIBINTL) || ENABLE_WCHAR_FALLBACK) && HAVE_WCHAR_T
+            else if (dp->conversion == 's'
+# if WIDE_CHAR_VERSION
+                     && a.arg[dp->arg_index].type != TYPE_WIDE_STRING
+# else
+                     && a.arg[dp->arg_index].type == TYPE_WIDE_STRING
+# endif
+                    )
+              {
+                /* The normal handling of the 's' directive below requires
+                   allocating a temporary buffer.  The determination of its
+                   length (tmp_length), in the case when a precision is
+                   specified, below requires a conversion between a char[]
+                   string and a wchar_t[] wide string.  It could be done, but
+                   we have no guarantee that the implementation of sprintf will
+                   use the exactly same algorithm.  Without this guarantee, it
+                   is possible to have buffer overrun bugs.  In order to avoid
+                   such bugs, we implement the entire processing of the 's'
+                   directive ourselves.  */
+                int flags = dp->flags;
+                int has_width;
+                size_t width;
+                int has_precision;
+                size_t precision;
+
+                has_width = 0;
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                    has_width = 1;
+                  }
+
+                has_precision = 0;
+                precision = 6;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+
+# if WIDE_CHAR_VERSION
+                /* %s in vasnwprintf.  See the specification of fwprintf.  */
+                {
+                  const char *arg = a.arg[dp->arg_index].a.a_string;
+                  const char *arg_end;
+                  size_t characters;
+
+                  if (has_precision)
+                    {
+                      /* Use only as many bytes as needed to produce PRECISION
+                         wide characters, from the left.  */
+#  if HAVE_MBRTOWC
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      arg_end = arg;
+                      characters = 0;
+                      for (; precision > 0; precision--)
+                        {
+                          int count;
+#  if HAVE_MBRTOWC
+                          count = mbrlen (arg_end, MB_CUR_MAX, &state);
+#  else
+                          count = mblen (arg_end, MB_CUR_MAX);
+#  endif
+                          if (count == 0)
+                            /* Found the terminating NUL.  */
+                            break;
+                          if (count < 0)
+                            {
+                              /* Invalid or incomplete multibyte character.  */
+                              if (!(result == resultbuf || result == NULL))
+                                free (result);
+                              if (buf_malloced != NULL)
+                                free (buf_malloced);
+                              CLEANUP ();
+                              errno = EILSEQ;
+                              return NULL;
+                            }
+                          arg_end += count;
+                          characters++;
+                        }
+                    }
+                  else if (has_width)
+                    {
+                      /* Use the entire string, and count the number of wide
+                         characters.  */
+#  if HAVE_MBRTOWC
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      arg_end = arg;
+                      characters = 0;
+                      for (;;)
+                        {
+                          int count;
+#  if HAVE_MBRTOWC
+                          count = mbrlen (arg_end, MB_CUR_MAX, &state);
+#  else
+                          count = mblen (arg_end, MB_CUR_MAX);
+#  endif
+                          if (count == 0)
+                            /* Found the terminating NUL.  */
+                            break;
+                          if (count < 0)
+                            {
+                              /* Invalid or incomplete multibyte character.  */
+                              if (!(result == resultbuf || result == NULL))
+                                free (result);
+                              if (buf_malloced != NULL)
+                                free (buf_malloced);
+                              CLEANUP ();
+                              errno = EILSEQ;
+                              return NULL;
+                            }
+                          arg_end += count;
+                          characters++;
+                        }
+                    }
+                  else
+                    {
+                      /* Use the entire string.  */
+                      arg_end = arg + strlen (arg);
+                      /* The number of characters doesn't matter.  */
+                      characters = 0;
+                    }
+
+                  if (characters < width && !(dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - characters;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+
+                  if (has_precision || has_width)
+                    {
+                      /* We know the number of wide characters in advance.  */
+                      size_t remaining;
+#  if HAVE_MBRTOWC
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      ENSURE_ALLOCATION (xsum (length, characters));
+                      for (remaining = characters; remaining > 0; remaining--)
+                        {
+                          wchar_t wc;
+                          int count;
+#  if HAVE_MBRTOWC
+                          count = mbrtowc (&wc, arg, arg_end - arg, &state);
+#  else
+                          count = mbtowc (&wc, arg, arg_end - arg);
+#  endif
+                          if (count <= 0)
+                            /* mbrtowc not consistent with mbrlen, or mbtowc
+                               not consistent with mblen.  */
+                            abort ();
+                          result[length++] = wc;
+                          arg += count;
+                        }
+                      if (!(arg == arg_end))
+                        abort ();
+                    }
+                  else
+                    {
+#  if HAVE_MBRTOWC
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      while (arg < arg_end)
+                        {
+                          wchar_t wc;
+                          int count;
+#  if HAVE_MBRTOWC
+                          count = mbrtowc (&wc, arg, arg_end - arg, &state);
+#  else
+                          count = mbtowc (&wc, arg, arg_end - arg);
+#  endif
+                          if (count <= 0)
+                            /* mbrtowc not consistent with mbrlen, or mbtowc
+                               not consistent with mblen.  */
+                            abort ();
+                          ENSURE_ALLOCATION (xsum (length, 1));
+                          result[length++] = wc;
+                          arg += count;
+                        }
+                    }
+
+                  if (characters < width && (dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - characters;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+                }
+# else
+                /* %ls in vasnprintf.  See the specification of fprintf.  */
+                {
+                  const wchar_t *arg = a.arg[dp->arg_index].a.a_wide_string;
+                  const wchar_t *arg_end;
+                  size_t characters;
+#  if !DCHAR_IS_TCHAR
+                  /* This code assumes that TCHAR_T is 'char'.  */
+                  verify (sizeof (TCHAR_T) == 1);
+                  TCHAR_T *tmpsrc;
+                  DCHAR_T *tmpdst;
+                  size_t tmpdst_len;
+#  endif
+                  size_t w;
+
+                  if (has_precision)
+                    {
+                      /* Use only as many wide characters as needed to produce
+                         at most PRECISION bytes, from the left.  */
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      arg_end = arg;
+                      characters = 0;
+                      while (precision > 0)
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+
+                          if (*arg_end == 0)
+                            /* Found the terminating null wide character.  */
+                            break;
+                          count = local_wcrtomb (cbuf, *arg_end, &state);
+                          if (count < 0)
+                            {
+                              /* Cannot convert.  */
+                              if (!(result == resultbuf || result == NULL))
+                                free (result);
+                              if (buf_malloced != NULL)
+                                free (buf_malloced);
+                              CLEANUP ();
+                              errno = EILSEQ;
+                              return NULL;
+                            }
+                          if (precision < (unsigned int) count)
+                            break;
+                          arg_end++;
+                          characters += count;
+                          precision -= count;
+                        }
+                    }
+#  if DCHAR_IS_TCHAR
+                  else if (has_width)
+#  else
+                  else
+#  endif
+                    {
+                      /* Use the entire string, and count the number of
+                         bytes.  */
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+                      arg_end = arg;
+                      characters = 0;
+                      for (;;)
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+
+                          if (*arg_end == 0)
+                            /* Found the terminating null wide character.  */
+                            break;
+                          count = local_wcrtomb (cbuf, *arg_end, &state);
+                          if (count < 0)
+                            {
+                              /* Cannot convert.  */
+                              if (!(result == resultbuf || result == NULL))
+                                free (result);
+                              if (buf_malloced != NULL)
+                                free (buf_malloced);
+                              CLEANUP ();
+                              errno = EILSEQ;
+                              return NULL;
+                            }
+                          arg_end++;
+                          characters += count;
+                        }
+                    }
+#  if DCHAR_IS_TCHAR
+                  else
+                    {
+                      /* Use the entire string.  */
+                      arg_end = arg + local_wcslen (arg);
+                      /* The number of bytes doesn't matter.  */
+                      characters = 0;
+                    }
+#  endif
+
+#  if !DCHAR_IS_TCHAR
+                  /* Convert the string into a piece of temporary memory.  */
+                  tmpsrc = (TCHAR_T *) malloc (characters * sizeof (TCHAR_T));
+                  if (tmpsrc == NULL)
+                    goto out_of_memory;
+                  {
+                    TCHAR_T *tmpptr = tmpsrc;
+                    size_t remaining;
+#   if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                    mbstate_t state;
+                    memset (&state, '\0', sizeof (mbstate_t));
+#   endif
+                    for (remaining = characters; remaining > 0; )
+                      {
+                        char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                        int count;
+
+                        if (*arg == 0)
+                          abort ();
+                        count = local_wcrtomb (cbuf, *arg, &state);
+                        if (count <= 0)
+                          /* Inconsistency.  */
+                          abort ();
+                        memcpy (tmpptr, cbuf, count);
+                        tmpptr += count;
+                        arg++;
+                        remaining -= count;
+                      }
+                    if (!(arg == arg_end))
+                      abort ();
+                  }
+
+                  /* Convert from TCHAR_T[] to DCHAR_T[].  */
+                  tmpdst =
+                    DCHAR_CONV_FROM_ENCODING (locale_charset (),
+                                              iconveh_question_mark,
+                                              tmpsrc, characters,
+                                              NULL,
+                                              NULL, &tmpdst_len);
+                  if (tmpdst == NULL)
+                    {
+                      free (tmpsrc);
+                      if (!(result == resultbuf || result == NULL))
+                        free (result);
+                      if (buf_malloced != NULL)
+                        free (buf_malloced);
+                      CLEANUP ();
+                      return NULL;
+                    }
+                  free (tmpsrc);
+#  endif
+
+                  if (has_width)
+                    {
+#  if ENABLE_UNISTDIO
+                      /* Outside POSIX, it's preferable to compare the width
+                         against the number of _characters_ of the converted
+                         value.  */
+                      w = DCHAR_MBSNLEN (result + length, characters);
+#  else
+                      /* The width is compared against the number of _bytes_
+                         of the converted value, says POSIX.  */
+                      w = characters;
+#  endif
+                    }
+                  else
+                    /* w doesn't matter.  */
+                    w = 0;
+
+                  if (w < width && !(dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - w;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+
+#  if DCHAR_IS_TCHAR
+                  if (has_precision || has_width)
+                    {
+                      /* We know the number of bytes in advance.  */
+                      size_t remaining;
+#   if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#   endif
+                      ENSURE_ALLOCATION (xsum (length, characters));
+                      for (remaining = characters; remaining > 0; )
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+
+                          if (*arg == 0)
+                            abort ();
+                          count = local_wcrtomb (cbuf, *arg, &state);
+                          if (count <= 0)
+                            /* Inconsistency.  */
+                            abort ();
+                          memcpy (result + length, cbuf, count);
+                          length += count;
+                          arg++;
+                          remaining -= count;
+                        }
+                      if (!(arg == arg_end))
+                        abort ();
+                    }
+                  else
+                    {
+#   if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#   endif
+                      while (arg < arg_end)
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+
+                          if (*arg == 0)
+                            abort ();
+                          count = local_wcrtomb (cbuf, *arg, &state);
+                          if (count <= 0)
+                            {
+                              /* Cannot convert.  */
+                              if (!(result == resultbuf || result == NULL))
+                                free (result);
+                              if (buf_malloced != NULL)
+                                free (buf_malloced);
+                              CLEANUP ();
+                              errno = EILSEQ;
+                              return NULL;
+                            }
+                          ENSURE_ALLOCATION (xsum (length, count));
+                          memcpy (result + length, cbuf, count);
+                          length += count;
+                          arg++;
+                        }
+                    }
+#  else
+                  ENSURE_ALLOCATION_ELSE (xsum (length, tmpdst_len),
+                                          { free (tmpdst); goto out_of_memory; });
+                  DCHAR_CPY (result + length, tmpdst, tmpdst_len);
+                  free (tmpdst);
+                  length += tmpdst_len;
+#  endif
+
+                  if (w < width && (dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - w;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+                }
+# endif
+              }
+#endif
+#if ENABLE_WCHAR_FALLBACK && HAVE_WINT_T && !WIDE_CHAR_VERSION
+            else if (dp->conversion == 'c'
+                     && a.arg[dp->arg_index].type == TYPE_WIDE_CHAR)
+              {
+                /* Implement the 'lc' directive ourselves, in order to provide
+                   the fallback that avoids EILSEQ.  */
+                int flags = dp->flags;
+                int has_width;
+                size_t width;
+
+                has_width = 0;
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                    has_width = 1;
+                  }
+
+                /* %lc in vasnprintf.  See the specification of fprintf.  */
+                {
+                  wchar_t arg = (wchar_t) a.arg[dp->arg_index].a.a_wide_char;
+                  size_t characters;
+# if !DCHAR_IS_TCHAR
+                  /* This code assumes that TCHAR_T is 'char'.  */
+                  verify (sizeof (TCHAR_T) == 1);
+                  TCHAR_T tmpsrc[64]; /* Assume MB_CUR_MAX <= 64.  */
+                  DCHAR_T *tmpdst;
+                  size_t tmpdst_len;
+# endif
+                  size_t w;
+
+# if DCHAR_IS_TCHAR
+                  if (has_width)
+# endif
+                    {
+                      /* Count the number of bytes.  */
+                      characters = 0;
+                      if (arg != 0)
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+# if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                          mbstate_t state;
+                          memset (&state, '\0', sizeof (mbstate_t));
+# endif
+
+                          count = local_wcrtomb (cbuf, arg, &state);
+                          if (count < 0)
+                            /* Inconsistency.  */
+                            abort ();
+                          characters = count;
+                        }
+                    }
+# if DCHAR_IS_TCHAR
+                  else
+                    {
+                      /* The number of bytes doesn't matter.  */
+                      characters = 0;
+                    }
+# endif
+
+# if !DCHAR_IS_TCHAR
+                  /* Convert the string into a piece of temporary memory.  */
+                  if (characters > 0) /* implies arg != 0 */
+                    {
+                      char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                      int count;
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                      mbstate_t state;
+                      memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+
+                      count = local_wcrtomb (cbuf, arg, &state);
+                      if (count <= 0)
+                        /* Inconsistency.  */
+                        abort ();
+                      memcpy (tmpsrc, cbuf, count);
+                    }
+
+                  /* Convert from TCHAR_T[] to DCHAR_T[].  */
+                  tmpdst =
+                    DCHAR_CONV_FROM_ENCODING (locale_charset (),
+                                              iconveh_question_mark,
+                                              tmpsrc, characters,
+                                              NULL,
+                                              NULL, &tmpdst_len);
+                  if (tmpdst == NULL)
+                    {
+                      if (!(result == resultbuf || result == NULL))
+                        free (result);
+                      if (buf_malloced != NULL)
+                        free (buf_malloced);
+                      CLEANUP ();
+                      return NULL;
+                    }
+# endif
+
+                  if (has_width)
+                    {
+# if ENABLE_UNISTDIO
+                      /* Outside POSIX, it's preferable to compare the width
+                         against the number of _characters_ of the converted
+                         value.  */
+                      w = DCHAR_MBSNLEN (result + length, characters);
+# else
+                      /* The width is compared against the number of _bytes_
+                         of the converted value, says POSIX.  */
+                      w = characters;
+# endif
+                    }
+                  else
+                    /* w doesn't matter.  */
+                    w = 0;
+
+                  if (w < width && !(dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - w;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+
+# if DCHAR_IS_TCHAR
+                  if (has_width)
+                    {
+                      /* We know the number of bytes in advance.  */
+                      ENSURE_ALLOCATION (xsum (length, characters));
+                      if (characters > 0) /* implies arg != 0 */
+                        {
+                          int count;
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                          mbstate_t state;
+                          memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+
+                          count = local_wcrtomb (result + length, arg, &state);
+                          if (count <= 0)
+                            /* Inconsistency.  */
+                            abort ();
+                          length += count;
+                        }
+                    }
+                  else
+                    {
+                      if (arg != 0)
+                        {
+                          char cbuf[64]; /* Assume MB_CUR_MAX <= 64.  */
+                          int count;
+#  if HAVE_WCRTOMB && !defined GNULIB_defined_mbstate_t
+                          mbstate_t state;
+                          memset (&state, '\0', sizeof (mbstate_t));
+#  endif
+
+                          count = local_wcrtomb (cbuf, arg, &state);
+                          if (count <= 0)
+                            /* Inconsistency.  */
+                            abort ();
+                          ENSURE_ALLOCATION (xsum (length, count));
+                          memcpy (result + length, cbuf, count);
+                          length += count;
+                        }
+                    }
+# else
+                  ENSURE_ALLOCATION_ELSE (xsum (length, tmpdst_len),
+                                          { free (tmpdst); goto out_of_memory; });
+                  DCHAR_CPY (result + length, tmpdst, tmpdst_len);
+                  free (tmpdst);
+                  length += tmpdst_len;
+# endif
+
+                  if (w < width && (dp->flags & FLAG_LEFT))
+                    {
+                      size_t n = width - w;
+                      ENSURE_ALLOCATION (xsum (length, n));
+                      DCHAR_SET (result + length, ' ', n);
+                      length += n;
+                    }
+                }
+              }
+#endif
+#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_DOUBLE) && !defined IN_LIBINTL
+            else if ((dp->conversion == 'a' || dp->conversion == 'A')
+# if !(NEED_PRINTF_DIRECTIVE_A || (NEED_PRINTF_LONG_DOUBLE && NEED_PRINTF_DOUBLE))
+                     && (0
+#  if NEED_PRINTF_DOUBLE
+                         || a.arg[dp->arg_index].type == TYPE_DOUBLE
+#  endif
+#  if NEED_PRINTF_LONG_DOUBLE
+                         || a.arg[dp->arg_index].type == TYPE_LONGDOUBLE
+#  endif
+                        )
+# endif
+                    )
+              {
+                arg_type type = a.arg[dp->arg_index].type;
+                int flags = dp->flags;
+                size_t width;
+                int has_precision;
+                size_t precision;
+                size_t tmp_length;
+                size_t count;
+                DCHAR_T tmpbuf[700];
+                DCHAR_T *tmp;
+                DCHAR_T *pad_ptr;
+                DCHAR_T *p;
+
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                  }
+
+                has_precision = 0;
+                precision = 0;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+
+                /* Allocate a temporary buffer of sufficient size.  */
+                if (type == TYPE_LONGDOUBLE)
+                  tmp_length =
+                    (unsigned int) ((LDBL_DIG + 1)
+                                    * 0.831 /* decimal -> hexadecimal */
+                                   )
+                    + 1; /* turn floor into ceil */
+                else
+                  tmp_length =
+                    (unsigned int) ((DBL_DIG + 1)
+                                    * 0.831 /* decimal -> hexadecimal */
+                                   )
+                    + 1; /* turn floor into ceil */
+                if (tmp_length < precision)
+                  tmp_length = precision;
+                /* Account for sign, decimal point etc. */
+                tmp_length = xsum (tmp_length, 12);
+
+                if (tmp_length < width)
+                  tmp_length = width;
+
+                tmp_length = xsum (tmp_length, 1); /* account for trailing NUL */
+
+                if (tmp_length <= sizeof (tmpbuf) / sizeof (DCHAR_T))
+                  tmp = tmpbuf;
+                else
+                  {
+                    size_t tmp_memsize = xtimes (tmp_length, sizeof (DCHAR_T));
+
+                    if (size_overflow_p (tmp_memsize))
+                      /* Overflow, would lead to out of memory.  */
+                      goto out_of_memory;
+                    tmp = (DCHAR_T *) malloc (tmp_memsize);
+                    if (tmp == NULL)
+                      /* Out of memory.  */
+                      goto out_of_memory;
+                  }
+
+                pad_ptr = NULL;
+                p = tmp;
+                if (type == TYPE_LONGDOUBLE)
+                  {
+# if NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE
+                    long double arg = a.arg[dp->arg_index].a.a_longdouble;
+
+                    if (isnanl (arg))
+                      {
+                        if (dp->conversion == 'A')
+                          {
+                            *p++ = 'N'; *p++ = 'A'; *p++ = 'N';
+                          }
+                        else
+                          {
+                            *p++ = 'n'; *p++ = 'a'; *p++ = 'n';
+                          }
+                      }
+                    else
+                      {
+                        int sign = 0;
+                        DECL_LONG_DOUBLE_ROUNDING
+
+                        BEGIN_LONG_DOUBLE_ROUNDING ();
+
+                        if (signbit (arg)) /* arg < 0.0L or negative zero */
+                          {
+                            sign = -1;
+                            arg = -arg;
+                          }
+
+                        if (sign < 0)
+                          *p++ = '-';
+                        else if (flags & FLAG_SHOWSIGN)
+                          *p++ = '+';
+                        else if (flags & FLAG_SPACE)
+                          *p++ = ' ';
+
+                        if (arg > 0.0L && arg + arg == arg)
+                          {
+                            if (dp->conversion == 'A')
+                              {
+                                *p++ = 'I'; *p++ = 'N'; *p++ = 'F';
+                              }
+                            else
+                              {
+                                *p++ = 'i'; *p++ = 'n'; *p++ = 'f';
+                              }
+                          }
+                        else
+                          {
+                            int exponent;
+                            long double mantissa;
+
+                            if (arg > 0.0L)
+                              mantissa = printf_frexpl (arg, &exponent);
+                            else
+                              {
+                                exponent = 0;
+                                mantissa = 0.0L;
+                              }
+
+                            if (has_precision
+                                && precision < (unsigned int) ((LDBL_DIG + 1) * 0.831) + 1)
+                              {
+                                /* Round the mantissa.  */
+                                long double tail = mantissa;
+                                size_t q;
+
+                                for (q = precision; ; q--)
+                                  {
+                                    int digit = (int) tail;
+                                    tail -= digit;
+                                    if (q == 0)
+                                      {
+                                        if (digit & 1 ? tail >= 0.5L : tail > 0.5L)
+                                          tail = 1 - tail;
+                                        else
+                                          tail = - tail;
+                                        break;
+                                      }
+                                    tail *= 16.0L;
+                                  }
+                                if (tail != 0.0L)
+                                  for (q = precision; q > 0; q--)
+                                    tail *= 0.0625L;
+                                mantissa += tail;
+                              }
+
+                            *p++ = '0';
+                            *p++ = dp->conversion - 'A' + 'X';
+                            pad_ptr = p;
+                            {
+                              int digit;
+
+                              digit = (int) mantissa;
+                              mantissa -= digit;
+                              *p++ = '0' + digit;
+                              if ((flags & FLAG_ALT)
+                                  || mantissa > 0.0L || precision > 0)
+                                {
+                                  *p++ = decimal_point_char ();
+                                  /* This loop terminates because we assume
+                                     that FLT_RADIX is a power of 2.  */
+                                  while (mantissa > 0.0L)
+                                    {
+                                      mantissa *= 16.0L;
+                                      digit = (int) mantissa;
+                                      mantissa -= digit;
+                                      *p++ = digit
+                                             + (digit < 10
+                                                ? '0'
+                                                : dp->conversion - 10);
+                                      if (precision > 0)
+                                        precision--;
+                                    }
+                                  while (precision > 0)
+                                    {
+                                      *p++ = '0';
+                                      precision--;
+                                    }
+                                }
+                              }
+                              *p++ = dp->conversion - 'A' + 'P';
+#  if WIDE_CHAR_VERSION
+                              {
+                                static const wchar_t decimal_format[] =
+                                  { '%', '+', 'd', '\0' };
+                                SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                              }
+                              while (*p != '\0')
+                                p++;
+#  else
+                              if (sizeof (DCHAR_T) == 1)
+                                {
+                                  sprintf ((char *) p, "%+d", exponent);
+                                  while (*p != '\0')
+                                    p++;
+                                }
+                              else
+                                {
+                                  char expbuf[6 + 1];
+                                  const char *ep;
+                                  sprintf (expbuf, "%+d", exponent);
+                                  for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                    p++;
+                                }
+#  endif
+                          }
+
+                        END_LONG_DOUBLE_ROUNDING ();
+                      }
+# else
+                    abort ();
+# endif
+                  }
+                else
+                  {
+# if NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_DOUBLE
+                    double arg = a.arg[dp->arg_index].a.a_double;
+
+                    if (isnand (arg))
+                      {
+                        if (dp->conversion == 'A')
+                          {
+                            *p++ = 'N'; *p++ = 'A'; *p++ = 'N';
+                          }
+                        else
+                          {
+                            *p++ = 'n'; *p++ = 'a'; *p++ = 'n';
+                          }
+                      }
+                    else
+                      {
+                        int sign = 0;
+
+                        if (signbit (arg)) /* arg < 0.0 or negative zero */
+                          {
+                            sign = -1;
+                            arg = -arg;
+                          }
+
+                        if (sign < 0)
+                          *p++ = '-';
+                        else if (flags & FLAG_SHOWSIGN)
+                          *p++ = '+';
+                        else if (flags & FLAG_SPACE)
+                          *p++ = ' ';
+
+                        if (arg > 0.0 && arg + arg == arg)
+                          {
+                            if (dp->conversion == 'A')
+                              {
+                                *p++ = 'I'; *p++ = 'N'; *p++ = 'F';
+                              }
+                            else
+                              {
+                                *p++ = 'i'; *p++ = 'n'; *p++ = 'f';
+                              }
+                          }
+                        else
+                          {
+                            int exponent;
+                            double mantissa;
+
+                            if (arg > 0.0)
+                              mantissa = printf_frexp (arg, &exponent);
+                            else
+                              {
+                                exponent = 0;
+                                mantissa = 0.0;
+                              }
+
+                            if (has_precision
+                                && precision < (unsigned int) ((DBL_DIG + 1) * 0.831) + 1)
+                              {
+                                /* Round the mantissa.  */
+                                double tail = mantissa;
+                                size_t q;
+
+                                for (q = precision; ; q--)
+                                  {
+                                    int digit = (int) tail;
+                                    tail -= digit;
+                                    if (q == 0)
+                                      {
+                                        if (digit & 1 ? tail >= 0.5 : tail > 0.5)
+                                          tail = 1 - tail;
+                                        else
+                                          tail = - tail;
+                                        break;
+                                      }
+                                    tail *= 16.0;
+                                  }
+                                if (tail != 0.0)
+                                  for (q = precision; q > 0; q--)
+                                    tail *= 0.0625;
+                                mantissa += tail;
+                              }
+
+                            *p++ = '0';
+                            *p++ = dp->conversion - 'A' + 'X';
+                            pad_ptr = p;
+                            {
+                              int digit;
+
+                              digit = (int) mantissa;
+                              mantissa -= digit;
+                              *p++ = '0' + digit;
+                              if ((flags & FLAG_ALT)
+                                  || mantissa > 0.0 || precision > 0)
+                                {
+                                  *p++ = decimal_point_char ();
+                                  /* This loop terminates because we assume
+                                     that FLT_RADIX is a power of 2.  */
+                                  while (mantissa > 0.0)
+                                    {
+                                      mantissa *= 16.0;
+                                      digit = (int) mantissa;
+                                      mantissa -= digit;
+                                      *p++ = digit
+                                             + (digit < 10
+                                                ? '0'
+                                                : dp->conversion - 10);
+                                      if (precision > 0)
+                                        precision--;
+                                    }
+                                  while (precision > 0)
+                                    {
+                                      *p++ = '0';
+                                      precision--;
+                                    }
+                                }
+                              }
+                              *p++ = dp->conversion - 'A' + 'P';
+#  if WIDE_CHAR_VERSION
+                              {
+                                static const wchar_t decimal_format[] =
+                                  { '%', '+', 'd', '\0' };
+                                SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                              }
+                              while (*p != '\0')
+                                p++;
+#  else
+                              if (sizeof (DCHAR_T) == 1)
+                                {
+                                  sprintf ((char *) p, "%+d", exponent);
+                                  while (*p != '\0')
+                                    p++;
+                                }
+                              else
+                                {
+                                  char expbuf[6 + 1];
+                                  const char *ep;
+                                  sprintf (expbuf, "%+d", exponent);
+                                  for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                    p++;
+                                }
+#  endif
+                          }
+                      }
+# else
+                    abort ();
+# endif
+                  }
+
+                /* The generated string now extends from tmp to p, with the
+                   zero padding insertion point being at pad_ptr.  */
+                count = p - tmp;
+
+                if (count < width)
+                  {
+                    size_t pad = width - count;
+                    DCHAR_T *end = p + pad;
+
+                    if (flags & FLAG_LEFT)
+                      {
+                        /* Pad with spaces on the right.  */
+                        for (; pad > 0; pad--)
+                          *p++ = ' ';
+                      }
+                    else if ((flags & FLAG_ZERO) && pad_ptr != NULL)
+                      {
+                        /* Pad with zeroes.  */
+                        DCHAR_T *q = end;
+
+                        while (p > pad_ptr)
+                          *--q = *--p;
+                        for (; pad > 0; pad--)
+                          *p++ = '0';
+                      }
+                    else
+                      {
+                        /* Pad with spaces on the left.  */
+                        DCHAR_T *q = end;
+
+                        while (p > tmp)
+                          *--q = *--p;
+                        for (; pad > 0; pad--)
+                          *p++ = ' ';
+                      }
+
+                    p = end;
+                  }
+
+                count = p - tmp;
+
+                if (count >= tmp_length)
+                  /* tmp_length was incorrectly calculated - fix the
+                     code above!  */
+                  abort ();
+
+                /* Make room for the result.  */
+                if (count >= allocated - length)
+                  {
+                    size_t n = xsum (length, count);
+
+                    ENSURE_ALLOCATION (n);
+                  }
+
+                /* Append the result.  */
+                memcpy (result + length, tmp, count * sizeof (DCHAR_T));
+                if (tmp != tmpbuf)
+                  free (tmp);
+                length += count;
+              }
+#endif
+#if (NEED_PRINTF_INFINITE_DOUBLE || NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE || NEED_PRINTF_LONG_DOUBLE) && !defined IN_LIBINTL
+            else if ((dp->conversion == 'f' || dp->conversion == 'F'
+                      || dp->conversion == 'e' || dp->conversion == 'E'
+                      || dp->conversion == 'g' || dp->conversion == 'G'
+                      || dp->conversion == 'a' || dp->conversion == 'A')
+                     && (0
+# if NEED_PRINTF_DOUBLE
+                         || a.arg[dp->arg_index].type == TYPE_DOUBLE
+# elif NEED_PRINTF_INFINITE_DOUBLE
+                         || (a.arg[dp->arg_index].type == TYPE_DOUBLE
+                             /* The systems (mingw) which produce wrong output
+                                for Inf, -Inf, and NaN also do so for -0.0.
+                                Therefore we treat this case here as well.  */
+                             && is_infinite_or_zero (a.arg[dp->arg_index].a.a_double))
+# endif
+# if NEED_PRINTF_LONG_DOUBLE
+                         || a.arg[dp->arg_index].type == TYPE_LONGDOUBLE
+# elif NEED_PRINTF_INFINITE_LONG_DOUBLE
+                         || (a.arg[dp->arg_index].type == TYPE_LONGDOUBLE
+                             /* Some systems produce wrong output for Inf,
+                                -Inf, and NaN.  Some systems in this category
+                                (IRIX 5.3) also do so for -0.0.  Therefore we
+                                treat this case here as well.  */
+                             && is_infinite_or_zerol (a.arg[dp->arg_index].a.a_longdouble))
+# endif
+                        ))
+              {
+# if (NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE) && (NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE)
+                arg_type type = a.arg[dp->arg_index].type;
+# endif
+                int flags = dp->flags;
+                size_t width;
+                size_t count;
+                int has_precision;
+                size_t precision;
+                size_t tmp_length;
+                DCHAR_T tmpbuf[700];
+                DCHAR_T *tmp;
+                DCHAR_T *pad_ptr;
+                DCHAR_T *p;
+
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+                  }
+
+                has_precision = 0;
+                precision = 0;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+
+                /* POSIX specifies the default precision to be 6 for %f, %F,
+                   %e, %E, but not for %g, %G.  Implementations appear to use
+                   the same default precision also for %g, %G.  But for %a, %A,
+                   the default precision is 0.  */
+                if (!has_precision)
+                  if (!(dp->conversion == 'a' || dp->conversion == 'A'))
+                    precision = 6;
+
+                /* Allocate a temporary buffer of sufficient size.  */
+# if NEED_PRINTF_DOUBLE && NEED_PRINTF_LONG_DOUBLE
+                tmp_length = (type == TYPE_LONGDOUBLE ? LDBL_DIG + 1 : DBL_DIG + 1);
+# elif NEED_PRINTF_INFINITE_DOUBLE && NEED_PRINTF_LONG_DOUBLE
+                tmp_length = (type == TYPE_LONGDOUBLE ? LDBL_DIG + 1 : 0);
+# elif NEED_PRINTF_LONG_DOUBLE
+                tmp_length = LDBL_DIG + 1;
+# elif NEED_PRINTF_DOUBLE
+                tmp_length = DBL_DIG + 1;
+# else
+                tmp_length = 0;
+# endif
+                if (tmp_length < precision)
+                  tmp_length = precision;
+# if NEED_PRINTF_LONG_DOUBLE
+#  if NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE
+                if (type == TYPE_LONGDOUBLE)
+#  endif
+                  if (dp->conversion == 'f' || dp->conversion == 'F')
+                    {
+                      long double arg = a.arg[dp->arg_index].a.a_longdouble;
+                      if (!(isnanl (arg) || arg + arg == arg))
+                        {
+                          /* arg is finite and nonzero.  */
+                          int exponent = floorlog10l (arg < 0 ? -arg : arg);
+                          if (exponent >= 0 && tmp_length < exponent + precision)
+                            tmp_length = exponent + precision;
+                        }
+                    }
+# endif
+# if NEED_PRINTF_DOUBLE
+#  if NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE
+                if (type == TYPE_DOUBLE)
+#  endif
+                  if (dp->conversion == 'f' || dp->conversion == 'F')
+                    {
+                      double arg = a.arg[dp->arg_index].a.a_double;
+                      if (!(isnand (arg) || arg + arg == arg))
+                        {
+                          /* arg is finite and nonzero.  */
+                          int exponent = floorlog10 (arg < 0 ? -arg : arg);
+                          if (exponent >= 0 && tmp_length < exponent + precision)
+                            tmp_length = exponent + precision;
+                        }
+                    }
+# endif
+                /* Account for sign, decimal point etc. */
+                tmp_length = xsum (tmp_length, 12);
+
+                if (tmp_length < width)
+                  tmp_length = width;
+
+                tmp_length = xsum (tmp_length, 1); /* account for trailing NUL */
+
+                if (tmp_length <= sizeof (tmpbuf) / sizeof (DCHAR_T))
+                  tmp = tmpbuf;
+                else
+                  {
+                    size_t tmp_memsize = xtimes (tmp_length, sizeof (DCHAR_T));
+
+                    if (size_overflow_p (tmp_memsize))
+                      /* Overflow, would lead to out of memory.  */
+                      goto out_of_memory;
+                    tmp = (DCHAR_T *) malloc (tmp_memsize);
+                    if (tmp == NULL)
+                      /* Out of memory.  */
+                      goto out_of_memory;
+                  }
+
+                pad_ptr = NULL;
+                p = tmp;
+
+# if NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE
+#  if NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE
+                if (type == TYPE_LONGDOUBLE)
+#  endif
+                  {
+                    long double arg = a.arg[dp->arg_index].a.a_longdouble;
+
+                    if (isnanl (arg))
+                      {
+                        if (dp->conversion >= 'A' && dp->conversion <= 'Z')
+                          {
+                            *p++ = 'N'; *p++ = 'A'; *p++ = 'N';
+                          }
+                        else
+                          {
+                            *p++ = 'n'; *p++ = 'a'; *p++ = 'n';
+                          }
+                      }
+                    else
+                      {
+                        int sign = 0;
+                        DECL_LONG_DOUBLE_ROUNDING
+
+                        BEGIN_LONG_DOUBLE_ROUNDING ();
+
+                        if (signbit (arg)) /* arg < 0.0L or negative zero */
+                          {
+                            sign = -1;
+                            arg = -arg;
+                          }
+
+                        if (sign < 0)
+                          *p++ = '-';
+                        else if (flags & FLAG_SHOWSIGN)
+                          *p++ = '+';
+                        else if (flags & FLAG_SPACE)
+                          *p++ = ' ';
+
+                        if (arg > 0.0L && arg + arg == arg)
+                          {
+                            if (dp->conversion >= 'A' && dp->conversion <= 'Z')
+                              {
+                                *p++ = 'I'; *p++ = 'N'; *p++ = 'F';
+                              }
+                            else
+                              {
+                                *p++ = 'i'; *p++ = 'n'; *p++ = 'f';
+                              }
+                          }
+                        else
+                          {
+#  if NEED_PRINTF_LONG_DOUBLE
+                            pad_ptr = p;
+
+                            if (dp->conversion == 'f' || dp->conversion == 'F')
+                              {
+                                char *digits;
+                                size_t ndigits;
+
+                                digits =
+                                  scale10_round_decimal_long_double (arg, precision);
+                                if (digits == NULL)
+                                  {
+                                    END_LONG_DOUBLE_ROUNDING ();
+                                    goto out_of_memory;
+                                  }
+                                ndigits = strlen (digits);
+
+                                if (ndigits > precision)
+                                  do
+                                    {
+                                      --ndigits;
+                                      *p++ = digits[ndigits];
+                                    }
+                                  while (ndigits > precision);
+                                else
+                                  *p++ = '0';
+                                /* Here ndigits <= precision.  */
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > ndigits; precision--)
+                                      *p++ = '0';
+                                    while (ndigits > 0)
+                                      {
+                                        --ndigits;
+                                        *p++ = digits[ndigits];
+                                      }
+                                  }
+
+                                free (digits);
+                              }
+                            else if (dp->conversion == 'e' || dp->conversion == 'E')
+                              {
+                                int exponent;
+
+                                if (arg == 0.0L)
+                                  {
+                                    exponent = 0;
+                                    *p++ = '0';
+                                    if ((flags & FLAG_ALT) || precision > 0)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        for (; precision > 0; precision--)
+                                          *p++ = '0';
+                                      }
+                                  }
+                                else
+                                  {
+                                    /* arg > 0.0L.  */
+                                    int adjusted;
+                                    char *digits;
+                                    size_t ndigits;
+
+                                    exponent = floorlog10l (arg);
+                                    adjusted = 0;
+                                    for (;;)
+                                      {
+                                        digits =
+                                          scale10_round_decimal_long_double (arg,
+                                                                             (int)precision - exponent);
+                                        if (digits == NULL)
+                                          {
+                                            END_LONG_DOUBLE_ROUNDING ();
+                                            goto out_of_memory;
+                                          }
+                                        ndigits = strlen (digits);
+
+                                        if (ndigits == precision + 1)
+                                          break;
+                                        if (ndigits < precision
+                                            || ndigits > precision + 2)
+                                          /* The exponent was not guessed
+                                             precisely enough.  */
+                                          abort ();
+                                        if (adjusted)
+                                          /* None of two values of exponent is
+                                             the right one.  Prevent an endless
+                                             loop.  */
+                                          abort ();
+                                        free (digits);
+                                        if (ndigits == precision)
+                                          exponent -= 1;
+                                        else
+                                          exponent += 1;
+                                        adjusted = 1;
+                                      }
+                                    /* Here ndigits = precision+1.  */
+                                    if (is_borderline (digits, precision))
+                                      {
+                                        /* Maybe the exponent guess was too high
+                                           and a smaller exponent can be reached
+                                           by turning a 10...0 into 9...9x.  */
+                                        char *digits2 =
+                                          scale10_round_decimal_long_double (arg,
+                                                                             (int)precision - exponent + 1);
+                                        if (digits2 == NULL)
+                                          {
+                                            free (digits);
+                                            END_LONG_DOUBLE_ROUNDING ();
+                                            goto out_of_memory;
+                                          }
+                                        if (strlen (digits2) == precision + 1)
+                                          {
+                                            free (digits);
+                                            digits = digits2;
+                                            exponent -= 1;
+                                          }
+                                        else
+                                          free (digits2);
+                                      }
+                                    /* Here ndigits = precision+1.  */
+
+                                    *p++ = digits[--ndigits];
+                                    if ((flags & FLAG_ALT) || precision > 0)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        while (ndigits > 0)
+                                          {
+                                            --ndigits;
+                                            *p++ = digits[ndigits];
+                                          }
+                                      }
+
+                                    free (digits);
+                                  }
+
+                                *p++ = dp->conversion; /* 'e' or 'E' */
+#   if WIDE_CHAR_VERSION
+                                {
+                                  static const wchar_t decimal_format[] =
+                                    { '%', '+', '.', '2', 'd', '\0' };
+                                  SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                                }
+                                while (*p != '\0')
+                                  p++;
+#   else
+                                if (sizeof (DCHAR_T) == 1)
+                                  {
+                                    sprintf ((char *) p, "%+.2d", exponent);
+                                    while (*p != '\0')
+                                      p++;
+                                  }
+                                else
+                                  {
+                                    char expbuf[6 + 1];
+                                    const char *ep;
+                                    sprintf (expbuf, "%+.2d", exponent);
+                                    for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                      p++;
+                                  }
+#   endif
+                              }
+                            else if (dp->conversion == 'g' || dp->conversion == 'G')
+                              {
+                                if (precision == 0)
+                                  precision = 1;
+                                /* precision >= 1.  */
+
+                                if (arg == 0.0L)
+                                  /* The exponent is 0, >= -4, < precision.
+                                     Use fixed-point notation.  */
+                                  {
+                                    size_t ndigits = precision;
+                                    /* Number of trailing zeroes that have to be
+                                       dropped.  */
+                                    size_t nzeroes =
+                                      (flags & FLAG_ALT ? 0 : precision - 1);
+
+                                    --ndigits;
+                                    *p++ = '0';
+                                    if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        while (ndigits > nzeroes)
+                                          {
+                                            --ndigits;
+                                            *p++ = '0';
+                                          }
+                                      }
+                                  }
+                                else
+                                  {
+                                    /* arg > 0.0L.  */
+                                    int exponent;
+                                    int adjusted;
+                                    char *digits;
+                                    size_t ndigits;
+                                    size_t nzeroes;
+
+                                    exponent = floorlog10l (arg);
+                                    adjusted = 0;
+                                    for (;;)
+                                      {
+                                        digits =
+                                          scale10_round_decimal_long_double (arg,
+                                                                             (int)(precision - 1) - exponent);
+                                        if (digits == NULL)
+                                          {
+                                            END_LONG_DOUBLE_ROUNDING ();
+                                            goto out_of_memory;
+                                          }
+                                        ndigits = strlen (digits);
+
+                                        if (ndigits == precision)
+                                          break;
+                                        if (ndigits < precision - 1
+                                            || ndigits > precision + 1)
+                                          /* The exponent was not guessed
+                                             precisely enough.  */
+                                          abort ();
+                                        if (adjusted)
+                                          /* None of two values of exponent is
+                                             the right one.  Prevent an endless
+                                             loop.  */
+                                          abort ();
+                                        free (digits);
+                                        if (ndigits < precision)
+                                          exponent -= 1;
+                                        else
+                                          exponent += 1;
+                                        adjusted = 1;
+                                      }
+                                    /* Here ndigits = precision.  */
+                                    if (is_borderline (digits, precision - 1))
+                                      {
+                                        /* Maybe the exponent guess was too high
+                                           and a smaller exponent can be reached
+                                           by turning a 10...0 into 9...9x.  */
+                                        char *digits2 =
+                                          scale10_round_decimal_long_double (arg,
+                                                                             (int)(precision - 1) - exponent + 1);
+                                        if (digits2 == NULL)
+                                          {
+                                            free (digits);
+                                            END_LONG_DOUBLE_ROUNDING ();
+                                            goto out_of_memory;
+                                          }
+                                        if (strlen (digits2) == precision)
+                                          {
+                                            free (digits);
+                                            digits = digits2;
+                                            exponent -= 1;
+                                          }
+                                        else
+                                          free (digits2);
+                                      }
+                                    /* Here ndigits = precision.  */
+
+                                    /* Determine the number of trailing zeroes
+                                       that have to be dropped.  */
+                                    nzeroes = 0;
+                                    if ((flags & FLAG_ALT) == 0)
+                                      while (nzeroes < ndigits
+                                             && digits[nzeroes] == '0')
+                                        nzeroes++;
+
+                                    /* The exponent is now determined.  */
+                                    if (exponent >= -4
+                                        && exponent < (long)precision)
+                                      {
+                                        /* Fixed-point notation:
+                                           max(exponent,0)+1 digits, then the
+                                           decimal point, then the remaining
+                                           digits without trailing zeroes.  */
+                                        if (exponent >= 0)
+                                          {
+                                            size_t ecount = exponent + 1;
+                                            /* Note: count <= precision = ndigits.  */
+                                            for (; ecount > 0; ecount--)
+                                              *p++ = digits[--ndigits];
+                                            if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                              {
+                                                *p++ = decimal_point_char ();
+                                                while (ndigits > nzeroes)
+                                                  {
+                                                    --ndigits;
+                                                    *p++ = digits[ndigits];
+                                                  }
+                                              }
+                                          }
+                                        else
+                                          {
+                                            size_t ecount = -exponent - 1;
+                                            *p++ = '0';
+                                            *p++ = decimal_point_char ();
+                                            for (; ecount > 0; ecount--)
+                                              *p++ = '0';
+                                            while (ndigits > nzeroes)
+                                              {
+                                                --ndigits;
+                                                *p++ = digits[ndigits];
+                                              }
+                                          }
+                                      }
+                                    else
+                                      {
+                                        /* Exponential notation.  */
+                                        *p++ = digits[--ndigits];
+                                        if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                          {
+                                            *p++ = decimal_point_char ();
+                                            while (ndigits > nzeroes)
+                                              {
+                                                --ndigits;
+                                                *p++ = digits[ndigits];
+                                              }
+                                          }
+                                        *p++ = dp->conversion - 'G' + 'E'; /* 'e' or 'E' */
+#   if WIDE_CHAR_VERSION
+                                        {
+                                          static const wchar_t decimal_format[] =
+                                            { '%', '+', '.', '2', 'd', '\0' };
+                                          SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                                        }
+                                        while (*p != '\0')
+                                          p++;
+#   else
+                                        if (sizeof (DCHAR_T) == 1)
+                                          {
+                                            sprintf ((char *) p, "%+.2d", exponent);
+                                            while (*p != '\0')
+                                              p++;
+                                          }
+                                        else
+                                          {
+                                            char expbuf[6 + 1];
+                                            const char *ep;
+                                            sprintf (expbuf, "%+.2d", exponent);
+                                            for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                              p++;
+                                          }
+#   endif
+                                      }
+
+                                    free (digits);
+                                  }
+                              }
+                            else
+                              abort ();
+#  else
+                            /* arg is finite.  */
+                            if (!(arg == 0.0L))
+                              abort ();
+
+                            pad_ptr = p;
+
+                            if (dp->conversion == 'f' || dp->conversion == 'F')
+                              {
+                                *p++ = '0';
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > 0; precision--)
+                                      *p++ = '0';
+                                  }
+                              }
+                            else if (dp->conversion == 'e' || dp->conversion == 'E')
+                              {
+                                *p++ = '0';
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > 0; precision--)
+                                      *p++ = '0';
+                                  }
+                                *p++ = dp->conversion; /* 'e' or 'E' */
+                                *p++ = '+';
+                                *p++ = '0';
+                                *p++ = '0';
+                              }
+                            else if (dp->conversion == 'g' || dp->conversion == 'G')
+                              {
+                                *p++ = '0';
+                                if (flags & FLAG_ALT)
+                                  {
+                                    size_t ndigits =
+                                      (precision > 0 ? precision - 1 : 0);
+                                    *p++ = decimal_point_char ();
+                                    for (; ndigits > 0; --ndigits)
+                                      *p++ = '0';
+                                  }
+                              }
+                            else if (dp->conversion == 'a' || dp->conversion == 'A')
+                              {
+                                *p++ = '0';
+                                *p++ = dp->conversion - 'A' + 'X';
+                                pad_ptr = p;
+                                *p++ = '0';
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > 0; precision--)
+                                      *p++ = '0';
+                                  }
+                                *p++ = dp->conversion - 'A' + 'P';
+                                *p++ = '+';
+                                *p++ = '0';
+                              }
+                            else
+                              abort ();
+#  endif
+                          }
+
+                        END_LONG_DOUBLE_ROUNDING ();
+                      }
+                  }
+#  if NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE
+                else
+#  endif
+# endif
+# if NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE
+                  {
+                    double arg = a.arg[dp->arg_index].a.a_double;
+
+                    if (isnand (arg))
+                      {
+                        if (dp->conversion >= 'A' && dp->conversion <= 'Z')
+                          {
+                            *p++ = 'N'; *p++ = 'A'; *p++ = 'N';
+                          }
+                        else
+                          {
+                            *p++ = 'n'; *p++ = 'a'; *p++ = 'n';
+                          }
+                      }
+                    else
+                      {
+                        int sign = 0;
+
+                        if (signbit (arg)) /* arg < 0.0 or negative zero */
+                          {
+                            sign = -1;
+                            arg = -arg;
+                          }
+
+                        if (sign < 0)
+                          *p++ = '-';
+                        else if (flags & FLAG_SHOWSIGN)
+                          *p++ = '+';
+                        else if (flags & FLAG_SPACE)
+                          *p++ = ' ';
+
+                        if (arg > 0.0 && arg + arg == arg)
+                          {
+                            if (dp->conversion >= 'A' && dp->conversion <= 'Z')
+                              {
+                                *p++ = 'I'; *p++ = 'N'; *p++ = 'F';
+                              }
+                            else
+                              {
+                                *p++ = 'i'; *p++ = 'n'; *p++ = 'f';
+                              }
+                          }
+                        else
+                          {
+#  if NEED_PRINTF_DOUBLE
+                            pad_ptr = p;
+
+                            if (dp->conversion == 'f' || dp->conversion == 'F')
+                              {
+                                char *digits;
+                                size_t ndigits;
+
+                                digits =
+                                  scale10_round_decimal_double (arg, precision);
+                                if (digits == NULL)
+                                  goto out_of_memory;
+                                ndigits = strlen (digits);
+
+                                if (ndigits > precision)
+                                  do
+                                    {
+                                      --ndigits;
+                                      *p++ = digits[ndigits];
+                                    }
+                                  while (ndigits > precision);
+                                else
+                                  *p++ = '0';
+                                /* Here ndigits <= precision.  */
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > ndigits; precision--)
+                                      *p++ = '0';
+                                    while (ndigits > 0)
+                                      {
+                                        --ndigits;
+                                        *p++ = digits[ndigits];
+                                      }
+                                  }
+
+                                free (digits);
+                              }
+                            else if (dp->conversion == 'e' || dp->conversion == 'E')
+                              {
+                                int exponent;
+
+                                if (arg == 0.0)
+                                  {
+                                    exponent = 0;
+                                    *p++ = '0';
+                                    if ((flags & FLAG_ALT) || precision > 0)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        for (; precision > 0; precision--)
+                                          *p++ = '0';
+                                      }
+                                  }
+                                else
+                                  {
+                                    /* arg > 0.0.  */
+                                    int adjusted;
+                                    char *digits;
+                                    size_t ndigits;
+
+                                    exponent = floorlog10 (arg);
+                                    adjusted = 0;
+                                    for (;;)
+                                      {
+                                        digits =
+                                          scale10_round_decimal_double (arg,
+                                                                        (int)precision - exponent);
+                                        if (digits == NULL)
+                                          goto out_of_memory;
+                                        ndigits = strlen (digits);
+
+                                        if (ndigits == precision + 1)
+                                          break;
+                                        if (ndigits < precision
+                                            || ndigits > precision + 2)
+                                          /* The exponent was not guessed
+                                             precisely enough.  */
+                                          abort ();
+                                        if (adjusted)
+                                          /* None of two values of exponent is
+                                             the right one.  Prevent an endless
+                                             loop.  */
+                                          abort ();
+                                        free (digits);
+                                        if (ndigits == precision)
+                                          exponent -= 1;
+                                        else
+                                          exponent += 1;
+                                        adjusted = 1;
+                                      }
+                                    /* Here ndigits = precision+1.  */
+                                    if (is_borderline (digits, precision))
+                                      {
+                                        /* Maybe the exponent guess was too high
+                                           and a smaller exponent can be reached
+                                           by turning a 10...0 into 9...9x.  */
+                                        char *digits2 =
+                                          scale10_round_decimal_double (arg,
+                                                                        (int)precision - exponent + 1);
+                                        if (digits2 == NULL)
+                                          {
+                                            free (digits);
+                                            goto out_of_memory;
+                                          }
+                                        if (strlen (digits2) == precision + 1)
+                                          {
+                                            free (digits);
+                                            digits = digits2;
+                                            exponent -= 1;
+                                          }
+                                        else
+                                          free (digits2);
+                                      }
+                                    /* Here ndigits = precision+1.  */
+
+                                    *p++ = digits[--ndigits];
+                                    if ((flags & FLAG_ALT) || precision > 0)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        while (ndigits > 0)
+                                          {
+                                            --ndigits;
+                                            *p++ = digits[ndigits];
+                                          }
+                                      }
+
+                                    free (digits);
+                                  }
+
+                                *p++ = dp->conversion; /* 'e' or 'E' */
+#   if WIDE_CHAR_VERSION
+                                {
+                                  static const wchar_t decimal_format[] =
+                                    /* Produce the same number of exponent digits
+                                       as the native printf implementation.  */
+#    if defined _WIN32 && ! defined __CYGWIN__
+                                    { '%', '+', '.', '3', 'd', '\0' };
+#    else
+                                    { '%', '+', '.', '2', 'd', '\0' };
+#    endif
+                                  SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                                }
+                                while (*p != '\0')
+                                  p++;
+#   else
+                                {
+                                  static const char decimal_format[] =
+                                    /* Produce the same number of exponent digits
+                                       as the native printf implementation.  */
+#    if defined _WIN32 && ! defined __CYGWIN__
+                                    "%+.3d";
+#    else
+                                    "%+.2d";
+#    endif
+                                  if (sizeof (DCHAR_T) == 1)
+                                    {
+                                      sprintf ((char *) p, decimal_format, exponent);
+                                      while (*p != '\0')
+                                        p++;
+                                    }
+                                  else
+                                    {
+                                      char expbuf[6 + 1];
+                                      const char *ep;
+                                      sprintf (expbuf, decimal_format, exponent);
+                                      for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                        p++;
+                                    }
+                                }
+#   endif
+                              }
+                            else if (dp->conversion == 'g' || dp->conversion == 'G')
+                              {
+                                if (precision == 0)
+                                  precision = 1;
+                                /* precision >= 1.  */
+
+                                if (arg == 0.0)
+                                  /* The exponent is 0, >= -4, < precision.
+                                     Use fixed-point notation.  */
+                                  {
+                                    size_t ndigits = precision;
+                                    /* Number of trailing zeroes that have to be
+                                       dropped.  */
+                                    size_t nzeroes =
+                                      (flags & FLAG_ALT ? 0 : precision - 1);
+
+                                    --ndigits;
+                                    *p++ = '0';
+                                    if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                      {
+                                        *p++ = decimal_point_char ();
+                                        while (ndigits > nzeroes)
+                                          {
+                                            --ndigits;
+                                            *p++ = '0';
+                                          }
+                                      }
+                                  }
+                                else
+                                  {
+                                    /* arg > 0.0.  */
+                                    int exponent;
+                                    int adjusted;
+                                    char *digits;
+                                    size_t ndigits;
+                                    size_t nzeroes;
+
+                                    exponent = floorlog10 (arg);
+                                    adjusted = 0;
+                                    for (;;)
+                                      {
+                                        digits =
+                                          scale10_round_decimal_double (arg,
+                                                                        (int)(precision - 1) - exponent);
+                                        if (digits == NULL)
+                                          goto out_of_memory;
+                                        ndigits = strlen (digits);
+
+                                        if (ndigits == precision)
+                                          break;
+                                        if (ndigits < precision - 1
+                                            || ndigits > precision + 1)
+                                          /* The exponent was not guessed
+                                             precisely enough.  */
+                                          abort ();
+                                        if (adjusted)
+                                          /* None of two values of exponent is
+                                             the right one.  Prevent an endless
+                                             loop.  */
+                                          abort ();
+                                        free (digits);
+                                        if (ndigits < precision)
+                                          exponent -= 1;
+                                        else
+                                          exponent += 1;
+                                        adjusted = 1;
+                                      }
+                                    /* Here ndigits = precision.  */
+                                    if (is_borderline (digits, precision - 1))
+                                      {
+                                        /* Maybe the exponent guess was too high
+                                           and a smaller exponent can be reached
+                                           by turning a 10...0 into 9...9x.  */
+                                        char *digits2 =
+                                          scale10_round_decimal_double (arg,
+                                                                        (int)(precision - 1) - exponent + 1);
+                                        if (digits2 == NULL)
+                                          {
+                                            free (digits);
+                                            goto out_of_memory;
+                                          }
+                                        if (strlen (digits2) == precision)
+                                          {
+                                            free (digits);
+                                            digits = digits2;
+                                            exponent -= 1;
+                                          }
+                                        else
+                                          free (digits2);
+                                      }
+                                    /* Here ndigits = precision.  */
+
+                                    /* Determine the number of trailing zeroes
+                                       that have to be dropped.  */
+                                    nzeroes = 0;
+                                    if ((flags & FLAG_ALT) == 0)
+                                      while (nzeroes < ndigits
+                                             && digits[nzeroes] == '0')
+                                        nzeroes++;
+
+                                    /* The exponent is now determined.  */
+                                    if (exponent >= -4
+                                        && exponent < (long)precision)
+                                      {
+                                        /* Fixed-point notation:
+                                           max(exponent,0)+1 digits, then the
+                                           decimal point, then the remaining
+                                           digits without trailing zeroes.  */
+                                        if (exponent >= 0)
+                                          {
+                                            size_t ecount = exponent + 1;
+                                            /* Note: ecount <= precision = ndigits.  */
+                                            for (; ecount > 0; ecount--)
+                                              *p++ = digits[--ndigits];
+                                            if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                              {
+                                                *p++ = decimal_point_char ();
+                                                while (ndigits > nzeroes)
+                                                  {
+                                                    --ndigits;
+                                                    *p++ = digits[ndigits];
+                                                  }
+                                              }
+                                          }
+                                        else
+                                          {
+                                            size_t ecount = -exponent - 1;
+                                            *p++ = '0';
+                                            *p++ = decimal_point_char ();
+                                            for (; ecount > 0; ecount--)
+                                              *p++ = '0';
+                                            while (ndigits > nzeroes)
+                                              {
+                                                --ndigits;
+                                                *p++ = digits[ndigits];
+                                              }
+                                          }
+                                      }
+                                    else
+                                      {
+                                        /* Exponential notation.  */
+                                        *p++ = digits[--ndigits];
+                                        if ((flags & FLAG_ALT) || ndigits > nzeroes)
+                                          {
+                                            *p++ = decimal_point_char ();
+                                            while (ndigits > nzeroes)
+                                              {
+                                                --ndigits;
+                                                *p++ = digits[ndigits];
+                                              }
+                                          }
+                                        *p++ = dp->conversion - 'G' + 'E'; /* 'e' or 'E' */
+#   if WIDE_CHAR_VERSION
+                                        {
+                                          static const wchar_t decimal_format[] =
+                                            /* Produce the same number of exponent digits
+                                               as the native printf implementation.  */
+#    if defined _WIN32 && ! defined __CYGWIN__
+                                            { '%', '+', '.', '3', 'd', '\0' };
+#    else
+                                            { '%', '+', '.', '2', 'd', '\0' };
+#    endif
+                                          SNPRINTF (p, 6 + 1, decimal_format, exponent);
+                                        }
+                                        while (*p != '\0')
+                                          p++;
+#   else
+                                        {
+                                          static const char decimal_format[] =
+                                            /* Produce the same number of exponent digits
+                                               as the native printf implementation.  */
+#    if defined _WIN32 && ! defined __CYGWIN__
+                                            "%+.3d";
+#    else
+                                            "%+.2d";
+#    endif
+                                          if (sizeof (DCHAR_T) == 1)
+                                            {
+                                              sprintf ((char *) p, decimal_format, exponent);
+                                              while (*p != '\0')
+                                                p++;
+                                            }
+                                          else
+                                            {
+                                              char expbuf[6 + 1];
+                                              const char *ep;
+                                              sprintf (expbuf, decimal_format, exponent);
+                                              for (ep = expbuf; (*p = *ep) != '\0'; ep++)
+                                                p++;
+                                            }
+                                        }
+#   endif
+                                      }
+
+                                    free (digits);
+                                  }
+                              }
+                            else
+                              abort ();
+#  else
+                            /* arg is finite.  */
+                            if (!(arg == 0.0))
+                              abort ();
+
+                            pad_ptr = p;
+
+                            if (dp->conversion == 'f' || dp->conversion == 'F')
+                              {
+                                *p++ = '0';
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > 0; precision--)
+                                      *p++ = '0';
+                                  }
+                              }
+                            else if (dp->conversion == 'e' || dp->conversion == 'E')
+                              {
+                                *p++ = '0';
+                                if ((flags & FLAG_ALT) || precision > 0)
+                                  {
+                                    *p++ = decimal_point_char ();
+                                    for (; precision > 0; precision--)
+                                      *p++ = '0';
+                                  }
+                                *p++ = dp->conversion; /* 'e' or 'E' */
+                                *p++ = '+';
+                                /* Produce the same number of exponent digits as
+                                   the native printf implementation.  */
+#   if defined _WIN32 && ! defined __CYGWIN__
+                                *p++ = '0';
+#   endif
+                                *p++ = '0';
+                                *p++ = '0';
+                              }
+                            else if (dp->conversion == 'g' || dp->conversion == 'G')
+                              {
+                                *p++ = '0';
+                                if (flags & FLAG_ALT)
+                                  {
+                                    size_t ndigits =
+                                      (precision > 0 ? precision - 1 : 0);
+                                    *p++ = decimal_point_char ();
+                                    for (; ndigits > 0; --ndigits)
+                                      *p++ = '0';
+                                  }
+                              }
+                            else
+                              abort ();
+#  endif
+                          }
+                      }
+                  }
+# endif
+
+                /* The generated string now extends from tmp to p, with the
+                   zero padding insertion point being at pad_ptr.  */
+                count = p - tmp;
+
+                if (count < width)
+                  {
+                    size_t pad = width - count;
+                    DCHAR_T *end = p + pad;
+
+                    if (flags & FLAG_LEFT)
+                      {
+                        /* Pad with spaces on the right.  */
+                        for (; pad > 0; pad--)
+                          *p++ = ' ';
+                      }
+                    else if ((flags & FLAG_ZERO) && pad_ptr != NULL)
+                      {
+                        /* Pad with zeroes.  */
+                        DCHAR_T *q = end;
+
+                        while (p > pad_ptr)
+                          *--q = *--p;
+                        for (; pad > 0; pad--)
+                          *p++ = '0';
+                      }
+                    else
+                      {
+                        /* Pad with spaces on the left.  */
+                        DCHAR_T *q = end;
+
+                        while (p > tmp)
+                          *--q = *--p;
+                        for (; pad > 0; pad--)
+                          *p++ = ' ';
+                      }
+
+                    p = end;
+                  }
+
+                count = p - tmp;
+
+                if (count >= tmp_length)
+                  /* tmp_length was incorrectly calculated - fix the
+                     code above!  */
+                  abort ();
+
+                /* Make room for the result.  */
+                if (count >= allocated - length)
+                  {
+                    size_t n = xsum (length, count);
+
+                    ENSURE_ALLOCATION (n);
+                  }
+
+                /* Append the result.  */
+                memcpy (result + length, tmp, count * sizeof (DCHAR_T));
+                if (tmp != tmpbuf)
+                  free (tmp);
+                length += count;
+              }
+#endif
+            else
+              {
+                arg_type type = a.arg[dp->arg_index].type;
+                int flags = dp->flags;
+#if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                int has_width;
+#endif
+#if !USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                size_t width;
+#endif
+#if !USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || NEED_PRINTF_UNBOUNDED_PRECISION
+                int has_precision;
+                size_t precision;
+#endif
+#if NEED_PRINTF_UNBOUNDED_PRECISION
+                int prec_ourselves;
+#else
+#               define prec_ourselves 0
+#endif
+#if NEED_PRINTF_FLAG_LEFTADJUST
+#               define pad_ourselves 1
+#elif !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                int pad_ourselves;
+#else
+#               define pad_ourselves 0
+#endif
+                TCHAR_T *fbp;
+                unsigned int prefix_count;
+                int prefixes[2] IF_LINT (= { 0 });
+                int orig_errno;
+#if !USE_SNPRINTF
+                size_t tmp_length;
+                TCHAR_T tmpbuf[700];
+                TCHAR_T *tmp;
+#endif
+
+#if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                has_width = 0;
+#endif
+#if !USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                width = 0;
+                if (dp->width_start != dp->width_end)
+                  {
+                    if (dp->width_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->width_arg_index].a.a_int;
+                        width = arg;
+                        if (arg < 0)
+                          {
+                            /* "A negative field width is taken as a '-' flag
+                                followed by a positive field width."  */
+                            flags |= FLAG_LEFT;
+                            width = -width;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->width_start;
+
+                        do
+                          width = xsum (xtimes (width, 10), *digitp++ - '0');
+                        while (digitp != dp->width_end);
+                      }
+#if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                    has_width = 1;
+#endif
+                  }
+#endif
+
+#if !USE_SNPRINTF || !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF || NEED_PRINTF_UNBOUNDED_PRECISION
+                has_precision = 0;
+                precision = 6;
+                if (dp->precision_start != dp->precision_end)
+                  {
+                    if (dp->precision_arg_index != ARG_NONE)
+                      {
+                        int arg;
+
+                        if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                          abort ();
+                        arg = a.arg[dp->precision_arg_index].a.a_int;
+                        /* "A negative precision is taken as if the precision
+                            were omitted."  */
+                        if (arg >= 0)
+                          {
+                            precision = arg;
+                            has_precision = 1;
+                          }
+                      }
+                    else
+                      {
+                        const FCHAR_T *digitp = dp->precision_start + 1;
+
+                        precision = 0;
+                        while (digitp != dp->precision_end)
+                          precision = xsum (xtimes (precision, 10), *digitp++ - '0');
+                        has_precision = 1;
+                      }
+                  }
+#endif
+
+                /* Decide whether to handle the precision ourselves.  */
+#if NEED_PRINTF_UNBOUNDED_PRECISION
+                switch (dp->conversion)
+                  {
+                  case 'd': case 'i': case 'u':
+                  case 'o':
+                  case 'x': case 'X': case 'p':
+                    prec_ourselves = has_precision && (precision > 0);
+                    break;
+                  default:
+                    prec_ourselves = 0;
+                    break;
+                  }
+#endif
+
+                /* Decide whether to perform the padding ourselves.  */
+#if !NEED_PRINTF_FLAG_LEFTADJUST && (!DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION)
+                switch (dp->conversion)
+                  {
+# if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO
+                  /* If we need conversion from TCHAR_T[] to DCHAR_T[], we need
+                     to perform the padding after this conversion.  Functions
+                     with unistdio extensions perform the padding based on
+                     character count rather than element count.  */
+                  case 'c': case 's':
+# endif
+# if NEED_PRINTF_FLAG_ZERO
+                  case 'f': case 'F': case 'e': case 'E': case 'g': case 'G':
+                  case 'a': case 'A':
+# endif
+                    pad_ourselves = 1;
+                    break;
+                  default:
+                    pad_ourselves = prec_ourselves;
+                    break;
+                  }
+#endif
+
+#if !USE_SNPRINTF
+                /* Allocate a temporary buffer of sufficient size for calling
+                   sprintf.  */
+                tmp_length =
+                  MAX_ROOM_NEEDED (&a, dp->arg_index, dp->conversion, type,
+                                   flags, width, has_precision, precision,
+                                   pad_ourselves);
+
+                if (tmp_length <= sizeof (tmpbuf) / sizeof (TCHAR_T))
+                  tmp = tmpbuf;
+                else
+                  {
+                    size_t tmp_memsize = xtimes (tmp_length, sizeof (TCHAR_T));
+
+                    if (size_overflow_p (tmp_memsize))
+                      /* Overflow, would lead to out of memory.  */
+                      goto out_of_memory;
+                    tmp = (TCHAR_T *) malloc (tmp_memsize);
+                    if (tmp == NULL)
+                      /* Out of memory.  */
+                      goto out_of_memory;
+                  }
+#endif
+
+                /* Construct the format string for calling snprintf or
+                   sprintf.  */
+                fbp = buf;
+                *fbp++ = '%';
+#if NEED_PRINTF_FLAG_GROUPING
+                /* The underlying implementation doesn't support the ' flag.
+                   Produce no grouping characters in this case; this is
+                   acceptable because the grouping is locale dependent.  */
+#else
+                if (flags & FLAG_GROUP)
+                  *fbp++ = '\'';
+#endif
+                if (flags & FLAG_LEFT)
+                  *fbp++ = '-';
+                if (flags & FLAG_SHOWSIGN)
+                  *fbp++ = '+';
+                if (flags & FLAG_SPACE)
+                  *fbp++ = ' ';
+                if (flags & FLAG_ALT)
+                  *fbp++ = '#';
+#if __GLIBC__ >= 2 && !defined __UCLIBC__
+                if (flags & FLAG_LOCALIZED)
+                  *fbp++ = 'I';
+#endif
+                if (!pad_ourselves)
+                  {
+                    if (flags & FLAG_ZERO)
+                      *fbp++ = '0';
+                    if (dp->width_start != dp->width_end)
+                      {
+                        size_t n = dp->width_end - dp->width_start;
+                        /* The width specification is known to consist only
+                           of standard ASCII characters.  */
+                        if (sizeof (FCHAR_T) == sizeof (TCHAR_T))
+                          {
+                            memcpy (fbp, dp->width_start, n * sizeof (TCHAR_T));
+                            fbp += n;
+                          }
+                        else
+                          {
+                            const FCHAR_T *mp = dp->width_start;
+                            do
+                              *fbp++ = *mp++;
+                            while (--n > 0);
+                          }
+                      }
+                  }
+                if (!prec_ourselves)
+                  {
+                    if (dp->precision_start != dp->precision_end)
+                      {
+                        size_t n = dp->precision_end - dp->precision_start;
+                        /* The precision specification is known to consist only
+                           of standard ASCII characters.  */
+                        if (sizeof (FCHAR_T) == sizeof (TCHAR_T))
+                          {
+                            memcpy (fbp, dp->precision_start, n * sizeof (TCHAR_T));
+                            fbp += n;
+                          }
+                        else
+                          {
+                            const FCHAR_T *mp = dp->precision_start;
+                            do
+                              *fbp++ = *mp++;
+                            while (--n > 0);
+                          }
+                      }
+                  }
+
+                switch (type)
+                  {
+                  case TYPE_LONGLONGINT:
+                  case TYPE_ULONGLONGINT:
+#if defined _WIN32 && ! defined __CYGWIN__
+                    *fbp++ = 'I';
+                    *fbp++ = '6';
+                    *fbp++ = '4';
+                    break;
+#else
+                    *fbp++ = 'l';
+#endif
+                    FALLTHROUGH;
+                  case TYPE_LONGINT:
+                  case TYPE_ULONGINT:
+#if HAVE_WINT_T
+                  case TYPE_WIDE_CHAR:
+#endif
+#if HAVE_WCHAR_T
+                  case TYPE_WIDE_STRING:
+#endif
+                    *fbp++ = 'l';
+                    break;
+                  case TYPE_LONGDOUBLE:
+                    *fbp++ = 'L';
+                    break;
+                  default:
+                    break;
+                  }
+#if NEED_PRINTF_DIRECTIVE_F
+                if (dp->conversion == 'F')
+                  *fbp = 'f';
+                else
+#endif
+                  *fbp = dp->conversion;
+#if USE_SNPRINTF
+# if ((HAVE_SNPRINTF_RETVAL_C99 && HAVE_SNPRINTF_TRUNCATION_C99)            \
+      || ((__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3))       \
+          && !defined __UCLIBC__)                                           \
+      || (defined __APPLE__ && defined __MACH__)                            \
+      || defined __ANDROID__                                                \
+      || (defined _WIN32 && ! defined __CYGWIN__))
+                /* On systems where we know that snprintf's return value
+                   conforms to ISO C 99 (HAVE_SNPRINTF_RETVAL_C99) and that
+                   snprintf always produces NUL-terminated strings
+                   (HAVE_SNPRINTF_TRUNCATION_C99), it is possible to avoid
+                   using %n.  And it is desirable to do so, because more and
+                   more platforms no longer support %n, for "security reasons".
+                   In particular, the following platforms:
+                     - On glibc2 systems from 2004-10-18 or newer, the use of
+                       %n in format strings in writable memory may crash the
+                       program (if compiled with _FORTIFY_SOURCE=2).
+                     - On Mac OS X 10.13 or newer, the use of %n in format
+                       strings in writable memory by default crashes the
+                       program.
+                     - On Android, starting on 2018-03-07, the use of %n in
+                       format strings produces a fatal error (see
+                       <https://android.googlesource.com/platform/bionic/+/41398d03b7e8e0dfb951660ae713e682e9fc0336>).
+                   On these platforms, HAVE_SNPRINTF_RETVAL_C99 and
+                   HAVE_SNPRINTF_TRUNCATION_C99 are 1. We have listed them
+                   explicitly in the condition above, in case of cross-
+                   compilation (just to be sure).  */
+                /* On native Windows systems (such as mingw), we can avoid using
+                   %n because:
+                     - Although the gl_SNPRINTF_TRUNCATION_C99 test fails,
+                       snprintf does not write more than the specified number
+                       of bytes. (snprintf (buf, 3, "%d %d", 4567, 89) writes
+                       '4', '5', '6' into buf, not '4', '5', '\0'.)
+                     - Although the gl_SNPRINTF_RETVAL_C99 test fails, snprintf
+                       allows us to recognize the case of an insufficient
+                       buffer size: it returns -1 in this case.
+                   On native Windows systems (such as mingw) where the OS is
+                   Windows Vista, the use of %n in format strings by default
+                   crashes the program. See
+                     <https://gcc.gnu.org/ml/gcc/2007-06/msg00122.html> and
+                     <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/set-printf-count-output>
+                   So we should avoid %n in this situation.  */
+                fbp[1] = '\0';
+# else           /* AIX <= 5.1, HP-UX, IRIX, OSF/1, Solaris <= 9, BeOS */
+                fbp[1] = '%';
+                fbp[2] = 'n';
+                fbp[3] = '\0';
+# endif
+#else
+                fbp[1] = '\0';
+#endif
+
+                /* Construct the arguments for calling snprintf or sprintf.  */
+                prefix_count = 0;
+                if (!pad_ourselves && dp->width_arg_index != ARG_NONE)
+                  {
+                    if (!(a.arg[dp->width_arg_index].type == TYPE_INT))
+                      abort ();
+                    prefixes[prefix_count++] = a.arg[dp->width_arg_index].a.a_int;
+                  }
+                if (!prec_ourselves && dp->precision_arg_index != ARG_NONE)
+                  {
+                    if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))
+                      abort ();
+                    prefixes[prefix_count++] = a.arg[dp->precision_arg_index].a.a_int;
+                  }
+
+#if USE_SNPRINTF
+                /* The SNPRINTF result is appended after result[0..length].
+                   The latter is an array of DCHAR_T; SNPRINTF appends an
+                   array of TCHAR_T to it.  This is possible because
+                   sizeof (TCHAR_T) divides sizeof (DCHAR_T) and
+                   alignof (TCHAR_T) <= alignof (DCHAR_T).  */
+# define TCHARS_PER_DCHAR (sizeof (DCHAR_T) / sizeof (TCHAR_T))
+                /* Ensure that maxlen below will be >= 2.  Needed on BeOS,
+                   where an snprintf() with maxlen==1 acts like sprintf().  */
+                ENSURE_ALLOCATION (xsum (length,
+                                         (2 + TCHARS_PER_DCHAR - 1)
+                                         / TCHARS_PER_DCHAR));
+                /* Prepare checking whether snprintf returns the count
+                   via %n.  */
+                *(TCHAR_T *) (result + length) = '\0';
+#endif
+
+                orig_errno = errno;
+
+                for (;;)
+                  {
+                    int count = -1;
+
+#if USE_SNPRINTF
+                    int retcount = 0;
+                    size_t maxlen = allocated - length;
+                    /* SNPRINTF can fail if its second argument is
+                       > INT_MAX.  */
+                    if (maxlen > INT_MAX / TCHARS_PER_DCHAR)
+                      maxlen = INT_MAX / TCHARS_PER_DCHAR;
+                    maxlen = maxlen * TCHARS_PER_DCHAR;
+# define SNPRINTF_BUF(arg) \
+                    switch (prefix_count)                                   \
+                      {                                                     \
+                      case 0:                                               \
+                        retcount = SNPRINTF ((TCHAR_T *) (result + length), \
+                                             maxlen, buf,                   \
+                                             arg, &count);                  \
+                        break;                                              \
+                      case 1:                                               \
+                        retcount = SNPRINTF ((TCHAR_T *) (result + length), \
+                                             maxlen, buf,                   \
+                                             prefixes[0], arg, &count);     \
+                        break;                                              \
+                      case 2:                                               \
+                        retcount = SNPRINTF ((TCHAR_T *) (result + length), \
+                                             maxlen, buf,                   \
+                                             prefixes[0], prefixes[1], arg, \
+                                             &count);                       \
+                        break;                                              \
+                      default:                                              \
+                        abort ();                                           \
+                      }
+#else
+# define SNPRINTF_BUF(arg) \
+                    switch (prefix_count)                                   \
+                      {                                                     \
+                      case 0:                                               \
+                        count = sprintf (tmp, buf, arg);                    \
+                        break;                                              \
+                      case 1:                                               \
+                        count = sprintf (tmp, buf, prefixes[0], arg);       \
+                        break;                                              \
+                      case 2:                                               \
+                        count = sprintf (tmp, buf, prefixes[0], prefixes[1],\
+                                         arg);                              \
+                        break;                                              \
+                      default:                                              \
+                        abort ();                                           \
+                      }
+#endif
+
+                    errno = 0;
+                    switch (type)
+                      {
+                      case TYPE_SCHAR:
+                        {
+                          int arg = a.arg[dp->arg_index].a.a_schar;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_UCHAR:
+                        {
+                          unsigned int arg = a.arg[dp->arg_index].a.a_uchar;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_SHORT:
+                        {
+                          int arg = a.arg[dp->arg_index].a.a_short;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_USHORT:
+                        {
+                          unsigned int arg = a.arg[dp->arg_index].a.a_ushort;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_INT:
+                        {
+                          int arg = a.arg[dp->arg_index].a.a_int;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_UINT:
+                        {
+                          unsigned int arg = a.arg[dp->arg_index].a.a_uint;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_LONGINT:
+                        {
+                          long int arg = a.arg[dp->arg_index].a.a_longint;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_ULONGINT:
+                        {
+                          unsigned long int arg = a.arg[dp->arg_index].a.a_ulongint;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_LONGLONGINT:
+                        {
+                          long long int arg = a.arg[dp->arg_index].a.a_longlongint;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_ULONGLONGINT:
+                        {
+                          unsigned long long int arg = a.arg[dp->arg_index].a.a_ulonglongint;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_DOUBLE:
+                        {
+                          double arg = a.arg[dp->arg_index].a.a_double;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_LONGDOUBLE:
+                        {
+                          long double arg = a.arg[dp->arg_index].a.a_longdouble;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      case TYPE_CHAR:
+                        {
+                          int arg = a.arg[dp->arg_index].a.a_char;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+#if HAVE_WINT_T
+                      case TYPE_WIDE_CHAR:
+                        {
+                          wint_t arg = a.arg[dp->arg_index].a.a_wide_char;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+#endif
+                      case TYPE_STRING:
+                        {
+                          const char *arg = a.arg[dp->arg_index].a.a_string;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+#if HAVE_WCHAR_T
+                      case TYPE_WIDE_STRING:
+                        {
+                          const wchar_t *arg = a.arg[dp->arg_index].a.a_wide_string;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+#endif
+                      case TYPE_POINTER:
+                        {
+                          void *arg = a.arg[dp->arg_index].a.a_pointer;
+                          SNPRINTF_BUF (arg);
+                        }
+                        break;
+                      default:
+                        abort ();
+                      }
+
+#if USE_SNPRINTF
+                    /* Portability: Not all implementations of snprintf()
+                       are ISO C 99 compliant.  Determine the number of
+                       bytes that snprintf() has produced or would have
+                       produced.  */
+                    if (count >= 0)
+                      {
+                        /* Verify that snprintf() has NUL-terminated its
+                           result.  */
+                        if ((unsigned int) count < maxlen
+                            && ((TCHAR_T *) (result + length)) [count] != '\0')
+                          abort ();
+                        /* Portability hack.  */
+                        if (retcount > count)
+                          count = retcount;
+                      }
+                    else
+                      {
+                        /* snprintf() doesn't understand the '%n'
+                           directive.  */
+                        if (fbp[1] != '\0')
+                          {
+                            /* Don't use the '%n' directive; instead, look
+                               at the snprintf() return value.  */
+                            fbp[1] = '\0';
+                            continue;
+                          }
+                        else
+                          {
+                            /* Look at the snprintf() return value.  */
+                            if (retcount < 0)
+                              {
+# if !HAVE_SNPRINTF_RETVAL_C99 || USE_MSVC__SNPRINTF
+                                /* HP-UX 10.20 snprintf() is doubly deficient:
+                                   It doesn't understand the '%n' directive,
+                                   *and* it returns -1 (rather than the length
+                                   that would have been required) when the
+                                   buffer is too small.
+                                   But a failure at this point can also come
+                                   from other reasons than a too small buffer,
+                                   such as an invalid wide string argument to
+                                   the %ls directive, or possibly an invalid
+                                   floating-point argument.  */
+                                size_t tmp_length =
+                                  MAX_ROOM_NEEDED (&a, dp->arg_index,
+                                                   dp->conversion, type, flags,
+                                                   width,
+                                                   has_precision,
+                                                   precision, pad_ourselves);
+
+                                if (maxlen < tmp_length)
+                                  {
+                                    /* Make more room.  But try to do through
+                                       this reallocation only once.  */
+                                    size_t bigger_need =
+                                      xsum (length,
+                                            xsum (tmp_length,
+                                                  TCHARS_PER_DCHAR - 1)
+                                            / TCHARS_PER_DCHAR);
+                                    /* And always grow proportionally.
+                                       (There may be several arguments, each
+                                       needing a little more room than the
+                                       previous one.)  */
+                                    size_t bigger_need2 =
+                                      xsum (xtimes (allocated, 2), 12);
+                                    if (bigger_need < bigger_need2)
+                                      bigger_need = bigger_need2;
+                                    ENSURE_ALLOCATION (bigger_need);
+                                    continue;
+                                  }
+# endif
+                              }
+                            else
+                              count = retcount;
+                          }
+                      }
+#endif
+
+                    /* Attempt to handle failure.  */
+                    if (count < 0)
+                      {
+                        /* SNPRINTF or sprintf failed.  Use the errno that it
+                           has set, if any.  */
+                        if (errno == 0)
+                          {
+                            if (dp->conversion == 'c' || dp->conversion == 's')
+                              errno = EILSEQ;
+                            else
+                              errno = EINVAL;
+                          }
+
+                        if (!(result == resultbuf || result == NULL))
+                          free (result);
+                        if (buf_malloced != NULL)
+                          free (buf_malloced);
+                        CLEANUP ();
+
+                        return NULL;
+                      }
+
+#if USE_SNPRINTF
+                    /* Handle overflow of the allocated buffer.
+                       If such an overflow occurs, a C99 compliant snprintf()
+                       returns a count >= maxlen.  However, a non-compliant
+                       snprintf() function returns only count = maxlen - 1.  To
+                       cover both cases, test whether count >= maxlen - 1.  */
+                    if ((unsigned int) count + 1 >= maxlen)
+                      {
+                        /* If maxlen already has attained its allowed maximum,
+                           allocating more memory will not increase maxlen.
+                           Instead of looping, bail out.  */
+                        if (maxlen == INT_MAX / TCHARS_PER_DCHAR)
+                          goto overflow;
+                        else
+                          {
+                            /* Need at least (count + 1) * sizeof (TCHAR_T)
+                               bytes.  (The +1 is for the trailing NUL.)
+                               But ask for (count + 2) * sizeof (TCHAR_T)
+                               bytes, so that in the next round, we likely get
+                                 maxlen > (unsigned int) count + 1
+                               and so we don't get here again.
+                               And allocate proportionally, to avoid looping
+                               eternally if snprintf() reports a too small
+                               count.  */
+                            size_t n =
+                              xmax (xsum (length,
+                                          ((unsigned int) count + 2
+                                           + TCHARS_PER_DCHAR - 1)
+                                          / TCHARS_PER_DCHAR),
+                                    xtimes (allocated, 2));
+
+                            ENSURE_ALLOCATION (n);
+                            continue;
+                          }
+                      }
+#endif
+
+#if NEED_PRINTF_UNBOUNDED_PRECISION
+                    if (prec_ourselves)
+                      {
+                        /* Handle the precision.  */
+                        TCHAR_T *prec_ptr =
+# if USE_SNPRINTF
+                          (TCHAR_T *) (result + length);
+# else
+                          tmp;
+# endif
+                        size_t prefix_count;
+                        size_t move;
+
+                        prefix_count = 0;
+                        /* Put the additional zeroes after the sign.  */
+                        if (count >= 1
+                            && (*prec_ptr == '-' || *prec_ptr == '+'
+                                || *prec_ptr == ' '))
+                          prefix_count = 1;
+                        /* Put the additional zeroes after the 0x prefix if
+                           (flags & FLAG_ALT) || (dp->conversion == 'p').  */
+                        else if (count >= 2
+                                 && prec_ptr[0] == '0'
+                                 && (prec_ptr[1] == 'x' || prec_ptr[1] == 'X'))
+                          prefix_count = 2;
+
+                        move = count - prefix_count;
+                        if (precision > move)
+                          {
+                            /* Insert zeroes.  */
+                            size_t insert = precision - move;
+                            TCHAR_T *prec_end;
+
+# if USE_SNPRINTF
+                            size_t n =
+                              xsum (length,
+                                    (count + insert + TCHARS_PER_DCHAR - 1)
+                                    / TCHARS_PER_DCHAR);
+                            length += (count + TCHARS_PER_DCHAR - 1) / TCHARS_PER_DCHAR;
+                            ENSURE_ALLOCATION (n);
+                            length -= (count + TCHARS_PER_DCHAR - 1) / TCHARS_PER_DCHAR;
+                            prec_ptr = (TCHAR_T *) (result + length);
+# endif
+
+                            prec_end = prec_ptr + count;
+                            prec_ptr += prefix_count;
+
+                            while (prec_end > prec_ptr)
+                              {
+                                prec_end--;
+                                prec_end[insert] = prec_end[0];
+                              }
+
+                            prec_end += insert;
+                            do
+                              *--prec_end = '0';
+                            while (prec_end > prec_ptr);
+
+                            count += insert;
+                          }
+                      }
+#endif
+
+#if !USE_SNPRINTF
+                    if (count >= tmp_length)
+                      /* tmp_length was incorrectly calculated - fix the
+                         code above!  */
+                      abort ();
+#endif
+
+#if !DCHAR_IS_TCHAR
+                    /* Convert from TCHAR_T[] to DCHAR_T[].  */
+                    if (dp->conversion == 'c' || dp->conversion == 's')
+                      {
+                        /* type = TYPE_CHAR or TYPE_WIDE_CHAR or TYPE_STRING
+                           TYPE_WIDE_STRING.
+                           The result string is not certainly ASCII.  */
+                        const TCHAR_T *tmpsrc;
+                        DCHAR_T *tmpdst;
+                        size_t tmpdst_len;
+                        /* This code assumes that TCHAR_T is 'char'.  */
+                        verify (sizeof (TCHAR_T) == 1);
+# if USE_SNPRINTF
+                        tmpsrc = (TCHAR_T *) (result + length);
+# else
+                        tmpsrc = tmp;
+# endif
+                        tmpdst =
+                          DCHAR_CONV_FROM_ENCODING (locale_charset (),
+                                                    iconveh_question_mark,
+                                                    tmpsrc, count,
+                                                    NULL,
+                                                    NULL, &tmpdst_len);
+                        if (tmpdst == NULL)
+                          {
+                            if (!(result == resultbuf || result == NULL))
+                              free (result);
+                            if (buf_malloced != NULL)
+                              free (buf_malloced);
+                            CLEANUP ();
+                            return NULL;
+                          }
+                        ENSURE_ALLOCATION_ELSE (xsum (length, tmpdst_len),
+                                                { free (tmpdst); goto out_of_memory; });
+                        DCHAR_CPY (result + length, tmpdst, tmpdst_len);
+                        free (tmpdst);
+                        count = tmpdst_len;
+                      }
+                    else
+                      {
+                        /* The result string is ASCII.
+                           Simple 1:1 conversion.  */
+# if USE_SNPRINTF
+                        /* If sizeof (DCHAR_T) == sizeof (TCHAR_T), it's a
+                           no-op conversion, in-place on the array starting
+                           at (result + length).  */
+                        if (sizeof (DCHAR_T) != sizeof (TCHAR_T))
+# endif
+                          {
+                            const TCHAR_T *tmpsrc;
+                            DCHAR_T *tmpdst;
+                            size_t n;
+
+# if USE_SNPRINTF
+                            if (result == resultbuf)
+                              {
+                                tmpsrc = (TCHAR_T *) (result + length);
+                                /* ENSURE_ALLOCATION will not move tmpsrc
+                                   (because it's part of resultbuf).  */
+                                ENSURE_ALLOCATION (xsum (length, count));
+                              }
+                            else
+                              {
+                                /* ENSURE_ALLOCATION will move the array
+                                   (because it uses realloc().  */
+                                ENSURE_ALLOCATION (xsum (length, count));
+                                tmpsrc = (TCHAR_T *) (result + length);
+                              }
+# else
+                            tmpsrc = tmp;
+                            ENSURE_ALLOCATION (xsum (length, count));
+# endif
+                            tmpdst = result + length;
+                            /* Copy backwards, because of overlapping.  */
+                            tmpsrc += count;
+                            tmpdst += count;
+                            for (n = count; n > 0; n--)
+                              *--tmpdst = *--tmpsrc;
+                          }
+                      }
+#endif
+
+#if DCHAR_IS_TCHAR && !USE_SNPRINTF
+                    /* Make room for the result.  */
+                    if (count > allocated - length)
+                      {
+                        /* Need at least count elements.  But allocate
+                           proportionally.  */
+                        size_t n =
+                          xmax (xsum (length, count), xtimes (allocated, 2));
+
+                        ENSURE_ALLOCATION (n);
+                      }
+#endif
+
+                    /* Here count <= allocated - length.  */
+
+                    /* Perform padding.  */
+#if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO || NEED_PRINTF_FLAG_LEFTADJUST || NEED_PRINTF_FLAG_ZERO || NEED_PRINTF_UNBOUNDED_PRECISION
+                    if (pad_ourselves && has_width)
+                      {
+                        size_t w;
+# if ENABLE_UNISTDIO
+                        /* Outside POSIX, it's preferable to compare the width
+                           against the number of _characters_ of the converted
+                           value.  */
+                        w = DCHAR_MBSNLEN (result + length, count);
+# else
+                        /* The width is compared against the number of _bytes_
+                           of the converted value, says POSIX.  */
+                        w = count;
+# endif
+                        if (w < width)
+                          {
+                            size_t pad = width - w;
+
+                            /* Make room for the result.  */
+                            if (xsum (count, pad) > allocated - length)
+                              {
+                                /* Need at least count + pad elements.  But
+                                   allocate proportionally.  */
+                                size_t n =
+                                  xmax (xsum3 (length, count, pad),
+                                        xtimes (allocated, 2));
+
+# if USE_SNPRINTF
+                                length += count;
+                                ENSURE_ALLOCATION (n);
+                                length -= count;
+# else
+                                ENSURE_ALLOCATION (n);
+# endif
+                              }
+                            /* Here count + pad <= allocated - length.  */
+
+                            {
+# if !DCHAR_IS_TCHAR || USE_SNPRINTF
+                              DCHAR_T * const rp = result + length;
+# else
+                              DCHAR_T * const rp = tmp;
+# endif
+                              DCHAR_T *p = rp + count;
+                              DCHAR_T *end = p + pad;
+                              DCHAR_T *pad_ptr;
+# if !DCHAR_IS_TCHAR || ENABLE_UNISTDIO
+                              if (dp->conversion == 'c'
+                                  || dp->conversion == 's')
+                                /* No zero-padding for string directives.  */
+                                pad_ptr = NULL;
+                              else
+# endif
+                                {
+                                  pad_ptr = (*rp == '-' ? rp + 1 : rp);
+                                  /* No zero-padding of "inf" and "nan".  */
+                                  if ((*pad_ptr >= 'A' && *pad_ptr <= 'Z')
+                                      || (*pad_ptr >= 'a' && *pad_ptr <= 'z'))
+                                    pad_ptr = NULL;
+                                }
+                              /* The generated string now extends from rp to p,
+                                 with the zero padding insertion point being at
+                                 pad_ptr.  */
+
+                              count = count + pad; /* = end - rp */
+
+                              if (flags & FLAG_LEFT)
+                                {
+                                  /* Pad with spaces on the right.  */
+                                  for (; pad > 0; pad--)
+                                    *p++ = ' ';
+                                }
+                              else if ((flags & FLAG_ZERO) && pad_ptr != NULL)
+                                {
+                                  /* Pad with zeroes.  */
+                                  DCHAR_T *q = end;
+
+                                  while (p > pad_ptr)
+                                    *--q = *--p;
+                                  for (; pad > 0; pad--)
+                                    *p++ = '0';
+                                }
+                              else
+                                {
+                                  /* Pad with spaces on the left.  */
+                                  DCHAR_T *q = end;
+
+                                  while (p > rp)
+                                    *--q = *--p;
+                                  for (; pad > 0; pad--)
+                                    *p++ = ' ';
+                                }
+                            }
+                          }
+                      }
+#endif
+
+                    /* Here still count <= allocated - length.  */
+
+#if !DCHAR_IS_TCHAR || USE_SNPRINTF
+                    /* The snprintf() result did fit.  */
+#else
+                    /* Append the sprintf() result.  */
+                    memcpy (result + length, tmp, count * sizeof (DCHAR_T));
+#endif
+#if !USE_SNPRINTF
+                    if (tmp != tmpbuf)
+                      free (tmp);
+#endif
+
+#if NEED_PRINTF_DIRECTIVE_F
+                    if (dp->conversion == 'F')
+                      {
+                        /* Convert the %f result to upper case for %F.  */
+                        DCHAR_T *rp = result + length;
+                        size_t rc;
+                        for (rc = count; rc > 0; rc--, rp++)
+                          if (*rp >= 'a' && *rp <= 'z')
+                            *rp = *rp - 'a' + 'A';
+                      }
+#endif
+
+                    length += count;
+                    break;
+                  }
+                errno = orig_errno;
+#undef pad_ourselves
+#undef prec_ourselves
+              }
+          }
+      }
+
+    /* Add the final NUL.  */
+    ENSURE_ALLOCATION (xsum (length, 1));
+    result[length] = '\0';
+
+    if (result != resultbuf && length + 1 < allocated)
+      {
+        /* Shrink the allocated memory if possible.  */
+        DCHAR_T *memory;
+
+        memory = (DCHAR_T *) realloc (result, (length + 1) * sizeof (DCHAR_T));
+        if (memory != NULL)
+          result = memory;
+      }
+
+    if (buf_malloced != NULL)
+      free (buf_malloced);
+    CLEANUP ();
+    *lengthp = length;
+    /* Note that we can produce a big string of a length > INT_MAX.  POSIX
+       says that snprintf() fails with errno = EOVERFLOW in this case, but
+       that's only because snprintf() returns an 'int'.  This function does
+       not have this limitation.  */
+    return result;
+
+#if USE_SNPRINTF
+  overflow:
+    if (!(result == resultbuf || result == NULL))
+      free (result);
+    if (buf_malloced != NULL)
+      free (buf_malloced);
+    CLEANUP ();
+    errno = EOVERFLOW;
+    return NULL;
+#endif
+
+  out_of_memory:
+    if (!(result == resultbuf || result == NULL))
+      free (result);
+    if (buf_malloced != NULL)
+      free (buf_malloced);
+  out_of_memory_1:
+    CLEANUP ();
+    errno = ENOMEM;
+    return NULL;
+  }
+}
+
+#undef MAX_ROOM_NEEDED
+#undef TCHARS_PER_DCHAR
+#undef SNPRINTF
+#undef USE_SNPRINTF
+#undef DCHAR_SET
+#undef DCHAR_CPY
+#undef PRINTF_PARSE
+#undef DIRECTIVES
+#undef DIRECTIVE
+#undef DCHAR_IS_TCHAR
+#undef TCHAR_T
+#undef DCHAR_T
+#undef FCHAR_T
+#undef VASNPRINTF
diff --git a/lib/intl/vasnprintf.h b/lib/intl/vasnprintf.h
new file mode 100644 (file)
index 0000000..e58bd40
--- /dev/null
@@ -0,0 +1,76 @@
+/* vsprintf with automatic memory allocation.
+   Copyright (C) 2002-2004, 2012 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _VASNPRINTF_H
+#define _VASNPRINTF_H
+
+/* Get va_list.  */
+#include <stdarg.h>
+
+/* Get size_t.  */
+#include <stddef.h>
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later.  */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
+#  define __attribute__(Spec) /* empty */
+# endif
+/* The __-protected variants of 'format' and 'printf' attributes
+   are accepted by gcc versions 2.6.4 (effectively 2.7) and later.  */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+#  define __format__ format
+#  define __printf__ printf
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Write formatted output to a string dynamically allocated with malloc().
+   You can pass a preallocated buffer for the result in RESULTBUF and its
+   size in *LENGTHP; otherwise you pass RESULTBUF = NULL.
+   If successful, return the address of the string (this may be = RESULTBUF
+   if no dynamic memory allocation was necessary) and set *LENGTHP to the
+   number of resulting bytes, excluding the trailing NUL.  Upon error, set
+   errno and return NULL.
+
+   When dynamic memory allocation occurs, the preallocated buffer is left
+   alone (with possibly modified contents).  This makes it possible to use
+   a statically allocated or stack-allocated buffer, like this:
+
+          char buf[100];
+          size_t len = sizeof (buf);
+          char *output = vasnprintf (buf, &len, format, args);
+          if (output == NULL)
+            ... error handling ...;
+          else
+            {
+              ... use the output string ...;
+              if (output != buf)
+                free (output);
+            }
+  */
+extern char * asnprintf (char *resultbuf, size_t *lengthp, const char *format, ...)
+       __attribute__ ((__format__ (__printf__, 3, 4)));
+extern char * vasnprintf (char *resultbuf, size_t *lengthp, const char *format, va_list args)
+       __attribute__ ((__format__ (__printf__, 3, 0)));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _VASNPRINTF_H */
diff --git a/lib/intl/vasnwprintf.h b/lib/intl/vasnwprintf.h
new file mode 100644 (file)
index 0000000..05ac021
--- /dev/null
@@ -0,0 +1,44 @@
+/* vswprintf with automatic memory allocation.
+   Copyright (C) 2002-2003 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _VASNWPRINTF_H
+#define _VASNWPRINTF_H
+
+/* Get va_list.  */
+#include <stdarg.h>
+
+/* Get wchar_t, size_t.  */
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Write formatted output to a string dynamically allocated with malloc().
+   You can pass a preallocated buffer for the result in RESULTBUF and its
+   size in *LENGTHP; otherwise you pass RESULTBUF = NULL.
+   If successful, return the address of the string (this may be = RESULTBUF
+   if no dynamic memory allocation was necessary) and set *LENGTHP to the
+   number of resulting bytes, excluding the trailing NUL.  Upon error, set
+   errno and return NULL.  */
+extern wchar_t * asnwprintf (wchar_t *resultbuf, size_t *lengthp, const wchar_t *format, ...);
+extern wchar_t * vasnwprintf (wchar_t *resultbuf, size_t *lengthp, const wchar_t *format, va_list args);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _VASNWPRINTF_H */
diff --git a/lib/intl/verify.h b/lib/intl/verify.h
new file mode 100644 (file)
index 0000000..42307c0
--- /dev/null
@@ -0,0 +1,336 @@
+/* Compile-time assert-like macros.
+
+   Copyright (C) 2005-2006, 2009-2022 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#ifndef _GL_VERIFY_H
+#define _GL_VERIFY_H
+
+
+/* Define _GL_HAVE__STATIC_ASSERT to 1 if _Static_assert (R, DIAGNOSTIC)
+   works as per C11.  This is supported by GCC 4.6.0+ and by clang 4+.
+
+   Define _GL_HAVE__STATIC_ASSERT1 to 1 if _Static_assert (R) works as
+   per C2x.  This is supported by GCC 9.1+.
+
+   Support compilers claiming conformance to the relevant standard,
+   and also support GCC when not pedantic.  If we were willing to slow
+   'configure' down we could also use it with other compilers, but
+   since this affects only the quality of diagnostics, why bother?  */
+#ifndef __cplusplus
+# if (201112L <= __STDC_VERSION__ \
+      || (!defined __STRICT_ANSI__ \
+          && (4 < __GNUC__ + (6 <= __GNUC_MINOR__) || 5 <= __clang_major__)))
+#  define _GL_HAVE__STATIC_ASSERT 1
+# endif
+# if (202000L <= __STDC_VERSION__ \
+      || (!defined __STRICT_ANSI__ && 9 <= __GNUC__))
+#  define _GL_HAVE__STATIC_ASSERT1 1
+# endif
+#endif
+
+/* FreeBSD 9.1 <sys/cdefs.h>, included by <stddef.h> and lots of other
+   system headers, defines a conflicting _Static_assert that is no
+   better than ours; override it.  */
+#ifndef _GL_HAVE__STATIC_ASSERT
+# include <stddef.h>
+# undef _Static_assert
+#endif
+
+/* Each of these macros verifies that its argument R is nonzero.  To
+   be portable, R should be an integer constant expression.  Unlike
+   assert (R), there is no run-time overhead.
+
+   If _Static_assert works, verify (R) uses it directly.  Similarly,
+   _GL_VERIFY_TRUE works by packaging a _Static_assert inside a struct
+   that is an operand of sizeof.
+
+   The code below uses several ideas for C++ compilers, and for C
+   compilers that do not support _Static_assert:
+
+   * The first step is ((R) ? 1 : -1).  Given an expression R, of
+     integral or boolean or floating-point type, this yields an
+     expression of integral type, whose value is later verified to be
+     constant and nonnegative.
+
+   * Next this expression W is wrapped in a type
+     struct _gl_verify_type {
+       unsigned int _gl_verify_error_if_negative: W;
+     }.
+     If W is negative, this yields a compile-time error.  No compiler can
+     deal with a bit-field of negative size.
+
+     One might think that an array size check would have the same
+     effect, that is, that the type struct { unsigned int dummy[W]; }
+     would work as well.  However, inside a function, some compilers
+     (such as C++ compilers and GNU C) allow local parameters and
+     variables inside array size expressions.  With these compilers,
+     an array size check would not properly diagnose this misuse of
+     the verify macro:
+
+       void function (int n) { verify (n < 0); }
+
+   * For the verify macro, the struct _gl_verify_type will need to
+     somehow be embedded into a declaration.  To be portable, this
+     declaration must declare an object, a constant, a function, or a
+     typedef name.  If the declared entity uses the type directly,
+     such as in
+
+       struct dummy {...};
+       typedef struct {...} dummy;
+       extern struct {...} *dummy;
+       extern void dummy (struct {...} *);
+       extern struct {...} *dummy (void);
+
+     two uses of the verify macro would yield colliding declarations
+     if the entity names are not disambiguated.  A workaround is to
+     attach the current line number to the entity name:
+
+       #define _GL_CONCAT0(x, y) x##y
+       #define _GL_CONCAT(x, y) _GL_CONCAT0 (x, y)
+       extern struct {...} * _GL_CONCAT (dummy, __LINE__);
+
+     But this has the problem that two invocations of verify from
+     within the same macro would collide, since the __LINE__ value
+     would be the same for both invocations.  (The GCC __COUNTER__
+     macro solves this problem, but is not portable.)
+
+     A solution is to use the sizeof operator.  It yields a number,
+     getting rid of the identity of the type.  Declarations like
+
+       extern int dummy [sizeof (struct {...})];
+       extern void dummy (int [sizeof (struct {...})]);
+       extern int (*dummy (void)) [sizeof (struct {...})];
+
+     can be repeated.
+
+   * Should the implementation use a named struct or an unnamed struct?
+     Which of the following alternatives can be used?
+
+       extern int dummy [sizeof (struct {...})];
+       extern int dummy [sizeof (struct _gl_verify_type {...})];
+       extern void dummy (int [sizeof (struct {...})]);
+       extern void dummy (int [sizeof (struct _gl_verify_type {...})]);
+       extern int (*dummy (void)) [sizeof (struct {...})];
+       extern int (*dummy (void)) [sizeof (struct _gl_verify_type {...})];
+
+     In the second and sixth case, the struct type is exported to the
+     outer scope; two such declarations therefore collide.  GCC warns
+     about the first, third, and fourth cases.  So the only remaining
+     possibility is the fifth case:
+
+       extern int (*dummy (void)) [sizeof (struct {...})];
+
+   * GCC warns about duplicate declarations of the dummy function if
+     -Wredundant-decls is used.  GCC 4.3 and later have a builtin
+     __COUNTER__ macro that can let us generate unique identifiers for
+     each dummy function, to suppress this warning.
+
+   * This implementation exploits the fact that older versions of GCC,
+     which do not support _Static_assert, also do not warn about the
+     last declaration mentioned above.
+
+   * GCC warns if -Wnested-externs is enabled and 'verify' is used
+     within a function body; but inside a function, you can always
+     arrange to use verify_expr instead.
+
+   * In C++, any struct definition inside sizeof is invalid.
+     Use a template type to work around the problem.  */
+
+/* Concatenate two preprocessor tokens.  */
+#define _GL_CONCAT(x, y) _GL_CONCAT0 (x, y)
+#define _GL_CONCAT0(x, y) x##y
+
+/* _GL_COUNTER is an integer, preferably one that changes each time we
+   use it.  Use __COUNTER__ if it works, falling back on __LINE__
+   otherwise.  __LINE__ isn't perfect, but it's better than a
+   constant.  */
+#if defined __COUNTER__ && __COUNTER__ != __COUNTER__
+# define _GL_COUNTER __COUNTER__
+#else
+# define _GL_COUNTER __LINE__
+#endif
+
+/* Generate a symbol with the given prefix, making it unique if
+   possible.  */
+#define _GL_GENSYM(prefix) _GL_CONCAT (prefix, _GL_COUNTER)
+
+/* Verify requirement R at compile-time, as an integer constant expression
+   that returns 1.  If R is false, fail at compile-time, preferably
+   with a diagnostic that includes the string-literal DIAGNOSTIC.  */
+
+#define _GL_VERIFY_TRUE(R, DIAGNOSTIC) \
+   (!!sizeof (_GL_VERIFY_TYPE (R, DIAGNOSTIC)))
+
+#ifdef __cplusplus
+# if !GNULIB_defined_struct__gl_verify_type
+template <int w>
+  struct _gl_verify_type {
+    unsigned int _gl_verify_error_if_negative: w;
+  };
+#  define GNULIB_defined_struct__gl_verify_type 1
+# endif
+# define _GL_VERIFY_TYPE(R, DIAGNOSTIC) \
+    _gl_verify_type<(R) ? 1 : -1>
+#elif defined _GL_HAVE__STATIC_ASSERT
+# define _GL_VERIFY_TYPE(R, DIAGNOSTIC) \
+    struct {                                   \
+      _Static_assert (R, DIAGNOSTIC);          \
+      int _gl_dummy;                          \
+    }
+#else
+# define _GL_VERIFY_TYPE(R, DIAGNOSTIC) \
+    struct { unsigned int _gl_verify_error_if_negative: (R) ? 1 : -1; }
+#endif
+
+/* Verify requirement R at compile-time, as a declaration without a
+   trailing ';'.  If R is false, fail at compile-time.
+
+   This macro requires three or more arguments but uses at most the first
+   two, so that the _Static_assert macro optionally defined below supports
+   both the C11 two-argument syntax and the C2x one-argument syntax.
+
+   Unfortunately, unlike C11, this implementation must appear as an
+   ordinary declaration, and cannot appear inside struct { ... }.  */
+
+#if 200410 <= __cpp_static_assert
+# define _GL_VERIFY(R, DIAGNOSTIC, ...) static_assert (R, DIAGNOSTIC)
+#elif defined _GL_HAVE__STATIC_ASSERT
+# define _GL_VERIFY(R, DIAGNOSTIC, ...) _Static_assert (R, DIAGNOSTIC)
+#else
+# define _GL_VERIFY(R, DIAGNOSTIC, ...)                                \
+    extern int (*_GL_GENSYM (_gl_verify_function) (void))             \
+      [_GL_VERIFY_TRUE (R, DIAGNOSTIC)]
+#endif
+
+/* _GL_STATIC_ASSERT_H is defined if this code is copied into assert.h.  */
+#ifdef _GL_STATIC_ASSERT_H
+# if !defined _GL_HAVE__STATIC_ASSERT1 && !defined _Static_assert
+#  define _Static_assert(R, ...) \
+     _GL_VERIFY ((R), "static assertion failed", -)
+# endif
+# if (!defined static_assert \
+      && (!defined __cplusplus \
+          || (__cpp_static_assert < 201411 \
+              && __GNUG__ < 6 && __clang_major__ < 6)))
+#  if defined __cplusplus && _MSC_VER >= 1900 && !defined __clang__
+/* MSVC 14 in C++ mode supports the two-arguments static_assert but not
+   the one-argument static_assert, and it does not support _Static_assert.
+   We have to play preprocessor tricks to distinguish the two cases.
+   Since the MSVC preprocessor is not ISO C compliant (cf.
+   <https://stackoverflow.com/questions/5134523/>), the solution is specific
+   to MSVC.  */
+#   define _GL_EXPAND(x) x
+#   define _GL_SA1(a1) static_assert ((a1), "static assertion failed")
+#   define _GL_SA2 static_assert
+#   define _GL_SA3 static_assert
+#   define _GL_SA_PICK(x1,x2,x3,x4,...) x4
+#   define static_assert(...) _GL_EXPAND(_GL_SA_PICK(__VA_ARGS__,_GL_SA3,_GL_SA2,_GL_SA1)) (__VA_ARGS__)
+#  else
+#   define static_assert _Static_assert /* C11 requires this #define. */
+#  endif
+# endif
+#endif
+
+/* @assert.h omit start@  */
+
+#if 3 < __GNUC__ + (3 < __GNUC_MINOR__ + (4 <= __GNUC_PATCHLEVEL__))
+# define _GL_HAS_BUILTIN_TRAP 1
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_TRAP __has_builtin (__builtin_trap)
+#else
+# define _GL_HAS_BUILTIN_TRAP 0
+#endif
+
+#if 4 < __GNUC__ + (5 <= __GNUC_MINOR__)
+# define _GL_HAS_BUILTIN_UNREACHABLE 1
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_UNREACHABLE __has_builtin (__builtin_unreachable)
+#else
+# define _GL_HAS_BUILTIN_UNREACHABLE 0
+#endif
+
+/* Each of these macros verifies that its argument R is nonzero.  To
+   be portable, R should be an integer constant expression.  Unlike
+   assert (R), there is no run-time overhead.
+
+   There are two macros, since no single macro can be used in all
+   contexts in C.  verify_expr (R, E) is for scalar contexts, including
+   integer constant expression contexts.  verify (R) is for declaration
+   contexts, e.g., the top level.  */
+
+/* Verify requirement R at compile-time.  Return the value of the
+   expression E.  */
+
+#define verify_expr(R, E) \
+   (_GL_VERIFY_TRUE (R, "verify_expr (" #R ", " #E ")") ? (E) : (E))
+
+/* Verify requirement R at compile-time, as a declaration without a
+   trailing ';'.  verify (R) acts like static_assert (R) except that
+   it is portable to C11/C++14 and earlier, it can issue better
+   diagnostics, and its name is shorter and may be more convenient.  */
+
+#ifdef __PGI
+/* PGI barfs if R is long.  */
+# define verify(R) _GL_VERIFY (R, "verify (...)", -)
+#else
+# define verify(R) _GL_VERIFY (R, "verify (" #R ")", -)
+#endif
+
+/* Assume that R always holds.  Behavior is undefined if R is false,
+   fails to evaluate, or has side effects.
+
+   'assume (R)' is a directive from the programmer telling the
+   compiler that R is true so the compiler needn't generate code to
+   test R.  This is why 'assume' is in verify.h: it's related to
+   static checking (in this case, static checking done by the
+   programmer), not dynamic checking.
+
+   'assume (R)' can affect compilation of all the code, not just code
+   that happens to be executed after the assume (R) is "executed".
+   For example, if the code mistakenly does 'assert (R); assume (R);'
+   the compiler is entitled to optimize away the 'assert (R)'.
+
+   Although assuming R can help a compiler generate better code or
+   diagnostics, performance can suffer if R uses hard-to-optimize
+   features such as function calls not inlined by the compiler.
+
+   Avoid Clang's __builtin_assume, as it breaks GNU Emacs master
+   as of 2020-08-23T21:09:49Z!eggert@cs.ucla.edu; see
+   <https://bugs.gnu.org/43152#71>.  It's not known whether this breakage
+   is a Clang bug or an Emacs bug; play it safe for now.  */
+
+#if _GL_HAS_BUILTIN_UNREACHABLE
+# define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
+#elif 1200 <= _MSC_VER
+# define assume(R) __assume (R)
+#elif 202311L <= __STDC_VERSION__
+# include <stddef.h>
+# define assume(R) ((R) ? (void) 0 : unreachable ())
+#elif (defined GCC_LINT || defined lint) && _GL_HAS_BUILTIN_TRAP
+  /* Doing it this way helps various packages when configured with
+     --enable-gcc-warnings, which compiles with -Dlint.  It's nicer
+     if 'assume' silences warnings with GCC 3.4 through GCC 4.4.7 (2012).  */
+# define assume(R) ((R) ? (void) 0 : __builtin_trap ())
+#else
+  /* Some older tools grok NOTREACHED, e.g., Oracle Studio 12.6 (2017).  */
+# define assume(R) ((R) ? (void) 0 : /*NOTREACHED*/ (void) 0)
+#endif
+
+/* @assert.h omit end@  */
+
+#endif
diff --git a/lib/intl/version.c b/lib/intl/version.c
new file mode 100644 (file)
index 0000000..d62a28b
--- /dev/null
@@ -0,0 +1,24 @@
+/* libintl library version.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "libgnuintl.h"
+
+/* Version number: (major<<16) + (minor<<8) + subminor */
+int libintl_version = LIBINTL_VERSION;
diff --git a/lib/intl/wprintf-parse.h b/lib/intl/wprintf-parse.h
new file mode 100644 (file)
index 0000000..ec16ca4
--- /dev/null
@@ -0,0 +1,84 @@
+/* Parse printf format string.
+   Copyright (C) 1999, 2002-2003, 2005, 2007, 2010-2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation; either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _WPRINTF_PARSE_H
+#define _WPRINTF_PARSE_H
+
+#if HAVE_FEATURES_H
+# include <features.h> /* for __GLIBC__, __UCLIBC__ */
+#endif
+
+#include "printf-args.h"
+
+
+/* Flags */
+#define FLAG_GROUP       1      /* ' flag */
+#define FLAG_LEFT        2      /* - flag */
+#define FLAG_SHOWSIGN    4      /* + flag */
+#define FLAG_SPACE       8      /* space flag */
+#define FLAG_ALT        16      /* # flag */
+#define FLAG_ZERO       32
+#if __GLIBC__ >= 2 && !defined __UCLIBC__
+# define FLAG_LOCALIZED 64      /* I flag, uses localized digits */
+#endif
+
+/* arg_index value indicating that no argument is consumed.  */
+#define ARG_NONE        (~(size_t)0)
+
+/* Number of directly allocated directives (no malloc() needed).  */
+#define N_DIRECT_ALLOC_DIRECTIVES 7
+
+/* A parsed directive.  */
+typedef struct
+{
+  const wchar_t* dir_start;
+  const wchar_t* dir_end;
+  int flags;
+  const wchar_t* width_start;
+  const wchar_t* width_end;
+  size_t width_arg_index;
+  const wchar_t* precision_start;
+  const wchar_t* precision_end;
+  size_t precision_arg_index;
+  wchar_t conversion; /* d i o u x X f F e E g G a A c s p n U % but not C S */
+  size_t arg_index;
+}
+wchar_t_directive;
+
+/* A parsed format string.  */
+typedef struct
+{
+  size_t count;
+  wchar_t_directive *dir;
+  size_t max_width_length;
+  size_t max_precision_length;
+  wchar_t_directive direct_alloc_dir[N_DIRECT_ALLOC_DIRECTIVES];
+}
+wchar_t_directives;
+
+
+/* Parses the format string.  Fills in the number N of directives, and fills
+   in directives[0], ..., directives[N-1], and sets directives[N].dir_start
+   to the end of the format string.  Also fills in the arg_type fields of the
+   arguments and the needed count of arguments.  */
+#ifdef STATIC
+STATIC
+#else
+extern
+#endif
+int wprintf_parse (const wchar_t *format, wchar_t_directives *d, arguments *a);
+
+#endif /* _WPRINTF_PARSE_H */
diff --git a/lib/intl/xsize.c b/lib/intl/xsize.c
new file mode 100644 (file)
index 0000000..4b4914c
--- /dev/null
@@ -0,0 +1,3 @@
+#include <config.h>
+#define XSIZE_INLINE _GL_EXTERN_INLINE
+#include "xsize.h"
diff --git a/lib/intl/xsize.h b/lib/intl/xsize.h
new file mode 100644 (file)
index 0000000..0bfa4c8
--- /dev/null
@@ -0,0 +1,108 @@
+/* xsize.h -- Checked size_t computations.
+
+   Copyright (C) 2003, 2008-2022 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _XSIZE_H
+#define _XSIZE_H
+
+/* Get size_t.  */
+#include <stddef.h>
+
+/* Get SIZE_MAX.  */
+#include <limits.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+
+/* Get ATTRIBUTE_PURE.  */
+#include "attribute.h"
+
+#ifndef _GL_INLINE_HEADER_BEGIN
+ #error "Please include config.h first."
+#endif
+_GL_INLINE_HEADER_BEGIN
+#ifndef XSIZE_INLINE
+# define XSIZE_INLINE _GL_INLINE
+#endif
+
+/* The size of memory objects is often computed through expressions of
+   type size_t. Example:
+      void* p = malloc (header_size + n * element_size).
+   These computations can lead to overflow.  When this happens, malloc()
+   returns a piece of memory that is way too small, and the program then
+   crashes while attempting to fill the memory.
+   To avoid this, the functions and macros in this file check for overflow.
+   The convention is that SIZE_MAX represents overflow.
+   malloc (SIZE_MAX) is not guaranteed to fail -- think of a malloc
+   implementation that uses mmap --, it's recommended to use size_overflow_p()
+   or size_in_bounds_p() before invoking malloc().
+   The example thus becomes:
+      size_t size = xsum (header_size, xtimes (n, element_size));
+      void *p = (size_in_bounds_p (size) ? malloc (size) : NULL);
+*/
+
+/* Convert an arbitrary value >= 0 to type size_t.  */
+#define xcast_size_t(N) \
+  ((N) <= SIZE_MAX ? (size_t) (N) : SIZE_MAX)
+
+/* Sum of two sizes, with overflow check.  */
+XSIZE_INLINE size_t ATTRIBUTE_PURE
+xsum (size_t size1, size_t size2)
+{
+  size_t sum = size1 + size2;
+  return (sum >= size1 ? sum : SIZE_MAX);
+}
+
+/* Sum of three sizes, with overflow check.  */
+XSIZE_INLINE size_t ATTRIBUTE_PURE
+xsum3 (size_t size1, size_t size2, size_t size3)
+{
+  return xsum (xsum (size1, size2), size3);
+}
+
+/* Sum of four sizes, with overflow check.  */
+XSIZE_INLINE size_t ATTRIBUTE_PURE
+xsum4 (size_t size1, size_t size2, size_t size3, size_t size4)
+{
+  return xsum (xsum (xsum (size1, size2), size3), size4);
+}
+
+/* Maximum of two sizes, with overflow check.  */
+XSIZE_INLINE size_t ATTRIBUTE_PURE
+xmax (size_t size1, size_t size2)
+{
+  /* No explicit check is needed here, because for any n:
+     max (SIZE_MAX, n) == SIZE_MAX and max (n, SIZE_MAX) == SIZE_MAX.  */
+  return (size1 >= size2 ? size1 : size2);
+}
+
+/* Multiplication of a count with an element size, with overflow check.
+   The count must be >= 0 and the element size must be > 0.
+   This is a macro, not a function, so that it works correctly even
+   when N is of a wider type and N > SIZE_MAX.  */
+#define xtimes(N, ELSIZE) \
+  ((N) <= SIZE_MAX / (ELSIZE) ? (size_t) (N) * (ELSIZE) : SIZE_MAX)
+
+/* Check for overflow.  */
+#define size_overflow_p(SIZE) \
+  ((SIZE) == SIZE_MAX)
+/* Check against overflow.  */
+#define size_in_bounds_p(SIZE) \
+  ((SIZE) != SIZE_MAX)
+
+_GL_INLINE_HEADER_END
+
+#endif /* _XSIZE_H */
diff --git a/lib/malloc/sbrk.c b/lib/malloc/sbrk.c
new file mode 100644 (file)
index 0000000..7be3156
--- /dev/null
@@ -0,0 +1,80 @@
+/* Copyright (C) 2023 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 <unistd.h>
+#include <errno.h>
+
+#if defined (HAVE_BRK) && !defined (HAVE_SBRK)
+
+static void *initialbrk;
+static void *curbrk;
+
+static int
+initbrk (void)
+{
+  if (initialbrk == 0)
+    {
+      void *b;
+
+      b = brk (NULL);
+      if (b == (void *)-1)
+       return -1;
+      initialbrk = curbrk = b;
+    }
+  return (0);
+}
+
+/* sbrk(3) implementation in terms of brk(2). Good enough for malloc to use. */
+void *
+sbrk (intptr_t incr)
+{
+  void *newbrk, *oldbrk;
+
+  if (initialbrk == 0 && initbrk () == -1)
+    {
+      errno = ENOMEM;
+      return (void *)-1;
+    }
+
+  if (incr == 0)
+    return curbrk;
+
+  /* bounds checking, overflow */
+  if ((incr > 0 && (uintptr_t) curbrk + incr < (uintptr_t) curbrk) ||
+      (incr < 0 && (uintptr_t) curbrk + incr > (uintptr_t) curbrk))
+    {
+      errno = ENOMEM;
+      return (void *)-1;
+    }
+
+  newbrk = curbrk + incr;  
+  if (newbrk < initialbrk)
+    {
+      errno = EINVAL;
+      return (void *)-1;
+    }
+
+  if (brk (newbrk) == (void *)-1)
+    return (void *)-1;         /* preserve errno */
+
+  oldbrk = curbrk;
+  curbrk = newbrk;
+
+  return (oldbrk);
+}
+#endif /* HAVE_BRK && !HAVE_SBRK */
diff --git a/lib/sh/anonfile.c b/lib/sh/anonfile.c
new file mode 100644 (file)
index 0000000..b3c8957
--- /dev/null
@@ -0,0 +1,147 @@
+/* anonfile.c - open and close temporary files (anonymous and memory-backed if possible). */
+
+/* Copyright (C) 2023 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)
+#  include <unistd.h>
+#endif
+#include <bashtypes.h>
+
+#if defined (HAVE_MEMFD_CREATE) || defined (HAVE_SHM_OPEN) || defined (HAVE_SHM_MKSTEMP)
+#  include <sys/mman.h>
+#endif
+#include <filecntl.h>
+
+#include <errno.h>
+
+#include <shell.h>
+#include <bashansi.h>
+
+static int anonunlink (const char *);
+
+#if defined (HAVE_SHM_OPEN)
+#ifndef O_NOFOLLOW
+#  define O_NOFOLLOW 0
+#endif
+
+static int
+anonshmunlink (const char *fn)
+{
+  return (shm_unlink (fn));
+}
+
+static int
+anonshmopen (const char *name, int flags, char **fn)
+{
+  int fd;
+  char *fname;
+
+  fd = -1;
+  if (fn)
+    *fn = 0;
+
+#if defined (HAVE_SHM_MKSTEMP)
+  fname = savestring ("/shm-XXXXXXXXXX");
+  fd = shm_mkstemp (fname);
+  if (fd < 0)
+    free (fname);
+#endif
+
+  if (fd < 0)
+    {
+      fname = sh_mktmpname (name, flags);
+      fd = shm_open (fname, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0600);
+    }
+
+  if (fd < 0)
+    {
+      free (fname);
+      return fd;
+    }
+
+  if (shm_unlink (fname) < 0)
+    {
+      int o;
+      o = errno;
+      free (fname);
+      close (fd);
+      errno = o;
+      return -1;
+    }
+
+  if (fn)
+    *fn = fname;
+  else
+    free (fname);
+
+  return fd;  
+}
+#endif
+
+int
+anonopen (const char *name, int flags, char **fn)
+{
+  int fd, flag;
+  char *fname;
+
+#if defined (HAVE_MEMFD_CREATE)
+  /* "Names do not affect the behavior of the file descriptor." */
+  fd = memfd_create ("anonopen", 0);
+  if (fd >= 0)
+    {
+      if (fn)
+       *fn = 0;
+      return fd;
+    }
+  /* If memfd_create fails, we fall through to the unlinked-shm-or-regular-file
+     implementation. */
+#endif
+
+  /* Heuristic */
+  flag = (name && *name == '/') ? MT_TEMPLATE : MT_USETMPDIR;
+
+#if defined (HAVE_SHM_OPEN)
+  fd = anonshmopen (name, flag, fn);
+  if (fd >= 0)
+    return fd;         /* anonshmopen sets *FN appropriately */
+#endif
+
+  fd = sh_mktmpfd (name, flag|MT_USERANDOM|MT_READWRITE|MT_UNLINK, fn);
+  return fd;
+}
+
+int
+anonclose (int fd, const char *name)
+{
+  int r;
+
+  r = close (fd);
+  return r;
+}
+
+static int
+anonunlink (const char *fn)
+{
+  int r;
+
+  r = unlink (fn);
+  return r;
+}
diff --git a/lib/sh/compat.c b/lib/sh/compat.c
new file mode 100644 (file)
index 0000000..a0e4702
--- /dev/null
@@ -0,0 +1,53 @@
+/* compat.c - functions for backwards compatibility with previous versions */
+
+/* Copyright (C) 2023 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)
+#  include <unistd.h>
+#endif
+
+#include <bashtypes.h>
+#include <shell.h>
+
+/* For backwards compatibility with existing loadable builtins. */
+int
+legal_number (const char *string, intmax_t *result)
+{
+  return (valid_number (string, result));
+}
+
+int
+legal_identifier (const char *string)
+{
+  return (valid_identifier (string));
+}
+
+int
+legal_alias_name (const char *string, int flags)
+{
+  return (valid_alias_name (string, flags));
+}
+
+int
+compat_init (void)
+{
+  return 0;
+}
diff --git a/lib/sh/mbsncmp.c b/lib/sh/mbsncmp.c
new file mode 100644 (file)
index 0000000..3b230aa
--- /dev/null
@@ -0,0 +1,80 @@
+/* mbsncmp - multibyte string comparison. */
+
+/* Copyright (C) 1995-2023 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_MBSNCMP) && defined (HANDLE_MULTIBYTE)
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <wchar.h>
+
+extern int locale_utf8locale;
+
+/* Compare MBS1 and MBS2 up to N multibyte characters. */
+int
+mbsncmp (const char *mbs1, const char *mbs2, size_t n)
+{
+  int len1, len2, mb_cur_max;
+  wchar_t c1, c2;
+  mbstate_t state1 = { 0 }, state2 = { 0 };
+
+  len1 = len2 = 0;
+  mb_cur_max = MB_CUR_MAX;
+
+  if (n == 0)
+    return 0;
+
+  do
+    {
+      len1 = mbrtowc (&c1, mbs1, mb_cur_max, &state1);
+      len2 = mbrtowc (&c2, mbs2, mb_cur_max, &state2);
+
+      if (len1 == 0)
+       return len2 == 0 ? 0 : -1;
+      else if (len2 == 0)
+       return 1;
+      else if (len1 > 0 && len2 < 0)
+        return -1;
+      else if (len1 < 0 && len2 > 0)
+        return 1;
+      else if (len1 < 0 && len2 < 0)
+       {
+         len1 = strlen (mbs1);
+         len2 = strlen (mbs2);
+         return (len1 == len2 ? memcmp (mbs1, mbs2, len1)
+                              : ((len1 < len2) ? (memcmp (mbs1, mbs2, len1) > 0 ? 1 : -1)
+                                               : (memcmp (mbs1, mbs2, len2) >= 0 ? 1 : -1)));
+       }
+
+      mbs1 += len1;
+      mbs2 += len2;
+      n--;
+      if (c1 != c2)
+        break;
+    }
+  while (n > 0);
+
+  return c1 - c2;
+}
+
+#endif
diff --git a/lib/sh/reallocarray.c b/lib/sh/reallocarray.c
new file mode 100644 (file)
index 0000000..53d7f77
--- /dev/null
@@ -0,0 +1,44 @@
+/* reallocarray.c - reallocate memory for an array given type size and
+   number of elements */
+
+/* Copyright (C) 2024 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 "bashansi.h"
+#include <stdckdint.h>
+
+#include <errno.h>
+#if !defined (errno)
+extern int errno;
+#endif
+
+void *
+reallocarray (void *ptr, size_t nmemb, size_t size)
+{
+  size_t nbytes;
+
+  if (ckd_mul (&nbytes, nmemb, size))
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+               
+  return realloc (ptr, nbytes);
+}
diff --git a/lib/sh/strlcpy.c b/lib/sh/strlcpy.c
new file mode 100644 (file)
index 0000000..e4fbaab
--- /dev/null
@@ -0,0 +1,39 @@
+/* strlcpy - null-terminated string copy with length checking. */
+
+/* Copyright (C) 2023 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 <bashansi.h>
+
+size_t
+strlcpy(char *dest, const char *src, size_t size)
+{
+  size_t ret;
+
+  ret = strlen(src);
+  if (size)
+    {
+      size_t len;
+      len = (ret >= size) ? size - 1 : ret;
+      memcpy (dest, src, len);
+      dest[len] = '\0';
+    }
+  return ret;
+}
diff --git a/lib/sh/strscpy.c b/lib/sh/strscpy.c
new file mode 100644 (file)
index 0000000..534fa79
--- /dev/null
@@ -0,0 +1,41 @@
+/* strscpy - null-terminated string copy with length checking. */
+
+/* Copyright (C) 2023 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)
+#  include <unistd.h>
+#endif
+#include <bashansi.h>
+
+ssize_t
+strscpy (char *d, const char *s, size_t len)
+{
+  size_t i;
+
+  for (i = 0; i < len; i++)
+    if ((d[i] = s[i]) == 0)
+      return ((ssize_t)i);
+
+  if (i != 0)
+    d[--i] = '\0';
+
+  return (-1);         /* strlen (s) > len */
+}
diff --git a/m4/bison.m4 b/m4/bison.m4
new file mode 100644 (file)
index 0000000..f812037
--- /dev/null
@@ -0,0 +1,92 @@
+# serial 10
+
+# Copyright (C) 2002-2006, 2008-2022 Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# There are two types of parser skeletons:
+#
+# * Those that can be used with any Yacc implementation, including bison.
+#   For these, in the configure.ac, up to Autoconf 2.69, you could use
+#     AC_PROG_YACC
+#   In newer Autoconf versions, however, this macro is broken. See
+#     https://lists.gnu.org/archive/html/autoconf-patches/2013-03/msg00000.html
+#     https://lists.gnu.org/archive/html/bug-autoconf/2018-12/msg00001.html
+#   In the Makefile.am you could use
+#     $(SHELL) $(YLWRAP) $(srcdir)/foo.y \
+#                        y.tab.c foo.c \
+#                        y.tab.h foo.h \
+#                        y.output foo.output \
+#                        -- $(YACC) $(YFLAGS) $(AM_YFLAGS)
+#   or similar.
+#
+# * Those that make use of Bison extensions. For example,
+#     - %define api.pure   requires bison 2.7 or newer,
+#     - %precedence        requires bison 3.0 or newer.
+#   For these, in the configure.ac you will need an invocation of
+#     gl_PROG_BISON([VARIABLE], [MIN_BISON_VERSION])
+#   Example:
+#     gl_PROG_BISON([PARSE_DATETIME_BISON], [2.4])
+#   With this preparation, in the Makefile.am there are two ways to formulate
+#   the invocation. Both are direct, without use of 'ylwrap'.
+#   (a) You can invoke
+#         $(VARIABLE) -d $(SOME_BISON_OPTIONS) --output foo.c $(srcdir)/foo.y
+#       or similar.
+#   (b) If you want the invocation to honor an YFLAGS=... parameter passed to
+#       'configure' or an YFLAGS environment variable present at 'configure'
+#       time, add an invocation of gl_BISON to the configure.ac, and write
+#         $(VARIABLE) -d $(YFLAGS) $(AM_YFLAGS) $(srcdir)/foo.y
+#       or similar.
+
+# This macro defines the autoconf variable VARIABLE to 'bison' if the specified
+# minimum version of bison is found in $PATH, or to ':' otherwise.
+AC_DEFUN([gl_PROG_BISON],
+[
+  AC_CHECK_PROGS([$1], [bison])
+  if test -z "$[$1]"; then
+    ac_verc_fail=yes
+  else
+    cat >conftest.y <<_ACEOF
+%require "$2"
+%%
+exp:
+_ACEOF
+    AC_MSG_CHECKING([for bison $2 or newer])
+    ac_prog_version=`$$1 --version 2>&1 | sed -n 's/^.*GNU Bison.* \([[0-9]]*\.[[0-9.]]*\).*$/\1/p'`
+    : ${ac_prog_version:='v. ?.??'}
+    if $$1 conftest.y -o conftest.c 2>/dev/null; then
+      ac_prog_version="$ac_prog_version, ok"
+      ac_verc_fail=no
+    else
+      ac_prog_version="$ac_prog_version, bad"
+      ac_verc_fail=yes
+    fi
+    rm -f conftest.y conftest.c
+    AC_MSG_RESULT([$ac_prog_version])
+  fi
+  if test $ac_verc_fail = yes; then
+    [$1]=:
+  fi
+  AC_SUBST([$1])
+])
+
+# This macro sets the autoconf variables YACC (for old-style yacc Makefile
+# rules) and YFLAGS (to allow options to be passed as 'configure' time).
+AC_DEFUN([gl_BISON],
+[
+  : ${YACC='bison -o y.tab.c'}
+dnl
+dnl Declaring YACC & YFLAGS precious will not be necessary after GNULIB
+dnl requires an Autoconf greater than 2.59c, but it will probably still be
+dnl useful to override the description of YACC in the --help output, re
+dnl parse-datetime.y assuming 'bison -o y.tab.c'.
+  AC_ARG_VAR([YACC],
+[The "Yet Another C Compiler" implementation to use.  Defaults to
+'bison -o y.tab.c'.  Values other than 'bison -o y.tab.c' will most likely
+break on most systems.])dnl
+  AC_ARG_VAR([YFLAGS],
+[YFLAGS contains the list arguments that will be passed by default to Bison.
+This script will default YFLAGS to the empty string to avoid a default value of
+'-d' given by some make applications.])dnl
+])
diff --git a/m4/c-bool.m4 b/m4/c-bool.m4
new file mode 100644 (file)
index 0000000..2718a17
--- /dev/null
@@ -0,0 +1,27 @@
+# Check for bool that conforms to C2023.
+
+dnl Copyright 2022-2024 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_C_BOOL],
+[
+  AC_CACHE_CHECK([for bool, true, false], [gl_cv_c_bool],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_SOURCE([[
+          #if true == false
+           #error "true == false"
+          #endif
+          extern bool b;
+          bool b = true == false;]])],
+       [gl_cv_c_bool=yes],
+       [gl_cv_c_bool=no])])
+  if test "$gl_cv_c_bool" = yes; then
+    AC_DEFINE([HAVE_C_BOOL], [1],
+      [Define to 1 if bool, true and false work as per C2023.])
+  fi
+
+  AC_CHECK_HEADERS_ONCE([stdbool.h])
+
+])
diff --git a/m4/flexmember.m4 b/m4/flexmember.m4
new file mode 100644 (file)
index 0000000..3b4237e
--- /dev/null
@@ -0,0 +1,44 @@
+# serial 5
+# Check for flexible array member support.
+
+# Copyright (C) 2006, 2009-2022 Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Written by Paul Eggert.
+
+AC_DEFUN([AC_C_FLEXIBLE_ARRAY_MEMBER],
+[
+  AC_CACHE_CHECK([for flexible array member],
+    ac_cv_c_flexmember,
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <stdlib.h>
+            #include <stdio.h>
+            #include <stddef.h>
+            struct m { struct m *next, **list; char name[]; };
+            struct s { struct s *p; struct m *m; int n; double d[]; };]],
+          [[int m = getchar ();
+            size_t nbytes = offsetof (struct s, d) + m * sizeof (double);
+            nbytes += sizeof (struct s) - 1;
+            nbytes -= nbytes % sizeof (struct s);
+            struct s *p = malloc (nbytes);
+            p->p = p;
+            p->m = NULL;
+            p->d[0] = 0.0;
+            return p->d != (double *) NULL;]])],
+       [ac_cv_c_flexmember=yes],
+       [ac_cv_c_flexmember=no])])
+  if test $ac_cv_c_flexmember = yes; then
+    AC_DEFINE([FLEXIBLE_ARRAY_MEMBER], [],
+      [Define to nothing if C supports flexible array members, and to
+       1 if it does not.  That way, with a declaration like 'struct s
+       { int n; short d@<:@FLEXIBLE_ARRAY_MEMBER@:>@; };', the struct hack
+       can be used with pre-C99 compilers.
+       Use 'FLEXSIZEOF (struct s, d, N * sizeof (short))' to calculate
+       the size in bytes of such a struct containing an N-element array.])
+  else
+    AC_DEFINE([FLEXIBLE_ARRAY_MEMBER], [1])
+  fi
+])
diff --git a/m4/locale_h.m4 b/m4/locale_h.m4
new file mode 100644 (file)
index 0000000..ca5d0d0
--- /dev/null
@@ -0,0 +1,174 @@
+# locale_h.m4 serial 28
+dnl Copyright (C) 2007, 2009-2022 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN_ONCE([gl_LOCALE_H],
+[
+  dnl Ensure to expand the default settings once only, before all statements
+  dnl that occur in other macros.
+  AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
+
+  dnl Persuade glibc <locale.h> to define locale_t and the int_p_*, int_n_*
+  dnl members of 'struct lconv'.
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+
+  dnl If <stddef.h> is replaced, then <locale.h> must also be replaced.
+  AC_REQUIRE([gl_STDDEF_H])
+
+  AC_REQUIRE([gl_LOCALE_T])
+
+  dnl Solaris 11.0 defines the int_p_*, int_n_* members of 'struct lconv'
+  dnl only if _LCONV_C99 is defined.
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  case "$host_os" in
+    solaris*)
+      AC_DEFINE([_LCONV_C99], [1], [Define to 1 on Solaris.])
+      ;;
+  esac
+
+  AC_CACHE_CHECK([whether locale.h conforms to POSIX:2001],
+    [gl_cv_header_locale_h_posix2001],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <locale.h>
+            int x = LC_MESSAGES;
+            int y = sizeof (((struct lconv *) 0)->decimal_point);]],
+          [[]])],
+       [gl_cv_header_locale_h_posix2001=yes],
+       [gl_cv_header_locale_h_posix2001=no])])
+
+  dnl Check whether 'struct lconv' is complete.
+  dnl Bionic libc's 'struct lconv' is just a dummy.
+  dnl On OpenBSD 4.9, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 9, Cygwin 1.5.x,
+  dnl mingw, MSVC 9, it lacks the int_p_* and int_n_* members.
+  AC_CACHE_CHECK([whether struct lconv is properly defined],
+    [gl_cv_sys_struct_lconv_ok],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <locale.h>
+            struct lconv l;
+            int x = sizeof (l.decimal_point);
+            int y = sizeof (l.int_p_cs_precedes);]],
+          [[]])],
+       [gl_cv_sys_struct_lconv_ok=yes],
+       [gl_cv_sys_struct_lconv_ok=no])
+    ])
+  if test $gl_cv_sys_struct_lconv_ok = no; then
+    dnl On native Windows with MSVC, merely define these member names as macros.
+    dnl This avoids trouble in C++ mode.
+    case "$host_os" in
+      mingw*)
+        AC_EGREP_CPP([Special], [
+#ifdef _MSC_VER
+ Special
+#endif
+          ],
+          [],
+          [REPLACE_STRUCT_LCONV=1])
+        ;;
+      *) REPLACE_STRUCT_LCONV=1 ;;
+    esac
+  fi
+
+  dnl <locale.h> is always overridden, because of GNULIB_POSIXCHECK.
+  gl_NEXT_HEADERS([locale.h])
+
+  dnl Check for declarations of anything we want to poison if the
+  dnl corresponding gnulib module is not in use.
+  gl_WARN_ON_USE_PREPARE([[#include <locale.h>
+/* Some systems provide declarations in a non-standard header.  */
+#if HAVE_XLOCALE_H
+# include <xlocale.h>
+#endif
+    ]],
+    [setlocale newlocale duplocale freelocale])
+])
+
+dnl Checks to determine whether the system has the locale_t type,
+dnl and how to obtain it.
+AC_DEFUN([gl_LOCALE_T],
+[
+  dnl Persuade glibc and Solaris <locale.h> to define locale_t.
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+
+  dnl Check whether use of locale_t requires inclusion of <xlocale.h>,
+  dnl e.g. on Mac OS X 10.5. If <locale.h> does not define locale_t by
+  dnl itself, we assume that <xlocale.h> will do so.
+  AC_CACHE_CHECK([whether locale.h defines locale_t],
+    [gl_cv_header_locale_has_locale_t],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <locale.h>
+            locale_t x;]],
+          [[]])],
+       [gl_cv_header_locale_has_locale_t=yes],
+       [gl_cv_header_locale_has_locale_t=no])
+    ])
+
+  dnl Check for <xlocale.h>.
+  AC_CHECK_HEADERS_ONCE([xlocale.h])
+  if test $ac_cv_header_xlocale_h = yes; then
+    HAVE_XLOCALE_H=1
+    if test $gl_cv_header_locale_has_locale_t = yes; then
+      gl_cv_header_locale_h_needs_xlocale_h=no
+    else
+      gl_cv_header_locale_h_needs_xlocale_h=yes
+    fi
+    HAVE_LOCALE_T=1
+  else
+    HAVE_XLOCALE_H=0
+    gl_cv_header_locale_h_needs_xlocale_h=no
+    if test $gl_cv_header_locale_has_locale_t = yes; then
+      HAVE_LOCALE_T=1
+    else
+      HAVE_LOCALE_T=0
+    fi
+  fi
+  AC_SUBST([HAVE_XLOCALE_H])
+])
+
+# gl_LOCALE_MODULE_INDICATOR([modulename])
+# sets the shell variable that indicates the presence of the given module
+# to a C preprocessor expression that will evaluate to 1.
+# This macro invocation must not occur in macros that are AC_REQUIREd.
+AC_DEFUN([gl_LOCALE_MODULE_INDICATOR],
+[
+  dnl Ensure to expand the default settings once only.
+  gl_LOCALE_H_REQUIRE_DEFAULTS
+  gl_MODULE_INDICATOR_SET_VARIABLE([$1])
+  dnl Define it also as a C macro, for the benefit of the unit tests.
+  gl_MODULE_INDICATOR_FOR_TESTS([$1])
+])
+
+# Initializes the default values for AC_SUBSTed shell variables.
+# This macro must not be AC_REQUIREd.  It must only be invoked, and only
+# outside of macros or in macros that are not AC_REQUIREd.
+AC_DEFUN([gl_LOCALE_H_REQUIRE_DEFAULTS],
+[
+  m4_defun(GL_MODULE_INDICATOR_PREFIX[_LOCALE_H_MODULE_INDICATOR_DEFAULTS], [
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_LOCALECONV])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SETLOCALE])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SETLOCALE_NULL])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_DUPLOCALE])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_LOCALENAME])
+  ])
+  m4_require(GL_MODULE_INDICATOR_PREFIX[_LOCALE_H_MODULE_INDICATOR_DEFAULTS])
+  AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
+])
+
+AC_DEFUN([gl_LOCALE_H_DEFAULTS],
+[
+  dnl Assume proper GNU behavior unless another module says otherwise.
+  HAVE_NEWLOCALE=1;       AC_SUBST([HAVE_NEWLOCALE])
+  HAVE_DUPLOCALE=1;       AC_SUBST([HAVE_DUPLOCALE])
+  HAVE_FREELOCALE=1;      AC_SUBST([HAVE_FREELOCALE])
+  REPLACE_LOCALECONV=0;   AC_SUBST([REPLACE_LOCALECONV])
+  REPLACE_SETLOCALE=0;    AC_SUBST([REPLACE_SETLOCALE])
+  REPLACE_NEWLOCALE=0;    AC_SUBST([REPLACE_NEWLOCALE])
+  REPLACE_DUPLOCALE=0;    AC_SUBST([REPLACE_DUPLOCALE])
+  REPLACE_FREELOCALE=0;   AC_SUBST([REPLACE_FREELOCALE])
+  REPLACE_STRUCT_LCONV=0; AC_SUBST([REPLACE_STRUCT_LCONV])
+  LOCALENAME_ENHANCE_LOCALE_FUNCS=0; AC_SUBST([LOCALENAME_ENHANCE_LOCALE_FUNCS])
+])
diff --git a/m4/unlocked-io.m4 b/m4/unlocked-io.m4
new file mode 100644 (file)
index 0000000..ff14f34
--- /dev/null
@@ -0,0 +1,31 @@
+# unlocked-io.m4 serial 16
+
+# Copyright (C) 1998-2006, 2009-2024 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+dnl From Jim Meyering.
+dnl
+dnl Adapted from gnulib:m4/unlocked-io.m4
+AC_DEFUN([BASH_FUNC_UNLOCKED_IO],
+[
+  dnl Persuade glibc and Solaris <stdio.h> to declare
+  dnl fgets_unlocked(), fputs_unlocked() etc.
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_DECLS_ONCE([clearerr_unlocked])
+  AC_CHECK_DECLS_ONCE([feof_unlocked])
+  AC_CHECK_DECLS_ONCE([ferror_unlocked])
+  AC_CHECK_DECLS_ONCE([fflush_unlocked])
+  AC_CHECK_DECLS_ONCE([fgets_unlocked])
+  AC_CHECK_DECLS_ONCE([fputc_unlocked])
+  AC_CHECK_DECLS_ONCE([fputs_unlocked])
+  AC_CHECK_DECLS_ONCE([fread_unlocked])
+  AC_CHECK_DECLS_ONCE([fwrite_unlocked])
+  AC_CHECK_DECLS_ONCE([getc_unlocked])
+  AC_CHECK_DECLS_ONCE([getchar_unlocked])
+  AC_CHECK_DECLS_ONCE([putc_unlocked])
+  AC_CHECK_DECLS_ONCE([putchar_unlocked])
+])
index ac3c2d29cca45643ebe7548dd7150733fac3dc4e..35fa324f47b612f01be03012c5ce03ea1aab58be 100644 (file)
@@ -1,6 +1,6 @@
 /* patchlevel.h -- current bash patch level */
 
-/* Copyright (C) 2001-2022 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2024 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
diff --git a/tests/alias7.sub b/tests/alias7.sub
new file mode 100644 (file)
index 0000000..b2aeb15
--- /dev/null
@@ -0,0 +1,92 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Change bash to expand the next word following aliases ending with a space
+# that are more than one level deep. Change how bash handles the expand-next-word
+# flag when recognizing a reserved word.
+#
+# Bash never did this before post-bash-5.2
+
+: ${THIS_SH:=./bash}
+
+shopt -s expand_aliases 2>/dev/null
+
+alias a1='echo '
+alias a2=a1
+
+alias foo=bar
+alias x=xtra
+
+a2 foo
+unalias foo
+
+alias e='echo '
+alias f='foo '
+alias b=bar
+
+e f b x
+
+alias e=echo
+
+a2 foo;e x
+
+unalias e f b
+
+alias e=echo
+alias foo='bar '
+alias c=';'
+
+a2 foo c e x
+
+unalias foo e c
+
+alias file='/dev/null ;'
+alias e=echo
+alias foo='bar '
+alias c='< '
+alias x=xtra
+a2 foo c file e x
+
+unalias a1 a2 e foo c x file
+
+alias foo=bar
+
+alias al=' '
+alias foo=bar
+
+al for foo in v
+do echo foo=$foo bar=$bar
+done
+
+al case foo in foo) echo foo;; bar) echo bar;; esac
+
+# one difference between bash in default and posix modes is that default mode
+# bash allows reserved words to be aliased, which posix says is a no-no
+
+${THIS_SH} -c '
+shopt -s expand_aliases 2>/dev/null
+alias al=" "
+alias foo=bar
+alias for=echo
+al for foo in v
+do echo foo=$foo bar=$bar
+done' bash
+
+${THIS_SH} -o posix -c '
+alias al=" "
+alias foo=bar
+alias for=echo
+al for foo in v
+do echo foo=$foo bar=$bar
+done' bash
diff --git a/tests/arith9.sub b/tests/arith9.sub
new file mode 100644 (file)
index 0000000..811139e
--- /dev/null
@@ -0,0 +1,38 @@
+# test expression evaluation with unset variables
+set -u
+
+( echo $(( a > 4 )) ; echo after 1 )   # error
+( echo $(( a[0] > 4 )); echo after 2)  # error
+
+set +u
+( echo $(( a > 4 )) ; echo after 3 $? )
+( echo $(( a[0] > 4 )); echo after 4 $?)
+
+# this is a recursion stack error
+a=b
+b=a
+echo $(( a + 7 ))
+
+# make sure command printing works for arithmetic expansions and commands
+set -x
+var=42
+
+echo $(( $var ))
+echo $?
+
+echo $(( $null ))
+echo $?
+
+(( $var ))
+echo $?
+
+(( $null ))
+echo $?
+
+set +x
+
+# invalid expressions in different cases
+x=4+
+declare -i x
+x+=7 y=4
+echo x = $x y = $y
diff --git a/tests/array31.sub b/tests/array31.sub
new file mode 100644 (file)
index 0000000..c1ea784
--- /dev/null
@@ -0,0 +1,43 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Tilde expansion in indexed array assignments; prep for future work
+HOME=/homes/cj
+
+declare -a aa
+aa=([0]=~/Desktop)
+declare -p aa
+unset aa
+
+declare -a aa=([0]=~/Desktop)
+declare -p aa
+unset aa
+
+declare -a aa=([0]=~/Desktop:~/Library:~/Documents)
+declare -p aa
+unset aa
+
+declare -a aa
+aa=([0]=~/Desktop:~/Library:~/Documents)
+declare -p aa
+unset aa
+
+declare -a aa=([0]=~/Desktop:~/Library:~/Documents [1]=~/Applications)
+declare -p aa
+unset aa
+
+declare -a aa
+aa=([0]=~/Desktop:~/Library:~/Documents [1]=~/Applications)
+declare -p aa
+unset aa
diff --git a/tests/array32.sub b/tests/array32.sub
new file mode 100644 (file)
index 0000000..5192d49
--- /dev/null
@@ -0,0 +1,98 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# change behavior of shell builtins to extend assoc_expand_once to indexed
+# arrays
+
+export subscript='$(echo INJECTION! >&2 ; echo 0)'
+shopt -s array_expand_once
+
+printf -v a["$subscript"] %s hi
+declare -p a
+unset a
+
+printf -v "a[$subscript]" %s hi
+declare -p a
+
+a[0]=hi
+
+unset a["$subscript"]
+declare -p a
+
+unset a
+
+unset a["$subscript"]
+declare -p a
+
+unset -v a
+read a["$subscript"] <<<hi
+declare -p a
+
+declare -a a
+read a["$subscript"] <<<hi
+declare -p a
+
+unset -v a
+
+declare -a a
+
+{ sleep 1; exit 12; } & bgpid=$!
+wait -n -p a["$subscript"] $bgpid
+
+declare -p a
+
+unset -v a
+declare -a a
+
+declare -i a["$subscript"]=42
+declare -p a
+
+# this still won't work because the quotes prevent it from being recognized as
+# an assignment statement
+#declare -i "a[$subscript]"=42
+#declare -p a
+
+test -v a["$subscript"] && echo set
+[ -v a["$subscript"] ] && echo set
+
+let a["$subscript"]+=1
+unset -v a
+
+# these are all already arithmetic expression errors
+
+declare -a a
+
+(( a[$subscript]++ ))
+declare -p a
+: $(( a[$subscript]++ ))
+declare -p a
+
+a[$subscript]=hi
+declare -p a
+
+# length shortcuts for unset variables, so give it a value
+a[0]=zero
+echo ${#a[$subscript]}
+
+unset -v a
+
+# compound assignments should not perform double expansion
+
+a=( [$subscript]=hi )
+declare -p a
+
+declare -a a
+a=( [$subscript]=hi )
+declare -p a
+
+unset -v a
diff --git a/tests/array33.sub b/tests/array33.sub
new file mode 100644 (file)
index 0000000..5eb40a4
--- /dev/null
@@ -0,0 +1,52 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# implicit and explicit variable type conversions
+
+declare -A A=(x x)
+declare -p A
+
+f() { declare -g A=([1]=1); }
+f
+
+declare -p A
+
+unset -f f
+# error to convert associative to indexed
+f() { declare -ga A=([1]=1); }
+f
+
+# error to convert associative to indexed
+declare -a A=([1]=1)
+declare -p A
+
+unset -v A
+
+declare -a A=(x x)
+declare -p A
+
+# error to convert indexed to associative
+f() { declare -gA A=([1]=1); }
+f
+
+declare -p A
+
+# error to convert indexed to associative
+declare -A A=([1]=1)
+declare -p A
+
+# can't read into an associative array
+unset -v A
+declare -A A
+read -a A </dev/null
diff --git a/tests/assoc19.sub b/tests/assoc19.sub
new file mode 100644 (file)
index 0000000..075bccc
--- /dev/null
@@ -0,0 +1,50 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Problems with tilde expansion of keys and values in bash-5.2
+
+HOME=/homes/cj
+
+declare -A aa=([key]=~/Desktop) 
+declare -p aa
+unset aa
+
+declare -A aa
+aa=([key]=~/Desktop) 
+declare -p aa
+unset aa
+
+declare -A aa=([key]=~/Desktop [k2]=~/Library )
+declare -p aa
+unset aa
+
+declare -A aa=([~/key]=~/Desktop) 
+declare -p aa
+unset aa
+
+declare -A aa
+aa=([~/key]=~/Desktop) 
+aa[~/Documents]=~/Library
+declare -p aa
+unset aa
+
+declare -A aa
+aa=([~/key]=~/Desktop:~/Documents:~/Applications) 
+aa[~/Documents]=~/Library
+declare -p aa
+unset aa
+
+declare -A aa=([~/key]=~/Desktop:~/Documents:~/Applications [~/Documents]=~/Library)
+declare -p aa
+
+
diff --git a/tests/builtins10.sub b/tests/builtins10.sub
new file mode 100644 (file)
index 0000000..e8413f8
--- /dev/null
@@ -0,0 +1,39 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# have to run through sed or grep to filter out version information
+
+# let's exercise print-help
+help -x
+
+help -- | sed 1d
+
+command help -s help
+builtin help -d shift
+shift --help
+help -s builtin shift
+
+# this hasn't ever been very useful
+help -s 'read*'
+# but prefix matching is
+help -s rea
+
+help :
+
+help -m : | grep -v version
+
+LC_ALL=en_US.UTF-8
+help -- | sed 1d
+
+# maybe sometime in the future this will do something
+help -- bash
diff --git a/tests/builtins11.sub b/tests/builtins11.sub
new file mode 100644 (file)
index 0000000..2f41df4
--- /dev/null
@@ -0,0 +1,45 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ulimit tests
+
+ulimit unlimited       # default to -f
+ulimit -f              # better be unlimited
+ulimit -Sf hard
+
+corelim=${ ulimit -c; }
+
+# pick -c because everyone's going to have it
+ulimit -Sc unlimited
+ulimit -c soft
+ulimit -c              # unlimited
+# maybe someday the leading `+' will be accepted, but not today
+ulimit -c -S -- +1999
+ulimit -c -S -- 1999
+ulimit -c 0
+ulimit -Hc             # hard and soft 0
+ulimit -Sc hard                # should be 0
+ulimit -c
+
+ulimit -a >/dev/null   # just make sure we have no errors
+
+# these are errors
+ulimit -g
+# have to see about this one
+ulimit -u $(( 2**31 - 1 ))
+
+lim=$(ulimit -Sn)
+ulimit -n $lim
+lim2=$(ulimit -n)
+
+[[ $lim -eq $lim2 ]] || echo 'ulimit: setting to soft limit fails' >&2
diff --git a/tests/builtins12.sub b/tests/builtins12.sub
new file mode 100644 (file)
index 0000000..89e23ec
--- /dev/null
@@ -0,0 +1,40 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# pushd/popd/dirs tests
+
+cd /bin
+pushd /tmp
+dirs --
+
+pushd --
+pushd -x
+
+popd --
+popd -x
+
+pushd /
+popd dir       # error
+
+dirs -p
+pushd --
+
+pushd /bin
+
+# out of range errors
+popd -8
+popd +8
+
+# this needs a fix to work right
+popd -- +8
+popd -- -8
diff --git a/tests/builtins8.sub b/tests/builtins8.sub
new file mode 100644 (file)
index 0000000..6b35e79
--- /dev/null
@@ -0,0 +1,85 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+umask 022
+umask u=r+w
+umask -S
+
+umask 022
+umask u=r-w
+umask -S
+
+umask 022
+umask g+u,o+rwx-u
+umask -S
+
+umask 022
+umask u=r+w,g=wx,o+xr
+umask -S
+
+umask 022
+umask u+w=r+x
+umask -S
+
+umask 022
+umask o=u
+umask -S
+
+umask 022
+umask g=u
+umask -S
+
+umask 022
+umask u=rwx,u-w
+umask -S
+
+umask 022
+umask u=xwr
+umask -S
+
+umask 022
+umask +xwr
+umask -S
+
+umask 022
+umask a+xwr
+umask -S
+
+umask 022
+umask +xr
+umask -S
+
+umask 022
+umask a+xr
+umask -S
+
+umask 022
+umask g+X
+umask -S
+
+umask 022
+umask o+X
+umask -S
+
+umask 022
+umask +X
+umask -S
+
+umask 022
+umask g+x,o+x
+umask -S
+
+umask 022
+umask u+g,g+o,o-rw
+umask -S
diff --git a/tests/builtins9.sub b/tests/builtins9.sub
new file mode 100644 (file)
index 0000000..d1fcbec
--- /dev/null
@@ -0,0 +1,59 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# hash printing, deleting tests
+hash -r        # start with empty hash table
+hash   # should be error/warning
+
+hash -d notthere
+echo $?
+
+hash -p /nosuchdir/nosuchfile cat
+
+hash -t cat
+hash -lt cat
+hash -l
+
+hash -d cat
+
+hash -d notthere
+
+hash -lt notthere
+hash -t notthere
+
+hash -t cat 2>/dev/null
+echo $?
+
+# this should fail
+hash -p /nosuchdir/nosuchfile cat
+cat </dev/null
+echo $?
+hash -t cat
+echo $?
+
+# but with checkhash set, it should not
+shopt -s checkhash
+cat </dev/null
+echo $?
+{ hash -t cat | grep cat >/dev/null; } && echo found
+
+hash -r
+hash -p / root
+
+hash -r
+
+# assignment to BASH_CMDS[x] should be like hash -p
+BASH_CMDS[cat]=/nosuchfile
+hash -lt cat
+hash -d cat
diff --git a/tests/comsub2.right b/tests/comsub2.right
new file mode 100644 (file)
index 0000000..3588cca
--- /dev/null
@@ -0,0 +1,192 @@
+aa bb cc dd
+AAaa bb cc ddBB
+aa bb cc dd
+aa bb cc dd
+DDDDDaa bb cc ddEEEEE
+aa bb cc dd
+outside: 42
+aa bb cc dd
+outside:
+assignment: 12
+func () 
+{ 
+    echo func-inside
+}
+abcde
+67890
+12345
+argv[1] = <>
+argv[1] = <>
+aa,bb
+JOBaa bb cc ddCONTROL
+./comsub2.tests: line 68: p: command not found
+NOTFOUND
+./comsub2.tests: line 75: p: command not found
+./comsub2.tests: line 75: p: command not found
+expand_aliases         off
+expand_aliases         off
+outside:
+./comsub2.tests: line 79: alias: p: not found
+alias e='echo inside redefine'
+expand_aliases         off
+1
+expand_aliases         on
+2
+expand_aliases         on
+outside:
+./comsub2.tests: line 89: alias: p: not found
+expand_aliases         on
+1
+xx
+expand_aliases         on
+2
+xx
+expand_aliases         on
+outside:
+expand_aliases         on
+inside: 12 22 42
+outside: 42 2
+newlines
+
+
+outside: 42
+before: 1 2
+after: 2
+before: 1 2
+after: 2
+before: 1 2
+after: 1 2
+XnestedY
+a nested b
+one two
+42
+42
+42
+123
+123
+0
+123
+123
+0
+Mon Aug 29 20:03:02 EDT 2022
+Mon Aug 29 20:03:02 EDT 2022
+Mon Aug 29 20:03:02 EDT 2022
+Mon Aug 29 20:03:02 EDT 2022
+123
+before 123
+in for 123
+outside before: value
+inside before: value
+inside after: funsub
+inside: after false xxx
+outside after: funsub
+=====posix mode=====
+outside before: value
+.
+declare -a a=([0]="1" [1]="2" [2]="3" [3]="4")
+declare -- int="2"
+after here-doc: 1
+[1]-  Running                 sleep 1 &
+[2]+  Running                 sleep 1 &
+[1]-  Running                 sleep 1 &
+[2]+  Running                 sleep 1 &
+17772 26794
+17772 26794
+we should try rhs
+comsub
+and
+funsub
+in here-documents
+after all they work here
+and work here
+a b c == 1 2 3
+ == 1 2 3
+before return
+after func
+1 2 3a b c
+2 2
+foobara b c
+declare -- IFS=" "
+*???
+*???
+yyy zzzz
+argv[1] = <AA^ABB>
+argv[1] = <AA^OBB>
+argv[1] = <AA^?BB>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <AA^ABB>
+argv[1] = <AA^ABB>
+argv[1] = <AA^OBB>
+argv[1] = <AA^OBB>
+argv[1] = <AA^?BB>
+argv[1] = <AA^?BB>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^A>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <^?>
+argv[1] = <AA^ABB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA^ABB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA^?BB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA^?BB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA BB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA BB>
+argv[1] = <AA BB>
+argv[1] = <AA BB>
+argv[1] = <AA>
+argv[2] = <BB>
+argv[1] = <AA BB>
+argv[1] = <AA BB>
+inside1-inside2-outside
+BEFOREAA
+BB
+CC
+AFTER
+BEFOREAA
+BB
+CC
+AFTER
+unbalanced braces}}
+combined comsubs
+combined comsubs
+inside
+after: var = inside
+after: 42 var = inside
+var=inside 42
+after: 0 var = inside
diff --git a/tests/comsub2.tests b/tests/comsub2.tests
new file mode 100644 (file)
index 0000000..73d378f
--- /dev/null
@@ -0,0 +1,153 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# initial set of tests for ${Ccommand; } nofork command substitution
+
+# basic functionality
+
+echo ${ printf '%s\n' aa bb cc dd; }
+echo AA${ printf '%s\n' aa bb cc dd; }BB
+
+echo ${ printf '%s\n' aa bb cc dd; return; echo ee ff; }
+echo ${ printf '%s\n' aa bb cc dd
+       }
+echo DDDDD${
+       printf '%s\n' aa bb cc dd
+}EEEEE
+unset x
+echo ${ printf '%s\n' aa bb cc dd; x=42 ; return 12; echo ee ff; }
+echo outside: $x
+unset x
+echo ${ local x; printf '%s\n' aa bb cc dd; x=42 ; return 12; echo ee ff; }
+echo outside: $x
+xx=${ local x; printf '%s\n' aa bb cc dd; x=42 ; return 12; echo ee ff; }
+echo assignment: $?
+unset xx
+
+declare -i x
+y=${ :;}
+declare -i z
+unset -v x y z
+
+# variables can be local, but all function declarations are global
+func() { echo func-outside; }
+xx=${ func() { echo func-inside; }; }
+declare -f func
+xx=${ unset -f func; }
+declare -f func
+
+echo ${ ( echo abcde );}
+
+echo ${| echo 67890;  REPLY=12345; }           # works in mksh
+x=${| REPLY= ;}
+recho "$x"
+unset x
+x=${| :;}
+recho "$x"
+unset x
+
+echo ${ echo aa; },${ echo bb; }
+
+# basic job control
+set -m
+echo this should disappear | echo JOB${ printf '%s\n' aa bb cc dd; }CONTROL | cat
+set +m
+
+# command not found should still echo error messages to stderr
+echo NOT${ p; }FOUND
+
+# alias handling in command substitutions, default and posix mode
+alias p=printf
+alias e='echo aliasval'
+echo "${ typeset x;
+       for f in 1 2; do p '%s\n' $f ; shopt expand_aliases; done
+       unalias p
+       alias e='echo inside redefine'
+       x=42 ; return; echo this should not be seen; }"
+echo outside: $x
+alias p e
+shopt expand_aliases
+
+alias p=printf
+set -o posix
+echo "${ typeset x;
+       for f in 1 2; do p '%s\n' $f ; shopt expand_aliases; done
+       unalias p;
+       x=42 ; return; echo this should not be seen; }"
+echo outside: $x
+alias p
+shopt expand_aliases
+
+set +o posix
+
+shopt -s expand_aliases
+
+alias p=printf
+echo "${ typeset x;
+       for f in 1 2; do p '%s\n' $f ; /bin/echo xx ; shopt expand_aliases; done
+       x=42 ; return; echo ee ff; }"
+echo outside: $x
+shopt expand_aliases
+
+# more tests for value substitutions and local variables
+a=1 b=2
+a=${| local b ; a=12 ; b=22 ; REPLY=42 ; echo inside: $a $b $REPLY; }
+echo outside: $a $b
+unset a b
+
+# this form doesn't remove the trailing newlines
+REPLY=42
+a=${| REPLY=$'newlines\n\n'; }
+echo "$a"
+echo outside: $REPLY
+
+# how do we handle shift with these weird ksh93 function-like semantics?
+# ksh93 doesn't reset the positional parameters here
+set -- 1 2
+echo before: "$@"
+: "${ shift;}"
+echo after: "$@"
+
+set -- 1 2
+echo before: "$@"
+: "${| shift;}"
+echo after: "$@"
+
+set -- 1 2
+echo before: "$@"
+: "${ ( shift) }"      # parse_comsub adds the closing semicolon anyway
+echo after: "$@"
+
+# nested funsubs
+echo ${ echo X${ echo nested; }Y; }
+echo ${ echo a ; echo ${ echo nested; }; echo b; }
+
+# nested funsubs/comsubs
+x=${
+       echo ${ echo one;} $(echo two)
+}
+echo $x
+
+# mixing funsubs and arithmetic expansion
+echo $(( ${ echo 24 + 18; }))
+echo $(( ${ echo 14 + 18; }+ 10))
+echo ${ echo $(( 24+18 )); }
+
+# alias expansion and nested funsubs in other constructs
+${THIS_SH} ./comsub21.sub
+${THIS_SH} ./comsub22.sub
+${THIS_SH} ./comsub23.sub
+${THIS_SH} ./comsub24.sub
+${THIS_SH} ./comsub25.sub
+${THIS_SH} ./comsub26.sub
diff --git a/tests/comsub21.sub b/tests/comsub21.sub
new file mode 100644 (file)
index 0000000..0837f72
--- /dev/null
@@ -0,0 +1,65 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# posix-mode alias expansion in nofork command substitutions within
+# other constructs
+
+DATE='Mon Aug 29 20:03:02 EDT 2022'
+shopt -s expand_aliases
+
+alias number="echo 123"
+
+echo ${ number; }
+echo $(( ${ number; } ))
+(( ${ number; } )) ; echo $?
+
+set -o posix
+echo ${ number; }
+echo $(( ${ number; } ))
+(( ${ number; } )) ; echo $?
+set +o posix
+
+# have to turn it back on after leaving posix mode
+shopt -s expand_aliases
+
+alias my_alias='echo $DATE'
+
+echo ${ eval my_alias; }
+echo ${ my_alias; }
+
+set -o posix
+echo ${ eval my_alias; }
+echo ${        my_alias; }
+set +o posix ; shopt -s expand_aliases
+
+alias e=echo
+alias v='e 123'
+
+set -o posix
+echo ${ v; }
+echo ${ echo before ; v; }
+echo ${        for f in 0; do
+echo in for
+done; v; }
+set +o posix ; shopt -s expand_aliases
+
+alias let='let --'
+
+let '1 == 1'
+: ${ let '1 == 1'; }
+
+set -o posix
+let '1 == 1'
+: ${ let '1 == 1'; }
+set +o posix ; shopt -s expand_aliases
diff --git a/tests/comsub22.sub b/tests/comsub22.sub
new file mode 100644 (file)
index 0000000..9fdf619
--- /dev/null
@@ -0,0 +1,26 @@
+# tests for inheriting set -e into command substitutions
+
+set -e
+var=value
+echo "outside before: $var"
+echo "${
+       echo "inside before: $var"
+       var=funsub
+       echo "inside after: $var"
+       false;
+       echo inside: after false
+}" xxx
+echo "outside after: $var"
+
+set -o posix
+echo =====posix mode=====
+var=value
+echo "outside before: $var"
+echo "${
+       echo "inside before: $var"
+       var=funsub
+       echo "inside after: $var"
+       false;
+       echo inside: after false
+}" xxx
+echo "outside after: $var"
diff --git a/tests/comsub23.sub b/tests/comsub23.sub
new file mode 100644 (file)
index 0000000..2edda3b
--- /dev/null
@@ -0,0 +1,79 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# more odds and ends for nofork command substitution
+
+# make sure parsing is right within conditional commands
+[[ ${ echo -n "[${ echo -n foo; }]" ; } == '[foo]' ]] || echo bad 1
+[[ "${ echo -n "[${ echo -n foo; }]" ; }" == '[foo]' ]] || echo bad 1
+
+# mix multiple calls to parse_and_execute
+got=$(eval 'x=${ for i in test; do case $i in test) echo .;; esac; done; }' ; echo $x)
+echo $got
+
+# mix compound assignment and nofork command substitution
+: ${ a=(1 2 3 ${ echo 4;} ); }
+declare -p a
+unset a
+
+# function execution with side effects
+int=0
+incr()
+{
+       echo incr: $int
+       (( int++ ))
+}
+: ${ incr; }
+: ${ incr; }
+declare -p int
+
+# expansion inside here-document body
+int=0
+: <<EOF
+${ incr; }
+EOF
+echo after here-doc: $int
+
+# jobs list in nofork command substitution
+sleep 1 &
+sleep 1 &
+
+jl1=${ jobs; }
+printf '%s\n' "$jl1"
+
+jl2=${ jobs; }
+printf '%s\n' "$jl2"
+
+# nofork command substitution doesn't affect the shell's random number sequence
+RANDOM=42
+echo $RANDOM ${ echo $RANDOM; }
+
+RANDOM=42
+echo $RANDOM $RANDOM
+
+# here-documents and other word expansions with comsub/funsub on the rhs
+
+exec 4<<EOF
+we should try rhs
+${word-$(echo comsub)}
+and
+${word-${ echo funsub; }}
+in here-documents
+EOF
+
+cat <&4
+exec 4<&-
+
+echo after all they ${word-$(echo work here)}
+echo and ${word-${ echo work here; }}
diff --git a/tests/comsub24.sub b/tests/comsub24.sub
new file mode 100644 (file)
index 0000000..2d406a7
--- /dev/null
@@ -0,0 +1,77 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# test function calling in nofork comsubs
+set -- 1 2 3
+
+func()
+{
+       echo "$@"
+}
+
+x=${ func a b c; }
+echo "$x" == "$@"
+
+x=${ func; }
+echo "$x" == "$@"
+
+unset -f func
+func()
+{
+       echo before return
+       return 12
+       echo after return
+}
+
+x=${ func; echo after func; }
+echo "$x"
+
+unset -f func
+unset x
+set --
+
+# more shift and order-of-expansion tests
+set -- 1 2 3
+echo "$*${ set -- a b c;}$*"
+
+set -- 1 2
+x=${ shift; echo "$@"; }
+echo $x "$@"
+
+# order of expansion
+
+echo "${ IFS= ; echo foo; }${ unset IFS; echo bar;}${ IFS=' '; echo a b c;}"
+declare -p IFS
+
+IFS=$' \t\n'
+
+: ${TMPDIR:=/tmp}
+TDIR=$TMPDIR/comsub24-$$
+[ -d "${TDIR}" ] || mkdir "${TDIR}"
+cd "${TDIR}" || {
+        echo "comsub24: cannot cd to ${TDIR}"
+        exit 2
+}
+touch xx yy zz
+
+echo "${ set -f; echo '*';}${ set +f; echo '???'; }"
+echo ${ set -f; echo '*';}${ set +f; echo '???'; }
+
+rm xx yy zz
+touch xx yyy zzzz
+
+echo ${ set -f; echo '*';}${ set +f; echo '???'; }
+
+cd $OLDPWD
+rm -rf $TDIR
diff --git a/tests/comsub25.sub b/tests/comsub25.sub
new file mode 100644 (file)
index 0000000..2cbe1c8
--- /dev/null
@@ -0,0 +1,138 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# tests of internal quoting characters in read_comsub and comsub_quote_string
+
+CTLESC=$'\001'
+CTLNUL=$'\177'
+
+x=${| REPLY=$'AA\001BB' ; }
+recho "$x"
+unset x
+
+x=${| REPLY=$'AA\017BB' ; }
+recho "$x"
+unset x
+
+x=${| REPLY=$'AA\177BB' ; }
+recho "$x"
+unset x
+
+x=${| REPLY=$'\001' ; }
+recho "$x"
+unset x
+x=${| REPLY="\ 1" ; }
+recho "$x"
+unset x
+x=${| REPLY=\ 1 ; }
+recho "$x"
+unset x
+x=${| REPLY=$CTLESC ; }
+recho "$x"
+unset x
+x=${| REPLY="$CTLESC" ; }
+recho "$x"
+unset x
+x="${| REPLY="$CTLESC" ; }"
+recho "$x"
+unset x
+
+x=${| REPLY=$'\177' ; }
+recho "$x"
+unset x
+x=${| REPLY="\7f" ; }
+recho "$x"
+unset x
+x=${| REPLY=\7f ; }
+recho "$x"
+unset x
+x=${| REPLY=$CTLNUL ; }
+recho "$x"
+unset x
+x=${| REPLY="$CTLNUL" ; }
+recho "$x"
+unset x
+x="${| REPLY="$CTLNUL" ; }"
+recho "$x"
+unset x
+
+recho "${ echo $'AA\001BB' ; }"
+recho ${ echo $'AA\001BB' ; }
+
+recho "${ echo $'AA\017BB' ; }"
+recho ${ echo $'AA\017BB' ; }
+
+recho "${ echo $'AA\177BB' ; }"
+recho ${ echo $'AA\177BB' ; }
+
+recho "${ echo $'\001' ; }"
+recho ${ echo $'\001' ; }
+
+recho "${ echo "\ 1" ; }"
+recho ${ echo "\ 1" ; }
+recho "${ echo \ 1 ; }"
+recho ${ echo \ 1 ; }
+
+recho "${ echo $CTLESC ; }"
+recho ${ echo $CTLESC ; }
+recho ${ echo "$CTLESC" ; }
+recho "${ echo "$CTLESC" ; }"
+
+recho "${ echo $'\177' ; }"
+recho ${ echo $'\177' ; }
+
+recho "${ echo "\7f" ; }"
+recho ${ echo "\7f" ; }
+recho "${ echo \7f ; }"
+recho ${ echo \7f ; }
+recho "${ echo $CTLNUL ; }"
+recho ${ echo $CTLNUL ; }
+recho "${ echo "$CTLNUL" ; }"
+recho ${ echo "$CTLNUL" ; }
+
+IFS=$CTLESC
+x=${| REPLY=$'AA\001BB' ; }
+recho "$x"
+recho $x
+unset x
+
+recho "${ echo $'AA\001BB' ; }"
+recho ${ echo $'AA\001BB' ; }
+
+IFS=$CTLNUL
+x=${| REPLY=$'AA\177BB' ; }
+recho "$x"
+recho $x
+unset x
+
+recho "${ echo $'AA\177BB' ; }"
+recho ${ echo $'AA\177BB' ; }
+
+unset IFS
+x=${| REPLY=AA" "BB ; }
+recho "$x"
+recho $x
+
+IFS=
+recho "$x"
+recho $x
+unset x
+
+unset IFS
+recho "${ echo AA" "BB ; }"
+recho ${ echo AA" "BB ; }
+
+IFS=
+recho "${ echo AA" "BB ; }"
+recho ${ echo AA" "BB ; }
diff --git a/tests/comsub26.sub b/tests/comsub26.sub
new file mode 100644 (file)
index 0000000..eb17fa3
--- /dev/null
@@ -0,0 +1,36 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# more tests for nofork comsub functionality
+
+REPLY=outside
+
+echo ${| REPLY=inside1; }-${| REPLY=inside2; }-$REPLY
+
+echo "BEFORE${| printf -v REPLY '%s\n' AA BB CC; }AFTER"
+echo "BEFORE${| printf -v REPLY $'%s\n' AA BB CC; }AFTER"
+
+echo ${ echo unbalanced braces; }}}
+
+echo $(echo combined ${| REPLY=comsubs; })
+echo ${ echo $(echo combined ${| REPLY=comsubs; }); }
+
+var=outside
+echo ${ var=inside; echo $var; }
+echo after: var = $var
+
+( echo ${ echo var=inside; exit 42 ; echo var=inside2; } )
+echo after: $? var = $var
+
+( echo ${ echo var=inside; return 42 ; echo var=inside2; } $? )
+echo after: $? var = $var
diff --git a/tests/comsub7.sub b/tests/comsub7.sub
new file mode 100644 (file)
index 0000000..135e3bc
--- /dev/null
@@ -0,0 +1,64 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# posix-mode alias expansion in command substitutions within other constructs
+
+DATE='Mon Aug 29 20:03:02 EDT 2022'
+shopt -s expand_aliases
+
+alias number="echo 123"
+
+echo $(number)
+echo $(( $(number) ))
+(( $(number) )) ; echo $?
+
+set -o posix
+echo $(number)
+echo $(( $(number) ))
+(( $(number) )) ; echo $?
+set +o posix
+
+# have to turn it back on after leaving posix mode
+shopt -s expand_aliases
+
+alias my_alias='echo $DATE'
+
+echo $(eval my_alias)
+echo $(my_alias)
+
+set -o posix
+echo $(eval my_alias)
+echo $(my_alias)
+set +o posix ; shopt -s expand_aliases
+
+alias e=echo
+alias v='e 123'
+
+set -o posix
+echo $(v)
+echo $(echo before ; v)
+echo $(for f in 0; do
+echo in for
+done; v)
+set +o posix ; shopt -s expand_aliases
+
+alias let='let --'
+
+let '1 == 1'
+: $(let '1 == 1')
+
+set -o posix
+let '1 == 1'
+: $(let '1 == 1')
+set +o posix ; shopt -s expand_aliases
diff --git a/tests/cond-error1.sub b/tests/cond-error1.sub
new file mode 100644 (file)
index 0000000..172c3f4
--- /dev/null
@@ -0,0 +1,28 @@
+: ${THIS_SH:=./bash}
+
+# all parse errors
+${THIS_SH} -c '[[ ( -n xx' bash
+${THIS_SH} -c '[[ ( -n xx )' bash
+
+${THIS_SH} -c '[[ ( -t X ) ]' bash
+
+${THIS_SH} -c '[[ -n &' bash
+${THIS_SH} -c '[[ -n XX &' bash
+${THIS_SH} -c '[[ -n XX & ]' bash
+
+${THIS_SH} -c '[[ 4 & ]]' bash
+${THIS_SH} -c '[[ 4 > & ]]' bash
+
+${THIS_SH} -c '[[ & ]]' bash
+${THIS_SH} -c '[[ -Q 7 ]]' bash
+${THIS_SH} -c '[[ -n < ]]' bash
+
+# let's see what failed cond commands do with ERR trap
+trap 'echo ERR: $LINENO: -$BASH_COMMAND- failed' ERR
+[[ -n $unset ]]
+
+func()
+{
+       [[ -z nonempty ]]
+}
+func
diff --git a/tests/cond-xtrace1.sub b/tests/cond-xtrace1.sub
new file mode 100644 (file)
index 0000000..e7bf685
--- /dev/null
@@ -0,0 +1,19 @@
+# printing conditional commands for xtrace
+
+set -x
+
+# error
+[[ -t X ]]
+# null operand
+[[ $b > 7 ]]
+
+# successful unary operator
+[[ -n X ]]
+
+# successful binary operator
+ivar=42
+[[ $ivar -eq 42 ]]
+
+# compound operator, displayed as two unary expressions
+
+[[ -n a && -n b ]]
diff --git a/tests/dollar-star11.sub b/tests/dollar-star11.sub
new file mode 100644 (file)
index 0000000..551c307
--- /dev/null
@@ -0,0 +1,98 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+set aa bb cc -- dd ; f=$'\1' IFS=$f
+
+recho "$f$*$f"
+recho "$f--$f"
+
+[[ $f$*$f == *$f--$f* ]] && echo ok 1
+[[ $f$*$f == "$f--$f" ]] || echo ok 2
+
+[[ ${f}${*}${f} == *$f--$f* ]] && echo ok 3
+
+[[ $f$*$f == $f$*$f ]] && echo ok 4
+[[ ${f}${*}${f} == $f$*$f ]] && echo ok 5
+[[ $f$*$f == *--* ]] && echo ok 6
+
+[[ $* == $* ]] && echo ok 7
+[[ $* == ${*} ]] && echo ok 8
+[[ $f == $f ]] && echo ok 9
+[[ $f == ${f} ]] && echo ok 10
+
+# now with f an array and $f -> ${f[0]}
+
+set aa bb cc -- dd ; f=( $'\1' )
+
+[[ $f$*$f == *$f--$f* ]] && echo ok 11
+[[ ${f}${*}${f} == *$f--$f* ]] && echo ok 12
+[[ $f$*$f == $f$*$f ]] && echo ok 13
+[[ ${f}${*}${f} == $f$*$f ]] && echo ok 14
+[[ $f$*$f == *--* ]] && echo ok 15
+
+[[ $* == $* ]] && echo ok 16
+[[ $* == ${*} ]] && echo ok 17
+[[ $f == $f ]] && echo ok 18
+[[ $f == ${f} ]] && echo ok 19
+
+# now use an array instead of $*
+A=( aa bb cc -- dd ); f=$'\1' IFS=$f
+
+[[ $f${A[*]}$f == $f${A[*]}$f ]] && echo ok 20
+[[ $f${A[*]}$f == *--* ]] && echo ok 21
+[[ ${f}${A[*]}${f} == *$f--$f* ]] && echo ok 22
+
+[[ ${f}${A[*]}${f} == $f${A[*]}$f ]] && echo ok 23
+[[ ${A[*]} == ${A[*]} ]] && echo ok 24
+
+# now test $N/${N}/${A[N]}
+set aa bb $'\1' cc -- dd ; f=$'\1' IFS=$f
+
+[[ $3$*$3 == $3$*$3 ]] && echo ok 25
+[[ $3$*$3 == ${3}${*}${3} ]] && echo ok 26
+[[ $3$*$3 == $3${*}${3} ]] && echo ok 27
+[[ $* == *$3* ]]&& echo ok 28
+[[ $* == *${3}* ]]&& echo ok 29
+
+# now use an array instead of $*
+A=( aa bb $'\1' cc -- dd )
+
+[[ ${A[2]}${A[*]}${A[2]} == ${A[2]}${A[*]}${A[2]} ]] && echo ok 30
+[[ ${A[2]}$*${A[2]} == ${A[2]}${*}${A[2]} ]] && echo ok 31
+[[ ${A[2]}$*${A[2]} == ${A[2]}${*}${A[2]} ]] && echo ok 32
+[[ $* == *${A[2]}* ]]&& echo ok 33
+[[ $* == *${A[2]}* ]]&& echo ok 34
+
+unset -v A
+
+set -- aa bb cc -- dd
+case $* in
+"$*")  echo ok 35;;
+*)     echo bad 35;;
+esac
+
+case $f in
+$f)    echo ok 36;;
+*)     echo bad 36;;
+esac
+
+case $f$*$f in
+$f"$*"$f)      echo ok 37;;
+*)     echo bad 37;;
+esac
+
+case $f$*$f in
+*$f--$f*)      echo ok 38;;
+*)     echo bad 38;;
+esac
diff --git a/tests/errors10.sub b/tests/errors10.sub
new file mode 100644 (file)
index 0000000..4ee2107
--- /dev/null
@@ -0,0 +1,70 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+: ${THIS_SH:=$PWD/bash} ${TMPDIR:=/tmp}
+POSIX_SH="${THIS_SH} -o posix"
+
+# these are the posix special builtins that take a numeric argument
+
+echo invalid numeric argument
+# default mode
+for b in exit break continue shift; do
+       BUILTIN=$b ${THIS_SH} -c 'set -- a b c; (exit 45); for f in _; do $BUILTIN abcde; done; echo after $BUILTIN: $?' bash
+done
+${THIS_SH} -c 'func() { return abcde; echo in func: $?; }; func; echo after return: $?' bash
+
+# posix mode
+for b in exit break continue shift; do
+       BUILTIN=$b ${POSIX_SH} -c 'set -- a b c; (exit 45); for f in _; do $BUILTIN abcde; done; echo after $BUILTIN: $?' bash
+done
+${POSIX_SH} -c 'func() { return abcde; echo in func: $?; }; func; echo after return: $?' bash
+
+# non-special builtins, no difference
+set -o history
+HISTFILE=/dev/null
+echo a >/dev/null
+echo b >/dev/null
+echo c >/dev/null
+history abcde
+echo after history: $?
+history 10 42
+echo after history: $?
+set +o history
+
+# too many arguments
+
+echo too many arguments
+
+TDIR=$TMPDIR/errors-$$
+TFILE=errors
+mkdir $TDIR || exit 1
+cd $TDIR
+
+cat <<\EOF >$TFILE
+set -- a b c
+(exit 45)
+for f in _; do $BUILTIN 42 abcde; done
+echo after $BUILTIN: $?
+EOF
+
+# default mode
+for b in exit return shift break continue; do
+       BUILTIN=$b ${THIS_SH} $TFILE    # TFILE for consistent error messages
+done
+# posix mode
+for b in exit return shift break continue; do
+       BUILTIN=$b ${POSIX_SH} $TFILE   # TFILE for consistent error messages
+done
+
+cd $OLDPWD
+rm -rf $TDIR
diff --git a/tests/errors11.sub b/tests/errors11.sub
new file mode 100644 (file)
index 0000000..28d3e00
--- /dev/null
@@ -0,0 +1,42 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+: ${THIS_SH:=./bash}
+
+${THIS_SH} -c 'readonly non-identifier ; echo after: $?' bash
+${THIS_SH} -c 'export non-identifier ; echo after: $?' bash
+
+${THIS_SH} -c 'readonly non-identifier invalid+ident ; echo after: $?' bash
+${THIS_SH} -c 'export non-identifier invalid+ident ; echo after: $?' bash
+
+${THIS_SH} -o posix -c 'readonly non-identifier ; echo after: $?' sh
+${THIS_SH} -o posix -c 'export non-identifier ; echo after: $?' sh
+
+${THIS_SH} -o posix -c 'readonly non-identifier invalid+ident ; echo after: $?' sh
+${THIS_SH} -o posix -c 'export non-identifier invalid+ident ; echo after: $?' sh
+
+${THIS_SH} -c 'command readonly non-identifier ; echo command: $?' bash
+${THIS_SH} -c 'command export non-identifier ; echo command: $?' bash
+
+${THIS_SH} -o posix -c 'command readonly non-identifier ; echo command: $?' sh
+${THIS_SH} -o posix -c 'command export non-identifier ; echo command: $?' sh
+
+# invalid array references
+
+${THIS_SH} -c 'export AA[4] ; echo array: $?' bash
+${THIS_SH} -c 'readonly AA[4] ; echo array: $?' bash
+
+${THIS_SH} -o posix -c 'export AA[4] ; echo array: $?' sh
+${THIS_SH} -o posix -c 'readonly AA[4] ; echo array: $?' sh
+
+
diff --git a/tests/exec15.sub b/tests/exec15.sub
new file mode 100644 (file)
index 0000000..4a3a088
--- /dev/null
@@ -0,0 +1,72 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# problems with fork optimization in subshells in bash-5.2
+
+: ${TMPDIR:=/tmp}
+TDIR=$TMPDIR/delta-$$
+mkdir "$TDIR" || exit 2
+
+CMD='env true && env ls -1 "$TDIR"'
+
+trap 'rm -rf "$TDIR"' EXIT
+
+cd "$TDIR" &&
+{
+       touch archive install test; 
+       echo "$CMD" > s
+       echo '( echo "1 start" ; . "$TDIR/sub2"  ; echo "1 done" ; exit 42 )' >sub1
+       echo 'echo "2 start" && env echo sub3' >sub2
+} &&
+cd "$OLDPWD"
+
+[ -f "$TDIR"/s ] || exit 2
+
+d2()
+{
+       eval "$1" ; return 78
+}
+
+d1()
+{
+       cmd="$1"
+
+       ( env true && env ls -1 "$TDIR";
+exit 68 )
+       echo $?
+       ( . $TDIR/s ; exit 44 )
+       echo $?
+       ( eval "$cmd" ; exit 86 )
+       echo $?
+       return 43
+}
+
+dfunc()
+{
+       local x="$CMD"
+
+       d1 "$x" || return 2
+       echo oops: after
+}
+
+${THIS_SH} -c '(. <(echo ": && env echo Darwin"); echo x)'
+
+( dfunc xxx )
+echo $?
+v=$(d2 "$CMD")
+echo $?
+
+. $TDIR/sub1
+echo $?
+
+exit 0
diff --git a/tests/extglob8.sub b/tests/extglob8.sub
new file mode 100644 (file)
index 0000000..6b644bb
--- /dev/null
@@ -0,0 +1,33 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# extglob option interaction with other parts of the shell that force
+# extended_glob on; only an issue in compatibility mode
+
+shopt -u extglob
+
+BASH_COMPAT=50
+shopt extglob
+
+echo $(echo $(echo $(echo $(echo $(echo x) ) ) ) )
+shopt extglob
+
+shopt -u extglob
+
+[[ '' = $(shopt extglob >&2) ]]
+shopt extglob
+
+shopt -u extglob
+
+[[ foo = $(: $(shopt extglob >&2)) ]]
+shopt extglob
diff --git a/tests/func5.sub b/tests/func5.sub
new file mode 100644 (file)
index 0000000..bacb82d
--- /dev/null
@@ -0,0 +1,99 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# these are ok
+
+function a=2
+{
+       printf "FUNCNAME: %s\n" $FUNCNAME
+}
+
+function 11111
+{
+       printf "FUNCNAME: %s\n" $FUNCNAME
+}
+
+# but this is still not
+function sys$read
+{
+       printf "FUNCNAME: %s\n" $FUNCNAME
+}
+       
+declare -f
+set -o posix
+declare -f
+set +o posix
+
+a\=2
+
+<(:) ()
+{
+       echo $FUNCNAME
+}
+\<\(:\)
+type '<(:)'
+
+break()
+{
+       echo inside function $FUNCNAME
+}
+
+testfunc()
+{
+       echo type
+       type break
+       type -t break
+       echo command -v
+       command -v break
+       echo command -V
+       command -V break
+
+       echo type -a
+       type -a break
+       echo declare
+       declare -f break
+       echo execution
+       break
+}
+
+set -o posix
+echo posix mode:
+testfunc
+
+set +o posix
+echo default mode:
+testfunc
+unset -f testfunc break
+
+# but in posix mode, declaring such a function is a fatal error
+( set -o posix
+break()
+{
+        echo FUNCNAME: $FUNCNAME
+}
+echo after
+)
+
+# in posix mode, functions whose names are invalid identifiers are fatal errors
+( set -o posix
+!! () { fc -s "$@" ; }
+type \!\!
+)
+
+# but you can create such functions and print them in posix mode
+!! () { fc -s "$@" ; }
+type '!!'
+set -o posix
+type '!!'
+set +o posix
diff --git a/tests/glob-bracket.right b/tests/glob-bracket.right
new file mode 100644 (file)
index 0000000..47c9d9d
--- /dev/null
@@ -0,0 +1,103 @@
+--- $GLOBIGNORE vs fnmatch(3) ---
+#1: pat=ab/cd/efg        yes/yes
+#2: pat=ab[/]cd/efg      no/no
+#3: pat=ab[/a]cd/efg     no/no
+#4: pat=ab[a/]cd/efg     no/no
+#5: pat=ab[!a]cd/efg     no/no
+#6: pat=ab[.-0]cd/efg    no/no
+#7: pat=*/*/efg          yes/yes
+#8: pat=*[/]*/efg        no/no
+#9: pat=*[/a]*/efg       no/no
+#10: pat=*[a/]*/efg       no/no
+#11: pat=*[!a]*/efg       no/no
+#12: pat=*[.-0]*/efg      no/no
+#13: pat=*/*/efg          yes/yes
+#14: pat=*[b]/*/efg       yes/yes
+#15: pat=*[ab]/*/efg      yes/yes
+#16: pat=*[ba]/*/efg      yes/yes
+#17: pat=*[!a]/*/efg      yes/yes
+#18: pat=*[a-c]/*/efg     yes/yes
+#19: pat=ab@(/)cd/efg     yes/yes
+#20: pat=*@(/)cd/efg      no/no
+#21: pat=*/cd/efg         yes/yes
+
+---Tests for a slash in bracket expressions---
+#22: pat=ab[/]ef              str=ab[/]ef          yes/yes
+#23: pat=ab[/]ef              str=ab/ef            no/no
+#24: pat=ab[c/d]ef            str=ab[c/d]ef        yes/yes
+#25: pat=ab[c/d]ef            str=abcef            no/no
+#26: pat=ab[.-/]ef            str=ab[.-/]ef        yes/yes
+#27: pat=ab[.-/]ef            str=ab.ef            no/no
+#28: pat=ab[[=/=]]ef          str=ab[[=/=]]ef      yes/yes
+#29: pat=ab[[=/=]]ef          str=ab/ef            no/no
+#30: pat=ab[[=c=]/]ef         str=ab[=/]ef         yes/yes
+#31: pat=ab[[=c=]/]ef         str=abcef            no/no
+#32: pat=ab[[:alpha:]/]ef     str=ab[:/]ef         yes/yes
+#33: pat=ab[[:alpha:]/]ef     str=abxef            no/no
+#34: pat=ab[/[abc]]ef         str=ab[/c]ef         yes/yes
+#35: pat=ab[/[abc]]ef         str=abc]ef           no/no
+#36: pat=ab[c[=/=]]ef         str=ab[c[=/=]]ef     yes/yes
+#37: pat=ab[c[=/=]]ef         str=abc[=/=]ef       no/no
+#38: pat=ab[c[=/=]]ef         str=abcef            no/no
+#39: pat=a[b\/c]              str=a[b/c]           yes/yes
+#40: pat=a[b\/c]              str=ab               no/no
+#41: pat=a[b\/c]              str=ac               no/no
+
+---Tests for incomplete bracket expressions---
+#42: pat=ab[c                 str=ab[c             yes/yes
+#43: pat=ab[c                 str=abc              no/no
+#44: pat=ab[c[=d=             str=ab[c[=d=         yes/yes
+#45: pat=ab[c[=d=             str=abc              no/no
+#46: pat=ab[c[.d              str=ab[c[.d          yes/yes
+#47: pat=ab[c[.d              str=abc              no/no
+#48: pat=ab[c[:alpha:         str=ab[c[:alpha:     yes/yes
+#49: pat=ab[c[:alpha:         str=abc              no/no
+#50: pat=ab[c-                str=ab[c-            yes/yes
+#51: pat=ab[c-                str=abc              no/no
+#52: pat=ab[c\                str=ab[c\            yes/yes
+#53: pat=ab[c\                str=abc              no/no
+#54: pat=ab[[\                str=ab[[\            yes/yes
+#55: pat=ab[[\                str=ab[              no/no
+
+--- PATSCAN vs BRACKMATCH ---
+#56: pat=@([[.].])A])         str=]                yes/yes
+#57: pat=@([[.].])A])         str===]A])           no/no
+#58: pat=@([[.].])A])         str=AA])             no/no
+#59: pat=@([[=]=])A])         str=]                no/no
+#60: pat=@([[=]=])A])         str===]A])           yes/yes
+#61: pat=@([[=]=])A])         str=AA])             no/no
+
+--- BRACKMATCH: after match vs before match ---
+#62: pat=[[=]=]ab]            str=a                no/no
+#63: pat=[[.[=.]ab]           str=a                yes/yes
+#64: pat=[[.[==].]ab]         str=a                yes/yes
+
+#65: pat=[a[=]=]b]            str=a                no/no
+#66: pat=[a[.[=.]b]           str=a                yes/yes
+#67: pat=[a[.[==].]b]         str=a                yes/yes
+
+#68: pat=[a[=]=]b]            str=b                no/no
+#69: pat=[a[=]=]b]            str=a=]b]            yes/yes
+#70: pat=[a[.[=.]b]           str=b                yes/yes
+#71: pat=[a[.[=.]b]           str=ab]              no/no
+#72: pat=[a[.[==].]b]         str=b                yes/yes
+#73: pat=[a[.[==].]b]         str=ab]              no/no
+
+--- incomplete POSIX brackets ---
+#74: pat=x[a[:y]              str=x[               yes/yes
+#75: pat=x[a[:y]              str=x:               yes/yes
+#76: pat=x[a[:y]              str=xy               yes/yes
+#77: pat=x[a[:y]              str=x[ay             no/no
+
+#78: pat=x[a[.y]              str=x[               yes/yes
+#79: pat=x[a[.y]              str=x.               yes/yes
+#80: pat=x[a[.y]              str=xy               yes/yes
+#81: pat=x[a[.y]              str=x[ay             no/no
+
+#82: pat=x[a[=y]              str=x[               yes/yes
+#83: pat=x[a[=y]              str=x=               yes/yes
+#84: pat=x[a[=y]              str=xy               yes/yes
+#85: pat=x[a[=y]              str=x[ay             no/no
+
+--- MISC tests ---
+#86: pat=a\                   str=a\               yes/yes
diff --git a/tests/glob-bracket.tests b/tests/glob-bracket.tests
new file mode 100644 (file)
index 0000000..057ffeb
--- /dev/null
@@ -0,0 +1,309 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# tests of various aspects of pathname expansion, mostly dealing with bracket
+# expressions
+#
+# Derived from tests contributed by Koichi Murase <myoga.murase@gmail.com>
+
+LC_COLLATE=C
+
+ORIG_DIR=$PWD
+
+: ${TMPDIR:=/tmp} ${BUILD_DIR:=$ORIG_DIR}
+
+trap 'rm -rf $TESTDIR $WORK_DIR' EXIT
+
+WORK_DIR=${TMPDIR}/globtest-$$
+
+mkdir $WORK_DIR || {
+       echo "glob-bracket: cannot create directory $WORK_DIR" >&2
+       exit 1
+}
+cd $WORK_DIR || {
+       echo "glob-bracket: cannot cd to directory $WORK_DIR" >&2
+       exit 1
+}
+
+eval $(grep -E '^(CC |SHOBJ_).*=' $BUILD_DIR/examples/loadables/Makefile | sed -e 's/[ ]*=[ ]*/="/' -e 's/\$@/strmatch/' -e 's/$/"/' )
+
+if [ "$SHOBJ_STATUS" != "supported" ]; then
+       echo "glob-bracket: shared objects not supported, cannot continue" >&2
+       exit 2
+fi
+
+# we assume gcc as a default here
+: ${CC:=gcc}
+
+cat > fnmatch.c <<-EOF
+       #include <fnmatch.h>
+       #include <stdlib.h>
+       #include <stdio.h>
+
+       int main(int argc, char **argv) {
+         if (2 >= argc) {
+           fprintf(stderr, "usage: fnmatch string pattern\n");
+           exit(2);
+         }
+
+#ifdef FNM_EXTMATCH
+         int flags = FNM_PATHNAME | FNM_PERIOD | FNM_EXTMATCH;
+#else
+         int flags = FNM_PATHNAME | FNM_PERIOD;
+#endif
+         if (fnmatch(argv[2], argv[1], flags) == 0)
+           return 0;
+         return 1;
+       }
+EOF
+$CC -O2 -o fnmatch fnmatch.c
+rm -f fnmatch.c
+
+if [ ! -f fnmatch ] ; then
+       echo "glob-bracket: cannot create fnmatch executable" >&2
+       exit 2
+fi
+
+cat > strmatch.c <<-EOF
+       #define BUILTIN_ENABLED 0x01
+       struct word_desc { char* word; int flags; };
+       struct word_list { struct word_list* next; struct word_desc* word; };
+       struct builtin {
+         const char* name;
+         int (*function)(struct word_list*);
+         int flags;
+         const char** long_doc;
+         const char* short_doc;
+         char* handle;
+       };
+
+       /*#include <glob/strmatch.h>*/
+       int strmatch(char *pattern, char *string, int flags);
+       #define FNM_PATHNAME    (1 << 0)
+       #define FNM_NOESCAPE    (1 << 1)
+       #define FNM_PERIOD      (1 << 2)
+       #define FNM_LEADING_DIR (1 << 3)
+       #define FNM_CASEFOLD    (1 << 4)
+       #define FNM_EXTMATCH    (1 << 5)
+       #define FNM_FIRSTCHAR   (1 << 6)
+       #define FNM_DOTDOT      (1 << 7)
+
+       static int strmatch_builtin(struct word_list* list) {
+         char *str, *pat;
+         if (!list || !list->word) return 2;
+         str = list->word->word;
+         if (!list->next || !list->next->word) return 2;
+         pat = list->next->word->word;
+
+         if (strmatch (pat, str, FNM_PATHNAME | FNM_PERIOD | FNM_EXTMATCH) == 0)
+           return 0;
+         return 1;
+       }
+       static const char* strmatch_doc[] = { "This is a builtin to test the behavior of strmatch", 0 };
+       struct builtin strmatch_struct = { "strmatch", strmatch_builtin, BUILTIN_ENABLED, strmatch_doc, "strmatch string pattern", 0, };
+EOF
+
+${SHOBJ_CC} ${SHOBJ_CFLAGS} -c -o strmatch.o strmatch.c
+rm -f strmatch.c
+
+${SHOBJ_LD} ${SHOBJ_LDFLAGS} ${SHOBJ_XLDFLAGS} -o strmatch.so strmatch.o ${SHOBJ_LIBS}
+rm -f strmatch.o
+
+if [ ! -f strmatch.so ] ; then
+       echo "glob-bracket: cannot create strmatch loadable builtin" >&2
+       exit 2
+fi
+
+enable -f ./strmatch.so strmatch || {
+       echo "glob-bracket: cannot load strmatch builtin" >&2
+       exit 2
+}
+       
+check_count=1
+
+if [ -z "$BASH_TSTOUT" ]; then
+       yes=$'\033[32myes\033[m' no=$'\033[31mno\033[m'
+else
+       yes=yes no=no
+fi
+
+function check {
+  # bash impl
+  if strmatch "$2" "$1"; then
+    local strmatch=$yes
+  else
+    local strmatch=$no
+  fi
+
+  # fnmatch
+  local expect=${3-}
+  if [[ ! $expect ]]; then
+    if $WORK_DIR/fnmatch "$2" "$1"; then
+      expect=$yes
+    else
+      expect=$no
+    fi
+  fi
+  printf '#%d: pat=%-20s str=%-16s %s/%s\n' "$((check_count++))" "$1" "$2" "$strmatch" "$expect"
+}
+
+function pcheck {
+  local GLOBIGNORE=$1
+
+  # bash impl
+  local -a f=(*/*/efg*)
+  if [[ $f == '*/*/efg*' ]]; then
+    local strmatch=$yes
+  else
+    local strmatch=$no
+  fi
+
+  # Linux fnmatch
+  local fnmatch=${2-}
+  if [[ ! $fnmatch ]]; then
+    if $WORK_DIR/fnmatch ab/cd/efg "$1"; then
+      fnmatch=$yes
+    else
+      fnmatch=$no
+    fi
+  fi
+
+  printf '#%d: pat=%-16s %s/%s\n' "$((check_count++))" "$1" "$strmatch" "$fnmatch"
+}
+
+TESTDIR=${TMPDIR}/pathtest-$$
+TESTPATH=${TESTDIR}/ab/cd/efg
+mkdir -p $TESTPATH
+
+if [ -d "$TESTPATH" ] && cd "$TESTDIR"; then
+echo '--- $GLOBIGNORE vs fnmatch(3) ---'
+pcheck 'ab/cd/efg'
+pcheck 'ab[/]cd/efg'
+pcheck 'ab[/a]cd/efg'
+pcheck 'ab[a/]cd/efg'
+pcheck 'ab[!a]cd/efg'
+pcheck 'ab[.-0]cd/efg'
+pcheck '*/*/efg'
+pcheck '*[/]*/efg'
+pcheck '*[/a]*/efg'
+pcheck '*[a/]*/efg'
+pcheck '*[!a]*/efg'
+pcheck '*[.-0]*/efg'
+
+pcheck '*/*/efg'
+pcheck '*[b]/*/efg'
+pcheck '*[ab]/*/efg'
+pcheck '*[ba]/*/efg'
+pcheck '*[!a]/*/efg'
+pcheck '*[a-c]/*/efg'
+
+shopt -s extglob
+pcheck 'ab@(/)cd/efg'  "$yes"
+pcheck '*@(/)cd/efg'   "$no"
+pcheck '*/cd/efg'
+shopt -u extglob
+
+cd "$WORK_DIR"
+fi
+
+echo
+echo '---Tests for a slash in bracket expressions---'
+check 'ab[/]ef'          'ab[/]ef'     "$yes"
+check 'ab[/]ef'          'ab/ef'       "$no"
+check 'ab[c/d]ef'        'ab[c/d]ef'   "$yes"
+check 'ab[c/d]ef'        'abcef'       "$no"
+check 'ab[.-/]ef'        'ab[.-/]ef'   "$yes"
+check 'ab[.-/]ef'        'ab.ef'       "$no"
+check 'ab[[=/=]]ef'      'ab[[=/=]]ef' "$yes"
+check 'ab[[=/=]]ef'      'ab/ef'       "$no"
+check 'ab[[=c=]/]ef'     'ab[=/]ef'    "$yes"
+check 'ab[[=c=]/]ef'     'abcef'       "$no"
+check 'ab[[:alpha:]/]ef' 'ab[:/]ef'    "$yes"
+check 'ab[[:alpha:]/]ef' 'abxef'       "$no"
+check 'ab[/[abc]]ef'     'ab[/c]ef'    "$yes"
+check 'ab[/[abc]]ef'     'abc]ef'      "$no"
+check 'ab[c[=/=]]ef'     'ab[c[=/=]]ef' "$yes"
+check 'ab[c[=/=]]ef'     'abc[=/=]ef'   "$no"
+check 'ab[c[=/=]]ef'     'abcef'        "$no"
+check 'a[b\/c]'          'a[b/c]'      "$yes"
+check 'a[b\/c]'          'ab'          "$no"
+check 'a[b\/c]'          'ac'          "$no"
+
+echo
+echo '---Tests for incomplete bracket expressions---'
+check 'ab[c'             'ab[c'         "$yes"
+check 'ab[c'             'abc'          "$no"
+check 'ab[c[=d='         'ab[c[=d='     "$yes"
+check 'ab[c[=d='         'abc'          "$no"
+check 'ab[c[.d'          'ab[c[.d'      "$yes"
+check 'ab[c[.d'          'abc'          "$no"
+check 'ab[c[:alpha:'     'ab[c[:alpha:' "$yes"
+check 'ab[c[:alpha:'     'abc'          "$no"
+check 'ab[c-'            'ab[c-'        "$yes"
+check 'ab[c-'            'abc'          "$no"
+check 'ab[c\'            'ab[c\'        "$yes"
+check 'ab[c\'            'abc'          "$no"
+check 'ab[[\'            'ab[[\'        "$yes"
+check 'ab[[\'            'ab['          "$no"
+
+echo
+echo '--- PATSCAN vs BRACKMATCH ---'
+check '@([[.].])A])' ']'        "$yes"
+check '@([[.].])A])' '==]A])'   "$no"
+check '@([[.].])A])' 'AA])'     "$no"
+check '@([[=]=])A])' ']'        "$no"
+check '@([[=]=])A])' '==]A])'   "$yes"
+check '@([[=]=])A])' 'AA])'     "$no"
+
+echo
+echo '--- BRACKMATCH: after match vs before match ---'
+check '[[=]=]ab]'    'a'     "$no"
+check '[[.[=.]ab]'   'a'     "$yes"
+check '[[.[==].]ab]' 'a'     "$yes"
+echo
+check '[a[=]=]b]'    'a'     "$no"
+check '[a[.[=.]b]'   'a'     "$yes"
+check '[a[.[==].]b]' 'a'     "$yes"
+echo
+check '[a[=]=]b]'    'b'     "$no"
+check '[a[=]=]b]'    'a=]b]' "$yes"
+check '[a[.[=.]b]'   'b'     "$yes"
+check '[a[.[=.]b]'   'ab]'   "$no"
+check '[a[.[==].]b]' 'b'     "$yes"
+check '[a[.[==].]b]' 'ab]'   "$no"
+
+echo
+echo '--- incomplete POSIX brackets ---'
+check 'x[a[:y]' 'x['   "$yes"
+check 'x[a[:y]' 'x:'   "$yes"
+check 'x[a[:y]' 'xy'   "$yes"
+check 'x[a[:y]' 'x[ay' "$no"
+echo                   
+check 'x[a[.y]' 'x['   "$yes"
+check 'x[a[.y]' 'x.'   "$yes"
+check 'x[a[.y]' 'xy'   "$yes"
+check 'x[a[.y]' 'x[ay' "$no"
+echo                   
+check 'x[a[=y]' 'x['   "$yes"
+check 'x[a[=y]' 'x='   "$yes"
+check 'x[a[=y]' 'xy'   "$yes"
+check 'x[a[=y]' 'x[ay' "$no"
+
+echo
+echo '--- MISC tests ---'
+check 'a\'               'a\'          "$yes"
+
+cd $ORIG_DIR
+
+enable -d strmatch     # just testing
+exit 0
diff --git a/tests/glob11.sub b/tests/glob11.sub
new file mode 100644 (file)
index 0000000..ff50efa
--- /dev/null
@@ -0,0 +1,69 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# testing GLOBSORT
+LC_COLLATE=C
+LC_CTYPE=C
+LANG=C
+
+: ${TMPDIR:=/tmp}
+
+TDIR=$TMPDIR/glob-$$
+{ mkdir $TDIR && cd $TDIR; } || exit 1
+
+# try to impose some kind of testable ordering
+echo 123 > mksyntax ; sleep 0.1
+echo 123456 > mksignames ; sleep 0.1
+echo 1234567879 > make_cmd.o ; sleep 0.1
+echo 123456789012 > mailcheck.o ; sleep 0.1
+echo 123456789012345 > mksignames.o ; sleep 0.1
+echo 123456789012345678 > mksyntax.dSYM ; sleep 0.1
+
+echo m*
+GLOBSORT=nosort
+#echo m*                       # might have to take this one out
+unset GLOBSORT
+echo
+
+GLOBSORT=
+echo m*
+GLOBSORT='-name'
+echo m*
+echo
+
+GLOBSORT='+nonsense'
+echo m*
+GLOBSORT='-nonsense'
+echo m*
+echo
+
+GLOBSORT='+atime'
+echo m*
+GLOBSORT='-atime'
+echo m*
+echo
+
+GLOBSORT='+mtime'
+echo m*
+GLOBSORT='-mtime'
+echo m*
+echo
+
+GLOBSORT=size
+echo m*
+GLOBSORT=-size
+echo m*
+
+cd $OLDPWD
+rm -rf $TDIR
diff --git a/tests/heredoc10.sub b/tests/heredoc10.sub
new file mode 100644 (file)
index 0000000..d5d3864
--- /dev/null
@@ -0,0 +1,55 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# test various forms of reading here-documents from an alias
+shopt -s expand_aliases
+
+# single alias definition contains entire here-document
+alias 'heredoc=cat <<EOF
+hello
+world
+EOF'
+heredoc
+
+# here-document body continues after alias definition
+alias 'headplus=cat <<EOF
+hello'
+headplus
+world
+EOF
+
+unalias heredoc headplus
+
+alias head='cat <<END'
+
+head
+here-doc line 1
+here-doc line 2
+END
+
+# here-document delimiter in one alias, body in another
+alias head='cat <<\END' body='head
+here-document
+END'
+body
+
+# make sure delimiter is recognized whether the alias ends with a newline or not
+shopt -s expand_aliases
+alias head='cat <<\END' body='head
+here-document
+END
+'
+body
+
+unalias head body
diff --git a/tests/heredoc8.sub b/tests/heredoc8.sub
new file mode 100644 (file)
index 0000000..97d0ccb
--- /dev/null
@@ -0,0 +1,62 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# problems with comsub printing and re-parsing with here-documents and compound
+# commands in bash-5.2
+
+z=$(
+    f1() {
+        c <<-!
+       !
+    }
+    f2() {
+        :
+    }
+)
+
+z=$(
+    f1() {
+        c
+    }
+    f2() {
+        :
+    }
+)
+
+z=$(
+    f1() {
+        c <<-!
+       !
+    }
+       :
+)
+
+z=$(
+    {
+        : <<-!
+       !
+    }
+    {
+        :
+    }
+)
+
+z=$(
+    for f in 0; do
+        : <<-!
+       !
+    done
+    {
+        :
+    }
+)
diff --git a/tests/heredoc9.sub b/tests/heredoc9.sub
new file mode 100644 (file)
index 0000000..a38dba3
--- /dev/null
@@ -0,0 +1,44 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# various issues with printing here-documents as part of function bodies
+
+foo()
+{
+echo begin
+if cat << HERE
+contents
+HERE
+then
+       echo 1 2
+       echo 3 4
+fi
+}
+
+declare -pf foo
+
+foo()
+{
+echo begin
+while read var << HERE
+contents
+HERE
+do
+       echo 1 2
+       echo 3 4
+done
+}
+
+declare -pf foo
+unset -f foo
diff --git a/tests/history7.sub b/tests/history7.sub
new file mode 100644 (file)
index 0000000..9c9d27b
--- /dev/null
@@ -0,0 +1,74 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# test history file truncation for various values of $HISTFILESIZE
+. ./test-glue-functions
+
+: ${THIS_SH:=./bash}
+: ${TMPDIR:=/var/tmp}
+
+export HISTTIMEFORMAT=
+export HISTFILESIZE=3
+export HISTFILE=$TMPDIR/hist-$$
+export PS1='$ '
+
+rm -f $HISTFILE
+
+# exactly the number of lines
+${THIS_SH} --norc -in <<<$'1\n2\n3' 
+wc -l < $HISTFILE | _cut_leading_spaces
+
+rm -f $HISTFILE
+
+# truncating to fewer lines
+${THIS_SH} --norc -in <<<$'1\n2\n3\n4\n5' 
+wc -l < $HISTFILE | _cut_leading_spaces
+
+rm -f $HISTFILE
+
+# the history file contains fewer lines than $HISTFILESIZE
+${THIS_SH} --norc -in <<<$'1\n2' 
+wc -l < $HISTFILE | _cut_leading_spaces
+
+rm -f $HISTFILE
+
+# now we try it without timestamps
+unset HISTTIMEFORMAT
+
+# exactly the number of lines
+${THIS_SH} --norc -in <<<$'e 1\ne 2\ne 3' 
+wc -l < $HISTFILE | _cut_leading_spaces
+cat $HISTFILE
+
+rm -f $HISTFILE
+
+# truncating to fewer lines
+${THIS_SH} --norc -in <<<$'x 1\nx 2\nx 3\nx 4\nx 5' 
+wc -l < $HISTFILE | _cut_leading_spaces
+cat $HISTFILE
+
+rm -f $HISTFILE
+
+# the history file contains fewer lines than $HISTFILESIZE
+${THIS_SH} --norc -in <<<$'y 1\ny 2' 
+wc -l < $HISTFILE | _cut_leading_spaces
+cat $HISTFILE
+
+rm -f $HISTFILE
+
+# we want to truncate the history file to zero length
+HISTFILESIZE=0
+${THIS_SH} --norc -in <<<$'1\n2' 
+wc -l < $HISTFILE | _cut_leading_spaces
+
+rm -f $HISTFILE
diff --git a/tests/history8.sub b/tests/history8.sub
new file mode 100644 (file)
index 0000000..77659fc
--- /dev/null
@@ -0,0 +1,16 @@
+trap 'rm -f "$OUT"' 0 1 2 3 6 15
+
+HISTFILE=$TMPDIR/fchist-$$ ; OUT=$HISTFILE
+unset HISTIGNORE HISTCONTROL
+set -o history
+
+echo a
+echo b
+echo c
+echo d
+
+history -d 2
+history
+
+history -d 72
+history -d -72
diff --git a/tests/intl4.sub b/tests/intl4.sub
new file mode 100644 (file)
index 0000000..9e58145
--- /dev/null
@@ -0,0 +1,35 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# printf "%ls" and "%lc" format specifiers for multibyte characters with
+# field width and precision in characters instead of bytes
+LC_ALL=en_US.UTF-8
+
+V=ಇಳಿಕೆಗಳು
+V2=${V:0:2}
+V3=${V:0:1}
+
+printf "%ls\n" "$V"
+printf "%ls\n" "$V2"
+printf "%lc\n" "$V"
+printf "%.2ls\n" "$V"
+
+printf "%4.2ls\n" "$V"
+printf "%-4.2ls---\n" "$V"
+
+printf "%ls\n" "$V3"
+printf "%lc\n" "$V3"
+
+printf "%4.2lc\n" "$V3"
+printf "%-4.2lc---\n" "$V3"
diff --git a/tests/invocation.right b/tests/invocation.right
new file mode 100644 (file)
index 0000000..304fc91
--- /dev/null
@@ -0,0 +1,104 @@
+.: .: Is a directory
+bash: -c: option requires an argument
+bash: --badopt: invalid option
+bash [GNU long option] [option] ...
+bash [GNU long option] [option] script-file ...
+GNU long options:
+       --debug
+       --debugger
+       --dump-po-strings
+       --dump-strings
+       --help
+       --init-file
+       --login
+       --noediting
+       --noprofile
+       --norc
+       --posix
+       --pretty-print
+       --rcfile
+       --restricted
+       --verbose
+       --version
+Shell options:
+       -ilrsD or -c command or -O shopt_option         (invocation only)
+       -abefhkmnptuvxBCEHPT or -o option
+bash: --initfile: invalid option
+bash [GNU long option] [option] ...
+bash [GNU long option] [option] script-file ...
+GNU long options:
+       --debug
+       --debugger
+       --dump-po-strings
+       --dump-strings
+       --help
+       --init-file
+       --login
+       --noediting
+       --noprofile
+       --norc
+       --posix
+       --pretty-print
+       --rcfile
+       --restricted
+       --verbose
+       --version
+Shell options:
+       -ilrsD or -c command or -O shopt_option         (invocation only)
+       -abefhkmnptuvxBCEHPT or -o option
+bash: -q: invalid option
+bash [GNU long option] [option] ...
+bash [GNU long option] [option] script-file ...
+GNU long options:
+       --debug
+       --debugger
+       --dump-po-strings
+       --dump-strings
+       --help
+       --init-file
+       --login
+       --noediting
+       --noprofile
+       --norc
+       --posix
+       --pretty-print
+       --rcfile
+       --restricted
+       --verbose
+       --version
+Shell options:
+       -ilrsD or -c command or -O shopt_option         (invocation only)
+       -abefhkmnptuvxBCEHPT or -o option
+this-bash this-bash
+$- for -c includes c
+bash: line 0: badopt: invalid shell option name
+checkwinsize:cmdhist:complete_fullquote:extquote:force_fignore:globasciiranges:globskipdots:hostcomplete:interactive_comments:patsub_replacement:progcomp:promptvars:sourcepath
+checkhash:checkwinsize:cmdhist:complete_fullquote:extquote:force_fignore:globasciiranges:globskipdots:hostcomplete:interactive_comments:patsub_replacement:progcomp:promptvars:sourcepath
+cmdhist:complete_fullquote:extquote:force_fignore:globasciiranges:globskipdots:hostcomplete:interactive_comments:patsub_replacement:progcomp:promptvars:sourcepath
+./invocation1.sub: line 40: BASHOPTS: readonly variable
+braceexpand:hashall:interactive-comments
+braceexpand:hashall:interactive-comments
+hashall:interactive-comments
+hashall:interactive-comments
+braceexpand:hashall:interactive-comments:noglob
+braceexpand:hashall:interactive-comments:noglob
+./invocation2.sub: line 50: SHELLOPTS: readonly variable
+for i in 1 2 3;
+do
+    select var in a b c;
+    do
+        echo $REPLY;
+    done <<< a; echo answer was $REPLY;
+done
+
+for ((i=1; i <= 3; i++ ))
+do
+    echo $(( 2**$i ));
+done
+
+this is bash_logout
+a
+a
+bad-interp
+./invocation.tests: ./x23: nosuchfile: bad interpreter: No such file or directory
+cannot execute binary file
diff --git a/tests/invocation.tests b/tests/invocation.tests
new file mode 100644 (file)
index 0000000..c629c29
--- /dev/null
@@ -0,0 +1,78 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+: ${THIS_SH:=./bash}
+
+# invocation modes and errors
+
+${THIS_SH} .
+
+#${THIS_SH} --version -c 'exit 0' bash
+#${THIS_SH} --help -c 'exit 0' bash
+
+${THIS_SH} -c |& sed 's|^.*/bash|bash|'
+
+${THIS_SH} --badopt |& sed 's|^.*/bash|bash|'
+${THIS_SH} --initfile |& sed 's|^.*/bash|bash|'
+${THIS_SH} -q |& sed 's|^.*/bash|bash|'
+
+export BASH_ARGV0=this-bash
+${THIS_SH} -c 'echo $0 $BASH_ARGV0'
+unset BASH_ARGV0
+
+{ ${THIS_SH} -c 'echo $-' bash | grep c >/dev/null; } && echo '$- for -c includes c'
+
+# BASHOPTS
+${THIS_SH} ./invocation1.sub
+# SHELLOPTS
+${THIS_SH} ./invocation2.sub
+
+# rudimentary pretty-print tests
+${THIS_SH} ./invocation3.sub
+
+: ${TMPDIR:=/tmp}
+TDIR=$TMPDIR/invocation-$$
+mkdir $TDIR || exit 1
+SAVEPWD=$PWD
+
+echo 'echo this is bash_logout' > $TDIR/.bash_logout
+HOME=$TDIR ${THIS_SH} --login -c 'logout'
+rm -f $TDIR/.bash_logout
+
+# script that ends with a comment and no newline
+printf 'echo a # comment' > $TDIR/x23.in
+${THIS_SH} $TDIR/x23.in
+printf 'echo a' > $TDIR/x23.in
+${THIS_SH} $TDIR/x23.in
+rm -f $TDIR/x23.in
+
+# script with invalid interpreter
+cat > $TDIR/x23 <<EOF
+#! nosuchfile
+echo bad-interp
+EOF
+chmod +x $TDIR/x23
+
+# this is fine
+${THIS_SH} $TDIR/x23
+command cd -L $TDIR
+# but this results in a bad-interpreter error
+./x23
+
+# this should result in a cannot execute binary file error since ls is in $PATH
+PATH=/bin:/usr/bin
+${THIS_SH} ls |& sed 's|^.*: ||'
+
+cd $SAVEPWD
+rm -rf $TDIR
diff --git a/tests/invocation1.sub b/tests/invocation1.sub
new file mode 100644 (file)
index 0000000..ea3d68c
--- /dev/null
@@ -0,0 +1,40 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+set +o posix
+shopt -u xpg_echo
+
+# check $BASHOPTS inheritance
+# the first should be an error
+${THIS_SH} -O checkhash -O badopt -c 'echo $BASHOPTS' bash 2>&1 | sed 's|^.*/bash|bash|'
+# this should reflect the default set of options
+${THIS_SH} -c 'echo $BASHOPTS'
+# now let's turn one of them on (checkhash is not enabled by default)
+${THIS_SH} -O checkhash -c 'echo $BASHOPTS' bash
+# let's turn one of them off
+${THIS_SH} +O checkwinsize -c 'echo $BASHOPTS' bash
+
+# turn on a non-default option
+shopt -s checkhash
+export BASHOPTS
+# and make sure the child shell sees it enabled
+if ${THIS_SH} -c 'echo $BASHOPTS' | grep checkhash >/dev/null 2>&1; then
+       :
+else
+       echo 'BASHOPTS: checkhash not inherited correctly'
+fi
+shopt -u checkhash
+export -n BASHOPTS
+
+# but assigning to BASHOPTS is an error
+BASHOPTS=$BASHOPTS
diff --git a/tests/invocation2.sub b/tests/invocation2.sub
new file mode 100644 (file)
index 0000000..05de401
--- /dev/null
@@ -0,0 +1,50 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+set +o posix
+shopt -u xpg_echo
+
+echo $SHELLOPTS
+set +B # on by default
+# shouldn't appear
+echo $SHELLOPTS | grep braceexpand
+export SHELLOPTS
+# but it will appear here because it's enabled by default
+${THIS_SH} -c 'echo $SHELLOPTS'
+
+# turn off an option that's on by default
+${THIS_SH} +B -c 'echo $SHELLOPTS'
+${THIS_SH} +o braceexpand -c 'echo $SHELLOPTS'
+
+# turn on an option that's off by default
+if ${THIS_SH} -f -c 'echo $SHELLOPTS' | grep noglob 2>&1; then
+       :
+else
+       echo 'SHELLOPTS: noglob not inherited correctly'
+fi
+
+# in a different way
+set -o noglob
+if ${THIS_SH} -c 'echo $SHELLOPTS' | grep noglob 2>&1; then
+       :
+else
+       echo 'SHELLOPTS: noglob not inherited correctly'
+fi
+
+# restore default state
+set -o braceexpand
+set +o noglob
+
+# but assigning to SHELLOPTS is an error; have to use set -o/+o
+SHELLOPTS=$SHELLOPTS
diff --git a/tests/invocation3.sub b/tests/invocation3.sub
new file mode 100644 (file)
index 0000000..d764a1e
--- /dev/null
@@ -0,0 +1,25 @@
+: ${THIS_SH:=./bash} ${TMPDIR:=/tmp}
+
+# start at tests for pretty-print mode
+# so far these are cases that aren't handled by the printing code anywhere
+# else in the test suite
+
+SCRIPT=$TMPDIR/pretty-print-$$
+
+cat >$SCRIPT <<\EOF
+for i in 1 2 3
+{
+       select var in a b c; { echo $REPLY; } <<<a
+       echo answer was $REPLY
+}
+
+for (( i=1; i <= 3; i++ ))
+{
+       echo $(( 2**$i ))
+}
+EOF
+
+${THIS_SH} --pretty-print $SCRIPT
+
+rm -f $SCRIPT
+       
diff --git a/tests/jobs8.sub b/tests/jobs8.sub
new file mode 100644 (file)
index 0000000..8bb21bd
--- /dev/null
@@ -0,0 +1,29 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# tests of disown -h
+
+set -m
+
+sleep 1&
+sleep 1&
+sleep 1&
+
+disown -ah
+
+sleep 1&
+sleep 1&
+sleep 1&
+
+disown -rh
diff --git a/tests/jobs9.sub b/tests/jobs9.sub
new file mode 100644 (file)
index 0000000..74d2d2c
--- /dev/null
@@ -0,0 +1,14 @@
+# 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
+# >128, immediately after which the trap associated with that signal shall be
+# taken. 
+
+trap 'echo got $(kill -l $BASH_TRAPSIG)' USR1
+
+sleep 10 &
+( sleep 2 ; kill -USR1 $$ ) &
+
+# should be interrupted by the signal
+wait
+[[ $? -gt 128 ]] || echo 'wait status not greater than 128'
diff --git a/tests/nameref24.sub b/tests/nameref24.sub
new file mode 100644 (file)
index 0000000..8dc4915
--- /dev/null
@@ -0,0 +1,42 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# issues with taking the length of nameref variables
+#
+# just the basics
+
+scalar=foo
+declare -a array=(foo)
+
+declare -n name=array
+declare -n name1=array[0]
+declare -n name2=unset
+declare -n name3=
+declare -n name4='a&b'
+
+echo ${#name}  # length of nameref resolving to set variable
+echo ${#name1} # length of nameref resolving to valid non-identifier
+echo ${#name2} # length of nameref resolving to unset variable
+echo ${#name3} # length of nameref resolving to empty string
+echo ${#name4} # length of nameref resolving to invalid non-identifier
+
+unset -n name name1
+
+declare -n name=scalar
+declare -n name1=scalar[0]
+name4='aa&bb'
+declare -n name4
+
+echo ${#name}  # length of nameref resolving to set variable
+echo ${#name1} # length of nameref resolving to valid non-identifier
+echo ${#name4} # length of nameref resolving to invalid non-identifier
diff --git a/tests/printf5.sub b/tests/printf5.sub
new file mode 100644 (file)
index 0000000..2ba6c73
--- /dev/null
@@ -0,0 +1,36 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+printf "%.4s\n" abcde
+printf "%.2b\n" abcde
+
+printf "%4d\n" 123
+printf "%5i\n" 123
+printf "%4o\n" 123
+printf "%4x\n" 123
+printf "%4X\n" 123
+printf "%7b\n" hello
+printf "%6s\n" hello
+printf "%4u\n" 123
+printf "%2c\n" 65
+
+printf "%-4d--\n" 123
+printf "%-5i--\n" 123
+printf "%-4o--\n" 123
+printf "%-4x--\n" 123
+printf "%-4X--\n" 123
+printf "%-7b--\n" hello
+printf "%-6s--\n" hello
+printf "%-4u--\n" 123
+printf "%-2c--\n" 65
diff --git a/tests/printf6.sub b/tests/printf6.sub
new file mode 100644 (file)
index 0000000..8a0af30
--- /dev/null
@@ -0,0 +1,47 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+LC_ALL=en_US.UTF-8
+
+# this should echo nothing
+printf '%ls'
+# this should echo a null byte
+printf '%lc' | hexdump -b
+
+# this should echo a null byte per posix interp 1647
+printf '%lc' '' | hexdump -b
+
+# test %ls and %lc with multibyte characters
+
+V=ಇಳಿಕೆಗಳು
+V2=${V:0:2}
+V3=${V:0:1}
+
+printf "%ls\n" "$V" | hexdump -b
+printf "%ls\n" "$V2" | hexdump -b
+printf "%lc\n" "$V" | hexdump -b
+printf "%.2ls\n" "$V" | hexdump -b
+
+printf "%4.2ls\n" "$V" | hexdump -b
+printf "%-4.2ls---\n" "$V" | hexdump -b
+
+printf "%ls\n" "$V3" | hexdump -b
+printf "%S\n" "$V3" | hexdump -b
+printf "%lc\n" "$V3" | hexdump -b
+printf "%C\n" "$V3" | hexdump -b
+
+printf "%4.2lc\n" "$V3" | hexdump -b
+printf "%-4.2lc---\n" "$V3" | hexdump -b
+
+# make sure %ls handles 0 precision the same as %s
+printf '[%.0s][%.0ls]\n' X X
diff --git a/tests/printf7.sub b/tests/printf7.sub
new file mode 100644 (file)
index 0000000..471a6ab
--- /dev/null
@@ -0,0 +1,71 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# tests of integer overflow for field width and precision arguments
+INT_MAX=$(getconf INT_MAX)
+TOOBIG=$(( $INT_MAX * 10 ))
+
+printf '[%*s]\n' "${TOOBIG}"
+printf '[%*s]\n' "${TOOBIG}" X
+
+printf -v VAR '[%*s]' "${TOOBIG}"
+echo VAR="$VAR"
+unset -v VAR
+printf -v VAR '[%*s]' "${TOOBIG}" X
+echo VAR="$VAR"
+unset -v VAR
+
+TOOBIG=9223372036854775825
+
+printf '[%*s]\n' "${TOOBIG}"
+printf '[%*s]\n' "${TOOBIG}" X
+
+printf -v VAR '[%*s]' "${TOOBIG}"
+echo VAR="$VAR"
+unset -v VAR
+printf -v VAR '[%*s]' "${TOOBIG}" X
+echo VAR="$VAR"
+unset -v VAR
+
+TOOBIG=$(( $INT_MAX * 10 ))
+
+printf '[%.*s]\n' "${TOOBIG}"
+printf '[%.*s]\n' "${TOOBIG}" X
+
+printf -v VAR '[%.*s]' "${TOOBIG}"
+echo VAR="$VAR"
+unset -v VAR
+printf -v VAR '[%.*s]' "${TOOBIG}" X
+echo VAR="$VAR"
+unset -v VAR
+
+TOOBIG=9223372036854775825
+
+printf '[%.*s]\n' "${TOOBIG}"
+printf '[%.*s]\n' "${TOOBIG}" X
+
+printf -v VAR '[%.*s]' "${TOOBIG}"
+echo VAR="$VAR"
+unset -v VAR
+printf -v VAR '[%.*s]' "${TOOBIG}" X
+echo VAR="$VAR"
+unset -v VAR
+
+# out of range inline precisions
+
+# can't test %s, too many varying implementations
+#printf "%.${TOOBIG}s\n" XY
+printf -v VAR "%.${TOOBIG}s\n" XY
+echo $VAR
+printf "%.${TOOBIG}Q\n" XY
diff --git a/tests/read10.sub b/tests/read10.sub
new file mode 100644 (file)
index 0000000..8bd08fa
--- /dev/null
@@ -0,0 +1,17 @@
+# the read builtin uses a consistent value of IFS to split the input line
+# into fields
+
+IFS=:
+INPUT=',:abc:def,ghi'
+read -r IFS var rest <<<"$INPUT"
+printf 'IFS=[%s] var=[%s] rest=[%s]\n' "$IFS" "$var" "$rest"
+
+IFS=:
+INPUT=':abc:def,ghi'
+read -r IFS var rest <<<"$INPUT"
+printf 'IFS=[%s] var=[%s] rest=[%s]\n' "$IFS" "$var" "$rest"
+
+IFS=:
+INPUT=' :  abc:def,ghi '
+read -r IFS var rest <<<"$INPUT"
+printf 'IFS=[%s] var=[%s] rest=[%s]\n' "$IFS" "$var" "$rest"
diff --git a/tests/read9.sub b/tests/read9.sub
new file mode 100644 (file)
index 0000000..3f56fcb
--- /dev/null
@@ -0,0 +1,41 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+: ${TMPDIR:=/var/tmp}
+
+TESTDIR=${TMPDIR}/read9-test-$$
+mkdir ${TESTDIR}
+cd $TESTDIR ||  {
+        echo "$TESTDIR: cannot cd" >&2
+        exit 1
+}
+
+printf '  line\tb \t\r\f\v\n'  > read9.stdin || exit 1
+printf 'var1="  line", var2="b "\n' > read9.expout || exit 1
+
+IFS=$'\t\r\f\v'
+
+{
+           # line 2
+           unset var1 var2
+           read var1 var2 &&
+           printf 'var1="%s", var2="%s"\n' "$var1" "$var2"
+} < read9.stdin > read9.stdout 2>&1
+
+cmp read9.expout read9.stdout || {
+       echo "read9.sub: expected output and actual output differ"
+       diff read9.expout read9.stdout
+}
+
+cd $OLDPWD
+rm -rf $TESTDIR
diff --git a/tests/redir13.in b/tests/redir13.in
new file mode 100644 (file)
index 0000000..bbcf84c
--- /dev/null
@@ -0,0 +1,5 @@
+# this should be used as standard input to the shell
+exec 4<&0
+cd /tmp
+command pwd
+exec 4<&-
diff --git a/tests/run-comsub2 b/tests/run-comsub2
new file mode 100644 (file)
index 0000000..e88d38f
--- /dev/null
@@ -0,0 +1,2 @@
+${THIS_SH} ./comsub2.tests > ${BASH_TSTOUT} 2>&1
+diff ${BASH_TSTOUT} comsub2.right && rm -f ${BASH_TSTOUT}
diff --git a/tests/run-glob-bracket b/tests/run-glob-bracket
new file mode 100644 (file)
index 0000000..3c240fd
--- /dev/null
@@ -0,0 +1,5 @@
+PATH=$PATH:`pwd`
+export PATH
+
+${THIS_SH} ./glob-bracket.tests > ${BASH_TSTOUT}
+diff ${BASH_TSTOUT} glob-bracket.right && rm -f ${BASH_TSTOUT}
diff --git a/tests/run-invocation b/tests/run-invocation
new file mode 100644 (file)
index 0000000..5851b6c
--- /dev/null
@@ -0,0 +1,2 @@
+${THIS_SH} ./invocation.tests 2>&1 | grep -v '^expect' > ${BASH_TSTOUT}
+diff ${BASH_TSTOUT} invocation.right && rm -f ${BASH_TSTOUT}
diff --git a/tests/set-x2.sub b/tests/set-x2.sub
new file mode 100644 (file)
index 0000000..ca27cb3
--- /dev/null
@@ -0,0 +1,15 @@
+declare -a metas
+set a b c
+
+set -x
+# some time we will have better compound assignment printing (after expansion)
+metas=( \|  \& \; \( \) \< \> $' ' $'\t' $'\n' )
+n=( $@ )
+
+# like regular expansions
+: "${metas[@]}"
+
+DEFAULT_IFS=$' \t\n'
+
+set +x
+declare -p metas
diff --git a/tests/trap7.sub b/tests/trap7.sub
new file mode 100644 (file)
index 0000000..8da401a
--- /dev/null
@@ -0,0 +1,8 @@
+func()
+{
+     eval "trap 'return 42' ERR; false"
+     return 0
+}
+
+set -o errtrace
+func || echo oops: $?
diff --git a/tests/trap8.sub b/tests/trap8.sub
new file mode 100644 (file)
index 0000000..6f719bb
--- /dev/null
@@ -0,0 +1,14 @@
+# tests for traps on SIGCHLD and async commands
+
+set -m
+
+trap 'echo CHLD' SIGCHLD
+
+{ echo before ; : ; echo after; } &
+wait
+
+sleep 1 &
+sleep 1 &
+
+sleep 1
+wait
diff --git a/tests/trap9.sub b/tests/trap9.sub
new file mode 100644 (file)
index 0000000..484f03b
--- /dev/null
@@ -0,0 +1,84 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# return with argument in trap string while function is executing
+trap 'echo BASH_TRAPSIG = $(kill -l $BASH_TRAPSIG); return 7' USR1
+func() { kill -USR1 $$; }
+func
+echo func=$?
+
+# test where return with no argument in a trap string gets its exit status
+# when a function is executing (posix interp 1602)
+
+setexit() { return "$1"; }
+
+# function called in trap string to set $?
+# posix interp 1602: the return in the trap string causees the end of
+# execution of the trap action
+trap 'setexit 222; echo $? ; return' USR1
+process() { kill -USR1 $$; }
+process
+echo exit=$?
+unset -f process
+
+# posix interp 1602; the return in the trap string interrupts loop and causes
+# the end of execution of the trap action
+trap 'setexit 123; return' USR1
+loop() { while :; do :; done; }
+get_loop_exit() { loop; echo "exit=$?"; }
+{ sleep 1; kill -USR1 $$; } &
+get_loop_exit
+unset -f loop get_loop_exit
+
+# posix interp 1602; the return in `check' does not cause the end of execution
+# of the trap action
+check() { false; return; }
+handle() { check && echo B || echo A; }
+trap handle USR1
+kill -USR1 $$
+unset -f check handle
+
+# posix interp 1602: show where we get the value of $? for trap actions
+# return in trap string causes end of execution of trap action
+invoke() { kill -USR1 $$; return 222; }
+trap 'setexit 111; return' USR1
+invoke
+case $? in
+(0)   echo 'In trap-argument: last command preceding the trap action' ;;
+(111) echo 'In trap-argument: last command in the trap action' ;;
+(222) echo 'In trap-argument: (failed to exit the function)' ;;
+(*)   echo 'In trap-argument: (unexpected)' ;;
+esac
+
+# return in `handler' does not cause the end of execution of the trap action
+stat=99
+handler() { setexit 111; return; }
+trap 'handler; stat=$?; return' USR1
+invoke
+case $stat in
+(0)   echo 'In a function call: last command preceding the trap action' ;;
+(111) echo 'In a function call: last command in the trap action' ;;
+(*)   echo 'In a function call: (unexpected)' ;;
+esac
+unset -f invoke handler
+unset -f setexit
+
+# posix interp 1602: return in function does not cause end of trap action
+ReturnFalse()
+{
+       false ; return
+}
+
+trap 'ReturnFalse && echo "woops"' EXIT
+(exit 0)       # set exit status
diff --git a/tests/type5.sub b/tests/type5.sub
new file mode 100644 (file)
index 0000000..42489b0
--- /dev/null
@@ -0,0 +1,59 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# make sure . is not in $PATH
+PATH=/bin:/usr/bin:/usr/local/bin
+OPATH=$PATH
+
+# test type -P
+
+# executable in current directory
+TDIR=$TMPDIR/type-$$
+[ -d "$TDIR" ] || mkdir "$TDIR"
+cd -P "$TDIR" || exit 1
+
+touch e ; chmod +x e; type -P e; rm -f e
+
+shopt -s expand_aliases
+
+type cat
+alias cat='echo cat'
+type -f cat
+type -P cat
+
+# some random tests
+
+# the difference between posix and default modes
+type break
+set -o posix; type break; set +o posix
+
+# test with PATH empty and unset
+realdir=$(pwd -P)
+touch e ; chmod +x e
+
+PATH=
+type -p e
+unset PATH
+z=$(type -p e)
+
+case $z in
+*/e)   ;;
+*)     echo 'type: unset PATH does not prefix with physical path to $PWD' ;;
+esac
+PATH=$OPATH
+
+rm -f e
+
+cd "$OLDPWD"
+rm -rf "$TDIR"
diff --git a/tests/varenv23.sub b/tests/varenv23.sub
new file mode 100644 (file)
index 0000000..03202ad
--- /dev/null
@@ -0,0 +1,95 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# posix rules for assignments preceding export/readonly in functions, using
+# local and global vars; default rules for assignments preceding declare/local
+# in functions, always using local vars.
+#
+# changes to local variables should never propagate upward from the function
+# to its caller, even in posix mode
+
+x=1
+x=4 declare -r x
+declare -p x
+y=2
+y=5 readonly y
+declare -p y
+# can't use x and y from here on
+
+f() { local -r a=3; echo f:$a; }
+f1() { declare -r b=3; echo f1:$b; }
+
+a=4 f
+b=4 f1
+echo global1:$a $b
+
+set -o posix
+a=4 f
+b=4 f1
+echo global2:$a $b
+set +o posix
+
+unset -f f f1
+
+f() { local a=3; readonly a; echo f:$a; }
+f1() { local b=3; declare -r b; echo f1:$b; }
+
+a=4 f
+b=4 f1
+echo global:$a $b
+
+set -o posix
+a=4 f
+b=4 f1
+echo global:$a $b
+set +o posix
+
+unset -f f f1
+
+f() { local a; a=3 readonly a; echo f:$a; }
+
+a=4 f
+echo $a
+set -o posix
+a=4 f
+echo $a
+set +o posix
+
+f1() { a=3 readonly a; echo f1:$a; }
+
+a=7 f1
+echo global: $a
+
+set -o posix
+a=7 f1
+echo global: $a
+set +o posix
+
+unset -f f f1
+# can't use a from here on
+
+c=7 b=8
+f() { c=3 readonly c; echo f: $c; }
+f1() { b=4 declare -r b; echo f1: $b; }
+
+f
+echo global: $(declare -p c)
+
+f1
+echo global: $(declare -p b)
+
+unset -f f f1
+
+# can't use c from here on
+
+
diff --git a/tests/varenv24.sub b/tests/varenv24.sub
new file mode 100644 (file)
index 0000000..272ee4a
--- /dev/null
@@ -0,0 +1,102 @@
+#   This program 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.
+#
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# set of tests for posix interp 1009, unset variable in tempenv
+
+# posix doesn't have local variables, but other shells echo
+# 
+# local local global global
+#
+# ash-based shells unset local x in f1 after call to f2; we can do this
+# with localvar_unset option
+
+f1()
+{
+       local x=local
+       echo $FUNCNAME: x = $x
+       f2
+       echo after f2: x = ${x-unset}
+}
+
+f2()
+{
+       echo $FUNCNAME: x = $x
+       unset x 
+}
+
+x=global
+f1
+echo default after f1: x = $x
+
+shopt -s localvar_unset
+x=global
+f1
+echo localvar_unset after f1: x = $x
+shopt -u localvar_unset
+
+unset -f f1 f2
+unset -v x
+
+# posix says this should echo temp, then global
+
+f1()
+{
+       echo $FUNCNAME: x = $x
+       unset x 
+}
+
+x=global
+x=temp f1
+
+echo after f1: x = $x
+unset -f f1
+unset -v x
+
+# posix says this should echo 'unset'
+# interp 1009
+
+x=global
+x=temp unset x
+echo 1009: after bash unset: x = ${x-unset}
+
+set -o posix
+x=global
+x=temp unset x
+echo 1009: after posix unset: x = ${x-unset}
+set +o posix
+
+unset -v x
+
+# posix says this should echo local, unset, global
+
+f1()
+{
+       local x=local
+       echo $FUNCNAME: x = $x
+       x=temp unset x  
+       echo after unset $FUNCNAME: x = ${x-unset}
+}
+
+x=global
+f1
+echo 1009: after bash f1: x = $x
+
+set -o posix
+x=global
+f1
+echo 1009: after posix f1: x = $x
+set +o posix
+
+unset -f f1
+unset -v x