1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2024 Oxide Computer Company
14 */
15
16 /*
17 * Verify basic behavior of ptsname() and ptsname_r().
18 */
19
20 #include <stdlib.h>
21 #include <fcntl.h>
22 #include <err.h>
23 #include <unistd.h>
24 #include <sys/debug.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <stdbool.h>
28 #include <sys/socket.h>
29
30 static const char *pts_base = "/dev/pts";
31
32 static bool
ptsname_err(const char * desc,int fd,int error)33 ptsname_err(const char *desc, int fd, int error)
34 {
35 if (ptsname(fd) != NULL) {
36 warnx("TEST FAILED: %s: ptsname incorrectly succeeded!", desc);
37 return (false);
38 }
39
40 if (errno != error) {
41 int e = errno;
42 warnx("TEST FAILED: %s: ptsname returned %s, but we expected "
43 "%s", desc, strerrorname_np(e), strerrorname_np(error));
44 return (false);
45 }
46
47 (void) printf("TEST PASSED: %s correctly returned %s\n", desc,
48 strerrorname_np(error));
49 return (true);
50 }
51
52 static bool
ptsname_r_err(const char * desc,int fd,char * buf,size_t len,int error)53 ptsname_r_err(const char *desc, int fd, char *buf, size_t len, int error)
54 {
55 int ret;
56
57 if ((ret = ptsname_r(fd, buf, len)) == 0) {
58 warnx("TEST FAILED: %s: ptsname incorrectly succeeded!", desc);
59 return (false);
60 }
61
62 if (ret != error) {
63 warnx("TEST FAILED: %s: ptsname returned %s, but we expected "
64 "%s", desc, strerrorname_np(ret), strerrorname_np(error));
65 return (false);
66 }
67
68 (void) printf("TEST PASSED: %s correctly returned %s\n", desc,
69 strerrorname_np(error));
70 return (true);
71 }
72
73 int
main(void)74 main(void)
75 {
76 char *buf, *alt_buf, *pts;
77 size_t len;
78 long l;
79 int ret = EXIT_SUCCESS;
80 int mngr, alt_mngr, zero, stream, pts_ret;
81
82 if ((mngr = posix_openpt(O_RDWR | O_NOCTTY)) < 0) {
83 err(EXIT_FAILURE, "TEST FAILED: failed to create manager "
84 "pseudo-terminal device");
85 }
86
87 if ((alt_mngr = posix_openpt(O_RDWR | O_NOCTTY)) < 0) {
88 err(EXIT_FAILURE, "TEST FAILED: failed to create second "
89 "manager pseudo-terminal device");
90 }
91
92 if ((l = sysconf(_SC_TTY_NAME_MAX)) < 0) {
93 err(EXIT_FAILURE, "TEST FAILED: failed to obtain TTY_NAME_MAX");
94 }
95 len = (size_t)l;
96
97 if ((buf = malloc(len)) == NULL) {
98 err(EXIT_FAILURE, "TEST FAILED: failed to allocate %zu bytes "
99 "for tty name buffer", len);
100 }
101
102 if ((alt_buf = malloc(len)) == NULL) {
103 err(EXIT_FAILURE, "TEST FAILED: failed to allocate %zu bytes "
104 "for second tty name buffer", len);
105 }
106
107 if ((stream = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
108 err(EXIT_FAILURE, "TEST FAILED: failed to open a UDS socket");
109 }
110
111 if ((zero = open("/dev/zero", O_RDWR)) < 0) {
112 err(EXIT_FAILURE, "TEST FAILED: failed to open /dev/zero");
113 }
114
115 closefrom(zero + 1);
116
117 /*
118 * The errors that are returned here are kind of all over the place.
119 * This is because we have historically just returned the results of an
120 * ioctl and haven't tried to consolidate anything in the past. So for
121 * now the following are kind of a check to both ensure that things have
122 * failed and see what changes. The EBADF is what we expect. EINVAL is
123 * okay. ENXIO is really not great, but it is what it is.
124 */
125 if (!ptsname_err("ptsname: non-existent fd", zero + 1, EBADF)) {
126 ret = EXIT_FAILURE;
127 }
128
129 if (!ptsname_err("ptsname: non-terminal fd", zero, ENXIO)) {
130 ret = EXIT_FAILURE;
131 }
132
133 if (!ptsname_err("ptsname: non-terminal fd", stream, EINVAL)) {
134 ret = EXIT_FAILURE;
135 }
136
137 if (!ptsname_r_err("ptsname: non-existent fd", zero + 1, buf, len,
138 EBADF)) {
139 ret = EXIT_FAILURE;
140 }
141
142 if (!ptsname_r_err("ptsname: non-terminal fd", zero, buf, len,
143 ENXIO)) {
144 ret = EXIT_FAILURE;
145 }
146
147 if (!ptsname_r_err("ptsname: non-terminal fd", stream, buf, len,
148 EINVAL)) {
149 ret = EXIT_FAILURE;
150 }
151
152 /*
153 * Next, verify that ptsname and ptsname_r() give the same results.
154 */
155 if ((pts = ptsname(mngr)) == NULL) {
156 err(EXIT_FAILURE, "TEST FAILED: ptsname on a manager "
157 "unexpected failed");
158 }
159
160 if (strncmp(pts, pts_base, strlen(pts_base)) != 0) {
161 warnx("TEST FAILED: ptsname() path '%s' does not have base "
162 "of %s", pts, pts_base);
163 } else {
164 (void) printf("TEST PASSED: ptsname() path has correct base\n");
165 }
166
167 if ((pts_ret = ptsname_r(mngr, buf, len)) != 0) {
168 errx(EXIT_FAILURE, "TEST FAILED: ptsname_r unexpected failed "
169 "with %s", strerrorname_np(pts_ret));
170 }
171
172 if (strncmp(buf, pts_base, strlen(pts_base)) != 0) {
173 warnx("TEST FAILED: ptsname_r() path '%s' does not have base "
174 "of %s", buf, pts_base);
175 } else {
176 (void) printf("TEST PASSED: ptsname_r() path has correct "
177 "base\n");
178 }
179
180 if (strcmp(pts, buf) == 0) {
181 (void) printf("TEST PASSED: ptsname() and ptsname_r() "
182 "agreed\n");
183 } else {
184 warnx("TEST FAILED: ptsname() and ptsname_r() returned "
185 "different strings: found '%s' and '%s' respectively", pts,
186 buf);
187 ret = EXIT_FAILURE;
188 }
189
190 /*
191 * Confirm that ptsname_r() and ptsname() on a second device doesn't
192 * impact the other.
193 */
194 if ((pts_ret = ptsname_r(alt_mngr, alt_buf, len)) != 0) {
195 errx(EXIT_FAILURE, "TEST FAILED: ptsname_r unexpected failed "
196 "with %s", strerrorname_np(pts_ret));
197 }
198
199 if (strncmp(alt_buf, pts_base, strlen(pts_base)) != 0) {
200 warnx("TEST FAILED: ptsname_r() path '%s' does not have base "
201 "of %s", buf, pts_base);
202 ret = EXIT_FAILURE;
203 } else {
204 (void) printf("TEST PASSED: ptsname_r() path has correct "
205 "base\n");
206 }
207
208 if (strcmp(buf, alt_buf) == 0) {
209 warnx("TEST FAILED: ptsname_r() on two separate devices "
210 "returned the same value: '%s'", buf);
211 ret = EXIT_FAILURE;
212 } else {
213 (void) printf("TEST PASSED: ptsname_r() on two separate "
214 "devices have different paths\n");
215 }
216
217 if (strcmp(pts, buf) == 0) {
218 (void) printf("TEST PASSED: ptsname() buffer not impacted by "
219 "ptsname_r() call\n");
220 } else {
221 warnx("TEST FAILED: ptsname() value changed after ptsname_r() "
222 "call: found '%s', but expected '%s'", pts, buf);
223 ret = EXIT_FAILURE;
224 }
225
226 if (ptsname(alt_mngr) == NULL) {
227 warn("TEST FAILED: ptsname() failed on second manager fd");
228 ret = EXIT_FAILURE;
229 }
230
231 if (strcmp(pts, alt_buf) == 0) {
232 (void) printf("TEST PASSED: ptsname() matches ptsname_r() on "
233 "second manager\n");
234 } else {
235 warnx("TEST FAILED: ptsname() and ptsname_r() returned "
236 "different strings for second manager: found '%s' and "
237 "'%s' respectively", pts, buf);
238 ret = EXIT_FAILURE;
239 }
240
241 if (!ptsname_r_err("ptsname: length 0", mngr, buf, 0, ERANGE)) {
242 ret = EXIT_FAILURE;
243 }
244
245 if (!ptsname_r_err("ptsname: length 8 (/dev/pts)", mngr, buf,
246 strlen(pts_base), ERANGE)) {
247 ret = EXIT_FAILURE;
248 }
249
250 if (!ptsname_r_err("ptsname: length no-NUL", mngr, buf, strlen(buf),
251 ERANGE)) {
252 ret = EXIT_FAILURE;
253 }
254
255 free(buf);
256 free(alt_buf);
257 VERIFY0(close(mngr));
258
259 if (ret == EXIT_SUCCESS) {
260 (void) printf("All tests passed successfully\n");
261 }
262
263 return (ret);
264 }
265