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