102723229665b2475b77ec304ac0724a7039766e
[mono.git] / mono / metadata / console-unix.c
1 /*
2  * console-io.c: ConsoleDriver internal calls for Unix systems.
3  *
4  * Author:
5  *      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6  *
7  * Copyright (C) 2005-2009 Novell, Inc. (http://www.novell.com)
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10 #if defined(__native_client__)
11 #include "console-null.c"
12 #else
13
14 #include <config.h>
15 #include <glib.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <signal.h>
21 #ifdef HAVE_SYS_SELECT_H
22 #    include <sys/select.h>
23 #endif
24 #ifdef HAVE_SYS_TIME_H
25 #    include <sys/time.h>
26 #endif
27 #include <sys/types.h>
28 #ifdef HAVE_UNISTD_H
29 #    include <unistd.h>
30 #endif
31 #include <mono/metadata/appdomain.h>
32 #include <mono/metadata/object-internals.h>
33 #include <mono/metadata/class-internals.h>
34 #include <mono/metadata/domain-internals.h>
35 #include <mono/metadata/gc-internals.h>
36 #include <mono/metadata/metadata.h>
37 #include <mono/metadata/threadpool-ms.h>
38 #include <mono/utils/mono-signal-handler.h>
39 #include <mono/utils/mono-proclib.h>
40 #include <mono/io-layer/io-layer.h>
41
42 /* On solaris, curses.h must come before both termios.h and term.h */
43 #ifdef HAVE_CURSES_H
44 #    include <curses.h>
45 #endif
46 #ifdef HAVE_TERMIOS_H
47 #    include <termios.h>
48 #endif
49 #ifdef HAVE_TERM_H
50 #    include <term.h>
51 #endif
52
53 /* Needed for FIONREAD under solaris */
54 #ifdef HAVE_SYS_FILIO_H
55 #    include <sys/filio.h>
56 #endif
57 #ifdef HAVE_SYS_IOCTL_H
58 #    include <sys/ioctl.h>
59 #endif
60
61 #include <mono/metadata/console-io.h>
62 #include <mono/metadata/exception.h>
63
64 static gboolean setup_finished;
65 static gboolean atexit_called;
66
67 /* The string used to return the terminal to its previous state */
68 static gchar *teardown_str;
69
70 /* The string used to set the terminal into keypad xmit mode after SIGCONT is received */
71 static gchar *keypad_xmit_str;
72
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
77
78 /* static void console_restore_signal_handlers (void); */
79 static void console_set_signal_handlers (void);
80
81 void
82 mono_console_init (void)
83 {
84         int fd;
85
86         /* Make sure the standard file descriptors are opened */
87         fd = open ("/dev/null", O_RDWR);
88         while (fd >= 0 && fd < 3) {
89                 fd = open ("/dev/null", O_RDWR);
90         }
91         close (fd);
92 }
93
94 static struct termios initial_attr;
95
96 MonoBoolean
97 ves_icall_System_ConsoleDriver_Isatty (HANDLE handle)
98 {
99         return isatty (GPOINTER_TO_INT (handle));
100 }
101
102 static MonoBoolean
103 set_property (gint property, gboolean value)
104 {
105         struct termios attr;
106         gboolean callset = FALSE;
107         gboolean check;
108         
109         if (tcgetattr (STDIN_FILENO, &attr) == -1)
110                 return FALSE;
111
112         check = (attr.c_lflag & property) != 0;
113         if ((value || check) && !(value && check)) {
114                 callset = TRUE;
115                 if (value)
116                         attr.c_lflag |= property;
117                 else
118                         attr.c_lflag &= ~property;
119         }
120
121         if (!callset)
122                 return TRUE;
123
124         if (tcsetattr (STDIN_FILENO, TCSANOW, &attr) == -1)
125                 return FALSE;
126
127         mono_attr = attr;
128         return TRUE;
129 }
130
131 MonoBoolean
132 ves_icall_System_ConsoleDriver_SetEcho (MonoBoolean want_echo)
133 {
134         
135         return set_property (ECHO, want_echo);
136 }
137
138 MonoBoolean
139 ves_icall_System_ConsoleDriver_SetBreak (MonoBoolean want_break)
140 {
141         return set_property (IGNBRK, !want_break);
142 }
143
144 gint32
145 ves_icall_System_ConsoleDriver_InternalKeyAvailable (gint32 timeout)
146 {
147         fd_set rfds;
148         struct timeval tv;
149         struct timeval *tvptr;
150         div_t divvy;
151         int ret, nbytes;
152
153         do {
154                 FD_ZERO (&rfds);
155                 FD_SET (STDIN_FILENO, &rfds);
156                 if (timeout >= 0) {
157                         divvy = div (timeout, 1000);
158                         tv.tv_sec = divvy.quot;
159                         tv.tv_usec = divvy.rem;
160                         tvptr = &tv;
161                 } else {
162                         tvptr = NULL;
163                 }
164                 ret = select (STDIN_FILENO + 1, &rfds, NULL, NULL, tvptr);
165         } while (ret == -1 && errno == EINTR);
166
167         if (ret > 0) {
168                 nbytes = 0;
169                 ret = ioctl (STDIN_FILENO, FIONREAD, &nbytes);
170                 if (ret >= 0)
171                         ret = nbytes;
172         }
173
174         return (ret > 0) ? ret : 0;
175 }
176
177 static gint32 cols_and_lines;
178
179 #ifdef TIOCGWINSZ
180 static int
181 terminal_get_dimensions (void)
182 {
183         struct winsize ws;
184         int ret;
185         int save_errno = errno;
186         
187         if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) == 0){
188                 ret = (ws.ws_col << 16) | ws.ws_row;
189                 errno = save_errno;
190                 return ret;
191         } 
192         return -1;
193 }
194 #else
195 static int
196 terminal_get_dimensions (void)
197 {
198         return -1;
199 }
200 #endif
201
202 static void
203 tty_teardown (void)
204 {
205         int unused G_GNUC_UNUSED;
206
207         if (!setup_finished)
208                 return;
209
210         if (teardown_str != NULL) {
211                 unused = write (STDOUT_FILENO, teardown_str, strlen (teardown_str));
212                 g_free (teardown_str);
213                 teardown_str = NULL;
214         }
215
216         tcflush (STDIN_FILENO, TCIFLUSH);
217         tcsetattr (STDIN_FILENO, TCSANOW, &initial_attr);
218         set_property (ECHO, TRUE);
219         setup_finished = FALSE;
220 }
221
222 static void
223 do_console_cancel_event (void)
224 {
225         static MonoClassField *cancel_handler_field;
226         MonoError error;
227         MonoDomain *domain = mono_domain_get ();
228         MonoClass *klass;
229         MonoDelegate *load_value;
230         MonoMethod *method;
231         MonoVTable *vtable;
232
233         /* FIXME: this should likely iterate all the domains, instead */
234         if (!domain->domain)
235                 return;
236
237         klass = mono_class_try_load_from_name (mono_defaults.corlib, "System", "Console");
238         if (klass == NULL)
239                 return;
240
241         if (cancel_handler_field == NULL) {
242                 cancel_handler_field = mono_class_get_field_from_name (klass, "cancel_handler");
243                 g_assert (cancel_handler_field);
244         }
245
246         vtable = mono_class_vtable_full (domain, klass, &error);
247         if (vtable == NULL || !is_ok (&error)) {
248                 mono_error_cleanup (&error);
249                 return;
250         }
251         mono_field_static_get_value_checked (vtable, cancel_handler_field, &load_value, &error);
252         if (load_value == NULL || !is_ok (&error)) {
253                 mono_error_cleanup (&error);
254                 return;
255         }
256
257         klass = load_value->object.vtable->klass;
258         method = mono_class_get_method_from_name (klass, "BeginInvoke", -1);
259         g_assert (method != NULL);
260
261         mono_threadpool_ms_begin_invoke (domain, (MonoObject*) load_value, method, NULL, &error);
262         if (!is_ok (&error)) {
263                 g_warning ("Couldn't invoke System.Console cancel handler due to %s", mono_error_get_message (&error));
264                 mono_error_cleanup (&error);
265         }
266 }
267
268 static int need_cancel = FALSE;
269 /* this is executed from the finalizer thread */
270 void
271 mono_console_handle_async_ops (void)
272 {
273         if (need_cancel) {
274                 need_cancel = FALSE;
275                 do_console_cancel_event ();
276         }
277 }
278
279 static gboolean in_sigint;
280
281 MONO_SIG_HANDLER_FUNC (static, sigint_handler)
282 {
283         int save_errno;
284
285         if (in_sigint)
286                 return;
287
288         in_sigint = TRUE;
289         save_errno = errno;
290         need_cancel = TRUE;
291         mono_gc_finalize_notify ();
292         errno = save_errno;
293         in_sigint = FALSE;
294 }
295
296 static struct sigaction save_sigcont, save_sigint, save_sigwinch;
297
298 MONO_SIG_HANDLER_FUNC (static, sigcont_handler)
299 {
300         int unused G_GNUC_UNUSED;
301         // Ignore error, there is not much we can do in the sigcont handler.
302         tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr);
303
304         if (keypad_xmit_str != NULL)
305                 unused = write (STDOUT_FILENO, keypad_xmit_str, strlen (keypad_xmit_str));
306
307         // Call previous handler
308         if (save_sigcont.sa_sigaction != NULL &&
309             save_sigcont.sa_sigaction != (void *)SIG_DFL &&
310             save_sigcont.sa_sigaction != (void *)SIG_IGN)
311                 (*save_sigcont.sa_sigaction) (MONO_SIG_HANDLER_PARAMS);
312 }
313
314 MONO_SIG_HANDLER_FUNC (static, sigwinch_handler)
315 {
316         int dims = terminal_get_dimensions ();
317         if (dims != -1)
318                 cols_and_lines = dims;
319         
320         // Call previous handler
321         if (save_sigwinch.sa_sigaction != NULL &&
322             save_sigwinch.sa_sigaction != (void *)SIG_DFL &&
323             save_sigwinch.sa_sigaction != (void *)SIG_IGN)
324                 (*save_sigwinch.sa_sigaction) (MONO_SIG_HANDLER_PARAMS);
325 }
326
327 /*
328  * console_set_signal_handlers:
329  *
330  * Installs various signals handlers for the use of the console, as
331  * follows:
332  *
333  * SIGCONT: this is received after the application has resumed execution
334  * if it was suspended with Control-Z before.   This signal handler needs
335  * to resend the terminal sequence to send keyboard in keypad mode (this
336  * is the difference between getting a cuu1 code or a kcuu1 code for up-arrow
337  * for example
338  *
339  * SIGINT: invokes the System.Console.DoConsoleCancelEvent method using
340  * a thread from the thread pool which notifies all registered cancel_event
341  * listeners.
342  *
343  * SIGWINCH: is used to track changes to the console window when a GUI
344  * terminal is resized.    It sets an internal variable that is checked
345  * by System.Console when the Terminfo driver has been activated.
346  */
347 static void
348 console_set_signal_handlers ()
349 {
350 #if defined(HAVE_SIGACTION)
351         struct sigaction sigcont, sigint, sigwinch;
352
353         memset (&sigcont, 0, sizeof (struct sigaction));
354         memset (&sigint, 0, sizeof (struct sigaction));
355         memset (&sigwinch, 0, sizeof (struct sigaction));
356         
357         // Continuing
358         sigcont.sa_handler = (void (*)(int)) sigcont_handler;
359         sigcont.sa_flags = SA_RESTART;
360         sigemptyset (&sigcont.sa_mask);
361         sigaction (SIGCONT, &sigcont, &save_sigcont);
362         
363         // Interrupt handler
364         sigint.sa_handler = (void (*)(int)) sigint_handler;
365         sigint.sa_flags = SA_RESTART;
366         sigemptyset (&sigint.sa_mask);
367         sigaction (SIGINT, &sigint, &save_sigint);
368
369         // Window size changed
370         sigwinch.sa_handler = (void (*)(int)) sigwinch_handler;
371         sigwinch.sa_flags = SA_RESTART;
372         sigemptyset (&sigwinch.sa_mask);
373         sigaction (SIGWINCH, &sigwinch, &save_sigwinch);
374 #endif
375 }
376
377 #if currently_unuused
378 //
379 // Currently unused, should we ever call the restore handler?
380 // Perhaps before calling into Process.Start?
381 //
382 void
383 console_restore_signal_handlers ()
384 {
385         sigaction (SIGCONT, &save_sigcont, NULL);
386         sigaction (SIGINT, &save_sigint, NULL);
387         sigaction (SIGWINCH, &save_sigwinch, NULL);
388 }
389 #endif
390
391 static void
392 set_control_chars (MonoArray *control_chars, const guchar *cc)
393 {
394         /* The index into the array comes from corlib/System/ControlCharacters.cs */
395 #ifdef VINTR
396         mono_array_set (control_chars, gchar, 0, cc [VINTR]);
397 #endif
398 #ifdef VQUIT
399         mono_array_set (control_chars, gchar, 1, cc [VQUIT]);
400 #endif
401 #ifdef VERASE
402         mono_array_set (control_chars, gchar, 2, cc [VERASE]);
403 #endif
404 #ifdef VKILL
405         mono_array_set (control_chars, gchar, 3, cc [VKILL]);
406 #endif
407 #ifdef VEOF
408         mono_array_set (control_chars, gchar, 4, cc [VEOF]);
409 #endif
410 #ifdef VTIME
411         mono_array_set (control_chars, gchar, 5, cc [VTIME]);
412 #endif
413 #ifdef VMIN
414         mono_array_set (control_chars, gchar, 6, cc [VMIN]);
415 #endif
416 #ifdef VSWTC
417         mono_array_set (control_chars, gchar, 7, cc [VSWTC]);
418 #endif
419 #ifdef VSTART
420         mono_array_set (control_chars, gchar, 8, cc [VSTART]);
421 #endif
422 #ifdef VSTOP
423         mono_array_set (control_chars, gchar, 9, cc [VSTOP]);
424 #endif
425 #ifdef VSUSP
426         mono_array_set (control_chars, gchar, 10, cc [VSUSP]);
427 #endif
428 #ifdef VEOL
429         mono_array_set (control_chars, gchar, 11, cc [VEOL]);
430 #endif
431 #ifdef VREPRINT
432         mono_array_set (control_chars, gchar, 12, cc [VREPRINT]);
433 #endif
434 #ifdef VDISCARD
435         mono_array_set (control_chars, gchar, 13, cc [VDISCARD]);
436 #endif
437 #ifdef VWERASE
438         mono_array_set (control_chars, gchar, 14, cc [VWERASE]);
439 #endif
440 #ifdef VLNEXT
441         mono_array_set (control_chars, gchar, 15, cc [VLNEXT]);
442 #endif
443 #ifdef VEOL2
444         mono_array_set (control_chars, gchar, 16, cc [VEOL2]);
445 #endif
446 }
447
448 MonoBoolean
449 ves_icall_System_ConsoleDriver_TtySetup (MonoString *keypad, MonoString *teardown, MonoArray **control_chars, int **size)
450 {
451         MonoError error;
452
453         int dims;
454
455         dims = terminal_get_dimensions ();
456         if (dims == -1){
457                 int cols = 0, rows = 0;
458                                       
459                 const char *str = g_getenv ("COLUMNS");
460                 if (str != NULL)
461                         cols = atoi (str);
462                 str = g_getenv ("LINES");
463                 if (str != NULL)
464                         rows = atoi (str);
465
466                 if (cols != 0 && rows != 0)
467                         cols_and_lines = (cols << 16) | rows;
468                 else
469                         cols_and_lines = -1;
470         } else {
471                 cols_and_lines = dims;
472         }
473         
474         *size = &cols_and_lines;
475
476         /* 17 is the number of entries set in set_control_chars() above.
477          * NCCS is the total size, but, by now, we only care about those 17 values*/
478         MonoArray *control_chars_arr = mono_array_new_checked (mono_domain_get (), mono_defaults.byte_class, 17, &error);
479         if (mono_error_set_pending_exception (&error))
480                 return FALSE;
481         mono_gc_wbarrier_generic_store (control_chars, (MonoObject*) control_chars_arr);
482         if (tcgetattr (STDIN_FILENO, &initial_attr) == -1)
483                 return FALSE;
484
485         mono_attr = initial_attr;
486         mono_attr.c_lflag &= ~(ICANON);
487         mono_attr.c_iflag &= ~(IXON|IXOFF);
488         mono_attr.c_cc [VMIN] = 1;
489         mono_attr.c_cc [VTIME] = 0;
490 #ifdef VDSUSP
491         /* Disable C-y being used as a suspend character on OSX */
492         mono_attr.c_cc [VDSUSP] = 255;
493 #endif
494         if (tcsetattr (STDIN_FILENO, TCSANOW, &mono_attr) == -1)
495                 return FALSE;
496
497         set_control_chars (*control_chars, mono_attr.c_cc);
498         /* If initialized from another appdomain... */
499         if (setup_finished)
500                 return TRUE;
501
502         keypad_xmit_str = NULL;
503         if (keypad != NULL) {
504                 keypad_xmit_str = mono_string_to_utf8_checked (keypad, &error);
505                 if (mono_error_set_pending_exception (&error))
506                         return FALSE;
507         }
508         
509         console_set_signal_handlers ();
510         setup_finished = TRUE;
511         if (!atexit_called) {
512                 if (teardown != NULL) {
513                         teardown_str = mono_string_to_utf8_checked (teardown, &error);
514                         if (mono_error_set_pending_exception (&error))
515                                 return FALSE;
516                 }
517
518                 mono_atexit (tty_teardown);
519         }
520
521         return TRUE;
522 }
523 #endif /* #if defined(__native_client__) */
524