• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "spinlock.h"
25 
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 static int orig_termios_fd = -1;
38 static struct termios orig_termios;
39 static uv_spinlock_t termios_spinlock = UV_SPINLOCK_INITIALIZER;
40 
uv__tty_is_slave(const int fd)41 static int uv__tty_is_slave(const int fd) {
42   int result;
43 #if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
44   int dummy;
45 
46   result = ioctl(fd, TIOCGPTN, &dummy) != 0;
47 #elif defined(__APPLE__)
48   char dummy[256];
49 
50   result = ioctl(fd, TIOCPTYGNAME, &dummy) != 0;
51 #elif defined(__NetBSD__)
52   /*
53    * NetBSD as an extension returns with ptsname(3) and ptsname_r(3) the slave
54    * device name for both descriptors, the master one and slave one.
55    *
56    * Implement function to compare major device number with pts devices.
57    *
58    * The major numbers are machine-dependent, on NetBSD/amd64 they are
59    * respectively:
60    *  - master tty: ptc - major 6
61    *  - slave tty:  pts - major 5
62    */
63 
64   struct stat sb;
65   /* Lookup device's major for the pts driver and cache it. */
66   static devmajor_t pts = NODEVMAJOR;
67 
68   if (pts == NODEVMAJOR) {
69     pts = getdevmajor("pts", S_IFCHR);
70     if (pts == NODEVMAJOR)
71       abort();
72   }
73 
74   /* Lookup stat structure behind the file descriptor. */
75   if (fstat(fd, &sb) != 0)
76     abort();
77 
78   /* Assert character device. */
79   if (!S_ISCHR(sb.st_mode))
80     abort();
81 
82   /* Assert valid major. */
83   if (major(sb.st_rdev) == NODEVMAJOR)
84     abort();
85 
86   result = (pts == major(sb.st_rdev));
87 #else
88   /* Fallback to ptsname
89    */
90   result = ptsname(fd) == NULL;
91 #endif
92   return result;
93 }
94 
uv_tty_init(uv_loop_t * loop,uv_tty_t * tty,int fd,int unused)95 int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, int fd, int unused) {
96   uv_handle_type type;
97   int flags;
98   int newfd;
99   int r;
100   int saved_flags;
101   int mode;
102   char path[256];
103   (void)unused; /* deprecated parameter is no longer needed */
104 
105   /* File descriptors that refer to files cannot be monitored with epoll.
106    * That restriction also applies to character devices like /dev/random
107    * (but obviously not /dev/tty.)
108    */
109   type = uv_guess_handle(fd);
110   if (type == UV_FILE || type == UV_UNKNOWN_HANDLE)
111     return UV_EINVAL;
112 
113   flags = 0;
114   newfd = -1;
115 
116   /* Save the fd flags in case we need to restore them due to an error. */
117   do
118     saved_flags = fcntl(fd, F_GETFL);
119   while (saved_flags == -1 && errno == EINTR);
120 
121   if (saved_flags == -1)
122     return UV__ERR(errno);
123   mode = saved_flags & O_ACCMODE;
124 
125   /* Reopen the file descriptor when it refers to a tty. This lets us put the
126    * tty in non-blocking mode without affecting other processes that share it
127    * with us.
128    *
129    * Example: `node | cat` - if we put our fd 0 in non-blocking mode, it also
130    * affects fd 1 of `cat` because both file descriptors refer to the same
131    * struct file in the kernel. When we reopen our fd 0, it points to a
132    * different struct file, hence changing its properties doesn't affect
133    * other processes.
134    */
135   if (type == UV_TTY) {
136     /* Reopening a pty in master mode won't work either because the reopened
137      * pty will be in slave mode (*BSD) or reopening will allocate a new
138      * master/slave pair (Linux). Therefore check if the fd points to a
139      * slave device.
140      */
141     if (uv__tty_is_slave(fd) && ttyname_r(fd, path, sizeof(path)) == 0)
142       r = uv__open_cloexec(path, mode | O_NOCTTY);
143     else
144       r = -1;
145 
146     if (r < 0) {
147       /* fallback to using blocking writes */
148       if (mode != O_RDONLY)
149         flags |= UV_HANDLE_BLOCKING_WRITES;
150       goto skip;
151     }
152 
153     newfd = r;
154 
155     r = uv__dup2_cloexec(newfd, fd);
156     if (r < 0 && r != UV_EINVAL) {
157       /* EINVAL means newfd == fd which could conceivably happen if another
158        * thread called close(fd) between our calls to isatty() and open().
159        * That's a rather unlikely event but let's handle it anyway.
160        */
161       uv__close(newfd);
162       return r;
163     }
164 
165     fd = newfd;
166   }
167 
168 skip:
169   uv__stream_init(loop, (uv_stream_t*) tty, UV_TTY);
170 
171   /* If anything fails beyond this point we need to remove the handle from
172    * the handle queue, since it was added by uv__handle_init in uv_stream_init.
173    */
174 
175   if (!(flags & UV_HANDLE_BLOCKING_WRITES))
176     uv__nonblock(fd, 1);
177 
178 #if defined(__APPLE__)
179   r = uv__stream_try_select((uv_stream_t*) tty, &fd);
180   if (r) {
181     int rc = r;
182     if (newfd != -1)
183       uv__close(newfd);
184     QUEUE_REMOVE(&tty->handle_queue);
185     do
186       r = fcntl(fd, F_SETFL, saved_flags);
187     while (r == -1 && errno == EINTR);
188     return rc;
189   }
190 #endif
191 
192   if (mode != O_WRONLY)
193     flags |= UV_HANDLE_READABLE;
194   if (mode != O_RDONLY)
195     flags |= UV_HANDLE_WRITABLE;
196 
197   uv__stream_open((uv_stream_t*) tty, fd, flags);
198   tty->mode = UV_TTY_MODE_NORMAL;
199 
200   return 0;
201 }
202 
uv__tty_make_raw(struct termios * tio)203 static void uv__tty_make_raw(struct termios* tio) {
204   assert(tio != NULL);
205 
206 #if defined __sun || defined __MVS__
207   /*
208    * This implementation of cfmakeraw for Solaris and derivatives is taken from
209    * http://www.perkin.org.uk/posts/solaris-portability-cfmakeraw.html.
210    */
211   tio->c_iflag &= ~(IMAXBEL | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR |
212                     IGNCR | ICRNL | IXON);
213   tio->c_oflag &= ~OPOST;
214   tio->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
215   tio->c_cflag &= ~(CSIZE | PARENB);
216   tio->c_cflag |= CS8;
217 #else
218   cfmakeraw(tio);
219 #endif /* #ifdef __sun */
220 }
221 
uv_tty_set_mode(uv_tty_t * tty,uv_tty_mode_t mode)222 int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) {
223   struct termios tmp;
224   int fd;
225 
226   if (tty->mode == (int) mode)
227     return 0;
228 
229   fd = uv__stream_fd(tty);
230   if (tty->mode == UV_TTY_MODE_NORMAL && mode != UV_TTY_MODE_NORMAL) {
231     if (tcgetattr(fd, &tty->orig_termios))
232       return UV__ERR(errno);
233 
234     /* This is used for uv_tty_reset_mode() */
235     uv_spinlock_lock(&termios_spinlock);
236     if (orig_termios_fd == -1) {
237       orig_termios = tty->orig_termios;
238       orig_termios_fd = fd;
239     }
240     uv_spinlock_unlock(&termios_spinlock);
241   }
242 
243   tmp = tty->orig_termios;
244   switch (mode) {
245     case UV_TTY_MODE_NORMAL:
246       break;
247     case UV_TTY_MODE_RAW:
248       tmp.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
249       tmp.c_oflag |= (ONLCR);
250       tmp.c_cflag |= (CS8);
251       tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
252       tmp.c_cc[VMIN] = 1;
253       tmp.c_cc[VTIME] = 0;
254       break;
255     case UV_TTY_MODE_IO:
256       uv__tty_make_raw(&tmp);
257       break;
258   }
259 
260   /* Apply changes after draining */
261   if (tcsetattr(fd, TCSADRAIN, &tmp))
262     return UV__ERR(errno);
263 
264   tty->mode = mode;
265   return 0;
266 }
267 
268 
uv_tty_get_winsize(uv_tty_t * tty,int * width,int * height)269 int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) {
270   struct winsize ws;
271   int err;
272 
273   do
274     err = ioctl(uv__stream_fd(tty), TIOCGWINSZ, &ws);
275   while (err == -1 && errno == EINTR);
276 
277   if (err == -1)
278     return UV__ERR(errno);
279 
280   *width = ws.ws_col;
281   *height = ws.ws_row;
282 
283   return 0;
284 }
285 
286 
uv_guess_handle(uv_file file)287 uv_handle_type uv_guess_handle(uv_file file) {
288   struct sockaddr sa;
289   struct stat s;
290   socklen_t len;
291   int type;
292 
293   if (file < 0)
294     return UV_UNKNOWN_HANDLE;
295 
296 #if defined(__PASE__)
297   /* On IBMi PASE isatty() always returns true for stdin, stdout and stderr.
298    * Use ioctl() instead to identify whether it's actually a TTY.
299    */
300   if (!ioctl(file, TXISATTY + 0x81, NULL) || errno != ENOTTY)
301 #else
302   if (isatty(file))
303 #endif
304     return UV_TTY;
305 
306   if (fstat(file, &s))
307     return UV_UNKNOWN_HANDLE;
308 
309   if (S_ISREG(s.st_mode))
310     return UV_FILE;
311 
312   if (S_ISCHR(s.st_mode))
313     return UV_FILE;  /* XXX UV_NAMED_PIPE? */
314 
315   if (S_ISFIFO(s.st_mode))
316     return UV_NAMED_PIPE;
317 
318   if (!S_ISSOCK(s.st_mode))
319     return UV_UNKNOWN_HANDLE;
320 
321   len = sizeof(type);
322   if (getsockopt(file, SOL_SOCKET, SO_TYPE, &type, &len))
323     return UV_UNKNOWN_HANDLE;
324 
325   len = sizeof(sa);
326   if (getsockname(file, &sa, &len))
327     return UV_UNKNOWN_HANDLE;
328 
329   if (type == SOCK_DGRAM)
330     if (sa.sa_family == AF_INET || sa.sa_family == AF_INET6)
331       return UV_UDP;
332 
333   if (type == SOCK_STREAM) {
334 #if defined(_AIX) || defined(__DragonFly__)
335     /* on AIX/DragonFly the getsockname call returns an empty sa structure
336      * for sockets of type AF_UNIX.  For all other types it will
337      * return a properly filled in structure.
338      */
339     if (len == 0)
340       return UV_NAMED_PIPE;
341 #endif /* defined(_AIX) || defined(__DragonFly__) */
342 
343     if (sa.sa_family == AF_INET || sa.sa_family == AF_INET6)
344       return UV_TCP;
345     if (sa.sa_family == AF_UNIX)
346       return UV_NAMED_PIPE;
347   }
348 
349   return UV_UNKNOWN_HANDLE;
350 }
351 
352 
353 /* This function is async signal-safe, meaning that it's safe to call from
354  * inside a signal handler _unless_ execution was inside uv_tty_set_mode()'s
355  * critical section when the signal was raised.
356  */
uv_tty_reset_mode(void)357 int uv_tty_reset_mode(void) {
358   int saved_errno;
359   int err;
360 
361   saved_errno = errno;
362   if (!uv_spinlock_trylock(&termios_spinlock))
363     return UV_EBUSY;  /* In uv_tty_set_mode(). */
364 
365   err = 0;
366   if (orig_termios_fd != -1)
367     if (tcsetattr(orig_termios_fd, TCSANOW, &orig_termios))
368       err = UV__ERR(errno);
369 
370   uv_spinlock_unlock(&termios_spinlock);
371   errno = saved_errno;
372 
373   return err;
374 }
375 
uv_tty_set_vterm_state(uv_tty_vtermstate_t state)376 void uv_tty_set_vterm_state(uv_tty_vtermstate_t state) {
377 }
378 
uv_tty_get_vterm_state(uv_tty_vtermstate_t * state)379 int uv_tty_get_vterm_state(uv_tty_vtermstate_t* state) {
380   return UV_ENOTSUP;
381 }
382