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(__GLIBC__)) && !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 | |
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(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(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(ttyin_fd >= 0); |
90 | ASSERT(ttyout_fd >= 0); |
91 | |
92 | ASSERT(UV_UNKNOWN_HANDLE == uv_guess_handle(-1)); |
93 | |
94 | ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); |
95 | ASSERT(UV_TTY == uv_guess_handle(ttyout_fd)); |
96 | |
97 | r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ |
98 | ASSERT(r == 0); |
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(uv_default_loop(), &tty_out, ttyout_fd, 0); /* Writable. */ |
103 | ASSERT(r == 0); |
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(r == 0); |
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(); |
116 | return TEST_SKIP; |
117 | } |
118 | |
119 | /* |
120 | * Is it a safe assumption that most people have terminals larger than |
121 | * 10x10? |
122 | */ |
123 | ASSERT(width > 10); |
124 | ASSERT(height > 10); |
125 | |
126 | /* Turn on raw mode. */ |
127 | r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); |
128 | ASSERT(r == 0); |
129 | |
130 | /* Turn off raw mode. */ |
131 | r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_NORMAL); |
132 | ASSERT(r == 0); |
133 | |
134 | /* Calling uv_tty_reset_mode() repeatedly should not clobber errno. */ |
135 | errno = 0; |
136 | ASSERT(0 == uv_tty_reset_mode()); |
137 | ASSERT(0 == uv_tty_reset_mode()); |
138 | ASSERT(0 == uv_tty_reset_mode()); |
139 | ASSERT(0 == errno); |
140 | |
141 | /* TODO check the actual mode! */ |
142 | |
143 | uv_close((uv_handle_t*) &tty_in, NULL); |
144 | uv_close((uv_handle_t*) &tty_out, NULL); |
145 | |
146 | uv_run(loop, UV_RUN_DEFAULT); |
147 | |
148 | MAKE_VALGRIND_HAPPY(); |
149 | return 0; |
150 | } |
151 | |
152 | |
153 | #ifdef _WIN32 |
154 | static void tty_raw_alloc(uv_handle_t* handle, size_t size, uv_buf_t* buf) { |
155 | buf->base = malloc(size); |
156 | buf->len = size; |
157 | } |
158 | |
159 | static void tty_raw_read(uv_stream_t* tty_in, ssize_t nread, const uv_buf_t* buf) { |
160 | if (nread > 0) { |
161 | ASSERT(nread == 1); |
162 | ASSERT(buf->base[0] == ' '); |
163 | uv_close((uv_handle_t*) tty_in, NULL); |
164 | } else { |
165 | ASSERT(nread == 0); |
166 | } |
167 | } |
168 | |
169 | TEST_IMPL(tty_raw) { |
170 | int r; |
171 | int ttyin_fd; |
172 | uv_tty_t tty_in; |
173 | uv_loop_t* loop = uv_default_loop(); |
174 | HANDLE handle; |
175 | INPUT_RECORD record; |
176 | DWORD written; |
177 | |
178 | /* Make sure we have an FD that refers to a tty */ |
179 | handle = CreateFileA("conin$" , |
180 | GENERIC_READ | GENERIC_WRITE, |
181 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
182 | NULL, |
183 | OPEN_EXISTING, |
184 | FILE_ATTRIBUTE_NORMAL, |
185 | NULL); |
186 | ASSERT(handle != INVALID_HANDLE_VALUE); |
187 | ttyin_fd = _open_osfhandle((intptr_t) handle, 0); |
188 | ASSERT(ttyin_fd >= 0); |
189 | ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); |
190 | |
191 | r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ |
192 | ASSERT(r == 0); |
193 | ASSERT(uv_is_readable((uv_stream_t*) &tty_in)); |
194 | ASSERT(!uv_is_writable((uv_stream_t*) &tty_in)); |
195 | |
196 | r = uv_read_start((uv_stream_t*)&tty_in, tty_raw_alloc, tty_raw_read); |
197 | ASSERT(r == 0); |
198 | |
199 | /* Give uv_tty_line_read_thread time to block on ReadConsoleW */ |
200 | Sleep(100); |
201 | |
202 | /* Turn on raw mode. */ |
203 | r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); |
204 | ASSERT(r == 0); |
205 | |
206 | /* Write ' ' that should be read in raw mode */ |
207 | record.EventType = KEY_EVENT; |
208 | record.Event.KeyEvent.bKeyDown = TRUE; |
209 | record.Event.KeyEvent.wRepeatCount = 1; |
210 | record.Event.KeyEvent.wVirtualKeyCode = VK_SPACE; |
211 | record.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(VK_SPACE, MAPVK_VK_TO_VSC); |
212 | record.Event.KeyEvent.uChar.UnicodeChar = L' '; |
213 | record.Event.KeyEvent.dwControlKeyState = 0; |
214 | WriteConsoleInputW(handle, &record, 1, &written); |
215 | |
216 | uv_run(loop, UV_RUN_DEFAULT); |
217 | |
218 | MAKE_VALGRIND_HAPPY(); |
219 | return 0; |
220 | } |
221 | |
222 | TEST_IMPL(tty_empty_write) { |
223 | int r; |
224 | int ttyout_fd; |
225 | uv_tty_t tty_out; |
226 | char dummy[1]; |
227 | uv_buf_t bufs[1]; |
228 | uv_loop_t* loop; |
229 | |
230 | /* Make sure we have an FD that refers to a tty */ |
231 | HANDLE handle; |
232 | |
233 | loop = uv_default_loop(); |
234 | |
235 | handle = CreateFileA("conout$" , |
236 | GENERIC_READ | GENERIC_WRITE, |
237 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
238 | NULL, |
239 | OPEN_EXISTING, |
240 | FILE_ATTRIBUTE_NORMAL, |
241 | NULL); |
242 | ASSERT(handle != INVALID_HANDLE_VALUE); |
243 | ttyout_fd = _open_osfhandle((intptr_t) handle, 0); |
244 | |
245 | ASSERT(ttyout_fd >= 0); |
246 | |
247 | ASSERT(UV_TTY == uv_guess_handle(ttyout_fd)); |
248 | |
249 | r = uv_tty_init(uv_default_loop(), &tty_out, ttyout_fd, 0); /* Writable. */ |
250 | ASSERT(r == 0); |
251 | ASSERT(!uv_is_readable((uv_stream_t*) &tty_out)); |
252 | ASSERT(uv_is_writable((uv_stream_t*) &tty_out)); |
253 | |
254 | bufs[0].len = 0; |
255 | bufs[0].base = &dummy[0]; |
256 | |
257 | r = uv_try_write((uv_stream_t*) &tty_out, bufs, 1); |
258 | ASSERT(r == 0); |
259 | |
260 | uv_close((uv_handle_t*) &tty_out, NULL); |
261 | |
262 | uv_run(loop, UV_RUN_DEFAULT); |
263 | |
264 | MAKE_VALGRIND_HAPPY(); |
265 | return 0; |
266 | } |
267 | |
268 | TEST_IMPL(tty_large_write) { |
269 | int r; |
270 | int ttyout_fd; |
271 | uv_tty_t tty_out; |
272 | char dummy[10000]; |
273 | uv_buf_t bufs[1]; |
274 | uv_loop_t* loop; |
275 | |
276 | /* Make sure we have an FD that refers to a tty */ |
277 | HANDLE handle; |
278 | |
279 | loop = uv_default_loop(); |
280 | |
281 | handle = CreateFileA("conout$" , |
282 | GENERIC_READ | GENERIC_WRITE, |
283 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
284 | NULL, |
285 | OPEN_EXISTING, |
286 | FILE_ATTRIBUTE_NORMAL, |
287 | NULL); |
288 | ASSERT(handle != INVALID_HANDLE_VALUE); |
289 | ttyout_fd = _open_osfhandle((intptr_t) handle, 0); |
290 | |
291 | ASSERT(ttyout_fd >= 0); |
292 | |
293 | ASSERT(UV_TTY == uv_guess_handle(ttyout_fd)); |
294 | |
295 | r = uv_tty_init(uv_default_loop(), &tty_out, ttyout_fd, 0); /* Writable. */ |
296 | ASSERT(r == 0); |
297 | |
298 | memset(dummy, '.', sizeof(dummy) - 1); |
299 | dummy[sizeof(dummy) - 1] = '\n'; |
300 | |
301 | bufs[0] = uv_buf_init(dummy, sizeof(dummy)); |
302 | |
303 | r = uv_try_write((uv_stream_t*) &tty_out, bufs, 1); |
304 | ASSERT(r == 10000); |
305 | |
306 | uv_close((uv_handle_t*) &tty_out, NULL); |
307 | |
308 | uv_run(loop, UV_RUN_DEFAULT); |
309 | |
310 | MAKE_VALGRIND_HAPPY(); |
311 | return 0; |
312 | } |
313 | |
314 | TEST_IMPL(tty_raw_cancel) { |
315 | int r; |
316 | int ttyin_fd; |
317 | uv_tty_t tty_in; |
318 | HANDLE handle; |
319 | |
320 | /* Make sure we have an FD that refers to a tty */ |
321 | handle = CreateFileA("conin$" , |
322 | GENERIC_READ | GENERIC_WRITE, |
323 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
324 | NULL, |
325 | OPEN_EXISTING, |
326 | FILE_ATTRIBUTE_NORMAL, |
327 | NULL); |
328 | ASSERT(handle != INVALID_HANDLE_VALUE); |
329 | ttyin_fd = _open_osfhandle((intptr_t) handle, 0); |
330 | ASSERT(ttyin_fd >= 0); |
331 | ASSERT(UV_TTY == uv_guess_handle(ttyin_fd)); |
332 | |
333 | r = uv_tty_init(uv_default_loop(), &tty_in, ttyin_fd, 1); /* Readable. */ |
334 | ASSERT(r == 0); |
335 | r = uv_tty_set_mode(&tty_in, UV_TTY_MODE_RAW); |
336 | ASSERT(r == 0); |
337 | r = uv_read_start((uv_stream_t*)&tty_in, tty_raw_alloc, tty_raw_read); |
338 | ASSERT(r == 0); |
339 | |
340 | r = uv_read_stop((uv_stream_t*) &tty_in); |
341 | ASSERT(r == 0); |
342 | |
343 | MAKE_VALGRIND_HAPPY(); |
344 | return 0; |
345 | } |
346 | #endif |
347 | |
348 | |
349 | TEST_IMPL(tty_file) { |
350 | #ifndef _WIN32 |
351 | uv_loop_t loop; |
352 | uv_tty_t tty; |
353 | uv_tty_t tty_ro; |
354 | uv_tty_t tty_wo; |
355 | int fd; |
356 | |
357 | ASSERT(0 == uv_loop_init(&loop)); |
358 | |
359 | fd = open("test/fixtures/empty_file" , O_RDONLY); |
360 | if (fd != -1) { |
361 | ASSERT(UV_EINVAL == uv_tty_init(&loop, &tty, fd, 1)); |
362 | ASSERT(0 == close(fd)); |
363 | } |
364 | |
365 | /* Bug on AIX where '/dev/random' returns 1 from isatty() */ |
366 | #ifndef _AIX |
367 | fd = open("/dev/random" , O_RDONLY); |
368 | if (fd != -1) { |
369 | ASSERT(UV_EINVAL == uv_tty_init(&loop, &tty, fd, 1)); |
370 | ASSERT(0 == close(fd)); |
371 | } |
372 | #endif /* _AIX */ |
373 | |
374 | fd = open("/dev/zero" , O_RDONLY); |
375 | if (fd != -1) { |
376 | ASSERT(UV_EINVAL == uv_tty_init(&loop, &tty, fd, 1)); |
377 | ASSERT(0 == close(fd)); |
378 | } |
379 | |
380 | fd = open("/dev/tty" , O_RDWR); |
381 | if (fd != -1) { |
382 | ASSERT(0 == uv_tty_init(&loop, &tty, fd, 1)); |
383 | ASSERT(0 == close(fd)); /* TODO: it's indeterminate who owns fd now */ |
384 | ASSERT(uv_is_readable((uv_stream_t*) &tty)); |
385 | ASSERT(uv_is_writable((uv_stream_t*) &tty)); |
386 | uv_close((uv_handle_t*) &tty, NULL); |
387 | ASSERT(!uv_is_readable((uv_stream_t*) &tty)); |
388 | ASSERT(!uv_is_writable((uv_stream_t*) &tty)); |
389 | } |
390 | |
391 | fd = open("/dev/tty" , O_RDONLY); |
392 | if (fd != -1) { |
393 | ASSERT(0 == uv_tty_init(&loop, &tty_ro, fd, 1)); |
394 | ASSERT(0 == close(fd)); /* TODO: it's indeterminate who owns fd now */ |
395 | ASSERT(uv_is_readable((uv_stream_t*) &tty_ro)); |
396 | ASSERT(!uv_is_writable((uv_stream_t*) &tty_ro)); |
397 | uv_close((uv_handle_t*) &tty_ro, NULL); |
398 | ASSERT(!uv_is_readable((uv_stream_t*) &tty_ro)); |
399 | ASSERT(!uv_is_writable((uv_stream_t*) &tty_ro)); |
400 | } |
401 | |
402 | fd = open("/dev/tty" , O_WRONLY); |
403 | if (fd != -1) { |
404 | ASSERT(0 == uv_tty_init(&loop, &tty_wo, fd, 0)); |
405 | ASSERT(0 == close(fd)); /* TODO: it's indeterminate who owns fd now */ |
406 | ASSERT(!uv_is_readable((uv_stream_t*) &tty_wo)); |
407 | ASSERT(uv_is_writable((uv_stream_t*) &tty_wo)); |
408 | uv_close((uv_handle_t*) &tty_wo, NULL); |
409 | ASSERT(!uv_is_readable((uv_stream_t*) &tty_wo)); |
410 | ASSERT(!uv_is_writable((uv_stream_t*) &tty_wo)); |
411 | } |
412 | |
413 | |
414 | ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); |
415 | ASSERT(0 == uv_loop_close(&loop)); |
416 | |
417 | MAKE_VALGRIND_HAPPY(); |
418 | #endif |
419 | return 0; |
420 | } |
421 | |
422 | TEST_IMPL(tty_pty) { |
423 | #if defined(__APPLE__) || \ |
424 | defined(__DragonFly__) || \ |
425 | defined(__FreeBSD__) || \ |
426 | defined(__FreeBSD_kernel__) || \ |
427 | (defined(__linux__) && !defined(__ANDROID__)) || \ |
428 | defined(__NetBSD__) || \ |
429 | defined(__OpenBSD__) |
430 | int master_fd, slave_fd, r; |
431 | struct winsize w; |
432 | uv_loop_t loop; |
433 | uv_tty_t master_tty, slave_tty; |
434 | |
435 | ASSERT(0 == uv_loop_init(&loop)); |
436 | |
437 | r = openpty(&master_fd, &slave_fd, NULL, NULL, &w); |
438 | if (r != 0) |
439 | RETURN_SKIP("No pty available, skipping." ); |
440 | |
441 | ASSERT(0 == uv_tty_init(&loop, &slave_tty, slave_fd, 0)); |
442 | ASSERT(0 == uv_tty_init(&loop, &master_tty, master_fd, 0)); |
443 | ASSERT(uv_is_readable((uv_stream_t*) &slave_tty)); |
444 | ASSERT(uv_is_writable((uv_stream_t*) &slave_tty)); |
445 | ASSERT(uv_is_readable((uv_stream_t*) &master_tty)); |
446 | ASSERT(uv_is_writable((uv_stream_t*) &master_tty)); |
447 | /* Check if the file descriptor was reopened. If it is, |
448 | * UV_HANDLE_BLOCKING_WRITES (value 0x100000) isn't set on flags. |
449 | */ |
450 | ASSERT(0 == (slave_tty.flags & 0x100000)); |
451 | /* The master_fd of a pty should never be reopened. |
452 | */ |
453 | ASSERT(master_tty.flags & 0x100000); |
454 | ASSERT(0 == close(slave_fd)); |
455 | uv_close((uv_handle_t*) &slave_tty, NULL); |
456 | ASSERT(0 == close(master_fd)); |
457 | uv_close((uv_handle_t*) &master_tty, NULL); |
458 | |
459 | ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); |
460 | |
461 | MAKE_VALGRIND_HAPPY(); |
462 | #endif |
463 | return 0; |
464 | } |
465 | |