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