xref: /libuv/test/test-tty.c (revision d8669609)
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 "task.h"
24 
25 #ifdef _WIN32
26 # include <io.h>
27 # include <windows.h>
28 #else /*  Unix */
29 # include <fcntl.h>
30 # include <unistd.h>
31 # if defined(__linux__) && !defined(__ANDROID__)
32 #  include <pty.h>
33 # elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
34 #  include <util.h>
35 # elif defined(__FreeBSD__) || defined(__DragonFly__)
36 #  include <libutil.h>
37 # endif
38 #endif
39 
40 #include <string.h>
41 #include <errno.h>
42 
43 
TEST_IMPL(tty)44 TEST_IMPL(tty) {
45   int r, width, height;
46   int ttyin_fd, ttyout_fd;
47   uv_tty_t tty_in, tty_out;
48   uv_loop_t* loop = uv_default_loop();
49 
50   /* Make sure we have an FD that refers to a tty */
51 #ifdef _WIN32
52   HANDLE handle;
53   handle = CreateFileA("conin$",
54                        GENERIC_READ | GENERIC_WRITE,
55                        FILE_SHARE_READ | FILE_SHARE_WRITE,
56                        NULL,
57                        OPEN_EXISTING,
58                        FILE_ATTRIBUTE_NORMAL,
59                        NULL);
60   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
61   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
62 
63   handle = CreateFileA("conout$",
64                        GENERIC_READ | GENERIC_WRITE,
65                        FILE_SHARE_READ | FILE_SHARE_WRITE,
66                        NULL,
67                        OPEN_EXISTING,
68                        FILE_ATTRIBUTE_NORMAL,
69                        NULL);
70   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
71   ttyout_fd = _open_osfhandle((intptr_t) handle, 0);
72 
73 #else /* unix */
74   ttyin_fd = open("/dev/tty", O_RDONLY, 0);
75   if (ttyin_fd < 0) {
76     fprintf(stderr, "Cannot open /dev/tty as read-only: %s\n", strerror(errno));
77     fflush(stderr);
78     return TEST_SKIP;
79   }
80 
81   ttyout_fd = open("/dev/tty", O_WRONLY, 0);
82   if (ttyout_fd < 0) {
83     fprintf(stderr, "Cannot open /dev/tty as write-only: %s\n", strerror(errno));
84     fflush(stderr);
85     return TEST_SKIP;
86   }
87 #endif
88 
89   ASSERT_GE(ttyin_fd, 0);
90   ASSERT_GE(ttyout_fd, 0);
91 
92   ASSERT_EQ(UV_UNKNOWN_HANDLE, uv_guess_handle(-1));
93 
94   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
95   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyout_fd));
96 
97   r = uv_tty_init(loop, &tty_in, ttyin_fd, 1);  /* Readable. */
98   ASSERT_OK(r);
99   ASSERT(uv_is_readable((uv_stream_t*) &tty_in));
100   ASSERT(!uv_is_writable((uv_stream_t*) &tty_in));
101 
102   r = uv_tty_init(loop, &tty_out, ttyout_fd, 0);  /* Writable. */
103   ASSERT_OK(r);
104   ASSERT(!uv_is_readable((uv_stream_t*) &tty_out));
105   ASSERT(uv_is_writable((uv_stream_t*) &tty_out));
106 
107   r = uv_tty_get_winsize(&tty_out, &width, &height);
108   ASSERT_OK(r);
109 
110   printf("width=%d height=%d\n", width, height);
111 
112   if (width == 0 && height == 0) {
113    /* Some environments such as containers or Jenkins behave like this
114     * sometimes */
115     MAKE_VALGRIND_HAPPY(loop);
116     return TEST_SKIP;
117   }
118 
119   ASSERT_GT(width, 0);
120   ASSERT_GT(height, 0);
121 
122   /* Turn on raw mode. */
123   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
124   ASSERT_OK(r);
125 
126   /* Turn off raw mode. */
127   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_NORMAL);
128   ASSERT_OK(r);
129 
130   /* Calling uv_tty_reset_mode() repeatedly should not clobber errno. */
131   errno = 0;
132   ASSERT_OK(uv_tty_reset_mode());
133   ASSERT_OK(uv_tty_reset_mode());
134   ASSERT_OK(uv_tty_reset_mode());
135   ASSERT_OK(errno);
136 
137   /* TODO check the actual mode! */
138 
139   uv_close((uv_handle_t*) &tty_in, NULL);
140   uv_close((uv_handle_t*) &tty_out, NULL);
141 
142   uv_run(loop, UV_RUN_DEFAULT);
143 
144   MAKE_VALGRIND_HAPPY(uv_default_loop());
145   return 0;
146 }
147 
148 
149 #ifdef _WIN32
tty_raw_alloc(uv_handle_t * handle,size_t size,uv_buf_t * buf)150 static void tty_raw_alloc(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
151   buf->base = malloc(size);
152   buf->len = size;
153 }
154 
tty_raw_read(uv_stream_t * tty_in,ssize_t nread,const uv_buf_t * buf)155 static void tty_raw_read(uv_stream_t* tty_in, ssize_t nread, const uv_buf_t* buf) {
156   if (nread > 0) {
157     ASSERT_EQ(1, nread );
158     ASSERT_EQ(buf->base[0], ' ');
159     uv_close((uv_handle_t*) tty_in, NULL);
160   } else {
161     ASSERT_OK(nread);
162   }
163 }
164 
TEST_IMPL(tty_raw)165 TEST_IMPL(tty_raw) {
166   int r;
167   int ttyin_fd;
168   uv_tty_t tty_in;
169   uv_loop_t* loop = uv_default_loop();
170   HANDLE handle;
171   INPUT_RECORD record;
172   DWORD written;
173 
174   /* Make sure we have an FD that refers to a tty */
175   handle = CreateFileA("conin$",
176                        GENERIC_READ | GENERIC_WRITE,
177                        FILE_SHARE_READ | FILE_SHARE_WRITE,
178                        NULL,
179                        OPEN_EXISTING,
180                        FILE_ATTRIBUTE_NORMAL,
181                        NULL);
182   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
183   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
184   ASSERT_GE(ttyin_fd, 0);
185   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
186 
187   r = uv_tty_init(loop, &tty_in, ttyin_fd, 1);  /* Readable. */
188   ASSERT_OK(r);
189   ASSERT(uv_is_readable((uv_stream_t*) &tty_in));
190   ASSERT(!uv_is_writable((uv_stream_t*) &tty_in));
191 
192   r = uv_read_start((uv_stream_t*)&tty_in, tty_raw_alloc, tty_raw_read);
193   ASSERT_OK(r);
194 
195   /* Give uv_tty_line_read_thread time to block on ReadConsoleW */
196   Sleep(100);
197 
198   /* Turn on raw mode. */
199   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
200   ASSERT_OK(r);
201 
202   /* Write ' ' that should be read in raw mode */
203   record.EventType = KEY_EVENT;
204   record.Event.KeyEvent.bKeyDown = TRUE;
205   record.Event.KeyEvent.wRepeatCount = 1;
206   record.Event.KeyEvent.wVirtualKeyCode = VK_SPACE;
207   record.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(VK_SPACE, MAPVK_VK_TO_VSC);
208   record.Event.KeyEvent.uChar.UnicodeChar = L' ';
209   record.Event.KeyEvent.dwControlKeyState = 0;
210   WriteConsoleInputW(handle, &record, 1, &written);
211 
212   uv_run(loop, UV_RUN_DEFAULT);
213 
214   MAKE_VALGRIND_HAPPY(loop);
215   return 0;
216 }
217 
TEST_IMPL(tty_empty_write)218 TEST_IMPL(tty_empty_write) {
219   int r;
220   int ttyout_fd;
221   uv_tty_t tty_out;
222   char dummy[1];
223   uv_buf_t bufs[1];
224   uv_loop_t* loop;
225 
226   /* Make sure we have an FD that refers to a tty */
227   HANDLE handle;
228 
229   loop = uv_default_loop();
230 
231   handle = CreateFileA("conout$",
232                        GENERIC_READ | GENERIC_WRITE,
233                        FILE_SHARE_READ | FILE_SHARE_WRITE,
234                        NULL,
235                        OPEN_EXISTING,
236                        FILE_ATTRIBUTE_NORMAL,
237                        NULL);
238   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
239   ttyout_fd = _open_osfhandle((intptr_t) handle, 0);
240 
241   ASSERT_GE(ttyout_fd, 0);
242 
243   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyout_fd));
244 
245   r = uv_tty_init(loop, &tty_out, ttyout_fd, 0);  /* Writable. */
246   ASSERT_OK(r);
247   ASSERT(!uv_is_readable((uv_stream_t*) &tty_out));
248   ASSERT(uv_is_writable((uv_stream_t*) &tty_out));
249 
250   bufs[0].len = 0;
251   bufs[0].base = &dummy[0];
252 
253   r = uv_try_write((uv_stream_t*) &tty_out, bufs, 1);
254   ASSERT_OK(r);
255 
256   uv_close((uv_handle_t*) &tty_out, NULL);
257 
258   uv_run(loop, UV_RUN_DEFAULT);
259 
260   MAKE_VALGRIND_HAPPY(loop);
261   return 0;
262 }
263 
TEST_IMPL(tty_large_write)264 TEST_IMPL(tty_large_write) {
265   int r;
266   int ttyout_fd;
267   uv_tty_t tty_out;
268   char dummy[10000];
269   uv_buf_t bufs[1];
270   uv_loop_t* loop;
271 
272   /* Make sure we have an FD that refers to a tty */
273   HANDLE handle;
274 
275   loop = uv_default_loop();
276 
277   handle = CreateFileA("conout$",
278                        GENERIC_READ | GENERIC_WRITE,
279                        FILE_SHARE_READ | FILE_SHARE_WRITE,
280                        NULL,
281                        OPEN_EXISTING,
282                        FILE_ATTRIBUTE_NORMAL,
283                        NULL);
284   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
285   ttyout_fd = _open_osfhandle((intptr_t) handle, 0);
286 
287   ASSERT_GE(ttyout_fd, 0);
288 
289   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyout_fd));
290 
291   r = uv_tty_init(loop, &tty_out, ttyout_fd, 0);  /* Writable. */
292   ASSERT_OK(r);
293 
294   memset(dummy, '.', sizeof(dummy) - 1);
295   dummy[sizeof(dummy) - 1] = '\n';
296 
297   bufs[0] = uv_buf_init(dummy, sizeof(dummy));
298 
299   r = uv_try_write((uv_stream_t*) &tty_out, bufs, 1);
300   ASSERT_EQ(10000, r);
301 
302   uv_close((uv_handle_t*) &tty_out, NULL);
303 
304   uv_run(loop, UV_RUN_DEFAULT);
305 
306   MAKE_VALGRIND_HAPPY(loop);
307   return 0;
308 }
309 
TEST_IMPL(tty_raw_cancel)310 TEST_IMPL(tty_raw_cancel) {
311   int r;
312   int ttyin_fd;
313   uv_tty_t tty_in;
314   HANDLE handle;
315 
316   /* Make sure we have an FD that refers to a tty */
317   handle = CreateFileA("conin$",
318                        GENERIC_READ | GENERIC_WRITE,
319                        FILE_SHARE_READ | FILE_SHARE_WRITE,
320                        NULL,
321                        OPEN_EXISTING,
322                        FILE_ATTRIBUTE_NORMAL,
323                        NULL);
324   ASSERT_PTR_NE(handle, INVALID_HANDLE_VALUE);
325   ttyin_fd = _open_osfhandle((intptr_t) handle, 0);
326   ASSERT_GE(ttyin_fd, 0);
327   ASSERT_EQ(UV_TTY, uv_guess_handle(ttyin_fd));
328 
329   r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1);  /* Readable. */
330   ASSERT_OK(r);
331   r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW);
332   ASSERT_OK(r);
333   r = uv_read_start((uv_stream_t*)&tty_in, tty_raw_alloc, tty_raw_read);
334   ASSERT_OK(r);
335 
336   r = uv_read_stop((uv_stream_t*) &tty_in);
337   ASSERT_OK(r);
338 
339   MAKE_VALGRIND_HAPPY(uv_default_loop());
340   return 0;
341 }
342 #endif
343 
344 
TEST_IMPL(tty_file)345 TEST_IMPL(tty_file) {
346 #ifndef _WIN32
347   uv_loop_t loop;
348   uv_tty_t tty;
349   uv_tty_t tty_ro;
350   uv_tty_t tty_wo;
351   int fd;
352 
353   ASSERT_OK(uv_loop_init(&loop));
354 
355   fd = open("test/fixtures/empty_file", O_RDONLY);
356   if (fd != -1) {
357     ASSERT_EQ(UV_EINVAL, uv_tty_init(&loop, &tty, fd, 1));
358     ASSERT_OK(close(fd));
359     /* test EBADF handling */
360     ASSERT_EQ(UV_EINVAL, uv_tty_init(&loop, &tty, fd, 1));
361   }
362 
363 /* Bug on AIX where '/dev/random' returns 1 from isatty() */
364 #ifndef _AIX
365   fd = open("/dev/random", O_RDONLY);
366   if (fd != -1) {
367     ASSERT_EQ(UV_EINVAL, uv_tty_init(&loop, &tty, fd, 1));
368     ASSERT_OK(close(fd));
369   }
370 #endif /* _AIX */
371 
372   fd = open("/dev/zero", O_RDONLY);
373   if (fd != -1) {
374     ASSERT_EQ(UV_EINVAL, uv_tty_init(&loop, &tty, fd, 1));
375     ASSERT_OK(close(fd));
376   }
377 
378   fd = open("/dev/tty", O_RDWR);
379   if (fd != -1) {
380     ASSERT_OK(uv_tty_init(&loop, &tty, fd, 1));
381     ASSERT_OK(close(fd)); /* TODO: it's indeterminate who owns fd now */
382     ASSERT(uv_is_readable((uv_stream_t*) &tty));
383     ASSERT(uv_is_writable((uv_stream_t*) &tty));
384     uv_close((uv_handle_t*) &tty, NULL);
385     ASSERT(!uv_is_readable((uv_stream_t*) &tty));
386     ASSERT(!uv_is_writable((uv_stream_t*) &tty));
387   }
388 
389   fd = open("/dev/tty", O_RDONLY);
390   if (fd != -1) {
391     ASSERT_OK(uv_tty_init(&loop, &tty_ro, fd, 1));
392     ASSERT_OK(close(fd)); /* TODO: it's indeterminate who owns fd now */
393     ASSERT(uv_is_readable((uv_stream_t*) &tty_ro));
394     ASSERT(!uv_is_writable((uv_stream_t*) &tty_ro));
395     uv_close((uv_handle_t*) &tty_ro, NULL);
396     ASSERT(!uv_is_readable((uv_stream_t*) &tty_ro));
397     ASSERT(!uv_is_writable((uv_stream_t*) &tty_ro));
398   }
399 
400   fd = open("/dev/tty", O_WRONLY);
401   if (fd != -1) {
402     ASSERT_OK(uv_tty_init(&loop, &tty_wo, fd, 0));
403     ASSERT_OK(close(fd)); /* TODO: it's indeterminate who owns fd now */
404     ASSERT(!uv_is_readable((uv_stream_t*) &tty_wo));
405     ASSERT(uv_is_writable((uv_stream_t*) &tty_wo));
406     uv_close((uv_handle_t*) &tty_wo, NULL);
407     ASSERT(!uv_is_readable((uv_stream_t*) &tty_wo));
408     ASSERT(!uv_is_writable((uv_stream_t*) &tty_wo));
409   }
410 
411 
412   ASSERT_OK(uv_run(&loop, UV_RUN_DEFAULT));
413 
414   MAKE_VALGRIND_HAPPY(&loop);
415 #endif
416   return 0;
417 }
418 
TEST_IMPL(tty_pty)419 TEST_IMPL(tty_pty) {
420 /* TODO(gengjiawen): Fix test on QEMU. */
421 #if defined(__QEMU__)
422   RETURN_SKIP("Test does not currently work in QEMU");
423 #endif
424 #if defined(__ASAN__)
425   RETURN_SKIP("Test does not currently work in ASAN");
426 #endif
427 
428 #if defined(__APPLE__)                            || \
429     defined(__DragonFly__)                        || \
430     defined(__FreeBSD__)                          || \
431     (defined(__linux__) && !defined(__ANDROID__)) || \
432     defined(__NetBSD__)                           || \
433     defined(__OpenBSD__)
434   int master_fd, slave_fd, r;
435   struct winsize w;
436   uv_loop_t loop;
437   uv_tty_t master_tty, slave_tty;
438 
439   ASSERT_OK(uv_loop_init(&loop));
440 
441   r = openpty(&master_fd, &slave_fd, NULL, NULL, &w);
442   if (r != 0)
443     RETURN_SKIP("No pty available, skipping.");
444 
445   ASSERT_OK(uv_tty_init(&loop, &slave_tty, slave_fd, 0));
446   ASSERT_OK(uv_tty_init(&loop, &master_tty, master_fd, 0));
447   ASSERT(uv_is_readable((uv_stream_t*) &slave_tty));
448   ASSERT(uv_is_writable((uv_stream_t*) &slave_tty));
449   ASSERT(uv_is_readable((uv_stream_t*) &master_tty));
450   ASSERT(uv_is_writable((uv_stream_t*) &master_tty));
451   /* Check if the file descriptor was reopened. If it is,
452    * UV_HANDLE_BLOCKING_WRITES (value 0x100000) isn't set on flags.
453    */
454   ASSERT_OK((slave_tty.flags & 0x100000));
455   /* The master_fd of a pty should never be reopened.
456    */
457   ASSERT(master_tty.flags & 0x100000);
458   ASSERT_OK(close(slave_fd));
459   uv_close((uv_handle_t*) &slave_tty, NULL);
460   ASSERT_OK(close(master_fd));
461   uv_close((uv_handle_t*) &master_tty, NULL);
462 
463   ASSERT_OK(uv_run(&loop, UV_RUN_DEFAULT));
464 
465   MAKE_VALGRIND_HAPPY(&loop);
466 #endif
467   return 0;
468 }
469