From patchwork Tue Sep 10 12:43:54 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nick Clifton X-Patchwork-Id: 97382 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 688333858C98 for ; Tue, 10 Sep 2024 12:44:32 +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 21C2A385C6D2 for ; Tue, 10 Sep 2024 12:44:02 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 21C2A385C6D2 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 21C2A385C6D2 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=1725972247; cv=none; b=a7Zp7VAxt8k5/jVu5R0Y5lZZzOXvubc/xRGufPU5jK3YAULJs2ilSKW89oo3+bK+ISBpU6xzj10IYPTkt0EhGgJr6HZ1e1iwZZbDz6RApnBfvOmXYdM5TbSxT50+wf9y/00XiCOYV/UKZq4dlVrovJKlMt7mW1SZkGFAyAXxaoA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1725972247; c=relaxed/simple; bh=37sn6a0j6YV/1yavXL4qYTP7c44gmHxKKWTlJB8y+Vk=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=llLBIoJDeuxbBWkQkrQ4L42l32nPJBcPP2vT/UqKuldzcznW1UdSEG+hN0EQDBzC6zavVFWJS/jOjXIOsiqtJeSI/Sh1YK6CRNCngfJOlZVA73S5yCQ4i1eDxmvwiGop8kYboSdznFxRdeaGWHO3Altlk4KDgZJm3r22HT8gdEM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1725972241; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type; bh=b1Rhb1zDRKheUvC8UBObmlMjpoOQ6YA1p0cEUyFrmtU=; b=VvFwkY7iIZ6KYTJzcEstNvVB+XnzUXWj9tPPs+jXaSYbLFyt1TU/C928b5TdvSC7oG/GeF CdOpPUyM4gRg4HASg+9bzE8Xvvh0t0oJ4VJDBgJotIGTuwKNXl1zBTKzP403rPMHfv+T6/ NEFpZyB4FTWZ3KgDbzljGcFoNZetypk= 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-18-6jcO-Gc6ONOYB2EHYkwP8g-1; Tue, 10 Sep 2024 08:43:59 -0400 X-MC-Unique: 6jcO-Gc6ONOYB2EHYkwP8g-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 EC6B71955F65 for ; Tue, 10 Sep 2024 12:43:57 +0000 (UTC) Received: from prancer.redhat.com (unknown [10.42.28.89]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C96FF19560AB for ; Tue, 10 Sep 2024 12:43:56 +0000 (UTC) From: Nick Clifton To: libc-alpha@sourceware.org Subject: [PATCH 1/1] stdio-common: Add more tests of the setvbuf() function. Date: Tue, 10 Sep 2024 13:43:54 +0100 Message-ID: <87a5gfhe5x.fsf@redhat.com> 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=-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_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~patchwork=sourceware.org@sourceware.org This patch series extends the current test of the setvbuf() function in order to cover more use cases. In particular it checks that: * stdout and stderr can be set into unbuffered mode and that writes to either stream appear immediately. * stdin and stderr can be set into line buffered mode and that writes to either are buffered until a new-line character is encountered. * full buffering can be set on a non-terminal file and that buffering does take place. * line buffering can be set on a non-terminal file and that line level buffering does take place. * buffering can be disabled for a non-terminal file and that data written is immediately available for reading. --- stdio-common/Makefile | 23 +++++ stdio-common/tst-setvbuf2.c | 85 +++++++++++++++ stdio-common/tst-setvbuf2.expect | 3 + stdio-common/tst-setvbuf3.c | 111 ++++++++++++++++++++ stdio-common/tst-setvbuf3.expect | 3 + stdio-common/tst-setvbuf4.c | 154 ++++++++++++++++++++++++++++ stdio-common/tst-setvbuf5.c | 171 +++++++++++++++++++++++++++++++ stdio-common/tst-setvbuf6.c | 136 ++++++++++++++++++++++++ 8 files changed, 686 insertions(+) create mode 100644 stdio-common/tst-setvbuf2.c create mode 100644 stdio-common/tst-setvbuf2.expect create mode 100644 stdio-common/tst-setvbuf3.c create mode 100644 stdio-common/tst-setvbuf3.expect create mode 100644 stdio-common/tst-setvbuf4.c create mode 100644 stdio-common/tst-setvbuf5.c create mode 100644 stdio-common/tst-setvbuf6.c diff --git a/stdio-common/Makefile b/stdio-common/Makefile index ce7f7cdd3b..a737ee1dc7 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -254,6 +254,11 @@ tests := \ tst-scanf-round \ tst-scanf-to_inpunct \ tst-setvbuf1 \ + tst-setvbuf2 \ + tst-setvbuf3 \ + tst-setvbuf4 \ + tst-setvbuf5 \ + tst-setvbuf6 \ tst-sprintf \ tst-sprintf-errno \ tst-sprintf2 \ @@ -316,6 +321,8 @@ tests-special += \ $(objpfx)tst-printf.out \ $(objpfx)tst-printfsz-islongdouble.out \ $(objpfx)tst-setvbuf1-cmp.out \ + $(objpfx)tst-setvbuf2-cmp.out \ + $(objpfx)tst-setvbuf3-cmp.out \ $(objpfx)tst-unbputc.out \ # tests-special @@ -583,5 +590,21 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out cmp $^ > $@; \ $(evaluate-test) +$(objpfx)tst-setvbuf2.out: /dev/null $(objpfx)tst-setvbuf2 + $(test-program-cmd) > $@ 2>&1; \ + $(evaluate-test) + +$(objpfx)tst-setvbuf2-cmp.out: tst-setvbuf2.expect $(objpfx)tst-setvbuf2.out + cmp $^ > $@; \ + $(evaluate-test) + +$(objpfx)tst-setvbuf3.out: /dev/null $(objpfx)tst-setvbuf3 + $(test-program-cmd) > $@ 2>&1; \ + $(evaluate-test) + +$(objpfx)tst-setvbuf3-cmp.out: tst-setvbuf3.expect $(objpfx)tst-setvbuf3.out + cmp $^ > $@; \ + $(evaluate-test) + $(objpfx)tst-printf-round: $(libm) $(objpfx)tst-scanf-round: $(libm) diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c new file mode 100644 index 0000000000..91b02cd842 --- /dev/null +++ b/stdio-common/tst-setvbuf2.c @@ -0,0 +1,85 @@ +/* Test using setvbuf to set unbuffered mode on standard streams. + 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 + +/* Check that the standard streams (stdout and stderr) can be set to + unbuffered. Note - the POSIX standard indicates that setting the + buffering on a stream might not work. It states: + + The setvbuf( ) function may be used after the stream + pointed to by stream is associated with an open file + but before any other operation (other than an unsuccessful + call to setvbuf( )) is performed on the stream. + + Hence if messages have already been written to stdout and/or stderr + before this code is executed then we may not be able to change + the buffering. The standard does not provide a way to detect if + this has happened, so we have to hope for the best. */ + +/* By default the test harness code will call setvbuf on stdout. + Since we want to do this ourselves, we disable the harness' + behaviour here. */ +#define TEST_NO_SETVBUF 1 + +#include + +static int +do_test (void) +{ + TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IONBF, 0) == 0); + TEST_VERIFY_EXIT (setvbuf (stdout, NULL, _IONBF, 0) == 0); + + /* Emit some text to both streams with newlines in the middle. */ + fprintf (stderr, "setvbuf2: Output #1 "); + printf ("setvbuf2: Output #2 "); + fprintf (stderr, ".\nAny day now "); + fprintf (stdout, ".\nWe should discover "); + + /* Flush the output buffers. */ + fflush (stderr); + fflush (stdout); + + /* Assuming that test is attached to a terminal and the shell mixes stdout + and stderr output, then... + + If the streams are unbuffered then the output should look like this: + + setvbuf2: Output #1 setvbuf2: Output #2 . + Any day now . + We should discover + + However if the streams are line buffered then it should look like this: + + setvbuf2: Output #1 . + setvbuf2: Output #2 . + Any day now We should discover + + And if they are fully buffered then it should look like this: + + setvbuf2: Output #1 . + Any day now + setvbuf2: Output #2 . + We should discover + + This is checked by comparing the output to the tst-setvbuf2.expect file. */ + + return 0; +} + +#include diff --git a/stdio-common/tst-setvbuf2.expect b/stdio-common/tst-setvbuf2.expect new file mode 100644 index 0000000000..2edc14a158 --- /dev/null +++ b/stdio-common/tst-setvbuf2.expect @@ -0,0 +1,3 @@ +setvbuf2: Output #1 setvbuf2: Output #2 . +Any day now . +We should discover diff --git a/stdio-common/tst-setvbuf3.c b/stdio-common/tst-setvbuf3.c new file mode 100644 index 0000000000..a85c3294b1 --- /dev/null +++ b/stdio-common/tst-setvbuf3.c @@ -0,0 +1,111 @@ +/* Test using setvbuf to set line buffered mode on standard streams. + 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 + +/* Check that the standard streams (stdout and stderr) can be set to + line buffered. Note - the POSIX standard indicates that setting + the buffering on a file might not work. It states: + + The setvbuf( ) function may be used after the stream + pointed to by stream is associated with an open file + but before any other operation (other than an unsuccessful + call to setvbuf( )) is performed on the stream. + + Hence if messages have already been written to stdout and/or stderr, + eg by the test harness code, but before this code is executed then + the calls to setvbuf might not actually change anything. */ + +/* By default the test harness code will call setvbuf on stdout. + Since we do not want that to happen we use the define below. */ +#define TEST_NO_SETVBUF 1 + +#include + +#define LOCAL_BUF_SIZE 128 + +static int +do_test (void) +{ + /* Note - the POSIX standard indicates that setting the buffering on a + stream might not give the results expected. It states: + + If BUF is not a null pointer, the array it points to *may* + be used instead of a buffer allocated by setvbuf( ) and the + argument SIZE specifies the size of the array; otherwise, + SIZE *may* determine the size of a buffer allocated by the + setvbuf( ) function. + + So whilst we can set the buffering mode, we cannot be certain of the size + of the buffer that will be used, or where that buffer will be held. */ + + /* Note - the POSIX standard also indicates that line buffering might only + work on input files: + + Applications should note that many implementations + only provide line buffering on input from terminal + devices. + + For the purposes of this test we assume that this restriction is not + enforced. */ + + /* Use a library allocated line buffer for stderr. */ + TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IOLBF, LOCAL_BUF_SIZE) == 0); + + static char local_buf [LOCAL_BUF_SIZE]; + + /* Use a program allocated line buffer for stdout. */ + TEST_VERIFY_EXIT (setvbuf (stdout, local_buf, _IOLBF, sizeof local_buf) == 0); + + /* Emit some text to both streams with newlines in the middle. */ + fprintf (stderr, "setvbuf3: Output #1 "); + printf ("setvbuf3: Output #2 "); + fprintf (stderr, ".\nAny day now "); + printf (".\nWe should discover "); + + /* Flush the output buffers. */ + fflush (stderr); + fflush (stdout); + + /* Assuming that test is attached to a terminal and the shell mixes stdout + and stderr output, then... + + If the streams are unbuffered then the output should look like this: + + setvbuf3: Output #1 setvbuf3: Output #2 . + Any day now . + We should discover + + However if the streams a line buffered then it should look like this: + + setvbuf3: Output #1 . + setvbuf3: Output #2 . + Any day now We should discover + + And if they are fully buffered then it should look like this: + + setvbuf3: Output #1 . + Any day now setvbuf3: Output #2 . + We should discover + + This is checked by comparing the output to the tst-setvbuf3.expect file. */ + + return 0; +} + +#include diff --git a/stdio-common/tst-setvbuf3.expect b/stdio-common/tst-setvbuf3.expect new file mode 100644 index 0000000000..80241ab0c9 --- /dev/null +++ b/stdio-common/tst-setvbuf3.expect @@ -0,0 +1,3 @@ +setvbuf3: Output #1 . +setvbuf3: Output #2 . +Any day now We should discover diff --git a/stdio-common/tst-setvbuf4.c b/stdio-common/tst-setvbuf4.c new file mode 100644 index 0000000000..f918432ab6 --- /dev/null +++ b/stdio-common/tst-setvbuf4.c @@ -0,0 +1,154 @@ +/* Test using setvbuf to set full buffered mode on a non-terminal file. + 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 + +/* Check the behaviour of enabling full buffering on a non-terminal stream. + + Note - the POSIX standard indicates that setting the buffering on a file + might not produce the expected results. It states: + + If BUF is not a null pointer, the array it points to *may* + be used instead of a buffer allocated by setvbuf() and the + argument SIZE specifies the size of the array; otherwise, + SIZE *may* determine the size of a buffer allocated by the + setvbuf() function. + + So whilst we can set the buffering mode, we cannot be certain of the size of + the buffer that will be used, or where that buffer will be held. For now we + proceed with the assumption that if the calls to setvbuf succeed then the + buffers are the size we expect. But we do not test to see if the buffer we + have supplied are the ones actually being used. + + Note - because of the POSIX rules on the interactions of multiple handles + on the same stream (see section 2.5.1 "Interaction of File Descriptors + and Standard I/O Streams" in the POSIX specification) we cannot just open a + file twice, once for reading and once for writing and then check that writes + to the file do not happen until the buffer is full. Nor can we open a + single stream for both reading and writing and test that way because any + time we reposition the file pointer (ie by calling fseek) the buffer is + flushed. + + In theory we could use fmemopen() to create a memory backed stream and + then check the buffering behaviour that way. But it turns out the glibc's + implementation does not support buffering, so that does not work. + + Another alternative is open_memstream() - which does use glibc's default + I/O code. But it turns out that the function is not suitable for this test + as it specifically does not support having the memory buffer examined after + a write has completed but before a flush has been performed. + + So we resort to opening an ordinary file and using mmap to provide us with + a memory page that we can examine. */ + +#define BIG_BUF_SIZE 128 +#define SMALL_BUF_SIZE 12 + +_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE, + "test assumes that its small buffer is shorter than its large buffer"); +_Static_assert (SMALL_BUF_SIZE > 1, + "test assumes that its small buffer is more than one byte long"); + +static char file_buf [SMALL_BUF_SIZE]; +static char small_buf [SMALL_BUF_SIZE]; +static char big_buf [BIG_BUF_SIZE]; + +static int +do_test (void) +{ + FILE * file; + char * mem_page; + size_t page_size; + int fd; + int val; + + TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0); + TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE); + + /* Create a temporay file. */ + TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf4", NULL)) != -1); + + /* Create a stream attached to the file. */ + TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL); + + /* Set full buffering on the file, using our (small) file buffer. + + Note - this has to be done now, right after opening the file. The POSIX + standard states: + + The setvbuf() function may be used after the stream + pointed to by stream is associated with an open file + but before any other operation (other than an unsuccessful + call to setvbuf( )) is performed on the stream. */ + TEST_VERIFY_EXIT (setvbuf (file, file_buf, _IOFBF, sizeof file_buf) == 0); + + /* Map the file into memory. + FIXME: Using xmmap would simplify this statement, but it would be + inconsistent with how we test all of the other library function calls. */ + TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) != MAP_FAILED); + + /* The page backing the file has not been initialised yet, so attempts + to read from it will produce a SIGBUS error. We fix that by setting + the file to the size of the page. */ + TEST_VERIFY (ftruncate (fd, page_size) == 0); + + /* Create our test data using a value that should not + be the same as the contents of the memory page. */ + val = mem_page[0] + 1; + memset (small_buf, val, sizeof small_buf); + + /* Write one byte (which is less than our file buffer size) to the file. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1); + + /* Check that the byte has not made it into the file. */ + TEST_VERIFY (mem_page[0] != val); + + /* Try reading the byte. This would fail, except for the + fact that we call fseek() first, which flushes the buffer. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (big_buf, 1, 1, file) == 1); + TEST_VERIFY (big_buf[0] == small_buf[0]); + + /* Write a whole buffer full. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf); + + /* Check that the in-memory buffer now contains these bytes. */ + TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0); + + /* Try reading lots of data. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf); + + /* Verify that what we have read the expected bytes. */ + TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0); + + fclose (file); + return 0; +} + +#include diff --git a/stdio-common/tst-setvbuf5.c b/stdio-common/tst-setvbuf5.c new file mode 100644 index 0000000000..4a32e19ebb --- /dev/null +++ b/stdio-common/tst-setvbuf5.c @@ -0,0 +1,171 @@ +/* Test using setvbuf to set line buffered mode on a non-terminal file. + 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 + +/* Check the behaviour of enabling line buffering mode on a non-terminal stream. + + Note - the POSIX standard indicates that setting the buffering on a file + might not produce the expected results. It states: + + If BUF is not a null pointer, the array it points to *may* + be used instead of a buffer allocated by setvbuf( ) and the + argument SIZE specifies the size of the array; otherwise, + SIZE *may* determine the size of a buffer allocated by the + setvbuf( ) function. + + So whilst we can set the buffering mode, we cannot be certain of the size of + the buffer that will be used, or where that buffer will be held. For now we + proceed with the assumption that if the calls to setvbuf succeed then the + buffers are the size we expect. But we do not test to see if the buffer we + have supplied are the ones actually being used. + + Note - the POSIX standard also indicates that line buffering mode might not + be supported: + + Applications should note that many implementations only + provide line buffering on input from terminal devices. + + So the test first tries to write less than a line of characters. If this + shows up in the memory buffer, then we know that line buffering is not + supported and the test exits. Otherwise it continues and tests that + writing a full line does cause the buffer to be flushed to memory. + + Note - because of the POSIX rules on the interactions of multiple handles + on the same stream (see section 2.5.1 "Interaction of File Descriptors + and Standard I/O Streams" in the POSIX specification) we cannot just open a + file twice, once for reading and once for writing and then check that writes + to the file do not happen until the buffer is full. Nor can we open a + single stream for both reading and writing and test that way because any + time we reposition the file pointer (ie by calling fseek) the buffer is + flushed. + + In theory we could use fmemopen() to create a memory backed stream and + then check the buffering behaviour that way. But it turns out the glibc's + implementation does not support buffering, so that does not work. + + Another alternative is open_memstream() - which does use glibc's default + I/O code. But it turns out that the function is not suitable for this test + as it specifically does not support having the memory buffer examined after + a write has completed but before a flush has been performed. + + So we resort to opening an ordinary file and using mmap to provide us with + a memory page that we can examine. */ + +#define BIG_BUF_SIZE 128 +#define SMALL_BUF_SIZE 12 + +_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE, + "test assumes that its small buffer is shorter than its large buffer"); +_Static_assert (SMALL_BUF_SIZE > 1, + "test assumes that its small buffer is more than one byte long"); + +static char line_buf [SMALL_BUF_SIZE]; +static char in_buf [BIG_BUF_SIZE]; + +const char string_without_newline[] = "test"; + +static int +do_test (void) +{ + FILE * file; + char * mem_page; + size_t page_size; + int fd; + int len; + + TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0); + TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE); + + /* Create a temporay file. */ + TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf5", NULL)) != -1); + + /* Create a stream attached to the file. */ + TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL); + + /* Set line buffering on the file, using our (small) file buffer. + + Note - this has to be done now, right after opening the file. The POSIX + standard states: + + The setvbuf() function may be used after the stream + pointed to by stream is associated with an open file + but before any other operation (other than an unsuccessful + call to setvbuf( )) is performed on the stream. */ + TEST_VERIFY_EXIT (setvbuf (file, line_buf, _IOLBF, sizeof line_buf) == 0); + + /* Map the file into memory. + FIXME: Using xmmap would simplify this statement, but it would be + inconsistent with how we test all of the other library function calls. */ + TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) != MAP_FAILED); + + /* The page backing the file has not been initialised yet, so attempts + to read from it will produce a SIGBUS error. We fix that by setting + the file to the size of the page. */ + TEST_VERIFY (ftruncate (fd, page_size) == 0); + + /* Write one byte to the file. */ + TEST_VERIFY (fwrite (string_without_newline, 1, 1, file) == 1); + + /* Check that the byte has not made it into the file. */ + if (mem_page[0] == string_without_newline[0]) + { + printf ("info: tst-setvbuf5.c: line buffering is not supported on non-terminal I/O streams\n"); + printf ("info: tst-setvbuf5.c: this is allowed by the POSIX standard\n"); + close (fd); + return 0; + } + + /* Try reading the byte. This would fail, except for the + fact that we call fseek() first, which flushes the buffer. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (in_buf, 1, 1, file) == 1); + TEST_VERIFY (in_buf[0] == string_without_newline[0]); + + /* Write one and half lines to the file. */ + len = strlen (string_without_newline); + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fprintf (file, "%s\n%s", string_without_newline, + string_without_newline) == len * 2 + 1); + + /* Check that the in-memory buffer now contains the first line. */ + TEST_VERIFY (strncmp (mem_page, string_without_newline, len) == 0); + + /* And that it does not contain the second line. */ + TEST_VERIFY (strncmp (mem_page + len + 1, string_without_newline, len) != 0); + + /* Read back what we have written. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (in_buf, 1, len * 2 + 1, file) == len * 2 + 1); + + /* Check that we read in the second, not-newline-terminated string. */ + TEST_VERIFY (strncmp (in_buf + len + 1, string_without_newline, len) == 0); + + fclose (file); + return 0; +} + +#include diff --git a/stdio-common/tst-setvbuf6.c b/stdio-common/tst-setvbuf6.c new file mode 100644 index 0000000000..10be391e3b --- /dev/null +++ b/stdio-common/tst-setvbuf6.c @@ -0,0 +1,136 @@ +/* Test using setvbuf to set unbuffered mode on a non-terminal file. + 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 + +/* Check the behaviour of setting unbuffered mode on a non-terminal stream. + + Note - because of the POSIX rules on the interactions of multiple handles + on the same stream (see section 2.5.1 "Interaction of File Descriptors + and Standard I/O Streams" in the POSIX specification) we cannot just open a + file twice, once for reading and once for writing and then check that writes + to the file happen immediately. Nor can we open a single stream for both + reading and writing and test that way because any time we reposition the + file pointer (ie by calling fseek) the buffer is flushed. + + In theory we can use fmemopen() to create a memory backed stream and + then check the buffering behaviour that way. But it turns out the glibc's + implementation does not support buffering, which would not matter for this + test except that we want to be sure that buffering is not enabled because + of our actions, rather than the library's internal code. + + Another alternative is open_memstream() - which does use glibc's default + I/O code. But it turns out that the function is not suitable for this test + as it specifically does not support having the memory buffer examined after + a write has completed but before a flush has been performed. + + So we resort to opening an ordinary file and using mmap to provide us with + a memory page that we can examine. */ + +#define BIG_BUF_SIZE 128 +#define SMALL_BUF_SIZE 12 + +_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE, + "test assumes that its small buffer is shorter than its large buffer"); + +static char small_buf [SMALL_BUF_SIZE]; +static char big_buf [BIG_BUF_SIZE]; + +static int +do_test (void) +{ + FILE * file; + char * mem_page; + size_t page_size; + int fd; + int val; + + TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0); + TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE); + + /* Create a temporay file. */ + TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf6", NULL)) != -1); + + /* Create a stream attached to the file. */ + TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL); + + /* Disable buffering on the file. + + Note - this has to be done now, right after opening the file. The POSIX + standard states: + + The setvbuf() function may be used after the stream + pointed to by stream is associated with an open file + but before any other operation (other than an unsuccessful + call to setvbuf( )) is performed on the stream. */ + TEST_VERIFY_EXIT (setvbuf (file, NULL, _IONBF, 0) == 0); + + /* Map the file into memory. + FIXME: Using xmmap would simplify this statement, but it would be + inconsistent with how we test all of the other library function calls. */ + TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) != MAP_FAILED); + + /* The page backing the file has not been initialised yet, so attempts + to read from it will produce a SIGBUS error. We fix that by setting + the file to the size of the page. */ + TEST_VERIFY (ftruncate (fd, page_size) == 0); + + /* Create our test data using a value that should not + be the same as the contents of the memory page. */ + val = mem_page[0] + 1; + memset (small_buf, val, sizeof small_buf); + + /* Write one byte to the file. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1); + + /* Check that the byte has made it into the file. */ + TEST_VERIFY (mem_page[0] == val); + + /* Try reading the byte. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (big_buf, 1, 1, file) == 1); + TEST_VERIFY (big_buf[0] == small_buf[0]); + + /* Write a whole buffer full. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf); + + /* Check that the in-memory buffer now contains these bytes. */ + TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0); + + /* Try reading lots of data. */ + TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0); + TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf); + + /* Verify that what we have read the expected bytes. */ + TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0); + + fclose (file); + return 0; +} + +#include