1 /* -*- mode: c ; c-file-style: "canonware-c-style" -*-
2 ******************************************************************************
3 *
4 * Copyright (C) 1996-2005 Jason Evans <jasone@canonware.com>.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice(s), this list of conditions and the following disclaimer
12 * unmodified other than the allowable addition of one or more
13 * copyright notices.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice(s), this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 ******************************************************************************
32 *
33 * Version: Onyx 5.1.2
34 *
35 ******************************************************************************/
36
37 #include "libonyx/libonyx.h"
38 #include "../include/modprompt.h"
39
40 #include <errno.h>
41 #include <histedit.h>
42
43 /* LinuxThreads (the precursor to NPTL) appears to do the wrong thing for
44 * SIGSTOP. Defining CW_MODPROMPT_LINUXTHREADS works around this problem.
45 *
46 * XXX This workaround should be removed once NPTL-based systems are the
47 * norm. */
48 /* #define CW_MODPROMPT_LINUXTHREADS */
49
50 #define CW_PROMPT_STRLEN 80
51 #define CW_BUFFER_SIZE 512
52
53 struct cw_modprompt_synth_s {
54 #ifdef CW_DBG
55 #define CW_MODPROMPT_SYNTH_MAGIC 0x32ad81a5
56 uint32_t magic;
57 #endif
58 cw_nxo_t modload_handle;
59 cw_nxo_t *thread;
60 cw_nxo_threadp_t threadp;
61 bool continuation;
62 #ifdef CW_THREADS
63 cw_thd_t *read_thd;
64 volatile bool quit;
65 volatile bool resize;
66
67 cw_mtx_t mtx;
68 bool have_data; /* The writer wrote data. */
69 cw_cnd_t put_cnd; /* The writer (libedit thread) waits on this. */
70 bool want_data; /* The reader wants data. */
71 cw_cnd_t get_cnd; /* The reader (interpreter thread) waits on this. */
72 #endif
73
74 char *buffer;
75 uint32_t buffer_size;
76 uint32_t buffer_count;
77
78 EditLine *el;
79 History *hist;
80 HistEvent hevent;
81 char prompt_str[CW_PROMPT_STRLEN];
82 };
83
84 /* Globals. */
85 /* This must be global so that the signal handler can get to it. */
86 static struct cw_modprompt_synth_s *synth;
87
88 /* Function prototypes. */
89 static cw_nxoe_t *
90 modprompt_synth_ref_iter(void *a_data, bool a_reset);
91
92 static void
93 modprompt_synth_delete(void *a_data);
94
95 static int32_t
96 modprompt_read(void *a_data, cw_nxo_t *a_file, uint32_t a_len,
97 char *r_str);
98
99 static void
100 modprompt_promptstring(struct cw_modprompt_synth_s *a_synth);
101
102 static char *
103 modprompt_prompt(EditLine *a_el);
104
105 static void
106 modprompt_handlers_install(void);
107
108 static void
109 modprompt_signal_handle(int a_signal);
110
111 #ifdef CW_THREADS
112 static void *
113 modprompt_entry(void *a_arg);
114 #endif
115
116 void
modprompt_init(void * a_arg,cw_nxo_t * a_thread)117 modprompt_init(void *a_arg, cw_nxo_t *a_thread)
118 {
119 cw_nxo_t *estack, *file;
120 char *editor;
121 #ifdef CW_THREADS
122 sigset_t set, oset;
123 #endif
124
125 synth = (struct cw_modprompt_synth_s *)
126 cw_calloc(1, sizeof(struct cw_modprompt_synth_s));
127
128 /* Initialize stdin. Only convert the initial thread's stdin, since it
129 * isn't safe for multiple threads to use the synthetic file. If the
130 * application is crazy enough use the initial thread's stdin in another
131 * thread, crashes are likely. */
132 file = nxo_thread_stdin_get(a_thread);
133 nxo_file_new(file, true);
134 nxo_file_synthetic(file, modprompt_read, NULL, modprompt_synth_ref_iter,
135 modprompt_synth_delete, synth);
136 nxo_file_origin_set(file, "*stdin*", sizeof("*stdin*") - 1);
137
138 nxo_attr_set(file, NXOA_EXECUTABLE);
139
140 /* The interpreter is currently executing a handle that holds a reference to
141 * the dynamically loaded module. Keep a reference to it, so that this
142 * module will not be unloaded until after the synthetic file object has
143 * been destroyed. */
144 estack = nxo_thread_estack_get(a_thread);
145 nxo_no_new(&synth->modload_handle);
146 nxo_dup(&synth->modload_handle, nxo_stack_get(estack));
147
148 /* Finish initializing synth. */
149 synth->thread = a_thread;
150 nxo_threadp_new(&synth->threadp);
151 #ifdef CW_THREADS
152 mtx_new(&synth->mtx);
153 cnd_new(&synth->put_cnd);
154 cnd_new(&synth->get_cnd);
155 #endif
156
157 /* Initialize the command editor. */
158 synth->hist = history_init();
159 history(synth->hist, &synth->hevent, H_SETSIZE, 512);
160
161 synth->el = el_init("onyx", stdin, stdout, stderr);
162 el_set(synth->el, EL_HIST, history, synth->hist);
163 el_set(synth->el, EL_PROMPT, modprompt_prompt);
164 el_set(synth->el, EL_CLIENTDATA, synth);
165
166 editor = getenv("ONYX_EDITOR");
167 if (editor == NULL || (strcmp(editor, "emacs") && strcmp(editor, "vi")))
168 {
169 /* Default to emacs key bindings, since they're more intuitive to the
170 * uninitiated. */
171 editor = "emacs";
172 }
173 el_set(synth->el, EL_EDITOR, editor);
174 #ifdef CW_DBG
175 synth->magic = CW_MODPROMPT_SYNTH_MAGIC;
176 #endif
177
178 #ifdef CW_THREADS
179 /* Mask all signals that the read thread handles, so that they're always
180 * delivered there. */
181 sigemptyset(&set);
182 sigaddset(&set, SIGINT);
183 #ifdef CW_MODPROMPT_LINUXTHREADS
184 sigaddset(&set, SIGTSTP);
185 sigaddset(&set, SIGSTOP);
186 #endif
187 sigaddset(&set, SIGQUIT);
188 sigaddset(&set, SIGHUP);
189 sigaddset(&set, SIGTERM);
190 #ifdef CW_MODPROMPT_LINUXTHREADS
191 sigaddset(&set, SIGCONT);
192 #endif
193 sigaddset(&set, SIGWINCH);
194 thd_sigmask(SIG_BLOCK, &set, &oset);
195 synth->read_thd = thd_new(modprompt_entry, synth, true);
196 #else
197 /* Install signal handlers. */
198 modprompt_handlers_install();
199 #endif
200
201 /* stdin is now a synthetic file. This does not change any file objects
202 * which may already be on estack, but under normal circumstances, this
203 * module is loaded by the bootstrap code, and stdin is not pushed onto
204 * estack and executed until after the bootstrap code has been completely
205 * executed.
206 *
207 * If this module is loaded later on, once stdin is being read from, loading
208 * this module will have no apparent effect unless something like the
209 * following code is recursed into:
210 *
211 * stdin cvx eval
212 *
213 * Strange things may happen though, since the file object already on estack
214 * may have already buffered data, but the data will not be evaluated until
215 * EOF is returned by the synthetic stdin. */
216 }
217
218 static cw_nxoe_t *
modprompt_synth_ref_iter(void * a_data,bool a_reset)219 modprompt_synth_ref_iter(void *a_data, bool a_reset)
220 {
221 cw_nxoe_t *retval;
222 struct cw_modprompt_synth_s *synth = (struct cw_modprompt_synth_s *) a_data;
223
224 cw_check_ptr(synth);
225 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
226
227 if (a_reset)
228 {
229 retval = nxo_nxoe_get(&synth->modload_handle);
230 }
231 else
232 {
233 retval = NULL;
234 }
235
236 return retval;
237 }
238
239 static void
modprompt_synth_delete(void * a_data)240 modprompt_synth_delete(void *a_data)
241 {
242 struct cw_modprompt_synth_s *synth = (struct cw_modprompt_synth_s *) a_data;
243
244 cw_check_ptr(synth);
245 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
246
247 #ifdef CW_THREADS
248 /* Tell the read thread to start shutting down. */
249 mtx_lock(&synth->mtx);
250 synth->quit = true;
251 cnd_signal(&synth->put_cnd);
252 mtx_unlock(&synth->mtx);
253 /* Wait for the read thread to exit. */
254 thd_join(synth->read_thd);
255 /* Clean up. */
256 mtx_delete(&synth->mtx);
257 cnd_delete(&synth->put_cnd);
258 cnd_delete(&synth->get_cnd);
259 #endif
260
261 if (synth->buffer != NULL)
262 {
263 cw_free(synth->buffer);
264 }
265
266 /* Clean up the command editor. */
267 el_end(synth->el);
268 history_end(synth->hist);
269
270 cw_free(synth);
271 }
272
273 #ifdef CW_THREADS
274 static int32_t
modprompt_read(void * a_data,cw_nxo_t * a_file,uint32_t a_len,char * r_str)275 modprompt_read(void *a_data, cw_nxo_t *a_file, uint32_t a_len,
276 char *r_str)
277 {
278 int32_t retval;
279 struct cw_modprompt_synth_s *synth = (struct cw_modprompt_synth_s *) a_data;
280
281 cw_check_ptr(synth);
282 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
283 cw_assert(a_len > 0);
284
285 mtx_lock(&synth->mtx);
286
287 if (synth->buffer_count == 0)
288 {
289 /* Tell the main thread to read more data, then wait for it. */
290 synth->want_data = true;
291 cnd_signal(&synth->put_cnd);
292 synth->have_data = false;
293 while (synth->have_data == false)
294 {
295 cnd_wait(&synth->get_cnd, &synth->mtx);
296 }
297 }
298 cw_assert(synth->buffer_count > 0);
299
300 /* Return as much of the data as possible. */
301 if (synth->buffer_count > a_len)
302 {
303 /* There are more data than we can return. */
304 retval = a_len;
305 memcpy(r_str, synth->buffer, a_len);
306 synth->buffer_count -= a_len;
307 memmove(synth->buffer, &synth->buffer[a_len], synth->buffer_count);
308 }
309 else
310 {
311 /* Return all the data. */
312 retval = synth->buffer_count;
313 memcpy(r_str, synth->buffer, synth->buffer_count);
314 synth->buffer_count = 0;
315 }
316
317 mtx_unlock(&synth->mtx);
318 return retval;
319 }
320 #else
321 static int32_t
modprompt_read(void * a_data,cw_nxo_t * a_file,uint32_t a_len,char * r_str)322 modprompt_read(void *a_data, cw_nxo_t *a_file, uint32_t a_len,
323 char *r_str)
324 {
325 int32_t retval;
326 struct cw_modprompt_synth_s *synth = (struct cw_modprompt_synth_s *) a_data;
327 const char *str;
328 int count = 0;
329
330 cw_check_ptr(synth);
331 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
332 cw_assert(a_len > 0);
333
334 if (synth->buffer_count == 0)
335 {
336 /* Update the promptstring if necessary. */
337 modprompt_promptstring(synth);
338
339 /* Read more data. */
340 while ((str = el_gets(synth->el, &count)) == NULL)
341 {
342 /* An interrupted system call (EINTR) caused an error in
343 * el_gets(). */
344 }
345 cw_assert(count > 0);
346
347 /* Update the command line history. */
348 if ((nxo_thread_deferred(synth->thread) == false)
349 && (nxo_thread_state(synth->thread) == THREADTS_START))
350 {
351 /* Completion of a history element. Insert it, taking care to avoid
352 * simple (non-continued) duplicates and empty lines (simple
353 * carriage returns). */
354 if (synth->continuation)
355 {
356 history(synth->hist, &synth->hevent, H_ENTER, str);
357 synth->continuation = false;
358 }
359 else
360 {
361 if ((history(synth->hist, &synth->hevent, H_FIRST) == -1
362 || strcmp(str, synth->hevent.str)) && strlen(str) > 1)
363 {
364 history(synth->hist, &synth->hevent, H_ENTER, str);
365 }
366 }
367 }
368 else
369 {
370 /* Continuation. Append it to the current history element. */
371 history(synth->hist, &synth->hevent, H_ADD, str);
372 synth->continuation = true;
373 }
374
375 /* Copy the data to the synth buffer. */
376 if (count > synth->buffer_size)
377 {
378 /* Expand the buffer. */
379 if (synth->buffer == NULL)
380 {
381 synth->buffer = (char *) cw_malloc(count);
382 }
383 else
384 {
385 synth->buffer = (char *) cw_realloc(synth->buffer, count);
386 }
387 synth->buffer_size = count;
388 }
389 memcpy(synth->buffer, str, count);
390 synth->buffer_count = count;
391 }
392 cw_assert(synth->buffer_count > 0);
393
394 /* Return as much of the data as possible. */
395 if (synth->buffer_count > a_len)
396 {
397 /* There are more data than we can return. */
398 retval = a_len;
399 memcpy(r_str, synth->buffer, a_len);
400 synth->buffer_count -= a_len;
401 memmove(synth->buffer, &synth->buffer[a_len], synth->buffer_count);
402 }
403 else
404 {
405 /* Return all the data. */
406 retval = synth->buffer_count;
407 memcpy(r_str, synth->buffer, synth->buffer_count);
408 synth->buffer_count = 0;
409 }
410
411 return retval;
412 }
413 #endif
414
415 static void
modprompt_promptstring(struct cw_modprompt_synth_s * a_synth)416 modprompt_promptstring(struct cw_modprompt_synth_s *a_synth)
417 {
418 /* Call promptstring if the conditions are right. Take lots of care not to
419 * let an error in promptstring cause recursion into the error handling
420 * machinery. */
421 if ((nxo_thread_deferred(synth->thread) == false)
422 && (nxo_thread_state(synth->thread) == THREADTS_START))
423 {
424 char *pstr;
425 uint32_t plen, maxlen;
426 cw_nxo_t *nxo;
427 cw_nxo_t *stack;
428 static const char code[] =
429 "{$promptstring where {\n"
430 "pop\n"
431 /* Save the current contents of errordict into promptdict. */
432 "$promptdict errordict dict copy def\n"
433 /* Temporarily reconfigure errordict. */
434 "errordict $handleerror {} put\n"
435 "errordict $stop $stop load put\n"
436 /* Actually call promptstring. */
437 "{promptstring} stopped {`'} if\n"
438 /* Restore errordict's configuration. */
439 "promptdict errordict copy pop\n"
440 /* Remove the definition of promptdict. */
441 "$promptdict where {$promptdict undef} if\n"
442 "}{`'} ifelse} start";
443
444 stack = nxo_thread_ostack_get(synth->thread);
445
446 /* Push the prompt onto the data stack. */
447 nxo_thread_interpret(synth->thread, &synth->threadp, code,
448 sizeof(code) - 1);
449 nxo_thread_flush(synth->thread, &synth->threadp);
450
451 /* Get the actual prompt string. */
452 nxo = nxo_stack_get(stack);
453 if (nxo == NULL)
454 {
455 nxo_thread_nerror(synth->thread, NXN_stackunderflow);
456 maxlen = 0;
457 }
458 else if (nxo_type_get(nxo) != NXOT_STRING)
459 {
460 nxo_thread_nerror(synth->thread, NXN_typecheck);
461 maxlen = 0;
462 }
463 else
464 {
465 pstr = nxo_string_get(nxo);
466 plen = nxo_string_len_get(nxo);
467
468 /* Copy the prompt string to a global buffer. */
469 maxlen = (plen > CW_PROMPT_STRLEN - 1)
470 ? CW_PROMPT_STRLEN - 1 : plen;
471 strncpy(synth->prompt_str, pstr, maxlen);
472 }
473
474 synth->prompt_str[maxlen] = '\0';
475
476 /* Pop the prompt string off the data stack. */
477 nxo_stack_pop(stack);
478 }
479 }
480
481 static char *
modprompt_prompt(EditLine * a_el)482 modprompt_prompt(EditLine *a_el)
483 {
484 struct cw_modprompt_synth_s *synth;
485
486 el_get(a_el, EL_CLIENTDATA, (void **)&synth);
487
488 cw_check_ptr(synth);
489 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
490
491 if ((nxo_thread_deferred(synth->thread))
492 || (nxo_thread_state(synth->thread) != THREADTS_START))
493 {
494 /* One or both of:
495 *
496 * - Continuation of a string or similarly parsed token.
497 * - Execution is deferred due to unmatched {}'s.
498 *
499 * Don't print a prompt. */
500 synth->prompt_str[0] = '\0';
501 }
502
503 return synth->prompt_str;
504 }
505
506 static void
modprompt_handlers_install(void)507 modprompt_handlers_install(void)
508 {
509 struct sigaction action;
510 #ifdef CW_THREADS
511 sigset_t set, oset;
512 #endif
513
514 /* Set up a signal handler for various signals that are important to an
515 * interactive program. */
516 memset(&action, 0, sizeof(struct sigaction));
517 action.sa_handler = modprompt_signal_handle;
518 sigemptyset(&action.sa_mask);
519 #define HANDLER_INSTALL(a_signal) \
520 if (sigaction((a_signal), &action, NULL) == -1) \
521 { \
522 fprintf(stderr, "Error in sigaction(%s, ...): %s\n", \
523 #a_signal, strerror(errno)); \
524 abort(); \
525 }
526 HANDLER_INSTALL(SIGHUP);
527 HANDLER_INSTALL(SIGWINCH);
528 #ifdef CW_MODPROMPT_LINUXTHREADS
529 HANDLER_INSTALL(SIGTSTP);
530 HANDLER_INSTALL(SIGCONT);
531 #endif
532 HANDLER_INSTALL(SIGINT);
533 HANDLER_INSTALL(SIGQUIT);
534 HANDLER_INSTALL(SIGTERM);
535 #undef HANDLER_INSTALL
536
537 #ifdef CW_THREADS
538 sigemptyset(&set);
539 sigaddset(&set, SIGINT);
540 #ifdef CW_MODPROMPT_LINUXTHREADS
541 sigaddset(&set, SIGTSTP);
542 sigaddset(&set, SIGSTOP);
543 #endif
544 sigaddset(&set, SIGQUIT);
545 sigaddset(&set, SIGHUP);
546 sigaddset(&set, SIGTERM);
547 #ifdef CW_MODPROMPT_LINUXTHREADS
548 sigaddset(&set, SIGCONT);
549 #endif
550 sigaddset(&set, SIGWINCH);
551 thd_sigmask(SIG_UNBLOCK, &set, &oset);
552 #endif
553 }
554
555 static void
modprompt_signal_handle(int a_signal)556 modprompt_signal_handle(int a_signal)
557 {
558 switch (a_signal)
559 {
560 case SIGWINCH:
561 {
562 el_resize(synth->el);
563 break;
564 }
565 #ifdef CW_MODPROMPT_LINUXTHREADS
566 case SIGTSTP:
567 {
568 /* Signal the process group. */
569 kill(0, SIGSTOP);
570 break;
571 }
572 case SIGCONT:
573 {
574 break;
575 }
576 #endif
577 case SIGHUP:
578 case SIGINT:
579 case SIGQUIT:
580 case SIGTERM:
581 {
582 el_end(synth->el);
583 _exit(0);
584 }
585 default:
586 {
587 fprintf(stderr, "Unexpected signal %d\n", a_signal);
588 abort();
589 }
590 }
591 }
592
593 #ifdef CW_THREADS
594 static void *
modprompt_entry(void * a_arg)595 modprompt_entry(void *a_arg)
596 {
597 struct cw_modprompt_synth_s *synth = (struct cw_modprompt_synth_s *) a_arg;
598 const char *str;
599 int count = 0;
600
601 cw_check_ptr(synth);
602 cw_dassert(synth->magic == CW_MODPROMPT_SYNTH_MAGIC);
603
604 /* Install signal handlers. */
605 modprompt_handlers_install();
606
607 mtx_lock(&synth->mtx);
608 for (;;)
609 {
610 /* Wait for the interpreter thread to request data. */
611 while (synth->want_data == false && synth->quit == false)
612 {
613 cnd_wait(&synth->put_cnd, &synth->mtx);
614 }
615 synth->want_data = false;
616 if (synth->quit)
617 {
618 break;
619 }
620
621 /* Update the promptstring if necessary. */
622 modprompt_promptstring(synth);
623
624 /* Read data. */
625 cw_assert(synth->buffer_count == 0);
626 while ((str = el_gets(synth->el, &count)) == NULL)
627 {
628 /* An interrupted system call (EINTR) caused an error in el_gets().
629 * Check to see if we should quit. */
630 }
631 cw_assert(count > 0);
632
633 /* Update the command line history. */
634 if ((nxo_thread_deferred(synth->thread) == false)
635 && (nxo_thread_state(synth->thread) == THREADTS_START))
636 {
637 /* Completion of a history element. Insert it, taking care to avoid
638 * simple (non-continued) duplicates and empty lines (simple
639 * carriage returns). */
640 if (synth->continuation)
641 {
642 history(synth->hist, &synth->hevent, H_ENTER, str);
643 synth->continuation = false;
644 }
645 else
646 {
647 if ((history(synth->hist, &synth->hevent, H_FIRST) == -1
648 || strcmp(str, synth->hevent.str)) && strlen(str) > 1)
649 {
650 history(synth->hist, &synth->hevent, H_ENTER, str);
651 }
652 }
653 }
654 else
655 {
656 /* Continuation. Append it to the current history element. */
657 history(synth->hist, &synth->hevent, H_ADD, str);
658 synth->continuation = true;
659 }
660
661 /* Copy the data to the synth buffer. */
662 if (count > synth->buffer_size)
663 {
664 /* Expand the buffer. */
665 if (synth->buffer == NULL)
666 {
667 synth->buffer = (char *) cw_malloc(count);
668 }
669 else
670 {
671 synth->buffer = (char *) cw_realloc(synth->buffer, count);
672 }
673 synth->buffer_size = count;
674 }
675 memcpy(synth->buffer, str, count);
676 synth->buffer_count = count;
677
678 /* Tell the interpreter thread that there are data available. */
679 synth->have_data = true;
680 cnd_signal(&synth->get_cnd);
681 }
682 mtx_unlock(&synth->mtx);
683
684 return NULL;
685 }
686 #endif
687