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