[netcore] Implement Thread.GetCurrentProcessorId (#18450)
[mono-project.git] / mono / metadata / console-unix.c
blobcc9e543ff30d7ee56787222139a826708ed88363
1 /**
2 * \file
3 * ConsoleDriver internal calls for Unix systems.
5 * Author:
6 * Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 * Copyright (C) 2005-2009 Novell, Inc. (http://www.novell.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11 #include <config.h>
12 #include <glib.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 #include <signal.h>
18 #ifdef HAVE_SYS_SELECT_H
19 # include <sys/select.h>
20 #endif
21 #ifdef HAVE_SYS_TIME_H
22 # include <sys/time.h>
23 #endif
24 #include <sys/types.h>
25 #ifdef HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28 #include <mono/metadata/appdomain.h>
29 #include <mono/metadata/object-internals.h>
30 #include <mono/metadata/class-internals.h>
31 #include <mono/metadata/domain-internals.h>
32 #include <mono/metadata/gc-internals.h>
33 #include <mono/metadata/metadata.h>
34 #include <mono/metadata/threadpool.h>
35 #include <mono/utils/mono-signal-handler.h>
36 #include <mono/utils/mono-proclib.h>
37 #include <mono/utils/w32api.h>
38 #include <mono/utils/mono-errno.h>
39 #include <mono/metadata/console-io.h>
40 #include <mono/metadata/exception.h>
41 #include "icall-decl.h"
43 #ifndef ENABLE_NETCORE
45 /* On solaris, curses.h must come before both termios.h and term.h */
46 #ifdef HAVE_CURSES_H
47 # include <curses.h>
48 #endif
49 #ifdef HAVE_TERMIOS_H
50 # include <termios.h>
51 #endif
52 #ifdef HAVE_TERM_H
53 # include <term.h>
54 #endif
56 /* Needed for FIONREAD under solaris */
57 #ifdef HAVE_SYS_FILIO_H
58 # include <sys/filio.h>
59 #endif
60 #ifdef HAVE_SYS_IOCTL_H
61 # include <sys/ioctl.h>
62 #endif
64 static gboolean setup_finished;
65 static gboolean atexit_called;
67 /* The string used to return the terminal to its previous state */
68 static gchar *teardown_str;
70 /* The string used to set the terminal into keypad xmit mode after SIGCONT is received */
71 static gchar *keypad_xmit_str;
73 #ifdef HAVE_TERMIOS_H
74 /* This is the last state used by Mono, used after a CONT signal is received */
75 static struct termios mono_attr;
76 #endif
78 /* static void console_restore_signal_handlers (void); */
79 static void console_set_signal_handlers (void);
81 static GENERATE_TRY_GET_CLASS_WITH_CACHE (console, "System", "Console");
83 void
84 mono_console_init (void)
86 int fd;
88 /* Make sure the standard file descriptors are opened */
89 fd = open ("/dev/null", O_RDWR);
90 while (fd >= 0 && fd < 3) {
91 fd = open ("/dev/null", O_RDWR);
93 close (fd);
96 static struct termios initial_attr;
98 MonoBoolean
99 ves_icall_System_ConsoleDriver_Isatty (HANDLE handle, MonoError* error)
101 return isatty (GPOINTER_TO_INT (handle));
104 static MonoBoolean
105 set_property (gint property, gboolean value)
107 struct termios attr;
108 gboolean callset = FALSE;
109 gboolean check;
111 if (tcgetattr (STDIN_FILENO, &attr) == -1)
112 return FALSE;
114 check = (attr.c_lflag & property) != 0;
115 if ((value || check) && !(value && check)) {
116 callset = TRUE;
117 if (value)
118 attr.c_lflag |= property;
119 else
120 attr.c_lflag &= ~property;
123 if (!callset)
124 return TRUE;
126 if (tcsetattr (STDIN_FILENO, TCSANOW, &attr) == -1)
127 return FALSE;
129 mono_attr = attr;
130 return TRUE;
133 MonoBoolean
134 ves_icall_System_ConsoleDriver_SetEcho (MonoBoolean want_echo, MonoError* error)
136 return set_property (ECHO, want_echo);
139 MonoBoolean
140 ves_icall_System_ConsoleDriver_SetBreak (MonoBoolean want_break, MonoError* error)
142 return set_property (IGNBRK, !want_break);
145 gint32
146 ves_icall_System_ConsoleDriver_InternalKeyAvailable (gint32 timeout, MonoError* error)
148 fd_set rfds;
149 struct timeval tv;
150 struct timeval *tvptr;
151 div_t divvy;
152 int ret, nbytes;
154 do {
155 FD_ZERO (&rfds);
156 FD_SET (STDIN_FILENO, &rfds);
157 if (timeout >= 0) {
158 divvy = div (timeout, 1000);
159 tv.tv_sec = divvy.quot;
160 tv.tv_usec = divvy.rem;
161 tvptr = &tv;
162 } else {
163 tvptr = NULL;
165 ret = select (STDIN_FILENO + 1, &rfds, NULL, NULL, tvptr);
166 } while (ret == -1 && errno == EINTR);
168 if (ret > 0) {
169 nbytes = 0;
170 ret = ioctl (STDIN_FILENO, FIONREAD, &nbytes);
171 if (ret >= 0)
172 ret = nbytes;
175 return (ret > 0) ? ret : 0;
178 static gint32 cols_and_lines;
180 #ifdef TIOCGWINSZ
181 static int
182 terminal_get_dimensions (void)
184 struct winsize ws;
185 int ret;
186 int save_errno = errno;
188 if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) == 0){
189 ret = (ws.ws_col << 16) | ws.ws_row;
190 mono_set_errno (save_errno);
191 return ret;
193 return -1;
195 #else
196 static int
197 terminal_get_dimensions (void)
199 return -1;
201 #endif
203 static void
204 tty_teardown (void)
206 int unused G_GNUC_UNUSED;
208 if (!setup_finished)
209 return;
211 if (teardown_str != NULL) {
212 unused = write (STDOUT_FILENO, teardown_str, strlen (teardown_str));
213 g_free (teardown_str);
214 teardown_str = NULL;
217 tcflush (STDIN_FILENO, TCIFLUSH);
218 tcsetattr (STDIN_FILENO, TCSANOW, &initial_attr);
219 set_property (ECHO, TRUE);
220 setup_finished = FALSE;
223 static void
224 do_console_cancel_event (void)
226 static MonoMethod *System_Console_DoConsoleCancelEventBackground_method = (MonoMethod*)(intptr_t)-1;
227 ERROR_DECL (error);
229 if (mono_class_try_get_console_class () == NULL)
230 return;
232 if (System_Console_DoConsoleCancelEventBackground_method == (gpointer)(intptr_t)-1) {
233 System_Console_DoConsoleCancelEventBackground_method = mono_class_get_method_from_name_checked (mono_class_try_get_console_class (), "DoConsoleCancelEventInBackground", 0, 0, error);
234 mono_error_assert_ok (error);
236 if (System_Console_DoConsoleCancelEventBackground_method == NULL)
237 return;
239 mono_runtime_invoke_checked (System_Console_DoConsoleCancelEventBackground_method, NULL, NULL, error);
240 mono_error_assert_ok (error);
243 static int need_cancel = FALSE;
244 /* this is executed from the finalizer thread */
245 void
246 mono_console_handle_async_ops (void)
248 if (need_cancel) {
249 need_cancel = FALSE;
250 do_console_cancel_event ();
254 static gboolean in_sigint;
256 MONO_SIG_HANDLER_FUNC (static, sigint_handler)
258 int save_errno;
260 if (in_sigint)
261 return;
263 in_sigint = TRUE;
264 save_errno = errno;
265 need_cancel = TRUE;
266 mono_gc_finalize_notify ();
267 mono_set_errno (save_errno);
268 in_sigint = FALSE;
271 static struct sigaction save_sigcont, save_sigwinch;
273 #if HAVE_SIGACTION
274 static struct sigaction save_sigint;
275 #endif
277 MONO_SIG_HANDLER_FUNC (static, sigcont_handler)
279 int unused G_GNUC_UNUSED;
280 // Ignore error, there is not much we can do in the sigcont handler.
281 tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr);
283 if (keypad_xmit_str != NULL)
284 unused = write (STDOUT_FILENO, keypad_xmit_str, strlen (keypad_xmit_str));
286 // Call previous handler
287 if (save_sigcont.sa_sigaction != NULL &&
288 save_sigcont.sa_sigaction != (void *)SIG_DFL &&
289 save_sigcont.sa_sigaction != (void *)SIG_IGN)
290 (*save_sigcont.sa_sigaction) (MONO_SIG_HANDLER_PARAMS);
293 MONO_SIG_HANDLER_FUNC (static, sigwinch_handler)
295 int dims = terminal_get_dimensions ();
296 if (dims != -1)
297 cols_and_lines = dims;
299 // Call previous handler
300 if (save_sigwinch.sa_sigaction != NULL &&
301 save_sigwinch.sa_sigaction != (void *)SIG_DFL &&
302 save_sigwinch.sa_sigaction != (void *)SIG_IGN)
303 (*save_sigwinch.sa_sigaction) (MONO_SIG_HANDLER_PARAMS);
307 * console_set_signal_handlers:
309 * Installs various signals handlers for the use of the console, as
310 * follows:
312 * SIGCONT: this is received after the application has resumed execution
313 * if it was suspended with Control-Z before. This signal handler needs
314 * to resend the terminal sequence to send keyboard in keypad mode (this
315 * is the difference between getting a cuu1 code or a kcuu1 code for up-arrow
316 * for example
318 * SIGINT: invokes the System.Console.DoConsoleCancelEvent method using
319 * a thread from the thread pool which notifies all registered cancel_event
320 * listeners.
322 * SIGWINCH: is used to track changes to the console window when a GUI
323 * terminal is resized. It sets an internal variable that is checked
324 * by System.Console when the Terminfo driver has been activated.
326 static void
327 console_set_signal_handlers ()
329 #if defined(HAVE_SIGACTION)
330 struct sigaction sigcont, sigint, sigwinch;
332 memset (&sigcont, 0, sizeof (struct sigaction));
333 memset (&sigint, 0, sizeof (struct sigaction));
334 memset (&sigwinch, 0, sizeof (struct sigaction));
336 // Continuing
337 sigcont.sa_handler = (void (*)(int)) sigcont_handler;
338 sigcont.sa_flags = SA_RESTART;
339 sigemptyset (&sigcont.sa_mask);
340 sigaction (SIGCONT, &sigcont, &save_sigcont);
342 // Interrupt handler
343 sigint.sa_handler = (void (*)(int)) sigint_handler;
344 sigint.sa_flags = SA_RESTART;
345 sigemptyset (&sigint.sa_mask);
346 sigaction (SIGINT, &sigint, &save_sigint);
348 // Window size changed
349 sigwinch.sa_handler = (void (*)(int)) sigwinch_handler;
350 sigwinch.sa_flags = SA_RESTART;
351 sigemptyset (&sigwinch.sa_mask);
352 sigaction (SIGWINCH, &sigwinch, &save_sigwinch);
353 #endif
356 #if currently_unuused
358 // Currently unused, should we ever call the restore handler?
359 // Perhaps before calling into Process.Start?
361 void
362 console_restore_signal_handlers ()
364 sigaction (SIGCONT, &save_sigcont, NULL);
365 sigaction (SIGINT, &save_sigint, NULL);
366 sigaction (SIGWINCH, &save_sigwinch, NULL);
368 #endif
370 static void
371 set_control_chars (gchar *control_chars, const guchar *cc)
373 /* The index into the array comes from corlib/System/ControlCharacters.cs */
374 #ifdef VINTR
375 control_chars [0] = cc [VINTR];
376 #endif
377 #ifdef VQUIT
378 control_chars [1] = cc [VQUIT];
379 #endif
380 #ifdef VERASE
381 control_chars [2] = cc [VERASE];
382 #endif
383 #ifdef VKILL
384 control_chars [3] = cc [VKILL];
385 #endif
386 #ifdef VEOF
387 control_chars [4] = cc [VEOF];
388 #endif
389 #ifdef VTIME
390 control_chars [5] = cc [VTIME];
391 #endif
392 #ifdef VMIN
393 control_chars [6] = cc [VMIN];
394 #endif
395 #ifdef VSWTC
396 control_chars [7] = cc [VSWTC];
397 #endif
398 #ifdef VSTART
399 control_chars [8] = cc [VSTART];
400 #endif
401 #ifdef VSTOP
402 control_chars [9] = cc [VSTOP];
403 #endif
404 #ifdef VSUSP
405 control_chars [10] = cc [VSUSP];
406 #endif
407 #ifdef VEOL
408 control_chars [11] = cc [VEOL];
409 #endif
410 #ifdef VREPRINT
411 control_chars [12] = cc [VREPRINT];
412 #endif
413 #ifdef VDISCARD
414 control_chars [13] = cc [VDISCARD];
415 #endif
416 #ifdef VWERASE
417 control_chars [14] = cc [VWERASE];
418 #endif
419 #ifdef VLNEXT
420 control_chars [15] = cc [VLNEXT];
421 #endif
422 #ifdef VEOL2
423 control_chars [16] = cc [VEOL2];
424 #endif
427 MonoBoolean
428 ves_icall_System_ConsoleDriver_TtySetup (MonoStringHandle keypad, MonoStringHandle teardown, MonoArrayHandleOut control_chars, int **size, MonoError* error)
430 // FIXME Lock around the globals?
432 int dims;
434 dims = terminal_get_dimensions ();
435 if (dims == -1){
436 int cols = 0, rows = 0;
438 char *str = g_getenv ("COLUMNS");
439 if (str != NULL) {
440 cols = atoi (str);
441 g_free (str);
443 str = g_getenv ("LINES");
444 if (str != NULL) {
445 rows = atoi (str);
446 g_free (str);
449 if (cols != 0 && rows != 0)
450 cols_and_lines = (cols << 16) | rows;
451 else
452 cols_and_lines = -1;
453 } else {
454 cols_and_lines = dims;
457 *size = &cols_and_lines;
459 /* 17 is the number of entries set in set_control_chars() above.
460 * NCCS is the total size, but, by now, we only care about those 17 values*/
461 MonoArrayHandle control_chars_arr = mono_array_new_handle (mono_domain_get (), mono_defaults.byte_class, 17, error);
462 return_val_if_nok (error, FALSE);
464 MONO_HANDLE_ASSIGN (control_chars, control_chars_arr);
465 if (tcgetattr (STDIN_FILENO, &initial_attr) == -1)
466 return FALSE;
468 mono_attr = initial_attr;
469 mono_attr.c_lflag &= ~(ICANON);
470 mono_attr.c_iflag &= ~(IXON|IXOFF);
471 mono_attr.c_cc [VMIN] = 1;
472 mono_attr.c_cc [VTIME] = 0;
473 #ifdef VDSUSP
474 /* Disable C-y being used as a suspend character on OSX */
475 mono_attr.c_cc [VDSUSP] = 255;
476 #endif
477 gint ret;
478 do {
479 MONO_ENTER_GC_SAFE;
480 ret = tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr);
481 MONO_EXIT_GC_SAFE;
482 } while (ret == -1 && errno == EINTR);
484 if (ret == -1)
485 return FALSE;
487 uint32_t h;
488 set_control_chars (MONO_ARRAY_HANDLE_PIN (control_chars_arr, gchar, 0, &h), mono_attr.c_cc);
489 mono_gchandle_free_internal (h);
490 /* If initialized from another appdomain... */
491 if (setup_finished)
492 return TRUE;
494 keypad_xmit_str = NULL;
495 if (!MONO_HANDLE_IS_NULL (keypad)) {
496 keypad_xmit_str = mono_string_handle_to_utf8 (keypad, error);
497 return_val_if_nok (error, FALSE);
500 console_set_signal_handlers ();
501 setup_finished = TRUE;
502 if (!atexit_called) {
503 if (!MONO_HANDLE_IS_NULL (teardown)) {
504 teardown_str = mono_string_handle_to_utf8 (teardown, error);
505 return_val_if_nok (error, FALSE);
508 mono_atexit (tty_teardown);
511 return TRUE;
514 #else /* ENABLE_NETCORE */
516 void
517 mono_console_init (void)
521 void
522 mono_console_handle_async_ops (void)
526 #endif