Skip to content

Convert glibc guests' text the way glibc's iconv does - #16

Open
buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/glibc-iconv
Open

buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/glibc-iconv

Conversation

@buyemune-sys

Copy link
Copy Markdown

The problem

iconv_open, iconv and iconv_close went straight to musl, whose converter differs from glibc's where programs rely on it:

call glibc solo
ASCII ← "aéz" -1/EILSEQ at é, output a rc=1, output a*z
ASCII//TRANSLIT ← "aéz" rc=1, a?z iconv_open: EINVAL
ASCII//IGNORE ← "aéz" -1/EILSEQ after the whole buffer, az iconv_open: EINVAL
UTF-8//IGNORE ← "a\xffb" -1/EILSEQ, ab iconv_open: EINVAL
UTF-16 ← "a" ff fe 61 00 (BOM, host order) 00 61
UTF-32 ← "a" ff fe 00 00 61 00 00 00 00 00 00 61
UCS-2 ← "a" 61 00 00 61
UCS-2 ← U+1F600 -1/EILSEQ fd ff, rc=0
UTF-8 ← UTF-16 "\x00a" (no BOM) U+6100 (host order) a

The first row silently alters text: Tarantool's iconv module never sees the error its test expects (app-tap/iconv.test.lua). //TRANSLIT is the usual idiom for lossy output, and UTF-8//IGNORE the usual one for dropping invalid bytes.

The fix

The bridge's descriptor wraps musl's (sh_iconv_open, lib/glibc_shim.cpp:3136).

Name handling:

  • The //SUFFIX part of the codes is parsed off. TRANSLIT and IGNORE are honored; other suffixes are ignored, as glibc does.
  • UTF-16, UTF-32, UNICODE, UCS-2 and UCS-4 map to the byte orders glibc uses.

Conversion:

  • Unicode targets convert directly through musl.
  • Every other target converts one character at a time through wchar_t (sh_iconv_character, :3253). That is how the bridge sees each * musl substitutes and applies glibc's rule: stop with EILSEQ, skip, or write ?.
  • //IGNORE also steps over invalid input by the source code unit and reports EILSEQ at the end.
  • The UTF-16/UTF-32 BOM is written before the first character and again after a reset.
  • An unmarked UTF-16/UTF-32 source is read in host order; a BOM picks the order.

The locale's transliteration tables stay out of reach. glibc spells U+20AC as EUR even in the C locale and drops accents in UTF-8 locales; the bridge writes ?.

Test

The conformance battery (conversions()) checks every row above, plus E2BIG and EINVAL positions, a WINDOWS-1251 conversion and the BOM across a reset. All expected values were first taken from real glibc.

On main: 15 checks fail. With the fix: ./build test passes.

buyemune-sys and others added 2 commits October 20, 2026 15:42
Security updates replaced libapparmor1 4.0.1really4.0.1-0ubuntu0.24.04.7
with .9 and libexpat1 2.6.1-2ubuntu0.4 with ubuntu0.6, and the pool kept
only the new files, so the cold-cache Fetch x86_64 job answers 404 and
every x86-64 job behind it is skipped. Both pins move to the versions
noble-updates ships now; their SHA256s match the archive's Packages index.
The other twelve pins are still served.
Programs lean on corners of glibc's iconv that musl's converter does not
share, and the bridge passed iconv_open, iconv and iconv_close straight
to musl:

- A character the target charset lacks fails with EILSEQ at that
  character. musl writes '*' and counts it as an irreversible
  conversion, so text is altered behind the caller's back: Tarantool's
  iconv module never sees the error its test expects.
- A code may carry //TRANSLIT and //IGNORE, or any other suffix glibc
  disregards. musl rejects such a name, so `iconv_open("ASCII//TRANSLIT",
  ...)`, the usual idiom for lossy output, fails with EINVAL. //IGNORE
  drops unconvertible characters and invalid input alike and reports
  EILSEQ once the buffer is through; //TRANSLIT writes '?' where the
  locale has no transliteration.
- UTF-16 and UTF-32 without a byte order mean a BOM and the host's order
  on output and the host's order on unmarked input; UCS-2 is the host's
  order outright and refuses characters beyond the BMP. musl means
  big-endian throughout and writes U+FFFD for what UCS-2 cannot hold.

The bridge's descriptor now wraps musl's. Unicode targets convert
directly. The others convert a character at a time through wchar_t, so
the bridge sees each '*' musl substitutes and applies glibc's rule:
stop, skip, or write '?'.

The locale's transliteration tables stay out of reach: glibc spells
U+20AC as "EUR" even in the C locale and drops accents in UTF-8 locales,
where the bridge writes '?'.

The conformance battery checks each rule above against the values glibc
produces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant