From patchwork Fri Oct 4 15:30:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nick Clifton X-Patchwork-Id: 1992788 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=VBsgK3XP; 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 4XKsvH3Bwsz1xtH for ; Sat, 5 Oct 2024 01:30:51 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 49F5F3844044 for ; Fri, 4 Oct 2024 15:30:49 +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 86222385E011 for ; Fri, 4 Oct 2024 15:30:25 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 86222385E011 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 86222385E011 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=1728055830; cv=none; b=Qs9LMVXccyGosEfjkaqo0gvXv4Y7yCsZi71p0oJz/40OxqGYB32kNXKBHGK1XuyEb7sP7XhW9rsuc1p49cbq1Fuc2Vbxqljftibu1VucBVB4KgIAPUqPnfJxeeNXmQYUSTkDrZEJ6msqKuxZz7xkVupHZdwnhQ0bIp+bA+cgGAM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1728055830; c=relaxed/simple; bh=oSi4NS4pPkiIBR+cCaBgzD8d26fU5gtnh9a5HfxFo5k=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=MZ1kToH1edv1H7R656BXLGDEbtWVsX63CxfKD+VSehiO5t8fZqpkB0hIMOsGXQrdQP7klt3aY14ZGz1+wpKGR6hx2frOMfo7PfDuRnsFIEcex4RN5gDRP+mBVj40m70eqV8dA2FccQhu9FmvmjdFnSzsVik23q09n1TZd/i0bwM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1728055825; 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=W38W530KXzWLDdzLx2kelfaZB6MA/7iYnXGcf2pQyNA=; b=VBsgK3XPtrVEEef7KB0113tPbNjpPdtpkOAyD409dGKHs6PB13iSme/QcwTvqhT3yJoRzV te3inH7R7Yt4j9Qq47NGewXyrbrhw7NktzemCp+UU/xzV5TxxfW+MYI64BlOWinfC+v40a 5OTxRbuhxFEktnmDfYAg9H2ZXhPrnLc= 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-31-r_FpEWqtPteW-Qzvy_10JA-1; Fri, 04 Oct 2024 11:30:23 -0400 X-MC-Unique: r_FpEWqtPteW-Qzvy_10JA-1 Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (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 C83E419300D8 for ; Fri, 4 Oct 2024 15:30:22 +0000 (UTC) Received: from prancer.redhat.com (unknown [10.42.28.91]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E32521956054 for ; Fri, 4 Oct 2024 15:30:21 +0000 (UTC) From: Nick Clifton To: libc-alpha@sourceware.org Subject: [PATCH v3 0/1] [PATCH v3] stdio-common: Add more tests of the setvbuf() function. Date: Fri, 04 Oct 2024 16:30:19 +0100 Message-ID: <878qv3sx6c.fsf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 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, 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 From c8907f4230c42a7453314dc4774483adc3975f58 Mon Sep 17 00:00:00 2001 From: Nick Clifton Date: Fri, 4 Oct 2024 16:22:36 +0100 Subject: [PATCH v3 1/1] [PATCH v3] stdio-common: Add more tests of the setvbuf() function. To: libc-alpha@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. * stdin and stderr can be set into line buffered mode. * full buffering can be set on reads and writes to an ordinary file and that buffering does take place. * line buffering can be set on reads and writes to an ordinary file and that buffering on writes does happen. * buffering can be disabled for reads and writes to/from an ordinary file and that data written is immediately available for reading. --- stdio-common/Makefile | 11 +++ stdio-common/tst-setvbuf2.c | 70 +++++++++++++++ stdio-common/tst-setvbuf3.c | 101 +++++++++++++++++++++ stdio-common/tst-setvbuf4.c | 154 ++++++++++++++++++++++++++++++++ stdio-common/tst-setvbuf5.c | 171 ++++++++++++++++++++++++++++++++++++ stdio-common/tst-setvbuf6.c | 136 ++++++++++++++++++++++++++++ 6 files changed, 643 insertions(+) create mode 100644 stdio-common/tst-setvbuf2.c create mode 100644 stdio-common/tst-setvbuf3.c 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 44165a9c59..a3f7fb0b3f 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -262,6 +262,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 \ @@ -284,6 +289,12 @@ tests := \ xbug \ # tests +# The tst-setvbuf4 test examines the use of full buffering on non-terminal +# based files. Currently it fails because somewhere inside glibc the buffering +# is disabled and writes to the file appear there immediately. This needs +# investigating. +test-xfail-tst-setvbuf4 = yes + ifeq ($(run-built-tests),yes) ifeq (yes,$(build-shared)) ifneq ($(PERL),no) diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c new file mode 100644 index 0000000000..cc0c086e7d --- /dev/null +++ b/stdio-common/tst-setvbuf2.c @@ -0,0 +1,70 @@ +/* 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 +#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 (stdin, NULL, _IONBF, 0) == 0); + TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IONBF, 0) == 0); + TEST_VERIFY_EXIT (setvbuf (stdout, NULL, _IONBF, 0) == 0); + + /* The theory was that this test would be run with stdout and stderr redirected + into a single file. Then writes to stdout and stderr would be performed + with and without newlines and finally the contents of the file would be + examined to find out if any buffering has taken place. Unfortunately whilt + this works when run by hand, or from a makefile running on an ordinary + terminal, it does not work when run by Linaro's CI system. + + There is no way to distinguish Linaro's execution environment from a normal + execution environment. (Testing that stdout/stderr are attached to + terminals does not work, since they are not - they are attached to an output + file: tst-setvbuf.out). All of which means that in the end we cannot test + the behaviour of buffering when writing to standard files. (See + tst-setvuf4.c and tst-setvbuf5.c for tests that do work when writing to disk + based files). + + Hence if we get this far, we consider that the test has passed. */ + + return 0; +} + +#include diff --git a/stdio-common/tst-setvbuf3.c b/stdio-common/tst-setvbuf3.c new file mode 100644 index 0000000000..69004aa0f9 --- /dev/null +++ b/stdio-common/tst-setvbuf3.c @@ -0,0 +1,101 @@ +/* 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 +#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) +{ + static char local_buf [LOCAL_BUF_SIZE]; + + /* 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. */ + + /* Use a library allocated line buffer for stdin. */ + TEST_VERIFY_EXIT (setvbuf (stdin, NULL, _IOLBF, LOCAL_BUF_SIZE) == 0); + + /* 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. + + So if the following two calls to setvbuf fail, it is not an error, just + an indication that the test cannot be run. */ + + /* Use a library allocated line buffer for stderr. */ + if (setvbuf (stderr, NULL, _IOLBF, LOCAL_BUF_SIZE) != 0) + FAIL_UNSUPPORTED ("tst-setvbuf3.c: POSIX standard does not guarantee being able to set line buffering mode on stderr"); + + /* Use a program allocated line buffer for stdout. */ + if (setvbuf (stdout, local_buf, _IOLBF, sizeof local_buf) != 0) + FAIL_UNSUPPORTED ("tst-setvbuf3.c: POSIX standard does not guarantee being able to set line buffering mode on stdout"); + + /* The theory was that this test would be run with stdout and stderr redirected + into a single file. Then writes to stdout and stderr would be performed + with and without newlines and finally the contents of the file would be + examined to find out if any buffering has taken place. Unfortunately whilt + this works when run by hand, or from a makefile running on an ordinary + terminal, it does not work when run by Linaro's CI system. + + There is no way to distinguish Linaro's execution environment from a normal + execution environment. (Testing that stdout/stderr are attached to + terminals does not work, since they are not - they are attached to an output + file: tst-setvbuf.out). All of which means that in the end we cannot test + the behaviour of buffering when writing to standard files. (See + tst-setvuf4.c and tst-setvbuf5.c for tests that do work when writing to disk + based files). + + Hence if we get this far, we consider that the test has passed. */ + + return 0; +} + +#include 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