From: Chet Ramey Date: Thu, 25 Apr 2024 19:50:39 +0000 (-0400) Subject: add some missing files, update copyrights X-Git-Tag: bash-5.3-alpha X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=aadb6ffb93359891760c58008539f549f06c5140;p=thirdparty%2Fbash.git add some missing files, update copyrights --- diff --git a/cross-build/msys32.cache b/cross-build/msys32.cache new file mode 100644 index 00000000..7d9f257d --- /dev/null +++ b/cross-build/msys32.cache @@ -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 index 00000000..ff912efe --- /dev/null +++ b/examples/loadables/kv.c @@ -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 . +*/ + +/* See Makefile for compilation details. */ + +#include + +#include +#if defined (HAVE_UNISTD_H) +# include +#endif +#include "bashansi.h" +#include "posixstat.h" +#include +#include + +#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 index 00000000..194ca470 --- /dev/null +++ b/examples/loadables/strptime.c @@ -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 . +*/ + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashtypes.h" +#include "posixtime.h" + +#include + +#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 index 00000000..e26802a3 --- /dev/null +++ b/examples/scripts/secure-script @@ -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 index 00000000..c8a87d2b --- /dev/null +++ b/include/intprops-internal.h @@ -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 . */ + +#ifndef _GL_INTPROPS_INTERNAL_H +#define _GL_INTPROPS_INTERNAL_H + +#include + +/* 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 + . */ +#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 + . */ +# 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 . */ +#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 . */ +# 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 +#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 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 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 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 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 index 00000000..91848806 --- /dev/null +++ b/include/stdckdint.in.h @@ -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 . */ + +#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 index 00000000..659e7986 --- /dev/null +++ b/include/unlocked-io.h @@ -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 . */ + +/* 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 + +# 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 index 00000000..0fa737ff --- /dev/null +++ b/lib/intl/arg-nonnull.h @@ -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 . */ + +/* _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 index 00000000..45b953c4 --- /dev/null +++ b/lib/intl/attribute.h @@ -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 . */ + +/* 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 index 00000000..10253e33 --- /dev/null +++ b/lib/intl/export.h @@ -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 index 00000000..01353124 --- /dev/null +++ b/lib/intl/filename.h @@ -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 . */ + +/* From Paul Eggert and Jim Meyering. */ + +#ifndef _FILENAME_H +#define _FILENAME_H + +#include + +#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 index 00000000..5a531726 --- /dev/null +++ b/lib/intl/flexmember.h @@ -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 . + + Written by Paul Eggert. */ + +#include + +/* 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 index 00000000..a81f1148 --- /dev/null +++ b/lib/intl/hash-string.c @@ -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 + . */ + +#ifdef HAVE_CONFIG_H +# include +#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 index 00000000..56542644 --- /dev/null +++ b/lib/intl/intl-exports.c @@ -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 , 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 . */ + + /* 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 index 00000000..f774ba2b --- /dev/null +++ b/lib/intl/langprefs.c @@ -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 . */ + +/* Written by Bruno Haible . + Win32 code originally by Michele Cicciotti . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE +# include +# if HAVE_CFLOCALECOPYPREFERREDLANGUAGES +# include +# elif HAVE_CFPREFERENCESCOPYAPPVALUE +# include +# endif +# include +# include +# include +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 + +# 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 index 00000000..9686feb7 --- /dev/null +++ b/lib/intl/libgnuintl.in.h @@ -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 . */ + +#ifndef _LIBINTL_H +#define _LIBINTL_H 1 + +#include +#if (defined __APPLE__ && defined __MACH__) && @HAVE_NEWLOCALE@ +# include +#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, defines __LOCALE_H (or _LOCALE_H in Solaris 2.5) + then includes (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 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 . + 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 or "gettext.h". */ + +#if !@HAVE_POSIX_PRINTF@ + +# include +# include + +/* Get va_list. */ +# if (defined __STDC__ && __STDC__) || defined __cplusplus || defined _MSC_VER +# include +# else +# include +# 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 index 00000000..10be7bed --- /dev/null +++ b/lib/intl/localename-table.c @@ -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 . */ + +/* Written by Bruno Haible , 2018. */ + +#include + +#if HAVE_WORKING_USELOCALE && HAVE_NAMELESS_LOCALES + +/* Specification. */ +#include "localename-table.h" + +#include + +/* 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 index 00000000..6b2a807b --- /dev/null +++ b/lib/intl/localename-table.in.h @@ -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 . */ + +/* Written by Bruno Haible , 2018. */ + +#if HAVE_WORKING_USELOCALE && HAVE_NAMELESS_LOCALES + +# include +# include + +# 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 index 00000000..f509e217 --- /dev/null +++ b/lib/intl/lock.c @@ -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 . */ + +/* Written by Bruno Haible , 2005. + Based on GCC's gthr-posix.h, gthr-posix95.h. */ + +#include + +#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 index 00000000..86dcd016 --- /dev/null +++ b/lib/intl/lock.h @@ -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 . */ + +/* Written by Bruno Haible , 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 +#include + +#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 +# 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 + +# 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 + +# 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 + +# 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 index 00000000..f894a94d --- /dev/null +++ b/lib/intl/plural.h @@ -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 . */ + +/* 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 index 00000000..af120baa --- /dev/null +++ b/lib/intl/printf-args.c @@ -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 . */ + +/* 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 +#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 index 00000000..a0f71595 --- /dev/null +++ b/lib/intl/printf-args.h @@ -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 . */ + +#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 + +/* Get wchar_t. */ +#if HAVE_WCHAR_T +# include +#endif + +/* Get wint_t. */ +#if HAVE_WINT_T +# include +#endif + +/* Get va_list. */ +#include + + +/* 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 index 00000000..7de467a1 --- /dev/null +++ b/lib/intl/printf-parse.c @@ -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 . */ + +/* 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 +#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 + +/* Get intmax_t. */ +#if defined IN_LIBINTL || defined IN_LIBASPRINTF +# if HAVE_STDINT_H_WITH_UINTMAX +# include +# endif +# if HAVE_INTTYPES_H_WITH_UINTMAX +# include +# endif +#else +# include +#endif + +/* malloc(), realloc(), free(). */ +#include + +/* memcpy(). */ +#include + +/* errno. */ +#include + +/* 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 index 00000000..c7c55045 --- /dev/null +++ b/lib/intl/printf-parse.h @@ -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 . */ + +#ifndef _PRINTF_PARSE_H +#define _PRINTF_PARSE_H + +#if HAVE_FEATURES_H +# include /* 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 index 00000000..b84daf07 --- /dev/null +++ b/lib/intl/printf.c @@ -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 , 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef __GNUC__ +# define alloca __builtin_alloca +# define HAVE_ALLOCA 1 +#else +# ifdef _MSC_VER +# include +# define alloca _alloca +# else +# if defined HAVE_ALLOCA_H || defined _LIBC +# include +# else +# ifdef _AIX + #pragma alloca +# else +# ifndef alloca +char *alloca (); +# endif +# endif +# endif +# endif +#endif + +#include + +#if !HAVE_POSIX_PRINTF + +#include +#include +#include +#include + +/* 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 + +#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 index 00000000..b824e383 --- /dev/null +++ b/lib/intl/setlocale-lock.c @@ -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 . */ + +/* Written by Bruno Haible , 2019. */ + +#include + +/* 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 + +# 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 + +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 +# include + +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 index 00000000..2995db1c --- /dev/null +++ b/lib/intl/setlocale.c @@ -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 , 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#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 +#include +#include +#include + +/* 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 +# elif HAVE_CFPREFERENCESCOPYAPPVALUE +# include +# endif +# include +# include +# include +#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 ". + 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 index 00000000..e62b3a2b --- /dev/null +++ b/lib/intl/setlocale_null.c @@ -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 . */ + +/* Written by Bruno Haible , 2019. */ + +#include + +#define SETLOCALE_NULL_ALL_MTSAFE 1 +#define SETLOCALE_NULL_ONE_MTSAFE 1 + +/* Specification. */ +#include "setlocale_null.h" + +#include +#include +#include +#include +#if defined _WIN32 && !defined __CYGWIN__ +# include +#endif + +#if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) +# if defined _WIN32 && !defined __CYGWIN__ + +# define WIN32_LEAN_AND_MEAN /* avoid including junk */ +# include + +# elif HAVE_PTHREAD_API + +# include +# if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS +# include +# 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 + +# 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 index 00000000..b6c5c315 --- /dev/null +++ b/lib/intl/setlocale_null.h @@ -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 . */ + +/* Written by Bruno Haible , 2019. */ + +#ifndef _SETLOCALE_NULL_H +#define _SETLOCALE_NULL_H + +#include + +#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 index 00000000..ef38ceb8 --- /dev/null +++ b/lib/intl/thread-optim.h @@ -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 . */ + +/* Written by Bruno Haible , 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 +# 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 index 00000000..9840ab49 --- /dev/null +++ b/lib/intl/threadlib.c @@ -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 . */ + +/* Written by Bruno Haible , 2005. */ + +#include + +/* ========================================================================= */ + +#if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS + +/* Use the POSIX threads library. */ + +# include +# include +# include + +# 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 index 00000000..9e191ef8 --- /dev/null +++ b/lib/intl/tsearch.c @@ -0,0 +1,682 @@ +/* Copyright (C) 1995-1997, 2000, 2006 Free Software Foundation, Inc. + Contributed by Bernd Schmidt , 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 . */ + +/* 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 + +/* Specification. */ +#ifdef IN_LIBINTL +# include "tsearch.h" +#else +# include +#endif + +#include + +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 + +#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 index 00000000..e3a5ca44 --- /dev/null +++ b/lib/intl/tsearch.h @@ -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 . */ + +#ifndef _TSEARCH_H +#define _TSEARCH_H + +#if HAVE_TSEARCH + +/* Get tseach(), tfind(), tdelete(), twalk() declarations. */ +#include + +#else + +#ifdef __cplusplus +extern "C" { +#endif + +/* See , + + 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 index 00000000..5a7bf818 --- /dev/null +++ b/lib/intl/vasnprintf.c @@ -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 . */ + +/* 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 to provide a prototype for snprintf(). + This must come before because may include + , and once has been included, it's too late. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#ifndef VASNPRINTF +# include +#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 +#endif + +/* Specification. */ +#ifndef VASNPRINTF +# if WIDE_CHAR_VERSION +# include "vasnwprintf.h" +# else +# include "vasnprintf.h" +# endif +#endif + +#include /* localeconv() */ +#include /* snprintf(), sprintf() */ +#include /* abort(), malloc(), realloc(), free() */ +#include /* memcpy(), strlen() */ +#include /* errno */ +#include /* CHAR_BIT */ +#include /* DBL_MAX_EXP, LDBL_MAX_EXP */ +#if HAVE_NL_LANGINFO +# include +#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 +# include "float+.h" +#endif + +#if (NEED_PRINTF_DOUBLE || NEED_PRINTF_INFINITE_DOUBLE) && !defined IN_LIBINTL +# include +# include "isnand-nolibm.h" +#endif + +#if (NEED_PRINTF_LONG_DOUBLE || NEED_PRINTF_INFINITE_LONG_DOUBLE) && !defined IN_LIBINTL +# include +# include "isnanl-nolibm.h" +# include "fpucw.h" +#endif + +#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_DOUBLE) && !defined IN_LIBINTL +# include +# include "isnand-nolibm.h" +# include "printf-frexp.h" +#endif + +#if (NEED_PRINTF_DIRECTIVE_A || NEED_PRINTF_LONG_DOUBLE) && !defined IN_LIBINTL +# include +# 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=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=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=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 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, 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 + ). + 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 + and + + 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 index 00000000..e58bd40c --- /dev/null +++ b/lib/intl/vasnprintf.h @@ -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 . */ + +#ifndef _VASNPRINTF_H +#define _VASNPRINTF_H + +/* Get va_list. */ +#include + +/* Get size_t. */ +#include + +#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 index 00000000..05ac0210 --- /dev/null +++ b/lib/intl/vasnwprintf.h @@ -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 . */ + +#ifndef _VASNWPRINTF_H +#define _VASNWPRINTF_H + +/* Get va_list. */ +#include + +/* Get wchar_t, size_t. */ +#include + +#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 index 00000000..42307c0b --- /dev/null +++ b/lib/intl/verify.h @@ -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 . */ + +/* 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 , included by 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 +# 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 + 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. + ), 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 + . 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 +# 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 index 00000000..d62a28ba --- /dev/null +++ b/lib/intl/version.c @@ -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 . */ + +#ifdef HAVE_CONFIG_H +# include +#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 index 00000000..ec16ca45 --- /dev/null +++ b/lib/intl/wprintf-parse.h @@ -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 . */ + +#ifndef _WPRINTF_PARSE_H +#define _WPRINTF_PARSE_H + +#if HAVE_FEATURES_H +# include /* 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 index 00000000..4b4914c2 --- /dev/null +++ b/lib/intl/xsize.c @@ -0,0 +1,3 @@ +#include +#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 index 00000000..0bfa4c88 --- /dev/null +++ b/lib/intl/xsize.h @@ -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 . */ + +#ifndef _XSIZE_H +#define _XSIZE_H + +/* Get size_t. */ +#include + +/* Get SIZE_MAX. */ +#include +#if HAVE_STDINT_H +# include +#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 index 00000000..7be3156d --- /dev/null +++ b/lib/malloc/sbrk.c @@ -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 . +*/ +#include +#include +#include + +#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 index 00000000..b3c8957c --- /dev/null +++ b/lib/sh/anonfile.c @@ -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 . +*/ + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#if defined (HAVE_MEMFD_CREATE) || defined (HAVE_SHM_OPEN) || defined (HAVE_SHM_MKSTEMP) +# include +#endif +#include + +#include + +#include +#include + +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 index 00000000..a0e4702b --- /dev/null +++ b/lib/sh/compat.c @@ -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 . +*/ + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +/* 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 index 00000000..3b230aa1 --- /dev/null +++ b/lib/sh/mbsncmp.c @@ -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 . +*/ + +#include + +#if !defined (HAVE_MBSNCMP) && defined (HANDLE_MULTIBYTE) + +#include +#include +#include + +#include + +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 index 00000000..53d7f779 --- /dev/null +++ b/lib/sh/reallocarray.c @@ -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 . +*/ + +#include + +#include "bashansi.h" +#include + +#include +#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 index 00000000..e4fbaabd --- /dev/null +++ b/lib/sh/strlcpy.c @@ -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 . +*/ + +#include + +#include + +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 index 00000000..534fa79a --- /dev/null +++ b/lib/sh/strscpy.c @@ -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 . +*/ + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +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 index 00000000..f8120375 --- /dev/null +++ b/m4/bison.m4 @@ -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 index 00000000..2718a175 --- /dev/null +++ b/m4/c-bool.m4 @@ -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 index 00000000..3b4237e9 --- /dev/null +++ b/m4/flexmember.m4 @@ -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 + #include + #include + 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 index 00000000..ca5d0d0f --- /dev/null +++ b/m4/locale_h.m4 @@ -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 to define locale_t and the int_p_*, int_n_* + dnl members of 'struct lconv'. + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + + dnl If is replaced, then 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 + 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 + 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 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 +/* Some systems provide declarations in a non-standard header. */ +#if HAVE_XLOCALE_H +# include +#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 to define locale_t. + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + + dnl Check whether use of locale_t requires inclusion of , + dnl e.g. on Mac OS X 10.5. If does not define locale_t by + dnl itself, we assume that 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_t x;]], + [[]])], + [gl_cv_header_locale_has_locale_t=yes], + [gl_cv_header_locale_has_locale_t=no]) + ]) + + dnl Check for . + 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 index 00000000..ff14f349 --- /dev/null +++ b/m4/unlocked-io.m4 @@ -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 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]) +]) diff --git a/patchlevel.h b/patchlevel.h index ac3c2d29..35fa324f 100644 --- a/patchlevel.h +++ b/patchlevel.h @@ -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 index 00000000..b2aeb150 --- /dev/null +++ b/tests/alias7.sub @@ -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 . +# + +# 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 index 00000000..811139ed --- /dev/null +++ b/tests/arith9.sub @@ -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 index 00000000..c1ea7848 --- /dev/null +++ b/tests/array31.sub @@ -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 . +# + +# 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 index 00000000..5192d49d --- /dev/null +++ b/tests/array32.sub @@ -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 . +# +# 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"] <<. +# + +# 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 . +# +# 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 index 00000000..e8413f81 --- /dev/null +++ b/tests/builtins10.sub @@ -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 . +# +# 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 index 00000000..2f41df4f --- /dev/null +++ b/tests/builtins11.sub @@ -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 . +# +# 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 index 00000000..89e23ec1 --- /dev/null +++ b/tests/builtins12.sub @@ -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 . +# +# 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 index 00000000..6b35e795 --- /dev/null +++ b/tests/builtins8.sub @@ -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 . +# + +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 index 00000000..d1fcbec9 --- /dev/null +++ b/tests/builtins9.sub @@ -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 . +# + +# 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 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 index 00000000..3588cca7 --- /dev/null +++ b/tests/comsub2.right @@ -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] = +argv[1] = +argv[1] = +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] = +argv[1] = +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] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +argv[1] = +argv[1] = +argv[2] = +argv[1] = +argv[1] = +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 index 00000000..73d378ff --- /dev/null +++ b/tests/comsub2.tests @@ -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 . +# + +# 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 index 00000000..0837f723 --- /dev/null +++ b/tests/comsub21.sub @@ -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 . +# + +# 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 index 00000000..9fdf6199 --- /dev/null +++ b/tests/comsub22.sub @@ -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 index 00000000..2edda3bf --- /dev/null +++ b/tests/comsub23.sub @@ -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 . +# + +# 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 +: <. +# + +# 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 index 00000000..2cbe1c83 --- /dev/null +++ b/tests/comsub25.sub @@ -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 . +# + +# 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="" ; } +recho "$x" +unset x +x=${| REPLY= ; } +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="" ; } +recho "$x" +unset x +x=${| REPLY= ; } +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 "" ; }" +recho ${ echo "" ; } +recho "${ echo  ; }" +recho ${ echo  ; } + +recho "${ echo $CTLESC ; }" +recho ${ echo $CTLESC ; } +recho ${ echo "$CTLESC" ; } +recho "${ echo "$CTLESC" ; }" + +recho "${ echo $'\177' ; }" +recho ${ echo $'\177' ; } + +recho "${ echo "" ; }" +recho ${ echo "" ; } +recho "${ echo  ; }" +recho ${ echo  ; } +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 index 00000000..eb17fa3e --- /dev/null +++ b/tests/comsub26.sub @@ -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 . +# +# 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 index 00000000..135e3bc0 --- /dev/null +++ b/tests/comsub7.sub @@ -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 . +# + +# 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 index 00000000..172c3f46 --- /dev/null +++ b/tests/cond-error1.sub @@ -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 index 00000000..e7bf6859 --- /dev/null +++ b/tests/cond-xtrace1.sub @@ -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 index 00000000..551c3076 --- /dev/null +++ b/tests/dollar-star11.sub @@ -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 . +# + +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 index 00000000..4ee21076 --- /dev/null +++ b/tests/errors10.sub @@ -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 . +# +: ${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 index 00000000..28d3e009 --- /dev/null +++ b/tests/errors11.sub @@ -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 . +# +: ${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 index 00000000..4a3a0880 --- /dev/null +++ b/tests/exec15.sub @@ -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 . +# +# 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 index 00000000..6b644bb5 --- /dev/null +++ b/tests/extglob8.sub @@ -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 . +# +# 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 index 00000000..bacb82db --- /dev/null +++ b/tests/func5.sub @@ -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 . +# + +# 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 index 00000000..47c9d9d1 --- /dev/null +++ b/tests/glob-bracket.right @@ -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 index 00000000..057ffeb2 --- /dev/null +++ b/tests/glob-bracket.tests @@ -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 . +# +# tests of various aspects of pathname expansion, mostly dealing with bracket +# expressions +# +# Derived from tests contributed by Koichi Murase + +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 + #include + #include + + 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 */ + 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 index 00000000..ff50efa4 --- /dev/null +++ b/tests/glob11.sub @@ -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 . +# + +# 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 index 00000000..d5d3864b --- /dev/null +++ b/tests/heredoc10.sub @@ -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 . +# + +# test various forms of reading here-documents from an alias +shopt -s expand_aliases + +# single alias definition contains entire here-document +alias 'heredoc=cat <. +# +# 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 index 00000000..a38dba34 --- /dev/null +++ b/tests/heredoc9.sub @@ -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 . +# + +# 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 index 00000000..9c9d27b4 --- /dev/null +++ b/tests/history7.sub @@ -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 . +# +# 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 index 00000000..77659fc6 --- /dev/null +++ b/tests/history8.sub @@ -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 index 00000000..9e58145d --- /dev/null +++ b/tests/intl4.sub @@ -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 . +# + +# 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 index 00000000..304fc91d --- /dev/null +++ b/tests/invocation.right @@ -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 index 00000000..c629c297 --- /dev/null +++ b/tests/invocation.tests @@ -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 . +# + +: ${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 <. +# +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 index 00000000..05de4019 --- /dev/null +++ b/tests/invocation2.sub @@ -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 . +# + +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 index 00000000..d764a1e0 --- /dev/null +++ b/tests/invocation3.sub @@ -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; } <<. +# + +# 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 index 00000000..74d2d2c2 --- /dev/null +++ b/tests/jobs9.sub @@ -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 index 00000000..8dc4915e --- /dev/null +++ b/tests/nameref24.sub @@ -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 . +# +# 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 index 00000000..2ba6c73a --- /dev/null +++ b/tests/printf5.sub @@ -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 . +# + +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 index 00000000..8a0af300 --- /dev/null +++ b/tests/printf6.sub @@ -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 . +# +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 index 00000000..471a6ab0 --- /dev/null +++ b/tests/printf7.sub @@ -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 . +# + +# 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 index 00000000..8bd08fa6 --- /dev/null +++ b/tests/read10.sub @@ -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 index 00000000..3f56fcbe --- /dev/null +++ b/tests/read9.sub @@ -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 . +# +: ${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 index 00000000..bbcf84cf --- /dev/null +++ b/tests/redir13.in @@ -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 index 00000000..e88d38ff --- /dev/null +++ b/tests/run-comsub2 @@ -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 index 00000000..3c240fd6 --- /dev/null +++ b/tests/run-glob-bracket @@ -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 index 00000000..5851b6ca --- /dev/null +++ b/tests/run-invocation @@ -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 index 00000000..ca27cb3e --- /dev/null +++ b/tests/set-x2.sub @@ -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 index 00000000..8da401a2 --- /dev/null +++ b/tests/trap7.sub @@ -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 index 00000000..6f719bbf --- /dev/null +++ b/tests/trap8.sub @@ -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 index 00000000..484f03be --- /dev/null +++ b/tests/trap9.sub @@ -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 . +# + +# 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 index 00000000..42489b05 --- /dev/null +++ b/tests/type5.sub @@ -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 . +# + +# 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 index 00000000..03202ad4 --- /dev/null +++ b/tests/varenv23.sub @@ -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 . +# +# 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 index 00000000..272ee4ad --- /dev/null +++ b/tests/varenv24.sub @@ -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 . +# + +# 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