mirror of git://sourceware.org/git/glibc.git
1031 lines
22 KiB
C
1031 lines
22 KiB
C
/* 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
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
/* 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 <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdio_ext.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <libio.h>
|
|
#include <termios.h>
|
|
|
|
#include <support/support.h>
|
|
#include <support/check.h>
|
|
#include <support/temp_file.h>
|
|
#include <support/xstdio.h>
|
|
#include <support/xunistd.h>
|
|
#include <support/xthread.h>
|
|
#include <support/tty.h>
|
|
|
|
/* 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 *) xmalloc (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;
|
|
xpipe (test_pipe);
|
|
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);
|
|
xmkfifo (test_file_name, 0600);
|
|
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 = xfopen (fname, "r");
|
|
break;
|
|
|
|
case test_stream_stdout:
|
|
fp = xfopen (fname, "w");
|
|
break;
|
|
|
|
case test_stream_stderr:
|
|
fp = xfopen (fname, "w");
|
|
break;
|
|
|
|
case test_stream_fopen_r:
|
|
fp = xfopen (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 = xfopen (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 ();
|
|
}
|
|
TEST_VERIFY_EXIT (fp != NULL);
|
|
|
|
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");
|
|
}
|
|
|
|
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 <support/test-driver.c>
|
|
#endif
|
|
|