Mercurial
comparison third_party/libuv/src/unix/tty.c @ 160:948de3f54cea
[ThirdParty] Added libuv
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 14 Jan 2026 19:39:52 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 159:05cf9467a1c3 | 160:948de3f54cea |
|---|---|
| 1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. | |
| 2 * | |
| 3 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 4 * of this software and associated documentation files (the "Software"), to | |
| 5 * deal in the Software without restriction, including without limitation the | |
| 6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
| 7 * sell copies of the Software, and to permit persons to whom the Software is | |
| 8 * furnished to do so, subject to the following conditions: | |
| 9 * | |
| 10 * The above copyright notice and this permission notice shall be included in | |
| 11 * all copies or substantial portions of the Software. | |
| 12 * | |
| 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 19 * IN THE SOFTWARE. | |
| 20 */ | |
| 21 | |
| 22 #include "uv.h" | |
| 23 #include "internal.h" | |
| 24 | |
| 25 #include <stdatomic.h> | |
| 26 #include <stdlib.h> | |
| 27 #include <assert.h> | |
| 28 #include <unistd.h> | |
| 29 #include <termios.h> | |
| 30 #include <errno.h> | |
| 31 #include <sys/ioctl.h> | |
| 32 | |
| 33 #if defined(__MVS__) && !defined(IMAXBEL) | |
| 34 #define IMAXBEL 0 | |
| 35 #endif | |
| 36 | |
| 37 #if defined(__PASE__) | |
| 38 /* On IBM i PASE, for better compatibility with running interactive programs in | |
| 39 * a 5250 environment, isatty() will return true for the stdin/stdout/stderr | |
| 40 * streams created by QSH/QP2TERM. | |
| 41 * | |
| 42 * For more, see docs on PASE_STDIO_ISATTY in | |
| 43 * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_74/apis/pase_environ.htm | |
| 44 * | |
| 45 * This behavior causes problems for Node as it expects that if isatty() returns | |
| 46 * true that TTY ioctls will be supported by that fd (which is not an | |
| 47 * unreasonable expectation) and when they don't it crashes with assertion | |
| 48 * errors. | |
| 49 * | |
| 50 * Here, we create our own version of isatty() that uses ioctl() to identify | |
| 51 * whether the fd is *really* a TTY or not. | |
| 52 */ | |
| 53 static int isreallyatty(int file) { | |
| 54 int rc; | |
| 55 | |
| 56 rc = !ioctl(file, TXISATTY + 0x81, NULL); | |
| 57 if (!rc && errno != EBADF) | |
| 58 errno = ENOTTY; | |
| 59 | |
| 60 return rc; | |
| 61 } | |
| 62 #define isatty(fd) isreallyatty(fd) | |
| 63 #endif | |
| 64 | |
| 65 static int orig_termios_fd = -1; | |
| 66 static struct termios orig_termios; | |
| 67 static _Atomic int termios_spinlock; | |
| 68 | |
| 69 int uv__tcsetattr(int fd, int how, const struct termios *term) { | |
| 70 int rc; | |
| 71 | |
| 72 do | |
| 73 rc = tcsetattr(fd, how, term); | |
| 74 while (rc == -1 && errno == EINTR); | |
| 75 | |
| 76 if (rc == -1) | |
| 77 return UV__ERR(errno); | |
| 78 | |
| 79 return 0; | |
| 80 } | |
| 81 | |
| 82 static int uv__tty_is_slave(const int fd) { | |
| 83 int result; | |
| 84 #if defined(__linux__) || defined(__FreeBSD__) | |
| 85 int dummy; | |
| 86 | |
| 87 result = ioctl(fd, TIOCGPTN, &dummy) != 0; | |
| 88 #elif defined(__APPLE__) | |
| 89 char dummy[256]; | |
| 90 | |
| 91 result = ioctl(fd, TIOCPTYGNAME, &dummy) != 0; | |
| 92 #elif defined(__NetBSD__) | |
| 93 /* | |
| 94 * NetBSD as an extension returns with ptsname(3) and ptsname_r(3) the slave | |
| 95 * device name for both descriptors, the master one and slave one. | |
| 96 * | |
| 97 * Implement function to compare major device number with pts devices. | |
| 98 * | |
| 99 * The major numbers are machine-dependent, on NetBSD/amd64 they are | |
| 100 * respectively: | |
| 101 * - master tty: ptc - major 6 | |
| 102 * - slave tty: pts - major 5 | |
| 103 */ | |
| 104 | |
| 105 struct stat sb; | |
| 106 /* Lookup device's major for the pts driver and cache it. */ | |
| 107 static devmajor_t pts = NODEVMAJOR; | |
| 108 | |
| 109 if (pts == NODEVMAJOR) { | |
| 110 pts = getdevmajor("pts", S_IFCHR); | |
| 111 if (pts == NODEVMAJOR) | |
| 112 abort(); | |
| 113 } | |
| 114 | |
| 115 /* Lookup stat structure behind the file descriptor. */ | |
| 116 if (uv__fstat(fd, &sb) != 0) | |
| 117 abort(); | |
| 118 | |
| 119 /* Assert character device. */ | |
| 120 if (!S_ISCHR(sb.st_mode)) | |
| 121 abort(); | |
| 122 | |
| 123 /* Assert valid major. */ | |
| 124 if (major(sb.st_rdev) == NODEVMAJOR) | |
| 125 abort(); | |
| 126 | |
| 127 result = (pts == major(sb.st_rdev)); | |
| 128 #else | |
| 129 /* Fallback to ptsname | |
| 130 */ | |
| 131 result = ptsname(fd) == NULL; | |
| 132 #endif | |
| 133 return result; | |
| 134 } | |
| 135 | |
| 136 int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, int fd, int unused) { | |
| 137 uv_handle_type type; | |
| 138 int flags; | |
| 139 int newfd; | |
| 140 int r; | |
| 141 int saved_flags; | |
| 142 int mode; | |
| 143 char path[256]; | |
| 144 (void)unused; /* deprecated parameter is no longer needed */ | |
| 145 | |
| 146 /* File descriptors that refer to files cannot be monitored with epoll. | |
| 147 * That restriction also applies to character devices like /dev/random | |
| 148 * (but obviously not /dev/tty.) | |
| 149 */ | |
| 150 type = uv_guess_handle(fd); | |
| 151 if (type == UV_FILE || type == UV_UNKNOWN_HANDLE) | |
| 152 return UV_EINVAL; | |
| 153 | |
| 154 flags = 0; | |
| 155 newfd = -1; | |
| 156 | |
| 157 /* Save the fd flags in case we need to restore them due to an error. */ | |
| 158 do | |
| 159 saved_flags = fcntl(fd, F_GETFL); | |
| 160 while (saved_flags == -1 && errno == EINTR); | |
| 161 | |
| 162 if (saved_flags == -1) | |
| 163 return UV__ERR(errno); | |
| 164 mode = saved_flags & O_ACCMODE; | |
| 165 | |
| 166 /* Reopen the file descriptor when it refers to a tty. This lets us put the | |
| 167 * tty in non-blocking mode without affecting other processes that share it | |
| 168 * with us. | |
| 169 * | |
| 170 * Example: `node | cat` - if we put our fd 0 in non-blocking mode, it also | |
| 171 * affects fd 1 of `cat` because both file descriptors refer to the same | |
| 172 * struct file in the kernel. When we reopen our fd 0, it points to a | |
| 173 * different struct file, hence changing its properties doesn't affect | |
| 174 * other processes. | |
| 175 */ | |
| 176 if (type == UV_TTY) { | |
| 177 /* Reopening a pty in master mode won't work either because the reopened | |
| 178 * pty will be in slave mode (*BSD) or reopening will allocate a new | |
| 179 * master/slave pair (Linux). Therefore check if the fd points to a | |
| 180 * slave device. | |
| 181 */ | |
| 182 if (uv__tty_is_slave(fd) && ttyname_r(fd, path, sizeof(path)) == 0) | |
| 183 r = uv__open_cloexec(path, mode | O_NOCTTY); | |
| 184 else | |
| 185 r = -1; | |
| 186 | |
| 187 if (r < 0) { | |
| 188 /* fallback to using blocking writes */ | |
| 189 if (mode != O_RDONLY) | |
| 190 flags |= UV_HANDLE_BLOCKING_WRITES; | |
| 191 goto skip; | |
| 192 } | |
| 193 | |
| 194 newfd = r; | |
| 195 | |
| 196 r = uv__dup2_cloexec(newfd, fd); | |
| 197 if (r < 0 && r != UV_EINVAL) { | |
| 198 /* EINVAL means newfd == fd which could conceivably happen if another | |
| 199 * thread called close(fd) between our calls to isatty() and open(). | |
| 200 * That's a rather unlikely event but let's handle it anyway. | |
| 201 */ | |
| 202 uv__close(newfd); | |
| 203 return r; | |
| 204 } | |
| 205 | |
| 206 fd = newfd; | |
| 207 } | |
| 208 | |
| 209 skip: | |
| 210 uv__stream_init(loop, (uv_stream_t*) tty, UV_TTY); | |
| 211 | |
| 212 /* If anything fails beyond this point we need to remove the handle from | |
| 213 * the handle queue, since it was added by uv__handle_init in uv_stream_init. | |
| 214 */ | |
| 215 | |
| 216 if (!(flags & UV_HANDLE_BLOCKING_WRITES)) | |
| 217 uv__nonblock(fd, 1); | |
| 218 | |
| 219 #if defined(__APPLE__) | |
| 220 r = uv__stream_try_select((uv_stream_t*) tty, &fd); | |
| 221 if (r) { | |
| 222 int rc = r; | |
| 223 if (newfd != -1) | |
| 224 uv__close(newfd); | |
| 225 uv__queue_remove(&tty->handle_queue); | |
| 226 do | |
| 227 r = fcntl(fd, F_SETFL, saved_flags); | |
| 228 while (r == -1 && errno == EINTR); | |
| 229 return rc; | |
| 230 } | |
| 231 #endif | |
| 232 | |
| 233 if (mode != O_WRONLY) | |
| 234 flags |= UV_HANDLE_READABLE; | |
| 235 if (mode != O_RDONLY) | |
| 236 flags |= UV_HANDLE_WRITABLE; | |
| 237 | |
| 238 uv__stream_open((uv_stream_t*) tty, fd, flags); | |
| 239 tty->mode = UV_TTY_MODE_NORMAL; | |
| 240 | |
| 241 return 0; | |
| 242 } | |
| 243 | |
| 244 static void uv__tty_make_raw(struct termios* tio) { | |
| 245 assert(tio != NULL); | |
| 246 | |
| 247 #if defined __sun || defined __MVS__ | |
| 248 /* | |
| 249 * This implementation of cfmakeraw for Solaris and derivatives is taken from | |
| 250 * http://www.perkin.org.uk/posts/solaris-portability-cfmakeraw.html. | |
| 251 */ | |
| 252 tio->c_iflag &= ~(IMAXBEL | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | | |
| 253 IGNCR | ICRNL | IXON); | |
| 254 tio->c_oflag &= ~OPOST; | |
| 255 tio->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); | |
| 256 tio->c_cflag &= ~(CSIZE | PARENB); | |
| 257 tio->c_cflag |= CS8; | |
| 258 | |
| 259 /* | |
| 260 * By default, most software expects a pending read to block until at | |
| 261 * least one byte becomes available. As per termio(7I), this requires | |
| 262 * setting the MIN and TIME parameters appropriately. | |
| 263 * | |
| 264 * As a somewhat unfortunate artifact of history, the MIN and TIME slots | |
| 265 * in the control character array overlap with the EOF and EOL slots used | |
| 266 * for canonical mode processing. Because the EOF character needs to be | |
| 267 * the ASCII EOT value (aka Control-D), it has the byte value 4. When | |
| 268 * switching to raw mode, this is interpreted as a MIN value of 4; i.e., | |
| 269 * reads will block until at least four bytes have been input. | |
| 270 * | |
| 271 * Other platforms with a distinct MIN slot like Linux and FreeBSD appear | |
| 272 * to default to a MIN value of 1, so we'll force that value here: | |
| 273 */ | |
| 274 tio->c_cc[VMIN] = 1; | |
| 275 tio->c_cc[VTIME] = 0; | |
| 276 #else | |
| 277 cfmakeraw(tio); | |
| 278 #endif /* #ifdef __sun */ | |
| 279 } | |
| 280 | |
| 281 int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) { | |
| 282 struct termios tmp; | |
| 283 int expected; | |
| 284 int fd; | |
| 285 int rc; | |
| 286 | |
| 287 if (uv__is_raw_tty_mode(mode)) { | |
| 288 /* There is only a single raw TTY mode on UNIX. */ | |
| 289 mode = UV_TTY_MODE_RAW; | |
| 290 } | |
| 291 | |
| 292 if (tty->mode == (int) mode) | |
| 293 return 0; | |
| 294 | |
| 295 fd = uv__stream_fd(tty); | |
| 296 if (tty->mode == UV_TTY_MODE_NORMAL && mode != UV_TTY_MODE_NORMAL) { | |
| 297 do | |
| 298 rc = tcgetattr(fd, &tty->orig_termios); | |
| 299 while (rc == -1 && errno == EINTR); | |
| 300 | |
| 301 if (rc == -1) | |
| 302 return UV__ERR(errno); | |
| 303 | |
| 304 /* This is used for uv_tty_reset_mode() */ | |
| 305 do | |
| 306 expected = 0; | |
| 307 while (!atomic_compare_exchange_strong(&termios_spinlock, &expected, 1)); | |
| 308 | |
| 309 if (orig_termios_fd == -1) { | |
| 310 orig_termios = tty->orig_termios; | |
| 311 orig_termios_fd = fd; | |
| 312 } | |
| 313 | |
| 314 atomic_store(&termios_spinlock, 0); | |
| 315 } | |
| 316 | |
| 317 tmp = tty->orig_termios; | |
| 318 switch (mode) { | |
| 319 case UV_TTY_MODE_NORMAL: | |
| 320 break; | |
| 321 case UV_TTY_MODE_RAW: | |
| 322 tmp.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | |
| 323 tmp.c_oflag |= (ONLCR); | |
| 324 tmp.c_cflag |= (CS8); | |
| 325 tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |
| 326 tmp.c_cc[VMIN] = 1; | |
| 327 tmp.c_cc[VTIME] = 0; | |
| 328 break; | |
| 329 case UV_TTY_MODE_IO: | |
| 330 uv__tty_make_raw(&tmp); | |
| 331 break; | |
| 332 default: | |
| 333 UNREACHABLE(); | |
| 334 } | |
| 335 | |
| 336 /* Apply changes after draining */ | |
| 337 rc = uv__tcsetattr(fd, TCSADRAIN, &tmp); | |
| 338 if (rc == 0) | |
| 339 tty->mode = mode; | |
| 340 | |
| 341 return rc; | |
| 342 } | |
| 343 | |
| 344 | |
| 345 void uv__tty_close(uv_tty_t* handle) { | |
| 346 int expected; | |
| 347 int fd; | |
| 348 | |
| 349 fd = handle->io_watcher.fd; | |
| 350 if (fd == -1) | |
| 351 goto done; | |
| 352 | |
| 353 /* This is used for uv_tty_reset_mode() */ | |
| 354 do | |
| 355 expected = 0; | |
| 356 while (!atomic_compare_exchange_strong(&termios_spinlock, &expected, 1)); | |
| 357 | |
| 358 if (fd == orig_termios_fd) { | |
| 359 /* XXX(bnoordhuis) the tcsetattr is probably wrong when there are still | |
| 360 * other uv_tty_t handles active that refer to the same tty/pty but it's | |
| 361 * hard to recognize that particular situation without maintaining some | |
| 362 * kind of process-global data structure, and that still won't work in a | |
| 363 * multi-process setup. | |
| 364 */ | |
| 365 uv__tcsetattr(fd, TCSANOW, &orig_termios); | |
| 366 orig_termios_fd = -1; | |
| 367 } | |
| 368 | |
| 369 atomic_store(&termios_spinlock, 0); | |
| 370 | |
| 371 done: | |
| 372 uv__stream_close((uv_stream_t*) handle); | |
| 373 } | |
| 374 | |
| 375 | |
| 376 int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) { | |
| 377 struct winsize ws; | |
| 378 int err; | |
| 379 | |
| 380 do | |
| 381 err = ioctl(uv__stream_fd(tty), TIOCGWINSZ, &ws); | |
| 382 while (err == -1 && errno == EINTR); | |
| 383 | |
| 384 if (err == -1) | |
| 385 return UV__ERR(errno); | |
| 386 | |
| 387 *width = ws.ws_col; | |
| 388 *height = ws.ws_row; | |
| 389 | |
| 390 return 0; | |
| 391 } | |
| 392 | |
| 393 | |
| 394 uv_handle_type uv_guess_handle(uv_file file) { | |
| 395 struct sockaddr_storage ss; | |
| 396 struct stat s; | |
| 397 socklen_t len; | |
| 398 int type; | |
| 399 | |
| 400 if (file < 0) | |
| 401 return UV_UNKNOWN_HANDLE; | |
| 402 | |
| 403 if (isatty(file)) | |
| 404 return UV_TTY; | |
| 405 | |
| 406 if (uv__fstat(file, &s)) { | |
| 407 #if defined(__PASE__) | |
| 408 /* On ibmi receiving RST from TCP instead of FIN immediately puts fd into | |
| 409 * an error state. fstat will return EINVAL, getsockname will also return | |
| 410 * EINVAL, even if sockaddr_storage is valid. (If file does not refer to a | |
| 411 * socket, ENOTSOCK is returned instead.) | |
| 412 * In such cases, we will permit the user to open the connection as uv_tcp | |
| 413 * still, so that the user can get immediately notified of the error in | |
| 414 * their read callback and close this fd. | |
| 415 */ | |
| 416 len = sizeof(ss); | |
| 417 if (getsockname(file, (struct sockaddr*) &ss, &len)) { | |
| 418 if (errno == EINVAL) | |
| 419 return UV_TCP; | |
| 420 } | |
| 421 #endif | |
| 422 return UV_UNKNOWN_HANDLE; | |
| 423 } | |
| 424 | |
| 425 if (S_ISREG(s.st_mode)) | |
| 426 return UV_FILE; | |
| 427 | |
| 428 if (S_ISCHR(s.st_mode)) | |
| 429 return UV_FILE; /* XXX UV_NAMED_PIPE? */ | |
| 430 | |
| 431 if (S_ISFIFO(s.st_mode)) | |
| 432 return UV_NAMED_PIPE; | |
| 433 | |
| 434 if (!S_ISSOCK(s.st_mode)) | |
| 435 return UV_UNKNOWN_HANDLE; | |
| 436 | |
| 437 len = sizeof(ss); | |
| 438 if (getsockname(file, (struct sockaddr*) &ss, &len)) { | |
| 439 #if defined(_AIX) | |
| 440 /* On aix receiving RST from TCP instead of FIN immediately puts fd into | |
| 441 * an error state. In such case getsockname will return EINVAL, even if | |
| 442 * sockaddr_storage is valid. | |
| 443 * In such cases, we will permit the user to open the connection as uv_tcp | |
| 444 * still, so that the user can get immediately notified of the error in | |
| 445 * their read callback and close this fd. | |
| 446 */ | |
| 447 if (errno == EINVAL) { | |
| 448 return UV_TCP; | |
| 449 } | |
| 450 #endif | |
| 451 return UV_UNKNOWN_HANDLE; | |
| 452 } | |
| 453 | |
| 454 len = sizeof(type); | |
| 455 if (getsockopt(file, SOL_SOCKET, SO_TYPE, &type, &len)) | |
| 456 return UV_UNKNOWN_HANDLE; | |
| 457 | |
| 458 if (type == SOCK_DGRAM) | |
| 459 if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) | |
| 460 return UV_UDP; | |
| 461 | |
| 462 if (type == SOCK_STREAM) { | |
| 463 #if defined(_AIX) || defined(__DragonFly__) | |
| 464 /* on AIX/DragonFly the getsockname call returns an empty sa structure | |
| 465 * for sockets of type AF_UNIX. For all other types it will | |
| 466 * return a properly filled in structure. | |
| 467 */ | |
| 468 if (len == 0) | |
| 469 return UV_NAMED_PIPE; | |
| 470 #endif /* defined(_AIX) || defined(__DragonFly__) */ | |
| 471 | |
| 472 if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) | |
| 473 return UV_TCP; | |
| 474 if (ss.ss_family == AF_UNIX) | |
| 475 return UV_NAMED_PIPE; | |
| 476 } | |
| 477 | |
| 478 return UV_UNKNOWN_HANDLE; | |
| 479 } | |
| 480 | |
| 481 | |
| 482 /* This function is async signal-safe, meaning that it's safe to call from | |
| 483 * inside a signal handler _unless_ execution was inside uv_tty_set_mode()'s | |
| 484 * critical section when the signal was raised. | |
| 485 */ | |
| 486 int uv_tty_reset_mode(void) { | |
| 487 int saved_errno; | |
| 488 int err; | |
| 489 | |
| 490 saved_errno = errno; | |
| 491 | |
| 492 if (atomic_exchange(&termios_spinlock, 1)) | |
| 493 return UV_EBUSY; /* In uv_tty_set_mode() or uv__tty_close(). */ | |
| 494 | |
| 495 err = 0; | |
| 496 if (orig_termios_fd != -1) | |
| 497 err = uv__tcsetattr(orig_termios_fd, TCSANOW, &orig_termios); | |
| 498 | |
| 499 atomic_store(&termios_spinlock, 0); | |
| 500 errno = saved_errno; | |
| 501 | |
| 502 return err; | |
| 503 } | |
| 504 | |
| 505 void uv_tty_set_vterm_state(uv_tty_vtermstate_t state) { | |
| 506 } | |
| 507 | |
| 508 int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state) { | |
| 509 return UV_ENOTSUP; | |
| 510 } |