From: Shuanglei Tao Date: Tue, 10 Dec 2019 16:09:54 +0000 (+0800) Subject: server: improve child process handling X-Git-Url: http://git.prime8.dev/?a=commitdiff_plain;h=b67e382ab88bcf9b3c56c8f2fa9fb0b962e41a46;p=ttyd.git server: improve child process handling --- diff --git a/src/protocol.c b/src/protocol.c index 4445b39..4594796 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -92,26 +93,22 @@ check_host_origin(struct lws *wsi) { } void -pss_tty_free(struct pss_tty *pss) { - uv_read_stop((uv_stream_t *) &pss->pipe); - uv_close((uv_handle_t*) &pss->pipe, NULL); - uv_signal_stop(&pss->watcher); +pty_proc_free(struct pty_proc *proc) { + uv_read_stop((uv_stream_t *) &proc->pipe); + uv_close((uv_handle_t*) &proc->pipe, NULL); - close(pss->pty); + close(proc->pty); - // free the buffer - if (pss->buffer != NULL) { - free(pss->buffer); - pss->buffer = NULL; - } - if (pss->pty_buffer != NULL) { - free(pss->pty_buffer); - pss->pty_buffer = NULL; + if (proc->pty_buffer != NULL) { + free(proc->pty_buffer); + proc->pty_buffer = NULL; } - for (int i = 0; i < pss->argc; i++) { - free(pss->args[i]); + for (int i = 0; i < proc->argc; i++) { + free(proc->args[i]); } + + free(proc); } void @@ -123,21 +120,22 @@ alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { struct pss_tty *pss = (struct pss_tty *) stream->data; - pss->pty_len = nread; + struct pty_proc *proc = pss->proc; + proc->pty_len = nread; uv_read_stop(stream); if (nread <= 0) { if (nread == UV_ENOBUFS || nread == 0) return; - pss->pty_buffer = NULL; + proc->pty_buffer = NULL; if (nread == UV_EOF) - pss->pty_len = 0; + proc->pty_len = 0; else lwsl_err("read_cb: %s\n", uv_err_name(nread)); } else { - pss->pty_buffer = xmalloc(LWS_PRE + 1 + (size_t ) nread); - memcpy(pss->pty_buffer + LWS_PRE + 1, buf->base, (size_t ) nread); + proc->pty_buffer = xmalloc(LWS_PRE + 1 + (size_t ) nread); + memcpy(proc->pty_buffer + LWS_PRE + 1, buf->base, (size_t ) nread); } free(buf->base); @@ -146,49 +144,67 @@ read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { void child_cb(uv_signal_t *handle, int signum) { - struct pss_tty *pss; pid_t pid; - int status; - - pss = (struct pss_tty *) handle->data; - status = wait_proc(pss->pid, &pid); - if (pid > 0) { - lwsl_notice("process exited with code %d, pid: %d\n", status, pid); - pss->pid = 0; - pss->pty_len = status > 0 ? -status : status; - pss_tty_free(pss); + int stat; + + struct pty_proc *proc; + LIST_HEAD(proc, pty_proc) *procs = handle->data; + LIST_FOREACH(proc, procs, entry) { + do + pid = waitpid(proc->pid, &stat, WNOHANG); + while (pid == -1 && errno == EINTR); + + if (pid <= 0) + continue; + + if (WIFEXITED(stat)) { + proc->status = WEXITSTATUS(stat); + lwsl_notice("process exited with code %d, pid: %d\n", proc->status, proc->pid); + } else if (WIFSIGNALED(stat)) { + int term_sig = WTERMSIG(stat); + proc->status = 128 + term_sig; + lwsl_notice("process killed with signal %d, pid: %d\n", term_sig, proc->pid); + } + + LIST_REMOVE(proc, entry); + if (proc->state == STATE_KILL) { + pty_proc_free(proc); + } else { + proc->state = STATE_EXIT; + } } } int spawn_process(struct pss_tty *pss) { + struct pty_proc *proc = pss->proc; // append url args to arguments - char *argv[server->argc + pss->argc + 1]; + char *argv[server->argc + proc->argc + 1]; int i, n = 0; for (i = 0; i < server->argc; i++) { argv[n++] = server->argv[i]; } - for (i = 0; i < pss->argc; i++) { - argv[n++] = pss->args[i]; + for (i = 0; i < proc->argc; i++) { + argv[n++] = proc->args[i]; } argv[n] = NULL; - uv_signal_start(&pss->watcher, child_cb, SIGCHLD); + uv_signal_start(&server->watcher, child_cb, SIGCHLD); // ensure the lws socket fd close-on-exec fd_set_cloexec(lws_get_socket_fd(pss->wsi)); // create process with pseudo-tty - pss->pid = pty_fork(&pss->pty, argv[0], argv, server->terminal_type); - if (pss->pid < 0) { + proc->pid = pty_fork(&proc->pty, argv[0], argv, server->terminal_type); + if (proc->pid < 0) { lwsl_err("pty_fork: %d (%s)\n", errno, strerror(errno)); return 1; } - lwsl_notice("started process, pid: %d\n", pss->pid); + lwsl_notice("started process, pid: %d\n", proc->pid); - pss->pipe.data = pss; - uv_pipe_open(&pss->pipe, pss->pty); + proc->pipe.data = pss; + uv_pipe_open(&proc->pipe, proc->pty); lws_callback_on_writable(pss->wsi); @@ -198,9 +214,7 @@ spawn_process(struct pss_tty *pss) { void kill_process(pid_t pid, int sig) { if (pid <= 0) return; - - // kill process (group) and free resource - lwsl_notice("killing process %d with signal %d\n", pid, sig); + lwsl_notice("killing process %d with signal: %d\n", pid, sig); int pgid = getpgid(pid); if (kill(pgid > 0 ? -pgid : pid, sig) != 0) { lwsl_err("kill: %d, errno: %d (%s)\n", pid, errno, strerror(errno)); @@ -211,6 +225,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss_tty *pss = (struct pss_tty *) user; + struct pty_proc *proc; char buf[256]; size_t n = 0; @@ -241,24 +256,24 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, pss->authenticated = false; pss->wsi = wsi; pss->buffer = NULL; - pss->pty_len = 0; - pss->argc = 0; - pss->loop = server->loop; - uv_pipe_init(pss->loop, &pss->pipe, 0); - uv_signal_init(pss->loop, &pss->watcher); - pss->watcher.data = pss; + pss->proc = proc = xmalloc(sizeof(struct pty_proc)); + memset(proc, 0, sizeof(struct pty_proc)); + proc->status = -1; + proc->state = STATE_INIT; + uv_pipe_init(server->loop, &proc->pipe, 0); if (server->url_arg) { while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) { if (strncmp(buf, "arg=", 4) == 0) { - pss->args = xrealloc(pss->args, (pss->argc + 1) * sizeof(char *)); - pss->args[pss->argc] = strdup(&buf[4]); - pss->argc++; + proc->args = xrealloc(proc->args, (proc->argc + 1) * sizeof(char *)); + proc->args[proc->argc] = strdup(&buf[4]); + proc->argc++; } } } + LIST_INSERT_HEAD(&server->procs, proc, entry); server->client_count++; lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI); @@ -273,10 +288,11 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_SERVER_WRITEABLE: + proc = pss->proc; if (!pss->initialized) { if (pss->initial_cmd_index == sizeof(initial_cmds)) { pss->initialized = true; - uv_read_start((uv_stream_t *)& pss->pipe, alloc_cb, read_cb); + uv_read_start((uv_stream_t *)& proc->pipe, alloc_cb, read_cb); break; } if (send_initial_message(wsi, pss->initial_cmd_index) < 0) { @@ -290,25 +306,25 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, } // read error or client exited, close connection - if (pss->pty_len == 0) { + if (proc->status == 0 || proc->pty_len == 0) { lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, NULL, 0); return 1; - } else if (pss->pty_len < 0) { + } else if (proc->status > 0 || proc->pty_len < 0) { lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, NULL, 0); return -1; } - if (pss->pty_buffer == NULL) + if (proc->pty_buffer == NULL) break; - pss->pty_buffer[LWS_PRE] = OUTPUT; - n = (size_t) (pss->pty_len + 1); - if (lws_write(wsi, (unsigned char *) pss->pty_buffer + LWS_PRE, n, LWS_WRITE_BINARY) < n) { + proc->pty_buffer[LWS_PRE] = OUTPUT; + n = (size_t) (proc->pty_len + 1); + if (lws_write(wsi, (unsigned char *) proc->pty_buffer + LWS_PRE, n, LWS_WRITE_BINARY) < n) { lwsl_err("write OUTPUT to WS\n"); } - free(pss->pty_buffer); - pss->pty_buffer = NULL; - uv_read_start((uv_stream_t *)& pss->pipe, alloc_cb, read_cb); + free(proc->pty_buffer); + proc->pty_buffer = NULL; + uv_read_start((uv_stream_t *)& proc->pipe, alloc_cb, read_cb); break; case LWS_CALLBACK_RECEIVE: @@ -335,14 +351,15 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, return 0; } + proc = pss->proc; switch (command) { case INPUT: - if (pss->pty == 0) + if (proc->pty == 0) break; if (server->readonly) return 0; uv_buf_t b = { pss->buffer + 1, pss->len - 1 }; - int err = uv_try_write((uv_stream_t *) &pss->pipe, &b, 1); + int err = uv_try_write((uv_stream_t *) &proc->pipe, &b, 1); if (err < 0) { lwsl_err("uv_try_write: %s\n", uv_err_name(err)); return -1; @@ -352,14 +369,14 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, { int cols, rows; if (parse_window_size(pss->buffer + 1, &cols, &rows)) { - if (pty_resize(pss->pty, cols, rows) < 0) { + if (pty_resize(proc->pty, cols, rows) < 0) { lwsl_err("pty_resize: %d (%s)\n", errno, strerror(errno)); } } } break; case JSON_DATA: - if (pss->pid > 0) + if (proc->pid > 0) break; if (server->credential != NULL) { json_object *obj = json_tokener_parse(pss->buffer); @@ -392,8 +409,19 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CLOSED: server->client_count--; lwsl_notice("WS closed from %s, clients: %d\n", pss->address, server->client_count); - uv_read_stop((uv_stream_t *) &pss->pipe); - kill_process(pss->pid, server->sig_code); + if (pss->buffer != NULL) { + free(pss->buffer); + } + + proc = pss->proc; + if (proc->state == STATE_EXIT) { + pty_proc_free(proc); + } else { + proc->state = STATE_KILL; + uv_read_stop((uv_stream_t *) &proc->pipe); + kill_process(proc->pid, server->sig_code); + } + if (server->once && server->client_count == 0) { lwsl_notice("exiting due to the --once option.\n"); force_exit = true; diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..daf4553 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,574 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * A singly-linked list is headed by a single forward pointer. The + * elements are singly linked for minimum space and pointer manipulation + * overhead at the expense of O(n) removal for arbitrary elements. New + * elements can be added to the list after an existing element or at the + * head of the list. Elements being removed from the head of the list + * should use the explicit macro for this purpose for optimum + * efficiency. A singly-linked list may only be traversed in the forward + * direction. Singly-linked lists are ideal for applications with large + * datasets and few or no removals or for implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + (head)->lh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (/*CONSTCOND*/0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = ((head)->lh_first); \ + (var); \ + (var) = ((var)->field.le_next)) + +/* + * List access methods. + */ +#define LIST_EMPTY(head) ((head)->lh_first == NULL) +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) do { \ + (head)->slh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while(curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) + +/* + * Singly-linked List access methods. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_INIT(head) do { \ + (head)->stqh_first = NULL; \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (head)->stqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (listelm)->field.stqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + if ((curelm->field.stqe_next = \ + curelm->field.stqe_next->field.stqe_next) == NULL) \ + (head)->stqh_last = &(curelm)->field.stqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->stqh_first); \ + (var); \ + (var) = ((var)->field.stqe_next)) + +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Singly-linked Tail queue access methods. + */ +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) +#define STAILQ_FIRST(head) ((head)->stqh_first) +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ + if ((head)->sqh_first == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->sqh_first; \ + while (curelm->field.sqe_next != (elm)) \ + curelm = curelm->field.sqe_next; \ + if ((curelm->field.sqe_next = \ + curelm->field.sqe_next->field.sqe_next) == NULL) \ + (head)->sqh_last = &(curelm)->field.sqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->sqh_first); \ + (var); \ + (var) = ((var)->field.sqe_next)) + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + + +/* + * Tail queue definitions. + */ +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ + (var); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Tail queue access methods. + */ +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { (void *)&head, (void *)&head } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = (void *)(head); \ + (head)->cqh_last = (void *)(head); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = (void *)(head); \ + if ((head)->cqh_last == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = (void *)(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->cqh_first); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_next)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for ((var) = ((head)->cqh_last); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_prev)) + +/* + * Circular queue access methods. + */ +#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) + +#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ + (((elm)->field.cqe_next == (void *)(head)) \ + ? ((head)->cqh_first) \ + : (elm->field.cqe_next)) +#define CIRCLEQ_LOOP_PREV(head, elm, field) \ + (((elm)->field.cqe_prev == (void *)(head)) \ + ? ((head)->cqh_last) \ + : (elm->field.cqe_prev)) + +#endif /* sys/queue.h */ diff --git a/src/server.c b/src/server.c index 05bc5d4..7453044 100644 --- a/src/server.c +++ b/src/server.c @@ -109,6 +109,7 @@ server_new(int argc, char **argv, int start) { ts = xmalloc(sizeof(struct server)); memset(ts, 0, sizeof(struct server)); + LIST_INIT(&ts->procs); ts->client_count = 0; ts->sig_code = SIGHUP; sprintf(ts->terminal_type, "%s", "xterm-256color"); @@ -141,6 +142,8 @@ server_new(int argc, char **argv, int start) { ts->loop = xmalloc(sizeof *ts->loop); uv_loop_init(ts->loop); + uv_signal_init(ts->loop, &ts->watcher); + ts->watcher.data = &ts->procs; return ts; } @@ -166,6 +169,7 @@ server_free(struct server *ts) { unlink(ts->socket_path); } } + uv_signal_stop(&ts->watcher); uv_loop_close(ts->loop); free(ts->loop); free(ts); diff --git a/src/server.h b/src/server.h index 42e9a29..c032772 100644 --- a/src/server.h +++ b/src/server.h @@ -1,6 +1,8 @@ #include #include +#include "queue.h" + // client message #define INPUT '0' #define RESIZE_TERMINAL '1' @@ -18,6 +20,10 @@ extern volatile bool force_exit; extern struct lws_context *context; extern struct server *server; +typedef enum { + STATE_INIT, STATE_KILL, STATE_EXIT +} proc_state; + struct pss_http { char path[128]; char *buffer; @@ -25,26 +31,34 @@ struct pss_http { size_t len; }; +struct pty_proc { + char **args; + int argc; + + pid_t pid; + int status; + proc_state state; + + int pty; + char *pty_buffer; + ssize_t pty_len; + + uv_pipe_t pipe; + + LIST_ENTRY(pty_proc) entry; +}; + struct pss_tty { bool initialized; int initial_cmd_index; bool authenticated; char address[50]; - char **args; - int argc; struct lws *wsi; char *buffer; size_t len; - pid_t pid; - int pty; - char *pty_buffer; - ssize_t pty_len; - - uv_loop_t *loop; - uv_pipe_t pipe; - uv_signal_t watcher; + struct pty_proc *proc; }; struct server { @@ -64,7 +78,11 @@ struct server { bool once; // whether accept only one client and exit on disconnection char socket_path[255]; // UNIX domain socket path char terminal_type[30]; // terminal type to report + uv_loop_t *loop; // the libuv event loop + uv_signal_t watcher; // SIGCHLD watcher + + LIST_HEAD(proc, pty_proc) procs; // started process list }; extern int diff --git a/src/utils.c b/src/utils.c index 4348f6f..bcb92bf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -4,9 +4,7 @@ #include #include #include -#include #include -#include #ifdef __linux__ // https://github.com/karelzak/util-linux/blob/master/misc-utils/kill.c @@ -94,22 +92,6 @@ fd_set_cloexec(const int fd) { return (flags & FD_CLOEXEC) == 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1; } -int -wait_proc(pid_t in, pid_t *out) { - int stat = 0, pid; - do { - pid = waitpid(in, &stat, WNOHANG); - } while (pid < 0 && errno == EINTR); - if (out != NULL) *out = pid; - int status = -1; - if (WIFEXITED(stat)) { - status = WEXITSTATUS(stat); - } else if (WIFSIGNALED(stat)) { - status = WTERMSIG(stat); - } - return status; -} - int open_uri(char *uri) { #ifdef __APPLE__ diff --git a/src/utils.h b/src/utils.h index 42477d3..a02d0c2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -29,10 +29,6 @@ get_sig(const char *sig_name); bool fd_set_cloexec(const int fd); -// waitpid with WNOHANG and return the status -int -wait_proc(pid_t in, pid_t *out); - // Open uri with the default application of system int open_uri(char *uri);