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
This commit is contained in:
Saúl Ibarra Corretgé 2014-03-07 21:35:28 +01:00
parent daa457b1c0
commit b197515367
6 changed files with 172 additions and 50 deletions

View File

@ -59,12 +59,15 @@
# include <sys/filio.h>
# include <sys/ioctl.h>
# include <sys/wait.h>
#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;
}
}

View File

@ -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);

View File

@ -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
}

View File

@ -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_ */

View File

@ -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;

View File

@ -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;
}