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