aboutsummaryrefslogtreecommitdiff
path: root/src/test_pty.c
diff options
context:
space:
mode:
authorng0 <ng0@n0.is>2019-04-13 16:43:08 +0000
committerng0 <ng0@n0.is>2019-04-13 16:43:08 +0000
commit83cf7d019c8421f50637e6ac1bfc60682a101c01 (patch)
tree88cb22e93e97aadd893037afe413a26cdadead62 /src/test_pty.c
downloadmsh-83cf7d019c8421f50637e6ac1bfc60682a101c01.tar.gz
msh-83cf7d019c8421f50637e6ac1bfc60682a101c01.zip
Re-init from old SVN checkout after data loss in 2019.HEADmaster
No history is preserved as our svn server is long gone.
Diffstat (limited to 'src/test_pty.c')
-rw-r--r--src/test_pty.c666
1 files changed, 666 insertions, 0 deletions
diff --git a/src/test_pty.c b/src/test_pty.c
new file mode 100644
index 0000000..b1efae1
--- /dev/null
+++ b/src/test_pty.c
@@ -0,0 +1,666 @@
1#include "common.h"
2#include <gnunet/gnunet_util_lib.h>
3#include <termios.h>
4#include "mtypes.h"
5
6/****************************************************************************/
7/* Implemented after http://rachid.koucha.free.fr/tech_corner/pty_pdip.html */
8/****************************************************************************/
9
10#define LOG(kind,...) \
11 GNUNET_log (kind, __VA_ARGS__)
12
13#define LOG_DEBUG(...) LOG(GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
14
15#define LOG_ERROR(...) LOG(GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__)
16
17#define TIMEOUT(secs) \
18 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, secs)
19
20#define DEFAULT_TIMEOUT TIMEOUT(30)
21
22#define BUF_SIZE 512
23
24/**
25 * handler for SIGCHLD.l Register it just before forking
26 */
27static struct GNUNET_SIGNAL_Context *shc_chld;
28
29/**
30 * Task to kill the child
31 */
32static GNUNET_SCHEDULER_TaskIdentifier child_death_task;
33
34/**
35 * Task to read output from child
36 */
37static GNUNET_SCHEDULER_TaskIdentifier read_child_task;
38
39/**
40 * Pipe used to communicate shutdown via signal.
41 */
42static struct GNUNET_DISK_PipeHandle *sigpipe;
43
44static struct GNUNET_DISK_FileHandle *chld_io;
45
46static struct GNUNET_CONNECTION_Handle *conn;
47
48static struct GNUNET_CONNECTION_TransmitHandle *tx;
49
50static char **chld_argv;
51
52static char *cp;
53static struct termios tio;
54static struct winsize ws;
55
56static int in_receive;
57
58static char buf[BUF_SIZE];
59static size_t buf_off;
60
61/**
62 * The process id of the child
63 */
64static pid_t child_pid;
65
66/**
67 * Function to copy NULL terminated list of arguments
68 *
69 * @param argv the NULL terminated list of arguments. Cannot be NULL.
70 * @return the copied NULL terminated arguments
71 */
72static char **
73copy_argv (char *const *argv)
74{
75 char **argv_dup;
76 unsigned int argp;
77
78 GNUNET_assert (NULL != argv);
79 for (argp = 0; NULL != argv[argp]; argp++) ;
80 argv_dup = GNUNET_malloc (sizeof (char *) * (argp + 1));
81 for (argp = 0; NULL != argv[argp]; argp++)
82 argv_dup[argp] = strdup (argv[argp]);
83 return argv_dup;
84}
85
86
87/**
88 * Frees the given NULL terminated arguments
89 *
90 * @param argv the NULL terminated list of arguments
91 */
92static void
93free_argv (char **argv)
94{
95 unsigned int argp;
96
97 for (argp = 0; NULL != argv[argp]; argp++)
98 GNUNET_free (argv[argp]);
99 GNUNET_free (argv);
100}
101
102
103/**
104 * shutdown task
105 */
106static void
107do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
108{
109 if (NULL != chld_argv)
110 {
111 free_argv (chld_argv);
112 chld_argv = NULL;
113 }
114 if (NULL != conn)
115 {
116 if (in_receive)
117 GNUNET_CONNECTION_receive_cancel (conn);
118 if (NULL != tx)
119 GNUNET_CONNECTION_notify_transmit_ready_cancel (tx);
120 GNUNET_CONNECTION_destroy (conn);
121 conn = NULL;
122 }
123 if (0 != child_pid)
124 {
125 GNUNET_break (0 == kill (child_pid, SIGTERM));
126 LOG_DEBUG ("Child still running. Delaying shutdown\n");
127 (void) GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
128 &do_shutdown, NULL);
129 return;
130 }
131 if (GNUNET_SCHEDULER_NO_TASK != child_death_task)
132 {
133 GNUNET_SCHEDULER_cancel (child_death_task);
134 child_death_task = GNUNET_SCHEDULER_NO_TASK;
135 }
136 if (NULL != chld_io)
137 GNUNET_DISK_file_close (chld_io);
138}
139
140
141/**
142 * Read the child processes output and send it to over network connection to waiter
143 */
144static void
145read_child (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
146
147
148/**
149 * Function called to notify a client about the connection
150 * begin ready to queue more data. "buf" will be
151 * NULL and "size" zero if the connection was closed for
152 * writing in the meantime.
153 *
154 * @param cls closure
155 * @param size number of bytes available in buf
156 * @param netbuf where the callee should write the message
157 * @return number of bytes written to buf
158 */
159static size_t
160do_tx (void *cls, size_t size, void *netbuf)
161{
162 tx = NULL;
163 if ((NULL == buf) || (0 == size))
164 {
165 LOG_ERROR ("Failure in connectivity\n");
166 GNUNET_SCHEDULER_shutdown ();
167 return 0;
168 }
169 GNUNET_assert (buf_off <= size);
170 (void) memcpy (netbuf, buf, buf_off);
171 size = buf_off;
172 buf_off = 0;
173 if (GNUNET_SCHEDULER_NO_TASK == read_child_task)
174 read_child_task =
175 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io,
176 &read_child, NULL);
177 return size;
178}
179
180
181/**
182 * Read the child processes output and send it to over network connection to waiter
183 */
184static void
185read_child (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
186{
187 ssize_t rsize;
188
189 read_child_task = GNUNET_SCHEDULER_NO_TASK;
190 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
191 return;
192 if (BUF_SIZE == buf_off)
193 {
194 GNUNET_assert (NULL != tx);
195 return;
196 }
197 rsize = GNUNET_DISK_file_read (chld_io, buf + buf_off, BUF_SIZE - buf_off);
198 if (rsize <= 0)
199 {
200 LOG_DEBUG ("Child stdout closed\n");
201 return;
202 }
203 buf_off += rsize;
204 if (NULL != tx)
205 GNUNET_CONNECTION_notify_transmit_ready_cancel (tx);
206 tx = GNUNET_CONNECTION_notify_transmit_ready (conn, buf_off, DEFAULT_TIMEOUT,
207 &do_tx, NULL);
208 if (BUF_SIZE == buf_off)
209 return;
210 read_child_task =
211 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io,
212 &read_child, NULL);
213}
214
215
216/**
217 * Task triggered whenever we receive a SIGCHLD (child
218 * process died).
219 *
220 * @param cls closure, NULL if we need to self-restart
221 * @param tc context
222 */
223static void
224child_died (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
225{
226 const struct GNUNET_DISK_FileHandle *pr;
227 char c[16];
228
229 pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
230 child_death_task = GNUNET_SCHEDULER_NO_TASK;
231 if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
232 {
233 child_death_task =
234 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
235 pr, &child_died, NULL);
236 return;
237 }
238 /* consume the signal */
239 GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
240 LOG_DEBUG ("Child died\n");
241 child_pid = 0;
242 GNUNET_SCHEDULER_shutdown ();
243 /* FIXME: read any of the left over output from child? */
244}
245
246
247/**
248 * Signal handler called for SIGCHLD.
249 */
250static void
251sighandler_child_death ()
252{
253 static char c;
254 int old_errno = errno; /* back-up errno */
255
256 GNUNET_break (1 ==
257 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
258 (sigpipe, GNUNET_DISK_PIPE_END_WRITE),
259 &c, sizeof (c)));
260 errno = old_errno; /* restore errno */
261}
262
263
264/**
265 * Configure the terminal for the child
266 */
267static int
268parse_term_mode (const struct MSH_MSG_PtyMode *msg)
269{
270 uint16_t *params;
271 unsigned int cnt;
272 unsigned int ns;
273 uint16_t msize;
274 size_t expected;
275 speed_t ospeed;
276 speed_t ispeed;
277
278 msize = ntohs (msg->header.size);
279 ns = ntohs (msg->nsettings);
280 expected = (sizeof (struct MSH_MSG_PtyMode) + (sizeof (uint16_t) * ns * 2));
281 if (msize < expected)
282 {
283 GNUNET_break (0);
284 return GNUNET_SYSERR;
285 }
286#define PARSE_WIN_SIZE(field) \
287 ws.field = ntohs (msg->field);
288 PARSE_WIN_SIZE (ws_row);
289 PARSE_WIN_SIZE (ws_col);
290 PARSE_WIN_SIZE (ws_xpixel);
291 PARSE_WIN_SIZE (ws_ypixel);
292#undef PARSE_WIN_SIZE
293 ospeed = baud_to_speed (ntohl (msg->ospeed));
294 ispeed = baud_to_speed (ntohl (msg->ispeed));
295 if (0 != cfsetospeed (&tio, ospeed))
296 {
297 GNUNET_break (0);
298 return GNUNET_SYSERR;
299 }
300 if (0 != cfsetispeed (&tio, ispeed))
301 {
302 GNUNET_break (0);
303 return GNUNET_SYSERR;
304 }
305 params = (uint16_t *) &msg[1];
306 for (cnt = 0; cnt < ns; cnt++)
307 {
308 switch (ntohs (params[2 * cnt]))
309 {
310#define TTYCHAR(NAME,OP) \
311 case OP: \
312 tio.c_cc[NAME] = ntohs (params[(2 * cnt)+1]); \
313 break;
314#define TTYMODE(NAME, FIELD, OP) \
315 case OP: \
316 if (1 == ntohs (params[(2 * cnt)+1])) \
317 tio.FIELD |= NAME; \
318 break;
319#include "ttymodes.h"
320#undef TTYCHAR
321#undef TTYMODE
322
323 default:
324 GNUNET_assert (0);
325 }
326 }
327 if (0 == (msize - expected))
328 return GNUNET_OK;
329 cp = ((void *) msg) + expected;
330 if ('\0' != cp[(msize - expected) - 1])
331 {
332 GNUNET_break (0);
333 cp = NULL;
334 return GNUNET_OK;
335 }
336 cp = GNUNET_strdup (cp);
337 return GNUNET_OK;
338}
339
340
341/**
342 * Create pty, fork, setup the tty modes and exec the given binary
343 */
344static int
345spawn_child (const struct MSH_MSG_PtyMode *msg)
346{
347 const char *fn;
348 int master;
349 int ret;
350
351 LOG_DEBUG ("Creating PTY\n");
352 master = posix_openpt (O_RDWR);
353 if (-1 == master)
354 {
355 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "posix_openpt");
356 goto err_ret;
357 }
358 if (-1 == grantpt (master))
359 {
360 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "grantpt");
361 goto err_ret;
362 }
363 if (-1 == unlockpt (master))
364 {
365 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "unlockpt");
366 goto err_ret;
367 }
368 if (NULL == (fn = ptsname (master)))
369 {
370 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ptsname");
371 goto err_ret;
372 }
373 shc_chld =
374 GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death);
375 ret = fork();
376 if (-1 == ret)
377 {
378 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork");
379 goto err_ret;
380 }
381 if (0 != ret)
382 {
383 LOG_DEBUG ("Forked child successfully\n");
384 child_pid = ret;
385 chld_io = GNUNET_DISK_get_handle_from_int_fd (master);
386 read_child_task =
387 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io,
388 &read_child, NULL);
389 return GNUNET_OK;
390 }
391 /* child process */
392 {
393 int slave;
394
395 close (master);
396 LOG_DEBUG ("Opening slave PTY %s\n", fn);
397 slave = open (fn, O_RDWR);
398 if (-1 == slave)
399 {
400 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "open");
401 _exit (1);
402 }
403 if (-1 == tcgetattr (slave, &tio))
404 {
405 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "tcsetattr");
406 _exit (1);
407 }
408 parse_term_mode (msg);
409 if (-1 == tcsetattr (slave, TCSANOW, &tio))
410 {
411 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "tcsetattr");
412 //_exit (1); /* Ignore for now */
413 }
414 if (-1 == ioctl (slave, TIOCSWINSZ, &ws))
415 {
416 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ioctl");
417 _exit (1);
418 }
419 if (-1 == setsid ())
420 {
421 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "setsid");
422 _exit (1);
423 }
424 close (0);
425 close (1);
426 close (2);
427 if ( (-1 == dup2 (slave, 0)) ||
428 (-1 == dup2 (slave, 1)) ||
429 (-1 == dup2 (slave, 2)) )
430 {
431 //GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup"); //we won't get it
432 //at this point
433 _exit (2);
434 }
435 close (slave);
436 LOG_DEBUG ("Execing %s\n", chld_argv[0]);
437 if (-1 == execvp (chld_argv[0], chld_argv))
438 {
439 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "execvp");
440 _exit (1);
441 }
442 }
443
444 err_ret:
445 GNUNET_SCHEDULER_shutdown ();
446 return GNUNET_SYSERR;
447}
448
449
450/**
451 * Callback function for data received from the network. Note that
452 * both "available" and "err" would be 0 if the read simply timed out.
453 *
454 * @param cls closure
455 * @param buf pointer to received data
456 * @param available number of bytes availabe in "buf",
457 * possibly 0 (on errors)
458 * @param addr address of the sender
459 * @param addrlen size of addr
460 * @param errCode value of errno (on errors receiving)
461 */
462static void
463net_receive (void *cls, const void *buf, size_t available,
464 const struct sockaddr * addr, socklen_t addrlen, int errCode)
465{
466 static enum {
467 STATE_HEADER = 0,
468 STATE_MSG,
469 STATE_FORWARD
470 } state;
471 static size_t rb;
472 struct GNUNET_TIME_Relative timeout;
473 static struct MSH_MSG_PtyMode *msg;
474 size_t rsize;
475
476 in_receive = 0;
477 if (0 == available)
478 {
479 GNUNET_SCHEDULER_shutdown ();
480 return;
481 }
482 switch (state)
483 {
484 case STATE_HEADER:
485 {
486 static struct GNUNET_MessageHeader hdr;
487 uint16_t msize;
488
489 GNUNET_assert (available <= sizeof (hdr));
490 memcpy (((void *) &hdr) + rb, buf, available);
491 rb += available;
492 timeout = DEFAULT_TIMEOUT;
493 rsize = sizeof (hdr) - rb;
494 if (0 != rsize)
495 goto read_again;
496 if (MSH_MTYPE_PTY_MODE != ntohs (hdr.type))
497 {
498 GNUNET_break_op (0);
499 GNUNET_SCHEDULER_shutdown ();
500 return;
501 }
502 msize = ntohs (hdr.size);
503 msg = GNUNET_malloc (msize);
504 memcpy (msg, &hdr, sizeof (hdr));
505 rsize = msize - sizeof (hdr);
506 state = STATE_MSG;
507 goto read_again;
508 }
509 case STATE_MSG:
510 {
511 uint16_t msize;
512
513 msize = ntohs (msg->header.size);
514 GNUNET_assert (available <= (msize - rb));
515 memcpy (((void *) msg) + rb, buf, available);
516 rb += available;
517 timeout = DEFAULT_TIMEOUT;
518 rsize = msize - rb;
519 if (0 != rsize)
520 goto read_again;
521 spawn_child (msg);
522 GNUNET_free (msg);
523 msg = NULL;
524 state = STATE_FORWARD;
525 }
526 break;
527 case STATE_FORWARD:
528 /* receive from net and write to child pty */
529 {
530 size_t wb;
531 ssize_t ret;
532
533 wb = 0;
534 do {
535 ret = GNUNET_DISK_file_write (chld_io, buf + wb, available - wb);
536 if (GNUNET_SYSERR == ret)
537 {
538 GNUNET_break (0);
539 GNUNET_SCHEDULER_shutdown ();
540 return;
541 }
542 wb += ret;
543 } while (wb < available);
544 }
545 break;
546 }
547 rsize = BUF_SIZE;
548 timeout = GNUNET_TIME_UNIT_FOREVER_REL;
549
550 read_again:
551 GNUNET_CONNECTION_receive (conn, rsize, timeout, &net_receive, NULL);
552 in_receive = 1;
553}
554
555
556/**
557 * Main function that will be run by the scheduler.
558 *
559 * @param cls closure
560 * @param args remaining command-line arguments
561 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
562 * @param c configuration
563 */
564static void
565run (void *cls,
566 char *const *args,
567 const char *cfgfile,
568 const struct GNUNET_CONFIGURATION_Handle *c)
569{
570 unsigned int argc;
571 const char *hostname;
572 const char *portstr;
573 struct sockaddr *addr;
574 struct addrinfo *res;
575 struct addrinfo hints;
576 int af_family;
577 socklen_t addrlen;
578 int ret;
579
580 argc = 0;
581 hostname = args[argc++];
582 if (NULL == hostname)
583 {
584 GNUNET_break (0);
585 return;
586 }
587 portstr = args[argc++];
588 if (NULL == portstr)
589 {
590 GNUNET_break (0);
591 return;
592 }
593 if (NULL == args[argc])
594 {
595 GNUNET_break (0);
596 return;
597 }
598 (void) memset (&hints, 0, sizeof (hints));
599 hints.ai_flags = AI_NUMERICSERV;
600 hints.ai_family = AF_INET;
601 hints.ai_socktype = SOCK_STREAM;
602 ret = getaddrinfo (hostname, portstr, &hints, &res);
603 if (0 != ret)
604 {
605 LOG_ERROR ("getaddrinfo() failed: %s\n", gai_strerror (ret));
606 return;
607 }
608 if (NULL == res)
609 {
610 GNUNET_break (0);
611 return;
612 }
613 addrlen = res->ai_addrlen;
614 addr = GNUNET_malloc (addrlen);
615 memcpy (addr, res->ai_addr, addrlen);
616 af_family = res->ai_family;
617 freeaddrinfo (res);
618 conn = GNUNET_CONNECTION_create_from_sockaddr (af_family, addr, addrlen);
619 GNUNET_free (addr);
620 if (NULL == conn)
621 {
622 GNUNET_break (0);
623 return;
624 }
625 GNUNET_CONNECTION_receive (conn, sizeof (struct GNUNET_MessageHeader),
626 DEFAULT_TIMEOUT, &net_receive, NULL);
627 in_receive = 1;
628 child_death_task =
629 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
630 GNUNET_DISK_pipe_handle (sigpipe,
631 GNUNET_DISK_PIPE_END_READ),
632 &child_died, NULL);
633 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
634 &do_shutdown, NULL);
635 chld_argv = copy_argv (&args[argc]);
636 return;
637}
638
639
640int main(int argc, char *const argv[])
641{
642 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
643 GNUNET_GETOPT_OPTION_END
644 };
645 int ret;
646
647 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
648 return 2;
649 if (NULL == (sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO,
650 GNUNET_NO, GNUNET_NO)))
651 {
652 GNUNET_break (0);
653 return 1;
654 }
655 ret =
656 GNUNET_PROGRAM_run (argc, argv, "test-pty",
657 gettext_noop
658 ("msh-waiter test program"),
659 options, &run, NULL);
660 if (NULL != shc_chld)
661 GNUNET_SIGNAL_handler_uninstall (shc_chld);
662 GNUNET_DISK_pipe_close (sigpipe);
663 GNUNET_free ((void *) argv);
664 LOG_DEBUG ("Exiting\n");
665 return ret;
666}