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
20
Sean Bright 4 weeks ago
parent 33971a173e
commit 36465a4ceb

@ -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
<managerEvent language="en_US" name="FullyBooted">
@ -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);

@ -0,0 +1,171 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sean Bright
*
* Sean Bright <sean@seanbright.com>
*
* 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 <histedit.h>
#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);
}

@ -0,0 +1,26 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sean Bright
*
* Sean Bright <sean@seanbright.com>
*
* 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 <histedit.h>
int editline_read_char(EditLine *el, wchar_t *cp);
#endif /* EDITLINE_COMPAT_PRIVATE_H */
Loading…
Cancel
Save