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
14static const char seq[] =
15    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
16    "abcdefghijklmnopqrstuvwxyz"
17    "0123456789+/";
18
19struct stream {
20	char buf[BUFSIZE];
21	unsigned int len;
22	unsigned int pos;
23};
24
25static 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
43ATF_TC_WITHOUT_HEAD(flushlbuf_partial);
44ATF_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
98ATF_TC_WITHOUT_HEAD(flushlbuf_full);
99ATF_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
148ATF_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