From: Sean Bright Date: Sat, 13 Dec 2025 20:15:24 +0000 (-0500) Subject: asterisk.c: Allow multi-byte characters on the Asterisk CLI. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27cab02ab21989a8359ff8e1b309f4d7ddee6821;p=thirdparty%2Fasterisk.git asterisk.c: Allow multi-byte characters on the Asterisk CLI. Versions of libedit that support Unicode expect that the EL_GETCFN (the function that does character I/O) will fill in a `wchar_t` with a character, which may be multi-byte. The built-in function that libedit provides, but does not expose with a public API, does properly handle multi-byte sequences. Due to the design of Asterisk's console processing loop, Asterisk provides its own implementation which does not handle multi-byte characters. Changing Asterisk to use libedit's built-in function would be ideal, but would also require changing some fundamental things about console processing which could be fairly disruptive. Instead, we bring in libedit's `read_char` implementation and modify it to suit our specific needs. Resolves: #60 --- diff --git a/main/asterisk.c b/main/asterisk.c index 5c38031cbe..8700a8f7ac 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -246,6 +246,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "../defaults.h" #include "channelstorage.h" +#include "editline_compat.h" /*** DOCUMENTATION @@ -2725,6 +2726,12 @@ static int ast_el_read_char(EditLine *editline, CHAR_T_LIBEDIT *cp) } if (!ast_opt_exec && fds[1].revents) { +#if HAVE_LIBEDIT_IS_UNICODE + num_read = editline_read_char(editline, cp); + if (num_read < 1) { + break; + } +#else char c = '\0'; num_read = read(STDIN_FILENO, &c, 1); @@ -2734,6 +2741,7 @@ static int ast_el_read_char(EditLine *editline, CHAR_T_LIBEDIT *cp) *cp = CHAR_TO_LIBEDIT(c); +#endif return num_read; } @@ -3621,6 +3629,11 @@ int main(int argc, char *argv[]) struct rlimit l; static const char *getopt_settings = "BC:cde:FfG:ghIiL:M:mnpqRrs:TtU:VvWXx:"; + /* Bring in locale settings from the environment. This is needed + for libedit, as the LC_CTYPE category of the locale impacts the + the multi-byte character functions provided by libc */ + setlocale(LC_ALL, ""); + /* Remember original args for restart */ if (argc > ARRAY_LEN(_argv) - 1) { fprintf(stderr, "Truncating argument size to %d\n", (int)ARRAY_LEN(_argv) - 1); diff --git a/main/editline_compat.c b/main/editline_compat.c new file mode 100644 index 0000000000..19464b72b5 --- /dev/null +++ b/main/editline_compat.c @@ -0,0 +1,171 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sean Bright + * + * Sean Bright + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +#include "asterisk.h" + +#include + +#include "editline_compat.h" + +/* + * The `read_char` function below is a modified (see inline comments) + * version of the `read_char` function in libedit's src/read.c file which, + * as of 2026-01-02, can be found here: + * + # https://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libedit/read.c?rev=HEAD + * + * The copyright and license information is reproduced here: + */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +/* read_char(): + * Read a character from the tty. + */ +static int +read_char(EditLine *el, wchar_t *cp) +{ + ssize_t num_read; +#ifdef EDITLINE_ORIG + /* Removed for Asterisk. FIXIO is set when the EL_SAFEREAD + flag is set on the EditLine handle, which we do not do + so `tried` will always be 1 */ + int tried = (el->el_flags & FIXIO) == 0; +#endif + char cbuf[MB_LEN_MAX]; + size_t cbp = 0; +#ifdef EDITLINE_ORIG + /* Removed for Asterisk. This is only needed for FIXIO, + as above. */ + int save_errno = errno; +#endif + + again: +#ifdef EDITLINE_ORIG + /* Removed for Asterisk. libedit only sets up signal handlers + for SIGCONT and SIGWINCH if the EL_SIGNAL flag is set on + the EditLine handle, which we do not do. + + Additionally, we don't have access to the internals of `el` + to get the file descriptor to read from, but we know that + we will always be reading from stdin, so hardcode it */ + el->el_signal->sig_no = 0; + while ((num_read = read(el->el_infd, cbuf + cbp, (size_t)1)) == -1) { + int e = errno; + switch (el->el_signal->sig_no) { + case SIGCONT: + el_wset(el, EL_REFRESH); + /*FALLTHROUGH*/ + case SIGWINCH: + sig_set(el); + goto again; + default: + break; + } + if (!tried && read__fixio(el->el_infd, e) == 0) { + errno = save_errno; + tried = 1; + } else { + errno = e; + *cp = L'\0'; + return -1; + } + } +#else + while ((num_read = read(STDIN_FILENO, cbuf + cbp, (size_t)1)) == -1) { + *cp = L'\0'; + return -1; + } +#endif + + /* Test for EOF */ + if (num_read == 0) { + *cp = L'\0'; + return 0; + } + + for (;;) { + mbstate_t mbs; + + ++cbp; + /* This only works because UTF8 is stateless. */ + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(cp, cbuf, cbp, &mbs)) { + case (size_t)-1: + if (cbp > 1) { + /* + * Invalid sequence, discard all bytes + * except the last one. + */ + cbuf[0] = cbuf[cbp - 1]; + cbp = 0; + break; + } else { + /* Invalid byte, discard it. */ + cbp = 0; + goto again; + } + case (size_t)-2: + if (cbp >= MB_LEN_MAX) { + errno = EILSEQ; + *cp = L'\0'; + return -1; + } + /* Incomplete sequence, read another byte. */ + goto again; + default: + /* Valid character, process it. */ + return 1; + } + } +} + +int editline_read_char(EditLine *el, wchar_t *cp) +{ + return read_char(el, cp); +} diff --git a/main/editline_compat.h b/main/editline_compat.h new file mode 100644 index 0000000000..9548cd5dda --- /dev/null +++ b/main/editline_compat.h @@ -0,0 +1,26 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Sean Bright + * + * Sean Bright + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +#ifndef EDITLINE_COMPAT_PRIVATE_H +#define EDITLINE_COMPAT_PRIVATE_H + +#include + +int editline_read_char(EditLine *el, wchar_t *cp); + +#endif /* EDITLINE_COMPAT_PRIVATE_H */