xref: /illumos-gate/usr/src/test/libc-tests/tests/ptsname.c (revision 2833423dc59f4c35fe4713dbb942950c82df0437)
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
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
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
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