From patchwork Mon Nov 11 13:32:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nick Clifton X-Patchwork-Id: 100757 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 7CAA43858C3A for ; Mon, 11 Nov 2024 13:33:14 +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 844033858D35 for ; Mon, 11 Nov 2024 13:32:43 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 844033858D35 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 844033858D35 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=1731331968; cv=none; b=HnG0MHo0/etxx0p7sDEm7H1I2SrekRjJxBT9yHTDG3AUoGYPJdYt6ieu/4cnzZB1zg6DjY4zbmOVYVfL5jTsoGsHR2AuKqhR+CbLHcfqeC6a7yy6Ee327+OerHRFZBhJqn63q98xcDvsto7WP3ylCqaEaDVmb8twCQiZ1jGShok= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1731331968; c=relaxed/simple; bh=oZYX85FKvvTv4uEN+nX3CXFkRDtigQyIudx+RCQ37eo=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=Zysk118qcF4fTOVwlEeXT/fMx1TicXtGCKSg7MeVz/0Mh5SEF1RO3ES59sDAPQdtClCfcnlJcIaaXd10ASzqua9kV67ZelFi5N5e6ZoEw/MhphXojCKBC13m5tyw67Io0OMK+/u5TwFcYLx9L8Bz86neIUF+7GndrHPH4yZ+L7Q= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1731331963; 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=ENZR22aWNhBktqrkwIfHp/rkQMioYIG8UD/r+q3gsyc=; b=CLTBuDBpP1luhqlR/Ws4MO7chtw8vqMEQpurgmhdKDt7fJtxBeryHV7hK0yOMxwXMDdlqp 765IVRVkv2Mj9H06oh3lwDiH+zqwHCoCoxMaojDHKtROHtpfKN9EQ6aCNum8DNKXB39NA/ q6AyC6wvZ7oJVxnYNq2jA2yxk/f4//Q= 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-22-Lmr4cfh_NOqsI-t5Hh6ksA-1; Mon, 11 Nov 2024 08:32:41 -0500 X-MC-Unique: Lmr4cfh_NOqsI-t5Hh6ksA-1 X-Mimecast-MFC-AGG-ID: Lmr4cfh_NOqsI-t5Hh6ksA 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-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0D4E2195609E for ; Mon, 11 Nov 2024 13:32:40 +0000 (UTC) Received: from prancer.redhat.com (unknown [10.42.28.36]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 24BC91955E93 for ; Mon, 11 Nov 2024 13:32:38 +0000 (UTC) From: Nick Clifton To: libc-alpha@sourceware.org Subject: [PATCH v5 1/1] stdio-common: Add more tests of the setvbuf() function Date: Mon, 11 Nov 2024 13:32:36 +0000 Message-ID: <877c99lwuj.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-MFC-PROC-ID: 0El6pHrGRyiTTzmpX5PoxbWA1aN5Ql3Vpe-ktHtiObo_1731331960 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~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 reads and writes to an in-memory file and that buffering does take place. * line buffering can be set on reads and writes to an in-memory 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. Change History: v1: Original version with just a test of unbuffered standard stream access. v2: Extended version with more tests. v3: File stream tests rewritten to use in-memory streams. v4: File stream tests rewritten to use mmap'ed files. v5: Full buffer file stream test switched to larger buffer sizes, --- stdio-common/Makefile | 5 ++ stdio-common/tst-setvbuf2.c | 70 +++++++++++++++ stdio-common/tst-setvbuf3.c | 101 +++++++++++++++++++++ stdio-common/tst-setvbuf4.c | 167 +++++++++++++++++++++++++++++++++++ stdio-common/tst-setvbuf5.c | 171 ++++++++++++++++++++++++++++++++++++ stdio-common/tst-setvbuf6.c | 136 ++++++++++++++++++++++++++++ 6 files changed, 650 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 e76e40e587..dcfe8fd6f1 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -292,6 +292,11 @@ tests := \ tst-scanf-nan \ tst-scanf-round \ tst-scanf-to_inpunct \ + tst-setvbuf2 \ + tst-setvbuf3 \ + tst-setvbuf4 \ + tst-setvbuf5 \ + tst-setvbuf6 \ tst-setvbuf1 \ tst-sprintf \ tst-sprintf-errno \ 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..5d2611ed0d --- /dev/null +++ b/stdio-common/tst-setvbuf4.c @@ -0,0 +1,167 @@ +/* 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. */ + +/* Note - using small buffers here does trigger a bug in glibc's + implementation of setvbuf, eg: + + #define BIG_BUF_SIZE 128 + #define SMALL_BUF_SIZE 12 + + The reason for this is explained in a post from Florian: + + https://inbox.sourceware.org/libc-alpha/87jzdqj0qu.fsf@oldenburg.str.redhat.com/ + + For now we use larger buffers so that we can test the function with a more + reasonable buffer size. */ + +#define BIG_BUF_SIZE 256 +#define SMALL_BUF_SIZE 128 + +_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