From patchwork Fri Sep 27 16:13:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nick Clifton X-Patchwork-Id: 98073 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 70FD33858C48 for ; Fri, 27 Sep 2024 16:14: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 B5D843858D34 for ; Fri, 27 Sep 2024 16:14:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B5D843858D34 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 B5D843858D34 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=1727453644; cv=none; b=GgczQpo5e0cFr1y5QpHqUiM8/DAespgzaU2/V41uAaS5faa4po0flZxnducZAOECGD4QWKanh/svtuV11zzqFoF9fymrJcuWjquyGyX8dl507+SnEy/MifAJmIgFr5s0bUrUlXcdc6mp+qv6/ewgirysWwZAsTzqLohwkGyaa2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1727453644; c=relaxed/simple; bh=/eIHLUsDYRzEdqFsgJJKSNt7nfPqAr/0m5/PBWbDl4o=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=iP3o9Pl8uN7jgVmqUA3RzjJpN8DEzCyw4O9ze481wgYlz0uK/YSkrIZwWU4Uondy0reUbcy1PD35Gb/NwZAhwnpH21yr9+a56TOuOTDlvrfTQyyznRuJA+F/mDWYBgXU3GTdZaSwszuGMNkTTaTze92kP90kBV+RPrGxK/tOVqA= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1727453640; 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=BvOmgloXpnidt8DwYUv55WHjKDdInQq+bqgRSM/ivYE=; b=CpdrdXcnEh86Od8lDmiF3LaXplmOfK0tk74woz08E3UQzUFg01SSZcgxsYQ4KJeyrSsVNH C/SJmxRxQrjqHGQYGlqPoISDvseZgj1PxgtimJ5nIwGwgjK6/BU4qgT2jwzCZebnr1Ir7B fag0dbUV2MgcqpNhvLz5CFg747W1Pag= 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-680-eMKFRvNrMbWPlzr8FmaMag-1; Fri, 27 Sep 2024 12:13:58 -0400 X-MC-Unique: eMKFRvNrMbWPlzr8FmaMag-1 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (unknown [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F285818F377C for ; Fri, 27 Sep 2024 16:13:56 +0000 (UTC) Received: from prancer.redhat.com (unknown [10.42.28.28]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E1B7B3003E7D for ; Fri, 27 Sep 2024 16:13:55 +0000 (UTC) From: Nick Clifton To: libc-alpha@sourceware.org Subject: [PATCH v2 1/1] stdio-common: Add more tests of the setvbuf() function. Date: Fri, 27 Sep 2024 17:13:53 +0100 Message-ID: <87zfntjcqm.fsf@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-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_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 diff --git a/stdio-common/Makefile b/stdio-common/Makefile index 89f263894c..ec6b1a078e 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -261,6 +261,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 \ @@ -283,6 +288,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) @@ -323,6 +334,8 @@ tests-special += \ $(objpfx)tst-printf.out \ $(objpfx)tst-printfsz-islongdouble.out \ $(objpfx)tst-setvbuf1-cmp.out \ + $(objpfx)tst-setvbuf2-diff.out \ + $(objpfx)tst-setvbuf3-diff.out \ $(objpfx)tst-unbputc.out \ # tests-special @@ -620,5 +633,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-diff.out: tst-setvbuf2.expect $(objpfx)tst-setvbuf2.out + diff $^ > $@; \ + $(evaluate-test) + +$(objpfx)tst-setvbuf3.out: /dev/null $(objpfx)tst-setvbuf3 + $(test-program-cmd) > $@ 2>&1; \ + $(evaluate-test) + +$(objpfx)tst-setvbuf3-diff.out: tst-setvbuf3.expect $(objpfx)tst-setvbuf3.out + diff $^ > $@; \ + $(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..0d14d21388 --- /dev/null +++ b/stdio-common/tst-setvbuf2.c @@ -0,0 +1,98 @@ +/* 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 (stderr, NULL, _IONBF, 0) == 0); + TEST_VERIFY_EXIT (setvbuf (stdout, NULL, _IONBF, 0) == 0); + + /* This test relies upon stdout and stderr being attached to a terminal. + FIXME: Actually we need to be sure that they are attached to the *same* + terminal, but there is no easy way to detect this. + Tested here, rather than before calling setvbuf, since it is still valid + to call setvbuf even if the streams are not attached to a terminal. It + just means that the buffering might not work as expected. */ + if (! isatty (1) || isatty (2)) + { + printf ("info: tst-setvbuf2.c: this test only works when attached to a terminal\n"); + return 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..8e4317f954 --- /dev/null +++ b/stdio-common/tst-setvbuf3.c @@ -0,0 +1,124 @@ +/* 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) +{ + /* 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. */ + + static char local_buf [LOCAL_BUF_SIZE]; + + /* Use a library allocated line buffer for stderr. */ + TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IOLBF, LOCAL_BUF_SIZE) == 0); + + /* Use a program allocated line buffer for stdout. */ + TEST_VERIFY_EXIT (setvbuf (stdout, local_buf, _IOLBF, sizeof local_buf) == 0); + + /* This test relies upon stdout and stderr being attached to a terminal. + FIXME: Actually we need to be sure that they are attached to the *same* + terminal, but there is no easy way to detect this. + Tested here, rather than before calling setvbuf, since it is still valid + to call setvbuf even if the streams are not attached to a terminal. It + just means that the buffering might not work as expected. */ + if (! isatty (1) || isatty (2)) + { + printf ("info: tst-setvbuf3.c: this test only works when attached to a terminal\n"); + return 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