#include "common.h" #include #include #include "mtypes.h" /****************************************************************************/ /* Implemented after http://rachid.koucha.free.fr/tech_corner/pty_pdip.html */ /****************************************************************************/ #define LOG(kind,...) \ GNUNET_log (kind, __VA_ARGS__) #define LOG_DEBUG(...) LOG(GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) #define LOG_ERROR(...) LOG(GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) #define TIMEOUT(secs) \ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, secs) #define DEFAULT_TIMEOUT TIMEOUT(30) #define BUF_SIZE 512 /** * handler for SIGCHLD.l Register it just before forking */ static struct GNUNET_SIGNAL_Context *shc_chld; /** * Task to kill the child */ static GNUNET_SCHEDULER_TaskIdentifier child_death_task; /** * Task to read output from child */ static GNUNET_SCHEDULER_TaskIdentifier read_child_task; /** * Pipe used to communicate shutdown via signal. */ static struct GNUNET_DISK_PipeHandle *sigpipe; static struct GNUNET_DISK_FileHandle *chld_io; static struct GNUNET_CONNECTION_Handle *conn; static struct GNUNET_CONNECTION_TransmitHandle *tx; static char **chld_argv; static char *cp; static struct termios tio; static struct winsize ws; static int in_receive; static char buf[BUF_SIZE]; static size_t buf_off; /** * The process id of the child */ static pid_t child_pid; /** * Function to copy NULL terminated list of arguments * * @param argv the NULL terminated list of arguments. Cannot be NULL. * @return the copied NULL terminated arguments */ static char ** copy_argv (char *const *argv) { char **argv_dup; unsigned int argp; GNUNET_assert (NULL != argv); for (argp = 0; NULL != argv[argp]; argp++) ; argv_dup = GNUNET_malloc (sizeof (char *) * (argp + 1)); for (argp = 0; NULL != argv[argp]; argp++) argv_dup[argp] = strdup (argv[argp]); return argv_dup; } /** * Frees the given NULL terminated arguments * * @param argv the NULL terminated list of arguments */ static void free_argv (char **argv) { unsigned int argp; for (argp = 0; NULL != argv[argp]; argp++) GNUNET_free (argv[argp]); GNUNET_free (argv); } /** * shutdown task */ static void do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { if (NULL != chld_argv) { free_argv (chld_argv); chld_argv = NULL; } if (NULL != conn) { if (in_receive) GNUNET_CONNECTION_receive_cancel (conn); if (NULL != tx) GNUNET_CONNECTION_notify_transmit_ready_cancel (tx); GNUNET_CONNECTION_destroy (conn); conn = NULL; } if (0 != child_pid) { GNUNET_break (0 == kill (child_pid, SIGTERM)); LOG_DEBUG ("Child still running. Delaying shutdown\n"); (void) GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown, NULL); return; } if (GNUNET_SCHEDULER_NO_TASK != child_death_task) { GNUNET_SCHEDULER_cancel (child_death_task); child_death_task = GNUNET_SCHEDULER_NO_TASK; } if (NULL != chld_io) GNUNET_DISK_file_close (chld_io); } /** * Read the child processes output and send it to over network connection to waiter */ static void read_child (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); /** * Function called to notify a client about the connection * begin ready to queue more data. "buf" will be * NULL and "size" zero if the connection was closed for * writing in the meantime. * * @param cls closure * @param size number of bytes available in buf * @param netbuf where the callee should write the message * @return number of bytes written to buf */ static size_t do_tx (void *cls, size_t size, void *netbuf) { tx = NULL; if ((NULL == buf) || (0 == size)) { LOG_ERROR ("Failure in connectivity\n"); GNUNET_SCHEDULER_shutdown (); return 0; } GNUNET_assert (buf_off <= size); (void) memcpy (netbuf, buf, buf_off); size = buf_off; buf_off = 0; if (GNUNET_SCHEDULER_NO_TASK == read_child_task) read_child_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io, &read_child, NULL); return size; } /** * Read the child processes output and send it to over network connection to waiter */ static void read_child (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { ssize_t rsize; read_child_task = GNUNET_SCHEDULER_NO_TASK; if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) return; if (BUF_SIZE == buf_off) { GNUNET_assert (NULL != tx); return; } rsize = GNUNET_DISK_file_read (chld_io, buf + buf_off, BUF_SIZE - buf_off); if (rsize <= 0) { LOG_DEBUG ("Child stdout closed\n"); return; } buf_off += rsize; if (NULL != tx) GNUNET_CONNECTION_notify_transmit_ready_cancel (tx); tx = GNUNET_CONNECTION_notify_transmit_ready (conn, buf_off, DEFAULT_TIMEOUT, &do_tx, NULL); if (BUF_SIZE == buf_off) return; read_child_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io, &read_child, NULL); } /** * Task triggered whenever we receive a SIGCHLD (child * process died). * * @param cls closure, NULL if we need to self-restart * @param tc context */ static void child_died (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { const struct GNUNET_DISK_FileHandle *pr; char c[16]; pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); child_death_task = GNUNET_SCHEDULER_NO_TASK; if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY)) { child_death_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, pr, &child_died, NULL); return; } /* consume the signal */ GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); LOG_DEBUG ("Child died\n"); child_pid = 0; GNUNET_SCHEDULER_shutdown (); /* FIXME: read any of the left over output from child? */ } /** * Signal handler called for SIGCHLD. */ static void sighandler_child_death () { static char c; int old_errno = errno; /* back-up errno */ GNUNET_break (1 == GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_WRITE), &c, sizeof (c))); errno = old_errno; /* restore errno */ } /** * Configure the terminal for the child */ static int parse_term_mode (const struct MSH_MSG_PtyMode *msg) { uint16_t *params; unsigned int cnt; unsigned int ns; uint16_t msize; size_t expected; speed_t ospeed; speed_t ispeed; msize = ntohs (msg->header.size); ns = ntohs (msg->nsettings); expected = (sizeof (struct MSH_MSG_PtyMode) + (sizeof (uint16_t) * ns * 2)); if (msize < expected) { GNUNET_break (0); return GNUNET_SYSERR; } #define PARSE_WIN_SIZE(field) \ ws.field = ntohs (msg->field); PARSE_WIN_SIZE (ws_row); PARSE_WIN_SIZE (ws_col); PARSE_WIN_SIZE (ws_xpixel); PARSE_WIN_SIZE (ws_ypixel); #undef PARSE_WIN_SIZE ospeed = baud_to_speed (ntohl (msg->ospeed)); ispeed = baud_to_speed (ntohl (msg->ispeed)); if (0 != cfsetospeed (&tio, ospeed)) { GNUNET_break (0); return GNUNET_SYSERR; } if (0 != cfsetispeed (&tio, ispeed)) { GNUNET_break (0); return GNUNET_SYSERR; } params = (uint16_t *) &msg[1]; for (cnt = 0; cnt < ns; cnt++) { switch (ntohs (params[2 * cnt])) { #define TTYCHAR(NAME,OP) \ case OP: \ tio.c_cc[NAME] = ntohs (params[(2 * cnt)+1]); \ break; #define TTYMODE(NAME, FIELD, OP) \ case OP: \ if (1 == ntohs (params[(2 * cnt)+1])) \ tio.FIELD |= NAME; \ break; #include "ttymodes.h" #undef TTYCHAR #undef TTYMODE default: GNUNET_assert (0); } } if (0 == (msize - expected)) return GNUNET_OK; cp = ((void *) msg) + expected; if ('\0' != cp[(msize - expected) - 1]) { GNUNET_break (0); cp = NULL; return GNUNET_OK; } cp = GNUNET_strdup (cp); return GNUNET_OK; } /** * Create pty, fork, setup the tty modes and exec the given binary */ static int spawn_child (const struct MSH_MSG_PtyMode *msg) { const char *fn; int master; int ret; LOG_DEBUG ("Creating PTY\n"); master = posix_openpt (O_RDWR); if (-1 == master) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "posix_openpt"); goto err_ret; } if (-1 == grantpt (master)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "grantpt"); goto err_ret; } if (-1 == unlockpt (master)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "unlockpt"); goto err_ret; } if (NULL == (fn = ptsname (master))) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ptsname"); goto err_ret; } shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death); ret = fork(); if (-1 == ret) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork"); goto err_ret; } if (0 != ret) { LOG_DEBUG ("Forked child successfully\n"); child_pid = ret; chld_io = GNUNET_DISK_get_handle_from_int_fd (master); read_child_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, chld_io, &read_child, NULL); return GNUNET_OK; } /* child process */ { int slave; close (master); LOG_DEBUG ("Opening slave PTY %s\n", fn); slave = open (fn, O_RDWR); if (-1 == slave) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "open"); _exit (1); } if (-1 == tcgetattr (slave, &tio)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "tcsetattr"); _exit (1); } parse_term_mode (msg); if (-1 == tcsetattr (slave, TCSANOW, &tio)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "tcsetattr"); //_exit (1); /* Ignore for now */ } if (-1 == ioctl (slave, TIOCSWINSZ, &ws)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ioctl"); _exit (1); } if (-1 == setsid ()) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "setsid"); _exit (1); } close (0); close (1); close (2); if ( (-1 == dup2 (slave, 0)) || (-1 == dup2 (slave, 1)) || (-1 == dup2 (slave, 2)) ) { //GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "dup"); //we won't get it //at this point _exit (2); } close (slave); LOG_DEBUG ("Execing %s\n", chld_argv[0]); if (-1 == execvp (chld_argv[0], chld_argv)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "execvp"); _exit (1); } } err_ret: GNUNET_SCHEDULER_shutdown (); return GNUNET_SYSERR; } /** * Callback function for data received from the network. Note that * both "available" and "err" would be 0 if the read simply timed out. * * @param cls closure * @param buf pointer to received data * @param available number of bytes availabe in "buf", * possibly 0 (on errors) * @param addr address of the sender * @param addrlen size of addr * @param errCode value of errno (on errors receiving) */ static void net_receive (void *cls, const void *buf, size_t available, const struct sockaddr * addr, socklen_t addrlen, int errCode) { static enum { STATE_HEADER = 0, STATE_MSG, STATE_FORWARD } state; static size_t rb; struct GNUNET_TIME_Relative timeout; static struct MSH_MSG_PtyMode *msg; size_t rsize; in_receive = 0; if (0 == available) { GNUNET_SCHEDULER_shutdown (); return; } switch (state) { case STATE_HEADER: { static struct GNUNET_MessageHeader hdr; uint16_t msize; GNUNET_assert (available <= sizeof (hdr)); memcpy (((void *) &hdr) + rb, buf, available); rb += available; timeout = DEFAULT_TIMEOUT; rsize = sizeof (hdr) - rb; if (0 != rsize) goto read_again; if (MSH_MTYPE_PTY_MODE != ntohs (hdr.type)) { GNUNET_break_op (0); GNUNET_SCHEDULER_shutdown (); return; } msize = ntohs (hdr.size); msg = GNUNET_malloc (msize); memcpy (msg, &hdr, sizeof (hdr)); rsize = msize - sizeof (hdr); state = STATE_MSG; goto read_again; } case STATE_MSG: { uint16_t msize; msize = ntohs (msg->header.size); GNUNET_assert (available <= (msize - rb)); memcpy (((void *) msg) + rb, buf, available); rb += available; timeout = DEFAULT_TIMEOUT; rsize = msize - rb; if (0 != rsize) goto read_again; spawn_child (msg); GNUNET_free (msg); msg = NULL; state = STATE_FORWARD; } break; case STATE_FORWARD: /* receive from net and write to child pty */ { size_t wb; ssize_t ret; wb = 0; do { ret = GNUNET_DISK_file_write (chld_io, buf + wb, available - wb); if (GNUNET_SYSERR == ret) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } wb += ret; } while (wb < available); } break; } rsize = BUF_SIZE; timeout = GNUNET_TIME_UNIT_FOREVER_REL; read_again: GNUNET_CONNECTION_receive (conn, rsize, timeout, &net_receive, NULL); in_receive = 1; } /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param c configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { unsigned int argc; const char *hostname; const char *portstr; struct sockaddr *addr; struct addrinfo *res; struct addrinfo hints; int af_family; socklen_t addrlen; int ret; argc = 0; hostname = args[argc++]; if (NULL == hostname) { GNUNET_break (0); return; } portstr = args[argc++]; if (NULL == portstr) { GNUNET_break (0); return; } if (NULL == args[argc]) { GNUNET_break (0); return; } (void) memset (&hints, 0, sizeof (hints)); hints.ai_flags = AI_NUMERICSERV; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo (hostname, portstr, &hints, &res); if (0 != ret) { LOG_ERROR ("getaddrinfo() failed: %s\n", gai_strerror (ret)); return; } if (NULL == res) { GNUNET_break (0); return; } addrlen = res->ai_addrlen; addr = GNUNET_malloc (addrlen); memcpy (addr, res->ai_addr, addrlen); af_family = res->ai_family; freeaddrinfo (res); conn = GNUNET_CONNECTION_create_from_sockaddr (af_family, addr, addrlen); GNUNET_free (addr); if (NULL == conn) { GNUNET_break (0); return; } GNUNET_CONNECTION_receive (conn, sizeof (struct GNUNET_MessageHeader), DEFAULT_TIMEOUT, &net_receive, NULL); in_receive = 1; child_death_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ), &child_died, NULL); GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown, NULL); chld_argv = copy_argv (&args[argc]); return; } int main(int argc, char *const argv[]) { static const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; int ret; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; if (NULL == (sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO))) { GNUNET_break (0); return 1; } ret = GNUNET_PROGRAM_run (argc, argv, "test-pty", gettext_noop ("msh-waiter test program"), options, &run, NULL); if (NULL != shc_chld) GNUNET_SIGNAL_handler_uninstall (shc_chld); GNUNET_DISK_pipe_close (sigpipe); GNUNET_free ((void *) argv); LOG_DEBUG ("Exiting\n"); return ret; }