From patchwork Mon Aug 5 09:49:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968983 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=IWst4Rrv; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Wcs9g6pjkz1yZb for ; Mon, 5 Aug 2024 19:49:59 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id C3AF8385DDDC for ; Mon, 5 Aug 2024 09:49:57 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 1BBDF3858294 for ; Mon, 5 Aug 2024 09:49:35 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1BBDF3858294 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 1BBDF3858294 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851377; cv=none; b=EthzlzJWk4GOcKzju0UfeLThdQ6bkNjxZRHdkoSj4jmmNXFqSpDLCWNhXicWRb/+mq9JH77M9756vXHYOpOuTe4l1h84Glcq2+tMFzQmIckSNIXKHYPO5io/ZmLRTpgLldmce0Vh4ddZtErwREFx+WW3+5GEjOhwH8lCxoqh0pQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851377; c=relaxed/simple; bh=4mEay03wvpaTuPnVj421ZGw4yEsg7P7MQjLFE7rBCPg=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=aPsrbLm7k41qtbB+TLM0Wshq8pONVntZcGhn0V/n6reDwxUZi5pKEGpffViSbXZQCJs+ZD2P6j7w0eLa+raxm9SOgWsRwclSparWmR2TuRQ/gNgJ3mhTPW6Gb+5N19fi7oDDg9zRhUqKlXsg5OWE+yjcIeT75lMqSPxK59Teasg= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851374; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=uzxz+lwQsdFOFXw6y3zIWLko/KEVyNpPkBUn+77y3Iw=; b=IWst4RrvNxPoK4PdRKWvTztZxl2XVdKtE6py+nhpwiS2rImh+eRb6jusDF7Bp545qA88d/ L+9GHWQa6odBL4VD19CRBj9fVHEj7nINUNRa8dXyFmJ/wtT4LotA5lA2kThB7wbKQbEimQ D+JbwSHSDetAyjec3EHaBxdLzG13/84= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-627-vLmakRISM_um3hDbLy7UTw-1; Mon, 05 Aug 2024 05:49:31 -0400 X-MC-Unique: vLmakRISM_um3hDbLy7UTw-1 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 487451955F49 for ; Mon, 5 Aug 2024 09:49:30 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 55576300019F for ; Mon, 5 Aug 2024 09:49:29 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 1/7] iconv: Base tests for buffer management In-Reply-To: Message-ID: <71b9f0cd0e96de609b4ac93c4ea40361d8ff6e2a.1722851053.git.fweimer@redhat.com> References: X-From-Line: 71b9f0cd0e96de609b4ac93c4ea40361d8ff6e2a Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:26 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org --- iconv/Makefile | 10 +- iconv/tst-iconv_prog-buffer.sh | 177 +++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 iconv/tst-iconv_prog-buffer.sh diff --git a/iconv/Makefile b/iconv/Makefile index 63afc853ff..2e9ce26573 100644 --- a/iconv/Makefile +++ b/iconv/Makefile @@ -72,7 +72,10 @@ include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left)) ifeq ($(run-built-tests),yes) xtests-special += $(objpfx)test-iconvconfig.out -tests-special += $(objpfx)tst-iconv_prog.out +tests-special += \ + $(objpfx)tst-iconv_prog-buffer.out \ + $(objpfx)tst-iconv_prog.out \ + # tests-special endif # Make a copy of the file because gconv module names are constructed @@ -126,3 +129,8 @@ $(objpfx)tst-iconv_prog.out: tst-iconv_prog.sh $(objpfx)iconv_prog $(BASH) $< $(common-objdir) '$(test-wrapper-env)' \ '$(run-program-env)' > $@; \ $(evaluate-test) + +$(objpfx)tst-iconv_prog-buffer.out: \ + tst-iconv_prog-buffer.sh $(objpfx)iconv_prog + $(BASH) $< $(common-objdir) '$(test-program-prefix)' > $@; \ + $(evaluate-test) diff --git a/iconv/tst-iconv_prog-buffer.sh b/iconv/tst-iconv_prog-buffer.sh new file mode 100644 index 0000000000..a27107f02b --- /dev/null +++ b/iconv/tst-iconv_prog-buffer.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# Test for iconv (the program) buffer management. +# Copyright (C) 2024 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 +# . + +exec 2>&1 +set -e + +exec {logfd}>&1 + +codir=$1 +test_program_prefix="$2" + +# Use internal converters to avoid issues with module loading. +iconv_args="-f ASCII -t UTF-8" + +failure=false + +tmp=`mktemp -d` +trap 'rm -rf "$tmp"' 0 +echo ABC > "$tmp/abc" +echo DEF > "$tmp/def" +echo GGG > "$tmp/ggg" +echo HH > "$tmp/hh" +echo XY > "$tmp/xy" +echo ZT > "$tmp/zt" +echo OUT > "$tmp/out-template" +printf '\xff' > "$tmp/0xff" +cat "$tmp/xy" "$tmp/0xff" "$tmp/zt" > "$tmp/0xff-wrapped" + +run_iconv () { + local c=0 + if test "${FUNCNAME[2]}" = main; then + c=1 + fi + echo "${BASH_SOURCE[$c]}:${BASH_LINENO[$c]}: iconv $iconv_args $@" >&$logfd + $test_program_prefix $codir/iconv/iconv_prog $iconv_args "$@" +} + +check_out_expected () { + if ! cmp -s "$tmp/out" "$tmp/expected" ; then + echo "error: iconv output difference" >&$logfd + echo "*** expected ***" >&$logfd + cat "$tmp/expected" >&$logfd + echo "*** actual ***" >&$logfd + cat "$tmp/out" >&$logfd + failure=true + fi +} + +expect_files () { + local f + ! test -z "$1" + cp "$tmp/$1" "$tmp/expected" + shift + for f in "$@" ; do + cat "$tmp/$f" >> "$tmp/expected" + done + check_out_expected +} + +check_out () { + cat > "$tmp/expected" + check_out_expected +} + +expect_exit () { + local expected=$1 + shift + # Prevent failure for stopping the script. + if "$@" ; then + actual=$? + else + actual=$? + fi + if test "$actual" -ne "$expected"; then + echo "error: expected exit status $expected, not $actual" >&$logfd + exit 1 + fi +} + +ignore_failure () { + set +e + "$@" + status=$? + set -e +} + +# Concatentation test. +run_iconv -o "$tmp/out" "$tmp/abc" "$tmp/def" +expect_files abc def + +# Single-file in-place conversion. +run_iconv -o "$tmp/out" "$tmp/out" +expect_files abc def + +# Multiple input files with in-place conversion. + +run_iconv -o "$tmp/out" "$tmp/out" "$tmp/abc" +expect_files abc def abc + +# But not if we are writing to standard output. + +cp "$tmp/out-template" "$tmp/out" +run_iconv >"$tmp/out" +expect_files out-template + +cp "$tmp/out-template" "$tmp/out" +run_iconv - >"$tmp/out" +expect_files out-template + +cp "$tmp/out-template" "$tmp/out" +run_iconv /dev/null >>"$tmp/out" +expect_files out-template + +# Conversion errors should avoid clobbering an existing file if +# it is also an input file. + +cp "$tmp/0xff" "$tmp/out" +expect_exit 1 run_iconv -o "$tmp/out" "$tmp/out" +expect_files 0xff + +cp "$tmp/0xff" "$tmp/out" +expect_exit 1 run_iconv -o "$tmp/out" < "$tmp/out" +expect_files 0xff + +cp "$tmp/0xff" "$tmp/out" +expect_exit 1 run_iconv -o "$tmp/out" - < "$tmp/out" +expect_files 0xff + +# If errors are ignored, the file should be overwritten. + +cp "$tmp/out-template" "$tmp/out" +expect_exit 1 \ + run_iconv -c -o "$tmp/out" "$tmp/abc" "$tmp/0xff" "$tmp/def" 2>"$tmp/err" +! test -s "$tmp/err" +expect_files abc def + +# FIXME: This is not correct, -c should not change the exit status. +cp "$tmp/out-template" "$tmp/out" +run_iconv -c -o "$tmp/out" \ + "$tmp/abc" "$tmp/0xff-wrapped" "$tmp/def" 2>"$tmp/err" +! test -s "$tmp/err" +expect_files abc xy zt def + +# If the file does not exist yet, it should not be created on error. + +rm "$tmp/out" +expect_exit 1 run_iconv -o "$tmp/out" "$tmp/0xff" +! test -e "$tmp/out" + +expect_exit 1 run_iconv -o "$tmp/out" < "$tmp/0xff" +! test -e "$tmp/out" + +expect_exit 1 run_iconv -o "$tmp/out" "$tmp/abc" "$tmp/0xff" "$tmp/def" +! test -e "$tmp/out" + +expect_exit 1 run_iconv -o "$tmp/out" "$tmp/abc" - < "$tmp/0xff" "$tmp/def" +! test -e "$tmp/out" + +if $failure ; then + exit 1 +fi From patchwork Mon Aug 5 09:49:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968984 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Qgv2rbp/; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Wcs9y4xWbz1yYD for ; Mon, 5 Aug 2024 19:50:14 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 80F55385DDEE for ; Mon, 5 Aug 2024 09:50:12 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 38779385842C for ; Mon, 5 Aug 2024 09:49:39 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 38779385842C Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 38779385842C Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851382; cv=none; b=BPYPa43Cb6R4Hc5cOrFNjc6FOlgMHdAtGZU5SRaX7/6aUJpHAX5mWX+f3liTJzkd4WVlOxz+9WsGOPF5WMTj7eExM0AR1G8fW7CIRvoZ7qlCdfoX8MbqhugZNfLAtr2AxigvXot3CLQmDxuhykFonjLCjOWRYyzBFHnht4CABCA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851382; c=relaxed/simple; bh=P4bq+5do8q+XE+WKbpD2z4JoIPRcVyl0Z7VU6TEi2cY=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=oD6NIBxbvzwZ0LNDSQAXSvrAVd0TU13bDi9rFMWLFvkq2y/f3AZgMautJUv6QcjdemCPOVY0WKLu2fFgmWhejp7HUHTb6bZFgu04pZGRzq1w1zbS08CA9QQTxGWLNrdVHHhc5W+g7wi9Htoc5C0RKkiq1RZydNZWB618WPuIoZQ= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851379; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=T/4/+xJr3K9x6wxUEK4GzqBlSj79Z3oZCUIXC0WNATQ=; b=Qgv2rbp/PjxZwnv0py5HJxBGS9MYKWU0JNsfplRQN9OtxYH0UoKD/qxa3sK4C9RieE+OLz zb2f0DhcVrRoyXghuK3zUi6lQ55WEPEVOOERDGpp1nbVmMzVrZU6oHAT7Ri+eXTmwzWLmC 4BBgDaYGsCmeOiwsMJ1ay4/1JTOnLsE= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-290-6zl3LNVEPGafK6rSQY_WMg-1; Mon, 05 Aug 2024 05:49:37 -0400 X-MC-Unique: 6zl3LNVEPGafK6rSQY_WMg-1 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4B34F1955D47 for ; Mon, 5 Aug 2024 09:49:36 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 394DC300019F for ; Mon, 5 Aug 2024 09:49:34 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 2/7] iconv: Do not use mmap in iconv (the program) (bug 17703) In-Reply-To: Message-ID: <69089f0a43ce13283df303392fac115208fda009.1722851053.git.fweimer@redhat.com> References: X-From-Line: 69089f0a43ce13283df303392fac115208fda009 Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:32 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org On current systems, very large files are needed before iconv becomes beneficial. Simplify the implementation. This exposed that inptr was not initialized correctly in process_fd. Handling multiple input files resulted in EFAULT in read because a null pointer was passed. This could be observed previously if an input file was not mappable and was reported as bug 17703. --- iconv/iconv_prog.c | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c index a765b1af21..88a928557e 100644 --- a/iconv/iconv_prog.c +++ b/iconv/iconv_prog.c @@ -31,9 +31,6 @@ #include #include #include -#ifdef _POSIX_MAPPED_FILES -# include -#endif #include #include #include "iconv_prog.h" @@ -253,10 +250,6 @@ conversions from `%s' and to `%s' are not supported"), else do { -#ifdef _POSIX_MAPPED_FILES - struct stat64 st; - char *addr; -#endif int fd, ret; if (verbose) @@ -276,39 +269,6 @@ conversions from `%s' and to `%s' are not supported"), } } -#ifdef _POSIX_MAPPED_FILES - /* We have possibilities for reading the input file. First try - to mmap() it since this will provide the fastest solution. */ - if (fstat64 (fd, &st) == 0 - && ((addr = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE, - fd, 0)) != MAP_FAILED)) - { - /* Yes, we can use mmap(). The descriptor is not needed - anymore. */ - if (close (fd) != 0) - error (EXIT_FAILURE, errno, - _("error while closing input `%s'"), - argv[remaining]); - - ret = process_block (cd, addr, st.st_size, &output, - output_file); - - /* We don't need the input data anymore. */ - munmap ((void *) addr, st.st_size); - - if (ret != 0) - { - status = EXIT_FAILURE; - - if (ret < 0) - /* We cannot go on with producing output since it might - lead to problem because the last output might leave - the output stream in an undefined state. */ - break; - } - } - else -#endif /* _POSIX_MAPPED_FILES */ { /* Read the file in pieces. */ ret = process_fd (cd, fd, &output, output_file); @@ -544,7 +504,7 @@ process_fd (iconv_t cd, int fd, FILE **output, const char *output_file) process it in one step. */ static char *inbuf = NULL; static size_t maxlen = 0; - char *inptr = NULL; + char *inptr = inbuf; size_t actlen = 0; while (actlen < maxlen) From patchwork Mon Aug 5 09:49:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968986 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Ql3JKsgs; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WcsC04Gljz1yYD for ; Mon, 5 Aug 2024 19:51:08 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6C214385DDE7 for ; Mon, 5 Aug 2024 09:51:06 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 74E033858430 for ; Mon, 5 Aug 2024 09:49:44 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 74E033858430 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 74E033858430 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851387; cv=none; b=HnezEFAwf2yRmrpjnIK457QUnI9B2FGea6m9dXsjAkS6aeCA1UaRcupc4vtEzqSiLhfZJrcxHf1rpx00uLHMkOrzfcsMRBcFK+Cvn1ILgg6lPFQqNPWJmrCuh8zCwUMOmRUeAvTNSbU8CY1bo6hL3G1tOWvmfDb/lwnmPA98W3w= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851387; c=relaxed/simple; bh=bAWqMZE1zaSz8VinSsFnP4l3GCAP4f5FNyk+07JO7Es=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=Ofhk5BBAsia886vA4TZGGwD5rATIR6OM+MMYoef2ORYCRqV3n9SZw3m8FW7NTFwOCmwUFtfkAaJJQB0MbKnGUVoBDHN6DLgkGU3zgi1uUj9kq2AbHSYkSwyJX3rxZ7c7pHIJuBbUqSudzH526Kviwuwh97iDtgVRPEBUrXsos2o= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851384; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=3ewcAO7IQJhHgdMSqy2nSu1HMrEkJ3jsKyYzzrCtvY4=; b=Ql3JKsgszJiuPfaTlmdUGcXIPcbC46Mdq2wIpmkel2Zi0gvh+5SjVxOQOv01LL9ImXUHqA QjQ7lfZAye9y+InIlm+SoSc69ruBQ1XR5riQmFAZuGvcQJtiu0EbcEzzALO/l9e6R9xKYg LeezjWK8C36PlqxVaaaRI0MEDRMJpo8= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-111-F052xBDrNUiuG1WNWXpxVA-1; Mon, 05 Aug 2024 05:49:42 -0400 X-MC-Unique: F052xBDrNUiuG1WNWXpxVA-1 Received: from mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.15]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0A14B1954126 for ; Mon, 5 Aug 2024 09:49:42 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 17E581955D42 for ; Mon, 5 Aug 2024 09:49:40 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 3/7] manual: __is_last is no longer part of iconv internals In-Reply-To: Message-ID: References: X-From-Line: d16eda752bf8d7e726216d8210ec7c8e4157fd7d Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:37 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.15 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org The __is_last field was replaced with a bitmask in commit 85830c4c4688b30d3d76111aa9a26745c7b141d6 in 2000, and multiple bits are in use today. --- manual/charset.texi | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manual/charset.texi b/manual/charset.texi index 427db3bc80..3aaa62d088 100644 --- a/manual/charset.texi +++ b/manual/charset.texi @@ -2422,11 +2422,11 @@ written into the buffer to signal how much output is available. If this conversion step is not the last one, the element must not be modified. The @code{__outbufend} element must not be modified. -@item int __is_last -This element is nonzero if this conversion step is the last one. This -information is necessary for the recursion. See the description of the -conversion function internals below. This element must never be -modified. +@item int __flags +This field is a set of flags. The @code{__GCONV_IS_LAST} bit is set if +this conversion step is the last one. This information is necessary for +the recursion. See the description of the conversion function internals +below. This element must never be modified. @item int __invocation_counter The conversion function can use this element to see how many calls of @@ -2731,8 +2731,8 @@ Otherwise the function has to emit a byte sequence to bring the state object into the initial state. Once this all happened the other conversion modules in the chain of conversions have to get the same chance. Whether another step follows can be determined from the -@code{__is_last} element of the step data structure to which the first -parameter points. +@code{__GCONV_IS_LAST} flag in the @code{__flags} field of the step +data structure to which the first parameter points. The more interesting mode is when actual text has to be converted. The first step in this case is to convert as much text as possible from the @@ -2866,7 +2866,7 @@ gconv (struct __gconv_step *step, struct __gconv_step_data *data, /* @r{Call the steps down the chain if there are any but only} @r{if we successfully emitted the escape sequence.} */ - if (status == __GCONV_OK && ! data->__is_last) + if (status == __GCONV_OK && ! (data->__flags & __GCONV_IS_LAST)) status = fct (next_step, next_data, NULL, NULL, written, 1); @} @@ -2892,7 +2892,7 @@ gconv (struct __gconv_step *step, struct __gconv_step_data *data, /* @r{If this is the last step, leave the loop. There is} @r{nothing we can do.} */ - if (data->__is_last) + if (data->__flags & __GCONV_IS_LAST) @{ /* @r{Store information about how many bytes are} @r{available.} */ From patchwork Mon Aug 5 09:49:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968987 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Jk6Y8Asf; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WcsCJ2HXlz1yYD for ; Mon, 5 Aug 2024 19:51:24 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1FA39385DDFD for ; Mon, 5 Aug 2024 09:51:22 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 805B5385DDEC for ; Mon, 5 Aug 2024 09:49:52 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 805B5385DDEC Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 805B5385DDEC Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851397; cv=none; b=I7lXi/QUWFIsO0vpkMKC56ST70kwqi1Toz2L1OJpkgPSv7/w/4nHgy9lTYS/AdSzisW4PRdJ/xbNMsTEXTyRlMN/dEC+i/Gyo5FWGhuS6l0JV7ysv2r2UhSUDm8kL+H0Kn92knsFnboCnMQHf/fC1qyHV6VnM0GPKPLWyakTh8M= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851397; c=relaxed/simple; bh=i88UZ5gsn/7dzDbr40wBE7A1cle1uWgyXseUi9zgvrU=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=Io5aJyEVCFZEIqN9NC1aCnQHPwEqmikp8PsXQr7oiiDO6tTtpT10KCTOkIugw9Gu7JOGDSNwiXSmVBwM/NR3PVH7hFqb21I/SAbIi9R8wDzoOCVl1L9KDFt7uYUh0M1kdRxemEg+pDwWRgyVb8tMoGp0YdB1OanhXsRzNLOWrJc= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851392; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=ZmnCfLoJoQaZd9E36TEHcdQ1X568a3XXEXHAlqArdoI=; b=Jk6Y8AsfbtzlNbubUYXnErThDTbhBZUSsHmQ4C3+KbwsTAWmVQBIfsRVxhx6QnadZwFLyS EoMHAvUQrGTEED4r9jgdpe/vVduNLC8SpJ7iIVBYbZor9LneTuFfCn4/44ECKXv7u3BF3x 9Qb7R5EcTFJfSL8kdeCcg/N9M3UqMSQ= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-185-c0njHAC0MC-cK0aq-4whXQ-1; Mon, 05 Aug 2024 05:49:49 -0400 X-MC-Unique: c0njHAC0MC-cK0aq-4whXQ-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id AC4F41955D45 for ; Mon, 5 Aug 2024 09:49:48 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 220841955F40 for ; Mon, 5 Aug 2024 09:49:46 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 4/7] iconv: Preserve iconv -c error exit on invalid inputs (bug 32046) In-Reply-To: Message-ID: <099bb03ee26b92b3152b6d81e33d30e0529504a0.1722851053.git.fweimer@redhat.com> References: X-From-Line: 099bb03ee26b92b3152b6d81e33d30e0529504a0 Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:43 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-9.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SCC_10_SHORT_WORD_LINES, SCC_5_SHORT_WORD_LINES, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org In several converters, a __GCONV_ILLEGAL_INPUT result gets overwritten with __GCONV_FULL_OUTPUT. As a result, iconv (the function) returns E2BIG instead of EILSEQ. The iconv program does not see the original EILSEQ failure, does not recognize the invalid input, and may incorrectly exit successfully. To address this, a new __flags bit is used to indicate a sticky input error state. All __GCONV_ILLEGAL_INPUT results are replaced with a function call that sets this new __GCONV_ENCOUNTERED_ILLEGAL_INPUT and returns __GCONV_ILLEGAL_INPUT. The iconv program checks for __GCONV_ENCOUNTERED_ILLEGAL_INPUT and overrides the exit status. The converter changes introducing __gconv_mark_illegal_input are mostly mechanical, except for the res variable initialization in iconvdata/iso-2022-jp.c: this error gets overwritten with __GCONV_OK and other results in the following code. If res == __GCONV_ILLEGAL_INPUT afterwards, STANDARD_TO_LOOP_ERR_HANDLER below will handle it. The __gconv_mark_illegal_input changes do not alter the errno value set by the iconv function. This is simpler to implement than reviewing each __GCONV_FULL_OUTPUT result and adjust it not to override a previous __GCONV_ILLEGAL_INPUT result. Doing it that way would also change some E2BIG errors in to EILSEQ errors, so it had to be done conditionally (under a flag set by the iconv program only), to avoid confusing buffer management in other applications. --- iconv/Makefile | 4 + iconv/gconv_int.h | 30 ++++++ iconv/gconv_simple.c | 18 ++-- iconv/gconv_trans.c | 2 +- iconv/iconv_prog.c | 5 + iconv/loop.c | 5 +- iconv/tst-iconv-sticky-input-error.c | 135 +++++++++++++++++++++++ iconv/tst-iconv_prog-buffer.sh | 3 +- iconvdata/cp932.c | 6 +- iconvdata/euc-jp-ms.c | 8 +- iconvdata/gbbig5.c | 4 +- iconvdata/ibm1364.c | 8 +- iconvdata/iso646.c | 154 +++++++++++++-------------- iconvdata/unicode.c | 2 +- iconvdata/utf-16.c | 2 +- iconvdata/utf-32.c | 2 +- 16 files changed, 280 insertions(+), 108 deletions(-) create mode 100644 iconv/tst-iconv-sticky-input-error.c diff --git a/iconv/Makefile b/iconv/Makefile index 2e9ce26573..6708e73791 100644 --- a/iconv/Makefile +++ b/iconv/Makefile @@ -57,6 +57,10 @@ tests = \ tst-iconv-opt \ # tests +tests-internal = \ + tst-iconv-sticky-input-error \ + # tests-internal + others = iconv_prog iconvconfig install-others-programs = $(inst_bindir)/iconv install-sbin = iconvconfig diff --git a/iconv/gconv_int.h b/iconv/gconv_int.h index 9fece3ea14..cd452d94cc 100644 --- a/iconv/gconv_int.h +++ b/iconv/gconv_int.h @@ -331,4 +331,34 @@ extern wint_t __gconv_btwoc_ascii (struct __gconv_step *step, unsigned char c); __END_DECLS +/* Internal extensions for . */ + +/* Internal flags for __flags in struct __gconv_step_data. Overlaps + with flags for __gconv_open. */ +enum + { + /* The conversion encountered an illegal input character at one + point. */ + __GCONV_ENCOUNTERED_ILLEGAL_INPUT = 1U << 30, + }; + +/* Mark *STEP_DATA as having seen illegal input, and return + __GCONV_ILLEGAL_INPUT. */ +static inline int +__gconv_mark_illegal_input (struct __gconv_step_data *step_data) +{ + step_data->__flags |= __GCONV_ENCOUNTERED_ILLEGAL_INPUT; + return __GCONV_ILLEGAL_INPUT; +} + +/* Returns true if any of the conversion steps encountered illegal input. */ +static _Bool __attribute__ ((unused)) +__gconv_has_illegal_input (__gconv_t cd) +{ + for (size_t i = 0; i < cd->__nsteps; ++i) + if (cd->__data[i].__flags & __GCONV_ENCOUNTERED_ILLEGAL_INPUT) + return true; + return false; +} + #endif /* gconv_int.h */ diff --git a/iconv/gconv_simple.c b/iconv/gconv_simple.c index 257be2f8ff..f22002cf81 100644 --- a/iconv/gconv_simple.c +++ b/iconv/gconv_simple.c @@ -207,7 +207,7 @@ ucs4_internal_loop (struct __gconv_step *step, UCS4 does not allow such values. */ if (irreversible == NULL) /* We are transliterating, don't try to correct anything. */ - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); if (flags & __GCONV_IGNORE_ERRORS) { @@ -218,7 +218,7 @@ ucs4_internal_loop (struct __gconv_step *step, *inptrp = inptr; *outptrp = outptr; - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); } put32 (outptr, inval); @@ -276,7 +276,7 @@ ucs4_internal_loop_single (struct __gconv_step *step, if (!(flags & __GCONV_IGNORE_ERRORS)) { *inptrp -= cnt - (state->__count & 7); - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); } } else @@ -453,7 +453,7 @@ ucs4le_internal_loop (struct __gconv_step *step, UCS4 does not allow such values. */ if (irreversible == NULL) /* We are transliterating, don't try to correct anything. */ - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); if (flags & __GCONV_IGNORE_ERRORS) { @@ -464,7 +464,7 @@ ucs4le_internal_loop (struct __gconv_step *step, *inptrp = inptr; *outptrp = outptr; - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); } put32 (outptr, inval); @@ -523,7 +523,7 @@ ucs4le_internal_loop_single (struct __gconv_step *step, represent the result. This is a genuine bug in the input since UCS4 does not allow such values. */ if (!(flags & __GCONV_IGNORE_ERRORS)) - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); } else { @@ -969,7 +969,7 @@ ucs4le_internal_loop_single (struct __gconv_step *step, surrogates pass through, attackers could make a security \ hole exploit by synthesizing any desired plane 1-16 \ character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ inptr += 4; \ @@ -1012,7 +1012,7 @@ ucs4le_internal_loop_single (struct __gconv_step *step, them. (Catching this here is not security relevant.) */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ inptr += 2; \ @@ -1061,7 +1061,7 @@ ucs4le_internal_loop_single (struct __gconv_step *step, character. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ inptr += 4; \ diff --git a/iconv/gconv_trans.c b/iconv/gconv_trans.c index 08b7a3f71d..70bc680eeb 100644 --- a/iconv/gconv_trans.c +++ b/iconv/gconv_trans.c @@ -232,6 +232,6 @@ __gconv_transliterate (struct __gconv_step *step, } /* Haven't found a match. */ - return __GCONV_ILLEGAL_INPUT; + return __gconv_mark_illegal_input (step_data); } libc_hidden_def (__gconv_transliterate) diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c index 88a928557e..5fe4fe7a6c 100644 --- a/iconv/iconv_prog.c +++ b/iconv/iconv_prog.c @@ -291,6 +291,11 @@ conversions from `%s' and to `%s' are not supported"), } while (++remaining < argc); + /* Ensure that iconv -c still exits with failure if iconv (the + function) has failed with E2BIG instead of EILSEQ. */ + if (__gconv_has_illegal_input (cd)) + status = EXIT_FAILURE; + /* Close the output file now. */ if (output != NULL && fclose (output)) error (EXIT_FAILURE, errno, _("error while closing output file")); diff --git a/iconv/loop.c b/iconv/loop.c index 5340dafc70..199fb28326 100644 --- a/iconv/loop.c +++ b/iconv/loop.c @@ -123,8 +123,7 @@ `continue' must reach certain points. */ #define STANDARD_FROM_LOOP_ERR_HANDLER(Incr) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ - \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ \ @@ -142,7 +141,7 @@ points. */ #define STANDARD_TO_LOOP_ERR_HANDLER(Incr) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ \ if (irreversible == NULL) \ /* This means we are in call from __gconv_transliterate. In this \ diff --git a/iconv/tst-iconv-sticky-input-error.c b/iconv/tst-iconv-sticky-input-error.c new file mode 100644 index 0000000000..c93b5b5160 --- /dev/null +++ b/iconv/tst-iconv-sticky-input-error.c @@ -0,0 +1,135 @@ +/* Test __GCONV_ENCOUNTERED_ILLEGAL_INPUT, as used by iconv -c (bug 32046). + Copyright (C) 2024 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 + . */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* FROM is the input character set, TO the output character set. If + IGNORE is true, the iconv descriptor is set up in the same way as + iconv -c would. INPUT is the input string, EXPECTED_OUTPUT the + output. OUTPUT_LIMIT is a byte count, specifying how many input + bytes are passed to the iconv function on each invocation. */ +static void +one_direction (const char *from, const char *to, bool ignore, + const char *input, const char *expected_output, + size_t output_limit) +{ + if (test_verbose) + { + printf ("info: testing from=\"%s\" to=\"%s\" ignore=%d input=\"", + from, to, (int) ignore); + for (const char *p = input; *p != '\0'; ++p) + if (*p >= ' ' && *p <= '~' && *p != '\\' && *p != '"') + putchar (*p); + else + printf ("\\x%02x", *p & 0xff); + printf (" \" expected_output=\"%s\" output_limit=%zu\n", + expected_output, output_limit); + } + + __gconv_t cd; + if (ignore) + { + struct gconv_spec conv_spec; + TEST_VERIFY_EXIT (__gconv_create_spec (&conv_spec, from, to) + == &conv_spec); + conv_spec.ignore = true; + cd = (iconv_t) -1; + TEST_COMPARE (__gconv_open (&conv_spec, &cd, 0), __GCONV_OK); + } + else + cd = iconv_open (to, from); + TEST_VERIFY_EXIT (cd != (iconv_t) -1); + + char *input_ptr = (char *) input; + size_t input_len = strlen (input); + char output_buf[20]; + char *output_ptr = output_buf; + size_t output_len; + do + { + output_len = array_end (output_buf) - output_ptr; + if (output_len > output_limit) + /* Limit the buffer size as requested by the caller. */ + output_len = output_limit; + TEST_VERIFY_EXIT (output_len > 0); + if (input_len == 0) + /* Trigger final flush. */ + input_ptr = NULL; + char *old_input_ptr = input_ptr; + size_t ret = iconv (cd, &input_ptr, &input_len, + &output_ptr, &output_len); + if (ret == (size_t) -1) + { + if (errno != EILSEQ) + TEST_COMPARE (errno, E2BIG); + } + + if (input_ptr == old_input_ptr) + /* Avoid endless loop if stuck on an invalid input character. */ + break; + } + while (input_ptr != NULL); + + /* Test the sticky illegal input bit. */ + TEST_VERIFY (__gconv_has_illegal_input (cd)); + + TEST_COMPARE_BLOB (expected_output, strlen (expected_output), + output_buf, output_ptr - output_buf); + + TEST_COMPARE (iconv_close (cd), 0); +} + +static int +do_test (void) +{ + static const char charsets[][14] = + { + "ASCII", + "ASCII//IGNORE", + "UTF-8", + "UTF-8//IGNORE", + }; + + for (size_t from_idx = 0; from_idx < array_length (charsets); ++from_idx) + for (size_t to_idx = 0; to_idx < array_length (charsets); ++to_idx) + for (int do_ignore = 0; do_ignore < 2; ++do_ignore) + for (int limit = 1; limit < 5; ++limit) + for (int skip = 0; skip < 3; ++skip) + { + const char *expected_output; + if (do_ignore || strstr (charsets[to_idx], "//IGNORE") != NULL) + expected_output = "ABXY" + skip; + else + expected_output = "AB" + skip; + one_direction (charsets[from_idx], charsets[to_idx], do_ignore, + "AB\xffXY" + skip, expected_output, limit); + } + + return 0; +} + +#include diff --git a/iconv/tst-iconv_prog-buffer.sh b/iconv/tst-iconv_prog-buffer.sh index a27107f02b..5ff99a02a3 100644 --- a/iconv/tst-iconv_prog-buffer.sh +++ b/iconv/tst-iconv_prog-buffer.sh @@ -150,9 +150,8 @@ expect_exit 1 \ ! test -s "$tmp/err" expect_files abc def -# FIXME: This is not correct, -c should not change the exit status. cp "$tmp/out-template" "$tmp/out" -run_iconv -c -o "$tmp/out" \ +expect_exit 1 run_iconv -c -o "$tmp/out" \ "$tmp/abc" "$tmp/0xff-wrapped" "$tmp/def" 2>"$tmp/err" ! test -s "$tmp/err" expect_files abc xy zt def diff --git a/iconvdata/cp932.c b/iconvdata/cp932.c index bf7297b114..3def70a70b 100644 --- a/iconvdata/cp932.c +++ b/iconvdata/cp932.c @@ -4559,7 +4559,7 @@ static const char from_ucs4_extra[229][2] = if (! ignore_errors_p ()) \ { \ /* This is an illegal character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ @@ -4599,7 +4599,7 @@ static const char from_ucs4_extra[229][2] = if (! ignore_errors_p ()) \ { \ /* This is an illegal character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ @@ -4634,7 +4634,7 @@ static const char from_ucs4_extra[229][2] = if (! ignore_errors_p ()) \ { \ /* This is an illegal character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ diff --git a/iconvdata/euc-jp-ms.c b/iconvdata/euc-jp-ms.c index d03a0e05bb..96c5325e9d 100644 --- a/iconvdata/euc-jp-ms.c +++ b/iconvdata/euc-jp-ms.c @@ -4659,7 +4659,7 @@ static const unsigned char from_ucs4_extra[229][2] = /* This is illegal. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ @@ -4689,7 +4689,7 @@ static const unsigned char from_ucs4_extra[229][2] = /* This is an illegal character. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ @@ -4709,7 +4709,7 @@ static const unsigned char from_ucs4_extra[229][2] = if (! ignore_errors_p ()) \ { \ /* This is an illegal character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ } \ @@ -4820,7 +4820,7 @@ static const unsigned char from_ucs4_extra[229][2] = if (! ignore_errors_p ()) \ { \ /* This is an illegal character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ diff --git a/iconvdata/gbbig5.c b/iconvdata/gbbig5.c index f05deeeb1a..b15a2ef932 100644 --- a/iconvdata/gbbig5.c +++ b/iconvdata/gbbig5.c @@ -4831,7 +4831,7 @@ const char __from_big5_to_gb2312 [13973][2] = { \ /* We do not have a mapping for this character. \ If ignore errors, map it to 0xa1bc - big5 box character */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ \ @@ -4922,7 +4922,7 @@ const char __from_big5_to_gb2312 [13973][2] = { \ /* We do not have a mapping for this character. \ If ignore errors, map it to 0xa1f5 - gb box character */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ \ diff --git a/iconvdata/ibm1364.c b/iconvdata/ibm1364.c index 4c37f30e98..d6c8ce7f68 100644 --- a/iconvdata/ibm1364.c +++ b/iconvdata/ibm1364.c @@ -179,7 +179,7 @@ enum /* This is an illegal character. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ ++*irreversible; \ @@ -219,7 +219,7 @@ enum /* This is an illegal character. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ ++*irreversible; \ @@ -300,7 +300,7 @@ enum \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ ++*irreversible; \ @@ -332,7 +332,7 @@ enum /* This is an illegal character. */ \ if (! ignore_errors_p ()) \ { \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ break; \ } \ ++*irreversible; \ diff --git a/iconvdata/iso646.c b/iconvdata/iso646.c index d96e5f86b9..ba7e23f8ac 100644 --- a/iconvdata/iso646.c +++ b/iconvdata/iso646.c @@ -313,7 +313,7 @@ gconv_end (struct __gconv_step *data) ch = 0xf9; \ else if (var == JP_OCR_B) \ /* Illegal character. */ \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ else if (var == YU) \ ch = 0x17e; \ else if (var == HU) \ @@ -387,7 +387,7 @@ gconv_end (struct __gconv_step *data) ch = 0xec; \ else if (var == JP_OCR_B) \ /* Illegal character. */ \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ else if (var == YU) \ ch = 0x10d; \ else if (var == HU) \ @@ -403,7 +403,7 @@ gconv_end (struct __gconv_step *data) break; \ case 0x80 ... 0xff: \ /* Illegal character. */ \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ } \ \ @@ -440,17 +440,17 @@ gconv_end (struct __gconv_step *data) case 0x23: \ if (var == GB || var == ES || var == IT || var == FR || var == FR1 \ || var == NO2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x24: \ if (var == CN || var == HU || var == CU || var == SE || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x40: \ if (var == CA || var == CA2 || var == DE || var == ES || var == ES2 \ || var == IT || var == YU || var == HU || var == FR || var == FR1 \ || var == PT || var == PT2 || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x5b: \ if (var == CA || var == CA2 || var == DE || var == DK || var == ES \ @@ -458,7 +458,7 @@ gconv_end (struct __gconv_step *data) || var == HU || var == FR || var == FR1 || var == NO \ || var == NO2 || var == PT || var == PT2 || var == SE \ || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ else if (var == CU) \ ch = 0x7d; \ break; \ @@ -468,7 +468,7 @@ gconv_end (struct __gconv_step *data) || var == YU || var == KR || var == HU || var == CU || var == FR \ || var == FR1 || var == NO || var == NO2 || var == PT \ || var == PT2 || var == SE || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x5d: \ if (var == CA || var == CA2 || var == DE || var == DK || var == ES \ @@ -476,17 +476,17 @@ gconv_end (struct __gconv_step *data) || var == HU || var == FR || var == FR1 || var == NO \ || var == NO2 || var == PT || var == PT2 || var == SE \ || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x5e: \ if (var == CA || var == CA2 || var == ES2 || var == YU || var == CU \ || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x60: \ if (var == CA || var == CA2 || var == IT || var == JP_OCR_B \ || var == YU || var == HU || var == FR || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x7b: \ if (var == CA || var == CA2 || var == DE || var == DK || var == ES \ @@ -494,14 +494,14 @@ gconv_end (struct __gconv_step *data) || var == CU || var == FR || var == FR1 || var == NO \ || var == NO2 || var == PT || var == PT2 || var == SE \ || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x7c: \ if (var == CA || var == CA2 || var == DE || var == DK || var == ES \ || var == ES2 || var == IT || var == YU || var == HU || var == CU \ || var == FR || var == FR1 || var == NO || var == PT \ || var == PT2 || var == SE || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ else if (var == NO2) \ ch = 0x7e; \ break; \ @@ -510,7 +510,7 @@ gconv_end (struct __gconv_step *data) || var == ES2 || var == IT || var == YU || var == HU || var == CU \ || var == FR || var == FR1 || var == NO || var == NO2 \ || var == PT || var == PT2 || var == SE || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x7e: \ if (var == GB || var == CA || var == CA2 || var == DE || var == ES2 \ @@ -518,21 +518,21 @@ gconv_end (struct __gconv_step *data) || var == YU || var == HU || var == CU || var == FR || var == FR1 \ || var == NO || var == NO2 || var == PT || var == SE \ || var == SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xa1: \ if (var != ES && var != ES2 && var != CU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0xa3: \ if (var != GB && var != ES && var != IT && var != FR && var != FR1) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x23; \ break; \ case 0xa4: \ if (var != HU && var != CU && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x24; \ break; \ case 0xa5: \ @@ -541,7 +541,7 @@ gconv_end (struct __gconv_step *data) else if (var == JP || var == JP_OCR_B) \ ch = 0x5c; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xa7: \ if (var == DE || var == ES || var == IT || var == PT) \ @@ -551,11 +551,11 @@ gconv_end (struct __gconv_step *data) else if (var == NO2) \ ch = 0x23; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xa8: \ if (var != ES2 && var != CU && var != FR && var != FR1) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0xb0: \ @@ -566,7 +566,7 @@ gconv_end (struct __gconv_step *data) else if (var == PT) \ ch = 0x7e; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xb4: \ if (var == ES2 || var == CU) \ @@ -574,11 +574,11 @@ gconv_end (struct __gconv_step *data) else if (var == PT2) \ ch = 0x40; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xb5: \ if (var != FR) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x60; \ break; \ case 0xbf: \ @@ -587,31 +587,31 @@ gconv_end (struct __gconv_step *data) else if (var == ES2 || var == CU) \ ch = 0x5e; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xc1: \ if (var != HU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x40; \ break; \ case 0xc3: \ if (var != PT && var != PT2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0xc4: \ if (var != DE && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0xc5: \ if (var != DK && var != NO && var != NO2 && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5d; \ break; \ case 0xc6: \ if (var != DK && var != NO && var != NO2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0xc7: \ @@ -620,7 +620,7 @@ gconv_end (struct __gconv_step *data) else if (var == PT || var == PT2) \ ch = 0x5c; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xc9: \ if (var == CA2) \ @@ -630,26 +630,26 @@ gconv_end (struct __gconv_step *data) else if (var == SE2) \ ch = 0x40; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xd1: \ if (var != ES && var != ES2 && var != CU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5c; \ break; \ case 0xd5: \ if (var != PT && var != PT2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5d; \ break; \ case 0xd6: \ if (var != DE && var != HU && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5c; \ break; \ case 0xd8: \ if (var != DK && var != NO && var != NO2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5c; \ break; \ case 0xdc: \ @@ -658,11 +658,11 @@ gconv_end (struct __gconv_step *data) else if (var == SE2) \ ch = 0x5e; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xdf: \ if (var != DE) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0xe0: \ @@ -671,36 +671,36 @@ gconv_end (struct __gconv_step *data) else if (var == IT) \ ch = 0x7b; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xe1: \ if (var != HU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x60; \ break; \ case 0xe2: \ if (var != CA && var != CA2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0xe3: \ if (var != PT && var != PT2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7b; \ break; \ case 0xe4: \ if (var != DE && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7b; \ break; \ case 0xe5: \ if (var != DK && var != NO && var != NO2 && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7d; \ break; \ case 0xe6: \ if (var != DK && var != NO && var != NO2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7b; \ break; \ case 0xe7: \ @@ -711,11 +711,11 @@ gconv_end (struct __gconv_step *data) else if (var == PT || var == PT2) \ ch = 0x7c; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xe8: \ if (var != CA && var != CA2 && var != IT && var != FR && var != FR1) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7d; \ break; \ case 0xe9: \ @@ -726,51 +726,51 @@ gconv_end (struct __gconv_step *data) else if (var == SE2) \ ch = 0x60; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xea: \ if (var != CA && var != CA2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5d; \ break; \ case 0xec: \ if (var != IT) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0xee: \ if (var != CA) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5e; \ break; \ case 0xf1: \ if (var != ES && var != ES2 && var != CU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7c; \ break; \ case 0xf2: \ if (var != IT) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7c; \ break; \ case 0xf4: \ if (var != CA && var != CA2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x60; \ break; \ case 0xf5: \ if (var != PT && var != PT2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7d; \ break; \ case 0xf6: \ if (var != DE && var != HU && var != SE && var != SE2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7c; \ break; \ case 0xf8: \ if (var != DK && var != NO && var != NO2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7c; \ break; \ case 0xf9: \ @@ -779,11 +779,11 @@ gconv_end (struct __gconv_step *data) else if (var == IT) \ ch = 0x60; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0xfb: \ if (var != CA && var != CA2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0xfc: \ @@ -792,93 +792,93 @@ gconv_end (struct __gconv_step *data) else if (var == SE2) \ ch = 0x7e; \ else \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ break; \ case 0x160: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0x106: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5d; \ break; \ case 0x107: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7d; \ break; \ case 0x10c: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5e; \ break; \ case 0x10d: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0x110: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5c; \ break; \ case 0x111: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7c; \ break; \ case 0x161: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7b; \ break; \ case 0x17d: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x40; \ break; \ case 0x17e: \ if (var != YU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x60; \ break; \ case 0x2dd: \ if (var != HU) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0x2022: \ if (var != ES2) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x40; \ break; \ case 0x203e: \ if (var != GB && var != CN && var != JP && var != NO && var != SE) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x7e; \ break; \ case 0x20a9: \ if (var != KR) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5c; \ break; \ case 0x2329: \ if (var != JP_OCR_B) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5b; \ break; \ case 0x232a: \ if (var != JP_OCR_B) \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ ch = 0x5d; \ break; \ default: \ if (__glibc_unlikely (ch > 0x7f)) \ { \ UNICODE_TAG_HANDLER (ch, 4); \ - failure = __GCONV_ILLEGAL_INPUT; \ + failure = __gconv_mark_illegal_input (step_data); \ } \ break; \ } \ diff --git a/iconvdata/unicode.c b/iconvdata/unicode.c index d69c9887a1..79bb9886e5 100644 --- a/iconvdata/unicode.c +++ b/iconvdata/unicode.c @@ -163,7 +163,7 @@ gconv_end (struct __gconv_step *data) surrogates pass through, attackers could make a security \ hole exploit by synthesizing any desired plane 1-16 \ character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ inptr += 4; \ diff --git a/iconvdata/utf-16.c b/iconvdata/utf-16.c index a869353f20..9d9fd2a2a3 100644 --- a/iconvdata/utf-16.c +++ b/iconvdata/utf-16.c @@ -206,7 +206,7 @@ gconv_end (struct __gconv_step *data) We must catch this. If we let surrogates pass through, \ attackers could make a security hole exploit by \ synthesizing any desired plane 1-16 character. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ inptr += 4; \ diff --git a/iconvdata/utf-32.c b/iconvdata/utf-32.c index 5693b48975..139eefb6d8 100644 --- a/iconvdata/utf-32.c +++ b/iconvdata/utf-32.c @@ -207,7 +207,7 @@ gconv_end (struct __gconv_step *data) We must catch this. If we let surrogates pass through, \ attackers could make a security hole exploit by \ generating "irregular UTF-32" sequences. */ \ - result = __GCONV_ILLEGAL_INPUT; \ + result = __gconv_mark_illegal_input (step_data); \ if (! ignore_errors_p ()) \ break; \ inptr += 4; \ From patchwork Mon Aug 5 09:49:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968985 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=D5sUXDxH; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WcsBq3bNvz1yYD for ; Mon, 5 Aug 2024 19:50:59 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 52071385E839 for ; Mon, 5 Aug 2024 09:50:57 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 1D1C8385C6E1 for ; Mon, 5 Aug 2024 09:49:58 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1D1C8385C6E1 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 1D1C8385C6E1 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851402; cv=none; b=u8+abcCnwjJYY+SVA/XhiLobjGWnQP0QzWqLtRbdgxfO2AYDAjE2kqoR3IuGnq3fm9qcD97PgNV8GIdZN4vRm3GuUK98xXr/A6okpUZYC8hrVs86+fen+PjxUSwBiOM+20U3PykCgsCnrbfplF//7Esp54DfvzBxnVBg70RLb1I= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851402; c=relaxed/simple; bh=0MP2s+jpQZSE4g7/ps+WhYCbRthNhOxUmuR22xqJcgk=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=E1q6IeoahnmoPxYeRKB+NcVvDu/tR/0b/EbM5HCxQxoYQm8dujceJkvx50a1JH6RedcyzgUmh4bEb3t+pk/VJZgdYPLE80FW/kVx8h23xrMYKR6Ex9U+w2gDdRAro5swaf5iv0Rv/3Otv7xe92H6d7kDX/0u2MBwUlFAXxDDQ7E= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851397; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=PuPiRpne/hg+ldtmnf7uW+lRCm3J42PkKDF5t+TPaGg=; b=D5sUXDxHTU2CmuvQ5hivVwuic+8Ze348oLLJsnRa3Onl4+KDUlMuJB8umJVO0AcFlTJ9IM s1RsqkkpbksxfVAy+1R4RW35zDP4b9oAZimhsA4O3cv0Co0v6HHMILJTifDBlFehlz/Sp9 Ch/6OBlZFlI3jf/SG+UZIGvEbvTWAxI= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-36-Hm6EqJBCOuifvQCgusgVHg-1; Mon, 05 Aug 2024 05:49:55 -0400 X-MC-Unique: Hm6EqJBCOuifvQCgusgVHg-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1CE681955F56 for ; Mon, 5 Aug 2024 09:49:55 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id CE36E1955F40 for ; Mon, 5 Aug 2024 09:49:53 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 5/7] iconv: Support in-place conversions (bug 10460, bug 32033) In-Reply-To: Message-ID: <9a32718165e63b99fb4592d90094f51ab3bca8d0.1722851053.git.fweimer@redhat.com> References: X-From-Line: 9a32718165e63b99fb4592d90094f51ab3bca8d0 Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:50 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org Check if any of the input files overlaps with the output file, and use a temporary file in this case, so that the input is no clobbered before it is read. This fixes bug 10460. It allows to use iconv more easily as a functional replacement for GNU recode. The updated output buffer management truncates the output file if there is no input, fixing bug 32033. --- NEWS | 3 + iconv/Makefile | 11 ++ iconv/iconv_prog.c | 351 ++++++++++++++++++++++++++------- iconv/tst-iconv_prog-buffer.sh | 96 ++++++++- 4 files changed, 386 insertions(+), 75 deletions(-) diff --git a/NEWS b/NEWS index d488874aba..34428e0bfe 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,9 @@ Major new features: which is why this mode is not enabled by default. A future version of the library may turn it on by default, however. +* The iconv program now supports converting files in place. The program + automatically uses a temporary file if required. + Deprecated and removed features, and other changes affecting compatibility: [Add deprecations, removals and changes affecting compatibility here] diff --git a/iconv/Makefile b/iconv/Makefile index 6708e73791..355bb313f3 100644 --- a/iconv/Makefile +++ b/iconv/Makefile @@ -77,6 +77,8 @@ include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left)) ifeq ($(run-built-tests),yes) xtests-special += $(objpfx)test-iconvconfig.out tests-special += \ + $(objpfx)tst-iconv_prog-buffer-large.out \ + $(objpfx)tst-iconv_prog-buffer-tiny.out \ $(objpfx)tst-iconv_prog-buffer.out \ $(objpfx)tst-iconv_prog.out \ # tests-special @@ -138,3 +140,12 @@ $(objpfx)tst-iconv_prog-buffer.out: \ tst-iconv_prog-buffer.sh $(objpfx)iconv_prog $(BASH) $< $(common-objdir) '$(test-program-prefix)' > $@; \ $(evaluate-test) +$(objpfx)tst-iconv_prog-buffer-tiny.out: \ + tst-iconv_prog-buffer.sh $(objpfx)iconv_prog + $(BASH) $< $(common-objdir) '$(test-program-prefix)' \ + '--buffer-size=1' > $@; \ + $(evaluate-test) +$(objpfx)tst-iconv_prog-buffer-large.out: \ + tst-iconv_prog-buffer.sh $(objpfx)iconv_prog + $(BASH) $< $(common-objdir) '$(test-program-prefix)' '' '22' > $@; \ + $(evaluate-test) diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c index 5fe4fe7a6c..3e02db7319 100644 --- a/iconv/iconv_prog.c +++ b/iconv/iconv_prog.c @@ -47,7 +47,11 @@ static void print_version (FILE *stream, struct argp_state *state); void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; -#define OPT_VERBOSE 1000 +enum + { + OPT_VERBOSE = 1000, + OPT_BUFFER_SIZE, + }; #define OPT_LIST 'l' /* Definitions of arguments for argp functions. */ @@ -63,6 +67,10 @@ static const struct argp_option options[] = { "output", 'o', N_("FILE"), 0, N_("output file") }, { "silent", 's', NULL, 0, N_("suppress warnings") }, { "verbose", OPT_VERBOSE, NULL, 0, N_("print progress information") }, + /* This is an internal option intended for testing only. Very small + buffers do not work with all character sets. */ + { "buffer-size", OPT_BUFFER_SIZE, N_("BYTE-COUNT"), OPTION_HIDDEN, + N_("size of in-memory scratch buffer") }, { NULL, 0, NULL, 0, NULL } }; @@ -100,13 +108,20 @@ static int list; /* If nonzero omit invalid character from output. */ int omit_invalid; +/* Current index in argv (after command line processing) with the + input file name. */ +static int current_input_file_index; + +/* Size of the temporary, in-memory buffer. Exceeding it needs + spooling to disk in a temporary file. Controlled by --buffer_size. */ +static size_t output_buffer_size = 1024 * 1024; + /* Prototypes for the functions doing the actual work. */ -static int process_block (iconv_t cd, char *addr, size_t len, FILE **output, - const char *output_file); -static int process_fd (iconv_t cd, int fd, FILE **output, - const char *output_file); -static int process_file (iconv_t cd, FILE *input, FILE **output, - const char *output_file); +static void prepare_output_file (char **argv); +static void close_output_file (int status); +static int process_block (iconv_t cd, char *addr, size_t len); +static int process_fd (iconv_t cd, int fd); +static int process_file (iconv_t cd, FILE *input); static void print_known_names (void); @@ -114,7 +129,6 @@ int main (int argc, char *argv[]) { int status = EXIT_SUCCESS; - int remaining; __gconv_t cd; struct charmap_t *from_charmap = NULL; struct charmap_t *to_charmap = NULL; @@ -126,7 +140,7 @@ main (int argc, char *argv[]) textdomain (_libc_intl_domainname); /* Parse and process arguments. */ - argp_parse (&argp, argc, argv, 0, &remaining, NULL); + argp_parse (&argp, argc, argv, 0, ¤t_input_file_index, NULL); /* List all coded character sets if wanted. */ if (list) @@ -161,7 +175,8 @@ main (int argc, char *argv[]) if (from_charmap != NULL || to_charmap != NULL) /* Construct the conversion table and do the conversion. */ status = charmap_conversion (from_code, from_charmap, to_code, to_charmap, - argc, remaining, argv, output_file); + argc, current_input_file_index, argv, + output_file); else { struct gconv_spec conv_spec; @@ -235,16 +250,14 @@ conversions from `%s' and to `%s' are not supported"), _("failed to start conversion processing")); } - /* The output file. Will be opened when we are ready to produce - output. */ - FILE *output = NULL; + prepare_output_file (argv); /* Now process the remaining files. Write them to stdout or the file specified with the `-o' parameter. If we have no file given as the parameter process all from stdin. */ - if (remaining == argc) + if (current_input_file_index == argc) { - if (process_file (cd, stdin, &output, output_file) != 0) + if (process_file (cd, stdin) != 0) status = EXIT_FAILURE; } else @@ -253,17 +266,17 @@ conversions from `%s' and to `%s' are not supported"), int fd, ret; if (verbose) - fprintf (stderr, "%s:\n", argv[remaining]); - if (strcmp (argv[remaining], "-") == 0) - fd = 0; + fprintf (stderr, "%s:\n", argv[current_input_file_index]); + if (strcmp (argv[current_input_file_index], "-") == 0) + fd = STDIN_FILENO; else { - fd = open (argv[remaining], O_RDONLY); + fd = open (argv[current_input_file_index], O_RDONLY); if (fd == -1) { error (0, errno, _("cannot open input file `%s'"), - argv[remaining]); + argv[current_input_file_index]); status = EXIT_FAILURE; continue; } @@ -271,7 +284,7 @@ conversions from `%s' and to `%s' are not supported"), { /* Read the file in pieces. */ - ret = process_fd (cd, fd, &output, output_file); + ret = process_fd (cd, fd); /* Now close the file. */ close (fd); @@ -289,7 +302,7 @@ conversions from `%s' and to `%s' are not supported"), } } } - while (++remaining < argc); + while (++current_input_file_index < argc); /* Ensure that iconv -c still exits with failure if iconv (the function) has failed with E2BIG instead of EILSEQ. */ @@ -297,8 +310,7 @@ conversions from `%s' and to `%s' are not supported"), status = EXIT_FAILURE; /* Close the output file now. */ - if (output != NULL && fclose (output)) - error (EXIT_FAILURE, errno, _("error while closing output file")); + close_output_file (status); } return status; @@ -328,6 +340,14 @@ parse_opt (int key, char *arg, struct argp_state *state) /* Omit invalid characters from output. */ omit_invalid = 1; break; + case OPT_BUFFER_SIZE: + { + int i = atoi (arg); + if (i <= 0) + error (EXIT_FAILURE, 0, _("invalid buffer size: %s"), arg); + output_buffer_size = i; + } + break; case OPT_VERBOSE: verbose = 1; break; @@ -374,59 +394,247 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); } +/* Command line index of the last input file that overlaps with the + output file. Zero means no temporary file is ever required. */ +static int last_overlapping_file_index; -static int -write_output (const char *outbuf, const char *outptr, FILE **output, - const char *output_file) +/* This is set to true if the output is written to a temporary file. */ +static bool output_using_temporary_file; + +/* This is the file descriptor that will be used by write_output. */ +static int output_fd = -1; + +/* Pointers at the start and end of the fixed-size output buffer. */ +static char *output_buffer_start; + +/* Current write position in the output buffer. */ +static char *output_buffer_current; + +/* Remaining bytes after output_buffer_current in the output buffer. */ +static size_t output_buffer_remaining; + + +/* Reduce the buffer size when writing directly to the output file, to + reduce cache utilization. */ +static size_t copy_buffer_size = BUFSIZ; + +static void +output_error (void) +{ + error (EXIT_FAILURE, errno, _("cannot open output file")); +} + +static void +input_error (const char *path) { - /* We have something to write out. */ - int errno_save = errno; + error (0, errno, _("cannot open input file `%s'"), path); +} - if (*output == NULL) +/* Opens output_file for writing, truncating it. */ +static void +open_output_direct (void) +{ + output_fd = open64 (output_file, O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (output_fd < 0) + output_error (); +} + +static void +prepare_output_file (char **argv) +{ + if (copy_buffer_size > output_buffer_size) + copy_buffer_size = output_buffer_size; + + if (output_file == NULL || strcmp (output_file, "-") == 0) { - /* Determine output file. */ - if (output_file != NULL && strcmp (output_file, "-") != 0) + /* No buffering is required when writing to standard output + because input overlap is expected to be solved externally. */ + output_fd = STDOUT_FILENO; + output_buffer_size = copy_buffer_size; + } + else + { + /* If iconv creates the output file, no overlap is possible. */ + output_fd = open64 (output_file, O_WRONLY | O_CREAT | O_EXCL, 0777); + if (output_fd >= 0) + output_buffer_size = copy_buffer_size; + else { - *output = fopen (output_file, "w"); - if (*output == NULL) - error (EXIT_FAILURE, errno, _("cannot open output file")); + /* Otherwise, check if any of the input files overlap with the + output file. */ + struct statx st; + if (statx (AT_FDCWD, output_file, 0, STATX_INO | STATX_MODE, &st) + != 0) + output_error (); + uint32_t out_dev_minor = st.stx_dev_minor; + uint32_t out_dev_major = st.stx_dev_major; + uint64_t out_ino = st.stx_ino; + + int idx = current_input_file_index; + while (true) + { + /* Special case: no input files means standard input. */ + if (argv[idx] == NULL && idx != current_input_file_index) + break; + + int ret; + if (argv[idx] == NULL || strcmp (argv[idx], "-") == 0) + ret = statx (STDIN_FILENO, "", AT_EMPTY_PATH, STATX_INO, &st); + else + ret = statx (AT_FDCWD, argv[idx], 0, STATX_INO, &st); + if (ret != 0) + { + input_error (argv[idx]); + exit (EXIT_FAILURE); + } + if (out_dev_minor == st.stx_dev_minor + && out_dev_major == st.stx_dev_major + && out_ino == st.stx_ino) + { + if (argv[idx] == NULL) + /* Corner case: index of NULL would be larger than + idx while converting, triggering a switch away + from the temporary file. */ + last_overlapping_file_index = INT_MAX; + else + last_overlapping_file_index = idx; + } + + if (argv[idx] == NULL) + break; + ++idx; + } + + /* If there is no overlap, avoid using a temporary file. */ + if (last_overlapping_file_index == 0) + { + open_output_direct (); + output_buffer_size = copy_buffer_size; + } } - else - *output = stdout; } - if (fwrite (outbuf, 1, outptr - outbuf, *output) < (size_t) (outptr - outbuf) - || ferror (*output)) + output_buffer_start = malloc (output_buffer_size); + if (output_buffer_start == NULL) + output_error (); + output_buffer_current = output_buffer_start; + output_buffer_remaining = output_buffer_size; +} + +/* Write out the range [first, last), terminating the process on write + error. */ +static void +write_fully (int fd, const char *first, const char *last) +{ + while (first < last) { - /* Error occurred while printing the result. */ - error (0, 0, _("\ + ssize_t ret = write (fd, first, last - first); + if (ret == 0) + { + errno = ENOSPC; + output_error (); + } + if (ret < 0) + error (EXIT_FAILURE, errno, _("\ conversion stopped due to problem in writing the output")); - return -1; + first += ret; + } +} + +static void +flush_output (void) +{ + bool temporary_file_not_needed + = current_input_file_index > last_overlapping_file_index; + if (output_fd < 0) + { + if (temporary_file_not_needed) + open_output_direct (); + else + { + /* Create an anonymous temporary file. */ + FILE *fp = tmpfile (); + if (fp == NULL) + output_error (); + output_fd = dup (fileno (fp)); + if (output_fd < 0) + output_error (); + fclose (fp); + output_using_temporary_file = true; + } + /* Either way, no longer use a memory-only staging buffer. */ + output_buffer_size = copy_buffer_size; } + else if (output_using_temporary_file && temporary_file_not_needed) + { + /* The temporary file is no longer needed. Switch to direct + output, replacing output_fd. */ + int temp_fd = output_fd; + open_output_direct (); + + /* Copy over the data spooled to the temporary file. */ + if (lseek (temp_fd, 0, SEEK_SET) < 0) + output_error (); + while (true) + { + char buf[BUFSIZ]; + ssize_t ret = read (temp_fd, buf, sizeof (buf)); + if (ret < 0) + output_error (); + if (ret == 0) + break; + write_fully (output_fd, buf, buf + ret); + } + close (temp_fd); - errno = errno_save; + /* No longer using a temporary file from now on. */ + output_using_temporary_file = false; + output_buffer_size = copy_buffer_size; + } - return 0; + write_fully (output_fd, output_buffer_start, output_buffer_current); + output_buffer_current = output_buffer_start; + output_buffer_remaining = output_buffer_size; } +static void +close_output_file (int status) +{ + /* Do not perform a flush if a temporary file or the in-memory + buffer is in use and there was an error. It would clobber the + overlapping input file. */ + if (status != EXIT_SUCCESS && !omit_invalid && + (output_using_temporary_file || output_fd < 0)) + return; + + /* The current_input_file_index variable is now larger than + last_overlapping_file_index, so the flush_output call switches + away from the temporary file. */ + flush_output (); + + if (output_fd == STDOUT_FILENO) + { + /* Close standard output in safe manner, to report certain + ENOSPC errors. */ + output_fd = dup (output_fd); + if (output_fd < 0) + output_error (); + } + if (close (output_fd) < 0) + output_error (); +} static int -process_block (iconv_t cd, char *addr, size_t len, FILE **output, - const char *output_file) +process_block (iconv_t cd, char *addr, size_t len) { -#define OUTBUF_SIZE 32768 const char *start = addr; - char outbuf[OUTBUF_SIZE]; - char *outptr; - size_t outlen; size_t n; int ret = 0; while (len > 0) { - outptr = outbuf; - outlen = OUTBUF_SIZE; - n = iconv (cd, &addr, &len, &outptr, &outlen); + n = iconv (cd, &addr, &len, + &output_buffer_current, &output_buffer_remaining); if (n == (size_t) -1 && omit_invalid && errno == EILSEQ) { @@ -437,39 +645,34 @@ process_block (iconv_t cd, char *addr, size_t len, FILE **output, errno = E2BIG; } - if (outptr != outbuf) - { - ret = write_output (outbuf, outptr, output, output_file); - if (ret != 0) - break; - } - if (n != (size_t) -1) { /* All the input test is processed. For state-dependent character sets we have to flush the state now. */ - outptr = outbuf; - outlen = OUTBUF_SIZE; - n = iconv (cd, NULL, NULL, &outptr, &outlen); - - if (outptr != outbuf) + n = iconv (cd, NULL, NULL, + &output_buffer_current, &output_buffer_remaining); + if (n == (size_t) -1 && errno == E2BIG) { - ret = write_output (outbuf, outptr, output, output_file); - if (ret != 0) - break; + /* Try again if the state flush exceeded the buffer space. */ + flush_output (); + n = iconv (cd, NULL, NULL, + &output_buffer_current, &output_buffer_remaining); } + bool errno_is_EILSEQ = errno == EILSEQ; if (n != (size_t) -1) break; - if (omit_invalid && errno == EILSEQ) + if (omit_invalid && errno_is_EILSEQ) { ret = 1; break; } } - if (errno != E2BIG) + if (errno == E2BIG) + flush_output (); + else { /* iconv() ran into a problem. */ switch (errno) @@ -500,7 +703,7 @@ incomplete character or shift sequence at end of buffer")); static int -process_fd (iconv_t cd, int fd, FILE **output, const char *output_file) +process_fd (iconv_t cd, int fd) { /* we have a problem with reading from a descriptor since we must not provide the iconv() function an incomplete character or shift @@ -574,16 +777,16 @@ process_fd (iconv_t cd, int fd, FILE **output, const char *output_file) } /* Now we have all the input in the buffer. Process it in one run. */ - return process_block (cd, inbuf, actlen, output, output_file); + return process_block (cd, inbuf, actlen); } static int -process_file (iconv_t cd, FILE *input, FILE **output, const char *output_file) +process_file (iconv_t cd, FILE *input) { /* This should be safe since we use this function only for `stdin' and we haven't read anything so far. */ - return process_fd (cd, fileno (input), output, output_file); + return process_fd (cd, fileno (input)); } diff --git a/iconv/tst-iconv_prog-buffer.sh b/iconv/tst-iconv_prog-buffer.sh index 5ff99a02a3..54ff871d32 100644 --- a/iconv/tst-iconv_prog-buffer.sh +++ b/iconv/tst-iconv_prog-buffer.sh @@ -17,6 +17,12 @@ # License along with the GNU C Library; if not, see # . +# Arguments: +# root of the build tree ($(objpfx-common)) +# test command wrapper (for running on the board/with new ld.so) +# extra flags to pass to iconv +# number of times to double the input files in size (default: 0) + exec 2>&1 set -e @@ -26,7 +32,9 @@ codir=$1 test_program_prefix="$2" # Use internal converters to avoid issues with module loading. -iconv_args="-f ASCII -t UTF-8" +iconv_args="-f ASCII -t UTF-8 $3" + +file_size_doublings=${4-0} failure=false @@ -39,7 +47,19 @@ echo HH > "$tmp/hh" echo XY > "$tmp/xy" echo ZT > "$tmp/zt" echo OUT > "$tmp/out-template" +: > "$tmp/empty" printf '\xff' > "$tmp/0xff" + +# Double all files to produce larger buffers. +for p in "$tmp"/* ; do + i=0 + while test $i -lt $file_size_doublings; do + cat "$p" "$p" > "$tmp/scratch" + mv "$tmp/scratch" "$p" + i=$(($i + 1)) + done +done + cat "$tmp/xy" "$tmp/0xff" "$tmp/zt" > "$tmp/0xff-wrapped" run_iconv () { @@ -113,6 +133,38 @@ expect_files abc def run_iconv -o "$tmp/out" "$tmp/out" "$tmp/abc" expect_files abc def abc +run_iconv -o "$tmp/out" "$tmp/ggg" "$tmp/out" +expect_files ggg abc def abc + +run_iconv -o "$tmp/out" "$tmp/hh" "$tmp/out" "$tmp/hh" +expect_files hh ggg abc def abc hh + +cp "$tmp/out-template" "$tmp/out" +run_iconv -o "$tmp/out" "$tmp/ggg" "$tmp/out" "$tmp/out" "$tmp/ggg" +expect_files ggg out-template out-template ggg + +cp "$tmp/out-template" "$tmp/out" +run_iconv -o "$tmp/out" "$tmp/ggg" "$tmp/out" "$tmp/hh" "$tmp/out" "$tmp/ggg" +expect_files ggg out-template hh out-template ggg + +# Empty output should truncate the output file if exists. + +cp "$tmp/out-template" "$tmp/out" +run_iconv -o "$tmp/out" "$tmp/err" @@ -156,6 +236,20 @@ expect_exit 1 run_iconv -c -o "$tmp/out" \ ! test -s "$tmp/err" expect_files abc xy zt def +cp "$tmp/0xff-wrapped" "$tmp/out" +expect_exit 1 run_iconv -c -o "$tmp/out" "$tmp/out" "$tmp/abc" "$tmp/out" "$tmp/def" +expect_files xy zt abc xy zt def + +cp "$tmp/0xff-wrapped" "$tmp/out" +expect_exit 1 run_iconv -o "$tmp/out" \ + "$tmp/out" "$tmp/abc" "$tmp/out" "$tmp/def" +expect_files 0xff-wrapped + +cp "$tmp/0xff-wrapped" "$tmp/out" +expect_exit 1 run_iconv -c -o "$tmp/out" \ + "$tmp/abc" "$tmp/out" "$tmp/def" "$tmp/out" +expect_files abc xy zt def xy zt + # If the file does not exist yet, it should not be created on error. rm "$tmp/out" From patchwork Mon Aug 5 09:49:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968988 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=iV/1gRI7; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WcsCM3BMQz1yYD for ; Mon, 5 Aug 2024 19:51:27 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 48A073858431 for ; Mon, 5 Aug 2024 09:51:25 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id A377F385DDF8 for ; Mon, 5 Aug 2024 09:50:03 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A377F385DDF8 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A377F385DDF8 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851405; cv=none; b=dc+QRZ3+5uHsZ1fHCW+RFpVGKZgLt4S/NiN5ie6o6ac4vZi0UVJaSdOkC5lnfb/pCrC7+T4UuQ1KKBrFHelhbZbe5WfMDp2OLFKAEEW6uBD+uXMgzJRgSKGasK7G6Un7SzqVwTxn8ZgwlWZu8gaRpI+NNkYmt/5NEdI9QibDpYM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851405; c=relaxed/simple; bh=8UT+c2d2A1mMfi83QAzvOzuhjdx23k3kO8SjfEakuz0=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=m/KgU5D62/DyJBpU/fffuub8qSkazKUjEsF+jvQG+YXHqArx8eJMHhVCAe+0k/SEOXKE5ucFqFwNtStXXeBhYMJxUbrXNsyiPbgps8879hycuNqtYGNL2hgbIlXOaAgWZ8WtVHiP/yJ/jdGKnSnmhWnyvX43gS+QJa4sFj3mXkI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851403; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=vJ9Lx1p0CMBVuOX+nSB3ppxWjoRbm8v51YQTJMJCKz8=; b=iV/1gRI7Kl1+KzVq2YEYM/KNgtd6igVcGoKNy8blMz6yT5xIm35ZBIHPWHh3vsbGueNzMZ iNj0nnHix37XPW3rn06noBWKDSaJlin/+OOjPIk8ZXLvTssisS3DtoKTflDLnGMRKuKKxY pPebIjR7FhuEq6k5HVfxS/XZljGF3Ec= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-616-xLDDDelHOUasrJlFw6QWKw-1; Mon, 05 Aug 2024 05:50:01 -0400 X-MC-Unique: xLDDDelHOUasrJlFw6QWKw-1 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 16C8E1955F6B for ; Mon, 5 Aug 2024 09:50:01 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 241731956046 for ; Mon, 5 Aug 2024 09:49:59 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 6/7] iconv: Multiple - on command line should not fail (bug 32050) In-Reply-To: Message-ID: <704178bb774944b0249febb9bb5f703e40bfd141.1722851053.git.fweimer@redhat.com> References: X-From-Line: 704178bb774944b0249febb9bb5f703e40bfd141 Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:49:56 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org Usually, the second and subsequent - return EOF immediately and do not contribute to the output, but this is not an error. --- iconv/iconv_prog.c | 3 ++- iconv/tst-iconv_prog-buffer.sh | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c index 3e02db7319..dd4bc3a59a 100644 --- a/iconv/iconv_prog.c +++ b/iconv/iconv_prog.c @@ -287,7 +287,8 @@ conversions from `%s' and to `%s' are not supported"), ret = process_fd (cd, fd); /* Now close the file. */ - close (fd); + if (fd != STDIN_FILENO) + close (fd); if (ret != 0) { diff --git a/iconv/tst-iconv_prog-buffer.sh b/iconv/tst-iconv_prog-buffer.sh index 54ff871d32..a9c3729d94 100644 --- a/iconv/tst-iconv_prog-buffer.sh +++ b/iconv/tst-iconv_prog-buffer.sh @@ -265,6 +265,11 @@ expect_exit 1 run_iconv -o "$tmp/out" "$tmp/abc" "$tmp/0xff" "$tmp/def" expect_exit 1 run_iconv -o "$tmp/out" "$tmp/abc" - < "$tmp/0xff" "$tmp/def" ! test -e "$tmp/out" +# Listing standard input multiple times should not fail (bug 32050). + +run_iconv -o "$tmp/out" "$tmp/xy" - - "$tmp/zt" < "$tmp/abc" +expect_files xy abc zt + if $failure ; then exit 1 fi From patchwork Mon Aug 5 09:50:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1968989 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=SyjbVLNO; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WcsDb5ybsz1yYD for ; Mon, 5 Aug 2024 19:52:31 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 062103858294 for ; Mon, 5 Aug 2024 09:52:30 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id CA64E385840F for ; Mon, 5 Aug 2024 09:50:10 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org CA64E385840F Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org CA64E385840F Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851413; cv=none; b=NBN9jx70z2VKoC+fFMHROFjfHYI+I0H6Ne7BfYtbeJKnKc13UTE5vQcVnkn9PDt8W4LGEbo+05xeH985AwrKPljmESdtb2AGj8/ANxUoKsWuQKFQ9IrnSDMpZBW+4m31pyBVatKuqQ1pcDJDCvtiaNIYeFMO9gwjPZzNkLEtYiY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722851413; c=relaxed/simple; bh=HOslAdy5oJ/CpLjlFH/XMYEoW8VATXHjM1MjNZVruFE=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=siaIu014MCDA++JPk0aboKXsPXt/xWLoJmVb4D9gDcKRKPLQJGBPCP7fCWBNrsExFXGQvOMjwUCBSSh5Zbi7ToAreGCCb6/jMiLFdeLQYYXKXS+uQjIJuuDIKuKtZHVmFOmjbCu069GD+TQKnHHPu0QywpetBaCyRmBNQf6dvFk= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722851410; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=Zq8OfA1p8OhmS3UIDWT+bvyq7EgmVuha1uICot8R+cM=; b=SyjbVLNOg9tCNMYez4tCdS504dNEFMHMZlvpR07lAID3JhTeZlRGDYCGjzMEoT2/oMBxFg E5JiE7s7M1yoJXB7xJFxn5eJFa1MIu00/D7g/uVXNz9HfEx9rN9OGdLKmgRZQKn4AYoXTd MhsVct8/97uuud5PrQRcurOlzet3p+E= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-53-B5rmU0Y1MN-d6UU0-AoKag-1; Mon, 05 Aug 2024 05:50:07 -0400 X-MC-Unique: B5rmU0Y1MN-d6UU0-AoKag-1 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E82641955D42 for ; Mon, 5 Aug 2024 09:50:06 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.16]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1F82A1956046 for ; Mon, 5 Aug 2024 09:50:05 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 7/7] iconv: Input buffering for the iconv program (bug 32050) In-Reply-To: Message-ID: References: X-From-Line: cacbe0a2fd1b6ba243014395f84a8bd7f9857aec Mon Sep 17 00:00:00 2001 Date: Mon, 05 Aug 2024 11:50:02 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org Do not read the entire input file into memory. --- iconv/iconv_prog.c | 184 ++++++++++++++------------------- iconv/tst-iconv_prog-buffer.sh | 31 ++++++ 2 files changed, 109 insertions(+), 106 deletions(-) diff --git a/iconv/iconv_prog.c b/iconv/iconv_prog.c index dd4bc3a59a..fcb2c2f5f8 100644 --- a/iconv/iconv_prog.c +++ b/iconv/iconv_prog.c @@ -118,8 +118,9 @@ static size_t output_buffer_size = 1024 * 1024; /* Prototypes for the functions doing the actual work. */ static void prepare_output_file (char **argv); -static void close_output_file (int status); -static int process_block (iconv_t cd, char *addr, size_t len); +static void close_output_file (__gconv_t cd, int status); +static int process_block (iconv_t cd, char **addr, size_t *len, + off64_t file_offset, bool *incomplete); static int process_fd (iconv_t cd, int fd); static int process_file (iconv_t cd, FILE *input); static void print_known_names (void); @@ -311,7 +312,7 @@ conversions from `%s' and to `%s' are not supported"), status = EXIT_FAILURE; /* Close the output file now. */ - close_output_file (status); + close_output_file (cd, status); } return status; @@ -599,7 +600,7 @@ flush_output (void) } static void -close_output_file (int status) +close_output_file (__gconv_t cd, int status) { /* Do not perform a flush if a temporary file or the in-memory buffer is in use and there was an error. It would clobber the @@ -608,10 +609,28 @@ close_output_file (int status) (output_using_temporary_file || output_fd < 0)) return; - /* The current_input_file_index variable is now larger than - last_overlapping_file_index, so the flush_output call switches + /* All the input test is processed. For state-dependent character + sets we have to flush the state now. + + The current_input_file_index variable is now larger than + last_overlapping_file_index, so the flush_output calls switch away from the temporary file. */ + size_t n = iconv (cd, NULL, NULL, + &output_buffer_current, &output_buffer_remaining); + if (n == (size_t) -1 && errno == E2BIG) + { + /* Try again if the state flush exceeded the buffer space. */ + flush_output (); + n = iconv (cd, NULL, NULL, + &output_buffer_current, &output_buffer_remaining); + } + int saved_errno = errno; flush_output (); + if (n == (size_t) -1 && !omit_invalid) + { + errno = saved_errno; + output_error (); + } if (output_fd == STDOUT_FILENO) { @@ -625,51 +644,35 @@ close_output_file (int status) output_error (); } +/* CD is the iconv handle. Input processing starts at *ADDR, and + consumes upto *LEN bytes. *ADDR and *LEN are updated. FILE_OFFSET + is the file offset of the data initially at ADDR. *INCOMPLETE is + set to true if conversion stops due to an incomplete input + sequence. */ static int -process_block (iconv_t cd, char *addr, size_t len) +process_block (iconv_t cd, char **addr, size_t *len, off64_t file_offset, + bool *incomplete) { - const char *start = addr; + const char *start = *addr; size_t n; int ret = 0; - while (len > 0) + while (*len > 0) { - n = iconv (cd, &addr, &len, + n = iconv (cd, addr, len, &output_buffer_current, &output_buffer_remaining); if (n == (size_t) -1 && omit_invalid && errno == EILSEQ) { ret = 1; - if (len == 0) + if (*len == 0) n = 0; else errno = E2BIG; } if (n != (size_t) -1) - { - /* All the input test is processed. For state-dependent - character sets we have to flush the state now. */ - n = iconv (cd, NULL, NULL, - &output_buffer_current, &output_buffer_remaining); - if (n == (size_t) -1 && errno == E2BIG) - { - /* Try again if the state flush exceeded the buffer space. */ - flush_output (); - n = iconv (cd, NULL, NULL, - &output_buffer_current, &output_buffer_remaining); - } - bool errno_is_EILSEQ = errno == EILSEQ; - - if (n != (size_t) -1) - break; - - if (omit_invalid && errno_is_EILSEQ) - { - ret = 1; - break; - } - } + break; if (errno == E2BIG) flush_output (); @@ -680,13 +683,12 @@ process_block (iconv_t cd, char *addr, size_t len) { case EILSEQ: if (! omit_invalid) - error (0, 0, _("illegal input sequence at position %ld"), - (long int) (addr - start)); + error (0, 0, _("illegal input sequence at position %lld"), + (long long int) (file_offset + (*addr - start))); break; case EINVAL: - error (0, 0, _("\ -incomplete character or shift sequence at end of buffer")); - break; + *incomplete = true; + return ret; case EBADF: error (0, 0, _("internal error (illegal descriptor)")); break; @@ -706,79 +708,49 @@ incomplete character or shift sequence at end of buffer")); static int process_fd (iconv_t cd, int fd) { - /* we have a problem with reading from a descriptor since we must not - provide the iconv() function an incomplete character or shift - sequence at the end of the buffer. Since we have to deal with - arbitrary encodings we must read the whole text in a buffer and - process it in one step. */ - static char *inbuf = NULL; - static size_t maxlen = 0; - char *inptr = inbuf; - size_t actlen = 0; - - while (actlen < maxlen) + char inbuf[BUFSIZ]; + char *inbuf_end = inbuf + sizeof (inbuf); + size_t inbuf_used = 0; + off64_t file_offset = 0; + int status = 0; + bool incomplete = false; + + while (true) { - ssize_t n = read (fd, inptr, maxlen - actlen); - - if (n == 0) - /* No more text to read. */ - break; - - if (n == -1) + char *p = inbuf + inbuf_used; + ssize_t read_ret = read (fd, p, inbuf_end - p); + if (read_ret == 0) + { + /* On EOF, check if the previous iconv invocation saw an + incomplete sequence. */ + if (incomplete) + { + error (0, 0, _("\ +incomplete character or shift sequence at end of buffer")); + return 1; + } + return 0; + } + if (read_ret < 0) { - /* Error while reading. */ error (0, errno, _("error while reading the input")); return -1; } - - inptr += n; - actlen += n; + inbuf_used += read_ret; + incomplete = false; + p = inbuf; + int ret = process_block (cd, &p, &inbuf_used, file_offset, &incomplete); + if (ret != 0) + { + status = ret; + if (ret < 0) + break; + } + /* The next loop iteration consumes the leftover bytes. */ + memmove (inbuf, p, inbuf_used); + file_offset += read_ret - inbuf_used; } - - if (actlen == maxlen) - while (1) - { - ssize_t n; - char *new_inbuf; - - /* Increase the buffer. */ - new_inbuf = (char *) realloc (inbuf, maxlen + 32768); - if (new_inbuf == NULL) - { - error (0, errno, _("unable to allocate buffer for input")); - return -1; - } - inbuf = new_inbuf; - maxlen += 32768; - inptr = inbuf + actlen; - - do - { - n = read (fd, inptr, maxlen - actlen); - - if (n == 0) - /* No more text to read. */ - break; - - if (n == -1) - { - /* Error while reading. */ - error (0, errno, _("error while reading the input")); - return -1; - } - - inptr += n; - actlen += n; - } - while (actlen < maxlen); - - if (n == 0) - /* Break again so we leave both loops. */ - break; - } - - /* Now we have all the input in the buffer. Process it in one run. */ - return process_block (cd, inbuf, actlen); + return status; } diff --git a/iconv/tst-iconv_prog-buffer.sh b/iconv/tst-iconv_prog-buffer.sh index a9c3729d94..23098ac56a 100644 --- a/iconv/tst-iconv_prog-buffer.sh +++ b/iconv/tst-iconv_prog-buffer.sh @@ -50,6 +50,9 @@ echo OUT > "$tmp/out-template" : > "$tmp/empty" printf '\xff' > "$tmp/0xff" +# Length should be a prime number, to help with buffer alignment testing. +printf '\xc3\xa4\xe2\x80\x94\xe2\x80\x94\xc3\xa4\n' > "$tmp/utf8-sequence" + # Double all files to produce larger buffers. for p in "$tmp"/* ; do i=0 @@ -270,6 +273,34 @@ expect_exit 1 run_iconv -o "$tmp/out" "$tmp/abc" - < "$tmp/0xff" "$tmp/def" run_iconv -o "$tmp/out" "$tmp/xy" - - "$tmp/zt" < "$tmp/abc" expect_files xy abc zt +# NB: Extra iconv args are ignored after this point. Actual +# multi-byte conversion does not work with tiny buffers. +iconv_args="-f UTF-8 -t ASCII" + +printf 'x\n\xc3' > "$tmp/incomplete" +expect_exit 1 run_iconv -o "$tmp/out" "$tmp/incomplete" +check_out <&$logfd + printf "%s" "$prefix" > "$tmp/prefix" + cat "$tmp/prefix" "$tmp/utf8-sequence" > "$tmp/tmp" + iconv_args="-f UTF-8 -t UCS-4" + run_iconv -o "$tmp/out1" "$tmp/tmp" + iconv_args="-f UCS-4 -t UTF-8" + run_iconv -o "$tmp/out" "$tmp/out1" + expect_files prefix utf8-sequence + + prefix="$prefix@" + prefix_length=$(($prefix_length + 1)) +done + if $failure ; then exit 1 fi