From patchwork Mon Mar 24 20:35:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 109123 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 06FE83858D3C for ; Mon, 24 Mar 2025 20:39:22 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 06FE83858D3C Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QrKLsdnF 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 8F9323858D33 for ; Mon, 24 Mar 2025 20:35:59 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8F9323858D33 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine 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 8F9323858D33 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=1742848559; cv=none; b=ftxtOQoEWdTMlnCKdAOsIy1tnpTDMfcFgBQzrs3fAj5VhvWkqpVrX5gaxFqVGSzIvRlekzu6PCpKG4uvwTiO3zw1CCnn0PsMr5T2r8Fm55CbxMADfjUF28uycW+5IHz9dpq7JnDDkT4DeqT28LeMotF46AWjSbAwjIBKjqEPCQs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1742848559; c=relaxed/simple; bh=d+Q6jx/NLrvvXNkNMZr7BcEk+sWKGro9osal3WE4040=; h=DKIM-Signature:Date:Message-Id:From:To:Subject; b=msCRgjiIxNkhsZ/GFZvh0kSTjrxihy0310a6v8jGvPO167RIjzg3xTgs9ok7WpT3ELIuac0Cnwpy1gzXkcYtXvADLaBXE+NF3EntaPO/YToAkvu9NqrHO3iBzU/OWsj+GnxF/H03yIyWLA9jFMaO/MLOw7yX16kAWOPqk+KytN4= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8F9323858D33 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1742848559; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:content-type:content-type; bh=Y1z5J67iZWEOlbz2Zc0gAAa4zwx/3ap/lZQlNklV238=; b=QrKLsdnFmBGoQLEX8RQx573PfGCJQCrezmXnaK/IAJEEQ2VX1cBXkD1BL+RPXianLljN0L wb2diaYCv56q6yGtXtKpqyT+8bJcdtV/kv/F7mregl1mrk9hZehwZvBV+0NZMkF7Hj0EAK ApbxbEJF8oNA6xUMo/INuFe0FWx3ylE= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-277-fPmoCDt4ML-NjSbvAK4Y-w-1; Mon, 24 Mar 2025 16:35:57 -0400 X-MC-Unique: fPmoCDt4ML-NjSbvAK4Y-w-1 X-Mimecast-MFC-AGG-ID: fPmoCDt4ML-NjSbvAK4Y-w_1742848557 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-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E86BF180AF50 for ; Mon, 24 Mar 2025 20:35:56 +0000 (UTC) Received: from greed.delorie.com (unknown [10.22.89.17]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 94D9919560AB for ; Mon, 24 Mar 2025 20:35:56 +0000 (UTC) Received: from greed.delorie.com.redhat.com (localhost [127.0.0.1]) by greed.delorie.com (8.16.1/8.16.1) with ESMTP id 52OKZnGj966463 for ; Mon, 24 Mar 2025 16:35:49 -0400 Date: Mon, 24 Mar 2025 16:35:49 -0400 Message-Id: From: DJ Delorie To: libc-alpha@sourceware.org Subject: [patch v1] stdio: Add more setvbuf tests X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: PeKGjkkFqA3TgJC9cnGL7760jVItTwz4p2ibusKe5U4_1742848557 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_ASCII_DIVIDERS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, 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 -- >8 -- Note that this test needs a second executable which it can exec in order to test its stdin etc, which runs the same tests as the main executable. I chose to implement this with one main file and some #ifdefs, as both the main test and the separate test share a lot of code, but because gcc is paranoid about unused variables and functions, separating the test into a common header would be problematic. I also think the test is easier to understand if it's all in one file. The tests are run in a container because it's the easiest way to run the separate test executable in the correct environment. This patch relies on the ptmx support patch I posted earlier; it will run without it but post errors for those parts of the test. diff --git a/stdio-common/Makefile b/stdio-common/Makefile index d3733d0c3d..2dcd7a7020 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -351,7 +351,9 @@ endif endif tests-container += \ - tst-popen3 + tst-popen3 \ + tst-setvbuf2 \ + tst-setvbuf2-ind # tests-container generated += \ @@ -363,6 +365,8 @@ generated += \ tests-internal = \ tst-grouping_iterator \ + tst-setvbuf2 \ + tst-setvbuf2-ind \ # tests-internal test-srcs = \ @@ -710,6 +714,10 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out cmp $^ > $@; \ $(evaluate-test) +CFLAGS-tst-setvbuf2.c += -DIND_PROC=\"$(objpfx)tst-setvbuf2-ind\" +$(objpfx)tst-setvbuf2-ind : $(objpfx)tst-setvbuf2-ind.o +$(objpfx)tst-setvbuf2.out: $(objpfx)tst-setvbuf2-ind + $(objpfx)tst-printf-round: $(libm) $(objpfx)tst-scanf-round: $(libm) diff --git a/stdio-common/tst-setvbuf2-ind.c b/stdio-common/tst-setvbuf2-ind.c new file mode 100644 index 0000000000..fda2942c24 --- /dev/null +++ b/stdio-common/tst-setvbuf2-ind.c @@ -0,0 +1,2 @@ +#define INDEPENDENT_PART 1 +#include "tst-setvbuf2.c" diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c new file mode 100644 index 0000000000..78791d1a22 --- /dev/null +++ b/stdio-common/tst-setvbuf2.c @@ -0,0 +1,1036 @@ +/* Test setvbuf under various conditions. + Copyright (C) 2025 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 + . */ + +/* This file is used twice, once as the test itself (where do_test + is defined) and once as a subprocess we spawn to test stdin et all + (where main is defined). INDEPENDENT_PART is defined for the + latter. + + Note also that the purpose of this test is to test setvbuf, not the + underlying buffering code. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Dear future developer: If you are reading this, you are likely + trying to change or understand this test. In that case, these + debug/dump macros will be helpful. */ +#if 0 +#define debug printf ("\033[3%dm%s:%d\033[0m\n", \ + (__LINE__%6)+1,__FUNCTION__, __LINE__); + +static void +dumpfp (FILE *fp) +{ + char f[10], *p=f; + + if (fp->_flags & _IO_UNBUFFERED) + *p++ = 'N'; + if (fp->_flags & _IO_LINE_BUF) + *p++ = 'L'; + if (p == f) + *p++ = 'B'; + *p = 0; + + printf ("FILE %p flags %s" + " read %p \033[%dm%+ld \033[%dm%+ld\033[0m" + " write %p \033[%dm%+ld \033[%dm%+ld\033[0m %ld" + " buf %p \033[%dm%+ld\033[0m sz %ld pend %ld\n", + fp, f, + + fp->_IO_read_base, + fp->_IO_read_ptr == fp->_IO_read_base ? 33 : 32, + fp->_IO_read_ptr - fp->_IO_read_base, + fp->_IO_read_end == fp->_IO_read_base ? 33 : 36, + fp->_IO_read_end - fp->_IO_read_base, + + fp->_IO_write_base, + fp->_IO_write_ptr == fp->_IO_write_base ? 33 : 32, + fp->_IO_write_ptr - fp->_IO_write_base, + fp->_IO_write_end == fp->_IO_write_base ? 33 : 36, + fp->_IO_write_end - fp->_IO_write_base, + fp->_IO_write_end - fp->_IO_write_base, + + fp->_IO_buf_base, + fp->_IO_buf_end == fp->_IO_buf_base ? 33 : 35, + fp->_IO_buf_end - fp->_IO_buf_base, + __fbufsize (fp), __fpending (fp) + ); +} +#else +#define debug +#define dumpfp(FP) +#endif + +#ifndef INDEPENDENT_PART +/* st_blksize value for that file, or BUFSIZ if out of range. */ +static int blksize = BUFSIZ; +#endif + +/* Our test buffer. */ +#define TEST_BUFSIZE 42 +static int bufsize = TEST_BUFSIZE < BUFSIZ ? TEST_BUFSIZE : BUFSIZ; +static char *buffer; + +/* Test data, both written to that file and used as an in-memory + stream. */ +char test_data[2 * BUFSIZ]; + +#define TEST_STRING "abcdef\n" + +enum test_source_case + { + test_source_file, + test_source_pipe, + test_source_fifo, + test_source_pseudo_terminal, + test_source_dev_null, + test_source_count, + }; + +static const char *const test_source_name[test_source_count] = + { + "regular file", + "pipe", + "fifo", + "pseudo_terminal", + "dev_null" + }; + +enum test_stream_case + { + test_stream_stdin, + test_stream_stdout, + test_stream_stderr, + test_stream_fopen_r, + test_stream_fdopen_r, + test_stream_fopen_w, + test_stream_fdopen_w, + test_stream_count + }; + +static bool test_stream_reads[test_stream_count] = + { + true, + false, + false, + true, + true, + false, + false + }; + +static const char *const test_stream_name[test_stream_count] = + { + "stdin", + "stdout", + "stderr", + "fopen (read)", + "fdopen (read)", + "fopen (write)", + "fdopen (write)" + }; + +enum test_config_case + { + test_config_none, + test_config_unbuffered, + test_config_line, + test_config_fully, + test_config_count + }; + +static const char *const test_config_name[test_config_count] = + { + "no change", + "unbuffered", + "line buffered", + "fully buffered" + }; + +FILE *test_stream; + +char *test_file_name = NULL; +int pty_fd; +char *test_pipe_name = NULL; +int test_pipe[2]; + +/* This is either -1 or represents a pre-opened file descriptor for + the test as returned by prepare_test_file. */ +int test_fd; + +/*------------------------------------------------------------*/ + +/* Note that throughout this test we reopen, remove, and change + to/from a fifo, the test file. This would normally cause a race + condition, except that we're in a test container. No other process + can run in the test container simultaneously. */ + +void +prepare_test_data (void) +{ + buffer = (char *) malloc (bufsize); + +#ifndef INDEPENDENT_PART + /* Both file and pipe need this. */ + if (test_file_name == NULL) + { + debug; + int fd = create_temp_file ("tst-setvbuf2", &test_file_name); + TEST_VERIFY_EXIT (fd != -1); + struct stat64 st; + xfstat64 (fd, &st); + if (st.st_blksize > 0 && st.st_blksize < BUFSIZ) + blksize = st.st_blksize; + xclose (fd); + } +#endif + + for (size_t i = 0; i < 2 * BUFSIZ; i++) + { + unsigned char c = TEST_STRING[i % strlen (TEST_STRING)]; + test_data[i] = c; + } +} + +#ifndef INDEPENDENT_PART + +/* These functions provide a source/sink for the "other" side of any + pipe-style descriptor we're using for test. */ + +static pthread_t writer_thread_tid = 0; +static pthread_t reader_thread_tid = 0; + +typedef struct { + int fd; + const char *fname; +} ThreadData; +/* It's OK if this is static, we only run one at a time. */ +ThreadData thread_data; + +static void * +writer_thread_proc (void *closure) +{ + ThreadData *td = (ThreadData *) closure; + int fd; + int i; + ssize_t wn; + debug; + + if (td->fname) + td->fd = xopen (td->fname, O_WRONLY, 0777); + fd = td->fd; + + while (1) + { + i = 0; + while (i < BUFSIZ) + { + wn = write (fd, test_data+i, BUFSIZ-i); + if (wn <= 0) + break; + i += wn; + } + } + return NULL; +} + +static void * +reader_thread_proc (void *closure) +{ + ThreadData *td = (ThreadData *) closure; + int fd; + ssize_t rn; + int n = 0; + debug; + + if (td->fname) + td->fd = xopen (td->fname, O_RDONLY, 0777); + fd = td->fd; + + while (1) + { + char buf[BUFSIZ]; + rn = read (fd, buf, BUFSIZ); + if (rn <= 0) + break; + TEST_COMPARE_BLOB (buf, rn, test_data+n, rn); + n += rn; + } + return NULL; +} + +static void +start_writer_thread (int fd) +{ + debug; + thread_data.fd = fd; + thread_data.fname = NULL; + writer_thread_tid = xpthread_create (NULL, writer_thread_proc, + (void *)& thread_data); +} + +static void +start_writer_thread_n (const char *fname) +{ + debug; + thread_data.fd = 0; + thread_data.fname = fname; + writer_thread_tid = xpthread_create (NULL, writer_thread_proc, + (void *)& thread_data); +} + +static void +end_writer_thread (void) +{ + debug; + if (writer_thread_tid) + { + pthread_cancel (writer_thread_tid); + xpthread_join (writer_thread_tid); + xclose (thread_data.fd); + writer_thread_tid = 0; + } +} + +static void +start_reader_thread (int fd) +{ + debug; + thread_data.fd = fd; + thread_data.fname = NULL; + reader_thread_tid = xpthread_create (NULL, reader_thread_proc, + (void *)& thread_data); +} + +static void +start_reader_thread_n (const char *fname) +{ + debug; + thread_data.fd = 0; + thread_data.fname = fname; + reader_thread_tid = xpthread_create (NULL, reader_thread_proc, + (void *)& thread_data); +} + +static void +end_reader_thread (void) +{ + debug; + if (reader_thread_tid) + { + pthread_cancel (reader_thread_tid); + xpthread_join (reader_thread_tid); + xclose (thread_data.fd); + reader_thread_tid = 0; + } +} + +/*------------------------------------------------------------*/ + +/* These two functions are reponsible for choosing a file to be tested + against, typically by returning a filename but in a few cases also + providing a file descriptor (i.e. for fdopen). */ + +static const char * +prepare_test_file (enum test_source_case f, enum test_stream_case s) +{ + debug; + + test_fd = -1; + + switch (f) + { + case test_source_file: + { + if (test_stream_reads[f]) + { + debug; + FILE *fp = xfopen (test_file_name, "w"); + TEST_VERIFY_EXIT (fwrite (test_data, 1, 2 * BUFSIZ, fp) + == 2 * BUFSIZ); + xfclose (fp); + } + debug; + return test_file_name; + } + + case test_source_pipe: + { + debug; + TEST_COMPARE (pipe (test_pipe), 0); + if (test_stream_reads[s]) + { + start_writer_thread (test_pipe[1]); + test_fd = test_pipe[0]; + } + else + { + start_reader_thread (test_pipe[0]); + test_fd = test_pipe[1]; + } + test_pipe_name = xasprintf ("/proc/self/fd/%d", test_fd); + debug; + return test_pipe_name; + } + + case test_source_fifo: + { + /* We do not want to fail/exit if the file doesn't exist. */ + unlink (test_file_name); + if (mknod (test_file_name, S_IFIFO | 0600, 0) != 0) + FAIL_EXIT1 ("mknod: %m"); + debug; + if (test_stream_reads[s]) + start_writer_thread_n (test_file_name); + else + start_reader_thread_n (test_file_name); + debug; + return test_file_name; + } + + case test_source_pseudo_terminal: + { + support_openpty (&pty_fd, &test_fd, &test_pipe_name, NULL, NULL); + + debug; + if (test_stream_reads[s]) + start_writer_thread (pty_fd); + else + start_reader_thread (pty_fd); + + debug; + return test_pipe_name; + } + + case test_source_dev_null: + debug; + return "/dev/null"; + + default: + abort (); + } +} + +static void +unprepare_test_file (FILE *fp, + enum test_source_case f, + enum test_stream_case s) +{ + debug; + switch (f) + { + case test_source_file: + break; + + case test_source_pipe: + free (test_pipe_name); + if (test_stream_reads[s]) + end_writer_thread (); + else + end_reader_thread (); + break; + + case test_source_fifo: + if (test_stream_reads[s]) + end_writer_thread (); + else + end_reader_thread (); + unlink (test_file_name); + break; + + case test_source_pseudo_terminal: + free (test_pipe_name); + if (test_stream_reads[s]) + end_writer_thread (); + else + end_reader_thread (); + break; + + case test_source_dev_null: + break; + + default: + abort (); + } + debug; +} + +/*------------------------------------------------------------*/ + +/* This function takes a filename and returns a file descriptor, + opened according to the method requested. */ + +static FILE * +open_test_stream (enum test_source_case f, enum test_stream_case s) +{ + int fd; + FILE *fp; + const char *fname; + + debug; + fname = prepare_test_file (f, s); + if (fname == NULL) + return NULL; + + switch (s) + { + case test_stream_stdin: + fp = fopen (fname, "r"); + break; + + case test_stream_stdout: + fp = fopen (fname, "w"); + break; + + case test_stream_stderr: + fp = fopen (fname, "w"); + break; + + case test_stream_fopen_r: + fp = fopen (fname, "r"); + break; + + case test_stream_fdopen_r: + if (test_fd == -1) + fd = xopen (fname, O_RDONLY, 0); + else + fd = test_fd; + fp = fdopen (fd, "r"); + break; + + case test_stream_fopen_w: + fp = fopen (fname, "w"); + break; + + case test_stream_fdopen_w: + fd = xopen (fname, O_WRONLY|O_CREAT|O_TRUNC, 0777); + fp = fdopen (fd, "w"); + break; + + default: + abort (); + } + + if (f == test_source_pseudo_terminal) + { + struct termios t; + /* We disable the NL to CR-LF conversion so that we can compare + data without having to remove the extra CRs. */ + if (tcgetattr (fileno (fp), &t) < 0) + FAIL_EXIT1 ("tcgetattr failed: %m"); + t.c_oflag &= ~ONLCR; + if (tcsetattr (fileno (fp), TCSANOW, &t) < 0) + FAIL_EXIT1 ("tcsetattr failed: %m"); + } + + TEST_VERIFY_EXIT (fp != NULL); + debug; + printf ("source %s stream %s file %s fd %d\n", + test_source_name[f], + test_stream_name[s], fname, fileno (fp)); + return fp; +} + +#endif + +/*------------------------------------------------------------*/ + +/* These functions do the actual testing - setting various buffering + options and verifying that they buffer as expected. */ + +static void +test_put_string (FILE *fp, const char *s, int count) +{ + while (*s && count--) + { + fputc (*s++, fp); + TEST_VERIFY_EXIT (!ferror (fp)); + } +} + +int +verify_fully_buffered (FILE *fp, + enum test_source_case f, + enum test_stream_case s, + enum test_config_case c) +{ + debug; + if (test_stream_reads[s]) + { + char buf[10]; + dumpfp (fp); + size_t fc = fread (buf, 1, 10-1, fp); + dumpfp (fp); + + ssize_t count = fp->_IO_read_ptr - fp->_IO_read_base; + + TEST_VERIFY (fp->_IO_read_base != NULL); + if (f == test_source_dev_null) + { + TEST_VERIFY (fc == 0); + TEST_VERIFY (count == 0); + } + else if (f == test_source_pseudo_terminal) + { + TEST_VERIFY (fc == 9); + TEST_VERIFY (count == 3 || count == 10); + } + else + { + TEST_VERIFY (fc == 9); + TEST_VERIFY (count == 10); + } + + /* We already checked for the first character being 'a'. */ + if (count > 1) + { + TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1); + TEST_COMPARE_BLOB (fp->_IO_read_base, count, test_data, count); + } + } + else + { + dumpfp (fp); + test_put_string (fp, test_data+1, 10-1); + dumpfp (fp); + TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 10); + TEST_COMPARE_BLOB (fp->_IO_write_base, 10, test_data, 10); + } + + TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)), 0); + if (c != test_config_none) + TEST_COMPARE (__fbufsize (fp), bufsize); + return 0; +} + +int +verify_line_buffered (FILE *fp, + enum test_source_case f, + enum test_stream_case s, + enum test_config_case c) +{ + debug; + /* "line buffered" for inputs is not really defined; what you really + want here is to control the device providing input. For GLIBC a + line-buffered input is treated as fully buffered. */ + if (test_stream_reads[s]) + { + char buf[10]; + dumpfp (fp); + size_t fc = fread (buf, 1, 10-1, fp); + dumpfp (fp); + + ssize_t count = fp->_IO_read_ptr - fp->_IO_read_base; + + TEST_VERIFY (fp->_IO_read_base != NULL); + if (f == test_source_dev_null) + { + TEST_VERIFY (fc == 0); + TEST_VERIFY (count == 0); + } + else if (f == test_source_pseudo_terminal) + { + TEST_VERIFY (fc == 9); + TEST_VERIFY (count == 3 || count == 10); + } + else + { + TEST_VERIFY (fc == 9); + TEST_VERIFY (count == 10); + } + + /* We already checked for the first character being 'a'. */ + if (count > 1) + { + TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1); + TEST_COMPARE_BLOB (fp->_IO_read_base, count, test_data, count); + } + } + else + { + dumpfp (fp); + test_put_string (fp, test_data+1, 10-1); + dumpfp (fp); + TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 3); + /* The first "abcdef\n" got flushed, leaving "abc". */ + TEST_COMPARE_BLOB (fp->_IO_write_base, 3, test_data + 7, 3); + } + + TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)), _IO_LINE_BUF); + if (c != test_config_none) + TEST_COMPARE (__fbufsize (fp), bufsize); + return 0; +} + +int +verify_unbuffered (FILE *fp, + enum test_source_case f, + enum test_stream_case s, + enum test_config_case c) +{ + debug; + if (test_stream_reads[s]) + { + /* We've already read one byte. */ + dumpfp (fp); + TEST_VERIFY (fp->_IO_read_base != NULL); + if (f == test_source_dev_null) + TEST_COMPARE (fp->_IO_read_ptr - fp->_IO_read_base, 0); + else + { + TEST_COMPARE (fp->_IO_read_ptr - fp->_IO_read_base, 1); + TEST_COMPARE (fp->_IO_read_base[0], test_data[0]); + TEST_VERIFY (fp->_IO_read_ptr == fp->_IO_read_end); + } + } + else + { + dumpfp (fp); + fputc (test_data[1], fp); + dumpfp (fp); + TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 0); + TEST_COMPARE (fp->_IO_write_base[0], test_data[1]); + TEST_VERIFY (fp->_IO_write_end == fp->_IO_write_base); + } + TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)), + _IO_UNBUFFERED); + TEST_COMPARE (__fbufsize (fp), 1); + return 0; +} + +static int +do_setvbuf (FILE *fp, void *buf, int flags, int size, + enum test_stream_case s) +{ + if (s != test_stream_stdout) + printf ("SETVBUF %p %p %s %d\n", + fp, buf, + flags == _IONBF ? "_IONBF" + : flags == _IOLBF ? "_IOLBF" + : flags == _IOFBF ? "_IOFBF" + : "???", size); + if (setvbuf (fp, buf, flags, size)) + { + perror ("setvbuf"); + return 1; + } + return 0; +} + +int +do_second_part (FILE *fp, + enum test_source_case f, + enum test_stream_case s, + enum test_config_case c) +{ + /* At this point, FP is the stream to test according to the other + parameters. */ + + int rv = 0; + int flags_before; + int flags_after; + + debug; + + flags_before = fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF); + + /* This is where we do the thing we're testing for. */ + switch (c) + { + case test_config_none: + /* Buffering is unchanged. */ + break; + + case test_config_unbuffered: + do_setvbuf (fp, NULL, _IONBF, 0, s); + break; + + case test_config_line: + do_setvbuf (fp, buffer, _IOLBF, bufsize, s); + break; + + case test_config_fully: + do_setvbuf (fp, buffer, _IOFBF, bufsize, s); + break; + + default: + abort (); + } + + flags_after = fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF); + + /* Check the buffer mode after we touch it, if we touched it. */ + switch (c) + { + case test_config_none: + /* Buffering is unchanged, but may change on the first read/write. */ + TEST_COMPARE (flags_after, flags_before); + break; + + case test_config_unbuffered: + TEST_COMPARE (flags_after, _IO_UNBUFFERED); + break; + + case test_config_line: + TEST_COMPARE (flags_after, _IO_LINE_BUF); + break; + + case test_config_fully: + TEST_COMPARE (flags_after, 0); + break; + + default: + abort (); + } + + /* Glibc defers calculating the appropriate buffering mechanism + until it reads from or writes to the device. So we read one + character here, and account for that in the tests. */ + if (test_stream_reads[s]) + { + dumpfp (fp); + int c = fgetc (fp); + if (c != TEST_STRING[0] && f != test_source_dev_null) + FAIL ("first char read is %c not %c", c, TEST_STRING[0]); + dumpfp (fp); + } + else + { + dumpfp (fp); + fputc (TEST_STRING[0], fp); + dumpfp (fp); + } + + switch (fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)) + { + case _IO_LINE_BUF: + rv += verify_line_buffered (fp, f, s, c); + break; + + case _IO_UNBUFFERED: + rv += verify_unbuffered (fp, f, s, c); + break; + + case 0: /* Fully buffered. */ + rv += verify_fully_buffered (fp, f, s, c); + break; + + default: + abort (); + } + + + fclose (fp); + return rv; +} + +/*------------------------------------------------------------*/ + +#ifdef INDEPENDENT_PART +/* This part is the independent sub-process we call to test stdin et + al. */ + +int +main (int argc, char **argv) +{ + /* This is one of the subprocesses we created to test stdin et + al. */ + FILE *fp; + + /* If we're called as a regular test, instead of as a sub-process, + don't complain. */ + if (argc == 1) + return 0; + + if (argc != 4) + { + int i; + for (i=0; i<=argc; i++) + printf ("argv[%d] = `%s'\n", i, argv[i] ?: "(null)"); + FAIL_EXIT1 ("sub-process called wrong"); + } + + prepare_test_data (); + + enum test_source_case f = atoi (argv[1]); + enum test_stream_case s = atoi (argv[2]); + enum test_config_case c = atoi (argv[3]); + + if (s != test_stream_stdout) + printf ("\n\033[41mRunning test %s : %s : %s\033[0m\n", + test_source_name[f], + test_stream_name[s], + test_config_name[c]); + + switch (s) + { + case test_stream_stdin: + fp = stdin; + break; + case test_stream_stdout: + fp = stdout; + break; + case test_stream_stderr: + fp = stderr; + break; + default: + abort (); + } + + return do_second_part (fp, f, s, c); +} + +#else +/* This part is the standard test process. */ + +/* Spawn an independent sub-process with std* redirected. */ +int +recurse (FILE *fp, + enum test_source_case f, + enum test_stream_case s, + enum test_config_case c) +{ + /* We need to test stdin, stdout, or stderr, which means creating a + subprocess with one of those redirected from FP. */ + debug; + + pid_t pid; + int status; + + pid = fork (); + + switch (pid) + { + case -1: /* error */ + perror ("fork"); + return 1; + break; + + default: /* parent */ + fclose (fp); + xwaitpid (pid, &status, 0); + if (WIFEXITED (status) + && WEXITSTATUS (status) == 0) + return 0; + return 1; + + case 0: /* child */ + switch (s) + { + case test_stream_stdin: + xclose (0); + dup2 (fileno (fp), 0); + break; + case test_stream_stdout: + xclose (1); + dup2 (fileno (fp), 1); + break; + case test_stream_stderr: + xclose (2); + dup2 (fileno (fp), 2); + break; + default: + abort (); + } + fclose (fp); + + /* At this point, we have to run a program... which is tricky to + properly support for remote targets or crosses, because of + glibc versions etc. Hence we run in a test-container. */ + + char fs[10], ss[10], cs[10]; + sprintf (fs, "%d", f); + sprintf (ss, "%d", s); + sprintf (cs, "%d", c); + execl (IND_PROC, IND_PROC, fs, ss, cs, NULL); + if (s == test_stream_stdout) + fprintf (stderr, "execl (%s) failed, ", IND_PROC); + else + printf ("execl (%s) failed, ", IND_PROC); + perror ("The error was"); + exit (1); + break; + } + + return 0; +} + +int +do_test (void) +{ + int rv = 0; + + signal (SIGPIPE, SIG_IGN); + + prepare_test_data (); + + for (enum test_source_case f = 0; f < test_source_count; ++ f) + for (enum test_stream_case s = 0; s < test_stream_count; ++ s) + for (enum test_config_case c = 0; c < test_config_count; ++ c) + { + printf ("\n\033[43mRunning test %s : %s : %s\033[0m\n", + test_source_name[f], + test_stream_name[s], + test_config_name[c]); + + FILE *fp = open_test_stream (f, s); + + if (fp) + { + + if (s <= test_stream_stderr) + { + rv += recurse (fp, f, s, c); + } + else + { + rv += do_second_part (fp, f, s, c); + } + + unprepare_test_file (fp, f, s); + } + } + + free (buffer); + + printf ("return %d\n", rv); + return rv; +} + +#include +#endif +