From b197515367d1a996dca9009483d202b306f9474e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Fri, 7 Mar 2014 21:35:28 +0100 Subject: [PATCH] unix: reopen tty as /dev/tty Reopen the file descriptor when it refers to a tty. This lets us put the tty in non-blocking mode without affecting other processes that share it with us. This brings back commit 31f9fbc, which was reverted in 20bb1bf. The OSX select trick is working now. Original patch by @bnoordhuis --- src/unix/core.c | 105 +++++++++++++++++++++++++++++++++++--- src/unix/internal.h | 2 + src/unix/linux-syscalls.c | 21 ++++++++ src/unix/linux-syscalls.h | 1 + src/unix/stream.c | 30 ----------- src/unix/tty.c | 63 ++++++++++++++++++----- 6 files changed, 172 insertions(+), 50 deletions(-) diff --git a/src/unix/core.c b/src/unix/core.c index a409f5ddc..b393097fd 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -59,12 +59,15 @@ # include # include # include -#endif - -#if __FreeBSD__ >= 10 -# define uv__accept4 accept4 -# define UV__SOCK_NONBLOCK SOCK_NONBLOCK -# define UV__SOCK_CLOEXEC SOCK_CLOEXEC +# define UV__O_CLOEXEC O_CLOEXEC +# if __FreeBSD__ >= 10 +# define uv__accept4 accept4 +# define UV__SOCK_NONBLOCK SOCK_NONBLOCK +# define UV__SOCK_CLOEXEC SOCK_CLOEXEC +# endif +# if !defined(F_DUP2FD_CLOEXEC) && defined(_F_DUP2FD_CLOEXEC) +# define F_DUP2FD_CLOEXEC _F_DUP2FD_CLOEXEC +# endif #endif static void uv__run_pending(uv_loop_t* loop); @@ -821,3 +824,93 @@ int uv_getrusage(uv_rusage_t* rusage) { return 0; } + + +int uv__open_cloexec(const char* path, int flags) { + int err; + int fd; + +#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 9) + static int no_cloexec; + + if (!no_cloexec) { + fd = open(path, flags | UV__O_CLOEXEC); + if (fd != -1) + return fd; + + if (errno != EINVAL) + return -errno; + + /* O_CLOEXEC not supported. */ + no_cloexec = 1; + } +#endif + + fd = open(path, flags); + if (fd == -1) + return -errno; + + err = uv__cloexec(fd, 1); + if (err) { + uv__close(fd); + return err; + } + + return fd; +} + + +int uv__dup2_cloexec(int oldfd, int newfd) { + int r; +#if defined(__FreeBSD__) && __FreeBSD__ >= 10 + do + r = dup3(oldfd, newfd, O_CLOEXEC); + while (r == -1 && errno == EINTR); + if (r == -1) + return -errno; + return r; +#elif defined(__FreeBSD__) && defined(F_DUP2FD_CLOEXEC) + do + r = fcntl(oldfd, F_DUP2FD_CLOEXEC, newfd); + while (r == -1 && errno == EINTR); + if (r != -1) + return r; + if (errno != EINVAL) + return -errno; + /* Fall through. */ +#elif defined(__linux__) + static int no_dup3; + if (!no_dup3) { + do + r = uv__dup3(oldfd, newfd, UV__O_CLOEXEC); + while (r == -1 && (errno == EINTR || errno == EBUSY)); + if (r != -1) + return r; + if (errno != ENOSYS) + return -errno; + /* Fall through. */ + no_dup3 = 1; + } +#endif + { + int err; + do + r = dup2(oldfd, newfd); +#if defined(__linux__) + while (r == -1 && (errno == EINTR || errno == EBUSY)); +#else + while (r == -1 && errno == EINTR); +#endif + + if (r == -1) + return -errno; + + err = uv__cloexec(newfd, 1); + if (err) { + uv__close(newfd); + return err; + } + + return r; + } +} diff --git a/src/unix/internal.h b/src/unix/internal.h index 59cae1bbe..61f5f6aa2 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -189,6 +189,8 @@ int uv__stream_try_select(uv_stream_t* stream, int* fd); #endif /* defined(__APPLE__) */ void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events); int uv__accept(int sockfd); +int uv__dup2_cloexec(int oldfd, int newfd); +int uv__open_cloexec(const char* path, int flags); /* tcp */ int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb); diff --git a/src/unix/linux-syscalls.c b/src/unix/linux-syscalls.c index c9cc44d8c..1ff8abd19 100644 --- a/src/unix/linux-syscalls.c +++ b/src/unix/linux-syscalls.c @@ -219,6 +219,16 @@ # endif #endif /* __NR_pwritev */ +#ifndef __NR_dup3 +# if defined(__x86_64__) +# define __NR_dup3 292 +# elif defined(__i386__) +# define __NR_dup3 330 +# elif defined(__arm__) +# define __NR_dup3 (UV_SYSCALL_BASE + 358) +# endif +#endif /* __NR_pwritev */ + int uv__accept4(int fd, struct sockaddr* addr, socklen_t* addrlen, int flags) { #if defined(__i386__) @@ -407,6 +417,7 @@ int uv__utimesat(int dirfd, #endif } + ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset) { #if defined(__NR_preadv) return syscall(__NR_preadv, fd, iov, iovcnt, offset); @@ -415,6 +426,7 @@ ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset) { #endif } + ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset) { #if defined(__NR_pwritev) return syscall(__NR_pwritev, fd, iov, iovcnt, offset); @@ -422,3 +434,12 @@ ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset) { return errno = ENOSYS, -1; #endif } + + +int uv__dup3(int oldfd, int newfd, int flags) { +#if defined(__NR_dup3) + return syscall(__NR_dup3, oldfd, newfd, flags); +#else + return errno = ENOSYS, -1; +#endif +} diff --git a/src/unix/linux-syscalls.h b/src/unix/linux-syscalls.h index 6d9ec9f22..0f0b34b1e 100644 --- a/src/unix/linux-syscalls.h +++ b/src/unix/linux-syscalls.h @@ -149,5 +149,6 @@ int uv__utimesat(int dirfd, int flags); ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset); ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset); +int uv__dup3(int oldfd, int newfd, int flags); #endif /* UV_LINUX_SYSCALL_H_ */ diff --git a/src/unix/stream.c b/src/unix/stream.c index d79b11094..370894bfd 100644 --- a/src/unix/stream.c +++ b/src/unix/stream.c @@ -63,36 +63,6 @@ static void uv__stream_io(uv_loop_t* loop, uv__io_t* w, unsigned int events); static size_t uv__write_req_size(uv_write_t* req); -/* Used by the accept() EMFILE party trick. */ -static int uv__open_cloexec(const char* path, int flags) { - int err; - int fd; - -#if defined(__linux__) - fd = open(path, flags | UV__O_CLOEXEC); - if (fd != -1) - return fd; - - if (errno != EINVAL) - return -errno; - - /* O_CLOEXEC not supported. */ -#endif - - fd = open(path, flags); - if (fd == -1) - return -errno; - - err = uv__cloexec(fd, 1); - if (err) { - uv__close(fd); - return err; - } - - return fd; -} - - static size_t uv_count_bufs(const uv_buf_t bufs[], unsigned int nbufs) { unsigned int i; size_t bytes; diff --git a/src/unix/tty.c b/src/unix/tty.c index ca9459dd0..c7ed101a7 100644 --- a/src/unix/tty.c +++ b/src/unix/tty.c @@ -35,26 +35,61 @@ static uv_spinlock_t termios_spinlock = UV_SPINLOCK_INITIALIZER; int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, int fd, int readable) { - uv__stream_init(loop, (uv_stream_t*)tty, UV_TTY); + int flags; + int newfd; + int r; + + newfd = -1; + + uv__stream_init(loop, (uv_stream_t*) tty, UV_TTY); + + /* Reopen the file descriptor when it refers to a tty. This lets us put the + * tty in non-blocking mode without affecting other processes that share it + * with us. + * + * Example: `node | cat` - if we put our fd 0 in non-blocking mode, it also + * affects fd 1 of `cat` because both file descriptors refer to the same + * struct file in the kernel. When we reopen our fd 0, it points to a + * different struct file, hence changing its properties doesn't affect + * other processes. + */ + if (isatty(fd)) { + newfd = uv__open_cloexec("/dev/tty", O_RDWR); + + if (newfd == -1) + return -errno; + + r = uv__dup2_cloexec(newfd, fd); + if (r < 0 && r != -EINVAL) { + /* EINVAL means newfd == fd which could conceivably happen if another + * thread called close(fd) between our calls to isatty() and open(). + * That's a rather unlikely event but let's handle it anyway. + */ + uv__close(newfd); + return r; + } + + fd = newfd; + } #if defined(__APPLE__) - { - int err = uv__stream_try_select((uv_stream_t*) tty, &fd); - if (err) - return err; + r = uv__stream_try_select((uv_stream_t*) tty, &fd); + if (r) { + if (newfd != -1) + uv__close(newfd); + return r; } -#endif /* defined(__APPLE__) */ +#endif - if (readable) { - uv__nonblock(fd, 1); - uv__stream_open((uv_stream_t*)tty, fd, UV_STREAM_READABLE); - } else { - /* Note: writable tty we set to blocking mode. */ - uv__stream_open((uv_stream_t*)tty, fd, UV_STREAM_WRITABLE); - tty->flags |= UV_STREAM_BLOCKING; - } + if (readable) + flags = UV_STREAM_READABLE; + else + flags = UV_STREAM_WRITABLE; + uv__nonblock(fd, 1); + uv__stream_open((uv_stream_t*) tty, fd, flags); tty->mode = 0; + return 0; }