1 /*
2 * Copyright (c) 1993-2016, 2018-2020 Paul Mattes.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the names of Paul Mattes nor the names of his contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * childscript.c
30 * The Script() action.
31 */
32
33 #include "globals.h"
34
35 #include <assert.h>
36 #include <fcntl.h>
37 #if !defined(_WIN32) /*[*/
38 # include <signal.h>
39 # include <sys/signal.h>
40 # include <sys/wait.h>
41 # include <sys/types.h>
42 # include <sys/socket.h>
43 # include <sys/un.h>
44 # include <netinet/in.h>
45 # include <arpa/inet.h>
46 #endif /*]*/
47
48 #include "actions.h"
49 #include "appres.h"
50 #include "child.h"
51 #include "popups.h"
52 #include "child_popups.h"
53 #include "childscript.h"
54 #include "find_console.h"
55 #include "httpd-core.h"
56 #include "httpd-io.h"
57 #include "lazya.h"
58 #include "names.h"
59 #include "peerscript.h"
60 #include "s3270_proto.h"
61 #include "task.h"
62 #include "telnet_core.h"
63 #include "trace.h"
64 #include "utils.h"
65 #include "varbuf.h"
66 #include "w3misc.h"
67
68 #define CHILD_BUF 1024
69 #define DELAYED_CLOSE_MS 3000
70
71 static void child_data(task_cbh handle, const char *buf, size_t len,
72 bool success);
73 static bool child_done(task_cbh handle, bool success, bool abort);
74 static bool child_run(task_cbh handle, bool *success);
75 static void child_closescript(task_cbh handle);
76 static void child_setflags(task_cbh handle, unsigned flags);
77 static unsigned child_getflags(task_cbh handle);
78
79 static void child_setir(task_cbh handle, void *irhandle);
80 static void *child_getir(task_cbh handle);
81 static void child_setir_state(task_cbh handle, const char *name, void *state,
82 ir_state_abort_cb abort_cb);
83 static void *child_getir_state(task_cbh handle, const char *name);
84 static const char *child_command(task_cbh handle);
85 static void child_reqinput(task_cbh handle, const char *buf, size_t len,
86 bool echo);
87
88 static irv_t child_irv = {
89 child_setir,
90 child_getir,
91 child_setir_state,
92 child_getir_state
93 };
94
95 /* Callback block for parent script. */
96 static tcb_t script_cb = {
97 "child",
98 IA_SCRIPT,
99 0,
100 child_data,
101 child_done,
102 child_run,
103 child_closescript,
104 child_setflags,
105 child_getflags,
106 &child_irv,
107 child_command,
108 child_reqinput
109 };
110
111 /* Asynchronous callback block for parent script. */
112 static tcb_t async_script_cb = {
113 "child",
114 IA_SCRIPT,
115 CB_NEW_TASKQ,
116 child_data,
117 child_done,
118 child_run,
119 child_closescript,
120 child_setflags,
121 child_getflags,
122 &child_irv,
123 child_command,
124 child_reqinput,
125 };
126
127 #if !defined(_WIN32) /*[*/
128 /* Callback block for child (creates new taskq). */
129 static tcb_t child_cb = {
130 "child",
131 IA_SCRIPT,
132 CB_NEW_TASKQ,
133 child_data,
134 child_done,
135 child_run,
136 child_closescript,
137 child_setflags,
138 child_getflags,
139 &child_irv,
140 child_command,
141 child_reqinput
142 };
143 #endif /*]*/
144
145 #if defined(_WIN32) /*[*/
146 /* Stdout read context. */
147 typedef struct {
148 HANDLE pipe_rd_handle; /* read handle for pipe */
149 HANDLE pipe_wr_handle; /* write handle for pipe */
150 HANDLE enable_event; /* enable event (emulator to read thread) */
151 HANDLE done_event; /* done event (read thread to emulator) */
152 ioid_t done_id; /* I/O identifier for done event */
153 HANDLE read_thread; /* read thread */
154 char buf[CHILD_BUF]; /* input buffer */
155 DWORD nr; /* length of data in I/O buffer */
156 int error; /* error code for failed read */
157 bool dead; /* read thread has exited, set by read thread */
158 bool collected_eof; /* EOF collected by I/O function */
159 } cr_t;
160 #endif /*]*/
161
162 typedef struct {
163 peer_listen_t peer;
164 hio_listener_t *httpd;
165 } listeners_t;
166
167 /* Child script context. */
168 typedef struct {
169 llist_t llist; /* linkage */
170 char *parent_name; /* cb name */
171 char *command; /* command text */
172 bool is_async; /* true if script is async */
173 bool done; /* true if script is complete */
174 bool success; /* success or failure */
175 ioid_t exit_id; /* I/O identifier for child exit */
176 int exit_status; /* exit status */
177 bool enabled; /* enabled */
178 char *output_buf; /* output buffer */
179 size_t output_buflen; /* size of output buffer */
180 listeners_t listeners; /* listeners for child commands */
181 bool keyboard_lock; /* lock/unlock keyboard while running */
182 unsigned capabilities; /* self-reported capabilities */
183 void *irhandle; /* input request handle */
184 task_cb_ir_state_t ir_state; /* named input request state */
185 #if defined(_WIN32) /*[*/
186 DWORD pid; /* process ID */
187 HANDLE child_handle; /* status collection handle */
188 cr_t cr; /* stdout read context */
189 #else /*][*/
190 char *child_name; /* cb name */
191 pid_t pid; /* process ID */
192 int infd; /* input (to emulator) file descriptor */
193 int outfd; /* output (to script) file descriptor */
194 ioid_t id; /* input I/O identifier */
195 char *buf; /* pending command */
196 size_t buf_len; /* length of pending command */
197 int stdoutpipe; /* stdout pipe */
198 ioid_t stdout_id; /* stdout I/O identifier */
199 #endif /*]*/
200 } child_t;
201 static llist_t child_scripts = LLIST_INIT(child_scripts);
202
203 #if !defined(_WIN32) /*[*/
204 typedef struct {
205 llist_t llist; /* linkage */
206 ioid_t id; /* I/O identifier for timeout */
207 listeners_t listeners; /* listeners */
208 } delayed_close_t;
209 static llist_t delayed_closes = LLIST_INIT(delayed_closes);
210 #endif /*]*/
211
212 /**
213 * Free a child.
214 *
215 * @param[in,out] c Child.
216 */
217 static void
free_child(child_t * c)218 free_child(child_t *c)
219 {
220 llist_unlink(&c->llist);
221 Replace(c->parent_name, NULL);
222 Replace(c->command, NULL);
223 #if !defined(_WIN32) /*[*/
224 Replace(c->child_name, NULL);
225 #endif /*]*/
226 Replace(c->output_buf, NULL);
227 Free(c);
228 }
229
230 /**
231 * Close a set of listeners.
232 *
233 * @param[in] l Listeners.
234 */
235 static void
close_listeners(listeners_t * l)236 close_listeners(listeners_t *l)
237 {
238 if (l->httpd != NULL) {
239 hio_stop_x(l->httpd);
240 l->httpd = NULL;
241 }
242 if (l->peer != NULL) {
243 peer_shutdown(l->peer);
244 l->peer = NULL;
245 }
246 }
247
248 #if !defined(_WIN32) /*[*/
249 /**
250 * Run the next command in the child buffer.
251 *
252 * @param[in,out] c Child
253 *
254 * @return true if command was run. Command is deleted from the buffer.
255 */
256 static bool
run_next(child_t * c)257 run_next(child_t *c)
258 {
259 size_t cmdlen;
260 char *name;
261
262 /* Find a newline in the buffer. */
263 for (cmdlen = 0; cmdlen < c->buf_len; cmdlen++) {
264 if (c->buf[cmdlen] == '\n') {
265 break;
266 }
267 }
268 if (cmdlen >= c->buf_len) {
269 return false;
270 }
271
272 /*
273 * Run the first command.
274 * cmdlen is the number of characters in the command, not including the
275 * newline.
276 */
277 name = push_cb(c->buf, cmdlen, &child_cb, (task_cbh)c);
278 Replace(c->child_name, NewString(name));
279
280 /* If there is more, shift it over. */
281 cmdlen++; /* count the newline */
282 if (c->buf_len > cmdlen) {
283 memmove(c->buf, c->buf + cmdlen, c->buf_len - cmdlen);
284 c->buf_len = c->buf_len - cmdlen;
285 } else {
286 Replace(c->buf, NULL);
287 c->buf_len = 0;
288 }
289 return true;
290 }
291
292 /**
293 * Delayed close of a child script listener.
294 *
295 * @param[in] id I/O identifier.
296 */
297 static void
delayed_close(ioid_t id)298 delayed_close(ioid_t id)
299 {
300 delayed_close_t *dc;
301
302 FOREACH_LLIST(&delayed_closes, dc, delayed_close_t *) {
303 if (dc->id == id) {
304 vtrace("Delayed shutdown of listeners\n");
305 close_listeners(&dc->listeners);
306 llist_unlink(&dc->llist);
307 Free(dc);
308 return;
309 }
310 } FOREACH_LLIST_END(&delayed_closes, dc, delayed_close_t *);
311
312 vtrace("Error: Delayed shutdown record not found\n");
313 }
314
315 /**
316 * Tear down a child.
317 *
318 * @param[in,out] c Child.
319 */
320 static void
close_child(child_t * c)321 close_child(child_t *c)
322 {
323 if (c->is_async && c->listeners.httpd != NULL) {
324 delayed_close_t *dc = Calloc(sizeof(delayed_close_t), 1);
325
326 /*
327 * Delay the close. gnome-terminal, for example, forks and execs a
328 * new process for the console. We need to wait a while for it to
329 * connect.
330 */
331 llist_init(&dc->llist);
332 dc->listeners = c->listeners; /* struct copy */
333 LLIST_APPEND(&dc->llist, delayed_closes);
334 dc->id = AddTimeOut(DELAYED_CLOSE_MS, delayed_close);
335 } else {
336 close_listeners(&c->listeners);
337 }
338 c->listeners.httpd = NULL;
339 c->listeners.peer = NULL;
340 if (c->infd != -1) {
341 close(c->infd);
342 c->infd = -1;
343 }
344 if (c->outfd != -1) {
345 close(c->outfd);
346 c->outfd = -1;
347 }
348 if (c->id != NULL_IOID) {
349 RemoveInput(c->id);
350 c->id = NULL_IOID;
351 }
352 Replace(c->buf, NULL);
353 if (c->stdout_id != NULL_IOID) {
354 RemoveInput(c->stdout_id);
355 c->stdout_id = NULL_IOID;
356 }
357 if (c->stdoutpipe != -1) {
358 close(c->stdoutpipe);
359 c->stdoutpipe = -1;
360 }
361 if (c->irhandle != NULL) {
362 task_abort_input_request_irhandle(c->irhandle);
363 c->irhandle = NULL;
364 }
365 task_cb_abort_ir_state(&c->ir_state);
366
367 /* Abort any pending child. */
368 if (c->child_name != NULL) {
369 abort_queue(c->child_name);
370 }
371 }
372
373 /**
374 * Read the next command from a child pipe.
375 * @param[in] fd File descriptor
376 * @param[in] id I/O identifier
377 */
378 static void
child_input(iosrc_t fd _is_unused,ioid_t id)379 child_input(iosrc_t fd _is_unused, ioid_t id)
380 {
381 child_t *c;
382 bool found_child = false;
383 char buf[8192];
384 size_t n2r;
385 size_t nr;
386 size_t i;
387
388 /* Find the child. */
389 FOREACH_LLIST(&child_scripts, c, child_t *) {
390 if (c->id == id) {
391 found_child = true;
392 break;
393 }
394 } FOREACH_LLIST_END(&child_scripts, c, child_t *);
395 assert(found_child);
396
397 /* Read input. */
398 n2r = sizeof(buf);
399 nr = read(c->infd, buf, (int)n2r);
400 assert(nr >= 0);
401 vtrace("%s input complete, nr=%d\n", c->parent_name, (int)nr);
402 if (nr == 0) {
403 vtrace("%s script EOF\n", c->parent_name);
404 close_child(c);
405 if (c->exit_id == NULL_IOID) {
406 c->done = true;
407 task_activate((task_cbh *)c);
408 }
409 RemoveInput(c->id);
410 c->id = NULL_IOID;
411 close(c->infd);
412 c->infd = -1;
413 return;
414 }
415
416 /* Append, filtering out CRs. */
417 c->buf = Realloc(c->buf, c->buf_len + nr + 1);
418 for (i = 0; i < nr; i++) {
419 char ch = buf[i];
420
421 if (ch != '\r') {
422 c->buf[c->buf_len++] = ch;
423 }
424 }
425
426 /* Disable further input. */
427 if (c->id != NULL_IOID) {
428 RemoveInput(c->id);
429 c->id = NULL_IOID;
430 }
431
432 /* Run the next command, if we have it all. */
433 if (!run_next(c) && c->id == NULL_IOID) {
434 /* Get more input. */
435 c->id = AddInput(c->infd, child_input);
436 }
437 }
438
439 /**
440 * Read output from a child script.
441 * @param[in] fd File descriptor
442 * @param[in] id I/O identifier
443 */
444 static void
child_stdout(iosrc_t fd _is_unused,ioid_t id)445 child_stdout(iosrc_t fd _is_unused, ioid_t id)
446 {
447 child_t *c;
448 bool found_child = false;
449 char buf[8192];
450 size_t n2r;
451 size_t nr;
452 size_t new_buflen;
453
454 /* Find the child. */
455 FOREACH_LLIST(&child_scripts, c, child_t *) {
456 if (c->stdout_id == id) {
457 found_child = true;
458 break;
459 }
460 } FOREACH_LLIST_END(&child_scripts, c, child_t *);
461 assert(found_child);
462
463 /* Read input. */
464 n2r = sizeof(buf);
465 nr = read(fd, buf, (int)n2r);
466 assert(nr >= 0);
467 vtrace("%s stdout read complete, nr=%d\n", c->parent_name, (int)nr);
468 if (nr == 0) {
469 vtrace("%s script stdout EOF\n", c->parent_name);
470 RemoveInput(c->stdout_id);
471 c->stdout_id = NULL_IOID;
472 close(c->stdoutpipe);
473 c->stdoutpipe = -1;
474 return;
475 }
476
477 /* Save it. */
478 new_buflen = c->output_buflen + nr;
479 c->output_buf = Realloc(c->output_buf, new_buflen + 1);
480 memcpy(c->output_buf + c->output_buflen, buf, nr);
481 c->output_buflen = new_buflen;
482 c->output_buf[new_buflen] = '\0';
483 }
484 #endif /*]*/
485
486 /**
487 * Callback for data returned to child script command via a pipe.
488 *
489 * @param[in] handle Callback handle
490 * @param[in] buf Buffer
491 * @param[in] len Buffer length
492 * @param[in] success True if data, false if error message
493 */
494 static void
child_data(task_cbh handle,const char * buf,size_t len,bool success)495 child_data(task_cbh handle, const char *buf, size_t len, bool success)
496 {
497 #if !defined(_WIN32) /*[*/
498 child_t *c = (child_t *)handle;
499 char *s = lazyaf(DATA_PREFIX "%.*s\n", (int)len, buf);
500 ssize_t nw;
501
502 nw = write(c->outfd, s, strlen(s));
503 if (nw != (ssize_t)strlen(s)) {
504 vtrace("child_data: short write\n");
505 }
506 #endif /*]*/
507 }
508
509 /**
510 * Callback for input request
511 *
512 * @param[in] handle Callback handle
513 * @param[in] buf Buffer
514 * @param[in] len Buffer length
515 * @param[in] echo True to echo input
516 */
517 static void
child_reqinput(task_cbh handle,const char * buf,size_t len,bool echo)518 child_reqinput(task_cbh handle, const char *buf, size_t len, bool echo)
519 {
520 #if !defined(_WIN32) /*[*/
521 child_t *c = (child_t *)handle;
522 char *s = lazyaf("%s%.*s\n", echo? INPUT_PREFIX: PWINPUT_PREFIX,
523 (int)len, buf);
524 ssize_t nw;
525
526 nw = write(c->outfd, s, strlen(s));
527 if (nw != (ssize_t)strlen(s)) {
528 vtrace("child_reqinput: short write\n");
529 }
530 #endif /*]*/
531 }
532
533 /**
534 * Callback for completion of one command executed from the child script in
535 * s3270 mode.
536 *
537 * @param[in] handle Callback handle
538 * @param[in] success True if child succeeded
539 * @param[in] abort True if aborting
540 *
541 * @return True of script has terminated
542 */
543 static bool
child_done(task_cbh handle,bool success,bool abort)544 child_done(task_cbh handle, bool success, bool abort)
545 {
546 child_t *c = (child_t *)handle;
547 #if !defined(_WIN32) /*[*/
548 bool new_child;
549 char *prompt;
550 char *s;
551 ssize_t nw;
552
553 if (abort || !c->enabled) {
554 close_listeners(&c->listeners);
555 vtrace("%s terminating script process\n", c->parent_name);
556 kill(c->pid, SIGTERM);
557 if (c->keyboard_lock) {
558 disable_keyboard(ENABLE, IMPLICIT, AnScript "() abort");
559 }
560 return true;
561 }
562
563 /* Print the prompt. */
564 prompt = task_cb_prompt(handle);
565 s = lazyaf("%s\n%s\n", prompt, success? "ok": "error");
566 vtrace("Output for %s: %s/%s\n", c->child_name, prompt,
567 success? "ok": "error");
568 nw = write(c->outfd, s, strlen(s));
569 if (nw != (ssize_t)strlen(s)) {
570 vtrace("child_done: short write\n");
571 }
572
573 /* Run any pending command that we already read in. */
574 new_child = run_next(c);
575 if (!new_child && c->id == NULL_IOID && c->infd != -1) {
576 /* Allow more input. */
577 c->id = AddInput(c->infd, child_input);
578 }
579
580 /*
581 * If there was a new child, we're still active. Otherwise, let our CB
582 * be popped.
583 */
584 return !new_child;
585
586 #else /*][*/
587
588 if (abort) {
589 close_listeners(&c->listeners);
590 vtrace("%s terminating script process\n", c->parent_name);
591 TerminateProcess(c->child_handle, 1);
592 if (c->keyboard_lock) {
593 disable_keyboard(ENABLE, IMPLICIT, AnScript "() abort");
594 }
595 }
596 return true;
597
598 #endif /*]*/
599 }
600
601 #if defined(_WIN32) /*[*/
602 static void
cr_teardown(cr_t * cr)603 cr_teardown(cr_t *cr)
604 {
605 if (cr->pipe_rd_handle != INVALID_HANDLE_VALUE) {
606 CloseHandle(cr->pipe_rd_handle);
607 cr->pipe_rd_handle = INVALID_HANDLE_VALUE;
608 }
609 if (cr->pipe_wr_handle != INVALID_HANDLE_VALUE) {
610 CloseHandle(cr->pipe_wr_handle);
611 cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
612 }
613 if (cr->enable_event != INVALID_HANDLE_VALUE) {
614 CloseHandle(cr->enable_event);
615 cr->enable_event = INVALID_HANDLE_VALUE;
616 }
617 if (cr->done_event != INVALID_HANDLE_VALUE) {
618 CloseHandle(cr->done_event);
619 cr->done_event = INVALID_HANDLE_VALUE;
620 }
621 if (cr->done_id != NULL_IOID) {
622 RemoveInput(cr->done_id);
623 cr->done_id = NULL_IOID;
624 }
625 if (cr->read_thread != INVALID_HANDLE_VALUE) {
626 CloseHandle(cr->read_thread);
627 cr->read_thread = INVALID_HANDLE_VALUE;
628 }
629 }
630
631 /**
632 * Tear down a child.
633 *
634 * @param[in,out] c Child.
635 */
636 static void
close_child(child_t * c)637 close_child(child_t *c)
638 {
639 if (c->child_handle != INVALID_HANDLE_VALUE) {
640 CloseHandle(c->child_handle);
641 c->child_handle = INVALID_HANDLE_VALUE;
642 }
643 close_listeners(&c->listeners);
644 cr_teardown(&c->cr);
645 if (c->irhandle != NULL) {
646 task_abort_input_request_irhandle(c->irhandle);
647 c->irhandle = NULL;
648 }
649 task_cb_abort_ir_state(&c->ir_state);
650 }
651
652 /*
653 * Collect output from the read thread.
654 * Returns true if more input may be available.
655 */
656 static bool
cr_collect(child_t * c)657 cr_collect(child_t *c)
658 {
659 cr_t *cr = &c->cr;
660 if (cr->nr != 0) {
661 vtrace("Got %d bytes of script stdout/stderr\n", (int)cr->nr);
662 if (cr->nr == 2 && !strncmp(cr->buf, "^C", 2)) {
663 /* Hack, hack, hack. */
664 vtrace("Suppressing '^C' output from child\n");
665 } else {
666 c->output_buf = Realloc(c->output_buf,
667 c->output_buflen + cr->nr + 1);
668 memcpy(c->output_buf + c->output_buflen, cr->buf, cr->nr);
669 c->output_buflen += cr->nr;
670 c->output_buf[c->output_buflen] = '\0';
671 }
672
673 /* Ready for more. */
674 cr->nr = 0;
675 }
676 if (cr->dead) {
677 if (cr->error != 0) {
678 vtrace("Script stdout/stderr read failed: %s\n",
679 win32_strerror(cr->error));
680 }
681 cr->collected_eof = true;
682 return false;
683 }
684 SetEvent(cr->enable_event);
685 return true;
686 }
687 #endif /*]*/
688
689 /**
690 * Run vector for child scripts.
691 *
692 * @param[in] handle Context.
693 * @param[out] success Returned true if script succeeded.
694 *
695 * @return True if script is complete.
696 */
697 static bool
child_run(task_cbh handle,bool * success)698 child_run(task_cbh handle, bool *success)
699 {
700 child_t *c = (child_t *)handle;
701
702 if (c->done) {
703 #if defined(_WIN32) /*[*/
704 /* Collect remaining output and let the read thread exit. */
705 cr_t *cr = &c->cr;
706
707 if (!cr->collected_eof) {
708 do {
709 vtrace("Waiting for child final stdout/stderr\n");
710 WaitForSingleObject(cr->done_event, INFINITE);
711 } while (cr_collect(c));
712 }
713 #endif /*]*/
714 if (c->output_buflen) {
715 /* Strip out CRs. */
716 char *tmp = Malloc(strlen(c->output_buf) + 1);
717 char *s = c->output_buf;
718 char *t = tmp;
719 char c;
720
721 while ((c = *s++) != '\0') {
722 if (c != '\r') {
723 *t++ = c;
724 }
725 }
726 *t = '\0';
727 action_output("%s", tmp);
728 Free(tmp);
729 }
730 close_child(c);
731 if (!c->success) {
732 #if !defined(_WIN32) /*[*/
733 if (WIFEXITED(c->exit_status)) {
734 popup_an_error("Script exited with status %d",
735 WEXITSTATUS(c->exit_status));
736 } else if (WIFSIGNALED(c->exit_status)) {
737 popup_an_error("Script killed by signal %d",
738 WTERMSIG(c->exit_status));
739 } else {
740 popup_an_error("Script stopped by unknown status %d",
741 c->exit_status);
742 }
743 #else /*][*/
744 popup_an_error("Script exited with status %d",
745 c->exit_status);
746 #endif /*]*/
747 }
748 *success = c->success;
749 if (c->keyboard_lock) {
750 disable_keyboard(ENABLE, IMPLICIT, "Script() completion");
751 }
752 free_child(c);
753 return true;
754 }
755
756 return false;
757 }
758
759 /**
760 * Close a running child script.
761 *
762 * @param[in] handle Child context
763 */
764 static void
child_closescript(task_cbh handle)765 child_closescript(task_cbh handle)
766 {
767 child_t *c = (child_t *)handle;
768
769 c->enabled = false;
770 }
771
772 /**
773 * Set capabilities flags.
774 *
775 * @param[in] handle Child context
776 * @param[in] flags Flags
777 */
778 static void
child_setflags(task_cbh handle,unsigned flags)779 child_setflags(task_cbh handle, unsigned flags)
780 {
781 child_t *c = (child_t *)handle;
782
783 c->capabilities = flags;
784 }
785
786 /**
787 * Get capabilities flags.
788 *
789 * @param[in] handle Child context
790 * @returns flags
791 */
792 static unsigned
child_getflags(task_cbh handle)793 child_getflags(task_cbh handle)
794 {
795 child_t *c = (child_t *)handle;
796
797 return c->capabilities;
798 }
799
800 /**
801 * Set the pending input request.
802 *
803 * @param[in] handle Child context
804 * @param[in] irhandle Input request handle
805 */
806 static void
child_setir(task_cbh handle,void * irhandle)807 child_setir(task_cbh handle, void *irhandle)
808 {
809 child_t *c = (child_t *)handle;
810
811 c->irhandle = irhandle;
812 }
813
814 /**
815 * Get the pending input request.
816 *
817 * @param[in] handle Child context
818 *
819 * @returns input request handle
820 */
821 static void *
child_getir(task_cbh handle)822 child_getir(task_cbh handle)
823 {
824 child_t *c = (child_t *)handle;
825
826 return c->irhandle;
827 }
828
829 /**
830 * Set input request state.
831 *
832 * @param[in] handle CB handle
833 * @param[in] name Input request type name
834 * @param[in] state State to store
835 * @param[in] abort Abort callback
836 */
837 static void
child_setir_state(task_cbh handle,const char * name,void * state,ir_state_abort_cb abort)838 child_setir_state(task_cbh handle, const char *name, void *state,
839 ir_state_abort_cb abort)
840 {
841 child_t *c = (child_t *)handle;
842
843 task_cb_set_ir_state(&c->ir_state, name, state, abort);
844 }
845
846 /**
847 * Get input request state.
848 *
849 * @param[in] handle CB handle
850 * @param[in] name Input request type name
851 *
852 * @returns input request state
853 */
854 static void *
child_getir_state(task_cbh handle,const char * name)855 child_getir_state(task_cbh handle, const char *name)
856 {
857 child_t *c = (child_t *)handle;
858
859 return task_cb_get_ir_state(&c->ir_state, name);
860 }
861
862 /**
863 * Get the command text.
864 *
865 * @param[in] handle CB handle
866 *
867 * @returns command text, or NULL
868 */
869 static const char *
child_command(task_cbh handle)870 child_command(task_cbh handle)
871 {
872 child_t *c = (child_t *)handle;
873
874 return c->command;
875 }
876
877 #if !defined(_WIN32) /*[*/
878 static void
child_exited(ioid_t id,int status)879 child_exited(ioid_t id, int status)
880 {
881 child_t *c;
882 bool found_child = false;
883
884 FOREACH_LLIST(&child_scripts, c, child_t *) {
885 if (c->exit_id == id) {
886 found_child = true;
887 break;
888 }
889 } FOREACH_LLIST_END(&child_scripts, c, child_t *);
890
891 if (!found_child) {
892 vtrace("child_exited: no match\n");
893 return;
894 }
895
896 vtrace("%s script %d exited with status %d\n",
897 (c->child_name != NULL) ? c->child_name : "socket",
898 (int)c->pid, status);
899
900 c->exit_status = status;
901 if (status != 0) {
902 c->success = false;
903 }
904 c->exit_id = NULL_IOID;
905 if (c->id == NULL_IOID) {
906 /* This task should be run. */
907 c->done = true;
908 task_activate((task_cbh *)c);
909 }
910 }
911 #endif /*]*/
912
913 /* Let the system pick a TCP port to bind to. */
914 static unsigned short
pick_port(socket_t * sp)915 pick_port(socket_t *sp)
916 {
917 socket_t s;
918 struct sockaddr_in sin;
919 socklen_t len;
920 int on = 1;
921
922 s = socket(PF_INET, SOCK_STREAM, 0);
923 if (s == INVALID_SOCKET) {
924 popup_a_sockerr("socket");
925 return 0;
926 }
927 memset(&sin, '\0', sizeof(sin));
928 sin.sin_family = AF_INET;
929 sin.sin_addr.s_addr = htonl(INADDR_ANY);
930 if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
931 popup_a_sockerr("bind");
932 SOCK_CLOSE(s);
933 return 0;
934 }
935 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) {
936 popup_a_sockerr("setsockopt");
937 SOCK_CLOSE(s);
938 return 0;
939 }
940 len = sizeof(sin);
941 if (getsockname(s, (struct sockaddr *)&sin, &len) < 0) {
942 popup_a_sockerr("getsockaddr");
943 SOCK_CLOSE(s);
944 return 0;
945 }
946 *sp = s;
947 return ntohs(sin.sin_port);
948 }
949
950 #if defined(_WIN32) /*[*/
951 /* Process an event on a child script handle (a process exit). */
952 static void
child_exited(iosrc_t fd _is_unused,ioid_t id _is_unused)953 child_exited(iosrc_t fd _is_unused, ioid_t id _is_unused)
954 {
955 child_t *c;
956 bool found_child = false;
957 DWORD status;
958
959 FOREACH_LLIST(&child_scripts, c, child_t *) {
960 if (c->exit_id == id) {
961 found_child = true;
962 break;
963 }
964 } FOREACH_LLIST_END(&child_scripts, c, child_t *);
965
966 if (!found_child) {
967 vtrace("child_exited: no match\n");
968 return;
969 }
970
971 status = 0;
972 if (GetExitCodeProcess(c->child_handle, &status) == 0) {
973 popup_an_error("GetExitCodeProcess failed: %s",
974 win32_strerror(GetLastError()));
975 } else if (status != STILL_ACTIVE) {
976 vtrace("%s script exited with status %d\n", c->parent_name,
977 (unsigned)status);
978 c->exit_status = status;
979 if (status != 0) {
980 c->success = false;
981 }
982 CloseHandle(c->child_handle);
983 c->child_handle = INVALID_HANDLE_VALUE;
984 RemoveInput(c->exit_id);
985 c->exit_id = NULL_IOID;
986
987 /* This task should be run. */
988 c->done = true;
989 task_activate((task_cbh *)c);
990 }
991 }
992
993 /* Read from the child's stdout or stderr. */
994 static DWORD WINAPI
child_read_thread(LPVOID parameter)995 child_read_thread(LPVOID parameter)
996 {
997 child_t *child = (child_t *)parameter;
998 cr_t *cr = &child->cr;
999 DWORD success;
1000 bool done = false;
1001
1002 while (!done) {
1003 switch (WaitForSingleObject(cr->enable_event, INFINITE)) {
1004 case WAIT_OBJECT_0:
1005 success = ReadFile(cr->pipe_rd_handle, cr->buf, CHILD_BUF, &cr->nr,
1006 NULL);
1007 if (!success) {
1008 /* Canceled or pipe broken. */
1009 cr->error = GetLastError();
1010 done = true;
1011 break;
1012 }
1013 SetEvent(cr->done_event);
1014 break;
1015 default:
1016 cr->error = GetLastError();
1017 done = true;
1018 break;
1019 }
1020 }
1021
1022 /* All done, I hope. */
1023 cr->nr = 0;
1024 cr->dead = true;
1025 SetEvent(cr->done_event);
1026 return 0;
1027 }
1028
1029 /* The child stdout/stderr thread produced output. */
1030 static void
cr_output(iosrc_t fd,ioid_t id)1031 cr_output(iosrc_t fd, ioid_t id)
1032 {
1033 child_t *c;
1034 bool found_child = false;
1035
1036 /* Find the descriptor. */
1037 FOREACH_LLIST(&child_scripts, c, child_t *) {
1038 if (c->cr.done_id == id) {
1039 found_child = true;
1040 break;
1041 }
1042 } FOREACH_LLIST_END(&child_scripts, c, child_t *);
1043 assert(found_child);
1044
1045 /* Collect the output. */
1046 cr_collect(c);
1047 }
1048
1049 /* Set up the stdout reader context. */
1050 static bool
setup_cr(child_t * c)1051 setup_cr(child_t *c)
1052 {
1053 cr_t *cr = &c->cr;
1054 SECURITY_ATTRIBUTES sa;
1055 DWORD mode;
1056
1057 /* Create the pipe. */
1058 memset(&sa, 0, sizeof(sa));
1059 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
1060 sa.bInheritHandle = TRUE;
1061 sa.lpSecurityDescriptor = NULL;
1062 if (!CreatePipe(&cr->pipe_rd_handle, &cr->pipe_wr_handle, &sa, 0)) {
1063 popup_an_error("CreatePipe() failed: %s",
1064 win32_strerror(GetLastError()));
1065 return false;
1066 }
1067 if (!SetHandleInformation(cr->pipe_rd_handle, HANDLE_FLAG_INHERIT, 0)) {
1068 popup_an_error("SetHandleInformation() failed: %s",
1069 win32_strerror(GetLastError()));
1070 CloseHandle(cr->pipe_rd_handle);
1071 CloseHandle(cr->pipe_wr_handle);
1072 return false;
1073 }
1074 mode = PIPE_READMODE_BYTE;
1075 if (!SetNamedPipeHandleState(cr->pipe_rd_handle, &mode, NULL, NULL)) {
1076 popup_an_error("SetNamedPipeHandleState(stdout) failed: %s",
1077 win32_strerror(GetLastError()));
1078 CloseHandle(cr->pipe_rd_handle);
1079 CloseHandle(cr->pipe_wr_handle);
1080 return false;
1081 }
1082
1083 /* Express interest in their output. */
1084 cr->enable_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1085 cr->done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
1086 cr->read_thread = CreateThread(NULL, 0, child_read_thread, c, 0, NULL);
1087 cr->done_id = AddInput(cr->done_event, cr_output);
1088
1089 return true;
1090 }
1091 #endif /*]*/
1092
1093
1094 /* "Script" action, runs a script as a child process. */
1095 bool
Script_action(ia_t ia,unsigned argc,const char ** argv)1096 Script_action(ia_t ia, unsigned argc, const char **argv)
1097 {
1098 child_t *c;
1099 char *name;
1100 bool async = false;
1101 bool keyboard_lock = true;
1102 bool stdout_redirect = true;
1103 peer_listen_mode mode = PLM_MULTI;
1104 listeners_t listeners;
1105 varbuf_t r;
1106 unsigned i;
1107 socket_t s;
1108 unsigned short httpd_port;
1109 unsigned short script_port;
1110 struct sockaddr_in *sin;
1111 #if !defined(_WIN32) /*[*/
1112 pid_t pid;
1113 int inpipe[2] = { -1, -1 };
1114 int outpipe[2] = { -1, -1 };
1115 int stdoutpipe[2];
1116 #else /*][*/
1117 bool share_console = false;
1118 STARTUPINFO startupinfo;
1119 PROCESS_INFORMATION process_information;
1120 char *args;
1121 cr_t *cr;
1122 #endif /*]*/
1123
1124 action_debug(AnScript, ia, argc, argv);
1125
1126 for (;;) {
1127 if (argc < 1) {
1128 popup_an_error(AnScript "() requires at least one argument");
1129 return false;
1130 }
1131 if (!strcasecmp(argv[0], KwDashAsync)) {
1132 async = true;
1133 keyboard_lock = false;
1134 argc--;
1135 argv++;
1136 } else if (!strcasecmp(argv[0], KwDashNoLock)) {
1137 keyboard_lock = false;
1138 argc--;
1139 argv++;
1140 } else if (!strcasecmp(argv[0], KwDashSingle)) {
1141 mode = PLM_SINGLE;
1142 argc--;
1143 argv++;
1144 } else if (!strcasecmp(argv[0], KwDashNoStdoutRedirect)) {
1145 stdout_redirect = false;
1146 argc--;
1147 argv++;
1148 #if defined(_WIN32) /*[*/
1149 } else if (!strcasecmp(argv[0], KwDashShareConsole)) {
1150 share_console = true;
1151 argc--;
1152 argv++;
1153 #endif /*]*/
1154 } else {
1155 break;
1156 }
1157 }
1158
1159 listeners.peer = NULL;
1160 listeners.httpd = NULL;
1161
1162 /* Set up X3270PORT or X3270URL for the child process. */
1163 httpd_port = pick_port(&s);
1164 if (httpd_port == 0) {
1165 return false;
1166 }
1167 sin = (struct sockaddr_in *)Calloc(1, sizeof(struct sockaddr_in));
1168 sin->sin_family = AF_INET;
1169 sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1170 sin->sin_port = htons(httpd_port);
1171 listeners.httpd = hio_init_x((struct sockaddr *)sin, sizeof(*sin));
1172 SOCK_CLOSE(s);
1173 if (listeners.httpd == NULL) {
1174 return false;
1175 }
1176
1177 script_port = pick_port(&s);
1178 if (script_port == 0) {
1179 hio_stop_x(listeners.httpd);
1180 return false;
1181 }
1182 sin = (struct sockaddr_in *)Calloc(1, sizeof(struct sockaddr_in));
1183 sin->sin_family = AF_INET;
1184 sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1185 sin->sin_port = htons(script_port);
1186 listeners.peer = peer_init((struct sockaddr *)sin, sizeof(*sin), mode);
1187 SOCK_CLOSE(s);
1188 if (listeners.peer == NULL) {
1189 hio_stop_x(listeners.httpd);
1190 return false;
1191 }
1192
1193 putenv(lazyaf(URL_ENV "=http://127.0.0.1:%u/3270/rest/", httpd_port));
1194 putenv(lazyaf(PORT_ENV "=%d", script_port));
1195
1196 #if !defined(_WIN32) /*[*/
1197 /*
1198 * Create pipes and stdout stream for the script process.
1199 * inpipe[] is read by x3270, written by the script
1200 * outpipe[] is written by x3270, read by the script
1201 */
1202 if (pipe(inpipe) < 0) {
1203 popup_an_error("pipe() failed");
1204 close_listeners(&listeners);
1205 return false;
1206 }
1207 if (pipe(outpipe) < 0) {
1208 popup_an_error("pipe() failed");
1209 close(inpipe[0]);
1210 close(inpipe[1]);
1211 close_listeners(&listeners);
1212 return false;
1213 }
1214
1215 /* Create a pipe to capture child stdout. */
1216 if (pipe(stdoutpipe) < 0) {
1217 popup_an_error("pipe() failed");
1218 close(outpipe[0]);
1219 close(outpipe[1]);
1220 close(inpipe[0]);
1221 close(inpipe[1]);
1222 close_listeners(&listeners);
1223 }
1224
1225 /* Fork and exec the script process. */
1226 if ((pid = fork()) < 0) {
1227 popup_an_error("fork() failed");
1228 close(inpipe[0]);
1229 close(inpipe[1]);
1230 close(outpipe[0]);
1231 close(outpipe[1]);
1232 close(stdoutpipe[0]);
1233 close(stdoutpipe[1]);
1234 close_listeners(&listeners);
1235 return false;
1236 }
1237
1238 /* Child processing. */
1239 if (pid == 0) {
1240 char **child_argv;
1241 unsigned i;
1242
1243 /* Become a process group. */
1244 setsid();
1245
1246 /* Clean up the pipes. */
1247 close(outpipe[1]);
1248 close(inpipe[0]);
1249 close(stdoutpipe[0]);
1250
1251 /* Redirect output. */
1252 if (stdout_redirect) {
1253 dup2(stdoutpipe[1], 1);
1254 } else {
1255 dup2(open("/dev/null", O_WRONLY), 1);
1256 }
1257 dup2(stdoutpipe[1], 2);
1258
1259 /* Export the names of the pipes into the environment. */
1260 putenv(xs_buffer(OUTPUT_ENV "=%d", outpipe[0]));
1261 putenv(xs_buffer(INPUT_ENV "=%d", inpipe[1]));
1262
1263 /* Set up arguments. */
1264 child_argv = (char **)Malloc((argc + 1) * sizeof(char *));
1265 for (i = 0; i < argc; i++) {
1266 child_argv[i] = (char *)argv[i];
1267 }
1268 child_argv[i] = NULL;
1269
1270 /* Exec. */
1271 execvp(argv[0], child_argv);
1272 fprintf(stderr, "exec(%s) failed\n", argv[0]);
1273 _exit(1);
1274 }
1275
1276 c = (child_t *)Calloc(1, sizeof(child_t));
1277 llist_init(&c->llist);
1278 LLIST_APPEND(&c->llist, child_scripts);
1279 c->success = true;
1280 c->done = false;
1281 c->buf = NULL;
1282 c->buf_len = 0;
1283 c->pid = pid;
1284 c->exit_id = AddChild(pid, child_exited);
1285 c->enabled = true;
1286 c->stdoutpipe = stdoutpipe[0];
1287 task_cb_init_ir_state(&c->ir_state);
1288
1289 /* Clean up our ends of the pipes. */
1290 c->infd = inpipe[0];
1291 close(inpipe[1]);
1292 c->outfd = outpipe[1];
1293 close(outpipe[0]);
1294 close(stdoutpipe[1]);
1295
1296 /* Link the listeners. */
1297 c->listeners = listeners; /* struct copy */
1298
1299 /* Allow child pipe input. */
1300 c->id = AddInput(c->infd, child_input);
1301
1302 /* Capture child output. */
1303 c->stdout_id = AddInput(c->stdoutpipe, child_stdout);
1304
1305 #else /*]*/
1306
1307 /* Set up the stdout/stderr output pipes. */
1308 c = (child_t *)Calloc(1, sizeof(child_t));
1309 if (!setup_cr(c)) {
1310 Free(c);
1311 return false;
1312 }
1313 task_cb_init_ir_state(&c->ir_state);
1314 cr = &c->cr;
1315
1316 /* Start the child process. */
1317 memset(&startupinfo, '\0', sizeof(STARTUPINFO));
1318 startupinfo.cb = sizeof(STARTUPINFO);
1319 if (stdout_redirect) {
1320 startupinfo.hStdOutput = cr->pipe_wr_handle;
1321 }
1322 startupinfo.hStdError = cr->pipe_wr_handle;
1323 startupinfo.dwFlags |= STARTF_USESTDHANDLES;
1324 memset(&process_information, '\0', sizeof(PROCESS_INFORMATION));
1325 args = NewString(argv[0]);
1326 for (i = 1; i < argc; i++) {
1327 char *t;
1328
1329 if (strchr(argv[i], ' ') != NULL &&
1330 argv[i][0] != '"' &&
1331 argv[i][strlen(argv[i]) - 1] != '"') {
1332 t = xs_buffer("%s \"%s\"", args, argv[i]);
1333 } else {
1334 t = xs_buffer("%s %s", args, argv[i]);
1335 }
1336 Free(args);
1337 args = t;
1338 }
1339 if (CreateProcess(NULL, args, NULL, NULL, TRUE,
1340 (stdout_redirect && !share_console)? DETACHED_PROCESS: 0,
1341 NULL, NULL, &startupinfo, &process_information) == 0) {
1342 popup_an_error("CreateProcess(%s) failed: %s", argv[0],
1343 win32_strerror(GetLastError()));
1344 close_listeners(&listeners);
1345
1346 /* Let the read thread complete. */
1347 CloseHandle(cr->pipe_wr_handle);
1348 cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
1349 SetEvent(cr->enable_event);
1350 WaitForSingleObject(cr->done_event, INFINITE);
1351
1352 cr_teardown(cr);
1353 Free(c);
1354 Free(args);
1355 return false;
1356 }
1357
1358 Free(args);
1359 CloseHandle(process_information.hThread);
1360 CloseHandle(cr->pipe_wr_handle);
1361 cr->pipe_wr_handle = INVALID_HANDLE_VALUE;
1362 SetEvent(cr->enable_event);
1363
1364 /* Create a new script description. */
1365 llist_init(&c->llist);
1366 LLIST_APPEND(&c->llist, child_scripts);
1367 c->success = true;
1368 c->done = false;
1369 c->child_handle = process_information.hProcess;
1370 c->pid = (int)process_information.dwProcessId;
1371 c->listeners = listeners; /* struct copy */
1372 c->enabled = true;
1373
1374 /*
1375 * Wait for the child process to exit.
1376 * Note that this is an asynchronous event -- exits for multiple
1377 * children can happen in any order.
1378 */
1379 c->exit_id = AddInput(process_information.hProcess, child_exited);
1380
1381 #endif /*]*/
1382
1383 /* Save the arguments. */
1384 vb_init(&r);
1385 for (i = 0; i < argc; i++) {
1386 if (i > 0) {
1387 vb_appends(&r, ",");
1388 }
1389 vb_appends(&r, argv[i]);
1390 }
1391 c->command = vb_consume(&r);
1392
1393 /* Create the context. It will be idle. */
1394 c->is_async = async;
1395 c->keyboard_lock = keyboard_lock;
1396 name = push_cb(NULL, 0, async? &async_script_cb: &script_cb, (task_cbh)c);
1397 Replace(c->parent_name, NewString(name));
1398 vtrace("%s script process is %d\n", c->parent_name, (int)c->pid);
1399
1400 if (keyboard_lock) {
1401 disable_keyboard(DISABLE, IMPLICIT, AnScript "() start");
1402 }
1403
1404 return true;
1405 }
1406
1407 /*
1408 * Start an x3270if-based interactive console, optionally overriding the app
1409 * name used as the prompt.
1410 *
1411 * Prompt([prompt[,help-action][,i18n-file]])
1412 */
1413 bool
Prompt_action(ia_t ia,unsigned argc,const char ** argv)1414 Prompt_action(ia_t ia, unsigned argc, const char **argv)
1415 {
1416 const char *params[3] = { programname, NULL, NULL };
1417 unsigned i;
1418 const char **nargv = NULL;
1419 int nargc = 0;
1420 #if !defined(_WIN32) /*[*/
1421 console_desc_t *t;
1422 const char *errmsg;
1423 #endif /*]*/
1424
1425 action_debug(AnPrompt, ia, argc, argv);
1426 if (check_argc(AnPrompt, argc, 0, 3) < 0) {
1427 return false;
1428 }
1429
1430 #if !defined(_WIN32) /*[*/
1431 /* Find a console emulator to run the prompt in. */
1432 t = find_console(&errmsg);
1433 if (t == NULL) {
1434 popup_an_error(AnPrompt "(): console program:\n%s", errmsg);
1435 return false;
1436 }
1437
1438 /* Make sure x3270if is available. */
1439 if (!find_in_path("x3270if")) {
1440 popup_an_error(AnPrompt "(): can't find x3270if");
1441 return false;
1442 }
1443 #endif /*]*/
1444
1445 if (appres.alias != NULL) {
1446 params[0] = appres.alias;
1447 }
1448 #if defined(_WIN32) /*[*/
1449 else {
1450 size_t sl = strlen(params[0]);
1451
1452 if (sl > 4 && !strcasecmp(params[0] + sl - 4, ".exe")) {
1453 params[0] = lazyaf("%.*s", (int)(sl - 4), params[0]);
1454 }
1455 }
1456 #endif /*]*/
1457
1458 for (i = 0; i < argc; i++) {
1459 const char *in = argv[i];
1460 char *new_param = lazya(NewString(argv[i]));
1461 char *out = new_param;
1462 char c;
1463
1464 while ((c = *in++)) {
1465 if (c != '\'' && c != '"' && (i == 2 || !isspace((int)c))) {
1466 *out++ = c;
1467 }
1468 }
1469 *out = '\0';
1470 if (strlen(new_param) > 0) {
1471 params[i] = new_param;
1472 }
1473 }
1474
1475 array_add(&nargv, nargc++, KwDashAsync);
1476 array_add(&nargv, nargc++, KwDashSingle);
1477 #if !defined(_WIN32) /*[*/
1478 nargc = console_args(t, lazyaf("%s>", params[0]), &nargv, nargc);
1479 array_add(&nargv, nargc++, "/bin/sh");
1480 array_add(&nargv, nargc++, "-c");
1481 array_add(&nargv, nargc++,
1482 lazyaf("x3270if -I '%s'%s%s || (echo 'Press <Enter>'; read x)",
1483 params[0],
1484 (params[1] != NULL)? lazyaf(" -H '%s'", params[1]): "",
1485 (params[2] != NULL)? lazyaf(" -L '%s'", params[2]): ""));
1486 #else /*][*/
1487 array_add(&nargv, nargc++, KwDashSingle);
1488 array_add(&nargv, nargc++, "cmd.exe");
1489 array_add(&nargv, nargc++, "/c");
1490 array_add(&nargv, nargc++, "start");
1491 array_add(&nargv, nargc++, lazyaf("\"%s\"", params[0]));
1492 array_add(&nargv, nargc++, "/wait");
1493 array_add(&nargv, nargc++, "x3270if.exe");
1494 array_add(&nargv, nargc++, "-I");
1495 array_add(&nargv, nargc++, params[0]);
1496 if (params[1] != NULL) {
1497 array_add(&nargv, nargc++, "-H");
1498 array_add(&nargv, nargc++, params[1]);
1499 }
1500 if (params[2] != NULL) {
1501 array_add(&nargv, nargc++, "-L");
1502 array_add(&nargv, nargc++, lazyaf("\"%s\"", params[2]));
1503 }
1504 #endif /*]*/
1505 array_add(&nargv, nargc++, NULL);
1506 lazya((void *)nargv);
1507
1508 return Script_action(ia, nargc - 1, nargv);
1509 }
1510