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
writefn(void * cookie,const char * buf,int len)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);
ATF_TC_BODY(flushlbuf_partial,tc)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);
ATF_TC_BODY(flushlbuf_full,tc)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
ATF_TP_ADD_TCS(tp)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