xref: /freebsd/lib/libc/tests/stdio/flushlbuf_test.c (revision b8dbfb0a6c181a9aeab0b793deb0813d06052df9)
1*b8dbfb0aSDag-Erling Smørgrav /*-
2*b8dbfb0aSDag-Erling Smørgrav  * Copyright (c) 2023 Klara, Inc.
3*b8dbfb0aSDag-Erling Smørgrav  *
4*b8dbfb0aSDag-Erling Smørgrav  * SPDX-License-Identifier: BSD-2-Clause
5*b8dbfb0aSDag-Erling Smørgrav  */
6*b8dbfb0aSDag-Erling Smørgrav 
7*b8dbfb0aSDag-Erling Smørgrav #include <errno.h>
8*b8dbfb0aSDag-Erling Smørgrav #include <stdio.h>
9*b8dbfb0aSDag-Erling Smørgrav 
10*b8dbfb0aSDag-Erling Smørgrav #include <atf-c.h>
11*b8dbfb0aSDag-Erling Smørgrav 
12*b8dbfb0aSDag-Erling Smørgrav #define BUFSIZE 16
13*b8dbfb0aSDag-Erling Smørgrav 
14*b8dbfb0aSDag-Erling Smørgrav static const char seq[] =
15*b8dbfb0aSDag-Erling Smørgrav     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
16*b8dbfb0aSDag-Erling Smørgrav     "abcdefghijklmnopqrstuvwxyz"
17*b8dbfb0aSDag-Erling Smørgrav     "0123456789+/";
18*b8dbfb0aSDag-Erling Smørgrav 
19*b8dbfb0aSDag-Erling Smørgrav struct stream {
20*b8dbfb0aSDag-Erling Smørgrav 	char buf[BUFSIZE];
21*b8dbfb0aSDag-Erling Smørgrav 	unsigned int len;
22*b8dbfb0aSDag-Erling Smørgrav 	unsigned int pos;
23*b8dbfb0aSDag-Erling Smørgrav };
24*b8dbfb0aSDag-Erling Smørgrav 
writefn(void * cookie,const char * buf,int len)25*b8dbfb0aSDag-Erling Smørgrav static int writefn(void *cookie, const char *buf, int len)
26*b8dbfb0aSDag-Erling Smørgrav {
27*b8dbfb0aSDag-Erling Smørgrav 	struct stream *s = cookie;
28*b8dbfb0aSDag-Erling Smørgrav 	int written = 0;
29*b8dbfb0aSDag-Erling Smørgrav 
30*b8dbfb0aSDag-Erling Smørgrav 	if (len <= 0)
31*b8dbfb0aSDag-Erling Smørgrav 		return 0;
32*b8dbfb0aSDag-Erling Smørgrav 	while (len > 0 && s->pos < s->len) {
33*b8dbfb0aSDag-Erling Smørgrav 		s->buf[s->pos++] = *buf++;
34*b8dbfb0aSDag-Erling Smørgrav 		written++;
35*b8dbfb0aSDag-Erling Smørgrav 		len--;
36*b8dbfb0aSDag-Erling Smørgrav 	}
37*b8dbfb0aSDag-Erling Smørgrav 	if (written > 0)
38*b8dbfb0aSDag-Erling Smørgrav 		return written;
39*b8dbfb0aSDag-Erling Smørgrav 	errno = EAGAIN;
40*b8dbfb0aSDag-Erling Smørgrav 	return -1;
41*b8dbfb0aSDag-Erling Smørgrav }
42*b8dbfb0aSDag-Erling Smørgrav 
43*b8dbfb0aSDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(flushlbuf_partial);
ATF_TC_BODY(flushlbuf_partial,tc)44*b8dbfb0aSDag-Erling Smørgrav ATF_TC_BODY(flushlbuf_partial, tc)
45*b8dbfb0aSDag-Erling Smørgrav {
46*b8dbfb0aSDag-Erling Smørgrav 	static struct stream s;
47*b8dbfb0aSDag-Erling Smørgrav 	static char buf[BUFSIZE + 1];
48*b8dbfb0aSDag-Erling Smørgrav 	FILE *f;
49*b8dbfb0aSDag-Erling Smørgrav 	unsigned int i = 0;
50*b8dbfb0aSDag-Erling Smørgrav 	int ret = 0;
51*b8dbfb0aSDag-Erling Smørgrav 
52*b8dbfb0aSDag-Erling Smørgrav 	/*
53*b8dbfb0aSDag-Erling Smørgrav 	 * Create the stream and its buffer, print just enough characters
54*b8dbfb0aSDag-Erling Smørgrav 	 * to the stream to fill the buffer without triggering a flush,
55*b8dbfb0aSDag-Erling Smørgrav 	 * then check the state.
56*b8dbfb0aSDag-Erling Smørgrav 	 */
57*b8dbfb0aSDag-Erling Smørgrav 	s.len = BUFSIZE / 2; // write will fail after this amount
58*b8dbfb0aSDag-Erling Smørgrav 	ATF_REQUIRE((f = fwopen(&s, writefn)) != NULL);
59*b8dbfb0aSDag-Erling Smørgrav 	ATF_REQUIRE(setvbuf(f, buf, _IOLBF, BUFSIZE) == 0);
60*b8dbfb0aSDag-Erling Smørgrav 	while (i < BUFSIZE)
61*b8dbfb0aSDag-Erling Smørgrav 		if ((ret = fprintf(f, "%c", seq[i++])) < 0)
62*b8dbfb0aSDag-Erling Smørgrav 			break;
63*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(BUFSIZE, i);
64*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(seq[i - 1], buf[BUFSIZE - 1]);
65*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(1, ret);
66*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(0, s.pos);
67*b8dbfb0aSDag-Erling Smørgrav 
68*b8dbfb0aSDag-Erling Smørgrav 	/*
69*b8dbfb0aSDag-Erling Smørgrav 	 * At this point, the buffer is full but writefn() has not yet
70*b8dbfb0aSDag-Erling Smørgrav 	 * been called.  The next fprintf() call will trigger a preemptive
71*b8dbfb0aSDag-Erling Smørgrav 	 * fflush(), and writefn() will consume s.len characters before
72*b8dbfb0aSDag-Erling Smørgrav 	 * returning EAGAIN, causing fprintf() to fail without having
73*b8dbfb0aSDag-Erling Smørgrav 	 * written anything (which is why we don't increment i here).
74*b8dbfb0aSDag-Erling Smørgrav 	 */
75*b8dbfb0aSDag-Erling Smørgrav 	ret = fprintf(f, "%c", seq[i]);
76*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_ERRNO(EAGAIN, ret < 0);
77*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(s.len, s.pos);
78*b8dbfb0aSDag-Erling Smørgrav 
79*b8dbfb0aSDag-Erling Smørgrav 	/*
80*b8dbfb0aSDag-Erling Smørgrav 	 * We have consumed s.len characters from the buffer, so continue
81*b8dbfb0aSDag-Erling Smørgrav 	 * printing until it is full again and check that no overflow has
82*b8dbfb0aSDag-Erling Smørgrav 	 * occurred yet.
83*b8dbfb0aSDag-Erling Smørgrav 	 */
84*b8dbfb0aSDag-Erling Smørgrav 	while (i < BUFSIZE + s.len)
85*b8dbfb0aSDag-Erling Smørgrav 		fprintf(f, "%c", seq[i++]);
86*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(BUFSIZE + s.len, i);
87*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(seq[i - 1], buf[BUFSIZE - 1]);
88*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(0, buf[BUFSIZE]);
89*b8dbfb0aSDag-Erling Smørgrav 
90*b8dbfb0aSDag-Erling Smørgrav 	/*
91*b8dbfb0aSDag-Erling Smørgrav 	 * The straw that breaks the camel's back: libc fails to recognize
92*b8dbfb0aSDag-Erling Smørgrav 	 * that the buffer is full and continues to write beyond its end.
93*b8dbfb0aSDag-Erling Smørgrav 	 */
94*b8dbfb0aSDag-Erling Smørgrav 	fprintf(f, "%c", seq[i++]);
95*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(0, buf[BUFSIZE]);
96*b8dbfb0aSDag-Erling Smørgrav }
97*b8dbfb0aSDag-Erling Smørgrav 
98*b8dbfb0aSDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(flushlbuf_full);
ATF_TC_BODY(flushlbuf_full,tc)99*b8dbfb0aSDag-Erling Smørgrav ATF_TC_BODY(flushlbuf_full, tc)
100*b8dbfb0aSDag-Erling Smørgrav {
101*b8dbfb0aSDag-Erling Smørgrav 	static struct stream s;
102*b8dbfb0aSDag-Erling Smørgrav 	static char buf[BUFSIZE];
103*b8dbfb0aSDag-Erling Smørgrav 	FILE *f;
104*b8dbfb0aSDag-Erling Smørgrav 	unsigned int i = 0;
105*b8dbfb0aSDag-Erling Smørgrav 	int ret = 0;
106*b8dbfb0aSDag-Erling Smørgrav 
107*b8dbfb0aSDag-Erling Smørgrav 	/*
108*b8dbfb0aSDag-Erling Smørgrav 	 * Create the stream and its buffer, print just enough characters
109*b8dbfb0aSDag-Erling Smørgrav 	 * to the stream to fill the buffer without triggering a flush,
110*b8dbfb0aSDag-Erling Smørgrav 	 * then check the state.
111*b8dbfb0aSDag-Erling Smørgrav 	 */
112*b8dbfb0aSDag-Erling Smørgrav 	s.len = 0; // any attempt to write will fail
113*b8dbfb0aSDag-Erling Smørgrav 	ATF_REQUIRE((f = fwopen(&s, writefn)) != NULL);
114*b8dbfb0aSDag-Erling Smørgrav 	ATF_REQUIRE(setvbuf(f, buf, _IOLBF, BUFSIZE) == 0);
115*b8dbfb0aSDag-Erling Smørgrav 	while (i < BUFSIZE)
116*b8dbfb0aSDag-Erling Smørgrav 		if ((ret = fprintf(f, "%c", seq[i++])) < 0)
117*b8dbfb0aSDag-Erling Smørgrav 			break;
118*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(BUFSIZE, i);
119*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(seq[i - 1], buf[BUFSIZE - 1]);
120*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(1, ret);
121*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(0, s.pos);
122*b8dbfb0aSDag-Erling Smørgrav 
123*b8dbfb0aSDag-Erling Smørgrav 	/*
124*b8dbfb0aSDag-Erling Smørgrav 	 * At this point, the buffer is full but writefn() has not yet
125*b8dbfb0aSDag-Erling Smørgrav 	 * been called.  The next fprintf() call will trigger a preemptive
126*b8dbfb0aSDag-Erling Smørgrav 	 * fflush(), and writefn() will immediately return EAGAIN, causing
127*b8dbfb0aSDag-Erling Smørgrav 	 * fprintf() to fail without having written anything (which is why
128*b8dbfb0aSDag-Erling Smørgrav 	 * we don't increment i here).
129*b8dbfb0aSDag-Erling Smørgrav 	 */
130*b8dbfb0aSDag-Erling Smørgrav 	ret = fprintf(f, "%c", seq[i]);
131*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_ERRNO(EAGAIN, ret < 0);
132*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(s.len, s.pos);
133*b8dbfb0aSDag-Erling Smørgrav 
134*b8dbfb0aSDag-Erling Smørgrav 	/*
135*b8dbfb0aSDag-Erling Smørgrav 	 * Now make our stream writeable.
136*b8dbfb0aSDag-Erling Smørgrav 	 */
137*b8dbfb0aSDag-Erling Smørgrav 	s.len = sizeof(s.buf);
138*b8dbfb0aSDag-Erling Smørgrav 
139*b8dbfb0aSDag-Erling Smørgrav 	/*
140*b8dbfb0aSDag-Erling Smørgrav 	 * Flush the stream again.  The data we failed to write previously
141*b8dbfb0aSDag-Erling Smørgrav 	 * should still be in the buffer and will now be written to the
142*b8dbfb0aSDag-Erling Smørgrav 	 * stream.
143*b8dbfb0aSDag-Erling Smørgrav 	 */
144*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(0, fflush(f));
145*b8dbfb0aSDag-Erling Smørgrav 	ATF_CHECK_EQ(seq[0], s.buf[0]);
146*b8dbfb0aSDag-Erling Smørgrav }
147*b8dbfb0aSDag-Erling Smørgrav 
ATF_TP_ADD_TCS(tp)148*b8dbfb0aSDag-Erling Smørgrav ATF_TP_ADD_TCS(tp)
149*b8dbfb0aSDag-Erling Smørgrav {
150*b8dbfb0aSDag-Erling Smørgrav 
151*b8dbfb0aSDag-Erling Smørgrav 	ATF_TP_ADD_TC(tp, flushlbuf_partial);
152*b8dbfb0aSDag-Erling Smørgrav 	ATF_TP_ADD_TC(tp, flushlbuf_full);
153*b8dbfb0aSDag-Erling Smørgrav 
154*b8dbfb0aSDag-Erling Smørgrav 	return (atf_no_error());
155*b8dbfb0aSDag-Erling Smørgrav }
156